From b5c0ec11788590e6219895105d7c0e5410c06a7e Mon Sep 17 00:00:00 2001 From: HeuJung Date: Thu, 6 Aug 2026 09:01:00 +0900 Subject: [PATCH 1/2] =?UTF-8?q?fix(core,ecommerce,board,page,admin=5Fbasic?= =?UTF-8?q?,basic):=20=EB=AA=A9=EB=A1=9D=20=EC=9D=91=EB=8B=B5=EC=9D=98=20?= =?UTF-8?q?=ED=95=98=EC=9C=84=20=EC=BB=AC=EB=A0=89=EC=85=98=20=EC=A0=84?= =?UTF-8?q?=EB=9F=89=20=EC=A7=81=EB=A0=AC=ED=99=94=20=ED=95=B4=EC=86=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 관리자 상품목록을 한 페이지 여는 것만으로 그 페이지 모든 상품의 옵션이 응답에 실렸다. 같은 패턴을 저장소 전역에서 찾아 14개 목록 엔드포인트를 함께 정리했다. 근본 원인은 둘이다. Resource 가 whenLoaded 로 방어하는데 Repository 가 목록 쿼리에서 관계를 무조건 로드해 가드가 항상 참이 되는 가짜 가드, 그리고 toListArray 경량 표현을 정의해 두고도 컬렉션이 toArray 를 부르는 목록/상세 미분리다. 둘 다 응답만 보면 정상이라 오류도 경고도 없이 페이로드만 불어난다. 목록은 화면이 실제로 그리는 것만 싣는다. 개수·합계는 PHP 컬렉션 연산이 아니라 DB 집계로, 대표 1건이 필요한 곳은 관계 자체를 oldestOfMany 로 좁힌다. eager load 의 limit(1) 은 부모별이 아니라 배치 쿼리 전체에 걸려 첫 행만 값을 갖게 되므로 쓸 수 없다. 뺀 값에는 대체 경로를 먼저 만들었다. 상품 옵션은 행을 펼칠 때 배치로 불러오고(상품 수와 무관하게 쿼리 상수), 종전 동작이 필요한 호출자를 위해 ?with_options=1 등 opt-in 을 남겼다. 배송정책 국가설정과 리뷰 첨부 이미지는 소비처를 실측한 결과 화면이 실제로 그리고 있어 제거하지 않았다 — 그 소비 사실을 회귀 테스트로 고정했다. 재발 방지로 정적 검사 룰 4종과 규정 문서 항목을 함께 넣었다. --- AGENTS.md | 18 + CHANGELOG.md | 7 + ...outExtensionVersionRepositoryInterface.php | 15 +- .../LayoutRepositoryInterface.php | 8 +- .../Api/Admin/LayoutController.php | 21 +- .../Api/Admin/LayoutExtensionController.php | 23 +- .../Admin/LayoutVersionListRequest.php | 61 + .../NotificationDefinitionIndexRequest.php | 5 + .../IdentityMessageDefinitionResource.php | 23 +- app/Http/Resources/BaseApiResource.php | 73 + .../Resources/LayoutExtensionResource.php | 50 + .../LayoutExtensionVersionResource.php | 27 + app/Http/Resources/LayoutVersionResource.php | 33 +- app/Http/Resources/MenuResource.php | 17 +- .../NotificationDefinitionResource.php | 23 +- app/Http/Resources/RoleResource.php | 8 + app/Models/IdentityMessageDefinition.php | 23 +- .../Concerns/PaginatesWithDeferredJoin.php | 10 +- .../IdentityMessageDefinitionRepository.php | 6 +- .../LayoutExtensionVersionRepository.php | 35 +- app/Repositories/LayoutRepository.php | 32 +- app/Repositories/MenuRepository.php | 16 +- .../NotificationDefinitionRepository.php | 23 +- app/Repositories/RoleRepository.php | 14 +- app/Services/LayoutExtensionService.php | 15 +- app/Services/LayoutService.php | 10 +- app/Support/ApiDoc/ApiDocScaffolder.php | 492 +- docs/backend/api-documentation.md | 30 +- docs/backend/api-resources.md | 76 + docs/backend/api/README.md | 7 +- docs/backend/api/activity-logs.md | 114 +- docs/backend/api/attachment.md | 4 +- docs/backend/api/attachments.md | 20 +- docs/backend/api/auth.md | 2282 +--- docs/backend/api/avatar.md | 10 +- docs/backend/api/broadcasting.md | 6 +- docs/backend/api/changelog.md | 6 +- docs/backend/api/core-update.md | 1534 +-- docs/backend/api/dashboard.md | 170 +- docs/backend/api/extensions.md | 16 +- docs/backend/api/identity.md | 500 +- docs/backend/api/language-packs.md | 252 +- docs/backend/api/layouts.md | 85 +- docs/backend/api/license.md | 4 +- docs/backend/api/locales.md | 14 +- docs/backend/api/me.md | 112 +- docs/backend/api/menus.md | 737 +- docs/backend/api/modules.md | 11137 +--------------- docs/backend/api/notification-channels.md | 4 +- docs/backend/api/notification-definitions.md | 608 +- docs/backend/api/notification-logs.md | 80 +- docs/backend/api/notification-templates.md | 113 +- docs/backend/api/notifications.md | 190 +- docs/backend/api/password.md | 6 +- docs/backend/api/permissions.md | 4513 +------ docs/backend/api/plugins.md | 672 +- docs/backend/api/profile.md | 14 +- docs/backend/api/roles.md | 9023 +------------ docs/backend/api/schedules.md | 128 +- docs/backend/api/search.md | 6 +- docs/backend/api/seo.md | 45 +- docs/backend/api/settings.md | 417 +- docs/backend/api/system.md | 88 + docs/backend/api/templates.md | 1671 ++- docs/backend/api/users.md | 742 +- docs/backend/api/verify-password.md | 8 +- docs/backend/service-repository.md | 37 + docs/frontend/data-sources.md | 20 + lang-packs/_bundled/g7-core-ja/CHANGELOG.md | 2 + .../g7-core-ja/backend/ja/validation.php | 10 +- .../frontend/partial/layout_editor.json | 3 +- .../CHANGELOG.md | 3 + .../backend/ja/messages.php | 2 + .../backend/ja/validation.php | 13 + .../frontend/partial/admin/product.json | 8 +- .../CHANGELOG.md | 1 + .../frontend/partial/admin.json | 1 + lang/en/validation.php | 9 + lang/ko/validation.php | 9 + lang/partial/en/layout_editor.json | 3 +- lang/partial/ko/layout_editor.json | 3 +- modules/_bundled/sirsoft-board/CHANGELOG.md | 2 + .../_bundled/sirsoft-board/docs/api/boards.md | 127 +- ...notification-channel-single-source.test.ts | 190 + .../layouts/admin/admin_board_settings.json | 4 +- .../_tab_notification_definitions.json | 35 +- .../src/Http/Resources/BoardCollection.php | 7 +- .../src/Http/Resources/BoardResource.php | 29 + .../Admin/AdminBoardListPayloadTest.php | 115 + .../Feature/Admin/BoardManagementTest.php | 182 + .../tests/Feature/BoardCloneTest.php | 12 +- .../board-admin-list-role-payload.spec.ts | 148 + ...rd-notification-definition-channel.spec.ts | 188 + ...board-admin-list-role-payload-pruning.yaml | 50 + ...ification-definition-channel-contract.yaml | 45 + .../_bundled/sirsoft-ecommerce/CHANGELOG.md | 9 + .../sirsoft-ecommerce/components.json | 2 +- .../sirsoft-ecommerce/dist/js/module.iife.js | 3 +- .../sirsoft-ecommerce/docs/api/README.md | 4 +- .../sirsoft-ecommerce/docs/api/addresses.md | 192 +- .../sirsoft-ecommerce/docs/api/brands.md | 214 +- .../sirsoft-ecommerce/docs/api/cart.md | 120 +- .../sirsoft-ecommerce/docs/api/categories.md | 440 +- .../docs/api/category-image.md | 4 +- .../sirsoft-ecommerce/docs/api/checkout.md | 12 +- .../docs/api/claim-reasons.md | 266 +- .../sirsoft-ecommerce/docs/api/coupons.md | 311 +- .../sirsoft-ecommerce/docs/api/currency.md | 6 +- .../sirsoft-ecommerce/docs/api/dashboard.md | 87 +- .../docs/api/extra-fee-templates.md | 405 +- .../sirsoft-ecommerce/docs/api/guest.md | 14 +- .../sirsoft-ecommerce/docs/api/inquiries.md | 149 +- .../docs/api/mileage-transactions.md | 187 +- .../sirsoft-ecommerce/docs/api/mileage.md | 111 +- .../sirsoft-ecommerce/docs/api/options.md | 10 +- .../sirsoft-ecommerce/docs/api/orders.md | 1182 +- .../sirsoft-ecommerce/docs/api/payments.md | 4 +- .../sirsoft-ecommerce/docs/api/presets.md | 12 +- .../docs/api/product-common-infos.md | 229 +- .../docs/api/product-image.md | 4 +- .../docs/api/product-labels.md | 179 +- .../docs/api/product-notice-templates.md | 366 +- .../sirsoft-ecommerce/docs/api/products.md | 2811 +--- .../docs/api/promotion-coupons.md | 433 +- .../docs/api/review-image.md | 4 +- .../sirsoft-ecommerce/docs/api/reviews.md | 390 +- .../sirsoft-ecommerce/docs/api/settings.md | 2344 +--- .../docs/api/shipping-carriers.md | 239 +- .../docs/api/shipping-country.md | 12 +- .../docs/api/shipping-policies.md | 398 +- .../sirsoft-ecommerce/docs/api/users.md | 10 +- .../sirsoft-ecommerce/docs/api/wishlist.md | 89 +- .../handlers/bulkUpdateHandlers.test.ts | 194 + .../handlers/expandedOptionsHandlers.test.ts | 419 + .../handlers/updateOptionField.test.ts | 92 + ...notification-channel-single-source.test.ts | 190 + ...in_ecommerce_settings_mileage_tab.test.tsx | 29 +- .../cartWishlistImageConsumers.test.ts | 93 + .../layouts/productListLayout.test.tsx | 20 +- .../productListNoCategoryBoolean.test.tsx | 3 +- .../layouts/productListOptionsLazy.test.ts | 311 + .../layouts/prunedListFieldConsumers.test.ts | 123 + .../layouts/reviewImageConsumers.test.ts | 105 + .../js/handlers/bulkUpdateHandlers.ts | 92 +- .../js/handlers/expandedOptionsHandlers.ts | 384 + .../resources/js/handlers/index.ts | 9 + .../js/handlers/updateOptionField.ts | 18 + .../lang/partial/en/admin/product.json | 10 +- .../lang/partial/ko/admin/product.json | 10 +- .../admin/admin_ecommerce_product_list.json | 68 +- .../admin/admin_ecommerce_settings.json | 4 +- .../_modal_bulk_confirm.json | 13 +- .../_modal_bulk_stock.json | 2 + .../_partial_filter_section.json | 34 +- .../_partial_product_datagrid.json | 105 +- .../_tab_notification_definitions.json | 35 +- .../Controllers/Admin/ProductController.php | 50 + .../Requests/Admin/ProductListRequest.php | 6 +- .../Admin/ProductOptionsListRequest.php | 58 + .../Admin/ShippingPolicyListRequest.php | 42 +- .../Requests/User/UserOrderListRequest.php | 24 + .../src/Http/Resources/OrderListResource.php | 128 +- .../Http/Resources/ProductListResource.php | 102 +- .../src/Http/Resources/ProductResource.php | 4 + .../Http/Resources/ProductReviewResource.php | 6 +- .../Resources/ShippingPolicyCollection.php | 4 +- .../ShippingPolicyCountrySettingResource.php | 39 + .../Http/Resources/ShippingPolicyResource.php | 74 +- .../Http/Resources/UserOrderListResource.php | 136 +- .../sirsoft-ecommerce/src/Models/Order.php | 30 + .../sirsoft-ecommerce/src/Models/Product.php | 11 + .../src/Models/ShippingPolicy.php | 82 +- .../src/Repositories/CartRepository.php | 25 +- .../Contracts/ProductRepositoryInterface.php | 13 + .../Repositories/CouponIssueRepository.php | 5 + .../src/Repositories/OrderRepository.php | 21 +- .../src/Repositories/ProductRepository.php | 84 +- .../Repositories/ProductReviewRepository.php | 33 +- .../ProductWishlistRepository.php | 10 +- .../Repositories/ShippingPolicyRepository.php | 28 +- .../src/Services/ProductService.php | 15 + .../src/lang/en/messages.php | 2 + .../src/lang/en/validation.php | 15 + .../src/lang/ko/messages.php | 2 + .../src/lang/ko/validation.php | 15 + .../sirsoft-ecommerce/src/routes/api.php | 9 + .../Admin/AdjacentListThumbnailQueryTest.php | 160 + .../Admin/OrderListDeferredJoinTest.php | 319 +- .../Admin/ProductControllerBulkUpdateTest.php | 44 + .../Admin/ProductListOptionsLazyTest.php | 430 + .../ProductListPayloadSizeBenchmarkTest.php | 153 + .../Admin/ProductOptionsBatchFetchTest.php | 357 + .../Admin/ShippingPolicyActiveListTest.php | 9 +- .../Admin/ShippingPolicyControllerTest.php | 13 +- .../Admin/ShippingPolicyListPayloadTest.php | 325 + .../Controllers/Public/CartControllerTest.php | 45 +- .../PublicProductReviewControllerTest.php | 72 +- .../User/UserOrderControllerTest.php | 128 + .../Playwright/fixtures/ecommerce-auth.ts | 12 + ...ce-notification-definition-channel.spec.ts | 188 + .../admin/product-list-lazy-options.spec.ts | 458 + ...pping-policy-list-country-settings.spec.ts | 176 + ...dmin-order-list-representative-option.yaml | 59 + .../cart-wishlist-image-column-pruning.yaml | 45 + ...ification-definition-channel-contract.yaml | 45 + .../mp-product-list-lazy-options.yaml | 67 + .../scenarios/mp09-product-list-total.yaml | 13 +- .../scenarios/mp12-bulk-update-selection.yaml | 20 +- .../review-list-image-payload-contract.yaml | 50 + ...g-policy-list-country-setting-pruning.yaml | 54 + modules/_bundled/sirsoft-page/CHANGELOG.md | 1 + .../src/Repositories/PageRepository.php | 8 +- .../Feature/Admin/PageControllerTest.php | 102 + .../sirsoft-page/tests/ModuleTestCase.php | 40 +- .../page-admin-list-column-pruning.yaml | 33 + .../extensions/notification_tab_board.json | 8 +- .../extensions/notification_tab_core.json | 8 +- .../notification_tab_ecommerce.json | 8 +- .../sirsoft-pay_kginicis/CHANGELOG.md | 1 + .../admin_order_list_test_badge.json | 366 +- .../AdminOrderListColumnSnapshotTest.php | 169 + .../_bundled/sirsoft-pay_nhnkcp/CHANGELOG.md | 1 + .../admin_order_list_test_badge.json | 370 +- .../AdminOrderListColumnSnapshotTest.php | 169 + .../sirsoft-pay_nicepayments/CHANGELOG.md | 1 + .../admin_order_list_test_badge.json | 366 +- .../AdminOrderListColumnSnapshotTest.php | 169 + public/build/core/layout-editor.min.js | 12 +- public/build/core/template-engine.min.js | 38 +- .../js/core/template-engine/CHANGELOG.md | 12 + .../components/VersionHistoryModal.test.tsx | 98 +- .../components/VersionHistoryModal.tsx | 47 +- .../layout-editor/hooks/useLayoutVersions.ts | 133 +- .../_bundled/sirsoft-admin_basic/CHANGELOG.md | 2 + .../admin-menu-list-child-roles.test.tsx | 241 + ...dmin-role-list-permission-pruning.test.tsx | 288 + ...tings-definition-template-pruning.test.tsx | 271 + ...template-layout-edit-list-pruning.test.tsx | 131 + .../dist/css/components.css | 2 +- .../dist/js/components.iife.js | 121 +- .../lang/partial/en/admin.json | 1 + .../lang/partial/ko/admin.json | 1 + .../layouts/admin_role_list.json | 29 + .../layouts/admin_settings.json | 4 +- .../admin_menu_list/_panel_menu_list.json | 18 + .../_tab_notification_definitions.json | 35 +- .../__tests__/initMenuFromUrlHandler.test.ts | 161 + .../src/handlers/initMenuFromUrlHandler.ts | 83 +- templates/_bundled/sirsoft-basic/CHANGELOG.md | 4 + .../sirsoft-basic/layouts/mypage/orders.json | 4 +- .../Api/Admin/CoreListPayloadPruningTest.php | 365 + ...entityMessageDefinitionListPayloadTest.php | 319 + .../Api/Admin/LayoutControllerTest.php | 257 +- .../Admin/LayoutExtensionControllerTest.php | 226 + .../Feature/Api/Admin/MenuControllerTest.php | 8 +- .../NotificationDefinitionListPayloadTest.php | 292 + .../Feature/Api/Admin/RoleControllerTest.php | 9 +- .../Feature/Api/Admin/TemplateUpdateTest.php | 5 + .../admin/menu-url-entry-restore.spec.ts | 126 + ...ation-definition-channel-roundtrip.spec.ts | 222 + .../version-history-paging.spec.ts | 125 + tests/Unit/LayoutVersionLimitParityTest.php | 96 + .../NotificationDefinitionResourceTest.php | 13 +- .../Support/ApiDoc/ApiDocPipelineTest.php | 268 + .../list-child-collection-pruning.yaml | 167 + 265 files changed, 22679 insertions(+), 39095 deletions(-) create mode 100644 app/Http/Requests/Admin/LayoutVersionListRequest.php create mode 100644 docs/backend/api/system.md create mode 100644 modules/_bundled/sirsoft-board/resources/js/__tests__/layouts/admin-board-notification-channel-single-source.test.ts create mode 100644 modules/_bundled/sirsoft-board/tests/Feature/Admin/AdminBoardListPayloadTest.php create mode 100644 modules/_bundled/sirsoft-board/tests/Playwright/specs/admin/board-admin-list-role-payload.spec.ts create mode 100644 modules/_bundled/sirsoft-board/tests/Playwright/specs/admin/board-notification-definition-channel.spec.ts create mode 100644 modules/_bundled/sirsoft-board/tests/scenarios/board-admin-list-role-payload-pruning.yaml create mode 100644 modules/_bundled/sirsoft-board/tests/scenarios/board-notification-definition-channel-contract.yaml create mode 100644 modules/_bundled/sirsoft-ecommerce/resources/js/__tests__/handlers/expandedOptionsHandlers.test.ts create mode 100644 modules/_bundled/sirsoft-ecommerce/resources/js/__tests__/layouts/admin-ecommerce-notification-channel-single-source.test.ts create mode 100644 modules/_bundled/sirsoft-ecommerce/resources/js/__tests__/layouts/cartWishlistImageConsumers.test.ts create mode 100644 modules/_bundled/sirsoft-ecommerce/resources/js/__tests__/layouts/productListOptionsLazy.test.ts create mode 100644 modules/_bundled/sirsoft-ecommerce/resources/js/__tests__/layouts/prunedListFieldConsumers.test.ts create mode 100644 modules/_bundled/sirsoft-ecommerce/resources/js/__tests__/layouts/reviewImageConsumers.test.ts create mode 100644 modules/_bundled/sirsoft-ecommerce/resources/js/handlers/expandedOptionsHandlers.ts create mode 100644 modules/_bundled/sirsoft-ecommerce/src/Http/Requests/Admin/ProductOptionsListRequest.php create mode 100644 modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Admin/AdjacentListThumbnailQueryTest.php create mode 100644 modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Admin/ProductListOptionsLazyTest.php create mode 100644 modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Admin/ProductListPayloadSizeBenchmarkTest.php create mode 100644 modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Admin/ProductOptionsBatchFetchTest.php create mode 100644 modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Admin/ShippingPolicyListPayloadTest.php create mode 100644 modules/_bundled/sirsoft-ecommerce/tests/Playwright/specs/admin/ecommerce-notification-definition-channel.spec.ts create mode 100644 modules/_bundled/sirsoft-ecommerce/tests/Playwright/specs/admin/product-list-lazy-options.spec.ts create mode 100644 modules/_bundled/sirsoft-ecommerce/tests/Playwright/specs/admin/shipping-policy-list-country-settings.spec.ts create mode 100644 modules/_bundled/sirsoft-ecommerce/tests/scenarios/admin-order-list-representative-option.yaml create mode 100644 modules/_bundled/sirsoft-ecommerce/tests/scenarios/cart-wishlist-image-column-pruning.yaml create mode 100644 modules/_bundled/sirsoft-ecommerce/tests/scenarios/ecommerce-notification-definition-channel-contract.yaml create mode 100644 modules/_bundled/sirsoft-ecommerce/tests/scenarios/mp-product-list-lazy-options.yaml create mode 100644 modules/_bundled/sirsoft-ecommerce/tests/scenarios/review-list-image-payload-contract.yaml create mode 100644 modules/_bundled/sirsoft-ecommerce/tests/scenarios/shipping-policy-list-country-setting-pruning.yaml create mode 100644 modules/_bundled/sirsoft-page/tests/scenarios/page-admin-list-column-pruning.yaml create mode 100644 plugins/_bundled/sirsoft-pay_kginicis/tests/Unit/Extensions/AdminOrderListColumnSnapshotTest.php create mode 100644 plugins/_bundled/sirsoft-pay_nhnkcp/tests/Unit/Extensions/AdminOrderListColumnSnapshotTest.php create mode 100644 plugins/_bundled/sirsoft-pay_nicepayments/tests/Unit/Extensions/AdminOrderListColumnSnapshotTest.php create mode 100644 templates/_bundled/sirsoft-admin_basic/__tests__/layouts/admin-menu-list-child-roles.test.tsx create mode 100644 templates/_bundled/sirsoft-admin_basic/__tests__/layouts/admin-role-list-permission-pruning.test.tsx create mode 100644 templates/_bundled/sirsoft-admin_basic/__tests__/layouts/admin-settings-definition-template-pruning.test.tsx create mode 100644 templates/_bundled/sirsoft-admin_basic/__tests__/layouts/admin-template-layout-edit-list-pruning.test.tsx create mode 100644 templates/_bundled/sirsoft-admin_basic/src/handlers/__tests__/initMenuFromUrlHandler.test.ts create mode 100644 tests/Feature/Api/Admin/CoreListPayloadPruningTest.php create mode 100644 tests/Feature/Api/Admin/Identity/AdminIdentityMessageDefinitionListPayloadTest.php create mode 100644 tests/Feature/Api/Admin/NotificationDefinitionListPayloadTest.php create mode 100644 tests/Playwright/specs/admin/menu-url-entry-restore.spec.ts create mode 100644 tests/Playwright/specs/admin/notification-definition-channel-roundtrip.spec.ts create mode 100644 tests/Playwright/specs/layout-editor/version-history-paging.spec.ts create mode 100644 tests/Unit/LayoutVersionLimitParityTest.php create mode 100644 tests/scenarios/list-child-collection-pruning.yaml diff --git a/AGENTS.md b/AGENTS.md index e670f7c1..745899c4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -356,6 +356,24 @@ Icon 은 `` 글리프라 박스 크기가 곧 `font-size` 다. `w-N h-N` 은 > 상세: [validation.md "계층 리소스 순환 참조" / "배열 항목의 상위 스코프"](docs/backend/validation.md), [service-repository.md "중첩 리소스 스코프" / "설정 기반 한계값"](docs/backend/service-repository.md) +### 목록 응답의 하위 컬렉션 + +목록은 화면이 그 행에서 **실제로 그리는 것**만 싣는다. 행마다 하위 컬렉션을 통째로 직렬화하면 한 페이지를 여는 것만으로 수백~수천 행이 응답에 실린다 (공개 #76 — 상품 100건 × 옵션 20건). + +| 금지 | 올바른 사용 | +|------|------------| +| `relationLoaded('x') ? $this->x : $this->whenLoaded('y')` (가짜 가드) | `whenLoaded('x', fn () => ...)` 하나만 — 로드 여부는 **Repository 가** 결정한다 | +| Resource 는 `whenLoaded` 로 방어하는데 Repository 가 목록에서 무조건 eager load | 목록의 `relations:` 는 목록이 **실제로 직렬화하는** 관계만 | +| 개수/합계를 PHP 컬렉션 연산으로 (`$this->options->where(...)->sum(...)`) | `withCount:` / `withSum`(`outerUsing:`) DB 집계 | +| `toListArray()` 를 정의해 두고 컬렉션이 `toArray()` 를 호출 | 컨트롤러/컬렉션이 목록 표현을 **명시 호출** | +| 목록 Resource 안에서 관계 재쿼리 (`$this->images()->first()`) | `relationLoaded` 분기로 로드된 컬렉션에서 고른다 | +| 집계 별칭 존재 여부를 `!== null` 로 판정 | `array_key_exists($alias, $model->getAttributes())` — SUM 은 0건에서 NULL 이라 값 검사로는 "집계 안 함" 과 구분되지 않는다 | +| 목록에서 뺀 값을 대체 경로 없이 제거 | 지연 로드 경로(배치 조회)를 먼저 만들고, 하위호환은 opt-in 파라미터(`?with_options=1`)로 | + +착수 전 **소비처를 실측**한다. 화면이 그 값을 실제로 순회·렌더하면 제거는 기능 축소다 — 계획서에 "안 쓴다" 고 적혀 있어도 레이아웃 JSON 을 열어 확인한다. + +> 상세: [api-resources.md](docs/backend/api-resources.md), [service-repository.md](docs/backend/service-repository.md) + ### 목록 조회 컬럼 프루닝과 지연 조인 | 금지 | 올바른 사용 | diff --git a/CHANGELOG.md b/CHANGELOG.md index 823940e4..a39aac7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -72,6 +72,13 @@ ### Changed +- 레이아웃 편집기의 파일 목록·버전 이력·확장 목록이 목록에 필요한 정보만 받아오도록 바꿨습니다. 예전에는 목록 한 번에 그 템플릿의 모든 레이아웃 본문과 버전마다의 본문 사본까지 함께 실려, 레이아웃이 많거나 저장을 자주 한 템플릿일수록 편집기 진입이 느려졌습니다. 편집 본문은 파일을 선택할 때 받아오므로 화면에 보이는 내용은 이전과 동일합니다. 버전 이력은 최근 100건까지 표시하며, 더 필요하면 조회 건수를 지정할 수 있습니다. (#76 @jordy-bitree 님께서 제보해주셨습니다.) +- 역할 관리 목록과 메뉴 관리 목록이 화면에서 쓰지 않는 정보를 함께 내려주던 것을 정리했습니다. 역할 목록은 권한 전체 목록을 세 가지 형태로 중복해서, 메뉴 목록은 하위 메뉴마다 역할 정보를 함께 싣고 있었습니다. 역할의 권한은 역할 수정 화면에서 종전대로 확인·편집할 수 있습니다. (#76 @jordy-bitree 님께서 제보해주셨습니다.) +- 레이아웃 버전 목록 조회에 건수 제한을 두었습니다. 허용 범위를 벗어난 값을 지정하면 안내 문구가 표시됩니다. 버전 이력의 '더 보기'는 조회 상한에 이르면 더 넓히지 않습니다. (#76 @jordy-bitree 님께서 제보해주셨습니다.) +- 알림 정의 목록이 지금 보고 있는 채널의 템플릿만 받아오도록 바꿨습니다. 예전에는 정의마다 모든 채널의 제목과 본문이 함께 실려, 채널을 여러 개 쓰는 사이트일수록 목록이 무거웠습니다. 채널을 지정하지 않고 목록을 조회하면 템플릿 본문은 함께 오지 않으며, 전체 채널의 내용이 필요하면 정의 단건 조회를 이용하면 됩니다. 화면에 보이는 내용과 '되돌리기' 버튼 표시 조건은 이전과 동일합니다. (#76 @jordy-bitree 님께서 제보해주셨습니다.) +- 본인인증 메시지 정의 목록이 대표 템플릿 한 건만 받아오도록 바꿨습니다. 전체 템플릿은 정의를 열었을 때 종전대로 받아옵니다. (#76 @jordy-bitree 님께서 제보해주셨습니다.) +- 역할 목록에 할당된 권한 개수를 함께 표시할 수 있도록 집계 값을 제공합니다. 권한 목록 자체를 싣지 않고도 규모를 확인할 수 있습니다. (#76 @jordy-bitree 님께서 제보해주셨습니다.) + - 활동 로그·알림 발송 이력·본인인증 기록·스케줄 실행 이력·회원 목록을 뒤쪽 페이지에서도 빠르게 열 수 있도록 조회 방식을 바꿨습니다. 예전에는 페이지가 뒤로 갈수록 건너뛰는 기록의 본문·변경 내역까지 함께 읽어 느려졌지만, 이제 현재 페이지에 해당하는 기록만 상세 정보를 읽습니다. 화면에 보이는 내용은 이전과 동일합니다. (#74 @jiwonpapa 님께서 제보해주셨습니다.) - 확장이 업로드 허용 형식을 넓힐 수 있도록 `core.attachment.allowed_extensions` 필터를 추가했습니다. 관리자 설정 목록을 기준으로, 확장이 자기 기능에 필요한 형식만 덧붙일 수 있습니다. - 활동 로그·알림 발송 이력·회원 목록의 정렬 색인을 정비했습니다. 같은 시각에 쌓인 기록이 많은 구간에서 순서를 정하느라 생기던 추가 작업이 사라져, 기록이 많은 사이트일수록 목록이 빨라집니다. 기존 사이트도 업데이트 시 자동으로 반영되며, 기록이 아주 많은 경우 이 과정에 수 분이 걸리고 그동안 해당 기록의 저장이 잠시 대기할 수 있습니다. diff --git a/app/Contracts/Repositories/LayoutExtensionVersionRepositoryInterface.php b/app/Contracts/Repositories/LayoutExtensionVersionRepositoryInterface.php index 9e05cc67..1800a6b0 100644 --- a/app/Contracts/Repositories/LayoutExtensionVersionRepositoryInterface.php +++ b/app/Contracts/Repositories/LayoutExtensionVersionRepositoryInterface.php @@ -24,7 +24,20 @@ interface LayoutExtensionVersionRepositoryInterface * @param int $extensionId 레이아웃 확장 ID * @return Collection 버전 컬렉션 */ - public function getVersions(int $extensionId): Collection; + public function getVersions(int $extensionId, int $limit = 100): Collection; + + /** + * 확장의 특정 버전 번호 조회 (본문 포함) + * + * 목록(getVersions)은 경량 조회라 `content` 를 담지 않고 건수 상한이 있다. 단건은 그 + * 대체 경로이므로 반드시 전용 조회를 쓴다 — 목록 조회를 재사용하면 본문이 사라지고 + * 상한 밖의 오래된 버전을 찾지 못한다. + * + * @param int $extensionId 레이아웃 확장 ID + * @param int $version 버전 번호 + * @return TemplateLayoutExtensionVersion|null 찾은 버전 모델 또는 null + */ + public function findVersionByNumber(int $extensionId, int $version): ?TemplateLayoutExtensionVersion; /** * 확장 ID 목록의 현재(최신) 버전 번호 맵 조회 diff --git a/app/Contracts/Repositories/LayoutRepositoryInterface.php b/app/Contracts/Repositories/LayoutRepositoryInterface.php index 20ece537..c675d249 100644 --- a/app/Contracts/Repositories/LayoutRepositoryInterface.php +++ b/app/Contracts/Repositories/LayoutRepositoryInterface.php @@ -138,12 +138,16 @@ interface LayoutRepositoryInterface public function updateContent(int $id, array $content, int $newLockVersion): TemplateLayout; /** - * 특정 레이아웃의 모든 버전 조회 + * 특정 레이아웃의 최근 버전 목록 조회 (최신순) + * + * 버전 행은 저장할 때마다 쌓이고 정리되지 않으므로 조회 건수에 상한이 있습니다. + * 목록이 쓰지 않는 `content`(버전마다 레이아웃 본문 사본)는 조회하지 않습니다. * * @param int $layoutId 레이아웃 ID + * @param int $limit 조회할 최대 버전 수 * @return Collection 버전 컬렉션 */ - public function getVersionsByLayoutId(int $layoutId): Collection; + public function getVersionsByLayoutId(int $layoutId, int $limit = 100): Collection; /** * 특정 버전 조회 diff --git a/app/Http/Controllers/Api/Admin/LayoutController.php b/app/Http/Controllers/Api/Admin/LayoutController.php index cd732266..8ee039cb 100644 --- a/app/Http/Controllers/Api/Admin/LayoutController.php +++ b/app/Http/Controllers/Api/Admin/LayoutController.php @@ -4,6 +4,7 @@ namespace App\Http\Controllers\Api\Admin; use App\Exceptions\ConcurrentModificationException; use App\Http\Controllers\Api\Base\AdminBaseController; +use App\Http\Requests\Admin\LayoutVersionListRequest; use App\Http\Requests\Layout\StoreLayoutPreviewRequest; use App\Http\Requests\Layout\UpdateLayoutContentRequest; use App\Http\Resources\LayoutListResource; @@ -148,11 +149,12 @@ class LayoutController extends AdminBaseController /** * 레이아웃의 모든 버전 목록 조회 * + * @param LayoutVersionListRequest $request 버전 목록 조회 요청 (limit) * @param string $templateName 템플릿 identifier * @param string $name 레이아웃 이름 * @return JsonResponse 버전 목록 응답 */ - public function versions(string $templateName, string $name): JsonResponse + public function versions(LayoutVersionListRequest $request, string $templateName, string $name): JsonResponse { $template = $this->templateService->findByIdentifier($templateName); @@ -166,12 +168,19 @@ class LayoutController extends AdminBaseController return $this->notFound('common.not_found'); } - $versions = $this->layoutService->getLayoutVersions($template->id, $name); - - return $this->success( - 'common.success', - LayoutVersionResource::collection($versions) + $versions = $this->layoutService->getLayoutVersions( + $template->id, + $name, + (int) ($request->validated()['limit'] ?? LayoutVersionListRequest::DEFAULT_LIMIT) ); + + // 목록은 경량 표현만 내려준다 — 분해된 본문은 버전 비교 diff 전용이라 단건 조회가 공급한다. + $items = $versions + ->map(fn ($version) => (new LayoutVersionResource($version))->toListArray($request)) + ->values() + ->all(); + + return $this->success('common.success', $items); } /** diff --git a/app/Http/Controllers/Api/Admin/LayoutExtensionController.php b/app/Http/Controllers/Api/Admin/LayoutExtensionController.php index 51bdf5fa..2233e296 100644 --- a/app/Http/Controllers/Api/Admin/LayoutExtensionController.php +++ b/app/Http/Controllers/Api/Admin/LayoutExtensionController.php @@ -4,6 +4,7 @@ namespace App\Http\Controllers\Api\Admin; use App\Exceptions\ConcurrentModificationException; use App\Http\Controllers\Api\Base\AdminBaseController; +use App\Http\Requests\Admin\LayoutVersionListRequest; use App\Http\Requests\Layout\StoreLayoutExtensionPreviewRequest; use App\Http\Requests\Layout\UpdateLayoutExtensionContentRequest; use App\Http\Resources\LayoutExtensionResource; @@ -58,7 +59,11 @@ class LayoutExtensionController extends AdminBaseController $this->layoutExtensionService->getExtensionHostLayouts($extension) ); } - $group['extensions'] = LayoutExtensionResource::collection($group['extensions']); + // 목록은 경량 표현만 내려준다 — 편집 본문(content)은 단건 조회가 공급한다. + $group['extensions'] = collect($group['extensions']) + ->map(fn ($extension) => (new LayoutExtensionResource($extension))->toListArray(request())) + ->values() + ->all(); return $group; }, $groups); @@ -156,11 +161,12 @@ class LayoutExtensionController extends AdminBaseController /** * 레이아웃 확장의 모든 버전 목록 조회 * + * @param LayoutVersionListRequest $request 버전 목록 조회 요청 (limit) * @param string $templateName 템플릿 identifier * @param int $extensionId 확장 ID * @return JsonResponse 버전 목록 응답 */ - public function versions(string $templateName, int $extensionId): JsonResponse + public function versions(LayoutVersionListRequest $request, string $templateName, int $extensionId): JsonResponse { $extension = $this->resolveExtension($templateName, $extensionId); @@ -168,9 +174,18 @@ class LayoutExtensionController extends AdminBaseController return $this->notFound('common.not_found'); } - $versions = $this->layoutExtensionService->getExtensionVersions($extensionId); + $versions = $this->layoutExtensionService->getExtensionVersions( + $extensionId, + (int) ($request->validated()['limit'] ?? LayoutVersionListRequest::DEFAULT_LIMIT) + ); - return $this->success('common.success', LayoutExtensionVersionResource::collection($versions)); + // 목록은 경량 표현만 내려준다 — 확장 본문은 버전 비교 diff 전용이라 단건 조회가 공급한다. + $items = $versions + ->map(fn ($version) => (new LayoutExtensionVersionResource($version))->toListArray($request)) + ->values() + ->all(); + + return $this->success('common.success', $items); } /** diff --git a/app/Http/Requests/Admin/LayoutVersionListRequest.php b/app/Http/Requests/Admin/LayoutVersionListRequest.php new file mode 100644 index 00000000..d4b7afbc --- /dev/null +++ b/app/Http/Requests/Admin/LayoutVersionListRequest.php @@ -0,0 +1,61 @@ + 검증 규칙 + */ + public function rules(): array + { + $rules = [ + 'limit' => ['nullable', 'integer', 'min:1', 'max:'.self::MAX_LIMIT], + ]; + + // 모듈/플러그인이 목록 조회 파라미터를 확장할 수 있도록 훅을 노출한다 + return HookManager::applyFilters('core.layout_version.list_validation_rules', $rules, $this); + } + + /** + * 검증 오류 메시지 커스터마이징 + * + * @return array 오류 메시지 + */ + public function messages(): array + { + return [ + 'limit.integer' => __('validation.layout_version.limit.integer'), + 'limit.min' => __('validation.layout_version.limit.min'), + 'limit.max' => __('validation.layout_version.limit.max', ['max' => self::MAX_LIMIT]), + ]; + } +} diff --git a/app/Http/Requests/NotificationDefinition/NotificationDefinitionIndexRequest.php b/app/Http/Requests/NotificationDefinition/NotificationDefinitionIndexRequest.php index 7b33768b..c7b5e095 100644 --- a/app/Http/Requests/NotificationDefinition/NotificationDefinitionIndexRequest.php +++ b/app/Http/Requests/NotificationDefinition/NotificationDefinitionIndexRequest.php @@ -29,6 +29,11 @@ class NotificationDefinitionIndexRequest extends FormRequest 'extension_type' => ['nullable', 'string', 'in:core,module,plugin'], 'extension_identifier' => ['nullable', 'string', 'max:100'], 'channel' => ['nullable', 'string', 'max:50'], + // 목록에 실을 템플릿을 한 채널로 좁힌다. `channel` 과 다르다 — `channel` 은 정의 행 + // 자체를 거르지만(그 채널을 지원하지 않는 정의는 목록에서 사라진다), 이 값은 행은 + // 그대로 두고 각 행에 딸려오는 템플릿만 좁힌다. 화면이 한 번에 한 채널만 그리므로 + // 나머지 채널의 제목·본문은 전송할 이유가 없다. + 'template_channel' => ['nullable', 'string', 'max:50'], 'is_active' => ['nullable', 'boolean'], 'per_page' => ['nullable', 'integer', 'min:1', 'max:100'], 'sort_by' => ['nullable', 'string', 'in:id,type,extension_type,is_active,created_at,updated_at'], diff --git a/app/Http/Resources/Admin/Identity/IdentityMessageDefinitionResource.php b/app/Http/Resources/Admin/Identity/IdentityMessageDefinitionResource.php index e82e5469..9b44bb60 100644 --- a/app/Http/Resources/Admin/Identity/IdentityMessageDefinitionResource.php +++ b/app/Http/Resources/Admin/Identity/IdentityMessageDefinitionResource.php @@ -4,6 +4,7 @@ namespace App\Http\Resources\Admin\Identity; use App\Http\Resources\BaseApiResource; use Illuminate\Http\Request; +use Illuminate\Http\Resources\MissingValue; /** * IDV 메시지 정의 리소스. @@ -60,9 +61,25 @@ class IdentityMessageDefinitionResource extends BaseApiResource 'is_active' => (bool) $this->getValue('is_active'), 'is_default' => (bool) $this->getValue('is_default'), 'user_overrides' => $this->getValue('user_overrides'), - 'templates' => $this->relationLoaded('templates') - ? IdentityMessageTemplateResource::collection($this->templates) - : null, + // 목록은 대표 템플릿 1건만 로드하고(firstTemplate), 단건은 전체를 로드한다(templates). + // 화면 계약은 양쪽 모두 `templates` 배열이라 여기서 하나로 합류시킨다 — + // 로드된 쪽을 쓰고, 어느 쪽도 로드되지 않았으면 키 자체를 내보내지 않는다. + // + // 대표 템플릿 분기는 `whenLoaded` 로 쓸 수 없다. `whenLoaded($relation, $value)` 는 + // 관계가 로드됐어도 값이 null 이면 $value 를 평가하지 않고 그대로 null 을 돌려준다 + // (HasOne 이 0건이면 null). 그러면 "로드하지 않음"(null)과 "로드했더니 0건"(빈 배열)이 + // 같은 값이 되어 소비자가 둘을 구분할 수 없다 — 관계 로드 여부는 직접 판정한다. + 'templates' => $this->whenLoaded( + 'templates', + fn () => IdentityMessageTemplateResource::collection($this->templates), + fn () => $this->resource->relationLoaded('firstTemplate') + ? ($this->firstTemplate + ? IdentityMessageTemplateResource::collection(collect([$this->firstTemplate])) + : []) + : new MissingValue, + ), + // 목록이 대표 1건만 싣기 때문에, "템플릿이 더 있는가" 는 배열 길이로 알 수 없다. + 'templates_count' => $this->whenHas('templates_count', fn () => (int) $this->templates_count), ...$this->formatTimestamps(), ...$this->resourceMeta($request), ]; diff --git a/app/Http/Resources/BaseApiResource.php b/app/Http/Resources/BaseApiResource.php index 4217539f..b4b4a765 100644 --- a/app/Http/Resources/BaseApiResource.php +++ b/app/Http/Resources/BaseApiResource.php @@ -5,13 +5,16 @@ namespace App\Http\Resources; use App\Helpers\TimezoneHelper; use App\Http\Resources\Traits\HasAbilityCheck; use Carbon\Carbon; +use Illuminate\Database\Eloquent\Model; use Illuminate\Http\Request; use Illuminate\Http\Resources\Json\JsonResource; +use Illuminate\Http\Resources\MissingValue; use Illuminate\Support\Facades\App; class BaseApiResource extends JsonResource { use HasAbilityCheck; + /** * 리소스를 배열로 변환합니다. * @@ -122,6 +125,76 @@ class BaseApiResource extends JsonResource ]; } + /** + * 조건부 필드(`when()`)가 걸러지지 않은 배열에서 미충족 항목을 제거합니다. + * + * `toArray()` 결과는 Laravel 이 응답 직전에 `filter()` 로 `MissingValue` 를 걷어내지만, + * `toListArray()` 처럼 **배열을 그대로 컨트롤러에 넘기는 경로**는 그 단계를 거치지 않는다. + * 그대로 두면 조건 미충족 필드가 `"updated_at": {}` 같은 빈 객체로 응답에 실린다 + * (예: `UPDATED_AT = null` 인 모델의 타임스탬프). + * + * @param array $data 직렬화 결과 + * @return array 미충족 항목이 제거된 배열 + */ + protected function withoutMissing(array $data): array + { + return array_filter($data, fn ($value) => ! $value instanceof MissingValue); + } + + /** + * 쿼리에서 계산된 집계 별칭(`withCount`/`withSum` 등)이 실제로 실려 있는지 판정합니다. + * + * 값 검사(`!== null`, `> 0`)로는 판정할 수 없다. SUM 은 대상 0건에서 NULL 을 돌려주고 + * COUNT 는 0 을 돌려주므로, "집계하지 않았다" 와 "집계했더니 비어 있다" 가 구분되지 않는다. + * 속성 키의 존재 여부만이 두 상황을 가른다. + * + * @param string $alias 집계 별칭 (예: `options_count`) + * @return bool 집계가 실려 있는지 여부 + */ + protected function hasAggregate(string $alias): bool + { + $model = $this->unwrapResource(); + + return $model instanceof Model && array_key_exists($alias, $model->getAttributes()); + } + + /** + * 집계 별칭 값을 반환합니다. 집계가 실려 있지 않으면 기본값을 돌려줍니다. + * + * @param string $alias 집계 별칭 + * @param mixed $default 집계 부재 시 사용할 값 + * @return mixed 집계 값 또는 기본값 + */ + protected function aggregate(string $alias, mixed $default = null): mixed + { + $model = $this->unwrapResource(); + + if (! $model instanceof Model || ! array_key_exists($alias, $model->getAttributes())) { + return $default; + } + + return $model->getAttributes()[$alias]; + } + + /** + * 리소스 래핑을 벗겨 원본 모델을 반환합니다. + * + * Resource 가 Resource 를 감싸는 경우(컬렉션 매핑 등)가 있어 한 겹만 벗기면 모델에 + * 닿지 못한다. + * + * @return mixed 래핑이 제거된 리소스 본체 + */ + private function unwrapResource(): mixed + { + $model = $this->resource; + + while ($model instanceof JsonResource) { + $model = $model->resource; + } + + return $model; + } + /** * datetime 값을 사용자 timezone으로 변환하여 ISO8601 문자열로 반환합니다. * diff --git a/app/Http/Resources/LayoutExtensionResource.php b/app/Http/Resources/LayoutExtensionResource.php index 36a3dde6..fc8deb79 100644 --- a/app/Http/Resources/LayoutExtensionResource.php +++ b/app/Http/Resources/LayoutExtensionResource.php @@ -69,6 +69,56 @@ class LayoutExtensionResource extends BaseApiResource ]; } + /** + * 목록용 경량 배열로 변환합니다. + * + * 확장 목록은 라우트 트리에 "어느 화면에 어떤 확장이 붙어 있는가" 를 그리는 데 쓰입니다. + * 편집 본문(`content`)은 그 화면이 쓰지 않고 단건 조회가 공급하는데, 목록에 실리면 템플릿에 + * 등록된 **모든 확장의 JSON 전문**이 한 응답에 담깁니다. + * + * 수정 여부(`is_modified`)와 크기(`size`/`size_formatted`)는 본문에서 계산하므로 값은 그대로 + * 유지합니다 — 본문을 직렬화하지 않을 뿐, 계산은 서버에서 수행해 결과만 내려줍니다. + * (목록 화면이 확장마다 크기를 표시하므로 빼면 전 행이 `0 B` 로 보인다.) + * + * @param Request $request 요청 객체 + * @return array 목록용 직렬화 결과 + */ + public function toListArray(Request $request): array + { + $content = $this->getValue('content', []); + $contentJson = is_array($content) ? json_encode($content, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) : '{}'; + + $extensionType = $this->getValue('extension_type'); + $sourceType = $this->getValue('source_type'); + + // 이 배열은 컨트롤러가 그대로 응답에 싣는다 — Laravel 의 MissingValue 제거 단계를 거치지 + // 않으므로 조건부 필드를 직접 걸러낸다. + return $this->withoutMissing([ + 'id' => $this->getValue('id'), + 'extension_type' => $extensionType?->value ?? $extensionType, + 'target_name' => $this->getValue('target_name'), + 'source_type' => $sourceType?->value ?? $sourceType, + 'source_identifier' => $this->getValue('source_identifier'), + 'source_label' => $this->getValue('source_label', $this->getValue('source_identifier')), + 'is_override' => (bool) $this->getValue( + 'is_override', + ($sourceType?->value ?? $sourceType) === 'template' + ), + 'priority' => $this->getValue('priority'), + 'is_active' => (bool) $this->getValue('is_active'), + 'is_modified' => $this->resolveIsModified($content), + 'size' => strlen($contentJson), + 'size_formatted' => $this->formatFileSize(strlen($contentJson)), + 'host_layouts' => $this->getValue('host_layouts', []), + 'current_version' => $this->getValue('current_version') !== null + ? (int) $this->getValue('current_version') + : null, + + ...$this->formatTimestamps(), + ...$this->resourceMeta($request), + ]); + } + /** * 리소스별 권한 매핑을 반환합니다. * diff --git a/app/Http/Resources/LayoutExtensionVersionResource.php b/app/Http/Resources/LayoutExtensionVersionResource.php index b83fb3e0..d8cb79e1 100644 --- a/app/Http/Resources/LayoutExtensionVersionResource.php +++ b/app/Http/Resources/LayoutExtensionVersionResource.php @@ -39,6 +39,33 @@ class LayoutExtensionVersionResource extends BaseApiResource ]; } + /** + * 목록용 경량 배열로 변환합니다. + * + * 버전 히스토리 패널이 쓰는 값은 버전 번호·변경 요약·저장자·저장 시각뿐입니다. 확장 본문 + * (`content`)은 버전 비교 diff 전용이라 단건 조회가 공급합니다. 목록에 실으면 **버전마다** + * 확장 본문 사본이 실려, 저장을 반복한 확장일수록 목록 응답이 선형으로 커집니다. + * (레이아웃 본체 LayoutVersionResource 와 동일 정책) + * + * @param Request $request 요청 객체 + * @return array 목록용 직렬화 결과 + */ + public function toListArray(Request $request): array + { + // 이 배열은 컨트롤러가 그대로 응답에 싣는다 — Laravel 의 MissingValue 제거 단계를 거치지 + // 않으므로 조건부 필드를 직접 걸러낸다 (이 모델은 UPDATED_AT 이 없어 updated_at 이 미충족). + return $this->withoutMissing([ + 'id' => $this->getValue('id'), + 'extension_id' => $this->getValue('extension_id'), + 'version' => $this->getValue('version'), + 'changes_summary' => $this->formatChangesSummary($this->getValue('changes_summary', [])), + 'created_by_name' => $this->resolveCreatorName(), + + ...$this->formatTimestamps(), + ...$this->resourceMeta($request), + ]); + } + /** * 저장자 이름을 해석합니다. * diff --git a/app/Http/Resources/LayoutVersionResource.php b/app/Http/Resources/LayoutVersionResource.php index c5554efc..98b3298a 100644 --- a/app/Http/Resources/LayoutVersionResource.php +++ b/app/Http/Resources/LayoutVersionResource.php @@ -2,6 +2,7 @@ namespace App\Http\Resources; +use App\Models\User; use Illuminate\Http\Request; class LayoutVersionResource extends BaseApiResource @@ -64,6 +65,36 @@ class LayoutVersionResource extends BaseApiResource return $payload; } + /** + * 목록용 경량 배열로 변환합니다. + * + * 버전 히스토리 패널이 쓰는 값은 버전 번호·변경 요약·저장자·저장 시각뿐입니다 + * (`LayoutVersionSummary`). 분해된 본문(`components`/`data_sources`/`metadata`/`endpoint`)은 + * 버전 비교 diff 전용이라 단건 조회가 공급합니다. 목록에 실으면 **버전마다** 레이아웃 본문 + * 사본이 실려, 저장을 반복한 레이아웃일수록 목록 응답이 선형으로 커집니다. + * + * 종전 주석은 "목록에는 content 를 제외한다" 고 했지만 실제로 제외된 것은 `full_content` + * 하나뿐이었습니다 — 분해된 키들이 그대로 남아 사실상 본문이 다 실렸습니다. + * + * @param Request $request 현재 요청 + * @return array 목록용 직렬화 결과 + */ + public function toListArray(Request $request): array + { + // 이 배열은 컨트롤러가 그대로 응답에 싣는다 — Laravel 의 MissingValue 제거 단계를 거치지 + // 않으므로 조건부 필드를 직접 걸러낸다 (이 모델은 UPDATED_AT 이 없어 updated_at 이 미충족). + return $this->withoutMissing([ + 'id' => $this->getValue('id'), + 'layout_id' => $this->getValue('layout_id'), + 'version' => $this->getValue('version'), + 'changes_summary' => $this->formatChangesSummary($this->getValue('changes_summary', [])), + 'created_by_name' => $this->resolveCreatorName(), + + ...$this->formatTimestamps(), + ...$this->resourceMeta($request), + ]); + } + /** * 저장자 이름을 해석합니다. * @@ -77,7 +108,7 @@ class LayoutVersionResource extends BaseApiResource $creator = $this->whenLoaded('creator'); // whenLoaded 미로딩 시 MissingValue 반환 → 삼항으로 null 처리 (MissingValue 누출 방지). - return $creator instanceof \App\Models\User ? $creator->name : null; + return $creator instanceof User ? $creator->name : null; } /** diff --git a/app/Http/Resources/MenuResource.php b/app/Http/Resources/MenuResource.php index cc78b0ff..069933ce 100644 --- a/app/Http/Resources/MenuResource.php +++ b/app/Http/Resources/MenuResource.php @@ -38,7 +38,7 @@ class MenuResource extends BaseApiResource 'children' => $this->whenLoaded('children', function () { return $this->children->map(function ($child) { - return [ + $data = [ 'id' => $child->id, 'name' => $child->name, // 다국어 객체 그대로 반환 'slug' => $child->slug, @@ -49,14 +49,23 @@ class MenuResource extends BaseApiResource 'parent_id' => $child->parent_id, 'extension_type' => $child->extension_type?->value, 'extension_identifier' => $child->extension_identifier, - 'roles' => $child->relationLoaded('roles') ? $child->roles->map(function ($role) { + ]; + + // 역할은 로드된 경우에만 싣는다. 미로드 시 `[]` 를 돌려주면 "역할 제한이 + // 없다" 는 사실이 아닌 단언이 되어, 화면이 그 값을 그대로 저장에 실어 + // 보내면 해당 메뉴의 역할 제한이 통째로 지워진다. 키 자체를 생략해 + // 소비 측이 "모른다" 를 구분할 수 있게 한다 (단건 조회로 보강). + if ($child->relationLoaded('roles')) { + $data['roles'] = $child->roles->map(function ($role) { return [ 'id' => $role->id, 'name' => $role->name, 'permission_type' => $role->pivot->permission_type ?? null, ]; - }) : [], - ]; + })->values(); + } + + return $data; })->sortBy('order')->values(); }), diff --git a/app/Http/Resources/NotificationDefinitionResource.php b/app/Http/Resources/NotificationDefinitionResource.php index 2e967444..591fd062 100644 --- a/app/Http/Resources/NotificationDefinitionResource.php +++ b/app/Http/Resources/NotificationDefinitionResource.php @@ -20,12 +20,15 @@ class NotificationDefinitionResource extends BaseApiResource /** * 리소스를 배열로 변환합니다. * - * @param Request $request + * @param Request $request * @return array */ public function toArray(Request $request): array { - return [ + // 컬렉션(NotificationDefinitionCollection)이 이 배열을 그대로 응답에 실어 Laravel 의 + // MissingValue 제거 단계를 거치지 않는다 — 미충족 조건부 필드를 직접 걸러낸다. + // (걸러내지 않으면 `"templates": {}` 같은 빈 객체가 응답에 남는다) + return $this->withoutMissing([ 'id' => $this->getValue('id'), 'type' => $this->getValue('type'), 'hook_prefix' => $this->getValue('hook_prefix'), @@ -38,11 +41,19 @@ class NotificationDefinitionResource extends BaseApiResource 'hooks' => $this->getValue('hooks'), 'is_active' => (bool) $this->getValue('is_active'), 'is_default' => (bool) $this->getValue('is_default'), - 'templates' => $this->relationLoaded('templates') - ? NotificationTemplateResource::collection($this->templates) - : null, + 'templates' => $this->whenLoaded( + 'templates', + fn () => NotificationTemplateResource::collection($this->templates) + ), + // 목록이 템플릿을 한 채널로 좁혀 싣기 때문에, "커스터마이즈된 템플릿이 있는가" 는 + // 배열을 훑어서는 알 수 없다 (다른 채널만 수정한 경우를 놓친다). 되돌리기 버튼 + // 노출 조건이 이 값에 의존하므로 집계로 내려보낸다. + 'has_customized_templates' => $this->whenHas( + 'customized_templates_count', + fn () => (int) $this->customized_templates_count > 0 + ), ...$this->formatTimestamps(), ...$this->resourceMeta($request), - ]; + ]); } } diff --git a/app/Http/Resources/RoleResource.php b/app/Http/Resources/RoleResource.php index 6f844e8d..b3758dc7 100644 --- a/app/Http/Resources/RoleResource.php +++ b/app/Http/Resources/RoleResource.php @@ -37,6 +37,14 @@ class RoleResource extends BaseApiResource $this->resource->users_count !== null, fn () => $this->resource->users_count ), + // 목록은 권한 배열 3종을 싣지 않는다. 그 대신 "몇 개가 할당됐는가" 는 집계로 남긴다 + // — 목록 화면이 권한 규모를 보여주면서도 권한 트리를 전송하지 않게 하는 대체 경로다. + // 값 검사(`!== null`)가 아니라 별칭 존재 여부로 판정한다: COUNT 는 0건에서도 0 을 + // 돌려주므로 "집계하지 않았다" 와 "집계했더니 0" 이 구분되지 않는다. + 'permissions_count' => $this->whenHas( + 'permissions_count', + fn () => (int) $this->resource->permissions_count + ), 'permission_ids' => $this->when( $this->resource->relationLoaded('permissions'), fn () => $this->resource->permissions->pluck('id')->toArray() diff --git a/app/Models/IdentityMessageDefinition.php b/app/Models/IdentityMessageDefinition.php index d3a25213..84ea6ef5 100644 --- a/app/Models/IdentityMessageDefinition.php +++ b/app/Models/IdentityMessageDefinition.php @@ -9,6 +9,7 @@ use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Database\Eloquent\Relations\HasOne; /** * 본인인증 메시지 정의 모델. @@ -125,11 +126,31 @@ class IdentityMessageDefinition extends Model /** * 메시지 템플릿 관계. * + * 등록 순서(기본키)로 정렬을 고정한다. 목록은 대표 1건만(`firstTemplate` = MIN(id)) 싣고 + * 단건은 전체를 싣는데, 화면 계약상 양쪽 모두 `templates[0]` 을 대표로 읽는다. 정렬이 없으면 + * 단건의 첫 항목이 DB 반환 순서(`(definition_id, channel)` 유니크 인덱스 순)를 타서, 목록에서 + * 본 제목과 다른 채널의 템플릿이 열린다. + * * @return HasMany */ public function templates(): HasMany { - return $this->hasMany(IdentityMessageTemplate::class, 'definition_id'); + return $this->hasMany(IdentityMessageTemplate::class, 'definition_id')->orderBy('id'); + } + + /** + * 대표 메시지 템플릿 관계 (가장 먼저 등록된 1건 = `templates` 정렬의 첫 항목). + * + * 정의 목록 화면은 대표 템플릿 1건의 제목/본문만 그린다. `templates` 를 통째로 eager load + * 하면 한 페이지를 여는 것만으로 그 페이지 전 정의의 모든 채널 템플릿 본문이 응답에 실린다. + * eager load 의 `limit(1)` 은 부모별이 아니라 배치 쿼리 전체에 걸리므로 쓸 수 없고, + * 관계 자체를 "가장 오래된 1건" 으로 좁혀야 정의별 1건이 보장된다. + * + * @return HasOne 대표 메시지 템플릿과의 관계 + */ + public function firstTemplate(): HasOne + { + return $this->hasOne(IdentityMessageTemplate::class, 'definition_id')->oldestOfMany(); } /** diff --git a/app/Repositories/Concerns/PaginatesWithDeferredJoin.php b/app/Repositories/Concerns/PaginatesWithDeferredJoin.php index 3d5f74a4..4587908f 100644 --- a/app/Repositories/Concerns/PaginatesWithDeferredJoin.php +++ b/app/Repositories/Concerns/PaginatesWithDeferredJoin.php @@ -53,7 +53,13 @@ trait PaginatesWithDeferredJoin * @param int $perPage 페이지당 건수 * @param int|null $page 현재 페이지 (null = 요청에서 해석) * @param array $relations outer 전용 eager load 관계 - * @param array $withCount outer 전용 관계 카운트 + * @param array $withCount outer 전용 관계 카운트. `withCount()` 로 + * 그대로 forward 되므로 Laravel 의 두 형태를 + * 모두 받는다 — 목록 형태(`['options']`) 와 + * 제약 클로저를 붙인 연관 형태 + * (`['options as active_count' => fn ($q) => ...]`). + * 별칭 없이 같은 관계를 두 번 세면 기본 별칭이 + * 충돌해 뒤엣것이 앞엣것을 덮으므로 `as` 로 구분한다 * @param string $keyName 키 컬럼명 (기본 id) * @param int|null $total 전체 건수 (null = COUNT 수행, 값 지정 시 캐시 total 사용) * @param bool $simple true = COUNT 없이 다음 페이지 유무만 판정 @@ -134,7 +140,7 @@ trait PaginatesWithDeferredJoin * @param array $sort 정렬 스펙 * @param array $ids 이번 페이지의 키 값 목록 * @param array $relations eager load 관계 - * @param array $withCount 관계 카운트 + * @param array $withCount 관계 카운트 (목록/연관 두 형태 모두 허용) * @param string $keyName 키 컬럼명 * @param bool $preserveIdOrder ID 순서 그대로 복원 여부 * @param Closure|null $outerUsing outer 전용 조인/집계 diff --git a/app/Repositories/IdentityMessageDefinitionRepository.php b/app/Repositories/IdentityMessageDefinitionRepository.php index 7d2ae18a..d3f02c6e 100644 --- a/app/Repositories/IdentityMessageDefinitionRepository.php +++ b/app/Repositories/IdentityMessageDefinitionRepository.php @@ -151,7 +151,11 @@ class IdentityMessageDefinitionRepository implements IdentityMessageDefinitionRe */ public function getPaginated(array $filters = [], int $perPage = 20): LengthAwarePaginator { - $query = IdentityMessageDefinition::with('templates'); + // 목록은 대표 템플릿 1건만 싣는다 — 화면이 그리는 것은 `templates[0]` 의 제목/본문뿐이다. + // `templates` 를 통째로 로드하면 한 페이지를 여는 것만으로 그 페이지 전 정의의 모든 채널 + // 템플릿 본문이 응답에 실린다. 전체 템플릿은 단건 조회(show)가 공급한다. + $query = IdentityMessageDefinition::with('firstTemplate') + ->withCount('templates as templates_count'); if (! empty($filters['provider_id'])) { $query->where('provider_id', $filters['provider_id']); diff --git a/app/Repositories/LayoutExtensionVersionRepository.php b/app/Repositories/LayoutExtensionVersionRepository.php index 3c9b9dab..04f82095 100644 --- a/app/Repositories/LayoutExtensionVersionRepository.php +++ b/app/Repositories/LayoutExtensionVersionRepository.php @@ -48,20 +48,49 @@ class LayoutExtensionVersionRepository implements LayoutExtensionVersionReposito } /** - * 특정 확장의 모든 버전 조회 (최신순) + * 특정 확장의 최근 버전 목록 조회 (최신순) * * @param int $extensionId 레이아웃 확장 ID + * @param int $limit 조회할 최대 버전 수 (버전 행은 정리되지 않고 쌓이므로 상한 필수) * @return Collection 버전 컬렉션 */ - public function getVersions(int $extensionId): Collection + public function getVersions(int $extensionId, int $limit = 100): Collection { // creator eager load — 버전 목록에 저장자 이름(created_by_name) 노출용 (N+1 회피, // 레이아웃 본체 getVersionsByLayoutId 와 동일 정책). + // + // 버전 행은 저장할 때마다 쌓이고 정리되지 않으므로 조회 건수에 상한을 둔다. 목록이 쓰지 + // 않는 `content`(버전마다 확장 본문 사본)도 조회하지 않는다 — 본문은 버전 비교 diff + // 전용이며 단건 조회가 공급한다. (레이아웃 본체와 동일 정책) return $this->model ->with('creator:id,name') ->where('extension_id', $extensionId) ->orderBy('version', 'desc') - ->get(); + ->limit($limit) + ->get(['id', 'extension_id', 'version', 'changes_summary', 'created_by', 'created_at']); + } + + /** + * 확장의 특정 버전 번호 조회 (본문 포함) + * + * 목록(getVersions)에서 뺀 `content` 의 대체 경로다. 목록 조회를 재사용하면 두 가지가 + * 동시에 깨진다 — ① 목록이 조회하지 않는 `content` 가 빈 값이 되어 버전 비교 diff 가 + * "전 항목 삭제"로 표시되고 ② 목록 상한(기본 100건) 밖의 오래된 버전이 404 가 된다. + * (레이아웃 본체 LayoutRepository::findVersionByNumber 와 동일 정책) + * + * @param int $extensionId 레이아웃 확장 ID + * @param int $version 버전 번호 + * @return TemplateLayoutExtensionVersion|null 찾은 버전 모델 또는 null + */ + public function findVersionByNumber(int $extensionId, int $version): ?TemplateLayoutExtensionVersion + { + // creator eager load — 단건 버전 조회도 저장자 이름을 목록과 일관 노출. + return $this->model + ->newQuery() + ->with('creator:id,name') + ->where('extension_id', $extensionId) + ->where('version', $version) + ->first(); } /** diff --git a/app/Repositories/LayoutRepository.php b/app/Repositories/LayoutRepository.php index 8a214465..388a6e17 100644 --- a/app/Repositories/LayoutRepository.php +++ b/app/Repositories/LayoutRepository.php @@ -17,12 +17,25 @@ class LayoutRepository implements LayoutRepositoryInterface /** * 특정 템플릿의 모든 레이아웃 조회 * + * 편집 본문(`content`)은 읽지 않는다. 이 메서드의 남은 소비처는 캐시 무효화 + * (`InvalidatesLayoutCache` 트레이트)뿐이고 그 경로는 캐시 키를 만드는 식별 컬럼만 쓰는데, + * 템플릿 하나에 수십~수백 레이아웃이 있으면 본문 JSON 전량이 메모리에 올라온다. + * + * 목록 응답은 이 메서드가 아니라 {@see getListByTemplateId()} 가 공급하고(본문에서 파생되는 + * 설명·크기만 남기고 본문은 즉시 버린다), 본문 자체가 필요한 경로는 단건 조회 + * (`findByName`)가 공급한다. + * * @param int $templateId 템플릿 ID * @return Collection 레이아웃 컬렉션 */ public function getByTemplateId(int $templateId): Collection { return TemplateLayout::where('template_id', $templateId) + ->select([ + 'id', 'template_id', 'name', 'original_content_hash', 'original_content_size', + 'extends', 'source_type', 'source_identifier', 'created_by', 'updated_by', + 'lock_version', 'created_at', 'updated_at', + ]) ->orderBy('name') ->get(); } @@ -336,15 +349,24 @@ class LayoutRepository implements LayoutRepositoryInterface * 특정 레이아웃의 모든 버전 조회 * * @param int $layoutId 레이아웃 ID + * @param int $limit 조회할 최대 버전 수 (버전 행은 정리되지 않고 쌓이므로 상한 필수) * @return Collection 버전 컬렉션 */ - public function getVersionsByLayoutId(int $layoutId): Collection + public function getVersionsByLayoutId(int $layoutId, int $limit = 100): Collection { // creator eager load — 버전 목록에 저장자 이름(created_by_name) 노출용 (N+1 회피). + // + // 목록이 쓰는 컬럼만 조회한다. `content` 는 버전마다 레이아웃 본문 사본이라, 상한이 있어도 + // 100건이면 본문 100벌을 읽는다. 본문은 버전 비교 diff 전용이며 단건 조회가 공급한다. + // 정렬 기준은 `version` — 레이아웃 안에서 유일하다(uk_layout_version). 종전의 + // `latest()`(created_at)는 같은 초에 저장된 버전들의 순서를 정하지 못해, 상한이 걸린 + // 지금은 "어느 100건이 오는가" 까지 실행마다 달라질 수 있다. return TemplateLayoutVersion::with('creator:id,name') ->where('layout_id', $layoutId) - ->latest() - ->get(); + ->orderBy('version', 'desc') + ->limit($limit) + // 이 테이블에는 updated_at 이 없다 (버전 행은 불변 스냅샷이라 created_at 만 둔다) + ->get(['id', 'layout_id', 'version', 'changes_summary', 'created_by', 'created_at']); } /** @@ -398,9 +420,9 @@ class LayoutRepository implements LayoutRepositoryInterface * 특정 템플릿의 모든 레이아웃 이름 조회 * * @param int $templateId 템플릿 ID - * @return \Illuminate\Support\Collection 레이아웃 이름 컬렉션 + * @return SupportCollection 레이아웃 이름 컬렉션 */ - public function getLayoutNamesByTemplateId(int $templateId): \Illuminate\Support\Collection + public function getLayoutNamesByTemplateId(int $templateId): SupportCollection { return TemplateLayout::where('template_id', $templateId) ->pluck('name') diff --git a/app/Repositories/MenuRepository.php b/app/Repositories/MenuRepository.php index 843c32ad..b9d5b17b 100644 --- a/app/Repositories/MenuRepository.php +++ b/app/Repositories/MenuRepository.php @@ -95,8 +95,13 @@ class MenuRepository implements MenuRepositoryInterface if ($user) { $query->accessibleBy($user); } - $query->with('roles') - ->orderBy('order'); + // 하위 메뉴의 역할은 목록에서 로드하지 않는다 — 화면이 쓰는 것은 하위 메뉴의 + // 개수와 표시 정보뿐이고, 역할 배지는 선택한(상위) 메뉴에만 나온다. 로드하면 + // 상위 메뉴마다 하위 전체의 역할 피벗까지 함께 실려 응답이 배가 된다. + // Resource 는 로드되지 않은 하위 메뉴의 `roles` 키를 **생략**한다(빈 배열이 아니다) + // — 빈 배열은 "역할 제한 없음" 이라는 사실 아닌 단언이 되어, 화면이 그 값을 그대로 + // 저장 요청에 실으면 역할 제한이 통째로 해제된다. + $query->orderBy('order'); }]) ->orderBy('order') ->get(); @@ -470,7 +475,12 @@ class MenuRepository implements MenuRepositoryInterface $childQuery->where('is_active', $filters['is_active']); } - $childQuery->with('roles')->orderBy('order'); + // 하위 메뉴의 역할은 목록에서 로드하지 않는다 — 화면이 쓰는 것은 하위 메뉴의 + // 개수와 표시 정보뿐이고, 역할 배지는 선택한(상위) 메뉴에만 나온다. 로드하면 + // 상위 메뉴마다 하위 전체의 역할 피벗까지 함께 실려 응답이 배가 된다. + // 접근 제어(accessibleBy)는 whereHas 로 결과 집합을 좁히므로 그대로 유지된다. + // Resource 는 로드되지 않은 하위 메뉴의 `roles` 키를 생략한다 — 위 메서드와 동일. + $childQuery->orderBy('order'); }])->get(); } diff --git a/app/Repositories/NotificationDefinitionRepository.php b/app/Repositories/NotificationDefinitionRepository.php index e1da1c0e..482fac38 100644 --- a/app/Repositories/NotificationDefinitionRepository.php +++ b/app/Repositories/NotificationDefinitionRepository.php @@ -131,7 +131,28 @@ class NotificationDefinitionRepository implements NotificationDefinitionReposito */ public function getPaginated(array $filters = [], int $perPage = 20): LengthAwarePaginator { - $query = NotificationDefinition::with('templates'); + // 목록은 템플릿 본문을 싣지 않는다. + // + // 좁히지 않으면 한 페이지를 여는 것만으로 그 페이지 전 정의의 **모든 채널** 제목·본문이 + // 응답에 실린다(정의 하나당 채널 × 로케일). 화면은 한 번에 한 채널만 그리므로 그 채널을 + // `template_channel` 로 지정해 요청한다 — 지정하지 않은 호출자는 템플릿을 받지 않고, + // 필요하면 단건 조회(`GET .../notification-definitions/{id}`)가 전 채널을 제공한다. + $templateChannel = $filters['template_channel'] ?? null; + + $query = NotificationDefinition::query() + // "되돌리기" 버튼 노출 조건은 채널을 가리지 않고 "커스터마이즈된 템플릿이 하나라도 + // 있는가" 다. 템플릿을 한 채널로 좁히면 배열만으로는 알 수 없으므로 집계로 낸다. + ->withCount([ + 'templates as customized_templates_count' => function ($q) { + $q->where('is_default', false); + }, + ]); + + if (! empty($templateChannel)) { + $query->with([ + 'templates' => fn ($q) => $q->where('channel', $templateChannel), + ]); + } if (! empty($filters['extension_type'])) { $query->where('extension_type', $filters['extension_type']); diff --git a/app/Repositories/RoleRepository.php b/app/Repositories/RoleRepository.php index 325dbd07..d453c882 100644 --- a/app/Repositories/RoleRepository.php +++ b/app/Repositories/RoleRepository.php @@ -195,9 +195,19 @@ class RoleRepository implements RoleRepositoryInterface */ public function getPaginated(array $filters = [], int $perPage = 20): LengthAwarePaginator { + // 권한 관계는 목록에서 로드하지 않는다. 목록 화면이 쓰는 것은 이름·설명·사용자 수·활성 + // 여부뿐인데, 로드하면 Resource 가 같은 권한 집합을 `permissions`(계층 트리)· + // `permission_ids`·`permission_values` 세 형태로 중복 직렬화하고, 계층 트리를 만드느라 + // 행마다 상위/카테고리 권한을 다시 조회한다(행 수만큼 추가 쿼리). + // + // Resource 의 `relationLoaded('permissions')` 가드는 그대로 두면 여기서 로드하지 않는 것만으로 + // 세 필드가 함께 빠진다 — 권한 편집은 단건 조회(`GET /admin/roles/{id}`)가 공급한다. + // + // 권한 **개수**는 집계로 남긴다. 목록에서 뺀 값의 대체 경로 — 권한 트리를 전송하지 않고도 + // "이 역할에 권한이 몇 개 걸려 있는가" 를 화면이 보여줄 수 있다. 값 검사가 아니라 별칭 + // 존재 여부로 판정하므로(RoleResource) 0건인 역할도 0 으로 정확히 표시된다. $query = Role::query() - ->withCount('users') - ->with('permissions'); + ->withCount(['users', 'permissions']); // 검색 필터 if (! empty($filters['search'])) { diff --git a/app/Services/LayoutExtensionService.php b/app/Services/LayoutExtensionService.php index d8077fc9..b143da21 100644 --- a/app/Services/LayoutExtensionService.php +++ b/app/Services/LayoutExtensionService.php @@ -1661,21 +1661,24 @@ class LayoutExtensionService } /** - * 특정 확장의 모든 버전을 조회합니다. + * 특정 확장의 최근 버전 목록을 조회합니다 (최신순). + * + * 버전 행은 저장할 때마다 쌓이고 정리되지 않으므로 조회 건수에 상한이 있습니다. * * @param int $extensionId 확장 ID + * @param int $limit 조회할 최대 버전 수 (미지정 시 100) * @return EloquentCollection * * @throws ModelNotFoundException 확장을 찾을 수 없는 경우 */ - public function getExtensionVersions(int $extensionId): EloquentCollection + public function getExtensionVersions(int $extensionId, int $limit = 100): EloquentCollection { HookManager::doAction('core.layout_extension.before_versions_index', $extensionId); // 확장 존재 검증 $this->getExtensionById($extensionId); - $versions = $this->versionRepository->getVersions($extensionId); + $versions = $this->versionRepository->getVersions($extensionId, $limit); return HookManager::applyFilters('core.layout_extension.after_versions_index', $versions, $extensionId); } @@ -1696,8 +1699,10 @@ class LayoutExtensionService // 확장 존재 검증 $this->getExtensionById($extensionId); - $extensionVersion = $this->versionRepository->getVersions($extensionId) - ->firstWhere('version', $version); + // 목록용 getVersions() 를 재사용하지 않는다 — 목록은 경량 조회라 확장 본문(content)을 + // 담지 않고 건수 상한도 있어, 재사용하면 버전 비교가 "전 항목 삭제"로 표시되고 상한 밖의 + // 오래된 버전이 404 가 된다. + $extensionVersion = $this->versionRepository->findVersionByNumber($extensionId, $version); if (! $extensionVersion) { throw new ModelNotFoundException( diff --git a/app/Services/LayoutService.php b/app/Services/LayoutService.php index 4cc5e8cd..e0f03b6e 100644 --- a/app/Services/LayoutService.php +++ b/app/Services/LayoutService.php @@ -1479,13 +1479,17 @@ class LayoutService } /** - * 특정 레이아웃의 모든 버전 조회 + * 특정 레이아웃의 최근 버전 목록 조회 (최신순) + * + * 버전 행은 저장할 때마다 쌓이고 정리되지 않으므로 조회 건수에 상한을 둡니다. + * 더 오래된 이력이 필요하면 호출자가 `$limit` 을 넓힙니다. * * @param int $templateId 대상 템플릿 ID * @param string $name 레이아웃 이름 + * @param int $limit 조회할 최대 버전 수 * @return Collection 버전 컬렉션 */ - public function getLayoutVersions(int $templateId, string $name) + public function getLayoutVersions(int $templateId, string $name, int $limit = 100) { // Before 훅 - 버전 목록 조회 전 HookManager::doAction('core.layout.before_versions_index', $templateId, $name); @@ -1500,7 +1504,7 @@ class LayoutService } // 버전 목록 조회 - $versions = $this->layoutRepository->getVersionsByLayoutId($layout->id); + $versions = $this->layoutRepository->getVersionsByLayoutId($layout->id, $limit); // After 훅 - 버전 목록 조회 후 HookManager::doAction('core.layout.after_versions_index', $versions, $templateId, $name); diff --git a/app/Support/ApiDoc/ApiDocScaffolder.php b/app/Support/ApiDoc/ApiDocScaffolder.php index 000a2d1f..bfbfa24f 100644 --- a/app/Support/ApiDoc/ApiDocScaffolder.php +++ b/app/Support/ApiDoc/ApiDocScaffolder.php @@ -29,6 +29,14 @@ class ApiDocScaffolder */ private const LIST_EXAMPLE_LIMIT = 2; + /** + * @var int 응답 예시 한 건의 최대 크기 (pretty JSON 기준 바이트) + * + * 목록 절단(`data.data[]`)만으로는 카탈로그·스펙처럼 목록이 아닌 큰 응답을 막지 못한다. + * 상한을 넘으면 절단 강도를 단계적으로 올려 이 크기 이하로 줄인다. + */ + private const EXAMPLE_MAX_BYTES = 8192; + /** * @var string 요청 예시의 토큰 마스킹 placeholder * @@ -792,7 +800,10 @@ class ApiDocScaffolder // 응답 body 내부의 절대 URL(페이지네이터 링크·콜백 URL 등)에 실측 기준 호스트가 // 그대로 직렬화돼 들어올 수 있으므로 공개 문서용 placeholder 호스트로 마스킹한다. $masked = $this->maskResponseHost($sanitized, $probeMeta['base_url'] ?? null); - $json = json_encode($masked, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); + // 목록 절단은 페이지네이션 형태(`data.data[]`)만 다룬다. 카탈로그·스펙처럼 목록이 아닌 + // 큰 응답은 그대로 두면 문서 한 파일이 수 MB 로 불어난다 — 전체 크기 상한을 별도로 건다. + $capped = $this->capExampleSize($masked); + $json = json_encode($capped, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); return "```http\nHTTP/1.1 {$status}\n```\n\n```json\n".(string) $json."\n```"; } @@ -911,6 +922,101 @@ class ApiDocScaffolder return $body; } + /** + * 응답 예시 전체 크기를 상한 이하로 줄입니다. + * + * `truncateListBody()` 는 페이지네이션 형태(`data.data[]`)만 절단하므로, 편집기 스펙·컴포넌트 + * 카탈로그·라우트 목록처럼 목록이 아닌 큰 응답은 통째로 직렬화된다. 그대로 두면 문서 한 파일이 + * 수 MB 가 되어 리뷰·git·에디터가 모두 망가진다(#518 에서 templates.md 가 115KB → 8.65MB). + * + * 절단 강도를 단계적으로 올리며 상한 이하가 될 때까지 재시도하고, 그래도 넘으면 최상위 키만 + * 남긴 요약으로 대체한다. 어느 경우에도 "무엇이 잘렸는지" 를 문서에 남긴다. + * + * @param array $body 마스킹까지 끝난 응답 body + * @return array 상한 이하로 절단된 body + */ + private function capExampleSize(array $body): array + { + if ($this->encodedSize($body) <= self::EXAMPLE_MAX_BYTES) { + return $body; + } + + // (배열 최대 항목 수, 문자열 최대 길이) 를 점점 조인다. + foreach ([[5, 400], [3, 200], [1, 80]] as [$maxItems, $maxChars]) { + $reduced = $this->shrinkNode($body, $maxItems, $maxChars); + + if ($this->encodedSize($reduced) <= self::EXAMPLE_MAX_BYTES) { + return $reduced; + } + } + + // 여기까지 와도 상한을 넘으면 구조만 남긴다. + $originalSize = $this->encodedSize($body); + $data = $body['data'] ?? null; + $keys = is_array($data) ? array_slice(array_keys($data), 0, 20) : []; + + $body['data'] = [ + '// 응답이 너무 커서 예시로 싣지 않습니다 (약 '.number_format($originalSize).' bytes)', + '// 최상위 키: '.($keys === [] ? '(스칼라 또는 빈 응답)' : implode(', ', $keys)), + ]; + + return $body; + } + + /** + * 노드를 재귀적으로 축소합니다. 배열은 항목 수를, 문자열은 길이를 자릅니다. + * + * @param mixed $node 축소 대상 + * @param int $maxItems 배열/객체에서 남길 최대 항목 수 + * @param int $maxChars 문자열 최대 길이 + * @return mixed 축소된 노드 + */ + private function shrinkNode(mixed $node, int $maxItems, int $maxChars): mixed + { + if (is_string($node)) { + return mb_strlen($node) > $maxChars + ? mb_substr($node, 0, $maxChars).'… (생략)' + : $node; + } + + if (! is_array($node)) { + return $node; + } + + $isList = array_is_list($node); + $total = count($node); + $sliced = array_slice($node, 0, $maxItems, ! $isList); + + $result = []; + foreach ($sliced as $key => $value) { + $result[$key] = $this->shrinkNode($value, $maxItems, $maxChars); + } + + if ($total > $maxItems) { + $omitted = $total - $maxItems; + if ($isList) { + $result[] = "... (총 {$total}건 중 {$maxItems}건 표시)"; + } else { + $result['...'] = "({$omitted}개 키 생략, 총 {$total}개)"; + } + } + + return $result; + } + + /** + * 노드를 문서에 실릴 형태로 인코딩했을 때의 바이트 수를 반환합니다. + * + * @param mixed $node 측정 대상 + * @return int 바이트 수 (인코딩 실패 시 상한 초과로 간주) + */ + private function encodedSize(mixed $node): int + { + $json = json_encode($node, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); + + return $json === false ? PHP_INT_MAX : strlen($json); + } + /** * 마크다운 표 셀 안에서 안전하도록 파이프/개행을 이스케이프합니다. * @@ -941,13 +1047,393 @@ class ApiDocScaffolder foreach ($sections as $i => $section) { $key = $sectionKeys[$i]; - $preserved = $this->extractHumanProse($existing, $key); - $merged .= $this->applyPreservedProse($section, $preserved)."\n"; + // 아직 생성 블록이 없던(사람이 직접 쓴) 섹션은 헤딩으로 찾는다. 키로만 찾으면 + // 최초 생성 회차에 그 섹션의 서술·표 설명이 통째로 사라진다. + $legacy = $this->extractLegacySection($existing, $section, $key); + + $preserved = $this->extractHumanProse($existing, $key) + ?? ($legacy === null ? null : $this->extractLegacyProse($legacy)); + $withProse = $this->applyPreservedProse($section, $preserved); + // 순서 주의: 표 자체를 되살린 뒤에 셀 설명을 맞춘다. + $withFields = $this->restoreEmptyProbeTable($withProse, $existing, $key, $legacy); + $withTables = $this->restoreTableDescriptions($withFields, $existing, $key, $legacy); + $merged .= $this->restoreErrorSection($withTables, $existing, $key, $legacy)."\n"; } return $merged; } + /** + * 새 섹션의 TODO 설명 셀을 기존 문서의 사람 서술로 되돌립니다. + * + * `@generated` 블록의 파라미터/응답 필드 표는 재생성 때마다 통째로 다시 조립되므로, + * 정적 추출로 재현할 수 없는 도메인 설명(예: `no_category: 카테고리 미지정 상품만 필터`)이 + * TODO 스텁으로 되돌아간다. 사람이 채운 설명이 재생성 한 번에 사라지는 것은 문서 손실이므로, + * 행 이름이 같고 새 셀이 TODO 스텁일 때만 기존 설명을 이어받는다. + * + * 기존 셀에 설명이 있으면 항상 그쪽을 쓴다. 도구가 만드는 설명은 필드명에서 유추한 일반 + * 문구(예: `대상의 이름/명칭`)라 사람이 적은 도메인 사실(예: `플러그인 이름 (다국어 JSON)`, + * `desc 는 주문별 가장 늦은 발송일`)보다 언제나 정보량이 적다. 문서 헤더가 약속한 + * "사람이 작성한 설명은 보존됩니다" 를 표 셀에도 적용한다. 사실이 바뀌면 사람이 그 셀을 + * 고치거나 비우고, 비운 셀은 다음 재생성이 채운다. + * + * @param string $section 새로 생성된 섹션 + * @param string $existing 기존 문서 전체 + * @param string $key 라우트명(생성 블록 키) + * @param string|null $legacy 생성 블록이 없던 사람 작성 섹션 원문 (헤딩 매칭 폴백) + * @return string 설명이 복원된 섹션 + */ + private function restoreTableDescriptions(string $section, string $existing, string $key, ?string $legacy = null): string + { + $oldBlock = $this->extractGeneratedBlockBody($existing, $key) ?? $legacy; + if ($oldBlock === null) { + return $section; + } + + $oldDescriptions = $this->collectRowDescriptions($oldBlock); + if ($oldDescriptions === []) { + return $section; + } + + $lines = explode("\n", $section); + foreach ($lines as $index => $line) { + if (! str_starts_with(trim($line), '|')) { + continue; + } + + $cells = $this->splitRowCells($line); + if (count($cells) < 2) { + continue; + } + + $lastIndex = count($cells) - 1; + $rowKey = $this->rowKey($cells); + if ($rowKey === null || ! isset($oldDescriptions[$rowKey])) { + continue; + } + + $cells[$lastIndex] = ' '.$oldDescriptions[$rowKey].' '; + $lines[$index] = '|'.implode('|', $cells).'|'; + } + + return implode("\n", $lines); + } + + /** + * 이번 실측이 빈 응답이라 필드 표가 사라졌으면 기존 표를 되살립니다. + * + * 실측 대상 데이터가 없는 상태로 재생성하면 응답 필드 표가 통째로 + * `` 한 줄로 대체된다. 이미 문서화돼 있던 필드 목록이 + * "이번에 데이터가 없었다" 는 이유로 사라지는 것은 사실의 후퇴이므로, 기존 표가 있으면 유지한다 + * (#518 에서 reviews.md 한 파일에서만 388줄이 이렇게 사라졌다). + * + * @param string $section 새로 생성된 섹션 + * @param string $existing 기존 문서 전체 + * @param string $key 라우트명(생성 블록 키) + * @param string|null $legacy 생성 블록이 없던 사람 작성 섹션 원문 + * @return string 필드 표가 복원된 섹션 + */ + private function restoreEmptyProbeTable(string $section, string $existing, string $key, ?string $legacy = null): string + { + $newFields = $this->sliceFieldSection($section); + + // 이번 실측이 필드를 확보했으면(표가 있으면) 그대로 둔다. + if ($newFields === null || ! str_contains($newFields, '실측 응답에 필드 없음')) { + return $section; + } + + $oldBlock = $this->extractGeneratedBlockBody($existing, $key) ?? $legacy; + if ($oldBlock === null) { + return $section; + } + + $oldFields = $this->sliceFieldSection($oldBlock); + if ($oldFields === null || ! str_contains($oldFields, '| --- |')) { + return $section; + } + + return str_replace($newFields, $oldFields, $section); + } + + /** + * 섹션에서 `**응답 필드**` 이후 ~ 다음 구획 전까지의 본문을 잘라냅니다. + * + * @param string $text 섹션 또는 생성 블록 본문 + * @return string|null 응답 필드 구간 본문 (없으면 null) + */ + private function sliceFieldSection(string $text): ?string + { + $pos = strpos($text, '**응답 필드**'); + if ($pos === false) { + return null; + } + + $rest = substr($text, $pos); + $lineEnd = strpos($rest, "\n"); + if ($lineEnd === false) { + return null; + } + + $body = substr($rest, $lineEnd); + $at = strpos($body, "\n**"); + $end = $at === false ? strlen($body) : $at; + + $slice = trim(substr($body, 0, $end)); + + return $slice === '' ? null : $slice; + } + + /** + * 에러 응답 구간이 일반 스텁으로 되돌아가면 기존의 사람 서술을 되살립니다. + * + * `errorTable()` 은 라우트 메타만 보고 "대표 에러 없음 (공개 조회)" 같은 일반 문구를 만든다. + * 사람이 도메인 특이 에러(예: "활성 에셋이 없으면 빈 200 응답")를 적어 뒀다면 재생성 한 번에 + * 그 사실이 사라지므로, 새 문구가 TODO 를 포함한 스텁일 때만 기존 구간을 이어받는다. + * + * @param string $section 새로 생성된 섹션 + * @param string $existing 기존 문서 전체 + * @param string $key 라우트명(생성 블록 키) + * @param string|null $legacy 생성 블록이 없던 사람 작성 섹션 원문 + * @return string 에러 구간이 복원된 섹션 + */ + private function restoreErrorSection(string $section, string $existing, string $key, ?string $legacy = null): string + { + $newError = $this->sliceErrorSection($section); + + // 새 문구가 스텁이 아니면(=도구가 실제 에러를 뽑았으면) 그대로 둔다. + if ($newError === null || ! str_contains($newError, '')) { + return null; + } + + $heading = strtok($section, "\n"); + if (! is_string($heading) || ! str_starts_with($heading, '### ')) { + return null; + } + + $startPos = strpos($existing, "\n".$heading."\n"); + if ($startPos === false) { + return null; + } + + $rest = substr($existing, $startPos + 1); + $nextHeading = preg_match('/\n### /', $rest, $m, PREG_OFFSET_CAPTURE) + ? $m[0][1] + : strlen($rest); + + return substr($rest, 0, $nextHeading); + } + + /** + * 사람 작성 섹션 원문에서 `**설명**` 이후의 서술을 뽑아냅니다. + * + * @param string $legacy 사람 작성 섹션 원문 + * @return string|null 보존할 서술 (없으면 null) + */ + private function extractLegacyProse(string $legacy): ?string + { + $pos = strpos($legacy, '**설명**'); + if ($pos === false) { + return null; + } + + $prose = trim(substr($legacy, $pos)); + + if ($prose === '' || Str::contains($prose, 'TODO: 이 엔드포인트의 용도')) { + return null; + } + + return $prose; + } + + /** + * 기존 문서에서 라우트의 `@generated` 블록 본문만 잘라냅니다 (헤딩 제외). + * + * 헤딩까지 함께 되살리는 {@see self::extractGeneratedBlock()} 과 달리, 표 셀 대조에만 + * 쓰이므로 블록 본문만 필요하다. + * + * @param string $existing 기존 문서 전체 + * @param string $key 라우트명(생성 블록 키) + * @return string|null 블록 본문 (없으면 null) + */ + private function extractGeneratedBlockBody(string $existing, string $key): ?string + { + $startPos = strpos($existing, self::GEN_START.$key.' -->'); + if ($startPos === false) { + return null; + } + + $endPos = strpos($existing, self::GEN_END, $startPos); + if ($endPos === false) { + return null; + } + + return substr($existing, $startPos, $endPos - $startPos); + } + + /** + * 표 블록에서 `행 키 => 설명` 맵을 수집합니다 (TODO 셀은 제외). + * + * @param string $block 생성 블록 본문 + * @return array 행 키 => 설명 + */ + private function collectRowDescriptions(string $block): array + { + $map = []; + + foreach (explode("\n", $block) as $line) { + if (! str_starts_with(trim($line), '|')) { + continue; + } + + $cells = $this->splitRowCells($line); + if (count($cells) < 2) { + continue; + } + + $description = trim($cells[count($cells) - 1]); + if ($description === '' || str_contains($description, '` 마커로 남긴다. -- 두 예시 블록은 전량 `@generated` 블록 **내부**에 있다. 다만 이미 파라미터/응답 필드 표의 사람 서술이 - 채워진 문서에 예시를 추가할 때는 전체 재생성(`api:docgen`)을 쓰지 않는다 — 전체 재생성은 표를 통째로 - 다시 조립해 정적 추출로 재현 불가능한 도메인 서술 셀을 TODO 로 되돌린다. 대신 `api:docgen --examples-only` - 로 표·서술을 건드리지 않고 예시 2블록만 in-place 삽입/치환한다(측정은 재수행하되 표는 불가침). 백필 - 커맨드(`backfill-params`/`backfill-fields`)도 예시 블록을 건드리지 않는다. +- 두 예시 블록은 전량 `@generated` 블록 **내부**에 있다. 이미 파라미터/응답 필드 표의 사람 서술이 + 채워진 문서에 예시만 추가할 때는 `api:docgen --examples-only` 로 표·서술을 건드리지 않고 예시 2블록만 + in-place 삽입/치환한다(측정은 재수행하되 표는 불가침). 백필 커맨드(`backfill-params`/`backfill-fields`)도 + 예시 블록을 건드리지 않는다. + +### 재생성이 지우지 못하는 것 + +전체 재생성(`api:docgen`)은 표를 통째로 다시 조립하지만, **이미 문서에 있던 사실을 지우지 않는다**. +"이번 실행이 알아내지 못했다" 는 "그 사실이 없다" 와 다르기 때문이다. 병합 단계가 다음을 보존한다. + +| 대상 | 규칙 | +| --- | --- | +| 표의 설명 셀 | 기존 설명이 있으면 유지. 도구가 만드는 문구는 필드명에서 유추한 일반 문장이라 사람이 적은 도메인 사실보다 정보량이 적다. 값이 바뀌는 컬럼(타입·예시·허용값)은 그대로 재생성된다 | +| 엔드포인트 서술(`**설명**`) | 생성 블록 뒤의 사람 서술을 원문 그대로 되살린다. 생성 마커가 없던 수기 섹션은 헤딩(`### METHOD /uri`)으로 찾아 보존한다 | +| 응답 필드 표 | 이번 실측이 빈 응답이라 표가 사라지는 경우 기존 표를 유지한다 | +| 에러 응답 구간 | 새 문구가 TODO 스텁일 때만 기존 서술을 이어받는다 | + +설명을 갱신하려면 그 셀을 직접 고치거나 비운다 — 비운 셀은 다음 재생성이 채운다. + +### 응답 예시 크기 상한 + +응답 예시는 pretty JSON 기준 8KB 를 넘지 않는다. 목록 형태(`data.data[]`)는 대표 2건으로 자르고, 목록이 +아닌 큰 응답(편집기 스펙·컴포넌트 카탈로그·라우트 목록 등)은 절단 강도를 단계적으로 올려 상한 이하로 +줄인다. 잘린 자리에는 `... (총 N건 중 K건 표시)` / `(N개 키 생략, 총 M개)` 표기를 남겨 무엇이 빠졌는지 +문서에 드러낸다. 상한이 없으면 문서 한 파일이 수 MB 가 되어 리뷰·git·에디터가 모두 망가진다. - **`--examples-only` 모드**: 기존 문서의 각 엔드포인트 `@generated` 블록에 요청 예시(응답 필드 표 앞)와 응답 예시(에러 응답 표 앞)만 삽입한다. 이미 예시가 있으면 그 블록만 새 실측으로 치환(멱등, 실측 상대시간 필드는 재측정으로 값이 달라질 수 있음). 표·필드 설명·엔드포인트 서술은 전량 보존된다. 최초 스캐폴딩이 diff --git a/docs/backend/api-resources.md b/docs/backend/api-resources.md index 25eecbfa..a7be87ff 100644 --- a/docs/backend/api-resources.md +++ b/docs/backend/api-resources.md @@ -315,6 +315,82 @@ public function toListArray(): array --- +## 목록 응답의 하위 컬렉션 + +목록은 화면이 그 행에서 **실제로 그리는 것**만 싣는다. 행마다 하위 컬렉션을 통째로 직렬화하면 한 페이지를 여는 것만으로 수백~수천 행이 응답에 실린다. + +### 가짜 가드 + +```php +// ❌ 가드처럼 보이지만 가드가 아니다 +'options' => $this->relationLoaded('options') + ? ProductOptionResource::collection($this->options) + : $this->whenLoaded('activeOptions'), +``` + +Repository 가 목록 쿼리에서 `options` 를 무조건 eager load 하면 이 조건은 **항상 참**이 되어, 방어하는 것처럼 보이는 코드가 실제로는 아무것도 막지 못한다. + +```php +// ✅ whenLoaded 하나만 — 로드 여부는 Repository 가 결정한다 +'options' => $this->whenLoaded('options', fn () => ProductOptionResource::collection($this->options)), +``` + +### 개수·합계는 DB 집계로 + +배열을 실어 보내고 화면에서 세지 않는다. PHP 컬렉션 연산(`$this->options->where(...)->sum(...)`)도 같은 문제다 — 세려면 이미 전부 메모리에 올라와 있어야 한다. + +```php +// ✅ Repository 의 withCount / withSum 결과를 그대로 노출 +'options_count' => $this->whenHas('options_count', fn () => (int) $this->options_count), +``` + +집계 별칭의 존재 여부는 값 검사(`!== null`)로 판정할 수 없다. `SUM` 은 대상 0건에서 `NULL` 을 돌려주므로 "집계하지 않았다" 와 "집계했더니 비어 있다" 가 구분되지 않는다. 속성 키의 존재 여부(`array_key_exists`)만이 두 상황을 가른다. + +### 대표 1건이 필요할 때 + +목록이 하위 컬렉션의 **첫 1건만** 그린다면 관계 자체를 1건으로 좁힌다. eager load 의 `limit(1)` 은 부모별이 아니라 배치 쿼리 전체에 걸리므로 첫 행만 값을 받고 나머지는 빈 값이 된다. + +```php +// 모델 — 관계를 "가장 오래된 1건" 으로 정의 +public function firstOption(): HasOne +{ + return $this->hasOne(OrderOption::class, 'order_id')->oldestOfMany(); +} +``` + +### 목록 표현은 명시 호출 + +`toListArray()` 를 정의해 두고 컬렉션이 `toArray()` 를 호출하면 경량 표현은 쓰이지 않는다. 컨트롤러나 컬렉션이 목록 표현을 **명시적으로** 부르는지 확인한다. + +```php +// ✅ 컬렉션이 목록 표현을 명시 호출 +'data' => $this->collection->map(fn ($row) => (new SampleResource($row))->toListArray($request))->all(), +``` + +이 형태는 Laravel 의 `MissingValue` 제거 단계를 거치지 않으므로, 조건부 필드를 직접 걸러내야 한다(위 "커스텀 메서드" 절 참조). 걸러내지 않으면 미충족 필드가 `{}` 로 응답에 남는다. + +### 목록 Resource 안에서 관계를 재쿼리하지 않는다 + +```php +// ❌ eager load 된 컬렉션을 무시하고 행마다 쿼리 +'thumbnail' => $this->images()->first()?->url, + +// ✅ 로드된 컬렉션에서 고른다 +'thumbnail' => $this->relationLoaded('images') + ? $this->images->firstWhere('is_thumbnail', true)?->url + : null, +``` + +### 뺀 값에는 대체 경로가 있어야 한다 + +목록에서 제거한 값은 (a) 집계로 대체되거나 (b) 단건 조회에 그대로 남아 있어야 한다. 어느 쪽도 아니면 그것은 성능 개선이 아니라 기능 삭제다. + +착수 전 **소비처를 실측한다.** 화면이 그 값을 실제로 순회·렌더하면 제거는 기능 축소다 — 계획에 "안 쓴다" 고 적혀 있어도 레이아웃 JSON 을 열어 확인한다. + +단건 조회가 대체 경로라면, 단건이 목록용 조회를 **재사용하지 않는지** 확인한다. 목록 조회는 컬럼을 좁히고 건수 상한을 두므로, 단건이 그것을 재사용하면 목록 최적화가 그대로 단건의 기능 삭제가 된다. + +--- + ## 패턴 예시 ### 기본 리소스 클래스 diff --git a/docs/backend/api/README.md b/docs/backend/api/README.md index 10aa5cc9..4ce65bb6 100644 --- a/docs/backend/api/README.md +++ b/docs/backend/api/README.md @@ -154,7 +154,7 @@ location ~* \.(js|css|json)$ { expires max; access_log off; } ## 코어 API 레퍼런스 -- **문서 수**: 35 · **엔드포인트 수**: 291 +- **문서 수**: 36 · **엔드포인트 수**: 319 | 문서 | 도메인 | 엔드포인트 | | --- | --- | --- | @@ -190,6 +190,7 @@ location ~* \.(js|css|json)$ { expires max; access_log off; } | [search.md](search.md) | `search` | 1 | | [seo.md](seo.md) | `seo` | 5 | | [settings.md](settings.md) | `settings` | 15 | +| [system.md](system.md) | `system` | 2 | | [templates.md](templates.md) | `templates` | 57 | | [users.md](users.md) | `users` | 12 | | [verify-password.md](verify-password.md) | `verify-password` | 1 | @@ -201,13 +202,13 @@ location ~* \.(js|css|json)$ { expires max; access_log off; } > 각 확장이 자신의 API 문서를 소유합니다. 아래 표는 자동 생성됩니다. -- **확장 수**: 13 · **엔드포인트 수**: 390 +- **확장 수**: 13 · **엔드포인트 수**: 391 | 확장 | 유형 | API 문서 목차 | 문서/엔드포인트 | | --- | --- | --- | --- | | `gnuboard7-hello_module` | 모듈 | [docs/api/](../../../modules/_bundled/gnuboard7-hello_module/docs/api/README.md) | 1 / 7 | | `sirsoft-board` | 모듈 | [docs/api/](../../../modules/_bundled/sirsoft-board/docs/api/README.md) | 10 / 80 | -| `sirsoft-ecommerce` | 모듈 | [docs/api/](../../../modules/_bundled/sirsoft-ecommerce/docs/api/README.md) | 33 / 231 | +| `sirsoft-ecommerce` | 모듈 | [docs/api/](../../../modules/_bundled/sirsoft-ecommerce/docs/api/README.md) | 33 / 232 | | `sirsoft-page` | 모듈 | [docs/api/](../../../modules/_bundled/sirsoft-page/docs/api/README.md) | 2 / 17 | | `sirsoft-ckeditor5` | 플러그인 | [docs/api/](../../../plugins/_bundled/sirsoft-ckeditor5/docs/api/README.md) | 2 / 2 | | `sirsoft-gdpr` | 플러그인 | [docs/api/](../../../plugins/_bundled/sirsoft-gdpr/docs/api/README.md) | 4 / 15 | diff --git a/docs/backend/api/activity-logs.md b/docs/backend/api/activity-logs.md index 4e66ebdd..95e50267 100644 --- a/docs/backend/api/activity-logs.md +++ b/docs/backend/api/activity-logs.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Activity Logs 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` @@ -57,25 +57,25 @@ _목록 응답: `data.data[]` 배열 항목의 필드 + `data.pagination`._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| number | integer | `87675` | 목록에서의 순번 (페이지네이션 반영 행 번호 — HasRowNumber 파생) | -| id | integer | `210842` | 기본 키 (내부 식별자) | +| number | integer | `88816` | 목록에서의 순번 (페이지네이션 반영 행 번호 — HasRowNumber 파생) | +| id | integer | `89118` | 기본 키 (내부 식별자) | | log_type | string | `user` | 로그 유형 (admin: 관리자, user: 사용자, system: 시스템) | | log_type_label | string | `사용자` | `log_type` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | -| loggable_type | string | `Modules\Sirsoft\Board\Models\Attachment` | 로그가 연관된 대상 리소스의 모델 클래스 FQCN (loggable 다형성 관계 타입) | -| loggable_type_display | string | `Attachment` | `loggable_type` 의 표시용 짧은 이름 (네임스페이스 제외 클래스명 파생) | -| loggable_id | integer | `155` | loggable 식별자 (연관 리소스 참조) | -| action | string | `attachment.download` | 액션 유형 (created, updated, deleted, login, export 등) | -| action_label | string | `다운로드` | `action` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | -| localized_description | string | `첨부파일 다운로드 (게시물: 237)` | `description` 의 현재 로케일 해석 값 (다국어 필드를 표시용 문자열로 해석) | -| description_key | string | `sirsoft-board::activity_log.descripti…` | 다국어 번역 키 (예: activity_log.description.user_create) | -| properties | object | `{"original_filename":"apidoc-sample.png","post_id":237,"c…` | 변경 상세 데이터 (old/new 값) | -| changes | object | `{"status":{"old":"inactive","new":"active","label":""}}` | 구조화된 변경 이력 (필드별 label_key, old, new, type) | +| loggable_type | string | `App\Models\User` | 로그가 연관된 대상 리소스의 모델 클래스 FQCN (loggable 다형성 관계 타입) | +| loggable_type_display | string | `User` | `loggable_type` 의 표시용 짧은 이름 (네임스페이스 제외 클래스명 파생) | +| loggable_id | integer | `1209` | loggable 식별자 (연관 리소스 참조) | +| action | string | `auth.login` | 액션 유형 (created, updated, deleted, login, export 등) | +| action_label | string | `로그인` | `action` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | +| localized_description | string | `관리자 로그인` | `description` 의 현재 로케일 해석 값 (다국어 필드를 표시용 문자열로 해석) | +| description_key | string | `activity_log.description.auth_login` | 다국어 번역 키 (예: activity_log.description.user_create) | +| properties | object | `{"result_count":82}` | 변경 상세 데이터 (old/new 값) | +| changes | array | `[{"field":"stock_quantity","label_key":"sirsoft-ecommerce…` | 구조화된 변경 이력 (필드별 label_key, old, new, type) | | bulk_changes | null | `null` | 일괄 수정 로그의 모델별 변경 이력 배열 (원소: model_id + changes[]). 단일 수정 로그이면 null이고 대신 changes 필드가 채워짐 | | has_changes | boolean | `false` | changes 여부 | -| actor_name | string | `API 문서 샘플 사용자` | 행위를 수행한 주체(사용자/시스템)의 이름 | -| user | object | `{"uuid":"a231747f-e82e-4cf2-9ae1-a261849dce40","name":"AP…` | 행위를 수행한 사용자 정보 (uuid/name/email). 시스템 발생 로그로 사용자가 없으면 name 에 "시스템" 라벨만 담김 | +| actor_name | string | `최고관리자` | 행위를 수행한 주체(사용자/시스템)의 이름 | +| user | object | `{"uuid":"a26219fc-94a0-4f63-9404-04c2a6ac99e4","name":"최고…` | 행위를 수행한 사용자 정보 (uuid/name/email). 시스템 발생 로그로 사용자가 없으면 name 에 "시스템" 라벨만 담김 | | ip_address | string | `127.0.0.1` | IP 주소 (IPv6 대응) | -| created_at | string | `2026-07-07 10:00:47` | 생성 일시 | +| created_at | string | `2026-08-04 19:00:10` | 생성 일시 | | is_owner | boolean | `true` | 현재 인증 사용자가 이 리소스의 소유자인지 여부 (BaseApiResource 표준 메타) | | abilities | object | `{"can_read":true,"can_delete":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | @@ -92,31 +92,29 @@ HTTP/1.1 200 "data": { "data": [ { - "number": 22, - "id": 22, - "log_type": "admin", - "log_type_label": "관리자", - "loggable_type": null, - "loggable_type_display": null, - "loggable_id": null, - "action": "user.index", - "action_label": "목록 조회", - "localized_description": "사용자 목록 조회", - "description_key": "activity_log.description.user_index", - "properties": { - "result_count": 2 - }, + "number": 88816, + "id": 89118, + "log_type": "user", + "log_type_label": "사용자", + "loggable_type": "App\\Models\\User", + "loggable_type_display": "User", + "loggable_id": 1209, + "action": "auth.login", + "action_label": "로그인", + "localized_description": "관리자 로그인", + "description_key": "activity_log.description.auth_login", + "properties": null, "changes": null, "bulk_changes": null, "has_changes": false, - "actor_name": "API 문서 샘플 사용자", + "actor_name": "최고관리자", "user": { - "uuid": "a234c2b1-cde8-437f-b28b-23323be2b98d", - "name": "API 문서 샘플 사용자", - "email": "apidoc-sample-user@example.com" + "uuid": "a26219fc-94a0-4f63-9404-04c2a6ac99e4", + "name": "최고관리자", + "email": "heuristing@gmail.com" }, "ip_address": "127.0.0.1", - "created_at": "2026-07-08 12:12:12", + "created_at": "2026-08-04 19:00:10", "is_owner": true, "abilities": { "can_read": true, @@ -124,47 +122,45 @@ HTTP/1.1 200 } }, { - "number": 21, - "id": 21, + "number": 88815, + "id": 89117, "log_type": "admin", "log_type_label": "관리자", - "loggable_type": null, - "loggable_type_display": null, - "loggable_id": null, - "action": "user.index", - "action_label": "목록 조회", - "localized_description": "사용자 목록 조회", - "description_key": "activity_log.description.user_index", - "properties": { - "result_count": 2 - }, + "loggable_type": "App\\Models\\TemplateLayout", + "loggable_type_display": "TemplateLayout", + "loggable_id": 62, + "action": "layout.update", + "action_label": "수정", + "localized_description": "레이아웃 수정 (home)", + "description_key": "activity_log.description.layout_update", + "properties": null, "changes": null, "bulk_changes": null, "has_changes": false, - "actor_name": "API 문서 샘플 사용자", + "actor_name": "최고관리자", "user": { - "uuid": "a234c2b1-cde8-437f-b28b-23323be2b98d", - "name": "API 문서 샘플 사용자", - "email": "apidoc-sample-user@example.com" + "uuid": "a26219fc-94a0-4f63-9404-04c2a6ac99e4", + "name": "최고관리자", + "email": "heuristing@gmail.com" }, "ip_address": "127.0.0.1", - "created_at": "2026-07-08 12:08:26", + "created_at": "2026-08-04 17:40:37", "is_owner": true, "abilities": { "can_read": true, "can_delete": true } }, - "... (총 22건 중 2건 표시)" + "... (총 25건 중 2건 표시)" ], "pagination": { "current_page": 1, - "last_page": 1, + "last_page": 3553, "per_page": 25, - "total": 22, + "total": 88816, "from": 1, - "to": 22, - "has_more_pages": false + "to": 25, + "has_more_pages": true }, "abilities": { "can_delete": true @@ -216,7 +212,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -250,7 +246,7 @@ Content-Type: application/json **요청 예시** ```http -DELETE /api/admin/activity-logs/1 HTTP/1.1 +DELETE /api/admin/activity-logs/159 HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} diff --git a/docs/backend/api/attachment.md b/docs/backend/api/attachment.md index ba6a2986..af9eb195 100644 --- a/docs/backend/api/attachment.md +++ b/docs/backend/api/attachment.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Attachment 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` diff --git a/docs/backend/api/attachments.md b/docs/backend/api/attachments.md index 771100b6..0625dabe 100644 --- a/docs/backend/api/attachments.md +++ b/docs/backend/api/attachments.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Attachments 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` @@ -34,9 +34,7 @@ | source_type | body | string | 아니오 | — | 첨부 생성 출처 구분 (AttachmentSourceType Enum — core: 코어 시스템, module: 모듈, plugin: 플러그인). 미지정 시 core로 기본 설정 | | source_identifier | body | string | 아니오 | max 255 | 출처 식별자 | -> 이 엔드포인트는 확장이 파라미터를 추가할 수 있습니다 (`core.attachment.upload_validation_rules`). -> -> 업로드 가능한 확장자는 환경설정의 허용 형식 목록을 따르며, 확장은 `core.attachment.allowed_extensions` 필터로 목록을 조정할 수 있습니다 (허용 목록이 비어 있으면 확장자를 제한하지 않습니다). +> 이 엔드포인트는 확장이 파라미터를 추가할 수 있습니다 (`core.attachment.upload_validation_rules`, `core.attachment.allowed_extensions`). **요청 예시** @@ -77,7 +75,7 @@ example-key **응답 필드** (`data` 내부) - + **응답 예시** @@ -113,9 +111,7 @@ example-key | source_type | body | string | 아니오 | — | 첨부 생성 출처 구분 (AttachmentSourceType Enum — core: 코어 시스템, module: 모듈, plugin: 플러그인). 미지정 시 core로 기본 설정 | | source_identifier | body | string | 아니오 | max 255 | 출처 식별자 | -> 이 엔드포인트는 확장이 파라미터를 추가할 수 있습니다 (`core.attachment.upload_batch_validation_rules`). -> -> 업로드 가능한 확장자는 환경설정의 허용 형식 목록을 따르며, 확장은 `core.attachment.allowed_extensions` 필터로 목록을 조정할 수 있습니다 (허용 목록이 비어 있으면 확장자를 제한하지 않습니다). +> 이 엔드포인트는 확장이 파라미터를 추가할 수 있습니다 (`core.attachment.upload_batch_validation_rules`, `core.attachment.allowed_extensions`). **요청 예시** @@ -140,7 +136,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -191,7 +187,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -233,7 +229,7 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - + **응답 예시** diff --git a/docs/backend/api/auth.md b/docs/backend/api/auth.md index e5402fca..9808f220 100644 --- a/docs/backend/api/auth.md +++ b/docs/backend/api/auth.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Auth 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` @@ -94,8 +94,8 @@ _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| user | object | `{"uuid":"a234c2b1-cde8-437f-b28b-23323be2b98d","name":"AP…` | 대상 사용자 정보 객체 (uuid/name/email 등 — user 관계 파생) | -| token | string | `75\|WgPUplvLGTv8YIj4507uIR6dEOHTXyNUed…` | 발급된 API 접근 토큰 평문 (Bearer 토큰으로 사용, 발급 시 1회만 노출) | +| user | object | `{"uuid":"a26219fc-94a0-4f63-9404-04c2a6ac99e4","name":"최고…` | 대상 사용자 정보 객체 (uuid/name/email 등 — user 관계 파생) | +| token | string | `380\|ZjZl46uRlhHt53MEhb7XAb0r5lYoDEsXF…` | 발급된 API 접근 토큰 평문 (Bearer 토큰으로 사용, 발급 시 1회만 노출) | | token_type | string | `Bearer` | 토큰 타입 (일반적으로 Bearer) | **응답 예시** @@ -110,37 +110,37 @@ HTTP/1.1 200 "message": "성공적으로 처리되었습니다.", "data": { "user": { - "uuid": "a234c2b1-cde8-437f-b28b-23323be2b98d", - "name": "API 문서 샘플 사용자", - "nickname": "song.hyunji", - "email": "apidoc-sample-user@example.com", + "uuid": "a26219fc-94a0-4f63-9404-04c2a6ac99e4", + "name": "최고관리자", + "nickname": "최고관리자", + "email": "heuristing@gmail.com", "avatar": null, "language": "ko", "language_label": "한국어", - "country": "KR", + "country": null, "status": "active", "status_label": "활성", "status_variant": "success", "is_admin": true, - "homepage": "https://example.com", - "mobile": "010-9595-2897", - "phone": "02-637-5618", - "zipcode": "16505", - "address": "경기도 안양시 봉은사로 2918", - "address_detail": "48동 718호", - "signature": "Fugit consequuntur repellendus sed.", - "bio": "Ut magni et sunt ducimus error adipisci. Pariatur corporis voluptatem ratione quo non saepe. Illo atque praesentium possimus dolores qui est fugit. Sint fugiat numquam voluptates.", - "last_login_at": "2026-07-07 10:41:24", - "email_verified_at": "2026-07-08 10:41:24", + "homepage": null, + "mobile": null, + "phone": null, + "zipcode": null, + "address": null, + "address_detail": null, + "signature": null, + "bio": null, + "last_login_at": "2026-08-04 19:00:10", + "email_verified_at": "2026-07-30 23:37:44", "timezone": "Asia/Seoul", - "created_at": "2026-07-08 10:41:24", - "updated_at": "2026-07-08 10:41:24", + "created_at": "2026-07-30 23:37:44", + "updated_at": "2026-08-04 19:00:10", "is_owner": true, "abilities": { "can_read": true, "can_create": true, "can_update": true, - "can_delete": true, + "can_delete": false, "can_assign_roles": true } }, @@ -188,33 +188,33 @@ _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| uuid | string | `a231747f-e82e-4cf2-9ae1-a261849dce40` | 외부 노출용 UUID (URL/API 식별자, 내부 id 비노출) | -| name | string | `API 문서 샘플 사용자` | 사용자 이름 | -| nickname | string | `gunwoo.oh` | 닉네임 | -| email | string | `apidoc-sample-user@example.com` | 이메일 주소 | +| uuid | string | `a26219fc-94a0-4f63-9404-04c2a6ac99e4` | 외부 노출용 UUID (URL/API 식별자, 내부 id 비노출) | +| name | string | `최고관리자` | 사용자 이름 | +| nickname | string | `최고관리자` | 닉네임 | +| email | string | `heuristing@gmail.com` | 이메일 주소 | | avatar | null | `null` | 아바타 이미지 URL (User::getAvatarUrl() — 아바타 미설정 시 null) | | language | string | `ko` | 사용자 언어 설정 (ko: 한국어, en: 영어) | | language_label | string | `한국어` | 언어 코드의 현지화 라벨 (user.language.{code} 번역) | -| country | string | `KR` | 국가 코드 (ISO 3166-1 alpha-2) | +| country | null | `null` | 국가 코드 (ISO 3166-1 alpha-2) | | status | string | `active` | 계정 상태 (active: 활성, inactive: 비활성, blocked: 차단, withdrawn: 탈퇴) | | status_label | string | `활성` | 상태의 사람이 읽는 라벨 (상태 Enum label() 산물) | | status_variant | string | `success` | 상태 표시 색상/스타일 변형 키 (상태 Enum variant() 산물 — UI 배지용) | | is_admin | boolean | `true` | 관리자 역할 보유 여부 (User::isAdmin() — 역할 관계 기반 파생) | -| homepage | string | `https://example.com` | 홈페이지 URL | -| mobile | string | `010-9070-5662` | 휴대폰 번호 | -| phone | string | `02-805-4759` | 전화번호 | -| zipcode | string | `93153` | 우편번호 | -| address | string | `대구광역시 북구 백제고분로 720` | 기본 주소 | -| address_detail | string | `40동 835호` | 상세 주소 | -| signature | string | `Ipsam rem amet expedita est.` | 서명 | -| bio | string | `Tenetur omnis et amet omnis veniam to…` | 자기소개 | -| last_login_at | string | `2026-07-05 19:15:16` | last login 일시 | -| email_verified_at | string | `2026-07-06 19:15:16` | email verified 일시 | +| homepage | null | `null` | 홈페이지 URL | +| mobile | null | `null` | 휴대폰 번호 | +| phone | null | `null` | 전화번호 | +| zipcode | null | `null` | 우편번호 | +| address | null | `null` | 기본 주소 | +| address_detail | null | `null` | 상세 주소 | +| signature | null | `null` | 서명 | +| bio | null | `null` | 자기소개 | +| last_login_at | string | `2026-08-04 19:00:10` | last login 일시 | +| email_verified_at | string | `2026-07-30 23:37:44` | email verified 일시 | | timezone | string | `Asia/Seoul` | 사용자 시간대 (예: Asia/Seoul, UTC) | | roles | array | `[{"id":1,"identifier":"admin","name":"관리자"}]` | 사용자에게 부여된 역할 목록 (원소 id/identifier/name — roles 관계 파생, name 은 현지화 라벨) | -| permissions | array | `[{"id":3,"identifier":"core.users.read","name":"사용자 조회"},…` | 연결된 권한 목록 (id/identifier/name — 역할 경유 권한 관계 파생) | -| created_at | string | `2026-07-06 19:15:16` | 생성 일시 | -| updated_at | string | `2026-07-06 19:15:16` | 최종 수정 일시 | +| permissions | array | `[{"id":2,"identifier":"sirsoft-ecommerce.user-products.re…` | 연결된 권한 목록 (id/identifier/name — 역할 경유 권한 관계 파생) | +| created_at | string | `2026-07-30 23:37:44` | 생성 일시 | +| updated_at | string | `2026-08-04 19:00:10` | 최종 수정 일시 | | is_owner | boolean | `true` | 현재 인증 사용자가 이 리소스의 소유자인지 여부 (BaseApiResource 표준 메타) | | abilities | object | `{"can_read":true,"can_create":true,"can_update":true,"can…` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | @@ -229,1043 +229,12 @@ HTTP/1.1 200 "success": true, "message": "성공적으로 처리되었습니다.", "data": { - "uuid": "a234c2b1-cde8-437f-b28b-23323be2b98d", - "name": "API 문서 샘플 사용자", - "nickname": "song.hyunji", - "email": "apidoc-sample-user@example.com", + "uuid": "a26219fc-94a0-4f63-9404-04c2a6ac99e4", + "name": "최고관리자", + "nickname": "최고관리자", + "email": "heuristing@gmail.com", "avatar": null, - "language": "ko", - "language_label": "한국어", - "country": "KR", - "status": "active", - "status_label": "활성", - "status_variant": "success", - "is_admin": true, - "homepage": "https://example.com", - "mobile": "010-9595-2897", - "phone": "02-637-5618", - "zipcode": "16505", - "address": "경기도 안양시 봉은사로 2918", - "address_detail": "48동 718호", - "signature": "Fugit consequuntur repellendus sed.", - "bio": "Ut magni et sunt ducimus error adipisci. Pariatur corporis voluptatem ratione quo non saepe. Illo atque praesentium possimus dolores qui est fugit. Sint fugiat numquam voluptates.", - "last_login_at": "2026-07-07 10:41:24", - "email_verified_at": "2026-07-08 10:41:24", - "timezone": "Asia/Seoul", - "roles": [ - { - "id": 1, - "identifier": "admin", - "name": "관리자" - }, - { - "id": 4, - "identifier": "apidoc-sample-role", - "name": "API 문서 샘플 역할" - } - ], - "permissions": [ - { - "id": 5, - "identifier": "sirsoft-board.apidoc-sample-board.admin.posts.read", - "name": "게시글 조회 (관리자)" - }, - { - "id": 6, - "identifier": "sirsoft-board.apidoc-sample-board.admin.posts.write", - "name": "게시글 작성/수정/삭제 (관리자)" - }, - { - "id": 7, - "identifier": "sirsoft-board.apidoc-sample-board.admin.posts.read-secret", - "name": "비밀글 조회 (관리자)" - }, - { - "id": 8, - "identifier": "sirsoft-board.apidoc-sample-board.admin.comments.read", - "name": "댓글 조회 (관리자)" - }, - { - "id": 9, - "identifier": "sirsoft-board.apidoc-sample-board.admin.comments.write", - "name": "댓글 작성/수정/삭제 (관리자)" - }, - { - "id": 10, - "identifier": "sirsoft-board.apidoc-sample-board.admin.attachments.upload", - "name": "파일 업로드/삭제 (관리자)" - }, - { - "id": 11, - "identifier": "sirsoft-board.apidoc-sample-board.admin.attachments.download", - "name": "파일 다운로드 (관리자)" - }, - { - "id": 12, - "identifier": "sirsoft-board.apidoc-sample-board.admin.manage", - "name": "게시판 관리 (관리자)" - }, - { - "id": 13, - "identifier": "sirsoft-board.apidoc-sample-board.posts.read", - "name": "게시글 조회" - }, - { - "id": 14, - "identifier": "sirsoft-board.apidoc-sample-board.posts.write", - "name": "게시글 작성/수정/삭제" - }, - { - "id": 15, - "identifier": "sirsoft-board.apidoc-sample-board.posts.read-secret", - "name": "비밀글 조회" - }, - { - "id": 16, - "identifier": "sirsoft-board.apidoc-sample-board.comments.read", - "name": "댓글 조회" - }, - { - "id": 17, - "identifier": "sirsoft-board.apidoc-sample-board.comments.write", - "name": "댓글 작성/수정/삭제" - }, - { - "id": 18, - "identifier": "sirsoft-board.apidoc-sample-board.attachments.upload", - "name": "파일 업로드/삭제" - }, - { - "id": 19, - "identifier": "sirsoft-board.apidoc-sample-board.attachments.download", - "name": "파일 다운로드" - }, - { - "id": 20, - "identifier": "sirsoft-board.apidoc-sample-board.manager", - "name": "게시판 관리 (사용자)" - }, - { - "id": 81, - "identifier": "sirsoft-ecommerce.user-products.read", - "name": "상품 조회" - }, - { - "id": 83, - "identifier": "sirsoft-ecommerce.user-orders.create", - "name": "주문하기" - }, - { - "id": 84, - "identifier": "sirsoft-ecommerce.user-orders.cancel", - "name": "주문 취소" - }, - { - "id": 85, - "identifier": "sirsoft-ecommerce.user-orders.confirm", - "name": "구매확정" - }, - { - "id": 87, - "identifier": "sirsoft-ecommerce.user-reviews.write", - "name": "리뷰 작성" - }, - { - "id": 23, - "identifier": "sirsoft-ecommerce.products.read", - "name": "상품 조회" - }, - { - "id": 24, - "identifier": "sirsoft-ecommerce.products.create", - "name": "상품 생성" - }, - { - "id": 25, - "identifier": "sirsoft-ecommerce.products.update", - "name": "상품 수정" - }, - { - "id": 26, - "identifier": "sirsoft-ecommerce.products.delete", - "name": "상품 삭제" - }, - { - "id": 28, - "identifier": "sirsoft-ecommerce.orders.read", - "name": "주문 조회" - }, - { - "id": 29, - "identifier": "sirsoft-ecommerce.orders.update", - "name": "주문 수정" - }, - { - "id": 31, - "identifier": "sirsoft-ecommerce.categories.read", - "name": "카테고리 조회" - }, - { - "id": 32, - "identifier": "sirsoft-ecommerce.categories.create", - "name": "카테고리 생성" - }, - { - "id": 33, - "identifier": "sirsoft-ecommerce.categories.update", - "name": "카테고리 수정" - }, - { - "id": 34, - "identifier": "sirsoft-ecommerce.categories.delete", - "name": "카테고리 삭제" - }, - { - "id": 36, - "identifier": "sirsoft-ecommerce.brands.read", - "name": "브랜드 조회" - }, - { - "id": 37, - "identifier": "sirsoft-ecommerce.brands.create", - "name": "브랜드 생성" - }, - { - "id": 38, - "identifier": "sirsoft-ecommerce.brands.update", - "name": "브랜드 수정" - }, - { - "id": 39, - "identifier": "sirsoft-ecommerce.brands.delete", - "name": "브랜드 삭제" - }, - { - "id": 41, - "identifier": "sirsoft-ecommerce.product-notice-templates.read", - "name": "조회" - }, - { - "id": 42, - "identifier": "sirsoft-ecommerce.product-notice-templates.create", - "name": "생성" - }, - { - "id": 43, - "identifier": "sirsoft-ecommerce.product-notice-templates.update", - "name": "수정" - }, - { - "id": 44, - "identifier": "sirsoft-ecommerce.product-notice-templates.delete", - "name": "삭제" - }, - { - "id": 46, - "identifier": "sirsoft-ecommerce.product-common-infos.read", - "name": "조회" - }, - { - "id": 47, - "identifier": "sirsoft-ecommerce.product-common-infos.create", - "name": "생성" - }, - { - "id": 48, - "identifier": "sirsoft-ecommerce.product-common-infos.update", - "name": "수정" - }, - { - "id": 49, - "identifier": "sirsoft-ecommerce.product-common-infos.delete", - "name": "삭제" - }, - { - "id": 51, - "identifier": "sirsoft-ecommerce.settings.read", - "name": "환경설정 조회" - }, - { - "id": 52, - "identifier": "sirsoft-ecommerce.settings.update", - "name": "환경설정 수정" - }, - { - "id": 54, - "identifier": "sirsoft-ecommerce.promotion-coupon.read", - "name": "쿠폰 조회" - }, - { - "id": 55, - "identifier": "sirsoft-ecommerce.promotion-coupon.create", - "name": "쿠폰 생성" - }, - { - "id": 56, - "identifier": "sirsoft-ecommerce.promotion-coupon.update", - "name": "쿠폰 수정" - }, - { - "id": 57, - "identifier": "sirsoft-ecommerce.promotion-coupon.delete", - "name": "쿠폰 삭제" - }, - { - "id": 59, - "identifier": "sirsoft-ecommerce.shipping-policies.read", - "name": "배송정책 조회" - }, - { - "id": 60, - "identifier": "sirsoft-ecommerce.shipping-policies.create", - "name": "배송정책 생성" - }, - { - "id": 61, - "identifier": "sirsoft-ecommerce.shipping-policies.update", - "name": "배송정책 수정" - }, - { - "id": 62, - "identifier": "sirsoft-ecommerce.shipping-policies.delete", - "name": "배송정책 삭제" - }, - { - "id": 64, - "identifier": "sirsoft-ecommerce.product-labels.read", - "name": "상품 라벨 조회" - }, - { - "id": 65, - "identifier": "sirsoft-ecommerce.product-labels.create", - "name": "상품 라벨 생성" - }, - { - "id": 66, - "identifier": "sirsoft-ecommerce.product-labels.update", - "name": "상품 라벨 수정" - }, - { - "id": 67, - "identifier": "sirsoft-ecommerce.product-labels.delete", - "name": "상품 라벨 삭제" - }, - { - "id": 69, - "identifier": "sirsoft-ecommerce.identity.policies.read", - "name": "본인인증 정책 조회" - }, - { - "id": 70, - "identifier": "sirsoft-ecommerce.identity.policies.update", - "name": "본인인증 정책 수정" - }, - { - "id": 72, - "identifier": "sirsoft-ecommerce.reviews.read", - "name": "리뷰 조회" - }, - { - "id": 73, - "identifier": "sirsoft-ecommerce.reviews.update", - "name": "리뷰 처리" - }, - { - "id": 74, - "identifier": "sirsoft-ecommerce.reviews.delete", - "name": "리뷰 삭제" - }, - { - "id": 76, - "identifier": "sirsoft-ecommerce.inquiries.update", - "name": "문의 처리" - }, - { - "id": 77, - "identifier": "sirsoft-ecommerce.inquiries.delete", - "name": "문의 삭제" - }, - { - "id": 79, - "identifier": "sirsoft-ecommerce.dashboard.view", - "name": "대시보드 조회" - }, - { - "id": 89, - "identifier": "sirsoft-ecommerce.mileage.read", - "name": "마일리지 내역 조회" - }, - { - "id": 90, - "identifier": "sirsoft-ecommerce.mileage.manage", - "name": "마일리지 수동 처리" - }, - { - "id": 92, - "identifier": "sirsoft-ecommerce.user-currency.manage", - "name": "회원 결제 통화 변경" - }, - { - "id": 94, - "identifier": "sirsoft-ecommerce.user-shipping-country.manage", - "name": "회원 배송국가 변경" - }, - { - "id": 96, - "identifier": "sirsoft-board.boards.read", - "name": "게시판 조회" - }, - { - "id": 97, - "identifier": "sirsoft-board.boards.create", - "name": "게시판 생성" - }, - { - "id": 98, - "identifier": "sirsoft-board.boards.update", - "name": "게시판 수정" - }, - { - "id": 99, - "identifier": "sirsoft-board.boards.delete", - "name": "게시판 삭제" - }, - { - "id": 101, - "identifier": "sirsoft-board.settings.read", - "name": "환경설정 조회" - }, - { - "id": 102, - "identifier": "sirsoft-board.settings.update", - "name": "환경설정 수정" - }, - { - "id": 104, - "identifier": "sirsoft-board.identity.policies.read", - "name": "본인인증 정책 조회" - }, - { - "id": 105, - "identifier": "sirsoft-board.identity.policies.update", - "name": "본인인증 정책 수정" - }, - { - "id": 107, - "identifier": "sirsoft-board.reports.view", - "name": "신고 조회" - }, - { - "id": 108, - "identifier": "sirsoft-board.reports.manage", - "name": "신고 처리" - }, - { - "id": 111, - "identifier": "sirsoft-page.pages.read", - "name": "페이지 조회" - }, - { - "id": 112, - "identifier": "sirsoft-page.pages.create", - "name": "페이지 생성" - }, - { - "id": 113, - "identifier": "sirsoft-page.pages.update", - "name": "페이지 수정" - }, - { - "id": 114, - "identifier": "sirsoft-page.pages.delete", - "name": "페이지 삭제" - }, - { - "id": 117, - "identifier": "core.users.read", - "name": "사용자 조회" - }, - { - "id": 118, - "identifier": "core.users.create", - "name": "사용자 생성" - }, - { - "id": 119, - "identifier": "core.users.update", - "name": "사용자 수정" - }, - { - "id": 120, - "identifier": "core.users.delete", - "name": "사용자 삭제" - }, - { - "id": 122, - "identifier": "core.menus.read", - "name": "메뉴 조회" - }, - { - "id": 123, - "identifier": "core.menus.create", - "name": "메뉴 생성" - }, - { - "id": 124, - "identifier": "core.menus.update", - "name": "메뉴 수정" - }, - { - "id": 125, - "identifier": "core.menus.delete", - "name": "메뉴 삭제" - }, - { - "id": 127, - "identifier": "core.modules.read", - "name": "모듈 조회" - }, - { - "id": 128, - "identifier": "core.modules.install", - "name": "모듈 설치" - }, - { - "id": 129, - "identifier": "core.modules.activate", - "name": "모듈 활성화" - }, - { - "id": 130, - "identifier": "core.modules.uninstall", - "name": "모듈 삭제" - }, - { - "id": 132, - "identifier": "core.plugins.read", - "name": "플러그인 조회" - }, - { - "id": 133, - "identifier": "core.plugins.install", - "name": "플러그인 설치" - }, - { - "id": 134, - "identifier": "core.plugins.activate", - "name": "플러그인 활성화" - }, - { - "id": 135, - "identifier": "core.plugins.update", - "name": "플러그인 설정" - }, - { - "id": 136, - "identifier": "core.plugins.uninstall", - "name": "플러그인 삭제" - }, - { - "id": 138, - "identifier": "core.templates.read", - "name": "템플릿 조회" - }, - { - "id": 139, - "identifier": "core.templates.install", - "name": "템플릿 설치" - }, - { - "id": 140, - "identifier": "core.templates.activate", - "name": "템플릿 활성화" - }, - { - "id": 141, - "identifier": "core.templates.uninstall", - "name": "템플릿 삭제" - }, - { - "id": 142, - "identifier": "core.templates.layouts.edit", - "name": "레이아웃 편집" - }, - { - "id": 144, - "identifier": "core.permissions.read", - "name": "권한 조회" - }, - { - "id": 145, - "identifier": "core.permissions.create", - "name": "역할 생성" - }, - { - "id": 146, - "identifier": "core.permissions.update", - "name": "역할 수정" - }, - { - "id": 147, - "identifier": "core.permissions.delete", - "name": "역할 삭제" - }, - { - "id": 149, - "identifier": "core.notification-logs.read", - "name": "발송 이력 조회" - }, - { - "id": 150, - "identifier": "core.notification-logs.delete", - "name": "발송 이력 삭제" - }, - { - "id": 152, - "identifier": "core.notifications.read", - "name": "알림 조회" - }, - { - "id": 153, - "identifier": "core.notifications.update", - "name": "알림 읽음 처리" - }, - { - "id": 154, - "identifier": "core.notifications.delete", - "name": "알림 삭제" - }, - { - "id": 156, - "identifier": "core.user-notifications.read", - "name": "알림 조회" - }, - { - "id": 157, - "identifier": "core.user-notifications.update", - "name": "알림 읽음 처리" - }, - { - "id": 158, - "identifier": "core.user-notifications.delete", - "name": "알림 삭제" - }, - { - "id": 160, - "identifier": "core.identity.request", - "name": "IDV 요청" - }, - { - "id": 161, - "identifier": "core.identity.verify", - "name": "IDV 검증" - }, - { - "id": 162, - "identifier": "core.identity.cancel", - "name": "IDV 취소" - }, - { - "id": 164, - "identifier": "core.admin.identity.providers.read", - "name": "프로바이더 설정 조회" - }, - { - "id": 165, - "identifier": "core.admin.identity.providers.update", - "name": "프로바이더 설정 수정" - }, - { - "id": 166, - "identifier": "core.admin.identity.policies.read", - "name": "정책 조회" - }, - { - "id": 167, - "identifier": "core.admin.identity.policies.update", - "name": "정책 수정" - }, - { - "id": 168, - "identifier": "core.admin.identity.logs.read", - "name": "로그 열람" - }, - { - "id": 169, - "identifier": "core.admin.identity.logs.purge", - "name": "로그 파기" - }, - { - "id": 170, - "identifier": "core.admin.identity.messages.read", - "name": "메시지 템플릿 조회" - }, - { - "id": 171, - "identifier": "core.admin.identity.messages.update", - "name": "메시지 템플릿 수정" - }, - { - "id": 173, - "identifier": "core.settings.read", - "name": "설정 조회" - }, - { - "id": 174, - "identifier": "core.settings.update", - "name": "설정 수정" - }, - { - "id": 176, - "identifier": "core.dashboard.read", - "name": "대시보드 조회" - }, - { - "id": 177, - "identifier": "core.dashboard.system-status", - "name": "시스템 상태" - }, - { - "id": 178, - "identifier": "core.dashboard.resources", - "name": "시스템 리소스" - }, - { - "id": 179, - "identifier": "core.dashboard.activities", - "name": "최근 활동" - }, - { - "id": 180, - "identifier": "core.dashboard.alerts", - "name": "시스템 알림" - }, - { - "id": 182, - "identifier": "core.activities.read", - "name": "활동 로그 조회" - }, - { - "id": 183, - "identifier": "core.activities.delete", - "name": "활동 로그 삭제" - }, - { - "id": 185, - "identifier": "core.attachments.create", - "name": "첨부파일 업로드" - }, - { - "id": 186, - "identifier": "core.attachments.update", - "name": "첨부파일 수정" - }, - { - "id": 187, - "identifier": "core.attachments.delete", - "name": "첨부파일 삭제" - }, - { - "id": 189, - "identifier": "core.schedules.read", - "name": "스케줄 조회" - }, - { - "id": 190, - "identifier": "core.schedules.create", - "name": "스케줄 생성" - }, - { - "id": 191, - "identifier": "core.schedules.update", - "name": "스케줄 수정" - }, - { - "id": 192, - "identifier": "core.schedules.delete", - "name": "스케줄 삭제" - }, - { - "id": 193, - "identifier": "core.schedules.run", - "name": "스케줄 실행" - }, - { - "id": 195, - "identifier": "core.language_packs.read", - "name": "언어팩 조회" - }, - { - "id": 196, - "identifier": "core.language_packs.install", - "name": "언어팩 설치" - }, - { - "id": 197, - "identifier": "core.language_packs.manage", - "name": "언어팩 관리" - }, - { - "id": 198, - "identifier": "core.language_packs.update", - "name": "언어팩 업데이트" - }, - { - "id": 1, - "identifier": "apidoc-sample.parent", - "name": "API 문서 샘플 권한" - }, - { - "id": 2, - "identifier": "apidoc-sample.child", - "name": "하위 권한" - }, - { - "id": 3, - "identifier": "sirsoft-board", - "name": "게시판" - }, - { - "id": 21, - "identifier": "sirsoft-ecommerce", - "name": "이커머스" - }, - { - "id": 109, - "identifier": "sirsoft-page", - "name": "페이지" - }, - { - "id": 4, - "identifier": "sirsoft-board.apidoc-sample-board", - "name": "API 문서 샘플 게시판 게시판" - }, - { - "id": 95, - "identifier": "sirsoft-board.boards", - "name": "게시판 관리" - }, - { - "id": 100, - "identifier": "sirsoft-board.settings", - "name": "환경설정" - }, - { - "id": 103, - "identifier": "sirsoft-board.identity.policies", - "name": "게시판 본인인증 정책" - }, - { - "id": 106, - "identifier": "sirsoft-board.reports", - "name": "게시판 신고 관리" - }, - { - "id": 22, - "identifier": "sirsoft-ecommerce.products", - "name": "상품 관리" - }, - { - "id": 27, - "identifier": "sirsoft-ecommerce.orders", - "name": "주문 관리" - }, - { - "id": 30, - "identifier": "sirsoft-ecommerce.categories", - "name": "카테고리 관리" - }, - { - "id": 35, - "identifier": "sirsoft-ecommerce.brands", - "name": "브랜드 관리" - }, - { - "id": 40, - "identifier": "sirsoft-ecommerce.product-notice-templates", - "name": "상품정보제공고시 관리" - }, - { - "id": 45, - "identifier": "sirsoft-ecommerce.product-common-infos", - "name": "공통정보 관리" - }, - { - "id": 50, - "identifier": "sirsoft-ecommerce.settings", - "name": "환경설정" - }, - { - "id": 53, - "identifier": "sirsoft-ecommerce.promotion-coupon", - "name": "쿠폰 관리" - }, - { - "id": 58, - "identifier": "sirsoft-ecommerce.shipping-policies", - "name": "배송정책 관리" - }, - { - "id": 63, - "identifier": "sirsoft-ecommerce.product-labels", - "name": "상품 라벨 관리" - }, - { - "id": 68, - "identifier": "sirsoft-ecommerce.identity.policies", - "name": "이커머스 본인인증 정책" - }, - { - "id": 71, - "identifier": "sirsoft-ecommerce.reviews", - "name": "리뷰 관리" - }, - { - "id": 75, - "identifier": "sirsoft-ecommerce.inquiries", - "name": "문의 관리" - }, - { - "id": 78, - "identifier": "sirsoft-ecommerce.dashboard", - "name": "대시보드" - }, - { - "id": 80, - "identifier": "sirsoft-ecommerce.user-products", - "name": "사용자 상품" - }, - { - "id": 82, - "identifier": "sirsoft-ecommerce.user-orders", - "name": "사용자 주문" - }, - { - "id": 86, - "identifier": "sirsoft-ecommerce.user-reviews", - "name": "사용자 리뷰" - }, - { - "id": 88, - "identifier": "sirsoft-ecommerce.mileage", - "name": "마일리지 관리" - }, - { - "id": 91, - "identifier": "sirsoft-ecommerce.user-currency", - "name": "회원 결제 통화 관리" - }, - { - "id": 93, - "identifier": "sirsoft-ecommerce.user-shipping-country", - "name": "회원 배송국가 관리" - }, - { - "id": 110, - "identifier": "sirsoft-page.pages", - "name": "페이지 관리" - }, - { - "id": 115, - "identifier": "core", - "name": "코어" - }, - { - "id": 116, - "identifier": "core.users", - "name": "사용자 관리" - }, - { - "id": 121, - "identifier": "core.menus", - "name": "메뉴 관리" - }, - { - "id": 126, - "identifier": "core.modules", - "name": "모듈 관리" - }, - { - "id": 131, - "identifier": "core.plugins", - "name": "플러그인 관리" - }, - { - "id": 137, - "identifier": "core.templates", - "name": "템플릿 관리" - }, - { - "id": 143, - "identifier": "core.permissions", - "name": "권한 관리" - }, - { - "id": 148, - "identifier": "core.notification-logs", - "name": "알림 발송 이력" - }, - { - "id": 151, - "identifier": "core.notifications", - "name": "알림 (관리자)" - }, - { - "id": 155, - "identifier": "core.user-notifications", - "name": "알림 (사용자)" - }, - { - "id": 159, - "identifier": "core.identity", - "name": "본인인증 (사용자)" - }, - { - "id": 163, - "identifier": "core.admin.identity", - "name": "본인인증 관리 (관리자)" - }, - { - "id": 172, - "identifier": "core.settings", - "name": "환경설정" - }, - { - "id": 175, - "identifier": "core.dashboard", - "name": "대시보드" - }, - { - "id": 181, - "identifier": "core.activities", - "name": "활동 로그" - }, - { - "id": 184, - "identifier": "core.attachments", - "name": "첨부파일 관리" - }, - { - "id": 188, - "identifier": "core.schedules", - "name": "스케줄 관리" - }, - { - "id": 194, - "identifier": "core.language_packs", - "name": "언어팩 관리" - } - ], - "created_at": "2026-07-08 10:41:24", - "updated_at": "2026-07-08 10:41:24", - "is_owner": true, - "abilities": { - "can_read": true, - "can_create": true, - "can_update": true, - "can_delete": true, - "can_assign_roles": true - } + "...": "(24개 키 생략, 총 29개)" } } ``` @@ -1296,7 +265,7 @@ HTTP/1.1 200 | 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | | --- | --- | --- | --- | --- | --- | | email | body | email | 예 | — | 이메일 주소 | -| password | body | string | 예 | min 6 | 비밀번호 | +| password | body | string | 예 | — | 비밀번호 | > 이 엔드포인트는 확장이 파라미터를 추가할 수 있습니다 (`core.auth.login_validation_rules`). @@ -1316,7 +285,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -1366,7 +335,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -1396,7 +365,7 @@ Content-Type: application/json | 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | | --- | --- | --- | --- | --- | --- | | email | body | email | 예 | — | 이메일 주소 | -| password | body | string | 예 | min 6 | 비밀번호 | +| password | body | string | 예 | — | 비밀번호 | > 이 엔드포인트는 확장이 파라미터를 추가할 수 있습니다 (`core.auth.login_validation_rules`). @@ -1416,7 +385,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -1447,17 +416,19 @@ Content-Type: application/json ### POST /api/auth/login/two-factor - + - **라우트명**: `api.auth.login.two-factor` - **컨트롤러**: `App\Http\Controllers\Api\Auth\AuthController@verifyTwoFactor` -- **인증/권한**: 공개 (인증 불필요 — 주체는 challenge 가 식별) +- **인증/권한**: 공개 (인증 불필요) **요청 파라미터** | 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | | --- | --- | --- | --- | --- | --- | -| challenge_id | body | uuid | 예 | — | 로그인 응답이 돌려준 challenge 식별자 | -| code | body | string | 예 | 4~16자 | 사용자가 받은 인증 코드 | +| challenge_id | body | string | 예 | — | 로그인 응답이 돌려준 challenge 식별자 | +| code | body | string | 예 | min 4, max 16 | 사용자가 받은 인증 코드 | + +> 이 엔드포인트는 확장이 파라미터를 추가할 수 있습니다 (`core.auth.two_factor_validation_rules`). **요청 예시** @@ -1466,26 +437,29 @@ POST /api/auth/login/two-factor HTTP/1.1 Host: api.example.com Accept: application/json Content-Type: application/json -``` -```json { - "challenge_id": "0f8c2b6e-1a2b-4c3d-9e8f-7a6b5c4d3e2f", - "code": "135790" + "challenge_id": "예시값", + "code": "예시값" } ``` **응답 필드** (`data` 내부) -로그인 성공과 동일하다 — `token`(Sanctum Bearer) · `token_type` · `user`. + + +**응답 예시** + + **에러 응답** | 상태코드 | 의미 | 발생 조건 | | --- | --- | --- | -| 401 | Unauthorized | 코드 불일치·만료·시도 횟수 초과, 또는 challenge 가 로그인 용도(`login`)가 아니거나 대상 사용자가 활성 상태가 아닌 경우 (`auth.two_factor_failed`) | | 422 | Unprocessable Entity | `challenge_id`/`code` 형식 위반 | + + **설명** 비밀번호 단계가 돌려준 challenge 를 확인해 로그인을 완료한다. 로그인과 동일한 요청 제한(`throttle:auth-login`)이 걸려 코드 대입 시도도 함께 억제된다. @@ -1558,10 +532,10 @@ HTTP/1.1 200 | name | body | string | 예 | max 255 | 대상의 이름/명칭 | | nickname | body | string | 아니오 | max 50 | 닉네임 | | email | body | string | 예 | max 255 | 이메일 주소 | -| password | body | string | 예 | min 8 | 비밀번호 | -| mobile | body | string | 아니오 | max 20, `숫자 - + ( ) 공백` | 휴대폰번호 (선택) | -| phone | body | string | 아니오 | max 20, `숫자 - + ( ) 공백` | 전화번호 (선택) | -| language | body | string | 아니오 | `ko`, `en`, `fr`, `ja` | 언어 코드 | +| password | body | string | 예 | — | 비밀번호 | +| mobile | body | string | 아니오 | max 20 | 휴대폰번호 (선택) | +| phone | body | string | 아니오 | max 20 | 전화번호 (선택) | +| language | body | string | 아니오 | `ko`, `en` | 언어 코드 | | agree_terms | body | string | 아니오 | — | 이용약관 동의 (코어 필수 동의 — accepted 규칙, 미동의 시 가입 거부) | | agree_privacy | body | string | 아니오 | — | 개인정보 처리방침 동의 (코어 필수 동의 — accepted 규칙, 미동의 시 가입 거부) | | agree_email_subscription | body | boolean | 아니오 | — | 광고성 이메일 수신 동의 (marketing 플러그인 주입, 선택 항목) | @@ -1586,9 +560,15 @@ Content-Type: application/json "nickname": "예시 이름", "email": "user@example.com", "password": "Password123!", + "mobile": "010-1234-5678", + "phone": "010-1234-5678", "language": "ko", "agree_terms": "예시값", "agree_privacy": "예시값", + "agree_email_subscription": true, + "agree_marketing_consent": true, + "agree_third_party_consent": true, + "agree_info_disclosure": true, "preferred_currency": "예시값", "preferred_shipping_country": "KR" } @@ -1596,7 +576,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -1627,7 +607,7 @@ Content-Type: application/json | --- | --- | --- | --- | --- | --- | | token | body | string | 예 | — | 인증/검증 토큰 | | email | body | email | 예 | — | 이메일 주소 | -| password | body | string | 예 | min 8 | 비밀번호 | +| password | body | string | 예 | — | 비밀번호 | > 이 엔드포인트는 확장이 파라미터를 추가할 수 있습니다 (`core.auth.reset_password_validation_rules`). @@ -1648,7 +628,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -1692,28 +672,28 @@ _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| uuid | string | `a231747f-e82e-4cf2-9ae1-a261849dce40` | 외부 노출용 UUID (URL/API 식별자, 내부 id 비노출) | -| name | string | `API 문서 샘플 사용자` | 사용자 이름 | -| nickname | string | `gunwoo.oh` | 닉네임 | -| email | string | `apidoc-sample-user@example.com` | 이메일 주소 | +| uuid | string | `a26219fc-94a0-4f63-9404-04c2a6ac99e4` | 외부 노출용 UUID (URL/API 식별자, 내부 id 비노출) | +| name | string | `최고관리자` | 사용자 이름 | +| nickname | string | `최고관리자` | 닉네임 | +| email | string | `heuristing@gmail.com` | 이메일 주소 | | avatar | null | `null` | 아바타 이미지 URL (User::getAvatarUrl() — 아바타 미설정 시 null) | | language | string | `ko` | 사용자 언어 설정 (ko: 한국어, en: 영어) | | language_label | string | `한국어` | 언어 코드의 현지화 라벨 (user.language.{code} 번역) | -| country | string | `KR` | 국가 코드 (ISO 3166-1 alpha-2) | +| country | null | `null` | 국가 코드 (ISO 3166-1 alpha-2) | | status | string | `active` | 계정 상태 (active: 활성, inactive: 비활성, blocked: 차단, withdrawn: 탈퇴) | | status_label | string | `활성` | 상태의 사람이 읽는 라벨 (상태 Enum label() 산물) | | status_variant | string | `success` | 상태 표시 색상/스타일 변형 키 (상태 Enum variant() 산물 — UI 배지용) | | is_admin | boolean | `true` | 관리자 역할 보유 여부 (User::isAdmin() — 역할 관계 기반 파생) | -| homepage | string | `https://example.com` | 홈페이지 URL | -| mobile | string | `010-9070-5662` | 휴대폰 번호 | -| phone | string | `02-805-4759` | 전화번호 | -| zipcode | string | `93153` | 우편번호 | -| address | string | `대구광역시 북구 백제고분로 720` | 기본 주소 | -| address_detail | string | `40동 835호` | 상세 주소 | -| signature | string | `Ipsam rem amet expedita est.` | 서명 | -| bio | string | `Tenetur omnis et amet omnis veniam to…` | 자기소개 | -| last_login_at | string | `2026-07-05 19:15:16` | last login 일시 | -| email_verified_at | string | `2026-07-06 19:15:16` | email verified 일시 | +| homepage | null | `null` | 홈페이지 URL | +| mobile | null | `null` | 휴대폰 번호 | +| phone | null | `null` | 전화번호 | +| zipcode | null | `null` | 우편번호 | +| address | null | `null` | 기본 주소 | +| address_detail | null | `null` | 상세 주소 | +| signature | null | `null` | 서명 | +| bio | null | `null` | 자기소개 | +| last_login_at | string | `2026-08-04 19:00:10` | last login 일시 | +| email_verified_at | string | `2026-07-30 23:37:44` | email verified 일시 | | timezone | string | `Asia/Seoul` | 사용자 시간대 (예: Asia/Seoul, UTC) | | modules_count | array | `[]` | 접근 가능 모듈 수 (modules_count 속성이 로드된 경우에만 포함 — whenLoaded 성격의 조건부 필드) | | plugins_count | array | `[]` | 접근 가능 플러그인 수 (plugins_count 속성이 로드된 경우에만 포함) | @@ -1722,12 +702,12 @@ _단건 응답: `data` 객체의 필드._ | plugins | array | `[]` | 접근 가능 플러그인 목록 (원소 id/name/slug/is_active — plugins 관계 로드 시에만 포함) | | menus | array | `[]` | 접근 가능 메뉴 목록 (원소 id/title/url/is_active — menus 관계 로드 시에만 포함) | | roles | array | `[{"id":1,"identifier":"admin","name":"관리자"}]` | 사용자에게 부여된 역할 목록 (원소 id/identifier/name — roles 관계 파생, name 은 현지화 라벨) | -| permissions | array | `[{"id":3,"identifier":"core.users.read","name":"사용자 조회"},…` | 연결된 권한 목록 (id/identifier/name — 역할 경유 권한 관계 파생) | +| permissions | array | `[{"id":2,"identifier":"sirsoft-ecommerce.user-products.re…` | 연결된 권한 목록 (id/identifier/name — 역할 경유 권한 관계 파생) | | consents | array | `[]` | 전체 약관 동의 이력 (원소 consent_type/agreed_at/revoked_at — consents 관계 로드 시 포함, 플러그인 참조용) | | terms_consent | array | `[]` | 이용약관 동의 정보 (agreed_at — ConsentType::Terms 동의 이력에서 파생, 미동의 시 null) | | privacy_consent | array | `[]` | 개인정보 처리방침 동의 정보 (agreed_at — ConsentType::Privacy 동의 이력에서 파생, 미동의 시 null) | -| created_at | string | `2026-07-06 19:15:16` | 생성 일시 | -| updated_at | string | `2026-07-06 19:15:16` | 최종 수정 일시 | +| created_at | string | `2026-07-30 23:37:44` | 생성 일시 | +| updated_at | string | `2026-08-04 19:00:10` | 최종 수정 일시 | | is_owner | boolean | `true` | 현재 인증 사용자가 이 리소스의 소유자인지 여부 (BaseApiResource 표준 메타) | | abilities | object | `{"can_read":true,"can_create":true,"can_update":true,"can…` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | | notify_post_complete | boolean | `false` | 게시판 새 글 작성 완료 알림 수신 설정 (marketing 플러그인 주입) | @@ -1772,1062 +752,12 @@ HTTP/1.1 200 "success": true, "message": "성공적으로 처리되었습니다.", "data": { - "uuid": "a234c2b1-cde8-437f-b28b-23323be2b98d", - "name": "API 문서 샘플 사용자", - "nickname": "song.hyunji", - "email": "apidoc-sample-user@example.com", + "uuid": "a26219fc-94a0-4f63-9404-04c2a6ac99e4", + "name": "최고관리자", + "nickname": "최고관리자", + "email": "heuristing@gmail.com", "avatar": null, - "language": "ko", - "language_label": "한국어", - "country": "KR", - "status": "active", - "status_label": "활성", - "status_variant": "success", - "is_admin": true, - "homepage": "https://example.com", - "mobile": "010-9595-2897", - "phone": "02-637-5618", - "zipcode": "16505", - "address": "경기도 안양시 봉은사로 2918", - "address_detail": "48동 718호", - "signature": "Fugit consequuntur repellendus sed.", - "bio": "Ut magni et sunt ducimus error adipisci. Pariatur corporis voluptatem ratione quo non saepe. Illo atque praesentium possimus dolores qui est fugit. Sint fugiat numquam voluptates.", - "last_login_at": "2026-07-07 10:41:24", - "email_verified_at": "2026-07-08 10:41:24", - "timezone": "Asia/Seoul", - "modules_count": [], - "plugins_count": [], - "menus_count": [], - "modules": [], - "plugins": [], - "menus": [], - "roles": [ - { - "id": 1, - "identifier": "admin", - "name": "관리자" - }, - { - "id": 4, - "identifier": "apidoc-sample-role", - "name": "API 문서 샘플 역할" - } - ], - "permissions": [ - { - "id": 5, - "identifier": "sirsoft-board.apidoc-sample-board.admin.posts.read", - "name": "게시글 조회 (관리자)" - }, - { - "id": 6, - "identifier": "sirsoft-board.apidoc-sample-board.admin.posts.write", - "name": "게시글 작성/수정/삭제 (관리자)" - }, - { - "id": 7, - "identifier": "sirsoft-board.apidoc-sample-board.admin.posts.read-secret", - "name": "비밀글 조회 (관리자)" - }, - { - "id": 8, - "identifier": "sirsoft-board.apidoc-sample-board.admin.comments.read", - "name": "댓글 조회 (관리자)" - }, - { - "id": 9, - "identifier": "sirsoft-board.apidoc-sample-board.admin.comments.write", - "name": "댓글 작성/수정/삭제 (관리자)" - }, - { - "id": 10, - "identifier": "sirsoft-board.apidoc-sample-board.admin.attachments.upload", - "name": "파일 업로드/삭제 (관리자)" - }, - { - "id": 11, - "identifier": "sirsoft-board.apidoc-sample-board.admin.attachments.download", - "name": "파일 다운로드 (관리자)" - }, - { - "id": 12, - "identifier": "sirsoft-board.apidoc-sample-board.admin.manage", - "name": "게시판 관리 (관리자)" - }, - { - "id": 13, - "identifier": "sirsoft-board.apidoc-sample-board.posts.read", - "name": "게시글 조회" - }, - { - "id": 14, - "identifier": "sirsoft-board.apidoc-sample-board.posts.write", - "name": "게시글 작성/수정/삭제" - }, - { - "id": 15, - "identifier": "sirsoft-board.apidoc-sample-board.posts.read-secret", - "name": "비밀글 조회" - }, - { - "id": 16, - "identifier": "sirsoft-board.apidoc-sample-board.comments.read", - "name": "댓글 조회" - }, - { - "id": 17, - "identifier": "sirsoft-board.apidoc-sample-board.comments.write", - "name": "댓글 작성/수정/삭제" - }, - { - "id": 18, - "identifier": "sirsoft-board.apidoc-sample-board.attachments.upload", - "name": "파일 업로드/삭제" - }, - { - "id": 19, - "identifier": "sirsoft-board.apidoc-sample-board.attachments.download", - "name": "파일 다운로드" - }, - { - "id": 20, - "identifier": "sirsoft-board.apidoc-sample-board.manager", - "name": "게시판 관리 (사용자)" - }, - { - "id": 81, - "identifier": "sirsoft-ecommerce.user-products.read", - "name": "상품 조회" - }, - { - "id": 83, - "identifier": "sirsoft-ecommerce.user-orders.create", - "name": "주문하기" - }, - { - "id": 84, - "identifier": "sirsoft-ecommerce.user-orders.cancel", - "name": "주문 취소" - }, - { - "id": 85, - "identifier": "sirsoft-ecommerce.user-orders.confirm", - "name": "구매확정" - }, - { - "id": 87, - "identifier": "sirsoft-ecommerce.user-reviews.write", - "name": "리뷰 작성" - }, - { - "id": 23, - "identifier": "sirsoft-ecommerce.products.read", - "name": "상품 조회" - }, - { - "id": 24, - "identifier": "sirsoft-ecommerce.products.create", - "name": "상품 생성" - }, - { - "id": 25, - "identifier": "sirsoft-ecommerce.products.update", - "name": "상품 수정" - }, - { - "id": 26, - "identifier": "sirsoft-ecommerce.products.delete", - "name": "상품 삭제" - }, - { - "id": 28, - "identifier": "sirsoft-ecommerce.orders.read", - "name": "주문 조회" - }, - { - "id": 29, - "identifier": "sirsoft-ecommerce.orders.update", - "name": "주문 수정" - }, - { - "id": 31, - "identifier": "sirsoft-ecommerce.categories.read", - "name": "카테고리 조회" - }, - { - "id": 32, - "identifier": "sirsoft-ecommerce.categories.create", - "name": "카테고리 생성" - }, - { - "id": 33, - "identifier": "sirsoft-ecommerce.categories.update", - "name": "카테고리 수정" - }, - { - "id": 34, - "identifier": "sirsoft-ecommerce.categories.delete", - "name": "카테고리 삭제" - }, - { - "id": 36, - "identifier": "sirsoft-ecommerce.brands.read", - "name": "브랜드 조회" - }, - { - "id": 37, - "identifier": "sirsoft-ecommerce.brands.create", - "name": "브랜드 생성" - }, - { - "id": 38, - "identifier": "sirsoft-ecommerce.brands.update", - "name": "브랜드 수정" - }, - { - "id": 39, - "identifier": "sirsoft-ecommerce.brands.delete", - "name": "브랜드 삭제" - }, - { - "id": 41, - "identifier": "sirsoft-ecommerce.product-notice-templates.read", - "name": "조회" - }, - { - "id": 42, - "identifier": "sirsoft-ecommerce.product-notice-templates.create", - "name": "생성" - }, - { - "id": 43, - "identifier": "sirsoft-ecommerce.product-notice-templates.update", - "name": "수정" - }, - { - "id": 44, - "identifier": "sirsoft-ecommerce.product-notice-templates.delete", - "name": "삭제" - }, - { - "id": 46, - "identifier": "sirsoft-ecommerce.product-common-infos.read", - "name": "조회" - }, - { - "id": 47, - "identifier": "sirsoft-ecommerce.product-common-infos.create", - "name": "생성" - }, - { - "id": 48, - "identifier": "sirsoft-ecommerce.product-common-infos.update", - "name": "수정" - }, - { - "id": 49, - "identifier": "sirsoft-ecommerce.product-common-infos.delete", - "name": "삭제" - }, - { - "id": 51, - "identifier": "sirsoft-ecommerce.settings.read", - "name": "환경설정 조회" - }, - { - "id": 52, - "identifier": "sirsoft-ecommerce.settings.update", - "name": "환경설정 수정" - }, - { - "id": 54, - "identifier": "sirsoft-ecommerce.promotion-coupon.read", - "name": "쿠폰 조회" - }, - { - "id": 55, - "identifier": "sirsoft-ecommerce.promotion-coupon.create", - "name": "쿠폰 생성" - }, - { - "id": 56, - "identifier": "sirsoft-ecommerce.promotion-coupon.update", - "name": "쿠폰 수정" - }, - { - "id": 57, - "identifier": "sirsoft-ecommerce.promotion-coupon.delete", - "name": "쿠폰 삭제" - }, - { - "id": 59, - "identifier": "sirsoft-ecommerce.shipping-policies.read", - "name": "배송정책 조회" - }, - { - "id": 60, - "identifier": "sirsoft-ecommerce.shipping-policies.create", - "name": "배송정책 생성" - }, - { - "id": 61, - "identifier": "sirsoft-ecommerce.shipping-policies.update", - "name": "배송정책 수정" - }, - { - "id": 62, - "identifier": "sirsoft-ecommerce.shipping-policies.delete", - "name": "배송정책 삭제" - }, - { - "id": 64, - "identifier": "sirsoft-ecommerce.product-labels.read", - "name": "상품 라벨 조회" - }, - { - "id": 65, - "identifier": "sirsoft-ecommerce.product-labels.create", - "name": "상품 라벨 생성" - }, - { - "id": 66, - "identifier": "sirsoft-ecommerce.product-labels.update", - "name": "상품 라벨 수정" - }, - { - "id": 67, - "identifier": "sirsoft-ecommerce.product-labels.delete", - "name": "상품 라벨 삭제" - }, - { - "id": 69, - "identifier": "sirsoft-ecommerce.identity.policies.read", - "name": "본인인증 정책 조회" - }, - { - "id": 70, - "identifier": "sirsoft-ecommerce.identity.policies.update", - "name": "본인인증 정책 수정" - }, - { - "id": 72, - "identifier": "sirsoft-ecommerce.reviews.read", - "name": "리뷰 조회" - }, - { - "id": 73, - "identifier": "sirsoft-ecommerce.reviews.update", - "name": "리뷰 처리" - }, - { - "id": 74, - "identifier": "sirsoft-ecommerce.reviews.delete", - "name": "리뷰 삭제" - }, - { - "id": 76, - "identifier": "sirsoft-ecommerce.inquiries.update", - "name": "문의 처리" - }, - { - "id": 77, - "identifier": "sirsoft-ecommerce.inquiries.delete", - "name": "문의 삭제" - }, - { - "id": 79, - "identifier": "sirsoft-ecommerce.dashboard.view", - "name": "대시보드 조회" - }, - { - "id": 89, - "identifier": "sirsoft-ecommerce.mileage.read", - "name": "마일리지 내역 조회" - }, - { - "id": 90, - "identifier": "sirsoft-ecommerce.mileage.manage", - "name": "마일리지 수동 처리" - }, - { - "id": 92, - "identifier": "sirsoft-ecommerce.user-currency.manage", - "name": "회원 결제 통화 변경" - }, - { - "id": 94, - "identifier": "sirsoft-ecommerce.user-shipping-country.manage", - "name": "회원 배송국가 변경" - }, - { - "id": 96, - "identifier": "sirsoft-board.boards.read", - "name": "게시판 조회" - }, - { - "id": 97, - "identifier": "sirsoft-board.boards.create", - "name": "게시판 생성" - }, - { - "id": 98, - "identifier": "sirsoft-board.boards.update", - "name": "게시판 수정" - }, - { - "id": 99, - "identifier": "sirsoft-board.boards.delete", - "name": "게시판 삭제" - }, - { - "id": 101, - "identifier": "sirsoft-board.settings.read", - "name": "환경설정 조회" - }, - { - "id": 102, - "identifier": "sirsoft-board.settings.update", - "name": "환경설정 수정" - }, - { - "id": 104, - "identifier": "sirsoft-board.identity.policies.read", - "name": "본인인증 정책 조회" - }, - { - "id": 105, - "identifier": "sirsoft-board.identity.policies.update", - "name": "본인인증 정책 수정" - }, - { - "id": 107, - "identifier": "sirsoft-board.reports.view", - "name": "신고 조회" - }, - { - "id": 108, - "identifier": "sirsoft-board.reports.manage", - "name": "신고 처리" - }, - { - "id": 111, - "identifier": "sirsoft-page.pages.read", - "name": "페이지 조회" - }, - { - "id": 112, - "identifier": "sirsoft-page.pages.create", - "name": "페이지 생성" - }, - { - "id": 113, - "identifier": "sirsoft-page.pages.update", - "name": "페이지 수정" - }, - { - "id": 114, - "identifier": "sirsoft-page.pages.delete", - "name": "페이지 삭제" - }, - { - "id": 117, - "identifier": "core.users.read", - "name": "사용자 조회" - }, - { - "id": 118, - "identifier": "core.users.create", - "name": "사용자 생성" - }, - { - "id": 119, - "identifier": "core.users.update", - "name": "사용자 수정" - }, - { - "id": 120, - "identifier": "core.users.delete", - "name": "사용자 삭제" - }, - { - "id": 122, - "identifier": "core.menus.read", - "name": "메뉴 조회" - }, - { - "id": 123, - "identifier": "core.menus.create", - "name": "메뉴 생성" - }, - { - "id": 124, - "identifier": "core.menus.update", - "name": "메뉴 수정" - }, - { - "id": 125, - "identifier": "core.menus.delete", - "name": "메뉴 삭제" - }, - { - "id": 127, - "identifier": "core.modules.read", - "name": "모듈 조회" - }, - { - "id": 128, - "identifier": "core.modules.install", - "name": "모듈 설치" - }, - { - "id": 129, - "identifier": "core.modules.activate", - "name": "모듈 활성화" - }, - { - "id": 130, - "identifier": "core.modules.uninstall", - "name": "모듈 삭제" - }, - { - "id": 132, - "identifier": "core.plugins.read", - "name": "플러그인 조회" - }, - { - "id": 133, - "identifier": "core.plugins.install", - "name": "플러그인 설치" - }, - { - "id": 134, - "identifier": "core.plugins.activate", - "name": "플러그인 활성화" - }, - { - "id": 135, - "identifier": "core.plugins.update", - "name": "플러그인 설정" - }, - { - "id": 136, - "identifier": "core.plugins.uninstall", - "name": "플러그인 삭제" - }, - { - "id": 138, - "identifier": "core.templates.read", - "name": "템플릿 조회" - }, - { - "id": 139, - "identifier": "core.templates.install", - "name": "템플릿 설치" - }, - { - "id": 140, - "identifier": "core.templates.activate", - "name": "템플릿 활성화" - }, - { - "id": 141, - "identifier": "core.templates.uninstall", - "name": "템플릿 삭제" - }, - { - "id": 142, - "identifier": "core.templates.layouts.edit", - "name": "레이아웃 편집" - }, - { - "id": 144, - "identifier": "core.permissions.read", - "name": "권한 조회" - }, - { - "id": 145, - "identifier": "core.permissions.create", - "name": "역할 생성" - }, - { - "id": 146, - "identifier": "core.permissions.update", - "name": "역할 수정" - }, - { - "id": 147, - "identifier": "core.permissions.delete", - "name": "역할 삭제" - }, - { - "id": 149, - "identifier": "core.notification-logs.read", - "name": "발송 이력 조회" - }, - { - "id": 150, - "identifier": "core.notification-logs.delete", - "name": "발송 이력 삭제" - }, - { - "id": 152, - "identifier": "core.notifications.read", - "name": "알림 조회" - }, - { - "id": 153, - "identifier": "core.notifications.update", - "name": "알림 읽음 처리" - }, - { - "id": 154, - "identifier": "core.notifications.delete", - "name": "알림 삭제" - }, - { - "id": 156, - "identifier": "core.user-notifications.read", - "name": "알림 조회" - }, - { - "id": 157, - "identifier": "core.user-notifications.update", - "name": "알림 읽음 처리" - }, - { - "id": 158, - "identifier": "core.user-notifications.delete", - "name": "알림 삭제" - }, - { - "id": 160, - "identifier": "core.identity.request", - "name": "IDV 요청" - }, - { - "id": 161, - "identifier": "core.identity.verify", - "name": "IDV 검증" - }, - { - "id": 162, - "identifier": "core.identity.cancel", - "name": "IDV 취소" - }, - { - "id": 164, - "identifier": "core.admin.identity.providers.read", - "name": "프로바이더 설정 조회" - }, - { - "id": 165, - "identifier": "core.admin.identity.providers.update", - "name": "프로바이더 설정 수정" - }, - { - "id": 166, - "identifier": "core.admin.identity.policies.read", - "name": "정책 조회" - }, - { - "id": 167, - "identifier": "core.admin.identity.policies.update", - "name": "정책 수정" - }, - { - "id": 168, - "identifier": "core.admin.identity.logs.read", - "name": "로그 열람" - }, - { - "id": 169, - "identifier": "core.admin.identity.logs.purge", - "name": "로그 파기" - }, - { - "id": 170, - "identifier": "core.admin.identity.messages.read", - "name": "메시지 템플릿 조회" - }, - { - "id": 171, - "identifier": "core.admin.identity.messages.update", - "name": "메시지 템플릿 수정" - }, - { - "id": 173, - "identifier": "core.settings.read", - "name": "설정 조회" - }, - { - "id": 174, - "identifier": "core.settings.update", - "name": "설정 수정" - }, - { - "id": 176, - "identifier": "core.dashboard.read", - "name": "대시보드 조회" - }, - { - "id": 177, - "identifier": "core.dashboard.system-status", - "name": "시스템 상태" - }, - { - "id": 178, - "identifier": "core.dashboard.resources", - "name": "시스템 리소스" - }, - { - "id": 179, - "identifier": "core.dashboard.activities", - "name": "최근 활동" - }, - { - "id": 180, - "identifier": "core.dashboard.alerts", - "name": "시스템 알림" - }, - { - "id": 182, - "identifier": "core.activities.read", - "name": "활동 로그 조회" - }, - { - "id": 183, - "identifier": "core.activities.delete", - "name": "활동 로그 삭제" - }, - { - "id": 185, - "identifier": "core.attachments.create", - "name": "첨부파일 업로드" - }, - { - "id": 186, - "identifier": "core.attachments.update", - "name": "첨부파일 수정" - }, - { - "id": 187, - "identifier": "core.attachments.delete", - "name": "첨부파일 삭제" - }, - { - "id": 189, - "identifier": "core.schedules.read", - "name": "스케줄 조회" - }, - { - "id": 190, - "identifier": "core.schedules.create", - "name": "스케줄 생성" - }, - { - "id": 191, - "identifier": "core.schedules.update", - "name": "스케줄 수정" - }, - { - "id": 192, - "identifier": "core.schedules.delete", - "name": "스케줄 삭제" - }, - { - "id": 193, - "identifier": "core.schedules.run", - "name": "스케줄 실행" - }, - { - "id": 195, - "identifier": "core.language_packs.read", - "name": "언어팩 조회" - }, - { - "id": 196, - "identifier": "core.language_packs.install", - "name": "언어팩 설치" - }, - { - "id": 197, - "identifier": "core.language_packs.manage", - "name": "언어팩 관리" - }, - { - "id": 198, - "identifier": "core.language_packs.update", - "name": "언어팩 업데이트" - }, - { - "id": 1, - "identifier": "apidoc-sample.parent", - "name": "API 문서 샘플 권한" - }, - { - "id": 2, - "identifier": "apidoc-sample.child", - "name": "하위 권한" - }, - { - "id": 3, - "identifier": "sirsoft-board", - "name": "게시판" - }, - { - "id": 21, - "identifier": "sirsoft-ecommerce", - "name": "이커머스" - }, - { - "id": 109, - "identifier": "sirsoft-page", - "name": "페이지" - }, - { - "id": 4, - "identifier": "sirsoft-board.apidoc-sample-board", - "name": "API 문서 샘플 게시판 게시판" - }, - { - "id": 95, - "identifier": "sirsoft-board.boards", - "name": "게시판 관리" - }, - { - "id": 100, - "identifier": "sirsoft-board.settings", - "name": "환경설정" - }, - { - "id": 103, - "identifier": "sirsoft-board.identity.policies", - "name": "게시판 본인인증 정책" - }, - { - "id": 106, - "identifier": "sirsoft-board.reports", - "name": "게시판 신고 관리" - }, - { - "id": 22, - "identifier": "sirsoft-ecommerce.products", - "name": "상품 관리" - }, - { - "id": 27, - "identifier": "sirsoft-ecommerce.orders", - "name": "주문 관리" - }, - { - "id": 30, - "identifier": "sirsoft-ecommerce.categories", - "name": "카테고리 관리" - }, - { - "id": 35, - "identifier": "sirsoft-ecommerce.brands", - "name": "브랜드 관리" - }, - { - "id": 40, - "identifier": "sirsoft-ecommerce.product-notice-templates", - "name": "상품정보제공고시 관리" - }, - { - "id": 45, - "identifier": "sirsoft-ecommerce.product-common-infos", - "name": "공통정보 관리" - }, - { - "id": 50, - "identifier": "sirsoft-ecommerce.settings", - "name": "환경설정" - }, - { - "id": 53, - "identifier": "sirsoft-ecommerce.promotion-coupon", - "name": "쿠폰 관리" - }, - { - "id": 58, - "identifier": "sirsoft-ecommerce.shipping-policies", - "name": "배송정책 관리" - }, - { - "id": 63, - "identifier": "sirsoft-ecommerce.product-labels", - "name": "상품 라벨 관리" - }, - { - "id": 68, - "identifier": "sirsoft-ecommerce.identity.policies", - "name": "이커머스 본인인증 정책" - }, - { - "id": 71, - "identifier": "sirsoft-ecommerce.reviews", - "name": "리뷰 관리" - }, - { - "id": 75, - "identifier": "sirsoft-ecommerce.inquiries", - "name": "문의 관리" - }, - { - "id": 78, - "identifier": "sirsoft-ecommerce.dashboard", - "name": "대시보드" - }, - { - "id": 80, - "identifier": "sirsoft-ecommerce.user-products", - "name": "사용자 상품" - }, - { - "id": 82, - "identifier": "sirsoft-ecommerce.user-orders", - "name": "사용자 주문" - }, - { - "id": 86, - "identifier": "sirsoft-ecommerce.user-reviews", - "name": "사용자 리뷰" - }, - { - "id": 88, - "identifier": "sirsoft-ecommerce.mileage", - "name": "마일리지 관리" - }, - { - "id": 91, - "identifier": "sirsoft-ecommerce.user-currency", - "name": "회원 결제 통화 관리" - }, - { - "id": 93, - "identifier": "sirsoft-ecommerce.user-shipping-country", - "name": "회원 배송국가 관리" - }, - { - "id": 110, - "identifier": "sirsoft-page.pages", - "name": "페이지 관리" - }, - { - "id": 115, - "identifier": "core", - "name": "코어" - }, - { - "id": 116, - "identifier": "core.users", - "name": "사용자 관리" - }, - { - "id": 121, - "identifier": "core.menus", - "name": "메뉴 관리" - }, - { - "id": 126, - "identifier": "core.modules", - "name": "모듈 관리" - }, - { - "id": 131, - "identifier": "core.plugins", - "name": "플러그인 관리" - }, - { - "id": 137, - "identifier": "core.templates", - "name": "템플릿 관리" - }, - { - "id": 143, - "identifier": "core.permissions", - "name": "권한 관리" - }, - { - "id": 148, - "identifier": "core.notification-logs", - "name": "알림 발송 이력" - }, - { - "id": 151, - "identifier": "core.notifications", - "name": "알림 (관리자)" - }, - { - "id": 155, - "identifier": "core.user-notifications", - "name": "알림 (사용자)" - }, - { - "id": 159, - "identifier": "core.identity", - "name": "본인인증 (사용자)" - }, - { - "id": 163, - "identifier": "core.admin.identity", - "name": "본인인증 관리 (관리자)" - }, - { - "id": 172, - "identifier": "core.settings", - "name": "환경설정" - }, - { - "id": 175, - "identifier": "core.dashboard", - "name": "대시보드" - }, - { - "id": 181, - "identifier": "core.activities", - "name": "활동 로그" - }, - { - "id": 184, - "identifier": "core.attachments", - "name": "첨부파일 관리" - }, - { - "id": 188, - "identifier": "core.schedules", - "name": "스케줄 관리" - }, - { - "id": 194, - "identifier": "core.language_packs", - "name": "언어팩 관리" - } - ], - "consents": [], - "terms_consent": [], - "privacy_consent": [], - "created_at": "2026-07-08 10:41:24", - "updated_at": "2026-07-08 10:41:24", - "is_owner": true, - "abilities": { - "can_read": true, - "can_create": true, - "can_update": true, - "can_delete": true, - "can_assign_roles": true - }, - "notify_post_complete": false, - "notify_post_reply": false, - "notify_comment": false, - "notify_reply_comment": false, - "ecommerce_mileage": { - "enabled": false - }, - "ecommerce_preferred_currency": null, - "ecommerce_preferred_shipping_country": null, - "ecommerce_preferred_shipping_country_name": null + "...": "(63개 키 생략, 총 68개)" } } ``` @@ -2860,7 +790,7 @@ HTTP/1.1 200 | token | body | string | 예 | — | 인증/검증 토큰 | | email | body | email | 예 | — | 이메일 주소 | -> 이 엔드포인트는 확장이 파라미터를 추가할 수 있습니다 (`core.auth.validate_reset_token_validation_rules`, `core.auth.validate_reset_token_messages`). +> 이 엔드포인트는 확장이 파라미터를 추가할 수 있습니다 (`core.auth.validate_reset_token_messages`). **요청 예시** @@ -2878,7 +808,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -2918,7 +848,7 @@ Authorization: Bearer {YOUR_TOKEN} (optional.sanctum: 비회원은 헤더 생 **응답 필드** (`data` 내부) - + **응답 예시** @@ -2958,7 +888,7 @@ Authorization: Bearer {YOUR_TOKEN} (optional.sanctum: 비회원은 헤더 생 **응답 필드** (`data` 내부) - + **응답 예시** @@ -2998,7 +928,7 @@ Authorization: Bearer {YOUR_TOKEN} (optional.sanctum: 비회원은 헤더 생 **응답 필드** (`data` 내부) - + **응답 예시** diff --git a/docs/backend/api/avatar.md b/docs/backend/api/avatar.md index 6c0a2046..efcdbc74 100644 --- a/docs/backend/api/avatar.md +++ b/docs/backend/api/avatar.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Avatar 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` @@ -38,7 +38,7 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - + **응답 예시** @@ -67,8 +67,6 @@ Authorization: Bearer {YOUR_TOKEN} | --- | --- | --- | --- | --- | --- | | avatar | body | image | 예 | max 2048 | 아바타 이미지 | -> 이 엔드포인트는 확장이 파라미터를 추가할 수 있습니다 (`core.user.upload_avatar_validation_rules`). - **요청 예시** ```http @@ -88,7 +86,7 @@ Content-Type: image/png **응답 필드** (`data` 내부) - + **응답 예시** diff --git a/docs/backend/api/broadcasting.md b/docs/backend/api/broadcasting.md index a2ee94ce..0bb3c297 100644 --- a/docs/backend/api/broadcasting.md +++ b/docs/backend/api/broadcasting.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Broadcasting 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` @@ -37,7 +37,7 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - + **응답 예시** diff --git a/docs/backend/api/changelog.md b/docs/backend/api/changelog.md index d903423a..92dfd091 100644 --- a/docs/backend/api/changelog.md +++ b/docs/backend/api/changelog.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Changelog 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` @@ -55,7 +55,7 @@ HTTP/1.1 200 "success": true, "message": "성공적으로 처리되었습니다.", "data": { - "content": "# Changelog\n\n이 프로젝트의 모든 주요 변경사항을 기록합니다.\n형식은 [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/)를 따르며,\n[Semantic Versioning](https://semver.org/lang/ko/)을 준수합니다.\n\n## [7.0.2] - 2026-07-05\n\n### Added\n\n- 설치된 모듈·플러그인이 늘어날수록 화면마다 개별로 내려받던 확장 스크립트·스타일 요청을 종류별로 하나로 묶어 내려받도록 개선해, 확장이 많은 사이트의 초기 로딩 요청 수를 크게 줄였습니다. 확장을 설치·활성화·비활성화하면 묶음이 자동으로 다시 만들어져 항상 현재 활성 상태와 일치합니다.\n\n### Changed\n\n- 화면·데이터를 내려받을 때의 전송 용량을 줄여 초기 로딩과 페이지 이동 속도를 개선했습니다.\n - 모듈·플러그인의 묶음 스크립트·스타일이 지금까지 압축 없이 전송되던 것을 압축 전송하도록 바꿔, 확장이 많은 사이트에서 첫 화면에 받는 용량을 크게 줄였습니다.\n - 한글 등 다국어 텍스트가 응답에 불필요하게 부풀려 담기던 것을 원래 형태 그대로 담도록 바꿔, 특히 다국어 번역 데이터의 전송 용량을 줄였습니다.\n - 화면을 그리는 데 쓰이지 않는 개발용 설명 주석을 화면 정보 응답에서 빼도록 해 응답 용량을 추가로 줄였습니다.\n\n### Fixed\n\n- 코어 업데이트 시 레이아웃 편집기·개발자 진단도구 번들의 빌드 설정 파일이 갱신 대상에서 누락되던 문제를 수정했습니다. 이제 코어를 업데이트하면 이 파일들도 최신 내용으로 반영됩니다.\n- 설치 마법사에서 Vendor 설치 방식을 \"Composer 실행\"으로 선택해 설치할 때, 일부 Windows 환경에서 Composer 설치 로그의 문자 인코딩이 어긋나면 진행 상태 응답이 비어 진행 상황이 표시되지 않고 오류가 반복될 수 있던 문제를 보완했습니다. 어떤 문자가 섞이더라도 진행 로그가 항상 정상적으로 전달되도록 처리했습니다. (#62 @kitrio 님께서 제보해주셨습니다.)\n- 설치 마법사의 \"설치 환경 확인\" 단계에서, storage 폴더 하위에 파일이 많이 쌓인 환경일수록 요구사항 검사가 오래 걸리던 문제를 개선했습니다. 폴더 쓰기 권한 검사가 불필요하게 깊은 하위까지 훑던 것을 필요한 범위로 제한해 이 단계가 즉시 완료되도록 했습니다.\n- 설치 시 \"읽기 전용 데이터베이스(Read DB)를 사용하지 않음\"으로 선택했는데도, 읽기 전용 DB 접속 정보를 확인하려다 설치 마지막 단계에서 실패하던 문제를 수정했습니다. 이제 읽기 전용 DB를 사용하지 않으면 쓰기 DB 정보만으로 설치가 정상 완료됩니다. 아울러 설치 이후에도 읽기 전용 DB 접속 정보를 비워 두면 조회 연결이 자동으로 쓰기 DB로 대체되도록 하여, 별도의 읽기 전용 DB 없이도 안정적으로 동작합니다. (#63 @kitrio 님께서 제보해주셨습니다.)\n\n## [7.0.1] - 2026-07-03\n\n### Changed\n\n- 첫 화면 접속 시 내려받는 코어 스크립트 용량을 절반 이하로 줄여 초기 로딩을 개선했습니다. 레이아웃 편집기와 개발자 진단도구를 코어에서 분리해, 편집기는 편집 화면에 들어갈 때만, 진단도구는 디버그 모드를 켰을 때만 내려받도록 했습니다. 일반 사용자는 평소 쓰지 않는 편집기·진단도구 코드를 더 이상 받지 않습니다.\n- 설치가 끝난 사이트에서 매 요청마다 반복되던 불필요한 데이터베이스 조회를 줄여 페이지 응답 속도를 개선했습니다. 알림·본인인증 설정을 준비하는 과정에서 이미 존재가 보장된 테이블을 매번 다시 확인하던 동작을 설치 완료 상태에서는 건너뛰도록 했습니다.\n- 화면을 그릴 때마다 서버가 매번 확장 기능의 연결 정보를 처음부터 다시 계산하던 부담을 줄여 페이지 응답 속도를 개선했습니다. 이 연결 정보는 확장을 설치·수정하거나 코어를 업데이트할 때만 바뀌므로 그 시점에 한 번 저장해 두고, 이후 요청에서는 저장된 값을 재사용하도록 했습니다. 확장이나 코어를 변경하면 저장 값이 자동으로 갱신됩니다.\n- 설치된 모듈·플러그인의 코드를 찾을 때마다 서버가 파일을 하나하나 탐색하던 부담을 줄여, 특히 공유 호스팅 등 파일 접근이 느린 환경에서 페이지 응답 속도를 개선했습니다. 확장 코드의 위치를 설치·수정 시점에 미리 정리해 두고 이후 요청에서는 그 목록을 바로 참조하도록 했습니다. 확장을 설치·수정·삭제하거나 코어를 업데이트하면 목록이 자동으로 갱신됩니다.\n- 코어 업데이트 중 데이터 변환 단계가 현재 환경에서 자동으로 끝까지 실행되지 못하고 중단될 때, 남은 단계를 이어서 실행하는 안내를 실행 권한에 맞게 개선했습니다. 특히 관리자 권한(sudo)으로 업데이트한 경우, 남은 단계를 관리자 권한 그대로 실행하면 그 과정에서 만들어지는 파일이 관리자 소유로 남아 이후 웹사이트가 그 파일에 쓰지 못하는 문제가 생길 수 있습니다. 이제 이런 경우 웹 서버 실행 계정(예: www-data)으로 실행하도록 정확한 명령을 함께 안내하며, 웹 서버 계정을 자동으로 확인하지 못하면 어떤 계정으로 실행해야 하는지 확인 방법을 안내합니다.\n\n### Fixed\n\n- Windows 환경에서 코어 업데이트가 `public/storage` 링크를 복사하는 단계에서 \"디렉토리는 복사할 수 없습니다\" 오류로 중단되던 문제를 수정했습니다. Windows의 저장소 링크(정션)가 이제 링크 그대로 안전하게 보존되어 업데이트가 정상 완료됩니다.\n- 별도 데이터 변환 작업이 필요 없는 버전으로 코어를 업데이트할 때, \"실행할 업그레이드 단계가 없다\"는 정상 상황을 실패로 표시하고 중단하던 문제를 수정했습니다. 이제 변환 작업이 없는 업데이트는 매끄럽게 완료되며, 이어서 설치된 확장의 최신 번들 반영 단계까지 정상적으로 진행됩니다.\n- 일부 호스팅 환경에서 관리자 환경설정의 보안 화면에 들어갈 때 시스템 정보 조회가 전체 실패(오류 응답)하던 문제를 수정했습니다. 이제 CPU·메모리·디스크 등 특정 항목을 서버 정책상 읽을 수 없더라도 그 항목만 \"알 수 없음\"으로 표시하고 나머지 시스템 정보는 정상적으로 보여줍니다. 읽지 못한 항목은 로그로 원인을 확인할 수 있습니다. (#59 @glitter-gim 님께서 제보해주셨습니다.)\n\n## [7.0.0] - 2026-07-01\n\n### Added\n\n- 모듈·플러그인이 기존 화면의 초기화 동작에 자신의 초기화 단계를 덧붙일 수 있도록 레이아웃 확장 기능을 확대했습니다. 화면이 특정 모듈을 직접 알지 않아도, 모듈이 설치되어 있을 때만 그 모듈의 초기화(예: 표시 통화 복원)가 함께 실행됩니다.\n\n- 관리자 대시보드 통계에 설치된 템플릿 수(활성/전체)와 활성 언어팩 수(활성/전체)를 추가했습니다.\n- 관리자 대시보드에 최근 발송된 알림 이력을 보여주는 \"최근 알림\" 위젯용 조회 기능을 추가했습니다. 발송 채널·수신자·제목·상태(성공/실패/건너뜀)·발송 시각을 최신순으로 확인할 수 있습니다.\n- 본인인증 challenge 취소(cancel) 와 verification 토큰 소비(consume_token) 시점에 외부 본인인증 플러그인이 자기 데이터 정리 로직을 등록할 수 있는 훅 4건 추가.\n- 플러그인 개발용 표준 베이스 클래스를 추가하여, 모듈 개발과 동일한 방식으로 Repository · 캐시 · 스토리지 의존성을 선언만으로 자동 주입받을 수 있도록 개선. 플러그인이 코어와 같은 표준 위에서 작성되어 일관된 캐시·파일 격리 동작을 제공합니다.\n- 모듈·플러그인이 페이지에 주입하는 레이아웃 확장을 관리자 화면에서 직접 편집할 수 있도록 개선. 편집 내역은 버전으로 기록되어 이전 버전으로 복구할 수 있으며, 저장 전 미리보기를 제공합니다.\n\n#### 위지윅 레이아웃 편집기 (신규 도입)\n\n화면을 직접 보며 편집하는 위지윅 방식의 레이아웃 편집기를 이번 버전에서 새로 도입했습니다. 아래는 도입된 기능 영역별 상세입니다.\n\n- 좌측 트리 — 편집 화면 좌측 목록을 트리 구조로 개편하여 일반 레이아웃과 확장 주입 레이아웃을 구분 표시. 확장 주입 레이아웃은 해당 템플릿에 실제 적용되는 항목만 표시됩니다.\n- 편집 진입과 캔버스 — 템플릿 관리 화면에 코드 편집과 레이아웃 편집 진입을 분리하고, 운영 화면 좌측 하단에 편집 권한 보유자 전용 진입 버튼을 추가했습니다. 편집 캔버스는 실제 페이지와 동일한 렌더링 결과를 보여주며, 데스크탑·태블릿·모바일 폭을 즉시 전환해 반응형 결과를 확인할 수 있습니다. 데이터 소스가 정의된 화면은 샘플 데이터로 자동 채워져 실제 API 없이도 화면을 검토할 수 있고, 본문 영역에 사용된 위지윅 에디터 등 동적 자원도 캔버스 안에서 그대로 동작합니다. 좌측 트리에서 화면을 선택하면 주소창이 즉시 반영되어 새로고침·공유·뒤로가기 동작이 자연스럽고, 같은 주소로 직접 진입하면 해당 화면이 자동 선택됩니다. 편집 중 로그인이 풀리면 안내 후 자동으로 로그인 화면으로 이동하며 재로그인 시 편집 화면으로 복귀합니다. 권한 부족·서버 오류·네트워크 끊김 등은 상황별 안내 카드(아이콘·설명·다음 행동 버튼)로 표시합니다.\n- 요소 추가·재배치 — 컴포넌트를 클릭해 선택하면 외곽에 + 버튼이 나타나고, + 버튼을 누르면 화면 중앙에 모달 팔레트가 열려 좌측 카테고리 사이드바와 우측 그리드 카드(아이콘 + 라벨 + 유형 뱃지)에서 추가할 컴포넌트를 고를 수 있습니다. 부모 컨테이너에 들어갈 수 있는 컴포넌트만 자동 필터링되며, 검색으로 컴포넌트를 빠르게 찾을 수 있습니다. 추가한 컴포넌트는 템플릿이 정의한 기본 모양·기본 텍스트로 캔버스에 바로 보이며, 추가 직후 해당 위치로 화면이 부드럽게 스크롤됩니다. 컴포넌트 우상단 ⓘ 아이콘으로 속성 설정·복사·삭제 메뉴를 열고, 호버/선택 시 외곽선·8방향 리사이즈 핸들 자리·잠금/네비게이션 어포던스가 표시됩니다. 변경 사항은 Ctrl+Z / Ctrl+Shift+Z 또는 툴바 ↶/↷ 로 되돌리거나 다시 실행할 수 있습니다. 편집 모드에서 컴포넌트를 클릭해도 페이지 이동이 일어나지 않고 선택만 되며, 내부 라우트로 가리키는 요소는 \"→ 이 화면 편집\" 어포던스로 목적지 화면 편집 모드로 즉시 전환됩니다.\n- 저장·충돌 방지 — 낙관적 잠금으로 저장 결과는 성공·검증 실패·동시 저장 충돌·네트워크 오류 등 상황별 안내(성공 토스트 / 오류 배너 / 충돌 모달)로 즉시 표시됩니다. 같은 레이아웃을 다른 관리자가 먼저 저장했으면 충돌 안내 모달에서 최신 버전 불러오기를 선택할 수 있고, 신규 추가한 컴포넌트가 그 사이 비활성화된 모듈·플러그인 소속이면 저장이 자동 차단됩니다. 저장 직후에는 되돌리기 이력이 정리되어 저장 시점이 새 기준이 되며, 변경 사항이 있는 상태에서 다른 화면으로 이동하려 하면 저장하지 않은 내용이 있다는 안내가 먼저 표시됩니다. 로그인·회원가입 등 비로그인 전용 화면을 편집할 때는 로그인 상태 미리보기가 적용되지 않아 안내 토스트가 잘못 뜨지 않습니다.\n- 속성 편집 — 요소를 선택하면 정렬·글자 크기·굵기·색상·여백·크기 등을 친화적인 컨트롤(분절 버튼·슬라이더·색상 선택·태그 입력)로 조정할 수 있고, 요소 모서리·변을 직접 드래그해 가로·세로 크기를 바꿀 수 있습니다(드래그 크기 조정도 되돌리기/다시 실행에 반영). 반복 영역(목록처럼 같은 틀이 여러 번 펼쳐지는 자리)의 속성을 편집하면 모든 항목에 함께 적용됩니다. 요소를 편집하는 동안에는 선택한 요소만 밝게 유지되고 나머지 화면은 어둡게 잠겨, 다른 요소를 실수로 건드리지 않습니다. 속성 편집 팝업은 헤더를 잡아 옮길 수 있으며 선택한 요소를 가리지 않도록 빈 공간으로 자동 배치되고, 큰 요소에서도 요소 추가·메뉴 버튼이 크기 조절 핸들과 겹치지 않도록 여백을 둡니다. 고급 설정에서 요소를 볼 수 있는 권한을 권한 목록에서 골라 지정할 수 있습니다. 목록 있음·없음, 회원·비회원, 검증 실패·저장 중 같은 화면 상태를 미리보기로 전환해 데이터 의존 영역을 실제와 가깝게 검토할 수 있고, 같은 데이터 이름을 여러 확장이 쓰더라도 각 화면이 자기 출처의 샘플로 올바르게 표시됩니다. 라우트를 전환해도 저장하지 않은 편집 내용이 유지되며, 미저장 상태로 화면을 떠나거나 새로고침하면 경고가 표시됩니다. 마지막 저장 상태로 한 번에 되돌리는 초기화 버튼도 제공합니다.\n- 이미지 관리 — 배경 이미지 등 레이아웃 첨부 이미지를 업로드·관리할 수 있으며, 상단 툴바와 배경 이미지 컨트롤에서 썸네일 갤러리를 열어 업로드·삭제·배경 적용을 할 수 있으며, 지정한 배경 이미지가 편집 캔버스에 즉시 반영되고(채움·맞춤·타일 표시 모드 포함) 모달을 다시 열어도 현재 설정이 그대로 복원됩니다. 발행된 배경 이미지는 일반 방문자 화면에서도 정상적으로 표시됩니다.\n- 동작·표시조건·정렬 편집 — 요소를 선택하고 속성 편집 팝업에서 (1) 클릭·마우스 오버 시 동작을 \"다른 페이지로 이동\"·\"안내 메시지 보여주기\"·\"화면 상태 바꾸기\"·\"목록 새로고침\"·\"서버에 보내고 결과 처리\"·\"결제 진입\" 같은 친화 명칭으로 조립하고(서버 호출은 성공·실패 후속 동작까지 단계로 구성), (2) 로그인 회원·관리자·데이터 있음/없음·수정/새 화면·입력 오류 등 친화 조건을 \"그리고/또는\"으로 결합해 요소를 보여줄 조건을 정하며, (3) 가로·세로 컨테이너의 자식 배치(방향·정렬·줄바꿈·간격)와 정렬 박스 안 요소의 늘어남·정렬·순서를 시각 컨트롤로 편집할 수 있습니다. 핸들러·코드 용어 없이 친화 입력만 노출되며, 이동할 페이지·데이터 이름은 목록에서 선택합니다. \"결제 진입\" 동작은 특정 결제사에 고정하지 않고, 서버 응답이 알려주는 결제 수단(결제 핸들러·결제 데이터)을 데이터 검색에서 골라 연결합니다 — 결제 수단을 바꾸거나 추가해도 화면 편집 없이 동작합니다.\n- 색 모드·디바이스별 스타일 — 속성 편집에 라이트/다크 색 모드와 디바이스별 편집을 더해, 스타일·표시조건 탭 상단의 서브탭에서 라이트/다크 색 모드와 기본값·PC·태블릿·모바일(+ 직접 지정하는 커스텀 화면 폭)을 선택해, 각 범위에 스타일·정렬·표시 조건을 따로 지정할 수 있습니다. 글자색·배경색은 패널의 프리셋 색을 고르면 라이트와 다크 모두에 적용되어 다크 화면용 색을 비개발자도 지정할 수 있고(컬러 피커로 직접 고른 자유 색은 라이트 화면 전용), 어느 범위에 기본값과 다른 설정이 있으면 탭에 표시점이 뜨며 \"기본값으로 초기화\"로 그 범위만 되돌릴 수 있습니다. 편집기 상단 라이트/다크 토글로 미리보기 색상 테마를 전환하면, 관리자 화면이 다크 테마여도 미리보기가 라이트/다크를 정확히 구분해 보여줍니다.\n- 화면 문구 다국어 편집 — 상단 툴바의 콘텐츠 언어 선택으로 미리보기에 표시되는 화면 문구의 언어를 전환할 수 있고, 캔버스의 텍스트를 더블클릭하면 그 자리에서 바로 고칠 수 있습니다(Enter 로 확정, Esc 로 취소). 문구를 고치면 그 문구가 다국어 키로 자동 등록되어, 입력한 내용이 즉시 화면에 반영됩니다. 편집 중 떠 있는 서식 막대로 굵게·기울임·밑줄을 켜고 끄거나, 글자 크기·정렬·글자색을 목록에서 골라 적용할 수 있으며(색은 색상 미리보기와 함께 선택), 바뀐 서식은 편집 중에도 즉시 보입니다. 편집 안내 배지를 누르면 속성 편집의 번역 탭이 열려 모든 언어의 값을 한 번에 입력할 수 있어, 한 언어만 고치고 다른 언어 번역을 놓치는 일을 줄입니다. (데이터에 연결된 문구·반복 영역 문구는 직접 편집 대상에서 제외됩니다.)\n- 모든 컴포넌트로 속성 편집 확대 — 어떤 요소를 선택해도 속성 탭에 \"요소 ID\" 칸이 항상 제공되어 화면 내 요소에 고유 식별자를 부여할 수 있으며(화면 안전 문자만 입력되고 한글·공백 등은 자동으로 걸러집니다), 컴포넌트별 편집 항목(링크 주소·이미지 주소·입력칸 종류·선택지·정렬 옵션 등)도 친화 컨트롤로 편집할 수 있습니다. 아이콘은 자유 입력 대신 검색 가능한 그리드에서 골라 지정할 수 있어(템플릿이 제공하는 아이콘 목록을 검색으로 빠르게 찾기), 아이콘 이름을 외우지 않아도 됩니다. 입력칸의 선택지처럼 여러 항목으로 된 값은 항목 추가·삭제·순서 변경으로 편집하며, 이미 데이터에 연결된 값은 실수로 덮어쓰지 않도록 보호됩니다.\n- 모달 시각 편집 — 좌측 트리에 각 화면이 사용하는 모달 목록이 \"이 화면의 모달\" 그룹으로 표시되고(공통 레이아웃의 모달, 모듈·플러그인 화면의 모달, 별도 파일로 분리된 모달 포함), 각 모달은 \"주문 취소\"·\"비회원 조회 비밀번호 재설정\"처럼 친화 제목으로 표시됩니다. 모달을 선택하면 실제 화면 위에 모달이 열린 상태로 문구 수정·요소 추가·저장을 할 수 있습니다. 모달 바깥 영역은 어둡게 잠겨 클릭·드래그·편집이 차단되어 실수로 호스트 화면을 건드리지 않습니다.\n- 버전 배지·복원 — 좌측 트리에 각 화면과 확장 주입 조각의 현재 저장 버전 배지가 표시되고, 버전 기록 모달에서 이전 버전과의 비교(diff)와 복원을 확장 주입 조각에서도 동일하게 사용할 수 있으며, 저장·복원 시 배지가 새로고침 없이 즉시 갱신되고 복원 결과가 캔버스에 바로 반영됩니다.\n- 공통 레이아웃 편집 — 각 화면의 콘텐츠가 채워질 슬롯 자리가 점선 박스와 라벨로 표시되고(슬롯 자체는 편집 대상이 아니라 잠금), 헤더·푸터를 클릭해 바로 선택·편집할 수 있습니다. 모달 편집 중에도 편집 캔버스의 스크롤이 그대로 유지됩니다.\n- 화면 상태 미리보기 — 모달·공통 레이아웃까지 확대해, 배송지 직접 입력, 모바일 메뉴 펼침, 쿠키 배너 표시, 본인인증 진행 모달처럼 특정 조작에서만 나타나는 영역을 상태 전환만으로 미리보며 편집할 수 있고, 모달 안에 확장이 주입한 영역도 시각 편집할 수 있습니다.\n- 일부 템플릿(사용자 템플릿 등)의 화면을 미리볼 때 헤더가 \"데이터를 표시할 수 없습니다\"로 표시되지 않고, 미리보기용 샘플 데이터가 모든 템플릿에서 안전하게 렌더됩니다.\n\n- 회원이 아닌 사용자(비회원)에게도 이메일 알림을 보낼 수 있도록 알림 시스템을 확장했습니다. 회원에게 알림을 보내던 것과 동일한 방식으로, 입력한 이메일을 수신자로 알림이 발송되며 발송 내역에도 함께 기록됩니다. 알림 종류별로 비회원 발송을 허용할 수단을 선택할 수 있어, 이메일은 비회원에게 보내되 사이트 안 알림처럼 로그인이 필요한 수단은 보내지 않도록 구분됩니다(새 알림 수단이 추가될 때 비회원 허용 여부를 별도로 정하지 않으면 기본적으로 보내지 않습니다). 비회원에게 가는 알림은 그 사용자가 보던 화면 언어로 발송됩니다.\n\n### Changed\n\n- 모든 번들 플러그인이 위 표준 베이스 클래스로 이관되어 캐시·스토리지 도메인이 코어 및 다른 확장과 명확히 격리되도록 개선. 한 플러그인의 캐시 키가 코어 캐시 영역을 침범하던 잠재 결함을 차단하며, 관리자 화면의 변경사항이 의도대로 즉시 반영됩니다.\n- 템플릿이 정의하는 에러 페이지(404 / 403 / 500 / 503 / 401 / 점검) 의 식별자를 다른 모든 페이지와 동일한 디렉토리 접두사 포함 형식(`errors/{코드}`) 으로 통일. 템플릿 설치 시 에러 페이지가 일관된 식별자로 시드되며, 일부 환경에서 에러 페이지가 \"찾을 수 없음\" 응답으로 떨어지던 회귀를 차단합니다. 번들 템플릿(`sirsoft-basic`, `sirsoft-admin_basic`) 의 에러 페이지 설정도 함께 정합화됩니다.\n- 템플릿 외부 리소스 위임 — Font Awesome·웹폰트 등 외부 CDN 리소스를 페이지 코드에서 하드코딩하지 않고 템플릿의 `externals` 선언으로 위임. 외부 스타일시트·웹폰트·스크립트와 preconnect·preload 등 리소스 힌트를 한 곳에서 관리하며, 관리자 템플릿과 사용자 템플릿에 동일하게 적용\n- 템플릿 컴포넌트 에셋 캐시 버스팅 — `?v=time()` → `?v={{ $extensionCacheVersion }}`로 변경하여 매 요청 무효화 → 확장 변경 시점에만 무효화\n- 모듈·플러그인 업데이트 시 관리자가 편집한 확장 레이아웃을 보존하거나 덮어쓰는 전략을 선택할 수 있도록 개선.\n- 코어 업그레이드 스텝 명령에 보조 단계(권한 정상화·마이그레이션·캐시 정리 등)를 건너뛰고 스텝만 실행하는 옵션 추가.\n- 코어 영역의 프론트엔드 다국어 자원(에러 메시지·시스템 토스트 등) 위치를 정리하여 어떤 템플릿이 부팅되어도 자동 노출되도록 개선. 이전에 코어 에러 화면에서 `core.errors.*` 같은 키가 raw 로 노출되던 결함이 해소됩니다.\n\n### Fixed\n\n#### 본인인증(IDV)\n\n- 정책 관리에서 코어·모듈·플러그인이 제공한 정책의 \"인증 목적\"·\"적용 대상\"·\"우선순위\"를 바꿔 저장해도 반영되지 않던 문제를 수정했습니다 — 이제 정책 키·강제 시점·강제 위치(정책이 걸리는 지점)만 고정되고, 그 외 모든 항목을 운영자가 자유롭게 변경할 수 있으며, 변경한 값은 확장 업데이트 후에도 유지됩니다.\n- 한 화면에 본인인증 정책이 여러 개 걸리고 우선순위가 같을 때, 어느 정책이 먼저 적용될지 정해지지 않아 의도한 인증(예: 성인 인증)이 무시되고 다른 인증이 적용될 수 있던 결함을 수정했습니다. 이제 우선순위가 같아도 항상 동일한 정책이 적용되도록 적용 순서를 고정하고, 정책 추가·수정 화면에 우선순위 입력칸을 제공하며 같은 위치에 같은 우선순위의 활성 정책을 저장하려 하면 저장을 막고 안내합니다. 정책 목록에도 우선순위 열을 추가해 한눈에 확인할 수 있습니다.\n- 성인 인증 같은 추가 조건을 함께 요구할 때, 그 조건을 충족하지 못해 인증이 실패하면 실패 사유 안내와 \"본인 확인이 필요합니다\" 안내가 동시에 뜨던 문제를 수정했습니다. 이제 추가 조건 미충족으로 실패하면 그 사유 안내 하나만 표시되며, 일반 본인인증 실패의 안내는 종전대로 표시됩니다.\n- 환경설정의 기본 프로바이더 / 목적별 프로바이더 / 코드 유효시간 / 최대 시도 횟수 4개 항목이 admin UI 저장값을 무시하던 결함 수정. 변경한 환경설정값이 회원가입·비밀번호 재설정 등 모든 본인인증 흐름에 즉시 반영됩니다.\n- 환경설정 > 본인인증 > 목적별 프로바이더에서 일부 본인인증 목적(예: 성인인증)의 프로바이더를 지정해 저장하면 \"설정 저장에 실패했습니다\" 오류로 전혀 저장되지 않던 결함 수정. 이제 모든 본인인증 목적의 프로바이더 매핑이 정상 저장됩니다. 아울러 저장 검증에 실패하면 문제가 된 목적별 프로바이더 입력칸이 빨갛게 강조되고 그 아래에 사유가 표시되어, 어느 항목을 고쳐야 하는지 바로 확인할 수 있습니다.\n- 정책에 프로바이더가 지정되지 않은 경우 환경설정의 기본 프로바이더가 자동 적용되도록 보강. 운영자가 정책마다 프로바이더를 일일이 지정하지 않아도 환경설정 기본값으로 일관되게 동작합니다.\n- 코드 입력 모달의 \"남은 시도 횟수\" 표시가 환경설정값과 무관하게 항상 5회로 굳어 있던 결함 수정. 운영자가 환경설정에서 지정한 한도가 사용자 화면에 정확히 표시됩니다.\n- 인증 코드를 최대 횟수까지 잘못 입력해 시도가 소진되었을 때, 화면에 \"최대 시도 초과 — 다시 요청해주세요\" 안내 대신 일반 오류 문구(\"인증 코드가 올바르지 않습니다\")가 표시되던 결함 수정. 시도가 소진되면 재요청을 안내하는 메시지가 정확히 표시됩니다.\n- 본인인증을 사용하는 모듈·플러그인을 비활성화해도 해당 확장이 설정한 본인인증 요구가 계속 적용되던 결함 수정. 이제 확장을 비활성화하면 그 확장이 선언한 본인인증 정책도 함께 적용 해제되고, 다시 활성화하면 운영자가 조정한 설정을 유지한 채 정상 복구됩니다.\n\n#### 확장 동기화·캐시·레이아웃 주입\n\n- 확장 메뉴 동기화 시점에 운영자가 수정하지 않은 필드 (아이콘·이름·정렬·URL) 가 잘못 \"사용자 수정\" 으로 표시되어 이후 모듈/플러그인 정의값으로의 자동 동기화가 영구히 차단되던 결함 수정. 확장 재설치·업데이트 시 메뉴 정의 변경이 정상 반영됩니다.\n- 확장 역할 동기화 시점에 운영자가 수정하지 않은 필드 (역할 이름·설명) 가 잘못 \"사용자 수정\" 으로 표시되어 이후 모듈/플러그인 정의값으로의 자동 동기화가 영구히 차단되던 결함 수정. 메뉴 동기화와 동일 맥락으로, 확장 재설치·업데이트 시 역할 정의 변경이 정상 반영됩니다.\n- 모듈·플러그인의 레이아웃 확장이 대상이 아닌 템플릿(관리자 확장 ↔ 사용자 템플릿)에도 등록되던 결함 수정. 업그레이드 시 잘못 등록된 항목을 자동 정리하며, 이후로는 대상 레이아웃이 존재하는 템플릿에만 등록됩니다.\n- 모듈·플러그인이 공통 레이아웃(헤더·푸터 등 모든 화면이 공유하는 틀)에 주입한 UI·초기화 동작이 실제 화면에는 전혀 나타나지 않던 결함 수정. 개별 화면이 공통 레이아웃을 물려받을 때 그 \"물려받은 출처\" 정보가 사라져, 공통 레이아웃을 대상으로 한 확장 주입이 어느 화면에도 적용되지 않았습니다. 이제 화면이 물려받은 공통 레이아웃을 대상으로 한 확장도 정상 적용되어, 모듈이 설치되어 있으면 해당 UI(예: 헤더 통화 선택기)와 초기화 동작이 모든 관련 화면에 표시됩니다.\n- 확장(모듈·플러그인) 변경 후 라우트 트리·메뉴·다국어 응답이 옛 값으로 남아 있던 결함 수정. 이전에는 클라이언트가 캐시 버전 신호를 동반하지 않아 백엔드 캐시가 갱신 시점을 놓쳤습니다. 확장 설치/활성화/업데이트가 일어나면 다음 페이지 로드부터 새 값이 즉시 보입니다.\n- 확장을 업데이트하거나 레이아웃을 저장해도 변경 내용이 화면에 반영되지 않고 옛 내용이 계속 보이던 결함 수정. 캐시 갱신 신호가 기록되는 저장소와 읽히는 저장소가 환경설정에 따라 어긋날 수 있어, 올린 갱신 신호가 화면 쪽에서 보이지 않던 문제였습니다. 이제 갱신 신호가 항상 같은 저장소로 일관되게 기록·조회되어 확장 업데이트·레이아웃 변경이 다음 화면 로드부터 즉시 반영됩니다.\n- 코어 업데이트 시 사용자가 모듈·플러그인·템플릿·언어팩의 `_bundled` 폴더 아래에 직접 만들어 둔 커스텀 폴더·파일이 함께 삭제되던 결함 수정. 이제 코어 업데이트는 코어가 제공하는 번들 확장만 갱신하고, 사용자가 추가한 항목은 그대로 보존합니다. (#43 @bigmsg 님께서 제보해주셨습니다.)\n- 관리자 권한(`sudo`)으로 코어를 업데이트한 뒤, 모듈·플러그인·템플릿·언어팩을 업데이트하려 하면 \"백업 생성 중\" 단계에서 `mkdir(): Permission denied` 오류로 실패하던 결함 수정. 관리자 권한 업데이트가 백업 폴더와 업그레이드 기록 파일을 관리자 소유로 남겨, 이후 웹서버 계정이 같은 위치에 쓰지 못하던 문제였습니다. 이제 이들 폴더·파일의 소유권과 그룹 쓰기 권한이 정상화되고, 코어와 확장의 업그레이드 기록을 별도 파일로 분리해 두 작업이 서로의 기록 파일을 두고 충돌하지 않습니다.\n\n#### 알림\n\n- 회원에게 보내는 알림(이메일·사이트 알림)이 보내는 사람(관리자 등)의 화면 언어가 아니라 받는 회원 본인의 언어로 발송되도록 수정. 관리자가 한국어 화면에서 다른 언어를 쓰는 회원의 주문 상태를 바꿔도, 알림은 그 회원의 언어로 전달됩니다.\n- 신규 주문 접수 시 관리자에게 발송되는 알림 메일의 결제금액이 0원으로 표시되던 결함 수정. 결제 예정 금액이 정확히 표시됩니다.\n- 알림 템플릿을 기본값으로 복원할 때 제목·본문만 복원되고 수신자·클릭 주소는 관리자가 수정한 값이 그대로 남던 결함 수정. 이제 복원 시 수신자·클릭 주소까지 기본값으로 정확히 되돌아갑니다.\n\n#### 설치·인스톨러\n\n- 설치 마법사에서 데이터베이스 테이블 접두사를 지나치게 길게 입력하면 일부 확장 설치 시 테이블 인덱스 이름이 데이터베이스 한도를 넘겨 설치가 실패할 수 있던 문제를 예방합니다 — 접두사 입력 길이를 6자로 제한하고, 한도를 넘기면 설치 전 단계에서 안내합니다.\n- 인스톨러에서 \"기존 테이블 삭제 후 설치\"를 선택할 때, 입력한 테이블 접두사와 무관한 다른 테이블까지 모두 삭제되던 결함 수정. 이제 입력한 접두사로 시작하는 테이블만 삭제되어, 같은 데이터베이스를 다른 프로그램과 함께 쓰거나 다른 접두사로 재설치할 때 무관한 테이블이 보존됩니다.\n\n#### 관리자 화면·검색·시스템·보안\n\n- 관리자 대시보드의 최근 활동·모듈/플러그인/템플릿 상태·게시판/이커머스 위젯 등 같은 틀이 여러 번 펼쳐지는 목록에서, 항목마다 고유 식별자를 붙이도록 작성된 화면이 모든 행에 같은 식별자를 그대로 내보내 일부 보조 기능·접근성 도구가 항목을 구분하지 못하고 일부 항목이 화면에서 누락되어 보이던 결함을 수정했습니다 — 이제 반복 목록의 각 행이 고유하게 식별되어 모든 항목이 정상 표시됩니다.\n- 한 화면 안에서 탭이나 필터를 클릭으로 전환할 때, 화면에 따라 표시 조건에 묶인 데이터 목록이 비어 보이던 결함 수정. 새로고침해야 보이던 목록이 이제 탭·필터 전환 즉시 정상 표시됩니다. (표시 조건에 따라 로딩되는 데이터 목록이 전환 시점에 다시 평가되지 않던 문제였습니다.)\n- 환경설정 화면 등에서 별도 표시 텍스트 없이 켜고 끄는 토글·체크박스를 켜서 저장하면 \"참/거짓 값이어야 합니다\" 오류로 저장이 거부되던 결함 수정. 토글의 켜짐/꺼짐 상태가 문자열이 아닌 참/거짓 값으로 정확히 전달되어 정상적으로 저장됩니다.\n- 관리자 목록 화면에서 필터(발급상태·사용여부 등)를 선택하고 검색하면 일부 필터가 검색 직후 \"전체\"로 풀려 보이던 결함 수정. 새로고침해야 선택한 필터가 다시 표시되던 불편을 없애고, 검색 직후에도 선택한 필터가 그대로 유지됩니다.\n- 검색창에 `<`, `>`, `(`, `+` 등 특수문자를 입력하면 오류가 발생하던 결함 수정. 특수문자는 일반 검색어로 처리되어, 어떤 문자를 입력해도 오류 없이 검색 결과(또는 빈 결과)가 표시됩니다.\n- IME(한글·일본어·중국어) 입력 도중 Enter 로 글자를 조합 확정할 때 마지막 글자가 누락되거나 검색·제출이 두 번 실행되던 결함 수정. 이제 조합이 끝난 뒤의 Enter 만 동작합니다. (#54 @devrhee16 님께서 제보해주셨습니다.)\n- 관리자 대시보드의 시스템 리소스(CPU·메모리·디스크) 수집이 일부 호스팅 환경(open_basedir 제한, 일부 시스템 함수 비활성)에서 실패할 때 대시보드 리소스 조회 전체가 오류로 응답하던 결함 수정. 이제 수집할 수 없는 항목만 \"알 수 없음\"으로 표시되고 나머지 화면은 정상 동작합니다. (#40 @glitter-gim 님께서 제보해주셨습니다.)\n- 시스템 정보 화면의 디스크 사용량 조회도 동일한 환경에서 안전하게 \"알 수 없음\"으로 표시되도록 보강했습니다.\n- 웹소켓 사용을 끈 상태에서도 실시간 연결이 일부 동작할 수 있던 문제 수정. 이제 웹소켓 사용을 끄면 실시간 연결·채널 인증이 완전히 비활성화됩니다. (#50 @bigmsg 님께서 제보해주셨습니다.)\n- 사이트 이름을 언어별로 다르게 저장한 환경에서 브라우저 탭 제목과 검색 메타데이터(JSON-LD)가 올바르게 표시되지 않거나 페이지가 깨질 수 있던 문제 수정. 이제 모든 메타 경로가 현재 언어의 사이트 이름을 일관되게 사용합니다. (#49 @glitter-gim 님께서 제보해주셨습니다.)\n- 존재하지 않는 페이지에 접근할 때 정상 페이지처럼 200 응답을 반환하던 문제 수정. 이제 미등록 경로는 올바른 404 응답을 반환해 검색엔진이 없는 페이지를 색인하지 않습니다. (#47 @glitter-gim 님께서 제보해주셨습니다.)\n- 인증이 필요한 관리자 API에 로그인 없이 접근할 때, 브라우저 주소창 직접 입력 등 일부 요청에서 서버 오류로 응답하던 결함 수정. 이제 어떤 방식으로 접근해도 일관되게 \"인증 필요\" 응답이 반환됩니다. (#39 @glitter-gim 님께서 제보해주셨습니다.)\n\n### Security\n\n- 사용되지 않는 파일 서빙 엔드포인트(`/storage` 직접 업로드·다운로드 경로) 노출을 제거했습니다. 실제 파일 업로드·다운로드는 별도의 인증된 경로로 처리되며 영향이 없습니다. (#52 @glitter-gim 님께서 제보해주셨습니다.)\n- 설치가 완료된 시스템에서 설치 상태 관리 엔드포인트(`/install/api/state-management.php`)가 다른 설치 API 와 동일하게 차단되지 않아, 설치 완료 후에도 설치 상태 조회·초기화·중단 요청이 가능하던 문제를 수정했습니다. 설치가 완전히 끝난 환경에서는 해당 요청이 모두 HTTP 410 으로 차단됩니다. 설치 진행 중 완료 화면으로 정상 전환되는 동작은 영향받지 않습니다. (KISA 측에서 제보해주셨습니다 — KVE-2026-1056)\n\n### Removed\n\n- 기본 제공 결제 수단을 KG이니시스로 단일화했습니다. 그 외 결제 연동(나이스페이먼츠·NHN KCP·토스페이먼츠)은 기본 번들에서 제외되며, 필요한 경우 별도 플러그인으로 설치해 사용할 수 있습니다. 이미 해당 결제 연동을 설치해 사용 중인 사이트는 영향받지 않습니다.\n\n## [7.0.0-beta.7] - 2026-05-15\n\n### Fixed\n\n- 인스톨러 설치 완료 후 `.env` 에 DB 자격증명과 `APP_KEY` 가 기록되지 않고 `storage/installer/runtime.php` 에 평문으로 잔존하던 결함을 수정. 운영 동작은 정상이었으나 자격증명이 정상적인 위치로 옮겨지지 않고 임시 파일에 보존되는 상태였습니다. (#37 @glitter-gim 님께서 제보해주셨습니다.)\n- beta.4~beta.6 출시본에서 위 결함으로 인해 자격증명이 잔존한 환경을 beta.7 업그레이드 시 자동으로 감지하여 `.env` 로 머지하고 임시 파일을 정리하도록 업그레이드 스텝을 추가했습니다. `.env` 의 `INSTALLER_COMPLETED` 마커가 이미 설정된 정합 이상 케이스는 자동 정리 대상에서 제외하고 운영자 수동 검토를 안내합니다.\n- 위 업그레이드 스텝이 `.env` 를 머지할 때 권한·소유자·그룹을 웹 서버 기준으로 정렬하도록 보강. 기존 `.env` 가 있던 환경에서는 운영자가 설정한 권한을 그대로 보존하고, 새로 생성되는 환경에서는 웹 서버가 읽을 수 있는 권한과 그룹을 자동으로 적용합니다. 명령줄 업그레이드 실행 사용자와 웹 서버 사용자가 다른 자체 구축 서버 환경에서 머지 직후 웹 서버가 `.env` 를 읽지 못해 사이트가 다운되는 회귀를 차단합니다.\n- 인스톨러 설치 완료 화면의 `.env` 권한 강화 안내(`chmod`/`chgrp` 명령 예시) 가 Windows 환경에서 비노출되도록 OS 가드 추가. POSIX 권한 모델이 없는 Windows 환경에서 무의미한 Linux/macOS 전용 명령이 노출되어 운영자 혼란을 유발하던 문제를 수정합니다.\n- beta.4~6 출시본의 인스톨러 결함으로 자격증명이 `storage/installer/runtime.php` 에 잔존한 환경에서 beta.7 업그레이드 시 번들 확장 일괄 업데이트 단계가 `Access denied for user 'root'@'localhost'` 로 실패하던 회귀 수정. 업그레이드 스텝이 `.env` 머지 직후 부모 프로세스의 환경 변수도 함께 갱신하여 후속 자식 프로세스가 정상 자격증명을 상속받도록 보강합니다. 정상 설치 환경 (beta.3 등) 에서는 트리거되지 않습니다.\n- 위 동일 회귀의 추가 차단 — 코어 업데이트가 별도 프로세스로 업그레이드 스텝을 실행하는 경로에서는 자식 종료 후 부모의 환경 변수가 갱신되지 않아 동일 증상이 재발하던 결함 수정. 자식 프로세스가 부팅 시점에 디스크의 `.env` 와 자신의 환경 변수를 대조하여 손상이 감지되면 디스크 값으로 보정하도록 보강하여, 다음 단계의 확장 일괄 업데이트가 정상 자격증명으로 진행되도록 합니다. 정상 설치 환경에서는 자동 비활성화됩니다.\n\n### Changed\n\n- `INSTALL.md` 에 \"업그레이드\" 섹션을 신설하여 자동 업데이트의 표준 절차, `sudo` 권장 사유 (`.env` · `bootstrap/cache` 의 권한 모델), 사용자군별 단계적 업그레이드 분기, 권한·캐시 트러블슈팅을 한곳에서 안내하도록 보완. 관리자 환경설정 > 정보 > 업데이트 확인 모달이 본 섹션으로 외부 링크를 노출합니다. (#30 @yks118 님께서 건의해주셨습니다.)\n- 관리자 환경설정 > 정보 > 업데이트 확인 모달의 자동 업데이트 명령어 안내를 `sudo php artisan core:update` 권장 형태로 변경. 권한이 일치하지 않는 환경(`.env` 가 웹 서버 사용자 소유) 에서의 실행 실패를 사용자가 사전에 회피할 수 있도록 모달 하단에 \"전체 업그레이드 가이드 보기\" 외부 링크를 추가해 INSTALL.md 의 업그레이드 섹션으로 연결합니다. (#30 @yks118 님께서 제보해주셨습니다.)\n\n### Notes — `.env` 권한 권장안\n\n위 업그레이드 스텝은 `.env` 가 이미 존재하는 환경에서는 권한을 임의로 변경하지 않고 기존 상태를 그대로 보존합니다. 보안 위생 강화를 원하시면 다음 권장안을 참고하세요.\n\n- 가장 안전한 권한은 `0600` (소유자만 읽기·쓰기) 입니다. `.env` 는 DB 비밀번호와 `APP_KEY` 등 평문 자격증명을 포함하므로 외부 접근을 최소화하는 것이 권장됩니다.\n- 단, `0600` 적용 전에 **`.env` 의 소유자가 웹 서버 실행 사용자와 동일해야** 합니다 (예: 단일 사용자 환경의 `chown www-data:www-data .env && chmod 600 .env`). 소유자가 웹 서버 사용자와 다른데 `0600` 으로 변경하면 웹 서버가 `.env` 를 읽지 못해 사이트가 응답하지 않습니다.\n- 명령줄 사용자와 웹 서버 사용자가 다른 환경 (자체 구축 서버 등) 에서는 `0640` 권한 + 그룹을 웹 서버 그룹으로 설정 (`chgrp www-data .env && chmod 640 .env`) 하는 것이 안전한 절충안입니다. 웹 서버는 그룹 멤버로 읽고, 명령줄 사용자는 직접 수정할 수 있습니다.\n\n## [7.0.0-beta.6] - 2026-05-14\n\n### Fixed\n\n- 코어 업데이트 자동 롤백 시 신 버전이 추가한 신규 파일이 활성 디렉토리에 잔존하여 `BindingResolutionException` 등 부정합 부팅 실패가 발생하던 결함 수정. 백업 디렉토리에 신규 파일 manifest 를 기록하고 롤백 시 정확히 그 목록만 정리합니다. 사용자가 코어 영역에 직접 추가한 파일과 모든 symlink (`public/storage` 등) 는 보존됩니다. (#34 @bigmsg 님께서 제보해주셨습니다.)\n- 자동 롤백 후 `bootstrap/cache` 잔존 PHP 캐시로 인한 추가 부팅 실패 차단 — 롤백 직후 캐시를 자동으로 정리합니다.\n- beta.5 이전 자동 롤백으로 인해 활성 디렉토리에 잔존했을 수 있는 ServiceProvider 후보를 beta.6 업그레이드 스텝이 진단 로그로 식별하여 운영자 수동 검토를 안내합니다. 자동 삭제하지 않으므로 사용자가 직접 추가한 ServiceProvider 는 보존됩니다.\n- 업그레이드 스텝 단독 실행 명령(`core:execute-upgrade-steps`) 이 마이그레이션·코어 재동기화·버전 갱신·캐시 정리·번들 확장 일괄 업데이트를 자동으로 함께 수행하도록 보강. 자동 업데이트 중단 후 안내된 수동 명령을 그대로 실행하면 별도 보조 작업 없이 업그레이드가 완료됩니다.\n- 인스톨러 Step 3 (PHP CLI / Composer / 자동 감지 / 코어 _pending 경로 검증) 의 프론트엔드 catch 분기에서 사용하는 다국어 키가 언어 파일에 정의되어 있지 않아, 서버 응답 실패 시 화면에 다국어 키 문자열이 그대로 노출되던 결함 수정.\n- 인스톨러 다국어 파일에 중복 정의된 키 4건 정리. POST 전용 API 의 메서드 거부 메시지가 일반 문구로 덮여 표시되던 문제, 데이터베이스 연결 실패 로그에서 placeholder 가 치환되지 않던 문제, HTTPS 카드 라벨이 안내 문장으로 표시되던 문제가 함께 해결됩니다.\n- 인스톨러 Step 3 의 PHP CLI 및 Composer 절대경로 검증이 공유 호스팅 환경(시놀로지 DSM 등 `open_basedir` 가 시스템 binary 영역을 차단하는 환경)과 Windows 환경에서 정상 경로임에도 거부되던 결함 수정. 셸 인자 escape 와 메타문자 차단은 유지하고, 파일 시스템 권한에 의존하던 사전 검사를 실제 실행 결과로 판정하도록 변경합니다. Windows 의 백슬래시 경로 구분자도 정상 입력으로 인식됩니다 (단, `C:\\Program Files\\...` 같은 공백 포함 디렉토리는 공백 없는 경로 또는 8.3 short path 사용 권장). (#33 @glitter-gim 님께서 제보해주셨습니다.)\n- 코어 운영 단계의 composer 바이너리 자동 인식이 같은 `open_basedir` 환경에서 실패하여 모듈/플러그인 설치 시 vendor-bundle 모드만 사용 가능하던 결함 수정. composer 가 정상 설치되어 PATH 에 존재하는 환경에서도 인식되도록 검출 로직을 개선했습니다.\n- 멀티 PHP 버전 환경(시놀로지 DSM Web Station, cPanel/Plesk multi-PHP 등) 에서 Composer 를 특정 PHP 인터프리터로 실행하려는 \"PHP 절대경로 + Composer 절대경로\" 공백 분리 입력 형식이 거부되던 결함 수정. 입력을 두 토큰으로 분리한 뒤 각 토큰을 개별 escape 처리하여 안전을 유지하면서 멀티 PHP 운영 시나리오를 지원합니다.\n- 인스톨러 세션 쿠키가 일부 환경(비표준 포트 + dynamic DNS 도메인 + 브라우저 추적 보호 등) 에서 브라우저에 의해 차단되어 첫 화면 \"설치하기\" 클릭이 동작하지 않고 새로고침처럼 보이던 결함을 보완. 세션 쿠키 SameSite 정책을 Strict → Lax 로 완화하여 차단 케이스 자체를 줄이고, Step 0 진입 시 쿠키 round-trip 사전 진단을 수행하여 차단이 감지되면 사용자에게 회피 방법(다른 브라우저 / 쿠키 차단 해제 / IP·localhost 접속) 을 명시 안내합니다.\n\n### Added\n\n- `hotfix:rollback-stale-files` Artisan 커맨드 추가 — 자동 롤백 후 활성 디렉토리에 잔존할 수 있는 신 파일을 운영자가 명시 실행하여 진단/정리할 수 있는 단발성 회복 도구. 기본은 진단 모드 (실제 삭제 없음), `--prune` 옵션 시 확인 프롬프트 후 정리. symlink 와 보호 경로(`storage/`, `vendor/`, `.env*` 등) 는 자동 제외.\n- 본인인증 메시지 정의 목록 API 응답에 `can_create` 권한 키 추가 — 운영자의 수정 권한 보유 여부에 따라 관리자 화면이 \"정의 추가\" 버튼을 정확히 활성화/비활성화할 수 있도록 보강.\n\n## [7.0.0-beta.5] - 2026-05-12\n\n### Upgrade Notice\n\nbeta.4 → beta.5 업그레이드 시 beta.4 의 결함으로 인해 활성 확장 디렉토리 일부가 손실됩니다. (#34 @bigmsg 님께서 제보해주셨습니다.)\n\n- 손실 항목 1: 운영자가 활성 디렉토리(`modules/{id}`, `plugins/{id}`, `templates/{id}`, `lang-packs/{id}`)에서 직접 수정한 코드\n- 손실 항목 2: 외부 install 한 확장 (GitHub 등 `_bundled` 에 포함되지 않은 모듈/플러그인/템플릿/언어팩)\n\nbeta.5 의 자동 복구 스텝이 `_bundled` 기반으로 번들 확장은 복원하지만, 위 두 항목은 `_bundled` 에 없어 자동 복구가 불가능합니다.\n\n업그레이드 전에 다음 디렉토리 전체를 별도 위치(외장 디스크, 별도 git 브랜치, tar.gz 파일)로 백업해두실 것을 권고합니다.\n\n- `modules/`\n- `plugins/`\n- `templates/`\n- `lang-packs/`\n\n업그레이드 후 자동 복구된 활성 디렉토리에 본인의 수정 사항/외부 확장이 누락되어 있다면, 사전 백업에서 복원하거나 외부 확장은 원본 소스(GitHub 등)에서 재설치하세요. 실패한 외부 확장 식별자는 업그레이드 로그(`storage/logs/core_update_success_*.log`)에 명시됩니다.\n\n#### 3가지 사용자군별 업그레이드 절차\n\n- **사용자군 B — 정상 beta.4 사용자 (대다수)**: `php artisan core:update` 표준 절차. beta.5 의 자동 복구 스텝이 활성 확장 디렉토리 + lang-packs 정합성을 보장합니다.\n\n- **사용자군 A — beta.3 → beta.4 업그레이드 도중 `Call to undefined method` 류 fatal 후 stuck 한 사용자**: `.env` `APP_VERSION` 을 변경하지 마시고 그대로 `php artisan core:update --force` 를 재실행하면 됩니다. 디스크는 이미 beta.4 이므로 새 자식 프로세스가 beta.5 메모리로 부팅되어 남은 업그레이드 스텝이 멱등 적용됩니다. 단, 활성 디렉토리에서 직접 수정한 코드 또는 `_bundled` 에 없는 외부 확장은 자동 복구 대상이 아니므로 업그레이드 전 위 디렉토리 백업이 동일하게 권장됩니다.\n\n- **사용자군 C — beta.1 또는 beta.2 사용자**: `php artisan core:update` 1회 호출로 beta.5 까지 직접 업그레이드는 권장하지 않습니다. 중간 버전의 업그레이드 스텝이 이전 버전 메모리에 없는 신규 코드에 의존하여 fatal 위험이 있습니다. 다음 단계적 업그레이드를 권장합니다:\n\n ```text\n beta.1 사용자: beta.1 → beta.2 → beta.3 → beta.4 → beta.5 (4 회)\n beta.2 사용자: beta.2 → beta.3 → beta.4 → beta.5 (3 회)\n ```\n\n 각 단계마다 `.env` `APP_VERSION` 이 자동 갱신되므로 다음 호출은 그대로 `php artisan core:update` 실행하면 됩니다. 가까운 릴리즈 zip 으로 `--source=` 또는 `--zip=` 옵션을 사용해 단계별 진행 가능합니다.\n\n#### 자동 롤백 후 `public/storage` 회복\n\n업그레이드 도중 자동 롤백 후 화면에서 업로드 파일이 보이지 않거나 미디어 경로가 깨졌다면 `public/storage` symlink 가 일반 디렉토리로 변질된 상태일 수 있습니다.\n\n**우선 시도 — beta.5 의 자동 복구**: beta.4 → beta.5 업그레이드 자체는 부모 beta.4 메모리 결함으로 symlink 가 깨질 수 있으나, beta.5 의 업그레이드 스텝이 다음 패턴을 자동 감지하여 복구합니다:\n\n- 감지 조건: `public/storage` 가 일반 디렉토리 + `storage/app/public` 이 존재 (Laravel 표준 구성)\n- 복구 동작: 손상된 디렉토리를 `public/storage.broken.YYYYMMDDHHMMSS` 로 보존 후 symlink 재생성\n- 운영자 조치: 자동 복구 후 `.broken` 디렉토리의 콘텐츠를 검증한 뒤 수동 삭제 (false positive 방어를 위해 자동 삭제하지 않음)\n\n**자동 복구가 작동하지 않은 경우 매뉴얼 회복**:\n\n```bash\nrm -rf public/storage\nphp artisan storage:link\n```\n\nbeta.5 부터 백업/복원 과정에서도 symlink 가 보존되므로 beta.5 → beta.6 이후로는 본 결함이 자동 차단됩니다. Windows 환경에서는 PHP `symlink()` 가 `SeCreateSymbolicLink` 권한을 요구하므로 자동 복구가 silent skip 될 수 있어 매뉴얼 회복 절차가 여전히 필요합니다.\n\n#### spawn 자식 프로세스 실패 시 동작 모드\n\nbeta.5 부터 코어 업데이트의 자식 프로세스 spawn 이 실패하면 즉시 abort 가 기본 동작입니다 (`G7_UPDATE_SPAWN_FAILURE_MODE=abort`). 다음 4가지 상황에서 부모 프로세스 메모리의 stale 클래스로 인한 fatal 위험을 차단합니다:\n\n1. `proc_open` 비활성 (일부 공유 호스팅)\n2. `proc_open` 자원 생성 실패\n3. 자식 비정상 종료 (uncaught exception)\n4. 자식 exit=0 이지만 업그레이드 스텝 0건 실행 (silent skip)\n\nabort 발생 시 운영자에게 수동 재개 명령 (`php artisan core:execute-upgrade-steps --from=... --to=...`) 을 안내합니다. `proc_open` 비활성 환경 등 호환성이 필요한 경우 `.env` 에 `G7_UPDATE_SPAWN_FAILURE_MODE=fallback` 을 설정하여 in-process fallback 으로 진행할 수 있으나, 부모 메모리 stale 로 인한 fatal 위험이 잔존합니다.\n\n### Added\n\n- 본인인증 관리자 권한을 조회/수정으로 분리 — \"프로바이더 설정 조회\" / \"정책 조회\" 권한 신설로 단순 조회 권한만 부여받은 운영자도 환경설정 본인인증 화면에 접근 가능 (기존에는 수정 권한이 없으면 조회 화면도 403)\n- 환경설정의 본인인증 정책/메시지 탭에 권한 기반 버튼 비활성화 적용 — 수정 권한이 없는 운영자에게는 \"정책 추가\" / \"수정\" / \"삭제\" / \"활성 토글\" 버튼이 자동으로 비활성화 (이커머스/게시판 본인인증 탭 동일)\n- 코어 업데이트의 spawn 자식 프로세스 실패 시 abort/fallback 동작 모드 선택 (`G7_UPDATE_SPAWN_FAILURE_MODE`, 기본 `abort`) — 부모 메모리 stale 로 인한 upgrade step fatal 위험을 fail-fast 로 차단 (#28 @bigmsg 님께서 제보해주셨습니다.)\n- 업그레이드 스텝에 \"버전별 데이터 스냅샷\" 규약 도입 — 멀티 버전 점프 시에도 사용자가 단계별로 업그레이드한 것과 동등한 결과를 보장하도록 각 스텝의 시드 카탈로그·적용 로직·단발성 핫픽스를 해당 버전 디렉토리로 격리. 외부 확장 작성자가 beta.5+ 신규 업그레이드 스텝 작성 시 적용 (상세: 업그레이드 스텝 가이드) (#29 @bigmsg 님께서 건의해주셨습니다.)\n- 언어팩 일괄 활성화 검증 규칙에 동적 확장 훅 추가 (`core.language_packs.bulk_activate_validation_rules`) — 모듈/플러그인이 호스트 확장 재활성화 cascade 흐름에서 추가 검증 필드를 동적으로 등록 가능\n\n### Changed\n\n#### Breaking\n\n- 본인인증 관리자 권한 키 정리 — 기존 `core.admin.identity.manage` / `core.admin.identity.policies.manage` 가 각각 `core.admin.identity.providers.update` / `core.admin.identity.policies.update` 로 변경. beta.4 환경에서 기존 권한을 부여받은 운영자 역할은 업그레이드 시 자동으로 새 권한 키로 마이그레이션 (부여 시점/부여자/스코프 메타데이터 모두 보존). 외부 확장 또는 운영 도구가 옛 키 문자열을 하드코딩한 경우 새 키로 갱신 필요\n\n### Fixed\n\n- 코어 업데이트 시 활성 모듈/플러그인/템플릿/언어팩 디렉토리가 삭제되던 문제 수정 — beta.4 → beta.5 업그레이드 시 번들 원본에서 활성 디렉토리를 자동 복원하고, 외부 설치 확장은 업데이트 로그로 재설치 안내\n- 코어 업데이트의 단계 전환 신호를 비정상 입력으로부터 차단하도록 검증 강화\n- 본인인증 정책 응답의 생성/수정 일시가 사용자 타임존이 아닌 UTC ISO 문자열로 노출되던 문제 수정\n- 백업/롤백 시 `public/storage` 등 symlink 가 target 디렉토리 내용으로 추적 복사되어 symlink 가 일반 디렉토리로 변질되던 문제 수정 (Linux 환경 한정 — Windows 는 권한 한계로 일반 디렉토리 폴백 + 수동 회복 안내 유지)\n- root/super user 환경에서 Composer 검증·설치가 비대화형 컨텍스트에서 실패하던 문제 수정 (코어 업데이트, 확장 의존성 설치, 인스톨러 포함) (#31 @glitter-gim 님께서 제보해주셨습니다.)\n- 본인인증 활동 로그의 \"액션\" 열에 `identity.verify` / `identity.verify_failed` 가 번역되지 않은 키 그대로 노출되던 문제 수정 — 코어 액션 라벨 매핑이 누락되어 있던 부분을 추가\n- 본인인증 단계에서 운영자가 지정한 본인인증 수단이 무시되고 항상 기본 수단(메일)으로 발행되던 문제 수정 — 가입 정책에 설정한 본인인증 수단과 API 요청에서 명시한 수단이 실제 인증 발행에 반영되도록 변경\n\n## [7.0.0-beta.4] - 2026-05-11\n\n### Changed\n\n#### Breaking\n\n- 레이아웃 `navigate` 액션의 기본 스크롤 동작이 페이지 이동 후 최상단 이동으로 변경됨 — 일반 하이퍼링크 이동 UX 와 일치. 이전처럼 스크롤 위치를 유지하려면 `scroll: \"preserve\"` 명시 필요. 검색 필터/페이지네이션 등 위치 유지가 필요한 화면에서 회귀가 의심되면 해당 옵션 추가 (engine-v1.45.0)\n\n#### 언어팩 매니페스트 정합화\n\n- 언어팩 매니페스트(`language-pack.json`) 를 모듈/플러그인/템플릿 매니페스트와 동일한 필드 구조로 정렬 — 외부 작성자가 다른 확장과 동일한 표준으로 언어팩을 만들 수 있도록 개선\n- 언어팩 매니페스트에 GitHub 저장소 필드 추가로 GitHub 기반 업데이트 경로 지원 (모듈/플러그인 매니페스트와 동일한 동작)\n- 관리자 환경설정의 언어팩 카드와 상세 모달이 모듈/플러그인/템플릿 카드와 동일한 형식으로 다국어 이름 · 설명 · GitHub 링크를 노출하도록 변경\n\n### Added\n\n#### Generator 메타 태그\n\n- 관리자 환경설정 → SEO 탭에 Generator 메타 태그 카드 추가 — 토글로 노출 여부를 제어하고 내용 입력으로 W3Techs 등 CMS 시장 점유율 측정 도구가 인식하는 `` 태그를 SEO 봇 페이지·SPA·관리자 셸 모두에 출력. 내용 미입력 시 \"GnuBoard7 {버전}\" 자동 적용, 운영자가 버전 노출을 원치 않으면 \"GnuBoard7\" 만 입력 가능 (#26 @Lastorder-DC 님께서 건의해주셨습니다.)\n\n#### 웹 인스톨러 — 번들 언어팩 동반 선택 · 설치\n\n- 인스톨러 4단계 확장 선택 화면에 \"언어팩\" 카드 신설 — 번들 언어팩을 locale 별 서브헤딩으로 노출하고, 사용자가 모듈/플러그인/템플릿을 선택하면 그 확장에 종속된 번들 언어팩 카드가 즉시 활성화. 종속 확장을 해제하면 그 언어팩 카드는 자동으로 비활성화 + 선택 해제\n- 5단계 설치 진행 시 모든 확장의 install/activate 가 끝난 뒤 선택된 번들 언어팩을 일괄 설치 — 1건 실패는 best-effort 처리로 전체 설치를 중단하지 않음\n- 코어/확장당 다수 locale(일본어 + 중국어 등)이 동시에 번들된 시나리오 대응 — 각 locale 은 독립된 서브헤딩 + 카드로 노출\n\n#### 인스톨러 SSE 호환성 자동 감지\n\n- 인스톨러 시작 시 SSE 호환성을 사전 점검하여 환경에 맞는 모드(SSE 또는 폴링)로 단방향 진입 — 워커 동시 실행 race(테이블 / unique 키 중복 에러) 차단\n- SSE 비호환 환경 사용자에게 폴링 모드 진행 여부를 명시적 다이얼로그로 확인 후 시작\n\n#### ActivityLog 다국어 영역 분리\n\n- 활동 로그의 액션 라벨이 모듈/플러그인 자체 다국어 파일에서 우선 해석되도록 변경 — 그동안 코어에 일괄 등록되어야 했던 모듈 origin 라벨(이커머스 주문·상품·마일리지, 게시판 게시물·댓글·신고, 페이지 등) 이 각 영역으로 이전되어 모듈/플러그인이 자기 도메인 라벨을 자기 영역에서 자기설명. 미정의 시 코어 라벨로 자동 fallback 하여 회귀 없음\n- 모듈/플러그인이 발화하는 활동 로그가 호출자/대상 모델의 영역을 자동으로 인식하여 자기 다국어 파일로 라우팅 — 새 활동 로그를 추가할 때 별도 분기 코드 없이 자기 lang 만 채우면 정합\n\n#### 다국어 시더 인프라\n\n- 확장 entity 시더가 활성 언어팩의 다국어 데이터를 자동 머지하도록 다국어 시더 인터페이스 도입 — 신규 시더 작성 시 회귀를 자동 차단\n- 번들 일본어 언어팩 빌드 스크립트가 시더 메타데이터를 일관된 경로로 조회하도록 단순화\n\n#### 다국어 라벨 helper + Provider/Registry 페이로드 정합화\n\n- 다국어 라벨 표시 시 활성 언어팩의 lang key fallback 을 자동으로 처리하는 다국어 라벨 보강 helper 추가 — Provider/Registry 등록 페이로드(알림 채널·결제 PG 등) 와 settings JSON 다국어 데이터를 단일 시그니처로 처리\n- 알림 채널(`config/notification.php` 의 `default_channels`) 의 라벨이 활성 언어팩(일본어 등) 으로 자동 보강되도록 변경 — 일본어 활성 시 한국어 fallback 으로 노출되던 회귀 차단\n- 토스페이먼츠 PG 프로바이더 등록 페이로드가 lang key 기반으로 라벨을 선언하도록 변경 — 활성 언어팩으로 자동 보강\n- Provider/Registry 등록 페이로드가 다국어 JSON(`['ko' => ..., 'en' => ...]`) 을 직접 보유하지 않도록 audit 룰 신설 (회귀 자동 차단)\n- settings JSON 다국어 entry 의 식별 키(code/id/key) 보유 검증 audit 룰 신설 (lang pack fallback 키 조립 가능성 보장)\n- 프론트엔드 `$localized()` 표현식이 두 번째 인수로 lang key fallback 을 받도록 확장 — 활성 로케일 라벨 부재 시 모듈/플러그인 언어팩의 키로 자동 보강\n- 이커머스 환경설정 카탈로그(배송 가능 국가 / 통화 / 결제수단) 표시 시 활성 언어팩의 라벨이 자동 적용되도록 변경 — 일본어 활성 시 카탈로그 라벨이 한국어로 노출되던 회귀 차단 (다음 일본어 언어팩 빌드 시 자동 적용)\n- audit 룰이 `getDefault*Channels/Providers/Methods` 등 registry 기본값 반환 메서드의 다국어 JSON 직접 보유도 감지하도록 강화\n\n#### Settings 카탈로그 다국어 자동 보강\n\n- 환경설정 카탈로그(결제수단·통화·배송 가능 국가 등) 의 다국어 라벨이 카탈로그 빌드 시점에 활성 언어팩으로 자동 보강되도록 변경 — 사용자 체크아웃 + 관리자 환경설정 양쪽 모두 일본어 등 활성 시 한국어 fallback 으로 노출되던 회귀 차단\n- 모듈/플러그인 개발자가 카탈로그를 추가할 때 누락하지 않도록 audit 룰이 helper 호출 누락 검출\n- [docs/backend/settings-multilingual-enrichment.md](docs/backend/settings-multilingual-enrichment.md) 에 단순 helper 사용 패턴 문서화\n\n#### 확장 시스템\n\n- manifest `hidden: true` 플래그 추가 — 관리자 UI 목록에서 학습용/내부용 확장을 숨김 (CLI 는 정상 노출). 관리자 UI 에 슈퍼관리자 전용 \"숨김 포함\" 토글, artisan `module:list` / `plugin:list` / `template:list` 에 `--hidden` 플래그, 관리자 API `/api/admin/{modules,plugins,templates}` 에 `include_hidden` 쿼리 파라미터 지원\n- 학습용 최소 샘플 확장 4종 번들 추가\n - 모듈: `gnuboard7-hello_module` (Memo CRUD + 훅 발행 시연)\n - 플러그인: `gnuboard7-hello_plugin` (Action/Filter 훅 구독 시연)\n - Admin 템플릿: `gnuboard7-hello_admin_template` (Basic 컴포넌트 최소 셋)\n - User 템플릿: `gnuboard7-hello_user_template` (홈 + Memo 리스트 연동)\n- 모듈/플러그인/템플릿 정보 모달에 \"지원 언어\" 섹션 추가 — 코어/번들/사용자설치 출처 배지로 한눈에 확인\n- 모듈/플러그인/템플릿 인스톨러에 의존 확장 + 동반 번들 언어팩 동반선택 UI 추가 — 미선택 의존성에 종속된 언어팩은 자동 비활성화\n- 인스톨러 요구사항 검증 단계에 언어팩 디렉토리 쓰기 권한 점검 + 권한 부여 안내 추가\n- 사용자 수동 비활성화와 코어 버전 호환성으로 인한 자동 비활성화를 DB 수준에서 구분 — 자동 비활성화된 확장만 재호환 감지/원클릭 복구 대상이 되도록 분리 (#18 @laelbe 님께서 제보해주셨습니다.)\n- 코어 업그레이드 후 자동 비활성화 확장이 다시 호환되면 관리자 대시보드에 \"다시 활성화\" 알림 표시 + 원클릭 복구 버튼 제공 (자동 재활성화는 하지 않음 — 운영자가 명시적으로 복구)\n- 모듈/플러그인/템플릿 목록 화면 상단에 자동 비활성화 확장 안내 배너 추가 (코어 업그레이드 가이드 링크 동반)\n- 업데이트 모달에 코어 버전 호환성 안내 + \"위험을 이해하고 강제로 진행\" 체크박스 추가 — 운영자가 위험을 인지하면 비호환 확장도 강제 설치 가능\n- 관리자 대시보드 \"시스템 알림\" 카드 노출 — 코어 호환성 자동 비활성화/재호환 알림이 분기 렌더되며 개별 dismiss 지원\n\n#### 코어 업데이트 가시성\n\n- 코어 업데이트 마무리 단계에서 sudo 환경 결함(파일시스템 ACL · immutable 비트 · NFS 권한 거부 등) 으로 일부 경로의 소유권/그룹 쓰기 권한을 정상화하지 못한 경우 즉시 콘솔에 실패 경로 + 운영자 수동 복구 명령(`sudo chown -R …` / `sudo chmod -R g+w …`) 안내 — 이전에는 silent fail 로 묻혀 운영자가 후속 권한 거부 발생 후에야 인지하던 문제 해소\n\n#### 알림 시스템\n\n- 권한 기반 수신자 타입 추가 — `permission` 타입으로 특정 권한을 가진 모든 사용자에게 알림 발송 가능 (예: 게시판 신고 알림은 신고 관리 권한자에게 자동 발송)\n- 모듈 환경설정에서 알림이 비활성화된 경우 발송 자체를 사전 차단하는 정책 게이트 도입 — 이전에는 수신자 해석 단계가 정책을 우회하여 발송되던 문제 해소\n- 게시판 환경설정 → 신고 정책 탭에 신고 알림 채널 선택 UI 추가 (이메일 / 사이트 알림 다중 선택)\n- 모듈/플러그인이 자기가 발송하는 알림 정의를 manifest 에서 직접 선언하도록 통일 — 활성화/업데이트 시 운영자 편집값을 보존하면서 자동 등록되고, 제거 시 함께 정리됨. 권한·메뉴·본인인증과 동일한 declarative getter 패턴\n- 코어 기본 알림(회원가입 환영·비밀번호 재설정·비밀번호 변경) 의 다국어 제목/본문/변수/채널 정의를 `config/core.php` 로 통합 — 운영자가 코드 수정 없이도 향후 표면 변경을 추적할 수 있는 단일 소스 확보\n\n#### 본인인증\n\n- 본인인증(IdentityVerification) 인프라 도입 — 코어에 범용 IDV 프로바이더 계약을 마련하고, 기본 메일 프로바이더를 내장. 플러그인이 KCP·이니시스·SMS 등 다른 경로를 동일 계약으로 붙일 수 있도록 확장점 제공\n- 외부 본인인증 provider (KCP·PortOne·토스인증·Stripe Identity 등) 가 G7 표준 Extension Point 패턴으로 자기 SDK UI 를 주입할 수 있는 슬롯 도입\n- 비동기 검증 흐름을 위한 백엔드 폴링/콜백 엔드포인트 추가 — `GET /api/identity/challenges/{id}` (상태 폴링), `POST /api/identity/callback/{providerId}` (외부 redirect 콜백 수신)\n- 본인인증 정책 시스템 신설 — 회원가입·비밀번호 재설정·민감 작업에 적용되는 모든 본인인증 시점을 라우트/훅 단위로 선언형 정책으로 통합 관리. 관리자 화면에서 정책 활성/유예 시간/프로바이더/실패 모드/단계(가입 제출 전 vs 가입 후 활성화 전)·적용 대상(self/admin/both)을 조정할 수 있으며, 운영자 수정값은 업데이트 재시딩 시 보존됨\n- 본인인증 정책의 enable 토글이 라우트 코드 수정 없이 즉시 적용 — 모든 API 라우트가 정책 DB 와 자동 매칭되어 운영자가 admin UI 에서 정책을 켜는 즉시 본인인증이 강제됨. 코어/모듈/플러그인 어떤 라우트의 응답 처리 패턴에서도 본인인증 흐름이 일관되게 모달까지 도달하도록 처리\n- 회원가입 단계 정책 2종 시드 — 가입 제출 전 동기 검증, 가입 후 활성화 전 비동기 challenge (기본값 비활성, 운영자 opt-in)\n- 본인인증 정책이 활성화된 모든 강제 지점(회원가입·비밀번호 재설정·민감 작업·게시판/이커머스 정책 등)에서 사용자/관리자 화면에 동일한 모달 UX 가 자동으로 표시되도록 코어 인터셉터와 공통 모달 인프라 도입\n- 전역 프론트엔드 인터셉트 — 서버가 HTTP 428 본인인증 요구 응답을 반환하면 자동으로 인증 모달을 띄우고 사용자가 인증에 성공하면 원 요청을 자동 재실행\n- 본인인증 가드가 모듈/플러그인이 선언한 훅에도 자동 적용되도록 동적 구독 도입 — 결제 직전·민감 액션 직전 등 도메인 특화 가드를 운영자 토글 한 번으로 활성화\n- 관리자 화면에 본인인증 정책 관리 페이지 추가 (정책 목록 DataGrid + 편집)\n- 관리자 화면에 본인인증 이력 페이지 추가 — 알림 발송 이력과 동일한 수준의 UI 제공: 인증 수단(Provider) 탭(활성화된 프로바이더에 따라 자동 갱신), 통합 검색(자동 감지/사용자 ID/대상 식별자/IP/정책 키), 상태·인증 목적·채널·발생 유형 다중선택 OR 필터, 날짜 범위 + 고급 검색(발생 유형·출처·정책 키) 토글 영역, 정렬·페이지 사이즈 선택, 행 펼침으로 인라인 상세 표시, 모바일 반응형, 사용자 타임존 기준 시각, 보관주기(180일) 일괄 파기, 인증 수단/발생 위치 다국어 라벨 표시\n- 본인인증 목적(purpose) 의 출처(source) 추적 — 코어/모듈/플러그인이 선언한 목적을 구분해 환경설정 화면에서 분리 표시하며, 코어 정책 화면 상단에 \"코어가 제공하는 본인인증 목적\" 칩 섹션(목적 코드/라벨 + 설명·허용 채널 툴팁) 추가\n- 모듈 환경설정의 본인인증 정책 탭을 코어 정책 관리 화면과 동일한 UI/UX 로 통일 — 검색·필터(강제 시점/출처)·페이지네이션·정책 추가/편집 모달 직접 호출(코어 화면으로 이동 X)·이 모듈이 등록한 본인인증 목적 칩 섹션(없으면 안내 메시지). 정책 목록의 \"인증 목적\" 컬럼이 코드명 대신 사람이 읽을 수 있는 라벨로 표시\n- 모듈/플러그인이 자기 컨텍스트의 본인인증 정책·목적·메시지 정의를 manifest 에서 직접 선언하도록 통일 — `module.php::getIdentityPolicies()` / `getIdentityPurposes()` / `getIdentityMessages()` 선언만으로 코어 정책 관리에 자동 연동되며, 활성화/업데이트 시 운영자 편집값을 보존하면서 자동 등록·정리됨. 권한·메뉴·알림과 동일한 declarative getter 패턴\n- 본인인증 정책/이력 목록 API 가 source 컨텍스트별 필터를 받아 모듈 환경설정 탭이 자기 정책/이력만 조회 가능하며, 운영자 정의 정책을 특정 모듈/플러그인 컨텍스트에 귀속시켜 추가할 수 있도록 source_identifier 입력 허용 (`admin` / `module:{id}` / `plugin:{id}`)\n- 본인인증 메일 메시지 템플릿 시스템 도입 — 프로바이더와 (목적/정책)별로 다국어 제목/본문을 개별 정의 가능하며, 메시지 발송 시 정책 → 목적 → 프로바이더 기본값 순서로 fallback 해석. 회원가입/계정 변경/중요 작업/비밀번호 재설정 + 프로바이더 기본값 등 5종 메일 템플릿이 한국어/영어로 시드되어 즉시 발송 가능\n- 본인인증 challenge 발급/검증/취소 라우트에 권한 미들웨어 + scope=self 가드 적용 — 게스트는 `core.identity.{request,verify,cancel}` 권한으로 비로그인 가입(Mode B) 흐름 진입, 로그인 사용자는 본인 challenge 만 다룰 수 있으며 관리자는 임의 challenge 도 처리 가능. 모달 취소 시 서버 cancel API 를 호출해 challenge 가 audit log 에 cancelled 상태로 즉시 기록되도록 정합화\n- 환경설정 → 본인인증 탭에 \"메시지 템플릿\" 서브탭 신설 — 알림 템플릿 관리와 동일한 UX (채널 서브탭·페이지당 항목 수 셀렉터·카드 펼침 본문 미리보기·활성/기본 배지·페이지네이션) 로 정의 목록·활성 토글·다국어 편집(변수 가이드 + 기본값 복원) 제공. 운영자가 추가한 정책에 전용 메일 메시지 정의를 화면에서 직접 등록·삭제 가능 (시드 기본값 보호 + 정책 키 매칭 검증). 외부 본인인증 프로바이더 플러그인이 자기 메시지 기본값을 코어 복원 로직에 기여할 수 있는 필터 훅 노출\n- 알림 발송 이력 / 본인인증 이력 샘플 시더를 코어/모듈별로 분리 — 코어 시더는 코어 정의·정책만, 각 모듈 시더는 자기 영역만 채우도록 영역 격리. 모듈은 `module:seed {id} --sample` 으로 자기 영역 이력만 독립 생성 가능. 샘플 데이터는 실제 등록된 사용자·정의·정책·프로바이더 기반으로 생성되어 운영 데이터와 동일한 스키마/분포 유지\n- 모듈/플러그인 제거 시 코어 공유 테이블에 적재된 해당 확장의 데이터(권한·관리자 메뉴·알림 정의·본인인증 정책·본인인증 메시지 정의·본인인증 목적) 가 함께 정리되며, 제거 모달의 \"삭제될 데이터\" 에도 항목별 건수가 표시되도록 개선\n- IDV 도메인 분류 데이터(목적·채널·트리거 출처·정책 범위·정책 실패 모드·정책 적용 대상·정책 출처·메시지 스코프) 8종을 PHP Backed Enum 으로 정의하여 정책 등록/수정 시 일관된 검증 적용\n\n#### 인스톨러\n\n- 인스톨러 설치 완료 화면에 설치된 코어 버전 표시 — 사용자가 어떤 버전이 설치되었는지 즉시 확인할 수 있도록 개선\n\n#### 언어팩 시스템\n\n- 새 언어(일본어/중국어 등)를 코어 수정 없이 추가할 수 있는 언어팩(Language Pack) 시스템 도입\n- ZIP 업로드 또는 GitHub URL로 언어팩 설치/제거/활성화 지원\n- 동일 언어 슬롯에 여러 벤더의 언어팩 공존 가능 — 라디오 전환으로 즉시 활성 변경\n- 코어/모듈/플러그인/템플릿 별도 적용 — 모듈 언어팩은 해당 코어 언어팩이 활성일 때만 활성화 가능\n- 관리자 메뉴: 환경설정 > 언어팩 관리(통합) + 모듈/플러그인/템플릿별 진입점 추가\n- 사용자가 직접 수정한 다국어 키는 언어팩이 덮어쓰지 않도록 보존 정책 적용\n- 보안: 언어 번역 외의 PHP 실행 코드 포함 시 설치 차단\n- 언어팩 관리 화면을 모듈 관리와 동일 수준의 운영 도구로 보강 — 검색(식별자/벤더/언어), 업데이트 확인, 캐시 갱신, 다중 선택 일괄 제거 지원\n- 활성/비활성 상태를 토글 스위치로 일원화 (보호된 팩은 비활성화된 토글로 표시)\n- 업데이트 가능 항목에 \"업데이트 가능\" 배지와 행 단위 업데이트 실행 버튼 노출\n- 정보 모달을 모듈 정보 모달과 동일한 4섹션 구조(기본정보 / 호환성 / 소스 정보 / 변경로그)로 제공하며, CHANGELOG.md 를 자동 파싱하여 버전별 카테고리로 표시\n- 설치 모달에서 ZIP 업로드 전에 manifest 와 검증 결과를 사전 확인할 수 있는 미리보기 제공\n- 모듈/플러그인/템플릿별 언어팩 페이지 진입 시 대상 확장 안내 배너와 환경설정 탭으로 회귀하는 링크 노출\n- 언어팩 업데이트 권한을 별도 권한 키로 분리하여 설치/관리 권한과 독립적으로 부여 가능\n- 언어팩 운영 풀 패리티 — 모듈/플러그인/템플릿 관리와 동일한 안전 장치/확장성 적용\n- 공식 일본어(ja) 번들 언어팩 12종 추가 — 코어, 주요 모듈(전자상거래/게시판/페이지), 주요 플러그인(CKEditor5/마케팅/토스페이먼츠), 기본 템플릿(admin/user)을 일본어로 즉시 사용 가능\n- 언어팩 관리 화면에서 번들 언어팩을 모듈/플러그인 관리와 동일하게 \"미설치\" 상태로 노출 — 행별 \"설치\" 버튼으로 즉시 설치 가능\n- 언어팩 목록 필터에 \"미설치 (번들)\" 상태 옵션 추가\n- 본인인증 메일 메시지 정의의 다국어 키를 언어팩으로 주입할 수 있도록 확장 — 코어 수정 없이 언어팩 ZIP 만으로 IDV 메일 본문/제목을 다국어화 가능\n- 모듈/플러그인이 선언한 알림/본인인증 메시지 정의의 다국어 키도 언어팩으로 주입되도록 정합 — 이전에는 hook 발화 누락으로 코어 정의에만 적용되던 동작 정상화\n - 업데이트 시 자동 백업 + 실패 시 직전 버전으로 자동 복구\n - 동시성 가드 — 진행 중인 작업 동안 활성/비활성/제거/재업데이트 진입 차단\n - 번들 디렉토리(`lang-packs/_bundled/{identifier}`) 에서 외부 다운로드 없이 (재)설치하는 경로 추가 — 코어 언어팩 복구/재배포 단순화\n - 라이프사이클 훅 명명을 모듈/플러그인/템플릿과 통일 — `core.language_packs.{installed|updated|uninstalled|activated|deactivated}` 발행 (확장 가능성 확대)\n - Artisan 커맨드 신규 — `language-pack:list`, `language-pack:install`, `language-pack:update`, `language-pack:uninstall`\n- 코어와 번들 확장(모듈/플러그인/템플릿)에 내장된 한국어/영어를 가상 보호 언어팩으로 자동 노출 — 별도 설치 없이 언어팩 관리자에서 항상 활성/보호 상태로 확인 가능, 수정/제거 차단\n- 호스트 확장 비활성화 시 그에 종속된 언어팩이 함께 비활성화되며, 재활성화 시 \"다음 언어팩도 활성화하시겠습니까\" 모달로 사용자 의사 확인 후 일괄 활성화\n- 여러 언어팩을 한 번에 활성화하는 `POST /api/admin/language-packs/bulk-activate` API 추가 (의존성/버전 호환성 자동 검사)\n- 언어팩 활성화 시 의존성 + 호스트 확장 버전 호환성 검사 자동 수행 — 호스트 확장이 비활성/미설치이거나 버전 미달이면 활성화 차단\n- 언어팩 업데이트 우선순위를 모듈/플러그인 패턴으로 정합화 — GitHub 1순위 + bundled 폴백, 강제 업데이트 시 bundled 우선\n- 언어팩 상세 모달에서 코어/번들 확장 내장 항목 클릭 시 \"별도 언어팩이 아닌 내장 번역\" 안내 배너 노출\n- 모듈/플러그인/템플릿 상세 모달의 닫기 버튼이 콘텐츠 길이와 무관하게 모달 하단에 항상 보이도록 sticky 처리\n\n#### SEO\n\n- 관리자 환경설정 > SEO 탭의 Sitemap 카드에서 sitemap 을 즉시 재생성하고 마지막 생성 시각을 확인할 수 있는 \"지금 생성\" 버튼 추가 — 큐 드라이버와 무관하게 동기 실행되며 결과를 즉시 표시\n\n#### SEO 봇 감지\n\n- 봇 감지 엔진을 `jaybizzle/crawler-detect` 라이브러리로 교체. 기본 약 1,000종의 봇(검색엔진·링크 미리보기·AI 검색 등)이 자동 감지됨 — 링크를 슬랙·페이스북·LinkedIn·트위터·디스코드·텔레그램 등에 붙여넣으면 제목·설명·이미지 미리보기가 즉시 동작\n- 라이브러리가 놓치는 봇 3종(`kakaotalk-scrap`·`Meta-ExternalAgent`·`ChatGPT-User`) 을 G7 보강 패턴으로 기본 포함\n- 봇 감지 확장 훅 `core.seo.resolve_is_bot` 신설 — 플러그인이 IP 범위 검증·역방향 DNS·Cloudflare 봇 점수 등을 주입할 수 있는 슬롯\n- \"봇 라이브러리 사용\" 관리자 토글 추가 (기본 on, 비활성 시 운영자 커스텀 목록만 사용하는 레거시 모드)\n\n#### SEO OG / Twitter 카드 / 도메인 ownership\n\n- 슬랙·페이스북 링크 미리보기에 이미지·카드가 표시되도록 OG 보강 태그를 자동 출력하도록 개선 — og:site_name, og:image:width, og:image:height, og:image:secure_url, og:image:type, og:image:alt, og:locale 추가\n- Twitter 카드 메타태그(twitter:card / twitter:site / twitter:title / twitter:image 등) 출력 신설 — 슬랙 unfurl 폴백 경로 정상화\n- 운영자 환경설정 SEO 탭에 \"OG / Twitter 카드 기본값\" 카드 추가 — 사이트 이름·이미지 기본 가로/세로·Twitter 카드 타입·Twitter 사이트 핸들 5개 입력\n- 모듈·플러그인이 자기 도메인의 OG/Twitter/JSON-LD 를 직접 선언하도록 SEO declaration API 신설 — 이커머스 상품(Product/Offer/AggregateRating)·게시판 게시글(Article) 등 도메인 스키마가 레이아웃에서 확장 코드로 owned 되어 데이터에 따라 정확한 부속 태그 생성\n- SEO 메타 확장 훅을 분기별·통합 모두 제공 — OG·Twitter·구조화 데이터 각각의 hook 슬롯과 통합 hook 모두 지원하여 확장이 원하는 단계에서 선택 변경 가능\n- 모듈 설정 타이틀 템플릿에서 변수가 비어있을 때 인접 구분자(- – · |) 가 자동 정리되도록 개선 — 옵셔널 그룹 `[ ... ]` 표기도 지원하여 페이지별 구성 명시 가능\n\n### Changed\n\n- 콘솔 confirm 입력 처리 통일 — yes/y, no/n 외 입력 시 안내 메시지 출력 후 재질문, empty 입력 시 default 사용. 코어 업데이트·매니저 커맨드(module/plugin/template install·update·uninstall)·설정 마이그레이션의 모든 yes/no 프롬프트에 동일 규칙 적용 (#15 @laelbe 님께서 건의해주셨습니다.)\n- 비밀번호 재설정 정책 기본값을 비활성으로 변경 — 본인인증 인프라가 미구성된 사이트에서도 기본 동작이 영향받지 않도록 운영자 opt-in 으로 전환\n- 본인인증 정책의 인증 조건(`conditions`) 운영자 편집 허용 — 회원가입 단계 등 정책 조건을 코드 수정 없이 관리자 화면에서 조정 가능. 모듈 업데이트 시 운영자 수정값 보존\n- 토큰 만료 등으로 권한 없는 레이아웃 진입 시 \"페이지 로딩 실패\" 에러 화면 대신 로그인 페이지로 자동 이동 — 로그인 화면에서 \"세션이 만료되었습니다. 다시 로그인해 주세요.\" 토스트로 사용자에게 안내. 템플릿이 자체 로그인 경로를 사용하는 경우 부트스트랩에서 인증 설정을 커스터마이즈할 수 있는 공개 API 도 함께 제공 (#19 @abc101 님께서 건의해주셨습니다.)\n- 템플릿 다국어 데이터 로딩 시 활성 언어팩의 다국어가 가장 높은 우선순위로 병합되도록 변경\n- 권한·역할·메뉴·알림 등 코어 기본 데이터와 배송유형·클레임 사유·게시판 유형 등 모듈 기본 데이터에 활성 언어팩의 다국어가 자동 반영되도록 개선\n- 사용자 수정 보존(user_overrides) 정책을 다국어 JSON 컬럼은 sub-key 단위(`name.ko` 등) 로 기록하도록 개선 — 운영자가 한 언어 라벨만 수정해도 그 언어만 보존되며, 신규 활성 언어팩(예: 일본어 추가) 의 라벨은 자동 동기화됨. 기존 컬럼 단위(`name`) 기록은 업그레이드 시 활성 locale dot-path 로 자동 변환\n- 언어팩 활성/비활성 시점에 영향받는 모듈/플러그인의 entity 시더가 자동 재실행되어 신규 언어 라벨이 즉시 DB 에 반영되도록 라이프사이클 통합 — scope 별 라우팅 (코어 언어팩 → 모든 활성 확장, 모듈 언어팩 → 해당 모듈만, 플러그인 언어팩 → 해당 플러그인만)\n- 환경설정 → SEO → \"추가 봇 패턴\" 필드의 역할 변경 — 기존에는 유일한 봇 매칭 소스(기본 5종)였으나, 이제는 라이브러리가 놓치는 조직별 커스텀 봇만 추가하는 보강 레이어로 동작. 기존 설치의 운영자 커스텀 값은 모두 보존되며, 신규 설치 기본값은 jaybizzle 미커버 3종으로 변경\n\n### Security\n\n- 회원가입·비밀번호 재설정 라우트에 본인인증 정책 강제 미들웨어 부착 — 정책이 활성화된 경우 미인증 요청을 라우트 단계에서 차단 (KISA 측에서 제보해주셨습니다 — KVE-2026-0851)\n- 플러그인이 정책 해석 필터 훅에서 잘못된 타입을 반환해도 원본 정책이 유지되도록 우회 차단 강화 (KISA 측에서 제보해주셨습니다 — KVE-2026-0851)\n- 웹 인스톨러의 Composer/PHP 바이너리 경로 검증에서 사용자 입력이 그대로 shell 명령으로 실행될 수 있던 문제 수정 — 입력은 실행 가능한 단일 파일 경로로만 허용하고 모든 분기에서 인자 escape 강제. 설치 워커가 동일한 입력을 사용하던 내부 헬퍼도 같은 정책으로 정렬 (KISA 측에서 제보해주셨습니다 — KVE-2026-0851)\n- 설치 단계 4 의 확장 기능 선택 API 가 사용자가 보낸 모듈/플러그인/템플릿/언어팩 식별자에 셸 메타문자 검증을 적용하도록 강화 — 부적절한 식별자는 400 응답으로 거부되어 이후 설치 명령에 도달하지 않음 (KISA 측에서 제보해주셨습니다 — KVE-2026-0851)\n- 인스톨러의 코어 업데이트 _pending 경로 검증이 `..` 등 부모 디렉토리 우회 시도를 거부하고 응답 메시지를 단일화하여 임의 디렉토리 enumeration 신호 차단 (KISA 측에서 제보해주셨습니다 — KVE-2026-0851)\n- 설치 시 `.env` 작성 헬퍼가 입력값에 포함된 개행 문자를 제거하도록 변경 — DB 비밀번호 등 사용자 입력으로 새로운 환경 변수 라인이 주입되는 시나리오 차단 (KISA 측에서 제보해주셨습니다 — KVE-2026-0851)\n- 데이터베이스 연결 정보의 host/port/database 값에 DSN 키-밸류 구분자(`;`, `=`) 또는 NUL/CRLF 가 포함되면 연결을 거부하도록 추가 검증 (KISA 측에서 제보해주셨습니다 — KVE-2026-0851)\n- 설치가 완료된 시스템에서 `public/install/` 하위 모든 엔드포인트가 비즈니스 로직 진입 전 HTTP 410 으로 차단되도록 공통 가드 도입 — 운영 환경에서 인스톨러 노출형 결함의 공격 표면 제거. 운영자가 인스톨러를 다시 사용해야 하는 경우 설치 완료 마커(`storage/app/g7_installed`) 와 `.env` 의 `INSTALLER_COMPLETED` 를 모두 제거 (KISA 측에서 제보해주셨습니다 — KVE-2026-0851)\n\n### Fixed\n\n- 코어 업데이트 후 일부 환경에서 모듈/플러그인이 저장한 사용자 데이터(상품 이미지·첨부파일 등)에 PHP 가 접근하지 못해 \"찾을 수 없음\" 오류가 발생하던 문제 수정 — 업데이트 종료 시점 권한 복원 범위를 PHP 쓰기 영역(storage/logs·storage/framework·bootstrap/cache·storage/app/core_pending)으로 한정하고 항목별 정확 복원으로 정합화. 이전 버전에서 본 릴리즈로 업그레이드한 환경에서는 업그레이드 시점에 storage/app 디렉토리/파일 권한을 PHP 가 접근 가능한 형태로 자동 정상화함. 향후 업데이트에서는 사용자 데이터 디렉토리에 자동 생성되는 보존 마커로 권한이 영구 보호됨\n- PHP 8.5 환경에서 모든 페이지 응답이 손상되어 Firefox 에서는 \"Content Encoding Error\", Edge/Chromium 에서는 빈 화면으로 표시되던 문제 수정\n- `php artisan serve` 환경에서 인스톨러 진행 중 .env 파일이 변경되면 개발 서버가 워커를 재시작하면서 설치 단계가 중도에 끊기던 문제 수정 — 설치 진행 중에는 .env 를 건드리지 않고 완료 화면 노출 후 한 번에 반영하도록 변경\n- 관리자 환경설정 \"시스템 사양\" 카드의 메모리 항목이 서버 물리 메모리가 아닌 현재 PHP 프로세스가 사용 중인 메모리(수 MB 단위)로 표시되던 문제 수정 — 디스크 사용량과 동일한 형식(사용량/전체/백분율)으로 실제 서버 RAM 을 표시하도록 개선\n- 관리자 환경설정 \"시스템 사양\" 카드의 CPU 항목이 Windows 11 / Windows Server 2025 에서 \"operable program or batch file.\" 로 표시되던 문제 수정 — 해당 OS 에서 제거된 wmic 의존을 걷어내고 PowerShell 기반 조회로 전환, 구형 Windows 환경에서는 기존 방식으로 자동 폴백\n- 관리자 환경설정 \"정보\" 탭을 한 번 조회한 뒤 다른 탭으로 전환할 때마다 수 초 지연이 반복되던 문제 수정 — 시스템 정보 조회 결과를 1시간 캐싱하여 탭 전환 시 대기 시간 제거 (시스템 캐시 초기화 시 함께 무효화)\n- 큐 워커가 훅 페이로드의 enum 값(주문 상태 등)을 복원하지 못해 활동 로그·알림 등 일부 후속 처리가 실패하던 문제 수정\n- 항목 삭제 후 큐 워커가 처리하는 후속 훅에서 대상 데이터를 복원하지 못해 처리가 중단되던 문제 수정 — 소프트 삭제 페이지·첨부파일, 하드 삭제 주문 옵션 등 포함\n- 페이지 키워드 검색 결과의 전체 건수가 발행된 모든 페이지 수로 부풀려지던 문제 수정\n- 사용자가 댓글을 단 게시글 활동 조회 시 500 에러가 발생하던 문제 수정\n- 사용자 활동 통계에 삭제된 게시글의 댓글 수가 포함되던 문제 수정\n- 일부 주문에서 옵션 정보 직렬화 시 500 에러가 발생하던 문제 수정\n- 상품 옵션 삭제 검증 시 런타임 에러가 발생하던 문제 수정\n- 설치 직후 또는 활성 모듈 디렉토리 부재 시 이커머스 환경설정 기본값이 비어있던 문제 수정\n- 검색 가능 드롭다운(SearchableDropdown)에서 빠른 모달 전환 시 race condition 가능성 차단\n- 반응형 설정과 반복 렌더링이 결합된 레이아웃에서 무한 재귀가 발생할 수 있던 엔진 결함 수정 (engine-v1.43.1)\n- 슬롯 치환 시 텍스트 노드를 컴포넌트로 가정하여 발생할 수 있던 레이아웃 렌더링 오류 차단\n- 인스톨러 실행 시 보안 키 생성 단계에서 개발용 패키지 ServiceProvider 를 찾지 못해 설치가 중단되던 문제 수정 — 이전 환경에서 남은 컴파일 캐시를 vendor 교체 직후와 보안 키 생성 직전에 자동 정리하도록 개선\n- 폴링 모드 인스톨러가 큰 확장(레이아웃·테스트 수천 파일) 설치 도중 멈추던 문제 수정 — 명령 출력을 파일 기반으로 처리하여 OS 파이프 버퍼 한도와 무관하게 동작\n- PHP 8.5 + Apache + mod_fcgid 환경에서 폴링 모드 진행 상황이 실시간 반영되지 않고 설치 완료 시점에 일괄 표시되던 문제 수정 — 어느 모드로도 진행 상황이 즉시 표시되도록 보정. Apache 환경별 권장 설정은 INSTALL.md 와 시스템 요구사항 문서에 명시\n- 설치 완료 후 임시 파일이 자동 정리되지 않던 문제 수정\n- 알림 템플릿 편집 모달의 입력 필드에서 글자를 입력할 때마다 화면이 심하게 버벅이던 문제 수정\n- 슈퍼관리자가 다른 관리자 계정을 삭제할 수 없던 문제 수정 — 관리자 계정 삭제 가능 여부를 역할/권한/스코프 설정에 따라 판단하도록 개선 (슈퍼관리자 본인 보호는 유지)\n- `seo:generate-sitemap` 커맨드가 큐 드라이버 설정과 무관하게 항상 \"큐에 디스패치\" 안내를 출력하던 문제 수정 — 동기 드라이버에서는 즉시 생성으로 동작하고 그에 맞는 안내를 표시하도록 변경\n- 관리자 템플릿을 활성화해도 즉시 반영되지 않아 사용자가 직접 새로고침해야 하던 문제 수정 — 관리자 템플릿 활성화 시 자동으로 페이지를 갱신하여 새 템플릿이 즉시 적용되도록 개선\n- 보안 환경설정의 \"최대 로그인 시도 횟수 / 차단 시간\" 설정이 실제로 적용되지 않아 무제한 로그인 시도가 가능하던 문제 수정 — 임계 도달 시 계정 잠금(HTTP 423), 잠금 해제 시각 안내 토스트, per-IP 백업 throttle, 활동 로그 기록까지 통합 구현\n- 게시판 글쓰기 화면을 URL 로 직접 진입하거나 강제 새로고침했을 때 업로드한 첨부파일이 게시글에 연결되지 않던 문제 수정 (engine-v1.49.2) (#24 @minyho 님께서 제보해주셨습니다.)\n- 코어 업그레이드 후 새 버전에서 추가된 권한·메뉴·알림 정의가 등록되지 않아 관리자 화면에서 \"해당 권한이 없습니다\" 가 반복 표시되거나 신규 메일 템플릿이 비어있던 문제 수정 — 업그레이드 시 새 버전 설정 파일을 정확히 인식하도록 보정. 본 릴리즈로 업그레이드하면 누락분이 자동 등록됨\n- PHP 8.5 환경에서 관리자 로그인 시 `PDO::MYSQL_ATTR_SSL_CA` 등 PDO 드라이버 상수가 deprecation 경고를 발생시키던 문제 수정 — `Pdo\\Mysql::ATTR_SSL_CA` 형태의 신규 상수로 전환하고, PHP 8.5 미만 환경에서는 기존 상수를 그대로 사용하도록 분기 처리 (#23 @yks118 님께서 제보해주셨습니다.)\n- 회원가입 시 일부 환경에서 동의 항목 메타데이터가 누락되어 활동 로그 처리에서 예외가 발생, 후속 훅 체인이 중단되며 신규 회원에게 `user` 역할이 자동 부여되지 않던 문제 수정 — 동의 메타데이터를 nullable 로 받아 누락 케이스에서도 후속 권한 부여가 정상 동작하도록 개선 (#25 @comtylove-netizen 님께서 제보해주셨습니다.)\n\n## [7.0.0-beta.3] - 2026-04-23\n\n### Fixed\n\n- CKEditor5 플러그인 활성 상태에서 게시판 글쓰기 저장 시 \"제목은 필수입니다\" 422 오류가 발생하던 호환성 문제 수정 — 제목·내용 입력 순서와 무관하게 정상 저장되도록 개선 (#17 @laelbe 님께서 제보해주셨습니다.)\n- 코어 업데이트가 sudo 로 실행된 환경에서 캐시·세션·확장 디렉토리의 그룹 쓰기 권한이 일부 손실되어 업데이트 직후 \"Permission denied\" 또는 플러그인 제거 검증 실패가 발생하던 문제 수정 — 업데이트 종료 시점에 그룹 쓰기 권한을 자동 정상화하며 기존 손실 분은 1회성 복구 스텝으로 회수\n- 업데이트 완료 후 Laravel 런타임이 새로 만드는 캐시·세션 하위 디렉토리가 기본 umask(022) 때문에 다시 그룹 쓰기 권한을 잃어 재차 \"Permission denied\" 가 발생하던 문제 수정 — 업그레이드 스텝이 업데이트 진행 프로세스의 umask 를 그룹 쓰기 친화적으로 전환하고, 이후 부팅 시점에도 `storage/` 의 현재 그룹 쓰기 설정을 감지해 프로세스 umask 를 자동 동조 (운영자가 그룹 공유를 비활성화한 환경은 그대로 보존)\n- 코어 업데이트 마지막 단계의 진행 표시줄이 끝난 뒤 줄바꿈 없이 다음 셸 프롬프트가 같은 줄에 붙어 표시되던 출력 문제 수정\n\n### Notes\n\n- 7.0.0-beta.1 에서 7.0.0-beta.3 로 직접 업그레이드하는 경우, 환경(opcache CLI 활성 등)에 따라 권한 복구 스텝이 자동 실행되지 않고 건너뛰어질 수 있습니다. 업데이트 후 `storage/framework/cache` 등에서 Permission denied 가 발생하면 아래 명령을 수동 실행해 주세요 — 7.0.0-beta.2 에서 올라오는 경로에서는 해당 없음\n - `php artisan core:execute-upgrade-steps --from=7.0.0-beta.2 --to=7.0.0-beta.3 --force`\n- 시스템 레벨에서도 일관된 그룹 공유 권한을 원하면 php-fpm pool 설정에 `umask = 002` (또는 systemd unit 의 `UMask=0002`) 추가를 권장합니다. 코드 레벨 동조와 병행하면 외부 프로세스(cron, composer 등) 도 동일 권한으로 파일을 생성합니다\n\n## [7.0.0-beta.2] - 2026-04-20\n\n### Added\n\n#### 알림 시스템 재설계\n\n- 알림 시스템을 Definition(정의) × Template(채널별 템플릿) × Recipients(수신자) 3계층으로 재설계\n- 채널별 독립 수신자 설정 — trigger_user, related_user, role, specific_users 4종 타입 지원\n- 채널 Readiness 검증 — 미설정 채널(SMTP 미구성 등)의 발송을 사전 차단\n- 알림 발송 공통 디스패처 — 채널별 독립 발송, 발송 전후 훅 자동 실행, 모든 채널 자동 로깅\n- 알림 클릭 URL 지원 — database 채널 알림에 클릭 시 이동할 URL 패턴 설정 가능\n- 사용자/관리자 알림 전체 삭제 API 추가\n- 비밀번호 변경 시 `password_changed` 알림 자동 발송\n- 코어 권한에 사용자 알림 전용 카테고리 분리 (관리자/사용자 권한 의미 분리)\n- 알림 정의 일괄 초기화 기능 — 커스터마이징된 알림의 모든 채널 템플릿을 기본값으로 복원 (확인 모달 + 스피너 UX 포함)\n- 모듈/플러그인이 자신의 기본 알림 정의를 코어 리셋 로직에 제공할 수 있는 필터 훅 제공\n- 템플릿 편집 시 편집 상태가 자동 추적되어 리셋 버튼이 자동 노출\n- 알림 정의/템플릿 응답에 편집/삭제 권한 정보(abilities) 포함으로 UI가 권한에 따라 자동 조정\n\n#### Vendor 번들 시스템\n\n- Composer 실행 불가 환경(공유 호스팅 등)을 위한 vendor 번들 시스템 추가\n- vendor-bundle Artisan 커맨드 — 빌드, 검증, 일괄 처리\n- 모듈/플러그인 설치·업데이트에 `--vendor-mode=auto|composer|bundled` 옵션 추가\n- 인스톨러 Step 3에 Vendor 설치 방식 선택 UI 추가 (환경 자동 감지)\n\n#### GeoIP 및 타임존\n\n- MaxMind GeoLite2 자동 다운로드 스케줄러 + 관리자 환경설정 UI 추가\n- IANA 타임존 전체(약 425개) 지원 — 기존 7개 하드코딩 화이트리스트 폐기 (#8 @abc101 님께서 건의해주셨습니다.)\n- Select 컴포넌트에 `searchable` prop 추가 — 타임존 등 대량 옵션에서 검색 가능\n\n#### 인스톨러 개선\n\n- SSE/폴링 듀얼 모드 지원 — Nginx 프록시 + Apache 환경에서 SSE 문제 시 폴링 모드로 전환 가능\n- 확장 의존성 자동 해결 — 템플릿 선택 시 필요한 모듈/플러그인을 즉시 자동 선택, 전이적 의존성까지 해결 (#10 @glitter-gim 님께서 건의해주셨습니다.)\n- 자동 선택된 항목을 시각적으로 구분하고 요구한 확장 이름을 함께 표시\n- 다른 확장이 의존하는 항목은 선택 해제 차단 (의존 관계 안내 메시지 포함)\n- 의존성 버전 제약 사전 검증 — 버전 불일치 시 설치 진행 전 경고 (semver 비교: `>=`, `^`, `~` 등 지원) (#3 @laelbe 님께서 제보해주셨습니다.)\n- 기존 DB 테이블 감지 및 안전한 재설치 지원 — 백업 안내 + 명시적 동의 후 진행 (#5 @laelbe 님께서 건의해주셨습니다.)\n- 권한 안내 단순화 — `chmod -R 755` 단일 명령어로 통합 (업계 표준 정렬)\n- 소유자 불일치(`ownership_mismatch`) 감지 — 전통적 Apache 환경에서 3가지 해결 옵션 제시\n- Step 5에 \"설치 시작\" 버튼 도입 — 모드 선택 후 사용자 클릭으로 설치 시작\n\n#### 공통 캐시 시스템\n\n- 코어/모듈/플러그인 격리 캐시 시스템 도입 (접두사 자동 부여)\n- 모델 변경 시 태그 기반 자동 캐시 무효화 트레이트 추가\n- 환경설정에서 캐시 TTL 중앙 관리 (7개 설정 키)\n\n#### 어드민 UI\n\n- 페이지 진입/탭 전환 시 로딩 spinner 표시 — 데이터 fetch 완료까지 유지 (#6 @laelbe 님께서 제보해주셨습니다.)\n- 목록 페이지네이션 시 DataGrid body 영역 한정 spinner (pagination 버튼 가림 방지)\n- DataGrid 컴포넌트에 `id` prop 추가\n\n#### 기타\n\n- HookManager 공통 큐 디스패치 — 환경설정 하나로 모든 훅 리스너 자동 비동기 실행, 큐 워커에서도 Auth/Request/Locale 컨텍스트 자동 복원\n- WebSocket 클라이언트/서버 endpoint 분리 설정 지원 — 리버스 프록시 환경 대응, SSL 검증 옵션 추가\n- SEO 변수 시스템 — 모듈/플러그인이 페이지별 SEO 변수를 제공할 수 있도록 개선\n- 코어 드라이버 확장 시스템 — 플러그인이 필터 훅으로 스토리지/캐시/세션/큐 등 드라이버를 등록 가능\n- `HasUserOverrides` 트레이트 — 시더 데이터와 사용자 수정을 자동으로 분리 추적\n- 템플릿 엔진 engine-v1.42.0 — `render: false` 선택적 리렌더 제어\n- 템플릿 엔진 engine-v1.41.0 — setLocal/dispatch debounce API + stale 값 오염 방지\n- 템플릿 엔진 engine-v1.40.0 — navigate fallback 옵션 (교차 템플릿 경로 대응)\n- 템플릿 엔진 engine-v1.30~38 — sortable wrapper 요소 지정, React.memo 자동 래핑, resolvedProps 참조 안정화, transition_overlay spinner 시스템, reloadExtensions 통합 핸들러\n\n#### 확장 업데이트\n\n- 코어 업데이트 완료 후 `_bundled` 에 새 버전이 있는 확장을 감지하고 일괄 업데이트 여부를 묻는 인터랙티브 프롬프트 제공\n- 일괄 업데이트 시 전역 레이아웃 전략(overwrite / keep) 1회 질의 후 확장별 예외 지정 가능\n- 모듈/플러그인 업데이트에도 `--layout-strategy=overwrite|keep` CLI 옵션 및 관리자 UI 모달 선택 지원 (기존 템플릿 전용 → 확장 전체 일관)\n- 관리자 모듈/플러그인 업데이트 모달에 사용자가 수정한 레이아웃 감지 및 목록 표시 기능 추가\n- 모듈/플러그인 업데이트 시 각 upgrade step 버전을 콘솔에 실시간 출력 (기존에는 파일 로그에만 기록)\n- 확장 업데이트 메서드 파라미터 순서를 3종(템플릿·모듈·플러그인) 공통 prefix(id, force, onProgress) 로 정렬해 일관성 확보\n- 모듈/플러그인이 런타임에 동적으로 생성한 권한·역할·메뉴(예: 게시판별 권한 세트)도 업데이트 시 보존되도록 개선\n- 모듈/플러그인/템플릿 업데이트 커맨드에 업데이트 소스 선택 옵션(`--source=auto|bundled|github`) 추가 — GitHub 원격 장애·태그 롤백 등 상황에서 번들 소스로 강제 설치 가능\n- GitHub 원격에 존재하지 않는 태그로 업데이트 시 \"아카이브 추출 실패\" 로 불명확한 오류가 나던 문제 개선 — 태그 존재 여부를 먼저 검증해 \"업데이트 소스 없음\" 안내로 명확화\n- 관리자 모듈/플러그인 업데이트 모달의 _global 상태키를 `hasModifiedTemplateLayouts`, `hasModifiedModuleLayouts`, `hasModifiedPluginLayouts` 로 통일해 네이밍 충돌 방지\n- `module:install` / `plugin:install` / `template:install` 에 `--force` 옵션 추가 — 이미 설치된 확장도 `_bundled`/`_pending` 원본으로 활성 디렉토리를 덮어써서 재설치 (불완전 설치 복구용)\n- 확장 활성 디렉토리 무결성 검사 추가 — `module.php`/`module.json` (모듈), `plugin.php`/`plugin.json` (플러그인), `template.json` (템플릿) 중 하나라도 누락된 활성 디렉토리를 로드 시 경고 로그 + `install --force` 복구 힌트 제공\n- 코어 업데이트 커맨드에 외부 ZIP 파일 직접 지정 옵션(`--zip=/path/to/g7.zip`) 추가 — GitHub 다운로드 대신 지정 ZIP 을 사용해 오프라인/수동 배포 가능\n- 모듈/플러그인/템플릿 업데이트 커맨드에 외부 ZIP 파일 직접 지정 옵션(`--zip=/path/to/ext.zip`) 추가 — manifest 식별자와 대상 확장이 일치하는지 검증 후 manifest 의 버전으로 업데이트\n- 외부 ZIP 은 GitHub 릴리스 zipball 의 래퍼 디렉토리와 평탄 루트 구조를 모두 자동 감지해 지원\n\n### Changed\n\n#### 알림 시스템\n\n- MailTemplate 시스템을 NotificationDefinition + NotificationTemplate 체계로 전면 전환\n- 코어 알림 3종의 직접 발송을 훅 기반 발송으로 전환\n- 알림 수신자를 definition 레벨에서 template 레벨로 이동 (채널별 독립 수신자)\n- 알림 broadcast 채널을 정수 User ID에서 UUID 기반으로 변경 (보안 강화)\n- 알림 시각 표시를 ISO 8601에서 사용자 타임존 기준으로 변경\n\n#### 캐시 시스템\n\n- 24개 서비스의 캐시 호출을 `Cache::` 파사드에서 `CacheInterface` DI로 전환\n- 13개 서비스의 TTL을 환경설정 중앙 관리로 통일 (하드코딩 제거)\n- 캐시 키 접두사를 `g7:core:`, `g7:module.{id}:`, `g7:plugin.{id}:` 체계로 통일\n- `CacheService` 클래스 삭제 — `CacheInterface`로 완전 대체\n\n#### 인스톨러\n\n- 권한 검증을 비트 값 비교에서 실제 읽기/쓰기 가능 여부 기반으로 단순화\n- `.env` 생성 안내를 소유자 일치 여부에 따라 1-step으로 통합\n\n#### 기타\n\n- 확장 정보 모달에 의존성 정보 노출 — 코어 요구 버전(`g7_version`), 의존 모듈/플러그인 목록과 각각의 요구 버전·설치 버전·충족 상태 뱃지를 모듈/플러그인/템플릿 정보 모달에서 확인 가능\n- `locale_names` 설정 추가 — 언어 표시명을 중앙에서 관리\n- 매 요청 `Schema::hasTable()` 호출 제거 — 설치 완료 환경에서 10~14회의 불필요한 스키마 쿼리 제거로 응답 시간 단축\n- SeoRenderer에 모듈/플러그인 SEO 변수 자동 해석 기능 추가\n- ResponseHelper — 디버그 모드에서 예외 상세 자동 포함, 프로덕션에서 내부 메시지 노출 차단\n- 환경설정 드라이버 변경 시 큐 워커 자동 재시작\n- `allow_url_fopen=Off` 환경 지원 — GitHub 연동 HTTP 호출을 Laravel Http 파사드로 전면 교체\n- 코어 업데이트 완료 후 안내 메시지를 \"수동 업데이트 명령 나열\"에서 \"일괄 업데이트 인터랙티브 프롬프트\"로 변경\n- 확장 삭제 시 권한·메뉴·역할은 \"데이터도 함께 삭제\" 옵션을 체크했을 때만 삭제되도록 변경 — 기본 삭제에서는 보존되어 재설치 시 사용자 역할 할당이 복원\n\n### Fixed\n\n- WebSocket 데이터소스가 progressive 목록에 포함되어 영구 블러되던 문제 수정\n- WebSocket 채널/이벤트 표현식 미평가로 인증 실패하던 문제 수정\n- WebSocket broadcast HTTP API가 클라이언트 host로 POST되어 실패하던 문제 수정\n- WebSocket 활성화 시 필수 필드 미검증 문제 수정\n- WebSocket 비활성화 시에도 Reverb 연결을 시도하던 문제 수정\n- `--force` 업데이트 시 번들 없는 외부 확장이 업데이트 소스를 찾지 못하던 문제 수정\n- 확장 의존성 데이터 구조 불일치 수정 — manifest JSON 기반으로 통일\n- 모듈/플러그인/템플릿 install/activate/deactivate/uninstall 직후 라우트 미반영 수정\n- 다국어 번역 캐시 경합으로 raw key 노출되던 문제 수정 (engine-v1.38.1)\n- 조건부 `$t:` 표현식에서 번역 실패하던 문제 수정 (engine-v1.38.2)\n- 테스트 환경과 개발 환경이 동일한 Redis 캐시를 공유하여 상호 오염되던 문제 수정\n- 대시보드 브로드캐스트 스케줄이 무한 락에 걸리던 문제 수정\n- 환경설정 저장 후 `config:cache` 상태에서 변경값 미반영 수정\n- Windows 환경에서 파일 잠금 감지 시 30초+ hang 수정 — 0.5초로 단축\n- 개발 의존성 없이 설치된 프로덕션 환경에서 샘플 시더와 팩토리가 실행되지 않던 문제 수정 — 한국어 샘플 데이터 생성기가 자동으로 대체 동작하도록 개선\n- `sudo php artisan core:update` 실행 시 vendor 디렉토리 전체가 root 소유로 오염되던 문제 수정\n- 코어 업데이트 후 신규 권한·메뉴가 DB에 반영되지 않아 관리자 환경설정 페이지 접근이 거부되던 문제 수정\n- beta.1에서 업그레이드 시 제거된 메일 템플릿 모델 참조로 Fatal 오류가 발생하던 문제 수정\n- 업그레이드 후 `bootstrap/cache` 등 일부 디렉토리가 root 소유로 남아 확장 설치·업데이트가 거부되던 문제 수정 — 소유권 복원 범위를 설치 안내 경로 전체로 확장\n- 코어 업데이트 진행률 표시와 upgrade step 실행 로그가 같은 줄에 뒤섞이던 문제 수정 — 각 step 안내가 별도 줄로 깔끔하게 출력됨\n- 템플릿/레이아웃 공개 API가 확장 활성화 전·설치 중에 받은 \"찾을 수 없음\" 에러 응답을 영구 캐시하여 복구 후에도 같은 오류를 반환하던 문제 수정 — 에러 응답은 캐시에서 제외\n- `_pending`/`_bundled` 에 업데이트 중 남은 임시 디렉토리(`{id}_YYYYMMDD_HHMMSS`, `{id}_updating_*` 등) 의 원본 manifest 때문에 `install` 이 존재하지 않는 표준 경로로 접근해 실패하던 문제 수정 — 디렉토리명과 identifier 불일치 시 스캔에서 제외\n- 업그레이드 후 일부 코어 권한이 DB에 반영되지 않아 사용자 역할의 알림 기능 권한이 비어있던 문제 수정 — 별도 프로세스 실행으로 최신 로직 기반 재동기화 보장\n- 코어·확장 설정에서 제거된 메뉴·권한·역할·알림 정의·알림 템플릿·게시판 유형·클레임 사유가 업데이트 후에도 DB에 잔존하던 문제 수정 — 고아 레코드 자동 정리\n- 관리자 UI에서 메뉴·역할·알림·게시판 유형·클레임 사유·배송 유형 등을 수정해도 다음 업데이트 시 기본값으로 덮어써지던 문제 수정 — 사용자가 수정한 필드는 모든 저장 경로에서 자동 추적되어 업데이트 후에도 보존\n- 알림 정의·템플릿도 업데이트 기준으로 동기화 — 새 버전에서 제거된 알림은 DB에서도 삭제\n- 관리자 모듈/플러그인 목록의 \"작성자\" 표기가 manifest 의 `vendor` 필드 대신 식별자 prefix 를 사용하던 문제 수정 — `vendor` 가 정의된 확장은 정확한 작성자명으로 표시 (#9 @glitter-gim 님께서 제보해주셨습니다.)\n\n### Removed\n\n- MailTemplate 시스템 일괄 제거 — 모델, 컨트롤러, 서비스, 리스너, 시더, 팩토리, 다국어 파일 등. NotificationDefinition + NotificationTemplate 체계로 완전 대체\n\n> 참고: beta.1에서 업그레이드하는 운영 환경의 데이터 이관은 `Upgrade_7_0_0_beta_2`가 자동 처리합니다.\n\n## [7.0.0-beta.1] - 2026-04-01\n\n### Changed\n\n- 오픈 베타 릴리즈\n\n## [7.0.0-alpha.21] - 2026-03-30\n\n### Added\n\n- 인스톨러 PHP CLI/Composer 필수 검증 기능 — 기본 `php` 미감지 시 CLI 설정 필수 전환, Composer 실행 확인 및 미설치 시 설치 안내, 둘 다 검증 완료 전 다음 단계 진행 차단\n- 템플릿 레이아웃 수정 감지 시스템 — `original_content_hash`/`original_content_size` 컬럼 추가, SHA-256 해시 기반 수정 감지로 `updated_by` 방식 대체 (TemplateManager, LayoutRepository, 마이그레이션)\n- 관리자 로그인 화면 개선 — 비밀번호 찾기/재설정 플로우 추가, 테마 Segmented Control, 언어 셀렉트 확대 (PasswordResetController, PasswordResetNotification, ForgotPasswordRequest, ResetPasswordRequest)\n- `{{raw:expression}}` 바인딩 번역 면제 마커 시스템 — `$t:` 자동 번역 대상에서 특정 바인딩을 제외하는 마커 도입 (rawMarkers.ts)\n- 레이아웃 프리뷰 모드 — 관리자 레이아웃 편집기에서 실시간 미리보기 지원 (LayoutPreviewController, LayoutPreviewService, 마이그레이션)\n- 활동 로그 이력 조회 메뉴 신설 — ActivityLogController, ActivityLogResource, ActivityLogService, ActivityLogRepository, config/core.php 메뉴/권한 등록, 관리자 레이아웃 추가\n\n### Changed\n\n- `window.G7Config` 설정 노출 최소화 Phase 1 — 프론트엔드 미참조 설정의 브라우저 소스 노출 차단\n - SettingsService: 필드 레벨 `expose: false` 지원 추가 (formatCategorySettings)\n - FiltersFrontendSchema: `fields: {}` (빈 객체) 시 전체 차단 안전 기본값 적용\n - View Composers: TemplateComposer/UserTemplateComposer에 ModuleSettingsService 주입 — frontend_schema 기반 필터링 적용 (기존 config() 직접 참조 우회 수정)\n - 코어 defaults.json: general 4개 필드, security 전체, seo 전체, advanced 9개 필드(debug_mode 제외), upload 2개 필드, drivers 전체를 `expose: false` 처리\n- 캐시 무효화 로직 정비 — 016862cb 이전의 단순한 방식으로 복귀 (버전 증가 + TTL 자연 만료), `extension_cache_previous_versions` 추적 및 `getCacheVersionsToInvalidate()`/`getCacheVersionsForLayoutInvalidation()` 제거, Cache Tags 드라이버 분기 제거로 캐시 드라이버 비의존성 확보\n\n### Fixed\n\n- MariaDB를 `DB_CONNECTION=mysql` 드라이버로 연결 시 `WITH PARSER ngram` 에러로 인스톨러 설치 실패 수정 — `isMariaDb()` 메서드 추가하여 서버 버전 문자열 기반 실제 DBMS 감지 (DatabaseFulltextEngine)\n- 캐시 무효화 버전 키 누락 수정 — `warmTemplateCache()`가 버전 포함 키(`.v{version}`)로 캐시를 생성하나 `clearTemplateCache()`가 레거시 키만 삭제하던 문제, routes/language 캐시에 버전 포함 키 삭제 추가\n- 캐시 버전 0 무효화 누락 수정 — `array_filter()`가 버전 `0`을 falsy로 제거하여 `.v0` 캐시 키가 삭제 대상에서 누락되던 버그 (ClearsTemplateCaches, InvalidatesLayoutCache)\n- 테스트 활성 디렉토리 보호 강화 — `ProtectsExtensionDirectories`에 `moveDirectory` spy 추가, `copyToActive()` 원자적 교체가 활성 디렉토리를 rename으로 파괴하는 것 방지\n- 활동 로그 하위 호환 — `ActivityLogResource`에서 기존 DB 레코드(`type: 'text'`)의 enum 필드를 모델의 현재 `$activityLogFields` 기준으로 동적 번역 + 일괄 변경 `:count` 미치환 보정\n- 활동 로그 enum 미번역 전수 수정 — 14개 모델 필드의 `$activityLogFields` `type: 'enum'` 전환 + 14개 Enum 클래스에 `labelKey()` 메서드 추가 (User, Order, OrderOption, Product, Coupon, Post, Comment, Board)\n- 활동 로그 삭제/일괄삭제 `description_key` 및 샘플 시더 누락 추가 — 코어 시더 `activity_log_descriptions` 테이블에 delete/bulk_delete 키 보충 + 다국어 파일 동기화\n- 관리자 다크모드 품질 전수 조사 및 수정 — 레이아웃 JSON ~546건, TSX ~37건, CSS 5건의 누락된 `dark:` variant 추가 (이커머스 101파일, 게시판 19파일, 페이지 2파일, 마케팅 1파일, 템플릿 컴포넌트 ~20파일)\n- 레이아웃 편집 캐시 무효화 및 버전 히스토리 수정 — LayoutService의 저장/복원 시 PublicLayoutController 서빙 캐시 무효화 + `extension_cache_version` 증가 누락 수정\n- 사용자 관리에서 관리자/슈퍼관리자 삭제 시 토스트 메시지 및 어빌리티 처리 — CannotDeleteAdminException 추가, UserResource에 `can_delete` 어빌리티 반영\n- ActivityLog 핸들러 `$result` 타입 불일치 수정 — CoreActivityLogListener에서 `bool` → `array` 반환 타입 정합성 보정\n- 환경설정 사이트 로고 저장 시 Attachment 객체 정수 검증 오류 수정 — SaveSettingsRequest에서 Attachment JSON 객체를 정수로 검증하던 문제 수정\n- 관리자 SPA 네비게이션 로고 깜빡임 수정 — extends base 컴포넌트 불필요 remount 방지 (`_fromBase` 마킹 기반 stable key 패턴)\n- 인스톨러 다크모드 적색 배경 가독성 개선 — `alert-title`, `alert-message`, `permission-badge`, `test-result` 다크모드 색상 override 추가\n- 인스톨러 `BASE_PATH` 심볼릭 링크 미해석 — `realpath()` 적용 + 절대경로/상대경로 병기로 호스팅 환경 대응\n- 인스톨러 403 에러 페이지 다크모드 미지원 — `prefers-color-scheme: dark` 미디어쿼리 추가\n- 템플릿 업데이트 수정 감지 실패 — `LayoutRepository::update()`가 `updated_by`를 설정하지 않아 `hasModifiedLayouts()`가 항상 \"수정 없음\" 반환하던 문제 수정 (hash 비교 방식으로 전환)\n- 템플릿 업데이트 \"수정 유지\" 전략 미작동 — `layoutStrategy === 'keep'` 시 `refreshTemplateLayouts()` 미호출 → 양쪽 전략 모두 호출하되 `preserveModified` 플래그로 분기\n- 템플릿 업데이트 API 응답 키 불일치 — 백엔드 `has_modified` → 프론트엔드 기대 `has_modified_layouts`/`modified_count` 키 정렬\n- 템플릿/모듈/플러그인 설치 시 `incrementExtensionCacheVersion()` 누락 수정 — TemplateManager(install/activate/deactivate/uninstall) + ModuleManager(install) + PluginManager(install) 총 7곳 추가\n- 캐시 버전 변경 시 프론트엔드 다국어 미갱신 수정 — TemplateApp.ts에서 캐시 버전 변경 감지 시 TranslationEngine 재로드 추가\n- `warmTemplateCache()` 다국어 캐시 워밍 시 `$partial` 디렉티브 미해석 수정 — `json_decode`만 수행하던 코드를 `TemplateService::getLanguageDataWithModules()` 호출로 교체하여 fragment 해석 및 모듈/플러그인 다국어 병합 정상화\n- `ActivateTemplateCommand` 의존성 미충족 시 성공으로 보고하는 버그 수정 — `if ($result)` → `if ($result['success'])` 변경 및 의존성 경고 메시지 출력, `--force` 옵션 추가\n- `ActivateModuleCommand`/`ActivatePluginCommand` 의존성 미충족 시 경고 메시지 미출력 수정 — 의존성 경고 표시 및 `--force` 옵션 전달 추가\n\n## [7.0.0-alpha.20] - 2026-03-30\n\n### Added\n\n- Laravel Scout 통합 및 MySQL FULLTEXT(ngram) 검색엔진 드라이버 확장 시스템 도입 — 커스텀 `DatabaseFulltextEngine`으로 `MATCH...AGAINST IN BOOLEAN MODE` 지원, `core.search.engine_drivers` 필터 훅으로 Meilisearch/Elasticsearch 등 외부 엔진 플러그인 등록 가능\n- `DatabaseFulltextEngine` 다중 DBMS 호환 — FULLTEXT 미지원 DBMS(PostgreSQL, SQLite)에서 LIKE fallback 자동 전환, `whereFulltext()` 정적 헬퍼(관계 검색용), `addFulltextIndex()` 마이그레이션 헬퍼(DBMS별 조건부 DDL)\n- `config/scout.php` 설정 파일 추가 — 기본 드라이버 `mysql-fulltext`, `SCOUT_DRIVER` 환경변수로 전환 가능\n- `FulltextSearchable` 인터페이스 — FULLTEXT 검색 대상 컬럼 및 가중치 정의 계약\n- `AsUnicodeJson` 커스텀 캐스트 — JSON 컬럼에 한글을 `\\uXXXX` 이스케이프 없이 실제 UTF-8로 저장하여 FULLTEXT ngram 토크나이저 정상 동작 보장\n- 환경설정 > 드라이버 탭에 검색엔진 설정 카드 추가 — 기본 MySQL FULLTEXT(ngram) 드라이버 표시, 플러그인 설치 시 추가 드라이버 자동 표시\n- `SaveSettingsRequest`에 검색엔진 드라이버 동적 validation 추가 — `core.search.engine_drivers` 필터 훅 기반 허용 목록\n\n## [7.0.0-alpha.19] - 2026-03-29\n\n### Added\n\n- 검색/필터/정렬 성능 향상을 위한 누락 인덱스 일괄 추가 — activity_logs(description_key), users(created_at), mail_send_logs(status), template_layouts(template_id), schedules(created_at)\n\n## [7.0.0-alpha.18] - 2026-03-26\n\n### Added\n\n- SEO ExpressionEvaluator 산술 연산자 확장 — `*`, `/`, `%` 추가 (기존 `+`, `-`만 지원)\n- SEO PipeRegistry 파이프 함수 엔진 구현 — 프론트엔드 PipeRegistry.ts 빌트인 파이프 15종 PHP 미러링 (date, datetime, relativeTime, number, truncate, uppercase, lowercase, stripHtml, default, fallback, first, last, join, length, filterBy, keys, values, json, localized)\n- ActivityLog `description_params` ID→이름 변환 필터 훅 (`core.activity_log.filter_description_params`) — `ActivityLog::getLocalizedDescriptionAttribute()`에서 실행\n- 코어 모델 `$activityLogFields` 정의 — `User`, `Role`, `Menu`, `Schedule`, `MailTemplate` (5개 모델)\n- ActivityLog ChangeDetector 필드 라벨 다국어 키 추가 (`lang/ko/activity_log.php`, `lang/en/activity_log.php` — `fields` 섹션)\n- `module_helpers.php` — `getModuleSetting()` 헬퍼 함수 추가\n- CoreActivityLogListener: bulk_update per-User/per-Schedule 전환 + bulk_delete per-Schedule 전환 (건별 loggable_id 기록)\n- ActivityLogHandler: 삭제된 엔티티용 loggable_type/loggable_id 직접 지정 fallback 지원\n- 메뉴 관리 크로스 depth 이동 지원 — `UpdateMenuOrderRequest`에 `moved_items` 검증 추가, `MenuRepository`에 크로스 depth reorder 로직 구현\n- `NotCircularParent` 검증 규칙 추가 — 메뉴 순환 참조 방지\n\n### Changed\n\n- ActivityLog 규정 문서 대폭 보강 (`docs/backend/activity-log.md`) — `description_params` 저장 정책, `ActivityLogDescriptionResolver` 패턴, Bulk Update ChangeDetector 패턴, 개발자 체크리스트 추가\n\n## [7.0.0-alpha.17] - 2026-03-26\n\n### Added\n\n- ActivityLog `description_params` ID→이름 변환 필터 훅 (`core.activity_log.filter_description_params`) — `ActivityLog::getLocalizedDescriptionAttribute()`에서 실행\n- 코어 모델 `$activityLogFields` 정의 — `User`, `Role`, `Menu`, `Schedule`, `MailTemplate` (5개 모델)\n- ActivityLog ChangeDetector 필드 라벨 다국어 키 추가 (`lang/ko/activity_log.php`, `lang/en/activity_log.php` — `fields` 섹션)\n- `module_helpers.php` — `getModuleSetting()` 헬퍼 함수 추가\n- CoreActivityLogListener: bulk_update per-User/per-Schedule 전환 + bulk_delete per-Schedule 전환 (건별 loggable_id 기록)\n- ActivityLogHandler: 삭제된 엔티티용 loggable_type/loggable_id 직접 지정 fallback 지원\n\n### Changed\n\n- ActivityLog 규정 문서 대폭 보강 (`docs/backend/activity-log.md`) — `description_params` 저장 정책, `ActivityLogDescriptionResolver` 패턴, Bulk Update ChangeDetector 패턴, 개발자 체크리스트 추가\n\n## [7.0.0-alpha.17] - 2026-03-26\n\n### Added\n\n- Monolog 기반 ActivityLog 아키텍처: `Log::channel('activity')` → `ActivityLogHandler` → DB (3단계)\n- `ActivityLogChannel` (커스텀 Monolog 채널), `ActivityLogHandler`, `ActivityLogProcessor` 신규\n- ActivityLog i18n 지원: `description_key` + `description_params` 기반 실시간 다국어 번역\n- ActivityLog 구조화된 변경 이력: `changes` JSON 컬럼 (필드별 `label_key`, `old`/`new`, `type` 포함)\n- `ChangeDetector` 유틸리티 (모델 스냅샷 비교 → 구조화된 변경 이력 생성)\n- `CoreActivityLogListener` 전면 확장: 모든 코어 Service 훅 구독 (User/Role/Menu/Settings/Schedule/Auth/Module/Plugin/Template/Layout/MailTemplate/Attachment — 66개 훅)\n- 활동 로그 다국어 키 105개 정의 (`lang/ko/activity_log.php`, `lang/en/activity_log.php`)\n- `config/logging.php`에 `activity` 채널 추가\n- `config/activity_log.php` 전용 설정 파일 신규\n- `activity_logs` 테이블 복합 인덱스 추가 (`loggable_type`+`loggable_id`+`created_at`, `log_type`+`action`+`created_at`)\n\n### Fixed\n\n- `resolveLogType()` 사용자 역할 기반 → 요청 경로 기반으로 변경: 관리자가 사용자 화면에서 수행한 액션이 `admin`으로 기록되던 문제 수정 (ResolvesActivityLogType)\n\n### Changed\n\n- `ActivityLog` 모델: `description` 컬럼 삭제 → `description_key`/`description_params` 기반 다국어 전환\n- `ActivityLogService`: 기록 메서드 전면 제거 → 조회 전용으로 축소\n- `ActivityLogResource`: `description` → `localized_description` (실시간 번역)\n- 모든 Controller에서 `logAdminActivity()` 호출 전면 제거 → Listener 경로로 전환\n\n### Removed\n\n- `activity_logs.description` 컬럼 (DB 삭제)\n- `ActivityLogManager`, `ActivityLogDriverInterface`, `DatabaseActivityLogDriver`, `NullActivityLogDriver`\n- `ActivityLogListener` (이중 훅 계층 — Monolog Handler로 대체)\n- `ActivityLogService.log`/`logAdmin`/`logUser`/`logSystem` (Monolog 채널로 대체)\n- `AdminBaseController.logAdminActivity()`, `generateActivityDescription()`, `flattenDataForTranslation()`\n\n### Fixed\n\n- 마이페이지 프로필 저장 시 국가·언어 미선택 상태에서 오류가 발생하던 문제 수정 — 해당 필드를 선택 사항으로 변경\n- 인스톨러 Step 2 .env 복사 명령어 안내 수정 — `.env.example.production` → `.env.example` (functions.php 2곳, installer.js 4곳)\n\n## [7.0.0-alpha.16] - 2026-03-23\n\n### Fixed\n\n- 코어 업데이트 롤백 시 vendor 디렉토리 복원 불가 수정 — 백업 targets에 vendor 포함, excludes에서 vendor 제거\n- 코어 백업 복원 시 개별 target 실패가 전체 복원을 중단하는 문제 수정 — 개별 try-catch로 나머지 target 복원 계속 진행\n- 코어 업데이트 롤백 실패 시 수동 복구 안내 미출력 수정 — composer install 등 복구 단계 안내 추가\n- 코어 업데이트 완전 복원 성공 시 유지보수 모드 자동 해제 추가\n- 코어 업데이트 시 vendor 디렉토리 이중 처리로 인한 마이그레이션 실패 수정 — `backup_only` 설정 분리 (applyUpdate 제외, 백업/복원 전용)\n\n## [7.0.0-alpha.15] - 2026-03-23\n\n### Added\n\n- Users UUID 전환 — 외부 노출 ID를 UUID v7으로 전환, 정수 `id`는 API 응답에서 숨김\n- UniqueIdService 코어 서비스 추가 (UUID v7 + NanoID 생성)\n- 코어 업그레이드 스텝 추가 (Upgrade_7_0_0_beta_15)\n\n### Changed\n\n- User 모델: `getRouteKeyName()` → 'uuid', `$hidden`에 'id' 추가\n- 공개 프로필 API: Route Model Binding 전환 (`{userId}` → `{user}`)\n- 사용자 벌크 상태변경: 정수 ID → UUID 기반\n- API Resource: user.id → user.uuid 전환 (UserResource, UserCollection 외 7개)\n- Activity Log 메타데이터: user_id → uuid 전환\n- FormRequest: 정수 검증 → UUID 검증 전환\n\n### Fixed\n\n- `_global.currentUser?.id` → `?.uuid` 전환 (sirsoft-basic 템플릿 12개 파일)\n- 글쓰기 버튼 abilities 비활성화 누락 수정\n- 코어 업그레이드 스텝 raw SQL 테이블 프리픽스 미적용 수정 — `UpgradeContext::table()` 헬퍼 추가 (Upgrade_7_0_0_beta_15)\n\n## [7.0.0-alpha.14] - 2026-03-20\n\n### Fixed\n\n- 확장 업데이트 시 임시 디렉토리(`_updating_*`, `_old_*`) 오토로드 오염 방지 — 임시 디렉토리를 `_pending/` 하위에 생성하여 IDE 잠금 등으로 잔존 시에도 Fatal Error 방지 (ExtensionPendingHelper)\n\n### Added\n\n- Windows 파일잠금 감지/해제 기능 — 확장 업데이트 시 IDE 등이 파일 핸들을 보유한 경우 자동 감지 및 해제 시도 (FileHandleHelper, ExtensionPendingHelper)\n\n- SEO 렌더러 훅 시스템: `core.seo.filter_context`, `core.seo.filter_meta`, `core.seo.filter_view_data` — 확장이 SEO 렌더링 파이프라인에 런타임 데이터 변환으로 개입 가능\n- `seo.blade.php` 확장 슬롯: `extraHeadTags` (`` 직전), `extraBodyEnd` (`` 직전) — `filter_view_data` 훅을 통해 커스텀 스크립트/스타일 주입\n- `ComponentHtmlMapper` pagination 렌더 모드 — Pagination 컴포넌트에서 SEO용 페이지 링크 자동 생성 (currentPage/totalPages props 기반)\n- `ComponentHtmlMapper` text_format dot notation 지원 — `{author.nickname}` 형태로 객체 prop의 중첩 필드 접근\n- `ExpressionEvaluator::evaluateRaw()` — 표현식 결과를 원본 타입(배열 등)으로 반환하는 메서드\n- `SeoRenderer` SEO 컨텍스트에 `_global`/`_local` 빈 객체 추가 — 프론트엔드 전용 상태 참조 시 null 대신 빈 객체 제공\n- `ComponentHtmlMapper` fields 렌더 모드 — 컴포지트 컴포넌트(ProductCard 등)의 객체 prop에서 SEO용 HTML 필드 자동 생성 (조건부/반복/속성 기반)\n- `SeoRenderer` seoVars 주입 — `meta.seo.vars` 선언을 해석하여 ComponentHtmlMapper format 모드에서 `{key}` 플레이스홀더 치환\n- `ExpressionEvaluator` 리터럴 값 감지 — 숫자, 문자열, boolean, null/undefined 리터럴을 경로 해석 없이 직접 반환\n- `ExpressionEvaluator` $t: 파라미터 `{{}}` 표현식 해석 — 번역 키 파라미터 값에 포함된 바인딩 표현식을 컨텍스트에서 평가\n- `TemplateManager` seo-config 검증에 `fields` 타입 추가\n- `ExpressionEvaluator` seo_overrides — seo-config.json에서 `_local`/`_global` 상태 오버라이드 선언 (와일드카드 매칭으로 접혀있는 콘텐츠 SEO 강제 펼침)\n- `TemplateManager` seo-config 검증에 `pagination` 타입 및 `seo_overrides` 검증 추가\n- `SeoConfigMerger` — 모듈/플러그인/템플릿의 seo-config.json을 수집·병합하는 동적 확장 시스템 (우선순위: 모듈 → 플러그인 → 템플릿, 24시간 TTL 캐싱)\n- `SeoRenderer` `_global` 컨텍스트 주입 — SettingsService/PluginSettingsService 프론트엔드 설정을 `_global`에 주입 + `initGlobal` 매핑으로 데이터소스 응답을 `_global` 경로에 바인딩\n- `AbstractModule`/`AbstractPlugin` SEO 기여 메서드 — `getSeoConfig()`, `getSeoDataSources()` 인터페이스 추가\n- `ModuleManager`/`PluginManager`/`TemplateManager` install/activate/update 시 훅 발행 추가 — Artisan 커맨드에서도 SEO 캐시 자동 무효화 보장\n- SEO Artisan 커맨드 다국어 파일 추가 (`lang/ko/seo.php`, `lang/en/seo.php`)\n- `ComponentHtmlMapper` fields 모드 `$all_props` source — 모든 props 표현식을 해석하여 데이터 객체로 사용 (Header/Footer 등 다수 props 컴포넌트용)\n- `ComponentHtmlMapper` fields 모드 `$t:` 번역 키 지원 — content 패턴에서 `$t:key` → 다국어 텍스트 렌더링\n- `ComponentHtmlMapper` fields iterate `item_attrs` — 아이템별 동적 HTML 속성 (예: `{ \"href\": \"/board/{slug}\" }`)\n- `ExpressionEvaluator` `evaluateRaw()` `??` null coalescing 지원 — 원본 타입(배열/객체) 유지하면서 null coalescing 수행\n- 인스톨러 Windows 환경 명령어 대응 — `chmod`/`chown` 스킵, 미존재 디렉토리 안내 메시지 추가\n- `ExpressionEvaluator` 삼항 연산자 (`a ? b : c`) — JS 우선순위 준수, `?.`/`??` 자동 구분, 중첩 우측 결합\n- `ExpressionEvaluator` `$t()` 함수 호출 구문 — 삼항 내부에서 `$t('key')` 형태로 번역 키 사용 가능 (기존 `$t:key` 방식 확장)\n- `ExpressionEvaluator` `$localized()` 전역 함수 — 다국어 객체에서 현재 로케일 값 추출 (`{ko: \"상품\", en: \"Product\"}` → `\"상품\"`)\n- `ExpressionEvaluator` 객체 리터럴 파서 — `{key: value, ...obj, [dynamicKey]: value}` 구문 지원\n- `ExpressionEvaluator` 스프레드 연산자 — 배열 `[...arr, item]` 및 객체 `{...obj, key: value}` 스프레드 지원\n- `SeoRenderer` computed 속성 해석 — 레이아웃 `computed` 섹션을 `_computed`/`$computed`에 저장 (문자열 표현식 + `$switch` 형식)\n- `ComponentHtmlMapper` classMap 지원 — `base`/`variants`/`key`/`default`로 조건부 CSS 클래스 선언적 적용\n\n### Fixed\n\n- Redis 캐시 DB가 환경설정 값(`REDIS_CACHE_DB`)을 따르도록 수정 — `config/database.php` cache 연결 DB 반영\n\n- `ExpressionEvaluator` 배열 리터럴 파싱 지원 — `['gallery','card'].includes(...)` 표현식에서 배열 리터럴을 PHP 배열로 변환하여 `includes` 등 배열 메서드 정상 동작 (SEO 게시판 타입 분기 조건 중복 렌더링 수정)\n- `ExpressionEvaluator` null 비교 JavaScript 시맨틱 적용 — `null !== value` → `true`, `null === null` → `true` (SEO 컨텍스트에서 `_global` 미존재 경로 비교 시 빈 문자열 대신 올바른 boolean 반환)\n- `ExpressionEvaluator` $t: 번역에서 `{{param}}` 형식 파라미터 치환 지원 — 템플릿 번역 파일의 `{{param}}` + Laravel 표준 `:param` 형식 모두 처리\n- `ExpressionEvaluator` 비교 연산 좌측 optional chaining 경로 타입 보존 — `?.` 포함 경로가 `evaluateExpression`으로 불필요 라우팅되어 boolean 타입이 문자열로 변환되던 문제 수정\n\n## [7.0.0-alpha.13] - 2026-03-18\n\n### Added\n\n- `SeoCacheRegenerator` — 단건 URL 캐시 즉시 재생성 서비스 (다국어 로케일별 렌더링 + 캐시 저장)\n- `SeoSettingsCacheListener` — 코어 SEO 설정 변경 시 전체 SEO 캐시 + 사이트맵 삭제\n\n### Fixed\n\n- `SeoMiddleware`에서 `put()` 대신 `putWithLayout()` 사용 — `invalidateByLayout()`이 레이아웃명 미저장으로 항상 0건 매칭되던 근본 버그 수정\n- `SeoRenderer`에서 레이아웃명을 request attribute로 저장 — `putWithLayout()` 연동\n\n## [7.0.0-alpha.12] - 2026-03-17\n\n### Changed\n\n- API 인증을 토큰 전용으로 전환 — 세션 기반 인증 의존 제거, Bearer 토큰 단일 방식으로 통일\n- README 업데이트 — 표기 통일, 섹션 정비, 문서 링크 연결\n\n## [7.0.0-alpha.11] - 2026-03-16\n\n### Added\n\n- 역할(Role) 상태 토글 API 추가 — `PATCH /api/admin/roles/{role}/toggle-status` 엔드포인트, 훅 지원 (`core.role.before_toggle_status`, `core.role.after_toggle_status`)\n- RoleResource에 `can_toggle_status` ability 추가 — 역할별 토글 권한 제어\n- RoleResource에 `extension_name` 필드 추가 — 확장 출처별 로케일 이름 표시\n- 역할 생성 시 `identifier` 직접 입력 기능 추가 — 미입력 시 name 기반 자동 생성 유지\n- 역할 관련 다국어 키 추가 (ko/en) — identifier 검증 메시지\n- `replaceUrl` 핸들러 추가 — refetch 없이 URL만 변경 (페이지네이션 등 브라우저 히스토리 관리용)\n- 사용자 검색 API에 `id` 파라미터 지원 추가 — 특정 사용자 ID로 직접 조회 가능\n- `TimezoneHelper` 유틸리티 클래스 추가 — 사용자/서버 타임존 간 변환 헬퍼\n- `HasSampleSeeders` 트레이트 추가 — 모듈/플러그인 시더에서 `--sample` 옵션으로 샘플 데이터만 분리 실행\n- `ComponentRegistry.getComponentNames()` 메서드 추가 — 등록된 컴포넌트 이름 목록 조회\n\n### Fixed\n\n- 템플릿 에러 페이지에서 `:identifier`가 리터럴로 표시되는 버그 수정 — Blade `@` 이스케이프 처리\n- ActionDispatcher onChange raw value fallback 제거 — 마운트/리렌더 시 의도치 않은 setState 실행으로 상품 폼 회귀 유발\n- Form 자동 바인딩 setState 경합 수정 — 자동 바인딩과 수동 setState가 동시 실행 시 stale 값으로 덮어쓰이는 문제 해결\n- Form 자동 바인딩 bindingType 메타데이터 기반 boolean 바인딩 수정 — Toggle/Checkbox 등 boolean 컴포넌트에서 문자열 변환 대신 boolean 값 유지\n- SPA 네비게이션 시 `_global._local`에 이전 페이지 상태가 잔존하는 버그 수정 — 페이지 전환 시 `_local` 초기화 처리\n- DynamicRenderer `_computed` 참조 prop에서 캐시된 stale 값이 사용되는 버그 수정 — `_computed`/`$computed` 참조 시 `skipCache: true` 적용\n\n### Changed\n\n- 마이그레이션 통합 — 증분 마이그레이션을 테이블당 1개 create 마이그레이션으로 정리\n- 시더 디렉토리 분리 — 설치 시더와 샘플 시더를 `Sample/` 하위로 분리, `--sample` 옵션 추가\n- 라이선스 프로그램 명칭 정비\n- 일정 관리 메뉴 기본 비활성화 (`config/core.php`)\n- Composer 의존성 업데이트 — Laravel Framework v12.54.1, Reverb v1.8.0, Symfony v7.4.6~7 등\n\n## [7.0.0-alpha.9] - 2026-03-13\n\n### Added\n\n- 그누보드7 커스텀 에러 페이지 도입 — Laravel 기본 에러 페이지(401, 403, 404, 500, 503)를 그누보드7 스타일로 교체, 다크 모드 지원, 접근 경로 기반 홈 링크 분기 (admin → `/admin`, 기타 → `/`)\n- 환경설정 > 고급 디버그 모드 활성화 시 개발 대시보드(`/dev`) 바로가기 버튼 추가\n- 루트 LICENSE 파일 생성 (MIT 라이선스, 한국어 번역 + 영문 원문)\n- 코어 라이선스/Changelog API 엔드포인트 추가 (`GET /api/admin/license`, `GET /api/admin/changelog`)\n- 확장 라이선스 API 엔드포인트 추가 (`GET /api/admin/modules/{id}/license`, `GET /api/admin/plugins/{id}/license`, `GET /api/admin/templates/{id}/license`)\n- Admin 푸터 copyright 클릭 → 코어 라이선스 모달, 버전 클릭 → Changelog 모달 표시 기능\n- 확장 상세 모달에서 라이선스 클릭 시 전문 모달 표시 기능 (모듈/플러그인/템플릿)\n- 각 번들 확장에 LICENSE 파일 및 manifest `license` 필드 추가\n\n### Changed\n\n- 설치 화면 라이선스를 루트 LICENSE 파일로 통합 (`public/install/lang/license-ko.txt`, `license-en.txt` 삭제)\n- `/dev` 라우트 뷰 이름을 `dev-dashboard`로 변경\n\n## [7.0.0-alpha.8] - 2026-03-13\n\n### Changed\n\n- `.env.example.develop`과 `.env.example.production`을 `.env.example`로 통합 — 설치형 솔루션에 환경별 분리 불필요, Laravel/Vite 표준 준수\n- 인스톨러에서 `.env.production` 백업 파일 생성 로직 제거 — Vite mode 기반 로딩 충돌 방지\n\n### Improved\n\n- 코어/확장 업데이트 시 composer install 스킵 최적화 — composer.json/composer.lock 미변경 시 composer install 및 vendor 디렉토리 교체를 건너뛰어 업데이트 시간 단축\n\n### Fixed\n\n- 확장 수동 설치 모달에서 설치 실패 시 상세 에러 사유(`errors.error`) 미표시 문제 수정 — 3개 모달에 상세 에러 P 요소 추가\n- `checkDependencies()` 복수 의존성 에러 수집 — 첫 번째 미충족 의존성에서 즉시 throw 대신 전체 수집 후 줄바꿈 연결 (ModuleManager, PluginManager, TemplateManager)\n- ModuleController/PluginController 에러 반환 형식을 TemplateController와 통일 — `['error' => $e->getMessage()]` 형태\n- 확장 설치 실패 시 `_pending/{identifier}` 디렉토리 자동 정리 — ModuleService, PluginService, TemplateService에 try-catch 추가\n\n## [7.0.0-alpha.7] - 2026-03-13\n\n### Added\n\n- 템플릿 엔진 `multipart/form-data` 지원 — apiCall 핸들러 및 DataSourceManager에서 `contentType: \"multipart/form-data\"` 설정 시 params를 FormData로 자동 변환\n - ActionDispatcher: `fetchWithOptions()`에 FormData 변환 로직 추가, Content-Type 헤더 자동 생략 (브라우저 boundary 설정)\n - DataSourceManager: `toFormData()` 메서드 추가, 인증/비인증 경로 모두 multipart 지원\n - File/Blob 원본 유지, null/undefined 제외, 객체/배열 JSON.stringify 변환\n- `deepMergeWithState()` non-plain 객체(File/Blob/Date) 보호 — spread 복사로 인한 내부 데이터 소실 방지\n- `resolveParams()` non-plain 객체 재귀 해석 스킵 — File 객체가 빈 객체로 변환되는 문제 방지\n- 컴포넌트 onChange raw value fallback — FileInput, Toggle 등 Event가 아닌 값을 전달하는 컴포넌트 지원\n\n### Fixed\n\n- DataSourceManager `isMultipart` 변수 TDZ(Temporal Dead Zone) 버그 수정 — 선언 전 참조로 인한 ReferenceError 해결\n\n## [7.0.0-alpha.6] - 2026-03-13\n\n### Fixed\n\n- 플러그인 환경설정 페이지 진입 시 404 오류 수정 — `registerPluginLayouts()` admin/user 분기 도입 후 루트 `settings.json`이 스킵되던 문제\n - 플러그인 설정 레이아웃을 `resources/layouts/settings.json` → `resources/layouts/admin/plugin_settings.json`으로 이동하여 모듈과 동일한 구조로 통일\n - `AbstractPlugin::getSettingsLayout()` 경로 변경\n - `PluginSettingsService` 오버라이드 경로 및 주석 수정\n - 영향받는 플러그인: sirsoft-daum_postcode, sirsoft-marketing, sirsoft-tosspayments\n\n### Changed\n\n- 플러그인 설정 레이아웃 규정 문서(`plugin-development.md`) 경로/설명 업데이트\n\n## [7.0.0-alpha.5] - 2026-03-12\n\n### Added\n\n- 확장(모듈/플러그인/템플릿) changelog GitHub 원격 소스 지원 — `source=github` 시 GitHub에서 CHANGELOG.md 조회, 실패 시 bundled 폴백\n- `ChangelogParser::parseFromString()`, `getVersionRangeFromString()` 문자열 기반 파싱 메서드 추가\n- `ChangelogRequest` validation 에러 다국어 메시지 추가 (source, version format, required_with)\n- 플러그인 GitHub/ZIP 설치 기능 추가 — 모듈/템플릿에는 있지만 플러그인에 누락되어 있던 기능 신규 구현\n - `PluginService::installFromGithub()`, `installFromZipFile()`, `findPluginJson()` 메서드 추가\n - `PluginController::installFromFile()`, `installFromGithub()` 엔드포인트 추가\n - `InstallPluginFromGithubRequest`, `InstallPluginFromFileRequest` FormRequest 추가\n - `install-from-file`, `install-from-github` API 라우트 추가\n- `ExtensionManager::hasComposerDependenciesAt(string $path)` 메서드 추가 — 임의 경로의 Composer 의존성 확인\n- 모듈/플러그인 설치 시 `_pending` Composer 선행 설치 로직 추가 — 활성 디렉토리 이관 전 의존성 설치\n\n### Changed\n\n- 확장 수동 설치 모달 3개(모듈/플러그인/템플릿) UI 통일 — TabNavigation underline, 에러 배너, 필드별 적색 테두리\n- 확장 GitHub/ZIP 설치 공통 로직을 `GithubHelper`, `ZipInstallHelper`로 추출하여 3개 Service 중복 제거\n- `CoreUpdateService` GitHub 관련 protected 메서드를 `GithubHelper` 위임으로 리팩토링\n- 확장 목록 PageHeader에서 새로고침 버튼 제거, 업데이트 확인 버튼으로 통일\n- 코어 업데이트 `core_pending` 고정 경로 → `core_{Ymd_His}` 타임스탬프 기반 격리 디렉토리로 변경\n- 확장(모듈/플러그인/템플릿) 업데이트에 `_pending/{identifier}_{timestamp}/` 스테이징 패턴 도입\n- 확장 업데이트 시 스테이징 내에서 composer install 실행 (활성 디렉토리 무영향)\n- 확장 GitHub 다운로드 코드를 코어와 동일한 패턴으로 통합 (인증 헤더, 폴백 체인, 타임아웃)\n- GitHub 다운로드 공용 로직을 `ExtensionManager`로 추출하여 3개 Manager 중복 제거\n- `config/app.php`에서 `preserves` 설정 키 제거 (타임스탬프 격리로 불필요)\n- 모듈/플러그인 GitHub/ZIP 설치 흐름을 `temp → _pending → composer install → 활성 디렉토리` 패턴으로 통일\n\n### Fixed\n\n- 코어 업데이트 결과 모달에서 from/to 버전 파라미터가 전달되지 않는 버그 수정 — `params.params` → `params.query`\n- 플러그인에 GitHub/ZIP 설치 기능이 누락되어 있던 결함 수정\n- 파일 복사 시 퍼미션/소유자/소유그룹 미보존 문제 수정 — `File::copy()` → `FilePermissionHelper::copyFile()` 교체 (6개 위치)\n- Windows 환경에서 확장/코어 업데이트 후 `_pending` 하위에 빈 디렉토리가 잔존하는 문제 수정 — `cleanupStaging()`에 3단계 retry 로직 추가\n- 코어 업데이트 후 vendor 교체 시 stale `packages.php`/`services.php`로 인한 500 오류 수정 — `clearAllCaches()`에 컴파일 캐시 삭제 + `package:discover` + `extension:update-autoload` 추가\n- 코어 업데이트 시 `bootstrap/cache` 디렉토리가 소스에서 덮어씌워지는 문제 수정 — excludes에 `bootstrap/cache` 추가\n\n## [7.0.0-alpha.4] - 2026-03-12\n\n### Added\n\n- 코어 업그레이드 스텝 검증용 샘플 마이그레이션 및 업그레이드 스텝 추가\n- 코어 업그레이드 스텝 경로를 `database/upgrades/` → `upgrades/`로 변경\n- 업그레이드 스텝 실행을 프로그레스바 별도 단계로 분리 및 터미널 피드백 추가\n\n### Changed\n\n- `--source` 모드에서 원본 소스 디렉토리를 `_pending`으로 복제 후 작업 (원본 보호)\n- Step 8: 운영 디렉토리 `composer install` 재실행 → `_pending/vendor/` 복사로 변경 (효율화)\n\n### Fixed\n\n- `--source` 모드에서 소스 버전 감지 시 현재 `env()` 대신 `config/app.php` default 값 파싱\n- 코어 업데이트 targets에 `upgrades` 디렉토리 누락 수정\n\n## [7.0.0-alpha.3] - 2026-03-12\n\n### Fixed\n\n- `.gitattributes`의 `CHANGELOG.md export-ignore`가 모든 CHANGELOG 파일을 릴리스 아카이브에서 제외하던 문제 수정\n- PharData(tar ustar) 100바이트 경로 제한으로 324개 파일이 누락되어 orphan 삭제가 발생하던 버그 수정\n\n### Removed\n\n- PharData 아카이브 추출 전략 제거 — tar ustar 형식의 100바이트 경로 제한은 근본적 해결 불가\n\n### Added\n\n- `core:update --source=` 옵션 추가 — ZipArchive/unzip 불가 환경에서 수동 업데이트 지원\n - 상대경로, 절대경로, Windows 경로 모두 지원\n - 소스 디렉토리의 그누보드7 프로젝트 유효성 검증 (`config/app.php` + `version` 키)\n- 업데이트 안내 모달에 수동 업데이트 가이드 섹션 추가\n- 시스템 요구사항 미충족 시 `--source` 옵션 안내 메시지 추가\n\n## [7.0.0-alpha.2] - 2026-03-12\n\n### Fixed\n- 코어 업데이트 확인 시 업데이트할 버전이 없으면 피드백 없던 문제 수정\n - `openModal` 호출 형식 수정 (`params.id` → `target`)\n - 모달 데이터 바인딩 경로 수정 (`_local` → `$parent._local`)\n- 코어 업데이트 명령어 에러 미출력 수정\n- `.env` 예제 파일에서 `G7_UPDATE_TARGETS` 하드코딩 제거\n\n### Added\n- 코어 업데이트 targets 확장 및 orphan 삭제 로직 추가\n- 코어 업데이트 `--local` 옵션 및 설정 기반 제외/보존 추가\n- 환경설정 고급 탭 코어 업데이트 설정 섹션 추가\n\n## [7.0.0-alpha.1] - 2026-03-07\n\n### Added\n\n#### 코어 아키텍처\n\n- Laravel 12 기반 CMS 플랫폼 초기 구조 설계 및 구현\n- Service-Repository 패턴 기반 계층 분리 아키텍처 구축\n- CoreServiceProvider를 통한 인터페이스-구현체 바인딩 시스템\n- ResponseHelper를 통한 통일된 API 응답 형식 (success/error/paginated)\n- AdminBaseController / AuthBaseController / PublicBaseController 컨트롤러 계층 구조\n- FormRequest + Custom Rule 기반 검증 시스템\n- BaseApiResource 상속 기반 API 리소스 패턴\n- PHP 8.2+ Backed Enum 기반 상태/타입/분류 관리\n\n#### 확장 시스템 (Extension System)\n\n- 모듈(Module) 시스템: 디렉토리 스캔 기반 자동 발견, 설치/활성화/비활성화/삭제 관리\n- 플러그인(Plugin) 시스템: 모듈 의존 기반 기능 확장, 설정 UI(settings.json) 지원\n- 템플릿(Template) 시스템: Admin/User 타입 분리, JSON 기반 레이아웃, 컴포넌트 레지스트리\n- ExtensionManager: 모듈/플러그인/템플릿 통합 관리 (설치, 업데이트, 삭제)\n- HookManager: Action/Filter 훅 시스템 (doAction, applyFilters, HookListenerInterface)\n- 확장 업데이트 시스템: _bundled/_pending 디렉토리 구조, GitHub/로컬 업데이트 감지 및 적용\n- 확장 백업/복원 시스템 (ExtensionBackupHelper)\n- 확장 상태 가드 (ExtensionStatusGuard): Installing/Updating/Uninstalled 상태 관리\n- 확장 권한 동기화 (ExtensionRoleSyncHelper): 설치/삭제 시 역할-권한 자동 동기화\n- 확장 메뉴 동기화 (ExtensionMenuSyncHelper): 모듈 메뉴 자동 등록/해제\n- 확장 오토로드 시스템: 런타임 Composer 오토로드 (composer.json 수정 불필요)\n- 확장 Composer 의존성 관리 (extension:composer-install 커맨드)\n- 확장 업그레이드 스텝 시스템 (UpgradeStepInterface, UpgradeContext)\n- 확장 설정 시스템 (SettingsMigrator): 모듈/플러그인별 독립 설정 관리\n- 확장 소유권 시스템: extension_type/extension_identifier 기반 리소스 귀속\n- 확장 에셋 시스템: module.json 매니페스트 기반 JS/CSS 자동 로딩\n- 확장 Changelog 시스템: ChangelogParser 헬퍼 + API 엔드포인트 + 관리 화면 인라인 표시\n- 확장 빌드 시스템: Artisan 커맨드 기반 (module:build, template:build, plugin:build)\n- 확장 캐시 관리: 확장별 독립 캐시 + 일괄 클리어 커맨드\n\n#### 템플릿 엔진 (Template Engine)\n\n- DynamicRenderer: JSON 레이아웃 기반 React 컴포넌트 동적 렌더링\n- DataBindingEngine: `{{expression}}` 문법, Optional Chaining(`?.`), Nullish Coalescing(`??`) 지원\n- ActionDispatcher: 20+ 내장 핸들러 (navigate, apiCall, setState, openModal, closeModal, sequence, condition, replaceUrl, scrollTo, copyToClipboard, downloadFile, debounce, emit, showToast, confirm, validate, submit, reset, filter, sort 등)\n- ComponentRegistry: 컴포넌트 등록/검색/해석 시스템 (기본/집합/레이아웃 타입)\n- TranslationEngine: `$t:key` 즉시 평가 / `$t:defer:key` 지연 평가 다국어 바인딩\n- LayoutLoader: JSON 레이아웃 로딩, 캐싱, ETag 기반 조건부 요청\n- Router: SPA 라우팅, 동적 경로 파라미터(`{{route.id}}`), 쿼리스트링 관리\n- 레이아웃 상속 시스템: `extends` 기반 베이스 상속 + `type: \"slot\"` 위치에 컨텐츠 삽입\n- Partial 시스템: 레이아웃 모듈화, 컴포넌트 치환 (data_sources/computed/modals/state 미지원)\n- 조건부 렌더링: `if` 속성 기반 표현식 평가 (type: \"conditional\" 미지원)\n- 반복 렌더링: `iteration` 설정 (source, item_var, index_var, key)\n- 반응형 레이아웃: `responsive` 속성 기반 breakpoint 오버라이드 (portable/compact/wide)\n- 다크 모드: Tailwind `dark:` variant 기반 자동 전환\n- classMap: 조건부 CSS 클래스 매핑 (key → variants)\n- computed: 계산된 속성 시스템 (의존성 추적, 자동 재계산)\n- 모달 시스템: `modals` 섹션 + openModal/closeModal 핸들러 (`_global.modalStack` 기반)\n- init_actions: 레이아웃 초기화 시 자동 실행 액션 (루트 레이아웃 레벨)\n- Named Actions: 액션 재사용 시스템 (DRY 패턴)\n- errorHandling: 전역/데이터소스별 에러 핸들링 설정\n- scripts: 레이아웃 레벨 커스텀 스크립트 로딩\n- globalHeaders: API 호출 공통 헤더 설정 (pattern 기반 매칭)\n- blur_until_loaded: 데이터 로딩 전 블러 처리\n- lifecycle: 컴포넌트 생명주기 훅 (onMount, onUnmount)\n- slots: 컴포넌트 슬롯 시스템\n- layout_extensions: 모듈/플러그인의 동적 UI 주입 포인트\n- isolatedState: 컴포넌트 상태 격리\n- 데이터소스 조건부 로딩: `if` 표현식 기반 활성화/비활성화\n\n#### 컴포넌트 시스템\n\n- Basic 컴포넌트 (27+): Div, Button, Input, Select, Form, A, H1~H6, Span, P, Img, Label, Textarea, Table, Thead, Tbody, Tr, Th, Td, Ul, Ol, Li, Hr, Strong, Em, Small, Pre, Code, Blockquote, Nav, Header, Footer, Main, Section, Article, Aside, Figure, FigCaption\n- Composite 컴포넌트: DataGrid, CardGrid, Pagination, Modal, SearchBar, Tabs, TabPanel, Accordion, Badge, Breadcrumb, Card, Checkbox, CheckboxGroup, DatePicker, Dropdown, FileUpload, Icon, Notification, Radio, RadioGroup, RangeSlider, Rating, Select (enhanced), Sidebar, Stepper, Switch, Tag, Timeline, Toast, Tooltip, PasswordInput, DynamicFieldList, SortableList, ColorPicker, NumberInput, TreeView, DateRangePicker\n- Layout 컴포넌트: FlexLayout, GridLayout, ScrollLayout, StickyLayout, Spacer, Container, AspectRatio\n- HtmlEditor: TinyMCE 기반 HTML 에디터 컴포넌트\n- Icon 컴포넌트: Font Awesome 6.4.x Free 아이콘 지원 (Solid 1,390개 / Regular 163개 / Brands 472개)\n- Alert, EmptyState, LoadingSpinner, Skeleton, StatusBadge, CopyButton 유틸리티 컴포넌트\n\n#### 상태 관리\n\n- 전역 상태 (`_global`): 앱 전체 공유, 페이지 이동 시 유지\n- 로컬 상태 (`_local`): 레이아웃 단위 격리\n- 계산된 상태 (`_computed`): 의존성 기반 자동 재계산\n- 폼 자동 바인딩: Form 컴포넌트 `stateKey` 기반 자동 상태 연동\n- setState 핸들러: target(global/local), 함수형 업데이트, 배열 조작 (push/filter/map)\n- 상태 구독: `G7Core.state.subscribe` 기반 반응형 업데이트\n- initGlobal: 전역 상태 초기값 선언\n- 모달 스코프 상태: `$parent._local` 스냅샷 기반 데이터 전달\n\n#### 인증 시스템\n\n- Laravel Sanctum 하이브리드 인증 (세션 + Bearer 토큰)\n- AuthManager: 싱글톤 인증 상태 관리\n- 자동 토큰 갱신: 401 응답 시 자동 리프레시 후 재시도\n- OptionalSanctumMiddleware: 선택적 인증 지원 (비인증 사용자 허용)\n- 로그인/로그아웃 3단계 프로토콜 (토큰 삭제 → 세션 무효화 → Auth::logout)\n- 비밀번호 재설정: 이메일 인증 기반 토큰 발급 및 검증\n- 회원가입: 약관 동의, 이메일 인증 지원\n\n#### 권한 시스템\n\n- Role 기반 권한 관리 (User → Role → Permission 3계층)\n- permission 미들웨어 체인 기반 접근 제어 (FormRequest authorize() 사용 금지)\n- 확장별 권한 자동 등록/해제\n- 역할별 메뉴 접근 제어 (role_menus 피벗 테이블)\n- 슈퍼 관리자 (superadmin) 전체 권한 자동 부여\n\n#### 메뉴 시스템\n\n- 계층형 메뉴 구조 (parent_id 기반)\n- 역할별 메뉴 가시성 제어\n- 모듈 메뉴 자동 등록 (getAdminMenus 인터페이스)\n- 메뉴 순서 관리 (드래그 앤 드롭 SortableList)\n- 다국어 메뉴명 지원 (JSON 배열 형식)\n\n#### 데이터 소스 (Data Sources)\n\n- API 엔드포인트 선언적 정의 (id, endpoint, method, params)\n- loading_strategy: immediate/lazy/manual 3가지 로딩 전략\n- 데이터소스 의존성: depends_on 기반 연쇄 로딩\n- 폴링: poll_interval 기반 주기적 갱신\n- 조건부 로딩: `if` 표현식 기반 활성화\n- transform: 응답 데이터 변환 함수\n- cache_duration: 응답 캐싱\n\n#### 관리자 기능 (Admin)\n\n- 대시보드: 시스템 정보, 통계 위젯, 최근 활동\n- 사용자 관리: CRUD, 역할 할당, 상태 관리 (활성/비활성/차단)\n- 역할 관리: CRUD, 권한 할당, 다국어 역할명\n- 권한 관리: 카테고리별 권한 목록, 역할별 권한 할당\n- 메뉴 관리: 계층형 메뉴 편집, 순서 변경, 역할별 가시성 설정\n- 모듈 관리: 설치/활성화/비활성화/삭제, 업데이트 확인, 상세 정보 모달, changelog 표시\n- 플러그인 관리: 설치/활성화/비활성화/삭제, 설정 UI, 업데이트 확인, changelog 표시\n- 템플릿 관리: 설치/활성화/비활성화/삭제, 업데이트 확인, changelog 표시\n- 환경설정: 사이트 기본 정보, SEO 설정, 메일 설정, 보안 설정, 탭 레이아웃\n- 일정 관리: 스케줄 CRUD, 캘린더 뷰, 카테고리 분류\n- 메일 템플릿 관리: DB 기반 메일 템플릿 CRUD, 변수 치환, 미리보기 기능\n- 메일 발송 로그: 발송 이력 조회, 상태 추적, 상세 정보 모달\n- 시스템 정보: PHP/Laravel/DB 버전, 디스크 사용량, 확장 현황 표시\n- 코어 업데이트: 버전 확인, 업데이트 가이드, changelog 인라인 표시, 백업 생성\n\n#### 사용자 기능 (User/Public)\n\n- 로그인/회원가입/비밀번호 재설정 페이지\n- 마이페이지: 프로필 수정, 비밀번호 변경\n- 통합 검색 기능\n- 게시판 뷰: 목록/상세/작성/수정 (board 모듈 연동)\n- 에러 페이지: 403, 404, 500, 503 커스텀 에러 페이지\n- 점검 모드(maintenance) 페이지\n\n#### 설치 프로그램 (Installer)\n\n- 다단계 웹 설치 마법사 (환경 체크 → DB 설정 → 관리자 생성 → 완료)\n- SSE(Server-Sent Events) 기반 실시간 설치 진행 상태 표시\n- 설치 롤백 기능: 실패 시 자동 복원 (마이그레이션/시더 롤백)\n- 언어 선택 지원 (한국어/영어)\n- 다크 모드 지원\n- 환경 요구사항 자동 검증 (PHP 버전, 확장, 디렉토리 권한)\n\n#### 모듈: sirsoft-board (게시판)\n\n- 게시판 관리: CRUD, 카테고리, 스킨 설정, 권한 설정\n- 게시글 관리: CRUD, 검색, 정렬, 페이지네이션\n- 댓글 시스템: CRUD, 대댓글 (계층형), 답글 알림\n- 신고 시스템: 게시글/댓글 신고, 관리자 처리 (승인/거절)\n- 첨부파일: 이미지/파일 업로드, 다운로드, 인라인 표시\n- 비밀글: 작성자/관리자만 열람 가능\n- 블라인드/복원: 관리자 블라인드 처리 및 복원 기능\n- 카드/갤러리 레이아웃: 다양한 목록 표시 형태 지원\n- 게시판 권한: 읽기/쓰기/댓글/관리 권한 분리\n- 인기글/최신글 위젯\n- SEO 메타 태그 자동 생성\n\n#### 모듈: sirsoft-ecommerce (이커머스)\n\n- 상품 관리: CRUD, 옵션(사이즈/색상), 라벨, SEO 메타, 이미지 갤러리\n- 상품 카테고리: 계층형 카테고리, 순서 관리, TreeView 편집\n- 브랜드 관리: CRUD, 로고, 설명\n- 주문 관리: 주문 목록, 상태 변경, 상세 정보, 주문 타임라인\n- 쿠폰 시스템: 정액/정률 할인, 사용 조건 (최소 금액, 특정 상품), 유효기간, 사용 횟수 제한\n- 배송 정책: 무료/유료/조건부 배송, 지역별 요금 설정\n- 공통 정보 관리: 배송/교환/환불 안내, 판매자 정보\n- 장바구니: 추가/수량 변경/삭제, 옵션별 관리, 품절 상품 알림\n- 체크아웃: 주소 입력 (다음 우편번호 연동), 배송지 저장 체크박스, 결제수단 선택, 쿠폰 적용\n- 주문 완료: 주문 번호, 결제 정보 요약, 장바구니 자동 비우기\n- 상품 상세 페이지: 이미지 갤러리, 옵션 선택, 수량 입력, 장바구니 담기\n- 상품 목록: 필터링 (카테고리/브랜드/가격), 정렬, 페이지네이션, 카드 그리드\n- 위시리스트: 찜하기/해제 기능\n- 상품 검색: 키워드 검색, 카테고리 필터 연동\n\n#### 모듈: sirsoft-page (페이지 관리)\n\n- CMS 페이지: CRUD, 슬러그 기반 URL 매핑\n- 페이지 버전 관리: 버전 이력 조회, 이전 버전 복원\n- 첨부파일: 이미지/파일 업로드 지원\n- 검색 통합: 페이지 내용 통합 검색 지원\n- SEO: 메타 태그, Open Graph 설정\n\n#### 플러그인: sirsoft-daum_postcode (다음 우편번호)\n\n- 다음 우편번호 검색 API 연동\n- 주소 선택 후 폼 자동 입력\n- 체크아웃 배송지 입력 연동\n\n#### 플러그인: sirsoft-tosspayments (토스페이먼츠)\n\n- 토스페이먼츠 결제 API 연동\n- 카드/계좌이체/가상계좌/무통장입금 결제 지원\n- 결제 확인 (confirm) 프로세스\n- 결제 성공/실패 콜백 처리\n- 관리자 설정 UI (API 키 관리)\n\n#### 플러그인: sirsoft-marketing (마케팅 동의)\n\n- 마케팅 동의 관리: 이메일 구독, 마케팅 동의, 제3자 제공 동의\n- MarketingConsent / MarketingConsentHistory 모델 및 서비스\n- MarketingConsentListener 훅 리스너 (회원가입/프로필 연동)\n- 사용자 관리 화면 레이아웃 확장 (마케팅 동의 상세/폼/프로필)\n- 회원가입 폼 마케팅 동의 항목 확장\n- 플러그인 설정 UI\n- 역할 기반 접근 제어 (마케팅 관리자)\n- 다국어 지원 (ko, en)\n\n#### 플러그인: sirsoft-verification (본인인증)\n\n- 휴대폰 인증, 아이핀 인증 등 본인인증 기능 제공\n- 사용자 관리 화면 레이아웃 확장 (본인인증 상세/폼)\n- 역할 기반 접근 제어 (본인인증 관리자)\n- 다국어 지원 (ko, en)\n\n#### 템플릿: sirsoft-admin_basic (관리자)\n\n- 관리자 대시보드 레이아웃\n- 사이드바 네비게이션: 접기/펼치기, 계층형 메뉴, 활성 상태 표시\n- 상단바: 사용자 정보, 알림 벨, 다크 모드 전환 토글\n- 반응형 레이아웃 (데스크톱/태블릿/모바일)\n- 관리자 전용 컴포넌트: DataGrid, CardGrid, SearchBar, 필터 패널 등\n- CRUD 화면 표준 레이아웃 (목록/생성/수정/상세)\n- 모달 기반 상세 보기/수정 기능\n- 토스트 알림 시스템\n- 확인 다이얼로그 (삭제 확인 등)\n- 환경설정 탭 레이아웃\n- 모듈/플러그인/템플릿 관리 화면 (설치/업데이트/상세 모달)\n- 사용자/역할/권한 관리 화면\n- 메뉴 관리 화면 (드래그 앤 드롭 순서 변경)\n- 일정 관리 캘린더 화면\n- 메일 템플릿 관리 화면\n- 메일 발송 로그 화면\n- 시스템 정보 화면\n- 코어 업데이트 가이드/결과 모달\n\n#### 템플릿: sirsoft-basic (사용자)\n\n- 사용자 메인 페이지 레이아웃\n- 헤더/푸터 공통 레이아웃 (반응형)\n- 로그인/회원가입/비밀번호 재설정 화면\n- 마이페이지 레이아웃\n- 게시판 목록/상세/작성/수정 화면\n- 상품 목록/상세 화면\n- 장바구니/체크아웃/주문완료 화면\n- 검색 결과 화면\n- CMS 페이지 표시 화면\n- 에러 페이지 (403, 404, 500, 503)\n- 점검 모드(maintenance) 전용 페이지\n- 반응형 레이아웃 (데스크톱/태블릿/모바일)\n\n#### 다국어 시스템 (i18n)\n\n- 백엔드 다국어: `__()` 함수 기반, `lang/{locale}/*.php` 파일 구조\n- 프론트엔드 다국어: `$t:key` 즉시 평가 바인딩, `$t:defer:key` 지연 평가 바인딩\n- 컴포넌트 다국어: `G7Core.t()` API 제공\n- 모듈 다국어: 모듈별 독립 언어 파일, 네임스페이스 분리 (`__('vendor-module::key')`)\n- 템플릿 다국어: partial 언어 파일 분할 (admin.json, errors.json 등)\n- DB 다국어 필드: JSON 배열 형식 (`{\"ko\": \"...\", \"en\": \"...\"}`)\n- 지원 로케일: 한국어(ko), 영어(en)\n- TranslatableField 트레이트: 모델 다국어 필드 자동 해석\n\n#### 보안 (Security)\n\n- ValidLayoutStructure: JSON 레이아웃 구조 검증 Custom Rule\n- WhitelistedEndpoint: API 엔드포인트 화이트리스트 검증 Custom Rule\n- NoExternalUrls: 외부 URL 차단 검증 Custom Rule\n- ComponentExists: 컴포넌트 존재 여부 검증 Custom Rule\n- CSRF 보호: Laravel 기본 CSRF 토큰 검증\n- XSS 방지: 출력 이스케이프, HTML sanitize 처리\n- SQL Injection 방지: Eloquent ORM, 파라미터 바인딩 사용\n- Rate Limiting: API 요청 제한 미들웨어\n\n#### 메일 시스템\n\n- DB 기반 메일 템플릿: 제목/본문 DB 관리, 변수 치환 (Blade 문법)\n- Notification + Mailable 통합 패턴: BaseNotification 상속으로 보일러플레이트 제거\n- 메일 발송 로그: 발송 이력 DB 기록, 수신자/상태/발송 시간 추적\n- 메일 템플릿 사용자 오버라이드 추적 (user_overrides 필드)\n\n#### 알림 시스템 (Notification)\n\n- BaseNotification: 모든 알림의 베이스 클래스 (via() 보일러플레이트 제거)\n- 알림 채널: 메일, 데이터베이스, 브로드캐스트 지원\n- 실시간 알림: Laravel Reverb (WebSocket) 기반 Broadcasting\n\n#### 스토리지 시스템\n\n- StorageInterface: 모든 파일 저장의 추상화 인터페이스 (Storage::disk() 직접 호출 금지)\n- CoreStorageDriver: 코어 스토리지 구현체\n- 확장별 독립 스토리지 공간 할당\n\n#### 코어 업데이트 시스템\n\n- CoreUpdateService: GitHub API 기반 코어 버전 확인 및 업데이트 감지\n- CoreUpdateController: 업데이트 상태 확인, 가이드 표시 API 엔드포인트\n- CoreBackupHelper: 업데이트 전 코어 파일 백업 생성\n- FilePermissionHelper: 파일/디렉토리 쓰기 권한 사전 검증\n- MaintenanceModePage 미들웨어: 점검 모드 시 전용 페이지 표시\n- 업데이트 가이드 모달: changelog 인라인 표시, 단계별 안내\n\n#### 그누보드7 DevTools\n\n- MCP 서버: 20+ 디버깅 도구 제공\n - 기본 도구: g7-state, g7-actions, g7-cache, g7-diagnose, g7-lifecycle, g7-network, g7-form, g7-expressions, g7-logs\n - 고급 분석: g7-datasources, g7-handlers, g7-events, g7-performance, g7-conditionals, g7-websocket\n - 상태 계층/스타일: g7-renders, g7-state-hierarchy, g7-context-flow, g7-styles, g7-auth, g7-tailwind, g7-layout\n - Phase 8 심화: g7-computed, g7-nested-context, g7-modal-state, g7-sequence, g7-stale-closure, g7-change-detection\n- 브라우저 상태 덤프: 런타임 상태 캡처 및 MCP 서버 전송\n- UI 패널: 브라우저 내 DevTools 패널 (실시간 상태/액션/로그 확인)\n- 페이지네이션: offset/limit 기반 대용량 데이터 조회 지원\n\n#### WYSIWYG 에디터\n\n- 비주얼 레이아웃 에디터: 드래그 앤 드롭 기반 레이아웃 편집\n- PropertyPanel: 컴포넌트 속성 편집 UI\n- 컴포넌트 팔레트: 사용 가능한 컴포넌트 드래그 목록\n- 실시간 미리보기: 편집 즉시 렌더링 결과 확인\n\n#### 성능 최적화\n\n- 레이아웃 캐싱: 파싱된 레이아웃 JSON + 상속 병합 결과 캐시\n- 확장 캐싱: 모듈/플러그인/템플릿 목록 및 메타데이터 캐시\n- 번역 캐싱: 언어 파일 파싱 결과 캐시\n- ETag 지원: API 응답 조건부 캐시 검증 (304 Not Modified)\n- Gzip 압축: API 응답 자동 압축\n- Debounce 액션: 연속 이벤트 디바운스 처리 핸들러\n- Lazy 로딩: 데이터소스 지연 로딩 (스크롤/이벤트 트리거)\n- 순환 참조 방지 메커니즘: 레이아웃 상속 무한 루프 감지\n\n#### 데이터베이스\n\n- 코어 테이블: users, roles, permissions, role_has_permissions, role_menus, menus, settings, mail_templates, mail_send_logs, schedules, modules, plugins, templates, template_layouts, template_layout_versions\n- 모든 컬럼 한국어 comment 필수 규칙 적용\n- down() 메서드 완전 롤백 구현 필수 규칙 적용\n- 다국어 필드 JSON 배열 형식 표준\n- MariaDB 호환성 지원\n- boolean/enum 컬럼 값 설명 comment 포함 규칙\n\n#### 테스트 인프라\n\n- PHPUnit 11.x: 백엔드 단위 테스트 (Unit) 및 기능 테스트 (Feature)\n- Vitest: 프론트엔드 컴포넌트/템플릿 엔진 테스트\n- createLayoutTest(): 레이아웃 JSON 렌더링 테스트 유틸리티 (Vitest + jsdom)\n- mockApi(): API 응답 모킹 유틸리티 (fetch 자동 모킹)\n- 트러블슈팅 회귀 테스트: 해결된 사례별 자동 검증\n- 테스트 커버리지: 모델, 서비스, 컨트롤러, 컴포넌트, 레이아웃 렌더링\n\n#### 빌드 시스템\n\n- Vite 기반 프론트엔드 빌드 (코드 분할, 트리 쉐이킹)\n- Artisan 커맨드 기반 빌드 관리:\n - `core:build`: 코어 템플릿 엔진 빌드 (--full, --watch 옵션)\n - `module:build`: 모듈 프론트엔드 빌드 (--all, --watch, --active 옵션)\n - `template:build`: 템플릿 빌드 (--all, --watch, --active 옵션)\n - `plugin:build`: 플러그인 빌드 (--all, --watch, --active 옵션)\n- _bundled 디렉토리 기본 빌드, --active 옵션으로 활성 디렉토리 빌드\n- --watch 모드: 파일 감시 기반 실시간 빌드 (활성 디렉토리 자동 사용)\n\n#### Artisan 커맨드\n\n- 모듈 관리: module:list, module:install, module:activate, module:deactivate, module:uninstall, module:composer-install, module:cache-clear, module:seed, module:check-updates, module:update, module:build\n- 플러그인 관리: plugin:list, plugin:install, plugin:activate, plugin:deactivate, plugin:uninstall, plugin:composer-install, plugin:cache-clear, plugin:seed, plugin:check-updates, plugin:update, plugin:build\n- 템플릿 관리: template:list, template:install, template:activate, template:deactivate, template:uninstall, template:cache-clear, template:check-updates, template:update, template:build\n- 확장 공통: extension:composer-install, extension:update-autoload\n- 코어: core:build\n\n#### 개발 도구 및 문서\n\n- AI 에이전트 개발 가이드 (AGENTS.md): 핵심 원칙, 코딩 규칙, 디버깅 프로토콜\n- 규정 문서 체계 (docs/): 백엔드 14개, 프론트엔드 59개, 확장 23개, 공통 4개 (총 100개)\n- AI 에이전트 자동화 도구: 검증/분석/구현 스킬 30+개, 인덱스 자동 생성 스크립트\n- 트러블슈팅 가이드: 상태 관리, 캐시, 컴포넌트, 백엔드 문제 해결 사례집\n- 코드 스타일: Laravel Pint (PSR-12) 자동 적용\n" + "content": "# Changelog\n\n이 프로젝트의 모든 주요 변경사항을 기록합니다.\n형식은 [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/)를 따르며,\n[Semantic Versioning](https://semver.org/lang/ko/)을 준수합니다.\n\n## [7.0.6] - 2026-07-19\n\n### Security\n\n- 보안 환경설정의 계정 잠금 시간을 `0`(무한대)으로 설정하면 실제로 무기한 잠기도록 수정했습니다. 화면 안내는 \"0을 입력하면 무한대\"였지만 실제로는 1분 뒤 자동 해제되어, 무차별 대입 시도를 영구 차단할 수 없었습니다. 무기한 잠긴 계정에는 남은 시간 대신 관리자 문의 안내가 표시됩니다. (#81 @jiwonpapa 님께서 제보해주셨습니… (생략)" } } ``` diff --git a/docs/backend/api/core-update.md b/docs/backend/api/core-update.md index 7fa9d80d..f94d618f 100644 --- a/docs/backend/api/core-update.md +++ b/docs/backend/api/core-update.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Core Update 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` @@ -31,8 +31,6 @@ | from_version | query | string | 아니오 | — | 시작 버전 (범위 하한) | | to_version | query | string | 아니오 | — | 대상 버전 (범위 상한) | -> 이 엔드포인트는 확장이 파라미터를 추가할 수 있습니다 (`core.extension.changelog_validation_rules`). - **요청 예시** ```http @@ -48,7 +46,7 @@ _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| changelog | array | `[{"version":"7.0.2","date":"2026-07-05","categories":[{"n…` | 변경 이력 텍스트 (원격/파일 CHANGELOG 본문) | +| changelog | array | `[{"version":"7.0.6","date":"2026-07-19","categories":[{"n…` | 변경 이력 텍스트 (원격/파일 CHANGELOG 본문) | **응답 예시** @@ -59,1520 +57,7 @@ HTTP/1.1 200 ```json { "success": true, - "message": "설정을 성공적으로 가져왔습니다.", - "data": { - "changelog": [ - { - "version": "7.0.2", - "date": "2026-07-05", - "categories": [ - { - "name": "Added", - "items": [ - "설치된 모듈·플러그인이 늘어날수록 화면마다 개별로 내려받던 확장 스크립트·스타일 요청을 종류별로 하나로 묶어 내려받도록 개선해, 확장이 많은 사이트의 초기 로딩 요청 수를 크게 줄였습니다. 확장을 설치·활성화·비활성화하면 묶음이 자동으로 다시 만들어져 항상 현재 활성 상태와 일치합니다." - ] - }, - { - "name": "Changed", - "items": [ - "화면·데이터를 내려받을 때의 전송 용량을 줄여 초기 로딩과 페이지 이동 속도를 개선했습니다.", - "모듈·플러그인의 묶음 스크립트·스타일이 지금까지 압축 없이 전송되던 것을 압축 전송하도록 바꿔, 확장이 많은 사이트에서 첫 화면에 받는 용량을 크게 줄였습니다.", - "한글 등 다국어 텍스트가 응답에 불필요하게 부풀려 담기던 것을 원래 형태 그대로 담도록 바꿔, 특히 다국어 번역 데이터의 전송 용량을 줄였습니다.", - "화면을 그리는 데 쓰이지 않는 개발용 설명 주석을 화면 정보 응답에서 빼도록 해 응답 용량을 추가로 줄였습니다." - ] - }, - { - "name": "Fixed", - "items": [ - "코어 업데이트 시 레이아웃 편집기·개발자 진단도구 번들의 빌드 설정 파일이 갱신 대상에서 누락되던 문제를 수정했습니다. 이제 코어를 업데이트하면 이 파일들도 최신 내용으로 반영됩니다.", - "설치 마법사에서 Vendor 설치 방식을 \"Composer 실행\"으로 선택해 설치할 때, 일부 Windows 환경에서 Composer 설치 로그의 문자 인코딩이 어긋나면 진행 상태 응답이 비어 진행 상황이 표시되지 않고 오류가 반복될 수 있던 문제를 보완했습니다. 어떤 문자가 섞이더라도 진행 로그가 항상 정상적으로 전달되도록 처리했습니다. (#62 @kitrio 님께서 제보해주셨습니다.)", - "설치 마법사의 \"설치 환경 확인\" 단계에서, storage 폴더 하위에 파일이 많이 쌓인 환경일수록 요구사항 검사가 오래 걸리던 문제를 개선했습니다. 폴더 쓰기 권한 검사가 불필요하게 깊은 하위까지 훑던 것을 필요한 범위로 제한해 이 단계가 즉시 완료되도록 했습니다.", - "설치 시 \"읽기 전용 데이터베이스(Read DB)를 사용하지 않음\"으로 선택했는데도, 읽기 전용 DB 접속 정보를 확인하려다 설치 마지막 단계에서 실패하던 문제를 수정했습니다. 이제 읽기 전용 DB를 사용하지 않으면 쓰기 DB 정보만으로 설치가 정상 완료됩니다. 아울러 설치 이후에도 읽기 전용 DB 접속 정보를 비워 두면 조회 연결이 자동으로 쓰기 DB로 대체되도록 하여, 별도의 읽기 전용 DB 없이도 안정적으로 동작합니다. (#63 @kitrio 님께서 제보해주셨습니다.)" - ] - } - ] - }, - { - "version": "7.0.1", - "date": "2026-07-03", - "categories": [ - { - "name": "Changed", - "items": [ - "첫 화면 접속 시 내려받는 코어 스크립트 용량을 절반 이하로 줄여 초기 로딩을 개선했습니다. 레이아웃 편집기와 개발자 진단도구를 코어에서 분리해, 편집기는 편집 화면에 들어갈 때만, 진단도구는 디버그 모드를 켰을 때만 내려받도록 했습니다. 일반 사용자는 평소 쓰지 않는 편집기·진단도구 코드를 더 이상 받지 않습니다.", - "설치가 끝난 사이트에서 매 요청마다 반복되던 불필요한 데이터베이스 조회를 줄여 페이지 응답 속도를 개선했습니다. 알림·본인인증 설정을 준비하는 과정에서 이미 존재가 보장된 테이블을 매번 다시 확인하던 동작을 설치 완료 상태에서는 건너뛰도록 했습니다.", - "화면을 그릴 때마다 서버가 매번 확장 기능의 연결 정보를 처음부터 다시 계산하던 부담을 줄여 페이지 응답 속도를 개선했습니다. 이 연결 정보는 확장을 설치·수정하거나 코어를 업데이트할 때만 바뀌므로 그 시점에 한 번 저장해 두고, 이후 요청에서는 저장된 값을 재사용하도록 했습니다. 확장이나 코어를 변경하면 저장 값이 자동으로 갱신됩니다.", - "설치된 모듈·플러그인의 코드를 찾을 때마다 서버가 파일을 하나하나 탐색하던 부담을 줄여, 특히 공유 호스팅 등 파일 접근이 느린 환경에서 페이지 응답 속도를 개선했습니다. 확장 코드의 위치를 설치·수정 시점에 미리 정리해 두고 이후 요청에서는 그 목록을 바로 참조하도록 했습니다. 확장을 설치·수정·삭제하거나 코어를 업데이트하면 목록이 자동으로 갱신됩니다.", - "코어 업데이트 중 데이터 변환 단계가 현재 환경에서 자동으로 끝까지 실행되지 못하고 중단될 때, 남은 단계를 이어서 실행하는 안내를 실행 권한에 맞게 개선했습니다. 특히 관리자 권한(sudo)으로 업데이트한 경우, 남은 단계를 관리자 권한 그대로 실행하면 그 과정에서 만들어지는 파일이 관리자 소유로 남아 이후 웹사이트가 그 파일에 쓰지 못하는 문제가 생길 수 있습니다. 이제 이런 경우 웹 서버 실행 계정(예: www-data)으로 실행하도록 정확한 명령을 함께 안내하며, 웹 서버 계정을 자동으로 확인하지 못하면 어떤 계정으로 실행해야 하는지 확인 방법을 안내합니다." - ] - }, - { - "name": "Fixed", - "items": [ - "Windows 환경에서 코어 업데이트가 `public/storage` 링크를 복사하는 단계에서 \"디렉토리는 복사할 수 없습니다\" 오류로 중단되던 문제를 수정했습니다. Windows의 저장소 링크(정션)가 이제 링크 그대로 안전하게 보존되어 업데이트가 정상 완료됩니다.", - "별도 데이터 변환 작업이 필요 없는 버전으로 코어를 업데이트할 때, \"실행할 업그레이드 단계가 없다\"는 정상 상황을 실패로 표시하고 중단하던 문제를 수정했습니다. 이제 변환 작업이 없는 업데이트는 매끄럽게 완료되며, 이어서 설치된 확장의 최신 번들 반영 단계까지 정상적으로 진행됩니다.", - "일부 호스팅 환경에서 관리자 환경설정의 보안 화면에 들어갈 때 시스템 정보 조회가 전체 실패(오류 응답)하던 문제를 수정했습니다. 이제 CPU·메모리·디스크 등 특정 항목을 서버 정책상 읽을 수 없더라도 그 항목만 \"알 수 없음\"으로 표시하고 나머지 시스템 정보는 정상적으로 보여줍니다. 읽지 못한 항목은 로그로 원인을 확인할 수 있습니다. (#59 @glitter-gim 님께서 제보해주셨습니다.)" - ] - } - ] - }, - { - "version": "7.0.0", - "date": "2026-07-01", - "categories": [ - { - "name": "Added", - "items": [ - "모듈·플러그인이 기존 화면의 초기화 동작에 자신의 초기화 단계를 덧붙일 수 있도록 레이아웃 확장 기능을 확대했습니다. 화면이 특정 모듈을 직접 알지 않아도, 모듈이 설치되어 있을 때만 그 모듈의 초기화(예: 표시 통화 복원)가 함께 실행됩니다.", - "관리자 대시보드 통계에 설치된 템플릿 수(활성/전체)와 활성 언어팩 수(활성/전체)를 추가했습니다.", - "관리자 대시보드에 최근 발송된 알림 이력을 보여주는 \"최근 알림\" 위젯용 조회 기능을 추가했습니다. 발송 채널·수신자·제목·상태(성공/실패/건너뜀)·발송 시각을 최신순으로 확인할 수 있습니다.", - "본인인증 challenge 취소(cancel) 와 verification 토큰 소비(consume_token) 시점에 외부 본인인증 플러그인이 자기 데이터 정리 로직을 등록할 수 있는 훅 4건 추가.", - "플러그인 개발용 표준 베이스 클래스를 추가하여, 모듈 개발과 동일한 방식으로 Repository · 캐시 · 스토리지 의존성을 선언만으로 자동 주입받을 수 있도록 개선. 플러그인이 코어와 같은 표준 위에서 작성되어 일관된 캐시·파일 격리 동작을 제공합니다.", - "모듈·플러그인이 페이지에 주입하는 레이아웃 확장을 관리자 화면에서 직접 편집할 수 있도록 개선. 편집 내역은 버전으로 기록되어 이전 버전으로 복구할 수 있으며, 저장 전 미리보기를 제공합니다.", - "좌측 트리 — 편집 화면 좌측 목록을 트리 구조로 개편하여 일반 레이아웃과 확장 주입 레이아웃을 구분 표시. 확장 주입 레이아웃은 해당 템플릿에 실제 적용되는 항목만 표시됩니다.", - "편집 진입과 캔버스 — 템플릿 관리 화면에 코드 편집과 레이아웃 편집 진입을 분리하고, 운영 화면 좌측 하단에 편집 권한 보유자 전용 진입 버튼을 추가했습니다. 편집 캔버스는 실제 페이지와 동일한 렌더링 결과를 보여주며, 데스크탑·태블릿·모바일 폭을 즉시 전환해 반응형 결과를 확인할 수 있습니다. 데이터 소스가 정의된 화면은 샘플 데이터로 자동 채워져 실제 API 없이도 화면을 검토할 수 있고, 본문 영역에 사용된 위지윅 에디터 등 동적 자원도 캔버스 안에서 그대로 동작합니다. 좌측 트리에서 화면을 선택하면 주소창이 즉시 반영되어 새로고침·공유·뒤로가기 동작이 자연스럽고, 같은 주소로 직접 진입하면 해당 화면이 자동 선택됩니다. 편집 중 로그인이 풀리면 안내 후 자동으로 로그인 화면으로 이동하며 재로그인 시 편집 화면으로 복귀합니다. 권한 부족·서버 오류·네트워크 끊김 등은 상황별 안내 카드(아이콘·설명·다음 행동 버튼)로 표시합니다.", - "요소 추가·재배치 — 컴포넌트를 클릭해 선택하면 외곽에 + 버튼이 나타나고, + 버튼을 누르면 화면 중앙에 모달 팔레트가 열려 좌측 카테고리 사이드바와 우측 그리드 카드(아이콘 + 라벨 + 유형 뱃지)에서 추가할 컴포넌트를 고를 수 있습니다. 부모 컨테이너에 들어갈 수 있는 컴포넌트만 자동 필터링되며, 검색으로 컴포넌트를 빠르게 찾을 수 있습니다. 추가한 컴포넌트는 템플릿이 정의한 기본 모양·기본 텍스트로 캔버스에 바로 보이며, 추가 직후 해당 위치로 화면이 부드럽게 스크롤됩니다. 컴포넌트 우상단 ⓘ 아이콘으로 속성 설정·복사·삭제 메뉴를 열고, 호버/선택 시 외곽선·8방향 리사이즈 핸들 자리·잠금/네비게이션 어포던스가 표시됩니다. 변경 사항은 Ctrl+Z / Ctrl+Shift+Z 또는 툴바 ↶/↷ 로 되돌리거나 다시 실행할 수 있습니다. 편집 모드에서 컴포넌트를 클릭해도 페이지 이동이 일어나지 않고 선택만 되며, 내부 라우트로 가리키는 요소는 \"→ 이 화면 편집\" 어포던스로 목적지 화면 편집 모드로 즉시 전환됩니다.", - "저장·충돌 방지 — 낙관적 잠금으로 저장 결과는 성공·검증 실패·동시 저장 충돌·네트워크 오류 등 상황별 안내(성공 토스트 / 오류 배너 / 충돌 모달)로 즉시 표시됩니다. 같은 레이아웃을 다른 관리자가 먼저 저장했으면 충돌 안내 모달에서 최신 버전 불러오기를 선택할 수 있고, 신규 추가한 컴포넌트가 그 사이 비활성화된 모듈·플러그인 소속이면 저장이 자동 차단됩니다. 저장 직후에는 되돌리기 이력이 정리되어 저장 시점이 새 기준이 되며, 변경 사항이 있는 상태에서 다른 화면으로 이동하려 하면 저장하지 않은 내용이 있다는 안내가 먼저 표시됩니다. 로그인·회원가입 등 비로그인 전용 화면을 편집할 때는 로그인 상태 미리보기가 적용되지 않아 안내 토스트가 잘못 뜨지 않습니다.", - "속성 편집 — 요소를 선택하면 정렬·글자 크기·굵기·색상·여백·크기 등을 친화적인 컨트롤(분절 버튼·슬라이더·색상 선택·태그 입력)로 조정할 수 있고, 요소 모서리·변을 직접 드래그해 가로·세로 크기를 바꿀 수 있습니다(드래그 크기 조정도 되돌리기/다시 실행에 반영). 반복 영역(목록처럼 같은 틀이 여러 번 펼쳐지는 자리)의 속성을 편집하면 모든 항목에 함께 적용됩니다. 요소를 편집하는 동안에는 선택한 요소만 밝게 유지되고 나머지 화면은 어둡게 잠겨, 다른 요소를 실수로 건드리지 않습니다. 속성 편집 팝업은 헤더를 잡아 옮길 수 있으며 선택한 요소를 가리지 않도록 빈 공간으로 자동 배치되고, 큰 요소에서도 요소 추가·메뉴 버튼이 크기 조절 핸들과 겹치지 않도록 여백을 둡니다. 고급 설정에서 요소를 볼 수 있는 권한을 권한 목록에서 골라 지정할 수 있습니다. 목록 있음·없음, 회원·비회원, 검증 실패·저장 중 같은 화면 상태를 미리보기로 전환해 데이터 의존 영역을 실제와 가깝게 검토할 수 있고, 같은 데이터 이름을 여러 확장이 쓰더라도 각 화면이 자기 출처의 샘플로 올바르게 표시됩니다. 라우트를 전환해도 저장하지 않은 편집 내용이 유지되며, 미저장 상태로 화면을 떠나거나 새로고침하면 경고가 표시됩니다. 마지막 저장 상태로 한 번에 되돌리는 초기화 버튼도 제공합니다.", - "이미지 관리 — 배경 이미지 등 레이아웃 첨부 이미지를 업로드·관리할 수 있으며, 상단 툴바와 배경 이미지 컨트롤에서 썸네일 갤러리를 열어 업로드·삭제·배경 적용을 할 수 있으며, 지정한 배경 이미지가 편집 캔버스에 즉시 반영되고(채움·맞춤·타일 표시 모드 포함) 모달을 다시 열어도 현재 설정이 그대로 복원됩니다. 발행된 배경 이미지는 일반 방문자 화면에서도 정상적으로 표시됩니다.", - "동작·표시조건·정렬 편집 — 요소를 선택하고 속성 편집 팝업에서 (1) 클릭·마우스 오버 시 동작을 \"다른 페이지로 이동\"·\"안내 메시지 보여주기\"·\"화면 상태 바꾸기\"·\"목록 새로고침\"·\"서버에 보내고 결과 처리\"·\"결제 진입\" 같은 친화 명칭으로 조립하고(서버 호출은 성공·실패 후속 동작까지 단계로 구성), (2) 로그인 회원·관리자·데이터 있음/없음·수정/새 화면·입력 오류 등 친화 조건을 \"그리고/또는\"으로 결합해 요소를 보여줄 조건을 정하며, (3) 가로·세로 컨테이너의 자식 배치(방향·정렬·줄바꿈·간격)와 정렬 박스 안 요소의 늘어남·정렬·순서를 시각 컨트롤로 편집할 수 있습니다. 핸들러·코드 용어 없이 친화 입력만 노출되며, 이동할 페이지·데이터 이름은 목록에서 선택합니다. \"결제 진입\" 동작은 특정 결제사에 고정하지 않고, 서버 응답이 알려주는 결제 수단(결제 핸들러·결제 데이터)을 데이터 검색에서 골라 연결합니다 — 결제 수단을 바꾸거나 추가해도 화면 편집 없이 동작합니다.", - "색 모드·디바이스별 스타일 — 속성 편집에 라이트/다크 색 모드와 디바이스별 편집을 더해, 스타일·표시조건 탭 상단의 서브탭에서 라이트/다크 색 모드와 기본값·PC·태블릿·모바일(+ 직접 지정하는 커스텀 화면 폭)을 선택해, 각 범위에 스타일·정렬·표시 조건을 따로 지정할 수 있습니다. 글자색·배경색은 패널의 프리셋 색을 고르면 라이트와 다크 모두에 적용되어 다크 화면용 색을 비개발자도 지정할 수 있고(컬러 피커로 직접 고른 자유 색은 라이트 화면 전용), 어느 범위에 기본값과 다른 설정이 있으면 탭에 표시점이 뜨며 \"기본값으로 초기화\"로 그 범위만 되돌릴 수 있습니다. 편집기 상단 라이트/다크 토글로 미리보기 색상 테마를 전환하면, 관리자 화면이 다크 테마여도 미리보기가 라이트/다크를 정확히 구분해 보여줍니다.", - "화면 문구 다국어 편집 — 상단 툴바의 콘텐츠 언어 선택으로 미리보기에 표시되는 화면 문구의 언어를 전환할 수 있고, 캔버스의 텍스트를 더블클릭하면 그 자리에서 바로 고칠 수 있습니다(Enter 로 확정, Esc 로 취소). 문구를 고치면 그 문구가 다국어 키로 자동 등록되어, 입력한 내용이 즉시 화면에 반영됩니다. 편집 중 떠 있는 서식 막대로 굵게·기울임·밑줄을 켜고 끄거나, 글자 크기·정렬·글자색을 목록에서 골라 적용할 수 있으며(색은 색상 미리보기와 함께 선택), 바뀐 서식은 편집 중에도 즉시 보입니다. 편집 안내 배지를 누르면 속성 편집의 번역 탭이 열려 모든 언어의 값을 한 번에 입력할 수 있어, 한 언어만 고치고 다른 언어 번역을 놓치는 일을 줄입니다. (데이터에 연결된 문구·반복 영역 문구는 직접 편집 대상에서 제외됩니다.)", - "모든 컴포넌트로 속성 편집 확대 — 어떤 요소를 선택해도 속성 탭에 \"요소 ID\" 칸이 항상 제공되어 화면 내 요소에 고유 식별자를 부여할 수 있으며(화면 안전 문자만 입력되고 한글·공백 등은 자동으로 걸러집니다), 컴포넌트별 편집 항목(링크 주소·이미지 주소·입력칸 종류·선택지·정렬 옵션 등)도 친화 컨트롤로 편집할 수 있습니다. 아이콘은 자유 입력 대신 검색 가능한 그리드에서 골라 지정할 수 있어(템플릿이 제공하는 아이콘 목록을 검색으로 빠르게 찾기), 아이콘 이름을 외우지 않아도 됩니다. 입력칸의 선택지처럼 여러 항목으로 된 값은 항목 추가·삭제·순서 변경으로 편집하며, 이미 데이터에 연결된 값은 실수로 덮어쓰지 않도록 보호됩니다.", - "모달 시각 편집 — 좌측 트리에 각 화면이 사용하는 모달 목록이 \"이 화면의 모달\" 그룹으로 표시되고(공통 레이아웃의 모달, 모듈·플러그인 화면의 모달, 별도 파일로 분리된 모달 포함), 각 모달은 \"주문 취소\"·\"비회원 조회 비밀번호 재설정\"처럼 친화 제목으로 표시됩니다. 모달을 선택하면 실제 화면 위에 모달이 열린 상태로 문구 수정·요소 추가·저장을 할 수 있습니다. 모달 바깥 영역은 어둡게 잠겨 클릭·드래그·편집이 차단되어 실수로 호스트 화면을 건드리지 않습니다.", - "버전 배지·복원 — 좌측 트리에 각 화면과 확장 주입 조각의 현재 저장 버전 배지가 표시되고, 버전 기록 모달에서 이전 버전과의 비교(diff)와 복원을 확장 주입 조각에서도 동일하게 사용할 수 있으며, 저장·복원 시 배지가 새로고침 없이 즉시 갱신되고 복원 결과가 캔버스에 바로 반영됩니다.", - "공통 레이아웃 편집 — 각 화면의 콘텐츠가 채워질 슬롯 자리가 점선 박스와 라벨로 표시되고(슬롯 자체는 편집 대상이 아니라 잠금), 헤더·푸터를 클릭해 바로 선택·편집할 수 있습니다. 모달 편집 중에도 편집 캔버스의 스크롤이 그대로 유지됩니다.", - "화면 상태 미리보기 — 모달·공통 레이아웃까지 확대해, 배송지 직접 입력, 모바일 메뉴 펼침, 쿠키 배너 표시, 본인인증 진행 모달처럼 특정 조작에서만 나타나는 영역을 상태 전환만으로 미리보며 편집할 수 있고, 모달 안에 확장이 주입한 영역도 시각 편집할 수 있습니다.", - "일부 템플릿(사용자 템플릿 등)의 화면을 미리볼 때 헤더가 \"데이터를 표시할 수 없습니다\"로 표시되지 않고, 미리보기용 샘플 데이터가 모든 템플릿에서 안전하게 렌더됩니다.", - "회원이 아닌 사용자(비회원)에게도 이메일 알림을 보낼 수 있도록 알림 시스템을 확장했습니다. 회원에게 알림을 보내던 것과 동일한 방식으로, 입력한 이메일을 수신자로 알림이 발송되며 발송 내역에도 함께 기록됩니다. 알림 종류별로 비회원 발송을 허용할 수단을 선택할 수 있어, 이메일은 비회원에게 보내되 사이트 안 알림처럼 로그인이 필요한 수단은 보내지 않도록 구분됩니다(새 알림 수단이 추가될 때 비회원 허용 여부를 별도로 정하지 않으면 기본적으로 보내지 않습니다). 비회원에게 가는 알림은 그 사용자가 보던 화면 언어로 발송됩니다." - ] - }, - { - "name": "Changed", - "items": [ - "모든 번들 플러그인이 위 표준 베이스 클래스로 이관되어 캐시·스토리지 도메인이 코어 및 다른 확장과 명확히 격리되도록 개선. 한 플러그인의 캐시 키가 코어 캐시 영역을 침범하던 잠재 결함을 차단하며, 관리자 화면의 변경사항이 의도대로 즉시 반영됩니다.", - "템플릿이 정의하는 에러 페이지(404 / 403 / 500 / 503 / 401 / 점검) 의 식별자를 다른 모든 페이지와 동일한 디렉토리 접두사 포함 형식(`errors/{코드}`) 으로 통일. 템플릿 설치 시 에러 페이지가 일관된 식별자로 시드되며, 일부 환경에서 에러 페이지가 \"찾을 수 없음\" 응답으로 떨어지던 회귀를 차단합니다. 번들 템플릿(`sirsoft-basic`, `sirsoft-admin_basic`) 의 에러 페이지 설정도 함께 정합화됩니다.", - "템플릿 외부 리소스 위임 — Font Awesome·웹폰트 등 외부 CDN 리소스를 페이지 코드에서 하드코딩하지 않고 템플릿의 `externals` 선언으로 위임. 외부 스타일시트·웹폰트·스크립트와 preconnect·preload 등 리소스 힌트를 한 곳에서 관리하며, 관리자 템플릿과 사용자 템플릿에 동일하게 적용", - "템플릿 컴포넌트 에셋 캐시 버스팅 — `?v=time()` → `?v={{ $extensionCacheVersion }}`로 변경하여 매 요청 무효화 → 확장 변경 시점에만 무효화", - "모듈·플러그인 업데이트 시 관리자가 편집한 확장 레이아웃을 보존하거나 덮어쓰는 전략을 선택할 수 있도록 개선.", - "코어 업그레이드 스텝 명령에 보조 단계(권한 정상화·마이그레이션·캐시 정리 등)를 건너뛰고 스텝만 실행하는 옵션 추가.", - "코어 영역의 프론트엔드 다국어 자원(에러 메시지·시스템 토스트 등) 위치를 정리하여 어떤 템플릿이 부팅되어도 자동 노출되도록 개선. 이전에 코어 에러 화면에서 `core.errors.*` 같은 키가 raw 로 노출되던 결함이 해소됩니다." - ] - }, - { - "name": "Fixed", - "items": [ - "정책 관리에서 코어·모듈·플러그인이 제공한 정책의 \"인증 목적\"·\"적용 대상\"·\"우선순위\"를 바꿔 저장해도 반영되지 않던 문제를 수정했습니다 — 이제 정책 키·강제 시점·강제 위치(정책이 걸리는 지점)만 고정되고, 그 외 모든 항목을 운영자가 자유롭게 변경할 수 있으며, 변경한 값은 확장 업데이트 후에도 유지됩니다.", - "한 화면에 본인인증 정책이 여러 개 걸리고 우선순위가 같을 때, 어느 정책이 먼저 적용될지 정해지지 않아 의도한 인증(예: 성인 인증)이 무시되고 다른 인증이 적용될 수 있던 결함을 수정했습니다. 이제 우선순위가 같아도 항상 동일한 정책이 적용되도록 적용 순서를 고정하고, 정책 추가·수정 화면에 우선순위 입력칸을 제공하며 같은 위치에 같은 우선순위의 활성 정책을 저장하려 하면 저장을 막고 안내합니다. 정책 목록에도 우선순위 열을 추가해 한눈에 확인할 수 있습니다.", - "성인 인증 같은 추가 조건을 함께 요구할 때, 그 조건을 충족하지 못해 인증이 실패하면 실패 사유 안내와 \"본인 확인이 필요합니다\" 안내가 동시에 뜨던 문제를 수정했습니다. 이제 추가 조건 미충족으로 실패하면 그 사유 안내 하나만 표시되며, 일반 본인인증 실패의 안내는 종전대로 표시됩니다.", - "환경설정의 기본 프로바이더 / 목적별 프로바이더 / 코드 유효시간 / 최대 시도 횟수 4개 항목이 admin UI 저장값을 무시하던 결함 수정. 변경한 환경설정값이 회원가입·비밀번호 재설정 등 모든 본인인증 흐름에 즉시 반영됩니다.", - "환경설정 > 본인인증 > 목적별 프로바이더에서 일부 본인인증 목적(예: 성인인증)의 프로바이더를 지정해 저장하면 \"설정 저장에 실패했습니다\" 오류로 전혀 저장되지 않던 결함 수정. 이제 모든 본인인증 목적의 프로바이더 매핑이 정상 저장됩니다. 아울러 저장 검증에 실패하면 문제가 된 목적별 프로바이더 입력칸이 빨갛게 강조되고 그 아래에 사유가 표시되어, 어느 항목을 고쳐야 하는지 바로 확인할 수 있습니다.", - "정책에 프로바이더가 지정되지 않은 경우 환경설정의 기본 프로바이더가 자동 적용되도록 보강. 운영자가 정책마다 프로바이더를 일일이 지정하지 않아도 환경설정 기본값으로 일관되게 동작합니다.", - "코드 입력 모달의 \"남은 시도 횟수\" 표시가 환경설정값과 무관하게 항상 5회로 굳어 있던 결함 수정. 운영자가 환경설정에서 지정한 한도가 사용자 화면에 정확히 표시됩니다.", - "인증 코드를 최대 횟수까지 잘못 입력해 시도가 소진되었을 때, 화면에 \"최대 시도 초과 — 다시 요청해주세요\" 안내 대신 일반 오류 문구(\"인증 코드가 올바르지 않습니다\")가 표시되던 결함 수정. 시도가 소진되면 재요청을 안내하는 메시지가 정확히 표시됩니다.", - "본인인증을 사용하는 모듈·플러그인을 비활성화해도 해당 확장이 설정한 본인인증 요구가 계속 적용되던 결함 수정. 이제 확장을 비활성화하면 그 확장이 선언한 본인인증 정책도 함께 적용 해제되고, 다시 활성화하면 운영자가 조정한 설정을 유지한 채 정상 복구됩니다.", - "확장 메뉴 동기화 시점에 운영자가 수정하지 않은 필드 (아이콘·이름·정렬·URL) 가 잘못 \"사용자 수정\" 으로 표시되어 이후 모듈/플러그인 정의값으로의 자동 동기화가 영구히 차단되던 결함 수정. 확장 재설치·업데이트 시 메뉴 정의 변경이 정상 반영됩니다.", - "확장 역할 동기화 시점에 운영자가 수정하지 않은 필드 (역할 이름·설명) 가 잘못 \"사용자 수정\" 으로 표시되어 이후 모듈/플러그인 정의값으로의 자동 동기화가 영구히 차단되던 결함 수정. 메뉴 동기화와 동일 맥락으로, 확장 재설치·업데이트 시 역할 정의 변경이 정상 반영됩니다.", - "모듈·플러그인의 레이아웃 확장이 대상이 아닌 템플릿(관리자 확장 ↔ 사용자 템플릿)에도 등록되던 결함 수정. 업그레이드 시 잘못 등록된 항목을 자동 정리하며, 이후로는 대상 레이아웃이 존재하는 템플릿에만 등록됩니다.", - "모듈·플러그인이 공통 레이아웃(헤더·푸터 등 모든 화면이 공유하는 틀)에 주입한 UI·초기화 동작이 실제 화면에는 전혀 나타나지 않던 결함 수정. 개별 화면이 공통 레이아웃을 물려받을 때 그 \"물려받은 출처\" 정보가 사라져, 공통 레이아웃을 대상으로 한 확장 주입이 어느 화면에도 적용되지 않았습니다. 이제 화면이 물려받은 공통 레이아웃을 대상으로 한 확장도 정상 적용되어, 모듈이 설치되어 있으면 해당 UI(예: 헤더 통화 선택기)와 초기화 동작이 모든 관련 화면에 표시됩니다.", - "확장(모듈·플러그인) 변경 후 라우트 트리·메뉴·다국어 응답이 옛 값으로 남아 있던 결함 수정. 이전에는 클라이언트가 캐시 버전 신호를 동반하지 않아 백엔드 캐시가 갱신 시점을 놓쳤습니다. 확장 설치/활성화/업데이트가 일어나면 다음 페이지 로드부터 새 값이 즉시 보입니다.", - "확장을 업데이트하거나 레이아웃을 저장해도 변경 내용이 화면에 반영되지 않고 옛 내용이 계속 보이던 결함 수정. 캐시 갱신 신호가 기록되는 저장소와 읽히는 저장소가 환경설정에 따라 어긋날 수 있어, 올린 갱신 신호가 화면 쪽에서 보이지 않던 문제였습니다. 이제 갱신 신호가 항상 같은 저장소로 일관되게 기록·조회되어 확장 업데이트·레이아웃 변경이 다음 화면 로드부터 즉시 반영됩니다.", - "코어 업데이트 시 사용자가 모듈·플러그인·템플릿·언어팩의 `_bundled` 폴더 아래에 직접 만들어 둔 커스텀 폴더·파일이 함께 삭제되던 결함 수정. 이제 코어 업데이트는 코어가 제공하는 번들 확장만 갱신하고, 사용자가 추가한 항목은 그대로 보존합니다. (#43 @bigmsg 님께서 제보해주셨습니다.)", - "관리자 권한(`sudo`)으로 코어를 업데이트한 뒤, 모듈·플러그인·템플릿·언어팩을 업데이트하려 하면 \"백업 생성 중\" 단계에서 `mkdir(): Permission denied` 오류로 실패하던 결함 수정. 관리자 권한 업데이트가 백업 폴더와 업그레이드 기록 파일을 관리자 소유로 남겨, 이후 웹서버 계정이 같은 위치에 쓰지 못하던 문제였습니다. 이제 이들 폴더·파일의 소유권과 그룹 쓰기 권한이 정상화되고, 코어와 확장의 업그레이드 기록을 별도 파일로 분리해 두 작업이 서로의 기록 파일을 두고 충돌하지 않습니다.", - "회원에게 보내는 알림(이메일·사이트 알림)이 보내는 사람(관리자 등)의 화면 언어가 아니라 받는 회원 본인의 언어로 발송되도록 수정. 관리자가 한국어 화면에서 다른 언어를 쓰는 회원의 주문 상태를 바꿔도, 알림은 그 회원의 언어로 전달됩니다.", - "신규 주문 접수 시 관리자에게 발송되는 알림 메일의 결제금액이 0원으로 표시되던 결함 수정. 결제 예정 금액이 정확히 표시됩니다.", - "알림 템플릿을 기본값으로 복원할 때 제목·본문만 복원되고 수신자·클릭 주소는 관리자가 수정한 값이 그대로 남던 결함 수정. 이제 복원 시 수신자·클릭 주소까지 기본값으로 정확히 되돌아갑니다.", - "설치 마법사에서 데이터베이스 테이블 접두사를 지나치게 길게 입력하면 일부 확장 설치 시 테이블 인덱스 이름이 데이터베이스 한도를 넘겨 설치가 실패할 수 있던 문제를 예방합니다 — 접두사 입력 길이를 6자로 제한하고, 한도를 넘기면 설치 전 단계에서 안내합니다.", - "인스톨러에서 \"기존 테이블 삭제 후 설치\"를 선택할 때, 입력한 테이블 접두사와 무관한 다른 테이블까지 모두 삭제되던 결함 수정. 이제 입력한 접두사로 시작하는 테이블만 삭제되어, 같은 데이터베이스를 다른 프로그램과 함께 쓰거나 다른 접두사로 재설치할 때 무관한 테이블이 보존됩니다.", - "관리자 대시보드의 최근 활동·모듈/플러그인/템플릿 상태·게시판/이커머스 위젯 등 같은 틀이 여러 번 펼쳐지는 목록에서, 항목마다 고유 식별자를 붙이도록 작성된 화면이 모든 행에 같은 식별자를 그대로 내보내 일부 보조 기능·접근성 도구가 항목을 구분하지 못하고 일부 항목이 화면에서 누락되어 보이던 결함을 수정했습니다 — 이제 반복 목록의 각 행이 고유하게 식별되어 모든 항목이 정상 표시됩니다.", - "한 화면 안에서 탭이나 필터를 클릭으로 전환할 때, 화면에 따라 표시 조건에 묶인 데이터 목록이 비어 보이던 결함 수정. 새로고침해야 보이던 목록이 이제 탭·필터 전환 즉시 정상 표시됩니다. (표시 조건에 따라 로딩되는 데이터 목록이 전환 시점에 다시 평가되지 않던 문제였습니다.)", - "환경설정 화면 등에서 별도 표시 텍스트 없이 켜고 끄는 토글·체크박스를 켜서 저장하면 \"참/거짓 값이어야 합니다\" 오류로 저장이 거부되던 결함 수정. 토글의 켜짐/꺼짐 상태가 문자열이 아닌 참/거짓 값으로 정확히 전달되어 정상적으로 저장됩니다.", - "관리자 목록 화면에서 필터(발급상태·사용여부 등)를 선택하고 검색하면 일부 필터가 검색 직후 \"전체\"로 풀려 보이던 결함 수정. 새로고침해야 선택한 필터가 다시 표시되던 불편을 없애고, 검색 직후에도 선택한 필터가 그대로 유지됩니다.", - "검색창에 `<`, `>`, `(`, `+` 등 특수문자를 입력하면 오류가 발생하던 결함 수정. 특수문자는 일반 검색어로 처리되어, 어떤 문자를 입력해도 오류 없이 검색 결과(또는 빈 결과)가 표시됩니다.", - "IME(한글·일본어·중국어) 입력 도중 Enter 로 글자를 조합 확정할 때 마지막 글자가 누락되거나 검색·제출이 두 번 실행되던 결함 수정. 이제 조합이 끝난 뒤의 Enter 만 동작합니다. (#54 @devrhee16 님께서 제보해주셨습니다.)", - "관리자 대시보드의 시스템 리소스(CPU·메모리·디스크) 수집이 일부 호스팅 환경(open_basedir 제한, 일부 시스템 함수 비활성)에서 실패할 때 대시보드 리소스 조회 전체가 오류로 응답하던 결함 수정. 이제 수집할 수 없는 항목만 \"알 수 없음\"으로 표시되고 나머지 화면은 정상 동작합니다. (#40 @glitter-gim 님께서 제보해주셨습니다.)", - "시스템 정보 화면의 디스크 사용량 조회도 동일한 환경에서 안전하게 \"알 수 없음\"으로 표시되도록 보강했습니다.", - "웹소켓 사용을 끈 상태에서도 실시간 연결이 일부 동작할 수 있던 문제 수정. 이제 웹소켓 사용을 끄면 실시간 연결·채널 인증이 완전히 비활성화됩니다. (#50 @bigmsg 님께서 제보해주셨습니다.)", - "사이트 이름을 언어별로 다르게 저장한 환경에서 브라우저 탭 제목과 검색 메타데이터(JSON-LD)가 올바르게 표시되지 않거나 페이지가 깨질 수 있던 문제 수정. 이제 모든 메타 경로가 현재 언어의 사이트 이름을 일관되게 사용합니다. (#49 @glitter-gim 님께서 제보해주셨습니다.)", - "존재하지 않는 페이지에 접근할 때 정상 페이지처럼 200 응답을 반환하던 문제 수정. 이제 미등록 경로는 올바른 404 응답을 반환해 검색엔진이 없는 페이지를 색인하지 않습니다. (#47 @glitter-gim 님께서 제보해주셨습니다.)", - "인증이 필요한 관리자 API에 로그인 없이 접근할 때, 브라우저 주소창 직접 입력 등 일부 요청에서 서버 오류로 응답하던 결함 수정. 이제 어떤 방식으로 접근해도 일관되게 \"인증 필요\" 응답이 반환됩니다. (#39 @glitter-gim 님께서 제보해주셨습니다.)" - ] - }, - { - "name": "Security", - "items": [ - "사용되지 않는 파일 서빙 엔드포인트(`/storage` 직접 업로드·다운로드 경로) 노출을 제거했습니다. 실제 파일 업로드·다운로드는 별도의 인증된 경로로 처리되며 영향이 없습니다. (#52 @glitter-gim 님께서 제보해주셨습니다.)", - "설치가 완료된 시스템에서 설치 상태 관리 엔드포인트(`/install/api/state-management.php`)가 다른 설치 API 와 동일하게 차단되지 않아, 설치 완료 후에도 설치 상태 조회·초기화·중단 요청이 가능하던 문제를 수정했습니다. 설치가 완전히 끝난 환경에서는 해당 요청이 모두 HTTP 410 으로 차단됩니다. 설치 진행 중 완료 화면으로 정상 전환되는 동작은 영향받지 않습니다. (KISA 측에서 제보해주셨습니다 — KVE-2026-1056)" - ] - }, - { - "name": "Removed", - "items": [ - "기본 제공 결제 수단을 KG이니시스로 단일화했습니다. 그 외 결제 연동(나이스페이먼츠·NHN KCP·토스페이먼츠)은 기본 번들에서 제외되며, 필요한 경우 별도 플러그인으로 설치해 사용할 수 있습니다. 이미 해당 결제 연동을 설치해 사용 중인 사이트는 영향받지 않습니다." - ] - } - ] - }, - { - "version": "7.0.0-beta.7", - "date": "2026-05-15", - "categories": [ - { - "name": "Fixed", - "items": [ - "인스톨러 설치 완료 후 `.env` 에 DB 자격증명과 `APP_KEY` 가 기록되지 않고 `storage/installer/runtime.php` 에 평문으로 잔존하던 결함을 수정. 운영 동작은 정상이었으나 자격증명이 정상적인 위치로 옮겨지지 않고 임시 파일에 보존되는 상태였습니다. (#37 @glitter-gim 님께서 제보해주셨습니다.)", - "beta.4~beta.6 출시본에서 위 결함으로 인해 자격증명이 잔존한 환경을 beta.7 업그레이드 시 자동으로 감지하여 `.env` 로 머지하고 임시 파일을 정리하도록 업그레이드 스텝을 추가했습니다. `.env` 의 `INSTALLER_COMPLETED` 마커가 이미 설정된 정합 이상 케이스는 자동 정리 대상에서 제외하고 운영자 수동 검토를 안내합니다.", - "위 업그레이드 스텝이 `.env` 를 머지할 때 권한·소유자·그룹을 웹 서버 기준으로 정렬하도록 보강. 기존 `.env` 가 있던 환경에서는 운영자가 설정한 권한을 그대로 보존하고, 새로 생성되는 환경에서는 웹 서버가 읽을 수 있는 권한과 그룹을 자동으로 적용합니다. 명령줄 업그레이드 실행 사용자와 웹 서버 사용자가 다른 자체 구축 서버 환경에서 머지 직후 웹 서버가 `.env` 를 읽지 못해 사이트가 다운되는 회귀를 차단합니다.", - "인스톨러 설치 완료 화면의 `.env` 권한 강화 안내(`chmod`/`chgrp` 명령 예시) 가 Windows 환경에서 비노출되도록 OS 가드 추가. POSIX 권한 모델이 없는 Windows 환경에서 무의미한 Linux/macOS 전용 명령이 노출되어 운영자 혼란을 유발하던 문제를 수정합니다.", - "beta.4~6 출시본의 인스톨러 결함으로 자격증명이 `storage/installer/runtime.php` 에 잔존한 환경에서 beta.7 업그레이드 시 번들 확장 일괄 업데이트 단계가 `Access denied for user 'root'@'localhost'` 로 실패하던 회귀 수정. 업그레이드 스텝이 `.env` 머지 직후 부모 프로세스의 환경 변수도 함께 갱신하여 후속 자식 프로세스가 정상 자격증명을 상속받도록 보강합니다. 정상 설치 환경 (beta.3 등) 에서는 트리거되지 않습니다.", - "위 동일 회귀의 추가 차단 — 코어 업데이트가 별도 프로세스로 업그레이드 스텝을 실행하는 경로에서는 자식 종료 후 부모의 환경 변수가 갱신되지 않아 동일 증상이 재발하던 결함 수정. 자식 프로세스가 부팅 시점에 디스크의 `.env` 와 자신의 환경 변수를 대조하여 손상이 감지되면 디스크 값으로 보정하도록 보강하여, 다음 단계의 확장 일괄 업데이트가 정상 자격증명으로 진행되도록 합니다. 정상 설치 환경에서는 자동 비활성화됩니다." - ] - }, - { - "name": "Changed", - "items": [ - "`INSTALL.md` 에 \"업그레이드\" 섹션을 신설하여 자동 업데이트의 표준 절차, `sudo` 권장 사유 (`.env` · `bootstrap/cache` 의 권한 모델), 사용자군별 단계적 업그레이드 분기, 권한·캐시 트러블슈팅을 한곳에서 안내하도록 보완. 관리자 환경설정 > 정보 > 업데이트 확인 모달이 본 섹션으로 외부 링크를 노출합니다. (#30 @yks118 님께서 건의해주셨습니다.)", - "관리자 환경설정 > 정보 > 업데이트 확인 모달의 자동 업데이트 명령어 안내를 `sudo php artisan core:update` 권장 형태로 변경. 권한이 일치하지 않는 환경(`.env` 가 웹 서버 사용자 소유) 에서의 실행 실패를 사용자가 사전에 회피할 수 있도록 모달 하단에 \"전체 업그레이드 가이드 보기\" 외부 링크를 추가해 INSTALL.md 의 업그레이드 섹션으로 연결합니다. (#30 @yks118 님께서 제보해주셨습니다.)" - ] - }, - { - "name": "Notes — `.env` 권한 권장안", - "items": [ - "가장 안전한 권한은 `0600` (소유자만 읽기·쓰기) 입니다. `.env` 는 DB 비밀번호와 `APP_KEY` 등 평문 자격증명을 포함하므로 외부 접근을 최소화하는 것이 권장됩니다.", - "단, `0600` 적용 전에 **`.env` 의 소유자가 웹 서버 실행 사용자와 동일해야** 합니다 (예: 단일 사용자 환경의 `chown www-data:www-data .env && chmod 600 .env`). 소유자가 웹 서버 사용자와 다른데 `0600` 으로 변경하면 웹 서버가 `.env` 를 읽지 못해 사이트가 응답하지 않습니다.", - "명령줄 사용자와 웹 서버 사용자가 다른 환경 (자체 구축 서버 등) 에서는 `0640` 권한 + 그룹을 웹 서버 그룹으로 설정 (`chgrp www-data .env && chmod 640 .env`) 하는 것이 안전한 절충안입니다. 웹 서버는 그룹 멤버로 읽고, 명령줄 사용자는 직접 수정할 수 있습니다." - ] - } - ] - }, - { - "version": "7.0.0-beta.6", - "date": "2026-05-14", - "categories": [ - { - "name": "Fixed", - "items": [ - "코어 업데이트 자동 롤백 시 신 버전이 추가한 신규 파일이 활성 디렉토리에 잔존하여 `BindingResolutionException` 등 부정합 부팅 실패가 발생하던 결함 수정. 백업 디렉토리에 신규 파일 manifest 를 기록하고 롤백 시 정확히 그 목록만 정리합니다. 사용자가 코어 영역에 직접 추가한 파일과 모든 symlink (`public/storage` 등) 는 보존됩니다. (#34 @bigmsg 님께서 제보해주셨습니다.)", - "자동 롤백 후 `bootstrap/cache` 잔존 PHP 캐시로 인한 추가 부팅 실패 차단 — 롤백 직후 캐시를 자동으로 정리합니다.", - "beta.5 이전 자동 롤백으로 인해 활성 디렉토리에 잔존했을 수 있는 ServiceProvider 후보를 beta.6 업그레이드 스텝이 진단 로그로 식별하여 운영자 수동 검토를 안내합니다. 자동 삭제하지 않으므로 사용자가 직접 추가한 ServiceProvider 는 보존됩니다.", - "업그레이드 스텝 단독 실행 명령(`core:execute-upgrade-steps`) 이 마이그레이션·코어 재동기화·버전 갱신·캐시 정리·번들 확장 일괄 업데이트를 자동으로 함께 수행하도록 보강. 자동 업데이트 중단 후 안내된 수동 명령을 그대로 실행하면 별도 보조 작업 없이 업그레이드가 완료됩니다.", - "인스톨러 Step 3 (PHP CLI / Composer / 자동 감지 / 코어 _pending 경로 검증) 의 프론트엔드 catch 분기에서 사용하는 다국어 키가 언어 파일에 정의되어 있지 않아, 서버 응답 실패 시 화면에 다국어 키 문자열이 그대로 노출되던 결함 수정.", - "인스톨러 다국어 파일에 중복 정의된 키 4건 정리. POST 전용 API 의 메서드 거부 메시지가 일반 문구로 덮여 표시되던 문제, 데이터베이스 연결 실패 로그에서 placeholder 가 치환되지 않던 문제, HTTPS 카드 라벨이 안내 문장으로 표시되던 문제가 함께 해결됩니다.", - "인스톨러 Step 3 의 PHP CLI 및 Composer 절대경로 검증이 공유 호스팅 환경(시놀로지 DSM 등 `open_basedir` 가 시스템 binary 영역을 차단하는 환경)과 Windows 환경에서 정상 경로임에도 거부되던 결함 수정. 셸 인자 escape 와 메타문자 차단은 유지하고, 파일 시스템 권한에 의존하던 사전 검사를 실제 실행 결과로 판정하도록 변경합니다. Windows 의 백슬래시 경로 구분자도 정상 입력으로 인식됩니다 (단, `C:\\Program Files\\...` 같은 공백 포함 디렉토리는 공백 없는 경로 또는 8.3 short path 사용 권장). (#33 @glitter-gim 님께서 제보해주셨습니다.)", - "코어 운영 단계의 composer 바이너리 자동 인식이 같은 `open_basedir` 환경에서 실패하여 모듈/플러그인 설치 시 vendor-bundle 모드만 사용 가능하던 결함 수정. composer 가 정상 설치되어 PATH 에 존재하는 환경에서도 인식되도록 검출 로직을 개선했습니다.", - "멀티 PHP 버전 환경(시놀로지 DSM Web Station, cPanel/Plesk multi-PHP 등) 에서 Composer 를 특정 PHP 인터프리터로 실행하려는 \"PHP 절대경로 + Composer 절대경로\" 공백 분리 입력 형식이 거부되던 결함 수정. 입력을 두 토큰으로 분리한 뒤 각 토큰을 개별 escape 처리하여 안전을 유지하면서 멀티 PHP 운영 시나리오를 지원합니다.", - "인스톨러 세션 쿠키가 일부 환경(비표준 포트 + dynamic DNS 도메인 + 브라우저 추적 보호 등) 에서 브라우저에 의해 차단되어 첫 화면 \"설치하기\" 클릭이 동작하지 않고 새로고침처럼 보이던 결함을 보완. 세션 쿠키 SameSite 정책을 Strict → Lax 로 완화하여 차단 케이스 자체를 줄이고, Step 0 진입 시 쿠키 round-trip 사전 진단을 수행하여 차단이 감지되면 사용자에게 회피 방법(다른 브라우저 / 쿠키 차단 해제 / IP·localhost 접속) 을 명시 안내합니다." - ] - }, - { - "name": "Added", - "items": [ - "`hotfix:rollback-stale-files` Artisan 커맨드 추가 — 자동 롤백 후 활성 디렉토리에 잔존할 수 있는 신 파일을 운영자가 명시 실행하여 진단/정리할 수 있는 단발성 회복 도구. 기본은 진단 모드 (실제 삭제 없음), `--prune` 옵션 시 확인 프롬프트 후 정리. symlink 와 보호 경로(`storage/`, `vendor/`, `.env*` 등) 는 자동 제외.", - "본인인증 메시지 정의 목록 API 응답에 `can_create` 권한 키 추가 — 운영자의 수정 권한 보유 여부에 따라 관리자 화면이 \"정의 추가\" 버튼을 정확히 활성화/비활성화할 수 있도록 보강." - ] - } - ] - }, - { - "version": "7.0.0-beta.5", - "date": "2026-05-12", - "categories": [ - { - "name": "Upgrade Notice", - "items": [ - "손실 항목 1: 운영자가 활성 디렉토리(`modules/{id}`, `plugins/{id}`, `templates/{id}`, `lang-packs/{id}`)에서 직접 수정한 코드", - "손실 항목 2: 외부 install 한 확장 (GitHub 등 `_bundled` 에 포함되지 않은 모듈/플러그인/템플릿/언어팩)", - "`modules/`", - "`plugins/`", - "`templates/`", - "`lang-packs/`", - "**사용자군 B — 정상 beta.4 사용자 (대다수)**: `php artisan core:update` 표준 절차. beta.5 의 자동 복구 스텝이 활성 확장 디렉토리 + lang-packs 정합성을 보장합니다.", - "**사용자군 A — beta.3 → beta.4 업그레이드 도중 `Call to undefined method` 류 fatal 후 stuck 한 사용자**: `.env` `APP_VERSION` 을 변경하지 마시고 그대로 `php artisan core:update --force` 를 재실행하면 됩니다. 디스크는 이미 beta.4 이므로 새 자식 프로세스가 beta.5 메모리로 부팅되어 남은 업그레이드 스텝이 멱등 적용됩니다. 단, 활성 디렉토리에서 직접 수정한 코드 또는 `_bundled` 에 없는 외부 확장은 자동 복구 대상이 아니므로 업그레이드 전 위 디렉토리 백업이 동일하게 권장됩니다.", - "**사용자군 C — beta.1 또는 beta.2 사용자**: `php artisan core:update` 1회 호출로 beta.5 까지 직접 업그레이드는 권장하지 않습니다. 중간 버전의 업그레이드 스텝이 이전 버전 메모리에 없는 신규 코드에 의존하여 fatal 위험이 있습니다. 다음 단계적 업그레이드를 권장합니다:", - "감지 조건: `public/storage` 가 일반 디렉토리 + `storage/app/public` 이 존재 (Laravel 표준 구성)", - "복구 동작: 손상된 디렉토리를 `public/storage.broken.YYYYMMDDHHMMSS` 로 보존 후 symlink 재생성", - "운영자 조치: 자동 복구 후 `.broken` 디렉토리의 콘텐츠를 검증한 뒤 수동 삭제 (false positive 방어를 위해 자동 삭제하지 않음)" - ] - }, - { - "name": "Added", - "items": [ - "본인인증 관리자 권한을 조회/수정으로 분리 — \"프로바이더 설정 조회\" / \"정책 조회\" 권한 신설로 단순 조회 권한만 부여받은 운영자도 환경설정 본인인증 화면에 접근 가능 (기존에는 수정 권한이 없으면 조회 화면도 403)", - "환경설정의 본인인증 정책/메시지 탭에 권한 기반 버튼 비활성화 적용 — 수정 권한이 없는 운영자에게는 \"정책 추가\" / \"수정\" / \"삭제\" / \"활성 토글\" 버튼이 자동으로 비활성화 (이커머스/게시판 본인인증 탭 동일)", - "코어 업데이트의 spawn 자식 프로세스 실패 시 abort/fallback 동작 모드 선택 (`G7_UPDATE_SPAWN_FAILURE_MODE`, 기본 `abort`) — 부모 메모리 stale 로 인한 upgrade step fatal 위험을 fail-fast 로 차단 (#28 @bigmsg 님께서 제보해주셨습니다.)", - "업그레이드 스텝에 \"버전별 데이터 스냅샷\" 규약 도입 — 멀티 버전 점프 시에도 사용자가 단계별로 업그레이드한 것과 동등한 결과를 보장하도록 각 스텝의 시드 카탈로그·적용 로직·단발성 핫픽스를 해당 버전 디렉토리로 격리. 외부 확장 작성자가 beta.5+ 신규 업그레이드 스텝 작성 시 적용 (상세: 업그레이드 스텝 가이드) (#29 @bigmsg 님께서 건의해주셨습니다.)", - "언어팩 일괄 활성화 검증 규칙에 동적 확장 훅 추가 (`core.language_packs.bulk_activate_validation_rules`) — 모듈/플러그인이 호스트 확장 재활성화 cascade 흐름에서 추가 검증 필드를 동적으로 등록 가능" - ] - }, - { - "name": "Changed", - "items": [ - "본인인증 관리자 권한 키 정리 — 기존 `core.admin.identity.manage` / `core.admin.identity.policies.manage` 가 각각 `core.admin.identity.providers.update` / `core.admin.identity.policies.update` 로 변경. beta.4 환경에서 기존 권한을 부여받은 운영자 역할은 업그레이드 시 자동으로 새 권한 키로 마이그레이션 (부여 시점/부여자/스코프 메타데이터 모두 보존). 외부 확장 또는 운영 도구가 옛 키 문자열을 하드코딩한 경우 새 키로 갱신 필요" - ] - }, - { - "name": "Fixed", - "items": [ - "코어 업데이트 시 활성 모듈/플러그인/템플릿/언어팩 디렉토리가 삭제되던 문제 수정 — beta.4 → beta.5 업그레이드 시 번들 원본에서 활성 디렉토리를 자동 복원하고, 외부 설치 확장은 업데이트 로그로 재설치 안내", - "코어 업데이트의 단계 전환 신호를 비정상 입력으로부터 차단하도록 검증 강화", - "본인인증 정책 응답의 생성/수정 일시가 사용자 타임존이 아닌 UTC ISO 문자열로 노출되던 문제 수정", - "백업/롤백 시 `public/storage` 등 symlink 가 target 디렉토리 내용으로 추적 복사되어 symlink 가 일반 디렉토리로 변질되던 문제 수정 (Linux 환경 한정 — Windows 는 권한 한계로 일반 디렉토리 폴백 + 수동 회복 안내 유지)", - "root/super user 환경에서 Composer 검증·설치가 비대화형 컨텍스트에서 실패하던 문제 수정 (코어 업데이트, 확장 의존성 설치, 인스톨러 포함) (#31 @glitter-gim 님께서 제보해주셨습니다.)", - "본인인증 활동 로그의 \"액션\" 열에 `identity.verify` / `identity.verify_failed` 가 번역되지 않은 키 그대로 노출되던 문제 수정 — 코어 액션 라벨 매핑이 누락되어 있던 부분을 추가", - "본인인증 단계에서 운영자가 지정한 본인인증 수단이 무시되고 항상 기본 수단(메일)으로 발행되던 문제 수정 — 가입 정책에 설정한 본인인증 수단과 API 요청에서 명시한 수단이 실제 인증 발행에 반영되도록 변경" - ] - } - ] - }, - { - "version": "7.0.0-beta.4", - "date": "2026-05-11", - "categories": [ - { - "name": "Changed", - "items": [ - "레이아웃 `navigate` 액션의 기본 스크롤 동작이 페이지 이동 후 최상단 이동으로 변경됨 — 일반 하이퍼링크 이동 UX 와 일치. 이전처럼 스크롤 위치를 유지하려면 `scroll: \"preserve\"` 명시 필요. 검색 필터/페이지네이션 등 위치 유지가 필요한 화면에서 회귀가 의심되면 해당 옵션 추가 (engine-v1.45.0)", - "언어팩 매니페스트(`language-pack.json`) 를 모듈/플러그인/템플릿 매니페스트와 동일한 필드 구조로 정렬 — 외부 작성자가 다른 확장과 동일한 표준으로 언어팩을 만들 수 있도록 개선", - "언어팩 매니페스트에 GitHub 저장소 필드 추가로 GitHub 기반 업데이트 경로 지원 (모듈/플러그인 매니페스트와 동일한 동작)", - "관리자 환경설정의 언어팩 카드와 상세 모달이 모듈/플러그인/템플릿 카드와 동일한 형식으로 다국어 이름 · 설명 · GitHub 링크를 노출하도록 변경" - ] - }, - { - "name": "Added", - "items": [ - "관리자 환경설정 → SEO 탭에 Generator 메타 태그 카드 추가 — 토글로 노출 여부를 제어하고 내용 입력으로 W3Techs 등 CMS 시장 점유율 측정 도구가 인식하는 `` 태그를 SEO 봇 페이지·SPA·관리자 셸 모두에 출력. 내용 미입력 시 \"GnuBoard7 {버전}\" 자동 적용, 운영자가 버전 노출을 원치 않으면 \"GnuBoard7\" 만 입력 가능 (#26 @Lastorder-DC 님께서 건의해주셨습니다.)", - "인스톨러 4단계 확장 선택 화면에 \"언어팩\" 카드 신설 — 번들 언어팩을 locale 별 서브헤딩으로 노출하고, 사용자가 모듈/플러그인/템플릿을 선택하면 그 확장에 종속된 번들 언어팩 카드가 즉시 활성화. 종속 확장을 해제하면 그 언어팩 카드는 자동으로 비활성화 + 선택 해제", - "5단계 설치 진행 시 모든 확장의 install/activate 가 끝난 뒤 선택된 번들 언어팩을 일괄 설치 — 1건 실패는 best-effort 처리로 전체 설치를 중단하지 않음", - "코어/확장당 다수 locale(일본어 + 중국어 등)이 동시에 번들된 시나리오 대응 — 각 locale 은 독립된 서브헤딩 + 카드로 노출", - "인스톨러 시작 시 SSE 호환성을 사전 점검하여 환경에 맞는 모드(SSE 또는 폴링)로 단방향 진입 — 워커 동시 실행 race(테이블 / unique 키 중복 에러) 차단", - "SSE 비호환 환경 사용자에게 폴링 모드 진행 여부를 명시적 다이얼로그로 확인 후 시작", - "활동 로그의 액션 라벨이 모듈/플러그인 자체 다국어 파일에서 우선 해석되도록 변경 — 그동안 코어에 일괄 등록되어야 했던 모듈 origin 라벨(이커머스 주문·상품·마일리지, 게시판 게시물·댓글·신고, 페이지 등) 이 각 영역으로 이전되어 모듈/플러그인이 자기 도메인 라벨을 자기 영역에서 자기설명. 미정의 시 코어 라벨로 자동 fallback 하여 회귀 없음", - "모듈/플러그인이 발화하는 활동 로그가 호출자/대상 모델의 영역을 자동으로 인식하여 자기 다국어 파일로 라우팅 — 새 활동 로그를 추가할 때 별도 분기 코드 없이 자기 lang 만 채우면 정합", - "확장 entity 시더가 활성 언어팩의 다국어 데이터를 자동 머지하도록 다국어 시더 인터페이스 도입 — 신규 시더 작성 시 회귀를 자동 차단", - "번들 일본어 언어팩 빌드 스크립트가 시더 메타데이터를 일관된 경로로 조회하도록 단순화", - "다국어 라벨 표시 시 활성 언어팩의 lang key fallback 을 자동으로 처리하는 다국어 라벨 보강 helper 추가 — Provider/Registry 등록 페이로드(알림 채널·결제 PG 등) 와 settings JSON 다국어 데이터를 단일 시그니처로 처리", - "알림 채널(`config/notification.php` 의 `default_channels`) 의 라벨이 활성 언어팩(일본어 등) 으로 자동 보강되도록 변경 — 일본어 활성 시 한국어 fallback 으로 노출되던 회귀 차단", - "토스페이먼츠 PG 프로바이더 등록 페이로드가 lang key 기반으로 라벨을 선언하도록 변경 — 활성 언어팩으로 자동 보강", - "Provider/Registry 등록 페이로드가 다국어 JSON(`['ko' => ..., 'en' => ...]`) 을 직접 보유하지 않도록 audit 룰 신설 (회귀 자동 차단)", - "settings JSON 다국어 entry 의 식별 키(code/id/key) 보유 검증 audit 룰 신설 (lang pack fallback 키 조립 가능성 보장)", - "프론트엔드 `$localized()` 표현식이 두 번째 인수로 lang key fallback 을 받도록 확장 — 활성 로케일 라벨 부재 시 모듈/플러그인 언어팩의 키로 자동 보강", - "이커머스 환경설정 카탈로그(배송 가능 국가 / 통화 / 결제수단) 표시 시 활성 언어팩의 라벨이 자동 적용되도록 변경 — 일본어 활성 시 카탈로그 라벨이 한국어로 노출되던 회귀 차단 (다음 일본어 언어팩 빌드 시 자동 적용)", - "audit 룰이 `getDefault*Channels/Providers/Methods` 등 registry 기본값 반환 메서드의 다국어 JSON 직접 보유도 감지하도록 강화", - "환경설정 카탈로그(결제수단·통화·배송 가능 국가 등) 의 다국어 라벨이 카탈로그 빌드 시점에 활성 언어팩으로 자동 보강되도록 변경 — 사용자 체크아웃 + 관리자 환경설정 양쪽 모두 일본어 등 활성 시 한국어 fallback 으로 노출되던 회귀 차단", - "모듈/플러그인 개발자가 카탈로그를 추가할 때 누락하지 않도록 audit 룰이 helper 호출 누락 검출", - "[docs/backend/settings-multilingual-enrichment.md](docs/backend/settings-multilingual-enrichment.md) 에 단순 helper 사용 패턴 문서화", - "manifest `hidden: true` 플래그 추가 — 관리자 UI 목록에서 학습용/내부용 확장을 숨김 (CLI 는 정상 노출). 관리자 UI 에 슈퍼관리자 전용 \"숨김 포함\" 토글, artisan `module:list` / `plugin:list` / `template:list` 에 `--hidden` 플래그, 관리자 API `/api/admin/{modules,plugins,templates}` 에 `include_hidden` 쿼리 파라미터 지원", - "학습용 최소 샘플 확장 4종 번들 추가", - "모듈: `gnuboard7-hello_module` (Memo CRUD + 훅 발행 시연)", - "플러그인: `gnuboard7-hello_plugin` (Action/Filter 훅 구독 시연)", - "Admin 템플릿: `gnuboard7-hello_admin_template` (Basic 컴포넌트 최소 셋)", - "User 템플릿: `gnuboard7-hello_user_template` (홈 + Memo 리스트 연동)", - "모듈/플러그인/템플릿 정보 모달에 \"지원 언어\" 섹션 추가 — 코어/번들/사용자설치 출처 배지로 한눈에 확인", - "모듈/플러그인/템플릿 인스톨러에 의존 확장 + 동반 번들 언어팩 동반선택 UI 추가 — 미선택 의존성에 종속된 언어팩은 자동 비활성화", - "인스톨러 요구사항 검증 단계에 언어팩 디렉토리 쓰기 권한 점검 + 권한 부여 안내 추가", - "사용자 수동 비활성화와 코어 버전 호환성으로 인한 자동 비활성화를 DB 수준에서 구분 — 자동 비활성화된 확장만 재호환 감지/원클릭 복구 대상이 되도록 분리 (#18 @laelbe 님께서 제보해주셨습니다.)", - "코어 업그레이드 후 자동 비활성화 확장이 다시 호환되면 관리자 대시보드에 \"다시 활성화\" 알림 표시 + 원클릭 복구 버튼 제공 (자동 재활성화는 하지 않음 — 운영자가 명시적으로 복구)", - "모듈/플러그인/템플릿 목록 화면 상단에 자동 비활성화 확장 안내 배너 추가 (코어 업그레이드 가이드 링크 동반)", - "업데이트 모달에 코어 버전 호환성 안내 + \"위험을 이해하고 강제로 진행\" 체크박스 추가 — 운영자가 위험을 인지하면 비호환 확장도 강제 설치 가능", - "관리자 대시보드 \"시스템 알림\" 카드 노출 — 코어 호환성 자동 비활성화/재호환 알림이 분기 렌더되며 개별 dismiss 지원", - "코어 업데이트 마무리 단계에서 sudo 환경 결함(파일시스템 ACL · immutable 비트 · NFS 권한 거부 등) 으로 일부 경로의 소유권/그룹 쓰기 권한을 정상화하지 못한 경우 즉시 콘솔에 실패 경로 + 운영자 수동 복구 명령(`sudo chown -R …` / `sudo chmod -R g+w …`) 안내 — 이전에는 silent fail 로 묻혀 운영자가 후속 권한 거부 발생 후에야 인지하던 문제 해소", - "권한 기반 수신자 타입 추가 — `permission` 타입으로 특정 권한을 가진 모든 사용자에게 알림 발송 가능 (예: 게시판 신고 알림은 신고 관리 권한자에게 자동 발송)", - "모듈 환경설정에서 알림이 비활성화된 경우 발송 자체를 사전 차단하는 정책 게이트 도입 — 이전에는 수신자 해석 단계가 정책을 우회하여 발송되던 문제 해소", - "게시판 환경설정 → 신고 정책 탭에 신고 알림 채널 선택 UI 추가 (이메일 / 사이트 알림 다중 선택)", - "모듈/플러그인이 자기가 발송하는 알림 정의를 manifest 에서 직접 선언하도록 통일 — 활성화/업데이트 시 운영자 편집값을 보존하면서 자동 등록되고, 제거 시 함께 정리됨. 권한·메뉴·본인인증과 동일한 declarative getter 패턴", - "코어 기본 알림(회원가입 환영·비밀번호 재설정·비밀번호 변경) 의 다국어 제목/본문/변수/채널 정의를 `config/core.php` 로 통합 — 운영자가 코드 수정 없이도 향후 표면 변경을 추적할 수 있는 단일 소스 확보", - "본인인증(IdentityVerification) 인프라 도입 — 코어에 범용 IDV 프로바이더 계약을 마련하고, 기본 메일 프로바이더를 내장. 플러그인이 KCP·이니시스·SMS 등 다른 경로를 동일 계약으로 붙일 수 있도록 확장점 제공", - "외부 본인인증 provider (KCP·PortOne·토스인증·Stripe Identity 등) 가 G7 표준 Extension Point 패턴으로 자기 SDK UI 를 주입할 수 있는 슬롯 도입", - "비동기 검증 흐름을 위한 백엔드 폴링/콜백 엔드포인트 추가 — `GET /api/identity/challenges/{id}` (상태 폴링), `POST /api/identity/callback/{providerId}` (외부 redirect 콜백 수신)", - "본인인증 정책 시스템 신설 — 회원가입·비밀번호 재설정·민감 작업에 적용되는 모든 본인인증 시점을 라우트/훅 단위로 선언형 정책으로 통합 관리. 관리자 화면에서 정책 활성/유예 시간/프로바이더/실패 모드/단계(가입 제출 전 vs 가입 후 활성화 전)·적용 대상(self/admin/both)을 조정할 수 있으며, 운영자 수정값은 업데이트 재시딩 시 보존됨", - "본인인증 정책의 enable 토글이 라우트 코드 수정 없이 즉시 적용 — 모든 API 라우트가 정책 DB 와 자동 매칭되어 운영자가 admin UI 에서 정책을 켜는 즉시 본인인증이 강제됨. 코어/모듈/플러그인 어떤 라우트의 응답 처리 패턴에서도 본인인증 흐름이 일관되게 모달까지 도달하도록 처리", - "회원가입 단계 정책 2종 시드 — 가입 제출 전 동기 검증, 가입 후 활성화 전 비동기 challenge (기본값 비활성, 운영자 opt-in)", - "본인인증 정책이 활성화된 모든 강제 지점(회원가입·비밀번호 재설정·민감 작업·게시판/이커머스 정책 등)에서 사용자/관리자 화면에 동일한 모달 UX 가 자동으로 표시되도록 코어 인터셉터와 공통 모달 인프라 도입", - "전역 프론트엔드 인터셉트 — 서버가 HTTP 428 본인인증 요구 응답을 반환하면 자동으로 인증 모달을 띄우고 사용자가 인증에 성공하면 원 요청을 자동 재실행", - "본인인증 가드가 모듈/플러그인이 선언한 훅에도 자동 적용되도록 동적 구독 도입 — 결제 직전·민감 액션 직전 등 도메인 특화 가드를 운영자 토글 한 번으로 활성화", - "관리자 화면에 본인인증 정책 관리 페이지 추가 (정책 목록 DataGrid + 편집)", - "관리자 화면에 본인인증 이력 페이지 추가 — 알림 발송 이력과 동일한 수준의 UI 제공: 인증 수단(Provider) 탭(활성화된 프로바이더에 따라 자동 갱신), 통합 검색(자동 감지/사용자 ID/대상 식별자/IP/정책 키), 상태·인증 목적·채널·발생 유형 다중선택 OR 필터, 날짜 범위 + 고급 검색(발생 유형·출처·정책 키) 토글 영역, 정렬·페이지 사이즈 선택, 행 펼침으로 인라인 상세 표시, 모바일 반응형, 사용자 타임존 기준 시각, 보관주기(180일) 일괄 파기, 인증 수단/발생 위치 다국어 라벨 표시", - "본인인증 목적(purpose) 의 출처(source) 추적 — 코어/모듈/플러그인이 선언한 목적을 구분해 환경설정 화면에서 분리 표시하며, 코어 정책 화면 상단에 \"코어가 제공하는 본인인증 목적\" 칩 섹션(목적 코드/라벨 + 설명·허용 채널 툴팁) 추가", - "모듈 환경설정의 본인인증 정책 탭을 코어 정책 관리 화면과 동일한 UI/UX 로 통일 — 검색·필터(강제 시점/출처)·페이지네이션·정책 추가/편집 모달 직접 호출(코어 화면으로 이동 X)·이 모듈이 등록한 본인인증 목적 칩 섹션(없으면 안내 메시지). 정책 목록의 \"인증 목적\" 컬럼이 코드명 대신 사람이 읽을 수 있는 라벨로 표시", - "모듈/플러그인이 자기 컨텍스트의 본인인증 정책·목적·메시지 정의를 manifest 에서 직접 선언하도록 통일 — `module.php::getIdentityPolicies()` / `getIdentityPurposes()` / `getIdentityMessages()` 선언만으로 코어 정책 관리에 자동 연동되며, 활성화/업데이트 시 운영자 편집값을 보존하면서 자동 등록·정리됨. 권한·메뉴·알림과 동일한 declarative getter 패턴", - "본인인증 정책/이력 목록 API 가 source 컨텍스트별 필터를 받아 모듈 환경설정 탭이 자기 정책/이력만 조회 가능하며, 운영자 정의 정책을 특정 모듈/플러그인 컨텍스트에 귀속시켜 추가할 수 있도록 source_identifier 입력 허용 (`admin` / `module:{id}` / `plugin:{id}`)", - "본인인증 메일 메시지 템플릿 시스템 도입 — 프로바이더와 (목적/정책)별로 다국어 제목/본문을 개별 정의 가능하며, 메시지 발송 시 정책 → 목적 → 프로바이더 기본값 순서로 fallback 해석. 회원가입/계정 변경/중요 작업/비밀번호 재설정 + 프로바이더 기본값 등 5종 메일 템플릿이 한국어/영어로 시드되어 즉시 발송 가능", - "본인인증 challenge 발급/검증/취소 라우트에 권한 미들웨어 + scope=self 가드 적용 — 게스트는 `core.identity.{request,verify,cancel}` 권한으로 비로그인 가입(Mode B) 흐름 진입, 로그인 사용자는 본인 challenge 만 다룰 수 있으며 관리자는 임의 challenge 도 처리 가능. 모달 취소 시 서버 cancel API 를 호출해 challenge 가 audit log 에 cancelled 상태로 즉시 기록되도록 정합화", - "환경설정 → 본인인증 탭에 \"메시지 템플릿\" 서브탭 신설 — 알림 템플릿 관리와 동일한 UX (채널 서브탭·페이지당 항목 수 셀렉터·카드 펼침 본문 미리보기·활성/기본 배지·페이지네이션) 로 정의 목록·활성 토글·다국어 편집(변수 가이드 + 기본값 복원) 제공. 운영자가 추가한 정책에 전용 메일 메시지 정의를 화면에서 직접 등록·삭제 가능 (시드 기본값 보호 + 정책 키 매칭 검증). 외부 본인인증 프로바이더 플러그인이 자기 메시지 기본값을 코어 복원 로직에 기여할 수 있는 필터 훅 노출", - "알림 발송 이력 / 본인인증 이력 샘플 시더를 코어/모듈별로 분리 — 코어 시더는 코어 정의·정책만, 각 모듈 시더는 자기 영역만 채우도록 영역 격리. 모듈은 `module:seed {id} --sample` 으로 자기 영역 이력만 독립 생성 가능. 샘플 데이터는 실제 등록된 사용자·정의·정책·프로바이더 기반으로 생성되어 운영 데이터와 동일한 스키마/분포 유지", - "모듈/플러그인 제거 시 코어 공유 테이블에 적재된 해당 확장의 데이터(권한·관리자 메뉴·알림 정의·본인인증 정책·본인인증 메시지 정의·본인인증 목적) 가 함께 정리되며, 제거 모달의 \"삭제될 데이터\" 에도 항목별 건수가 표시되도록 개선", - "IDV 도메인 분류 데이터(목적·채널·트리거 출처·정책 범위·정책 실패 모드·정책 적용 대상·정책 출처·메시지 스코프) 8종을 PHP Backed Enum 으로 정의하여 정책 등록/수정 시 일관된 검증 적용", - "인스톨러 설치 완료 화면에 설치된 코어 버전 표시 — 사용자가 어떤 버전이 설치되었는지 즉시 확인할 수 있도록 개선", - "새 언어(일본어/중국어 등)를 코어 수정 없이 추가할 수 있는 언어팩(Language Pack) 시스템 도입", - "ZIP 업로드 또는 GitHub URL로 언어팩 설치/제거/활성화 지원", - "동일 언어 슬롯에 여러 벤더의 언어팩 공존 가능 — 라디오 전환으로 즉시 활성 변경", - "코어/모듈/플러그인/템플릿 별도 적용 — 모듈 언어팩은 해당 코어 언어팩이 활성일 때만 활성화 가능", - "관리자 메뉴: 환경설정 > 언어팩 관리(통합) + 모듈/플러그인/템플릿별 진입점 추가", - "사용자가 직접 수정한 다국어 키는 언어팩이 덮어쓰지 않도록 보존 정책 적용", - "보안: 언어 번역 외의 PHP 실행 코드 포함 시 설치 차단", - "언어팩 관리 화면을 모듈 관리와 동일 수준의 운영 도구로 보강 — 검색(식별자/벤더/언어), 업데이트 확인, 캐시 갱신, 다중 선택 일괄 제거 지원", - "활성/비활성 상태를 토글 스위치로 일원화 (보호된 팩은 비활성화된 토글로 표시)", - "업데이트 가능 항목에 \"업데이트 가능\" 배지와 행 단위 업데이트 실행 버튼 노출", - "정보 모달을 모듈 정보 모달과 동일한 4섹션 구조(기본정보 / 호환성 / 소스 정보 / 변경로그)로 제공하며, CHANGELOG.md 를 자동 파싱하여 버전별 카테고리로 표시", - "설치 모달에서 ZIP 업로드 전에 manifest 와 검증 결과를 사전 확인할 수 있는 미리보기 제공", - "모듈/플러그인/템플릿별 언어팩 페이지 진입 시 대상 확장 안내 배너와 환경설정 탭으로 회귀하는 링크 노출", - "언어팩 업데이트 권한을 별도 권한 키로 분리하여 설치/관리 권한과 독립적으로 부여 가능", - "언어팩 운영 풀 패리티 — 모듈/플러그인/템플릿 관리와 동일한 안전 장치/확장성 적용", - "공식 일본어(ja) 번들 언어팩 12종 추가 — 코어, 주요 모듈(전자상거래/게시판/페이지), 주요 플러그인(CKEditor5/마케팅/토스페이먼츠), 기본 템플릿(admin/user)을 일본어로 즉시 사용 가능", - "언어팩 관리 화면에서 번들 언어팩을 모듈/플러그인 관리와 동일하게 \"미설치\" 상태로 노출 — 행별 \"설치\" 버튼으로 즉시 설치 가능", - "언어팩 목록 필터에 \"미설치 (번들)\" 상태 옵션 추가", - "본인인증 메일 메시지 정의의 다국어 키를 언어팩으로 주입할 수 있도록 확장 — 코어 수정 없이 언어팩 ZIP 만으로 IDV 메일 본문/제목을 다국어화 가능", - "모듈/플러그인이 선언한 알림/본인인증 메시지 정의의 다국어 키도 언어팩으로 주입되도록 정합 — 이전에는 hook 발화 누락으로 코어 정의에만 적용되던 동작 정상화", - "업데이트 시 자동 백업 + 실패 시 직전 버전으로 자동 복구", - "동시성 가드 — 진행 중인 작업 동안 활성/비활성/제거/재업데이트 진입 차단", - "번들 디렉토리(`lang-packs/_bundled/{identifier}`) 에서 외부 다운로드 없이 (재)설치하는 경로 추가 — 코어 언어팩 복구/재배포 단순화", - "라이프사이클 훅 명명을 모듈/플러그인/템플릿과 통일 — `core.language_packs.{installed|updated|uninstalled|activated|deactivated}` 발행 (확장 가능성 확대)", - "Artisan 커맨드 신규 — `language-pack:list`, `language-pack:install`, `language-pack:update`, `language-pack:uninstall`", - "코어와 번들 확장(모듈/플러그인/템플릿)에 내장된 한국어/영어를 가상 보호 언어팩으로 자동 노출 — 별도 설치 없이 언어팩 관리자에서 항상 활성/보호 상태로 확인 가능, 수정/제거 차단", - "호스트 확장 비활성화 시 그에 종속된 언어팩이 함께 비활성화되며, 재활성화 시 \"다음 언어팩도 활성화하시겠습니까\" 모달로 사용자 의사 확인 후 일괄 활성화", - "여러 언어팩을 한 번에 활성화하는 `POST /api/admin/language-packs/bulk-activate` API 추가 (의존성/버전 호환성 자동 검사)", - "언어팩 활성화 시 의존성 + 호스트 확장 버전 호환성 검사 자동 수행 — 호스트 확장이 비활성/미설치이거나 버전 미달이면 활성화 차단", - "언어팩 업데이트 우선순위를 모듈/플러그인 패턴으로 정합화 — GitHub 1순위 + bundled 폴백, 강제 업데이트 시 bundled 우선", - "언어팩 상세 모달에서 코어/번들 확장 내장 항목 클릭 시 \"별도 언어팩이 아닌 내장 번역\" 안내 배너 노출", - "모듈/플러그인/템플릿 상세 모달의 닫기 버튼이 콘텐츠 길이와 무관하게 모달 하단에 항상 보이도록 sticky 처리", - "관리자 환경설정 > SEO 탭의 Sitemap 카드에서 sitemap 을 즉시 재생성하고 마지막 생성 시각을 확인할 수 있는 \"지금 생성\" 버튼 추가 — 큐 드라이버와 무관하게 동기 실행되며 결과를 즉시 표시", - "봇 감지 엔진을 `jaybizzle/crawler-detect` 라이브러리로 교체. 기본 약 1,000종의 봇(검색엔진·링크 미리보기·AI 검색 등)이 자동 감지됨 — 링크를 슬랙·페이스북·LinkedIn·트위터·디스코드·텔레그램 등에 붙여넣으면 제목·설명·이미지 미리보기가 즉시 동작", - "라이브러리가 놓치는 봇 3종(`kakaotalk-scrap`·`Meta-ExternalAgent`·`ChatGPT-User`) 을 G7 보강 패턴으로 기본 포함", - "봇 감지 확장 훅 `core.seo.resolve_is_bot` 신설 — 플러그인이 IP 범위 검증·역방향 DNS·Cloudflare 봇 점수 등을 주입할 수 있는 슬롯", - "\"봇 라이브러리 사용\" 관리자 토글 추가 (기본 on, 비활성 시 운영자 커스텀 목록만 사용하는 레거시 모드)", - "슬랙·페이스북 링크 미리보기에 이미지·카드가 표시되도록 OG 보강 태그를 자동 출력하도록 개선 — og:site_name, og:image:width, og:image:height, og:image:secure_url, og:image:type, og:image:alt, og:locale 추가", - "Twitter 카드 메타태그(twitter:card / twitter:site / twitter:title / twitter:image 등) 출력 신설 — 슬랙 unfurl 폴백 경로 정상화", - "운영자 환경설정 SEO 탭에 \"OG / Twitter 카드 기본값\" 카드 추가 — 사이트 이름·이미지 기본 가로/세로·Twitter 카드 타입·Twitter 사이트 핸들 5개 입력", - "모듈·플러그인이 자기 도메인의 OG/Twitter/JSON-LD 를 직접 선언하도록 SEO declaration API 신설 — 이커머스 상품(Product/Offer/AggregateRating)·게시판 게시글(Article) 등 도메인 스키마가 레이아웃에서 확장 코드로 owned 되어 데이터에 따라 정확한 부속 태그 생성", - "SEO 메타 확장 훅을 분기별·통합 모두 제공 — OG·Twitter·구조화 데이터 각각의 hook 슬롯과 통합 hook 모두 지원하여 확장이 원하는 단계에서 선택 변경 가능", - "모듈 설정 타이틀 템플릿에서 변수가 비어있을 때 인접 구분자(- – · |) 가 자동 정리되도록 개선 — 옵셔널 그룹 `[ ... ]` 표기도 지원하여 페이지별 구성 명시 가능" - ] - }, - { - "name": "Changed", - "items": [ - "콘솔 confirm 입력 처리 통일 — yes/y, no/n 외 입력 시 안내 메시지 출력 후 재질문, empty 입력 시 default 사용. 코어 업데이트·매니저 커맨드(module/plugin/template install·update·uninstall)·설정 마이그레이션의 모든 yes/no 프롬프트에 동일 규칙 적용 (#15 @laelbe 님께서 건의해주셨습니다.)", - "비밀번호 재설정 정책 기본값을 비활성으로 변경 — 본인인증 인프라가 미구성된 사이트에서도 기본 동작이 영향받지 않도록 운영자 opt-in 으로 전환", - "본인인증 정책의 인증 조건(`conditions`) 운영자 편집 허용 — 회원가입 단계 등 정책 조건을 코드 수정 없이 관리자 화면에서 조정 가능. 모듈 업데이트 시 운영자 수정값 보존", - "토큰 만료 등으로 권한 없는 레이아웃 진입 시 \"페이지 로딩 실패\" 에러 화면 대신 로그인 페이지로 자동 이동 — 로그인 화면에서 \"세션이 만료되었습니다. 다시 로그인해 주세요.\" 토스트로 사용자에게 안내. 템플릿이 자체 로그인 경로를 사용하는 경우 부트스트랩에서 인증 설정을 커스터마이즈할 수 있는 공개 API 도 함께 제공 (#19 @abc101 님께서 건의해주셨습니다.)", - "템플릿 다국어 데이터 로딩 시 활성 언어팩의 다국어가 가장 높은 우선순위로 병합되도록 변경", - "권한·역할·메뉴·알림 등 코어 기본 데이터와 배송유형·클레임 사유·게시판 유형 등 모듈 기본 데이터에 활성 언어팩의 다국어가 자동 반영되도록 개선", - "사용자 수정 보존(user_overrides) 정책을 다국어 JSON 컬럼은 sub-key 단위(`name.ko` 등) 로 기록하도록 개선 — 운영자가 한 언어 라벨만 수정해도 그 언어만 보존되며, 신규 활성 언어팩(예: 일본어 추가) 의 라벨은 자동 동기화됨. 기존 컬럼 단위(`name`) 기록은 업그레이드 시 활성 locale dot-path 로 자동 변환", - "언어팩 활성/비활성 시점에 영향받는 모듈/플러그인의 entity 시더가 자동 재실행되어 신규 언어 라벨이 즉시 DB 에 반영되도록 라이프사이클 통합 — scope 별 라우팅 (코어 언어팩 → 모든 활성 확장, 모듈 언어팩 → 해당 모듈만, 플러그인 언어팩 → 해당 플러그인만)", - "환경설정 → SEO → \"추가 봇 패턴\" 필드의 역할 변경 — 기존에는 유일한 봇 매칭 소스(기본 5종)였으나, 이제는 라이브러리가 놓치는 조직별 커스텀 봇만 추가하는 보강 레이어로 동작. 기존 설치의 운영자 커스텀 값은 모두 보존되며, 신규 설치 기본값은 jaybizzle 미커버 3종으로 변경" - ] - }, - { - "name": "Security", - "items": [ - "회원가입·비밀번호 재설정 라우트에 본인인증 정책 강제 미들웨어 부착 — 정책이 활성화된 경우 미인증 요청을 라우트 단계에서 차단 (KISA 측에서 제보해주셨습니다 — KVE-2026-0851)", - "플러그인이 정책 해석 필터 훅에서 잘못된 타입을 반환해도 원본 정책이 유지되도록 우회 차단 강화 (KISA 측에서 제보해주셨습니다 — KVE-2026-0851)", - "웹 인스톨러의 Composer/PHP 바이너리 경로 검증에서 사용자 입력이 그대로 shell 명령으로 실행될 수 있던 문제 수정 — 입력은 실행 가능한 단일 파일 경로로만 허용하고 모든 분기에서 인자 escape 강제. 설치 워커가 동일한 입력을 사용하던 내부 헬퍼도 같은 정책으로 정렬 (KISA 측에서 제보해주셨습니다 — KVE-2026-0851)", - "설치 단계 4 의 확장 기능 선택 API 가 사용자가 보낸 모듈/플러그인/템플릿/언어팩 식별자에 셸 메타문자 검증을 적용하도록 강화 — 부적절한 식별자는 400 응답으로 거부되어 이후 설치 명령에 도달하지 않음 (KISA 측에서 제보해주셨습니다 — KVE-2026-0851)", - "인스톨러의 코어 업데이트 _pending 경로 검증이 `..` 등 부모 디렉토리 우회 시도를 거부하고 응답 메시지를 단일화하여 임의 디렉토리 enumeration 신호 차단 (KISA 측에서 제보해주셨습니다 — KVE-2026-0851)", - "설치 시 `.env` 작성 헬퍼가 입력값에 포함된 개행 문자를 제거하도록 변경 — DB 비밀번호 등 사용자 입력으로 새로운 환경 변수 라인이 주입되는 시나리오 차단 (KISA 측에서 제보해주셨습니다 — KVE-2026-0851)", - "데이터베이스 연결 정보의 host/port/database 값에 DSN 키-밸류 구분자(`;`, `=`) 또는 NUL/CRLF 가 포함되면 연결을 거부하도록 추가 검증 (KISA 측에서 제보해주셨습니다 — KVE-2026-0851)", - "설치가 완료된 시스템에서 `public/install/` 하위 모든 엔드포인트가 비즈니스 로직 진입 전 HTTP 410 으로 차단되도록 공통 가드 도입 — 운영 환경에서 인스톨러 노출형 결함의 공격 표면 제거. 운영자가 인스톨러를 다시 사용해야 하는 경우 설치 완료 마커(`storage/app/g7_installed`) 와 `.env` 의 `INSTALLER_COMPLETED` 를 모두 제거 (KISA 측에서 제보해주셨습니다 — KVE-2026-0851)" - ] - }, - { - "name": "Fixed", - "items": [ - "코어 업데이트 후 일부 환경에서 모듈/플러그인이 저장한 사용자 데이터(상품 이미지·첨부파일 등)에 PHP 가 접근하지 못해 \"찾을 수 없음\" 오류가 발생하던 문제 수정 — 업데이트 종료 시점 권한 복원 범위를 PHP 쓰기 영역(storage/logs·storage/framework·bootstrap/cache·storage/app/core_pending)으로 한정하고 항목별 정확 복원으로 정합화. 이전 버전에서 본 릴리즈로 업그레이드한 환경에서는 업그레이드 시점에 storage/app 디렉토리/파일 권한을 PHP 가 접근 가능한 형태로 자동 정상화함. 향후 업데이트에서는 사용자 데이터 디렉토리에 자동 생성되는 보존 마커로 권한이 영구 보호됨", - "PHP 8.5 환경에서 모든 페이지 응답이 손상되어 Firefox 에서는 \"Content Encoding Error\", Edge/Chromium 에서는 빈 화면으로 표시되던 문제 수정", - "`php artisan serve` 환경에서 인스톨러 진행 중 .env 파일이 변경되면 개발 서버가 워커를 재시작하면서 설치 단계가 중도에 끊기던 문제 수정 — 설치 진행 중에는 .env 를 건드리지 않고 완료 화면 노출 후 한 번에 반영하도록 변경", - "관리자 환경설정 \"시스템 사양\" 카드의 메모리 항목이 서버 물리 메모리가 아닌 현재 PHP 프로세스가 사용 중인 메모리(수 MB 단위)로 표시되던 문제 수정 — 디스크 사용량과 동일한 형식(사용량/전체/백분율)으로 실제 서버 RAM 을 표시하도록 개선", - "관리자 환경설정 \"시스템 사양\" 카드의 CPU 항목이 Windows 11 / Windows Server 2025 에서 \"operable program or batch file.\" 로 표시되던 문제 수정 — 해당 OS 에서 제거된 wmic 의존을 걷어내고 PowerShell 기반 조회로 전환, 구형 Windows 환경에서는 기존 방식으로 자동 폴백", - "관리자 환경설정 \"정보\" 탭을 한 번 조회한 뒤 다른 탭으로 전환할 때마다 수 초 지연이 반복되던 문제 수정 — 시스템 정보 조회 결과를 1시간 캐싱하여 탭 전환 시 대기 시간 제거 (시스템 캐시 초기화 시 함께 무효화)", - "큐 워커가 훅 페이로드의 enum 값(주문 상태 등)을 복원하지 못해 활동 로그·알림 등 일부 후속 처리가 실패하던 문제 수정", - "항목 삭제 후 큐 워커가 처리하는 후속 훅에서 대상 데이터를 복원하지 못해 처리가 중단되던 문제 수정 — 소프트 삭제 페이지·첨부파일, 하드 삭제 주문 옵션 등 포함", - "페이지 키워드 검색 결과의 전체 건수가 발행된 모든 페이지 수로 부풀려지던 문제 수정", - "사용자가 댓글을 단 게시글 활동 조회 시 500 에러가 발생하던 문제 수정", - "사용자 활동 통계에 삭제된 게시글의 댓글 수가 포함되던 문제 수정", - "일부 주문에서 옵션 정보 직렬화 시 500 에러가 발생하던 문제 수정", - "상품 옵션 삭제 검증 시 런타임 에러가 발생하던 문제 수정", - "설치 직후 또는 활성 모듈 디렉토리 부재 시 이커머스 환경설정 기본값이 비어있던 문제 수정", - "검색 가능 드롭다운(SearchableDropdown)에서 빠른 모달 전환 시 race condition 가능성 차단", - "반응형 설정과 반복 렌더링이 결합된 레이아웃에서 무한 재귀가 발생할 수 있던 엔진 결함 수정 (engine-v1.43.1)", - "슬롯 치환 시 텍스트 노드를 컴포넌트로 가정하여 발생할 수 있던 레이아웃 렌더링 오류 차단", - "인스톨러 실행 시 보안 키 생성 단계에서 개발용 패키지 ServiceProvider 를 찾지 못해 설치가 중단되던 문제 수정 — 이전 환경에서 남은 컴파일 캐시를 vendor 교체 직후와 보안 키 생성 직전에 자동 정리하도록 개선", - "폴링 모드 인스톨러가 큰 확장(레이아웃·테스트 수천 파일) 설치 도중 멈추던 문제 수정 — 명령 출력을 파일 기반으로 처리하여 OS 파이프 버퍼 한도와 무관하게 동작", - "PHP 8.5 + Apache + mod_fcgid 환경에서 폴링 모드 진행 상황이 실시간 반영되지 않고 설치 완료 시점에 일괄 표시되던 문제 수정 — 어느 모드로도 진행 상황이 즉시 표시되도록 보정. Apache 환경별 권장 설정은 INSTALL.md 와 시스템 요구사항 문서에 명시", - "설치 완료 후 임시 파일이 자동 정리되지 않던 문제 수정", - "알림 템플릿 편집 모달의 입력 필드에서 글자를 입력할 때마다 화면이 심하게 버벅이던 문제 수정", - "슈퍼관리자가 다른 관리자 계정을 삭제할 수 없던 문제 수정 — 관리자 계정 삭제 가능 여부를 역할/권한/스코프 설정에 따라 판단하도록 개선 (슈퍼관리자 본인 보호는 유지)", - "`seo:generate-sitemap` 커맨드가 큐 드라이버 설정과 무관하게 항상 \"큐에 디스패치\" 안내를 출력하던 문제 수정 — 동기 드라이버에서는 즉시 생성으로 동작하고 그에 맞는 안내를 표시하도록 변경", - "관리자 템플릿을 활성화해도 즉시 반영되지 않아 사용자가 직접 새로고침해야 하던 문제 수정 — 관리자 템플릿 활성화 시 자동으로 페이지를 갱신하여 새 템플릿이 즉시 적용되도록 개선", - "보안 환경설정의 \"최대 로그인 시도 횟수 / 차단 시간\" 설정이 실제로 적용되지 않아 무제한 로그인 시도가 가능하던 문제 수정 — 임계 도달 시 계정 잠금(HTTP 423), 잠금 해제 시각 안내 토스트, per-IP 백업 throttle, 활동 로그 기록까지 통합 구현", - "게시판 글쓰기 화면을 URL 로 직접 진입하거나 강제 새로고침했을 때 업로드한 첨부파일이 게시글에 연결되지 않던 문제 수정 (engine-v1.49.2) (#24 @minyho 님께서 제보해주셨습니다.)", - "코어 업그레이드 후 새 버전에서 추가된 권한·메뉴·알림 정의가 등록되지 않아 관리자 화면에서 \"해당 권한이 없습니다\" 가 반복 표시되거나 신규 메일 템플릿이 비어있던 문제 수정 — 업그레이드 시 새 버전 설정 파일을 정확히 인식하도록 보정. 본 릴리즈로 업그레이드하면 누락분이 자동 등록됨", - "PHP 8.5 환경에서 관리자 로그인 시 `PDO::MYSQL_ATTR_SSL_CA` 등 PDO 드라이버 상수가 deprecation 경고를 발생시키던 문제 수정 — `Pdo\\Mysql::ATTR_SSL_CA` 형태의 신규 상수로 전환하고, PHP 8.5 미만 환경에서는 기존 상수를 그대로 사용하도록 분기 처리 (#23 @yks118 님께서 제보해주셨습니다.)", - "회원가입 시 일부 환경에서 동의 항목 메타데이터가 누락되어 활동 로그 처리에서 예외가 발생, 후속 훅 체인이 중단되며 신규 회원에게 `user` 역할이 자동 부여되지 않던 문제 수정 — 동의 메타데이터를 nullable 로 받아 누락 케이스에서도 후속 권한 부여가 정상 동작하도록 개선 (#25 @comtylove-netizen 님께서 제보해주셨습니다.)" - ] - } - ] - }, - { - "version": "7.0.0-beta.3", - "date": "2026-04-23", - "categories": [ - { - "name": "Fixed", - "items": [ - "CKEditor5 플러그인 활성 상태에서 게시판 글쓰기 저장 시 \"제목은 필수입니다\" 422 오류가 발생하던 호환성 문제 수정 — 제목·내용 입력 순서와 무관하게 정상 저장되도록 개선 (#17 @laelbe 님께서 제보해주셨습니다.)", - "코어 업데이트가 sudo 로 실행된 환경에서 캐시·세션·확장 디렉토리의 그룹 쓰기 권한이 일부 손실되어 업데이트 직후 \"Permission denied\" 또는 플러그인 제거 검증 실패가 발생하던 문제 수정 — 업데이트 종료 시점에 그룹 쓰기 권한을 자동 정상화하며 기존 손실 분은 1회성 복구 스텝으로 회수", - "업데이트 완료 후 Laravel 런타임이 새로 만드는 캐시·세션 하위 디렉토리가 기본 umask(022) 때문에 다시 그룹 쓰기 권한을 잃어 재차 \"Permission denied\" 가 발생하던 문제 수정 — 업그레이드 스텝이 업데이트 진행 프로세스의 umask 를 그룹 쓰기 친화적으로 전환하고, 이후 부팅 시점에도 `storage/` 의 현재 그룹 쓰기 설정을 감지해 프로세스 umask 를 자동 동조 (운영자가 그룹 공유를 비활성화한 환경은 그대로 보존)", - "코어 업데이트 마지막 단계의 진행 표시줄이 끝난 뒤 줄바꿈 없이 다음 셸 프롬프트가 같은 줄에 붙어 표시되던 출력 문제 수정" - ] - }, - { - "name": "Notes", - "items": [ - "7.0.0-beta.1 에서 7.0.0-beta.3 로 직접 업그레이드하는 경우, 환경(opcache CLI 활성 등)에 따라 권한 복구 스텝이 자동 실행되지 않고 건너뛰어질 수 있습니다. 업데이트 후 `storage/framework/cache` 등에서 Permission denied 가 발생하면 아래 명령을 수동 실행해 주세요 — 7.0.0-beta.2 에서 올라오는 경로에서는 해당 없음", - "`php artisan core:execute-upgrade-steps --from=7.0.0-beta.2 --to=7.0.0-beta.3 --force`", - "시스템 레벨에서도 일관된 그룹 공유 권한을 원하면 php-fpm pool 설정에 `umask = 002` (또는 systemd unit 의 `UMask=0002`) 추가를 권장합니다. 코드 레벨 동조와 병행하면 외부 프로세스(cron, composer 등) 도 동일 권한으로 파일을 생성합니다" - ] - } - ] - }, - { - "version": "7.0.0-beta.2", - "date": "2026-04-20", - "categories": [ - { - "name": "Added", - "items": [ - "알림 시스템을 Definition(정의) × Template(채널별 템플릿) × Recipients(수신자) 3계층으로 재설계", - "채널별 독립 수신자 설정 — trigger_user, related_user, role, specific_users 4종 타입 지원", - "채널 Readiness 검증 — 미설정 채널(SMTP 미구성 등)의 발송을 사전 차단", - "알림 발송 공통 디스패처 — 채널별 독립 발송, 발송 전후 훅 자동 실행, 모든 채널 자동 로깅", - "알림 클릭 URL 지원 — database 채널 알림에 클릭 시 이동할 URL 패턴 설정 가능", - "사용자/관리자 알림 전체 삭제 API 추가", - "비밀번호 변경 시 `password_changed` 알림 자동 발송", - "코어 권한에 사용자 알림 전용 카테고리 분리 (관리자/사용자 권한 의미 분리)", - "알림 정의 일괄 초기화 기능 — 커스터마이징된 알림의 모든 채널 템플릿을 기본값으로 복원 (확인 모달 + 스피너 UX 포함)", - "모듈/플러그인이 자신의 기본 알림 정의를 코어 리셋 로직에 제공할 수 있는 필터 훅 제공", - "템플릿 편집 시 편집 상태가 자동 추적되어 리셋 버튼이 자동 노출", - "알림 정의/템플릿 응답에 편집/삭제 권한 정보(abilities) 포함으로 UI가 권한에 따라 자동 조정", - "Composer 실행 불가 환경(공유 호스팅 등)을 위한 vendor 번들 시스템 추가", - "vendor-bundle Artisan 커맨드 — 빌드, 검증, 일괄 처리", - "모듈/플러그인 설치·업데이트에 `--vendor-mode=auto|composer|bundled` 옵션 추가", - "인스톨러 Step 3에 Vendor 설치 방식 선택 UI 추가 (환경 자동 감지)", - "MaxMind GeoLite2 자동 다운로드 스케줄러 + 관리자 환경설정 UI 추가", - "IANA 타임존 전체(약 425개) 지원 — 기존 7개 하드코딩 화이트리스트 폐기 (#8 @abc101 님께서 건의해주셨습니다.)", - "Select 컴포넌트에 `searchable` prop 추가 — 타임존 등 대량 옵션에서 검색 가능", - "SSE/폴링 듀얼 모드 지원 — Nginx 프록시 + Apache 환경에서 SSE 문제 시 폴링 모드로 전환 가능", - "확장 의존성 자동 해결 — 템플릿 선택 시 필요한 모듈/플러그인을 즉시 자동 선택, 전이적 의존성까지 해결 (#10 @glitter-gim 님께서 건의해주셨습니다.)", - "자동 선택된 항목을 시각적으로 구분하고 요구한 확장 이름을 함께 표시", - "다른 확장이 의존하는 항목은 선택 해제 차단 (의존 관계 안내 메시지 포함)", - "의존성 버전 제약 사전 검증 — 버전 불일치 시 설치 진행 전 경고 (semver 비교: `>=`, `^`, `~` 등 지원) (#3 @laelbe 님께서 제보해주셨습니다.)", - "기존 DB 테이블 감지 및 안전한 재설치 지원 — 백업 안내 + 명시적 동의 후 진행 (#5 @laelbe 님께서 건의해주셨습니다.)", - "권한 안내 단순화 — `chmod -R 755` 단일 명령어로 통합 (업계 표준 정렬)", - "소유자 불일치(`ownership_mismatch`) 감지 — 전통적 Apache 환경에서 3가지 해결 옵션 제시", - "Step 5에 \"설치 시작\" 버튼 도입 — 모드 선택 후 사용자 클릭으로 설치 시작", - "코어/모듈/플러그인 격리 캐시 시스템 도입 (접두사 자동 부여)", - "모델 변경 시 태그 기반 자동 캐시 무효화 트레이트 추가", - "환경설정에서 캐시 TTL 중앙 관리 (7개 설정 키)", - "페이지 진입/탭 전환 시 로딩 spinner 표시 — 데이터 fetch 완료까지 유지 (#6 @laelbe 님께서 제보해주셨습니다.)", - "목록 페이지네이션 시 DataGrid body 영역 한정 spinner (pagination 버튼 가림 방지)", - "DataGrid 컴포넌트에 `id` prop 추가", - "HookManager 공통 큐 디스패치 — 환경설정 하나로 모든 훅 리스너 자동 비동기 실행, 큐 워커에서도 Auth/Request/Locale 컨텍스트 자동 복원", - "WebSocket 클라이언트/서버 endpoint 분리 설정 지원 — 리버스 프록시 환경 대응, SSL 검증 옵션 추가", - "SEO 변수 시스템 — 모듈/플러그인이 페이지별 SEO 변수를 제공할 수 있도록 개선", - "코어 드라이버 확장 시스템 — 플러그인이 필터 훅으로 스토리지/캐시/세션/큐 등 드라이버를 등록 가능", - "`HasUserOverrides` 트레이트 — 시더 데이터와 사용자 수정을 자동으로 분리 추적", - "템플릿 엔진 engine-v1.42.0 — `render: false` 선택적 리렌더 제어", - "템플릿 엔진 engine-v1.41.0 — setLocal/dispatch debounce API + stale 값 오염 방지", - "템플릿 엔진 engine-v1.40.0 — navigate fallback 옵션 (교차 템플릿 경로 대응)", - "템플릿 엔진 engine-v1.30~38 — sortable wrapper 요소 지정, React.memo 자동 래핑, resolvedProps 참조 안정화, transition_overlay spinner 시스템, reloadExtensions 통합 핸들러", - "코어 업데이트 완료 후 `_bundled` 에 새 버전이 있는 확장을 감지하고 일괄 업데이트 여부를 묻는 인터랙티브 프롬프트 제공", - "일괄 업데이트 시 전역 레이아웃 전략(overwrite / keep) 1회 질의 후 확장별 예외 지정 가능", - "모듈/플러그인 업데이트에도 `--layout-strategy=overwrite|keep` CLI 옵션 및 관리자 UI 모달 선택 지원 (기존 템플릿 전용 → 확장 전체 일관)", - "관리자 모듈/플러그인 업데이트 모달에 사용자가 수정한 레이아웃 감지 및 목록 표시 기능 추가", - "모듈/플러그인 업데이트 시 각 upgrade step 버전을 콘솔에 실시간 출력 (기존에는 파일 로그에만 기록)", - "확장 업데이트 메서드 파라미터 순서를 3종(템플릿·모듈·플러그인) 공통 prefix(id, force, onProgress) 로 정렬해 일관성 확보", - "모듈/플러그인이 런타임에 동적으로 생성한 권한·역할·메뉴(예: 게시판별 권한 세트)도 업데이트 시 보존되도록 개선", - "모듈/플러그인/템플릿 업데이트 커맨드에 업데이트 소스 선택 옵션(`--source=auto|bundled|github`) 추가 — GitHub 원격 장애·태그 롤백 등 상황에서 번들 소스로 강제 설치 가능", - "GitHub 원격에 존재하지 않는 태그로 업데이트 시 \"아카이브 추출 실패\" 로 불명확한 오류가 나던 문제 개선 — 태그 존재 여부를 먼저 검증해 \"업데이트 소스 없음\" 안내로 명확화", - "관리자 모듈/플러그인 업데이트 모달의 _global 상태키를 `hasModifiedTemplateLayouts`, `hasModifiedModuleLayouts`, `hasModifiedPluginLayouts` 로 통일해 네이밍 충돌 방지", - "`module:install` / `plugin:install` / `template:install` 에 `--force` 옵션 추가 — 이미 설치된 확장도 `_bundled`/`_pending` 원본으로 활성 디렉토리를 덮어써서 재설치 (불완전 설치 복구용)", - "확장 활성 디렉토리 무결성 검사 추가 — `module.php`/`module.json` (모듈), `plugin.php`/`plugin.json` (플러그인), `template.json` (템플릿) 중 하나라도 누락된 활성 디렉토리를 로드 시 경고 로그 + `install --force` 복구 힌트 제공", - "코어 업데이트 커맨드에 외부 ZIP 파일 직접 지정 옵션(`--zip=/path/to/g7.zip`) 추가 — GitHub 다운로드 대신 지정 ZIP 을 사용해 오프라인/수동 배포 가능", - "모듈/플러그인/템플릿 업데이트 커맨드에 외부 ZIP 파일 직접 지정 옵션(`--zip=/path/to/ext.zip`) 추가 — manifest 식별자와 대상 확장이 일치하는지 검증 후 manifest 의 버전으로 업데이트", - "외부 ZIP 은 GitHub 릴리스 zipball 의 래퍼 디렉토리와 평탄 루트 구조를 모두 자동 감지해 지원" - ] - }, - { - "name": "Changed", - "items": [ - "MailTemplate 시스템을 NotificationDefinition + NotificationTemplate 체계로 전면 전환", - "코어 알림 3종의 직접 발송을 훅 기반 발송으로 전환", - "알림 수신자를 definition 레벨에서 template 레벨로 이동 (채널별 독립 수신자)", - "알림 broadcast 채널을 정수 User ID에서 UUID 기반으로 변경 (보안 강화)", - "알림 시각 표시를 ISO 8601에서 사용자 타임존 기준으로 변경", - "24개 서비스의 캐시 호출을 `Cache::` 파사드에서 `CacheInterface` DI로 전환", - "13개 서비스의 TTL을 환경설정 중앙 관리로 통일 (하드코딩 제거)", - "캐시 키 접두사를 `g7:core:`, `g7:module.{id}:`, `g7:plugin.{id}:` 체계로 통일", - "`CacheService` 클래스 삭제 — `CacheInterface`로 완전 대체", - "권한 검증을 비트 값 비교에서 실제 읽기/쓰기 가능 여부 기반으로 단순화", - "`.env` 생성 안내를 소유자 일치 여부에 따라 1-step으로 통합", - "확장 정보 모달에 의존성 정보 노출 — 코어 요구 버전(`g7_version`), 의존 모듈/플러그인 목록과 각각의 요구 버전·설치 버전·충족 상태 뱃지를 모듈/플러그인/템플릿 정보 모달에서 확인 가능", - "`locale_names` 설정 추가 — 언어 표시명을 중앙에서 관리", - "매 요청 `Schema::hasTable()` 호출 제거 — 설치 완료 환경에서 10~14회의 불필요한 스키마 쿼리 제거로 응답 시간 단축", - "SeoRenderer에 모듈/플러그인 SEO 변수 자동 해석 기능 추가", - "ResponseHelper — 디버그 모드에서 예외 상세 자동 포함, 프로덕션에서 내부 메시지 노출 차단", - "환경설정 드라이버 변경 시 큐 워커 자동 재시작", - "`allow_url_fopen=Off` 환경 지원 — GitHub 연동 HTTP 호출을 Laravel Http 파사드로 전면 교체", - "코어 업데이트 완료 후 안내 메시지를 \"수동 업데이트 명령 나열\"에서 \"일괄 업데이트 인터랙티브 프롬프트\"로 변경", - "확장 삭제 시 권한·메뉴·역할은 \"데이터도 함께 삭제\" 옵션을 체크했을 때만 삭제되도록 변경 — 기본 삭제에서는 보존되어 재설치 시 사용자 역할 할당이 복원" - ] - }, - { - "name": "Fixed", - "items": [ - "WebSocket 데이터소스가 progressive 목록에 포함되어 영구 블러되던 문제 수정", - "WebSocket 채널/이벤트 표현식 미평가로 인증 실패하던 문제 수정", - "WebSocket broadcast HTTP API가 클라이언트 host로 POST되어 실패하던 문제 수정", - "WebSocket 활성화 시 필수 필드 미검증 문제 수정", - "WebSocket 비활성화 시에도 Reverb 연결을 시도하던 문제 수정", - "`--force` 업데이트 시 번들 없는 외부 확장이 업데이트 소스를 찾지 못하던 문제 수정", - "확장 의존성 데이터 구조 불일치 수정 — manifest JSON 기반으로 통일", - "모듈/플러그인/템플릿 install/activate/deactivate/uninstall 직후 라우트 미반영 수정", - "다국어 번역 캐시 경합으로 raw key 노출되던 문제 수정 (engine-v1.38.1)", - "조건부 `$t:` 표현식에서 번역 실패하던 문제 수정 (engine-v1.38.2)", - "테스트 환경과 개발 환경이 동일한 Redis 캐시를 공유하여 상호 오염되던 문제 수정", - "대시보드 브로드캐스트 스케줄이 무한 락에 걸리던 문제 수정", - "환경설정 저장 후 `config:cache` 상태에서 변경값 미반영 수정", - "Windows 환경에서 파일 잠금 감지 시 30초+ hang 수정 — 0.5초로 단축", - "개발 의존성 없이 설치된 프로덕션 환경에서 샘플 시더와 팩토리가 실행되지 않던 문제 수정 — 한국어 샘플 데이터 생성기가 자동으로 대체 동작하도록 개선", - "`sudo php artisan core:update` 실행 시 vendor 디렉토리 전체가 root 소유로 오염되던 문제 수정", - "코어 업데이트 후 신규 권한·메뉴가 DB에 반영되지 않아 관리자 환경설정 페이지 접근이 거부되던 문제 수정", - "beta.1에서 업그레이드 시 제거된 메일 템플릿 모델 참조로 Fatal 오류가 발생하던 문제 수정", - "업그레이드 후 `bootstrap/cache` 등 일부 디렉토리가 root 소유로 남아 확장 설치·업데이트가 거부되던 문제 수정 — 소유권 복원 범위를 설치 안내 경로 전체로 확장", - "코어 업데이트 진행률 표시와 upgrade step 실행 로그가 같은 줄에 뒤섞이던 문제 수정 — 각 step 안내가 별도 줄로 깔끔하게 출력됨", - "템플릿/레이아웃 공개 API가 확장 활성화 전·설치 중에 받은 \"찾을 수 없음\" 에러 응답을 영구 캐시하여 복구 후에도 같은 오류를 반환하던 문제 수정 — 에러 응답은 캐시에서 제외", - "`_pending`/`_bundled` 에 업데이트 중 남은 임시 디렉토리(`{id}_YYYYMMDD_HHMMSS`, `{id}_updating_*` 등) 의 원본 manifest 때문에 `install` 이 존재하지 않는 표준 경로로 접근해 실패하던 문제 수정 — 디렉토리명과 identifier 불일치 시 스캔에서 제외", - "업그레이드 후 일부 코어 권한이 DB에 반영되지 않아 사용자 역할의 알림 기능 권한이 비어있던 문제 수정 — 별도 프로세스 실행으로 최신 로직 기반 재동기화 보장", - "코어·확장 설정에서 제거된 메뉴·권한·역할·알림 정의·알림 템플릿·게시판 유형·클레임 사유가 업데이트 후에도 DB에 잔존하던 문제 수정 — 고아 레코드 자동 정리", - "관리자 UI에서 메뉴·역할·알림·게시판 유형·클레임 사유·배송 유형 등을 수정해도 다음 업데이트 시 기본값으로 덮어써지던 문제 수정 — 사용자가 수정한 필드는 모든 저장 경로에서 자동 추적되어 업데이트 후에도 보존", - "알림 정의·템플릿도 업데이트 기준으로 동기화 — 새 버전에서 제거된 알림은 DB에서도 삭제", - "관리자 모듈/플러그인 목록의 \"작성자\" 표기가 manifest 의 `vendor` 필드 대신 식별자 prefix 를 사용하던 문제 수정 — `vendor` 가 정의된 확장은 정확한 작성자명으로 표시 (#9 @glitter-gim 님께서 제보해주셨습니다.)" - ] - }, - { - "name": "Removed", - "items": [ - "MailTemplate 시스템 일괄 제거 — 모델, 컨트롤러, 서비스, 리스너, 시더, 팩토리, 다국어 파일 등. NotificationDefinition + NotificationTemplate 체계로 완전 대체" - ] - } - ] - }, - { - "version": "7.0.0-beta.1", - "date": "2026-04-01", - "categories": [ - { - "name": "Changed", - "items": [ - "오픈 베타 릴리즈" - ] - } - ] - }, - { - "version": "7.0.0-alpha.21", - "date": "2026-03-30", - "categories": [ - { - "name": "Added", - "items": [ - "인스톨러 PHP CLI/Composer 필수 검증 기능 — 기본 `php` 미감지 시 CLI 설정 필수 전환, Composer 실행 확인 및 미설치 시 설치 안내, 둘 다 검증 완료 전 다음 단계 진행 차단", - "템플릿 레이아웃 수정 감지 시스템 — `original_content_hash`/`original_content_size` 컬럼 추가, SHA-256 해시 기반 수정 감지로 `updated_by` 방식 대체 (TemplateManager, LayoutRepository, 마이그레이션)", - "관리자 로그인 화면 개선 — 비밀번호 찾기/재설정 플로우 추가, 테마 Segmented Control, 언어 셀렉트 확대 (PasswordResetController, PasswordResetNotification, ForgotPasswordRequest, ResetPasswordRequest)", - "`{{raw:expression}}` 바인딩 번역 면제 마커 시스템 — `$t:` 자동 번역 대상에서 특정 바인딩을 제외하는 마커 도입 (rawMarkers.ts)", - "레이아웃 프리뷰 모드 — 관리자 레이아웃 편집기에서 실시간 미리보기 지원 (LayoutPreviewController, LayoutPreviewService, 마이그레이션)", - "활동 로그 이력 조회 메뉴 신설 — ActivityLogController, ActivityLogResource, ActivityLogService, ActivityLogRepository, config/core.php 메뉴/권한 등록, 관리자 레이아웃 추가" - ] - }, - { - "name": "Changed", - "items": [ - "`window.G7Config` 설정 노출 최소화 Phase 1 — 프론트엔드 미참조 설정의 브라우저 소스 노출 차단", - "SettingsService: 필드 레벨 `expose: false` 지원 추가 (formatCategorySettings)", - "FiltersFrontendSchema: `fields: {}` (빈 객체) 시 전체 차단 안전 기본값 적용", - "View Composers: TemplateComposer/UserTemplateComposer에 ModuleSettingsService 주입 — frontend_schema 기반 필터링 적용 (기존 config() 직접 참조 우회 수정)", - "코어 defaults.json: general 4개 필드, security 전체, seo 전체, advanced 9개 필드(debug_mode 제외), upload 2개 필드, drivers 전체를 `expose: false` 처리", - "캐시 무효화 로직 정비 — 016862cb 이전의 단순한 방식으로 복귀 (버전 증가 + TTL 자연 만료), `extension_cache_previous_versions` 추적 및 `getCacheVersionsToInvalidate()`/`getCacheVersionsForLayoutInvalidation()` 제거, Cache Tags 드라이버 분기 제거로 캐시 드라이버 비의존성 확보" - ] - }, - { - "name": "Fixed", - "items": [ - "MariaDB를 `DB_CONNECTION=mysql` 드라이버로 연결 시 `WITH PARSER ngram` 에러로 인스톨러 설치 실패 수정 — `isMariaDb()` 메서드 추가하여 서버 버전 문자열 기반 실제 DBMS 감지 (DatabaseFulltextEngine)", - "캐시 무효화 버전 키 누락 수정 — `warmTemplateCache()`가 버전 포함 키(`.v{version}`)로 캐시를 생성하나 `clearTemplateCache()`가 레거시 키만 삭제하던 문제, routes/language 캐시에 버전 포함 키 삭제 추가", - "캐시 버전 0 무효화 누락 수정 — `array_filter()`가 버전 `0`을 falsy로 제거하여 `.v0` 캐시 키가 삭제 대상에서 누락되던 버그 (ClearsTemplateCaches, InvalidatesLayoutCache)", - "테스트 활성 디렉토리 보호 강화 — `ProtectsExtensionDirectories`에 `moveDirectory` spy 추가, `copyToActive()` 원자적 교체가 활성 디렉토리를 rename으로 파괴하는 것 방지", - "활동 로그 하위 호환 — `ActivityLogResource`에서 기존 DB 레코드(`type: 'text'`)의 enum 필드를 모델의 현재 `$activityLogFields` 기준으로 동적 번역 + 일괄 변경 `:count` 미치환 보정", - "활동 로그 enum 미번역 전수 수정 — 14개 모델 필드의 `$activityLogFields` `type: 'enum'` 전환 + 14개 Enum 클래스에 `labelKey()` 메서드 추가 (User, Order, OrderOption, Product, Coupon, Post, Comment, Board)", - "활동 로그 삭제/일괄삭제 `description_key` 및 샘플 시더 누락 추가 — 코어 시더 `activity_log_descriptions` 테이블에 delete/bulk_delete 키 보충 + 다국어 파일 동기화", - "관리자 다크모드 품질 전수 조사 및 수정 — 레이아웃 JSON ~546건, TSX ~37건, CSS 5건의 누락된 `dark:` variant 추가 (이커머스 101파일, 게시판 19파일, 페이지 2파일, 마케팅 1파일, 템플릿 컴포넌트 ~20파일)", - "레이아웃 편집 캐시 무효화 및 버전 히스토리 수정 — LayoutService의 저장/복원 시 PublicLayoutController 서빙 캐시 무효화 + `extension_cache_version` 증가 누락 수정", - "사용자 관리에서 관리자/슈퍼관리자 삭제 시 토스트 메시지 및 어빌리티 처리 — CannotDeleteAdminException 추가, UserResource에 `can_delete` 어빌리티 반영", - "ActivityLog 핸들러 `$result` 타입 불일치 수정 — CoreActivityLogListener에서 `bool` → `array` 반환 타입 정합성 보정", - "환경설정 사이트 로고 저장 시 Attachment 객체 정수 검증 오류 수정 — SaveSettingsRequest에서 Attachment JSON 객체를 정수로 검증하던 문제 수정", - "관리자 SPA 네비게이션 로고 깜빡임 수정 — extends base 컴포넌트 불필요 remount 방지 (`_fromBase` 마킹 기반 stable key 패턴)", - "인스톨러 다크모드 적색 배경 가독성 개선 — `alert-title`, `alert-message`, `permission-badge`, `test-result` 다크모드 색상 override 추가", - "인스톨러 `BASE_PATH` 심볼릭 링크 미해석 — `realpath()` 적용 + 절대경로/상대경로 병기로 호스팅 환경 대응", - "인스톨러 403 에러 페이지 다크모드 미지원 — `prefers-color-scheme: dark` 미디어쿼리 추가", - "템플릿 업데이트 수정 감지 실패 — `LayoutRepository::update()`가 `updated_by`를 설정하지 않아 `hasModifiedLayouts()`가 항상 \"수정 없음\" 반환하던 문제 수정 (hash 비교 방식으로 전환)", - "템플릿 업데이트 \"수정 유지\" 전략 미작동 — `layoutStrategy === 'keep'` 시 `refreshTemplateLayouts()` 미호출 → 양쪽 전략 모두 호출하되 `preserveModified` 플래그로 분기", - "템플릿 업데이트 API 응답 키 불일치 — 백엔드 `has_modified` → 프론트엔드 기대 `has_modified_layouts`/`modified_count` 키 정렬", - "템플릿/모듈/플러그인 설치 시 `incrementExtensionCacheVersion()` 누락 수정 — TemplateManager(install/activate/deactivate/uninstall) + ModuleManager(install) + PluginManager(install) 총 7곳 추가", - "캐시 버전 변경 시 프론트엔드 다국어 미갱신 수정 — TemplateApp.ts에서 캐시 버전 변경 감지 시 TranslationEngine 재로드 추가", - "`warmTemplateCache()` 다국어 캐시 워밍 시 `$partial` 디렉티브 미해석 수정 — `json_decode`만 수행하던 코드를 `TemplateService::getLanguageDataWithModules()` 호출로 교체하여 fragment 해석 및 모듈/플러그인 다국어 병합 정상화", - "`ActivateTemplateCommand` 의존성 미충족 시 성공으로 보고하는 버그 수정 — `if ($result)` → `if ($result['success'])` 변경 및 의존성 경고 메시지 출력, `--force` 옵션 추가", - "`ActivateModuleCommand`/`ActivatePluginCommand` 의존성 미충족 시 경고 메시지 미출력 수정 — 의존성 경고 표시 및 `--force` 옵션 전달 추가" - ] - } - ] - }, - { - "version": "7.0.0-alpha.20", - "date": "2026-03-30", - "categories": [ - { - "name": "Added", - "items": [ - "Laravel Scout 통합 및 MySQL FULLTEXT(ngram) 검색엔진 드라이버 확장 시스템 도입 — 커스텀 `DatabaseFulltextEngine`으로 `MATCH...AGAINST IN BOOLEAN MODE` 지원, `core.search.engine_drivers` 필터 훅으로 Meilisearch/Elasticsearch 등 외부 엔진 플러그인 등록 가능", - "`DatabaseFulltextEngine` 다중 DBMS 호환 — FULLTEXT 미지원 DBMS(PostgreSQL, SQLite)에서 LIKE fallback 자동 전환, `whereFulltext()` 정적 헬퍼(관계 검색용), `addFulltextIndex()` 마이그레이션 헬퍼(DBMS별 조건부 DDL)", - "`config/scout.php` 설정 파일 추가 — 기본 드라이버 `mysql-fulltext`, `SCOUT_DRIVER` 환경변수로 전환 가능", - "`FulltextSearchable` 인터페이스 — FULLTEXT 검색 대상 컬럼 및 가중치 정의 계약", - "`AsUnicodeJson` 커스텀 캐스트 — JSON 컬럼에 한글을 `\\uXXXX` 이스케이프 없이 실제 UTF-8로 저장하여 FULLTEXT ngram 토크나이저 정상 동작 보장", - "환경설정 > 드라이버 탭에 검색엔진 설정 카드 추가 — 기본 MySQL FULLTEXT(ngram) 드라이버 표시, 플러그인 설치 시 추가 드라이버 자동 표시", - "`SaveSettingsRequest`에 검색엔진 드라이버 동적 validation 추가 — `core.search.engine_drivers` 필터 훅 기반 허용 목록" - ] - } - ] - }, - { - "version": "7.0.0-alpha.19", - "date": "2026-03-29", - "categories": [ - { - "name": "Added", - "items": [ - "검색/필터/정렬 성능 향상을 위한 누락 인덱스 일괄 추가 — activity_logs(description_key), users(created_at), mail_send_logs(status), template_layouts(template_id), schedules(created_at)" - ] - } - ] - }, - { - "version": "7.0.0-alpha.18", - "date": "2026-03-26", - "categories": [ - { - "name": "Added", - "items": [ - "SEO ExpressionEvaluator 산술 연산자 확장 — `*`, `/`, `%` 추가 (기존 `+`, `-`만 지원)", - "SEO PipeRegistry 파이프 함수 엔진 구현 — 프론트엔드 PipeRegistry.ts 빌트인 파이프 15종 PHP 미러링 (date, datetime, relativeTime, number, truncate, uppercase, lowercase, stripHtml, default, fallback, first, last, join, length, filterBy, keys, values, json, localized)", - "ActivityLog `description_params` ID→이름 변환 필터 훅 (`core.activity_log.filter_description_params`) — `ActivityLog::getLocalizedDescriptionAttribute()`에서 실행", - "코어 모델 `$activityLogFields` 정의 — `User`, `Role`, `Menu`, `Schedule`, `MailTemplate` (5개 모델)", - "ActivityLog ChangeDetector 필드 라벨 다국어 키 추가 (`lang/ko/activity_log.php`, `lang/en/activity_log.php` — `fields` 섹션)", - "`module_helpers.php` — `getModuleSetting()` 헬퍼 함수 추가", - "CoreActivityLogListener: bulk_update per-User/per-Schedule 전환 + bulk_delete per-Schedule 전환 (건별 loggable_id 기록)", - "ActivityLogHandler: 삭제된 엔티티용 loggable_type/loggable_id 직접 지정 fallback 지원", - "메뉴 관리 크로스 depth 이동 지원 — `UpdateMenuOrderRequest`에 `moved_items` 검증 추가, `MenuRepository`에 크로스 depth reorder 로직 구현", - "`NotCircularParent` 검증 규칙 추가 — 메뉴 순환 참조 방지" - ] - }, - { - "name": "Changed", - "items": [ - "ActivityLog 규정 문서 대폭 보강 (`docs/backend/activity-log.md`) — `description_params` 저장 정책, `ActivityLogDescriptionResolver` 패턴, Bulk Update ChangeDetector 패턴, 개발자 체크리스트 추가" - ] - } - ] - }, - { - "version": "7.0.0-alpha.17", - "date": "2026-03-26", - "categories": [ - { - "name": "Added", - "items": [ - "ActivityLog `description_params` ID→이름 변환 필터 훅 (`core.activity_log.filter_description_params`) — `ActivityLog::getLocalizedDescriptionAttribute()`에서 실행", - "코어 모델 `$activityLogFields` 정의 — `User`, `Role`, `Menu`, `Schedule`, `MailTemplate` (5개 모델)", - "ActivityLog ChangeDetector 필드 라벨 다국어 키 추가 (`lang/ko/activity_log.php`, `lang/en/activity_log.php` — `fields` 섹션)", - "`module_helpers.php` — `getModuleSetting()` 헬퍼 함수 추가", - "CoreActivityLogListener: bulk_update per-User/per-Schedule 전환 + bulk_delete per-Schedule 전환 (건별 loggable_id 기록)", - "ActivityLogHandler: 삭제된 엔티티용 loggable_type/loggable_id 직접 지정 fallback 지원" - ] - }, - { - "name": "Changed", - "items": [ - "ActivityLog 규정 문서 대폭 보강 (`docs/backend/activity-log.md`) — `description_params` 저장 정책, `ActivityLogDescriptionResolver` 패턴, Bulk Update ChangeDetector 패턴, 개발자 체크리스트 추가" - ] - } - ] - }, - { - "version": "7.0.0-alpha.17", - "date": "2026-03-26", - "categories": [ - { - "name": "Added", - "items": [ - "Monolog 기반 ActivityLog 아키텍처: `Log::channel('activity')` → `ActivityLogHandler` → DB (3단계)", - "`ActivityLogChannel` (커스텀 Monolog 채널), `ActivityLogHandler`, `ActivityLogProcessor` 신규", - "ActivityLog i18n 지원: `description_key` + `description_params` 기반 실시간 다국어 번역", - "ActivityLog 구조화된 변경 이력: `changes` JSON 컬럼 (필드별 `label_key`, `old`/`new`, `type` 포함)", - "`ChangeDetector` 유틸리티 (모델 스냅샷 비교 → 구조화된 변경 이력 생성)", - "`CoreActivityLogListener` 전면 확장: 모든 코어 Service 훅 구독 (User/Role/Menu/Settings/Schedule/Auth/Module/Plugin/Template/Layout/MailTemplate/Attachment — 66개 훅)", - "활동 로그 다국어 키 105개 정의 (`lang/ko/activity_log.php`, `lang/en/activity_log.php`)", - "`config/logging.php`에 `activity` 채널 추가", - "`config/activity_log.php` 전용 설정 파일 신규", - "`activity_logs` 테이블 복합 인덱스 추가 (`loggable_type`+`loggable_id`+`created_at`, `log_type`+`action`+`created_at`)" - ] - }, - { - "name": "Fixed", - "items": [ - "`resolveLogType()` 사용자 역할 기반 → 요청 경로 기반으로 변경: 관리자가 사용자 화면에서 수행한 액션이 `admin`으로 기록되던 문제 수정 (ResolvesActivityLogType)" - ] - }, - { - "name": "Changed", - "items": [ - "`ActivityLog` 모델: `description` 컬럼 삭제 → `description_key`/`description_params` 기반 다국어 전환", - "`ActivityLogService`: 기록 메서드 전면 제거 → 조회 전용으로 축소", - "`ActivityLogResource`: `description` → `localized_description` (실시간 번역)", - "모든 Controller에서 `logAdminActivity()` 호출 전면 제거 → Listener 경로로 전환" - ] - }, - { - "name": "Removed", - "items": [ - "`activity_logs.description` 컬럼 (DB 삭제)", - "`ActivityLogManager`, `ActivityLogDriverInterface`, `DatabaseActivityLogDriver`, `NullActivityLogDriver`", - "`ActivityLogListener` (이중 훅 계층 — Monolog Handler로 대체)", - "`ActivityLogService.log`/`logAdmin`/`logUser`/`logSystem` (Monolog 채널로 대체)", - "`AdminBaseController.logAdminActivity()`, `generateActivityDescription()`, `flattenDataForTranslation()`" - ] - }, - { - "name": "Fixed", - "items": [ - "마이페이지 프로필 저장 시 국가·언어 미선택 상태에서 오류가 발생하던 문제 수정 — 해당 필드를 선택 사항으로 변경", - "인스톨러 Step 2 .env 복사 명령어 안내 수정 — `.env.example.production` → `.env.example` (functions.php 2곳, installer.js 4곳)" - ] - } - ] - }, - { - "version": "7.0.0-alpha.16", - "date": "2026-03-23", - "categories": [ - { - "name": "Fixed", - "items": [ - "코어 업데이트 롤백 시 vendor 디렉토리 복원 불가 수정 — 백업 targets에 vendor 포함, excludes에서 vendor 제거", - "코어 백업 복원 시 개별 target 실패가 전체 복원을 중단하는 문제 수정 — 개별 try-catch로 나머지 target 복원 계속 진행", - "코어 업데이트 롤백 실패 시 수동 복구 안내 미출력 수정 — composer install 등 복구 단계 안내 추가", - "코어 업데이트 완전 복원 성공 시 유지보수 모드 자동 해제 추가", - "코어 업데이트 시 vendor 디렉토리 이중 처리로 인한 마이그레이션 실패 수정 — `backup_only` 설정 분리 (applyUpdate 제외, 백업/복원 전용)" - ] - } - ] - }, - { - "version": "7.0.0-alpha.15", - "date": "2026-03-23", - "categories": [ - { - "name": "Added", - "items": [ - "Users UUID 전환 — 외부 노출 ID를 UUID v7으로 전환, 정수 `id`는 API 응답에서 숨김", - "UniqueIdService 코어 서비스 추가 (UUID v7 + NanoID 생성)", - "코어 업그레이드 스텝 추가 (Upgrade_7_0_0_beta_15)" - ] - }, - { - "name": "Changed", - "items": [ - "User 모델: `getRouteKeyName()` → 'uuid', `$hidden`에 'id' 추가", - "공개 프로필 API: Route Model Binding 전환 (`{userId}` → `{user}`)", - "사용자 벌크 상태변경: 정수 ID → UUID 기반", - "API Resource: user.id → user.uuid 전환 (UserResource, UserCollection 외 7개)", - "Activity Log 메타데이터: user_id → uuid 전환", - "FormRequest: 정수 검증 → UUID 검증 전환" - ] - }, - { - "name": "Fixed", - "items": [ - "`_global.currentUser?.id` → `?.uuid` 전환 (sirsoft-basic 템플릿 12개 파일)", - "글쓰기 버튼 abilities 비활성화 누락 수정", - "코어 업그레이드 스텝 raw SQL 테이블 프리픽스 미적용 수정 — `UpgradeContext::table()` 헬퍼 추가 (Upgrade_7_0_0_beta_15)" - ] - } - ] - }, - { - "version": "7.0.0-alpha.14", - "date": "2026-03-20", - "categories": [ - { - "name": "Fixed", - "items": [ - "확장 업데이트 시 임시 디렉토리(`_updating_*`, `_old_*`) 오토로드 오염 방지 — 임시 디렉토리를 `_pending/` 하위에 생성하여 IDE 잠금 등으로 잔존 시에도 Fatal Error 방지 (ExtensionPendingHelper)" - ] - }, - { - "name": "Added", - "items": [ - "Windows 파일잠금 감지/해제 기능 — 확장 업데이트 시 IDE 등이 파일 핸들을 보유한 경우 자동 감지 및 해제 시도 (FileHandleHelper, ExtensionPendingHelper)", - "SEO 렌더러 훅 시스템: `core.seo.filter_context`, `core.seo.filter_meta`, `core.seo.filter_view_data` — 확장이 SEO 렌더링 파이프라인에 런타임 데이터 변환으로 개입 가능", - "`seo.blade.php` 확장 슬롯: `extraHeadTags` (`` 직전), `extraBodyEnd` (`` 직전) — `filter_view_data` 훅을 통해 커스텀 스크립트/스타일 주입", - "`ComponentHtmlMapper` pagination 렌더 모드 — Pagination 컴포넌트에서 SEO용 페이지 링크 자동 생성 (currentPage/totalPages props 기반)", - "`ComponentHtmlMapper` text_format dot notation 지원 — `{author.nickname}` 형태로 객체 prop의 중첩 필드 접근", - "`ExpressionEvaluator::evaluateRaw()` — 표현식 결과를 원본 타입(배열 등)으로 반환하는 메서드", - "`SeoRenderer` SEO 컨텍스트에 `_global`/`_local` 빈 객체 추가 — 프론트엔드 전용 상태 참조 시 null 대신 빈 객체 제공", - "`ComponentHtmlMapper` fields 렌더 모드 — 컴포지트 컴포넌트(ProductCard 등)의 객체 prop에서 SEO용 HTML 필드 자동 생성 (조건부/반복/속성 기반)", - "`SeoRenderer` seoVars 주입 — `meta.seo.vars` 선언을 해석하여 ComponentHtmlMapper format 모드에서 `{key}` 플레이스홀더 치환", - "`ExpressionEvaluator` 리터럴 값 감지 — 숫자, 문자열, boolean, null/undefined 리터럴을 경로 해석 없이 직접 반환", - "`ExpressionEvaluator` $t: 파라미터 `{{}}` 표현식 해석 — 번역 키 파라미터 값에 포함된 바인딩 표현식을 컨텍스트에서 평가", - "`TemplateManager` seo-config 검증에 `fields` 타입 추가", - "`ExpressionEvaluator` seo_overrides — seo-config.json에서 `_local`/`_global` 상태 오버라이드 선언 (와일드카드 매칭으로 접혀있는 콘텐츠 SEO 강제 펼침)", - "`TemplateManager` seo-config 검증에 `pagination` 타입 및 `seo_overrides` 검증 추가", - "`SeoConfigMerger` — 모듈/플러그인/템플릿의 seo-config.json을 수집·병합하는 동적 확장 시스템 (우선순위: 모듈 → 플러그인 → 템플릿, 24시간 TTL 캐싱)", - "`SeoRenderer` `_global` 컨텍스트 주입 — SettingsService/PluginSettingsService 프론트엔드 설정을 `_global`에 주입 + `initGlobal` 매핑으로 데이터소스 응답을 `_global` 경로에 바인딩", - "`AbstractModule`/`AbstractPlugin` SEO 기여 메서드 — `getSeoConfig()`, `getSeoDataSources()` 인터페이스 추가", - "`ModuleManager`/`PluginManager`/`TemplateManager` install/activate/update 시 훅 발행 추가 — Artisan 커맨드에서도 SEO 캐시 자동 무효화 보장", - "SEO Artisan 커맨드 다국어 파일 추가 (`lang/ko/seo.php`, `lang/en/seo.php`)", - "`ComponentHtmlMapper` fields 모드 `$all_props` source — 모든 props 표현식을 해석하여 데이터 객체로 사용 (Header/Footer 등 다수 props 컴포넌트용)", - "`ComponentHtmlMapper` fields 모드 `$t:` 번역 키 지원 — content 패턴에서 `$t:key` → 다국어 텍스트 렌더링", - "`ComponentHtmlMapper` fields iterate `item_attrs` — 아이템별 동적 HTML 속성 (예: `{ \"href\": \"/board/{slug}\" }`)", - "`ExpressionEvaluator` `evaluateRaw()` `??` null coalescing 지원 — 원본 타입(배열/객체) 유지하면서 null coalescing 수행", - "인스톨러 Windows 환경 명령어 대응 — `chmod`/`chown` 스킵, 미존재 디렉토리 안내 메시지 추가", - "`ExpressionEvaluator` 삼항 연산자 (`a ? b : c`) — JS 우선순위 준수, `?.`/`??` 자동 구분, 중첩 우측 결합", - "`ExpressionEvaluator` `$t()` 함수 호출 구문 — 삼항 내부에서 `$t('key')` 형태로 번역 키 사용 가능 (기존 `$t:key` 방식 확장)", - "`ExpressionEvaluator` `$localized()` 전역 함수 — 다국어 객체에서 현재 로케일 값 추출 (`{ko: \"상품\", en: \"Product\"}` → `\"상품\"`)", - "`ExpressionEvaluator` 객체 리터럴 파서 — `{key: value, ...obj, [dynamicKey]: value}` 구문 지원", - "`ExpressionEvaluator` 스프레드 연산자 — 배열 `[...arr, item]` 및 객체 `{...obj, key: value}` 스프레드 지원", - "`SeoRenderer` computed 속성 해석 — 레이아웃 `computed` 섹션을 `_computed`/`$computed`에 저장 (문자열 표현식 + `$switch` 형식)", - "`ComponentHtmlMapper` classMap 지원 — `base`/`variants`/`key`/`default`로 조건부 CSS 클래스 선언적 적용" - ] - }, - { - "name": "Fixed", - "items": [ - "Redis 캐시 DB가 환경설정 값(`REDIS_CACHE_DB`)을 따르도록 수정 — `config/database.php` cache 연결 DB 반영", - "`ExpressionEvaluator` 배열 리터럴 파싱 지원 — `['gallery','card'].includes(...)` 표현식에서 배열 리터럴을 PHP 배열로 변환하여 `includes` 등 배열 메서드 정상 동작 (SEO 게시판 타입 분기 조건 중복 렌더링 수정)", - "`ExpressionEvaluator` null 비교 JavaScript 시맨틱 적용 — `null !== value` → `true`, `null === null` → `true` (SEO 컨텍스트에서 `_global` 미존재 경로 비교 시 빈 문자열 대신 올바른 boolean 반환)", - "`ExpressionEvaluator` $t: 번역에서 `{{param}}` 형식 파라미터 치환 지원 — 템플릿 번역 파일의 `{{param}}` + Laravel 표준 `:param` 형식 모두 처리", - "`ExpressionEvaluator` 비교 연산 좌측 optional chaining 경로 타입 보존 — `?.` 포함 경로가 `evaluateExpression`으로 불필요 라우팅되어 boolean 타입이 문자열로 변환되던 문제 수정" - ] - } - ] - }, - { - "version": "7.0.0-alpha.13", - "date": "2026-03-18", - "categories": [ - { - "name": "Added", - "items": [ - "`SeoCacheRegenerator` — 단건 URL 캐시 즉시 재생성 서비스 (다국어 로케일별 렌더링 + 캐시 저장)", - "`SeoSettingsCacheListener` — 코어 SEO 설정 변경 시 전체 SEO 캐시 + 사이트맵 삭제" - ] - }, - { - "name": "Fixed", - "items": [ - "`SeoMiddleware`에서 `put()` 대신 `putWithLayout()` 사용 — `invalidateByLayout()`이 레이아웃명 미저장으로 항상 0건 매칭되던 근본 버그 수정", - "`SeoRenderer`에서 레이아웃명을 request attribute로 저장 — `putWithLayout()` 연동" - ] - } - ] - }, - { - "version": "7.0.0-alpha.12", - "date": "2026-03-17", - "categories": [ - { - "name": "Changed", - "items": [ - "API 인증을 토큰 전용으로 전환 — 세션 기반 인증 의존 제거, Bearer 토큰 단일 방식으로 통일", - "README 업데이트 — 표기 통일, 섹션 정비, 문서 링크 연결" - ] - } - ] - }, - { - "version": "7.0.0-alpha.11", - "date": "2026-03-16", - "categories": [ - { - "name": "Added", - "items": [ - "역할(Role) 상태 토글 API 추가 — `PATCH /api/admin/roles/{role}/toggle-status` 엔드포인트, 훅 지원 (`core.role.before_toggle_status`, `core.role.after_toggle_status`)", - "RoleResource에 `can_toggle_status` ability 추가 — 역할별 토글 권한 제어", - "RoleResource에 `extension_name` 필드 추가 — 확장 출처별 로케일 이름 표시", - "역할 생성 시 `identifier` 직접 입력 기능 추가 — 미입력 시 name 기반 자동 생성 유지", - "역할 관련 다국어 키 추가 (ko/en) — identifier 검증 메시지", - "`replaceUrl` 핸들러 추가 — refetch 없이 URL만 변경 (페이지네이션 등 브라우저 히스토리 관리용)", - "사용자 검색 API에 `id` 파라미터 지원 추가 — 특정 사용자 ID로 직접 조회 가능", - "`TimezoneHelper` 유틸리티 클래스 추가 — 사용자/서버 타임존 간 변환 헬퍼", - "`HasSampleSeeders` 트레이트 추가 — 모듈/플러그인 시더에서 `--sample` 옵션으로 샘플 데이터만 분리 실행", - "`ComponentRegistry.getComponentNames()` 메서드 추가 — 등록된 컴포넌트 이름 목록 조회" - ] - }, - { - "name": "Fixed", - "items": [ - "템플릿 에러 페이지에서 `:identifier`가 리터럴로 표시되는 버그 수정 — Blade `@` 이스케이프 처리", - "ActionDispatcher onChange raw value fallback 제거 — 마운트/리렌더 시 의도치 않은 setState 실행으로 상품 폼 회귀 유발", - "Form 자동 바인딩 setState 경합 수정 — 자동 바인딩과 수동 setState가 동시 실행 시 stale 값으로 덮어쓰이는 문제 해결", - "Form 자동 바인딩 bindingType 메타데이터 기반 boolean 바인딩 수정 — Toggle/Checkbox 등 boolean 컴포넌트에서 문자열 변환 대신 boolean 값 유지", - "SPA 네비게이션 시 `_global._local`에 이전 페이지 상태가 잔존하는 버그 수정 — 페이지 전환 시 `_local` 초기화 처리", - "DynamicRenderer `_computed` 참조 prop에서 캐시된 stale 값이 사용되는 버그 수정 — `_computed`/`$computed` 참조 시 `skipCache: true` 적용" - ] - }, - { - "name": "Changed", - "items": [ - "마이그레이션 통합 — 증분 마이그레이션을 테이블당 1개 create 마이그레이션으로 정리", - "시더 디렉토리 분리 — 설치 시더와 샘플 시더를 `Sample/` 하위로 분리, `--sample` 옵션 추가", - "라이선스 프로그램 명칭 정비", - "일정 관리 메뉴 기본 비활성화 (`config/core.php`)", - "Composer 의존성 업데이트 — Laravel Framework v12.54.1, Reverb v1.8.0, Symfony v7.4.6~7 등" - ] - } - ] - }, - { - "version": "7.0.0-alpha.9", - "date": "2026-03-13", - "categories": [ - { - "name": "Added", - "items": [ - "그누보드7 커스텀 에러 페이지 도입 — Laravel 기본 에러 페이지(401, 403, 404, 500, 503)를 그누보드7 스타일로 교체, 다크 모드 지원, 접근 경로 기반 홈 링크 분기 (admin → `/admin`, 기타 → `/`)", - "환경설정 > 고급 디버그 모드 활성화 시 개발 대시보드(`/dev`) 바로가기 버튼 추가", - "루트 LICENSE 파일 생성 (MIT 라이선스, 한국어 번역 + 영문 원문)", - "코어 라이선스/Changelog API 엔드포인트 추가 (`GET /api/admin/license`, `GET /api/admin/changelog`)", - "확장 라이선스 API 엔드포인트 추가 (`GET /api/admin/modules/{id}/license`, `GET /api/admin/plugins/{id}/license`, `GET /api/admin/templates/{id}/license`)", - "Admin 푸터 copyright 클릭 → 코어 라이선스 모달, 버전 클릭 → Changelog 모달 표시 기능", - "확장 상세 모달에서 라이선스 클릭 시 전문 모달 표시 기능 (모듈/플러그인/템플릿)", - "각 번들 확장에 LICENSE 파일 및 manifest `license` 필드 추가" - ] - }, - { - "name": "Changed", - "items": [ - "설치 화면 라이선스를 루트 LICENSE 파일로 통합 (`public/install/lang/license-ko.txt`, `license-en.txt` 삭제)", - "`/dev` 라우트 뷰 이름을 `dev-dashboard`로 변경" - ] - } - ] - }, - { - "version": "7.0.0-alpha.8", - "date": "2026-03-13", - "categories": [ - { - "name": "Changed", - "items": [ - "`.env.example.develop`과 `.env.example.production`을 `.env.example`로 통합 — 설치형 솔루션에 환경별 분리 불필요, Laravel/Vite 표준 준수", - "인스톨러에서 `.env.production` 백업 파일 생성 로직 제거 — Vite mode 기반 로딩 충돌 방지" - ] - }, - { - "name": "Improved", - "items": [ - "코어/확장 업데이트 시 composer install 스킵 최적화 — composer.json/composer.lock 미변경 시 composer install 및 vendor 디렉토리 교체를 건너뛰어 업데이트 시간 단축" - ] - }, - { - "name": "Fixed", - "items": [ - "확장 수동 설치 모달에서 설치 실패 시 상세 에러 사유(`errors.error`) 미표시 문제 수정 — 3개 모달에 상세 에러 P 요소 추가", - "`checkDependencies()` 복수 의존성 에러 수집 — 첫 번째 미충족 의존성에서 즉시 throw 대신 전체 수집 후 줄바꿈 연결 (ModuleManager, PluginManager, TemplateManager)", - "ModuleController/PluginController 에러 반환 형식을 TemplateController와 통일 — `['error' => $e->getMessage()]` 형태", - "확장 설치 실패 시 `_pending/{identifier}` 디렉토리 자동 정리 — ModuleService, PluginService, TemplateService에 try-catch 추가" - ] - } - ] - }, - { - "version": "7.0.0-alpha.7", - "date": "2026-03-13", - "categories": [ - { - "name": "Added", - "items": [ - "템플릿 엔진 `multipart/form-data` 지원 — apiCall 핸들러 및 DataSourceManager에서 `contentType: \"multipart/form-data\"` 설정 시 params를 FormData로 자동 변환", - "ActionDispatcher: `fetchWithOptions()`에 FormData 변환 로직 추가, Content-Type 헤더 자동 생략 (브라우저 boundary 설정)", - "DataSourceManager: `toFormData()` 메서드 추가, 인증/비인증 경로 모두 multipart 지원", - "File/Blob 원본 유지, null/undefined 제외, 객체/배열 JSON.stringify 변환", - "`deepMergeWithState()` non-plain 객체(File/Blob/Date) 보호 — spread 복사로 인한 내부 데이터 소실 방지", - "`resolveParams()` non-plain 객체 재귀 해석 스킵 — File 객체가 빈 객체로 변환되는 문제 방지", - "컴포넌트 onChange raw value fallback — FileInput, Toggle 등 Event가 아닌 값을 전달하는 컴포넌트 지원" - ] - }, - { - "name": "Fixed", - "items": [ - "DataSourceManager `isMultipart` 변수 TDZ(Temporal Dead Zone) 버그 수정 — 선언 전 참조로 인한 ReferenceError 해결" - ] - } - ] - }, - { - "version": "7.0.0-alpha.6", - "date": "2026-03-13", - "categories": [ - { - "name": "Fixed", - "items": [ - "플러그인 환경설정 페이지 진입 시 404 오류 수정 — `registerPluginLayouts()` admin/user 분기 도입 후 루트 `settings.json`이 스킵되던 문제", - "플러그인 설정 레이아웃을 `resources/layouts/settings.json` → `resources/layouts/admin/plugin_settings.json`으로 이동하여 모듈과 동일한 구조로 통일", - "`AbstractPlugin::getSettingsLayout()` 경로 변경", - "`PluginSettingsService` 오버라이드 경로 및 주석 수정", - "영향받는 플러그인: sirsoft-daum_postcode, sirsoft-marketing, sirsoft-tosspayments" - ] - }, - { - "name": "Changed", - "items": [ - "플러그인 설정 레이아웃 규정 문서(`plugin-development.md`) 경로/설명 업데이트" - ] - } - ] - }, - { - "version": "7.0.0-alpha.5", - "date": "2026-03-12", - "categories": [ - { - "name": "Added", - "items": [ - "확장(모듈/플러그인/템플릿) changelog GitHub 원격 소스 지원 — `source=github` 시 GitHub에서 CHANGELOG.md 조회, 실패 시 bundled 폴백", - "`ChangelogParser::parseFromString()`, `getVersionRangeFromString()` 문자열 기반 파싱 메서드 추가", - "`ChangelogRequest` validation 에러 다국어 메시지 추가 (source, version format, required_with)", - "플러그인 GitHub/ZIP 설치 기능 추가 — 모듈/템플릿에는 있지만 플러그인에 누락되어 있던 기능 신규 구현", - "`PluginService::installFromGithub()`, `installFromZipFile()`, `findPluginJson()` 메서드 추가", - "`PluginController::installFromFile()`, `installFromGithub()` 엔드포인트 추가", - "`InstallPluginFromGithubRequest`, `InstallPluginFromFileRequest` FormRequest 추가", - "`install-from-file`, `install-from-github` API 라우트 추가", - "`ExtensionManager::hasComposerDependenciesAt(string $path)` 메서드 추가 — 임의 경로의 Composer 의존성 확인", - "모듈/플러그인 설치 시 `_pending` Composer 선행 설치 로직 추가 — 활성 디렉토리 이관 전 의존성 설치" - ] - }, - { - "name": "Changed", - "items": [ - "확장 수동 설치 모달 3개(모듈/플러그인/템플릿) UI 통일 — TabNavigation underline, 에러 배너, 필드별 적색 테두리", - "확장 GitHub/ZIP 설치 공통 로직을 `GithubHelper`, `ZipInstallHelper`로 추출하여 3개 Service 중복 제거", - "`CoreUpdateService` GitHub 관련 protected 메서드를 `GithubHelper` 위임으로 리팩토링", - "확장 목록 PageHeader에서 새로고침 버튼 제거, 업데이트 확인 버튼으로 통일", - "코어 업데이트 `core_pending` 고정 경로 → `core_{Ymd_His}` 타임스탬프 기반 격리 디렉토리로 변경", - "확장(모듈/플러그인/템플릿) 업데이트에 `_pending/{identifier}_{timestamp}/` 스테이징 패턴 도입", - "확장 업데이트 시 스테이징 내에서 composer install 실행 (활성 디렉토리 무영향)", - "확장 GitHub 다운로드 코드를 코어와 동일한 패턴으로 통합 (인증 헤더, 폴백 체인, 타임아웃)", - "GitHub 다운로드 공용 로직을 `ExtensionManager`로 추출하여 3개 Manager 중복 제거", - "`config/app.php`에서 `preserves` 설정 키 제거 (타임스탬프 격리로 불필요)", - "모듈/플러그인 GitHub/ZIP 설치 흐름을 `temp → _pending → composer install → 활성 디렉토리` 패턴으로 통일" - ] - }, - { - "name": "Fixed", - "items": [ - "코어 업데이트 결과 모달에서 from/to 버전 파라미터가 전달되지 않는 버그 수정 — `params.params` → `params.query`", - "플러그인에 GitHub/ZIP 설치 기능이 누락되어 있던 결함 수정", - "파일 복사 시 퍼미션/소유자/소유그룹 미보존 문제 수정 — `File::copy()` → `FilePermissionHelper::copyFile()` 교체 (6개 위치)", - "Windows 환경에서 확장/코어 업데이트 후 `_pending` 하위에 빈 디렉토리가 잔존하는 문제 수정 — `cleanupStaging()`에 3단계 retry 로직 추가", - "코어 업데이트 후 vendor 교체 시 stale `packages.php`/`services.php`로 인한 500 오류 수정 — `clearAllCaches()`에 컴파일 캐시 삭제 + `package:discover` + `extension:update-autoload` 추가", - "코어 업데이트 시 `bootstrap/cache` 디렉토리가 소스에서 덮어씌워지는 문제 수정 — excludes에 `bootstrap/cache` 추가" - ] - } - ] - }, - { - "version": "7.0.0-alpha.4", - "date": "2026-03-12", - "categories": [ - { - "name": "Added", - "items": [ - "코어 업그레이드 스텝 검증용 샘플 마이그레이션 및 업그레이드 스텝 추가", - "코어 업그레이드 스텝 경로를 `database/upgrades/` → `upgrades/`로 변경", - "업그레이드 스텝 실행을 프로그레스바 별도 단계로 분리 및 터미널 피드백 추가" - ] - }, - { - "name": "Changed", - "items": [ - "`--source` 모드에서 원본 소스 디렉토리를 `_pending`으로 복제 후 작업 (원본 보호)", - "Step 8: 운영 디렉토리 `composer install` 재실행 → `_pending/vendor/` 복사로 변경 (효율화)" - ] - }, - { - "name": "Fixed", - "items": [ - "`--source` 모드에서 소스 버전 감지 시 현재 `env()` 대신 `config/app.php` default 값 파싱", - "코어 업데이트 targets에 `upgrades` 디렉토리 누락 수정" - ] - } - ] - }, - { - "version": "7.0.0-alpha.3", - "date": "2026-03-12", - "categories": [ - { - "name": "Fixed", - "items": [ - "`.gitattributes`의 `CHANGELOG.md export-ignore`가 모든 CHANGELOG 파일을 릴리스 아카이브에서 제외하던 문제 수정", - "PharData(tar ustar) 100바이트 경로 제한으로 324개 파일이 누락되어 orphan 삭제가 발생하던 버그 수정" - ] - }, - { - "name": "Removed", - "items": [ - "PharData 아카이브 추출 전략 제거 — tar ustar 형식의 100바이트 경로 제한은 근본적 해결 불가" - ] - }, - { - "name": "Added", - "items": [ - "`core:update --source=` 옵션 추가 — ZipArchive/unzip 불가 환경에서 수동 업데이트 지원", - "상대경로, 절대경로, Windows 경로 모두 지원", - "소스 디렉토리의 그누보드7 프로젝트 유효성 검증 (`config/app.php` + `version` 키)", - "업데이트 안내 모달에 수동 업데이트 가이드 섹션 추가", - "시스템 요구사항 미충족 시 `--source` 옵션 안내 메시지 추가" - ] - } - ] - }, - { - "version": "7.0.0-alpha.2", - "date": "2026-03-12", - "categories": [ - { - "name": "Fixed", - "items": [ - "코어 업데이트 확인 시 업데이트할 버전이 없으면 피드백 없던 문제 수정", - "`openModal` 호출 형식 수정 (`params.id` → `target`)", - "모달 데이터 바인딩 경로 수정 (`_local` → `$parent._local`)", - "코어 업데이트 명령어 에러 미출력 수정", - "`.env` 예제 파일에서 `G7_UPDATE_TARGETS` 하드코딩 제거" - ] - }, - { - "name": "Added", - "items": [ - "코어 업데이트 targets 확장 및 orphan 삭제 로직 추가", - "코어 업데이트 `--local` 옵션 및 설정 기반 제외/보존 추가", - "환경설정 고급 탭 코어 업데이트 설정 섹션 추가" - ] - } - ] - }, - { - "version": "7.0.0-alpha.1", - "date": "2026-03-07", - "categories": [ - { - "name": "Added", - "items": [ - "Laravel 12 기반 CMS 플랫폼 초기 구조 설계 및 구현", - "Service-Repository 패턴 기반 계층 분리 아키텍처 구축", - "CoreServiceProvider를 통한 인터페이스-구현체 바인딩 시스템", - "ResponseHelper를 통한 통일된 API 응답 형식 (success/error/paginated)", - "AdminBaseController / AuthBaseController / PublicBaseController 컨트롤러 계층 구조", - "FormRequest + Custom Rule 기반 검증 시스템", - "BaseApiResource 상속 기반 API 리소스 패턴", - "PHP 8.2+ Backed Enum 기반 상태/타입/분류 관리", - "모듈(Module) 시스템: 디렉토리 스캔 기반 자동 발견, 설치/활성화/비활성화/삭제 관리", - "플러그인(Plugin) 시스템: 모듈 의존 기반 기능 확장, 설정 UI(settings.json) 지원", - "템플릿(Template) 시스템: Admin/User 타입 분리, JSON 기반 레이아웃, 컴포넌트 레지스트리", - "ExtensionManager: 모듈/플러그인/템플릿 통합 관리 (설치, 업데이트, 삭제)", - "HookManager: Action/Filter 훅 시스템 (doAction, applyFilters, HookListenerInterface)", - "확장 업데이트 시스템: _bundled/_pending 디렉토리 구조, GitHub/로컬 업데이트 감지 및 적용", - "확장 백업/복원 시스템 (ExtensionBackupHelper)", - "확장 상태 가드 (ExtensionStatusGuard): Installing/Updating/Uninstalled 상태 관리", - "확장 권한 동기화 (ExtensionRoleSyncHelper): 설치/삭제 시 역할-권한 자동 동기화", - "확장 메뉴 동기화 (ExtensionMenuSyncHelper): 모듈 메뉴 자동 등록/해제", - "확장 오토로드 시스템: 런타임 Composer 오토로드 (composer.json 수정 불필요)", - "확장 Composer 의존성 관리 (extension:composer-install 커맨드)", - "확장 업그레이드 스텝 시스템 (UpgradeStepInterface, UpgradeContext)", - "확장 설정 시스템 (SettingsMigrator): 모듈/플러그인별 독립 설정 관리", - "확장 소유권 시스템: extension_type/extension_identifier 기반 리소스 귀속", - "확장 에셋 시스템: module.json 매니페스트 기반 JS/CSS 자동 로딩", - "확장 Changelog 시스템: ChangelogParser 헬퍼 + API 엔드포인트 + 관리 화면 인라인 표시", - "확장 빌드 시스템: Artisan 커맨드 기반 (module:build, template:build, plugin:build)", - "확장 캐시 관리: 확장별 독립 캐시 + 일괄 클리어 커맨드", - "DynamicRenderer: JSON 레이아웃 기반 React 컴포넌트 동적 렌더링", - "DataBindingEngine: `{{expression}}` 문법, Optional Chaining(`?.`), Nullish Coalescing(`??`) 지원", - "ActionDispatcher: 20+ 내장 핸들러 (navigate, apiCall, setState, openModal, closeModal, sequence, condition, replaceUrl, scrollTo, copyToClipboard, downloadFile, debounce, emit, showToast, confirm, validate, submit, reset, filter, sort 등)", - "ComponentRegistry: 컴포넌트 등록/검색/해석 시스템 (기본/집합/레이아웃 타입)", - "TranslationEngine: `$t:key` 즉시 평가 / `$t:defer:key` 지연 평가 다국어 바인딩", - "LayoutLoader: JSON 레이아웃 로딩, 캐싱, ETag 기반 조건부 요청", - "Router: SPA 라우팅, 동적 경로 파라미터(`{{route.id}}`), 쿼리스트링 관리", - "레이아웃 상속 시스템: `extends` 기반 베이스 상속 + `type: \"slot\"` 위치에 컨텐츠 삽입", - "Partial 시스템: 레이아웃 모듈화, 컴포넌트 치환 (data_sources/computed/modals/state 미지원)", - "조건부 렌더링: `if` 속성 기반 표현식 평가 (type: \"conditional\" 미지원)", - "반복 렌더링: `iteration` 설정 (source, item_var, index_var, key)", - "반응형 레이아웃: `responsive` 속성 기반 breakpoint 오버라이드 (portable/compact/wide)", - "다크 모드: Tailwind `dark:` variant 기반 자동 전환", - "classMap: 조건부 CSS 클래스 매핑 (key → variants)", - "computed: 계산된 속성 시스템 (의존성 추적, 자동 재계산)", - "모달 시스템: `modals` 섹션 + openModal/closeModal 핸들러 (`_global.modalStack` 기반)", - "init_actions: 레이아웃 초기화 시 자동 실행 액션 (루트 레이아웃 레벨)", - "Named Actions: 액션 재사용 시스템 (DRY 패턴)", - "errorHandling: 전역/데이터소스별 에러 핸들링 설정", - "scripts: 레이아웃 레벨 커스텀 스크립트 로딩", - "globalHeaders: API 호출 공통 헤더 설정 (pattern 기반 매칭)", - "blur_until_loaded: 데이터 로딩 전 블러 처리", - "lifecycle: 컴포넌트 생명주기 훅 (onMount, onUnmount)", - "slots: 컴포넌트 슬롯 시스템", - "layout_extensions: 모듈/플러그인의 동적 UI 주입 포인트", - "isolatedState: 컴포넌트 상태 격리", - "데이터소스 조건부 로딩: `if` 표현식 기반 활성화/비활성화", - "Basic 컴포넌트 (27+): Div, Button, Input, Select, Form, A, H1~H6, Span, P, Img, Label, Textarea, Table, Thead, Tbody, Tr, Th, Td, Ul, Ol, Li, Hr, Strong, Em, Small, Pre, Code, Blockquote, Nav, Header, Footer, Main, Section, Article, Aside, Figure, FigCaption", - "Composite 컴포넌트: DataGrid, CardGrid, Pagination, Modal, SearchBar, Tabs, TabPanel, Accordion, Badge, Breadcrumb, Card, Checkbox, CheckboxGroup, DatePicker, Dropdown, FileUpload, Icon, Notification, Radio, RadioGroup, RangeSlider, Rating, Select (enhanced), Sidebar, Stepper, Switch, Tag, Timeline, Toast, Tooltip, PasswordInput, DynamicFieldList, SortableList, ColorPicker, NumberInput, TreeView, DateRangePicker", - "Layout 컴포넌트: FlexLayout, GridLayout, ScrollLayout, StickyLayout, Spacer, Container, AspectRatio", - "HtmlEditor: TinyMCE 기반 HTML 에디터 컴포넌트", - "Icon 컴포넌트: Font Awesome 6.4.x Free 아이콘 지원 (Solid 1,390개 / Regular 163개 / Brands 472개)", - "Alert, EmptyState, LoadingSpinner, Skeleton, StatusBadge, CopyButton 유틸리티 컴포넌트", - "전역 상태 (`_global`): 앱 전체 공유, 페이지 이동 시 유지", - "로컬 상태 (`_local`): 레이아웃 단위 격리", - "계산된 상태 (`_computed`): 의존성 기반 자동 재계산", - "폼 자동 바인딩: Form 컴포넌트 `stateKey` 기반 자동 상태 연동", - "setState 핸들러: target(global/local), 함수형 업데이트, 배열 조작 (push/filter/map)", - "상태 구독: `G7Core.state.subscribe` 기반 반응형 업데이트", - "initGlobal: 전역 상태 초기값 선언", - "모달 스코프 상태: `$parent._local` 스냅샷 기반 데이터 전달", - "Laravel Sanctum 하이브리드 인증 (세션 + Bearer 토큰)", - "AuthManager: 싱글톤 인증 상태 관리", - "자동 토큰 갱신: 401 응답 시 자동 리프레시 후 재시도", - "OptionalSanctumMiddleware: 선택적 인증 지원 (비인증 사용자 허용)", - "로그인/로그아웃 3단계 프로토콜 (토큰 삭제 → 세션 무효화 → Auth::logout)", - "비밀번호 재설정: 이메일 인증 기반 토큰 발급 및 검증", - "회원가입: 약관 동의, 이메일 인증 지원", - "Role 기반 권한 관리 (User → Role → Permission 3계층)", - "permission 미들웨어 체인 기반 접근 제어 (FormRequest authorize() 사용 금지)", - "확장별 권한 자동 등록/해제", - "역할별 메뉴 접근 제어 (role_menus 피벗 테이블)", - "슈퍼 관리자 (superadmin) 전체 권한 자동 부여", - "계층형 메뉴 구조 (parent_id 기반)", - "역할별 메뉴 가시성 제어", - "모듈 메뉴 자동 등록 (getAdminMenus 인터페이스)", - "메뉴 순서 관리 (드래그 앤 드롭 SortableList)", - "다국어 메뉴명 지원 (JSON 배열 형식)", - "API 엔드포인트 선언적 정의 (id, endpoint, method, params)", - "loading_strategy: immediate/lazy/manual 3가지 로딩 전략", - "데이터소스 의존성: depends_on 기반 연쇄 로딩", - "폴링: poll_interval 기반 주기적 갱신", - "조건부 로딩: `if` 표현식 기반 활성화", - "transform: 응답 데이터 변환 함수", - "cache_duration: 응답 캐싱", - "대시보드: 시스템 정보, 통계 위젯, 최근 활동", - "사용자 관리: CRUD, 역할 할당, 상태 관리 (활성/비활성/차단)", - "역할 관리: CRUD, 권한 할당, 다국어 역할명", - "권한 관리: 카테고리별 권한 목록, 역할별 권한 할당", - "메뉴 관리: 계층형 메뉴 편집, 순서 변경, 역할별 가시성 설정", - "모듈 관리: 설치/활성화/비활성화/삭제, 업데이트 확인, 상세 정보 모달, changelog 표시", - "플러그인 관리: 설치/활성화/비활성화/삭제, 설정 UI, 업데이트 확인, changelog 표시", - "템플릿 관리: 설치/활성화/비활성화/삭제, 업데이트 확인, changelog 표시", - "환경설정: 사이트 기본 정보, SEO 설정, 메일 설정, 보안 설정, 탭 레이아웃", - "일정 관리: 스케줄 CRUD, 캘린더 뷰, 카테고리 분류", - "메일 템플릿 관리: DB 기반 메일 템플릿 CRUD, 변수 치환, 미리보기 기능", - "메일 발송 로그: 발송 이력 조회, 상태 추적, 상세 정보 모달", - "시스템 정보: PHP/Laravel/DB 버전, 디스크 사용량, 확장 현황 표시", - "코어 업데이트: 버전 확인, 업데이트 가이드, changelog 인라인 표시, 백업 생성", - "로그인/회원가입/비밀번호 재설정 페이지", - "마이페이지: 프로필 수정, 비밀번호 변경", - "통합 검색 기능", - "게시판 뷰: 목록/상세/작성/수정 (board 모듈 연동)", - "에러 페이지: 403, 404, 500, 503 커스텀 에러 페이지", - "점검 모드(maintenance) 페이지", - "다단계 웹 설치 마법사 (환경 체크 → DB 설정 → 관리자 생성 → 완료)", - "SSE(Server-Sent Events) 기반 실시간 설치 진행 상태 표시", - "설치 롤백 기능: 실패 시 자동 복원 (마이그레이션/시더 롤백)", - "언어 선택 지원 (한국어/영어)", - "다크 모드 지원", - "환경 요구사항 자동 검증 (PHP 버전, 확장, 디렉토리 권한)", - "게시판 관리: CRUD, 카테고리, 스킨 설정, 권한 설정", - "게시글 관리: CRUD, 검색, 정렬, 페이지네이션", - "댓글 시스템: CRUD, 대댓글 (계층형), 답글 알림", - "신고 시스템: 게시글/댓글 신고, 관리자 처리 (승인/거절)", - "첨부파일: 이미지/파일 업로드, 다운로드, 인라인 표시", - "비밀글: 작성자/관리자만 열람 가능", - "블라인드/복원: 관리자 블라인드 처리 및 복원 기능", - "카드/갤러리 레이아웃: 다양한 목록 표시 형태 지원", - "게시판 권한: 읽기/쓰기/댓글/관리 권한 분리", - "인기글/최신글 위젯", - "SEO 메타 태그 자동 생성", - "상품 관리: CRUD, 옵션(사이즈/색상), 라벨, SEO 메타, 이미지 갤러리", - "상품 카테고리: 계층형 카테고리, 순서 관리, TreeView 편집", - "브랜드 관리: CRUD, 로고, 설명", - "주문 관리: 주문 목록, 상태 변경, 상세 정보, 주문 타임라인", - "쿠폰 시스템: 정액/정률 할인, 사용 조건 (최소 금액, 특정 상품), 유효기간, 사용 횟수 제한", - "배송 정책: 무료/유료/조건부 배송, 지역별 요금 설정", - "공통 정보 관리: 배송/교환/환불 안내, 판매자 정보", - "장바구니: 추가/수량 변경/삭제, 옵션별 관리, 품절 상품 알림", - "체크아웃: 주소 입력 (다음 우편번호 연동), 배송지 저장 체크박스, 결제수단 선택, 쿠폰 적용", - "주문 완료: 주문 번호, 결제 정보 요약, 장바구니 자동 비우기", - "상품 상세 페이지: 이미지 갤러리, 옵션 선택, 수량 입력, 장바구니 담기", - "상품 목록: 필터링 (카테고리/브랜드/가격), 정렬, 페이지네이션, 카드 그리드", - "위시리스트: 찜하기/해제 기능", - "상품 검색: 키워드 검색, 카테고리 필터 연동", - "CMS 페이지: CRUD, 슬러그 기반 URL 매핑", - "페이지 버전 관리: 버전 이력 조회, 이전 버전 복원", - "첨부파일: 이미지/파일 업로드 지원", - "검색 통합: 페이지 내용 통합 검색 지원", - "SEO: 메타 태그, Open Graph 설정", - "다음 우편번호 검색 API 연동", - "주소 선택 후 폼 자동 입력", - "체크아웃 배송지 입력 연동", - "토스페이먼츠 결제 API 연동", - "카드/계좌이체/가상계좌/무통장입금 결제 지원", - "결제 확인 (confirm) 프로세스", - "결제 성공/실패 콜백 처리", - "관리자 설정 UI (API 키 관리)", - "마케팅 동의 관리: 이메일 구독, 마케팅 동의, 제3자 제공 동의", - "MarketingConsent / MarketingConsentHistory 모델 및 서비스", - "MarketingConsentListener 훅 리스너 (회원가입/프로필 연동)", - "사용자 관리 화면 레이아웃 확장 (마케팅 동의 상세/폼/프로필)", - "회원가입 폼 마케팅 동의 항목 확장", - "플러그인 설정 UI", - "역할 기반 접근 제어 (마케팅 관리자)", - "다국어 지원 (ko, en)", - "휴대폰 인증, 아이핀 인증 등 본인인증 기능 제공", - "사용자 관리 화면 레이아웃 확장 (본인인증 상세/폼)", - "역할 기반 접근 제어 (본인인증 관리자)", - "다국어 지원 (ko, en)", - "관리자 대시보드 레이아웃", - "사이드바 네비게이션: 접기/펼치기, 계층형 메뉴, 활성 상태 표시", - "상단바: 사용자 정보, 알림 벨, 다크 모드 전환 토글", - "반응형 레이아웃 (데스크톱/태블릿/모바일)", - "관리자 전용 컴포넌트: DataGrid, CardGrid, SearchBar, 필터 패널 등", - "CRUD 화면 표준 레이아웃 (목록/생성/수정/상세)", - "모달 기반 상세 보기/수정 기능", - "토스트 알림 시스템", - "확인 다이얼로그 (삭제 확인 등)", - "환경설정 탭 레이아웃", - "모듈/플러그인/템플릿 관리 화면 (설치/업데이트/상세 모달)", - "사용자/역할/권한 관리 화면", - "메뉴 관리 화면 (드래그 앤 드롭 순서 변경)", - "일정 관리 캘린더 화면", - "메일 템플릿 관리 화면", - "메일 발송 로그 화면", - "시스템 정보 화면", - "코어 업데이트 가이드/결과 모달", - "사용자 메인 페이지 레이아웃", - "헤더/푸터 공통 레이아웃 (반응형)", - "로그인/회원가입/비밀번호 재설정 화면", - "마이페이지 레이아웃", - "게시판 목록/상세/작성/수정 화면", - "상품 목록/상세 화면", - "장바구니/체크아웃/주문완료 화면", - "검색 결과 화면", - "CMS 페이지 표시 화면", - "에러 페이지 (403, 404, 500, 503)", - "점검 모드(maintenance) 전용 페이지", - "반응형 레이아웃 (데스크톱/태블릿/모바일)", - "백엔드 다국어: `__()` 함수 기반, `lang/{locale}/*.php` 파일 구조", - "프론트엔드 다국어: `$t:key` 즉시 평가 바인딩, `$t:defer:key` 지연 평가 바인딩", - "컴포넌트 다국어: `G7Core.t()` API 제공", - "모듈 다국어: 모듈별 독립 언어 파일, 네임스페이스 분리 (`__('vendor-module::key')`)", - "템플릿 다국어: partial 언어 파일 분할 (admin.json, errors.json 등)", - "DB 다국어 필드: JSON 배열 형식 (`{\"ko\": \"...\", \"en\": \"...\"}`)", - "지원 로케일: 한국어(ko), 영어(en)", - "TranslatableField 트레이트: 모델 다국어 필드 자동 해석", - "ValidLayoutStructure: JSON 레이아웃 구조 검증 Custom Rule", - "WhitelistedEndpoint: API 엔드포인트 화이트리스트 검증 Custom Rule", - "NoExternalUrls: 외부 URL 차단 검증 Custom Rule", - "ComponentExists: 컴포넌트 존재 여부 검증 Custom Rule", - "CSRF 보호: Laravel 기본 CSRF 토큰 검증", - "XSS 방지: 출력 이스케이프, HTML sanitize 처리", - "SQL Injection 방지: Eloquent ORM, 파라미터 바인딩 사용", - "Rate Limiting: API 요청 제한 미들웨어", - "DB 기반 메일 템플릿: 제목/본문 DB 관리, 변수 치환 (Blade 문법)", - "Notification + Mailable 통합 패턴: BaseNotification 상속으로 보일러플레이트 제거", - "메일 발송 로그: 발송 이력 DB 기록, 수신자/상태/발송 시간 추적", - "메일 템플릿 사용자 오버라이드 추적 (user_overrides 필드)", - "BaseNotification: 모든 알림의 베이스 클래스 (via() 보일러플레이트 제거)", - "알림 채널: 메일, 데이터베이스, 브로드캐스트 지원", - "실시간 알림: Laravel Reverb (WebSocket) 기반 Broadcasting", - "StorageInterface: 모든 파일 저장의 추상화 인터페이스 (Storage::disk() 직접 호출 금지)", - "CoreStorageDriver: 코어 스토리지 구현체", - "확장별 독립 스토리지 공간 할당", - "CoreUpdateService: GitHub API 기반 코어 버전 확인 및 업데이트 감지", - "CoreUpdateController: 업데이트 상태 확인, 가이드 표시 API 엔드포인트", - "CoreBackupHelper: 업데이트 전 코어 파일 백업 생성", - "FilePermissionHelper: 파일/디렉토리 쓰기 권한 사전 검증", - "MaintenanceModePage 미들웨어: 점검 모드 시 전용 페이지 표시", - "업데이트 가이드 모달: changelog 인라인 표시, 단계별 안내", - "MCP 서버: 20+ 디버깅 도구 제공", - "기본 도구: g7-state, g7-actions, g7-cache, g7-diagnose, g7-lifecycle, g7-network, g7-form, g7-expressions, g7-logs", - "고급 분석: g7-datasources, g7-handlers, g7-events, g7-performance, g7-conditionals, g7-websocket", - "상태 계층/스타일: g7-renders, g7-state-hierarchy, g7-context-flow, g7-styles, g7-auth, g7-tailwind, g7-layout", - "Phase 8 심화: g7-computed, g7-nested-context, g7-modal-state, g7-sequence, g7-stale-closure, g7-change-detection", - "브라우저 상태 덤프: 런타임 상태 캡처 및 MCP 서버 전송", - "UI 패널: 브라우저 내 DevTools 패널 (실시간 상태/액션/로그 확인)", - "페이지네이션: offset/limit 기반 대용량 데이터 조회 지원", - "비주얼 레이아웃 에디터: 드래그 앤 드롭 기반 레이아웃 편집", - "PropertyPanel: 컴포넌트 속성 편집 UI", - "컴포넌트 팔레트: 사용 가능한 컴포넌트 드래그 목록", - "실시간 미리보기: 편집 즉시 렌더링 결과 확인", - "레이아웃 캐싱: 파싱된 레이아웃 JSON + 상속 병합 결과 캐시", - "확장 캐싱: 모듈/플러그인/템플릿 목록 및 메타데이터 캐시", - "번역 캐싱: 언어 파일 파싱 결과 캐시", - "ETag 지원: API 응답 조건부 캐시 검증 (304 Not Modified)", - "Gzip 압축: API 응답 자동 압축", - "Debounce 액션: 연속 이벤트 디바운스 처리 핸들러", - "Lazy 로딩: 데이터소스 지연 로딩 (스크롤/이벤트 트리거)", - "순환 참조 방지 메커니즘: 레이아웃 상속 무한 루프 감지", - "코어 테이블: users, roles, permissions, role_has_permissions, role_menus, menus, settings, mail_templates, mail_send_logs, schedules, modules, plugins, templates, template_layouts, template_layout_versions", - "모든 컬럼 한국어 comment 필수 규칙 적용", - "down() 메서드 완전 롤백 구현 필수 규칙 적용", - "다국어 필드 JSON 배열 형식 표준", - "MariaDB 호환성 지원", - "boolean/enum 컬럼 값 설명 comment 포함 규칙", - "PHPUnit 11.x: 백엔드 단위 테스트 (Unit) 및 기능 테스트 (Feature)", - "Vitest: 프론트엔드 컴포넌트/템플릿 엔진 테스트", - "createLayoutTest(): 레이아웃 JSON 렌더링 테스트 유틸리티 (Vitest + jsdom)", - "mockApi(): API 응답 모킹 유틸리티 (fetch 자동 모킹)", - "트러블슈팅 회귀 테스트: 해결된 사례별 자동 검증", - "테스트 커버리지: 모델, 서비스, 컨트롤러, 컴포넌트, 레이아웃 렌더링", - "Vite 기반 프론트엔드 빌드 (코드 분할, 트리 쉐이킹)", - "Artisan 커맨드 기반 빌드 관리:", - "`core:build`: 코어 템플릿 엔진 빌드 (--full, --watch 옵션)", - "`module:build`: 모듈 프론트엔드 빌드 (--all, --watch, --active 옵션)", - "`template:build`: 템플릿 빌드 (--all, --watch, --active 옵션)", - "`plugin:build`: 플러그인 빌드 (--all, --watch, --active 옵션)", - "_bundled 디렉토리 기본 빌드, --active 옵션으로 활성 디렉토리 빌드", - "--watch 모드: 파일 감시 기반 실시간 빌드 (활성 디렉토리 자동 사용)", - "모듈 관리: module:list, module:install, module:activate, module:deactivate, module:uninstall, module:composer-install, module:cache-clear, module:seed, module:check-updates, module:update, module:build", - "플러그인 관리: plugin:list, plugin:install, plugin:activate, plugin:deactivate, plugin:uninstall, plugin:composer-install, plugin:cache-clear, plugin:seed, plugin:check-updates, plugin:update, plugin:build", - "템플릿 관리: template:list, template:install, template:activate, template:deactivate, template:uninstall, template:cache-clear, template:check-updates, template:update, template:build", - "확장 공통: extension:composer-install, extension:update-autoload", - "코어: core:build", - "AI 에이전트 개발 가이드 (AGENTS.md): 핵심 원칙, 코딩 규칙, 디버깅 프로토콜", - "규정 문서 체계 (docs/): 백엔드 14개, 프론트엔드 59개, 확장 23개, 공통 4개 (총 100개)", - "AI 에이전트 자동화 도구: 검증/분석/구현 스킬 30+개, 인덱스 자동 생성 스크립트", - "트러블슈팅 가이드: 상태 관리, 캐시, 컴포넌트, 백엔드 문제 해결 사례집", - "코드 스타일: Laravel Pint (PSR-12) 자동 적용" - ] - } - ] - } - ] - } + "...": "(2개 키 생략, 총 3개)" } ``` @@ -1610,16 +95,7 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - - -_단건 응답: `data` 객체의 필드._ - -| 필드 | 타입 | 용도/설명 | -| --- | --- | --- | -| update_available | boolean | 업데이트 가능 여부 (최신 버전이 현재 버전보다 높으면 true) | -| current_version | string | 현재 설치된 코어 버전 | -| latest_version | string | GitHub 릴리스에서 확인한 최신 버전 (조회 실패 시 현재 버전으로 대체) | -| github_url | string | 버전 확인 대상 GitHub 저장소 URL (`config('app.update.github_url')`) | + **응답 예시** diff --git a/docs/backend/api/dashboard.md b/docs/backend/api/dashboard.md index 37fd1a3e..3cd9df32 100644 --- a/docs/backend/api/dashboard.md +++ b/docs/backend/api/dashboard.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Dashboard 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` @@ -41,12 +41,12 @@ Authorization: Bearer {YOUR_TOKEN} | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | | type | string | `user` | 활동 분류 (log_type Enum 값 — admin: 관리자, user: 사용자, system: 시스템) | -| icon | string | `circle-info` | 아이콘 식별자 (아이콘 클래스/이름) | +| icon | string | `right-to-bracket` | 아이콘 식별자 (아이콘 클래스/이름) | | icon_color | string | `green` | 분류별 색상 (log_type Enum variant() 파생 — admin: blue, user: green, system: gray) | -| title | string | `첨부파일 다운로드 (게시물: 237)` | 제목 | -| description | string | `API 문서 샘플 사용자` | 설명 (다국어 필드는 로케일별 값 객체) | -| time | string | `4시간 전` | 상대 시각 표시 (예: "24초 전" — diffForHumans() 산물) | -| timestamp | string | `2026-07-07T10:00:47+09:00` | 활동 발생 절대 시각 (created_at 을 사용자 타임존으로 변환한 ISO 8601) | +| title | string | `관리자 로그인` | 제목 | +| description | string | `최고관리자` | 설명 (다국어 필드는 로케일별 값 객체) | +| time | string | `2시간 전` | 상대 시각 표시 (예: "24초 전" — diffForHumans() 산물) | +| timestamp | string | `2026-08-04T19:00:10+09:00` | 활동 발생 절대 시각 (created_at 을 사용자 타임존으로 변환한 ISO 8601) | **응답 예시** @@ -59,50 +59,50 @@ HTTP/1.1 200 "success": true, "message": "최근 활동을 성공적으로 조회했습니다.", "data": [ + { + "type": "user", + "icon": "right-to-bracket", + "icon_color": "green", + "title": "관리자 로그인", + "description": "최고관리자", + "time": "2시간 전", + "timestamp": "2026-08-04T19:00:10+09:00" + }, { "type": "admin", - "icon": "circle-info", + "icon": "pen-to-square", "icon_color": "blue", - "title": "사용자 목록 조회", - "description": "API 문서 샘플 사용자", - "time": "2분 전", - "timestamp": "2026-07-08T12:12:12+09:00" + "title": "레이아웃 수정 (home)", + "description": "최고관리자", + "time": "4시간 전", + "timestamp": "2026-08-04T17:40:37+09:00" }, { "type": "admin", "icon": "circle-info", "icon_color": "blue", "title": "사용자 목록 조회", - "description": "API 문서 샘플 사용자", - "time": "6분 전", - "timestamp": "2026-07-08T12:08:26+09:00" + "description": "최고관리자", + "time": "4시간 전", + "timestamp": "2026-08-04T17:37:43+09:00" }, { - "type": "admin", - "icon": "circle-info", - "icon_color": "blue", - "title": "사용자 목록 조회", - "description": "API 문서 샘플 사용자", - "time": "9분 전", - "timestamp": "2026-07-08T12:04:38+09:00" + "type": "user", + "icon": "right-to-bracket", + "icon_color": "green", + "title": "관리자 로그인", + "description": "최고관리자", + "time": "4시간 전", + "timestamp": "2026-08-04T17:35:37+09:00" }, { - "type": "admin", + "type": "user", "icon": "circle-info", - "icon_color": "blue", - "title": "사용자 목록 조회", - "description": "API 문서 샘플 사용자", - "time": "43분 전", - "timestamp": "2026-07-08T11:31:03+09:00" - }, - { - "type": "admin", - "icon": "circle-info", - "icon_color": "blue", - "title": "사용자 목록 조회", - "description": "API 문서 샘플 사용자", - "time": "45분 전", - "timestamp": "2026-07-08T11:29:30+09:00" + "icon_color": "green", + "title": "주문 옵션 부분 취소 (옵션 ID: 1440)", + "description": "최고관리자", + "time": "4시간 전", + "timestamp": "2026-08-04T17:23:44+09:00" } ] } @@ -194,14 +194,14 @@ Authorization: Bearer {YOUR_TOKEN} | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| id | integer | `677` | 기본 키 (내부 식별자) | +| id | integer | `7559` | 기본 키 (내부 식별자) | | type | string | `apidoc.sample.event` | 알림 유형 식별자 (notification_type — 발송을 유발한 알림 정의 키) | | channel | string | `mail` | 발송 채널 (mail: 이메일, database: 인앱, sms 등 알림이 전달된 매체) | | recipient | string | `API 문서 샘플 사용자` | 수신자 표시명 (recipientUser 관계의 name → recipient_name → recipient_identifier 순 폴백) | | subject | string | `API 문서 샘플 알림` | 알림 제목 (subject 를 50자로 절삭한 값) | | status | string | `sent` | 발송 상태 (status Enum 값 — sent: 발송 성공, failed: 발송 실패, skipped: 발송 건너뜀) | -| time | string | `19시간 전` | 상대 시각 표시 (예: "24초 전" — diffForHumans() 산물) | -| timestamp | string | `2026-07-06T18:20:23+09:00` | 발송 절대 시각 (sent_at, 없으면 created_at 을 사용자 타임존으로 변환한 ISO 8601) | +| time | string | `3일 전` | 상대 시각 표시 (예: "24초 전" — diffForHumans() 산물) | +| timestamp | string | `2026-07-31T21:55:00+09:00` | 발송 절대 시각 (sent_at, 없으면 created_at 을 사용자 타임존으로 변환한 ISO 8601) | **응답 예시** @@ -215,14 +215,54 @@ HTTP/1.1 200 "message": "최근 알림을 성공적으로 조회했습니다.", "data": [ { - "id": 1, + "id": 7559, "type": "apidoc.sample.event", "channel": "mail", "recipient": "API 문서 샘플 사용자", "subject": "API 문서 샘플 알림", "status": "sent", - "time": "2시간 전", - "timestamp": "2026-07-08T09:41:24+09:00" + "time": "3일 전", + "timestamp": "2026-07-31T21:55:00+09:00" + }, + { + "id": 7558, + "type": "inquiry_received", + "channel": "mail", + "recipient": "송민성", + "subject": "", + "status": "skipped", + "time": "4일 전", + "timestamp": "2026-07-31T21:41:59+09:00" + }, + { + "id": 7557, + "type": "inquiry_received", + "channel": "mail", + "recipient": "석지은", + "subject": "", + "status": "skipped", + "time": "4일 전", + "timestamp": "2026-07-31T21:41:59+09:00" + }, + { + "id": 7556, + "type": "inquiry_received", + "channel": "mail", + "recipient": "심상수", + "subject": "", + "status": "skipped", + "time": "4일 전", + "timestamp": "2026-07-31T21:41:59+09:00" + }, + { + "id": 7555, + "type": "inquiry_received", + "channel": "mail", + "recipient": "임아름", + "subject": "", + "status": "skipped", + "time": "4일 전", + "timestamp": "2026-07-31T21:41:59+09:00" } ] } @@ -265,9 +305,9 @@ _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| cpu | object | `{"percentage":6,"color":"green"}` | CPU 사용률 정보 (percentage: 0~100 사용률, color: 임계 색상 — green<50, blue 50~69, yellow 70~89, red≥90) | -| memory | object | `{"percentage":96,"used":"30.1 GB","total":"31.5 GB","colo…` | 메모리 사용량 정보 (percentage 사용률, used/total: 사용량·총량 형식화 문자열, color: 임계 색상). 수집 불가 시 percentage 0·"알 수 없음"·color gray 폴백 | -| disk | object | `{"percentage":76,"used":"360.2 GB","total":"474.7 GB","co…` | 디스크 사용량 정보 (percentage 사용률, used/total: 사용량·총량 형식화 문자열, color: 임계 색상). 수집 불가 시 percentage 0·"알 수 없음"·color gray 폴백 | +| cpu | object | `{"percentage":21,"color":"green"}` | CPU 사용률 정보 (percentage: 0~100 사용률, color: 임계 색상 — green<50, blue 50~69, yellow 70~89, red≥90) | +| memory | object | `{"percentage":77,"used":"24.3 GB","total":"31.5 GB","colo…` | 메모리 사용량 정보 (percentage 사용률, used/total: 사용량·총량 형식화 문자열, color: 임계 색상). 수집 불가 시 percentage 0·"알 수 없음"·color gray 폴백 | +| disk | object | `{"percentage":86,"used":"408.2 GB","total":"474.7 GB","co…` | 디스크 사용량 정보 (percentage 사용률, used/total: 사용량·총량 형식화 문자열, color: 임계 색상). 수집 불가 시 percentage 0·"알 수 없음"·color gray 폴백 | **응답 예시** @@ -281,18 +321,18 @@ HTTP/1.1 200 "message": "시스템 리소스 정보를 성공적으로 조회했습니다.", "data": { "cpu": { - "percentage": 52, - "color": "blue" + "percentage": 21, + "color": "green" }, "memory": { - "percentage": 86, - "used": "27 GB", + "percentage": 77, + "used": "24.3 GB", "total": "31.5 GB", "color": "yellow" }, "disk": { - "percentage": 76, - "used": "362.6 GB", + "percentage": 86, + "used": "408.2 GB", "total": "474.7 GB", "color": "yellow" } @@ -337,11 +377,11 @@ _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| total_users | object | `{"count":156,"change_percent":15500,"change_display":"+15…` | 전체 사용자 수 (통계 객체는 count/추이 포함) | -| installed_modules | object | `{"total":3,"active":3}` | 설치된 모듈 집계 객체 (total/active) | -| active_plugins | object | `{"total":9,"active":9}` | 활성 플러그인 집계 객체 (total/active) | +| total_users | object | `{"count":82,"change_percent":0,"change_display":"+82","tr…` | 전체 사용자 수 (통계 객체는 count/추이 포함) | +| installed_modules | object | `{"total":4,"active":4}` | 설치된 모듈 집계 객체 (total/active) | +| active_plugins | object | `{"total":8,"active":8}` | 활성 플러그인 집계 객체 (total/active) | | installed_templates | object | `{"total":2,"active":2}` | 설치된 템플릿 집계 객체 (total/active) | -| language_packs | object | `{"total":20,"active":16}` | 언어팩 집계 객체 (active: 현재 활성 언어팩 수, total: 활성 + 미설치 번들 팩 수) | +| language_packs | object | `{"total":22,"active":2}` | 언어팩 집계 객체 (active: 현재 활성 언어팩 수, total: 활성 + 미설치 번들 팩 수) | | system_status | object | `{"status":"normal","label":"정상","all_services_running":true}` | 시스템 상태 객체 (status: normal 정상 / warning 경고, label: 상태 다국어 라벨, all_services_running: 전체 서비스 정상 동작 여부) | **응답 예시** @@ -356,26 +396,26 @@ HTTP/1.1 200 "message": "대시보드 통계를 성공적으로 조회했습니다.", "data": { "total_users": { - "count": 2, + "count": 82, "change_percent": 0, - "change_display": "+2", + "change_display": "+82", "trend": "up" }, "installed_modules": { - "total": 3, - "active": 3 + "total": 4, + "active": 4 }, "active_plugins": { - "total": 9, - "active": 0 + "total": 8, + "active": 8 }, "installed_templates": { "total": 2, - "active": 0 + "active": 2 }, "language_packs": { - "total": 17, - "active": 1 + "total": 22, + "active": 2 }, "system_status": { "status": "normal", diff --git a/docs/backend/api/extensions.md b/docs/backend/api/extensions.md index 8843fc97..10bfd426 100644 --- a/docs/backend/api/extensions.md +++ b/docs/backend/api/extensions.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Extensions 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` @@ -43,7 +43,7 @@ _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | | items | object | `{"plugins":[],"modules":[],"templates":[]}` | 코어 비호환으로 자동 비활성화된 확장을 타입별(`plugins`/`modules`/`templates`)로 묶은 목록. 각 원소는 식별자(`identifier`), 비호환 요구 버전(`incompatible_required_version`), 비활성화 시각(`deactivated_at`)을 가지며, 사용자가 dismiss했거나 hidden(학습용 샘플) 확장은 제외됨 | -| current_core_version | string | `7.0.1` | 현재 설치된 코어 버전 | +| current_core_version | string | `7.0.6` | 현재 설치된 코어 버전 | **응답 예시** @@ -61,7 +61,7 @@ HTTP/1.1 200 "modules": [], "templates": [] }, - "current_core_version": "7.0.1" + "current_core_version": "7.0.6" } } ``` @@ -88,7 +88,7 @@ HTTP/1.1 200 | 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | | --- | --- | --- | --- | --- | --- | -| type | path | string | 예 | module, plugin, template | 대상 확장의 타입 (module: 모듈, plugin: 플러그인, template: 템플릿). 타입에 맞는 Repository/Manager를 해석하는 데 사용되며 그 외 값은 422 | +| type | path | string | 예 | — | 대상 확장의 타입 (module: 모듈, plugin: 플러그인, template: 템플릿). 타입에 맞는 Repository/Manager를 해석하는 데 사용되며 그 외 값은 422 | | identifier | path | string | 예 | — | 대상 리소스의 식별자 | **요청 예시** @@ -102,7 +102,7 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - + **응답 예시** @@ -131,7 +131,7 @@ Authorization: Bearer {YOUR_TOKEN} | 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | | --- | --- | --- | --- | --- | --- | -| type | path | string | 예 | module, plugin, template | 대상 확장의 타입 (module: 모듈, plugin: 플러그인, template: 템플릿). 타입에 맞는 Repository/Manager를 해석하는 데 사용되며 그 외 값은 422 | +| type | path | string | 예 | — | 대상 확장의 타입 (module: 모듈, plugin: 플러그인, template: 템플릿). 타입에 맞는 Repository/Manager를 해석하는 데 사용되며 그 외 값은 422 | | identifier | path | string | 예 | — | 대상 리소스의 식별자 | **요청 예시** @@ -145,7 +145,7 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - + **응답 예시** diff --git a/docs/backend/api/identity.md b/docs/backend/api/identity.md index 89ba8027..4ce4506d 100644 --- a/docs/backend/api/identity.md +++ b/docs/backend/api/identity.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Identity 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` @@ -64,25 +64,25 @@ _목록 응답: `data.data[]` 배열 항목의 필드 + `data.pagination`._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| id | string | `e6ab6cd6-cdff-46cd-b4c2-b89dfda8745b` | 기본 키 (내부 식별자) | +| id | string | `06f077b4-fc53-4dad-81ff-846cd6426a0f` | 기본 키 (내부 식별자) | | provider_id | string | `inicis` | provider 식별자 (연관 리소스 참조) | -| purpose | string | `sensitive_action` | 인증 목적 (signup/password_reset/self_update/sensitive_action/login 또는 모듈 정의 목적) | -| channel | string | `ipin` | 인증에 사용된 전송 채널 (email 등 코어 채널 또는 모듈 provider 자체 식별자) | -| user_id | integer | `130` | user 식별자 (연관 리소스 참조) | -| target_hash | string | `d88d36166bbeffc41eb6994e390f4715280c1…` | 인증 대상 해시 (SHA256(email\|phone) — PII 원본 저장 회피) | -| status | string | `cancelled` | 인증 시도 결과 상태 (requested/sent/processing/verified/failed/expired/cancelled/policy_violation_logged) | -| attempts | integer | `0` | 현재까지 누적된 검증 시도 횟수 | -| max_attempts | integer | `0` | 허용되는 최대 검증 시도 횟수 (초과 시 실패 처리) | -| ip_address | string | `127.0.0.1` | 요청/행위가 발생한 IP 주소 | +| purpose | string | `checkout_verification` | 인증 목적 (signup/password_reset/self_update/sensitive_action/login 또는 모듈 정의 목적) | +| channel | string | `email` | 인증에 사용된 전송 채널 (email 등 코어 채널 또는 모듈 provider 자체 식별자) | +| user_id | integer | `1209` | user 식별자 (연관 리소스 참조) | +| target_hash | string | `eebbbc80cbc838162aaa9437c7aba415dc81d…` | 인증 대상 해시 (SHA256(email\|phone) — PII 원본 저장 회피) | +| status | string | `verified` | 인증 시도 결과 상태 (requested/sent/processing/verified/failed/expired/cancelled/policy_violation_logged) | +| attempts | integer | `2` | 현재까지 누적된 검증 시도 횟수 | +| max_attempts | integer | `5` | 허용되는 최대 검증 시도 횟수 (초과 시 실패 처리) | +| ip_address | string | `59.16.7.205` | 요청/행위가 발생한 IP 주소 | | user_agent | string | `Mozilla/5.0 (Windows NT 10.0; Win64; …` | 요청 클라이언트의 User-Agent 문자열 | -| origin_type | string | `api` | 인증 트리거 출처 유형 (route/hook/policy/middleware/api/custom/system — IdentityOriginType) | -| origin_identifier | string | `/api/identity/challenges` | 실제 트리거 경로/훅명 (예: PUT /api/me/password, core.user.before_update) | -| origin_policy_key | null | `null` | 정책이 인증을 강제한 경우 해당 identity_policies.key (정책 외 트리거는 null) | -| properties | null | `null` | 요청 페이로드 요약 (감사용 부가 정보, 없으면 null) | -| metadata | object | `{"mid":"INIiasTest","reqSvcCd":"03","mtxid_hash":"981546e…` | 프로바이더 내부 데이터 (코드 해시·외부 인증 식별자 등, PII 원본 미포함) | -| created_at | string | `2026-06-27 19:06:17` | 생성 일시 | -| verified_at | string | `2026-06-27 18:58:30` | verified 일시 | -| expires_at | string | `2026-06-27 19:21:17` | expires 일시 | +| origin_type | string | `policy` | 인증 트리거 출처 유형 (route/hook/policy/middleware/api/custom/system — IdentityOriginType) | +| origin_identifier | string | `sirsoft-ecommerce` | 실제 트리거 경로/훅명 (예: PUT /api/me/password, core.user.before_update) | +| origin_policy_key | string | `sirsoft-ecommerce.checkout.before_pay` | 정책이 인증을 강제한 경우 해당 identity_policies.key (정책 외 트리거는 null) | +| properties | object | `{"code_length":6}` | 요청 페이로드 요약 (감사용 부가 정보, 없으면 null) | +| metadata | object | `{"hint_used":"text_code"}` | 프로바이더 내부 데이터 (코드 해시·외부 인증 식별자 등, PII 원본 미포함) | +| created_at | string | `2026-07-30 21:14:43` | 생성 일시 | +| verified_at | string | `2026-07-30 21:21:16` | verified 일시 | +| expires_at | string | `2026-07-30 21:29:43` | expires 일시 | **응답 예시** @@ -95,15 +95,67 @@ HTTP/1.1 200 "success": true, "message": "messages.success", "data": { - "data": [], + "data": [ + { + "id": "06f077b4-fc53-4dad-81ff-846cd6426a0f", + "provider_id": "inicis", + "purpose": "checkout_verification", + "channel": "email", + "user_id": null, + "target_hash": "eebbbc80cbc838162aaa9437c7aba415dc81d33a7c2c8ad21ef11e102d8ac16b", + "status": "verified", + "attempts": 2, + "max_attempts": 5, + "ip_address": "59.16.7.205", + "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0", + "origin_type": "policy", + "origin_identifier": "sirsoft-ecommerce", + "origin_policy_key": "sirsoft-ecommerce.checkout.before_pay", + "properties": { + "code_length": 6 + }, + "metadata": { + "hint_used": "text_code" + }, + "created_at": "2026-07-30 21:14:43", + "verified_at": "2026-07-30 21:21:16", + "expires_at": "2026-07-30 21:29:43" + }, + { + "id": "2cd9f0a8-44da-447b-96b9-81fe2174016f", + "provider_id": "g7:core.mail", + "purpose": "checkout_verification", + "channel": "email", + "user_id": null, + "target_hash": "08f2668d172fd18e4103d0e13de6159b3f7112f4c427918104af923c6c086790", + "status": "verified", + "attempts": 3, + "max_attempts": 5, + "ip_address": "180.182.50.7", + "user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36", + "origin_type": "policy", + "origin_identifier": "sirsoft-ecommerce", + "origin_policy_key": "sirsoft-ecommerce.checkout.before_pay", + "properties": { + "code_length": 6 + }, + "metadata": { + "hint_used": "text_code" + }, + "created_at": "2026-07-30 16:58:29", + "verified_at": "2026-07-30 16:59:06", + "expires_at": "2026-07-30 17:13:29" + }, + "... (총 25건 중 2건 표시)" + ], "pagination": { "current_page": 1, - "last_page": 1, + "last_page": 8, "per_page": 25, - "total": 0, - "from": null, - "to": null, - "has_more_pages": false + "total": 200, + "from": 1, + "to": 25, + "has_more_pages": true }, "abilities": { "can_purge": true @@ -157,7 +209,7 @@ _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| purged_count | integer | `0` | purged 개수 (집계) | +| purged_count | integer | `200` | purged 개수 (집계) | | older_than_days | integer | `1` | 기준 경과 일수 (이 일수보다 오래된 대상 필터/집계) | **응답 예시** @@ -171,7 +223,7 @@ HTTP/1.1 200 "success": true, "message": "messages.success", "data": { - "purged_count": 0, + "purged_count": 200, "older_than_days": 1 } } @@ -232,20 +284,21 @@ _목록 응답: `data.data[]` 배열 항목의 필드 + `data.pagination`._ | number | integer | `1` | 목록에서의 순번 (페이지네이션 반영 행 번호 — HasRowNumber 파생) | | id | integer | `1` | 기본 키 (내부 식별자) | | provider_id | string | `g7:core.mail` | provider 식별자 (연관 리소스 참조) | -| scope_type | string | `provider_default` | 메시지 정의 스코프 (provider_default: 프로바이더 기본 / purpose: 목적별 / policy: 정책별 — IdentityMessageScopeType) | -| scope_value | string | `` | 스코프 값 (provider_default 빈 문자열 / purpose 목적 키 / policy 정책 키) | -| name | object | `{"ko":"메일 본인 확인 (기본)","en":"Mail Verification (default)",…` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | -| description | object | `{"ko":"특정 목적이 매칭되지 않을 때 사용되는 기본 메일 템플릿","en":"Fallback ma…` | 설명 (다국어 필드는 로케일별 값 객체) | +| scope_type | string | `purpose` | 메시지 정의 스코프 (provider_default: 프로바이더 기본 / purpose: 목적별 / policy: 정책별 — IdentityMessageScopeType) | +| scope_value | string | `checkout_verification` | 스코프 값 (provider_default 빈 문자열 / purpose 목적 키 / policy 정책 키) | +| name | object | `{"ko":"결제 시 본인 확인","en":"Checkout Verification"}` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | +| description | object | `{"ko":"결제 진행 전 본인\/성인 확인 인증 코드 메일","en":"Identity\/adult …` | 설명 (다국어 필드는 로케일별 값 객체) | | channels | array | `["mail"]` | 이 정의가 지원하는 활성 채널 목록 (현재 mail, 향후 sms 등 확장) | | variables | array | `[{"key":"code","description":"인증 코드 (text_code 흐름)"},{"ke…` | 템플릿에서 치환 가능한 변수 메타데이터 목록 (원소 key/description) | -| extension_type | string | `core` | 이 리소스를 소유한 확장의 타입 (core/module/plugin/template) | -| extension_identifier | string | `core` | 이 리소스를 소유한 확장의 식별자 | +| extension_type | string | `module` | 이 리소스를 소유한 확장의 타입 (core/module/plugin/template) | +| extension_identifier | string | `sirsoft-ecommerce` | 이 리소스를 소유한 확장의 식별자 | | is_active | boolean | `true` | active 여부 | | is_default | boolean | `true` | default 여부 | | user_overrides | array | `["name.ja"]` | 운영자가 시드 기본값에서 수정한 필드 경로 목록 (예: name.ja — 시더 재실행 시 보존 대상) | -| templates | array | `[{"id":1,"definition_id":1,"channel":"mail","subject":{"k…` | 이 정의에 속한 채널별 하위 메시지 템플릿 목록 (원소 id/channel/subject/body 등, eager load 시에만 포함) | -| created_at | string | `2026-05-27 15:20:18` | 생성 일시 | -| updated_at | string | `2026-06-30 13:33:16` | 최종 수정 일시 | +| templates | array | `[{"id":1,"definition_id":1,"channel":"mail","subject":{"k…` | 이 정의에 속한 채널별 하위 메시지 템플릿 목록 (원소 id/channel/subject/body 등). **목록 조회에서는 대표 1건만 실린다** — 전체 채널이 필요하면 단건 조회를 사용한다 | +| templates_count | integer | `1` | templates 개수 (집계) | +| created_at | string | `2026-07-30 18:47:09` | 생성 일시 | +| updated_at | string | `2026-07-30 18:47:09` | 최종 수정 일시 | | abilities | object | `{"can_update":true,"can_delete":false}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | **응답 예시** @@ -266,89 +319,25 @@ HTTP/1.1 200 "provider_id": "g7:core.mail", "scope_type": "purpose", "scope_value": "checkout_verification", - "name": { - "ko": "결제 시 본인 확인", - "en": "Checkout Verification" - }, - "description": { - "ko": "결제 진행 전 본인/성인 확인 인증 코드 메일", - "en": "Identity/adult verification code mail before checkout" - }, - "channels": [ - "mail" - ], - "variables": [ - { - "key": "code", - "description": "인증 코드 (text_code 흐름)" - }, - { - "key": "expire_minutes", - "description": "만료까지 남은 분" - }, - { - "key": "purpose_label", - "description": "인증 목적 라벨" - }, - { - "key": "app_name", - "description": "사이트명" - }, - { - "key": "site_url", - "description": "사이트 URL" - }, - { - "key": "recipient_email", - "description": "수신자 이메일" - } - ], - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "is_active": true, - "is_default": true, - "user_overrides": null, - "templates": [ - { - "id": 1, - "definition_id": 1, - "channel": "mail", - "subject": { - "ko": "[{app_name}] 결제 본인 확인 인증 코드", - "en": "[{app_name}] Checkout Verification Code" - }, - "body": { - "ko": "

결제 본인 확인

결제를 진행하기 위해 본인 확인이 필요합니다. 아래 인증 코드를 입력해 주세요.

{code}

이 코드는 {expire_minutes}분 후 만료됩니다.

본인이 결제를 진행하지 않았다면 이 메일을 무시하고 즉시 비밀번호를 변경해 주세요.

감사합니다,
{app_name}

", - "en": "

Checkout Verification

Please enter the code below to proceed with payment.

{code}

This code will expire in {expire_minutes} minutes.

If you did not initiate this payment, please ignore this email and change your password immediately.

Thank you,
{app_name}

" - }, - "is_active": true, - "is_default": true, - "user_overrides": null, - "updated_by": null, - "created_at": "2026-07-08 10:43:32", - "updated_at": "2026-07-08 10:43:32", - "abilities": { - "can_update": true, - "can_delete": true - } - } - ], - "created_at": "2026-07-08 10:43:32", - "updated_at": "2026-07-08 10:43:32", - "abilities": { - "can_update": true, - "can_delete": false - } - } + "...": "(14개 키 생략, 총 19개)" + }, + { + "number": 2, + "id": 2, + "provider_id": "g7:core.mail", + "scope_type": "provider_default", + "scope_value": "", + "...": "(14개 키 생략, 총 19개)" + }, + "... (총 6건 중 2건 표시)" ], "pagination": { "current_page": 1, "last_page": 1, "per_page": 25, - "total": 1, + "total": 6, "from": 1, - "to": 1, - "has_more_pages": false + "...": "(2개 키 생략, 총 7개)" }, "abilities": { "can_create": true, @@ -368,7 +357,7 @@ HTTP/1.1 200 -**설명** 본인인증 알림 메시지 정의(프로바이더별·목적별·정책별 메일/SMS 템플릿 묶음) 목록을 필터·검색하여 페이지네이션 조회합니다. `auth:sanctum` + `core.admin.identity.messages.read` 관리자 권한이 필요합니다. 확장이 `core.identity.message_definition.index_validation_rules` 필터 훅으로 추가 검색 파라미터를 등록할 수 있습니다. 응답 각 항목의 `user_overrides` 로 운영자가 시드 기본값에서 수정한 필드를, `templates` 로 채널별 하위 템플릿을 함께 내려 관리자 메시지 설정 화면을 구성할 때 사용합니다. +**설명** 본인인증 알림 메시지 정의(프로바이더별·목적별·정책별 메일/SMS 템플릿 묶음) 목록을 필터·검색하여 페이지네이션 조회합니다. `auth:sanctum` + `core.admin.identity.messages.read` 관리자 권한이 필요합니다. 확장이 `core.identity.message_definition.index_validation_rules` 필터 훅으로 추가 검색 파라미터를 등록할 수 있습니다. 응답 각 항목의 `user_overrides` 로 운영자가 시드 기본값에서 수정한 필드를 함께 내려 관리자 메시지 설정 화면을 구성할 때 사용합니다. 목록의 `templates` 에는 **대표 템플릿 1건만** 실리고, 전체 채널 수는 `templates_count` 로 제공합니다 — 정의마다 채널×로케일 템플릿 본문을 전부 실으면 목록을 여는 것만으로 전 행의 메일 본문이 전송되기 때문입니다. 채널별 템플릿 전체가 필요하면 단건 조회(`GET /api/admin/identity/messages/definitions/{definition}`)를 사용합니다. ### POST /api/admin/identity/messages/definitions @@ -425,7 +414,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -467,7 +456,7 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - + **응답 예시** @@ -515,20 +504,20 @@ _단건 응답: `data` 객체의 필드._ | --- | --- | --- | --- | | id | integer | `1` | 기본 키 (내부 식별자) | | provider_id | string | `g7:core.mail` | IDV 프로바이더 ID (예: g7:core.mail, kcp, portone) | -| scope_type | string | `provider_default` | 메시지 정의 스코프 (provider_default\|purpose\|policy) — App\Enums\IdentityMessageScopeType enum | -| scope_value | string | `` | 범위 값: provider_default 빈 문자열 / purpose 키 / policy 키 | -| name | object | `{"ko":"메일 본인 확인 (기본)","en":"Mail Verification (default)",…` | 다국어 표시명 ({"ko":"...", "en":"..."}) | -| description | object | `{"ko":"특정 목적이 매칭되지 않을 때 사용되는 기본 메일 템플릿","en":"Fallback ma…` | 다국어 설명 | +| scope_type | string | `purpose` | 메시지 정의 스코프 (provider_default\|purpose\|policy) — App\Enums\IdentityMessageScopeType enum | +| scope_value | string | `checkout_verification` | 범위 값: provider_default 빈 문자열 / purpose 키 / policy 키 | +| name | object | `{"ko":"결제 시 본인 확인","en":"Checkout Verification"}` | 다국어 표시명 ({"ko":"...", "en":"..."}) | +| description | object | `{"ko":"결제 진행 전 본인\/성인 확인 인증 코드 메일","en":"Identity\/adult …` | 다국어 설명 | | channels | array | `["mail"]` | 활성 채널 (현재 ["mail"], 향후 sms 등 확장) | | variables | array | `[{"key":"code","description":"인증 코드 (text_code 흐름)"},{"ke…` | 사용 가능 변수 메타데이터 ([{key, description}]) | -| extension_type | string | `core` | 확장 타입: core, module, plugin | -| extension_identifier | string | `core` | 확장 식별자 | +| extension_type | string | `module` | 확장 타입: core, module, plugin | +| extension_identifier | string | `sirsoft-ecommerce` | 확장 식별자 | | is_active | boolean | `true` | active 여부 | | is_default | boolean | `true` | default 여부 | -| user_overrides | array | `["name.ja"]` | 운영자가 수정한 필드명 목록 (예: ["name","is_active"]) | +| user_overrides | null | `null` | 운영자가 수정한 필드명 목록 (예: ["name","is_active"]) | | templates | array | `[{"id":1,"definition_id":1,"channel":"mail","subject":{"k…` | 이 정의에 속한 채널별 하위 메시지 템플릿 목록 (원소 id/channel/subject/body 등) | -| created_at | string | `2026-05-27 15:20:18` | 생성 일시 | -| updated_at | string | `2026-06-30 13:33:16` | 최종 수정 일시 | +| created_at | string | `2026-07-30 18:47:09` | 생성 일시 | +| updated_at | string | `2026-07-30 18:47:09` | 최종 수정 일시 | | abilities | object | `{"can_update":true,"can_delete":false}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | **응답 예시** @@ -605,16 +594,16 @@ HTTP/1.1 200 "is_default": true, "user_overrides": null, "updated_by": null, - "created_at": "2026-07-08 10:43:32", - "updated_at": "2026-07-08 10:43:32", + "created_at": "2026-07-30 18:47:09", + "updated_at": "2026-07-30 18:47:09", "abilities": { "can_update": true, "can_delete": true } } ], - "created_at": "2026-07-08 10:43:32", - "updated_at": "2026-07-08 10:43:32", + "created_at": "2026-07-30 18:47:09", + "updated_at": "2026-07-30 18:47:09", "abilities": { "can_update": true, "can_delete": false @@ -679,7 +668,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -740,8 +729,8 @@ _단건 응답: `data` 객체의 필드._ | is_default | boolean | `true` | default 여부 | | user_overrides | null | `null` | 운영자가 수정한 필드명 목록 (예: ["name","is_active"]) | | templates | array | `[{"id":1,"definition_id":1,"channel":"mail","subject":{"k…` | 템플릿 목록 (각 원소 identifier/name 등 — 템플릿 관계 파생) | -| created_at | string | `2026-07-08 10:43:32` | 생성 일시 | -| updated_at | string | `2026-07-08 10:43:32` | 최종 수정 일시 | +| created_at | string | `2026-07-30 18:47:09` | 생성 일시 | +| updated_at | string | `2026-07-30 18:47:09` | 최종 수정 일시 | | abilities | object | `{"can_update":true,"can_delete":false}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | **응답 예시** @@ -818,16 +807,16 @@ HTTP/1.1 200 "is_default": true, "user_overrides": null, "updated_by": null, - "created_at": "2026-07-08 10:43:32", - "updated_at": "2026-07-08 10:43:32", + "created_at": "2026-07-30 18:47:09", + "updated_at": "2026-07-30 18:47:09", "abilities": { "can_update": true, "can_delete": true } } ], - "created_at": "2026-07-08 10:43:32", - "updated_at": "2026-07-08 10:43:32", + "created_at": "2026-07-30 18:47:09", + "updated_at": "2026-07-30 18:47:09", "abilities": { "can_update": true, "can_delete": false @@ -889,9 +878,8 @@ _단건 응답: `data` 객체의 필드._ | is_active | boolean | `false` | active 여부 | | is_default | boolean | `true` | default 여부 | | user_overrides | array | `["is_active"]` | 운영자가 수정한 필드명 목록 (예: ["name","is_active"]) | -| templates | null | `null` | 템플릿 목록 (각 원소 identifier/name 등 — 템플릿 관계 파생) | -| created_at | string | `2026-07-08 10:43:32` | 생성 일시 | -| updated_at | string | `2026-07-08 12:14:36` | 최종 수정 일시 | +| created_at | string | `2026-07-30 18:47:09` | 생성 일시 | +| updated_at | string | `2026-08-04 21:53:38` | 최종 수정 일시 | | abilities | object | `{"can_update":true,"can_delete":false}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | **응답 예시** @@ -953,9 +941,8 @@ HTTP/1.1 200 "user_overrides": [ "is_active" ], - "templates": null, - "created_at": "2026-07-08 10:43:32", - "updated_at": "2026-07-08 12:14:36", + "created_at": "2026-07-30 18:47:09", + "updated_at": "2026-08-04 21:53:38", "abilities": { "can_update": true, "can_delete": false @@ -1011,7 +998,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -1069,7 +1056,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -1125,8 +1112,8 @@ _단건 응답: `data` 객체의 필드._ | is_default | boolean | `true` | default 여부 | | user_overrides | null | `null` | 운영자가 수정한 필드명 목록 (예: ["subject","body","is_active"]) | | updated_by | null | `null` | 최종 수정한 사용자 정보 (uuid/name — updated_by 관계 파생, 없으면 null) | -| created_at | string | `2026-07-08 10:43:32` | 생성 일시 | -| updated_at | string | `2026-07-08 10:43:32` | 최종 수정 일시 | +| created_at | string | `2026-07-30 18:47:09` | 생성 일시 | +| updated_at | string | `2026-07-30 18:47:09` | 최종 수정 일시 | | abilities | object | `{"can_update":true,"can_delete":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | **응답 예시** @@ -1155,8 +1142,8 @@ HTTP/1.1 200 "is_default": true, "user_overrides": null, "updated_by": null, - "created_at": "2026-07-08 10:43:32", - "updated_at": "2026-07-08 10:43:32", + "created_at": "2026-07-30 18:47:09", + "updated_at": "2026-07-30 18:47:09", "abilities": { "can_update": true, "can_delete": true @@ -1214,8 +1201,8 @@ _단건 응답: `data` 객체의 필드._ | is_default | boolean | `true` | default 여부 | | user_overrides | array | `["is_active"]` | 운영자가 수정한 필드명 목록 (예: ["subject","body","is_active"]) | | updated_by | null | `null` | 최종 수정한 사용자 정보 (uuid/name — updated_by 관계 파생, 없으면 null) | -| created_at | string | `2026-07-08 10:43:32` | 생성 일시 | -| updated_at | string | `2026-07-08 12:14:36` | 최종 수정 일시 | +| created_at | string | `2026-07-30 18:47:09` | 생성 일시 | +| updated_at | string | `2026-08-04 21:53:39` | 최종 수정 일시 | | abilities | object | `{"can_update":true,"can_delete":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | **응답 예시** @@ -1246,8 +1233,8 @@ HTTP/1.1 200 "is_active" ], "updated_by": null, - "created_at": "2026-07-08 10:43:32", - "updated_at": "2026-07-08 12:14:36", + "created_at": "2026-07-30 18:47:09", + "updated_at": "2026-08-04 21:53:39", "abilities": { "can_update": true, "can_delete": true @@ -1304,23 +1291,23 @@ _목록 응답: `data.data[]` 배열 항목의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| id | integer | `25` | 기본 키 (내부 식별자) | -| key | string | `test` | 정책 식별자 (고유, 예: core.profile.password_change) | +| id | integer | `29` | 기본 키 (내부 식별자) | +| key | string | `core.admin.extension_uninstall` | 정책 식별자 (고유, 예: core.profile.password_change) | | scope | string | `route` | 정책 적용 범위 (route: 라우트 패턴 / hook: Service 훅 / custom: 모듈 커스텀 키, IdentityPolicyScope) | -| target | string | `sdfsfsf` | 매칭 대상 (scope 에 따라 라우트명/URI 패턴, 훅 이름, 또는 custom key) | -| purpose | string | `inicis.adult_verification` | 이 정책이 요구하는 인증 목적 | -| provider_id | string | `inicis` | provider 식별자 (연관 리소스 참조) | +| target | string | `api.admin.{modules,plugins}.uninstall` | 매칭 대상 (scope 에 따라 라우트명/URI 패턴, 훅 이름, 또는 custom key) | +| purpose | string | `sensitive_action` | 이 정책이 요구하는 인증 목적 | +| provider_id | null | `null` | provider 식별자 (연관 리소스 참조) | | grace_minutes | integer | `0` | 재인증 유예 시간(분) — 최근 N분 이내 동일 목적 인증 성공 시 재인증 생략 (0=매번 요구) | -| enabled | boolean | `true` | 정책 사용 여부 (false 시 인증 미강제) | +| enabled | boolean | `false` | 정책 사용 여부 (false 시 인증 미강제) | | priority | integer | `100` | 정책 우선순위 (같은 대상에 여러 정책 매칭 시 작을수록 우선) | -| conditions | array | `[]` | 추가 매칭 조건 (역할/HTTP 메서드/파라미터 매칭 조건 JSON, 없으면 빈 배열) | -| source_type | string | `admin` | 정책 출처 (core/module/plugin: 선언형 / admin: 운영자 직접 등록, IdentityPolicySourceType) | -| source_identifier | string | `sirsoft-board` | 출처 식별자 (선언형 정책의 소유 확장 identifier) | -| applies_to | string | `both` | 적용 대상 사용자 (self: 일반 사용자 / admin: 관리자 / both: 모두, IdentityPolicyAppliesTo) | +| conditions | object | `{"changed_fields":["email","phone","mobile"]}` | 추가 매칭 조건 (역할/HTTP 메서드/파라미터 매칭 조건 JSON, 없으면 빈 배열) | +| source_type | string | `core` | 정책 출처 (core/module/plugin: 선언형 / admin: 운영자 직접 등록, IdentityPolicySourceType) | +| source_identifier | string | `core` | 출처 식별자 (선언형 정책의 소유 확장 identifier) | +| applies_to | string | `admin` | 적용 대상 사용자 (self: 일반 사용자 / admin: 관리자 / both: 모두, IdentityPolicyAppliesTo) | | fail_mode | string | `block` | 실패 시 동작 (block: HTTP 428 차단 / log_only: 감사 로그만 남기고 통과, IdentityPolicyFailMode) | | user_overrides | array | `[]` | 운영자가 선언 기본값에서 재정의한 필드 목록 (선언형 정책만 의미, 시더 재실행 시 보존) | -| created_at | string | `2026-06-26 16:33:04` | 생성 일시 | -| updated_at | string | `2026-06-26 16:33:04` | 최종 수정 일시 | +| created_at | string | `2026-07-31 00:15:09` | 생성 일시 | +| updated_at | string | `2026-07-31 00:15:09` | 최종 수정 일시 | | abilities | object | `{"can_update":true,"can_delete":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | **응답 예시** @@ -1336,52 +1323,52 @@ HTTP/1.1 200 "data": { "data": [ { - "id": 12, - "key": "sirsoft-board.post.user_create", - "scope": "hook", - "target": "sirsoft-board.post.before_create", + "id": 29, + "key": "core.admin.extension_uninstall", + "scope": "route", + "target": "api.admin.{modules,plugins}.uninstall", "purpose": "sensitive_action", "provider_id": null, "grace_minutes": 0, "enabled": false, "priority": 100, "conditions": null, - "source_type": "module", - "source_identifier": "sirsoft-board", - "applies_to": "self", + "source_type": "core", + "source_identifier": "core", + "applies_to": "admin", "fail_mode": "block", "user_overrides": [], - "created_at": "2026-07-08 10:44:35", - "updated_at": "2026-07-08 10:44:35", + "created_at": "2026-07-31 00:15:09", + "updated_at": "2026-07-31 00:15:09", "abilities": { "can_update": true, "can_delete": true } }, { - "id": 11, - "key": "sirsoft-board.report.create", + "id": 28, + "key": "core.admin.user_delete", "scope": "hook", - "target": "sirsoft-board.report.before_create", + "target": "core.user.before_delete", "purpose": "sensitive_action", "provider_id": null, - "grace_minutes": 30, + "grace_minutes": 0, "enabled": false, "priority": 100, "conditions": null, - "source_type": "module", - "source_identifier": "sirsoft-board", - "applies_to": "self", + "source_type": "core", + "source_identifier": "core", + "applies_to": "admin", "fail_mode": "block", "user_overrides": [], - "created_at": "2026-07-08 10:44:35", - "updated_at": "2026-07-08 10:44:35", + "created_at": "2026-07-31 00:15:09", + "updated_at": "2026-07-31 00:15:09", "abilities": { "can_update": true, "can_delete": true } }, - "... (총 12건 중 2건 표시)" + "... (총 21건 중 2건 표시)" ], "abilities": { "can_create": true, @@ -1391,7 +1378,7 @@ HTTP/1.1 200 "meta": { "current_page": 1, "per_page": 25, - "total": 12, + "total": 21, "last_page": 1 } } @@ -1465,7 +1452,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -1509,7 +1496,7 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - + **응답 예시** @@ -1582,7 +1569,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -1633,7 +1620,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -1715,6 +1702,8 @@ HTTP/1.1 200 "label": "인증 코드 길이", "type": "integer", "default": 6, + "min": 4, + "max": 10, "help": "발송되는 숫자 코드의 자릿수 (기본 6, 최소 4, 최대 10)." }, "from_address": { @@ -1724,6 +1713,63 @@ HTTP/1.1 200 "help": "비어 있으면 시스템 기본 발신자를 사용합니다." } } + }, + { + "id": "inicis", + "label": "KG이니시스 본인확인", + "channels": [ + "ipin" + ], + "channel_labels": { + "ipin": "아이핀" + }, + "render_hint": "text_code", + "is_available": true, + "abilities": { + "can_update": true + }, + "settings_schema": { + "is_test_mode": { + "label": "테스트 모드", + "type": "boolean", + "default": true + }, + "test_mid": { + "label": "Test MID", + "type": "text", + "default": "INIiasTest" + }, + "test_api_key": { + "label": "Test API Key", + "type": "password", + "default": "TGdxb2l3enJDWFRTbTgvREU3MGYwUT09" + }, + "live_mid": { + "label": "Live MID (SRB prefix)", + "type": "text", + "default": "" + }, + "live_api_key": { + "label": "Live API Key", + "type": "password", + "default": "" + }, + "duplicate_field": { + "label": "Duplicate Field", + "type": "radio", + "options": [ + "di", + "ci" + ], + "default": "di" + }, + "duplicate_block_enabled": { + "label": "중복 가입 차단", + "description": "활성화 시 본인인증을 통과한 사람이 이전에 다른 이메일로 가입한 적이 있으면 가입을 거부합니다. 가족 휴대폰 공유 또는 B2B 시나리오 등에서 한 사람이 여러 계정을 가입해야 한다면 비활성화하세요. 이 설정과 무관하게 동일 이메일 재가입은 항상 차단됩니다 (코어 기본 동작).", + "type": "toggle", + "default": true + } + } } ] } @@ -1778,7 +1824,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -1832,7 +1878,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -1865,7 +1911,7 @@ Content-Type: application/json **요청 예시** ```http -GET /api/identity/challenges/{challenge} HTTP/1.1 +GET /api/identity/challenges/006209fd-1b6c-40aa-a38f-9b907f1a4bca HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} (optional.sanctum: 비회원은 헤더 생략 가능) @@ -1877,18 +1923,37 @@ _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| id | string | `00484973-8cd3-4a1d-85f2-78361feb6f0d` | 기본 키 (내부 식별자) | -| status | string | `verified` | requested\|sent\|processing\|verified\|failed\|expired\|cancelled\|policy_violation_logged | -| provider_id | string | `inicis` | 프로바이더 식별자 (예: g7:core.mail, kcp) | +| id | string | `006209fd-1b6c-40aa-a38f-9b907f1a4bca` | 기본 키 (내부 식별자) | +| status | string | `requested` | requested\|sent\|processing\|verified\|failed\|expired\|cancelled\|policy_violation_logged | +| provider_id | string | `g7:core.mail` | 프로바이더 식별자 (예: g7:core.mail, kcp) | | purpose | string | `sensitive_action` | 인증 목적 (signup\|password_reset\|self_update\|sensitive_action\|login\|*module-defined*) — 코어 5종은 App\Enums\IdentityVerificationPurpose enum, 모듈/플러그인은 declaredPurposes 레지스트리 | | render_hint | string | `text_code` | 프론트 렌더 힌트 (text_code\|link\|external_redirect) | -| expires_at | string | `2026-05-12T18:14:19+00:00` | expires 일시 | +| expires_at | string | `2026-07-20T20:11:14+00:00` | expires 일시 | | max_attempts | integer | `5` | 허용 최대 시도 횟수 (정책 상수). 누적 시도 횟수(attempts)는 잠금 직전까지 시도 횟수를 맞춰 보는 데 쓰일 수 있어 본 응답에 포함하지 않는다 | | public_payload | array | `[]` | 프론트 렌더에 필요한 공개 안전 페이로드 (민감 metadata 제외, 프로바이더별 UI 힌트 — 없으면 빈 배열) | **응답 예시** - +```http +HTTP/1.1 200 +``` + +```json +{ + "success": true, + "message": "messages.success", + "data": { + "id": "006209fd-1b6c-40aa-a38f-9b907f1a4bca", + "status": "requested", + "provider_id": "g7:core.mail", + "purpose": "sensitive_action", + "render_hint": "text_code", + "expires_at": "2026-07-20T20:11:14+00:00", + "max_attempts": 5, + "public_payload": [] + } +} +``` **에러 응답** @@ -1916,7 +1981,7 @@ _단건 응답: `data` 객체의 필드._ **요청 예시** ```http -POST /api/identity/challenges/{challenge}/cancel HTTP/1.1 +POST /api/identity/challenges/006209fd-1b6c-40aa-a38f-9b907f1a4bca/cancel HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} (optional.sanctum: 비회원은 헤더 생략 가능) @@ -1924,11 +1989,11 @@ Authorization: Bearer {YOUR_TOKEN} (optional.sanctum: 비회원은 헤더 생 **응답 필드** (`data` 내부) - + **응답 예시** - + **에러 응답** @@ -1961,7 +2026,7 @@ Authorization: Bearer {YOUR_TOKEN} (optional.sanctum: 비회원은 헤더 생 **요청 예시** ```http -POST /api/identity/challenges/{challenge}/verify HTTP/1.1 +POST /api/identity/challenges/006209fd-1b6c-40aa-a38f-9b907f1a4bca/verify HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} (optional.sanctum: 비회원은 헤더 생략 가능) @@ -1975,11 +2040,11 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** - + **에러 응답** @@ -2090,6 +2155,21 @@ HTTP/1.1 200 "abilities": { "can_update": false } + }, + { + "id": "inicis", + "label": "KG이니시스 본인확인", + "channels": [ + "ipin" + ], + "channel_labels": { + "ipin": "아이핀" + }, + "render_hint": "text_code", + "is_available": true, + "abilities": { + "can_update": false + } } ] } @@ -2197,10 +2277,9 @@ HTTP/1.1 200 "id": "login", "label": "로그인 2단계 인증", "description": "2단계 인증을 켰을 때 비밀번호 확인 뒤 한 단계를 더 요구.", - "default_provider": "g7:core.mail", + "default_provider": null, "allowed_channels": [ - "mail", - "sms" + "email" ], "source_type": "core", "source_identifier": "core" @@ -2217,6 +2296,17 @@ HTTP/1.1 200 ], "source_type": "module", "source_identifier": "sirsoft-ecommerce" + }, + { + "id": "inicis.adult_verification", + "label": "성인인증 (KG이니시스 본인확인 전용)", + "description": "반드시 KG이니시스 provider 로만 매핑하세요. 메일/SMS provider 는 생년월일을 반환하지 않아 성인 여부 판정이 불가능합니다. 잘못 매핑 시 비성인 사용자에게 19금 컨텐츠가 노출될 수 있습니다.", + "default_provider": "inicis", + "allowed_channels": [ + "ipin" + ], + "source_type": "plugin", + "source_identifier": "sirsoft-verification_kginicis" } ] } diff --git a/docs/backend/api/language-packs.md b/docs/backend/api/language-packs.md index 72fe98d2..1ee081a8 100644 --- a/docs/backend/api/language-packs.md +++ b/docs/backend/api/language-packs.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Language Packs 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` @@ -52,22 +52,22 @@ _목록 응답: `data.data[]` 배열 항목의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| id | integer | `16` | 기본 키 (내부 식별자) | +| id | integer | `1` | 기본 키 (내부 식별자) | | identifier | string | `g7-core-en` | 언어팩 고유 식별자 ({vendor}-{scope}-{target?}-{locale}) | | vendor | string | `g7` | 언어팩 제작자 식별자 | | scope | string | `core` | 적용 대상 분류 | -| target_identifier | string | `gnuboard7-hello_module` | 대상 확장 식별자 (scope=core일 때 null) | +| target_identifier | string | `apidoc-sample-module` | 대상 확장 식별자 (scope=core일 때 null) | | locale | string | `en` | IETF BCP-47 locale 태그 | | locale_name | string | `EN` | 영문 언어명 | | locale_native_name | string | `English` | 원어 언어명 | | text_direction | string | `ltr` | 텍스트 방향 | -| version | string | `7.0.1` | 언어팩 버전 | +| version | string | `7.0.6` | 언어팩 버전 | | latest_version | string | `1.0.0` | 감지된 최신 배포 버전 | | target_version_constraint | null | `null` | 대상 확장 버전 제약 (semver) | | target_version_mismatch | boolean | `false` | 대상 버전 불일치 경고 플래그 | -| name | string | `API 문서 샘플 언어팩` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | +| name | string | `G7 코어 일본어 언어팩` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | | license | string | `MIT` | 라이선스 | -| description | string | `문서 실측용 언어팩` | 언어팩 설명 (다국어) | +| description | string | `G7 코어 일본어 언어팩 (번들)` | 언어팩 설명 (다국어) | | status | string | `active` | 언어팩 상태 | | is_protected | boolean | `true` | protected 여부 | | source_type | string | `built_in` | 설치 소스 유형 (zip/github/url/bundled/bundled_with_extension) | @@ -75,15 +75,15 @@ _목록 응답: `data.data[]` 배열 항목의 필드._ | source_url | string | `lang/en` | 설치 소스 URL 또는 경로 | | github_url | null | `null` | GitHub 저장소 URL (manifest 파생) | | github_changelog_url | null | `null` | GitHub 변경 이력(CHANGELOG) URL (manifest 파생) | -| bundled_identifier | string | `g7-module-gnuboard7-hello_module-ja` | 대응하는 번들 확장 식별자 (번들 원본 매칭용) | -| install_blocked_reason | string | `target_not_installed` | 설치가 차단된 사유 (차단 없으면 null) | +| bundled_identifier | string | `g7-core-ja` | 대응하는 번들 확장 식별자 (번들 원본 매칭용) | +| install_blocked_reason | string | `core_locale_missing` | 설치가 차단된 사유 (차단 없으면 null) | | files_missing | boolean | `false` | 드리프트 여부 — active 로 기록됐으나 설치본 파일이 부재해 런타임에 조용히 ko 로 폴백하는 상태이면 true | | bundled_source_available | boolean | `false` | 번들 소스(`lang-packs/_bundled/{identifier}`)가 실재해 재설치로 복구 가능하면 true. 설치 경로(source_type)와 무관하게 소스 실재 여부로 판정 | -| target_name | string | `게시판` | 대상 확장의 표시 이름 (scope+target_identifier 로 해석) | -| installed_at | string | `2026-07-03 19:20:23` | installed 일시 | -| activated_at | string | `2026-07-03 19:20:23` | activated 일시 | -| created_at | string | `2026-07-06 19:20:23` | 생성 일시 | -| updated_at | string | `2026-07-06 19:20:23` | 최종 수정 일시 | +| target_name | string | `Hello 모듈` | 대상 확장의 표시 이름 (scope+target_identifier 로 해석) | +| installed_at | string | `2026-07-28 22:55:00` | installed 일시 | +| activated_at | string | `2026-08-01 11:36:31` | activated 일시 | +| created_at | string | `2026-07-31 22:55:00` | 생성 일시 | +| updated_at | string | `2026-07-31 22:55:00` | 최종 수정 일시 | | has_update | boolean | `false` | update 여부 | | abilities | object | `{"can_activate":true,"can_deactivate":true,"can_uninstall…` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | @@ -109,7 +109,7 @@ HTTP/1.1 200 "locale_name": "EN", "locale_native_name": "English", "text_direction": "ltr", - "version": "7.0.1", + "version": "7.0.6", "latest_version": null, "target_version_constraint": null, "target_version_mismatch": false, @@ -140,56 +140,54 @@ HTTP/1.1 200 } }, { - "id": 1, - "identifier": "apidoc-sample-lang", - "vendor": "apidoc", + "id": null, + "identifier": "g7-core-ja", + "vendor": "sirsoft", "scope": "core", "target_identifier": null, - "locale": "fr", - "locale_name": "French", - "locale_native_name": "Français", + "locale": "ja", + "locale_name": "Japanese", + "locale_native_name": "日本語", "text_direction": "ltr", - "version": "1.0.0", - "latest_version": "1.0.0", + "version": "1.0.9", + "latest_version": null, "target_version_constraint": null, "target_version_mismatch": false, - "name": "API 문서 샘플 언어팩", + "name": "G7 코어 일본어 언어팩", "license": "MIT", - "description": "문서 실측용 언어팩", - "status": "active", + "description": "G7 코어 일본어 언어팩 (번들)", + "status": "uninstalled", "is_protected": false, "source_type": "bundled", "origin": "bundled", - "source_url": null, + "source_url": "g7-core-ja", "github_url": null, "github_changelog_url": null, - "bundled_identifier": null, + "bundled_identifier": "g7-core-ja", "install_blocked_reason": null, "files_missing": false, - "bundled_source_available": false, + "bundled_source_available": true, "target_name": null, - "installed_at": "2026-07-05 10:41:24", - "activated_at": "2026-07-05 10:41:24", - "created_at": "2026-07-08 10:41:24", - "updated_at": "2026-07-08 10:41:24", + "installed_at": null, + "activated_at": null, + "created_at": null, + "updated_at": null, "has_update": false, "abilities": { - "can_activate": true, - "can_deactivate": true, - "can_uninstall": true + "can_install": true } }, "... (총 25건 중 2건 표시)" ], "meta": { - "total": 43, + "total": 57, "active": 15, - "installed": 0, + "installed": 1, "inactive": 0, "error": 0, - "uninstalled": 10, + "uninstalled": 9, "current_page": 1, - "last_page": 2, + "last_page": 3, "per_page": 25 }, "abilities": { @@ -250,7 +248,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -290,7 +288,7 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - + **응답 예시** @@ -338,7 +336,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -393,7 +391,7 @@ Content-Disposition: form-data; name="auto_activate" **응답 필드** (`data` 내부) - + **응답 예시** @@ -442,7 +440,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -475,6 +473,8 @@ Content-Type: application/json | checksum | body | string | 아니오 | — | 무결성 검증 체크섬 (SHA-256) | | auto_activate | body | boolean | 아니오 | — | 설치 후 자동 활성화 여부. 외부 소스(파일·URL·GitHub)에서 설치할 때 이 값을 참으로 보내려면 `core.language_packs.manage` 권한이 함께 필요하며, 없으면 이 항목만 422 로 거부됩니다 — 항목을 빼면 설치는 그대로 진행되고 상태는 `installed` 로 남습니다. 설치(install)와 활성화(manage)를 별도 권한으로 두는 정책을 따르기 위함입니다 | +> 이 엔드포인트는 확장이 파라미터를 추가할 수 있습니다 (`core.language_packs.install_from_url_validation_rules`). + **요청 예시** ```http @@ -493,7 +493,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -543,7 +543,7 @@ Content-Type: application/octet-stream **응답 필드** (`data` 내부) - + **응답 예시** @@ -583,7 +583,7 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - + **응답 예시** @@ -617,7 +617,7 @@ Authorization: Bearer {YOUR_TOKEN} **요청 예시** ```http -DELETE /api/admin/language-packs/1?cascade=1 HTTP/1.1 +DELETE /api/admin/language-packs/{id}?cascade=1 HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -625,7 +625,7 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - + **응답 예시** @@ -660,7 +660,7 @@ Authorization: Bearer {YOUR_TOKEN} **요청 예시** ```http -GET /api/admin/language-packs/1 HTTP/1.1 +GET /api/admin/language-packs/{id} HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -668,117 +668,11 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) -_단건 응답: `data` 객체의 필드._ - -| 필드 | 타입 | 실측 예시값 | 용도/설명 | -| --- | --- | --- | --- | -| id | integer | `1` | 기본 키 (내부 식별자) | -| identifier | string | `apidoc-sample-lang` | 언어팩 고유 식별자 ({vendor}-{scope}-{target?}-{locale}) | -| vendor | string | `apidoc` | 언어팩 제작자 식별자 | -| scope | string | `core` | 적용 대상 분류 | -| target_identifier | null | `null` | 대상 확장 식별자 (scope=core일 때 null) | -| locale | string | `fr` | IETF BCP-47 locale 태그 | -| locale_name | string | `French` | 영문 언어명 | -| locale_native_name | string | `Français` | 원어 언어명 | -| text_direction | string | `ltr` | 텍스트 방향 | -| version | string | `1.0.0` | 언어팩 버전 | -| latest_version | string | `1.0.0` | 감지된 최신 배포 버전 | -| target_version_constraint | null | `null` | 대상 확장 버전 제약 (semver) | -| target_version_mismatch | boolean | `false` | 대상 버전 불일치 경고 플래그 | -| name | string | `API 문서 샘플 언어팩` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | -| license | string | `MIT` | 라이선스 | -| description | string | `문서 실측용 언어팩` | 설명 (다국어 필드는 로케일별 값 객체) | -| status | string | `active` | 상태 값 (도메인별 상태 집합 — 사람이 읽는 라벨은 status_label, UI 변형은 status_variant 참조) | -| is_protected | boolean | `false` | protected 여부 | -| source_type | string | `bundled` | 설치 소스 유형 (zip/github/url/bundled/bundled_with_extension) | -| origin | string | `bundled` | 출처 (설치/등록 원천 구분 값) | -| source_url | null | `null` | source URL | -| github_url | null | `null` | GitHub 저장소 URL (manifest 파생) | -| github_changelog_url | null | `null` | GitHub 변경 이력(CHANGELOG) URL (manifest 파생) | -| bundled_identifier | null | `null` | 대응하는 번들 확장 식별자 (번들 원본 매칭용) | -| install_blocked_reason | null | `null` | 설치가 차단된 사유 (차단 없으면 null) | -| target_name | null | `null` | 대상 확장의 표시 이름 (scope+target_identifier 로 해석) | -| installed_at | string | `2026-07-05 10:41:24` | installed 일시 | -| activated_at | string | `2026-07-05 10:41:24` | activated 일시 | -| created_at | string | `2026-07-08 10:41:24` | 생성 일시 | -| updated_at | string | `2026-07-08 10:41:24` | 최종 수정 일시 | -| has_update | boolean | `false` | update 여부 | -| abilities | object | `{"can_activate":true,"can_deactivate":true,"can_uninstall…` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | -| manifest | object | `{"name":{"en":"API Doc Sample Pack","ko":"API 문서 샘플 언어팩"}…` | language-pack.json 전체 스냅샷 | -| validation_summary | object | `{"target_version_mismatch":false,"depends_on_core_locale"…` | 검증 결과 요약 객체 (통과/실패 건수 등) | -| source_meta | object | `{"type":"bundled","url":null,"installed_by":6,"latest_ver…` | 원천 메타데이터 객체 (출처·경로·해석 정보) | -| changelog_entries | array | `[]` | 변경 이력 항목 목록 (버전별 변경 내용) | + **응답 예시** -```http -HTTP/1.1 200 -``` - -```json -{ - "success": true, - "message": "언어팩 목록을 조회했습니다.", - "data": { - "id": 1, - "identifier": "apidoc-sample-lang", - "vendor": "apidoc", - "scope": "core", - "target_identifier": null, - "locale": "fr", - "locale_name": "French", - "locale_native_name": "Français", - "text_direction": "ltr", - "version": "1.0.0", - "latest_version": "1.0.0", - "target_version_constraint": null, - "target_version_mismatch": false, - "name": "API 문서 샘플 언어팩", - "license": "MIT", - "description": "문서 실측용 언어팩", - "status": "active", - "is_protected": false, - "source_type": "bundled", - "origin": "bundled", - "source_url": null, - "github_url": null, - "github_changelog_url": null, - "bundled_identifier": null, - "install_blocked_reason": null, - "target_name": null, - "installed_at": "2026-07-05 10:41:24", - "activated_at": "2026-07-05 10:41:24", - "created_at": "2026-07-08 10:41:24", - "updated_at": "2026-07-08 10:41:24", - "has_update": false, - "abilities": { - "can_activate": true, - "can_deactivate": true, - "can_uninstall": true - }, - "manifest": { - "name": { - "en": "API Doc Sample Pack", - "ko": "API 문서 샘플 언어팩" - }, - "locale": "fr", - "version": "1.0.0" - }, - "validation_summary": { - "target_version_mismatch": false, - "depends_on_core_locale": null - }, - "source_meta": { - "type": "bundled", - "url": null, - "installed_by": 6, - "latest_version": "1.0.0", - "directory_path": "lang-packs/apidoc-sample-lang" - }, - "changelog_entries": [] - } -} -``` + **에러 응답** @@ -808,7 +702,7 @@ HTTP/1.1 200 **요청 예시** ```http -POST /api/admin/language-packs/1/activate HTTP/1.1 +POST /api/admin/language-packs/{id}/activate HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -816,7 +710,7 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - + **응답 예시** @@ -850,7 +744,7 @@ Authorization: Bearer {YOUR_TOKEN} **요청 예시** ```http -GET /api/admin/language-packs/1/changelog HTTP/1.1 +GET /api/admin/language-packs/{id}/changelog HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -858,33 +752,11 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) -_단건 응답: `data` 객체의 필드._ - -| 필드 | 타입 | 실측 예시값 | 용도/설명 | -| --- | --- | --- | --- | -| identifier | string | `apidoc-sample-lang` | 언어팩 고유 식별자 ({vendor}-{scope}-{target?}-{locale}) | -| entries | array | `[]` | 항목 목록 (각 원소는 대상 도메인 레코드) | -| changelog | string | `` | 변경 이력 텍스트 (원격/파일 CHANGELOG 본문) | -| has_changelog | boolean | `false` | changelog 여부 | + **응답 예시** -```http -HTTP/1.1 200 -``` - -```json -{ - "success": true, - "message": "언어팩 목록을 조회했습니다.", - "data": { - "identifier": "apidoc-sample-lang", - "entries": [], - "changelog": "", - "has_changelog": false - } -} -``` + **에러 응답** @@ -914,7 +786,7 @@ HTTP/1.1 200 **요청 예시** ```http -POST /api/admin/language-packs/1/deactivate HTTP/1.1 +POST /api/admin/language-packs/{id}/deactivate HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -922,7 +794,7 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - + **응답 예시** @@ -956,7 +828,7 @@ Authorization: Bearer {YOUR_TOKEN} **요청 예시** ```http -POST /api/admin/language-packs/1/update HTTP/1.1 +POST /api/admin/language-packs/{id}/update HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -964,7 +836,7 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - + **응답 예시** diff --git a/docs/backend/api/layouts.md b/docs/backend/api/layouts.md index 15de5754..335e14b6 100644 --- a/docs/backend/api/layouts.md +++ b/docs/backend/api/layouts.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Layouts 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` @@ -17,6 +17,46 @@ --- +### GET /api/layouts/preview/{token} + +- **라우트명**: `api.public.layouts.preview.serve.extensionless` +- **컨트롤러**: `App\Http\Controllers\Api\Public\LayoutPreviewController@serve` +- **인증/권한**: `optional.sanctum` (선택적 인증: 회원/비회원 모두 접근) + +**요청 파라미터** + +| 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | +| --- | --- | --- | --- | --- | --- | +| token | path | string | 예 | — | 인증/검증 토큰 | + +**요청 예시** + +```http +GET /api/layouts/preview/{token} HTTP/1.1 +Host: api.example.com +Accept: application/json +Authorization: Bearer {YOUR_TOKEN} (optional.sanctum: 비회원은 헤더 생략 가능) +``` + +**응답 필드** (`data` 내부) + + + +**응답 예시** + + + +**에러 응답** + +| 상태코드 | 의미 | 발생 조건 | +| --- | --- | --- | +| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 | + + + +**설명** + + ### GET /api/layouts/preview/{token}.json - **라우트명**: `api.public.layouts.preview.serve` @@ -57,6 +97,47 @@ Authorization: Bearer {YOUR_TOKEN} (optional.sanctum: 비회원은 헤더 생 **설명** 편집 중인 레이아웃을 미리보기 토큰(UUID)으로 조회해 JSON으로 서빙합니다. 토큰으로 대상 레이아웃을 찾은 뒤 상속 병합과 확장(extension) 적용까지 마친 결과를 반환하며, 토큰이 유효하지 않으면 404를 반환합니다. 인증 미들웨어가 적용되지만 실질적 보안 메커니즘은 토큰 자체이며, 레이아웃 편집기에서 저장 전 변경분을 실제 렌더링으로 확인하는 용도입니다. +### GET /api/layouts/{templateIdentifier}/{layoutName} + +- **라우트명**: `api.public.layouts.serve.extensionless` +- **컨트롤러**: `App\Http\Controllers\Api\Public\PublicLayoutController@serve` +- **인증/권한**: `optional.sanctum` (선택적 인증: 회원/비회원 모두 접근) + +**요청 파라미터** + +| 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | +| --- | --- | --- | --- | --- | --- | +| templateIdentifier | path | string | 예 | — | 대상 template의 식별자 | +| layoutName | path | string | 예 | — | 대상 layout의 이름 (식별자) | + +**요청 예시** + +```http +GET /api/layouts/{templateIdentifier}/{layoutName} HTTP/1.1 +Host: api.example.com +Accept: application/json +Authorization: Bearer {YOUR_TOKEN} (optional.sanctum: 비회원은 헤더 생략 가능) +``` + +**응답 필드** (`data` 내부) + + + +**응답 예시** + + + +**에러 응답** + +| 상태코드 | 의미 | 발생 조건 | +| --- | --- | --- | +| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 | + + + +**설명** + + ### GET /api/layouts/{templateIdentifier}/{layoutName}.json - **라우트명**: `api.public.layouts.serve` diff --git a/docs/backend/api/license.md b/docs/backend/api/license.md index 6f5f6524..8fe86a0d 100644 --- a/docs/backend/api/license.md +++ b/docs/backend/api/license.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 License 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` diff --git a/docs/backend/api/locales.md b/docs/backend/api/locales.md index df4bc639..8bd89174 100644 --- a/docs/backend/api/locales.md +++ b/docs/backend/api/locales.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Locales 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` @@ -41,8 +41,8 @@ _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| locales | array | `["ko","en","fr","ja"]` | 활성 로케일 코드 배열 | -| locale_names | object | `{"ko":"한국어","en":"English","ja":"日本語","fr":"Français"}` | 로케일 코드별 표시명 맵 (config app.locale_names) | +| locales | array | `["ko","en"]` | 활성 로케일 코드 배열 | +| locale_names | object | `{"ko":"한국어","en":"English"}` | 로케일 코드별 표시명 맵 (config app.locale_names) | **응답 예시** @@ -57,13 +57,11 @@ HTTP/1.1 200 "data": { "locales": [ "ko", - "en", - "fr" + "en" ], "locale_names": { "ko": "한국어", - "en": "English", - "fr": "Français" + "en": "English" } } } diff --git a/docs/backend/api/me.md b/docs/backend/api/me.md index c04f059b..26277ca3 100644 --- a/docs/backend/api/me.md +++ b/docs/backend/api/me.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Me 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` @@ -38,7 +38,7 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - + **응답 예시** @@ -82,31 +82,31 @@ _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| uuid | string | `a231747f-e82e-4cf2-9ae1-a261849dce40` | 외부 노출용 UUID (URL/API 식별자, 내부 id 비노출) | -| name | string | `API 문서 샘플 사용자` | 사용자 이름 | -| nickname | string | `gunwoo.oh` | 닉네임 | -| email | string | `apidoc-sample-user@example.com` | 이메일 주소 | +| uuid | string | `a26219fc-94a0-4f63-9404-04c2a6ac99e4` | 외부 노출용 UUID (URL/API 식별자, 내부 id 비노출) | +| name | string | `최고관리자` | 사용자 이름 | +| nickname | string | `최고관리자` | 닉네임 | +| email | string | `heuristing@gmail.com` | 이메일 주소 | | avatar | null | `null` | 아바타 이미지 URL (User::getAvatarUrl() 산물, 미등록 시 null) | | language | string | `ko` | 사용자 언어 설정 (ko: 한국어, en: 영어) | | timezone | string | `Asia/Seoul` | 사용자 시간대 (예: Asia/Seoul, UTC) | -| country | string | `KR` | 국가 코드 (ISO 3166-1 alpha-2) | +| country | null | `null` | 국가 코드 (ISO 3166-1 alpha-2) | | status | string | `active` | 계정 상태 (active: 활성, inactive: 비활성, blocked: 차단, withdrawn: 탈퇴) | | status_label | string | `활성` | 상태의 사람이 읽는 라벨 (상태 Enum label() 산물) | | status_variant | string | `success` | 상태 표시 색상/스타일 변형 키 (상태 Enum variant() 산물 — UI 배지용) | -| homepage | string | `https://example.com` | 홈페이지 URL | -| mobile | string | `010-9070-5662` | 휴대폰 번호 | -| phone | string | `02-805-4759` | 전화번호 | -| zipcode | string | `93153` | 우편번호 | -| address | string | `대구광역시 북구 백제고분로 720` | 기본 주소 | -| address_detail | string | `40동 835호` | 상세 주소 | -| signature | string | `Ipsam rem amet expedita est.` | 서명 | -| bio | string | `Tenetur omnis et amet omnis veniam to…` | 자기소개 | -| is_super | boolean | `false` | super 여부 | +| homepage | null | `null` | 홈페이지 URL | +| mobile | null | `null` | 휴대폰 번호 | +| phone | null | `null` | 전화번호 | +| zipcode | null | `null` | 우편번호 | +| address | null | `null` | 기본 주소 | +| address_detail | null | `null` | 상세 주소 | +| signature | null | `null` | 서명 | +| bio | null | `null` | 자기소개 | +| is_super | boolean | `true` | super 여부 | | is_admin | boolean | `true` | 관리자 역할 보유 여부 (User::isAdmin() — 역할 관계 기반 파생) | | withdrawn_at | null | `null` | withdrawn 일시 | -| last_login_at | string | `2026-07-05 19:15:16` | last login 일시 | -| last_login_human | string | `1일 전` | 마지막 로그인 시각의 상대 표현 (diffForHumans() 산물, 사용자 시간대 기준) | -| created_at | string | `2026-07-06 19:15:16` | 생성 일시 | +| last_login_at | string | `2026-08-04 19:00:10` | last login 일시 | +| last_login_human | string | `2시간 전` | 마지막 로그인 시각의 상대 표현 (diffForHumans() 산물, 사용자 시간대 기준) | +| created_at | string | `2026-07-30 23:37:44` | 생성 일시 | | is_owner | boolean | `true` | 현재 인증 사용자가 이 리소스의 소유자인지 여부 (BaseApiResource 표준 메타) | | notify_post_complete | boolean | `false` | 게시글 작성 완료 알림 수신 설정 (게시판 모듈 주입) | | notify_post_reply | boolean | `false` | 내 게시글에 대한 답글 알림 수신 설정 (게시판 모듈 주입) | @@ -150,36 +150,66 @@ HTTP/1.1 200 "success": true, "message": "프로필 정보를 성공적으로 가져왔습니다.", "data": { - "uuid": "a234c2b1-cde8-437f-b28b-23323be2b98d", - "name": "API 문서 샘플 사용자", - "nickname": "song.hyunji", - "email": "apidoc-sample-user@example.com", + "uuid": "a26219fc-94a0-4f63-9404-04c2a6ac99e4", + "name": "최고관리자", + "nickname": "최고관리자", + "email": "heuristing@gmail.com", "avatar": null, "language": "ko", "timezone": "Asia/Seoul", - "country": "KR", + "country": null, "status": "active", "status_label": "활성", "status_variant": "success", - "homepage": "https://example.com", - "mobile": "010-9595-2897", - "phone": "02-637-5618", - "zipcode": "16505", - "address": "경기도 안양시 봉은사로 2918", - "address_detail": "48동 718호", - "signature": "Fugit consequuntur repellendus sed.", - "bio": "Ut magni et sunt ducimus error adipisci. Pariatur corporis voluptatem ratione quo non saepe. Illo atque praesentium possimus dolores qui est fugit. Sint fugiat numquam voluptates.", - "is_super": false, + "homepage": null, + "mobile": null, + "phone": null, + "zipcode": null, + "address": null, + "address_detail": null, + "signature": null, + "bio": null, + "is_super": true, "is_admin": true, "withdrawn_at": null, - "last_login_at": "2026-07-07 10:41:24", - "last_login_human": "1일 전", - "created_at": "2026-07-08 10:41:24", + "last_login_at": "2026-08-04 19:00:10", + "last_login_human": "2시간 전", + "created_at": "2026-07-30 23:37:44", "is_owner": true, "notify_post_complete": false, "notify_post_reply": false, "notify_comment": false, "notify_reply_comment": false, + "email_subscription": false, + "email_subscription_at": null, + "marketing_consent": false, + "marketing_consent_at": null, + "third_party_consent": false, + "third_party_consent_at": null, + "info_disclosure": false, + "info_disclosure_at": null, + "marketing_consent_enabled": true, + "marketing_consent_terms_slug": "marketing-terms", + "marketing_consent_terms_slug_set": true, + "third_party_consent_enabled": true, + "third_party_consent_terms_slug": null, + "third_party_consent_terms_slug_set": false, + "info_disclosure_enabled": true, + "info_disclosure_terms_slug": null, + "info_disclosure_terms_slug_set": false, + "email_subscription_enabled": true, + "email_subscription_terms_slug": null, + "email_subscription_terms_slug_set": false, + "channels": [ + { + "key": "email_subscription", + "label": "광고성 이메일 수신", + "enabled": true, + "terms_slug": null, + "terms_slug_set": false + } + ], + "consent_histories": [], "ecommerce_mileage": { "enabled": false }, @@ -269,13 +299,17 @@ Content-Type: application/json "notify_post_complete": true, "notify_post_reply": true, "notify_comment": true, - "notify_reply_comment": true + "notify_reply_comment": true, + "email_subscription": true, + "marketing_consent": true, + "third_party_consent": true, + "info_disclosure": true } ``` **응답 필드** (`data` 내부) - + **응답 예시** diff --git a/docs/backend/api/menus.md b/docs/backend/api/menus.md index fa202660..723ce27d 100644 --- a/docs/backend/api/menus.md +++ b/docs/backend/api/menus.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Menus 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` @@ -49,7 +49,7 @@ _목록 응답: `data.data[]` 배열 항목의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| id | integer | `1` | 기본 키 (내부 식별자) | +| id | integer | `19` | 기본 키 (내부 식별자) | | name | object | `{"ko":"대시보드","en":"Dashboard","ja":"ダッシュボード"}` | 메뉴 이름 (다국어 JSON) | | slug | string | `admin-dashboard` | 메뉴 슬러그 | | url | string | `/admin/dashboard` | 메뉴 URL | @@ -61,9 +61,9 @@ _목록 응답: `data.data[]` 배열 항목의 필드._ | extension_identifier | string | `core` | 확장 식별자 (예: core, sirsoft-board, sirsoft-payment) | | children | array | `[]` | 하위 항목 배열 (계층 트리 — children 관계 파생) | | creator | null | `null` | 생성자 정보 객체 (uuid/name/email — creator 관계 파생) | -| roles | array | `[{"id":1,"name":{"ko":"관리자","en":"Administrator"},"permis…` | 이 메뉴 노출이 허용된 역할 목록 (원소 id/name/permission_type — roles 관계 파생, permission_type 은 pivot 의 노출 권한 유형) | -| created_at | string | `2026-05-27 15:20:18` | 생성 일시 | -| updated_at | string | `2026-05-27 15:21:38` | 최종 수정 일시 | +| roles | array | `[{"id":1,"name":{"ko":"관리자","en":"Administrator","ja":"管理…` | 이 메뉴 노출이 허용된 역할 목록 (원소 id/name/permission_type — roles 관계 파생, permission_type 은 pivot 의 노출 권한 유형) | +| created_at | string | `2026-07-31 00:13:57` | 생성 일시 | +| updated_at | string | `2026-08-01 11:46:14` | 최종 수정 일시 | | is_owner | boolean | `false` | 현재 인증 사용자가 이 리소스의 소유자인지 여부 (BaseApiResource 표준 메타) | | abilities | object | `{"can_create":true,"can_update":true,"can_delete":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | @@ -82,17 +82,18 @@ HTTP/1.1 200 { "id": 19, "name": { - "ko": "페이지 관리", - "en": "Page Management" + "ko": "대시보드", + "en": "Dashboard", + "ja": "ダッシュボード" }, - "slug": "sirsoft-page", - "url": "/admin/pages", - "icon": "fas fa-file-alt", - "order": 25, + "slug": "admin-dashboard", + "url": "/admin/dashboard", + "icon": "fas fa-tachometer-alt", + "order": 1, "is_active": true, "parent_id": null, - "extension_type": "module", - "extension_identifier": "sirsoft-page", + "extension_type": "core", + "extension_identifier": "core", "children": [], "creator": null, "roles": [ @@ -100,13 +101,23 @@ HTTP/1.1 200 "id": 1, "name": { "ko": "관리자", - "en": "Administrator" + "en": "Administrator", + "ja": "管理者" + }, + "permission_type": "read" + }, + { + "id": 156, + "name": { + "ko": "매니저", + "en": "Manager", + "ja": "マネージャー" }, "permission_type": "read" } ], - "created_at": "2026-07-08 10:44:43", - "updated_at": "2026-07-08 10:44:43", + "created_at": "2026-07-31 00:13:57", + "updated_at": "2026-08-01 11:46:14", "is_owner": false, "abilities": { "can_create": true, @@ -115,109 +126,35 @@ HTTP/1.1 200 } }, { - "id": 15, + "id": 20, "name": { - "ko": "게시판 관리", - "en": "Board Management" + "ko": "환경설정", + "en": "Settings", + "ja": "環境設定" }, - "slug": "sirsoft-board", - "url": null, - "icon": "fas fa-clipboard-list", - "order": 30, + "slug": "admin-settings", + "url": "/admin/settings", + "icon": "fas fa-cog", + "order": 2, "is_active": true, "parent_id": null, - "extension_type": "module", - "extension_identifier": "sirsoft-board", - "children": [ - { - "id": 16, - "name": { - "ko": "환경설정", - "en": "Settings" - }, - "slug": "sirsoft-board-settings", - "url": "/admin/boards/settings", - "icon": "fas fa-cog", - "order": 1, - "is_active": true, - "parent_id": 15, - "extension_type": "module", - "extension_identifier": "sirsoft-board", - "roles": [ - { - "id": 1, - "name": { - "ko": "관리자", - "en": "Administrator" - }, - "permission_type": "read" - } - ] - }, - { - "id": 17, - "name": { - "ko": "게시판 목록", - "en": "Board List" - }, - "slug": "sirsoft-board-list", - "url": "/admin/boards", - "icon": "fas fa-list", - "order": 2, - "is_active": true, - "parent_id": 15, - "extension_type": "module", - "extension_identifier": "sirsoft-board", - "roles": [ - { - "id": 1, - "name": { - "ko": "관리자", - "en": "Administrator" - }, - "permission_type": "read" - } - ] - }, - { - "id": 18, - "name": { - "ko": "게시판 신고현황", - "en": "Board Reports" - }, - "slug": "sirsoft-board-reports", - "url": "/admin/boards/reports", - "icon": "fas fa-flag", - "order": 3, - "is_active": true, - "parent_id": 15, - "extension_type": "module", - "extension_identifier": "sirsoft-board", - "roles": [ - { - "id": 1, - "name": { - "ko": "관리자", - "en": "Administrator" - }, - "permission_type": "read" - } - ] - } - ], + "extension_type": "core", + "extension_identifier": "core", + "children": [], "creator": null, "roles": [ { "id": 1, "name": { "ko": "관리자", - "en": "Administrator" + "en": "Administrator", + "ja": "管理者" }, "permission_type": "read" } ], - "created_at": "2026-07-08 10:44:35", - "updated_at": "2026-07-08 10:44:35", + "created_at": "2026-07-31 00:13:57", + "updated_at": "2026-08-01 11:46:14", "is_owner": false, "abilities": { "can_create": true, @@ -225,7 +162,7 @@ HTTP/1.1 200 "can_delete": true } }, - "... (총 3건 중 2건 표시)" + "... (총 17건 중 2건 표시)" ], "abilities": { "can_create": true, @@ -248,7 +185,7 @@ HTTP/1.1 200 **설명** -관리자 메뉴 관리 화면(`partials/admin_menu_list/*`)의 목록 표시 기준 엔드포인트. `is_active` 또는 `filters` 가 있으면 필터링된 관리용 메뉴를, 없으면 최상위 메뉴 전체를 반환한다. `filters` 는 `field`(name/slug/url/all)·`value`·`operator`(like/eq/starts_with/ends_with) 조합의 배열이며 최대 10개까지 허용된다. `name` 은 다국어 JSON 객체로 내려오고, 각 항목에 `children`/`creator`/`roles` 관계와 `abilities`(현재 사용자의 수정/삭제 가능 여부)가 포함된다. 인증 계약: `auth:sanctum` + `permission:core.menus.read`. +관리자 메뉴 관리 화면(`partials/admin_menu_list/*`)의 목록 표시 기준 엔드포인트. `is_active` 또는 `filters` 가 있으면 필터링된 관리용 메뉴를, 없으면 최상위 메뉴 전체를 반환한다. `filters` 는 `field`(name/slug/url/all)·`value`·`operator`(like/eq/starts_with/ends_with) 조합의 배열이며 최대 10개까지 허용된다. `name` 은 다국어 JSON 객체로 내려오고, 각 항목에 `children`/`creator`/`roles` 관계와 `abilities`(현재 사용자의 수정/삭제 가능 여부)가 포함된다. 단, **하위 항목(`children[]`)에는 `roles` 키 자체가 없다** — 상위 메뉴마다 하위 전체의 역할 피벗까지 싣지 않기 위해서다. 빈 배열이 아니라 키를 생략하는 이유는, 빈 배열이 "역할 제한이 없다" 는 사실이 아닌 단언이 되어 그 값을 그대로 수정 요청에 실어 보내면 해당 메뉴의 역할 제한이 통째로 해제되기 때문이다. 하위 메뉴의 역할이 필요하면 `GET /api/admin/menus/{menu}` 로 단건 조회한다 (단건 응답은 항상 `roles` 를 포함한다). 인증 계약: `auth:sanctum` + `permission:core.menus.read`. ### POST /api/admin/menus @@ -305,22 +242,22 @@ _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| id | integer | `26` | 기본 키 (내부 식별자) | -| name | object | `{"ko":"실측 예시값","en":"실측 예시값","fr":"실측 예시값"}` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | -| slug | string | `probe_6a4dc09f5a6bd` | URL 친화 식별자 (slug) | +| id | integer | `39` | 기본 키 (내부 식별자) | +| name | object | `{"ko":"실측 예시값","en":"실측 예시값"}` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | +| slug | string | `probe_6a71e0d4afdbc` | URL 친화 식별자 (slug) | | url | string | `https://example.com` | 메뉴 URL | | icon | string | `실측 예시값` | 아이콘 식별자 (아이콘 클래스/이름) | | order | integer | `1` | 메뉴 순서 | | is_active | boolean | `true` | active 여부 | | parent_id | integer | `1` | parent 식별자 (연관 리소스 참조) | | extension_type | string | `core` | 이 리소스를 소유한 확장의 타입 (core/module/plugin/template) | -| extension_identifier | string | `probe_6a4dc09f5a6c3` | 이 리소스를 소유한 확장의 식별자 | -| parent | object | `{"id":1,"name":{"ko":"API 문서 샘플 메뉴","en":"API Doc Sample …` | 상위 항목 객체 (parent 관계 파생) | +| extension_identifier | string | `probe_6a71e0d4afdc2` | 이 리소스를 소유한 확장의 식별자 | +| parent | object | `{"id":1,"name":{"ko":"게시판 관리","en":"Board Management"},"u…` | 상위 항목 객체 (parent 관계 파생) | | children | array | `[]` | 하위 항목 배열 (계층 트리 — children 관계 파생) | | creator | null | `null` | 생성자 정보 객체 (uuid/name/email — creator 관계 파생) | -| roles | array | `[{"id":1,"name":{"ko":"관리자","en":"Administrator"},"permis…` | 보유 역할 목록 (각 원소 id/name/permissions — roles 관계 파생) | -| created_at | string | `2026-07-08 12:14:39` | 생성 일시 | -| updated_at | string | `2026-07-08 12:14:39` | 최종 수정 일시 | +| roles | array | `[{"id":1,"name":{"ko":"관리자","en":"Administrator","ja":"管理…` | 보유 역할 목록 (각 원소 id/name/permissions — roles 관계 파생) | +| created_at | string | `2026-08-04 21:53:40` | 생성 일시 | +| updated_at | string | `2026-08-04 21:53:40` | 최종 수정 일시 | | is_owner | boolean | `false` | 현재 인증 사용자가 이 리소스의 소유자인지 여부 (BaseApiResource 표준 메타) | | abilities | object | `{"can_create":true,"can_update":true,"can_delete":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | @@ -335,28 +272,27 @@ HTTP/1.1 201 "success": true, "message": "메뉴가 성공적으로 생성되었습니다.", "data": { - "id": 26, + "id": 39, "name": { "ko": "실측 예시값", - "en": "실측 예시값", - "fr": "실측 예시값" + "en": "실측 예시값" }, - "slug": "probe_6a4dc09f5a6bd", + "slug": "probe_6a71e0d4afdbc", "url": "https://example.com", "icon": "실측 예시값", "order": 1, "is_active": true, "parent_id": 1, "extension_type": "core", - "extension_identifier": "probe_6a4dc09f5a6c3", + "extension_identifier": "probe_6a71e0d4afdc2", "parent": { "id": 1, "name": { - "ko": "API 문서 샘플 메뉴", - "en": "API Doc Sample Menu" + "ko": "게시판 관리", + "en": "Board Management" }, - "url": "/admin/apidoc-sample", - "icon": "fas fa-book" + "url": null, + "icon": "fas fa-clipboard-list" }, "children": [], "creator": null, @@ -365,21 +301,14 @@ HTTP/1.1 201 "id": 1, "name": { "ko": "관리자", - "en": "Administrator" - }, - "permission_type": "read" - }, - { - "id": 4, - "name": { - "ko": "API 문서 샘플 역할", - "en": "API Doc Sample Role" + "en": "Administrator", + "ja": "管理者" }, "permission_type": "read" } ], - "created_at": "2026-07-08 12:14:39", - "updated_at": "2026-07-08 12:14:39", + "created_at": "2026-08-04 21:53:40", + "updated_at": "2026-08-04 21:53:40", "is_owner": false, "abilities": { "can_create": true, @@ -428,7 +357,7 @@ Authorization: Bearer {YOUR_TOKEN} | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| id | integer | `1` | 기본 키 (내부 식별자) | +| id | integer | `19` | 기본 키 (내부 식별자) | | name | object | `{"ko":"대시보드","en":"Dashboard","ja":"ダッシュボード"}` | 메뉴 이름 (다국어 JSON) | | slug | string | `admin-dashboard` | 메뉴 슬러그 | | url | string | `/admin/dashboard` | 메뉴 URL | @@ -451,254 +380,64 @@ HTTP/1.1 200 { "id": 19, "name": { - "ko": "페이지 관리", - "en": "Page Management" + "ko": "대시보드", + "en": "Dashboard", + "ja": "ダッシュボード" }, - "slug": "sirsoft-page", - "url": "/admin/pages", - "icon": "fas fa-file-alt", - "order": 25, - "is_active": true, - "children": [] + "slug": "admin-dashboard", + "url": "/admin/dashboard", + "icon": "fas fa-tachometer-alt", + "...": "(3개 키 생략, 총 8개)" }, { - "id": 15, + "id": 20, "name": { - "ko": "게시판 관리", - "en": "Board Management" + "ko": "환경설정", + "en": "Settings", + "ja": "環境設定" }, - "slug": "sirsoft-board", - "url": null, - "icon": "fas fa-clipboard-list", - "order": 30, - "is_active": true, - "children": [ - { - "id": 16, - "name": { - "ko": "환경설정", - "en": "Settings" - }, - "slug": "sirsoft-board-settings", - "url": "/admin/boards/settings", - "icon": "fas fa-cog", - "order": 1, - "is_active": true, - "parent_id": 15, - "extension_type": "module", - "extension_identifier": "sirsoft-board" - }, - { - "id": 17, - "name": { - "ko": "게시판 목록", - "en": "Board List" - }, - "slug": "sirsoft-board-list", - "url": "/admin/boards", - "icon": "fas fa-list", - "order": 2, - "is_active": true, - "parent_id": 15, - "extension_type": "module", - "extension_identifier": "sirsoft-board" - }, - { - "id": 18, - "name": { - "ko": "게시판 신고현황", - "en": "Board Reports" - }, - "slug": "sirsoft-board-reports", - "url": "/admin/boards/reports", - "icon": "fas fa-flag", - "order": 3, - "is_active": true, - "parent_id": 15, - "extension_type": "module", - "extension_identifier": "sirsoft-board" - } - ] + "slug": "admin-settings", + "url": "/admin/settings", + "icon": "fas fa-cog", + "...": "(3개 키 생략, 총 8개)" }, { - "id": 3, + "id": 21, "name": { - "ko": "이커머스", - "en": "Ecommerce" + "ko": "알림 발송 이력", + "en": "Notification Logs", + "ja": "通知発送履歴" }, - "slug": "sirsoft-ecommerce", - "url": null, - "icon": "fas fa-shopping-cart", - "order": 40, - "is_active": true, - "children": [ - { - "id": 4, - "name": { - "ko": "환경설정", - "en": "Settings" - }, - "slug": "sirsoft-ecommerce-settings", - "url": "/admin/ecommerce/settings", - "icon": "fas fa-cog", - "order": 1, - "is_active": true, - "parent_id": 3, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce" - }, - { - "id": 5, - "name": { - "ko": "상품 관리", - "en": "Products" - }, - "slug": "sirsoft-ecommerce-products", - "url": "/admin/ecommerce/products", - "icon": "fas fa-box", - "order": 2, - "is_active": true, - "parent_id": 3, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce" - }, - { - "id": 6, - "name": { - "ko": "카테고리 관리", - "en": "Categories" - }, - "slug": "sirsoft-ecommerce-categories", - "url": "/admin/ecommerce/categories", - "icon": "fas fa-folder", - "order": 3, - "is_active": true, - "parent_id": 3, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce" - }, - { - "id": 7, - "name": { - "ko": "브랜드 관리", - "en": "Brands" - }, - "slug": "sirsoft-ecommerce-brands", - "url": "/admin/ecommerce/brands", - "icon": "fas fa-tag", - "order": 4, - "is_active": true, - "parent_id": 3, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce" - }, - { - "id": 8, - "name": { - "ko": "상품정보제공고시", - "en": "Product Notice" - }, - "slug": "sirsoft-ecommerce-product-notices", - "url": "/admin/ecommerce/product-notices", - "icon": "fas fa-file-alt", - "order": 5, - "is_active": true, - "parent_id": 3, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce" - }, - { - "id": 9, - "name": { - "ko": "공통정보 관리", - "en": "Common Info" - }, - "slug": "sirsoft-ecommerce-common-info", - "url": "/admin/ecommerce/common-info", - "icon": "fas fa-info-circle", - "order": 6, - "is_active": true, - "parent_id": 3, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce" - }, - { - "id": 10, - "name": { - "ko": "주문 관리", - "en": "Orders" - }, - "slug": "sirsoft-ecommerce-orders", - "url": "/admin/ecommerce/orders", - "icon": "fas fa-receipt", - "order": 7, - "is_active": true, - "parent_id": 3, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce" - }, - { - "id": 11, - "name": { - "ko": "쿠폰 관리", - "en": "Coupons" - }, - "slug": "sirsoft-ecommerce-promotion-coupons", - "url": "/admin/ecommerce/promotion-coupons", - "icon": "fas fa-ticket-alt", - "order": 8, - "is_active": true, - "parent_id": 3, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce" - }, - { - "id": 12, - "name": { - "ko": "배송정책", - "en": "Shipping Policies" - }, - "slug": "sirsoft-ecommerce-shipping-policies", - "url": "/admin/ecommerce/shipping-policies", - "icon": "fas fa-truck", - "order": 9, - "is_active": true, - "parent_id": 3, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce" - }, - { - "id": 13, - "name": { - "ko": "리뷰 관리", - "en": "Reviews" - }, - "slug": "sirsoft-ecommerce-reviews", - "url": "/admin/ecommerce/reviews", - "icon": "fas fa-star", - "order": 10, - "is_active": true, - "parent_id": 3, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce" - }, - { - "id": 14, - "name": { - "ko": "마일리지 내역", - "en": "Mileage History" - }, - "slug": "sirsoft-ecommerce-mileage-transactions", - "url": "/admin/ecommerce/mileage-transactions", - "icon": "fas fa-coins", - "order": 11, - "is_active": true, - "parent_id": 3, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce" - } - ] - } + "slug": "admin-notification-logs", + "url": "/admin/notification-logs", + "icon": "fas fa-bell", + "...": "(3개 키 생략, 총 8개)" + }, + { + "id": 22, + "name": { + "ko": "본인인증 이력", + "en": "Identity Logs", + "ja": "本人認証履歴" + }, + "slug": "admin-identity-logs", + "url": "/admin/identity/logs", + "icon": "fas fa-clipboard-check", + "...": "(3개 키 생략, 총 8개)" + }, + { + "id": 23, + "name": { + "ko": "활동 로그", + "en": "Activity Logs", + "ja": "アクティビティログ" + }, + "slug": "admin-activity-logs", + "url": "/admin/activity-logs", + "icon": "fas fa-history", + "...": "(3개 키 생략, 총 8개)" + }, + "... (총 16건 중 5건 표시)" ] } ``` @@ -733,7 +472,7 @@ HTTP/1.1 200 **요청 예시** ```http -GET /api/admin/menus/extension/{type}/1 HTTP/1.1 +GET /api/admin/menus/extension/{type}/{identifier} HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -787,7 +526,7 @@ _목록 응답: `data.data[]` 배열 항목의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| id | integer | `1` | 기본 키 (내부 식별자) | +| id | integer | `19` | 기본 키 (내부 식별자) | | name | object | `{"ko":"대시보드","en":"Dashboard","ja":"ダッシュボード"}` | 메뉴 이름 (다국어 JSON) | | slug | string | `admin-dashboard` | 메뉴 슬러그 | | url | string | `/admin/dashboard` | 메뉴 URL | @@ -811,76 +550,32 @@ HTTP/1.1 200 { "id": 19, "name": { - "ko": "페이지 관리", - "en": "Page Management" + "ko": "대시보드", + "en": "Dashboard", + "ja": "ダッシュボード" }, - "slug": "sirsoft-page", - "url": "/admin/pages", - "icon": "fas fa-file-alt", - "order": 25, + "slug": "admin-dashboard", + "url": "/admin/dashboard", + "icon": "fas fa-tachometer-alt", + "order": 1, "is_active": true, "children": [] }, { - "id": 15, + "id": 20, "name": { - "ko": "게시판 관리", - "en": "Board Management" + "ko": "환경설정", + "en": "Settings", + "ja": "環境設定" }, - "slug": "sirsoft-board", - "url": null, - "icon": "fas fa-clipboard-list", - "order": 30, + "slug": "admin-settings", + "url": "/admin/settings", + "icon": "fas fa-cog", + "order": 2, "is_active": true, - "children": [ - { - "id": 16, - "name": { - "ko": "환경설정", - "en": "Settings" - }, - "slug": "sirsoft-board-settings", - "url": "/admin/boards/settings", - "icon": "fas fa-cog", - "order": 1, - "is_active": true, - "parent_id": 15, - "extension_type": "module", - "extension_identifier": "sirsoft-board" - }, - { - "id": 17, - "name": { - "ko": "게시판 목록", - "en": "Board List" - }, - "slug": "sirsoft-board-list", - "url": "/admin/boards", - "icon": "fas fa-list", - "order": 2, - "is_active": true, - "parent_id": 15, - "extension_type": "module", - "extension_identifier": "sirsoft-board" - }, - { - "id": 18, - "name": { - "ko": "게시판 신고현황", - "en": "Board Reports" - }, - "slug": "sirsoft-board-reports", - "url": "/admin/boards/reports", - "icon": "fas fa-flag", - "order": 3, - "is_active": true, - "parent_id": 15, - "extension_type": "module", - "extension_identifier": "sirsoft-board" - } - ] + "children": [] }, - "... (총 4건 중 2건 표시)" + "... (총 17건 중 2건 표시)" ] } } @@ -940,7 +635,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -984,7 +679,7 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - + **응답 예시** @@ -1032,23 +727,23 @@ _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| id | integer | `33` | 기본 키 (내부 식별자) | -| name | object | `{"ko":"API 문서 샘플 메뉴","en":"API Doc Sample Menu"}` | 메뉴 이름 (다국어 JSON) | -| slug | string | `apidoc-sample-menu` | 메뉴 슬러그 | -| url | string | `/admin/apidoc-sample` | 메뉴 URL | -| icon | string | `fas fa-book` | 메뉴 아이콘 | -| order | integer | `28` | 메뉴 순서 | +| id | integer | `1` | 기본 키 (내부 식별자) | +| name | object | `{"ko":"게시판 관리","en":"Board Management"}` | 메뉴 이름 (다국어 JSON) | +| slug | string | `sirsoft-board` | 메뉴 슬러그 | +| url | null | `null` | 메뉴 URL | +| icon | string | `fas fa-clipboard-list` | 메뉴 아이콘 | +| order | integer | `30` | 메뉴 순서 | | is_active | boolean | `true` | active 여부 | | parent_id | null | `null` | 상위 메뉴 ID | -| extension_type | null | `null` | 확장 소유 타입: core(코어), module(모듈), plugin(플러그인), NULL(사용자 정의) | -| extension_identifier | null | `null` | 확장 식별자 (예: core, sirsoft-board, sirsoft-payment) | +| extension_type | string | `module` | 확장 소유 타입: core(코어), module(모듈), plugin(플러그인), NULL(사용자 정의) | +| extension_identifier | string | `sirsoft-board` | 확장 식별자 (예: core, sirsoft-board, sirsoft-payment) | | parent | null | `null` | 상위 항목 객체 (parent 관계 파생) | -| children | array | `[{"id":34,"name":{"ko":"하위 메뉴","en":"Child Menu"},"slug":…` | 하위 항목 배열 (계층 트리 — children 관계 파생) | -| creator | object | `{"uuid":"a231747f-e82e-4cf2-9ae1-a261849dce40","name":"AP…` | 생성자 정보 객체 (uuid/name/email — creator 관계 파생) | -| roles | array | `[]` | 이 메뉴 노출이 허용된 역할 목록 (원소 id/name/permission_type — roles 관계 파생, permission_type 은 pivot 의 노출 권한 유형) | -| created_at | string | `2026-07-06 19:15:16` | 생성 일시 | -| updated_at | string | `2026-07-06 19:15:16` | 최종 수정 일시 | -| is_owner | boolean | `true` | 현재 인증 사용자가 이 리소스의 소유자인지 여부 (BaseApiResource 표준 메타) | +| children | array | `[{"id":2,"name":{"ko":"환경설정","en":"Settings"},"slug":"sir…` | 하위 항목 배열 (계층 트리 — children 관계 파생) | +| creator | null | `null` | 생성자 정보 객체 (uuid/name/email — creator 관계 파생) | +| roles | array | `[{"id":1,"name":{"ko":"관리자","en":"Administrator","ja":"管理…` | 이 메뉴 노출이 허용된 역할 목록 (원소 id/name/permission_type — roles 관계 파생, permission_type 은 pivot 의 노출 권한 유형) | +| created_at | string | `2026-07-30 18:45:11` | 생성 일시 | +| updated_at | string | `2026-07-30 18:45:11` | 최종 수정 일시 | +| is_owner | boolean | `false` | 현재 인증 사용자가 이 리소스의 소유자인지 여부 (BaseApiResource 표준 메타) | | abilities | object | `{"can_create":true,"can_update":true,"can_delete":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | **응답 예시** @@ -1064,45 +759,83 @@ HTTP/1.1 200 "data": { "id": 1, "name": { - "ko": "API 문서 샘플 메뉴", - "en": "API Doc Sample Menu" + "ko": "게시판 관리", + "en": "Board Management" }, - "slug": "apidoc-sample-menu", - "url": "/admin/apidoc-sample", - "icon": "fas fa-book", - "order": 35, + "slug": "sirsoft-board", + "url": null, + "icon": "fas fa-clipboard-list", + "order": 30, "is_active": true, "parent_id": null, - "extension_type": null, - "extension_identifier": null, + "extension_type": "module", + "extension_identifier": "sirsoft-board", "parent": null, "children": [ { "id": 2, "name": { - "ko": "하위 메뉴", - "en": "Child Menu" + "ko": "환경설정", + "en": "Settings" }, - "slug": "apidoc-sample-menu-child", - "url": "/admin/nihil-non-eos-doloribus-occaecati-optio", - "icon": "fas fa-chart", - "order": 38, + "slug": "sirsoft-board-settings", + "url": "/admin/boards/settings", + "icon": "fas fa-cog", + "order": 1, "is_active": true, "parent_id": 1, - "extension_type": null, - "extension_identifier": null, + "extension_type": "module", + "extension_identifier": "sirsoft-board", + "roles": [] + }, + { + "id": 3, + "name": { + "ko": "게시판 목록", + "en": "Board List" + }, + "slug": "sirsoft-board-list", + "url": "/admin/boards", + "icon": "fas fa-list", + "order": 2, + "is_active": true, + "parent_id": 1, + "extension_type": "module", + "extension_identifier": "sirsoft-board", + "roles": [] + }, + { + "id": 4, + "name": { + "ko": "게시판 신고현황", + "en": "Board Reports" + }, + "slug": "sirsoft-board-reports", + "url": "/admin/boards/reports", + "icon": "fas fa-flag", + "order": 3, + "is_active": true, + "parent_id": 1, + "extension_type": "module", + "extension_identifier": "sirsoft-board", "roles": [] } ], - "creator": { - "uuid": "a234c2b1-cde8-437f-b28b-23323be2b98d", - "name": "API 문서 샘플 사용자", - "email": "apidoc-sample-user@example.com" - }, - "roles": [], - "created_at": "2026-07-08 10:41:24", - "updated_at": "2026-07-08 10:41:24", - "is_owner": true, + "creator": null, + "roles": [ + { + "id": 1, + "name": { + "ko": "관리자", + "en": "Administrator", + "ja": "管理者" + }, + "permission_type": "read" + } + ], + "created_at": "2026-07-30 18:45:11", + "updated_at": "2026-07-30 18:45:11", + "is_owner": false, "abilities": { "can_create": true, "can_update": true, @@ -1178,7 +911,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -1230,18 +963,18 @@ _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | | id | integer | `1` | 기본 키 (내부 식별자) | -| name | object | `{"ko":"API 문서 샘플 메뉴","en":"API Doc Sample Menu"}` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | -| slug | string | `apidoc-sample-menu` | URL 친화 식별자 (slug) | -| url | string | `/admin/apidoc-sample` | 메뉴 URL | -| icon | string | `fas fa-book` | 아이콘 식별자 (아이콘 클래스/이름) | -| order | integer | `35` | 메뉴 순서 | +| name | object | `{"ko":"게시판 관리","en":"Board Management"}` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | +| slug | string | `sirsoft-board` | URL 친화 식별자 (slug) | +| url | null | `null` | 메뉴 URL | +| icon | string | `fas fa-clipboard-list` | 아이콘 식별자 (아이콘 클래스/이름) | +| order | integer | `30` | 메뉴 순서 | | is_active | boolean | `false` | active 여부 | | parent_id | null | `null` | parent 식별자 (연관 리소스 참조) | -| extension_type | null | `null` | 이 리소스를 소유한 확장의 타입 (core/module/plugin/template) | -| extension_identifier | null | `null` | 이 리소스를 소유한 확장의 식별자 | -| created_at | string | `2026-07-08 10:41:24` | 생성 일시 | -| updated_at | string | `2026-07-08 12:14:40` | 최종 수정 일시 | -| is_owner | boolean | `true` | 현재 인증 사용자가 이 리소스의 소유자인지 여부 (BaseApiResource 표준 메타) | +| extension_type | string | `module` | 이 리소스를 소유한 확장의 타입 (core/module/plugin/template) | +| extension_identifier | string | `sirsoft-board` | 이 리소스를 소유한 확장의 식별자 | +| created_at | string | `2026-07-30 18:45:11` | 생성 일시 | +| updated_at | string | `2026-08-04 21:53:41` | 최종 수정 일시 | +| is_owner | boolean | `false` | 현재 인증 사용자가 이 리소스의 소유자인지 여부 (BaseApiResource 표준 메타) | | abilities | object | `{"can_create":true,"can_update":true,"can_delete":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | **응답 예시** @@ -1257,20 +990,20 @@ HTTP/1.1 200 "data": { "id": 1, "name": { - "ko": "API 문서 샘플 메뉴", - "en": "API Doc Sample Menu" + "ko": "게시판 관리", + "en": "Board Management" }, - "slug": "apidoc-sample-menu", - "url": "/admin/apidoc-sample", - "icon": "fas fa-book", - "order": 35, + "slug": "sirsoft-board", + "url": null, + "icon": "fas fa-clipboard-list", + "order": 30, "is_active": false, "parent_id": null, - "extension_type": null, - "extension_identifier": null, - "created_at": "2026-07-08 10:41:24", - "updated_at": "2026-07-08 12:14:40", - "is_owner": true, + "extension_type": "module", + "extension_identifier": "sirsoft-board", + "created_at": "2026-07-30 18:45:11", + "updated_at": "2026-08-04 21:53:41", + "is_owner": false, "abilities": { "can_create": true, "can_update": true, diff --git a/docs/backend/api/modules.md b/docs/backend/api/modules.md index 1486aadf..2fad9498 100644 --- a/docs/backend/api/modules.md +++ b/docs/backend/api/modules.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Modules 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` @@ -55,15 +55,15 @@ _목록 응답: `data.data[]` 배열 항목의 필드 + `data.pagination`._ | identifier | string | `sirsoft-board` | 모듈 고유 식별자 (vendor-module 형식) | | vendor | string | `sirsoft` | 벤더/개발자명 | | name | string | `게시판` | 모듈 이름 (다국어 JSON) | -| version | string | `1.0.0` | 모듈 버전 | +| version | string | `1.0.3` | 모듈 버전 | | description | string | `게시판 관리를 위한 모듈` | 모듈 설명 (다국어 JSON) | | dependencies | array | `[]` | 의존하는 확장 맵 (manifest 파생 — {modules, plugins}) | | status | string | `active` | 상태 (active: 활성화, inactive: 비활성화, installing: 설치 중, uninstalling: 제거 중, updating: 업데이트 중) | -| assets | object | `{"js":"\/api\/modules\/assets\/sirsoft-ecommerce\/dist\/j…` | 프론트엔드 에셋 매니페스트 (manifest 파생 — js/css 진입점·로딩 전략) | +| assets | object | `{"js":"\/api\/modules\/assets\/sirsoft-ecommerce?file=dis…` | 프론트엔드 에셋 매니페스트 (manifest 파생 — js/css 진입점·로딩 전략) | | update_available | boolean | `false` | 최신 버전 대비 업데이트 가능 여부 | | update_source | null | `null` | 업데이트 감지 출처 (github, bundled 등) | -| latest_version | string | `1.0.0` | 감지된 최신 배포 버전 | -| file_version | string | `1.0.0` | 설치된 파일의 manifest 버전 | +| latest_version | string | `1.0.3` | 감지된 최신 배포 버전 | +| file_version | string | `1.0.3` | 설치된 파일의 manifest 버전 | | github_url | string | `https://github.com/gnuboard/g7-module…` | GitHub 저장소 URL | | github_changelog_url | string | `https://github.com/gnuboard/g7-module…` | GitHub 변경 내역 URL | | is_pending | boolean | `false` | _pending 대기소에 있어 설치 대기 중인지 여부 | @@ -89,15 +89,15 @@ HTTP/1.1 200 "identifier": "sirsoft-board", "vendor": "sirsoft", "name": "게시판", - "version": "1.0.0", + "version": "1.0.3", "description": "게시판 관리를 위한 모듈", "dependencies": [], "status": "active", "assets": null, "update_available": false, "update_source": null, - "latest_version": null, - "file_version": "1.0.0", + "latest_version": "1.0.3", + "file_version": "1.0.3", "github_url": "https://github.com/gnuboard/g7-module-sirsoft-board", "github_changelog_url": "https://github.com/gnuboard/g7-module-sirsoft-board/releases", "is_pending": false, @@ -115,19 +115,19 @@ HTTP/1.1 200 "identifier": "sirsoft-ecommerce", "vendor": "sirsoft", "name": "이커머스", - "version": "1.0.1", + "version": "1.0.5", "description": "그누보드7 이커머스 모듈 - 상품, 주문, 결제 관리", "dependencies": [], "status": "active", "assets": { - "js": "/api/modules/assets/sirsoft-ecommerce/dist/js/module.iife.js", - "css": "/api/modules/assets/sirsoft-ecommerce/dist/css/module.css", + "js": "/api/modules/assets/sirsoft-ecommerce?file=dist%2Fjs%2Fmodule.iife.js", + "css": "/api/modules/assets/sirsoft-ecommerce?file=dist%2Fcss%2Fmodule.css", "priority": 100 }, "update_available": false, "update_source": null, - "latest_version": null, - "file_version": "1.0.1", + "latest_version": "1.0.5", + "file_version": "1.0.5", "github_url": "https://github.com/gnuboard/g7-module-sirsoft-ecommerce", "github_changelog_url": "https://github.com/gnuboard/g7-module-sirsoft-ecommerce/releases", "is_pending": false, @@ -156,7 +156,7 @@ HTTP/1.1 200 "user_modules": 3, "total_installs": 0, "average_rating": null, - "latest_version": "1.0.1", + "latest_version": "1.0.5", "categories": [ null ], @@ -176,7 +176,7 @@ HTTP/1.1 200 | 상태코드 | 의미 | 발생 조건 | | --- | --- | --- | | 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 | -| 403 | Forbidden | 요구 권한(`core.modules.read|core.menus.read`)이 없는 경우 | +| 403 | Forbidden | 요구 권한(`core.modules.read| core.menus.read`)이 없는 경우 | | 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) | @@ -216,7 +216,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -256,7 +256,7 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - + **응답 예시** @@ -306,7 +306,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -365,7 +365,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -417,7 +417,7 @@ Content-Type: application/octet-stream **응답 필드** (`data` 내부) - + **응답 예시** @@ -466,7 +466,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -510,18 +510,18 @@ _목록 응답: `data.data[]` 배열 항목의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| identifier | string | `sirsoft-board` | 모듈 고유 식별자 (vendor-module 형식) | -| vendor | string | `sirsoft` | 벤더/개발자명 | -| name | string | `게시판` | 모듈 이름 (다국어 JSON) | -| version | string | `1.0.0` | 모듈 버전 | -| description | string | `게시판 관리를 위한 모듈` | 모듈 설명 (다국어 JSON) | +| identifier | string | `gnuboard7-hello_module` | 모듈 고유 식별자 (vendor-module 형식) | +| vendor | string | `gnuboard7` | 벤더/개발자명 | +| name | string | `Hello 모듈` | 모듈 이름 (다국어 JSON) | +| version | string | `0.1.1` | 모듈 버전 | +| description | string | `학습용 최소 샘플 모듈 (Memo CRUD)` | 모듈 설명 (다국어 JSON) | | dependencies | array | `[]` | 의존하는 확장 맵 (manifest 파생 — {modules, plugins}) | | status | string | `active` | 상태 (active: 활성화, inactive: 비활성화, installing: 설치 중, uninstalling: 제거 중, updating: 업데이트 중) | -| assets | object | `{"js":"\/api\/modules\/assets\/sirsoft-ecommerce\/dist\/j…` | 프론트엔드 에셋 매니페스트 (manifest 파생 — js/css 진입점·로딩 전략) | +| assets | object | `{"js":"\/api\/modules\/assets\/sirsoft-ecommerce?file=dis…` | 프론트엔드 에셋 매니페스트 (manifest 파생 — js/css 진입점·로딩 전략) | | update_available | boolean | `false` | 최신 버전 대비 업데이트 가능 여부 | | update_source | null | `null` | 업데이트 감지 출처 (github, bundled 등) | -| latest_version | string | `1.0.0` | 감지된 최신 배포 버전 | -| file_version | string | `1.0.0` | 설치된 파일의 manifest 버전 | +| latest_version | string | `0.1.1` | 감지된 최신 배포 버전 | +| file_version | string | `0.1.1` | 설치된 파일의 manifest 버전 | | github_url | string | `https://github.com/gnuboard/g7-module…` | GitHub 저장소 URL | | github_changelog_url | string | `https://github.com/gnuboard/g7-module…` | GitHub 변경 내역 URL | | is_pending | boolean | `false` | _pending 대기소에 있어 설치 대기 중인지 여부 | @@ -543,19 +543,45 @@ HTTP/1.1 200 "message": "모듈을 성공적으로 가져왔습니다.", "data": { "data": [ + { + "identifier": "gnuboard7-hello_module", + "vendor": "gnuboard7", + "name": "Hello 모듈", + "version": "0.1.1", + "description": "학습용 최소 샘플 모듈 (Memo CRUD)", + "dependencies": [], + "status": "active", + "assets": null, + "update_available": false, + "update_source": null, + "latest_version": "0.1.1", + "file_version": "0.1.1", + "github_url": null, + "github_changelog_url": null, + "is_pending": false, + "is_bundled": false, + "deactivated_reason": null, + "deactivated_at": null, + "incompatible_required_version": null, + "abilities": { + "can_install": true, + "can_activate": true, + "can_uninstall": true + } + }, { "identifier": "sirsoft-board", "vendor": "sirsoft", "name": "게시판", - "version": "1.0.0", + "version": "1.0.3", "description": "게시판 관리를 위한 모듈", "dependencies": [], "status": "active", "assets": null, "update_available": false, "update_source": null, - "latest_version": null, - "file_version": "1.0.0", + "latest_version": "1.0.3", + "file_version": "1.0.3", "github_url": "https://github.com/gnuboard/g7-module-sirsoft-board", "github_changelog_url": "https://github.com/gnuboard/g7-module-sirsoft-board/releases", "is_pending": false, @@ -569,46 +595,16 @@ HTTP/1.1 200 "can_uninstall": true } }, - { - "identifier": "sirsoft-ecommerce", - "vendor": "sirsoft", - "name": "이커머스", - "version": "1.0.1", - "description": "그누보드7 이커머스 모듈 - 상품, 주문, 결제 관리", - "dependencies": [], - "status": "active", - "assets": { - "js": "/api/modules/assets/sirsoft-ecommerce/dist/js/module.iife.js", - "css": "/api/modules/assets/sirsoft-ecommerce/dist/css/module.css", - "priority": 100 - }, - "update_available": false, - "update_source": null, - "latest_version": null, - "file_version": "1.0.1", - "github_url": "https://github.com/gnuboard/g7-module-sirsoft-ecommerce", - "github_changelog_url": "https://github.com/gnuboard/g7-module-sirsoft-ecommerce/releases", - "is_pending": false, - "is_bundled": false, - "deactivated_reason": null, - "deactivated_at": null, - "incompatible_required_version": null, - "abilities": { - "can_install": true, - "can_activate": true, - "can_uninstall": true - } - }, - "... (총 3건 중 2건 표시)" + "... (총 4건 중 2건 표시)" ], "meta": { - "total_modules": 3, + "total_modules": 4, "active_modules": 0, "system_modules": 0, - "user_modules": 3, + "user_modules": 4, "total_installs": 0, "average_rating": null, - "latest_version": "1.0.1", + "latest_version": "1.0.5", "categories": [ null ], @@ -660,7 +656,7 @@ Content-Type: application/octet-stream **응답 필드** (`data` 내부) - + **응답 예시** @@ -709,7 +705,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -754,7 +750,7 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - + **응답 예시** @@ -830,45 +826,16 @@ HTTP/1.1 200 "success": true, "message": "모듈을 성공적으로 가져왔습니다.", "data": { - "data": [ - { - "identifier": "gnuboard7-hello_module", - "vendor": "gnuboard7", - "name": "Hello 모듈", - "version": "0.1.0", - "description": "학습용 최소 샘플 모듈 (Memo CRUD)", - "dependencies": [], - "status": "uninstalled", - "assets": null, - "update_available": false, - "update_source": null, - "latest_version": null, - "file_version": null, - "github_url": null, - "github_changelog_url": null, - "is_pending": false, - "is_bundled": true, - "deactivated_reason": null, - "deactivated_at": null, - "incompatible_required_version": null, - "abilities": { - "can_install": true, - "can_activate": true, - "can_uninstall": true - } - } - ], + "data": [], "meta": { - "total_modules": 1, + "total_modules": 0, "active_modules": 0, "system_modules": 0, - "user_modules": 1, + "user_modules": 0, "total_installs": 0, "average_rating": null, - "latest_version": "0.1.0", - "categories": [ - null - ], + "latest_version": null, + "categories": [], "dependency_count": 0 } } @@ -902,8 +869,6 @@ HTTP/1.1 200 | from_version | query | string | 아니오 | — | 시작 버전 (범위 하한) | | to_version | query | string | 아니오 | — | 대상 버전 (범위 상한) | -> 이 엔드포인트는 확장이 파라미터를 추가할 수 있습니다 (`core.extension.changelog_validation_rules`). - **요청 예시** ```http @@ -919,7 +884,7 @@ _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| changelog | array | `[{"version":"1.0.1","date":"2026-07-02","categories":[{"n…` | 변경 이력 텍스트 (원격/파일 CHANGELOG 본문) | +| changelog | array | `[{"version":"1.0.5","date":"2026-07-16","categories":[{"n…` | 변경 이력 텍스트 (원격/파일 CHANGELOG 본문) | **응답 예시** @@ -930,2191 +895,7 @@ HTTP/1.1 200 ```json { "success": true, - "message": "모듈을 성공적으로 가져왔습니다.", - "data": { - "changelog": [ - { - "version": "1.0.1", - "date": "2026-07-02", - "categories": [ - { - "name": "Fixed", - "items": [ - "관리자 주문 상세에서 주문상품을 \"구매확정\"으로 변경할 때 구매확정 처리가 고객이 직접 구매확정한 경우와 동일하게 이뤄지지 않던 문제를 수정했습니다 — 이제 구매확정 시점이 정확히 기록되어(부분 수량만 구매확정한 경우 포함), 구매확정 기준으로 지급되는 마일리지 적립이 정상적으로 지급되고, 리뷰 작성 기한도 올바르게 계산됩니다. 기존에는 관리자가 구매확정 처리한 주문상품이 마일리지 적립 대상에서 누락되고 리뷰 작성 기한이 계산되지 않을 수 있었습니다. (#61 @miles44229 님께서 제보해주셨습니다.)", - "주문상품을 여러 번에 나누어 구매확정할 때(예: 2개 중 1개씩 따로 확정) 적립 포인트가 일부만 지급되던 문제를 수정했습니다 — 이제 나누어 확정해도 예정 적립 포인트가 정확한 총액으로 지급됩니다.", - "마일리지 기능을 사용 안 함으로 설정한 뒤에는 지연 적립뿐 아니라 즉시 적립도 지급되지 않도록 통일했습니다 — 기존에는 지급 시점 설정에 따라 어떤 적립은 지급되고 어떤 적립은 지급되지 않을 수 있었습니다." - ] - } - ] - }, - { - "version": "1.0.0", - "date": "2026-07-01", - "categories": [ - { - "name": "Fixed", - "items": [ - "상품의 기본 통화와 다른 통화로 결제한 주문(예: 엔화로 등록된 상품을 원화로 결제)에서 결제·취소 금액의 통화 표기와 실제 환불 금액이 정확해졌습니다 — 주문서·주문 상세·결제 정보·취소 화면에서 금액은 주문 시점 기준 통화(상품 기본 통화)로 표기하고, 고객이 실제 결제한 통화와 그 통화 기준 청구액(예: \"결제 통화 KRW 4,750원 청구\")을 함께 표시합니다. 또한 주문을 취소·환불할 때 환불 금액이 고객이 실제 결제한 통화로 정확히 결제사에 요청되어, 기본 통화와 결제 통화가 다른 주문도 결제한 금액 그대로 환불됩니다(부분취소를 여러 번 나눠 진행해도 누적 환불액이 결제 통화 기준으로 정확히 계산됩니다). 이전에는 결제·취소 금액이 기본 통화 기호로만 고정 표기되고, 환불 요청 금액이 기본 통화 숫자로 전달되어 결제 통화와 단위가 어긋날 수 있었습니다.", - "상품의 기본 통화와 다른 통화로 결제할 때(예: 엔화로 등록된 상품을 원화로 결제) \"결제 금액이 일치하지 않습니다\"라는 오류로 결제가 막히던 문제를 수정했습니다 — 결제창에 청구되는 금액과 주문 승인 시 확인하는 금액의 통화 기준이 어긋나 있었습니다. 이제 결제창 청구액·승인 확인·입금 통보 확인이 모두 고객이 선택한 결제 통화 기준으로 일관되게 검증되어, 기본 통화와 결제 통화가 어떤 조합이든 정상적으로 결제가 진행됩니다.", - "상품에 배송정책을 따로 지정하지 않은 경우(\"기본 배송정책 사용\")에도 기본 배송정책이 그대로 적용됩니다 — 기존에는 배송정책을 지정하지 않은 상품이 해외 배송국가(예: 일본)를 선택하면 기본 배송정책에 그 국가가 설정돼 있어도 \"배송할 수 없는 상품\"으로 안내되고 배송비가 무료로 잡히던 문제가 있었습니다. 이제 상품 상세·장바구니·주문서·주문 처리·배송비 계산 전반에서 기본 배송정책의 국가별 설정과 배송비가 일관되게 반영됩니다. 주문 시점의 기본 배송정책은 주문에 함께 보관되어, 이후 기본 배송정책이 바뀌어도 취소·환불 배송비는 주문 당시 기준으로 계산됩니다.", - "관리자 대시보드의 이커머스 위젯(최신 리뷰·미답변 문의 목록)에서 각 항목이 같은 화면 식별자를 공유해 일부 보조 기능·접근성 도구가 항목을 구분하지 못하던 문제를 수정했습니다 — 이제 목록의 각 행이 고유하게 식별됩니다.", - "헤더의 통화·배송국가 선택기가 데스크톱과 모바일 화면에 동시에 배치되면서 같은 화면 식별자를 공유하던 문제를 수정했습니다 — 두 위치의 선택기가 각각 고유하게 식별됩니다." - ] - }, - { - "name": "Changed", - "items": [ - "상품 등록 화면에서 새로 시작할 때 과세 여부는 \"과세\", 판매상태는 \"판매중\", 전시상태는 \"전시중\"이 기본으로 선택되어 있고, 옵션을 추가하면 재고가 1로 미리 채워집니다 — 가장 흔한 설정이 처음부터 지정되어 매번 직접 고르거나 입력하는 수고를 덜 수 있습니다. 옵션을 일괄 생성하거나 행을 추가·삭제하면 원 상품의 재고가 옵션 재고 합계로 즉시 맞춰집니다(옵션 보유 상품의 재고는 직접 입력하지 않고 옵션 재고 합으로 표시됩니다) — 이전에는 옵션을 만든 직후에는 상품 재고가 0으로 남아 있다가 옵션 재고를 한 번 직접 수정해야 반영되었습니다. 기존 상품을 수정할 때는 저장된 값이 그대로 표시됩니다.", - "상품 등록·수정 화면과 상품 관리 목록에서 옵션별 외화 가격을 직접 입력하던 칸을 없애고, 판매가 아래에 환율로 자동 계산된 외화 가격을 읽기 전용으로 보여주도록 바꿨습니다. 외화 가격은 기본 통화 판매가를 기준으로 환율을 적용해 표시되므로(장바구니·주문서와 동일한 방식), 판매가만 입력하면 설정한 통화들의 환산 금액이 함께 표시됩니다. 표시되는 통화는 쇼핑몰 설정에서 추가한 통화를 그대로 따르며, 통화를 추가하면 자동으로 함께 나타납니다. 상품 관리 목록의 통화별 열과 열 선택 메뉴의 통화 항목, \"다중통화 가격 자동계산\" 켜기/끄기도 더 이상 필요하지 않아 정리했습니다. 판매가 아래에 외화 환산값이 여러 줄로 표시되어도 정가·재고·SKU 등 다른 입력칸이 같은 줄에 가지런히 정렬되도록 행 정렬을 맞췄습니다.", - "쇼핑몰 설정의 마일리지 알림 카드에서 사이트 알림 / 이메일 아이콘이 채널 라벨 위로 올라가 비대칭으로 보이던 회귀를 수정했습니다 — 아이콘이 채널 라벨 옆 가운데로 정렬되어 한 줄로 응집됩니다.", - "쿠폰 등록 / 수정 화면의 직접발급 안내문에서 정보 아이콘이 안내 문구 위로 살짝 떠 보이던 정렬을 바로잡았습니다 — 아이콘이 안내 문구와 세로 가운데로 맞춰집니다.", - "상품 등록 / 수정 화면에서 상단 탭 내비와 본문 콘텐츠가 너무 붙어 보이던 회귀를 수정했습니다 — 탭 내비와 첫 카드 사이에 표준 상단 여백이 자동으로 들어가 다른 관리자 화면과 동일한 결로 통일됩니다.", - "관리자 환경설정·상품·주문·배송정책·쿠폰 화면의 카드 모양과 간격을 다른 관리자 화면과 동일하게 통일했습니다 — 사용자 영역용 카드 자산이 혼용되던 부분을 관리자 표준 카드로 환원하고, 카드 안의 헤더 묶음·본문 묶음을 표준 패턴으로 정리했습니다.", - "관리자 환경설정 페이지의 빈 컨테이너 한 겹을 제거해 다른 관리자 화면과 본문 구조가 동일합니다 — 저장 중 시각 피드백(저장 버튼 안의 회전 아이콘과 \"저장 중...\" 문구)은 그대로 제공됩니다.", - "상품·카테고리의 SEO 제목·SEO 설명을 언어별로 입력할 수 있습니다 — 상품명·상세설명과 동일하게 언어 탭으로 한국어·영어 등 각 언어의 SEO 문구를 따로 작성할 수 있고, 검색엔진이 해당 언어로 접근하면 그 언어의 제목·설명이 노출됩니다. \"상품명과 동일하게 설정\"을 켜면 입력한 언어별 상품명·설명이 그대로 SEO에 반영되고, SEO 문구를 비워 두면 상품명·상세설명으로 자동 대체됩니다. 기존에 입력해 둔 SEO 문구는 기본 언어 값으로 보존됩니다. (이전에는 한 줄 문구만 저장되어 모든 언어에 같은 값이 쓰였고, 입력 중 검색결과 미리보기가 매 글자마다 다시 그려져 타이핑이 느려지던 문제도 함께 해소되었습니다.)", - "배송정책의 통화가 상품 등록과 동일하게 쇼핑몰 기본 통화로 자동 적용됩니다 — 배송정책 등록·수정 화면의 통화 항목이 직접 고르는 칸에서 현재 기본 통화를 보여 주는 읽기 전용 표시로 바뀌었고, 배송비는 항상 기본 통화 기준으로 계산됩니다. 배송비·무료배송 기준금액 입력칸 옆의 통화 표시도 기준 통화 칸과 동일하게 현재 기본 통화로 통일했습니다. 기존에 다른 통화로 저장돼 있던 배송정책도 기본 통화로 통일됩니다. (이전에는 배송정책마다 통화를 따로 지정할 수 있었으나 배송비 계산에는 반영되지 않아 합계가 어긋날 수 있었습니다.)", - "주문서 작성과 장바구니의 배송비가 선택한 배송 국가 기준으로 계산되어 표시됩니다 — 배송정책에 국가별 배송비(예: 국내·미국)를 다르게 설정한 경우, 헤더에서 고른 배송 국가 또는 주문서에서 선택한 배송 국가에 맞는 배송비가 적용됩니다. 주문서에서는 헤더에서 고른 국가가 기본으로 선택되며, 주문서의 배송 국가를 바꾸면 그 즉시 해당 국가 배송비로 다시 계산됩니다. 우편번호를 입력하지 않아도 국가만으로 해당 국가의 기본 배송비가 미리 표시됩니다. (이전에는 미국 등 해외 국가를 선택해도 배송비가 국내 기준으로만 계산되어 표시되던 문제가 있었습니다.)", - "상품 등록·수정 화면의 목록·복사·삭제·저장 버튼을 화면 하단 고정으로 옮기고, 환경설정 화면의 상단 탭과 저장 버튼이 스크롤 중에도 고정되도록 개선했습니다 — 긴 폼을 스크롤하는 동안에도 주요 버튼이 항상 화면에 보입니다. 배송정책·쿠폰·상품·주문 상세 화면에서 페이지 제목이 함께 따라 고정되던 동작은 일반 흐름으로 정리했습니다.", - "카테고리·브랜드 슬러그 수정 모드와 주문 상세의 자동 채움·참조용 입력 칸의 글자를 옅은 회색으로 표시해, 수정할 수 없는 항목임을 한눈에 구분할 수 있습니다.", - "이커머스 관리 전반(상품·주문·결제·배송·환경설정·카테고리·브랜드·쿠폰·마일리지·예치금·리뷰)의 카드·입력 칸·라벨·본문 텍스트·구분선·여백 모양을 다른 관리자 화면과 동일한 결로 통일했습니다 — 화면마다 조금씩 다르게 보이던 카드 모양·간격, 입력 칸 테두리·배경, 글자 톤, 2단 그리드 간격이 한 가지 기준으로 정합되고, 마일리지/예치금 환경설정의 페이지 배경이 누락되어 다르게 보이던 회귀도 함께 해소됩니다.", - "이커머스 관리 화면 곳곳의 역할 없는 빈 컨테이너 중첩을 정리해, 화면 모양은 그대로 둔 채 페이지 구조만 단순해졌습니다.", - "주문 결제 진입이 특정 결제사에 고정되지 않고, 주문을 만들 때 서버 응답이 알려주는 결제 수단으로 진행됩니다 — 결제 플러그인을 바꾸거나 새로 추가해도 주문 화면 수정 없이 해당 결제사의 결제창이 열립니다. 결제 수단을 따로 지정하지 않은 결제(무통장 입금 등)는 기존과 동일하게 동작합니다.", - "코어 최소 요구 버전을 7.0.0-beta.8 로 상향. SEO 자동값 연결 칩 편집 기능이 코어 7.0.0-beta.8 에서 제공하는 편집기 연동 표면을 사용합니다." - ] - }, - { - "name": "Added", - "items": [ - "레이아웃 편집기에서 마일리지 거래내역·쿠폰 목록·쿠폰 생성·주문 목록·주문 상세 화면의 화면 상태를 미리보기로 전환할 수 있습니다 — 목록의 비어 있음, 쿠폰 신규 작성·검증 실패, 주문 상세의 부분 취소 등 특정 상황에서만 나타나는 모양을 편집기에서 골라 미리보고 편집할 수 있습니다.", - "비회원으로 주문하면 주문 접수·입금 확인·배송·취소 등 주요 단계에서 입력한 이메일로 주문 알림이 발송됩니다 — 회원 주문과 동일한 알림을 비회원도 받으며, 메일은 주문할 때 보던 화면 언어로 전달됩니다. 주문 완료 화면에 \"주문 확인 안내를 입력하신 이메일로 보내드렸습니다\"라는 안내가 함께 표시됩니다.", - "회원이 자신의 배송국가를 직접 설정할 수 있습니다 — 해외배송을 켜면 사이트 상단 헤더의 통화 선택기에 배송국가 선택이 함께 표시되고(선택기 버튼에 배송국가 코드와 통화가 나란히 표시), 회원정보 수정·마이페이지·가입 화면에서도 배송국가를 고를 수 있습니다. 로그인한 회원은 선택한 배송국가가 계정에 저장되어 다음 방문에도 유지되고, 비회원은 브라우저에 임시 저장됩니다. 처음 방문 시에는 접속 위치를 추정해 기본 배송국가가 정해지며, 추정이 어려우면 설정한 기본 국가가 사용됩니다. 주문서·주문 상세·주문 내역·배송지 관리에 배송국가가 함께 표시됩니다(국내 포함). 선택한 배송국가로 배송할 수 없는 상품이 장바구니·주문서에 하나라도 있으면 그 상품이 표시되고 주문하기가 막히며, 상품 상세에서도 배송 가능 여부를 미리 확인할 수 있습니다. 해외 주소로 주문하면 도시·우편번호 등 해외 주소가 온전히 저장되고, 주문 알림 메일에도 받는 분·배송국가·배송지가 표시됩니다. 주문을 취소·환불할 때도 그 시점의 배송국가·우편번호가 함께 보관되어, 이후 배송지가 바뀌어도 도서산간·국가별 배송비 환불이 주문 당시 기준으로 정확히 계산됩니다. 해외배송을 끄면 배송국가 선택이 함께 사라집니다.", - "사이트 상단 헤더에 통화 선택기를 추가했습니다 — 이커머스 모듈을 켜면 헤더에 통화 전환 버튼이 나타나고, 관리자가 등록한 통화(환율이 설정된 통화) 중에서 표시 통화를 고를 수 있습니다. 로그인한 회원은 선택한 통화가 계정에 저장되어 다음 방문에도 유지되고, 비회원은 브라우저에 임시 저장됩니다. 관리자 화면 헤더에서도 동일하게 표시 통화를 전환할 수 있습니다. 모듈을 끄면 통화 선택기가 함께 사라집니다. 레이아웃 편집기 미리보기에서도 헤더의 통화·배송국가 선택기가 실제 화면과 동일하게 표시되어, 편집 중 위치와 모양을 미리 확인할 수 있습니다(사용자·관리자 템플릿 공통).", - "기본 통화가 원(KRW)이 아닌 통화(달러·유로 등)일 때도 상품을 등록·수정할 수 있습니다 — 기본 통화가 소수점을 쓰는 통화이면 상품 정가·판매가·옵션 가격과 추가옵션 추가금을 3.30 처럼 소수점까지 입력할 수 있고, 통화별 소수 자릿수(예: 달러 2자리)를 초과하는 입력은 막습니다. 원·엔처럼 소수점이 없는 통화에서는 정수만 허용됩니다. 기존에는 가격이 정수로만 저장되어 기본 통화를 달러 등으로 바꾸면 새 상품 저장이 되지 않았습니다. 아울러 상품·옵션·주문·결제·환불·쿠폰·마일리지·배송비·대시보드 매출 등 금액이 표시되는 화면 전반에서, 소수점이 없는 통화(원·엔 등)인데도 `200.00`처럼 불필요한 소수점이 붙던 부분을 정리해 — 모든 금액이 쇼핑몰 설정의 통화별 소수 자릿수를 그대로 따라 표시됩니다(소수점 없는 통화는 `200`, 달러·유로 등은 `1.70`).", - "기본 통화가 원(KRW)이 아닌 통화(달러 등)일 때 상품·장바구니·주문의 외화 환산 금액이 0 또는 비정상값으로 표시되던 문제를 해결했습니다 — 통화마다 \"환율 입력의 기준 단위\"(예: 원은 1,000원당, 달러·엔 등은 1단위당)를 설정에 두어, 어떤 통화를 기본으로 삼든 외화 가격이 올바르게 환산됩니다. 환율/통화 설정 화면에서 통화마다 \"기준 단위\"를 직접 입력·수정할 수 있고, 환율 입력 안내도 기본 통화의 기준 단위에 맞춰 \"USD 1 =\", \"KRW 1,000 =\"처럼 표시됩니다. 기준 단위·환율을 잘못 입력해 저장하면 해당 입력칸이 빨갛게 강조되고 칸 아래에 사유가 표시됩니다. 이미 접수된 주문의 환불 금액은 주문 당시 환율 그대로 유지됩니다.", - "상품 등록·수정 시 입력 오류 안내에 \"판매가\"·\"옵션 판매가\"처럼 사람이 읽을 수 있는 항목 이름이 표시됩니다 — 기존에는 일부 항목에서 내부 식별자(예: options.0.selling_price)가 그대로 노출됐습니다.", - "쇼핑몰 설정의 언어·통화 화면에서 통화마다 표시 기호를 직접 지정할 수 있습니다 — 통화 추가·수정 시 \"기호\" 칸에 ₩·$·¥·€ 등 원하는 기호를 입력하면, 상품 등록·수정 화면의 기본 통화 표시와 판매가 일괄 변경 창의 금액 단위, 상품 복사 미리보기의 가격이 모두 그 기호로 표시됩니다. 기존 통화(원·달러·엔·위안·유로)에는 표준 기호가 기본으로 채워지며, 통화를 추가·삭제하거나 기본 통화를 바꾸면 상품 관리 화면의 기호 표시가 설정을 그대로 따라갑니다. 위안화(CNY)는 엔화(JPY)와 같은 ¥ 기호를 쓰면 가격을 혼동할 수 있어 元 기호를 기본으로 사용합니다 — 장바구니·상품·주문 화면에서 엔화는 ¥, 위안화는 元 로 구분되어 표시됩니다. 아울러 장바구니·상품·주문서·주문 상세·주문 내역·영수증·배송정책 등 금액이 표시되는 화면 전반에서 통화 기호가 원화로 고정되어 있던 부분을 정리해, 기본 통화를 달러·엔 등으로 바꾸면 그 통화 기호로 표시됩니다(상품 옵션 추가금·과세금액 표기 포함). 이미 접수된 주문의 금액은 주문 시점의 통화로 계속 표시되어, 이후 기본 통화를 바꿔도 과거 주문 표기가 달라지지 않습니다. (이전에는 상품 관리 일부 화면이 통화 설정과 무관하게 원화 기호로 고정 표시되었고, 엔화와 위안화가 모두 ¥ 로 표시되어 구분되지 않았습니다.)", - "관리자 대시보드에 이커머스 영역이 추가되었습니다 — 이커머스 모듈을 켜면 상단에 상품·주문·리뷰·쿠폰·배송 바로가기 버튼이 나타나고, 오늘 주문 현황(결제완료·준비중·배송중·취소 등) 배지, 최근 7일 판매 추세 그래프(판매 수량·매출액과 직전 7일 대비 증감율), 최신 리뷰, 미답변 문의 위젯이 실제 데이터로 표시됩니다. 모듈을 끄면 이 영역과 버튼이 함께 사라집니다.", - "대시보드 판매 현황은 매시간 집계되어 빠르게 표시됩니다 — 판매 수량과 매출은 결제 완료 이후(배송보류·준비중·배송준비·배송중·배송완료·구매확정) 주문상품을 기준으로 집계하며, 취소된 수량은 자동으로 차감되고 주문일 기준으로 날짜에 반영됩니다.", - "마이페이지 취소 주문 상세에서 \"재주문\" 기능 추가 — 과거 주문의 상품 옵션을 현재 장바구니에 일괄 추가, 품절/단종 상품은 사유와 함께 안내", - "비회원 주문 지원 — 로그인 없이 주문/결제하고, 주문번호·휴대폰·조회 비밀번호(8자 이상)로 본인 확인 후 30분간 유효한 토큰으로 주문을 조회할 수 있습니다. 회원/비회원은 주문 단계에서 자동 분기됩니다.", - "비회원 주문 상세에서 주문 취소·환불 예상·구매확정과 배송 전 배송지 변경(직접 입력)을 회원과 동일하게 이용할 수 있으며, 상품 합계·할인·배송비·총액의 다중 통화 금액도 함께 표시됩니다.", - "상품 \"구매 대상 제한\"을 실제 주문 단계에 적용 — 허용 역할에 없는 사용자(비회원 포함)는 제한 상품을 주문할 수 없으며, 주문서 진입과 결제 시점 양쪽에서 안내됩니다. 기존에는 설정만 저장되고 구매를 막지 않았습니다.", - "관리자가 주문 목록에서 회원/비회원을 구분해 조회하고, 비회원 주문의 조회 비밀번호를 재설정할 수 있습니다.", - "관리자 주문 상세의 주문상품 표에서 상품별 재고 차감 여부(\"재고 차감\"/\"재고 미차감\")를 구매수량과 함께 확인할 수 있습니다.", - "주문이 취소된 경우 관리자 주문 상세에 취소 사유가 표시됩니다 — 취소 유형(전체/부분)과 사유, \"기타\" 선택 시 입력한 상세 사유, 취소 일시를 취소 건별로 확인할 수 있습니다. 부분 취소가 여러 번 이뤄진 주문은 각 취소 건이 최근 순으로 모두 나열됩니다.", - "상품·장바구니·주문·찜·문의 등 쇼핑 화면을 샘플 데이터로 미리보고 편집할 수 있습니다 — 재고 있음/품절, 목록 있음/없음, 장바구니 비었음 같은 화면 상태도 미리보기로 전환해 볼 수 있습니다.", - "화면 상태 미리보기를 더 많은 쇼핑 화면으로 넓혔습니다 — 상품의 품절·판매 중지·판매 예정, 상품 목록의 접근 제한, 장바구니 주문 처리 중, 결제 오류, 주문 완료의 결제수단별 안내(카드·가상계좌·무통장), 재주문 진행/일부/실패와 함께, 관리자의 상품·쿠폰·배송정책 폼(신규/수정/검증 실패)과 쇼핑몰 설정의 모든 탭(기본 정보·언어 통화·SEO·주문·취소 반품·배송·리뷰·알림 정의·본인인증)·마일리지/예치금 탭을 전환해 미리볼 수 있습니다.", - "주문서(결제) 화면의 입력 검증 실패 상태를 미리보기에 추가 — 주문자 이름·연락처·이메일, 받는 분 이름·연락처, 우편번호·주소 입력칸에 빨간 오류 안내가 표시된 화면을 전환해 미리볼 수 있습니다.", - "비회원 주문 조회 화면을 회원/비회원 상태로 전환해 미리볼 수 있습니다 — 비회원 상태에서는 주문 조회 입력 폼을, 회원 상태에서는 마이페이지 안내를 각각 편집할 수 있습니다.", - "데이터 소스 목록에서 이 확장이 제공하는 데이터 소스가 친화 명칭으로 표시되고, 어느 확장이 제공했는지 출처가 함께 표시됩니다.", - "[검색엔진] 탭에서 상품·카테고리의 공유 이미지·제목·구조화 데이터 등 이 확장이 제공하는 SEO 자동값이 \"상품 이름\"·\"상품 대표 이미지\" 같은 데이터 연결 칩(출처 표시 포함)으로 보이고, 다른 데이터로 바꿔 지정할 수 있습니다.", - "주문 구매확정 시 결제 금액에 적립률을 적용해 마일리지를 자동 적립하고, 결제 시 보유 마일리지를 사용할 수 있습니다. 적립·사용·소멸 내역이 회원별로 원장에 기록됩니다.", - "마일리지로 결제 금액 전액을 충당하면 별도 결제 절차 없이 주문이 즉시 결제완료 처리됩니다.", - "마일리지 차감 시점을 결제수단별로 \"결제완료 시\" 또는 \"주문접수 시\" 중에서 선택할 수 있습니다. 무통장입금·가상계좌는 입금 전 마일리지가 재사용되지 않도록 주문접수 시 차감이 기본이고, 신용카드(PG)는 결제창을 닫거나 결제에 실패한 주문에서 마일리지가 선차감되어 사라지지 않도록 결제완료 시 차감이 기본입니다. 차감된 뒤 결제가 실패하거나 주문을 취소한 경우에는 사용 마일리지가 자동으로 복원됩니다. 마일리지 사용을 꺼 두면 결제수단별 차감 시점 설정은 비활성화됩니다.", - "마일리지 사용 시 먼저 적립된 분부터 차감(선입선출)되며, 주문 취소·부분 취소 시 사용분 복원과 적립 회수가 정확히 재계산됩니다.", - "무통장입금 주문 접수 시 마일리지 차감 후 실제 입금할 금액과 입금 계좌·기한을 담은 입금 안내가 자동 발송됩니다.", - "통화별 마일리지 규칙(적립률·사용 한도·최소 사용 금액·소멸 기간)을 설정할 수 있으며, 다중 통화 환경에서 통화별로 독립 적용됩니다.", - "적립 마일리지에 유효기간을 두어 만료분이 자동 소멸하며, 소멸 예정 마일리지를 회원에게 사전 안내할 수 있습니다.", - "회원 탈퇴 시 보유 마일리지가 정리되어 잔여가 남지 않습니다.", - "환경설정에 \"마일리지\" 탭이 추가되어 기능 사용 여부·기본 적립률·통화별 규칙·유효기간·소멸 알림을 한 화면에서 관리합니다. 통화 추가가 미확정인 상태로는 저장이 차단되고, 적립률 0%·잘못된 통화 코드 등 잘못된 입력은 저장 전 안내됩니다.", - "관리자 \"마일리지 내역\" 화면에서 회원·유형·통화·기간으로 거래를 조회하고, 특정 회원에게 마일리지를 수동 지급/차감하거나 선택한 적립분의 유효기간을 일괄 연장할 수 있습니다. 다른 관리자 목록 화면(주문/리뷰)과 동일한 필터·페이지네이션·표 구조로 정렬했습니다.", - "각 적립 내역의 \"사유·기간 변경\" 메뉴에서 적립 사유와 만료일을 수정할 수 있습니다. 회원·금액·유형·적립일은 변경할 수 없도록 함께 표시되며, 이미 소멸되었거나 모두 사용된 적립분은 만료일을 바꿀 수 없습니다.", - "마일리지 내역 표에 \"유효기간\" 열이 추가되어 적립분의 만료 예정일, 무기한 지급 여부, 그리고 소멸된 경우 \"전체 소멸\"/\"일부 소멸\" 여부와 소멸된 금액을 한눈에 확인할 수 있습니다.", - "거래 행을 펼치면 표시되는 연결 거래에 관련 주문번호(클릭 시 주문 상세 열람)와 지급한 관리자(또는 시스템 발생) 정보가 함께 표시됩니다.", - "수동 지급 시 적립분의 유효기간을 \"정책 따름\" 또는 \"직접 지정\"(날짜 선택) 중에서 명확한 안내와 함께 선택할 수 있습니다.", - "주문 관련 모든 화면에서 마일리지 사용액·적립 예정액이 일관되게 표시됩니다 — 주문서 결제 요약, 주문완료 화면, 회원/비회원 주문 상세, 마이페이지·관리자 주문 목록에서 사용·적립 마일리지를 확인할 수 있으며, 관리자 주문 상세에는 환불된 마일리지도 함께 표시됩니다.", - "관리자 주문 상세의 주문상품 표에서 상품별 적립 예정액 옆에 실제 적립 여부(\"적립완료\"/\"적립예정\")를 함께 확인할 수 있습니다.", - "장바구니에 담긴 상품이 설정한 보관기간이 지나면 자동으로 정리됩니다. 환경설정의 장바구니 보관기간(일)에 따라 마지막 활동 이후 기간이 지난 항목을 매일 자동 삭제하며, 보관기간을 0(또는 비움)으로 두면 자동 삭제하지 않습니다.", - "배송정책 계산 API의 요청 참고 필드를 시스템이 지원하는 항목(배송정책 ID·국가 코드·주문 항목·그룹 합계 금액·총 수량) 중에서 선택하도록 개선했습니다. 자유 입력으로 인해 지원하지 않는 필드명이 조용히 무시되던 문제를 막고, 선택하지 않으면 모든 항목을 전송합니다.", - "배송정책 계산 API 연동을 외부 API 규격에 맞춰 세부 설정할 수 있도록 확장했습니다.", - "요청 방식을 GET 또는 POST로 선택할 수 있습니다.", - "인증이 필요한 API를 위해 Bearer 토큰 또는 커스텀 헤더(API 키 등)를 지정할 수 있습니다. 저장한 토큰은 안전하게 보관되며 화면에는 가려져 표시됩니다.", - "요청 항목별로 외부 API가 요구하는 필드명을 따로 지정(매핑)할 수 있어, 항목 이름이 다른 API에도 맞출 수 있습니다.", - "응답이 JSON일 때 배송비 값의 위치를 점(.)으로 중첩 경로(예: data.shipping.fee)까지 지정할 수 있고, 텍스트로 응답하는 API는 통화기호·콤마가 섞여 있어도 금액을 인식합니다.", - "\"테스트 호출\" 버튼으로 입력한 설정 그대로 실제 API를 한 번 호출해 요청 내용과 응답·추출된 배송비를 즉시 확인할 수 있습니다.", - "무통장입금 주문의 입금을 관리자가 직접 확인할 수 있는 \"입금확인\" 기능이 추가되었습니다 — 주문 상세 결제정보에서 입금자명과 입금액을 입력해 입금을 기록하며, 입금액이 결제예정금액과 정확히 일치할 때만 처리됩니다. 입금확인 버튼은 무통장입금이면서 입금이 아직 확인되지 않은 결제 건에 표시됩니다. \"주문을 결제완료로 처리\" 체크박스를 함께 제공해, 체크하면 입금 기록과 함께 주문을 결제완료로 변경(마일리지 적립·재고 차감·알림 발송)하고, 체크하지 않으면 입금 내역만 기록합니다. 이미 결제완료된 주문에 추가 금액(교환·반품 배송비 등)을 별도로 입금받아 기록하는 경우에 활용할 수 있습니다.", - "\"배송 완료\" 알림이 추가되었습니다 — 주문이 배송완료 상태로 바뀌면 구매자에게 택배사·운송장번호와 함께 배송완료 안내(메일·알림함)가 발송됩니다.", - "상품 등록·수정 화면의 기타정보에서 새 라벨을 바로 추가할 수 있습니다 — \"라벨 추가\" 버튼으로 이름·색상을 입력해 등록하면 곧바로 해당 상품에 연결됩니다. 같은 모달에서 기존 라벨 수정도 가능합니다.", - "추가옵션 선택지에 \"직접입력 허용\"을 켤 수 있습니다 — 켜면 구매자가 그 선택지를 골랐을 때 각인 문구처럼 원하는 내용을 직접 입력해 주문할 수 있고, 입력한 내용은 장바구니·주문서·주문 상세에 함께 표시되며 주문 시점 그대로 보존됩니다. 필수 추가옵션의 직접입력 선택지는 내용을 입력해야 담기·주문이 진행되고, 필수가 아닌 추가옵션은 비워 두어도 됩니다. 직접입력 내용이 다르면 장바구니에서 별도 항목으로 담깁니다.", - "추가옵션을 선택한 주문이 마이페이지 주문 내역과 관리자 주문 관리 목록에도 대표 옵션과 함께 추가옵션 요약(\"첫 추가옵션 외 N건\")으로 표시됩니다 — 직접입력한 내용도 함께 병기됩니다. 이전에는 목록에서 대표 옵션만 보여 추가옵션 주문 여부를 알 수 없었습니다.", - "추가옵션 그룹·선택지 등록 시 입력 검증 안내가 정확한 우리말 안내로 표시됩니다 — 선택지를 등록하지 않은 빈 그룹을 저장하면 영문 키 대신 \"각 추가옵션 그룹에는 선택지를 1개 이상 등록해주세요.\" 안내가 해당 그룹 영역 강조와 함께 표시됩니다.", - "관리자가 쿠폰 목록에서 회원을 검색·선택해 쿠폰을 즉시 발급할 수 있습니다 — 회원 이름·이메일을 입력하면 실시간으로 검색되며, 발급 한도에 도달한 회원은 자동으로 제외되고 발급 결과를 안내합니다.", - "관리자가 쿠폰 발급 내역에서 발급된 쿠폰의 사용 여부와 사용된 주문(주문번호 바로가기)을 확인할 수 있습니다.", - "관리자가 아직 사용하지 않은 발급 쿠폰을 발급취소할 수 있습니다 — 취소하면 해당 발급분이 회수되어 회원 쿠폰함에서 사라지고 발급 수량이 복원됩니다. 이미 사용했거나 만료·취소된 발급 건은 취소할 수 없습니다.", - "장바구니·주문서 작성 화면에 정가(취소선)·할인율(%)·판매가가 함께 표시되며, 보조 통화를 선택하면 정가도 같이 환산되어 보입니다.", - "회원이 마이페이지에서 결제 통화를 직접 설정할 수 있으며, 관리자도 회원 편집 화면에서 회원별 결제 통화를 지정할 수 있습니다. 로그인하면 헤더에서 임시로 고른 통화 대신 계정에 저장된 통화가 우선 적용됩니다.", - "회원 가입 시 브라우저 언어/지역에 맞는 결제 통화가 자동으로 지정됩니다 — 환경설정의 통화별 사용 언어 매핑을 따르며, 매핑이 겹치거나 없으면 기본 통화가 지정됩니다. 관리자는 환경설정에서 통화별 사용 언어를 여러 개 지정할 수 있습니다.", - "상품 또는 주문이 한 건이라도 등록된 후에는 데이터 정합성을 위해 기본 통화를 변경할 수 없습니다.", - "상품 목록·검색·인기 상품, 상품 상세의 옵션 합계 금액, 상품 상세 쿠폰, 체크아웃의 쿠폰·배송비 쿠폰 금액이 모두 선택한 통화로 환산되어 표시됩니다 — 그동안 이 영역들이 기본 통화(원)로 고정되어 보이던 문제를 바로잡았습니다. 헤더에서 통화를 바꾸면 상품 목록 가격도 즉시 함께 바뀝니다.", - "마일리지 \"통화별 사용 단위\" 설정에서 통화는 언어·통화 설정에 등록된 통화 중에서만 선택할 수 있고, 기본 통화에 대한 사용 단위가 설정되지 않으면 결제 시 마일리지를 사용할 수 없습니다.", - "주문 시 선택한 배송 메모가 내부 코드 대신 안내 문구(라벨)로 저장·표시됩니다 — 마이페이지 주문 상세와 관리자 주문 상세에서 \"경비실에 맡겨주세요\" 같은 문구가 그대로 보이며, 직접 입력한 자유 메모는 입력한 내용이 보존됩니다.", - "주문 상세의 배송 현황에서 운송장 번호를 누르면 택배사 배송조회 페이지가 새 창으로 열립니다 — 운송장 정보가 없으면 기존처럼 번호만 표시됩니다.", - "결제창으로 넘어가는 청구 금액이 결제 통화로 환산되어 전달됩니다 — 결제창에는 주문 시점 환율로 환산한 결제 통화 금액(화면에 표시된 총 결제금액과 동일한 값)이 전달되며, 달러처럼 소수점을 쓰는 통화는 결제대행사 규격에 맞춰 청구됩니다. 결제할 수 없는 통화(환율이 설정되지 않은 통화 등)로는 주문 단계에서 안내와 함께 결제가 차단됩니다. 무통장입금 안내액도 같은 기준으로 결제 통화 금액으로 안내됩니다.", - "환율/통화 설정에서 통화마다 \"사용 언어\"를 사이트가 지원하는 언어 중에서 지정할 수 있습니다 — 방문자가 보는 사이트 언어에 맞춰 표시 통화를 자동으로 정하는 데 쓰입니다. 로그인하지 않은 방문자도 현재 사이트 언어에 해당하는 통화로 가격이 표시되며(예: 영어로 보는 방문자에게는 영어로 지정된 통화), 회원 가입 시에도 같은 기준으로 결제 통화가 자동 지정됩니다. 방문자가 헤더에서 직접 고른 통화가 있으면 그 선택이 우선합니다. 처음 제공되던 통화 중 위안·유로에 지원하지 않는 언어가 들어 있어 통화 설정을 저장하면 \"설정 저장에 실패했습니다\"로 막히던 문제를 해결했습니다 — 기본값을 원은 한국어, 그 외 통화는 영어로 정리하고, 기존에 저장된 설정에서도 지원하지 않는 언어를 자동으로 정리합니다(직접 지정한 지원 언어는 그대로 유지). 지원하지 않는 언어를 선택해 저장하면 해당 입력칸이 빨갛게 강조되고 칸 아래에 \"사용 언어\" 항목명으로 사유가 표시됩니다.", - "쇼핑몰 본인인증 정책 편집 화면에서 \"인증 목적\"과 \"적용 대상\"을 바꿔 저장해도 반영되지 않던 문제를 수정했습니다 — 이제 정책 키·강제 시점·강제 위치를 제외한 모든 항목(인증 목적·적용 대상 등)을 자유롭게 변경할 수 있으며, 변경한 값은 업데이트 후에도 유지됩니다.", - "쇼핑몰 환경설정의 본인인증 정책 탭에서 출처 필터(전체/코어/모듈/플러그인)를 골라도 결과가 항상 비어 보이던 문제를 해소했습니다 — 이 탭은 쇼핑몰 정책만 보여주는 화면이므로, 동작하지 않던 출처 필터를 정리했습니다. 강제 시점 필터와 검색은 그대로 사용할 수 있으며, \"이 모듈이 등록한 본인인증 목적\" 안내는 그대로 표시됩니다.", - "데이터베이스 테이블 접두사를 기본값(`g7_`)보다 길게 설정한 환경에서 쇼핑몰 모듈 설치가 실패하던 문제를 수정했습니다 — 배송정책 관련 일부 테이블의 인덱스 이름이 데이터베이스 한도를 넘겨 설치가 중단되던 결함으로, 접두사 길이와 관계없이 설치가 정상 완료됩니다.", - "할인이 적용되지 않은 주문 항목의 금액이 주문 상세에서 표시되지 않던 문제를 수정했습니다 — 할인이 없는 항목도 소계 금액이 정상적으로 보입니다.", - "주문을 취소한 뒤에도 관리자 주문 상세·주문 목록에 \"적립 예정\" 마일리지가 계속 표시되던 문제를 수정했습니다 — 전체 취소·부분 취소된 주문에는 적립 예정 표시가 더 이상 나타나지 않습니다(실제 적립은 종전에도 발생하지 않았으며 표시만 정리되었습니다).", - "다른 언어를 쓰는 회원에게 보내는 알림(이메일·사이트 알림)이 받는 사람 본인의 언어로 발송됩니다 — 관리자가 한국어 화면에서 영어 회원의 주문 상태를 바꿔도 알림은 영어로 전달됩니다.", - "상품 일괄 적용 확인 창의 옵션 변경 요약에서 \"안전재고\"·\"사용\" 항목이 영어 화면에서도 올바른 언어로 표시됩니다 — 그동안 한국어로 고정 표시되던 문제를 바로잡았습니다.", - "공통정보 관리의 다국어 탭 저장이 정상 동작합니다 — 한국어 탭에 입력하고 저장할 때 \"한국어 필수\" 오류가 뜨거나, 영문 탭으로 옮긴 뒤 저장하면 공통정보명이 비워지던 문제를 해소했습니다.", - "주문 이력이 있는 상품을 삭제하려 하면 \"N건의 주문 이력이 있어 삭제할 수 없습니다\"라고 사유가 표시되고, 삭제 확인 창이 목록 화면과 동일하게 삭제 가능 여부를 먼저 확인해 불가 시 안내와 함께 버튼이 비활성화됩니다. 삭제에 성공한 뒤 잘못된 오류 안내가 뜨던 문제도 함께 해소했습니다.", - "장바구니 선택 삭제·전체 삭제 후 안내 문구에 실제 삭제 개수가 표시됩니다 — 그동안 \"{개수}개 상품이 삭제되었습니다\"처럼 자리표시자가 그대로 노출되던 문제를 바로잡았습니다.", - "통화 선택 메뉴가 관리자가 설정한 통화 목록을 표시하도록 수정했습니다 — 그동안 목록이 비어 통화를 고를 수 없던 문제를 바로잡았고, 환율이 설정되지 않은 통화는 메뉴에서 자동으로 숨겨집니다. 기본 화폐를 바꾸면 사용자 화면의 가격·합계·쿠폰 금액이 선택한 통화로 환산되어 표시됩니다.", - "관리자가 통화 설정을 저장할 때 일부 통화의 환율 정보가 사라지던 문제를 수정했습니다 — 빠진 통화가 기본 카탈로그에서 자동으로 보충됩니다.", - "쿠폰 다운로드 모달에서 정액 할인·최소주문금액이 선택한 통화로 환산되어 표시됩니다 — 그동안 기호만 바뀌고 금액은 환산되지 않던 문제를 바로잡았습니다(정률 할인은 통화와 무관하게 % 그대로 표시).", - "\"언어·통화\" 설정에서 기본 언어 항목을 제거하고, 사이트 언어는 일반 설정에서 일원화하여 관리합니다.", - "결제·결제 통지·부분 환불 금액이 사이트 기본 통화를 기준으로 산정되며, 선택 통화는 주문 시점 환율로 환산된 금액으로 표시·청구됩니다. 환율이 바뀌어도 기존 주문의 환불 금액은 주문 당시 환율로 계산되어 환차손이 없습니다.", - "신용카드(PG) 주문의 관리자 신규주문 알림을 결제 완료 시점에 보내도록 바꿨습니다 — 그동안 결제 전(주문서 생성) 시점에 알림을 보내면서 결제를 마치지 않은 주문도 알림이 발송되고, 알림 발송이 결제하기 응답을 지연시키던 문제가 함께 해소되었습니다. 무통장입금 주문은 기존대로 주문 접수 시점에 입금 안내가 발송됩니다.", - "상품 관리 목록에서 옵션명만 인라인으로 바꾼 뒤 일괄 적용하면 \"일괄 업데이트에 실패했습니다\"로 저장되지 않던 문제 수정 — 한국어 옵션명만 입력하고 영어·일본어를 비워 둔 옵션도 정상 저장됩니다. 또한 일괄 적용이 실패하면 어떤 항목이 왜 거부됐는지 확인 모달과 알림에 구체적인 사유가 표시됩니다.", - "상품 수정 화면에서 이미지를 추가하고 저장하면, 업로드는 됐는데도 저장 직후 새로고침하면 추가한 이미지가 모두 사라지던 문제 수정 — 이미지를 추가하고 저장할 때 그 이미지들이 상품에 정상 반영됩니다. 기존 이미지가 있는 상태에서 추가, 모두 지우고 다시 첨부, 추가 후 순서 바꾸기 모두 저장한 그대로 유지됩니다.", - "상품 관리 목록에서 기본으로 설정한 옵션의 판매가를 바꿔도 상품 판매가가 따라 바뀌지 않던 문제 수정 — 상품 등록·수정 화면과 동일하게, 기본 옵션의 판매가를 바꾸면 상품 판매가가 함께 맞춰지고 나머지 옵션 판매가도 가산액 기준으로 다시 계산됩니다.", - "상품 등록·수정 화면에서 추가옵션의 옵션명을 입력할 때 글자 입력이 버벅이던 문제 수정 — 입력이 끝난 뒤 잠깐 후에 반영되도록 바꿔 타이핑이 매끄러워집니다.", - "상품 등록·수정 화면의 추가옵션 \"필수\" 선택을 체크박스에서 켜기/끄기 토글로 바꿔, 옵션의 사용/노출 토글과 동일한 조작감으로 통일했습니다.", - "쿠폰 정액 할인값에 음수를 입력하면 정률(%) 전용 안내 문구가 잘못 표시되던 문제와, 최소주문금액을 비우고 저장하면 오류가 나던 문제를 수정했습니다 — 정액 할인값은 1원 이상으로 안내되고, 최소주문금액을 비우면 \"제한 없음\"으로 저장됩니다.", - "쿠폰 적용 대상을 \"특정 상품\" 또는 \"특정 카테고리\"로 고른 뒤 대상을 하나도 선택하지 않아도 저장되던 문제를 수정했습니다 — 대상을 1개 이상 선택해야 저장됩니다.", - "쿠폰 등록·수정 화면의 상품 검색이 입력한 검색어를 무시하고 전체 상품을 보여주던 문제를 수정했습니다.", - "\"특정 카테고리\" 쿠폰이 해당 카테고리에 속하지 않은 상품에도 노출·적용되던 문제를 수정했습니다 — 쿠폰의 카테고리와 상품의 카테고리가 일치할 때만 적용됩니다.", - "쿠폰 목록 검색에서 검색 필드(쿠폰명/설명/등록자)를 골라도 항상 전체가 검색되던 문제와, 상세 필터 선택이 주소(URL)로 복원될 때 상세 필터 영역이 펼쳐지지 않아 선택이 보이지 않던 문제를 수정했습니다.", - "상품 상세의 쿠폰 다운로드 모달을 무한 스크롤로 개선했습니다 — 페이지 전환 버튼 없이 목록을 아래로 내리면 다음 쿠폰을 한 묶음씩 이어서 불러오고(불러오는 동안 \"불러오는 중\" 표시), 전체를 다 불러오면 멈춥니다.", - "상품 등록·수정 화면의 \"공통정보\" 선택 목록에 비활성 처리한 공통정보까지 나타나던 문제 수정 — 활성 상태인 공통정보만 선택 대상으로 표시됩니다. 상품정보제공고시 템플릿과 동일하게 동작합니다.", - "신규 주문 접수 시 관리자에게 발송되는 알림 메일에서 결제금액이 0원으로 표시되던 문제 수정 — 결제 예정 금액이 정확히 표시됩니다.", - "관리자 주문 상세의 결제정보에서 \"결제요청일시\"가 비어 있고, 무통장입금의 입금 계좌(은행·계좌번호·예금주) 요약이 표시되지 않던 문제 수정 — 주문 접수 시 결제 요청 시각과 구매자 정보가 기록되며, 무통장입금 주문에 입금 계좌 요약이 표시됩니다.", - "신용카드(PG) 결제가 완료된 뒤에도 주문 상세·마이페이지의 개별 상품 \"주문상태\"가 \"주문대기\"로 남던 문제 수정 — 결제가 완료되면 주문 상품 상태도 함께 결제완료로 표시됩니다. 결제 금액 전액을 마일리지로 충당한 주문도 동일하게 반영됩니다.", - "관리자가 주문 상세에서 주문 상태를 변경할 때 주문 전체 상태만 바뀌고 개별 상품 상태는 이전 상태로 남던 문제 수정 — 상품 상태도 함께 변경됩니다. 이미 취소된 상품은 변경 대상에서 제외되어 그대로 유지됩니다.", - "결제 실패·결제창 닫힘으로 주문이 취소될 때 개별 상품 상태가 \"주문대기\"로 남던 문제 수정 — 주문이 취소되면 상품 상태도 함께 취소로 정리되며, 이미 부분취소된 상품은 그대로 보존됩니다. 그동안 상품 상태가 주문대기에 갇혀 구매확정·리뷰 작성이 막히던 연쇄 문제도 함께 해소됩니다.", - "신용카드 결제가 정상 완료됐는데도 주문 상세에서 주문은 \"결제완료\"이면서 개별 상품만 \"주문취소\"로 표시되던 문제 수정 — 결제 승인과 결제창 닫힘 처리가 겹칠 때 결제가 이미 완료된 주문은 취소 처리하지 않도록 바로잡아, 결제완료 주문의 상품이 임의로 취소 상태가 되지 않습니다.", - "관리자가 주문 상태를 결제완료·배송중·배송완료·구매확정으로 바꿔도(단건/목록 일괄/운송장 일괄 포함) 구매자에게 해당 알림이 발송되지 않던 문제 수정 — 모든 상태변경 경로에서 결제완료·배송중·배송완료·구매확정 알림이 누락 없이 발송됩니다. 이미 같은 상태인 주문을 다시 저장할 때는 중복 발송되지 않습니다.", - "결제 완료 시 발송되는 알림의 제목·본문이 \"주문 확인\"으로 표시되어 결제 완료 시점과 의미가 어긋나던 문구를 \"결제 완료\"로 명확히 정정했습니다.", - "본인인증 정책(결제 시 확인·무통장 입금확인·결제 승인·결제 취소)을 활성화해도 실제 액션 시 본인인증이 요구되지 않던 문제 수정 — 4종 정책 모두 실제 결제·취소·입금확인·주문 진입 시점에 정상 동작합니다.", - "상품 \"구매 대상 제한\" 설정 화면에서 제한으로 전환할 때 이전 역할이 빈 칩으로 남던 문제와, 역할이 많을 때 일부 역할(관리자 등)이 선택 목록에서 누락되던 문제 수정", - "회원 주문 상세에서 \"배송 메모\"·\"배송 현황\" 영역이 표시되지 않던 문제 수정 — 회원/비회원 모두 동일한 배송 정보가 표시됩니다.", - "상품 상세 페이지의 검색엔진용 구조화 데이터(Schema.org Product/Offer)에서 재고 상태가 실제 판매 상태와 무관하게 항상 \"품절(OutOfStock)\"로 출력되던 결함 수정. 판매 중인 상품은 \"재고 있음(InStock)\", 판매 중지·예정 상품은 \"품절\"로 정확히 출력됩니다.", - "레이아웃 편집기 라우트 트리에서 상품정보제공고시 화면이 잘못된 다국어 키 참조로 라벨이 비어 보이던 결함 수정. 정확한 화면명(\"상품정보제공고시 관리\")이 표시됩니다.", - "레이아웃 편집기 라우트 트리에서 상품 등록 단축 경로(`/admin/ecommerce/product-create`) 가 다국어 라벨 없이 경로 그대로 표시되던 문제 수정. \"상품 등록\" 라벨에 \"리다이렉트\" 배지를 함께 표시합니다.", - "쇼핑몰 환경설정의 결제수단·통화·배송 국가 목록 라벨이 일본어 등 비한국어 로케일에서도 한국어로 표시되던 결함 수정. 활성 언어팩의 번역 라벨이 정확히 노출됩니다.", - "주문 취소 시 재고가 이중으로 복구되던 결함 수정. 전체취소·부분취소 모두 취소한 수량만큼만 정확히 한 번 복구됩니다(이전에는 전체취소 시 최대 두 배가 복구되어 오버셀 위험이 있었습니다).", - "환경설정의 \"취소 시 재고 복구\" 끄기 설정이 무시되어, 꺼 두어도 취소 시 재고가 복구되던 문제 수정. 이제 설정대로 켜면 복구, 끄면 복구하지 않습니다.", - "무통장입금 주문이 입금 기한이 지나도 자동으로 취소되지 않던 문제 수정. 가상계좌와 무통장입금 결제대기 주문 모두 설정한 자동취소 기한이 지나면 정상적으로 자동 취소됩니다.", - "무통장입금 주문의 결제 정보에서 입금 은행·계좌 요약이 비어 보이던 문제 수정. 주문 상세에서 입금 은행명과 계좌번호가 정상 표시됩니다.", - "취소했던 주문을 다시 판매 상태(결제완료 등)로 되돌릴 때, 취소로 복구되었던 재고가 다시 차감됩니다. 되돌리려는 시점에 재고가 부족하면 상태 변경이 차단되어 재고가 음수로 빠지거나 오버셀이 발생하지 않습니다. 단건 변경·목록 일괄 변경과 더불어, 주문 상세에서 상품(옵션) 단위로 상태를 되돌리는 경우에도 동일하게 재고가 재차감됩니다.", - "주문 상세에서 상품(옵션) 단위로 상태를 \"주문취소\"로 바꿀 때, 결제(PG) 취소를 함께 하지 않는 경우에는 재고가 복구되지 않던 문제 수정. 이제 결제 취소 동반 여부와 관계없이 취소 시 재고가 설정대로 복구됩니다. 결제를 함께 취소하는 경우에도 재고가 이중으로 복구되지 않고 정확히 한 번만 복구됩니다.", - "단일 상품 주문에서 취소 수량을 줄여 부분취소하려 해도 항상 전체취소로 처리되던 문제 수정. 취소 수량을 상품 수량보다 적게 지정하면 그 수량만 부분취소되고, 모든 상품을 전량 취소할 때만 전체취소로 처리됩니다.", - "쇼핑몰 환경설정의 \"취소 시 재고 복구\" 토글을 켜서 저장하면 \"참/거짓 값이어야 합니다\" 오류로 저장이 거부되던 문제 수정. 이제 토글을 켜고 끄는 설정이 정상적으로 저장됩니다.", - "주문서 작성 시 보유한 마일리지보다 많은 금액을 사용하려 하면, 조용히 보유 잔액으로 줄여 결제하지 않고 \"보유 마일리지를 초과하여 사용할 수 없습니다\"라고 명확히 안내하도록 변경. 의도와 다른 금액으로 결제되던 혼란을 제거했습니다.", - "판매중지·품절·출시예정·전시중지 상태의 상품이 장바구니에 담기거나 결제까지 진행되던 문제 수정. 이제 담기 단계에서 차단되며, 이미 담겨 있던 상품이 판매중지로 바뀌면 합계 금액에서 자동으로 제외되고 구매 불가로 표시됩니다.", - "상품에 설정한 최소·최대 구매 수량이 실제 구매 과정(옵션 상품 포함)에 적용되지 않던 문제 수정. 이제 같은 상품의 옵션 수량을 합산한 총수량 기준으로 최소 미만·최대 초과 구매가 차단됩니다.", - "상품 등록·수정에서 \"구매 대상 제한\"을 선택하고도 허용 역할을 하나도 선택하지 않은 채 저장되던 문제 수정. 이제 제한을 선택하면 허용 역할을 최소 1개 이상 지정해야 저장됩니다.", - "상품 상세에서 \"바로 구매\"를 하면 해당 상품이 장바구니에도 함께 담기던 문제 수정. 이제 바로 구매는 장바구니를 거치지 않으며, 장바구니에 같은 상품이 들어 있어도 그 수량과 합산되지 않고 지금 선택한 수량만으로 주문됩니다(구매 수량 제한도 이번 선택 수량 기준으로 적용). 바로 구매 후 장바구니에는 기존 항목만 그대로 유지됩니다.", - "장바구니 담기·바로 구매가 차단될 때 \"담을 수 없습니다\" 같은 일반 안내만 나오던 문제 수정. 이제 어떤 상품이 왜(판매 중지·품절·구매 권한 없음·최소/최대 구매 수량 초과) 막혔는지 상품명과 함께 구체적으로 안내합니다.", - "쇼핑몰 상품 목록에서 상위 카테고리를 선택하면 그 하위 카테고리에만 속한 상품이 함께 표시되도록 개선. 이전에는 상위 카테고리 버튼을 눌러도 직접 지정된 상품만 나오고 하위 카테고리 상품은 누락되었으며, 카테고리 버튼이 목록을 실제로 필터링하지 않던 문제도 함께 수정했습니다. 선택한 카테고리는 버튼에 표시되어 현재 어떤 분류를 보고 있는지 알 수 있습니다.", - "관리자 상품 목록에서 검색어로 조회하면 결과는 나오는데 \"총 N개\"가 0으로 잘못 표시되던 문제 수정. 이제 검색 결과 수가 실제 목록과 일치하며, 검색어와 판매 상태 등 다른 필터를 함께 적용해도 정확히 집계됩니다. 관리자 쿠폰 목록의 검색 총 개수 오표시도 동일하게 수정했습니다.", - "관리자 상품 목록의 \"카테고리 미등록\"·\"브랜드 미등록\" 필터를 켜면 오류가 발생하며 영문 키가 그대로 노출되던 문제 수정. 이제 해당 필터가 정상 동작하여 분류가 지정되지 않은 상품만 정확히 조회됩니다.", - "관리자 상품·주문 목록에서 검색어를 빠르게 입력한 직후 곧바로 Enter 를 누르면 방금 입력한 검색어가 누락된 채 조회되던 문제 수정. 검색 입력 시 화면 버벅임도 함께 줄였습니다.", - "무통장입금 주문을 취소할 때 결제(PG) 취소 처리가 실패하며 취소 자체가 막히던 문제 수정. 무통장 등 PG를 거치지 않는 결제수단은 취소 시 결제대행 환불 단계를 건너뛰며, 환불금은 운영자가 직접 송금하도록 \"환불 승인(대기)\" 상태로 표시됩니다.", - "입금 전(미입금)이거나 실제 결제금액이 0원인 주문에서 일부 상품을 부분취소하려 하면 항상 \"취소할 수 없습니다\"로 막히던 문제 수정. 실제로 낸 돈이 없는 주문은 환불·추가청구 대상이 아니므로 그대로 부분취소됩니다.", - "여러 상품이 담긴 주문에서 일부 상품만 선택해 취소해도 주문 전체가 취소되던 문제 수정. 선택한 상품만 정확히 취소되고, 남은 상품은 그대로 유지됩니다. 모든 상품을 전량 취소할 때만 주문 전체가 취소됩니다.", - "일부 상품만 취소한 주문도 남은 상품을 이어서 취소할 수 있습니다. 남은 상품을 모두 취소하면 주문 전체가 취소 처리됩니다.", - "일부 상품만 취소한 주문이 그 주문의 실제 진행 단계(결제완료·상품준비중·배송중 등)에서 사라지지 않고 그대로 추적되도록 개선했습니다. 일부가 취소된 주문에는 주문내역·주문 상세에 \"일부취소\" 표시가 함께 붙어, 남은 상품은 정상 배송 흐름을 따르면서 일부 취소 사실도 한눈에 확인할 수 있습니다. (관리자 주문 목록·상세에도 동일하게 표시됩니다.)", - "취소 차단 안내 문구가 \"쿠폰 할인 조건\" 으로 단정하던 표현을 실제 사유(적용 중인 할인 조건 변경으로 결제금액 증가)에 맞게 일반화했습니다.", - "마이페이지 주문내역의 \"상품준비중\" 건수에는 배송준비완료 주문까지 포함되는데, 그 건수를 눌러 필터링하면 배송준비완료 주문이 빠져 표시 건수와 목록 수가 어긋나던 문제 수정. 이제 건수와 목록이 일치합니다.", - "무통장입금 등 결제대행을 거치지 않는 주문의 취소 화면에서 의미 없는 \"결제(PG) 함께 취소\" 선택이 노출되던 문제 수정. 해당 결제수단에서는 자동으로 숨겨집니다. 또한 \"환불 우선순위\"(PG↔마일리지 배분 순서)는 사용한 마일리지가 없는 주문에서 선택 의미가 없으므로, 항목은 보이되 선택할 수 없도록(비활성) 처리하고 사유를 함께 안내합니다.", - "주문 취소 시각이 전체취소에만 기록되고 부분취소에는 남지 않던 문제 수정. 전체·부분취소 모두 취소 시각이 정확히 기록됩니다.", - "관리자 배송정책 목록에서 데이터가 없을 때 빈 표 머리글과 \"검색 결과가 없습니다\" 안내가 동시에 어색하게 겹쳐 보이던 문제 수정. 쿠폰·주문·상품 목록과 동일하게 표 영역에 빈 상태 안내만 깔끔하게 표시되도록 정리했습니다.", - "무통장입금·가상계좌 입금 대기 주문의 자동취소 기한이 환경설정의 자동취소 기한(일)대로 적용됩니다. 이전에는 이 설정이 실제 동작에 반영되지 않았으며, 결제수단별로 나뉘어 있던 입금기한 설정을 하나의 자동취소 기한으로 통일했습니다(기존에 설정해 둔 값은 그대로 보존됩니다).", - "리뷰 작성 화면에 리뷰 이미지 최대 개수·최대 용량 설정이 실제로 반영됩니다. 이전에는 설정과 무관하게 항상 기본값(5장·10MB)이 적용되었으며, 최대 개수를 0으로 두면 이미지 첨부 영역이 숨겨집니다. 용량 초과 시 안내 메시지에도 설정한 용량이 표시됩니다.", - "리뷰 작성 가능 기간(구매확정 후 일수) 설정이 리뷰 작성 버튼 노출과 실제 작성 가능 여부에 동일하게 적용됩니다. 이전에는 두 판정이 미세하게 어긋나 버튼은 보이는데 작성이 막히거나 그 반대가 될 수 있었고, 기간을 0(무제한)으로 두면 버튼이 사라지던 문제도 해소되었습니다.", - "배송정책 목록이 빈 화면일 때 안내 문구가 한 번만 표시되도록 정리했습니다. 상품 등록·수정 폼의 배송정책 선택에는 사용 중인(활성) 정책만 표시되며, 기존 상품에 적용된 정책이 비활성화된 경우에는 그 정책을 그대로 유지하면서 \"비활성화된 배송정책 적용 중\" 안내를 함께 보여 줍니다.", - "배송정책 계산 API 주소를 입력하지 않은 채 저장하면 조용히 기본 배송비로 처리되던 문제를 막아, 계산 API 정책을 선택하면 API 주소 입력을 요구합니다. 또한 구간별 배송비·계산 API 설정에서 음수나 잘못된 값 입력 시 오류 메시지가 내부 코드 경로로 표시되던 문제를 수정해, 이해하기 쉬운 안내 문구로 표시됩니다.", - "상품정보제공고시(및 공통정보) 관리 목록의 무한 스크롤을 정비했습니다. 목록 상단의 개수가 전체 등록 건수를 정확히 표시하고(이전에는 처음 불러온 만큼만 표시), 스크롤하면 다음 항목을 한 묶음씩 불러오다 전체를 다 불러오면 멈춥니다(이전에는 더 불러올 항목이 없어도 추가 요청을 반복). 다음 묶음을 불러오는 동안 \"불러오는 중\" 표시가 화면에 보이도록 했습니다. 같은 방식의 배송정책 \"추가 배송비 템플릿\" 선택 목록과 쿠폰 \"상품 검색\" 목록에도 동일하게 적용했습니다. 목록에서 각 항목의 사용 여부를 바로 켜고 끌 수 있는 토글을 추가했고, 상품 등록·수정 폼의 고시 선택에는 사용 중인 항목만 표시됩니다.", - "상품 옵션을 장바구니에 담거나 수량·옵션을 바꿀 때 품절·재고 부족이면 오류 화면 대신 \"재고가 부족합니다 (요청 N개, 가용 M개)\"처럼 명확한 안내가 표시됩니다. 이미 삭제됐거나 다른 사용자의 장바구니 항목을 조작하려 할 때도 알 수 없는 오류 대신 적절한 안내가 표시됩니다.", - "주문 상태를 진행 순서에 어긋나게(예: 구매확정 → 결제완료) 변경하던 문제 수정 — 관리자 주문 관리의 단건·일괄·상품옵션별 상태 변경 모두에서 허용되지 않는 상태 전이를 차단합니다. 정상 진행 방향(중간 단계 건너뛰기 포함)과 운영상 필요한 일부 되돌림(배송중→상품준비중 등), 취소했던 주문을 다시 판매 상태로 되살리는 복원은 그대로 허용됩니다. 차단될 때에는 \"구매확정 상태에서 결제완료 상태로는 변경할 수 없습니다\"처럼 어떤 전이가 왜 막혔는지 알 수 있는 안내가 표시됩니다(이전에는 주문 일괄·상품옵션 일괄 변경에서 \"Request failed with status code 422\"나 일반 실패 문구만 보였습니다).", - "상품 쿠폰의 \"최소 주문금액\" 조건이 주문 전체 합계가 아니라 그 쿠폰을 적용한 상품 금액 기준으로 검증되도록 수정. 다른 상품을 함께 담아 전체 금액만 채워 최소 주문금액 조건을 우회하던 문제를 막았습니다(주문·배송비 쿠폰은 종전처럼 전체 합계 기준).", - "쿠폰의 \"1인 사용 횟수 제한\"이 주문 단계에서 실제로 적용되도록 수정. 이전에는 다운로드 시점에만 확인되어, 한 주문에 같은 쿠폰을 여러 상품에 중복 적용하거나 이미 사용한 쿠폰을 다시 사용할 수 있었습니다. 이제 과거 사용 횟수와 한 주문 내 중복 적용을 합산해 한도를 초과하면 차단합니다.", - "\"다른 쿠폰과 함께 사용 불가\"로 설정한 쿠폰이 서로 다른 종류의 쿠폰(상품·주문·배송비)과 동시에 적용되던 문제 수정. 이제 어떤 종류의 쿠폰과도 함께 적용되지 않도록 차단합니다.", - "만료되었거나 존재하지 않는 주문서(임시 주문)에 접근할 때 서버 오류(500)로 응답하던 문제 수정. 이제 한국어·영어 환경 모두에서 \"찾을 수 없음\"(404)으로 정확히 응답하며, 주문서 작성 단계에서 적용 불가한 쿠폰(최소금액 미달·사용 한도 초과·중복 불가)이 있으면 해당 쿠폰을 할인에서 제외하고 사유를 안내합니다. 주문 확정 시 적용 불가 쿠폰이 남아 있으면 결제가 차단됩니다.", - "무통장입금 주문에서 1회 사용 제한 쿠폰이 입금 처리 전까지 다시 사용되던 문제 수정. 이제 주문 접수 즉시 쿠폰이 사용 처리되며, 미입금으로 자동취소되거나 주문을 취소하면(부분취소로 적용 조건이 깨지는 경우 포함) 해당 쿠폰이 다시 사용 가능하도록 복원됩니다. (#57 @ChoDongHyeon 님께서 제보해주셨습니다.)", - "상품 등록·수정의 옵션에서 기본 옵션 판매가를 바꾸면 상단 판매정보의 판매가가 함께 갱신되도록 수정 — 기본 옵션 라디오를 다른 옵션으로 바꿀 때도 상단 판매가가 새 기본 옵션 가격으로 맞춰지며, 비기본 옵션은 기존 가산액을 유지한 채 재계산됩니다.", - "추가옵션을 \"사용\"으로 켠 뒤 항목을 추가해도 설정이 \"미사용\"으로 풀리지 않도록 수정 — 추가옵션 항목 유무로 표시 상태가 결정되며, \"미사용\"으로 끌 때 입력한 항목이 있으면 확인 후 비웁니다.", - "상품 이미지를 상한(20장)보다 많이 선택하면 조용히 잘리지 않고 안내가 표시되도록 수정했고, 대표 이미지 안내 문구를 실제 동작(★ 버튼으로 지정)에 맞게 정정했습니다. 서버에서도 상품당 이미지 개수 상한을 검증합니다.", - "SEO 제목·설명을 직접 입력해 저장하면 상품명·설명으로 덮어써지던 문제 수정 — 동기화 사용 여부가 저장되어, 끄면 입력한 값이 그대로 유지되고 켜면 상품명·설명으로 자동 채워집니다. 동기화를 끈 뒤 수정 화면을 다시 열면 끈 상태가 그대로 복원됩니다.", - "판매가·재고 일괄변경에서 상품/옵션 헤더로 전체선택했을 때 \"변경할 항목 없음\"으로 표시되던 문제 수정 — 전체선택에서도 대상이 정확히 집계되어 변경이 적용됩니다. 변경 완료 안내 문구에 실제 변경 건수가 표시됩니다.", - "상품 라벨 적용 기간의 날짜를 직접 입력할 때 화면 오류(\"컴포넌트 로드 실패\")가 발생하던 문제 수정 — 라벨 기간 날짜, 쇼핑몰 연동 설정, 양식 저장 모달 입력이 모두 정상 동작합니다.", - "추가옵션이 등록된 상품을 수정 화면에서 열면 추가옵션이 표시되지 않고 \"미사용\"으로 보여 저장 시 사라질 수 있던 문제 수정 — 저장된 추가옵션이 그대로 불러와집니다.", - "상품 등록·수정의 추가옵션 \"미사용\" 확인 창, 공지 양식 저장 창이 열리지 않던 문제 수정 — 확인 창이 정상적으로 뜨고, 공지 양식 저장 창에서 이름을 입력해 저장할 수 있습니다.", - "상품 관리 일괄변경 확인 창에서 판매·전시 상태가 \"on_sale\", \"visible\" 같은 내부 코드값으로 표시되던 문제 수정 — 드롭다운에서 고른 그대로 \"판매중\", \"전시중\" 등 한글 라벨로 표시됩니다.", - "상품 관리 재고 일괄변경 확인 창의 옵션별 변경 내용이 \"재고: undefined undefined\"로 표시되던 문제 수정 — 입력한 \"+10개\" 같은 변경 값이 그대로 표시됩니다. 가격 일괄변경의 옵션별 표시도 동일하게 정정됩니다.", - "쇼핑몰 환경설정에서 상품 목록·카테고리·상품 상세의 \"SEO 제공 페이지\"를 꺼도 검색엔진 사이트맵(sitemap.xml)에 해당 주소가 그대로 남던 문제 수정 — 끈 페이지 유형의 주소는 사이트맵에서 제외됩니다." - ] - }, - { - "name": "Security", - "items": [ - "결제 거래번호 DB 레벨 멱등성 보장 — PG 결제 콜백이 동일한 거래번호로 중복 수신되어도 DB 가 자동 차단하여 중복 결제완료/포인트 적립 등의 사고를 원천 차단" - ] - } - ] - }, - { - "version": "1.0.0-beta.4", - "date": "2026-05-12", - "categories": [ - { - "name": "Added", - "items": [ - "이커머스 환경설정의 본인인증 정책 탭에 권한 기반 버튼 비활성화 적용 — 수정 권한이 없는 운영자는 \"정책 추가\" / \"수정\" / \"삭제\" / \"활성 토글\" 버튼이 자동 비활성화" - ] - }, - { - "name": "Fixed", - "items": [ - "위시리스트 활동 로그의 \"액션\" 열에 `wishlist.remove` 가 번역되지 않은 키 그대로 노출되던 문제 수정" - ] - } - ] - }, - { - "version": "1.0.0-beta.3", - "date": "2026-05-11", - "categories": [ - { - "name": "Added", - "items": [ - "상품 상세 페이지의 OG 메타태그·Product 구조화 데이터를 이커머스 모듈이 직접 선언하도록 SEO declaration 구현 — 썸네일 가로/세로 크기·가격·통화·재고 상태가 og:product:* 와 Schema.org Product/Offer/AggregateRating 스키마로 자동 출력됨", - "결제 취소·승인·무통장 입금 확인·재고 차감/복원·배송지 관리(CRUD + 기본 설정)·체크아웃 응답 변환 지점에 본인인증(IDV) 정책 등 외부 확장을 붙일 수 있는 확장점 제공", - "IDV 정책 3종(결제 취소·승인·무통장 입금 확인) 을 코어 본인인증 정책에 자동 동기화. 운영자가 관리자 UI 에서 수정한 필드(활성/유예/프로바이더/실패 모드) 는 업데이트 재설치 시 보존 — 모두 기본 비활성", - "결제 직전 성인/본인 확인 목적(`checkout_verification`) 등록 — 별도 본인인증 프로바이더 설치 시 결제 단계 전 검증 가능", - "결제 진행 직전 본인인증(`checkout_verification`) 발생 시 결제 도메인 전용 메일 문구가 발송되도록 모듈에서 메시지 템플릿을 직접 제공 (이전에는 일반 fallback 문구가 발송됨)", - "이커머스 환경설정에 \"본인인증 정책\" 탭 추가 — 결제 취소·승인 등 이커머스 컨텍스트 정책을 코어 정책 관리 화면과 동일한 UI/UX (검색·필터(강제 시점/출처)·페이지네이션·정책 추가/편집 모달 직접 호출·이 모듈이 등록한 본인인증 목적(checkout_verification) 메타 카드 상단 노출·데스크탑 테이블/모바일 카드 자동 분기 + enabled 인라인 토글) 로 한 곳에서 관리. 정책 목록 \"인증 목적\" 컬럼이 코드명 대신 사람이 읽을 수 있는 라벨로 표시", - "결제 직전 본인 확인 정책 신설 (`checkout_verification` 목적) — 운영자가 활성화하면 결제 진행 전 자동 본인인증 진행", - "이커머스 모듈 자체 권한 신설: `sirsoft-ecommerce.identity.policies.{read,update}` (정책 관리 최소 권한)", - "이커머스 알림 발송 이력 / 본인인증 이력 샘플 시더 추가 — `module:seed sirsoft-ecommerce --sample` 으로 결제·문의·주문 등 이커머스 영역 더미 이력만 독립 생성 (코어/타 모듈 영역 침범 없음)", - "이커머스 알림 정의 7종(주문 확인·배송 시작·구매 확정·주문 취소·신규 주문 관리자·상품 문의 접수·문의 답변)을 모듈 manifest 에서 직접 선언하도록 통일 — 활성화/업데이트 시 운영자 편집값을 보존하면서 자동 등록·정리되며, 권한·메뉴·본인인증과 동일한 declarative 패턴으로 일관화", - "활동 로그의 이커머스 액션 라벨(주문·상품·카테고리·쿠폰·배송 정책 등)을 이커머스 모듈 다국어 파일에서 관리하도록 정리 — 모듈 라벨이 자기 영역에서 자기설명되며, 일본어 등 추가 언어팩이 키 누락 없이 동기화" - ] - }, - { - "name": "Changed", - "items": [ - "코어 최소 요구 버전을 7.0.0-beta.4 로 상향 — 본인인증 정책 인프라 의존", - "결제 본인 확인 인증 목적 라벨/설명을 모듈 다국어 파일에서 관리하도록 정리 (운영자/번역자가 lang 파일만 편집해도 라벨 변경 가능)", - "클레임 사유·배송 유형·배송사의 다국어 라벨이 활성 언어팩 추가 시 자동으로 새 언어 키를 포함하도록 정합화 — 운영자가 한국어 라벨만 직접 수정한 row 도 한국어는 보존하면서 신규 언어(예: 일본어) 라벨은 자동 동기화", - "배송사 다국어 라벨이 활성 언어팩의 데이터를 자동으로 머지하도록 시더 정합화 (다른 entity 시더와 일관)" - ] - }, - { - "name": "Fixed", - "items": [ - "모듈 시더 실행 시 배송 유형 시더가 자동 호출되지 않던 회귀 수정 — 언어팩 활성/비활성 토글 시 즉시 다국어 라벨 반영", - "일부 주문에서 옵션 정보 직렬화 시 500 에러가 발생하던 문제 수정", - "상품 옵션 삭제 검증 시 런타임 에러가 발생하던 문제 수정", - "설치 직후 또는 활성 모듈 디렉토리 부재 시 환경설정 기본값이 비어있던 문제 수정 — 활성 디렉토리 부재 시 번들 디렉토리를 fallback 으로 사용", - "알림 템플릿 편집 모달의 입력 필드에서 글자를 입력할 때마다 화면이 심하게 버벅이던 문제 수정", - "이커머스 환경설정 알림 탭의 채널 라벨이 식별자(mail / database)로 노출되던 문제 수정 — 활성 언어 기준의 사람이 읽을 수 있는 라벨로 표시" - ] - } - ] - }, - { - "version": "1.0.0-beta.2", - "date": "2026-04-20", - "categories": [ - { - "name": "Added", - "items": [ - "**알림 시스템 강화**", - "7종 알림(주문확인/배송시작/구매확정/주문취소/신규주문/문의접수/문의답변) 실제 발송 구현", - "알림 목록에 채널별 수신자 뱃지 표시", - "알림 편집 모달에 수신자 설정 섹션 추가 — 타입 선택 및 규칙 관리", - "알림 클릭 시 이동 URL 기본값 설정", - "알림 정의 일괄 초기화 기능 — 확인 모달 + 진행 스피너 UX 포함", - "이커머스 알림 정의를 코어 리셋 로직에 자동 기여하여 기본값 복원 가능", - "알림 탭은 코어 알림 권한 기준으로 편집 가능 여부를 판단하도록 개선 (모듈 설정 권한과 분리)", - "**SEO 환경설정 강화**", - "페이지 타입별 SEO 변수 메타데이터 선언 (상품/카테고리/검색/쇼핑몰 메인)", - "쇼핑몰 메인 페이지 메타태그 설정 추가", - "캐시 삭제 확인 모달 및 캐시 용량 동적 표시", - "**배송유형 관리 기능**", - "환경설정 > 배송설정에서 배송유형 추가/수정/삭제/활성화 가능", - "기본 11종(국내 7종 + 해외 2종 + 기타 2종) 시딩", - "배송유형/배송사 테이블 드래그앤드롭 순서 변경", - "배송방법 \"직접입력\" 선택 시 다국어 배송방법명 입력 지원", - "클레임 사유(ClaimReason) 사용자 수정 보존 지원", - "주소 중복 등록 시 전용 도메인 예외 처리" - ] - }, - { - "name": "Changed", - "items": [ - "코어 최소 요구 버전을 7.0.0-beta.2 로 상향", - "알림 정의 시더를 인라인 방식으로 전환 — 사용자 수정 자동 보존 패턴 적용", - "레거시 메일 템플릿 테이블을 통합 알림 시스템으로 이관", - "알림 수신자를 채널별 독립 관리 방식으로 변경", - "이전 업그레이드 스텝에 클래스 부재 시 안전 스킵 처리 추가", - "상품 설명/공통정보 에디터를 확장 포인트(extension_point)로 변환", - "SEO 캐시 무효화를 코어 공통 캐시 인터페이스로 전환", - "상품정보제공고시, 공통정보, 알림 템플릿 편집의 다국어 탭을 동적 생성으로 전환", - "배송유형을 Enum에서 DB 테이블로 변경하여 관리자 화면에서 동적 관리 가능하도록 변경", - "주문 필터, 배송정책 폼의 하드코딩 옵션을 DB 기반 동적 렌더링으로 변경", - "주문/배송정책에서 직접입력 배송방법명 해석 지원 (스냅샷 기반)" - ] - }, - { - "name": "Removed", - "items": [ - "레거시 메일 템플릿 시더 및 관련 다국어 키 제거", - "ShippingTypeEnum, ShippingMethodEnum 제거 — DB 관리 방식으로 이관", - "환경설정의 도서산간 배송비 기본설정 제거 — 배송정책별 설정으로 완전 이관" - ] - }, - { - "name": "Fixed", - "items": [ - "마이페이지 배송지 관리에서 수정 버튼에 권한 체크가 누락되어 있던 문제 수정", - "사용자 화면에서 상품 설명 HTML이 텍스트로 노출되던 문제 수정", - "상품 설명/공통정보 에디터에 HTML 모드 전환 옵션 누락 수정", - "공통정보 패널 폼 초기화 및 모달 전환 관련 버그 수정", - "주문목록 검색필터 배송방법 선택 시 검증 에러 수정", - "환경설정/상품옵션 테이블의 Select 드롭다운이 가려지는 문제 수정", - "재설치·재시드 시 주문/상품 시퀀스 counter 가 0으로 리셋되어 주문번호 중복 위험이 있던 문제 수정 — 기존 counter 보존", - "재설치·재시드 시 관리자가 수정·추가한 택배사 정보가 기본 목록으로 초기화되던 문제 수정 — 사용자 수정분 자동 보존" - ] - } - ] - }, - { - "version": "1.0.0-beta.1", - "date": "2026-04-01", - "categories": [ - { - "name": "Changed", - "items": [ - "오픈 베타 릴리즈" - ] - } - ] - }, - { - "version": "0.20.0", - "date": "2026-03-31", - "categories": [ - { - "name": "Added", - "items": [ - "기본 클레임 사유(환불/취소) 자동 등록 — 모듈 설치 시 7건 기본 사유 시딩 (고객 귀책 4건 + 판매자 귀책 3건)", - "`ClaimReasonSeeder` 시더 추가 (`getSeeders()` 및 `DatabaseSeeder` 등록)", - "업그레이드 스텝 `Upgrade_0_20_0` — 기존 설치 환경에 기본 클레임 사유 삽입" - ] - }, - { - "name": "Fixed", - "items": [ - "장바구니 API 서버 에러 수정 — 삭제된 배송정책을 참조하는 상품의 배송비 계산 시 null TypeError 방지, 배송비 0원 처리 (OrderCalculationService)", - "사용자 주문 취소 완료 후 모달이 닫히지 않는 문제 수정 — `showCancelModal: false` 상태값 방식을 `G7Core.modal.close('modal_cancel_order')` 호출 방식으로 교체 (userCancelOrderHandlers)", - "사용자 주문 취소 모달 취소 사유 selectbox 다크모드 미적용 수정 — 다크모드 색상 클래스 추가 (_modal_cancel.json)", - "사용자 주문 취소 모달 취소 사유 selectbox 하단 여백 누락 수정 — `mb-4` 추가 (_modal_cancel.json)" - ] - } - ] - }, - { - "version": "0.19.6", - "date": "2026-03-30", - "categories": [ - { - "name": "Fixed", - "items": [ - "상품옵션/고시정보 다국어 입력 stale closure 수정 — productOptionHandlers/productNoticeHandlers에서 디바운스 병합 시 이전 키 변경 유실 방지 (`_partial_product_options.json`, `_partial_product_notices.json`)", - "주문상세 배송방법 `$t:` 번역 토큰 원문 노출 수정 — 배송방법 표시 바인딩에 `raw:` 마커 적용 (_partial_order_info.json)", - "FileUploader onRemove 시 form 데이터 동기화 누락 수정 — remove 핸들러에 setState 추가하여 폼 데이터에서 삭제된 파일 즉시 반영 (_partial_image_upload.json)" - ] - } - ] - }, - { - "version": "0.19.5", - "date": "2026-03-30", - "categories": [ - { - "name": "Fixed", - "items": [ - "주문 일괄 변경 활동 로그 `:count` 플레이스홀더 미치환 수정 — bulk 핸들러 4개(bulkUpdate, bulkStatusUpdate, bulkShippingUpdate, bulkOptionStatusChange)의 `description_params`에 `count` 키 추가", - "주문 상태 변경 활동 로그에서 enum 값(payment_complete, shipping 등)이 번역되지 않고 원시값 노출 수정 — Order/OrderOption 모델 `$activityLogFields`의 status 필드를 `type: 'enum'`으로 변경 + `OrderStatusEnum::labelKey()` 메서드 추가" - ] - } - ] - }, - { - "version": "0.19.4", - "date": "2026-03-30", - "categories": [ - { - "name": "Changed", - "items": [ - "`window.G7Config` 설정 노출 최소화 — frontend_schema 필드별 `expose: false` 처리 (basic_info: route_path, no_route만 노출, language_currency: default_currency, currencies만 노출, order_settings/shipping/seo/review_settings 전체 미노출)" - ] - } - ] - }, - { - "version": "0.19.3", - "date": "2026-03-30", - "categories": [ - { - "name": "Fixed", - "items": [ - "관리자 다크모드 레이아웃 전수 수정 — 101개 파일에서 누락된 `dark:` variant 약 415건 추가 (text-gray, bg-white/gray, focus:ring, text-red/blue/green/yellow, border-gray, hover 등)", - "상품 일괄 변경 모달 라벨 미정의 CSS 클래스 수정 — `className: \"label\"` → `\"form-label\"` (다크모드 텍스트 가독성 복원)", - "상품 목록 판매가/재고 일괄 변경 버튼 다크모드 텍스트 색상 누락 수정 — `text-gray-700 dark:text-gray-300` 추가", - "레이아웃 JSON 내 Select 컴포넌트 다크모드 배경색 수정 — `dark:bg-gray-800` → `dark:bg-gray-700` (카드 배경과 구분)" - ] - } - ] - }, - { - "version": "0.19.2", - "date": "2026-03-30", - "categories": [ - { - "name": "Added", - "items": [ - "검색/필터/정렬 성능 향상을 위한 일반 인덱스 추가 — ecommerce_products(selling_price, list_price, stock_quantity, shipping_policy_id, barcode, tax_status, updated_at), ecommerce_orders(total_amount, order_device, confirmed_at, [user_id+ordered_at]), ecommerce_order_addresses(orderer_phone, recipient_phone)" - ] - }, - { - "name": "Changed", - "items": [ - "Repository 7개의 직접 `MATCH...AGAINST` 호출을 Laravel Scout `Model::search()` 파이프라인으로 전환 — DBMS별 검색 분기를 엔진 내부에서 처리 (ProductRepository, CategoryRepository, BrandRepository, CouponRepository, ProductCommonInfoRepository)", - "ReportRepository의 관계 검색을 `DatabaseFulltextEngine::whereFulltext()` 정적 헬퍼로 전환 — 다중 DBMS 호환", - "Category, Brand, PromotionCoupon, ProductCommonInfo 모델에 `FulltextSearchable` 인터페이스 + `Searchable` 트레이트 추가", - "`searchIndexShouldBeUpdated()` 훅 기반 전환 — `HookManager::applyFilters()` 사용으로 검색 플러그인이 모델별 인덱스 업데이트 제어 가능", - "마이그레이션 FULLTEXT 인덱스 생성을 `DatabaseFulltextEngine::addFulltextIndex()` 정적 헬퍼로 전환 — DBMS별 조건부 처리" - ] - }, - { - "name": "Fixed", - "items": [ - "인덱스명 약어 수정 — `idx_ecom_*` → `idx_ecommerce_*`, `stock_qty` → `stock_quantity` (네이밍 규칙 준수)" - ] - } - ] - }, - { - "version": "0.19.1", - "date": "2026-03-30", - "categories": [ - { - "name": "Added", - "items": [ - "상품 옵션 재생성 시 기존 옵션 초기화 확인 모달 연결 — 기존 옵션이 존재할 때 generateOptions 호출 시 확인 모달 표시, 확인 후 skipConfirm 플래그로 실제 생성 진행 (optionHandlers, _modal_confirm_regenerate.json)" - ] - } - ] - }, - { - "version": "0.19.0", - "date": "2026-03-30", - "categories": [ - { - "name": "Added", - "items": [ - "FULLTEXT 인덱스(ngram) 추가 — ecommerce_products(name, description), ecommerce_categories(name, description), ecommerce_brands(name), ecommerce_promotion_coupons(name, description), ecommerce_product_common_infos(name, content) 총 9개", - "Product 모델에 Laravel Scout `Searchable` 트레이트 + `FulltextSearchable` 인터페이스 적용" - ] - }, - { - "name": "Changed", - "items": [ - "상품/카테고리/브랜드/쿠폰/공통정보 관리자 검색 쿼리를 `LIKE '%keyword%'`에서 `MATCH...AGAINST IN BOOLEAN MODE`로 전환 — JSON TEXT 컬럼 검색 성능 향상", - "공개 상품 검색(통합검색) 쿼리를 로케일 루프 `JSON_EXTRACT + LIKE`에서 `MATCH...AGAINST`로 전환", - "관리자 상품 목록 검색 필드에 '상품설명', 'SKU' 옵션 추가 — description FULLTEXT 검색 지원" - ] - } - ] - }, - { - "version": "0.18.4", - "date": "2026-03-30", - "categories": [ - { - "name": "Fixed", - "items": [ - "상품 수정 시 이미지 순서 유실 수정 — syncImages에서 배열 인덱스를 sort_order SSoT로 사용, stale sort_order 값 무시 (ProductService)", - "상품 수정 시 대표이미지 hash 기반 설정 — syncImages에 thumbnail_hash 파라미터 추가, hash 기준 is_thumbnail 갱신 (ProductService)", - "상품 복사 시 원본 이미지 파일 복사 — copyFromSource() 메서드 추가, hash 기반 원본 조회 및 스토리지 파일 복사 (ProductImageService)", - "상품 복사 시 createImages에서 hash 기반 이미지 복사 분기 — hash 존재 시 copyFromSource 호출, 미존재 시 기존 직접 생성 (ProductService)", - "상품 이미지 임시 파일 연결 시 sort_order 재배치 — linkTempImages에서 기존 이미지 최대 sort_order 뒤에 배치, is_thumbnail 해제 (ProductImageService)", - "상품 수정 폼 이미지 메타데이터 확장 — download_url, size, size_formatted, mime_type, is_image, width, height, thumbnail_hash 추가 (ProductService)", - "상품 이미지 업로드 레이아웃 전면 수정 — event 기반 콜백, hash 기반 대표이미지, 편집/복사 모드별 API 엔드포인트 분기, onReorder/onRemove 핸들러 추가 (_partial_image_upload.json)", - "ProductResource에 thumbnail_hash 필드 추가 — 대표이미지 hash 반환 (ProductResource)", - "StoreProductRequest에 thumbnail_hash 검증 추가, description maxLength 65535 적용 (StoreProductRequest)", - "ProductController 에러 응답에 상세 에러 정보 추가 — ValidationException errors 전달, debug 모드 예외 메시지 포함 (ProductController)" - ] - } - ] - }, - { - "version": "0.18.3", - "date": "2026-03-30", - "categories": [ - { - "name": "Fixed", - "items": [ - "카테고리 삭제 모달 영문 다국어 ICU MessageFormat 오류 수정 — `{count, plural, ...}` 형식을 단순 `{count}` 치환 형식으로 변경 (category.json)" - ] - } - ] - }, - { - "version": "0.18.2", - "date": "2026-03-29", - "categories": [ - { - "name": "Fixed", - "items": [ - "주문 일괄 배송중 변경 시 운송장/택배사 정보가 저장되지 않는 버그 수정 — `bulkUpdateShipping()`에서 shipping 레코드 미존재 시 옵션별 신규 생성 로직 추가 (OrderRepository)" - ] - }, - { - "name": "Changed", - "items": [ - "order_shippings 테이블에서 `carrier_name` 컬럼 삭제 — 택배사명은 carrier_id 관계를 통해 사용자 로케일에 맞게 동적 조회로 전환 (OrderShipping, OrderOptionResource, OrderListResource, OrderShippingResource)" - ] - } - ] - }, - { - "version": "0.18.1", - "date": "2026-03-29", - "categories": [ - { - "name": "Fixed", - "items": [ - "배송완료 상태 변경 시 운송장 입력 강제 해제 — `shippingInfoRequiredStatuses()`에서 DELIVERED 제거 (OrderStatusEnum)", - "주문 목록 일괄 배송완료 시 상태가 \"배송중\"으로 표시되는 버그 수정 — `bulkUpdateShipping()`에서 order_status를 SHIPPING으로 덮어쓰는 코드 삭제 (OrderRepository)", - "주문상세 일괄변경 시 carrier vs carrier_id 키 불일치로 422 에러 발생 수정 — FE `body.carrier` → `body.carrier_id`, BE `$validated['carrier']` → `$validated['carrier_id']` (orderDetailHandlers, OrderController)", - "주문상세 옵션 일괄 상태 변경 후 부모 주문 상태 미동기화 수정 — `syncParentOrderStatus()` 메서드 추가, 모든 활성 옵션 동일 상태면 주문도 해당 상태로, 혼합 시 최저 진행 단계로 설정 (OrderOptionService)", - "ActivityLog 일괄 상태 변경 로그 미기록 수정 — Service results 키를 `order_option_id`로 변경하여 Listener pluck와 일치, 분할/병합 케이스별 loggable 대상 정확히 지정 (OrderOptionService, OrderActivityLogListener)", - "프론트엔드 주문 목록 배송완료 선택 시 운송장 필수 경고/비활성화 해제 — shippingStatuses 배열 및 modal 조건에서 delivered 제거 (orderHandlers, _modal_bulk_confirm.json)" - ] - }, - { - "name": "Changed", - "items": [ - "OrderOption 일괄 상태 변경 results 구조 확장 — `option_id` → `order_option_id`, `split_order_option_id`, `merged_into_order_option_id`, `is_full_quantity` 추가 (OrderOptionService)" - ] - } - ] - }, - { - "version": "0.18.0", - "date": "2026-03-29", - "categories": [ - { - "name": "Added", - "items": [ - "검색/필터/정렬 성능 향상을 위한 누락 인덱스 일괄 추가 — ecommerce_products 7개(selling_price, list_price, stock_quantity, shipping_policy_id, barcode, tax_status, updated_at), ecommerce_orders 4개(total_amount, order_device, confirmed_at, [user_id+ordered_at] 복합), ecommerce_order_addresses 2개(orderer_phone, recipient_phone)", - "상품 복사 모드 이미지 파일 복사 기능 — copyFromSource() 메서드로 원본 이미지 파일과 메타데이터를 새 상품에 복제 (ProductImageService)", - "상품 등록 시 에러 상세 로깅 — ValidationException/Exception 분리 처리 및 debug 모드 시 exception 메시지 포함 응답 (ProductController)" - ] - }, - { - "name": "Fixed", - "items": [ - "상품 등록 시 상세설명 255자 제한 버그 수정 — TranslatableField maxLength 65535 명시 (StoreProductRequest)", - "상품 복사/수정 화면에서 FileUploader 렌더링 에러 수정 — getDetailForForm() 이미지 매핑에 누락 필드(mime_type, is_image, download_url, size, order 등) 추가 (ProductService)", - "상품 복사 컨트롤러에서 중복 download_url 추가 로직 제거 — getDetailForForm()에서 이미 포함 (ProductController)", - "상품 복사 등록 시 500 에러 수정 — createImages()에서 hash 기반 복사 모드 이미지를 copyFromSource()로 파일 복사 처리 (ProductService)", - "인덱스 마이그레이션 날짜 순서 수정 — 테이블 생성 마이그레이션 이후로 재배치 + 인덱스 중복 방어 코드 추가", - "상품 수정/복사 화면에서 대표이미지 표시 안됨 수정 — id 기반 → hash 기반 대표이미지 식별로 전환, 복사 모드 이미지 삭제 시 원본 보호 (ProductResource, ProductService, _partial_image_upload.json)", - "상품 복사 등록 시 임시 이미지 정렬/썸네일 버그 수정 — linkTempImages()에서 sort_order 미설정(기존 이미지와 충돌) 및 is_thumbnail 미해제(이중 썸네일) 문제 해결 (ProductImageService)", - "상품 이미지 filesChange 이벤트가 form.images를 pendingFiles로 덮어쓰는 버그 수정 — 복사 이미지 데이터 유실 방지, uploadComplete도 form.images 미수정으로 createImages/linkTempImages 이중 처리 해소 (_partial_image_upload.json)" - ] - } - ] - }, - { - "version": "0.17.2", - "date": "2026-03-29", - "categories": [ - { - "name": "Fixed", - "items": [ - "주문상세 일괄변경 시 toast에 번역 키(`no_items_selected`)가 원문 그대로 표시되는 버그 수정 — 누락된 다국어 키 추가 (ko/en)", - "주문상세 일괄변경에서 \"선택 없음\"과 \"취소 가능 상품 없음\" 메시지를 동일 키로 사용하던 것을 `no_items_selected` / `no_cancellable_items`로 분리 (orderDetailHandlers)" - ] - } - ] - }, - { - "version": "0.17.1", - "date": "2026-03-28", - "categories": [ - { - "name": "Fixed", - "items": [ - "공통정보 추가 시 \"ko 언어의 값은 필수입니다\" 검증 오류 수정 — MultilingualInput의 setState에서 `$event` 전체 대신 `$event.target.value`로 다국어 값 객체만 저장", - "공통정보 뷰 패널 \"기본값으로 설정\" 버튼 미동작 수정 — 모달 ID 불일치(`set_default_confirm_modal` → `modal_set_default_confirm`) 및 확인 모달에 기본값 설정 API 호출 추가" - ] - } - ] - }, - { - "version": "0.17.0", - "date": "2026-03-30", - "categories": [ - { - "name": "Added", - "items": [ - "상품 상세 페이지에 1:1 문의 탭 추가", - "문의 게시판이 설정된 경우에만 탭 노출", - "비회원 포함 누구나 문의 목록 조회 가능, 로그인 사용자가 문의 작성/수정/삭제 가능", - "비밀글 토글 필터 (전체 / 비밀글 제외) 제공", - "관리자 권한 보유자가 문의에 답변 등록/수정/삭제 가능", - "작성자명 마스킹, 비밀글 잠금 처리", - "마이페이지에 내 문의내역 조회 API 추가 (답변 내용 포함)", - "이커머스 환경설정에 1:1 문의게시판 설정 섹션 추가", - "게시판 모듈과 연동하여 사용할 게시판을 지정할 수 있음", - "게시판 모듈 미설치 시에도 이커머스 단독 운영 가능", - "문의 접수/답변 완료 메일 템플릿 2종 추가 (`inquiry_received`, `inquiry_replied`)", - "문의 작성 시 상품명 다국어 스냅샷 저장 (상품 삭제 후에도 문의 내역에서 상품명 표시 유지)", - "기존 설치 환경을 위한 업그레이드 스텝 추가 (`Upgrade_0_9_0`)" - ] - }, - { - "name": "Changed", - "items": [ - "상품 복사 기능을 POST API 방식에서 navigate 패턴으로 전환 — 복사 옵션 선택 → 상품 등록 페이지로 이동하여 사전 입력 (역할 복사와 동일 UX)", - "복사 옵션 11개로 확장: 이미지, 옵션, 카테고리, 판매정보, 상품설명, 상품정보제공고시, 공통정보, 기타정보, 배송정책, SEO, 식별코드", - "수정/목록 페이지 복사/삭제 버튼을 setState 방식에서 openModal/closeModal 패턴으로 변경" - ] - }, - { - "name": "Fixed", - "items": [ - "문의 삭제 시 Action 훅이 트랜잭션 내부에서 실행되던 문제 수정 (롤백 시 부작용 방지)", - "훅명 표준화: 엔티티 부분의 하이픈(`-`)을 언더스코어(`_`)로 변경", - "마이페이지 문의 목록 조회 시 타입 불일치로 발생할 수 있는 오류 수정", - "문의 가능 여부(`inquiry_available`) 판단 기준을 게시판 slug 존재 여부로 통일", - "목록 페이지 복사 모달 제출 시 \"POST method not supported\" 에러 수정 — GET 라우트에 POST 요청 불일치 해결", - "수정 페이지 복사/삭제 버튼 미동작 수정 — modals 섹션에서 setState 플래그 대신 openModal 핸들러 사용", - "배송정책 목록 페이지에서 데이터 0건일 때 DataGrid와 Empty State가 동시 표시되는 버그 수정 — DataGrid, bulk_actions, table_header_bar, pagination에 조건부 렌더링 추가", - "배송정책 복사 시 원본 데이터가 폼에 로드되지 않는 버그 수정 — copy_source data_source 추가 및 initShippingPolicyForm 핸들러에 isCopy 모드 처리", - "배송정책 신규 등록/수정 시 is_default=true 설정 시 기존 기본 정책이 해제되지 않는 버그 수정 — create()/update()에 clearDefault() 호출 추가", - "배송정책 목록 DataGrid subRow에서 `$t:` 번역 토큰이 원문 그대로 표시되는 버그 수정 — `{{}}` 표현식 내 문자열 리터럴에서 `$t()` 함수 호출로 변경", - "배송정책 복사 모달이 실제 국가별 설정을 반영하지 않는 버그 수정 — 플랫 필드(배송방법, 운송사, 부과정책)에서 country_settings 기반 반복 렌더링으로 재설계" - ] - } - ] - }, - { - "version": "0.16.2", - "date": "2026-03-27", - "categories": [ - { - "name": "Fixed", - "items": [ - "카테고리 크로스 depth 이동 시 parent_id가 null로 변경되지 않는 버그 수정 — `isset()`이 null에 대해 false를 반환하여 `array_key_exists()`로 교체 (CategoryService)", - "이커머스 부모 메뉴 URL을 null로 변경 — 대응 라우트 없는 `/admin/ecommerce` URL이 404 발생 (module.php)" - ] - } - ] - }, - { - "version": "0.16.1", - "date": "2026-03-26", - "categories": [ - { - "name": "Fixed", - "items": [ - "상품 하위 라우트(form, copy, can-delete, logs) `whereNumber` 제약으로 product_code(영숫자) 404 발생 수정 — `where('product', '[0-9a-zA-Z]+')` 패턴으로 변경하여 ID/코드 둘 다 대응" - ] - } - ] - }, - { - "version": "0.16.0", - "date": "2026-03-26", - "categories": [ - { - "name": "Added", - "items": [ - "ProductLog → ActivityLog 통합: 상품 처리로그를 표준 ActivityLog 시스템으로 전환", - "ProductActivityLogListener: ActivityLog 표준 패턴 적용 (description_key, ChangeDetector)", - "ActivityLogDescriptionResolver: product_id → 상품명 실시간 해석", - "ProductController::logs(): 상품별 처리로그 API (OrderController::logs 패턴 동일)", - "Bulk Update ChangeDetector 지원: 일괄 변경 시 필드별 old/new 변경 내역 기록", - "OrderService: bulkUpdate, bulkUpdateStatus, bulkUpdateShipping에 스냅샷 캡처 + ChangeDetector 적용", - "OrderOptionService: bulkChangeStatusWithQuantity에 스냅샷 캡처 + ChangeDetector 적용", - "ProductOptionService: bulkUpdatePriceByMixedIds, bulkUpdateStockByMixedIds, bulkUpdate(통합)에 스냅샷 캡처 + ChangeDetector 적용", - "EcommerceAdminActivityLogListener: `option.after_bulk_update` 훅 구독 추가 (통합 일괄 수정 로깅)", - "ProductOptionRepositoryInterface: `findByIds()`, `getIdsByProductIds()` 메서드 추가", - "Listener 레벨 Bulk 스냅샷/ChangeDetector 전수 적용 (13건)", - "ProductActivityLogListener: bulk_update/bulk_price_update/bulk_stock_update before 스냅샷 + after ChangeDetector (6훅 추가)", - "CouponActivityLogListener: before_bulk_status 스냅샷 캡처 + after ChangeDetector 연동 (1훅 추가)", - "EcommerceAdminActivityLogListener: ExtraFeeTemplate bulk delete/toggle/create, ShippingPolicy bulk delete/toggle, ProductReview bulk delete 스냅샷 + ChangeDetector (9훅 추가)", - "삭제 작업 시 deleted_items 스냅샷 보존 (ExtraFeeTemplate, ShippingPolicy, ProductReview)", - "21개 모델에 $activityLogFields 정의 (코어 5개, 이커머스 11개, 게시판 4개, 페이지 1개)", - "Upgrade_0_16_0: ecommerce_product_logs 테이블 DROP + 레이아웃 캐시 클리어", - "활동 로그 Per-Item 로깅 전환: 모든 bulk 핸들러가 변경된 건별로 loggable_id 기록", - "OrderActivityLogListener: bulk_update/bulk_status_update/bulk_shipping_update per-Order, bulk_option_status_change per-OrderOption 전환", - "ProductActivityLogListener: bulk_update/bulk_price_update/bulk_stock_update per-Product 전환", - "CouponActivityLogListener: bulk_status per-Coupon 전환", - "EcommerceAdminActivityLogListener: ExtraFee/ShippingPolicy bulk toggle per-item, bulk_delete per-item (loggable_type/loggable_id 직접 지정) 전환", - "ShippingPolicyActivityLogListener: 중복 bulk 훅 구독 2개 삭제 (EcommerceAdminActivityLogListener에 위임)", - "EcommerceUserActivityLogListener: 사용자 주문생성(order.after_create) + 구매확인(order-option.after_confirm) 로그 추가", - "ActivityLogHandler: 삭제된 엔티티용 loggable_type/loggable_id 직접 지정 지원" - ] - }, - { - "name": "Removed", - "items": [ - "ecommerce_product_logs 테이블 (ActivityLog로 대체)", - "ProductLogService, ProductLogRepository, ProductLogController, ProductLog 모델 등 관련 코드 전량 삭제" - ] - }, - { - "name": "Changed", - "items": [ - "처리로그 레이아웃: log.description → log.localized_description (ActivityLogResource 기준)", - "Bulk Update 훅 시그니처: after_bulk_* 훅에 $allChanges 파라미터 추가 (하위 호환 유지)" - ] - } - ] - }, - { - "version": "0.15.0", - "date": "2026-03-26", - "categories": [ - { - "name": "Added", - "items": [ - "구매확정 기능 — 마이페이지 주문상세에서 옵션별 구매확정 API 및 UI", - "`confirmed_at` 컬럼 추가 (ecommerce_order_options 테이블)", - "OrderOption 모델: `confirmed_at` fillable/casts, `review()` HasOne 관계", - "ConfirmOrderOptionRequest: 소유권, 상태, 중복 확정 검증", - "OrderService.confirmOption(): 옵션 확정 + 전체 옵션 확정 시 주문도 CONFIRMED 전환", - "User OrderController.confirmOption(): 구매확정 API 엔드포인트", - "`sirsoft-ecommerce.order-option.before_confirm` / `after_confirm` 훅", - "OrderConfirmPointListener: 구매확정 시 포인트 적립 더미 리스너", - "리뷰 작성 기능 — 구매확정 후 리뷰 작성 모달 UI 및 이미지 순차 업로드", - "userConfirmOrderHandlers.ts: 구매확정 커스텀 핸들러", - "userReviewHandlers.ts: 리뷰 작성 + 이미지 순차 업로드 커스텀 핸들러", - "구매확정/리뷰 작성 모달 레이아웃 (_modal_confirm_purchase.json, _modal_write_review.json)", - "_items.json에 구매확정/리뷰작성/리뷰완료 버튼 조건부 렌더링 (PC + 모바일 동일)", - "OrderOptionResource에 `can_confirm`, `can_write_review`, `has_review` 필드 추가", - "환경설정: `confirmable_statuses` (주문설정), `review_settings` (리뷰설정) 추가", - "사용자 권한: `user-orders.confirm`, `user-reviews.write` 등록 (module.php + Upgrade_0_15_0)", - "리뷰 라우트에 `permission:user,sirsoft-ecommerce.user-reviews.write` 미들웨어 추가", - "다국어 키 추가 (ko/en): 구매확정/리뷰 관련 메시지, 예외, 프론트엔드 번역" - ] - }, - { - "name": "Changed", - "items": [ - "show.json: initLocal에 구매확정/리뷰 상태 변수 추가, modals에 구매확정/리뷰 모달 partial 등록", - "handlers/index.ts: confirmOrderOption, submitReview 핸들러 등록" - ] - }, - { - "name": "Fixed", - "items": [ - "상품폼 처리로그 설명 미표시 — ProductLogResource는 `description` 필드를 반환하나 레이아웃이 `localized_description`을 참조하여 빈 값 표시 (v0.13.0 변경 시 오류)" - ] - } - ] - }, - { - "version": "0.14.0", - "date": "2026-03-25", - "categories": [ - { - "name": "Added", - "items": [ - "클레임 사유 관리 시스템 — DB 기반 환불 사유 관리 (ClaimReason 모델, Repository, Service, Controller, FormRequest, Resource)", - "`ecommerce_claim_reasons` 테이블: type, code, name(다국어), fault_type, is_user_selectable, is_active, sort_order", - "Admin API 7개: 목록/생성/상세/수정/삭제/상태토글/활성목록 (`/admin/claim-reasons`)", - "User API 1개: 사용자 선택 가능 사유 목록 (`/user/claim-reasons`)", - "ClaimReasonFaultTypeEnum (customer/seller/carrier), ClaimReasonTypeEnum (refund)", - "쇼핑몰 환경설정 > 클레임 탭 — 환불 사유 인라인 편집 UI (배송사 관리 패턴)", - "테이블 뷰(PC) + 카드 뷰(모바일) 반응형 레이아웃", - "EcommerceSettingsController에 syncReasons() 연동", - "업그레이드 스크립트 (Upgrade_0_14_0) — 기존 CancelReasonTypeEnum 7개 값을 DB 시드 데이터로 마이그레이션" - ] - }, - { - "name": "Changed", - "items": [ - "주문 취소 사유를 하드코딩 Enum에서 DB 기반으로 전환", - "CancelReasonTypeEnum 삭제 → ClaimReason DB 조회로 대체", - "CancelOrderRequest (Admin/User): `Rule::in(enum)` → `Rule::exists(ecommerce_claim_reasons, code)`", - "OrderCancel 모델: Enum cast 제거, getRefundReasonLabel() 헬퍼 추가", - "OrderCancellationService: Enum 참조 제거, 단순 string 저장", - "관리자/사용자 취소 모달 — 하드코딩 Option에서 동적 iteration으로 변경", - "Admin 모달: refundReasons 데이터소스 + iteration 기반 동적 렌더링", - "User 모달: 동일 패턴 적용 (user/claim-reasons API 사용)" - ] - }, - { - "name": "Fixed", - "items": [ - "활동 로그 description 표시 시 번역 키가 원문 그대로 노출되는 문제 수정", - "ActivityLogDescriptionResolver 추가 — 표시 시점에 ID를 엔티티 이름으로 해석 (HookManager 필터 훅)", - "src/lang/{ko,en}/activity_log.php 번역 파일 동기화 (TranslationServiceProvider 로딩 경로 일치)", - "OrderActivityLogListener: handleOrderAfterSendEmail/handleOrderOptionAfterStatusChange description_params 불일치 수정", - "OrderOption의 product_option_name, option_name, option_value 미저장 및 다국어 미지원 수정 (OrderProcessingService, OrderOption)", - "createOrderOptions()에서 product_option_name/option_value 누락 → ProductOption 다국어 원본 직접 저장", - "3개 컬럼 varchar(255) → JSON 마이그레이션 + 모델 array cast 추가", - "Resource 3개(OrderOptionResource, OrderListResource, UserOrderListResource)에서 로케일 변환 추가", - "기존 문자열 데이터 하위호환 (is_array 체크)", - "StoreEcommerceSettingsRequest: claim/review_settings 탭 저장 실패 수정 — `_tab` validation 및 `validatedSettings()` 누락 보완", - "FormRequest 로케일 하드코딩 수정 — `name.ko` 직접 검증을 `LocaleRequiredTranslatable` Rule로 교체 (StoreEcommerceSettingsRequest, StoreProductRequest)" - ] - } - ] - }, - { - "version": "0.13.8", - "date": "2026-03-25", - "categories": [ - { - "name": "Fixed", - "items": [ - "옵션 레벨 promotions_applied_snapshot이 camelCase로 저장되는 버그 수정 — DTO `toArray()` 호출 누락으로 PHP 프로퍼티명이 그대로 직렬화됨 (OrderProcessingService)" - ] - } - ] - }, - { - "version": "0.13.7", - "date": "2026-03-25", - "categories": [ - { - "name": "Fixed", - "items": [ - "취소 모달 debounce 타이머에서 __g7ActionContext 미복원으로 모달 상태 업데이트 실패 수정 (cancelOrderHandlers, userCancelOrderHandlers)", - "debounce 콜백 실행 시 ActionDispatcher의 try/finally가 이미 __g7ActionContext를 복원하여 모달의 actionContext 사라짐", - "캡처/복원 패턴 적용으로 setLocal()이 모달의 actionContext.setState()를 정상 호출" - ] - }, - { - "name": "Changed", - "items": [ - "OrderActivityLogListener에서 명시적 log_type 지정 제거 — 코어 resolveLogType() 요청 경로 기반 판별로 대체 (6개 핸들러)" - ] - } - ] - }, - { - "version": "0.13.6", - "date": "2026-03-25", - "categories": [ - { - "name": "Fixed", - "items": [ - "toPreviewArray() refund_total 배송비 이중 계산: refundAmount에 이미 배송비 차이가 내포되어 있는데 shippingDifference를 별도 가산하여 환불 예정액 과다 표시 (AdjustmentResult)" - ] - }, - { - "name": "Added", - "items": [ - "refund_total 정합성 검증 테스트 4건: 부분취소 무료→유료 전환, 전체취소 배송비 포함, 쿠폰+배송비 복합 시나리오 (OrderAdjustmentServiceTest G-1~G-4)" - ] - } - ] - }, - { - "version": "0.13.5", - "date": "2026-03-25", - "categories": [ - { - "name": "Fixed", - "items": [ - "상품금액 정액 쿠폰 수량 미반영 수정: 정액 할인이 수량과 무관하게 1회만 적용되던 결함 → 적용 대상 옵션의 수량만큼 할인 적용 (OrderCalculationService.calculateCouponDiscount)", - "OrderSeeder 정액 상품쿠폰 수량 미반영 수정: 시더에서도 target_scope별 적용 대상 수량 계산하여 할인 적용 (OrderSeeder.getTargetQuantity)" - ] - }, - { - "name": "Added", - "items": [ - "부분취소 시 쿠폰 조건 미달로 결제금액 증가 시 취소 차단: validateRefundNotNegative 검증 + cancel_blocked 플래그 (OrderCancellationService, AdjustmentResult)", - "Admin/User 취소 모달에 취소 불가 경고 배너 + 취소 버튼 비활성화 (_modal_cancel_order.json, _modal_cancel.json)", - "수량별 정액 할인 테스트 16개 + cancel_blocked 테스트 4개 (OrderCalculationServiceTest, OrderAdjustmentServiceTest)" - ] - } - ] - }, - { - "version": "0.13.4", - "date": "2026-03-25", - "categories": [ - { - "name": "Fixed", - "items": [ - "쿠폰 수정 화면 적용 상품 테이블 데이터 미표시: CouponResource의 included_products/excluded_products에 product_code, name_localized, selling_price_formatted 필드 추가 (CouponResource.php)" - ] - } - ] - }, - { - "version": "0.13.3", - "date": "2026-03-25", - "categories": [ - { - "name": "Fixed", - "items": [ - "관리자 취소 모달 수량 변경 시 비교 테이블 미갱신 결함 수정: updateCancelQuantity 핸들러에 value 파라미터 누락 추가 (_modal_cancel_order.json)", - "관리자 취소 모달 onMount setState로 인한 모달/페이지 scope 단절 해소: onMount 제거 + 초기화 핸들러에서 상태 설정 및 estimateRefundAmount 호출 패턴으로 전환 (orderDetailHandlers.ts, _modal_cancel_order.json)" - ] - } - ] - }, - { - "version": "0.13.2", - "date": "2026-03-25", - "categories": [ - { - "name": "Fixed", - "items": [ - "상품수량(item_count) 스냅샷이 고유 상품 수(distinct)가 아닌 총 수량 합계로 계산되도록 수정 (captureOrderSnapshot)", - "상품쿠폰 최대할인금액 초과 결함 수정: 아이템별 할인 산출 후 글로벌 cap 비례 안분 적용 (applyProductCoupons 3단계 리팩토링)", - "CouponApplication 기록 시 존재하지 않는 필드($coupon->max_discount_amount) 대신 실제 DB 컬럼($coupon->discount_max_amount) 사용하도록 수정 (4곳)", - "buildCouponSnapshotsFromOrder()에 max_discount_amount 필드 추가하여 취소 재계산 시 쿠폰 최대할인 정보 보존" - ] - } - ] - }, - { - "version": "0.13.1", - "date": "2026-03-25", - "categories": [ - { - "name": "Added", - "items": [ - "취소 모달 주문금액 비교 테이블: 취소 전/후 금액 비교 표시 (상품수량, 총 정가금액, 상품/주문/배송비쿠폰 할인, 할인코드 할인, 기본/추가배송비, 포인트 사용, 과세금액, 부가세, 실결제금액, 적립예정포인트)", - "쿠폰 상세 표시: 상품쿠폰/주문쿠폰/배송비쿠폰별 적용 쿠폰명 + 할인금액 표시 (original_coupons, recalculated_coupons)", - "다통화 금액 표시: 총 정가금액/실결제금액 셀 하단에 다통화 금액 인라인 표시 (mc_original_snapshot, mc_recalculated_snapshot)", - "스냅샷 필드 확장: captureOrderSnapshot/captureRecalcSnapshot에 5개 필드 추가 (total_product_coupon_discount_amount, total_order_coupon_discount_amount, total_vat_amount, total_earned_points_amount, item_count)", - "AdjustmentResult DTO: mcOriginalSnapshot, mcRecalculatedSnapshot, originalCoupons, recalculatedCoupons 프로퍼티 추가", - "상품 소계 표시: 단가 × 수량 = 소계 형태로 변경 (Admin/User 모달 공통)", - "Admin 취소 모달 크기 1.5배 확대 (750px → 1125px), User 모달 크기 조정 (lg → 750px)", - "다국어 키 18개 추가 (ko/en, Admin ecommerce + User sirsoft-basic)" - ] - }, - { - "name": "Changed", - "items": [ - "취소 모달 \"환불 예정금액\" 섹션을 \"주문금액 비교\" 테이블로 전면 교체 (Admin/User 공통)", - "변경된 금액에 적색 하이라이트 적용 (text-red-600 dark:text-red-400 font-semibold)" - ] - } - ] - }, - { - "version": "0.13.0", - "date": "2026-03-25", - "categories": [ - { - "name": "Added", - "items": [ - "ActivityLog 리스너 전면 구현:", - "OrderActivityLogListener (주문 CUD + 상태변경 + 일괄처리 + 배송 + 이메일 + 취소/부분취소/환불)", - "CouponActivityLogListener (쿠폰 CUD + 일괄 상태변경)", - "ShippingPolicyActivityLogListener (배송정책 CUD + 토글 + 일괄처리)", - "CategoryActivityLogListener (카테고리 CUD + 순서변경 + 상태전환)", - "EcommerceAdminActivityLogListener (브랜드/라벨/공통정보/고시정보/추가비용/배송업체/상품옵션/상품이미지/리뷰 통합)", - "EcommerceUserActivityLogListener (장바구니/위시리스트/쿠폰사용/마일리지 — ActivityLogType::User)", - "활동 로그 다국어 키 100개 정의 (resources/lang/ko/activity_log.php, en/activity_log.php)", - "$activityLogFields 메타데이터: Product, Order, Coupon, ShippingPolicy 모델", - "ActivityLog 샘플 시더 (database/seeders/ActivityLogSampleSeeder.php)" - ] - }, - { - "name": "Changed", - "items": [ - "ProductActivityLogListener: ActivityLogService 호출 → Log::channel('activity') 직접 호출로 전환", - "주문상세 로그 탭 레이아웃: log.description → log.localized_description", - "상품폼 로그 섹션 레이아웃: log.description → log.localized_description" - ] - } - ] - }, - { - "version": "0.12.6", - "date": "2026-03-25", - "categories": [ - { - "name": "Added", - "items": [ - "OrderSeeder: 쿠폰 적용 주문 생성 — CouponIssue 테이블 조회 기반, 상품금액/주문금액 쿠폰 배분, promotions_applied_snapshot 스냅샷, 약 40% 주문에 쿠폰 적용", - "OrderSeeder: 주문 삭제 시 쿠폰 발급 상태 자동 복원 (used → available)", - "OrderSeeder: 배송비 쿠폰(shipping_fee) 적용 — 배송 레코드에 shipping_discount_amount 배분, 주문 합계에 배송 할인 반영", - "OrderSeeder: 결제완료 이상 상태 쿠폰 적용 비율 80%로 상향, 쿠폰 우선 배정 상태를 먼저 생성하여 쿠폰 재고 확보", - "OrderSeeder: payment_complete 비율 15→20%로 상향, 쿠폰 우선 상태의 비회원 비율 절반으로 축소" - ] - }, - { - "name": "Fixed", - "items": [ - "OrderSeeder: 주문 합계 재계산 시 쿠폰별 할인 필드(product_coupon, order_coupon, coupon, code) 정확히 합산하도록 개선", - "OrderSeeder: 주문 합계의 base_shipping_amount, shipping_discount_amount가 배송 레코드 실값을 반영하도록 수정", - "OrderSeeder: orders.promotions_applied_snapshot 저장 누락 수정 — 부분취소 시 쿠폰 재적용 불가 문제 해결 (coupon_issue_ids, product_promotions, order_promotions 구조)", - "OrderSeeder: 쿠폰 복원을 주문 삭제 전으로 이동 — order_id FK ON DELETE SET NULL로 인한 고아 쿠폰(status=used, order_id=null) 방지", - "OrderAdjustmentService: 복원 쿠폰 정보에서 다국어 쿠폰명(JSON 배열)을 문자열로 변환하지 않아 React 렌더링 오류 발생 — getLocalizedName() 사용으로 수정" - ] - } - ] - }, - { - "version": "0.12.5", - "date": "2026-03-25", - "categories": [ - { - "name": "Fixed", - "items": [ - "시더/팩토리 유령 할인 제거 — 출처 없는 랜덤 할인이 환불 재계산 시 잘못된 discount_difference 유발 (OrderSeeder, OrderOptionFactory, OrderFactory)", - "OrderSeeder: 결제 완료 주문의 mc_total_paid_amount가 항상 0이던 다중통화 불일치 수정" - ] - } - ] - }, - { - "version": "0.12.4", - "date": "2026-03-25", - "categories": [ - { - "name": "Added", - "items": [ - "사용자 주문 상품 선택 핸들러 추가 — toggleItemSelection, toggleSelectAllItems (userCancelOrderHandlers)", - "initUserCancelItems: 선택된 상품(_local.selectedItemIds) 기반으로 취소 대상 필터링" - ] - }, - { - "name": "Fixed", - "items": [ - "사용자 주문 취소 모달이 열리지 않던 버그 수정 — modals 섹션 모달에 id 추가 및 openModal/closeModal 패턴으로 전환 (userCancelOrderHandlers, _modal_cancel.json)", - "Upgrade_0_7_0: app(ModuleSettingsService::class) 직접 호출을 module_setting() 헬퍼로 교체" - ] - } - ] - }, - { - "version": "0.12.3", - "date": "2026-03-24", - "categories": [ - { - "name": "Fixed", - "items": [ - "OrderResource(주문상세 API)에 can_cancel ability 누락 — 사용자 주문상세 화면에서 취소 버튼이 절대 표시되지 않던 버그 수정", - "OrderResource에 resolveAbilities() override 추가 (상태 + 환경설정 + 권한 복합 조건)", - "Resource/Request에서 app(EcommerceSettingsService::class) 직접 호출을 module_setting() 헬퍼로 교체 (4개 파일)" - ] - }, - { - "name": "Added", - "items": [ - "주문상세 API abilities 테스트 3건 추가 (can_cancel true/false/권한 없음)" - ] - } - ] - }, - { - "version": "0.12.2", - "date": "2026-03-24", - "categories": [ - { - "name": "Fixed", - "items": [ - "주문 취소 시 환경설정(cancellable_statuses)을 참조하지 않고 하드코딩된 기본값만 사용하던 버그 수정", - "Admin/User CancelOrderRequest: EcommerceSettingsService에서 취소 가능 상태 조회", - "UserOrderListResource: can_cancel 판정에 환경설정 반영", - "OrderCancellationService: 상세 에러 메시지(현재 상태 + 취소 가능 상태 목록) 반환", - "프론트엔드 취소 핸들러에서 API 상세 에러 메시지를 토스트에 표시하도록 개선", - "주문 취소 시 취소 사유(reason) 미선택으로 검증 우회되던 버그 수정 (nullable → required)", - "취소 모달에서 422 validation 에러가 PG 에러 영역에 잘못 표시되던 버그 수정", - "422 → 필드별 에러 표시, 기타 에러 → PG 에러 영역으로 분리", - "CancelOrderRequest failedValidation에 필드별 에러(errors) 미포함 수정", - "취소 사유 필드 에러 메시지에 원시 필드명(reason) 노출 → 한국어 표시명(취소 사유) 적용" - ] - }, - { - "name": "Added", - "items": [ - "Order 모델에 getCancelDeniedReason(), getCancellableStatuses() 헬퍼 메서드 추가", - "취소 불가 상세 에러 다국어 키 추가 (order_not_cancellable_detail, order_already_cancelled)", - "환경설정 기반 취소 가능 상태 검증 테스트 4건 추가", - "취소 사유 필수 검증 테스트 추가", - "취소 모달 validation_error_title 다국어 키 추가 (ko/en)" - ] - }, - { - "name": "Changed", - "items": [ - "취소 모달 환불 우선순위 라디오: 마일리지 사용 주문 조건 제거 → 항상 표시" - ] - } - ] - }, - { - "version": "0.12.1", - "date": "2026-03-24", - "categories": [ - { - "name": "Fixed", - "items": [ - "취소 모달 배송비 정책명이 번역 키 그대로 노출되던 버그 수정 (OrderAdjustmentService)", - "OrderShipping에 shippingPolicy 관계 추가 (누락)", - "buildFullCancelResult() / buildShippingDetails()에서 ShippingPolicy::getLocalizedName() 사용", - "order.json에 default_shipping 번역 키 추가 (폴백용)" - ] - } - ] - }, - { - "version": "0.12.0", - "date": "2026-03-24", - "categories": [ - { - "name": "Added", - "items": [ - "배송비 계산 확장 훅: `sirsoft-ecommerce.shipping.calculate_fee` — 외부 확장이 배송비 계산을 오버라이드 가능", - "프로모션 스냅샷 저장 훅: `sirsoft-ecommerce.calculation.filter_promotions_snapshot` — 주문 생성 시 스냅샷에 확장 데이터 주입 가능", - "프로모션 스냅샷 복원 훅: `sirsoft-ecommerce.adjustment.filter_restore_promotions` — 환불 재계산 시 스냅샷에서 확장 데이터 해석/복원 가능", - "AppliedShippingPolicy DTO: `hookOverridden` 플래그 — 훅 오버라이드 배송비 식별용", - "스냅샷 기반 훅 오버라이드 배송비 보존: 환불 시 훅이 계산한 배송비를 스냅샷에서 복원 (훅 비활성 대응)", - "PluginExtensibilityTest: 16개 테스트 (배송정책 6, 할인정책 7, 복합 시나리오 3)", - "관리자/사용자 취소 모달 환불 예상금액 UI 확장: 배송비 정책별 상세, 잔여 PG/마일리지 잔액, 복원 쿠폰 목록, 총 환불액(PG+마일리지+배송비)", - "`changeRefundPriority` / `changeUserRefundPriority` 커스텀 핸들러: 환불 우선순위 변경 → debounce 재계산 연동", - "`refund_priority` 파라미터를 환불 예상금액 API에 전달 (관리자/사용자 공통)", - "mc_* 다중통화 환불 테스트 5건: 기본 변환, null 스냅샷, 마일리지 변환, 배송비 변환, previewArray 포함 확인", - "관리자 취소 모달 취소사유 라디오 7개 옵션 (CancelReasonTypeEnum 기반): order_mistake, changed_mind, reorder_other, delayed_delivery, product_info_different, admin_cancel, etc", - "i18n 취소사유 키 중첩 구조 변경: `cancel_reason_type_label` + `reason.{type}` 형태 (ko/en)", - "스냅샷 기반 환불 재계산 테스트 84건 추가 (B-1~B-7):", - "OrderAdjustmentServiceTest +33건 (쿠폰 복원 감지, 환불 우선순위, 배송비 상세, mc_* 변환, 복합 시나리오)", - "OrderCancellationServiceTest +17건 (쿠폰 복원 실행, 환불 처리, mc_* 레코드, 스냅샷 갱신)", - "OrderCalculationServiceTest +11건 (스냅샷 모드 검증)", - "OrderCalculationServiceMultiCurrencyTest +5건 (스냅샷 환율 변환)", - "Admin/User Feature 테스트 +12건 (환불 예상 API, 쿠폰 복원 DB)", - "프론트엔드 레이아웃 렌더링 테스트 +42건 (취소 모달 구조, 라디오, 조건부 렌더링)" - ] - }, - { - "name": "Fixed", - "items": [ - "`buildFullCancelResult`에 mc_* 다통화 변환, 배송비 상세, 복원 쿠폰 정보, 잔여 잔액, 환불 우선순위 누락 수정", - "`buildFullCancelResult`에서 OrderShipping 컬럼명 오류 수정 (`base_shipping_fee` → `base_shipping_amount`, `extra_shipping_fee` → `extra_shipping_amount`)" - ] - } - ] - }, - { - "version": "0.11.1", - "date": "2026-03-24", - "categories": [ - { - "name": "Added", - "items": [ - "OrderShippingRepository: 배송 모델 Repository 인터페이스 + 구현체 신규 생성", - "CouponIssueRepository: `update()`, `findByIds()`, `findByIdsWithRelations()` 메서드 추가", - "SnapshotProduct, SnapshotProductOption DTO: 스냅샷 기반 환불 재계산용", - "RefundPriorityEnum: 환불 우선순위 정의 (SHIPPING_DISCOUNT, ORDER_DISCOUNT, MILEAGE 등)", - "CouponRestoreListener: 주문 취소 시 쿠폰 사용 상태 자동 복원", - "OrderRefund 다통화 환불 컬럼 마이그레이션 (mc_refund_columns)", - "스냅샷 기반 환불 재계산 로직 (OrderCalculationService snapshot_mode, OrderAdjustmentService)", - "취소/환불 관련 예외 다국어 키 추가 (cancel_option_not_found, pg_refund_failed 등)", - "취소/환불 관련 Enum 다국어 라벨 추가 (CancelStatusEnum, RefundStatusEnum 등)", - "스냅샷 재계산 테스트 (SnapshotRecalculationTest), 쿠폰 복원 리스너 테스트 (CouponRestoreListenerTest)" - ] - }, - { - "name": "Fixed", - "items": [ - "Repository 패턴 위반 수정: 4개 서비스 + 1개 리스너의 직접 Eloquent 쿼리 → Repository 인터페이스 주입", - "CurrencyConversionService 예외 메시지 하드코딩 → `__()` 다국어 처리" - ] - }, - { - "name": "Changed", - "items": [ - "OrderAdjustmentService: CouponIssue 직접 접근 → CouponIssueRepositoryInterface 주입", - "OrderCancellationService: OrderShipping 직접 접근 → OrderShippingRepositoryInterface 주입", - "OrderOptionService: OrderShipping 직접 접근 → OrderShippingRepositoryInterface 주입", - "ShippingCarrierService: OrderShipping 직접 접근 → OrderShippingRepositoryInterface 주입", - "CouponRestoreListener: CouponIssue 직접 접근 → CouponIssueRepositoryInterface 주입", - "EcommerceServiceProvider: OrderShippingRepository 바인딩 등록" - ] - } - ] - }, - { - "version": "0.11.0", - "date": "2026-03-24", - "categories": [ - { - "name": "Added", - "items": [ - "주문 취소/환불 시스템: 관리자 전체취소·부분취소, 사용자 취소 요청, 환불 금액 산출", - "주문 취소 관련 모델 추가 (OrderCancel, OrderCancelOption, OrderRefund, OrderRefundOption)", - "주문 취소 Enum 추가 (CancelStatusEnum, CancelTypeEnum, CancelReasonTypeEnum, RefundStatusEnum, RefundMethodEnum 등)", - "주문 취소 서비스 추가 (OrderCancellationService, OrderAdjustmentService)", - "주문 취소 DTO 추가 (CancellationResult, CancellationAdjustment, OrderAdjustment, AdjustmentResult)", - "관리자 주문 취소 API 엔드포인트 (estimate-refund, cancel)", - "사용자 주문 취소 API 엔드포인트 (estimate-refund, cancel)", - "관리자 주문상세 취소 모달 레이아웃 (_modal_cancel_order.json)", - "사용자 마이페이지 주문 취소 모달 확장 (부분취소 지원)", - "주문 취소 프론트엔드 핸들러 (cancelOrderHandlers, userCancelOrderHandlers)", - "토스페이먼츠 환불 리스너 (PaymentRefundListener)", - "관리자 환경설정 주문취소 가능 상태 다중 선택 UI (_tab_order_settings.json)", - "관리자 주문상세 결제정보에 취소/환불 금액 표시 (_partial_payment_info.json)", - "주문관리 목록 주문자(회원) 검색 필터: SearchableDropdown 기반 UUID 검색", - "주문관리 DataGrid 주문자 클릭 시 회원/비회원 분기 검색 (회원: orderer_uuid, 비회원: search_keyword)", - "관리자 주문상세 일괄변경에서 주문취소 선택 시 PG 결제 취소 체크박스 인라인 표시" - ] - }, - { - "name": "Fixed", - "items": [ - "주문 취소 모달: 환불 예상금액 미표시 수정 (isolated 모달 스코프 상태 불일치 해결)", - "주문 취소 모달: 현재상태 컬럼에 enum 원시값 대신 다국어 라벨 표시", - "주문 취소 모달: PG 체크박스/라벨 클릭 연동 (htmlFor 추가)" - ] - }, - { - "name": "Changed", - "items": [ - "취소 상태 Enum 재설계: PENDING→REQUESTED, FAILED 제거 (2단계: 신청/완료)", - "환불 상태 Enum 재설계: 6단계 (신청/승인/처리중/보류/완료/반려), FAILED 제거", - "Option 상태를 부모 상태와 동일하게 동기화", - "OrderCancel 모델: isFailed() 메서드 제거", - "OrderRefund 모델: isFailed()→isRejected() 변경", - "PG 환불 실패 시 FAILED 상태 설정 대신 REQUESTED 유지 (에러 정보만 기록)", - "OrderStatusEnum: 주문취소(CANCELLED) 상태 추가", - "SequenceType: 취소(CANCEL), 환불(REFUND) 시퀀스 타입 추가", - "Order 모델: 취소 관련 관계 및 메서드 추가 (cancels, refunds, isCancellable 등)", - "OrderOption 모델: cancelled_quantity 컬럼 추가", - "OrderProcessingService: 취소 처리 로직 연동", - "관리자 OrderController: 취소/환불 산출 액션 추가", - "사용자 OrderController: 취소 요청 액션 추가" - ] - } - ] - }, - { - "version": "0.10.6", - "date": "2026-03-24", - "categories": [ - { - "name": "Changed", - "items": [ - "상품목록 API: 비활성 옵션도 포함하여 로드 (관리자 화면에서 비활성 옵션 표시)", - "상품상세 API: `includeInactive` 파라미터 추가 (관리자 수정 화면에서 비활성 옵션 포함)", - "옵션 일괄 변경 `option_name` 검증 규칙: `string` → `array` (다국어 배열 지원)", - "옵션 일괄 변경 `list_price` 검증 규칙 추가", - "일괄 변경 확인 모달: 처리중 상태 표시 (spinner + 버튼 비활성화)", - "상품 인라인 수정 필드 추적 확장 (`modifiedProductFields` 추가)", - "상품목록 DataGrid 선택 개수 표시 비활성화", - "주문목록 배송국가: 국가 코드(KR) → 다국어 국가명(한국) 표시 (OrderListResource, DataGrid)" - ] - }, - { - "name": "Fixed", - "items": [ - "시더(OrderAddressFactory)에서 `recipient_country_code` 기본값 누락으로 주문목록 배송국가 미표시 문제 수정", - "관리자 주문 수정 시 배송국가(`recipient_country_code`) 변경 불가 문제 수정 (OrderService, UpdateOrderRequest)", - "상품목록 리소스: `options` relation 로드 시 `option_stock_sum`, `options_count`가 빈 값으로 표시되는 문제 수정 (`relationLoaded` 분기 추가)", - "상품옵션 일괄 변경 후 상품 재고(stock_quantity)가 동기화되지 않는 문제 수정 (`ProductOptionService::bulkUpdate`에 `syncStockFromOptions` 호출 추가)", - "옵션 재고 합산 시 비활성 옵션이 포함되는 문제 수정 (프론트엔드 `updateOptionField`, `calculateTotalOptionStock` 핸들러에 `is_active` 필터 추가)", - "일괄 변경 확인 모달: 인라인 수정 시 `{method}` 플레이스홀더가 리터럴로 표시되는 문제 수정 (인라인 전용 다국어 키 분리)" - ] - } - ] - }, - { - "version": "0.10.5", - "date": "2026-03-23", - "categories": [ - { - "name": "Changed", - "items": [ - "상품수정 화면: 일부 미구현 탭 숨김 처리" - ] - }, - { - "name": "Fixed", - "items": [ - "UserAddressSeeder: name 필드에 다국어 배열 대입으로 인한 \"Array to string conversion\" 오류 수정 (단순 문자열로 변경)" - ] - } - ] - }, - { - "version": "0.10.4", - "date": "2026-03-23", - "categories": [ - { - "name": "Changed", - "items": [ - "주문상세 활동 로그: Badge → 아바타 원형+이름 스타일로 변경, 처리자 클릭 시 ActionMenu(회원정보 보기) 추가 (PC+모바일)", - "상품수정 활동 로그: \"작업\" 컬럼 제거, 처리자 클릭 시 ActionMenu(회원정보 보기) 추가 (PC+모바일)", - "주문상세/상품수정 활동 로그 UI 일관성 통일" - ] - }, - { - "name": "Fixed", - "items": [ - "상품수정 활동 로그: Select $event → $event.target.value, refreshDataSource → refetchDataSource, Pagination type:pageChange → event:onPageChange, 빈 상태 조건 경로 수정, 데이터소스 sort_order/per_page 파라미터 추가", - "주문상세 활동 로그: 동일 버그 6건 수정 (주문상세와 상품수정 패턴 통일)" - ] - } - ] - }, - { - "version": "0.10.3", - "date": "2026-03-23", - "categories": [ - { - "name": "Added", - "items": [ - "주문목록 주문상품 컬럼: 대표상품 썸네일, \"외 X건\" 표시, 상품수정 링크(새 창)", - "OrderListResource: first_option에 product_code 필드 추가" - ] - } - ] - }, - { - "version": "0.10.2", - "date": "2026-03-23", - "categories": [ - { - "name": "Added", - "items": [ - "주문상세 상품 이미지/상품명 클릭 시 상품수정 페이지로 이동 (새 창)" - ] - } - ] - }, - { - "version": "0.10.1", - "date": "2026-03-23", - "categories": [ - { - "name": "Changed", - "items": [ - "API Resource: user.id → user.uuid 전환 (OrderResource, OrderListResource, CouponResource, CouponIssueResource, ProductReviewResource, ProductLogResource, UserAddressResource, ExtraFeeTemplateResource)", - "user_id FK → user.uuid 전환 (CouponIssueResource, ProductReviewResource, UserAddressResource)", - "ExtraFeeTemplate: created_by → creator.uuid 전환 (creator 관계 활용)", - "쿠폰 관리 등록자 컬럼: eager-load에 uuid 컬럼 추가 (CouponRepository, ProductLogRepository)", - "관리자 레이아웃: user.id → user.uuid 참조 전환 (4개 파일)", - "orderHandlers.ts: localStorage 키에 user.uuid 사용", - "FormRequest: 정수 검증 → UUID 검증 전환 (CouponListRequest, CouponIssuesListRequest)", - "MergeCartOnLoginListener: Activity Log user_id → uuid 전환" - ] - } - ] - }, - { - "version": "0.10.0", - "date": "2026-03-22", - "categories": [ - { - "name": "Added", - "items": [ - "주문상세 이메일 발송 기능 — `POST /orders/{order}/send-email` API + 모달 UI 연동", - "SendOrderEmailRequest FormRequest — 이메일/메시지 검증", - "OrderService.sendEmail() — `Mail::raw()` + `HookManager::doAction()` 패턴 (코어 `sendTestMail()` 참조)", - "이메일 발송 다국어 메시지 (ko/en messages + validation + 프론트엔드 toast)" - ] - }, - { - "name": "Fixed", - "items": [ - "이메일 모달 보내기 버튼 무반응 — `actions` 배열 추가 (sequence: setState → apiCall → toast/closeModal)", - "이메일 모달 Textarea 높이 미적용 — `min-h-[100px]` (빌드 CSS 미존재) → `min-h-32` (safelist 존재)", - "이메일 모달 API 호출 시 `/undefined` 요청 — `params.endpoint` → `action.target` 수정", - "이메일 발송 실패 시 에러 원인 미표시 — catch 블록에 `$e->getMessage()` 상세 포함" - ] - }, - { - "name": "Changed", - "items": [ - "이메일 모달 `_global.isSending` → `_local.isSending` (모달 스코프 내 상태 관리)" - ] - } - ] - }, - { - "version": "0.9.3", - "date": "2026-03-22", - "categories": [ - { - "name": "Added", - "items": [ - "비회원 주문 Factory/Seeder 추가 — `OrderFactory::guest()` 상태, `OrderSeeder`에 비회원 주문 생성 로직", - "OrderOptionResource에 `list_price`, `list_price_formatted`, `final_amount`, `final_amount_formatted` 필드 추가", - "다국어 키 추가 — `phone_or_tel_hint` (ko/en)" - ] - }, - { - "name": "Fixed", - "items": [ - "주문상세 저장 버튼 미동작 — apiCall `params.endpoint` → `target` 속성, `auth_mode: \"required\"` 추가, onSuccess/onError 콜백 위치 수정", - "주문상세 수취인 정보 미표시 — `orderDetailHandlers.ts` 데이터 접근 경로 수정 (`_context.data.data` 우선 접근)", - "주문상세 수취인 정보 수정 값 미반영 — 폼 자동 바인딩 (`dataKey: \"form\"` + child `name`) 패턴 적용", - "Validation error 적색 테두리 미표시 — `.input` 클래스에 border 없음, `border border-red-500 dark:border-red-500` 으로 수정" - ] - }, - { - "name": "Changed", - "items": [ - "OrderService.update() 수취인 필드 저장 처리 추가 — shippingAddress 관계 업데이트", - "주문상세 폼 데이터 바인딩을 `initLocal` 패턴으로 변경 — 커스텀 핸들러(`initOrderDetailForm`) → 데이터소스 `initLocal`", - "UpdateOrderRequest validation 강화 — `recipient_name`, `recipient_zipcode`, `recipient_detail_address` 필수, `recipient_phone`/`recipient_tel` 택1 필수 (`required_without`)", - "CreateOrderRequest validation 통일 — `recipient_phone` required → `required_without:recipient_tel`, `address_detail` nullable → required", - "배송메시지 Textarea `textarea-sm` (80px), 관리자메모 Textarea `textarea-md` (128px) 크기 적용", - "OrderOptionResource `mc_subtotal_discount_amount` 제거 — 존재하지 않는 DB 필드 참조" - ] - } - ] - }, - { - "version": "0.9.2", - "date": "2026-03-22", - "categories": [ - { - "name": "Added", - "items": [ - "OrderResource에 `total_quantity`, `total_list_price`, `total_list_price_formatted` 필드 추가 — 합계행 백엔드 필드 기반 표시", - "주문상세 합계행(footerCells) 및 개별행에 다통화(보조 통화) 표시 — iteration 패턴으로 판매가/소계 하단에 표시" - ] - }, - { - "name": "Fixed", - "items": [ - "주문상세 합계행 판매가/수량/할인/적립 합계가 실제 데이터와 불일치 — computed 전면 제거 후 백엔드 필드로 대체", - "할인 0원에 마이너스 부호 표시(-0원) — 백엔드 `total_discount_amount > 0` 조건으로 수정", - "개별행 실구매가격이 할인 전 금액 표시 — `subtotal_price_formatted` → `final_amount_formatted`(할인 후)로 변경", - "OrderOptionResource의 `mc_subtotal_discount_amount` 버그 — 잘못된 필드(`mc_coupon_discount_amount`) 참조 수정" - ] - }, - { - "name": "Changed", - "items": [ - "관리자 주문상세 라우트를 ID 기준에서 order_number 기준으로 변경 — URL이 `/admin/ecommerce/orders/ORD-xxx` 형태로 표시 (상품 수정의 product_code 패턴과 동일)", - "Order 모델에 `resolveRouteBinding()` 추가 — 숫자는 ID, 문자열은 order_number로 자동 판별 (하위 호환)", - "API 라우트 `whereNumber('order')` 제약 제거 — order_number(문자열) 수용", - "주문 목록/상세/리뷰 레이아웃 및 커스텀 핸들러에서 navigate 경로를 order_number 기반으로 변경" - ] - } - ] - }, - { - "version": "0.9.1", - "date": "2026-03-21", - "categories": [ - { - "name": "Fixed", - "items": [ - "상품리뷰 정렬 기준 validation 수정 — sort 파라미터 `latest,oldest` → `created_at_desc,created_at_asc`로 통일 (PublicReviewListRequest)" - ] - } - ] - }, - { - "version": "0.9.0", - "date": "2026-03-21", - "categories": [ - { - "name": "Added", - "items": [ - "주문상세 수량 단위 상태 변경 기능 — 주문상품옵션의 일부 수량만 선택하여 상태 변경 (예: 3개 중 2개만 배송중으로 변경)", - "주문상품옵션 병합(Merge) 로직 — 분할 후 남은 수량을 같은 상태로 변경 시 자동 병합 (cascade 삭제 안전 처리 포함)", - "일괄변경 확인 모달에 상품 목록 표시 — 썸네일, 상품명/옵션/SKU, 판매가, 수량 편집 Input, 현재상태 Badge", - "updateChangeQuantityHandler 핸들러 추가 — 모달 내 수량 편집 시 1~최대수량 클램핑", - "다국어 키 5종 추가 — product_col, price_col, change_quantity_col, current_status_col, quantity_note (ko/en)", - "백엔드 테스트 15건 추가 — 금액 분할 정확성, 병합 로직, cascade 안전성, 스냅샷 보존, 순차 분할", - "프론트엔드 핸들러 테스트 12건 추가 — bulkConfirmItems 구성, 수량 클램핑, changeQuantities 반영" - ] - }, - { - "name": "Fixed", - "items": [ - "금액 분할 계산 2배 오류 수정 — replicate() 후 원본+복제본 금액이 동일하여 ratio 적용 시 2배로 계산되던 버그 (원본 금액 사전 캡처 방식으로 수정)" - ] - }, - { - "name": "Changed", - "items": [ - "buildOrderDetailBulkConfirmDataHandler 개선 — 선택 상품의 상세 정보(bulkConfirmItems)와 수량 맵(changeQuantities)을 _local에 저장 후 모달 열기", - "일괄변경 확인 모달 크기 lg로 확대 및 상품 목록 테이블 추가" - ] - } - ] - }, - { - "version": "0.8.3", - "date": "2026-03-21", - "categories": [ - { - "name": "Fixed", - "items": [ - "주문목록/주문상세 모달 9개 규정 미준수 전면 수정 — btn 클래스 → 명시적 Tailwind 클래스, 푸터 gap/mt 규정 준수, Icon name 규정 준수 (pen-to-square, trash)", - "모달 onError 핸들러 배열 형식 수정 — 객체 → 배열, error.message → $error.message (preset_save, preset_edit, preset_delete_confirm)", - "모달 액션 버튼 스피너/비활성화 처리 추가 — _global.isProcessing/isSaving/isDeleting/isDownloading/isSending 상태 기반 로딩 UI", - "Excel 다운로드 모달 meta.is_partial 누락 추가, 불필요한 props.id/closeOnOutsideClick/p-6 래퍼 제거" - ] - } - ] - }, - { - "version": "0.8.2", - "date": "2026-03-20", - "categories": [ - { - "name": "Changed", - "items": [ - "주문 일괄변경 드롭다운에서 pending_order(주문대기) 상태 제외 — 주문목록/주문상세 레이아웃 + 백엔드 FormRequest 검증 (BulkUpdateOrdersRequest)", - "주문상세 일괄변경 드롭다운에 누락된 상태 추가 — pending_payment, shipping_hold, confirmed, cancelled (pending_order 제외 전체 상태 표시)", - "주문 일괄변경 드롭다운 너비 확대(w-36 → w-44) — 주문목록/주문상세 공통, 한글 상태명 잘림 방지" - ] - }, - { - "name": "Fixed", - "items": [ - "주문상세 일괄변경 드롭다운 pending_payment 다국어 키 오류 수정 — order_status_options.pending_payment → order_status_options.payment_pending (enum 값과 다국어 키 불일치)" - ] - } - ] - }, - { - "version": "0.8.1", - "date": "2026-03-20", - "categories": [ - { - "name": "Added", - "items": [ - "OrderStatusEnum 배송 정보 필수 메서드 추가 — requiresShippingInfo(), shippingInfoRequiredStatuses(), shippingInfoRequiredValues()", - "주문 일괄변경 배송 상태(shipping_ready/shipping/delivered) 선택 시 택배사/송장번호 필수 검증 — 프론트엔드 핸들러(toast 경고), 레이아웃 모달(버튼 비활성화 + 경고 메시지), 백엔드 FormRequest 3계층 검증", - "BulkUpdateOrdersRequest carrier_id 존재 검증 추가 — Rule::exists(ShippingCarrier::class, 'id')", - "주문상세/주문관리 일괄변경 확인 모달에 배송정보 누락 경고 메시지 추가 (적색 텍스트)", - "다국어 키 추가 — carrier_required (ko/en), tracking_number_required (ko/en)", - "OrderOptionBulkStatusTest 배송정보 필수 검증 테스트 3건 추가" - ] - }, - { - "name": "Fixed", - "items": [ - "processOrderDetailBulkChangeHandler/saveAdminMemoHandler API 호출 수정 — G7Core.api.call() → G7Core.api.patch() (존재하지 않는 메서드 호출 오류)", - "processOrderDetailBulkChangeHandler 모달/디스패치 호출 수정 — G7Core.modal?.close?.() / G7Core.dispatch?.() 패턴 적용", - "processOrderDetailBulkChangeHandler 상태 조회 방식 변경 — getLocal() → action.params 직접 전달 (stale 상태 방지)" - ] - }, - { - "name": "Changed", - "items": [ - "BulkUpdateOrdersRequest/BulkChangeOrderOptionStatusRequest 배송 상태 검증을 하드코딩 → OrderStatusEnum::shippingInfoRequiredValues() 메서드로 리팩토링" - ] - } - ] - }, - { - "version": "0.8.0", - "date": "2026-03-20", - "categories": [ - { - "name": "Changed", - "items": [ - "OrderOptionStatusEnum 제거 및 OrderStatusEnum으로 통일 — 주문옵션 상태를 주문 상태와 동일한 Enum으로 관리", - "주문 일괄변경(bulkUpdate) 시 주문상품옵션 상태도 동일하게 일괄 변경 — 기존에는 orders 테이블만 업데이트" - ] - }, - { - "name": "Removed", - "items": [ - "OrderOptionStatusEnum 삭제 — OrderStatusEnum으로 완전 대체 (클레임 상태는 별도 모듈에서 지원 예정)", - "다국어 order_option_status 섹션 제거 — order_status 키 공용 사용" - ] - }, - { - "name": "Added", - "items": [ - "OrderRepository::bulkUpdateOptionStatus() 메서드 추가 — 주문 ID 배열 기준 옵션 상태 일괄 변경", - "OrderStatusEnum::isShipped() / shippedStatuses() 메서드 추가 — 발송 이후 상태 판별", - "OrderOption::shipped_quantity 접근자 추가 — 자신 및 분할 자식 옵션의 발송 수량 합산", - "업그레이드 스크립트 Upgrade_0_8_0 — option_status DB 값 변환 (pending→pending_order, shipped→shipping, 클레임→cancelled)" - ] - } - ] - }, - { - "version": "0.7.6", - "date": "2026-03-20", - "categories": [ - { - "name": "Changed", - "items": [ - "주문상세 주문상품 레이아웃을 CSS Grid → DataGrid 컴포넌트로 전환 — Tailwind purge 미적용 문제 해결, selectable/footerCells/footerCardChildren 활용", - "주문상세 합계 행을 DataGrid footerCells로 통합 — 기존 수동 CSS Grid 합계 영역 제거", - "주문상세 일괄변경 영역 스타일을 주문관리 목록과 동일하게 통일 — composite Select → basic Select, 배경/테두리/버튼 스타일 일치", - "주문상세 일괄변경 버튼을 buildOrderDetailBulkConfirmData 핸들러로 변경 — 검증(상태 미선택/상품 미선택) 후 모달 오픈 패턴 적용", - "주문상세 DataGrid selectedCountText를 빈 문자열로 설정 — 기본 \"N개 선택됨\" 텍스트 비활성화" - ] - }, - { - "name": "Fixed", - "items": [ - "주문상세 0원 항목 표시 — 할인/과세/부가세/쿠폰/할인코드/마일리지/예치금 금액이 0인 항목에 `condition` 속성 추가하여 숨김 처리", - "주문상세 합계 computed 문자열 연결 버그 — API 금액이 문자열(`\"4000.00\"`)로 반환되어 `reduce` 합산 시 문자열 연결 발생 → `Number()` 래핑으로 수정", - "주문상세 0원 금액 마이너스 표기 방지 — 금액 0원 항목이 `-0원`으로 표시되던 문제, condition 기반 숨김으로 해결", - "주문상세 일괄변경 버튼 클릭 무응답 — openModal 직접 호출 → buildOrderDetailBulkConfirmData 검증 핸들러로 교체", - "주문상세 일괄변경 확인 모달 closeModal 패턴 수정 — `params.target` → `target` 직접 지정", - "주문상세 DataGrid 셀 줄바꿈 방지 — 상품정보/헤더 제외 모든 셀과 footer에 whitespace-nowrap 적용" - ] - }, - { - "name": "Added", - "items": [ - "다국어 키 추가 — `no_products` (주문상품 없음) ko/en", - "buildOrderDetailBulkConfirmDataHandler 핸들러 추가 — 상태/상품 미선택 시 경고 토스트, 검증 통과 시 확인 모달 오픈", - "운송사/송장번호 입력 필드 조건부 표시 — 배송 관련 상태(shipping_ready/shipping/delivered) 선택 시에만 표시" - ] - } - ] - }, - { - "version": "0.7.5", - "date": "2026-03-20", - "categories": [ - { - "name": "Fixed", - "items": [ - "주문상세 헤더 \"상태:\" 텍스트 누락 — Badge 앞에 상태 라벨 Span 추가", - "주문상세 결제정보 금액 경로 오류 — `payment.*` 주문레벨 금액을 `order.data.*` 경로로 수정", - "주문상세 처리로그 더미 데이터 표시 — `_local.dummyLogs` → `order_logs` API 데이터소스 연동", - "주문상세 일괄변경 핸들러 변수명 불일치 — `batchCarrier` → `batchCarrierId`로 통일", - "OrderPaymentResource 필드명 5종 DB 컬럼 불일치 수정 (pg_tid→transaction_id, paid_amount→paid_amount_local, vbank_num_masked→vbank_number, vbank_due_date→vbank_due_at, cash_receipt_number→cash_receipt_identifier)" - ] - }, - { - "name": "Added", - "items": [ - "주문 처리 로그 API 엔드포인트 추가 (`GET /admin/orders/{id}/logs`) — ActivityLogService 연동, 페이지네이션/정렬 지원", - "Order 모델 payments HasMany 관계 추가 (기존 payment HasOne 유지)", - "OrderResource 플래튼 필드 추가 — 주문자(orderer_name/phone/email), 수취인(recipient_name/phone/zipcode/address), user_login_id, payments(복수), 세금(total_vat_amount)", - "OrderPaymentResource 추가 필드 — payment_type_label, payment_number, account_info, requested_at_formatted, due_date_formatted, vat_amount_formatted, card_approval_number, is_interest_free", - "OrderOptionResource option_name alias 추가", - "OrderService 활동 로그 기록 — update/bulkUpdate 시 ActivityLogService 연동", - "total_vat_amount 컬럼 마이그레이션 추가", - "처리로그 정렬/페이지당 옵션 변경 시 서버사이드 refetch 지원", - "다국어 키 추가 — 처리로그 정렬(date_asc), 페이지당 옵션(per_page_option), 로그 메시지 5종 (ko/en)", - "백엔드 테스트 3종 추가 (logs 엔드포인트 조회/페이지네이션/인증)", - "프론트엔드 레이아웃 테스트 갱신 — order_logs/active_carriers 데이터소스 검증, 금액 경로 검증, batchCarrierId 반영" - ] - } - ] - }, - { - "version": "0.7.4", - "date": "2026-03-19", - "categories": [ - { - "name": "Fixed", - "items": [ - "주문 목록 필터 5종 미작동 수정 — Repository 필터 키를 FormRequest 키와 정렬 (shipping_type, min_amount, max_amount, country_codes, order_device)", - "주문 목록 페이지네이션/총건수 미작동 — `orders?.data?.current_page` → `orders?.data?.pagination?.current_page` 경로 수정", - "배송국가 국기 미표시 — `row.country_code` → `row.address?.recipient_country_code` 경로 수정", - "OrderRepositoryTest 가격 필터 테스트 OLD 키 수정 (min_price → min_amount, max_price → max_amount)" - ] - }, - { - "name": "Added", - "items": [ - "구매환경(디바이스) 컬럼 추가 — OrderListResource에 `order_device`, `order_device_label` 필드 추가 + DataGrid 컬럼", - "첫구매 여부 표시 — OrderListResource에 `is_first_order` 필드 추가 + orderer 컬럼에 첫구매 뱃지", - "상품카테고리 4단계 계층 필터 — categories API 데이터소스 + computed 옵션 바인딩", - "배송국가 필터 동적화 — 하드코딩 6개국 → ecommerce_settings API 기반 iteration 렌더링", - "결제수단 필터 동적화 — 하드코딩 6종 → ecommerce_settings API 기반 iteration 렌더링", - "다국어 키 추가 — `column.device` (구매환경), `column.first_order` (첫구매) ko/en", - "백엔드 테스트 7종 추가 — 금액/국가/디바이스/배송방법 필터 + 페이지네이션 구조 + 신규 필드 확인", - "프론트엔드 레이아웃 테스트 29종 — 페이지네이션 경로, 필터 키, 컬럼, ActionMenu, 날짜 포맷, DataGrid 설정, 동적 필터, 클레임 제거 검증" - ] - }, - { - "name": "Changed", - "items": [ - "페이지 헤더 아이콘 제거 (디자인 통일)", - "기간 버튼 CSS `btn btn-xs` → `btn-date` 패턴 통일 (상품관리와 동일)", - "NO 컬럼 기본 숨김 (visibleColumns에서 제거)", - "주문자명 클릭 시 직접 navigate 제거 — 셀 레벨 ActionMenu로 변경 (쿠폰관리와 동일 패턴)", - "주문일시 날짜 표기 ISO → formatted 형식으로 수정 (`ordered_at` → `ordered_at_formatted`)", - "DataGrid 컬럼선택기 활성화 (`showColumnSelector: true`) + `responsiveBreakpoint: 768` 모바일 대응 추가" - ] - }, - { - "name": "Removed", - "items": [ - "클레임상태 필터 전체 제거 (state, data_sources params, init_actions, filter UI)" - ] - } - ] - }, - { - "version": "0.7.3", - "date": "2026-03-18", - "categories": [ - { - "name": "Added", - "items": [ - "`WishlistResource` + `WishlistCollection` 신규 생성 — 위시리스트 API에 표준 ResourceCollection 패턴 적용", - "`EcommerceSettingsService::getPublicPaymentSettings()` 메서드 추가 — 은행명 매핑 로직을 Service로 이동" - ] - }, - { - "name": "Changed", - "items": [ - "Public 컨트롤러 4개 `ResponseHelper` 패턴 일관성 수정 — `moduleSuccess`/`moduleError` 통일", - "`WishlistController`: 이중 번역 버그(`__()` + `success()`) 7곳 수정", - "`CartController`: `success('sirsoft-ecommerce::...')` → `moduleSuccess()` 10곳 수정", - "`CheckoutController`: success/error 패턴 혼재 6곳 수정", - "`WishlistController::index()` 인라인 `through()` 변환 → `WishlistCollection` 적용", - "`EcommerceSettingsController::payment()` 은행명 매핑 로직 → `EcommerceSettingsService`로 이동", - "`CartController`: `issueCartKey()`, `count()`에 `logApiUsage` 추가", - "`EcommerceSettingsController`: 3개 메서드에 `logApiUsage` 추가", - "`CheckoutController`: PHPDoc `@param` 타입 3곳 실제 FormRequest 타입으로 수정", - "`CategoryImageController::download()` 리턴 타입 선언 추가" - ] - } - ] - }, - { - "version": "0.7.2", - "date": "2026-03-18", - "categories": [ - { - "name": "Fixed", - "items": [ - "Public 상품 목록 API에 `ProductCollection` 패턴 적용 — 페이지네이션/abilities 포함 응답 구조 정상화", - "`ProductCollection` 콜백에서 `toArray()` → `resolve()` 변경 — 중첩 Resource(MissingValue) 안전 해석" - ] - }, - { - "name": "Changed", - "items": [ - "`TestingSeeder`에 `guest` 역할 및 사용자 타입 권한 추가 — Public API 테스트에서 PermissionMiddleware 통과 보장" - ] - } - ] - }, - { - "version": "0.7.1", - "date": "2026-03-18", - "categories": [ - { - "name": "Added", - "items": [ - "SEO 설정 변경 리스너 (`SeoSettingsCacheListener`) — 이커머스 모듈 SEO 설정 변경 시 영향받는 레이아웃 캐시를 선별적으로 무효화" - ] - }, - { - "name": "Changed", - "items": [ - "`SeoProductCacheListener` 보강 — 상품 수정 시 `home`/`search/index` 캐시 무효화 추가, 생성/수정 시 상세 페이지 캐시 즉시 재생성", - "`SeoCategoryCacheListener` 보강 — `search/index` 캐시 무효화 추가", - "배송지 `name` 필드를 다국어 JSON에서 단순 문자열로 변환 — 마이그레이션, 모델, 팩토리, 리포지토리, 서비스, FormRequest, 테스트 일괄 수정" - ] - } - ] - }, - { - "version": "0.7.0", - "date": "2026-03-17", - "categories": [ - { - "name": "Changed", - "items": [ - "SEO 설정 중 코어 범위 항목을 코어 환경설정으로 이관 (`seo_user_agents` → 코어 `seo.bot_user_agents`)", - "이커머스 SEO 탭에서 메인화면 관련 설정 제거 (`meta_main_title`, `meta_main_description`, `seo_site_main`)", - "이커머스 SEO 탭에서 User-Agent 관리 섹션 제거 (코어로 이관)" - ] - }, - { - "name": "Added", - "items": [ - "업그레이드 스텝 (`Upgrade_0_7_0`) — 기존 사용자 정의 User-Agent를 코어로 자동 이관" - ] - }, - { - "name": "Removed", - "items": [ - "`defaults.json`에서 `seo_user_agents`, `seo_site_main`, `meta_main_title`, `meta_main_description` 필드 제거", - "`StoreEcommerceSettingsRequest`에서 제거된 필드의 검증 규칙 삭제", - "SEO 탭 레이아웃에서 메인화면 메타 아코디언, 사이트 메인 체크박스, User-Agent 관리 섹션 제거" - ] - } - ] - }, - { - "version": "0.5.5", - "date": "2026-03-18", - "categories": [ - { - "name": "Added", - "items": [ - "상품 리뷰 기능 구현", - "DB: 리뷰 테이블, 리뷰 이미지 테이블 생성. 답변 수정일 컬럼 추가", - "모델/서비스: 리뷰 작성 가능 여부 확인, 리뷰 생성·삭제, 상태 변경, 판매자 답변 등록·수정·삭제, 이미��� 업로드·삭제", - "관리자 API: 리뷰 목록 조회(기간·별점·상태·포토리뷰·답변 여부 필터), 상태 변경, 삭제, 일괄 처리, 판매자 답변 CRUD", - "유저 API: 리뷰 작성 가능 여부 확인, 리뷰 작성·삭제, 이미지 업로드·삭제 (최대 5장)", - "공개 API: 상품별 리뷰 목록 (별점 통계, 옵션 필터 포함)", - "관리자 레이아웃: 리뷰 목록 화면 (검색 필터, 답변 모달, 이미지 미리보기 모달, 일괄 처리)", - "관리자 설정: 리뷰 작성 기한, 포토리뷰 최대 장수 설정 탭 추가", - "권한 및 메뉴: 관리자 사이드바 \"상품 리뷰 관리\" 메뉴 등록", - "다국어: 리뷰 관련 다국어 파일 추가 (한국어/영어)", - "샘플 시더: 별점 분포·답변·포토리뷰·답변 수정일 랜덤 생성", - "테스트: 관리자·유저·이미지 Feature 테스트 57개, 레이아웃 렌더링 테스트 94개", - "상품 목록 API에 평균 별점, 리뷰수 필드 추가" - ] - }, - { - "name": "Fixed", - "items": [ - "리뷰 작성 가능 여부 확인 시 구매확정 상태 비교가 항상 실패하던 버그 수정", - "리뷰 저장 시 옵션 스냅샷 배열 직렬화 오류 수정", - "리뷰 이미지 업로드 검증 실패 시 응답 코드가 400으로 반환되던 버그 수정 (422으로 수정)", - "리뷰 이미지 API 응답에 리뷰 ID 누락 수정" - ] - } - ] - }, - { - "version": "0.5.4", - "date": "2026-03-17", - "categories": [ - { - "name": "Fixed", - "items": [ - "주문 상세 SMS/이메일 발송 버튼 openModal 핸들러 포맷 수정 (`params.target` → `target`)" - ] - } - ] - }, - { - "version": "0.5.3", - "date": "2026-03-16", - "categories": [ - { - "name": "Fixed", - "items": [ - "쿠폰 리스트 DataGrid `cellChildren` 액션에서 등록자 필터 검색 시 SearchableDropdown에 선택값이 표시되지 않는 버그 수정 — `setState(target: \"local\")`가 globalStateUpdater 경로로 실행되어 Form stale 값에 덮어쓰이는 문제 (템플릿 DataGrid componentContext 수정으로 해결)", - "CouponResource에 카테고리 로케일 브레드크럼 경로 표시 추가", - "쿠폰 1인당 발급 제한 `per_user_limit` null 오류 수정 — nullable 처리 및 검증 로직 보완", - "쿠폰 할인율 검증 수정 — 정률 할인 시 100% 초과 방지, 정액 할인 시 할인율 검증 제거", - "쿠폰 유효기간/발급기간 타임존 변환 구현 — `TimezoneHelper`를 통한 사용자 타임존 ↔ UTC 변환", - "쿠폰 발급기간 `datetime-local` 입력 변환 및 `CouponResource` 타임존 출력 수정", - "쿠폰 폼 레이아웃에서 `apiCall` 호출 시 `auth_mode` 누락 수정" - ] - }, - { - "name": "Changed", - "items": [ - "쿠폰 폼 상품 검색을 무한스크롤 방식으로 개선", - "쿠폰 폼 레이아웃에서 사용 조건 Partial 분리", - "마이그레이션 통합 — 증분 마이그레이션을 테이블당 1개 create 마이그레이션으로 정리", - "시더 디렉토리 분리 — 샘플 시더를 `Sample/` 하위로 이동", - "라이선스 프로그램 명칭 정비", - "쿠폰 폼 하단 버튼 영역 제거 — 상단 PageHeader 액션으로 통일" - ] - } - ] - }, - { - "version": "0.5.1", - "date": "2026-03-11", - "categories": [ - { - "name": "Added", - "items": [ - "주문 취소 API 엔드포인트 추가 (`POST /user/orders/{id}/cancel`) + `user-orders.cancel` 권한 미들웨어 적용", - "`CancelOrderRequest` — 주문 취소 요청 검증 (소유자 확인, 취소 가능 상태 검증)", - "`UserOrderListResource` — `abilities` 키 추가 (`can_view`, `can_cancel` — 상태+권한 기반)", - "`UserOrderCollection` — `abilities` 키 추가 (`can_create` — 권한 기반)", - "`UserAddressResource` — `abilities` 키 추가 (`can_update`, `can_delete`, `can_set_default`)", - "`UserAddressCollection` — `abilities` 키 추가 (`can_create`)", - "`EcommerceUserAbilitiesTest` — 주문/배송지 abilities 및 주문 취소 API 테스트 13개", - "manifest에 license 필드 및 LICENSE 파일 추가" - ] - } - ] - }, - { - "version": "0.5.0", - "date": "2026-03-11", - "categories": [ - { - "name": "Added", - "items": [ - "사용자 권한 3개 추가 (블랙컨슈머 차단용)", - "`sirsoft-ecommerce.user-products.read`: 상품 조회 권한", - "`sirsoft-ecommerce.user-orders.create`: 주문하기 권한", - "`sirsoft-ecommerce.user-orders.cancel`: 주문 취소 권한", - "상품 조회 라우트에 `optional.sanctum` + `permission:user` 미들웨어 적용", - "주문 생성 라우트에 `permission:user` 미들웨어 적용", - "주문 배송지 변경 API (`PUT /user/orders/{id}/shipping-address`)", - "주문 상세 배송지 변경 모달 (주문상세 화면)", - "배송지명 중복 덮어쓰기 확인 모달", - "업그레이드 스크립트 `Upgrade_0_5_0.php` (권한 생성 + 기존 역할 할당)", - "업그레이드 스크립트 `Upgrade_0_6_0.php` (배송지명 컬럼 검증 + 캐시 클리어)", - "사용자 권한 미들웨어 테스트 `EcommerceUserPermissionTest` (7개)" - ] - }, - { - "name": "Changed", - "items": [ - "배송지명(`name`) 필드 다국어 JSON → 단순 string 변환 (마이그레이션 포함)" - ] - }, - { - "name": "Fixed", - "items": [ - "마이페이지 배송지 관리 API 엔드포인트 및 필드명 불일치 수정", - "체크아웃 배송지 모달 필드명 불일치 수정", - "무통장입금(dbank) 주문 시 결제수단이 'card'로 잘못 전송되던 버그 수정", - "도서산간 추가배송비 제주도 누락 수정", - "배송지 변경 시 배송비 미재계산 수정", - "배송지 모달 폼 전송 실패 수정", - "주문 완료 후 장바구니 아이템이 삭제되지 않던 버그 수정 (재고 차감 타이밍과 동기화)" - ] - } - ] - }, - { - "version": "0.4.2", - "date": "2026-03-10", - "categories": [ - { - "name": "Changed", - "items": [ - "권한 카테고리에 `resource_route_key`, `owner_key` 스코프 메타데이터 추가 (products, orders, brands, promotion-coupon, shipping-policies)", - "라우트 미들웨어 `except:owner:*` 옵션 제거 (scope_type 데이터 기반 시스템으로 전환)", - "Repository 목록 조회에 `PermissionHelper::applyPermissionScope()` 적용 (Product, Order, Brand, ShippingPolicy, Coupon)", - "`PermissionBypassable` 인터페이스 및 `getBypassUserId()` 제거 (Product, Order, Brand, ShippingPolicy, Coupon 등 모델)" - ] - } - ] - }, - { - "version": "0.4.1", - "date": "2026-03-08", - "categories": [ - { - "name": "Changed", - "items": [ - "API 리소스 권한 플래그 키 `permissions` → `abilities`로 변경 (코어 표준화)", - "`permissionMap()` → `abilityMap()` 메서드명 변경 (11개 리소스)", - "`BrandCollection`, `CategoryCollection`, `ShippingCarrierCollection`에 `HasAbilityCheck` 트레이트 적용", - "컬렉션 레벨 권한 응답 키를 `abilities`로 통일", - "관리자 레이아웃 JSON의 `permissions.can_*` 바인딩을 `abilities.can_*`로 변경" - ] - } - ] - }, - { - "version": "0.4.0", - "date": "2026-03-06", - "categories": [ - { - "name": "Changed", - "items": [ - "메일 템플릿 목록 API에 검색/필터/페이지네이션 지원 추가", - "메일 템플릿 편집 모달 UX 개선 (blur_until_loaded, sticky footer, 함수형 body)", - "`getDefaultTemplateData()`를 Controller에서 Service로 이동 (Service-Repository 패턴 준수)", - "메일 템플릿 탭 UI를 코어 환경설정과 동일한 구조로 변경" - ] - }, - { - "name": "Added", - "items": [ - "메일 템플릿 미리보기(preview) API 엔드포인트 추가", - "메일 템플릿 검색 기능 (제목/본문/전체)", - "페이지당 항목 수 선택 및 페이지네이션", - "메일 템플릿 관련 다국어 키 추가 (검색/필터/빈 상태/편집 모달)" - ] - } - ] - }, - { - "version": "0.3.1", - "date": "2026-03-06", - "categories": [ - { - "name": "Fixed", - "items": [ - "`CategoryService::deleteCategory()` — 카테고리 삭제 시 이미지(`images()`) 명시적 삭제 추가 (DB CASCADE 의존 제거)", - "`OrderService::delete()` — 주문 삭제 시 관계 레코드 5건 명시적 삭제 추가 (`taxInvoices`, `shippings`, `addresses`, `payment`, `options`)" - ] - } - ] - }, - { - "version": "0.3.0", - "date": "2026-03-06", - "categories": [ - { - "name": "Added", - "items": [ - "사용자 쿠폰 다운로드 API (`UserCouponController`) — 다운로드 가능 쿠폰 목록 조회/다운로드", - "상품별 다운로드 가능 쿠폰 공개 API (`PublicCouponController`) — 비로그인 사용자도 조회 가능", - "`UserCouponService` — 쿠폰 다운로드 비즈니스 로직 (중복 다운로드 방지, 수량 검증)", - "`CouponRepository`, `CouponIssueRepository` — 쿠폰 데이터 접근 계층", - "`DownloadCouponRequest` — 쿠폰 다운로드 요청 검증", - "쿠폰 다운로드 관련 다국어 메시지 (ko/en `messages.php`, `validation.php`)", - "사용자 쿠폰 다운로드 Feature 테스트 14건 (UserCouponControllerTest + PublicCouponControllerTest)", - "DB 기반 메일 템플릿 시스템 (5종: 주문 확인, 배송 시작, 구매 확정, 주문 취소, 관리자 신규 주문)", - "`ecommerce_mail_templates` 테이블, EcommerceMailTemplate 모델/서비스/리포지토리/컨트롤러", - "메일 템플릿 관리 API 및 환경설정 UI", - "업그레이드 스텝 (`Upgrade_0_3_0`) — 기존 설치에 메일 템플릿 초기 시딩", - "`getSeeders()`에 `EcommerceMailTemplateSeeder` 추가" - ] - } - ] - }, - { - "version": "0.2.1", - "date": "2026-03-03", - "categories": [ - { - "name": "Added", - "items": [ - "도서산간 추가배송비 템플릿 시더 (`ExtraFeeTemplateSeeder`) — 34건 도서산간 우편번호 데이터", - "도서산간 템플릿 모달 CRUD 기능 (생성/수정/삭제/일괄삭제/검색)", - "템플릿 모달에서 전체 적용/선택 적용 기능", - "백엔드 다국어 메시지 (`extra_fee_template` 섹션) 추가 (ko/en)" - ] - }, - { - "name": "Changed", - "items": [ - "도서산간 템플릿 모달 크기 `md` → `xl` 확대", - "도서산간 템플릿 모달을 API 응답 구조에 맞게 전면 재설계", - "`zipcode` 컬럼 `string(10)` → `string(20)` 확장 (범위 형식 지원)", - "FormRequest zipcode 검증 `max:10` → `max:20` 변경", - "부모 레이아웃에 템플릿 CRUD 상태 및 데이터소스 검색 params 추가" - ] - } - ] - }, - { - "version": "0.2.0", - "date": "2026-03-03", - "categories": [ - { - "name": "Added", - "items": [ - "배송사 관리 기능 신설 (CRUD API + 관리 UI)", - "배송사 마스터 테이블 (`ecommerce_shipping_carriers`) 생성", - "배송 설정 탭에 배송사 관리 인라인 섹션 추가", - "주문 배송 추적 URL 생성 (`OrderShipping.getTrackingUrl()`)", - "해외배송 비활성화 확인 모달 추가", - "배송가능국가 테이블에 국기 아이콘 표시", - "업그레이드 스텝 (`Upgrade_0_2_0`) — 배송사 시딩 + 기존 주문 carrier_id 역매핑" - ] - }, - { - "name": "Changed", - "items": [ - "해외배송 토글 수동 바인딩 전환 (해외배송 OFF 시 국가 추가/토글/삭제 비활성화)", - "주문 레이아웃 배송사 옵션을 하드코딩에서 API 기반 동적 로딩으로 전환", - "모듈 설치 시더에 `ShippingCarrierSeeder` 추가" - ] - }, - { - "name": "Removed", - "items": [ - "`CarrierEnum` 삭제 — 배송사를 하드코딩 Enum에서 DB 기반 동적 관리로 전환", - "배송정책에서 carrier 필드 제거 (`shipping_policy_country_settings.carrier` 컬럼 삭제)", - "배송정책 폼/목록에서 carrier Select 및 필터 UI 제거" - ] - } - ] - }, - { - "version": "0.1.7", - "date": "2026-02-27", - "categories": [ - { - "name": "Fixed", - "items": [ - "배송정책 수정 폼에서 가시성 플래그가 잘못 설정되는 버그 수정 (init_actions if 미지원 대응)", - "구간별 배송비 정책 수정 시 구간 테이블 미표시 및 고정배송비 필드 오표시 문제 해결", - "배송정책명(MultilingualInput) 수정 모드 미표시 문제 해결 (dataKey 패턴 적용)" - ] - }, - { - "name": "Changed", - "items": [ - "배송정책 폼에 dataKey=\"form\" + debounce 패턴 적용 (상품 폼과 동일 구조)", - "단순 setState 핸들러 제거 (shipping_method, currency_code → 자동 바인딩)" - ] - } - ] - }, - { - "version": "0.1.2", - "date": "2026-02-25", - "categories": [ - { - "name": "Changed", - "items": [ - "모듈 라우트 admin/user 분기 서빙 적용 (routes.json → routes/admin.json 이동)" - ] - } - ] - }, - { - "version": "0.1.1", - "date": "2026-02-24", - "categories": [ - { - "name": "Changed", - "items": [ - "버전 체계 조정 (정식 출시 전 0.x 체계로 변경)" - ] - } - ] - }, - { - "version": "0.1.0", - "date": "2026-02-23", - "categories": [ - { - "name": "Added", - "items": [ - "이커머스 모듈 초기 구현", - "상품 관리: CRUD, 옵션(사이즈/색상), 라벨, SEO 메타, 이미지 갤러리", - "상품 카테고리: 계층형 카테고리, 순서 관리, TreeView 편집", - "브랜드 관리: CRUD, 로고, 설명", - "쿠폰 시스템: 정액/정률 할인, 사용 조건 (최소 금액, 특정 상품), 유효기간, 사용 횟수 제한", - "배송 정책: 무료/유료/조건부 배송", - "공통 정보 관리: 배송/교환/환불 안내, 판매자 정보", - "장바구니: 추가/수량 변경/삭제, 옵션별 관리", - "체크아웃: 주소 입력, 결제수단 선택, 쿠폰 적용", - "주문 관리: 주문 목록, 상태 변경, 상세 정보, 주문 타임라인", - "주문 완료: 주문 번호, 결제 정보 요약", - "결제 연동 인터페이스 (PaymentInterface)", - "상품 상세 페이지: 이미지 갤러리, 옵션 선택, 수량 입력, 장바구니 담기", - "상품 목록: 필터링 (카테고리/브랜드/가격), 정렬, 페이지네이션, 카드 그리드", - "위시리스트: 찜하기/해제 기능", - "상품 검색: 키워드 검색, 카테고리 필터 연동", - "관리자 레이아웃 (상품, 주문, 카테고리, 브랜드, 쿠폰, 배송정책 관리)", - "사용자 레이아웃 (상품 목록/상세, 장바구니, 체크아웃, 주문완료)", - "권한 시스템 연동", - "다국어 지원 (ko, en)" - ] - } - ] - } - ] - } + "...": "(2개 키 생략, 총 3개)" } ``` @@ -3157,7 +938,14 @@ Authorization: Bearer {YOUR_TOKEN} _목록 응답: `data.data[]` 배열 항목의 필드._ - +| 필드 | 타입 | 실측 예시값 | 용도/설명 | +| --- | --- | --- | --- | +| identifier | string | `sirsoft-basic` | 모듈 고유 식별자 (vendor-module 형식) | +| name | string | `Basic` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | +| version | string | `1.0.4` | 모듈 버전 | +| type | string | `user` | | +| status | string | `active` | 상태 값 (도메인별 상태 집합 — 사람이 읽는 라벨은 status_label, UI 변형은 status_variant 참조) | +| required_version | string | `>=1.0.5` | | **응답 예시** @@ -3170,8 +958,17 @@ HTTP/1.1 200 "success": true, "message": "의존 템플릿 정보를 성공적으로 조회했습니다.", "data": { - "data": [], - "total": 0 + "data": [ + { + "identifier": "sirsoft-basic", + "name": "Basic", + "version": "1.0.4", + "type": "user", + "status": "active", + "required_version": ">=1.0.5" + } + ], + "total": 1 } } ``` @@ -3312,38 +1109,11 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) -| 필드 | 타입 | 설명 | -| --- | --- | --- | -| has_modified_layouts | boolean | 수정된 레이아웃 존재 여부 | -| modified_count | integer | 수정된 레이아웃 수 | -| modified_layouts | array | 수정된 레이아웃 목록 | -| modified_layouts[].id | integer | 레이아웃 ID | -| modified_layouts[].name | string | 레이아웃 이름 | -| modified_layouts[].updated_at | string\|null | 최종 수정 일시 (`Y-m-d H:i:s`) | -| modified_layouts[].size_diff | integer | 원본 대비 바이트 증감 (음수는 축소) | + **응답 예시** -```json -{ - "success": true, - "message": "수정된 레이아웃 확인이 완료되었습니다.", - "data": { - "has_modified_layouts": true, - "modified_count": 1, - "modified_layouts": [ - { - "id": 47, - "name": "sirsoft-board.admin_board_index", - "updated_at": "2026-07-31 09:20:05", - "size_diff": 28 - } - ] - } -} -``` - -집계 범위는 해당 모듈이 **소유한** 레이아웃으로 한정됩니다. 같은 템플릿에 등록된 다른 확장의 레이아웃은 포함되지 않습니다. + **에러 응답** @@ -3473,13 +1243,13 @@ Content-Type: application/json "layout_strategy": "overwrite", "vendor_mode": "auto", "force": true, - "rebuild_search_index": false + "rebuild_search_index": true } ``` **응답 필드** (`data` 내부) - + **응답 예시** @@ -3499,6 +1269,47 @@ Content-Type: application/json **설명** 특정 모듈을 최신 버전으로 업데이트합니다. `core.modules.install` 권한이 필요합니다. `layout_strategy` 로 레이아웃 처리 방식을(overwrite: 새 버전으로 교체, keep: 사용자 수정본 유지) 지정하며, `vendor_mode` 로 Composer 의존성 처리 방식을 선택합니다. 버전 제약·호환성 문제로 막힐 경우 `force: true` 로 강제 진행할 수 있습니다. `keep` 을 지정하면 사용자가 수정한 레이아웃(원본 해시와 현재 내용이 다른 레이아웃)은 갱신 대상에서 제외되어 현재 내용이 그대로 유지되고, 나머지 레이아웃만 파일 기준으로 갱신됩니다. 성공 응답 메시지에는 대상 식별자와 적용된 버전이 채워집니다. `rebuild_search_index: true` 를 함께 보내면 업데이트 후 색인이 누락된 검색 인덱스를 재생성합니다 — 인덱스 잠금·재색인 비용이 있어 기본은 수행하지 않으며, 보내지 않아도 응답의 `search_index` 에 현재 누락 여부가 담깁니다. +### GET /api/modules/assets/{identifier} + +- **라우트명**: `api.public.modules.assets.extensionless` +- **컨트롤러**: `App\Http\Controllers\Api\Public\PublicModuleController@serveAsset` +- **인증/권한**: 공개 (인증 불필요) + +**요청 파라미터** + +| 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | +| --- | --- | --- | --- | --- | --- | +| identifier | path | string | 예 | — | 대상 리소스의 식별자 | +| path | query | string | 예 | — | 경로 | + +**요청 예시** + +```http +GET /api/modules/assets/sirsoft-ecommerce?identifier=example-key&path=%EC%98%88%EC%8B%9C%EA%B0%92 HTTP/1.1 +Host: api.example.com +Accept: application/json +``` + +**응답 필드** (`data` 내부) + + + +**응답 예시** + + + +**에러 응답** + +| 상태코드 | 의미 | 발생 조건 | +| --- | --- | --- | +| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) | +| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 | + + + +**설명** + + ### GET /api/modules/assets/{identifier}/{path} - **라우트명**: `api.public.modules.assets` @@ -3560,11 +1371,11 @@ Accept: application/json **응답 필드** (`data` 내부) - + **응답 예시** - + **에러 응답** @@ -3595,11 +1406,11 @@ Accept: application/json **응답 필드** (`data` 내부) - + **응답 예시** - + **에러 응답** @@ -3610,6 +1421,132 @@ _대표 에러 없음 (공개 조회). 활성 모듈 에셋이 없어도 빈 200 **설명** 활성 모듈들의 프론트엔드 IIFE JS 를 서버에서 하나로 병합한 번들을 서빙하는 공개 엔드포인트입니다. 인증이 필요하지 않습니다. 활성 global 모듈 에셋이 없으면 빈 200(text/javascript) 응답을 반환하고(프론트는 빈 스크립트 로드로 무해), 있으면 병합 파일을 ETag·환경별 Cache-Control 과 함께 서빙합니다. 프론트는 `G7Config.bundleUrls` 를 읽어 이 번들을 로드합니다. +### GET /api/modules/bundle/css + +- **라우트명**: `api.public.modules.bundle.css.extensionless` +- **컨트롤러**: `App\Http\Controllers\Api\Public\PublicModuleController@serveBundleCss` +- **인증/권한**: 공개 (인증 불필요) + +**요청 파라미터** + +_요청 파라미터 없음._ + +**요청 예시** + +```http +GET /api/modules/bundle/css HTTP/1.1 +Host: api.example.com +Accept: application/json +``` + +**응답 필드** (`data` 내부) + + + +**응답 예시** + + + +**에러 응답** + +_대표 에러 없음 (공개 조회). _ + + + +**설명** + + +### GET /api/modules/bundle/js + +- **라우트명**: `api.public.modules.bundle.js.extensionless` +- **컨트롤러**: `App\Http\Controllers\Api\Public\PublicModuleController@serveBundleJs` +- **인증/권한**: 공개 (인증 불필요) + +**요청 파라미터** + +_요청 파라미터 없음._ + +**요청 예시** + +```http +GET /api/modules/bundle/js HTTP/1.1 +Host: api.example.com +Accept: application/json +``` + +**응답 필드** (`data` 내부) + + + +**응답 예시** + + + +**에러 응답** + +_대표 에러 없음 (공개 조회). _ + + + +**설명** + + +### GET /api/modules/{identifier}/components + +- **라우트명**: `api.public.modules.components.extensionless` +- **컨트롤러**: `App\Http\Controllers\Api\Public\PublicModuleController@serveComponents` +- **인증/권한**: 공개 (인증 불필요) + +**요청 파라미터** + +| 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | +| --- | --- | --- | --- | --- | --- | +| identifier | path | string | 예 | — | 대상 리소스의 식별자 | + +**요청 예시** + +```http +GET /api/modules/sirsoft-ecommerce/components HTTP/1.1 +Host: api.example.com +Accept: application/json +``` + +**응답 필드** (`data` 내부) + + + + + +**응답 예시** + +```http +HTTP/1.1 200 +``` + +```json +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "identifier": "sirsoft-ecommerce", + "version": "1.0.3", + "components": { + "basic": [], + "composite": [], + "layout": [] + } +} +``` + +**에러 응답** + +| 상태코드 | 의미 | 발생 조건 | +| --- | --- | --- | +| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 | + + + +**설명** + + ### GET /api/modules/{identifier}/components.json - **라우트명**: `api.public.modules.components` @@ -3632,28 +1569,11 @@ Accept: application/json **응답 필드** (`data` 내부) - - - + **응답 예시** -```http -HTTP/1.1 200 -``` - -```json -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "identifier": "sirsoft-ecommerce", - "version": "1.0.0-beta.5", - "components": { - "basic": [], - "composite": [], - "layout": [] - } -} -``` + **에러 응답** @@ -3739,8524 +1659,7 @@ HTTP/1.1 200 } } }, - "actionChipCandidates": { - "comment": "이커머스 도메인 응답 칩 후보 — 코어 기본 응답 칩(response.data/message) 뒤에 병합. 주문 생성 응답이 내려주는 PG 결제 진입 필드(pg_payment_handler/pg_payment_data)를 결제 진입 레시피의 데이터 칩으로 연결할 수 있게 노출한다. 코어는 PG 도메인을 모르고, 본 모듈이 자기 응답 필드를 선언한다(라벨은 모듈 격리 네임스페이스).", - "response": [ - { - "path": "data.pg_payment_handler", - "labelKey": "sirsoft-ecommerce.editor.action_chip.response_pg_handler", - "shape": "scalar" - }, - { - "path": "data.pg_payment_data", - "labelKey": "sirsoft-ecommerce.editor.action_chip.response_pg_data", - "shape": "object" - } - ] - }, - "sampleGlobal": { - "comment": "이커머스 도메인 _global keyspace. 코어 keyspace(currentUser/settings)는 시드하지 않음(충돌 시 코어 우선). 헤더 통화/배송국가 셀렉터(header_currency 슬롯 주입)는 실사이트에서 init_actions 가 채우는 파생 상태(availableCurrencies/availableShippingCountries)와 modules['sirsoft-ecommerce'] 설정을 표시 조건으로 읽는다. 편집기 캔버스는 init_actions 를 실행하지 않으므로 이 키들을 직접 시드해 편집기에서도 셀렉터가 보이게 한다(양 템플릿 공용 — 헤더는 모듈 무지 유지).", - "cart": { - "items": [], - "count": 0, - "total": 0 - }, - "wishlist": { - "items": [], - "count": 0 - }, - "recent_products": [], - "modules": { - "sirsoft-ecommerce": { - "comment": "헤더 통화/배송국가 셀렉터 표시 조건 소스. 실사이트 G7Config(window.__G7_MODULE_CONFIG) 미러 — 편집기 캔버스 전용 시드.", - "language_currency": { - "default_currency": "KRW", - "currencies": [ - { - "code": "KRW", - "symbol": "₩", - "flag": "🇰🇷", - "exchange_rate": 1, - "is_default": true - }, - { - "code": "USD", - "symbol": "$", - "flag": "🇺🇸", - "exchange_rate": 0.00073, - "is_default": false - }, - { - "code": "JPY", - "symbol": "¥", - "flag": "🇯🇵", - "exchange_rate": 0.11, - "is_default": false - } - ] - }, - "shipping": { - "international_shipping_enabled": true, - "default_country": "KR", - "available_countries": [ - { - "code": "KR", - "name": { - "ko": "대한민국", - "en": "South Korea", - "ja": "韓国" - }, - "is_active": true - }, - { - "code": "US", - "name": { - "ko": "미국", - "en": "United States", - "ja": "アメリカ" - }, - "is_active": true - }, - { - "code": "JP", - "name": { - "ko": "일본", - "en": "Japan", - "ja": "日本" - }, - "is_active": true - } - ] - } - } - }, - "defaultCurrency": "KRW", - "preferredCurrency": "KRW", - "preferredShippingCountry": "KR", - "availableCurrencies": [ - { - "code": "KRW", - "symbol": "₩", - "flag": "🇰🇷", - "exchange_rate": 1, - "is_default": true - }, - { - "code": "USD", - "symbol": "$", - "flag": "🇺🇸", - "exchange_rate": 0.00073, - "is_default": false - }, - { - "code": "JPY", - "symbol": "¥", - "flag": "🇯🇵", - "exchange_rate": 0.11, - "is_default": false - } - ], - "availableShippingCountries": [ - { - "code": "KR", - "name": "대한민국" - }, - { - "code": "US", - "name": "미국" - }, - { - "code": "JP", - "name": "일본" - } - ], - "mileageManual": [], - "mileageEdit": [], - "mileageExtend": [] - }, - "sampleData": { - "comment": "실제 모듈 레이아웃 data_source ID(admin: products/categories/orders/order/reviews 등) + 사용자 페이지(템플릿이 렌더, /api/modules/sirsoft-ecommerce/* 호출) byEndpointPattern. 계획서 추정과 실제 ID 차이 반영(데이터소스가 SSoT).", - "byDataSourceId": { - "transactions": { - "data": { - "data": [ - { - "id": 2001, - "user_id": 100, - "currency": "KRW", - "type": "earn", - "type_label": "적립", - "admin_badge_group": null, - "user_display_category": "수동 지급", - "amount": 10000, - "amount_formatted": "10,000", - "remaining_amount": 10000, - "remaining_amount_formatted": "10,000", - "balance_after": 65000, - "order_id": null, - "order_option_id": null, - "order_cancel_id": null, - "source_transaction_id": null, - "granted_by": 1, - "granted_by_name": "관리자", - "granted_by_uuid": "admin-uuid-1", - "user_name": "홍길동", - "user_uuid": "u-1001", - "order_number": null, - "description": "Admin earn", - "memo": "이벤트 보상", - "expires_at": "2027-06-21T00:00:00+09:00", - "expires_at_formatted": "Jun 21, 2027 12:00 AM", - "expires_at_date": "2027-06-21", - "expired_at": null, - "expired_at_formatted": null, - "created_at": "2026-06-20T10:00:00+09:00", - "created_at_formatted": "Jun 20, 2026 10:00 AM", - "created_at_date": "2026-06-20", - "is_earning": true, - "can_edit_expiry": true, - "expired_amount": 0, - "expired_amount_formatted": "0", - "expiry_state": "active", - "abilities": { - "can_manage": true - } - }, - { - "id": 2002, - "user_id": 101, - "currency": "KRW", - "type": "deduct", - "type_label": "차감", - "admin_badge_group": null, - "user_display_category": "수동 차감", - "amount": -5000, - "amount_formatted": "-5,000", - "remaining_amount": 0, - "remaining_amount_formatted": "0", - "balance_after": 45000, - "order_id": null, - "order_option_id": null, - "order_cancel_id": null, - "source_transaction_id": null, - "granted_by": 1, - "granted_by_name": "관리자", - "granted_by_uuid": "admin-uuid-1", - "user_name": "김영희", - "user_uuid": "u-1002", - "order_number": null, - "description": null, - "memo": "잘못된 적립 차감", - "expires_at": null, - "expires_at_formatted": null, - "expires_at_date": null, - "expired_at": null, - "expired_at_formatted": null, - "created_at": "2026-06-19T15:30:00+09:00", - "created_at_formatted": "Jun 19, 2026 3:30 PM", - "created_at_date": "2026-06-19", - "is_earning": false, - "can_edit_expiry": false, - "expired_amount": 0, - "expired_amount_formatted": "0", - "expiry_state": "active", - "abilities": { - "can_manage": true - } - } - ], - "abilities": { - "can_manage": true - }, - "currencies": [ - "KRW" - ], - "pagination": { - "current_page": 1, - "last_page": 1, - "per_page": 20, - "total": 2, - "from": 1, - "to": 2, - "has_more_pages": false - } - } - }, - "products": { - "data": { - "abilities": { - "can_create": true, - "can_update": true - }, - "data": [ - { - "id": 101, - "name": { - "ko": "베이직 오버핏 코튼 티셔츠" - }, - "name_localized": "베이직 오버핏 코튼 티셔츠", - "product_code": "TS-1001", - "sku": "TS-1001", - "thumbnail_url": "/images/placeholder.png", - "list_price": 29000, - "list_price_formatted": "29,000원", - "selling_price": 23200, - "selling_price_formatted": "23,200원", - "discount_rate": 20, - "multi_currency_list_price": { - "KRW": { - "price": 29000, - "formatted": "29,000원", - "is_default": true, - "editable": true - }, - "USD": { - "price": 22.31, - "formatted": "$22.31", - "is_default": false, - "editable": false, - "exchange_rate": 1300 - } - }, - "multi_currency_selling_price": { - "KRW": { - "price": 23200, - "formatted": "23,200원", - "is_default": true, - "editable": true - }, - "USD": { - "price": 17.85, - "formatted": "$17.85", - "is_default": false, - "editable": false, - "exchange_rate": 1300 - } - }, - "stock_quantity": 320, - "safe_stock_quantity": 10, - "is_below_safe_stock": false, - "option_stock_sum": 320, - "sales_status": "on_sale", - "sales_status_label": "판매중", - "sales_status_variant": "success", - "display_status": "visible", - "display_status_label": "노출", - "display_status_variant": "success", - "categories": [ - { - "id": 11, - "name": "상의", - "is_primary": true - } - ], - "primary_category": "상의", - "categories_with_path": [ - { - "id": 11, - "path": [ - { - "id": 1, - "name": "패션의류" - }, - { - "id": 11, - "name": "상의" - } - ], - "path_string": "패션의류 > 상의", - "is_primary": true - } - ], - "brand_name": "코지홈", - "shipping_policy_id": 1, - "shipping_policy_name": "기본 배송정책", - "min_purchase_qty": 1, - "max_purchase_qty": 0, - "has_options": true, - "options_count": 6, - "options": [], - "labels": [ - { - "name": "BEST", - "color": "#ef4444" - } - ], - "review_count": 128, - "rating_avg": 4.7, - "created_at": "2026-05-20 14:30:00", - "updated_at": "2026-05-28 09:15:00", - "is_owner": false, - "abilities": { - "can_update": true, - "can_delete": true - } - }, - { - "id": 102, - "name": { - "ko": "데일리 와이드 슬랙스" - }, - "name_localized": "데일리 와이드 슬랙스", - "product_code": "PT-2042", - "sku": "PT-2042", - "thumbnail_url": "/images/placeholder.png", - "list_price": 39000, - "list_price_formatted": "39,000원", - "selling_price": 39000, - "selling_price_formatted": "39,000원", - "discount_rate": 0, - "multi_currency_list_price": { - "KRW": { - "price": 39000, - "formatted": "39,000원", - "is_default": true, - "editable": true - }, - "USD": { - "price": 30, - "formatted": "$30.00", - "is_default": false, - "editable": false, - "exchange_rate": 1300 - } - }, - "multi_currency_selling_price": { - "KRW": { - "price": 39000, - "formatted": "39,000원", - "is_default": true, - "editable": true - }, - "USD": { - "price": 30, - "formatted": "$30.00", - "is_default": false, - "editable": false, - "exchange_rate": 1300 - } - }, - "stock_quantity": 85, - "safe_stock_quantity": 10, - "is_below_safe_stock": false, - "option_stock_sum": 85, - "sales_status": "on_sale", - "sales_status_label": "판매중", - "sales_status_variant": "success", - "display_status": "visible", - "display_status_label": "노출", - "display_status_variant": "success", - "categories": [ - { - "id": 11, - "name": "상의", - "is_primary": true - } - ], - "primary_category": "상의", - "categories_with_path": [ - { - "id": 11, - "path": [ - { - "id": 1, - "name": "패션의류" - }, - { - "id": 11, - "name": "상의" - } - ], - "path_string": "패션의류 > 상의", - "is_primary": true - } - ], - "brand_name": "어반핏", - "shipping_policy_id": 1, - "shipping_policy_name": "기본 배송정책", - "min_purchase_qty": 1, - "max_purchase_qty": 0, - "has_options": false, - "options_count": 0, - "options": [], - "labels": [], - "review_count": 42, - "rating_avg": 4.3, - "created_at": "2026-05-20 14:30:00", - "updated_at": "2026-05-28 09:15:00", - "is_owner": false, - "abilities": { - "can_update": true, - "can_delete": true - } - }, - { - "id": 103, - "name": { - "ko": "구스다운 경량 패딩 점퍼" - }, - "name_localized": "구스다운 경량 패딩 점퍼", - "product_code": "OW-3310", - "sku": "OW-3310", - "thumbnail_url": "/images/placeholder.png", - "list_price": 189000, - "list_price_formatted": "189,000원", - "selling_price": 132300, - "selling_price_formatted": "132,300원", - "discount_rate": 30, - "multi_currency_list_price": { - "KRW": { - "price": 189000, - "formatted": "189,000원", - "is_default": true, - "editable": true - }, - "USD": { - "price": 145.38, - "formatted": "$145.38", - "is_default": false, - "editable": false, - "exchange_rate": 1300 - } - }, - "multi_currency_selling_price": { - "KRW": { - "price": 132300, - "formatted": "132,300원", - "is_default": true, - "editable": true - }, - "USD": { - "price": 101.77, - "formatted": "$101.77", - "is_default": false, - "editable": false, - "exchange_rate": 1300 - } - }, - "stock_quantity": 0, - "safe_stock_quantity": 10, - "is_below_safe_stock": true, - "option_stock_sum": 0, - "sales_status": "sold_out", - "sales_status_label": "품절", - "sales_status_variant": "danger", - "display_status": "hidden", - "display_status_label": "미노출", - "display_status_variant": "gray", - "categories": [ - { - "id": 11, - "name": "상의", - "is_primary": true - } - ], - "primary_category": "상의", - "categories_with_path": [ - { - "id": 11, - "path": [ - { - "id": 1, - "name": "패션의류" - }, - { - "id": 11, - "name": "상의" - } - ], - "path_string": "패션의류 > 상의", - "is_primary": true - } - ], - "brand_name": "노스벨리", - "shipping_policy_id": 1, - "shipping_policy_name": "기본 배송정책", - "min_purchase_qty": 1, - "max_purchase_qty": 0, - "has_options": true, - "options_count": 9, - "options": [], - "labels": [ - { - "name": "BEST", - "color": "#ef4444" - } - ], - "review_count": 271, - "rating_avg": 4.9, - "created_at": "2026-05-20 14:30:00", - "updated_at": "2026-05-28 09:15:00", - "is_owner": false, - "abilities": { - "can_update": true, - "can_delete": true - } - } - ], - "pagination": { - "current_page": 1, - "last_page": 4, - "per_page": 20, - "total": 73 - } - } - }, - "product": { - "data": { - "id": 101, - "name": { - "ko": "베이직 오버핏 코튼 티셔츠", - "en": "Basic Overfit Cotton T-Shirt" - }, - "product_code": "TS-1001", - "sales_product_code": "SP-TS-1001", - "abilities": { - "can_update": true, - "can_delete": true - }, - "images": [ - { - "id": 5001, - "hash": "img5001", - "original_filename": "tshirt_main.jpg", - "download_url": "/images/placeholder.png", - "alt_text": "정면", - "is_thumbnail": true, - "sort_order": 0, - "order": 0, - "width": 1000, - "height": 1000, - "file_size": 184320, - "size": 184320, - "size_formatted": "180 KB", - "mime_type": "image/jpeg", - "is_image": true - }, - { - "id": 5002, - "hash": "img5002", - "original_filename": "tshirt_back.jpg", - "download_url": "/images/placeholder.png", - "alt_text": "후면", - "is_thumbnail": false, - "sort_order": 1, - "order": 1, - "width": 1000, - "height": 1000, - "file_size": 176128, - "size": 176128, - "size_formatted": "172 KB", - "mime_type": "image/jpeg", - "is_image": true - }, - { - "id": 5003, - "hash": "img5003", - "original_filename": "tshirt_detail.jpg", - "download_url": "/images/placeholder.png", - "alt_text": "디테일", - "is_thumbnail": false, - "sort_order": 2, - "order": 2, - "width": 1000, - "height": 1000, - "file_size": 158720, - "size": 158720, - "size_formatted": "155 KB", - "mime_type": "image/jpeg", - "is_image": true - } - ], - "created_at": "2026-05-20 14:30:00", - "updated_at": "2026-05-28 09:15:00" - } - }, - "categories": { - "data": { - "abilities": { - "can_create": true, - "can_update": true - }, - "data": [ - { - "id": 1, - "name": { - "ko": "패션의류" - }, - "localized_name": "패션의류", - "description": null, - "parent_id": null, - "path": "1", - "depth": 0, - "sort_order": 1, - "is_active": true, - "slug": "fashion", - "url": "fashion", - "icon": "folder", - "meta_title": null, - "meta_description": null, - "created_at": "2026-04-01 10:00:00", - "updated_at": "2026-05-01 10:00:00", - "children": [ - { - "id": 11, - "name": { - "ko": "상의" - }, - "localized_name": "상의", - "description": null, - "parent_id": 1, - "path": "1/11", - "depth": 1, - "sort_order": 11, - "is_active": true, - "slug": "tops", - "url": "tops", - "icon": "folder", - "meta_title": null, - "meta_description": null, - "created_at": "2026-04-01 10:00:00", - "updated_at": "2026-05-01 10:00:00", - "children": [ - { - "id": 111, - "name": { - "ko": "티셔츠" - }, - "localized_name": "티셔츠", - "description": null, - "parent_id": 11, - "path": "11/111", - "depth": 2, - "sort_order": 111, - "is_active": true, - "slug": "tshirts", - "url": "tshirts", - "icon": "folder", - "meta_title": null, - "meta_description": null, - "created_at": "2026-04-01 10:00:00", - "updated_at": "2026-05-01 10:00:00", - "children": [] - }, - { - "id": 112, - "name": { - "ko": "셔츠/블라우스" - }, - "localized_name": "셔츠/블라우스", - "description": null, - "parent_id": 11, - "path": "11/112", - "depth": 2, - "sort_order": 112, - "is_active": true, - "slug": "shirts", - "url": "shirts", - "icon": "folder", - "meta_title": null, - "meta_description": null, - "created_at": "2026-04-01 10:00:00", - "updated_at": "2026-05-01 10:00:00", - "children": [] - } - ] - }, - { - "id": 12, - "name": { - "ko": "하의" - }, - "localized_name": "하의", - "description": null, - "parent_id": 1, - "path": "1/12", - "depth": 1, - "sort_order": 12, - "is_active": true, - "slug": "bottoms", - "url": "bottoms", - "icon": "folder", - "meta_title": null, - "meta_description": null, - "created_at": "2026-04-01 10:00:00", - "updated_at": "2026-05-01 10:00:00", - "children": [ - { - "id": 121, - "name": { - "ko": "슬랙스" - }, - "localized_name": "슬랙스", - "description": null, - "parent_id": 12, - "path": "12/121", - "depth": 2, - "sort_order": 121, - "is_active": true, - "slug": "slacks", - "url": "slacks", - "icon": "folder", - "meta_title": null, - "meta_description": null, - "created_at": "2026-04-01 10:00:00", - "updated_at": "2026-05-01 10:00:00", - "children": [] - } - ] - } - ] - }, - { - "id": 2, - "name": { - "ko": "가방/잡화" - }, - "localized_name": "가방/잡화", - "description": null, - "parent_id": null, - "path": "2", - "depth": 0, - "sort_order": 2, - "is_active": true, - "slug": "bags", - "url": "bags", - "icon": "folder", - "meta_title": null, - "meta_description": null, - "created_at": "2026-04-01 10:00:00", - "updated_at": "2026-05-01 10:00:00", - "children": [ - { - "id": 21, - "name": { - "ko": "백팩" - }, - "localized_name": "백팩", - "description": null, - "parent_id": 2, - "path": "2/21", - "depth": 1, - "sort_order": 21, - "is_active": true, - "slug": "backpack", - "url": "backpack", - "icon": "folder", - "meta_title": null, - "meta_description": null, - "created_at": "2026-04-01 10:00:00", - "updated_at": "2026-05-01 10:00:00", - "children": [] - } - ] - }, - { - "id": 3, - "name": { - "ko": "아우터" - }, - "localized_name": "아우터", - "description": null, - "parent_id": null, - "path": "3", - "depth": 0, - "sort_order": 3, - "is_active": true, - "slug": "outer", - "url": "outer", - "icon": "folder", - "meta_title": null, - "meta_description": null, - "created_at": "2026-04-01 10:00:00", - "updated_at": "2026-05-01 10:00:00", - "children": [] - } - ] - } - }, - "orders": { - "data": { - "data": [ - { - "id": 5101, - "no": 1, - "order_number": "20260601-0042", - "order_status": "preparing", - "order_status_label": "배송준비중", - "order_status_variant": "info", - "total_amount": 69500, - "total_amount_formatted": "69,500원", - "total_shipping_amount": 3000, - "total_shipping_amount_formatted": "3,000원", - "total_paid_amount": 69500, - "total_paid_amount_formatted": "69,500원", - "total_unpaid_amount": 0, - "total_unpaid_amount_formatted": "0원", - "ordered_at_formatted": "2026-06-01 19:30", - "order_device_label": "PC", - "is_first_order": false, - "options_count": 2, - "user": { - "uuid": "u-1001", - "name": "홍길동", - "email": "gildong@example.com" - }, - "first_option": { - "product_name": "프리미엄 무선 이어버드 프로", - "product_option_name": "미드나잇 블랙 / 기본", - "product_code": "EARBUDS-PRO", - "quantity": 2, - "thumbnail_url": null - }, - "address": { - "orderer_name": "홍길동", - "recipient_name": "홍길동", - "recipient_country_code": "KR", - "recipient_country_name": "대한민국" - }, - "payment": { - "payment_method": "card", - "payment_method_label": "신용카드" - }, - "shipping": { - "shipping_type": "parcel", - "shipping_type_label": "택배", - "shipping_method_label": "일반배송", - "carrier_name": "CJ대한통운", - "tracking_number": null - } - }, - { - "id": 5102, - "no": 2, - "order_number": "20260530-0118", - "order_status": "shipping", - "order_status_label": "배송중", - "order_status_variant": "primary", - "total_amount": 42000, - "total_amount_formatted": "42,000원", - "total_shipping_amount": 0, - "total_shipping_amount_formatted": "0원", - "total_paid_amount": 42000, - "total_paid_amount_formatted": "42,000원", - "total_unpaid_amount": 0, - "total_unpaid_amount_formatted": "0원", - "ordered_at_formatted": "2026-05-30 13:05", - "order_device_label": "PC", - "is_first_order": false, - "options_count": 1, - "user": { - "uuid": "u-1002", - "name": "김영희", - "email": "younghee@example.com" - }, - "first_option": { - "product_name": "휴대용 블루투스 스피커", - "product_option_name": "기본", - "product_code": "SPEAKER-MINI", - "quantity": 1, - "thumbnail_url": null - }, - "address": { - "orderer_name": "김영희", - "recipient_name": "김영희", - "recipient_country_code": "KR", - "recipient_country_name": "대한민국" - }, - "payment": { - "payment_method": "vbank", - "payment_method_label": "가상계좌" - }, - "shipping": { - "shipping_type": "parcel", - "shipping_type_label": "택배", - "shipping_method_label": "일반배송", - "carrier_name": "CJ대한통운", - "tracking_number": "6123456789020" - } - }, - { - "id": 5103, - "no": 3, - "order_number": "20260528-0096", - "order_status": "payment_pending", - "order_status_label": "입금대기", - "order_status_variant": "warning", - "total_amount": 128000, - "total_amount_formatted": "128,000원", - "total_shipping_amount": 3000, - "total_shipping_amount_formatted": "3,000원", - "total_paid_amount": 0, - "total_paid_amount_formatted": "0원", - "total_unpaid_amount": 128000, - "total_unpaid_amount_formatted": "128,000원", - "ordered_at_formatted": "2026-05-28 21:40", - "order_device_label": "PC", - "is_first_order": true, - "options_count": 1, - "user": { - "uuid": "u-1003", - "name": "John Smith", - "email": "john@example.com" - }, - "first_option": { - "product_name": "스마트 워치 밴드", - "product_option_name": "브라운 / M", - "product_code": "WATCH-BAND", - "quantity": 1, - "thumbnail_url": null - }, - "address": { - "orderer_name": "John Smith", - "recipient_name": "John Smith", - "recipient_country_code": "US", - "recipient_country_name": "United States" - }, - "payment": { - "payment_method": "dbank", - "payment_method_label": "무통장입금" - }, - "shipping": { - "shipping_type": "parcel", - "shipping_type_label": "택배", - "shipping_method_label": "일반배송", - "carrier_name": "CJ대한통운", - "tracking_number": null - } - }, - { - "id": 5104, - "no": 4, - "order_number": "20260525-0071", - "order_status": "cancelled", - "order_status_label": "주문취소", - "order_status_variant": "danger", - "total_amount": 24000, - "total_amount_formatted": "24,000원", - "total_shipping_amount": 3000, - "total_shipping_amount_formatted": "3,000원", - "total_paid_amount": 0, - "total_paid_amount_formatted": "0원", - "total_unpaid_amount": 0, - "total_unpaid_amount_formatted": "0원", - "ordered_at_formatted": "2026-05-25 10:12", - "order_device_label": "PC", - "is_first_order": false, - "options_count": 1, - "user": { - "uuid": "u-1004", - "name": "이철수", - "email": "chulsoo@example.com" - }, - "first_option": { - "product_name": "USB-C 멀티 어댑터", - "product_option_name": "실버", - "product_code": "ADAPTER-USBC", - "quantity": 1, - "thumbnail_url": null - }, - "address": { - "orderer_name": "이철수", - "recipient_name": "이철수", - "recipient_country_code": "KR", - "recipient_country_name": "대한민국" - }, - "payment": { - "payment_method": "card", - "payment_method_label": "신용카드" - }, - "shipping": { - "shipping_type": "parcel", - "shipping_type_label": "택배", - "shipping_method_label": "일반배송", - "carrier_name": "CJ대한통운", - "tracking_number": null - } - } - ], - "statistics": { - "pending_payment": 1, - "payment_complete": 0, - "preparing": 1, - "shipping": 1, - "delivered": 0, - "confirmed": 0, - "cancelled": 1 - }, - "pagination": { - "current_page": 1, - "last_page": 1, - "per_page": 20, - "total": 4, - "from": 1, - "to": 4, - "has_more_pages": false - }, - "abilities": { - "can_update": true - } - } - }, - "order": { - "data": { - "id": 5101, - "order_number": "20260601-0042", - "order_status": "preparing", - "order_status_label": "배송준비중", - "order_status_variant": "info", - "order_device": "pc", - "order_device_label": "PC", - "is_first_order": false, - "abilities": { - "can_read": true, - "can_update": true, - "can_cancel": true - }, - "subtotal_amount": 76000, - "subtotal_amount_formatted": "76,000원", - "total_discount_amount": 9500, - "total_discount_amount_formatted": "9,500원", - "total_shipping_amount": 3000, - "total_shipping_amount_formatted": "3,000원", - "total_amount": 69500, - "total_amount_formatted": "69,500원", - "total_paid_amount": 69500, - "total_paid_amount_formatted": "69,500원", - "total_product_coupon_discount_amount": 5000, - "total_product_coupon_discount_amount_formatted": "5,000원", - "total_order_coupon_discount_amount": 2500, - "total_order_coupon_discount_amount_formatted": "2,500원", - "total_coupon_discount_amount": 7500, - "total_coupon_discount_amount_formatted": "7,500원", - "total_code_discount_amount": 2000, - "total_code_discount_amount_formatted": "2,000원", - "total_points_used_amount": 1000, - "total_points_used_amount_formatted": "1,000원", - "total_deposit_used_amount": 0, - "total_deposit_used_amount_formatted": "0원", - "total_earned_points_amount": 710, - "total_earned_points_amount_formatted": "710원", - "total_tax_amount": 6318, - "total_tax_amount_formatted": "6,318원", - "total_vat_amount": 6318, - "total_vat_amount_formatted": "6,318원", - "total_list_price": 82000, - "total_list_price_formatted": "82,000원", - "total_quantity": 3, - "item_count": 2, - "mc_subtotal_amount": { - "KRW": { - "price": 76000, - "formatted": "76,000원", - "is_default": true - }, - "USD": { - "price": 55.7, - "formatted": "$55.70", - "is_default": false - } - }, - "mc_total_discount_amount": { - "KRW": { - "price": 9500, - "formatted": "9,500원", - "is_default": true - }, - "USD": { - "price": 6.9, - "formatted": "$6.90", - "is_default": false - } - }, - "mc_total_shipping_amount": { - "KRW": { - "price": 3000, - "formatted": "3,000원", - "is_default": true - }, - "USD": { - "price": 2.2, - "formatted": "$2.20", - "is_default": false - } - }, - "mc_total_amount": { - "KRW": { - "price": 69500, - "formatted": "69,500원", - "is_default": true - }, - "USD": { - "price": 50.7, - "formatted": "$50.70", - "is_default": false - } - }, - "ordered_at_formatted": "2026-06-01 19:30", - "paid_at_formatted": "2026-06-01 19:31", - "delivered_at": null, - "user_id": "u-1001", - "user_login_id": "gildong", - "orderer_name": "홍길동", - "orderer_phone": "010-1234-5678", - "orderer_tel": "02-555-1234", - "orderer_email": "gildong@example.com", - "recipient_name": "홍길동", - "recipient_phone": "010-1234-5678", - "recipient_zipcode": "06234", - "recipient_address": "서울특별시 강남구 테헤란로 123", - "recipient_detail_address": "4층 401호", - "delivery_memo": "부재 시 경비실에 맡겨주세요", - "shipping_address": { - "recipient_name": "홍길동", - "recipient_phone": "010-1234-5678", - "zipcode": "06234", - "address": "서울특별시 강남구 테헤란로 123", - "address_detail": "4층 401호", - "full_address": "서울특별시 강남구 테헤란로 123 4층 401호", - "delivery_memo": "부재 시 경비실에 맡겨주세요" - }, - "options": [ - { - "id": 9101, - "option_status": "preparing", - "option_status_label": "배송준비중", - "option_status_variant": "info", - "product_id": 1, - "product_option_id": 101, - "sku": "EARBUDS-PRO-BLK", - "product_name": "프리미엄 무선 이어버드 프로", - "product_option_name": "미드나잇 블랙 / 기본", - "option_name": "미드나잇 블랙 / 기본", - "quantity": 2, - "shipped_quantity": 0, - "unit_price": 29000, - "unit_price_formatted": "29,000원", - "subtotal_price": 58000, - "subtotal_price_formatted": "58,000원", - "subtotal_discount_amount": 7500, - "subtotal_discount_amount_formatted": "7,500원", - "list_price": 32000, - "list_price_formatted": "32,000원", - "final_amount": 50500, - "final_amount_formatted": "50,500원", - "product_coupon_discount_amount": 5000, - "product_coupon_discount_amount_formatted": "5,000원", - "order_coupon_discount_amount": 2500, - "order_coupon_discount_amount_formatted": "2,500원", - "coupon_discount_amount": 7500, - "coupon_discount_amount_formatted": "7,500원", - "code_discount_amount": 0, - "code_discount_amount_formatted": "0원", - "referral_discount_amount": 0, - "referral_discount_amount_formatted": "0원", - "subtotal_points_used_amount": 500, - "subtotal_points_used_amount_formatted": "500원", - "subtotal_deposit_used_amount": 0, - "subtotal_deposit_used_amount_formatted": "0원", - "subtotal_earned_points_amount": 530, - "subtotal_earned_points_amount_formatted": "530원", - "subtotal_tax_amount": 4818, - "subtotal_tax_amount_formatted": "4,818원", - "subtotal_vat_amount": 4818, - "subtotal_vat_amount_formatted": "4,818원", - "shipping_policy_name": "기본 배송 정책", - "shipping_type_label": "택배", - "shipping_amount": 3000, - "shipping_amount_formatted": "3,000원", - "carrier_name": "CJ대한통운", - "tracking_number": "6123456789012", - "thumbnail_url": null, - "product_snapshot": { - "product_code": "EARBUDS-PRO" - }, - "mc_unit_price": { - "KRW": { - "price": 29000, - "formatted": "29,000원", - "is_default": true - }, - "USD": { - "price": 21.3, - "formatted": "$21.30", - "is_default": false - } - }, - "mc_subtotal_price": { - "KRW": { - "price": 58000, - "formatted": "58,000원", - "is_default": true - }, - "USD": { - "price": 42.6, - "formatted": "$42.60", - "is_default": false - } - }, - "confirmed_at": null, - "promotions_applied_snapshot": { - "coupons": [ - { - "name": "이어버드 단독 10% 할인", - "total_discount_formatted": "5,000원" - } - ], - "discount_codes": [ - { - "code": "WELCOME2000", - "name": "신규가입 2,000원 할인코드", - "total_discount_formatted": "2,000원" - } - ], - "events": [] - } - }, - { - "id": 9102, - "option_status": "preparing", - "option_status_label": "배송준비중", - "option_status_variant": "info", - "product_id": 2, - "product_option_id": 102, - "sku": "CABLE-15M-WHT", - "product_name": "고속 충전 케이블 1.5m", - "product_option_name": "1.5m", - "option_name": "1.5m", - "quantity": 1, - "shipped_quantity": 0, - "unit_price": 18000, - "unit_price_formatted": "18,000원", - "subtotal_price": 18000, - "subtotal_price_formatted": "18,000원", - "subtotal_discount_amount": 0, - "subtotal_discount_amount_formatted": "0원", - "list_price": 18000, - "list_price_formatted": "18,000원", - "final_amount": 18000, - "final_amount_formatted": "18,000원", - "product_coupon_discount_amount": 0, - "product_coupon_discount_amount_formatted": "0원", - "order_coupon_discount_amount": 0, - "order_coupon_discount_amount_formatted": "0원", - "coupon_discount_amount": 0, - "coupon_discount_amount_formatted": "0원", - "code_discount_amount": 0, - "code_discount_amount_formatted": "0원", - "referral_discount_amount": 0, - "referral_discount_amount_formatted": "0원", - "subtotal_points_used_amount": 0, - "subtotal_points_used_amount_formatted": "0원", - "subtotal_deposit_used_amount": 0, - "subtotal_deposit_used_amount_formatted": "0원", - "subtotal_earned_points_amount": 180, - "subtotal_earned_points_amount_formatted": "180원", - "subtotal_tax_amount": 1500, - "subtotal_tax_amount_formatted": "1,500원", - "subtotal_vat_amount": 1500, - "subtotal_vat_amount_formatted": "1,500원", - "shipping_policy_name": "무료 배송 정책", - "shipping_type_label": "택배", - "shipping_amount": 0, - "shipping_amount_formatted": "0원", - "carrier_name": "CJ대한통운", - "tracking_number": "6123456789013", - "thumbnail_url": null, - "product_snapshot": { - "product_code": "CABLE-15M" - }, - "mc_unit_price": { - "KRW": { - "price": 18000, - "formatted": "18,000원", - "is_default": true - }, - "USD": { - "price": 13.2, - "formatted": "$13.20", - "is_default": false - } - }, - "mc_subtotal_price": { - "KRW": { - "price": 18000, - "formatted": "18,000원", - "is_default": true - }, - "USD": { - "price": 13.2, - "formatted": "$13.20", - "is_default": false - } - }, - "confirmed_at": null, - "promotions_applied_snapshot": { - "coupons": [], - "discount_codes": [], - "events": [] - } - } - ], - "payment": { - "payment_method_label": "신용카드" - }, - "payments": [ - { - "id": 8101, - "payment_status": "paid", - "payment_status_label": "결제완료", - "payment_status_variant": "success", - "payment_method": "card", - "payment_method_label": "신용카드", - "payment_type_label": "일반결제", - "payment_number": "PAY-20260601-0042", - "paid_amount_formatted": "69,500원", - "account_info": null, - "requested_at_formatted": "2026-06-01 19:30", - "paid_at_formatted": "2026-06-01 19:31", - "due_date_formatted": null, - "cancelled_amount": 0, - "cancelled_amount_formatted": "0원", - "refunded_amount": 0, - "refunded_amount_formatted": "0원", - "refund_status_label": null, - "refund_status_variant": null, - "refund_points_amount": 0, - "refund_points_amount_formatted": "0원", - "event_discount_amount": 0, - "event_discount_amount_formatted": "0원", - "referral_discount_amount": 0, - "referral_discount_amount_formatted": "0원" - } - ], - "shippings": [], - "promotions_applied_snapshot": { - "product_promotions": { - "coupons": [ - { - "coupon_id": 5001, - "name": "이어버드 단독 10% 할인", - "target_type": "product_amount", - "total_discount": 5000, - "total_discount_formatted": "5,000원" - } - ], - "discount_codes": [ - { - "code_id": 7001, - "code": "WELCOME2000", - "name": "신규가입 2,000원 할인코드", - "total_discount": 2000, - "total_discount_formatted": "2,000원" - } - ], - "events": [] - }, - "order_promotions": { - "coupons": [ - { - "coupon_id": 6001, - "name": "5만원 이상 5,000원 할인", - "target_type": "order_amount", - "total_discount": 5000, - "total_discount_formatted": "5,000원" - } - ], - "discount_codes": [], - "events": [] - } - }, - "shipping_policy_applied_snapshot": { - "policy_name": "기본 배송 정책", - "shipping_amount": 3000 - } - } - }, - "reviews": { - "data": { - "abilities": { - "can_update": true, - "can_delete": true - }, - "data": [ - { - "id": 9101, - "product_id": 101, - "user": { - "uuid": "u-aaa", - "name": "김민지", - "email": "minji@example.com" - }, - "product": { - "id": 101, - "name": "베이직 오버핏 코튼 티셔츠", - "thumbnail_url": "/images/placeholder.png" - }, - "option_snapshot_label": "블랙 / M", - "rating": 5, - "content": "핏이 정말 예쁘고 원단도 도톰해서 만족스러워요.", - "content_mode": "text", - "status": "visible", - "status_label": "노출", - "status_badge_color": "green", - "image_count": 2, - "has_reply": true, - "has_reply_label": "답변완료", - "has_reply_badge_color": "green", - "reply_content": "소중한 후기 감사합니다!", - "replied_at": "2026-05-27 09:10:00", - "created_at": "2026-05-26 11:20:00", - "updated_at": "2026-05-26 11:20:00", - "is_owner": false, - "abilities": { - "can_update": true, - "can_delete": true - } - }, - { - "id": 9102, - "product_id": 101, - "user": { - "uuid": "u-bbb", - "name": "이서준", - "email": "seojun@example.com" - }, - "product": { - "id": 101, - "name": "베이직 오버핏 코튼 티셔츠", - "thumbnail_url": "/images/placeholder.png" - }, - "option_snapshot_label": "화이트 / L", - "rating": 4, - "content": "색감 예쁘고 좋아요.", - "content_mode": "text", - "status": "visible", - "status_label": "노출", - "status_badge_color": "green", - "image_count": 0, - "has_reply": false, - "has_reply_label": "미답변", - "has_reply_badge_color": "gray", - "reply_content": null, - "replied_at": null, - "created_at": "2026-05-24 16:42:00", - "updated_at": "2026-05-24 16:42:00", - "is_owner": false, - "abilities": { - "can_update": true, - "can_delete": true - } - }, - { - "id": 9103, - "product_id": 102, - "user": { - "uuid": "u-ccc", - "name": "박도윤", - "email": "doyun@example.com" - }, - "product": { - "id": 102, - "name": "데일리 와이드 슬랙스", - "thumbnail_url": "/images/placeholder.png" - }, - "option_snapshot_label": "", - "rating": 2, - "content": "기대보다 별로였어요.", - "content_mode": "text", - "status": "hidden", - "status_label": "숨김", - "status_badge_color": "gray", - "image_count": 0, - "has_reply": false, - "has_reply_label": "미답변", - "has_reply_badge_color": "gray", - "reply_content": null, - "replied_at": null, - "created_at": "2026-05-22 10:05:00", - "updated_at": "2026-05-22 10:05:00", - "is_owner": false, - "abilities": { - "can_update": true, - "can_delete": true - } - } - ], - "meta": { - "current_page": 1, - "last_page": 2, - "per_page": 15, - "total": 21 - } - } - }, - "brands": { - "data": { - "abilities": { - "can_create": true, - "can_update": true - }, - "data": [ - { - "id": 1, - "name": { - "ko": "코지홈" - }, - "localized_name": "코지홈", - "slug": "cozyhome", - "url": "cozyhome", - "website": "https://cozyhome.example.com", - "sort_order": 1, - "is_active": true, - "icon": "tag", - "description": { - "ko": "데일리 베이직 의류 브랜드" - }, - "products_count": 42, - "created_at": "2026-03-01 10:00:00", - "updated_at": "2026-05-01 10:00:00", - "is_owner": false, - "abilities": { - "can_update": true, - "can_delete": true - } - }, - { - "id": 2, - "name": { - "ko": "어반핏" - }, - "localized_name": "어반핏", - "slug": "urbanfit", - "url": "urbanfit", - "website": null, - "sort_order": 2, - "is_active": true, - "icon": "tag", - "description": { - "ko": "모던 시티 캐주얼" - }, - "products_count": 18, - "created_at": "2026-03-05 10:00:00", - "updated_at": "2026-05-02 10:00:00", - "is_owner": false, - "abilities": { - "can_update": true, - "can_delete": true - } - }, - { - "id": 3, - "name": { - "ko": "노스벨리" - }, - "localized_name": "노스벨리", - "slug": "northvalley", - "url": "northvalley", - "website": "https://northvalley.example.com", - "sort_order": 3, - "is_active": false, - "icon": "tag", - "description": null, - "products_count": 7, - "created_at": "2026-03-10 10:00:00", - "updated_at": "2026-05-03 10:00:00", - "is_owner": false, - "abilities": { - "can_update": true, - "can_delete": true - } - } - ] - } - }, - "carriers": { - "data": [ - { - "id": 1, - "name": "샘플 택배", - "code": "sample", - "enabled": true - }, - { - "id": 2, - "name": "샘플 퀵", - "code": "quick", - "enabled": false - } - ] - }, - "active_carriers": { - "data": [ - { - "id": 1, - "name": "샘플 택배", - "code": "sample" - } - ] - }, - "coupons": { - "data": { - "abilities": { - "can_create": true, - "can_update": true - }, - "data": [ - { - "id": 3001, - "name": { - "ko": "신규회원 10% 할인" - }, - "localized_name": "신규회원 10% 할인", - "code": "WELCOME10", - "target_type": "order_amount", - "target_type_label": "주문금액", - "discount_type": "rate", - "discount_value": 10, - "benefit_formatted": "10% 할인", - "min_order_amount": 20000, - "max_discount_amount": 5000, - "is_active": true, - "issued_count": 1240, - "used_count": 318, - "valid_from": "2026-05-01 00:00:00", - "valid_to": "2026-06-30 23:59:59", - "created_at": "2026-04-25 10:00:00", - "is_owner": false, - "abilities": { - "can_update": true, - "can_delete": true - } - }, - { - "id": 3002, - "name": { - "ko": "5,000원 즉시할인" - }, - "localized_name": "5,000원 즉시할인", - "code": "SAVE5000", - "target_type": "order_amount", - "target_type_label": "주문금액", - "discount_type": "fixed", - "discount_value": 5000, - "benefit_formatted": "5,000원 할인", - "min_order_amount": 30000, - "max_discount_amount": null, - "is_active": true, - "issued_count": 800, - "used_count": 145, - "valid_from": "2026-05-10 00:00:00", - "valid_to": "2026-06-15 23:59:59", - "created_at": "2026-05-08 10:00:00", - "is_owner": false, - "abilities": { - "can_update": true, - "can_delete": true - } - }, - { - "id": 3003, - "name": { - "ko": "무료배송 쿠폰" - }, - "localized_name": "무료배송 쿠폰", - "code": "FREESHIP", - "target_type": "shipping_fee", - "target_type_label": "배송비", - "discount_type": "fixed", - "discount_value": 3000, - "benefit_formatted": "배송비 3,000원 할인", - "min_order_amount": 0, - "max_discount_amount": 3000, - "is_active": false, - "issued_count": 0, - "used_count": 0, - "valid_from": "2026-06-01 00:00:00", - "valid_to": "2026-07-31 23:59:59", - "created_at": "2026-05-20 10:00:00", - "is_owner": false, - "abilities": { - "can_update": true, - "can_delete": true - } - } - ], - "pagination": { - "current_page": 1, - "last_page": 3, - "per_page": 15, - "total": 38 - } - } - }, - "coupon": { - "data": { - "id": 1, - "name": { - "ko": "신규회원 10% 할인", - "en": "New Member 10% Off" - }, - "localized_name": "신규회원 10% 할인", - "description": { - "ko": "신규 가입 회원 대상 주문금액 10% 할인 쿠폰", - "en": "10% off for new members" - }, - "localized_description": "신규 가입 회원 대상 주문금액 10% 할인 쿠폰", - "target_type": "order_amount", - "target_type_label": "주문금액", - "target_type_badge_color": "blue", - "discount_type": "rate", - "discount_type_label": "정률", - "discount_value": 10, - "discount_max_amount": 20000, - "min_order_amount": 30000, - "benefit_formatted": "10% 할인 (최대 20,000원)", - "issue_method": "download", - "issue_method_label": "다운로드", - "issue_method_badge_color": "green", - "issue_condition": "manual", - "issue_condition_label": "수동발급", - "issue_condition_badge_color": "gray", - "issue_status": "active", - "issue_status_label": "발급중", - "issue_status_badge_color": "green", - "total_quantity": 1000, - "issued_count": 342, - "per_user_limit": 1, - "issue_count_formatted": "342 / 1,000", - "valid_type": "days", - "valid_days": 30, - "valid_from": null, - "valid_to": null, - "valid_period_formatted": "발급일로부터 30일", - "issue_from": "2026-05-01T00:00", - "issue_to": "2026-06-30T23:59", - "issue_period_formatted": "2026-05-01 ~ 2026-06-30", - "is_combinable": false, - "target_scope": "all", - "target_scope_label": "제한 없음", - "is_issuable": true, - "created_at": "2026-04-25 10:00", - "updated_at": "2026-05-08 14:20", - "created_by_name": "관리자", - "included_products": [], - "excluded_products": [], - "included_categories": [], - "excluded_categories": [], - "issues_count": 342, - "abilities": { - "can_create": true, - "can_update": true, - "can_delete": true - } - } - }, - "couponIssues": { - "data": { - "data": [ - { - "id": 9001, - "coupon_id": 1, - "user_id": "u-1001", - "user_name": "홍길동", - "coupon_code": "WELCOME-A1B2", - "status": "used", - "status_label": "사용완료", - "status_badge_color": "gray", - "issued_at": "2026-05-02 10:15", - "expired_at": "2026-06-01 23:59", - "used_at": "2026-05-10 14:20", - "order_id": 5101, - "discount_amount": 7000, - "is_expired": false, - "is_usable": false - }, - { - "id": 9002, - "coupon_id": 1, - "user_id": "u-1002", - "user_name": "김영희", - "coupon_code": "WELCOME-C3D4", - "status": "issued", - "status_label": "발급완료", - "status_badge_color": "green", - "issued_at": "2026-05-15 09:40", - "expired_at": "2026-06-14 23:59", - "used_at": null, - "order_id": null, - "discount_amount": 0, - "is_expired": false, - "is_usable": true - }, - { - "id": 9003, - "coupon_id": 1, - "user_id": "u-1003", - "user_name": "이철수", - "coupon_code": "WELCOME-E5F6", - "status": "expired", - "status_label": "기간만료", - "status_badge_color": "red", - "issued_at": "2026-04-01 11:00", - "expired_at": "2026-05-01 23:59", - "used_at": null, - "order_id": null, - "discount_amount": 0, - "is_expired": true, - "is_usable": false - } - ], - "pagination": { - "current_page": 1, - "last_page": 1, - "per_page": 10, - "total": 3 - } - } - }, - "policy": { - "data": { - "id": 1, - "name": { - "ko": "기본 배송정책", - "en": "Standard Shipping" - }, - "name_localized": "기본 배송정책", - "is_active": true, - "is_default": true, - "sort_order": 0, - "created_at": "2026-05-01 09:00", - "updated_at": "2026-05-20 11:30", - "fee_summary": "3,000원 (50,000원 이상 무료배송)", - "countries_display": [ - { - "country_code": "KR", - "country_name": "대한민국", - "flag": "🇰🇷" - }, - { - "country_code": "US", - "country_name": "미국", - "flag": "🇺🇸" - } - ], - "country_settings": [ - { - "id": 11, - "country_code": "KR", - "shipping_method": "parcel", - "shipping_method_label": "택배", - "custom_shipping_name": null, - "currency_code": "KRW", - "charge_policy": "conditional_free", - "charge_policy_label": "조건부 무료", - "base_fee": 3000, - "free_threshold": 50000, - "ranges": null, - "api_endpoint": null, - "api_request_fields": null, - "api_response_fee_field": null, - "extra_fee_enabled": true, - "extra_fee_settings": [ - { - "region": "제주특별자치도", - "zipcode": "63000-63644", - "fee": 3000 - }, - { - "region": "도서산간", - "zipcode": "", - "fee": 4000 - } - ], - "extra_fee_multiply": false, - "is_active": true - }, - { - "id": 12, - "country_code": "US", - "shipping_method": "express", - "shipping_method_label": "특송", - "custom_shipping_name": null, - "currency_code": "USD", - "charge_policy": "range_amount", - "charge_policy_label": "구간별(금액)", - "base_fee": 0, - "free_threshold": null, - "ranges": [ - { - "min": 0, - "max": 100000, - "fee": 25000 - }, - { - "min": 100000, - "max": null, - "fee": 15000 - } - ], - "api_endpoint": null, - "api_request_fields": null, - "api_response_fee_field": null, - "extra_fee_enabled": false, - "extra_fee_settings": null, - "extra_fee_multiply": false, - "is_active": true - } - ], - "abilities": { - "can_create": true, - "can_update": true, - "can_delete": true - } - } - }, - "shipping_policies": { - "data": { - "abilities": { - "can_create": true, - "can_update": true, - "can_delete": true - }, - "data": [ - { - "id": 1, - "name": { - "ko": "기본 배송정책" - }, - "name_localized": "기본 배송정책", - "is_active": true, - "is_default": true, - "sort_order": 1, - "fee_summary": "3,000원 (50,000원 이상 무료배송)", - "countries_display": "🇰🇷 대한민국", - "country_settings": [ - { - "id": 1, - "country_code": "KR", - "country_name": "대한민국", - "shipping_method": "parcel", - "charge_policy": "conditional_free", - "base_fee": 3000, - "base_fee_formatted": "3,000원", - "currency_code": "KRW", - "free_threshold": 50000, - "free_threshold_formatted": "50,000원", - "carrier_label": "CJ대한통운" - } - ], - "created_at": "2026-03-01 10:00:00", - "updated_at": "2026-05-01 10:00:00", - "is_owner": false, - "abilities": { - "can_update": true, - "can_delete": true - } - }, - { - "id": 2, - "name": { - "ko": "해외 배송정책" - }, - "name_localized": "해외 배송정책", - "is_active": true, - "is_default": false, - "sort_order": 2, - "fee_summary": "국가별 차등 (25,000원~)", - "countries_display": "🇰🇷 🇺🇸 외 3개국", - "country_settings": [ - { - "id": 1, - "country_code": "KR", - "country_name": "대한민국", - "shipping_method": "parcel", - "charge_policy": "conditional_free", - "base_fee": 3000, - "base_fee_formatted": "3,000원", - "currency_code": "KRW", - "free_threshold": 50000, - "free_threshold_formatted": "50,000원", - "carrier_label": "CJ대한통운" - }, - { - "id": 2, - "country_code": "US", - "country_name": "미국", - "shipping_method": "express", - "charge_policy": "paid", - "base_fee": 25000, - "base_fee_formatted": "25,000원", - "currency_code": "KRW", - "free_threshold": null, - "free_threshold_formatted": null, - "carrier_label": "EMS" - } - ], - "created_at": "2026-03-02 10:00:00", - "updated_at": "2026-05-02 10:00:00", - "is_owner": false, - "abilities": { - "can_update": true, - "can_delete": true - } - }, - { - "id": 3, - "name": { - "ko": "무료배송 정책" - }, - "name_localized": "무료배송 정책", - "is_active": false, - "is_default": false, - "sort_order": 3, - "fee_summary": "전 상품 무료배송", - "countries_display": "🇰🇷 대한민국", - "country_settings": [ - { - "id": 1, - "country_code": "KR", - "country_name": "대한민국", - "shipping_method": "parcel", - "charge_policy": "free", - "base_fee": 0, - "base_fee_formatted": "0원", - "currency_code": "KRW", - "free_threshold": 0, - "free_threshold_formatted": "0원", - "carrier_label": "CJ대한통운" - } - ], - "created_at": "2026-03-03 10:00:00", - "updated_at": "2026-05-03 10:00:00", - "is_owner": false, - "abilities": { - "can_update": true, - "can_delete": true - } - } - ], - "pagination": { - "current_page": 1, - "last_page": 1, - "per_page": 20, - "total": 3 - } - } - }, - "extra_fee_templates": { - "data": { - "data": [ - { - "id": 1, - "description": "제주 도서산간 추가배송비", - "region": "제주특별자치도", - "zipcode": "63000-63644", - "fee": 3000, - "fee_formatted": "3,000원", - "is_active": true - }, - { - "id": 2, - "description": "울릉도/독도 추가배송비", - "region": "경상북도 울릉군", - "zipcode": "40200-40240", - "fee": 5000, - "fee_formatted": "5,000원", - "is_active": true - }, - { - "id": 3, - "description": "기타 도서산간 추가배송비", - "region": "전국 도서산간", - "zipcode": "", - "fee": 4000, - "fee_formatted": "4,000원", - "is_active": false - } - ] - } - }, - "presets": { - "data": [ - { - "id": 1, - "name": "기본 프리셋", - "config": [] - } - ] - }, - "product_labels": { - "data": { - "abilities": { - "can_create": true, - "can_update": true - }, - "data": [ - { - "id": 1, - "name": { - "ko": "BEST" - }, - "color": "#ef4444", - "is_active": true, - "sort_order": 1, - "assignments_count": 12, - "created_at": "2026-03-01 10:00:00", - "updated_at": "2026-05-01 10:00:00", - "is_owner": false, - "abilities": { - "can_update": true, - "can_delete": true - } - }, - { - "id": 2, - "name": { - "ko": "NEW" - }, - "color": "#3b82f6", - "is_active": true, - "sort_order": 2, - "assignments_count": 8, - "created_at": "2026-03-02 10:00:00", - "updated_at": "2026-05-02 10:00:00", - "is_owner": false, - "abilities": { - "can_update": true, - "can_delete": true - } - }, - { - "id": 3, - "name": { - "ko": "한정특가" - }, - "color": "#f59e0b", - "is_active": false, - "sort_order": 3, - "assignments_count": 0, - "created_at": "2026-03-03 10:00:00", - "updated_at": "2026-05-03 10:00:00", - "is_owner": false, - "abilities": { - "can_update": true, - "can_delete": true - } - } - ] - } - }, - "notice_templates": { - "data": { - "abilities": { - "can_create": true, - "can_update": true - }, - "data": [ - { - "id": 1, - "name": { - "ko": "의류 상품정보제공고시" - }, - "localized_name": "의류 상품정보제공고시", - "category": "clothing", - "fields": [ - { - "key": "material", - "label": { - "ko": "제품 소재" - } - }, - { - "key": "size", - "label": { - "ko": "치수" - } - }, - { - "key": "manufacturer", - "label": { - "ko": "제조자" - } - } - ], - "fields_count": 3, - "is_active": true, - "sort_order": 1, - "icon": "file-alt", - "created_at": "2026-03-01 10:00:00", - "updated_at": "2026-05-01 10:00:00", - "is_owner": false, - "abilities": { - "can_update": true, - "can_delete": true - } - }, - { - "id": 2, - "name": { - "ko": "가공식품 상품정보제공고시" - }, - "localized_name": "가공식품 상품정보제공고시", - "category": "food", - "fields": [ - { - "key": "name", - "label": { - "ko": "제품명" - } - }, - { - "key": "expiry", - "label": { - "ko": "소비기한" - } - } - ], - "fields_count": 2, - "is_active": true, - "sort_order": 2, - "icon": "file-alt", - "created_at": "2026-03-02 10:00:00", - "updated_at": "2026-05-02 10:00:00", - "is_owner": false, - "abilities": { - "can_update": true, - "can_delete": true - } - }, - { - "id": 3, - "name": { - "ko": "전자제품 상품정보제공고시" - }, - "localized_name": "전자제품 상품정보제공고시", - "category": "electronics", - "fields": [ - { - "key": "model", - "label": { - "ko": "모델명" - } - } - ], - "fields_count": 1, - "is_active": false, - "sort_order": 3, - "icon": "file-alt", - "created_at": "2026-03-03 10:00:00", - "updated_at": "2026-05-03 10:00:00", - "is_owner": false, - "abilities": { - "can_update": true, - "can_delete": true - } - } - ] - } - }, - "templates": { - "data": { - "data": [ - { - "id": 1, - "name": "기본 안내문", - "body_html": "

샘플 상품 안내 템플릿

" - } - ], - "meta": { - "total": 1, - "per_page": 20, - "current_page": 1 - }, - "abilities": { - "can_create": true - } - } - }, - "common_infos": { - "data": { - "abilities": { - "can_create": true, - "can_update": true - }, - "data": [ - { - "id": 1, - "name": { - "ko": "기본 배송/교환/환불 안내" - }, - "localized_name": "기본 배송/교환/환불 안내", - "content": { - "ko": "

주문 후 영업일 기준 1~2일 내 출고됩니다. 단순 변심 교환·반품은 수령 후 7일 이내 가능합니다.

" - }, - "localized_content": "

주문 후 영업일 기준 1~2일 내 출고됩니다. 단순 변심 교환·반품은 수령 후 7일 이내 가능합니다.

", - "content_mode": "html", - "is_default": true, - "is_active": true, - "sort_order": 1, - "icon": "info-circle", - "products_count": 30, - "language_count": 1, - "created_at": "2026-03-01 10:00:00", - "updated_at": "2026-05-01 10:00:00", - "is_owner": false, - "abilities": { - "can_update": true, - "can_delete": true - } - }, - { - "id": 2, - "name": { - "ko": "식품 보관 안내" - }, - "localized_name": "식품 보관 안내", - "content": { - "ko": "직사광선을 피해 서늘한 곳에 보관하세요." - }, - "localized_content": "직사광선을 피해 서늘한 곳에 보관하세요.", - "content_mode": "text", - "is_default": false, - "is_active": true, - "sort_order": 2, - "icon": "info-circle", - "products_count": 5, - "language_count": 1, - "created_at": "2026-03-02 10:00:00", - "updated_at": "2026-05-02 10:00:00", - "is_owner": false, - "abilities": { - "can_update": true, - "can_delete": true - } - }, - { - "id": 3, - "name": { - "ko": "대형가전 설치 안내" - }, - "localized_name": "대형가전 설치 안내", - "content": { - "ko": "

설치 기사 방문 일정은 별도 안내됩니다.

" - }, - "localized_content": "

설치 기사 방문 일정은 별도 안내됩니다.

", - "content_mode": "html", - "is_default": false, - "is_active": true, - "sort_order": 3, - "icon": "info-circle", - "products_count": 2, - "language_count": 1, - "created_at": "2026-03-03 10:00:00", - "updated_at": "2026-05-03 10:00:00", - "is_owner": false, - "abilities": { - "can_update": true, - "can_delete": true - } - } - ] - } - }, - "commonInfos": { - "data": { - "abilities": { - "can_create": true, - "can_update": true - }, - "data": [ - { - "id": 1, - "name": { - "ko": "기본 배송/교환/환불 안내" - }, - "localized_name": "기본 배송/교환/환불 안내", - "content": { - "ko": "

주문 후 영업일 기준 1~2일 내 출고됩니다. 단순 변심 교환·반품은 수령 후 7일 이내 가능합니다.

" - }, - "localized_content": "

주문 후 영업일 기준 1~2일 내 출고됩니다. 단순 변심 교환·반품은 수령 후 7일 이내 가능합니다.

", - "content_mode": "html", - "is_default": true, - "is_active": true, - "sort_order": 1, - "icon": "info-circle", - "products_count": 30, - "language_count": 1, - "created_at": "2026-03-01 10:00:00", - "updated_at": "2026-05-01 10:00:00", - "is_owner": false, - "abilities": { - "can_update": true, - "can_delete": true - } - }, - { - "id": 2, - "name": { - "ko": "식품 보관 안내" - }, - "localized_name": "식품 보관 안내", - "content": { - "ko": "직사광선을 피해 서늘한 곳에 보관하세요." - }, - "localized_content": "직사광선을 피해 서늘한 곳에 보관하세요.", - "content_mode": "text", - "is_default": false, - "is_active": true, - "sort_order": 2, - "icon": "info-circle", - "products_count": 5, - "language_count": 1, - "created_at": "2026-03-02 10:00:00", - "updated_at": "2026-05-02 10:00:00", - "is_owner": false, - "abilities": { - "can_update": true, - "can_delete": true - } - }, - { - "id": 3, - "name": { - "ko": "대형가전 설치 안내" - }, - "localized_name": "대형가전 설치 안내", - "content": { - "ko": "

설치 기사 방문 일정은 별도 안내됩니다.

" - }, - "localized_content": "

설치 기사 방문 일정은 별도 안내됩니다.

", - "content_mode": "html", - "is_default": false, - "is_active": true, - "sort_order": 3, - "icon": "info-circle", - "products_count": 2, - "language_count": 1, - "created_at": "2026-03-03 10:00:00", - "updated_at": "2026-05-03 10:00:00", - "is_owner": false, - "abilities": { - "can_update": true, - "can_delete": true - } - } - ] - } - }, - "order_logs": { - "data": { - "data": [ - { - "id": 3001, - "created_at": "2026-06-03 09:12", - "localized_description": "주문 상태를 결제완료에서 배송준비중으로 변경했습니다", - "user": { - "uuid": "admin-1", - "name": "관리자" - }, - "changes": [ - { - "field": "order_status", - "label": "주문상태", - "old": "payment_complete", - "old_label": "결제완료", - "new": "preparing", - "new_label": "배송준비중" - } - ], - "bulk_changes": [] - }, - { - "id": 3002, - "created_at": "2026-06-01 19:31", - "localized_description": "결제가 완료되었습니다", - "user": null, - "changes": [ - { - "field": "payment_status", - "label": "결제상태", - "old": "pending", - "old_label": "결제대기", - "new": "paid", - "new_label": "결제완료" - } - ], - "bulk_changes": [] - }, - { - "id": 3003, - "created_at": "2026-06-01 19:30", - "localized_description": "관리자 메모를 수정했습니다", - "user": { - "uuid": "admin-1", - "name": "관리자" - }, - "changes": [ - { - "field": "admin_memo", - "label": "관리자 메모", - "old": "", - "old_label": "(없음)", - "new": "VIP 고객 — 빠른 배송 요청", - "new_label": "VIP 고객 — 빠른 배송 요청" - }, - { - "field": "delivery_memo", - "label": "배송 메모", - "old": "", - "old_label": "(없음)", - "new": "부재 시 경비실", - "new_label": "부재 시 경비실" - } - ], - "bulk_changes": [] - } - ], - "meta": { - "current_page": 1, - "last_page": 1, - "per_page": 20, - "total": 3 - } - } - }, - "activity_logs": { - "data": { - "data": [ - { - "id": 1, - "log_type": "admin", - "log_type_label": "관리자", - "loggable_type": "Product", - "loggable_type_display": "상품", - "loggable_id": 101, - "action": "product.updated", - "action_label": "상품 수정", - "localized_description": "'베이직 오버핏 코튼 티셔츠' 상품을 수정했습니다.", - "description_key": "product.updated", - "properties": null, - "changes": [ - { - "field": "selling_price", - "label": "판매가", - "old": 29000, - "new": 23200, - "old_label": "29,000원", - "new_label": "23,200원" - }, - { - "field": "sales_status", - "label": "판매상태", - "old": "sold_out", - "new": "on_sale", - "old_label": "품절", - "new_label": "판매중" - } - ], - "bulk_changes": null, - "has_changes": true, - "actor_name": "관리자", - "user": { - "uuid": "admin-uuid-1", - "name": "관리자", - "email": "admin@example.com" - }, - "ip_address": "127.0.0.1", - "created_at": "2026-05-28 09:15:00", - "is_owner": false - }, - { - "id": 2, - "log_type": "admin", - "log_type_label": "관리자", - "loggable_type": "Product", - "loggable_type_display": "상품", - "loggable_id": 101, - "action": "product.created", - "action_label": "상품 등록", - "localized_description": "'베이직 오버핏 코튼 티셔츠' 상품을 등록했습니다.", - "description_key": "product.created", - "properties": null, - "changes": null, - "bulk_changes": null, - "has_changes": false, - "actor_name": "운영자", - "user": { - "uuid": "admin-uuid-2", - "name": "운영자", - "email": "manager@example.com" - }, - "ip_address": "127.0.0.1", - "created_at": "2026-05-20 14:30:00", - "is_owner": false - }, - { - "id": 3, - "log_type": "admin", - "log_type_label": "관리자", - "loggable_type": "Product", - "loggable_type_display": "상품", - "loggable_id": 101, - "action": "product.stock_adjusted", - "action_label": "재고 조정", - "localized_description": "재고를 조정했습니다.", - "description_key": "product.stock_adjusted", - "properties": null, - "changes": [ - { - "field": "stock_quantity", - "label": "재고수량", - "old": 200, - "new": 320, - "old_label": "200", - "new_label": "320" - } - ], - "bulk_changes": null, - "has_changes": true, - "actor_name": "시스템", - "user": { - "name": "시스템" - }, - "ip_address": null, - "created_at": "2026-05-25 03:00:00", - "is_owner": false - } - ], - "meta": { - "current_page": 1, - "last_page": 1, - "per_page": 10, - "total": 3 - } - } - }, - "settings": { - "data": { - "general": { - "shop_name": "샘플 쇼핑몰", - "currency": "KRW" - }, - "shipping": { - "default_fee": 3000, - "free_threshold": 50000 - }, - "inquiry": { - "board_slug": "product-inquiry" - }, - "abilities": { - "can_update": true - } - } - }, - "ecommerce_settings": { - "data": { - "basic_info": { - "shop_name": "", - "route_path": "shop", - "no_route": false, - "company_name": "", - "business_number": "", - "ceo_name": "", - "business_type": "", - "business_category": "", - "zipcode": "", - "base_address": "", - "detail_address": "", - "phone": "", - "fax": "", - "email": "", - "privacy_officer": "", - "privacy_officer_email": "", - "mail_order_number": "", - "telecom_number": "" - }, - "language_currency": { - "default_language": "ko", - "default_currency": "KRW", - "currencies": [ - { - "code": "KRW", - "name": { - "ko": "KRW (원)", - "en": "KRW (Won)", - "ja": "KRW (ウォン)" - }, - "exchange_rate": null, - "rounding_unit": "1", - "rounding_method": "floor", - "decimal_places": 0, - "is_default": true - }, - { - "code": "USD", - "name": { - "ko": "USD (달러)", - "en": "USD (Dollar)", - "ja": "USD (ドル)" - }, - "exchange_rate": 0.85, - "rounding_unit": "0.01", - "rounding_method": "round", - "decimal_places": 2, - "is_default": false - }, - { - "code": "JPY", - "name": { - "ko": "JPY (엔)", - "en": "JPY (Yen)", - "ja": "JPY (円)" - }, - "exchange_rate": 115, - "rounding_unit": "1", - "rounding_method": "floor", - "decimal_places": 0, - "is_default": false - }, - { - "code": "CNY", - "name": { - "ko": "CNY (위안)", - "en": "CNY (Yuan)", - "ja": "CNY (人民元)" - }, - "exchange_rate": 5.8, - "rounding_unit": "0.01", - "rounding_method": "round", - "decimal_places": 2, - "is_default": false - }, - { - "code": "EUR", - "name": { - "ko": "EUR (유로)", - "en": "EUR (Euro)", - "ja": "EUR (ユーロ)" - }, - "exchange_rate": 0.78, - "rounding_unit": "0.01", - "rounding_method": "round", - "decimal_places": 2, - "is_default": false - } - ] - }, - "order_settings": { - "default_pg_provider": null, - "payment_methods": [ - { - "id": "card", - "pg_provider": null, - "sort_order": 1, - "is_active": false, - "min_order_amount": 0, - "stock_deduction_timing": "payment_complete", - "_cached_name": { - "ko": "신용카드", - "en": "Credit Card", - "ja": "クレジットカード" - }, - "_cached_description": { - "ko": "신용카드로 안전하게 결제", - "en": "Pay securely with credit card", - "ja": "クレジットカードで安全に決済" - }, - "_cached_icon": "credit-card", - "_cached_source": "builtin" - }, - { - "id": "vbank", - "pg_provider": null, - "sort_order": 2, - "is_active": false, - "min_order_amount": 0, - "stock_deduction_timing": "payment_complete", - "_cached_name": { - "ko": "가상계좌", - "en": "Virtual Account", - "ja": "バーチャル口座" - }, - "_cached_description": { - "ko": "가상계좌로 입금", - "en": "Pay via virtual account", - "ja": "バーチャル口座に入金" - }, - "_cached_icon": "money-check", - "_cached_source": "builtin" - }, - { - "id": "dbank", - "pg_provider": null, - "sort_order": 3, - "is_active": true, - "min_order_amount": 0, - "stock_deduction_timing": "order_placed", - "_cached_name": { - "ko": "무통장입금", - "en": "Bank Transfer", - "ja": "銀行振込" - }, - "_cached_description": { - "ko": "지정 계좌로 직접 입금", - "en": "Direct bank transfer", - "ja": "指定口座に直接入金" - }, - "_cached_icon": "building-columns", - "_cached_source": "builtin" - }, - { - "id": "bank", - "pg_provider": null, - "sort_order": 4, - "is_active": false, - "min_order_amount": 0, - "stock_deduction_timing": "payment_complete", - "_cached_name": { - "ko": "계좌이체", - "en": "Account Transfer", - "ja": "口座振替" - }, - "_cached_description": { - "ko": "실시간 계좌이체", - "en": "Real-time bank transfer", - "ja": "リアルタイム口座振替" - }, - "_cached_icon": "building-columns", - "_cached_source": "builtin" - }, - { - "id": "phone", - "pg_provider": null, - "sort_order": 5, - "is_active": false, - "min_order_amount": 0, - "stock_deduction_timing": "payment_complete", - "_cached_name": { - "ko": "휴대폰결제", - "en": "Mobile Payment", - "ja": "携帯電話決済" - }, - "_cached_description": { - "ko": "휴대폰 소액결제", - "en": "Mobile phone payment", - "ja": "携帯電話小額決済" - }, - "_cached_icon": "mobile-screen-button", - "_cached_source": "builtin" - }, - { - "id": "kginicis_samsung_pay", - "pg_provider": null, - "sort_order": 6, - "is_active": false, - "min_order_amount": 0, - "stock_deduction_timing": "payment_complete", - "_cached_name": { - "ko": "삼성페이 (KG이니시스)", - "en": "Samsung Pay (KG Inicis)" - }, - "_cached_description": { - "ko": "삼성페이로 결제 — KG 이니시스를 통해 처리", - "en": "Pay with Samsung Pay via KG Inicis" - }, - "_cached_icon": "mobile-screen-button", - "_cached_source": "plugin:sirsoft-pay_kginicis" - }, - { - "id": "kginicis_naverpay", - "pg_provider": null, - "sort_order": 7, - "is_active": false, - "min_order_amount": 0, - "stock_deduction_timing": "payment_complete", - "_cached_name": { - "ko": "네이버페이 (KG이니시스)", - "en": "Naver Pay (KG Inicis)" - }, - "_cached_description": { - "ko": "네이버페이로 결제 — KG 이니시스를 통해 처리", - "en": "Pay with Naver Pay via KG Inicis" - }, - "_cached_icon": "wallet", - "_cached_source": "plugin:sirsoft-pay_kginicis" - }, - { - "id": "kginicis_lpay", - "pg_provider": null, - "sort_order": 8, - "is_active": false, - "min_order_amount": 0, - "stock_deduction_timing": "payment_complete", - "_cached_name": { - "ko": "L.pay (KG이니시스)", - "en": "L.pay (KG Inicis)" - }, - "_cached_description": { - "ko": "L.pay 로 결제 — KG 이니시스를 통해 처리", - "en": "Pay with L.pay via KG Inicis" - }, - "_cached_icon": "mobile-screen-button", - "_cached_source": "plugin:sirsoft-pay_kginicis" - }, - { - "id": "kginicis_kakaopay", - "pg_provider": null, - "sort_order": 9, - "is_active": false, - "min_order_amount": 0, - "stock_deduction_timing": "payment_complete", - "_cached_name": { - "ko": "카카오페이 (KG이니시스)", - "en": "Kakao Pay (KG Inicis)" - }, - "_cached_description": { - "ko": "카카오페이로 결제 — KG 이니시스를 통해 처리", - "en": "Pay with Kakao Pay via KG Inicis" - }, - "_cached_icon": "mobile-screen-button", - "_cached_source": "plugin:sirsoft-pay_kginicis" - }, - { - "id": "kginicis_japan_paypay", - "pg_provider": null, - "sort_order": 10, - "is_active": false, - "min_order_amount": 0, - "stock_deduction_timing": "payment_complete", - "_cached_name": { - "ko": "PayPay (일본 KG이니시스)", - "en": "PayPay (KG Inicis Japan)" - }, - "_cached_description": { - "ko": "일본 엔(JPY) 주문을 KG 이니시스 CBT PayPay로 결제", - "en": "Pay JPY orders with KG Inicis CBT PayPay" - }, - "_cached_icon": "wallet", - "_cached_source": "plugin:sirsoft-pay_kginicis" - }, - { - "id": "point", - "pg_provider": null, - "sort_order": 10, - "is_active": false, - "min_order_amount": 0, - "stock_deduction_timing": "order_placed", - "_cached_name": { - "ko": "포인트결제", - "en": "Points", - "ja": "ポイント決済" - }, - "_cached_description": { - "ko": "적립 포인트로 결제", - "en": "Pay with points", - "ja": "積立ポイントで決済" - }, - "_cached_icon": "coins", - "_cached_source": "builtin" - }, - { - "id": "kginicis_japan_cvs", - "pg_provider": null, - "sort_order": 11, - "is_active": false, - "min_order_amount": 0, - "stock_deduction_timing": "payment_complete", - "_cached_name": { - "ko": "일본 편의점결제 (KG이니시스)", - "en": "Japan Convenience Store (KG Inicis)" - }, - "_cached_description": { - "ko": "일본 엔(JPY) 주문을 KG 이니시스 CBT 편의점 결제로 접수", - "en": "Accept JPY orders with KG Inicis CBT convenience store payment" - }, - "_cached_icon": "store", - "_cached_source": "plugin:sirsoft-pay_kginicis" - }, - { - "id": "deposit", - "pg_provider": null, - "sort_order": 11, - "is_active": false, - "min_order_amount": 0, - "stock_deduction_timing": "order_placed", - "_cached_name": { - "ko": "예치금결제", - "en": "Store Credit", - "ja": "預金決済" - }, - "_cached_description": { - "ko": "예치금으로 결제", - "en": "Pay with store credit", - "ja": "預金で決済" - }, - "_cached_icon": "wallet", - "_cached_source": "builtin" - }, - { - "id": "free", - "pg_provider": null, - "sort_order": 12, - "is_active": false, - "min_order_amount": 0, - "stock_deduction_timing": "order_placed", - "_cached_name": { - "ko": "무료", - "en": "Free", - "ja": "無料" - }, - "_cached_description": { - "ko": "결제 없이 주문 완료", - "en": "Order without payment", - "ja": "決済なしで注文完了" - }, - "_cached_icon": "gift", - "_cached_source": "builtin" - } - ], - "banks": [ - { - "code": "004", - "name": { - "ko": "국민은행", - "en": "Kookmin Bank" - } - }, - { - "code": "088", - "name": { - "ko": "신한은행", - "en": "Shinhan Bank" - } - }, - { - "code": "020", - "name": { - "ko": "우리은행", - "en": "Woori Bank" - } - }, - { - "code": "081", - "name": { - "ko": "하나은행", - "en": "Hana Bank" - } - }, - { - "code": "003", - "name": { - "ko": "IBK기업은행", - "en": "IBK Industrial Bank" - } - }, - { - "code": "011", - "name": { - "ko": "NH농협은행", - "en": "NH Nonghyup Bank" - } - }, - { - "code": "071", - "name": { - "ko": "우체국", - "en": "Korea Post" - } - }, - { - "code": "031", - "name": { - "ko": "DGB대구은행", - "en": "DGB Daegu Bank" - } - }, - { - "code": "032", - "name": { - "ko": "BNK부산은행", - "en": "BNK Busan Bank" - } - }, - { - "code": "034", - "name": { - "ko": "광주은행", - "en": "Kwangju Bank" - } - }, - { - "code": "035", - "name": { - "ko": "제주은행", - "en": "Jeju Bank" - } - }, - { - "code": "037", - "name": { - "ko": "전북은행", - "en": "Jeonbuk Bank" - } - }, - { - "code": "039", - "name": { - "ko": "BNK경남은행", - "en": "BNK Kyongnam Bank" - } - }, - { - "code": "045", - "name": { - "ko": "새마을금고", - "en": "MG Community Credit Cooperatives" - } - }, - { - "code": "048", - "name": { - "ko": "신협", - "en": "KFCC" - } - }, - { - "code": "090", - "name": { - "ko": "카카오뱅크", - "en": "Kakao Bank" - } - }, - { - "code": "092", - "name": { - "ko": "토스뱅크", - "en": "Toss Bank" - } - } - ], - "bank_accounts": [ - { - "bank_code": "004", - "account_number": "", - "account_holder": "", - "is_active": false, - "is_default": false - } - ], - "auto_cancel_expired": true, - "auto_cancel_days": 3, - "vbank_due_days": 3, - "dbank_due_days": 7, - "cart_expiry_days": 30, - "stock_restore_on_cancel": true, - "cancellable_statuses": [ - "payment_complete" - ], - "confirmable_statuses": [ - "shipping", - "delivered" - ] - }, - "shipping": { - "default_country": "KR", - "available_countries": [ - { - "code": "KR", - "name": { - "ko": "대한민국", - "en": "South Korea", - "ja": "大韓民国" - }, - "is_active": true - }, - { - "code": "US", - "name": { - "ko": "미국", - "en": "United States", - "ja": "アメリカ" - }, - "is_active": false - }, - { - "code": "JP", - "name": { - "ko": "일본", - "en": "Japan", - "ja": "日本" - }, - "is_active": false - }, - { - "code": "CN", - "name": { - "ko": "중국", - "en": "China", - "ja": "中国" - }, - "is_active": false - }, - { - "code": "SG", - "name": { - "ko": "싱가포르", - "en": "Singapore", - "ja": "シンガポール" - }, - "is_active": false - }, - { - "code": "HK", - "name": { - "ko": "홍콩", - "en": "Hong Kong", - "ja": "香港" - }, - "is_active": false - }, - { - "code": "TW", - "name": { - "ko": "대만", - "en": "Taiwan", - "ja": "台湾" - }, - "is_active": false - }, - { - "code": "VN", - "name": { - "ko": "베트남", - "en": "Vietnam", - "ja": "ベトナム" - }, - "is_active": false - }, - { - "code": "TH", - "name": { - "ko": "태국", - "en": "Thailand", - "ja": "タイ" - }, - "is_active": false - }, - { - "code": "MY", - "name": { - "ko": "말레이시아", - "en": "Malaysia", - "ja": "マレーシア" - }, - "is_active": false - } - ], - "international_shipping_enabled": false, - "free_shipping_threshold": 50000, - "free_shipping_enabled": true, - "address_validation_enabled": false, - "address_api_provider": "kakao", - "carriers": [ - { - "id": 1, - "code": "cj", - "name": { - "ko": "CJ대한통운", - "en": "CJ Logistics", - "ja": "CJ大韓通運" - }, - "type": "domestic", - "tracking_url": "https://trace.cjlogistics.com/next/tracking.html?wblNo={tracking_number}", - "is_active": true, - "sort_order": 1 - }, - { - "id": 2, - "code": "hanjin", - "name": { - "ko": "한진택배", - "en": "Hanjin Express", - "ja": "韓進택배" - }, - "type": "domestic", - "tracking_url": "https://www.hanjin.com/kor/CMS/DeliveryMgr/WaybillResult.do?wblnb={tracking_number}", - "is_active": true, - "sort_order": 2 - }, - { - "id": 3, - "code": "lotte", - "name": { - "ko": "롯데택배", - "en": "Lotte Global Logistics", - "ja": "ロッテ택배" - }, - "type": "domestic", - "tracking_url": "https://www.lotteglogis.com/home/reservation/tracking/link498?InvNo={tracking_number}", - "is_active": true, - "sort_order": 3 - }, - { - "id": 4, - "code": "logen", - "name": { - "ko": "로젠택배", - "en": "Logen Logistics", - "ja": "ロジェン택배" - }, - "type": "domestic", - "tracking_url": "https://www.ilogen.com/web/personal/trace/{tracking_number}", - "is_active": true, - "sort_order": 4 - }, - { - "id": 5, - "code": "ups", - "name": { - "ko": "UPS", - "en": "UPS", - "ja": "UPS" - }, - "type": "international", - "tracking_url": "https://www.ups.com/track?tracknum={tracking_number}", - "is_active": true, - "sort_order": 5 - }, - { - "id": 6, - "code": "ems", - "name": { - "ko": "EMS", - "en": "EMS", - "ja": "EMS" - }, - "type": "international", - "tracking_url": "https://service.epost.go.kr/trace.RetrieveEmsRi498.postal?POST_CODE={tracking_number}", - "is_active": true, - "sort_order": 6 - }, - { - "id": 7, - "code": "dhl", - "name": { - "ko": "DHL", - "en": "DHL", - "ja": "DHL" - }, - "type": "international", - "tracking_url": "https://www.dhl.com/kr-ko/home/tracking/tracking-express.html?submit=1&tracking-id={tracking_number}", - "is_active": true, - "sort_order": 7 - }, - { - "id": 8, - "code": "fedex", - "name": { - "ko": "FedEx", - "en": "FedEx", - "ja": "FedEx" - }, - "type": "international", - "tracking_url": "https://www.fedex.com/fedextrack/?tracknumbers={tracking_number}", - "is_active": true, - "sort_order": 8 - }, - { - "id": 9, - "code": "sf", - "name": { - "ko": "SF Express", - "en": "SF Express", - "ja": "SF Express" - }, - "type": "international", - "tracking_url": null, - "is_active": true, - "sort_order": 9 - }, - { - "id": 10, - "code": "yamato", - "name": { - "ko": "야마토운수", - "en": "Yamato Transport", - "ja": "ヤマト運輸" - }, - "type": "international", - "tracking_url": null, - "is_active": true, - "sort_order": 10 - }, - { - "id": 11, - "code": "sagawa", - "name": { - "ko": "사가와익스프레스", - "en": "Sagawa Express", - "ja": "佐川急便" - }, - "type": "international", - "tracking_url": null, - "is_active": true, - "sort_order": 11 - }, - { - "id": 12, - "code": "other", - "name": { - "ko": "기타", - "en": "Other", - "ja": "その他" - }, - "type": "domestic", - "tracking_url": null, - "is_active": true, - "sort_order": 99 - } - ], - "types": [ - { - "id": 1, - "code": "parcel", - "name": { - "ko": "택배", - "en": "Parcel", - "ja": "宅配便" - }, - "category": "domestic", - "is_active": true, - "sort_order": 1 - }, - { - "id": 2, - "code": "direct", - "name": { - "ko": "직접배송", - "en": "Direct Delivery", - "ja": "直送" - }, - "category": "domestic", - "is_active": true, - "sort_order": 2 - }, - { - "id": 3, - "code": "quick", - "name": { - "ko": "퀵서비스", - "en": "Quick Service", - "ja": "クイックサービス" - }, - "category": "domestic", - "is_active": true, - "sort_order": 3 - }, - { - "id": 4, - "code": "freight", - "name": { - "ko": "화물배송", - "en": "Freight", - "ja": "貨物配送" - }, - "category": "domestic", - "is_active": true, - "sort_order": 4 - }, - { - "id": 5, - "code": "pickup", - "name": { - "ko": "매장수령", - "en": "Store Pickup", - "ja": "店舗受取" - }, - "category": "domestic", - "is_active": true, - "sort_order": 5 - }, - { - "id": 6, - "code": "express", - "name": { - "ko": "국내특급", - "en": "Express", - "ja": "国内特急" - }, - "category": "domestic", - "is_active": false, - "sort_order": 6 - }, - { - "id": 7, - "code": "international_ems", - "name": { - "ko": "국제EMS", - "en": "International EMS", - "ja": "国際EMS" - }, - "category": "international", - "is_active": false, - "sort_order": 7 - }, - { - "id": 8, - "code": "international_standard", - "name": { - "ko": "국제일반", - "en": "International Standard", - "ja": "国際通常" - }, - "category": "international", - "is_active": false, - "sort_order": 8 - }, - { - "id": 9, - "code": "cvs", - "name": { - "ko": "편의점택배", - "en": "Convenience Store", - "ja": "コンビニ宅配便" - }, - "category": "other", - "is_active": false, - "sort_order": 9 - }, - { - "id": 10, - "code": "digital", - "name": { - "ko": "디지털상품", - "en": "Digital", - "ja": "デジタル商品" - }, - "category": "other", - "is_active": false, - "sort_order": 10 - }, - { - "id": 11, - "code": "custom", - "name": { - "ko": "직접입력", - "en": "Custom", - "ja": "直接入力" - }, - "category": "domestic", - "is_active": true, - "sort_order": 99 - } - ] - }, - "seo": { - "meta_category_title": "{commerce_name} - {category_name}", - "meta_category_description": "", - "meta_search_title": "{commerce_name} - {keyword_name}", - "meta_search_description": "", - "meta_product_title": "{commerce_name} - {product_name}", - "meta_product_description": "", - "meta_shop_index_title": "{commerce_name}", - "meta_shop_index_description": "", - "seo_category": true, - "seo_search_result": true, - "seo_product_detail": true, - "seo_shop_index": true - }, - "review_settings": { - "write_deadline_days": 90, - "max_images": 5, - "max_image_size_mb": 10 - }, - "inquiry": { - "board_slug": null - }, - "notifications": { - "channels": [ - { - "id": "mail", - "is_active": true, - "sort_order": 1 - }, - { - "id": "database", - "is_active": true, - "sort_order": 2 - } - ] - }, - "claim": { - "refund_reasons": [ - { - "id": 1, - "type": "refund", - "code": "order_mistake", - "name": { - "ko": "주문 실수", - "en": "Order Mistake", - "ja": "注文ミス" - }, - "localized_name": "주문 실수", - "fault_type": "customer", - "is_user_selectable": true, - "is_active": true, - "sort_order": 0 - }, - { - "id": 2, - "type": "refund", - "code": "changed_mind", - "name": { - "ko": "단순 변심", - "en": "Changed Mind", - "ja": "単純な心変わり" - }, - "localized_name": "단순 변심", - "fault_type": "customer", - "is_user_selectable": true, - "is_active": true, - "sort_order": 1 - }, - { - "id": 3, - "type": "refund", - "code": "reorder_other", - "name": { - "ko": "다른 상품으로 재주문", - "en": "Reorder with Different Product", - "ja": "別の商品で再注文" - }, - "localized_name": "다른 상품으로 재주문", - "fault_type": "customer", - "is_user_selectable": true, - "is_active": true, - "sort_order": 2 - }, - { - "id": 4, - "type": "refund", - "code": "delayed_delivery", - "name": { - "ko": "배송 지연", - "en": "Delayed Delivery", - "ja": "配送遅延" - }, - "localized_name": "배송 지연", - "fault_type": "seller", - "is_user_selectable": true, - "is_active": true, - "sort_order": 3 - }, - { - "id": 5, - "type": "refund", - "code": "product_info_different", - "name": { - "ko": "상품 정보 상이", - "en": "Product Info Different", - "ja": "商品情報の相違" - }, - "localized_name": "상품 정보 상이", - "fault_type": "seller", - "is_user_selectable": true, - "is_active": true, - "sort_order": 4 - }, - { - "id": 6, - "type": "refund", - "code": "admin_cancel", - "name": { - "ko": "관리자 취소", - "en": "Admin Cancel", - "ja": "管理者キャンセル" - }, - "localized_name": "관리자 취소", - "fault_type": "seller", - "is_user_selectable": false, - "is_active": true, - "sort_order": 5 - }, - { - "id": 7, - "type": "refund", - "code": "etc", - "name": { - "ko": "기타", - "en": "Etc", - "ja": "その他" - }, - "localized_name": "기타", - "fault_type": "customer", - "is_user_selectable": true, - "is_active": true, - "sort_order": 6 - } - ] - }, - "available_pg_providers": [ - { - "id": "kginicis", - "name": "KG 이니시스", - "icon": "credit-card", - "supported_methods": [ - "card", - "bank_transfer", - "virtual_account", - "mobile" - ] - } - ], - "abilities": { - "can_update": true - } - } - }, - "seoStats": { - "data": { - "count": 0, - "size_bytes": 0, - "size_formatted": "0 B" - } - }, - "copy_source": { - "data": { - "id": 102, - "name": { - "ko": "데일리 와이드 슬랙스", - "en": "Daily Wide Slacks" - }, - "product_code": "PT-2042", - "selling_price": 39000, - "thumbnail_url": "/images/placeholder.png", - "images": [ - { - "id": 5101, - "hash": "img5101", - "original_filename": "slacks_main.jpg", - "download_url": "/images/placeholder.png", - "alt_text": "정면", - "is_thumbnail": true, - "sort_order": 0, - "order": 0, - "width": 1000, - "height": 1000, - "file_size": 172000, - "size": 172000, - "size_formatted": "168 KB", - "mime_type": "image/jpeg", - "is_image": true - }, - { - "id": 5102, - "hash": "img5102", - "original_filename": "slacks_detail.jpg", - "download_url": "/images/placeholder.png", - "alt_text": "디테일", - "is_thumbnail": false, - "sort_order": 1, - "order": 1, - "width": 1000, - "height": 1000, - "file_size": 165000, - "size": 165000, - "size_formatted": "161 KB", - "mime_type": "image/jpeg", - "is_image": true - } - ] - } - }, - "refundReasons": { - "data": [ - { - "id": 1, - "reason": "단순 변심", - "code": "change_mind" - }, - { - "id": 2, - "reason": "상품 불량", - "code": "defective" - } - ] - }, - "availableChannels": { - "data": { - "channels": [ - { - "id": "mail", - "name": "이메일", - "icon": "fas fa-envelope", - "source": "core", - "source_label": "코어" - }, - { - "id": "sms", - "name": "SMS", - "icon": "fas fa-comment-sms", - "source": "core", - "source_label": "코어" - }, - { - "id": "push", - "name": "푸시", - "icon": "fas fa-bell", - "source": "core", - "source_label": "코어" - } - ] - } - }, - "identityProviders": { - "data": [ - { - "id": "kginicis", - "label": "KG이니시스" - }, - { - "id": "nice", - "label": "나이스" - } - ] - }, - "ecommerceIdentityPurposes": { - "data": [ - { - "id": "signup", - "label": "회원가입", - "source_identifier": "sirsoft-ecommerce", - "description": "회원가입 시 본인인증", - "allowed_channels": [ - "mobile" - ] - }, - { - "id": "sensitive_action", - "label": "민감 작업", - "source_identifier": "core", - "description": "민감 작업 시 본인인증", - "allowed_channels": [ - "mobile", - "ipin" - ] - } - ] - }, - "ecommerceIdentityPolicies": { - "data": { - "data": [ - { - "id": 1, - "key": "shop.signup", - "scope": "route", - "target": "/shop/signup", - "purpose": "signup", - "provider_id": "kginicis", - "grace_minutes": 0, - "enabled": true, - "source_type": "admin", - "abilities": { - "can_update": true, - "can_delete": true - } - } - ], - "meta": { - "total": 1, - "current_page": 1, - "last_page": 1, - "per_page": 20 - }, - "abilities": { - "can_create": true - } - } - }, - "ecommerceNotificationDefinitions": { - "data": { - "data": [ - { - "id": 1, - "type": "order.paid", - "is_active": true, - "is_default": true, - "variables": [ - { - "key": "order_no" - } - ], - "templates": [ - { - "id": 11, - "channel": "mail", - "is_active": true, - "is_default": true, - "subject": { - "ko": "결제 완료 안내", - "en": "Payment completed" - }, - "body": { - "ko": "

결제가 완료되었습니다.

", - "en": "

Payment completed.

" - }, - "recipients": [ - { - "type": "trigger_user" - } - ] - } - ] - } - ], - "pagination": { - "total": 1, - "current_page": 1, - "last_page": 1 - }, - "abilities": { - "can_update": true - } - } - }, - "roles": { - "data": { - "data": [ - { - "id": 1, - "identifier": "super_admin", - "name": "최고 관리자", - "display_name": "최고 관리자", - "description": "전체 권한", - "is_system": true - }, - { - "id": 2, - "identifier": "manager", - "name": "운영자", - "display_name": "운영자", - "description": "운영 권한", - "is_system": false - }, - { - "id": 3, - "identifier": "member", - "name": "일반 회원", - "display_name": "일반 회원", - "description": "기본 회원", - "is_system": true - } - ] - } - }, - "category": { - "data": { - "breadcrumb": [ - "샘플" - ], - "children": [ - "샘플" - ], - "description": "

샘플 설명입니다.

", - "description_localized": "샘플 description_localized", - "images": [ - "샘플" - ], - "name": "샘플 name", - "name_localized": "샘플 name_localized" - } - }, - "qna": { - "data": { - "items": [ - "샘플" - ], - "meta": { - "abilities": { - "can_delete": true, - "can_update": true - }, - "board_settings": "샘플", - "current_page": 3, - "last_page": 3, - "total": 10000 - } - } - }, - "productDownloadableCoupons": { - "data": { - "data": "샘플" - } - }, - "cartItems": { - "data": { - "calculation": { - "items": [ - "샘플" - ], - "summary": { - "base_shipping_total": 10000, - "code_discount": 10000, - "discount_formatted": "10,000원", - "extra_shipping_total": 10000, - "final_amount_formatted": "10,000원", - "mileage_formatted": "10,000원", - "multi_currency": "샘플", - "order_coupon_discount": 10000, - "payment_amount_formatted": "10,000원", - "points_earning": 10000, - "product_coupon_discount": 10000, - "shipping_discount": 10000, - "shipping_fee_formatted": "10,000원", - "subtotal_formatted": "10,000원", - "tax_free_amount": 10000, - "taxable_amount": 10000, - "total_discount": 10000, - "total_shipping": 10000 - } - }, - "item_count": 3, - "items": [ - "샘플" - ] - } - }, - "checkoutData": { - "data": { - "available_coupons": [ - "샘플" - ], - "calculation": { - "items": [ - "샘플" - ], - "promotions": { - "order_promotions": { - "coupons": [ - "샘플" - ] - }, - "product_promotions": { - "coupons": [ - "샘플" - ] - } - }, - "summary": { - "base_shipping_total": 10000, - "code_discount": 10000, - "code_discount_formatted": "10,000원", - "extra_shipping_total": 10000, - "final_amount": 10000, - "final_amount_formatted": "10,000원", - "mileage_formatted": "10,000원", - "multi_currency": "샘플", - "order_coupon_discount": 10000, - "order_coupon_discount_formatted": "10,000원", - "payment_amount_formatted": "10,000원", - "points_earning": 10000, - "points_used": 10000, - "points_used_formatted": "10,000원", - "product_coupon_discount": 10000, - "shipping_discount": 10000, - "shipping_discount_formatted": "10,000원", - "subtotal_formatted": "10,000원", - "tax_free_amount": 10000, - "taxable_amount": 10000, - "total_discount": 10000, - "total_discount_formatted": "10,000원", - "total_shipping": 10000, - "total_shipping_formatted": "10,000원" - } - }, - "items": [ - "샘플" - ], - "mileage": { - "balance": 10000, - "balance_formatted": "10,000원", - "max_usable": "샘플", - "max_usable_formatted": "10,000원" - }, - "temp_order_id": 1 - } - }, - "userAddresses": { - "data": { - "addresses": { - "data": "샘플" - } - } - }, - "paymentSettings": { - "data": { - "order_settings": { - "bank_accounts": [ - "샘플" - ], - "dbank_due_days": 3, - "payment_methods": "샘플", - "vbank_due_days": 3 - } - } - }, - "orderData": { - "data": { - "mc_subtotal_amount": 10000, - "mc_total_amount": 10000, - "mc_total_discount_amount": 10000, - "mc_total_shipping_amount": 10000, - "options": [ - "샘플" - ], - "order_number": 3, - "payment": { - "dbank_account": "샘플", - "dbank_holder": "샘플 dbank_holder", - "dbank_name": "샘플 dbank_name", - "deposit_due_at_formatted": "10,000원", - "depositor_name": "샘플 depositor_name", - "payment_method": "card", - "vbank_due_at": "2026-05-24 09:32:38", - "vbank_holder": "샘플 vbank_holder", - "vbank_name": "샘플 vbank_name", - "vbank_number": 3 - }, - "shipping_address": { - "address": "샘플 address", - "address_detail": "샘플 address_detail", - "city": "샘플", - "recipient_country_code": "KR", - "recipient_name": "샘플 recipient_name", - "recipient_phone": "010-1234-5678", - "state": "sample", - "zipcode": "sample" - }, - "subtotal_amount_formatted": "10,000원", - "total_amount_formatted": "10,000원", - "total_discount_amount": 10000, - "total_discount_amount_formatted": "10,000원", - "total_shipping_amount": 10000, - "total_shipping_amount_formatted": "10,000원" - } - }, - "wishlist": { - "data": { - "data": [ - "샘플" - ], - "pagination": { - "current_page": 3, - "last_page": 3, - "total": 10000 - } - } - }, - "addresses": { - "data": { - "addresses": { - "data": "샘플" - } - } - }, - "myInquiries": { - "data": { - "items": [ - "샘플" - ], - "meta": { - "board_settings": "샘플", - "current_page": 3, - "last_page": 3, - "total": 10000 - } - } - }, - "board_list_for_inquiry": { - "data": { - "data": [ - { - "id": 1, - "name": "공지사항", - "slug": "notice", - "is_active": true, - "type": "basic", - "description": "공지사항 게시판입니다.", - "per_page": 20, - "per_page_mobile": 15, - "order_by": "created_at", - "order_direction": "DESC", - "categories": [], - "show_view_count": true, - "secret_mode": "{MASKED}", - "use_comment": true, - "use_reply": true, - "max_reply_depth": 5, - "use_report": false, - "comment_order": "ASC", - "max_comment_depth": 10, - "min_title_length": 2, - "max_title_length": 200, - "min_content_length": 2, - "max_content_length": 10000, - "min_comment_length": 2, - "max_comment_length": 1000, - "blocked_keywords": [], - "use_file_upload": false, - "max_file_size": 10, - "max_file_count": 5, - "allowed_extensions": [ - "jpg", - "jpeg", - "png", - "gif", - "webp", - "pdf", - "doc", - "docx", - "xls", - "xlsx", - "ppt", - "pptx", - "hwp", - "txt", - "zip" - ], - "new_display_hours": 24, - "board_managers": [ - { - "uuid": "00000000-0000-4000-8000-000000000001", - "name": "관리자", - "email": "admin@example.com" - } - ], - "board_steps": [], - "board_manager_ids": [ - "00000000-0000-4000-8000-000000000001" - ], - "board_step_ids": [], - "notify_author": true, - "notify_admin_on_post": true, - "created_at": "2026-05-26 02:41:23", - "updated_at": "2026-05-26 02:41:23", - "permissions": null, - "category_post_counts": null, - "posts_count": 0, - "user_abilities": null, - "abilities": { - "can_create": true, - "can_update": true, - "can_delete": true - } - } - ], - "pagination": { - "current_page": 1, - "last_page": 1, - "per_page": 20, - "total": 1, - "from": 1, - "to": 1, - "has_more_pages": false - }, - "abilities": { - "can_create": true, - "can_update": true, - "can_delete": true - } - } - }, - "notifications": { - "channels": [ - { - "key": "email", - "label": "이메일", - "enabled": true - }, - { - "key": "push", - "label": "푸시", - "enabled": true - } - ], - "data": { - "data": [ - { - "id": 1, - "type": "comment", - "title": "샘플 알림", - "message": "샘플 알림 내용입니다.", - "is_read": false, - "created_at": "2026-05-24 09:32:38", - "link": "/" - } - ], - "pagination": { - "current_page": 1, - "last_page": 1, - "total": 1, - "has_more_pages": false - }, - "unread_count": 2 - } - }, - "mileage_balance": { - "data": { - "mileage": { - "available": 50000, - "pending": 10000, - "total_earned": 150000, - "expiring_soon": 5000, - "enabled": true - } - } - }, - "mileage_history": { - "data": { - "data": [ - { - "id": 1001, - "user_id": 100, - "currency": "KRW", - "type": "earn", - "type_label": "적립", - "admin_badge_group": null, - "user_display_category": "주문 완료", - "amount": 5000, - "amount_formatted": "5,000", - "remaining_amount": 5000, - "remaining_amount_formatted": "5,000", - "balance_after": 55000, - "order_id": null, - "order_option_id": null, - "order_cancel_id": null, - "source_transaction_id": null, - "granted_by": null, - "granted_by_name": null, - "granted_by_uuid": null, - "user_name": "홍길동", - "user_uuid": "u-1001", - "order_number": "20260601-0042", - "description": "Order reward", - "memo": null, - "expires_at": "2027-06-01T00:00:00+09:00", - "expires_at_formatted": "Jun 1, 2027 12:00 AM", - "expires_at_date": "2027-06-01", - "expired_at": null, - "expired_at_formatted": null, - "created_at": "2026-06-01T14:30:00+09:00", - "created_at_formatted": "Jun 1, 2026 2:30 PM", - "created_at_date": "2026-06-01", - "is_earning": true, - "can_edit_expiry": true, - "expired_amount": 0, - "expired_amount_formatted": "0", - "expiry_state": "active", - "abilities": { - "can_manage": false - } - }, - { - "id": 1002, - "user_id": 100, - "currency": "KRW", - "type": "use", - "type_label": "사용", - "admin_badge_group": null, - "user_display_category": "주문", - "amount": -3000, - "amount_formatted": "-3,000", - "remaining_amount": 0, - "remaining_amount_formatted": "0", - "balance_after": 52000, - "order_id": null, - "order_option_id": null, - "order_cancel_id": null, - "source_transaction_id": null, - "granted_by": null, - "granted_by_name": null, - "granted_by_uuid": null, - "user_name": "홍길동", - "user_uuid": "u-1001", - "order_number": "20260530-0118", - "description": null, - "memo": null, - "expires_at": null, - "expires_at_formatted": null, - "expires_at_date": null, - "expired_at": null, - "expired_at_formatted": null, - "created_at": "2026-05-30T13:05:00+09:00", - "created_at_formatted": "May 30, 2026 1:05 PM", - "created_at_date": "2026-05-30", - "is_earning": false, - "can_edit_expiry": false, - "expired_amount": 0, - "expired_amount_formatted": "0", - "expiry_state": "active", - "abilities": { - "can_manage": false - } - } - ], - "abilities": { - "can_manage": false - }, - "currencies": [ - "KRW" - ], - "pagination": { - "current_page": 1, - "last_page": 1, - "per_page": 20, - "total": 2, - "from": 1, - "to": 2, - "has_more_pages": false - } - } - }, - "reviewSettings": { - "data": { - "review_settings": { - "max_images": 5, - "max_image_size_mb": 10, - "write_deadline_days": 30 - } - } - } - }, - "byEndpointPattern": { - "/api/modules/sirsoft-ecommerce/products*": { - "data": { - "data": [ - { - "id": 1, - "name": "샘플 상품 1", - "price": 29000, - "sale_price": 24000, - "thumbnail": null, - "stock_status": "in_stock", - "rating": 4.5 - }, - { - "id": 2, - "name": "샘플 상품 2", - "price": 15000, - "sale_price": null, - "thumbnail": null, - "stock_status": "in_stock", - "rating": 4 - }, - { - "id": 3, - "name": "샘플 상품 3 (품절)", - "price": 42000, - "sale_price": null, - "thumbnail": null, - "stock_status": "out_of_stock", - "rating": 3.5 - } - ], - "meta": { - "total": 3, - "per_page": 20, - "current_page": 1 - } - } - }, - "/api/modules/sirsoft-ecommerce/cart*": { - "data": { - "items": [ - { - "product_id": 1, - "name": "샘플 상품 1", - "thumbnail": null, - "price": 24000, - "quantity": 2, - "options": "블랙" - }, - { - "product_id": 2, - "name": "샘플 상품 2", - "thumbnail": null, - "price": 15000, - "quantity": 1, - "options": null - } - ], - "total": 63000, - "discount": 0, - "shipping_fee": 3000 - } - }, - "/api/modules/sirsoft-ecommerce/checkout*": { - "data": { - "items": [ - { - "product_id": 1, - "name": "샘플 상품 1", - "price": 24000, - "quantity": 2 - } - ], - "addresses": [ - { - "id": 1, - "recipient": "샘플 수령인", - "address": "서울시 샘플구 샘플로 1", - "is_default": true - } - ], - "payment_methods": [ - { - "code": "card", - "name": "신용카드" - }, - { - "code": "vbank", - "name": "무통장입금" - } - ], - "coupons": [], - "points": 1250 - } - }, - "/api/modules/sirsoft-ecommerce/wishlist*": { - "data": { - "data": [ - { - "id": 1, - "name": "샘플 찜 상품 1", - "price": 29000, - "thumbnail": null, - "stock_status": "in_stock" - }, - { - "id": 2, - "name": "샘플 찜 상품 2", - "price": 15000, - "thumbnail": null, - "stock_status": "in_stock" - } - ], - "meta": { - "total": 2, - "per_page": 20, - "current_page": 1 - } - } - }, - "/api/modules/sirsoft-ecommerce/user/orders*": { - "data": { - "data": [ - { - "id": 1, - "order_no": "20260529-0001", - "created_at": "2026-05-29", - "status": "paid", - "total": 48000, - "first_item_name": "샘플 상품 1", - "item_count": 2 - } - ], - "meta": { - "total": 1, - "per_page": 20, - "current_page": 1 - } - } - }, - "/api/modules/sirsoft-ecommerce/user/addresses*": { - "data": [ - { - "id": 1, - "recipient": "샘플 수령인", - "phone": "010-0000-0000", - "zip": "00000", - "address": "서울시 샘플구 샘플로 1", - "detail": "101동 101호", - "is_default": true, - "label": "집" - } - ] - }, - "/api/modules/sirsoft-ecommerce/user/inquiries*": { - "data": { - "data": [ - { - "id": 1, - "product": { - "id": 1, - "name": "샘플 상품 1" - }, - "title": "배송 문의", - "body": "언제 배송되나요?", - "answer": "내일 발송 예정입니다.", - "created_at": "2026-05-25", - "status": "answered" - } - ], - "meta": { - "total": 1, - "per_page": 20, - "current_page": 1 - } - } - } - } - }, - "states": { - "comment": "이커머스 도메인 변종. 사용자 shop/* 라우트(템플릿 sirsoft-basic 소속이나 코어 concat 으로 일원화)와 관리자 라우트(*/admin/ecommerce/*) 변종을 모두 포함한다. shop/* scope.match 는 편집기가 동적 path 를 'shop' 으로 평가한 selectedRoute.path 와 일치한다(Chrome MCP 실측).", - "groups": [ - { - "comment": "shop/show — product.data.sales_status 값(on_sale/sold_out/suspended/coming_soon)으로 판매 상태 배지·버튼을 if 분기한다(본문 line 388/406). 재고는 stock 으로 분기.", - "scope": { - "kind": "route", - "match": "/shop/products/:id" - }, - "items": [ - { - "id": "in_stock", - "label": "$t:editor.state.in_stock", - "default": true - }, - { - "id": "out_of_stock", - "label": "$t:editor.state.out_of_stock", - "sampleDataOverrides": { - "byEndpointPattern": { - "/api/modules/sirsoft-ecommerce/products/*": { - "data": { - "id": 101, - "name": { - "ko": "베이직 오버핏 코튼 티셔츠", - "en": "Basic Overfit Cotton T-Shirt" - }, - "name_localized": "베이직 오버핏 코튼 티셔츠", - "product_code": "TS-1001", - "sku": "TS-1001-BLK", - "categories": [ - { - "id": 1, - "name": { - "ko": "패션의류", - "en": "Fashion" - }, - "name_localized": "패션의류", - "breadcrumb": "패션의류", - "path": "1", - "is_primary": false - }, - { - "id": 11, - "name": { - "ko": "상의", - "en": "Tops" - }, - "name_localized": "상의", - "breadcrumb": "패션의류 > 상의", - "path": "1/11", - "is_primary": true - } - ], - "category_name": "상의", - "list_price": 29000, - "list_price_formatted": "29,000원", - "selling_price": 23200, - "selling_price_formatted": "23,200원", - "discount_rate": 20, - "multi_currency_list_price": { - "KRW": { - "price": 29000, - "formatted": "29,000원", - "is_default": true, - "editable": true - }, - "USD": { - "price": 22.31, - "formatted": "$22.31", - "is_default": false, - "editable": false, - "exchange_rate": 1300 - } - }, - "multi_currency_selling_price": { - "KRW": { - "price": 23200, - "formatted": "23,200원", - "is_default": true, - "editable": true - }, - "USD": { - "price": 17.85, - "formatted": "$17.85", - "is_default": false, - "editable": false, - "exchange_rate": 1300 - } - }, - "stock_quantity": 0, - "min_purchase_qty": 1, - "max_purchase_qty": 10, - "sales_status": "on_sale", - "sales_status_label": "판매중", - "brand_name": "코지홈", - "labels": [ - { - "name": "BEST", - "color": "#ef4444" - }, - { - "name": "한정특가", - "color": "#f59e0b" - } - ], - "additional_options": [ - { - "id": 9001, - "name": "이니셜 자수 추가", - "is_required": false - }, - { - "id": 9002, - "name": "선물 포장", - "is_required": false - } - ], - "shipping_policy_id": 1, - "free_shipping": false, - "shipping_fee_formatted": "3,000원 (50,000원 이상 무료)", - "shipping_policy": { - "name": "기본 배송정책", - "charge_policy": "conditional_free", - "charge_policy_label": "조건부 무료", - "base_fee": 3000, - "base_fee_formatted": "3,000원", - "free_threshold": 50000, - "free_threshold_formatted": "50,000원", - "fee_summary": "3,000원 (50,000원 이상 무료배송)" - }, - "short_description": "사계절 입기 좋은 오버핏 기본 티셔츠", - "short_description_localized": "사계절 입기 좋은 오버핏 기본 티셔츠", - "description": { - "ko": "

부드러운 30수 코튼 소재로 제작된 데일리 오버핏 티셔츠입니다.

" - }, - "description_localized": "

제품 특징

부드러운 30수 코튼 100% 소재로 제작되어 사계절 내내 편안하게 착용할 수 있습니다. 오버핏 실루엣으로 체형 커버에 탁월하며, 어떤 하의와도 자연스럽게 매치됩니다.

  • 소재: 코튼 100%
  • 핏: 오버핏
  • 제조국: 대한민국

\"상세

", - "description_mode": "html", - "images": [ - { - "id": 5001, - "hash": "img5001", - "original_filename": "tshirt_main.jpg", - "download_url": "/images/placeholder.png", - "alt_text": "정면", - "alt_text_current": "베이직 오버핏 티셔츠 정면", - "is_thumbnail": true, - "sort_order": 0, - "order": 0, - "width": 1000, - "height": 1000, - "file_size": 184320, - "size": 184320, - "size_formatted": "180 KB", - "mime_type": "image/jpeg", - "is_image": true - }, - { - "id": 5002, - "hash": "img5002", - "original_filename": "tshirt_back.jpg", - "download_url": "/images/placeholder.png", - "alt_text": "후면", - "alt_text_current": "베이직 오버핏 티셔츠 후면", - "is_thumbnail": false, - "sort_order": 1, - "order": 1, - "width": 1000, - "height": 1000, - "file_size": 176128, - "size": 176128, - "size_formatted": "172 KB", - "mime_type": "image/jpeg", - "is_image": true - }, - { - "id": 5003, - "hash": "img5003", - "original_filename": "tshirt_detail.jpg", - "download_url": "/images/placeholder.png", - "alt_text": "디테일", - "alt_text_current": "소매 디테일 컷", - "is_thumbnail": false, - "sort_order": 2, - "order": 2, - "width": 1000, - "height": 1000, - "file_size": 158720, - "size": 158720, - "size_formatted": "155 KB", - "mime_type": "image/jpeg", - "is_image": true - } - ], - "thumbnail_url": "/images/placeholder.png", - "meta_title": "베이직 오버핏 코튼 티셔츠 | 코지홈", - "meta_description": "사계절 입기 좋은 오버핏 기본 티셔츠", - "meta_keywords": [ - "티셔츠", - "오버핏", - "코튼" - ], - "has_options": true, - "option_groups": [ - { - "name": { - "ko": "색상", - "en": "Color" - }, - "name_localized": "색상", - "values": [ - { - "ko": "블랙", - "en": "Black" - }, - { - "ko": "화이트", - "en": "White" - }, - { - "ko": "네이비", - "en": "Navy" - } - ], - "values_localized": [ - "블랙", - "화이트", - "네이비" - ] - }, - { - "name": { - "ko": "사이즈", - "en": "Size" - }, - "name_localized": "사이즈", - "values": [ - { - "ko": "S", - "en": "S" - }, - { - "ko": "M", - "en": "M" - }, - { - "ko": "L", - "en": "L" - } - ], - "values_localized": [ - "S", - "M", - "L" - ] - } - ], - "options": [ - { - "id": 7001, - "option_code": "OPT-BLK-S", - "option_values": [ - { - "key": { - "ko": "색상" - }, - "value": { - "ko": "블랙" - } - }, - { - "key": { - "ko": "사이즈" - }, - "value": { - "ko": "S" - } - } - ], - "option_values_localized": [ - "블랙", - "S" - ], - "option_name": { - "ko": "블랙 / S" - }, - "option_name_localized": "블랙 / S", - "price_adjustment": 0, - "price_adjustment_formatted": "+0원", - "price_adjustment_type": "increase", - "list_price": 29000, - "list_price_formatted": "29,000원", - "selling_price": 23200, - "selling_price_formatted": "23,200원", - "final_price": 23200, - "final_price_formatted": "23,200원", - "multi_currency_list_price": { - "KRW": { - "price": 29000, - "formatted": "29,000원", - "is_default": true, - "editable": true - }, - "USD": { - "price": 22.31, - "formatted": "$22.31", - "is_default": false, - "editable": false, - "exchange_rate": 1300 - } - }, - "multi_currency_selling_price": { - "KRW": { - "price": 23200, - "formatted": "23,200원", - "is_default": true, - "editable": true - }, - "USD": { - "price": 17.85, - "formatted": "$17.85", - "is_default": false, - "editable": false, - "exchange_rate": 1300 - } - }, - "stock_quantity": 0, - "safe_stock_quantity": 10, - "is_below_safe_stock": true, - "is_default": true, - "is_active": true, - "sku": "TS-1001-BLK-S", - "sort_order": 0 - }, - { - "id": 7002, - "option_code": "OPT-BLK-M", - "option_values": [ - { - "key": { - "ko": "색상" - }, - "value": { - "ko": "블랙" - } - }, - { - "key": { - "ko": "사이즈" - }, - "value": { - "ko": "M" - } - } - ], - "option_values_localized": [ - "블랙", - "M" - ], - "option_name": { - "ko": "블랙 / M" - }, - "option_name_localized": "블랙 / M", - "price_adjustment": 0, - "price_adjustment_formatted": "+0원", - "price_adjustment_type": "increase", - "list_price": 29000, - "list_price_formatted": "29,000원", - "selling_price": 23200, - "selling_price_formatted": "23,200원", - "final_price": 23200, - "final_price_formatted": "23,200원", - "multi_currency_list_price": { - "KRW": { - "price": 29000, - "formatted": "29,000원", - "is_default": true, - "editable": true - }, - "USD": { - "price": 22.31, - "formatted": "$22.31", - "is_default": false, - "editable": false, - "exchange_rate": 1300 - } - }, - "multi_currency_selling_price": { - "KRW": { - "price": 23200, - "formatted": "23,200원", - "is_default": true, - "editable": true - }, - "USD": { - "price": 17.85, - "formatted": "$17.85", - "is_default": false, - "editable": false, - "exchange_rate": 1300 - } - }, - "stock_quantity": 0, - "safe_stock_quantity": 10, - "is_below_safe_stock": true, - "is_default": false, - "is_active": true, - "sku": "TS-1001-BLK-M", - "sort_order": 1 - }, - { - "id": 7003, - "option_code": "OPT-WHT-L", - "option_values": [ - { - "key": { - "ko": "색상" - }, - "value": { - "ko": "화이트" - } - }, - { - "key": { - "ko": "사이즈" - }, - "value": { - "ko": "L" - } - } - ], - "option_values_localized": [ - "화이트", - "L" - ], - "option_name": { - "ko": "화이트 / L" - }, - "option_name_localized": "화이트 / L", - "price_adjustment": 2000, - "price_adjustment_formatted": "+2,000원", - "price_adjustment_type": "increase", - "list_price": 31000, - "list_price_formatted": "31,000원", - "selling_price": 25200, - "selling_price_formatted": "25,200원", - "final_price": 25200, - "final_price_formatted": "25,200원", - "multi_currency_list_price": { - "KRW": { - "price": 31000, - "formatted": "31,000원", - "is_default": true, - "editable": true - }, - "USD": { - "price": 23.85, - "formatted": "$23.85", - "is_default": false, - "editable": false, - "exchange_rate": 1300 - } - }, - "multi_currency_selling_price": { - "KRW": { - "price": 25200, - "formatted": "25,200원", - "is_default": true, - "editable": true - }, - "USD": { - "price": 19.38, - "formatted": "$19.38", - "is_default": false, - "editable": false, - "exchange_rate": 1300 - } - }, - "stock_quantity": 0, - "safe_stock_quantity": 10, - "is_below_safe_stock": true, - "is_default": false, - "is_active": true, - "sku": "TS-1001-WHT-L", - "sort_order": 2 - } - ], - "notice": { - "template_name": "의류 상품정보제공고시", - "values": [ - { - "label": "제품 소재", - "value": "면 100%" - }, - { - "label": "색상", - "value": "블랙, 화이트, 네이비" - }, - { - "label": "치수", - "value": "S, M, L (상세 사이즈표 참고)" - }, - { - "label": "제조자", - "value": "코지홈" - }, - { - "label": "제조국", - "value": "대한민국" - }, - { - "label": "취급 시 주의사항", - "value": "찬물 단독 손세탁 권장" - }, - { - "label": "품질보증기준", - "value": "관련 법 및 소비자분쟁해결기준에 따름" - }, - { - "label": "A/S 책임자와 전화번호", - "value": "코지홈 고객센터 1588-0000" - } - ] - }, - "common_info": { - "name": "배송/교환/환불 안내", - "content": "

주문 후 영업일 기준 1~2일 내 출고됩니다. 단순 변심 교환·반품은 수령 후 7일 이내 가능하며, 왕복 배송비가 부과됩니다.

", - "content_mode": "html" - }, - "is_wishlisted": false - } - } - } - } - }, - { - "id": "sold_out", - "label": "$t:editor.state.sold_out", - "sampleDataOverrides": { - "byEndpointPattern": { - "/api/modules/sirsoft-ecommerce/products/*": { - "data": { - "id": 101, - "name": { - "ko": "베이직 오버핏 코튼 티셔츠", - "en": "Basic Overfit Cotton T-Shirt" - }, - "name_localized": "베이직 오버핏 코튼 티셔츠", - "product_code": "TS-1001", - "sku": "TS-1001-BLK", - "categories": [ - { - "id": 1, - "name": { - "ko": "패션의류", - "en": "Fashion" - }, - "name_localized": "패션의류", - "breadcrumb": "패션의류", - "path": "1", - "is_primary": false - }, - { - "id": 11, - "name": { - "ko": "상의", - "en": "Tops" - }, - "name_localized": "상의", - "breadcrumb": "패션의류 > 상의", - "path": "1/11", - "is_primary": true - } - ], - "category_name": "상의", - "list_price": 29000, - "list_price_formatted": "29,000원", - "selling_price": 23200, - "selling_price_formatted": "23,200원", - "discount_rate": 20, - "multi_currency_list_price": { - "KRW": { - "price": 29000, - "formatted": "29,000원", - "is_default": true, - "editable": true - }, - "USD": { - "price": 22.31, - "formatted": "$22.31", - "is_default": false, - "editable": false, - "exchange_rate": 1300 - } - }, - "multi_currency_selling_price": { - "KRW": { - "price": 23200, - "formatted": "23,200원", - "is_default": true, - "editable": true - }, - "USD": { - "price": 17.85, - "formatted": "$17.85", - "is_default": false, - "editable": false, - "exchange_rate": 1300 - } - }, - "stock_quantity": 0, - "min_purchase_qty": 1, - "max_purchase_qty": 10, - "sales_status": "sold_out", - "sales_status_label": "품절", - "brand_name": "코지홈", - "labels": [ - { - "name": "BEST", - "color": "#ef4444" - }, - { - "name": "한정특가", - "color": "#f59e0b" - } - ], - "additional_options": [ - { - "id": 9001, - "name": "이니셜 자수 추가", - "is_required": false - }, - { - "id": 9002, - "name": "선물 포장", - "is_required": false - } - ], - "shipping_policy_id": 1, - "free_shipping": false, - "shipping_fee_formatted": "3,000원 (50,000원 이상 무료)", - "shipping_policy": { - "name": "기본 배송정책", - "charge_policy": "conditional_free", - "charge_policy_label": "조건부 무료", - "base_fee": 3000, - "base_fee_formatted": "3,000원", - "free_threshold": 50000, - "free_threshold_formatted": "50,000원", - "fee_summary": "3,000원 (50,000원 이상 무료배송)" - }, - "short_description": "사계절 입기 좋은 오버핏 기본 티셔츠", - "short_description_localized": "사계절 입기 좋은 오버핏 기본 티셔츠", - "description": { - "ko": "

부드러운 30수 코튼 소재로 제작된 데일리 오버핏 티셔츠입니다.

" - }, - "description_localized": "

제품 특징

부드러운 30수 코튼 100% 소재로 제작되어 사계절 내내 편안하게 착용할 수 있습니다. 오버핏 실루엣으로 체형 커버에 탁월하며, 어떤 하의와도 자연스럽게 매치됩니다.

  • 소재: 코튼 100%
  • 핏: 오버핏
  • 제조국: 대한민국

\"상세

", - "description_mode": "html", - "images": [ - { - "id": 5001, - "hash": "img5001", - "original_filename": "tshirt_main.jpg", - "download_url": "/images/placeholder.png", - "alt_text": "정면", - "alt_text_current": "베이직 오버핏 티셔츠 정면", - "is_thumbnail": true, - "sort_order": 0, - "order": 0, - "width": 1000, - "height": 1000, - "file_size": 184320, - "size": 184320, - "size_formatted": "180 KB", - "mime_type": "image/jpeg", - "is_image": true - }, - { - "id": 5002, - "hash": "img5002", - "original_filename": "tshirt_back.jpg", - "download_url": "/images/placeholder.png", - "alt_text": "후면", - "alt_text_current": "베이직 오버핏 티셔츠 후면", - "is_thumbnail": false, - "sort_order": 1, - "order": 1, - "width": 1000, - "height": 1000, - "file_size": 176128, - "size": 176128, - "size_formatted": "172 KB", - "mime_type": "image/jpeg", - "is_image": true - }, - { - "id": 5003, - "hash": "img5003", - "original_filename": "tshirt_detail.jpg", - "download_url": "/images/placeholder.png", - "alt_text": "디테일", - "alt_text_current": "소매 디테일 컷", - "is_thumbnail": false, - "sort_order": 2, - "order": 2, - "width": 1000, - "height": 1000, - "file_size": 158720, - "size": 158720, - "size_formatted": "155 KB", - "mime_type": "image/jpeg", - "is_image": true - } - ], - "thumbnail_url": "/images/placeholder.png", - "meta_title": "베이직 오버핏 코튼 티셔츠 | 코지홈", - "meta_description": "사계절 입기 좋은 오버핏 기본 티셔츠", - "meta_keywords": [ - "티셔츠", - "오버핏", - "코튼" - ], - "has_options": true, - "option_groups": [ - { - "name": { - "ko": "색상", - "en": "Color" - }, - "name_localized": "색상", - "values": [ - { - "ko": "블랙", - "en": "Black" - }, - { - "ko": "화이트", - "en": "White" - }, - { - "ko": "네이비", - "en": "Navy" - } - ], - "values_localized": [ - "블랙", - "화이트", - "네이비" - ] - }, - { - "name": { - "ko": "사이즈", - "en": "Size" - }, - "name_localized": "사이즈", - "values": [ - { - "ko": "S", - "en": "S" - }, - { - "ko": "M", - "en": "M" - }, - { - "ko": "L", - "en": "L" - } - ], - "values_localized": [ - "S", - "M", - "L" - ] - } - ], - "options": [ - { - "id": 7001, - "option_code": "OPT-BLK-S", - "option_values": [ - { - "key": { - "ko": "색상" - }, - "value": { - "ko": "블랙" - } - }, - { - "key": { - "ko": "사이즈" - }, - "value": { - "ko": "S" - } - } - ], - "option_values_localized": [ - "블랙", - "S" - ], - "option_name": { - "ko": "블랙 / S" - }, - "option_name_localized": "블랙 / S", - "price_adjustment": 0, - "price_adjustment_formatted": "+0원", - "price_adjustment_type": "increase", - "list_price": 29000, - "list_price_formatted": "29,000원", - "selling_price": 23200, - "selling_price_formatted": "23,200원", - "final_price": 23200, - "final_price_formatted": "23,200원", - "multi_currency_list_price": { - "KRW": { - "price": 29000, - "formatted": "29,000원", - "is_default": true, - "editable": true - }, - "USD": { - "price": 22.31, - "formatted": "$22.31", - "is_default": false, - "editable": false, - "exchange_rate": 1300 - } - }, - "multi_currency_selling_price": { - "KRW": { - "price": 23200, - "formatted": "23,200원", - "is_default": true, - "editable": true - }, - "USD": { - "price": 17.85, - "formatted": "$17.85", - "is_default": false, - "editable": false, - "exchange_rate": 1300 - } - }, - "stock_quantity": 0, - "safe_stock_quantity": 10, - "is_below_safe_stock": true, - "is_default": true, - "is_active": true, - "sku": "TS-1001-BLK-S", - "sort_order": 0 - }, - { - "id": 7002, - "option_code": "OPT-BLK-M", - "option_values": [ - { - "key": { - "ko": "색상" - }, - "value": { - "ko": "블랙" - } - }, - { - "key": { - "ko": "사이즈" - }, - "value": { - "ko": "M" - } - } - ], - "option_values_localized": [ - "블랙", - "M" - ], - "option_name": { - "ko": "블랙 / M" - }, - "option_name_localized": "블랙 / M", - "price_adjustment": 0, - "price_adjustment_formatted": "+0원", - "price_adjustment_type": "increase", - "list_price": 29000, - "list_price_formatted": "29,000원", - "selling_price": 23200, - "selling_price_formatted": "23,200원", - "final_price": 23200, - "final_price_formatted": "23,200원", - "multi_currency_list_price": { - "KRW": { - "price": 29000, - "formatted": "29,000원", - "is_default": true, - "editable": true - }, - "USD": { - "price": 22.31, - "formatted": "$22.31", - "is_default": false, - "editable": false, - "exchange_rate": 1300 - } - }, - "multi_currency_selling_price": { - "KRW": { - "price": 23200, - "formatted": "23,200원", - "is_default": true, - "editable": true - }, - "USD": { - "price": 17.85, - "formatted": "$17.85", - "is_default": false, - "editable": false, - "exchange_rate": 1300 - } - }, - "stock_quantity": 0, - "safe_stock_quantity": 10, - "is_below_safe_stock": true, - "is_default": false, - "is_active": true, - "sku": "TS-1001-BLK-M", - "sort_order": 1 - }, - { - "id": 7003, - "option_code": "OPT-WHT-L", - "option_values": [ - { - "key": { - "ko": "색상" - }, - "value": { - "ko": "화이트" - } - }, - { - "key": { - "ko": "사이즈" - }, - "value": { - "ko": "L" - } - } - ], - "option_values_localized": [ - "화이트", - "L" - ], - "option_name": { - "ko": "화이트 / L" - }, - "option_name_localized": "화이트 / L", - "price_adjustment": 2000, - "price_adjustment_formatted": "+2,000원", - "price_adjustment_type": "increase", - "list_price": 31000, - "list_price_formatted": "31,000원", - "selling_price": 25200, - "selling_price_formatted": "25,200원", - "final_price": 25200, - "final_price_formatted": "25,200원", - "multi_currency_list_price": { - "KRW": { - "price": 31000, - "formatted": "31,000원", - "is_default": true, - "editable": true - }, - "USD": { - "price": 23.85, - "formatted": "$23.85", - "is_default": false, - "editable": false, - "exchange_rate": 1300 - } - }, - "multi_currency_selling_price": { - "KRW": { - "price": 25200, - "formatted": "25,200원", - "is_default": true, - "editable": true - }, - "USD": { - "price": 19.38, - "formatted": "$19.38", - "is_default": false, - "editable": false, - "exchange_rate": 1300 - } - }, - "stock_quantity": 0, - "safe_stock_quantity": 10, - "is_below_safe_stock": true, - "is_default": false, - "is_active": true, - "sku": "TS-1001-WHT-L", - "sort_order": 2 - } - ], - "notice": { - "template_name": "의류 상품정보제공고시", - "values": [ - { - "label": "제품 소재", - "value": "면 100%" - }, - { - "label": "색상", - "value": "블랙, 화이트, 네이비" - }, - { - "label": "치수", - "value": "S, M, L (상세 사이즈표 참고)" - }, - { - "label": "제조자", - "value": "코지홈" - }, - { - "label": "제조국", - "value": "대한민국" - }, - { - "label": "취급 시 주의사항", - "value": "찬물 단독 손세탁 권장" - }, - { - "label": "품질보증기준", - "value": "관련 법 및 소비자분쟁해결기준에 따름" - }, - { - "label": "A/S 책임자와 전화번호", - "value": "코지홈 고객센터 1588-0000" - } - ] - }, - "common_info": { - "name": "배송/교환/환불 안내", - "content": "

주문 후 영업일 기준 1~2일 내 출고됩니다. 단순 변심 교환·반품은 수령 후 7일 이내 가능하며, 왕복 배송비가 부과됩니다.

", - "content_mode": "html" - }, - "is_wishlisted": false - } - } - } - } - }, - { - "id": "suspended", - "label": "$t:editor.state.suspended", - "sampleDataOverrides": { - "byEndpointPattern": { - "/api/modules/sirsoft-ecommerce/products/*": { - "data": { - "id": 101, - "name": { - "ko": "베이직 오버핏 코튼 티셔츠", - "en": "Basic Overfit Cotton T-Shirt" - }, - "name_localized": "베이직 오버핏 코튼 티셔츠", - "product_code": "TS-1001", - "sku": "TS-1001-BLK", - "categories": [ - { - "id": 1, - "name": { - "ko": "패션의류", - "en": "Fashion" - }, - "name_localized": "패션의류", - "breadcrumb": "패션의류", - "path": "1", - "is_primary": false - }, - { - "id": 11, - "name": { - "ko": "상의", - "en": "Tops" - }, - "name_localized": "상의", - "breadcrumb": "패션의류 > 상의", - "path": "1/11", - "is_primary": true - } - ], - "category_name": "상의", - "list_price": 29000, - "list_price_formatted": "29,000원", - "selling_price": 23200, - "selling_price_formatted": "23,200원", - "discount_rate": 20, - "multi_currency_list_price": { - "KRW": { - "price": 29000, - "formatted": "29,000원", - "is_default": true, - "editable": true - }, - "USD": { - "price": 22.31, - "formatted": "$22.31", - "is_default": false, - "editable": false, - "exchange_rate": 1300 - } - }, - "multi_currency_selling_price": { - "KRW": { - "price": 23200, - "formatted": "23,200원", - "is_default": true, - "editable": true - }, - "USD": { - "price": 17.85, - "formatted": "$17.85", - "is_default": false, - "editable": false, - "exchange_rate": 1300 - } - }, - "stock_quantity": 320, - "min_purchase_qty": 1, - "max_purchase_qty": 10, - "sales_status": "suspended", - "sales_status_label": "판매중지", - "brand_name": "코지홈", - "labels": [ - { - "name": "BEST", - "color": "#ef4444" - }, - { - "name": "한정특가", - "color": "#f59e0b" - } - ], - "additional_options": [ - { - "id": 9001, - "name": "이니셜 자수 추가", - "is_required": false - }, - { - "id": 9002, - "name": "선물 포장", - "is_required": false - } - ], - "shipping_policy_id": 1, - "free_shipping": false, - "shipping_fee_formatted": "3,000원 (50,000원 이상 무료)", - "shipping_policy": { - "name": "기본 배송정책", - "charge_policy": "conditional_free", - "charge_policy_label": "조건부 무료", - "base_fee": 3000, - "base_fee_formatted": "3,000원", - "free_threshold": 50000, - "free_threshold_formatted": "50,000원", - "fee_summary": "3,000원 (50,000원 이상 무료배송)" - }, - "short_description": "사계절 입기 좋은 오버핏 기본 티셔츠", - "short_description_localized": "사계절 입기 좋은 오버핏 기본 티셔츠", - "description": { - "ko": "

부드러운 30수 코튼 소재로 제작된 데일리 오버핏 티셔츠입니다.

" - }, - "description_localized": "

제품 특징

부드러운 30수 코튼 100% 소재로 제작되어 사계절 내내 편안하게 착용할 수 있습니다. 오버핏 실루엣으로 체형 커버에 탁월하며, 어떤 하의와도 자연스럽게 매치됩니다.

  • 소재: 코튼 100%
  • 핏: 오버핏
  • 제조국: 대한민국

\"상세

", - "description_mode": "html", - "images": [ - { - "id": 5001, - "hash": "img5001", - "original_filename": "tshirt_main.jpg", - "download_url": "/images/placeholder.png", - "alt_text": "정면", - "alt_text_current": "베이직 오버핏 티셔츠 정면", - "is_thumbnail": true, - "sort_order": 0, - "order": 0, - "width": 1000, - "height": 1000, - "file_size": 184320, - "size": 184320, - "size_formatted": "180 KB", - "mime_type": "image/jpeg", - "is_image": true - }, - { - "id": 5002, - "hash": "img5002", - "original_filename": "tshirt_back.jpg", - "download_url": "/images/placeholder.png", - "alt_text": "후면", - "alt_text_current": "베이직 오버핏 티셔츠 후면", - "is_thumbnail": false, - "sort_order": 1, - "order": 1, - "width": 1000, - "height": 1000, - "file_size": 176128, - "size": 176128, - "size_formatted": "172 KB", - "mime_type": "image/jpeg", - "is_image": true - }, - { - "id": 5003, - "hash": "img5003", - "original_filename": "tshirt_detail.jpg", - "download_url": "/images/placeholder.png", - "alt_text": "디테일", - "alt_text_current": "소매 디테일 컷", - "is_thumbnail": false, - "sort_order": 2, - "order": 2, - "width": 1000, - "height": 1000, - "file_size": 158720, - "size": 158720, - "size_formatted": "155 KB", - "mime_type": "image/jpeg", - "is_image": true - } - ], - "thumbnail_url": "/images/placeholder.png", - "meta_title": "베이직 오버핏 코튼 티셔츠 | 코지홈", - "meta_description": "사계절 입기 좋은 오버핏 기본 티셔츠", - "meta_keywords": [ - "티셔츠", - "오버핏", - "코튼" - ], - "has_options": true, - "option_groups": [ - { - "name": { - "ko": "색상", - "en": "Color" - }, - "name_localized": "색상", - "values": [ - { - "ko": "블랙", - "en": "Black" - }, - { - "ko": "화이트", - "en": "White" - }, - { - "ko": "네이비", - "en": "Navy" - } - ], - "values_localized": [ - "블랙", - "화이트", - "네이비" - ] - }, - { - "name": { - "ko": "사이즈", - "en": "Size" - }, - "name_localized": "사이즈", - "values": [ - { - "ko": "S", - "en": "S" - }, - { - "ko": "M", - "en": "M" - }, - { - "ko": "L", - "en": "L" - } - ], - "values_localized": [ - "S", - "M", - "L" - ] - } - ], - "options": [ - { - "id": 7001, - "option_code": "OPT-BLK-S", - "option_values": [ - { - "key": { - "ko": "색상" - }, - "value": { - "ko": "블랙" - } - }, - { - "key": { - "ko": "사이즈" - }, - "value": { - "ko": "S" - } - } - ], - "option_values_localized": [ - "블랙", - "S" - ], - "option_name": { - "ko": "블랙 / S" - }, - "option_name_localized": "블랙 / S", - "price_adjustment": 0, - "price_adjustment_formatted": "+0원", - "price_adjustment_type": "increase", - "list_price": 29000, - "list_price_formatted": "29,000원", - "selling_price": 23200, - "selling_price_formatted": "23,200원", - "final_price": 23200, - "final_price_formatted": "23,200원", - "multi_currency_list_price": { - "KRW": { - "price": 29000, - "formatted": "29,000원", - "is_default": true, - "editable": true - }, - "USD": { - "price": 22.31, - "formatted": "$22.31", - "is_default": false, - "editable": false, - "exchange_rate": 1300 - } - }, - "multi_currency_selling_price": { - "KRW": { - "price": 23200, - "formatted": "23,200원", - "is_default": true, - "editable": true - }, - "USD": { - "price": 17.85, - "formatted": "$17.85", - "is_default": false, - "editable": false, - "exchange_rate": 1300 - } - }, - "stock_quantity": 80, - "safe_stock_quantity": 10, - "is_below_safe_stock": false, - "is_default": true, - "is_active": true, - "sku": "TS-1001-BLK-S", - "sort_order": 0 - }, - { - "id": 7002, - "option_code": "OPT-BLK-M", - "option_values": [ - { - "key": { - "ko": "색상" - }, - "value": { - "ko": "블랙" - } - }, - { - "key": { - "ko": "사이즈" - }, - "value": { - "ko": "M" - } - } - ], - "option_values_localized": [ - "블랙", - "M" - ], - "option_name": { - "ko": "블랙 / M" - }, - "option_name_localized": "블랙 / M", - "price_adjustment": 0, - "price_adjustment_formatted": "+0원", - "price_adjustment_type": "increase", - "list_price": 29000, - "list_price_formatted": "29,000원", - "selling_price": 23200, - "selling_price_formatted": "23,200원", - "final_price": 23200, - "final_price_formatted": "23,200원", - "multi_currency_list_price": { - "KRW": { - "price": 29000, - "formatted": "29,000원", - "is_default": true, - "editable": true - }, - "USD": { - "price": 22.31, - "formatted": "$22.31", - "is_default": false, - "editable": false, - "exchange_rate": 1300 - } - }, - "multi_currency_selling_price": { - "KRW": { - "price": 23200, - "formatted": "23,200원", - "is_default": true, - "editable": true - }, - "USD": { - "price": 17.85, - "formatted": "$17.85", - "is_default": false, - "editable": false, - "exchange_rate": 1300 - } - }, - "stock_quantity": 120, - "safe_stock_quantity": 10, - "is_below_safe_stock": false, - "is_default": false, - "is_active": true, - "sku": "TS-1001-BLK-M", - "sort_order": 1 - }, - { - "id": 7003, - "option_code": "OPT-WHT-L", - "option_values": [ - { - "key": { - "ko": "색상" - }, - "value": { - "ko": "화이트" - } - }, - { - "key": { - "ko": "사이즈" - }, - "value": { - "ko": "L" - } - } - ], - "option_values_localized": [ - "화이트", - "L" - ], - "option_name": { - "ko": "화이트 / L" - }, - "option_name_localized": "화이트 / L", - "price_adjustment": 2000, - "price_adjustment_formatted": "+2,000원", - "price_adjustment_type": "increase", - "list_price": 31000, - "list_price_formatted": "31,000원", - "selling_price": 25200, - "selling_price_formatted": "25,200원", - "final_price": 25200, - "final_price_formatted": "25,200원", - "multi_currency_list_price": { - "KRW": { - "price": 31000, - "formatted": "31,000원", - "is_default": true, - "editable": true - }, - "USD": { - "price": 23.85, - "formatted": "$23.85", - "is_default": false, - "editable": false, - "exchange_rate": 1300 - } - }, - "multi_currency_selling_price": { - "KRW": { - "price": 25200, - "formatted": "25,200원", - "is_default": true, - "editable": true - }, - "USD": { - "price": 19.38, - "formatted": "$19.38", - "is_default": false, - "editable": false, - "exchange_rate": 1300 - } - }, - "stock_quantity": 5, - "safe_stock_quantity": 10, - "is_below_safe_stock": true, - "is_default": false, - "is_active": true, - "sku": "TS-1001-WHT-L", - "sort_order": 2 - } - ], - "notice": { - "template_name": "의류 상품정보제공고시", - "values": [ - { - "label": "제품 소재", - "value": "면 100%" - }, - { - "label": "색상", - "value": "블랙, 화이트, 네이비" - }, - { - "label": "치수", - "value": "S, M, L (상세 사이즈표 참고)" - }, - { - "label": "제조자", - "value": "코지홈" - }, - { - "label": "제조국", - "value": "대한민국" - }, - { - "label": "취급 시 주의사항", - "value": "찬물 단독 손세탁 권장" - }, - { - "label": "품질보증기준", - "value": "관련 법 및 소비자분쟁해결기준에 따름" - }, - { - "label": "A/S 책임자와 전화번호", - "value": "코지홈 고객센터 1588-0000" - } - ] - }, - "common_info": { - "name": "배송/교환/환불 안내", - "content": "

주문 후 영업일 기준 1~2일 내 출고됩니다. 단순 변심 교환·반품은 수령 후 7일 이내 가능하며, 왕복 배송비가 부과됩니다.

", - "content_mode": "html" - }, - "is_wishlisted": false - } - } - } - } - }, - { - "id": "coming_soon", - "label": "$t:editor.state.coming_soon", - "sampleDataOverrides": { - "byEndpointPattern": { - "/api/modules/sirsoft-ecommerce/products/*": { - "data": { - "id": 101, - "name": { - "ko": "베이직 오버핏 코튼 티셔츠", - "en": "Basic Overfit Cotton T-Shirt" - }, - "name_localized": "베이직 오버핏 코튼 티셔츠", - "product_code": "TS-1001", - "sku": "TS-1001-BLK", - "categories": [ - { - "id": 1, - "name": { - "ko": "패션의류", - "en": "Fashion" - }, - "name_localized": "패션의류", - "breadcrumb": "패션의류", - "path": "1", - "is_primary": false - }, - { - "id": 11, - "name": { - "ko": "상의", - "en": "Tops" - }, - "name_localized": "상의", - "breadcrumb": "패션의류 > 상의", - "path": "1/11", - "is_primary": true - } - ], - "category_name": "상의", - "list_price": 29000, - "list_price_formatted": "29,000원", - "selling_price": 23200, - "selling_price_formatted": "23,200원", - "discount_rate": 20, - "multi_currency_list_price": { - "KRW": { - "price": 29000, - "formatted": "29,000원", - "is_default": true, - "editable": true - }, - "USD": { - "price": 22.31, - "formatted": "$22.31", - "is_default": false, - "editable": false, - "exchange_rate": 1300 - } - }, - "multi_currency_selling_price": { - "KRW": { - "price": 23200, - "formatted": "23,200원", - "is_default": true, - "editable": true - }, - "USD": { - "price": 17.85, - "formatted": "$17.85", - "is_default": false, - "editable": false, - "exchange_rate": 1300 - } - }, - "stock_quantity": 320, - "min_purchase_qty": 1, - "max_purchase_qty": 10, - "sales_status": "coming_soon", - "sales_status_label": "판매예정", - "brand_name": "코지홈", - "labels": [ - { - "name": "BEST", - "color": "#ef4444" - }, - { - "name": "한정특가", - "color": "#f59e0b" - } - ], - "additional_options": [ - { - "id": 9001, - "name": "이니셜 자수 추가", - "is_required": false - }, - { - "id": 9002, - "name": "선물 포장", - "is_required": false - } - ], - "shipping_policy_id": 1, - "free_shipping": false, - "shipping_fee_formatted": "3,000원 (50,000원 이상 무료)", - "shipping_policy": { - "name": "기본 배송정책", - "charge_policy": "conditional_free", - "charge_policy_label": "조건부 무료", - "base_fee": 3000, - "base_fee_formatted": "3,000원", - "free_threshold": 50000, - "free_threshold_formatted": "50,000원", - "fee_summary": "3,000원 (50,000원 이상 무료배송)" - }, - "short_description": "사계절 입기 좋은 오버핏 기본 티셔츠", - "short_description_localized": "사계절 입기 좋은 오버핏 기본 티셔츠", - "description": { - "ko": "

부드러운 30수 코튼 소재로 제작된 데일리 오버핏 티셔츠입니다.

" - }, - "description_localized": "

제품 특징

부드러운 30수 코튼 100% 소재로 제작되어 사계절 내내 편안하게 착용할 수 있습니다. 오버핏 실루엣으로 체형 커버에 탁월하며, 어떤 하의와도 자연스럽게 매치됩니다.

  • 소재: 코튼 100%
  • 핏: 오버핏
  • 제조국: 대한민국

\"상세

", - "description_mode": "html", - "images": [ - { - "id": 5001, - "hash": "img5001", - "original_filename": "tshirt_main.jpg", - "download_url": "/images/placeholder.png", - "alt_text": "정면", - "alt_text_current": "베이직 오버핏 티셔츠 정면", - "is_thumbnail": true, - "sort_order": 0, - "order": 0, - "width": 1000, - "height": 1000, - "file_size": 184320, - "size": 184320, - "size_formatted": "180 KB", - "mime_type": "image/jpeg", - "is_image": true - }, - { - "id": 5002, - "hash": "img5002", - "original_filename": "tshirt_back.jpg", - "download_url": "/images/placeholder.png", - "alt_text": "후면", - "alt_text_current": "베이직 오버핏 티셔츠 후면", - "is_thumbnail": false, - "sort_order": 1, - "order": 1, - "width": 1000, - "height": 1000, - "file_size": 176128, - "size": 176128, - "size_formatted": "172 KB", - "mime_type": "image/jpeg", - "is_image": true - }, - { - "id": 5003, - "hash": "img5003", - "original_filename": "tshirt_detail.jpg", - "download_url": "/images/placeholder.png", - "alt_text": "디테일", - "alt_text_current": "소매 디테일 컷", - "is_thumbnail": false, - "sort_order": 2, - "order": 2, - "width": 1000, - "height": 1000, - "file_size": 158720, - "size": 158720, - "size_formatted": "155 KB", - "mime_type": "image/jpeg", - "is_image": true - } - ], - "thumbnail_url": "/images/placeholder.png", - "meta_title": "베이직 오버핏 코튼 티셔츠 | 코지홈", - "meta_description": "사계절 입기 좋은 오버핏 기본 티셔츠", - "meta_keywords": [ - "티셔츠", - "오버핏", - "코튼" - ], - "has_options": true, - "option_groups": [ - { - "name": { - "ko": "색상", - "en": "Color" - }, - "name_localized": "색상", - "values": [ - { - "ko": "블랙", - "en": "Black" - }, - { - "ko": "화이트", - "en": "White" - }, - { - "ko": "네이비", - "en": "Navy" - } - ], - "values_localized": [ - "블랙", - "화이트", - "네이비" - ] - }, - { - "name": { - "ko": "사이즈", - "en": "Size" - }, - "name_localized": "사이즈", - "values": [ - { - "ko": "S", - "en": "S" - }, - { - "ko": "M", - "en": "M" - }, - { - "ko": "L", - "en": "L" - } - ], - "values_localized": [ - "S", - "M", - "L" - ] - } - ], - "options": [ - { - "id": 7001, - "option_code": "OPT-BLK-S", - "option_values": [ - { - "key": { - "ko": "색상" - }, - "value": { - "ko": "블랙" - } - }, - { - "key": { - "ko": "사이즈" - }, - "value": { - "ko": "S" - } - } - ], - "option_values_localized": [ - "블랙", - "S" - ], - "option_name": { - "ko": "블랙 / S" - }, - "option_name_localized": "블랙 / S", - "price_adjustment": 0, - "price_adjustment_formatted": "+0원", - "price_adjustment_type": "increase", - "list_price": 29000, - "list_price_formatted": "29,000원", - "selling_price": 23200, - "selling_price_formatted": "23,200원", - "final_price": 23200, - "final_price_formatted": "23,200원", - "multi_currency_list_price": { - "KRW": { - "price": 29000, - "formatted": "29,000원", - "is_default": true, - "editable": true - }, - "USD": { - "price": 22.31, - "formatted": "$22.31", - "is_default": false, - "editable": false, - "exchange_rate": 1300 - } - }, - "multi_currency_selling_price": { - "KRW": { - "price": 23200, - "formatted": "23,200원", - "is_default": true, - "editable": true - }, - "USD": { - "price": 17.85, - "formatted": "$17.85", - "is_default": false, - "editable": false, - "exchange_rate": 1300 - } - }, - "stock_quantity": 80, - "safe_stock_quantity": 10, - "is_below_safe_stock": false, - "is_default": true, - "is_active": true, - "sku": "TS-1001-BLK-S", - "sort_order": 0 - }, - { - "id": 7002, - "option_code": "OPT-BLK-M", - "option_values": [ - { - "key": { - "ko": "색상" - }, - "value": { - "ko": "블랙" - } - }, - { - "key": { - "ko": "사이즈" - }, - "value": { - "ko": "M" - } - } - ], - "option_values_localized": [ - "블랙", - "M" - ], - "option_name": { - "ko": "블랙 / M" - }, - "option_name_localized": "블랙 / M", - "price_adjustment": 0, - "price_adjustment_formatted": "+0원", - "price_adjustment_type": "increase", - "list_price": 29000, - "list_price_formatted": "29,000원", - "selling_price": 23200, - "selling_price_formatted": "23,200원", - "final_price": 23200, - "final_price_formatted": "23,200원", - "multi_currency_list_price": { - "KRW": { - "price": 29000, - "formatted": "29,000원", - "is_default": true, - "editable": true - }, - "USD": { - "price": 22.31, - "formatted": "$22.31", - "is_default": false, - "editable": false, - "exchange_rate": 1300 - } - }, - "multi_currency_selling_price": { - "KRW": { - "price": 23200, - "formatted": "23,200원", - "is_default": true, - "editable": true - }, - "USD": { - "price": 17.85, - "formatted": "$17.85", - "is_default": false, - "editable": false, - "exchange_rate": 1300 - } - }, - "stock_quantity": 120, - "safe_stock_quantity": 10, - "is_below_safe_stock": false, - "is_default": false, - "is_active": true, - "sku": "TS-1001-BLK-M", - "sort_order": 1 - }, - { - "id": 7003, - "option_code": "OPT-WHT-L", - "option_values": [ - { - "key": { - "ko": "색상" - }, - "value": { - "ko": "화이트" - } - }, - { - "key": { - "ko": "사이즈" - }, - "value": { - "ko": "L" - } - } - ], - "option_values_localized": [ - "화이트", - "L" - ], - "option_name": { - "ko": "화이트 / L" - }, - "option_name_localized": "화이트 / L", - "price_adjustment": 2000, - "price_adjustment_formatted": "+2,000원", - "price_adjustment_type": "increase", - "list_price": 31000, - "list_price_formatted": "31,000원", - "selling_price": 25200, - "selling_price_formatted": "25,200원", - "final_price": 25200, - "final_price_formatted": "25,200원", - "multi_currency_list_price": { - "KRW": { - "price": 31000, - "formatted": "31,000원", - "is_default": true, - "editable": true - }, - "USD": { - "price": 23.85, - "formatted": "$23.85", - "is_default": false, - "editable": false, - "exchange_rate": 1300 - } - }, - "multi_currency_selling_price": { - "KRW": { - "price": 25200, - "formatted": "25,200원", - "is_default": true, - "editable": true - }, - "USD": { - "price": 19.38, - "formatted": "$19.38", - "is_default": false, - "editable": false, - "exchange_rate": 1300 - } - }, - "stock_quantity": 5, - "safe_stock_quantity": 10, - "is_below_safe_stock": true, - "is_default": false, - "is_active": true, - "sku": "TS-1001-WHT-L", - "sort_order": 2 - } - ], - "notice": { - "template_name": "의류 상품정보제공고시", - "values": [ - { - "label": "제품 소재", - "value": "면 100%" - }, - { - "label": "색상", - "value": "블랙, 화이트, 네이비" - }, - { - "label": "치수", - "value": "S, M, L (상세 사이즈표 참고)" - }, - { - "label": "제조자", - "value": "코지홈" - }, - { - "label": "제조국", - "value": "대한민국" - }, - { - "label": "취급 시 주의사항", - "value": "찬물 단독 손세탁 권장" - }, - { - "label": "품질보증기준", - "value": "관련 법 및 소비자분쟁해결기준에 따름" - }, - { - "label": "A/S 책임자와 전화번호", - "value": "코지홈 고객센터 1588-0000" - } - ] - }, - "common_info": { - "name": "배송/교환/환불 안내", - "content": "

주문 후 영업일 기준 1~2일 내 출고됩니다. 단순 변심 교환·반품은 수령 후 7일 이내 가능하며, 왕복 배송비가 부과됩니다.

", - "content_mode": "html" - }, - "is_wishlisted": false - } - } - } - } - }, - { - "id": "reviews_tab", - "label": "$t:editor.state.product_reviews_tab", - "comment": "상품상세 리뷰 탭은 _local.activeTab === 'reviews' 게이트 뒤라 기본('info') 상태 캔버스에 미렌더. activeTab 시드 + reviews 를 유저 탭 shape(reviews.data.reviews.data + rating_stats + option_filters + total_count)로 override. base reviews 샘플은 admin 리뷰관리 shape(reviews.data.data[])라 SSoT 분리(copy_source 패턴).", - "initialState": { - "local": { - "activeTab": "reviews" - } - }, - "sampleDataOverrides": { - "byDataSourceId": { - "reviews": { - "data": { - "total_count": 37, - "rating_stats": { - "1": { - "count": 1, - "percent": 3 - }, - "2": { - "count": 2, - "percent": 5 - }, - "3": { - "count": 4, - "percent": 11 - }, - "4": { - "count": 8, - "percent": 22 - }, - "5": { - "count": 22, - "percent": 59 - }, - "avg": 4.3 - }, - "option_filters": [ - { - "key": "색상", - "values": [ - { - "value": "블랙", - "count": 18 - }, - { - "value": "화이트", - "count": 12 - }, - { - "value": "네이비", - "count": 7 - } - ] - }, - { - "key": "사이즈", - "values": [ - { - "value": "S", - "count": 6 - }, - { - "value": "M", - "count": 19 - }, - { - "value": "L", - "count": 12 - } - ] - } - ], - "reviews": { - "data": [ - { - "id": 9101, - "user": { - "name": "김민지", - "avatar": null - }, - "rating": 5, - "content": "핏이 정말 예쁘고 원단도 도톰해서 가을에 입기 딱 좋아요. 세탁 후에도 변형이 없어서 만족합니다.", - "option_snapshot": { - "option_values": [ - { - "key": { - "ko": "색상" - }, - "value": { - "ko": "블랙" - } - }, - { - "key": { - "ko": "사이즈" - }, - "value": { - "ko": "M" - } - } - ] - }, - "image_count": 2, - "images": [ - { - "download_url": "/images/placeholder.png" - }, - { - "download_url": "/images/placeholder.png" - } - ], - "has_reply": true, - "reply_content": "소중한 후기 감사합니다! 앞으로도 좋은 상품으로 보답하겠습니다.", - "replied_at": "2026-05-27 09:10", - "created_at": "2026-05-26 11:20" - }, - { - "id": 9102, - "user": { - "name": "이서준", - "avatar": null - }, - "rating": 4, - "content": "색감 예쁘고 무난하게 입기 좋습니다. 다만 생각보다 살짝 얇은 느낌이에요.", - "option_snapshot": { - "option_values": [ - { - "key": { - "ko": "색상" - }, - "value": { - "ko": "화이트" - } - }, - { - "key": { - "ko": "사이즈" - }, - "value": { - "ko": "L" - } - } - ] - }, - "image_count": 0, - "images": [], - "has_reply": false, - "reply_content": null, - "replied_at": null, - "created_at": "2026-05-24 16:42" - }, - { - "id": 9103, - "user": { - "name": "박도윤", - "avatar": null - }, - "rating": 5, - "content": "재구매했어요. 데일리로 입기 좋고 색상별로 사고 싶네요. 배송도 빨랐습니다.", - "option_snapshot": { - "option_values": [ - { - "key": { - "ko": "색상" - }, - "value": { - "ko": "네이비" - } - }, - { - "key": { - "ko": "사이즈" - }, - "value": { - "ko": "S" - } - } - ] - }, - "image_count": 5, - "images": [ - { - "download_url": "/images/placeholder.png" - }, - { - "download_url": "/images/placeholder.png" - }, - { - "download_url": "/images/placeholder.png" - }, - { - "download_url": "/images/placeholder.png" - }, - { - "download_url": "/images/placeholder.png" - } - ], - "has_reply": true, - "reply_content": "재구매 감사합니다! 네이비 색상도 곧 재입고 예정입니다.", - "replied_at": "2026-05-21 10:00", - "created_at": "2026-05-20 19:05" - }, - { - "id": 9104, - "user": { - "name": "최예린", - "avatar": null - }, - "rating": 3, - "content": "무난해요. 기본템으로는 괜찮은데 특별하진 않네요.", - "option_snapshot": { - "option_values": [ - { - "key": { - "ko": "색상" - }, - "value": { - "ko": "블랙" - } - }, - { - "key": { - "ko": "사이즈" - }, - "value": { - "ko": "M" - } - } - ] - }, - "image_count": 0, - "images": [], - "has_reply": false, - "reply_content": null, - "replied_at": null, - "created_at": "2026-05-18 08:33" - } - ], - "meta": { - "current_page": 1, - "last_page": 4, - "per_page": 10, - "total": 37 - } - } - } - } - } - } - }, - { - "id": "qna_tab", - "label": "$t:editor.state.product_qna_tab", - "comment": "상품상세 문의 탭은 _local.activeTab === 'qna' && _global.modules['sirsoft-ecommerce'].inquiry.board_slug 게이트 + 탭 hiddenTabIds 가 board_slug 없으면 qna 탭 숨김. activeTab + global modules.inquiry.board_slug 시드 + qna 를 유저 shape(items[] + meta)로 override(기존 stub items:['샘플'] 해소).", - "initialState": { - "local": { - "activeTab": "qna" - }, - "global": { - "modules": { - "sirsoft-ecommerce": { - "inquiry": { - "board_slug": "product-qna" - } - } - } - } - } - } - ] - }, - { - "comment": "shop/index — 목록 유무 + 접근 제한. _local.productAccessDenied=true 시 접근 제한 화면을 if 분기한다(본문 line 192).", - "scope": { - "kind": "route", - "match": "/shop/products" - }, - "items": [ - { - "id": "with_data", - "label": "$t:editor.state.with_data", - "default": true - }, - { - "id": "empty_data", - "label": "$t:editor.state.empty_data", - "sampleDataOverrides": { - "byEndpointPattern": { - "/api/modules/sirsoft-ecommerce/products*": { - "data": { - "data": [], - "meta": { - "total": 0, - "per_page": 20, - "current_page": 1 - } - } - } - } - } - }, - { - "id": "access_denied", - "label": "$t:editor.state.shop_access_denied", - "initialState": { - "local": { - "productAccessDenied": true - } - } - } - ] - }, - { - "comment": "shop/cart — 담긴 상품 유무 + 주문 처리 중(_local.isOrdering 시 오버레이 잠금, 본문 line 115).", - "scope": { - "kind": "route", - "match": "/shop/cart" - }, - "items": [ - { - "id": "with_items", - "label": "$t:editor.state.cart_with_items", - "default": true - }, - { - "id": "empty_cart", - "label": "$t:editor.state.cart_empty", - "sampleDataOverrides": { - "byDataSourceId": { - "cartItems": { - "data": { - "items": [], - "item_ids": [], - "item_count": 0, - "calculation": { - "items": [], - "promotions": { - "product_promotions": { - "coupons": [], - "discount_codes": [], - "events": [] - }, - "order_promotions": { - "coupons": [], - "discount_codes": [], - "events": [] - } - }, - "validation_errors": [], - "summary": { - "subtotal": 0, - "subtotal_formatted": "0원", - "total_discount": 0, - "discount_formatted": "0원", - "total_shipping": 0, - "shipping_fee_formatted": "0원", - "final_amount": 0, - "final_amount_formatted": "0원", - "payment_amount_formatted": "0원", - "points_earning": 0, - "multi_currency": [] - } - } - } - } - } - } - }, - { - "id": "ordering", - "label": "$t:editor.state.cart_ordering", - "initialState": { - "local": { - "isOrdering": true - } - } - }, - { - "id": "summary_tax_expanded", - "label": "$t:editor.state.cart_summary_details", - "comment": "장바구니 요약의 세금 상세는 (1) 금액 영역이 _local.selectedItems.length>0 게이트(_cart_summary.json line 75), (2) 세금 상세가 _local.showTaxDetails 토글 게이트(line 128)로 이중 중첩. 둘 다 시드(selectedItems=[1,2,3] + showTaxDetails:true)해 과세/면세 금액(multi_currency) breakdown 을 노출. 편집기는 정적 시뮬레이션이라 체크박스 클릭·토글 클릭 불가.", - "initialState": { - "local": { - "showTaxDetails": true, - "selectedItems": [ - 1, - 2, - 3 - ] - } - } - } - ] - }, - { - "comment": "shop/checkout — 정상/결제오류(_local.orderError·query.error, 본문 line 45/194)/구매불가(_local.unavailableModal, line 102)/입력검증실패 분기. 검증 실패는 주문자/배송지 입력칸별 인라인 에러를 _local.errors 의 점 포함 flat 키(예 'orderer.name')로 읽는다(_checkout_orderer.json line 99/101, _checkout_shipping.json) — formErrors 키를 대괄호 표기로 지정한다.", - "scope": { - "kind": "route", - "match": "/shop/checkout" - }, - "items": [ - { - "id": "normal", - "label": "$t:editor.state.checkout_normal", - "default": true, - "comment": "빈 local 패치 — sampleGlobal._local baseline(받는분/주소/배송메모 시드)을 캔버스에 force-inject 하는 트리거. 패치가 없으면 편집기가 _localInit 을 주입하지 않아 레이아웃 자체 initLocal 이 baseline 을 덮어쓴다. 시드 데이터는 sampleGlobal._local 1곳에만 존재(상태별 중복 없음).", - "initialState": { - "local": [] - } - }, - { - "id": "payment_error", - "label": "$t:editor.state.checkout_payment_error", - "initialState": { - "local": { - "orderError": true, - "orderErrorMessage": "결제 처리 중 오류가 발생했습니다" - }, - "query": { - "error": "confirm_failed" - } - } - }, - { - "id": "validation_failed", - "label": "$t:editor.state.checkout_validation_failed", - "formErrors": { - "_local.errors['orderer.name']": [ - "주문자 이름을 입력해 주세요" - ], - "_local.errors['orderer.phone']": [ - "연락처를 입력해 주세요" - ], - "_local.errors['orderer.email']": [ - "올바른 이메일 형식이 아닙니다" - ], - "_local.errors['shipping.recipient_name']": [ - "받는 분 이름을 입력해 주세요" - ], - "_local.errors['shipping.recipient_phone']": [ - "받는 분 연락처를 입력해 주세요" - ], - "_local.errors['shipping.zipcode']": [ - "우편번호를 입력해 주세요" - ], - "_local.errors['shipping.address']": [ - "주소를 입력해 주세요" - ] - } - }, - { - "id": "summary_details_expanded", - "label": "$t:editor.state.checkout_summary_details", - "comment": "결제금액 요약의 세금/할인 상세는 _local.showTaxDetails / showDiscountDetails 토글 게이트 뒤(_checkout_summary.json line 93/161)라 기본 상태 미렌더. 두 토글 ON 으로 과세/면세 금액·상품/주문/배송 쿠폰 할인 breakdown(multi_currency 전 경로) 노출. base checkoutData 가 풍부하므로 override 불요.", - "initialState": { - "local": { - "showTaxDetails": true, - "showDiscountDetails": true - } - } - } - ] - }, - { - "comment": "shop/order_complete — orderData.data.payment.payment_method 값(card/vbank/dbank)으로 결제수단별 안내를 if 분기한다(본문 line 52/95/138/229). card 는 base sampleData(=card)가 흐르므로 오버라이드 없음, vbank/dbank 는 byDataSourceId.orderData 통째 교체.", - "scope": { - "kind": "route", - "match": "/shop/orders/:id/complete" - }, - "items": [ - { - "id": "card", - "label": "$t:editor.state.payment_card", - "default": true - }, - { - "id": "vbank", - "label": "$t:editor.state.payment_vbank", - "sampleDataOverrides": { - "byDataSourceId": { - "orderData": { - "data": { - "id": 5001, - "order_number": "20260603-0001", - "order_status": "payment_pending", - "order_status_label": "입금대기", - "order_status_variant": "warning", - "options": [ - { - "id": 9001, - "option_status": "preparing", - "option_status_label": "배송준비중", - "option_status_variant": "info", - "product_id": 1, - "product_option_id": 101, - "sku": "EARBUDS-PRO-BLK", - "product_name": "프리미엄 무선 이어버드 프로", - "product_option_name": "미드나잇 블랙 / 기본", - "option_name": "미드나잇 블랙 / 기본", - "quantity": 2, - "shipped_quantity": 0, - "unit_price": 29000, - "unit_price_formatted": "29,000원", - "subtotal_price": 58000, - "subtotal_price_formatted": "58,000원", - "subtotal_discount_amount": 5000, - "subtotal_discount_amount_formatted": "5,000원", - "list_price": 32000, - "list_price_formatted": "32,000원", - "final_amount": 53000, - "final_amount_formatted": "53,000원", - "thumbnail_url": null, - "mc_unit_price": { - "KRW": { - "price": 29000, - "formatted": "29,000원", - "is_default": true - }, - "USD": { - "price": 21.3, - "formatted": "$21.30", - "is_default": false - } - }, - "mc_subtotal_price": { - "KRW": { - "price": 58000, - "formatted": "58,000원", - "is_default": true - }, - "USD": { - "price": 42.6, - "formatted": "$42.60", - "is_default": false - } - }, - "confirmed_at": null, - "can_confirm": false, - "can_write_review": false, - "has_review": false - }, - { - "id": 9002, - "option_status": "preparing", - "option_status_label": "배송준비중", - "option_status_variant": "info", - "product_id": 2, - "product_option_id": 102, - "sku": "CABLE-15M-WHT", - "product_name": "고속 충전 케이블 1.5m", - "product_option_name": "1.5m", - "option_name": "1.5m", - "quantity": 1, - "shipped_quantity": 0, - "unit_price": 18000, - "unit_price_formatted": "18,000원", - "subtotal_price": 18000, - "subtotal_price_formatted": "18,000원", - "subtotal_discount_amount": 0, - "subtotal_discount_amount_formatted": "0원", - "list_price": 18000, - "list_price_formatted": "18,000원", - "final_amount": 18000, - "final_amount_formatted": "18,000원", - "thumbnail_url": null, - "mc_unit_price": { - "KRW": { - "price": 18000, - "formatted": "18,000원", - "is_default": true - }, - "USD": { - "price": 13.2, - "formatted": "$13.20", - "is_default": false - } - }, - "mc_subtotal_price": { - "KRW": { - "price": 18000, - "formatted": "18,000원", - "is_default": true - }, - "USD": { - "price": 13.2, - "formatted": "$13.20", - "is_default": false - } - }, - "confirmed_at": null, - "can_confirm": false, - "can_write_review": false, - "has_review": false - } - ], - "payment": { - "id": 8001, - "payment_status": "pending", - "payment_status_label": "입금대기", - "payment_status_variant": "warning", - "payment_method": "vbank", - "payment_method_label": "가상계좌", - "payment_type_label": "일반결제", - "pg_provider": "kginicis", - "payment_number": "PAY-20260603-0002", - "paid_amount_formatted": "102,000원", - "card_name": null, - "card_number_masked": null, - "card_installment_months": 0, - "is_interest_free": false, - "paid_at_formatted": null, - "dbank_name": null, - "dbank_account": null, - "dbank_holder": null, - "depositor_name": null, - "deposit_due_at_formatted": null, - "vbank_name": "국민은행", - "vbank_number": "901234-56-789012", - "vbank_holder": "(주)지세븐커머스", - "vbank_due_at": "2026-06-05T23:59:59+09:00" - }, - "shipping_address": { - "id": 7001, - "address_type": "shipping", - "recipient_name": "홍길동", - "recipient_phone": "010-1234-5678", - "recipient_country_code": "KR", - "zipcode": "06234", - "address": "서울특별시 강남구 테헤란로 123", - "address_detail": "4층 401호", - "city": "", - "state": "", - "full_address": "서울특별시 강남구 테헤란로 123 4층 401호", - "delivery_memo": "부재 시 경비실에 맡겨주세요" - }, - "subtotal_amount": 76000, - "subtotal_amount_formatted": "76,000원", - "total_discount_amount": 5000, - "total_discount_amount_formatted": "5,000원", - "total_shipping_amount": 0, - "total_shipping_amount_formatted": "0원", - "total_amount": 102000, - "total_amount_formatted": "102,000원", - "mc_subtotal_amount": { - "KRW": { - "price": 76000, - "formatted": "76,000원", - "is_default": true - }, - "USD": { - "price": 55.7, - "formatted": "$55.70", - "is_default": false - } - }, - "mc_total_discount_amount": { - "KRW": { - "price": 5000, - "formatted": "5,000원", - "is_default": true - }, - "USD": { - "price": 3.65, - "formatted": "$3.65", - "is_default": false - } - }, - "mc_total_shipping_amount": { - "KRW": { - "price": 0, - "formatted": "0원", - "is_default": true - }, - "USD": { - "price": 0, - "formatted": "$0.00", - "is_default": false - } - }, - "mc_total_amount": { - "KRW": { - "price": 102000, - "formatted": "102,000원", - "is_default": true - }, - "USD": { - "price": 74.5, - "formatted": "$74.50", - "is_default": false - } - } - } - } - } - } - }, - { - "id": "dbank", - "label": "$t:editor.state.payment_dbank", - "sampleDataOverrides": { - "byDataSourceId": { - "orderData": { - "data": { - "id": 5001, - "order_number": "20260603-0001", - "order_status": "payment_pending", - "order_status_label": "입금대기", - "order_status_variant": "warning", - "options": [ - { - "id": 9001, - "option_status": "preparing", - "option_status_label": "배송준비중", - "option_status_variant": "info", - "product_id": 1, - "product_option_id": 101, - "sku": "EARBUDS-PRO-BLK", - "product_name": "프리미엄 무선 이어버드 프로", - "product_option_name": "미드나잇 블랙 / 기본", - "option_name": "미드나잇 블랙 / 기본", - "quantity": 2, - "shipped_quantity": 0, - "unit_price": 29000, - "unit_price_formatted": "29,000원", - "subtotal_price": 58000, - "subtotal_price_formatted": "58,000원", - "subtotal_discount_amount": 5000, - "subtotal_discount_amount_formatted": "5,000원", - "list_price": 32000, - "list_price_formatted": "32,000원", - "final_amount": 53000, - "final_amount_formatted": "53,000원", - "thumbnail_url": null, - "mc_unit_price": { - "KRW": { - "price": 29000, - "formatted": "29,000원", - "is_default": true - }, - "USD": { - "price": 21.3, - "formatted": "$21.30", - "is_default": false - } - }, - "mc_subtotal_price": { - "KRW": { - "price": 58000, - "formatted": "58,000원", - "is_default": true - }, - "USD": { - "price": 42.6, - "formatted": "$42.60", - "is_default": false - } - }, - "confirmed_at": null, - "can_confirm": false, - "can_write_review": false, - "has_review": false - }, - { - "id": 9002, - "option_status": "preparing", - "option_status_label": "배송준비중", - "option_status_variant": "info", - "product_id": 2, - "product_option_id": 102, - "sku": "CABLE-15M-WHT", - "product_name": "고속 충전 케이블 1.5m", - "product_option_name": "1.5m", - "option_name": "1.5m", - "quantity": 1, - "shipped_quantity": 0, - "unit_price": 18000, - "unit_price_formatted": "18,000원", - "subtotal_price": 18000, - "subtotal_price_formatted": "18,000원", - "subtotal_discount_amount": 0, - "subtotal_discount_amount_formatted": "0원", - "list_price": 18000, - "list_price_formatted": "18,000원", - "final_amount": 18000, - "final_amount_formatted": "18,000원", - "thumbnail_url": null, - "mc_unit_price": { - "KRW": { - "price": 18000, - "formatted": "18,000원", - "is_default": true - }, - "USD": { - "price": 13.2, - "formatted": "$13.20", - "is_default": false - } - }, - "mc_subtotal_price": { - "KRW": { - "price": 18000, - "formatted": "18,000원", - "is_default": true - }, - "USD": { - "price": 13.2, - "formatted": "$13.20", - "is_default": false - } - }, - "confirmed_at": null, - "can_confirm": false, - "can_write_review": false, - "has_review": false - } - ], - "payment": { - "id": 8001, - "payment_status": "pending", - "payment_status_label": "입금대기", - "payment_status_variant": "warning", - "payment_method": "dbank", - "payment_method_label": "무통장입금", - "payment_type_label": "일반결제", - "pg_provider": "kginicis", - "payment_number": "PAY-20260603-0003", - "paid_amount_formatted": "102,000원", - "card_name": null, - "card_number_masked": null, - "card_installment_months": 0, - "is_interest_free": false, - "paid_at_formatted": null, - "dbank_name": "신한은행", - "dbank_account": "110-123-456789", - "dbank_holder": "(주)지세븐커머스", - "depositor_name": "홍길동", - "deposit_due_at_formatted": "2026-06-05 23:59", - "vbank_name": null, - "vbank_number": null, - "vbank_holder": null, - "vbank_due_at": null - }, - "shipping_address": { - "id": 7001, - "address_type": "shipping", - "recipient_name": "홍길동", - "recipient_phone": "010-1234-5678", - "recipient_country_code": "KR", - "zipcode": "06234", - "address": "서울특별시 강남구 테헤란로 123", - "address_detail": "4층 401호", - "city": "", - "state": "", - "full_address": "서울특별시 강남구 테헤란로 123 4층 401호", - "delivery_memo": "부재 시 경비실에 맡겨주세요" - }, - "subtotal_amount": 76000, - "subtotal_amount_formatted": "76,000원", - "total_discount_amount": 5000, - "total_discount_amount_formatted": "5,000원", - "total_shipping_amount": 0, - "total_shipping_amount_formatted": "0원", - "total_amount": 102000, - "total_amount_formatted": "102,000원", - "mc_subtotal_amount": { - "KRW": { - "price": 76000, - "formatted": "76,000원", - "is_default": true - }, - "USD": { - "price": 55.7, - "formatted": "$55.70", - "is_default": false - } - }, - "mc_total_discount_amount": { - "KRW": { - "price": 5000, - "formatted": "5,000원", - "is_default": true - }, - "USD": { - "price": 3.65, - "formatted": "$3.65", - "is_default": false - } - }, - "mc_total_shipping_amount": { - "KRW": { - "price": 0, - "formatted": "0원", - "is_default": true - }, - "USD": { - "price": 0, - "formatted": "$0.00", - "is_default": false - } - }, - "mc_total_amount": { - "KRW": { - "price": 102000, - "formatted": "102,000원", - "is_default": true - }, - "USD": { - "price": 74.5, - "formatted": "$74.50", - "is_default": false - } - } - } - } - } - } - } - ] - }, - { - "comment": "shop/reorder — _local.status 값(pending/success/error)으로 재주문 진행 화면을 if 분기한다(본문 line 72/99/225).", - "scope": { - "kind": "route", - "match": "/shop/reorder/:id" - }, - "items": [ - { - "id": "pending", - "label": "$t:editor.state.reorder_pending", - "default": true, - "initialState": { - "local": { - "status": "pending" - } - } - }, - { - "id": "success_partial", - "label": "$t:editor.state.reorder_success_partial", - "initialState": { - "local": { - "status": "success", - "skipped": [ - { - "name": "품절 상품" - } - ] - } - } - }, - { - "id": "error", - "label": "$t:editor.state.reorder_error", - "initialState": { - "local": { - "status": "error" - } - } - } - ] - }, - { - "comment": "admin 상품 폼 — route.itemCode 유무로 수정/신규, query.copy_id 로 복사 생성을 분기한다(본문 line 9/32/75). 검증 오류는 _local.errors.{field} 경로.", - "scope": { - "kind": "route", - "match": "*/admin/ecommerce/products/:itemCode/edit" - }, - "items": [ - { - "id": "edit_mode", - "label": "$t:editor.state.edit_existing", - "default": true - }, - { - "id": "create_mode", - "label": "$t:editor.state.create_mode", - "initialState": { - "route": { - "itemCode": null - } - } - }, - { - "id": "validation_failed", - "label": "$t:editor.state.validation_failed", - "formErrors": { - "_local.errors.name": [ - "필수 입력 항목입니다" - ] - } - } - ] - }, - { - "comment": "admin 쿠폰 폼 — route.id 유무로 수정/신규 분기(본문 line 9/74). 검증 오류는 _local.errors.", - "scope": { - "kind": "route", - "match": "*/admin/ecommerce/promotion-coupons/:id/edit" - }, - "items": [ - { - "id": "edit_mode", - "label": "$t:editor.state.edit_existing", - "default": true, - "initialState": { - "local": { - "form": { - "target_type": "order_amount", - "name": { - "ko": "신규회원 10% 할인", - "en": "New Member 10% Off" - }, - "description": { - "ko": "신규 가입 회원 대상 주문금액 10% 할인 쿠폰", - "en": "10% off for new members" - }, - "issue_method": "download", - "issue_condition": "manual", - "discount_type": "rate", - "discount_value": 10, - "discount_max_amount": 20000, - "min_order_amount": 30000, - "valid_type": "days", - "valid_days": 30, - "valid_from": null, - "valid_to": null, - "total_quantity": 1000, - "issue_from": "2026-05-01T00:00", - "issue_to": "2026-06-30T23:59", - "per_user_limit": 1, - "is_combinable": false, - "target_scope": "all" - } - } - } - }, - { - "id": "create_mode", - "label": "$t:editor.state.create_mode", - "initialState": { - "route": { - "id": null - } - } - }, - { - "id": "validation_failed", - "label": "$t:editor.state.validation_failed", - "formErrors": { - "_local.errors.name": [ - "필수 입력 항목입니다" - ] - } - } - ] - }, - { - "comment": "admin 배송 정책 폼 — route.id 유무로 수정/신규, query.copy_id 로 복사 분기(본문 line 9/60/98). 검증 오류는 _local.errors.", - "scope": { - "kind": "route", - "match": "*/admin/ecommerce/shipping-policies/:id/edit" - }, - "items": [ - { - "id": "edit_mode", - "label": "부과정책: 조건부 무료", - "default": true, - "initialState": { - "local": { - "form": { - "name": { - "ko": "기본 배송정책", - "en": "Standard Shipping" - }, - "is_active": true, - "is_default": true, - "country_settings": [ - { - "country_code": "KR", - "shipping_method": "parcel", - "custom_shipping_name": null, - "carrier": null, - "currency_code": "KRW", - "charge_policy": "conditional_free", - "base_fee": 3000, - "free_threshold": 50000, - "ranges": null, - "api_endpoint": null, - "api_request_fields": null, - "api_response_fee_field": null, - "extra_fee_enabled": true, - "extra_fee_settings": [ - { - "region": "제주특별자치도", - "zipcode": "63000-63644", - "fee": 3000 - }, - { - "region": "도서산간", - "zipcode": "", - "fee": 4000 - } - ], - "extra_fee_multiply": false, - "is_active": true - }, - { - "country_code": "US", - "shipping_method": "parcel", - "custom_shipping_name": null, - "carrier": null, - "currency_code": "USD", - "charge_policy": "range_amount", - "base_fee": 0, - "free_threshold": null, - "ranges": { - "type": "amount", - "tiers": [ - { - "min": 0, - "max": 50000, - "fee": 3000 - }, - { - "min": 50000, - "max": 100000, - "fee": 1500 - }, - { - "min": 100000, - "max": null, - "fee": 0 - } - ] - }, - "api_endpoint": null, - "api_request_fields": null, - "api_response_fee_field": null, - "extra_fee_enabled": false, - "extra_fee_settings": [], - "extra_fee_multiply": false, - "is_active": true - } - ] - }, - "activeCountryTab": 0, - "rangeErrors": [], - "showBaseFee": true, - "showFreeThreshold": true, - "showRanges": false, - "showApiSettings": false, - "showUnitValue": false - } - } - }, - { - "id": "charge_fixed", - "label": "부과정책: 고정", - "default": false, - "initialState": { - "local": { - "form": { - "name": { - "ko": "기본 배송정책", - "en": "Standard Shipping" - }, - "is_active": true, - "is_default": false, - "country_settings": [ - { - "country_code": "KR", - "shipping_method": "parcel", - "custom_shipping_name": null, - "carrier": null, - "currency_code": "KRW", - "charge_policy": "fixed", - "base_fee": 3000, - "free_threshold": null, - "ranges": null, - "api_endpoint": null, - "api_request_fields": null, - "api_response_fee_field": null, - "extra_fee_enabled": false, - "extra_fee_settings": [], - "extra_fee_multiply": false, - "is_active": true - } - ] - }, - "activeCountryTab": 0, - "rangeErrors": [], - "showBaseFee": true, - "showFreeThreshold": false, - "showRanges": false, - "showApiSettings": false, - "showUnitValue": false - } - } - }, - { - "id": "charge_range", - "label": "부과정책: 구간별(금액)", - "default": false, - "initialState": { - "local": { - "form": { - "name": { - "ko": "기본 배송정책", - "en": "Standard Shipping" - }, - "is_active": true, - "is_default": false, - "country_settings": [ - { - "country_code": "KR", - "shipping_method": "parcel", - "custom_shipping_name": null, - "carrier": null, - "currency_code": "KRW", - "charge_policy": "range_amount", - "base_fee": 0, - "free_threshold": null, - "ranges": { - "type": "amount", - "tiers": [ - { - "min": 0, - "max": 50000, - "fee": 3000 - }, - { - "min": 50000, - "max": 100000, - "fee": 1500 - }, - { - "min": 100000, - "max": null, - "fee": 0 - } - ] - }, - "api_endpoint": null, - "api_request_fields": null, - "api_response_fee_field": null, - "extra_fee_enabled": false, - "extra_fee_settings": [], - "extra_fee_multiply": false, - "is_active": true - } - ] - }, - "activeCountryTab": 0, - "rangeErrors": [], - "showBaseFee": false, - "showFreeThreshold": false, - "showRanges": true, - "showApiSettings": false, - "showUnitValue": false - } - } - }, - { - "id": "charge_per_weight", - "label": "부과정책: 무게당", - "default": false, - "initialState": { - "local": { - "form": { - "name": { - "ko": "기본 배송정책", - "en": "Standard Shipping" - }, - "is_active": true, - "is_default": false, - "country_settings": [ - { - "country_code": "KR", - "shipping_method": "parcel", - "custom_shipping_name": null, - "carrier": null, - "currency_code": "KRW", - "charge_policy": "per_weight", - "base_fee": 2500, - "free_threshold": null, - "ranges": { - "type": "weight", - "unit_value": 5 - }, - "api_endpoint": null, - "api_request_fields": null, - "api_response_fee_field": null, - "extra_fee_enabled": false, - "extra_fee_settings": [], - "extra_fee_multiply": false, - "is_active": true - } - ] - }, - "activeCountryTab": 0, - "rangeErrors": [], - "showBaseFee": true, - "showFreeThreshold": false, - "showRanges": false, - "showApiSettings": false, - "showUnitValue": true - } - } - }, - { - "id": "charge_free", - "label": "부과정책: 무료", - "default": false, - "initialState": { - "local": { - "form": { - "name": { - "ko": "기본 배송정책", - "en": "Standard Shipping" - }, - "is_active": true, - "is_default": false, - "country_settings": [ - { - "country_code": "KR", - "shipping_method": "parcel", - "custom_shipping_name": null, - "carrier": null, - "currency_code": "KRW", - "charge_policy": "free", - "base_fee": 0, - "free_threshold": null, - "ranges": null, - "api_endpoint": null, - "api_request_fields": null, - "api_response_fee_field": null, - "extra_fee_enabled": false, - "extra_fee_settings": [], - "extra_fee_multiply": false, - "is_active": true - } - ] - }, - "activeCountryTab": 0, - "rangeErrors": [], - "showBaseFee": false, - "showFreeThreshold": false, - "showRanges": false, - "showApiSettings": false, - "showUnitValue": false - } - } - }, - { - "id": "charge_api", - "label": "부과정책: 계산 API", - "default": false, - "initialState": { - "local": { - "form": { - "name": { - "ko": "기본 배송정책", - "en": "Standard Shipping" - }, - "is_active": true, - "is_default": false, - "country_settings": [ - { - "country_code": "KR", - "shipping_method": "parcel", - "custom_shipping_name": null, - "carrier": null, - "currency_code": "KRW", - "charge_policy": "api", - "base_fee": 0, - "free_threshold": null, - "ranges": null, - "api_endpoint": "https://shipping.example.com/api/quote", - "api_request_fields": [ - "zipcode", - "weight" - ], - "api_response_fee_field": "fee", - "extra_fee_enabled": false, - "extra_fee_settings": [], - "extra_fee_multiply": false, - "is_active": true - } - ] - }, - "activeCountryTab": 0, - "rangeErrors": [], - "showBaseFee": false, - "showFreeThreshold": false, - "showRanges": false, - "showApiSettings": true, - "showUnitValue": false - } - } - }, - { - "id": "create_mode", - "label": "$t:editor.state.create_mode", - "initialState": { - "route": { - "id": null - } - } - }, - { - "id": "validation_failed", - "label": "$t:editor.state.validation_failed", - "formErrors": { - "_local.errors.name": [ - "필수 입력 항목입니다" - ] - } - } - ] - }, - { - "comment": "admin 이커머스 설정 — {{_global.activeEcommerceSettingsTab || query.tab || 'basic_info'}} 으로 9개 탭 화면을 if 분기한다(본문 line 303/344~). query.tab + 우선 키 함께 주입.", - "scope": { - "kind": "route", - "match": "*/admin/ecommerce/settings" - }, - "items": [ - { - "id": "tab_basic_info", - "label": "$t:editor.state.tab.basic_info", - "initialState": { - "query": { - "tab": "basic_info" - }, - "global": { - "activeEcommerceSettingsTab": "basic_info" - } - }, - "default": true - }, - { - "id": "tab_language_currency", - "label": "$t:editor.state.tab.language_currency", - "initialState": { - "query": { - "tab": "language_currency" - }, - "global": { - "activeEcommerceSettingsTab": "language_currency" - } - } - }, - { - "id": "tab_seo", - "label": "$t:editor.state.tab.seo", - "initialState": { - "query": { - "tab": "seo" - }, - "global": { - "activeEcommerceSettingsTab": "seo" - } - } - }, - { - "id": "tab_order_settings", - "label": "$t:editor.state.tab.order_settings", - "initialState": { - "query": { - "tab": "order_settings" - }, - "global": { - "activeEcommerceSettingsTab": "order_settings" - } - } - }, - { - "id": "tab_claim", - "label": "$t:editor.state.tab.claim", - "initialState": { - "query": { - "tab": "claim" - }, - "global": { - "activeEcommerceSettingsTab": "claim" - } - } - }, - { - "id": "tab_shipping", - "label": "$t:editor.state.tab.shipping", - "initialState": { - "query": { - "tab": "shipping" - }, - "global": { - "activeEcommerceSettingsTab": "shipping" - } - } - }, - { - "id": "tab_review_settings", - "label": "$t:editor.state.tab.review_settings", - "initialState": { - "query": { - "tab": "review_settings" - }, - "global": { - "activeEcommerceSettingsTab": "review_settings" - } - } - }, - { - "id": "tab_notification_definitions", - "label": "$t:editor.state.tab.notification_definitions", - "initialState": { - "query": { - "tab": "notification_definitions" - }, - "global": { - "activeEcommerceSettingsTab": "notification_definitions" - } - } - }, - { - "id": "tab_identity_policies", - "label": "$t:editor.state.tab.identity_policies", - "initialState": { - "query": { - "tab": "identity_policies" - }, - "global": { - "activeEcommerceSettingsTab": "identity_policies" - } - } - } - ] - }, - { - "comment": "admin 마일리지/예치금 설정 — _local.activeTab(mileage/deposit)으로 탭을 분기한다(본문 line 130/165).", - "scope": { - "kind": "route", - "match": "*/admin/ecommerce/mileage-deposit-settings" - }, - "items": [ - { - "id": "tab_mileage", - "label": "$t:editor.state.tab.mileage", - "default": true, - "initialState": { - "local": { - "activeTab": "mileage" - } - } - }, - { - "id": "tab_deposit", - "label": "$t:editor.state.tab.deposit", - "initialState": { - "local": { - "activeTab": "deposit" - } - } - } - ] - }, - { - "comment": "shop/guest_order_form — 비회원 주문 조회 폼. 본문이 _global.currentUser.uuid 유무로 양분된다(slots.content[0] 회원→마이페이지 리다이렉트 안내 / content[1] 비회원→조회 폼). 편집기 sampleGlobal 은 로그인 상태(currentUser 시드)라 비회원 조회 폼이 기본 미표시이므로, currentUser 를 null 로 패치한 비회원 상태를 기본으로 노출해 폼을 편집 가능하게 한다.", - "scope": { - "kind": "route", - "match": "/shop/guest/orders" - }, - "items": [ - { - "id": "guest", - "label": "$t:editor.state.guest_order_form_guest", - "default": true, - "comment": "비회원 상태 — currentUser 를 null 로 덮어 content[1] 조회 폼을 캔버스에 노출한다.", - "initialState": { - "global": { - "currentUser": null - } - } - }, - { - "id": "member", - "label": "$t:editor.state.guest_order_form_member", - "comment": "회원 로그인 상태 — currentUser.uuid 를 명시 시드해 content[0] 마이페이지 리다이렉트 안내 분기를 활성화한다. sampleGlobal 은 코어 keyspace(currentUser)를 시드하지 않으므로(코어 우선) 빈 패치로는 guest(null) 와 다른 결과를 만들지 못한다 — 로그인 상태를 명시 패치해야 양분 분기가 회원 쪽으로 전환되고 캔버스가 실제로 달라진다.", - "initialState": { - "global": { - "currentUser": { - "uuid": "editor-sample-member-uuid" - } - } - } - } - ] - }, - { - "comment": "admin 마일리지 거래내역 목록 — transactions 데이터소스 목록 유무. 마일리지 시스템.", - "scope": { - "kind": "route", - "match": "*/admin/ecommerce/mileage-transactions" - }, - "items": [ - { - "id": "with_data", - "label": "$t:editor.state.with_data", - "default": true - }, - { - "id": "empty_data", - "label": "$t:editor.state.empty_data", - "sampleDataOverrides": { - "byEndpointPattern": { - "/api/modules/sirsoft-ecommerce/admin/mileage-transactions*": { - "data": { - "data": [], - "meta": { - "total": 0, - "per_page": 20, - "current_page": 1 - } - } - } - } - } - } - ] - }, - { - "comment": "admin 쿠폰 목록 — coupons 데이터소스 목록 유무. 쿠폰 관리.", - "scope": { - "kind": "route", - "match": "*/admin/ecommerce/promotion-coupons" - }, - "items": [ - { - "id": "with_data", - "label": "$t:editor.state.with_data", - "default": true - }, - { - "id": "empty_data", - "label": "$t:editor.state.empty_data", - "sampleDataOverrides": { - "byEndpointPattern": { - "/api/modules/sirsoft-ecommerce/admin/promotion-coupons*": { - "data": { - "data": [], - "meta": { - "total": 0, - "per_page": 20, - "current_page": 1 - } - } - } - } - } - } - ] - }, - { - "comment": "admin 쿠폰 생성 — promotion-coupon-create 라우트(route.id 없음 = 신규 작성 폼). 쿠폰 관리. 검증 오류는 _local.errors.", - "scope": { - "kind": "route", - "match": "*/admin/ecommerce/promotion-coupon-create" - }, - "items": [ - { - "id": "create_mode", - "label": "$t:editor.state.create_mode", - "default": true, - "initialState": { - "route": { - "id": null - } - } - }, - { - "id": "validation_failed", - "label": "$t:editor.state.validation_failed", - "formErrors": { - "_local.errors.name": [ - "필수 입력 항목입니다" - ] - } - } - ] - }, - { - "comment": "admin 주문 목록 — orders 데이터소스 목록 유무.", - "scope": { - "kind": "route", - "match": "*/admin/ecommerce/orders" - }, - "items": [ - { - "id": "with_data", - "label": "$t:editor.state.with_data", - "default": true - }, - { - "id": "empty_data", - "label": "$t:editor.state.empty_data", - "sampleDataOverrides": { - "byEndpointPattern": { - "/api/modules/sirsoft-ecommerce/admin/orders*": { - "data": { - "data": [], - "meta": { - "total": 0, - "per_page": 20, - "current_page": 1 - } - } - } - } - } - } - ] - }, - { - "comment": "admin 주문 상세 — order 데이터소스 + 부분취소 분기(본문 if order.data.is_partially_cancelled). 주문 항목상태 전이.", - "scope": { - "kind": "route", - "match": "*/admin/ecommerce/orders/:orderNumber" - }, - "items": [ - { - "id": "default", - "label": "$t:editor.state.order_detail_default", - "default": true - }, - { - "id": "partially_cancelled", - "label": "$t:editor.state.order_detail_partially_cancelled", - "comment": "부분취소 상태 — order.data.is_partially_cancelled=true 로 취소 표시 분기를 캔버스에 노출.", - "sampleDataOverrides": { - "byEndpointPattern": { - "/api/modules/sirsoft-ecommerce/admin/orders/*": { - "data": { - "is_partially_cancelled": true - } - } - } - } - } - ] - } - ] - } + "...": "(4개 키 생략, 총 9개)" } } } diff --git a/docs/backend/api/notification-channels.md b/docs/backend/api/notification-channels.md index 0ca154fb..867a9e79 100644 --- a/docs/backend/api/notification-channels.md +++ b/docs/backend/api/notification-channels.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Notification Channels 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` diff --git a/docs/backend/api/notification-definitions.md b/docs/backend/api/notification-definitions.md index faf79056..6920332b 100644 --- a/docs/backend/api/notification-definitions.md +++ b/docs/backend/api/notification-definitions.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Notification Definitions 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` @@ -31,6 +31,7 @@ | extension_type | query | string | 아니오 | `core`, `module`, `plugin` | 확장 유형 (core/module/plugin/template) | | extension_identifier | query | string | 아니오 | max 100 | 확장 식별자 | | channel | query | string | 아니오 | max 50 | 채널 필터 — 활성 채널(`channels`) 배열에 이 채널을 포함하는 정의만 조회 (mail, database 등) | +| template_channel | query | string | 아니오 | max 50 | 목록에 실을 템플릿을 한 채널로 좁힙니다. `channel` 과 달리 정의 행은 그대로 두고 각 행의 `templates` 만 좁힙니다. **지정하지 않으면 `templates` 키 자체가 응답에 없습니다** — 정의 하나당 채널 × 로케일만큼의 제목·본문이 실리는 것을 막기 위한 기본값이며, 전 채널이 필요하면 단건 조회를 이용하세요 | | is_active | query | boolean | 아니오 | — | 활성 여부 (true 활성 / false 비활성) | | per_page | query | integer | 아니오 | min 1, max 100 | 페이지당 항목 수 | | sort_by | query | string | 아니오 | `id`, `type`, `extension_type`, `is_active`, `created_at`, `updated_at` | 정렬 기준 필드명 | @@ -41,7 +42,7 @@ **요청 예시** ```http -GET /api/admin/notification-definitions?search=%EC%98%88%EC%8B%9C%EA%B0%92&extension_type=core&extension_identifier=example-key&channel=%EC%98%88%EC%8B%9C%EA%B0%92&is_active=1&per_page=1&sort_by=id&sort_order=asc HTTP/1.1 +GET /api/admin/notification-definitions?search=%EC%98%88%EC%8B%9C%EA%B0%92&extension_type=core&extension_identifier=example-key&channel=%EC%98%88%EC%8B%9C%EA%B0%92&template_channel=%EC%98%88%EC%8B%9C%EA%B0%92&is_active=1&per_page=1&sort_by=id&sort_order=asc HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -55,20 +56,21 @@ _목록 응답: `data.data[]` 배열 항목의 필드 + `data.pagination`._ | --- | --- | --- | --- | | number | integer | `1` | 목록에서의 순번 (페이지네이션 반영 행 번호 — HasRowNumber 파생) | | id | integer | `1` | 기본 키 (내부 식별자) | -| type | string | `welcome` | 알림 타입 (welcome, order_confirmed 등) | -| hook_prefix | string | `core.auth` | 훅 접두사 (core.auth, sirsoft-ecommerce 등) | -| extension_type | string | `core` | 확장 타입: core, module, plugin | -| extension_identifier | string | `core` | 확장 식별자: core, sirsoft-board 등 | -| name | object | `{"ko":"회원가입 환영","en":"Welcome","ja":"会員登録 ウェルカム"}` | 다국어 이름 ({"ko": "회원가입 환영", "en": "Welcome"}) | -| description | object | `{"ko":"회원가입 완료 시 발송되는 환영 알림","en":"Welcome notification s…` | 다국어 설명 | +| type | string | `new_comment` | 알림 타입 (welcome, order_confirmed 등) | +| hook_prefix | string | `sirsoft-board` | 훅 접두사 (core.auth, sirsoft-ecommerce 등) | +| extension_type | string | `module` | 확장 타입: core, module, plugin | +| extension_identifier | string | `sirsoft-board` | 확장 식별자: core, sirsoft-board 등 | +| name | object | `{"ko":"새 댓글 알림","en":"New Comment Notification"}` | 다국어 이름 ({"ko": "회원가입 환영", "en": "Welcome"}) | +| description | object | `{"ko":"게시글에 새 댓글이 작성되면 게시글 작성자에게 발송","en":"Sent to post a…` | 다국어 설명 | | variables | array | `[{"key":"name","description":"수신자 이름"},{"key":"app_name",…` | 사용 가능 변수 메타데이터 ([{key, description}]) | | channels | array | `["mail","database"]` | 활성 채널 (["mail", "database"]) | -| hooks | array | `["core.auth.after_register"]` | 트리거 훅 목록 (["core.auth.after_register"]) | +| hooks | array | `["sirsoft-board.comment.after_create"]` | 트리거 훅 목록 (["core.auth.after_register"]) | | is_active | boolean | `true` | active 여부 | | is_default | boolean | `true` | default 여부 | | templates | array | `[{"id":1,"definition_id":1,"channel":"mail","subject":{"k…` | 채널별 알림 템플릿 목록 (templates 관계 로드 시 NotificationTemplateResource 배열, 미로드 시 null) | -| created_at | string | `2026-05-27 15:20:18` | 생성 일시 | -| updated_at | string | `2026-06-30 13:33:16` | 최종 수정 일시 | +| has_customized_templates | boolean | `false` | 기본값에서 수정된 템플릿이 하나라도 있는지. `template_channel` 로 좁혀도 **모든 채널**을 기준으로 판정하므로, 지금 보고 있지 않은 채널만 수정된 경우에도 true 다 (되돌리기 버튼 노출 조건) | +| created_at | string | `2026-07-30 18:45:11` | 생성 일시 | +| updated_at | string | `2026-07-30 18:45:11` | 최종 수정 일시 | | abilities | object | `{"can_update":true,"can_delete":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | **응답 예시** @@ -86,199 +88,28 @@ HTTP/1.1 200 { "number": 1, "id": 1, - "type": "apidoc-sample.event", - "hook_prefix": "core", - "extension_type": "core", - "extension_identifier": "", - "name": { - "ko": "API 문서 샘플 알림", - "en": "API Doc Sample Notification" - }, - "description": { - "ko": "문서 실측용 알림 정의", - "en": "Sample notification" - }, - "variables": [], - "channels": [ - "database", - "mail" - ], - "hooks": [], - "is_active": true, - "is_default": false, - "templates": [ - { - "id": 1, - "definition_id": 1, - "channel": "mail", - "subject": "API 문서 샘플 템플릿 제목", - "body": "안녕하세요 {{name}} 님, 문서 실측용 본문입니다.", - "click_url": "/admin/apidoc-sample", - "recipients": [ - { - "type": "role", - "value": "admin", - "display_name": "관리자" - } - ], - "is_active": true, - "is_default": false, - "user_overrides": null, - "updated_by": "a234c2b1-cde8-437f-b28b-23323be2b98d", - "created_at": "2026-07-08 10:41:24", - "updated_at": "2026-07-08 10:41:24", - "abilities": { - "can_update": true, - "can_delete": true - } - } - ], - "created_at": "2026-07-08 10:41:24", - "updated_at": "2026-07-08 10:41:24", - "abilities": { - "can_update": true, - "can_delete": true - } + "type": "new_comment", + "hook_prefix": "sirsoft-board", + "extension_type": "module", + "...": "(13개 키 생략, 총 18개)" }, { "number": 2, "id": 2, - "type": "order_confirmed", - "hook_prefix": "sirsoft-ecommerce", + "type": "reply_comment", + "hook_prefix": "sirsoft-board", "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "name": { - "ko": "결제 완료", - "en": "Payment Completed" - }, - "description": { - "ko": "결제 완료 시 고객에게 발송", - "en": "Sent to customer when payment is completed" - }, - "variables": [ - { - "key": "name", - "description": "수신자 이름" - }, - { - "key": "app_name", - "description": "사이트 이름" - }, - { - "key": "order_number", - "description": "주문번호" - }, - { - "key": "total_amount", - "description": "결제 금액" - }, - { - "key": "order_url", - "description": "주문 상세 URL" - }, - { - "key": "site_url", - "description": "사이트 URL" - }, - { - "key": "shipping_recipient_name", - "description": "수취인 이름" - }, - { - "key": "shipping_country_name", - "description": "배송국가명" - }, - { - "key": "shipping_address", - "description": "배송지 주소" - } - ], - "channels": [ - "mail", - "database" - ], - "hooks": [ - "sirsoft-ecommerce.order.after_confirm" - ], - "is_active": true, - "is_default": true, - "templates": [ - { - "id": 2, - "definition_id": 2, - "channel": "mail", - "subject": { - "ko": "[{app_name}] 결제가 완료되었습니다 (주문번호: {order_number})", - "en": "[{app_name}] Your payment has been completed (Order #{order_number})" - }, - "body": { - "ko": "

결제 완료

{name}님, 결제가 완료되었습니다. 주문해 주셔서 감사합니다.

주문번호: {order_number}

결제금액: {total_amount}

받는분: {shipping_recipient_name}

배송국가: {shipping_country_name}

배송지: {shipping_address}

주문 상세 내용은 아래 버튼을 클릭하여 확인하실 수 있습니다.


본 메일은 {app_name}에서 발송되었습니다.

", - "en": "

Payment Completed

Dear {name}, your payment has been completed. Thank you for your order.

Order Number: {order_number}

Total Amount: {total_amount}

Recipient: {shipping_recipient_name}

Shipping Country: {shipping_country_name}

Shipping Address: {shipping_address}

Click the button below to view your order details.


This email was sent from {app_name}.

" - }, - "click_url": null, - "recipients": [ - { - "type": "trigger_user" - } - ], - "is_active": true, - "is_default": true, - "user_overrides": null, - "updated_by": null, - "created_at": "2026-07-08 10:43:32", - "updated_at": "2026-07-08 10:43:32", - "abilities": { - "can_update": true, - "can_delete": true - } - }, - { - "id": 3, - "definition_id": 2, - "channel": "database", - "subject": { - "ko": "결제가 완료되었습니다", - "en": "Your payment has been completed" - }, - "body": { - "ko": "{name}님, 주문번호 {order_number}의 결제가 완료되었습니다.", - "en": "{name}, payment for your order {order_number} has been completed." - }, - "click_url": "{order_url}", - "recipients": [ - { - "type": "trigger_user" - } - ], - "is_active": true, - "is_default": true, - "user_overrides": null, - "updated_by": null, - "created_at": "2026-07-08 10:43:32", - "updated_at": "2026-07-08 10:43:32", - "abilities": { - "can_update": true, - "can_delete": true - } - } - ], - "created_at": "2026-07-08 10:43:32", - "updated_at": "2026-07-08 10:43:32", - "abilities": { - "can_update": true, - "can_delete": true - } + "...": "(13개 키 생략, 총 18개)" }, - "... (총 18건 중 2건 표시)" + "... (총 23건 중 2건 표시)" ], "pagination": { "current_page": 1, "last_page": 1, "per_page": 25, - "total": 18, + "total": 23, "from": 1, - "to": 18, - "has_more_pages": false + "...": "(2개 키 생략, 총 7개)" }, "abilities": { "can_update": true @@ -327,21 +158,21 @@ _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| id | integer | `21` | 기본 키 (내부 식별자) | -| type | string | `apidoc-sample.event` | 알림 타입 (welcome, order_confirmed 등) | -| hook_prefix | string | `core` | 훅 접두사 (core.auth, sirsoft-ecommerce 등) | -| extension_type | string | `core` | 확장 타입: core, module, plugin | -| extension_identifier | string | `` | 확장 식별자: core, sirsoft-board 등 | -| name | object | `{"ko":"API 문서 샘플 알림","en":"API Doc Sample Notification"}` | 다국어 이름 ({"ko": "회원가입 환영", "en": "Welcome"}) | -| description | object | `{"ko":"문서 실측용 알림 정의","en":"Sample notification"}` | 다국어 설명 | -| variables | array | `[]` | 사용 가능 변수 메타데이터 ([{key, description}]) | -| channels | array | `["database","mail"]` | 활성 채널 (["mail", "database"]) | -| hooks | array | `[]` | 트리거 훅 목록 (["core.auth.after_register"]) | +| id | integer | `1` | 기본 키 (내부 식별자) | +| type | string | `new_comment` | 알림 타입 (welcome, order_confirmed 등) | +| hook_prefix | string | `sirsoft-board` | 훅 접두사 (core.auth, sirsoft-ecommerce 등) | +| extension_type | string | `module` | 확장 타입: core, module, plugin | +| extension_identifier | string | `sirsoft-board` | 확장 식별자: core, sirsoft-board 등 | +| name | object | `{"ko":"새 댓글 알림","en":"New Comment Notification"}` | 다국어 이름 ({"ko": "회원가입 환영", "en": "Welcome"}) | +| description | object | `{"ko":"게시글에 새 댓글이 작성되면 게시글 작성자에게 발송","en":"Sent to post a…` | 다국어 설명 | +| variables | array | `[{"key":"name","description":"수신자 이름"},{"key":"app_name",…` | 사용 가능 변수 메타데이터 ([{key, description}]) | +| channels | array | `["mail","database"]` | 활성 채널 (["mail", "database"]) | +| hooks | array | `["sirsoft-board.comment.after_create"]` | 트리거 훅 목록 (["core.auth.after_register"]) | | is_active | boolean | `true` | active 여부 | -| is_default | boolean | `false` | default 여부 | -| templates | array | `[{"id":41,"definition_id":21,"channel":"mail","subject":"…` | 채널별 알림 템플릿 목록 (templates 관계 로드 시 NotificationTemplateResource 배열, 미로드 시 null) | -| created_at | string | `2026-07-06 19:15:16` | 생성 일시 | -| updated_at | string | `2026-07-06 19:15:16` | 최종 수정 일시 | +| is_default | boolean | `true` | default 여부 | +| templates | array | `[{"id":2,"definition_id":1,"channel":"database","subject"…` | 채널별 알림 템플릿 목록 (templates 관계 로드 시 NotificationTemplateResource 배열, 미로드 시 null) | +| created_at | string | `2026-07-30 18:45:11` | 생성 일시 | +| updated_at | string | `2026-07-30 18:45:11` | 최종 수정 일시 | | abilities | object | `{"can_update":true,"can_delete":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | **응답 예시** @@ -356,55 +187,127 @@ HTTP/1.1 200 "message": "알림 정의를 조회했습니다.", "data": { "id": 1, - "type": "apidoc-sample.event", - "hook_prefix": "core", - "extension_type": "core", - "extension_identifier": "", + "type": "new_comment", + "hook_prefix": "sirsoft-board", + "extension_type": "module", + "extension_identifier": "sirsoft-board", "name": { - "ko": "API 문서 샘플 알림", - "en": "API Doc Sample Notification" + "ko": "새 댓글 알림", + "en": "New Comment Notification" }, "description": { - "ko": "문서 실측용 알림 정의", - "en": "Sample notification" + "ko": "게시글에 새 댓글이 작성되면 게시글 작성자에게 발송", + "en": "Sent to post author when a new comment is posted" }, - "variables": [], + "variables": [ + { + "key": "name", + "description": "수신자 이름" + }, + { + "key": "app_name", + "description": "사이트명" + }, + { + "key": "board_name", + "description": "게시판 이름" + }, + { + "key": "post_title", + "description": "게시글 제목" + }, + { + "key": "comment_author", + "description": "댓글 작성자" + }, + { + "key": "comment_content", + "description": "댓글 내용 (200자)" + }, + { + "key": "post_url", + "description": "게시글 URL" + }, + { + "key": "site_url", + "description": "사이트 URL" + } + ], "channels": [ - "database", - "mail" + "mail", + "database" + ], + "hooks": [ + "sirsoft-board.comment.after_create" ], - "hooks": [], "is_active": true, - "is_default": false, + "is_default": true, "templates": [ + { + "id": 2, + "definition_id": 1, + "channel": "database", + "subject": { + "ko": "게시글에 새 댓글이 달렸습니다", + "en": "New comment on your post" + }, + "body": { + "ko": "{comment_author}님이 '{board_name}' 게시글 '{post_title}'에 댓글을 남겼습니다.", + "en": "{comment_author} commented on your post '{post_title}' in '{board_name}'." + }, + "click_url": "{post_url}", + "recipients": [ + { + "type": "related_user", + "relation": "post_author", + "exclude_trigger_user": true + } + ], + "is_active": true, + "is_default": true, + "user_overrides": null, + "updated_by": null, + "created_at": "2026-07-30 18:45:11", + "updated_at": "2026-07-30 18:45:11", + "abilities": { + "can_update": true, + "can_delete": true + } + }, { "id": 1, "definition_id": 1, "channel": "mail", - "subject": "API 문서 샘플 템플릿 제목", - "body": "안녕하세요 {{name}} 님, 문서 실측용 본문입니다.", - "click_url": "/admin/apidoc-sample", + "subject": { + "ko": "[{board_name}] 게시글에 새 댓글이 등록되었습니다", + "en": "[{board_name}] New comment on your post" + }, + "body": { + "ko": "

{name}님, 안녕하세요.

{board_name} 게시판의 게시글에 {comment_author}님이 댓글을 남겼습니다.

{comment_content}
게시글 보기

감사합니다,
{app_name}

", + "en": "

Hello, {name}.

{comment_author} commented on your post in {board_name}.

{comment_content}
View Post

Thank you,
{app_name}

" + }, + "click_url": null, "recipients": [ { - "type": "role", - "value": "admin", - "display_name": "관리자" + "type": "related_user", + "relation": "post_author", + "exclude_trigger_user": true } ], "is_active": true, - "is_default": false, + "is_default": true, "user_overrides": null, - "updated_by": "a234c2b1-cde8-437f-b28b-23323be2b98d", - "created_at": "2026-07-08 10:41:24", - "updated_at": "2026-07-08 10:41:24", + "updated_by": null, + "created_at": "2026-07-30 18:45:11", + "updated_at": "2026-07-30 18:45:11", "abilities": { "can_update": true, "can_delete": true } } ], - "created_at": "2026-07-08 10:41:24", - "updated_at": "2026-07-08 10:41:24", + "created_at": "2026-07-30 18:45:11", + "updated_at": "2026-07-30 18:45:11", "abilities": { "can_update": true, "can_delete": true @@ -465,7 +368,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -513,20 +416,20 @@ _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | | id | integer | `1` | 기본 키 (내부 식별자) | -| type | string | `apidoc-sample.event` | 알림 타입 (welcome, order_confirmed 등) | -| hook_prefix | string | `core` | 훅 접두사 (core.auth, sirsoft-ecommerce 등) | -| extension_type | string | `core` | 이 리소스를 소유한 확장의 타입 (core/module/plugin/template) | -| extension_identifier | string | `` | 이 리소스를 소유한 확장의 식별자 | -| name | object | `{"ko":"API 문서 샘플 알림","en":"API Doc Sample Notification"}` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | -| description | object | `{"ko":"문서 실측용 알림 정의","en":"Sample notification"}` | 설명 (다국어 필드는 로케일별 값 객체) | -| variables | array | `[]` | 사용 가능 변수 메타데이터 ([{key, description}]) | -| channels | array | `["database","mail"]` | 활성 채널 (["mail", "database"]) | -| hooks | array | `[]` | 트리거 훅 목록 (["core.auth.after_register"]) | +| type | string | `new_comment` | 알림 타입 (welcome, order_confirmed 등) | +| hook_prefix | string | `sirsoft-board` | 훅 접두사 (core.auth, sirsoft-ecommerce 등) | +| extension_type | string | `module` | 이 리소스를 소유한 확장의 타입 (core/module/plugin/template) | +| extension_identifier | string | `sirsoft-board` | 이 리소스를 소유한 확장의 식별자 | +| name | object | `{"ko":"새 댓글 알림","en":"New Comment Notification"}` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | +| description | object | `{"ko":"게시글에 새 댓글이 작성되면 게시글 작성자에게 발송","en":"Sent to post a…` | 설명 (다국어 필드는 로케일별 값 객체) | +| variables | array | `[{"key":"name","description":"수신자 이름"},{"key":"app_name",…` | 사용 가능 변수 메타데이터 ([{key, description}]) | +| channels | array | `["mail","database"]` | 활성 채널 (["mail", "database"]) | +| hooks | array | `["sirsoft-board.comment.after_create"]` | 트리거 훅 목록 (["core.auth.after_register"]) | | is_active | boolean | `true` | active 여부 | | is_default | boolean | `true` | default 여부 | -| templates | array | `[{"id":1,"definition_id":1,"channel":"mail","subject":"AP…` | 템플릿 목록 (각 원소 identifier/name 등 — 템플릿 관계 파생) | -| created_at | string | `2026-07-08 10:41:24` | 생성 일시 | -| updated_at | string | `2026-07-08 12:14:43` | 최종 수정 일시 | +| templates | array | `[{"id":2,"definition_id":1,"channel":"database","subject"…` | 템플릿 목록 (각 원소 identifier/name 등 — 템플릿 관계 파생) | +| created_at | string | `2026-07-30 18:45:11` | 생성 일시 | +| updated_at | string | `2026-07-30 18:45:11` | 최종 수정 일시 | | abilities | object | `{"can_update":true,"can_delete":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | **응답 예시** @@ -541,55 +444,127 @@ HTTP/1.1 200 "message": "알림 정의의 모든 템플릿이 기본값으로 복원되었습니다.", "data": { "id": 1, - "type": "apidoc-sample.event", - "hook_prefix": "core", - "extension_type": "core", - "extension_identifier": "", + "type": "new_comment", + "hook_prefix": "sirsoft-board", + "extension_type": "module", + "extension_identifier": "sirsoft-board", "name": { - "ko": "API 문서 샘플 알림", - "en": "API Doc Sample Notification" + "ko": "새 댓글 알림", + "en": "New Comment Notification" }, "description": { - "ko": "문서 실측용 알림 정의", - "en": "Sample notification" + "ko": "게시글에 새 댓글이 작성되면 게시글 작성자에게 발송", + "en": "Sent to post author when a new comment is posted" }, - "variables": [], + "variables": [ + { + "key": "name", + "description": "수신자 이름" + }, + { + "key": "app_name", + "description": "사이트명" + }, + { + "key": "board_name", + "description": "게시판 이름" + }, + { + "key": "post_title", + "description": "게시글 제목" + }, + { + "key": "comment_author", + "description": "댓글 작성자" + }, + { + "key": "comment_content", + "description": "댓글 내용 (200자)" + }, + { + "key": "post_url", + "description": "게시글 URL" + }, + { + "key": "site_url", + "description": "사이트 URL" + } + ], "channels": [ - "database", - "mail" + "mail", + "database" + ], + "hooks": [ + "sirsoft-board.comment.after_create" ], - "hooks": [], "is_active": true, "is_default": true, "templates": [ { - "id": 1, + "id": 2, "definition_id": 1, - "channel": "mail", - "subject": "API 문서 샘플 템플릿 제목", - "body": "안녕하세요 {{name}} 님, 문서 실측용 본문입니다.", - "click_url": "/admin/apidoc-sample", + "channel": "database", + "subject": { + "ko": "게시글에 새 댓글이 달렸습니다", + "en": "New comment on your post" + }, + "body": { + "ko": "{comment_author}님이 '{board_name}' 게시글 '{post_title}'에 댓글을 남겼습니다.", + "en": "{comment_author} commented on your post '{post_title}' in '{board_name}'." + }, + "click_url": "{post_url}", "recipients": [ { - "type": "role", - "value": "admin", - "display_name": "관리자" + "type": "related_user", + "relation": "post_author", + "exclude_trigger_user": true } ], "is_active": true, - "is_default": false, + "is_default": true, "user_overrides": null, - "updated_by": "a234c2b1-cde8-437f-b28b-23323be2b98d", - "created_at": "2026-07-08 10:41:24", - "updated_at": "2026-07-08 10:41:24", + "updated_by": null, + "created_at": "2026-07-30 18:45:11", + "updated_at": "2026-07-30 18:45:11", + "abilities": { + "can_update": true, + "can_delete": true + } + }, + { + "id": 1, + "definition_id": 1, + "channel": "mail", + "subject": { + "ko": "[{board_name}] 게시글에 새 댓글이 등록되었습니다", + "en": "[{board_name}] New comment on your post" + }, + "body": { + "ko": "

{name}님, 안녕하세요.

{board_name} 게시판의 게시글에 {comment_author}님이 댓글을 남겼습니다.

{comment_content}
게시글 보기

감사합니다,
{app_name}

", + "en": "

Hello, {name}.

{comment_author} commented on your post in {board_name}.

{comment_content}
View Post

Thank you,
{app_name}

" + }, + "click_url": null, + "recipients": [ + { + "type": "related_user", + "relation": "post_author", + "exclude_trigger_user": true + } + ], + "is_active": true, + "is_default": true, + "user_overrides": null, + "updated_by": null, + "created_at": "2026-07-30 18:45:11", + "updated_at": "2026-07-30 18:45:11", "abilities": { "can_update": true, "can_delete": true } } ], - "created_at": "2026-07-08 10:41:24", - "updated_at": "2026-07-08 12:14:43", + "created_at": "2026-07-30 18:45:11", + "updated_at": "2026-07-30 18:45:11", "abilities": { "can_update": true, "can_delete": true @@ -639,20 +614,19 @@ _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | | id | integer | `1` | 기본 키 (내부 식별자) | -| type | string | `apidoc-sample.event` | 알림 타입 (welcome, order_confirmed 등) | -| hook_prefix | string | `core` | 훅 접두사 (core.auth, sirsoft-ecommerce 등) | -| extension_type | string | `core` | 이 리소스를 소유한 확장의 타입 (core/module/plugin/template) | -| extension_identifier | string | `` | 이 리소스를 소유한 확장의 식별자 | -| name | object | `{"ko":"API 문서 샘플 알림","en":"API Doc Sample Notification"}` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | -| description | object | `{"ko":"문서 실측용 알림 정의","en":"Sample notification"}` | 설명 (다국어 필드는 로케일별 값 객체) | -| variables | array | `[]` | 사용 가능 변수 메타데이터 ([{key, description}]) | -| channels | array | `["database","mail"]` | 활성 채널 (["mail", "database"]) | -| hooks | array | `[]` | 트리거 훅 목록 (["core.auth.after_register"]) | +| type | string | `new_comment` | 알림 타입 (welcome, order_confirmed 등) | +| hook_prefix | string | `sirsoft-board` | 훅 접두사 (core.auth, sirsoft-ecommerce 등) | +| extension_type | string | `module` | 이 리소스를 소유한 확장의 타입 (core/module/plugin/template) | +| extension_identifier | string | `sirsoft-board` | 이 리소스를 소유한 확장의 식별자 | +| name | object | `{"ko":"새 댓글 알림","en":"New Comment Notification"}` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | +| description | object | `{"ko":"게시글에 새 댓글이 작성되면 게시글 작성자에게 발송","en":"Sent to post a…` | 설명 (다국어 필드는 로케일별 값 객체) | +| variables | array | `[{"key":"name","description":"수신자 이름"},{"key":"app_name",…` | 사용 가능 변수 메타데이터 ([{key, description}]) | +| channels | array | `["mail","database"]` | 활성 채널 (["mail", "database"]) | +| hooks | array | `["sirsoft-board.comment.after_create"]` | 트리거 훅 목록 (["core.auth.after_register"]) | | is_active | boolean | `false` | active 여부 | -| is_default | boolean | `false` | default 여부 | -| templates | null | `null` | 템플릿 목록 (각 원소 identifier/name 등 — 템플릿 관계 파생) | -| created_at | string | `2026-07-08 10:41:24` | 생성 일시 | -| updated_at | string | `2026-07-08 12:14:43` | 최종 수정 일시 | +| is_default | boolean | `true` | default 여부 | +| created_at | string | `2026-07-30 18:45:11` | 생성 일시 | +| updated_at | string | `2026-08-04 21:53:43` | 최종 수정 일시 | | abilities | object | `{"can_update":true,"can_delete":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | **응답 예시** @@ -667,29 +641,63 @@ HTTP/1.1 200 "message": "알림 정의 활성 상태가 변경되었습니다.", "data": { "id": 1, - "type": "apidoc-sample.event", - "hook_prefix": "core", - "extension_type": "core", - "extension_identifier": "", + "type": "new_comment", + "hook_prefix": "sirsoft-board", + "extension_type": "module", + "extension_identifier": "sirsoft-board", "name": { - "ko": "API 문서 샘플 알림", - "en": "API Doc Sample Notification" + "ko": "새 댓글 알림", + "en": "New Comment Notification" }, "description": { - "ko": "문서 실측용 알림 정의", - "en": "Sample notification" + "ko": "게시글에 새 댓글이 작성되면 게시글 작성자에게 발송", + "en": "Sent to post author when a new comment is posted" }, - "variables": [], + "variables": [ + { + "key": "name", + "description": "수신자 이름" + }, + { + "key": "app_name", + "description": "사이트명" + }, + { + "key": "board_name", + "description": "게시판 이름" + }, + { + "key": "post_title", + "description": "게시글 제목" + }, + { + "key": "comment_author", + "description": "댓글 작성자" + }, + { + "key": "comment_content", + "description": "댓글 내용 (200자)" + }, + { + "key": "post_url", + "description": "게시글 URL" + }, + { + "key": "site_url", + "description": "사이트 URL" + } + ], "channels": [ - "database", - "mail" + "mail", + "database" + ], + "hooks": [ + "sirsoft-board.comment.after_create" ], - "hooks": [], "is_active": false, - "is_default": false, - "templates": null, - "created_at": "2026-07-08 10:41:24", - "updated_at": "2026-07-08 12:14:43", + "is_default": true, + "created_at": "2026-07-30 18:45:11", + "updated_at": "2026-08-04 21:53:43", "abilities": { "can_update": true, "can_delete": true diff --git a/docs/backend/api/notification-logs.md b/docs/backend/api/notification-logs.md index 6c15c53a..7fd951fd 100644 --- a/docs/backend/api/notification-logs.md +++ b/docs/backend/api/notification-logs.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Notification Logs 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` @@ -55,26 +55,26 @@ _목록 응답: `data.data[]` 배열 항목의 필드 + `data.pagination`._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| number | integer | `677` | 목록에서의 순번 (페이지네이션 반영 행 번호 — HasRowNumber 파생) | -| id | integer | `677` | 기본 키 (내부 식별자) | +| number | integer | `7549` | 목록에서의 순번 (페이지네이션 반영 행 번호 — HasRowNumber 파생) | +| id | integer | `7559` | 기본 키 (내부 식별자) | | channel | string | `mail` | 채널: mail, database, fcm 등 | | notification_type | string | `apidoc.sample.event` | 알림 타입: welcome, order_confirmed 등 | | extension_type | string | `core` | 확장 타입: core, module, plugin | | extension_identifier | string | `` | 확장 식별자 | -| recipient_user_id | integer | `166` | recipient user 식별자 (연관 리소스 참조) | +| recipient_user_id | integer | `1240` | recipient user 식별자 (연관 리소스 참조) | | recipient_identifier | string | `apidoc-sample-user@example.com` | 수신자 식별자 (채널별: 이메일, 디바이스토큰, user_id 등) | | recipient_name | string | `API 문서 샘플 사용자` | 수신자 표시명 (발송 시점 스냅샷) | -| sender_user_id | integer | `166` | sender user 식별자 (연관 리소스 참조) | -| sender | object | `{"uuid":"a231747f-e82e-4cf2-9ae1-a261849dce40","name":"AP…` | 발신자 사용자 객체 (uuid/name/email — senderUser 관계 파생) | -| recipient | object | `{"uuid":"a231747f-e82e-4cf2-9ae1-a261849dce40","name":"AP…` | 수신자 사용자 객체 (uuid/name/email — recipientUser 관계 파생) | +| sender_user_id | integer | `1240` | sender user 식별자 (연관 리소스 참조) | +| sender | object | `{"uuid":"a2640dae-e87c-4a28-b4f1-481fd961dc02","name":"AP…` | 발신자 사용자 객체 (uuid/name/email — senderUser 관계 파생) | +| recipient | object | `{"uuid":"a2640dae-e87c-4a28-b4f1-481fd961dc02","name":"AP…` | 수신자 사용자 객체 (uuid/name/email — recipientUser 관계 파생) | | subject | string | `API 문서 샘플 알림` | 렌더링된 제목 | | body | string | `문서 실측용 알림 본문입니다.` | 렌더링된 본문 | | status | string | `sent` | 상태: sent, failed, skipped | -| error_message | string | `해당 채널은 비회원 발송을 허용하지 않아 발송을 건너뛰었습니다.` | 에러 메시지 | +| error_message | string | `SMTP 호스트가 설정되지 않았습니다.` | 에러 메시지 | | source | string | `apidoc` | 발송 출처: notification, test_mail 등 | -| sent_at | string | `2026-07-06 18:20:23` | sent 일시 | -| created_at | string | `2026-07-06 19:20:23` | 생성 일시 | -| updated_at | string | `2026-07-06 19:20:23` | 최종 수정 일시 | +| sent_at | string | `2026-07-31 21:55:00` | sent 일시 | +| created_at | string | `2026-07-31 22:55:00` | 생성 일시 | +| updated_at | string | `2026-07-31 22:55:00` | 최종 수정 일시 | | abilities | object | `{"can_delete":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | **응답 예시** @@ -90,23 +90,23 @@ HTTP/1.1 200 "data": { "data": [ { - "number": 1, - "id": 1, + "number": 7549, + "id": 7559, "channel": "mail", "notification_type": "apidoc.sample.event", "extension_type": "core", "extension_identifier": "", - "recipient_user_id": 6, + "recipient_user_id": 1240, "recipient_identifier": "apidoc-sample-user@example.com", "recipient_name": "API 문서 샘플 사용자", - "sender_user_id": 6, + "sender_user_id": 1240, "sender": { - "uuid": "a234c2b1-cde8-437f-b28b-23323be2b98d", + "uuid": "a2640dae-e87c-4a28-b4f1-481fd961dc02", "name": "API 문서 샘플 사용자", "email": "apidoc-sample-user@example.com" }, "recipient": { - "uuid": "a234c2b1-cde8-437f-b28b-23323be2b98d", + "uuid": "a2640dae-e87c-4a28-b4f1-481fd961dc02", "name": "API 문서 샘플 사용자", "email": "apidoc-sample-user@example.com" }, @@ -115,22 +115,48 @@ HTTP/1.1 200 "status": "sent", "error_message": null, "source": "apidoc", - "sent_at": "2026-07-08 09:41:24", - "created_at": "2026-07-08 10:41:24", - "updated_at": "2026-07-08 10:41:24", + "sent_at": "2026-07-31 21:55:00", + "created_at": "2026-07-31 22:55:00", + "updated_at": "2026-07-31 22:55:00", "abilities": { "can_delete": true } - } + }, + { + "number": 7548, + "id": 7558, + "channel": "mail", + "notification_type": "inquiry_received", + "extension_type": "module", + "extension_identifier": "sirsoft-ecommerce", + "recipient_user_id": null, + "recipient_identifier": "saemi.cho@example.com", + "recipient_name": "송민성", + "sender_user_id": null, + "sender": null, + "recipient": null, + "subject": null, + "body": null, + "status": "skipped", + "error_message": "SMTP 호스트가 설정되지 않았습니다.", + "source": "notification", + "sent_at": "2026-07-31 21:41:59", + "created_at": "2026-07-31 21:41:59", + "updated_at": "2026-07-31 21:41:59", + "abilities": { + "can_delete": true + } + }, + "... (총 25건 중 2건 표시)" ], "pagination": { "current_page": 1, - "last_page": 1, + "last_page": 302, "per_page": 25, - "total": 1, + "total": 7549, "from": 1, - "to": 1, - "has_more_pages": false + "to": 25, + "has_more_pages": true }, "abilities": { "can_delete": true @@ -182,7 +208,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** diff --git a/docs/backend/api/notification-templates.md b/docs/backend/api/notification-templates.md index 1610a578..c6f83599 100644 --- a/docs/backend/api/notification-templates.md +++ b/docs/backend/api/notification-templates.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Notification Templates 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` @@ -28,7 +28,7 @@ | 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | | --- | --- | --- | --- | --- | --- | | definition_id | body | integer | 예 | — | definition 식별자 | -| subject | body | array | 아니오 | — | 다국어 제목. 컬럼이 nullable 이므로 미전송·`null` 이 허용된다(제목 개념이 없는 채널 대응). 전송하는 경우 로케일별 문자열 배열이어야 하며 각 값은 500자 이하 | +| subject | body | array | 예 | — | 다국어 제목. 컬럼이 nullable 이므로 미전송·`null` 이 허용된다(제목 개념이 없는 채널 대응). 전송하는 경우 로케일별 문자열 배열이어야 하며 각 값은 500자 이하 | | body | body | array | 예 | — | 본문 | | locale | body | string | 아니오 | max 10 | 로케일 코드 (표시 언어/지역) | @@ -55,7 +55,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -119,7 +119,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -162,11 +162,68 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - +_단건 응답: `data` 객체의 필드._ + +| 필드 | 타입 | 실측 예시값 | 용도/설명 | +| --- | --- | --- | --- | +| id | integer | `1` | 기본 키 (내부 식별자) | +| definition_id | integer | `1` | definition 식별자 (연관 리소스 참조) | +| channel | string | `mail` | 채널: mail, database, fcm | +| subject | object | `{"ko":"[{board_name}] 게시글에 새 댓글이 등록되었습니다","en":"[{board_n…` | 다국어 제목 ({"ko": "...", "en": "..."}) | +| body | object | `{"ko":"

{name}님, 안녕하세요.<\/h1>

{board_name}<\…` | 다국어 본문 ({"ko": "...", "en": "..."}) | +| click_url | null | `null` | click URL | +| recipients | array | `[{"type":"related_user","relation":"post_author","exclude…` | 수신자 규칙 JSON ([{type, value, relation, exclude_trigger_user}]) | +| is_active | boolean | `true` | active 여부 | +| is_default | boolean | `true` | default 여부 | +| user_overrides | null | `null` | 사용자가 수정한 필드명 목록 | +| updated_by | null | `null` | 최종 수정한 사용자 정보 (uuid/name — updated_by 관계 파생, 없으면 null) | +| created_at | string | `2026-07-30 18:45:11` | 생성 일시 | +| updated_at | string | `2026-07-30 18:45:11` | 최종 수정 일시 | +| abilities | object | `{"can_update":true,"can_delete":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | **응답 예시** - +```http +HTTP/1.1 200 +``` + +```json +{ + "success": true, + "message": "알림 템플릿이 기본값으로 복원되었습니다.", + "data": { + "id": 1, + "definition_id": 1, + "channel": "mail", + "subject": { + "ko": "[{board_name}] 게시글에 새 댓글이 등록되었습니다", + "en": "[{board_name}] New comment on your post" + }, + "body": { + "ko": "

{name}님, 안녕하세요.

{board_name} 게시판의 게시글에 {comment_author}님이 댓글을 남겼습니다.

{comment_content}
게시글 보기

감사합니다,
{app_name}

", + "en": "

Hello, {name}.

{comment_author} commented on your post in {board_name}.

{comment_content}
View Post

Thank you,
{app_name}

" + }, + "click_url": null, + "recipients": [ + { + "type": "related_user", + "relation": "post_author", + "exclude_trigger_user": true + } + ], + "is_active": true, + "is_default": true, + "user_overrides": null, + "updated_by": null, + "created_at": "2026-07-30 18:45:11", + "updated_at": "2026-07-30 18:45:11", + "abilities": { + "can_update": true, + "can_delete": true + } + } +} +``` **에러 응답** @@ -211,16 +268,16 @@ _단건 응답: `data` 객체의 필드._ | id | integer | `1` | 기본 키 (내부 식별자) | | definition_id | integer | `1` | definition 식별자 (연관 리소스 참조) | | channel | string | `mail` | 채널: mail, database, fcm | -| subject | string | `API 문서 샘플 템플릿 제목` | 다국어 제목 ({"ko": "...", "en": "..."}) | -| body | string | `안녕하세요 {{name}} 님, 문서 실측용 본문입니다.` | 다국어 본문 ({"ko": "...", "en": "..."}) | -| click_url | string | `/admin/apidoc-sample` | click URL | -| recipients | array | `[{"type":"role","value":"admin","display_name":"관리자"}]` | 수신자 규칙 JSON ([{type, value, relation, exclude_trigger_user}]) | +| subject | object | `{"ko":"[{board_name}] 게시글에 새 댓글이 등록되었습니다","en":"[{board_n…` | 다국어 제목 ({"ko": "...", "en": "..."}) | +| body | object | `{"ko":"

{name}님, 안녕하세요.<\/h1>

{board_name}<\…` | 다국어 본문 ({"ko": "...", "en": "..."}) | +| click_url | null | `null` | click URL | +| recipients | array | `[{"type":"related_user","relation":"post_author","exclude…` | 수신자 규칙 JSON ([{type, value, relation, exclude_trigger_user}]) | | is_active | boolean | `false` | active 여부 | -| is_default | boolean | `false` | default 여부 | +| is_default | boolean | `true` | default 여부 | | user_overrides | array | `["is_active"]` | 사용자가 수정한 필드명 목록 | -| updated_by | string | `a234c2b1-cde8-437f-b28b-23323be2b98d` | 최종 수정한 사용자 정보 (uuid/name — updated_by 관계 파생, 없으면 null) | -| created_at | string | `2026-07-08 10:41:24` | 생성 일시 | -| updated_at | string | `2026-07-08 12:14:43` | 최종 수정 일시 | +| updated_by | null | `null` | 최종 수정한 사용자 정보 (uuid/name — updated_by 관계 파생, 없으면 null) | +| created_at | string | `2026-07-30 18:45:11` | 생성 일시 | +| updated_at | string | `2026-08-04 21:53:43` | 최종 수정 일시 | | abilities | object | `{"can_update":true,"can_delete":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | **응답 예시** @@ -237,24 +294,30 @@ HTTP/1.1 200 "id": 1, "definition_id": 1, "channel": "mail", - "subject": "API 문서 샘플 템플릿 제목", - "body": "안녕하세요 {{name}} 님, 문서 실측용 본문입니다.", - "click_url": "/admin/apidoc-sample", + "subject": { + "ko": "[{board_name}] 게시글에 새 댓글이 등록되었습니다", + "en": "[{board_name}] New comment on your post" + }, + "body": { + "ko": "

{name}님, 안녕하세요.

{board_name} 게시판의 게시글에 {comment_author}님이 댓글을 남겼습니다.

{comment_content}
게시글 보기

감사합니다,
{app_name}

", + "en": "

Hello, {name}.

{comment_author} commented on your post in {board_name}.

{comment_content}
View Post

Thank you,
{app_name}

" + }, + "click_url": null, "recipients": [ { - "type": "role", - "value": "admin", - "display_name": "관리자" + "type": "related_user", + "relation": "post_author", + "exclude_trigger_user": true } ], "is_active": false, - "is_default": false, + "is_default": true, "user_overrides": [ "is_active" ], - "updated_by": "a234c2b1-cde8-437f-b28b-23323be2b98d", - "created_at": "2026-07-08 10:41:24", - "updated_at": "2026-07-08 12:14:43", + "updated_by": null, + "created_at": "2026-07-30 18:45:11", + "updated_at": "2026-08-04 21:53:43", "abilities": { "can_update": true, "can_delete": true diff --git a/docs/backend/api/notifications.md b/docs/backend/api/notifications.md index d7b39d50..8edee41b 100644 --- a/docs/backend/api/notifications.md +++ b/docs/backend/api/notifications.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Notifications 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` @@ -47,7 +47,18 @@ Authorization: Bearer {YOUR_TOKEN} _목록 응답: `data.data[]` 배열 항목의 필드 + `data.pagination`._ - +| 필드 | 타입 | 실측 예시값 | 용도/설명 | +| --- | --- | --- | --- | +| number | integer | `23` | 목록에서의 순번 (페이지네이션 반영 행 번호 — HasRowNumber 파생) | +| id | string | `518b9c94-5853-4b68-8193-83af1851bbc6` | 기본 키 (내부 식별자) | +| type | string | `inquiry_received` | | +| type_label | string | `상품 문의 접수` | `type` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | +| subject | string | `새로운 상품 문의가 접수되었습니다` | | +| body | string | `옥혜진님이 "면 손수건 3매입 #1" 상품에 문의를 남겼습니다.` | | +| url | string | `https://g7_2.dev/admin/ecommerce/prod…` | | +| data | object | `{"type":"inquiry_received","subject":"새로운 상품 문의가 접수되었습니다"…` | | +| read_at | string | `2026-07-31 22:06:53` | read 일시 | +| created_at | string | `2026-07-31 21:41:58` | 생성 일시 | **응답 예시** @@ -60,14 +71,68 @@ HTTP/1.1 200 "success": true, "message": "알림 목록을 조회했습니다.", "data": { - "data": [], + "data": [ + { + "number": 23, + "id": "518b9c94-5853-4b68-8193-83af1851bbc6", + "type": "inquiry_received", + "type_label": "상품 문의 접수", + "subject": "새로운 상품 문의가 접수되었습니다", + "body": "옥혜진님이 \"면 손수건 3매입 #1\" 상품에 문의를 남겼습니다.", + "url": "https://api.example.com/admin/ecommerce/product-inquiries", + "data": { + "type": "inquiry_received", + "subject": "새로운 상품 문의가 접수되었습니다", + "body": "옥혜진님이 \"면 손수건 3매입 #1\" 상품에 문의를 남겼습니다.", + "click_url": "https://api.example.com/admin/ecommerce/product-inquiries", + "data": { + "name": "최고관리자", + "app_name": "그누보드7", + "product_name": "면 손수건 3매입 #1", + "customer_name": "옥혜진", + "inquiry_content": "타인 계정 문의 3 입니다. 스코프 격리 검증용.", + "inquiry_url": "https://api.example.com/admin/ecommerce/product-inquiries", + "site_url": "https://api.example.com" + } + }, + "read_at": null, + "created_at": "2026-07-31 21:41:58" + }, + { + "number": 22, + "id": "75da60b8-3ec9-457f-ad8c-b52652f7adcf", + "type": "inquiry_received", + "type_label": "상품 문의 접수", + "subject": "새로운 상품 문의가 접수되었습니다", + "body": "옥혜진님이 \"면 손수건 3매입 #1\" 상품에 문의를 남겼습니다.", + "url": "https://api.example.com/admin/ecommerce/product-inquiries", + "data": { + "type": "inquiry_received", + "subject": "새로운 상품 문의가 접수되었습니다", + "body": "옥혜진님이 \"면 손수건 3매입 #1\" 상품에 문의를 남겼습니다.", + "click_url": "https://api.example.com/admin/ecommerce/product-inquiries", + "data": { + "name": "최고관리자", + "app_name": "그누보드7", + "product_name": "면 손수건 3매입 #1", + "customer_name": "옥혜진", + "inquiry_content": "타인 계정 문의 2 입니다. 스코프 격리 검증용.", + "inquiry_url": "https://api.example.com/admin/ecommerce/product-inquiries", + "site_url": "https://api.example.com" + } + }, + "read_at": null, + "created_at": "2026-07-31 21:41:55" + }, + "... (총 23건 중 2건 표시)" + ], "pagination": { "current_page": 1, "last_page": 1, "per_page": 25, - "total": 0, - "from": null, - "to": null, + "total": 23, + "from": 1, + "to": 23, "has_more_pages": false } } @@ -114,7 +179,7 @@ _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| deleted_count | integer | `0` | deleted 개수 (집계) | +| deleted_count | integer | `23` | deleted 개수 (집계) | **응답 예시** @@ -127,7 +192,7 @@ HTTP/1.1 200 "success": true, "message": "모든 알림이 삭제되었습니다.", "data": { - "deleted_count": 0 + "deleted_count": 23 } } ``` @@ -171,7 +236,7 @@ _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| marked_count | integer | `0` | marked 개수 (집계) | +| marked_count | integer | `7` | marked 개수 (집계) | **응답 예시** @@ -184,7 +249,7 @@ HTTP/1.1 200 "success": true, "message": "모든 알림을 읽음 처리했습니다.", "data": { - "marked_count": 0 + "marked_count": 7 } } ``` @@ -235,7 +300,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -281,7 +346,7 @@ _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| unread_count | integer | `0` | unread 개수 (집계) | +| unread_count | integer | `7` | unread 개수 (집계) | **응답 예시** @@ -294,7 +359,7 @@ HTTP/1.1 200 "success": true, "message": "미읽음 알림 수를 조회했습니다.", "data": { - "unread_count": 0 + "unread_count": 7 } } ``` @@ -336,7 +401,7 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - + **응답 예시** @@ -380,7 +445,7 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - + **응답 예시** @@ -431,7 +496,18 @@ Authorization: Bearer {YOUR_TOKEN} (optional.sanctum: 비회원은 헤더 생 _목록 응답: `data.data[]` 배열 항목의 필드 + `data.pagination`._ - +| 필드 | 타입 | 실측 예시값 | 용도/설명 | +| --- | --- | --- | --- | +| number | integer | `23` | 목록에서의 순번 (페이지네이션 반영 행 번호 — HasRowNumber 파생) | +| id | string | `518b9c94-5853-4b68-8193-83af1851bbc6` | 기본 키 (내부 식별자) | +| type | string | `inquiry_received` | | +| type_label | string | `상품 문의 접수` | `type` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | +| subject | string | `새로운 상품 문의가 접수되었습니다` | | +| body | string | `옥혜진님이 "면 손수건 3매입 #1" 상품에 문의를 남겼습니다.` | | +| url | string | `https://g7_2.dev/admin/ecommerce/prod…` | | +| data | object | `{"type":"inquiry_received","subject":"새로운 상품 문의가 접수되었습니다"…` | | +| read_at | string | `2026-07-31 22:06:53` | read 일시 | +| created_at | string | `2026-07-31 21:41:58` | 생성 일시 | **응답 예시** @@ -444,14 +520,68 @@ HTTP/1.1 200 "success": true, "message": "알림 목록을 조회했습니다.", "data": { - "data": [], + "data": [ + { + "number": 23, + "id": "518b9c94-5853-4b68-8193-83af1851bbc6", + "type": "inquiry_received", + "type_label": "상품 문의 접수", + "subject": "새로운 상품 문의가 접수되었습니다", + "body": "옥혜진님이 \"면 손수건 3매입 #1\" 상품에 문의를 남겼습니다.", + "url": "https://api.example.com/admin/ecommerce/product-inquiries", + "data": { + "type": "inquiry_received", + "subject": "새로운 상품 문의가 접수되었습니다", + "body": "옥혜진님이 \"면 손수건 3매입 #1\" 상품에 문의를 남겼습니다.", + "click_url": "https://api.example.com/admin/ecommerce/product-inquiries", + "data": { + "name": "최고관리자", + "app_name": "그누보드7", + "product_name": "면 손수건 3매입 #1", + "customer_name": "옥혜진", + "inquiry_content": "타인 계정 문의 3 입니다. 스코프 격리 검증용.", + "inquiry_url": "https://api.example.com/admin/ecommerce/product-inquiries", + "site_url": "https://api.example.com" + } + }, + "read_at": null, + "created_at": "2026-07-31 21:41:58" + }, + { + "number": 22, + "id": "75da60b8-3ec9-457f-ad8c-b52652f7adcf", + "type": "inquiry_received", + "type_label": "상품 문의 접수", + "subject": "새로운 상품 문의가 접수되었습니다", + "body": "옥혜진님이 \"면 손수건 3매입 #1\" 상품에 문의를 남겼습니다.", + "url": "https://api.example.com/admin/ecommerce/product-inquiries", + "data": { + "type": "inquiry_received", + "subject": "새로운 상품 문의가 접수되었습니다", + "body": "옥혜진님이 \"면 손수건 3매입 #1\" 상품에 문의를 남겼습니다.", + "click_url": "https://api.example.com/admin/ecommerce/product-inquiries", + "data": { + "name": "최고관리자", + "app_name": "그누보드7", + "product_name": "면 손수건 3매입 #1", + "customer_name": "옥혜진", + "inquiry_content": "타인 계정 문의 2 입니다. 스코프 격리 검증용.", + "inquiry_url": "https://api.example.com/admin/ecommerce/product-inquiries", + "site_url": "https://api.example.com" + } + }, + "read_at": null, + "created_at": "2026-07-31 21:41:55" + }, + "... (총 23건 중 2건 표시)" + ], "pagination": { "current_page": 1, "last_page": 1, "per_page": 25, - "total": 0, - "from": null, - "to": null, + "total": 23, + "from": 1, + "to": 23, "has_more_pages": false } } @@ -497,7 +627,7 @@ _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| deleted_count | integer | `0` | deleted 개수 (집계) | +| deleted_count | integer | `23` | deleted 개수 (집계) | **응답 예시** @@ -510,7 +640,7 @@ HTTP/1.1 200 "success": true, "message": "모든 알림이 삭제되었습니다.", "data": { - "deleted_count": 0 + "deleted_count": 23 } } ``` @@ -553,7 +683,7 @@ _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| marked_count | integer | `0` | marked 개수 (집계) | +| marked_count | integer | `7` | marked 개수 (집계) | **응답 예시** @@ -566,7 +696,7 @@ HTTP/1.1 200 "success": true, "message": "모든 알림을 읽음 처리했습니다.", "data": { - "marked_count": 0 + "marked_count": 7 } } ``` @@ -616,7 +746,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -661,7 +791,7 @@ _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| unread_count | integer | `0` | unread 개수 (집계) | +| unread_count | integer | `7` | unread 개수 (집계) | **응답 예시** @@ -674,7 +804,7 @@ HTTP/1.1 200 "success": true, "message": "미읽음 알림 수를 조회했습니다.", "data": { - "unread_count": 0 + "unread_count": 7 } } ``` @@ -715,7 +845,7 @@ Authorization: Bearer {YOUR_TOKEN} (optional.sanctum: 비회원은 헤더 생 **응답 필드** (`data` 내부) - + **응답 예시** @@ -758,7 +888,7 @@ Authorization: Bearer {YOUR_TOKEN} (optional.sanctum: 비회원은 헤더 생 **응답 필드** (`data` 내부) - + **응답 예시** diff --git a/docs/backend/api/password.md b/docs/backend/api/password.md index ef99e7f7..ddc9b676 100644 --- a/docs/backend/api/password.md +++ b/docs/backend/api/password.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Password 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` @@ -51,7 +51,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** diff --git a/docs/backend/api/permissions.md b/docs/backend/api/permissions.md index 48a23900..f35a10bd 100644 --- a/docs/backend/api/permissions.md +++ b/docs/backend/api/permissions.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Permissions 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` @@ -42,8 +42,8 @@ _목록 응답: `data.data[]` 배열 항목의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| admin | object | `{"label":"관리자 권한","icon":"cog","permissions":[{"id":1,"id…` | 관리자(admin) 타입 권한 그룹. `label`·`icon`(PermissionType::Admin 의 label()/icon() 산물)과 admin 타입으로 필터링된 권한 트리(`permissions`)를 담는다. | -| user | object | `{"label":"사용자 권한","icon":"user","permissions":[{"id":1,"i…` | 사용자(user) 타입 권한 그룹. `label`·`icon`(PermissionType::User 의 label()/icon() 산물)과 user 타입으로 필터링된 권한 트리(`permissions`)를 담는다. | +| admin | object | `{"label":"관리자 권한","icon":"cog","permissions":[{"id":1799,…` | 관리자(admin) 타입 권한 그룹. `label`·`icon`(PermissionType::Admin 의 label()/icon() 산물)과 admin 타입으로 필터링된 권한 트리(`permissions`)를 담는다. | +| user | object | `{"label":"사용자 권한","icon":"user","permissions":[{"id":1715…` | 사용자(user) 타입 권한 그룹. `label`·`icon`(PermissionType::User 의 label()/icon() 산물)과 user 타입으로 필터링된 권한 트리(`permissions`)를 담는다. | **응답 예시** @@ -57,1701 +57,59 @@ HTTP/1.1 200 "message": "권한 정보를 성공적으로 가져왔습니다.", "data": { "data": { + "0": "... (총 4건 중 2건 표시)", "permissions": { "admin": { "label": "관리자 권한", "icon": "cog", "permissions": [ { - "id": 115, + "id": 1799, + "identifier": "sirsoft-ecommerce.coupons.read", + "name": "sirsoft-ecommerce.coupons.read", + "name_raw": { + "ko": "sirsoft-ecommerce.coupons.read", + "en": "sirsoft-ecommerce.coupons.read" + }, + "description": "sirsoft-ecommerce.coupons.read", + "...": "(9개 키 생략, 총 14개)" + }, + { + "id": 1810, + "identifier": "core.plugins.manage", + "name": "core.plugins.manage", + "name_raw": { + "ko": "core.plugins.manage", + "en": "core.plugins.manage" + }, + "description": "core.plugins.manage", + "...": "(9개 키 생략, 총 14개)" + }, + { + "id": 1811, + "identifier": "sirsoft-gdpr.consent-log.read", + "name": "sirsoft-gdpr.consent-log.read", + "name_raw": { + "ko": "sirsoft-gdpr.consent-log.read", + "en": "sirsoft-gdpr.consent-log.read" + }, + "description": "sirsoft-gdpr.consent-log.read", + "...": "(9개 키 생략, 총 14개)" + }, + { + "id": 1715, "identifier": "core", "name": "코어", "name_raw": { "ko": "코어", - "en": "Core" + "en": "Core", + "ja": "コア" }, "description": "코어 시스템 권한", - "description_raw": { - "ko": "코어 시스템 권한", - "en": "Core system permissions" - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": false, - "resource_route_key": null, - "owner_key": null, - "children": [ - { - "id": 116, - "identifier": "core.users", - "name": "사용자 관리", - "name_raw": { - "ko": "사용자 관리", - "en": "User Management" - }, - "description": "사용자 관리 권한", - "description_raw": { - "ko": "사용자 관리 권한", - "en": "User management permissions" - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": false, - "resource_route_key": null, - "owner_key": null, - "children": [ - { - "id": 117, - "identifier": "core.users.read", - "name": "사용자 조회", - "name_raw": { - "ko": "사용자 조회", - "en": "View Users" - }, - "description": "사용자 목록 및 상세 정보를 조회할 수 있습니다.", - "description_raw": { - "ko": "사용자 목록 및 상세 정보를 조회할 수 있습니다.", - "en": "Can view user list and details." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": true, - "resource_route_key": "user", - "owner_key": "id", - "children": [], - "leaf_count": 1 - }, - { - "id": 118, - "identifier": "core.users.create", - "name": "사용자 생성", - "name_raw": { - "ko": "사용자 생성", - "en": "Create Users" - }, - "description": "새로운 사용자를 생성할 수 있습니다.", - "description_raw": { - "ko": "새로운 사용자를 생성할 수 있습니다.", - "en": "Can create new users." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 119, - "identifier": "core.users.update", - "name": "사용자 수정", - "name_raw": { - "ko": "사용자 수정", - "en": "Update Users" - }, - "description": "사용자 정보를 수정할 수 있습니다.", - "description_raw": { - "ko": "사용자 정보를 수정할 수 있습니다.", - "en": "Can update user information." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": true, - "resource_route_key": "user", - "owner_key": "id", - "children": [], - "leaf_count": 1 - }, - { - "id": 120, - "identifier": "core.users.delete", - "name": "사용자 삭제", - "name_raw": { - "ko": "사용자 삭제", - "en": "Delete Users" - }, - "description": "사용자를 삭제할 수 있습니다.", - "description_raw": { - "ko": "사용자를 삭제할 수 있습니다.", - "en": "Can delete users." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": true, - "resource_route_key": "user", - "owner_key": "id", - "children": [], - "leaf_count": 1 - } - ], - "leaf_count": 4 - }, - { - "id": 121, - "identifier": "core.menus", - "name": "메뉴 관리", - "name_raw": { - "ko": "메뉴 관리", - "en": "Menu Management" - }, - "description": "메뉴 관리 권한", - "description_raw": { - "ko": "메뉴 관리 권한", - "en": "Menu management permissions" - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": false, - "resource_route_key": null, - "owner_key": null, - "children": [ - { - "id": 122, - "identifier": "core.menus.read", - "name": "메뉴 조회", - "name_raw": { - "ko": "메뉴 조회", - "en": "View Menus" - }, - "description": "메뉴 목록을 조회할 수 있습니다.", - "description_raw": { - "ko": "메뉴 목록을 조회할 수 있습니다.", - "en": "Can view menu list." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": true, - "resource_route_key": "menu", - "owner_key": "created_by", - "children": [], - "leaf_count": 1 - }, - { - "id": 123, - "identifier": "core.menus.create", - "name": "메뉴 생성", - "name_raw": { - "ko": "메뉴 생성", - "en": "Create Menus" - }, - "description": "새로운 메뉴를 생성할 수 있습니다.", - "description_raw": { - "ko": "새로운 메뉴를 생성할 수 있습니다.", - "en": "Can create new menus." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 124, - "identifier": "core.menus.update", - "name": "메뉴 수정", - "name_raw": { - "ko": "메뉴 수정", - "en": "Update Menus" - }, - "description": "메뉴 정보를 수정할 수 있습니다.", - "description_raw": { - "ko": "메뉴 정보를 수정할 수 있습니다.", - "en": "Can update menu information." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": true, - "resource_route_key": "menu", - "owner_key": "created_by", - "children": [], - "leaf_count": 1 - }, - { - "id": 125, - "identifier": "core.menus.delete", - "name": "메뉴 삭제", - "name_raw": { - "ko": "메뉴 삭제", - "en": "Delete Menus" - }, - "description": "메뉴를 삭제할 수 있습니다.", - "description_raw": { - "ko": "메뉴를 삭제할 수 있습니다.", - "en": "Can delete menus." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": true, - "resource_route_key": "menu", - "owner_key": "created_by", - "children": [], - "leaf_count": 1 - } - ], - "leaf_count": 4 - }, - { - "id": 126, - "identifier": "core.modules", - "name": "모듈 관리", - "name_raw": { - "ko": "모듈 관리", - "en": "Module Management" - }, - "description": "모듈 관리 권한", - "description_raw": { - "ko": "모듈 관리 권한", - "en": "Module management permissions" - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": false, - "resource_route_key": null, - "owner_key": null, - "children": [ - { - "id": 127, - "identifier": "core.modules.read", - "name": "모듈 조회", - "name_raw": { - "ko": "모듈 조회", - "en": "View Modules" - }, - "description": "모듈 목록을 조회할 수 있습니다.", - "description_raw": { - "ko": "모듈 목록을 조회할 수 있습니다.", - "en": "Can view module list." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 128, - "identifier": "core.modules.install", - "name": "모듈 설치", - "name_raw": { - "ko": "모듈 설치", - "en": "Install Modules" - }, - "description": "새로운 모듈을 설치할 수 있습니다.", - "description_raw": { - "ko": "새로운 모듈을 설치할 수 있습니다.", - "en": "Can install new modules." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 129, - "identifier": "core.modules.activate", - "name": "모듈 활성화", - "name_raw": { - "ko": "모듈 활성화", - "en": "Activate Modules" - }, - "description": "모듈을 활성화/비활성화할 수 있습니다.", - "description_raw": { - "ko": "모듈을 활성화/비활성화할 수 있습니다.", - "en": "Can activate/deactivate modules." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 130, - "identifier": "core.modules.uninstall", - "name": "모듈 삭제", - "name_raw": { - "ko": "모듈 삭제", - "en": "Uninstall Modules" - }, - "description": "모듈을 삭제할 수 있습니다.", - "description_raw": { - "ko": "모듈을 삭제할 수 있습니다.", - "en": "Can uninstall modules." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - } - ], - "leaf_count": 4 - }, - { - "id": 131, - "identifier": "core.plugins", - "name": "플러그인 관리", - "name_raw": { - "ko": "플러그인 관리", - "en": "Plugin Management" - }, - "description": "플러그인 관리 권한", - "description_raw": { - "ko": "플러그인 관리 권한", - "en": "Plugin management permissions" - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": false, - "resource_route_key": null, - "owner_key": null, - "children": [ - { - "id": 132, - "identifier": "core.plugins.read", - "name": "플러그인 조회", - "name_raw": { - "ko": "플러그인 조회", - "en": "View Plugins" - }, - "description": "플러그인 목록을 조회할 수 있습니다.", - "description_raw": { - "ko": "플러그인 목록을 조회할 수 있습니다.", - "en": "Can view plugin list." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 133, - "identifier": "core.plugins.install", - "name": "플러그인 설치", - "name_raw": { - "ko": "플러그인 설치", - "en": "Install Plugins" - }, - "description": "새로운 플러그인을 설치할 수 있습니다.", - "description_raw": { - "ko": "새로운 플러그인을 설치할 수 있습니다.", - "en": "Can install new plugins." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 134, - "identifier": "core.plugins.activate", - "name": "플러그인 활성화", - "name_raw": { - "ko": "플러그인 활성화", - "en": "Activate Plugins" - }, - "description": "플러그인을 활성화/비활성화할 수 있습니다.", - "description_raw": { - "ko": "플러그인을 활성화/비활성화할 수 있습니다.", - "en": "Can activate/deactivate plugins." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 135, - "identifier": "core.plugins.update", - "name": "플러그인 설정", - "name_raw": { - "ko": "플러그인 설정", - "en": "Configure Plugins" - }, - "description": "플러그인 환경설정을 수정할 수 있습니다.", - "description_raw": { - "ko": "플러그인 환경설정을 수정할 수 있습니다.", - "en": "Can update plugin settings." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 136, - "identifier": "core.plugins.uninstall", - "name": "플러그인 삭제", - "name_raw": { - "ko": "플러그인 삭제", - "en": "Uninstall Plugins" - }, - "description": "플러그인을 삭제할 수 있습니다.", - "description_raw": { - "ko": "플러그인을 삭제할 수 있습니다.", - "en": "Can uninstall plugins." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - } - ], - "leaf_count": 5 - }, - { - "id": 137, - "identifier": "core.templates", - "name": "템플릿 관리", - "name_raw": { - "ko": "템플릿 관리", - "en": "Template Management" - }, - "description": "템플릿 관리 권한", - "description_raw": { - "ko": "템플릿 관리 권한", - "en": "Template management permissions" - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": false, - "resource_route_key": null, - "owner_key": null, - "children": [ - { - "id": 138, - "identifier": "core.templates.read", - "name": "템플릿 조회", - "name_raw": { - "ko": "템플릿 조회", - "en": "View Templates" - }, - "description": "템플릿 목록을 조회할 수 있습니다.", - "description_raw": { - "ko": "템플릿 목록을 조회할 수 있습니다.", - "en": "Can view template list." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 139, - "identifier": "core.templates.install", - "name": "템플릿 설치", - "name_raw": { - "ko": "템플릿 설치", - "en": "Install Templates" - }, - "description": "새로운 템플릿을 설치할 수 있습니다.", - "description_raw": { - "ko": "새로운 템플릿을 설치할 수 있습니다.", - "en": "Can install new templates." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 140, - "identifier": "core.templates.activate", - "name": "템플릿 활성화", - "name_raw": { - "ko": "템플릿 활성화", - "en": "Activate Templates" - }, - "description": "템플릿을 활성화/비활성화할 수 있습니다.", - "description_raw": { - "ko": "템플릿을 활성화/비활성화할 수 있습니다.", - "en": "Can activate/deactivate templates." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 141, - "identifier": "core.templates.uninstall", - "name": "템플릿 삭제", - "name_raw": { - "ko": "템플릿 삭제", - "en": "Uninstall Templates" - }, - "description": "템플릿을 삭제할 수 있습니다.", - "description_raw": { - "ko": "템플릿을 삭제할 수 있습니다.", - "en": "Can uninstall templates." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 142, - "identifier": "core.templates.layouts.edit", - "name": "레이아웃 편집", - "name_raw": { - "ko": "레이아웃 편집", - "en": "Edit Layouts" - }, - "description": "템플릿 레이아웃을 편집할 수 있습니다.", - "description_raw": { - "ko": "템플릿 레이아웃을 편집할 수 있습니다.", - "en": "Can edit template layouts." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - } - ], - "leaf_count": 5 - }, - { - "id": 143, - "identifier": "core.permissions", - "name": "권한 관리", - "name_raw": { - "ko": "권한 관리", - "en": "Permission Management" - }, - "description": "역할 및 권한 관리 권한", - "description_raw": { - "ko": "역할 및 권한 관리 권한", - "en": "Role and permission management permissions" - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": false, - "resource_route_key": null, - "owner_key": null, - "children": [ - { - "id": 144, - "identifier": "core.permissions.read", - "name": "권한 조회", - "name_raw": { - "ko": "권한 조회", - "en": "View Permissions" - }, - "description": "역할 및 권한 목록을 조회할 수 있습니다.", - "description_raw": { - "ko": "역할 및 권한 목록을 조회할 수 있습니다.", - "en": "Can view roles and permissions." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 145, - "identifier": "core.permissions.create", - "name": "역할 생성", - "name_raw": { - "ko": "역할 생성", - "en": "Create Roles" - }, - "description": "새로운 역할을 생성할 수 있습니다.", - "description_raw": { - "ko": "새로운 역할을 생성할 수 있습니다.", - "en": "Can create new roles." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 146, - "identifier": "core.permissions.update", - "name": "역할 수정", - "name_raw": { - "ko": "역할 수정", - "en": "Update Roles" - }, - "description": "역할 정보와 권한을 수정할 수 있습니다.", - "description_raw": { - "ko": "역할 정보와 권한을 수정할 수 있습니다.", - "en": "Can update role information and permissions." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 147, - "identifier": "core.permissions.delete", - "name": "역할 삭제", - "name_raw": { - "ko": "역할 삭제", - "en": "Delete Roles" - }, - "description": "역할을 삭제할 수 있습니다.", - "description_raw": { - "ko": "역할을 삭제할 수 있습니다.", - "en": "Can delete roles." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - } - ], - "leaf_count": 4 - }, - { - "id": 148, - "identifier": "core.notification-logs", - "name": "알림 발송 이력", - "name_raw": { - "ko": "알림 발송 이력", - "en": "Notification Logs" - }, - "description": "알림 발송 이력 관리 권한", - "description_raw": { - "ko": "알림 발송 이력 관리 권한", - "en": "Notification log management permissions" - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": false, - "resource_route_key": null, - "owner_key": null, - "children": [ - { - "id": 149, - "identifier": "core.notification-logs.read", - "name": "발송 이력 조회", - "name_raw": { - "ko": "발송 이력 조회", - "en": "View Notification Logs" - }, - "description": "알림 발송 이력을 조회할 수 있습니다.", - "description_raw": { - "ko": "알림 발송 이력을 조회할 수 있습니다.", - "en": "Can view notification logs." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 150, - "identifier": "core.notification-logs.delete", - "name": "발송 이력 삭제", - "name_raw": { - "ko": "발송 이력 삭제", - "en": "Delete Notification Logs" - }, - "description": "알림 발송 이력을 삭제할 수 있습니다.", - "description_raw": { - "ko": "알림 발송 이력을 삭제할 수 있습니다.", - "en": "Can delete notification logs." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - } - ], - "leaf_count": 2 - }, - { - "id": 151, - "identifier": "core.notifications", - "name": "알림 (관리자)", - "name_raw": { - "ko": "알림 (관리자)", - "en": "Notifications (Admin)" - }, - "description": "관리자용 알림 관리 권한 (관리자 화면에서 사용)", - "description_raw": { - "ko": "관리자용 알림 관리 권한 (관리자 화면에서 사용)", - "en": "Admin notification management permissions (used in admin UI)" - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": false, - "resource_route_key": null, - "owner_key": null, - "children": [ - { - "id": 152, - "identifier": "core.notifications.read", - "name": "알림 조회", - "name_raw": { - "ko": "알림 조회", - "en": "View Notifications" - }, - "description": "알림 목록 및 읽지 않은 수를 조회할 수 있습니다.", - "description_raw": { - "ko": "알림 목록 및 읽지 않은 수를 조회할 수 있습니다.", - "en": "Can view notification list and unread count." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 153, - "identifier": "core.notifications.update", - "name": "알림 읽음 처리", - "name_raw": { - "ko": "알림 읽음 처리", - "en": "Mark Notifications Read" - }, - "description": "알림을 읽음 처리할 수 있습니다.", - "description_raw": { - "ko": "알림을 읽음 처리할 수 있습니다.", - "en": "Can mark notifications as read." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 154, - "identifier": "core.notifications.delete", - "name": "알림 삭제", - "name_raw": { - "ko": "알림 삭제", - "en": "Delete Notifications" - }, - "description": "알림을 삭제할 수 있습니다.", - "description_raw": { - "ko": "알림을 삭제할 수 있습니다.", - "en": "Can delete notifications." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - } - ], - "leaf_count": 3 - }, - { - "id": 163, - "identifier": "core.admin.identity", - "name": "본인인증 관리 (관리자)", - "name_raw": { - "ko": "본인인증 관리 (관리자)", - "en": "Identity Verification Management (Admin)" - }, - "description": "관리자용 IDV 프로바이더/정책/로그 관리 권한", - "description_raw": { - "ko": "관리자용 IDV 프로바이더/정책/로그 관리 권한", - "en": "Admin permissions for IDV providers, policies, and logs" - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": false, - "resource_route_key": null, - "owner_key": null, - "children": [ - { - "id": 164, - "identifier": "core.admin.identity.providers.read", - "name": "프로바이더 설정 조회", - "name_raw": { - "ko": "프로바이더 설정 조회", - "en": "View IDV Providers" - }, - "description": "본인인증 프로바이더 설정을 조회할 수 있습니다.", - "description_raw": { - "ko": "본인인증 프로바이더 설정을 조회할 수 있습니다.", - "en": "Can view identity verification providers." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 165, - "identifier": "core.admin.identity.providers.update", - "name": "프로바이더 설정 수정", - "name_raw": { - "ko": "프로바이더 설정 수정", - "en": "Update IDV Providers" - }, - "description": "본인인증 프로바이더 설정을 수정할 수 있습니다.", - "description_raw": { - "ko": "본인인증 프로바이더 설정을 수정할 수 있습니다.", - "en": "Can update identity verification providers." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 166, - "identifier": "core.admin.identity.policies.read", - "name": "정책 조회", - "name_raw": { - "ko": "정책 조회", - "en": "View IDV Policies" - }, - "description": "본인인증 정책(라우트/훅별)을 조회할 수 있습니다.", - "description_raw": { - "ko": "본인인증 정책(라우트/훅별)을 조회할 수 있습니다.", - "en": "Can view identity verification policies by route/hook." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 167, - "identifier": "core.admin.identity.policies.update", - "name": "정책 수정", - "name_raw": { - "ko": "정책 수정", - "en": "Update IDV Policies" - }, - "description": "본인인증 정책(라우트/훅별)을 CRUD 할 수 있습니다.", - "description_raw": { - "ko": "본인인증 정책(라우트/훅별)을 CRUD 할 수 있습니다.", - "en": "Can CRUD identity verification policies by route/hook." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 168, - "identifier": "core.admin.identity.logs.read", - "name": "로그 열람", - "name_raw": { - "ko": "로그 열람", - "en": "View IDV Logs" - }, - "description": "본인인증 이력을 조회할 수 있습니다.", - "description_raw": { - "ko": "본인인증 이력을 조회할 수 있습니다.", - "en": "Can view identity verification logs." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 169, - "identifier": "core.admin.identity.logs.purge", - "name": "로그 파기", - "name_raw": { - "ko": "로그 파기", - "en": "Purge IDV Logs" - }, - "description": "본인인증 이력을 파기(보관주기 외)할 수 있습니다.", - "description_raw": { - "ko": "본인인증 이력을 파기(보관주기 외)할 수 있습니다.", - "en": "Can purge identity verification logs (retention-based)." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 170, - "identifier": "core.admin.identity.messages.read", - "name": "메시지 템플릿 조회", - "name_raw": { - "ko": "메시지 템플릿 조회", - "en": "View IDV Message Templates" - }, - "description": "본인인증 메시지 정의/템플릿을 조회할 수 있습니다.", - "description_raw": { - "ko": "본인인증 메시지 정의/템플릿을 조회할 수 있습니다.", - "en": "Can view identity verification message definitions and templates." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 171, - "identifier": "core.admin.identity.messages.update", - "name": "메시지 템플릿 수정", - "name_raw": { - "ko": "메시지 템플릿 수정", - "en": "Update IDV Message Templates" - }, - "description": "본인인증 메시지 정의/템플릿을 수정할 수 있습니다.", - "description_raw": { - "ko": "본인인증 메시지 정의/템플릿을 수정할 수 있습니다.", - "en": "Can update identity verification message definitions and templates." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - } - ], - "leaf_count": 8 - }, - { - "id": 172, - "identifier": "core.settings", - "name": "환경설정", - "name_raw": { - "ko": "환경설정", - "en": "Settings" - }, - "description": "시스템 환경설정 권한", - "description_raw": { - "ko": "시스템 환경설정 권한", - "en": "System settings permissions" - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": false, - "resource_route_key": null, - "owner_key": null, - "children": [ - { - "id": 173, - "identifier": "core.settings.read", - "name": "설정 조회", - "name_raw": { - "ko": "설정 조회", - "en": "View Settings" - }, - "description": "시스템 설정을 조회할 수 있습니다.", - "description_raw": { - "ko": "시스템 설정을 조회할 수 있습니다.", - "en": "Can view system settings." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 174, - "identifier": "core.settings.update", - "name": "설정 수정", - "name_raw": { - "ko": "설정 수정", - "en": "Update Settings" - }, - "description": "시스템 설정을 수정할 수 있습니다.", - "description_raw": { - "ko": "시스템 설정을 수정할 수 있습니다.", - "en": "Can update system settings." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - } - ], - "leaf_count": 2 - }, - { - "id": 175, - "identifier": "core.dashboard", - "name": "대시보드", - "name_raw": { - "ko": "대시보드", - "en": "Dashboard" - }, - "description": "대시보드 접근 권한", - "description_raw": { - "ko": "대시보드 접근 권한", - "en": "Dashboard access permissions" - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": false, - "resource_route_key": null, - "owner_key": null, - "children": [ - { - "id": 176, - "identifier": "core.dashboard.read", - "name": "대시보드 조회", - "name_raw": { - "ko": "대시보드 조회", - "en": "View Dashboard" - }, - "description": "대시보드 통계 및 정보를 조회할 수 있습니다.", - "description_raw": { - "ko": "대시보드 통계 및 정보를 조회할 수 있습니다.", - "en": "Can view dashboard statistics and information." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 177, - "identifier": "core.dashboard.system-status", - "name": "시스템 상태", - "name_raw": { - "ko": "시스템 상태", - "en": "System Status" - }, - "description": "시스템 상태 정보를 조회할 수 있습니다.", - "description_raw": { - "ko": "시스템 상태 정보를 조회할 수 있습니다.", - "en": "Can view system status information." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 178, - "identifier": "core.dashboard.resources", - "name": "시스템 리소스", - "name_raw": { - "ko": "시스템 리소스", - "en": "System Resources" - }, - "description": "CPU, 메모리, 디스크 사용량을 조회할 수 있습니다.", - "description_raw": { - "ko": "CPU, 메모리, 디스크 사용량을 조회할 수 있습니다.", - "en": "Can view CPU, memory, and disk usage." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 179, - "identifier": "core.dashboard.activities", - "name": "최근 활동", - "name_raw": { - "ko": "최근 활동", - "en": "Recent Activities" - }, - "description": "최근 활동 이력을 조회할 수 있습니다.", - "description_raw": { - "ko": "최근 활동 이력을 조회할 수 있습니다.", - "en": "Can view recent activity history." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": true, - "resource_route_key": "activityLog", - "owner_key": "user_id", - "children": [], - "leaf_count": 1 - }, - { - "id": 180, - "identifier": "core.dashboard.alerts", - "name": "시스템 알림", - "name_raw": { - "ko": "시스템 알림", - "en": "System Alerts" - }, - "description": "시스템 알림을 조회할 수 있습니다.", - "description_raw": { - "ko": "시스템 알림을 조회할 수 있습니다.", - "en": "Can view system alerts." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - } - ], - "leaf_count": 5 - }, - { - "id": 181, - "identifier": "core.activities", - "name": "활동 로그", - "name_raw": { - "ko": "활동 로그", - "en": "Activity Logs" - }, - "description": "활동 로그 조회 권한", - "description_raw": { - "ko": "활동 로그 조회 권한", - "en": "Activity log access permissions" - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": false, - "resource_route_key": null, - "owner_key": null, - "children": [ - { - "id": 182, - "identifier": "core.activities.read", - "name": "활동 로그 조회", - "name_raw": { - "ko": "활동 로그 조회", - "en": "View Activity Logs" - }, - "description": "활동 로그를 조회할 수 있습니다.", - "description_raw": { - "ko": "활동 로그를 조회할 수 있습니다.", - "en": "Can view activity logs." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": true, - "resource_route_key": "activityLog", - "owner_key": "user_id", - "children": [], - "leaf_count": 1 - }, - { - "id": 183, - "identifier": "core.activities.delete", - "name": "활동 로그 삭제", - "name_raw": { - "ko": "활동 로그 삭제", - "en": "Delete Activity Logs" - }, - "description": "활동 로그를 삭제할 수 있습니다.", - "description_raw": { - "ko": "활동 로그를 삭제할 수 있습니다.", - "en": "Can delete activity logs." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - } - ], - "leaf_count": 2 - }, - { - "id": 184, - "identifier": "core.attachments", - "name": "첨부파일 관리", - "name_raw": { - "ko": "첨부파일 관리", - "en": "Attachment Management" - }, - "description": "첨부파일 관리 권한", - "description_raw": { - "ko": "첨부파일 관리 권한", - "en": "Attachment management permissions" - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": false, - "resource_route_key": null, - "owner_key": null, - "children": [ - { - "id": 185, - "identifier": "core.attachments.create", - "name": "첨부파일 업로드", - "name_raw": { - "ko": "첨부파일 업로드", - "en": "Upload Attachments" - }, - "description": "첨부파일을 업로드할 수 있습니다.", - "description_raw": { - "ko": "첨부파일을 업로드할 수 있습니다.", - "en": "Can upload attachments." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 186, - "identifier": "core.attachments.update", - "name": "첨부파일 수정", - "name_raw": { - "ko": "첨부파일 수정", - "en": "Update Attachments" - }, - "description": "첨부파일 정보 및 순서를 수정할 수 있습니다.", - "description_raw": { - "ko": "첨부파일 정보 및 순서를 수정할 수 있습니다.", - "en": "Can update attachment information and order." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": true, - "resource_route_key": "attachment", - "owner_key": "created_by", - "children": [], - "leaf_count": 1 - }, - { - "id": 187, - "identifier": "core.attachments.delete", - "name": "첨부파일 삭제", - "name_raw": { - "ko": "첨부파일 삭제", - "en": "Delete Attachments" - }, - "description": "첨부파일을 삭제할 수 있습니다.", - "description_raw": { - "ko": "첨부파일을 삭제할 수 있습니다.", - "en": "Can delete attachments." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": true, - "resource_route_key": "attachment", - "owner_key": "created_by", - "children": [], - "leaf_count": 1 - } - ], - "leaf_count": 3 - }, - { - "id": 188, - "identifier": "core.schedules", - "name": "스케줄 관리", - "name_raw": { - "ko": "스케줄 관리", - "en": "Schedule Management" - }, - "description": "스케줄 작업 관리 권한", - "description_raw": { - "ko": "스케줄 작업 관리 권한", - "en": "Schedule task management permissions" - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": false, - "resource_route_key": null, - "owner_key": null, - "children": [ - { - "id": 189, - "identifier": "core.schedules.read", - "name": "스케줄 조회", - "name_raw": { - "ko": "스케줄 조회", - "en": "View Schedules" - }, - "description": "스케줄 목록 및 상세 정보를 조회할 수 있습니다.", - "description_raw": { - "ko": "스케줄 목록 및 상세 정보를 조회할 수 있습니다.", - "en": "Can view schedule list and details." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 190, - "identifier": "core.schedules.create", - "name": "스케줄 생성", - "name_raw": { - "ko": "스케줄 생성", - "en": "Create Schedules" - }, - "description": "새로운 스케줄을 생성할 수 있습니다.", - "description_raw": { - "ko": "새로운 스케줄을 생성할 수 있습니다.", - "en": "Can create new schedules." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 191, - "identifier": "core.schedules.update", - "name": "스케줄 수정", - "name_raw": { - "ko": "스케줄 수정", - "en": "Update Schedules" - }, - "description": "스케줄 정보를 수정할 수 있습니다.", - "description_raw": { - "ko": "스케줄 정보를 수정할 수 있습니다.", - "en": "Can update schedule information." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 192, - "identifier": "core.schedules.delete", - "name": "스케줄 삭제", - "name_raw": { - "ko": "스케줄 삭제", - "en": "Delete Schedules" - }, - "description": "스케줄을 삭제할 수 있습니다.", - "description_raw": { - "ko": "스케줄을 삭제할 수 있습니다.", - "en": "Can delete schedules." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 193, - "identifier": "core.schedules.run", - "name": "스케줄 실행", - "name_raw": { - "ko": "스케줄 실행", - "en": "Run Schedules" - }, - "description": "스케줄을 수동으로 실행할 수 있습니다.", - "description_raw": { - "ko": "스케줄을 수동으로 실행할 수 있습니다.", - "en": "Can manually run schedules." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - } - ], - "leaf_count": 5 - }, - { - "id": 194, - "identifier": "core.language_packs", - "name": "언어팩 관리", - "name_raw": { - "ko": "언어팩 관리", - "en": "Language Pack Management" - }, - "description": "언어팩 설치/제거/활성화 권한", - "description_raw": { - "ko": "언어팩 설치/제거/활성화 권한", - "en": "Language pack install/uninstall/activation permissions" - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": false, - "resource_route_key": null, - "owner_key": null, - "children": [ - { - "id": 195, - "identifier": "core.language_packs.read", - "name": "언어팩 조회", - "name_raw": { - "ko": "언어팩 조회", - "en": "View Language Packs" - }, - "description": "설치된 언어팩 목록 및 상세 정보를 조회할 수 있습니다.", - "description_raw": { - "ko": "설치된 언어팩 목록 및 상세 정보를 조회할 수 있습니다.", - "en": "Can view installed language pack list and details." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 196, - "identifier": "core.language_packs.install", - "name": "언어팩 설치", - "name_raw": { - "ko": "언어팩 설치", - "en": "Install Language Packs" - }, - "description": "ZIP/GitHub/URL 로 언어팩을 설치할 수 있습니다.", - "description_raw": { - "ko": "ZIP/GitHub/URL 로 언어팩을 설치할 수 있습니다.", - "en": "Can install language packs from ZIP/GitHub/URL." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 197, - "identifier": "core.language_packs.manage", - "name": "언어팩 관리", - "name_raw": { - "ko": "언어팩 관리", - "en": "Manage Language Packs" - }, - "description": "언어팩을 활성화/비활성화/제거할 수 있습니다.", - "description_raw": { - "ko": "언어팩을 활성화/비활성화/제거할 수 있습니다.", - "en": "Can activate/deactivate/uninstall language packs." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 198, - "identifier": "core.language_packs.update", - "name": "언어팩 업데이트", - "name_raw": { - "ko": "언어팩 업데이트", - "en": "Update Language Packs" - }, - "description": "설치된 언어팩의 업데이트를 확인하고 실행할 수 있습니다.", - "description_raw": { - "ko": "설치된 언어팩의 업데이트를 확인하고 실행할 수 있습니다.", - "en": "Can check and apply updates for installed language packs." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - } - ], - "leaf_count": 4 - } - ], - "leaf_count": 60 + "...": "(9개 키 생략, 총 14개)" }, { - "id": 1, + "id": 1808, "identifier": "apidoc-sample.parent", "name": "API 문서 샘플 권한", "name_raw": { @@ -1759,2180 +117,9 @@ HTTP/1.1 200 "en": "API Doc Sample Permission" }, "description": "문서 실측용 권한", - "description_raw": { - "ko": "문서 실측용 권한", - "en": "Sample permission" - }, - "extension_type": null, - "extension_identifier": null, - "type": "admin", - "is_assignable": false, - "resource_route_key": null, - "owner_key": null, - "children": [ - { - "id": 2, - "identifier": "apidoc-sample.child", - "name": "하위 권한", - "name_raw": { - "ko": "하위 권한", - "en": "Child Permission" - }, - "description": "Quod consequatur sunt necessitatibus quam quia voluptas quod aperiam.", - "description_raw": { - "ko": "Quod consequatur sunt necessitatibus quam quia voluptas quod aperiam.", - "en": "Mollitia incidunt dolor rem maxime." - }, - "extension_type": null, - "extension_identifier": null, - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - } - ], - "leaf_count": 1 + "...": "(9개 키 생략, 총 14개)" }, - { - "id": 3, - "identifier": "sirsoft-board", - "name": "게시판", - "name_raw": { - "ko": "게시판", - "en": "Board" - }, - "description": "게시판 모듈 권한", - "description_raw": { - "ko": "게시판 모듈 권한", - "en": "Board module permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-board", - "type": "admin", - "is_assignable": false, - "resource_route_key": null, - "owner_key": null, - "children": [ - { - "id": 4, - "identifier": "sirsoft-board.apidoc-sample-board", - "name": "API 문서 샘플 게시판 게시판", - "name_raw": { - "ko": "API 문서 샘플 게시판 게시판", - "en": "API Doc Sample Board board" - }, - "description": "API 문서 샘플 게시판 게시판 권한", - "description_raw": { - "ko": "API 문서 샘플 게시판 게시판 권한", - "en": "API Doc Sample Board board permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-board", - "type": "admin", - "is_assignable": false, - "resource_route_key": null, - "owner_key": null, - "children": [ - { - "id": 5, - "identifier": "sirsoft-board.apidoc-sample-board.admin.posts.read", - "name": "게시글 조회 (관리자)", - "name_raw": { - "ko": "게시글 조회 (관리자)", - "en": "View Posts (Admin)" - }, - "description": "관리자 페이지에서 게시글 조회", - "description_raw": { - "ko": "관리자 페이지에서 게시글 조회", - "en": "View posts in admin page" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-board", - "type": "admin", - "is_assignable": true, - "resource_route_key": "post", - "owner_key": "user_id", - "children": [], - "leaf_count": 1 - }, - { - "id": 6, - "identifier": "sirsoft-board.apidoc-sample-board.admin.posts.write", - "name": "게시글 작성/수정/삭제 (관리자)", - "name_raw": { - "ko": "게시글 작성/수정/삭제 (관리자)", - "en": "Create/Edit/Delete Posts (Admin)" - }, - "description": "관리자 페이지에서 게시글 작성 및 수정/삭제", - "description_raw": { - "ko": "관리자 페이지에서 게시글 작성 및 수정/삭제", - "en": "Create and edit/delete posts in admin page" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-board", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 7, - "identifier": "sirsoft-board.apidoc-sample-board.admin.posts.read-secret", - "name": "비밀글 조회 (관리자)", - "name_raw": { - "ko": "비밀글 조회 (관리자)", - "en": "View Secret Posts (Admin)" - }, - "description": "관리자 페이지에서 비밀글 조회", - "description_raw": { - "ko": "관리자 페이지에서 비밀글 조회", - "en": "View secret posts in admin page" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-board", - "type": "admin", - "is_assignable": true, - "resource_route_key": "post", - "owner_key": "user_id", - "children": [], - "leaf_count": 1 - }, - { - "id": 8, - "identifier": "sirsoft-board.apidoc-sample-board.admin.comments.read", - "name": "댓글 조회 (관리자)", - "name_raw": { - "ko": "댓글 조회 (관리자)", - "en": "View Comments (Admin)" - }, - "description": "관리자 페이지에서 댓글 조회", - "description_raw": { - "ko": "관리자 페이지에서 댓글 조회", - "en": "View comments in admin page" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-board", - "type": "admin", - "is_assignable": true, - "resource_route_key": "comment", - "owner_key": "user_id", - "children": [], - "leaf_count": 1 - }, - { - "id": 9, - "identifier": "sirsoft-board.apidoc-sample-board.admin.comments.write", - "name": "댓글 작성/수정/삭제 (관리자)", - "name_raw": { - "ko": "댓글 작성/수정/삭제 (관리자)", - "en": "Create/Edit/Delete Comments (Admin)" - }, - "description": "관리자 페이지에서 댓글 작성 및 수정/삭제", - "description_raw": { - "ko": "관리자 페이지에서 댓글 작성 및 수정/삭제", - "en": "Create and edit/delete comments in admin page" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-board", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 10, - "identifier": "sirsoft-board.apidoc-sample-board.admin.attachments.upload", - "name": "파일 업로드/삭제 (관리자)", - "name_raw": { - "ko": "파일 업로드/삭제 (관리자)", - "en": "Upload/Delete Files (Admin)" - }, - "description": "관리자 페이지에서 파일 업로드 및 삭제", - "description_raw": { - "ko": "관리자 페이지에서 파일 업로드 및 삭제", - "en": "Upload and delete files in admin page" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-board", - "type": "admin", - "is_assignable": true, - "resource_route_key": "attachment", - "owner_key": "created_by", - "children": [], - "leaf_count": 1 - }, - { - "id": 11, - "identifier": "sirsoft-board.apidoc-sample-board.admin.attachments.download", - "name": "파일 다운로드 (관리자)", - "name_raw": { - "ko": "파일 다운로드 (관리자)", - "en": "Download Files (Admin)" - }, - "description": "관리자 페이지에서 파일 다운로드", - "description_raw": { - "ko": "관리자 페이지에서 파일 다운로드", - "en": "Download files in admin page" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-board", - "type": "admin", - "is_assignable": true, - "resource_route_key": "attachment", - "owner_key": "created_by", - "children": [], - "leaf_count": 1 - }, - { - "id": 12, - "identifier": "sirsoft-board.apidoc-sample-board.admin.manage", - "name": "게시판 관리 (관리자)", - "name_raw": { - "ko": "게시판 관리 (관리자)", - "en": "Manage Board (Admin)" - }, - "description": "타인 게시글/댓글 관리, 블라인드, 복원, 공지 설정", - "description_raw": { - "ko": "타인 게시글/댓글 관리, 블라인드, 복원, 공지 설정", - "en": "Manage others posts/comments, blind, restore, set notice" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-board", - "type": "admin", - "is_assignable": true, - "resource_route_key": "board", - "owner_key": "created_by", - "children": [], - "leaf_count": 1 - } - ], - "leaf_count": 8 - }, - { - "id": 95, - "identifier": "sirsoft-board.boards", - "name": "게시판 관리", - "name_raw": { - "ko": "게시판 관리", - "en": "Board Management" - }, - "description": "게시판 관리 권한", - "description_raw": { - "ko": "게시판 관리 권한", - "en": "Board management permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-board", - "type": "admin", - "is_assignable": false, - "resource_route_key": null, - "owner_key": null, - "children": [ - { - "id": 96, - "identifier": "sirsoft-board.boards.read", - "name": "게시판 조회", - "name_raw": { - "ko": "게시판 조회", - "en": "View Boards" - }, - "description": "게시판 목록 및 상세 조회", - "description_raw": { - "ko": "게시판 목록 및 상세 조회", - "en": "View board list and details" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-board", - "type": "admin", - "is_assignable": true, - "resource_route_key": "board", - "owner_key": "created_by", - "children": [], - "leaf_count": 1 - }, - { - "id": 97, - "identifier": "sirsoft-board.boards.create", - "name": "게시판 생성", - "name_raw": { - "ko": "게시판 생성", - "en": "Create Board" - }, - "description": "새 게시판 생성", - "description_raw": { - "ko": "새 게시판 생성", - "en": "Create new board" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-board", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 98, - "identifier": "sirsoft-board.boards.update", - "name": "게시판 수정", - "name_raw": { - "ko": "게시판 수정", - "en": "Update Board" - }, - "description": "게시판 설정 수정", - "description_raw": { - "ko": "게시판 설정 수정", - "en": "Update board settings" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-board", - "type": "admin", - "is_assignable": true, - "resource_route_key": "board", - "owner_key": "created_by", - "children": [], - "leaf_count": 1 - }, - { - "id": 99, - "identifier": "sirsoft-board.boards.delete", - "name": "게시판 삭제", - "name_raw": { - "ko": "게시판 삭제", - "en": "Delete Board" - }, - "description": "게시판 삭제", - "description_raw": { - "ko": "게시판 삭제", - "en": "Delete board" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-board", - "type": "admin", - "is_assignable": true, - "resource_route_key": "board", - "owner_key": "created_by", - "children": [], - "leaf_count": 1 - } - ], - "leaf_count": 4 - }, - { - "id": 100, - "identifier": "sirsoft-board.settings", - "name": "환경설정", - "name_raw": { - "ko": "환경설정", - "en": "Settings" - }, - "description": "게시판 환경설정 권한", - "description_raw": { - "ko": "게시판 환경설정 권한", - "en": "Board settings permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-board", - "type": "admin", - "is_assignable": false, - "resource_route_key": null, - "owner_key": null, - "children": [ - { - "id": 101, - "identifier": "sirsoft-board.settings.read", - "name": "환경설정 조회", - "name_raw": { - "ko": "환경설정 조회", - "en": "View Settings" - }, - "description": "게시판 환경설정 조회", - "description_raw": { - "ko": "게시판 환경설정 조회", - "en": "View board settings" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-board", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 102, - "identifier": "sirsoft-board.settings.update", - "name": "환경설정 수정", - "name_raw": { - "ko": "환경설정 수정", - "en": "Update Settings" - }, - "description": "게시판 환경설정 수정", - "description_raw": { - "ko": "게시판 환경설정 수정", - "en": "Update board settings" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-board", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - } - ], - "leaf_count": 2 - }, - { - "id": 103, - "identifier": "sirsoft-board.identity.policies", - "name": "게시판 본인인증 정책", - "name_raw": { - "ko": "게시판 본인인증 정책", - "en": "Board Identity Policies" - }, - "description": "게시판 컨텍스트의 본인인증 정책 관리 권한", - "description_raw": { - "ko": "게시판 컨텍스트의 본인인증 정책 관리 권한", - "en": "Manage identity verification policies in board context" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-board", - "type": "admin", - "is_assignable": false, - "resource_route_key": null, - "owner_key": null, - "children": [ - { - "id": 104, - "identifier": "sirsoft-board.identity.policies.read", - "name": "본인인증 정책 조회", - "name_raw": { - "ko": "본인인증 정책 조회", - "en": "View Identity Policies" - }, - "description": "게시판 본인인증 정책 조회", - "description_raw": { - "ko": "게시판 본인인증 정책 조회", - "en": "View board identity policies" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-board", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 105, - "identifier": "sirsoft-board.identity.policies.update", - "name": "본인인증 정책 수정", - "name_raw": { - "ko": "본인인증 정책 수정", - "en": "Update Identity Policies" - }, - "description": "게시판 본인인증 정책 수정/추가/삭제", - "description_raw": { - "ko": "게시판 본인인증 정책 수정/추가/삭제", - "en": "Update, add, delete board identity policies" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-board", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - } - ], - "leaf_count": 2 - }, - { - "id": 106, - "identifier": "sirsoft-board.reports", - "name": "게시판 신고 관리", - "name_raw": { - "ko": "게시판 신고 관리", - "en": "Report Management" - }, - "description": "게시판 신고 관리 권한", - "description_raw": { - "ko": "게시판 신고 관리 권한", - "en": "Board report management permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-board", - "type": "admin", - "is_assignable": false, - "resource_route_key": null, - "owner_key": null, - "children": [ - { - "id": 107, - "identifier": "sirsoft-board.reports.view", - "name": "신고 조회", - "name_raw": { - "ko": "신고 조회", - "en": "View Reports" - }, - "description": "신고 목록 및 상세 조회", - "description_raw": { - "ko": "신고 목록 및 상세 조회", - "en": "View report list and details" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-board", - "type": "admin", - "is_assignable": true, - "resource_route_key": "report", - "owner_key": "reporter_id", - "children": [], - "leaf_count": 1 - }, - { - "id": 108, - "identifier": "sirsoft-board.reports.manage", - "name": "신고 처리", - "name_raw": { - "ko": "신고 처리", - "en": "Manage Reports" - }, - "description": "신고 상태 변경 및 처리", - "description_raw": { - "ko": "신고 상태 변경 및 처리", - "en": "Update report status and process" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-board", - "type": "admin", - "is_assignable": true, - "resource_route_key": "report", - "owner_key": "reporter_id", - "children": [], - "leaf_count": 1 - } - ], - "leaf_count": 2 - } - ], - "leaf_count": 18 - }, - { - "id": 21, - "identifier": "sirsoft-ecommerce", - "name": "이커머스", - "name_raw": { - "ko": "이커머스", - "en": "Ecommerce" - }, - "description": "이커머스 모듈 권한", - "description_raw": { - "ko": "이커머스 모듈 권한", - "en": "Ecommerce module permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": false, - "resource_route_key": null, - "owner_key": null, - "children": [ - { - "id": 22, - "identifier": "sirsoft-ecommerce.products", - "name": "상품 관리", - "name_raw": { - "ko": "상품 관리", - "en": "Product Management" - }, - "description": "상품 관리 권한", - "description_raw": { - "ko": "상품 관리 권한", - "en": "Product management permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": false, - "resource_route_key": null, - "owner_key": null, - "children": [ - { - "id": 23, - "identifier": "sirsoft-ecommerce.products.read", - "name": "상품 조회", - "name_raw": { - "ko": "상품 조회", - "en": "Read Products" - }, - "description": "상품 목록 및 상세 조회", - "description_raw": { - "ko": "상품 목록 및 상세 조회", - "en": "Read product list and details" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": true, - "resource_route_key": "product", - "owner_key": "created_by", - "children": [], - "leaf_count": 1 - }, - { - "id": 24, - "identifier": "sirsoft-ecommerce.products.create", - "name": "상품 생성", - "name_raw": { - "ko": "상품 생성", - "en": "Create Product" - }, - "description": "새 상품 등록", - "description_raw": { - "ko": "새 상품 등록", - "en": "Create new product" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 25, - "identifier": "sirsoft-ecommerce.products.update", - "name": "상품 수정", - "name_raw": { - "ko": "상품 수정", - "en": "Update Product" - }, - "description": "상품 정보 수정", - "description_raw": { - "ko": "상품 정보 수정", - "en": "Update product information" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": true, - "resource_route_key": "product", - "owner_key": "created_by", - "children": [], - "leaf_count": 1 - }, - { - "id": 26, - "identifier": "sirsoft-ecommerce.products.delete", - "name": "상품 삭제", - "name_raw": { - "ko": "상품 삭제", - "en": "Delete Product" - }, - "description": "상품 삭제", - "description_raw": { - "ko": "상품 삭제", - "en": "Delete product" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": true, - "resource_route_key": "product", - "owner_key": "created_by", - "children": [], - "leaf_count": 1 - } - ], - "leaf_count": 4 - }, - { - "id": 27, - "identifier": "sirsoft-ecommerce.orders", - "name": "주문 관리", - "name_raw": { - "ko": "주문 관리", - "en": "Order Management" - }, - "description": "주문 관리 권한", - "description_raw": { - "ko": "주문 관리 권한", - "en": "Order management permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": false, - "resource_route_key": null, - "owner_key": null, - "children": [ - { - "id": 28, - "identifier": "sirsoft-ecommerce.orders.read", - "name": "주문 조회", - "name_raw": { - "ko": "주문 조회", - "en": "Read Orders" - }, - "description": "주문 목록 및 상세 조회", - "description_raw": { - "ko": "주문 목록 및 상세 조회", - "en": "Read order list and details" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": true, - "resource_route_key": "order", - "owner_key": "user_id", - "children": [], - "leaf_count": 1 - }, - { - "id": 29, - "identifier": "sirsoft-ecommerce.orders.update", - "name": "주문 수정", - "name_raw": { - "ko": "주문 수정", - "en": "Update Order" - }, - "description": "주문 상태 변경", - "description_raw": { - "ko": "주문 상태 변경", - "en": "Update order status" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": true, - "resource_route_key": "order", - "owner_key": "user_id", - "children": [], - "leaf_count": 1 - } - ], - "leaf_count": 2 - }, - { - "id": 30, - "identifier": "sirsoft-ecommerce.categories", - "name": "카테고리 관리", - "name_raw": { - "ko": "카테고리 관리", - "en": "Category Management" - }, - "description": "카테고리 관리 권한", - "description_raw": { - "ko": "카테고리 관리 권한", - "en": "Category management permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": false, - "resource_route_key": null, - "owner_key": null, - "children": [ - { - "id": 31, - "identifier": "sirsoft-ecommerce.categories.read", - "name": "카테고리 조회", - "name_raw": { - "ko": "카테고리 조회", - "en": "Read Categories" - }, - "description": "카테고리 목록 및 상세 조회", - "description_raw": { - "ko": "카테고리 목록 및 상세 조회", - "en": "Read category list and details" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 32, - "identifier": "sirsoft-ecommerce.categories.create", - "name": "카테고리 생성", - "name_raw": { - "ko": "카테고리 생성", - "en": "Create Category" - }, - "description": "카테고리 생성", - "description_raw": { - "ko": "카테고리 생성", - "en": "Create category" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 33, - "identifier": "sirsoft-ecommerce.categories.update", - "name": "카테고리 수정", - "name_raw": { - "ko": "카테고리 수정", - "en": "Update Category" - }, - "description": "카테고리 수정", - "description_raw": { - "ko": "카테고리 수정", - "en": "Update category" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 34, - "identifier": "sirsoft-ecommerce.categories.delete", - "name": "카테고리 삭제", - "name_raw": { - "ko": "카테고리 삭제", - "en": "Delete Category" - }, - "description": "카테고리 삭제", - "description_raw": { - "ko": "카테고리 삭제", - "en": "Delete category" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - } - ], - "leaf_count": 4 - }, - { - "id": 35, - "identifier": "sirsoft-ecommerce.brands", - "name": "브랜드 관리", - "name_raw": { - "ko": "브랜드 관리", - "en": "Brand Management" - }, - "description": "브랜드 관리 권한", - "description_raw": { - "ko": "브랜드 관리 권한", - "en": "Brand management permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": false, - "resource_route_key": null, - "owner_key": null, - "children": [ - { - "id": 36, - "identifier": "sirsoft-ecommerce.brands.read", - "name": "브랜드 조회", - "name_raw": { - "ko": "브랜드 조회", - "en": "Read Brands" - }, - "description": "브랜드 목록 및 상세 조회", - "description_raw": { - "ko": "브랜드 목록 및 상세 조회", - "en": "Read brand list and details" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": true, - "resource_route_key": "brand", - "owner_key": "created_by", - "children": [], - "leaf_count": 1 - }, - { - "id": 37, - "identifier": "sirsoft-ecommerce.brands.create", - "name": "브랜드 생성", - "name_raw": { - "ko": "브랜드 생성", - "en": "Create Brand" - }, - "description": "브랜드 생성", - "description_raw": { - "ko": "브랜드 생성", - "en": "Create brand" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 38, - "identifier": "sirsoft-ecommerce.brands.update", - "name": "브랜드 수정", - "name_raw": { - "ko": "브랜드 수정", - "en": "Update Brand" - }, - "description": "브랜드 수정", - "description_raw": { - "ko": "브랜드 수정", - "en": "Update brand" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": true, - "resource_route_key": "brand", - "owner_key": "created_by", - "children": [], - "leaf_count": 1 - }, - { - "id": 39, - "identifier": "sirsoft-ecommerce.brands.delete", - "name": "브랜드 삭제", - "name_raw": { - "ko": "브랜드 삭제", - "en": "Delete Brand" - }, - "description": "브랜드 삭제", - "description_raw": { - "ko": "브랜드 삭제", - "en": "Delete brand" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": true, - "resource_route_key": "brand", - "owner_key": "created_by", - "children": [], - "leaf_count": 1 - } - ], - "leaf_count": 4 - }, - { - "id": 40, - "identifier": "sirsoft-ecommerce.product-notice-templates", - "name": "상품정보제공고시 관리", - "name_raw": { - "ko": "상품정보제공고시 관리", - "en": "Product Notice Template Management" - }, - "description": "상품정보제공고시 템플릿 관리 권한", - "description_raw": { - "ko": "상품정보제공고시 템플릿 관리 권한", - "en": "Product notice template management permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": false, - "resource_route_key": null, - "owner_key": null, - "children": [ - { - "id": 41, - "identifier": "sirsoft-ecommerce.product-notice-templates.read", - "name": "조회", - "name_raw": { - "ko": "조회", - "en": "Read" - }, - "description": "상품정보제공고시 조회", - "description_raw": { - "ko": "상품정보제공고시 조회", - "en": "Read product notice templates" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 42, - "identifier": "sirsoft-ecommerce.product-notice-templates.create", - "name": "생성", - "name_raw": { - "ko": "생성", - "en": "Create" - }, - "description": "상품정보제공고시 생성", - "description_raw": { - "ko": "상품정보제공고시 생성", - "en": "Create product notice template" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 43, - "identifier": "sirsoft-ecommerce.product-notice-templates.update", - "name": "수정", - "name_raw": { - "ko": "수정", - "en": "Update" - }, - "description": "상품정보제공고시 수정", - "description_raw": { - "ko": "상품정보제공고시 수정", - "en": "Update product notice template" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 44, - "identifier": "sirsoft-ecommerce.product-notice-templates.delete", - "name": "삭제", - "name_raw": { - "ko": "삭제", - "en": "Delete" - }, - "description": "상품정보제공고시 삭제", - "description_raw": { - "ko": "상품정보제공고시 삭제", - "en": "Delete product notice template" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - } - ], - "leaf_count": 4 - }, - { - "id": 45, - "identifier": "sirsoft-ecommerce.product-common-infos", - "name": "공통정보 관리", - "name_raw": { - "ko": "공통정보 관리", - "en": "Product Common Info Management" - }, - "description": "상품 공통정보 관리 권한", - "description_raw": { - "ko": "상품 공통정보 관리 권한", - "en": "Product common info management permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": false, - "resource_route_key": null, - "owner_key": null, - "children": [ - { - "id": 46, - "identifier": "sirsoft-ecommerce.product-common-infos.read", - "name": "조회", - "name_raw": { - "ko": "조회", - "en": "Read" - }, - "description": "공통정보 조회", - "description_raw": { - "ko": "공통정보 조회", - "en": "Read product common info" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 47, - "identifier": "sirsoft-ecommerce.product-common-infos.create", - "name": "생성", - "name_raw": { - "ko": "생성", - "en": "Create" - }, - "description": "공통정보 생성", - "description_raw": { - "ko": "공통정보 생성", - "en": "Create product common info" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 48, - "identifier": "sirsoft-ecommerce.product-common-infos.update", - "name": "수정", - "name_raw": { - "ko": "수정", - "en": "Update" - }, - "description": "공통정보 수정", - "description_raw": { - "ko": "공통정보 수정", - "en": "Update product common info" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 49, - "identifier": "sirsoft-ecommerce.product-common-infos.delete", - "name": "삭제", - "name_raw": { - "ko": "삭제", - "en": "Delete" - }, - "description": "공통정보 삭제", - "description_raw": { - "ko": "공통정보 삭제", - "en": "Delete product common info" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - } - ], - "leaf_count": 4 - }, - { - "id": 50, - "identifier": "sirsoft-ecommerce.settings", - "name": "환경설정", - "name_raw": { - "ko": "환경설정", - "en": "Settings" - }, - "description": "이커머스 환경설정 권한", - "description_raw": { - "ko": "이커머스 환경설정 권한", - "en": "Ecommerce settings permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": false, - "resource_route_key": null, - "owner_key": null, - "children": [ - { - "id": 51, - "identifier": "sirsoft-ecommerce.settings.read", - "name": "환경설정 조회", - "name_raw": { - "ko": "환경설정 조회", - "en": "View Settings" - }, - "description": "이커머스 환경설정 조회", - "description_raw": { - "ko": "이커머스 환경설정 조회", - "en": "View ecommerce settings" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 52, - "identifier": "sirsoft-ecommerce.settings.update", - "name": "환경설정 수정", - "name_raw": { - "ko": "환경설정 수정", - "en": "Update Settings" - }, - "description": "이커머스 환경설정 수정", - "description_raw": { - "ko": "이커머스 환경설정 수정", - "en": "Update ecommerce settings" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - } - ], - "leaf_count": 2 - }, - { - "id": 53, - "identifier": "sirsoft-ecommerce.promotion-coupon", - "name": "쿠폰 관리", - "name_raw": { - "ko": "쿠폰 관리", - "en": "Coupon Management" - }, - "description": "쿠폰 관리 권한", - "description_raw": { - "ko": "쿠폰 관리 권한", - "en": "Coupon management permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": false, - "resource_route_key": null, - "owner_key": null, - "children": [ - { - "id": 54, - "identifier": "sirsoft-ecommerce.promotion-coupon.read", - "name": "쿠폰 조회", - "name_raw": { - "ko": "쿠폰 조회", - "en": "Read Coupons" - }, - "description": "쿠폰 목록 및 상세 조회", - "description_raw": { - "ko": "쿠폰 목록 및 상세 조회", - "en": "Read coupon list and details" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": true, - "resource_route_key": "coupon", - "owner_key": "created_by", - "children": [], - "leaf_count": 1 - }, - { - "id": 55, - "identifier": "sirsoft-ecommerce.promotion-coupon.create", - "name": "쿠폰 생성", - "name_raw": { - "ko": "쿠폰 생성", - "en": "Create Coupon" - }, - "description": "새 쿠폰 등록", - "description_raw": { - "ko": "새 쿠폰 등록", - "en": "Create new coupon" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 56, - "identifier": "sirsoft-ecommerce.promotion-coupon.update", - "name": "쿠폰 수정", - "name_raw": { - "ko": "쿠폰 수정", - "en": "Update Coupon" - }, - "description": "쿠폰 정보 수정", - "description_raw": { - "ko": "쿠폰 정보 수정", - "en": "Update coupon information" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": true, - "resource_route_key": "coupon", - "owner_key": "created_by", - "children": [], - "leaf_count": 1 - }, - { - "id": 57, - "identifier": "sirsoft-ecommerce.promotion-coupon.delete", - "name": "쿠폰 삭제", - "name_raw": { - "ko": "쿠폰 삭제", - "en": "Delete Coupon" - }, - "description": "쿠폰 삭제", - "description_raw": { - "ko": "쿠폰 삭제", - "en": "Delete coupon" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": true, - "resource_route_key": "coupon", - "owner_key": "created_by", - "children": [], - "leaf_count": 1 - } - ], - "leaf_count": 4 - }, - { - "id": 58, - "identifier": "sirsoft-ecommerce.shipping-policies", - "name": "배송정책 관리", - "name_raw": { - "ko": "배송정책 관리", - "en": "Shipping Policy Management" - }, - "description": "배송정책 관리 권한", - "description_raw": { - "ko": "배송정책 관리 권한", - "en": "Shipping policy management permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": false, - "resource_route_key": null, - "owner_key": null, - "children": [ - { - "id": 59, - "identifier": "sirsoft-ecommerce.shipping-policies.read", - "name": "배송정책 조회", - "name_raw": { - "ko": "배송정책 조회", - "en": "Read Shipping Policies" - }, - "description": "배송정책 목록 및 상세 조회", - "description_raw": { - "ko": "배송정책 목록 및 상세 조회", - "en": "Read shipping policy list and details" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": true, - "resource_route_key": "shippingPolicy", - "owner_key": "created_by", - "children": [], - "leaf_count": 1 - }, - { - "id": 60, - "identifier": "sirsoft-ecommerce.shipping-policies.create", - "name": "배송정책 생성", - "name_raw": { - "ko": "배송정책 생성", - "en": "Create Shipping Policy" - }, - "description": "새 배송정책 등록", - "description_raw": { - "ko": "새 배송정책 등록", - "en": "Create new shipping policy" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 61, - "identifier": "sirsoft-ecommerce.shipping-policies.update", - "name": "배송정책 수정", - "name_raw": { - "ko": "배송정책 수정", - "en": "Update Shipping Policy" - }, - "description": "배송정책 정보 수정", - "description_raw": { - "ko": "배송정책 정보 수정", - "en": "Update shipping policy information" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": true, - "resource_route_key": "shippingPolicy", - "owner_key": "created_by", - "children": [], - "leaf_count": 1 - }, - { - "id": 62, - "identifier": "sirsoft-ecommerce.shipping-policies.delete", - "name": "배송정책 삭제", - "name_raw": { - "ko": "배송정책 삭제", - "en": "Delete Shipping Policy" - }, - "description": "배송정책 삭제", - "description_raw": { - "ko": "배송정책 삭제", - "en": "Delete shipping policy" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": true, - "resource_route_key": "shippingPolicy", - "owner_key": "created_by", - "children": [], - "leaf_count": 1 - } - ], - "leaf_count": 4 - }, - { - "id": 63, - "identifier": "sirsoft-ecommerce.product-labels", - "name": "상품 라벨 관리", - "name_raw": { - "ko": "상품 라벨 관리", - "en": "Product Label Management" - }, - "description": "상품 라벨 관리 권한", - "description_raw": { - "ko": "상품 라벨 관리 권한", - "en": "Product label management permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": false, - "resource_route_key": null, - "owner_key": null, - "children": [ - { - "id": 64, - "identifier": "sirsoft-ecommerce.product-labels.read", - "name": "상품 라벨 조회", - "name_raw": { - "ko": "상품 라벨 조회", - "en": "Read Product Labels" - }, - "description": "상품 라벨 목록 및 상세 조회", - "description_raw": { - "ko": "상품 라벨 목록 및 상세 조회", - "en": "Read product label list and details" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 65, - "identifier": "sirsoft-ecommerce.product-labels.create", - "name": "상품 라벨 생성", - "name_raw": { - "ko": "상품 라벨 생성", - "en": "Create Product Label" - }, - "description": "새 상품 라벨 등록", - "description_raw": { - "ko": "새 상품 라벨 등록", - "en": "Create new product label" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 66, - "identifier": "sirsoft-ecommerce.product-labels.update", - "name": "상품 라벨 수정", - "name_raw": { - "ko": "상품 라벨 수정", - "en": "Update Product Label" - }, - "description": "상품 라벨 정보 수정", - "description_raw": { - "ko": "상품 라벨 정보 수정", - "en": "Update product label information" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 67, - "identifier": "sirsoft-ecommerce.product-labels.delete", - "name": "상품 라벨 삭제", - "name_raw": { - "ko": "상품 라벨 삭제", - "en": "Delete Product Label" - }, - "description": "상품 라벨 삭제", - "description_raw": { - "ko": "상품 라벨 삭제", - "en": "Delete product label" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - } - ], - "leaf_count": 4 - }, - { - "id": 68, - "identifier": "sirsoft-ecommerce.identity.policies", - "name": "이커머스 본인인증 정책", - "name_raw": { - "ko": "이커머스 본인인증 정책", - "en": "Ecommerce Identity Policies" - }, - "description": "이커머스 컨텍스트의 본인인증 정책 관리 권한", - "description_raw": { - "ko": "이커머스 컨텍스트의 본인인증 정책 관리 권한", - "en": "Manage identity verification policies in ecommerce context" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": false, - "resource_route_key": null, - "owner_key": null, - "children": [ - { - "id": 69, - "identifier": "sirsoft-ecommerce.identity.policies.read", - "name": "본인인증 정책 조회", - "name_raw": { - "ko": "본인인증 정책 조회", - "en": "View Identity Policies" - }, - "description": "이커머스 본인인증 정책 조회", - "description_raw": { - "ko": "이커머스 본인인증 정책 조회", - "en": "View ecommerce identity policies" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 70, - "identifier": "sirsoft-ecommerce.identity.policies.update", - "name": "본인인증 정책 수정", - "name_raw": { - "ko": "본인인증 정책 수정", - "en": "Update Identity Policies" - }, - "description": "이커머스 본인인증 정책 수정/추가/삭제", - "description_raw": { - "ko": "이커머스 본인인증 정책 수정/추가/삭제", - "en": "Update, add, delete ecommerce identity policies" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - } - ], - "leaf_count": 2 - }, - { - "id": 71, - "identifier": "sirsoft-ecommerce.reviews", - "name": "리뷰 관리", - "name_raw": { - "ko": "리뷰 관리", - "en": "Review Management" - }, - "description": "상품 리뷰 관리 권한", - "description_raw": { - "ko": "상품 리뷰 관리 권한", - "en": "Product review management permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": false, - "resource_route_key": null, - "owner_key": null, - "children": [ - { - "id": 72, - "identifier": "sirsoft-ecommerce.reviews.read", - "name": "리뷰 조회", - "name_raw": { - "ko": "리뷰 조회", - "en": "Read Reviews" - }, - "description": "리뷰 목록 및 상세 조회", - "description_raw": { - "ko": "리뷰 목록 및 상세 조회", - "en": "Read review list and details" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": true, - "resource_route_key": "review", - "owner_key": "user_id", - "children": [], - "leaf_count": 1 - }, - { - "id": 73, - "identifier": "sirsoft-ecommerce.reviews.update", - "name": "리뷰 처리", - "name_raw": { - "ko": "리뷰 처리", - "en": "Manage Review" - }, - "description": "리뷰 상태 변경, 답변 등록/수정/삭제, 일괄 처리", - "description_raw": { - "ko": "리뷰 상태 변경, 답변 등록/수정/삭제, 일괄 처리", - "en": "Update review status, manage replies, bulk actions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": true, - "resource_route_key": "review", - "owner_key": "user_id", - "children": [], - "leaf_count": 1 - }, - { - "id": 74, - "identifier": "sirsoft-ecommerce.reviews.delete", - "name": "리뷰 삭제", - "name_raw": { - "ko": "리뷰 삭제", - "en": "Delete Review" - }, - "description": "리뷰 삭제", - "description_raw": { - "ko": "리뷰 삭제", - "en": "Delete review" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": true, - "resource_route_key": "review", - "owner_key": "user_id", - "children": [], - "leaf_count": 1 - } - ], - "leaf_count": 3 - }, - { - "id": 75, - "identifier": "sirsoft-ecommerce.inquiries", - "name": "문의 관리", - "name_raw": { - "ko": "문의 관리", - "en": "Inquiry Management" - }, - "description": "상품 1:1 문의 관리 권한", - "description_raw": { - "ko": "상품 1:1 문의 관리 권한", - "en": "Product inquiry management permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": false, - "resource_route_key": null, - "owner_key": null, - "children": [ - { - "id": 76, - "identifier": "sirsoft-ecommerce.inquiries.update", - "name": "문의 처리", - "name_raw": { - "ko": "문의 처리", - "en": "Manage Inquiry" - }, - "description": "답변 등록/수정/삭제, 비밀글 내용 열람", - "description_raw": { - "ko": "답변 등록/수정/삭제, 비밀글 내용 열람", - "en": "Create, update, and delete inquiry replies; view secret inquiry contents" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 77, - "identifier": "sirsoft-ecommerce.inquiries.delete", - "name": "문의 삭제", - "name_raw": { - "ko": "문의 삭제", - "en": "Delete Inquiry" - }, - "description": "고객이 작성한 문의 자체를 삭제", - "description_raw": { - "ko": "고객이 작성한 문의 자체를 삭제", - "en": "Delete inquiries submitted by customers" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - } - ], - "leaf_count": 2 - }, - { - "id": 78, - "identifier": "sirsoft-ecommerce.dashboard", - "name": "대시보드", - "name_raw": { - "ko": "대시보드", - "en": "Dashboard" - }, - "description": "관리자 대시보드 이커머스 영역 조회 권한", - "description_raw": { - "ko": "관리자 대시보드 이커머스 영역 조회 권한", - "en": "Admin dashboard commerce area view permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": false, - "resource_route_key": null, - "owner_key": null, - "children": [ - { - "id": 79, - "identifier": "sirsoft-ecommerce.dashboard.view", - "name": "대시보드 조회", - "name_raw": { - "ko": "대시보드 조회", - "en": "View Dashboard" - }, - "description": "관리자 대시보드의 이커머스 판매 현황/리뷰/문의 위젯 조회", - "description_raw": { - "ko": "관리자 대시보드의 이커머스 판매 현황/리뷰/문의 위젯 조회", - "en": "View commerce sales/review/inquiry widgets on the admin dashboard" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - } - ], - "leaf_count": 1 - }, - { - "id": 88, - "identifier": "sirsoft-ecommerce.mileage", - "name": "마일리지 관리", - "name_raw": { - "ko": "마일리지 관리", - "en": "Mileage Management" - }, - "description": "마일리지 내역 조회 및 수동 지급/차감 권한", - "description_raw": { - "ko": "마일리지 내역 조회 및 수동 지급/차감 권한", - "en": "Mileage history and manual grant/deduct permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": false, - "resource_route_key": null, - "owner_key": null, - "children": [ - { - "id": 89, - "identifier": "sirsoft-ecommerce.mileage.read", - "name": "마일리지 내역 조회", - "name_raw": { - "ko": "마일리지 내역 조회", - "en": "Read Mileage" - }, - "description": "마일리지 내역 목록 및 상세 조회", - "description_raw": { - "ko": "마일리지 내역 목록 및 상세 조회", - "en": "Read mileage history list and details" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": true, - "resource_route_key": "mileage-transaction", - "owner_key": "user_id", - "children": [], - "leaf_count": 1 - }, - { - "id": 90, - "identifier": "sirsoft-ecommerce.mileage.manage", - "name": "마일리지 수동 처리", - "name_raw": { - "ko": "마일리지 수동 처리", - "en": "Manage Mileage" - }, - "description": "마일리지 수동 지급/차감 및 일괄 유효기간 연장", - "description_raw": { - "ko": "마일리지 수동 지급/차감 및 일괄 유효기간 연장", - "en": "Manual grant/deduct and bulk expiry extension" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": true, - "resource_route_key": "mileage-transaction", - "owner_key": "user_id", - "children": [], - "leaf_count": 1 - } - ], - "leaf_count": 2 - }, - { - "id": 91, - "identifier": "sirsoft-ecommerce.user-currency", - "name": "회원 결제 통화 관리", - "name_raw": { - "ko": "회원 결제 통화 관리", - "en": "User Currency Management" - }, - "description": "관리자가 회원별 결제 통화를 변경하는 권한", - "description_raw": { - "ko": "관리자가 회원별 결제 통화를 변경하는 권한", - "en": "Permission for admins to change a user's payment currency" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": false, - "resource_route_key": null, - "owner_key": null, - "children": [ - { - "id": 92, - "identifier": "sirsoft-ecommerce.user-currency.manage", - "name": "회원 결제 통화 변경", - "name_raw": { - "ko": "회원 결제 통화 변경", - "en": "Manage User Currency" - }, - "description": "회원별 결제 통화 변경", - "description_raw": { - "ko": "회원별 결제 통화 변경", - "en": "Change a user's payment currency" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - } - ], - "leaf_count": 1 - }, - { - "id": 93, - "identifier": "sirsoft-ecommerce.user-shipping-country", - "name": "회원 배송국가 관리", - "name_raw": { - "ko": "회원 배송국가 관리", - "en": "User Shipping Country Management" - }, - "description": "관리자가 회원별 배송국가를 변경하는 권한", - "description_raw": { - "ko": "관리자가 회원별 배송국가를 변경하는 권한", - "en": "Permission for admins to change a user's shipping country" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": false, - "resource_route_key": null, - "owner_key": null, - "children": [ - { - "id": 94, - "identifier": "sirsoft-ecommerce.user-shipping-country.manage", - "name": "회원 배송국가 변경", - "name_raw": { - "ko": "회원 배송국가 변경", - "en": "Manage User Shipping Country" - }, - "description": "회원별 배송국가 변경", - "description_raw": { - "ko": "회원별 배송국가 변경", - "en": "Change a user's shipping country" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - } - ], - "leaf_count": 1 - } - ], - "leaf_count": 48 - }, - { - "id": 109, - "identifier": "sirsoft-page", - "name": "페이지", - "name_raw": { - "ko": "페이지", - "en": "Page" - }, - "description": "페이지 모듈 권한", - "description_raw": { - "ko": "페이지 모듈 권한", - "en": "Page module permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-page", - "type": "admin", - "is_assignable": false, - "resource_route_key": null, - "owner_key": null, - "children": [ - { - "id": 110, - "identifier": "sirsoft-page.pages", - "name": "페이지 관리", - "name_raw": { - "ko": "페이지 관리", - "en": "Page Management" - }, - "description": "페이지 관리 권한", - "description_raw": { - "ko": "페이지 관리 권한", - "en": "Page management permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-page", - "type": "admin", - "is_assignable": false, - "resource_route_key": null, - "owner_key": null, - "children": [ - { - "id": 111, - "identifier": "sirsoft-page.pages.read", - "name": "페이지 조회", - "name_raw": { - "ko": "페이지 조회", - "en": "View Pages" - }, - "description": "페이지 목록 및 상세 조회", - "description_raw": { - "ko": "페이지 목록 및 상세 조회", - "en": "View page list and details" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-page", - "type": "admin", - "is_assignable": true, - "resource_route_key": "page", - "owner_key": "created_by", - "children": [], - "leaf_count": 1 - }, - { - "id": 112, - "identifier": "sirsoft-page.pages.create", - "name": "페이지 생성", - "name_raw": { - "ko": "페이지 생성", - "en": "Create Page" - }, - "description": "새 페이지 생성", - "description_raw": { - "ko": "새 페이지 생성", - "en": "Create new page" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-page", - "type": "admin", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 113, - "identifier": "sirsoft-page.pages.update", - "name": "페이지 수정", - "name_raw": { - "ko": "페이지 수정", - "en": "Update Page" - }, - "description": "페이지 내용 수정", - "description_raw": { - "ko": "페이지 내용 수정", - "en": "Update page content" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-page", - "type": "admin", - "is_assignable": true, - "resource_route_key": "page", - "owner_key": "created_by", - "children": [], - "leaf_count": 1 - }, - { - "id": 114, - "identifier": "sirsoft-page.pages.delete", - "name": "페이지 삭제", - "name_raw": { - "ko": "페이지 삭제", - "en": "Delete Page" - }, - "description": "페이지 삭제", - "description_raw": { - "ko": "페이지 삭제", - "en": "Delete page" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-page", - "type": "admin", - "is_assignable": true, - "resource_route_key": "page", - "owner_key": "created_by", - "children": [], - "leaf_count": 1 - } - ], - "leaf_count": 4 - } - ], - "leaf_count": 4 - } + "... (총 14건 중 5건 표시)" ] }, "user": { @@ -3940,208 +127,19 @@ HTTP/1.1 200 "icon": "user", "permissions": [ { - "id": 115, + "id": 1715, "identifier": "core", "name": "코어", "name_raw": { "ko": "코어", - "en": "Core" + "en": "Core", + "ja": "コア" }, "description": "코어 시스템 권한", - "description_raw": { - "ko": "코어 시스템 권한", - "en": "Core system permissions" - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "admin", - "is_assignable": false, - "resource_route_key": null, - "owner_key": null, - "children": [ - { - "id": 155, - "identifier": "core.user-notifications", - "name": "알림 (사용자)", - "name_raw": { - "ko": "알림 (사용자)", - "en": "Notifications (User)" - }, - "description": "사용자용 알림 권한 (사용자 화면에서 본인 알림 관리)", - "description_raw": { - "ko": "사용자용 알림 권한 (사용자 화면에서 본인 알림 관리)", - "en": "User notification permissions (managing own notifications in user UI)" - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "user", - "is_assignable": false, - "resource_route_key": null, - "owner_key": null, - "children": [ - { - "id": 156, - "identifier": "core.user-notifications.read", - "name": "알림 조회", - "name_raw": { - "ko": "알림 조회", - "en": "View Notifications" - }, - "description": "본인의 알림 목록 및 읽지 않은 수를 조회할 수 있습니다.", - "description_raw": { - "ko": "본인의 알림 목록 및 읽지 않은 수를 조회할 수 있습니다.", - "en": "Can view own notification list and unread count." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "user", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 157, - "identifier": "core.user-notifications.update", - "name": "알림 읽음 처리", - "name_raw": { - "ko": "알림 읽음 처리", - "en": "Mark Notifications Read" - }, - "description": "본인의 알림을 읽음 처리할 수 있습니다.", - "description_raw": { - "ko": "본인의 알림을 읽음 처리할 수 있습니다.", - "en": "Can mark own notifications as read." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "user", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 158, - "identifier": "core.user-notifications.delete", - "name": "알림 삭제", - "name_raw": { - "ko": "알림 삭제", - "en": "Delete Notifications" - }, - "description": "본인의 알림을 삭제할 수 있습니다.", - "description_raw": { - "ko": "본인의 알림을 삭제할 수 있습니다.", - "en": "Can delete own notifications." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "user", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - } - ], - "leaf_count": 3 - }, - { - "id": 159, - "identifier": "core.identity", - "name": "본인인증 (사용자)", - "name_raw": { - "ko": "본인인증 (사용자)", - "en": "Identity Verification (User)" - }, - "description": "로그인 사용자의 본인인증 challenge 요청/검증/취소 권한", - "description_raw": { - "ko": "로그인 사용자의 본인인증 challenge 요청/검증/취소 권한", - "en": "Permissions for authenticated users to request/verify/cancel IDV challenges" - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "user", - "is_assignable": false, - "resource_route_key": null, - "owner_key": null, - "children": [ - { - "id": 160, - "identifier": "core.identity.request", - "name": "IDV 요청", - "name_raw": { - "ko": "IDV 요청", - "en": "Request IDV Challenge" - }, - "description": "본인인증 challenge 를 요청할 수 있습니다.", - "description_raw": { - "ko": "본인인증 challenge 를 요청할 수 있습니다.", - "en": "Can request identity verification challenges." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "user", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 161, - "identifier": "core.identity.verify", - "name": "IDV 검증", - "name_raw": { - "ko": "IDV 검증", - "en": "Verify IDV Challenge" - }, - "description": "본인의 본인인증 challenge 를 검증할 수 있습니다.", - "description_raw": { - "ko": "본인의 본인인증 challenge 를 검증할 수 있습니다.", - "en": "Can verify own identity verification challenges." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "user", - "is_assignable": true, - "resource_route_key": "challenge", - "owner_key": "user_id", - "children": [], - "leaf_count": 1 - }, - { - "id": 162, - "identifier": "core.identity.cancel", - "name": "IDV 취소", - "name_raw": { - "ko": "IDV 취소", - "en": "Cancel IDV Challenge" - }, - "description": "본인의 본인인증 challenge 를 취소할 수 있습니다.", - "description_raw": { - "ko": "본인의 본인인증 challenge 를 취소할 수 있습니다.", - "en": "Can cancel own identity verification challenges." - }, - "extension_type": "core", - "extension_identifier": "core", - "type": "user", - "is_assignable": true, - "resource_route_key": "challenge", - "owner_key": "user_id", - "children": [], - "leaf_count": 1 - } - ], - "leaf_count": 3 - } - ], - "leaf_count": 6 + "...": "(9개 키 생략, 총 14개)" }, { - "id": 3, + "id": 1468, "identifier": "sirsoft-board", "name": "게시판", "name_raw": { @@ -4149,221 +147,10 @@ HTTP/1.1 200 "en": "Board" }, "description": "게시판 모듈 권한", - "description_raw": { - "ko": "게시판 모듈 권한", - "en": "Board module permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-board", - "type": "admin", - "is_assignable": false, - "resource_route_key": null, - "owner_key": null, - "children": [ - { - "id": 4, - "identifier": "sirsoft-board.apidoc-sample-board", - "name": "API 문서 샘플 게시판 게시판", - "name_raw": { - "ko": "API 문서 샘플 게시판 게시판", - "en": "API Doc Sample Board board" - }, - "description": "API 문서 샘플 게시판 게시판 권한", - "description_raw": { - "ko": "API 문서 샘플 게시판 게시판 권한", - "en": "API Doc Sample Board board permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-board", - "type": "admin", - "is_assignable": false, - "resource_route_key": null, - "owner_key": null, - "children": [ - { - "id": 13, - "identifier": "sirsoft-board.apidoc-sample-board.posts.read", - "name": "게시글 조회", - "name_raw": { - "ko": "게시글 조회", - "en": "View Posts" - }, - "description": "게시글 목록 및 상세 조회", - "description_raw": { - "ko": "게시글 목록 및 상세 조회", - "en": "View post list and details" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-board", - "type": "user", - "is_assignable": true, - "resource_route_key": "post", - "owner_key": "user_id", - "children": [], - "leaf_count": 1 - }, - { - "id": 14, - "identifier": "sirsoft-board.apidoc-sample-board.posts.write", - "name": "게시글 작성/수정/삭제", - "name_raw": { - "ko": "게시글 작성/수정/삭제", - "en": "Create/Edit/Delete Posts" - }, - "description": "게시글 작성 및 본인 글 수정/삭제", - "description_raw": { - "ko": "게시글 작성 및 본인 글 수정/삭제", - "en": "Create posts and edit/delete own posts" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-board", - "type": "user", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 15, - "identifier": "sirsoft-board.apidoc-sample-board.posts.read-secret", - "name": "비밀글 조회", - "name_raw": { - "ko": "비밀글 조회", - "en": "View Secret Posts" - }, - "description": "비밀글 조회", - "description_raw": { - "ko": "비밀글 조회", - "en": "View secret posts" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-board", - "type": "user", - "is_assignable": true, - "resource_route_key": "post", - "owner_key": "user_id", - "children": [], - "leaf_count": 1 - }, - { - "id": 16, - "identifier": "sirsoft-board.apidoc-sample-board.comments.read", - "name": "댓글 조회", - "name_raw": { - "ko": "댓글 조회", - "en": "View Comments" - }, - "description": "댓글 목록 조회", - "description_raw": { - "ko": "댓글 목록 조회", - "en": "View comment list" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-board", - "type": "user", - "is_assignable": true, - "resource_route_key": "comment", - "owner_key": "user_id", - "children": [], - "leaf_count": 1 - }, - { - "id": 17, - "identifier": "sirsoft-board.apidoc-sample-board.comments.write", - "name": "댓글 작성/수정/삭제", - "name_raw": { - "ko": "댓글 작성/수정/삭제", - "en": "Create/Edit/Delete Comments" - }, - "description": "댓글 작성 및 본인 댓글 수정/삭제", - "description_raw": { - "ko": "댓글 작성 및 본인 댓글 수정/삭제", - "en": "Create comments and edit/delete own comments" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-board", - "type": "user", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 18, - "identifier": "sirsoft-board.apidoc-sample-board.attachments.upload", - "name": "파일 업로드/삭제", - "name_raw": { - "ko": "파일 업로드/삭제", - "en": "Upload/Delete Files" - }, - "description": "게시글에 파일 첨부 및 삭제", - "description_raw": { - "ko": "게시글에 파일 첨부 및 삭제", - "en": "Attach and delete files to posts" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-board", - "type": "user", - "is_assignable": true, - "resource_route_key": "attachment", - "owner_key": "created_by", - "children": [], - "leaf_count": 1 - }, - { - "id": 19, - "identifier": "sirsoft-board.apidoc-sample-board.attachments.download", - "name": "파일 다운로드", - "name_raw": { - "ko": "파일 다운로드", - "en": "Download Files" - }, - "description": "첨부파일 다운로드", - "description_raw": { - "ko": "첨부파일 다운로드", - "en": "Download attached files" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-board", - "type": "user", - "is_assignable": true, - "resource_route_key": "attachment", - "owner_key": "created_by", - "children": [], - "leaf_count": 1 - }, - { - "id": 20, - "identifier": "sirsoft-board.apidoc-sample-board.manager", - "name": "게시판 관리 (사용자)", - "name_raw": { - "ko": "게시판 관리 (사용자)", - "en": "Manage Board (User)" - }, - "description": "사용자 페이지에서 게시판 관리", - "description_raw": { - "ko": "사용자 페이지에서 게시판 관리", - "en": "Manage board in user page" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-board", - "type": "user", - "is_assignable": true, - "resource_route_key": "board", - "owner_key": "created_by", - "children": [], - "leaf_count": 1 - } - ], - "leaf_count": 8 - } - ], - "leaf_count": 8 + "...": "(9개 키 생략, 총 14개)" }, { - "id": 21, + "id": 1489, "identifier": "sirsoft-ecommerce", "name": "이커머스", "name_raw": { @@ -4371,198 +158,7 @@ HTTP/1.1 200 "en": "Ecommerce" }, "description": "이커머스 모듈 권한", - "description_raw": { - "ko": "이커머스 모듈 권한", - "en": "Ecommerce module permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "admin", - "is_assignable": false, - "resource_route_key": null, - "owner_key": null, - "children": [ - { - "id": 80, - "identifier": "sirsoft-ecommerce.user-products", - "name": "사용자 상품", - "name_raw": { - "ko": "사용자 상품", - "en": "User Products" - }, - "description": "사용자 상품 접근 권한 (블랙컨슈머 차단용)", - "description_raw": { - "ko": "사용자 상품 접근 권한 (블랙컨슈머 차단용)", - "en": "User product access permissions (for blocking malicious consumers)" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "user", - "is_assignable": false, - "resource_route_key": null, - "owner_key": null, - "children": [ - { - "id": 81, - "identifier": "sirsoft-ecommerce.user-products.read", - "name": "상품 조회", - "name_raw": { - "ko": "상품 조회", - "en": "View Products" - }, - "description": "상품 목록 및 상세 조회", - "description_raw": { - "ko": "상품 목록 및 상세 조회", - "en": "View product list and details" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "user", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - } - ], - "leaf_count": 1 - }, - { - "id": 82, - "identifier": "sirsoft-ecommerce.user-orders", - "name": "사용자 주문", - "name_raw": { - "ko": "사용자 주문", - "en": "User Orders" - }, - "description": "사용자 주문 관련 권한 (블랙컨슈머 차단용)", - "description_raw": { - "ko": "사용자 주문 관련 권한 (블랙컨슈머 차단용)", - "en": "User order permissions (for blocking malicious consumers)" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "user", - "is_assignable": false, - "resource_route_key": null, - "owner_key": null, - "children": [ - { - "id": 83, - "identifier": "sirsoft-ecommerce.user-orders.create", - "name": "주문하기", - "name_raw": { - "ko": "주문하기", - "en": "Create Order" - }, - "description": "주문 생성", - "description_raw": { - "ko": "주문 생성", - "en": "Create a new order" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "user", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 84, - "identifier": "sirsoft-ecommerce.user-orders.cancel", - "name": "주문 취소", - "name_raw": { - "ko": "주문 취소", - "en": "Cancel Order" - }, - "description": "주문 취소 요청", - "description_raw": { - "ko": "주문 취소 요청", - "en": "Request order cancellation" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "user", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - }, - { - "id": 85, - "identifier": "sirsoft-ecommerce.user-orders.confirm", - "name": "구매확정", - "name_raw": { - "ko": "구매확정", - "en": "Confirm Purchase" - }, - "description": "주문 상품 구매확정", - "description_raw": { - "ko": "주문 상품 구매확정", - "en": "Confirm purchase of order item" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "user", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - } - ], - "leaf_count": 3 - }, - { - "id": 86, - "identifier": "sirsoft-ecommerce.user-reviews", - "name": "사용자 리뷰", - "name_raw": { - "ko": "사용자 리뷰", - "en": "User Reviews" - }, - "description": "사용자 리뷰 관련 권한 (블랙컨슈머 차단용)", - "description_raw": { - "ko": "사용자 리뷰 관련 권한 (블랙컨슈머 차단용)", - "en": "User review permissions (for blocking malicious consumers)" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "user", - "is_assignable": false, - "resource_route_key": null, - "owner_key": null, - "children": [ - { - "id": 87, - "identifier": "sirsoft-ecommerce.user-reviews.write", - "name": "리뷰 작성", - "name_raw": { - "ko": "리뷰 작성", - "en": "Write Review" - }, - "description": "상품 리뷰 작성", - "description_raw": { - "ko": "상품 리뷰 작성", - "en": "Write product review" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "type": "user", - "is_assignable": true, - "resource_route_key": null, - "owner_key": null, - "children": [], - "leaf_count": 1 - } - ], - "leaf_count": 1 - } - ], - "leaf_count": 5 + "...": "(9개 키 생략, 총 14개)" } ] } @@ -4570,8 +166,7 @@ HTTP/1.1 200 "types": [ "admin", "user" - ], - "0": "... (총 4건 중 2건 표시)" + ] } } } diff --git a/docs/backend/api/plugins.md b/docs/backend/api/plugins.md index f32be2ad..7ae8fcb8 100644 --- a/docs/backend/api/plugins.md +++ b/docs/backend/api/plugins.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Plugins 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` @@ -66,7 +66,7 @@ _목록 응답: `data.data[]` 배열 항목의 필드 + `data.pagination`._ | is_installed | boolean | `false` | installed 여부 | | has_settings | boolean | `true` | settings 여부 | | settings_route | string | `/admin/plugins/sirsoft-ckeditor5/sett…` | 설정 페이지 경로 (설정 UI 진입 라우트, 설정 미제공 시 null) | -| assets | object | `{"js":"\/api\/plugins\/assets\/sirsoft-ckeditor5\/dist\/j…` | 프론트엔드 에셋 매니페스트 (manifest 파생 — js/css 진입점·로딩 전략) | +| assets | object | `{"js":"\/api\/plugins\/assets\/sirsoft-ckeditor5?file=dis…` | 프론트엔드 에셋 매니페스트 (manifest 파생 — js/css 진입점·로딩 전략) | | update_available | boolean | `false` | 최신 버전 대비 업데이트 가능 여부 | | update_source | null | `null` | 업데이트 감지 출처 (github, bundled 등) | | latest_version | string | `1.0.0` | 감지된 최신 배포 버전 | @@ -104,21 +104,21 @@ HTTP/1.1 200 "roles": [], "config": [], "hooks": [], - "status": "uninstalled", + "status": "active", "is_installed": false, "has_settings": true, "settings_route": "/admin/plugins/sirsoft-ckeditor5/settings", "assets": { - "js": "/api/plugins/assets/sirsoft-ckeditor5/dist/js/plugin.iife.js", + "js": "/api/plugins/assets/sirsoft-ckeditor5?file=dist%2Fjs%2Fplugin.iife.js", "css": null, "priority": 100 }, "update_available": false, "update_source": null, - "latest_version": null, - "file_version": null, - "github_url": null, - "github_changelog_url": null, + "latest_version": "1.0.0", + "file_version": "1.0.0", + "github_url": "https://github.com/gnuboard/g7-plugin-sirsoft-ckeditor5", + "github_changelog_url": "https://github.com/gnuboard/g7-plugin-sirsoft-ckeditor5/releases", "is_pending": false, "is_bundled": false, "deactivated_reason": null, @@ -135,28 +135,28 @@ HTTP/1.1 200 "identifier": "sirsoft-daum_postcode", "vendor": "sirsoft", "name": "Daum 우편번호", - "version": "1.0.0", + "version": "1.0.1", "description": "Daum 우편번호 서비스를 통한 주소 검색 기능을 제공하는 플러그인입니다. API 키 없이 무료로 사용할 수 있습니다.", "dependencies": [], "permissions": [], "roles": [], "config": [], "hooks": [], - "status": "uninstalled", + "status": "active", "is_installed": false, "has_settings": true, "settings_route": "/admin/plugins/sirsoft-daum_postcode/settings", "assets": { - "js": "/api/plugins/assets/sirsoft-daum_postcode/dist/js/plugin.iife.js", + "js": "/api/plugins/assets/sirsoft-daum_postcode?file=dist%2Fjs%2Fplugin.iife.js", "css": null, "priority": 100 }, "update_available": false, "update_source": null, - "latest_version": null, - "file_version": null, - "github_url": null, - "github_changelog_url": null, + "latest_version": "1.0.1", + "file_version": "1.0.1", + "github_url": "https://github.com/gnuboard/g7-plugin-sirsoft-daum_postcode", + "github_changelog_url": "https://github.com/gnuboard/g7-plugin-sirsoft-daum_postcode/releases", "is_pending": false, "is_bundled": false, "deactivated_reason": null, @@ -168,20 +168,20 @@ HTTP/1.1 200 "can_uninstall": true } }, - "... (총 9건 중 2건 표시)" + "... (총 10건 중 2건 표시)" ], "pagination": { - "total": 9, + "total": 10, "current_page": 1, "last_page": 1, "per_page": 25 }, "meta": { - "total_plugins": 9, - "active_plugins": 0, + "total_plugins": 10, + "active_plugins": 8, "inactive_plugins": 0, - "installed_plugins": 0, - "uninstalled_plugins": 9 + "installed_plugins": 8, + "uninstalled_plugins": 2 }, "abilities": { "can_install": true, @@ -237,7 +237,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -277,7 +277,7 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - + **응답 예시** @@ -327,7 +327,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -386,7 +386,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -438,7 +438,7 @@ Content-Type: application/octet-stream **응답 필드** (`data` 내부) - + **응답 예시** @@ -487,7 +487,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -546,7 +546,7 @@ _목록 응답: `data.data[]` 배열 항목의 필드._ | is_installed | boolean | `false` | installed 여부 | | has_settings | boolean | `true` | settings 여부 | | settings_route | string | `/admin/plugins/sirsoft-ckeditor5/sett…` | 설정 페이지 경로 (설정 UI 진입 라우트, 설정 미제공 시 null) | -| assets | object | `{"js":"\/api\/plugins\/assets\/sirsoft-ckeditor5\/dist\/j…` | 프론트엔드 에셋 매니페스트 (manifest 파생 — js/css 진입점·로딩 전략) | +| assets | object | `{"js":"\/api\/plugins\/assets\/sirsoft-ckeditor5?file=dis…` | 프론트엔드 에셋 매니페스트 (manifest 파생 — js/css 진입점·로딩 전략) | | update_available | boolean | `false` | 최신 버전 대비 업데이트 가능 여부 | | update_source | null | `null` | 업데이트 감지 출처 (github, bundled 등) | | latest_version | string | `1.0.0` | 감지된 최신 배포 버전 | @@ -571,12 +571,90 @@ HTTP/1.1 200 "success": true, "message": "플러그인 목록을 성공적으로 가져왔습니다.", "data": { - "data": [], + "data": [ + { + "id": null, + "identifier": "sirsoft-ckeditor5", + "vendor": "sirsoft", + "name": "CKEditor 5 WYSIWYG 에디터", + "version": "1.0.0", + "description": "CKEditor 5를 이용한 WYSIWYG 에디터 플러그인입니다. 플러그인 설치만으로 기존 HtmlEditor가 교체됩니다.", + "dependencies": [], + "permissions": [], + "roles": [], + "config": [], + "hooks": [], + "status": "active", + "is_installed": false, + "has_settings": true, + "settings_route": "/admin/plugins/sirsoft-ckeditor5/settings", + "assets": { + "js": "/api/plugins/assets/sirsoft-ckeditor5?file=dist%2Fjs%2Fplugin.iife.js", + "css": null, + "priority": 100 + }, + "update_available": false, + "update_source": null, + "latest_version": "1.0.0", + "file_version": "1.0.0", + "github_url": "https://github.com/gnuboard/g7-plugin-sirsoft-ckeditor5", + "github_changelog_url": "https://github.com/gnuboard/g7-plugin-sirsoft-ckeditor5/releases", + "is_pending": false, + "is_bundled": false, + "deactivated_reason": null, + "deactivated_at": null, + "incompatible_required_version": null, + "abilities": { + "can_install": true, + "can_activate": true, + "can_uninstall": true + } + }, + { + "id": null, + "identifier": "sirsoft-daum_postcode", + "vendor": "sirsoft", + "name": "Daum 우편번호", + "version": "1.0.1", + "description": "Daum 우편번호 서비스를 통한 주소 검색 기능을 제공하는 플러그인입니다. API 키 없이 무료로 사용할 수 있습니다.", + "dependencies": [], + "permissions": [], + "roles": [], + "config": [], + "hooks": [], + "status": "active", + "is_installed": false, + "has_settings": true, + "settings_route": "/admin/plugins/sirsoft-daum_postcode/settings", + "assets": { + "js": "/api/plugins/assets/sirsoft-daum_postcode?file=dist%2Fjs%2Fplugin.iife.js", + "css": null, + "priority": 100 + }, + "update_available": false, + "update_source": null, + "latest_version": "1.0.1", + "file_version": "1.0.1", + "github_url": "https://github.com/gnuboard/g7-plugin-sirsoft-daum_postcode", + "github_changelog_url": "https://github.com/gnuboard/g7-plugin-sirsoft-daum_postcode/releases", + "is_pending": false, + "is_bundled": false, + "deactivated_reason": null, + "deactivated_at": null, + "incompatible_required_version": null, + "abilities": { + "can_install": true, + "can_activate": true, + "can_uninstall": true + } + }, + "... (총 8건 중 2건 표시)" + ], "meta": { - "total_plugins": 0, - "active_plugins": 0, + "total_plugins": 8, + "active_plugins": 8, "inactive_plugins": 0, - "installed_plugins": 0, + "installed_plugins": 8, "uninstalled_plugins": 0 } } @@ -625,7 +703,7 @@ Content-Type: application/octet-stream **응답 필드** (`data` 내부) - + **응답 예시** @@ -674,7 +752,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -719,7 +797,7 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - + **응답 예시** @@ -753,12 +831,10 @@ Authorization: Bearer {YOUR_TOKEN} | from_version | query | string | 아니오 | — | 시작 버전 (범위 하한) | | to_version | query | string | 아니오 | — | 대상 버전 (범위 상한) | -> 이 엔드포인트는 확장이 파라미터를 추가할 수 있습니다 (`core.extension.changelog_validation_rules`). - **요청 예시** ```http -GET /api/admin/plugins/{identifier}/changelog?source=active&from_version=%EC%98%88%EC%8B%9C%EA%B0%92&to_version=%EC%98%88%EC%8B%9C%EA%B0%92 HTTP/1.1 +GET /api/admin/plugins/sirsoft-daum_postcode/changelog?source=active&from_version=%EC%98%88%EC%8B%9C%EA%B0%92&to_version=%EC%98%88%EC%8B%9C%EA%B0%92 HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -766,11 +842,149 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - +_단건 응답: `data` 객체의 필드._ + +| 필드 | 타입 | 실측 예시값 | 용도/설명 | +| --- | --- | --- | --- | +| changelog | array | `[{"version":"1.0.1","date":"2026-07-22","categories":[{"n…` | 변경 이력 텍스트 (원격/파일 CHANGELOG 본문) | **응답 예시** - +```http +HTTP/1.1 200 +``` + +```json +{ + "success": true, + "message": "플러그인 정보를 성공적으로 가져왔습니다.", + "data": { + "changelog": [ + { + "version": "1.0.1", + "date": "2026-07-22", + "categories": [ + { + "name": "Fixed", + "items": [ + "오류 안내가 뜨기는 하지만 내용이 비어 있던 문제를 수정했습니다. 설정 저장에 실패하면 서버가 알려 준 사유가 그대로 표시됩니다.", + "설정 화면의 아이콘이 의도한 크기보다 크거나 작게 보이던 문제를 수정했습니다." + ] + } + ] + }, + { + "version": "1.0.0", + "date": "2026-07-01", + "categories": [ + { + "name": "Added", + "items": [ + "레이아웃 편집기 데이터 소스 목록에서 이 확장이 제공하는 데이터 소스가 친화 명칭으로 표시되고, 어느 확장이 제공했는지 출처가 함께 표시됩니다." + ] + }, + { + "name": "Changed", + "items": [ + "플러그인 환경설정 화면의 하단 저장 버튼이 스크롤 중에도 화면에 고정되도록 개선." + ] + } + ] + }, + { + "version": "1.0.0-beta.2", + "date": "2026-04-20", + "categories": [ + { + "name": "Changed", + "items": [ + "주소 검색 영역의 콘텐츠 카드 / 세로 정렬 컨테이너 외형을 sirsoft-admin_basic 표준 시맨틱과 정합 — 다른 화면과 같은 결로 통일.", + "코어 최소 요구 버전을 7.0.0-beta.2 로 상향", + "extension JSON: `extensionPointProps.onAddressSelect` → `extensionPointCallbacks.onAddressSelect` 참조 변경 (extension_point props/callbacks 분리)", + "플러그인 환경설정 화면의 하단 저장 버튼이 스크롤 중에도 화면에 고정되도록 개선.", + "플러그인 환경설정 화면의 폼 라벨 / 보조 설명 / 에러 메시지 시각 시맨틱을 sirsoft-admin_basic 표준 시맨틱과 정합 — 다른 관리자 화면과 같은 결로 통일.", + "플러그인 환경설정 화면 곳곳의 텍스트 톤 (보조 설명 · 라벨 · 본문 · 강조 · 작은 보조) 시각 시맨틱을 관리자 표준 시맨틱과 정합 — 같은 결의 글자 톤이 한 곳에서 일괄 조정 가능.", + "플러그인 환경설정 화면의 세로 정렬 컨테이너 / 입력 박스를 sirsoft-admin_basic 표준 시맨틱 (.stack / .input) 과 정합 — 표준 간격과 외형이 일관 표시되도록 정리." + ] + } + ] + }, + { + "version": "1.0.0-beta.1", + "date": "2026-04-01", + "categories": [ + { + "name": "Changed", + "items": [ + "오픈 베타 릴리즈" + ] + } + ] + }, + { + "version": "0.1.3", + "date": "2026-03-16", + "categories": [ + { + "name": "Changed", + "items": [ + "라이선스 프로그램 명칭 정비" + ] + } + ] + }, + { + "version": "0.1.2", + "date": "2026-03-13", + "categories": [ + { + "name": "Added", + "items": [ + "manifest에 license 필드 및 LICENSE 파일 추가" + ] + }, + { + "name": "Changed", + "items": [ + "설정 레이아웃 경로를 `resources/layouts/settings.json` → `resources/layouts/admin/plugin_settings.json`으로 이동 (모듈과 동일한 구조 통일)" + ] + } + ] + }, + { + "version": "0.1.1", + "date": "2026-02-24", + "categories": [ + { + "name": "Changed", + "items": [ + "버전 체계 조정 (정식 출시 전 0.x 체계로 변경)" + ] + } + ] + }, + { + "version": "0.1.0", + "date": "2026-02-23", + "categories": [ + { + "name": "Added", + "items": [ + "Daum 우편번호 검색 플러그인 초기 구현", + "Daum 우편번호 서비스 API 연동 (API 키 불필요)", + "주소 검색 팝업/레이어 표시 모드 설정", + "커스텀 핸들러 (openPostcode, setFieldReadOnly)", + "이커머스 주소 검색 레이아웃 확장 (ecommerce-address-search)", + "플러그인 설정 UI (표시 모드, 테마 설정)", + "다국어 지원 (ko, en)" + ] + } + ] + } + ] + } +} +``` **에러 응답** @@ -801,7 +1015,7 @@ Authorization: Bearer {YOUR_TOKEN} **요청 예시** ```http -GET /api/admin/plugins/{identifier}/dependent-templates HTTP/1.1 +GET /api/admin/plugins/sirsoft-daum_postcode/dependent-templates HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -809,11 +1023,42 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - +_목록 응답: `data.data[]` 배열 항목의 필드._ + +| 필드 | 타입 | 실측 예시값 | 용도/설명 | +| --- | --- | --- | --- | +| identifier | string | `sirsoft-basic` | 플러그인 고유 식별자 (vendor-plugin 형식) | +| name | string | `Basic` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | +| version | string | `1.0.4` | 플러그인 버전 | +| type | string | `user` | | +| status | string | `active` | 상태 값 (도메인별 상태 집합 — 사람이 읽는 라벨은 status_label, UI 변형은 status_variant 참조) | +| required_version | string | `>=1.0.0` | | **응답 예시** - +```http +HTTP/1.1 200 +``` + +```json +{ + "success": true, + "message": "의존 템플릿 정보를 성공적으로 조회했습니다.", + "data": { + "data": [ + { + "identifier": "sirsoft-basic", + "name": "Basic", + "version": "1.0.4", + "type": "user", + "status": "active", + "required_version": ">=1.0.0" + } + ], + "total": 1 + } +} +``` **에러 응답** @@ -843,7 +1088,7 @@ Authorization: Bearer {YOUR_TOKEN} **요청 예시** ```http -GET /api/admin/plugins/{identifier}/license HTTP/1.1 +GET /api/admin/plugins/sirsoft-daum_postcode/license HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -851,11 +1096,27 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - +_단건 응답: `data` 객체의 필드._ + +| 필드 | 타입 | 실측 예시값 | 용도/설명 | +| --- | --- | --- | --- | +| content | string | `프로그램 명칭 : 그누보드7용 Daum 우편번호 플러그인 (sirs…` | 본문 내용 | **응답 예시** - +```http +HTTP/1.1 200 +``` + +```json +{ + "success": true, + "message": "플러그인 정보를 성공적으로 가져왔습니다.", + "data": { + "content": "프로그램 명칭 : 그누보드7용 Daum 우편번호 플러그인 (sirsoft-daum_postcode)\n\n저작자 : (주)에스아이알소프트\n\n----- MIT 라이선스 (한국어 번역) --------------------------------------------------------\n\nMIT 라이선스\n\nCopyright (c) 2026 (주)에스아이알소프트\n\n이 소프트웨어와 관련 문서 파일(이하 \"소프트웨어\")의 복사본을 취득하는 모든 사람에게\n소프트웨어를 제한 없이 사용, 복사, 수정, 병합, 출판, 배포, 서브라이선스 허여 및/또는\n판매할 수 있는 권리를 무상으로 부여합니다. 다만, 소프트웨어를 제공받은 사람은 다음\n조건을 따라야 합니다:\n\n위 저작권 고지와 본 허가 고지는 소프트웨어의 모든 복사본 또는 상당 부분에 포함되어야\n합니다.\n\n소프트웨어는 \"있는 그대로\" 제공되며, 명시적이든 묵시적이든 어떠한 종류의 보증도 하지\n않습니다. 여기에는 상품성, 특정 목적에의 적합성 및 비침해에 대한 보증이 포함되나 이에\n국한되지 않습니다. 어떠한 경우에도 저작자 또는 저작권자는 소프트웨어나 소프트웨어의\n사용 또는 기타 거래로 인해 발생하는 계약, 불법행위 또는 기타 청구, 손해 또는 기타\n책임에 대해 책임을 지지 않습니다.\n\n----- MIT License (English Original) --------------------------------------------------------\n\nThe MIT License (MIT)\n\nCopyright (c) 2026 SIRSOFT\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n" + } +} +``` **에러 응답** @@ -885,7 +1146,7 @@ Authorization: Bearer {YOUR_TOKEN} **요청 예시** ```http -GET /api/admin/plugins/{identifier}/settings HTTP/1.1 +GET /api/admin/plugins/sirsoft-daum_postcode/settings HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -893,11 +1154,33 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - +_단건 응답: `data` 객체의 필드._ + +| 필드 | 타입 | 실측 예시값 | 용도/설명 | +| --- | --- | --- | --- | +| display_mode | string | `layer` | | +| popup_width | integer | `500` | | +| popup_height | integer | `600` | | +| theme_color | string | `#1D4ED8` | | **응답 예시** - +```http +HTTP/1.1 200 +``` + +```json +{ + "success": true, + "message": "messages.success", + "data": { + "display_mode": "layer", + "popup_width": 500, + "popup_height": 600, + "theme_color": "#1D4ED8" + } +} +``` **에러 응답** @@ -924,12 +1207,10 @@ Authorization: Bearer {YOUR_TOKEN} | --- | --- | --- | --- | --- | --- | | identifier | path | string | 예 | — | 대상 리소스의 식별자 | -> 이 엔드포인트는 확장이 파라미터를 추가할 수 있습니다 (`core.plugin_settings.update_validation_rules`). - **요청 예시** ```http -PUT /api/admin/plugins/{identifier}/settings HTTP/1.1 +PUT /api/admin/plugins/sirsoft-daum_postcode/settings HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -937,7 +1218,7 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - + **응답 예시** @@ -949,7 +1230,6 @@ Authorization: Bearer {YOUR_TOKEN} | --- | --- | --- | | 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 | | 403 | Forbidden | 요구 권한(`core.plugins.update`)이 없는 경우 | -| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) | | 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 | @@ -972,7 +1252,7 @@ Authorization: Bearer {YOUR_TOKEN} **요청 예시** ```http -GET /api/admin/plugins/{identifier}/settings/layout HTTP/1.1 +GET /api/admin/plugins/sirsoft-daum_postcode/settings/layout HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -980,11 +1260,45 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - +_단건 응답: `data` 객체의 필드._ + +| 필드 | 타입 | 실측 예시값 | 용도/설명 | +| --- | --- | --- | --- | +| version | string | `1.0.0` | 플러그인 버전 | +| layout_name | string | `plugin_settings` | | +| permissions | array | `["core.plugins.update"]` | 권한 목록 (각 원소 id/identifier/name — permissions 관계 또는 역할 경유 파생) | +| extends | string | `_admin_base` | | +| meta | object | `{"title":"$t:sirsoft-daum_postcode.settings.title","descr…` | | +| data_sources | array | `[{"id":"settings","label_key":"$t:sirsoft-daum_postcode.e…` | | +| slots | object | `{"content":[{"id":"plugin_settings_content","type":"basic…` | | +| pageConfig | object | `{"notice":"$t:sirsoft-daum_postcode.settings.notice","gui…` | | +| schema | object | `{"display_mode":{"type":"enum","options":["popup","layer"…` | | **응답 예시** - +```http +HTTP/1.1 200 +``` + +```json +{ + "success": true, + "message": "messages.success", + "data": { + "version": "1.0.0", + "layout_name": "plugin_settings", + "permissions": [ + "core.plugins.update" + ], + "extends": "_admin_base", + "meta": { + "title": "$t:sirsoft-daum_postcode.settings.title", + "description": "$t:sirsoft-daum_postcode.settings.description" + }, + "...": "(4개 키 생략, 총 9개)" + } +} +``` **에러 응답** @@ -1064,38 +1378,11 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) -| 필드 | 타입 | 설명 | -| --- | --- | --- | -| has_modified_layouts | boolean | 수정된 레이아웃 존재 여부 | -| modified_count | integer | 수정된 레이아웃 수 | -| modified_layouts | array | 수정된 레이아웃 목록 | -| modified_layouts[].id | integer | 레이아웃 ID | -| modified_layouts[].name | string | 레이아웃 이름 | -| modified_layouts[].updated_at | string\|null | 최종 수정 일시 (`Y-m-d H:i:s`) | -| modified_layouts[].size_diff | integer | 원본 대비 바이트 증감 (음수는 축소) | + **응답 예시** -```json -{ - "success": true, - "message": "수정된 레이아웃 확인이 완료되었습니다.", - "data": { - "has_modified_layouts": true, - "modified_count": 1, - "modified_layouts": [ - { - "id": 47, - "name": "sirsoft-board.admin_board_index", - "updated_at": "2026-07-31 09:20:05", - "size_diff": 28 - } - ] - } -} -``` - -집계 범위는 해당 플러그인이 **소유한** 레이아웃으로 한정됩니다. 같은 템플릿에 등록된 다른 확장의 레이아웃은 포함되지 않습니다. + **에러 응답** @@ -1225,13 +1512,13 @@ Content-Type: application/json "layout_strategy": "overwrite", "vendor_mode": "auto", "force": true, - "rebuild_search_index": false + "rebuild_search_index": true } ``` **응답 필드** (`data` 내부) - + **응답 예시** @@ -1251,6 +1538,47 @@ Content-Type: application/json **설명** 특정 플러그인을 최신 버전으로 업데이트합니다. `core.plugins.install` 권한이 필요합니다. `layout_strategy` 로 레이아웃 처리 방식을(overwrite: 새 버전으로 교체, keep: 사용자 수정본 유지) 지정하며, `vendor_mode` 로 Composer 의존성 처리 방식을 선택합니다. 버전 제약·호환성 문제로 막힐 경우 `force: true` 로 강제 진행할 수 있습니다. `keep` 을 지정하면 사용자가 수정한 레이아웃(원본 해시와 현재 내용이 다른 레이아웃)은 갱신 대상에서 제외되어 현재 내용이 그대로 유지되고, 나머지 레이아웃만 파일 기준으로 갱신됩니다. 성공 응답 메시지에는 대상 식별자와 적용된 버전이 채워집니다. `rebuild_search_index: true` 를 함께 보내면 업데이트 후 색인이 누락된 검색 인덱스를 재생성합니다 — 인덱스 잠금·재색인 비용이 있어 기본은 수행하지 않으며, 보내지 않아도 응답의 `search_index` 에 현재 누락 여부가 담깁니다. +### GET /api/plugins/assets/{identifier} + +- **라우트명**: `api.public.plugins.assets.extensionless` +- **컨트롤러**: `App\Http\Controllers\Api\Public\PublicPluginController@serveAsset` +- **인증/권한**: 공개 (인증 불필요) + +**요청 파라미터** + +| 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | +| --- | --- | --- | --- | --- | --- | +| identifier | path | string | 예 | — | 대상 리소스의 식별자 | +| path | query | string | 예 | — | 경로 | + +**요청 예시** + +```http +GET /api/plugins/assets/sirsoft-daum_postcode?identifier=example-key&path=%EC%98%88%EC%8B%9C%EA%B0%92 HTTP/1.1 +Host: api.example.com +Accept: application/json +``` + +**응답 필드** (`data` 내부) + + + +**응답 예시** + + + +**에러 응답** + +| 상태코드 | 의미 | 발생 조건 | +| --- | --- | --- | +| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) | +| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 | + + + +**설명** + + ### GET /api/plugins/assets/{identifier}/{path} - **라우트명**: `api.public.plugins.assets` @@ -1267,7 +1595,7 @@ Content-Type: application/json **요청 예시** ```http -GET /api/plugins/assets/{identifier}/{path} HTTP/1.1 +GET /api/plugins/assets/sirsoft-daum_postcode/{path}?identifier=example-key HTTP/1.1 Host: api.example.com Accept: application/json ``` @@ -1312,11 +1640,11 @@ Accept: application/json **응답 필드** (`data` 내부) - + **응답 예시** - + **에러 응답** @@ -1347,11 +1675,11 @@ Accept: application/json **응답 필드** (`data` 내부) - + **응답 예시** - + **에러 응답** @@ -1362,6 +1690,132 @@ _대표 에러 없음 (공개 조회 — 활성 에셋이 없으면 빈 200 응 **설명** 활성 플러그인들의 프론트엔드 IIFE JS 를 서버에서 하나로 병합한 번들을 서빙하는 공개 엔드포인트입니다. 인증이 필요하지 않습니다. 활성 global 플러그인 에셋이 없으면 빈 200(text/javascript) 응답을 반환하고, 있으면 병합 파일을 ETag·환경별 Cache-Control 과 함께 서빙합니다. 프론트는 `G7Config.bundleUrls` 를 읽어 이 번들을 로드합니다. +### GET /api/plugins/bundle/css + +- **라우트명**: `api.public.plugins.bundle.css.extensionless` +- **컨트롤러**: `App\Http\Controllers\Api\Public\PublicPluginController@serveBundleCss` +- **인증/권한**: 공개 (인증 불필요) + +**요청 파라미터** + +_요청 파라미터 없음._ + +**요청 예시** + +```http +GET /api/plugins/bundle/css HTTP/1.1 +Host: api.example.com +Accept: application/json +``` + +**응답 필드** (`data` 내부) + + + +**응답 예시** + + + +**에러 응답** + +_대표 에러 없음 (공개 조회). _ + + + +**설명** + + +### GET /api/plugins/bundle/js + +- **라우트명**: `api.public.plugins.bundle.js.extensionless` +- **컨트롤러**: `App\Http\Controllers\Api\Public\PublicPluginController@serveBundleJs` +- **인증/권한**: 공개 (인증 불필요) + +**요청 파라미터** + +_요청 파라미터 없음._ + +**요청 예시** + +```http +GET /api/plugins/bundle/js HTTP/1.1 +Host: api.example.com +Accept: application/json +``` + +**응답 필드** (`data` 내부) + + + +**응답 예시** + + + +**에러 응답** + +_대표 에러 없음 (공개 조회). _ + + + +**설명** + + +### GET /api/plugins/{identifier}/components + +- **라우트명**: `api.public.plugins.components.extensionless` +- **컨트롤러**: `App\Http\Controllers\Api\Public\PublicPluginController@serveComponents` +- **인증/권한**: 공개 (인증 불필요) + +**요청 파라미터** + +| 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | +| --- | --- | --- | --- | --- | --- | +| identifier | path | string | 예 | — | 대상 리소스의 식별자 | + +**요청 예시** + +```http +GET /api/plugins/sirsoft-daum_postcode/components HTTP/1.1 +Host: api.example.com +Accept: application/json +``` + +**응답 필드** (`data` 내부) + + + + + +**응답 예시** + +```http +HTTP/1.1 200 +``` + +```json +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "identifier": "sirsoft-daum_postcode", + "version": "1.0.0", + "components": { + "basic": [], + "composite": [], + "layout": [] + } +} +``` + +**에러 응답** + +| 상태코드 | 의미 | 발생 조건 | +| --- | --- | --- | +| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 | + + + +**설명** + + ### GET /api/plugins/{identifier}/components.json - **라우트명**: `api.public.plugins.components` @@ -1377,18 +1831,18 @@ _대표 에러 없음 (공개 조회 — 활성 에셋이 없으면 빈 200 응 **요청 예시** ```http -GET /api/plugins/{identifier}/components.json HTTP/1.1 +GET /api/plugins/sirsoft-daum_postcode/components.json HTTP/1.1 Host: api.example.com Accept: application/json ``` **응답 필드** (`data` 내부) - + **응답 예시** - + **에러 응답** @@ -1416,18 +1870,36 @@ Accept: application/json **요청 예시** ```http -GET /api/plugins/{identifier}/editor-spec HTTP/1.1 +GET /api/plugins/sirsoft-daum_postcode/editor-spec HTTP/1.1 Host: api.example.com Accept: application/json ``` **응답 필드** (`data` 내부) - +_단건 응답: `data` 객체의 필드._ + +| 필드 | 타입 | 실측 예시값 | 용도/설명 | +| --- | --- | --- | --- | +| identifier | string | `sirsoft-daum_postcode` | 플러그인 고유 식별자 (vendor-plugin 형식) | +| spec | null | `null` | 스펙 정의 객체 (편집기/컴포넌트 선언 스키마 등) | **응답 예시** - +```http +HTTP/1.1 200 +``` + +```json +{ + "success": true, + "message": "편집기 스펙이 비어 있습니다.", + "data": { + "identifier": "sirsoft-daum_postcode", + "spec": null + } +} +``` **에러 응답** diff --git a/docs/backend/api/profile.md b/docs/backend/api/profile.md index 90cbbda1..ca5d1711 100644 --- a/docs/backend/api/profile.md +++ b/docs/backend/api/profile.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Profile 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` @@ -123,13 +123,17 @@ Content-Type: application/json "notify_post_complete": true, "notify_post_reply": true, "notify_comment": true, - "notify_reply_comment": true + "notify_reply_comment": true, + "email_subscription": true, + "marketing_consent": true, + "third_party_consent": true, + "info_disclosure": true } ``` **응답 필드** (`data` 내부) - + **응답 예시** @@ -210,7 +214,7 @@ Authorization: Bearer {YOUR_TOKEN} (optional.sanctum: 비회원은 헤더 생 **응답 필드** (`data` 내부) - + **응답 예시** diff --git a/docs/backend/api/roles.md b/docs/backend/api/roles.md index 1c03b361..5f2f0360 100644 --- a/docs/backend/api/roles.md +++ b/docs/backend/api/roles.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Roles 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` @@ -47,23 +47,21 @@ _목록 응답: `data.data[]` 배열 항목의 필드 + `data.pagination`._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| id | integer | `23` | 기본 키 (내부 식별자) | -| identifier | string | `sirsoft-board.archive.manager` | 역할명 (예: admin, user, manager) | -| name | string | `아카이브 게시판 관리자` | 역할 이름 (다국어 JSON) | -| name_raw | object | `{"ko":"아카이브 게시판 관리자","en":"Archive Board Manager"}` | `name` 의 원본 값 (현재 로케일 미해석 원본 JSON/문자열) | -| description | string | `아카이브 게시판의 관리자 역할` | 역할 설명 (다국어 JSON) | -| description_raw | object | `{"ko":"아카이브 게시판의 관리자 역할","en":"Manager role for Archive b…` | `description` 의 원본 값 (현재 로케일 미해석 원본 JSON/문자열) | -| extension_type | string | `module` | 확장 소유 타입: core(코어), module(모듈), plugin(플러그인), NULL(사용자 정의) | -| extension_identifier | string | `sirsoft-board` | 확장 식별자 (예: core, sirsoft-board, sirsoft-payment) | +| id | integer | `157` | 기본 키 (내부 식별자) | +| identifier | string | `i492_scope_self` | 역할명 (예: admin, user, manager) | +| name | string | `#492 스코프검증(self)` | 역할 이름 (다국어 JSON) | +| name_raw | object | `{"ko":"#492 스코프검증(self)","en":"#492 Scope Self"}` | `name` 의 원본 값 (현재 로케일 미해석 원본 JSON/문자열) | +| description | string | `#492 브라우저 실측 G10 전용` | 역할 설명 (다국어 JSON) | +| description_raw | object | `{"ko":"#492 브라우저 실측 G10 전용","en":"for #492 G10"}` | `description` 의 원본 값 (현재 로케일 미해석 원본 JSON/문자열) | +| extension_type | string | `core` | 확장 소유 타입: core(코어), module(모듈), plugin(플러그인), NULL(사용자 정의) | +| extension_identifier | string | `core` | 확장 식별자 (예: core, sirsoft-board, sirsoft-payment) | | extension_name | string | `게시판` | 이 리소스를 소유한 확장의 표시 이름 (manifest name) | | is_deletable | boolean | `false` | deletable 여부 | | is_active | boolean | `true` | active 여부 | | users_count | integer | `1` | users 개수 (집계) | -| permission_ids | array | `[312,313,314,315,316,317,318,319,320,321,322,323,324,325,…` | permission 식별자 배열 (연관 리소스 참조) | -| permission_values | array | `[{"id":312,"scope_type":null},{"id":313,"scope_type":null…` | 할당된 각 권한의 id와 적용 범위만 담은 경량 목록 (원소 id/scope_type — 역할-권한 pivot 파생). scope_type: null=전체, role=역할 범위, self=본인 범위 | -| permissions | array | `[{"id":85,"parent_id":null,"identifier":"sirsoft-board","…` | 연결된 권한 목록 (id/identifier/name — 역할 경유 권한 관계 파생) | -| created_at | string | `2026-06-04 09:35:35` | 생성 일시 | -| updated_at | string | `2026-06-04 09:35:35` | 최종 수정 일시 | +| permissions_count | integer | `2` | 이 역할에 할당된 권한 개수 (집계 — 목록은 권한 배열을 싣지 않고 규모만 제공) | +| created_at | string | `2026-07-31 00:46:12` | 생성 일시 | +| updated_at | string | `2026-07-31 00:46:12` | 최종 수정 일시 | | abilities | object | `{"can_create":true,"can_update":true,"can_delete":true,"c…` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | **응답 예시** @@ -79,592 +77,26 @@ HTTP/1.1 200 "data": { "data": [ { - "id": 6, - "identifier": "manager", - "name": "매니저", + "id": 157, + "identifier": "i492_scope_self", + "name": "#492 스코프검증(self)", "name_raw": { - "ko": "매니저", - "en": "Manager" + "ko": "#492 스코프검증(self)", + "en": "#492 Scope Self" }, - "description": "콘텐츠 및 사용자 관리 권한을 가진 관리자입니다.", + "description": "#492 브라우저 실측 G10 전용", "description_raw": { - "ko": "콘텐츠 및 사용자 관리 권한을 가진 관리자입니다.", - "en": "Manager with content and user management permissions." + "ko": "#492 브라우저 실측 G10 전용", + "en": "for #492 G10" }, "extension_type": "core", "extension_identifier": "core", "extension_name": null, "is_deletable": false, "is_active": true, - "users_count": 0, - "permission_ids": [ - 117, - 118, - 119, - 122, - 123, - 124, - 149, - 176, - 177, - 179, - 182, - 185, - 186, - 187 - ], - "permission_values": [ - { - "id": 117, - "scope_type": "self" - }, - { - "id": 118, - "scope_type": null - }, - { - "id": 119, - "scope_type": "self" - }, - { - "id": 122, - "scope_type": null - }, - { - "id": 123, - "scope_type": null - }, - { - "id": 124, - "scope_type": null - }, - { - "id": 149, - "scope_type": "self" - }, - { - "id": 176, - "scope_type": null - }, - { - "id": 177, - "scope_type": null - }, - { - "id": 179, - "scope_type": null - }, - { - "id": 182, - "scope_type": "self" - }, - { - "id": 185, - "scope_type": null - }, - { - "id": 186, - "scope_type": "self" - }, - { - "id": 187, - "scope_type": "self" - } - ], - "permissions": [ - { - "id": 115, - "parent_id": null, - "identifier": "core", - "name": "코어", - "name_raw": { - "ko": "코어", - "en": "Core" - }, - "description": "코어 시스템 권한", - "description_raw": { - "ko": "코어 시스템 권한", - "en": "Core system permissions" - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 1, - "is_assigned": false, - "is_assignable": false, - "children": [ - { - "id": 116, - "parent_id": 115, - "identifier": "core.users", - "name": "사용자 관리", - "name_raw": { - "ko": "사용자 관리", - "en": "User Management" - }, - "description": "사용자 관리 권한", - "description_raw": { - "ko": "사용자 관리 권한", - "en": "User management permissions" - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 1, - "is_assigned": false, - "is_assignable": false, - "children": [ - { - "id": 117, - "parent_id": 116, - "identifier": "core.users.read", - "name": "사용자 조회", - "name_raw": { - "ko": "사용자 조회", - "en": "View Users" - }, - "description": "사용자 목록 및 상세 정보를 조회할 수 있습니다.", - "description_raw": { - "ko": "사용자 목록 및 상세 정보를 조회할 수 있습니다.", - "en": "Can view user list and details." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": "user", - "owner_key": "id", - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": "self" - }, - { - "id": 118, - "parent_id": 116, - "identifier": "core.users.create", - "name": "사용자 생성", - "name_raw": { - "ko": "사용자 생성", - "en": "Create Users" - }, - "description": "새로운 사용자를 생성할 수 있습니다.", - "description_raw": { - "ko": "새로운 사용자를 생성할 수 있습니다.", - "en": "Can create new users." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 2, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 119, - "parent_id": 116, - "identifier": "core.users.update", - "name": "사용자 수정", - "name_raw": { - "ko": "사용자 수정", - "en": "Update Users" - }, - "description": "사용자 정보를 수정할 수 있습니다.", - "description_raw": { - "ko": "사용자 정보를 수정할 수 있습니다.", - "en": "Can update user information." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": "user", - "owner_key": "id", - "order": 3, - "is_assigned": true, - "is_assignable": true, - "scope_type": "self" - } - ] - }, - { - "id": 121, - "parent_id": 115, - "identifier": "core.menus", - "name": "메뉴 관리", - "name_raw": { - "ko": "메뉴 관리", - "en": "Menu Management" - }, - "description": "메뉴 관리 권한", - "description_raw": { - "ko": "메뉴 관리 권한", - "en": "Menu management permissions" - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 2, - "is_assigned": false, - "is_assignable": false, - "children": [ - { - "id": 122, - "parent_id": 121, - "identifier": "core.menus.read", - "name": "메뉴 조회", - "name_raw": { - "ko": "메뉴 조회", - "en": "View Menus" - }, - "description": "메뉴 목록을 조회할 수 있습니다.", - "description_raw": { - "ko": "메뉴 목록을 조회할 수 있습니다.", - "en": "Can view menu list." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": "menu", - "owner_key": "created_by", - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 123, - "parent_id": 121, - "identifier": "core.menus.create", - "name": "메뉴 생성", - "name_raw": { - "ko": "메뉴 생성", - "en": "Create Menus" - }, - "description": "새로운 메뉴를 생성할 수 있습니다.", - "description_raw": { - "ko": "새로운 메뉴를 생성할 수 있습니다.", - "en": "Can create new menus." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 2, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 124, - "parent_id": 121, - "identifier": "core.menus.update", - "name": "메뉴 수정", - "name_raw": { - "ko": "메뉴 수정", - "en": "Update Menus" - }, - "description": "메뉴 정보를 수정할 수 있습니다.", - "description_raw": { - "ko": "메뉴 정보를 수정할 수 있습니다.", - "en": "Can update menu information." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": "menu", - "owner_key": "created_by", - "order": 3, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - } - ] - }, - { - "id": 148, - "parent_id": 115, - "identifier": "core.notification-logs", - "name": "알림 발송 이력", - "name_raw": { - "ko": "알림 발송 이력", - "en": "Notification Logs" - }, - "description": "알림 발송 이력 관리 권한", - "description_raw": { - "ko": "알림 발송 이력 관리 권한", - "en": "Notification log management permissions" - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 7, - "is_assigned": false, - "is_assignable": false, - "children": [ - { - "id": 149, - "parent_id": 148, - "identifier": "core.notification-logs.read", - "name": "발송 이력 조회", - "name_raw": { - "ko": "발송 이력 조회", - "en": "View Notification Logs" - }, - "description": "알림 발송 이력을 조회할 수 있습니다.", - "description_raw": { - "ko": "알림 발송 이력을 조회할 수 있습니다.", - "en": "Can view notification logs." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": "self" - } - ] - }, - { - "id": 175, - "parent_id": 115, - "identifier": "core.dashboard", - "name": "대시보드", - "name_raw": { - "ko": "대시보드", - "en": "Dashboard" - }, - "description": "대시보드 접근 권한", - "description_raw": { - "ko": "대시보드 접근 권한", - "en": "Dashboard access permissions" - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 9, - "is_assigned": false, - "is_assignable": false, - "children": [ - { - "id": 176, - "parent_id": 175, - "identifier": "core.dashboard.read", - "name": "대시보드 조회", - "name_raw": { - "ko": "대시보드 조회", - "en": "View Dashboard" - }, - "description": "대시보드 통계 및 정보를 조회할 수 있습니다.", - "description_raw": { - "ko": "대시보드 통계 및 정보를 조회할 수 있습니다.", - "en": "Can view dashboard statistics and information." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 177, - "parent_id": 175, - "identifier": "core.dashboard.system-status", - "name": "시스템 상태", - "name_raw": { - "ko": "시스템 상태", - "en": "System Status" - }, - "description": "시스템 상태 정보를 조회할 수 있습니다.", - "description_raw": { - "ko": "시스템 상태 정보를 조회할 수 있습니다.", - "en": "Can view system status information." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 2, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 179, - "parent_id": 175, - "identifier": "core.dashboard.activities", - "name": "최근 활동", - "name_raw": { - "ko": "최근 활동", - "en": "Recent Activities" - }, - "description": "최근 활동 이력을 조회할 수 있습니다.", - "description_raw": { - "ko": "최근 활동 이력을 조회할 수 있습니다.", - "en": "Can view recent activity history." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": "activityLog", - "owner_key": "user_id", - "order": 4, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - } - ] - }, - { - "id": 181, - "parent_id": 115, - "identifier": "core.activities", - "name": "활동 로그", - "name_raw": { - "ko": "활동 로그", - "en": "Activity Logs" - }, - "description": "활동 로그 조회 권한", - "description_raw": { - "ko": "활동 로그 조회 권한", - "en": "Activity log access permissions" - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 10, - "is_assigned": false, - "is_assignable": false, - "children": [ - { - "id": 182, - "parent_id": 181, - "identifier": "core.activities.read", - "name": "활동 로그 조회", - "name_raw": { - "ko": "활동 로그 조회", - "en": "View Activity Logs" - }, - "description": "활동 로그를 조회할 수 있습니다.", - "description_raw": { - "ko": "활동 로그를 조회할 수 있습니다.", - "en": "Can view activity logs." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": "activityLog", - "owner_key": "user_id", - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": "self" - } - ] - }, - { - "id": 184, - "parent_id": 115, - "identifier": "core.attachments", - "name": "첨부파일 관리", - "name_raw": { - "ko": "첨부파일 관리", - "en": "Attachment Management" - }, - "description": "첨부파일 관리 권한", - "description_raw": { - "ko": "첨부파일 관리 권한", - "en": "Attachment management permissions" - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 11, - "is_assigned": false, - "is_assignable": false, - "children": [ - { - "id": 185, - "parent_id": 184, - "identifier": "core.attachments.create", - "name": "첨부파일 업로드", - "name_raw": { - "ko": "첨부파일 업로드", - "en": "Upload Attachments" - }, - "description": "첨부파일을 업로드할 수 있습니다.", - "description_raw": { - "ko": "첨부파일을 업로드할 수 있습니다.", - "en": "Can upload attachments." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 186, - "parent_id": 184, - "identifier": "core.attachments.update", - "name": "첨부파일 수정", - "name_raw": { - "ko": "첨부파일 수정", - "en": "Update Attachments" - }, - "description": "첨부파일 정보 및 순서를 수정할 수 있습니다.", - "description_raw": { - "ko": "첨부파일 정보 및 순서를 수정할 수 있습니다.", - "en": "Can update attachment information and order." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": "attachment", - "owner_key": "created_by", - "order": 2, - "is_assigned": true, - "is_assignable": true, - "scope_type": "self" - }, - { - "id": 187, - "parent_id": 184, - "identifier": "core.attachments.delete", - "name": "첨부파일 삭제", - "name_raw": { - "ko": "첨부파일 삭제", - "en": "Delete Attachments" - }, - "description": "첨부파일을 삭제할 수 있습니다.", - "description_raw": { - "ko": "첨부파일을 삭제할 수 있습니다.", - "en": "Can delete attachments." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": "attachment", - "owner_key": "created_by", - "order": 3, - "is_assigned": true, - "is_assignable": true, - "scope_type": "self" - } - ] - } - ] - } - ], - "created_at": "2026-07-08 10:47:51", - "updated_at": "2026-07-08 10:47:51", + "users_count": 1, + "created_at": "2026-07-31 00:46:12", + "updated_at": "2026-07-31 00:46:12", "abilities": { "can_create": true, "can_update": true, @@ -673,1940 +105,28 @@ HTTP/1.1 200 } }, { - "id": 5, - "identifier": "sirsoft-ecommerce.manager", - "name": "이커머스 관리자", + "id": 156, + "identifier": "manager", + "name": "매니저", "name_raw": { - "ko": "이커머스 관리자", - "en": "Ecommerce Manager" + "ko": "매니저", + "en": "Manager", + "ja": "マネージャー" }, - "description": "이커머스 관리 권한을 가진 역할", + "description": "콘텐츠 및 사용자 관리 권한을 가진 관리자입니다.", "description_raw": { - "ko": "이커머스 관리 권한을 가진 역할", - "en": "Role with ecommerce management permissions" + "ko": "콘텐츠 및 사용자 관리 권한을 가진 관리자입니다.", + "en": "Manager with content and user management permissions.", + "ja": "コンテンツおよびユーザー管理権限を持つ管理者です。" }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "extension_name": "이커머스", + "extension_type": "core", + "extension_identifier": "core", + "extension_name": null, "is_deletable": false, "is_active": true, "users_count": 0, - "permission_ids": [ - 23, - 24, - 25, - 26, - 28, - 29, - 31, - 32, - 33, - 34, - 36, - 37, - 38, - 39, - 41, - 42, - 43, - 44, - 46, - 47, - 48, - 49, - 51, - 52, - 54, - 55, - 56, - 57, - 59, - 60, - 61, - 62, - 64, - 65, - 66, - 67, - 72, - 73, - 74, - 76, - 77, - 79, - 81, - 83, - 84, - 85, - 87, - 89, - 90, - 92, - 94 - ], - "permission_values": [ - { - "id": 23, - "scope_type": null - }, - { - "id": 24, - "scope_type": null - }, - { - "id": 25, - "scope_type": null - }, - { - "id": 26, - "scope_type": null - }, - { - "id": 28, - "scope_type": null - }, - { - "id": 29, - "scope_type": null - }, - { - "id": 31, - "scope_type": null - }, - { - "id": 32, - "scope_type": null - }, - { - "id": 33, - "scope_type": null - }, - { - "id": 34, - "scope_type": null - }, - { - "id": 36, - "scope_type": null - }, - { - "id": 37, - "scope_type": null - }, - { - "id": 38, - "scope_type": null - }, - { - "id": 39, - "scope_type": null - }, - { - "id": 41, - "scope_type": null - }, - { - "id": 42, - "scope_type": null - }, - { - "id": 43, - "scope_type": null - }, - { - "id": 44, - "scope_type": null - }, - { - "id": 46, - "scope_type": null - }, - { - "id": 47, - "scope_type": null - }, - { - "id": 48, - "scope_type": null - }, - { - "id": 49, - "scope_type": null - }, - { - "id": 51, - "scope_type": null - }, - { - "id": 52, - "scope_type": null - }, - { - "id": 54, - "scope_type": null - }, - { - "id": 55, - "scope_type": null - }, - { - "id": 56, - "scope_type": null - }, - { - "id": 57, - "scope_type": null - }, - { - "id": 59, - "scope_type": null - }, - { - "id": 60, - "scope_type": null - }, - { - "id": 61, - "scope_type": null - }, - { - "id": 62, - "scope_type": null - }, - { - "id": 64, - "scope_type": null - }, - { - "id": 65, - "scope_type": null - }, - { - "id": 66, - "scope_type": null - }, - { - "id": 67, - "scope_type": null - }, - { - "id": 72, - "scope_type": null - }, - { - "id": 73, - "scope_type": null - }, - { - "id": 74, - "scope_type": null - }, - { - "id": 76, - "scope_type": null - }, - { - "id": 77, - "scope_type": null - }, - { - "id": 79, - "scope_type": null - }, - { - "id": 81, - "scope_type": null - }, - { - "id": 83, - "scope_type": null - }, - { - "id": 84, - "scope_type": null - }, - { - "id": 85, - "scope_type": null - }, - { - "id": 87, - "scope_type": null - }, - { - "id": 89, - "scope_type": null - }, - { - "id": 90, - "scope_type": null - }, - { - "id": 92, - "scope_type": null - }, - { - "id": 94, - "scope_type": null - } - ], - "permissions": [ - { - "id": 21, - "parent_id": null, - "identifier": "sirsoft-ecommerce", - "name": "이커머스", - "name_raw": { - "ko": "이커머스", - "en": "Ecommerce" - }, - "description": "이커머스 모듈 권한", - "description_raw": { - "ko": "이커머스 모듈 권한", - "en": "Ecommerce module permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 100, - "is_assigned": false, - "is_assignable": false, - "children": [ - { - "id": 22, - "parent_id": 21, - "identifier": "sirsoft-ecommerce.products", - "name": "상품 관리", - "name_raw": { - "ko": "상품 관리", - "en": "Product Management" - }, - "description": "상품 관리 권한", - "description_raw": { - "ko": "상품 관리 권한", - "en": "Product management permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 1, - "is_assigned": false, - "is_assignable": false, - "children": [ - { - "id": 23, - "parent_id": 22, - "identifier": "sirsoft-ecommerce.products.read", - "name": "상품 조회", - "name_raw": { - "ko": "상품 조회", - "en": "Read Products" - }, - "description": "상품 목록 및 상세 조회", - "description_raw": { - "ko": "상품 목록 및 상세 조회", - "en": "Read product list and details" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": "product", - "owner_key": "created_by", - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 24, - "parent_id": 22, - "identifier": "sirsoft-ecommerce.products.create", - "name": "상품 생성", - "name_raw": { - "ko": "상품 생성", - "en": "Create Product" - }, - "description": "새 상품 등록", - "description_raw": { - "ko": "새 상품 등록", - "en": "Create new product" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 2, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 25, - "parent_id": 22, - "identifier": "sirsoft-ecommerce.products.update", - "name": "상품 수정", - "name_raw": { - "ko": "상품 수정", - "en": "Update Product" - }, - "description": "상품 정보 수정", - "description_raw": { - "ko": "상품 정보 수정", - "en": "Update product information" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": "product", - "owner_key": "created_by", - "order": 3, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 26, - "parent_id": 22, - "identifier": "sirsoft-ecommerce.products.delete", - "name": "상품 삭제", - "name_raw": { - "ko": "상품 삭제", - "en": "Delete Product" - }, - "description": "상품 삭제", - "description_raw": { - "ko": "상품 삭제", - "en": "Delete product" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": "product", - "owner_key": "created_by", - "order": 4, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - } - ] - }, - { - "id": 27, - "parent_id": 21, - "identifier": "sirsoft-ecommerce.orders", - "name": "주문 관리", - "name_raw": { - "ko": "주문 관리", - "en": "Order Management" - }, - "description": "주문 관리 권한", - "description_raw": { - "ko": "주문 관리 권한", - "en": "Order management permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 2, - "is_assigned": false, - "is_assignable": false, - "children": [ - { - "id": 28, - "parent_id": 27, - "identifier": "sirsoft-ecommerce.orders.read", - "name": "주문 조회", - "name_raw": { - "ko": "주문 조회", - "en": "Read Orders" - }, - "description": "주문 목록 및 상세 조회", - "description_raw": { - "ko": "주문 목록 및 상세 조회", - "en": "Read order list and details" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": "order", - "owner_key": "user_id", - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 29, - "parent_id": 27, - "identifier": "sirsoft-ecommerce.orders.update", - "name": "주문 수정", - "name_raw": { - "ko": "주문 수정", - "en": "Update Order" - }, - "description": "주문 상태 변경", - "description_raw": { - "ko": "주문 상태 변경", - "en": "Update order status" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": "order", - "owner_key": "user_id", - "order": 2, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - } - ] - }, - { - "id": 30, - "parent_id": 21, - "identifier": "sirsoft-ecommerce.categories", - "name": "카테고리 관리", - "name_raw": { - "ko": "카테고리 관리", - "en": "Category Management" - }, - "description": "카테고리 관리 권한", - "description_raw": { - "ko": "카테고리 관리 권한", - "en": "Category management permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 3, - "is_assigned": false, - "is_assignable": false, - "children": [ - { - "id": 31, - "parent_id": 30, - "identifier": "sirsoft-ecommerce.categories.read", - "name": "카테고리 조회", - "name_raw": { - "ko": "카테고리 조회", - "en": "Read Categories" - }, - "description": "카테고리 목록 및 상세 조회", - "description_raw": { - "ko": "카테고리 목록 및 상세 조회", - "en": "Read category list and details" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 32, - "parent_id": 30, - "identifier": "sirsoft-ecommerce.categories.create", - "name": "카테고리 생성", - "name_raw": { - "ko": "카테고리 생성", - "en": "Create Category" - }, - "description": "카테고리 생성", - "description_raw": { - "ko": "카테고리 생성", - "en": "Create category" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 2, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 33, - "parent_id": 30, - "identifier": "sirsoft-ecommerce.categories.update", - "name": "카테고리 수정", - "name_raw": { - "ko": "카테고리 수정", - "en": "Update Category" - }, - "description": "카테고리 수정", - "description_raw": { - "ko": "카테고리 수정", - "en": "Update category" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 3, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 34, - "parent_id": 30, - "identifier": "sirsoft-ecommerce.categories.delete", - "name": "카테고리 삭제", - "name_raw": { - "ko": "카테고리 삭제", - "en": "Delete Category" - }, - "description": "카테고리 삭제", - "description_raw": { - "ko": "카테고리 삭제", - "en": "Delete category" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 4, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - } - ] - }, - { - "id": 35, - "parent_id": 21, - "identifier": "sirsoft-ecommerce.brands", - "name": "브랜드 관리", - "name_raw": { - "ko": "브랜드 관리", - "en": "Brand Management" - }, - "description": "브랜드 관리 권한", - "description_raw": { - "ko": "브랜드 관리 권한", - "en": "Brand management permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 4, - "is_assigned": false, - "is_assignable": false, - "children": [ - { - "id": 36, - "parent_id": 35, - "identifier": "sirsoft-ecommerce.brands.read", - "name": "브랜드 조회", - "name_raw": { - "ko": "브랜드 조회", - "en": "Read Brands" - }, - "description": "브랜드 목록 및 상세 조회", - "description_raw": { - "ko": "브랜드 목록 및 상세 조회", - "en": "Read brand list and details" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": "brand", - "owner_key": "created_by", - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 37, - "parent_id": 35, - "identifier": "sirsoft-ecommerce.brands.create", - "name": "브랜드 생성", - "name_raw": { - "ko": "브랜드 생성", - "en": "Create Brand" - }, - "description": "브랜드 생성", - "description_raw": { - "ko": "브랜드 생성", - "en": "Create brand" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 2, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 38, - "parent_id": 35, - "identifier": "sirsoft-ecommerce.brands.update", - "name": "브랜드 수정", - "name_raw": { - "ko": "브랜드 수정", - "en": "Update Brand" - }, - "description": "브랜드 수정", - "description_raw": { - "ko": "브랜드 수정", - "en": "Update brand" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": "brand", - "owner_key": "created_by", - "order": 3, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 39, - "parent_id": 35, - "identifier": "sirsoft-ecommerce.brands.delete", - "name": "브랜드 삭제", - "name_raw": { - "ko": "브랜드 삭제", - "en": "Delete Brand" - }, - "description": "브랜드 삭제", - "description_raw": { - "ko": "브랜드 삭제", - "en": "Delete brand" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": "brand", - "owner_key": "created_by", - "order": 4, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - } - ] - }, - { - "id": 40, - "parent_id": 21, - "identifier": "sirsoft-ecommerce.product-notice-templates", - "name": "상품정보제공고시 관리", - "name_raw": { - "ko": "상품정보제공고시 관리", - "en": "Product Notice Template Management" - }, - "description": "상품정보제공고시 템플릿 관리 권한", - "description_raw": { - "ko": "상품정보제공고시 템플릿 관리 권한", - "en": "Product notice template management permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 5, - "is_assigned": false, - "is_assignable": false, - "children": [ - { - "id": 41, - "parent_id": 40, - "identifier": "sirsoft-ecommerce.product-notice-templates.read", - "name": "조회", - "name_raw": { - "ko": "조회", - "en": "Read" - }, - "description": "상품정보제공고시 조회", - "description_raw": { - "ko": "상품정보제공고시 조회", - "en": "Read product notice templates" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 42, - "parent_id": 40, - "identifier": "sirsoft-ecommerce.product-notice-templates.create", - "name": "생성", - "name_raw": { - "ko": "생성", - "en": "Create" - }, - "description": "상품정보제공고시 생성", - "description_raw": { - "ko": "상품정보제공고시 생성", - "en": "Create product notice template" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 2, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 43, - "parent_id": 40, - "identifier": "sirsoft-ecommerce.product-notice-templates.update", - "name": "수정", - "name_raw": { - "ko": "수정", - "en": "Update" - }, - "description": "상품정보제공고시 수정", - "description_raw": { - "ko": "상품정보제공고시 수정", - "en": "Update product notice template" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 3, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 44, - "parent_id": 40, - "identifier": "sirsoft-ecommerce.product-notice-templates.delete", - "name": "삭제", - "name_raw": { - "ko": "삭제", - "en": "Delete" - }, - "description": "상품정보제공고시 삭제", - "description_raw": { - "ko": "상품정보제공고시 삭제", - "en": "Delete product notice template" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 4, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - } - ] - }, - { - "id": 45, - "parent_id": 21, - "identifier": "sirsoft-ecommerce.product-common-infos", - "name": "공통정보 관리", - "name_raw": { - "ko": "공통정보 관리", - "en": "Product Common Info Management" - }, - "description": "상품 공통정보 관리 권한", - "description_raw": { - "ko": "상품 공통정보 관리 권한", - "en": "Product common info management permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 6, - "is_assigned": false, - "is_assignable": false, - "children": [ - { - "id": 46, - "parent_id": 45, - "identifier": "sirsoft-ecommerce.product-common-infos.read", - "name": "조회", - "name_raw": { - "ko": "조회", - "en": "Read" - }, - "description": "공통정보 조회", - "description_raw": { - "ko": "공통정보 조회", - "en": "Read product common info" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 47, - "parent_id": 45, - "identifier": "sirsoft-ecommerce.product-common-infos.create", - "name": "생성", - "name_raw": { - "ko": "생성", - "en": "Create" - }, - "description": "공통정보 생성", - "description_raw": { - "ko": "공통정보 생성", - "en": "Create product common info" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 2, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 48, - "parent_id": 45, - "identifier": "sirsoft-ecommerce.product-common-infos.update", - "name": "수정", - "name_raw": { - "ko": "수정", - "en": "Update" - }, - "description": "공통정보 수정", - "description_raw": { - "ko": "공통정보 수정", - "en": "Update product common info" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 3, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 49, - "parent_id": 45, - "identifier": "sirsoft-ecommerce.product-common-infos.delete", - "name": "삭제", - "name_raw": { - "ko": "삭제", - "en": "Delete" - }, - "description": "공통정보 삭제", - "description_raw": { - "ko": "공통정보 삭제", - "en": "Delete product common info" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 4, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - } - ] - }, - { - "id": 50, - "parent_id": 21, - "identifier": "sirsoft-ecommerce.settings", - "name": "환경설정", - "name_raw": { - "ko": "환경설정", - "en": "Settings" - }, - "description": "이커머스 환경설정 권한", - "description_raw": { - "ko": "이커머스 환경설정 권한", - "en": "Ecommerce settings permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 7, - "is_assigned": false, - "is_assignable": false, - "children": [ - { - "id": 51, - "parent_id": 50, - "identifier": "sirsoft-ecommerce.settings.read", - "name": "환경설정 조회", - "name_raw": { - "ko": "환경설정 조회", - "en": "View Settings" - }, - "description": "이커머스 환경설정 조회", - "description_raw": { - "ko": "이커머스 환경설정 조회", - "en": "View ecommerce settings" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 52, - "parent_id": 50, - "identifier": "sirsoft-ecommerce.settings.update", - "name": "환경설정 수정", - "name_raw": { - "ko": "환경설정 수정", - "en": "Update Settings" - }, - "description": "이커머스 환경설정 수정", - "description_raw": { - "ko": "이커머스 환경설정 수정", - "en": "Update ecommerce settings" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 2, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - } - ] - }, - { - "id": 53, - "parent_id": 21, - "identifier": "sirsoft-ecommerce.promotion-coupon", - "name": "쿠폰 관리", - "name_raw": { - "ko": "쿠폰 관리", - "en": "Coupon Management" - }, - "description": "쿠폰 관리 권한", - "description_raw": { - "ko": "쿠폰 관리 권한", - "en": "Coupon management permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 8, - "is_assigned": false, - "is_assignable": false, - "children": [ - { - "id": 54, - "parent_id": 53, - "identifier": "sirsoft-ecommerce.promotion-coupon.read", - "name": "쿠폰 조회", - "name_raw": { - "ko": "쿠폰 조회", - "en": "Read Coupons" - }, - "description": "쿠폰 목록 및 상세 조회", - "description_raw": { - "ko": "쿠폰 목록 및 상세 조회", - "en": "Read coupon list and details" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": "coupon", - "owner_key": "created_by", - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 55, - "parent_id": 53, - "identifier": "sirsoft-ecommerce.promotion-coupon.create", - "name": "쿠폰 생성", - "name_raw": { - "ko": "쿠폰 생성", - "en": "Create Coupon" - }, - "description": "새 쿠폰 등록", - "description_raw": { - "ko": "새 쿠폰 등록", - "en": "Create new coupon" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 2, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 56, - "parent_id": 53, - "identifier": "sirsoft-ecommerce.promotion-coupon.update", - "name": "쿠폰 수정", - "name_raw": { - "ko": "쿠폰 수정", - "en": "Update Coupon" - }, - "description": "쿠폰 정보 수정", - "description_raw": { - "ko": "쿠폰 정보 수정", - "en": "Update coupon information" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": "coupon", - "owner_key": "created_by", - "order": 3, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 57, - "parent_id": 53, - "identifier": "sirsoft-ecommerce.promotion-coupon.delete", - "name": "쿠폰 삭제", - "name_raw": { - "ko": "쿠폰 삭제", - "en": "Delete Coupon" - }, - "description": "쿠폰 삭제", - "description_raw": { - "ko": "쿠폰 삭제", - "en": "Delete coupon" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": "coupon", - "owner_key": "created_by", - "order": 4, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - } - ] - }, - { - "id": 58, - "parent_id": 21, - "identifier": "sirsoft-ecommerce.shipping-policies", - "name": "배송정책 관리", - "name_raw": { - "ko": "배송정책 관리", - "en": "Shipping Policy Management" - }, - "description": "배송정책 관리 권한", - "description_raw": { - "ko": "배송정책 관리 권한", - "en": "Shipping policy management permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 9, - "is_assigned": false, - "is_assignable": false, - "children": [ - { - "id": 59, - "parent_id": 58, - "identifier": "sirsoft-ecommerce.shipping-policies.read", - "name": "배송정책 조회", - "name_raw": { - "ko": "배송정책 조회", - "en": "Read Shipping Policies" - }, - "description": "배송정책 목록 및 상세 조회", - "description_raw": { - "ko": "배송정책 목록 및 상세 조회", - "en": "Read shipping policy list and details" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": "shippingPolicy", - "owner_key": "created_by", - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 60, - "parent_id": 58, - "identifier": "sirsoft-ecommerce.shipping-policies.create", - "name": "배송정책 생성", - "name_raw": { - "ko": "배송정책 생성", - "en": "Create Shipping Policy" - }, - "description": "새 배송정책 등록", - "description_raw": { - "ko": "새 배송정책 등록", - "en": "Create new shipping policy" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 2, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 61, - "parent_id": 58, - "identifier": "sirsoft-ecommerce.shipping-policies.update", - "name": "배송정책 수정", - "name_raw": { - "ko": "배송정책 수정", - "en": "Update Shipping Policy" - }, - "description": "배송정책 정보 수정", - "description_raw": { - "ko": "배송정책 정보 수정", - "en": "Update shipping policy information" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": "shippingPolicy", - "owner_key": "created_by", - "order": 3, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 62, - "parent_id": 58, - "identifier": "sirsoft-ecommerce.shipping-policies.delete", - "name": "배송정책 삭제", - "name_raw": { - "ko": "배송정책 삭제", - "en": "Delete Shipping Policy" - }, - "description": "배송정책 삭제", - "description_raw": { - "ko": "배송정책 삭제", - "en": "Delete shipping policy" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": "shippingPolicy", - "owner_key": "created_by", - "order": 4, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - } - ] - }, - { - "id": 63, - "parent_id": 21, - "identifier": "sirsoft-ecommerce.product-labels", - "name": "상품 라벨 관리", - "name_raw": { - "ko": "상품 라벨 관리", - "en": "Product Label Management" - }, - "description": "상품 라벨 관리 권한", - "description_raw": { - "ko": "상품 라벨 관리 권한", - "en": "Product label management permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 10, - "is_assigned": false, - "is_assignable": false, - "children": [ - { - "id": 64, - "parent_id": 63, - "identifier": "sirsoft-ecommerce.product-labels.read", - "name": "상품 라벨 조회", - "name_raw": { - "ko": "상품 라벨 조회", - "en": "Read Product Labels" - }, - "description": "상품 라벨 목록 및 상세 조회", - "description_raw": { - "ko": "상품 라벨 목록 및 상세 조회", - "en": "Read product label list and details" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 65, - "parent_id": 63, - "identifier": "sirsoft-ecommerce.product-labels.create", - "name": "상품 라벨 생성", - "name_raw": { - "ko": "상품 라벨 생성", - "en": "Create Product Label" - }, - "description": "새 상품 라벨 등록", - "description_raw": { - "ko": "새 상품 라벨 등록", - "en": "Create new product label" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 2, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 66, - "parent_id": 63, - "identifier": "sirsoft-ecommerce.product-labels.update", - "name": "상품 라벨 수정", - "name_raw": { - "ko": "상품 라벨 수정", - "en": "Update Product Label" - }, - "description": "상품 라벨 정보 수정", - "description_raw": { - "ko": "상품 라벨 정보 수정", - "en": "Update product label information" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 3, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 67, - "parent_id": 63, - "identifier": "sirsoft-ecommerce.product-labels.delete", - "name": "상품 라벨 삭제", - "name_raw": { - "ko": "상품 라벨 삭제", - "en": "Delete Product Label" - }, - "description": "상품 라벨 삭제", - "description_raw": { - "ko": "상품 라벨 삭제", - "en": "Delete product label" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 4, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - } - ] - }, - { - "id": 71, - "parent_id": 21, - "identifier": "sirsoft-ecommerce.reviews", - "name": "리뷰 관리", - "name_raw": { - "ko": "리뷰 관리", - "en": "Review Management" - }, - "description": "상품 리뷰 관리 권한", - "description_raw": { - "ko": "상품 리뷰 관리 권한", - "en": "Product review management permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 12, - "is_assigned": false, - "is_assignable": false, - "children": [ - { - "id": 72, - "parent_id": 71, - "identifier": "sirsoft-ecommerce.reviews.read", - "name": "리뷰 조회", - "name_raw": { - "ko": "리뷰 조회", - "en": "Read Reviews" - }, - "description": "리뷰 목록 및 상세 조회", - "description_raw": { - "ko": "리뷰 목록 및 상세 조회", - "en": "Read review list and details" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": "review", - "owner_key": "user_id", - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 73, - "parent_id": 71, - "identifier": "sirsoft-ecommerce.reviews.update", - "name": "리뷰 처리", - "name_raw": { - "ko": "리뷰 처리", - "en": "Manage Review" - }, - "description": "리뷰 상태 변경, 답변 등록/수정/삭제, 일괄 처리", - "description_raw": { - "ko": "리뷰 상태 변경, 답변 등록/수정/삭제, 일괄 처리", - "en": "Update review status, manage replies, bulk actions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": "review", - "owner_key": "user_id", - "order": 2, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 74, - "parent_id": 71, - "identifier": "sirsoft-ecommerce.reviews.delete", - "name": "리뷰 삭제", - "name_raw": { - "ko": "리뷰 삭제", - "en": "Delete Review" - }, - "description": "리뷰 삭제", - "description_raw": { - "ko": "리뷰 삭제", - "en": "Delete review" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": "review", - "owner_key": "user_id", - "order": 3, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - } - ] - }, - { - "id": 75, - "parent_id": 21, - "identifier": "sirsoft-ecommerce.inquiries", - "name": "문의 관리", - "name_raw": { - "ko": "문의 관리", - "en": "Inquiry Management" - }, - "description": "상품 1:1 문의 관리 권한", - "description_raw": { - "ko": "상품 1:1 문의 관리 권한", - "en": "Product inquiry management permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 13, - "is_assigned": false, - "is_assignable": false, - "children": [ - { - "id": 76, - "parent_id": 75, - "identifier": "sirsoft-ecommerce.inquiries.update", - "name": "문의 처리", - "name_raw": { - "ko": "문의 처리", - "en": "Manage Inquiry" - }, - "description": "답변 등록/수정/삭제, 비밀글 내용 열람", - "description_raw": { - "ko": "답변 등록/수정/삭제, 비밀글 내용 열람", - "en": "Create, update, and delete inquiry replies; view secret inquiry contents" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 77, - "parent_id": 75, - "identifier": "sirsoft-ecommerce.inquiries.delete", - "name": "문의 삭제", - "name_raw": { - "ko": "문의 삭제", - "en": "Delete Inquiry" - }, - "description": "고객이 작성한 문의 자체를 삭제", - "description_raw": { - "ko": "고객이 작성한 문의 자체를 삭제", - "en": "Delete inquiries submitted by customers" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 2, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - } - ] - }, - { - "id": 78, - "parent_id": 21, - "identifier": "sirsoft-ecommerce.dashboard", - "name": "대시보드", - "name_raw": { - "ko": "대시보드", - "en": "Dashboard" - }, - "description": "관리자 대시보드 이커머스 영역 조회 권한", - "description_raw": { - "ko": "관리자 대시보드 이커머스 영역 조회 권한", - "en": "Admin dashboard commerce area view permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 14, - "is_assigned": false, - "is_assignable": false, - "children": [ - { - "id": 79, - "parent_id": 78, - "identifier": "sirsoft-ecommerce.dashboard.view", - "name": "대시보드 조회", - "name_raw": { - "ko": "대시보드 조회", - "en": "View Dashboard" - }, - "description": "관리자 대시보드의 이커머스 판매 현황/리뷰/문의 위젯 조회", - "description_raw": { - "ko": "관리자 대시보드의 이커머스 판매 현황/리뷰/문의 위젯 조회", - "en": "View commerce sales/review/inquiry widgets on the admin dashboard" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - } - ] - }, - { - "id": 80, - "parent_id": 21, - "identifier": "sirsoft-ecommerce.user-products", - "name": "사용자 상품", - "name_raw": { - "ko": "사용자 상품", - "en": "User Products" - }, - "description": "사용자 상품 접근 권한 (블랙컨슈머 차단용)", - "description_raw": { - "ko": "사용자 상품 접근 권한 (블랙컨슈머 차단용)", - "en": "User product access permissions (for blocking malicious consumers)" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 15, - "is_assigned": false, - "is_assignable": false, - "children": [ - { - "id": 81, - "parent_id": 80, - "identifier": "sirsoft-ecommerce.user-products.read", - "name": "상품 조회", - "name_raw": { - "ko": "상품 조회", - "en": "View Products" - }, - "description": "상품 목록 및 상세 조회", - "description_raw": { - "ko": "상품 목록 및 상세 조회", - "en": "View product list and details" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - } - ] - }, - { - "id": 82, - "parent_id": 21, - "identifier": "sirsoft-ecommerce.user-orders", - "name": "사용자 주문", - "name_raw": { - "ko": "사용자 주문", - "en": "User Orders" - }, - "description": "사용자 주문 관련 권한 (블랙컨슈머 차단용)", - "description_raw": { - "ko": "사용자 주문 관련 권한 (블랙컨슈머 차단용)", - "en": "User order permissions (for blocking malicious consumers)" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 16, - "is_assigned": false, - "is_assignable": false, - "children": [ - { - "id": 83, - "parent_id": 82, - "identifier": "sirsoft-ecommerce.user-orders.create", - "name": "주문하기", - "name_raw": { - "ko": "주문하기", - "en": "Create Order" - }, - "description": "주문 생성", - "description_raw": { - "ko": "주문 생성", - "en": "Create a new order" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 84, - "parent_id": 82, - "identifier": "sirsoft-ecommerce.user-orders.cancel", - "name": "주문 취소", - "name_raw": { - "ko": "주문 취소", - "en": "Cancel Order" - }, - "description": "주문 취소 요청", - "description_raw": { - "ko": "주문 취소 요청", - "en": "Request order cancellation" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 2, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 85, - "parent_id": 82, - "identifier": "sirsoft-ecommerce.user-orders.confirm", - "name": "구매확정", - "name_raw": { - "ko": "구매확정", - "en": "Confirm Purchase" - }, - "description": "주문 상품 구매확정", - "description_raw": { - "ko": "주문 상품 구매확정", - "en": "Confirm purchase of order item" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 3, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - } - ] - }, - { - "id": 86, - "parent_id": 21, - "identifier": "sirsoft-ecommerce.user-reviews", - "name": "사용자 리뷰", - "name_raw": { - "ko": "사용자 리뷰", - "en": "User Reviews" - }, - "description": "사용자 리뷰 관련 권한 (블랙컨슈머 차단용)", - "description_raw": { - "ko": "사용자 리뷰 관련 권한 (블랙컨슈머 차단용)", - "en": "User review permissions (for blocking malicious consumers)" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 17, - "is_assigned": false, - "is_assignable": false, - "children": [ - { - "id": 87, - "parent_id": 86, - "identifier": "sirsoft-ecommerce.user-reviews.write", - "name": "리뷰 작성", - "name_raw": { - "ko": "리뷰 작성", - "en": "Write Review" - }, - "description": "상품 리뷰 작성", - "description_raw": { - "ko": "상품 리뷰 작성", - "en": "Write product review" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - } - ] - }, - { - "id": 88, - "parent_id": 21, - "identifier": "sirsoft-ecommerce.mileage", - "name": "마일리지 관리", - "name_raw": { - "ko": "마일리지 관리", - "en": "Mileage Management" - }, - "description": "마일리지 내역 조회 및 수동 지급/차감 권한", - "description_raw": { - "ko": "마일리지 내역 조회 및 수동 지급/차감 권한", - "en": "Mileage history and manual grant/deduct permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 18, - "is_assigned": false, - "is_assignable": false, - "children": [ - { - "id": 89, - "parent_id": 88, - "identifier": "sirsoft-ecommerce.mileage.read", - "name": "마일리지 내역 조회", - "name_raw": { - "ko": "마일리지 내역 조회", - "en": "Read Mileage" - }, - "description": "마일리지 내역 목록 및 상세 조회", - "description_raw": { - "ko": "마일리지 내역 목록 및 상세 조회", - "en": "Read mileage history list and details" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": "mileage-transaction", - "owner_key": "user_id", - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 90, - "parent_id": 88, - "identifier": "sirsoft-ecommerce.mileage.manage", - "name": "마일리지 수동 처리", - "name_raw": { - "ko": "마일리지 수동 처리", - "en": "Manage Mileage" - }, - "description": "마일리지 수동 지급/차감 및 일괄 유효기간 연장", - "description_raw": { - "ko": "마일리지 수동 지급/차감 및 일괄 유효기간 연장", - "en": "Manual grant/deduct and bulk expiry extension" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": "mileage-transaction", - "owner_key": "user_id", - "order": 2, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - } - ] - }, - { - "id": 91, - "parent_id": 21, - "identifier": "sirsoft-ecommerce.user-currency", - "name": "회원 결제 통화 관리", - "name_raw": { - "ko": "회원 결제 통화 관리", - "en": "User Currency Management" - }, - "description": "관리자가 회원별 결제 통화를 변경하는 권한", - "description_raw": { - "ko": "관리자가 회원별 결제 통화를 변경하는 권한", - "en": "Permission for admins to change a user's payment currency" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 19, - "is_assigned": false, - "is_assignable": false, - "children": [ - { - "id": 92, - "parent_id": 91, - "identifier": "sirsoft-ecommerce.user-currency.manage", - "name": "회원 결제 통화 변경", - "name_raw": { - "ko": "회원 결제 통화 변경", - "en": "Manage User Currency" - }, - "description": "회원별 결제 통화 변경", - "description_raw": { - "ko": "회원별 결제 통화 변경", - "en": "Change a user's payment currency" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - } - ] - }, - { - "id": 93, - "parent_id": 21, - "identifier": "sirsoft-ecommerce.user-shipping-country", - "name": "회원 배송국가 관리", - "name_raw": { - "ko": "회원 배송국가 관리", - "en": "User Shipping Country Management" - }, - "description": "관리자가 회원별 배송국가를 변경하는 권한", - "description_raw": { - "ko": "관리자가 회원별 배송국가를 변경하는 권한", - "en": "Permission for admins to change a user's shipping country" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 20, - "is_assigned": false, - "is_assignable": false, - "children": [ - { - "id": 94, - "parent_id": 93, - "identifier": "sirsoft-ecommerce.user-shipping-country.manage", - "name": "회원 배송국가 변경", - "name_raw": { - "ko": "회원 배송국가 변경", - "en": "Manage User Shipping Country" - }, - "description": "회원별 배송국가 변경", - "description_raw": { - "ko": "회원별 배송국가 변경", - "en": "Change a user's shipping country" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - } - ] - } - ] - } - ], - "created_at": "2026-07-08 10:43:31", - "updated_at": "2026-07-08 10:43:31", + "created_at": "2026-07-31 00:03:11", + "updated_at": "2026-08-01 11:46:14", "abilities": { "can_create": true, "can_update": true, @@ -2614,7 +134,7 @@ HTTP/1.1 200 "can_toggle_status": true } }, - "... (총 6건 중 2건 표시)" + "... (총 25건 중 2건 표시)" ], "abilities": { "can_create": true, @@ -2623,12 +143,12 @@ HTTP/1.1 200 }, "pagination": { "current_page": 1, - "last_page": 1, + "last_page": 4, "per_page": 25, - "total": 6, + "total": 100, "from": 1, - "to": 6, - "has_more_pages": false + "to": 25, + "has_more_pages": true } } } @@ -2646,7 +166,7 @@ HTTP/1.1 200 **설명** -역할 관리 화면(`admin_role_list.json`)의 목록 데이터를 제공하는 페이지네이션 조회 엔드포인트다. `search`(identifier/name 텍스트 검색)와 `is_active`(활성 여부)로 필터링하며 `per_page` 로 페이지 크기를 조절한다. 응답에는 각 역할의 할당 권한(permission_ids/permission_values/permissions), 소유 확장 정보, 사용자 수, 현재 사용자의 조작 가능 여부(abilities)가 포함된다. `core.permissions.read` 권한이 필요하다. +역할 관리 화면(`admin_role_list.json`)의 목록 데이터를 제공하는 페이지네이션 조회 엔드포인트다. `search`(identifier/name 텍스트 검색)와 `is_active`(활성 여부)로 필터링하며 `per_page` 로 페이지 크기를 조절한다. 응답에는 소유 확장 정보, 사용자 수(`users_count`), 할당 권한 개수(`permissions_count`), 현재 사용자의 조작 가능 여부(abilities)가 포함된다. 목록에는 **할당 권한의 목록(`permission_ids`/`permission_values`/`permissions`)이 실리지 않는다** — 권한 트리는 역할 하나당 수백 건까지 갈 수 있어 목록을 여는 것만으로 전 행의 권한이 전송됐다. 권한 목록이 필요하면 단건 조회(`GET /api/admin/roles/{role}`)를 사용한다. `core.permissions.read` 권한이 필요하다. ### POST /api/admin/roles @@ -2693,19 +213,19 @@ _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| id | integer | `14` | 기본 키 (내부 식별자) | -| identifier | string | `probe_6a4dc0a720350` | 역할명 (예: admin, user, manager) | +| id | integer | `893` | 기본 키 (내부 식별자) | +| identifier | string | `probe_6a71e0db284f0` | 역할명 (예: admin, user, manager) | | name | string | `실측 예시값` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | -| name_raw | object | `{"ko":"실측 예시값","en":"실측 예시값","fr":"실측 예시값"}` | `name` 의 원본 값 (현재 로케일 미해석 원본 JSON/문자열) | +| name_raw | object | `{"ko":"실측 예시값","en":"실측 예시값"}` | `name` 의 원본 값 (현재 로케일 미해석 원본 JSON/문자열) | | description | string | `실측 예시값` | 설명 (다국어 필드는 로케일별 값 객체) | -| description_raw | object | `{"ko":"실측 예시값","en":"실측 예시값","fr":"실측 예시값"}` | `description` 의 원본 값 (현재 로케일 미해석 원본 JSON/문자열) | +| description_raw | object | `{"ko":"실측 예시값","en":"실측 예시값"}` | `description` 의 원본 값 (현재 로케일 미해석 원본 JSON/문자열) | | extension_type | null | `null` | 이 리소스를 소유한 확장의 타입 (core/module/plugin/template) | | extension_identifier | null | `null` | 이 리소스를 소유한 확장의 식별자 | | extension_name | null | `null` | 이 리소스를 소유한 확장의 표시 이름 (manifest name) | | is_deletable | boolean | `true` | deletable 여부 | | is_active | boolean | `true` | active 여부 | -| created_at | string | `2026-07-08 12:14:47` | 생성 일시 | -| updated_at | string | `2026-07-08 12:14:47` | 최종 수정 일시 | +| created_at | string | `2026-08-04 21:53:47` | 생성 일시 | +| updated_at | string | `2026-08-04 21:53:47` | 최종 수정 일시 | | abilities | object | `{"can_create":true,"can_update":true,"can_delete":true,"c…` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | **응답 예시** @@ -2719,27 +239,25 @@ HTTP/1.1 201 "success": true, "message": "역할이 성공적으로 생성되었습니다.", "data": { - "id": 14, - "identifier": "probe_6a4dc0a720350", + "id": 893, + "identifier": "probe_6a71e0db284f0", "name": "실측 예시값", "name_raw": { "ko": "실측 예시값", - "en": "실측 예시값", - "fr": "실측 예시값" + "en": "실측 예시값" }, "description": "실측 예시값", "description_raw": { "ko": "실측 예시값", - "en": "실측 예시값", - "fr": "실측 예시값" + "en": "실측 예시값" }, "extension_type": null, "extension_identifier": null, "extension_name": null, "is_deletable": true, "is_active": true, - "created_at": "2026-07-08 12:14:47", - "updated_at": "2026-07-08 12:14:47", + "created_at": "2026-08-04 21:53:47", + "updated_at": "2026-08-04 21:53:47", "abilities": { "can_create": true, "can_update": true, @@ -2793,7 +311,7 @@ _목록 응답: `data.data[]` 배열 항목의 필드._ | id | integer | `1` | 기본 키 (내부 식별자) | | identifier | string | `admin` | 역할명 (예: admin, user, manager) | | name | string | `관리자` | 역할 이름 (다국어 JSON) | -| name_raw | object | `{"ko":"관리자","en":"Administrator"}` | `name` 의 원본 값 (현재 로케일 미해석 원본 JSON/문자열) | +| name_raw | object | `{"ko":"관리자","en":"Administrator","ja":"管理者"}` | `name` 의 원본 값 (현재 로케일 미해석 원본 JSON/문자열) | | description | string | `시스템의 모든 기능에 접근할 수 있는 최고 관리자입니다.` | 역할 설명 (다국어 JSON) | | description_raw | object | `{"ko":"시스템의 모든 기능에 접근할 수 있는 최고 관리자입니다.","en":"Super admin…` | `description` 의 원본 값 (현재 로케일 미해석 원본 JSON/문자열) | | extension_type | string | `core` | 확장 소유 타입: core(코어), module(모듈), plugin(플러그인), NULL(사용자 정의) | @@ -2801,8 +319,8 @@ _목록 응답: `data.data[]` 배열 항목의 필드._ | extension_name | string | `이커머스` | 이 리소스를 소유한 확장의 표시 이름 (manifest name) | | is_deletable | boolean | `false` | deletable 여부 | | is_active | boolean | `true` | active 여부 | -| created_at | string | `2026-05-27 15:20:18` | 생성 일시 | -| updated_at | string | `2026-06-30 13:41:48` | 최종 수정 일시 | +| created_at | string | `2026-07-30 17:36:00` | 생성 일시 | +| updated_at | string | `2026-08-01 11:46:14` | 최종 수정 일시 | | abilities | object | `{"can_create":true,"can_update":true,"can_delete":true,"c…` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | **응답 예시** @@ -2823,20 +341,22 @@ HTTP/1.1 200 "name": "관리자", "name_raw": { "ko": "관리자", - "en": "Administrator" + "en": "Administrator", + "ja": "管理者" }, "description": "시스템의 모든 기능에 접근할 수 있는 최고 관리자입니다.", "description_raw": { "ko": "시스템의 모든 기능에 접근할 수 있는 최고 관리자입니다.", - "en": "Super administrator with access to all system features." + "en": "Super administrator with access to all system features.", + "ja": "システムのすべての機能にアクセスできる最高管理者です。" }, "extension_type": "core", "extension_identifier": "core", "extension_name": null, "is_deletable": false, "is_active": true, - "created_at": "2026-07-07 16:38:39", - "updated_at": "2026-07-08 10:47:51", + "created_at": "2026-07-30 17:36:00", + "updated_at": "2026-08-01 11:46:14", "abilities": { "can_create": true, "can_update": true, @@ -2850,20 +370,22 @@ HTTP/1.1 200 "name": "일반 사용자", "name_raw": { "ko": "일반 사용자", - "en": "User" + "en": "User", + "ja": "一般ユーザー" }, "description": "기본 사용자 역할입니다.", "description_raw": { "ko": "기본 사용자 역할입니다.", - "en": "Default user role." + "en": "Default user role.", + "ja": "基本ユーザーロールです。" }, "extension_type": "core", "extension_identifier": "core", "extension_name": null, "is_deletable": false, "is_active": true, - "created_at": "2026-07-07 16:38:39", - "updated_at": "2026-07-08 10:47:51", + "created_at": "2026-07-30 17:36:00", + "updated_at": "2026-08-01 11:46:14", "abilities": { "can_create": true, "can_update": true, @@ -2871,7 +393,7 @@ HTTP/1.1 200 "can_toggle_status": true } }, - "... (총 6건 중 2건 표시)" + "... (총 100건 중 2건 표시)" ], "abilities": { "can_assign_roles": true @@ -2908,7 +430,7 @@ HTTP/1.1 200 **요청 예시** ```http -DELETE /api/admin/roles/4 HTTP/1.1 +DELETE /api/admin/roles/1 HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -2916,23 +438,11 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - - - + **응답 예시** -```http -HTTP/1.1 200 -``` - -```json -{ - "success": true, - "message": "역할이 성공적으로 삭제되었습니다.", - "data": null -} -``` + **에러 응답** @@ -2964,7 +474,7 @@ HTTP/1.1 200 **요청 예시** ```http -GET /api/admin/roles/4 HTTP/1.1 +GET /api/admin/roles/1 HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -2976,23 +486,23 @@ _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| id | integer | `66` | 기본 키 (내부 식별자) | -| identifier | string | `apidoc-sample-role` | 역할명 (예: admin, user, manager) | -| name | string | `API 문서 샘플 역할` | 역할 이름 (다국어 JSON) | -| name_raw | object | `{"ko":"API 문서 샘플 역할","en":"API Doc Sample Role"}` | `name` 의 원본 값 (현재 로케일 미해석 원본 JSON/문자열) | -| description | string | `문서 실측용 역할` | 역할 설명 (다국어 JSON) | -| description_raw | object | `{"ko":"문서 실측용 역할","en":"Sample role for API docs"}` | `description` 의 원본 값 (현재 로케일 미해석 원본 JSON/문자열) | -| extension_type | null | `null` | 확장 소유 타입: core(코어), module(모듈), plugin(플러그인), NULL(사용자 정의) | -| extension_identifier | null | `null` | 확장 식별자 (예: core, sirsoft-board, sirsoft-payment) | +| id | integer | `1` | 기본 키 (내부 식별자) | +| identifier | string | `admin` | 역할명 (예: admin, user, manager) | +| name | string | `관리자` | 역할 이름 (다국어 JSON) | +| name_raw | object | `{"ko":"관리자","en":"Administrator","ja":"管理者"}` | `name` 의 원본 값 (현재 로케일 미해석 원본 JSON/문자열) | +| description | string | `시스템의 모든 기능에 접근할 수 있는 최고 관리자입니다.` | 역할 설명 (다국어 JSON) | +| description_raw | object | `{"ko":"시스템의 모든 기능에 접근할 수 있는 최고 관리자입니다.","en":"Super admin…` | `description` 의 원본 값 (현재 로케일 미해석 원본 JSON/문자열) | +| extension_type | string | `core` | 확장 소유 타입: core(코어), module(모듈), plugin(플러그인), NULL(사용자 정의) | +| extension_identifier | string | `core` | 확장 식별자 (예: core, sirsoft-board, sirsoft-payment) | | extension_name | null | `null` | 이 리소스를 소유한 확장의 표시 이름 (manifest name) | -| is_deletable | boolean | `true` | deletable 여부 | +| is_deletable | boolean | `false` | deletable 여부 | | is_active | boolean | `true` | active 여부 | -| users_count | integer | `0` | users 개수 (집계) | -| permission_ids | array | `[1,85,100]` | permission 식별자 배열 (연관 리소스 참조) | -| permission_values | array | `[{"id":1,"scope_type":null},{"id":85,"scope_type":null},{…` | 할당된 각 권한의 id와 적용 범위만 담은 경량 목록 (원소 id/scope_type — 역할-권한 pivot 파생). scope_type: null=전체, role=역할 범위, self=본인 범위 | -| permissions | array | `[{"id":1,"parent_id":null,"identifier":"core","name":"코어"…` | 연결된 권한 목록 (id/identifier/name — 역할 경유 권한 관계 파생) | -| created_at | string | `2026-07-06 19:15:16` | 생성 일시 | -| updated_at | string | `2026-07-06 19:15:16` | 최종 수정 일시 | +| users_count | integer | `71` | users 개수 (집계) | +| permission_ids | array | `[2,3,4,5,6,1470,1471,1472,1473,1475,1476,1478,1479,1481,1…` | permission 식별자 배열 (연관 리소스 참조) | +| permission_values | array | `[{"id":2,"scope_type":null},{"id":3,"scope_type":null},{"…` | 할당된 각 권한의 id와 적용 범위만 담은 경량 목록 (원소 id/scope_type — 역할-권한 pivot 파생). scope_type: null=전체, role=역할 범위, self=본인 범위 | +| permissions | array | `[{"id":1468,"parent_id":null,"identifier":"sirsoft-board"…` | 연결된 권한 목록 (id/identifier/name — 역할 경유 권한 관계 파생) | +| created_at | string | `2026-07-30 17:36:00` | 생성 일시 | +| updated_at | string | `2026-08-01 11:46:14` | 최종 수정 일시 | | abilities | object | `{"can_create":true,"can_update":true,"can_delete":true,"c…` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | **응답 예시** @@ -3006,6243 +516,16 @@ HTTP/1.1 200 "success": true, "message": "역할 정보를 성공적으로 가져왔습니다.", "data": { - "id": 4, - "identifier": "apidoc-sample-role", - "name": "API 문서 샘플 역할", + "id": 1, + "identifier": "admin", + "name": "관리자", "name_raw": { - "ko": "API 문서 샘플 역할", - "en": "API Doc Sample Role" + "ko": "관리자", + "en": "Administrator", + "ja": "管理者" }, - "description": "문서 실측용 역할", - "description_raw": { - "ko": "문서 실측용 역할", - "en": "Sample role for API docs" - }, - "extension_type": null, - "extension_identifier": null, - "extension_name": null, - "is_deletable": true, - "is_active": true, - "users_count": 1, - "permission_ids": [ - 1, - 2, - 3, - 4, - 21, - 22, - 23, - 24, - 25, - 26, - 27, - 28, - 29, - 30, - 31, - 32, - 33, - 34, - 35, - 36, - 37, - 38, - 39, - 40, - 41, - 42, - 43, - 44, - 45, - 46, - 47, - 48, - 49, - 50, - 51, - 52, - 53, - 54, - 55, - 56, - 57, - 58, - 59, - 60, - 61, - 62, - 63, - 64, - 65, - 66, - 67, - 68, - 69, - 70, - 71, - 72, - 73, - 74, - 75, - 76, - 77, - 78, - 79, - 80, - 81, - 82, - 83, - 84, - 85, - 86, - 87, - 88, - 89, - 90, - 91, - 92, - 93, - 94, - 95, - 96, - 97, - 98, - 99, - 100, - 101, - 102, - 103, - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120, - 121, - 122, - 123, - 124, - 125, - 126, - 127, - 128, - 129, - 130, - 131, - 132, - 133, - 134, - 135, - 136, - 137, - 138, - 139, - 140, - 141, - 142, - 143, - 144, - 145, - 146, - 147, - 148, - 149, - 150, - 151, - 152, - 153, - 154, - 155, - 156, - 157, - 158, - 159, - 160, - 161, - 162, - 163, - 164, - 165, - 166, - 167, - 168, - 169, - 170, - 171, - 172, - 173, - 174, - 175, - 176, - 177, - 178, - 179, - 180, - 181, - 182, - 183, - 184, - 185, - 186, - 187, - 188, - 189, - 190, - 191, - 192, - 193, - 194, - 195, - 196, - 197, - 198 - ], - "permission_values": [ - { - "id": 1, - "scope_type": null - }, - { - "id": 2, - "scope_type": null - }, - { - "id": 3, - "scope_type": null - }, - { - "id": 4, - "scope_type": null - }, - { - "id": 21, - "scope_type": null - }, - { - "id": 22, - "scope_type": null - }, - { - "id": 23, - "scope_type": null - }, - { - "id": 24, - "scope_type": null - }, - { - "id": 25, - "scope_type": null - }, - { - "id": 26, - "scope_type": null - }, - { - "id": 27, - "scope_type": null - }, - { - "id": 28, - "scope_type": null - }, - { - "id": 29, - "scope_type": null - }, - { - "id": 30, - "scope_type": null - }, - { - "id": 31, - "scope_type": null - }, - { - "id": 32, - "scope_type": null - }, - { - "id": 33, - "scope_type": null - }, - { - "id": 34, - "scope_type": null - }, - { - "id": 35, - "scope_type": null - }, - { - "id": 36, - "scope_type": null - }, - { - "id": 37, - "scope_type": null - }, - { - "id": 38, - "scope_type": null - }, - { - "id": 39, - "scope_type": null - }, - { - "id": 40, - "scope_type": null - }, - { - "id": 41, - "scope_type": null - }, - { - "id": 42, - "scope_type": null - }, - { - "id": 43, - "scope_type": null - }, - { - "id": 44, - "scope_type": null - }, - { - "id": 45, - "scope_type": null - }, - { - "id": 46, - "scope_type": null - }, - { - "id": 47, - "scope_type": null - }, - { - "id": 48, - "scope_type": null - }, - { - "id": 49, - "scope_type": null - }, - { - "id": 50, - "scope_type": null - }, - { - "id": 51, - "scope_type": null - }, - { - "id": 52, - "scope_type": null - }, - { - "id": 53, - "scope_type": null - }, - { - "id": 54, - "scope_type": null - }, - { - "id": 55, - "scope_type": null - }, - { - "id": 56, - "scope_type": null - }, - { - "id": 57, - "scope_type": null - }, - { - "id": 58, - "scope_type": null - }, - { - "id": 59, - "scope_type": null - }, - { - "id": 60, - "scope_type": null - }, - { - "id": 61, - "scope_type": null - }, - { - "id": 62, - "scope_type": null - }, - { - "id": 63, - "scope_type": null - }, - { - "id": 64, - "scope_type": null - }, - { - "id": 65, - "scope_type": null - }, - { - "id": 66, - "scope_type": null - }, - { - "id": 67, - "scope_type": null - }, - { - "id": 68, - "scope_type": null - }, - { - "id": 69, - "scope_type": null - }, - { - "id": 70, - "scope_type": null - }, - { - "id": 71, - "scope_type": null - }, - { - "id": 72, - "scope_type": null - }, - { - "id": 73, - "scope_type": null - }, - { - "id": 74, - "scope_type": null - }, - { - "id": 75, - "scope_type": null - }, - { - "id": 76, - "scope_type": null - }, - { - "id": 77, - "scope_type": null - }, - { - "id": 78, - "scope_type": null - }, - { - "id": 79, - "scope_type": null - }, - { - "id": 80, - "scope_type": null - }, - { - "id": 81, - "scope_type": null - }, - { - "id": 82, - "scope_type": null - }, - { - "id": 83, - "scope_type": null - }, - { - "id": 84, - "scope_type": null - }, - { - "id": 85, - "scope_type": null - }, - { - "id": 86, - "scope_type": null - }, - { - "id": 87, - "scope_type": null - }, - { - "id": 88, - "scope_type": null - }, - { - "id": 89, - "scope_type": null - }, - { - "id": 90, - "scope_type": null - }, - { - "id": 91, - "scope_type": null - }, - { - "id": 92, - "scope_type": null - }, - { - "id": 93, - "scope_type": null - }, - { - "id": 94, - "scope_type": null - }, - { - "id": 95, - "scope_type": null - }, - { - "id": 96, - "scope_type": null - }, - { - "id": 97, - "scope_type": null - }, - { - "id": 98, - "scope_type": null - }, - { - "id": 99, - "scope_type": null - }, - { - "id": 100, - "scope_type": null - }, - { - "id": 101, - "scope_type": null - }, - { - "id": 102, - "scope_type": null - }, - { - "id": 103, - "scope_type": null - }, - { - "id": 104, - "scope_type": null - }, - { - "id": 105, - "scope_type": null - }, - { - "id": 106, - "scope_type": null - }, - { - "id": 107, - "scope_type": null - }, - { - "id": 108, - "scope_type": null - }, - { - "id": 109, - "scope_type": null - }, - { - "id": 110, - "scope_type": null - }, - { - "id": 111, - "scope_type": null - }, - { - "id": 112, - "scope_type": null - }, - { - "id": 113, - "scope_type": null - }, - { - "id": 114, - "scope_type": null - }, - { - "id": 115, - "scope_type": null - }, - { - "id": 116, - "scope_type": null - }, - { - "id": 117, - "scope_type": null - }, - { - "id": 118, - "scope_type": null - }, - { - "id": 119, - "scope_type": null - }, - { - "id": 120, - "scope_type": null - }, - { - "id": 121, - "scope_type": null - }, - { - "id": 122, - "scope_type": null - }, - { - "id": 123, - "scope_type": null - }, - { - "id": 124, - "scope_type": null - }, - { - "id": 125, - "scope_type": null - }, - { - "id": 126, - "scope_type": null - }, - { - "id": 127, - "scope_type": null - }, - { - "id": 128, - "scope_type": null - }, - { - "id": 129, - "scope_type": null - }, - { - "id": 130, - "scope_type": null - }, - { - "id": 131, - "scope_type": null - }, - { - "id": 132, - "scope_type": null - }, - { - "id": 133, - "scope_type": null - }, - { - "id": 134, - "scope_type": null - }, - { - "id": 135, - "scope_type": null - }, - { - "id": 136, - "scope_type": null - }, - { - "id": 137, - "scope_type": null - }, - { - "id": 138, - "scope_type": null - }, - { - "id": 139, - "scope_type": null - }, - { - "id": 140, - "scope_type": null - }, - { - "id": 141, - "scope_type": null - }, - { - "id": 142, - "scope_type": null - }, - { - "id": 143, - "scope_type": null - }, - { - "id": 144, - "scope_type": null - }, - { - "id": 145, - "scope_type": null - }, - { - "id": 146, - "scope_type": null - }, - { - "id": 147, - "scope_type": null - }, - { - "id": 148, - "scope_type": null - }, - { - "id": 149, - "scope_type": null - }, - { - "id": 150, - "scope_type": null - }, - { - "id": 151, - "scope_type": null - }, - { - "id": 152, - "scope_type": null - }, - { - "id": 153, - "scope_type": null - }, - { - "id": 154, - "scope_type": null - }, - { - "id": 155, - "scope_type": null - }, - { - "id": 156, - "scope_type": null - }, - { - "id": 157, - "scope_type": null - }, - { - "id": 158, - "scope_type": null - }, - { - "id": 159, - "scope_type": null - }, - { - "id": 160, - "scope_type": null - }, - { - "id": 161, - "scope_type": null - }, - { - "id": 162, - "scope_type": null - }, - { - "id": 163, - "scope_type": null - }, - { - "id": 164, - "scope_type": null - }, - { - "id": 165, - "scope_type": null - }, - { - "id": 166, - "scope_type": null - }, - { - "id": 167, - "scope_type": null - }, - { - "id": 168, - "scope_type": null - }, - { - "id": 169, - "scope_type": null - }, - { - "id": 170, - "scope_type": null - }, - { - "id": 171, - "scope_type": null - }, - { - "id": 172, - "scope_type": null - }, - { - "id": 173, - "scope_type": null - }, - { - "id": 174, - "scope_type": null - }, - { - "id": 175, - "scope_type": null - }, - { - "id": 176, - "scope_type": null - }, - { - "id": 177, - "scope_type": null - }, - { - "id": 178, - "scope_type": null - }, - { - "id": 179, - "scope_type": null - }, - { - "id": 180, - "scope_type": null - }, - { - "id": 181, - "scope_type": null - }, - { - "id": 182, - "scope_type": null - }, - { - "id": 183, - "scope_type": null - }, - { - "id": 184, - "scope_type": null - }, - { - "id": 185, - "scope_type": null - }, - { - "id": 186, - "scope_type": null - }, - { - "id": 187, - "scope_type": null - }, - { - "id": 188, - "scope_type": null - }, - { - "id": 189, - "scope_type": null - }, - { - "id": 190, - "scope_type": null - }, - { - "id": 191, - "scope_type": null - }, - { - "id": 192, - "scope_type": null - }, - { - "id": 193, - "scope_type": null - }, - { - "id": 194, - "scope_type": null - }, - { - "id": 195, - "scope_type": null - }, - { - "id": 196, - "scope_type": null - }, - { - "id": 197, - "scope_type": null - }, - { - "id": 198, - "scope_type": null - } - ], - "permissions": [ - { - "id": 3, - "parent_id": null, - "identifier": "sirsoft-board", - "name": "게시판", - "name_raw": { - "ko": "게시판", - "en": "Board" - }, - "description": "게시판 모듈 권한", - "description_raw": { - "ko": "게시판 모듈 권한", - "en": "Board module permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-board", - "resource_route_key": null, - "owner_key": null, - "order": 100, - "is_assigned": true, - "is_assignable": false, - "children": [ - { - "id": 95, - "parent_id": 3, - "identifier": "sirsoft-board.boards", - "name": "게시판 관리", - "name_raw": { - "ko": "게시판 관리", - "en": "Board Management" - }, - "description": "게시판 관리 권한", - "description_raw": { - "ko": "게시판 관리 권한", - "en": "Board management permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-board", - "resource_route_key": null, - "owner_key": null, - "order": 1, - "is_assigned": true, - "is_assignable": false, - "children": [ - { - "id": 96, - "parent_id": 95, - "identifier": "sirsoft-board.boards.read", - "name": "게시판 조회", - "name_raw": { - "ko": "게시판 조회", - "en": "View Boards" - }, - "description": "게시판 목록 및 상세 조회", - "description_raw": { - "ko": "게시판 목록 및 상세 조회", - "en": "View board list and details" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-board", - "resource_route_key": "board", - "owner_key": "created_by", - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 97, - "parent_id": 95, - "identifier": "sirsoft-board.boards.create", - "name": "게시판 생성", - "name_raw": { - "ko": "게시판 생성", - "en": "Create Board" - }, - "description": "새 게시판 생성", - "description_raw": { - "ko": "새 게시판 생성", - "en": "Create new board" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-board", - "resource_route_key": null, - "owner_key": null, - "order": 2, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 98, - "parent_id": 95, - "identifier": "sirsoft-board.boards.update", - "name": "게시판 수정", - "name_raw": { - "ko": "게시판 수정", - "en": "Update Board" - }, - "description": "게시판 설정 수정", - "description_raw": { - "ko": "게시판 설정 수정", - "en": "Update board settings" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-board", - "resource_route_key": "board", - "owner_key": "created_by", - "order": 3, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 99, - "parent_id": 95, - "identifier": "sirsoft-board.boards.delete", - "name": "게시판 삭제", - "name_raw": { - "ko": "게시판 삭제", - "en": "Delete Board" - }, - "description": "게시판 삭제", - "description_raw": { - "ko": "게시판 삭제", - "en": "Delete board" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-board", - "resource_route_key": "board", - "owner_key": "created_by", - "order": 4, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - } - ] - }, - { - "id": 100, - "parent_id": 3, - "identifier": "sirsoft-board.settings", - "name": "환경설정", - "name_raw": { - "ko": "환경설정", - "en": "Settings" - }, - "description": "게시판 환경설정 권한", - "description_raw": { - "ko": "게시판 환경설정 권한", - "en": "Board settings permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-board", - "resource_route_key": null, - "owner_key": null, - "order": 2, - "is_assigned": true, - "is_assignable": false, - "children": [ - { - "id": 101, - "parent_id": 100, - "identifier": "sirsoft-board.settings.read", - "name": "환경설정 조회", - "name_raw": { - "ko": "환경설정 조회", - "en": "View Settings" - }, - "description": "게시판 환경설정 조회", - "description_raw": { - "ko": "게시판 환경설정 조회", - "en": "View board settings" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-board", - "resource_route_key": null, - "owner_key": null, - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 102, - "parent_id": 100, - "identifier": "sirsoft-board.settings.update", - "name": "환경설정 수정", - "name_raw": { - "ko": "환경설정 수정", - "en": "Update Settings" - }, - "description": "게시판 환경설정 수정", - "description_raw": { - "ko": "게시판 환경설정 수정", - "en": "Update board settings" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-board", - "resource_route_key": null, - "owner_key": null, - "order": 2, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - } - ] - }, - { - "id": 103, - "parent_id": 3, - "identifier": "sirsoft-board.identity.policies", - "name": "게시판 본인인증 정책", - "name_raw": { - "ko": "게시판 본인인증 정책", - "en": "Board Identity Policies" - }, - "description": "게시판 컨텍스트의 본인인증 정책 관리 권한", - "description_raw": { - "ko": "게시판 컨텍스트의 본인인증 정책 관리 권한", - "en": "Manage identity verification policies in board context" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-board", - "resource_route_key": null, - "owner_key": null, - "order": 3, - "is_assigned": true, - "is_assignable": false, - "children": [ - { - "id": 104, - "parent_id": 103, - "identifier": "sirsoft-board.identity.policies.read", - "name": "본인인증 정책 조회", - "name_raw": { - "ko": "본인인증 정책 조회", - "en": "View Identity Policies" - }, - "description": "게시판 본인인증 정책 조회", - "description_raw": { - "ko": "게시판 본인인증 정책 조회", - "en": "View board identity policies" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-board", - "resource_route_key": null, - "owner_key": null, - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 105, - "parent_id": 103, - "identifier": "sirsoft-board.identity.policies.update", - "name": "본인인증 정책 수정", - "name_raw": { - "ko": "본인인증 정책 수정", - "en": "Update Identity Policies" - }, - "description": "게시판 본인인증 정책 수정/추가/삭제", - "description_raw": { - "ko": "게시판 본인인증 정책 수정/추가/삭제", - "en": "Update, add, delete board identity policies" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-board", - "resource_route_key": null, - "owner_key": null, - "order": 2, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - } - ] - }, - { - "id": 106, - "parent_id": 3, - "identifier": "sirsoft-board.reports", - "name": "게시판 신고 관리", - "name_raw": { - "ko": "게시판 신고 관리", - "en": "Report Management" - }, - "description": "게시판 신고 관리 권한", - "description_raw": { - "ko": "게시판 신고 관리 권한", - "en": "Board report management permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-board", - "resource_route_key": null, - "owner_key": null, - "order": 4, - "is_assigned": true, - "is_assignable": false, - "children": [ - { - "id": 107, - "parent_id": 106, - "identifier": "sirsoft-board.reports.view", - "name": "신고 조회", - "name_raw": { - "ko": "신고 조회", - "en": "View Reports" - }, - "description": "신고 목록 및 상세 조회", - "description_raw": { - "ko": "신고 목록 및 상세 조회", - "en": "View report list and details" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-board", - "resource_route_key": "report", - "owner_key": "reporter_id", - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 108, - "parent_id": 106, - "identifier": "sirsoft-board.reports.manage", - "name": "신고 처리", - "name_raw": { - "ko": "신고 처리", - "en": "Manage Reports" - }, - "description": "신고 상태 변경 및 처리", - "description_raw": { - "ko": "신고 상태 변경 및 처리", - "en": "Update report status and process" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-board", - "resource_route_key": "report", - "owner_key": "reporter_id", - "order": 2, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - } - ] - } - ] - }, - { - "id": 21, - "parent_id": null, - "identifier": "sirsoft-ecommerce", - "name": "이커머스", - "name_raw": { - "ko": "이커머스", - "en": "Ecommerce" - }, - "description": "이커머스 모듈 권한", - "description_raw": { - "ko": "이커머스 모듈 권한", - "en": "Ecommerce module permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 100, - "is_assigned": true, - "is_assignable": false, - "children": [ - { - "id": 22, - "parent_id": 21, - "identifier": "sirsoft-ecommerce.products", - "name": "상품 관리", - "name_raw": { - "ko": "상품 관리", - "en": "Product Management" - }, - "description": "상품 관리 권한", - "description_raw": { - "ko": "상품 관리 권한", - "en": "Product management permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 1, - "is_assigned": true, - "is_assignable": false, - "children": [ - { - "id": 23, - "parent_id": 22, - "identifier": "sirsoft-ecommerce.products.read", - "name": "상품 조회", - "name_raw": { - "ko": "상품 조회", - "en": "Read Products" - }, - "description": "상품 목록 및 상세 조회", - "description_raw": { - "ko": "상품 목록 및 상세 조회", - "en": "Read product list and details" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": "product", - "owner_key": "created_by", - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 24, - "parent_id": 22, - "identifier": "sirsoft-ecommerce.products.create", - "name": "상품 생성", - "name_raw": { - "ko": "상품 생성", - "en": "Create Product" - }, - "description": "새 상품 등록", - "description_raw": { - "ko": "새 상품 등록", - "en": "Create new product" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 2, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 25, - "parent_id": 22, - "identifier": "sirsoft-ecommerce.products.update", - "name": "상품 수정", - "name_raw": { - "ko": "상품 수정", - "en": "Update Product" - }, - "description": "상품 정보 수정", - "description_raw": { - "ko": "상품 정보 수정", - "en": "Update product information" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": "product", - "owner_key": "created_by", - "order": 3, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 26, - "parent_id": 22, - "identifier": "sirsoft-ecommerce.products.delete", - "name": "상품 삭제", - "name_raw": { - "ko": "상품 삭제", - "en": "Delete Product" - }, - "description": "상품 삭제", - "description_raw": { - "ko": "상품 삭제", - "en": "Delete product" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": "product", - "owner_key": "created_by", - "order": 4, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - } - ] - }, - { - "id": 27, - "parent_id": 21, - "identifier": "sirsoft-ecommerce.orders", - "name": "주문 관리", - "name_raw": { - "ko": "주문 관리", - "en": "Order Management" - }, - "description": "주문 관리 권한", - "description_raw": { - "ko": "주문 관리 권한", - "en": "Order management permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 2, - "is_assigned": true, - "is_assignable": false, - "children": [ - { - "id": 28, - "parent_id": 27, - "identifier": "sirsoft-ecommerce.orders.read", - "name": "주문 조회", - "name_raw": { - "ko": "주문 조회", - "en": "Read Orders" - }, - "description": "주문 목록 및 상세 조회", - "description_raw": { - "ko": "주문 목록 및 상세 조회", - "en": "Read order list and details" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": "order", - "owner_key": "user_id", - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 29, - "parent_id": 27, - "identifier": "sirsoft-ecommerce.orders.update", - "name": "주문 수정", - "name_raw": { - "ko": "주문 수정", - "en": "Update Order" - }, - "description": "주문 상태 변경", - "description_raw": { - "ko": "주문 상태 변경", - "en": "Update order status" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": "order", - "owner_key": "user_id", - "order": 2, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - } - ] - }, - { - "id": 30, - "parent_id": 21, - "identifier": "sirsoft-ecommerce.categories", - "name": "카테고리 관리", - "name_raw": { - "ko": "카테고리 관리", - "en": "Category Management" - }, - "description": "카테고리 관리 권한", - "description_raw": { - "ko": "카테고리 관리 권한", - "en": "Category management permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 3, - "is_assigned": true, - "is_assignable": false, - "children": [ - { - "id": 31, - "parent_id": 30, - "identifier": "sirsoft-ecommerce.categories.read", - "name": "카테고리 조회", - "name_raw": { - "ko": "카테고리 조회", - "en": "Read Categories" - }, - "description": "카테고리 목록 및 상세 조회", - "description_raw": { - "ko": "카테고리 목록 및 상세 조회", - "en": "Read category list and details" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 32, - "parent_id": 30, - "identifier": "sirsoft-ecommerce.categories.create", - "name": "카테고리 생성", - "name_raw": { - "ko": "카테고리 생성", - "en": "Create Category" - }, - "description": "카테고리 생성", - "description_raw": { - "ko": "카테고리 생성", - "en": "Create category" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 2, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 33, - "parent_id": 30, - "identifier": "sirsoft-ecommerce.categories.update", - "name": "카테고리 수정", - "name_raw": { - "ko": "카테고리 수정", - "en": "Update Category" - }, - "description": "카테고리 수정", - "description_raw": { - "ko": "카테고리 수정", - "en": "Update category" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 3, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 34, - "parent_id": 30, - "identifier": "sirsoft-ecommerce.categories.delete", - "name": "카테고리 삭제", - "name_raw": { - "ko": "카테고리 삭제", - "en": "Delete Category" - }, - "description": "카테고리 삭제", - "description_raw": { - "ko": "카테고리 삭제", - "en": "Delete category" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 4, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - } - ] - }, - { - "id": 35, - "parent_id": 21, - "identifier": "sirsoft-ecommerce.brands", - "name": "브랜드 관리", - "name_raw": { - "ko": "브랜드 관리", - "en": "Brand Management" - }, - "description": "브랜드 관리 권한", - "description_raw": { - "ko": "브랜드 관리 권한", - "en": "Brand management permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 4, - "is_assigned": true, - "is_assignable": false, - "children": [ - { - "id": 36, - "parent_id": 35, - "identifier": "sirsoft-ecommerce.brands.read", - "name": "브랜드 조회", - "name_raw": { - "ko": "브랜드 조회", - "en": "Read Brands" - }, - "description": "브랜드 목록 및 상세 조회", - "description_raw": { - "ko": "브랜드 목록 및 상세 조회", - "en": "Read brand list and details" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": "brand", - "owner_key": "created_by", - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 37, - "parent_id": 35, - "identifier": "sirsoft-ecommerce.brands.create", - "name": "브랜드 생성", - "name_raw": { - "ko": "브랜드 생성", - "en": "Create Brand" - }, - "description": "브랜드 생성", - "description_raw": { - "ko": "브랜드 생성", - "en": "Create brand" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 2, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 38, - "parent_id": 35, - "identifier": "sirsoft-ecommerce.brands.update", - "name": "브랜드 수정", - "name_raw": { - "ko": "브랜드 수정", - "en": "Update Brand" - }, - "description": "브랜드 수정", - "description_raw": { - "ko": "브랜드 수정", - "en": "Update brand" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": "brand", - "owner_key": "created_by", - "order": 3, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 39, - "parent_id": 35, - "identifier": "sirsoft-ecommerce.brands.delete", - "name": "브랜드 삭제", - "name_raw": { - "ko": "브랜드 삭제", - "en": "Delete Brand" - }, - "description": "브랜드 삭제", - "description_raw": { - "ko": "브랜드 삭제", - "en": "Delete brand" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": "brand", - "owner_key": "created_by", - "order": 4, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - } - ] - }, - { - "id": 40, - "parent_id": 21, - "identifier": "sirsoft-ecommerce.product-notice-templates", - "name": "상품정보제공고시 관리", - "name_raw": { - "ko": "상품정보제공고시 관리", - "en": "Product Notice Template Management" - }, - "description": "상품정보제공고시 템플릿 관리 권한", - "description_raw": { - "ko": "상품정보제공고시 템플릿 관리 권한", - "en": "Product notice template management permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 5, - "is_assigned": true, - "is_assignable": false, - "children": [ - { - "id": 41, - "parent_id": 40, - "identifier": "sirsoft-ecommerce.product-notice-templates.read", - "name": "조회", - "name_raw": { - "ko": "조회", - "en": "Read" - }, - "description": "상품정보제공고시 조회", - "description_raw": { - "ko": "상품정보제공고시 조회", - "en": "Read product notice templates" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 42, - "parent_id": 40, - "identifier": "sirsoft-ecommerce.product-notice-templates.create", - "name": "생성", - "name_raw": { - "ko": "생성", - "en": "Create" - }, - "description": "상품정보제공고시 생성", - "description_raw": { - "ko": "상품정보제공고시 생성", - "en": "Create product notice template" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 2, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 43, - "parent_id": 40, - "identifier": "sirsoft-ecommerce.product-notice-templates.update", - "name": "수정", - "name_raw": { - "ko": "수정", - "en": "Update" - }, - "description": "상품정보제공고시 수정", - "description_raw": { - "ko": "상품정보제공고시 수정", - "en": "Update product notice template" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 3, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 44, - "parent_id": 40, - "identifier": "sirsoft-ecommerce.product-notice-templates.delete", - "name": "삭제", - "name_raw": { - "ko": "삭제", - "en": "Delete" - }, - "description": "상품정보제공고시 삭제", - "description_raw": { - "ko": "상품정보제공고시 삭제", - "en": "Delete product notice template" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 4, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - } - ] - }, - { - "id": 45, - "parent_id": 21, - "identifier": "sirsoft-ecommerce.product-common-infos", - "name": "공통정보 관리", - "name_raw": { - "ko": "공통정보 관리", - "en": "Product Common Info Management" - }, - "description": "상품 공통정보 관리 권한", - "description_raw": { - "ko": "상품 공통정보 관리 권한", - "en": "Product common info management permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 6, - "is_assigned": true, - "is_assignable": false, - "children": [ - { - "id": 46, - "parent_id": 45, - "identifier": "sirsoft-ecommerce.product-common-infos.read", - "name": "조회", - "name_raw": { - "ko": "조회", - "en": "Read" - }, - "description": "공통정보 조회", - "description_raw": { - "ko": "공통정보 조회", - "en": "Read product common info" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 47, - "parent_id": 45, - "identifier": "sirsoft-ecommerce.product-common-infos.create", - "name": "생성", - "name_raw": { - "ko": "생성", - "en": "Create" - }, - "description": "공통정보 생성", - "description_raw": { - "ko": "공통정보 생성", - "en": "Create product common info" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 2, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 48, - "parent_id": 45, - "identifier": "sirsoft-ecommerce.product-common-infos.update", - "name": "수정", - "name_raw": { - "ko": "수정", - "en": "Update" - }, - "description": "공통정보 수정", - "description_raw": { - "ko": "공통정보 수정", - "en": "Update product common info" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 3, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 49, - "parent_id": 45, - "identifier": "sirsoft-ecommerce.product-common-infos.delete", - "name": "삭제", - "name_raw": { - "ko": "삭제", - "en": "Delete" - }, - "description": "공통정보 삭제", - "description_raw": { - "ko": "공통정보 삭제", - "en": "Delete product common info" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 4, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - } - ] - }, - { - "id": 50, - "parent_id": 21, - "identifier": "sirsoft-ecommerce.settings", - "name": "환경설정", - "name_raw": { - "ko": "환경설정", - "en": "Settings" - }, - "description": "이커머스 환경설정 권한", - "description_raw": { - "ko": "이커머스 환경설정 권한", - "en": "Ecommerce settings permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 7, - "is_assigned": true, - "is_assignable": false, - "children": [ - { - "id": 51, - "parent_id": 50, - "identifier": "sirsoft-ecommerce.settings.read", - "name": "환경설정 조회", - "name_raw": { - "ko": "환경설정 조회", - "en": "View Settings" - }, - "description": "이커머스 환경설정 조회", - "description_raw": { - "ko": "이커머스 환경설정 조회", - "en": "View ecommerce settings" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 52, - "parent_id": 50, - "identifier": "sirsoft-ecommerce.settings.update", - "name": "환경설정 수정", - "name_raw": { - "ko": "환경설정 수정", - "en": "Update Settings" - }, - "description": "이커머스 환경설정 수정", - "description_raw": { - "ko": "이커머스 환경설정 수정", - "en": "Update ecommerce settings" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 2, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - } - ] - }, - { - "id": 53, - "parent_id": 21, - "identifier": "sirsoft-ecommerce.promotion-coupon", - "name": "쿠폰 관리", - "name_raw": { - "ko": "쿠폰 관리", - "en": "Coupon Management" - }, - "description": "쿠폰 관리 권한", - "description_raw": { - "ko": "쿠폰 관리 권한", - "en": "Coupon management permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 8, - "is_assigned": true, - "is_assignable": false, - "children": [ - { - "id": 54, - "parent_id": 53, - "identifier": "sirsoft-ecommerce.promotion-coupon.read", - "name": "쿠폰 조회", - "name_raw": { - "ko": "쿠폰 조회", - "en": "Read Coupons" - }, - "description": "쿠폰 목록 및 상세 조회", - "description_raw": { - "ko": "쿠폰 목록 및 상세 조회", - "en": "Read coupon list and details" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": "coupon", - "owner_key": "created_by", - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 55, - "parent_id": 53, - "identifier": "sirsoft-ecommerce.promotion-coupon.create", - "name": "쿠폰 생성", - "name_raw": { - "ko": "쿠폰 생성", - "en": "Create Coupon" - }, - "description": "새 쿠폰 등록", - "description_raw": { - "ko": "새 쿠폰 등록", - "en": "Create new coupon" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 2, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 56, - "parent_id": 53, - "identifier": "sirsoft-ecommerce.promotion-coupon.update", - "name": "쿠폰 수정", - "name_raw": { - "ko": "쿠폰 수정", - "en": "Update Coupon" - }, - "description": "쿠폰 정보 수정", - "description_raw": { - "ko": "쿠폰 정보 수정", - "en": "Update coupon information" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": "coupon", - "owner_key": "created_by", - "order": 3, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 57, - "parent_id": 53, - "identifier": "sirsoft-ecommerce.promotion-coupon.delete", - "name": "쿠폰 삭제", - "name_raw": { - "ko": "쿠폰 삭제", - "en": "Delete Coupon" - }, - "description": "쿠폰 삭제", - "description_raw": { - "ko": "쿠폰 삭제", - "en": "Delete coupon" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": "coupon", - "owner_key": "created_by", - "order": 4, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - } - ] - }, - { - "id": 58, - "parent_id": 21, - "identifier": "sirsoft-ecommerce.shipping-policies", - "name": "배송정책 관리", - "name_raw": { - "ko": "배송정책 관리", - "en": "Shipping Policy Management" - }, - "description": "배송정책 관리 권한", - "description_raw": { - "ko": "배송정책 관리 권한", - "en": "Shipping policy management permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 9, - "is_assigned": true, - "is_assignable": false, - "children": [ - { - "id": 59, - "parent_id": 58, - "identifier": "sirsoft-ecommerce.shipping-policies.read", - "name": "배송정책 조회", - "name_raw": { - "ko": "배송정책 조회", - "en": "Read Shipping Policies" - }, - "description": "배송정책 목록 및 상세 조회", - "description_raw": { - "ko": "배송정책 목록 및 상세 조회", - "en": "Read shipping policy list and details" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": "shippingPolicy", - "owner_key": "created_by", - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 60, - "parent_id": 58, - "identifier": "sirsoft-ecommerce.shipping-policies.create", - "name": "배송정책 생성", - "name_raw": { - "ko": "배송정책 생성", - "en": "Create Shipping Policy" - }, - "description": "새 배송정책 등록", - "description_raw": { - "ko": "새 배송정책 등록", - "en": "Create new shipping policy" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 2, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 61, - "parent_id": 58, - "identifier": "sirsoft-ecommerce.shipping-policies.update", - "name": "배송정책 수정", - "name_raw": { - "ko": "배송정책 수정", - "en": "Update Shipping Policy" - }, - "description": "배송정책 정보 수정", - "description_raw": { - "ko": "배송정책 정보 수정", - "en": "Update shipping policy information" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": "shippingPolicy", - "owner_key": "created_by", - "order": 3, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 62, - "parent_id": 58, - "identifier": "sirsoft-ecommerce.shipping-policies.delete", - "name": "배송정책 삭제", - "name_raw": { - "ko": "배송정책 삭제", - "en": "Delete Shipping Policy" - }, - "description": "배송정책 삭제", - "description_raw": { - "ko": "배송정책 삭제", - "en": "Delete shipping policy" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": "shippingPolicy", - "owner_key": "created_by", - "order": 4, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - } - ] - }, - { - "id": 63, - "parent_id": 21, - "identifier": "sirsoft-ecommerce.product-labels", - "name": "상품 라벨 관리", - "name_raw": { - "ko": "상품 라벨 관리", - "en": "Product Label Management" - }, - "description": "상품 라벨 관리 권한", - "description_raw": { - "ko": "상품 라벨 관리 권한", - "en": "Product label management permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 10, - "is_assigned": true, - "is_assignable": false, - "children": [ - { - "id": 64, - "parent_id": 63, - "identifier": "sirsoft-ecommerce.product-labels.read", - "name": "상품 라벨 조회", - "name_raw": { - "ko": "상품 라벨 조회", - "en": "Read Product Labels" - }, - "description": "상품 라벨 목록 및 상세 조회", - "description_raw": { - "ko": "상품 라벨 목록 및 상세 조회", - "en": "Read product label list and details" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 65, - "parent_id": 63, - "identifier": "sirsoft-ecommerce.product-labels.create", - "name": "상품 라벨 생성", - "name_raw": { - "ko": "상품 라벨 생성", - "en": "Create Product Label" - }, - "description": "새 상품 라벨 등록", - "description_raw": { - "ko": "새 상품 라벨 등록", - "en": "Create new product label" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 2, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 66, - "parent_id": 63, - "identifier": "sirsoft-ecommerce.product-labels.update", - "name": "상품 라벨 수정", - "name_raw": { - "ko": "상품 라벨 수정", - "en": "Update Product Label" - }, - "description": "상품 라벨 정보 수정", - "description_raw": { - "ko": "상품 라벨 정보 수정", - "en": "Update product label information" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 3, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 67, - "parent_id": 63, - "identifier": "sirsoft-ecommerce.product-labels.delete", - "name": "상품 라벨 삭제", - "name_raw": { - "ko": "상품 라벨 삭제", - "en": "Delete Product Label" - }, - "description": "상품 라벨 삭제", - "description_raw": { - "ko": "상품 라벨 삭제", - "en": "Delete product label" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 4, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - } - ] - }, - { - "id": 68, - "parent_id": 21, - "identifier": "sirsoft-ecommerce.identity.policies", - "name": "이커머스 본인인증 정책", - "name_raw": { - "ko": "이커머스 본인인증 정책", - "en": "Ecommerce Identity Policies" - }, - "description": "이커머스 컨텍스트의 본인인증 정책 관리 권한", - "description_raw": { - "ko": "이커머스 컨텍스트의 본인인증 정책 관리 권한", - "en": "Manage identity verification policies in ecommerce context" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 11, - "is_assigned": true, - "is_assignable": false, - "children": [ - { - "id": 69, - "parent_id": 68, - "identifier": "sirsoft-ecommerce.identity.policies.read", - "name": "본인인증 정책 조회", - "name_raw": { - "ko": "본인인증 정책 조회", - "en": "View Identity Policies" - }, - "description": "이커머스 본인인증 정책 조회", - "description_raw": { - "ko": "이커머스 본인인증 정책 조회", - "en": "View ecommerce identity policies" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 70, - "parent_id": 68, - "identifier": "sirsoft-ecommerce.identity.policies.update", - "name": "본인인증 정책 수정", - "name_raw": { - "ko": "본인인증 정책 수정", - "en": "Update Identity Policies" - }, - "description": "이커머스 본인인증 정책 수정/추가/삭제", - "description_raw": { - "ko": "이커머스 본인인증 정책 수정/추가/삭제", - "en": "Update, add, delete ecommerce identity policies" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 2, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - } - ] - }, - { - "id": 71, - "parent_id": 21, - "identifier": "sirsoft-ecommerce.reviews", - "name": "리뷰 관리", - "name_raw": { - "ko": "리뷰 관리", - "en": "Review Management" - }, - "description": "상품 리뷰 관리 권한", - "description_raw": { - "ko": "상품 리뷰 관리 권한", - "en": "Product review management permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 12, - "is_assigned": true, - "is_assignable": false, - "children": [ - { - "id": 72, - "parent_id": 71, - "identifier": "sirsoft-ecommerce.reviews.read", - "name": "리뷰 조회", - "name_raw": { - "ko": "리뷰 조회", - "en": "Read Reviews" - }, - "description": "리뷰 목록 및 상세 조회", - "description_raw": { - "ko": "리뷰 목록 및 상세 조회", - "en": "Read review list and details" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": "review", - "owner_key": "user_id", - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 73, - "parent_id": 71, - "identifier": "sirsoft-ecommerce.reviews.update", - "name": "리뷰 처리", - "name_raw": { - "ko": "리뷰 처리", - "en": "Manage Review" - }, - "description": "리뷰 상태 변경, 답변 등록/수정/삭제, 일괄 처리", - "description_raw": { - "ko": "리뷰 상태 변경, 답변 등록/수정/삭제, 일괄 처리", - "en": "Update review status, manage replies, bulk actions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": "review", - "owner_key": "user_id", - "order": 2, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 74, - "parent_id": 71, - "identifier": "sirsoft-ecommerce.reviews.delete", - "name": "리뷰 삭제", - "name_raw": { - "ko": "리뷰 삭제", - "en": "Delete Review" - }, - "description": "리뷰 삭제", - "description_raw": { - "ko": "리뷰 삭제", - "en": "Delete review" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": "review", - "owner_key": "user_id", - "order": 3, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - } - ] - }, - { - "id": 75, - "parent_id": 21, - "identifier": "sirsoft-ecommerce.inquiries", - "name": "문의 관리", - "name_raw": { - "ko": "문의 관리", - "en": "Inquiry Management" - }, - "description": "상품 1:1 문의 관리 권한", - "description_raw": { - "ko": "상품 1:1 문의 관리 권한", - "en": "Product inquiry management permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 13, - "is_assigned": true, - "is_assignable": false, - "children": [ - { - "id": 76, - "parent_id": 75, - "identifier": "sirsoft-ecommerce.inquiries.update", - "name": "문의 처리", - "name_raw": { - "ko": "문의 처리", - "en": "Manage Inquiry" - }, - "description": "답변 등록/수정/삭제, 비밀글 내용 열람", - "description_raw": { - "ko": "답변 등록/수정/삭제, 비밀글 내용 열람", - "en": "Create, update, and delete inquiry replies; view secret inquiry contents" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 77, - "parent_id": 75, - "identifier": "sirsoft-ecommerce.inquiries.delete", - "name": "문의 삭제", - "name_raw": { - "ko": "문의 삭제", - "en": "Delete Inquiry" - }, - "description": "고객이 작성한 문의 자체를 삭제", - "description_raw": { - "ko": "고객이 작성한 문의 자체를 삭제", - "en": "Delete inquiries submitted by customers" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 2, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - } - ] - }, - { - "id": 78, - "parent_id": 21, - "identifier": "sirsoft-ecommerce.dashboard", - "name": "대시보드", - "name_raw": { - "ko": "대시보드", - "en": "Dashboard" - }, - "description": "관리자 대시보드 이커머스 영역 조회 권한", - "description_raw": { - "ko": "관리자 대시보드 이커머스 영역 조회 권한", - "en": "Admin dashboard commerce area view permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 14, - "is_assigned": true, - "is_assignable": false, - "children": [ - { - "id": 79, - "parent_id": 78, - "identifier": "sirsoft-ecommerce.dashboard.view", - "name": "대시보드 조회", - "name_raw": { - "ko": "대시보드 조회", - "en": "View Dashboard" - }, - "description": "관리자 대시보드의 이커머스 판매 현황/리뷰/문의 위젯 조회", - "description_raw": { - "ko": "관리자 대시보드의 이커머스 판매 현황/리뷰/문의 위젯 조회", - "en": "View commerce sales/review/inquiry widgets on the admin dashboard" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - } - ] - }, - { - "id": 80, - "parent_id": 21, - "identifier": "sirsoft-ecommerce.user-products", - "name": "사용자 상품", - "name_raw": { - "ko": "사용자 상품", - "en": "User Products" - }, - "description": "사용자 상품 접근 권한 (블랙컨슈머 차단용)", - "description_raw": { - "ko": "사용자 상품 접근 권한 (블랙컨슈머 차단용)", - "en": "User product access permissions (for blocking malicious consumers)" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 15, - "is_assigned": true, - "is_assignable": false, - "children": [ - { - "id": 81, - "parent_id": 80, - "identifier": "sirsoft-ecommerce.user-products.read", - "name": "상품 조회", - "name_raw": { - "ko": "상품 조회", - "en": "View Products" - }, - "description": "상품 목록 및 상세 조회", - "description_raw": { - "ko": "상품 목록 및 상세 조회", - "en": "View product list and details" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - } - ] - }, - { - "id": 82, - "parent_id": 21, - "identifier": "sirsoft-ecommerce.user-orders", - "name": "사용자 주문", - "name_raw": { - "ko": "사용자 주문", - "en": "User Orders" - }, - "description": "사용자 주문 관련 권한 (블랙컨슈머 차단용)", - "description_raw": { - "ko": "사용자 주문 관련 권한 (블랙컨슈머 차단용)", - "en": "User order permissions (for blocking malicious consumers)" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 16, - "is_assigned": true, - "is_assignable": false, - "children": [ - { - "id": 83, - "parent_id": 82, - "identifier": "sirsoft-ecommerce.user-orders.create", - "name": "주문하기", - "name_raw": { - "ko": "주문하기", - "en": "Create Order" - }, - "description": "주문 생성", - "description_raw": { - "ko": "주문 생성", - "en": "Create a new order" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 84, - "parent_id": 82, - "identifier": "sirsoft-ecommerce.user-orders.cancel", - "name": "주문 취소", - "name_raw": { - "ko": "주문 취소", - "en": "Cancel Order" - }, - "description": "주문 취소 요청", - "description_raw": { - "ko": "주문 취소 요청", - "en": "Request order cancellation" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 2, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 85, - "parent_id": 82, - "identifier": "sirsoft-ecommerce.user-orders.confirm", - "name": "구매확정", - "name_raw": { - "ko": "구매확정", - "en": "Confirm Purchase" - }, - "description": "주문 상품 구매확정", - "description_raw": { - "ko": "주문 상품 구매확정", - "en": "Confirm purchase of order item" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 3, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - } - ] - }, - { - "id": 86, - "parent_id": 21, - "identifier": "sirsoft-ecommerce.user-reviews", - "name": "사용자 리뷰", - "name_raw": { - "ko": "사용자 리뷰", - "en": "User Reviews" - }, - "description": "사용자 리뷰 관련 권한 (블랙컨슈머 차단용)", - "description_raw": { - "ko": "사용자 리뷰 관련 권한 (블랙컨슈머 차단용)", - "en": "User review permissions (for blocking malicious consumers)" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 17, - "is_assigned": true, - "is_assignable": false, - "children": [ - { - "id": 87, - "parent_id": 86, - "identifier": "sirsoft-ecommerce.user-reviews.write", - "name": "리뷰 작성", - "name_raw": { - "ko": "리뷰 작성", - "en": "Write Review" - }, - "description": "상품 리뷰 작성", - "description_raw": { - "ko": "상품 리뷰 작성", - "en": "Write product review" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - } - ] - }, - { - "id": 88, - "parent_id": 21, - "identifier": "sirsoft-ecommerce.mileage", - "name": "마일리지 관리", - "name_raw": { - "ko": "마일리지 관리", - "en": "Mileage Management" - }, - "description": "마일리지 내역 조회 및 수동 지급/차감 권한", - "description_raw": { - "ko": "마일리지 내역 조회 및 수동 지급/차감 권한", - "en": "Mileage history and manual grant/deduct permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 18, - "is_assigned": true, - "is_assignable": false, - "children": [ - { - "id": 89, - "parent_id": 88, - "identifier": "sirsoft-ecommerce.mileage.read", - "name": "마일리지 내역 조회", - "name_raw": { - "ko": "마일리지 내역 조회", - "en": "Read Mileage" - }, - "description": "마일리지 내역 목록 및 상세 조회", - "description_raw": { - "ko": "마일리지 내역 목록 및 상세 조회", - "en": "Read mileage history list and details" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": "mileage-transaction", - "owner_key": "user_id", - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 90, - "parent_id": 88, - "identifier": "sirsoft-ecommerce.mileage.manage", - "name": "마일리지 수동 처리", - "name_raw": { - "ko": "마일리지 수동 처리", - "en": "Manage Mileage" - }, - "description": "마일리지 수동 지급/차감 및 일괄 유효기간 연장", - "description_raw": { - "ko": "마일리지 수동 지급/차감 및 일괄 유효기간 연장", - "en": "Manual grant/deduct and bulk expiry extension" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": "mileage-transaction", - "owner_key": "user_id", - "order": 2, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - } - ] - }, - { - "id": 91, - "parent_id": 21, - "identifier": "sirsoft-ecommerce.user-currency", - "name": "회원 결제 통화 관리", - "name_raw": { - "ko": "회원 결제 통화 관리", - "en": "User Currency Management" - }, - "description": "관리자가 회원별 결제 통화를 변경하는 권한", - "description_raw": { - "ko": "관리자가 회원별 결제 통화를 변경하는 권한", - "en": "Permission for admins to change a user's payment currency" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 19, - "is_assigned": true, - "is_assignable": false, - "children": [ - { - "id": 92, - "parent_id": 91, - "identifier": "sirsoft-ecommerce.user-currency.manage", - "name": "회원 결제 통화 변경", - "name_raw": { - "ko": "회원 결제 통화 변경", - "en": "Manage User Currency" - }, - "description": "회원별 결제 통화 변경", - "description_raw": { - "ko": "회원별 결제 통화 변경", - "en": "Change a user's payment currency" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - } - ] - }, - { - "id": 93, - "parent_id": 21, - "identifier": "sirsoft-ecommerce.user-shipping-country", - "name": "회원 배송국가 관리", - "name_raw": { - "ko": "회원 배송국가 관리", - "en": "User Shipping Country Management" - }, - "description": "관리자가 회원별 배송국가를 변경하는 권한", - "description_raw": { - "ko": "관리자가 회원별 배송국가를 변경하는 권한", - "en": "Permission for admins to change a user's shipping country" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 20, - "is_assigned": true, - "is_assignable": false, - "children": [ - { - "id": 94, - "parent_id": 93, - "identifier": "sirsoft-ecommerce.user-shipping-country.manage", - "name": "회원 배송국가 변경", - "name_raw": { - "ko": "회원 배송국가 변경", - "en": "Manage User Shipping Country" - }, - "description": "회원별 배송국가 변경", - "description_raw": { - "ko": "회원별 배송국가 변경", - "en": "Change a user's shipping country" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - } - ] - } - ] - }, - { - "id": 109, - "parent_id": null, - "identifier": "sirsoft-page", - "name": "페이지", - "name_raw": { - "ko": "페이지", - "en": "Page" - }, - "description": "페이지 모듈 권한", - "description_raw": { - "ko": "페이지 모듈 권한", - "en": "Page module permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-page", - "resource_route_key": null, - "owner_key": null, - "order": 100, - "is_assigned": true, - "is_assignable": false, - "children": [ - { - "id": 110, - "parent_id": 109, - "identifier": "sirsoft-page.pages", - "name": "페이지 관리", - "name_raw": { - "ko": "페이지 관리", - "en": "Page Management" - }, - "description": "페이지 관리 권한", - "description_raw": { - "ko": "페이지 관리 권한", - "en": "Page management permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-page", - "resource_route_key": null, - "owner_key": null, - "order": 1, - "is_assigned": true, - "is_assignable": false, - "children": [ - { - "id": 111, - "parent_id": 110, - "identifier": "sirsoft-page.pages.read", - "name": "페이지 조회", - "name_raw": { - "ko": "페이지 조회", - "en": "View Pages" - }, - "description": "페이지 목록 및 상세 조회", - "description_raw": { - "ko": "페이지 목록 및 상세 조회", - "en": "View page list and details" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-page", - "resource_route_key": "page", - "owner_key": "created_by", - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 112, - "parent_id": 110, - "identifier": "sirsoft-page.pages.create", - "name": "페이지 생성", - "name_raw": { - "ko": "페이지 생성", - "en": "Create Page" - }, - "description": "새 페이지 생성", - "description_raw": { - "ko": "새 페이지 생성", - "en": "Create new page" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-page", - "resource_route_key": null, - "owner_key": null, - "order": 2, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 113, - "parent_id": 110, - "identifier": "sirsoft-page.pages.update", - "name": "페이지 수정", - "name_raw": { - "ko": "페이지 수정", - "en": "Update Page" - }, - "description": "페이지 내용 수정", - "description_raw": { - "ko": "페이지 내용 수정", - "en": "Update page content" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-page", - "resource_route_key": "page", - "owner_key": "created_by", - "order": 3, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 114, - "parent_id": 110, - "identifier": "sirsoft-page.pages.delete", - "name": "페이지 삭제", - "name_raw": { - "ko": "페이지 삭제", - "en": "Delete Page" - }, - "description": "페이지 삭제", - "description_raw": { - "ko": "페이지 삭제", - "en": "Delete page" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-page", - "resource_route_key": "page", - "owner_key": "created_by", - "order": 4, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - } - ] - } - ] - }, - { - "id": 115, - "parent_id": null, - "identifier": "core", - "name": "코어", - "name_raw": { - "ko": "코어", - "en": "Core" - }, - "description": "코어 시스템 권한", - "description_raw": { - "ko": "코어 시스템 권한", - "en": "Core system permissions" - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 1, - "is_assigned": true, - "is_assignable": false, - "children": [ - { - "id": 116, - "parent_id": 115, - "identifier": "core.users", - "name": "사용자 관리", - "name_raw": { - "ko": "사용자 관리", - "en": "User Management" - }, - "description": "사용자 관리 권한", - "description_raw": { - "ko": "사용자 관리 권한", - "en": "User management permissions" - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 1, - "is_assigned": true, - "is_assignable": false, - "children": [ - { - "id": 117, - "parent_id": 116, - "identifier": "core.users.read", - "name": "사용자 조회", - "name_raw": { - "ko": "사용자 조회", - "en": "View Users" - }, - "description": "사용자 목록 및 상세 정보를 조회할 수 있습니다.", - "description_raw": { - "ko": "사용자 목록 및 상세 정보를 조회할 수 있습니다.", - "en": "Can view user list and details." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": "user", - "owner_key": "id", - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 118, - "parent_id": 116, - "identifier": "core.users.create", - "name": "사용자 생성", - "name_raw": { - "ko": "사용자 생성", - "en": "Create Users" - }, - "description": "새로운 사용자를 생성할 수 있습니다.", - "description_raw": { - "ko": "새로운 사용자를 생성할 수 있습니다.", - "en": "Can create new users." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 2, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 119, - "parent_id": 116, - "identifier": "core.users.update", - "name": "사용자 수정", - "name_raw": { - "ko": "사용자 수정", - "en": "Update Users" - }, - "description": "사용자 정보를 수정할 수 있습니다.", - "description_raw": { - "ko": "사용자 정보를 수정할 수 있습니다.", - "en": "Can update user information." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": "user", - "owner_key": "id", - "order": 3, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 120, - "parent_id": 116, - "identifier": "core.users.delete", - "name": "사용자 삭제", - "name_raw": { - "ko": "사용자 삭제", - "en": "Delete Users" - }, - "description": "사용자를 삭제할 수 있습니다.", - "description_raw": { - "ko": "사용자를 삭제할 수 있습니다.", - "en": "Can delete users." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": "user", - "owner_key": "id", - "order": 4, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - } - ] - }, - { - "id": 121, - "parent_id": 115, - "identifier": "core.menus", - "name": "메뉴 관리", - "name_raw": { - "ko": "메뉴 관리", - "en": "Menu Management" - }, - "description": "메뉴 관리 권한", - "description_raw": { - "ko": "메뉴 관리 권한", - "en": "Menu management permissions" - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 2, - "is_assigned": true, - "is_assignable": false, - "children": [ - { - "id": 122, - "parent_id": 121, - "identifier": "core.menus.read", - "name": "메뉴 조회", - "name_raw": { - "ko": "메뉴 조회", - "en": "View Menus" - }, - "description": "메뉴 목록을 조회할 수 있습니다.", - "description_raw": { - "ko": "메뉴 목록을 조회할 수 있습니다.", - "en": "Can view menu list." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": "menu", - "owner_key": "created_by", - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 123, - "parent_id": 121, - "identifier": "core.menus.create", - "name": "메뉴 생성", - "name_raw": { - "ko": "메뉴 생성", - "en": "Create Menus" - }, - "description": "새로운 메뉴를 생성할 수 있습니다.", - "description_raw": { - "ko": "새로운 메뉴를 생성할 수 있습니다.", - "en": "Can create new menus." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 2, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 124, - "parent_id": 121, - "identifier": "core.menus.update", - "name": "메뉴 수정", - "name_raw": { - "ko": "메뉴 수정", - "en": "Update Menus" - }, - "description": "메뉴 정보를 수정할 수 있습니다.", - "description_raw": { - "ko": "메뉴 정보를 수정할 수 있습니다.", - "en": "Can update menu information." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": "menu", - "owner_key": "created_by", - "order": 3, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 125, - "parent_id": 121, - "identifier": "core.menus.delete", - "name": "메뉴 삭제", - "name_raw": { - "ko": "메뉴 삭제", - "en": "Delete Menus" - }, - "description": "메뉴를 삭제할 수 있습니다.", - "description_raw": { - "ko": "메뉴를 삭제할 수 있습니다.", - "en": "Can delete menus." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": "menu", - "owner_key": "created_by", - "order": 4, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - } - ] - }, - { - "id": 126, - "parent_id": 115, - "identifier": "core.modules", - "name": "모듈 관리", - "name_raw": { - "ko": "모듈 관리", - "en": "Module Management" - }, - "description": "모듈 관리 권한", - "description_raw": { - "ko": "모듈 관리 권한", - "en": "Module management permissions" - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 3, - "is_assigned": true, - "is_assignable": false, - "children": [ - { - "id": 127, - "parent_id": 126, - "identifier": "core.modules.read", - "name": "모듈 조회", - "name_raw": { - "ko": "모듈 조회", - "en": "View Modules" - }, - "description": "모듈 목록을 조회할 수 있습니다.", - "description_raw": { - "ko": "모듈 목록을 조회할 수 있습니다.", - "en": "Can view module list." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 128, - "parent_id": 126, - "identifier": "core.modules.install", - "name": "모듈 설치", - "name_raw": { - "ko": "모듈 설치", - "en": "Install Modules" - }, - "description": "새로운 모듈을 설치할 수 있습니다.", - "description_raw": { - "ko": "새로운 모듈을 설치할 수 있습니다.", - "en": "Can install new modules." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 2, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 129, - "parent_id": 126, - "identifier": "core.modules.activate", - "name": "모듈 활성화", - "name_raw": { - "ko": "모듈 활성화", - "en": "Activate Modules" - }, - "description": "모듈을 활성화/비활성화할 수 있습니다.", - "description_raw": { - "ko": "모듈을 활성화/비활성화할 수 있습니다.", - "en": "Can activate/deactivate modules." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 3, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 130, - "parent_id": 126, - "identifier": "core.modules.uninstall", - "name": "모듈 삭제", - "name_raw": { - "ko": "모듈 삭제", - "en": "Uninstall Modules" - }, - "description": "모듈을 삭제할 수 있습니다.", - "description_raw": { - "ko": "모듈을 삭제할 수 있습니다.", - "en": "Can uninstall modules." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 4, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - } - ] - }, - { - "id": 131, - "parent_id": 115, - "identifier": "core.plugins", - "name": "플러그인 관리", - "name_raw": { - "ko": "플러그인 관리", - "en": "Plugin Management" - }, - "description": "플러그인 관리 권한", - "description_raw": { - "ko": "플러그인 관리 권한", - "en": "Plugin management permissions" - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 4, - "is_assigned": true, - "is_assignable": false, - "children": [ - { - "id": 132, - "parent_id": 131, - "identifier": "core.plugins.read", - "name": "플러그인 조회", - "name_raw": { - "ko": "플러그인 조회", - "en": "View Plugins" - }, - "description": "플러그인 목록을 조회할 수 있습니다.", - "description_raw": { - "ko": "플러그인 목록을 조회할 수 있습니다.", - "en": "Can view plugin list." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 133, - "parent_id": 131, - "identifier": "core.plugins.install", - "name": "플러그인 설치", - "name_raw": { - "ko": "플러그인 설치", - "en": "Install Plugins" - }, - "description": "새로운 플러그인을 설치할 수 있습니다.", - "description_raw": { - "ko": "새로운 플러그인을 설치할 수 있습니다.", - "en": "Can install new plugins." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 2, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 134, - "parent_id": 131, - "identifier": "core.plugins.activate", - "name": "플러그인 활성화", - "name_raw": { - "ko": "플러그인 활성화", - "en": "Activate Plugins" - }, - "description": "플러그인을 활성화/비활성화할 수 있습니다.", - "description_raw": { - "ko": "플러그인을 활성화/비활성화할 수 있습니다.", - "en": "Can activate/deactivate plugins." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 3, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 135, - "parent_id": 131, - "identifier": "core.plugins.update", - "name": "플러그인 설정", - "name_raw": { - "ko": "플러그인 설정", - "en": "Configure Plugins" - }, - "description": "플러그인 환경설정을 수정할 수 있습니다.", - "description_raw": { - "ko": "플러그인 환경설정을 수정할 수 있습니다.", - "en": "Can update plugin settings." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 4, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 136, - "parent_id": 131, - "identifier": "core.plugins.uninstall", - "name": "플러그인 삭제", - "name_raw": { - "ko": "플러그인 삭제", - "en": "Uninstall Plugins" - }, - "description": "플러그인을 삭제할 수 있습니다.", - "description_raw": { - "ko": "플러그인을 삭제할 수 있습니다.", - "en": "Can uninstall plugins." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 5, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - } - ] - }, - { - "id": 137, - "parent_id": 115, - "identifier": "core.templates", - "name": "템플릿 관리", - "name_raw": { - "ko": "템플릿 관리", - "en": "Template Management" - }, - "description": "템플릿 관리 권한", - "description_raw": { - "ko": "템플릿 관리 권한", - "en": "Template management permissions" - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 5, - "is_assigned": true, - "is_assignable": false, - "children": [ - { - "id": 138, - "parent_id": 137, - "identifier": "core.templates.read", - "name": "템플릿 조회", - "name_raw": { - "ko": "템플릿 조회", - "en": "View Templates" - }, - "description": "템플릿 목록을 조회할 수 있습니다.", - "description_raw": { - "ko": "템플릿 목록을 조회할 수 있습니다.", - "en": "Can view template list." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 139, - "parent_id": 137, - "identifier": "core.templates.install", - "name": "템플릿 설치", - "name_raw": { - "ko": "템플릿 설치", - "en": "Install Templates" - }, - "description": "새로운 템플릿을 설치할 수 있습니다.", - "description_raw": { - "ko": "새로운 템플릿을 설치할 수 있습니다.", - "en": "Can install new templates." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 2, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 140, - "parent_id": 137, - "identifier": "core.templates.activate", - "name": "템플릿 활성화", - "name_raw": { - "ko": "템플릿 활성화", - "en": "Activate Templates" - }, - "description": "템플릿을 활성화/비활성화할 수 있습니다.", - "description_raw": { - "ko": "템플릿을 활성화/비활성화할 수 있습니다.", - "en": "Can activate/deactivate templates." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 3, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 141, - "parent_id": 137, - "identifier": "core.templates.uninstall", - "name": "템플릿 삭제", - "name_raw": { - "ko": "템플릿 삭제", - "en": "Uninstall Templates" - }, - "description": "템플릿을 삭제할 수 있습니다.", - "description_raw": { - "ko": "템플릿을 삭제할 수 있습니다.", - "en": "Can uninstall templates." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 4, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 142, - "parent_id": 137, - "identifier": "core.templates.layouts.edit", - "name": "레이아웃 편집", - "name_raw": { - "ko": "레이아웃 편집", - "en": "Edit Layouts" - }, - "description": "템플릿 레이아웃을 편집할 수 있습니다.", - "description_raw": { - "ko": "템플릿 레이아웃을 편집할 수 있습니다.", - "en": "Can edit template layouts." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 5, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - } - ] - }, - { - "id": 143, - "parent_id": 115, - "identifier": "core.permissions", - "name": "권한 관리", - "name_raw": { - "ko": "권한 관리", - "en": "Permission Management" - }, - "description": "역할 및 권한 관리 권한", - "description_raw": { - "ko": "역할 및 권한 관리 권한", - "en": "Role and permission management permissions" - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 6, - "is_assigned": true, - "is_assignable": false, - "children": [ - { - "id": 144, - "parent_id": 143, - "identifier": "core.permissions.read", - "name": "권한 조회", - "name_raw": { - "ko": "권한 조회", - "en": "View Permissions" - }, - "description": "역할 및 권한 목록을 조회할 수 있습니다.", - "description_raw": { - "ko": "역할 및 권한 목록을 조회할 수 있습니다.", - "en": "Can view roles and permissions." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 145, - "parent_id": 143, - "identifier": "core.permissions.create", - "name": "역할 생성", - "name_raw": { - "ko": "역할 생성", - "en": "Create Roles" - }, - "description": "새로운 역할을 생성할 수 있습니다.", - "description_raw": { - "ko": "새로운 역할을 생성할 수 있습니다.", - "en": "Can create new roles." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 2, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 146, - "parent_id": 143, - "identifier": "core.permissions.update", - "name": "역할 수정", - "name_raw": { - "ko": "역할 수정", - "en": "Update Roles" - }, - "description": "역할 정보와 권한을 수정할 수 있습니다.", - "description_raw": { - "ko": "역할 정보와 권한을 수정할 수 있습니다.", - "en": "Can update role information and permissions." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 3, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 147, - "parent_id": 143, - "identifier": "core.permissions.delete", - "name": "역할 삭제", - "name_raw": { - "ko": "역할 삭제", - "en": "Delete Roles" - }, - "description": "역할을 삭제할 수 있습니다.", - "description_raw": { - "ko": "역할을 삭제할 수 있습니다.", - "en": "Can delete roles." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 4, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - } - ] - }, - { - "id": 148, - "parent_id": 115, - "identifier": "core.notification-logs", - "name": "알림 발송 이력", - "name_raw": { - "ko": "알림 발송 이력", - "en": "Notification Logs" - }, - "description": "알림 발송 이력 관리 권한", - "description_raw": { - "ko": "알림 발송 이력 관리 권한", - "en": "Notification log management permissions" - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 7, - "is_assigned": true, - "is_assignable": false, - "children": [ - { - "id": 149, - "parent_id": 148, - "identifier": "core.notification-logs.read", - "name": "발송 이력 조회", - "name_raw": { - "ko": "발송 이력 조회", - "en": "View Notification Logs" - }, - "description": "알림 발송 이력을 조회할 수 있습니다.", - "description_raw": { - "ko": "알림 발송 이력을 조회할 수 있습니다.", - "en": "Can view notification logs." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 150, - "parent_id": 148, - "identifier": "core.notification-logs.delete", - "name": "발송 이력 삭제", - "name_raw": { - "ko": "발송 이력 삭제", - "en": "Delete Notification Logs" - }, - "description": "알림 발송 이력을 삭제할 수 있습니다.", - "description_raw": { - "ko": "알림 발송 이력을 삭제할 수 있습니다.", - "en": "Can delete notification logs." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 2, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - } - ] - }, - { - "id": 151, - "parent_id": 115, - "identifier": "core.notifications", - "name": "알림 (관리자)", - "name_raw": { - "ko": "알림 (관리자)", - "en": "Notifications (Admin)" - }, - "description": "관리자용 알림 관리 권한 (관리자 화면에서 사용)", - "description_raw": { - "ko": "관리자용 알림 관리 권한 (관리자 화면에서 사용)", - "en": "Admin notification management permissions (used in admin UI)" - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 8, - "is_assigned": true, - "is_assignable": false, - "children": [ - { - "id": 152, - "parent_id": 151, - "identifier": "core.notifications.read", - "name": "알림 조회", - "name_raw": { - "ko": "알림 조회", - "en": "View Notifications" - }, - "description": "알림 목록 및 읽지 않은 수를 조회할 수 있습니다.", - "description_raw": { - "ko": "알림 목록 및 읽지 않은 수를 조회할 수 있습니다.", - "en": "Can view notification list and unread count." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 153, - "parent_id": 151, - "identifier": "core.notifications.update", - "name": "알림 읽음 처리", - "name_raw": { - "ko": "알림 읽음 처리", - "en": "Mark Notifications Read" - }, - "description": "알림을 읽음 처리할 수 있습니다.", - "description_raw": { - "ko": "알림을 읽음 처리할 수 있습니다.", - "en": "Can mark notifications as read." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 2, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 154, - "parent_id": 151, - "identifier": "core.notifications.delete", - "name": "알림 삭제", - "name_raw": { - "ko": "알림 삭제", - "en": "Delete Notifications" - }, - "description": "알림을 삭제할 수 있습니다.", - "description_raw": { - "ko": "알림을 삭제할 수 있습니다.", - "en": "Can delete notifications." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 3, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - } - ] - }, - { - "id": 155, - "parent_id": 115, - "identifier": "core.user-notifications", - "name": "알림 (사용자)", - "name_raw": { - "ko": "알림 (사용자)", - "en": "Notifications (User)" - }, - "description": "사용자용 알림 권한 (사용자 화면에서 본인 알림 관리)", - "description_raw": { - "ko": "사용자용 알림 권한 (사용자 화면에서 본인 알림 관리)", - "en": "User notification permissions (managing own notifications in user UI)" - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 8, - "is_assigned": true, - "is_assignable": false, - "children": [ - { - "id": 156, - "parent_id": 155, - "identifier": "core.user-notifications.read", - "name": "알림 조회", - "name_raw": { - "ko": "알림 조회", - "en": "View Notifications" - }, - "description": "본인의 알림 목록 및 읽지 않은 수를 조회할 수 있습니다.", - "description_raw": { - "ko": "본인의 알림 목록 및 읽지 않은 수를 조회할 수 있습니다.", - "en": "Can view own notification list and unread count." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 157, - "parent_id": 155, - "identifier": "core.user-notifications.update", - "name": "알림 읽음 처리", - "name_raw": { - "ko": "알림 읽음 처리", - "en": "Mark Notifications Read" - }, - "description": "본인의 알림을 읽음 처리할 수 있습니다.", - "description_raw": { - "ko": "본인의 알림을 읽음 처리할 수 있습니다.", - "en": "Can mark own notifications as read." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 2, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 158, - "parent_id": 155, - "identifier": "core.user-notifications.delete", - "name": "알림 삭제", - "name_raw": { - "ko": "알림 삭제", - "en": "Delete Notifications" - }, - "description": "본인의 알림을 삭제할 수 있습니다.", - "description_raw": { - "ko": "본인의 알림을 삭제할 수 있습니다.", - "en": "Can delete own notifications." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 3, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - } - ] - }, - { - "id": 159, - "parent_id": 115, - "identifier": "core.identity", - "name": "본인인증 (사용자)", - "name_raw": { - "ko": "본인인증 (사용자)", - "en": "Identity Verification (User)" - }, - "description": "로그인 사용자의 본인인증 challenge 요청/검증/취소 권한", - "description_raw": { - "ko": "로그인 사용자의 본인인증 challenge 요청/검증/취소 권한", - "en": "Permissions for authenticated users to request/verify/cancel IDV challenges" - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 8, - "is_assigned": true, - "is_assignable": false, - "children": [ - { - "id": 160, - "parent_id": 159, - "identifier": "core.identity.request", - "name": "IDV 요청", - "name_raw": { - "ko": "IDV 요청", - "en": "Request IDV Challenge" - }, - "description": "본인인증 challenge 를 요청할 수 있습니다.", - "description_raw": { - "ko": "본인인증 challenge 를 요청할 수 있습니다.", - "en": "Can request identity verification challenges." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 161, - "parent_id": 159, - "identifier": "core.identity.verify", - "name": "IDV 검증", - "name_raw": { - "ko": "IDV 검증", - "en": "Verify IDV Challenge" - }, - "description": "본인의 본인인증 challenge 를 검증할 수 있습니다.", - "description_raw": { - "ko": "본인의 본인인증 challenge 를 검증할 수 있습니다.", - "en": "Can verify own identity verification challenges." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": "challenge", - "owner_key": "user_id", - "order": 2, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 162, - "parent_id": 159, - "identifier": "core.identity.cancel", - "name": "IDV 취소", - "name_raw": { - "ko": "IDV 취소", - "en": "Cancel IDV Challenge" - }, - "description": "본인의 본인인증 challenge 를 취소할 수 있습니다.", - "description_raw": { - "ko": "본인의 본인인증 challenge 를 취소할 수 있습니다.", - "en": "Can cancel own identity verification challenges." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": "challenge", - "owner_key": "user_id", - "order": 3, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - } - ] - }, - { - "id": 163, - "parent_id": 115, - "identifier": "core.admin.identity", - "name": "본인인증 관리 (관리자)", - "name_raw": { - "ko": "본인인증 관리 (관리자)", - "en": "Identity Verification Management (Admin)" - }, - "description": "관리자용 IDV 프로바이더/정책/로그 관리 권한", - "description_raw": { - "ko": "관리자용 IDV 프로바이더/정책/로그 관리 권한", - "en": "Admin permissions for IDV providers, policies, and logs" - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 8, - "is_assigned": true, - "is_assignable": false, - "children": [ - { - "id": 164, - "parent_id": 163, - "identifier": "core.admin.identity.providers.read", - "name": "프로바이더 설정 조회", - "name_raw": { - "ko": "프로바이더 설정 조회", - "en": "View IDV Providers" - }, - "description": "본인인증 프로바이더 설정을 조회할 수 있습니다.", - "description_raw": { - "ko": "본인인증 프로바이더 설정을 조회할 수 있습니다.", - "en": "Can view identity verification providers." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 165, - "parent_id": 163, - "identifier": "core.admin.identity.providers.update", - "name": "프로바이더 설정 수정", - "name_raw": { - "ko": "프로바이더 설정 수정", - "en": "Update IDV Providers" - }, - "description": "본인인증 프로바이더 설정을 수정할 수 있습니다.", - "description_raw": { - "ko": "본인인증 프로바이더 설정을 수정할 수 있습니다.", - "en": "Can update identity verification providers." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 2, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 166, - "parent_id": 163, - "identifier": "core.admin.identity.policies.read", - "name": "정책 조회", - "name_raw": { - "ko": "정책 조회", - "en": "View IDV Policies" - }, - "description": "본인인증 정책(라우트/훅별)을 조회할 수 있습니다.", - "description_raw": { - "ko": "본인인증 정책(라우트/훅별)을 조회할 수 있습니다.", - "en": "Can view identity verification policies by route/hook." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 3, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 167, - "parent_id": 163, - "identifier": "core.admin.identity.policies.update", - "name": "정책 수정", - "name_raw": { - "ko": "정책 수정", - "en": "Update IDV Policies" - }, - "description": "본인인증 정책(라우트/훅별)을 CRUD 할 수 있습니다.", - "description_raw": { - "ko": "본인인증 정책(라우트/훅별)을 CRUD 할 수 있습니다.", - "en": "Can CRUD identity verification policies by route/hook." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 4, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 168, - "parent_id": 163, - "identifier": "core.admin.identity.logs.read", - "name": "로그 열람", - "name_raw": { - "ko": "로그 열람", - "en": "View IDV Logs" - }, - "description": "본인인증 이력을 조회할 수 있습니다.", - "description_raw": { - "ko": "본인인증 이력을 조회할 수 있습니다.", - "en": "Can view identity verification logs." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 5, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 169, - "parent_id": 163, - "identifier": "core.admin.identity.logs.purge", - "name": "로그 파기", - "name_raw": { - "ko": "로그 파기", - "en": "Purge IDV Logs" - }, - "description": "본인인증 이력을 파기(보관주기 외)할 수 있습니다.", - "description_raw": { - "ko": "본인인증 이력을 파기(보관주기 외)할 수 있습니다.", - "en": "Can purge identity verification logs (retention-based)." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 6, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 170, - "parent_id": 163, - "identifier": "core.admin.identity.messages.read", - "name": "메시지 템플릿 조회", - "name_raw": { - "ko": "메시지 템플릿 조회", - "en": "View IDV Message Templates" - }, - "description": "본인인증 메시지 정의/템플릿을 조회할 수 있습니다.", - "description_raw": { - "ko": "본인인증 메시지 정의/템플릿을 조회할 수 있습니다.", - "en": "Can view identity verification message definitions and templates." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 7, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 171, - "parent_id": 163, - "identifier": "core.admin.identity.messages.update", - "name": "메시지 템플릿 수정", - "name_raw": { - "ko": "메시지 템플릿 수정", - "en": "Update IDV Message Templates" - }, - "description": "본인인증 메시지 정의/템플릿을 수정할 수 있습니다.", - "description_raw": { - "ko": "본인인증 메시지 정의/템플릿을 수정할 수 있습니다.", - "en": "Can update identity verification message definitions and templates." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 8, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - } - ] - }, - { - "id": 172, - "parent_id": 115, - "identifier": "core.settings", - "name": "환경설정", - "name_raw": { - "ko": "환경설정", - "en": "Settings" - }, - "description": "시스템 환경설정 권한", - "description_raw": { - "ko": "시스템 환경설정 권한", - "en": "System settings permissions" - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 8, - "is_assigned": true, - "is_assignable": false, - "children": [ - { - "id": 173, - "parent_id": 172, - "identifier": "core.settings.read", - "name": "설정 조회", - "name_raw": { - "ko": "설정 조회", - "en": "View Settings" - }, - "description": "시스템 설정을 조회할 수 있습니다.", - "description_raw": { - "ko": "시스템 설정을 조회할 수 있습니다.", - "en": "Can view system settings." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 174, - "parent_id": 172, - "identifier": "core.settings.update", - "name": "설정 수정", - "name_raw": { - "ko": "설정 수정", - "en": "Update Settings" - }, - "description": "시스템 설정을 수정할 수 있습니다.", - "description_raw": { - "ko": "시스템 설정을 수정할 수 있습니다.", - "en": "Can update system settings." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 2, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - } - ] - }, - { - "id": 175, - "parent_id": 115, - "identifier": "core.dashboard", - "name": "대시보드", - "name_raw": { - "ko": "대시보드", - "en": "Dashboard" - }, - "description": "대시보드 접근 권한", - "description_raw": { - "ko": "대시보드 접근 권한", - "en": "Dashboard access permissions" - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 9, - "is_assigned": true, - "is_assignable": false, - "children": [ - { - "id": 176, - "parent_id": 175, - "identifier": "core.dashboard.read", - "name": "대시보드 조회", - "name_raw": { - "ko": "대시보드 조회", - "en": "View Dashboard" - }, - "description": "대시보드 통계 및 정보를 조회할 수 있습니다.", - "description_raw": { - "ko": "대시보드 통계 및 정보를 조회할 수 있습니다.", - "en": "Can view dashboard statistics and information." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 177, - "parent_id": 175, - "identifier": "core.dashboard.system-status", - "name": "시스템 상태", - "name_raw": { - "ko": "시스템 상태", - "en": "System Status" - }, - "description": "시스템 상태 정보를 조회할 수 있습니다.", - "description_raw": { - "ko": "시스템 상태 정보를 조회할 수 있습니다.", - "en": "Can view system status information." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 2, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 178, - "parent_id": 175, - "identifier": "core.dashboard.resources", - "name": "시스템 리소스", - "name_raw": { - "ko": "시스템 리소스", - "en": "System Resources" - }, - "description": "CPU, 메모리, 디스크 사용량을 조회할 수 있습니다.", - "description_raw": { - "ko": "CPU, 메모리, 디스크 사용량을 조회할 수 있습니다.", - "en": "Can view CPU, memory, and disk usage." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 3, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 179, - "parent_id": 175, - "identifier": "core.dashboard.activities", - "name": "최근 활동", - "name_raw": { - "ko": "최근 활동", - "en": "Recent Activities" - }, - "description": "최근 활동 이력을 조회할 수 있습니다.", - "description_raw": { - "ko": "최근 활동 이력을 조회할 수 있습니다.", - "en": "Can view recent activity history." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": "activityLog", - "owner_key": "user_id", - "order": 4, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 180, - "parent_id": 175, - "identifier": "core.dashboard.alerts", - "name": "시스템 알림", - "name_raw": { - "ko": "시스템 알림", - "en": "System Alerts" - }, - "description": "시스템 알림을 조회할 수 있습니다.", - "description_raw": { - "ko": "시스템 알림을 조회할 수 있습니다.", - "en": "Can view system alerts." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 5, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - } - ] - }, - { - "id": 181, - "parent_id": 115, - "identifier": "core.activities", - "name": "활동 로그", - "name_raw": { - "ko": "활동 로그", - "en": "Activity Logs" - }, - "description": "활동 로그 조회 권한", - "description_raw": { - "ko": "활동 로그 조회 권한", - "en": "Activity log access permissions" - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 10, - "is_assigned": true, - "is_assignable": false, - "children": [ - { - "id": 182, - "parent_id": 181, - "identifier": "core.activities.read", - "name": "활동 로그 조회", - "name_raw": { - "ko": "활동 로그 조회", - "en": "View Activity Logs" - }, - "description": "활동 로그를 조회할 수 있습니다.", - "description_raw": { - "ko": "활동 로그를 조회할 수 있습니다.", - "en": "Can view activity logs." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": "activityLog", - "owner_key": "user_id", - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 183, - "parent_id": 181, - "identifier": "core.activities.delete", - "name": "활동 로그 삭제", - "name_raw": { - "ko": "활동 로그 삭제", - "en": "Delete Activity Logs" - }, - "description": "활동 로그를 삭제할 수 있습니다.", - "description_raw": { - "ko": "활동 로그를 삭제할 수 있습니다.", - "en": "Can delete activity logs." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 2, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - } - ] - }, - { - "id": 184, - "parent_id": 115, - "identifier": "core.attachments", - "name": "첨부파일 관리", - "name_raw": { - "ko": "첨부파일 관리", - "en": "Attachment Management" - }, - "description": "첨부파일 관리 권한", - "description_raw": { - "ko": "첨부파일 관리 권한", - "en": "Attachment management permissions" - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 11, - "is_assigned": true, - "is_assignable": false, - "children": [ - { - "id": 185, - "parent_id": 184, - "identifier": "core.attachments.create", - "name": "첨부파일 업로드", - "name_raw": { - "ko": "첨부파일 업로드", - "en": "Upload Attachments" - }, - "description": "첨부파일을 업로드할 수 있습니다.", - "description_raw": { - "ko": "첨부파일을 업로드할 수 있습니다.", - "en": "Can upload attachments." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 186, - "parent_id": 184, - "identifier": "core.attachments.update", - "name": "첨부파일 수정", - "name_raw": { - "ko": "첨부파일 수정", - "en": "Update Attachments" - }, - "description": "첨부파일 정보 및 순서를 수정할 수 있습니다.", - "description_raw": { - "ko": "첨부파일 정보 및 순서를 수정할 수 있습니다.", - "en": "Can update attachment information and order." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": "attachment", - "owner_key": "created_by", - "order": 2, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 187, - "parent_id": 184, - "identifier": "core.attachments.delete", - "name": "첨부파일 삭제", - "name_raw": { - "ko": "첨부파일 삭제", - "en": "Delete Attachments" - }, - "description": "첨부파일을 삭제할 수 있습니다.", - "description_raw": { - "ko": "첨부파일을 삭제할 수 있습니다.", - "en": "Can delete attachments." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": "attachment", - "owner_key": "created_by", - "order": 3, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - } - ] - }, - { - "id": 188, - "parent_id": 115, - "identifier": "core.schedules", - "name": "스케줄 관리", - "name_raw": { - "ko": "스케줄 관리", - "en": "Schedule Management" - }, - "description": "스케줄 작업 관리 권한", - "description_raw": { - "ko": "스케줄 작업 관리 권한", - "en": "Schedule task management permissions" - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 12, - "is_assigned": true, - "is_assignable": false, - "children": [ - { - "id": 189, - "parent_id": 188, - "identifier": "core.schedules.read", - "name": "스케줄 조회", - "name_raw": { - "ko": "스케줄 조회", - "en": "View Schedules" - }, - "description": "스케줄 목록 및 상세 정보를 조회할 수 있습니다.", - "description_raw": { - "ko": "스케줄 목록 및 상세 정보를 조회할 수 있습니다.", - "en": "Can view schedule list and details." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 190, - "parent_id": 188, - "identifier": "core.schedules.create", - "name": "스케줄 생성", - "name_raw": { - "ko": "스케줄 생성", - "en": "Create Schedules" - }, - "description": "새로운 스케줄을 생성할 수 있습니다.", - "description_raw": { - "ko": "새로운 스케줄을 생성할 수 있습니다.", - "en": "Can create new schedules." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 2, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 191, - "parent_id": 188, - "identifier": "core.schedules.update", - "name": "스케줄 수정", - "name_raw": { - "ko": "스케줄 수정", - "en": "Update Schedules" - }, - "description": "스케줄 정보를 수정할 수 있습니다.", - "description_raw": { - "ko": "스케줄 정보를 수정할 수 있습니다.", - "en": "Can update schedule information." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 3, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 192, - "parent_id": 188, - "identifier": "core.schedules.delete", - "name": "스케줄 삭제", - "name_raw": { - "ko": "스케줄 삭제", - "en": "Delete Schedules" - }, - "description": "스케줄을 삭제할 수 있습니다.", - "description_raw": { - "ko": "스케줄을 삭제할 수 있습니다.", - "en": "Can delete schedules." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 4, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 193, - "parent_id": 188, - "identifier": "core.schedules.run", - "name": "스케줄 실행", - "name_raw": { - "ko": "스케줄 실행", - "en": "Run Schedules" - }, - "description": "스케줄을 수동으로 실행할 수 있습니다.", - "description_raw": { - "ko": "스케줄을 수동으로 실행할 수 있습니다.", - "en": "Can manually run schedules." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 5, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - } - ] - }, - { - "id": 194, - "parent_id": 115, - "identifier": "core.language_packs", - "name": "언어팩 관리", - "name_raw": { - "ko": "언어팩 관리", - "en": "Language Pack Management" - }, - "description": "언어팩 설치/제거/활성화 권한", - "description_raw": { - "ko": "언어팩 설치/제거/활성화 권한", - "en": "Language pack install/uninstall/activation permissions" - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 13, - "is_assigned": true, - "is_assignable": false, - "children": [ - { - "id": 195, - "parent_id": 194, - "identifier": "core.language_packs.read", - "name": "언어팩 조회", - "name_raw": { - "ko": "언어팩 조회", - "en": "View Language Packs" - }, - "description": "설치된 언어팩 목록 및 상세 정보를 조회할 수 있습니다.", - "description_raw": { - "ko": "설치된 언어팩 목록 및 상세 정보를 조회할 수 있습니다.", - "en": "Can view installed language pack list and details." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 1, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 196, - "parent_id": 194, - "identifier": "core.language_packs.install", - "name": "언어팩 설치", - "name_raw": { - "ko": "언어팩 설치", - "en": "Install Language Packs" - }, - "description": "ZIP/GitHub/URL 로 언어팩을 설치할 수 있습니다.", - "description_raw": { - "ko": "ZIP/GitHub/URL 로 언어팩을 설치할 수 있습니다.", - "en": "Can install language packs from ZIP/GitHub/URL." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 2, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 197, - "parent_id": 194, - "identifier": "core.language_packs.manage", - "name": "언어팩 관리", - "name_raw": { - "ko": "언어팩 관리", - "en": "Manage Language Packs" - }, - "description": "언어팩을 활성화/비활성화/제거할 수 있습니다.", - "description_raw": { - "ko": "언어팩을 활성화/비활성화/제거할 수 있습니다.", - "en": "Can activate/deactivate/uninstall language packs." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 3, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - }, - { - "id": 198, - "parent_id": 194, - "identifier": "core.language_packs.update", - "name": "언어팩 업데이트", - "name_raw": { - "ko": "언어팩 업데이트", - "en": "Update Language Packs" - }, - "description": "설치된 언어팩의 업데이트를 확인하고 실행할 수 있습니다.", - "description_raw": { - "ko": "설치된 언어팩의 업데이트를 확인하고 실행할 수 있습니다.", - "en": "Can check and apply updates for installed language packs." - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 4, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - } - ] - } - ] - }, - { - "id": 1, - "parent_id": null, - "identifier": "apidoc-sample.parent", - "name": "API 문서 샘플 권한", - "name_raw": { - "ko": "API 문서 샘플 권한", - "en": "API Doc Sample Permission" - }, - "description": "문서 실측용 권한", - "description_raw": { - "ko": "문서 실측용 권한", - "en": "Sample permission" - }, - "extension_type": null, - "extension_identifier": null, - "resource_route_key": null, - "owner_key": null, - "order": 0, - "is_assigned": true, - "is_assignable": false, - "children": [ - { - "id": 2, - "parent_id": 1, - "identifier": "apidoc-sample.child", - "name": "하위 권한", - "name_raw": { - "ko": "하위 권한", - "en": "Child Permission" - }, - "description": "Quod consequatur sunt necessitatibus quam quia voluptas quod aperiam.", - "description_raw": { - "ko": "Quod consequatur sunt necessitatibus quam quia voluptas quod aperiam.", - "en": "Mollitia incidunt dolor rem maxime." - }, - "extension_type": null, - "extension_identifier": null, - "resource_route_key": null, - "owner_key": null, - "order": 0, - "is_assigned": true, - "is_assignable": true, - "scope_type": null - } - ] - }, - { - "id": 3, - "parent_id": null, - "identifier": "sirsoft-board", - "name": "게시판", - "name_raw": { - "ko": "게시판", - "en": "Board" - }, - "description": "게시판 모듈 권한", - "description_raw": { - "ko": "게시판 모듈 권한", - "en": "Board module permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-board", - "resource_route_key": null, - "owner_key": null, - "order": 100, - "is_assigned": true, - "is_assignable": false, - "children": [ - { - "id": 4, - "parent_id": 3, - "identifier": "sirsoft-board.apidoc-sample-board", - "name": "API 문서 샘플 게시판 게시판", - "name_raw": { - "ko": "API 문서 샘플 게시판 게시판", - "en": "API Doc Sample Board board" - }, - "description": "API 문서 샘플 게시판 게시판 권한", - "description_raw": { - "ko": "API 문서 샘플 게시판 게시판 권한", - "en": "API Doc Sample Board board permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-board", - "resource_route_key": null, - "owner_key": null, - "order": 0, - "is_assigned": true, - "is_assignable": false, - "scope_type": null - }, - { - "id": 95, - "parent_id": 3, - "identifier": "sirsoft-board.boards", - "name": "게시판 관리", - "name_raw": { - "ko": "게시판 관리", - "en": "Board Management" - }, - "description": "게시판 관리 권한", - "description_raw": { - "ko": "게시판 관리 권한", - "en": "Board management permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-board", - "resource_route_key": null, - "owner_key": null, - "order": 1, - "is_assigned": true, - "is_assignable": false, - "scope_type": null - }, - { - "id": 100, - "parent_id": 3, - "identifier": "sirsoft-board.settings", - "name": "환경설정", - "name_raw": { - "ko": "환경설정", - "en": "Settings" - }, - "description": "게시판 환경설정 권한", - "description_raw": { - "ko": "게시판 환경설정 권한", - "en": "Board settings permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-board", - "resource_route_key": null, - "owner_key": null, - "order": 2, - "is_assigned": true, - "is_assignable": false, - "scope_type": null - }, - { - "id": 103, - "parent_id": 3, - "identifier": "sirsoft-board.identity.policies", - "name": "게시판 본인인증 정책", - "name_raw": { - "ko": "게시판 본인인증 정책", - "en": "Board Identity Policies" - }, - "description": "게시판 컨텍스트의 본인인증 정책 관리 권한", - "description_raw": { - "ko": "게시판 컨텍스트의 본인인증 정책 관리 권한", - "en": "Manage identity verification policies in board context" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-board", - "resource_route_key": null, - "owner_key": null, - "order": 3, - "is_assigned": true, - "is_assignable": false, - "scope_type": null - }, - { - "id": 106, - "parent_id": 3, - "identifier": "sirsoft-board.reports", - "name": "게시판 신고 관리", - "name_raw": { - "ko": "게시판 신고 관리", - "en": "Report Management" - }, - "description": "게시판 신고 관리 권한", - "description_raw": { - "ko": "게시판 신고 관리 권한", - "en": "Board report management permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-board", - "resource_route_key": null, - "owner_key": null, - "order": 4, - "is_assigned": true, - "is_assignable": false, - "scope_type": null - } - ] - }, - { - "id": 21, - "parent_id": null, - "identifier": "sirsoft-ecommerce", - "name": "이커머스", - "name_raw": { - "ko": "이커머스", - "en": "Ecommerce" - }, - "description": "이커머스 모듈 권한", - "description_raw": { - "ko": "이커머스 모듈 권한", - "en": "Ecommerce module permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 100, - "is_assigned": true, - "is_assignable": false, - "children": [ - { - "id": 22, - "parent_id": 21, - "identifier": "sirsoft-ecommerce.products", - "name": "상품 관리", - "name_raw": { - "ko": "상품 관리", - "en": "Product Management" - }, - "description": "상품 관리 권한", - "description_raw": { - "ko": "상품 관리 권한", - "en": "Product management permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 1, - "is_assigned": true, - "is_assignable": false, - "scope_type": null - }, - { - "id": 27, - "parent_id": 21, - "identifier": "sirsoft-ecommerce.orders", - "name": "주문 관리", - "name_raw": { - "ko": "주문 관리", - "en": "Order Management" - }, - "description": "주문 관리 권한", - "description_raw": { - "ko": "주문 관리 권한", - "en": "Order management permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 2, - "is_assigned": true, - "is_assignable": false, - "scope_type": null - }, - { - "id": 30, - "parent_id": 21, - "identifier": "sirsoft-ecommerce.categories", - "name": "카테고리 관리", - "name_raw": { - "ko": "카테고리 관리", - "en": "Category Management" - }, - "description": "카테고리 관리 권한", - "description_raw": { - "ko": "카테고리 관리 권한", - "en": "Category management permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 3, - "is_assigned": true, - "is_assignable": false, - "scope_type": null - }, - { - "id": 35, - "parent_id": 21, - "identifier": "sirsoft-ecommerce.brands", - "name": "브랜드 관리", - "name_raw": { - "ko": "브랜드 관리", - "en": "Brand Management" - }, - "description": "브랜드 관리 권한", - "description_raw": { - "ko": "브랜드 관리 권한", - "en": "Brand management permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 4, - "is_assigned": true, - "is_assignable": false, - "scope_type": null - }, - { - "id": 40, - "parent_id": 21, - "identifier": "sirsoft-ecommerce.product-notice-templates", - "name": "상품정보제공고시 관리", - "name_raw": { - "ko": "상품정보제공고시 관리", - "en": "Product Notice Template Management" - }, - "description": "상품정보제공고시 템플릿 관리 권한", - "description_raw": { - "ko": "상품정보제공고시 템플릿 관리 권한", - "en": "Product notice template management permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 5, - "is_assigned": true, - "is_assignable": false, - "scope_type": null - }, - { - "id": 45, - "parent_id": 21, - "identifier": "sirsoft-ecommerce.product-common-infos", - "name": "공통정보 관리", - "name_raw": { - "ko": "공통정보 관리", - "en": "Product Common Info Management" - }, - "description": "상품 공통정보 관리 권한", - "description_raw": { - "ko": "상품 공통정보 관리 권한", - "en": "Product common info management permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 6, - "is_assigned": true, - "is_assignable": false, - "scope_type": null - }, - { - "id": 50, - "parent_id": 21, - "identifier": "sirsoft-ecommerce.settings", - "name": "환경설정", - "name_raw": { - "ko": "환경설정", - "en": "Settings" - }, - "description": "이커머스 환경설정 권한", - "description_raw": { - "ko": "이커머스 환경설정 권한", - "en": "Ecommerce settings permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 7, - "is_assigned": true, - "is_assignable": false, - "scope_type": null - }, - { - "id": 53, - "parent_id": 21, - "identifier": "sirsoft-ecommerce.promotion-coupon", - "name": "쿠폰 관리", - "name_raw": { - "ko": "쿠폰 관리", - "en": "Coupon Management" - }, - "description": "쿠폰 관리 권한", - "description_raw": { - "ko": "쿠폰 관리 권한", - "en": "Coupon management permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 8, - "is_assigned": true, - "is_assignable": false, - "scope_type": null - }, - { - "id": 58, - "parent_id": 21, - "identifier": "sirsoft-ecommerce.shipping-policies", - "name": "배송정책 관리", - "name_raw": { - "ko": "배송정책 관리", - "en": "Shipping Policy Management" - }, - "description": "배송정책 관리 권한", - "description_raw": { - "ko": "배송정책 관리 권한", - "en": "Shipping policy management permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 9, - "is_assigned": true, - "is_assignable": false, - "scope_type": null - }, - { - "id": 63, - "parent_id": 21, - "identifier": "sirsoft-ecommerce.product-labels", - "name": "상품 라벨 관리", - "name_raw": { - "ko": "상품 라벨 관리", - "en": "Product Label Management" - }, - "description": "상품 라벨 관리 권한", - "description_raw": { - "ko": "상품 라벨 관리 권한", - "en": "Product label management permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 10, - "is_assigned": true, - "is_assignable": false, - "scope_type": null - }, - { - "id": 68, - "parent_id": 21, - "identifier": "sirsoft-ecommerce.identity.policies", - "name": "이커머스 본인인증 정책", - "name_raw": { - "ko": "이커머스 본인인증 정책", - "en": "Ecommerce Identity Policies" - }, - "description": "이커머스 컨텍스트의 본인인증 정책 관리 권한", - "description_raw": { - "ko": "이커머스 컨텍스트의 본인인증 정책 관리 권한", - "en": "Manage identity verification policies in ecommerce context" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 11, - "is_assigned": true, - "is_assignable": false, - "scope_type": null - }, - { - "id": 71, - "parent_id": 21, - "identifier": "sirsoft-ecommerce.reviews", - "name": "리뷰 관리", - "name_raw": { - "ko": "리뷰 관리", - "en": "Review Management" - }, - "description": "상품 리뷰 관리 권한", - "description_raw": { - "ko": "상품 리뷰 관리 권한", - "en": "Product review management permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 12, - "is_assigned": true, - "is_assignable": false, - "scope_type": null - }, - { - "id": 75, - "parent_id": 21, - "identifier": "sirsoft-ecommerce.inquiries", - "name": "문의 관리", - "name_raw": { - "ko": "문의 관리", - "en": "Inquiry Management" - }, - "description": "상품 1:1 문의 관리 권한", - "description_raw": { - "ko": "상품 1:1 문의 관리 권한", - "en": "Product inquiry management permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 13, - "is_assigned": true, - "is_assignable": false, - "scope_type": null - }, - { - "id": 78, - "parent_id": 21, - "identifier": "sirsoft-ecommerce.dashboard", - "name": "대시보드", - "name_raw": { - "ko": "대시보드", - "en": "Dashboard" - }, - "description": "관리자 대시보드 이커머스 영역 조회 권한", - "description_raw": { - "ko": "관리자 대시보드 이커머스 영역 조회 권한", - "en": "Admin dashboard commerce area view permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 14, - "is_assigned": true, - "is_assignable": false, - "scope_type": null - }, - { - "id": 80, - "parent_id": 21, - "identifier": "sirsoft-ecommerce.user-products", - "name": "사용자 상품", - "name_raw": { - "ko": "사용자 상품", - "en": "User Products" - }, - "description": "사용자 상품 접근 권한 (블랙컨슈머 차단용)", - "description_raw": { - "ko": "사용자 상품 접근 권한 (블랙컨슈머 차단용)", - "en": "User product access permissions (for blocking malicious consumers)" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 15, - "is_assigned": true, - "is_assignable": false, - "scope_type": null - }, - { - "id": 82, - "parent_id": 21, - "identifier": "sirsoft-ecommerce.user-orders", - "name": "사용자 주문", - "name_raw": { - "ko": "사용자 주문", - "en": "User Orders" - }, - "description": "사용자 주문 관련 권한 (블랙컨슈머 차단용)", - "description_raw": { - "ko": "사용자 주문 관련 권한 (블랙컨슈머 차단용)", - "en": "User order permissions (for blocking malicious consumers)" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 16, - "is_assigned": true, - "is_assignable": false, - "scope_type": null - }, - { - "id": 86, - "parent_id": 21, - "identifier": "sirsoft-ecommerce.user-reviews", - "name": "사용자 리뷰", - "name_raw": { - "ko": "사용자 리뷰", - "en": "User Reviews" - }, - "description": "사용자 리뷰 관련 권한 (블랙컨슈머 차단용)", - "description_raw": { - "ko": "사용자 리뷰 관련 권한 (블랙컨슈머 차단용)", - "en": "User review permissions (for blocking malicious consumers)" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 17, - "is_assigned": true, - "is_assignable": false, - "scope_type": null - }, - { - "id": 88, - "parent_id": 21, - "identifier": "sirsoft-ecommerce.mileage", - "name": "마일리지 관리", - "name_raw": { - "ko": "마일리지 관리", - "en": "Mileage Management" - }, - "description": "마일리지 내역 조회 및 수동 지급/차감 권한", - "description_raw": { - "ko": "마일리지 내역 조회 및 수동 지급/차감 권한", - "en": "Mileage history and manual grant/deduct permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 18, - "is_assigned": true, - "is_assignable": false, - "scope_type": null - }, - { - "id": 91, - "parent_id": 21, - "identifier": "sirsoft-ecommerce.user-currency", - "name": "회원 결제 통화 관리", - "name_raw": { - "ko": "회원 결제 통화 관리", - "en": "User Currency Management" - }, - "description": "관리자가 회원별 결제 통화를 변경하는 권한", - "description_raw": { - "ko": "관리자가 회원별 결제 통화를 변경하는 권한", - "en": "Permission for admins to change a user's payment currency" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 19, - "is_assigned": true, - "is_assignable": false, - "scope_type": null - }, - { - "id": 93, - "parent_id": 21, - "identifier": "sirsoft-ecommerce.user-shipping-country", - "name": "회원 배송국가 관리", - "name_raw": { - "ko": "회원 배송국가 관리", - "en": "User Shipping Country Management" - }, - "description": "관리자가 회원별 배송국가를 변경하는 권한", - "description_raw": { - "ko": "관리자가 회원별 배송국가를 변경하는 권한", - "en": "Permission for admins to change a user's shipping country" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-ecommerce", - "resource_route_key": null, - "owner_key": null, - "order": 20, - "is_assigned": true, - "is_assignable": false, - "scope_type": null - } - ] - }, - { - "id": 109, - "parent_id": null, - "identifier": "sirsoft-page", - "name": "페이지", - "name_raw": { - "ko": "페이지", - "en": "Page" - }, - "description": "페이지 모듈 권한", - "description_raw": { - "ko": "페이지 모듈 권한", - "en": "Page module permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-page", - "resource_route_key": null, - "owner_key": null, - "order": 100, - "is_assigned": true, - "is_assignable": false, - "children": [ - { - "id": 110, - "parent_id": 109, - "identifier": "sirsoft-page.pages", - "name": "페이지 관리", - "name_raw": { - "ko": "페이지 관리", - "en": "Page Management" - }, - "description": "페이지 관리 권한", - "description_raw": { - "ko": "페이지 관리 권한", - "en": "Page management permissions" - }, - "extension_type": "module", - "extension_identifier": "sirsoft-page", - "resource_route_key": null, - "owner_key": null, - "order": 1, - "is_assigned": true, - "is_assignable": false, - "scope_type": null - } - ] - }, - { - "id": 115, - "parent_id": null, - "identifier": "core", - "name": "코어", - "name_raw": { - "ko": "코어", - "en": "Core" - }, - "description": "코어 시스템 권한", - "description_raw": { - "ko": "코어 시스템 권한", - "en": "Core system permissions" - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 1, - "is_assigned": true, - "is_assignable": false, - "children": [ - { - "id": 116, - "parent_id": 115, - "identifier": "core.users", - "name": "사용자 관리", - "name_raw": { - "ko": "사용자 관리", - "en": "User Management" - }, - "description": "사용자 관리 권한", - "description_raw": { - "ko": "사용자 관리 권한", - "en": "User management permissions" - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 1, - "is_assigned": true, - "is_assignable": false, - "scope_type": null - }, - { - "id": 121, - "parent_id": 115, - "identifier": "core.menus", - "name": "메뉴 관리", - "name_raw": { - "ko": "메뉴 관리", - "en": "Menu Management" - }, - "description": "메뉴 관리 권한", - "description_raw": { - "ko": "메뉴 관리 권한", - "en": "Menu management permissions" - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 2, - "is_assigned": true, - "is_assignable": false, - "scope_type": null - }, - { - "id": 126, - "parent_id": 115, - "identifier": "core.modules", - "name": "모듈 관리", - "name_raw": { - "ko": "모듈 관리", - "en": "Module Management" - }, - "description": "모듈 관리 권한", - "description_raw": { - "ko": "모듈 관리 권한", - "en": "Module management permissions" - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 3, - "is_assigned": true, - "is_assignable": false, - "scope_type": null - }, - { - "id": 131, - "parent_id": 115, - "identifier": "core.plugins", - "name": "플러그인 관리", - "name_raw": { - "ko": "플러그인 관리", - "en": "Plugin Management" - }, - "description": "플러그인 관리 권한", - "description_raw": { - "ko": "플러그인 관리 권한", - "en": "Plugin management permissions" - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 4, - "is_assigned": true, - "is_assignable": false, - "scope_type": null - }, - { - "id": 137, - "parent_id": 115, - "identifier": "core.templates", - "name": "템플릿 관리", - "name_raw": { - "ko": "템플릿 관리", - "en": "Template Management" - }, - "description": "템플릿 관리 권한", - "description_raw": { - "ko": "템플릿 관리 권한", - "en": "Template management permissions" - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 5, - "is_assigned": true, - "is_assignable": false, - "scope_type": null - }, - { - "id": 143, - "parent_id": 115, - "identifier": "core.permissions", - "name": "권한 관리", - "name_raw": { - "ko": "권한 관리", - "en": "Permission Management" - }, - "description": "역할 및 권한 관리 권한", - "description_raw": { - "ko": "역할 및 권한 관리 권한", - "en": "Role and permission management permissions" - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 6, - "is_assigned": true, - "is_assignable": false, - "scope_type": null - }, - { - "id": 148, - "parent_id": 115, - "identifier": "core.notification-logs", - "name": "알림 발송 이력", - "name_raw": { - "ko": "알림 발송 이력", - "en": "Notification Logs" - }, - "description": "알림 발송 이력 관리 권한", - "description_raw": { - "ko": "알림 발송 이력 관리 권한", - "en": "Notification log management permissions" - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 7, - "is_assigned": true, - "is_assignable": false, - "scope_type": null - }, - { - "id": 151, - "parent_id": 115, - "identifier": "core.notifications", - "name": "알림 (관리자)", - "name_raw": { - "ko": "알림 (관리자)", - "en": "Notifications (Admin)" - }, - "description": "관리자용 알림 관리 권한 (관리자 화면에서 사용)", - "description_raw": { - "ko": "관리자용 알림 관리 권한 (관리자 화면에서 사용)", - "en": "Admin notification management permissions (used in admin UI)" - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 8, - "is_assigned": true, - "is_assignable": false, - "scope_type": null - }, - { - "id": 155, - "parent_id": 115, - "identifier": "core.user-notifications", - "name": "알림 (사용자)", - "name_raw": { - "ko": "알림 (사용자)", - "en": "Notifications (User)" - }, - "description": "사용자용 알림 권한 (사용자 화면에서 본인 알림 관리)", - "description_raw": { - "ko": "사용자용 알림 권한 (사용자 화면에서 본인 알림 관리)", - "en": "User notification permissions (managing own notifications in user UI)" - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 8, - "is_assigned": true, - "is_assignable": false, - "scope_type": null - }, - { - "id": 159, - "parent_id": 115, - "identifier": "core.identity", - "name": "본인인증 (사용자)", - "name_raw": { - "ko": "본인인증 (사용자)", - "en": "Identity Verification (User)" - }, - "description": "로그인 사용자의 본인인증 challenge 요청/검증/취소 권한", - "description_raw": { - "ko": "로그인 사용자의 본인인증 challenge 요청/검증/취소 권한", - "en": "Permissions for authenticated users to request/verify/cancel IDV challenges" - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 8, - "is_assigned": true, - "is_assignable": false, - "scope_type": null - }, - { - "id": 163, - "parent_id": 115, - "identifier": "core.admin.identity", - "name": "본인인증 관리 (관리자)", - "name_raw": { - "ko": "본인인증 관리 (관리자)", - "en": "Identity Verification Management (Admin)" - }, - "description": "관리자용 IDV 프로바이더/정책/로그 관리 권한", - "description_raw": { - "ko": "관리자용 IDV 프로바이더/정책/로그 관리 권한", - "en": "Admin permissions for IDV providers, policies, and logs" - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 8, - "is_assigned": true, - "is_assignable": false, - "scope_type": null - }, - { - "id": 172, - "parent_id": 115, - "identifier": "core.settings", - "name": "환경설정", - "name_raw": { - "ko": "환경설정", - "en": "Settings" - }, - "description": "시스템 환경설정 권한", - "description_raw": { - "ko": "시스템 환경설정 권한", - "en": "System settings permissions" - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 8, - "is_assigned": true, - "is_assignable": false, - "scope_type": null - }, - { - "id": 175, - "parent_id": 115, - "identifier": "core.dashboard", - "name": "대시보드", - "name_raw": { - "ko": "대시보드", - "en": "Dashboard" - }, - "description": "대시보드 접근 권한", - "description_raw": { - "ko": "대시보드 접근 권한", - "en": "Dashboard access permissions" - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 9, - "is_assigned": true, - "is_assignable": false, - "scope_type": null - }, - { - "id": 181, - "parent_id": 115, - "identifier": "core.activities", - "name": "활동 로그", - "name_raw": { - "ko": "활동 로그", - "en": "Activity Logs" - }, - "description": "활동 로그 조회 권한", - "description_raw": { - "ko": "활동 로그 조회 권한", - "en": "Activity log access permissions" - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 10, - "is_assigned": true, - "is_assignable": false, - "scope_type": null - }, - { - "id": 184, - "parent_id": 115, - "identifier": "core.attachments", - "name": "첨부파일 관리", - "name_raw": { - "ko": "첨부파일 관리", - "en": "Attachment Management" - }, - "description": "첨부파일 관리 권한", - "description_raw": { - "ko": "첨부파일 관리 권한", - "en": "Attachment management permissions" - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 11, - "is_assigned": true, - "is_assignable": false, - "scope_type": null - }, - { - "id": 188, - "parent_id": 115, - "identifier": "core.schedules", - "name": "스케줄 관리", - "name_raw": { - "ko": "스케줄 관리", - "en": "Schedule Management" - }, - "description": "스케줄 작업 관리 권한", - "description_raw": { - "ko": "스케줄 작업 관리 권한", - "en": "Schedule task management permissions" - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 12, - "is_assigned": true, - "is_assignable": false, - "scope_type": null - }, - { - "id": 194, - "parent_id": 115, - "identifier": "core.language_packs", - "name": "언어팩 관리", - "name_raw": { - "ko": "언어팩 관리", - "en": "Language Pack Management" - }, - "description": "언어팩 설치/제거/활성화 권한", - "description_raw": { - "ko": "언어팩 설치/제거/활성화 권한", - "en": "Language pack install/uninstall/activation permissions" - }, - "extension_type": "core", - "extension_identifier": "core", - "resource_route_key": null, - "owner_key": null, - "order": 13, - "is_assigned": true, - "is_assignable": false, - "scope_type": null - } - ] - } - ], - "created_at": "2026-07-08 10:41:24", - "updated_at": "2026-07-08 10:41:24", - "abilities": { - "can_create": true, - "can_update": true, - "can_delete": true, - "can_toggle_status": true - } + "description": "시스템의 모든 기능에 접근할 수 있는 최고 관리자입니다.", + "...": "(13개 키 생략, 총 18개)" } } ``` @@ -9283,7 +566,7 @@ HTTP/1.1 200 **요청 예시** ```http -PUT /api/admin/roles/4 HTTP/1.1 +PUT /api/admin/roles/1 HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -9305,20 +588,20 @@ _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| id | integer | `4` | 기본 키 (내부 식별자) | -| identifier | string | `apidoc-sample-role` | 역할명 (예: admin, user, manager) | +| id | integer | `1` | 기본 키 (내부 식별자) | +| identifier | string | `admin` | 역할명 (예: admin, user, manager) | | name | string | `실측 예시값` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | -| name_raw | object | `{"ko":"실측 예시값","en":"실측 예시값","fr":"실측 예시값"}` | `name` 의 원본 값 (현재 로케일 미해석 원본 JSON/문자열) | +| name_raw | object | `{"ko":"실측 예시값","en":"실측 예시값"}` | `name` 의 원본 값 (현재 로케일 미해석 원본 JSON/문자열) | | description | string | `실측 예시값` | 설명 (다국어 필드는 로케일별 값 객체) | -| description_raw | object | `{"ko":"실측 예시값","en":"실측 예시값","fr":"실측 예시값"}` | `description` 의 원본 값 (현재 로케일 미해석 원본 JSON/문자열) | -| extension_type | null | `null` | 이 리소스를 소유한 확장의 타입 (core/module/plugin/template) | -| extension_identifier | null | `null` | 이 리소스를 소유한 확장의 식별자 | +| description_raw | object | `{"ko":"실측 예시값","en":"실측 예시값"}` | `description` 의 원본 값 (현재 로케일 미해석 원본 JSON/문자열) | +| extension_type | string | `core` | 이 리소스를 소유한 확장의 타입 (core/module/plugin/template) | +| extension_identifier | string | `core` | 이 리소스를 소유한 확장의 식별자 | | extension_name | null | `null` | 이 리소스를 소유한 확장의 표시 이름 (manifest name) | -| is_deletable | boolean | `true` | deletable 여부 | +| is_deletable | boolean | `false` | deletable 여부 | | is_active | boolean | `true` | active 여부 | -| created_at | string | `2026-07-08 10:41:24` | 생성 일시 | -| updated_at | string | `2026-07-08 12:14:48` | 최종 수정 일시 | -| abilities | object | `{"can_create":true,"can_update":true,"can_delete":true,"c…` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | +| created_at | string | `2026-07-30 17:36:00` | 생성 일시 | +| updated_at | string | `2026-08-04 21:53:48` | 최종 수정 일시 | +| abilities | object | `{"can_create":false,"can_update":false,"can_delete":false…` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | **응답 예시** @@ -9331,32 +614,30 @@ HTTP/1.1 200 "success": true, "message": "역할이 성공적으로 수정되었습니다.", "data": { - "id": 4, - "identifier": "apidoc-sample-role", + "id": 1, + "identifier": "admin", "name": "실측 예시값", "name_raw": { "ko": "실측 예시값", - "en": "실측 예시값", - "fr": "실측 예시값" + "en": "실측 예시값" }, "description": "실측 예시값", "description_raw": { "ko": "실측 예시값", - "en": "실측 예시값", - "fr": "실측 예시값" + "en": "실측 예시값" }, - "extension_type": null, - "extension_identifier": null, + "extension_type": "core", + "extension_identifier": "core", "extension_name": null, - "is_deletable": true, + "is_deletable": false, "is_active": true, - "created_at": "2026-07-08 10:41:24", - "updated_at": "2026-07-08 12:14:48", + "created_at": "2026-07-30 17:36:00", + "updated_at": "2026-08-04 21:53:48", "abilities": { - "can_create": true, - "can_update": true, - "can_delete": true, - "can_toggle_status": true + "can_create": false, + "can_update": false, + "can_delete": false, + "can_toggle_status": false } } } @@ -9393,7 +674,7 @@ HTTP/1.1 200 **요청 예시** ```http -PATCH /api/admin/roles/4/toggle-status HTTP/1.1 +PATCH /api/admin/roles/1/toggle-status HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -9405,20 +686,20 @@ _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| id | integer | `4` | 기본 키 (내부 식별자) | -| identifier | string | `apidoc-sample-role` | 역할명 (예: admin, user, manager) | -| name | string | `API 문서 샘플 역할` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | -| name_raw | object | `{"ko":"API 문서 샘플 역할","en":"API Doc Sample Role"}` | `name` 의 원본 값 (현재 로케일 미해석 원본 JSON/문자열) | -| description | string | `문서 실측용 역할` | 설명 (다국어 필드는 로케일별 값 객체) | -| description_raw | object | `{"ko":"문서 실측용 역할","en":"Sample role for API docs"}` | `description` 의 원본 값 (현재 로케일 미해석 원본 JSON/문자열) | -| extension_type | null | `null` | 이 리소스를 소유한 확장의 타입 (core/module/plugin/template) | -| extension_identifier | null | `null` | 이 리소스를 소유한 확장의 식별자 | +| id | integer | `1` | 기본 키 (내부 식별자) | +| identifier | string | `admin` | 역할명 (예: admin, user, manager) | +| name | string | `관리자` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | +| name_raw | object | `{"ko":"관리자","en":"Administrator","ja":"管理者"}` | `name` 의 원본 값 (현재 로케일 미해석 원본 JSON/문자열) | +| description | string | `시스템의 모든 기능에 접근할 수 있는 최고 관리자입니다.` | 설명 (다국어 필드는 로케일별 값 객체) | +| description_raw | object | `{"ko":"시스템의 모든 기능에 접근할 수 있는 최고 관리자입니다.","en":"Super admin…` | `description` 의 원본 값 (현재 로케일 미해석 원본 JSON/문자열) | +| extension_type | string | `core` | 이 리소스를 소유한 확장의 타입 (core/module/plugin/template) | +| extension_identifier | string | `core` | 이 리소스를 소유한 확장의 식별자 | | extension_name | null | `null` | 이 리소스를 소유한 확장의 표시 이름 (manifest name) | -| is_deletable | boolean | `true` | deletable 여부 | +| is_deletable | boolean | `false` | deletable 여부 | | is_active | boolean | `false` | active 여부 | -| users_count | integer | `1` | users 개수 (집계) | -| created_at | string | `2026-07-08 10:41:24` | 생성 일시 | -| updated_at | string | `2026-07-08 12:14:48` | 최종 수정 일시 | +| users_count | integer | `71` | users 개수 (집계) | +| created_at | string | `2026-07-30 17:36:00` | 생성 일시 | +| updated_at | string | `2026-08-04 21:53:48` | 최종 수정 일시 | | abilities | object | `{"can_create":true,"can_update":true,"can_delete":true,"c…` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | **응답 예시** @@ -9432,26 +713,28 @@ HTTP/1.1 200 "success": true, "message": "역할이 성공적으로 수정되었습니다.", "data": { - "id": 4, - "identifier": "apidoc-sample-role", - "name": "API 문서 샘플 역할", + "id": 1, + "identifier": "admin", + "name": "관리자", "name_raw": { - "ko": "API 문서 샘플 역할", - "en": "API Doc Sample Role" + "ko": "관리자", + "en": "Administrator", + "ja": "管理者" }, - "description": "문서 실측용 역할", + "description": "시스템의 모든 기능에 접근할 수 있는 최고 관리자입니다.", "description_raw": { - "ko": "문서 실측용 역할", - "en": "Sample role for API docs" + "ko": "시스템의 모든 기능에 접근할 수 있는 최고 관리자입니다.", + "en": "Super administrator with access to all system features.", + "ja": "システムのすべての機能にアクセスできる最高管理者です。" }, - "extension_type": null, - "extension_identifier": null, + "extension_type": "core", + "extension_identifier": "core", "extension_name": null, - "is_deletable": true, + "is_deletable": false, "is_active": false, - "users_count": 1, - "created_at": "2026-07-08 10:41:24", - "updated_at": "2026-07-08 12:14:48", + "users_count": 71, + "created_at": "2026-07-30 17:36:00", + "updated_at": "2026-08-04 21:53:48", "abilities": { "can_create": true, "can_update": true, diff --git a/docs/backend/api/schedules.md b/docs/backend/api/schedules.md index 9df03620..e1d17d9d 100644 --- a/docs/backend/api/schedules.md +++ b/docs/backend/api/schedules.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Schedules 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` @@ -73,11 +73,11 @@ _목록 응답: `data.data[]` 배열 항목의 필드 + `data.pagination`._ | is_active | boolean | `true` | active 여부 | | last_result | string | `success` | 마지막 실행 결과: success(성공), failed(실패), running(실행중), never(미실행) | | last_result_label | string | `성공` | `last_result` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | -| last_run_at | string | `2026-07-05T19:20:23+09:00` | last run 일시 | +| last_run_at | string | `2026-07-30T22:55:00+09:00` | last run 일시 | | last_duration | null | `null` | 마지막 실행의 소요 시간을 사람이 읽는 문자열로 포맷한 값 (예: "45초", "2분 3초" — 마지막 실행 이력의 duration 파생, 실행 이력이 없으면 null) | -| next_run_at | string | `2026-07-07T12:00:00+09:00` | next run 일시 | -| creator | object | `{"uuid":"a231747f-e82e-4cf2-9ae1-a261849dce40","name":"AP…` | 생성자 정보 객체 (uuid/name/email — creator 관계 파생) | -| created_at | string | `2026-07-06` | 생성 일시 | +| next_run_at | string | `2026-08-01T12:00:00+09:00` | next run 일시 | +| creator | object | `{"uuid":"a2640dae-e87c-4a28-b4f1-481fd961dc02","name":"AP…` | 생성자 정보 객체 (uuid/name/email — creator 관계 파생) | +| created_at | string | `2026-07-31` | 생성 일시 | | abilities | object | `{"can_create":true,"can_update":true,"can_delete":true,"c…` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | **응답 예시** @@ -106,14 +106,14 @@ HTTP/1.1 200 "is_active": true, "last_result": "success", "last_result_label": "성공", - "last_run_at": "2026-07-07T10:41:24+09:00", + "last_run_at": "2026-07-30T22:55:00+09:00", "last_duration": null, - "next_run_at": "2026-07-08T12:00:00+09:00", + "next_run_at": "2026-08-01T12:00:00+09:00", "creator": { - "uuid": "a234c2b1-cde8-437f-b28b-23323be2b98d", + "uuid": "a2640dae-e87c-4a28-b4f1-481fd961dc02", "name": "API 문서 샘플 사용자" }, - "created_at": "2026-07-08", + "created_at": "2026-07-31", "abilities": { "can_create": true, "can_update": true, @@ -213,7 +213,7 @@ _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| id | integer | `14` | 기본 키 (내부 식별자) | +| id | integer | `12` | 기본 키 (내부 식별자) | | name | string | `실측 예시값` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | | description | string | `실측 예시값` | 설명 (다국어 필드는 로케일별 값 객체) | | type | string | `artisan` | 작업 유형: artisan(Artisan 커맨드), shell(쉘 명령), url(URL 호출) | @@ -230,10 +230,10 @@ _단건 응답: `data` 객체의 필드._ | last_result_label | string | `미실행` | `last_result` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | | last_duration | null | `null` | 마지막 실행 소요 시간 (초/밀리초 — 실행 이력 파생) | | extension_type | string | `core` | 이 리소스를 소유한 확장의 타입 (core/module/plugin/template) | -| extension_identifier | string | `probe_6a4dc0a862b69` | 이 리소스를 소유한 확장의 식별자 | -| creator | object | `{"uuid":"a234c2b1-cde8-437f-b28b-23323be2b98d","name":"AP…` | 생성자 정보 객체 (uuid/name/email — creator 관계 파생) | -| created_at | string | `2026-07-08 12:14:48` | 생성 일시 | -| updated_at | string | `2026-07-08 12:14:48` | 최종 수정 일시 | +| extension_identifier | string | `probe_6a71e0dc9a253` | 이 리소스를 소유한 확장의 식별자 | +| creator | object | `{"uuid":"a26219fc-94a0-4f63-9404-04c2a6ac99e4","name":"최고…` | 생성자 정보 객체 (uuid/name/email — creator 관계 파생) | +| created_at | string | `2026-08-04 21:53:48` | 생성 일시 | +| updated_at | string | `2026-08-04 21:53:48` | 최종 수정 일시 | | abilities | object | `{"can_create":true,"can_update":true,"can_delete":true,"c…` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | **응답 예시** @@ -247,7 +247,7 @@ HTTP/1.1 201 "success": true, "message": "스케줄이 생성되었습니다.", "data": { - "id": 14, + "id": 12, "name": "실측 예시값", "description": "실측 예시값", "type": "artisan", @@ -264,13 +264,13 @@ HTTP/1.1 201 "last_result_label": "미실행", "last_duration": null, "extension_type": "core", - "extension_identifier": "probe_6a4dc0a862b69", + "extension_identifier": "probe_6a71e0dc9a253", "creator": { - "uuid": "a234c2b1-cde8-437f-b28b-23323be2b98d", - "name": "API 문서 샘플 사용자" + "uuid": "a26219fc-94a0-4f63-9404-04c2a6ac99e4", + "name": "최고관리자" }, - "created_at": "2026-07-08 12:14:48", - "updated_at": "2026-07-08 12:14:48", + "created_at": "2026-08-04 21:53:48", + "updated_at": "2026-08-04 21:53:48", "abilities": { "can_create": true, "can_update": true, @@ -319,7 +319,7 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - + **응답 예시** @@ -372,7 +372,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -414,7 +414,7 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - + **응답 예시** @@ -596,14 +596,14 @@ _단건 응답: `data` 객체의 필드._ | is_active | boolean | `true` | active 여부 | | last_result | string | `success` | 마지막 실행 결과: success(성공), failed(실패), running(실행중), never(미실행) | | last_result_label | string | `성공` | `last_result` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | -| last_run_at | string | `2026-07-05T19:20:23+09:00` | last run 일시 | +| last_run_at | string | `2026-07-30T22:55:00+09:00` | last run 일시 | | last_duration | null | `null` | 마지막 실행의 소요 시간을 사람이 읽는 문자열로 포맷한 값 (예: "45초", "2분 3초" — 마지막 실행 이력의 duration 파생, 실행 이력이 없으면 null) | -| next_run_at | string | `2026-07-07T12:00:00+09:00` | next run 일시 | +| next_run_at | string | `2026-08-01T12:00:00+09:00` | next run 일시 | | extension_type | null | `null` | 확장 소유 타입: core(코어), module(모듈), plugin(플러그인), NULL(사용자 정의) | | extension_identifier | null | `null` | 확장 식별자 (예: core, sirsoft-board, sirsoft-payment) | -| creator | object | `{"uuid":"a231747f-e82e-4cf2-9ae1-a261849dce40","name":"AP…` | 생성자 정보 객체 (uuid/name/email — creator 관계 파생) | -| created_at | string | `2026-07-06 19:20:23` | 생성 일시 | -| updated_at | string | `2026-07-06 19:20:23` | 최종 수정 일시 | +| creator | object | `{"uuid":"a2640dae-e87c-4a28-b4f1-481fd961dc02","name":"AP…` | 생성자 정보 객체 (uuid/name/email — creator 관계 파생) | +| created_at | string | `2026-07-31 22:55:00` | 생성 일시 | +| updated_at | string | `2026-07-31 22:55:00` | 최종 수정 일시 | | abilities | object | `{"can_create":true,"can_update":true,"can_delete":true,"c…` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | **응답 예시** @@ -632,17 +632,17 @@ HTTP/1.1 200 "is_active": true, "last_result": "success", "last_result_label": "성공", - "last_run_at": "2026-07-07T10:41:24+09:00", + "last_run_at": "2026-07-30T22:55:00+09:00", "last_duration": null, - "next_run_at": "2026-07-08T12:00:00+09:00", + "next_run_at": "2026-08-01T12:00:00+09:00", "extension_type": null, "extension_identifier": null, "creator": { - "uuid": "a234c2b1-cde8-437f-b28b-23323be2b98d", + "uuid": "a2640dae-e87c-4a28-b4f1-481fd961dc02", "name": "API 문서 샘플 사용자" }, - "created_at": "2026-07-08 10:41:24", - "updated_at": "2026-07-08 10:41:24", + "created_at": "2026-07-31 22:55:00", + "updated_at": "2026-07-31 22:55:00", "abilities": { "can_create": true, "can_update": true, @@ -738,13 +738,13 @@ _단건 응답: `data` 객체의 필드._ | is_active | boolean | `true` | active 여부 | | last_result | string | `success` | 마지막 실행 결과: success(성공), failed(실패), running(실행중), never(미실행) | | last_result_label | string | `성공` | `last_result` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | -| last_run_at | string | `2026-07-07T10:41:24+09:00` | last run 일시 | +| last_run_at | string | `2026-07-30T22:55:00+09:00` | last run 일시 | | last_duration | null | `null` | 마지막 실행 소요 시간 (초/밀리초 — 실행 이력 파생) | | extension_type | string | `core` | 이 리소스를 소유한 확장의 타입 (core/module/plugin/template) | -| extension_identifier | string | `probe_6a4dc0a8e44c0` | 이 리소스를 소유한 확장의 식별자 | -| creator | object | `{"uuid":"a234c2b1-cde8-437f-b28b-23323be2b98d","name":"AP…` | 생성자 정보 객체 (uuid/name/email — creator 관계 파생) | -| created_at | string | `2026-07-08 10:41:24` | 생성 일시 | -| updated_at | string | `2026-07-08 12:14:48` | 최종 수정 일시 | +| extension_identifier | string | `probe_6a71e0dceb9a2` | 이 리소스를 소유한 확장의 식별자 | +| creator | object | `{"uuid":"a2640dae-e87c-4a28-b4f1-481fd961dc02","name":"AP…` | 생성자 정보 객체 (uuid/name/email — creator 관계 파생) | +| created_at | string | `2026-07-31 22:55:00` | 생성 일시 | +| updated_at | string | `2026-08-04 21:53:48` | 최종 수정 일시 | | abilities | object | `{"can_create":true,"can_update":true,"can_delete":true,"c…` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | **응답 예시** @@ -773,16 +773,16 @@ HTTP/1.1 200 "is_active": true, "last_result": "success", "last_result_label": "성공", - "last_run_at": "2026-07-07T10:41:24+09:00", + "last_run_at": "2026-07-30T22:55:00+09:00", "last_duration": null, "extension_type": "core", - "extension_identifier": "probe_6a4dc0a8e44c0", + "extension_identifier": "probe_6a71e0dceb9a2", "creator": { - "uuid": "a234c2b1-cde8-437f-b28b-23323be2b98d", + "uuid": "a2640dae-e87c-4a28-b4f1-481fd961dc02", "name": "API 문서 샘플 사용자" }, - "created_at": "2026-07-08 10:41:24", - "updated_at": "2026-07-08 12:14:48", + "created_at": "2026-07-31 22:55:00", + "updated_at": "2026-08-04 21:53:48", "abilities": { "can_create": true, "can_update": true, @@ -834,7 +834,7 @@ _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| id | integer | `15` | 기본 키 (내부 식별자) | +| id | integer | `13` | 기본 키 (내부 식별자) | | name | string | `API 문서 샘플 스케줄 (복사본)` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | | description | string | `문서 실측용 스케줄` | 설명 (다국어 필드는 로케일별 값 객체) | | type | string | `artisan` | 작업 유형: artisan(Artisan 커맨드), shell(쉘 명령), url(URL 호출) | @@ -852,8 +852,8 @@ _단건 응답: `data` 객체의 필드._ | last_duration | null | `null` | 마지막 실행 소요 시간 (초/밀리초 — 실행 이력 파생) | | extension_type | null | `null` | 이 리소스를 소유한 확장의 타입 (core/module/plugin/template) | | extension_identifier | null | `null` | 이 리소스를 소유한 확장의 식별자 | -| created_at | string | `2026-07-08 12:14:48` | 생성 일시 | -| updated_at | string | `2026-07-08 12:14:48` | 최종 수정 일시 | +| created_at | string | `2026-08-04 21:53:48` | 생성 일시 | +| updated_at | string | `2026-08-04 21:53:48` | 최종 수정 일시 | | abilities | object | `{"can_create":true,"can_update":true,"can_delete":true,"c…` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | **응답 예시** @@ -867,7 +867,7 @@ HTTP/1.1 201 "success": true, "message": "스케줄이 복제되었습니다.", "data": { - "id": 15, + "id": 13, "name": "API 문서 샘플 스케줄 (복사본)", "description": "문서 실측용 스케줄", "type": "artisan", @@ -885,8 +885,8 @@ HTTP/1.1 201 "last_duration": null, "extension_type": null, "extension_identifier": null, - "created_at": "2026-07-08 12:14:48", - "updated_at": "2026-07-08 12:14:48", + "created_at": "2026-08-04 21:53:48", + "updated_at": "2026-08-04 21:53:48", "abilities": { "can_create": true, "can_update": true, @@ -1013,23 +1013,23 @@ _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| id | integer | `7` | 기본 키 (내부 식별자) | +| id | integer | `6` | 기본 키 (내부 식별자) | | schedule_id | integer | `1` | schedule 식별자 (연관 리소스 참조) | -| started_at | string | `2026-07-08T12:14:49+09:00` | started 일시 | -| ended_at | string | `2026-07-08T12:14:49+09:00` | ended 일시 | +| started_at | string | `2026-08-04T21:53:49+09:00` | started 일시 | +| ended_at | string | `2026-08-04T21:53:49+09:00` | ended 일시 | | duration | integer | `0` | 실행 소요 시간 (초/밀리초) | | duration_formatted | null | `null` | `duration` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | | status | string | `success` | 상태 값 (도메인별 상태 집합 — 사람이 읽는 라벨은 status_label, UI 변형은 status_variant 참조) | | status_label | string | `성공` | 상태의 사람이 읽는 라벨 (상태 Enum label() 산물) | | exit_code | integer | `0` | 실행 종료 코드 (0=성공, 그 외=실패) | -| memory_usage | integer | `427352` | 실행 중 최대 메모리 사용량 (바이트) | -| memory_usage_formatted | string | `417.34 KB` | `memory_usage` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | +| memory_usage | integer | `427312` | 실행 중 최대 메모리 사용량 (바이트) | +| memory_usage_formatted | string | `417.3 KB` | `memory_usage` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | | output | string | ` INFO Application cache cleared s…` | 실행 표준 출력 내용 | | error_output | null | `null` | 실행 표준 에러 출력 내용 (없으면 빈 문자열/null) | | trigger_type | string | `manual` | 동작을 유발한 방식/주체 구분 값 | | trigger_type_label | string | `수동 실행` | `trigger_type` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | -| created_at | string | `2026-07-08 12:14:49` | 생성 일시 | -| updated_at | string | `2026-07-08 12:14:49` | 최종 수정 일시 | +| created_at | string | `2026-08-04 21:53:49` | 생성 일시 | +| updated_at | string | `2026-08-04 21:53:49` | 최종 수정 일시 | **응답 예시** @@ -1042,23 +1042,23 @@ HTTP/1.1 200 "success": true, "message": "스케줄이 실행되었습니다.", "data": { - "id": 7, + "id": 6, "schedule_id": 1, - "started_at": "2026-07-08T12:14:49+09:00", - "ended_at": "2026-07-08T12:14:49+09:00", + "started_at": "2026-08-04T21:53:49+09:00", + "ended_at": "2026-08-04T21:53:49+09:00", "duration": 0, "duration_formatted": null, "status": "success", "status_label": "성공", "exit_code": 0, - "memory_usage": 427352, - "memory_usage_formatted": "417.34 KB", + "memory_usage": 427312, + "memory_usage_formatted": "417.3 KB", "output": "\n INFO Application cache cleared successfully. \n\r\n", "error_output": null, "trigger_type": "manual", "trigger_type_label": "수동 실행", - "created_at": "2026-07-08 12:14:49", - "updated_at": "2026-07-08 12:14:49" + "created_at": "2026-08-04 21:53:49", + "updated_at": "2026-08-04 21:53:49" } } ``` diff --git a/docs/backend/api/search.md b/docs/backend/api/search.md index 1a763230..022a19dd 100644 --- a/docs/backend/api/search.md +++ b/docs/backend/api/search.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Search 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` @@ -35,8 +35,6 @@ | board_slug | query | string | 아니오 | max 100 | 검색 범위를 특정 게시판으로 한정 (게시판 모듈이 `core.search.index_validation_rules` 훅으로 추가하는 파라미터, 해당 slug의 게시판 글만 검색) | | category_id | query | integer | 아니오 | — | category 식별자 | -> 이 엔드포인트는 확장이 파라미터를 추가할 수 있습니다 (`core.search.index_validation_rules`). - **요청 예시** ```http diff --git a/docs/backend/api/seo.md b/docs/backend/api/seo.md index 70e1eadb..be34bfeb 100644 --- a/docs/backend/api/seo.md +++ b/docs/backend/api/seo.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Seo 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` @@ -106,7 +106,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -148,12 +148,12 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) -_단건 응답: `data` 객체의 필드 — `GET /api/admin/seo/sitemap/status` 와 동일한 스키마입니다._ +_단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| last_updated_at | string\|null | `2026-07-08T03:14:49+00:00` | **예약 시점 기준** 마지막 생성 일시. 이번 재생성의 결과가 아니라 직전 생성 시각이며, 잡 완료 후 갱신됩니다 (아직 한 번도 생성되지 않았으면 null) | -| progress | object\|null | `{"status":"queued","mode":"full",...}` | 재생성 진행상황. 예약 직후 `status=queued`, `mode=full` 로 즉시 기록됩니다. 필드 상세는 아래 상태 조회 API 참조 (진행 이력이 없으면 null) | +| last_updated_at | string | `2026-08-04T02:29:57+00:00` | **예약 시점 기준** 마지막 생성 일시. 이번 재생성의 결과가 아니라 직전 생성 시각이며, 잡 완료 후 갱신됩니다 (아직 한 번도 생성되지 않았으면 null) | +| progress | object | `{"status":"queued","mode":"full","started_at":"2026-08-04…` | 재생성 진행상황. 예약 직후 `status=queued`, `mode=full` 로 즉시 기록됩니다. 필드 상세는 아래 상태 조회 API 참조 (진행 이력이 없으면 null) | | realtime_enabled | boolean | `false` | 실시간 연결(WebSocket) 가능 여부. true 면 프론트가 진행상황 채널을 구독하고, false 면 상태 API 를 주기적으로 폴링합니다 | **응답 예시** @@ -167,11 +167,11 @@ HTTP/1.1 200 "success": true, "message": "Sitemap 재생성을 시작했습니다. 완료까지 시간이 걸릴 수 있습니다.", "data": { - "last_updated_at": "2026-07-08T03:14:49+00:00", + "last_updated_at": "2026-08-04T02:29:57+00:00", "progress": { "status": "queued", "mode": "full", - "started_at": "2026-07-08T05:20:00+00:00", + "started_at": "2026-08-04T12:53:51+00:00", "finished_at": null, "phase": null, "urls": 0, @@ -188,7 +188,6 @@ HTTP/1.1 200 | 상태코드 | 의미 | 발생 조건 | | --- | --- | --- | -| 400 | Bad Request | SEO 설정에서 sitemap 생성이 비활성(`seo.sitemap_enabled=false`)인 경우 (`seo.sitemap_disabled`) | | 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 | | 403 | Forbidden | 요구 권한(`core.settings.update`)이 없는 경우 | @@ -204,6 +203,7 @@ sitemap.xml 재생성을 큐에 예약합니다. 관리자 수동 재생성은 이 엔드포인트로 접수된 **관리자 수동 재생성**은 실행한 관리자의 ID 를 잡에 실어, 생성 완료 시 `sitemap_regenerated`, 실패 시 `sitemap_regenerate_failed` 알림이 **그 관리자에게만** 발송됩니다(기본 채널: 앱 내 알림). 스케줄러·리소스 변경(증분)·봇 캐시 미스로 유발된 재생성은 실행 관리자가 없어 알림을 보내지 않습니다. 알림 정의는 `config/core.php` 의 `notification_definitions` 에 있으며 관리자 알림 설정에서 채널을 조정할 수 있습니다. + ### GET /api/admin/seo/sitemap/status - **라우트명**: `api.admin.seo.sitemap.status` @@ -229,17 +229,8 @@ _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| last_updated_at | string\|null | `2026-07-08T03:14:49+00:00` | 마지막 생성 일시 (아직 한 번도 생성되지 않았으면 null) | -| progress | object\|null | `{"status":"running",...}` | 재생성 진행상황. 진행 이력이 없으면 null | -| progress.status | string | `running` | `queued`(예약) / `running`(기여자 처리 중) / `writing`(파일 작성 중) / `completed`(완료) / `failed`(실패) | -| progress.mode | string\|null | `full` | 재생성 모드 (`full` / `auto` / `incremental`) | -| progress.phase | string\|null | `sirsoft-board` | 현재 처리 중인 기여자 식별자 (running 단계에서만) | -| progress.urls | integer | `12000` | 누적 처리 URL 수 | -| progress.url_count | integer\|null | `5000` | 완료 시 총 URL 수 | -| progress.child_count | integer\|null | `2` | 완료 시 분할 파일 수 | -| progress.started_at | string\|null | `2026-07-08T05:20:00+00:00` | 예약 시각 | -| progress.finished_at | string\|null | `2026-07-08T05:21:30+00:00` | 완료/실패 시각 | -| progress.message | string\|null | `null` | 실패 시 오류 메시지 | +| last_updated_at | string | `2026-08-04T12:53:51+00:00` | 마지막 생성 일시 (아직 한 번도 생성되지 않았으면 null) | +| progress | object | `{"status":"queued","mode":"full","started_at":"2026-08-04…` | 재생성 진행상황. 진행 이력이 없으면 null | | realtime_enabled | boolean | `false` | 실시간 연결(WebSocket) 가능 여부. true 면 프론트가 `core.admin.seo.sitemap` 채널을 구독하고, false 면 이 API 를 주기적으로 폴링합니다 | **응답 예시** @@ -251,16 +242,16 @@ HTTP/1.1 200 ```json { "success": true, - "message": "성공적으로 처리되었습니다.", + "message": "messages.success", "data": { - "last_updated_at": "2026-07-08T03:14:49+00:00", + "last_updated_at": "2026-08-04T12:53:51+00:00", "progress": { - "status": "running", + "status": "queued", "mode": "full", - "started_at": "2026-07-08T05:20:00+00:00", + "started_at": "2026-08-04T12:53:51+00:00", "finished_at": null, - "phase": "sirsoft-board", - "urls": 12000, + "phase": null, + "urls": 0, "url_count": null, "child_count": null, "message": null @@ -374,7 +365,7 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - + **응답 예시** diff --git a/docs/backend/api/settings.md b/docs/backend/api/settings.md index f9ee3392..e1530ecc 100644 --- a/docs/backend/api/settings.md +++ b/docs/backend/api/settings.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Settings 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` @@ -42,20 +42,21 @@ _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| general | object | `{"site_name":"Test Site","site_url":"https:\/\/test.examp…` | 일반 탭 설정 그룹 (사이트명·사이트 URL·설명·관리자 이메일·타임존·기본 언어·통화·점검 모드·사이트 로고 첨부). site_logo 는 SettingsService 가 별도 주입한 첨부 정보 | -| security | object | `{"force_https":true,"login_attempt_enabled":true,"auth_to…` | 보안 탭 설정 그룹 (HTTPS 강제·로그인 시도 제한 사용·인증 토큰 유지시간(분, 0=무한)·최대 로그인 시도 횟수·잠금 시간·내부 네트워크 주소 호출 허용). `allow_internal_outbound_urls`(boolean, 기본 false): 서버가 대신 호출하는 outbound 요청(예약 작업 URL 호출, 외부 API 연동)에서 사설 IP·`localhost` 등 내부 주소를 허용할지 여부 — 켜면 사내 서버 호출이 가능해지지만 서버가 내부망으로 요청을 보낼 수 있게 되므로 기본은 차단입니다. 언어팩 URL 설치는 원격 코드를 내려받으므로 이 설정과 무관하게 항상 내부 주소를 거부합니다 | +| general | object | `{"site_name":"그누보드7","site_url":"https:\/\/g7_2.dev","sit…` | 일반 탭 설정 그룹 (사이트명·사이트 URL·설명·관리자 이메일·타임존·기본 언어·통화·점검 모드·사이트 로고 첨부). site_logo 는 SettingsService 가 별도 주입한 첨부 정보 | +| security | object | `{"force_https":false,"login_attempt_enabled":true,"auth_t…` | 보안 탭 설정 그룹 (HTTPS 강제·로그인 시도 제한 사용·인증 토큰 유지시간(분, 0=무한)·최대 로그인 시도 횟수·잠금 시간·내부 네트워크 주소 호출 허용). `allow_internal_outbound_urls`(boolean, 기본 false): 서버가 대신 호출하는 outbound 요청(예약 작업 URL 호출, 외부 API 연동)에서 사설 IP·`localhost` 등 내부 주소를 허용할지 여부 — 켜면 사내 서버 호출이 가능해지지만 서버가 내부망으로 요청을 보낼 수 있게 되므로 기본은 차단입니다. 언어팩 URL 설치는 원격 코드를 내려받으므로 이 설정과 무관하게 항상 내부 주소를 거부합니다 | | mail | object | `{"mailer":"smtp","host":"","port":587,"username":"","pass…` | 메일 탭 설정 그룹 (메일러 종류(smtp/mailgun/ses)·SMTP 호스트/포트/인증 정보·암호화 방식·발신자 주소/이름·Mailgun/SES 자격 정보) | | upload | object | `{"max_file_size":10,"allowed_extensions":["jpg","jpeg","p…` | 업로드 탭 설정 그룹 (최대 파일 크기(MB)·허용 확장자 목록·이미지 최대 가로/세로·이미지 품질) | -| seo | object | `{"meta_title_suffix":"","meta_description":"","meta_keywo…` | SEO 탭 설정 그룹 (메타 타이틀 접미사·메타 설명/키워드·검색엔진 인증 코드·봇 감지·OG/Twitter 기본값·SEO 캐시·사이트맵·생성기 설정) | +| seo | object | `{"meta_title_suffix":null,"meta_description":null,"meta_k…` | SEO 탭 설정 그룹 (메타 타이틀 접미사·메타 설명/키워드·검색엔진 인증 코드·봇 감지·OG/Twitter 기본값·SEO 캐시·사이트맵·생성기 설정) | | advanced | object | `{"cache_enabled":true,"cache_default_ttl":86400,"layout_c…` | 고급 탭 설정 그룹 (캐시·디버그·코어 업데이트·GeoIP 설정을 한 탭으로 합친 병합 뷰). cache/debug 카테고리 값이 함께 노출됨 | | cache | object | `{"cache_enabled":true,"cache_default_ttl":86400,"layout_c…` | 캐시 원본 카테고리 (전역 캐시 사용·기본 TTL·레이아웃/통계/SEO 캐시 사용 및 TTL). advanced 탭에 병합되면서 개별 접근용으로 별도 노출된 파생 뷰 | -| debug | object | `{"debug_mode":true,"sql_query_log":false,"log_level":"err…` | 디버그 원본 카테고리 (디버그 모드·SQL 쿼리 로그·로그 레벨). advanced 탭에 병합되면서 개별 접근용으로 별도 노출된 파생 뷰 | +| debug | object | `{"debug_mode":false,"sql_query_log":false,"log_level":"er…` | 디버그 원본 카테고리 (디버그 모드·SQL 쿼리 로그·로그 레벨). advanced 탭에 병합되면서 개별 접근용으로 별도 노출된 파생 뷰 | | drivers | object | `{"storage_driver":"local","s3_bucket":null,"s3_region":"a…` | 드라이버 탭 설정 그룹 (스토리지/캐시/세션/큐/로그 드라이버 선택 + S3·Redis·Memcached·WebSocket·검색엔진 접속 파라미터) | -| core_update | object | `{"core_update_github_url":"https:\/\/github.com\/custom\/…` | 코어 업데이트 원본 카테고리 (코어 업데이트를 받아올 GitHub 저장소 URL·비공개 저장소 접근용 토큰). advanced 탭에 병합된 파생 뷰 | +| core_update | object | `{"core_update_github_url":"https:\/\/github.com\/gnuboard…` | 코어 업데이트 원본 카테고리 (코어 업데이트를 받아올 GitHub 저장소 URL·비공개 저장소 접근용 토큰). advanced 탭에 병합된 파생 뷰 | | geoip | object | `{"geoip_enabled":false,"geoip_license_key":null,"geoip_au…` | GeoIP 원본 카테고리 (GeoIP 사용 여부·MaxMind 라이선스 키·DB 자동 갱신 사용). advanced 탭에 병합된 파생 뷰 | | notifications | object | `{"channels":[{"id":"mail","is_active":true,"sort_order":1…` | 알림 탭 설정 그룹. channels 는 알림 채널 목록으로 각 원소가 id(채널 식별자)·is_active(활성 여부)·sort_order(표시 순서)를 가짐 | | identity | object | `{"default_provider":"g7:core.mail","purpose_providers":{"…` | 본인인증(IDV) 탭 설정 그룹 (기본 provider·목적별 provider 매핑(purpose_providers)·챌린지 유효시간(분)·최대 시도 횟수) | | available_drivers | object | `{"storage":[{"id":"local","label":{"ko":"로컬","en":"Local"…` | 드라이버 선택지 카탈로그 (DriverRegistryService 산물). 종류별(storage/cache/session/queue 등) 선택 가능한 드라이버 목록을 id/다국어 label 형태로 제공 | +| _meta | object | `{"limits":{"upload_max_file_size_min":1,"upload_max_file_…` | | | abilities | object | `{"can_update":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | **응답 예시** @@ -70,25 +71,20 @@ HTTP/1.1 200 "message": "설정을 성공적으로 가져왔습니다.", "data": { "general": { - "site_name": "Test Site", - "site_url": "https://test.example.com", - "site_description": "", - "admin_email": "admin@example.com", + "site_name": "그누보드7", + "site_url": "https://api.example.com", + "site_description": null, + "admin_email": "heuristing@gmail.com", "timezone": "Asia/Seoul", - "language": "ko", - "currency": "KRW", - "maintenance_mode": false, - "site_logo": [] + "...": "(5개 키 생략, 총 10개)" }, "security": { - "force_https": true, + "force_https": false, "login_attempt_enabled": true, "auth_token_lifetime": "{MASKED}", "max_login_attempts": 5, - "login_lockout_time": 30, - "two_factor_auth": false, - "password_min_length": "{MASKED}", - "require_password_special_char": "{MASKED}" + "login_lockout_time": 5, + "...": "(4개 키 생략, 총 9개)" }, "mail": { "mailer": "smtp", @@ -96,15 +92,7 @@ HTTP/1.1 200 "port": 587, "username": "", "password": "{MASKED}", - "encryption": "tls", - "mailgun_domain": "", - "mailgun_secret": "{MASKED}", - "mailgun_endpoint": "api.mailgun.net", - "ses_key": "", - "ses_secret": "{MASKED}", - "ses_region": "ap-northeast-2", - "from_address": "heuristing@gmail.com", - "from_name": "그누보드7" + "...": "(9개 키 생략, 총 14개)" }, "upload": { "max_file_size": 10, @@ -114,313 +102,21 @@ HTTP/1.1 200 "png", "gif", "webp", - "pdf", - "doc", - "docx", - "xls", - "xlsx", - "zip" + "... (총 11건 중 5건 표시)" ], - "image_max_width": 2000, - "image_max_height": 2000, + "image_max_width": "800", + "image_max_height": "600", "image_quality": 85 }, "seo": { - "meta_title_suffix": "", - "meta_description": "", - "meta_keywords": "", - "google_analytics_id": "", - "google_site_verification": "", - "naver_site_verification": "", - "bot_user_agents": [ - "kakaotalk-scrap", - "Meta-ExternalAgent", - "ChatGPT-User" - ], - "bot_detection_enabled": true, - "bot_detection_library_enabled": true, - "og_default_site_name": "", - "og_image_default_width": 1200, - "og_image_default_height": 630, - "twitter_default_card": "summary_large_image", - "twitter_default_site": "", - "cache_enabled": true, - "cache_ttl": 7200, - "sitemap_enabled": true, - "sitemap_cache_ttl": 86400, - "sitemap_urls_per_file": 50000, - "sitemap_gzip": false, - "sitemap_serve_stale_on_miss": true, - "sitemap_max_urls_per_contributor": 0, - "sitemap_hreflang_enabled": true, - "sitemap_schedule": "daily", - "sitemap_schedule_time": "02:00", - "sitemap_last_updated_at": "2026-07-08T03:14:49+00:00", - "generator_enabled": true, - "generator_content": "" + "meta_title_suffix": null, + "meta_description": null, + "meta_keywords": null, + "google_analytics_id": null, + "google_site_verification": null, + "...": "(24개 키 생략, 총 29개)" }, - "advanced": { - "cache_enabled": true, - "cache_default_ttl": 86400, - "layout_cache_enabled": true, - "layout_cache_ttl": 3600, - "seo_cache_enabled": true, - "seo_cache_ttl": 7200, - "seo_sitemap_cache_ttl": 86400, - "stats_cache_enabled": true, - "stats_cache_ttl": 1800, - "notification_cache_ttl": 3600, - "extension_status_cache_ttl": 86400, - "geoip_cache_enabled": true, - "geoip_cache_ttl": 86400, - "version_check_cache_ttl": 3600, - "debug_mode": true, - "sql_query_log": false, - "log_level": "error", - "core_update_github_url": "https://github.com/custom/repo", - "core_update_github_token": "{MASKED}", - "geoip_enabled": false, - "geoip_license_key": null, - "geoip_auto_update_enabled": true, - "geoip_last_updated_at": "" - }, - "cache": { - "cache_enabled": true, - "cache_default_ttl": 86400, - "layout_cache_enabled": true, - "layout_cache_ttl": 3600, - "seo_cache_enabled": true, - "seo_cache_ttl": 7200, - "seo_sitemap_cache_ttl": 86400, - "stats_cache_enabled": true, - "stats_cache_ttl": 1800, - "notification_cache_ttl": 3600, - "extension_status_cache_ttl": 86400, - "geoip_cache_enabled": true, - "geoip_cache_ttl": 86400, - "version_check_cache_ttl": 3600 - }, - "debug": { - "debug_mode": true, - "sql_query_log": false, - "log_level": "error" - }, - "drivers": { - "storage_driver": "local", - "s3_bucket": null, - "s3_region": "ap-northeast-2", - "s3_access_key": null, - "s3_secret_key": null, - "s3_url": null, - "cache_driver": "file", - "redis_host": "127.0.0.1", - "redis_port": 6379, - "redis_password": null, - "redis_database": "1", - "memcached_host": "127.0.0.1", - "memcached_port": 11211, - "session_driver": "file", - "session_lifetime": 120, - "queue_driver": "sync", - "log_driver": "daily", - "log_level": "error", - "log_days": 14, - "websocket_enabled": true, - "websocket_app_id": "test-app-id", - "websocket_app_key": "{MASKED}", - "websocket_app_secret": "{MASKED}", - "websocket_host": "localhost", - "websocket_port": 8080, - "websocket_scheme": "https", - "websocket_verify_ssl": true, - "websocket_server_host": "127.0.0.1", - "websocket_server_port": 8080, - "websocket_server_scheme": "http", - "search_engine_driver": "mysql-fulltext" - }, - "core_update": { - "core_update_github_url": "https://github.com/custom/repo", - "core_update_github_token": "{MASKED}" - }, - "geoip": { - "geoip_enabled": false, - "geoip_license_key": null, - "geoip_auto_update_enabled": true, - "geoip_last_updated_at": "" - }, - "notifications": { - "channels": [ - { - "id": "mail", - "is_active": true, - "sort_order": 1 - }, - { - "id": "database", - "is_active": true, - "sort_order": 2 - } - ] - }, - "identity": { - "default_provider": "g7:core.mail", - "purpose_providers": { - "signup": null, - "password_reset": null, - "inicis": { - "adult_verification": "inicis" - } - }, - "challenge_ttl_minutes": 15, - "max_attempts": 5 - }, - "available_drivers": { - "storage": [ - { - "id": "local", - "label": { - "ko": "로컬", - "en": "Local", - "fr": "로컬" - } - }, - { - "id": "s3", - "label": { - "ko": "Amazon S3", - "en": "Amazon S3", - "fr": "Amazon S3" - } - } - ], - "cache": [ - { - "id": "file", - "label": { - "ko": "파일", - "en": "File", - "fr": "파일" - } - }, - { - "id": "redis", - "label": { - "ko": "Redis", - "en": "Redis", - "fr": "Redis" - } - } - ], - "session": [ - { - "id": "file", - "label": { - "ko": "파일", - "en": "File", - "fr": "파일" - } - }, - { - "id": "database", - "label": { - "ko": "데이터베이스", - "en": "Database", - "fr": "데이터베이스" - } - }, - { - "id": "redis", - "label": { - "ko": "Redis", - "en": "Redis", - "fr": "Redis" - } - } - ], - "queue": [ - { - "id": "sync", - "label": { - "ko": "동기", - "en": "Sync", - "fr": "동기" - } - }, - { - "id": "database", - "label": { - "ko": "데이터베이스", - "en": "Database", - "fr": "데이터베이스" - } - }, - { - "id": "redis", - "label": { - "ko": "Redis", - "en": "Redis", - "fr": "Redis" - } - } - ], - "log": [ - { - "id": "single", - "label": { - "ko": "단일 파일", - "en": "Single File", - "fr": "단일 파일" - } - }, - { - "id": "daily", - "label": { - "ko": "일별 파일", - "en": "Daily File", - "fr": "일별 파일" - } - } - ], - "websocket": [ - { - "id": "reverb", - "label": { - "ko": "Laravel Reverb", - "en": "Laravel Reverb", - "fr": "Laravel Reverb" - } - } - ], - "mail": [ - { - "id": "smtp", - "label": { - "ko": "SMTP", - "en": "SMTP", - "fr": "SMTP" - } - }, - { - "id": "mailgun", - "label": { - "ko": "Mailgun", - "en": "Mailgun", - "fr": "Mailgun" - } - }, - { - "id": "ses", - "label": { - "ko": "SES (Amazon)", - "en": "SES (Amazon)", - "fr": "SES (Amazon)" - } - } - ] - }, - "abilities": { - "can_update": true - } + "...": "(11개 키 생략, 총 16개)" } } ``` @@ -505,7 +201,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -551,7 +247,7 @@ _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| app_key | string | `base64:97gZH*************************…` | 현재 애플리케이션 키(`APP_KEY`)를 마스킹한 문자열. 앞부분 일부만 노출하고 나머지는 별표로 가려 전체 원문은 반환하지 않음 | +| app_key | string | `base64:YlAis*************************…` | 현재 애플리케이션 키(`APP_KEY`)를 마스킹한 문자열. 앞부분 일부만 노출하고 나머지는 별표로 가려 전체 원문은 반환하지 않음 | **응답 예시** @@ -604,7 +300,7 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - + **응답 예시** @@ -645,7 +341,7 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - + **응답 예시** @@ -686,7 +382,7 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - + **응답 예시** @@ -727,7 +423,7 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - + **응답 예시** @@ -830,7 +526,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -881,7 +577,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -928,19 +624,20 @@ _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | | os_info | string | `Windows NT 10.0` | 운영체제 종류와 버전 (`php_uname` 산물). probe 차단 시 "알 수 없음" 폴백 | -| web_server | string | `Apache/2.4.62 (Win64) OpenSSL/3.0.16 …` | 웹서버 소프트웨어 식별 문자열 (`$_SERVER['SERVER_SOFTWARE']`) | +| web_server | string | `nginx/1.27.3` | 웹서버 소프트웨어 식별 문자열 (`$_SERVER['SERVER_SOFTWARE']`) | | php_version | string | `8.3.26` | 실행 중인 PHP 버전 (`PHP_VERSION`) | | mysql_version | string | `Mysql 8.4.3` | 연결된 데이터베이스 서버 종류와 버전 (DB 조회 산물). probe 실패 시 "알 수 없음" 폴백 | -| g7_version | string | `7.0.1` | G7 코어 버전 (`config('app.version')`) | +| g7_version | string | `7.0.6` | G7 코어 버전 (`config('app.version')`) | | g7_release_year | string | `2026` | G7 릴리즈 연도 (`config('app.release_year')`, 저작권 표기 등에 사용) | -| laravel_version | string | `12.54.1` | 프레임워크 Laravel 버전 (`app()->version()`) | +| laravel_version | string | `12.62.0` | 프레임워크 Laravel 버전 (`app()->version()`) | | environment | string | `local` | 현재 실행 환경 (`app()->environment()` — local/production/testing 등) | | cpu_info | string | `Intel(R) Core(TM) Ultra 5 225H` | CPU 모델명 (OS별 시스템 probe 산물). 수집 실패 시 "알 수 없음" 폴백 | -| memory_usage | object | `{"total":"31.49 GB","used":"29.63 GB","free":"1.86 GB","p…` | 물리 메모리 사용량. total/used/free 는 사람이 읽기 쉬운 단위 문자열, percentage 는 사용률(%) | -| disk_usage | object | `{"total":"474.72 GB","used":"360.2 GB","free":"114.51 GB"…` | 설치 볼륨 디스크 사용량. total/used/free 단위 문자열 + percentage 사용률(%) | +| memory_usage | object | `{"total":"31.49 GB","used":"24.31 GB","free":"7.18 GB","p…` | 물리 메모리 사용량. total/used/free 는 사람이 읽기 쉬운 단위 문자열, percentage 는 사용률(%) | +| disk_usage | object | `{"total":"474.72 GB","used":"408.15 GB","free":"66.57 GB"…` | 설치 볼륨 디스크 사용량. total/used/free 단위 문자열 + percentage 사용률(%) | | php_memory_limit | string | `512M` | PHP `memory_limit` ini 값 | | max_execution_time | string | `36000초` | PHP `max_execution_time` ini 값 (초 단위 접미사 부착) | | upload_max_filesize | string | `2G` | PHP `upload_max_filesize` ini 값 | +| opcache | object | `{"loaded":true,"enabled":true}` | | | install_path | string | `C:\Users\HeuJung\htdocs\g7_2` | 애플리케이션 설치 루트 경로 (`base_path()`) | | config_path | string | `C:\Users\HeuJung\htdocs\g7_2\storage\…` | 설정 파일 저장 경로 (`storage/app/settings`) | | log_path | string | `C:\Users\HeuJung\htdocs\g7_2\storage\…` | 로그 파일 저장 경로 (`storage/logs`) | @@ -948,7 +645,7 @@ _단건 응답: `data` 객체의 필드._ | php_extensions | object | `{"required":{"openssl":true,"pdo":true,"mbstring":true,"t…` | PHP 확장 로드 상태. required(필수)·optional(선택) 두 그룹으로 나뉘며 각 확장명→로드 여부(bool) 매핑 | | database_config | object | `{"has_read_write_split":false,"write":{"host":"localhost"…` | DB 연결 구성 요약. has_read_write_split(읽기/쓰기 분리 여부)·write(쓰기 연결 정보)·read(읽기 replica 목록, write 와 동일하면 제외) | | timezone | string | `UTC` | 애플리케이션 기본 타임존 (`config('app.timezone')`) | -| server_time | string | `2026-07-07 05:08:58` | 서버 현재 시각 (Y-m-d H:i:s) | +| server_time | string | `2026-08-04 12:53:55` | 서버 현재 시각 (Y-m-d H:i:s) | **응답 예시** @@ -962,29 +659,33 @@ HTTP/1.1 200 "message": "성공적으로 처리되었습니다.", "data": { "os_info": "Windows NT 10.0", - "web_server": "Apache/2.4.62 (Win64) OpenSSL/3.0.16 PHP/8.3.26", + "web_server": "nginx/1.27.3", "php_version": "8.3.26", "mysql_version": "Mysql 8.4.3", - "g7_version": "7.0.1", + "g7_version": "7.0.6", "g7_release_year": "2026", - "laravel_version": "12.54.1", + "laravel_version": "12.62.0", "environment": "local", "cpu_info": "Intel(R) Core(TM) Ultra 5 225H", "memory_usage": { "total": "31.49 GB", - "used": "26.92 GB", - "free": "4.57 GB", - "percentage": 85.49 + "used": "24.31 GB", + "free": "7.18 GB", + "percentage": 77.2 }, "disk_usage": { "total": "474.72 GB", - "used": "362.59 GB", - "free": "112.12 GB", - "percentage": 76.38 + "used": "408.15 GB", + "free": "66.57 GB", + "percentage": 85.98 }, "php_memory_limit": "512M", "max_execution_time": "36000초", "upload_max_filesize": "2G", + "opcache": { + "loaded": true, + "enabled": true + }, "install_path": "C:\\Users\\HeuJung\\htdocs\\g7_2", "config_path": "C:\\Users\\HeuJung\\htdocs\\g7_2\\storage\\app/settings", "log_path": "C:\\Users\\HeuJung\\htdocs\\g7_2\\storage\\logs", @@ -1025,7 +726,7 @@ HTTP/1.1 200 "read": [] }, "timezone": "UTC", - "server_time": "2026-07-08 03:14:54" + "server_time": "2026-08-04 12:53:55" } } ``` @@ -1112,7 +813,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -1191,7 +892,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -1287,7 +988,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** diff --git a/docs/backend/api/system.md b/docs/backend/api/system.md new file mode 100644 index 00000000..a139aaf0 --- /dev/null +++ b/docs/backend/api/system.md @@ -0,0 +1,88 @@ +# System API 레퍼런스 + +> **소유**: 코어 · **생성**: `php artisan api:docgen` (실측 기반). @generated 블록은 재생성 시 갱신되며, 사람이 작성한 설명은 보존됩니다. + +--- + +## TL;DR (5초 요약) + +```text +1. 이 문서는 실제 API 호출로 실측한 System 엔드포인트 레퍼런스입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 +4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 +5. 설명(TODO) 칸은 사람이 채웁니다 +``` + +--- + + +### GET /api/system/asset-probe + +- **라우트명**: `api.public.system.asset-probe.extensionless` +- **컨트롤러**: `App\Http\Controllers\Api\Public\AssetProbeController@probe` +- **인증/권한**: 공개 (인증 불필요) + +**요청 파라미터** + +_요청 파라미터 없음._ + +**요청 예시** + +```http +GET /api/system/asset-probe HTTP/1.1 +Host: api.example.com +Accept: application/json +``` + +**응답 필드** (`data` 내부) + + + +**응답 예시** + + + +**에러 응답** + +_대표 에러 없음 (공개 조회). _ + + + +**설명** + + +### GET /api/system/asset-probe.js + +- **라우트명**: `api.public.system.asset-probe` +- **컨트롤러**: `App\Http\Controllers\Api\Public\AssetProbeController@probe` +- **인증/권한**: 공개 (인증 불필요) + +**요청 파라미터** + +_요청 파라미터 없음._ + +**요청 예시** + +```http +GET /api/system/asset-probe.js HTTP/1.1 +Host: api.example.com +Accept: application/json +``` + +**응답 필드** (`data` 내부) + + + +**응답 예시** + + + +**에러 응답** + +_대표 에러 없음 (공개 조회). _ + + + +**설명** + diff --git a/docs/backend/api/templates.md b/docs/backend/api/templates.md index 90df19cc..d15efc2f 100644 --- a/docs/backend/api/templates.md +++ b/docs/backend/api/templates.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Templates 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` @@ -55,7 +55,7 @@ _목록 응답: `data.data[]` 배열 항목의 필드 + `data.pagination`._ | identifier | string | `sirsoft-admin_basic` | 템플릿 고유 식별자 (vendor-name 형식, 예: sirsoft-admin_basic) | | vendor | string | `sirsoft` | 벤더/개발자명 (예: sirsoft) | | name | string | `Admin Basic` | 템플릿 이름 (다국어 JSON) | -| version | string | `1.0.0` | 템플릿 버전 (예: 1.0.0) | +| version | string | `1.0.4` | 템플릿 버전 (예: 1.0.0) | | type | string | `admin` | 템플릿 타입 (admin: 관리자용, user: 사용자용) | | status | string | `active` | 상태 (active: 활성화, inactive: 비활성화, installing: 설치 중, uninstalling: 제거 중, updating: 업데이트 중) | | description | string | `그누보드7 기본 관리자 템플릿` | 템플릿 설명 (다국어 JSON) | @@ -63,8 +63,8 @@ _목록 응답: `data.data[]` 배열 항목의 필드 + `data.pagination`._ | dependencies_met | boolean | `true` | 의존하는 모듈/플러그인이 모두 설치·활성 상태로 충족되었는지 여부 (activate 선행 검사 파생) | | update_available | boolean | `false` | 최신 버전 대비 업데이트 가능 여부 | | update_source | null | `null` | 업데이트 감지 출처 (github, bundled 등) | -| latest_version | string | `1.0.0` | 감지된 최신 배포 버전 | -| file_version | string | `1.0.0` | 설치된 파일의 manifest 버전 | +| latest_version | string | `1.0.4` | 감지된 최신 배포 버전 | +| file_version | string | `1.0.4` | 설치된 파일의 manifest 버전 | | github_url | string | `https://github.com/gnuboard/g7-templa…` | GitHub 저장소 URL | | github_changelog_url | string | `https://github.com/gnuboard/g7-templa…` | GitHub 변경 내역 URL | | is_pending | boolean | `false` | _pending 대기소에 있어 설치 대기 중인지 여부 | @@ -90,9 +90,9 @@ HTTP/1.1 200 "identifier": "sirsoft-admin_basic", "vendor": "sirsoft", "name": "Admin Basic", - "version": "1.0.0", + "version": "1.0.4", "type": "admin", - "status": "uninstalled", + "status": "active", "description": "그누보드7 기본 관리자 템플릿", "dependencies": { "modules": [], @@ -101,10 +101,10 @@ HTTP/1.1 200 "dependencies_met": true, "update_available": false, "update_source": null, - "latest_version": null, - "file_version": null, - "github_url": null, - "github_changelog_url": null, + "latest_version": "1.0.4", + "file_version": "1.0.4", + "github_url": "https://github.com/gnuboard/g7-template-sirsoft-admin_basic", + "github_changelog_url": "https://github.com/gnuboard/g7-template-sirsoft-admin_basic/releases", "is_pending": false, "is_bundled": false, "deactivated_reason": null, @@ -121,9 +121,9 @@ HTTP/1.1 200 "identifier": "sirsoft-basic", "vendor": "sirsoft", "name": "Basic", - "version": "1.0.0", + "version": "1.0.4", "type": "user", - "status": "uninstalled", + "status": "active", "description": "그누보드7 기본 사용자 템플릿", "dependencies": { "modules": [ @@ -146,18 +146,18 @@ HTTP/1.1 200 "plugins": [ { "identifier": "sirsoft-daum_postcode", - "name": "sirsoft-daum_postcode", + "name": "Daum 우편번호", "type": "plugin" } ] }, - "dependencies_met": false, + "dependencies_met": true, "update_available": false, "update_source": null, - "latest_version": null, - "file_version": null, - "github_url": null, - "github_changelog_url": null, + "latest_version": "1.0.4", + "file_version": "1.0.4", + "github_url": "https://github.com/gnuboard/g7-template-sirsoft-basic", + "github_changelog_url": "https://github.com/gnuboard/g7-template-sirsoft-basic/releases", "is_pending": false, "is_bundled": false, "deactivated_reason": null, @@ -179,7 +179,7 @@ HTTP/1.1 200 }, "meta": { "total_templates": 2, - "active_templates": 0, + "active_templates": 2, "admin_templates": 1, "user_templates": 1, "installed_templates": 2 @@ -238,7 +238,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -278,7 +278,7 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - + **응답 예시** @@ -326,7 +326,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -383,7 +383,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -435,7 +435,7 @@ Content-Type: application/octet-stream **응답 필드** (`data` 내부) - + **응답 예시** @@ -484,7 +484,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -526,7 +526,7 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - + **응답 예시** @@ -576,7 +576,7 @@ Content-Type: application/octet-stream **응답 필드** (`data` 내부) - + **응답 예시** @@ -625,7 +625,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -670,7 +670,7 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - + **응답 예시** @@ -704,12 +704,10 @@ Authorization: Bearer {YOUR_TOKEN} | from_version | query | string | 아니오 | — | 시작 버전 (범위 하한) | | to_version | query | string | 아니오 | — | 대상 버전 (범위 상한) | -> 이 엔드포인트는 확장이 파라미터를 추가할 수 있습니다 (`core.extension.changelog_validation_rules`). - **요청 예시** ```http -GET /api/admin/templates/{identifier}/changelog?source=active&from_version=%EC%98%88%EC%8B%9C%EA%B0%92&to_version=%EC%98%88%EC%8B%9C%EA%B0%92 HTTP/1.1 +GET /api/admin/templates/sirsoft-admin_basic/changelog?source=active&from_version=%EC%98%88%EC%8B%9C%EA%B0%92&to_version=%EC%98%88%EC%8B%9C%EA%B0%92 HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -717,11 +715,104 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - +_단건 응답: `data` 객체의 필드._ + +| 필드 | 타입 | 실측 예시값 | 용도/설명 | +| --- | --- | --- | --- | +| changelog | array | `[{"version":"1.0.4","date":"2026-07-17","categories":[{"n…` | 변경 이력 텍스트 (원격/파일 CHANGELOG 본문) | **응답 예시** - +```http +HTTP/1.1 200 +``` + +```json +{ + "success": true, + "message": "template.fetch_success", + "data": { + "changelog": [ + { + "version": "1.0.4", + "date": "2026-07-17", + "categories": [ + { + "name": "Added", + "items": [ + "회원 상세에 계정 잠금 상태 표시와 잠금 해제 버튼을 추가했습니다. 로그인 실패가 누적돼 잠긴 계정, 특히 무기한 잠긴 계정을 관리자가 이 화면에서 바로 풀 수 있습니다. (#81 @jiwonpapa 님께서 제보해주셨습니다.)", + "환경설정 > 보안에 비밀번호 최소 길이·특수문자 필수 항목을 추가하고, 로그인 시도 제한 항목에 IP 단위 제한 계산 방식 안내를 덧붙였습니다. (#81 @jiwonpapa 님께서 제보해주셨습니다.)", + "일반 설정에 에셋 파일 서빙 방식 항목을 추가했습니다. 정적 파일 최적화 규칙(nginx `location ~* \\.(js|css|json|...)$` 블록 등)이 확장자 주소를 가로채 화면이 뜨지 않을 때 확장자 없는 주소로 전환할 수 있습니다. \"자동 감지\" 버튼을 누르면 판정 결과가 항목 아래에 인라인으로 표시되며, 확장자를 가로채는 환경이면 원인이 … (생략)", + "... (총 12건 중 3건 표시)" + ] + }, + { + "name": "Changed", + "items": [ + "SEO 설정의 \"지금 생성\" 버튼이 sitemap 생성을 예약하고 바로 응답합니다. 사이트 규모가 커도 화면이 멈추지 않으며, 생성 중에는 버튼이 비활성화되고 진행 상황이 화면에 표시됩니다.", + "총 건수를 정확히 셀 수 없을 만큼 기록이 많은 관리자 목록에서도 페이지 이동 버튼이 사라지지 않습니다. 마지막 페이지로 바로 뛰는 버튼만 이때 감춰집니다.", + "목록의 총 건수 정확도 표시를 사용하기 위해 이 템플릿은 이제 코어 7.0.6 이상이 필요합니다." + ] + }, + { + "name": "Fixed", + "items": [ + "활동 로그 목록에서 이전·다음 버튼이 모두 눌리지 않아 첫 페이지에 갇히던 문제를 수정했습니다.", + "활동 로그의 총 건수가 끝까지 세지 못한 값인데도 정확한 숫자처럼 표시되던 문제를 수정했습니다. 이제 그 경우 「N건 이상」으로 표시되며, 글 번호도 「-」로 표시됩니다.", + "모듈·플러그인·템플릿 관리 화면에서 '업데이트 확인'을 눌러 업데이트가 발견돼도 항상 \"모두 최신 상태입니다\"라고 안내되던 문제를 수정했습니다.", + "... (총 28건 중 3건 표시)" + ] + }, + "... (총 5건 중 3건 표시)" + ] + }, + { + "version": "1.0.3", + "date": "2026-07-14", + "categories": [ + { + "name": "Fixed", + "items": [ + "권한 관리 화면의 새로고침 버튼이 다른 관리자 화면의 새로고침 버튼과 모양이 달랐던 문제를 수정했습니다.", + "환경설정 화면의 탭 줄과 게시판 설정 화면의 상단 고정 영역이 화면 여백과 어긋나 가로로 밀려 보일 수 있던 문제를 정리했습니다." + ] + } + ] + }, + { + "version": "1.0.2", + "date": "2026-07-09", + "categories": [ + { + "name": "Added", + "items": [ + "환경설정 > 보안 화면에 \"내부 네트워크 주소 호출 허용\" 항목이 추가되었습니다. 예약 작업의 주소 호출이나 외부 API 연동에서 사내 서버 주소를 사용해야 할 때 켜면 됩니다. 기본값은 꺼짐입니다.", + "레이아웃 편집기에서 관리자 기본 레이아웃을 편집할 때 \"모바일 메뉴 펼침\" 상태를 선택할 수 있습니다 — 모바일 메뉴 안의 언어·통화·배송국가 버튼을 캔버스에서 바로 편집할 수 있습니다." + ] + }, + { + "name": "Changed", + "items": [ + "모바일에서 언어·통화·배송국가 선택을 관리자 메뉴 안으로 옮기고, 사용자 화면과 동일하게 가로로 나열된 버튼 형태로 통일했습니다. 상단 헤더에는 테마 전환과 알림만 남습니다.", + "모바일 메뉴 안의 언어와 통화·배송국가가 각각 접었다 펼치는 항목으로 바뀌어, 메뉴를 열었을 때 곧바로 관리자 메뉴가 보입니다. 접혀 있을 때도 현재 선택된 언어와 통화·배송국가가 표시되므로 펼치지 않아도 확인할 수 있으며, 두 항목은 서로 독립적으로 여닫힙니다." + ] + }, + { + "name": "Fixed", + "items": [ + "모바일에서 상단 헤더의 통화 선택 버튼이 화면 밖으로 잘려 보이던 문제를 수정했습니다. 화면이 좁을수록 심해져 320px 기기에서는 대부분이 가려졌습니다.", + "관리자 메뉴에서 언어를 선택해도 언어가 바뀌지 않던 문제를 수정했습니다. 로그인·비밀번호 찾기·비밀번호 재설정 화면의 언어 선택도 함께 정리했습니다.", + "좁은 화면에서 팝업 창이 화면 좌우에 빈틈 없이 붙어 보이던 문제를 수정했습니다 — 이제 양옆에 여백이 생깁니다.", + "... (총 5건 중 3건 표시)" + ] + } + ] + }, + "... (총 56건 중 3건 표시)" + ] + } +} +``` **에러 응답** @@ -752,7 +843,7 @@ Authorization: Bearer {YOUR_TOKEN} **요청 예시** ```http -GET /api/admin/templates/{identifier}/editor-assets HTTP/1.1 +GET /api/admin/templates/sirsoft-admin_basic/editor-assets HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -760,11 +851,39 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - +_단건 응답: `data` 객체의 필드._ + +| 필드 | 타입 | 실측 예시값 | 용도/설명 | +| --- | --- | --- | --- | +| identifier | string | `sirsoft-admin_basic` | 템플릿 고유 식별자 (vendor-name 형식, 예: sirsoft-admin_basic) | +| js | array | `["\/api\/templates\/assets\/sirsoft-admin_basic?file=js%2…` | | +| css | array | `["\/api\/admin\/templates\/sirsoft-admin_basic\/editor\/c…` | | +| manifest_present | boolean | `true` | | +| manifest_source | string | `active` | | **응답 예시** - +```http +HTTP/1.1 200 +``` + +```json +{ + "success": true, + "message": "편집기 자산 정보를 조회했습니다.", + "data": { + "identifier": "sirsoft-admin_basic", + "js": [ + "/api/templates/assets/sirsoft-admin_basic?file=js%2Fcomponents.iife.js" + ], + "css": [ + "/api/admin/templates/sirsoft-admin_basic/editor/component-styles" + ], + "manifest_present": true, + "manifest_source": "active" + } +} +``` **에러 응답** @@ -779,6 +898,81 @@ Authorization: Bearer {YOUR_TOKEN} **설명** 레이아웃 편집기 부팅용 자산 매니페스트(컴포넌트 IIFE JS / CSS URL)를 반환합니다. 비활성 템플릿도 편집할 수 있도록 활성 디렉토리 → `_bundled` 폴백으로 빌드 산출물을 탐색하며, 빌드가 없으면 빈 목록으로 폴백합니다. `core.templates.layouts.edit` 권한이 필요합니다. +### GET /api/admin/templates/{identifier}/editor/broadcast-catalog + +- **라우트명**: `api.admin.templates.editor-broadcast-catalog.extensionless` +- **컨트롤러**: `App\Http\Controllers\Api\Admin\BroadcastCatalogController@index` +- **인증/권한**: `auth:sanctum` + `permission:core.templates.layouts.edit` + +**요청 파라미터** + +| 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | +| --- | --- | --- | --- | --- | --- | +| identifier | path | string | 예 | — | 대상 리소스의 식별자 | + +**요청 예시** + +```http +GET /api/admin/templates/sirsoft-admin_basic/editor/broadcast-catalog HTTP/1.1 +Host: api.example.com +Accept: application/json +Authorization: Bearer {YOUR_TOKEN} +``` + +**응답 필드** (`data` 내부) + +_단건 응답: `data` 객체의 필드._ + +| 필드 | 타입 | 실측 예시값 | 용도/설명 | +| --- | --- | --- | --- | +| identifier | string | `sirsoft-admin_basic` | 템플릿 고유 식별자 (vendor-name 형식, 예: sirsoft-admin_basic) | +| channels | array | `[{"name":"core.admin.dashboard","source":{"kind":"core"}}…` | | +| events | array | `[]` | | + +**응답 예시** + +```http +HTTP/1.1 200 +``` + +```json +{ + "success": true, + "message": "성공적으로 처리되었습니다.", + "data": { + "identifier": "sirsoft-admin_basic", + "channels": [ + { + "name": "core.admin.dashboard", + "source": { + "kind": "core" + } + }, + { + "name": "core.user.notifications.{uuid}", + "source": { + "kind": "core" + } + } + ], + "events": [] + } +} +``` + +**에러 응답** + +| 상태코드 | 의미 | 발생 조건 | +| --- | --- | --- | +| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 | +| 403 | Forbidden | 요구 권한(`core.templates.layouts.edit`)이 없는 경우 | +| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 | + + + +**설명** + + ### GET /api/admin/templates/{identifier}/editor/broadcast-catalog.json - **라우트명**: `api.admin.templates.editor-broadcast-catalog` @@ -794,7 +988,7 @@ Authorization: Bearer {YOUR_TOKEN} **요청 예시** ```http -GET /api/admin/templates/{identifier}/editor/broadcast-catalog.json HTTP/1.1 +GET /api/admin/templates/sirsoft-admin_basic/editor/broadcast-catalog.json HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -802,11 +996,11 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - + **응답 예시** - + **에러 응답** @@ -821,6 +1015,48 @@ Authorization: Bearer {YOUR_TOKEN} **설명** 데이터소스 websocket 후보용으로 등록된 브로드캐스트 채널/이벤트 카탈로그를 반환합니다(BroadcastCatalogService::collect). 편집기 전용 가드 하에서만 노출되며(admin 전역 broadcast 회피), `identifier`는 라우트 일관성용이고 카탈로그는 설치본 전역 기준입니다. `core.templates.layouts.edit` 권한이 필요합니다. +### GET /api/admin/templates/{identifier}/editor/component-styles + +- **라우트명**: `api.admin.templates.editor-css.extensionless` +- **컨트롤러**: `App\Http\Controllers\Api\Admin\AdminTemplateAssetController@serveEditorCss` +- **인증/권한**: `auth:sanctum` + `permission:core.templates.layouts.edit` + +**요청 파라미터** + +| 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | +| --- | --- | --- | --- | --- | --- | +| identifier | path | string | 예 | — | 대상 리소스의 식별자 | + +**요청 예시** + +```http +GET /api/admin/templates/sirsoft-admin_basic/editor/component-styles HTTP/1.1 +Host: api.example.com +Accept: application/json +Authorization: Bearer {YOUR_TOKEN} +``` + +**응답 필드** (`data` 내부) + + + +**응답 예시** + + + +**에러 응답** + +| 상태코드 | 의미 | 발생 조건 | +| --- | --- | --- | +| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 | +| 403 | Forbidden | 요구 권한(`core.templates.layouts.edit`)이 없는 경우 | +| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 | + + + +**설명** + + ### GET /api/admin/templates/{identifier}/editor/component-styles.css - **라우트명**: `api.admin.templates.editor-css` @@ -836,7 +1072,7 @@ Authorization: Bearer {YOUR_TOKEN} **요청 예시** ```http -GET /api/admin/templates/{identifier}/editor/component-styles.css HTTP/1.1 +GET /api/admin/templates/sirsoft-admin_basic/editor/component-styles.css HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -844,11 +1080,11 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - + **응답 예시** - + **에러 응답** @@ -865,6 +1101,70 @@ Authorization: Bearer {YOUR_TOKEN} URI 가 `editor/components.css` 가 아니라 `editor/component-styles.css` 인 이유는 [자산 URL 이중 모드](README.md#자산-url-이중-모드) 때문입니다. 확장자를 뗀 형태(`editor/components`)가 `editor/components.json` 의 확장자 없는 형태와 충돌하므로 CSS 쪽 URI 를 분리했습니다. 확장자 없는 형태는 `editor/component-styles` 입니다. +### GET /api/admin/templates/{identifier}/editor/components + +- **라우트명**: `api.admin.templates.editor-components.extensionless` +- **컨트롤러**: `App\Http\Controllers\Api\Admin\AdminTemplateAssetController@serveComponents` +- **인증/권한**: `auth:sanctum` + `permission:core.templates.layouts.edit` + +**요청 파라미터** + +| 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | +| --- | --- | --- | --- | --- | --- | +| identifier | path | string | 예 | — | 대상 리소스의 식별자 | + +**요청 예시** + +```http +GET /api/admin/templates/sirsoft-admin_basic/editor/components HTTP/1.1 +Host: api.example.com +Accept: application/json +Authorization: Bearer {YOUR_TOKEN} +``` + +**응답 필드** (`data` 내부) + +_단건 응답: `data` 객체의 필드._ + +| 필드 | 타입 | 실측 예시값 | 용도/설명 | +| --- | --- | --- | --- | +| $schema | string | `https://json-schema.org/draft/2020-12…` | | +| templateId | string | `sirsoft-admin_basic` | | +| version | string | `1.0.0` | 템플릿 버전 (예: 1.0.0) | +| components | object | `{"basic":[{"name":"Button","type":"basic","description":"…` | | + +**응답 예시** + +```http +HTTP/1.1 200 +``` + +```json +{ + "success": true, + "message": "템플릿 설정 정보를 조회했습니다.", + "data": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "templateId": "sirsoft-admin_basic", + "version": "1.0.0", + "...": "(1개 키 생략, 총 4개)" + } +} +``` + +**에러 응답** + +| 상태코드 | 의미 | 발생 조건 | +| --- | --- | --- | +| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 | +| 403 | Forbidden | 요구 권한(`core.templates.layouts.edit`)이 없는 경우 | +| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 | + + + +**설명** + + ### GET /api/admin/templates/{identifier}/editor/components.json - **라우트명**: `api.admin.templates.editor-components` @@ -880,7 +1180,7 @@ URI 가 `editor/components.css` 가 아니라 `editor/component-styles.css` 인 **요청 예시** ```http -GET /api/admin/templates/{identifier}/editor/components.json HTTP/1.1 +GET /api/admin/templates/sirsoft-admin_basic/editor/components.json HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -888,11 +1188,11 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - + **응답 예시** - + **에러 응답** @@ -907,6 +1207,73 @@ Authorization: Bearer {YOUR_TOKEN} **설명** 편집기용 components.json(컴포넌트 정의)을 서빙합니다. 비활성 템플릿도 편집 가능하도록 활성 디렉토리 → `_bundled` 폴백으로 파일을 읽어 반환합니다. `core.templates.layouts.edit` 권한이 필요합니다. +### GET /api/admin/templates/{identifier}/editor/editor-spec + +- **라우트명**: `api.admin.templates.editor-spec.extensionless` +- **컨트롤러**: `App\Http\Controllers\Api\Admin\AdminTemplateAssetController@serveEditorSpec` +- **인증/권한**: `auth:sanctum` + `permission:core.templates.layouts.edit` + +**요청 파라미터** + +| 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | +| --- | --- | --- | --- | --- | --- | +| identifier | path | string | 예 | — | 대상 리소스의 식별자 | + +**요청 예시** + +```http +GET /api/admin/templates/sirsoft-admin_basic/editor/editor-spec HTTP/1.1 +Host: api.example.com +Accept: application/json +Authorization: Bearer {YOUR_TOKEN} +``` + +**응답 필드** (`data` 내부) + +_단건 응답: `data` 객체의 필드._ + +| 필드 | 타입 | 실측 예시값 | 용도/설명 | +| --- | --- | --- | --- | +| identifier | string | `sirsoft-admin_basic` | 템플릿 고유 식별자 (vendor-name 형식, 예: sirsoft-admin_basic) | +| spec | object | `{"$schema":"https:\/\/json-schema.org\/draft\/2020-12\/sc…` | 스펙 정의 객체 (편집기/컴포넌트 선언 스키마 등) | + +**응답 예시** + +```http +HTTP/1.1 200 +``` + +```json +{ + "success": true, + "message": "편집기 스펙을 조회했습니다.", + "data": { + "identifier": "sirsoft-admin_basic", + "spec": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "templateId": "sirsoft-admin_basic", + "version": "1.0.0", + "description": "레이아웃 편집기 스펙 — Phase 3 (nesting + componentPalette 블록). controls/componentCapabilities/actionRecipes 등은 Phase 4/5 에서 추가.", + "styleSystem": "tailwind", + "...": "(14개 키 생략, 총 19개)" + } + } +} +``` + +**에러 응답** + +| 상태코드 | 의미 | 발생 조건 | +| --- | --- | --- | +| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 | +| 403 | Forbidden | 요구 권한(`core.templates.layouts.edit`)이 없는 경우 | +| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 | + + + +**설명** + + ### GET /api/admin/templates/{identifier}/editor/editor-spec.json - **라우트명**: `api.admin.templates.editor-spec` @@ -922,7 +1289,50 @@ Authorization: Bearer {YOUR_TOKEN} **요청 예시** ```http -GET /api/admin/templates/{identifier}/editor/editor-spec.json HTTP/1.1 +GET /api/admin/templates/sirsoft-admin_basic/editor/editor-spec.json HTTP/1.1 +Host: api.example.com +Accept: application/json +Authorization: Bearer {YOUR_TOKEN} +``` + +**응답 필드** (`data` 내부) + + + +**응답 예시** + + + +**에러 응답** + +| 상태코드 | 의미 | 발생 조건 | +| --- | --- | --- | +| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 | +| 403 | Forbidden | 요구 권한(`core.templates.layouts.edit`)이 없는 경우 | +| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 | + + + +**설명** 편집기 스펙(editor-spec.json)을 서빙합니다. 분할 스펙은 manifest + `$include` 블록을 합본한 단일 spec으로 반환하며(활성 디렉토리 기준), sampleData/sampleGlobal/states 등 전 블록이 포함됩니다. 파일 미존재 시 `spec=null`로 폴백합니다. `core.templates.layouts.edit` 권한이 필요합니다. + + +### GET /api/admin/templates/{identifier}/editor/lang/{locale} + +- **라우트명**: `api.admin.templates.editor-lang.extensionless` +- **컨트롤러**: `App\Http\Controllers\Api\Admin\AdminTemplateAssetController@serveLanguage` +- **인증/권한**: `auth:sanctum` + `permission:core.templates.layouts.edit` + +**요청 파라미터** + +| 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | +| --- | --- | --- | --- | --- | --- | +| identifier | path | string | 예 | — | 대상 리소스의 식별자 | +| locale | path | string | 예 | — | 로케일 코드 (표시 언어/지역) | + +**요청 예시** + +```http +GET /api/admin/templates/sirsoft-admin_basic/editor/lang/{locale} HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -946,7 +1356,7 @@ Authorization: Bearer {YOUR_TOKEN} -**설명** 편집기 스펙(editor-spec.json)을 서빙합니다. 분할 스펙은 manifest + `$include` 블록을 합본한 단일 spec으로 반환하며(활성 디렉토리 기준), sampleData/sampleGlobal/states 등 전 블록이 포함됩니다. 파일 미존재 시 `spec=null`로 폴백합니다. `core.templates.layouts.edit` 권한이 필요합니다. +**설명** ### GET /api/admin/templates/{identifier}/editor/lang/{locale}.json @@ -965,7 +1375,7 @@ Authorization: Bearer {YOUR_TOKEN} **요청 예시** ```http -GET /api/admin/templates/{identifier}/editor/lang/{locale}.json HTTP/1.1 +GET /api/admin/templates/sirsoft-admin_basic/editor/lang/{locale}.json HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -992,6 +1402,88 @@ Authorization: Bearer {YOUR_TOKEN} **설명** 편집기용 다국어 데이터(lang/{locale}.json)를 서빙합니다. 활성 상태 검증 없이 활성 디렉토리 → `_bundled` 폴백으로 읽으며, 파일 부재 시 빈 객체로 폴백합니다. `core.templates.layouts.edit` 권한이 필요합니다. +### GET /api/admin/templates/{identifier}/editor/permission-candidates + +- **라우트명**: `api.admin.templates.editor-permission-candidates.extensionless` +- **컨트롤러**: `App\Http\Controllers\Api\Admin\AdminTemplateAssetController@servePermissionCandidates` +- **인증/권한**: `auth:sanctum` + `permission:core.templates.layouts.edit` + +**요청 파라미터** + +| 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | +| --- | --- | --- | --- | --- | --- | +| identifier | path | string | 예 | — | 대상 리소스의 식별자 | + +**요청 예시** + +```http +GET /api/admin/templates/sirsoft-admin_basic/editor/permission-candidates HTTP/1.1 +Host: api.example.com +Accept: application/json +Authorization: Bearer {YOUR_TOKEN} +``` + +**응답 필드** (`data` 내부) + +_단건 응답: `data` 객체의 필드._ + +| 필드 | 타입 | 실측 예시값 | 용도/설명 | +| --- | --- | --- | --- | +| identifier | string | `sirsoft-admin_basic` | 템플릿 고유 식별자 (vendor-name 형식, 예: sirsoft-admin_basic) | +| permissions | array | `[{"key":"sirsoft-ecommerce.user-products.read","name":"상품…` | 권한 목록 (각 원소 id/identifier/name — permissions 관계 또는 역할 경유 파생) | + +**응답 예시** + +```http +HTTP/1.1 200 +``` + +```json +{ + "success": true, + "message": "템플릿 설정 정보를 조회했습니다.", + "data": { + "identifier": "sirsoft-admin_basic", + "permissions": [ + { + "key": "sirsoft-ecommerce.user-products.read", + "name": "상품 조회" + }, + { + "key": "sirsoft-ecommerce.user-orders.create", + "name": "주문하기" + }, + { + "key": "sirsoft-ecommerce.user-orders.cancel", + "name": "주문 취소" + }, + { + "key": "sirsoft-ecommerce.user-orders.confirm", + "name": "구매확정" + }, + { + "key": "sirsoft-ecommerce.user-reviews.write", + "name": "리뷰 작성" + }, + "... (총 275건 중 5건 표시)" + ] + } +} +``` + +**에러 응답** + +| 상태코드 | 의미 | 발생 조건 | +| --- | --- | --- | +| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 | +| 403 | Forbidden | 요구 권한(`core.templates.layouts.edit`)이 없는 경우 | +| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 | + + + +**설명** + + ### GET /api/admin/templates/{identifier}/editor/permission-candidates.json - **라우트명**: `api.admin.templates.editor-permission-candidates` @@ -1007,7 +1499,7 @@ Authorization: Bearer {YOUR_TOKEN} **요청 예시** ```http -GET /api/admin/templates/{identifier}/editor/permission-candidates.json HTTP/1.1 +GET /api/admin/templates/sirsoft-admin_basic/editor/permission-candidates.json HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -1015,11 +1507,11 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - + **응답 예시** - + **에러 응답** @@ -1034,6 +1526,166 @@ Authorization: Bearer {YOUR_TOKEN} **설명** 레이아웃 편집기의 표시 권한 지정용으로 코어+활성 확장의 전체 권한을 `{key, name}` 목록으로 반환합니다(PermissionService::getPermissionCandidates). 편집기 진입 가드 하에서만 노출되어, 권한 카탈로그가 모든 admin 페이지에 상시 노출되던 방식보다 범위가 편집기로 한정됩니다. `core.templates.layouts.edit` 권한이 필요합니다. +### GET /api/admin/templates/{identifier}/editor/routes + +- **라우트명**: `api.admin.templates.editor-routes.extensionless` +- **컨트롤러**: `App\Http\Controllers\Api\Admin\AdminTemplateAssetController@serveRoutes` +- **인증/권한**: `auth:sanctum` + `permission:core.templates.layouts.edit` + +**요청 파라미터** + +| 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | +| --- | --- | --- | --- | --- | --- | +| identifier | path | string | 예 | — | 대상 리소스의 식별자 | + +**요청 예시** + +```http +GET /api/admin/templates/sirsoft-admin_basic/editor/routes HTTP/1.1 +Host: api.example.com +Accept: application/json +Authorization: Bearer {YOUR_TOKEN} +``` + +**응답 필드** (`data` 내부) + +_단건 응답: `data` 객체의 필드._ + +| 필드 | 타입 | 실측 예시값 | 용도/설명 | +| --- | --- | --- | --- | +| version | string | `1.0.0` | 템플릿 버전 (예: 1.0.0) | +| routes | array | `[{"path":"*\/admin","redirect":"\/admin\/dashboard","auth…` | | +| base_layouts | array | `[{"layout_name":"_admin_base","label":null}]` | | +| modals | array | `[{"modal_id":"identity-challenge-modal","host_layout":"_a…` | | +| layout_versions | array | `[]` | | + +**응답 예시** + +```http +HTTP/1.1 200 +``` + +```json +{ + "success": true, + "message": "템플릿 라우트 정보를 조회했습니다.", + "data": { + "version": "1.0.0", + "routes": [ + { + "path": "*/admin", + "redirect": "/admin/dashboard", + "auth_required": true, + "meta": { + "title": "관리자" + }, + "source": { + "kind": "template", + "identifier": null + } + }, + { + "path": "*/admin/login", + "layout": "admin_login", + "auth_required": false, + "meta": { + "title": "관리자 로그인" + }, + "source": { + "kind": "template", + "identifier": null + } + }, + { + "path": "*/admin/forgot-password", + "layout": "admin_forgot_password", + "auth_required": false, + "meta": { + "title": "$t:auth.forgot_password.title" + }, + "source": { + "kind": "template", + "identifier": null + } + }, + { + "path": "*/admin/reset-password", + "layout": "admin_reset_password", + "auth_required": false, + "meta": { + "title": "$t:auth.reset_password.title" + }, + "source": { + "kind": "template", + "identifier": null + } + }, + { + "path": "*/admin/dashboard", + "layout": "admin_dashboard", + "auth_required": true, + "meta": { + "title": "관리자 대시보드" + }, + "source": { + "kind": "template", + "identifier": null + } + }, + "... (총 85건 중 5건 표시)" + ], + "base_layouts": [ + { + "layout_name": "_admin_base", + "label": null + } + ], + "modals": [ + { + "modal_id": "identity-challenge-modal", + "host_layout": "_admin_base", + "label": "$t:admin.modal_label.identity_challenge_modal" + }, + { + "modal_id": "license_modal", + "host_layout": "_admin_base", + "label": "$t:admin.modal_label.license_modal" + }, + { + "modal_id": "changelog_modal", + "host_layout": "_admin_base", + "label": "$t:admin.modal_label.changelog_modal" + }, + { + "modal_id": "notification_delete_all_confirm_modal", + "host_layout": "_admin_base", + "label": "$t:admin.modal_label.notification_delete_all_confirm_modal" + }, + { + "modal_id": "delete_confirm_modal", + "host_layout": "admin_activity_log_list", + "label": "$t:admin.modal_label.activity_log_list.delete_confirm_modal" + }, + "... (총 205건 중 5건 표시)" + ], + "layout_versions": [] + } +} +``` + +**에러 응답** + +| 상태코드 | 의미 | 발생 조건 | +| --- | --- | --- | +| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 | +| 403 | Forbidden | 요구 권한(`core.templates.layouts.edit`)이 없는 경우 | +| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 | + + + +**설명** + + ### GET /api/admin/templates/{identifier}/editor/routes.json - **라우트명**: `api.admin.templates.editor-routes` @@ -1049,7 +1701,7 @@ Authorization: Bearer {YOUR_TOKEN} **요청 예시** ```http -GET /api/admin/templates/{identifier}/editor/routes.json HTTP/1.1 +GET /api/admin/templates/sirsoft-admin_basic/editor/routes.json HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -1057,11 +1709,11 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - + **응답 예시** - + **에러 응답** @@ -1100,7 +1752,7 @@ Authorization: Bearer {YOUR_TOKEN} **요청 예시** ```http -POST /api/admin/templates/{identifier}/editor/seo-bot-preview HTTP/1.1 +POST /api/admin/templates/sirsoft-admin_basic/editor/seo-bot-preview HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -1125,7 +1777,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -1145,6 +1797,252 @@ Content-Type: application/json **설명** 편집기 [검색엔진] 탭의 봇 HTML 실시간 미리보기를 반환합니다. dirty 레이아웃 + 편집기 샘플 데이터로 운영과 동일한 렌더 경로를 거쳐(SEO 캐시 우회) 완성 HTML을 만들며, `meta.seo.enabled=false`이거나 미렌더 시 `enabled=false`로 미노출을 안내합니다. `core.templates.layouts.edit` 권한이 필요합니다. +### GET /api/admin/templates/{identifier}/editor/seo-candidates + +- **라우트명**: `api.admin.templates.editor-seo-candidates.extensionless` +- **컨트롤러**: `App\Http\Controllers\Api\Admin\SeoCandidateController@index` +- **인증/권한**: `auth:sanctum` + `permission:core.templates.layouts.edit` + +**요청 파라미터** + +| 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | +| --- | --- | --- | --- | --- | --- | +| identifier | path | string | 예 | — | 대상 리소스의 식별자 | +| extensions | query | string | 아니오 | — | | +| page_type | query | string | 아니오 | — | | + +> 이 엔드포인트는 확장이 파라미터를 추가할 수 있습니다 (`core.seo_candidate.index_validation_rules`). + +**요청 예시** + +```http +GET /api/admin/templates/sirsoft-admin_basic/editor/seo-candidates?extensions=%EC%98%88%EC%8B%9C%EA%B0%92&page_type=%EC%98%88%EC%8B%9C%EA%B0%92 HTTP/1.1 +Host: api.example.com +Accept: application/json +Authorization: Bearer {YOUR_TOKEN} +``` + +**응답 필드** (`data` 내부) + +_단건 응답: `data` 객체의 필드._ + +| 필드 | 타입 | 실측 예시값 | 용도/설명 | +| --- | --- | --- | --- | +| identifier | string | `sirsoft-admin_basic` | 템플릿 고유 식별자 (vendor-name 형식, 예: sirsoft-admin_basic) | +| page_types | array | `[{"value":"boards","owner":{"type":"module","id":"sirsoft…` | | +| toggle_settings | array | `[{"ref":"$module_settings:sirsoft-board:seo.seo_boards","…` | | +| vars | array | `[]` | | +| extensions | array | `[{"type":"module","id":"gnuboard7-hello_module","label":"…` | | + +**응답 예시** + +```http +HTTP/1.1 200 +``` + +```json +{ + "success": true, + "message": "성공적으로 처리되었습니다.", + "data": { + "identifier": "sirsoft-admin_basic", + "page_types": [ + { + "value": "boards", + "owner": { + "type": "module", + "id": "sirsoft-board" + } + }, + { + "value": "board", + "owner": { + "type": "module", + "id": "sirsoft-board" + } + }, + { + "value": "post", + "owner": { + "type": "module", + "id": "sirsoft-board" + } + }, + { + "value": "product", + "owner": { + "type": "module", + "id": "sirsoft-ecommerce" + } + }, + { + "value": "category", + "owner": { + "type": "module", + "id": "sirsoft-ecommerce" + } + }, + { + "value": "search", + "owner": { + "type": "module", + "id": "sirsoft-ecommerce" + } + }, + { + "value": "shop_index", + "owner": { + "type": "module", + "id": "sirsoft-ecommerce" + } + } + ], + "toggle_settings": [ + { + "ref": "$module_settings:sirsoft-board:seo.seo_boards", + "key": "seo_boards", + "label": "seo_boards", + "owner": { + "type": "module", + "id": "sirsoft-board" + } + }, + { + "ref": "$module_settings:sirsoft-board:seo.seo_board", + "key": "seo_board", + "label": "seo_board", + "owner": { + "type": "module", + "id": "sirsoft-board" + } + }, + { + "ref": "$module_settings:sirsoft-board:seo.seo_post_detail", + "key": "seo_post_detail", + "label": "seo_post_detail", + "owner": { + "type": "module", + "id": "sirsoft-board" + } + }, + { + "ref": "$module_settings:sirsoft-ecommerce:seo.seo_category", + "key": "seo_category", + "label": "seo_category", + "owner": { + "type": "module", + "id": "sirsoft-ecommerce" + } + }, + { + "ref": "$module_settings:sirsoft-ecommerce:seo.seo_search_result", + "key": "seo_search_result", + "label": "seo_search_result", + "owner": { + "type": "module", + "id": "sirsoft-ecommerce" + } + }, + { + "ref": "$module_settings:sirsoft-ecommerce:seo.seo_product_detail", + "key": "seo_product_detail", + "label": "seo_product_detail", + "owner": { + "type": "module", + "id": "sirsoft-ecommerce" + } + }, + { + "ref": "$module_settings:sirsoft-ecommerce:seo.seo_shop_index", + "key": "seo_shop_index", + "label": "seo_shop_index", + "owner": { + "type": "module", + "id": "sirsoft-ecommerce" + } + } + ], + "vars": [], + "extensions": [ + { + "type": "module", + "id": "gnuboard7-hello_module", + "label": "Hello 모듈" + }, + { + "type": "module", + "id": "sirsoft-board", + "label": "게시판" + }, + { + "type": "module", + "id": "sirsoft-ecommerce", + "label": "이커머스" + }, + { + "type": "module", + "id": "sirsoft-page", + "label": "페이지" + }, + { + "type": "plugin", + "id": "sirsoft-ckeditor5", + "label": "CKEditor 5 WYSIWYG 에디터" + }, + { + "type": "plugin", + "id": "sirsoft-daum_postcode", + "label": "Daum 우편번호" + }, + { + "type": "plugin", + "id": "sirsoft-gdpr", + "label": "GDPR (일반 데이터 보호 규정)" + }, + { + "type": "plugin", + "id": "sirsoft-marketing", + "label": "마케팅 동의" + }, + { + "type": "plugin", + "id": "sirsoft-pay_kginicis", + "label": "KG 이니시스" + }, + { + "type": "plugin", + "id": "sirsoft-pay_nhnkcp", + "label": "NHN KCP" + }, + { + "type": "plugin", + "id": "sirsoft-pay_nicepayments", + "label": "나이스페이먼츠" + }, + { + "type": "plugin", + "id": "sirsoft-verification_kginicis", + "label": "KG이니시스 본인인증" + } + ] + } +} +``` + +**에러 응답** + +| 상태코드 | 의미 | 발생 조건 | +| --- | --- | --- | +| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 | +| 403 | Forbidden | 요구 권한(`core.templates.layouts.edit`)이 없는 경우 | +| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) | +| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 | + + + +**설명** + + ### GET /api/admin/templates/{identifier}/editor/seo-candidates.json - **라우트명**: `api.admin.templates.editor-seo-candidates` @@ -1164,7 +2062,7 @@ Content-Type: application/json **요청 예시** ```http -GET /api/admin/templates/{identifier}/editor/seo-candidates.json?extensions=%EC%98%88%EC%8B%9C%EA%B0%92&page_type=%EC%98%88%EC%8B%9C%EA%B0%92 HTTP/1.1 +GET /api/admin/templates/sirsoft-admin_basic/editor/seo-candidates.json?extensions=%EC%98%88%EC%8B%9C%EA%B0%92&page_type=%EC%98%88%EC%8B%9C%EA%B0%92 HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -1172,11 +2070,11 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - + **응답 예시** - + **에러 응답** @@ -1213,7 +2111,7 @@ Authorization: Bearer {YOUR_TOKEN} **요청 예시** ```http -POST /api/admin/templates/{identifier}/editor/seo-og-preview HTTP/1.1 +POST /api/admin/templates/sirsoft-admin_basic/editor/seo-og-preview HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -1237,7 +2135,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -1275,7 +2173,7 @@ Content-Type: application/json **요청 예시** ```http -GET /api/admin/templates/{identifier}/layout-attachments?layout_name=%EC%98%88%EC%8B%9C%20%EC%9D%B4%EB%A6%84 HTTP/1.1 +GET /api/admin/templates/sirsoft-admin_basic/layout-attachments?layout_name=%EC%98%88%EC%8B%9C%20%EC%9D%B4%EB%A6%84 HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -1283,11 +2181,23 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - + + + **응답 예시** - +```http +HTTP/1.1 200 +``` + +```json +{ + "success": true, + "message": "첨부 파일 목록을 조회했습니다.", + "data": [] +} +``` **에러 응답** @@ -1322,7 +2232,7 @@ Authorization: Bearer {YOUR_TOKEN} **요청 예시** ```http -POST /api/admin/templates/{identifier}/layout-attachments HTTP/1.1 +POST /api/admin/templates/sirsoft-admin_basic/layout-attachments HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -1342,7 +2252,7 @@ Content-Disposition: form-data; name="layout_name" **응답 필드** (`data` 내부) - + **응답 예시** @@ -1377,7 +2287,7 @@ Content-Disposition: form-data; name="layout_name" **요청 예시** ```http -GET /api/admin/templates/{identifier}/license HTTP/1.1 +GET /api/admin/templates/sirsoft-admin_basic/license HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -1385,11 +2295,27 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - +_단건 응답: `data` 객체의 필드._ + +| 필드 | 타입 | 실측 예시값 | 용도/설명 | +| --- | --- | --- | --- | +| content | string | `프로그램 명칭 : 그누보드7용 Admin Basic 템플릿 (sir…` | 본문 내용 | **응답 예시** - +```http +HTTP/1.1 200 +``` + +```json +{ + "success": true, + "message": "템플릿 정보를 성공적으로 가져왔습니다.", + "data": { + "content": "프로그램 명칭 : 그누보드7용 Admin Basic 템플릿 (sirsoft-admin_basic)\n\n저작자 : (주)에스아이알소프트\n\n----- MIT 라이선스 (한국어 번역) --------------------------------------------------------\n\nMIT 라이선스\n\nCopyright (c) 2026 (주)에스아이알소프트\n\n이 소프트웨어와 관련 문서 파일(이하 \"소프트웨어\")의 복사본을 취득하는 모든 사람에게\n소프트웨어를 제한 없이 사용, 복사, 수정, 병합, 출판, 배포, 서브라이선스 허여 및/또는\n판매할 수 있는 권리를 무상으로 부여합니다. 다만, 소프트웨어를 제공받은 사람은 다음\n조건을 따라야 합니다:\n\n위 저작권 고지와 본 허가 고지는 소프트웨어의 모든 복사본 또는 상당 부분에 포함되어야\n합니다.\n\n소프트웨어는 \"있는 그대로\" 제공되며, 명시적이든 묵시적이든 어떠한 종류의 보증도 하지\n않습니다. 여기에는 상품성, 특정 목적에의 적합성 및 비침해에 대한 보증이 포함되나 이에\n국한되지 않습니다. 어떠한 경우에도 저작자 또는 저작권자는 소프트웨어나 소프트웨어의\n사용 또는 기타 거래로 인해 발생하는 계약, 불법행위 또는 기타 청구, 손해 또는 기타\n책임에 대해 책임을 지지 않습니다.\n\n----- MIT License (English Original) --------------------------------------------------------\n\nThe MIT License (MIT)\n\nCopyright (c) 2026 SIRSOFT\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n" + } +} +``` **에러 응답** @@ -1469,38 +2395,11 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) -| 필드 | 타입 | 설명 | -| --- | --- | --- | -| has_modified_layouts | boolean | 수정된 레이아웃 존재 여부 | -| modified_count | integer | 수정된 레이아웃 수 | -| modified_layouts | array | 수정된 레이아웃 목록 | -| modified_layouts[].id | integer | 레이아웃 ID | -| modified_layouts[].name | string | 레이아웃 이름 | -| modified_layouts[].updated_at | string\|null | 최종 수정 일시 (`Y-m-d H:i:s`) | -| modified_layouts[].size_diff | integer | 원본 대비 바이트 증감 (음수는 축소) | + **응답 예시** -```json -{ - "success": true, - "message": "수정된 레이아웃 확인이 완료되었습니다.", - "data": { - "has_modified_layouts": true, - "modified_count": 1, - "modified_layouts": [ - { - "id": 47, - "name": "sirsoft-board.admin_board_index", - "updated_at": "2026-07-31 09:20:05", - "size_diff": 28 - } - ] - } -} -``` - -집계 범위는 해당 템플릿이 **소유한** 레이아웃으로 한정됩니다. 같은 템플릿에 등록된 다른 확장의 레이아웃은 포함되지 않습니다. + **에러 응답** @@ -1541,7 +2440,7 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - + **응답 예시** @@ -1643,7 +2542,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -1679,7 +2578,7 @@ Content-Type: application/json **요청 예시** ```http -DELETE /api/admin/templates/{templateName}/custom-translations/{id} HTTP/1.1 +DELETE /api/admin/templates/{templateName}/custom-translations/1 HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -1687,7 +2586,7 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - + **응답 예시** @@ -1726,7 +2625,7 @@ Authorization: Bearer {YOUR_TOKEN} **요청 예시** ```http -PUT /api/admin/templates/{templateName}/custom-translations/{id} HTTP/1.1 +PUT /api/admin/templates/{templateName}/custom-translations/1 HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -1742,7 +2641,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -1843,7 +2742,7 @@ Authorization: Bearer {YOUR_TOKEN} -**설명** 특정 템플릿에 모듈/플러그인이 주입한 레이아웃 확장 목록을 출처별로 그룹핑해 조회합니다. 각 확장에는 호스트 레이아웃 목록(`host_layouts`)이 부착되어, 캔버스 로드 없이 화면별 연결 확장을 정적 구성할 수 있습니다(LayoutExtensionService::getExtensionsByTemplateId). `core.templates.read` 권한이 필요합니다. +**설명** 특정 템플릿에 모듈/플러그인이 주입한 레이아웃 확장 목록을 출처별로 그룹핑해 조회합니다. 각 확장에는 호스트 레이아웃 목록(`host_layouts`)이 부착되어, 캔버스 로드 없이 화면별 연결 확장을 정적 구성할 수 있습니다(LayoutExtensionService::getExtensionsByTemplateId). 응답에는 라우트 트리가 실제로 그리는 필드만 담기고 **확장 레이아웃 본문(`content`)은 제외됩니다** — 목록은 페이지네이션 없이 전 확장을 반환하므로 본문을 함께 실으면 전 행의 JSON 전문이 전송됩니다. 본문은 단건 조회(`GET /api/admin/templates/{template}/layout-extensions/{extension}`)가 공급합니다. `core.templates.read` 권한이 필요합니다. ### GET /api/admin/templates/{templateName}/layout-extensions/{extensionId} @@ -1927,7 +2826,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -1983,7 +2882,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -2015,11 +2914,14 @@ Content-Type: application/json | --- | --- | --- | --- | --- | --- | | templateName | path | string | 예 | — | 대상 template의 이름 (식별자) | | extensionId | path | string | 예 | — | 대상 extension의 식별자 | +| limit | query | integer | 아니오 | min 1, max 500 | 반환할 최대 항목 수 | + +> 이 엔드포인트는 확장이 파라미터를 추가할 수 있습니다 (`core.layout_version.list_validation_rules`). **요청 예시** ```http -GET /api/admin/templates/{templateName}/layout-extensions/{extensionId}/versions HTTP/1.1 +GET /api/admin/templates/{templateName}/layout-extensions/{extensionId}/versions?limit=1 HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -2039,11 +2941,12 @@ Authorization: Bearer {YOUR_TOKEN} | --- | --- | --- | | 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 | | 403 | Forbidden | 요구 권한(`core.templates.read`)이 없는 경우 | +| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) | | 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 | -**설명** 레이아웃 확장의 모든 버전 이력을 조회합니다(LayoutExtensionService::getExtensionVersions). `core.templates.read` 권한이 필요합니다. +**설명** 레이아웃 확장의 버전 이력을 최신순으로 조회합니다(LayoutExtensionService::getExtensionVersions). 한 번에 돌려주는 개수는 `limit` 으로 정하며 기본 100건, 최대 500건입니다 — 전체를 무제한으로 돌려주지 않습니다. 상한을 넘는 과거 버전은 단건 조회로 접근합니다. `core.templates.read` 권한이 필요합니다. 응답에는 목록 표시용 필드만 담기고 본문(`content`)은 제외됩니다. ### POST /api/admin/templates/{templateName}/layout-extensions/{extensionId}/versions/{versionId}/restore @@ -2071,7 +2974,7 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - + **응답 예시** @@ -2220,7 +3123,7 @@ Authorization: Bearer {YOUR_TOKEN} -**설명** 특정 템플릿의 모든 레이아웃 목록을 조회합니다. 각 레이아웃에 이름 → 라우트 path 매핑을 부착해, 코드 편집기가 파일 선택 시 `?route=` URL 동기화나 위지윅에서 넘어온 라우트로 파일을 복원할 수 있게 합니다(LayoutService::getLayoutListByTemplateId). 목록은 본문을 담지 않는 경량 조회를 사용합니다. `core.templates.read` 권한이 필요합니다. +**설명** 특정 템플릿의 모든 레이아웃 목록을 조회합니다. 각 레이아웃에 이름 → 라우트 path 매핑을 부착해, 코드 편집기가 파일 선택 시 `?route=` URL 동기화나 위지윅에서 넘어온 라우트로 파일을 복원할 수 있게 합니다(LayoutService::getLayoutListByTemplateId). 응답에는 파일트리가 실제로 그리는 필드(이름·설명·라우트 path·크기·수정일시·업데이트 여부)만 담기고, **레이아웃 본문(`content`)과 파싱된 블록(`components`·`data_sources`·`metadata`)은 제외됩니다** — 목록은 페이지네이션 없이 템플릿의 전 레이아웃을 반환하므로 본문을 함께 실으면 편집기 진입만으로 모든 레이아웃 JSON 전문이 전송됩니다. 설명·크기는 조회 계층이 본문에서 파생값만 뽑아 넘기며 본문 자체는 응답에 남지 않습니다. 본문은 단건 조회(`GET /api/admin/templates/{template}/layouts/{layout}`)가 공급합니다. `core.templates.read` 권한이 필요합니다. ### GET /api/admin/templates/{templateName}/layouts/{name} @@ -2302,7 +3205,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -2354,7 +3257,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -2386,11 +3289,14 @@ Content-Type: application/json | --- | --- | --- | --- | --- | --- | | templateName | path | string | 예 | — | 대상 template의 이름 (식별자) | | name | path | string | 예 | — | 대상의 이름/명칭 | +| limit | query | integer | 아니오 | min 1, max 500 | 반환할 최대 항목 수 | + +> 이 엔드포인트는 확장이 파라미터를 추가할 수 있습니다 (`core.layout_version.list_validation_rules`). **요청 예시** ```http -GET /api/admin/templates/{templateName}/layouts/{name}/versions HTTP/1.1 +GET /api/admin/templates/{templateName}/layouts/{name}/versions?limit=1 HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -2410,11 +3316,12 @@ Authorization: Bearer {YOUR_TOKEN} | --- | --- | --- | | 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 | | 403 | Forbidden | 요구 권한(`core.templates.read`)이 없는 경우 | +| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) | | 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 | -**설명** 레이아웃의 모든 버전 이력을 조회합니다(LayoutService::getLayoutVersions). `core.templates.read` 권한이 필요합니다. +**설명** 레이아웃의 버전 이력을 최신순으로 조회합니다(LayoutService::getLayoutVersions). 한 번에 돌려주는 개수는 `limit` 으로 정하며 기본 100건, 최대 500건입니다 — 전체를 무제한으로 돌려주지 않습니다. 상한을 넘는 과거 버전은 단건 조회(`GET .../versions/{version}`)로 접근합니다. `core.templates.read` 권한이 필요합니다. 응답에는 목록 표시용 필드만 담기고 본문(`components` 등)은 제외됩니다. ### POST /api/admin/templates/{templateName}/layouts/{name}/versions/{versionId}/restore @@ -2442,7 +3349,7 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - + **응답 예시** @@ -2580,7 +3487,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -2600,6 +3507,47 @@ Content-Type: application/json **설명** 설치된 템플릿을 최신 버전으로 업데이트합니다. `layout_strategy`(overwrite: 레이아웃 전면 교체 / keep: 사용자 수정 레이아웃 유지)로 레이아웃 처리 방식을 결정하며, `force`로 강제 진행할 수 있습니다(TemplateService::performVersionUpdate). `core.templates.install` 권한이 필요합니다. `keep` 을 지정하면 사용자가 수정한 레이아웃(원본 해시와 현재 내용이 다른 레이아웃)은 갱신 대상에서 제외되어 현재 내용이 그대로 유지되고, 나머지 레이아웃만 파일 기준으로 갱신됩니다. 성공 응답 메시지에는 대상 식별자와 적용된 버전이 채워집니다. +### GET /api/templates/assets/{identifier} + +- **라우트명**: `api.public.templates.assets.extensionless` +- **컨트롤러**: `App\Http\Controllers\Api\Public\PublicTemplateController@serveAsset` +- **인증/권한**: 공개 (인증 불필요) + +**요청 파라미터** + +| 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | +| --- | --- | --- | --- | --- | --- | +| identifier | path | string | 예 | — | 대상 리소스의 식별자 | +| path | query | string | 예 | — | 경로 | + +**요청 예시** + +```http +GET /api/templates/assets/sirsoft-admin_basic?identifier=example-key&path=%EC%98%88%EC%8B%9C%EA%B0%92 HTTP/1.1 +Host: api.example.com +Accept: application/json +``` + +**응답 필드** (`data` 내부) + + + +**응답 예시** + + + +**에러 응답** + +| 상태코드 | 의미 | 발생 조건 | +| --- | --- | --- | +| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) | +| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 | + + + +**설명** + + ### GET /api/templates/assets/{identifier}/{path} - **라우트명**: `api.public.templates.assets` @@ -2616,7 +3564,7 @@ Content-Type: application/json **요청 예시** ```http -GET /api/templates/assets/{identifier}/{path} HTTP/1.1 +GET /api/templates/assets/sirsoft-admin_basic/{path}?identifier=example-key HTTP/1.1 Host: api.example.com Accept: application/json ``` @@ -2641,6 +3589,58 @@ Accept: application/json **설명** 템플릿의 정적 자산 파일(JS/CSS/이미지 등)을 서빙하는 공개 엔드포인트입니다. FormRequest에서 경로 보안 검증을 마친 뒤 파일을 조회하며(TemplateService::getAssetFilePath), ETag 및 장기 캐싱 헤더와 함께 반환합니다. 허용되지 않은 파일 유형은 403, 파일 부재 시 404입니다. 인증이 필요 없습니다. +### GET /api/templates/{identifier}/components + +- **라우트명**: `api.public.templates.components.extensionless` +- **컨트롤러**: `App\Http\Controllers\Api\Public\PublicTemplateController@serveComponents` +- **인증/권한**: 공개 (인증 불필요) + +**요청 파라미터** + +| 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | +| --- | --- | --- | --- | --- | --- | +| identifier | path | string | 예 | — | 대상 리소스의 식별자 | + +**요청 예시** + +```http +GET /api/templates/sirsoft-admin_basic/components HTTP/1.1 +Host: api.example.com +Accept: application/json +``` + +**응답 필드** (`data` 내부) + + + + + +**응답 예시** + +```http +HTTP/1.1 200 +``` + +```json +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "templateId": "sirsoft-admin_basic", + "version": "1.0.0", + "...": "(1개 키 생략, 총 4개)" +} +``` + +**에러 응답** + +| 상태코드 | 의미 | 발생 조건 | +| --- | --- | --- | +| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 | + + + +**설명** + + ### GET /api/templates/{identifier}/components.json - **라우트명**: `api.public.templates.components` @@ -2656,18 +3656,18 @@ Accept: application/json **요청 예시** ```http -GET /api/templates/{identifier}/components.json HTTP/1.1 +GET /api/templates/sirsoft-admin_basic/components.json HTTP/1.1 Host: api.example.com Accept: application/json ``` **응답 필드** (`data` 내부) - + **응답 예시** - + **에러 응답** @@ -2680,6 +3680,227 @@ Accept: application/json **설명** 템플릿의 컴포넌트 정의(components.json)를 서빙하는 공개 엔드포인트입니다(TemplateService::getComponentsFilePath). 프론트엔드 렌더 엔진 부팅에 사용하며 1시간 캐시됩니다. 인증이 필요 없습니다. +### GET /api/templates/{identifier}/config + +- **라우트명**: `api.public.templates.config.extensionless` +- **컨트롤러**: `App\Http\Controllers\Api\Public\PublicTemplateController@serveConfig` +- **인증/권한**: 공개 (인증 불필요) + +**요청 파라미터** + +| 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | +| --- | --- | --- | --- | --- | --- | +| identifier | path | string | 예 | — | 대상 리소스의 식별자 | + +**요청 예시** + +```http +GET /api/templates/sirsoft-admin_basic/config HTTP/1.1 +Host: api.example.com +Accept: application/json +``` + +**응답 필드** (`data` 내부) + +_단건 응답: `data` 객체의 필드._ + +| 필드 | 타입 | 실측 예시값 | 용도/설명 | +| --- | --- | --- | --- | +| identifier | string | `sirsoft-admin_basic` | 템플릿 고유 식별자 (vendor-name 형식, 예: sirsoft-admin_basic) | +| vendor | string | `sirsoft` | 벤더/개발자명 (예: sirsoft) | +| name | object | `{"ko":"Admin Basic","en":"Admin Basic"}` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | +| version | string | `1.0.4` | 템플릿 버전 (예: 1.0.0) | +| license | string | `MIT` | | +| description | object | `{"ko":"그누보드7 기본 관리자 템플릿","en":"Gnuboard7 default admin te…` | 설명 (다국어 필드는 로케일별 값 객체) | +| type | string | `admin` | 템플릿 타입 (admin: 관리자용, user: 사용자용) | +| locales | array | `["ko","en"]` | 활성 로케일 코드 배열 | +| author | object | `{"name":"sirsoft","email":"contact@sirsoft.com","url":"ht…` | 작성자 사용자 객체 (uuid/name — author 관계 파생) | +| release_date | string | `2026-05-15` | | +| g7_version | string | `>=7.0.6` | | +| dependencies | object | `{"modules":[],"plugins":[]}` | 의존하는 확장 맵 (manifest 파생 — {modules, plugins}) | +| assets | object | `{"css":["assets\/css\/main.css","assets\/css\/ui-system.c…` | 프론트엔드 에셋 매니페스트 (manifest 파생 — js/css 진입점·로딩 전략) | +| components | object | `{"basic":["a","button","checkbox","div","form","h1","h2",…` | | +| preview | object | `{"thumbnail":"preview\/thumbnail.png","screenshots":["pre…` | | +| error_config | object | `{"layouts":{"401":"errors\/401","403":"errors\/403","404"…` | | +| github_url | string | `https://github.com/gnuboard/g7-templa…` | GitHub 저장소 URL (manifest 파생) | +| github_changelog_url | string | `https://github.com/gnuboard/g7-templa…` | GitHub 변경 이력(CHANGELOG) URL (manifest 파생) | +| externals | array | `[{"id":"fontawesome","type":"style","url":"https:\/\/cdnj…` | | +| cache_version | integer | `1785848038` | | + +**응답 예시** + +```http +HTTP/1.1 200 +``` + +```json +{ + "success": true, + "message": "템플릿 설정 정보를 조회했습니다.", + "data": { + "identifier": "sirsoft-admin_basic", + "vendor": "sirsoft", + "name": { + "ko": "Admin Basic", + "en": "Admin Basic" + }, + "version": "1.0.4", + "license": "MIT", + "description": { + "ko": "그누보드7 기본 관리자 템플릿", + "en": "Gnuboard7 default admin template" + }, + "type": "admin", + "locales": [ + "ko", + "en" + ], + "author": { + "name": "sirsoft", + "email": "contact@sirsoft.com", + "url": "https://sirsoft.com" + }, + "release_date": "2026-05-15", + "g7_version": ">=7.0.6", + "dependencies": { + "modules": [], + "plugins": [] + }, + "assets": { + "css": [ + "assets/css/main.css", + "assets/css/ui-system.css" + ], + "js": [ + "dist/components.js" + ], + "fonts": [ + "assets/fonts/" + ], + "images": [ + "assets/images/" + ] + }, + "components": { + "basic": [ + "a", + "button", + "checkbox", + "div", + "form", + "h1", + "h2", + "h3", + "hr", + "icon", + "img", + "input", + "label", + "li", + "nav", + "p", + "section", + "select", + "span", + "svg", + "table", + "tbody", + "td", + "textarea", + "th", + "thead", + "tr", + "ul" + ], + "composite": [ + "action-menu", + "admin-footer", + "admin-header", + "admin-sidebar", + "alert-dialog", + "breadcrumb", + "card", + "confirm-dialog", + "data-grid", + "dialog", + "dropdown", + "empty-state", + "filter-group", + "icon-button", + "loading-spinner", + "modal", + "page-header", + "pagination", + "product-card", + "search-bar", + "stat-card", + "status-badge", + "tab-navigation", + "template-card", + "toast" + ], + "layout": [ + "container", + "flex", + "grid", + "section" + ] + }, + "preview": { + "thumbnail": "preview/thumbnail.png", + "screenshots": [ + "preview/screenshot1.png", + "preview/screenshot2.png" + ] + }, + "error_config": { + "layouts": { + "401": "errors/401", + "403": "errors/403", + "404": "errors/404", + "500": "errors/500", + "503": "errors/503", + "maintenance": "errors/maintenance" + } + }, + "github_url": "https://github.com/gnuboard/g7-template-sirsoft-admin_basic", + "github_changelog_url": "https://github.com/gnuboard/g7-template-sirsoft-admin_basic/blob/main/CHANGELOG.md", + "externals": [ + { + "id": "fontawesome", + "type": "style", + "url": "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css", + "preconnect": "https://cdnjs.cloudflare.com" + }, + { + "id": "pretendard", + "type": "webfont", + "url": "https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css", + "preconnect": "https://cdn.jsdelivr.net", + "crossorigin": "anonymous" + }, + { + "id": "flag-icons", + "type": "style", + "url": "https://cdn.jsdelivr.net/npm/flag-icons@7.2.3/css/flag-icons.min.css" + } + ], + "cache_version": 1785848038 + } +} +``` + +**에러 응답** + +| 상태코드 | 의미 | 발생 조건 | +| --- | --- | --- | +| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 | + + + +**설명** + + ### GET /api/templates/{identifier}/config.json - **라우트명**: `api.public.templates.config` @@ -2695,18 +3916,18 @@ Accept: application/json **요청 예시** ```http -GET /api/templates/{identifier}/config.json HTTP/1.1 +GET /api/templates/sirsoft-admin_basic/config.json HTTP/1.1 Host: api.example.com Accept: application/json ``` **응답 필드** (`data` 내부) - + **응답 예시** - + **에러 응답** @@ -2734,7 +3955,72 @@ Accept: application/json **요청 예시** ```http -GET /api/templates/{identifier}/editor-spec HTTP/1.1 +GET /api/templates/sirsoft-admin_basic/editor-spec HTTP/1.1 +Host: api.example.com +Accept: application/json +``` + +**응답 필드** (`data` 내부) + +_단건 응답: `data` 객체의 필드._ + +| 필드 | 타입 | 실측 예시값 | 용도/설명 | +| --- | --- | --- | --- | +| identifier | string | `sirsoft-admin_basic` | 템플릿 고유 식별자 (vendor-name 형식, 예: sirsoft-admin_basic) | +| spec | object | `{"$schema":"https:\/\/json-schema.org\/draft\/2020-12\/sc…` | 스펙 정의 객체 (편집기/컴포넌트 선언 스키마 등) | + +**응답 예시** + +```http +HTTP/1.1 200 +``` + +```json +{ + "success": true, + "message": "편집기 스펙을 조회했습니다.", + "data": { + "identifier": "sirsoft-admin_basic", + "spec": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "templateId": "sirsoft-admin_basic", + "version": "1.0.0", + "description": "레이아웃 편집기 스펙 — Phase 3 (nesting + componentPalette 블록). controls/componentCapabilities/actionRecipes 등은 Phase 4/5 에서 추가.", + "styleSystem": "tailwind", + "...": "(14개 키 생략, 총 19개)" + } + } +} +``` + +**에러 응답** + +| 상태코드 | 의미 | 발생 조건 | +| --- | --- | --- | +| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 | + + + +**설명** 템플릿의 편집기 스펙(editor-spec.json)을 서빙하는 공개 엔드포인트입니다. 분할 스펙은 manifest + `$include` 블록을 합본한 단일 spec으로(활성 디렉토리 기준) 반환하며, 파일 미존재 시 `spec=null`로 폴백합니다. 인증이 필요 없습니다. + + +### GET /api/templates/{identifier}/lang/{locale} + +- **라우트명**: `api.public.templates.language.extensionless` +- **컨트롤러**: `App\Http\Controllers\Api\Public\PublicTemplateController@serveLanguage` +- **인증/권한**: 공개 (인증 불필요) + +**요청 파라미터** + +| 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | +| --- | --- | --- | --- | --- | --- | +| identifier | path | string | 예 | — | 대상 리소스의 식별자 | +| locale | path | string | 예 | — | 로케일 코드 (표시 언어/지역) | + +**요청 예시** + +```http +GET /api/templates/sirsoft-admin_basic/lang/{locale} HTTP/1.1 Host: api.example.com Accept: application/json ``` @@ -2755,7 +4041,7 @@ Accept: application/json -**설명** 템플릿의 편집기 스펙(editor-spec.json)을 서빙하는 공개 엔드포인트입니다. 분할 스펙은 manifest + `$include` 블록을 합본한 단일 spec으로(활성 디렉토리 기준) 반환하며, 파일 미존재 시 `spec=null`로 폴백합니다. 인증이 필요 없습니다. +**설명** ### GET /api/templates/{identifier}/lang/{locale}.json @@ -2774,7 +4060,7 @@ Accept: application/json **요청 예시** ```http -GET /api/templates/{identifier}/lang/{locale}.json HTTP/1.1 +GET /api/templates/sirsoft-admin_basic/lang/{locale}.json HTTP/1.1 Host: api.example.com Accept: application/json ``` @@ -2814,7 +4100,7 @@ Accept: application/json **요청 예시** ```http -GET /api/templates/{identifier}/layout-attachments/{attachment}/file HTTP/1.1 +GET /api/templates/sirsoft-admin_basic/layout-attachments/{attachment}/file HTTP/1.1 Host: api.example.com Accept: application/json ``` @@ -2838,6 +4124,125 @@ Accept: application/json **설명** 레이아웃 첨부 이미지 파일을 서빙하는 공개 엔드포인트입니다. 발행된 배경 이미지가 일반 방문자에게도 로드되어야 하므로 인증 없이 노출하며, 비공개 attachments 디스크의 파일을 캐싱 헤더와 함께 인라인 스트림합니다. 첨부가 경로의 템플릿 소속이 아니거나 파일이 없으면 404입니다. 인증이 필요 없습니다. +### GET /api/templates/{identifier}/routes + +- **라우트명**: `api.public.templates.routes.extensionless` +- **컨트롤러**: `App\Http\Controllers\Api\Public\PublicTemplateController@getRoutes` +- **인증/권한**: 공개 (인증 불필요) + +**요청 파라미터** + +| 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | +| --- | --- | --- | --- | --- | --- | +| identifier | path | string | 예 | — | 대상 리소스의 식별자 | + +**요청 예시** + +```http +GET /api/templates/sirsoft-admin_basic/routes HTTP/1.1 +Host: api.example.com +Accept: application/json +``` + +**응답 필드** (`data` 내부) + +_단건 응답: `data` 객체의 필드._ + +| 필드 | 타입 | 실측 예시값 | 용도/설명 | +| --- | --- | --- | --- | +| version | string | `1.0.0` | 템플릿 버전 (예: 1.0.0) | +| routes | array | `[{"path":"*\/admin","redirect":"\/admin\/dashboard","auth…` | | + +**응답 예시** + +```http +HTTP/1.1 200 +``` + +```json +{ + "success": true, + "message": "템플릿 라우트 정보를 조회했습니다.", + "data": { + "version": "1.0.0", + "routes": [ + { + "path": "*/admin", + "redirect": "/admin/dashboard", + "auth_required": true, + "meta": { + "title": "관리자" + }, + "source": { + "kind": "template", + "identifier": null + } + }, + { + "path": "*/admin/login", + "layout": "admin_login", + "auth_required": false, + "meta": { + "title": "관리자 로그인" + }, + "source": { + "kind": "template", + "identifier": null + } + }, + { + "path": "*/admin/forgot-password", + "layout": "admin_forgot_password", + "auth_required": false, + "meta": { + "title": "$t:auth.forgot_password.title" + }, + "source": { + "kind": "template", + "identifier": null + } + }, + { + "path": "*/admin/reset-password", + "layout": "admin_reset_password", + "auth_required": false, + "meta": { + "title": "$t:auth.reset_password.title" + }, + "source": { + "kind": "template", + "identifier": null + } + }, + { + "path": "*/admin/dashboard", + "layout": "admin_dashboard", + "auth_required": true, + "meta": { + "title": "관리자 대시보드" + }, + "source": { + "kind": "template", + "identifier": null + } + }, + "... (총 85건 중 5건 표시)" + ] + } +} +``` + +**에러 응답** + +| 상태코드 | 의미 | 발생 조건 | +| --- | --- | --- | +| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 | + + + +**설명** + + ### GET /api/templates/{identifier}/routes.json - **라우트명**: `api.public.templates.routes` @@ -2853,18 +4258,18 @@ Accept: application/json **요청 예시** ```http -GET /api/templates/{identifier}/routes.json HTTP/1.1 +GET /api/templates/sirsoft-admin_basic/routes.json HTTP/1.1 Host: api.example.com Accept: application/json ``` **응답 필드** (`data` 내부) - + **응답 예시** - + **에러 응답** diff --git a/docs/backend/api/users.md b/docs/backend/api/users.md index e74b099b..04ae823d 100644 --- a/docs/backend/api/users.md +++ b/docs/backend/api/users.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Users 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` @@ -53,25 +53,25 @@ _목록 응답: `data.data[]` 배열 항목의 필드 + `data.pagination`._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| number | integer | `156` | 목록에서의 순번 (페이지네이션 반영 행 번호 — HasRowNumber 파생) | -| uuid | string | `a231747f-e82e-4cf2-9ae1-a261849dce40` | 외부 노출용 UUID (URL/API 식별자, 내부 id 비노출) | -| name | string | `API 문서 샘플 사용자` | 사용자 이름 | -| nickname | string | `gunwoo.oh` | 닉네임 | -| email | string | `apidoc-sample-user@example.com` | 이메일 주소 | +| number | integer | `82` | 목록에서의 순번 (페이지네이션 반영 행 번호 — HasRowNumber 파생) | +| uuid | string | `a26b99a4-5a47-4233-ba2e-41d2b7e9d2c9` | 외부 노출용 UUID (URL/API 식별자, 내부 id 비노출) | +| name | string | `설규산` | 사용자 이름 | +| nickname | null | `null` | 닉네임 | +| email | string | `gunho.choi@example.com` | 이메일 주소 | | language | string | `ko` | 사용자 언어 설정 (ko: 한국어, en: 영어) | | language_label | string | `한국어` | 언어 코드의 현지화 라벨 (user.language.{code} 번역) | -| country | string | `KR` | 국가 코드 (ISO 3166-1 alpha-2) | -| country_flag | string | `🇰🇷` | 국가 코드의 국기 이모지 (country 값에서 파생) | -| country_name | string | `한국` | 국가 코드의 현지화 국가명 (country 값에서 파생) | +| country | null | `null` | 국가 코드 (ISO 3166-1 alpha-2) | +| country_flag | null | `null` | 국가 코드의 국기 이모지 (country 값에서 파생) | +| country_name | null | `null` | 국가 코드의 현지화 국가명 (country 값에서 파생) | | status | string | `active` | 계정 상태 (active: 활성, inactive: 비활성, blocked: 차단, withdrawn: 탈퇴) | | status_label | string | `활성` | 상태의 사람이 읽는 라벨 (상태 Enum label() 산물) | | status_variant | string | `success` | 상태 표시 색상/스타일 변형 키 (상태 Enum variant() 산물 — UI 배지용) | -| mobile | string | `010-9070-5662` | 휴대폰 번호 | -| roles | array | `[{"id":1,"identifier":"admin","name":"관리자"}]` | 사용자에게 부여된 역할 목록 (원소: id/identifier/name — 역할 관계 파생) | -| email_verified_at | string | `2026-07-06 19:15:16` | email verified 일시 | -| last_login_at | string | `2026-07-05 19:15:16` | last login 일시 | -| created_at | string | `2026-07-06` | 생성 일시 | -| is_owner | boolean | `true` | 현재 인증 사용자가 이 리소스의 소유자인지 여부 (BaseApiResource 표준 메타) | +| mobile | null | `null` | 휴대폰 번호 | +| roles | array | `[{"id":1,"identifier":"admin","name":"관리자"},{"id":892,"id…` | 사용자에게 부여된 역할 목록 (원소: id/identifier/name — 역할 관계 파생) | +| email_verified_at | string | `2026-08-04 16:57:08` | email verified 일시 | +| last_login_at | null | `null` | last login 일시 | +| created_at | string | `2026-08-04` | 생성 일시 | +| is_owner | boolean | `false` | 현재 인증 사용자가 이 리소스의 소유자인지 여부 (BaseApiResource 표준 메타) | | abilities | object | `{"can_read":true,"can_create":true,"can_update":true,"can…` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | **응답 예시** @@ -87,11 +87,11 @@ HTTP/1.1 200 "data": { "data": [ { - "number": 2, - "uuid": "a234c3ea-a4f9-496d-a5e7-b44f0d53fc2f", - "name": "선지영", + "number": 82, + "uuid": "a26b99a4-5a47-4233-ba2e-41d2b7e9d2c9", + "name": "설규산", "nickname": null, - "email": "jihee74@example.net", + "email": "gunho.choi@example.com", "language": "ko", "language_label": "한국어", "country": null, @@ -101,10 +101,21 @@ HTTP/1.1 200 "status_label": "활성", "status_variant": "success", "mobile": null, - "roles": [], - "email_verified_at": "2026-07-08 10:44:49", + "roles": [ + { + "id": 1, + "identifier": "admin", + "name": "관리자" + }, + { + "id": 892, + "identifier": "playwright_test_6a719b554d5f5", + "name": "Playwright 테스트 관리자" + } + ], + "email_verified_at": "2026-08-04 16:57:08", "last_login_at": null, - "created_at": "2026-07-08", + "created_at": "2026-08-04", "is_owner": false, "abilities": { "can_read": true, @@ -115,20 +126,20 @@ HTTP/1.1 200 } }, { - "number": 1, - "uuid": "a234c2b1-cde8-437f-b28b-23323be2b98d", - "name": "API 문서 샘플 사용자", - "nickname": "song.hyunji", - "email": "apidoc-sample-user@example.com", + "number": 81, + "uuid": "a26b999a-d541-4865-b170-da2eba823996", + "name": "채하은", + "nickname": null, + "email": "eahn@example.com", "language": "ko", "language_label": "한국어", - "country": "KR", - "country_flag": "🇰🇷", - "country_name": "한국", + "country": null, + "country_flag": null, + "country_name": null, "status": "active", "status_label": "활성", "status_variant": "success", - "mobile": "010-9595-2897", + "mobile": null, "roles": [ { "id": 1, @@ -136,15 +147,15 @@ HTTP/1.1 200 "name": "관리자" }, { - "id": 4, - "identifier": "apidoc-sample-role", - "name": "API 문서 샘플 역할" + "id": 891, + "identifier": "playwright_test_6a719b4eed13d", + "name": "Playwright 테스트 관리자" } ], - "email_verified_at": "2026-07-08 10:41:24", - "last_login_at": "2026-07-07 10:41:24", - "created_at": "2026-07-08", - "is_owner": true, + "email_verified_at": "2026-08-04 16:57:02", + "last_login_at": null, + "created_at": "2026-08-04", + "is_owner": false, "abilities": { "can_read": true, "can_create": true, @@ -152,14 +163,15 @@ HTTP/1.1 200 "can_delete": true, "can_assign_roles": true } - } + }, + "... (총 25건 중 2건 표시)" ], "statistics": { - "total_users": 2, - "users_this_week": 2, - "users_this_month": 2, - "users_today": 2, - "active_users_this_week": 1 + "total_users": 82, + "users_this_week": 82, + "users_this_month": 82, + "users_today": 70, + "active_users_this_week": 3 }, "abilities": { "can_create": true, @@ -169,12 +181,12 @@ HTTP/1.1 200 }, "pagination": { "current_page": 1, - "last_page": 1, + "last_page": 4, "per_page": 25, - "total": 2, + "total": 82, "from": 1, - "to": 2, - "has_more_pages": false + "to": 25, + "has_more_pages": true } } } @@ -209,7 +221,7 @@ HTTP/1.1 200 | nickname | body | string | 아니오 | max 50 | 닉네임 | | email | body | email | 예 | max 255 | 이메일 주소 | | password | body | string | 예 | — | 비밀번호 | -| language | body | string | 아니오 | `ko`, `en`, `fr`, `ja` | 언어 코드 | +| language | body | string | 아니오 | `ko`, `en` | 언어 코드 | | country | body | string | 아니오 | — | 국가 코드 (ISO 3166-1 alpha-2) | | timezone | body | string | 아니오 | — | 타임존 식별자 | | status | body | string | 아니오 | `active`, `inactive`, `blocked`, `withdrawn` | 계정 상태 (미지정 시 active) | @@ -271,13 +283,17 @@ Content-Type: application/json "notify_post_complete": true, "notify_post_reply": true, "notify_comment": true, - "notify_reply_comment": true + "notify_reply_comment": true, + "email_subscription": true, + "marketing_consent": true, + "third_party_consent": true, + "info_disclosure": true } ``` **응답 필드** (`data` 내부) - + **응답 예시** @@ -332,7 +348,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -385,7 +401,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -416,7 +432,7 @@ Content-Type: application/json | 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | | --- | --- | --- | --- | --- | --- | -| language | body | string | 예 | `ko`, `en`, `fr`, `ja` | 언어 코드 | +| language | body | string | 예 | `ko`, `en` | 언어 코드 | > 이 엔드포인트는 확장이 파라미터를 추가할 수 있습니다 (`core.user.update_language_validation_rules`). @@ -440,31 +456,31 @@ _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| uuid | string | `a234c2b1-cde8-437f-b28b-23323be2b98d` | 외부 노출용 UUID (URL/API 식별자, 내부 id 비노출) | -| name | string | `API 문서 샘플 사용자` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | -| nickname | string | `song.hyunji` | 닉네임 | -| email | string | `apidoc-sample-user@example.com` | 이메일 주소 | +| uuid | string | `a26219fc-94a0-4f63-9404-04c2a6ac99e4` | 외부 노출용 UUID (URL/API 식별자, 내부 id 비노출) | +| name | string | `최고관리자` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | +| nickname | string | `최고관리자` | 닉네임 | +| email | string | `heuristing@gmail.com` | 이메일 주소 | | avatar | null | `null` | 아바타 이미지 URL (미등록 시 null) | | language | string | `ko` | 사용자 언어 설정 (ko: 한국어, en: 영어) | | language_label | string | `한국어` | 언어 코드의 현지화 라벨 (user.language.{code} 번역) | -| country | string | `KR` | 국가 코드 (ISO 3166-1 alpha-2) | +| country | null | `null` | 국가 코드 (ISO 3166-1 alpha-2) | | status | string | `active` | 상태 값 (도메인별 상태 집합 — 사람이 읽는 라벨은 status_label, UI 변형은 status_variant 참조) | | status_label | string | `활성` | 상태의 사람이 읽는 라벨 (상태 Enum label() 산물) | | status_variant | string | `success` | 상태 표시 색상/스타일 변형 키 (상태 Enum variant() 산물 — UI 배지용) | | is_admin | boolean | `true` | 관리자 역할 보유 여부 (User::isAdmin() — 역할 관계 기반 파생) | -| homepage | string | `https://example.com` | 홈페이지 URL | -| mobile | string | `010-9595-2897` | 휴대폰 번호 | -| phone | string | `02-637-5618` | 전화번호 | -| zipcode | string | `16505` | 우편번호 | -| address | string | `경기도 안양시 봉은사로 2918` | 기본 주소 | -| address_detail | string | `48동 718호` | 상세 주소 | -| signature | string | `Fugit consequuntur repellendus sed.` | 서명 | -| bio | string | `Ut magni et sunt ducimus error adipis…` | 자기소개 | -| last_login_at | string | `2026-07-07 10:41:24` | last login 일시 | -| email_verified_at | string | `2026-07-08 10:41:24` | email verified 일시 | +| homepage | null | `null` | 홈페이지 URL | +| mobile | null | `null` | 휴대폰 번호 | +| phone | null | `null` | 전화번호 | +| zipcode | null | `null` | 우편번호 | +| address | null | `null` | 기본 주소 | +| address_detail | null | `null` | 상세 주소 | +| signature | null | `null` | 서명 | +| bio | null | `null` | 자기소개 | +| last_login_at | string | `2026-08-04 19:00:10` | last login 일시 | +| email_verified_at | string | `2026-07-30 23:37:44` | email verified 일시 | | timezone | string | `Asia/Seoul` | 사용자 시간대 (예: Asia/Seoul, UTC) | -| created_at | string | `2026-07-08 10:41:24` | 생성 일시 | -| updated_at | string | `2026-07-08 10:41:24` | 최종 수정 일시 | +| created_at | string | `2026-07-30 23:37:44` | 생성 일시 | +| updated_at | string | `2026-08-04 19:00:10` | 최종 수정 일시 | | is_owner | boolean | `true` | 현재 인증 사용자가 이 리소스의 소유자인지 여부 (BaseApiResource 표준 메타) | | abilities | object | `{"can_read":true,"can_create":true,"can_update":true,"can…` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | @@ -479,37 +495,37 @@ HTTP/1.1 200 "success": true, "message": "언어 설정이 성공적으로 변경되었습니다.", "data": { - "uuid": "a234c2b1-cde8-437f-b28b-23323be2b98d", - "name": "API 문서 샘플 사용자", - "nickname": "song.hyunji", - "email": "apidoc-sample-user@example.com", + "uuid": "a26219fc-94a0-4f63-9404-04c2a6ac99e4", + "name": "최고관리자", + "nickname": "최고관리자", + "email": "heuristing@gmail.com", "avatar": null, "language": "ko", "language_label": "한국어", - "country": "KR", + "country": null, "status": "active", "status_label": "활성", "status_variant": "success", "is_admin": true, - "homepage": "https://example.com", - "mobile": "010-9595-2897", - "phone": "02-637-5618", - "zipcode": "16505", - "address": "경기도 안양시 봉은사로 2918", - "address_detail": "48동 718호", - "signature": "Fugit consequuntur repellendus sed.", - "bio": "Ut magni et sunt ducimus error adipisci. Pariatur corporis voluptatem ratione quo non saepe. Illo atque praesentium possimus dolores qui est fugit. Sint fugiat numquam voluptates.", - "last_login_at": "2026-07-07 10:41:24", - "email_verified_at": "2026-07-08 10:41:24", + "homepage": null, + "mobile": null, + "phone": null, + "zipcode": null, + "address": null, + "address_detail": null, + "signature": null, + "bio": null, + "last_login_at": "2026-08-04 19:00:10", + "email_verified_at": "2026-07-30 23:37:44", "timezone": "Asia/Seoul", - "created_at": "2026-07-08 10:41:24", - "updated_at": "2026-07-08 10:41:24", + "created_at": "2026-07-30 23:37:44", + "updated_at": "2026-08-04 19:00:10", "is_owner": true, "abilities": { "can_read": true, "can_create": true, "can_update": true, - "can_delete": true, + "can_delete": false, "can_assign_roles": true } } @@ -553,32 +569,32 @@ Authorization: Bearer {YOUR_TOKEN} | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| uuid | string | `a231747f-e82e-4cf2-9ae1-a261849dce40` | 외부 노출용 UUID (URL/API 식별자, 내부 id 비노출) | -| name | string | `API 문서 샘플 사용자` | 사용자 이름 | -| nickname | string | `gunwoo.oh` | 닉네임 | -| email | string | `apidoc-sample-user@example.com` | 이메일 주소 | +| uuid | string | `a26b99a4-5a47-4233-ba2e-41d2b7e9d2c9` | 외부 노출용 UUID (URL/API 식별자, 내부 id 비노출) | +| name | string | `설규산` | 사용자 이름 | +| nickname | null | `null` | 닉네임 | +| email | string | `gunho.choi@example.com` | 이메일 주소 | | avatar | null | `null` | 프로필 아바타 이미지 URL (미설정 시 null) | | language | string | `ko` | 사용자 언어 설정 (ko: 한국어, en: 영어) | | language_label | string | `한국어` | 언어 코드의 현지화 라벨 (user.language.{code} 번역) | -| country | string | `KR` | 국가 코드 (ISO 3166-1 alpha-2) | +| country | null | `null` | 국가 코드 (ISO 3166-1 alpha-2) | | status | string | `active` | 계정 상태 (active: 활성, inactive: 비활성, blocked: 차단, withdrawn: 탈퇴) | | status_label | string | `활성` | 상태의 사람이 읽는 라벨 (상태 Enum label() 산물) | | status_variant | string | `success` | 상태 표시 색상/스타일 변형 키 (상태 Enum variant() 산물 — UI 배지용) | | is_admin | boolean | `true` | 관리자 역할 보유 여부 (User::isAdmin() — 역할 관계 기반 파생) | -| homepage | string | `https://example.com` | 홈페이지 URL | -| mobile | string | `010-9070-5662` | 휴대폰 번호 | -| phone | string | `02-805-4759` | 전화번호 | -| zipcode | string | `93153` | 우편번호 | -| address | string | `대구광역시 북구 백제고분로 720` | 기본 주소 | -| address_detail | string | `40동 835호` | 상세 주소 | -| signature | string | `Ipsam rem amet expedita est.` | 서명 | -| bio | string | `Tenetur omnis et amet omnis veniam to…` | 자기소개 | -| last_login_at | string | `2026-07-05 19:15:16` | last login 일시 | -| email_verified_at | string | `2026-07-06 19:15:16` | email verified 일시 | +| homepage | null | `null` | 홈페이지 URL | +| mobile | null | `null` | 휴대폰 번호 | +| phone | null | `null` | 전화번호 | +| zipcode | null | `null` | 우편번호 | +| address | null | `null` | 기본 주소 | +| address_detail | null | `null` | 상세 주소 | +| signature | null | `null` | 서명 | +| bio | null | `null` | 자기소개 | +| last_login_at | null | `null` | last login 일시 | +| email_verified_at | string | `2026-08-04 16:57:08` | email verified 일시 | | timezone | string | `Asia/Seoul` | 사용자 시간대 (예: Asia/Seoul, UTC) | -| created_at | string | `2026-07-06 19:15:16` | 생성 일시 | -| updated_at | string | `2026-07-06 19:15:16` | 최종 수정 일시 | -| is_owner | boolean | `true` | 현재 인증 사용자가 이 리소스의 소유자인지 여부 (BaseApiResource 표준 메타) | +| created_at | string | `2026-08-04 16:57:09` | 생성 일시 | +| updated_at | string | `2026-08-04 16:57:09` | 최종 수정 일시 | +| is_owner | boolean | `false` | 현재 인증 사용자가 이 리소스의 소유자인지 여부 (BaseApiResource 표준 메타) | | abilities | object | `{"can_read":true,"can_create":true,"can_update":true,"can…` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | **응답 예시** @@ -593,75 +609,46 @@ HTTP/1.1 200 "message": "사용자 정보를 성공적으로 가져왔습니다.", "data": [ { - "uuid": "a234c3ea-a4f9-496d-a5e7-b44f0d53fc2f", - "name": "선지영", + "uuid": "a26b99a4-5a47-4233-ba2e-41d2b7e9d2c9", + "name": "설규산", "nickname": null, - "email": "jihee74@example.net", + "email": "gunho.choi@example.com", "avatar": null, - "language": "ko", - "language_label": "한국어", - "country": null, - "status": "active", - "status_label": "활성", - "status_variant": "success", - "is_admin": false, - "homepage": null, - "mobile": null, - "phone": null, - "zipcode": null, - "address": null, - "address_detail": null, - "signature": null, - "bio": null, - "last_login_at": null, - "email_verified_at": "2026-07-08 10:44:49", - "timezone": "Asia/Seoul", - "created_at": "2026-07-08 10:44:49", - "updated_at": "2026-07-08 10:44:49", - "is_owner": false, - "abilities": { - "can_read": true, - "can_create": true, - "can_update": true, - "can_delete": true, - "can_assign_roles": true - } + "...": "(22개 키 생략, 총 27개)" }, { - "uuid": "a234c2b1-cde8-437f-b28b-23323be2b98d", - "name": "API 문서 샘플 사용자", - "nickname": "song.hyunji", - "email": "apidoc-sample-user@example.com", + "uuid": "a26b999a-d541-4865-b170-da2eba823996", + "name": "채하은", + "nickname": null, + "email": "eahn@example.com", "avatar": null, - "language": "ko", - "language_label": "한국어", - "country": "KR", - "status": "active", - "status_label": "활성", - "status_variant": "success", - "is_admin": true, - "homepage": "https://example.com", - "mobile": "010-9595-2897", - "phone": "02-637-5618", - "zipcode": "16505", - "address": "경기도 안양시 봉은사로 2918", - "address_detail": "48동 718호", - "signature": "Fugit consequuntur repellendus sed.", - "bio": "Ut magni et sunt ducimus error adipisci. Pariatur corporis voluptatem ratione quo non saepe. Illo atque praesentium possimus dolores qui est fugit. Sint fugiat numquam voluptates.", - "last_login_at": "2026-07-07 10:41:24", - "email_verified_at": "2026-07-08 10:41:24", - "timezone": "Asia/Seoul", - "created_at": "2026-07-08 10:41:24", - "updated_at": "2026-07-08 10:41:24", - "is_owner": true, - "abilities": { - "can_read": true, - "can_create": true, - "can_update": true, - "can_delete": true, - "can_assign_roles": true - } - } + "...": "(22개 키 생략, 총 27개)" + }, + { + "uuid": "a26b999a-d1c1-4157-a72f-0a1fc9f95c26", + "name": "기종훈", + "nickname": null, + "email": "uno@example.com", + "avatar": null, + "...": "(22개 키 생략, 총 27개)" + }, + { + "uuid": "a26b999a-78a7-4565-a24f-9b9b49d3b47a", + "name": "추나은", + "nickname": null, + "email": "jihoo.lee@example.org", + "avatar": null, + "...": "(22개 키 생략, 총 27개)" + }, + { + "uuid": "a26b999a-7521-4c6c-a03d-68eb72702c22", + "name": "주영호", + "nickname": null, + "email": "doyoun93@example.net", + "avatar": null, + "...": "(22개 키 생략, 총 27개)" + }, + "... (총 10건 중 5건 표시)" ] } ``` @@ -752,12 +739,12 @@ _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| total_users | integer | `156` | 전체 사용자 수 (통계 객체는 count/추이 포함) | -| users_this_week | integer | `1` | 이번 주 신규 가입자 수 | -| users_this_month | integer | `155` | 이번 달 신규 가입자 수 | -| users_today | integer | `0` | 오늘 신규 가입자 수 | -| active_users_this_week | integer | `2` | 이번 주 활동(로그인) 사용자 수 | -| language_distribution | object | `{"ko":92,"en":64}` | 언어별 사용자 분포 (언어 코드 => 사용자 수) | +| total_users | integer | `82` | 전체 사용자 수 (통계 객체는 count/추이 포함) | +| users_this_week | integer | `82` | 이번 주 신규 가입자 수 | +| users_this_month | integer | `82` | 이번 달 신규 가입자 수 | +| users_today | integer | `70` | 오늘 신규 가입자 수 | +| active_users_this_week | integer | `3` | 이번 주 활동(로그인) 사용자 수 | +| language_distribution | object | `{"ko":82}` | 언어별 사용자 분포 (언어 코드 => 사용자 수) | **응답 예시** @@ -770,13 +757,13 @@ HTTP/1.1 200 "success": true, "message": "사용자 통계를 성공적으로 가져왔습니다.", "data": { - "total_users": 2, - "users_this_week": 2, - "users_this_month": 2, - "users_today": 2, - "active_users_this_week": 1, + "total_users": 82, + "users_this_week": 82, + "users_this_month": 82, + "users_today": 70, + "active_users_this_week": 3, "language_distribution": { - "ko": 2 + "ko": 82 } } } @@ -811,7 +798,7 @@ HTTP/1.1 200 **요청 예시** ```http -DELETE /api/admin/users/a234c2b1-cde8-437f-b28b-23323be2b98d HTTP/1.1 +DELETE /api/admin/users/a26219fc-94a0-4f63-9404-04c2a6ac99e4 HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -819,23 +806,11 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - - - + **응답 예시** -```http -HTTP/1.1 200 -``` - -```json -{ - "success": true, - "message": "사용자가 성공적으로 삭제되었습니다.", - "data": null -} -``` + **에러 응답** @@ -853,78 +828,6 @@ HTTP/1.1 200 지정한 사용자(경로 파라미터는 UUID 로 바인딩)를 삭제합니다. 슈퍼관리자 계정은 삭제할 수 없으며 시도 시 422(`exceptions.cannot_delete_super_admin`)를 반환한다. 그 외 삭제 실패 시에는 실패 상세 사유가 담긴 422 를, 나머지 오류는 500 을 반환한다. 삭제는 Service 계층에서 관련 데이터 정리와 훅을 거쳐 처리된다. -### POST /api/admin/users/{user}/unlock - -- **라우트명**: `api.admin.users.unlock` -- **컨트롤러**: `App\Http\Controllers\Api\Admin\UserController@unlock` -- **인증/권한**: `auth:sanctum` + `permission:core.users.update` - -**요청 파라미터** - -| 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | -| --- | --- | --- | --- | --- | --- | -| user | path | string | 예 | — | 대상 user 의 UUID | - -본문 파라미터는 없습니다. - -**요청 예시** - -```http -POST /api/admin/users/a234c2b1-cde8-437f-b28b-23323be2b98d/unlock HTTP/1.1 -Host: api.example.com -Accept: application/json -Authorization: Bearer {YOUR_TOKEN} -``` - -**응답 필드** (`data` 내부) - -갱신된 사용자 리소스(`UserResource`)를 반환합니다. 관리자 응답에는 잠금 상태 필드가 포함됩니다. - -| 이름 | 타입 | 용도 | -| --- | --- | --- | -| is_locked | boolean | 현재 잠금 여부 (영구 잠금이거나 `locked_until` 이 미래) | -| locked_permanently | boolean | 무기한 잠금 여부 | -| locked_until | string\|null | 잠금 해제 예정 시각 (영구 잠금이면 null) | -| failed_login_attempts | integer | 연속 로그인 실패 횟수 | - -**응답 예시** - -```http -HTTP/1.1 200 -``` - -```json -{ - "success": true, - "message": "계정 잠금이 해제되었습니다.", - "data": { - "uuid": "a234c2b1-cde8-437f-b28b-23323be2b98d", - "is_locked": false, - "locked_permanently": false, - "locked_until": null, - "failed_login_attempts": 0 - } -} -``` - -**에러 응답** - -| 상태코드 | 의미 | 발생 조건 | -| --- | --- | --- | -| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 | -| 403 | Forbidden | 요구 권한(`core.users.update`)이 없는 경우 | -| 404 | Not Found | 해당 UUID 의 사용자가 없는 경우 | - -**설명** - -로그인 실패 누적으로 잠긴 계정의 잠금을 관리자가 수동으로 해제합니다. 로그인 시도 추적 값 -(`failed_login_attempts` / `locked_until` / `locked_permanently`)을 모두 초기화합니다. - -보안 환경설정의 잠금 시간(`security.login_lockout_time`)을 `0`(무한대)으로 두면 계정이 무기한 -잠기며, 이 경우 자동 해제도 성공 로그인도 불가능하므로 **이 엔드포인트가 유일한 복구 경로**입니다. -해제는 활동 로그(`auth.account_unlocked`)에 기록됩니다. - - ### GET /api/admin/users/{user} - **라우트명**: `api.admin.users.show` @@ -940,7 +843,7 @@ HTTP/1.1 200 **요청 예시** ```http -GET /api/admin/users/a234c2b1-cde8-437f-b28b-23323be2b98d HTTP/1.1 +GET /api/admin/users/a26219fc-94a0-4f63-9404-04c2a6ac99e4 HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -952,48 +855,52 @@ _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| uuid | string | `a231747f-e82e-4cf2-9ae1-a261849dce40` | 외부 노출용 UUID (URL/API 식별자, 내부 id 비노출) | -| name | string | `API 문서 샘플 사용자` | 사용자 이름 | -| nickname | string | `gunwoo.oh` | 닉네임 | -| email | string | `apidoc-sample-user@example.com` | 이메일 주소 | +| uuid | string | `a26219fc-94a0-4f63-9404-04c2a6ac99e4` | 외부 노출용 UUID (URL/API 식별자, 내부 id 비노출) | +| name | string | `최고관리자` | 사용자 이름 | +| nickname | string | `최고관리자` | 닉네임 | +| email | string | `heuristing@gmail.com` | 이메일 주소 | | avatar | null | `null` | 프로필 아바타 이미지 URL (미설정 시 null) | | language | string | `ko` | 사용자 언어 설정 (ko: 한국어, en: 영어) | | language_label | string | `한국어` | 언어 코드의 현지화 라벨 (user.language.{code} 번역) | -| country | string | `KR` | 국가 코드 (ISO 3166-1 alpha-2) | +| country | null | `null` | 국가 코드 (ISO 3166-1 alpha-2) | | status | string | `active` | 계정 상태 (active: 활성, inactive: 비활성, blocked: 차단, withdrawn: 탈퇴) | | status_label | string | `활성` | 상태의 사람이 읽는 라벨 (상태 Enum label() 산물) | | status_variant | string | `success` | 상태 표시 색상/스타일 변형 키 (상태 Enum variant() 산물 — UI 배지용) | | is_admin | boolean | `true` | 관리자 역할 보유 여부 (User::isAdmin() — 역할 관계 기반 파생) | -| homepage | string | `https://example.com` | 홈페이지 URL | -| mobile | string | `010-9070-5662` | 휴대폰 번호 | -| phone | string | `02-805-4759` | 전화번호 | -| zipcode | string | `93153` | 우편번호 | -| address | string | `대구광역시 북구 백제고분로 720` | 기본 주소 | -| address_detail | string | `40동 835호` | 상세 주소 | -| signature | string | `Ipsam rem amet expedita est.` | 서명 | -| bio | string | `Tenetur omnis et amet omnis veniam to…` | 자기소개 | -| last_login_at | string | `2026-07-05 19:15:16` | last login 일시 | -| email_verified_at | string | `2026-07-06 19:15:16` | email verified 일시 | +| homepage | null | `null` | 홈페이지 URL | +| mobile | null | `null` | 휴대폰 번호 | +| phone | null | `null` | 전화번호 | +| zipcode | null | `null` | 우편번호 | +| address | null | `null` | 기본 주소 | +| address_detail | null | `null` | 상세 주소 | +| signature | null | `null` | 서명 | +| bio | null | `null` | 자기소개 | +| last_login_at | string | `2026-08-04 19:00:10` | last login 일시 | +| email_verified_at | string | `2026-07-30 23:37:44` | email verified 일시 | | timezone | string | `Asia/Seoul` | 사용자 시간대 (예: Asia/Seoul, UTC) | | modules_count | integer | `0` | modules 개수 (집계) | | plugins_count | integer | `0` | plugins 개수 (집계) | -| menus_count | integer | `2` | menus 개수 (집계) | +| menus_count | integer | `0` | menus 개수 (집계) | | modules | array | `[]` | 이 사용자가 접근 권한을 가진 모듈 목록 (역할 경유 권한 관계 파생) | | plugins | array | `[]` | 이 사용자가 접근 권한을 가진 플러그인 목록 (역할 경유 권한 관계 파생) | -| menus | array | `[{"id":33,"title":"API 문서 샘플 메뉴","url":"\/admin\/apidoc-s…` | 이 사용자가 접근 가능한 관리자 메뉴 목록 (원소: id/title/url — 역할 경유 메뉴 관계 파생) | +| menus | array | `[]` | 이 사용자가 접근 가능한 관리자 메뉴 목록 (원소: id/title/url — 역할 경유 메뉴 관계 파생) | | roles | array | `[{"id":1,"identifier":"admin","name":"관리자"}]` | 사용자에게 부여된 역할 목록 (원소: id/identifier/name — 역할 관계 파생) | | permissions | array | `[]` | 연결된 권한 목록 (id/identifier/name — 역할 경유 권한 관계 파생) | | consents | array | `[]` | 사용자가 동의한 약관 동의 레코드 목록 (약관 관계 파생) | | terms_consent | null | `null` | 이용약관 동의 정보 (동의 시각 등, 미동의 시 null) | | privacy_consent | null | `null` | 개인정보 처리방침 동의 정보 (동의 시각 등, 미동의 시 null) | -| created_at | string | `2026-07-06 19:15:16` | 생성 일시 | -| updated_at | string | `2026-07-06 19:15:16` | 최종 수정 일시 | +| created_at | string | `2026-07-30 23:37:44` | 생성 일시 | +| updated_at | string | `2026-08-04 19:00:10` | 최종 수정 일시 | | is_owner | boolean | `true` | 현재 인증 사용자가 이 리소스의 소유자인지 여부 (BaseApiResource 표준 메타) | | abilities | object | `{"can_read":true,"can_create":true,"can_update":true,"can…` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | -| admin_memo | string | `Perferendis ut suscipit quia unde sed.` | 관리자 메모 | -| ip_address | string | `77.105.222.87` | 마지막 접속 IP 주소 | +| admin_memo | null | `null` | 관리자 메모 | +| ip_address | null | `null` | 마지막 접속 IP 주소 | | withdrawn_at | null | `null` | withdrawn 일시 | | blocked_at | null | `null` | blocked 일시 | +| failed_login_attempts | integer | `0` | 연속 로그인 실패 횟수 | +| locked_permanently | boolean | `false` | | +| locked_until | null | `null` | 계정 잠금 해제 시각 (NULL = 잠금 없음) | +| is_locked | boolean | `false` | locked 여부 | | notify_post_complete | boolean | `false` | 게시글 작성 완료 알림 수신 여부 (게시판 모듈 알림 설정) | | notify_post_reply | boolean | `false` | 내 게시글 답글 알림 수신 여부 | | notify_comment | boolean | `false` | 내 게시글 댓글 알림 수신 여부 | @@ -1036,82 +943,98 @@ HTTP/1.1 200 "success": true, "message": "사용자 정보를 성공적으로 가져왔습니다.", "data": { - "uuid": "a234c2b1-cde8-437f-b28b-23323be2b98d", - "name": "API 문서 샘플 사용자", - "nickname": "song.hyunji", - "email": "apidoc-sample-user@example.com", + "uuid": "a26219fc-94a0-4f63-9404-04c2a6ac99e4", + "name": "최고관리자", + "nickname": "최고관리자", + "email": "heuristing@gmail.com", "avatar": null, "language": "ko", "language_label": "한국어", - "country": "KR", + "country": null, "status": "active", "status_label": "활성", "status_variant": "success", "is_admin": true, - "homepage": "https://example.com", - "mobile": "010-9595-2897", - "phone": "02-637-5618", - "zipcode": "16505", - "address": "경기도 안양시 봉은사로 2918", - "address_detail": "48동 718호", - "signature": "Fugit consequuntur repellendus sed.", - "bio": "Ut magni et sunt ducimus error adipisci. Pariatur corporis voluptatem ratione quo non saepe. Illo atque praesentium possimus dolores qui est fugit. Sint fugiat numquam voluptates.", - "last_login_at": "2026-07-07 10:41:24", - "email_verified_at": "2026-07-08 10:41:24", + "homepage": null, + "mobile": null, + "phone": null, + "zipcode": null, + "address": null, + "address_detail": null, + "signature": null, + "bio": null, + "last_login_at": "2026-08-04 19:00:10", + "email_verified_at": "2026-07-30 23:37:44", "timezone": "Asia/Seoul", "modules_count": 0, "plugins_count": 0, - "menus_count": 2, + "menus_count": 0, "modules": [], "plugins": [], - "menus": [ - { - "id": 1, - "title": "API 문서 샘플 메뉴", - "url": "/admin/apidoc-sample", - "is_active": true - }, - { - "id": 2, - "title": "하위 메뉴", - "url": "/admin/nihil-non-eos-doloribus-occaecati-optio", - "is_active": true - } - ], + "menus": [], "roles": [ { "id": 1, "identifier": "admin", "name": "관리자" - }, - { - "id": 4, - "identifier": "apidoc-sample-role", - "name": "API 문서 샘플 역할" } ], "permissions": [], "consents": [], "terms_consent": null, "privacy_consent": null, - "created_at": "2026-07-08 10:41:24", - "updated_at": "2026-07-08 10:41:24", + "created_at": "2026-07-30 23:37:44", + "updated_at": "2026-08-04 19:00:10", "is_owner": true, "abilities": { "can_read": true, "can_create": true, "can_update": true, - "can_delete": true, + "can_delete": false, "can_assign_roles": true }, - "admin_memo": "In officiis nemo quaerat debitis quia.", - "ip_address": "166.217.51.216", + "admin_memo": null, + "ip_address": null, "withdrawn_at": null, "blocked_at": null, + "failed_login_attempts": 0, + "locked_permanently": false, + "locked_until": null, + "is_locked": false, "notify_post_complete": false, "notify_post_reply": false, "notify_comment": false, "notify_reply_comment": false, + "email_subscription": false, + "email_subscription_at": null, + "marketing_consent": false, + "marketing_consent_at": null, + "third_party_consent": false, + "third_party_consent_at": null, + "info_disclosure": false, + "info_disclosure_at": null, + "marketing_consent_enabled": true, + "marketing_consent_terms_slug": "marketing-terms", + "marketing_consent_terms_slug_set": true, + "third_party_consent_enabled": true, + "third_party_consent_terms_slug": null, + "third_party_consent_terms_slug_set": false, + "info_disclosure_enabled": true, + "info_disclosure_terms_slug": null, + "info_disclosure_terms_slug_set": false, + "email_subscription_enabled": true, + "email_subscription_terms_slug": null, + "email_subscription_terms_slug_set": false, + "channels": [ + { + "key": "email_subscription", + "label": "광고성 이메일 수신", + "enabled": true, + "terms_slug": null, + "terms_slug_set": false + } + ], + "consent_histories": [], "ecommerce_mileage": { "enabled": false }, @@ -1152,7 +1075,7 @@ HTTP/1.1 200 | nickname | body | string | 아니오 | max 50 | 닉네임 | | email | body | email | 예 | max 255 | 이메일 주소 | | password | body | string | 아니오 | — | 비밀번호 | -| language | body | string | 아니오 | `ko`, `en`, `fr`, `ja` | 언어 코드 | +| language | body | string | 아니오 | `ko`, `en` | 언어 코드 | | country | body | string | 아니오 | — | 국가 코드 (ISO 3166-1 alpha-2) | | timezone | body | string | 아니오 | — | 타임존 식별자 | | status | body | string | 아니오 | `active`, `inactive`, `blocked`, `withdrawn` | 계정 상태 (미지정 시 active) | @@ -1181,7 +1104,7 @@ HTTP/1.1 200 **요청 예시** ```http -PUT /api/admin/users/a234c2b1-cde8-437f-b28b-23323be2b98d HTTP/1.1 +PUT /api/admin/users/a26219fc-94a0-4f63-9404-04c2a6ac99e4 HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -1214,13 +1137,17 @@ Content-Type: application/json "notify_post_complete": true, "notify_post_reply": true, "notify_comment": true, - "notify_reply_comment": true + "notify_reply_comment": true, + "email_subscription": true, + "marketing_consent": true, + "third_party_consent": true, + "info_disclosure": true } ``` **응답 필드** (`data` 내부) - + **응답 예시** @@ -1242,6 +1169,129 @@ Content-Type: application/json 기존 사용자 정보를 수정합니다(경로 파라미터는 UUID 로 바인딩). `name`, `email` 은 필수이며 `email` 은 해당 사용자를 제외한 고유성 검사를 거친다. `password` 는 선택이며 값을 주면 8자 이상·`password_confirmation` 확인을 요구한다(미전송 시 기존 비밀번호 유지). 역할은 `roles` 또는 `role_ids` 중 하나로 지정하고 둘 다 오면 `role_ids` 가 우선한다. 성공 시 갱신된 사용자를 `UserResource` 로 반환한다. `notify_*`/`marketing_consent`/`third_party_consent`/`info_disclosure`/`email_subscription` 파라미터는 확장(sirsoft-marketing)이 검증 규칙을 주입한 필드로, 상세는 해당 확장 문서를 참조한다. +### POST /api/admin/users/{user}/unlock + +- **라우트명**: `api.admin.users.unlock` +- **컨트롤러**: `App\Http\Controllers\Api\Admin\UserController@unlock` +- **인증/권한**: `auth:sanctum` + `permission:core.users.update` + +**요청 파라미터** + +| 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | +| --- | --- | --- | --- | --- | --- | +| user | path | string | 예 | — | 대상 user 의 UUID | + +**요청 예시** + +```http +POST /api/admin/users/a26219fc-94a0-4f63-9404-04c2a6ac99e4/unlock HTTP/1.1 +Host: api.example.com +Accept: application/json +Authorization: Bearer {YOUR_TOKEN} +``` + +**응답 필드** (`data` 내부) + +_단건 응답: `data` 객체의 필드._ + +| 필드 | 타입 | 실측 예시값 | 용도/설명 | +| --- | --- | --- | --- | +| uuid | string | `a26219fc-94a0-4f63-9404-04c2a6ac99e4` | 외부 노출용 UUID (URL/API 식별자, 내부 id 비노출) | +| name | string | `최고관리자` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | +| nickname | string | `최고관리자` | 닉네임 | +| email | string | `heuristing@gmail.com` | 이메일 주소 | +| avatar | null | `null` | 아바타 이미지 URL (미등록 시 null) | +| language | string | `ko` | 사용자 언어 설정 (ko: 한국어, en: 영어) | +| language_label | string | `한국어` | 언어 코드의 현지화 라벨 (user.language.{code} 번역) | +| country | null | `null` | 국가 코드 (ISO 3166-1 alpha-2) | +| status | string | `active` | 상태 값 (도메인별 상태 집합 — 사람이 읽는 라벨은 status_label, UI 변형은 status_variant 참조) | +| status_label | string | `활성` | 상태의 사람이 읽는 라벨 (상태 Enum label() 산물) | +| status_variant | string | `success` | 상태 표시 색상/스타일 변형 키 (상태 Enum variant() 산물 — UI 배지용) | +| is_admin | boolean | `true` | 관리자 역할 보유 여부 (User::isAdmin() — 역할 관계 기반 파생) | +| homepage | null | `null` | 홈페이지 URL | +| mobile | null | `null` | 휴대폰 번호 | +| phone | null | `null` | 전화번호 | +| zipcode | null | `null` | 우편번호 | +| address | null | `null` | 기본 주소 | +| address_detail | null | `null` | 상세 주소 | +| signature | null | `null` | 서명 | +| bio | null | `null` | 자기소개 | +| last_login_at | string | `2026-08-04 19:00:10` | last login 일시 | +| email_verified_at | string | `2026-07-30 23:37:44` | email verified 일시 | +| timezone | string | `Asia/Seoul` | 사용자 시간대 (예: Asia/Seoul, UTC) | +| created_at | string | `2026-07-30 23:37:44` | 생성 일시 | +| updated_at | string | `2026-08-04 19:00:10` | 최종 수정 일시 | +| is_owner | boolean | `true` | 현재 인증 사용자가 이 리소스의 소유자인지 여부 (BaseApiResource 표준 메타) | +| abilities | object | `{"can_read":true,"can_create":true,"can_update":true,"can…` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | + +**응답 예시** + +```http +HTTP/1.1 200 +``` + +```json +{ + "success": true, + "message": "계정 잠금이 해제되었습니다.", + "data": { + "uuid": "a26219fc-94a0-4f63-9404-04c2a6ac99e4", + "name": "최고관리자", + "nickname": "최고관리자", + "email": "heuristing@gmail.com", + "avatar": null, + "language": "ko", + "language_label": "한국어", + "country": null, + "status": "active", + "status_label": "활성", + "status_variant": "success", + "is_admin": true, + "homepage": null, + "mobile": null, + "phone": null, + "zipcode": null, + "address": null, + "address_detail": null, + "signature": null, + "bio": null, + "last_login_at": "2026-08-04 19:00:10", + "email_verified_at": "2026-07-30 23:37:44", + "timezone": "Asia/Seoul", + "created_at": "2026-07-30 23:37:44", + "updated_at": "2026-08-04 19:00:10", + "is_owner": true, + "abilities": { + "can_read": true, + "can_create": true, + "can_update": true, + "can_delete": false, + "can_assign_roles": true + } + } +} +``` + +**에러 응답** + +| 상태코드 | 의미 | 발생 조건 | +| --- | --- | --- | +| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 | +| 403 | Forbidden | 요구 권한(`core.users.update`)이 없는 경우 | +| 404 | Not Found | 해당 UUID 의 사용자가 없는 경우 | + + + +**설명** + +로그인 실패 누적으로 잠긴 계정의 잠금을 관리자가 수동으로 해제합니다. 로그인 시도 추적 값 +(`failed_login_attempts` / `locked_until` / `locked_permanently`)을 모두 초기화합니다. + +보안 환경설정의 잠금 시간(`security.login_lockout_time`)을 `0`(무한대)으로 두면 계정이 무기한 +잠기며, 이 경우 자동 해제도 성공 로그인도 불가능하므로 **이 엔드포인트가 유일한 복구 경로**입니다. +해제는 활동 로그(`auth.account_unlocked`)에 기록됩니다. + + ### GET /api/users/{user}/profile - **라우트명**: `api.public.users.profile` @@ -1257,7 +1307,7 @@ Content-Type: application/json **요청 예시** ```http -GET /api/users/a234c2b1-cde8-437f-b28b-23323be2b98d/profile HTTP/1.1 +GET /api/users/a26219fc-94a0-4f63-9404-04c2a6ac99e4/profile HTTP/1.1 Host: api.example.com Accept: application/json ``` @@ -1268,13 +1318,13 @@ _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| uuid | string | `a231747f-e82e-4cf2-9ae1-a261849dce40` | 외부 노출용 UUID (URL/API 식별자, 내부 id 비노출) | -| name | string | `API 문서 샘플 사용자` | 사용자 이름 | +| uuid | string | `a26219fc-94a0-4f63-9404-04c2a6ac99e4` | 외부 노출용 UUID (URL/API 식별자, 내부 id 비노출) | +| name | string | `최고관리자` | 사용자 이름 | | status | string | `active` | 계정 상태 (active: 활성, inactive: 비활성, blocked: 차단, withdrawn: 탈퇴) | | status_label | string | `활성` | 상태의 사람이 읽는 라벨 (상태 Enum label() 산물) | | avatar | null | `null` | 프로필 아바타 이미지 URL (미설정 시 null) | -| bio | string | `Tenetur omnis et amet omnis veniam to…` | 자기소개 | -| created_at | string | `2026-07-06` | 생성 일시 | +| bio | null | `null` | 자기소개 | +| created_at | string | `2026-07-30` | 생성 일시 | | is_withdrawn | boolean | `false` | withdrawn 여부 | **응답 예시** @@ -1288,13 +1338,13 @@ HTTP/1.1 200 "success": true, "message": "프로필 정보를 성공적으로 가져왔습니다.", "data": { - "uuid": "a234c2b1-cde8-437f-b28b-23323be2b98d", - "name": "API 문서 샘플 사용자", + "uuid": "a26219fc-94a0-4f63-9404-04c2a6ac99e4", + "name": "최고관리자", "status": "active", "status_label": "활성", "avatar": null, - "bio": "Ut magni et sunt ducimus error adipisci. Pariatur corporis voluptatem ratione quo non saepe. Illo atque praesentium possimus dolores qui est fugit. Sint fugiat numquam voluptates.", - "created_at": "2026-07-08", + "bio": null, + "created_at": "2026-07-30", "is_withdrawn": false } } diff --git a/docs/backend/api/verify-password.md b/docs/backend/api/verify-password.md index 7bff8313..7828bf6d 100644 --- a/docs/backend/api/verify-password.md +++ b/docs/backend/api/verify-password.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Verify Password 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` @@ -29,8 +29,6 @@ | --- | --- | --- | --- | --- | --- | | password | body | string | 예 | — | 비밀번호 | -> 이 엔드포인트는 확장이 파라미터를 추가할 수 있습니다 (`core.auth.verify_password_validation_rules`). - **요청 예시** ```http @@ -47,7 +45,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** diff --git a/docs/backend/service-repository.md b/docs/backend/service-repository.md index 668464eb..8206d4cc 100644 --- a/docs/backend/service-repository.md +++ b/docs/backend/service-repository.md @@ -832,6 +832,43 @@ public function getAll(): Collection } ``` +### 목록의 eager load 는 목록이 직렬화하는 관계만 + +eager load 는 N+1 을 막지만, **목록에 필요 없는 관계까지 로드하면** 이번에는 페이로드가 커진다. 한 페이지를 여는 것만으로 그 페이지 전 행의 하위 컬렉션이 통째로 메모리에 올라오고 응답에 실린다. + +```php +// ❌ Resource 는 whenLoaded 로 방어하는데 Repository 가 목록에서 무조건 로드 +// → 가드가 항상 참이 되어 무력화된다 +$listRelations = ['categories', 'images', 'brand', 'options']; + +// ✅ 목록이 실제로 직렬화하는 관계만. 개수·합계는 집계로 +$listRelations = ['categories', 'images', 'brand']; +// withCount: ['options as options_count'], outerUsing: fn ($q) => $q->withSum(...) +``` + +로드 여부를 정하는 곳은 Repository 다. Resource 의 `whenLoaded` 는 그 결정을 **따르는** 장치이지, 그 자체가 페이로드를 줄이지는 않는다. + +목록에서 뺀 관계는 단건 조회가 공급해야 한다. 이때 **단건이 목록용 조회 메서드를 재사용하지 않도록** 한다 — 목록 조회는 컬럼을 좁히고 건수 상한을 두므로, 재사용하면 단건이 그 제약을 그대로 물려받아 값이 비거나 상한 밖 행을 찾지 못한다. + +```php +// ❌ 단건이 목록용 조회를 재사용 → content 소실 + 상한 밖 행은 못 찾음 +$version = $this->repository->getVersions($id)->firstWhere('version', $version); + +// ✅ 단건 전용 조회 (전 컬럼 + 키로 직접 조회) +$version = $this->repository->findVersionByNumber($id, $version); +``` + +### 대표 1건만 필요하면 관계를 1건으로 좁힌다 + +```php +// ❌ eager load 의 limit 은 부모별이 아니라 배치 전체에 걸린다 +// → 첫 주문만 옵션을 받고 나머지는 빈 값 +->with(['options' => fn ($q) => $q->limit(1)]) + +// ✅ 관계 자체를 "가장 오래된 1건" 으로 정의해 부모별 1건 보장 +->with('firstOption') // hasOne(...)->oldestOfMany() +``` + --- ## 목록 조회 컬럼 프루닝과 지연 조인 diff --git a/docs/frontend/data-sources.md b/docs/frontend/data-sources.md index c14f731a..684aea93 100644 --- a/docs/frontend/data-sources.md +++ b/docs/frontend/data-sources.md @@ -155,6 +155,26 @@ 권장: 빈 배열/null 전송 방지를 위한 조건부 표현식 사용 ``` +### 서버 필터와 화면 필터는 같은 출처를 읽는다 + +`params` 로 서버에 넘겨 응답을 좁힌 값(채널·상태·분류 등)을 화면이 다시 필터 조건으로 쓴다면, 두 표현식은 **같은 출처 하나**를 읽어야 한다. 표현식을 글자까지 똑같이 적는 것으로는 부족하다 — 출처가 둘이면 갱신 시점이 달라 어긋난다. + +```json +// ❌ 두 출처 (URL + 페이지 로컬 상태) +"params": { "template_channel": "{{_local.activeChannel ?? query.channel ?? 'mail'}}" } +"if": "{{(row.templates ?? []).find(t => t.channel === (_local.activeChannel ?? query.channel ?? 'mail'))}}" + +// ✅ URL 단일 출처 +"params": { "template_channel": "{{query.channel ?? 'mail'}}" } +"if": "{{(row.templates ?? []).find(t => t.channel === (query.channel ?? 'mail'))}}" +``` + +`_local` 은 화면 안에서만 사는 값이라 URL 이 나르지 못한다. 다른 탭에 갔다 돌아오면 URL 의 필터 키는 사라지는데(탭 전환은 목록 상태를 승계하지 않는다) `_local` 만 이전 값으로 남는다. 그 사이 목록은 기본값으로 다시 불려 오고 화면은 남은 값으로 걸러내므로, **응답에는 있는 데이터를 화면이 "없음" 으로 표시**한다. 예외도 콘솔 경고도 4xx/5xx 도 남지 않아 로그로는 발견되지 않는다. + +같은 이유로 정적 대조 테스트도 이 결함을 놓친다. 두 표현식이 문자열로 동일하기 때문이다. 검사해야 하는 것은 동일성이 아니라 **출처의 개수**다. + +로컬 상태를 함께 두고 싶은 이유가 "클릭 즉시 반응" 이라면, `navigate` 가 URL 을 갱신하면서 이미 같은 렌더 사이클에 반영되므로 별도 복제가 필요 없다. + ### multipart/form-data 지원 (파일 업로드) > **버전**: engine-v1.19.0+ diff --git a/lang-packs/_bundled/g7-core-ja/CHANGELOG.md b/lang-packs/_bundled/g7-core-ja/CHANGELOG.md index a45c6619..347a529f 100644 --- a/lang-packs/_bundled/g7-core-ja/CHANGELOG.md +++ b/lang-packs/_bundled/g7-core-ja/CHANGELOG.md @@ -23,6 +23,8 @@ ### Added +- 레이아웃 편집기 버전 이력의 "더 보기" 버튼과 변경내용이 너무 클 때의 안내 문구 일본어 번역 추가. +- 버전 목록을 한 번에 몇 개까지 가져올지 정하는 값의 입력 안내 문구 일본어 번역 추가. - 통신이 끊겼을 때 표시되는 네트워크 오류 안내 문구 일본어 번역 추가 — 종전에는 내부 식별 문구가 영문 그대로 노출됐습니다. ## [1.0.8] - 2026-08-01 diff --git a/lang-packs/_bundled/g7-core-ja/backend/ja/validation.php b/lang-packs/_bundled/g7-core-ja/backend/ja/validation.php index 3a913fb1..cba83bff 100644 --- a/lang-packs/_bundled/g7-core-ja/backend/ja/validation.php +++ b/lang-packs/_bundled/g7-core-ja/backend/ja/validation.php @@ -526,7 +526,7 @@ return [ ], 'notification_channel' => [ 'invalid' => '通知チャネル識別子が正しくありません。', - 'unavailable' => "利用できない通知チャネルです: ':value'", + 'unavailable' => '利用できない通知チャネルです: \':value\'', ], 'translatable' => [ 'must_be_array' => '多言語フィールドは配列である必要があります。', @@ -678,7 +678,6 @@ return [ 'empty_word' => '拡張識別子でアンダースコアを連続させたり両端に置くことはできません。', 'word_starts_with_digit' => '拡張識別子の各単語は数字で始めることはできません。', ], - 'module_path' => [ 'file_type_not_allowed' => '許可されていないファイルタイプです。拡張子: :extension(許可: :allowed)', 'must_be_string' => 'パスは文字列である必要があります。', @@ -1145,4 +1144,11 @@ return [ 'log_level' => 'ログレベル', 'log_days' => 'ログ保持日数', ], + 'layout_version' => [ + 'limit' => [ + 'integer' => '閲覧数は数字である必要があります。', + 'min' => '閲覧数は1以上である必要があります。', + 'max' => '閲覧数は最大:max件です。', + ], + ], ]; diff --git a/lang-packs/_bundled/g7-core-ja/frontend/partial/layout_editor.json b/lang-packs/_bundled/g7-core-ja/frontend/partial/layout_editor.json index b18d3985..11123248 100644 --- a/lang-packs/_bundled/g7-core-ja/frontend/partial/layout_editor.json +++ b/lang-packs/_bundled/g7-core-ja/frontend/partial/layout_editor.json @@ -126,7 +126,8 @@ "diff_title": "v{old} → v{new} 変更内容", "diff_back": "一覧", "diff_identical": "2つのバージョンの内容は同じです。", - "diff_too_large": "変更内容が大きすぎて表示できません。" + "diff_too_large": "変更内容が大きすぎて表示できません。", + "load_more": "もっと見る" }, "preview_action": { "no_document": "プレビュー用のレイアウトが選択されていません。" diff --git a/lang-packs/_bundled/g7-module-sirsoft-ecommerce-ja/CHANGELOG.md b/lang-packs/_bundled/g7-module-sirsoft-ecommerce-ja/CHANGELOG.md index bd5ec181..22b60ff7 100644 --- a/lang-packs/_bundled/g7-module-sirsoft-ecommerce-ja/CHANGELOG.md +++ b/lang-packs/_bundled/g7-module-sirsoft-ecommerce-ja/CHANGELOG.md @@ -8,6 +8,9 @@ ### Added +- 관리자 상품 목록에서 행을 펼칠 때 나오는 옵션 불러오기 문구(불러오는 중·실패·재시도·등록된 옵션 없음)의 일본어 번역 추가. +- 옵션 일괄 변경 확인 화면에서 펼치지 않은 상품을 개수로 묶어 보여주는 문구와 실패 안내의 일본어 번역 추가. +- 상품 옵션 조회 요청 값(상품 ID 목록, 옵션 포함 여부) 검증 안내 문구 일본어 번역 추가. - 무통장입금 자동취소 기한을 비운 채 저장할 때 나오는 안내 문구 일본어 번역 추가. - 유효기간이 설정되지 않은 쿠폰을 발급할 때 나오는 안내 문구 일본어 번역 추가. - 마일리지 설정의 통화별 적립 절사 기준(단위·방식) 항목명과 선택지, 허용값 위반 안내 문구 일본어 번역 추가. diff --git a/lang-packs/_bundled/g7-module-sirsoft-ecommerce-ja/backend/ja/messages.php b/lang-packs/_bundled/g7-module-sirsoft-ecommerce-ja/backend/ja/messages.php index 0dfa3ab6..903f22e2 100644 --- a/lang-packs/_bundled/g7-module-sirsoft-ecommerce-ja/backend/ja/messages.php +++ b/lang-packs/_bundled/g7-module-sirsoft-ecommerce-ja/backend/ja/messages.php @@ -58,6 +58,8 @@ return [ 'code_generation_failed' => '商品コード作成に失敗しました。', 'logs_fetch_success' => '商品処理履歴を閲覧しました。', 'logs_fetch_failed' => '商品処理履歴の閲覧に失敗しました。', + 'options_fetch_success' => '商品オプションを閲覧しました。', + 'options_fetch_failed' => '商品オプション閲覧に失敗しました。', ], 'presets' => [ 'created' => '検索プリセットが保存されました。', diff --git a/lang-packs/_bundled/g7-module-sirsoft-ecommerce-ja/backend/ja/validation.php b/lang-packs/_bundled/g7-module-sirsoft-ecommerce-ja/backend/ja/validation.php index 287e114e..bc43b2ab 100644 --- a/lang-packs/_bundled/g7-module-sirsoft-ecommerce-ja/backend/ja/validation.php +++ b/lang-packs/_bundled/g7-module-sirsoft-ecommerce-ja/backend/ja/validation.php @@ -210,6 +210,9 @@ return [ 'shipping_policy_id' => [ 'integer' => '配送ポリシーIDは数字である必要があります。', ], + 'with_options' => [ + 'boolean' => 'オプション含有の有無は true または false である必要があります。', + ], ], 'category_required' => 'カテゴリを選択してください。', 'category_min' => '最小1個以上のカテゴリを選択してください。', @@ -1772,4 +1775,14 @@ return [ 'earn_rate_required_when_enabled' => 'マイレージを使用するには、基本積立率は0より大きい必要があります。', 'expires_at_invalid' => '有効期限は正しい日付である必要があります。', ], + 'options_list' => [ + 'product_ids' => [ + 'required' => '閲覧する商品を選択してください。', + 'array' => '商品 ID リストは配列である必要があります。', + 'min' => '閲覧する商品を1つ以上指定してください。', + 'max' => '一度に閲覧できる商品は最大 :max 個です。', + 'integer' => '商品 ID は数字である必要があります。', + 'item_min' => '商品 ID は1以上である必要があります。', + ], + ], ]; diff --git a/lang-packs/_bundled/g7-module-sirsoft-ecommerce-ja/frontend/partial/admin/product.json b/lang-packs/_bundled/g7-module-sirsoft-ecommerce-ja/frontend/partial/admin/product.json index 5397e684..8415dd22 100644 --- a/lang-packs/_bundled/g7-module-sirsoft-ecommerce-ja/frontend/partial/admin/product.json +++ b/lang-packs/_bundled/g7-module-sirsoft-ecommerce-ja/frontend/partial/admin/product.json @@ -189,7 +189,10 @@ "safe_stock": "安全在庫", "active": "使用" }, - "no_options": "登録されたオプションがありません" + "no_options": "登録されたオプションがありません", + "loading": "オプションを読み込み中です...", + "load_failed": "オプションの読み込みに失敗しました。", + "retry": "再試行" }, "shipping": { "free": "送料無料", @@ -592,7 +595,8 @@ "price_change": "販売価格一括変更", "stock_change": "在庫一括変更", "no_changes": "変更する項目がありません。", - "error_section": "変更に失敗しました" + "error_section": "変更に失敗しました", + "options_aggregate": "{{product}} > オプション {{count}}個すべて" }, "bulk_placeholder": { "sales_status": "販売ステータス選択", 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 d2b8948e..0c313879 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 @@ -8,6 +8,7 @@ ### Added +- 역할 관리 목록의 「권한 수」 열 제목 일본어 번역 추가. - 확장 업데이트 모달의 「검색 인덱스 재생성」 선택 항목 문구 일본어 번역 추가 — 항목 이름과 설명(재생성 시 인덱스 잠금·재색인 안내)이 일본어 로케일에서 자연스럽게 표시됩니다. ## [1.0.4] - 2026-07-17 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 d3dae1aa..55328ee1 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 @@ -1053,6 +1053,7 @@ "name": "ロール名", "description": "説明", "users_count": "ユーザー数", + "permissions_count": "権限数", "status": "ステータス", "created_at": "作成日" }, diff --git a/lang/en/validation.php b/lang/en/validation.php index eba08dff..24bd97de 100644 --- a/lang/en/validation.php +++ b/lang/en/validation.php @@ -1127,6 +1127,15 @@ return [ 'definition_already_exists' => 'A definition with the same (provider, scope_type, scope_value) already exists.', ], + // Layout version list + 'layout_version' => [ + 'limit' => [ + 'integer' => 'The limit must be a number.', + 'min' => 'The limit must be at least 1.', + 'max' => 'The limit may not be greater than :max.', + ], + ], + // Validation attribute names (validation.attributes) 'attributes' => [ 'ids' => 'user ID list', diff --git a/lang/ko/validation.php b/lang/ko/validation.php index b5727d6c..bcf3a393 100644 --- a/lang/ko/validation.php +++ b/lang/ko/validation.php @@ -1120,6 +1120,15 @@ return [ 'definition_already_exists' => '동일 (provider, scope_type, scope_value) 정의가 이미 존재합니다.', ], + // 레이아웃 버전 목록 조회 + 'layout_version' => [ + 'limit' => [ + 'integer' => '조회 건수는 숫자여야 합니다.', + 'min' => '조회 건수는 1 이상이어야 합니다.', + 'max' => '조회 건수는 최대 :max건입니다.', + ], + ], + // 검증 속성명 (validation.attributes) 'attributes' => [ 'ids' => '사용자 ID 목록', diff --git a/lang/partial/en/layout_editor.json b/lang/partial/en/layout_editor.json index 149ef621..01a3c412 100644 --- a/lang/partial/en/layout_editor.json +++ b/lang/partial/en/layout_editor.json @@ -126,7 +126,8 @@ "diff_title": "Changes v{old} → v{new}", "diff_back": "Back", "diff_identical": "The two versions are identical.", - "diff_too_large": "The changes are too large to display." + "diff_too_large": "The changes are too large to display.", + "load_more": "Load more" }, "preview_action": { "no_document": "No layout is selected to preview." diff --git a/lang/partial/ko/layout_editor.json b/lang/partial/ko/layout_editor.json index 293673bc..9f2f7f27 100644 --- a/lang/partial/ko/layout_editor.json +++ b/lang/partial/ko/layout_editor.json @@ -126,7 +126,8 @@ "diff_title": "v{old} → v{new} 변경 내용", "diff_back": "목록", "diff_identical": "두 버전의 내용이 동일합니다.", - "diff_too_large": "변경 내용이 너무 커서 표시할 수 없습니다." + "diff_too_large": "변경 내용이 너무 커서 표시할 수 없습니다.", + "load_more": "더 보기" }, "preview_action": { "no_document": "미리보기할 레이아웃이 선택되지 않았습니다." diff --git a/modules/_bundled/sirsoft-board/CHANGELOG.md b/modules/_bundled/sirsoft-board/CHANGELOG.md index bfcf6ab3..17ee4e4e 100644 --- a/modules/_bundled/sirsoft-board/CHANGELOG.md +++ b/modules/_bundled/sirsoft-board/CHANGELOG.md @@ -24,10 +24,12 @@ - 신고 관리 목록도 같은 방식으로 개선했습니다. 신고 건수·대상 글 상태처럼 목록에 함께 표시되는 정보를 예전에는 건너뛰는 신고까지 모두 계산했지만, 이제 현재 페이지의 신고에 대해서만 계산합니다. 신고가 많이 쌓인 사이트에서 목록 페이지 이동이 빨라집니다. (#74 @jiwonpapa 님께서 제보해주셨습니다.) - 마이페이지의 「내가 쓴 댓글」·「내가 댓글 단 글」 목록도 같은 방식으로 개선했습니다. 각 글의 최근 댓글을 예전에는 건너뛰는 글까지 모두 찾아봤지만, 이제 현재 페이지의 글에 대해서만 찾습니다. 댓글 활동이 많은 회원의 목록 페이지 이동이 빨라집니다. (#74 @jiwonpapa 님께서 제보해주셨습니다.) - 게시글이 많은 게시판에서 목록 정렬이 더 빨라지도록 색인을 정비했습니다. 신고 관리 목록에도 같은 정비를 적용했습니다. 같은 시각에 등록된 게시글이 많은 구간에서 순서를 정하느라 생기던 추가 작업이 사라집니다. 기존 사이트도 업데이트 시 자동으로 반영되며, 게시글이 아주 많은 경우 이 과정에 수 분이 걸리고 그동안 글쓰기가 잠시 대기할 수 있습니다. +- 관리자 게시판 목록이 게시판마다 매니저·스텝 역할과 그 역할에 속한 회원 명단(이름·이메일)을 함께 내려주지 않습니다. 목록 화면이 쓰지 않는 값이라 게시판 수만큼 늘어나던 조회와 전송량이 사라집니다. 역할 지정은 종전대로 게시판 상세·설정 화면에서 하며, 목록에 보이는 항목은 이전과 동일합니다. (#76 @jordy-bitree 님께서 제보해주셨습니다.) - 개선된 사이트맵 생성 기능을 사용하기 위해 이 모듈은 이제 코어 7.0.6 이상이 필요합니다. ### Fixed +- 게시판 알림 설정 화면에서 모든 알림의 제목·수신자·활성 여부가 비어 보이고 「이 채널에 대한 템플릿이 없습니다」로만 표시되던 문제를 수정했습니다. 편집 창을 열어도 내용이 채워지지 않았습니다. 채널을 전환하거나 새로고침해도 마찬가지였습니다. (#76 @jordy-bitree 님께서 제보해주셨습니다.) - 게시판 설정의 '새 글 표시 시간'이 저장 직후에는 숫자가 아닌 형태로 다뤄져, 조회 시점에 따라 값의 형태가 달라지던 문제를 수정했습니다. 새 글 표시 여부 판정에 쓰이는 값이므로 항상 숫자로 처리합니다. - 글을 쓰면서 파일을 함께 첨부하면 글은 저장되는데 첨부파일만 사라지던 문제를 수정했습니다. 첨부 개수·용량·형식 검사와 권한 확인은 모두 통과한 뒤 저장 단계에서만 빠져, 등록된 글에 첨부가 하나도 남지 않았습니다. (#81 @jiwonpapa 님께서 제보해주셨습니다.) - 게시판 신고 정책의 자동 숨김 기준 횟수를 0(자동 숨김 사용 안 함)으로 입력할 수 없던 문제를 수정했습니다. 서버는 0을 "사용 안 함"으로 처리하는데 화면에서만 1 이상을 요구해, 0을 입력하면 저장은 되면서도 입력칸이 계속 오류 상태로 남았습니다. (#81 @jiwonpapa 님께서 제보해주셨습니다.) diff --git a/modules/_bundled/sirsoft-board/docs/api/boards.md b/modules/_bundled/sirsoft-board/docs/api/boards.md index ed68e4f6..746f2a21 100644 --- a/modules/_bundled/sirsoft-board/docs/api/boards.md +++ b/modules/_bundled/sirsoft-board/docs/api/boards.md @@ -40,53 +40,21 @@ Authorization: Bearer {YOUR_TOKEN} _목록 응답: `data.data[]` 배열 항목의 필드 + `data.pagination`._ +> **목록은 경량 표현이다.** 관리자 게시판 목록 화면이 쓰는 행 필드는 `id`/`name`/`slug`/`type`/`is_active`/`categories`/`abilities`/`posts_count` 뿐이다. 종전에는 행마다 매니저·스텝 역할에 속한 **모든 사용자(uuid·이름·이메일)** 까지 함께 직렬화하면서 행 수만큼 역할 조회가 추가로 나갔다. 역할/사용자 정보는 게시판 수정 화면(단건 조회 `GET .../admin/boards/{slug}`)이 공급한다. + + | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | | id | integer | `12` | 기본 키 (내부 식별자) | -| name | string | `API 문서 샘플 게시판` | 게시판명 (다국어 JSON) | +| name | string | `API 문서 샘플 게시판` | 게시판명 (현재 로케일로 해석된 문자열) | | slug | string | `apidoc-sample-board` | 게시판 슬러그 (URL/테이블명) | -| is_active | boolean | `true` | active 여부 | +| description | string | `` | 게시판 설명 (현재 로케일로 해석된 문자열) | +| posts_count | integer | `0` | 게시글 수 (집계) | | type | string | `basic` | 게시판 타입 (basic, gallery, card 등) | -| description | string | `` | 게시판 설명 (다국어 JSON) | -| per_page | integer | `20` | 페이지당 게시글 수 (PC) | -| per_page_mobile | integer | `15` | 페이지당 게시글 수 (Mobile) | -| order_by | string | `created_at` | 정렬 기준 (created_at, view_count, title, author) | -| order_direction | string | `DESC` | 정렬 방향 (ASC, DESC) | +| is_active | boolean | `true` | 활성 여부 | | categories | array | `[]` | 분류 목록 (배열) | -| show_view_count | boolean | `true` | 조회수 노출 | -| secret_mode | string | `disabled` | 비밀글 설정 (disabled: 사용안함, enabled: 사용함, always: 고정) | -| use_comment | boolean | `true` | 댓글 기능 사용 | -| use_reply | boolean | `true` | 게시글 답변 기능 사용 (댓글에 대한 답글 아님) | -| max_reply_depth | integer | `5` | 답변글 최대 깊이 (1~5) | -| use_report | boolean | `true` | 게시글/댓글 신고 기능 사용 | -| comment_order | string | `ASC` | 댓글 정렬 순서 (ASC: 오름차순, DESC: 내림차순) | -| max_comment_depth | integer | `10` | 대댓글 최대 깊이 (1~10) | -| min_title_length | integer | `2` | 최소 제목 글자 수 | -| max_title_length | integer | `200` | 최대 제목 글자 수 | -| min_content_length | integer | `10` | 최소 게시글 글자 수 | -| max_content_length | integer | `10000` | 최대 게시글 글자 수 | -| min_comment_length | integer | `2` | 최소 댓글 글자 수 | -| max_comment_length | integer | `1000` | 최대 댓글 글자 수 | -| blocked_keywords | array | `[]` | 금지어 목록 (배열) | -| use_file_upload | boolean | `true` | 파일 업로드 사용 | -| max_file_size | integer | `10` | 최대 파일 크기 (MB) | -| max_file_count | integer | `5` | max file 개수 (집계) | -| allowed_extensions | array | `["jpg","jpeg","png","gif","pdf","zip"]` | 허용 확장자 배열 | -| add_to_menu | null | `null` | 관리자 메뉴 등록 여부 (폼 요청에서만 채워지는 토글 초기값 — 조회 응답에서는 항상 null) | -| new_display_hours | integer | `24` | 신규 게시글 표시 기간 (시간 단위) | -| board_managers | array | `[]` | 게시판 관리자로 지정된 사용자 목록 (manager 역할 사용자의 uuid/name/email — 역할 기반 파생) | -| board_steps | array | `[]` | 게시판 승인/처리 담당자(스텝)로 지정된 사용자 목록 (step 역할 사용자의 uuid/name/email — 역할 기반 파생) | -| board_manager_ids | array | `[]` | board manager 식별자 배열 (연관 리소스 참조) | -| board_step_ids | array | `[]` | board step 식별자 배열 (연관 리소스 참조) | -| notify_author | boolean | `true` | 작성자 이메일 알림 (댓글, 대댓글, 답변글, 관리자 처리 시) | -| notify_admin_on_post | boolean | `true` | 관리자 이메일 알림 (게시글 등록 시) | -| created_at | string | `2026-07-07 09:34:50` | 생성 일시 | -| updated_at | string | `2026-07-07 09:34:50` | 최종 수정 일시 | -| permissions | null | `null` | 연결된 권한 목록 (id/identifier/name — 역할 경유 권한 관계 파생) | -| category_post_counts | null | `null` | 분류별 게시글 개수 맵 (요청 시 조건부로만 채워지며, 미포함 시 null) | -| posts_count | integer | `0` | posts 개수 (집계) | -| user_abilities | null | `null` | 현재 사용자의 게시판별 세부 권한 맵 (can_read/can_write/can_read_secret/can_manage 등 — include_user_abilities 요청 시에만 채워지며 미포함 시 null) | -| abilities | object | `{"can_create":true,"can_update":true,"can_delete":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | +| is_owner | boolean | `false` | 현재 인증 사용자가 이 게시판의 소유자인지 여부 | +| abilities | object | `{"can_create":true,"can_update":true,"can_delete":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 | **응답 예시** @@ -97,81 +65,32 @@ HTTP/1.1 200 ```json { "success": true, - "message": "게시판 정보를 조회했습니다.", + "message": "게시판 목록을 조회했습니다.", "data": { "data": [ { - "id": 1, + "id": 12, "name": "API 문서 샘플 게시판", "slug": "apidoc-sample-board", - "is_active": true, - "type": "basic", "description": "", - "per_page": 20, - "per_page_mobile": 15, - "order_by": "created_at", - "order_direction": "DESC", - "categories": [], - "show_view_count": true, - "secret_mode": "{MASKED}", - "use_comment": true, - "use_reply": true, - "max_reply_depth": 5, - "use_report": true, - "comment_order": "ASC", - "max_comment_depth": 10, - "min_title_length": 2, - "max_title_length": 200, - "min_content_length": 10, - "max_content_length": 10000, - "min_comment_length": 2, - "max_comment_length": 1000, - "blocked_keywords": [], - "use_file_upload": true, - "max_file_size": 10, - "max_file_count": 5, - "allowed_extensions": [ - "jpg", - "jpeg", - "png", - "gif", - "pdf", - "zip" - ], - "add_to_menu": null, - "new_display_hours": 24, - "board_managers": [], - "board_steps": [], - "board_manager_ids": [], - "board_step_ids": [], - "notify_author": true, - "notify_admin_on_post": true, - "created_at": "2026-07-08 10:41:34", - "updated_at": "2026-07-08 10:41:34", - "permissions": null, - "category_post_counts": null, "posts_count": 0, - "user_abilities": null, + "type": "basic", + "is_active": true, + "categories": [], + "is_owner": false, "abilities": { "can_create": true, "can_update": true, "can_delete": true } - } + }, + "... (총 3건 중 1건 표시)" ], "pagination": { "current_page": 1, "last_page": 1, - "per_page": 25, - "total": 1, - "from": 1, - "to": 1, - "has_more_pages": false - }, - "abilities": { - "can_create": true, - "can_update": true, - "can_delete": true + "per_page": 20, + "total": 3 } } } @@ -2879,7 +2798,7 @@ HTTP/1.1 200 | 상태코드 | 의미 | 발생 조건 | | --- | --- | --- | -| 403 | Forbidden | 요구 권한(`sirsoft-board.{slug}.posts.write|sirsoft-board.{slug}.manager`)이 없는 경우 | +| 403 | Forbidden | 요구 권한(`sirsoft-board.{slug}.posts.write| sirsoft-board.{slug}.manager`)이 없는 경우 | | 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 | @@ -3333,7 +3252,7 @@ HTTP/1.1 200 | 상태코드 | 의미 | 발생 조건 | | --- | --- | --- | -| 403 | Forbidden | 요구 권한(`sirsoft-board.{slug}.posts.write|sirsoft-board.{slug}.manager`)이 없는 경우 | +| 403 | Forbidden | 요구 권한(`sirsoft-board.{slug}.posts.write| sirsoft-board.{slug}.manager`)이 없는 경우 | | 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) | | 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 | @@ -3702,7 +3621,7 @@ HTTP/1.1 200 | 상태코드 | 의미 | 발생 조건 | | --- | --- | --- | -| 403 | Forbidden | 요구 권한(`sirsoft-board.{slug}.comments.write|sirsoft-board.{slug}.manager`)이 없는 경우 | +| 403 | Forbidden | 요구 권한(`sirsoft-board.{slug}.comments.write| sirsoft-board.{slug}.manager`)이 없는 경우 | | 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 | @@ -3747,7 +3666,7 @@ Authorization: Bearer {YOUR_TOKEN} (optional.sanctum: 비회원은 헤더 생 | 상태코드 | 의미 | 발생 조건 | | --- | --- | --- | -| 403 | Forbidden | 요구 권한(`sirsoft-board.{slug}.comments.write|sirsoft-board.{slug}.manager`)이 없는 경우 | +| 403 | Forbidden | 요구 권한(`sirsoft-board.{slug}.comments.write| sirsoft-board.{slug}.manager`)이 없는 경우 | | 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) | | 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 | diff --git a/modules/_bundled/sirsoft-board/resources/js/__tests__/layouts/admin-board-notification-channel-single-source.test.ts b/modules/_bundled/sirsoft-board/resources/js/__tests__/layouts/admin-board-notification-channel-single-source.test.ts new file mode 100644 index 00000000..2ada912d --- /dev/null +++ b/modules/_bundled/sirsoft-board/resources/js/__tests__/layouts/admin-board-notification-channel-single-source.test.ts @@ -0,0 +1,190 @@ +/** + * @file admin-board-notification-channel-single-source.test.ts + * @description 게시판 환경설정 알림 정의 탭 — 채널 표현식 단일 출처 회귀 테스트 + * + * 회귀 배경 (#518 3차 실측 결함 #1): + * 알림 정의 탭의 채널은 URL(`query.channel`)과 페이지 로컬 상태(`_local.activeNotificationChannel`) + * 두 곳에서 읽히고 있었다. 두 값은 갱신 시점이 다르다 — 다른 설정 탭에 갔다 돌아오면 URL 의 + * channel 은 사라지지만(탭 전환은 목록 상태를 승계하지 않는다) `_local` 은 이전 채널로 남는다. + * + * 코어 설정 화면은 그 사이에 목록을 기본 채널로 다시 불러오므로, 남아 있는 이전 채널로 템플릿을 + * 찾다가 행마다 「이 채널에 대한 템플릿이 없습니다」 를 띄웠다. + * + * 그래서 조건은 "두 표현식이 같은가" 가 아니라 "출처가 하나인가" 다. + * + * 2차 회귀 (#518 계획서 검증 브라우저 실측, 2026-08-05): + * 목록 프루닝으로 서버가 `template_channel` 을 받은 요청에만 `templates` 를 싣게 됐는데 + * (`NotificationDefinitionRepository::getPaginated`), 코어 화면만 그 파라미터를 보내도록 + * 고쳐지고 게시판 탭 데이터소스는 빠졌다. 그 결과 7건 전 행이 「이 채널에 대한 템플릿이 + * 없습니다」 로 표시되고 수신자·활성 토글·편집 모달 초기값이 통째로 비었다. + * + * 조용히 깨지는 종류다 — `(def.templates ?? [])` 가드 뒤라 예외도 콘솔 경고도 없고, + * 응답은 200 이며 행 수도 정상이라 "템플릿을 아직 안 만들었나 보다" 로 읽힌다. + * 그래서 아래 두 단언은 소비와 전송을 **함께** 묶는다: templates 를 읽는 화면이면 + * 그 채널을 서버에 넘겨야 한다. + * + * @scenario resource=board_notification_definition,endpoint=admin_settings,observation=consumer_screen + * @effects board_notification_channel_reads_single_source, board_notification_list_requests_template_channel, board_notification_reset_button_uses_server_aggregate + */ + +import fs from 'fs'; +import path from 'path'; +import { describe, it, expect } from 'vitest'; + +const SETTINGS_LAYOUT = path.resolve( + __dirname, + '../../../layouts/admin/admin_board_settings.json', +); +const NOTIFICATION_TAB = path.resolve( + __dirname, + '../../../layouts/admin/partials/admin_board_settings/_tab_notification_definitions.json', +); + +/** + * JSON 트리에서 `{{ }}` 바인딩 표현식을 모두 수집합니다. + * + * @param node 탐색할 JSON 노드 + * @param acc 수집 결과 누적 배열 + * @returns 수집된 바인딩 표현식 목록 + */ +function collectBindings(node: unknown, acc: string[] = []): string[] { + if (typeof node === 'string') { + const matches = node.match(/\{\{[\s\S]*?\}\}/g); + + if (matches) { + acc.push(...matches); + } + + return acc; + } + + if (Array.isArray(node)) { + node.forEach((child) => collectBindings(child, acc)); + + return acc; + } + + if (node && typeof node === 'object') { + Object.values(node as Record).forEach((child) => + collectBindings(child, acc), + ); + } + + return acc; +} + +/** + * 레이아웃 파일을 읽어 파싱합니다. + * + * @param filePath 레이아웃 파일 절대 경로 + * @returns 파싱된 JSON 객체 + */ +function readLayout(filePath: string): Record { + return JSON.parse(fs.readFileSync(filePath, 'utf-8')); +} + +describe('게시판 알림 정의 탭 — 채널 단일 출처 (#518)', () => { + it('채널 표현식이 URL 만 읽는다', () => { + const bindings = [ + ...collectBindings(readLayout(SETTINGS_LAYOUT)), + ...collectBindings(readLayout(NOTIFICATION_TAB)), + ]; + + const channelReads = bindings.filter((binding) => /query\.channel/.test(binding)); + + expect( + channelReads.length, + '채널을 읽는 표현식이 있어야 한다 (0건이면 이 단언은 공회전한다)', + ).toBeGreaterThan(0); + + const dualSourced = channelReads.filter((binding) => /_local\./.test(binding)); + + expect( + dualSourced, + 'URL 이 나르지 않는 값(_local)을 채널 출처로 섞으면 데이터와 화면이 서로 다른 채널을 본다', + ).toEqual([]); + }); + + it('채널 전환은 URL 만 갱신한다 (로컬 상태에 복제하지 않는다)', () => { + const raw = fs.readFileSync(NOTIFICATION_TAB, 'utf-8'); + + expect( + raw.includes('activeNotificationChannel'), + '채널을 로컬 상태에 복제하면 URL 과 어긋날 수 있는 두 번째 출처가 다시 생긴다', + ).toBe(false); + + expect( + fs.readFileSync(SETTINGS_LAYOUT, 'utf-8').includes('activeNotificationChannel'), + 'initLocal 에 남겨두면 소비자가 다시 참조하게 된다', + ).toBe(false); + }); +}); + +describe('게시판 알림 정의 탭 — 서버 채널 필터 정합 (#518)', () => { + /** + * 알림 정의 목록 데이터소스를 찾습니다. + * + * @returns 데이터소스 정의 객체 + */ + function findDefinitionsDataSource(): Record { + const layout = readLayout(SETTINGS_LAYOUT); + const source = (layout.data_sources ?? []).find((ds: Record) => + typeof ds.endpoint === 'string' && ds.endpoint.includes('/api/admin/notification-definitions'), + ); + + expect(source, '알림 정의 목록 데이터소스를 찾지 못했다').toBeTruthy(); + + return source; + } + + it('templates 를 그리는 화면이면 목록 요청에 template_channel 을 보낸다', () => { + const consumesTemplates = collectBindings(readLayout(NOTIFICATION_TAB)).some((binding) => + /def\.templates/.test(binding), + ); + + expect( + consumesTemplates, + 'templates 소비가 0건이면 이 단언은 공회전한다 (탭 구조가 바뀌었는지 확인)', + ).toBe(true); + + // 서버는 `template_channel` 을 받은 요청에만 templates 를 싣는다. 화면이 templates 를 + // 읽으면서 이 파라미터를 빼면 전 행이 「이 채널에 대한 템플릿이 없습니다」 가 된다. + expect( + findDefinitionsDataSource().params?.template_channel, + 'templates 를 소비하는 목록이 채널을 서버에 넘기지 않으면 응답에 templates 키 자체가 없다', + ).toBeDefined(); + }); + + it('서버에 넘기는 채널과 화면이 찾는 채널이 같은 표현식이다', () => { + const sent = findDefinitionsDataSource().params.template_channel; + + // 화면이 템플릿을 고를 때 쓰는 채널 표현식을 추출한다. + const screenChannel = collectBindings(readLayout(NOTIFICATION_TAB)) + .find((binding) => /t\.channel === /.test(binding)) + ?.match(/t\.channel === \(([^)]+)\)/)?.[1]; + + expect(screenChannel, '화면의 채널 비교 표현식을 찾지 못했다').toBeTruthy(); + + // 예: 전송 "{{query.channel ?? 'mail'}}" ↔ 화면 "query.channel ?? 'mail'" + expect( + sent.replace(/^\{\{|\}\}$/g, '').trim(), + '두 채널이 어긋나면 서버가 실어 보낸 채널과 화면이 찾는 채널이 달라 빈 값이 된다', + ).toBe(screenChannel!.trim()); + }); + + it('되돌리기 버튼은 서버 집계(has_customized_templates)로 판정한다', () => { + const raw = fs.readFileSync(NOTIFICATION_TAB, 'utf-8'); + + // templates 는 한 채널로 좁혀 실리므로, 배열을 순회해서는 "다른 채널에서 커스터마이즈됨" + // 을 알 수 없다. 채널을 가리지 않는 판정은 서버 집계가 유일한 출처다. + expect( + /def\.templates[\s\S]{0,80}?\.some\(/.test(raw), + '되돌리기 조건을 templates 배열 순회로 판정하면 다른 채널의 커스터마이즈를 놓친다', + ).toBe(false); + + expect( + raw.includes('def.has_customized_templates'), + '되돌리기 노출 조건이 서버 집계를 읽어야 한다', + ).toBe(true); + }); +}); 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 e6b0877b..5ad705ee 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 @@ -37,7 +37,6 @@ ] }, "initLocal": { - "activeNotificationChannel": null, "editingDefinition": null, "editingTemplate": null, "editingRecipients": [], @@ -128,7 +127,8 @@ "page": "{{query.page || 1}}", "per_page": "{{query.per_page || 20}}", "search": "{{query.search}}", - "search_type": "{{query.search_type}}" + "search_type": "{{query.search_type}}", + "template_channel": "{{query.channel ?? 'mail'}}" }, "initLocal": { "_merge": "deep", diff --git a/modules/_bundled/sirsoft-board/resources/layouts/admin/partials/admin_board_settings/_tab_notification_definitions.json b/modules/_bundled/sirsoft-board/resources/layouts/admin/partials/admin_board_settings/_tab_notification_definitions.json index 0a2bf959..403b57e7 100644 --- a/modules/_bundled/sirsoft-board/resources/layouts/admin/partials/admin_board_settings/_tab_notification_definitions.json +++ b/modules/_bundled/sirsoft-board/resources/layouts/admin/partials/admin_board_settings/_tab_notification_definitions.json @@ -146,7 +146,7 @@ }, "props": { "type": "button", - "className": "{{(_local.activeNotificationChannel ?? query.channel ?? (availableChannels?.data?.channels ?? [])[0]?.id ?? 'mail') === ch.id ? 'border-b-2 border-indigo-500 dark:border-indigo-400 text-indigo-600 dark:text-indigo-400' : 'text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300'}} px-4 py-2.5 text-sm font-medium whitespace-nowrap flex-center gap-2" + "className": "{{(query.channel ?? 'mail') === ch.id ? 'border-b-2 border-indigo-500 dark:border-indigo-400 text-indigo-600 dark:text-indigo-400' : 'text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300'}} px-4 py-2.5 text-sm font-medium whitespace-nowrap flex-center gap-2" }, "children": [ { @@ -169,13 +169,6 @@ "handler": "sequence", "params": { "actions": [ - { - "handler": "setState", - "params": { - "target": "local", - "activeNotificationChannel": "{{ch.id}}" - } - }, { "handler": "navigate", "params": { @@ -400,16 +393,16 @@ { "type": "basic", "name": "P", - "if": "{{(def.templates ?? []).find(t => t.channel === (_local.activeNotificationChannel ?? query.channel ?? 'mail'))}}", + "if": "{{(def.templates ?? []).find(t => t.channel === (query.channel ?? 'mail'))}}", "props": { "className": "text-label-subtle truncate" }, - "text": "{{(def.templates ?? []).find(t => t.channel === (_local.activeNotificationChannel ?? query.channel ?? 'mail'))?.subject?.[$locale] ?? ''}}" + "text": "{{(def.templates ?? []).find(t => t.channel === (query.channel ?? 'mail'))?.subject?.[$locale] ?? ''}}" }, { "type": "basic", "name": "P", - "if": "{{!(def.templates ?? []).find(t => t.channel === (_local.activeNotificationChannel ?? query.channel ?? 'mail'))}}", + "if": "{{!(def.templates ?? []).find(t => t.channel === (query.channel ?? 'mail'))}}", "props": { "className": "text-sm text-gray-400 dark:text-gray-500 italic" }, @@ -419,7 +412,7 @@ "id": "board_recipients_badge_{{defIdx}}", "type": "basic", "name": "Div", - "if": "{{((def.templates ?? []).find(t => t.channel === (_local.activeNotificationChannel ?? query.channel ?? 'mail'))?.recipients ?? []).length > 0}}", + "if": "{{((def.templates ?? []).find(t => t.channel === (query.channel ?? 'mail'))?.recipients ?? []).length > 0}}", "props": { "className": "flex-center flex-wrap gap-1.5 mt-2" }, @@ -436,7 +429,7 @@ "type": "basic", "name": "Span", "iteration": { - "source": "{{(def.templates ?? []).find(t => t.channel === (_local.activeNotificationChannel ?? query.channel ?? 'mail'))?.recipients ?? []}}", + "source": "{{(def.templates ?? []).find(t => t.channel === (query.channel ?? 'mail'))?.recipients ?? []}}", "item_var": "rcpt" }, "props": { @@ -505,7 +498,7 @@ "name": "notification_definition_row_footer", "props": { "definition": "{{def}}", - "activeChannel": "{{_local.activeNotificationChannel ?? query.channel ?? 'mail'}}" + "activeChannel": "{{query.channel ?? 'mail'}}" } } ] @@ -520,9 +513,9 @@ { "type": "composite", "name": "Toggle", - "if": "{{(def.templates ?? []).find(t => t.channel === (_local.activeNotificationChannel ?? query.channel ?? 'mail'))}}", + "if": "{{(def.templates ?? []).find(t => t.channel === (query.channel ?? 'mail'))}}", "props": { - "checked": "{{(def.templates ?? []).find(t => t.channel === (_local.activeNotificationChannel ?? query.channel ?? 'mail'))?.is_active ?? false}}", + "checked": "{{(def.templates ?? []).find(t => t.channel === (query.channel ?? 'mail'))?.is_active ?? false}}", "disabled": "{{_computed.isNotificationReadOnly}}" }, "actions": [ @@ -530,7 +523,7 @@ "type": "change", "handler": "apiCall", "auth_required": true, - "target": "{{'/api/admin/notification-templates/' + (def.templates ?? []).find(t => t.channel === (_local.activeNotificationChannel ?? query.channel ?? 'mail'))?.id + '/toggle-active'}}", + "target": "{{'/api/admin/notification-templates/' + (def.templates ?? []).find(t => t.channel === (query.channel ?? 'mail'))?.id + '/toggle-active'}}", "params": { "method": "PATCH" }, @@ -581,9 +574,9 @@ "params": { "target": "global", "board_notification_template_form_modal": { - "template": "{{(def.templates ?? []).find(t => t.channel === (_local.activeNotificationChannel ?? query.channel ?? 'mail'))}}", + "template": "{{(def.templates ?? []).find(t => t.channel === (query.channel ?? 'mail'))}}", "definition": "{{def}}", - "recipients": "{{JSON.parse(JSON.stringify((def.templates ?? []).find(t => t.channel === (_local.activeNotificationChannel ?? query.channel ?? 'mail'))?.recipients ?? []))}}", + "recipients": "{{JSON.parse(JSON.stringify((def.templates ?? []).find(t => t.channel === (query.channel ?? 'mail'))?.recipients ?? []))}}", "editLang": "{{$locale}}", "errors": null, "isSaving": false, @@ -604,7 +597,7 @@ { "type": "basic", "name": "Button", - "if": "{{!def.is_default || (def.templates ?? []).some(t => t.is_default === false)}}", + "if": "{{!def.is_default || def.has_customized_templates === true}}", "props": { "type": "button", "disabled": "{{_computed.isNotificationReadOnly}}", @@ -656,7 +649,7 @@ "type": "composite", "name": "HtmlContent", "props": { - "content": "{{(def.templates ?? []).find(t => t.channel === (_local.activeNotificationChannel ?? query.channel ?? 'mail'))?.body?.[$locale] ?? ''}}", + "content": "{{(def.templates ?? []).find(t => t.channel === (query.channel ?? 'mail'))?.body?.[$locale] ?? ''}}", "isHtml": true, "className": "prose dark:prose-invert prose-sm max-w-none bg-gray-50 dark:bg-gray-900/50 rounded-lg p-3 max-h-48 overflow-y-auto dark:text-white" } diff --git a/modules/_bundled/sirsoft-board/src/Http/Resources/BoardCollection.php b/modules/_bundled/sirsoft-board/src/Http/Resources/BoardCollection.php index 1bf1c260..4cb54a25 100644 --- a/modules/_bundled/sirsoft-board/src/Http/Resources/BoardCollection.php +++ b/modules/_bundled/sirsoft-board/src/Http/Resources/BoardCollection.php @@ -2,8 +2,8 @@ namespace Modules\Sirsoft\Board\Http\Resources; -use Illuminate\Http\Request; use App\Http\Resources\BaseApiCollection; +use Illuminate\Http\Request; use Illuminate\Pagination\LengthAwarePaginator; use Modules\Sirsoft\Board\Traits\ChecksBoardPermission; @@ -48,8 +48,11 @@ class BoardCollection extends BaseApiCollection public function withPermissions(): array { return [ + // 목록은 경량 표현만 내려준다. 전체 표현(toArray)은 게시판마다 + // 매니저/스텝 역할과 그 소속 사용자 전원을 조회해 붙이는데(행당 추가 쿼리), + // 관리자 목록 화면은 그 값을 쓰지 않는다 — 역할 편집은 게시판 상세/설정이 공급한다. 'data' => $this->collection->map(function ($board) { - return new BoardResource($board); + return (new BoardResource($board))->toAdminListArray(request()); }), 'pagination' => $this->resource instanceof LengthAwarePaginator ? [ 'current_page' => $this->resource->currentPage(), diff --git a/modules/_bundled/sirsoft-board/src/Http/Resources/BoardResource.php b/modules/_bundled/sirsoft-board/src/Http/Resources/BoardResource.php index f67489f9..e1e997b5 100644 --- a/modules/_bundled/sirsoft-board/src/Http/Resources/BoardResource.php +++ b/modules/_bundled/sirsoft-board/src/Http/Resources/BoardResource.php @@ -142,6 +142,35 @@ class BoardResource extends BaseApiResource ]; } + /** + * 관리자 게시판 목록용 경량 배열로 변환합니다. + * + * 사용자 목록(`toListArray`)보다 표시 항목이 조금 더 많습니다 — 관리자 목록은 유형 뱃지·사용 + * 여부·분류 개수를 함께 보여줍니다. 그렇다고 전체 표현(`toArray`)을 쓸 수는 없습니다. + * 전체 표현은 게시판마다 매니저/스텝 역할과 **그 역할에 속한 사용자 전원(uuid·이름·이메일)** 을 + * 조회해 붙이는데, 목록 화면은 그 값을 쓰지 않으면서 행 수만큼 추가 쿼리와 개인정보를 싣게 됩니다. + * 역할 편집은 게시판 상세/설정 화면이 공급합니다. + * + * @param Request|null $request HTTP 요청 + * @return array 관리자 목록용 배열 + */ + public function toAdminListArray(?Request $request = null): array + { + $request = $request ?? request(); + + return [ + ...$this->toListArray($request), + + 'type' => $this->getValue('type'), + 'is_active' => (bool) $this->getValue('is_active'), + 'categories' => $this->getValue('categories') ?? [], + + // 관리자 목록 화면은 「등록일」 컬럼을 그린다(admin_board_index.json). `toListArray` 에는 + // 없는 값이라 여기서 명시적으로 싣는다 — 빠지면 예외도 오류도 없이 그 칸만 공란이 된다. + 'created_at' => $this->formatDateTimeStringForUser($this->created_at), + ]; + } + /** * 게시글 목록 API 응답에 포함할 게시판 정보를 반환합니다 (User용). * diff --git a/modules/_bundled/sirsoft-board/tests/Feature/Admin/AdminBoardListPayloadTest.php b/modules/_bundled/sirsoft-board/tests/Feature/Admin/AdminBoardListPayloadTest.php new file mode 100644 index 00000000..232aaf0a --- /dev/null +++ b/modules/_bundled/sirsoft-board/tests/Feature/Admin/AdminBoardListPayloadTest.php @@ -0,0 +1,115 @@ + 'basic'], + ['name' => ['ko' => '기본', 'en' => 'Basic'], 'is_active' => true, 'is_default' => true] + ); + + $this->adminUser = $this->createAdminUser([ + 'sirsoft-board.boards.read', + ]); + } + + /** + * 관리자 목록 행에 등록일이 실린다. + * + * 관리자 게시판 목록 화면(admin_board_index.json)은 「등록일」 컬럼에 `row.created_at` 을 + * 그린다. 목록 표현이 이 키를 빼면 화면은 조용히 공란이 된다. + * + * @return void + * + * @scenario endpoint=admin_list,role_assignment=unassigned + * @effects admin_list_row_carries_created_at + */ + public function test_admin_board_list_row_carries_created_at(): void + { + $board = Board::create([ + 'name' => ['ko' => '등록일 확인 게시판'], + 'slug' => 'created-at-'.uniqid(), + 'type' => 'basic', + 'is_active' => true, + ]); + + $rows = $this->actingAs($this->adminUser) + ->getJson('/api/modules/sirsoft-board/admin/boards') + ->assertStatus(200) + ->json('data.data'); + + $row = collect($rows)->firstWhere('id', $board->id); + + $this->assertNotNull($row, '생성한 게시판이 목록에 있어야 한다'); + $this->assertArrayHasKey( + 'created_at', + $row, + '관리자 목록 화면이 등록일 컬럼을 그리는데 응답에 키가 없으면 공란으로만 렌더된다' + ); + $this->assertNotNull($row['created_at'], '등록일 값이 null 이면 화면은 여전히 공란이다'); + $this->assertMatchesRegularExpression( + '/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/', + $row['created_at'], + '전체 표현(toArray)과 같은 사용자 타임존 문자열 형식이어야 한다' + ); + } + + /** + * 목록 표현은 화면이 쓰지 않는 무거운 항목을 싣지 않는다 (프루닝 유지). + * + * 등록일 누락을 고치면서 전체 표현으로 되돌리면 게시판마다 매니저/스텝 역할의 + * 소속 사용자 전원(uuid·이름·이메일)이 다시 실린다 — 행당 추가 쿼리이자 개인정보 노출이다. + * + * @return void + * + * @scenario endpoint=admin_list,role_assignment=unassigned + * @effects admin_list_omits_role_member_payload + */ + public function test_admin_board_list_stays_pruned(): void + { + Board::create([ + 'name' => ['ko' => '프루닝 확인 게시판'], + 'slug' => 'pruned-'.uniqid(), + 'type' => 'basic', + 'is_active' => true, + ]); + + $row = $this->actingAs($this->adminUser) + ->getJson('/api/modules/sirsoft-board/admin/boards') + ->assertStatus(200) + ->json('data.data.0'); + + foreach (['board_managers', 'board_steps', 'board_manager_ids', 'board_step_ids', 'permissions'] as $key) { + $this->assertArrayNotHasKey( + $key, + $row, + "목록 표현에 {$key} 가 다시 실리면 행 수만큼 추가 쿼리가 발생한다" + ); + } + } +} diff --git a/modules/_bundled/sirsoft-board/tests/Feature/Admin/BoardManagementTest.php b/modules/_bundled/sirsoft-board/tests/Feature/Admin/BoardManagementTest.php index 82026233..f6e756ea 100644 --- a/modules/_bundled/sirsoft-board/tests/Feature/Admin/BoardManagementTest.php +++ b/modules/_bundled/sirsoft-board/tests/Feature/Admin/BoardManagementTest.php @@ -216,6 +216,173 @@ class BoardManagementTest extends ModuleTestCase $this->assertTrue($board->fresh()->is_active); } + /** + * 관리자 게시판 목록에는 역할 소속 사용자 정보가 실리지 않는다 (#518 / 공개 #76). + * + * 종전에는 행마다 매니저/스텝 역할을 조회해 그 역할에 속한 **사용자 전원의 uuid·이름·이메일**을 + * 목록에 실었다. 목록 화면은 그 값을 쓰지 않으면서 행 수만큼 추가 쿼리가 나가고 개인정보가 + * 노출됐다. 역할 편집은 게시판 상세/설정 화면이 공급한다. + * + * @scenario endpoint=admin_list,role_assignment=assigned + * + * @effects admin_list_omits_role_member_payload + */ + public function test_board_list_omits_role_member_payload(): void + { + Board::factory()->count(2)->create(['is_active' => true]); + + $response = $this->actingAs($this->adminUser) + ->getJson('/api/modules/sirsoft-board/admin/boards'); + + $response->assertStatus(200); + + foreach ($response->json('data.data') as $row) { + foreach (['board_managers', 'board_steps', 'board_manager_ids', 'board_step_ids'] as $heavy) { + $this->assertArrayNotHasKey($heavy, $row, "게시판 목록에 {$heavy} 가 실리면 안 된다"); + } + } + } + + /** + * 관리자 목록 화면이 쓰는 표시 필드는 그대로 남는다 (기능 축소 아님). + * + * @scenario endpoint=admin_list,role_assignment=unassigned + * + * @effects admin_list_keeps_display_fields + */ + public function test_board_list_keeps_admin_display_fields(): void + { + Board::factory()->create(['is_active' => true]); + + $response = $this->actingAs($this->adminUser) + ->getJson('/api/modules/sirsoft-board/admin/boards'); + + $response->assertStatus(200); + + $row = $response->json('data.data.0'); + + foreach (['id', 'name', 'slug', 'type', 'is_active', 'categories', 'posts_count', 'abilities'] as $field) { + $this->assertArrayHasKey($field, $row, "관리자 목록이 쓰는 {$field} 가 사라졌다"); + } + } + + /** + * 목록 응답 어디에도 사용자 이메일이 실리지 않는다. + * + * 키 이름만 검사하면 필드가 다른 이름으로 되살아났을 때 통과한다. 노출되면 안 되는 것은 + * 특정 키가 아니라 **값**이므로 응답 전문에서 이메일 문자열 자체를 찾는다. + * + * @effects admin_list_never_exposes_member_email + */ + public function test_board_list_never_exposes_member_email(): void + { + $manager = User::factory()->create(['email' => 'board-manager-probe@example.com']); + $board = Board::factory()->create(['is_active' => true]); + + $managerRole = Role::firstOrCreate( + ['identifier' => "sirsoft-board.{$board->slug}.manager"], + ['name' => json_encode(['ko' => '매니저', 'en' => 'Manager']), 'is_active' => true] + ); + $managerRole->users()->syncWithoutDetaching([$manager->id]); + + $response = $this->actingAs($this->adminUser) + ->getJson('/api/modules/sirsoft-board/admin/boards'); + + $response->assertStatus(200); + $this->assertStringNotContainsString( + 'board-manager-probe@example.com', + $response->getContent(), + '게시판 목록 응답에 역할 소속 사용자의 이메일이 실렸다' + ); + } + + /** + * 목록에서 뺀 역할 배정은 상세 조회가 여전히 공급한다 (기능 축소 아님). + * + * @scenario endpoint=detail,role_assignment=assigned + * + * @effects detail_still_provides_role_assignments + */ + public function test_board_detail_still_provides_role_assignments(): void + { + $manager = User::factory()->create(); + $board = Board::factory()->create(['is_active' => true]); + + $managerRole = Role::firstOrCreate( + ['identifier' => "sirsoft-board.{$board->slug}.manager"], + ['name' => json_encode(['ko' => '매니저', 'en' => 'Manager']), 'is_active' => true] + ); + $managerRole->users()->syncWithoutDetaching([$manager->id]); + + $response = $this->actingAs($this->adminUser) + ->getJson("/api/modules/sirsoft-board/admin/boards/{$board->id}"); + + $response->assertStatus(200); + + $data = $response->json('data'); + + $this->assertArrayHasKey('board_manager_ids', $data, '상세에서 역할 배정이 사라지면 설정 화면이 비어 열린다'); + $this->assertContains($manager->uuid, $data['board_manager_ids']); + } + + /** + * 역할이 배정되지 않은 게시판도 상세에서 필드 자체는 유지된다 (빈 배열). + * + * 필드가 통째로 사라지면 화면이 "아직 안 불러온 상태" 와 "배정이 없는 상태" 를 구분하지 + * 못한다. + * + * @scenario endpoint=detail,role_assignment=unassigned + * + * @effects detail_still_provides_role_assignments + */ + public function test_board_detail_returns_empty_role_assignments_when_unassigned(): void + { + $board = Board::factory()->create(['is_active' => true]); + + $response = $this->actingAs($this->adminUser) + ->getJson("/api/modules/sirsoft-board/admin/boards/{$board->id}"); + + $response->assertStatus(200); + + $data = $response->json('data'); + + $this->assertArrayHasKey('board_manager_ids', $data); + $this->assertSame([], $data['board_manager_ids']); + } + + /** + * 게시판 수가 늘어도 역할 조회가 늘지 않는다 (행당 재조회 부재). + * + * @effects role_query_count_does_not_scale_with_board_count + */ + public function test_board_list_role_query_count_does_not_grow_with_rows(): void + { + Board::factory()->count(2)->create(['is_active' => true]); + + // 권한/설정 캐시 워밍 (측정 대상 제외) + $this->actingAs($this->adminUser)->getJson('/api/modules/sirsoft-board/admin/boards')->assertOk(); + + $queries = []; + DB::listen(function ($query) use (&$queries) { + $queries[] = $query->sql; + }); + + $this->actingAs($this->adminUser)->getJson('/api/modules/sirsoft-board/admin/boards')->assertOk(); + $baseline = count(array_filter($queries, fn (string $sql) => $this->isBoardRoleMemberQuery($sql))); + + Board::factory()->count(8)->create(['is_active' => true]); + + $queries = []; + $this->actingAs($this->adminUser)->getJson('/api/modules/sirsoft-board/admin/boards')->assertOk(); + $grown = count(array_filter($queries, fn (string $sql) => $this->isBoardRoleMemberQuery($sql))); + + $this->assertSame( + $baseline, + $grown, + "게시판 수에 비례해 역할 조회가 늘었다 (기준: {$baseline}, 증가 후: {$grown})" + ); + } + /** * 게시판 목록 조회 시 is_active 필드 포함 테스트 */ @@ -779,4 +946,19 @@ class BoardManagementTest extends ModuleTestCase $managerRole->refresh(); $this->assertEquals($originalName, $managerRole->name); } + + /** + * 게시판 역할 **소속 사용자 조회** 쿼리인지 판정합니다. + * + * 권한 체크(`hasPermission`)도 같은 피벗 테이블을 참조하지만 그쪽은 `exists(...)` 서브쿼리이며, + * 행마다 실행되는 그 N+1 은 별도 이슈(#519)가 소유한다. 이 테스트는 목록이 게시판마다 역할 + * 소속 사용자를 다시 끌어오지 않는지만 본다. + * + * @param string $sql 실행된 SQL + * @return bool 역할 소속 사용자 조회 여부 + */ + private function isBoardRoleMemberQuery(string $sql): bool + { + return str_contains($sql, 'user_roles') && ! str_contains($sql, 'exists('); + } } diff --git a/modules/_bundled/sirsoft-board/tests/Feature/BoardCloneTest.php b/modules/_bundled/sirsoft-board/tests/Feature/BoardCloneTest.php index aa4c5b8e..05eac717 100644 --- a/modules/_bundled/sirsoft-board/tests/Feature/BoardCloneTest.php +++ b/modules/_bundled/sirsoft-board/tests/Feature/BoardCloneTest.php @@ -10,6 +10,7 @@ use App\Models\Role; use App\Models\User; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; +use Modules\Sirsoft\Board\Http\Resources\BoardResource; use Modules\Sirsoft\Board\Models\Board; use Modules\Sirsoft\Board\Models\BoardType; use Modules\Sirsoft\Board\Services\BoardService; @@ -88,7 +89,8 @@ class BoardCloneTest extends ModuleTestCase * 16-(1)/(2): copyBoard 반환에 board_manager_ids 와 manager 객체 배열이 채워진다. * * @scenario original_permissions=default - * @effects clone_includes_manager_ids, clone_includes_manager_objects_with_labels + * + * @effects clone_includes_manager_ids, clone_includes_manager_objects_with_labels, board_copy_still_resolves_roles */ public function test_copy_board_includes_manager_ids_and_objects(): void { @@ -122,6 +124,7 @@ class BoardCloneTest extends ModuleTestCase * 4-3 형식 일치: copyBoard 의 role 데이터가 BoardResource 산출과 동일 구조여야 한다. * * @scenario original_permissions=default + * * @effects clone_role_data_matches_resource_output */ public function test_copy_board_role_data_matches_resource_output(): void @@ -132,7 +135,7 @@ class BoardCloneTest extends ModuleTestCase $copyData = $this->boardService->copyBoard($board->id); - $expected = \Modules\Sirsoft\Board\Http\Resources\BoardResource::getBoardRoleData( + $expected = BoardResource::getBoardRoleData( "sirsoft-board.{$board->slug}" ); @@ -147,6 +150,7 @@ class BoardCloneTest extends ModuleTestCase * manager/step identifier 가 없어야 하고, 비-스코프 역할은 보존되어야 한다. * * @scenario original_permissions=custom + * * @effects clone_strips_old_slug_scope_roles, clone_preserves_non_scope_roles */ public function test_copy_board_strips_old_slug_scope_roles_but_keeps_others(): void @@ -188,6 +192,7 @@ class BoardCloneTest extends ModuleTestCase * 422 가 아니라 201 로 생성되고, manager 명단이 원본과 동일하게 승계된다. * * @scenario original_permissions=default + * * @effects clone_saved_201, managers_copied */ public function test_clone_form_data_saves_201_and_inherits_managers(): void @@ -225,6 +230,7 @@ class BoardCloneTest extends ModuleTestCase * 역할(sirsoft-board.{src}.manager/step)이 attach 되지 않는다 (교차 게시판 누수 0). * * @scenario original_permissions=custom + * * @effects permissions_copied, no_cross_board_role_leak */ public function test_clone_save_does_not_leak_source_scope_roles(): void @@ -260,6 +266,7 @@ class BoardCloneTest extends ModuleTestCase * 동일 권한에 그대로 복사된다. (누수 차단과 별개로, 의도 권한이 실제 승계되는지) * * @scenario original_permissions=custom + * * @effects permissions_copied, custom_role_assignment_preserved */ public function test_clone_save_copies_custom_role_assignment(): void @@ -298,6 +305,7 @@ class BoardCloneTest extends ModuleTestCase * 복제 폼 저장이 boolean 검증(422)에 걸리지 않는다. * * @scenario original_permissions=default + * * @effects clone_includes_add_to_menu_boolean */ public function test_copy_board_includes_add_to_menu_as_boolean(): void diff --git a/modules/_bundled/sirsoft-board/tests/Playwright/specs/admin/board-admin-list-role-payload.spec.ts b/modules/_bundled/sirsoft-board/tests/Playwright/specs/admin/board-admin-list-role-payload.spec.ts new file mode 100644 index 00000000..4df28350 --- /dev/null +++ b/modules/_bundled/sirsoft-board/tests/Playwright/specs/admin/board-admin-list-role-payload.spec.ts @@ -0,0 +1,148 @@ +/** + * 관리자 게시판 목록 역할 페이로드 프루닝 — 목록이 역할·소속 회원 명단을 싣지 않는지 (#518 / 공개 #76). + * + * 결함: 목록 Collection 이 행마다 `Role::whereIn()->with('users')` 를 돌려 N+1 을 만들고, + * 매니저·스텝 역할에 소속된 **모든 사용자의 uuid·이름·이메일**을 응답에 실었다. 화면은 + * 그 값을 그리지 않는다 — 게시판 이름/식별자/유형/활성여부/분류만 그린다. + * + * 이 spec 이 브라우저 수준을 담당하는 이유: 서버 단언(BoardManagementTest)은 응답 형태만 본다. + * 목록 표현을 경량 경로로 갈아끼우면서 화면이 쓰던 필드를 함께 떨구면 열이 조용히 비는데, + * 그건 응답 단언으로는 드러나지 않는다. 여기서는 실제 렌더된 행을 확인한다. + * + * 시드 의존: 샘플 시드의 게시판 목록(최소 1건). + * + * @scenario board-admin-list-role-payload-pruning + * @axes endpoint=admin_list endpoint=detail + * @effects admin_list_omits_role_member_payload admin_list_never_exposes_member_email admin_list_keeps_display_fields detail_still_provides_role_assignments + */ +import { test, expect, authenticatePage } from '../../fixtures/board-auth'; + +const LIST_URL = '/admin/boards'; +const LIST_API = /\/api\/modules\/sirsoft-board\/admin\/boards(\?|$)/; + +/** 목록에서 빠져야 하는 역할 페이로드 키 */ +const ROLE_KEYS = [ + 'board_managers', + 'board_steps', + 'board_manager_ids', + 'board_step_ids', +]; + +test.describe('관리자 게시판 목록 역할 페이로드 (#518)', () => { + // @scenario endpoint=admin_list + // @effects admin_list_omits_role_member_payload + test('목록 응답에 역할 배열과 역할 ID 목록이 없다', async ({ page, boardManageToken }) => { + await authenticatePage(page, boardManageToken); + + const listResponse = page.waitForResponse( + (res) => LIST_API.test(res.url()) && res.request().method() === 'GET', + { timeout: 30_000 }, + ); + + await page.goto(LIST_URL); + const payload = await (await listResponse).json(); + + const rows = payload?.data?.data ?? []; + expect(rows.length, '게시판이 최소 1건은 있어야 판정할 수 있다').toBeGreaterThan(0); + + for (const row of rows) { + for (const key of ROLE_KEYS) { + expect(row, `목록 행에 ${key} 가 실리면 안 된다`).not.toHaveProperty(key); + } + } + }); + + // @scenario endpoint=admin_list + // @effects admin_list_never_exposes_member_email + test('목록 응답 본문에 회원 이메일이 등장하지 않는다', async ({ page, boardManageToken }) => { + await authenticatePage(page, boardManageToken); + + const listResponse = page.waitForResponse( + (res) => LIST_API.test(res.url()) && res.request().method() === 'GET', + { timeout: 30_000 }, + ); + + await page.goto(LIST_URL); + const body = await (await listResponse).text(); + + // 키 이름이 아니라 본문 전체를 본다 — 역할 페이로드가 다른 키 이름으로 되살아나도 잡힌다. + expect(body, '게시판 목록에 회원 이메일이 실리면 안 된다').not.toMatch( + /"email"\s*:\s*"[^"]+@/, + ); + expect(body, '게시판 목록에 회원 uuid 가 실리면 안 된다').not.toMatch(/"uuid"\s*:/); + }); + + // @scenario endpoint=admin_list + // @effects admin_list_keeps_display_fields + test('목록이 그리는 열(이름·식별자·유형·활성여부)이 그대로 렌더된다', async ({ + page, + boardManageToken, + }) => { + await authenticatePage(page, boardManageToken); + + const listResponse = page.waitForResponse( + (res) => LIST_API.test(res.url()) && res.request().method() === 'GET', + { timeout: 30_000 }, + ); + + await page.goto(LIST_URL); + const payload = await (await listResponse).json(); + await page.waitForLoadState('networkidle', { timeout: 30_000 }); + + const first = payload?.data?.data?.[0]; + expect(first, '첫 행이 있어야 한다').toBeTruthy(); + + // 응답 계약 — 화면이 순회하는 6필드 + for (const key of ['name', 'slug', 'type', 'is_active', 'categories', 'abilities']) { + expect(first, `목록이 그리는 필드 ${key} 가 빠졌다`).toHaveProperty(key); + } + + // 렌더 결과 — 존재를 먼저 확정한 뒤 값을 본다 + const rows = page.locator('tbody tr'); + await expect(rows.first()).toBeVisible({ timeout: 30_000 }); + + const slug = String(first.slug); + await expect( + page.locator('tbody').getByText(slug, { exact: false }).first(), + '식별자 열이 비면 경량 전환이 화면을 깎은 것이다', + ).toBeVisible(); + }); + + // @scenario endpoint=detail + // @effects detail_still_provides_role_assignments + test('단건 조회는 역할 정보를 그대로 제공한다 (대체 경로 보존)', async ({ + page, + boardManageToken, + }) => { + await authenticatePage(page, boardManageToken); + + const listResponse = page.waitForResponse( + (res) => LIST_API.test(res.url()) && res.request().method() === 'GET', + { timeout: 30_000 }, + ); + await page.goto(LIST_URL); + const payload = await (await listResponse).json(); + const slug = payload?.data?.data?.[0]?.slug; + expect(slug, '단건 조회 대상 slug 가 필요하다').toBeTruthy(); + + // 인증은 Bearer 토큰이다 — 쿠키 세션이 아니므로 헤더를 명시해야 401 이 나지 않는다. + const detail = await page.evaluate( + async ({ boardSlug, token }) => { + const res = await fetch(`/api/modules/sirsoft-board/admin/boards/${boardSlug}`, { + headers: { + Accept: 'application/json', + Authorization: `Bearer ${token}`, + }, + }); + + return { status: res.status, body: await res.json() }; + }, + { boardSlug: slug, token: boardManageToken }, + ); + + expect(detail.status).toBe(200); + // 목록에서 뺀 값은 단건이 공급한다 — 없애는 것이 아니라 옮기는 것이다. + expect(detail.body?.data).toHaveProperty('board_manager_ids'); + expect(detail.body?.data).toHaveProperty('board_step_ids'); + }); +}); diff --git a/modules/_bundled/sirsoft-board/tests/Playwright/specs/admin/board-notification-definition-channel.spec.ts b/modules/_bundled/sirsoft-board/tests/Playwright/specs/admin/board-notification-definition-channel.spec.ts new file mode 100644 index 00000000..b3da6e0b --- /dev/null +++ b/modules/_bundled/sirsoft-board/tests/Playwright/specs/admin/board-notification-definition-channel.spec.ts @@ -0,0 +1,188 @@ +/** + * 게시판 알림 정의 탭 채널 계약 — 목록 요청이 화면 채널을 나르는지 (#518 / 공개 #76). + * + * 결함(2026-08-05 브라우저 실측): 목록 프루닝으로 서버가 `template_channel` 을 받은 요청에만 + * `templates` 를 싣게 됐는데, 코어 설정 화면만 그 파라미터를 보내도록 고쳐지고 게시판 탭 + * 데이터소스는 빠졌다. 7건 전 행이 「이 채널에 대한 템플릿이 없습니다」로 표시되고 + * 제목·수신자·활성 토글·편집 모달 초기값이 통째로 비었다. + * + * 이 spec 이 브라우저 수준을 담당하는 이유: 구조 테스트(Vitest)는 레이아웃 JSON 에 + * `template_channel` 키가 있는지만 본다. 그 키가 **실제로 요청에 실려 나가는지**와 + * 그 응답으로 행이 채워지는지는 렌더까지 가야 드러난다 — 결함 당시 응답은 200 이었고 + * 행 수도 정상이었다. + * + * 시드 의존: 게시판 모듈 알림 정의(최소 1건). 커스터마이즈 축은 화면에서 만들고 되돌린다. + */ +import { test, expect, authenticatePage } from '../../fixtures/board-auth'; +import type { Page } from '@playwright/test'; + +const SETTINGS_URL = '/admin/boards/settings?tab=notification_definitions'; +const DEFINITIONS_API = /\/api\/admin\/notification-definitions(\?|$)/; + +/** 템플릿이 비었을 때 행에 표시되는 문구 */ +const EMPTY_TEMPLATE_TEXT = '이 채널에 대한 템플릿이 없습니다'; + +/** + * 알림 정의 목록 응답을 기다리며 해당 채널의 설정 탭으로 이동합니다. + * + * @param page Playwright 페이지 + * @param channel 조회할 알림 채널 + * @returns 요청 URL 과 응답 본문 + */ +async function loadChannel(page: Page, channel: string) { + const pending = page.waitForResponse( + (res) => DEFINITIONS_API.test(res.url()) && res.request().method() === 'GET', + { timeout: 30_000 }, + ); + + await page.goto(`${SETTINGS_URL}&channel=${channel}`); + + const response = await pending; + + return { requestUrl: response.url(), payload: await response.json() }; +} + +/** + * 기본 상태(커스터마이즈 없음) 축의 공통 단언을 수행합니다. + * + * @param page Playwright 페이지 + * @param channel 검증 대상 채널 + */ +async function assertChannelIsCarried(page: Page, channel: string) { + const { requestUrl, payload } = await loadChannel(page, channel); + + expect( + requestUrl, + '화면이 보는 채널을 서버에 넘기지 않으면 응답에 templates 키 자체가 없다', + ).toContain(`template_channel=${channel}`); + + const rows = payload?.data?.data ?? []; + + test.skip(rows.length === 0, '게시판 알림 정의 시드가 없어 판정할 수 없다'); + + // 서버가 요청한 채널만 실었는지 — 다른 채널이 섞이면 프루닝이 무의미하다. + for (const row of rows) { + expect(row, 'templates 키가 응답에 있어야 한다').toHaveProperty('templates'); + + for (const template of row.templates ?? []) { + expect(template.channel, '요청하지 않은 채널이 실렸다').toBe(channel); + } + } + + // 렌더까지 확인 — 응답에 실렸어도 화면이 다른 채널을 찾으면 빈 문구가 남는다. + await expect( + page.getByText(EMPTY_TEMPLATE_TEXT).first(), + '템플릿이 실렸는데도 빈 문구가 보이면 화면과 요청의 채널이 어긋난 것이다', + ).toBeHidden(); +} + +/** + * 지정 채널의 첫 정의를 편집해 커스터마이즈 상태로 만든 뒤, + * 「기본값 복원」이 채널을 가리지 않고 노출되는지 확인하고 원상 복구합니다. + * + * @param page Playwright 페이지 + * @param channel 커스터마이즈를 수행할 채널 + */ +async function assertResetButtonSpansChannels(page: Page, channel: string) { + // 편집→저장→재조회→복원까지 도는 다단계 플로우라 기본 30초를 넘는다. + test.setTimeout(120_000); + + const { payload } = await loadChannel(page, channel); + + test.skip((payload?.data?.data ?? []).length === 0, '게시판 알림 정의 시드가 없어 판정할 수 없다'); + + const resetButton = page.getByRole('button', { name: '기본값 복원' }); + const initialCount = await resetButton.count(); + + await page.getByRole('button', { name: '편집' }).first().click(); + + const dialog = page.getByRole('dialog'); + const subject = dialog.getByRole('textbox').first(); + + await subject.fill(`${await subject.inputValue()} [E2E]`); + await dialog.getByRole('button', { name: '저장' }).click(); + await expect(dialog).toBeHidden({ timeout: 15_000 }); + + // 반대 채널로 옮겨도 복원 버튼이 보여야 한다. 화면이 templates 배열을 순회해 판정하면 + // 반대 채널 templates 에는 커스터마이즈가 없어 버튼이 사라진다. + const otherChannel = channel === 'mail' ? 'database' : 'mail'; + + try { + await loadChannel(page, otherChannel); + + await expect( + resetButton.first(), + '채널을 가리지 않는 판정은 서버 집계(has_customized_templates)가 유일한 출처다', + ).toBeVisible({ timeout: 15_000 }); + + expect( + await resetButton.count(), + '커스터마이즈한 정의에만 복원 버튼이 붙어야 한다', + ).toBeGreaterThan(initialCount); + } finally { + // 시드 상태로 되돌린다 — 남겨두면 다음 실행의 기준선이 달라진다. + await loadChannel(page, otherChannel); + + if ((await resetButton.count()) > 0) { + await resetButton.first().click(); + await page.getByRole('button', { name: '복원', exact: true }).click(); + await expect(resetButton).toHaveCount(initialCount, { timeout: 15_000 }); + } + } +} + +test.describe('게시판 알림 정의 탭 — 채널 계약 (#518)', () => { + /** + * @scenario channel=mail,template_state=default + * @effects board_notification_list_requests_template_channel, board_notification_channel_reads_single_source + */ + test('mail 채널: 목록 요청이 그 채널을 나르고 행에 템플릿이 채워진다', async ({ + page, + boardManageToken, + }) => { + await authenticatePage(page, boardManageToken); + await assertChannelIsCarried(page, 'mail'); + }); + + /** + * @scenario channel=database,template_state=default + * @effects board_notification_list_requests_template_channel, board_notification_channel_reads_single_source + */ + test('database 채널: 목록 요청이 그 채널을 나르고 행에 템플릿이 채워진다', async ({ + page, + boardManageToken, + }) => { + await authenticatePage(page, boardManageToken); + await assertChannelIsCarried(page, 'database'); + }); + + test.describe('커스터마이즈 상태', () => { + // 두 축 모두 같은 시드 정의를 편집·복원하므로 병렬로 돌면 서로의 기준선을 무너뜨린다. + // 이 블록 안에서만 직렬 — 부모 describe 에 걸면 기본 상태 2건까지 한 건 실패로 중단된다. + test.describe.configure({ mode: 'serial' }); + + /** + * @scenario channel=mail,template_state=customized + * @effects board_notification_reset_button_uses_server_aggregate + */ + test('mail 채널에서 커스터마이즈하면 database 화면에서도 기본값 복원이 보인다', async ({ + page, + boardManageToken, + }) => { + await authenticatePage(page, boardManageToken); + await assertResetButtonSpansChannels(page, 'mail'); + }); + + /** + * @scenario channel=database,template_state=customized + * @effects board_notification_reset_button_uses_server_aggregate + */ + test('database 채널에서 커스터마이즈하면 mail 화면에서도 기본값 복원이 보인다', async ({ + page, + boardManageToken, + }) => { + await authenticatePage(page, boardManageToken); + await assertResetButtonSpansChannels(page, 'database'); + }); + }); +}); diff --git a/modules/_bundled/sirsoft-board/tests/scenarios/board-admin-list-role-payload-pruning.yaml b/modules/_bundled/sirsoft-board/tests/scenarios/board-admin-list-role-payload-pruning.yaml new file mode 100644 index 00000000..db331344 --- /dev/null +++ b/modules/_bundled/sirsoft-board/tests/scenarios/board-admin-list-role-payload-pruning.yaml @@ -0,0 +1,50 @@ +feature: 관리자 게시판 목록의 역할 소속 사용자 프루닝 (#518 / 공개 #76) + +description: | + 관리자 게시판 목록이 행마다 매니저/스텝 역할을 조회해 **그 역할에 속한 사용자 전원의 + uuid·이름·이메일**을 응답에 실었다. 목록 화면이 그리는 행 필드는 이름·슬러그·유형·활성 + 여부·분류·권한뿐이라 그 값은 쓰이지 않았고, 게시판 수만큼 추가 쿼리가 나가면서 개인정보인 + 이메일이 함께 노출됐다. + + 목록은 화면이 실제로 그리는 것만 싣고, 역할 편집에 필요한 값은 게시판 상세/설정 화면이 + 공급한다. 목록에서 뺀 값이 상세에 남아 있는지를 함께 고정한다 — 남아 있지 않다면 그것은 + 성능 개선이 아니라 기능 삭제다. + + 게시판 복사(`copyBoard`)는 Resource 를 거치지 않고 역할 조회 헬퍼를 직접 부른다. 목록에서 + 호출이 사라졌다고 그 헬퍼를 정리하면 복사가 깨지므로 회귀로 함께 고정한다. + +# axis 의미: +# 무엇이 실리는지를 가르는 축은 (응답 경로) × (역할 배정 상태) 다. +# 역할이 배정되지 않은 게시판은 "빈 배열" 과 "필드 자체 부재" 가 갈리는 지점이라 분리한다. +axes: + endpoint: [admin_list, detail] + role_assignment: [assigned, unassigned] + +effects: + - admin_list_omits_role_member_payload + - admin_list_never_exposes_member_email + - admin_list_keeps_display_fields + - admin_list_row_carries_created_at + - detail_still_provides_role_assignments + - role_query_count_does_not_scale_with_board_count + - board_copy_still_resolves_roles + +sub_flows: + - id: copy_board_regression + description: 목록이 역할 헬퍼를 더 이상 부르지 않아도 게시판 복사는 그대로 동작한다 + effects: + - board_copy_still_resolves_roles + +test_files: + - modules/_bundled/sirsoft-board/tests/Feature/Admin/AdminBoardListPayloadTest.php + - modules/_bundled/sirsoft-board/tests/Feature/Admin/BoardManagementTest.php + - modules/_bundled/sirsoft-board/tests/Feature/BoardCloneTest.php + - modules/_bundled/sirsoft-board/tests/Playwright/specs/admin/board-admin-list-role-payload.spec.ts + +notes: | + E2E 를 두는 이유: 변경 자체는 백엔드(Resource/Collection)지만, 목록 표현을 경량 경로로 + 갈아끼우면서 화면이 쓰던 필드를 함께 떨구면 열이 조용히 빈다. 응답 형태만 보는 서버 단언은 + 그것을 잡지 못한다 — 실제 렌더된 행에서 식별자 열이 살아 있는지까지 확인한다. + + 이메일 부재는 키 이름이 아니라 **응답 본문 전체**를 정규식으로 본다. 역할 페이로드가 다른 + 키 이름으로 되살아나도 걸리게 하기 위해서다. diff --git a/modules/_bundled/sirsoft-board/tests/scenarios/board-notification-definition-channel-contract.yaml b/modules/_bundled/sirsoft-board/tests/scenarios/board-notification-definition-channel-contract.yaml new file mode 100644 index 00000000..0e4ab19c --- /dev/null +++ b/modules/_bundled/sirsoft-board/tests/scenarios/board-notification-definition-channel-contract.yaml @@ -0,0 +1,45 @@ +feature: 게시판 알림 정의 탭의 채널 계약 (#518 / 공개 #76) + +description: | + 알림 정의 목록은 정의 하나당 (채널 × 로케일) 템플릿 본문을 전부 실었다. 화면은 한 번에 한 + 채널만 그리므로, 서버는 `template_channel` 로 지정된 채널의 템플릿만 싣도록 좁혔다. + + 그 좁힘은 **요청과 화면이 같은 채널을 볼 때만** 성립한다. 이 매니페스트가 고정하는 것은 + 두 종류의 어긋남이다. + + 1. 화면이 채널을 서버에 넘기지 않는 경우 — 응답에 `templates` 키 자체가 없어 전 행이 + 「이 채널에 대한 템플릿이 없습니다」가 된다. 제목·수신자·활성 토글·편집 모달 초기값이 + 통째로 빈다. `(def.templates ?? [])` 가드 뒤라 예외도 콘솔 경고도 없고 응답은 200, + 행 수도 정상이라 "템플릿을 아직 안 만들었나 보다" 로 읽힌다. + 2. 넘기는 채널과 화면이 찾는 채널이 다른 경우 — 서버는 A 채널을 실어 보내고 화면은 B 채널을 + 찾아 같은 증상이 난다. 채널 출처가 URL 하나여야 하는 이유다. + + 「기본값 복원」 노출은 채널을 가리지 않는 판정이다. templates 가 한 채널로 좁혀 실리므로 + 배열 순회로는 다른 채널의 커스터마이즈를 알 수 없다 — 서버 집계가 유일한 출처다. + +# axis 의미: +# 갈리는 입력은 (화면이 보는 채널) × (그 채널의 템플릿 커스터마이즈 상태) 다. +# 채널 전환 경로는 URL 만 바꾸므로 초기 진입과 같은 규칙이고, effects 로 검증한다. +axes: + channel: [mail, database] + template_state: [default, customized] + +effects: + - board_notification_channel_reads_single_source + - board_notification_list_requests_template_channel + - board_notification_reset_button_uses_server_aggregate + +test_files: + - modules/_bundled/sirsoft-board/resources/js/__tests__/layouts/admin-board-notification-channel-single-source.test.ts + - modules/_bundled/sirsoft-board/tests/Playwright/specs/admin/board-notification-definition-channel.spec.ts + +notes: | + 회귀 이력: 목록 프루닝 도입 시 코어 설정 화면만 `template_channel` 을 보내도록 고쳐지고 + 게시판 탭 데이터소스가 빠져, 7건 전 행이 빈 상태로 표시됐다 (2026-08-05 브라우저 실측). + + 그래서 단언은 소비와 전송을 **함께** 묶는다 — templates 를 읽는 표현식이 있는데 데이터소스가 + 채널을 넘기지 않으면 실패한다. 한쪽만 보면 "화면은 templates 를 안 쓴다" 는 잘못된 전제로 + 통과해 버린다. + + 활성 토글은 URL 을 `templates[...].id` 로 조립한다. 템플릿이 없으면 그 값이 `undefined` 가 + 되어 존재하지 않는 경로로 요청이 나가므로, 실제 템플릿 ID 로 나가는지까지 축에 포함한다. diff --git a/modules/_bundled/sirsoft-ecommerce/CHANGELOG.md b/modules/_bundled/sirsoft-ecommerce/CHANGELOG.md index 18dd5c24..256c59b5 100644 --- a/modules/_bundled/sirsoft-ecommerce/CHANGELOG.md +++ b/modules/_bundled/sirsoft-ecommerce/CHANGELOG.md @@ -25,6 +25,13 @@ ### Changed +- 관리자 상품 목록이 상품마다 옵션 전체를 함께 내려주지 않습니다. 목록에는 옵션 개수와 재고 합계만 실리고, 옵션 상세는 행을 펼칠 때 필요한 상품의 것만 한 번에 가져옵니다. 옵션을 많이 쓰는 쇼핑몰일수록 목록을 여는 속도와 전송량이 크게 개선됩니다(상품 100건에 상품당 옵션 20건 기준, 목록 응답 크기 약 94% 감소). 화면에서 보이는 내용과 일괄 변경 적용 범위는 이전과 동일하며, 옵션을 목록 응답에 함께 받으려면 주소 뒤에 `?with_options=1` 을 붙이면 됩니다. (#76 @jordy-bitree 님께서 제보해주셨습니다.) +- 관리자 주문 목록이 주문마다 주문상품 전체를 함께 내려주지 않습니다. 목록은 대표 상품 1건과 "외 N건" 표기만 사용하므로, 이제 그 1건과 개수만 읽습니다. 한 주문에 상품을 많이 담는 쇼핑몰일수록 목록을 여는 속도와 전송량이 개선됩니다. 화면에 보이는 내용(대표 상품·추가옵션 요약·"외 N건"·부분취소 표시)은 이전과 동일하며, 마이페이지 주문내역은 종전대로 주문상품 전체를 보여줍니다. (#76 @jordy-bitree 님께서 제보해주셨습니다.) +- 상품 옵션의 표시 순서를 상품 등록/수정 화면과 동일하게 정렬 순서 기준으로 통일했습니다. 이전에는 관리자 상품 목록의 펼친 옵션만 정해진 순서 없이 표시돼 화면마다 순서가 달라 보일 수 있었습니다. (#76 @jordy-bitree 님께서 제보해주셨습니다.) +- 상품 목록에서 대표 이미지를 고를 때 이미 읽어 둔 이미지 정보를 다시 조회하지 않도록 바꿨습니다. 관리자 상품 목록·쇼핑몰 상품 목록·위시리스트·관리자 리뷰 목록이 함께 빨라집니다. (#76 @jordy-bitree 님께서 제보해주셨습니다.) +- 관리자 주문 목록이 주문마다 배송 정보 전체를 함께 내려주지 않습니다. 목록은 대표 배송 1건(배송유형·배송방법·택배사·송장번호)만 표시하므로 그 1건만 읽으며, 택배사 이름도 함께 읽어 행마다 다시 조회하지 않습니다. 분할 배송이 많은 쇼핑몰일수록 목록이 가벼워집니다. (#76 @jordy-bitree 님께서 제보해주셨습니다.) +- 배송정책 목록이 정책마다 국가별 배송 설정을 통째로 내려주지 않습니다. 목록에는 화면이 실제로 표시하는 값(국가·배송방법·부과정책·배송비·무료배송 기준액·도서산간 사용 여부·사용 여부)만 실리고, 구간별 배송비 표·도서산간 지역 설정·배송비 계산 API 설정은 빠집니다. 정책 하나에 국가 설정이 수백 건까지 달릴 수 있어 목록을 여는 것만으로 그만큼의 상세 설정이 전송됐습니다. 배송정책 관리 목록과 상품 등록/수정 화면의 표시 내용은 종전과 동일하며, 전체 설정이 필요한 외부 연동은 주소 뒤에 `?with_country_settings=1` 을 붙이면 됩니다. (#76 @jordy-bitree 님께서 제보해주셨습니다.) +- 마이페이지 주문내역 조회가 기본으로 주문마다 주문상품 전체를 내려주지 않습니다. 기본 응답에는 대표 상품 1건과 전체 개수만 실리며, 주문상품을 전부 나열하는 마이페이지 화면은 전량을 함께 요청하므로 보이는 내용은 종전과 동일합니다. 주문내역 조회를 직접 사용하는 외부 연동에서 상품 전체가 필요하면 주소 뒤에 `?with_items=1` 을 붙이면 됩니다. (#76 @jordy-bitree 님께서 제보해주셨습니다.) - 실제로 사용되지 않던 설정 항목(상품·주문 목록의 페이지당 개수, 결제 자동취소 기한 중복 항목)을 정리했습니다. 미입금 자동취소 기한은 관리자 환경설정의 값 하나만 사용합니다. (#81 @jiwonpapa 님께서 제보해주셨습니다.) - 사이트맵을 만들 때 상품과 카테고리를 한 번에 모두 메모리에 올리지 않고 나눠서 읽도록 바꿨습니다. 상품이 많은 쇼핑몰에서 사이트맵 생성 중 메모리가 부족해 실패하던 문제가 줄어듭니다. - 상품 리뷰·마일리지 내역·쿠폰 발급 내역 목록도 같은 방식으로 뒤쪽 페이지 조회를 개선했습니다. 상품 리뷰는 작성일 정렬용 인덱스를 추가하고 기간 검색 조건을 인덱스가 활용되는 형태로 바꿔, 리뷰가 많은 쇼핑몰에서 목록과 기간 검색이 함께 빨라집니다. (#74 @jiwonpapa 님께서 제보해주셨습니다.) @@ -42,6 +49,8 @@ - 할인코드 목록에서 선택 개수·전체 건수·페이지당 표시 개수 문구에 숫자가 채워지지 않고 자리표시자가 그대로 보이던 문제를 수정했습니다. - 마이페이지와 비회원 주문 조회의 주문 상세에서 상품별 배송정책명과 개별 배송비가 아무 안내 없이 표시되지 않던 문제를 수정했습니다. 주문에 보관된 배송정책 기록의 저장 형태가 화면이 기대하는 형태와 달라 해당 줄이 통째로 빠져 있었습니다. 이미 저장된 주문도 업그레이드 시 함께 정리됩니다. - 비회원 주문 조회의 주문 상세에서 상품별 배송정책명과 개별 배송비가 표시되지 않던 문제를 추가로 수정했습니다. 회원 주문 상세와 같은 화면 구성을 쓰는데 비회원 응답에만 배송정책 정보가 빠져 있어, 오류 없이 그 줄만 비어 있었습니다. 표시에 필요한 정책명과 배송비만 내려주며 내부 계산 정보는 종전대로 제공하지 않습니다. +- 쇼핑몰 알림 설정 화면에서 모든 알림의 제목·수신자·활성 여부가 비어 보이고 「이 채널에 대한 템플릿이 없습니다」로만 표시되던 문제를 수정했습니다. 편집 창을 열어도 내용이 채워지지 않았습니다. 채널을 전환하거나 새로고침해도 마찬가지였습니다. (#76 @jordy-bitree 님께서 제보해주셨습니다.) +- 관리자 상품 목록의 새로고침·상품등록·엑셀 내려받기 아이콘이 의도한 크기보다 작게 표시되던 문제와, 일괄적용 버튼이 다크 모드에서 밝은 배경 그대로 보이던 문제를 수정했습니다. - 미입금 자동취소 기한을 비운 채 저장하면 "저장되었습니다"라고 안내되면서도 값은 사라지던 문제를 수정했습니다. 숫자 항목에 숫자가 아닌 값을 입력하면 입력칸이 비워지는데, 그대로 저장하면 설정이 없는 상태로 남아 화면에 보이는 값과 실제 동작이 어긋났습니다. 이제 비워 둔 채로는 저장되지 않고 값을 입력하라고 안내합니다. - 미입금 자동취소 기한 입력 안내가 실제 입력 하한과 다른 "0일 이상"으로 표시되던 문구를 "1일 이상"으로 바로잡았습니다. - 쿠폰 수정 화면에서 유효기간을 "발급일로부터"로 두고 일수를 비운 채 저장할 수 있던 문제를 수정했습니다. 그렇게 저장된 쿠폰은 발급되는 즉시 만료 상태가 되어, 받은 회원이 사용할 수 없으면서도 아무 오류가 나지 않았습니다. 이제 저장 단계에서 일수를 입력하도록 안내하고, 이미 그렇게 저장돼 있던 쿠폰은 발급 시 유효기간이 설정되지 않았다고 알려 줍니다. diff --git a/modules/_bundled/sirsoft-ecommerce/components.json b/modules/_bundled/sirsoft-ecommerce/components.json index 40b4cba8..8988446a 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.3", + "version": "1.0.5", "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 00640ee4..82e47720 100644 --- a/modules/_bundled/sirsoft-ecommerce/dist/js/module.iife.js +++ b/modules/_bundled/sirsoft-ecommerce/dist/js/module.iife.js @@ -1 +1,2 @@ -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}({}); +var SirsoftEcommerce=function(Pe){"use strict";const St={KRW:0,JPY:0},vt={KRW:"₩",USD:"$",JPY:"¥",CNY:"元",EUR:"€",GBP:"£"},Lt={KRW:1e3,JPY:100};function xt(r){if(!r)return 1;const c=r.base_unit??Lt[r.code]??1;return Math.max(1,c)}function Pt(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 ye(r,c){const e=typeof c=="string"?{code:c}:c,t=e.code,o=e.symbol&&e.symbol.length>0?e.symbol:vt[t]??"",a=e.decimal_places??St[t]??2,n=r.toLocaleString(void 0,{minimumFractionDigits:a,maximumFractionDigits:a});return o?t==="KRW"||o==="₩"||o==="원"?n+"원":o+n:n+" "+t}function se(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=xt(a),s={};for(const i of t){const l=i.code;if(i.is_default??!1)s[l]={price:o,formatted:ye(o,i),is_default:!0,editable:!0};else if(i.exchange_rate&&i.exchange_rate>0){const d=o/n*i.exchange_rate,m=Pt(d,i.rounding_unit||"0.01",i.rounding_method||"round");s[l]={price:m,formatted:ye(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 At(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 f=[...u._local?.productFieldErrors||[]];const g=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 I=n;const N=parseFloat(n)||0;if(o==="selling_price"){const O=parseFloat(C.list_price)||0;O>0&&N>O?(f.some(Q=>Q.productId===String(t)&&Q.field===o)||f.push({productId:String(t),field:"selling_price",message:"판매가는 정가를 초과할 수 없습니다."}),I=O):f=f.filter($=>!($.productId===String(t)&&$.field===o))}else f=f.filter(O=>!(O.productId===String(t)&&O.field===o));const L={...C,[o]:I,_modified:!0};return o==="selling_price"&&d&&Array.isArray(d)&&(L.multi_currency_selling_price=se({basePrice:I,currencies:d}),z.log(`[updateProductField] Recalculated multi_currency_selling_price for product ${t}`)),L}return C});s.dataSource.set(a,{...i,data:{...i.data,data:g}});const w=s.state.getLocal()||{},y=new Set(w.modifiedProductIds||[]);y.add(t);const _={...w.modifiedProductFields||{}},p=String(t),v=new Set(_[p]||[]);v.add(o),_[p]=Array.from(v),s.state.setLocal({modifiedProductIds:Array.from(y),modifiedProductFields:_,productFieldErrors:f}),z.log(`[updateProductField] Updated product ${t}.${o} =`,n)}const B=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 kt(r,c){const e=r.params||{},{productId:t,optionId:o,field:a,value:n,dataSourceId:s="products"}=e;if(!t||!o||!a){B.warn("[updateOptionField] Missing required params:",{productId:t,optionId:o,field:a});return}const i=window.G7Core;if(!i?.dataSource?.get||!i?.dataSource?.set){B.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){B.warn("[updateOptionField] Products data is empty or invalid");return}const d=i.state.get()||{},m=d.modules?.["sirsoft-ecommerce"]?.language_currency?.currencies;let g=[...d._local?.optionFieldErrors||[]],w=!1,y=!1;const _=u.map(N=>{if(String(N.id)===String(t)){const L=N.options||[],O=L.findIndex(D=>String(D.id)===String(o));if(O===-1)return B.warn("[updateOptionField] Option not found:",o),N;y=!0;const $=L[O],Q=$[a];if(typeof n=="object"&&n!==null?JSON.stringify(Q)===JSON.stringify(n):String(Q)===String(n))return B.log(`[updateOptionField] No change for option ${o}.${a}, skipping`),N;let J=n;const xe=parseFloat(n)||0;if(a==="selling_price"){const D=parseFloat($.list_price)||0;D>0&&xe>D?(g.some(we=>we.productId===String(t)&&we.optionId===String(o)&&we.field===a)||g.push({productId:String(t),optionId:String(o),field:"selling_price",message:"판매가는 정가를 초과할 수 없습니다."}),J=D):g=g.filter(H=>!(H.productId===String(t)&&H.optionId===String(o)&&H.field===a))}else g=g.filter(D=>!(D.productId===String(t)&&D.optionId===String(o)&&D.field===a));const re=[...L],ne={...re[O],[a]:J,_modified:!0};let ae=null;if(a==="selling_price"){const D=parseFloat(J)||0;if($.is_default===!0)ae=D,ne.price_adjustment=0;else{const H=parseFloat(N.selling_price)||0;ne.price_adjustment=D-H}ne.price_adjustment_formatted=(ne.price_adjustment>=0?"+":"")+ne.price_adjustment.toLocaleString()+"원"}if(a==="selling_price"&&m&&Array.isArray(m)&&(ne.multi_currency_selling_price=se({basePrice:J,currencies:m}),B.log(`[updateOptionField] Recalculated multi_currency_selling_price for option ${o}`)),re[O]=ne,ae!==null)for(let D=0;DH.is_active===!1?D:D+(parseInt(H.stock_quantity,10)||0),0),Be=Ke,B.log(`[updateOptionField] Updated product stock to option sum: ${Be}`));const Qe={...N,options:re,stock_quantity:Be,option_stock_sum:Ke,_modified:!0};return ae!==null&&(Qe.selling_price=ae,m&&Array.isArray(m)&&(Qe.multi_currency_selling_price=se({basePrice:ae,currencies:m})),w=!0,B.log(`[updateOptionField] Reverse-synced product ${t} selling_price to ${ae} from default option`)),Qe}return N});if(!y)return;i.dataSource.set(s,{...l,data:{...l.data,data:_}});const p=i.state.getLocal()||{},v=new Set(p.modifiedOptionIds||[]),C=`${t}-${o}`;v.add(C);const A={...p.modifiedOptionFields||{}},x=new Set(A[C]||[]);x.add(a),A[C]=Array.from(x);const I={modifiedOptionIds:Array.from(v),modifiedOptionFields:A,optionFieldErrors:g};if(w){const N=new Set(p.modifiedProductIds||[]);N.add(t);const L={...p.modifiedProductFields||{}},O=String(t),$=new Set(L[O]||[]);$.add("selling_price"),L[O]=Array.from($),I.modifiedProductIds=Array.from(N),I.modifiedProductFields=L}i.state.setLocal(I),B.log(`[updateOptionField] Updated option ${t}/${o}.${a} =`,n)}const Ot="g7_preferred_currency",Gt="g7_locale",ze=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 Et(r){const c=window.G7Core;c?.state?.set?c.state.set(r):ze.warn("G7Core.state.set not available")}function Ft(){try{return localStorage.getItem(Gt)}catch{return null}}function Dt(){try{const e=(window.G7Core?.state?.get?.()||{})?.modules?.["sirsoft-ecommerce"]?.language_currency;return Array.isArray(e?.currencies)?e.currencies:[]}catch{return[]}}function $t(r){if(!r)return null;const c=r.toLowerCase().split("-")[0].split("_")[0];if(!c)return null;const t=Dt().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 Nt(){try{const r=window.G7Config;return r?.user?r.user:(window.G7Core?.state?.get?.()||{}).currentUser||null}catch{return null}}function Tt(r,c){const e=r?.params?.defaultCurrency||"KRW",t=l=>typeof l=="string"&&/^[A-Z]{3}$/.test(l),o=Nt(),a=r?.params?.accountCurrency??o?.ecommerce_preferred_currency;let n=null;try{n=localStorage.getItem(Ot)}catch{}const s=a?null:$t(Ft()),i=t(a)?a:t(n)?n:t(s)?s:t(e)?e:"KRW";Et({preferredCurrency:i}),ze.log("Preferred currency initialized:",i)}const Rt="g7_preferred_shipping_country",We=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 Mt(r){const c=window.G7Core;c?.state?.set?c.state.set(r):We.warn("G7Core.state.set not available")}function Ht(){try{const r=window.G7Config;return r?.user?r.user:(window.G7Core?.state?.get?.()||{}).currentUser||null}catch{return null}}function qt(r,c){const e=r?.params?.defaultCountry||"KR",t=i=>typeof i=="string"&&/^[A-Z]{2}$/.test(i),o=Ht(),a=r?.params?.accountCountry??o?.ecommerce_preferred_shipping_country;let n=null;try{n=localStorage.getItem(Rt)}catch{}const s=t(a)?a:t(n)?n:t(e)?e:"KR";Mt({preferredShippingCountry:s}),We.log("Preferred shipping country initialized:",s)}function X(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 Ut(r){const c=new Date,e=X(c);let t;switch(r){case"today":t=e;break;case"3days":{const o=new Date(c);o.setDate(o.getDate()-2),t=X(o);break}case"week":{const o=new Date(c);o.setDate(o.getDate()-6),t=X(o);break}case"month":{const o=new Date(c);o.setMonth(o.getMonth()-1),t=X(o);break}case"3months":{const o=new Date(c);o.setMonth(o.getMonth()-3),t=X(o);break}case"6months":{const o=new Date(c);o.setMonth(o.getMonth()-6),t=X(o);break}case"1year":{const o=new Date(c);o.setFullYear(o.getFullYear()-1),t=X(o);break}case"all":default:return t="",{startDate:"",endDate:""}}return{startDate:t,endDate:e}}function Vt(r){const{preset:c}=r.params||{preset:"today"},{startDate:e,endDate:t}=Ut(c);return{startDate:e,endDate:t,preset:c}}const ie=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 jt(r,c){const e=r.params||{},{productId:t,optionId:o,dataSourceId:a="products"}=e;if(!t||!o){ie.warn("[setDefaultOption] Missing required params:",{productId:t,optionId:o});return}const n=window.G7Core;if(!n?.dataSource?.get||!n?.dataSource?.set){ie.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){ie.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(x=>String(x.id)===String(o));if(!_)return ie.warn("[setDefaultOption] Option not found:",o),w;const p=y.map(x=>{const I=String(x.id)===String(o);return{...x,is_default:I,_modified:I?!0:x._modified}}),v=_.list_price,C=_.selling_price;let A=w.multi_currency_selling_price;return u&&Array.isArray(u)&&(A=se({basePrice:C,currencies:u})),ie.log(`[setDefaultOption] Set option ${o} as default for product ${t}. Syncing prices: list_price=${v}, selling_price=${C}`),{...w,options:p,list_price:v,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()||{},f=new Set(m.modifiedProductIds||[]);f.add(String(t));const g=new Set(m.modifiedOptionIds||[]);g.add(`${t}-${o}`),n.state.setLocal({modifiedProductIds:Array.from(f),modifiedOptionIds:Array.from(g)}),ie.log(`[setDefaultOption] Option ${o} set as default for product ${t}`)}const Ae=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}`,ke=r=>r.split("-")[0];function Bt(r,c){const e=r.params||{},{productId:t,optionId:o,checked:a}=e,n=window.G7Core;if(!n?.state){Ae.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 Ye(r,c){const e=r.params||{},{productId:t,options:o,checked:a}=e,n=window.G7Core;if(!n?.state){Ae.warn("[toggleProductOptions] G7Core.state를 사용할 수 없습니다.");return}const s=n.state.getLocal(),i=s.selectedOptionIds||[],l=s.selectedItems||[],u=(o||[]).map(g=>de(t,g.id));let d,m=[...l];const f=Number(t);a?(d=[...i.filter(w=>ke(w)!==String(t)),...u],m.includes(f)||m.push(f)):(d=i.filter(g=>ke(g)!==String(t)),m=m.filter(g=>g!==f)),n.state.setLocal({selectedOptionIds:d,selectedItems:m})}function Kt(r,c){return Ye(r)}function Qt(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 zt(r,c){const e=r.params||{},{selectedIds:t}=e,o=window.G7Core;if(!o?.state){Ae.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(f=>!n.includes(f)),d=n.filter(f=>!t.includes(f));let m=[...s];for(const f of u){const g=l.find(w=>Number(w.id)===f);if(g?.options)for(const w of g.options){const y=de(f,w.id);m.includes(y)||m.push(y)}}for(const f of d)m=m.filter(g=>ke(g)!==String(f));o.state.setLocal({selectedItems:t,selectedOptionIds:m})}const he=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 Wt(r,c){const e=window.G7Core;if(!e?.state||!e?.api){he.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}),he.log(`[generateCopyProductCode] Generated (parent): ${o}`);else{const s=e.state.getLocal()||{};e.state.setLocal({ui:{...s.ui,copyOptions:{...s.ui?.copyOptions||{},newProductCode:o}}}),he.log(`[generateCopyProductCode] Generated (local): ${o}`)}}catch(t){he.error("[generateCopyProductCode] Failed:",t),e.toast?.error?.(e.t?.("sirsoft-ecommerce.admin.product.messages.code_generate_error")??"Failed to generate product code.")}}const Je=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 Yt(r,c){const e=window.G7Core;if(!e?.state){Je.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()}`;Je.log("[copyProduct] Navigating to:",u),e.navigate?e.navigate(u):c.navigate?c.navigate(u):window.location.href=u}const Ce=window.G7Core?.createLogger?.("Ecom:ExpandedOptions")??{log:(...r)=>console.log("[Ecom:ExpandedOptions]",...r),warn:(...r)=>console.warn("[Ecom:ExpandedOptions]",...r),error:(...r)=>console.error("[Ecom:ExpandedOptions]",...r)},Jt="/api/modules/sirsoft-ecommerce/admin/products/options",Oe=100,Ge=new Set;function Xt(r,c){const t=r?.dataSource?.get?.(c)?.data?.data;return Array.isArray(t)?t:[]}function Xe(r,c,e){const t=r?.dataSource?.get?.(c),o=t?.data?.data;if(!t||!Array.isArray(o)){Ce.warn("[loadExpandedOptions] DataSource not available for merge:",c);return}const a=o.map(n=>{const s=String(n?.id);return Object.prototype.hasOwnProperty.call(e,s)?{...n,options:e[s]}:n});r.dataSource.set(c,{...t,data:{...t.data,data:a}})}function Zt(r,c){const e=r?.state?.getLocal?.()||{},t=e.selectedItems||[];if(t.length===0)return;const o=new Set(t.map(s=>String(s))),a=new Set(e.selectedOptionIds||[]);let n=!1;Object.entries(c).forEach(([s,i])=>{o.has(s)&&i.forEach(l=>{const u=`${s}-${l?.id}`;a.has(u)||(a.add(u),n=!0)})}),n&&r.state.setLocal({selectedOptionIds:Array.from(a)})}function Ee(r,c){const e=r?.state?.getLocal?.()||{},t=c(e.optionsLoadingIds||[],e.optionsErrorIds||[]);r.state.setLocal({optionsLoadingIds:t.loading,optionsErrorIds:t.errored})}function Fe(r,c){const e=r.params||{},t=e.dataSourceId||"products",o=e.force===!0,a=window.G7Core;if(!a?.dataSource?.get||!a?.dataSource?.set||!a?.state?.setLocal){Ce.warn("[loadExpandedOptions] G7Core dataSource/state API is not available");return}Array.isArray(e.expandedRows)&&a.state.setLocal({expandedRows:e.expandedRows});const n=Array.isArray(e.productIds)?e.productIds:e.productId!==void 0&&e.productId!==null?[e.productId]:Array.isArray(e.expandedRows)?e.expandedRows:a.state.getLocal?.()?.expandedRows||[];if(!Array.isArray(n)||n.length===0)return;const s=Xt(a,t),i=new Map(s.map(d=>[String(d?.id),d])),l=[],u={};n.forEach(d=>{const m=String(d),f=i.get(m);if(f&&!(!o&&Array.isArray(f.options))){if(Number(f.options_total_count)===0){u[m]=[];return}Ge.has(m)||l.push(m)}}),Object.keys(u).length>0&&Xe(a,t,u),l.length!==0&&(l.length>Oe&&(Ce.warn(`[loadExpandedOptions] Requested ${l.length} products, truncating to ${Oe}`),l.length=Oe),l.forEach(d=>Ge.add(d)),Ee(a,(d,m)=>({loading:Array.from(new Set([...d,...l])),errored:m.filter(f=>!l.includes(f))})),a.api.get(Jt,{params:{product_ids:l.map(d=>Number(d))}}).then(d=>{const f=(d?.data||{}).options||{},g={};l.forEach(w=>{const y=f[w];g[w]=Array.isArray(y)?y:[]}),Xe(a,t,g),Zt(a,g),Ee(a,(w,y)=>({loading:w.filter(_=>!l.includes(_)),errored:y.filter(_=>!l.includes(_))}))}).catch(d=>{Ce.error("[loadExpandedOptions] Failed to load options:",d),Ee(a,(f,g)=>({loading:f.filter(w=>!l.includes(w)),errored:Array.from(new Set([...g,...l]))}));const m=d?.response?.data?.message||a.t?.("sirsoft-ecommerce.admin.product.table.options.load_failed")||"Failed to load product options.";a.toast?.error?.(m)}).finally(()=>{l.forEach(d=>Ge.delete(d))}))}function eo(r,c={}){const e=r?.state?.getLocal?.()?.expandedRows||[];!Array.isArray(e)||e.length===0||Fe({params:{productIds:e,force:c.force===!0,dataSourceId:c.dataSourceId||"products"}})}function to(r,c){const e=r.params||{};Fe({params:{productId:e.productId,productIds:e.productIds,force:!0,dataSourceId:e.dataSourceId}})}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 Ze(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=Ze(o.children,c,e,[...t,a]);if(n)return n}}return null}function oo(r,c,e){return r.map(t=>{if(t.breadcrumb)return{id:t.id,breadcrumb:t.breadcrumb};const o=Ze(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 ro(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,f)=>fdp0?t+2:f;a.mergeState({selectedCategories:g,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,f)=>f0?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 ao(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,f=(e.state.getDataSources?.()||{}).categories?.data?.data||[];let g=t.selectedCategoryInfos||[];const w=t.form?.categories||[];g.length===0&&w.length>0&&(P.log("[addCategoryToSelection] Initializing from form.categories:",w.length,"items"),g=oo(w,f,o));const y=[...g,{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 so(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 io(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 f=m.name?.[n]||m.name?.ko||"";if(m.id===u)return[...d,f];if(m.children&&m.children.length>0){const g=s(m.children,u,[...d,f]);if(g)return g}}return null},i=s(o,t);return i?i.join(" > "):""}function co(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,f=[])=>{for(const g of d){const w=g.name?.[o]||g.name?.ko||"";if(g.id===m)return[...f,w];if(g.children&&g.children.length>0){const y=l(g.children,m,[...f,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 f=s.find(g=>g.id===d);if(f){const g=f.name?.[o]||f.name?.ko||f.name_localized||"";u.push({id:d,breadcrumb:g})}}}P.log("[initCategoryInfosFromProduct] Generated selectedCategoryInfos:",u),e.state.setLocal({selectedCategoryInfos:u}),P.log("[initCategoryInfosFromProduct] DONE")}function lo(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(f=>String(f.id)===s);m&&m.depth===3&&(e.category4=s)}}}}return P.log("[validateCategoryPath] Result:",e),e}function uo(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 mo(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 Z=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)},po={KRW:1e3,JPY:100};function fo(r,c){const t=r.find(o=>o.code===c)?.base_unit??po[c]??1;return Math.max(1,t)}function go(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 _o(r,c){const e=r.params||{},t=e.field,o=e.currency,a=e.value,n=e.autoFill;if(!t||!o||a===void 0){Z.warn("[updatePrice] Missing required params: field, currency, or value");return}const s=window.G7Core;if(!s?.state){Z.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",f={...i.form?.multi_currency_prices||{}};if(f[o]={...f[o]||{},[t]:a},n&&o===d){const y=fo(u,d);u.forEach(_=>{if(_.code!==d){const p=_.exchange_rate??1,v=go(a,p,m,y);f[_.code]={...f[_.code]||{},[t]:v}}})}const g={list:"list_price",selling:"selling_price"},w=o===d?a:i.form?.[g[t]];s.state.setLocal({form:{...i.form,multi_currency_prices:f,[g[t]]:w},hasChanges:!0}),Z.log(`[updatePrice] Updated ${t} price for ${o}: ${a}`)}function wo(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 Z.log(`[calculateTotalOptionStock] Total stock: ${o}`),o}function yo(r,c){const e=window.G7Core;if(!e?.state){Z.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),Z.warn(`[validatePriceRelation] Selling price (${a}) exceeds list price (${o})`)):Z.log("[validatePriceRelation] Price validation passed")}const T=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 ho(){return window.G7Core?.config?.("app.supported_locales")??["ko","en"]}function et(){return ho().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 Co={KRW:1e3,JPY:100};function De(){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??Co[e.code]??1;return Math.max(1,t)}function bo(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 $e(r,c,e){const t=c.exchange_rate||0;if(t<=0)return{price:0,formatted:ye(0,c)};const o=e??De(),a=r/o*t,n=bo(a,c.rounding_unit||"0.01",c.rounding_method||"round"),s=c.decimal_places??2,i=parseFloat(n.toFixed(s));return{price:i,formatted:ye(i,c)}}function Ne(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=De(),i={};e.forEach(l=>{i[l.code]=$e(a,l,s)}),n.multi_currency_selling_price=i}return n})}function Te(){return((window.G7Core?.state?.get?.()||{}).modules?.["sirsoft-ecommerce"]?.language_currency?.currencies||[]).filter(c=>!c.is_default&&c.exchange_rate)}function Io(r,c){const e=r.params||{},t=window.G7Core;if(!t?.state){T.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){T.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=Te();l=Ne(l,d,m)}t.state.setLocal({form:{...o.form,options:l,selling_price:d},hasChanges:!0}),T.log(`[setDefaultOption] Set default option to ${i}, synced product selling_price to ${d}`)}function So(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){T.warn("[updateFormOptionField] Missing required params:",{index:t,field:o});return}const n=window.G7Core;if(!n?.state){T.warn("[updateFormOptionField] G7Core.state API is not available");return}const s=n.state.getLocal()||{},i=[...s.form?.options??[]];if(t<0||t>=i.length){T.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=Te();if(i[t].is_default===!0){i[t].price_adjustment=0;const g=Ne(i,d,m);n.state.setLocal({form:{...s.form,options:g,selling_price:d},hasChanges:!0}),T.log(`[updateFormOptionField] Reverse-synced product selling_price to ${d} from default option`);return}const f=parseFloat(String(s.form?.selling_price))||0;if(i[t].price_adjustment=d-f,m.length>0){const g=De(),w={};m.forEach(y=>{w[y.code]=$e(d,y,g)}),i[t].multi_currency_selling_price=w,T.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}),T.log(`[updateFormOptionField] Updated product stock_quantity to ${d} (sum of active options)`);return}n.state.setLocal({form:{...s.form,options:i},hasChanges:!0}),T.log(`[updateFormOptionField] Updated options[${t}].${o} =`,u)}function vo(r,c){const e=window.G7Core;if(!e?.state){T.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(f=>({key:f.name,value:et()})),u={id:null,option_code:i,option_name:et(),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}),T.log(`[addOptionRow] Added new option row: ${i}, product stock_quantity = ${m}`)}function Lo(r,c){const e=window.G7Core;if(!e?.state){T.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){T.log("[recalculateOptionPriceAdjustments] No options to recalculate");return}const s=Te(),i=Ne(n,a,s);e.state.setLocal({form:{...o.form,options:i}}),T.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 Re(){return window.G7Core?.config?.("app.supported_locales")??["ko","en"]}function ce(){return window.G7Core?.config?.("app.locale")??Re()[0]??"ko"}function q(){return Re().reduce((c,e)=>({...c,[e]:""}),{})}function tt(r){return{id:null,name:q(),price_adjustment:0,is_default:r===0,is_active:!0,allow_custom_text:!1,sort_order:r}}function xo(r,c){return typeof r=="string"?r:r[c]??r.ko??Object.values(r)[0]??""}function ot(r){return r&&typeof r=="object"&&!Array.isArray(r)}function Po(r){return Array.isArray(r)?r:r&&typeof r=="object"?Object.values(r):[]}function Ao(r){return r.reduce((c,e)=>{const t=[];return c.forEach(o=>{e.forEach(a=>{t.push([...o,a])})}),t},[[]])}function ko(r){const c=Re(),e={};for(const t of c){const o=r.map(a=>xo(a,t));e[t]=o.join("/")}return e}function Oo(r,c){return c.map((e,t)=>({key:typeof e.name=="string"?{...q(),[ce()]:e.name}:e.name,value:ot(r[t])?r[t]:{...q(),[ce()]:r[t]}}))}function Go(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 Eo(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 Fo(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=ce(),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 Do(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=ce(),s=p=>p.name?typeof p.name=="string"?p.name:p.name[n]??"":"",i=a.map(p=>({...p,values:Po(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 f=r.params?.isCreate===!0||r.params?.isCreate==="true"?1:0,g=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((L,O)=>(h.log(`[generateOptions] Processing input[${O}]:`,{name:L.name,values:L.values,valuesType:typeof L.values,valuesIsArray:Array.isArray(L.values)}),{name:typeof L.name=="string"?{...q(),[n]:L.name}:L.name,values:L.values.map($=>ot($)?$:{...q(),[n]:$})})),C=Ao(l.map(L=>L.values)).map((L,O)=>{const $=Oo(L,l),Q=ko(L),Ct=`OPT-${String(O+1).padStart(3,"0")}`;let J={};return y&&typeof y=="object"&&Object.keys(y).length>0?J=JSON.parse(JSON.stringify(y)):w>0&&_.length>0&&_.forEach(xe=>{J[xe.code]=$e(w,xe)}),{id:null,option_code:Ct,option_values:$,option_name:Q,list_price:g,selling_price:w,price_adjustment:0,stock_quantity:f,safe_stock_quantity:0,is_default:O===0,is_active:!0,sku:"",multi_currency_selling_price:J}}),A=t.form?.options??[],x=L=>L.option_name?typeof L.option_name=="string"?L.option_name:L.option_name[n]??L.option_name.ko??"":"",I=C.map(L=>{const O=x(L),$=A.find(Q=>x(Q)===O);return $?{...L,...$,id:$.id}:L}),N=be(I);e.state.setLocal({form:{...t.form,option_groups:p,options:I,has_options:I.length>0,stock_quantity:N},hasChanges:!0}),e.toast?.success?.(e.t?.("sirsoft-ecommerce.admin.product.options.messages.generated",{count:I.length})??`${I.length} options have been generated.`),h.log(`[generateOptions] Generated ${I.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 $o(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 No(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 To(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 Ro(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 Mo(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 Ho(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 qo(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:[tt(0)]}]},hasChanges:!0}),h.log(`[addAdditionalOption] Added additional option. Total: ${o.length+1}`)}function Uo(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=ce(),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 Vo(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 jo(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 Bo(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 Me(r){if(!r)return[];const c=r.values;return Array.isArray(c)?c:c&&typeof c=="object"?Object.values(c):[]}function Ko(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=[...Me(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(tt(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 Qo(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=[...Me(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=ce(),m=u[o].name,f=m&&typeof m=="object"&&!Array.isArray(m)?m:q();u[o]={...u[o],name:{...f,[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,f)=>{m.is_default=f===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 zo(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=[...Me(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)},Wo=["image/jpeg","image/png","image/gif","image/webp"],Yo=10*1024*1024,Jo=20;async function Xo(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=Jo-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(!Wo.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>Yo){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:v=>{const C=o.state.getLocal()||{};o.state.setLocal({ui:{...C.ui,uploadProgress:{...C.ui?.uploadProgress||{},[l.name]:v}}})}}),f=o.state.getLocal()||{},g=f.form?.images??[],w={...m.data,is_thumbnail:g.length===0,sort_order:g.length};o.state.setLocal({form:{...f.form,images:[...g,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]:f,...g}=m.ui?.uploadProgress||{};o.state.setLocal({ui:{...m.ui,uploadProgress:g}}),j.error(`[uploadImages] Failed to upload ${l.name}:`,d)}}}function Zo(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 er(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 rt=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 tr(r,c){const e=r.params||{},t=e.locale,o=e.value;if(!t){rt.warn("[updateDescription] Missing locale param");return}const a=window.G7Core;if(!a?.state){rt.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 or(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){nt(r);return}o.state.setGlobal({pendingNoticeTemplateId:t}),o.modal?.open?.("notice_template_confirm_modal"),k.log(`[confirmSelectNoticeTemplate] Showing confirm modal for template: ${t}`)}function nt(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 f=JSON.stringify(d.name),g=l.find(w=>JSON.stringify(w.name)===f);return{key:`field_${m}`,name:d.name,content:g?.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 rr(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 nr(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 ar(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 sr(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 ir(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 cr(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 lr(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 dr(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 ur(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 mr(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 pr(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 fr(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 S=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 gr(r,c){const t=(r.params||{}).labelId;if(!t){S.warn("[toggleLabel] Missing labelId param");return}const o=window.G7Core;if(!o?.state){S.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}),S.log(`[toggleLabel] Toggled label ${t}. Current labels: ${s.length}`)}async function _r(r,c){const e=window.G7Core;if(!e?.state||!e?.api){S.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}}),S.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."),S.error("[generateProductCode] Failed:",t)}}function wr(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 yr(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 hr(r,c){const e=r.params||{},t=e.platform,o=e.field,a=e.value;if(!t||!o){S.warn("[updateShoppingIntegration] Missing required params");return}const n=window.G7Core;if(!n?.state){S.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}),S.log(`[updateShoppingIntegration] Updated ${t}.${o} to ${a}`)}function Cr(r,c){const t=(r.params||{}).type;if(!t){S.warn("[updateShippingType] Missing type param");return}const o=window.G7Core;if(!o?.state){S.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}),S.log(`[updateShippingType] Updated shipping type to ${t}`)}function br(r,c){const e=r.params||{},t=e.field,o=e.value;if(!t){S.warn("[updateIdentificationCode] Missing field param");return}const a=window.G7Core;if(!a?.state){S.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 Ir(r,c){const e=r.params||{},t=e.labelId,o=e.labelName;if(!t){S.warn("[openLabelPeriodModal] Missing labelId param");return}const a=window.G7Core;if(!a?.state){S.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 Sr(r,c){const e=r.params||{},t=e.labelId,o=e.startDate,a=e.endDate;if(!t){S.warn("[saveLabelPeriod] Missing labelId param");return}const n=window.G7Core;if(!n?.state){S.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."),S.log(`[saveLabelPeriod] Saved period for label ${t}`)}function vr(r,c){const t=(r.params||{}).labelId;if(!t){S.warn("[removeLabelPeriod] Missing labelId param");return}const o=window.G7Core;if(!o?.state){S.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."),S.log(`[removeLabelPeriod] Removed period for label ${t}`)}function Lr(r,c){const t=(r.params||{}).sort;if(!t){S.warn("[updateActivityLogSort] Missing sort param");return}const o=window.G7Core;if(!o?.state){S.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}}),S.log(`[updateActivityLogSort] Updated sort to ${t}`)}function xr(r,c){const t=(r.params||{}).perPage;if(!t){S.warn("[updateActivityLogPerPage] Missing perPage param");return}const o=window.G7Core;if(!o?.state){S.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}}),S.log(`[updateActivityLogPerPage] Updated perPage to ${t}`)}function Pr(r,c){const e=window.G7Core;if(!e?.state){S.warn("[setDefaultShippingPolicy] G7Core.state API is not available");return}const t=e.state.getLocal()||{};if(t.form?.shipping_policy_id){S.log("[setDefaultShippingPolicy] Shipping policy already set, skipping");return}if(t.ui?.useDefaultShippingPolicy===!1){S.log("[setDefaultShippingPolicy] useDefaultShippingPolicy is OFF, skipping");return}const o=e.dataSource?.get?.("shipping_policies"),a=o?.data?.data??o?.data??[];if(a.length===0){S.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}}),S.log(`[setDefaultShippingPolicy] Set default shipping policy: ${n.id} (${n.name})`)):S.log("[setDefaultShippingPolicy] No default shipping policy found")}function Ar(r,c){const e=window.G7Core;if(!e?.state){S.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?S.log(`[toggleDefaultShippingPolicy] ON - Selected default policy: ${i.id}`):S.log("[toggleDefaultShippingPolicy] ON - No default policy found")}else e.state.setLocal({ui:a}),S.log("[toggleDefaultShippingPolicy] OFF - Keeping current shipping policy")}function kr(r,c){const e=window.G7Core;if(!e?.state){S.warn("[setLabelDatePreset] G7Core.state API is not available");return}const t=r.params?.preset;if(!t){S.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:S.warn(`[setLabelDatePreset] Unknown preset: ${t}`);return}e.state.setGlobal({"labelFormData.start_date":n,"labelFormData.end_date":s}),S.log(`[setLabelDatePreset] Applied preset '${t}': ${n} ~ ${s??"permanent"}`)}const Or=(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})},Gr=(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}))},Er=(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(f=>f.label_id===a);if(i<0)return;const l=new Date,u=f=>f.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})},Fr=(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?.()},Dr=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})}}},$r=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})}}},ee=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 at(){return window.G7Core?.config?.("app.supported_locales")??["ko","en"]}function Nr(r,c){const t=(r.params||{}).locale;if(!t){ee.warn("[removeDescriptionLocale] Missing locale param");return}const o=window.G7Core;if(!o?.state){ee.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=at(),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.`),ee.log(`[removeDescriptionLocale] Removed locale: ${t}`)}function Tr(r,c){const e=window.G7Core;if(!e?.state){ee.warn("[showAddLocaleModal] G7Core.state API is not available");return}const t=e.state.getLocal()||{};e.state.setLocal({ui:{...t.ui,showAddLocaleModal:!0}})}function Rr(r,c){const t=(r.params||{}).locale;if(!t){ee.warn("[addDescriptionLocale] Missing locale param");return}const o=window.G7Core;if(!o?.state){ee.warn("[addDescriptionLocale] G7Core.state API is not available");return}const a=o.state.getLocal()||{},n=at(),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.`),ee.log(`[addDescriptionLocale] Added locale: ${t}`)}const W=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 st(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 Y(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 it(){const r=window.G7Core;return r||(W.error("[bulkUpdate] G7Core is not available"),null)}function Mr(r,c){const e=it();if(!e)return;const t=e.state.getLocal()||{},o=e.state.get()||{},a=o.bulkSelectedItems||t.selectedItems||[],n=o.bulkSelectedOptionIds||t.selectedOptionIds||[];W.log("[bulkUpdate] globalState.bulkSelectedItems:",o.bulkSelectedItems),W.log("[bulkUpdate] localState.selectedItems:",t.selectedItems),W.log("[bulkUpdate] Final selectedItems:",a),a.length>0?Hr(e,o,t,a):n.length>0?qr(e,o,t,n):e.toast.warning(e.t("sirsoft-ecommerce.admin.product.bulk.no_selection"))}function Hr(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||[],f=new Set(t),g=[],w=[];function y(p,v,C){if(v._modified||l.includes(C)){const x={product_id:p.id,option_id:v.id},I=d[C]||[];!s.price_adjustment&&I.includes("price_adjustment")&&(x.price_adjustment=v.price_adjustment),!s.price_adjustment&&I.includes("selling_price")&&(x.price_adjustment=v.price_adjustment),!s.stock_quantity&&I.includes("stock_quantity")&&(x.stock_quantity=v.stock_quantity),I.includes("option_name")&&(x.option_name=v.option_name),I.includes("sku")&&(x.sku=v.sku),I.includes("list_price")&&(x.list_price=v.list_price),I.includes("safe_stock_quantity")&&(x.safe_stock_quantity=v.safe_stock_quantity),I.includes("is_default")&&(x.is_default=v.is_default),I.includes("is_active")&&(x.is_active=v.is_active),Object.keys(x).length>2&&w.push(x)}}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),x=u[A]||[];!n.sales_status&&x.includes("sales_status")&&(C.sales_status=p.sales_status),!n.display_status&&x.includes("display_status")&&(C.display_status=p.display_status),x.includes("name")&&(C.name=p.name),x.includes("list_price")&&(C.list_price=p.list_price),x.includes("selling_price")&&(C.selling_price=p.selling_price),Object.keys(C).length>1&&g.push(C)}(p.options||[]).forEach(C=>{const A=`${p.id}-${C.id}`;y(p,C,A)})}),m.length>0&&a.forEach(p=>{f.has(p.id)||(p.options||[]).forEach(v=>{const C=`${p.id}-${v.id}`;m.includes(C)&&y(p,v,C)})});const _={ids:t};Object.keys(n).length>0&&(_.bulk_changes=n),g.length>0&&(_.items=g),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})),lt(r),ct(r),r.modal.close()}).catch(p=>{W.error("[bulkUpdateProducts] Error:",p),st(r,p,"sirsoft-ecommerce.admin.product.bulk.update_error")}).finally(()=>{r.state.set({isProcessing:!1})})}function qr(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 f=`${d.id}-${m.id}`,g=m._modified||i.includes(f);if(t.includes(f)&&g){const w={product_id:d.id,option_id:m.id},y=s[f]||[];!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})),lt(r),ct(r),r.modal.close()}).catch(d=>{W.error("[bulkUpdateOptions] Error:",d),st(r,d,"sirsoft-ecommerce.admin.product.bulk.option_update_error")}).finally(()=>{r.state.set({isProcessing:!1})})}function ct(r){Promise.resolve(r.dataSource.refetch("products")).then(()=>{eo(r,{force:!0})}).catch(c=>{W.error("[bulkUpdate] Failed to refetch products:",c)})}function lt(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 Ur(r,c){const e=it();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||[],f=t.modifiedProductFields||{},g=t.modifiedOptionFields||{},w=new Set(s);function y(_,p,v){const C=[],A=g[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}`),v){if(!o.bulkPriceCondition&&A.includes("price_adjustment")){const x=_.price_adjustment?.toLocaleString?.()??_.price_adjustment;C.push(e.t("sirsoft-ecommerce.admin.product.messages.bulk_summary_price_adjustment_inline",{value:x})||`Price adjustment: ${x}`)}!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:Y(_.option_name)})||`Option name: ${Y(_.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=[],v=_._modified||d.includes(_.id),C=String(_.id),A=f[C]||[];if(o.bulkSalesStatus){const I=Se(e,"sales_status",o.bulkSalesStatus);p.push(e.t("sirsoft-ecommerce.admin.product.messages.bulk_summary_sales_status",{status:I})||`Sales status: ${I}`)}if(o.bulkDisplayStatus){const I=Se(e,"display_status",o.bulkDisplayStatus);p.push(e.t("sirsoft-ecommerce.admin.product.messages.bulk_summary_display_status",{status:I})||`Display status: ${I}`)}if(v){if(!o.bulkSalesStatus&&A.includes("sales_status")){const I=Se(e,"sales_status",_.sales_status);p.push(e.t("sirsoft-ecommerce.admin.product.messages.bulk_summary_sales_status",{status:I})||`Sales status: ${I}`)}if(!o.bulkDisplayStatus&&A.includes("display_status")){const I=Se(e,"display_status",_.display_status);p.push(e.t("sirsoft-ecommerce.admin.product.messages.bulk_summary_display_status",{status:I})||`Display status: ${I}`)}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||v)&&l.products.push({id:_.id,name:Y(_.name),changes:p.length>0?p.join(", "):v?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"});const x=u&&!!(o.bulkPriceCondition||o.bulkStockCondition);if(Array.isArray(_.options))_.options.forEach(I=>{const N=`${_.id}-${I.id}`,L=I._modified||m.includes(N),O=y(I,N,L);(O.length>0||x||L)&&l.options.push({productId:_.id,optionId:I.id,productName:Y(_.name),optionName:I.option_name_localized||Y(I.option_name)||I.name||"",changes:O.length>0?O.join(", "):L?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"})});else if(x){const I=Number(_.options_total_count)||0;I>0&&l.options.push({aggregate:!0,productId:_.id,optionId:0,optionCount:I,productName:Y(_.name),optionName:"",changes:y({},`${_.id}-*`,!1).join(", ")||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 v=`${_.id}-${p.id}`;if(i.includes(v)){const C=p._modified||m.includes(v),A=y(p,v,C);l.options.push({productId:_.id,optionId:p.id,productName:Y(_.name),optionName:p.option_name_localized||Y(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.reduce((_,p)=>_+(p.aggregate?p.optionCount||0:1),0),e.state.set({bulkConfirmData:l}),W.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 Vr(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 jr(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 Br(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 Kr(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 Qr(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 zr(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 Wr(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),f=document.createElement("a");f.href=m;const g=new Date,w=`${g.getFullYear()}-${String(g.getMonth()+1).padStart(2,"0")}-${String(g.getDate()).padStart(2,"0")}`;return f.download=`orders_${w}.xlsx`,document.body.appendChild(f),f.click(),document.body.removeChild(f),window.URL.revokeObjectURL(m),{success:!0}}function Yr(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 Jr(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 Xr(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 Zr(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 en(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 He=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 tn(r,c){const e=r.params||{},{actionId:t,row:o}=e,a=window.G7Core;if(!t||!o?.id)return He.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){He.error("[handleProductRowAction] 삭제 가능 여부 확인 실패:",n),a?.state?.setLocal({productList:{deleteTargetProduct:o,canDelete:!1,deleteBlockReason:"삭제 가능 여부를 확인할 수 없습니다.",relatedData:null},ui:{showDeleteModal:!0}})}break;default:return He.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 dt(){return typeof document>"u"?"":document.querySelector('input[name="batchTrackingNumber"]')?.value?.trim()??""}function on(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 rn(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 nn(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 an(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||dt();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 f=t.cancelPg??!0,g=e.dataSource?.get?.("order")?.data,w=g?.options||[],y=o.map(_=>{const p=w.find(v=>v.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(f){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 _=g?.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 f of o){const g=u.find(w=>w.id===f);g&&(d.push({id:g.id,product_name:g.product_name,product_option_name:g.product_option_name,sku:g.sku,thumbnail_url:g.product_snapshot?.thumbnail_url||"",unit_price:g.unit_price,original_price:g.product_snapshot?.original_price||g.unit_price,quantity:g.quantity,option_status:g.option_status}),m[g.id]=g.quantity)}e.state.setLocal({bulkConfirmItems:d,changeQuantities:m,batchOrderStatus:a,batchCarrierId:n,batchTrackingNumber:s}),e.modal?.open?.("modal_batch_change_confirm")}async function sn(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||dt(),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||[],f={items:a.map(g=>{const w=d.find(y=>y.id===g);return{option_id:g,quantity:l?.[g]??w?.quantity??1}}),status:n};s&&(f.carrier_id=s),i&&(f.tracking_number=i);try{const g=await e.api.patch(`/api/modules/sirsoft-ecommerce/admin/orders/${t}/options/bulk-status`,f);if(g?.success){const w=e.t,y=g.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(g?.message||"상태 변경에 실패했습니다.")}catch(g){U.error("[processOrderDetailBulkChange] API 호출 실패:",g);const y=(g?.response?.data||g?.data||{})?.message||g?.message;e.toast?.error?.(y??e.t?.("sirsoft-ecommerce.admin.order.detail.handler.bulk_change_failed")??"상태 변경에 실패했습니다.")}}async function cn(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 ln(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 dn(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 un(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 qe=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 K=null;function mn(){K&&(clearTimeout(K),K=null,qe.log("[clearCancelOrderTimers] debounce 타이머 정리 완료"))}function pn(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;K&&clearTimeout(K),K=setTimeout(()=>{const m=window.__g7ActionContext;window.__g7ActionContext=d;try{Ue(e,a)}finally{window.__g7ActionContext=m}},500)}async function fn(r,c){const e=window.G7Core;if(!e?.state)return;const{orderId:t,cancelItems:o}=r.params||{};t&&await Ue(e,t,o)}async function Ue(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){qe.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 gn(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;K&&clearTimeout(K),K=setTimeout(()=>{const n=window.__g7ActionContext;window.__g7ActionContext=a;try{Ue(e,o)}finally{window.__g7ActionContext=n}},300)}async function _n(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){qe.error("[executeCancelOrder] 실패:",d);const m=d?.response?.data||d?.data,g=(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")??"주문 취소에 실패했습니다.";g?(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 ut=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 R=null;function ve(r){const c=r?.state?.get?.("_global")?.guestOrderToken,e=r?.dataSource?.get?.("order")?.data?.order_number;return!!(c&&e)}function wn(){R&&(clearTimeout(R),R=null)}function yn(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 hn(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 Cn(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)&&le(e,i)}function bn(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}),R&&clearTimeout(R),R=setTimeout(()=>{le(e,o)},500)}function In(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}),R&&clearTimeout(R),R=setTimeout(()=>{le(e,t)},500)}function Sn(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;R&&clearTimeout(R),R=setTimeout(()=>{const m=window.__g7ActionContext;window.__g7ActionContext=d;try{le(e,a)}finally{window.__g7ActionContext=m}},500)}async function vn(r,c){const e=window.G7Core;if(!e?.state)return;const{orderId:t}=r.params||{};!t&&!ve(e)||await le(e,t)}async function le(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){ut.error("[estimateUserRefund] 실패:",s),r.state.setLocal({refundLoading:!1,refundEstimate:null}),r.toast?.error?.(r.t?.("mypage.order_detail.cancel_modal.estimate_failed")??"환불 예상금액 계산에 실패했습니다.")}}function Ln(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;R&&clearTimeout(R),R=setTimeout(()=>{const n=window.__g7ActionContext;window.__g7ActionContext=a;try{le(e,o)}finally{window.__g7ActionContext=n}},300)}async function xn(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,f=!!(d&&m),g=f?`/api/modules/sirsoft-ecommerce/guest/orders/${m}/cancel`:`/api/modules/sirsoft-ecommerce/user/orders/${t}/cancel`,w=f?{headers:{"X-Guest-Order-Token":d}}:void 0,y=await e.api.post(g,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){ut.error("[executeUserCancelOrder] 실패:",d);const m=d?.response?.data||d?.data,f=m?.message||d?.message||"",g=m?.errors??null,w=e.t?.("mypage.order_detail.cancel_modal.cancel_failed")??"주문 취소에 실패했습니다.";e.state.setLocal({isCancelling:!1,cancelError:f||w,cancelValidationErrors:g}),e.toast?.error?.(f||w)}}const mt=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 Pn(r){const c=r?.state?.get?.("_global")?.guestOrderToken,e=r?.dataSource?.get?.("order")?.data?.order_number;return!!(c&&e)}async function An(r,c){const e=window.G7Core;if(!e?.state)return;const{orderId:t,optionId:o}=r.params||{};if(!o||!t&&!Pn(e)){mt.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){mt.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 pt=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 kn(r){const c=r?.state?.get?.("_global")?.guestOrderToken,e=r?.dataSource?.get?.("order")?.data?.order_number;return!!(c&&e)}async function On(r,c){const e=window.G7Core;if(!e?.state)return;const{orderId:t}=r.params||{},o=kn(e);if(!t&&!o){pt.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){pt.error("[changeShippingAddress] 실패:",i),e.state.setLocal({isSubmittingAddress:!1});const l=i?.response?.data?.message||i?.data?.message||i?.message||"배송지 변경에 실패했습니다.";e.toast?.error?.(l)}}const Ve=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 Gn(r,c){const e=window.G7Core;if(!e?.state)return;const{orderId:t,optionId:o,productId:a}=r.params||{};if(!t||!o||!a){Ve.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 f=0;fconsole.log("[Ecom:ShippingPolicyForm]",...r),warn:(...r)=>console.warn("[Ecom:ShippingPolicyForm]",...r),error:(...r)=>console.error("[Ecom:ShippingPolicyForm]",...r)},En=["fixed","conditional_free","per_quantity","per_weight","per_volume","per_volume_weight","per_amount"],Fn=["conditional_free"],Dn=["range_amount","range_quantity","range_weight","range_volume","range_volume_weight"],$n=["api"],Nn=["per_quantity","per_weight","per_volume","per_volume_weight","per_amount"],Tn={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 te(r){return{showBaseFee:En.includes(r),showFreeThreshold:Fn.includes(r),showRanges:Dn.includes(r),showApiSettings:$n.includes(r),showUnitValue:Nn.includes(r)}}function G(){const r=window.G7Core;return r?.state?r:(b.warn("G7Core.state not available"),null)}function E(r,c){r.setLocalState?r.setLocalState(c):G()?.state?.setLocal(c)}function F(r){return[...(r.state.getLocal?.()??{}).form?.country_settings??[]]}function Rn(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=te(u);Object.assign(s,d),b.log("[initShippingPolicyForm]",o?"Edit":"Copy","mode, countries:",i.length,"first charge_policy:",u)}else{const i=te("fixed");Object.assign(s,i),b.log("[initShippingPolicyForm] Create mode, default flags")}E(c,s)}function Mn(r,c){const t=(r.params||{}).country_code??"";if(!t){b.warn("[addCountrySetting] country_code is empty");return}const o=G();if(!o)return;const a=F(o);if(a.some(u=>u.country_code===t)){b.warn("[addCountrySetting] Duplicate country_code:",t);return}const n={country_code:t,...Tn};a.push(n);const s=a.length-1,i=te(n.charge_policy),l={"form.country_settings":a,activeCountryTab:s,...i};E(c,l),b.log("[addCountrySetting] Added country:",t,"index:",s)}function Hn(r,c){const e=r.params||{},t=Number(e.index);if(isNaN(t)||t<0){b.warn("[removeCountrySetting] Invalid index:",e.index);return}const o=G();if(!o)return;const a=F(o),n=o.state.getLocal?.()??{};if(t>=a.length){b.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=te(d?d.charge_policy:"fixed"),f={"form.country_settings":a,activeCountryTab:l,rangeErrors:u,...m};E(c,f),b.log("[removeCountrySetting] Removed index:",t,"country:",s,"newTab:",l)}function qn(r,c){const e=r.params||{},t=Number(e.index);if(isNaN(t)||t<0){b.warn("[switchCountryTab] Invalid index:",e.index);return}const o=G();if(!o)return;const a=F(o);if(t>=a.length){b.warn("[switchCountryTab] Index out of bounds:",t);return}const n=a[t],s=te(n.charge_policy),i={activeCountryTab:t,...s};E(c,i),b.log("[switchCountryTab] Switched to tab:",t,"country:",n.country_code,"charge_policy:",n.charge_policy)}function Un(r,c){const e=r.params||{},t=e.field??"",o=e.value;if(!t){b.warn("[updateCountryField] field is empty");return}const a=G();if(!a)return;const n=a.state.getLocal?.()??{},s=Number(n.activeCountryTab??0),i=F(a);if(s>=i.length){b.warn("[updateCountryField] Index out of bounds:",s);return}i[s]={...i[s],[t]:o},E(c,{"form.country_settings":i}),b.log("[updateCountryField] Updated",t,"=",o,"at index:",s)}function Vn(r,c){const e=r.params||{},t=e.value??"",o=Number(e.index??0);if(!t){b.warn("[onChargePolicyChange] charge_policy value is empty");return}const a=G();if(!a)return;const n=te(t);b.log("[onChargePolicyChange]",t,n,"countryIndex:",o);const s=F(a),i=s[o];if(!i){b.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};E(c,u)}function jn(r,c){const e=r.params||{},t=Number(e.index??0),o=G();if(!o)return;const a=F(o),n=a[t];if(!n){b.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}},E(c,{"form.country_settings":a}),Le(o,c,t,s,n.country_code),b.log("[addRangeTier] Added tier, total:",s.length)}function Bn(r,c){const e=r.params||{},t=Number(e.countryIndex??0),o=Number(e.tierIndex);if(isNaN(o)||o<0){b.warn("[removeRangeTier] Invalid tierIndex:",e.tierIndex);return}const a=G();if(!a)return;const n=F(a),s=n[t];if(!s){b.warn("[removeRangeTier] No country setting at index:",t);return}const i=[...s.ranges?.tiers??[]];if(o>=i.length){b.warn("[removeRangeTier] tierIndex out of bounds:",o);return}i.splice(o,1),n[t]={...s,ranges:{...s.ranges??{},tiers:i}},E(c,{"form.country_settings":n}),Le(a,c,t,i,s.country_code),b.log("[removeRangeTier] Removed tier:",o,"remaining:",i.length)}function Kn(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){b.warn("[updateRangeTierField] Invalid tierIndex:",e.tierIndex);return}if(!["min","max","fee"].includes(a)){b.warn("[updateRangeTierField] Invalid field:",a);return}const s=G();if(!s)return;const i=F(s),l=i[t];if(!l){b.warn("[updateRangeTierField] No country setting at index:",t);return}const u=[...l.ranges?.tiers??[]];if(o>=u.length){b.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}},E(c,{"form.country_settings":i}),Le(s,c,t,u,l.country_code)}function Qn(r,c){const e=r.params||{},t=Number(e.countryIndex??0),o=G();if(!o)return;const n=F(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]=[],E(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){b.warn("[removeExtraFeeRow] feeIndex out of bounds:",o);return}i.splice(o,1),n[t]={...s,extra_fee_settings:i},E(c,{"form.country_settings":n}),b.log("[removeExtraFeeRow] Removed index:",o,"remaining:",i.length)}function Yn(r,c){const e=r.params||{},t=Number(e.countryIndex??0),o=e.settings;if(!o||!Array.isArray(o)){b.warn("[applyExtraFeeTemplate] Invalid settings:",o);return}const a=G();if(!a)return;const n=F(a),s=n[t];if(!s){b.warn("[applyExtraFeeTemplate] No country setting at index:",t);return}n[t]={...s,extra_fee_settings:o},E(c,{"form.country_settings":n});const i=a.t,l=i?.("sirsoft-ecommerce.admin.shipping_policy.form.template_applied")??"템플릿이 적용되었습니다.";a.toast?.success?.(l),b.log("[applyExtraFeeTemplate] Applied template with",o.length,"rows")}function Jn(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=F(o),i=s[n];i&&(s[n]={...i,ranges:{...i.ranges??{},type:i.charge_policy,unit_value:t}},E(c,{"form.country_settings":s}),b.log("[updateUnitValue] Updated unit_value:",t))}function Xn(r,c){const e=G();if(!e)return;const t=e.state.getLocal?.()??{},o=Number(t.activeCountryTab??0),a=F(e),n=a[o];if(!n)return;const s=[...n.api_request_fields??[]];s.push(""),a[o]={...n,api_request_fields:s},E(c,{"form.country_settings":a}),b.log("[addApiRequestField] Added field, total:",s.length)}function Zn(r,c){const e=r.params||{},t=Number(e.fieldIndex),o=e.value??"";if(isNaN(t)||t<0){b.warn("[updateApiRequestField] Invalid fieldIndex:",e.fieldIndex);return}const a=G();if(!a)return;const n=a.state.getLocal?.()??{},s=Number(n.activeCountryTab??0),i=F(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},E(c,{"form.country_settings":i}),b.log("[updateApiRequestField] Updated index:",t,"value:",o))}function ea(r,c){const e=r.params||{},t=Number(e.fieldIndex);if(isNaN(t)||t<0){b.warn("[removeApiRequestField] Invalid fieldIndex:",e.fieldIndex);return}const o=G();if(!o)return;const a=o.state.getLocal?.()??{},n=Number(a.activeCountryTab??0),s=F(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},E(c,{"form.country_settings":s}),b.log("[removeApiRequestField] Removed index:",t,"remaining:",l.length))}function ta(r,c){const t=(r.params||{}).field??"";if(!t){b.warn("[toggleApiRequestField] Missing field param");return}const o=G();if(!o)return;const a=o.state.getLocal?.()??{},n=Number(a.activeCountryTab??0),s=F(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},E(c,{"form.country_settings":s}),b.log("[toggleApiRequestField] Toggled:",t,"selected:",d)}function oa(r,c){const e=r.params||{},t=e.field??"";if(!t){b.warn("[updateApiConfigField] Missing field param");return}const o=G();if(!o)return;const a=o.state.getLocal?.()??{},n=Number(a.activeCountryTab??0),s=F(o),i=s[n];if(!i)return;const l={...i.api_config??{},[t]:e.value};s[n]={...i,api_config:l},E(c,{"form.country_settings":s}),b.log("[updateApiConfigField] Updated",t,"at index:",n)}function ra(r,c){const e=r.params||{},t=e.field??"";if(!t){b.warn("[updateApiFieldMap] Missing field param");return}const o=G();if(!o)return;const a=o.state.getLocal?.()??{},n=Number(a.activeCountryTab??0),s=F(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},E(c,{"form.country_settings":s}),b.log("[updateApiFieldMap] Mapped",t,"→",l||"(removed)")}function na(r,c){const e=G();if(!e)return;const t=e.state.getLocal?.()??{},o=Number(t.activeCountryTab??0),n=F(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 aa(r,c){const e=r.params||{},t=Number(e.feeIndex),o=e.field,a=e.value;if(isNaN(t)||t<0){b.warn("[updateExtraFeeField] Invalid feeIndex:",e.feeIndex);return}if(!["zipcode","fee","region"].includes(o)){b.warn("[updateExtraFeeField] Invalid field:",o);return}const n=G();if(!n)return;const s=n.state.getLocal?.()??{},i=Number(s.activeCountryTab??0),l=F(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},E(c,{"form.country_settings":l}),b.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 ft(r,c){for(const e of r){if(e.id===c)return e;if(e.children&&e.children.length>0){const t=ft(e.children,c);if(t)return t}}return null}function gt(r){return new URLSearchParams(window.location.search).get(r)}function sa(r){return window.G7Core?.state?.getDataSource?.(r)}async function ia(r,c,e=30,t=100){for(let o=0;o0)return n;await new Promise(s=>setTimeout(s,t))}return null}async function ca(r,c){const e=window.G7Core,t=gt("id"),o=gt("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 ia("categories",l=>l?.data?.data);if(!a){me.warn("[initCategoryFromUrl] categories data not available after waiting");return}const n=parseInt(t,10),s=ft(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 _t(r){return new URLSearchParams(window.location.search).get(r)}function la(r){return window.G7Core?.state?.getDataSource?.(r)}async function da(r,c,e=30,t=100){for(let o=0;o0)return n;await new Promise(s=>setTimeout(s,t))}return null}async function ua(r,c){const e=window.G7Core,t=_t("id"),o=_t("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 da("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 wt(r){return new URLSearchParams(window.location.search).get(r)}function ma(r){return window.G7Core?.state?.getDataSource?.(r)}async function pa(r,c,e=30,t=100){for(let o=0;o0)return n;await new Promise(s=>setTimeout(s,t))}return null}async function fa(r,c){const e=window.G7Core,t=wt("id"),o=wt("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 pa("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 yt(r){return new URLSearchParams(window.location.search).get(r)}function ga(r){return window.G7Core?.state?.getDataSource?.(r)}async function _a(r,c,e=30,t=100){for(let o=0;o0)return n;await new Promise(s=>setTimeout(s,t))}return null}async function wa(r,c){const e=window.G7Core,t=yt("id"),o=yt("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 _a("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 oe={updateProductField:At,updateOptionField:kt,calculateCurrencyPrices:se,initPreferredCurrency:Tt,initPreferredShippingCountry:qt,setDateRange:Vt,setDefaultOption:jt,toggleOption:Bt,toggleProductOptions:Ye,toggleAllOptionsInRow:Kt,getProductOptionStates:Qt,syncProductSelection:zt,loadExpandedOptions:Fe,retryExpandedOptions:to,generateCopyProductCode:Wt,copyProduct:Yt,selectCategory:ro,selectCategoryMobile:no,addCategoryToSelection:ao,removeCategoryFromSelection:so,getCategoryBreadcrumb:io,validateCategoryPath:lo,initCategoryInfosFromProduct:co,getBrandName:uo,getBrandDescription:mo,updatePrice:_o,calculateTotalOptionStock:wo,validatePriceRelation:yo,addOptionInput:Go,removeOptionInput:Eo,updateOptionInput:Fo,generateOptions:Do,deleteOption:$o,applyOptionAddTool:No,addRequiredItem:To,updateRequiredItem:Ro,removeRequiredItem:Mo,reorderRequiredItems:Ho,addAdditionalOption:qo,updateAdditionalOption:Uo,removeAdditionalOption:Vo,reorderAdditionalOptions:jo,clearAdditionalOptions:Bo,addAdditionalOptionValue:Ko,updateAdditionalOptionValue:Qo,removeAdditionalOptionValue:zo,uploadImages:Xo,setThumbnail:Zo,reorderImages:er,updateDescription:tr,confirmSelectNoticeTemplate:or,selectNoticeTemplate:nt,updateNoticeItem:rr,removeNoticeItem:nr,reorderNoticeItems:ar,fillNoticeWithValue:sr,switchNoticeMode:ir,updateNewTemplateName:cr,addNoticeItem:lr,updateNoticeItemName:dr,saveAsNoticeTemplate:ur,confirmSaveNoticeTemplate:fr,fillTemplateFieldsWithDetailReference:mr,fillNoticeItemsWithDetailReference:pr,toggleLabel:gr,generateProductCode:_r,getShippingPolicyInfo:wr,getCommonInfoContent:yr,updateShoppingIntegration:hr,updateShippingType:Cr,updateIdentificationCode:br,openLabelPeriodModal:Ir,saveLabelPeriod:Sr,removeLabelPeriod:vr,updateActivityLogSort:Lr,updateActivityLogPerPage:xr,setDefaultShippingPolicy:Pr,setLabelDatePreset:kr,toggleDefaultShippingPolicy:Ar,toggleLabelAssignment:Or,saveLabelSettings:$r,deleteLabel:Dr,updateLabelPeriodInline:Gr,setLabelDatePresetInline:Er,confirmUncheckLabel:Fr,removeDescriptionLocale:Nr,showAddLocaleModal:Tr,addDescriptionLocale:Rr,setDefaultOptionFromGrid:Io,addOptionRow:vo,updateFormOptionField:So,recalculateOptionPriceAdjustments:Lo,bulkUpdate:Mr,buildConfirmData:Ur,buildOrderColumns:Vr,toggleArrayValue:jr,toggleVisibleFilter:Br,syncOrderSelection:Kr,handleOrderRowAction:Qr,processOrderBulkAction:zr,buildOrderBulkConfirmData:Zr,executeOrderBulkAction:en,downloadOrderExcel:Wr,saveVisibleColumns:Yr,loadVisibleColumns:Jr,loadVisibleFilters:Xr,handleProductRowAction:tn,initOrderDetailForm:on,toggleProductSelection:rn,toggleAllProducts:nn,buildOrderDetailBulkConfirmData:an,processOrderDetailBulkChange:sn,saveAdminMemo:cn,updateChangeQuantity:ln,openConfirmDepositModal:dn,confirmDeposit:un,initShippingPolicyForm:Rn,addCountrySetting:Mn,removeCountrySetting:Hn,switchCountryTab:qn,updateCountryField:Un,onChargePolicyChange:Vn,addRangeTier:jn,removeRangeTier:Bn,updateRangeTierField:Kn,validateRangeTiers:Qn,addExtraFeeRow:zn,removeExtraFeeRow:Wn,applyExtraFeeTemplate:Yn,updateUnitValue:Jn,addApiRequestField:Xn,updateApiRequestField:Zn,removeApiRequestField:ea,toggleApiRequestField:ta,updateApiConfigField:oa,updateApiFieldMap:ra,testShippingApi:na,updateExtraFeeField:aa,updateCancelQuantity:pn,estimateRefundAmount:fn,changeRefundPriority:gn,executeCancelOrder:_n,clearCancelOrderTimers:mn,toggleItemSelection:yn,toggleSelectAllItems:hn,initUserCancelItems:Cn,toggleUserCancelItem:bn,toggleUserCancelSelectAll:In,updateUserCancelQuantity:Sn,estimateUserRefund:vn,changeUserRefundPriority:Ln,executeUserCancelOrder:xn,clearUserCancelOrderTimers:wn,confirmOrderOption:An,changeShippingAddress:On,submitReview:Gn,initCategoryFromUrl:ca,initBrandFromUrl:ua,initCommonInfoFromUrl:fa,initNoticeFromUrl:wa},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 ht(r=!1){const c=window.G7Core?.getActionDispatcher?.();if(c)Object.entries(oe).forEach(([e,t])=>{const o=`${V}.${e}`;c.registerHandler(o,t,{category:"module",source:V})}),_e.log(`${Object.keys(oe).length} handler(s) registered:`,Object.keys(oe).map(e=>`${V}.${e}`));else if(r){let e=0;const t=50,o=()=>{const a=window.G7Core?.getActionDispatcher?.();a?(Object.entries(oe).forEach(([n,s])=>{const i=`${V}.${n}`;a.registerHandler(i,s,{category:"module",source:V})}),_e.log(`${Object.keys(oe).length} handler(s) registered:`,Object.keys(oe).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 je(){if(document.readyState==="loading")document.addEventListener("DOMContentLoaded",()=>ht(!0));else{const r=!!window.G7Core?.getActionDispatcher?.();ht(!r)}}return je(),typeof window<"u"&&(window.__SirsoftEcommerce={identifier:V,handlers:Object.keys(oe),initModule:je}),Pe.initModule=je,Object.defineProperty(Pe,Symbol.toStringTag,{value:"Module"}),Pe}({}); +//# sourceMappingURL=module.iife.js.map diff --git a/modules/_bundled/sirsoft-ecommerce/docs/api/README.md b/modules/_bundled/sirsoft-ecommerce/docs/api/README.md index 593b69e1..ea170343 100644 --- a/modules/_bundled/sirsoft-ecommerce/docs/api/README.md +++ b/modules/_bundled/sirsoft-ecommerce/docs/api/README.md @@ -4,7 +4,7 @@ > 아래 표는 자동 생성됩니다. 각 문서를 열면 엔드포인트별 파라미터·응답·예시를 볼 수 있습니다. -- **문서 수**: 33 · **엔드포인트 수**: 231 +- **문서 수**: 33 · **엔드포인트 수**: 232 | 문서 | 도메인 | 엔드포인트 | | --- | --- | --- | @@ -31,7 +31,7 @@ | [product-image.md](product-image.md) | `product-image` | 1 | | [product-labels.md](product-labels.md) | `product-labels` | 6 | | [product-notice-templates.md](product-notice-templates.md) | `product-notice-templates` | 7 | -| [products.md](products.md) | `products` | 30 | +| [products.md](products.md) | `products` | 31 | | [promotion-coupons.md](promotion-coupons.md) | `promotion-coupons` | 9 | | [review-image.md](review-image.md) | `review-image` | 1 | | [reviews.md](reviews.md) | `reviews` | 12 | diff --git a/modules/_bundled/sirsoft-ecommerce/docs/api/addresses.md b/modules/_bundled/sirsoft-ecommerce/docs/api/addresses.md index ecee15fa..58153741 100644 --- a/modules/_bundled/sirsoft-ecommerce/docs/api/addresses.md +++ b/modules/_bundled/sirsoft-ecommerce/docs/api/addresses.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Addresses 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` @@ -42,7 +42,7 @@ _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| addresses | object | `{"data":[{"id":263,"user_id":"a1e0a91a-fba6-491c-a53e-728…` | 회원 본인 소유 배송지 컬렉션 (`data[]` 배송지 항목 배열 + `abilities.can_create` — UserAddressCollection 파생) | +| addresses | object | `{"data":[],"abilities":{"can_create":true}}` | 회원 본인 소유 배송지 컬렉션 (`data[]` 배송지 항목 배열 + `abilities.can_create` — UserAddressCollection 파생) | **응답 예시** @@ -56,39 +56,7 @@ HTTP/1.1 200 "message": "배송지 목록을 조회했습니다.", "data": { "addresses": { - "data": [ - { - "id": 1, - "user_id": "a234c2b1-cde8-437f-b28b-23323be2b98d", - "name": "API 문서 샘플 배송지", - "recipient_name": "원진수", - "recipient_phone": "010-4988-7051", - "country_code": "KR", - "country_name": { - "ko": "한국", - "en": "South Korea" - }, - "zipcode": "27058", - "address": "충청남도 남양주시 테헤란로 6114", - "address_detail": "Quidem natus iste.", - "address_line_1": null, - "address_line_2": null, - "city": null, - "state": null, - "postal_code": null, - "is_default": false, - "is_domestic": true, - "is_international": false, - "full_address": "충청남도 남양주시 테헤란로 6114 Quidem natus iste.", - "created_at": "2026-07-08 10:44:49", - "updated_at": "2026-07-08 10:44:49", - "abilities": { - "can_update": true, - "can_delete": true, - "can_set_default": true - } - } - ], + "data": [], "abilities": { "can_create": true } @@ -174,7 +142,7 @@ _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| address | object | `{"id":9,"user_id":"a234c2b1-cde8-437f-b28b-23323be2b98d",…` | 기본주소 (국내) | +| address | object | `{"id":99,"user_id":"a26219fc-94a0-4f63-9404-04c2a6ac99e4"…` | 기본주소 (국내) | **응답 예시** @@ -188,8 +156,8 @@ HTTP/1.1 201 "message": "배송지가 추가되었습니다.", "data": { "address": { - "id": 9, - "user_id": "a234c2b1-cde8-437f-b28b-23323be2b98d", + "id": 99, + "user_id": "a26219fc-94a0-4f63-9404-04c2a6ac99e4", "name": "실측 예시값", "recipient_name": "실측 예시값", "recipient_phone": "실측 예시값", @@ -210,8 +178,8 @@ HTTP/1.1 201 "is_domestic": true, "is_international": false, "full_address": "실측 예시값 실측 예시값", - "created_at": "2026-07-08 15:00:36", - "updated_at": "2026-07-08 15:00:36", + "created_at": "2026-08-05 09:46:56", + "updated_at": "2026-08-05 09:46:56", "abilities": { "can_update": true, "can_delete": false, @@ -249,7 +217,7 @@ HTTP/1.1 201 **요청 예시** ```http -DELETE /api/modules/sirsoft-ecommerce/user/addresses/1 HTTP/1.1 +DELETE /api/modules/sirsoft-ecommerce/user/addresses/{id} HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -257,23 +225,11 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - - - + **응답 예시** -```http -HTTP/1.1 200 -``` - -```json -{ - "success": true, - "message": "배송지가 삭제되었습니다.", - "data": null -} -``` + **에러 응답** @@ -302,7 +258,7 @@ HTTP/1.1 200 **요청 예시** ```http -GET /api/modules/sirsoft-ecommerce/user/addresses/1 HTTP/1.1 +GET /api/modules/sirsoft-ecommerce/user/addresses/{id} HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -310,57 +266,11 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) -_단건 응답: `data` 객체의 필드._ - -| 필드 | 타입 | 실측 예시값 | 용도/설명 | -| --- | --- | --- | --- | -| address | object | `{"id":1,"user_id":"a234c2b1-cde8-437f-b28b-23323be2b98d",…` | 기본주소 (국내) | + **응답 예시** -```http -HTTP/1.1 200 -``` - -```json -{ - "success": true, - "message": "배송지 정보를 조회했습니다.", - "data": { - "address": { - "id": 1, - "user_id": "a234c2b1-cde8-437f-b28b-23323be2b98d", - "name": "API 문서 샘플 배송지", - "recipient_name": "원진수", - "recipient_phone": "010-4988-7051", - "country_code": "KR", - "country_name": { - "ko": "한국", - "en": "South Korea" - }, - "zipcode": "27058", - "address": "충청남도 남양주시 테헤란로 6114", - "address_detail": "Quidem natus iste.", - "address_line_1": null, - "address_line_2": null, - "city": null, - "state": null, - "postal_code": null, - "is_default": false, - "is_domestic": true, - "is_international": false, - "full_address": "충청남도 남양주시 테헤란로 6114 Quidem natus iste.", - "created_at": "2026-07-08 10:44:49", - "updated_at": "2026-07-08 10:44:49", - "abilities": { - "can_update": true, - "can_delete": true, - "can_set_default": true - } - } - } -} -``` + **에러 응답** @@ -402,14 +312,12 @@ HTTP/1.1 200 | intl_postal_code | body | string | 아니오 | max 20 | 우편번호 (국제 주소) | | is_default | body | boolean | 아니오 | — | 기본값 지정 여부 | -개별 필드는 모두 선택이지만 주소 필수 조합은 "기존 저장값 + 이번 요청" 결과 상태 기준으로 검증됩니다. 국내(`country_code=KR` 또는 미전송 시 기존 국가가 KR)는 `zipcode` + `address`, 해외는 `address_line_1` + `intl_city` + `intl_postal_code` 가 결과적으로 모두 채워져 있어야 하며, 그렇지 않으면 422 로 거절됩니다. 요청에 없는 필드는 저장된 값으로 판정하므로 `is_default` 만 바꾸는 요청이나 도로명 주소만 교체하는 요청은 그대로 통과합니다. 해외 주소의 저장 컬럼은 `city`/`state`/`postal_code` 이므로 요청에 `intl_city`/`intl_postal_code` 가 없으면 그 저장값이 판정 기준이 됩니다. - > 이 엔드포인트는 확장이 파라미터를 추가할 수 있습니다 (`sirsoft-ecommerce.user_address.update_validation_rules`). **요청 예시** ```http -PUT /api/modules/sirsoft-ecommerce/user/addresses/1 HTTP/1.1 +PUT /api/modules/sirsoft-ecommerce/user/addresses/{id} HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -437,57 +345,11 @@ Content-Type: application/json **응답 필드** (`data` 내부) -_단건 응답: `data` 객체의 필드._ - -| 필드 | 타입 | 실측 예시값 | 용도/설명 | -| --- | --- | --- | --- | -| address | object | `{"id":1,"user_id":"a234c2b1-cde8-437f-b28b-23323be2b98d",…` | 기본주소 (국내) | + **응답 예시** -```http -HTTP/1.1 200 -``` - -```json -{ - "success": true, - "message": "배송지가 수정되었습니다.", - "data": { - "address": { - "id": 1, - "user_id": "a234c2b1-cde8-437f-b28b-23323be2b98d", - "name": "실측 예시값", - "recipient_name": "실측 예시값", - "recipient_phone": "실측 예시값", - "country_code": "KR", - "country_name": { - "ko": "한국", - "en": "South Korea" - }, - "zipcode": "실측 예시값", - "address": "실측 예시값", - "address_detail": "실측 예시값", - "address_line_1": null, - "address_line_2": null, - "city": "실측 예시값", - "state": null, - "postal_code": null, - "is_default": true, - "is_domestic": true, - "is_international": false, - "full_address": "실측 예시값 실측 예시값", - "created_at": "2026-07-08 10:44:49", - "updated_at": "2026-07-08 15:00:36", - "abilities": { - "can_update": true, - "can_delete": false, - "can_set_default": false - } - } - } -} -``` + **에러 응답** @@ -517,7 +379,7 @@ HTTP/1.1 200 **요청 예시** ```http -PATCH /api/modules/sirsoft-ecommerce/user/addresses/1/default HTTP/1.1 +PATCH /api/modules/sirsoft-ecommerce/user/addresses/{id}/default HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -525,23 +387,11 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - - - + **응답 예시** -```http -HTTP/1.1 200 -``` - -```json -{ - "success": true, - "message": "기본 배송지로 설정되었습니다.", - "data": null -} -``` + **에러 응답** diff --git a/modules/_bundled/sirsoft-ecommerce/docs/api/brands.md b/modules/_bundled/sirsoft-ecommerce/docs/api/brands.md index d26992e3..706feaa3 100644 --- a/modules/_bundled/sirsoft-ecommerce/docs/api/brands.md +++ b/modules/_bundled/sirsoft-ecommerce/docs/api/brands.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Brands 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` @@ -32,7 +32,7 @@ | sort | query | string | 아니오 | `name_asc`, `name_desc`, `created_asc`, `created_desc` | 정렬 기준 (필드명, `-` 접두 시 내림차순) | | sort_by | query | string | 아니오 | `name`, `sort_order` | 정렬 기준 필드명 | | sort_order | query | string | 아니오 | `asc`, `desc` | 정렬 방향 (asc 오름차순 / desc 내림차순) | -| locale | query | string | 아니오 | `ko`, `en`, `fr`, `ja` | 로케일 코드 (표시 언어/지역) | +| locale | query | string | 아니오 | `ko`, `en` | 로케일 코드 (표시 언어/지역) | **요청 예시** @@ -49,21 +49,21 @@ _목록 응답: `data.data[]` 배열 항목의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| number | integer | `43` | 목록에서의 순번 (페이지네이션 반영 행 번호 — HasRowNumber 파생) | -| id | integer | `127` | 기본 키 (내부 식별자) | -| name | object | `{"ko":"API 문서 샘플 브랜드","en":"API Doc Sample Brand"}` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | -| localized_name | string | `API 문서 샘플 브랜드` | `name` 의 현재 로케일 해석 값 (다국어 필드를 표시용 문자열로 해석) | -| slug | string | `apidoc-sample-brand` | URL 친화 식별자 (slug) | -| url | string | `apidoc-sample-brand` | SortableMenuItem 표시용 URL (slug 값을 그대로 노출) | +| number | integer | `42` | 목록에서의 순번 (페이지네이션 반영 행 번호 — HasRowNumber 파생) | +| id | integer | `20` | 기본 키 (내부 식별자) | +| name | object | `{"ko":"ASUS","en":"ASUS"}` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | +| localized_name | string | `ASUS` | `name` 의 현재 로케일 해석 값 (다국어 필드를 표시용 문자열로 해석) | +| slug | string | `asus` | URL 친화 식별자 (slug) | +| url | string | `asus` | SortableMenuItem 표시용 URL (slug 값을 그대로 노출) | | website | string | `https://www.asus.com` | 브랜드 공식 웹사이트 URL | -| sort_order | integer | `0` | 표시 정렬 순서 값 (작을수록 우선) | +| sort_order | integer | `20` | 표시 정렬 순서 값 (작을수록 우선) | | is_active | boolean | `true` | active 여부 | | icon | string | `tag` | 아이콘 식별자 (아이콘 클래스/이름) | -| created_at | string | `2026-07-07 14:47:31` | 생성 일시 | -| updated_at | string | `2026-07-07 14:47:31` | 최종 수정 일시 | +| created_at | string | `2026-07-30 23:35:46` | 생성 일시 | +| updated_at | string | `2026-07-30 23:35:46` | 최종 수정 일시 | | creator | array | `[]` | 생성자 정보 객체 (uuid/name/email — creator 관계 파생) | | updater | array | `[]` | 수정자 정보 객체 (id/name — updater 관계 파생, 로드 시에만 포함) | -| products_count | integer | `0` | products 개수 (집계) | +| products_count | integer | `1` | products 개수 (집계) | | abilities | object | `{"can_create":true,"can_update":true,"can_delete":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | **응답 예시** @@ -79,21 +79,46 @@ HTTP/1.1 200 "data": { "data": [ { - "number": 1, - "id": 1, + "number": 42, + "id": 20, "name": { - "ko": "API 문서 샘플 브랜드", - "en": "API Doc Sample Brand" + "ko": "ASUS", + "en": "ASUS" }, - "localized_name": "API 문서 샘플 브랜드", - "slug": "apidoc-sample-brand", - "url": "apidoc-sample-brand", - "website": null, - "sort_order": 0, + "localized_name": "ASUS", + "slug": "asus", + "url": "asus", + "website": "https://www.asus.com", + "sort_order": 20, "is_active": true, "icon": "tag", - "created_at": "2026-07-08 10:44:49", - "updated_at": "2026-07-08 10:44:49", + "created_at": "2026-07-30 23:35:46", + "updated_at": "2026-07-30 23:35:46", + "creator": [], + "updater": [], + "products_count": 1, + "abilities": { + "can_create": true, + "can_update": true, + "can_delete": true + } + }, + { + "number": 41, + "id": 26, + "name": { + "ko": "CJ제일제당", + "en": "CJ CheilJedang" + }, + "localized_name": "CJ제일제당", + "slug": "cj", + "url": "cj", + "website": "https://www.cj.co.kr", + "sort_order": 26, + "is_active": true, + "icon": "tag", + "created_at": "2026-07-30 23:35:46", + "updated_at": "2026-07-30 23:35:46", "creator": [], "updater": [], "products_count": 0, @@ -102,7 +127,8 @@ HTTP/1.1 200 "can_update": true, "can_delete": true } - } + }, + "... (총 42건 중 2건 표시)" ], "abilities": { "can_create": true, @@ -166,7 +192,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -200,7 +226,7 @@ Content-Type: application/json **요청 예시** ```http -DELETE /api/modules/sirsoft-ecommerce/admin/brands/1 HTTP/1.1 +DELETE /api/modules/sirsoft-ecommerce/admin/brands/{brand} HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -208,29 +234,11 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) -_단건 응답: `data` 객체의 필드._ - -| 필드 | 타입 | 실측 예시값 | 용도/설명 | -| --- | --- | --- | --- | -| brand_id | integer | `1` | brand 식별자 (연관 리소스 참조) | -| products_count | integer | `0` | products 개수 (집계) | + **응답 예시** -```http -HTTP/1.1 200 -``` - -```json -{ - "success": true, - "message": "브랜드가 삭제되었습니다.", - "data": { - "brand_id": 1, - "products_count": 0 - } -} -``` + **에러 응답** @@ -267,7 +275,7 @@ HTTP/1.1 200 **요청 예시** ```http -PUT /api/modules/sirsoft-ecommerce/admin/brands/1 HTTP/1.1 +PUT /api/modules/sirsoft-ecommerce/admin/brands/{brand} HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -286,11 +294,11 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** - + **에러 응답** @@ -321,7 +329,7 @@ Content-Type: application/json **요청 예시** ```http -GET /api/modules/sirsoft-ecommerce/admin/brands/1 HTTP/1.1 +GET /api/modules/sirsoft-ecommerce/admin/brands/{id} HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -329,58 +337,11 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) -_단건 응답: `data` 객체의 필드._ - -| 필드 | 타입 | 실측 예시값 | 용도/설명 | -| --- | --- | --- | --- | -| id | integer | `1` | 기본 키 (내부 식별자) | -| name | object | `{"ko":"API 문서 샘플 브랜드","en":"API Doc Sample Brand"}` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | -| localized_name | string | `API 문서 샘플 브랜드` | `name` 의 현재 로케일 해석 값 (다국어 필드를 표시용 문자열로 해석) | -| slug | string | `apidoc-sample-brand` | URL 친화 식별자 (slug) | -| url | string | `apidoc-sample-brand` | SortableMenuItem 표시용 URL (slug 값을 그대로 노출) | -| website | null | `null` | 브랜드 웹사이트 URL | -| sort_order | integer | `0` | 표시 정렬 순서 값 (작을수록 우선) | -| is_active | boolean | `true` | active 여부 | -| icon | string | `tag` | 아이콘 식별자 (아이콘 클래스/이름) | -| created_at | string | `2026-07-08 10:44:49` | 생성 일시 | -| updated_at | string | `2026-07-08 10:44:49` | 최종 수정 일시 | -| products_count | integer | `0` | products 개수 (집계) | -| abilities | object | `{"can_create":true,"can_update":true,"can_delete":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | + **응답 예시** -```http -HTTP/1.1 200 -``` - -```json -{ - "success": true, - "message": "브랜드 정보를 조회했습니다.", - "data": { - "id": 1, - "name": { - "ko": "API 문서 샘플 브랜드", - "en": "API Doc Sample Brand" - }, - "localized_name": "API 문서 샘플 브랜드", - "slug": "apidoc-sample-brand", - "url": "apidoc-sample-brand", - "website": null, - "sort_order": 0, - "is_active": true, - "icon": "tag", - "created_at": "2026-07-08 10:44:49", - "updated_at": "2026-07-08 10:44:49", - "products_count": 0, - "abilities": { - "can_create": true, - "can_update": true, - "can_delete": true - } - } -} -``` + **에러 응답** @@ -410,7 +371,7 @@ HTTP/1.1 200 **요청 예시** ```http -PATCH /api/modules/sirsoft-ecommerce/admin/brands/1/toggle-status HTTP/1.1 +PATCH /api/modules/sirsoft-ecommerce/admin/brands/{id}/toggle-status HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -418,58 +379,11 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) -_단건 응답: `data` 객체의 필드._ - -| 필드 | 타입 | 실측 예시값 | 용도/설명 | -| --- | --- | --- | --- | -| id | integer | `1` | 기본 키 (내부 식별자) | -| name | object | `{"ko":"API 문서 샘플 브랜드","en":"API Doc Sample Brand"}` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | -| localized_name | string | `API 문서 샘플 브랜드` | `name` 의 현재 로케일 해석 값 (다국어 필드를 표시용 문자열로 해석) | -| slug | string | `apidoc-sample-brand` | URL 친화 식별자 (slug) | -| url | string | `apidoc-sample-brand` | SortableMenuItem 표시용 URL (slug 값을 그대로 노출) | -| website | null | `null` | 브랜드 웹사이트 URL | -| sort_order | integer | `0` | 표시 정렬 순서 값 (작을수록 우선) | -| is_active | boolean | `false` | active 여부 | -| icon | string | `tag` | 아이콘 식별자 (아이콘 클래스/이름) | -| created_at | string | `2026-07-08 10:44:49` | 생성 일시 | -| updated_at | string | `2026-07-08 15:00:16` | 최종 수정 일시 | -| products_count | integer | `0` | products 개수 (집계) | -| abilities | object | `{"can_create":true,"can_update":true,"can_delete":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | + **응답 예시** -```http -HTTP/1.1 200 -``` - -```json -{ - "success": true, - "message": "sirsoft-ecommerce::messages.brands.status_changed", - "data": { - "id": 1, - "name": { - "ko": "API 문서 샘플 브랜드", - "en": "API Doc Sample Brand" - }, - "localized_name": "API 문서 샘플 브랜드", - "slug": "apidoc-sample-brand", - "url": "apidoc-sample-brand", - "website": null, - "sort_order": 0, - "is_active": false, - "icon": "tag", - "created_at": "2026-07-08 10:44:49", - "updated_at": "2026-07-08 15:00:16", - "products_count": 0, - "abilities": { - "can_create": true, - "can_update": true, - "can_delete": true - } - } -} -``` + **에러 응답** diff --git a/modules/_bundled/sirsoft-ecommerce/docs/api/cart.md b/modules/_bundled/sirsoft-ecommerce/docs/api/cart.md index 6a39b6e7..3c28f4d5 100644 --- a/modules/_bundled/sirsoft-ecommerce/docs/api/cart.md +++ b/modules/_bundled/sirsoft-ecommerce/docs/api/cart.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Cart 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` @@ -42,7 +42,7 @@ Authorization: Bearer {YOUR_TOKEN} (optional.sanctum: 비회원은 헤더 생 **응답 필드** (`data` 내부) - + **응답 예시** @@ -88,10 +88,10 @@ _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| items | array | `[{"id":962,"product_id":201,"product_option_id":1086,"qua…` | 장바구니 라인 아이템 목록 (상품·옵션·수량 등 — CartItemResource 파생) | -| item_ids | array | `[962]` | item 식별자 배열 (연관 리소스 참조) | -| item_count | integer | `1` | item 개수 (집계) | -| calculation | object | `{"items":[{"product_id":201,"product_option_id":1086,"pro…` | 선택 아이템 기준 금액 계산 결과 (상품 소계·할인·배송비 등 — OrderCalculationResult 파생) | +| items | array | `[]` | 장바구니 라인 아이템 목록 (상품·옵션·수량 등 — CartItemResource 파생) | +| item_ids | array | `[]` | item 식별자 배열 (연관 리소스 참조) | +| item_count | integer | `0` | item 개수 (집계) | +| calculation | object | `{"items":[],"summary":{"subtotal":0,"subtotal_formatted":…` | 선택 아이템 기준 금액 계산 결과 (상품 소계·할인·배송비 등 — OrderCalculationResult 파생) | | has_unshippable_items | boolean | `false` | unshippable items 여부 | | selected_shipping_country | string | `KR` | 배송비 계산에 적용된 배송 국가 코드 (ResolveShippingCountry 해석 결과) | @@ -113,39 +113,40 @@ HTTP/1.1 200 "items": [], "summary": { "subtotal": 0, - "subtotal_formatted": "0원", + "subtotal_formatted": "¥0", "product_coupon_discount": 0, - "product_coupon_discount_formatted": "0원", + "product_coupon_discount_formatted": "¥0", "code_discount": 0, - "code_discount_formatted": "0원", + "code_discount_formatted": "¥0", "order_coupon_discount": 0, - "order_coupon_discount_formatted": "0원", + "order_coupon_discount_formatted": "¥0", "total_coupon_discount": 0, - "total_coupon_discount_formatted": "0원", + "total_coupon_discount_formatted": "¥0", "total_discount": 0, - "discount_formatted": "0원", + "discount_formatted": "¥0", "base_shipping_total": 0, "extra_shipping_total": 0, "total_shipping": 0, "total_shipping_fee": 0, - "shipping_fee_formatted": "0원", + "shipping_fee_formatted": "¥0", "shipping_discount": 0, - "shipping_discount_formatted": "0원", + "shipping_discount_formatted": "¥0", "taxable_amount": 0, "tax_free_amount": 0, + "vat_amount": 0, "points_earning": 0, "total_mileage": 0, "mileage_formatted": "0P", "points_used": 0, - "points_used_formatted": "0원", + "points_used_formatted": "¥0", "payment_amount": 0, - "payment_amount_formatted": "0원", + "payment_amount_formatted": "¥0", "final_amount": 0, - "final_amount_formatted": "0원", + "final_amount_formatted": "¥0", "coupon_discount": 0, - "coupon_discount_formatted": "0원", + "coupon_discount_formatted": "¥0", "order_discount": 0, - "order_discount_formatted": "0원" + "order_discount_formatted": "¥0" }, "promotions": { "coupon_issue_ids": [], @@ -215,7 +216,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -313,7 +314,7 @@ _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| count | integer | `1` | 장바구니에 담긴 아이템 개수 (`selected_ids` 지정 시 해당 항목만 집계) | +| count | integer | `0` | 장바구니에 담긴 아이템 개수 (`selected_ids` 지정 시 해당 항목만 집계) | **응답 예시** @@ -367,7 +368,7 @@ _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| cart_key | string | `ck_QBQT80EINHHXLO8HYHntMpmUZrPujPka` | 비회원 장바구니 식별 키 (`ck_` + 32자 영숫자. 이후 요청의 `X-Cart-Key` 헤더에 실어 자신의 장바구니를 식별) | +| cart_key | string | `ck_F2M15LD5Wu9UQsOwvOVrfp1UeECn3q6f` | 비회원 장바구니 식별 키 (`ck_` + 32자 영숫자. 이후 요청의 `X-Cart-Key` 헤더에 실어 자신의 장바구니를 식별) | **응답 예시** @@ -380,7 +381,7 @@ HTTP/1.1 200 "success": true, "message": "장바구니 키가 발급되었습니다.", "data": { - "cart_key": "ck_QBQT80EINHHXLO8HYHntMpmUZrPujPka" + "cart_key": "ck_F2M15LD5Wu9UQsOwvOVrfp1UeECn3q6f" } } ``` @@ -415,41 +416,11 @@ Authorization: Bearer {YOUR_TOKEN} (optional.sanctum: 비회원은 헤더 생 **응답 필드** (`data` 내부) -| 필드 | 타입 | 예시값 | 용도/설명 | -| --- | --- | --- | --- | -| merged_count | integer | `2` | 회원 장바구니로 옮겨진 비회원 아이템 수 | -| adjustments | array | `[]` | 병합 과정에서 수량이 상한으로 줄어든 항목 목록 (조정이 없으면 빈 배열) | -| adjustments[].product_id | integer | `12` | 조정된 상품 ID | -| adjustments[].product_option_id | integer | `34` | 조정된 상품 옵션 ID | -| adjustments[].requested | integer | `10` | 병합 시 합산된 수량 | -| adjustments[].applied | integer | `5` | 상한 적용 후 실제 저장된 수량 | - -> 병합은 로그인 흐름에서 자동 수행되므로 수량 상한을 넘겨도 예외를 던지지 않고 상한까지 줄입니다. -> 줄어든 사실을 사용자에게 알릴 수 있도록 `adjustments` 를 확인해 안내하세요. + **응답 예시** -```http -HTTP/1.1 200 -``` - -```json -{ - "success": true, - "message": "장바구니를 병합했습니다.", - "data": { - "merged_count": 2, - "adjustments": [ - { - "product_id": 12, - "product_option_id": 34, - "requested": 10, - "applied": 5 - } - ] - } -} -``` + **에러 응답** @@ -524,39 +495,40 @@ HTTP/1.1 200 "items": [], "summary": { "subtotal": 0, - "subtotal_formatted": "0원", + "subtotal_formatted": "¥0", "product_coupon_discount": 0, - "product_coupon_discount_formatted": "0원", + "product_coupon_discount_formatted": "¥0", "code_discount": 0, - "code_discount_formatted": "0원", + "code_discount_formatted": "¥0", "order_coupon_discount": 0, - "order_coupon_discount_formatted": "0원", + "order_coupon_discount_formatted": "¥0", "total_coupon_discount": 0, - "total_coupon_discount_formatted": "0원", + "total_coupon_discount_formatted": "¥0", "total_discount": 0, - "discount_formatted": "0원", + "discount_formatted": "¥0", "base_shipping_total": 0, "extra_shipping_total": 0, "total_shipping": 0, "total_shipping_fee": 0, - "shipping_fee_formatted": "0원", + "shipping_fee_formatted": "¥0", "shipping_discount": 0, - "shipping_discount_formatted": "0원", + "shipping_discount_formatted": "¥0", "taxable_amount": 0, "tax_free_amount": 0, + "vat_amount": 0, "points_earning": 0, "total_mileage": 0, "mileage_formatted": "0P", "points_used": 0, - "points_used_formatted": "0원", + "points_used_formatted": "¥0", "payment_amount": 0, - "payment_amount_formatted": "0원", + "payment_amount_formatted": "¥0", "final_amount": 0, - "final_amount_formatted": "0원", + "final_amount_formatted": "¥0", "coupon_discount": 0, - "coupon_discount_formatted": "0원", + "coupon_discount_formatted": "¥0", "order_discount": 0, - "order_discount_formatted": "0원" + "order_discount_formatted": "¥0" }, "promotions": { "coupon_issue_ids": [], @@ -615,7 +587,7 @@ Authorization: Bearer {YOUR_TOKEN} (optional.sanctum: 비회원은 헤더 생 **응답 필드** (`data` 내부) - + **응답 예시** @@ -644,7 +616,7 @@ Authorization: Bearer {YOUR_TOKEN} (optional.sanctum: 비회원은 헤더 생 | --- | --- | --- | --- | --- | --- | | id | path | string | 예 | — | 대상 리소스의 식별자 | | product_option_id | body | integer | 예 | — | product option 식별자 | -| quantity | body | integer | 예 | min 1, max: 장바구니 수량 상한 설정 | 변경할 구매 수량 (1 ~ 관리자 설정 상한, 기본 99) | +| quantity | body | integer | 예 | min 1, max 99 | 변경할 구매 수량 (1 ~ 관리자 설정 상한, 기본 99) | | additional_option_selections | body | array | 아니오 | — | 추가 옵션 재선택 목록 (항목별 additional_option_id/value_id, 직접입력 custom_text — 미전달 시 기존 선택 유지) | > 이 엔드포인트는 확장이 파라미터를 추가할 수 있습니다 (`sirsoft-ecommerce.cart.change_option_validation_rules`). @@ -669,7 +641,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -698,7 +670,7 @@ Content-Type: application/json | 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | | --- | --- | --- | --- | --- | --- | | id | path | string | 예 | — | 대상 리소스의 식별자 | -| quantity | body | integer | 예 | min 1, max: 장바구니 수량 상한 설정 | 변경할 구매 수량 (1 ~ 관리자 설정 상한, 기본 99) | +| quantity | body | integer | 예 | min 1, max 99 | 변경할 구매 수량 (1 ~ 관리자 설정 상한, 기본 99) | > 이 엔드포인트는 확장이 파라미터를 추가할 수 있습니다 (`sirsoft-ecommerce.cart.update_quantity_validation_rules`). @@ -718,7 +690,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** diff --git a/modules/_bundled/sirsoft-ecommerce/docs/api/categories.md b/modules/_bundled/sirsoft-ecommerce/docs/api/categories.md index 49b129c8..c5357a83 100644 --- a/modules/_bundled/sirsoft-ecommerce/docs/api/categories.md +++ b/modules/_bundled/sirsoft-ecommerce/docs/api/categories.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Categories 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` @@ -49,12 +49,12 @@ _목록 응답: `data.data[]` 배열 항목의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| id | integer | `87` | 기본 키 (내부 식별자) | +| id | integer | `29` | 기본 키 (내부 식별자) | | name | object | `{"ko":"의류","en":"Clothing"}` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | | description | object | `{"ko":"다양한 스타일의 의류 제품","en":"Various styles of clothing p…` | 설명 (다국어 필드는 로케일별 값 객체) | | localized_name | string | `의류` | `name` 의 현재 로케일 해석 값 (다국어 필드를 표시용 문자열로 해석) | | parent_id | null | `null` | parent 식별자 (연관 리소스 참조) | -| path | string | `87` | 조상부터 자기 자신까지의 ID를 `/`로 이은 materialized path (조상 조회·하위 일괄 선택에 사용) | +| path | string | `29` | 조상부터 자기 자신까지의 ID를 `/`로 이은 materialized path (조상 조회·하위 일괄 선택에 사용) | | depth | integer | `0` | 계층 트리에서의 깊이 (0 = 최상위, 하위로 갈수록 증가) | | sort_order | integer | `0` | 표시 정렬 순서 값 (작을수록 우선) | | is_active | boolean | `true` | active 여부 | @@ -63,10 +63,10 @@ _목록 응답: `data.data[]` 배열 항목의 필드._ | icon | string | `folder` | 아이콘 식별자 (아이콘 클래스/이름) | | meta_title | null | `null` | SEO 메타 제목 (검색엔진/소셜 공유 표시 제목, 미설정 시 null) | | meta_description | null | `null` | SEO 메타 설명 (검색엔진/소셜 공유 표시 요약, 미설정 시 null) | -| created_at | string | `2026-06-15 02:24:00` | 생성 일시 | -| updated_at | string | `2026-06-15 02:24:00` | 최종 수정 일시 | +| created_at | string | `2026-07-30 14:35:46` | 생성 일시 | +| updated_at | string | `2026-07-30 14:35:46` | 최종 수정 일시 | | images | array | `[]` | 카테고리 이미지 배열 (images 관계 로드 시 — id/hash/download_url/alt_text 등) | -| products_count | integer | `22` | products 개수 (집계) | +| products_count | integer | `24` | products 개수 (집계) | | children_count | integer | `0` | children 개수 (집계) | | abilities | object | `{"can_create":true,"can_update":true,"can_delete":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | @@ -83,34 +83,70 @@ HTTP/1.1 200 "data": { "data": [ { - "id": 1, + "id": 29, "name": { - "ko": "API 문서 샘플 카테고리", - "en": "API Doc Sample Category" + "ko": "의류", + "en": "Clothing" }, - "description": null, - "localized_name": "API 문서 샘플 카테고리", + "description": { + "ko": "다양한 스타일의 의류 제품", + "en": "Various styles of clothing products" + }, + "localized_name": "의류", "parent_id": null, - "path": "0", + "path": "29", "depth": 0, "sort_order": 0, "is_active": true, - "slug": "apidoc-sample-category", - "url": "apidoc-sample-category", + "slug": "clothing", + "url": "clothing", "icon": "folder", "meta_title": null, "meta_description": null, - "created_at": "2026-07-08 01:44:49", - "updated_at": "2026-07-08 01:44:49", + "created_at": "2026-07-30 14:35:46", + "updated_at": "2026-07-30 14:35:46", "images": [], - "products_count": 0, + "products_count": 24, "children_count": 0, "abilities": { "can_create": true, "can_update": true, "can_delete": true } - } + }, + { + "id": 38, + "name": { + "ko": "전자기기", + "en": "Electronics" + }, + "description": { + "ko": "최신 전자제품과 기기", + "en": "Latest electronic products and devices" + }, + "localized_name": "전자기기", + "parent_id": null, + "path": "38", + "depth": 0, + "sort_order": 1, + "is_active": true, + "slug": "electronics", + "url": "electronics", + "icon": "folder", + "meta_title": null, + "meta_description": null, + "created_at": "2026-07-30 14:35:47", + "updated_at": "2026-07-30 14:35:47", + "images": [], + "products_count": 15, + "children_count": 0, + "abilities": { + "can_create": true, + "can_update": true, + "can_delete": true + } + }, + "... (총 8건 중 2건 표시)" ], "abilities": { "can_create": true, @@ -182,7 +218,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -249,7 +285,7 @@ Content-Disposition: form-data; name="alt_text" **응답 필드** (`data` 내부) - + **응답 예시** @@ -300,7 +336,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -334,7 +370,7 @@ Content-Type: application/json **요청 예시** ```http -DELETE /api/modules/sirsoft-ecommerce/admin/categories/images/1 HTTP/1.1 +DELETE /api/modules/sirsoft-ecommerce/admin/categories/images/{id} HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -342,11 +378,11 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - + **응답 예시** - + **에러 응답** @@ -397,7 +433,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -441,12 +477,12 @@ _목록 응답: `data.data[]` 배열 항목의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| id | integer | `87` | 기본 키 (내부 식별자) | +| id | integer | `29` | 기본 키 (내부 식별자) | | name | object | `{"ko":"의류","en":"Clothing"}` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | | description | object | `{"ko":"다양한 스타일의 의류 제품","en":"Various styles of clothing p…` | 설명 (다국어 필드는 로케일별 값 객체) | | localized_name | string | `의류` | `name` 의 현재 로케일 해석 값 (다국어 필드를 표시용 문자열로 해석) | | parent_id | null | `null` | parent 식별자 (연관 리소스 참조) | -| path | string | `87` | 조상부터 자기 자신까지의 ID를 `/`로 이은 materialized path (조상 조회·하위 일괄 선택에 사용) | +| path | string | `29` | 조상부터 자기 자신까지의 ID를 `/`로 이은 materialized path (조상 조회·하위 일괄 선택에 사용) | | depth | integer | `0` | 계층 트리에서의 깊이 (0 = 최상위, 하위로 갈수록 증가) | | sort_order | integer | `0` | 표시 정렬 순서 값 (작을수록 우선) | | is_active | boolean | `true` | active 여부 | @@ -455,12 +491,12 @@ _목록 응답: `data.data[]` 배열 항목의 필드._ | icon | string | `folder` | 아이콘 식별자 (아이콘 클래스/이름) | | meta_title | null | `null` | SEO 메타 제목 (검색엔진/소셜 공유 표시 제목, 미설정 시 null) | | meta_description | null | `null` | SEO 메타 설명 (검색엔진/소셜 공유 표시 요약, 미설정 시 null) | -| created_at | string | `2026-06-15 02:24:00` | 생성 일시 | -| updated_at | string | `2026-06-15 02:24:00` | 최종 수정 일시 | +| created_at | string | `2026-07-30 14:35:46` | 생성 일시 | +| updated_at | string | `2026-07-30 14:35:46` | 최종 수정 일시 | | parent | null | `null` | 상위 항목 객체 (parent 관계 파생) | -| children | array | `[{"id":88,"name":{"ko":"남성","en":"Men"},"description":{"k…` | 하위 항목 배열 (계층 트리 — children 관계 파생) | +| children | array | `[{"id":30,"name":{"ko":"남성","en":"Men"},"description":{"k…` | 하위 항목 배열 (계층 트리 — children 관계 파생) | | images | array | `[]` | 카테고리 이미지 배열 (images 관계 로드 시 — id/hash/download_url/alt_text 등) | -| products_count | integer | `22` | products 개수 (집계) | +| products_count | integer | `24` | products 개수 (집계) | | children_count | integer | `2` | children 개수 (집계) | | abilities | object | `{"can_create":true,"can_update":true,"can_delete":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | @@ -477,36 +513,34 @@ HTTP/1.1 200 "data": { "data": [ { - "id": 1, + "id": 29, "name": { - "ko": "API 문서 샘플 카테고리", - "en": "API Doc Sample Category" + "ko": "의류", + "en": "Clothing" }, - "description": null, - "localized_name": "API 문서 샘플 카테고리", + "description": { + "ko": "다양한 스타일의 의류 제품", + "en": "Various styles of clothing products" + }, + "localized_name": "의류", "parent_id": null, - "path": "0", - "depth": 0, - "sort_order": 0, - "is_active": true, - "slug": "apidoc-sample-category", - "url": "apidoc-sample-category", - "icon": "folder", - "meta_title": null, - "meta_description": null, - "created_at": "2026-07-08 01:44:49", - "updated_at": "2026-07-08 01:44:49", - "parent": null, - "children": [], - "images": [], - "products_count": 0, - "children_count": 0, - "abilities": { - "can_create": true, - "can_update": true, - "can_delete": true - } - } + "...": "(17개 키 생략, 총 22개)" + }, + { + "id": 38, + "name": { + "ko": "전자기기", + "en": "Electronics" + }, + "description": { + "ko": "최신 전자제품과 기기", + "en": "Latest electronic products and devices" + }, + "localized_name": "전자기기", + "parent_id": null, + "...": "(17개 키 생략, 총 22개)" + }, + "... (총 8건 중 2건 표시)" ], "abilities": { "can_create": true, @@ -578,7 +612,7 @@ Content-Disposition: form-data; name="alt_text" **응답 필드** (`data` 내부) - + **응답 예시** @@ -613,7 +647,7 @@ Content-Disposition: form-data; name="alt_text" **요청 예시** ```http -DELETE /api/modules/sirsoft-ecommerce/admin/categories/1 HTTP/1.1 +DELETE /api/modules/sirsoft-ecommerce/admin/categories/{category} HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -621,27 +655,11 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) -_단건 응답: `data` 객체의 필드._ - -| 필드 | 타입 | 실측 예시값 | 용도/설명 | -| --- | --- | --- | --- | -| category_id | integer | `1` | category 식별자 (연관 리소스 참조) | + **응답 예시** -```http -HTTP/1.1 200 -``` - -```json -{ - "success": true, - "message": "카테고리가 삭제되었습니다.", - "data": { - "category_id": 1 - } -} -``` + **에러 응답** @@ -681,7 +699,7 @@ HTTP/1.1 200 **요청 예시** ```http -PUT /api/modules/sirsoft-ecommerce/admin/categories/1 HTTP/1.1 +PUT /api/modules/sirsoft-ecommerce/admin/categories/{category} HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -705,11 +723,11 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** - + **에러 응답** @@ -740,7 +758,7 @@ Content-Type: application/json **요청 예시** ```http -GET /api/modules/sirsoft-ecommerce/admin/categories/1 HTTP/1.1 +GET /api/modules/sirsoft-ecommerce/admin/categories/{id} HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -748,76 +766,11 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) -_단건 응답: `data` 객체의 필드._ - -| 필드 | 타입 | 실측 예시값 | 용도/설명 | -| --- | --- | --- | --- | -| id | integer | `1` | 기본 키 (내부 식별자) | -| name | object | `{"ko":"API 문서 샘플 카테고리","en":"API Doc Sample Category"}` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | -| description | null | `null` | 설명 (다국어 필드는 로케일별 값 객체) | -| localized_name | string | `API 문서 샘플 카테고리` | `name` 의 현재 로케일 해석 값 (다국어 필드를 표시용 문자열로 해석) | -| parent_id | null | `null` | parent 식별자 (연관 리소스 참조) | -| path | string | `0` | Materialized Path: 1/5/23 | -| depth | integer | `0` | 계층 트리에서의 깊이 (0 = 최상위, 하위로 갈수록 증가) | -| sort_order | integer | `0` | 표시 정렬 순서 값 (작을수록 우선) | -| is_active | boolean | `true` | active 여부 | -| slug | string | `apidoc-sample-category` | URL 친화 식별자 (slug) | -| url | string | `apidoc-sample-category` | SortableMenuItem 표시용 URL (slug 값을 그대로 사용) | -| icon | string | `folder` | 아이콘 식별자 (아이콘 클래스/이름) | -| meta_title | null | `null` | SEO 제목 (다국어 JSON) | -| meta_description | null | `null` | SEO 설명 (다국어 JSON) | -| created_at | string | `2026-07-08 01:44:49` | 생성 일시 | -| updated_at | string | `2026-07-08 01:44:49` | 최종 수정 일시 | -| parent | null | `null` | 상위 항목 객체 (parent 관계 파생) | -| children | array | `[]` | 하위 항목 배열 (계층 트리 — children 관계 파생) | -| images | array | `[]` | 카테고리 이미지 배열 (images 관계 로드 시 — id/hash/download_url/alt_text 등) | -| products_count | integer | `0` | products 개수 (집계) | -| children_count | integer | `0` | children 개수 (집계) | -| abilities | object | `{"can_create":true,"can_update":true,"can_delete":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | + **응답 예시** -```http -HTTP/1.1 200 -``` - -```json -{ - "success": true, - "message": "카테고리 정보를 조회했습니다.", - "data": { - "id": 1, - "name": { - "ko": "API 문서 샘플 카테고리", - "en": "API Doc Sample Category" - }, - "description": null, - "localized_name": "API 문서 샘플 카테고리", - "parent_id": null, - "path": "0", - "depth": 0, - "sort_order": 0, - "is_active": true, - "slug": "apidoc-sample-category", - "url": "apidoc-sample-category", - "icon": "folder", - "meta_title": null, - "meta_description": null, - "created_at": "2026-07-08 01:44:49", - "updated_at": "2026-07-08 01:44:49", - "parent": null, - "children": [], - "images": [], - "products_count": 0, - "children_count": 0, - "abilities": { - "can_create": true, - "can_update": true, - "can_delete": true - } - } -} -``` + **에러 응답** @@ -847,7 +800,7 @@ HTTP/1.1 200 **요청 예시** ```http -PATCH /api/modules/sirsoft-ecommerce/admin/categories/1/toggle-status HTTP/1.1 +PATCH /api/modules/sirsoft-ecommerce/admin/categories/{id}/toggle-status HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -855,72 +808,11 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) -_단건 응답: `data` 객체의 필드._ - -| 필드 | 타입 | 실측 예시값 | 용도/설명 | -| --- | --- | --- | --- | -| id | integer | `1` | 기본 키 (내부 식별자) | -| name | object | `{"ko":"API 문서 샘플 카테고리","en":"API Doc Sample Category"}` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | -| description | null | `null` | 설명 (다국어 필드는 로케일별 값 객체) | -| localized_name | string | `API 문서 샘플 카테고리` | `name` 의 현재 로케일 해석 값 (다국어 필드를 표시용 문자열로 해석) | -| parent_id | null | `null` | parent 식별자 (연관 리소스 참조) | -| path | string | `0` | Materialized Path: 1/5/23 | -| depth | integer | `0` | 계층 트리에서의 깊이 (0 = 최상위, 하위로 갈수록 증가) | -| sort_order | integer | `0` | 표시 정렬 순서 값 (작을수록 우선) | -| is_active | boolean | `false` | active 여부 | -| slug | string | `apidoc-sample-category` | URL 친화 식별자 (slug) | -| url | string | `apidoc-sample-category` | SortableMenuItem 표시용 URL (slug 값을 그대로 사용) | -| icon | string | `folder` | 아이콘 식별자 (아이콘 클래스/이름) | -| meta_title | null | `null` | SEO 제목 (다국어 JSON) | -| meta_description | null | `null` | SEO 설명 (다국어 JSON) | -| created_at | string | `2026-07-08 01:44:49` | 생성 일시 | -| updated_at | string | `2026-07-08 06:00:17` | 최종 수정 일시 | -| images | array | `[]` | 카테고리 이미지 배열 (images 관계 로드 시 — id/hash/download_url/alt_text 등) | -| products_count | integer | `0` | products 개수 (집계) | -| children_count | integer | `0` | children 개수 (집계) | -| abilities | object | `{"can_create":true,"can_update":true,"can_delete":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | + **응답 예시** -```http -HTTP/1.1 200 -``` - -```json -{ - "success": true, - "message": "sirsoft-ecommerce::messages.categories.status_changed", - "data": { - "id": 1, - "name": { - "ko": "API 문서 샘플 카테고리", - "en": "API Doc Sample Category" - }, - "description": null, - "localized_name": "API 문서 샘플 카테고리", - "parent_id": null, - "path": "0", - "depth": 0, - "sort_order": 0, - "is_active": false, - "slug": "apidoc-sample-category", - "url": "apidoc-sample-category", - "icon": "folder", - "meta_title": null, - "meta_description": null, - "created_at": "2026-07-08 01:44:49", - "updated_at": "2026-07-08 06:00:17", - "images": [], - "products_count": 0, - "children_count": 0, - "abilities": { - "can_create": true, - "can_update": true, - "can_delete": true - } - } -} -``` + **에러 응답** @@ -957,14 +849,14 @@ Accept: application/json | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| id | integer | `87` | 기본 키 (내부 식별자) | +| id | integer | `29` | 기본 키 (내부 식별자) | | name | object | `{"ko":"의류","en":"Clothing"}` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | | name_localized | string | `의류` | `name` 의 현재 로케일 해석 값 (다국어 필드를 표시용 문자열로 해석) | | slug | string | `clothing` | URL 친화 식별자 (slug) | | depth | integer | `0` | 계층 트리에서의 깊이 (0 = 최상위, 하위로 갈수록 증가) | | parent_id | null | `null` | parent 식별자 (연관 리소스 참조) | -| products_count | integer | `22` | products 개수 (집계) | -| children | array | `[{"id":88,"name":{"ko":"남성","en":"Men"},"name_localized":…` | 하위 항목 배열 (계층 트리 — children 관계 파생) | +| products_count | integer | `24` | products 개수 (집계) | +| children | array | `[{"id":30,"name":{"ko":"남성","en":"Men"},"name_localized":…` | 하위 항목 배열 (계층 트리 — children 관계 파생) | **응답 예시** @@ -978,18 +870,61 @@ HTTP/1.1 200 "message": "카테고리 목록을 조회했습니다.", "data": [ { - "id": 1, + "id": 29, "name": { - "ko": "API 문서 샘플 카테고리", - "en": "API Doc Sample Category" + "ko": "의류", + "en": "Clothing" }, - "name_localized": "API 문서 샘플 카테고리", - "slug": "apidoc-sample-category", + "name_localized": "의류", + "slug": "clothing", "depth": 0, - "parent_id": null, - "products_count": 0, - "children": [] - } + "...": "(3개 키 생략, 총 8개)" + }, + { + "id": 38, + "name": { + "ko": "전자기기", + "en": "Electronics" + }, + "name_localized": "전자기기", + "slug": "electronics", + "depth": 0, + "...": "(3개 키 생략, 총 8개)" + }, + { + "id": 46, + "name": { + "ko": "가구", + "en": "Furniture" + }, + "name_localized": "가구", + "slug": "furniture", + "depth": 0, + "...": "(3개 키 생략, 총 8개)" + }, + { + "id": 51, + "name": { + "ko": "식품", + "en": "Food" + }, + "name_localized": "식품", + "slug": "food", + "depth": 0, + "...": "(3개 키 생략, 총 8개)" + }, + { + "id": 56, + "name": { + "ko": "스포츠", + "en": "Sports" + }, + "name_localized": "스포츠", + "slug": "sports", + "depth": 0, + "...": "(3개 키 생략, 총 8개)" + }, + "... (총 8건 중 5건 표시)" ] } ``` @@ -1018,65 +953,18 @@ _대표 에러 없음 (공개 조회). **응답 예시** -```http -HTTP/1.1 200 -``` - -```json -{ - "success": true, - "message": "sirsoft-ecommerce::messages.categories.fetch_success", - "data": { - "id": 1, - "name": { - "ko": "API 문서 샘플 카테고리", - "en": "API Doc Sample Category" - }, - "name_localized": "API 문서 샘플 카테고리", - "description": null, - "description_localized": null, - "slug": "apidoc-sample-category", - "depth": 0, - "parent_id": null, - "products_count": 0, - "breadcrumb": [ - { - "id": 1, - "name": "API 문서 샘플 카테고리", - "slug": "apidoc-sample-category" - } - ], - "images": [], - "children": [] - } -} -``` + **에러 응답** diff --git a/modules/_bundled/sirsoft-ecommerce/docs/api/category-image.md b/modules/_bundled/sirsoft-ecommerce/docs/api/category-image.md index 71296329..80241562 100644 --- a/modules/_bundled/sirsoft-ecommerce/docs/api/category-image.md +++ b/modules/_bundled/sirsoft-ecommerce/docs/api/category-image.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Category Image 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` diff --git a/modules/_bundled/sirsoft-ecommerce/docs/api/checkout.md b/modules/_bundled/sirsoft-ecommerce/docs/api/checkout.md index f04e1189..e9646d16 100644 --- a/modules/_bundled/sirsoft-ecommerce/docs/api/checkout.md +++ b/modules/_bundled/sirsoft-ecommerce/docs/api/checkout.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Checkout 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` @@ -38,7 +38,7 @@ Authorization: Bearer {YOUR_TOKEN} (optional.sanctum: 비회원은 헤더 생 **응답 필드** (`data` 내부) - + **응답 예시** @@ -141,7 +141,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -204,7 +204,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -242,7 +242,7 @@ Authorization: Bearer {YOUR_TOKEN} (optional.sanctum: 비회원은 헤더 생 **응답 필드** (`data` 내부) - + **응답 예시** diff --git a/modules/_bundled/sirsoft-ecommerce/docs/api/claim-reasons.md b/modules/_bundled/sirsoft-ecommerce/docs/api/claim-reasons.md index 59f78399..625ee9cb 100644 --- a/modules/_bundled/sirsoft-ecommerce/docs/api/claim-reasons.md +++ b/modules/_bundled/sirsoft-ecommerce/docs/api/claim-reasons.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Claim Reasons 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` @@ -25,12 +25,17 @@ **요청 파라미터** -_요청 파라미터 없음._ +| 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | +| --- | --- | --- | --- | --- | --- | +| type | query | string | 아니오 | — | 유형 필터 (해당 유형의 항목만 조회) | +| fault_type | query | string | 아니오 | — | | +| is_active | query | boolean | 아니오 | — | 활성 여부 (true 활성 / false 비활성) | +| search | query | string | 아니오 | max 255 | 검색어 (지정한 검색 대상 필드에서 부분 일치) | **요청 예시** ```http -GET /api/modules/sirsoft-ecommerce/admin/claim-reasons HTTP/1.1 +GET /api/modules/sirsoft-ecommerce/admin/claim-reasons?type=%EC%98%88%EC%8B%9C%EA%B0%92&fault_type=%EC%98%88%EC%8B%9C%EA%B0%92&is_active=1&search=%EC%98%88%EC%8B%9C%EA%B0%92 HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -42,19 +47,19 @@ _목록 응답: `data.data[]` 배열 항목의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| number | integer | `8` | 목록에서의 순번 (페이지네이션 반영 행 번호 — HasRowNumber 파생) | +| number | integer | `7` | 목록에서의 순번 (페이지네이션 반영 행 번호 — HasRowNumber 파생) | | id | integer | `1` | 기본 키 (내부 식별자) | | type | string | `refund` | 클레임 사유 유형 (ClaimReasonTypeEnum — `refund`(환불/취소)) | | code | string | `order_mistake` | 사유 식별 코드 (같은 type 내 고유, 영문 소문자/숫자/`_`) | -| name | object | `{"ko":"주문 실수","en":"Order Mistake","ja":"注文ミス"}` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | +| name | object | `{"ko":"주문 실수","en":"Order Mistake"}` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | | localized_name | string | `주문 실수` | `name` 의 현재 로케일 해석 값 (다국어 필드를 표시용 문자열로 해석) | | fault_type | string | `customer` | 귀책 구분 (ClaimReasonFaultTypeEnum — `customer`(고객)/`seller`(판매자)/`carrier`(배송사)) | | fault_type_label | string | `고객 귀책` | `fault_type` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | | is_user_selectable | boolean | `true` | user selectable 여부 | | is_active | boolean | `true` | active 여부 | | sort_order | integer | `0` | 표시 정렬 순서 값 (작을수록 우선) | -| created_at | string | `2026-05-27 15:20:43` | 생성 일시 | -| updated_at | string | `2026-06-27 00:49:51` | 최종 수정 일시 | +| created_at | string | `2026-07-30 17:36:00` | 생성 일시 | +| updated_at | string | `2026-07-30 17:36:00` | 최종 수정 일시 | | creator | array | `[]` | 생성자 정보 객체 (uuid/name/email — creator 관계 파생) | | updater | array | `[]` | 최종 수정자 정보 객체 (id/name — updater 관계 로드 시) | | abilities | object | `{"can_create":true,"can_update":true,"can_delete":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | @@ -72,7 +77,7 @@ HTTP/1.1 200 "data": { "data": [ { - "number": 8, + "number": 7, "id": 1, "type": "refund", "code": "order_mistake", @@ -86,8 +91,8 @@ HTTP/1.1 200 "is_user_selectable": true, "is_active": true, "sort_order": 0, - "created_at": "2026-07-08 10:43:32", - "updated_at": "2026-07-08 10:43:32", + "created_at": "2026-07-30 17:36:00", + "updated_at": "2026-07-30 17:36:00", "creator": [], "updater": [], "abilities": { @@ -97,22 +102,22 @@ HTTP/1.1 200 } }, { - "number": 7, - "id": 8, + "number": 6, + "id": 2, "type": "refund", - "code": "apidoc_sample", + "code": "changed_mind", "name": { - "ko": "API 문서 샘플 사유", - "en": "API Doc Sample Reason" + "ko": "단순 변심", + "en": "Changed Mind" }, - "localized_name": "API 문서 샘플 사유", + "localized_name": "단순 변심", "fault_type": "customer", "fault_type_label": "고객 귀책", "is_user_selectable": true, "is_active": true, - "sort_order": 0, - "created_at": "2026-07-08 10:44:49", - "updated_at": "2026-07-08 10:44:49", + "sort_order": 1, + "created_at": "2026-07-30 17:36:00", + "updated_at": "2026-07-30 17:36:00", "creator": [], "updater": [], "abilities": { @@ -121,7 +126,7 @@ HTTP/1.1 200 "can_delete": true } }, - "... (총 8건 중 2건 표시)" + "... (총 7건 중 2건 표시)" ], "abilities": { "can_create": true, @@ -138,6 +143,7 @@ HTTP/1.1 200 | --- | --- | --- | | 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 | | 403 | Forbidden | 요구 권한(`sirsoft-ecommerce.settings.read`)이 없는 경우 | +| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) | @@ -186,7 +192,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -213,12 +219,14 @@ Content-Type: application/json **요청 파라미터** -_요청 파라미터 없음._ +| 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | +| --- | --- | --- | --- | --- | --- | +| type | query | string | 아니오 | — | 유형 필터 (해당 유형의 항목만 조회) | **요청 예시** ```http -GET /api/modules/sirsoft-ecommerce/admin/claim-reasons/active HTTP/1.1 +GET /api/modules/sirsoft-ecommerce/admin/claim-reasons/active?type=%EC%98%88%EC%8B%9C%EA%B0%92 HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -230,19 +238,19 @@ _목록 응답: `data.data[]` 배열 항목의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| number | integer | `8` | 목록에서의 순번 (페이지네이션 반영 행 번호 — HasRowNumber 파생) | +| number | integer | `7` | 목록에서의 순번 (페이지네이션 반영 행 번호 — HasRowNumber 파생) | | id | integer | `1` | 기본 키 (내부 식별자) | | type | string | `refund` | 클레임 사유 유형 (ClaimReasonTypeEnum — `refund`(환불/취소)) | | code | string | `order_mistake` | 사유 식별 코드 (같은 type 내 고유, 영문 소문자/숫자/`_`) | -| name | object | `{"ko":"주문 실수","en":"Order Mistake","ja":"注文ミス"}` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | +| name | object | `{"ko":"주문 실수","en":"Order Mistake"}` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | | localized_name | string | `주문 실수` | `name` 의 현재 로케일 해석 값 (다국어 필드를 표시용 문자열로 해석) | | fault_type | string | `customer` | 귀책 구분 (ClaimReasonFaultTypeEnum — `customer`(고객)/`seller`(판매자)/`carrier`(배송사)) | | fault_type_label | string | `고객 귀책` | `fault_type` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | | is_user_selectable | boolean | `true` | user selectable 여부 | | is_active | boolean | `true` | active 여부 | | sort_order | integer | `0` | 표시 정렬 순서 값 (작을수록 우선) | -| created_at | string | `2026-05-27 15:20:43` | 생성 일시 | -| updated_at | string | `2026-06-27 00:49:51` | 최종 수정 일시 | +| created_at | string | `2026-07-30 17:36:00` | 생성 일시 | +| updated_at | string | `2026-07-30 17:36:00` | 최종 수정 일시 | | creator | array | `[]` | 생성자 정보 객체 (uuid/name/email — creator 관계 파생) | | updater | array | `[]` | 최종 수정자 정보 객체 (id/name — updater 관계 로드 시) | | abilities | object | `{"can_create":true,"can_update":true,"can_delete":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | @@ -260,7 +268,7 @@ HTTP/1.1 200 "data": { "data": [ { - "number": 8, + "number": 7, "id": 1, "type": "refund", "code": "order_mistake", @@ -274,8 +282,8 @@ HTTP/1.1 200 "is_user_selectable": true, "is_active": true, "sort_order": 0, - "created_at": "2026-07-08 10:43:32", - "updated_at": "2026-07-08 10:43:32", + "created_at": "2026-07-30 17:36:00", + "updated_at": "2026-07-30 17:36:00", "creator": [], "updater": [], "abilities": { @@ -285,22 +293,22 @@ HTTP/1.1 200 } }, { - "number": 7, - "id": 8, + "number": 6, + "id": 2, "type": "refund", - "code": "apidoc_sample", + "code": "changed_mind", "name": { - "ko": "API 문서 샘플 사유", - "en": "API Doc Sample Reason" + "ko": "단순 변심", + "en": "Changed Mind" }, - "localized_name": "API 문서 샘플 사유", + "localized_name": "단순 변심", "fault_type": "customer", "fault_type_label": "고객 귀책", "is_user_selectable": true, "is_active": true, - "sort_order": 0, - "created_at": "2026-07-08 10:44:49", - "updated_at": "2026-07-08 10:44:49", + "sort_order": 1, + "created_at": "2026-07-30 17:36:00", + "updated_at": "2026-07-30 17:36:00", "creator": [], "updater": [], "abilities": { @@ -309,7 +317,7 @@ HTTP/1.1 200 "can_delete": true } }, - "... (총 8건 중 2건 표시)" + "... (총 7건 중 2건 표시)" ], "abilities": { "can_create": true, @@ -326,6 +334,7 @@ HTTP/1.1 200 | --- | --- | --- | | 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 | | 403 | Forbidden | 요구 권한(`sirsoft-ecommerce.settings.read`)이 없는 경우 | +| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) | @@ -347,7 +356,7 @@ HTTP/1.1 200 **요청 예시** ```http -DELETE /api/modules/sirsoft-ecommerce/admin/claim-reasons/8 HTTP/1.1 +DELETE /api/modules/sirsoft-ecommerce/admin/claim-reasons/{id} HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -355,27 +364,11 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) -_단건 응답: `data` 객체의 필드._ - -| 필드 | 타입 | 실측 예시값 | 용도/설명 | -| --- | --- | --- | --- | -| reason_id | integer | `8` | reason 식별자 (연관 리소스 참조) | + **응답 예시** -```http -HTTP/1.1 200 -``` - -```json -{ - "success": true, - "message": "클레임 사유가 삭제되었습니다.", - "data": { - "reason_id": 8 - } -} -``` + **에러 응답** @@ -405,7 +398,7 @@ HTTP/1.1 200 **요청 예시** ```http -GET /api/modules/sirsoft-ecommerce/admin/claim-reasons/8 HTTP/1.1 +GET /api/modules/sirsoft-ecommerce/admin/claim-reasons/{id} HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -413,58 +406,11 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) -_단건 응답: `data` 객체의 필드._ - -| 필드 | 타입 | 실측 예시값 | 용도/설명 | -| --- | --- | --- | --- | -| id | integer | `8` | 기본 키 (내부 식별자) | -| type | string | `refund` | 사유 유형 (refund, exchange, return 등) | -| code | string | `apidoc_sample` | 고유 코드 (order_mistake 등) | -| name | object | `{"ko":"API 문서 샘플 사유","en":"API Doc Sample Reason"}` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | -| localized_name | string | `API 문서 샘플 사유` | `name` 의 현재 로케일 해석 값 (다국어 필드를 표시용 문자열로 해석) | -| fault_type | string | `customer` | 귀책 구분 (customer, seller, carrier) | -| fault_type_label | string | `고객 귀책` | `fault_type` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | -| is_user_selectable | boolean | `true` | user selectable 여부 | -| is_active | boolean | `true` | active 여부 | -| sort_order | integer | `0` | 표시 정렬 순서 값 (작을수록 우선) | -| created_at | string | `2026-07-08 10:44:49` | 생성 일시 | -| updated_at | string | `2026-07-08 10:44:49` | 최종 수정 일시 | -| abilities | object | `{"can_create":true,"can_update":true,"can_delete":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | + **응답 예시** -```http -HTTP/1.1 200 -``` - -```json -{ - "success": true, - "message": "클레임 사유 정보를 조회했습니다.", - "data": { - "id": 8, - "type": "refund", - "code": "apidoc_sample", - "name": { - "ko": "API 문서 샘플 사유", - "en": "API Doc Sample Reason" - }, - "localized_name": "API 문서 샘플 사유", - "fault_type": "customer", - "fault_type_label": "고객 귀책", - "is_user_selectable": true, - "is_active": true, - "sort_order": 0, - "created_at": "2026-07-08 10:44:49", - "updated_at": "2026-07-08 10:44:49", - "abilities": { - "can_create": true, - "can_update": true, - "can_delete": true - } - } -} -``` + **에러 응답** @@ -501,7 +447,7 @@ HTTP/1.1 200 **요청 예시** ```http -PUT /api/modules/sirsoft-ecommerce/admin/claim-reasons/8 HTTP/1.1 +PUT /api/modules/sirsoft-ecommerce/admin/claim-reasons/{id} HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -522,11 +468,11 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** - + **에러 응답** @@ -557,7 +503,7 @@ Content-Type: application/json **요청 예시** ```http -PATCH /api/modules/sirsoft-ecommerce/admin/claim-reasons/8/toggle-status HTTP/1.1 +PATCH /api/modules/sirsoft-ecommerce/admin/claim-reasons/{id}/toggle-status HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -565,58 +511,11 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) -_단건 응답: `data` 객체의 필드._ - -| 필드 | 타입 | 실측 예시값 | 용도/설명 | -| --- | --- | --- | --- | -| id | integer | `8` | 기본 키 (내부 식별자) | -| type | string | `refund` | 사유 유형 (refund, exchange, return 등) | -| code | string | `apidoc_sample` | 고유 코드 (order_mistake 등) | -| name | object | `{"ko":"API 문서 샘플 사유","en":"API Doc Sample Reason"}` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | -| localized_name | string | `API 문서 샘플 사유` | `name` 의 현재 로케일 해석 값 (다국어 필드를 표시용 문자열로 해석) | -| fault_type | string | `customer` | 귀책 구분 (customer, seller, carrier) | -| fault_type_label | string | `고객 귀책` | `fault_type` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | -| is_user_selectable | boolean | `true` | user selectable 여부 | -| is_active | boolean | `false` | active 여부 | -| sort_order | integer | `0` | 표시 정렬 순서 값 (작을수록 우선) | -| created_at | string | `2026-07-08 10:44:49` | 생성 일시 | -| updated_at | string | `2026-07-08 15:00:18` | 최종 수정 일시 | -| abilities | object | `{"can_create":true,"can_update":true,"can_delete":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | + **응답 예시** -```http -HTTP/1.1 200 -``` - -```json -{ - "success": true, - "message": "클레임 사유 상태가 변경되었습니다.", - "data": { - "id": 8, - "type": "refund", - "code": "apidoc_sample", - "name": { - "ko": "API 문서 샘플 사유", - "en": "API Doc Sample Reason" - }, - "localized_name": "API 문서 샘플 사유", - "fault_type": "customer", - "fault_type_label": "고객 귀책", - "is_user_selectable": true, - "is_active": false, - "sort_order": 0, - "created_at": "2026-07-08 10:44:49", - "updated_at": "2026-07-08 15:00:18", - "abilities": { - "can_create": true, - "can_update": true, - "can_delete": true - } - } -} -``` + **에러 응답** @@ -639,12 +538,14 @@ HTTP/1.1 200 **요청 파라미터** -_요청 파라미터 없음._ +| 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | +| --- | --- | --- | --- | --- | --- | +| type | query | string | 아니오 | — | 유형 필터 (해당 유형의 항목만 조회) | **요청 예시** ```http -GET /api/modules/sirsoft-ecommerce/user/claim-reasons HTTP/1.1 +GET /api/modules/sirsoft-ecommerce/user/claim-reasons?type=%EC%98%88%EC%8B%9C%EA%B0%92 HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} (optional.sanctum: 비회원은 헤더 생략 가능) @@ -656,19 +557,19 @@ _목록 응답: `data.data[]` 배열 항목의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| number | integer | `7` | 목록에서의 순번 (페이지네이션 반영 행 번호 — HasRowNumber 파생) | +| number | integer | `6` | 목록에서의 순번 (페이지네이션 반영 행 번호 — HasRowNumber 파생) | | id | integer | `1` | 기본 키 (내부 식별자) | | type | string | `refund` | 클레임 사유 유형 (ClaimReasonTypeEnum — `refund`(환불/취소)) | | code | string | `order_mistake` | 사유 식별 코드 (같은 type 내 고유, 영문 소문자/숫자/`_`) | -| name | object | `{"ko":"주문 실수","en":"Order Mistake","ja":"注文ミス"}` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | +| name | object | `{"ko":"주문 실수","en":"Order Mistake"}` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | | localized_name | string | `주문 실수` | `name` 의 현재 로케일 해석 값 (다국어 필드를 표시용 문자열로 해석) | | fault_type | string | `customer` | 귀책 구분 (ClaimReasonFaultTypeEnum — `customer`(고객)/`seller`(판매자)/`carrier`(배송사)) | | fault_type_label | string | `고객 귀책` | `fault_type` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | | is_user_selectable | boolean | `true` | user selectable 여부 | | is_active | boolean | `true` | active 여부 | | sort_order | integer | `0` | 표시 정렬 순서 값 (작을수록 우선) | -| created_at | string | `2026-05-27 15:20:43` | 생성 일시 | -| updated_at | string | `2026-06-27 00:49:51` | 최종 수정 일시 | +| created_at | string | `2026-07-30 17:36:00` | 생성 일시 | +| updated_at | string | `2026-07-30 17:36:00` | 최종 수정 일시 | | creator | array | `[]` | 생성자 정보 객체 (uuid/name/email — creator 관계 파생) | | updater | array | `[]` | 최종 수정자 정보 객체 (id/name — updater 관계 로드 시) | | abilities | object | `{"can_create":true,"can_update":true,"can_delete":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | @@ -686,7 +587,7 @@ HTTP/1.1 200 "data": { "data": [ { - "number": 7, + "number": 6, "id": 1, "type": "refund", "code": "order_mistake", @@ -700,8 +601,8 @@ HTTP/1.1 200 "is_user_selectable": true, "is_active": true, "sort_order": 0, - "created_at": "2026-07-08 10:43:32", - "updated_at": "2026-07-08 10:43:32", + "created_at": "2026-07-30 17:36:00", + "updated_at": "2026-07-30 17:36:00", "creator": [], "updater": [], "abilities": { @@ -711,22 +612,22 @@ HTTP/1.1 200 } }, { - "number": 6, - "id": 8, + "number": 5, + "id": 2, "type": "refund", - "code": "apidoc_sample", + "code": "changed_mind", "name": { - "ko": "API 문서 샘플 사유", - "en": "API Doc Sample Reason" + "ko": "단순 변심", + "en": "Changed Mind" }, - "localized_name": "API 문서 샘플 사유", + "localized_name": "단순 변심", "fault_type": "customer", "fault_type_label": "고객 귀책", "is_user_selectable": true, "is_active": true, - "sort_order": 0, - "created_at": "2026-07-08 10:44:49", - "updated_at": "2026-07-08 10:44:49", + "sort_order": 1, + "created_at": "2026-07-30 17:36:00", + "updated_at": "2026-07-30 17:36:00", "creator": [], "updater": [], "abilities": { @@ -735,7 +636,7 @@ HTTP/1.1 200 "can_delete": true } }, - "... (총 7건 중 2건 표시)" + "... (총 6건 중 2건 표시)" ], "abilities": { "can_create": true, @@ -751,6 +652,7 @@ HTTP/1.1 200 | 상태코드 | 의미 | 발생 조건 | | --- | --- | --- | | 403 | Forbidden | 요구 권한(`sirsoft-ecommerce.user-orders.cancel`)이 없는 경우 | +| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) | diff --git a/modules/_bundled/sirsoft-ecommerce/docs/api/coupons.md b/modules/_bundled/sirsoft-ecommerce/docs/api/coupons.md index e1abac71..4c92368a 100644 --- a/modules/_bundled/sirsoft-ecommerce/docs/api/coupons.md +++ b/modules/_bundled/sirsoft-ecommerce/docs/api/coupons.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Coupons 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` @@ -47,7 +47,7 @@ _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| coupons | object | `{"data":[{"id":8611,"coupon_id":156,"user_id":"a1e0a91a-f…` | 회원이 발급받은 쿠폰(발급 내역) 페이지네이션 객체 (`data[]` 발급 건 + `pagination` — CouponIssueCollection 직렬화, 쿠폰 정의가 아닌 회원별 발급 건) | +| coupons | object | `{"data":[{"id":2512,"coupon_id":107,"user_id":"a26219fc-9…` | 회원이 발급받은 쿠폰(발급 내역) 페이지네이션 객체 (`data[]` 발급 건 + `pagination` — CouponIssueCollection 직렬화, 쿠폰 정의가 아닌 회원별 발급 건) | **응답 예시** @@ -61,15 +61,56 @@ HTTP/1.1 200 "message": "쿠폰 목록을 조회했습니다.", "data": { "coupons": { - "data": [], + "data": [ + { + "id": 2512, + "coupon_id": 107, + "user_id": "a26219fc-94a0-4f63-9404-04c2a6ac99e4", + "coupon_code": "DL-ORG8PPAH", + "status": "used", + "...": "(12개 키 생략, 총 17개)" + }, + { + "id": 2513, + "coupon_id": 61, + "user_id": "a26219fc-94a0-4f63-9404-04c2a6ac99e4", + "coupon_code": "DR-WC3RLEDB", + "status": "used", + "...": "(12개 키 생략, 총 17개)" + }, + { + "id": 2514, + "coupon_id": 62, + "user_id": "a26219fc-94a0-4f63-9404-04c2a6ac99e4", + "coupon_code": "DR-VHQFNYRR", + "status": "used", + "...": "(12개 키 생략, 총 17개)" + }, + { + "id": 2515, + "coupon_id": 63, + "user_id": "a26219fc-94a0-4f63-9404-04c2a6ac99e4", + "coupon_code": "DR-4QRXNXAI", + "status": "used", + "...": "(12개 키 생략, 총 17개)" + }, + { + "id": 2516, + "coupon_id": 64, + "user_id": "a26219fc-94a0-4f63-9404-04c2a6ac99e4", + "coupon_code": "DR-IT4CCTSM", + "status": "used", + "...": "(12개 키 생략, 총 17개)" + }, + "... (총 25건 중 5건 표시)" + ], "pagination": { "current_page": 1, - "last_page": 1, + "last_page": 2, "per_page": 25, - "total": 0, - "from": null, - "to": null, - "has_more_pages": false + "total": 41, + "from": 1, + "...": "(2개 키 생략, 총 7개)" } } } @@ -117,7 +158,7 @@ _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| coupons | array | `[{"id":4702,"coupon_id":106,"user_id":1,"coupon_code":nul…` | 현재 장바구니 상품에 적용 가능한 보유 쿠폰(발급 건) 배열 (상품/카테고리 범위·최소 주문금액·유효기간을 만족해 주문에 곧바로 선택 가능한 후보만) | +| coupons | array | `[{"id":2534,"coupon_id":85,"user_id":1209,"coupon_code":"…` | 현재 장바구니 상품에 적용 가능한 보유 쿠폰(발급 건) 배열 (상품/카테고리 범위·최소 주문금액·유효기간을 만족해 주문에 곧바로 선택 가능한 후보만) | **응답 예시** @@ -130,7 +171,49 @@ HTTP/1.1 200 "success": true, "message": "사용 가능한 쿠폰 목록을 조회했습니다.", "data": { - "coupons": [] + "coupons": [ + { + "id": 2534, + "coupon_id": 85, + "user_id": 1209, + "coupon_code": "DR-PDZ7HT4G", + "status": "available", + "...": "(8개 키 생략, 총 13개)" + }, + { + "id": 2542, + "coupon_id": 94, + "user_id": 1209, + "coupon_code": "DR-ROQPOOED", + "status": "available", + "...": "(8개 키 생략, 총 13개)" + }, + { + "id": 2552, + "coupon_id": 106, + "user_id": 1209, + "coupon_code": "DR-XBLQUABC", + "status": "available", + "...": "(8개 키 생략, 총 13개)" + }, + { + "id": 2529, + "coupon_id": 79, + "user_id": 1209, + "coupon_code": "DR-90IO2SWI", + "status": "available", + "...": "(8개 키 생략, 총 13개)" + }, + { + "id": 2532, + "coupon_id": 83, + "user_id": 1209, + "coupon_code": "DR-FEKYVAFT", + "status": "available", + "...": "(8개 키 생략, 총 13개)" + }, + "... (총 27건 중 5건 표시)" + ] } } ``` @@ -176,44 +259,44 @@ _목록 응답: `data.data[]` 배열 항목의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| id | integer | `157` | 기본 키 (내부 식별자) | -| name | object | `{"ko":"API 문서 샘플 쿠폰","en":"API Doc Sample Coupon"}` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | +| id | integer | `107` | 기본 키 (내부 식별자) | +| name | object | `{"ko":"카테고리 배송비 3,000원 할인","en":"Category Shipping 3,000 …` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | | description | object | `{"ko":"특정 카테고리 배송비 할인","en":"Shipping discount on categor…` | 설명 (다국어 필드는 로케일별 값 객체) | -| target_type | string | `order_amount` | 할인 적용 대상 (product_amount=상품금액, order_amount=주문금액, shipping_fee=배송비) | +| target_type | string | `shipping_fee` | 할인 적용 대상 (product_amount=상품금액, order_amount=주문금액, shipping_fee=배송비) | | discount_type | string | `fixed` | 혜택 유형 (fixed=정액 할인, rate=정률(%) 할인) | -| discount_value | string | `1000.00` | 혜택값 (정액이면 할인 금액, 정률이면 할인율 %) | +| discount_value | string | `3000.00` | 혜택값 (정액이면 할인 금액, 정률이면 할인율 %) | | discount_max_amount | string | `3000.00` | 정률 할인 시 최대 할인 금액 상한 (없으면 null) | -| min_order_amount | string | `0.00` | 쿠폰 적용 최소 주문금액 (미만 주문에는 사용 불가) | +| min_order_amount | string | `20000.00` | 쿠폰 적용 최소 주문금액 (미만 주문에는 사용 불가) | | issue_method | string | `download` | 발급 방법 (direct=직접발급, download=다운로드, auto=자동발급) | | issue_condition | string | `manual` | 발급 조건 (manual=수동, signup=회원가입, first_purchase=첫구매, birthday=생일) | | issue_status | string | `issuing` | 발급 상태 (issuing=발급중, stopped=발급중단) | | total_quantity | integer | `300` | 총 발급 수량 상한 (null=무제한) | -| issued_count | integer | `0` | issued 개수 (집계) | +| issued_count | integer | `1` | issued 개수 (집계) | | per_user_limit | integer | `1` | 회원 1인당 발급 제한 수량 | | valid_type | string | `period` | 유효기간 유형 (period=기간지정, days_from_issue=발급일로부터 N일) | | valid_days | integer | `14` | 발급일로부터 유효한 일수 (valid_type=days_from_issue 인 경우) | -| valid_from | string | `2026-06-08T02:24:18.000000Z` | 유효기간 시작일 (쿠폰 사용 가능 시작 시각) | -| valid_to | string | `2026-08-07T02:24:18.000000Z` | 유효기간 종료일 (쿠폰 사용 가능 종료 시각) | -| issue_from | string | `2026-06-08T02:24:18.000000Z` | 발급기간 시작일 (다운로드 가능 시작 시각) | -| issue_to | string | `2026-07-15T02:24:18.000000Z` | 발급기간 종료일 (다운로드 가능 종료 시각) | -| is_combinable | boolean | `false` | combinable 여부 | -| target_scope | string | `all` | 적용 범위 (all=전체 상품, products=특정 상품, categories=특정 카테고리) | -| created_by | integer | `1` | 쿠폰 등록자(관리자) 식별자 (users 참조, 삭제 시 null) | -| created_at | string | `2026-07-07T05:47:31.000000Z` | 생성 일시 | -| updated_at | string | `2026-07-07T05:47:31.000000Z` | 최종 수정 일시 | +| valid_from | string | `2026-07-23T14:36:17.000000Z` | 유효기간 시작일 (쿠폰 사용 가능 시작 시각) | +| valid_to | string | `2026-09-21T14:36:17.000000Z` | 유효기간 종료일 (쿠폰 사용 가능 종료 시각) | +| issue_from | string | `2026-07-23T14:36:17.000000Z` | 발급기간 시작일 (다운로드 가능 시작 시각) | +| issue_to | string | `2026-08-29T14:36:17.000000Z` | 발급기간 종료일 (다운로드 가능 종료 시각) | +| is_combinable | boolean | `true` | combinable 여부 | +| target_scope | string | `categories` | 적용 범위 (all=전체 상품, products=특정 상품, categories=특정 카테고리) | +| created_by | null | `null` | 쿠폰 등록자(관리자) 식별자 (users 참조, 삭제 시 null) | +| created_at | string | `2026-07-30T14:36:17.000000Z` | 생성 일시 | +| updated_at | string | `2026-07-31T12:51:54.000000Z` | 최종 수정 일시 | | deleted_at | null | `null` | 소프트 삭제 일시 (미삭제 시 null) | -| is_downloaded | boolean | `false` | downloaded 여부 | -| user_issued_count | integer | `0` | user issued 개수 (집계) | -| coupon_id | integer | `157` | coupon 식별자 (연관 리소스 참조) | -| localized_name | string | `API 문서 샘플 쿠폰` | `name` 의 현재 로케일 해석 값 (다국어 필드를 표시용 문자열로 해석) | -| target_type_short_label | string | `주문` | `target_type_short` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | -| valid_period_formatted | string | `-` | `valid_period` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | -| min_order_amount_formatted | string | `0원` | `min_order_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | -| remaining_quantity | integer | `300` | 잔여 발급 가능 수량 (total_quantity − issued_count, 무제한이면 null) | -| benefit_formatted | string | `1,000원 할인` | `benefit` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | +| is_downloaded | boolean | `true` | downloaded 여부 | +| user_issued_count | integer | `1` | user issued 개수 (집계) | +| coupon_id | integer | `107` | coupon 식별자 (연관 리소스 참조) | +| localized_name | string | `카테고리 배송비 3,000원 할인` | `name` 의 현재 로케일 해석 값 (다국어 필드를 표시용 문자열로 해석) | +| target_type_short_label | string | `배송비` | `target_type_short` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | +| valid_period_formatted | string | `2026-07-23 ~ 2026-09-21` | `valid_period` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | +| min_order_amount_formatted | string | `¥20,000` | `min_order_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | +| remaining_quantity | integer | `299` | 잔여 발급 가능 수량 (total_quantity − issued_count, 무제한이면 null) | +| benefit_formatted | string | `¥3,000 할인` | `benefit` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | | included_products | array | `[]` | 적용 대상 포함 상품 목록 (target_scope=products 시 이 상품에만 적용) | | excluded_products | array | `[]` | 적용 제외 상품 목록 (해당 상품은 쿠폰 적용에서 제외) | -| included_categories | array | `[]` | 적용 대상 포함 카테고리 목록 (target_scope=categories 시 이 카테고리에만 적용) | +| included_categories | array | `[{"id":32,"name":{"ko":"바지","en":"Pants"},"description":{…` | 적용 대상 포함 카테고리 목록 (target_scope=categories 시 이 카테고리에만 적용) | | excluded_categories | array | `[]` | 적용 제외 카테고리 목록 (해당 카테고리는 쿠폰 적용에서 제외) | **응답 예시** @@ -230,49 +313,151 @@ HTTP/1.1 200 "current_page": 1, "data": [ { - "id": 1, + "id": 107, "name": { - "ko": "API 문서 샘플 쿠폰", - "en": "API Doc Sample Coupon" + "ko": "카테고리 배송비 3,000원 할인", + "en": "Category Shipping 3,000 KRW Off" }, - "description": null, - "target_type": "order_amount", + "description": { + "ko": "특정 카테고리 배송비 할인", + "en": "Shipping discount on categories" + }, + "target_type": "shipping_fee", "discount_type": "fixed", - "discount_value": "1000.00", + "discount_value": "3000.00", "discount_max_amount": null, - "min_order_amount": "0.00", + "min_order_amount": "20000.00", "issue_method": "download", "issue_condition": "manual", "issue_status": "issuing", - "total_quantity": null, - "issued_count": 0, + "total_quantity": 300, + "issued_count": 1, "per_user_limit": 1, "valid_type": "period", "valid_days": null, + "valid_from": "2026-07-23T14:36:17.000000Z", + "valid_to": "2026-09-21T14:36:17.000000Z", + "issue_from": "2026-07-23T14:36:17.000000Z", + "issue_to": "2026-08-29T14:36:17.000000Z", + "is_combinable": true, + "target_scope": "categories", + "created_by": null, + "created_at": "2026-07-30T14:36:17.000000Z", + "updated_at": "2026-07-31T12:51:54.000000Z", + "deleted_at": null, + "is_downloaded": true, + "user_issued_count": 1, + "coupon_id": 107, + "localized_name": "카테고리 배송비 3,000원 할인", + "target_type_short_label": "배송비", + "valid_period_formatted": "2026-07-23 ~ 2026-09-21", + "min_order_amount_formatted": "¥20,000", + "remaining_quantity": 299, + "benefit_formatted": "¥3,000 할인", + "included_products": [], + "excluded_products": [], + "included_categories": [ + { + "id": 32, + "name": { + "ko": "바지", + "en": "Pants" + }, + "description": { + "ko": "다양한 스타일의 바지", + "en": "Various styles of pants" + }, + "parent_id": 30, + "path": "29/30/32", + "depth": 2, + "sort_order": 1, + "is_active": true, + "slug": "men-pants", + "meta_title": null, + "meta_description": null, + "created_at": "2026-07-30T14:35:47.000000Z", + "updated_at": "2026-07-30T14:35:47.000000Z", + "pivot": { + "coupon_id": 107, + "category_id": 32 + } + }, + { + "id": 42, + "name": { + "ko": "노트북", + "en": "Laptops" + }, + "description": { + "ko": "노트북 컴퓨터", + "en": "Laptop computers" + }, + "parent_id": 38, + "path": "38/42", + "depth": 1, + "sort_order": 1, + "is_active": true, + "slug": "laptops", + "meta_title": null, + "meta_description": null, + "created_at": "2026-07-30T14:35:47.000000Z", + "updated_at": "2026-07-30T14:35:47.000000Z", + "pivot": { + "coupon_id": 107, + "category_id": 42 + } + } + ], + "excluded_categories": [] + }, + { + "id": 103, + "name": { + "ko": "배송비 50% 할인", + "en": "Shipping 50% Off" + }, + "description": { + "ko": "배송비 반값 할인", + "en": "50% off shipping fee" + }, + "target_type": "shipping_fee", + "discount_type": "rate", + "discount_value": "50.00", + "discount_max_amount": "3000.00", + "min_order_amount": "20000.00", + "issue_method": "download", + "issue_condition": "manual", + "issue_status": "issuing", + "total_quantity": 1000, + "issued_count": 62, + "per_user_limit": 1, + "valid_type": "days_from_issue", + "valid_days": 14, "valid_from": null, "valid_to": null, - "issue_from": null, - "issue_to": null, - "is_combinable": false, + "issue_from": "2026-07-25T14:36:17.000000Z", + "issue_to": "2026-09-28T14:36:17.000000Z", + "is_combinable": true, "target_scope": "all", "created_by": null, - "created_at": "2026-07-08T01:44:49.000000Z", - "updated_at": "2026-07-08T01:44:49.000000Z", + "created_at": "2026-07-30T14:36:17.000000Z", + "updated_at": "2026-07-31T12:52:41.000000Z", "deleted_at": null, - "is_downloaded": false, - "user_issued_count": 0, - "coupon_id": 1, - "localized_name": "API 문서 샘플 쿠폰", - "target_type_short_label": "주문", - "valid_period_formatted": "-", - "min_order_amount_formatted": "0원", - "remaining_quantity": null, - "benefit_formatted": "1,000원 할인", + "is_downloaded": true, + "user_issued_count": 1, + "coupon_id": 103, + "localized_name": "배송비 50% 할인", + "target_type_short_label": "배송비", + "valid_period_formatted": "발급일로부터 14일", + "min_order_amount_formatted": "¥20,000", + "remaining_quantity": 938, + "benefit_formatted": "50.00% 할인 (최대 ¥3,000)", "included_products": [], "excluded_products": [], "included_categories": [], "excluded_categories": [] - } + }, + "... (총 11건 중 2건 표시)" ], "first_page_url": "https://api.example.com/api/modules/sirsoft-ecommerce/user/coupons/downloadable?page=1", "from": 1, @@ -302,8 +487,8 @@ HTTP/1.1 200 "path": "https://api.example.com/api/modules/sirsoft-ecommerce/user/coupons/downloadable", "per_page": 25, "prev_page_url": null, - "to": 1, - "total": 1 + "to": 11, + "total": 11 } } ``` @@ -349,7 +534,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** diff --git a/modules/_bundled/sirsoft-ecommerce/docs/api/currency.md b/modules/_bundled/sirsoft-ecommerce/docs/api/currency.md index c471ab7c..6146da73 100644 --- a/modules/_bundled/sirsoft-ecommerce/docs/api/currency.md +++ b/modules/_bundled/sirsoft-ecommerce/docs/api/currency.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Currency 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` @@ -99,7 +99,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** diff --git a/modules/_bundled/sirsoft-ecommerce/docs/api/dashboard.md b/modules/_bundled/sirsoft-ecommerce/docs/api/dashboard.md index c9a97e48..7e90c3db 100644 --- a/modules/_bundled/sirsoft-ecommerce/docs/api/dashboard.md +++ b/modules/_bundled/sirsoft-ecommerce/docs/api/dashboard.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Dashboard 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` @@ -111,9 +111,9 @@ _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| items | array | `[{"id":1,"product_id":320,"inquirable_id":79762,"product_…` | 미답변 상품문의 목록 (최신순, PendingInquiryResource — 문의 id/상품/작성자/게시판 글 id 등) | -| total | integer | `1` | 전체 개수 (집계) | -| board_slug | null | `null` | 문의가 저장된 연동 게시판의 slug (관리자 문의 상세 링크용, 미연동 시 null) | +| items | array | `[{"id":60,"product_id":1732,"inquirable_id":284,"product_…` | 미답변 상품문의 목록 (최신순, PendingInquiryResource — 문의 id/상품/작성자/게시판 글 id 등) | +| total | integer | `22` | 전체 개수 (집계) | +| board_slug | string | `inquiry` | 문의가 저장된 연동 게시판의 slug (관리자 문의 상세 링크용, 미연동 시 null) | **응답 예시** @@ -128,16 +128,48 @@ HTTP/1.1 200 "data": { "items": [ { - "id": 1, - "product_id": 1, - "inquirable_id": 59404, - "product_name": "iste et inventore", - "author_name": "API 문서 샘플 사용자", - "created_at": "2026-07-08 10:44:49" + "id": 60, + "product_id": 1732, + "inquirable_id": 284, + "product_name": "면 손수건 3매입 #1", + "author_name": "", + "created_at": "2026-07-31 21:41:56" + }, + { + "id": 59, + "product_id": 1732, + "inquirable_id": 283, + "product_name": "면 손수건 3매입 #1", + "author_name": "", + "created_at": "2026-07-31 21:41:54" + }, + { + "id": 58, + "product_id": 1732, + "inquirable_id": 282, + "product_name": "면 손수건 3매입 #1", + "author_name": "", + "created_at": "2026-07-31 21:41:50" + }, + { + "id": 21, + "product_id": 1732, + "inquirable_id": 225, + "product_name": "면 손수건 3매입 #1", + "author_name": "최고관리자", + "created_at": "2026-07-30 21:20:03" + }, + { + "id": 23, + "product_id": 1732, + "inquirable_id": 227, + "product_name": "면 손수건 3매입 #1", + "author_name": "최고관리자", + "created_at": "2026-07-28 21:06:03" } ], - "total": 1, - "board_slug": null + "total": 22, + "board_slug": "inquiry" } } ``` @@ -178,6 +210,8 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) + + | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | | id | integer | `99` | 기본 키 (내부 식별자) | @@ -197,16 +231,7 @@ HTTP/1.1 200 { "success": true, "message": "대시보드 데이터를 조회했습니다.", - "data": [ - { - "id": 1, - "product_id": 1, - "product_name": "API 문서 샘플 상품", - "rating": 5, - "author_name": "API 문서 샘플 사용자", - "created_at": "2026-07-08 10:44:49" - } - ] + "data": [] } ``` @@ -248,7 +273,7 @@ _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| days | array | `[{"date":"2026-07-01","sales_quantity":0,"sales_amount":0…` | 일자별 판매 집계 배열 (각 항목 `{date, sales_quantity, sales_amount}` — 그래프 막대 데이터) | +| days | array | `[{"date":"2026-07-30","sales_quantity":0,"sales_amount":0…` | 일자별 판매 집계 배열 (각 항목 `{date, sales_quantity, sales_amount}` — 그래프 막대 데이터) | | total_quantity | integer | `0` | 표시 기간 판매 수량 합계 | | total_sales | integer | `0` | 표시 기간 순매출 합계 (기본 통화 자릿수로 라운딩) | | quantity_change | null | `null` | 직전 동일 기간 대비 판매 수량 증감율(%) (직전 합계 0 이면 null) | @@ -269,37 +294,37 @@ HTTP/1.1 200 "data": { "days": [ { - "date": "2026-07-02", + "date": "2026-07-30", "sales_quantity": 0, "sales_amount": 0 }, { - "date": "2026-07-03", + "date": "2026-07-31", "sales_quantity": 0, "sales_amount": 0 }, { - "date": "2026-07-04", + "date": "2026-08-01", "sales_quantity": 0, "sales_amount": 0 }, { - "date": "2026-07-05", + "date": "2026-08-02", "sales_quantity": 0, "sales_amount": 0 }, { - "date": "2026-07-06", + "date": "2026-08-03", "sales_quantity": 0, "sales_amount": 0 }, { - "date": "2026-07-07", + "date": "2026-08-04", "sales_quantity": 0, "sales_amount": 0 }, { - "date": "2026-07-08", + "date": "2026-08-05", "sales_quantity": 0, "sales_amount": 0 } diff --git a/modules/_bundled/sirsoft-ecommerce/docs/api/extra-fee-templates.md b/modules/_bundled/sirsoft-ecommerce/docs/api/extra-fee-templates.md index 82062633..77c03da9 100644 --- a/modules/_bundled/sirsoft-ecommerce/docs/api/extra-fee-templates.md +++ b/modules/_bundled/sirsoft-ecommerce/docs/api/extra-fee-templates.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Extra Fee Templates 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` @@ -50,9 +50,9 @@ _목록 응답: `data.data[]` 배열 항목의 필드 + `data.pagination`._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| number | integer | `37` | 목록에서의 순번 (페이지네이션 반영 행 번호 — HasRowNumber 파생) | -| id | integer | `109` | 기본 키 (내부 식별자) | -| zipcode | string | `00000` | 추가배송비를 적용할 우편번호 (단일 또는 `-` 로 이은 범위) | +| number | integer | `36` | 목록에서의 순번 (페이지네이션 반영 행 번호 — HasRowNumber 파생) | +| id | integer | `399` | 기본 키 (내부 식별자) | +| zipcode | string | `15654` | 추가배송비를 적용할 우편번호 (단일 또는 `-` 로 이은 범위) | | fee | integer | `3000` | 해당 우편번호에 부과할 추가 배송비 (상점 기본 통화 기준 반올림) | | fee_formatted | string | `3,000원` | `fee` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | | region | string | `경기 안산 풍도동` | 지역명 (도서산간 등 관리자 참고용 표시 라벨) | @@ -60,8 +60,8 @@ _목록 응답: `data.data[]` 배열 항목의 필드 + `data.pagination`._ | is_active | boolean | `true` | active 여부 | | created_by | null | `null` | 등록자 UUID (creator 관계 파생, 없으면 null) | | updated_by | null | `null` | 최종 수정자 UUID (updater 관계 파생, 없으면 null) | -| created_at | string | `2026-07-07 14:47:31` | 생성 일시 | -| updated_at | string | `2026-07-07 14:47:31` | 최종 수정 일시 | +| created_at | string | `2026-07-30 23:35:47` | 생성 일시 | +| updated_at | string | `2026-07-30 23:35:47` | 최종 수정 일시 | | abilities | object | `{"can_create":true,"can_update":true,"can_delete":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | **응답 예시** @@ -77,24 +77,22 @@ HTTP/1.1 200 "data": { "data": [ { - "number": 1, - "id": 1, - "zipcode": "00000", + "number": 36, + "id": 399, + "zipcode": "15654", "fee": 3000, "fee_formatted": "3,000원", - "region": null, - "description": null, - "is_active": true, - "created_by": null, - "updated_by": null, - "created_at": "2026-07-08 10:44:49", - "updated_at": "2026-07-08 10:44:49", - "abilities": { - "can_create": true, - "can_update": true, - "can_delete": true - } - } + "...": "(8개 키 생략, 총 13개)" + }, + { + "number": 35, + "id": 400, + "zipcode": "23008-23010", + "fee": 3000, + "fee_formatted": "3,000원", + "...": "(8개 키 생략, 총 13개)" + }, + "... (총 25건 중 2건 표시)" ], "abilities": { "can_create": true, @@ -102,19 +100,55 @@ HTTP/1.1 200 "can_delete": true }, "statistics": { - "total": 1, - "active": 1, + "total": 36, + "active": 36, "inactive": 0, - "by_region": [] + "by_region": [ + { + "region": "경기 안산 풍도동", + "count": 1, + "avg_fee": 3000, + "min_fee": "3000.00", + "max_fee": "3000.00" + }, + { + "region": "경남 사천 섬지역", + "count": 1, + "avg_fee": 3000, + "min_fee": "3000.00", + "max_fee": "3000.00" + }, + { + "region": "경남 통영 산양/한산/욕지/사량면", + "count": 1, + "avg_fee": 3000, + "min_fee": "3000.00", + "max_fee": "3000.00" + }, + { + "region": "경남 통영 용남면", + "count": 1, + "avg_fee": 3000, + "min_fee": "3000.00", + "max_fee": "3000.00" + }, + { + "region": "경북 울릉도", + "count": 1, + "avg_fee": 3000, + "min_fee": "3000.00", + "max_fee": "3000.00" + }, + "... (총 34건 중 5건 표시)" + ] }, "pagination": { "current_page": 1, - "last_page": 1, + "last_page": 2, "per_page": 25, - "total": 1, + "total": 36, "from": 1, - "to": 1, - "has_more_pages": false + "...": "(2개 키 생략, 총 7개)" } } } @@ -169,7 +203,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -211,9 +245,9 @@ Authorization: Bearer {YOUR_TOKEN} | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| zipcode | string | `00000` | 추가배송비 적용 우편번호 (배송정책 설정용 축약 필드) | +| zipcode | string | `15654` | 추가배송비 적용 우편번호 (배송정책 설정용 축약 필드) | | fee | integer | `3000` | 해당 우편번호의 추가 배송비 (float 변환값) | -| region | string | `` | 지역명 (없으면 빈 문자열) | +| region | string | `경기 안산 풍도동` | 지역명 (없으면 빈 문자열) | **응답 예시** @@ -227,9 +261,184 @@ HTTP/1.1 200 "message": "활성 추가배송비 템플릿 설정을 조회했습니다.", "data": [ { - "zipcode": "00000", + "zipcode": "15654", "fee": 3000, - "region": "" + "region": "경기 안산 풍도동" + }, + { + "zipcode": "23008-23010", + "fee": 3000, + "region": "인천 강화 섬지역" + }, + { + "zipcode": "23100-23116", + "fee": 3000, + "region": "인천 옹진 백령/대청/연평/북도면" + }, + { + "zipcode": "23124-23136", + "fee": 3000, + "region": "인천 옹진 자월/덕적면" + }, + { + "zipcode": "32133", + "fee": 3000, + "region": "충남 태안 섬지역" + }, + { + "zipcode": "33411", + "fee": 3000, + "region": "충남 보령 섬지역" + }, + { + "zipcode": "40200-40240", + "fee": 3000, + "region": "경북 울릉도" + }, + { + "zipcode": "52570-52571", + "fee": 3000, + "region": "경남 사천 섬지역" + }, + { + "zipcode": "53031-53033", + "fee": 3000, + "region": "경남 통영 용남면" + }, + { + "zipcode": "53088-53104", + "fee": 3000, + "region": "경남 통영 산양/한산/욕지/사량면" + }, + { + "zipcode": "54000", + "fee": 3000, + "region": "전북 군산 옥도면" + }, + { + "zipcode": "56347-56349", + "fee": 3000, + "region": "전북 부안 위도면" + }, + { + "zipcode": "57068-57069", + "fee": 3000, + "region": "전남 영광 낙월면" + }, + { + "zipcode": "58760-58761", + "fee": 3000, + "region": "전남 목포 섬지역" + }, + { + "zipcode": "58800-58804", + "fee": 3000, + "region": "전남 신안 임자면" + }, + { + "zipcode": "58809-58810", + "fee": 3000, + "region": "전남 신안 증도/지도" + }, + { + "zipcode": "58816-58818", + "fee": 3000, + "region": "전남 신안 지도/압해" + }, + { + "zipcode": "58826", + "fee": 3000, + "region": "전남 신안 압해" + }, + { + "zipcode": "58832", + "fee": 3000, + "region": "전남 신안 암태면" + }, + { + "zipcode": "58839-58841", + "fee": 3000, + "region": "전남 신안 안좌면" + }, + { + "zipcode": "58843-58866", + "fee": 3000, + "region": "전남 신안 비금/도초/하의/신의/장산/흑산면" + }, + { + "zipcode": "58953-58958", + "fee": 3000, + "region": "전남 진도 조도면" + }, + { + "zipcode": "59102-59103", + "fee": 3000, + "region": "전남 완도 군외면" + }, + { + "zipcode": "59127", + "fee": 3000, + "region": "전남 완도 군외면" + }, + { + "zipcode": "59137-59145", + "fee": 3000, + "region": "전남 완도 금당/금일/생일면" + }, + { + "zipcode": "59149-59170", + "fee": 3000, + "region": "전남 완도 청산/소안/노화/보길/군외/금일면" + }, + { + "zipcode": "59421", + "fee": 3000, + "region": "전남 보성 벌교" + }, + { + "zipcode": "59531", + "fee": 3000, + "region": "전남 고흥 도화면" + }, + { + "zipcode": "59551", + "fee": 3000, + "region": "전남 고흥 도양읍" + }, + { + "zipcode": "59563", + "fee": 3000, + "region": "전남 고흥 도양읍" + }, + { + "zipcode": "59568", + "fee": 3000, + "region": "전남 고흥 봉래면" + }, + { + "zipcode": "59650", + "fee": 3000, + "region": "전남 여수 화정면" + }, + { + "zipcode": "59766", + "fee": 3000, + "region": "전남 여수 경호동" + }, + { + "zipcode": "59781-59790", + "fee": 3000, + "region": "전남 여수 화정/남/삼산면" + }, + { + "zipcode": "63000-63365", + "fee": 3000, + "region": "제주 제주시" + }, + { + "zipcode": "63500-63644", + "fee": 3000, + "region": "제주 서귀포시" } ] } @@ -270,7 +479,7 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - + **응답 예시** @@ -319,7 +528,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -370,7 +579,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -404,7 +613,7 @@ Content-Type: application/json **요청 예시** ```http -DELETE /api/modules/sirsoft-ecommerce/admin/extra-fee-templates/1 HTTP/1.1 +DELETE /api/modules/sirsoft-ecommerce/admin/extra-fee-templates/{id} HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -412,23 +621,11 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - - - + **응답 예시** -```http -HTTP/1.1 200 -``` - -```json -{ - "success": true, - "message": "추가배송비 템플릿이 삭제되었습니다.", - "data": null -} -``` + **에러 응답** @@ -458,7 +655,7 @@ HTTP/1.1 200 **요청 예시** ```http -GET /api/modules/sirsoft-ecommerce/admin/extra-fee-templates/1 HTTP/1.1 +GET /api/modules/sirsoft-ecommerce/admin/extra-fee-templates/{id} HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -466,53 +663,11 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) -_단건 응답: `data` 객체의 필드._ - -| 필드 | 타입 | 실측 예시값 | 용도/설명 | -| --- | --- | --- | --- | -| id | integer | `1` | 기본 키 (내부 식별자) | -| zipcode | string | `00000` | 우편번호 (단일 또는 범위) | -| fee | integer | `3000` | 추가 배송비 | -| fee_formatted | string | `3,000원` | `fee` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | -| region | null | `null` | 지역명 (예: 제주도, 울릉도) | -| description | null | `null` | 설명 (다국어 필드는 로케일별 값 객체) | -| is_active | boolean | `true` | active 여부 | -| created_by | null | `null` | 생성자 ID | -| updated_by | null | `null` | 최종 수정한 사용자 정보 (uuid/name — updated_by 관계 파생, 없으면 null) | -| created_at | string | `2026-07-08 10:44:49` | 생성 일시 | -| updated_at | string | `2026-07-08 10:44:49` | 최종 수정 일시 | -| abilities | object | `{"can_create":true,"can_update":true,"can_delete":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | + **응답 예시** -```http -HTTP/1.1 200 -``` - -```json -{ - "success": true, - "message": "추가배송비 템플릿 정보를 조회했습니다.", - "data": { - "id": 1, - "zipcode": "00000", - "fee": 3000, - "fee_formatted": "3,000원", - "region": null, - "description": null, - "is_active": true, - "created_by": null, - "updated_by": null, - "created_at": "2026-07-08 10:44:49", - "updated_at": "2026-07-08 10:44:49", - "abilities": { - "can_create": true, - "can_update": true, - "can_delete": true - } - } -} -``` + **에러 응답** @@ -547,7 +702,7 @@ HTTP/1.1 200 **요청 예시** ```http -PUT /api/modules/sirsoft-ecommerce/admin/extra-fee-templates/1 HTTP/1.1 +PUT /api/modules/sirsoft-ecommerce/admin/extra-fee-templates/{id} HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -564,11 +719,11 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** - + **에러 응답** @@ -599,7 +754,7 @@ Content-Type: application/json **요청 예시** ```http -PATCH /api/modules/sirsoft-ecommerce/admin/extra-fee-templates/1/toggle-active HTTP/1.1 +PATCH /api/modules/sirsoft-ecommerce/admin/extra-fee-templates/{id}/toggle-active HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -607,53 +762,11 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) -_단건 응답: `data` 객체의 필드._ - -| 필드 | 타입 | 실측 예시값 | 용도/설명 | -| --- | --- | --- | --- | -| id | integer | `1` | 기본 키 (내부 식별자) | -| zipcode | string | `00000` | 우편번호 (단일 또는 범위) | -| fee | integer | `3000` | 추가 배송비 | -| fee_formatted | string | `3,000원` | `fee` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | -| region | null | `null` | 지역명 (예: 제주도, 울릉도) | -| description | null | `null` | 설명 (다국어 필드는 로케일별 값 객체) | -| is_active | boolean | `false` | active 여부 | -| created_by | null | `null` | 생성자 ID | -| updated_by | string | `a234c2b1-cde8-437f-b28b-23323be2b98d` | 최종 수정한 사용자 정보 (uuid/name — updated_by 관계 파생, 없으면 null) | -| created_at | string | `2026-07-08 10:44:49` | 생성 일시 | -| updated_at | string | `2026-07-08 15:00:20` | 최종 수정 일시 | -| abilities | object | `{"can_create":true,"can_update":true,"can_delete":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | + **응답 예시** -```http -HTTP/1.1 200 -``` - -```json -{ - "success": true, - "message": "추가배송비 템플릿 사용여부가 변경되었습니다.", - "data": { - "id": 1, - "zipcode": "00000", - "fee": 3000, - "fee_formatted": "3,000원", - "region": null, - "description": null, - "is_active": false, - "created_by": null, - "updated_by": "a234c2b1-cde8-437f-b28b-23323be2b98d", - "created_at": "2026-07-08 10:44:49", - "updated_at": "2026-07-08 15:00:20", - "abilities": { - "can_create": true, - "can_update": true, - "can_delete": true - } - } -} -``` + **에러 응답** diff --git a/modules/_bundled/sirsoft-ecommerce/docs/api/guest.md b/modules/_bundled/sirsoft-ecommerce/docs/api/guest.md index 545eb3d0..43aafbb8 100644 --- a/modules/_bundled/sirsoft-ecommerce/docs/api/guest.md +++ b/modules/_bundled/sirsoft-ecommerce/docs/api/guest.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Guest 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` @@ -48,7 +48,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -101,7 +101,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -151,7 +151,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -192,7 +192,7 @@ Accept: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -261,7 +261,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** diff --git a/modules/_bundled/sirsoft-ecommerce/docs/api/inquiries.md b/modules/_bundled/sirsoft-ecommerce/docs/api/inquiries.md index 871e32d5..477e2200 100644 --- a/modules/_bundled/sirsoft-ecommerce/docs/api/inquiries.md +++ b/modules/_bundled/sirsoft-ecommerce/docs/api/inquiries.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Inquiries 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` @@ -40,7 +40,7 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - + **응답 예시** @@ -82,7 +82,7 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - + **응답 예시** @@ -130,7 +130,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -179,7 +179,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -207,12 +207,17 @@ Content-Type: application/json **요청 파라미터** -_요청 파라미터 없음._ +| 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | +| --- | --- | --- | --- | --- | --- | +| page | query | integer | 아니오 | min 1 | 조회할 페이지 번호 (1부터 시작) | +| per_page | query | integer | 아니오 | min 1, max 100 | 페이지당 항목 수 | +| search | query | string | 아니오 | max 100 | 검색어 (지정한 검색 대상 필드에서 부분 일치) | +| is_answered | query | boolean | 아니오 | — | answered 여부 | **요청 예시** ```http -GET /api/modules/sirsoft-ecommerce/user/inquiries HTTP/1.1 +GET /api/modules/sirsoft-ecommerce/user/inquiries?page=1&per_page=1&search=%EC%98%88%EC%8B%9C%EA%B0%92&is_answered=1 HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -224,8 +229,8 @@ _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| items | array | `[]` | 내 문의 항목 배열 (각 항목: id, product 요약, product_name, is_answered, 게시판 연동 시 title/category/content/is_secret/reply/attachments) | -| meta | object | `{"current_page":1,"per_page":25,"total":0,"last_page":1,"…` | 페이지네이션 메타 (current_page/per_page/total/last_page/from/to, 문의 게시판 연동 여부 inquiry_available, abilities 답변·삭제 권한, board_settings) | +| items | array | `[{"id":19,"product_id":1732,"product":{"id":1732,"product…` | 내 문의 항목 배열 (각 항목: id, product 요약, product_name, is_answered, 게시판 연동 시 title/category/content/is_secret/reply/attachments) | +| meta | object | `{"current_page":1,"per_page":25,"total":39,"last_page":2,…` | 페이지네이션 메타 (current_page/per_page/total/last_page/from/to, 문의 게시판 연동 여부 inquiry_available, abilities 답변·삭제 권한, board_settings) | **응답 예시** @@ -240,51 +245,84 @@ HTTP/1.1 200 "data": { "items": [ { - "id": 1, - "product_id": 1, + "id": 19, + "product_id": 1732, "product": { - "id": 1, - "name": "API 문서 샘플 상품", - "thumbnail_url": null, - "url": "/shop/products/1" + "id": 1732, + "product_code": "CJTFHBL8SLRQ8ILM", + "name": "면 손수건 3매입 #1", + "thumbnail_url": "/api/modules/sirsoft-ecommerce/product-image/7df7761cdf16", + "url": "/shop/products/CJTFHBL8SLRQ8ILM" }, - "product_name": "iste et inventore", + "product_name": "면 손수건 3매입 #1", + "is_answered": true, + "...": "(9개 키 생략, 총 14개)" + }, + { + "id": 20, + "product_id": 1732, + "product": { + "id": 1732, + "product_code": "CJTFHBL8SLRQ8ILM", + "name": "면 손수건 3매입 #1", + "thumbnail_url": "/api/modules/sirsoft-ecommerce/product-image/7df7761cdf16", + "url": "/shop/products/CJTFHBL8SLRQ8ILM" + }, + "product_name": "면 손수건 3매입 #1", + "is_answered": true, + "...": "(9개 키 생략, 총 14개)" + }, + { + "id": 21, + "product_id": 1732, + "product": { + "id": 1732, + "product_code": "CJTFHBL8SLRQ8ILM", + "name": "면 손수건 3매입 #1", + "thumbnail_url": "/api/modules/sirsoft-ecommerce/product-image/7df7761cdf16", + "url": "/shop/products/CJTFHBL8SLRQ8ILM" + }, + "product_name": "면 손수건 3매입 #1", "is_answered": false, - "answered_at": null, - "created_at": "2026-07-08T01:44:49+00:00", - "updated_at": "2026-07-08T01:44:49+00:00", - "title": null, - "category": null, - "content": null, - "is_secret": "{MASKED}", - "reply": null, - "attachments": [] - } + "...": "(9개 키 생략, 총 14개)" + }, + { + "id": 22, + "product_id": 1732, + "product": { + "id": 1732, + "product_code": "CJTFHBL8SLRQ8ILM", + "name": "면 손수건 3매입 #1", + "thumbnail_url": "/api/modules/sirsoft-ecommerce/product-image/7df7761cdf16", + "url": "/shop/products/CJTFHBL8SLRQ8ILM" + }, + "product_name": "면 손수건 3매입 #1", + "is_answered": true, + "...": "(9개 키 생략, 총 14개)" + }, + { + "id": 23, + "product_id": 1732, + "product": { + "id": 1732, + "product_code": "CJTFHBL8SLRQ8ILM", + "name": "면 손수건 3매입 #1", + "thumbnail_url": "/api/modules/sirsoft-ecommerce/product-image/7df7761cdf16", + "url": "/shop/products/CJTFHBL8SLRQ8ILM" + }, + "product_name": "면 손수건 3매입 #1", + "is_answered": false, + "...": "(9개 키 생략, 총 14개)" + }, + "... (총 25건 중 5건 표시)" ], "meta": { "current_page": 1, "per_page": 25, - "total": 1, - "last_page": 1, + "total": 39, + "last_page": 2, "from": 1, - "to": 1, - "inquiry_available": false, - "abilities": { - "can_update": true, - "can_delete": true - }, - "board_settings": { - "secret_mode": "{MASKED}", - "categories": [], - "use_file_upload": false, - "max_file_count": 5, - "max_file_size": 10485760, - "allowed_extensions": [], - "min_title_length": 2, - "max_title_length": 200, - "min_content_length": 10, - "max_content_length": 10000 - } + "...": "(4개 키 생략, 총 9개)" } } } @@ -295,12 +333,13 @@ HTTP/1.1 200 | 상태코드 | 의미 | 발생 조건 | | --- | --- | --- | | 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 | +| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) | **요청 파라미터** -| 이름 | 타입 | 필수 | 기본값 | 설명 | +| 이름 | 타입 | 필수 | 기본값 | 용도 | | --- | --- | :---: | --- | --- | | `page` | integer | - | 1 | 페이지 번호 (최소 1) | | `per_page` | integer | - | 10 | 페이지당 건수 (1~100). 범위를 벗어나면 422 | @@ -333,7 +372,7 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - + **응답 예시** @@ -362,9 +401,9 @@ Authorization: Bearer {YOUR_TOKEN} | 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | | --- | --- | --- | --- | --- | --- | | inquiryId | path | string | 예 | — | 대상 inquiry의 식별자 | -| title | body | string | 아니오 | — | 제목 | +| title | body | string | 아니오 | min 2, max 200 | 제목 | | category | body | string | 아니오 | — | 문의 분류 (게시판 설정 기반 유형 슬러그, 연동 게시판 Post 로 저장) | -| content | body | string | 예 | — | 본문 내용 | +| content | body | string | 예 | min 10, max 10000 | 본문 내용 | | is_secret | body | boolean | 아니오 | — | secret 여부 | > 이 엔드포인트는 확장이 파라미터를 추가할 수 있습니다 (`sirsoft-ecommerce.inquiry.update_validation_rules`). @@ -388,7 +427,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -430,7 +469,7 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - + **응답 예시** @@ -477,7 +516,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -525,7 +564,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** diff --git a/modules/_bundled/sirsoft-ecommerce/docs/api/mileage-transactions.md b/modules/_bundled/sirsoft-ecommerce/docs/api/mileage-transactions.md index c177968a..d1f45f0c 100644 --- a/modules/_bundled/sirsoft-ecommerce/docs/api/mileage-transactions.md +++ b/modules/_bundled/sirsoft-ecommerce/docs/api/mileage-transactions.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Mileage Transactions 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` @@ -52,45 +52,45 @@ _목록 응답: `data.data[]` 배열 항목의 필드 + `data.pagination`._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| number | integer | `68` | 목록에서의 순번 (페이지네이션 반영 행 번호 — HasRowNumber 파생) | -| id | integer | `528` | 기본 키 (내부 식별자) | -| user_id | integer | `166` | user 식별자 (연관 리소스 참조) | +| number | integer | `21` | 목록에서의 순번 (페이지네이션 반영 행 번호 — HasRowNumber 파생) | +| id | integer | `830` | 기본 키 (내부 식별자) | +| user_id | integer | `1209` | user 식별자 (연관 리소스 참조) | | currency | string | `KRW` | 거래 기록 통화 코드 (주문 기준통화 스냅샷, 금액 표기·잔액 집계 단위) | -| type | string | `admin_earn` | 거래 유형 (MileageTransactionTypeEnum 8종: purchase_earn·admin_earn·order_use·admin_deduct·expired·refund_restore·order_cancel_restore·earn_cancel) | -| type_label | string | `관리자 지급` | `type` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | +| type | string | `admin_deduct` | 거래 유형 (MileageTransactionTypeEnum 8종: purchase_earn·admin_earn·order_use·admin_deduct·expired·refund_restore·order_cancel_restore·earn_cancel) | +| type_label | string | `관리자 차감` | `type` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | | admin_badge_group | string | `amber` | 관리자 내역 화면 배지 색상 그룹 (적립계=green·사용계=blue·소멸=gray·복원계=teal·수동/회수계=amber) | | user_display_category | string | `adjust` | 회원 마이페이지 표시용 4분류 (earn·use·expire·adjust — 복원·수동·회수는 adjust 로 통합) | -| amount | integer | `1000` | 거래 금액 (양수=적립, 음수=차감) | -| amount_formatted | string | `1,000원` | `amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | +| amount | integer | `-300` | 거래 금액 (양수=적립, 음수=차감) | +| amount_formatted | string | `-300원` | `amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | | remaining_amount | integer | `0` | 잔여 금액 (적립건만 양수, FIFO 차감으로 소진 — 미만료 잔여 합이 잔액 SSoT) | | remaining_amount_formatted | string | `0원` | `remaining_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | -| balance_after | integer | `1000` | 거래 직후 잔액 (감사용 스냅샷, 베스트에포트) | -| order_id | integer | `436` | order 식별자 (연관 리소스 참조) | -| order_option_id | integer | `824` | order option 식별자 (연관 리소스 참조) | -| order_cancel_id | integer | `18` | order cancel 식별자 (연관 리소스 참조) | -| source_transaction_id | integer | `523` | source transaction 식별자 (연관 리소스 참조) | -| granted_by | integer | `1` | 부여 주체 식별자 (NULL=시스템 자동, user ID=관리자 수동 부여) | -| granted_by_name | string | `관리자` | 부여 관리자 이름 (grantedByUser 관계 eager load 시에만 노출) | -| granted_by_uuid | string | `a1e0a91a-fba6-491c-a53e-7285a5686857` | 부여 관리자 UUID (grantedByUser 관계 eager load 시에만 노출) | -| user_name | string | `API 문서 샘플 사용자` | 거래 대상 회원 이름 (user 관계 eager load 시에만 노출) | -| user_uuid | string | `a231747f-e82e-4cf2-9ae1-a261849dce40` | 거래 대상 회원 UUID (user 관계 eager load 시에만 노출) | -| order_number | string | `20260619-1425382147` | 연관 주문번호 (order 관계 eager load 시에만 노출) | -| description | string | `마일리지 적립 (660원)` | 설명 (다국어 필드는 로케일별 값 객체) | -| memo | string | `111` | 관리자 메모 (수동 지급/차감·적립건 편집 시 입력) | -| expires_at | string | `2027-07-02T15:29:30+00:00` | expires 일시 | -| expires_at_formatted | string | `2027-07-03 00:29:30` | `expires_at` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | -| expires_at_date | string | `2027-07-03` | `expires_at` 의 사이트 타임존 기준 날짜 부분 (시각 제외) | +| balance_after | integer | `102200` | 거래 직후 잔액 (감사용 스냅샷, 베스트에포트) | +| order_id | null | `null` | order 식별자 (연관 리소스 참조) | +| order_option_id | null | `null` | order option 식별자 (연관 리소스 참조) | +| order_cancel_id | null | `null` | order cancel 식별자 (연관 리소스 참조) | +| source_transaction_id | integer | `802` | source transaction 식별자 (연관 리소스 참조) | +| granted_by | integer | `1209` | 부여 주체 식별자 (NULL=시스템 자동, user ID=관리자 수동 부여) | +| granted_by_name | string | `최고관리자` | 부여 관리자 이름 (grantedByUser 관계 eager load 시에만 노출) | +| granted_by_uuid | string | `a26219fc-94a0-4f63-9404-04c2a6ac99e4` | 부여 관리자 UUID (grantedByUser 관계 eager load 시에만 노출) | +| user_name | string | `최고관리자` | 거래 대상 회원 이름 (user 관계 eager load 시에만 노출) | +| user_uuid | string | `a26219fc-94a0-4f63-9404-04c2a6ac99e4` | 거래 대상 회원 UUID (user 관계 eager load 시에만 노출) | +| order_number | null | `null` | 연관 주문번호 (order 관계 eager load 시에만 노출) | +| description | string | `관리자 마일리지 차감 (300원)` | 설명 (다국어 필드는 로케일별 값 객체) | +| memo | string | `D-30 재실측` | 관리자 메모 (수동 지급/차감·적립건 편집 시 입력) | +| expires_at | string | `2027-07-31T12:56:55+00:00` | expires 일시 | +| expires_at_formatted | string | `2027-07-31 21:56:55` | `expires_at` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | +| expires_at_date | string | `2027-07-31` | `expires_at` 의 사이트 타임존 기준 날짜 부분 (시각 제외) | | expired_at | null | `null` | expired 일시 | | expired_at_formatted | null | `null` | `expired_at` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | -| created_at | string | `2026-07-07T05:47:31+00:00` | 생성 일시 | -| created_at_formatted | string | `2026-07-07 14:47:31` | `created_at` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | -| created_at_date | string | `2026-07-07` | `created_at` 의 사이트 타임존 기준 날짜 부분 (시각 제외) | -| is_earning | boolean | `true` | earning 여부 | +| created_at | string | `2026-07-31T13:36:05+00:00` | 생성 일시 | +| created_at_formatted | string | `2026-07-31 22:36:05` | `created_at` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | +| created_at_date | string | `2026-07-31` | `created_at` 의 사이트 타임존 기준 날짜 부분 (시각 제외) | +| is_earning | boolean | `false` | earning 여부 | | can_edit_expiry | boolean | `false` | edit expiry 수행 가능 여부 (권한 기반) | | expired_amount | integer | `0` | 이 적립 lot 을 source 로 소멸(expired)된 금액 합계 (목록 조회 시 eager 집계, 단건 조회 시 0 폴백) | | expired_amount_formatted | string | `0원` | `expired_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | | expiry_state | string | `active` | 적립건 소멸 상태 (active=미소멸, partial_expired=일부 소멸, fully_expired=전액 소멸 — 적립계만 의미) | -| abilities | object | `{"can_manage":true,"can_edit":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | +| abilities | object | `{"can_manage":true,"can_edit":false}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | **응답 예시** @@ -105,49 +105,94 @@ HTTP/1.1 200 "data": { "data": [ { - "number": 1, - "id": 1, - "user_id": 6, + "number": 21, + "id": 830, + "user_id": 1209, "currency": "KRW", - "type": "admin_earn", - "type_label": "관리자 지급", + "type": "admin_deduct", + "type_label": "관리자 차감", "admin_badge_group": "amber", "user_display_category": "adjust", - "amount": 1000, - "amount_formatted": "1,000원", + "amount": -300, + "amount_formatted": "-300원", "remaining_amount": 0, "remaining_amount_formatted": "0원", - "balance_after": 1000, + "balance_after": 102200, "order_id": null, "order_option_id": null, "order_cancel_id": null, - "source_transaction_id": null, - "granted_by": null, - "granted_by_name": null, - "granted_by_uuid": null, - "user_name": "API 문서 샘플 사용자", - "user_uuid": "a234c2b1-cde8-437f-b28b-23323be2b98d", + "source_transaction_id": 802, + "granted_by": 1209, + "granted_by_name": "최고관리자", + "granted_by_uuid": "a26219fc-94a0-4f63-9404-04c2a6ac99e4", + "user_name": "최고관리자", + "user_uuid": "a26219fc-94a0-4f63-9404-04c2a6ac99e4", "order_number": null, - "description": null, - "memo": null, + "description": "관리자 마일리지 차감 (300원)", + "memo": "D-30 재실측", "expires_at": null, "expires_at_formatted": null, "expires_at_date": null, "expired_at": null, "expired_at_formatted": null, - "created_at": "2026-07-08T01:44:49+00:00", - "created_at_formatted": "2026-07-08 10:44:49", - "created_at_date": "2026-07-08", - "is_earning": true, + "created_at": "2026-07-31T13:36:05+00:00", + "created_at_formatted": "2026-07-31 22:36:05", + "created_at_date": "2026-07-31", + "is_earning": false, "can_edit_expiry": false, "expired_amount": 0, "expired_amount_formatted": "0원", "expiry_state": "active", "abilities": { "can_manage": true, - "can_edit": true + "can_edit": false } - } + }, + { + "number": 20, + "id": 829, + "user_id": 1209, + "currency": "KRW", + "type": "admin_deduct", + "type_label": "관리자 차감", + "admin_badge_group": "amber", + "user_display_category": "adjust", + "amount": -2500, + "amount_formatted": "-2,500원", + "remaining_amount": 0, + "remaining_amount_formatted": "0원", + "balance_after": 102500, + "order_id": null, + "order_option_id": null, + "order_cancel_id": null, + "source_transaction_id": 801, + "granted_by": 1209, + "granted_by_name": "최고관리자", + "granted_by_uuid": "a26219fc-94a0-4f63-9404-04c2a6ac99e4", + "user_name": "최고관리자", + "user_uuid": "a26219fc-94a0-4f63-9404-04c2a6ac99e4", + "order_number": null, + "description": "관리자 마일리지 차감 (2500원)", + "memo": "검수 차감 #5", + "expires_at": null, + "expires_at_formatted": null, + "expires_at_date": null, + "expired_at": null, + "expired_at_formatted": null, + "created_at": "2026-07-31T12:50:13+00:00", + "created_at_formatted": "2026-07-31 21:50:13", + "created_at_date": "2026-07-31", + "is_earning": false, + "can_edit_expiry": false, + "expired_amount": 0, + "expired_amount_formatted": "0원", + "expiry_state": "active", + "abilities": { + "can_manage": true, + "can_edit": false + } + }, + "... (총 21건 중 2건 표시)" ], "abilities": { "can_manage": true @@ -159,9 +204,9 @@ HTTP/1.1 200 "current_page": 1, "last_page": 1, "per_page": 25, - "total": 1, + "total": 21, "from": 1, - "to": 1, + "to": 21, "has_more_pages": false } } @@ -223,7 +268,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -276,7 +321,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -312,7 +357,7 @@ Content-Type: application/json **요청 예시** ```http -PATCH /api/modules/sirsoft-ecommerce/admin/mileage-transactions/1 HTTP/1.1 +PATCH /api/modules/sirsoft-ecommerce/admin/mileage-transactions/{id} HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -326,11 +371,11 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** - + **에러 응답** @@ -361,7 +406,7 @@ Content-Type: application/json **요청 예시** ```http -GET /api/modules/sirsoft-ecommerce/admin/mileage-transactions/1/linked HTTP/1.1 +GET /api/modules/sirsoft-ecommerce/admin/mileage-transactions/{id}/linked HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -369,29 +414,11 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) -_목록 응답: `data.data[]` 배열 항목의 필드._ - - + **응답 예시** -```http -HTTP/1.1 200 -``` - -```json -{ - "success": true, - "message": "연결 거래를 조회했습니다.", - "data": { - "data": [], - "abilities": { - "can_manage": true - }, - "currencies": [] - } -} -``` + **에러 응답** diff --git a/modules/_bundled/sirsoft-ecommerce/docs/api/mileage.md b/modules/_bundled/sirsoft-ecommerce/docs/api/mileage.md index ec3f163a..18d96c08 100644 --- a/modules/_bundled/sirsoft-ecommerce/docs/api/mileage.md +++ b/modules/_bundled/sirsoft-ecommerce/docs/api/mileage.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Mileage 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` @@ -42,7 +42,7 @@ _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| mileage | object | `{"enabled":false,"available":12910,"pending":14000,"expir…` | 마일리지 잔액 요약 객체 (enabled 기능 활성화 여부, available 사용 가능, pending 적립 대기, expiring_soon/expiring_date 소멸 예정, total_earned/total_used 누적 적립·사용, by_currency 통화별 잔액) | +| mileage | object | `{"enabled":false,"available":102200,"pending":0,"expiring…` | 마일리지 잔액 요약 객체 (enabled 기능 활성화 여부, available 사용 가능, pending 적립 대기, expiring_soon/expiring_date 소멸 예정, total_earned/total_used 누적 적립·사용, by_currency 통화별 잔액) | **응답 예시** @@ -57,13 +57,22 @@ HTTP/1.1 200 "data": { "mileage": { "enabled": false, - "available": 0, + "available": 102200, "pending": 0, "expiring_soon": 0, "expiring_date": null, - "total_earned": 0, - "total_used": 0, - "by_currency": [] + "total_earned": 110000, + "total_used": 7800, + "by_currency": { + "KRW": { + "available": 102200, + "pending": 0, + "total_earned": 110000, + "total_used": 7800, + "expiring_soon": 0, + "expiring_date": null + } + } } } } @@ -109,7 +118,7 @@ _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| transactions | object | `{"data":[{"number":6,"id":527,"user_id":1,"currency":"KRW…` | 마일리지 거래 내역 페이지네이션 객체 (`data` 거래 항목 배열 + 페이지 메타, `MileageTransactionCollection` 으로 직렬화) | +| transactions | object | `{"data":[{"number":21,"id":830,"user_id":1209,"currency":…` | 마일리지 거래 내역 페이지네이션 객체 (`data` 거래 항목 배열 + 페이지 메타, `MileageTransactionCollection` 으로 직렬화) | **응답 예시** @@ -125,49 +134,46 @@ HTTP/1.1 200 "transactions": { "data": [ { - "number": 1, - "id": 1, - "user_id": 6, + "number": 21, + "id": 830, + "user_id": 1209, "currency": "KRW", - "type": "admin_earn", - "type_label": "관리자 지급", - "admin_badge_group": "amber", - "user_display_category": "adjust", - "amount": 1000, - "amount_formatted": "1,000원", - "remaining_amount": 0, - "remaining_amount_formatted": "0원", - "balance_after": 1000, - "order_id": null, - "order_option_id": null, - "order_cancel_id": null, - "source_transaction_id": null, - "granted_by": null, - "granted_by_name": [], - "granted_by_uuid": [], - "user_name": [], - "user_uuid": [], - "order_number": [], - "description": null, - "memo": null, - "expires_at": null, - "expires_at_formatted": null, - "expires_at_date": null, - "expired_at": null, - "expired_at_formatted": null, - "created_at": "2026-07-08T01:44:49+00:00", - "created_at_formatted": "2026-07-08 10:44:49", - "created_at_date": "2026-07-08", - "is_earning": true, - "can_edit_expiry": false, - "expired_amount": 0, - "expired_amount_formatted": "0원", - "expiry_state": "active", - "abilities": { - "can_manage": true, - "can_edit": true - } - } + "type": "admin_deduct", + "...": "(34개 키 생략, 총 39개)" + }, + { + "number": 20, + "id": 829, + "user_id": 1209, + "currency": "KRW", + "type": "admin_deduct", + "...": "(34개 키 생략, 총 39개)" + }, + { + "number": 19, + "id": 828, + "user_id": 1209, + "currency": "KRW", + "type": "admin_deduct", + "...": "(34개 키 생략, 총 39개)" + }, + { + "number": 18, + "id": 827, + "user_id": 1209, + "currency": "KRW", + "type": "admin_deduct", + "...": "(34개 키 생략, 총 39개)" + }, + { + "number": 17, + "id": 826, + "user_id": 1209, + "currency": "KRW", + "type": "admin_deduct", + "...": "(34개 키 생략, 총 39개)" + }, + "... (총 21건 중 5건 표시)" ], "abilities": { "can_manage": true @@ -177,10 +183,9 @@ HTTP/1.1 200 "current_page": 1, "last_page": 1, "per_page": 25, - "total": 1, + "total": 21, "from": 1, - "to": 1, - "has_more_pages": false + "...": "(2개 키 생략, 총 7개)" } } } diff --git a/modules/_bundled/sirsoft-ecommerce/docs/api/options.md b/modules/_bundled/sirsoft-ecommerce/docs/api/options.md index a9748ed7..0d9a6d90 100644 --- a/modules/_bundled/sirsoft-ecommerce/docs/api/options.md +++ b/modules/_bundled/sirsoft-ecommerce/docs/api/options.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Options 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` @@ -59,7 +59,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -118,7 +118,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -177,7 +177,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** diff --git a/modules/_bundled/sirsoft-ecommerce/docs/api/orders.md b/modules/_bundled/sirsoft-ecommerce/docs/api/orders.md index f9dfed9f..3e4386cc 100644 --- a/modules/_bundled/sirsoft-ecommerce/docs/api/orders.md +++ b/modules/_bundled/sirsoft-ecommerce/docs/api/orders.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Orders 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` @@ -35,7 +35,7 @@ | order_status | query | array | 아니오 | — | 주문상태 다중 선택 필터 (OrderStatusEnum 값 배열, 해당 상태의 주문만 조회) | | option_status | query | array | 아니오 | — | 주문옵션 상태 다중 선택 필터 (OrderStatusEnum 값 배열, 해당 옵션 상태를 가진 주문만 조회) | | shipping_type | query | array | 아니오 | — | 배송유형 다중 선택 필터 (ShippingType 코드 배열) | -| payment_method | query | array | 아니오 | 결제수단 카탈로그에 등록된 ID | 결제수단 다중 선택 필터. 코어 8종과 PG 플러그인이 등록한 확장 결제수단 ID(예: `nhnkcp_naverpay`)를 모두 허용 | +| payment_method | query | array | 아니오 | — | 결제수단 다중 선택 필터. 코어 8종과 PG 플러그인이 등록한 확장 결제수단 ID(예: `nhnkcp_naverpay`)를 모두 허용 | | category_id | query | integer | 아니오 | — | category 식별자 | | min_amount | query | integer | 아니오 | min 0 | 주문금액 범위 필터 하한 (이 금액 이상 주문만 조회) | | max_amount | query | integer | 아니오 | min 0 | 주문금액 범위 필터 상한 (이 금액 이하 주문만 조회) | @@ -69,39 +69,39 @@ _목록 응답: `data.data[]` 배열 항목의 필드 + `data.pagination`._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| number | integer | `128` | 목록에서의 순번 (페이지네이션 반영 행 번호 — HasRowNumber 파생) | -| id | integer | `455` | 기본 키 (내부 식별자) | -| order_number | string | `ORD-20260707-000002` | 주문번호 (사용자 노출용 고유 식별 코드) | +| number | integer | `100` | 목록에서의 순번 (페이지네이션 반영 행 번호 — HasRowNumber 파생) | +| id | integer | `1408` | 기본 키 (내부 식별자) | +| order_number | string | `20260730-1436301187` | 주문번호 (사용자 노출용 고유 식별 코드) | | order_status | string | `pending_payment` | 주문상태 (OrderStatusEnum 값 — 결제대기/결제완료/배송중 등) | | order_status_label | string | `결제대기` | `order_status` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | | order_status_variant | string | `warning` | `order_status` 값의 표시 변형 키 (UI 배지 색상/스타일) | -| base_currency | string | `KRW` | 금액 표기 기준 통화 (모든 *_formatted 필드의 통화, 주문 시점 base_currency 고정) | -| payment_currency | string | `KRW` | 결제 통화 (유저가 선택·결제한 통화, base_currency 와 다르면 병기 표시) | +| base_currency | string | `JPY` | 금액 표기 기준 통화 (모든 *_formatted 필드의 통화, 주문 시점 base_currency 고정) | +| payment_currency | string | `JPY` | 결제 통화 (유저가 선택·결제한 통화, base_currency 와 다르면 병기 표시) | | is_cross_currency | boolean | `false` | cross currency 여부 | | is_partially_cancelled | boolean | `false` | partially cancelled 여부 | -| total_amount | integer | `193397` | 최종 주문금액 (상품합계 − 할인 + 배송비) | -| total_amount_formatted | string | `193,397원` | `total_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | +| total_amount | integer | `125000` | 최종 주문금액 (상품합계 − 할인 + 배송비) | +| total_amount_formatted | string | `¥125,000` | `total_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | | total_shipping_amount | integer | `0` | 총 배송비 | -| total_shipping_amount_formatted | string | `0원` | `total_shipping_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | +| total_shipping_amount_formatted | string | `¥0` | `total_shipping_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | | total_paid_amount | integer | `0` | 총 실제 결제금액 (PG 결제된 금액) | -| total_paid_amount_formatted | string | `0원` | `total_paid_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | -| total_unpaid_amount | integer | `193397` | 미결제 잔액 (최종 주문금액 − 실제 결제금액) | -| total_unpaid_amount_formatted | string | `193,397원` | `total_unpaid_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | +| total_paid_amount_formatted | string | `¥0` | `total_paid_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | +| total_unpaid_amount | integer | `125000` | 미결제 잔액 (최종 주문금액 − 실제 결제금액) | +| total_unpaid_amount_formatted | string | `¥125,000` | `total_unpaid_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | | total_cancelled_amount | integer | `0` | 총 취소금액 | | total_refunded_amount | integer | `0` | 총 환불금액 | | total_points_used_amount | integer | `0` | 총 포인트(마일리지) 사용액 | -| total_points_used_amount_formatted | string | `0원` | `total_points_used_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | -| total_earned_points_amount | integer | `1934` | 총 적립 예정 포인트 | -| total_earned_points_amount_formatted | string | `1,934원` | `total_earned_points_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | -| ordered_at | string | `2026-07-07T05:47:30+00:00` | ordered 일시 | -| ordered_at_formatted | string | `2026-07-07 14:47:30` | `ordered_at` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | +| total_points_used_amount_formatted | string | `¥0` | `total_points_used_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | +| total_earned_points_amount | integer | `1250` | 총 적립 예정 포인트 | +| total_earned_points_amount_formatted | string | `¥1,250` | `total_earned_points_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | +| ordered_at | string | `2026-07-30T10:36:30+00:00` | ordered 일시 | +| ordered_at_formatted | string | `2026-07-30 19:36:30` | `ordered_at` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | | order_device | string | `pc` | 주문 디바이스 (DeviceTypeEnum 값 — pc/mobile/app) | | order_device_label | string | `PC` | `order_device` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | -| is_first_order | boolean | `true` | first order 여부 | -| user | object | `{"uuid":"a23317ba-05bc-4de3-8272-2b73c091a266","name":"남상준"}` | 회원 주문의 주문자 요약 (uuid·name, 비회원 주문이면 미포함) | -| first_option | object | `{"product_name":"quisquam et quia","product_option_name":…` | 대표 표시용 첫 번째 주문 옵션 요약 (상품명·옵션명·수량·썸네일·추가옵션 요약) | +| is_first_order | boolean | `false` | first order 여부 | +| user | object | `{"uuid":"a26219fc-94a0-4f63-9404-04c2a6ac99e4","name":"최고…` | 회원 주문의 주문자 요약 (uuid·name, 비회원 주문이면 미포함) | +| first_option | object | `{"product_name":"겨울 패딩 점퍼 #16","product_option_name":"카키\…` | 대표 표시용 첫 번째 주문 옵션 요약 (상품명·옵션명·수량·썸네일·추가옵션 요약) | | options_count | integer | `1` | options 개수 (집계) | -| address | object | `{"orderer_name":"관리자","recipient_name":"구태호","recipient_c…` | 배송지 요약 (주문자명·수령인명·배송국가 코드/현지화명) | +| address | object | `{"orderer_name":"연정훈","recipient_name":"소정훈","recipient_c…` | 배송지 요약 (주문자명·수령인명·배송국가 코드/현지화명) | | payment | object | `{"payment_method":"dbank","payment_method_label":"무통장입금"}` | 결제 요약 (결제수단 값·현지화 라벨) | | shipping | object | `{"shipping_type":null,"shipping_type_label":null,"shippin…` | 배송 요약 (배송유형·배송방법 라벨·택배사명·송장번호, 첫 번째 배송 기준) | | is_owner | boolean | `false` | 현재 인증 사용자가 이 리소스의 소유자인지 여부 (BaseApiResource 표준 메타) | @@ -120,49 +120,54 @@ HTTP/1.1 200 "data": { "data": [ { - "number": 2, - "id": 5, - "order_number": "ORD-20260708-000002", + "number": 100, + "id": 1408, + "order_number": "20260730-1436301187", "order_status": "pending_payment", "order_status_label": "결제대기", "order_status_variant": "warning", - "base_currency": "KRW", - "payment_currency": "KRW", + "base_currency": "JPY", + "payment_currency": "JPY", "is_cross_currency": false, "is_partially_cancelled": false, - "total_amount": 163969, - "total_amount_formatted": "163,969원", - "total_shipping_amount": 2500, - "total_shipping_amount_formatted": "2,500원", + "total_amount": 125000, + "total_amount_formatted": "¥125,000", + "total_shipping_amount": 0, + "total_shipping_amount_formatted": "¥0", "total_paid_amount": 0, - "total_paid_amount_formatted": "0원", - "total_unpaid_amount": 163969, - "total_unpaid_amount_formatted": "163,969원", + "total_paid_amount_formatted": "¥0", + "total_unpaid_amount": 125000, + "total_unpaid_amount_formatted": "¥125,000", "total_cancelled_amount": 0, "total_refunded_amount": 0, "total_points_used_amount": 0, - "total_points_used_amount_formatted": "0원", - "total_earned_points_amount": 1640, - "total_earned_points_amount_formatted": "1,640원", - "ordered_at": "2026-07-08T01:44:49+00:00", - "ordered_at_formatted": "2026-07-08 10:44:49", + "total_points_used_amount_formatted": "¥0", + "total_earned_points_amount": 1250, + "total_earned_points_amount_formatted": "¥1,250", + "ordered_at": "2026-07-30T10:36:30+00:00", + "ordered_at_formatted": "2026-07-30 19:36:30", "order_device": "pc", "order_device_label": "PC", - "is_first_order": true, - "user": { - "uuid": "a234c3ea-a4f9-496d-a5e7-b44f0d53fc2f", - "name": "선지영" - }, + "is_first_order": false, + "user": null, "first_option": { - "product_name": "assumenda doloremque ipsam", - "product_option_name": "ea", - "product_code": null, - "quantity": 2, - "thumbnail_url": null, + "product_name": "겨울 패딩 점퍼 #16", + "product_option_name": "카키/XL", + "product_code": "5E1WSBY0CHFX7UJU", + "quantity": 1, + "thumbnail_url": "/api/modules/sirsoft-ecommerce/product-image/81d10a0b743c", "additional_options_summary": null }, "options_count": 1, - "address": null, + "address": { + "orderer_name": "연정훈", + "recipient_name": "소정훈", + "recipient_country_code": "KR", + "recipient_country_name": { + "ko": "한국", + "en": "South Korea" + } + }, "payment": null, "shipping": { "shipping_type": null, @@ -178,49 +183,54 @@ HTTP/1.1 200 } }, { - "number": 1, - "id": 4, - "order_number": "APIDOC-20260708-000001", + "number": 99, + "id": 1405, + "order_number": "20260730-1436298546", "order_status": "pending_payment", "order_status_label": "결제대기", "order_status_variant": "warning", - "base_currency": "KRW", - "payment_currency": "KRW", + "base_currency": "JPY", + "payment_currency": "JPY", "is_cross_currency": false, - "is_partially_cancelled": false, - "total_amount": 327327, - "total_amount_formatted": "327,327원", - "total_shipping_amount": 3000, - "total_shipping_amount_formatted": "3,000원", - "total_paid_amount": 0, - "total_paid_amount_formatted": "0원", - "total_unpaid_amount": 327327, - "total_unpaid_amount_formatted": "327,327원", + "is_partially_cancelled": true, + "total_amount": 66000, + "total_amount_formatted": "¥66,000", + "total_shipping_amount": 0, + "total_shipping_amount_formatted": "¥0", + "total_paid_amount": 66000, + "total_paid_amount_formatted": "¥66,000", + "total_unpaid_amount": 0, + "total_unpaid_amount_formatted": "¥0", "total_cancelled_amount": 0, "total_refunded_amount": 0, "total_points_used_amount": 0, - "total_points_used_amount_formatted": "0원", - "total_earned_points_amount": 3273, - "total_earned_points_amount_formatted": "3,273원", - "ordered_at": "2026-07-08T01:44:49+00:00", - "ordered_at_formatted": "2026-07-08 10:44:49", + "total_points_used_amount_formatted": "¥0", + "total_earned_points_amount": 698, + "total_earned_points_amount_formatted": "¥698", + "ordered_at": "2026-07-30T08:36:29+00:00", + "ordered_at_formatted": "2026-07-30 17:36:29", "order_device": "pc", "order_device_label": "PC", "is_first_order": false, - "user": { - "uuid": "a234c2b1-cde8-437f-b28b-23323be2b98d", - "name": "API 문서 샘플 사용자" - }, + "user": null, "first_option": { - "product_name": "", - "product_option_name": "", - "product_code": null, - "quantity": null, - "thumbnail_url": null, + "product_name": "기본 양말 5족 #2", + "product_option_name": "그레이/M", + "product_code": "S0SO3A6SJFYLAKSF", + "quantity": 3, + "thumbnail_url": "/api/modules/sirsoft-ecommerce/product-image/8fc3ae669be2", "additional_options_summary": null }, - "options_count": 0, - "address": null, + "options_count": 3, + "address": { + "orderer_name": "양호민", + "recipient_name": "전종수", + "recipient_country_code": "KR", + "recipient_country_name": { + "ko": "한국", + "en": "South Korea" + } + }, "payment": null, "shipping": { "shipping_type": null, @@ -229,33 +239,42 @@ HTTP/1.1 200 "carrier_name": null, "tracking_number": null }, - "is_owner": true, + "is_owner": false, "abilities": { "can_read": true, "can_update": true } - } + }, + "... (총 25건 중 2건 표시)" ], "abilities": { "can_update": true }, "statistics": { - "total": 2, + "total": 100, "status_counts": { - "pending_payment": 2 + "confirmed": 10, + "delivered": 20, + "cancelled": 5, + "shipping": 13, + "payment_complete": 20, + "preparing": 15, + "shipping_ready": 5, + "shipping_hold": 5, + "pending_payment": 7 }, - "today_count": 2, - "today_revenue": "0.00", - "monthly_revenue": "0.00" + "today_count": 0, + "today_revenue": 0, + "monthly_revenue": 0 }, "pagination": { "current_page": 1, - "last_page": 1, + "last_page": 4, "per_page": 25, - "total": 2, + "total": 100, "from": 1, - "to": 2, - "has_more_pages": false + "to": 25, + "has_more_pages": true } } } @@ -310,7 +329,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -344,7 +363,7 @@ Content-Type: application/json **요청 예시** ```http -DELETE /api/modules/sirsoft-ecommerce/admin/orders/4 HTTP/1.1 +DELETE /api/modules/sirsoft-ecommerce/admin/orders/1316 HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -352,7 +371,7 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - + **응답 예시** @@ -386,7 +405,7 @@ Authorization: Bearer {YOUR_TOKEN} **요청 예시** ```http -GET /api/modules/sirsoft-ecommerce/admin/orders/4 HTTP/1.1 +GET /api/modules/sirsoft-ecommerce/admin/orders/1316 HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -398,10 +417,10 @@ _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| id | integer | `322` | 기본 키 (내부 식별자) | -| order_number | string | `20260617-0207256237` | 주문번호 | -| base_currency | string | `KRW` | 금액 표기 기준 통화 (모든 *_formatted 필드의 통화, 주문 시점 base_currency 고정) | -| payment_currency | string | `KRW` | 결제 통화 (유저가 선택·결제한 통화, base_currency 와 다르면 병기 표시) | +| id | integer | `1316` | 기본 키 (내부 식별자) | +| order_number | string | `20260730-1436224914` | 주문번호 | +| base_currency | string | `JPY` | 금액 표기 기준 통화 (모든 *_formatted 필드의 통화, 주문 시점 base_currency 고정) | +| payment_currency | string | `JPY` | 결제 통화 (유저가 선택·결제한 통화, base_currency 와 다르면 병기 표시) | | is_cross_currency | boolean | `false` | cross currency 여부 | | order_status | string | `payment_complete` | 주문상태 (OrderStatusEnum) | | order_status_label | string | `결제완료` | `order_status` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | @@ -410,90 +429,90 @@ _단건 응답: `data` 객체의 필드._ | order_device | string | `pc` | 주문 디바이스 (pc/mobile/app) | | order_device_label | string | `PC` | `order_device` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | | is_first_order | boolean | `true` | first order 여부 | -| subtotal_amount | integer | `140000` | 상품 합계 (할인 전, 상품가×수량 합계) | -| subtotal_amount_formatted | string | `140,000원` | `subtotal_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | -| total_discount_amount | integer | `25000` | 총 할인금액 (모든 할인 합계) | -| total_discount_amount_formatted | string | `25,000원` | `total_discount_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | +| subtotal_amount | integer | `179000` | 상품 합계 (할인 전, 상품가×수량 합계) | +| subtotal_amount_formatted | string | `¥179,000` | `subtotal_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | +| total_discount_amount | integer | `3000` | 총 할인금액 (모든 할인 합계) | +| total_discount_amount_formatted | string | `¥3,000` | `total_discount_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | | total_shipping_amount | integer | `0` | 총 배송비 | -| total_shipping_amount_formatted | string | `0원` | `total_shipping_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | -| total_amount | integer | `115000` | 최종 주문금액 (subtotal - discount + shipping) | -| total_amount_formatted | string | `115,000원` | `total_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | -| total_paid_amount | integer | `115000` | 총 실제 결제금액 (PG 결제액) | -| total_paid_amount_formatted | string | `115,000원` | `total_paid_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | +| total_shipping_amount_formatted | string | `¥0` | `total_shipping_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | +| total_amount | integer | `176000` | 최종 주문금액 (subtotal - discount + shipping) | +| total_amount_formatted | string | `¥176,000` | `total_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | +| total_paid_amount | integer | `176000` | 총 실제 결제금액 (PG 결제액) | +| total_paid_amount_formatted | string | `¥176,000` | `total_paid_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | | total_due_amount | integer | `0` | 총 결제예정금액 (무통장 등) | -| total_due_amount_formatted | string | `0원` | `total_due_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | +| total_due_amount_formatted | string | `¥0` | `total_due_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | | depositor_name | null | `null` | 무통장 입금자명 (입금확인 모달 기본값, payment 관계 로드 시에만 노출) | | total_cancelled_amount | integer | `0` | 총 취소금액 | -| total_cancelled_amount_formatted | string | `0원` | `total_cancelled_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | +| total_cancelled_amount_formatted | string | `¥0` | `total_cancelled_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | | total_refunded_amount | integer | `0` | 총 환불금액 | -| total_refunded_amount_formatted | string | `0원` | `total_refunded_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | +| total_refunded_amount_formatted | string | `¥0` | `total_refunded_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | | total_refunded_points_amount | integer | `0` | 총 환불 포인트 | -| total_refunded_points_amount_formatted | string | `0원` | `total_refunded_points_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | -| total_product_coupon_discount_amount | integer | `0` | 상품 쿠폰 할인 합계 | -| total_product_coupon_discount_amount_formatted | string | `0원` | `total_product_coupon_discount_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | -| total_order_coupon_discount_amount | integer | `25000` | 주문 쿠폰 할인 합계 | -| total_order_coupon_discount_amount_formatted | string | `25,000원` | `total_order_coupon_discount_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | -| total_coupon_discount_amount | integer | `25000` | 총 쿠폰 할인금액 | -| total_coupon_discount_amount_formatted | string | `25,000원` | `total_coupon_discount_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | +| total_refunded_points_amount_formatted | string | `¥0` | `total_refunded_points_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | +| total_product_coupon_discount_amount | integer | `3000` | 상품 쿠폰 할인 합계 | +| total_product_coupon_discount_amount_formatted | string | `¥3,000` | `total_product_coupon_discount_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | +| total_order_coupon_discount_amount | integer | `0` | 주문 쿠폰 할인 합계 | +| total_order_coupon_discount_amount_formatted | string | `¥0` | `total_order_coupon_discount_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | +| total_coupon_discount_amount | integer | `3000` | 총 쿠폰 할인금액 | +| total_coupon_discount_amount_formatted | string | `¥3,000` | `total_coupon_discount_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | | total_code_discount_amount | integer | `0` | 총 할인코드 할인금액 | -| total_code_discount_amount_formatted | string | `0원` | `total_code_discount_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | +| total_code_discount_amount_formatted | string | `¥0` | `total_code_discount_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | | total_points_used_amount | integer | `0` | 총 포인트 사용액 | -| total_points_used_amount_formatted | string | `0원` | `total_points_used_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | +| total_points_used_amount_formatted | string | `¥0` | `total_points_used_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | | total_deposit_used_amount | integer | `0` | 총 예치금 사용액 | -| total_deposit_used_amount_formatted | string | `0원` | `total_deposit_used_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | -| total_earned_points_amount | integer | `1150` | 총 적립 예정 포인트 | -| total_earned_points_amount_formatted | string | `1,150원` | `total_earned_points_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | -| mc_subtotal_amount | object | `{"KRW":{"amount":140000,"formatted":"140,000원"},"USD":{"a…` | 상품합계 다중 통화 | -| mc_total_discount_amount | object | `{"KRW":{"amount":25000,"formatted":"25,000원"},"USD":{"amo…` | 총 할인 다중 통화 | +| total_deposit_used_amount_formatted | string | `¥0` | `total_deposit_used_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | +| total_earned_points_amount | integer | `1760` | 총 적립 예정 포인트 | +| total_earned_points_amount_formatted | string | `¥1,760` | `total_earned_points_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | +| mc_subtotal_amount | object | `{"KRW":{"amount":179000,"formatted":"179,000원"},"USD":{"a…` | 상품합계 다중 통화 | +| mc_total_discount_amount | object | `{"KRW":{"amount":3000,"formatted":"3,000원"},"USD":{"amoun…` | 총 할인 다중 통화 | | mc_total_shipping_amount | object | `{"KRW":{"amount":0,"formatted":"0원"},"USD":{"amount":0,"f…` | 총 배송비 다중 통화 | -| mc_total_amount | object | `{"KRW":{"amount":115000,"formatted":"115,000원"},"USD":{"a…` | 최종금액 다중 통화 (payment_amount) | -| mc_total_product_coupon_discount_amount | object | `{"KRW":{"amount":0,"formatted":"0원"},"USD":{"amount":0,"f…` | 상품 쿠폰 할인 다중 통화 | -| mc_total_order_coupon_discount_amount | object | `{"KRW":{"amount":25000,"formatted":"25,000원"},"USD":{"amo…` | 주문 쿠폰 할인 다중 통화 | -| mc_total_coupon_discount_amount | object | `{"KRW":{"amount":25000,"formatted":"25,000원"},"USD":{"amo…` | 쿠폰 할인 합계 다중 통화 | +| mc_total_amount | object | `{"KRW":{"amount":176000,"formatted":"176,000원"},"USD":{"a…` | 최종금액 다중 통화 (payment_amount) | +| mc_total_product_coupon_discount_amount | object | `{"KRW":{"amount":3000,"formatted":"3,000원"},"USD":{"amoun…` | 상품 쿠폰 할인 다중 통화 | +| mc_total_order_coupon_discount_amount | object | `{"KRW":{"amount":0,"formatted":"0원"},"USD":{"amount":0,"f…` | 주문 쿠폰 할인 다중 통화 | +| mc_total_coupon_discount_amount | object | `{"KRW":{"amount":3000,"formatted":"3,000원"},"USD":{"amoun…` | 쿠폰 할인 합계 다중 통화 | | mc_total_code_discount_amount | object | `{"KRW":{"amount":0,"formatted":"0원"},"USD":{"amount":0,"f…` | 할인코드 할인 다중 통화 | | mc_total_points_used_amount | object | `{"KRW":{"amount":0,"formatted":"0원"},"USD":{"amount":0,"f…` | 포인트 사용 다중 통화 | | mc_total_deposit_used_amount | object | `{"KRW":{"amount":0,"formatted":"0원"},"USD":{"amount":0,"f…` | 예치금 사용 다중 통화 | | item_count | integer | `2` | item 개수 (집계) | | total_quantity | integer | `5` | 주문 옵션 수량 합계 (options 로드 시) | -| total_list_price | integer | `177000` | 정가 합계 (옵션 스냅샷 정가 × 수량 합계) | -| total_list_price_formatted | string | `177,000원` | `total_list_price` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | -| ordered_at | string | `2026-06-15T02:07:25+00:00` | ordered 일시 | -| ordered_at_formatted | string | `2026-06-15 11:07:25` | `ordered_at` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | -| paid_at | string | `2026-06-17T02:07:25+00:00` | paid 일시 | -| paid_at_formatted | string | `2026-06-17 11:07:25` | `paid_at` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | +| total_list_price | integer | `207000` | 정가 합계 (옵션 스냅샷 정가 × 수량 합계) | +| total_list_price_formatted | string | `¥207,000` | `total_list_price` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | +| ordered_at | string | `2026-07-27T14:36:22+00:00` | ordered 일시 | +| ordered_at_formatted | string | `2026-07-27 23:36:22` | `ordered_at` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | +| paid_at | string | `2026-07-30T14:36:22+00:00` | paid 일시 | +| paid_at_formatted | string | `2026-07-30 23:36:22` | `paid_at` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | | confirmed_at | null | `null` | confirmed 일시 | | confirmed_at_formatted | null | `null` | `confirmed_at` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | | cancelled_at | null | `null` | cancelled 일시 | | cancelled_at_formatted | null | `null` | `cancelled_at` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | | delivered_at | null | `null` | delivered 일시 | -| total_tax_amount | integer | `10455` | 총 과세금액 | -| total_tax_amount_formatted | string | `10,455원` | `total_tax_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | -| total_vat_amount | integer | `0` | 총 부가세금액 | -| total_vat_amount_formatted | string | `0원` | `total_vat_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | -| total_taxable_supply_amount | integer | `10455` | 과세 공급가액 (총 과세금액 − 부가세, 영수증 과세금액 표시 SSoT) | -| total_taxable_supply_amount_formatted | string | `10,455원` | `total_taxable_supply_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | +| total_tax_amount | integer | `16000` | 총 과세금액 | +| total_tax_amount_formatted | string | `¥16,000` | `total_tax_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | +| total_vat_amount | integer | `1455` | 총 부가세금액 | +| total_vat_amount_formatted | string | `¥1,455` | `total_vat_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | +| total_taxable_supply_amount | integer | `14545` | 과세 공급가액 (총 과세금액 − 부가세, 영수증 과세금액 표시 SSoT) | +| total_taxable_supply_amount_formatted | string | `¥14,545` | `total_taxable_supply_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | | total_tax_free_amount | integer | `0` | 총 면세금액 | -| total_tax_free_amount_formatted | string | `0원` | `total_tax_free_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | -| user | object | `{"uuid":"a20683c6-14f8-4061-baa9-157c45e9de5a","name":"Jo…` | 회원 주문의 주문자 정보 (uuid·name·email, user 관계 로드 시 · 비회원이면 미포함) | -| user_id | string | `a20683c6-14f8-4061-baa9-157c45e9de5a` | user 식별자 (연관 리소스 참조) | +| total_tax_free_amount_formatted | string | `¥0` | `total_tax_free_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | +| user | object | `{"uuid":"a26219fc-94a0-4f63-9404-04c2a6ac99e4","name":"최고…` | 회원 주문의 주문자 정보 (uuid·name·email, user 관계 로드 시 · 비회원이면 미포함) | +| user_id | string | `a26219fc-94a0-4f63-9404-04c2a6ac99e4` | user 식별자 (연관 리소스 참조) | | user_login_id | null | `null` | 회원 로그인 아이디 (login_id, 비회원 주문이면 null) | -| orderer_name | string | `설창용` | 주문자 이름 (배송지에서 플래튼) | -| orderer_phone | string | `010-0650-9192` | 주문자 휴대전화 (배송지에서 플래튼) | +| orderer_name | string | `박대수` | 주문자 이름 (배송지에서 플래튼) | +| orderer_phone | string | `010-4416-4675` | 주문자 휴대전화 (배송지에서 플래튼) | | orderer_tel | null | `null` | 주문자 일반전화 (배송지에서 플래튼, 미입력 시 null) | -| orderer_email | string | `moonchang.shim@gmail.com` | 주문자 이메일 (배송지에서 플래튼, 비회원 알림 수신 통로) | -| recipient_name | string | `길준` | 수령인 이름 (배송지에서 플래튼) | -| recipient_phone | string | `010-1612-1979` | 수령인 휴대전화 (배송지에서 플래튼) | +| orderer_email | string | `shin.yewon@cheon.biz` | 주문자 이메일 (배송지에서 플래튼, 비회원 알림 수신 통로) | +| recipient_name | string | `권강희` | 수령인 이름 (배송지에서 플래튼) | +| recipient_phone | string | `010-6286-6243` | 수령인 휴대전화 (배송지에서 플래튼) | | recipient_tel | null | `null` | 수령인 일반전화 (배송지에서 플래튼, 미입력 시 null) | -| recipient_zipcode | string | `19882` | 수령인 우편번호 (배송지에서 플래튼) | -| recipient_address | string | `부산광역시 도봉구 역삼로 201` | 수령인 기본 주소 (배송지에서 플래튼) | +| recipient_zipcode | string | `25530` | 수령인 우편번호 (배송지에서 플래튼) | +| recipient_address | string | `경상남도 광주시 선릉로 768` | 수령인 기본 주소 (배송지에서 플래튼) | | recipient_detail_address | null | `null` | 수령인 상세 주소 (배송지에서 플래튼, 미입력 시 null) | -| delivery_memo | null | `null` | 배송 메모 (배송지에서 플래튼, 미입력 시 null) | -| delivery_memo_label | null | `null` | `delivery_memo` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | -| options | array | `[{"id":612,"option_status":"payment_complete","option_sta…` | 주문 옵션(품목) 목록 (OrderOptionResource — 상품·옵션·수량·옵션상태·금액) | -| shipping_address | object | `{"id":320,"address_type":"shipping","orderer_name":"설창용",…` | 배송지 상세 (OrderAddressResource — 주문자/수령인/국내·해외 주소) | +| delivery_memo | string | `parcel_box` | 배송 메모 (배송지에서 플래튼, 미입력 시 null) | +| delivery_memo_label | string | `택배함에 넣어주세요` | `delivery_memo` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | +| options | array | `[{"id":1263,"option_status":"payment_complete","option_st…` | 주문 옵션(품목) 목록 (OrderOptionResource — 상품·옵션·수량·옵션상태·금액) | +| shipping_address | object | `{"id":319,"address_type":"shipping","orderer_name":"박대수",…` | 배송지 상세 (OrderAddressResource — 주문자/수령인/국내·해외 주소) | | billing_address | null | `null` | 청구지 상세 (OrderAddressResource, 미분리 시 null) | -| payment | object | `{"id":284,"payment_status":"paid","payment_status_label":…` | 대표 결제 정보 (OrderPaymentResource — 결제수단·결제상태·금액) | -| payments | array | `[{"id":284,"payment_status":"paid","payment_status_label"…` | 결제 이력 목록 (OrderPaymentResource 배열 — 다회 결제/부분결제 포함) | +| payment | object | `{"id":334,"payment_status":"paid","payment_status_label":…` | 대표 결제 정보 (OrderPaymentResource — 결제수단·결제상태·금액) | +| payments | array | `[{"id":334,"payment_status":"paid","payment_status_label"…` | 결제 이력 목록 (OrderPaymentResource 배열 — 다회 결제/부분결제 포함) | | shippings | array | `[]` | 배송 이력 목록 (OrderShippingResource 배열 — 배송유형·택배사·송장번호) | | cancels | array | `[]` | 취소 이력 목록 (OrderCancelResource 배열 — 취소 사유·상세·취소일시, 최근순) | | promotions_applied_snapshot | object | `{"coupon_issue_ids":[7330],"item_coupons":[],"discount_co…` | 적용된 프로모션 스냅샷 (재계산용) | @@ -501,9 +520,9 @@ _단건 응답: `data` 객체의 필드._ | shipping_policy_applied_snapshot (비회원 응답) | object | `{"items": [{"product_option_id": 481, "policy": {"policy_name": "국내 무료배송", "standalone_shipping_amount": 0, "standalone_shipping_amount_formatted": "무료배송"}}]}` | 비로그인(비회원 조회 토큰) 응답은 **표시용 필드만** 내보낸다 — `policy_name` · `standalone_shipping_amount(_formatted)`. 정책 id·계산 근거와 배송지 메타(`address`)는 제외된다. 비회원 주문 상세 화면이 회원과 같은 partial 로 상품별 정책명·개별 배송비를 그리므로 필드 자체를 빼면 그 줄만 오류 없이 사라진다 | | admin_memo | null | `null` | 관리자 메모 (내부 관리용) | | customer_memo | null | `null` | 고객 메모 (주문 시 고객이 남긴 메모) | -| created_at | string | `2026-06-17T02:07:25+00:00` | 생성 일시 | -| updated_at | string | `2026-06-17T02:07:25+00:00` | 최종 수정 일시 | -| is_owner | boolean | `false` | 현재 인증 사용자가 이 리소스의 소유자인지 여부 (BaseApiResource 표준 메타) | +| created_at | string | `2026-07-30T14:36:22+00:00` | 생성 일시 | +| updated_at | string | `2026-07-30T14:36:22+00:00` | 최종 수정 일시 | +| is_owner | boolean | `true` | 현재 인증 사용자가 이 리소스의 소유자인지 여부 (BaseApiResource 표준 메타) | | abilities | object | `{"can_read":true,"can_update":true,"can_cancel":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | **응답 예시** @@ -517,120 +536,12 @@ HTTP/1.1 200 "success": true, "message": "주문 정보를 조회했습니다.", "data": { - "id": 4, - "order_number": "APIDOC-20260708-000001", - "base_currency": "KRW", - "payment_currency": "KRW", + "id": 1316, + "order_number": "20260730-1436224914", + "base_currency": "JPY", + "payment_currency": "JPY", "is_cross_currency": false, - "order_status": "pending_payment", - "order_status_label": "결제대기", - "order_status_variant": "warning", - "is_partially_cancelled": false, - "order_device": "pc", - "order_device_label": "PC", - "is_first_order": false, - "subtotal_amount": 324327, - "subtotal_amount_formatted": "324,327원", - "total_discount_amount": 0, - "total_discount_amount_formatted": "0원", - "total_shipping_amount": 3000, - "total_shipping_amount_formatted": "3,000원", - "total_amount": 327327, - "total_amount_formatted": "327,327원", - "total_paid_amount": 0, - "total_paid_amount_formatted": "0원", - "total_due_amount": 327327, - "total_due_amount_formatted": "327,327원", - "depositor_name": null, - "total_cancelled_amount": 0, - "total_cancelled_amount_formatted": "0원", - "total_refunded_amount": 0, - "total_refunded_amount_formatted": "0원", - "total_refunded_points_amount": 0, - "total_refunded_points_amount_formatted": "0원", - "total_product_coupon_discount_amount": 0, - "total_product_coupon_discount_amount_formatted": "0원", - "total_order_coupon_discount_amount": 0, - "total_order_coupon_discount_amount_formatted": "0원", - "total_coupon_discount_amount": 0, - "total_coupon_discount_amount_formatted": "0원", - "total_code_discount_amount": 0, - "total_code_discount_amount_formatted": "0원", - "total_points_used_amount": 0, - "total_points_used_amount_formatted": "0원", - "total_deposit_used_amount": 0, - "total_deposit_used_amount_formatted": "0원", - "total_earned_points_amount": 3273, - "total_earned_points_amount_formatted": "3,273원", - "mc_subtotal_amount": [], - "mc_total_discount_amount": [], - "mc_total_shipping_amount": [], - "mc_total_amount": [], - "mc_total_product_coupon_discount_amount": [], - "mc_total_order_coupon_discount_amount": [], - "mc_total_coupon_discount_amount": [], - "mc_total_code_discount_amount": [], - "mc_total_points_used_amount": [], - "mc_total_deposit_used_amount": [], - "item_count": 4, - "total_quantity": 0, - "total_list_price": 0, - "total_list_price_formatted": "0원", - "ordered_at": "2026-07-08T01:44:49+00:00", - "ordered_at_formatted": "2026-07-08 10:44:49", - "paid_at": null, - "paid_at_formatted": null, - "confirmed_at": null, - "confirmed_at_formatted": null, - "cancelled_at": null, - "cancelled_at_formatted": null, - "delivered_at": null, - "total_tax_amount": 29757, - "total_tax_amount_formatted": "29,757원", - "total_vat_amount": 0, - "total_vat_amount_formatted": "0원", - "total_taxable_supply_amount": 29757, - "total_taxable_supply_amount_formatted": "29,757원", - "total_tax_free_amount": 0, - "total_tax_free_amount_formatted": "0원", - "user": { - "uuid": "a234c2b1-cde8-437f-b28b-23323be2b98d", - "name": "API 문서 샘플 사용자", - "email": "apidoc-sample-user@example.com" - }, - "user_id": "a234c2b1-cde8-437f-b28b-23323be2b98d", - "user_login_id": null, - "orderer_name": null, - "orderer_phone": null, - "orderer_tel": null, - "orderer_email": null, - "recipient_name": null, - "recipient_phone": null, - "recipient_tel": null, - "recipient_zipcode": null, - "recipient_address": null, - "recipient_detail_address": null, - "delivery_memo": null, - "delivery_memo_label": null, - "options": [], - "shipping_address": null, - "billing_address": null, - "payment": null, - "payments": [], - "shippings": [], - "cancels": [], - "promotions_applied_snapshot": null, - "shipping_policy_applied_snapshot": null, - "admin_memo": null, - "customer_memo": null, - "created_at": "2026-07-08T01:44:49+00:00", - "updated_at": "2026-07-08T01:44:49+00:00", - "is_owner": true, - "abilities": { - "can_read": true, - "can_update": true, - "can_cancel": true - } + "...": "(101개 키 생략, 총 106개)" } } ``` @@ -678,7 +589,7 @@ HTTP/1.1 200 **요청 예시** ```http -PATCH /api/modules/sirsoft-ecommerce/admin/orders/4 HTTP/1.1 +PATCH /api/modules/sirsoft-ecommerce/admin/orders/1316 HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -705,7 +616,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -736,7 +647,7 @@ Content-Type: application/json | 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | | --- | --- | --- | --- | --- | --- | | order | path | string | 예 | — | 정렬 방향 (asc 오름차순 / desc 내림차순) | -| type | body | string | 예 | `full`, `partial` | 취소 유형 (full 전체취소 / partial 부분취소 — partial 이면 items 필수) | +| type | body | string | 예 | — | 취소 유형 (full 전체취소 / partial 부분취소 — partial 이면 items 필수) | | reason | body | string | 예 | — | 취소 사유 코드 (ClaimReason 의 refund·활성 코드) | | reason_detail | body | string | 아니오 | max 500 | 취소 사유 상세 (관리자 입력 자유 텍스트) | | items | body | array | 아니오 | min 1 | 처리 대상 항목 배열 | @@ -746,7 +657,7 @@ Content-Type: application/json **요청 예시** ```http -POST /api/modules/sirsoft-ecommerce/admin/orders/4/cancel HTTP/1.1 +POST /api/modules/sirsoft-ecommerce/admin/orders/1316/cancel HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -766,7 +677,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -804,7 +715,7 @@ Content-Type: application/json **요청 예시** ```http -PATCH /api/modules/sirsoft-ecommerce/admin/orders/4/confirm-deposit HTTP/1.1 +PATCH /api/modules/sirsoft-ecommerce/admin/orders/1316/confirm-deposit HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -819,7 +730,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -856,7 +767,7 @@ Content-Type: application/json **요청 예시** ```http -POST /api/modules/sirsoft-ecommerce/admin/orders/4/estimate-refund HTTP/1.1 +POST /api/modules/sirsoft-ecommerce/admin/orders/1316/estimate-refund HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -872,7 +783,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -909,7 +820,7 @@ Content-Type: application/json **요청 예시** ```http -GET /api/modules/sirsoft-ecommerce/admin/orders/4/logs?per_page=1&sort_order=%EC%98%88%EC%8B%9C%EA%B0%92 HTTP/1.1 +GET /api/modules/sirsoft-ecommerce/admin/orders/1316/logs?per_page=1&sort_order=%EC%98%88%EC%8B%9C%EA%B0%92 HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -919,7 +830,28 @@ Authorization: Bearer {YOUR_TOKEN} _목록 응답: `data.data[]` 배열 항목의 필드._ - +| 필드 | 타입 | 실측 예시값 | 용도/설명 | +| --- | --- | --- | --- | +| id | integer | `27789` | 기본 키 (내부 식별자) | +| log_type | string | `admin` | | +| log_type_label | string | `관리자` | `log_type` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | +| loggable_type | string | `Modules\Sirsoft\Ecommerce\Models\Orde…` | | +| loggable_type_display | string | `OrderOption` | | +| loggable_id | integer | `1264` | loggable 식별자 (연관 리소스 참조) | +| action | string | `order_option.partial_cancel` | | +| action_label | string | `부분 취소` | `action` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | +| localized_description | string | `주문 옵션 부분 취소 (옵션 ID: 1264)` | `description` 의 현재 로케일 해석 값 (다국어 필드를 표시용 문자열로 해석) | +| description_key | string | `sirsoft-ecommerce::activity_log.descr…` | | +| properties | object | `{"order_id":1316,"product_name":{"ko":"신제품 출시 예정 #21","en…` | | +| changes | null | `null` | | +| bulk_changes | null | `null` | | +| has_changes | boolean | `false` | changes 여부 | +| actor_name | string | `시스템` | 행위를 수행한 주체(사용자/시스템)의 이름 | +| user | object | `{"name":"시스템"}` | 대상 사용자 정보 객체 (uuid/name/email 등 — user 관계 파생) | +| ip_address | string | `10.10.10.1` | 요청/행위가 발생한 IP 주소 | +| created_at | string | `2026-07-29 19:56:44` | 생성 일시 | +| is_owner | boolean | `false` | 현재 인증 사용자가 이 리소스의 소유자인지 여부 (BaseApiResource 표준 메타) | +| abilities | object | `{"can_read":true,"can_delete":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | **응답 예시** @@ -932,17 +864,82 @@ HTTP/1.1 200 "success": true, "message": "주문 처리 이력을 조회했습니다.", "data": { - "data": [], + "data": [ + { + "id": 27789, + "log_type": "admin", + "log_type_label": "관리자", + "loggable_type": "Modules\\Sirsoft\\Ecommerce\\Models\\OrderOption", + "loggable_type_display": "OrderOption", + "loggable_id": 1264, + "action": "order_option.partial_cancel", + "action_label": "부분 취소", + "localized_description": "주문 옵션 부분 취소 (옵션 ID: 1264)", + "description_key": "sirsoft-ecommerce::activity_log.description.order_option_partial_cancel", + "properties": { + "order_id": 1316, + "product_name": { + "ko": "신제품 출시 예정 #21", + "en": "Coming Soon Product #21" + }, + "quantity": 3 + }, + "changes": null, + "bulk_changes": null, + "has_changes": false, + "actor_name": "시스템", + "user": { + "name": "시스템" + }, + "ip_address": "10.10.10.1", + "created_at": "2026-07-29 19:56:44", + "is_owner": false, + "abilities": { + "can_read": true, + "can_delete": true + } + }, + { + "id": 27766, + "log_type": "admin", + "log_type_label": "관리자", + "loggable_type": "Modules\\Sirsoft\\Ecommerce\\Models\\OrderOption", + "loggable_type_display": "OrderOption", + "loggable_id": 1263, + "action": "order_option.confirm", + "action_label": "구매 확정", + "localized_description": "구매확인 (옵션 ID: 1263)", + "description_key": "sirsoft-ecommerce::activity_log.description.order_option_confirm", + "properties": { + "order_id": 1316 + }, + "changes": null, + "bulk_changes": null, + "has_changes": false, + "actor_name": "시스템", + "user": { + "name": "시스템" + }, + "ip_address": "10.0.0.5", + "created_at": "2026-07-29 09:06:44", + "is_owner": false, + "abilities": { + "can_read": true, + "can_delete": true + } + }, + "... (총 25건 중 2건 표시)" + ], "links": { - "first": "https://api.example.com/api/modules/sirsoft-ecommerce/admin/orders/4/logs?page=1", - "last": "https://api.example.com/api/modules/sirsoft-ecommerce/admin/orders/4/logs?page=1", + "first": "https://api.example.com/api/modules/sirsoft-ecommerce/admin/orders/1316/logs?page=1", + "last": "https://api.example.com/api/modules/sirsoft-ecommerce/admin/orders/1316/logs?page=6", "prev": null, - "next": null + "next": "https://api.example.com/api/modules/sirsoft-ecommerce/admin/orders/1316/logs?page=2" }, "meta": { "current_page": 1, - "from": null, - "last_page": 1, + "from": 1, + "last_page": 6, "links": [ { "url": null, @@ -951,22 +948,52 @@ HTTP/1.1 200 "active": false }, { - "url": "https://api.example.com/api/modules/sirsoft-ecommerce/admin/orders/4/logs?page=1", + "url": "https://api.example.com/api/modules/sirsoft-ecommerce/admin/orders/1316/logs?page=1", "label": "1", "page": 1, "active": true }, { - "url": null, + "url": "https://api.example.com/api/modules/sirsoft-ecommerce/admin/orders/1316/logs?page=2", + "label": "2", + "page": 2, + "active": false + }, + { + "url": "https://api.example.com/api/modules/sirsoft-ecommerce/admin/orders/1316/logs?page=3", + "label": "3", + "page": 3, + "active": false + }, + { + "url": "https://api.example.com/api/modules/sirsoft-ecommerce/admin/orders/1316/logs?page=4", + "label": "4", + "page": 4, + "active": false + }, + { + "url": "https://api.example.com/api/modules/sirsoft-ecommerce/admin/orders/1316/logs?page=5", + "label": "5", + "page": 5, + "active": false + }, + { + "url": "https://api.example.com/api/modules/sirsoft-ecommerce/admin/orders/1316/logs?page=6", + "label": "6", + "page": 6, + "active": false + }, + { + "url": "https://api.example.com/api/modules/sirsoft-ecommerce/admin/orders/1316/logs?page=2", "label": "pagination.next", - "page": null, + "page": 2, "active": false } ], - "path": "https://api.example.com/api/modules/sirsoft-ecommerce/admin/orders/4/logs", + "path": "https://api.example.com/api/modules/sirsoft-ecommerce/admin/orders/1316/logs", "per_page": 25, - "to": null, - "total": 0 + "to": 25, + "total": 126 } } } @@ -1005,7 +1032,7 @@ HTTP/1.1 200 **요청 예시** ```http -PATCH /api/modules/sirsoft-ecommerce/admin/orders/4/options/bulk-status HTTP/1.1 +PATCH /api/modules/sirsoft-ecommerce/admin/orders/1316/options/bulk-status HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -1023,7 +1050,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -1060,7 +1087,7 @@ Content-Type: application/json **요청 예시** ```http -POST /api/modules/sirsoft-ecommerce/admin/orders/4/reset-guest-lookup-password HTTP/1.1 +POST /api/modules/sirsoft-ecommerce/admin/orders/1316/reset-guest-lookup-password HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -1074,7 +1101,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -1111,7 +1138,7 @@ Content-Type: application/json **요청 예시** ```http -POST /api/modules/sirsoft-ecommerce/admin/orders/4/send-email HTTP/1.1 +POST /api/modules/sirsoft-ecommerce/admin/orders/1316/send-email HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -1125,7 +1152,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -1162,7 +1189,7 @@ Content-Type: application/json **요청 예시** ```http -POST /api/modules/sirsoft-ecommerce/orders/APIDOC-20260708-000001/cancel-payment HTTP/1.1 +POST /api/modules/sirsoft-ecommerce/orders/{orderNumber}/cancel-payment HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} (optional.sanctum: 비회원은 헤더 생략 가능) @@ -1176,11 +1203,11 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** - + **에러 응답** @@ -1207,11 +1234,18 @@ Content-Type: application/json | page | query | integer | 아니오 | min 1 | 조회할 페이지 번호 (1부터 시작) | | per_page | query | integer | 아니오 | min 1, max 50 | 페이지당 항목 수 | | status | query | string | 아니오 | — | 상태 필터 (해당 상태의 항목만 조회) | +| with_items | query | boolean | 아니오 | 기본 `false` | 주문 아이템 전량을 포함할지. 기본값에서는 대표 아이템 1건과 전체 개수(`item_count`)만 내려갑니다. 주문마다 상품을 전부 나열하는 화면만 켜세요 | + +**목록은 경량 표현입니다.** + +기본 응답의 `items[]` 에는 대표 아이템 1건만 담기고, 전체 개수는 `item_count` 로 제공됩니다. 아이템이 없는 주문은 빈 배열입니다. 주문마다 상품을 전부 나열해야 하는 화면은 `with_items=1` 로 전량을 요청하세요 — 그렇지 않은 호출자까지 주문 수 × 아이템 수를 받지 않게 하려는 기본값입니다. + +부분취소 뱃지(`is_partially_cancelled`)는 아이템 전량 없이도 정확합니다. 서버가 집계로 판정하므로 두 경로의 값이 같습니다. **요청 예시** ```http -GET /api/modules/sirsoft-ecommerce/user/orders?page=1&per_page=1&status=%EC%98%88%EC%8B%9C%EA%B0%92 HTTP/1.1 +GET /api/modules/sirsoft-ecommerce/user/orders?page=1&per_page=1&status=%EC%98%88%EC%8B%9C%EA%B0%92&with_items=1 HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -1223,27 +1257,27 @@ _목록 응답: `data.data[]` 배열 항목의 필드 + `data.pagination`._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| id | integer | `452` | 기본 키 (내부 식별자) | -| order_number | string | `20260625-1144420949` | 주문번호 (사용자 노출용 고유 식별 코드) | -| status | string | `shipping` | 주문상태 값 (OrderStatusEnum value — 마이페이지용 status 별칭) | -| status_label | string | `배송중` | 상태의 사람이 읽는 라벨 (상태 Enum label() 산물) | -| status_variant | string | `primary` | 상태 표시 색상/스타일 변형 키 (상태 Enum variant() 산물 — UI 배지용) | +| id | integer | `1345` | 기본 키 (내부 식별자) | +| order_number | string | `20260730-1436245243` | 주문번호 (사용자 노출용 고유 식별 코드) | +| status | string | `preparing` | 주문상태 값 (OrderStatusEnum value — 마이페이지용 status 별칭) | +| status_label | string | `상품준비중` | 상태의 사람이 읽는 라벨 (상태 Enum label() 산물) | +| status_variant | string | `info` | 상태 표시 색상/스타일 변형 키 (상태 Enum variant() 산물 — UI 배지용) | | is_partially_cancelled | boolean | `false` | partially cancelled 여부 | | recipient_country_code | string | `KR` | 배송국가 코드 (ISO 3166-1 alpha-2, shippingAddress 로드 시) | | recipient_country_name | object | `{"ko":"한국","en":"South Korea"}` | 배송국가 현지화명 (로케일별 국가명 맵) | -| ordered_at | string | `2026-06-25T11:44:42+00:00` | ordered 일시 | -| ordered_at_formatted | string | `2026-06-25 20:44:42` | `ordered_at` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | -| total_amount | integer | `31000` | 최종 주문금액 (상품합계 − 할인 + 배송비) | -| total_amount_formatted | string | `31,000원` | `total_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | -| mc_total_amount | object | `{"KRW":{"amount":31000,"formatted":"31,000원"},"USD":{"amo…` | 최종 주문금액 다중 통화 (주문 시점 스냅샷, 통화별 amount·formatted) | +| ordered_at | string | `2026-07-29T14:36:24+00:00` | ordered 일시 | +| ordered_at_formatted | string | `2026-07-29 23:36:24` | `ordered_at` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | +| total_amount | integer | `30000` | 최종 주문금액 (상품합계 − 할인 + 배송비) | +| total_amount_formatted | string | `¥30,000` | `total_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | +| mc_total_amount | object | `{"KRW":{"amount":30000,"formatted":"30,000원"},"USD":{"amo…` | 최종 주문금액 다중 통화 (주문 시점 스냅샷, 통화별 amount·formatted) | | total_shipping_amount | integer | `0` | 총 배송비 | -| total_shipping_amount_formatted | string | `0원` | `total_shipping_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | +| total_shipping_amount_formatted | string | `¥0` | `total_shipping_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | | mc_total_shipping_amount | object | `{"KRW":{"amount":0,"formatted":"0원"},"USD":{"amount":0,"f…` | 총 배송비 다중 통화 (주문 시점 스냅샷, 통화별 amount·formatted) | | total_points_used_amount | integer | `0` | 총 포인트(마일리지) 사용액 | -| total_points_used_amount_formatted | string | `0원` | `total_points_used_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | -| total_earned_points_amount | integer | `310` | 총 적립 예정 포인트 | -| total_earned_points_amount_formatted | string | `310원` | `total_earned_points_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | -| items | array | `[{"product_name":"프리미엄 샤인머스캣 2kg #94","product_option_nam…` | 주문 품목 목록 (상품명·옵션명·썸네일·수량·단가/소계·추가옵션 요약) | +| total_points_used_amount_formatted | string | `¥0` | `total_points_used_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | +| total_earned_points_amount | integer | `300` | 총 적립 예정 포인트 | +| total_earned_points_amount_formatted | string | `¥300` | `total_earned_points_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | +| items | array | `[{"product_name":"USB 충전 케이블 #7","product_option_name":"2…` | 주문 품목 목록 (상품명·옵션명·썸네일·수량·단가/소계·추가옵션 요약) | | item_count | integer | `1` | item 개수 (집계) | | abilities | object | `{"can_view":true,"can_cancel":false}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | @@ -1260,50 +1294,39 @@ HTTP/1.1 200 "data": { "data": [ { - "id": 4, - "order_number": "APIDOC-20260708-000001", - "status": "pending_payment", - "status_label": "결제대기", - "status_variant": "warning", - "is_partially_cancelled": false, - "recipient_country_code": null, - "recipient_country_name": null, - "ordered_at": "2026-07-08T01:44:49+00:00", - "ordered_at_formatted": "2026-07-08 10:44:49", - "total_amount": 327327, - "total_amount_formatted": "327,327원", - "mc_total_amount": [], - "total_shipping_amount": 3000, - "total_shipping_amount_formatted": "3,000원", - "mc_total_shipping_amount": [], - "total_points_used_amount": 0, - "total_points_used_amount_formatted": "0원", - "total_earned_points_amount": 3273, - "total_earned_points_amount_formatted": "3,273원", - "items": [], - "item_count": 0, - "abilities": { - "can_view": true, - "can_cancel": true - } - } + "id": 1345, + "order_number": "20260730-1436245243", + "status": "preparing", + "status_label": "상품준비중", + "status_variant": "info", + "...": "(18개 키 생략, 총 23개)" + }, + { + "id": 1344, + "order_number": "20260730-1436244391", + "status": "preparing", + "status_label": "상품준비중", + "status_variant": "info", + "...": "(18개 키 생략, 총 23개)" + }, + "... (총 25건 중 2건 표시)" ], "statistics": { - "pending_payment": 1, - "payment_complete": 0, - "preparing": 0, + "pending_payment": 0, + "payment_complete": 20, + "preparing": 10, "shipping": 0, "delivered": 0, - "confirmed": 0 + "...": "(1개 키 생략, 총 6개)" }, "abilities": { "can_create": true }, "pagination": { "current_page": 1, - "last_page": 1, + "last_page": 2, "per_page": 25, - "total": 1 + "total": 30 } } } @@ -1331,7 +1354,7 @@ HTTP/1.1 200 | 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | | --- | --- | --- | --- | --- | --- | -| payment_method | body | string | 예 | 결제수단 카탈로그에 등록된 ID | 결제수단. 코어 8종(`card`/`vbank`/`dbank`/`bank`/`phone`/`point`/`deposit`/`free`) 과 PG 플러그인이 등록한 확장 결제수단 ID(예: `nhnkcp_naverpay`, `kginicis_lpay`)를 모두 허용한다. 확장 결제수단도 1급 시민으로 그대로 저장된다. 카탈로그에 없는 값은 422 | +| payment_method | body | string | 예 | — | 결제수단. 코어 8종(`card`/`vbank`/`dbank`/`bank`/`phone`/`point`/`deposit`/`free`) 과 PG 플러그인이 등록한 확장 결제수단 ID(예: `nhnkcp_naverpay`, `kginicis_lpay`)를 모두 허용한다. 확장 결제수단도 1급 시민으로 그대로 저장된다. 카탈로그에 없는 값은 422 | | expected_total_amount | body | number | 예 | min 0 | 프론트가 계산한 예상 결제금액 (서버 재계산값과 대조해 금액 위변조 검증) | | shipping_memo | body | string | 아니오 | max 500 | 배송 요청사항 메모 | | depositor_name | body | string | 아니오 | max 50 | depositor 이름 (식별자) | @@ -1363,26 +1386,11 @@ Content-Type: application/json **응답 필드** (`data` 내부) -| 이름 | 타입 | 조건 | 용도 | -| --- | --- | --- | --- | -| order | object | 항상 | 생성된 주문 리소스 | -| redirect_url | string | 항상 | 결제가 필요 없는 주문의 이동 대상 (주문완료 페이지) | -| requires_pg_payment | boolean | 항상 | PG 결제창을 거쳐야 하는 주문인지. 결제수단이 PG 를 요구하고(`needs_pg`), 해석된 PG 가 `manual`/`internal`/`none` 이 아니며, 잔여 결제액이 0보다 클 때 `true` | -| pg_provider | string | `requires_pg_payment=true` | 결제를 처리할 PG (`sirsoft-` 접두사 부착 — 예: `sirsoft-nhnkcp`) | -| pg_payment_data | object | `requires_pg_payment=true` | PG 결제창에 전달할 결제 정보 (아래 표) | -| pg_payment_handler | string | `requires_pg_payment=true` **이고** PG 가 결제 진입 핸들러를 선언한 경우 | 프론트가 그대로 dispatch 할 결제 진입 핸들러 풀네임 (예: `sirsoft-pay_nhnkcp.requestPayment`). PG 가 미선언이면 이 키가 없으며, 템플릿은 PG 분기를 발화하지 않고 비-PG 흐름으로 안전 강하한다 | + -`pg_payment_data` 필드: +**응답 예시** -| 이름 | 타입 | 용도 | -| --- | --- | --- | -| order_number | string | 주문번호 | -| order_name | string | 주문명 (첫 상품명 + 외 N건) | -| amount | number | PG 청구액 (결제 통화의 최소 화폐단위 정수) | -| currency | string | 결제 통화 코드 | -| customer_name / customer_email / customer_phone | string\|null | 주문자 정보 | -| customer_key | string\|null | 회원 주문의 고객 식별자 (`user_{id}`), 비회원은 null | -| payment_method | string | 선택된 결제수단 ID. 확장 결제수단이면 확장 ID 그대로(예: `nhnkcp_naverpay`) — PG 플러그인이 이 값으로 결제창의 결제수단을 결정한다 | + **에러 응답** @@ -1413,7 +1421,7 @@ Content-Type: application/json **요청 예시** ```http -GET /api/modules/sirsoft-ecommerce/user/orders/4 HTTP/1.1 +GET /api/modules/sirsoft-ecommerce/user/orders/{id} HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -1534,132 +1542,7 @@ _단건 응답: `data` 객체의 필드._ **응답 예시** -```http -HTTP/1.1 200 -``` - -```json -{ - "success": true, - "message": "주문 정보를 조회했습니다.", - "data": { - "id": 4, - "order_number": "APIDOC-20260708-000001", - "base_currency": "KRW", - "payment_currency": "KRW", - "is_cross_currency": false, - "order_status": "pending_payment", - "order_status_label": "결제대기", - "order_status_variant": "warning", - "is_partially_cancelled": false, - "order_device": "pc", - "order_device_label": "PC", - "is_first_order": false, - "subtotal_amount": 324327, - "subtotal_amount_formatted": "324,327원", - "total_discount_amount": 0, - "total_discount_amount_formatted": "0원", - "total_shipping_amount": 3000, - "total_shipping_amount_formatted": "3,000원", - "total_amount": 327327, - "total_amount_formatted": "327,327원", - "total_paid_amount": 0, - "total_paid_amount_formatted": "0원", - "total_due_amount": 327327, - "total_due_amount_formatted": "327,327원", - "depositor_name": null, - "total_cancelled_amount": 0, - "total_cancelled_amount_formatted": "0원", - "total_refunded_amount": 0, - "total_refunded_amount_formatted": "0원", - "total_refunded_points_amount": 0, - "total_refunded_points_amount_formatted": "0원", - "total_product_coupon_discount_amount": 0, - "total_product_coupon_discount_amount_formatted": "0원", - "total_order_coupon_discount_amount": 0, - "total_order_coupon_discount_amount_formatted": "0원", - "total_coupon_discount_amount": 0, - "total_coupon_discount_amount_formatted": "0원", - "total_code_discount_amount": 0, - "total_code_discount_amount_formatted": "0원", - "total_points_used_amount": 0, - "total_points_used_amount_formatted": "0원", - "total_deposit_used_amount": 0, - "total_deposit_used_amount_formatted": "0원", - "total_earned_points_amount": 3273, - "total_earned_points_amount_formatted": "3,273원", - "mc_subtotal_amount": [], - "mc_total_discount_amount": [], - "mc_total_shipping_amount": [], - "mc_total_amount": [], - "mc_total_product_coupon_discount_amount": [], - "mc_total_order_coupon_discount_amount": [], - "mc_total_coupon_discount_amount": [], - "mc_total_code_discount_amount": [], - "mc_total_points_used_amount": [], - "mc_total_deposit_used_amount": [], - "item_count": 4, - "total_quantity": 0, - "total_list_price": 0, - "total_list_price_formatted": "0원", - "ordered_at": "2026-07-08T01:44:49+00:00", - "ordered_at_formatted": "2026-07-08 10:44:49", - "paid_at": null, - "paid_at_formatted": null, - "confirmed_at": null, - "confirmed_at_formatted": null, - "cancelled_at": null, - "cancelled_at_formatted": null, - "delivered_at": null, - "total_tax_amount": 29757, - "total_tax_amount_formatted": "29,757원", - "total_vat_amount": 0, - "total_vat_amount_formatted": "0원", - "total_taxable_supply_amount": 29757, - "total_taxable_supply_amount_formatted": "29,757원", - "total_tax_free_amount": 0, - "total_tax_free_amount_formatted": "0원", - "user": { - "uuid": "a234c2b1-cde8-437f-b28b-23323be2b98d", - "name": "API 문서 샘플 사용자", - "email": "apidoc-sample-user@example.com" - }, - "user_id": "a234c2b1-cde8-437f-b28b-23323be2b98d", - "user_login_id": null, - "orderer_name": null, - "orderer_phone": null, - "orderer_tel": null, - "orderer_email": null, - "recipient_name": null, - "recipient_phone": null, - "recipient_tel": null, - "recipient_zipcode": null, - "recipient_address": null, - "recipient_detail_address": null, - "delivery_memo": null, - "delivery_memo_label": null, - "options": [], - "shipping_address": null, - "billing_address": null, - "payment": null, - "payments": [], - "shippings": [], - "cancels": [], - "promotions_applied_snapshot": null, - "shipping_policy_applied_snapshot": null, - "admin_memo": null, - "customer_memo": null, - "created_at": "2026-07-08T01:44:49+00:00", - "updated_at": "2026-07-08T01:44:49+00:00", - "is_owner": true, - "abilities": { - "can_read": true, - "can_update": true, - "can_cancel": true - } - } -} -``` + **에러 응답** @@ -1692,7 +1575,7 @@ HTTP/1.1 200 **요청 예시** ```http -POST /api/modules/sirsoft-ecommerce/user/orders/4/cancel HTTP/1.1 +POST /api/modules/sirsoft-ecommerce/user/orders/{id}/cancel HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -1710,11 +1593,11 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** - + **에러 응답** @@ -1747,7 +1630,7 @@ Content-Type: application/json **요청 예시** ```http -POST /api/modules/sirsoft-ecommerce/user/orders/4/estimate-refund HTTP/1.1 +POST /api/modules/sirsoft-ecommerce/user/orders/{id}/estimate-refund HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -1763,11 +1646,11 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** - + **에러 응답** @@ -1799,7 +1682,7 @@ Content-Type: application/json **요청 예시** ```http -POST /api/modules/sirsoft-ecommerce/user/orders/4/options/{optionId}/confirm HTTP/1.1 +POST /api/modules/sirsoft-ecommerce/user/orders/{id}/options/{optionId}/confirm HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -1807,7 +1690,7 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - + **응답 예시** @@ -1841,7 +1724,7 @@ Authorization: Bearer {YOUR_TOKEN} **요청 예시** ```http -POST /api/modules/sirsoft-ecommerce/user/orders/4/reorder HTTP/1.1 +POST /api/modules/sirsoft-ecommerce/user/orders/{id}/reorder HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -1849,31 +1732,11 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) -_단건 응답: `data` 객체의 필드._ - -| 필드 | 타입 | 실측 예시값 | 용도/설명 | -| --- | --- | --- | --- | -| added_count | integer | `0` | added 개수 (집계) | -| skipped | array | `[]` | 장바구니에 담지 못한 항목 목록 (품절·단종·구매수량 한도 초과 등으로 재주문에서 건너뛴 옵션) | -| cart_count | integer | `0` | cart 개수 (집계) | + **응답 예시** -```http -HTTP/1.1 200 -``` - -```json -{ - "success": true, - "message": "과거 주문의 상품을 장바구니에 추가했습니다.", - "data": { - "added_count": 0, - "skipped": [], - "cart_count": 0 - } -} -``` + **에러 응답** @@ -1917,7 +1780,7 @@ HTTP/1.1 200 **요청 예시** ```http -PUT /api/modules/sirsoft-ecommerce/user/orders/4/shipping-address HTTP/1.1 +PUT /api/modules/sirsoft-ecommerce/user/orders/{id}/shipping-address HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -1942,131 +1805,11 @@ Content-Type: application/json **응답 필드** (`data` 내부) -_단건 응답: `data` 객체의 필드._ - -| 필드 | 타입 | 실측 예시값 | 용도/설명 | -| --- | --- | --- | --- | -| order | object | `{"id":4,"order_number":"APIDOC-20260708-000001","base_cur…` | 배송지 변경이 반영된 주문 상세 (OrderResource — 변경 후 최신 주문 전체) | + **응답 예시** -```http -HTTP/1.1 200 -``` - -```json -{ - "success": true, - "message": "배송지가 변경되었습니다.", - "data": { - "order": { - "id": 4, - "order_number": "APIDOC-20260708-000001", - "base_currency": "KRW", - "payment_currency": "KRW", - "is_cross_currency": false, - "order_status": "pending_payment", - "order_status_label": "결제대기", - "order_status_variant": "warning", - "is_partially_cancelled": false, - "order_device": "pc", - "order_device_label": "PC", - "is_first_order": false, - "subtotal_amount": 324327, - "subtotal_amount_formatted": "324,327원", - "total_discount_amount": 0, - "total_discount_amount_formatted": "0원", - "total_shipping_amount": 3000, - "total_shipping_amount_formatted": "3,000원", - "total_amount": 327327, - "total_amount_formatted": "327,327원", - "total_paid_amount": 0, - "total_paid_amount_formatted": "0원", - "total_due_amount": 327327, - "total_due_amount_formatted": "327,327원", - "total_cancelled_amount": 0, - "total_cancelled_amount_formatted": "0원", - "total_refunded_amount": 0, - "total_refunded_amount_formatted": "0원", - "total_refunded_points_amount": 0, - "total_refunded_points_amount_formatted": "0원", - "total_product_coupon_discount_amount": 0, - "total_product_coupon_discount_amount_formatted": "0원", - "total_order_coupon_discount_amount": 0, - "total_order_coupon_discount_amount_formatted": "0원", - "total_coupon_discount_amount": 0, - "total_coupon_discount_amount_formatted": "0원", - "total_code_discount_amount": 0, - "total_code_discount_amount_formatted": "0원", - "total_points_used_amount": 0, - "total_points_used_amount_formatted": "0원", - "total_deposit_used_amount": 0, - "total_deposit_used_amount_formatted": "0원", - "total_earned_points_amount": 3273, - "total_earned_points_amount_formatted": "3,273원", - "mc_subtotal_amount": [], - "mc_total_discount_amount": [], - "mc_total_shipping_amount": [], - "mc_total_amount": [], - "mc_total_product_coupon_discount_amount": [], - "mc_total_order_coupon_discount_amount": [], - "mc_total_coupon_discount_amount": [], - "mc_total_code_discount_amount": [], - "mc_total_points_used_amount": [], - "mc_total_deposit_used_amount": [], - "item_count": 4, - "total_quantity": 0, - "total_list_price": 0, - "total_list_price_formatted": "0원", - "ordered_at": "2026-07-08T01:44:49+00:00", - "ordered_at_formatted": "2026-07-08 10:44:49", - "paid_at": null, - "paid_at_formatted": null, - "confirmed_at": null, - "confirmed_at_formatted": null, - "cancelled_at": null, - "cancelled_at_formatted": null, - "delivered_at": null, - "total_tax_amount": 29757, - "total_tax_amount_formatted": "29,757원", - "total_vat_amount": 0, - "total_vat_amount_formatted": "0원", - "total_taxable_supply_amount": 29757, - "total_taxable_supply_amount_formatted": "29,757원", - "total_tax_free_amount": 0, - "total_tax_free_amount_formatted": "0원", - "user_id": "a234c2b1-cde8-437f-b28b-23323be2b98d", - "user_login_id": null, - "orderer_name": null, - "orderer_phone": null, - "orderer_tel": null, - "orderer_email": null, - "recipient_name": null, - "recipient_phone": null, - "recipient_tel": null, - "recipient_zipcode": null, - "recipient_address": null, - "recipient_detail_address": null, - "delivery_memo": null, - "delivery_memo_label": null, - "options": [], - "shipping_address": null, - "promotions_applied_snapshot": null, - "shipping_policy_applied_snapshot": null, - "admin_memo": null, - "customer_memo": null, - "created_at": "2026-07-08T01:44:49+00:00", - "updated_at": "2026-07-08T01:44:49+00:00", - "is_owner": true, - "abilities": { - "can_read": true, - "can_update": true, - "can_cancel": true - } - } - } -} -``` + **에러 응답** @@ -2096,7 +1839,7 @@ HTTP/1.1 200 **요청 예시** ```http -GET /api/modules/sirsoft-ecommerce/user/orders/APIDOC-20260708-000001 HTTP/1.1 +GET /api/modules/sirsoft-ecommerce/user/orders/{orderNumber} HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} (optional.sanctum: 비회원은 헤더 생략 가능) @@ -2217,132 +1960,7 @@ _단건 응답: `data` 객체의 필드._ **응답 예시** -```http -HTTP/1.1 200 -``` - -```json -{ - "success": true, - "message": "주문 정보를 조회했습니다.", - "data": { - "id": 4, - "order_number": "APIDOC-20260708-000001", - "base_currency": "KRW", - "payment_currency": "KRW", - "is_cross_currency": false, - "order_status": "pending_payment", - "order_status_label": "결제대기", - "order_status_variant": "warning", - "is_partially_cancelled": false, - "order_device": "pc", - "order_device_label": "PC", - "is_first_order": false, - "subtotal_amount": 324327, - "subtotal_amount_formatted": "324,327원", - "total_discount_amount": 0, - "total_discount_amount_formatted": "0원", - "total_shipping_amount": 3000, - "total_shipping_amount_formatted": "3,000원", - "total_amount": 327327, - "total_amount_formatted": "327,327원", - "total_paid_amount": 0, - "total_paid_amount_formatted": "0원", - "total_due_amount": 327327, - "total_due_amount_formatted": "327,327원", - "depositor_name": null, - "total_cancelled_amount": 0, - "total_cancelled_amount_formatted": "0원", - "total_refunded_amount": 0, - "total_refunded_amount_formatted": "0원", - "total_refunded_points_amount": 0, - "total_refunded_points_amount_formatted": "0원", - "total_product_coupon_discount_amount": 0, - "total_product_coupon_discount_amount_formatted": "0원", - "total_order_coupon_discount_amount": 0, - "total_order_coupon_discount_amount_formatted": "0원", - "total_coupon_discount_amount": 0, - "total_coupon_discount_amount_formatted": "0원", - "total_code_discount_amount": 0, - "total_code_discount_amount_formatted": "0원", - "total_points_used_amount": 0, - "total_points_used_amount_formatted": "0원", - "total_deposit_used_amount": 0, - "total_deposit_used_amount_formatted": "0원", - "total_earned_points_amount": 3273, - "total_earned_points_amount_formatted": "3,273원", - "mc_subtotal_amount": [], - "mc_total_discount_amount": [], - "mc_total_shipping_amount": [], - "mc_total_amount": [], - "mc_total_product_coupon_discount_amount": [], - "mc_total_order_coupon_discount_amount": [], - "mc_total_coupon_discount_amount": [], - "mc_total_code_discount_amount": [], - "mc_total_points_used_amount": [], - "mc_total_deposit_used_amount": [], - "item_count": 4, - "total_quantity": 0, - "total_list_price": 0, - "total_list_price_formatted": "0원", - "ordered_at": "2026-07-08T01:44:49+00:00", - "ordered_at_formatted": "2026-07-08 10:44:49", - "paid_at": null, - "paid_at_formatted": null, - "confirmed_at": null, - "confirmed_at_formatted": null, - "cancelled_at": null, - "cancelled_at_formatted": null, - "delivered_at": null, - "total_tax_amount": 29757, - "total_tax_amount_formatted": "29,757원", - "total_vat_amount": 0, - "total_vat_amount_formatted": "0원", - "total_taxable_supply_amount": 29757, - "total_taxable_supply_amount_formatted": "29,757원", - "total_tax_free_amount": 0, - "total_tax_free_amount_formatted": "0원", - "user": { - "uuid": "a234c2b1-cde8-437f-b28b-23323be2b98d", - "name": "API 문서 샘플 사용자", - "email": "apidoc-sample-user@example.com" - }, - "user_id": "a234c2b1-cde8-437f-b28b-23323be2b98d", - "user_login_id": null, - "orderer_name": null, - "orderer_phone": null, - "orderer_tel": null, - "orderer_email": null, - "recipient_name": null, - "recipient_phone": null, - "recipient_tel": null, - "recipient_zipcode": null, - "recipient_address": null, - "recipient_detail_address": null, - "delivery_memo": null, - "delivery_memo_label": null, - "options": [], - "shipping_address": null, - "billing_address": null, - "payment": null, - "payments": [], - "shippings": [], - "cancels": [], - "promotions_applied_snapshot": null, - "shipping_policy_applied_snapshot": null, - "admin_memo": null, - "customer_memo": null, - "created_at": "2026-07-08T01:44:49+00:00", - "updated_at": "2026-07-08T01:44:49+00:00", - "is_owner": true, - "abilities": { - "can_read": true, - "can_update": true, - "can_cancel": true - } - } -} -``` + **에러 응답** diff --git a/modules/_bundled/sirsoft-ecommerce/docs/api/payments.md b/modules/_bundled/sirsoft-ecommerce/docs/api/payments.md index 97a7316d..43514e43 100644 --- a/modules/_bundled/sirsoft-ecommerce/docs/api/payments.md +++ b/modules/_bundled/sirsoft-ecommerce/docs/api/payments.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Payments 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` diff --git a/modules/_bundled/sirsoft-ecommerce/docs/api/presets.md b/modules/_bundled/sirsoft-ecommerce/docs/api/presets.md index 5cb25e37..d77c17b6 100644 --- a/modules/_bundled/sirsoft-ecommerce/docs/api/presets.md +++ b/modules/_bundled/sirsoft-ecommerce/docs/api/presets.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Presets 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` @@ -55,7 +55,7 @@ HTTP/1.1 200 ```json { "success": true, - "message": "sirsoft-ecommerce::messages.presets.fetch_success", + "message": "검색 프리셋 목록을 조회했습니다.", "data": [] } ``` @@ -108,7 +108,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -149,7 +149,7 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - + **응답 예시** @@ -204,7 +204,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** diff --git a/modules/_bundled/sirsoft-ecommerce/docs/api/product-common-infos.md b/modules/_bundled/sirsoft-ecommerce/docs/api/product-common-infos.md index 09bb1d2e..488454f0 100644 --- a/modules/_bundled/sirsoft-ecommerce/docs/api/product-common-infos.md +++ b/modules/_bundled/sirsoft-ecommerce/docs/api/product-common-infos.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Product Common Infos 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` @@ -25,12 +25,17 @@ **요청 파라미터** -_요청 파라미터 없음._ +| 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | +| --- | --- | --- | --- | --- | --- | +| search | query | string | 아니오 | max 255 | 검색어 (지정한 검색 대상 필드에서 부분 일치) | +| active_only | query | boolean | 아니오 | — | | +| default_only | query | boolean | 아니오 | — | | +| per_page | query | string | 아니오 | — | 페이지당 항목 수 | **요청 예시** ```http -GET /api/modules/sirsoft-ecommerce/admin/product-common-infos HTTP/1.1 +GET /api/modules/sirsoft-ecommerce/admin/product-common-infos?search=%EC%98%88%EC%8B%9C%EA%B0%92&active_only=1&default_only=1&per_page=%EC%98%88%EC%8B%9C%EA%B0%92 HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -42,7 +47,7 @@ _목록 응답: `data.data[]` 배열 항목의 필드 + `data.pagination`._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| id | integer | `207` | 기본 키 (내부 식별자) | +| id | integer | `1` | 기본 키 (내부 식별자) | | name | object | `{"ko":"일반 배송 안내","en":"Standard Shipping"}` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | | localized_name | string | `일반 배송 안내` | `name` 의 현재 로케일 해석 값 (다국어 필드를 표시용 문자열로 해석) | | content | object | `{"ko":"• 배송 기간: 결제 완료 후 1~3일 이내 출고 (영업일 기준)\n• 배송 업체: CJ대…` | 본문 내용 | @@ -52,9 +57,9 @@ _목록 응답: `data.data[]` 배열 항목의 필드 + `data.pagination`._ | is_active | boolean | `true` | active 여부 | | sort_order | integer | `0` | 표시 정렬 순서 값 (작을수록 우선) | | icon | string | `info-circle` | 아이콘 식별자 (아이콘 클래스/이름) | -| created_at | string | `2026-06-15 11:24:00` | 생성 일시 | -| updated_at | string | `2026-06-15 11:24:00` | 최종 수정 일시 | -| products_count | integer | `81` | products 개수 (집계) | +| created_at | string | `2026-07-30 23:35:46` | 생성 일시 | +| updated_at | string | `2026-07-30 23:35:46` | 최종 수정 일시 | +| products_count | integer | `85` | products 개수 (집계) | | language_count | integer | `2` | language 개수 (집계) | | abilities | object | `{"can_create":true,"can_update":true,"can_delete":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | @@ -73,27 +78,58 @@ HTTP/1.1 200 { "id": 1, "name": { - "ko": "API 문서 샘플 공통정보", - "en": "API Doc Sample Common Info" + "ko": "일반 배송 안내", + "en": "Standard Shipping" }, - "localized_name": "API 문서 샘플 공통정보", - "content": [], - "localized_content": "", + "localized_name": "일반 배송 안내", + "content": { + "ko": "• 배송 기간: 결제 완료 후 1~3일 이내 출고 (영업일 기준)\n• 배송 업체: CJ대한통운, 한진택배, 롯데택배\n• 배송 지역: 전국 배송 가능 (일부 도서산간 지역 제외)\n• 배송 조회: 마이페이지 > 주문내역에서 실시간 조회 가능\n• 배송비: 3만원 이상 구매 시 무료, 미만 시 3,000원", + "en": "• Delivery Time: Ships within 1-3 business days after payment\n• Carriers: CJ Logistics, Hanjin, Lotte\n• Coverage: Nationwide (some remote areas excluded)\n• Tracking: Available in My Page > Order History\n• Shipping Fee: Free for orders over 30,000 KRW, otherwise 3,000 KRW" + }, + "localized_content": "• 배송 기간: 결제 완료 후 1~3일 이내 출고 (영업일 기준)\n• 배송 업체: CJ대한통운, 한진택배, 롯데택배\n• 배송 지역: 전국 배송 가능 (일부 도서산간 지역 제외)\n• 배송 조회: 마이페이지 > 주문내역에서 실시간 조회 가능\n• 배송비: 3만원 이상 구매 시 무료, 미만 시 3,000원", "content_mode": "text", - "is_default": false, + "is_default": true, "is_active": true, "sort_order": 0, "icon": "info-circle", - "created_at": "2026-07-08 10:44:49", - "updated_at": "2026-07-08 10:44:49", - "products_count": 0, - "language_count": 0, + "created_at": "2026-07-30 23:35:46", + "updated_at": "2026-07-30 23:35:46", + "products_count": 85, + "language_count": 2, "abilities": { "can_create": true, "can_update": true, "can_delete": true } - } + }, + { + "id": 2, + "name": { + "ko": "당일 배송 안내", + "en": "Same-Day Delivery" + }, + "localized_name": "당일 배송 안내", + "content": { + "ko": "• 주문 마감: 오전 11시까지 결제 완료 시 당일 출고\n• 배송 지역: 서울, 경기, 인천 일부 지역\n• 배송비: 7만원 이상 무료, 미만 시 4,000원\n• 수령 시간: 오후 6시~9시 사이 도착\n• 주의: 재고 상황에 따라 익일 배송으로 변경될 수 있습니다", + "en": "• Order Deadline: Complete payment by 11 AM for same-day shipping\n• Coverage: Parts of Seoul, Gyeonggi, Incheon\n• Fee: Free for orders over 70,000 KRW, otherwise 4,000 KRW\n• Arrival: Between 6 PM - 9 PM\n• Note: May change to next-day delivery based on stock availability" + }, + "localized_content": "• 주문 마감: 오전 11시까지 결제 완료 시 당일 출고\n• 배송 지역: 서울, 경기, 인천 일부 지역\n• 배송비: 7만원 이상 무료, 미만 시 4,000원\n• 수령 시간: 오후 6시~9시 사이 도착\n• 주의: 재고 상황에 따라 익일 배송으로 변경될 수 있습니다", + "content_mode": "text", + "is_default": false, + "is_active": true, + "sort_order": 1, + "icon": "info-circle", + "created_at": "2026-07-30 23:35:46", + "updated_at": "2026-07-30 23:35:46", + "products_count": 0, + "language_count": 2, + "abilities": { + "can_create": true, + "can_update": true, + "can_delete": true + } + }, + "... (총 25건 중 2건 표시)" ], "abilities": { "can_create": true, @@ -102,12 +138,12 @@ HTTP/1.1 200 }, "pagination": { "current_page": 1, - "last_page": 1, + "last_page": 5, "per_page": 25, - "total": 1, + "total": 103, "from": 1, - "to": 1, - "has_more_pages": false + "to": 25, + "has_more_pages": true } } } @@ -119,6 +155,7 @@ HTTP/1.1 200 | --- | --- | --- | | 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 | | 403 | Forbidden | 요구 권한(`sirsoft-ecommerce.product-common-infos.read`)이 없는 경우 | +| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) | @@ -169,7 +206,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -203,7 +240,7 @@ Content-Type: application/json **요청 예시** ```http -DELETE /api/modules/sirsoft-ecommerce/admin/product-common-infos/1 HTTP/1.1 +DELETE /api/modules/sirsoft-ecommerce/admin/product-common-infos/{id} HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -211,29 +248,11 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) -_단건 응답: `data` 객체의 필드._ - -| 필드 | 타입 | 실측 예시값 | 용도/설명 | -| --- | --- | --- | --- | -| common_info_id | integer | `1` | common info 식별자 (연관 리소스 참조) | -| products_count | integer | `0` | products 개수 (집계) | + **응답 예시** -```http -HTTP/1.1 200 -``` - -```json -{ - "success": true, - "message": "공통정보가 삭제되었습니다.", - "data": { - "common_info_id": 1, - "products_count": 0 - } -} -``` + **에러 응답** @@ -263,7 +282,7 @@ HTTP/1.1 200 **요청 예시** ```http -GET /api/modules/sirsoft-ecommerce/admin/product-common-infos/1 HTTP/1.1 +GET /api/modules/sirsoft-ecommerce/admin/product-common-infos/{id} HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -271,62 +290,11 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) -_단건 응답: `data` 객체의 필드._ - -| 필드 | 타입 | 실측 예시값 | 용도/설명 | -| --- | --- | --- | --- | -| id | integer | `1` | 기본 키 (내부 식별자) | -| name | object | `{"ko":"API 문서 샘플 공통정보","en":"API Doc Sample Common Info"}` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | -| localized_name | string | `API 문서 샘플 공통정보` | `name` 의 현재 로케일 해석 값 (다국어 필드를 표시용 문자열로 해석) | -| content | array | `[]` | 본문 내용 | -| localized_content | string | `` | `content` 의 현재 로케일 해석 값 (다국어 필드를 표시용 문자열로 해석) | -| content_mode | string | `text` | 내용 모드 (text: 텍스트, html: HTML) | -| is_default | boolean | `false` | default 여부 | -| is_active | boolean | `true` | active 여부 | -| sort_order | integer | `0` | 표시 정렬 순서 값 (작을수록 우선) | -| icon | string | `info-circle` | 아이콘 식별자 (아이콘 클래스/이름) | -| created_at | string | `2026-07-08 10:44:49` | 생성 일시 | -| updated_at | string | `2026-07-08 10:44:49` | 최종 수정 일시 | -| products_count | integer | `0` | products 개수 (집계) | -| language_count | integer | `0` | language 개수 (집계) | -| abilities | object | `{"can_create":true,"can_update":true,"can_delete":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | + **응답 예시** -```http -HTTP/1.1 200 -``` - -```json -{ - "success": true, - "message": "공통정보 목록을 불러왔습니다.", - "data": { - "id": 1, - "name": { - "ko": "API 문서 샘플 공통정보", - "en": "API Doc Sample Common Info" - }, - "localized_name": "API 문서 샘플 공통정보", - "content": [], - "localized_content": "", - "content_mode": "text", - "is_default": false, - "is_active": true, - "sort_order": 0, - "icon": "info-circle", - "created_at": "2026-07-08 10:44:49", - "updated_at": "2026-07-08 10:44:49", - "products_count": 0, - "language_count": 0, - "abilities": { - "can_create": true, - "can_update": true, - "can_delete": true - } - } -} -``` + **에러 응답** @@ -364,7 +332,7 @@ HTTP/1.1 200 **요청 예시** ```http -PUT /api/modules/sirsoft-ecommerce/admin/product-common-infos/1 HTTP/1.1 +PUT /api/modules/sirsoft-ecommerce/admin/product-common-infos/{id} HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -386,11 +354,11 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** - + **에러 응답** @@ -421,7 +389,7 @@ Content-Type: application/json **요청 예시** ```http -PATCH /api/modules/sirsoft-ecommerce/admin/product-common-infos/1/toggle-active HTTP/1.1 +PATCH /api/modules/sirsoft-ecommerce/admin/product-common-infos/{id}/toggle-active HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -429,62 +397,11 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) -_단건 응답: `data` 객체의 필드._ - -| 필드 | 타입 | 실측 예시값 | 용도/설명 | -| --- | --- | --- | --- | -| id | integer | `1` | 기본 키 (내부 식별자) | -| name | object | `{"ko":"API 문서 샘플 공통정보","en":"API Doc Sample Common Info"}` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | -| localized_name | string | `API 문서 샘플 공통정보` | `name` 의 현재 로케일 해석 값 (다국어 필드를 표시용 문자열로 해석) | -| content | array | `[]` | 본문 내용 | -| localized_content | string | `` | `content` 의 현재 로케일 해석 값 (다국어 필드를 표시용 문자열로 해석) | -| content_mode | string | `text` | 내용 모드 (text: 텍스트, html: HTML) | -| is_default | boolean | `false` | default 여부 | -| is_active | boolean | `false` | active 여부 | -| sort_order | integer | `0` | 표시 정렬 순서 값 (작을수록 우선) | -| icon | string | `info-circle` | 아이콘 식별자 (아이콘 클래스/이름) | -| created_at | string | `2026-07-08 10:44:49` | 생성 일시 | -| updated_at | string | `2026-07-08 15:00:26` | 최종 수정 일시 | -| products_count | integer | `0` | products 개수 (집계) | -| language_count | integer | `0` | language 개수 (집계) | -| abilities | object | `{"can_create":true,"can_update":true,"can_delete":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | + **응답 예시** -```http -HTTP/1.1 200 -``` - -```json -{ - "success": true, - "message": "공통정보가 비활성화되었습니다.", - "data": { - "id": 1, - "name": { - "ko": "API 문서 샘플 공통정보", - "en": "API Doc Sample Common Info" - }, - "localized_name": "API 문서 샘플 공통정보", - "content": [], - "localized_content": "", - "content_mode": "text", - "is_default": false, - "is_active": false, - "sort_order": 0, - "icon": "info-circle", - "created_at": "2026-07-08 10:44:49", - "updated_at": "2026-07-08 15:00:26", - "products_count": 0, - "language_count": 0, - "abilities": { - "can_create": true, - "can_update": true, - "can_delete": true - } - } -} -``` + **에러 응답** diff --git a/modules/_bundled/sirsoft-ecommerce/docs/api/product-image.md b/modules/_bundled/sirsoft-ecommerce/docs/api/product-image.md index 8c499c56..d9aabb33 100644 --- a/modules/_bundled/sirsoft-ecommerce/docs/api/product-image.md +++ b/modules/_bundled/sirsoft-ecommerce/docs/api/product-image.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Product Image 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` diff --git a/modules/_bundled/sirsoft-ecommerce/docs/api/product-labels.md b/modules/_bundled/sirsoft-ecommerce/docs/api/product-labels.md index ac029b50..86abd57c 100644 --- a/modules/_bundled/sirsoft-ecommerce/docs/api/product-labels.md +++ b/modules/_bundled/sirsoft-ecommerce/docs/api/product-labels.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Product Labels 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` @@ -31,7 +31,7 @@ | active_only | query | boolean | 아니오 | — | 활성 라벨만 필터 (true 시 내부적으로 `is_active=true` 로 변환 — 기존 호환용) | | search | query | string | 아니오 | max 100 | 검색어 (지정한 검색 대상 필드에서 부분 일치) | | sort | query | string | 아니오 | `name_asc`, `name_desc`, `created_asc`, `created_desc` | 정렬 기준 (필드명, `-` 접두 시 내림차순) | -| locale | query | string | 아니오 | `ko`, `en`, `fr`, `ja` | 로케일 코드 (표시 언어/지역) | +| locale | query | string | 아니오 | `ko`, `en` | 로케일 코드 (표시 언어/지역) | **요청 예시** @@ -48,14 +48,14 @@ _목록 응답: `data.data[]` 배열 항목의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| id | integer | `37` | 기본 키 (내부 식별자) | -| name | object | `{"ko":"API 문서 샘플 라벨","en":"API Doc Sample Label"}` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | -| color | string | `#6B7280` | 라벨 색상 코드 (`#RRGGBB` 6자리 HEX, 뱃지 배경/글자색 등 표시에 사용) | +| id | integer | `29` | 기본 키 (내부 식별자) | +| name | object | `{"ko":"신상품","en":"New"}` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | +| color | string | `#22C55E` | 라벨 색상 코드 (`#RRGGBB` 6자리 HEX, 뱃지 배경/글자색 등 표시에 사용) | | is_active | boolean | `true` | active 여부 | -| sort_order | integer | `0` | 표시 정렬 순서 값 (작을수록 우선) | -| created_at | string | `2026-07-07 14:47:31` | 생성 일시 | -| updated_at | string | `2026-07-07 14:47:31` | 최종 수정 일시 | -| assignments_count | integer | `0` | assignments 개수 (집계) | +| sort_order | integer | `1` | 표시 정렬 순서 값 (작을수록 우선) | +| created_at | string | `2026-07-30 23:35:47` | 생성 일시 | +| updated_at | string | `2026-07-30 23:35:47` | 최종 수정 일시 | +| assignments_count | integer | `12` | assignments 개수 (집계) | | abilities | object | `{"can_create":true,"can_update":true,"can_delete":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | **응답 예시** @@ -71,23 +71,42 @@ HTTP/1.1 200 "data": { "data": [ { - "id": 1, + "id": 29, "name": { - "ko": "API 문서 샘플 라벨", - "en": "API Doc Sample Label" + "ko": "신상품", + "en": "New" }, - "color": null, + "color": "#22C55E", "is_active": true, - "sort_order": 0, - "created_at": "2026-07-08 10:44:49", - "updated_at": "2026-07-08 10:44:49", - "assignments_count": 0, + "sort_order": 1, + "created_at": "2026-07-30 23:35:47", + "updated_at": "2026-07-30 23:35:47", + "assignments_count": 12, "abilities": { "can_create": true, "can_update": true, "can_delete": true } - } + }, + { + "id": 30, + "name": { + "ko": "베스트", + "en": "Best" + }, + "color": "#F59E0B", + "is_active": true, + "sort_order": 2, + "created_at": "2026-07-30 23:35:47", + "updated_at": "2026-07-30 23:35:47", + "assignments_count": 18, + "abilities": { + "can_create": true, + "can_update": true, + "can_delete": true + } + }, + "... (총 10건 중 2건 표시)" ], "abilities": { "can_create": true, @@ -147,7 +166,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -181,7 +200,7 @@ Content-Type: application/json **요청 예시** ```http -DELETE /api/modules/sirsoft-ecommerce/admin/product-labels/1 HTTP/1.1 +DELETE /api/modules/sirsoft-ecommerce/admin/product-labels/{id} HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -189,29 +208,11 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) -_단건 응답: `data` 객체의 필드._ - -| 필드 | 타입 | 실측 예시값 | 용도/설명 | -| --- | --- | --- | --- | -| label_id | integer | `1` | label 식별자 (연관 리소스 참조) | -| products_count | integer | `0` | products 개수 (집계) | + **응답 예시** -```http -HTTP/1.1 200 -``` - -```json -{ - "success": true, - "message": "라벨이 삭제되었습니다.", - "data": { - "label_id": 1, - "products_count": 0 - } -} -``` + **에러 응답** @@ -241,7 +242,7 @@ HTTP/1.1 200 **요청 예시** ```http -GET /api/modules/sirsoft-ecommerce/admin/product-labels/1 HTTP/1.1 +GET /api/modules/sirsoft-ecommerce/admin/product-labels/{id} HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -249,50 +250,11 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) -_단건 응답: `data` 객체의 필드._ - -| 필드 | 타입 | 실측 예시값 | 용도/설명 | -| --- | --- | --- | --- | -| id | integer | `1` | 기본 키 (내부 식별자) | -| name | object | `{"ko":"API 문서 샘플 라벨","en":"API Doc Sample Label"}` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | -| color | null | `null` | 색상 코드 (예: #FF5733) | -| is_active | boolean | `true` | active 여부 | -| sort_order | integer | `0` | 표시 정렬 순서 값 (작을수록 우선) | -| created_at | string | `2026-07-08 10:44:49` | 생성 일시 | -| updated_at | string | `2026-07-08 10:44:49` | 최종 수정 일시 | -| assignments_count | integer | `0` | assignments 개수 (집계) | -| abilities | object | `{"can_create":true,"can_update":true,"can_delete":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | + **응답 예시** -```http -HTTP/1.1 200 -``` - -```json -{ - "success": true, - "message": "라벨 정보를 조회했습니다.", - "data": { - "id": 1, - "name": { - "ko": "API 문서 샘플 라벨", - "en": "API Doc Sample Label" - }, - "color": null, - "is_active": true, - "sort_order": 0, - "created_at": "2026-07-08 10:44:49", - "updated_at": "2026-07-08 10:44:49", - "assignments_count": 0, - "abilities": { - "can_create": true, - "can_update": true, - "can_delete": true - } - } -} -``` + **에러 응답** @@ -326,7 +288,7 @@ HTTP/1.1 200 **요청 예시** ```http -PUT /api/modules/sirsoft-ecommerce/admin/product-labels/1 HTTP/1.1 +PUT /api/modules/sirsoft-ecommerce/admin/product-labels/{id} HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -344,11 +306,11 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** - + **에러 응답** @@ -379,7 +341,7 @@ Content-Type: application/json **요청 예시** ```http -PATCH /api/modules/sirsoft-ecommerce/admin/product-labels/1/toggle-status HTTP/1.1 +PATCH /api/modules/sirsoft-ecommerce/admin/product-labels/{id}/toggle-status HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -387,50 +349,11 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) -_단건 응답: `data` 객체의 필드._ - -| 필드 | 타입 | 실측 예시값 | 용도/설명 | -| --- | --- | --- | --- | -| id | integer | `1` | 기본 키 (내부 식별자) | -| name | object | `{"ko":"API 문서 샘플 라벨","en":"API Doc Sample Label"}` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | -| color | null | `null` | 색상 코드 (예: #FF5733) | -| is_active | boolean | `false` | active 여부 | -| sort_order | integer | `0` | 표시 정렬 순서 값 (작을수록 우선) | -| created_at | string | `2026-07-08 10:44:49` | 생성 일시 | -| updated_at | string | `2026-07-08 15:00:27` | 최종 수정 일시 | -| assignments_count | integer | `0` | assignments 개수 (집계) | -| abilities | object | `{"can_create":true,"can_update":true,"can_delete":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | + **응답 예시** -```http -HTTP/1.1 200 -``` - -```json -{ - "success": true, - "message": "라벨 상태가 변경되었습니다.", - "data": { - "id": 1, - "name": { - "ko": "API 문서 샘플 라벨", - "en": "API Doc Sample Label" - }, - "color": null, - "is_active": false, - "sort_order": 0, - "created_at": "2026-07-08 10:44:49", - "updated_at": "2026-07-08 15:00:27", - "assignments_count": 0, - "abilities": { - "can_create": true, - "can_update": true, - "can_delete": true - } - } -} -``` + **에러 응답** diff --git a/modules/_bundled/sirsoft-ecommerce/docs/api/product-notice-templates.md b/modules/_bundled/sirsoft-ecommerce/docs/api/product-notice-templates.md index 5ed274e5..bcd60114 100644 --- a/modules/_bundled/sirsoft-ecommerce/docs/api/product-notice-templates.md +++ b/modules/_bundled/sirsoft-ecommerce/docs/api/product-notice-templates.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Product Notice Templates 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` @@ -29,7 +29,7 @@ | --- | --- | --- | --- | --- | --- | | search | query | string | 아니오 | max 200 | 검색어 (지정한 검색 대상 필드에서 부분 일치) | | active_only | query | boolean | 아니오 | — | true 시 활성(is_active) 템플릿만 조회 | -| per_page | query | string | 아니오 | 정수 1~100 또는 `all` | 페이지당 항목 수 (`all` 은 전체 조회, 숫자는 최대 100) | +| per_page | query | string | 아니오 | — | 페이지당 항목 수 (`all` 은 전체 조회, 숫자는 최대 100) | | page | query | integer | 아니오 | min 1 | 조회할 페이지 번호 (1부터 시작) | **요청 예시** @@ -47,17 +47,17 @@ _목록 응답: `data.data[]` 배열 항목의 필드 + `data.pagination`._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| id | integer | `172` | 기본 키 (내부 식별자) | -| name | object | `{"ko":"API 문서 샘플 고시템플릿","en":"API Doc Sample Notice Templ…` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | -| localized_name | string | `API 문서 샘플 고시템플릿` | `name` 의 현재 로케일 해석 값 (다국어 필드를 표시용 문자열로 해석) | +| id | integer | `1` | 기본 키 (내부 식별자) | +| name | object | `{"ko":"의류","en":"Clothing"}` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | +| localized_name | string | `의류` | `name` 의 현재 로케일 해석 값 (다국어 필드를 표시용 문자열로 해석) | | category | string | `clothing` | 이 템플릿이 적용되는 품목 카테고리 식별자 | -| fields | array | `[{"label":"품명","value":"샘플"}]` | 고시 항목 정의 배열 (항목별 name/content 다국어 — 상품 등록 시 고시 항목 자동 채움에 사용) | -| fields_count | integer | `1` | fields 개수 (집계) | +| fields | array | `[{"name":{"ko":"제품 소재 (섬유의 조성 또는 혼용률)","en":"Material Com…` | 고시 항목 정의 배열 (항목별 name/content 다국어 — 상품 등록 시 고시 항목 자동 채움에 사용) | +| fields_count | integer | `9` | fields 개수 (집계) | | is_active | boolean | `true` | active 여부 | -| sort_order | integer | `0` | 표시 정렬 순서 값 (작을수록 우선) | +| sort_order | integer | `1` | 표시 정렬 순서 값 (작을수록 우선) | | icon | string | `file-alt` | 아이콘 식별자 (아이콘 클래스/이름) | -| created_at | string | `2026-07-07 14:47:31` | 생성 일시 | -| updated_at | string | `2026-07-07 14:47:31` | 최종 수정 일시 | +| created_at | string | `2026-07-30 23:35:46` | 생성 일시 | +| updated_at | string | `2026-07-30 23:35:46` | 최종 수정 일시 | | abilities | object | `{"can_create":true,"can_update":true,"can_delete":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | **응답 예시** @@ -75,29 +75,130 @@ HTTP/1.1 200 { "id": 1, "name": { - "ko": "API 문서 샘플 고시템플릿", - "en": "API Doc Sample Notice Template" + "ko": "의류", + "en": "Clothing" }, - "localized_name": "API 문서 샘플 고시템플릿", - "category": null, + "localized_name": "의류", + "category": "clothing", "fields": [ { - "label": "품명", - "value": "샘플" - } + "name": { + "ko": "제품 소재 (섬유의 조성 또는 혼용률)", + "en": "Material Composition" + }, + "content": { + "ko": "상세페이지 참조", + "en": "See product page" + } + }, + { + "name": { + "ko": "색상", + "en": "Color" + }, + "content": { + "ko": "상세페이지 참조", + "en": "See product page" + } + }, + { + "name": { + "ko": "치수", + "en": "Size" + }, + "content": { + "ko": "상세페이지 참조", + "en": "See product page" + } + }, + { + "name": { + "ko": "제조자/수입자", + "en": "Manufacturer/Importer" + }, + "content": { + "ko": "상세페이지 참조", + "en": "See product page" + } + }, + { + "name": { + "ko": "제조국", + "en": "Country of Origin" + }, + "content": { + "ko": "상세페이지 참조", + "en": "See product page" + } + }, + "... (총 9건 중 5건 표시)" ], - "fields_count": 1, - "is_active": true, - "sort_order": 0, - "icon": "file-alt", - "created_at": "2026-07-08 10:44:49", - "updated_at": "2026-07-08 10:44:49", - "abilities": { - "can_create": true, - "can_update": true, - "can_delete": true - } - } + "...": "(7개 키 생략, 총 12개)" + }, + { + "id": 2, + "name": { + "ko": "구두/신발", + "en": "Shoes" + }, + "localized_name": "구두/신발", + "category": "shoes", + "fields": [ + { + "name": { + "ko": "제품 주소재 (겉감/안감)", + "en": "Main Material (Outer/Inner)" + }, + "content": { + "ko": "상세페이지 참조", + "en": "See product page" + } + }, + { + "name": { + "ko": "색상", + "en": "Color" + }, + "content": { + "ko": "상세페이지 참조", + "en": "See product page" + } + }, + { + "name": { + "ko": "치수 (발 길이)", + "en": "Size (Foot Length)" + }, + "content": { + "ko": "상세페이지 참조", + "en": "See product page" + } + }, + { + "name": { + "ko": "굽 높이", + "en": "Heel Height" + }, + "content": { + "ko": "상세페이지 참조", + "en": "See product page" + } + }, + { + "name": { + "ko": "제조자/수입자", + "en": "Manufacturer/Importer" + }, + "content": { + "ko": "상세페이지 참조", + "en": "See product page" + } + }, + "... (총 9건 중 5건 표시)" + ], + "...": "(7개 키 생략, 총 12개)" + }, + "... (총 25건 중 2건 표시)" ], "abilities": { "can_create": true, @@ -106,12 +207,11 @@ HTTP/1.1 200 }, "pagination": { "current_page": 1, - "last_page": 1, + "last_page": 3, "per_page": 25, - "total": 1, + "total": 57, "from": 1, - "to": 1, - "has_more_pages": false + "...": "(2개 키 생략, 총 7개)" } } } @@ -172,7 +272,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -206,7 +306,7 @@ Content-Type: application/json **요청 예시** ```http -DELETE /api/modules/sirsoft-ecommerce/admin/product-notice-templates/1 HTTP/1.1 +DELETE /api/modules/sirsoft-ecommerce/admin/product-notice-templates/{id} HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -214,27 +314,11 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) -_단건 응답: `data` 객체의 필드._ - -| 필드 | 타입 | 실측 예시값 | 용도/설명 | -| --- | --- | --- | --- | -| template_id | integer | `1` | template 식별자 (연관 리소스 참조) | + **응답 예시** -```http -HTTP/1.1 200 -``` - -```json -{ - "success": true, - "message": "상품정보제공고시가 삭제되었습니다.", - "data": { - "template_id": 1 - } -} -``` + **에러 응답** @@ -264,7 +348,7 @@ HTTP/1.1 200 **요청 예시** ```http -GET /api/modules/sirsoft-ecommerce/admin/product-notice-templates/1 HTTP/1.1 +GET /api/modules/sirsoft-ecommerce/admin/product-notice-templates/{id} HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -272,61 +356,11 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) -_단건 응답: `data` 객체의 필드._ - -| 필드 | 타입 | 실측 예시값 | 용도/설명 | -| --- | --- | --- | --- | -| id | integer | `1` | 기본 키 (내부 식별자) | -| name | object | `{"ko":"API 문서 샘플 고시템플릿","en":"API Doc Sample Notice Templ…` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | -| localized_name | string | `API 문서 샘플 고시템플릿` | `name` 의 현재 로케일 해석 값 (다국어 필드를 표시용 문자열로 해석) | -| category | null | `null` | 품목 카테고리 | -| fields | array | `[{"label":"품명","value":"샘플"}]` | 필드 정의 JSON | -| fields_count | integer | `1` | fields 개수 (집계) | -| is_active | boolean | `true` | active 여부 | -| sort_order | integer | `0` | 표시 정렬 순서 값 (작을수록 우선) | -| icon | string | `file-alt` | 아이콘 식별자 (아이콘 클래스/이름) | -| created_at | string | `2026-07-08 10:44:49` | 생성 일시 | -| updated_at | string | `2026-07-08 10:44:49` | 최종 수정 일시 | -| abilities | object | `{"can_create":true,"can_update":true,"can_delete":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | + **응답 예시** -```http -HTTP/1.1 200 -``` - -```json -{ - "success": true, - "message": "상품정보제공고시 목록을 불러왔습니다.", - "data": { - "id": 1, - "name": { - "ko": "API 문서 샘플 고시템플릿", - "en": "API Doc Sample Notice Template" - }, - "localized_name": "API 문서 샘플 고시템플릿", - "category": null, - "fields": [ - { - "label": "품명", - "value": "샘플" - } - ], - "fields_count": 1, - "is_active": true, - "sort_order": 0, - "icon": "file-alt", - "created_at": "2026-07-08 10:44:49", - "updated_at": "2026-07-08 10:44:49", - "abilities": { - "can_create": true, - "can_update": true, - "can_delete": true - } - } -} -``` + **에러 응답** @@ -363,7 +397,7 @@ HTTP/1.1 200 **요청 예시** ```http -PUT /api/modules/sirsoft-ecommerce/admin/product-notice-templates/1 HTTP/1.1 +PUT /api/modules/sirsoft-ecommerce/admin/product-notice-templates/{id} HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -384,11 +418,11 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** - + **에러 응답** @@ -419,7 +453,7 @@ Content-Type: application/json **요청 예시** ```http -POST /api/modules/sirsoft-ecommerce/admin/product-notice-templates/1/copy HTTP/1.1 +POST /api/modules/sirsoft-ecommerce/admin/product-notice-templates/{id}/copy HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -427,61 +461,11 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) -_단건 응답: `data` 객체의 필드._ - -| 필드 | 타입 | 실측 예시값 | 용도/설명 | -| --- | --- | --- | --- | -| id | integer | `2` | 기본 키 (내부 식별자) | -| name | object | `{"ko":"API 문서 샘플 고시템플릿 (복사)","en":"API Doc Sample Notice …` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | -| localized_name | string | `API 문서 샘플 고시템플릿 (복사)` | `name` 의 현재 로케일 해석 값 (다국어 필드를 표시용 문자열로 해석) | -| category | null | `null` | 품목 카테고리 | -| fields | array | `[{"label":"품명","value":"샘플"}]` | 필드 정의 JSON | -| fields_count | integer | `1` | fields 개수 (집계) | -| is_active | boolean | `true` | active 여부 | -| sort_order | integer | `1` | 표시 정렬 순서 값 (작을수록 우선) | -| icon | string | `file-alt` | 아이콘 식별자 (아이콘 클래스/이름) | -| created_at | string | `2026-07-08 15:00:27` | 생성 일시 | -| updated_at | string | `2026-07-08 15:00:27` | 최종 수정 일시 | -| abilities | object | `{"can_create":true,"can_update":true,"can_delete":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | + **응답 예시** -```http -HTTP/1.1 201 -``` - -```json -{ - "success": true, - "message": "상품정보제공고시가 복사되었습니다.", - "data": { - "id": 2, - "name": { - "ko": "API 문서 샘플 고시템플릿 (복사)", - "en": "API Doc Sample Notice Template (Copy)" - }, - "localized_name": "API 문서 샘플 고시템플릿 (복사)", - "category": null, - "fields": [ - { - "label": "품명", - "value": "샘플" - } - ], - "fields_count": 1, - "is_active": true, - "sort_order": 1, - "icon": "file-alt", - "created_at": "2026-07-08 15:00:27", - "updated_at": "2026-07-08 15:00:27", - "abilities": { - "can_create": true, - "can_update": true, - "can_delete": true - } - } -} -``` + **에러 응답** @@ -511,7 +495,7 @@ HTTP/1.1 201 **요청 예시** ```http -PATCH /api/modules/sirsoft-ecommerce/admin/product-notice-templates/1/toggle-active HTTP/1.1 +PATCH /api/modules/sirsoft-ecommerce/admin/product-notice-templates/{id}/toggle-active HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -519,61 +503,11 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) -_단건 응답: `data` 객체의 필드._ - -| 필드 | 타입 | 실측 예시값 | 용도/설명 | -| --- | --- | --- | --- | -| id | integer | `1` | 기본 키 (내부 식별자) | -| name | object | `{"ko":"API 문서 샘플 고시템플릿","en":"API Doc Sample Notice Templ…` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | -| localized_name | string | `API 문서 샘플 고시템플릿` | `name` 의 현재 로케일 해석 값 (다국어 필드를 표시용 문자열로 해석) | -| category | null | `null` | 품목 카테고리 | -| fields | array | `[{"label":"품명","value":"샘플"}]` | 필드 정의 JSON | -| fields_count | integer | `1` | fields 개수 (집계) | -| is_active | boolean | `false` | active 여부 | -| sort_order | integer | `0` | 표시 정렬 순서 값 (작을수록 우선) | -| icon | string | `file-alt` | 아이콘 식별자 (아이콘 클래스/이름) | -| created_at | string | `2026-07-08 10:44:49` | 생성 일시 | -| updated_at | string | `2026-07-08 15:00:27` | 최종 수정 일시 | -| abilities | object | `{"can_create":true,"can_update":true,"can_delete":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | + **응답 예시** -```http -HTTP/1.1 200 -``` - -```json -{ - "success": true, - "message": "상품정보제공고시가 비활성화되었습니다.", - "data": { - "id": 1, - "name": { - "ko": "API 문서 샘플 고시템플릿", - "en": "API Doc Sample Notice Template" - }, - "localized_name": "API 문서 샘플 고시템플릿", - "category": null, - "fields": [ - { - "label": "품명", - "value": "샘플" - } - ], - "fields_count": 1, - "is_active": false, - "sort_order": 0, - "icon": "file-alt", - "created_at": "2026-07-08 10:44:49", - "updated_at": "2026-07-08 15:00:27", - "abilities": { - "can_create": true, - "can_update": true, - "can_delete": true - } - } -} -``` + **에러 응답** diff --git a/modules/_bundled/sirsoft-ecommerce/docs/api/products.md b/modules/_bundled/sirsoft-ecommerce/docs/api/products.md index 7273d09b..e491bf18 100644 --- a/modules/_bundled/sirsoft-ecommerce/docs/api/products.md +++ b/modules/_bundled/sirsoft-ecommerce/docs/api/products.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Products 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` @@ -45,6 +45,7 @@ | min_stock | query | integer | 아니오 | — | 재고 범위 필터 하한 (재고 수량이 이 값 이상) | | max_stock | query | integer | 아니오 | — | 재고 범위 필터 상한 (재고 수량이 이 값 이하) | | shipping_policy_id | query | integer | 아니오 | — | shipping policy 식별자 | +| with_options | query | boolean | 아니오 | 기본 `false` | 옵션 배열(`options`)을 응답에 포함할지. 기본값에서는 옵션 상세 대신 집계(`options_count`/`options_total_count`/`option_stock_sum`)만 내려갑니다. 화면은 행을 펼칠 때 별도 배치 조회를 씁니다 | | sort_by | query | string | 아니오 | `created_at`, `updated_at`, `selling_price`, `stock_quantity`, `name` | 정렬 기준 필드명 | | sort_order | query | string | 아니오 | `asc`, `desc` | 정렬 방향 (asc 오름차순 / desc 내림차순) | | per_page | query | integer | 아니오 | min 10, max 100 | 페이지당 항목 수 | @@ -55,7 +56,7 @@ **요청 예시** ```http -GET /api/modules/sirsoft-ecommerce/admin/products?search_field=all&search_keyword=%EC%98%88%EC%8B%9C%EA%B0%92&category_id=1&no_category=1&date_type=%EC%98%88%EC%8B%9C%EA%B0%92&start_date=2026-01-01&end_date=2026-01-01&sales_status=%EC%98%88%EC%8B%9C%EA%B0%92&display_status=%EC%98%88%EC%8B%9C%EA%B0%92&brand_id=1&no_brand=1&tax_status=%EC%98%88%EC%8B%9C%EA%B0%92&price_type=%EC%98%88%EC%8B%9C%EA%B0%92&min_price=1&max_price=1&min_stock=1&max_stock=1&shipping_policy_id=1&sort_by=created_at&sort_order=asc&per_page=1&page=1 HTTP/1.1 +GET /api/modules/sirsoft-ecommerce/admin/products?search_field=all&search_keyword=%EC%98%88%EC%8B%9C%EA%B0%92&category_id=1&no_category=1&date_type=%EC%98%88%EC%8B%9C%EA%B0%92&start_date=2026-01-01&end_date=2026-01-01&sales_status=%EC%98%88%EC%8B%9C%EA%B0%92&display_status=%EC%98%88%EC%8B%9C%EA%B0%92&brand_id=1&no_brand=1&tax_status=%EC%98%88%EC%8B%9C%EA%B0%92&price_type=%EC%98%88%EC%8B%9C%EA%B0%92&min_price=1&max_price=1&min_stock=1&max_stock=1&shipping_policy_id=1&with_options=1&sort_by=created_at&sort_order=asc&per_page=1&page=1 HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -67,45 +68,45 @@ _목록 응답: `data.data[]` 배열 항목의 필드 + `data.pagination`._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| number | integer | `115` | 목록에서의 순번 (페이지네이션 반영 행 번호 — HasRowNumber 파생) | -| id | integer | `322` | 기본 키 (내부 식별자) | -| name | object | `{"ko":"eum et quia","en":"tenetur id quae"}` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | -| name_localized | string | `eum et quia` | `name` 의 현재 로케일 해석 값 (다국어 필드를 표시용 문자열로 해석) | -| product_code | string | `PROD-GJUX-1484` | 상품코드 (상품 고유 관리 식별자) | -| sku | string | `SKU-MRAD-9306` | 재고관리코드(SKU) | +| number | integer | `100` | 목록에서의 순번 (페이지네이션 반영 행 번호 — HasRowNumber 파생) | +| id | integer | `1831` | 기본 키 (내부 식별자) | +| name | object | `{"ko":"겨울 패딩 점퍼 #100","en":"Winter Padded Jacket #100"}` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | +| name_localized | string | `겨울 패딩 점퍼 #100` | `name` 의 현재 로케일 해석 값 (다국어 필드를 표시용 문자열로 해석) | +| product_code | string | `GGK6A9N8PXNR35OQ` | 상품코드 (상품 고유 관리 식별자) | +| sku | string | `JK-0100` | 재고관리코드(SKU) | | thumbnail_url | string | `/api/modules/sirsoft-ecommerce/produc…` | thumbnail URL | -| list_price | integer | `112594` | 정가 (기본통화 자릿수로 정규화된 값) | -| list_price_formatted | string | `112,594원` | `list_price` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | -| selling_price | integer | `88949` | 판매가 (기본통화 자릿수로 정규화된 값) | -| selling_price_formatted | string | `88,949원` | `selling_price` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | -| discount_rate | integer | `21` | 할인율(%) (정가 대비 판매가 할인 비율, (1 - 판매가/정가) × 100) | -| multi_currency_list_price | object | `{"KRW":{"price":112594,"formatted":"112,594원","is_default…` | 통화별 정가 맵 (통화코드 → {price, formatted, is_default, editable}, 설정된 모든 통화의 환산 정가) | -| multi_currency_selling_price | object | `{"KRW":{"price":88949,"formatted":"88,949원","is_default":…` | 통화별 판매가 맵 (통화코드 → {price, formatted, is_default, editable}, 설정된 모든 통화의 환산 판매가) | -| stock_quantity | integer | `22` | 재고 수량 (옵션 사용 시 옵션 재고 합계) | -| safe_stock_quantity | integer | `12` | 안전재고 수량 (이 값 미만이면 재고 부족으로 표시) | +| list_price | integer | `200000` | 정가 (기본통화 자릿수로 정규화된 값) | +| list_price_formatted | string | `200,000원` | `list_price` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | +| selling_price | integer | `169000` | 판매가 (기본통화 자릿수로 정규화된 값) | +| selling_price_formatted | string | `169,000원` | `selling_price` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | +| discount_rate | number | `15.5` | 할인율(%) (정가 대비 판매가 할인 비율, (1 - 판매가/정가) × 100) | +| multi_currency_list_price | object | `{"KRW":{"price":200000,"formatted":"200,000원","is_default…` | 통화별 정가 맵 (통화코드 → {price, formatted, is_default, editable}, 설정된 모든 통화의 환산 정가) | +| multi_currency_selling_price | object | `{"KRW":{"price":169000,"formatted":"169,000원","is_default…` | 통화별 판매가 맵 (통화코드 → {price, formatted, is_default, editable}, 설정된 모든 통화의 환산 판매가) | +| stock_quantity | integer | `213` | 재고 수량 (옵션 사용 시 옵션 재고 합계) | +| safe_stock_quantity | integer | `18` | 안전재고 수량 (이 값 미만이면 재고 부족으로 표시) | | is_below_safe_stock | boolean | `false` | below safe stock 여부 | -| option_stock_sum | integer | `51` | 활성 옵션의 재고 합계 (is_active 옵션들의 stock_quantity 총합) | +| option_stock_sum | integer | `213` | 활성 옵션의 재고 합계 (is_active 옵션들의 stock_quantity 총합) | | sales_status | string | `on_sale` | 판매상태 값 (on_sale 판매중 / suspended 판매중지 / sold_out 품절 / coming_soon 출시예정) | | sales_status_label | string | `판매중` | `sales_status` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | | sales_status_variant | string | `success` | `sales_status` 값의 표시 변형 키 (UI 배지 색상/스타일) | | display_status | string | `visible` | 전시상태 값 (visible 전시 / hidden 숨김) | | display_status_label | string | `전시` | `display_status` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | | display_status_variant | string | `success` | `display_status` 값의 표시 변형 키 (UI 배지 색상/스타일) | -| categories | array | `[]` | 소속 카테고리 목록 (각 항목: id·현지화 이름·대표 여부. categories 관계 eager load 시에만 채워짐) | -| primary_category | string | `바지` | 대표 카테고리명 (is_primary 카테고리의 현지화 이름) | -| categories_with_path | array | `[]` | 소속 카테고리 목록 + 경로 (각 항목: id·breadcrumb path·대표 여부) | -| brand_name | string | `ASUS` | 브랜드명 (연관 브랜드의 현지화 이름) | -| shipping_policy_id | integer | `31` | shipping policy 식별자 (연관 리소스 참조) | +| categories | array | `[{"id":56,"name":"스포츠","is_primary":0},{"id":57,"name":"축…` | 소속 카테고리 목록 (각 항목: id·현지화 이름·대표 여부. categories 관계 eager load 시에만 채워짐) | +| primary_category | string | `축구` | 대표 카테고리명 (is_primary 카테고리의 현지화 이름) | +| categories_with_path | array | `[{"id":56,"path":[{"id":56,"name":"스포츠","slug":"sports"}]…` | 소속 카테고리 목록 + 경로 (각 항목: id·breadcrumb path·대표 여부) | +| brand_name | string | `유니클로` | 브랜드명 (연관 브랜드의 현지화 이름) | +| shipping_policy_id | integer | `444` | shipping policy 식별자 (연관 리소스 참조) | | shipping_policy_name | string | `국내 무료배송` | 배송정책명 (연관 배송정책의 현지화 이름) | | min_purchase_qty | integer | `1` | 최소 구매 수량 (1회 주문 시 이 수량 이상 구매) | | max_purchase_qty | integer | `0` | 최대 구매 수량 (0=무제한) | -| has_options | boolean | `false` | options 여부 | -| options_count | integer | `1` | options 개수 (집계) | -| options | array | `[{"id":1597,"option_code":"OPT-JLNF-2511","option_values"…` | 활성 옵션(SKU) 목록 (각 옵션의 코드·옵션값·가격·재고 등, ProductOptionResource) | +| has_options | boolean | `true` | options 여부 | +| options_count | integer | `9` | options 개수 (집계) | +| options_total_count | integer | `9` | options total 개수 (집계) | | review_count | integer | `0` | review 개수 (집계) | | rating_avg | integer | `0` | 평균 별점 (공개 리뷰 별점 평균, 소수 1자리 반올림) | -| created_at | string | `2026-07-07 14:47:31` | 생성 일시 | -| updated_at | string | `2026-07-07 14:47:31` | 최종 수정 일시 | +| created_at | string | `2026-07-30 23:36:17` | 생성 일시 | +| updated_at | string | `2026-08-05 07:27:04` | 최종 수정 일시 | | is_owner | boolean | `false` | 현재 인증 사용자가 이 리소스의 소유자인지 여부 (BaseApiResource 표준 메타) | | abilities | object | `{"can_update":true,"can_delete":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | @@ -122,369 +123,28 @@ HTTP/1.1 200 "data": { "data": [ { - "number": 3, - "id": 3, + "number": 100, + "id": 1831, "name": { - "ko": "nisi et corrupti", - "en": "suscipit esse id" + "ko": "겨울 패딩 점퍼 #100", + "en": "Winter Padded Jacket #100" }, - "name_localized": "nisi et corrupti", - "product_code": "PROD-YNAZ-3669", - "sku": "SKU-EOQY-1167", - "thumbnail_url": null, - "list_price": 22605, - "list_price_formatted": "22,605원", - "selling_price": 20118, - "selling_price_formatted": "20,118원", - "discount_rate": 11, - "multi_currency_list_price": { - "KRW": { - "price": 22605, - "formatted": "22,605원", - "is_default": true, - "editable": true - }, - "USD": { - "price": 19.21, - "formatted": "$19.21", - "is_default": false, - "editable": false, - "exchange_rate": 0.85 - }, - "JPY": { - "price": 2599, - "formatted": "¥2,599", - "is_default": false, - "editable": false, - "exchange_rate": 115 - }, - "CNY": { - "price": 131.11, - "formatted": "元131.11", - "is_default": false, - "editable": false, - "exchange_rate": 5.8 - }, - "EUR": { - "price": 17.63, - "formatted": "€17.63", - "is_default": false, - "editable": false, - "exchange_rate": 0.78 - } - }, - "multi_currency_selling_price": { - "KRW": { - "price": 20118, - "formatted": "20,118원", - "is_default": true, - "editable": true - }, - "USD": { - "price": 17.1, - "formatted": "$17.10", - "is_default": false, - "editable": false, - "exchange_rate": 0.85 - }, - "JPY": { - "price": 2313, - "formatted": "¥2,313", - "is_default": false, - "editable": false, - "exchange_rate": 115 - }, - "CNY": { - "price": 116.68, - "formatted": "元116.68", - "is_default": false, - "editable": false, - "exchange_rate": 5.8 - }, - "EUR": { - "price": 15.69, - "formatted": "€15.69", - "is_default": false, - "editable": false, - "exchange_rate": 0.78 - } - }, - "stock_quantity": 164, - "safe_stock_quantity": 8, - "is_below_safe_stock": false, - "option_stock_sum": 24, - "sales_status": "on_sale", - "sales_status_label": "판매중", - "sales_status_variant": "success", - "display_status": "visible", - "display_status_label": "전시", - "display_status_variant": "success", - "categories": [], - "primary_category": null, - "categories_with_path": [], - "brand_name": null, - "shipping_policy_id": null, - "shipping_policy_name": null, - "min_purchase_qty": 1, - "max_purchase_qty": 0, - "has_options": false, - "options_count": 1, - "options": [ - { - "id": 1, - "option_code": "OPT-IXWA-1855", - "option_values": [ - { - "key": { - "ko": "색상", - "en": "Color" - }, - "value": { - "ko": "흰색", - "en": "Blue" - } - }, - { - "key": { - "ko": "사이즈", - "en": "Size" - }, - "value": { - "ko": "M", - "en": "M" - } - } - ], - "option_values_localized": { - "색상": "흰색", - "사이즈": "M" - }, - "option_name": { - "ko": "흰색/M", - "en": "Blue/M" - }, - "option_name_localized": "흰색/M", - "price_adjustment": 1000, - "price_adjustment_formatted": "+1,000원", - "price_adjustment_type": "increase", - "list_price": 23605, - "list_price_formatted": "23,605원", - "selling_price": 21118, - "selling_price_formatted": "21,118원", - "final_price": 21118, - "final_price_formatted": "21,118원", - "multi_currency_list_price": { - "KRW": { - "price": 23605, - "formatted": "23,605원", - "is_default": true, - "editable": true - }, - "USD": { - "price": 20.06, - "formatted": "$20.06", - "is_default": false, - "editable": false, - "exchange_rate": 0.85 - }, - "JPY": { - "price": 2714, - "formatted": "¥2,714", - "is_default": false, - "editable": false, - "exchange_rate": 115 - }, - "CNY": { - "price": 136.91, - "formatted": "元136.91", - "is_default": false, - "editable": false, - "exchange_rate": 5.8 - }, - "EUR": { - "price": 18.41, - "formatted": "€18.41", - "is_default": false, - "editable": false, - "exchange_rate": 0.78 - } - }, - "multi_currency_selling_price": { - "KRW": { - "price": 21118, - "formatted": "21,118원", - "is_default": true, - "editable": true - }, - "USD": { - "price": 17.95, - "formatted": "$17.95", - "is_default": false, - "editable": false, - "exchange_rate": 0.85 - }, - "JPY": { - "price": 2428, - "formatted": "¥2,428", - "is_default": false, - "editable": false, - "exchange_rate": 115 - }, - "CNY": { - "price": 122.48, - "formatted": "元122.48", - "is_default": false, - "editable": false, - "exchange_rate": 5.8 - }, - "EUR": { - "price": 16.47, - "formatted": "€16.47", - "is_default": false, - "editable": false, - "exchange_rate": 0.78 - } - }, - "stock_quantity": 24, - "safe_stock_quantity": 11, - "is_below_safe_stock": false, - "is_sold_out": false, - "is_default": false, - "is_active": true, - "sku": "SKU-OPT-MMYU-1269", - "sort_order": 75 - } - ], - "review_count": 0, - "rating_avg": 0, - "created_at": "2026-07-08 10:44:49", - "updated_at": "2026-07-08 10:44:49", - "is_owner": false, - "abilities": { - "can_update": true, - "can_delete": true - } + "name_localized": "겨울 패딩 점퍼 #100", + "product_code": "GGK6A9N8PXNR35OQ", + "...": "(36개 키 생략, 총 41개)" }, { - "number": 2, - "id": 2, + "number": 99, + "id": 1830, "name": { - "ko": "repudiandae et et", - "en": "velit alias distinctio" + "ko": "무선 마우스 세트 #99", + "en": "Wireless Mouse Set #99" }, - "name_localized": "repudiandae et et", - "product_code": "PROD-XSMH-5438", - "sku": "SKU-XXSC-9588", - "thumbnail_url": null, - "list_price": 654947, - "list_price_formatted": "654,947원", - "selling_price": 465012, - "selling_price_formatted": "465,012원", - "discount_rate": 29, - "multi_currency_list_price": { - "KRW": { - "price": 654947, - "formatted": "654,947원", - "is_default": true, - "editable": true - }, - "USD": { - "price": 556.7, - "formatted": "$556.70", - "is_default": false, - "editable": false, - "exchange_rate": 0.85 - }, - "JPY": { - "price": 75318, - "formatted": "¥75,318", - "is_default": false, - "editable": false, - "exchange_rate": 115 - }, - "CNY": { - "price": 3798.69, - "formatted": "元3,798.69", - "is_default": false, - "editable": false, - "exchange_rate": 5.8 - }, - "EUR": { - "price": 510.86, - "formatted": "€510.86", - "is_default": false, - "editable": false, - "exchange_rate": 0.78 - } - }, - "multi_currency_selling_price": { - "KRW": { - "price": 465012, - "formatted": "465,012원", - "is_default": true, - "editable": true - }, - "USD": { - "price": 395.26, - "formatted": "$395.26", - "is_default": false, - "editable": false, - "exchange_rate": 0.85 - }, - "JPY": { - "price": 53476, - "formatted": "¥53,476", - "is_default": false, - "editable": false, - "exchange_rate": 115 - }, - "CNY": { - "price": 2697.07, - "formatted": "元2,697.07", - "is_default": false, - "editable": false, - "exchange_rate": 5.8 - }, - "EUR": { - "price": 362.71, - "formatted": "€362.71", - "is_default": false, - "editable": false, - "exchange_rate": 0.78 - } - }, - "stock_quantity": 161, - "safe_stock_quantity": 49, - "is_below_safe_stock": false, - "option_stock_sum": 0, - "sales_status": "on_sale", - "sales_status_label": "판매중", - "sales_status_variant": "success", - "display_status": "visible", - "display_status_label": "전시", - "display_status_variant": "success", - "categories": [], - "primary_category": null, - "categories_with_path": [], - "brand_name": null, - "shipping_policy_id": null, - "shipping_policy_name": null, - "min_purchase_qty": 1, - "max_purchase_qty": 0, - "has_options": false, - "options_count": 0, - "options": [], - "review_count": 0, - "rating_avg": 0, - "created_at": "2026-07-08 10:44:49", - "updated_at": "2026-07-08 10:44:49", - "is_owner": false, - "abilities": { - "can_update": true, - "can_delete": true - } + "name_localized": "무선 마우스 세트 #99", + "product_code": "J9HTLXFNIJZHI4B1", + "...": "(36개 키 생략, 총 41개)" }, - "... (총 3건 중 2건 표시)" + "... (총 25건 중 2건 표시)" ], "abilities": { "can_create": true, @@ -492,24 +152,26 @@ HTTP/1.1 200 "can_delete": true }, "statistics": { - "total": 3, + "total": 100, "sales_status": { - "on_sale": 3 + "coming_soon": 2, + "on_sale": 97, + "sold_out": 1 }, "display_status": { - "visible": 3 + "hidden": 2, + "visible": 98 }, - "low_stock_count": 0, + "low_stock_count": 4, "out_of_stock_count": 0 }, "pagination": { "current_page": 1, - "last_page": 1, + "last_page": 4, "per_page": 25, - "total": 3, + "total": 100, "from": 1, - "to": 3, - "has_more_pages": false + "...": "(2개 키 생략, 총 7개)" } } } @@ -662,7 +324,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -719,7 +381,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -774,7 +436,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -829,7 +491,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -896,7 +558,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -930,7 +592,7 @@ Content-Type: application/json **요청 예시** ```http -GET /api/modules/sirsoft-ecommerce/admin/products/by-code/APIDOCSAMPLE01 HTTP/1.1 +GET /api/modules/sirsoft-ecommerce/admin/products/by-code/{code} HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -938,132 +600,11 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) -_단건 응답: `data` 객체의 필드._ - -| 필드 | 타입 | 실측 예시값 | 용도/설명 | -| --- | --- | --- | --- | -| id | integer | `1` | 기본 키 (내부 식별자) | -| name | object | `{"ko":"API 문서 샘플 상품","en":"API Doc Sample Product"}` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | -| name_localized | string | `API 문서 샘플 상품` | `name` 의 현재 로케일 해석 값 (다국어 필드를 표시용 문자열로 해석) | -| product_code | string | `APIDOCSAMPLE01` | 상품코드 | -| sku | string | `SKU-DKOK-1319` | SKU | -| categories | array | `[]` | 소속 카테고리 목록 (각 항목: id·현지화 이름·대표 여부. categories 관계 eager load 시에만 채워짐) | -| category_ids | array | `[]` | category 식별자 배열 (연관 리소스 참조) | -| primary_category_id | null | `null` | primary category 식별자 (연관 리소스 참조) | -| brand_id | null | `null` | brand 식별자 (연관 리소스 참조) | -| list_price | integer | `703155` | 정가 (기본통화 기준) | -| selling_price | integer | `597682` | 판매가 (기본통화 기준) | -| discount_rate | integer | `15` | 할인율(%) (정가 대비 판매가 할인 비율, (1 - 판매가/정가) × 100) | -| stock_quantity | integer | `509` | 재고 수량 (옵션 있으면 옵션 합계) | -| safe_stock_quantity | integer | `38` | 안전재고 수량 | -| is_below_safe_stock | boolean | `false` | below safe stock 여부 | -| is_stock_consistent | boolean | `true` | stock consistent 여부 | -| sales_status | string | `on_sale` | 판매상태: on_sale(판매중), suspended(판매중지), sold_out(품절), coming_soon(출시예정) | -| sales_status_label | string | `판매중` | `sales_status` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | -| display_status | string | `visible` | 전시상태: visible(전시), hidden(숨김) | -| display_status_label | string | `전시` | `display_status` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | -| tax_status | string | `taxable` | 과세여부: taxable(과세), tax_free(면세) | -| tax_status_label | string | `과세` | `tax_status` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | -| tax_rate | string | `10.00` | 세율 (%) | -| shipping_policy_id | null | `null` | shipping policy 식별자 (연관 리소스 참조) | -| common_info_id | null | `null` | common info 식별자 (연관 리소스 참조) | -| description | object | `{"ko":"Tempore et non fugit eos et aperiam dolore. Aut es…` | 설명 (다국어 필드는 로케일별 값 객체) | -| description_localized | string | `Tempore et non fugit eos et aperiam d…` | `description` 의 현재 로케일 해석 값 (다국어 필드를 표시용 문자열로 해석) | -| description_mode | string | `text` | 설명 모드: text(텍스트), html(HTML) | -| min_purchase_qty | integer | `1` | 최소 구매 수량 | -| max_purchase_qty | integer | `0` | 최대 구매 수량 (0=무제한) | -| purchase_restriction | string | `none` | 구매 제한: none(없음), restricted(제한) | -| allowed_roles | null | `null` | 구매 허용 역할 ID 배열 | -| barcode | null | `null` | 바코드 | -| hs_code | null | `null` | HS 코드 (관세 분류) | -| images | array | `[]` | 상품 이미지 목록 (각 항목: hash·url·alt_text·is_thumbnail·sort_order 등, images 관계 로드 시) | -| thumbnail_hash | null | `null` | 대표 이미지 해시 (썸네일로 지정된 이미지의 hash) | -| thumbnail_url | null | `null` | thumbnail URL | -| meta_title | null | `null` | SEO 제목 (다국어 JSON) | -| meta_description | null | `null` | SEO 설명 (다국어 JSON) | -| meta_keywords | null | `null` | SEO 키워드 (배열) | -| seo_sync_title | boolean | `true` | SEO 제목 동기화 여부 (1: 상품명으로 자동 채움, 0: 직접 입력 보존) | -| seo_sync_description | boolean | `true` | SEO 설명 동기화 여부 (1: 상품 설명으로 자동 채움, 0: 직접 입력 보존) | -| has_options | boolean | `false` | options 여부 | -| option_groups | array | `[]` | 옵션 그룹 정의: [{name: "색상", values: ["빨강", "파랑"]}] | -| options | array | `[]` | 옵션(SKU) 목록 (수정 폼 바인딩용, 각 옵션의 id·코드·옵션값·가격·재고 등) | -| additional_options | array | `[]` | 추가옵션 그룹 목록 (수정 폼 바인딩용, 그룹명·선택지·추가금 등) | -| created_at | string | `2026-07-08 10:44:49` | 생성 일시 | -| updated_at | string | `2026-07-08 10:44:49` | 최종 수정 일시 | -| abilities | object | `{"can_update":true,"can_delete":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | + **응답 예시** -```http -HTTP/1.1 200 -``` - -```json -{ - "success": true, - "message": "상품 정보를 조회했습니다.", - "data": { - "id": 1, - "name": { - "ko": "API 문서 샘플 상품", - "en": "API Doc Sample Product" - }, - "name_localized": "API 문서 샘플 상품", - "product_code": "APIDOCSAMPLE01", - "sku": "SKU-DKOK-1319", - "categories": [], - "category_ids": [], - "primary_category_id": null, - "brand_id": null, - "list_price": 703155, - "selling_price": 597682, - "discount_rate": 15, - "stock_quantity": 509, - "safe_stock_quantity": 38, - "is_below_safe_stock": false, - "is_stock_consistent": true, - "sales_status": "on_sale", - "sales_status_label": "판매중", - "display_status": "visible", - "display_status_label": "전시", - "tax_status": "taxable", - "tax_status_label": "과세", - "tax_rate": "10.00", - "shipping_policy_id": null, - "common_info_id": null, - "description": { - "ko": "Tempore et non fugit eos et aperiam dolore. Aut esse fugiat adipisci iure itaque nisi. Earum ab dignissimos ipsa harum dolor sunt voluptatem.\n\nEst voluptatibus in sed distinctio. Delectus occaecati et earum est rerum ex. Beatae ex aut autem impedit facere totam. Et tempore minus veniam doloribus maiores eveniet dolorem ut. Culpa sed inventore ipsam.\n\nFuga praesentium distinctio quia ducimus harum assumenda. Ipsa dolorum odio reiciendis quis id. Tempore facere molestiae et reprehenderit reiciendis dicta quos. Quae labore deserunt fuga quam beatae. Praesentium aut fuga ratione aliquid.", - "en": "Soluta et ut repellendus rem. Id ea quibusdam in. Doloribus saepe rem praesentium et quis et molestiae.\n\nDolor voluptas aut qui unde sed quia. Corrupti qui enim laborum maxime possimus quis. Odit ducimus doloremque qui officiis fugit rerum. Magni ea possimus quia commodi optio saepe. Recusandae itaque in voluptatum et.\n\nRerum enim velit est inventore. Ipsa laboriosam provident ullam." - }, - "description_localized": "Tempore et non fugit eos et aperiam dolore. Aut esse fugiat adipisci iure itaque nisi. Earum ab dignissimos ipsa harum dolor sunt voluptatem.\n\nEst voluptatibus in sed distinctio. Delectus occaecati et earum est rerum ex. Beatae ex aut autem impedit facere totam. Et tempore minus veniam doloribus maiores eveniet dolorem ut. Culpa sed inventore ipsam.\n\nFuga praesentium distinctio quia ducimus harum assumenda. Ipsa dolorum odio reiciendis quis id. Tempore facere molestiae et reprehenderit reiciendis dicta quos. Quae labore deserunt fuga quam beatae. Praesentium aut fuga ratione aliquid.", - "description_mode": "text", - "min_purchase_qty": 1, - "max_purchase_qty": 0, - "purchase_restriction": "none", - "allowed_roles": null, - "barcode": null, - "hs_code": null, - "images": [], - "thumbnail_hash": null, - "thumbnail_url": null, - "meta_title": null, - "meta_description": null, - "meta_keywords": null, - "seo_sync_title": true, - "seo_sync_description": true, - "has_options": false, - "option_groups": [], - "options": [], - "additional_options": [], - "created_at": "2026-07-08 10:44:49", - "updated_at": "2026-07-08 10:44:49", - "abilities": { - "can_update": true, - "can_delete": true - } - } -} -``` + **에러 응답** @@ -1135,7 +676,7 @@ HTTP/1.1 200 **요청 예시** ```http -PUT /api/modules/sirsoft-ecommerce/admin/products/by-code/APIDOCSAMPLE01 HTTP/1.1 +PUT /api/modules/sirsoft-ecommerce/admin/products/by-code/{code} HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -1213,11 +754,11 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** - + **에러 응답** @@ -1254,7 +795,7 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - + **응답 예시** @@ -1320,7 +861,7 @@ Content-Disposition: form-data; name="alt_text" **응답 필드** (`data` 내부) - + **응답 예시** @@ -1371,7 +912,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -1405,7 +946,7 @@ Content-Type: application/json **요청 예시** ```http -DELETE /api/modules/sirsoft-ecommerce/admin/products/images/1 HTTP/1.1 +DELETE /api/modules/sirsoft-ecommerce/admin/products/images/{id} HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -1413,11 +954,11 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - + **응답 예시** - + **에러 응답** @@ -1432,6 +973,52 @@ Authorization: Bearer {YOUR_TOKEN} **설명** 상품 이미지 1건을 삭제합니다. `auth:sanctum` + `sirsoft-ecommerce.products.update` 권한이 필요하며, `ProductImageService::delete()`가 `id`에 해당하는 이미지 레코드와 저장 파일을 제거합니다. 임시 업로드 이미지와 상품에 귀속된 이미지 모두 삭제할 수 있으며, 해당 이미지가 없으면 404를 반환합니다. +### GET /api/modules/sirsoft-ecommerce/admin/products/options + +- **라우트명**: `api.modules.sirsoft-ecommerce.admin.products.options` +- **컨트롤러**: `Modules\Sirsoft\Ecommerce\Http\Controllers\Admin\ProductController@options` +- **인증/권한**: `auth:sanctum` + `permission:sirsoft-ecommerce.products.read` + +**요청 파라미터** + +| 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | +| --- | --- | --- | --- | --- | --- | +| product_ids | query | array | 예 | min 1, max 100 | product 식별자 배열 | + +**요청 예시** + +```http +GET /api/modules/sirsoft-ecommerce/admin/products/options?product_ids=%EC%98%88%EC%8B%9C%EA%B0%92 HTTP/1.1 +Host: api.example.com +Accept: application/json +Authorization: Bearer {YOUR_TOKEN} +``` + +**응답 필드** (`data` 내부) + + + +**응답 예시** + + + +**에러 응답** + +| 상태코드 | 의미 | 발생 조건 | +| --- | --- | --- | +| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 | +| 403 | Forbidden | 요구 권한(`sirsoft-ecommerce.products.read`)이 없는 경우 | +| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) | + + + +**설명** 여러 상품의 옵션을 한 번에 조회합니다. 관리자 상품 목록이 옵션을 기본 적재하지 않게 되면서(`GET /admin/products` 는 개수·재고합계 집계만 반환), 행을 펼칠 때 이 엔드포인트가 옵션 상세를 공급합니다. 단건 라우트만 두면 한 페이지를 전부 펼쳤을 때 100요청이 되므로 배치를 기본형으로 합니다. 상품 수와 무관하게 쿼리 3개(허용 ID 확정 → 옵션 조회 → 옵션 가격 계산용 상품 로드)로 처리됩니다. + +이 엔드포인트는 **부분 성공**을 계약으로 합니다. 존재하지 않거나 권한 스코프 밖인 상품 ID 는 404 를 만들지 않고 응답의 `product_ids` 에서 빠집니다 — 하나가 없다고 나머지의 정상 조회를 버릴 이유가 없고, 배치에서 "무엇이 없었는지" 는 상태코드로 표현할 수 없기 때문입니다. 단건 404 semantics 가 필요하면 `GET /admin/products/{identifier}` 를 사용합니다. 권한 스코프는 옵션 테이블이 아니라 상품 쿼리에서 먼저 적용되므로, `scope=self` 사용자가 타인 상품 ID 를 실어 보내도 옵션이 노출되지 않습니다. + +라우트는 `/{identifier}` 보다 위에 등록해야 합니다 — identifier 제약이 `[0-9a-zA-Z]+` 라 리터럴 `options` 를 삼켜 상품 상세로 잘못 라우팅됩니다. + + ### GET /api/modules/sirsoft-ecommerce/admin/products/{identifier} - **라우트명**: `api.modules.sirsoft-ecommerce.admin.products.show` @@ -1447,7 +1034,7 @@ Authorization: Bearer {YOUR_TOKEN} **요청 예시** ```http -GET /api/modules/sirsoft-ecommerce/admin/products/1 HTTP/1.1 +GET /api/modules/sirsoft-ecommerce/admin/products/{identifier} HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -1455,136 +1042,11 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) -_단건 응답: `data` 객체의 필드._ - -| 필드 | 타입 | 실측 예시값 | 용도/설명 | -| --- | --- | --- | --- | -| id | integer | `1` | 기본 키 (내부 식별자) | -| name | object | `{"ko":"API 문서 샘플 상품","en":"API Doc Sample Product"}` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | -| name_localized | string | `API 문서 샘플 상품` | `name` 의 현재 로케일 해석 값 (다국어 필드를 표시용 문자열로 해석) | -| product_code | string | `APIDOCSAMPLE01` | 상품코드 | -| sku | string | `SKU-DKOK-1319` | SKU | -| categories | array | `[]` | 소속 카테고리 목록 (각 항목: id·현지화 이름·대표 여부. categories 관계 eager load 시에만 채워짐) | -| category_ids | array | `[]` | category 식별자 배열 (연관 리소스 참조) | -| primary_category_id | null | `null` | primary category 식별자 (연관 리소스 참조) | -| brand_id | null | `null` | brand 식별자 (연관 리소스 참조) | -| list_price | integer | `703155` | 정가 (기본통화 기준) | -| selling_price | integer | `597682` | 판매가 (기본통화 기준) | -| discount_rate | integer | `15` | 할인율(%) (정가 대비 판매가 할인 비율, (1 - 판매가/정가) × 100) | -| stock_quantity | integer | `509` | 재고 수량 (옵션 있으면 옵션 합계) | -| safe_stock_quantity | integer | `38` | 안전재고 수량 | -| is_below_safe_stock | boolean | `false` | below safe stock 여부 | -| is_stock_consistent | boolean | `true` | stock consistent 여부 | -| sales_status | string | `on_sale` | 판매상태: on_sale(판매중), suspended(판매중지), sold_out(품절), coming_soon(출시예정) | -| sales_status_label | string | `판매중` | `sales_status` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | -| display_status | string | `visible` | 전시상태: visible(전시), hidden(숨김) | -| display_status_label | string | `전시` | `display_status` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | -| tax_status | string | `taxable` | 과세여부: taxable(과세), tax_free(면세) | -| tax_status_label | string | `과세` | `tax_status` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | -| tax_rate | string | `10.00` | 세율 (%) | -| shipping_policy_id | null | `null` | shipping policy 식별자 (연관 리소스 참조) | -| common_info_id | null | `null` | common info 식별자 (연관 리소스 참조) | -| description | object | `{"ko":"Tempore et non fugit eos et aperiam dolore. Aut es…` | 설명 (다국어 필드는 로케일별 값 객체) | -| description_localized | string | `Tempore et non fugit eos et aperiam d…` | `description` 의 현재 로케일 해석 값 (다국어 필드를 표시용 문자열로 해석) | -| description_mode | string | `text` | 설명 모드: text(텍스트), html(HTML) | -| min_purchase_qty | integer | `1` | 최소 구매 수량 | -| max_purchase_qty | integer | `0` | 최대 구매 수량 (0=무제한) | -| purchase_restriction | string | `none` | 구매 제한: none(없음), restricted(제한) | -| allowed_roles | null | `null` | 구매 허용 역할 ID 배열 | -| barcode | null | `null` | 바코드 | -| hs_code | null | `null` | HS 코드 (관세 분류) | -| label_assignments | array | `[]` | 라벨 할당 목록 (각 항목: label_id + 노출 시작/종료일) | -| notice_items | null | `null` | 상품정보제공고시 항목 목록 (각 항목: 항목명·내용 다국어) | -| images | array | `[]` | 상품 이미지 목록 (각 항목: hash·url·alt_text·is_thumbnail·sort_order 등, images 관계 로드 시) | -| thumbnail_hash | null | `null` | 대표 이미지 해시 (썸네일로 지정된 이미지의 hash) | -| thumbnail_url | null | `null` | thumbnail URL | -| meta_title | null | `null` | SEO 제목 (다국어 JSON) | -| meta_description | null | `null` | SEO 설명 (다국어 JSON) | -| meta_keywords | null | `null` | SEO 키워드 (배열) | -| seo_sync_title | boolean | `true` | SEO 제목 동기화 여부 (1: 상품명으로 자동 채움, 0: 직접 입력 보존) | -| seo_sync_description | boolean | `true` | SEO 설명 동기화 여부 (1: 상품 설명으로 자동 채움, 0: 직접 입력 보존) | -| has_options | boolean | `false` | options 여부 | -| option_groups | array | `[]` | 옵션 그룹 정의: [{name: "색상", values: ["빨강", "파랑"]}] | -| options | array | `[]` | 옵션(SKU) 목록 (수정 폼 바인딩용, 각 옵션의 id·코드·옵션값·가격·재고 등) | -| additional_options | array | `[]` | 추가옵션 그룹 목록 (수정 폼 바인딩용, 그룹명·선택지·추가금 등) | -| created_at | string | `2026-07-08 10:44:49` | 생성 일시 | -| updated_at | string | `2026-07-08 10:44:49` | 최종 수정 일시 | -| abilities | object | `{"can_update":true,"can_delete":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | + **응답 예시** -```http -HTTP/1.1 200 -``` - -```json -{ - "success": true, - "message": "상품 정보를 조회했습니다.", - "data": { - "id": 1, - "name": { - "ko": "API 문서 샘플 상품", - "en": "API Doc Sample Product" - }, - "name_localized": "API 문서 샘플 상품", - "product_code": "APIDOCSAMPLE01", - "sku": "SKU-DKOK-1319", - "categories": [], - "category_ids": [], - "primary_category_id": null, - "brand_id": null, - "list_price": 703155, - "selling_price": 597682, - "discount_rate": 15, - "stock_quantity": 509, - "safe_stock_quantity": 38, - "is_below_safe_stock": false, - "is_stock_consistent": true, - "sales_status": "on_sale", - "sales_status_label": "판매중", - "display_status": "visible", - "display_status_label": "전시", - "tax_status": "taxable", - "tax_status_label": "과세", - "tax_rate": "10.00", - "shipping_policy_id": null, - "common_info_id": null, - "description": { - "ko": "Tempore et non fugit eos et aperiam dolore. Aut esse fugiat adipisci iure itaque nisi. Earum ab dignissimos ipsa harum dolor sunt voluptatem.\n\nEst voluptatibus in sed distinctio. Delectus occaecati et earum est rerum ex. Beatae ex aut autem impedit facere totam. Et tempore minus veniam doloribus maiores eveniet dolorem ut. Culpa sed inventore ipsam.\n\nFuga praesentium distinctio quia ducimus harum assumenda. Ipsa dolorum odio reiciendis quis id. Tempore facere molestiae et reprehenderit reiciendis dicta quos. Quae labore deserunt fuga quam beatae. Praesentium aut fuga ratione aliquid.", - "en": "Soluta et ut repellendus rem. Id ea quibusdam in. Doloribus saepe rem praesentium et quis et molestiae.\n\nDolor voluptas aut qui unde sed quia. Corrupti qui enim laborum maxime possimus quis. Odit ducimus doloremque qui officiis fugit rerum. Magni ea possimus quia commodi optio saepe. Recusandae itaque in voluptatum et.\n\nRerum enim velit est inventore. Ipsa laboriosam provident ullam." - }, - "description_localized": "Tempore et non fugit eos et aperiam dolore. Aut esse fugiat adipisci iure itaque nisi. Earum ab dignissimos ipsa harum dolor sunt voluptatem.\n\nEst voluptatibus in sed distinctio. Delectus occaecati et earum est rerum ex. Beatae ex aut autem impedit facere totam. Et tempore minus veniam doloribus maiores eveniet dolorem ut. Culpa sed inventore ipsam.\n\nFuga praesentium distinctio quia ducimus harum assumenda. Ipsa dolorum odio reiciendis quis id. Tempore facere molestiae et reprehenderit reiciendis dicta quos. Quae labore deserunt fuga quam beatae. Praesentium aut fuga ratione aliquid.", - "description_mode": "text", - "min_purchase_qty": 1, - "max_purchase_qty": 0, - "purchase_restriction": "none", - "allowed_roles": null, - "barcode": null, - "hs_code": null, - "label_assignments": [], - "notice_items": null, - "images": [], - "thumbnail_hash": null, - "thumbnail_url": null, - "meta_title": null, - "meta_description": null, - "meta_keywords": null, - "seo_sync_title": true, - "seo_sync_description": true, - "has_options": false, - "option_groups": [], - "options": [], - "additional_options": [], - "created_at": "2026-07-08 10:44:49", - "updated_at": "2026-07-08 10:44:49", - "abilities": { - "can_update": true, - "can_delete": true - } - } -} -``` + **에러 응답** @@ -1620,7 +1082,7 @@ HTTP/1.1 200 **요청 예시** ```http -POST /api/modules/sirsoft-ecommerce/admin/products/1/images HTTP/1.1 +POST /api/modules/sirsoft-ecommerce/admin/products/{productId}/images HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -1648,11 +1110,11 @@ Content-Disposition: form-data; name="alt_text" **응답 필드** (`data` 내부) - + **응답 예시** - + **에러 응답** @@ -1684,7 +1146,7 @@ Content-Disposition: form-data; name="alt_text" **요청 예시** ```http -PATCH /api/modules/sirsoft-ecommerce/admin/products/1/images/{imageId}/thumbnail HTTP/1.1 +PATCH /api/modules/sirsoft-ecommerce/admin/products/{productId}/images/{imageId}/thumbnail HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -1692,7 +1154,7 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - + **응답 예시** @@ -1726,7 +1188,7 @@ Authorization: Bearer {YOUR_TOKEN} **요청 예시** ```http -DELETE /api/modules/sirsoft-ecommerce/admin/products/1 HTTP/1.1 +DELETE /api/modules/sirsoft-ecommerce/admin/products/1732 HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -1734,27 +1196,11 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) -_단건 응답: `data` 객체의 필드._ - -| 필드 | 타입 | 실측 예시값 | 용도/설명 | -| --- | --- | --- | --- | -| deleted | boolean | `true` | 삭제 성공 여부 (상품이 정상 삭제되면 true) | + **응답 예시** -```http -HTTP/1.1 200 -``` - -```json -{ - "success": true, - "message": "상품이 삭제되었습니다.", - "data": { - "deleted": true - } -} -``` + **에러 응답** @@ -1826,7 +1272,7 @@ HTTP/1.1 200 **요청 예시** ```http -PUT /api/modules/sirsoft-ecommerce/admin/products/1 HTTP/1.1 +PUT /api/modules/sirsoft-ecommerce/admin/products/1732 HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -1904,7 +1350,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -1939,7 +1385,7 @@ Content-Type: application/json **요청 예시** ```http -GET /api/modules/sirsoft-ecommerce/admin/products/1/can-delete HTTP/1.1 +GET /api/modules/sirsoft-ecommerce/admin/products/1732/can-delete HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -1952,8 +1398,8 @@ _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | | canDelete | boolean | `false` | 삭제 가능 여부 (true 삭제 가능 / false 주문 이력 등으로 삭제 불가) | -| reason | string | `이 상품은 5건의 주문 이력이 있어 삭제할 수 없습니다.` | 삭제 불가 사유 (canDelete=false 일 때 안내 문구) | -| relatedData | object | `{"orders":5,"images":4,"options":3,"additionalOptions":0,…` | 연관 데이터 건수 (orders/images/options 등 상품에 연결된 하위 데이터 수) | +| reason | string | `이 상품은 8건의 주문 이력이 있어 삭제할 수 없습니다.` | 삭제 불가 사유 (canDelete=false 일 때 안내 문구) | +| relatedData | object | `{"orders":8,"images":4,"options":3,"additionalOptions":0,…` | 연관 데이터 건수 (orders/images/options 등 상품에 연결된 하위 데이터 수) | **응답 예시** @@ -1966,14 +1412,14 @@ HTTP/1.1 200 "success": true, "message": "삭제 가능 여부를 확인했습니다.", "data": { - "canDelete": true, - "reason": null, + "canDelete": false, + "reason": "이 상품은 8건의 주문 이력이 있어 삭제할 수 없습니다.", "relatedData": { - "orders": 0, - "images": 0, - "options": 0, + "orders": 8, + "images": 4, + "options": 3, "additionalOptions": 0, - "labelAssignments": 0 + "labelAssignments": 2 } } } @@ -2003,24 +1449,24 @@ HTTP/1.1 200 | 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | | --- | --- | --- | --- | --- | --- | | product | path | string | 예 | — | 대상 product의 식별자 | -| copy_images | query | boolean | 아니오 | true/false (기본 true) | 상품 이미지 복사 여부 | -| copy_options | query | boolean | 아니오 | true/false (기본 true) | 옵션 복사 여부 | -| copy_categories | query | boolean | 아니오 | true/false (기본 true) | 카테고리 연결 복사 여부 | -| copy_sales_info | query | boolean | 아니오 | true/false (기본 true) | 판매 정보(가격·재고·상태) 복사 여부 | -| copy_description | query | boolean | 아니오 | true/false (기본 true) | 상세 설명 복사 여부 | -| copy_notice | query | boolean | 아니오 | true/false (기본 true) | 상품 공지 복사 여부 | -| copy_common_info | query | boolean | 아니오 | true/false (기본 true) | 상품 공통정보 복사 여부 | -| copy_other_info | query | boolean | 아니오 | true/false (기본 true) | 기타 정보 복사 여부 | -| copy_shipping | query | boolean | 아니오 | true/false (기본 true) | 배송 정책 복사 여부 | -| copy_seo | query | boolean | 아니오 | true/false (기본 **false**) | SEO 메타 복사 여부 — 메타 제목·설명은 상품마다 달라야 검색 결과에서 구분되므로 기본 제외 | -| copy_identification | query | boolean | 아니오 | true/false (기본 true) | 식별 정보(SKU·상품코드 등) 복사 여부 | +| copy_images | query | boolean | 아니오 | — | 상품 이미지 복사 여부 | +| copy_options | query | boolean | 아니오 | — | 옵션 복사 여부 | +| copy_categories | query | boolean | 아니오 | — | 카테고리 연결 복사 여부 | +| copy_sales_info | query | boolean | 아니오 | — | 판매 정보(가격·재고·상태) 복사 여부 | +| copy_description | query | boolean | 아니오 | — | 상세 설명 복사 여부 | +| copy_notice | query | boolean | 아니오 | — | 상품 공지 복사 여부 | +| copy_common_info | query | boolean | 아니오 | — | 상품 공통정보 복사 여부 | +| copy_other_info | query | boolean | 아니오 | — | 기타 정보 복사 여부 | +| copy_shipping | query | boolean | 아니오 | — | 배송 정책 복사 여부 | +| copy_seo | query | boolean | 아니오 | — | SEO 메타 복사 여부 — 메타 제목·설명은 상품마다 달라야 검색 결과에서 구분되므로 기본 제외 | +| copy_identification | query | boolean | 아니오 | — | 식별 정보(SKU·상품코드 등) 복사 여부 | > 이 엔드포인트는 확장이 파라미터를 추가할 수 있습니다 (`sirsoft-ecommerce.product.show_for_copy_validation_rules`). **요청 예시** ```http -GET /api/modules/sirsoft-ecommerce/admin/products/1/copy HTTP/1.1 +GET /api/modules/sirsoft-ecommerce/admin/products/1732/copy?copy_images=1©_options=1©_categories=1©_sales_info=1©_description=1©_notice=1©_common_info=1©_other_info=1©_shipping=1©_seo=1©_identification=1 HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -2033,30 +1479,30 @@ _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | | name | object | `{"ko":"면 손수건 3매입 #1","en":"Cotton Handkerchief 3pcs #1"}` | 상품명 (다국어 JSON: {ko: "...", en: "..."}) | -| product_code | string | `BP747N3QSSSNEPII` | 상품코드 | +| product_code | string | `4D6NNDBJOL08X291` | 상품코드 | | sales_product_code | null | `null` | 판매자 상품코드 (사용자 입력용) | | sku | string | `HK-0001` | SKU | -| brand_id | integer | `91` | 브랜드 ID | -| category_ids | array | `[114,116]` | category 식별자 배열 (연관 리소스 참조) | -| primary_category_id | integer | `116` | primary category 식별자 (연관 리소스 참조) | -| list_price | string | `4000.00` | 정가 (기본통화 기준) | -| selling_price | string | `2000.00` | 판매가 (기본통화 기준) | -| stock_quantity | integer | `59` | 재고 수량 (옵션 있으면 옵션 합계) | -| safe_stock_quantity | integer | `15` | 안전재고 수량 | +| brand_id | integer | `7` | 브랜드 ID | +| category_ids | array | `[29,30,32]` | category 식별자 배열 (연관 리소스 참조) | +| primary_category_id | integer | `32` | primary category 식별자 (연관 리소스 참조) | +| list_price | string | `5000.00` | 정가 (기본통화 기준) | +| selling_price | string | `3000.00` | 판매가 (기본통화 기준) | +| stock_quantity | integer | `26` | 재고 수량 (옵션 있으면 옵션 합계) | +| safe_stock_quantity | integer | `5` | 안전재고 수량 | | tax_status | string | `taxable` | 과세여부: taxable(과세), tax_free(면세) | | sales_status | string | `on_sale` | 판매상태: on_sale(판매중), suspended(판매중지), sold_out(품절), coming_soon(출시예정) | | display_status | string | `visible` | 전시상태: visible(전시), hidden(숨김) | -| options | array | `[{"option_code":"AS2HM7CEDFHEGS43-001","option_name":{"ko…` | 복사 대상 옵션(SKU) 목록 (신규 등록 폼에 채울 옵션 정의, 다중통화 가격 포함) | +| options | array | `[{"option_code":"CJTFHBL8SLRQ8ILM-001","option_name":{"ko…` | 복사 대상 옵션(SKU) 목록 (신규 등록 폼에 채울 옵션 정의, 다중통화 가격 포함) | | additional_options | array | `[]` | 복사 대상 추가옵션 그룹 목록 (그룹명·선택지·추가금 등) | -| images | array | `[{"hash":"7858be3cf217","url":null,"original_filename":"p…` | 복사 대상 이미지 목록 (각 항목: hash·원본파일명 등, copy_images 선택 시 포함) | -| thumbnail_hash | string | `7858be3cf217` | 대표 이미지 해시 (썸네일로 지정된 이미지의 hash) | +| images | array | `[{"hash":"7df7761cdf16","url":null,"original_filename":"p…` | 복사 대상 이미지 목록 (각 항목: hash·원본파일명 등, copy_images 선택 시 포함) | +| thumbnail_hash | string | `7df7761cdf16` | 대표 이미지 해시 (썸네일로 지정된 이미지의 hash) | | description | object | `{"ko":"

부드러운 면 100% 손수건 3매 세트입니다.<\/p>","en":"

A set …` | 상세 설명 (다국어 JSON, HTML 포함) | | description_mode | string | `text` | 설명 모드: text(텍스트), html(HTML) | -| notice_items | array | `[{"name":{"ko":"제품 소재 (충전재 포함)","en":"Material (Including…` | 상품정보제공고시 항목 목록 (각 항목: 항목명·내용 다국어) | -| shipping_policy_id | integer | `31` | 배송정책 ID | -| shipping_policy | object | `{"id":31,"name":{"ko":"국내 무료배송","en":"Domestic Free Shipp…` | 현재 부여된 배송정책 객체 (비활성 포함 — 수정폼 활성 목록에 없을 때 union 표시용) | -| common_info_id | integer | `207` | 공통정보 템플릿 ID | -| label_assignments | array | `[{"label_id":26,"start_date":null,"end_date":null}]` | 라벨 할당 목록 (각 항목: label_id + 노출 시작/종료일) | +| notice_items | array | `[{"name":{"ko":"항목1","en":"Field 1"},"content":{"ko":"상세페…` | 상품정보제공고시 항목 목록 (각 항목: 항목명·내용 다국어) | +| shipping_policy_id | integer | `444` | 배송정책 ID | +| shipping_policy | object | `{"id":444,"name":{"ko":"국내 무료배송","en":"Domestic Free Ship…` | 현재 부여된 배송정책 객체 (비활성 포함 — 수정폼 활성 목록에 없을 때 union 표시용) | +| common_info_id | integer | `11` | 공통정보 템플릿 ID | +| label_assignments | array | `[{"label_id":31,"start_date":null,"end_date":null},{"labe…` | 라벨 할당 목록 (각 항목: label_id + 노출 시작/종료일) | | min_purchase_qty | integer | `1` | 최소 구매 수량 | | max_purchase_qty | integer | `0` | 최대 구매 수량 (0=무제한) | | purchase_restriction | string | `none` | 구매 제한: none(없음), restricted(제한) | @@ -2069,7 +1515,7 @@ _단건 응답: `data` 객체의 필드._ | barcode | null | `null` | 바코드 | | hs_code | null | `null` | HS 코드 (관세 분류) | | thumbnail_url | string | `/api/modules/sirsoft-ecommerce/produc…` | thumbnail URL | -| categories | array | `[{"id":114,"name":{"ko":"스포츠","en":"Sports"},"name_locali…` | 소속 카테고리 목록 (breadcrumb 포함 — 복사 폼의 카테고리 표시용) | +| categories | array | `[{"id":29,"name":{"ko":"의류","en":"Clothing"},"name_locali…` | 소속 카테고리 목록 (breadcrumb 포함 — 복사 폼의 카테고리 표시용) | **응답 예시** @@ -2083,48 +1529,14 @@ HTTP/1.1 200 "message": "상품 정보를 조회했습니다.", "data": { "name": { - "ko": "API 문서 샘플 상품", - "en": "API Doc Sample Product" + "ko": "면 손수건 3매입 #1", + "en": "Cotton Handkerchief 3pcs #1" }, - "product_code": "WLA9NKCCGO7026T5", + "product_code": "4D6NNDBJOL08X291", "sales_product_code": null, - "sku": "SKU-DKOK-1319", - "brand_id": null, - "category_ids": [], - "primary_category_id": null, - "list_price": "703155.00", - "selling_price": "597682.00", - "stock_quantity": 509, - "safe_stock_quantity": 38, - "tax_status": "taxable", - "sales_status": "on_sale", - "display_status": "visible", - "options": [], - "additional_options": [], - "images": [], - "thumbnail_hash": null, - "description": { - "ko": "Tempore et non fugit eos et aperiam dolore. Aut esse fugiat adipisci iure itaque nisi. Earum ab dignissimos ipsa harum dolor sunt voluptatem.\n\nEst voluptatibus in sed distinctio. Delectus occaecati et earum est rerum ex. Beatae ex aut autem impedit facere totam. Et tempore minus veniam doloribus maiores eveniet dolorem ut. Culpa sed inventore ipsam.\n\nFuga praesentium distinctio quia ducimus harum assumenda. Ipsa dolorum odio reiciendis quis id. Tempore facere molestiae et reprehenderit reiciendis dicta quos. Quae labore deserunt fuga quam beatae. Praesentium aut fuga ratione aliquid.", - "en": "Soluta et ut repellendus rem. Id ea quibusdam in. Doloribus saepe rem praesentium et quis et molestiae.\n\nDolor voluptas aut qui unde sed quia. Corrupti qui enim laborum maxime possimus quis. Odit ducimus doloremque qui officiis fugit rerum. Magni ea possimus quia commodi optio saepe. Recusandae itaque in voluptatum et.\n\nRerum enim velit est inventore. Ipsa laboriosam provident ullam." - }, - "description_mode": "text", - "notice_items": null, - "shipping_policy_id": null, - "shipping_policy": null, - "common_info_id": null, - "label_assignments": [], - "min_purchase_qty": 1, - "max_purchase_qty": 0, - "purchase_restriction": "none", - "allowed_roles": [], - "meta_title": null, - "meta_description": null, - "seo_tags": [], - "seo_sync_title": true, - "seo_sync_description": true, - "barcode": null, - "hs_code": null, - "thumbnail_url": null + "sku": "HK-0001", + "brand_id": 7, + "...": "(33개 키 생략, 총 38개)" } } ``` @@ -2135,6 +1547,7 @@ HTTP/1.1 200 | --- | --- | --- | | 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 | | 403 | Forbidden | 요구 권한(`sirsoft-ecommerce.products.read`)이 없는 경우 | +| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) | | 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 | @@ -2157,7 +1570,7 @@ HTTP/1.1 200 **요청 예시** ```http -GET /api/modules/sirsoft-ecommerce/admin/products/1/form HTTP/1.1 +GET /api/modules/sirsoft-ecommerce/admin/products/1732/form HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -2169,34 +1582,34 @@ _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| id | integer | `201` | 기본 키 (내부 식별자) | +| id | integer | `1732` | 기본 키 (내부 식별자) | | name | object | `{"ko":"면 손수건 3매입 #1","en":"Cotton Handkerchief 3pcs #1"}` | 상품명 (다국어 JSON: {ko: "...", en: "..."}) | -| product_code | string | `AS2HM7CEDFHEGS43` | 상품코드 | +| product_code | string | `CJTFHBL8SLRQ8ILM` | 상품코드 | | sales_product_code | null | `null` | 판매자 상품코드 (사용자 입력용) | | sku | string | `HK-0001` | SKU | -| brand_id | integer | `91` | 브랜드 ID | -| category_ids | array | `[114,116]` | category 식별자 배열 (연관 리소스 참조) | -| primary_category_id | integer | `116` | primary category 식별자 (연관 리소스 참조) | -| created_at | string | `2026-06-15 02:24:15` | 생성 일시 | -| updated_at | string | `2026-06-15 02:24:15` | 최종 수정 일시 | -| list_price | string | `4000.00` | 정가 (기본통화 기준) | -| selling_price | string | `2000.00` | 판매가 (기본통화 기준) | -| stock_quantity | integer | `59` | 재고 수량 (옵션 있으면 옵션 합계) | -| safe_stock_quantity | integer | `15` | 안전재고 수량 | +| brand_id | integer | `7` | 브랜드 ID | +| category_ids | array | `[29,30,32]` | category 식별자 배열 (연관 리소스 참조) | +| primary_category_id | integer | `32` | primary category 식별자 (연관 리소스 참조) | +| created_at | string | `2026-07-30 14:36:13` | 생성 일시 | +| updated_at | string | `2026-07-30 14:36:13` | 최종 수정 일시 | +| list_price | string | `5000.00` | 정가 (기본통화 기준) | +| selling_price | string | `3000.00` | 판매가 (기본통화 기준) | +| stock_quantity | integer | `26` | 재고 수량 (옵션 있으면 옵션 합계) | +| safe_stock_quantity | integer | `5` | 안전재고 수량 | | tax_status | string | `taxable` | 과세여부: taxable(과세), tax_free(면세) | | sales_status | string | `on_sale` | 판매상태: on_sale(판매중), suspended(판매중지), sold_out(품절), coming_soon(출시예정) | | display_status | string | `visible` | 전시상태: visible(전시), hidden(숨김) | -| options | array | `[{"id":1086,"option_code":"AS2HM7CEDFHEGS43-001","option_…` | 옵션(SKU) 목록 (수정 폼 바인딩용, 각 옵션의 id·코드·옵션값·가격·재고 등) | +| options | array | `[{"id":948,"option_code":"CJTFHBL8SLRQ8ILM-001","option_n…` | 옵션(SKU) 목록 (수정 폼 바인딩용, 각 옵션의 id·코드·옵션값·가격·재고 등) | | additional_options | array | `[]` | 추가옵션 그룹 목록 (수정 폼 바인딩용, 그룹명·선택지·추가금 등) | -| images | array | `[{"id":801,"hash":"7858be3cf217","url":null,"original_fil…` | 이미지 목록 (각 항목: id·hash·원본파일명 등) | -| thumbnail_hash | string | `7858be3cf217` | 대표 이미지 해시 (썸네일로 지정된 이미지의 hash) | +| images | array | `[{"id":7,"hash":"7df7761cdf16","url":null,"original_filen…` | 이미지 목록 (각 항목: id·hash·원본파일명 등) | +| thumbnail_hash | string | `7df7761cdf16` | 대표 이미지 해시 (썸네일로 지정된 이미지의 hash) | | description | object | `{"ko":"

부드러운 면 100% 손수건 3매 세트입니다.<\/p>","en":"

A set …` | 상세 설명 (다국어 JSON, HTML 포함) | | description_mode | string | `text` | 설명 모드: text(텍스트), html(HTML) | -| notice_items | array | `[{"name":{"ko":"제품 소재 (충전재 포함)","en":"Material (Including…` | 상품정보제공고시 항목 목록 (각 항목: 항목명·내용 다국어) | -| shipping_policy_id | integer | `31` | 배송정책 ID | -| shipping_policy | object | `{"id":31,"name":{"ko":"국내 무료배송","en":"Domestic Free Shipp…` | 현재 부여된 배송정책 객체 (비활성 포함 — 수정폼 활성 목록에 없을 때 union 표시용) | -| common_info_id | integer | `207` | 공통정보 템플릿 ID | -| label_assignments | array | `[{"label_id":26,"start_date":null,"end_date":null}]` | 라벨 할당 목록 (각 항목: label_id + 노출 시작/종료일) | +| notice_items | array | `[{"name":{"ko":"항목1","en":"Field 1"},"content":{"ko":"상세페…` | 상품정보제공고시 항목 목록 (각 항목: 항목명·내용 다국어) | +| shipping_policy_id | integer | `444` | 배송정책 ID | +| shipping_policy | object | `{"id":444,"name":{"ko":"국내 무료배송","en":"Domestic Free Ship…` | 현재 부여된 배송정책 객체 (비활성 포함 — 수정폼 활성 목록에 없을 때 union 표시용) | +| common_info_id | integer | `11` | 공통정보 템플릿 ID | +| label_assignments | array | `[{"label_id":31,"start_date":null,"end_date":null},{"labe…` | 라벨 할당 목록 (각 항목: label_id + 노출 시작/종료일) | | min_purchase_qty | integer | `1` | 최소 구매 수량 | | max_purchase_qty | integer | `0` | 최대 구매 수량 (0=무제한) | | purchase_restriction | string | `none` | 구매 제한: none(없음), restricted(제한) | @@ -2220,51 +1633,15 @@ HTTP/1.1 200 "success": true, "message": "상품 정보를 조회했습니다.", "data": { - "id": 1, + "id": 1732, "name": { - "ko": "API 문서 샘플 상품", - "en": "API Doc Sample Product" + "ko": "면 손수건 3매입 #1", + "en": "Cotton Handkerchief 3pcs #1" }, - "product_code": "APIDOCSAMPLE01", + "product_code": "CJTFHBL8SLRQ8ILM", "sales_product_code": null, - "sku": "SKU-DKOK-1319", - "brand_id": null, - "category_ids": [], - "primary_category_id": null, - "created_at": "2026-07-08 01:44:49", - "updated_at": "2026-07-08 01:44:49", - "list_price": "703155.00", - "selling_price": "597682.00", - "stock_quantity": 509, - "safe_stock_quantity": 38, - "tax_status": "taxable", - "sales_status": "on_sale", - "display_status": "visible", - "options": [], - "additional_options": [], - "images": [], - "thumbnail_hash": null, - "description": { - "ko": "Tempore et non fugit eos et aperiam dolore. Aut esse fugiat adipisci iure itaque nisi. Earum ab dignissimos ipsa harum dolor sunt voluptatem.\n\nEst voluptatibus in sed distinctio. Delectus occaecati et earum est rerum ex. Beatae ex aut autem impedit facere totam. Et tempore minus veniam doloribus maiores eveniet dolorem ut. Culpa sed inventore ipsam.\n\nFuga praesentium distinctio quia ducimus harum assumenda. Ipsa dolorum odio reiciendis quis id. Tempore facere molestiae et reprehenderit reiciendis dicta quos. Quae labore deserunt fuga quam beatae. Praesentium aut fuga ratione aliquid.", - "en": "Soluta et ut repellendus rem. Id ea quibusdam in. Doloribus saepe rem praesentium et quis et molestiae.\n\nDolor voluptas aut qui unde sed quia. Corrupti qui enim laborum maxime possimus quis. Odit ducimus doloremque qui officiis fugit rerum. Magni ea possimus quia commodi optio saepe. Recusandae itaque in voluptatum et.\n\nRerum enim velit est inventore. Ipsa laboriosam provident ullam." - }, - "description_mode": "text", - "notice_items": null, - "shipping_policy_id": null, - "shipping_policy": null, - "common_info_id": null, - "label_assignments": [], - "min_purchase_qty": 1, - "max_purchase_qty": 0, - "purchase_restriction": "none", - "allowed_roles": [], - "meta_title": null, - "meta_description": null, - "seo_tags": [], - "seo_sync_title": true, - "seo_sync_description": true, - "barcode": null, - "hs_code": null + "sku": "HK-0001", + "...": "(34개 키 생략, 총 39개)" } } ``` @@ -2293,15 +1670,15 @@ HTTP/1.1 200 | 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | | --- | --- | --- | --- | --- | --- | | product | path | string | 예 | — | 대상 product의 식별자 | -| per_page | query | integer | 아니오 | 1~100 (기본 10) | 페이지당 로그 수 | -| sort_order | query | string | 아니오 | `asc` / `desc` (기본 `desc`) | 작성 시각 정렬 방향 | +| per_page | query | integer | 아니오 | min 1, max 100 | 페이지당 로그 수 | +| sort_order | query | string | 아니오 | — | 작성 시각 정렬 방향 | > 이 엔드포인트는 확장이 파라미터를 추가할 수 있습니다 (`sirsoft-ecommerce.product.logs_validation_rules`). **요청 예시** ```http -GET /api/modules/sirsoft-ecommerce/admin/products/1/logs HTTP/1.1 +GET /api/modules/sirsoft-ecommerce/admin/products/1732/logs?per_page=1&sort_order=%EC%98%88%EC%8B%9C%EA%B0%92 HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -2313,25 +1690,25 @@ _목록 응답: `data.data[]` 배열 항목의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| id | integer | `151066` | 기본 키 (내부 식별자) | -| log_type | string | `admin` | 로그 구분 값 (admin 관리자 작업 / user 사용자 작업 등 활동 로그 채널) | -| log_type_label | string | `관리자` | `log_type` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | +| id | integer | `45291` | 기본 키 (내부 식별자) | +| log_type | string | `user` | 로그 구분 값 (admin 관리자 작업 / user 사용자 작업 등 활동 로그 채널) | +| log_type_label | string | `사용자` | `log_type` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | | loggable_type | string | `Modules\Sirsoft\Ecommerce\Models\Product` | 로그 대상 모델의 전체 클래스명 (상품 또는 상품옵션 모델) | | loggable_type_display | string | `Product` | 로그 대상 모델의 표시용 짧은 이름 (클래스 basename) | -| loggable_id | integer | `201` | loggable 식별자 (연관 리소스 참조) | -| action | string | `product.create` | 활동 액션 키 (product.create/update 등 수행된 작업 식별자) | -| action_label | string | `생성` | `action` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | -| localized_description | string | `상품 생성 (면 손수건 3매입 #1)` | `description` 의 현재 로케일 해석 값 (다국어 필드를 표시용 문자열로 해석) | +| loggable_id | integer | `1732` | loggable 식별자 (연관 리소스 참조) | +| action | string | `wishlist.add` | 활동 액션 키 (product.create/update 등 수행된 작업 식별자) | +| action_label | string | `추가` | `action` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | +| localized_description | string | `위시리스트 추가 (면 손수건 3매입 #1)` | `description` 의 현재 로케일 해석 값 (다국어 필드를 표시용 문자열로 해석) | | description_key | string | `sirsoft-ecommerce::activity_log.descr…` | 설명 번역 키 (localized_description 을 생성하는 다국어 키) | | properties | null | `null` | 로그 부가 속성 (액션에 첨부된 임의 메타데이터, 없으면 null) | -| changes | array | `[{"field":"sku","label_key":"sirsoft-ecommerce::activity_…` | 단일 수정 변경 내역 (각 항목: field·label·old·new, 일괄 수정 로그면 null) | +| changes | array | `[{"field":"selling_price","label_key":"sirsoft-ecommerce:…` | 단일 수정 변경 내역 (각 항목: field·label·old·new, 일괄 수정 로그면 null) | | bulk_changes | null | `null` | 일괄 수정 변경 내역 (각 항목: model_id·changes 배열, 단일 수정 로그면 null) | | has_changes | boolean | `false` | changes 여부 | -| actor_name | string | `관리자` | 행위를 수행한 주체(사용자/시스템)의 이름 | -| user | object | `{"uuid":"a1e0a91a-fba6-491c-a53e-7285a5686857","name":"관리…` | 행위 수행 사용자 정보 (uuid·name·email, 시스템 작업이면 name 만 '시스템') | +| actor_name | string | `시스템` | 행위를 수행한 주체(사용자/시스템)의 이름 | +| user | object | `{"name":"시스템"}` | 행위 수행 사용자 정보 (uuid·name·email, 시스템 작업이면 name 만 '시스템') | | ip_address | string | `192.168.1.10` | 요청/행위가 발생한 IP 주소 | -| created_at | string | `2026-06-14 08:28:44` | 생성 일시 | -| is_owner | boolean | `true` | 현재 인증 사용자가 이 리소스의 소유자인지 여부 (BaseApiResource 표준 메타) | +| created_at | string | `2026-07-29 14:31:19` | 생성 일시 | +| is_owner | boolean | `false` | 현재 인증 사용자가 이 리소스의 소유자인지 여부 (BaseApiResource 표준 메타) | | abilities | object | `{"can_read":true,"can_delete":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | **응답 예시** @@ -2345,17 +1722,82 @@ HTTP/1.1 200 "success": true, "message": "상품 처리 이력을 조회했습니다.", "data": { - "data": [], + "data": [ + { + "id": 45291, + "log_type": "user", + "log_type_label": "사용자", + "loggable_type": "Modules\\Sirsoft\\Ecommerce\\Models\\Product", + "loggable_type_display": "Product", + "loggable_id": 1732, + "action": "wishlist.add", + "action_label": "추가", + "localized_description": "위시리스트 추가 (면 손수건 3매입 #1)", + "description_key": "sirsoft-ecommerce::activity_log.description.wishlist_add", + "properties": null, + "changes": null, + "bulk_changes": null, + "has_changes": false, + "actor_name": "시스템", + "user": { + "name": "시스템" + }, + "ip_address": "192.168.1.10", + "created_at": "2026-07-29 14:31:19", + "is_owner": false, + "abilities": { + "can_read": true, + "can_delete": true + } + }, + { + "id": 170, + "log_type": "admin", + "log_type_label": "관리자", + "loggable_type": "Modules\\Sirsoft\\Ecommerce\\Models\\Product", + "loggable_type_display": "Product", + "loggable_id": 1732, + "action": "product.bulk_price_update", + "action_label": "일괄 가격 수정", + "localized_description": "상품 일괄 가격 수정 (1건)", + "description_key": "sirsoft-ecommerce::activity_log.description.product_bulk_price_update", + "properties": null, + "changes": [ + { + "field": "selling_price", + "label_key": "sirsoft-ecommerce::activity_log.fields.selling_price", + "old": 2553, + "new": "3000.00", + "type": "currency", + "label": "판매가" + } + ], + "bulk_changes": null, + "has_changes": true, + "actor_name": "시스템", + "user": { + "name": "시스템" + }, + "ip_address": "10.0.0.5", + "created_at": "2026-07-29 13:06:43", + "is_owner": false, + "abilities": { + "can_read": true, + "can_delete": true + } + }, + "... (총 25건 중 2건 표시)" + ], "links": { - "first": "https://api.example.com/api/modules/sirsoft-ecommerce/admin/products/1/logs?page=1", - "last": "https://api.example.com/api/modules/sirsoft-ecommerce/admin/products/1/logs?page=1", + "first": "https://api.example.com/api/modules/sirsoft-ecommerce/admin/products/1732/logs?page=1", + "last": "https://api.example.com/api/modules/sirsoft-ecommerce/admin/products/1732/logs?page=4", "prev": null, - "next": null + "next": "https://api.example.com/api/modules/sirsoft-ecommerce/admin/products/1732/logs?page=2" }, "meta": { "current_page": 1, - "from": null, - "last_page": 1, + "from": 1, + "last_page": 4, "links": [ { "url": null, @@ -2364,22 +1806,40 @@ HTTP/1.1 200 "active": false }, { - "url": "https://api.example.com/api/modules/sirsoft-ecommerce/admin/products/1/logs?page=1", + "url": "https://api.example.com/api/modules/sirsoft-ecommerce/admin/products/1732/logs?page=1", "label": "1", "page": 1, "active": true }, { - "url": null, + "url": "https://api.example.com/api/modules/sirsoft-ecommerce/admin/products/1732/logs?page=2", + "label": "2", + "page": 2, + "active": false + }, + { + "url": "https://api.example.com/api/modules/sirsoft-ecommerce/admin/products/1732/logs?page=3", + "label": "3", + "page": 3, + "active": false + }, + { + "url": "https://api.example.com/api/modules/sirsoft-ecommerce/admin/products/1732/logs?page=4", + "label": "4", + "page": 4, + "active": false + }, + { + "url": "https://api.example.com/api/modules/sirsoft-ecommerce/admin/products/1732/logs?page=2", "label": "pagination.next", - "page": null, + "page": 2, "active": false } ], - "path": "https://api.example.com/api/modules/sirsoft-ecommerce/admin/products/1/logs", + "path": "https://api.example.com/api/modules/sirsoft-ecommerce/admin/products/1732/logs", "per_page": 25, - "to": null, - "total": 0 + "to": 25, + "total": 100 } } } @@ -2391,6 +1851,7 @@ HTTP/1.1 200 | --- | --- | --- | | 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 | | 403 | Forbidden | 요구 권한(`sirsoft-ecommerce.products.read`)이 없는 경우 | +| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) | | 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 | @@ -2434,22 +1895,22 @@ _목록 응답: `data.data[]` 배열 항목의 필드 + `data.pagination`._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| number | integer | `109` | 목록에서의 순번 (페이지네이션 반영 행 번호 — HasRowNumber 파생) | -| id | integer | `322` | 기본 키 (내부 식별자) | -| name | object | `{"ko":"eum et quia","en":"tenetur id quae"}` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | -| name_localized | string | `eum et quia` | `name` 의 현재 로케일 해석 값 (다국어 필드를 표시용 문자열로 해석) | -| product_code | string | `PROD-GJUX-1484` | 상품코드 (상품 고유 관리 식별자) | -| sku | string | `SKU-MRAD-9306` | 재고관리코드(SKU) | +| number | integer | `98` | 목록에서의 순번 (페이지네이션 반영 행 번호 — HasRowNumber 파생) | +| id | integer | `1831` | 기본 키 (내부 식별자) | +| name | object | `{"ko":"겨울 패딩 점퍼 #100","en":"Winter Padded Jacket #100"}` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | +| name_localized | string | `겨울 패딩 점퍼 #100` | `name` 의 현재 로케일 해석 값 (다국어 필드를 표시용 문자열로 해석) | +| product_code | string | `GGK6A9N8PXNR35OQ` | 상품코드 (상품 고유 관리 식별자) | +| sku | string | `JK-0100` | 재고관리코드(SKU) | | thumbnail_url | string | `/api/modules/sirsoft-ecommerce/produc…` | thumbnail URL | -| list_price | integer | `112594` | 정가 (기본통화 자릿수로 정규화된 값) | -| list_price_formatted | string | `112,594원` | `list_price` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | -| selling_price | integer | `88949` | 판매가 (기본통화 자릿수로 정규화된 값) | -| selling_price_formatted | string | `88,949원` | `selling_price` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | -| discount_rate | integer | `21` | 할인율(%) (정가 대비 판매가 할인 비율, (1 - 판매가/정가) × 100) | -| multi_currency_list_price | object | `{"KRW":{"price":112594,"formatted":"112,594원","is_default…` | 통화별 정가 맵 (통화코드 → {price, formatted, is_default, editable}, 설정된 모든 통화의 환산 정가) | -| multi_currency_selling_price | object | `{"KRW":{"price":88949,"formatted":"88,949원","is_default":…` | 통화별 판매가 맵 (통화코드 → {price, formatted, is_default, editable}, 설정된 모든 통화의 환산 판매가) | -| stock_quantity | integer | `22` | 재고 수량 (옵션 사용 시 옵션 재고 합계) | -| safe_stock_quantity | integer | `12` | 안전재고 수량 (이 값 미만이면 재고 부족으로 표시) | +| list_price | integer | `200000` | 정가 (기본통화 자릿수로 정규화된 값) | +| list_price_formatted | string | `200,000원` | `list_price` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | +| selling_price | integer | `169000` | 판매가 (기본통화 자릿수로 정규화된 값) | +| selling_price_formatted | string | `169,000원` | `selling_price` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | +| discount_rate | number | `15.5` | 할인율(%) (정가 대비 판매가 할인 비율, (1 - 판매가/정가) × 100) | +| multi_currency_list_price | object | `{"KRW":{"price":200000,"formatted":"200,000원","is_default…` | 통화별 정가 맵 (통화코드 → {price, formatted, is_default, editable}, 설정된 모든 통화의 환산 정가) | +| multi_currency_selling_price | object | `{"KRW":{"price":169000,"formatted":"169,000원","is_default…` | 통화별 판매가 맵 (통화코드 → {price, formatted, is_default, editable}, 설정된 모든 통화의 환산 판매가) | +| stock_quantity | integer | `213` | 재고 수량 (옵션 사용 시 옵션 재고 합계) | +| safe_stock_quantity | integer | `18` | 안전재고 수량 (이 값 미만이면 재고 부족으로 표시) | | is_below_safe_stock | boolean | `false` | below safe stock 여부 | | sales_status | string | `on_sale` | 판매상태 값 (on_sale 판매중 / suspended 판매중지 / sold_out 품절 / coming_soon 출시예정) | | sales_status_label | string | `판매중` | `sales_status` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | @@ -2457,19 +1918,19 @@ _목록 응답: `data.data[]` 배열 항목의 필드 + `data.pagination`._ | display_status | string | `visible` | 전시상태 값 (visible 전시 / hidden 숨김) | | display_status_label | string | `전시` | `display_status` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | | display_status_variant | string | `success` | `display_status` 값의 표시 변형 키 (UI 배지 색상/스타일) | -| categories | array | `[]` | 소속 카테고리 목록 (각 항목: id·현지화 이름·대표 여부. categories 관계 eager load 시에만 채워짐) | -| primary_category | string | `스마트폰` | 대표 카테고리명 (is_primary 카테고리의 현지화 이름) | -| categories_with_path | array | `[]` | 소속 카테고리 목록 + 경로 (각 항목: id·breadcrumb path·대표 여부) | -| brand_name | string | `CJ제일제당` | 브랜드명 (연관 브랜드의 현지화 이름) | -| shipping_policy_id | integer | `31` | shipping policy 식별자 (연관 리소스 참조) | +| categories | array | `[{"id":56,"name":"스포츠","is_primary":0},{"id":57,"name":"축…` | 소속 카테고리 목록 (각 항목: id·현지화 이름·대표 여부. categories 관계 eager load 시에만 채워짐) | +| primary_category | string | `축구` | 대표 카테고리명 (is_primary 카테고리의 현지화 이름) | +| categories_with_path | array | `[{"id":56,"path":[{"id":56,"name":"스포츠","slug":"sports"}]…` | 소속 카테고리 목록 + 경로 (각 항목: id·breadcrumb path·대표 여부) | +| brand_name | string | `유니클로` | 브랜드명 (연관 브랜드의 현지화 이름) | +| shipping_policy_id | integer | `444` | shipping policy 식별자 (연관 리소스 참조) | | min_purchase_qty | integer | `1` | 최소 구매 수량 (1회 주문 시 이 수량 이상 구매) | | max_purchase_qty | integer | `0` | 최대 구매 수량 (0=무제한) | -| has_options | boolean | `false` | options 여부 | -| labels | array | `[]` | 노출 중인 상품 라벨 목록 (각 항목: 라벨명·색상, 활성 라벨을 sort_order 순으로 정렬) | +| has_options | boolean | `true` | options 여부 | +| labels | array | `[{"name":"베스트","color":"#F59E0B"}]` | 노출 중인 상품 라벨 목록 (각 항목: 라벨명·색상, 활성 라벨을 sort_order 순으로 정렬) | | review_count | integer | `0` | review 개수 (집계) | | rating_avg | integer | `0` | 평균 별점 (공개 리뷰 별점 평균, 소수 1자리 반올림) | -| created_at | string | `2026-07-07 14:47:31` | 생성 일시 | -| updated_at | string | `2026-07-07 14:47:31` | 최종 수정 일시 | +| created_at | string | `2026-07-30 23:36:17` | 생성 일시 | +| updated_at | string | `2026-08-05 07:27:04` | 최종 수정 일시 | | is_owner | boolean | `false` | 현재 인증 사용자가 이 리소스의 소유자인지 여부 (BaseApiResource 표준 메타) | | abilities | object | `{"can_update":true,"can_delete":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | @@ -2486,238 +1947,28 @@ HTTP/1.1 200 "data": { "data": [ { - "number": 3, - "id": 3, + "number": 98, + "id": 1831, "name": { - "ko": "nisi et corrupti", - "en": "suscipit esse id" + "ko": "겨울 패딩 점퍼 #100", + "en": "Winter Padded Jacket #100" }, - "name_localized": "nisi et corrupti", - "product_code": "PROD-YNAZ-3669", - "sku": "SKU-EOQY-1167", - "thumbnail_url": null, - "list_price": 22605, - "list_price_formatted": "22,605원", - "selling_price": 20118, - "selling_price_formatted": "20,118원", - "discount_rate": 11, - "multi_currency_list_price": { - "KRW": { - "price": 22605, - "formatted": "22,605원", - "is_default": true, - "editable": true - }, - "USD": { - "price": 19.21, - "formatted": "$19.21", - "is_default": false, - "editable": false, - "exchange_rate": 0.85 - }, - "JPY": { - "price": 2599, - "formatted": "¥2,599", - "is_default": false, - "editable": false, - "exchange_rate": 115 - }, - "CNY": { - "price": 131.11, - "formatted": "元131.11", - "is_default": false, - "editable": false, - "exchange_rate": 5.8 - }, - "EUR": { - "price": 17.63, - "formatted": "€17.63", - "is_default": false, - "editable": false, - "exchange_rate": 0.78 - } - }, - "multi_currency_selling_price": { - "KRW": { - "price": 20118, - "formatted": "20,118원", - "is_default": true, - "editable": true - }, - "USD": { - "price": 17.1, - "formatted": "$17.10", - "is_default": false, - "editable": false, - "exchange_rate": 0.85 - }, - "JPY": { - "price": 2313, - "formatted": "¥2,313", - "is_default": false, - "editable": false, - "exchange_rate": 115 - }, - "CNY": { - "price": 116.68, - "formatted": "元116.68", - "is_default": false, - "editable": false, - "exchange_rate": 5.8 - }, - "EUR": { - "price": 15.69, - "formatted": "€15.69", - "is_default": false, - "editable": false, - "exchange_rate": 0.78 - } - }, - "stock_quantity": 164, - "safe_stock_quantity": 8, - "is_below_safe_stock": false, - "sales_status": "on_sale", - "sales_status_label": "판매중", - "sales_status_variant": "success", - "display_status": "visible", - "display_status_label": "전시", - "display_status_variant": "success", - "categories": [], - "primary_category": null, - "categories_with_path": [], - "brand_name": null, - "shipping_policy_id": null, - "min_purchase_qty": 1, - "max_purchase_qty": 0, - "has_options": false, - "labels": [], - "review_count": 0, - "rating_avg": 0, - "created_at": "2026-07-08 10:44:49", - "updated_at": "2026-07-08 10:44:49", - "is_owner": false, - "abilities": { - "can_update": true, - "can_delete": true - } + "name_localized": "겨울 패딩 점퍼 #100", + "product_code": "GGK6A9N8PXNR35OQ", + "...": "(33개 키 생략, 총 38개)" }, { - "number": 2, - "id": 2, + "number": 97, + "id": 1830, "name": { - "ko": "repudiandae et et", - "en": "velit alias distinctio" + "ko": "무선 마우스 세트 #99", + "en": "Wireless Mouse Set #99" }, - "name_localized": "repudiandae et et", - "product_code": "PROD-XSMH-5438", - "sku": "SKU-XXSC-9588", - "thumbnail_url": null, - "list_price": 654947, - "list_price_formatted": "654,947원", - "selling_price": 465012, - "selling_price_formatted": "465,012원", - "discount_rate": 29, - "multi_currency_list_price": { - "KRW": { - "price": 654947, - "formatted": "654,947원", - "is_default": true, - "editable": true - }, - "USD": { - "price": 556.7, - "formatted": "$556.70", - "is_default": false, - "editable": false, - "exchange_rate": 0.85 - }, - "JPY": { - "price": 75318, - "formatted": "¥75,318", - "is_default": false, - "editable": false, - "exchange_rate": 115 - }, - "CNY": { - "price": 3798.69, - "formatted": "元3,798.69", - "is_default": false, - "editable": false, - "exchange_rate": 5.8 - }, - "EUR": { - "price": 510.86, - "formatted": "€510.86", - "is_default": false, - "editable": false, - "exchange_rate": 0.78 - } - }, - "multi_currency_selling_price": { - "KRW": { - "price": 465012, - "formatted": "465,012원", - "is_default": true, - "editable": true - }, - "USD": { - "price": 395.26, - "formatted": "$395.26", - "is_default": false, - "editable": false, - "exchange_rate": 0.85 - }, - "JPY": { - "price": 53476, - "formatted": "¥53,476", - "is_default": false, - "editable": false, - "exchange_rate": 115 - }, - "CNY": { - "price": 2697.07, - "formatted": "元2,697.07", - "is_default": false, - "editable": false, - "exchange_rate": 5.8 - }, - "EUR": { - "price": 362.71, - "formatted": "€362.71", - "is_default": false, - "editable": false, - "exchange_rate": 0.78 - } - }, - "stock_quantity": 161, - "safe_stock_quantity": 49, - "is_below_safe_stock": false, - "sales_status": "on_sale", - "sales_status_label": "판매중", - "sales_status_variant": "success", - "display_status": "visible", - "display_status_label": "전시", - "display_status_variant": "success", - "categories": [], - "primary_category": null, - "categories_with_path": [], - "brand_name": null, - "shipping_policy_id": null, - "min_purchase_qty": 1, - "max_purchase_qty": 0, - "has_options": false, - "labels": [], - "review_count": 0, - "rating_avg": 0, - "created_at": "2026-07-08 10:44:49", - "updated_at": "2026-07-08 10:44:49", - "is_owner": false, - "abilities": { - "can_update": true, - "can_delete": true - } + "name_localized": "무선 마우스 세트 #99", + "product_code": "J9HTLXFNIJZHI4B1", + "...": "(33개 키 생략, 총 38개)" }, - "... (총 3건 중 2건 표시)" + "... (총 25건 중 2건 표시)" ], "abilities": { "can_create": true, @@ -2726,12 +1977,11 @@ HTTP/1.1 200 }, "pagination": { "current_page": 1, - "last_page": 1, + "last_page": 4, "per_page": 25, - "total": 3, + "total": 98, "from": 1, - "to": 3, - "has_more_pages": false + "...": "(2개 키 생략, 총 7개)" } } } @@ -2776,21 +2026,21 @@ Authorization: Bearer {YOUR_TOKEN} (optional.sanctum: 비회원은 헤더 생 | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| id | integer | `322` | 기본 키 (내부 식별자) | -| name | object | `{"ko":"eum et quia","en":"tenetur id quae"}` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | -| name_localized | string | `eum et quia` | `name` 의 현재 로케일 해석 값 (다국어 필드를 표시용 문자열로 해석) | -| product_code | string | `PROD-GJUX-1484` | 상품코드 (상품 고유 관리 식별자) | -| sku | string | `SKU-MRAD-9306` | 재고관리코드(SKU) | +| id | integer | `1831` | 기본 키 (내부 식별자) | +| name | object | `{"ko":"겨울 패딩 점퍼 #100","en":"Winter Padded Jacket #100"}` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | +| name_localized | string | `겨울 패딩 점퍼 #100` | `name` 의 현재 로케일 해석 값 (다국어 필드를 표시용 문자열로 해석) | +| product_code | string | `GGK6A9N8PXNR35OQ` | 상품코드 (상품 고유 관리 식별자) | +| sku | string | `JK-0100` | 재고관리코드(SKU) | | thumbnail_url | string | `/api/modules/sirsoft-ecommerce/produc…` | thumbnail URL | -| list_price | integer | `112594` | 정가 (기본통화 자릿수로 정규화된 값) | -| list_price_formatted | string | `112,594원` | `list_price` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | -| selling_price | integer | `88949` | 판매가 (기본통화 자릿수로 정규화된 값) | -| selling_price_formatted | string | `88,949원` | `selling_price` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | -| discount_rate | integer | `21` | 할인율(%) (정가 대비 판매가 할인 비율, (1 - 판매가/정가) × 100) | -| multi_currency_list_price | object | `{"KRW":{"price":112594,"formatted":"112,594원","is_default…` | 통화별 정가 맵 (통화코드 → {price, formatted, is_default, editable}, 설정된 모든 통화의 환산 정가) | -| multi_currency_selling_price | object | `{"KRW":{"price":88949,"formatted":"88,949원","is_default":…` | 통화별 판매가 맵 (통화코드 → {price, formatted, is_default, editable}, 설정된 모든 통화의 환산 판매가) | -| stock_quantity | integer | `22` | 재고 수량 (옵션 사용 시 옵션 재고 합계) | -| safe_stock_quantity | integer | `12` | 안전재고 수량 (이 값 미만이면 재고 부족으로 표시) | +| list_price | integer | `200000` | 정가 (기본통화 자릿수로 정규화된 값) | +| list_price_formatted | string | `200,000원` | `list_price` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | +| selling_price | integer | `169000` | 판매가 (기본통화 자릿수로 정규화된 값) | +| selling_price_formatted | string | `169,000원` | `selling_price` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | +| discount_rate | number | `15.5` | 할인율(%) (정가 대비 판매가 할인 비율, (1 - 판매가/정가) × 100) | +| multi_currency_list_price | object | `{"KRW":{"price":200000,"formatted":"200,000원","is_default…` | 통화별 정가 맵 (통화코드 → {price, formatted, is_default, editable}, 설정된 모든 통화의 환산 정가) | +| multi_currency_selling_price | object | `{"KRW":{"price":169000,"formatted":"169,000원","is_default…` | 통화별 판매가 맵 (통화코드 → {price, formatted, is_default, editable}, 설정된 모든 통화의 환산 판매가) | +| stock_quantity | integer | `213` | 재고 수량 (옵션 사용 시 옵션 재고 합계) | +| safe_stock_quantity | integer | `18` | 안전재고 수량 (이 값 미만이면 재고 부족으로 표시) | | is_below_safe_stock | boolean | `false` | below safe stock 여부 | | sales_status | string | `on_sale` | 판매상태 값 (on_sale 판매중 / suspended 판매중지 / sold_out 품절 / coming_soon 출시예정) | | sales_status_label | string | `판매중` | `sales_status` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | @@ -2798,18 +2048,18 @@ Authorization: Bearer {YOUR_TOKEN} (optional.sanctum: 비회원은 헤더 생 | display_status | string | `visible` | 전시상태 값 (visible 전시 / hidden 숨김) | | display_status_label | string | `전시` | `display_status` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | | display_status_variant | string | `success` | `display_status` 값의 표시 변형 키 (UI 배지 색상/스타일) | -| categories | array | `[]` | 소속 카테고리 목록 (각 항목: id·현지화 이름·대표 여부. categories 관계 eager load 시에만 채워짐) | -| primary_category | string | `스마트폰` | 대표 카테고리명 (is_primary 카테고리의 현지화 이름) | -| categories_with_path | array | `[]` | 소속 카테고리 목록 + 경로 (각 항목: id·breadcrumb path·대표 여부) | -| shipping_policy_id | integer | `31` | shipping policy 식별자 (연관 리소스 참조) | +| categories | array | `[{"id":56,"name":"스포츠","is_primary":0},{"id":57,"name":"축…` | 소속 카테고리 목록 (각 항목: id·현지화 이름·대표 여부. categories 관계 eager load 시에만 채워짐) | +| primary_category | string | `축구` | 대표 카테고리명 (is_primary 카테고리의 현지화 이름) | +| categories_with_path | array | `[{"id":56,"path":[{"id":56,"name":"스포츠","slug":"sports"}]…` | 소속 카테고리 목록 + 경로 (각 항목: id·breadcrumb path·대표 여부) | +| shipping_policy_id | integer | `444` | shipping policy 식별자 (연관 리소스 참조) | | min_purchase_qty | integer | `1` | 최소 구매 수량 (1회 주문 시 이 수량 이상 구매) | | max_purchase_qty | integer | `0` | 최대 구매 수량 (0=무제한) | -| has_options | boolean | `false` | options 여부 | -| labels | array | `[]` | 노출 중인 상품 라벨 목록 (각 항목: 라벨명·색상, 활성 라벨을 sort_order 순으로 정렬) | +| has_options | boolean | `true` | options 여부 | +| labels | array | `[{"name":"베스트","color":"#F59E0B"}]` | 노출 중인 상품 라벨 목록 (각 항목: 라벨명·색상, 활성 라벨을 sort_order 순으로 정렬) | | review_count | integer | `0` | review 개수 (집계) | | rating_avg | integer | `0` | 평균 별점 (공개 리뷰 별점 평균, 소수 1자리 반올림) | -| created_at | string | `2026-07-07 14:47:31` | 생성 일시 | -| updated_at | string | `2026-07-07 14:47:31` | 최종 수정 일시 | +| created_at | string | `2026-07-30 23:36:17` | 생성 일시 | +| updated_at | string | `2026-08-05 07:27:04` | 최종 수정 일시 | | is_owner | boolean | `false` | 현재 인증 사용자가 이 리소스의 소유자인지 여부 (BaseApiResource 표준 메타) | | abilities | object | `{"can_update":true,"can_delete":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | @@ -2825,347 +2075,61 @@ HTTP/1.1 200 "message": "상품 정보를 조회했습니다.", "data": [ { - "id": 3, + "id": 1831, "name": { - "ko": "nisi et corrupti", - "en": "suscipit esse id" + "ko": "겨울 패딩 점퍼 #100", + "en": "Winter Padded Jacket #100" }, - "name_localized": "nisi et corrupti", - "product_code": "PROD-YNAZ-3669", - "sku": "SKU-EOQY-1167", - "thumbnail_url": null, - "list_price": 22605, - "list_price_formatted": "22,605원", - "selling_price": 20118, - "selling_price_formatted": "20,118원", - "discount_rate": 11, - "multi_currency_list_price": { - "KRW": { - "price": 22605, - "formatted": "22,605원", - "is_default": true, - "editable": true - }, - "USD": { - "price": 19.21, - "formatted": "$19.21", - "is_default": false, - "editable": false, - "exchange_rate": 0.85 - }, - "JPY": { - "price": 2599, - "formatted": "¥2,599", - "is_default": false, - "editable": false, - "exchange_rate": 115 - }, - "CNY": { - "price": 131.11, - "formatted": "元131.11", - "is_default": false, - "editable": false, - "exchange_rate": 5.8 - }, - "EUR": { - "price": 17.63, - "formatted": "€17.63", - "is_default": false, - "editable": false, - "exchange_rate": 0.78 - } - }, - "multi_currency_selling_price": { - "KRW": { - "price": 20118, - "formatted": "20,118원", - "is_default": true, - "editable": true - }, - "USD": { - "price": 17.1, - "formatted": "$17.10", - "is_default": false, - "editable": false, - "exchange_rate": 0.85 - }, - "JPY": { - "price": 2313, - "formatted": "¥2,313", - "is_default": false, - "editable": false, - "exchange_rate": 115 - }, - "CNY": { - "price": 116.68, - "formatted": "元116.68", - "is_default": false, - "editable": false, - "exchange_rate": 5.8 - }, - "EUR": { - "price": 15.69, - "formatted": "€15.69", - "is_default": false, - "editable": false, - "exchange_rate": 0.78 - } - }, - "stock_quantity": 164, - "safe_stock_quantity": 8, - "is_below_safe_stock": false, - "sales_status": "on_sale", - "sales_status_label": "판매중", - "sales_status_variant": "success", - "display_status": "visible", - "display_status_label": "전시", - "display_status_variant": "success", - "categories": [], - "primary_category": null, - "categories_with_path": [], - "shipping_policy_id": null, - "min_purchase_qty": 1, - "max_purchase_qty": 0, - "has_options": false, - "labels": [], - "review_count": 0, - "rating_avg": 0, - "created_at": "2026-07-08 10:44:49", - "updated_at": "2026-07-08 10:44:49", - "is_owner": false, - "abilities": { - "can_update": true, - "can_delete": true - } + "name_localized": "겨울 패딩 점퍼 #100", + "product_code": "GGK6A9N8PXNR35OQ", + "sku": "JK-0100", + "...": "(31개 키 생략, 총 36개)" }, { - "id": 2, + "id": 1830, "name": { - "ko": "repudiandae et et", - "en": "velit alias distinctio" + "ko": "무선 마우스 세트 #99", + "en": "Wireless Mouse Set #99" }, - "name_localized": "repudiandae et et", - "product_code": "PROD-XSMH-5438", - "sku": "SKU-XXSC-9588", - "thumbnail_url": null, - "list_price": 654947, - "list_price_formatted": "654,947원", - "selling_price": 465012, - "selling_price_formatted": "465,012원", - "discount_rate": 29, - "multi_currency_list_price": { - "KRW": { - "price": 654947, - "formatted": "654,947원", - "is_default": true, - "editable": true - }, - "USD": { - "price": 556.7, - "formatted": "$556.70", - "is_default": false, - "editable": false, - "exchange_rate": 0.85 - }, - "JPY": { - "price": 75318, - "formatted": "¥75,318", - "is_default": false, - "editable": false, - "exchange_rate": 115 - }, - "CNY": { - "price": 3798.69, - "formatted": "元3,798.69", - "is_default": false, - "editable": false, - "exchange_rate": 5.8 - }, - "EUR": { - "price": 510.86, - "formatted": "€510.86", - "is_default": false, - "editable": false, - "exchange_rate": 0.78 - } - }, - "multi_currency_selling_price": { - "KRW": { - "price": 465012, - "formatted": "465,012원", - "is_default": true, - "editable": true - }, - "USD": { - "price": 395.26, - "formatted": "$395.26", - "is_default": false, - "editable": false, - "exchange_rate": 0.85 - }, - "JPY": { - "price": 53476, - "formatted": "¥53,476", - "is_default": false, - "editable": false, - "exchange_rate": 115 - }, - "CNY": { - "price": 2697.07, - "formatted": "元2,697.07", - "is_default": false, - "editable": false, - "exchange_rate": 5.8 - }, - "EUR": { - "price": 362.71, - "formatted": "€362.71", - "is_default": false, - "editable": false, - "exchange_rate": 0.78 - } - }, - "stock_quantity": 161, - "safe_stock_quantity": 49, - "is_below_safe_stock": false, - "sales_status": "on_sale", - "sales_status_label": "판매중", - "sales_status_variant": "success", - "display_status": "visible", - "display_status_label": "전시", - "display_status_variant": "success", - "categories": [], - "primary_category": null, - "categories_with_path": [], - "shipping_policy_id": null, - "min_purchase_qty": 1, - "max_purchase_qty": 0, - "has_options": false, - "labels": [], - "review_count": 0, - "rating_avg": 0, - "created_at": "2026-07-08 10:44:49", - "updated_at": "2026-07-08 10:44:49", - "is_owner": false, - "abilities": { - "can_update": true, - "can_delete": true - } + "name_localized": "무선 마우스 세트 #99", + "product_code": "J9HTLXFNIJZHI4B1", + "sku": "MS-0099", + "...": "(31개 키 생략, 총 36개)" }, { - "id": 1, + "id": 1829, "name": { - "ko": "API 문서 샘플 상품", - "en": "API Doc Sample Product" + "ko": "가죽 크로스백 #98", + "en": "Leather Crossbody Bag #98" }, - "name_localized": "API 문서 샘플 상품", - "product_code": "APIDOCSAMPLE01", - "sku": "SKU-DKOK-1319", - "thumbnail_url": null, - "list_price": 703155, - "list_price_formatted": "703,155원", - "selling_price": 597682, - "selling_price_formatted": "597,682원", - "discount_rate": 15, - "multi_currency_list_price": { - "KRW": { - "price": 703155, - "formatted": "703,155원", - "is_default": true, - "editable": true - }, - "USD": { - "price": 597.68, - "formatted": "$597.68", - "is_default": false, - "editable": false, - "exchange_rate": 0.85 - }, - "JPY": { - "price": 80862, - "formatted": "¥80,862", - "is_default": false, - "editable": false, - "exchange_rate": 115 - }, - "CNY": { - "price": 4078.3, - "formatted": "元4,078.30", - "is_default": false, - "editable": false, - "exchange_rate": 5.8 - }, - "EUR": { - "price": 548.46, - "formatted": "€548.46", - "is_default": false, - "editable": false, - "exchange_rate": 0.78 - } + "name_localized": "가죽 크로스백 #98", + "product_code": "ZVNPXKW88D0ESYDK", + "sku": "BG-0098", + "...": "(31개 키 생략, 총 36개)" + }, + { + "id": 1828, + "name": { + "ko": "스테인리스 텀블러 #97", + "en": "Stainless Tumbler #97" }, - "multi_currency_selling_price": { - "KRW": { - "price": 597682, - "formatted": "597,682원", - "is_default": true, - "editable": true - }, - "USD": { - "price": 508.03, - "formatted": "$508.03", - "is_default": false, - "editable": false, - "exchange_rate": 0.85 - }, - "JPY": { - "price": 68733, - "formatted": "¥68,733", - "is_default": false, - "editable": false, - "exchange_rate": 115 - }, - "CNY": { - "price": 3466.56, - "formatted": "元3,466.56", - "is_default": false, - "editable": false, - "exchange_rate": 5.8 - }, - "EUR": { - "price": 466.19, - "formatted": "€466.19", - "is_default": false, - "editable": false, - "exchange_rate": 0.78 - } + "name_localized": "스테인리스 텀블러 #97", + "product_code": "G6TWL4RFBTWHJD71", + "sku": "TB-0097", + "...": "(31개 키 생략, 총 36개)" + }, + { + "id": 1827, + "name": { + "ko": "코튼 후드티 #96", + "en": "Cotton Hoodie #96" }, - "stock_quantity": 509, - "safe_stock_quantity": 38, - "is_below_safe_stock": false, - "sales_status": "on_sale", - "sales_status_label": "판매중", - "sales_status_variant": "success", - "display_status": "visible", - "display_status_label": "전시", - "display_status_variant": "success", - "categories": [], - "primary_category": null, - "categories_with_path": [], - "shipping_policy_id": null, - "min_purchase_qty": 1, - "max_purchase_qty": 0, - "has_options": false, - "labels": [], - "review_count": 1, - "rating_avg": 5, - "created_at": "2026-07-08 10:44:49", - "updated_at": "2026-07-08 10:44:49", - "is_owner": false, - "abilities": { - "can_update": true, - "can_delete": true - } - } + "name_localized": "코튼 후드티 #96", + "product_code": "E8CDGTL7BHAPXRHA", + "sku": "HD-0096", + "...": "(31개 키 생략, 총 36개)" + }, + "... (총 10건 중 5건 표시)" ] } ``` @@ -3209,21 +2173,21 @@ Authorization: Bearer {YOUR_TOKEN} (optional.sanctum: 비회원은 헤더 생 | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| id | integer | `204` | 기본 키 (내부 식별자) | -| name | object | `{"ko":"베이직 라운드 티셔츠 #4","en":"Basic Round T-Shirt #4"}` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | -| name_localized | string | `베이직 라운드 티셔츠 #4` | `name` 의 현재 로케일 해석 값 (다국어 필드를 표시용 문자열로 해석) | -| product_code | string | `2R9AKHR0GH2DR3NG` | 상품코드 (상품 고유 관리 식별자) | -| sku | string | `TS-0004` | 재고관리코드(SKU) | +| id | integer | `1736` | 기본 키 (내부 식별자) | +| name | object | `{"ko":"프리미엄 브이넥 티셔츠 #5","en":"Premium V-Neck T-Shirt #5"}` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | +| name_localized | string | `프리미엄 브이넥 티셔츠 #5` | `name` 의 현재 로케일 해석 값 (다국어 필드를 표시용 문자열로 해석) | +| product_code | string | `4R5OG8VYO0JKG1WQ` | 상품코드 (상품 고유 관리 식별자) | +| sku | string | `TS-0005` | 재고관리코드(SKU) | | thumbnail_url | string | `/api/modules/sirsoft-ecommerce/produc…` | thumbnail URL | -| list_price | integer | `29000` | 정가 (기본통화 자릿수로 정규화된 값) | -| list_price_formatted | string | `29,000원` | `list_price` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | -| selling_price | integer | `22000` | 판매가 (기본통화 자릿수로 정규화된 값) | -| selling_price_formatted | string | `22,000원` | `selling_price` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | -| discount_rate | number | `24.1` | 할인율(%) (정가 대비 판매가 할인 비율, (1 - 판매가/정가) × 100) | -| multi_currency_list_price | object | `{"KRW":{"price":29000,"formatted":"29,000원","is_default":…` | 통화별 정가 맵 (통화코드 → {price, formatted, is_default, editable}, 설정된 모든 통화의 환산 정가) | -| multi_currency_selling_price | object | `{"KRW":{"price":22000,"formatted":"22,000원","is_default":…` | 통화별 판매가 맵 (통화코드 → {price, formatted, is_default, editable}, 설정된 모든 통화의 환산 판매가) | -| stock_quantity | integer | `264` | 재고 수량 (옵션 사용 시 옵션 재고 합계) | -| safe_stock_quantity | integer | `15` | 안전재고 수량 (이 값 미만이면 재고 부족으로 표시) | +| list_price | integer | `34000` | 정가 (기본통화 자릿수로 정규화된 값) | +| list_price_formatted | string | `34,000원` | `list_price` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | +| selling_price | integer | `27000` | 판매가 (기본통화 자릿수로 정규화된 값) | +| selling_price_formatted | string | `27,000원` | `selling_price` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | +| discount_rate | number | `20.6` | 할인율(%) (정가 대비 판매가 할인 비율, (1 - 판매가/정가) × 100) | +| multi_currency_list_price | object | `{"KRW":{"price":34000,"formatted":"34,000원","is_default":…` | 통화별 정가 맵 (통화코드 → {price, formatted, is_default, editable}, 설정된 모든 통화의 환산 정가) | +| multi_currency_selling_price | object | `{"KRW":{"price":27000,"formatted":"27,000원","is_default":…` | 통화별 판매가 맵 (통화코드 → {price, formatted, is_default, editable}, 설정된 모든 통화의 환산 판매가) | +| stock_quantity | integer | `137` | 재고 수량 (옵션 사용 시 옵션 재고 합계) | +| safe_stock_quantity | integer | `14` | 안전재고 수량 (이 값 미만이면 재고 부족으로 표시) | | is_below_safe_stock | boolean | `false` | below safe stock 여부 | | sales_status | string | `on_sale` | 판매상태 값 (on_sale 판매중 / suspended 판매중지 / sold_out 품절 / coming_soon 출시예정) | | sales_status_label | string | `판매중` | `sales_status` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | @@ -3231,18 +2195,18 @@ Authorization: Bearer {YOUR_TOKEN} (optional.sanctum: 비회원은 헤더 생 | display_status | string | `visible` | 전시상태 값 (visible 전시 / hidden 숨김) | | display_status_label | string | `전시` | `display_status` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | | display_status_variant | string | `success` | `display_status` 값의 표시 변형 키 (UI 배지 색상/스타일) | -| categories | array | `[{"id":109,"name":"식품","is_primary":0},{"id":113,"name":"…` | 소속 카테고리 목록 (각 항목: id·현지화 이름·대표 여부) | -| primary_category | string | `해산물` | 대표 카테고리명 (is_primary 카테고리의 현지화 이름) | -| categories_with_path | array | `[{"id":109,"path":[{"id":109,"name":"식품","slug":"food"}],…` | 소속 카테고리 목록 + 경로 (각 항목: id·breadcrumb path·대표 여부) | -| shipping_policy_id | integer | `31` | shipping policy 식별자 (연관 리소스 참조) | +| categories | array | `[{"id":51,"name":"식품","is_primary":0},{"id":52,"name":"과일…` | 소속 카테고리 목록 (각 항목: id·현지화 이름·대표 여부) | +| primary_category | string | `과일` | 대표 카테고리명 (is_primary 카테고리의 현지화 이름) | +| categories_with_path | array | `[{"id":51,"path":[{"id":51,"name":"식품","slug":"food"}],"p…` | 소속 카테고리 목록 + 경로 (각 항목: id·breadcrumb path·대표 여부) | +| shipping_policy_id | integer | `444` | shipping policy 식별자 (연관 리소스 참조) | | min_purchase_qty | integer | `1` | 최소 구매 수량 (1회 주문 시 이 수량 이상 구매) | | max_purchase_qty | integer | `0` | 최대 구매 수량 (0=무제한) | | has_options | boolean | `true` | options 여부 | -| labels | array | `[]` | 노출 중인 상품 라벨 목록 (각 항목: 라벨명·색상, 활성 라벨을 sort_order 순으로 정렬) | +| labels | array | `[{"name":"품절임박","color":"#F97316"}]` | 노출 중인 상품 라벨 목록 (각 항목: 라벨명·색상, 활성 라벨을 sort_order 순으로 정렬) | | review_count | integer | `0` | review 개수 (집계) | | rating_avg | integer | `0` | 평균 별점 (공개 리뷰 별점 평균, 소수 1자리 반올림) | -| created_at | string | `2026-06-15 11:24:15` | 생성 일시 | -| updated_at | string | `2026-06-15 11:24:15` | 최종 수정 일시 | +| created_at | string | `2026-07-30 23:36:13` | 생성 일시 | +| updated_at | string | `2026-07-30 23:36:13` | 최종 수정 일시 | | is_owner | boolean | `false` | 현재 인증 사용자가 이 리소스의 소유자인지 여부 (BaseApiResource 표준 메타) | | abilities | object | `{"can_update":true,"can_delete":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | @@ -3258,347 +2222,61 @@ HTTP/1.1 200 "message": "상품 정보를 조회했습니다.", "data": [ { - "id": 2, + "id": 1736, "name": { - "ko": "repudiandae et et", - "en": "velit alias distinctio" + "ko": "프리미엄 브이넥 티셔츠 #5", + "en": "Premium V-Neck T-Shirt #5" }, - "name_localized": "repudiandae et et", - "product_code": "PROD-XSMH-5438", - "sku": "SKU-XXSC-9588", - "thumbnail_url": null, - "list_price": 654947, - "list_price_formatted": "654,947원", - "selling_price": 465012, - "selling_price_formatted": "465,012원", - "discount_rate": 29, - "multi_currency_list_price": { - "KRW": { - "price": 654947, - "formatted": "654,947원", - "is_default": true, - "editable": true - }, - "USD": { - "price": 556.7, - "formatted": "$556.70", - "is_default": false, - "editable": false, - "exchange_rate": 0.85 - }, - "JPY": { - "price": 75318, - "formatted": "¥75,318", - "is_default": false, - "editable": false, - "exchange_rate": 115 - }, - "CNY": { - "price": 3798.69, - "formatted": "元3,798.69", - "is_default": false, - "editable": false, - "exchange_rate": 5.8 - }, - "EUR": { - "price": 510.86, - "formatted": "€510.86", - "is_default": false, - "editable": false, - "exchange_rate": 0.78 - } - }, - "multi_currency_selling_price": { - "KRW": { - "price": 465012, - "formatted": "465,012원", - "is_default": true, - "editable": true - }, - "USD": { - "price": 395.26, - "formatted": "$395.26", - "is_default": false, - "editable": false, - "exchange_rate": 0.85 - }, - "JPY": { - "price": 53476, - "formatted": "¥53,476", - "is_default": false, - "editable": false, - "exchange_rate": 115 - }, - "CNY": { - "price": 2697.07, - "formatted": "元2,697.07", - "is_default": false, - "editable": false, - "exchange_rate": 5.8 - }, - "EUR": { - "price": 362.71, - "formatted": "€362.71", - "is_default": false, - "editable": false, - "exchange_rate": 0.78 - } - }, - "stock_quantity": 161, - "safe_stock_quantity": 49, - "is_below_safe_stock": false, - "sales_status": "on_sale", - "sales_status_label": "판매중", - "sales_status_variant": "success", - "display_status": "visible", - "display_status_label": "전시", - "display_status_variant": "success", - "categories": [], - "primary_category": null, - "categories_with_path": [], - "shipping_policy_id": null, - "min_purchase_qty": 1, - "max_purchase_qty": 0, - "has_options": false, - "labels": [], - "review_count": 0, - "rating_avg": 0, - "created_at": "2026-07-08 10:44:49", - "updated_at": "2026-07-08 10:44:49", - "is_owner": false, - "abilities": { - "can_update": true, - "can_delete": true - } + "name_localized": "프리미엄 브이넥 티셔츠 #5", + "product_code": "4R5OG8VYO0JKG1WQ", + "sku": "TS-0005", + "...": "(31개 키 생략, 총 36개)" }, { - "id": 1, + "id": 1743, "name": { - "ko": "API 문서 샘플 상품", - "en": "API Doc Sample Product" + "ko": "코튼 후드티 #12", + "en": "Cotton Hoodie #12" }, - "name_localized": "API 문서 샘플 상품", - "product_code": "APIDOCSAMPLE01", - "sku": "SKU-DKOK-1319", - "thumbnail_url": null, - "list_price": 703155, - "list_price_formatted": "703,155원", - "selling_price": 597682, - "selling_price_formatted": "597,682원", - "discount_rate": 15, - "multi_currency_list_price": { - "KRW": { - "price": 703155, - "formatted": "703,155원", - "is_default": true, - "editable": true - }, - "USD": { - "price": 597.68, - "formatted": "$597.68", - "is_default": false, - "editable": false, - "exchange_rate": 0.85 - }, - "JPY": { - "price": 80862, - "formatted": "¥80,862", - "is_default": false, - "editable": false, - "exchange_rate": 115 - }, - "CNY": { - "price": 4078.3, - "formatted": "元4,078.30", - "is_default": false, - "editable": false, - "exchange_rate": 5.8 - }, - "EUR": { - "price": 548.46, - "formatted": "€548.46", - "is_default": false, - "editable": false, - "exchange_rate": 0.78 - } - }, - "multi_currency_selling_price": { - "KRW": { - "price": 597682, - "formatted": "597,682원", - "is_default": true, - "editable": true - }, - "USD": { - "price": 508.03, - "formatted": "$508.03", - "is_default": false, - "editable": false, - "exchange_rate": 0.85 - }, - "JPY": { - "price": 68733, - "formatted": "¥68,733", - "is_default": false, - "editable": false, - "exchange_rate": 115 - }, - "CNY": { - "price": 3466.56, - "formatted": "元3,466.56", - "is_default": false, - "editable": false, - "exchange_rate": 5.8 - }, - "EUR": { - "price": 466.19, - "formatted": "€466.19", - "is_default": false, - "editable": false, - "exchange_rate": 0.78 - } - }, - "stock_quantity": 509, - "safe_stock_quantity": 38, - "is_below_safe_stock": false, - "sales_status": "on_sale", - "sales_status_label": "판매중", - "sales_status_variant": "success", - "display_status": "visible", - "display_status_label": "전시", - "display_status_variant": "success", - "categories": [], - "primary_category": null, - "categories_with_path": [], - "shipping_policy_id": null, - "min_purchase_qty": 1, - "max_purchase_qty": 0, - "has_options": false, - "labels": [], - "review_count": 1, - "rating_avg": 5, - "created_at": "2026-07-08 10:44:49", - "updated_at": "2026-07-08 10:44:49", - "is_owner": false, - "abilities": { - "can_update": true, - "can_delete": true - } + "name_localized": "코튼 후드티 #12", + "product_code": "5D0E22LJLQVSKI1G", + "sku": "HD-0012", + "...": "(31개 키 생략, 총 36개)" }, { - "id": 3, + "id": 1747, "name": { - "ko": "nisi et corrupti", - "en": "suscipit esse id" + "ko": "겨울 패딩 점퍼 #16", + "en": "Winter Padded Jacket #16" }, - "name_localized": "nisi et corrupti", - "product_code": "PROD-YNAZ-3669", - "sku": "SKU-EOQY-1167", - "thumbnail_url": null, - "list_price": 22605, - "list_price_formatted": "22,605원", - "selling_price": 20118, - "selling_price_formatted": "20,118원", - "discount_rate": 11, - "multi_currency_list_price": { - "KRW": { - "price": 22605, - "formatted": "22,605원", - "is_default": true, - "editable": true - }, - "USD": { - "price": 19.21, - "formatted": "$19.21", - "is_default": false, - "editable": false, - "exchange_rate": 0.85 - }, - "JPY": { - "price": 2599, - "formatted": "¥2,599", - "is_default": false, - "editable": false, - "exchange_rate": 115 - }, - "CNY": { - "price": 131.11, - "formatted": "元131.11", - "is_default": false, - "editable": false, - "exchange_rate": 5.8 - }, - "EUR": { - "price": 17.63, - "formatted": "€17.63", - "is_default": false, - "editable": false, - "exchange_rate": 0.78 - } + "name_localized": "겨울 패딩 점퍼 #16", + "product_code": "5E1WSBY0CHFX7UJU", + "sku": "JK-0016", + "...": "(31개 키 생략, 총 36개)" + }, + { + "id": 1735, + "name": { + "ko": "베이직 라운드 티셔츠 #4", + "en": "Basic Round T-Shirt #4" }, - "multi_currency_selling_price": { - "KRW": { - "price": 20118, - "formatted": "20,118원", - "is_default": true, - "editable": true - }, - "USD": { - "price": 17.1, - "formatted": "$17.10", - "is_default": false, - "editable": false, - "exchange_rate": 0.85 - }, - "JPY": { - "price": 2313, - "formatted": "¥2,313", - "is_default": false, - "editable": false, - "exchange_rate": 115 - }, - "CNY": { - "price": 116.68, - "formatted": "元116.68", - "is_default": false, - "editable": false, - "exchange_rate": 5.8 - }, - "EUR": { - "price": 15.69, - "formatted": "€15.69", - "is_default": false, - "editable": false, - "exchange_rate": 0.78 - } + "name_localized": "베이직 라운드 티셔츠 #4", + "product_code": "Y3Y792BFFW3HRW7A", + "sku": "TS-0004", + "...": "(31개 키 생략, 총 36개)" + }, + { + "id": 1733, + "name": { + "ko": "기본 양말 5족 #2", + "en": "Basic Socks 5 Pairs #2" }, - "stock_quantity": 164, - "safe_stock_quantity": 8, - "is_below_safe_stock": false, - "sales_status": "on_sale", - "sales_status_label": "판매중", - "sales_status_variant": "success", - "display_status": "visible", - "display_status_label": "전시", - "display_status_variant": "success", - "categories": [], - "primary_category": null, - "categories_with_path": [], - "shipping_policy_id": null, - "min_purchase_qty": 1, - "max_purchase_qty": 0, - "has_options": false, - "labels": [], - "review_count": 0, - "rating_avg": 0, - "created_at": "2026-07-08 10:44:49", - "updated_at": "2026-07-08 10:44:49", - "is_owner": false, - "abilities": { - "can_update": true, - "can_delete": true - } - } + "name_localized": "기본 양말 5족 #2", + "product_code": "S0SO3A6SJFYLAKSF", + "sku": "SK-0002", + "...": "(31개 키 생략, 총 36개)" + }, + "... (총 10건 중 5건 표시)" ] } ``` @@ -3670,7 +2348,7 @@ HTTP/1.1 200 **설명** 최근 본 상품 목록을 조회합니다. `optional.sanctum`(회원/비회원 모두 접근) + `sirsoft-ecommerce.user-products.read` 권한이 적용되며, 클라이언트가 로컬에 보관한 조회 이력 상품 ID들을 쉼표 구분 문자열(`ids`)로 전달하면 컨트롤러가 정수 배열로 파싱해 `ProductService::getProductsByIds()`로 조회한 뒤 `ProductListResource` 컬렉션을 반환합니다. `ids`가 비어 있으면 빈 배열을 반환하며, 확장은 `sirsoft-ecommerce.product.public_recent_validation_rules` 훅으로 검증 규칙을 확장할 수 있습니다. -### GET /api/modules/sirsoft-ecommerce/products/{id} +### GET /api/modules/sirsoft-ecommerce/products/{product} - **라우트명**: `api.modules.sirsoft-ecommerce.products.show` - **컨트롤러**: `Modules\Sirsoft\Ecommerce\Http\Controllers\Public\ProductController@show` @@ -3680,12 +2358,12 @@ HTTP/1.1 200 | 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | | --- | --- | --- | --- | --- | --- | -| id | path | string | 예 | — | 대상 리소스의 식별자 | +| product | path | string | 예 | — | 대상 product의 식별자 | **요청 예시** ```http -GET /api/modules/sirsoft-ecommerce/products/1 HTTP/1.1 +GET /api/modules/sirsoft-ecommerce/products/1732 HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} (optional.sanctum: 비회원은 헤더 생략 가능) @@ -3697,50 +2375,51 @@ _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| id | integer | `1` | 기본 키 (내부 식별자) | -| name | object | `{"ko":"API 문서 샘플 상품","en":"API Doc Sample Product"}` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | -| name_localized | string | `API 문서 샘플 상품` | `name` 의 현재 로케일 해석 값 (다국어 필드를 표시용 문자열로 해석) | -| product_code | string | `APIDOCSAMPLE01` | 상품코드 | -| sku | string | `SKU-DKOK-1319` | SKU | -| categories | array | `[]` | 소속 카테고리 목록 (각 항목: id·현지화 이름·대표 여부. categories 관계 eager load 시에만 채워짐) | -| category_name | null | `null` | 대표 카테고리명 (is_primary 카테고리의 현지화 이름) | -| list_price | integer | `703155` | 정가 (기본통화 기준) | -| list_price_formatted | string | `703,155원` | `list_price` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | -| selling_price | integer | `597682` | 판매가 (기본통화 기준) | -| selling_price_formatted | string | `597,682원` | `selling_price` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | -| discount_rate | integer | `15` | 할인율(%) (정가 대비 판매가 할인 비율, (1 - 판매가/정가) × 100) | -| multi_currency_list_price | object | `{"KRW":{"price":703155,"formatted":"703,155원","is_default…` | 통화별 정가 맵 (통화코드 → {price, formatted, is_default, editable}, 설정된 모든 통화의 환산 정가) | -| multi_currency_selling_price | object | `{"KRW":{"price":597682,"formatted":"597,682원","is_default…` | 통화별 판매가 맵 (통화코드 → {price, formatted, is_default, editable}, 설정된 모든 통화의 환산 판매가) | -| stock_quantity | integer | `509` | 재고 수량 (옵션 있으면 옵션 합계) | +| id | integer | `1732` | 기본 키 (내부 식별자) | +| name | object | `{"ko":"면 손수건 3매입 #1","en":"Cotton Handkerchief 3pcs #1"}` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | +| name_localized | string | `면 손수건 3매입 #1` | `name` 의 현재 로케일 해석 값 (다국어 필드를 표시용 문자열로 해석) | +| product_code | string | `CJTFHBL8SLRQ8ILM` | 상품코드 | +| sku | string | `HK-0001` | SKU | +| categories | array | `[{"id":29,"name":{"ko":"의류","en":"Clothing"},"name_locali…` | 소속 카테고리 목록 (각 항목: id·현지화 이름·대표 여부. categories 관계 eager load 시에만 채워짐) | +| category_name | string | `바지` | 대표 카테고리명 (is_primary 카테고리의 현지화 이름) | +| list_price | integer | `5000` | 정가 (기본통화 기준) | +| list_price_formatted | string | `5,000원` | `list_price` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | +| selling_price | integer | `3000` | 판매가 (기본통화 기준) | +| selling_price_formatted | string | `3,000원` | `selling_price` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | +| discount_rate | integer | `40` | 할인율(%) (정가 대비 판매가 할인 비율, (1 - 판매가/정가) × 100) | +| multi_currency_list_price | object | `{"KRW":{"price":5000,"formatted":"5,000원","is_default":tr…` | 통화별 정가 맵 (통화코드 → {price, formatted, is_default, editable}, 설정된 모든 통화의 환산 정가) | +| multi_currency_selling_price | object | `{"KRW":{"price":3000,"formatted":"3,000원","is_default":tr…` | 통화별 판매가 맵 (통화코드 → {price, formatted, is_default, editable}, 설정된 모든 통화의 환산 판매가) | +| stock_quantity | integer | `26` | 재고 수량 (옵션 있으면 옵션 합계) | | min_purchase_qty | integer | `1` | 최소 구매 수량 | | max_purchase_qty | integer | `0` | 최대 구매 수량 (0=무제한) | | sales_status | string | `on_sale` | 판매상태: on_sale(판매중), suspended(판매중지), sold_out(품절), coming_soon(출시예정) | | sales_status_label | string | `판매중` | `sales_status` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | -| brand_name | null | `null` | 브랜드명 (연관 브랜드의 현지화 이름) | -| labels | array | `[]` | 노출 중인 상품 라벨 목록 (각 항목: 라벨명·색상, 활성 라벨을 sort_order 순으로 정렬) | +| brand_name | string | `유니클로` | 브랜드명 (연관 브랜드의 현지화 이름) | +| labels | array | `[{"name":"할인","color":"#EF4444"},{"name":"이벤트","color":"#…` | 노출 중인 상품 라벨 목록 (각 항목: 라벨명·색상, 활성 라벨을 sort_order 순으로 정렬) | | additional_options | array | `[]` | 추가옵션 그룹 목록 (각 그룹: 그룹명·필수 여부·선택지 목록, 활성 옵션을 sort_order 순으로 정렬) | -| shipping_policy_id | null | `null` | shipping policy 식별자 (연관 리소스 참조) | +| shipping_policy_id | integer | `444` | shipping policy 식별자 (연관 리소스 참조) | | is_shippable_to_selected_country | boolean | `true` | shippable to selected country 여부 | | selected_shipping_country | string | `KR` | 배송비 계산에 적용된 배송 국가 코드 (ResolveShippingCountry 해석 결과) | | free_shipping | boolean | `false` | 무료배송 여부 (적용 배송정책의 청구방식이 무료(FREE)인 경우 true) | -| shipping_fee_formatted | string | `` | `shipping_fee` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | -| shipping_policy | null | `null` | 적용 배송정책 요약 객체 (정책명·청구방식·기본배송비·무료배송 기준액 등, 상품 정책 없으면 기본 정책으로 폴백) | +| shipping_fee_formatted | string | `KR: 무료배송` | `shipping_fee` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | +| shipping_policy | object | `{"name":"국내 무료배송","charge_policy":null,"charge_policy_lab…` | 적용 배송정책 요약 객체 (정책명·청구방식·기본배송비·무료배송 기준액 등, 상품 정책 없으면 기본 정책으로 폴백) | | short_description | null | `null` | 짧은 설명 (상품 목록/카드용 요약 설명, 다국어 필드는 로케일별 값 객체) | | short_description_localized | null | `null` | `short description` 의 현재 로케일 해석 값 (다국어 필드를 표시용 문자열로 해석) | -| description | object | `{"ko":"Tempore et non fugit eos et aperiam dolore. Aut es…` | 설명 (다국어 필드는 로케일별 값 객체) | -| description_localized | string | `Tempore et non fugit eos et aperiam d…` | `description` 의 현재 로케일 해석 값 (다국어 필드를 표시용 문자열로 해석) | +| description | object | `{"ko":"

부드러운 면 100% 손수건 3매 세트입니다.<\/p>","en":"

A set …` | 설명 (다국어 필드는 로케일별 값 객체) | +| description_localized | string | `

부드러운 면 100% 손수건 3매 세트입니다.

` | `description` 의 현재 로케일 해석 값 (다국어 필드를 표시용 문자열로 해석) | | description_mode | string | `text` | 설명 모드: text(텍스트), html(HTML) | -| images | array | `[]` | 상품 이미지 목록 (각 항목: hash·url·alt_text·is_thumbnail·sort_order 등, images 관계 로드 시) | -| thumbnail_url | null | `null` | thumbnail URL | +| images | array | `[{"id":7,"hash":"7df7761cdf16","original_filename":"produ…` | 상품 이미지 목록 (각 항목: hash·url·alt_text·is_thumbnail·sort_order 등, images 관계 로드 시) | +| thumbnail_url | string | `/api/modules/sirsoft-ecommerce/produc…` | thumbnail URL | | meta_title | null | `null` | SEO 제목 (다국어 JSON) | | meta_description | null | `null` | SEO 설명 (다국어 JSON) | | meta_keywords | null | `null` | SEO 키워드 (배열) | -| has_options | boolean | `false` | options 여부 | -| option_groups | array | `[]` | 옵션 그룹 정의: [{name: "색상", values: ["빨강", "파랑"]}] | -| options | array | `[]` | 활성 옵션(SKU) 목록 (각 옵션의 코드·옵션값·가격·재고 등, ProductOptionResource) | -| notice | null | `null` | 상품정보제공고시 (템플릿명 + 항목별 라벨/내용 목록, notice 관계 로드 시) | -| common_info | null | `null` | 공통정보 (name·content·content_mode, commonInfo 관계 로드 시 — 여러 상품이 공유하는 공통 안내문) | -| is_wishlisted | boolean | `false` | wishlisted 여부 | +| has_options | boolean | `true` | options 여부 | +| option_groups | array | `[{"name":{"ko":"색상","en":"Color"},"name_localized":"색상","…` | 옵션 그룹 정의: [{name: "색상", values: ["빨강", "파랑"]}] | +| options | array | `[{"id":948,"option_code":"CJTFHBL8SLRQ8ILM-001","option_v…` | 활성 옵션(SKU) 목록 (각 옵션의 코드·옵션값·가격·재고 등, ProductOptionResource) | +| notice | object | `{"template_name":null,"values":[{"label":"항목1","value":"상…` | 상품정보제공고시 (템플릿명 + 항목별 라벨/내용 목록, notice 관계 로드 시) | +| common_info | object | `{"name":"부분 배송 안내","content":"• 적용 조건: 주문 상품의 재고 상황이 다른 경…` | 공통정보 (name·content·content_mode, commonInfo 관계 로드 시 — 여러 상품이 공유하는 공통 안내문) | +| is_wishlisted | boolean | `true` | wishlisted 여부 | +| abilities | object | `{"can_update":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | **응답 예시** @@ -3753,126 +2432,15 @@ HTTP/1.1 200 "success": true, "message": "상품 정보를 조회했습니다.", "data": { - "id": 1, + "id": 1732, "name": { - "ko": "API 문서 샘플 상품", - "en": "API Doc Sample Product" + "ko": "면 손수건 3매입 #1", + "en": "Cotton Handkerchief 3pcs #1" }, - "name_localized": "API 문서 샘플 상품", - "product_code": "APIDOCSAMPLE01", - "sku": "SKU-DKOK-1319", - "categories": [], - "category_name": null, - "list_price": 703155, - "list_price_formatted": "703,155원", - "selling_price": 597682, - "selling_price_formatted": "597,682원", - "discount_rate": 15, - "multi_currency_list_price": { - "KRW": { - "price": 703155, - "formatted": "703,155원", - "is_default": true, - "editable": true - }, - "USD": { - "price": 597.68, - "formatted": "$597.68", - "is_default": false, - "editable": false, - "exchange_rate": 0.85 - }, - "JPY": { - "price": 80862, - "formatted": "¥80,862", - "is_default": false, - "editable": false, - "exchange_rate": 115 - }, - "CNY": { - "price": 4078.3, - "formatted": "元4,078.30", - "is_default": false, - "editable": false, - "exchange_rate": 5.8 - }, - "EUR": { - "price": 548.46, - "formatted": "€548.46", - "is_default": false, - "editable": false, - "exchange_rate": 0.78 - } - }, - "multi_currency_selling_price": { - "KRW": { - "price": 597682, - "formatted": "597,682원", - "is_default": true, - "editable": true - }, - "USD": { - "price": 508.03, - "formatted": "$508.03", - "is_default": false, - "editable": false, - "exchange_rate": 0.85 - }, - "JPY": { - "price": 68733, - "formatted": "¥68,733", - "is_default": false, - "editable": false, - "exchange_rate": 115 - }, - "CNY": { - "price": 3466.56, - "formatted": "元3,466.56", - "is_default": false, - "editable": false, - "exchange_rate": 5.8 - }, - "EUR": { - "price": 466.19, - "formatted": "€466.19", - "is_default": false, - "editable": false, - "exchange_rate": 0.78 - } - }, - "stock_quantity": 509, - "min_purchase_qty": 1, - "max_purchase_qty": 0, - "sales_status": "on_sale", - "sales_status_label": "판매중", - "brand_name": null, - "labels": [], - "additional_options": [], - "shipping_policy_id": null, - "is_shippable_to_selected_country": true, - "selected_shipping_country": "KR", - "free_shipping": false, - "shipping_fee_formatted": "", - "shipping_policy": null, - "short_description": null, - "short_description_localized": null, - "description": { - "ko": "Tempore et non fugit eos et aperiam dolore. Aut esse fugiat adipisci iure itaque nisi. Earum ab dignissimos ipsa harum dolor sunt voluptatem.\n\nEst voluptatibus in sed distinctio. Delectus occaecati et earum est rerum ex. Beatae ex aut autem impedit facere totam. Et tempore minus veniam doloribus maiores eveniet dolorem ut. Culpa sed inventore ipsam.\n\nFuga praesentium distinctio quia ducimus harum assumenda. Ipsa dolorum odio reiciendis quis id. Tempore facere molestiae et reprehenderit reiciendis dicta quos. Quae labore deserunt fuga quam beatae. Praesentium aut fuga ratione aliquid.", - "en": "Soluta et ut repellendus rem. Id ea quibusdam in. Doloribus saepe rem praesentium et quis et molestiae.\n\nDolor voluptas aut qui unde sed quia. Corrupti qui enim laborum maxime possimus quis. Odit ducimus doloremque qui officiis fugit rerum. Magni ea possimus quia commodi optio saepe. Recusandae itaque in voluptatum et.\n\nRerum enim velit est inventore. Ipsa laboriosam provident ullam." - }, - "description_localized": "Tempore et non fugit eos et aperiam dolore. Aut esse fugiat adipisci iure itaque nisi. Earum ab dignissimos ipsa harum dolor sunt voluptatem.\n\nEst voluptatibus in sed distinctio. Delectus occaecati et earum est rerum ex. Beatae ex aut autem impedit facere totam. Et tempore minus veniam doloribus maiores eveniet dolorem ut. Culpa sed inventore ipsam.\n\nFuga praesentium distinctio quia ducimus harum assumenda. Ipsa dolorum odio reiciendis quis id. Tempore facere molestiae et reprehenderit reiciendis dicta quos. Quae labore deserunt fuga quam beatae. Praesentium aut fuga ratione aliquid.", - "description_mode": "text", - "images": [], - "thumbnail_url": null, - "meta_title": null, - "meta_description": null, - "meta_keywords": null, - "has_options": false, - "option_groups": [], - "options": [], - "notice": null, - "common_info": null, - "is_wishlisted": false + "name_localized": "면 손수건 3매입 #1", + "product_code": "CJTFHBL8SLRQ8ILM", + "sku": "HK-0001", + "...": "(40개 키 생략, 총 45개)" } } ``` @@ -3889,7 +2457,7 @@ HTTP/1.1 200 **설명** 쇼핑몰 프런트용 공개 상품 상세를 조회합니다. `optional.sanctum`(회원/비회원 모두 접근) + `sirsoft-ecommerce.user-products.read` 권한이 적용되며, `ProductService::getDetail()`로 상품을 로드하되 전시상태가 visible이 아니면 404를 반환합니다. 상세 페이지에 필요한 배송정책·상품고시·공통정보·브랜드·라벨·추가옵션·현재 사용자 위시리스트 관계를 추가 로드하고 `PublicProductResource`로 반환하며, 응답에는 다중통화 가격·배송비 안내(`shipping_fee_formatted`)·`is_wishlisted` 등 프런트 표시용 파생 필드가 포함됩니다. -### GET /api/modules/sirsoft-ecommerce/products/{productId}/downloadable-coupons +### GET /api/modules/sirsoft-ecommerce/products/{product}/downloadable-coupons - **라우트명**: `api.modules.sirsoft-ecommerce.products.downloadable-coupons` - **컨트롤러**: `Modules\Sirsoft\Ecommerce\Http\Controllers\Public\PublicCouponController@downloadableCoupons` @@ -3899,12 +2467,12 @@ HTTP/1.1 200 | 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | | --- | --- | --- | --- | --- | --- | -| productId | path | string | 예 | — | 대상 product의 식별자 | +| product | path | string | 예 | — | 대상 product의 식별자 | **요청 예시** ```http -GET /api/modules/sirsoft-ecommerce/products/1/downloadable-coupons HTTP/1.1 +GET /api/modules/sirsoft-ecommerce/products/1732/downloadable-coupons HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} (optional.sanctum: 비회원은 헤더 생략 가능) @@ -3916,19 +2484,19 @@ _목록 응답: `data.data[]` 배열 항목의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| coupon_id | integer | `1` | coupon 식별자 (연관 리소스 참조) | -| localized_name | string | `API 문서 샘플 쿠폰` | `name` 의 현재 로케일 해석 값 (다국어 필드를 표시용 문자열로 해석) | -| benefit_formatted | string | `1,000원 할인` | `benefit` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | -| multi_currency_benefit_formatted | object | `{"KRW":"1,000원 할인","USD":"$0.85 할인","JPY":"¥115 할인","CNY"…` | `multi_currency_benefit` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | -| target_type | string | `order_amount` | 적용대상(할인 기준): product_amount(상품금액), order_amount(주문금액), shipping_fee(배송비) | -| target_type_short_label | string | `주문` | `target_type_short` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | -| valid_period_formatted | string | `-` | `valid_period` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | -| min_order_amount | string | `0.00` | 쿠폰 적용 최소 주문금액 (0=제한 없음) | -| min_order_amount_formatted | string | `0원` | `min_order_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | -| multi_currency_min_order_amount | null | `null` | 최소 주문금액의 통화별 환산 맵 (0이면 null) | -| total_quantity | null | `null` | 총 발급 수량 (null=무제한) | -| remaining_quantity | null | `null` | 잔여 발급 가능 수량 (total_quantity − issued_count, 무제한이면 null) | -| is_downloaded | boolean | `false` | downloaded 여부 | +| coupon_id | integer | `107` | coupon 식별자 (연관 리소스 참조) | +| localized_name | string | `카테고리 배송비 3,000원 할인` | `name` 의 현재 로케일 해석 값 (다국어 필드를 표시용 문자열로 해석) | +| benefit_formatted | string | `¥3,000 할인` | `benefit` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | +| multi_currency_benefit_formatted | object | `{"KRW":"3,000원 할인","USD":"$25.50 할인","JPY":"¥3,000 할인","C…` | `multi_currency_benefit` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | +| target_type | string | `shipping_fee` | 적용대상(할인 기준): product_amount(상품금액), order_amount(주문금액), shipping_fee(배송비) | +| target_type_short_label | string | `배송비` | `target_type_short` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | +| valid_period_formatted | string | `2026-07-23 ~ 2026-09-21` | `valid_period` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | +| min_order_amount | string | `20000.00` | 쿠폰 적용 최소 주문금액 (0=제한 없음) | +| min_order_amount_formatted | string | `¥20,000` | `min_order_amount` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | +| multi_currency_min_order_amount | object | `{"KRW":{"price":20000,"formatted":"20,000원","is_default":…` | 최소 주문금액의 통화별 환산 맵 (0이면 null) | +| total_quantity | integer | `300` | 총 발급 수량 (null=무제한) | +| remaining_quantity | integer | `299` | 잔여 발급 가능 수량 (total_quantity − issued_count, 무제한이면 null) | +| is_downloaded | boolean | `true` | downloaded 여부 | **응답 예시** @@ -3943,26 +2511,106 @@ HTTP/1.1 200 "data": { "data": [ { - "coupon_id": 1, - "localized_name": "API 문서 샘플 쿠폰", - "benefit_formatted": "1,000원 할인", + "coupon_id": 107, + "localized_name": "카테고리 배송비 3,000원 할인", + "benefit_formatted": "¥3,000 할인", "multi_currency_benefit_formatted": { - "KRW": "1,000원 할인", - "USD": "$0.85 할인", - "JPY": "¥115 할인", - "CNY": "元5.80 할인", - "EUR": "€0.78 할인" + "KRW": "3,000원 할인", + "USD": "$25.50 할인", + "JPY": "¥3,000 할인", + "CNY": "元174.00 할인", + "EUR": "€23.40 할인" }, - "target_type": "order_amount", - "target_type_short_label": "주문", - "valid_period_formatted": "-", - "min_order_amount": "0.00", - "min_order_amount_formatted": "0원", - "multi_currency_min_order_amount": null, - "total_quantity": null, - "remaining_quantity": null, - "is_downloaded": false - } + "target_type": "shipping_fee", + "target_type_short_label": "배송비", + "valid_period_formatted": "2026-07-23 ~ 2026-09-21", + "min_order_amount": "20000.00", + "min_order_amount_formatted": "¥20,000", + "multi_currency_min_order_amount": { + "KRW": { + "price": 20000, + "formatted": "20,000원", + "is_default": true + }, + "USD": { + "price": 170, + "formatted": "$170.00", + "is_default": false, + "exchange_rate": 0.85 + }, + "JPY": { + "price": 20000, + "formatted": "¥20,000", + "is_default": true + }, + "CNY": { + "price": 1160, + "formatted": "元1,160.00", + "is_default": false, + "exchange_rate": 5.8 + }, + "EUR": { + "price": 156, + "formatted": "€156.00", + "is_default": false, + "exchange_rate": 0.78 + } + }, + "total_quantity": 300, + "remaining_quantity": 299, + "is_downloaded": true + }, + { + "coupon_id": 103, + "localized_name": "배송비 50% 할인", + "benefit_formatted": "50.00% 할인 (최대 ¥3,000)", + "multi_currency_benefit_formatted": { + "KRW": "50.00% 할인 (최대 3,000원)", + "USD": "50.00% 할인 (최대 $25.50)", + "JPY": "50.00% 할인 (최대 ¥3,000)", + "CNY": "50.00% 할인 (최대 元174.00)", + "EUR": "50.00% 할인 (최대 €23.40)" + }, + "target_type": "shipping_fee", + "target_type_short_label": "배송비", + "valid_period_formatted": "발급일로부터 14일", + "min_order_amount": "20000.00", + "min_order_amount_formatted": "¥20,000", + "multi_currency_min_order_amount": { + "KRW": { + "price": 20000, + "formatted": "20,000원", + "is_default": true + }, + "USD": { + "price": 170, + "formatted": "$170.00", + "is_default": false, + "exchange_rate": 0.85 + }, + "JPY": { + "price": 20000, + "formatted": "¥20,000", + "is_default": true + }, + "CNY": { + "price": 1160, + "formatted": "元1,160.00", + "is_default": false, + "exchange_rate": 5.8 + }, + "EUR": { + "price": 156, + "formatted": "€156.00", + "is_default": false, + "exchange_rate": 0.78 + } + }, + "total_quantity": 1000, + "remaining_quantity": 938, + "is_downloaded": true + }, + "... (총 8건 중 2건 표시)" ] } } @@ -3980,7 +2628,7 @@ HTTP/1.1 200 **설명** 특정 상품에서 다운로드 가능한 쿠폰 목록을 조회합니다. `optional.sanctum`(회원/비회원 모두 접근) + `sirsoft-ecommerce.user-products.read` 권한이 적용되며, `UserCouponService::getProductDownloadableCoupons()`가 해당 상품에 적용 가능한 발급 대기 쿠폰을 반환합니다. 로그인 상태면 사용자 ID를 함께 넘겨 각 쿠폰의 `is_downloaded`(이미 받았는지) 여부를 채워주고, 다중통화 혜택·최소주문금액(`multi_currency_benefit_formatted` 등)이 포함됩니다. 상품 상세의 쿠폰 받기 영역에 사용됩니다. -### GET /api/modules/sirsoft-ecommerce/products/{productId}/inquiries +### GET /api/modules/sirsoft-ecommerce/products/{product}/inquiries - **라우트명**: `api.modules.sirsoft-ecommerce.products.inquiries.index` - **컨트롤러**: `Modules\Sirsoft\Ecommerce\Http\Controllers\Public\ProductInquiryController@index` @@ -3990,12 +2638,15 @@ HTTP/1.1 200 | 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | | --- | --- | --- | --- | --- | --- | -| productId | path | string | 예 | — | 대상 product의 식별자 | +| product | path | string | 예 | — | 대상 product의 식별자 | +| page | query | integer | 아니오 | min 1 | 조회할 페이지 번호 (1부터 시작) | +| per_page | query | integer | 아니오 | min 1, max 100 | 페이지당 항목 수 | +| exclude_secret | query | boolean | 아니오 | — | | **요청 예시** ```http -GET /api/modules/sirsoft-ecommerce/products/1/inquiries HTTP/1.1 +GET /api/modules/sirsoft-ecommerce/products/1732/inquiries?page=1&per_page=1&exclude_secret=1 HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} (optional.sanctum: 비회원은 헤더 생략 가능) @@ -4007,8 +2658,8 @@ _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| items | array | `[]` | 상품문의 항목 배열 (각 항목: id·작성자·작성일·답변 여부·게시판 연동 시 제목/내용/비밀글 여부/답변/첨부) | -| meta | object | `{"board_settings":{"secret_mode":"disabled","categories":…` | 문의 목록 메타 (board_settings 게시판 설정, inquiry_available 문의 게시판 연동 여부, current_page/per_page/total/last_page 페이지네이션, abilities 답변·삭제 권한) | +| items | array | `[{"id":60,"post_id":284,"user_id":1116,"author_name":null…` | 상품문의 항목 배열 (각 항목: id·작성자·작성일·답변 여부·게시판 연동 시 제목/내용/비밀글 여부/답변/첨부) | +| meta | object | `{"board_settings":{"secret_mode":"always","categories":[]…` | 문의 목록 메타 (board_settings 게시판 설정, inquiry_available 문의 게시판 연동 여부, current_page/per_page/total/last_page 페이지네이션, abilities 답변·삭제 권한) | **응답 예시** @@ -4021,25 +2672,63 @@ HTTP/1.1 200 "success": true, "message": "문의 목록을 조회했습니다.", "data": { - "items": [], + "items": [ + { + "id": 60, + "post_id": 284, + "user_id": 1116, + "author_name": null, + "title": "[S34-OTHER-3] 타인 문의", + "...": "(9개 키 생략, 총 14개)" + }, + { + "id": 59, + "post_id": 283, + "user_id": 1116, + "author_name": null, + "title": "[S34-OTHER-2] 타인 문의", + "...": "(9개 키 생략, 총 14개)" + }, + { + "id": 58, + "post_id": 282, + "user_id": 1116, + "author_name": null, + "title": "[S34-OTHER-1] 타인 문의", + "...": "(9개 키 생략, 총 14개)" + }, + { + "id": 19, + "post_id": 223, + "user_id": 1209, + "author_name": "최***자", + "title": "[S33-T4-01] 배송 기간 문의드립니다", + "...": "(9개 키 생략, 총 14개)" + }, + { + "id": 20, + "post_id": 224, + "user_id": 1209, + "author_name": "최***자", + "title": "[S33-2] 재입고 관련 문의", + "...": "(9개 키 생략, 총 14개)" + }, + "... (총 25건 중 5건 표시)" + ], "meta": { "board_settings": { "secret_mode": "{MASKED}", "categories": [], - "use_file_upload": false, - "max_file_count": 5, - "max_file_size": 10485760, - "allowed_extensions": [], - "min_title_length": 2, - "max_title_length": 200, - "min_content_length": 10, - "max_content_length": 10000 + "use_file_upload": true, + "max_file_count": 3, + "max_file_size": 5, + "...": "(7개 키 생략, 총 12개)" }, - "inquiry_available": false, - "total": 0, + "inquiry_available": true, "current_page": 1, "per_page": 25, - "last_page": 1 + "total": 36, + "...": "(2개 키 생략, 총 7개)" } } } @@ -4050,6 +2739,7 @@ HTTP/1.1 200 | 상태코드 | 의미 | 발생 조건 | | --- | --- | --- | | 403 | Forbidden | 요구 권한(`sirsoft-ecommerce.user-products.read`)이 없는 경우 | +| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) | | 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 | @@ -4057,7 +2747,7 @@ HTTP/1.1 200 **설명** 상품의 1:1 문의 목록을 조회합니다. `optional.sanctum`(회원/비회원 모두 접근) + `sirsoft-ecommerce.user-products.read` 권한이 적용되며, `ProductInquiryService::getProductInquiries()`가 게시판 모듈과 연동된 문의 글을 페이지네이션해 `items`와 `board_settings`(비밀글 모드·카테고리 등) 메타를 반환합니다. `per_page`/`page`/`exclude_secret` 쿼리로 조회 범위를 조정하며, 비밀 문의는 설정과 열람 권한에 따라 마스킹됩니다. 상품 상세의 문의 탭에 사용됩니다. -### POST /api/modules/sirsoft-ecommerce/products/{productId}/inquiries +### POST /api/modules/sirsoft-ecommerce/products/{product}/inquiries - **라우트명**: `api.modules.sirsoft-ecommerce.products.inquiries.store` - **컨트롤러**: `Modules\Sirsoft\Ecommerce\Http\Controllers\Public\ProductInquiryController@store` @@ -4067,10 +2757,10 @@ HTTP/1.1 200 | 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | | --- | --- | --- | --- | --- | --- | -| productId | path | string | 예 | — | 대상 product의 식별자 | -| title | body | string | 아니오 | — | 제목 | +| product | path | string | 예 | — | 대상 product의 식별자 | +| title | body | string | 아니오 | min 2, max 200 | 제목 | | category | body | string | 아니오 | — | 문의 분류 (게시판 설정에 정의된 카테고리, 미지정 시 기본값) | -| content | body | string | 예 | — | 본문 내용 | +| content | body | string | 예 | min 10, max 10000 | 본문 내용 | | is_secret | body | boolean | 아니오 | — | secret 여부 | | temp_key | body | string | 아니오 | — | 첨부파일 임시 업로드 키 (사전 업로드한 첨부를 이 문의에 연결) | @@ -4079,7 +2769,7 @@ HTTP/1.1 200 **요청 예시** ```http -POST /api/modules/sirsoft-ecommerce/products/1/inquiries HTTP/1.1 +POST /api/modules/sirsoft-ecommerce/products/1732/inquiries HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} (optional.sanctum: 비회원은 헤더 생략 가능) @@ -4096,7 +2786,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -4115,7 +2805,7 @@ Content-Type: application/json **설명** 상품에 1:1 문의를 작성합니다. `optional.sanctum` + `sirsoft-ecommerce.user-products.read` 권한이 적용되며(선택적 인증 표면이지만 실제 작성은 인증 사용자를 전제), `ProductInquiryService::createInquiry()`가 게시판 모듈과 연동해 문의 글을 생성하고 성공 시 201과 생성된 `id`를 반환합니다. `content`는 필수, `title`/`category`/`is_secret`은 선택이며, 첨부는 사전 업로드한 `temp_key`로 연결됩니다. 도메인 규칙 위반(비밀글 비허용 등)은 `RuntimeException`으로 422를 반환합니다. -### GET /api/modules/sirsoft-ecommerce/products/{productId}/reviews +### GET /api/modules/sirsoft-ecommerce/products/{product}/reviews - **라우트명**: `api.modules.sirsoft-ecommerce.products.reviews.index` - **컨트롤러**: `Modules\Sirsoft\Ecommerce\Http\Controllers\Public\ProductReviewController@index` @@ -4125,7 +2815,7 @@ Content-Type: application/json | 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | | --- | --- | --- | --- | --- | --- | -| productId | path | string | 예 | — | 대상 product의 식별자 | +| product | path | string | 예 | — | 대상 product의 식별자 | | sort | query | string | 아니오 | `created_at_desc`, `created_at_asc`, `rating_desc`, `rating_asc` | 정렬 기준 (필드명, `-` 접두 시 내림차순) | | photo_only | query | string | 아니오 | `0`, `1`, `true`, `false` | 포토리뷰만 필터 (true 시 사진이 첨부된 리뷰만 조회) | | page | query | integer | 아니오 | min 1 | 조회할 페이지 번호 (1부터 시작) | @@ -4138,7 +2828,7 @@ Content-Type: application/json **요청 예시** ```http -GET /api/modules/sirsoft-ecommerce/products/1/reviews?sort=created_at_desc&photo_only=0&page=1&per_page=1&rating=1&option_filters=%EC%98%88%EC%8B%9C%EA%B0%92 HTTP/1.1 +GET /api/modules/sirsoft-ecommerce/products/1732/reviews?sort=created_at_desc&photo_only=0&page=1&per_page=1&rating=1&option_filters=%EC%98%88%EC%8B%9C%EA%B0%92 HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} (optional.sanctum: 비회원은 헤더 생략 가능) @@ -4150,10 +2840,10 @@ _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| reviews | object | `{"data":[{"id":1,"product_id":1,"order_option_id":1,"user…` | 리뷰 목록 (data 배열 + 페이지네이션 메타, 공개 상태 리뷰만 정렬/필터 적용해 반환) | -| rating_stats | object | `{"5":{"count":1,"percent":100},"4":{"count":0,"percent":0…` | 별점 분포 통계 (별점(1~5)별 count·percent 맵 + avg 평균 별점, 공개 리뷰 기준) | -| option_filters | array | `[]` | 옵션 조건 필터 후보 (구매 옵션 키·값별 리뷰 건수 집계, 옵션별 리뷰 필터 UI용) | -| total_count | integer | `1` | total 개수 (집계) | +| reviews | object | `{"data":[],"links":{"first":"https:\/\/g7_2.dev\/api\/mod…` | 리뷰 목록 (data 배열 + 페이지네이션 메타, 공개 상태 리뷰만 정렬/필터 적용해 반환) | +| rating_stats | object | `{"5":{"count":0,"percent":0},"4":{"count":0,"percent":0},…` | 별점 분포 통계 (별점(1~5)별 count·percent 맵 + avg 평균 별점, 공개 리뷰 기준) | +| option_filters | array | `[{"key":"색상","values":[{"value":"화이트","count":0},{"value"…` | 옵션 조건 필터 후보 (구매 옵션 키·값별 리뷰 건수 집계, 옵션별 리뷰 필터 UI용) | +| total_count | integer | `0` | total 개수 (집계) | **응답 예시** @@ -4167,51 +2857,16 @@ HTTP/1.1 200 "message": "리뷰 목록을 조회했습니다.", "data": { "reviews": { - "data": [ - { - "id": 1, - "product_id": 1, - "order_option_id": 1, - "user_id": "a234c2b1-cde8-437f-b28b-23323be2b98d", - "user": { - "uuid": "a234c2b1-cde8-437f-b28b-23323be2b98d", - "name": "API 문서 샘플 사용자", - "email": "apidoc-sample-user@example.com" - }, - "option_snapshot": null, - "option_snapshot_label": "", - "rating": 5, - "content": "Alias quas iusto dolorem eum eveniet ad omnis. Id neque consequatur fuga ut. Enim cum mollitia nisi. Adipisci sunt tenetur et tempora tempora eius rerum.", - "content_mode": "text", - "status": "visible", - "status_label": "전시중", - "status_badge_color": "blue", - "images": [], - "image_count": 0, - "has_reply": false, - "has_reply_label": "미답변", - "has_reply_badge_color": "gray", - "reply_content": null, - "reply_content_mode": "text", - "replied_at": null, - "reply_updated_at": null, - "created_at": "2026-07-08 10:44:49", - "updated_at": "2026-07-08 10:44:49", - "abilities": { - "can_update": true, - "can_delete": true - } - } - ], + "data": [], "links": { - "first": "https://api.example.com/api/modules/sirsoft-ecommerce/products/1/reviews?page=1", - "last": "https://api.example.com/api/modules/sirsoft-ecommerce/products/1/reviews?page=1", + "first": "https://api.example.com/api/modules/sirsoft-ecommerce/products/1732/reviews?page=1", + "last": "https://api.example.com/api/modules/sirsoft-ecommerce/products/1732/reviews?page=1", "prev": null, "next": null }, "meta": { "current_page": 1, - "from": 1, + "from": null, "last_page": 1, "links": [ { @@ -4221,7 +2876,7 @@ HTTP/1.1 200 "active": false }, { - "url": "https://api.example.com/api/modules/sirsoft-ecommerce/products/1/reviews?page=1", + "url": "https://api.example.com/api/modules/sirsoft-ecommerce/products/1732/reviews?page=1", "label": "1", "page": 1, "active": true @@ -4233,16 +2888,16 @@ HTTP/1.1 200 "active": false } ], - "path": "https://api.example.com/api/modules/sirsoft-ecommerce/products/1/reviews", + "path": "https://api.example.com/api/modules/sirsoft-ecommerce/products/1732/reviews", "per_page": 25, - "to": 1, - "total": 1 + "to": null, + "total": 0 } }, "rating_stats": { "5": { - "count": 1, - "percent": 100 + "count": 0, + "percent": 0 }, "4": { "count": 0, @@ -4260,10 +2915,28 @@ HTTP/1.1 200 "count": 0, "percent": 0 }, - "avg": 5 + "avg": 0 }, - "option_filters": [], - "total_count": 1 + "option_filters": [ + { + "key": "색상", + "values": [ + { + "value": "화이트", + "count": 0 + }, + { + "value": "블루", + "count": 0 + }, + { + "value": "핑크", + "count": 0 + } + ] + } + ], + "total_count": 0 } } ``` diff --git a/modules/_bundled/sirsoft-ecommerce/docs/api/promotion-coupons.md b/modules/_bundled/sirsoft-ecommerce/docs/api/promotion-coupons.md index 15f07a96..95da22c8 100644 --- a/modules/_bundled/sirsoft-ecommerce/docs/api/promotion-coupons.md +++ b/modules/_bundled/sirsoft-ecommerce/docs/api/promotion-coupons.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Promotion Coupons 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` @@ -64,54 +64,54 @@ _목록 응답: `data.data[]` 배열 항목의 필드 + `data.pagination`._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| id | integer | `157` | 기본 키 (내부 식별자) | -| name | object | `{"ko":"API 문서 샘플 쿠폰","en":"API Doc Sample Coupon"}` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | -| localized_name | string | `API 문서 샘플 쿠폰` | `name` 의 현재 로케일 해석 값 (다국어 필드를 표시용 문자열로 해석) | -| description | object | `{"ko":null,"en":null}` | 설명 (다국어 필드는 로케일별 값 객체) | +| id | integer | `110` | 기본 키 (내부 식별자) | +| name | object | `{"ko":"설날 무료배송 (예정)","en":"Lunar New Year Free Shipping (…` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | +| localized_name | string | `설날 무료배송 (예정)` | `name` 의 현재 로케일 해석 값 (다국어 필드를 표시용 문자열로 해석) | +| description | object | `{"ko":"설날 특별 무료배송 예정","en":"Upcoming Lunar New Year free …` | 설명 (다국어 필드는 로케일별 값 객체) | | localized_description | string | `설날 특별 무료배송 예정` | `description` 의 현재 로케일 해석 값 (다국어 필드를 표시용 문자열로 해석) | -| target_type | string | `order_amount` | 적용대상(할인 기준): product_amount(상품금액), order_amount(주문금액), shipping_fee(배송비) | -| target_type_label | string | `주문금액` | `target_type` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | -| target_type_badge_color | string | `blue` | `target_type` 배지 색상 (상품금액=teal, 주문금액=blue, 배송비=orange) | +| target_type | string | `shipping_fee` | 적용대상(할인 기준): product_amount(상품금액), order_amount(주문금액), shipping_fee(배송비) | +| target_type_label | string | `배송비` | `target_type` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | +| target_type_badge_color | string | `orange` | `target_type` 배지 색상 (상품금액=teal, 주문금액=blue, 배송비=orange) | | discount_type | string | `fixed` | 혜택유형: fixed(정액 금액 할인), rate(정률 % 할인) | | discount_type_label | string | `정액할인` | `discount_type` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | -| discount_value | integer | `1000` | 혜택값 (정액이면 할인 금액, 정률이면 할인율 %). 정액은 기본 통화 자릿수로 정규화 | +| discount_value | integer | `5000` | 혜택값 (정액이면 할인 금액, 정률이면 할인율 %). 정액은 기본 통화 자릿수로 정규화 | | discount_max_amount | integer | `2000` | 최대 할인액 (정률 할인 시 상한 금액, 미설정 시 null) | -| min_order_amount | integer | `0` | 쿠폰 적용 최소 주문금액 (0=제한 없음) | -| benefit_formatted | string | `1,000원 할인` | `benefit` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | -| multi_currency_discount_value | object | `{"KRW":{"price":1000,"formatted":"1,000원","is_default":tr…` | 정액 할인 금액의 통화별 환산 맵 (정률은 통화 무관이라 null) | -| multi_currency_min_order_amount | object | `{"KRW":{"price":10000,"formatted":"10,000원","is_default":…` | 최소 주문금액의 통화별 환산 맵 (0이면 null) | +| min_order_amount | integer | `20000` | 쿠폰 적용 최소 주문금액 (0=제한 없음) | +| benefit_formatted | string | `¥5,000 할인` | `benefit` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | +| multi_currency_discount_value | object | `{"KRW":{"price":5000,"formatted":"5,000원","is_default":tr…` | 정액 할인 금액의 통화별 환산 맵 (정률은 통화 무관이라 null) | +| multi_currency_min_order_amount | object | `{"KRW":{"price":20000,"formatted":"20,000원","is_default":…` | 최소 주문금액의 통화별 환산 맵 (0이면 null) | | multi_currency_discount_max_amount | object | `{"KRW":{"price":2000,"formatted":"2,000원","is_default":tr…` | 최대 할인액의 통화별 환산 맵 (미설정 시 null) | -| issue_method | string | `download` | 발급방법: direct(직접발급), download(다운로드), auto(자동발급) | -| issue_method_label | string | `다운로드` | `issue_method` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | -| issue_method_badge_color | string | `teal` | `issue_method` 배지 색상 (직접발급=gray, 다운로드=teal, 자동발급=blue) | +| issue_method | string | `direct` | 발급방법: direct(직접발급), download(다운로드), auto(자동발급) | +| issue_method_label | string | `직접발급` | `issue_method` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | +| issue_method_badge_color | string | `gray` | `issue_method` 배지 색상 (직접발급=gray, 다운로드=teal, 자동발급=blue) | | issue_condition | string | `manual` | 발급조건: manual(수동), signup(회원가입), first_purchase(첫구매), birthday(생일) | | issue_condition_label | string | `수동발급` | `issue_condition` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | | issue_condition_badge_color | string | `orange` | `issue_condition` 배지 색상 (수동=orange, 회원가입=blue, 첫구매=teal, 생일=pink) | | issue_status | string | `issuing` | 발급상태: issuing(발급중), stopped(발급중단) | | issue_status_label | string | `발급중` | `issue_status` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | | issue_status_badge_color | string | `blue` | `issue_status` 배지 색상 (발급중=blue, 발급중단=orange) | -| total_quantity | integer | `1` | 총 발급 수량 (null=무제한) | -| issued_count | integer | `0` | issued 개수 (집계) | +| total_quantity | integer | `3000` | 총 발급 수량 (null=무제한) | +| issued_count | integer | `59` | issued 개수 (집계) | | per_user_limit | integer | `1` | 회원 1인당 발급 제한 수량 (0=무제한) | -| issue_count_formatted | string | `0/무제한` | `issue_count` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | +| issue_count_formatted | string | `59/3000` | `issue_count` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | | valid_type | string | `period` | 유효기간 유형: period(기간 지정), days_from_issue(발급일로부터 N일) | -| valid_days | integer | `1` | 발급일로부터 유효 일수 (valid_type=days_from_issue 일 때) | -| valid_from | string | `2026-06-30` | 유효기간 시작일 (사이트 타임존 기준 날짜 문자열) | -| valid_to | string | `2026-07-30` | 유효기간 종료일 (사이트 타임존 기준 날짜 문자열) | -| valid_period_formatted | string | `-` | `valid_period` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | -| issue_from | string | `2026-06-30T11:24` | 발급기간 시작 일시 (datetime-local 입력 호환 문자열) | -| issue_to | string | `2026-07-15T11:24` | 발급기간 종료 일시 (datetime-local 입력 호환 문자열) | -| issue_period_formatted | string | `상시발급` | `issue_period` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | -| is_combinable | boolean | `false` | combinable 여부 | +| valid_days | integer | `7` | 발급일로부터 유효 일수 (valid_type=days_from_issue 일 때) | +| valid_from | string | `2026-08-14` | 유효기간 시작일 (사이트 타임존 기준 날짜 문자열) | +| valid_to | string | `2026-09-13` | 유효기간 종료일 (사이트 타임존 기준 날짜 문자열) | +| valid_period_formatted | string | `2026-08-14 ~ 2026-09-13` | `valid_period` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | +| issue_from | string | `2026-08-14T23:36` | 발급기간 시작 일시 (datetime-local 입력 호환 문자열) | +| issue_to | string | `2026-08-29T23:36` | 발급기간 종료 일시 (datetime-local 입력 호환 문자열) | +| issue_period_formatted | string | `2026-08-14 ~ 2026-08-29` | `issue_period` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | +| is_combinable | boolean | `true` | combinable 여부 | | target_scope | string | `all` | 적용 범위: all(전체 상품), products(특정 상품), categories(특정 카테고리) | | target_scope_label | string | `전체상품` | `target_scope` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | -| is_issuable | boolean | `true` | issuable 여부 | -| created_at | string | `2026-07-07 14:47:31` | 생성 일시 | -| updated_at | string | `2026-07-07 14:47:31` | 최종 수정 일시 | -| created_by | string | `a1e0a91a-fba6-491c-a53e-7285a5686857` | 등록자(생성한 관리자) UUID (creator 관계 로드 시) | +| is_issuable | boolean | `false` | issuable 여부 | +| created_at | string | `2026-07-30 23:36:17` | 생성 일시 | +| updated_at | string | `2026-07-31 07:42:40` | 최종 수정 일시 | +| created_by | null | `null` | 등록자(생성한 관리자) UUID (creator 관계 로드 시) | | created_by_name | string | `-` | 등록자 이름 (creator 미로드/미설정 시 `-`) | -| created_by_email | string | `heuristing@gmail.com` | 등록자 이메일 (creator 관계 파생) | -| creator | object | `{"uuid":"a1e0a91a-fba6-491c-a53e-7285a5686857","name":"관리자"}` | 생성자 정보 객체 (uuid/name/email — creator 관계 파생) | +| created_by_email | null | `null` | 등록자 이메일 (creator 관계 파생) | +| creator | null | `null` | 생성자 정보 객체 (uuid/name/email — creator 관계 파생) | | issues_count | integer | `0` | issues 개수 (집계) | | abilities | object | `{"can_create":true,"can_update":true,"can_delete":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | @@ -128,99 +128,34 @@ HTTP/1.1 200 "data": { "data": [ { - "id": 1, + "id": 110, "name": { - "ko": "API 문서 샘플 쿠폰", - "en": "API Doc Sample Coupon" + "ko": "설날 무료배송 (예정)", + "en": "Lunar New Year Free Shipping (Upcoming)" }, - "localized_name": "API 문서 샘플 쿠폰", - "description": null, - "localized_description": null, - "target_type": "order_amount", - "target_type_label": "주문금액", - "target_type_badge_color": "blue", - "discount_type": "fixed", - "discount_type_label": "정액할인", - "discount_value": 1000, - "discount_max_amount": null, - "min_order_amount": 0, - "benefit_formatted": "1,000원 할인", - "multi_currency_discount_value": { - "KRW": { - "price": 1000, - "formatted": "1,000원", - "is_default": true, - "editable": true - }, - "USD": { - "price": 0.85, - "formatted": "$0.85", - "is_default": false, - "editable": false, - "exchange_rate": 0.85 - }, - "JPY": { - "price": 115, - "formatted": "¥115", - "is_default": false, - "editable": false, - "exchange_rate": 115 - }, - "CNY": { - "price": 5.8, - "formatted": "元5.80", - "is_default": false, - "editable": false, - "exchange_rate": 5.8 - }, - "EUR": { - "price": 0.78, - "formatted": "€0.78", - "is_default": false, - "editable": false, - "exchange_rate": 0.78 - } + "localized_name": "설날 무료배송 (예정)", + "description": { + "ko": "설날 특별 무료배송 예정", + "en": "Upcoming Lunar New Year free shipping" }, - "multi_currency_min_order_amount": null, - "multi_currency_discount_max_amount": null, - "issue_method": "download", - "issue_method_label": "다운로드", - "issue_method_badge_color": "teal", - "issue_condition": "manual", - "issue_condition_label": "수동발급", - "issue_condition_badge_color": "orange", - "issue_status": "issuing", - "issue_status_label": "발급중", - "issue_status_badge_color": "blue", - "total_quantity": null, - "issued_count": 0, - "per_user_limit": 1, - "issue_count_formatted": "0/무제한", - "valid_type": "period", - "valid_days": null, - "valid_from": null, - "valid_to": null, - "valid_period_formatted": "-", - "issue_from": null, - "issue_to": null, - "issue_period_formatted": "상시발급", - "is_combinable": false, - "target_scope": "all", - "target_scope_label": "전체상품", - "is_issuable": true, - "created_at": "2026-07-08 10:44:49", - "updated_at": "2026-07-08 10:44:49", - "created_by": null, - "created_by_name": "-", - "created_by_email": null, - "creator": null, - "issues_count": 0, - "abilities": { - "can_create": true, - "can_update": true, - "can_delete": true - } - } + "localized_description": "설날 특별 무료배송 예정", + "...": "(45개 키 생략, 총 50개)" + }, + { + "id": 109, + "name": { + "ko": "배송비 30% 할인 (종료)", + "en": "Shipping 30% Off (Ended)" + }, + "localized_name": "배송비 30% 할인 (종료)", + "description": { + "ko": "배송비 30% 할인 이벤트 종료", + "en": "Shipping 30% off event ended" + }, + "localized_description": "배송비 30% 할인 이벤트 종료", + "...": "(45개 키 생략, 총 50개)" + }, + "... (총 25건 중 2건 표시)" ], "abilities": { "can_create": true, @@ -229,12 +164,11 @@ HTTP/1.1 200 }, "pagination": { "current_page": 1, - "last_page": 1, + "last_page": 2, "per_page": 25, - "total": 1, + "total": 50, "from": 1, - "to": 1, - "has_more_pages": false + "...": "(2개 키 생략, 총 7개)" } } } @@ -333,7 +267,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -384,7 +318,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -418,7 +352,7 @@ Content-Type: application/json **요청 예시** ```http -DELETE /api/modules/sirsoft-ecommerce/admin/promotion-coupons/1 HTTP/1.1 +DELETE /api/modules/sirsoft-ecommerce/admin/promotion-coupons/{id} HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -426,27 +360,11 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) -_단건 응답: `data` 객체의 필드._ - -| 필드 | 타입 | 실측 예시값 | 용도/설명 | -| --- | --- | --- | --- | -| coupon_id | integer | `1` | coupon 식별자 (연관 리소스 참조) | + **응답 예시** -```http -HTTP/1.1 200 -``` - -```json -{ - "success": true, - "message": "쿠폰이 삭제되었습니다.", - "data": { - "coupon_id": 1 - } -} -``` + **에러 응답** @@ -476,7 +394,7 @@ HTTP/1.1 200 **요청 예시** ```http -GET /api/modules/sirsoft-ecommerce/admin/promotion-coupons/1 HTTP/1.1 +GET /api/modules/sirsoft-ecommerce/admin/promotion-coupons/{id} HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -484,175 +402,11 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) -_단건 응답: `data` 객체의 필드._ - -| 필드 | 타입 | 실측 예시값 | 용도/설명 | -| --- | --- | --- | --- | -| id | integer | `1` | 기본 키 (내부 식별자) | -| name | object | `{"ko":"API 문서 샘플 쿠폰","en":"API Doc Sample Coupon"}` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | -| localized_name | string | `API 문서 샘플 쿠폰` | `name` 의 현재 로케일 해석 값 (다국어 필드를 표시용 문자열로 해석) | -| description | null | `null` | 설명 (다국어 필드는 로케일별 값 객체) | -| localized_description | null | `null` | `description` 의 현재 로케일 해석 값 (다국어 필드를 표시용 문자열로 해석) | -| target_type | string | `order_amount` | 적용대상: product_amount(상품금액), order_amount(주문금액), shipping_fee(배송비) | -| target_type_label | string | `주문금액` | `target_type` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | -| target_type_badge_color | string | `blue` | `target_type` 배지 색상 (상품금액=teal, 주문금액=blue, 배송비=orange) | -| discount_type | string | `fixed` | 혜택유형: fixed(정액), rate(정률) | -| discount_type_label | string | `정액할인` | `discount_type` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | -| discount_value | integer | `1000` | 혜택값 (정액: 금액, 정률: %) | -| discount_max_amount | null | `null` | 최대 할인액 (정률 시) | -| min_order_amount | integer | `0` | 최소 주문금액 | -| benefit_formatted | string | `1,000원 할인` | `benefit` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | -| multi_currency_discount_value | object | `{"KRW":{"price":1000,"formatted":"1,000원","is_default":tr…` | 정액 할인 금액의 통화별 환산 맵 (정률은 통화 무관이라 null) | -| multi_currency_min_order_amount | null | `null` | 최소 주문금액의 통화별 환산 맵 (0이면 null) | -| multi_currency_discount_max_amount | null | `null` | 최대 할인액의 통화별 환산 맵 (미설정 시 null) | -| issue_method | string | `download` | 발급방법: direct(직접발급), download(다운로드), auto(자동발급) | -| issue_method_label | string | `다운로드` | `issue_method` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | -| issue_method_badge_color | string | `teal` | `issue_method` 배지 색상 (직접발급=gray, 다운로드=teal, 자동발급=blue) | -| issue_condition | string | `manual` | 발급조건: manual(수동), signup(회원가입), first_purchase(첫구매), birthday(생일) | -| issue_condition_label | string | `수동발급` | `issue_condition` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | -| issue_condition_badge_color | string | `orange` | `issue_condition` 배지 색상 (수동=orange, 회원가입=blue, 첫구매=teal, 생일=pink) | -| issue_status | string | `issuing` | 발급상태: issuing(발급중), stopped(발급중단) | -| issue_status_label | string | `발급중` | `issue_status` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | -| issue_status_badge_color | string | `blue` | `issue_status` 배지 색상 (발급중=blue, 발급중단=orange) | -| total_quantity | null | `null` | 총 발급 수량 (NULL=무제한) | -| issued_count | integer | `0` | issued 개수 (집계) | -| per_user_limit | integer | `1` | 회원당 발급 제한 | -| issue_count_formatted | string | `0/무제한` | `issue_count` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | -| valid_type | string | `period` | 유효기간 유형: period(기간지정), days_from_issue(발급일로부터) | -| valid_days | null | `null` | 발급일로부터 N일 (valid_type=days_from_issue) | -| valid_from | null | `null` | 유효기간 시작 | -| valid_to | null | `null` | 유효기간 종료 | -| valid_period_formatted | string | `-` | `valid_period` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | -| issue_from | null | `null` | 발급기간 시작 | -| issue_to | null | `null` | 발급기간 종료 | -| issue_period_formatted | string | `상시발급` | `issue_period` 값의 표시용 포맷 문자열 (통화/용량/일시 등 로케일·단위 포맷) | -| is_combinable | boolean | `false` | combinable 여부 | -| target_scope | string | `all` | 적용 범위: all(전체), products(특정상품), categories(특정카테고리) | -| target_scope_label | string | `전체상품` | `target_scope` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | -| is_issuable | boolean | `true` | issuable 여부 | -| created_at | string | `2026-07-08 10:44:49` | 생성 일시 | -| updated_at | string | `2026-07-08 10:44:49` | 최종 수정 일시 | -| created_by | null | `null` | 쿠폰 등록자(관리자) 식별자 (users 참조, 삭제 시 null) | -| created_by_name | string | `-` | 등록자 이름 (creator 미로드/미설정 시 `-`) | -| created_by_email | null | `null` | 등록자 이메일 (creator 관계 파생) | -| creator | null | `null` | 생성자 정보 객체 (uuid/name/email — creator 관계 파생) | -| included_products | array | `[]` | 적용 대상 포함 상품 목록 (target_scope=products 시 이 상품에만 적용) | -| excluded_products | array | `[]` | 적용 제외 상품 목록 (해당 상품은 쿠폰 적용에서 제외) | -| included_categories | array | `[]` | 적용 대상 포함 카테고리 목록 (target_scope=categories 시 이 카테고리에만 적용) | -| excluded_categories | array | `[]` | 적용 제외 카테고리 목록 (해당 카테고리는 쿠폰 적용에서 제외) | -| issues_count | integer | `0` | issues 개수 (집계) | -| abilities | object | `{"can_create":true,"can_update":true,"can_delete":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | + **응답 예시** -```http -HTTP/1.1 200 -``` - -```json -{ - "success": true, - "message": "쿠폰 정보를 조회했습니다.", - "data": { - "id": 1, - "name": { - "ko": "API 문서 샘플 쿠폰", - "en": "API Doc Sample Coupon" - }, - "localized_name": "API 문서 샘플 쿠폰", - "description": null, - "localized_description": null, - "target_type": "order_amount", - "target_type_label": "주문금액", - "target_type_badge_color": "blue", - "discount_type": "fixed", - "discount_type_label": "정액할인", - "discount_value": 1000, - "discount_max_amount": null, - "min_order_amount": 0, - "benefit_formatted": "1,000원 할인", - "multi_currency_discount_value": { - "KRW": { - "price": 1000, - "formatted": "1,000원", - "is_default": true, - "editable": true - }, - "USD": { - "price": 0.85, - "formatted": "$0.85", - "is_default": false, - "editable": false, - "exchange_rate": 0.85 - }, - "JPY": { - "price": 115, - "formatted": "¥115", - "is_default": false, - "editable": false, - "exchange_rate": 115 - }, - "CNY": { - "price": 5.8, - "formatted": "元5.80", - "is_default": false, - "editable": false, - "exchange_rate": 5.8 - }, - "EUR": { - "price": 0.78, - "formatted": "€0.78", - "is_default": false, - "editable": false, - "exchange_rate": 0.78 - } - }, - "multi_currency_min_order_amount": null, - "multi_currency_discount_max_amount": null, - "issue_method": "download", - "issue_method_label": "다운로드", - "issue_method_badge_color": "teal", - "issue_condition": "manual", - "issue_condition_label": "수동발급", - "issue_condition_badge_color": "orange", - "issue_status": "issuing", - "issue_status_label": "발급중", - "issue_status_badge_color": "blue", - "total_quantity": null, - "issued_count": 0, - "per_user_limit": 1, - "issue_count_formatted": "0/무제한", - "valid_type": "period", - "valid_days": null, - "valid_from": null, - "valid_to": null, - "valid_period_formatted": "-", - "issue_from": null, - "issue_to": null, - "issue_period_formatted": "상시발급", - "is_combinable": false, - "target_scope": "all", - "target_scope_label": "전체상품", - "is_issuable": true, - "created_at": "2026-07-08 10:44:49", - "updated_at": "2026-07-08 10:44:49", - "created_by": null, - "created_by_name": "-", - "created_by_email": null, - "creator": null, - "included_products": [], - "excluded_products": [], - "included_categories": [], - "excluded_categories": [], - "issues_count": 0, - "abilities": { - "can_create": true, - "can_update": true, - "can_delete": true - } - } -} -``` + **에러 응답** @@ -701,14 +455,12 @@ HTTP/1.1 200 | products | body | array | 아니오 | — | 적용 상품 목록 (`target_scope=products`), 항목별 `{id, type: include\|exclude}` | | categories | body | array | 아니오 | — | 적용 카테고리 목록 (`target_scope=categories`), 항목별 `{id, type: include\|exclude}` | -> 필수 표기는 "전송했을 때 값이 있어야 함"을 뜻합니다. 이 엔드포인트는 부분 수정을 지원하므로 body 필드는 모두 생략할 수 있습니다 (생략하면 기존 값 유지). 유효기간은 `valid_type` 을 함께 전송할 때만 조건부 필수가 적용됩니다 — `period` 면 `valid_from`/`valid_to`, `days_from_issue` 면 `valid_days` 가 비어 있으면 422 입니다. `valid_type` 을 보내지 않는 부분 수정은 유효기간 필드를 요구하지 않습니다. - > 이 엔드포인트는 확장이 파라미터를 추가할 수 있습니다 (`sirsoft-ecommerce.coupon.update_validation_rules`). **요청 예시** ```http -PUT /api/modules/sirsoft-ecommerce/admin/promotion-coupons/1 HTTP/1.1 +PUT /api/modules/sirsoft-ecommerce/admin/promotion-coupons/{id} HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -750,11 +502,11 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** - + **에러 응답** @@ -786,7 +538,7 @@ Content-Type: application/json **요청 예시** ```http -POST /api/modules/sirsoft-ecommerce/admin/promotion-coupons/1/issue-direct HTTP/1.1 +POST /api/modules/sirsoft-ecommerce/admin/promotion-coupons/{id}/issue-direct HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -801,11 +553,11 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** - + **에러 응답** @@ -841,7 +593,7 @@ Content-Type: application/json **요청 예시** ```http -GET /api/modules/sirsoft-ecommerce/admin/promotion-coupons/1/issues?user_id=9f8b2c1a-4d3e-4a2b-8c1d-0e1f2a3b4c5d&status=available&per_page=1 HTTP/1.1 +GET /api/modules/sirsoft-ecommerce/admin/promotion-coupons/{id}/issues?user_id=9f8b2c1a-4d3e-4a2b-8c1d-0e1f2a3b4c5d&status=available&per_page=1 HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -849,34 +601,11 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) -_목록 응답: `data.data[]` 배열 항목의 필드 + `data.pagination`._ - - + **응답 예시** -```http -HTTP/1.1 200 -``` - -```json -{ - "success": true, - "message": "쿠폰 발급 내역을 조회했습니다.", - "data": { - "data": [], - "pagination": { - "current_page": 1, - "last_page": 1, - "per_page": 25, - "total": 0, - "from": null, - "to": null, - "has_more_pages": false - } - } -} -``` + **에러 응답** @@ -908,7 +637,7 @@ HTTP/1.1 200 **요청 예시** ```http -DELETE /api/modules/sirsoft-ecommerce/admin/promotion-coupons/1/issues/{issueId} HTTP/1.1 +DELETE /api/modules/sirsoft-ecommerce/admin/promotion-coupons/{id}/issues/{issueId} HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -916,7 +645,7 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - + **응답 예시** diff --git a/modules/_bundled/sirsoft-ecommerce/docs/api/review-image.md b/modules/_bundled/sirsoft-ecommerce/docs/api/review-image.md index 2d66acb9..b185aa33 100644 --- a/modules/_bundled/sirsoft-ecommerce/docs/api/review-image.md +++ b/modules/_bundled/sirsoft-ecommerce/docs/api/review-image.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Review Image 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` diff --git a/modules/_bundled/sirsoft-ecommerce/docs/api/reviews.md b/modules/_bundled/sirsoft-ecommerce/docs/api/reviews.md index 9b9f8036..04776b87 100644 --- a/modules/_bundled/sirsoft-ecommerce/docs/api/reviews.md +++ b/modules/_bundled/sirsoft-ecommerce/docs/api/reviews.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Reviews 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` @@ -100,56 +100,7 @@ HTTP/1.1 200 "success": true, "message": "리뷰 목록을 조회했습니다.", "data": { - "data": [ - { - "id": 1, - "product_id": 1, - "order_option_id": 1, - "user_id": "a234c2b1-cde8-437f-b28b-23323be2b98d", - "user": { - "uuid": "a234c2b1-cde8-437f-b28b-23323be2b98d", - "name": "API 문서 샘플 사용자", - "email": "apidoc-sample-user@example.com" - }, - "product": { - "id": 1, - "name": "API 문서 샘플 상품", - "thumbnail_url": null - }, - "option_snapshot": null, - "option_snapshot_label": "", - "rating": 5, - "content": "Alias quas iusto dolorem eum eveniet ad omnis. Id neque consequatur fuga ut. Enim cum mollitia nisi. Adipisci sunt tenetur et tempora tempora eius rerum.", - "content_mode": "text", - "status": "visible", - "status_label": "전시중", - "status_badge_color": "blue", - "images": [], - "image_count": 0, - "orderOption": { - "id": 1, - "order_id": 5, - "order_number": "ORD-20260708-000002", - "quantity": 2, - "created_at": "2026-07-08 10:44:49" - }, - "has_reply": false, - "has_reply_label": "미답변", - "has_reply_badge_color": "gray", - "reply_content": null, - "reply_content_mode": "text", - "reply_admin_uuid": null, - "reply_admin": null, - "replied_at": null, - "reply_updated_at": null, - "created_at": "2026-07-08 10:44:49", - "updated_at": "2026-07-08 10:44:49", - "abilities": { - "can_update": true, - "can_delete": true - } - } - ], + "data": [], "links": { "first": "https://api.example.com/api/modules/sirsoft-ecommerce/admin/reviews?page=1", "last": "https://api.example.com/api/modules/sirsoft-ecommerce/admin/reviews?page=1", @@ -158,7 +109,7 @@ HTTP/1.1 200 }, "meta": { "current_page": 1, - "from": 1, + "from": null, "last_page": 1, "links": [ { @@ -182,8 +133,8 @@ HTTP/1.1 200 ], "path": "https://api.example.com/api/modules/sirsoft-ecommerce/admin/reviews", "per_page": 25, - "to": 1, - "total": 1 + "to": null, + "total": 0 } } } @@ -238,7 +189,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -272,7 +223,7 @@ Content-Type: application/json **요청 예시** ```http -DELETE /api/modules/sirsoft-ecommerce/admin/reviews/1 HTTP/1.1 +DELETE /api/modules/sirsoft-ecommerce/admin/reviews/{review} HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -280,27 +231,11 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) -_단건 응답: `data` 객체의 필드._ - -| 필드 | 타입 | 실측 예시값 | 용도/설명 | -| --- | --- | --- | --- | -| deleted | boolean | `true` | 삭제 처리 성공 여부 (true 이면 리뷰와 첨부 이미지가 제거됨) | + **응답 예시** -```http -HTTP/1.1 200 -``` - -```json -{ - "success": true, - "message": "리뷰가 삭제되었습니다.", - "data": { - "deleted": true - } -} -``` + **에러 응답** @@ -330,7 +265,7 @@ HTTP/1.1 200 **요청 예시** ```http -GET /api/modules/sirsoft-ecommerce/admin/reviews/1 HTTP/1.1 +GET /api/modules/sirsoft-ecommerce/admin/reviews/{review} HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -338,100 +273,11 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) -_단건 응답: `data` 객체의 필드._ - -| 필드 | 타입 | 실측 예시값 | 용도/설명 | -| --- | --- | --- | --- | -| id | integer | `99` | 기본 키 (내부 식별자) | -| product_id | integer | `320` | 상품 ID | -| order_option_id | integer | `859` | 주문 옵션 ID | -| user_id | string | `a231747f-e82e-4cf2-9ae1-a261849dce40` | 작성자 ID | -| user | object | `{"uuid":"a231747f-e82e-4cf2-9ae1-a261849dce40","name":"AP…` | 작성자 정보 (uuid·name·email, `user` 관계 로드 시) | -| product | object | `{"id":320,"name":"API 문서 샘플 상품","thumbnail_url":null}` | 리뷰 대상 상품 정보 (id·현지화 상품명·썸네일 URL) | -| option_snapshot | null | `null` | 주문 시점 옵션 스냅샷 (옵션명 보존용) | -| option_snapshot_label | string | `` | `option_snapshot` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | -| rating | integer | `5` | 별점 (1~5) | -| content | string | `Molestiae repellendus accusantium omn…` | 리뷰 내용 | -| content_mode | string | `text` | 콘텐츠 모드: text / html | -| status | string | `visible` | 리뷰 상태: visible / hidden | -| status_label | string | `전시중` | 상태의 사람이 읽는 라벨 (상태 Enum label() 산물) | -| status_badge_color | string | `blue` | 상태 뱃지 색상 (visible=blue / hidden=gray) | -| images | array | `[]` | 첨부 이미지 목록 (이미지 리소스 배열, `images` 관계 로드 시) | -| image_count | integer | `0` | image 개수 (집계) | -| orderOption | object | `{"id":859,"order_id":455,"order_number":"ORD-20260707-000…` | 리뷰가 연결된 주문 옵션 정보 (주문 ID·주문번호·수량·주문일) | -| has_reply | boolean | `false` | reply 여부 | -| has_reply_label | string | `미답변` | `has_reply` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | -| has_reply_badge_color | string | `gray` | 답변 여부 뱃지 색상 (답변완료=green / 미답변=gray) | -| reply_content | null | `null` | 판매자 답변 내용 | -| reply_content_mode | string | `text` | 답변 콘텐츠 모드: text / html | -| reply_admin_uuid | null | `null` | 답변 작성 관리자 UUID (`replyAdmin` 관계 로드 시) | -| reply_admin | null | `null` | 답변 작성 관리자 정보 (uuid·name·email, `replyAdmin` 관계 로드 시) | -| replied_at | null | `null` | replied 일시 | -| reply_updated_at | null | `null` | reply updated 일시 | -| created_at | string | `2026-07-07 14:47:31` | 생성 일시 | -| updated_at | string | `2026-07-07 14:47:31` | 최종 수정 일시 | -| abilities | object | `{"can_update":true,"can_delete":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | + **응답 예시** -```http -HTTP/1.1 200 -``` - -```json -{ - "success": true, - "message": "리뷰 목록을 조회했습니다.", - "data": { - "id": 1, - "product_id": 1, - "order_option_id": 1, - "user_id": "a234c2b1-cde8-437f-b28b-23323be2b98d", - "user": { - "uuid": "a234c2b1-cde8-437f-b28b-23323be2b98d", - "name": "API 문서 샘플 사용자", - "email": "apidoc-sample-user@example.com" - }, - "product": { - "id": 1, - "name": "API 문서 샘플 상품", - "thumbnail_url": null - }, - "option_snapshot": null, - "option_snapshot_label": "", - "rating": 5, - "content": "Alias quas iusto dolorem eum eveniet ad omnis. Id neque consequatur fuga ut. Enim cum mollitia nisi. Adipisci sunt tenetur et tempora tempora eius rerum.", - "content_mode": "text", - "status": "visible", - "status_label": "전시중", - "status_badge_color": "blue", - "images": [], - "image_count": 0, - "orderOption": { - "id": 1, - "order_id": 5, - "order_number": "ORD-20260708-000002", - "quantity": 2, - "created_at": "2026-07-08 10:44:49" - }, - "has_reply": false, - "has_reply_label": "미답변", - "has_reply_badge_color": "gray", - "reply_content": null, - "reply_content_mode": "text", - "reply_admin_uuid": null, - "reply_admin": null, - "replied_at": null, - "reply_updated_at": null, - "created_at": "2026-07-08 10:44:49", - "updated_at": "2026-07-08 10:44:49", - "abilities": { - "can_update": true, - "can_delete": true - } - } -} -``` + **에러 응답** @@ -461,7 +307,7 @@ HTTP/1.1 200 **요청 예시** ```http -DELETE /api/modules/sirsoft-ecommerce/admin/reviews/1/reply HTTP/1.1 +DELETE /api/modules/sirsoft-ecommerce/admin/reviews/{review}/reply HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -469,78 +315,11 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) -_단건 응답: `data` 객체의 필드._ - -| 필드 | 타입 | 실측 예시값 | 용도/설명 | -| --- | --- | --- | --- | -| id | integer | `1` | 기본 키 (내부 식별자) | -| product_id | integer | `1` | product 식별자 (연관 리소스 참조) | -| order_option_id | integer | `1` | order option 식별자 (연관 리소스 참조) | -| user_id | string | `a234c2b1-cde8-437f-b28b-23323be2b98d` | user 식별자 (연관 리소스 참조) | -| user | object | `{"uuid":"a234c2b1-cde8-437f-b28b-23323be2b98d","name":"AP…` | 대상 사용자 정보 객체 (uuid/name/email 등 — user 관계 파생) | -| option_snapshot | null | `null` | 주문 시점 옵션 스냅샷 (옵션명 보존용) | -| option_snapshot_label | string | `` | `option_snapshot` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | -| rating | integer | `5` | 별점 (1~5) | -| content | string | `Alias quas iusto dolorem eum eveniet …` | 본문 내용 | -| content_mode | string | `text` | 콘텐츠 모드: text / html | -| status | string | `visible` | 상태 값 (도메인별 상태 집합 — 사람이 읽는 라벨은 status_label, UI 변형은 status_variant 참조) | -| status_label | string | `전시중` | 상태의 사람이 읽는 라벨 (상태 Enum label() 산물) | -| status_badge_color | string | `blue` | 상태 뱃지 색상 (visible=blue / hidden=gray) | -| has_reply | boolean | `false` | reply 여부 | -| has_reply_label | string | `미답변` | `has_reply` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | -| has_reply_badge_color | string | `gray` | 답변 여부 뱃지 색상 (답변완료=green / 미답변=gray) | -| reply_content | null | `null` | 판매자 답변 내용 | -| reply_content_mode | string | `text` | 답변 콘텐츠 모드: text / html | -| replied_at | null | `null` | replied 일시 | -| reply_updated_at | null | `null` | reply updated 일시 | -| created_at | string | `2026-07-08 10:44:49` | 생성 일시 | -| updated_at | string | `2026-07-08 10:44:49` | 최종 수정 일시 | -| abilities | object | `{"can_update":true,"can_delete":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | + **응답 예시** -```http -HTTP/1.1 200 -``` - -```json -{ - "success": true, - "message": "답변이 삭제되었습니다.", - "data": { - "id": 1, - "product_id": 1, - "order_option_id": 1, - "user_id": "a234c2b1-cde8-437f-b28b-23323be2b98d", - "user": { - "uuid": "a234c2b1-cde8-437f-b28b-23323be2b98d", - "name": "API 문서 샘플 사용자", - "email": "apidoc-sample-user@example.com" - }, - "option_snapshot": null, - "option_snapshot_label": "", - "rating": 5, - "content": "Alias quas iusto dolorem eum eveniet ad omnis. Id neque consequatur fuga ut. Enim cum mollitia nisi. Adipisci sunt tenetur et tempora tempora eius rerum.", - "content_mode": "text", - "status": "visible", - "status_label": "전시중", - "status_badge_color": "blue", - "has_reply": false, - "has_reply_label": "미답변", - "has_reply_badge_color": "gray", - "reply_content": null, - "reply_content_mode": "text", - "replied_at": null, - "reply_updated_at": null, - "created_at": "2026-07-08 10:44:49", - "updated_at": "2026-07-08 10:44:49", - "abilities": { - "can_update": true, - "can_delete": true - } - } -} -``` + **에러 응답** @@ -574,7 +353,7 @@ HTTP/1.1 200 **요청 예시** ```http -POST /api/modules/sirsoft-ecommerce/admin/reviews/1/reply HTTP/1.1 +POST /api/modules/sirsoft-ecommerce/admin/reviews/{review}/reply HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -588,78 +367,11 @@ Content-Type: application/json **응답 필드** (`data` 내부) -_단건 응답: `data` 객체의 필드._ - -| 필드 | 타입 | 실측 예시값 | 용도/설명 | -| --- | --- | --- | --- | -| id | integer | `1` | 기본 키 (내부 식별자) | -| product_id | integer | `1` | product 식별자 (연관 리소스 참조) | -| order_option_id | integer | `1` | order option 식별자 (연관 리소스 참조) | -| user_id | string | `a234c2b1-cde8-437f-b28b-23323be2b98d` | user 식별자 (연관 리소스 참조) | -| user | object | `{"uuid":"a234c2b1-cde8-437f-b28b-23323be2b98d","name":"AP…` | 대상 사용자 정보 객체 (uuid/name/email 등 — user 관계 파생) | -| option_snapshot | null | `null` | 주문 시점 옵션 스냅샷 (옵션명 보존용) | -| option_snapshot_label | string | `` | `option_snapshot` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | -| rating | integer | `5` | 별점 (1~5) | -| content | string | `Alias quas iusto dolorem eum eveniet …` | 본문 내용 | -| content_mode | string | `text` | 콘텐츠 모드: text / html | -| status | string | `visible` | 상태 값 (도메인별 상태 집합 — 사람이 읽는 라벨은 status_label, UI 변형은 status_variant 참조) | -| status_label | string | `전시중` | 상태의 사람이 읽는 라벨 (상태 Enum label() 산물) | -| status_badge_color | string | `blue` | 상태 뱃지 색상 (visible=blue / hidden=gray) | -| has_reply | boolean | `true` | reply 여부 | -| has_reply_label | string | `답변완료` | `has_reply` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | -| has_reply_badge_color | string | `green` | 답변 여부 뱃지 색상 (답변완료=green / 미답변=gray) | -| reply_content | string | `실측 예시값` | 판매자 답변 내용 | -| reply_content_mode | string | `text` | 답변 콘텐츠 모드: text / html | -| replied_at | string | `2026-07-08 15:00:32` | replied 일시 | -| reply_updated_at | null | `null` | reply updated 일시 | -| created_at | string | `2026-07-08 10:44:49` | 생성 일시 | -| updated_at | string | `2026-07-08 15:00:32` | 최종 수정 일시 | -| abilities | object | `{"can_update":true,"can_delete":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | + **응답 예시** -```http -HTTP/1.1 200 -``` - -```json -{ - "success": true, - "message": "답변이 저장되었습니다.", - "data": { - "id": 1, - "product_id": 1, - "order_option_id": 1, - "user_id": "a234c2b1-cde8-437f-b28b-23323be2b98d", - "user": { - "uuid": "a234c2b1-cde8-437f-b28b-23323be2b98d", - "name": "API 문서 샘플 사용자", - "email": "apidoc-sample-user@example.com" - }, - "option_snapshot": null, - "option_snapshot_label": "", - "rating": 5, - "content": "Alias quas iusto dolorem eum eveniet ad omnis. Id neque consequatur fuga ut. Enim cum mollitia nisi. Adipisci sunt tenetur et tempora tempora eius rerum.", - "content_mode": "text", - "status": "visible", - "status_label": "전시중", - "status_badge_color": "blue", - "has_reply": true, - "has_reply_label": "답변완료", - "has_reply_badge_color": "green", - "reply_content": "실측 예시값", - "reply_content_mode": "text", - "replied_at": "2026-07-08 15:00:32", - "reply_updated_at": null, - "created_at": "2026-07-08 10:44:49", - "updated_at": "2026-07-08 15:00:32", - "abilities": { - "can_update": true, - "can_delete": true - } - } -} -``` + **에러 응답** @@ -693,7 +405,7 @@ HTTP/1.1 200 **요청 예시** ```http -PATCH /api/modules/sirsoft-ecommerce/admin/reviews/1/status HTTP/1.1 +PATCH /api/modules/sirsoft-ecommerce/admin/reviews/{review}/status HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -706,11 +418,11 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** - + **에러 응답** @@ -764,7 +476,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -798,7 +510,7 @@ Content-Type: application/json **요청 예시** ```http -GET /api/modules/sirsoft-ecommerce/user/reviews/can-write/1 HTTP/1.1 +GET /api/modules/sirsoft-ecommerce/user/reviews/can-write/{orderOptionId} HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -806,29 +518,11 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) -_단건 응답: `data` 객체의 필드._ - -| 필드 | 타입 | 실측 예시값 | 용도/설명 | -| --- | --- | --- | --- | -| can_write | boolean | `false` | write 수행 가능 여부 (권한 기반) | -| reason | string | `not_own_order` | 리뷰 작성 불가 사유 코드 (`can_write` 가 false 일 때만 값이 있고 가능하면 null. order_option_not_found 주문옵션 없음, not_own_order 본인 주문 아님, not_confirmed 구매확정 전, deadline_passed 작성 기한 초과, already_written 이미 작성함) | + **응답 예시** -```http -HTTP/1.1 200 -``` - -```json -{ - "success": true, - "message": "리뷰 작성 가능 여부를 확인했습니다.", - "data": { - "can_write": false, - "reason": "not_own_order" - } -} -``` + **에러 응답** @@ -857,7 +551,7 @@ HTTP/1.1 200 **요청 예시** ```http -DELETE /api/modules/sirsoft-ecommerce/user/reviews/1 HTTP/1.1 +DELETE /api/modules/sirsoft-ecommerce/user/reviews/{review} HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -865,27 +559,11 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) -_단건 응답: `data` 객체의 필드._ - -| 필드 | 타입 | 실측 예시값 | 용도/설명 | -| --- | --- | --- | --- | -| deleted | boolean | `true` | 삭제 처리 성공 여부 (true 이면 리뷰와 첨부 이미지가 제거됨) | + **응답 예시** -```http -HTTP/1.1 200 -``` - -```json -{ - "success": true, - "message": "리뷰가 삭제되었습니다.", - "data": { - "deleted": true - } -} -``` + **에러 응답** @@ -916,7 +594,7 @@ HTTP/1.1 200 **요청 예시** ```http -POST /api/modules/sirsoft-ecommerce/user/reviews/1/images HTTP/1.1 +POST /api/modules/sirsoft-ecommerce/user/reviews/{review}/images HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -932,11 +610,11 @@ Content-Type: application/octet-stream **응답 필드** (`data` 내부) - + **응답 예시** - + **에러 응답** @@ -968,7 +646,7 @@ Content-Type: application/octet-stream **요청 예시** ```http -DELETE /api/modules/sirsoft-ecommerce/user/reviews/1/images/{image} HTTP/1.1 +DELETE /api/modules/sirsoft-ecommerce/user/reviews/{review}/images/{image} HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -976,7 +654,7 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - + **응답 예시** diff --git a/modules/_bundled/sirsoft-ecommerce/docs/api/settings.md b/modules/_bundled/sirsoft-ecommerce/docs/api/settings.md index 4ed45540..cc91cde7 100644 --- a/modules/_bundled/sirsoft-ecommerce/docs/api/settings.md +++ b/modules/_bundled/sirsoft-ecommerce/docs/api/settings.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Settings 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` @@ -42,18 +42,19 @@ _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| basic_info | object | `{"shop_name":"","route_path":"shop","no_route":false,"com…` | 쇼핑몰 기본 정보 (쇼핑몰명·라우트 경로·상호·사업자번호·주소·연락처·이메일 등) | -| language_currency | object | `{"default_currency":"KRW","currencies":[{"code":"KRW","na…` | 통화 설정 (기본 통화 + 등록 통화 목록: 코드·다국어명·환율·기호·국기·반올림 규칙) | +| basic_info | object | `{"shop_name":"111","route_path":"shop","no_route":false,"…` | 쇼핑몰 기본 정보 (쇼핑몰명·라우트 경로·상호·사업자번호·주소·연락처·이메일 등) | +| language_currency | object | `{"default_currency":"JPY","currencies":[{"code":"KRW","na…` | 통화 설정 (기본 통화 + 등록 통화 목록: 코드·다국어명·환율·기호·국기·반올림 규칙) | | order_settings | object | `{"default_pg_provider":null,"payment_methods":[{"id":"car…` | 주문/결제 설정 (기본 PG·병합된 결제수단·은행/무통장 계좌·자동취소·장바구니 만료 등) | | shipping | object | `{"default_country":"KR","available_countries":[{"code":"K…` | 배송 설정 (기본 국가·배송 가능 국가·무료배송·DB 관리 배송사(carriers)·배송유형(types)·계산 API 후보 필드 포함) | | seo | object | `{"meta_category_title":"{commerce_name} - {category_name}…` | SEO 메타 설정 (카테고리·검색·상품·쇼핑몰 인덱스별 메타 타이틀/설명 및 SEO 활성 토글) | | review_settings | object | `{"write_deadline_days":90,"max_images":5,"max_image_size_…` | 리뷰 정책 (작성 기한일·이미지 최대 개수·이미지 최대 용량 MB) | -| inquiry | object | `{"board_slug":null}` | 문의 연동 설정 (문의 게시판 slug) | +| inquiry | object | `{"board_slug":"inquiry"}` | 문의 연동 설정 (문의 게시판 slug) | | notifications | object | `{"channels":[{"id":"mail","is_active":true,"sort_order":1…` | 알림 채널 설정 (채널 ID·활성 여부·정렬 순서) | | mileage | object | `{"enabled":false,"default_earn_rate":1,"earn_trigger":"co…` | 마일리지 설정 (사용 여부·기본 적립률·적립 트리거·통화별 규칙·소멸/소멸 알림·실제 활성 알림 채널 포함) | | claim | object | `{"refund_reasons":[{"id":1,"type":"refund","code":"order_…` | 클레임 설정 (DB 관리 대상인 환불 사유 목록: 코드·다국어명·귀책 유형·노출/활성 여부) | | available_pg_providers | array | `[{"id":"kginicis","name_key":"sirsoft-pay_kginicis::provi…` | 설치된 PG 플러그인이 훅으로 등록한 PG 제공자 목록 (id·name_key·지원 결제수단) | | abilities | object | `{"can_update":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | +| _meta | object | `{"limits":{"auto_cancel_days_min":1,"auto_cancel_days_max…` | | **응답 예시** @@ -64,125 +65,71 @@ HTTP/1.1 200 ```json { "success": true, - "message": "sirsoft-ecommerce::messages.settings.fetch_success", + "message": "설정을 조회했습니다.", "data": { "basic_info": { - "shop_name": "", + "shop_name": "111", "route_path": "shop", "no_route": false, - "company_name": "", + "company_name": null, "business_number": "", - "ceo_name": "", - "business_type": "", - "business_category": "", - "zipcode": "", - "base_address": "", - "detail_address": "", - "phone": "", - "fax": "", - "email": "", - "privacy_officer": "", - "privacy_officer_email": "", - "mail_order_number": "", - "telecom_number": "" + "...": "(13개 키 생략, 총 18개)" }, "language_currency": { - "default_currency": "KRW", + "default_currency": "JPY", "currencies": [ { "code": "KRW", "name": { "ko": "KRW (원)", - "en": "KRW (Won)", - "fr": "KRW (원)" + "en": "KRW (Won)" }, "symbol": "₩", "exchange_rate": null, "base_unit": 1000, - "rounding_unit": "1", - "rounding_method": "floor", - "decimal_places": 0, - "is_default": true, - "locales": [ - "ko" - ], - "flag": "🇰🇷" + "...": "(6개 키 생략, 총 11개)" }, { "code": "USD", "name": { - "ko": "USD (달러)", - "en": "USD (Dollar)", - "fr": "USD (달러)" + "en": "Dollar" }, - "symbol": "$", - "exchange_rate": 0.85, - "base_unit": 1, - "rounding_unit": "0.01", - "rounding_method": "round", - "decimal_places": 2, "is_default": false, - "locales": [ - "en" - ], - "flag": "🇺🇸" + "decimal_places": 2, + "base_unit": 1, + "...": "(3개 키 생략, 총 8개)" }, { "code": "JPY", "name": { - "ko": "JPY (엔)", - "en": "JPY (Yen)", - "fr": "JPY (엔)" + "ja": "円" }, - "symbol": "¥", - "exchange_rate": 115, - "base_unit": 100, - "rounding_unit": "1", - "rounding_method": "floor", + "is_default": true, "decimal_places": 0, - "is_default": false, - "locales": [ - "en" - ], - "flag": "🇯🇵" + "base_unit": 100, + "...": "(3개 키 생략, 총 8개)" }, { "code": "CNY", "name": { "ko": "CNY (위안)", - "en": "CNY (Yuan)", - "fr": "CNY (위안)" + "en": "CNY (Yuan)" }, "symbol": "元", "exchange_rate": 5.8, "base_unit": 1, - "rounding_unit": "0.01", - "rounding_method": "round", - "decimal_places": 2, - "is_default": false, - "locales": [ - "en" - ], - "flag": "🇨🇳" + "...": "(6개 키 생략, 총 11개)" }, { "code": "EUR", "name": { "ko": "EUR (유로)", - "en": "EUR (Euro)", - "fr": "EUR (유로)" + "en": "EUR (Euro)" }, "symbol": "€", "exchange_rate": 0.78, "base_unit": 1, - "rounding_unit": "0.01", - "rounding_method": "round", - "decimal_places": 2, - "is_default": false, - "locales": [ - "en" - ], - "flag": "🇪🇺" + "...": "(6개 키 생략, 총 11개)" } ] }, @@ -195,20 +142,15 @@ HTTP/1.1 200 "sort_order": 1, "is_active": false, "min_order_amount": 0, - "stock_deduction_timing": "payment_complete", - "mileage_deduction_timing": "payment_complete", - "_cached_name": { - "ko": "신용카드", - "en": "Credit Card", - "fr": "신용카드" - }, - "_cached_description": { - "ko": "신용카드로 안전하게 결제", - "en": "Pay securely with credit card", - "fr": "신용카드로 안전하게 결제" - }, - "_cached_icon": "credit-card", - "_cached_source": "builtin" + "...": "(10개 키 생략, 총 15개)" + }, + { + "id": "dbank", + "pg_provider": null, + "sort_order": 1, + "is_active": true, + "min_order_amount": 0, + "...": "(10개 키 생략, 총 15개)" }, { "id": "vbank", @@ -216,269 +158,27 @@ HTTP/1.1 200 "sort_order": 2, "is_active": false, "min_order_amount": 0, - "stock_deduction_timing": "payment_complete", - "mileage_deduction_timing": "order_placed", - "_cached_name": { - "ko": "가상계좌", - "en": "Virtual Account", - "fr": "가상계좌" - }, - "_cached_description": { - "ko": "가상계좌로 입금", - "en": "Pay via virtual account", - "fr": "가상계좌로 입금" - }, - "_cached_icon": "money-check", - "_cached_source": "builtin" - }, - { - "id": "dbank", - "pg_provider": null, - "sort_order": 3, - "is_active": true, - "min_order_amount": 0, - "stock_deduction_timing": "order_placed", - "mileage_deduction_timing": "order_placed", - "_cached_name": { - "ko": "무통장입금", - "en": "Bank Transfer", - "fr": "무통장입금" - }, - "_cached_description": { - "ko": "지정 계좌로 직접 입금", - "en": "Direct bank transfer", - "fr": "지정 계좌로 직접 입금" - }, - "_cached_icon": "building-columns", - "_cached_source": "builtin" - }, - { - "id": "bank", - "pg_provider": null, - "sort_order": 4, - "is_active": false, - "min_order_amount": 0, - "stock_deduction_timing": "payment_complete", - "mileage_deduction_timing": "payment_complete", - "_cached_name": { - "ko": "계좌이체", - "en": "Account Transfer", - "fr": "계좌이체" - }, - "_cached_description": { - "ko": "실시간 계좌이체", - "en": "Real-time bank transfer", - "fr": "실시간 계좌이체" - }, - "_cached_icon": "building-columns", - "_cached_source": "builtin" - }, - { - "id": "phone", - "pg_provider": null, - "sort_order": 5, - "is_active": false, - "min_order_amount": 0, - "stock_deduction_timing": "payment_complete", - "mileage_deduction_timing": "payment_complete", - "_cached_name": { - "ko": "휴대폰결제", - "en": "Mobile Payment", - "fr": "휴대폰결제" - }, - "_cached_description": { - "ko": "휴대폰 소액결제", - "en": "Mobile phone payment", - "fr": "휴대폰 소액결제" - }, - "_cached_icon": "mobile-screen-button", - "_cached_source": "builtin" + "...": "(10개 키 생략, 총 15개)" }, { "id": "point", "pg_provider": null, - "sort_order": 6, - "is_active": false, + "sort_order": 2, + "is_active": true, "min_order_amount": 0, - "stock_deduction_timing": "order_placed", - "mileage_deduction_timing": "payment_complete", - "_cached_name": { - "ko": "포인트결제", - "en": "Points", - "fr": "포인트결제" - }, - "_cached_description": { - "ko": "적립 포인트로 결제", - "en": "Pay with points", - "fr": "적립 포인트로 결제" - }, - "_cached_icon": "coins", - "_cached_source": "builtin" + "...": "(10개 키 생략, 총 15개)" }, { "id": "deposit", "pg_provider": null, - "sort_order": 7, - "is_active": false, + "sort_order": 3, + "is_active": true, "min_order_amount": 0, - "stock_deduction_timing": "order_placed", - "mileage_deduction_timing": "payment_complete", - "_cached_name": { - "ko": "예치금결제", - "en": "Store Credit", - "fr": "예치금결제" - }, - "_cached_description": { - "ko": "예치금으로 결제", - "en": "Pay with store credit", - "fr": "예치금으로 결제" - }, - "_cached_icon": "wallet", - "_cached_source": "builtin" + "...": "(10개 키 생략, 총 15개)" }, - { - "id": "free", - "pg_provider": null, - "sort_order": 8, - "is_active": false, - "min_order_amount": 0, - "stock_deduction_timing": "order_placed", - "mileage_deduction_timing": "payment_complete", - "_cached_name": { - "ko": "무료", - "en": "Free", - "fr": "무료" - }, - "_cached_description": { - "ko": "결제 없이 주문 완료", - "en": "Order without payment", - "fr": "결제 없이 주문 완료" - }, - "_cached_icon": "gift", - "_cached_source": "builtin" - } - ], - "banks": [ - { - "code": "004", - "name": { - "ko": "국민은행", - "en": "Kookmin Bank" - } - }, - { - "code": "088", - "name": { - "ko": "신한은행", - "en": "Shinhan Bank" - } - }, - { - "code": "020", - "name": { - "ko": "우리은행", - "en": "Woori Bank" - } - }, - { - "code": "081", - "name": { - "ko": "하나은행", - "en": "Hana Bank" - } - }, - { - "code": "003", - "name": { - "ko": "IBK기업은행", - "en": "IBK Industrial Bank" - } - }, - { - "code": "011", - "name": { - "ko": "NH농협은행", - "en": "NH Nonghyup Bank" - } - }, - { - "code": "071", - "name": { - "ko": "우체국", - "en": "Korea Post" - } - }, - { - "code": "031", - "name": { - "ko": "DGB대구은행", - "en": "DGB Daegu Bank" - } - }, - { - "code": "032", - "name": { - "ko": "BNK부산은행", - "en": "BNK Busan Bank" - } - }, - { - "code": "034", - "name": { - "ko": "광주은행", - "en": "Kwangju Bank" - } - }, - { - "code": "035", - "name": { - "ko": "제주은행", - "en": "Jeju Bank" - } - }, - { - "code": "037", - "name": { - "ko": "전북은행", - "en": "Jeonbuk Bank" - } - }, - { - "code": "039", - "name": { - "ko": "BNK경남은행", - "en": "BNK Kyongnam Bank" - } - }, - { - "code": "045", - "name": { - "ko": "새마을금고", - "en": "MG Community Credit Cooperatives" - } - }, - { - "code": "048", - "name": { - "ko": "신협", - "en": "KFCC" - } - }, - { - "code": "090", - "name": { - "ko": "카카오뱅크", - "en": "Kakao Bank" - } - }, - { - "code": "092", - "name": { - "ko": "토스뱅크", - "en": "Toss Bank" - } - } + "... (총 25건 중 5건 표시)" ], + "banks": [], "bank_accounts": [ { "bank_code": "004", @@ -489,16 +189,7 @@ HTTP/1.1 200 } ], "auto_cancel_expired": true, - "auto_cancel_days": 3, - "cart_expiry_days": 30, - "stock_restore_on_cancel": true, - "cancellable_statuses": [ - "payment_complete" - ], - "confirmable_statuses": [ - "shipping", - "delivered" - ] + "...": "(5개 키 생략, 총 10개)" }, "shipping": { "default_country": "KR", @@ -507,8 +198,7 @@ HTTP/1.1 200 "code": "KR", "name": { "ko": "대한민국", - "en": "South Korea", - "fr": "대한민국" + "en": "South Korea" }, "is_active": true }, @@ -516,304 +206,15 @@ HTTP/1.1 200 "code": "US", "name": { "ko": "미국", - "en": "United States", - "fr": "미국" + "en": "United States" }, - "is_active": false - }, - { - "code": "JP", - "name": { - "ko": "일본", - "en": "Japan", - "fr": "일본" - }, - "is_active": false - }, - { - "code": "CN", - "name": { - "ko": "중국", - "en": "China", - "fr": "중국" - }, - "is_active": false - }, - { - "code": "SG", - "name": { - "ko": "싱가포르", - "en": "Singapore", - "fr": "싱가포르" - }, - "is_active": false - }, - { - "code": "HK", - "name": { - "ko": "홍콩", - "en": "Hong Kong", - "fr": "홍콩" - }, - "is_active": false - }, - { - "code": "TW", - "name": { - "ko": "대만", - "en": "Taiwan", - "fr": "대만" - }, - "is_active": false - }, - { - "code": "VN", - "name": { - "ko": "베트남", - "en": "Vietnam", - "fr": "베트남" - }, - "is_active": false - }, - { - "code": "TH", - "name": { - "ko": "태국", - "en": "Thailand", - "fr": "태국" - }, - "is_active": false - }, - { - "code": "MY", - "name": { - "ko": "말레이시아", - "en": "Malaysia", - "fr": "말레이시아" - }, - "is_active": false + "is_active": true } ], - "international_shipping_enabled": false, + "international_shipping_enabled": true, "free_shipping_threshold": 50000, "free_shipping_enabled": true, - "address_validation_enabled": false, - "address_api_provider": "kakao", - "carriers": [ - { - "id": 13, - "code": "apidoc", - "name": { - "ko": "API 문서 샘플 배송사", - "en": "API Doc Sample Carrier" - }, - "type": "domestic", - "tracking_url": null, - "is_active": true, - "sort_order": 0 - }, - { - "id": 1, - "code": "cj", - "name": { - "ko": "CJ대한통운", - "en": "CJ Logistics" - }, - "type": "domestic", - "tracking_url": "https://trace.cjlogistics.com/next/tracking.html?wblNo={tracking_number}", - "is_active": true, - "sort_order": 1 - }, - { - "id": 2, - "code": "hanjin", - "name": { - "ko": "한진택배", - "en": "Hanjin Express" - }, - "type": "domestic", - "tracking_url": "https://www.hanjin.com/kor/CMS/DeliveryMgr/WaybillResult.do?wblnb={tracking_number}", - "is_active": true, - "sort_order": 2 - }, - { - "id": 3, - "code": "lotte", - "name": { - "ko": "롯데택배", - "en": "Lotte Global Logistics" - }, - "type": "domestic", - "tracking_url": "https://www.lotteglogis.com/home/reservation/tracking/link498?InvNo={tracking_number}", - "is_active": true, - "sort_order": 3 - }, - { - "id": 4, - "code": "logen", - "name": { - "ko": "로젠택배", - "en": "Logen Logistics" - }, - "type": "domestic", - "tracking_url": "https://www.ilogen.com/web/personal/trace/{tracking_number}", - "is_active": true, - "sort_order": 4 - }, - { - "id": 5, - "code": "ups", - "name": { - "ko": "UPS", - "en": "UPS" - }, - "type": "international", - "tracking_url": "https://www.ups.com/track?tracknum={tracking_number}", - "is_active": true, - "sort_order": 5 - }, - { - "id": 6, - "code": "ems", - "name": { - "ko": "EMS", - "en": "EMS" - }, - "type": "international", - "tracking_url": "https://service.epost.go.kr/trace.RetrieveEmsRi498.postal?POST_CODE={tracking_number}", - "is_active": true, - "sort_order": 6 - }, - { - "id": 7, - "code": "dhl", - "name": { - "ko": "DHL", - "en": "DHL" - }, - "type": "international", - "tracking_url": "https://www.dhl.com/kr-ko/home/tracking/tracking-express.html?submit=1&tracking-id={tracking_number}", - "is_active": true, - "sort_order": 7 - }, - { - "id": 8, - "code": "fedex", - "name": { - "ko": "FedEx", - "en": "FedEx" - }, - "type": "international", - "tracking_url": "https://www.fedex.com/fedextrack/?tracknumbers={tracking_number}", - "is_active": true, - "sort_order": 8 - }, - { - "id": 9, - "code": "sf", - "name": { - "ko": "SF Express", - "en": "SF Express" - }, - "type": "international", - "tracking_url": null, - "is_active": true, - "sort_order": 9 - }, - { - "id": 10, - "code": "yamato", - "name": { - "ko": "야마토운수", - "en": "Yamato Transport" - }, - "type": "international", - "tracking_url": null, - "is_active": true, - "sort_order": 10 - }, - { - "id": 11, - "code": "sagawa", - "name": { - "ko": "사가와익스프레스", - "en": "Sagawa Express" - }, - "type": "international", - "tracking_url": null, - "is_active": true, - "sort_order": 11 - }, - { - "id": 12, - "code": "other", - "name": { - "ko": "기타", - "en": "Other" - }, - "type": "domestic", - "tracking_url": null, - "is_active": true, - "sort_order": 99 - } - ], - "types": [], - "api_request_fields": [ - { - "value": "policy_id", - "label": "배송정책 ID" - }, - { - "value": "country_code", - "label": "국가 코드" - }, - { - "value": "items", - "label": "주문 항목" - }, - { - "value": "group_total", - "label": "그룹 합계 금액" - }, - { - "value": "total_quantity", - "label": "총 수량" - } - ], - "api_http_methods": [ - { - "value": "GET", - "label": "GET" - }, - { - "value": "POST", - "label": "POST" - } - ], - "api_auth_types": [ - { - "value": "none", - "label": "인증 없음" - }, - { - "value": "bearer", - "label": "Bearer 토큰" - }, - { - "value": "custom_header", - "label": "커스텀 헤더" - } - ], - "api_response_types": [ - { - "value": "json", - "label": "JSON" - }, - { - "value": "text", - "label": "텍스트" - } - ] + "...": "(8개 키 생략, 총 13개)" }, "seo": { "meta_category_title": "{commerce_name} - {category_name}", @@ -821,13 +222,7 @@ HTTP/1.1 200 "meta_search_title": "{commerce_name} - {keyword_name}", "meta_search_description": "", "meta_product_title": "{commerce_name} - {product_name}", - "meta_product_description": "", - "meta_shop_index_title": "{commerce_name}", - "meta_shop_index_description": "", - "seo_category": true, - "seo_search_result": true, - "seo_product_detail": true, - "seo_shop_index": true + "...": "(7개 키 생략, 총 12개)" }, "review_settings": { "write_deadline_days": 90, @@ -1086,17 +481,17 @@ _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| basic_info | object | `{"shop_name":"","route_path":"shop","no_route":false,"com…` | 쇼핑몰 기본 정보 (쇼핑몰명·라우트 경로·상호·사업자번호·주소·연락처·이메일 등) | -| language_currency | object | `{"default_currency":"KRW","currencies":[{"code":"KRW","na…` | 통화 설정 (기본 통화 + 등록 통화 목록: 코드·다국어명·환율·기호·국기·반올림 규칙) | +| basic_info | object | `{"shop_name":"111","route_path":"shop","no_route":false,"…` | 쇼핑몰 기본 정보 (쇼핑몰명·라우트 경로·상호·사업자번호·주소·연락처·이메일 등) | +| language_currency | object | `{"default_currency":"JPY","currencies":[{"code":"KRW","na…` | 통화 설정 (기본 통화 + 등록 통화 목록: 코드·다국어명·환율·기호·국기·반올림 규칙) | | order_settings | object | `{"default_pg_provider":null,"payment_methods":[{"id":"car…` | 주문/결제 설정 (기본 PG·병합된 결제수단·은행/무통장 계좌·자동취소·장바구니 만료 등) | | shipping | object | `{"default_country":"KR","available_countries":[{"code":"K…` | 배송 설정 (기본 국가·배송 가능 국가·무료배송·DB 관리 배송사(carriers)·배송유형(types)·계산 API 후보 필드 포함) | | seo | object | `{"meta_category_title":"{commerce_name} - {category_name}…` | SEO 메타 설정 (카테고리·검색·상품·쇼핑몰 인덱스별 메타 타이틀/설명 및 SEO 활성 토글) | | review_settings | object | `{"write_deadline_days":90,"max_images":5,"max_image_size_…` | 리뷰 정책 (작성 기한일·이미지 최대 개수·이미지 최대 용량 MB) | -| inquiry | object | `{"board_slug":null}` | 문의 연동 설정 (문의 게시판 slug) | +| inquiry | object | `{"board_slug":"inquiry"}` | 문의 연동 설정 (문의 게시판 slug) | | notifications | object | `{"channels":[{"id":"mail","is_active":true,"sort_order":1…` | 알림 채널 설정 (채널 ID·활성 여부·정렬 순서) | | mileage | object | `{"enabled":false,"default_earn_rate":1,"earn_trigger":"co…` | 마일리지 설정 (사용 여부·기본 적립률·적립 트리거·통화별 규칙·소멸/소멸 알림·실제 활성 알림 채널 포함) | | claim | object | `{"refund_reasons":[{"id":1,"type":"refund","code":"order_…` | 클레임 설정 (DB 관리 대상인 환불 사유 목록: 코드·다국어명·귀책 유형·노출/활성 여부) | -| available_pg_providers | array | `[]` | 설치된 PG 플러그인이 훅으로 등록한 PG 제공자 목록 (id·name_key·지원 결제수단) | +| available_pg_providers | array | `[{"id":"kginicis","name_key":"sirsoft-pay_kginicis::provi…` | 설치된 PG 플러그인이 훅으로 등록한 PG 제공자 목록 (id·name_key·지원 결제수단) | **응답 예시** @@ -1107,125 +502,71 @@ HTTP/1.1 200 ```json { "success": true, - "message": "sirsoft-ecommerce::messages.settings.save_success", + "message": "설정이 저장되었습니다.", "data": { "basic_info": { - "shop_name": "", + "shop_name": "111", "route_path": "shop", "no_route": false, - "company_name": "", + "company_name": null, "business_number": "", - "ceo_name": "", - "business_type": "", - "business_category": "", - "zipcode": "", - "base_address": "", - "detail_address": "", - "phone": "", - "fax": "", - "email": "", - "privacy_officer": "", - "privacy_officer_email": "", - "mail_order_number": "", - "telecom_number": "" + "...": "(13개 키 생략, 총 18개)" }, "language_currency": { - "default_currency": "KRW", + "default_currency": "JPY", "currencies": [ { "code": "KRW", "name": { "ko": "KRW (원)", - "en": "KRW (Won)", - "fr": "KRW (원)" + "en": "KRW (Won)" }, "symbol": "₩", "exchange_rate": null, "base_unit": 1000, - "rounding_unit": "1", - "rounding_method": "floor", - "decimal_places": 0, - "is_default": true, - "locales": [ - "ko" - ], - "flag": "🇰🇷" + "...": "(6개 키 생략, 총 11개)" }, { "code": "USD", "name": { - "ko": "USD (달러)", - "en": "USD (Dollar)", - "fr": "USD (달러)" + "en": "Dollar" }, - "symbol": "$", - "exchange_rate": 0.85, - "base_unit": 1, - "rounding_unit": "0.01", - "rounding_method": "round", - "decimal_places": 2, "is_default": false, - "locales": [ - "en" - ], - "flag": "🇺🇸" + "decimal_places": 2, + "base_unit": 1, + "...": "(3개 키 생략, 총 8개)" }, { "code": "JPY", "name": { - "ko": "JPY (엔)", - "en": "JPY (Yen)", - "fr": "JPY (엔)" + "ja": "円" }, - "symbol": "¥", - "exchange_rate": 115, - "base_unit": 100, - "rounding_unit": "1", - "rounding_method": "floor", + "is_default": true, "decimal_places": 0, - "is_default": false, - "locales": [ - "en" - ], - "flag": "🇯🇵" + "base_unit": 100, + "...": "(3개 키 생략, 총 8개)" }, { "code": "CNY", "name": { "ko": "CNY (위안)", - "en": "CNY (Yuan)", - "fr": "CNY (위안)" + "en": "CNY (Yuan)" }, "symbol": "元", "exchange_rate": 5.8, "base_unit": 1, - "rounding_unit": "0.01", - "rounding_method": "round", - "decimal_places": 2, - "is_default": false, - "locales": [ - "en" - ], - "flag": "🇨🇳" + "...": "(6개 키 생략, 총 11개)" }, { "code": "EUR", "name": { "ko": "EUR (유로)", - "en": "EUR (Euro)", - "fr": "EUR (유로)" + "en": "EUR (Euro)" }, "symbol": "€", "exchange_rate": 0.78, "base_unit": 1, - "rounding_unit": "0.01", - "rounding_method": "round", - "decimal_places": 2, - "is_default": false, - "locales": [ - "en" - ], - "flag": "🇪🇺" + "...": "(6개 키 생략, 총 11개)" } ] }, @@ -1238,20 +579,15 @@ HTTP/1.1 200 "sort_order": 1, "is_active": false, "min_order_amount": 0, - "stock_deduction_timing": "payment_complete", - "mileage_deduction_timing": "payment_complete", - "_cached_name": { - "ko": "신용카드", - "en": "Credit Card", - "fr": "신용카드" - }, - "_cached_description": { - "ko": "신용카드로 안전하게 결제", - "en": "Pay securely with credit card", - "fr": "신용카드로 안전하게 결제" - }, - "_cached_icon": "credit-card", - "_cached_source": "builtin" + "...": "(10개 키 생략, 총 15개)" + }, + { + "id": "dbank", + "pg_provider": null, + "sort_order": 1, + "is_active": true, + "min_order_amount": 0, + "...": "(10개 키 생략, 총 15개)" }, { "id": "vbank", @@ -1259,269 +595,27 @@ HTTP/1.1 200 "sort_order": 2, "is_active": false, "min_order_amount": 0, - "stock_deduction_timing": "payment_complete", - "mileage_deduction_timing": "order_placed", - "_cached_name": { - "ko": "가상계좌", - "en": "Virtual Account", - "fr": "가상계좌" - }, - "_cached_description": { - "ko": "가상계좌로 입금", - "en": "Pay via virtual account", - "fr": "가상계좌로 입금" - }, - "_cached_icon": "money-check", - "_cached_source": "builtin" - }, - { - "id": "dbank", - "pg_provider": null, - "sort_order": 3, - "is_active": true, - "min_order_amount": 0, - "stock_deduction_timing": "order_placed", - "mileage_deduction_timing": "order_placed", - "_cached_name": { - "ko": "무통장입금", - "en": "Bank Transfer", - "fr": "무통장입금" - }, - "_cached_description": { - "ko": "지정 계좌로 직접 입금", - "en": "Direct bank transfer", - "fr": "지정 계좌로 직접 입금" - }, - "_cached_icon": "building-columns", - "_cached_source": "builtin" - }, - { - "id": "bank", - "pg_provider": null, - "sort_order": 4, - "is_active": false, - "min_order_amount": 0, - "stock_deduction_timing": "payment_complete", - "mileage_deduction_timing": "payment_complete", - "_cached_name": { - "ko": "계좌이체", - "en": "Account Transfer", - "fr": "계좌이체" - }, - "_cached_description": { - "ko": "실시간 계좌이체", - "en": "Real-time bank transfer", - "fr": "실시간 계좌이체" - }, - "_cached_icon": "building-columns", - "_cached_source": "builtin" - }, - { - "id": "phone", - "pg_provider": null, - "sort_order": 5, - "is_active": false, - "min_order_amount": 0, - "stock_deduction_timing": "payment_complete", - "mileage_deduction_timing": "payment_complete", - "_cached_name": { - "ko": "휴대폰결제", - "en": "Mobile Payment", - "fr": "휴대폰결제" - }, - "_cached_description": { - "ko": "휴대폰 소액결제", - "en": "Mobile phone payment", - "fr": "휴대폰 소액결제" - }, - "_cached_icon": "mobile-screen-button", - "_cached_source": "builtin" + "...": "(10개 키 생략, 총 15개)" }, { "id": "point", "pg_provider": null, - "sort_order": 6, - "is_active": false, + "sort_order": 2, + "is_active": true, "min_order_amount": 0, - "stock_deduction_timing": "order_placed", - "mileage_deduction_timing": "payment_complete", - "_cached_name": { - "ko": "포인트결제", - "en": "Points", - "fr": "포인트결제" - }, - "_cached_description": { - "ko": "적립 포인트로 결제", - "en": "Pay with points", - "fr": "적립 포인트로 결제" - }, - "_cached_icon": "coins", - "_cached_source": "builtin" + "...": "(10개 키 생략, 총 15개)" }, { "id": "deposit", "pg_provider": null, - "sort_order": 7, - "is_active": false, + "sort_order": 3, + "is_active": true, "min_order_amount": 0, - "stock_deduction_timing": "order_placed", - "mileage_deduction_timing": "payment_complete", - "_cached_name": { - "ko": "예치금결제", - "en": "Store Credit", - "fr": "예치금결제" - }, - "_cached_description": { - "ko": "예치금으로 결제", - "en": "Pay with store credit", - "fr": "예치금으로 결제" - }, - "_cached_icon": "wallet", - "_cached_source": "builtin" + "...": "(10개 키 생략, 총 15개)" }, - { - "id": "free", - "pg_provider": null, - "sort_order": 8, - "is_active": false, - "min_order_amount": 0, - "stock_deduction_timing": "order_placed", - "mileage_deduction_timing": "payment_complete", - "_cached_name": { - "ko": "무료", - "en": "Free", - "fr": "무료" - }, - "_cached_description": { - "ko": "결제 없이 주문 완료", - "en": "Order without payment", - "fr": "결제 없이 주문 완료" - }, - "_cached_icon": "gift", - "_cached_source": "builtin" - } - ], - "banks": [ - { - "code": "004", - "name": { - "ko": "국민은행", - "en": "Kookmin Bank" - } - }, - { - "code": "088", - "name": { - "ko": "신한은행", - "en": "Shinhan Bank" - } - }, - { - "code": "020", - "name": { - "ko": "우리은행", - "en": "Woori Bank" - } - }, - { - "code": "081", - "name": { - "ko": "하나은행", - "en": "Hana Bank" - } - }, - { - "code": "003", - "name": { - "ko": "IBK기업은행", - "en": "IBK Industrial Bank" - } - }, - { - "code": "011", - "name": { - "ko": "NH농협은행", - "en": "NH Nonghyup Bank" - } - }, - { - "code": "071", - "name": { - "ko": "우체국", - "en": "Korea Post" - } - }, - { - "code": "031", - "name": { - "ko": "DGB대구은행", - "en": "DGB Daegu Bank" - } - }, - { - "code": "032", - "name": { - "ko": "BNK부산은행", - "en": "BNK Busan Bank" - } - }, - { - "code": "034", - "name": { - "ko": "광주은행", - "en": "Kwangju Bank" - } - }, - { - "code": "035", - "name": { - "ko": "제주은행", - "en": "Jeju Bank" - } - }, - { - "code": "037", - "name": { - "ko": "전북은행", - "en": "Jeonbuk Bank" - } - }, - { - "code": "039", - "name": { - "ko": "BNK경남은행", - "en": "BNK Kyongnam Bank" - } - }, - { - "code": "045", - "name": { - "ko": "새마을금고", - "en": "MG Community Credit Cooperatives" - } - }, - { - "code": "048", - "name": { - "ko": "신협", - "en": "KFCC" - } - }, - { - "code": "090", - "name": { - "ko": "카카오뱅크", - "en": "Kakao Bank" - } - }, - { - "code": "092", - "name": { - "ko": "토스뱅크", - "en": "Toss Bank" - } - } + "... (총 25건 중 5건 표시)" ], + "banks": [], "bank_accounts": [ { "bank_code": "004", @@ -1532,16 +626,7 @@ HTTP/1.1 200 } ], "auto_cancel_expired": true, - "auto_cancel_days": 3, - "cart_expiry_days": 30, - "stock_restore_on_cancel": true, - "cancellable_statuses": [ - "payment_complete" - ], - "confirmable_statuses": [ - "shipping", - "delivered" - ] + "...": "(5개 키 생략, 총 10개)" }, "shipping": { "default_country": "KR", @@ -1550,8 +635,7 @@ HTTP/1.1 200 "code": "KR", "name": { "ko": "대한민국", - "en": "South Korea", - "fr": "대한민국" + "en": "South Korea" }, "is_active": true }, @@ -1559,304 +643,15 @@ HTTP/1.1 200 "code": "US", "name": { "ko": "미국", - "en": "United States", - "fr": "미국" + "en": "United States" }, - "is_active": false - }, - { - "code": "JP", - "name": { - "ko": "일본", - "en": "Japan", - "fr": "일본" - }, - "is_active": false - }, - { - "code": "CN", - "name": { - "ko": "중국", - "en": "China", - "fr": "중국" - }, - "is_active": false - }, - { - "code": "SG", - "name": { - "ko": "싱가포르", - "en": "Singapore", - "fr": "싱가포르" - }, - "is_active": false - }, - { - "code": "HK", - "name": { - "ko": "홍콩", - "en": "Hong Kong", - "fr": "홍콩" - }, - "is_active": false - }, - { - "code": "TW", - "name": { - "ko": "대만", - "en": "Taiwan", - "fr": "대만" - }, - "is_active": false - }, - { - "code": "VN", - "name": { - "ko": "베트남", - "en": "Vietnam", - "fr": "베트남" - }, - "is_active": false - }, - { - "code": "TH", - "name": { - "ko": "태국", - "en": "Thailand", - "fr": "태국" - }, - "is_active": false - }, - { - "code": "MY", - "name": { - "ko": "말레이시아", - "en": "Malaysia", - "fr": "말레이시아" - }, - "is_active": false + "is_active": true } ], - "international_shipping_enabled": false, + "international_shipping_enabled": true, "free_shipping_threshold": 50000, "free_shipping_enabled": true, - "address_validation_enabled": false, - "address_api_provider": "kakao", - "carriers": [ - { - "id": 13, - "code": "apidoc", - "name": { - "ko": "API 문서 샘플 배송사", - "en": "API Doc Sample Carrier" - }, - "type": "domestic", - "tracking_url": null, - "is_active": true, - "sort_order": 0 - }, - { - "id": 1, - "code": "cj", - "name": { - "ko": "CJ대한통운", - "en": "CJ Logistics" - }, - "type": "domestic", - "tracking_url": "https://trace.cjlogistics.com/next/tracking.html?wblNo={tracking_number}", - "is_active": true, - "sort_order": 1 - }, - { - "id": 2, - "code": "hanjin", - "name": { - "ko": "한진택배", - "en": "Hanjin Express" - }, - "type": "domestic", - "tracking_url": "https://www.hanjin.com/kor/CMS/DeliveryMgr/WaybillResult.do?wblnb={tracking_number}", - "is_active": true, - "sort_order": 2 - }, - { - "id": 3, - "code": "lotte", - "name": { - "ko": "롯데택배", - "en": "Lotte Global Logistics" - }, - "type": "domestic", - "tracking_url": "https://www.lotteglogis.com/home/reservation/tracking/link498?InvNo={tracking_number}", - "is_active": true, - "sort_order": 3 - }, - { - "id": 4, - "code": "logen", - "name": { - "ko": "로젠택배", - "en": "Logen Logistics" - }, - "type": "domestic", - "tracking_url": "https://www.ilogen.com/web/personal/trace/{tracking_number}", - "is_active": true, - "sort_order": 4 - }, - { - "id": 5, - "code": "ups", - "name": { - "ko": "UPS", - "en": "UPS" - }, - "type": "international", - "tracking_url": "https://www.ups.com/track?tracknum={tracking_number}", - "is_active": true, - "sort_order": 5 - }, - { - "id": 6, - "code": "ems", - "name": { - "ko": "EMS", - "en": "EMS" - }, - "type": "international", - "tracking_url": "https://service.epost.go.kr/trace.RetrieveEmsRi498.postal?POST_CODE={tracking_number}", - "is_active": true, - "sort_order": 6 - }, - { - "id": 7, - "code": "dhl", - "name": { - "ko": "DHL", - "en": "DHL" - }, - "type": "international", - "tracking_url": "https://www.dhl.com/kr-ko/home/tracking/tracking-express.html?submit=1&tracking-id={tracking_number}", - "is_active": true, - "sort_order": 7 - }, - { - "id": 8, - "code": "fedex", - "name": { - "ko": "FedEx", - "en": "FedEx" - }, - "type": "international", - "tracking_url": "https://www.fedex.com/fedextrack/?tracknumbers={tracking_number}", - "is_active": true, - "sort_order": 8 - }, - { - "id": 9, - "code": "sf", - "name": { - "ko": "SF Express", - "en": "SF Express" - }, - "type": "international", - "tracking_url": null, - "is_active": true, - "sort_order": 9 - }, - { - "id": 10, - "code": "yamato", - "name": { - "ko": "야마토운수", - "en": "Yamato Transport" - }, - "type": "international", - "tracking_url": null, - "is_active": true, - "sort_order": 10 - }, - { - "id": 11, - "code": "sagawa", - "name": { - "ko": "사가와익스프레스", - "en": "Sagawa Express" - }, - "type": "international", - "tracking_url": null, - "is_active": true, - "sort_order": 11 - }, - { - "id": 12, - "code": "other", - "name": { - "ko": "기타", - "en": "Other" - }, - "type": "domestic", - "tracking_url": null, - "is_active": true, - "sort_order": 99 - } - ], - "types": [], - "api_request_fields": [ - { - "value": "policy_id", - "label": "배송정책 ID" - }, - { - "value": "country_code", - "label": "국가 코드" - }, - { - "value": "items", - "label": "주문 항목" - }, - { - "value": "group_total", - "label": "그룹 합계 금액" - }, - { - "value": "total_quantity", - "label": "총 수량" - } - ], - "api_http_methods": [ - { - "value": "GET", - "label": "GET" - }, - { - "value": "POST", - "label": "POST" - } - ], - "api_auth_types": [ - { - "value": "none", - "label": "인증 없음" - }, - { - "value": "bearer", - "label": "Bearer 토큰" - }, - { - "value": "custom_header", - "label": "커스텀 헤더" - } - ], - "api_response_types": [ - { - "value": "json", - "label": "JSON" - }, - { - "value": "text", - "label": "텍스트" - } - ] + "...": "(8개 키 생략, 총 13개)" }, "seo": { "meta_category_title": "{commerce_name} - {category_name}", @@ -1864,13 +659,7 @@ HTTP/1.1 200 "meta_search_title": "{commerce_name} - {keyword_name}", "meta_search_description": "", "meta_product_title": "{commerce_name} - {product_name}", - "meta_product_description": "", - "meta_shop_index_title": "{commerce_name}", - "meta_shop_index_description": "", - "seo_category": true, - "seo_search_result": true, - "seo_product_detail": true, - "seo_shop_index": true + "...": "(7개 키 생략, 총 12개)" }, "review_settings": { "write_deadline_days": 90, @@ -2107,13 +896,13 @@ _단건 응답: `data` 객체의 필드._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| basic_info | object | `{"shop_name":"","route_path":"shop","no_route":false,"com…` | 쇼핑몰 기본 정보 (쇼핑몰명·라우트 경로·상호·사업자번호·주소·연락처·이메일 등) | -| language_currency | object | `{"default_currency":"KRW","currencies":[{"code":"KRW","na…` | 통화 설정 (기본 통화 + 등록 통화 목록: 코드·다국어명·환율·기호·국기·반올림 규칙) | +| basic_info | object | `{"shop_name":"111","route_path":"shop","no_route":false,"…` | 쇼핑몰 기본 정보 (쇼핑몰명·라우트 경로·상호·사업자번호·주소·연락처·이메일 등) | +| language_currency | object | `{"default_currency":"JPY","currencies":[{"code":"KRW","na…` | 통화 설정 (기본 통화 + 등록 통화 목록: 코드·다국어명·환율·기호·국기·반올림 규칙) | | order_settings | object | `{"default_pg_provider":null,"payment_methods":[{"id":"car…` | 주문/결제 설정 (기본 PG·병합된 결제수단·은행/무통장 계좌·자동취소·장바구니 만료 등) | | shipping | object | `{"default_country":"KR","available_countries":[{"code":"K…` | 배송 설정 (기본 국가·배송 가능 국가·무료배송·DB 관리 배송사(carriers)·배송유형(types)·계산 API 후보 필드 포함) | | seo | object | `{"meta_category_title":"{commerce_name} - {category_name}…` | SEO 메타 설정 (카테고리·검색·상품·쇼핑몰 인덱스별 메타 타이틀/설명 및 SEO 활성 토글) | | review_settings | object | `{"write_deadline_days":90,"max_images":5,"max_image_size_…` | 리뷰 정책 (작성 기한일·이미지 최대 개수·이미지 최대 용량 MB) | -| inquiry | object | `{"board_slug":null}` | 문의 연동 설정 (문의 게시판 slug) | +| inquiry | object | `{"board_slug":"inquiry"}` | 문의 연동 설정 (문의 게시판 slug) | | notifications | object | `{"channels":[{"id":"mail","is_active":true,"sort_order":1…` | 알림 채널 설정 (채널 ID·활성 여부·정렬 순서) | | mileage | object | `{"enabled":false,"default_earn_rate":1,"earn_trigger":"co…` | 마일리지 설정 (사용 여부·기본 적립률·적립 트리거·통화별 규칙·소멸/소멸 알림·실제 활성 알림 채널 포함) | @@ -2126,125 +915,71 @@ HTTP/1.1 200 ```json { "success": true, - "message": "sirsoft-ecommerce::messages.settings.save_success", + "message": "설정이 저장되었습니다.", "data": { "basic_info": { - "shop_name": "", + "shop_name": "111", "route_path": "shop", "no_route": false, - "company_name": "", + "company_name": null, "business_number": "", - "ceo_name": "", - "business_type": "", - "business_category": "", - "zipcode": "", - "base_address": "", - "detail_address": "", - "phone": "", - "fax": "", - "email": "", - "privacy_officer": "", - "privacy_officer_email": "", - "mail_order_number": "", - "telecom_number": "" + "...": "(13개 키 생략, 총 18개)" }, "language_currency": { - "default_currency": "KRW", + "default_currency": "JPY", "currencies": [ { "code": "KRW", "name": { "ko": "KRW (원)", - "en": "KRW (Won)", - "fr": "KRW (원)" + "en": "KRW (Won)" }, "symbol": "₩", "exchange_rate": null, "base_unit": 1000, - "rounding_unit": "1", - "rounding_method": "floor", - "decimal_places": 0, - "is_default": true, - "locales": [ - "ko" - ], - "flag": "🇰🇷" + "...": "(6개 키 생략, 총 11개)" }, { "code": "USD", "name": { - "ko": "USD (달러)", - "en": "USD (Dollar)", - "fr": "USD (달러)" + "en": "Dollar" }, - "symbol": "$", - "exchange_rate": 0.85, - "base_unit": 1, - "rounding_unit": "0.01", - "rounding_method": "round", - "decimal_places": 2, "is_default": false, - "locales": [ - "en" - ], - "flag": "🇺🇸" + "decimal_places": 2, + "base_unit": 1, + "...": "(3개 키 생략, 총 8개)" }, { "code": "JPY", "name": { - "ko": "JPY (엔)", - "en": "JPY (Yen)", - "fr": "JPY (엔)" + "ja": "円" }, - "symbol": "¥", - "exchange_rate": 115, - "base_unit": 100, - "rounding_unit": "1", - "rounding_method": "floor", + "is_default": true, "decimal_places": 0, - "is_default": false, - "locales": [ - "en" - ], - "flag": "🇯🇵" + "base_unit": 100, + "...": "(3개 키 생략, 총 8개)" }, { "code": "CNY", "name": { "ko": "CNY (위안)", - "en": "CNY (Yuan)", - "fr": "CNY (위안)" + "en": "CNY (Yuan)" }, "symbol": "元", "exchange_rate": 5.8, "base_unit": 1, - "rounding_unit": "0.01", - "rounding_method": "round", - "decimal_places": 2, - "is_default": false, - "locales": [ - "en" - ], - "flag": "🇨🇳" + "...": "(6개 키 생략, 총 11개)" }, { "code": "EUR", "name": { "ko": "EUR (유로)", - "en": "EUR (Euro)", - "fr": "EUR (유로)" + "en": "EUR (Euro)" }, "symbol": "€", "exchange_rate": 0.78, "base_unit": 1, - "rounding_unit": "0.01", - "rounding_method": "round", - "decimal_places": 2, - "is_default": false, - "locales": [ - "en" - ], - "flag": "🇪🇺" + "...": "(6개 키 생략, 총 11개)" } ] }, @@ -2257,20 +992,15 @@ HTTP/1.1 200 "sort_order": 1, "is_active": false, "min_order_amount": 0, - "stock_deduction_timing": "payment_complete", - "mileage_deduction_timing": "payment_complete", - "_cached_name": { - "ko": "신용카드", - "en": "Credit Card", - "fr": "신용카드" - }, - "_cached_description": { - "ko": "신용카드로 안전하게 결제", - "en": "Pay securely with credit card", - "fr": "신용카드로 안전하게 결제" - }, - "_cached_icon": "credit-card", - "_cached_source": "builtin" + "...": "(10개 키 생략, 총 15개)" + }, + { + "id": "dbank", + "pg_provider": null, + "sort_order": 1, + "is_active": true, + "min_order_amount": 0, + "...": "(10개 키 생략, 총 15개)" }, { "id": "vbank", @@ -2278,147 +1008,25 @@ HTTP/1.1 200 "sort_order": 2, "is_active": false, "min_order_amount": 0, - "stock_deduction_timing": "payment_complete", - "mileage_deduction_timing": "order_placed", - "_cached_name": { - "ko": "가상계좌", - "en": "Virtual Account", - "fr": "가상계좌" - }, - "_cached_description": { - "ko": "가상계좌로 입금", - "en": "Pay via virtual account", - "fr": "가상계좌로 입금" - }, - "_cached_icon": "money-check", - "_cached_source": "builtin" - }, - { - "id": "dbank", - "pg_provider": null, - "sort_order": 3, - "is_active": true, - "min_order_amount": 0, - "stock_deduction_timing": "order_placed", - "mileage_deduction_timing": "order_placed", - "_cached_name": { - "ko": "무통장입금", - "en": "Bank Transfer", - "fr": "무통장입금" - }, - "_cached_description": { - "ko": "지정 계좌로 직접 입금", - "en": "Direct bank transfer", - "fr": "지정 계좌로 직접 입금" - }, - "_cached_icon": "building-columns", - "_cached_source": "builtin" - }, - { - "id": "bank", - "pg_provider": null, - "sort_order": 4, - "is_active": false, - "min_order_amount": 0, - "stock_deduction_timing": "payment_complete", - "mileage_deduction_timing": "payment_complete", - "_cached_name": { - "ko": "계좌이체", - "en": "Account Transfer", - "fr": "계좌이체" - }, - "_cached_description": { - "ko": "실시간 계좌이체", - "en": "Real-time bank transfer", - "fr": "실시간 계좌이체" - }, - "_cached_icon": "building-columns", - "_cached_source": "builtin" - }, - { - "id": "phone", - "pg_provider": null, - "sort_order": 5, - "is_active": false, - "min_order_amount": 0, - "stock_deduction_timing": "payment_complete", - "mileage_deduction_timing": "payment_complete", - "_cached_name": { - "ko": "휴대폰결제", - "en": "Mobile Payment", - "fr": "휴대폰결제" - }, - "_cached_description": { - "ko": "휴대폰 소액결제", - "en": "Mobile phone payment", - "fr": "휴대폰 소액결제" - }, - "_cached_icon": "mobile-screen-button", - "_cached_source": "builtin" + "...": "(10개 키 생략, 총 15개)" }, { "id": "point", "pg_provider": null, - "sort_order": 6, - "is_active": false, + "sort_order": 2, + "is_active": true, "min_order_amount": 0, - "stock_deduction_timing": "order_placed", - "mileage_deduction_timing": "payment_complete", - "_cached_name": { - "ko": "포인트결제", - "en": "Points", - "fr": "포인트결제" - }, - "_cached_description": { - "ko": "적립 포인트로 결제", - "en": "Pay with points", - "fr": "적립 포인트로 결제" - }, - "_cached_icon": "coins", - "_cached_source": "builtin" + "...": "(10개 키 생략, 총 15개)" }, { "id": "deposit", "pg_provider": null, - "sort_order": 7, - "is_active": false, + "sort_order": 3, + "is_active": true, "min_order_amount": 0, - "stock_deduction_timing": "order_placed", - "mileage_deduction_timing": "payment_complete", - "_cached_name": { - "ko": "예치금결제", - "en": "Store Credit", - "fr": "예치금결제" - }, - "_cached_description": { - "ko": "예치금으로 결제", - "en": "Pay with store credit", - "fr": "예치금으로 결제" - }, - "_cached_icon": "wallet", - "_cached_source": "builtin" + "...": "(10개 키 생략, 총 15개)" }, - { - "id": "free", - "pg_provider": null, - "sort_order": 8, - "is_active": false, - "min_order_amount": 0, - "stock_deduction_timing": "order_placed", - "mileage_deduction_timing": "payment_complete", - "_cached_name": { - "ko": "무료", - "en": "Free", - "fr": "무료" - }, - "_cached_description": { - "ko": "결제 없이 주문 완료", - "en": "Order without payment", - "fr": "결제 없이 주문 완료" - }, - "_cached_icon": "gift", - "_cached_source": "builtin" - } + "... (총 25건 중 5건 표시)" ], "banks": [], "bank_accounts": [ @@ -2431,16 +1039,7 @@ HTTP/1.1 200 } ], "auto_cancel_expired": true, - "auto_cancel_days": 3, - "cart_expiry_days": 30, - "stock_restore_on_cancel": true, - "cancellable_statuses": [ - "payment_complete" - ], - "confirmable_statuses": [ - "shipping", - "delivered" - ] + "...": "(5개 키 생략, 총 10개)" }, "shipping": { "default_country": "KR", @@ -2449,8 +1048,7 @@ HTTP/1.1 200 "code": "KR", "name": { "ko": "대한민국", - "en": "South Korea", - "fr": "대한민국" + "en": "South Korea" }, "is_active": true }, @@ -2458,89 +1056,15 @@ HTTP/1.1 200 "code": "US", "name": { "ko": "미국", - "en": "United States", - "fr": "미국" + "en": "United States" }, - "is_active": false - }, - { - "code": "JP", - "name": { - "ko": "일본", - "en": "Japan", - "fr": "일본" - }, - "is_active": false - }, - { - "code": "CN", - "name": { - "ko": "중국", - "en": "China", - "fr": "중국" - }, - "is_active": false - }, - { - "code": "SG", - "name": { - "ko": "싱가포르", - "en": "Singapore", - "fr": "싱가포르" - }, - "is_active": false - }, - { - "code": "HK", - "name": { - "ko": "홍콩", - "en": "Hong Kong", - "fr": "홍콩" - }, - "is_active": false - }, - { - "code": "TW", - "name": { - "ko": "대만", - "en": "Taiwan", - "fr": "대만" - }, - "is_active": false - }, - { - "code": "VN", - "name": { - "ko": "베트남", - "en": "Vietnam", - "fr": "베트남" - }, - "is_active": false - }, - { - "code": "TH", - "name": { - "ko": "태국", - "en": "Thailand", - "fr": "태국" - }, - "is_active": false - }, - { - "code": "MY", - "name": { - "ko": "말레이시아", - "en": "Malaysia", - "fr": "말레이시아" - }, - "is_active": false + "is_active": true } ], - "international_shipping_enabled": false, + "international_shipping_enabled": true, "free_shipping_threshold": 50000, "free_shipping_enabled": true, - "address_validation_enabled": false, - "address_api_provider": "kakao" + "...": "(2개 키 생략, 총 7개)" }, "seo": { "meta_category_title": "{commerce_name} - {category_name}", @@ -2548,13 +1072,7 @@ HTTP/1.1 200 "meta_search_title": "{commerce_name} - {keyword_name}", "meta_search_description": "", "meta_product_title": "{commerce_name} - {product_name}", - "meta_product_description": "", - "meta_shop_index_title": "{commerce_name}", - "meta_shop_index_description": "", - "seo_category": true, - "seo_search_result": true, - "seo_product_detail": true, - "seo_shop_index": true + "...": "(7개 키 생략, 총 12개)" }, "review_settings": { "write_deadline_days": 90, @@ -2639,7 +1157,7 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - + **응답 예시** @@ -2695,7 +1213,7 @@ HTTP/1.1 200 ```json { "success": true, - "message": "sirsoft-ecommerce::messages.settings.fetch_success", + "message": "설정을 조회했습니다.", "data": { "count": 0, "size_bytes": 0, @@ -2731,7 +1249,7 @@ HTTP/1.1 200 **요청 예시** ```http -GET /api/modules/sirsoft-ecommerce/admin/settings/basic_info HTTP/1.1 +GET /api/modules/sirsoft-ecommerce/admin/settings/{category} HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -2739,52 +1257,11 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) -_단건 응답: `data` 객체의 필드._ - -| 필드 | 타입 | 실측 예시값 | 용도/설명 | -| --- | --- | --- | --- | -| category | string | `basic_info` | 조회한 설정 분류(탭) 식별자 (`basic_info`·`language_currency`·`shipping` 등 요청 경로의 category 값 반영) | -| settings | object | `{"shop_name":"","route_path":"shop","no_route":false,"com…` | 해당 분류의 설정 값 객체 (분류에 따라 구조가 달라지며, 배송·클레임 등은 DB 관리 대상이 병합되어 반환) | -| abilities | object | `{"can_update":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | + **응답 예시** -```http -HTTP/1.1 200 -``` - -```json -{ - "success": true, - "message": "sirsoft-ecommerce::messages.settings.fetch_success", - "data": { - "category": "basic_info", - "settings": { - "shop_name": "", - "route_path": "shop", - "no_route": false, - "company_name": "", - "business_number": "", - "ceo_name": "", - "business_type": "", - "business_category": "", - "zipcode": "", - "base_address": "", - "detail_address": "", - "phone": "", - "fax": "", - "email": "", - "privacy_officer": "", - "privacy_officer_email": "", - "mail_order_number": "", - "telecom_number": "" - }, - "abilities": { - "can_update": true - } - } -} -``` + **에러 응답** @@ -2835,7 +1312,7 @@ HTTP/1.1 200 ```json { "success": true, - "message": "sirsoft-ecommerce::messages.settings.fetch_success", + "message": "설정을 조회했습니다.", "data": { "shipping": { "default_country": "KR", @@ -2844,8 +1321,7 @@ HTTP/1.1 200 "code": "KR", "name": { "ko": "대한민국", - "en": "South Korea", - "fr": "대한민국" + "en": "South Korea" }, "is_active": true }, @@ -2853,89 +1329,15 @@ HTTP/1.1 200 "code": "US", "name": { "ko": "미국", - "en": "United States", - "fr": "미국" + "en": "United States" }, - "is_active": false - }, - { - "code": "JP", - "name": { - "ko": "일본", - "en": "Japan", - "fr": "일본" - }, - "is_active": false - }, - { - "code": "CN", - "name": { - "ko": "중국", - "en": "China", - "fr": "중국" - }, - "is_active": false - }, - { - "code": "SG", - "name": { - "ko": "싱가포르", - "en": "Singapore", - "fr": "싱가포르" - }, - "is_active": false - }, - { - "code": "HK", - "name": { - "ko": "홍콩", - "en": "Hong Kong", - "fr": "홍콩" - }, - "is_active": false - }, - { - "code": "TW", - "name": { - "ko": "대만", - "en": "Taiwan", - "fr": "대만" - }, - "is_active": false - }, - { - "code": "VN", - "name": { - "ko": "베트남", - "en": "Vietnam", - "fr": "베트남" - }, - "is_active": false - }, - { - "code": "TH", - "name": { - "ko": "태국", - "en": "Thailand", - "fr": "태국" - }, - "is_active": false - }, - { - "code": "MY", - "name": { - "ko": "말레이시아", - "en": "Malaysia", - "fr": "말레이시아" - }, - "is_active": false + "is_active": true } ], - "international_shipping_enabled": false, + "international_shipping_enabled": true, "free_shipping_threshold": 50000, "free_shipping_enabled": true, - "address_validation_enabled": false, - "address_api_provider": "kakao" + "...": "(2개 키 생략, 총 7개)" }, "order_settings": { "default_pg_provider": null, @@ -2946,20 +1348,15 @@ HTTP/1.1 200 "sort_order": 1, "is_active": false, "min_order_amount": 0, - "stock_deduction_timing": "payment_complete", - "mileage_deduction_timing": "payment_complete", - "_cached_name": { - "ko": "신용카드", - "en": "Credit Card", - "fr": "신용카드" - }, - "_cached_description": { - "ko": "신용카드로 안전하게 결제", - "en": "Pay securely with credit card", - "fr": "신용카드로 안전하게 결제" - }, - "_cached_icon": "credit-card", - "_cached_source": "builtin" + "...": "(10개 키 생략, 총 15개)" + }, + { + "id": "dbank", + "pg_provider": null, + "sort_order": 1, + "is_active": true, + "min_order_amount": 0, + "...": "(10개 키 생략, 총 15개)" }, { "id": "vbank", @@ -2967,147 +1364,25 @@ HTTP/1.1 200 "sort_order": 2, "is_active": false, "min_order_amount": 0, - "stock_deduction_timing": "payment_complete", - "mileage_deduction_timing": "order_placed", - "_cached_name": { - "ko": "가상계좌", - "en": "Virtual Account", - "fr": "가상계좌" - }, - "_cached_description": { - "ko": "가상계좌로 입금", - "en": "Pay via virtual account", - "fr": "가상계좌로 입금" - }, - "_cached_icon": "money-check", - "_cached_source": "builtin" - }, - { - "id": "dbank", - "pg_provider": null, - "sort_order": 3, - "is_active": true, - "min_order_amount": 0, - "stock_deduction_timing": "order_placed", - "mileage_deduction_timing": "order_placed", - "_cached_name": { - "ko": "무통장입금", - "en": "Bank Transfer", - "fr": "무통장입금" - }, - "_cached_description": { - "ko": "지정 계좌로 직접 입금", - "en": "Direct bank transfer", - "fr": "지정 계좌로 직접 입금" - }, - "_cached_icon": "building-columns", - "_cached_source": "builtin" - }, - { - "id": "bank", - "pg_provider": null, - "sort_order": 4, - "is_active": false, - "min_order_amount": 0, - "stock_deduction_timing": "payment_complete", - "mileage_deduction_timing": "payment_complete", - "_cached_name": { - "ko": "계좌이체", - "en": "Account Transfer", - "fr": "계좌이체" - }, - "_cached_description": { - "ko": "실시간 계좌이체", - "en": "Real-time bank transfer", - "fr": "실시간 계좌이체" - }, - "_cached_icon": "building-columns", - "_cached_source": "builtin" - }, - { - "id": "phone", - "pg_provider": null, - "sort_order": 5, - "is_active": false, - "min_order_amount": 0, - "stock_deduction_timing": "payment_complete", - "mileage_deduction_timing": "payment_complete", - "_cached_name": { - "ko": "휴대폰결제", - "en": "Mobile Payment", - "fr": "휴대폰결제" - }, - "_cached_description": { - "ko": "휴대폰 소액결제", - "en": "Mobile phone payment", - "fr": "휴대폰 소액결제" - }, - "_cached_icon": "mobile-screen-button", - "_cached_source": "builtin" + "...": "(10개 키 생략, 총 15개)" }, { "id": "point", "pg_provider": null, - "sort_order": 6, - "is_active": false, + "sort_order": 2, + "is_active": true, "min_order_amount": 0, - "stock_deduction_timing": "order_placed", - "mileage_deduction_timing": "payment_complete", - "_cached_name": { - "ko": "포인트결제", - "en": "Points", - "fr": "포인트결제" - }, - "_cached_description": { - "ko": "적립 포인트로 결제", - "en": "Pay with points", - "fr": "적립 포인트로 결제" - }, - "_cached_icon": "coins", - "_cached_source": "builtin" + "...": "(10개 키 생략, 총 15개)" }, { "id": "deposit", "pg_provider": null, - "sort_order": 7, - "is_active": false, + "sort_order": 3, + "is_active": true, "min_order_amount": 0, - "stock_deduction_timing": "order_placed", - "mileage_deduction_timing": "payment_complete", - "_cached_name": { - "ko": "예치금결제", - "en": "Store Credit", - "fr": "예치금결제" - }, - "_cached_description": { - "ko": "예치금으로 결제", - "en": "Pay with store credit", - "fr": "예치금으로 결제" - }, - "_cached_icon": "wallet", - "_cached_source": "builtin" + "...": "(10개 키 생략, 총 15개)" }, - { - "id": "free", - "pg_provider": null, - "sort_order": 8, - "is_active": false, - "min_order_amount": 0, - "stock_deduction_timing": "order_placed", - "mileage_deduction_timing": "payment_complete", - "_cached_name": { - "ko": "무료", - "en": "Free", - "fr": "무료" - }, - "_cached_description": { - "ko": "결제 없이 주문 완료", - "en": "Order without payment", - "fr": "결제 없이 주문 완료" - }, - "_cached_icon": "gift", - "_cached_source": "builtin" - } + "... (총 25건 중 5건 표시)" ], "banks": [], "bank_accounts": [ @@ -3120,16 +1395,7 @@ HTTP/1.1 200 } ], "auto_cancel_expired": true, - "auto_cancel_days": 3, - "cart_expiry_days": 30, - "stock_restore_on_cancel": true, - "cancellable_statuses": [ - "payment_complete" - ], - "confirmable_statuses": [ - "shipping", - "delivered" - ] + "...": "(5개 키 생략, 총 10개)" } } } @@ -3179,7 +1445,7 @@ HTTP/1.1 200 ```json { "success": true, - "message": "sirsoft-ecommerce::messages.settings.fetch_success", + "message": "설정을 조회했습니다.", "data": { "order_settings": { "default_pg_provider": null, @@ -3190,20 +1456,15 @@ HTTP/1.1 200 "sort_order": 1, "is_active": false, "min_order_amount": 0, - "stock_deduction_timing": "payment_complete", - "mileage_deduction_timing": "payment_complete", - "_cached_name": { - "ko": "신용카드", - "en": "Credit Card", - "fr": "신용카드" - }, - "_cached_description": { - "ko": "신용카드로 안전하게 결제", - "en": "Pay securely with credit card", - "fr": "신용카드로 안전하게 결제" - }, - "_cached_icon": "credit-card", - "_cached_source": "builtin" + "...": "(10개 키 생략, 총 15개)" + }, + { + "id": "dbank", + "pg_provider": null, + "sort_order": 1, + "is_active": true, + "min_order_amount": 0, + "...": "(10개 키 생략, 총 15개)" }, { "id": "vbank", @@ -3211,147 +1472,25 @@ HTTP/1.1 200 "sort_order": 2, "is_active": false, "min_order_amount": 0, - "stock_deduction_timing": "payment_complete", - "mileage_deduction_timing": "order_placed", - "_cached_name": { - "ko": "가상계좌", - "en": "Virtual Account", - "fr": "가상계좌" - }, - "_cached_description": { - "ko": "가상계좌로 입금", - "en": "Pay via virtual account", - "fr": "가상계좌로 입금" - }, - "_cached_icon": "money-check", - "_cached_source": "builtin" - }, - { - "id": "dbank", - "pg_provider": null, - "sort_order": 3, - "is_active": true, - "min_order_amount": 0, - "stock_deduction_timing": "order_placed", - "mileage_deduction_timing": "order_placed", - "_cached_name": { - "ko": "무통장입금", - "en": "Bank Transfer", - "fr": "무통장입금" - }, - "_cached_description": { - "ko": "지정 계좌로 직접 입금", - "en": "Direct bank transfer", - "fr": "지정 계좌로 직접 입금" - }, - "_cached_icon": "building-columns", - "_cached_source": "builtin" - }, - { - "id": "bank", - "pg_provider": null, - "sort_order": 4, - "is_active": false, - "min_order_amount": 0, - "stock_deduction_timing": "payment_complete", - "mileage_deduction_timing": "payment_complete", - "_cached_name": { - "ko": "계좌이체", - "en": "Account Transfer", - "fr": "계좌이체" - }, - "_cached_description": { - "ko": "실시간 계좌이체", - "en": "Real-time bank transfer", - "fr": "실시간 계좌이체" - }, - "_cached_icon": "building-columns", - "_cached_source": "builtin" - }, - { - "id": "phone", - "pg_provider": null, - "sort_order": 5, - "is_active": false, - "min_order_amount": 0, - "stock_deduction_timing": "payment_complete", - "mileage_deduction_timing": "payment_complete", - "_cached_name": { - "ko": "휴대폰결제", - "en": "Mobile Payment", - "fr": "휴대폰결제" - }, - "_cached_description": { - "ko": "휴대폰 소액결제", - "en": "Mobile phone payment", - "fr": "휴대폰 소액결제" - }, - "_cached_icon": "mobile-screen-button", - "_cached_source": "builtin" + "...": "(10개 키 생략, 총 15개)" }, { "id": "point", "pg_provider": null, - "sort_order": 6, - "is_active": false, + "sort_order": 2, + "is_active": true, "min_order_amount": 0, - "stock_deduction_timing": "order_placed", - "mileage_deduction_timing": "payment_complete", - "_cached_name": { - "ko": "포인트결제", - "en": "Points", - "fr": "포인트결제" - }, - "_cached_description": { - "ko": "적립 포인트로 결제", - "en": "Pay with points", - "fr": "적립 포인트로 결제" - }, - "_cached_icon": "coins", - "_cached_source": "builtin" + "...": "(10개 키 생략, 총 15개)" }, { "id": "deposit", "pg_provider": null, - "sort_order": 7, - "is_active": false, + "sort_order": 3, + "is_active": true, "min_order_amount": 0, - "stock_deduction_timing": "order_placed", - "mileage_deduction_timing": "payment_complete", - "_cached_name": { - "ko": "예치금결제", - "en": "Store Credit", - "fr": "예치금결제" - }, - "_cached_description": { - "ko": "예치금으로 결제", - "en": "Pay with store credit", - "fr": "예치금으로 결제" - }, - "_cached_icon": "wallet", - "_cached_source": "builtin" + "...": "(10개 키 생략, 총 15개)" }, - { - "id": "free", - "pg_provider": null, - "sort_order": 8, - "is_active": false, - "min_order_amount": 0, - "stock_deduction_timing": "order_placed", - "mileage_deduction_timing": "payment_complete", - "_cached_name": { - "ko": "무료", - "en": "Free", - "fr": "무료" - }, - "_cached_description": { - "ko": "결제 없이 주문 완료", - "en": "Order without payment", - "fr": "결제 없이 주문 완료" - }, - "_cached_icon": "gift", - "_cached_source": "builtin" - } + "... (총 25건 중 5건 표시)" ], "banks": [], "bank_accounts": [ @@ -3361,20 +1500,11 @@ HTTP/1.1 200 "account_holder": "", "is_active": false, "is_default": false, - "bank_name": "004" + "...": "(1개 키 생략, 총 6개)" } ], "auto_cancel_expired": true, - "auto_cancel_days": 3, - "cart_expiry_days": 30, - "stock_restore_on_cancel": true, - "cancellable_statuses": [ - "payment_complete" - ], - "confirmable_statuses": [ - "shipping", - "delivered" - ] + "...": "(5개 키 생략, 총 10개)" } } } @@ -3424,7 +1554,7 @@ HTTP/1.1 200 ```json { "success": true, - "message": "sirsoft-ecommerce::messages.settings.fetch_success", + "message": "설정을 조회했습니다.", "data": { "review_settings": { "write_deadline_days": 90, @@ -3479,7 +1609,7 @@ HTTP/1.1 200 ```json { "success": true, - "message": "sirsoft-ecommerce::messages.settings.fetch_success", + "message": "설정을 조회했습니다.", "data": { "shipping": { "default_country": "KR", @@ -3488,8 +1618,7 @@ HTTP/1.1 200 "code": "KR", "name": { "ko": "대한민국", - "en": "South Korea", - "fr": "대한민국" + "en": "South Korea" }, "is_active": true }, @@ -3497,85 +1626,12 @@ HTTP/1.1 200 "code": "US", "name": { "ko": "미국", - "en": "United States", - "fr": "미국" + "en": "United States" }, - "is_active": false - }, - { - "code": "JP", - "name": { - "ko": "일본", - "en": "Japan", - "fr": "일본" - }, - "is_active": false - }, - { - "code": "CN", - "name": { - "ko": "중국", - "en": "China", - "fr": "중국" - }, - "is_active": false - }, - { - "code": "SG", - "name": { - "ko": "싱가포르", - "en": "Singapore", - "fr": "싱가포르" - }, - "is_active": false - }, - { - "code": "HK", - "name": { - "ko": "홍콩", - "en": "Hong Kong", - "fr": "홍콩" - }, - "is_active": false - }, - { - "code": "TW", - "name": { - "ko": "대만", - "en": "Taiwan", - "fr": "대만" - }, - "is_active": false - }, - { - "code": "VN", - "name": { - "ko": "베트남", - "en": "Vietnam", - "fr": "베트남" - }, - "is_active": false - }, - { - "code": "TH", - "name": { - "ko": "태국", - "en": "Thailand", - "fr": "태국" - }, - "is_active": false - }, - { - "code": "MY", - "name": { - "ko": "말레이시아", - "en": "Malaysia", - "fr": "말레이시아" - }, - "is_active": false + "is_active": true } ], - "international_shipping_enabled": false, + "international_shipping_enabled": true, "free_shipping_threshold": 50000, "free_shipping_enabled": true, "address_validation_enabled": false, @@ -3624,3 +1680,5 @@ _대표 에러 없음 (공개 조회). @@ -183,7 +188,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -210,12 +215,14 @@ Content-Type: application/json **요청 파라미터** -_요청 파라미터 없음._ +| 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | +| --- | --- | --- | --- | --- | --- | +| type | query | string | 아니오 | max 50 | 유형 필터 (해당 유형의 항목만 조회) | **요청 예시** ```http -GET /api/modules/sirsoft-ecommerce/admin/shipping-carriers/active HTTP/1.1 +GET /api/modules/sirsoft-ecommerce/admin/shipping-carriers/active?type=%EC%98%88%EC%8B%9C%EA%B0%92 HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -242,13 +249,6 @@ HTTP/1.1 200 "success": true, "message": "배송사 목록을 조회했습니다.", "data": [ - { - "value": 13, - "label": "API 문서 샘플 배송사", - "code": "apidoc", - "type": "domestic", - "tracking_url": null - }, { "value": 1, "label": "CJ대한통운", @@ -278,56 +278,56 @@ HTTP/1.1 200 "tracking_url": "https://www.ilogen.com/web/personal/trace/{tracking_number}" }, { - "value": 5, + "value": 9, "label": "UPS", "code": "ups", "type": "international", "tracking_url": "https://www.ups.com/track?tracknum={tracking_number}" }, { - "value": 6, + "value": 5, "label": "EMS", "code": "ems", "type": "international", "tracking_url": "https://service.epost.go.kr/trace.RetrieveEmsRi498.postal?POST_CODE={tracking_number}" }, { - "value": 7, + "value": 6, "label": "DHL", "code": "dhl", "type": "international", "tracking_url": "https://www.dhl.com/kr-ko/home/tracking/tracking-express.html?submit=1&tracking-id={tracking_number}" }, { - "value": 8, + "value": 10, "label": "FedEx", "code": "fedex", "type": "international", "tracking_url": "https://www.fedex.com/fedextrack/?tracknumbers={tracking_number}" }, { - "value": 9, + "value": 11, "label": "SF Express", "code": "sf", "type": "international", "tracking_url": null }, { - "value": 10, + "value": 12, "label": "야마토운수", "code": "yamato", "type": "international", "tracking_url": null }, { - "value": 11, + "value": 13, "label": "사가와익스프레스", "code": "sagawa", "type": "international", "tracking_url": null }, { - "value": 12, + "value": 7, "label": "기타", "code": "other", "type": "domestic", @@ -343,6 +343,7 @@ HTTP/1.1 200 | --- | --- | --- | | 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 | | 403 | Forbidden | 요구 권한(`sirsoft-ecommerce.settings.read`)이 없는 경우 | +| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) | @@ -364,7 +365,7 @@ HTTP/1.1 200 **요청 예시** ```http -DELETE /api/modules/sirsoft-ecommerce/admin/shipping-carriers/13 HTTP/1.1 +DELETE /api/modules/sirsoft-ecommerce/admin/shipping-carriers/{id} HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -372,27 +373,11 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) -_단건 응답: `data` 객체의 필드._ - -| 필드 | 타입 | 실측 예시값 | 용도/설명 | -| --- | --- | --- | --- | -| carrier_id | integer | `13` | carrier 식별자 (연관 리소스 참조) | + **응답 예시** -```http -HTTP/1.1 200 -``` - -```json -{ - "success": true, - "message": "배송사가 삭제되었습니다.", - "data": { - "carrier_id": 13 - } -} -``` + **에러 응답** @@ -422,7 +407,7 @@ HTTP/1.1 200 **요청 예시** ```http -GET /api/modules/sirsoft-ecommerce/admin/shipping-carriers/13 HTTP/1.1 +GET /api/modules/sirsoft-ecommerce/admin/shipping-carriers/{id} HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -430,56 +415,11 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) -_단건 응답: `data` 객체의 필드._ - -| 필드 | 타입 | 실측 예시값 | 용도/설명 | -| --- | --- | --- | --- | -| id | integer | `13` | 기본 키 (내부 식별자) | -| code | string | `apidoc` | 고유 코드 (cj, fedex 등) | -| name | object | `{"ko":"API 문서 샘플 배송사","en":"API Doc Sample Carrier"}` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | -| localized_name | string | `API 문서 샘플 배송사` | `name` 의 현재 로케일 해석 값 (다국어 필드를 표시용 문자열로 해석) | -| type | string | `domestic` | 배송사 유형: domestic(국내), international(해외) | -| type_label | string | `국내` | `type` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | -| tracking_url | null | `null` | tracking URL | -| is_active | boolean | `true` | active 여부 | -| sort_order | integer | `0` | 표시 정렬 순서 값 (작을수록 우선) | -| created_at | string | `2026-07-08 10:44:49` | 생성 일시 | -| updated_at | string | `2026-07-08 10:44:49` | 최종 수정 일시 | -| abilities | object | `{"can_create":true,"can_update":true,"can_delete":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | + **응답 예시** -```http -HTTP/1.1 200 -``` - -```json -{ - "success": true, - "message": "배송사 정보를 조회했습니다.", - "data": { - "id": 13, - "code": "apidoc", - "name": { - "ko": "API 문서 샘플 배송사", - "en": "API Doc Sample Carrier" - }, - "localized_name": "API 문서 샘플 배송사", - "type": "domestic", - "type_label": "국내", - "tracking_url": null, - "is_active": true, - "sort_order": 0, - "created_at": "2026-07-08 10:44:49", - "updated_at": "2026-07-08 10:44:49", - "abilities": { - "can_create": true, - "can_update": true, - "can_delete": true - } - } -} -``` + **에러 응답** @@ -517,7 +457,7 @@ HTTP/1.1 200 **요청 예시** ```http -PUT /api/modules/sirsoft-ecommerce/admin/shipping-carriers/13 HTTP/1.1 +PUT /api/modules/sirsoft-ecommerce/admin/shipping-carriers/{id} HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -537,11 +477,11 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** - + **에러 응답** @@ -572,7 +512,7 @@ Content-Type: application/json **요청 예시** ```http -PATCH /api/modules/sirsoft-ecommerce/admin/shipping-carriers/13/toggle-status HTTP/1.1 +PATCH /api/modules/sirsoft-ecommerce/admin/shipping-carriers/{id}/toggle-status HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -580,56 +520,11 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) -_단건 응답: `data` 객체의 필드._ - -| 필드 | 타입 | 실측 예시값 | 용도/설명 | -| --- | --- | --- | --- | -| id | integer | `13` | 기본 키 (내부 식별자) | -| code | string | `apidoc` | 고유 코드 (cj, fedex 등) | -| name | object | `{"ko":"API 문서 샘플 배송사","en":"API Doc Sample Carrier"}` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | -| localized_name | string | `API 문서 샘플 배송사` | `name` 의 현재 로케일 해석 값 (다국어 필드를 표시용 문자열로 해석) | -| type | string | `domestic` | 배송사 유형: domestic(국내), international(해외) | -| type_label | string | `국내` | `type` 값의 사람이 읽는 라벨 (현지화/Enum 파생) | -| tracking_url | null | `null` | tracking URL | -| is_active | boolean | `false` | active 여부 | -| sort_order | integer | `0` | 표시 정렬 순서 값 (작을수록 우선) | -| created_at | string | `2026-07-08 10:44:49` | 생성 일시 | -| updated_at | string | `2026-07-08 15:00:34` | 최종 수정 일시 | -| abilities | object | `{"can_create":true,"can_update":true,"can_delete":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | + **응답 예시** -```http -HTTP/1.1 200 -``` - -```json -{ - "success": true, - "message": "sirsoft-ecommerce::messages.shipping_carriers.status_changed", - "data": { - "id": 13, - "code": "apidoc", - "name": { - "ko": "API 문서 샘플 배송사", - "en": "API Doc Sample Carrier" - }, - "localized_name": "API 문서 샘플 배송사", - "type": "domestic", - "type_label": "국내", - "tracking_url": null, - "is_active": false, - "sort_order": 0, - "created_at": "2026-07-08 10:44:49", - "updated_at": "2026-07-08 15:00:34", - "abilities": { - "can_create": true, - "can_update": true, - "can_delete": true - } - } -} -``` + **에러 응답** diff --git a/modules/_bundled/sirsoft-ecommerce/docs/api/shipping-country.md b/modules/_bundled/sirsoft-ecommerce/docs/api/shipping-country.md index cb406e36..d8157b9f 100644 --- a/modules/_bundled/sirsoft-ecommerce/docs/api/shipping-country.md +++ b/modules/_bundled/sirsoft-ecommerce/docs/api/shipping-country.md @@ -8,22 +8,14 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Shipping Country 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` --- -## 요청별 배송국가 해석 (ResolveShippingCountry 미들웨어) - -위 두 엔드포인트는 회원이 **저장해 둔** 선호 배송국가를 조회·저장한다. 이와 별개로, 상품 목록·장바구니·결제(`products/*`, `cart/*`, `checkout/*`)와 비회원 주문 생성(`user/orders`) 요청은 요청 시점의 배송국가를 해석하는 `ResolveShippingCountry` 미들웨어를 거친다. 이 미들웨어는 로그인 회원의 저장값·요청 파라미터·기본 배송국가를 순서대로 반영해 이후 배송비/배송가능 여부 계산의 기준 국가를 확정한다. - -이 미들웨어는 코어의 확장 미들웨어 self-gate(`Module::getMiddleware()` 의 `targets` 로 위 라우트명에만 정밀 타게팅)로 부착된다. 라우트 파일에서 직접 부착하던 이전 방식과 적용 대상·동작은 동일하다. 상세: [docs/backend/middleware.md "확장 미들웨어 선언 (self-gate)"](../../../../../docs/backend/middleware.md). - ---- - ### GET /api/modules/sirsoft-ecommerce/user/shipping-country diff --git a/modules/_bundled/sirsoft-ecommerce/docs/api/shipping-policies.md b/modules/_bundled/sirsoft-ecommerce/docs/api/shipping-policies.md index 61858fec..05589636 100644 --- a/modules/_bundled/sirsoft-ecommerce/docs/api/shipping-policies.md +++ b/modules/_bundled/sirsoft-ecommerce/docs/api/shipping-policies.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Shipping Policies 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` @@ -32,6 +32,7 @@ | charge_policies | query | array | 아니오 | — | 배송비 부과정책으로 필터 (free/fixed/conditional_free/range_*/api/per_* 등, 국가별 설정 매치) | | countries | query | array | 아니오 | — | 배송 국가 코드로 필터 (ISO 코드 배열, 해당 국가 설정을 가진 정책만) | | is_active | query | string | 아니오 | ``, `true`, `false` | 활성 여부 (true 활성 / false 비활성) | +| with_country_settings | query | boolean | 아니오 | 기본 `false` | 국가별 설정을 **전체 컬럼**으로 포함할지. 기본값에서도 `country_settings` 는 내려가지만 목록 표시용 필드만 담깁니다(구간 설정·도서산간 설정·계산 API 설정 제외). 편집 폼처럼 전체 값이 필요한 호출자만 켜세요 | | sort_by | query | string | 아니오 | `id`, `name`, `is_active`, `sort_order`, `created_at`, `updated_at` | 정렬 기준 필드명 | | sort_order | query | string | 아니오 | `asc`, `desc` | 정렬 방향 (asc 오름차순 / desc 내림차순) | | per_page | query | integer | 아니오 | min 10, max 100 | 페이지당 항목 수 | @@ -39,10 +40,16 @@ > 이 엔드포인트는 확장이 파라미터를 추가할 수 있습니다 (`sirsoft-ecommerce.shipping_policy.list_validation_rules`, `sirsoft-ecommerce.shipping_policy.list_validation_messages`). +**목록은 경량 표현입니다.** + +국가별 설정(`country_settings[]`)은 목록 화면이 그리는 필드만 담습니다 — 국가 코드, 배송방법, 부과정책, 배송비, 무료배송 기준액, 도서산간 사용 여부, 활성 여부. 구간 설정(`ranges`), 도서산간 상세(`extra_fee_settings`), 계산 API 설정(`api_*`)은 정책 하나에 국가 수만큼 곱해지는 중첩 데이터라 목록에서 제외합니다. 전체 값이 필요하면 `with_country_settings=1` 을 쓰거나 단건 조회(`GET .../admin/shipping-policies/{id}`)를 이용하세요. + +비활성 국가 설정도 함께 내려갑니다(`is_active: false`) — 목록이 비활성 배지를 그리기 때문입니다. 다만 배송비 요약(`fee_summary`)과 국가 표시(`countries_display`)는 활성 설정만 세므로, 조회 경로와 무관하게 같은 값이 나옵니다. + **요청 예시** ```http -GET /api/modules/sirsoft-ecommerce/admin/shipping-policies?search=%EC%98%88%EC%8B%9C%EA%B0%92&shipping_methods=%EC%98%88%EC%8B%9C%EA%B0%92&charge_policies=%EC%98%88%EC%8B%9C%EA%B0%92&countries=KR&is_active=%2C%20&sort_by=id&sort_order=asc&per_page=1&page=1 HTTP/1.1 +GET /api/modules/sirsoft-ecommerce/admin/shipping-policies?search=%EC%98%88%EC%8B%9C%EA%B0%92&shipping_methods=%EC%98%88%EC%8B%9C%EA%B0%92&charge_policies=%EC%98%88%EC%8B%9C%EA%B0%92&countries=KR&is_active=%2C%20&with_country_settings=1&sort_by=id&sort_order=asc&per_page=1&page=1 HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -54,18 +61,15 @@ _목록 응답: `data.data[]` 배열 항목의 필드 + `data.pagination`._ | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| number | integer | `17` | 목록에서의 순번 (페이지네이션 반영 행 번호 — HasRowNumber 파생) | -| id | integer | `47` | 기본 키 (내부 식별자) | -| name | object | `{"ko":"API 문서 샘플 배송정책","en":"API Doc Sample Shipping Poli…` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | -| name_localized | string | `API 문서 샘플 배송정책` | `name` 의 현재 로케일 해석 값 (다국어 필드를 표시용 문자열로 해석) | -| country_settings | array | `[]` | 국가별 배송 설정 목록 (countrySettings 관계 로드 시 각 국가의 배송방식·부과정책·배송비 상세) | -| fee_summary | string | `` | 활성 국가별 설정을 종합한 배송비 요약 텍스트 (예: `KR: 3000원 \| US: $20`, 활성 설정 없으면 빈 문자열) | -| countries_display | string | `` | 활성 배송 국가를 국기 이모지로 표시한 문자열 (최대 3개 노출, 초과분은 `+N` 축약) | +| number | integer | `15` | 목록에서의 순번 (페이지네이션 반영 행 번호 — HasRowNumber 파생) | +| id | integer | `444` | 기본 키 (내부 식별자) | +| name | object | `{"ko":"국내 무료배송","en":"Domestic Free Shipping"}` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | +| name_localized | string | `국내 무료배송` | `name` 의 현재 로케일 해석 값 (다국어 필드를 표시용 문자열로 해석) | | is_active | boolean | `true` | active 여부 | -| is_default | boolean | `false` | default 여부 | -| sort_order | integer | `0` | 표시 정렬 순서 값 (작을수록 우선) | -| created_at | string | `2026-07-07 14:47:31` | 생성 일시 | -| updated_at | string | `2026-07-07 14:47:31` | 최종 수정 일시 | +| is_default | boolean | `true` | default 여부 | +| sort_order | integer | `1` | 표시 정렬 순서 값 (작을수록 우선) | +| created_at | string | `2026-07-30 23:35:47` | 생성 일시 | +| updated_at | string | `2026-07-30 23:35:47` | 최종 수정 일시 | | abilities | object | `{"can_create":true,"can_update":true,"can_delete":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | **응답 예시** @@ -81,27 +85,44 @@ HTTP/1.1 200 "data": { "data": [ { - "number": 1, - "id": 1, + "number": 15, + "id": 444, "name": { - "ko": "API 문서 샘플 배송정책", - "en": "API Doc Sample Shipping Policy" + "ko": "국내 무료배송", + "en": "Domestic Free Shipping" }, - "name_localized": "API 문서 샘플 배송정책", - "country_settings": [], - "fee_summary": "", - "countries_display": "", + "name_localized": "국내 무료배송", "is_active": true, - "is_default": false, - "sort_order": 0, - "created_at": "2026-07-08 10:44:49", - "updated_at": "2026-07-08 10:44:49", + "is_default": true, + "sort_order": 1, + "created_at": "2026-07-30 23:35:47", + "updated_at": "2026-07-30 23:35:47", "abilities": { "can_create": true, "can_update": true, "can_delete": true } - } + }, + { + "number": 14, + "id": 445, + "name": { + "ko": "국내 택배 (고정)", + "en": "Domestic Parcel (Fixed)" + }, + "name_localized": "국내 택배 (고정)", + "is_active": true, + "is_default": false, + "sort_order": 2, + "created_at": "2026-07-30 23:35:47", + "updated_at": "2026-07-30 23:35:47", + "abilities": { + "can_create": true, + "can_update": true, + "can_delete": true + } + }, + "... (총 15건 중 2건 표시)" ], "abilities": { "can_create": true, @@ -109,19 +130,38 @@ HTTP/1.1 200 "can_delete": true }, "statistics": { - "total": 1, - "active": 1, - "inactive": 0, - "shipping_method": [], - "charge_policy": [] + "total": 15, + "active": 14, + "inactive": 1, + "shipping_method": { + "direct": 2, + "parcel": 12, + "quick": 1 + }, + "charge_policy": { + "api": 1, + "conditional_free": 1, + "fixed": 2, + "free": 1, + "per_amount": 1, + "per_quantity": 1, + "per_volume": 1, + "per_volume_weight": 1, + "per_weight": 1, + "range_amount": 1, + "range_quantity": 1, + "range_volume": 1, + "range_volume_weight": 1, + "range_weight": 2 + } }, "pagination": { "current_page": 1, "last_page": 1, "per_page": 25, - "total": 1, + "total": 15, "from": 1, - "to": 1, + "to": 15, "has_more_pages": false } } @@ -183,7 +223,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -225,11 +265,11 @@ Authorization: Bearer {YOUR_TOKEN} | 필드 | 타입 | 실측 예시값 | 용도/설명 | | --- | --- | --- | --- | -| value | integer | `46` | 배송정책 ID (Select 옵션의 value) | -| label | string | `sdfsf` | 표시용 라벨 | +| value | integer | `444` | 배송정책 ID (Select 옵션의 value) | +| label | string | `국내 무료배송` | 표시용 라벨 | | countries_display | string | `🇰🇷` | 활성 배송 국가를 국기 이모지로 표시한 문자열 (최대 3개, 초과분 `+N`) | -| fee_summary | string | `KR: 외부 API 연동 (실시간 계산)` | 국가별 배송비 요약 텍스트 (`country_code: fee` 형태를 ` \| ` 로 결합) | -| is_default | boolean | `false` | default 여부 | +| fee_summary | string | `KR: 무료배송` | 국가별 배송비 요약 텍스트 (`country_code: fee` 형태를 ` \| ` 로 결합) | +| is_default | boolean | `true` | default 여부 | **응답 예시** @@ -240,13 +280,104 @@ HTTP/1.1 200 ```json { "success": true, - "message": "sirsoft-ecommerce::messages.shipping_policy.active_list_retrieved", + "message": "사용 중인 배송정책 목록을 조회했습니다.", "data": [ { - "value": 1, - "label": "API 문서 샘플 배송정책", - "countries_display": "", - "fee_summary": "", + "value": 444, + "label": "국내 무료배송", + "countries_display": "🇰🇷", + "fee_summary": "KR: 무료배송", + "is_default": true + }, + { + "value": 445, + "label": "국내 택배 (고정)", + "countries_display": "🇰🇷", + "fee_summary": "KR: 배송비: ¥3,000", + "is_default": false + }, + { + "value": 446, + "label": "조건부 무료배송 (5만원 이상)", + "countries_display": "🇰🇷", + "fee_summary": "KR: ¥50,000 미만 ¥2,500 / ¥50,000 이상 무료", + "is_default": false + }, + { + "value": 447, + "label": "금액별 구간 배송비", + "countries_display": "🇰🇷", + "fee_summary": "KR: ~10000원: ¥5,000 / 10000~30000원: ¥3,000 / 30000~50000원: ¥2,000 / 50000~100000원: ¥1,000 / 100000원~: ¥0", + "is_default": false + }, + { + "value": 448, + "label": "수량별 구간 배송비", + "countries_display": "🇰🇷", + "fee_summary": "KR: 1~5개: ¥3,000 / 6개~: ¥5,000", + "is_default": false + }, + { + "value": 449, + "label": "무게별 구간 배송비", + "countries_display": "🇰🇷", + "fee_summary": "KR: ~2kg: ¥3,000 / 2~5kg: ¥4,000 / 5~10kg: ¥6,000 / 10kg~: ¥8,000", + "is_default": false + }, + { + "value": 450, + "label": "부피별 구간 배송비", + "countries_display": "🇰🇷", + "fee_summary": "KR: ~50L: ¥5,000 / 50~100L: ¥10,000 / 100L~: ¥20,000", + "is_default": false + }, + { + "value": 451, + "label": "부피무게 구간 배송비", + "countries_display": "🇰🇷", + "fee_summary": "KR: ~5kg: ¥3,500 / 5~10kg: ¥5,000 / 10~20kg: ¥8,000 / 20kg~: ¥12,000", + "is_default": false + }, + { + "value": 452, + "label": "해외배송 (DHL)", + "countries_display": "🇨🇳🇯🇵🇺🇸", + "fee_summary": "CN: 외부 API 연동 (실시간 계산) | JP: 외부 API 연동 (실시간 계산) | US: 외부 API 연동 (실시간 계산)", + "is_default": false + }, + { + "value": 454, + "label": "수량당 배송비 (3개당)", + "countries_display": "🇰🇷", + "fee_summary": "KR: 3개당 ¥3,000", + "is_default": false + }, + { + "value": 455, + "label": "무게당 배송비 (1kg당)", + "countries_display": "🇰🇷", + "fee_summary": "KR: 1kg당 ¥1,000", + "is_default": false + }, + { + "value": 456, + "label": "부피당 배송비 (10L당)", + "countries_display": "🇰🇷", + "fee_summary": "KR: 10L당 ¥2,000", + "is_default": false + }, + { + "value": 457, + "label": "국내외 복합 배송 (부피무게당)", + "countries_display": "🇰🇷🇺🇸", + "fee_summary": "KR: 5kg당 ¥3,000 | US: ~2kg: ¥25 / 2~5kg: ¥40 / 5kg~: ¥60", + "is_default": false + }, + { + "value": 458, + "label": "금액당 배송비 (1만원당)", + "countries_display": "🇰🇷", + "fee_summary": "KR: 10,000당 ¥500", "is_default": false } ] @@ -290,7 +421,7 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - + **응답 예시** @@ -343,7 +474,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -402,7 +533,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -436,7 +567,7 @@ Content-Type: application/json **요청 예시** ```http -DELETE /api/modules/sirsoft-ecommerce/admin/shipping-policies/1 HTTP/1.1 +DELETE /api/modules/sirsoft-ecommerce/admin/shipping-policies/{id} HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -444,23 +575,11 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - - - + **응답 예시** -```http -HTTP/1.1 200 -``` - -```json -{ - "success": true, - "message": "배송정책이 삭제되었습니다.", - "data": null -} -``` + **에러 응답** @@ -490,7 +609,7 @@ HTTP/1.1 200 **요청 예시** ```http -GET /api/modules/sirsoft-ecommerce/admin/shipping-policies/1 HTTP/1.1 +GET /api/modules/sirsoft-ecommerce/admin/shipping-policies/{id} HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -498,56 +617,11 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) -_단건 응답: `data` 객체의 필드._ - -| 필드 | 타입 | 실측 예시값 | 용도/설명 | -| --- | --- | --- | --- | -| id | integer | `1` | 기본 키 (내부 식별자) | -| name | object | `{"ko":"API 문서 샘플 배송정책","en":"API Doc Sample Shipping Poli…` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | -| name_localized | string | `API 문서 샘플 배송정책` | `name` 의 현재 로케일 해석 값 (다국어 필드를 표시용 문자열로 해석) | -| country_settings | array | `[]` | 국가별 배송 설정 목록 (countrySettings 관계 로드 시 각 국가의 배송방식·부과정책·배송비 상세) | -| fee_summary | string | `` | 활성 국가별 설정을 종합한 배송비 요약 텍스트 (예: `KR: 3000원 \| US: $20`, 활성 설정 없으면 빈 문자열) | -| countries_display | string | `` | 활성 배송 국가를 국기 이모지로 표시한 문자열 (최대 3개 노출, 초과분은 `+N` 축약) | -| is_active | boolean | `true` | active 여부 | -| is_default | boolean | `false` | default 여부 | -| sort_order | integer | `0` | 표시 정렬 순서 값 (작을수록 우선) | -| created_at | string | `2026-07-08 10:44:49` | 생성 일시 | -| updated_at | string | `2026-07-08 10:44:49` | 최종 수정 일시 | -| abilities | object | `{"can_create":true,"can_update":true,"can_delete":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | + **응답 예시** -```http -HTTP/1.1 200 -``` - -```json -{ - "success": true, - "message": "배송정책 정보를 조회했습니다.", - "data": { - "id": 1, - "name": { - "ko": "API 문서 샘플 배송정책", - "en": "API Doc Sample Shipping Policy" - }, - "name_localized": "API 문서 샘플 배송정책", - "country_settings": [], - "fee_summary": "", - "countries_display": "", - "is_active": true, - "is_default": false, - "sort_order": 0, - "created_at": "2026-07-08 10:44:49", - "updated_at": "2026-07-08 10:44:49", - "abilities": { - "can_create": true, - "can_update": true, - "can_delete": true - } - } -} -``` + **에러 응답** @@ -584,7 +658,7 @@ HTTP/1.1 200 **요청 예시** ```http -PUT /api/modules/sirsoft-ecommerce/admin/shipping-policies/1 HTTP/1.1 +PUT /api/modules/sirsoft-ecommerce/admin/shipping-policies/{id} HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -605,11 +679,11 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** - + **에러 응답** @@ -640,7 +714,7 @@ Content-Type: application/json **요청 예시** ```http -PATCH /api/modules/sirsoft-ecommerce/admin/shipping-policies/1/set-default HTTP/1.1 +PATCH /api/modules/sirsoft-ecommerce/admin/shipping-policies/{id}/set-default HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -648,54 +722,11 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) -_단건 응답: `data` 객체의 필드._ - -| 필드 | 타입 | 실측 예시값 | 용도/설명 | -| --- | --- | --- | --- | -| id | integer | `1` | 기본 키 (내부 식별자) | -| name | object | `{"ko":"API 문서 샘플 배송정책","en":"API Doc Sample Shipping Poli…` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | -| name_localized | string | `API 문서 샘플 배송정책` | `name` 의 현재 로케일 해석 값 (다국어 필드를 표시용 문자열로 해석) | -| fee_summary | string | `` | 활성 국가별 설정을 종합한 배송비 요약 텍스트 (예: `KR: 3000원 \| US: $20`, 활성 설정 없으면 빈 문자열) | -| countries_display | string | `` | 활성 배송 국가를 국기 이모지로 표시한 문자열 (최대 3개 노출, 초과분은 `+N` 축약) | -| is_active | boolean | `true` | active 여부 | -| is_default | boolean | `true` | default 여부 | -| sort_order | integer | `0` | 표시 정렬 순서 값 (작을수록 우선) | -| created_at | string | `2026-07-08 10:44:49` | 생성 일시 | -| updated_at | string | `2026-07-08 15:00:35` | 최종 수정 일시 | -| abilities | object | `{"can_create":true,"can_update":true,"can_delete":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | + **응답 예시** -```http -HTTP/1.1 200 -``` - -```json -{ - "success": true, - "message": "기본 배송정책이 설정되었습니다.", - "data": { - "id": 1, - "name": { - "ko": "API 문서 샘플 배송정책", - "en": "API Doc Sample Shipping Policy" - }, - "name_localized": "API 문서 샘플 배송정책", - "fee_summary": "", - "countries_display": "", - "is_active": true, - "is_default": true, - "sort_order": 0, - "created_at": "2026-07-08 10:44:49", - "updated_at": "2026-07-08 15:00:35", - "abilities": { - "can_create": true, - "can_update": true, - "can_delete": true - } - } -} -``` + **에러 응답** @@ -725,7 +756,7 @@ HTTP/1.1 200 **요청 예시** ```http -PATCH /api/modules/sirsoft-ecommerce/admin/shipping-policies/1/toggle-active HTTP/1.1 +PATCH /api/modules/sirsoft-ecommerce/admin/shipping-policies/{id}/toggle-active HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -733,54 +764,11 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) -_단건 응답: `data` 객체의 필드._ - -| 필드 | 타입 | 실측 예시값 | 용도/설명 | -| --- | --- | --- | --- | -| id | integer | `1` | 기본 키 (내부 식별자) | -| name | object | `{"ko":"API 문서 샘플 배송정책","en":"API Doc Sample Shipping Poli…` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | -| name_localized | string | `API 문서 샘플 배송정책` | `name` 의 현재 로케일 해석 값 (다국어 필드를 표시용 문자열로 해석) | -| fee_summary | string | `` | 활성 국가별 설정을 종합한 배송비 요약 텍스트 (예: `KR: 3000원 \| US: $20`, 활성 설정 없으면 빈 문자열) | -| countries_display | string | `` | 활성 배송 국가를 국기 이모지로 표시한 문자열 (최대 3개 노출, 초과분은 `+N` 축약) | -| is_active | boolean | `false` | active 여부 | -| is_default | boolean | `false` | default 여부 | -| sort_order | integer | `0` | 표시 정렬 순서 값 (작을수록 우선) | -| created_at | string | `2026-07-08 10:44:49` | 생성 일시 | -| updated_at | string | `2026-07-08 15:00:35` | 최종 수정 일시 | -| abilities | object | `{"can_create":true,"can_update":true,"can_delete":true}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | + **응답 예시** -```http -HTTP/1.1 200 -``` - -```json -{ - "success": true, - "message": "배송정책 사용여부가 변경되었습니다.", - "data": { - "id": 1, - "name": { - "ko": "API 문서 샘플 배송정책", - "en": "API Doc Sample Shipping Policy" - }, - "name_localized": "API 문서 샘플 배송정책", - "fee_summary": "", - "countries_display": "", - "is_active": false, - "is_default": false, - "sort_order": 0, - "created_at": "2026-07-08 10:44:49", - "updated_at": "2026-07-08 15:00:35", - "abilities": { - "can_create": true, - "can_update": true, - "can_delete": true - } - } -} -``` + **에러 응답** diff --git a/modules/_bundled/sirsoft-ecommerce/docs/api/users.md b/modules/_bundled/sirsoft-ecommerce/docs/api/users.md index 70877bc8..a0ba9d26 100644 --- a/modules/_bundled/sirsoft-ecommerce/docs/api/users.md +++ b/modules/_bundled/sirsoft-ecommerce/docs/api/users.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Users 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` @@ -33,7 +33,7 @@ **요청 예시** ```http -PATCH /api/modules/sirsoft-ecommerce/admin/users/a234c2b1-cde8-437f-b28b-23323be2b98d/currency HTTP/1.1 +PATCH /api/modules/sirsoft-ecommerce/admin/users/a26219fc-94a0-4f63-9404-04c2a6ac99e4/currency HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -46,7 +46,7 @@ Content-Type: application/json **응답 필드** (`data` 내부) - + **응답 예시** @@ -82,7 +82,7 @@ Content-Type: application/json **요청 예시** ```http -PATCH /api/modules/sirsoft-ecommerce/admin/users/a234c2b1-cde8-437f-b28b-23323be2b98d/shipping-country HTTP/1.1 +PATCH /api/modules/sirsoft-ecommerce/admin/users/a26219fc-94a0-4f63-9404-04c2a6ac99e4/shipping-country HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} diff --git a/modules/_bundled/sirsoft-ecommerce/docs/api/wishlist.md b/modules/_bundled/sirsoft-ecommerce/docs/api/wishlist.md index 8313fba1..5f9ad1ff 100644 --- a/modules/_bundled/sirsoft-ecommerce/docs/api/wishlist.md +++ b/modules/_bundled/sirsoft-ecommerce/docs/api/wishlist.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Wishlist 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` @@ -25,12 +25,15 @@ **요청 파라미터** -_요청 파라미터 없음._ +| 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | +| --- | --- | --- | --- | --- | --- | +| page | query | integer | 아니오 | min 1 | 조회할 페이지 번호 (1부터 시작) | +| per_page | query | integer | 아니오 | min 1, max 100 | 페이지당 항목 수 | **요청 예시** ```http -GET /api/modules/sirsoft-ecommerce/wishlist HTTP/1.1 +GET /api/modules/sirsoft-ecommerce/wishlist?page=1&per_page=1 HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} @@ -40,7 +43,12 @@ Authorization: Bearer {YOUR_TOKEN} _목록 응답: `data.data[]` 배열 항목의 필드 + `data.pagination`._ - +| 필드 | 타입 | 실측 예시값 | 용도/설명 | +| --- | --- | --- | --- | +| id | integer | `2` | 기본 키 (내부 식별자) | +| product_id | integer | `1732` | product 식별자 (연관 리소스 참조) | +| created_at | string | `2026-07-31T12:45:03.000000Z` | 생성 일시 | +| product | object | `{"id":1732,"name":{"ko":"면 손수건 3매입 #1","en":"Cotton Handk…` | | **응답 예시** @@ -53,15 +61,48 @@ HTTP/1.1 200 "success": true, "message": "찜 목록을 불러왔습니다.", "data": { - "data": [], + "data": [ + { + "id": 2, + "product_id": 1732, + "created_at": "2026-07-31T12:45:03.000000Z", + "product": { + "id": 1732, + "name": { + "ko": "면 손수건 3매입 #1", + "en": "Cotton Handkerchief 3pcs #1" + }, + "name_localized": "면 손수건 3매입 #1", + "product_code": "CJTFHBL8SLRQ8ILM", + "sku": "HK-0001", + "...": "(32개 키 생략, 총 37개)" + } + }, + { + "id": 1, + "product_id": 1733, + "created_at": "2026-07-31T12:44:43.000000Z", + "product": { + "id": 1733, + "name": { + "ko": "기본 양말 5족 #2", + "en": "Basic Socks 5 Pairs #2" + }, + "name_localized": "기본 양말 5족 #2", + "product_code": "S0SO3A6SJFYLAKSF", + "sku": "SK-0002", + "...": "(32개 키 생략, 총 37개)" + } + }, + "... (총 25건 중 2건 표시)" + ], "pagination": { "current_page": 1, - "last_page": 1, + "last_page": 2, "per_page": 25, - "total": 0, - "from": null, - "to": null, - "has_more_pages": false + "total": 45, + "from": 1, + "...": "(2개 키 생략, 총 7개)" } } } @@ -72,13 +113,13 @@ HTTP/1.1 200 | 상태코드 | 의미 | 발생 조건 | | --- | --- | --- | | 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 | -| 422 | Validation Error | `page` < 1, `per_page` 가 1~100 범위를 벗어나거나 정수가 아닌 경우 | +| 422 | Unprocessable Entity | `page` < 1, `per_page` 가 1~100 범위를 벗어나거나 정수가 아닌 경우 | **요청 파라미터** -| 이름 | 타입 | 필수 | 기본값 | 설명 | +| 이름 | 타입 | 필수 | 기본값 | 용도 | | --- | --- | :---: | --- | --- | | `page` | integer | - | 1 | 페이지 번호 (최소 1) | | `per_page` | integer | - | 20 | 페이지당 건수 (1~100). 범위를 벗어나면 422 | @@ -116,27 +157,11 @@ Content-Type: application/json **응답 필드** (`data` 내부) -_단건 응답: `data` 객체의 필드._ - -| 필드 | 타입 | 실측 예시값 | 용도/설명 | -| --- | --- | --- | --- | -| added | boolean | `true` | 토글 후 찜 상태 (true 이면 찜 목록에 추가됨 / false 이면 제거됨) | + **응답 예시** -```http -HTTP/1.1 200 -``` - -```json -{ - "success": true, - "message": "상품이 찜 목록에 추가되었습니다.", - "data": { - "added": true - } -} -``` + **에러 응답** @@ -173,7 +198,7 @@ Authorization: Bearer {YOUR_TOKEN} **응답 필드** (`data` 내부) - + **응답 예시** diff --git a/modules/_bundled/sirsoft-ecommerce/resources/js/__tests__/handlers/bulkUpdateHandlers.test.ts b/modules/_bundled/sirsoft-ecommerce/resources/js/__tests__/handlers/bulkUpdateHandlers.test.ts index 308a7acb..e4db4143 100644 --- a/modules/_bundled/sirsoft-ecommerce/resources/js/__tests__/handlers/bulkUpdateHandlers.test.ts +++ b/modules/_bundled/sirsoft-ecommerce/resources/js/__tests__/handlers/bulkUpdateHandlers.test.ts @@ -14,6 +14,7 @@ import { describe, it, expect, beforeEach, vi } from 'vitest'; import { buildConfirmDataHandler, bulkUpdateHandler } from '../../handlers/bulkUpdateHandlers'; import { updateOptionFieldHandler } from '../../handlers/updateOptionField'; import { updateProductFieldHandler } from '../../handlers/updateProductField'; +import { __resetExpandedOptionsState } from '../../handlers/expandedOptionsHandlers'; // G7Core mock let mockLocalState: Record = {}; @@ -88,6 +89,7 @@ const mockG7Core = { }, api: { patch: vi.fn(), + get: vi.fn(), }, createLogger: () => ({ log: vi.fn(), @@ -144,6 +146,9 @@ beforeEach(() => { mockGlobalState = {}; mockDataSources = {}; vi.clearAllMocks(); + __resetExpandedOptionsState(); + mockG7Core.dataSource.refetch.mockResolvedValue(undefined); + mockG7Core.api.get.mockResolvedValue({ data: { options: {}, product_ids: [] } }); (window as any).G7Core = mockG7Core; }); @@ -698,3 +703,192 @@ describe('bulkUpdateHandler - 실패 시 서버 에러 노출', () => { expect(mockG7Core.toast.error).toHaveBeenCalledWith('Network Error'); }); }); + +// ============================================================================ +// 목록 옵션 지연 로딩과의 연계 +// ============================================================================ + +/** + * 옵션 2개짜리 상품을 만듭니다. + * + * @param id 상품 ID + * @param expanded true = 목록에서 펼쳐 옵션이 로드된 상태, false = 미펼침(옵션 미로드) + */ +function makeProduct(id: number, expanded: boolean) { + const base: Record = { + id, + name: { ko: `상품${id}`, en: `Product ${id}` }, + sales_status: 'on_sale', + display_status: 'visible', + list_price: 10000, + selling_price: 8000, + stock_quantity: 10, + // 비활성 포함 전체 옵션 수 — 서버가 전개하는 모집단과 같다 + options_total_count: 2, + options_count: 1, + }; + + if (expanded) { + base.options = [ + { id: id * 10 + 1, option_name: { ko: 'A' }, option_name_localized: 'A', stock_quantity: 5, is_active: true }, + { id: id * 10 + 2, option_name: { ko: 'B' }, option_name_localized: 'B', stock_quantity: 5, is_active: false }, + ]; + } + + return base; +} + +describe('buildConfirmData - 미펼침 상품 옵션 집계', () => { + /** 재고 일괄 변경 조건을 건 상태로 확인 데이터를 만듭니다. */ + function buildWith(products: any[]) { + mockDataSources.products = { data: { data: products } }; + mockLocalState = { selectedItems: products.map((p) => p.id) }; + mockGlobalState = { + bulkSelectedItems: products.map((p) => p.id), + bulkStockCondition: '+10개', + bulkStockMethod: 'increase', + bulkStockValue: 10, + }; + + buildConfirmDataHandler({ handler: 'sirsoft-ecommerce.buildConfirmData' }, mockContext); + + return mockGlobalState.bulkConfirmData; + } + + // @scenario selection=product_header_all,target=stock,source=global_only + // @effects summary_count_matches_regardless_of_expansion + it('전부 펼친 경우와 전부 미펼침인 경우의 optionCount 가 같다', () => { + const expanded = buildWith([makeProduct(1, true), makeProduct(2, true)]); + const expandedCount = expanded.summary.optionCount; + + // 상태 초기화 후 미펼침으로 동일 구성 재계산 + mockGlobalState = {}; + const collapsed = buildWith([makeProduct(1, false), makeProduct(2, false)]); + + expect(expandedCount).toBe(4); + expect(collapsed.summary.optionCount).toBe(4); + }); + + // @scenario selection=product_header_all,target=price,source=global_only + // @effects aggregate_entry_for_unloaded_options + it('미펼침 상품은 집계 엔트리 1건으로 표현된다', () => { + const data = buildWith([makeProduct(1, false)]); + + expect(data.options).toHaveLength(1); + expect(data.options[0].aggregate).toBe(true); + expect(data.options[0].optionCount).toBe(2); + expect(data.options[0].productId).toBe(1); + // 집계 엔트리도 무엇이 바뀌는지 보여준다 + expect(data.options[0].changes).toContain('재고'); + }); + + it('펼침 + 미펼침 혼합에서 중복 없이 합산된다', () => { + const data = buildWith([makeProduct(1, true), makeProduct(2, false)]); + + // 배열은 열거 2건 + 집계 1건 = 3칸이지만 수치는 4건 + expect(data.options).toHaveLength(3); + expect(data.summary.optionCount).toBe(4); + }); + + it('집계는 비활성 포함 전체 수(options_total_count)를 센다', () => { + const product = makeProduct(1, false); + product.options_count = 1; // 활성만 1건 + product.options_total_count = 5; // 비활성 포함 5건 + + const data = buildWith([product]); + + expect(data.summary.optionCount).toBe(5); + }); + + it('옵션 일괄 조건이 없으면 미펼침 상품에 집계 엔트리를 만들지 않는다', () => { + mockDataSources.products = { data: { data: [makeProduct(1, false)] } }; + mockLocalState = { selectedItems: [1] }; + mockGlobalState = { + bulkSelectedItems: [1], + // 상품 상태만 변경 — 옵션에는 적용되지 않는다 + bulkSalesStatus: 'suspended', + }; + + buildConfirmDataHandler({ handler: 'sirsoft-ecommerce.buildConfirmData' }, mockContext); + + expect(mockGlobalState.bulkConfirmData.options).toHaveLength(0); + expect(mockGlobalState.bulkConfirmData.summary.optionCount).toBe(0); + }); +}); + +describe('bulkUpdate - 저장 후 펼친 행 옵션 재로드', () => { + it('저장 성공 시 목록 리페치 후 펼친 행의 옵션을 다시 가져온다', async () => { + mockDataSources.products = { data: { data: [makeProduct(1, true)] } }; + mockLocalState = { + selectedItems: [1], + expandedRows: [1], + modifiedOptionIds: [], + modifiedOptionFields: {}, + }; + mockGlobalState = { bulkSelectedItems: [1], bulkSalesStatus: 'suspended' }; + + mockG7Core.api.patch.mockResolvedValueOnce({ data: { products_updated: 1, options_updated: 2 } }); + mockG7Core.api.get.mockResolvedValueOnce({ + data: { options: { '1': [{ id: 11 }, { id: 12 }] }, product_ids: [1] }, + }); + + bulkUpdateHandler({ handler: 'sirsoft-ecommerce.bulkUpdate' }, mockContext); + + await vi.waitFor(() => { + expect(mockG7Core.dataSource.refetch).toHaveBeenCalledWith('products'); + }); + + await vi.waitFor(() => { + expect(mockG7Core.api.get).toHaveBeenCalledWith( + '/api/modules/sirsoft-ecommerce/admin/products/options', + { params: { product_ids: [1] } } + ); + }); + }); + + it('펼친 행이 없으면 옵션 재로드를 하지 않는다', async () => { + mockDataSources.products = { data: { data: [makeProduct(1, false)] } }; + mockLocalState = { selectedItems: [1], expandedRows: [] }; + mockGlobalState = { bulkSelectedItems: [1], bulkSalesStatus: 'suspended' }; + + mockG7Core.api.patch.mockResolvedValueOnce({ data: { products_updated: 1, options_updated: 0 } }); + + bulkUpdateHandler({ handler: 'sirsoft-ecommerce.bulkUpdate' }, mockContext); + + await vi.waitFor(() => { + expect(mockG7Core.dataSource.refetch).toHaveBeenCalledWith('products'); + }); + + expect(mockG7Core.api.get).not.toHaveBeenCalled(); + }); + + it('미펼침 상품은 option_items 로 보내지 않는다 (서버가 ids 로 전개)', async () => { + mockDataSources.products = { data: { data: [makeProduct(1, false)] } }; + mockLocalState = { + selectedItems: [1], + expandedRows: [], + modifiedOptionIds: ['1-11'], + modifiedOptionFields: { '1-11': ['stock_quantity'] }, + }; + mockGlobalState = { + bulkSelectedItems: [1], + bulkStockCondition: '+10개', + bulkStockMethod: 'increase', + bulkStockValue: 10, + }; + + mockG7Core.api.patch.mockResolvedValueOnce({ data: { products_updated: 1, options_updated: 2 } }); + + bulkUpdateHandler({ handler: 'sirsoft-ecommerce.bulkUpdate' }, mockContext); + + await vi.waitFor(() => { + expect(mockG7Core.api.patch).toHaveBeenCalled(); + }); + + const payload = mockG7Core.api.patch.mock.calls[0][1]; + + expect(payload.ids).toEqual([1]); + expect(payload.option_bulk_changes).toBeDefined(); + expect(payload.option_items).toBeUndefined(); + }); +}); diff --git a/modules/_bundled/sirsoft-ecommerce/resources/js/__tests__/handlers/expandedOptionsHandlers.test.ts b/modules/_bundled/sirsoft-ecommerce/resources/js/__tests__/handlers/expandedOptionsHandlers.test.ts new file mode 100644 index 00000000..40bc57ef --- /dev/null +++ b/modules/_bundled/sirsoft-ecommerce/resources/js/__tests__/handlers/expandedOptionsHandlers.test.ts @@ -0,0 +1,419 @@ +/** + * loadExpandedOptionsHandler 테스트 (상품 목록 확장 행 옵션 지연 로딩) + * + * @description + * 목록 응답이 옵션을 싣지 않게 되면서, 옵션은 행을 펼칠 때 배치 엔드포인트가 공급한다. + * 이 테스트가 고정하는 계약은 다음과 같다. + * + * - 아직 안 불러온 행만 요청한다 (`undefined` = 미로드, `[]` = 로드 완료·옵션 없음) + * - `options_total_count === 0` 이면 네트워크를 쓰지 않는다 + * - 같은 상품에 대한 중복 요청을 병합한다 + * - 병합은 응답 콜백 안에서 데이터소스를 **다시 읽어** 수행한다 (대기 중 인라인 편집 보존) + * - 응답에 없는 요청 ID 도 `[]` 로 확정한다 (무한 스켈레톤 방지) + * - 체크된 상품의 옵션은 선택 상태에 합쳐진다 + * + * @vitest-environment jsdom + */ + +import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; +import { + loadExpandedOptionsHandler, + retryExpandedOptionsHandler, + reloadExpandedOptions, + __resetExpandedOptionsState, +} from '../../handlers/expandedOptionsHandlers'; + +let mockLocalState: Record = {}; +let mockDataSource: Record = {}; +let apiGet: any; +let toastError: any; + +const mockContext = {} as any; + +function makeG7Core() { + return { + state: { + getLocal: () => mockLocalState, + get: () => ({ _local: mockLocalState }), + setLocal: vi.fn((updates: Record) => { + mockLocalState = { ...mockLocalState, ...updates }; + }), + set: vi.fn(), + }, + dataSource: { + get: vi.fn((id: string) => mockDataSource[id]), + set: vi.fn((id: string, value: any) => { + mockDataSource[id] = value; + }), + refetch: vi.fn(() => Promise.resolve()), + }, + api: { + get: apiGet, + }, + toast: { + error: toastError, + success: vi.fn(), + }, + t: (key: string) => key, + createLogger: () => ({ log: vi.fn(), warn: vi.fn(), error: vi.fn() }), + }; +} + +/** + * products 데이터소스를 시드합니다. + * + * @param products 상품 배열 + */ +function seedProducts(products: any[]): void { + mockDataSource['products'] = { + success: true, + data: { + data: products, + pagination: { current_page: 1 }, + }, + }; +} + +/** 데이터소스에서 상품을 찾습니다. */ +function findProduct(id: number): any { + return mockDataSource['products'].data.data.find((p: any) => p.id === id); +} + +beforeEach(() => { + __resetExpandedOptionsState(); + mockLocalState = {}; + mockDataSource = {}; + apiGet = vi.fn(() => + Promise.resolve({ data: { options: {}, product_ids: [] } }) + ); + toastError = vi.fn(); + (window as any).G7Core = makeG7Core(); + vi.clearAllMocks(); +}); + +afterEach(() => { + delete (window as any).G7Core; +}); + +describe('loadExpandedOptionsHandler', () => { + it('확장 상태를 먼저 반영한 뒤 미로드 행만 요청한다', async () => { + seedProducts([ + { id: 1, options_total_count: 2 }, + { id: 2, options_total_count: 1, options: [{ id: 20 }] }, + ]); + + apiGet.mockResolvedValue({ + data: { options: { '1': [{ id: 10 }, { id: 11 }] }, product_ids: [1] }, + }); + + loadExpandedOptionsHandler( + { handler: 'x', params: { expandedRows: [1, 2] } }, + mockContext + ); + + // 확장 상태가 즉시 반영된다 (fetch 결과를 기다리지 않는다) + expect(mockLocalState.expandedRows).toEqual([1, 2]); + + // 이미 options 배열을 가진 상품 2 는 요청 대상이 아니다 + expect(apiGet).toHaveBeenCalledTimes(1); + expect(apiGet.mock.calls[0][1]).toEqual({ params: { product_ids: [1] } }); + + await vi.waitFor(() => { + expect(findProduct(1).options).toHaveLength(2); + }); + }); + + // @scenario row_state=re_expanded,option_profile=mixed + // @effects options_reloaded_after_bulk_save + it('force 면 이미 로드된 행도 다시 요청한다', async () => { + seedProducts([{ id: 1, options_total_count: 1, options: [{ id: 10 }] }]); + + apiGet.mockResolvedValue({ + data: { options: { '1': [{ id: 99 }] }, product_ids: [1] }, + }); + + loadExpandedOptionsHandler( + { handler: 'x', params: { productIds: [1], force: true } }, + mockContext + ); + + expect(apiGet).toHaveBeenCalledTimes(1); + + await vi.waitFor(() => { + expect(findProduct(1).options).toEqual([{ id: 99 }]); + }); + }); + + // @scenario row_state=re_expanded,option_profile=active_only + // @effects no_refetch_on_re_expand + it('빈 배열은 "로드 완료·옵션 없음" 이므로 재요청하지 않는다', () => { + seedProducts([{ id: 1, options_total_count: 0, options: [] }]); + + loadExpandedOptionsHandler( + { handler: 'x', params: { expandedRows: [1] } }, + mockContext + ); + + expect(apiGet).not.toHaveBeenCalled(); + }); + + it('options_total_count 가 0 이면 네트워크 없이 빈 배열로 확정한다', () => { + seedProducts([{ id: 1, options_total_count: 0 }]); + + loadExpandedOptionsHandler( + { handler: 'x', params: { expandedRows: [1] } }, + mockContext + ); + + expect(apiGet).not.toHaveBeenCalled(); + expect(findProduct(1).options).toEqual([]); + }); + + // @scenario row_state=expanding,option_profile=active_only + // @effects inflight_requests_merged + it('진행 중인 요청이 있는 상품은 중복 요청하지 않는다', () => { + seedProducts([{ id: 1, options_total_count: 2 }]); + + // 응답이 오지 않는 요청 (in-flight 유지) + apiGet.mockReturnValue(new Promise(() => {})); + + loadExpandedOptionsHandler({ handler: 'x', params: { expandedRows: [1] } }, mockContext); + loadExpandedOptionsHandler({ handler: 'x', params: { expandedRows: [1] } }, mockContext); + + expect(apiGet).toHaveBeenCalledTimes(1); + }); + + // @scenario row_state=expanding,option_profile=mixed + // @effects merge_reads_datasource_again + it('병합 시 데이터소스를 다시 읽어 대기 중 편집을 보존한다', async () => { + seedProducts([{ id: 1, options_total_count: 1, name: 'before' }]); + + let resolveApi: (value: any) => void = () => {}; + apiGet.mockReturnValue(new Promise((resolve) => { resolveApi = resolve; })); + + loadExpandedOptionsHandler({ handler: 'x', params: { expandedRows: [1] } }, mockContext); + + // 요청이 날아간 뒤 다른 경로가 같은 상품을 수정한다 (인라인 편집 시뮬레이션) + seedProducts([{ id: 1, options_total_count: 1, name: 'edited-while-loading' }]); + + resolveApi({ data: { options: { '1': [{ id: 10 }] }, product_ids: [1] } }); + + await vi.waitFor(() => { + expect(findProduct(1).options).toEqual([{ id: 10 }]); + }); + + // 요청 전 스냅샷을 썼다면 'before' 로 되돌아갔을 것이다 + expect(findProduct(1).name).toBe('edited-while-loading'); + }); + + // @scenario row_state=expanded,option_profile=active_only + // @effects missing_ids_resolved_as_empty + it('응답에 없는 요청 ID 도 빈 배열로 확정한다', async () => { + seedProducts([ + { id: 1, options_total_count: 1 }, + { id: 2, options_total_count: 1 }, + ]); + + // 상품 2 는 권한 밖 — 응답에서 빠진다 + apiGet.mockResolvedValue({ + data: { options: { '1': [{ id: 10 }] }, product_ids: [1] }, + }); + + loadExpandedOptionsHandler( + { handler: 'x', params: { expandedRows: [1, 2] } }, + mockContext + ); + + await vi.waitFor(() => { + expect(findProduct(2).options).toEqual([]); + }); + }); + + // @scenario row_state=expanded,option_profile=mixed + // @effects selected_option_ids_hydrated + it('체크된 상품의 옵션은 선택 상태에 합쳐진다', async () => { + seedProducts([{ id: 1, options_total_count: 2 }]); + mockLocalState.selectedItems = [1]; + mockLocalState.selectedOptionIds = []; + + apiGet.mockResolvedValue({ + data: { options: { '1': [{ id: 10 }, { id: 11 }] }, product_ids: [1] }, + }); + + loadExpandedOptionsHandler( + { handler: 'x', params: { expandedRows: [1] } }, + mockContext + ); + + await vi.waitFor(() => { + expect(mockLocalState.selectedOptionIds).toEqual(['1-10', '1-11']); + }); + }); + + it('체크되지 않은 상품의 옵션은 선택 상태를 건드리지 않는다', async () => { + seedProducts([{ id: 1, options_total_count: 1 }]); + mockLocalState.selectedItems = []; + mockLocalState.selectedOptionIds = []; + + apiGet.mockResolvedValue({ + data: { options: { '1': [{ id: 10 }] }, product_ids: [1] }, + }); + + loadExpandedOptionsHandler({ handler: 'x', params: { expandedRows: [1] } }, mockContext); + + await vi.waitFor(() => { + expect(findProduct(1).options).toHaveLength(1); + }); + + expect(mockLocalState.selectedOptionIds).toEqual([]); + }); + + it('로딩 중에는 optionsLoadingIds 에 담기고 완료 후 비워진다', async () => { + seedProducts([{ id: 1, options_total_count: 1 }]); + + let resolveApi: (value: any) => void = () => {}; + apiGet.mockReturnValue(new Promise((resolve) => { resolveApi = resolve; })); + + loadExpandedOptionsHandler({ handler: 'x', params: { expandedRows: [1] } }, mockContext); + + expect(mockLocalState.optionsLoadingIds).toEqual(['1']); + + resolveApi({ data: { options: { '1': [] }, product_ids: [1] } }); + + await vi.waitFor(() => { + expect(mockLocalState.optionsLoadingIds).toEqual([]); + }); + }); + + // @scenario row_state=error,option_profile=active_only + it('실패 시 optionsErrorIds 에 기록하고 토스트로 알린다', async () => { + seedProducts([{ id: 1, options_total_count: 1 }]); + + apiGet.mockRejectedValue({ response: { data: { message: '서버 오류' } } }); + + loadExpandedOptionsHandler({ handler: 'x', params: { expandedRows: [1] } }, mockContext); + + await vi.waitFor(() => { + expect(mockLocalState.optionsErrorIds).toEqual(['1']); + }); + + expect(mockLocalState.optionsLoadingIds).toEqual([]); + expect(toastError).toHaveBeenCalledWith('서버 오류'); + }); + + // @scenario row_state=error,option_profile=mixed + it('실패 후 재시도가 가능하다 (in-flight 가 해제된다)', async () => { + seedProducts([{ id: 1, options_total_count: 1 }]); + + apiGet.mockRejectedValueOnce({ response: { data: { message: 'boom' } } }); + + loadExpandedOptionsHandler({ handler: 'x', params: { expandedRows: [1] } }, mockContext); + + await vi.waitFor(() => { + expect(mockLocalState.optionsErrorIds).toEqual(['1']); + }); + + apiGet.mockResolvedValue({ data: { options: { '1': [{ id: 10 }] }, product_ids: [1] } }); + + loadExpandedOptionsHandler( + { handler: 'x', params: { productId: 1, force: true } }, + mockContext + ); + + await vi.waitFor(() => { + expect(findProduct(1).options).toEqual([{ id: 10 }]); + }); + + expect(mockLocalState.optionsErrorIds).toEqual([]); + }); + + it('데이터소스에 없는 ID 는 요청하지 않는다', () => { + seedProducts([{ id: 1, options_total_count: 1, options: [] }]); + + loadExpandedOptionsHandler( + { handler: 'x', params: { expandedRows: [999] } }, + mockContext + ); + + expect(apiGet).not.toHaveBeenCalled(); + }); +}); + +describe('retryExpandedOptionsHandler', () => { + it('레이아웃이 보내는 단수 productId 로 그 행만 다시 요청한다', async () => { + // 3행을 펼쳐 두고 그중 2번만 실패한 상황 — 재시도는 2번만 다시 불러야 한다. + seedProducts([ + { id: 1, options_total_count: 1, options: [{ id: 10 }] }, + { id: 2, options_total_count: 1 }, + { id: 3, options_total_count: 1, options: [{ id: 30 }] }, + ]); + mockLocalState.expandedRows = [1, 2, 3]; + mockLocalState.optionsErrorIds = [2]; + + apiGet.mockResolvedValue({ + data: { options: { '2': [{ id: 20 }] }, product_ids: [2] }, + }); + + retryExpandedOptionsHandler( + { handler: 'sirsoft-ecommerce.retryExpandedOptions', params: { productId: 2 } } as any, + mockContext + ); + + expect(apiGet).toHaveBeenCalledTimes(1); + + // 요청 대상이 2번 한 행뿐이어야 한다. 단수 키가 포워딩되지 않으면 expandedRows 폴백으로 + // 떨어져 이미 성공한 1·3번까지 force 로 다시 불린다. + const requested = apiGet.mock.calls[0][1]?.params?.product_ids; + expect(requested).toEqual([2]); + + await vi.waitFor(() => { + expect(findProduct(2).options).toEqual([{ id: 20 }]); + }); + }); + + it('복수 productIds 형태도 그대로 받는다', () => { + seedProducts([ + { id: 1, options_total_count: 1, options: [{ id: 10 }] }, + { id: 2, options_total_count: 1 }, + ]); + mockLocalState.expandedRows = [1, 2]; + + retryExpandedOptionsHandler( + { handler: 'sirsoft-ecommerce.retryExpandedOptions', params: { productIds: [1, 2] } } as any, + mockContext + ); + + expect(apiGet).toHaveBeenCalledTimes(1); + expect(apiGet.mock.calls[0][1]?.params?.product_ids).toEqual([1, 2]); + }); +}); + +describe('reloadExpandedOptions', () => { + it('펼친 행이 없으면 아무것도 하지 않는다', () => { + seedProducts([{ id: 1, options_total_count: 1 }]); + mockLocalState.expandedRows = []; + + reloadExpandedOptions((window as any).G7Core, { force: true }); + + expect(apiGet).not.toHaveBeenCalled(); + }); + + // @scenario selection=individual_partial,target=stock,source=both_global_priority + // @effects options_reloaded_after_save + it('펼친 행을 force 로 다시 가져온다', async () => { + seedProducts([{ id: 1, options_total_count: 1, options: [{ id: 10, stock_quantity: 1 }] }]); + mockLocalState.expandedRows = [1]; + + apiGet.mockResolvedValue({ + data: { options: { '1': [{ id: 10, stock_quantity: 77 }] }, product_ids: [1] }, + }); + + reloadExpandedOptions((window as any).G7Core, { force: true }); + + expect(apiGet).toHaveBeenCalledTimes(1); + + await vi.waitFor(() => { + expect(findProduct(1).options[0].stock_quantity).toBe(77); + }); + }); +}); diff --git a/modules/_bundled/sirsoft-ecommerce/resources/js/__tests__/handlers/updateOptionField.test.ts b/modules/_bundled/sirsoft-ecommerce/resources/js/__tests__/handlers/updateOptionField.test.ts index 39c1a861..640bbfcf 100644 --- a/modules/_bundled/sirsoft-ecommerce/resources/js/__tests__/handlers/updateOptionField.test.ts +++ b/modules/_bundled/sirsoft-ecommerce/resources/js/__tests__/handlers/updateOptionField.test.ts @@ -171,3 +171,95 @@ describe('updateOptionFieldHandler - 비기본 옵션 판매가 변경 (상품 expect(productFields).not.toContain('selling_price'); }); }); + +/** + * 옵션 지연 로딩(#518 / 공개 #76) 이후 생긴 상태: 목록 행의 `options` 는 펼치기 전까지 + * `undefined` 다. 이 핸들러는 그 행을 만나도 **아무것도 바꾸지 않아야** 한다. + * + * 특히 `options` 를 `[]` 로 채워 넣으면 안 된다. 지연 로딩은 `Array.isArray(row.options)` 로 + * 미로드(`undefined`)와 로드완료·옵션없음(`[]`)을 구분하므로, `[]` 로 뒤바뀌면 그 행은 + * "이미 불러왔다" 로 오인되어 펼쳐도 영영 옵션을 받지 못한다 — 에러도 토스트도 없이. + */ +describe('updateOptionFieldHandler - 옵션 미로드 행 (지연 로딩)', () => { + /** 옵션을 아직 불러오지 않은 행을 하나 더 붙인다 */ + function seedUnloadedRow() { + mockDataSource['products'].data.data.push({ + id: 999, + selling_price: 7000, + list_price: 9000, + // options 키 자체가 없다 — 서버가 목록에서 싣지 않는다 + }); + } + + it('options 가 undefined 인 행에 편집이 들어와도 undefined 로 남는다', () => { + seedUnloadedRow(); + + updateOptionFieldHandler( + { handler: 'updateOptionField', params: { productId: 999, optionId: 1, field: 'selling_price', value: 8000 } }, + mockContext + ); + + const unloaded = mockDataSource['products'].data.data.find((p: any) => p.id === 999); + expect(unloaded.options).toBeUndefined(); + expect( + Array.isArray(unloaded.options), + 'options 가 배열이 되면 지연 로딩이 "로드 완료" 로 오인해 영영 불러오지 않는다' + ).toBe(false); + }); + + it('options 가 undefined 인 행의 다른 값도 바뀌지 않는다', () => { + seedUnloadedRow(); + + updateOptionFieldHandler( + { handler: 'updateOptionField', params: { productId: 999, optionId: 1, field: 'selling_price', value: 8000 } }, + mockContext + ); + + const unloaded = mockDataSource['products'].data.data.find((p: any) => p.id === 999); + expect(unloaded.selling_price).toBe(7000); + }); + + it('미로드 행이 섞여 있어도 로드된 행의 편집은 정상 동작한다', () => { + seedUnloadedRow(); + + updateOptionFieldHandler( + { handler: 'updateOptionField', params: { productId: 313, optionId: 1, field: 'selling_price', value: 4500 } }, + mockContext + ); + + expect(getProduct().selling_price).toBe(4500); + // 미로드 행은 그대로 + const unloaded = mockDataSource['products'].data.data.find((p: any) => p.id === 999); + expect(unloaded.options).toBeUndefined(); + }); + + it('미로드 행은 modified 추적에도 들어가지 않는다', () => { + seedUnloadedRow(); + + updateOptionFieldHandler( + { handler: 'updateOptionField', params: { productId: 999, optionId: 1, field: 'selling_price', value: 8000 } }, + mockContext + ); + + const modifiedProducts = mockLocalState.modifiedProductFields ?? {}; + expect(modifiedProducts['999']).toBeUndefined(); + + // 존재하지 않는 옵션이 수정 대상으로 기록되면 일괄 저장이 유령 키를 option_items 에 + // 실어 보낸다. 옵션 지연 로딩 전에는 모든 행이 옵션을 갖고 있어 드러나지 않던 경로다. + expect(mockLocalState.modifiedOptionIds ?? []).not.toContain('999-1'); + expect(mockLocalState.modifiedOptionFields ?? {}).not.toHaveProperty('999-1'); + }); + + it('찾지 못한 옵션에는 데이터소스 쓰기 자체가 일어나지 않는다', () => { + seedUnloadedRow(); + + updateOptionFieldHandler( + { handler: 'updateOptionField', params: { productId: 999, optionId: 1, field: 'selling_price', value: 8000 } }, + mockContext + ); + + // 같은 값을 다시 써서 불필요한 리렌더를 만들지 않는다 + expect(mockG7Core.dataSource.set).not.toHaveBeenCalled(); + expect(mockG7Core.state.setLocal).not.toHaveBeenCalled(); + }); +}); diff --git a/modules/_bundled/sirsoft-ecommerce/resources/js/__tests__/layouts/admin-ecommerce-notification-channel-single-source.test.ts b/modules/_bundled/sirsoft-ecommerce/resources/js/__tests__/layouts/admin-ecommerce-notification-channel-single-source.test.ts new file mode 100644 index 00000000..8da0db84 --- /dev/null +++ b/modules/_bundled/sirsoft-ecommerce/resources/js/__tests__/layouts/admin-ecommerce-notification-channel-single-source.test.ts @@ -0,0 +1,190 @@ +/** + * @file admin-ecommerce-notification-channel-single-source.test.ts + * @description 커머스 환경설정 알림 정의 탭 — 채널 표현식 단일 출처 회귀 테스트 + * + * 회귀 배경 (#518 3차 실측 결함 #1): + * 알림 정의 탭의 채널은 URL(`query.channel`)과 페이지 로컬 상태(`_local.activeNotificationChannel`) + * 두 곳에서 읽히고 있었다. 두 값은 갱신 시점이 다르다 — 다른 설정 탭에 갔다 돌아오면 URL 의 + * channel 은 사라지지만(탭 전환은 목록 상태를 승계하지 않는다) `_local` 은 이전 채널로 남는다. + * + * 코어 설정 화면은 그 사이에 목록을 기본 채널로 다시 불러오므로, 남아 있는 이전 채널로 템플릿을 + * 찾다가 행마다 「이 채널에 대한 템플릿이 없습니다」 를 띄웠다. + * + * 그래서 조건은 "두 표현식이 같은가" 가 아니라 "출처가 하나인가" 다. + * + * 2차 회귀 (#518 계획서 검증 브라우저 실측, 2026-08-05): + * 목록 프루닝으로 서버가 `template_channel` 을 받은 요청에만 `templates` 를 싣게 됐는데 + * (`NotificationDefinitionRepository::getPaginated`), 코어 화면만 그 파라미터를 보내도록 + * 고쳐지고 커머스 탭 데이터소스는 빠졌다. 그 결과 10건 전 행이 「이 채널에 대한 템플릿이 + * 없습니다」 로 표시되고 수신자·활성 토글·편집 모달 초기값이 통째로 비었다. + * + * 조용히 깨지는 종류다 — `(def.templates ?? [])` 가드 뒤라 예외도 콘솔 경고도 없고, + * 응답은 200 이며 행 수도 정상이라 "템플릿을 아직 안 만들었나 보다" 로 읽힌다. + * 그래서 아래 두 단언은 소비와 전송을 **함께** 묶는다: templates 를 읽는 화면이면 + * 그 채널을 서버에 넘겨야 한다. + * + * @scenario resource=ecommerce_notification_definition,endpoint=admin_settings,observation=consumer_screen + * @effects ecommerce_notification_channel_reads_single_source, ecommerce_notification_list_requests_template_channel, ecommerce_notification_reset_button_uses_server_aggregate + */ + +import fs from 'fs'; +import path from 'path'; +import { describe, it, expect } from 'vitest'; + +const SETTINGS_LAYOUT = path.resolve( + __dirname, + '../../../layouts/admin/admin_ecommerce_settings.json', +); +const NOTIFICATION_TAB = path.resolve( + __dirname, + '../../../layouts/admin/partials/admin_ecommerce_settings/_tab_notification_definitions.json', +); + +/** + * JSON 트리에서 `{{ }}` 바인딩 표현식을 모두 수집합니다. + * + * @param node 탐색할 JSON 노드 + * @param acc 수집 결과 누적 배열 + * @returns 수집된 바인딩 표현식 목록 + */ +function collectBindings(node: unknown, acc: string[] = []): string[] { + if (typeof node === 'string') { + const matches = node.match(/\{\{[\s\S]*?\}\}/g); + + if (matches) { + acc.push(...matches); + } + + return acc; + } + + if (Array.isArray(node)) { + node.forEach((child) => collectBindings(child, acc)); + + return acc; + } + + if (node && typeof node === 'object') { + Object.values(node as Record).forEach((child) => + collectBindings(child, acc), + ); + } + + return acc; +} + +/** + * 레이아웃 파일을 읽어 파싱합니다. + * + * @param filePath 레이아웃 파일 절대 경로 + * @returns 파싱된 JSON 객체 + */ +function readLayout(filePath: string): Record { + return JSON.parse(fs.readFileSync(filePath, 'utf-8')); +} + +describe('커머스 알림 정의 탭 — 채널 단일 출처 (#518)', () => { + it('채널 표현식이 URL 만 읽는다', () => { + const bindings = [ + ...collectBindings(readLayout(SETTINGS_LAYOUT)), + ...collectBindings(readLayout(NOTIFICATION_TAB)), + ]; + + const channelReads = bindings.filter((binding) => /query\.channel/.test(binding)); + + expect( + channelReads.length, + '채널을 읽는 표현식이 있어야 한다 (0건이면 이 단언은 공회전한다)', + ).toBeGreaterThan(0); + + const dualSourced = channelReads.filter((binding) => /_local\./.test(binding)); + + expect( + dualSourced, + 'URL 이 나르지 않는 값(_local)을 채널 출처로 섞으면 데이터와 화면이 서로 다른 채널을 본다', + ).toEqual([]); + }); + + it('채널 전환은 URL 만 갱신한다 (로컬 상태에 복제하지 않는다)', () => { + const raw = fs.readFileSync(NOTIFICATION_TAB, 'utf-8'); + + expect( + raw.includes('activeNotificationChannel'), + '채널을 로컬 상태에 복제하면 URL 과 어긋날 수 있는 두 번째 출처가 다시 생긴다', + ).toBe(false); + + expect( + fs.readFileSync(SETTINGS_LAYOUT, 'utf-8').includes('activeNotificationChannel'), + 'initLocal 에 남겨두면 소비자가 다시 참조하게 된다', + ).toBe(false); + }); +}); + +describe('커머스 알림 정의 탭 — 서버 채널 필터 정합 (#518)', () => { + /** + * 알림 정의 목록 데이터소스를 찾습니다. + * + * @returns 데이터소스 정의 객체 + */ + function findDefinitionsDataSource(): Record { + const layout = readLayout(SETTINGS_LAYOUT); + const source = (layout.data_sources ?? []).find((ds: Record) => + typeof ds.endpoint === 'string' && ds.endpoint.includes('/api/admin/notification-definitions'), + ); + + expect(source, '알림 정의 목록 데이터소스를 찾지 못했다').toBeTruthy(); + + return source; + } + + it('templates 를 그리는 화면이면 목록 요청에 template_channel 을 보낸다', () => { + const consumesTemplates = collectBindings(readLayout(NOTIFICATION_TAB)).some((binding) => + /def\.templates/.test(binding), + ); + + expect( + consumesTemplates, + 'templates 소비가 0건이면 이 단언은 공회전한다 (탭 구조가 바뀌었는지 확인)', + ).toBe(true); + + // 서버는 `template_channel` 을 받은 요청에만 templates 를 싣는다. 화면이 templates 를 + // 읽으면서 이 파라미터를 빼면 전 행이 「이 채널에 대한 템플릿이 없습니다」 가 된다. + expect( + findDefinitionsDataSource().params?.template_channel, + 'templates 를 소비하는 목록이 채널을 서버에 넘기지 않으면 응답에 templates 키 자체가 없다', + ).toBeDefined(); + }); + + it('서버에 넘기는 채널과 화면이 찾는 채널이 같은 표현식이다', () => { + const sent = findDefinitionsDataSource().params.template_channel; + + // 화면이 템플릿을 고를 때 쓰는 채널 표현식을 추출한다. + const screenChannel = collectBindings(readLayout(NOTIFICATION_TAB)) + .find((binding) => /t\.channel === /.test(binding)) + ?.match(/t\.channel === \(([^)]+)\)/)?.[1]; + + expect(screenChannel, '화면의 채널 비교 표현식을 찾지 못했다').toBeTruthy(); + + // 예: 전송 "{{query.channel ?? 'mail'}}" ↔ 화면 "query.channel ?? 'mail'" + expect( + sent.replace(/^\{\{|\}\}$/g, '').trim(), + '두 채널이 어긋나면 서버가 실어 보낸 채널과 화면이 찾는 채널이 달라 빈 값이 된다', + ).toBe(screenChannel!.trim()); + }); + + it('되돌리기 버튼은 서버 집계(has_customized_templates)로 판정한다', () => { + const raw = fs.readFileSync(NOTIFICATION_TAB, 'utf-8'); + + // templates 는 한 채널로 좁혀 실리므로, 배열을 순회해서는 "다른 채널에서 커스터마이즈됨" + // 을 알 수 없다. 채널을 가리지 않는 판정은 서버 집계가 유일한 출처다. + expect( + /def\.templates[\s\S]{0,80}?\.some\(/.test(raw), + '되돌리기 조건을 templates 배열 순회로 판정하면 다른 채널의 커스터마이즈를 놓친다', + ).toBe(false); + + expect( + raw.includes('def.has_customized_templates'), + '되돌리기 노출 조건이 서버 집계를 읽어야 한다', + ).toBe(true); + }); +}); 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 bd2504f9..19c20c4d 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 @@ -247,19 +247,34 @@ describe('통화 규칙 테이블 (_tab_mileage_currency_table.json)', () => { expect(percentInput.props.disabled).toContain("rule.max_use_type !== 'percent'"); }); - it('max_use_type 라디오는 Label 클릭과 키보드 change 양쪽으로 같은 상태를 만든다', () => { + it('max_use_type 라디오는 라디오 자신과 부모 Label 양쪽에서 상태를 바꾼다 (키보드 조작 가능)', () => { // 회귀: 라디오 자동바인딩은 문자열 값을 value 바인딩으로 처리해 라디오 그룹을 깨뜨린다. - // 그래서 Label click 으로 상태를 갱신한다. 다만 라디오를 pointer-events-none 으로만 두면 - // 키보드 방향키(change)로 옮겼을 때 화면 표시와 저장될 값이 어긋난다(#493) — - // 라디오 자체에도 click 과 **동일한 params** 를 갖는 change 액션을 둔다. + // 그래서 자동바인딩 대신 명시적 setState 로 해당 행(rule._idx)의 max_use_type 을 + // 배열 map 교체로 갱신한다. + // + // 종전에는 라디오를 `pointer-events-none`(시각 전용)으로 두고 부모 Label click 만 처리했으나, + // 그 형태는 키보드(Tab → Space/방향키)로 값을 바꿀 수 없어 #493 에서 되돌렸다. 지금 계약은 + // **라디오 자신의 change + 부모 Label 의 click 둘 다** 같은 setState 를 수행하는 것이다 + // (같은 값을 쓰므로 이벤트가 겹쳐 두 번 실행돼도 결과가 같다). const radios = findAll(currencyTable, (n) => n.name === 'Input' && n.props?.type === 'radio' && String(n.props?.name).includes('max_use_type')); // currency_rules 행(2) + 인라인 추가 행(2) = 4 expect(radios.length).toBe(4); for (const radio of radios) { - const change = (radio.actions ?? []).find((a: any) => a.type === 'change' && a.handler === 'setState'); - expect(change, `radio(${radio.props.value}) 에 키보드 조작용 change 액션 누락`).toBeTruthy(); - expect(change.params.target).toBe('local'); + // 라디오가 포인터 이벤트를 막으면 키보드 포커스 이동 후 조작도 함께 막힌다 + expect( + String(radio.props.className ?? ''), + `radio(${radio.props.value}) 가 pointer-events-none 이면 키보드로 값을 바꿀 수 없다`, + ).not.toContain('pointer-events-none'); + + // 라디오 자신이 change 로 값을 바꾼다 (자동바인딩이 아니라 명시적 setState) + const changeActions = (radio.actions ?? []).filter((a: any) => a.type === 'change'); + expect(changeActions.length, `radio(${radio.props.value}) 에 change 액션 누락`).toBe(1); + expect(changeActions[0].handler).toBe('setState'); + // Label click 과 **동일한 params** 여야 한다 — 대상 저장소(local)와 갱신 키가 갈리면 + // 마우스로 고른 값과 키보드로 고른 값이 서로 다른 곳에 쓰인다 (#493) + expect(changeActions[0].params?.target).toBe('local'); + expect(JSON.stringify(changeActions[0].params ?? {})).toContain('max_use_type'); } // 부모 Label 이 click → setState 로 max_use_type 갱신 diff --git a/modules/_bundled/sirsoft-ecommerce/resources/js/__tests__/layouts/cartWishlistImageConsumers.test.ts b/modules/_bundled/sirsoft-ecommerce/resources/js/__tests__/layouts/cartWishlistImageConsumers.test.ts new file mode 100644 index 00000000..92fc6054 --- /dev/null +++ b/modules/_bundled/sirsoft-ecommerce/resources/js/__tests__/layouts/cartWishlistImageConsumers.test.ts @@ -0,0 +1,93 @@ +/** + * @file cartWishlistImageConsumers.test.ts + * @description 카트·찜목록 — 상품 이미지 소비 경로 고정 (#518 / 공개 #76) + * + * 배경: + * 카트·찜목록 조회가 `product.images` 를 전량 hydration 하고 있었다. 두 화면 모두 상품 + * 썸네일 1장만 그리므로 **컬럼을 좁혀** 로드하도록 바꿨다(`images:id,product_id,hash, + * is_thumbnail,sort_order`). 응답에는 애초에 배열이 직렬화되지 않으므로 **응답 형태는 불변**이고 + * DB/메모리만 줄어든다. + * + * 이 방식이 안전한 근거는 하나다 — 화면이 배열을 읽지 않는다는 것. 그래서 그 사실을 고정한다. + * 누군가 화면에 `product.images` 를 쓰기 시작하면 좁혀둔 컬럼으로는 부족해질 수 있고, 그때는 + * 이 테스트가 먼저 깨져 알려준다. + * + * 반대로 썸네일 경로(`thumbnail_url`)는 반드시 살아 있어야 한다. 서버는 이 값을 좁혀 읽은 + * 컬럼(`hash`/`is_thumbnail`/`sort_order`)으로 조립하므로, 그 컬럼 집합을 더 줄이면 대표 + * 미지정 상품의 폴백이 깨진다. + * + * 마킹은 아래 describe 블록마다 따로 붙인다 — 한 docblock 에서는 @scenario 가 첫 줄만 읽혀, + * 여기에 두 화면을 나열하면 뒤엣것이 조용히 누락된다. + */ + +import fs from 'fs'; +import path from 'path'; +import { describe, it, expect } from 'vitest'; + +const REPO_ROOT = path.resolve(__dirname, '../../../../../../..'); + +const CART_ITEM = path.join( + REPO_ROOT, + 'templates/_bundled/sirsoft-basic/layouts/partials/shop/_cart_item.json', +); +const WISHLIST_LIST = path.join( + REPO_ROOT, + 'templates/_bundled/sirsoft-basic/layouts/partials/mypage/wishlist/_list.json', +); +const PRODUCT_CARD = path.join( + REPO_ROOT, + 'templates/_bundled/sirsoft-basic/src/components/composite/ProductCard.tsx', +); + +/** + * 파일 원문을 읽습니다. + * + * @param filePath 대상 파일 절대 경로 + * @returns 파일 원문 + */ +function readRaw(filePath: string): string { + return fs.readFileSync(filePath, 'utf-8'); +} + +/** + * @scenario surface=cart,observation=consumer_screen + * @effects cart_and_wishlist_consume_thumbnail_only + */ +describe('카트 — 상품 이미지 소비 (#518 / 공개 #76)', () => { + it('썸네일 1장만 그린다 (이미지 배열을 순회하지 않는다)', () => { + const raw = readRaw(CART_ITEM); + + expect(raw, '카트 항목이 상품 썸네일을 그려야 한다').toContain( + 'item.product.thumbnail_url', + ); + + // 배열을 읽기 시작하면 좁혀둔 컬럼으로 부족해질 수 있다. + expect(raw).not.toContain('item.product.images'); + }); +}); + +/** + * @scenario surface=wishlist,observation=consumer_screen + * @effects cart_and_wishlist_consume_thumbnail_only + */ +describe('찜목록 — 상품 이미지 소비 (#518 / 공개 #76)', () => { + it('상품 카드에 상품 객체를 넘기고, 배열을 직접 읽지 않는다', () => { + const raw = readRaw(WISHLIST_LIST); + + expect(raw, '찜목록이 상품 카드로 렌더한다').toContain('"product": "{{item.product}}"'); + expect(raw).not.toContain('item.product.images'); + }); + + it('상품 카드가 썸네일 필드로 이미지를 그린다', () => { + const raw = readRaw(PRODUCT_CARD); + + // 찜목록의 이미지는 이 컴포넌트가 그린다 — 레이아웃만 보면 소비 경로가 보이지 않는다. + expect(raw).toContain('product.thumbnail_url'); + }); + + it('상품 카드가 이미지 배열을 쓰지 않는다', () => { + const raw = readRaw(PRODUCT_CARD); + + expect(raw).not.toMatch(/product\.images\b/); + }); +}); diff --git a/modules/_bundled/sirsoft-ecommerce/resources/js/__tests__/layouts/productListLayout.test.tsx b/modules/_bundled/sirsoft-ecommerce/resources/js/__tests__/layouts/productListLayout.test.tsx index 6cb1c727..fe90aaad 100644 --- a/modules/_bundled/sirsoft-ecommerce/resources/js/__tests__/layouts/productListLayout.test.tsx +++ b/modules/_bundled/sirsoft-ecommerce/resources/js/__tests__/layouts/productListLayout.test.tsx @@ -150,13 +150,21 @@ describe('상품 목록 레이아웃 named_actions 검증', () => { }); it('searchProducts named_action이 올바른 navigate 핸들러를 가져야 함', () => { + // 검색 실행은 "펼침 상태 초기화 → 이동" 두 단계다. 이동 정의는 searchProductsNavigate 에 있다. const searchProducts = (productList as any).named_actions.searchProducts; - expect(searchProducts.handler).toBe('navigate'); - expect(searchProducts.params.path).toBe('/admin/ecommerce/products'); - expect(searchProducts.params.replace).toBe(true); - expect(searchProducts.params.mergeQuery).toBe(true); - expect(searchProducts.params.query.search_keyword).toBeDefined(); - expect(searchProducts.params.query.page).toBe(1); + expect(searchProducts.handler).toBe('sequence'); + expect(searchProducts.actions.map((a: any) => a.actionRef)).toEqual([ + 'resetExpandedOptionState', + 'searchProductsNavigate', + ]); + + const navigate = (productList as any).named_actions.searchProductsNavigate; + expect(navigate.handler).toBe('navigate'); + expect(navigate.params.path).toBe('/admin/ecommerce/products'); + expect(navigate.params.replace).toBe(true); + expect(navigate.params.mergeQuery).toBe(true); + expect(navigate.params.query.search_keyword).toBeDefined(); + expect(navigate.params.query.page).toBe(1); }); it('검색 버튼도 actionRef로 searchProducts를 참조해야 함', () => { diff --git a/modules/_bundled/sirsoft-ecommerce/resources/js/__tests__/layouts/productListNoCategoryBoolean.test.tsx b/modules/_bundled/sirsoft-ecommerce/resources/js/__tests__/layouts/productListNoCategoryBoolean.test.tsx index c5d92a9d..8830775c 100644 --- a/modules/_bundled/sirsoft-ecommerce/resources/js/__tests__/layouts/productListNoCategoryBoolean.test.tsx +++ b/modules/_bundled/sirsoft-ecommerce/resources/js/__tests__/layouts/productListNoCategoryBoolean.test.tsx @@ -15,7 +15,8 @@ import productList from '../../../layouts/admin/admin_ecommerce_product_list.jso describe('상품 목록 no_category/no_brand boolean 바인딩 (A19②)', () => { it('searchProducts named_action 의 no_category 표현식이 boolean true/null 로 평가되어야 함', () => { - const query = (productList as any).named_actions.searchProducts.params.query; + // 이동 정의는 searchProductsNavigate — searchProducts 는 초기화 후 이를 참조하는 sequence 다 + const query = (productList as any).named_actions.searchProductsNavigate.params.query; // 문자열 'true' 리터럴을 결과로 쓰지 않아야 함 (? true : null 형태) expect(query.no_category).toContain('? true : null'); diff --git a/modules/_bundled/sirsoft-ecommerce/resources/js/__tests__/layouts/productListOptionsLazy.test.ts b/modules/_bundled/sirsoft-ecommerce/resources/js/__tests__/layouts/productListOptionsLazy.test.ts new file mode 100644 index 00000000..9b345b3d --- /dev/null +++ b/modules/_bundled/sirsoft-ecommerce/resources/js/__tests__/layouts/productListOptionsLazy.test.ts @@ -0,0 +1,311 @@ +/** + * @file productListOptionsLazy.test.ts + * @description 관리자 상품목록 확장 행 옵션 지연 로딩 레이아웃 회귀 (#518 / 공개 #76) + * + * 목록 응답이 옵션을 더 이상 싣지 않으므로, 행을 펼치는 순간 옵션을 가져오는 경로가 레이아웃에 + * 선언돼 있어야 한다. 이 테스트는 실 레이아웃 JSON 을 읽어 그 선언을 고정한다. + * + * 특히 조용히 깨지는 두 가지를 검사한다. + * 1. `expandContext` 값이 `{{...}}` **단일 바인딩** 형태여야 한다. 엔진의 판정 정규식은 + * `}` 를 허용하지 않아, 맵 조회 같은 표현식은 미매칭 → 문자열 보간으로 떨어져 배열/객체가 + * JSON 문자열로 굳는다 (예외 없이 값만 망가진다) + * 2. "옵션 없음" 조건이 `Array.isArray` 로 로드 완료를 확인해야 한다. `!row.options` 로 두면 + * 아직 안 불러온 상태(=undefined)에서도 참이 되어 로딩 중 "옵션 없음" 이 깜빡인다 + * + * @scenario row_state=expanding,option_profile=mixed + * @effects list_response_omits_options, no_fetch_when_total_count_zero + * + * @vitest-environment jsdom + */ + +import { describe, it, expect } from 'vitest'; +import fs from 'fs'; +import path from 'path'; + +const PARTIALS_ROOT = path.resolve( + __dirname, + '../../../layouts/admin/partials/admin_ecommerce_product_list' +); + +const DATAGRID_PATH = path.join(PARTIALS_ROOT, '_partial_product_datagrid.json'); +const CONFIRM_MODAL_PATH = path.join(PARTIALS_ROOT, '_modal_bulk_confirm.json'); + +/** 엔진이 "식으로 평가"할 수 있는 단일 바인딩 형태 (G7CoreGlobals 의 판정과 동일) */ +const SINGLE_BINDING = /^\{\{([^}]+)\}\}$/; + +/** + * 레이아웃 JSON 을 읽습니다. + * + * @param file 파일 경로 + * @return 파싱된 JSON + */ +function readLayout(file: string): any { + return JSON.parse(fs.readFileSync(file, 'utf8')); +} + +/** + * 트리를 순회하며 조건에 맞는 노드를 모읍니다. + * + * @param node 시작 노드 + * @param predicate 수집 조건 + * @return 일치 노드 목록 + */ +function collectNodes(node: any, predicate: (n: any) => boolean): any[] { + const found: any[] = []; + + const walk = (current: any): void => { + if (Array.isArray(current)) { + current.forEach(walk); + + return; + } + + if (!current || typeof current !== 'object') { + return; + } + + if (predicate(current)) { + found.push(current); + } + + Object.values(current).forEach(walk); + }; + + walk(node); + + return found; +} + +/** DataGrid 노드를 찾습니다. */ +function findDataGrid(layout: any): any { + const grids = collectNodes(layout, (n) => n?.name === 'DataGrid' && !!n?.props?.expandChildren); + + expect(grids.length).toBe(1); + + return grids[0]; +} + +describe('상품목록 확장 행 옵션 지연 로딩 — 레이아웃 선언', () => { + const datagrid = readLayout(DATAGRID_PATH); + const grid = findDataGrid(datagrid); + + it('onExpandChange 가 옵션 로드 핸들러로 연결된다', () => { + const actions = collectNodes(datagrid, (n) => n?.event === 'onExpandChange'); + + expect(actions).toHaveLength(1); + expect(actions[0].handler).toBe('sirsoft-ecommerce.loadExpandedOptions'); + expect(actions[0].params).toEqual({ expandedRows: '{{$args[0]}}' }); + }); + + it('확장 상태 갱신과 옵션 로드가 한 액션으로 묶여 있다', () => { + // setState 로 expandedRows 를 **갱신**하는 액션이 남아 있으면 순서에 따라 갓 펼친 행이 + // 누락된다. 목록 구성이 바뀔 때의 **초기화**(빈 배열 대입)는 별개 목적이라 제외한다. + const staleSetState = collectNodes( + datagrid, + (n) => + n?.handler === 'setState' + && n?.params?.expandedRows !== undefined + && !(Array.isArray(n.params.expandedRows) && n.params.expandedRows.length === 0) + ); + + expect(staleSetState).toHaveLength(0); + }); + + it('expandContext 값이 모두 단일 바인딩 형태다', () => { + const context = grid.props.expandContext; + + expect(context).toBeDefined(); + + Object.entries(context).forEach(([key, value]) => { + expect(typeof value).toBe('string'); + expect( + SINGLE_BINDING.test(value as string), + `expandContext.${key} 가 단일 바인딩이 아니다: ${value}` + ).toBe(true); + }); + }); + + it('expandContext 에 로딩/에러 표시용 키가 노출된다', () => { + const context = grid.props.expandContext; + + expect(context.optionsLoadingIds).toBe('{{_local.optionsLoadingIds ?? []}}'); + expect(context.optionsErrorIds).toBe('{{_local.optionsErrorIds ?? []}}'); + }); + + it('"옵션 없음" 은 로드 완료(Array.isArray)를 확인한 뒤에만 표시된다', () => { + const noOptions = collectNodes( + datagrid, + (n) => typeof n?.id === 'string' && n.id.startsWith('no_options_message_') + ); + + expect(noOptions).toHaveLength(1); + expect(noOptions[0].if).toContain('Array.isArray(row.options)'); + expect(noOptions[0].if).toContain('row.options.length === 0'); + }); + + it('로딩/에러 블록이 존재하고 조건이 단일 바인딩이다', () => { + const loading = collectNodes( + datagrid, + (n) => typeof n?.id === 'string' && n.id.startsWith('options_loading_') + ); + const errored = collectNodes( + datagrid, + (n) => typeof n?.id === 'string' && n.id.startsWith('options_error_') + ); + + expect(loading).toHaveLength(1); + expect(errored).toHaveLength(1); + + [loading[0], errored[0]].forEach((node) => { + expect(SINGLE_BINDING.test(node.if), `if 가 단일 바인딩이 아니다: ${node.if}`).toBe(true); + }); + + expect(loading[0].if).toContain('optionsLoadingIds'); + expect(errored[0].if).toContain('optionsErrorIds'); + }); + + it('옵션 테이블은 로딩/에러 상태가 아닐 때만 렌더된다', () => { + const table = collectNodes( + datagrid, + (n) => n?.props?.className === 'overflow-x-auto' && typeof n?.if === 'string' + ); + + expect(table.length).toBeGreaterThanOrEqual(1); + + const condition = table[0].if; + + expect(SINGLE_BINDING.test(condition)).toBe(true); + expect(condition).toContain('optionsLoadingIds'); + expect(condition).toContain('optionsErrorIds'); + }); + + it('재시도 버튼이 재로드 핸들러로 연결된다', () => { + const retry = collectNodes( + datagrid, + (n) => n?.handler === 'sirsoft-ecommerce.retryExpandedOptions' + ); + + expect(retry).toHaveLength(1); + expect(retry[0].params).toEqual({ productId: '{{row.id}}' }); + }); +}); + +describe('일괄 변경 확인 모달 — 집계 표기', () => { + const modal = readLayout(CONFIRM_MODAL_PATH); + + it('옵션 개수는 배열 길이가 아니라 summary.optionCount 를 쓴다', () => { + const raw = JSON.stringify(modal); + + expect(raw).toContain('_global.bulkConfirmData.summary.optionCount'); + expect(raw).not.toContain('options_section|count={{_global.bulkConfirmData.options.length}}'); + }); + + it('집계 엔트리와 개별 엔트리가 서로 배타적으로 표시된다', () => { + const aggregate = collectNodes( + modal, + (n) => typeof n?.text === 'string' && n.text.includes('bulk_confirm.options_aggregate') + ); + + expect(aggregate).toHaveLength(1); + expect(aggregate[0].if).toBe('{{option.aggregate === true}}'); + + const individual = collectNodes( + modal, + (n) => n?.text === '{{option.productName}} > {{option.optionName}}' + ); + + expect(individual).toHaveLength(1); + expect(individual[0].if).toBe('{{option.aggregate !== true}}'); + }); +}); + +/** + * 목록 구성이 바뀌는 이동에서 펼침 상태가 초기화되는지 고정한다. + * + * 옵션은 목록 응답에 없고 펼칠 때만 채워지므로, 정렬·페이지·검색·프리셋으로 행 구성이 바뀐 뒤에도 + * 이전 펼침 상태가 남아 있으면 그 행은 옵션이 없는 채로 펼쳐진다 — 로딩도 "옵션 없음" 도 아닌 + * 빈 영역이 남는다. + * + * @scenario row_state=expanded,option_profile=active_only + * @effects expanded_state_reset_on_list_change + */ +describe('목록 구성 변경 시 펼침 상태 초기화', () => { + const LIST_PATH = path.resolve( + __dirname, + '../../../layouts/admin/admin_ecommerce_product_list.json' + ); + const FILTER_PATH = path.join(PARTIALS_ROOT, '_partial_filter_section.json'); + + /** 초기화 대상 키 — 하나라도 빠지면 그 흔적이 다음 목록으로 새어 나간다 */ + const RESET_KEYS = ['expandedRows', 'optionsLoadingIds', 'optionsErrorIds', 'selectedOptionIds']; + + /** + * setState 로 펼침 상태를 비우는 액션인지 판정합니다. + * + * @param node 검사 노드 + * @return 초기화 액션 여부 + */ + const isResetAction = (node: any): boolean => + node?.handler === 'setState' + && node?.params?.target === 'local' + && RESET_KEYS.every((key) => Array.isArray(node.params[key]) && node.params[key].length === 0); + + it('초기화 액션이 named_actions 에 단일 정의로 존재한다', () => { + const layout = readLayout(LIST_PATH); + const reset = layout.named_actions?.resetExpandedOptionState; + + expect(reset).toBeDefined(); + expect(isResetAction(reset)).toBe(true); + }); + + it('검색 실행이 초기화를 거친 뒤 이동한다', () => { + const layout = readLayout(LIST_PATH); + const search = layout.named_actions?.searchProducts; + + expect(search?.handler).toBe('sequence'); + expect(search.actions[0].actionRef).toBe('resetExpandedOptionState'); + expect(search.actions[1].actionRef).toBe('searchProductsNavigate'); + }); + + it('정렬 변경이 초기화를 거친 뒤 이동한다', () => { + const layout = readLayout(LIST_PATH); + + const sortSequences = collectNodes( + layout, + (n) => + n?.handler === 'sequence' + && Array.isArray(n.actions) + && n.actions.some( + (a: any) => typeof a?.params?.query?.sort_by === 'string' + ) + ); + + expect(sortSequences).toHaveLength(1); + expect(isResetAction(sortSequences[0].actions[0])).toBe(true); + }); + + it('페이지 이동이 초기화를 거친 뒤 이동한다', () => { + const grid = findDataGrid(readLayout(DATAGRID_PATH)); + + const onPageChange = (grid.actions ?? []).find((a: any) => a.event === 'onPageChange'); + + expect(onPageChange?.handler).toBe('sequence'); + expect(isResetAction(onPageChange.actions[0])).toBe(true); + expect(onPageChange.actions[1].handler).toBe('navigate'); + }); + + it('프리셋 적용과 필터 초기화도 초기화를 거친다', () => { + const filter = readLayout(FILTER_PATH); + + const navigates = collectNodes( + filter, + (n) => n?.handler === 'navigate' && n?.params?.path === '/admin/ecommerce/products' + ); + + expect(navigates.length).toBeGreaterThan(0); + + const refs = collectNodes(filter, (n) => n?.actionRef === 'resetExpandedOptionState'); + + expect(refs).toHaveLength(navigates.length); + }); +}); diff --git a/modules/_bundled/sirsoft-ecommerce/resources/js/__tests__/layouts/prunedListFieldConsumers.test.ts b/modules/_bundled/sirsoft-ecommerce/resources/js/__tests__/layouts/prunedListFieldConsumers.test.ts new file mode 100644 index 00000000..b0e0404a --- /dev/null +++ b/modules/_bundled/sirsoft-ecommerce/resources/js/__tests__/layouts/prunedListFieldConsumers.test.ts @@ -0,0 +1,123 @@ +/** + * 목록에서 뺀 값을 화면이 여전히 쓰는지 고정하는 회귀 테스트 (#518 / 공개 #76) + * + * 이번 변경은 여러 목록에서 "화면이 안 쓴다" 는 판단으로 값을 뺐다. 그 판단이 틀리면 응답만 + * 보는 테스트로는 잡히지 않는다 — 메뉴 목록에서 실제로 그렇게 데이터가 지워졌고, 배송정책 + * 목록에서는 국가 칩·배송비 요약 열이 조용히 비었다. + * + * 그래서 여기서는 **레이아웃 JSON 원본**을 읽어 두 가지를 함께 고정한다. + * 1. 화면이 그 필드를 그리고 있다 (소비 사실) + * 2. 그 화면의 목록 요청이 그 필드를 받아오도록 되어 있다 (공급 경로) + * 둘 중 하나만 바뀌면 실패한다. + * + * @vitest-environment node + */ + +import fs from 'fs'; +import path from 'path'; +import { describe, it, expect } from 'vitest'; + +const LAYOUT_ROOT = path.resolve(__dirname, '../../../layouts/admin'); +// 저장소 루트 — 이 모듈의 목록 계약을 소비하는 사용자 템플릿까지 함께 고정한다. +const REPO_ROOT = path.resolve(__dirname, '../../../../../../..'); + +function readLayout(relativePath: string): any { + return JSON.parse(fs.readFileSync(path.join(LAYOUT_ROOT, relativePath), 'utf-8')); +} + +function readRaw(relativePath: string): string { + return fs.readFileSync(path.join(LAYOUT_ROOT, relativePath), 'utf-8'); +} + +/** + * @scenario endpoint=active_list,opt_in=default + * @effects product_form_selector_still_draws_country_chips + */ +describe('배송정책 목록 — 국가별 설정 공급 경로', () => { + const LIST = 'admin_ecommerce_shipping_policy_list.json'; + const DATAGRID = 'partials/admin_ecommerce_shipping_policy_list/_partial_datagrid.json'; + + it('화면이 국가 칩·배송비 요약·국가별 설정을 그린다 (소비 사실)', () => { + const raw = readRaw(DATAGRID); + + // 존재를 먼저 확정한다 — 화면이 이 값을 쓰지 않게 되면 아래 공급 요구도 함께 풀어야 한다. + expect(raw).toContain('countries_display'); + expect(raw).toContain('fee_summary'); + expect(raw).toContain('country_settings'); + }); + + it('목록 요청이 무거운 전체 컬럼을 켜지 않는다 (기본이 표시용 필드를 공급한다)', () => { + // 서버 기본 응답이 목록 표시용 국가별 설정을 이미 싣는다. 여기서 opt-in 을 켜면 + // 화면이 쓰지도 않는 구간 설정·도서산간 설정·계산 API 설정까지 되받아, 프루닝 + // 효과가 0 이 된다 — 실제로 한 번 그렇게 되돌아갔다. + const layout = readLayout(LIST); + const source = (layout.data_sources ?? []).find( + (ds: any) => ds.id === 'shipping_policies', + ); + + expect(source).toBeDefined(); + expect(source.params ?? {}).not.toHaveProperty('with_country_settings'); + }); + + it('상품 등록/수정의 배송정책 선택기도 전체 컬럼을 켜지 않는다', () => { + const layout = readLayout('admin_ecommerce_product_form.json'); + const source = (layout.data_sources ?? []).find((ds: any) => + String(ds.endpoint ?? '').includes('/admin/shipping-policies'), + ); + + expect(source, '배송정책 선택기 데이터소스가 있어야 한다').toBeDefined(); + expect(source.params ?? {}).not.toHaveProperty('with_country_settings'); + }); +}); + +describe('마이페이지 주문내역 — 주문상품 공급 경로', () => { + const USER_LAYOUT = path.join(REPO_ROOT, 'templates/_bundled/sirsoft-basic/layouts/mypage/orders.json'); + + it('전량 나열 화면은 with_items 를 명시적으로 켠다 (공급 경로)', () => { + // 서버 기본은 대표 1건이다. 이 화면은 주문상품을 전부 나열하므로 켜야 한다 — + // 빠지면 주문마다 상품 한 줄만 남는다. + const layout = JSON.parse(fs.readFileSync(USER_LAYOUT, 'utf-8')); + const source = (layout.data_sources ?? []).find((ds: any) => + String(ds.endpoint ?? '').includes('/user/orders'), + ); + + expect(source, '주문내역 데이터소스가 있어야 한다').toBeDefined(); + expect(source.params).toHaveProperty('with_items'); + expect(String(source.params.with_items)).toBe('1'); + }); + + it('params 에 주석 키를 두지 않는다 (전부 쿼리스트링으로 전송된다)', () => { + // `_comment_*` 를 params 안에 두면 그 문장이 그대로 URL 쿼리로 나간다 — + // 브라우저 실측에서 한글 주석 한 문단이 요청 URL 에 실려 있는 것을 확인했다. + // 설명은 데이터소스 레벨 `comment` 에 둔다. + const layout = JSON.parse(fs.readFileSync(USER_LAYOUT, 'utf-8')); + const source = (layout.data_sources ?? []).find((ds: any) => + String(ds.endpoint ?? '').includes('/user/orders'), + ); + + const commentKeys = Object.keys(source.params ?? {}).filter((k) => k.startsWith('_comment')); + expect(commentKeys, `params 안의 주석 키: ${commentKeys.join(', ')}`).toEqual([]); + }); + + it('화면이 주문상품 배열을 실제로 순회한다 (소비 사실)', () => { + const partial = path.join(REPO_ROOT, 'templates/_bundled/sirsoft-basic/layouts/partials/mypage/orders/_list.json'); + + // 소비가 사라지면 위의 공급 요구도 함께 풀어야 한다. + expect(fs.readFileSync(partial, 'utf-8')).toContain('order.items'); + }); +}); + +describe('주문 목록 — 대표 상품/배송 표시', () => { + const DATAGRID = 'partials/admin_ecommerce_order_list/_partial_order_datagrid.json'; + + it('목록은 대표 1건과 "외 N건" 집계만 쓴다 (옵션 배열 순회 없음)', () => { + const raw = readRaw(DATAGRID); + + // 집계 필드를 실제로 쓰고 있어야 대표 1건 축약이 성립한다. + expect(raw).toContain('options_count'); + + // 옵션 배열 자체를 순회하면 목록이 다시 전량을 필요로 한다는 뜻이다. + expect(raw).not.toContain('row.options.map'); + expect(raw).not.toContain('"source": "row.options'); + }); +}); diff --git a/modules/_bundled/sirsoft-ecommerce/resources/js/__tests__/layouts/reviewImageConsumers.test.ts b/modules/_bundled/sirsoft-ecommerce/resources/js/__tests__/layouts/reviewImageConsumers.test.ts new file mode 100644 index 00000000..3ca6e3a9 --- /dev/null +++ b/modules/_bundled/sirsoft-ecommerce/resources/js/__tests__/layouts/reviewImageConsumers.test.ts @@ -0,0 +1,105 @@ +/** + * @file reviewImageConsumers.test.ts + * @description 리뷰 목록 첨부 이미지 — 소비처 고정 (#518 / 공개 #76) + * + * 회귀 배경: + * 목록 응답에서 리뷰 첨부 이미지를 빼면서, 관리자 리뷰 목록은 "확장 행이 썸네일을 그린다" 는 + * 실측으로 유지했으나 **공개 상품상세의 리뷰 탭은 놓쳤다**. `shop/show.json` 만 열어보고 + * "리뷰 첨부는 표시하지 않는다" 고 판단했는데, 실제로 그리는 곳은 그 파일이 include 하는 + * 파셜(`partials/shop/detail/_tab_reviews.json`)이었다. + * + * 이 결함은 조용하다. 썸네일이 `{{(review.images ?? []).length > 0}}` 가드 뒤에 있어 배열이 + * 없으면 예외도 콘솔 경고도 없이 그냥 안 그려진다. 응답만 보는 백엔드 테스트는 "뺐다" 를 + * 확인할 뿐이고, 그 값을 화면이 쓰고 있었다는 사실은 잡지 못한다. + * + * 그래서 이 파일은 **화면이 무엇을 그리는가** 를 고정한다. 여기서 참조가 발견되는 한, 그 + * 엔드포인트의 목록 응답은 첨부 이미지를 실어야 한다. + * + * 마킹은 아래 describe 블록마다 따로 붙인다 — 한 docblock 에서는 @scenario 가 첫 줄만 읽혀, + * 여기에 두 화면을 나열하면 뒤엣것이 조용히 누락된다. + */ + +import fs from 'fs'; +import path from 'path'; +import { describe, it, expect } from 'vitest'; + +const REPO_ROOT = path.resolve(__dirname, '../../../../../../..'); + +const PUBLIC_REVIEW_TAB = path.join( + REPO_ROOT, + 'templates/_bundled/sirsoft-basic/layouts/partials/shop/detail/_tab_reviews.json', +); +const PUBLIC_PRODUCT_DETAIL = path.join( + REPO_ROOT, + 'templates/_bundled/sirsoft-basic/layouts/shop/show.json', +); +const ADMIN_REVIEW_INDEX = path.join( + REPO_ROOT, + 'modules/_bundled/sirsoft-ecommerce/resources/layouts/admin/admin_ecommerce_product_review_index.json', +); + +/** + * 레이아웃 파일 원문을 읽습니다. + * + * @param filePath 레이아웃 파일 절대 경로 + * @returns 파일 원문 + */ +function readRaw(filePath: string): string { + return fs.readFileSync(filePath, 'utf-8'); +} + +/** + * @scenario surface=public_product_review_list,observation=consumer_screen + * @effects review_list_images_are_consumed_by_screen + */ +describe('공개 상품상세 리뷰 탭 — 첨부 이미지 소비 (#518 / 공개 #76)', () => { + it('상품상세가 리뷰 탭 파셜을 실제로 포함한다', () => { + // 이 연결이 끊기면 아래 소비 단언들이 "쓰이지 않는 파일" 을 검사하게 된다. + expect(readRaw(PUBLIC_PRODUCT_DETAIL)).toContain( + 'partials/shop/detail/_tab_reviews.json', + ); + }); + + it('리뷰 탭이 공개 리뷰 목록 엔드포인트를 데이터소스로 쓴다', () => { + const detail = JSON.parse(readRaw(PUBLIC_PRODUCT_DETAIL)); + const source = (detail.data_sources ?? []).find((ds: any) => ds.id === 'reviews'); + + expect(source, '리뷰 데이터소스가 있어야 한다').toBeDefined(); + expect(String(source.endpoint)).toContain('/reviews'); + }); + + it('리뷰 탭이 첨부 이미지 썸네일을 그린다 (목록에서 빼면 이 자리가 빈다)', () => { + const raw = readRaw(PUBLIC_REVIEW_TAB); + + // 썸네일 3장은 각각 배열 인덱스를 직접 읽는다. + expect(raw).toContain('review.images[0].download_url'); + expect(raw).toContain('review.images[1].download_url'); + expect(raw).toContain('review.images[2].download_url'); + }); + + it('리뷰 탭이 이미지 모달에 첨부 배열 전체를 넘긴다', () => { + expect(readRaw(PUBLIC_REVIEW_TAB)).toContain('"images": "{{review.images ?? []}}"'); + }); + + it('첨부 개수만으로는 대체되지 않는다 (개수는 노출 조건일 뿐 그림은 배열이 그린다)', () => { + const raw = readRaw(PUBLIC_REVIEW_TAB); + + // 개수 조건이 먼저 켜지고 그 안에서 배열을 읽는다. 개수만 내려주면 컨테이너는 열리는데 + // 안이 비는 상태가 된다 — 가장 나쁜 형태의 조용한 실패다. + expect(raw).toContain('review.image_count > 0'); + expect(raw).toContain('(review.images ?? []).length > 0'); + }); +}); + +/** + * @scenario surface=admin_product_review_list,observation=consumer_screen + * @effects review_list_images_are_consumed_by_screen + */ +describe('관리자 리뷰 목록 — 첨부 이미지 소비 (#518 / 공개 #76)', () => { + it('확장 행이 첨부 이미지를 그리고 미리보기 모달에 배열을 넘긴다', () => { + const raw = readRaw(ADMIN_REVIEW_INDEX); + + expect(raw).toContain('"rowImages": "{{row.images ?? []}}"'); + expect(raw).toContain('"images": "{{rowImages ?? []}}"'); + }); +}); diff --git a/modules/_bundled/sirsoft-ecommerce/resources/js/handlers/bulkUpdateHandlers.ts b/modules/_bundled/sirsoft-ecommerce/resources/js/handlers/bulkUpdateHandlers.ts index 8c1e10d1..e1992569 100644 --- a/modules/_bundled/sirsoft-ecommerce/resources/js/handlers/bulkUpdateHandlers.ts +++ b/modules/_bundled/sirsoft-ecommerce/resources/js/handlers/bulkUpdateHandlers.ts @@ -9,6 +9,7 @@ */ import type { ActionContext } from '../types'; +import { reloadExpandedOptions } from './expandedOptionsHandlers'; // Logger 설정 (G7Core 초기화 전에도 동작하도록 폴백 포함) const logger = ((window as any).G7Core?.createLogger?.('Ecom:BulkUpdate')) ?? { @@ -96,6 +97,10 @@ interface BulkConfirmData { productName: string; optionName: string; changes: string; + /** 개별 열거 대신 상품 단위로 묶은 항목인지 (목록에서 펼치지 않은 상품) */ + aggregate?: boolean; + /** aggregate 항목이 대표하는 옵션 수 (비활성 포함 전체) */ + optionCount?: number; }>; summary: { productCount: number; @@ -444,8 +449,8 @@ function bulkUpdateProductsInternal( ); // 상태 초기화 resetBulkState(G7Core); - // DataSource 리프레시 - G7Core.dataSource.refetch('products'); + // DataSource 리프레시 + 펼친 행 옵션 재로드 + refetchProductsAndReloadExpanded(G7Core); // 모달 닫기 G7Core.modal.close(); }) @@ -570,8 +575,8 @@ function bulkUpdateOptionsInternal( ); // 상태 초기화 resetBulkState(G7Core); - // DataSource 리프레시 - G7Core.dataSource.refetch('products'); + // DataSource 리프레시 + 펼친 행 옵션 재로드 + refetchProductsAndReloadExpanded(G7Core); // 모달 닫기 G7Core.modal.close(); }) @@ -584,6 +589,27 @@ function bulkUpdateOptionsInternal( }); } +/** + * 목록을 새로 받은 뒤 펼쳐진 행의 옵션을 다시 채웁니다. + * + * 목록 응답은 옵션을 싣지 않으므로, 리페치만 하면 펼친 행이 열린 채 옵션만 사라진다 + * (`resetBulkState` 는 `expandedRows` 를 지우지 않는다 — 저장 후에도 보던 행은 그대로 열려 있는 + * 것이 맞다). 저장 결과를 확인해야 하므로 `force: true` 로 서버 값을 다시 가져온다. + * + * @param G7Core G7Core 인스턴스 + * @return void + */ +function refetchProductsAndReloadExpanded(G7Core: G7CoreInterface): void { + Promise.resolve(G7Core.dataSource.refetch('products')) + .then(() => { + reloadExpandedOptions(G7Core, { force: true }); + }) + .catch((error: any) => { + // 옵션 재로드 실패의 사용자 통지는 reloadExpandedOptions 내부가 담당한다 + logger.error('[bulkUpdate] Failed to refetch products:', error); + }); +} + /** * 일괄 변경 상태 초기화 */ @@ -828,24 +854,52 @@ export function buildConfirmDataHandler(action: ActionWithParams, context: Actio } // 옵션 변경 사항 수집 (상품 체크 시 모든 옵션 포함) - (p.options || []).forEach((opt: any) => { - const optionKey = `${p.id}-${opt.id}`; - const isOptionModified = opt._modified || modifiedOptionIds.includes(optionKey); + // + // 목록은 옵션을 기본 적재하지 않으므로, 펼치지 않은 상품은 `options` 가 아예 없다 + // (`undefined`). 그렇다고 적용 범위가 줄어드는 것은 아니다 — 서버가 상품 ID 를 받아 + // 그 상품의 모든 옵션(비활성 포함)으로 전개하기 때문이다. 그래서 열거는 못 하더라도 + // **개수는 반드시 세어** 확인 모달의 수치가 실제 적용 건수와 일치하게 한다. + const optionsApplied = hasBulkChanges + && !!(globalState.bulkPriceCondition || globalState.bulkStockCondition); - const optChanges = collectOptionChanges(opt, optionKey, isOptionModified); + if (Array.isArray(p.options)) { + p.options.forEach((opt: any) => { + const optionKey = `${p.id}-${opt.id}`; + const isOptionModified = opt._modified || modifiedOptionIds.includes(optionKey); - if (optChanges.length > 0 || (hasBulkChanges && (globalState.bulkPriceCondition || globalState.bulkStockCondition)) || isOptionModified) { + const optChanges = collectOptionChanges(opt, optionKey, isOptionModified); + + if (optChanges.length > 0 || optionsApplied || isOptionModified) { + confirmData.options.push({ + productId: p.id, + optionId: opt.id, + productName: localizeValue(p.name), + optionName: opt.option_name_localized || localizeValue(opt.option_name) || opt.name || '', + changes: optChanges.length > 0 ? optChanges.join(', ') : (isOptionModified + ? (G7Core.t('sirsoft-ecommerce.admin.product.messages.bulk_summary_inline_modified') || 'Inline modified') + : (G7Core.t('sirsoft-ecommerce.admin.product.messages.bulk_summary_bulk_applied') || 'Bulk changes applied')), + }); + } + }); + } else if (optionsApplied) { + // 미펼침 상품 — 집계 엔트리 1건으로 대체한다. `options_total_count` 는 비활성을 + // 포함한 전체 옵션 수로, 서버가 전개하는 모집단과 같다(활성 수인 `options_count` + // 를 쓰면 미펼침 상품의 요약이 실제보다 적게 나온다). + const aggregateCount = Number(p.options_total_count) || 0; + + if (aggregateCount > 0) { confirmData.options.push({ + aggregate: true, productId: p.id, - optionId: opt.id, + optionId: 0, + optionCount: aggregateCount, productName: localizeValue(p.name), - optionName: opt.option_name_localized || localizeValue(opt.option_name) || opt.name || '', - changes: optChanges.length > 0 ? optChanges.join(', ') : (isOptionModified - ? (G7Core.t('sirsoft-ecommerce.admin.product.messages.bulk_summary_inline_modified') || 'Inline modified') - : (G7Core.t('sirsoft-ecommerce.admin.product.messages.bulk_summary_bulk_applied') || 'Bulk changes applied')), + optionName: '', + changes: collectOptionChanges({}, `${p.id}-*`, false).join(', ') + || (G7Core.t('sirsoft-ecommerce.admin.product.messages.bulk_summary_bulk_applied') || 'Bulk changes applied'), }); } - }); + } }); } @@ -880,7 +934,13 @@ export function buildConfirmDataHandler(action: ActionWithParams, context: Actio } confirmData.summary.productCount = confirmData.products.length; - confirmData.summary.optionCount = confirmData.options.length; + + // 집계 엔트리는 배열에서 1칸을 쓰지만 실제로는 N건을 대표한다. 그래서 개수는 배열 길이가 + // 아니라 "열거분 + 집계분" 으로 센다 — 전부 펼친 경우와 전부 미펼침인 경우의 수치가 같아야 한다. + confirmData.summary.optionCount = confirmData.options.reduce( + (sum, entry) => sum + (entry.aggregate ? (entry.optionCount || 0) : 1), + 0 + ); // 글로벌 상태에 확인 데이터 저장 G7Core.state.set({ diff --git a/modules/_bundled/sirsoft-ecommerce/resources/js/handlers/expandedOptionsHandlers.ts b/modules/_bundled/sirsoft-ecommerce/resources/js/handlers/expandedOptionsHandlers.ts new file mode 100644 index 00000000..7294597c --- /dev/null +++ b/modules/_bundled/sirsoft-ecommerce/resources/js/handlers/expandedOptionsHandlers.ts @@ -0,0 +1,384 @@ +/** + * 상품 목록 확장 행 옵션 지연 로딩 핸들러 + * + * 관리자 상품목록은 옵션을 기본 적재하지 않는다(한 페이지를 여는 것만으로 그 페이지 전 상품의 + * 옵션이 응답에 실리기 때문). 옵션 상세는 행을 펼칠 때 배치 엔드포인트로 가져와 + * **products 데이터소스의 `product.options` 에 그대로 써넣는다.** + * + * 별도 캐시(`_local.optionsByProduct`)를 두지 않는 이유: + * - 옵션을 소비하는 기존 핸들러 6종(updateOptionField / setDefaultOption / toggleOption / + * syncProductSelection / getProductOptionStates / bulkUpdate)이 전부 `product.options` 를 + * 읽고 쓴다. 데이터소스에 병합하면 이들은 한 줄도 고칠 게 없다 + * - 사본을 두면 인라인 편집 결과가 두 곳에 존재해 stale 사본 버그가 새로 생긴다 + * - `expandContext` 는 `{{...}}` 단일 바인딩만 식으로 평가한다. 맵 조회 표현식 + * (`_local.optionsByProduct[row.id] || {}`)은 `}` 때문에 그 판정에 걸려 문자열로 굳는다 + */ + +import type { ActionContext } from '../types'; + +// Logger 설정 (G7Core 초기화 전에도 동작하도록 폴백 포함) +const logger = ((window as any).G7Core?.createLogger?.('Ecom:ExpandedOptions')) ?? { + log: (...args: unknown[]) => console.log('[Ecom:ExpandedOptions]', ...args), + warn: (...args: unknown[]) => console.warn('[Ecom:ExpandedOptions]', ...args), + error: (...args: unknown[]) => console.error('[Ecom:ExpandedOptions]', ...args), +}; + +/** 옵션 배치 조회 엔드포인트 */ +const OPTIONS_ENDPOINT = '/api/modules/sirsoft-ecommerce/admin/products/options'; + +/** 한 번에 요청할 수 있는 상품 수 (서버 FormRequest 의 max 와 동일) */ +const MAX_BATCH_SIZE = 100; + +interface LoadExpandedOptionsParams { + /** 확장된 행 ID 목록 (DataGrid onExpandChange 인자) */ + expandedRows?: Array; + /** 확장 상태와 무관하게 특정 상품만 로드할 때 */ + productIds?: Array; + /** 단일 상품만 지정할 때 (재시도 버튼 등 — 레이아웃에서 배열 리터럴을 피하기 위함) */ + productId?: number | string; + /** 이미 로드된 행도 다시 가져올지 여부 (저장 후 재로드) */ + force?: boolean; + /** 대상 데이터소스 ID */ + dataSourceId?: string; +} + +interface ActionWithParams { + handler: string; + params?: LoadExpandedOptionsParams; + [key: string]: any; +} + +/** + * 진행 중인 요청의 상품 ID 집합 (모듈 스코프) + * + * 빠르게 접었다 펴기를 반복하면 같은 상품에 대한 요청이 겹친다. 이미 날아간 요청이 있는 상품은 + * 대상에서 빼서 중복 호출을 병합한다. + */ +const inFlight = new Set(); + +/** + * 테스트에서 모듈 스코프 상태를 초기화하기 위한 진입점. + */ +export function __resetExpandedOptionsState(): void { + inFlight.clear(); +} + +/** + * products 데이터소스의 상품 배열을 돌려줍니다. + * + * @param G7Core G7Core 인스턴스 + * @param dataSourceId 데이터소스 ID + * @return 상품 배열 (없으면 빈 배열) + */ +function readProducts(G7Core: any, dataSourceId: string): any[] { + const current = G7Core?.dataSource?.get?.(dataSourceId); + const products = current?.data?.data; + + return Array.isArray(products) ? products : []; +} + +/** + * 상품 ID → 옵션 배열 맵을 데이터소스에 병합합니다. + * + * 요청을 보낸 시점의 배열을 캡처해 두고 나중에 그대로 쓰면, 응답을 기다리는 동안 이뤄진 + * 인라인 편집이 통째로 사라진다. 그래서 **콜백 안에서 데이터소스를 다시 읽어** 병합한다. + * + * @param G7Core G7Core 인스턴스 + * @param dataSourceId 데이터소스 ID + * @param optionsByProductId 상품 ID(문자열) → 옵션 배열 + * @return void + */ +function mergeOptionsIntoDataSource( + G7Core: any, + dataSourceId: string, + optionsByProductId: Record +): void { + const current = G7Core?.dataSource?.get?.(dataSourceId); + const products = current?.data?.data; + + if (!current || !Array.isArray(products)) { + logger.warn('[loadExpandedOptions] DataSource not available for merge:', dataSourceId); + + return; + } + + const merged = products.map((product: any) => { + const key = String(product?.id); + + if (!Object.prototype.hasOwnProperty.call(optionsByProductId, key)) { + return product; + } + + return { ...product, options: optionsByProductId[key] }; + }); + + G7Core.dataSource.set(dataSourceId, { + ...current, + data: { + ...current.data, + data: merged, + }, + }); +} + +/** + * 체크된 상품의 옵션을 옵션 선택 상태에 합칩니다. + * + * 상품을 체크한 뒤 행을 펼치면, 옵션이 그제서야 도착하므로 옵션 체크박스가 비어 보인다. + * 실제 저장 범위는 서버가 상품 ID 로 전개하므로 동작에는 문제가 없지만 표시가 어긋난다. + * + * @param G7Core G7Core 인스턴스 + * @param optionsByProductId 상품 ID(문자열) → 옵션 배열 + * @return void + */ +function hydrateSelectedOptionIds(G7Core: any, optionsByProductId: Record): void { + const localState = G7Core?.state?.getLocal?.() || {}; + const selectedItems: Array = localState.selectedItems || []; + + if (selectedItems.length === 0) { + return; + } + + const selectedProductKeys = new Set(selectedItems.map((id) => String(id))); + const nextSelected = new Set(localState.selectedOptionIds || []); + let changed = false; + + Object.entries(optionsByProductId).forEach(([productKey, options]) => { + if (!selectedProductKeys.has(productKey)) { + return; + } + + options.forEach((option: any) => { + const optionKey = `${productKey}-${option?.id}`; + + if (!nextSelected.has(optionKey)) { + nextSelected.add(optionKey); + changed = true; + } + }); + }); + + if (changed) { + G7Core.state.setLocal({ selectedOptionIds: Array.from(nextSelected) }); + } +} + +/** + * 로딩/에러 표시 상태를 갱신합니다. + * + * @param G7Core G7Core 인스턴스 + * @param mutate 현재 상태를 받아 다음 상태를 만드는 함수 + * @return void + */ +function updateProgressState( + G7Core: any, + mutate: (loading: string[], errored: string[]) => { loading: string[]; errored: string[] } +): void { + const localState = G7Core?.state?.getLocal?.() || {}; + const next = mutate(localState.optionsLoadingIds || [], localState.optionsErrorIds || []); + + G7Core.state.setLocal({ + optionsLoadingIds: next.loading, + optionsErrorIds: next.errored, + }); +} + +/** + * 확장된 행의 옵션을 로드합니다. + * + * 확장 상태 갱신(`_local.expandedRows`)과 옵션 로드를 한 액션으로 묶는다. 레이아웃에서 setState + * 와 로드를 두 액션으로 나열하면 실행 순서에 따라 방금 펼친 행이 대상에서 빠질 수 있다. + * + * @param action 액션 객체 (params 포함) + * @param context 액션 컨텍스트 + * @return void + */ +export function loadExpandedOptionsHandler(action: ActionWithParams, context: ActionContext): void { + const params = (action.params || {}) as LoadExpandedOptionsParams; + const dataSourceId = params.dataSourceId || 'products'; + const force = params.force === true; + + const G7Core = (window as any).G7Core; + + if (!G7Core?.dataSource?.get || !G7Core?.dataSource?.set || !G7Core?.state?.setLocal) { + logger.warn('[loadExpandedOptions] G7Core dataSource/state API is not available'); + + return; + } + + // 확장 상태를 **먼저** 반영한다 (레이아웃의 setState 액션을 이 핸들러가 흡수한다) + if (Array.isArray(params.expandedRows)) { + G7Core.state.setLocal({ expandedRows: params.expandedRows }); + } + + const requestedIds = Array.isArray(params.productIds) + ? params.productIds + : (params.productId !== undefined && params.productId !== null + ? [params.productId] + : (Array.isArray(params.expandedRows) + ? params.expandedRows + : (G7Core.state.getLocal?.()?.expandedRows || []))); + + if (!Array.isArray(requestedIds) || requestedIds.length === 0) { + return; + } + + const products = readProducts(G7Core, dataSourceId); + const productByKey = new Map(products.map((p: any) => [String(p?.id), p])); + + const toFetch: string[] = []; + const emptyByLocalKnowledge: Record = {}; + + requestedIds.forEach((rawId) => { + const key = String(rawId); + const product = productByKey.get(key); + + if (!product) { + return; + } + + // 배열 여부로 판정한다. `undefined` 는 "아직 안 불러옴", `[]` 는 "불러왔고 옵션이 없음". + // 이걸 구분하지 못하면 옵션 0건 상품을 펼칠 때마다 매번 다시 요청한다. + if (!force && Array.isArray(product.options)) { + return; + } + + // 전체 옵션 수가 0 이면 네트워크 없이 로컬에서 확정한다 + if (Number(product.options_total_count) === 0) { + emptyByLocalKnowledge[key] = []; + + return; + } + + if (inFlight.has(key)) { + return; + } + + toFetch.push(key); + }); + + if (Object.keys(emptyByLocalKnowledge).length > 0) { + mergeOptionsIntoDataSource(G7Core, dataSourceId, emptyByLocalKnowledge); + } + + if (toFetch.length === 0) { + return; + } + + if (toFetch.length > MAX_BATCH_SIZE) { + logger.warn( + `[loadExpandedOptions] Requested ${toFetch.length} products, truncating to ${MAX_BATCH_SIZE}` + ); + toFetch.length = MAX_BATCH_SIZE; + } + + toFetch.forEach((key) => inFlight.add(key)); + + updateProgressState(G7Core, (loading, errored) => ({ + loading: Array.from(new Set([...loading, ...toFetch])), + errored: errored.filter((id) => !toFetch.includes(id)), + })); + + G7Core.api + .get(OPTIONS_ENDPOINT, { params: { product_ids: toFetch.map((id) => Number(id)) } }) + .then((response: any) => { + const payload = response?.data || {}; + const optionsMap = payload.options || {}; + + const merged: Record = {}; + + // 요청한 ID 는 응답에 없더라도 `[]` 로 확정한다. 그러지 않으면 권한 밖이거나 사라진 + // 상품의 행이 영원히 스켈레톤 상태로 남는다. + toFetch.forEach((key) => { + const options = optionsMap[key]; + merged[key] = Array.isArray(options) ? options : []; + }); + + mergeOptionsIntoDataSource(G7Core, dataSourceId, merged); + hydrateSelectedOptionIds(G7Core, merged); + + updateProgressState(G7Core, (loading, errored) => ({ + loading: loading.filter((id) => !toFetch.includes(id)), + errored: errored.filter((id) => !toFetch.includes(id)), + })); + }) + .catch((error: any) => { + logger.error('[loadExpandedOptions] Failed to load options:', error); + + updateProgressState(G7Core, (loading, errored) => ({ + loading: loading.filter((id) => !toFetch.includes(id)), + errored: Array.from(new Set([...errored, ...toFetch])), + })); + + const message = error?.response?.data?.message + || G7Core.t?.('sirsoft-ecommerce.admin.product.table.options.load_failed') + || 'Failed to load product options.'; + + G7Core.toast?.error?.(message); + }) + .finally(() => { + toFetch.forEach((key) => inFlight.delete(key)); + }); +} + +/** + * 현재 펼쳐진 모든 행의 옵션을 다시 로드합니다. + * + * 일괄 저장 후 `refetch('products')` 로 목록을 새로 받으면 옵션은 응답에 없으므로, 펼친 행은 + * 열린 채 옵션만 사라진다(`resetBulkState` 는 `expandedRows` 를 지우지 않는다). 리페치 직후 + * 이 함수를 이어 붙여 펼침 상태와 내용이 어긋나지 않게 한다. + * + * @param G7Core G7Core 인스턴스 + * @param options 로드 옵션 (force: 이미 로드된 행도 다시 가져올지) + * @return void + */ +export function reloadExpandedOptions( + G7Core: any, + options: { force?: boolean; dataSourceId?: string } = {} +): void { + const expandedRows = G7Core?.state?.getLocal?.()?.expandedRows || []; + + if (!Array.isArray(expandedRows) || expandedRows.length === 0) { + return; + } + + loadExpandedOptionsHandler( + { + handler: 'sirsoft-ecommerce.loadExpandedOptions', + params: { + productIds: expandedRows, + force: options.force === true, + dataSourceId: options.dataSourceId || 'products', + }, + }, + {} as ActionContext + ); +} + +/** + * 옵션 로드 실패 행을 다시 시도하는 핸들러 (에러 블록의 재시도 버튼용). + * + * 재시도 버튼은 실패한 **그 행** 에 붙어 있어 단수 `productId` 를 보낸다. 단수 키를 함께 + * 포워딩하지 않으면 대상 산출이 `expandedRows` 폴백으로 떨어져, 한 행 재시도가 펼쳐 둔 모든 + * 행을 `force` 로 다시 불러온다(이미 성공한 행까지 재요청 + 인라인 편집분 덮어쓰기). + * + * @param action 액션 객체 (params.productId 또는 params.productIds) + * @param context 액션 컨텍스트 + * @return void + */ +export function retryExpandedOptionsHandler(action: ActionWithParams, context: ActionContext): void { + const params = (action.params || {}) as LoadExpandedOptionsParams; + + loadExpandedOptionsHandler( + { + handler: 'sirsoft-ecommerce.retryExpandedOptions', + params: { + productId: params.productId, + productIds: params.productIds, + force: true, + dataSourceId: params.dataSourceId, + }, + }, + context + ); +} diff --git a/modules/_bundled/sirsoft-ecommerce/resources/js/handlers/index.ts b/modules/_bundled/sirsoft-ecommerce/resources/js/handlers/index.ts index 9a11e29d..6607dc30 100644 --- a/modules/_bundled/sirsoft-ecommerce/resources/js/handlers/index.ts +++ b/modules/_bundled/sirsoft-ecommerce/resources/js/handlers/index.ts @@ -22,6 +22,12 @@ import { import { generateCopyProductCodeHandler } from './generateCopyProductCode'; import { copyProductHandler } from './copyProduct'; +// 목록 확장 행 옵션 지연 로딩 +import { + loadExpandedOptionsHandler, + retryExpandedOptionsHandler, +} from './expandedOptionsHandlers'; + // 카테고리 관련 핸들러 import { selectCategoryHandler, @@ -268,6 +274,9 @@ export const handlerMap = { toggleAllOptionsInRow: toggleAllOptionsInRowHandler, getProductOptionStates: getProductOptionStatesHandler, syncProductSelection: syncProductSelectionHandler, + // 목록 확장 행 옵션 지연 로딩 + loadExpandedOptions: loadExpandedOptionsHandler, + retryExpandedOptions: retryExpandedOptionsHandler, // 상품 폼 모달 관련 핸들러 generateCopyProductCode: generateCopyProductCodeHandler, copyProduct: copyProductHandler, diff --git a/modules/_bundled/sirsoft-ecommerce/resources/js/handlers/updateOptionField.ts b/modules/_bundled/sirsoft-ecommerce/resources/js/handlers/updateOptionField.ts index 4fb8266c..03a9190e 100644 --- a/modules/_bundled/sirsoft-ecommerce/resources/js/handlers/updateOptionField.ts +++ b/modules/_bundled/sirsoft-ecommerce/resources/js/handlers/updateOptionField.ts @@ -94,6 +94,13 @@ export function updateOptionFieldHandler( // (발생 시 상품의 selling_price 도 modifiedProductFields 에 기록해야 일괄 저장에 포함됨) let productSellingPriceSynced = false; + // 대상 옵션을 실제로 찾았는지 추적. + // + // 옵션 지연 로딩(#518) 이후 목록 행의 `options` 는 펼치기 전까지 `undefined` 다. 찾지 + // 못한 채로 아래 modified 추적을 돌리면 존재하지 않는 옵션이 수정 대상으로 기록되고, + // 일괄 저장이 그 유령 키를 `option_items` 에 실어 보낸다. + let optionFound = false; + // 상품 목록에서 해당 상품과 옵션 찾아서 업데이트 const updatedProducts = productsArray.map((product: any) => { if (String(product.id) === String(productId)) { @@ -109,6 +116,8 @@ export function updateOptionFieldHandler( return product; } + optionFound = true; + const currentOption = options[optionIndex]; // 값이 변경되지 않았으면 업데이트하지 않음 (성능 최적화) @@ -271,6 +280,15 @@ export function updateOptionFieldHandler( return product; }); + // 대상 옵션을 찾지 못했으면 아무것도 바꾸지 않는다. + // + // 아직 옵션을 불러오지 않은 행(`options === undefined`)이나 이미 삭제된 옵션이 여기에 + // 해당한다. 그대로 진행하면 데이터소스를 같은 값으로 다시 써서 불필요한 리렌더를 만들고, + // 존재하지 않는 옵션이 modified 로 기록돼 일괄 저장 페이로드에 실린다. + if (!optionFound) { + return; + } + // 데이터소스 업데이트 (UI 자동 리렌더링) // 구조: { success, data: { data: [...], pagination, statistics } } G7Core.dataSource.set(dataSourceId, { diff --git a/modules/_bundled/sirsoft-ecommerce/resources/lang/partial/en/admin/product.json b/modules/_bundled/sirsoft-ecommerce/resources/lang/partial/en/admin/product.json index 33bc7ed7..aa93d2fa 100644 --- a/modules/_bundled/sirsoft-ecommerce/resources/lang/partial/en/admin/product.json +++ b/modules/_bundled/sirsoft-ecommerce/resources/lang/partial/en/admin/product.json @@ -189,7 +189,10 @@ "safe_stock": "Safety Stock", "active": "Active" }, - "no_options": "No options registered" + "no_options": "No options registered", + "loading": "Loading options...", + "load_failed": "Failed to load options.", + "retry": "Retry" }, "shipping": { "free": "Free Shipping", @@ -593,7 +596,8 @@ "price_change": "Bulk price change", "stock_change": "Bulk stock change", "no_changes": "No items to change.", - "error_section": "Change failed" + "error_section": "Change failed", + "options_aggregate": "{{product}} > all {{count}} option(s)" }, "bulk_placeholder": { "sales_status": "Select Sales Status", @@ -1203,4 +1207,4 @@ "manage_notice_template": "Manage Product Notices", "create_shipping_policy": "Create Shipping Policy" } -} \ No newline at end of file +} diff --git a/modules/_bundled/sirsoft-ecommerce/resources/lang/partial/ko/admin/product.json b/modules/_bundled/sirsoft-ecommerce/resources/lang/partial/ko/admin/product.json index fca84174..b25cc6a6 100644 --- a/modules/_bundled/sirsoft-ecommerce/resources/lang/partial/ko/admin/product.json +++ b/modules/_bundled/sirsoft-ecommerce/resources/lang/partial/ko/admin/product.json @@ -189,7 +189,10 @@ "safe_stock": "안전재고", "active": "사용" }, - "no_options": "등록된 옵션이 없습니다" + "no_options": "등록된 옵션이 없습니다", + "loading": "옵션을 불러오는 중입니다...", + "load_failed": "옵션을 불러오지 못했습니다.", + "retry": "다시 시도" }, "shipping": { "free": "무료배송", @@ -593,7 +596,8 @@ "price_change": "판매가 일괄 변경", "stock_change": "재고 일괄 변경", "no_changes": "변경할 항목이 없습니다.", - "error_section": "변경에 실패했습니다" + "error_section": "변경에 실패했습니다", + "options_aggregate": "{{product}} > 옵션 {{count}}개 전체" }, "bulk_placeholder": { "sales_status": "판매 상태 선택", @@ -1203,4 +1207,4 @@ "manage_notice_template": "상품정보제공고시 관리", "create_shipping_policy": "배송정책 생성" } -} \ No newline at end of file +} diff --git a/modules/_bundled/sirsoft-ecommerce/resources/layouts/admin/admin_ecommerce_product_list.json b/modules/_bundled/sirsoft-ecommerce/resources/layouts/admin/admin_ecommerce_product_list.json index 0b08c040..983deb88 100644 --- a/modules/_bundled/sirsoft-ecommerce/resources/layouts/admin/admin_ecommerce_product_list.json +++ b/modules/_bundled/sirsoft-ecommerce/resources/layouts/admin/admin_ecommerce_product_list.json @@ -132,7 +132,18 @@ ] }, "named_actions": { - "searchProducts": { + "resetExpandedOptionState": { + "comment": "목록 구성이 바뀌는 이동(검색·정렬·페이지·프리셋·초기화) 앞에 둔다. 펼침 상태를 이어받으면 옵션이 아직 없는 행이 펼쳐진 채로 빈 영역을 보인다.", + "handler": "setState", + "params": { + "target": "local", + "expandedRows": [], + "optionsLoadingIds": [], + "optionsErrorIds": [], + "selectedOptionIds": [] + } + }, + "searchProductsNavigate": { "handler": "navigate", "params": { "path": "/admin/ecommerce/products", @@ -164,6 +175,13 @@ }, "transition_overlay_target": "product_datagrid__body" } + }, + "searchProducts": { + "handler": "sequence", + "actions": [ + { "actionRef": "resetExpandedOptionState" }, + { "actionRef": "searchProductsNavigate" } + ] } }, "data_sources": [ @@ -391,7 +409,7 @@ "name": "Icon", "props": { "name": "refresh-cw", - "className": "w-5 h-5" + "className": "text-xl" } } ] @@ -410,7 +428,7 @@ "name": "Icon", "props": { "name": "plus", - "className": "w-4 h-4" + "className": "text-base" } }, { @@ -590,6 +608,7 @@ "name": "Button", "props": { "type": "button", + "data-testid": "product-bulk-stock-open", "className": "{{_global.bulkStockCondition ? 'px-3 py-1.5 text-sm border border-blue-500 dark:border-blue-400 bg-blue-50 dark:bg-blue-900/20 text-info-soft rounded-lg hover:bg-blue-100 dark:hover:bg-blue-900/30 whitespace-nowrap disabled:opacity-50 disabled:cursor-not-allowed' : 'px-3 py-1.5 text-body border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 whitespace-nowrap disabled:opacity-50 disabled:cursor-not-allowed'}}", "disabled": "{{products?.data?.abilities?.can_update !== true}}" }, @@ -632,7 +651,8 @@ "name": "Button", "props": { "type": "button", - "className": "px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 font-medium transition-colors disabled:opacity-50 disabled:cursor-not-allowed", + "data-testid": "product-bulk-apply", + "className": "px-4 py-2 bg-blue-600 dark:bg-blue-500 text-white dark:text-white rounded-lg hover:bg-blue-700 dark:hover:bg-blue-600 font-medium transition-colors disabled:opacity-50 disabled:cursor-not-allowed", "disabled": "{{((!_local.selectedItems || _local.selectedItems.length === 0) && (!_local.selectedOptionIds || _local.selectedOptionIds.length === 0)) || products?.data?.abilities?.can_update !== true}}" }, "text": "$t:sirsoft-ecommerce.admin.product.bulk_apply", @@ -732,18 +752,34 @@ "actions": [ { "type": "change", - "handler": "navigate", - "params": { - "path": "/admin/ecommerce/products", - "replace": true, - "mergeQuery": true, - "query": { - "sort_by": "{{$event.target.value.split('_').slice(0, -1).join('_')}}", - "sort_order": "{{$event.target.value.split('_').pop()}}", - "page": 1 + "handler": "sequence", + "actions": [ + { + "comment": "정렬이 바뀌면 행 구성이 통째로 달라진다 — 펼침 상태를 이어받으면 옵션이 아직 없는 행이 펼쳐진 채로 빈 영역을 보인다.", + "handler": "setState", + "params": { + "target": "local", + "expandedRows": [], + "optionsLoadingIds": [], + "optionsErrorIds": [], + "selectedOptionIds": [] + } }, - "transition_overlay_target": "product_datagrid__body" - } + { + "handler": "navigate", + "params": { + "path": "/admin/ecommerce/products", + "replace": true, + "mergeQuery": true, + "query": { + "sort_by": "{{$event.target.value.split('_').slice(0, -1).join('_')}}", + "sort_order": "{{$event.target.value.split('_').pop()}}", + "page": 1 + }, + "transition_overlay_target": "product_datagrid__body" + } + } + ] } ] }, @@ -805,7 +841,7 @@ "name": "Icon", "props": { "name": "arrow-down-tray", - "className": "w-4 h-4" + "className": "text-base" } }, { 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 6cfc3877..1617bdec 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 @@ -91,7 +91,8 @@ "page": "{{query.page || 1}}", "per_page": "{{query.per_page || 20}}", "search": "{{query.search}}", - "search_type": "{{query.search_type}}" + "search_type": "{{query.search_type}}", + "template_channel": "{{query.channel ?? 'mail'}}" }, "initLocal": { "_merge": "deep", @@ -191,7 +192,6 @@ } ], "initLocal": { - "activeNotificationChannel": null, "editingDefinition": null, "editingTemplate": null, "editLang": "ko", diff --git a/modules/_bundled/sirsoft-ecommerce/resources/layouts/admin/partials/admin_ecommerce_product_list/_modal_bulk_confirm.json b/modules/_bundled/sirsoft-ecommerce/resources/layouts/admin/partials/admin_ecommerce_product_list/_modal_bulk_confirm.json index 2f4889b6..a366ff4d 100644 --- a/modules/_bundled/sirsoft-ecommerce/resources/layouts/admin/partials/admin_ecommerce_product_list/_modal_bulk_confirm.json +++ b/modules/_bundled/sirsoft-ecommerce/resources/layouts/admin/partials/admin_ecommerce_product_list/_modal_bulk_confirm.json @@ -123,6 +123,9 @@ "type": "basic", "name": "Div", "if": "{{_global.bulkConfirmData?.options?.length > 0}}", + "props": { + "data-testid": "bulk-confirm-options-section" + }, "children": [ { "type": "basic", @@ -130,7 +133,7 @@ "props": { "className": "font-semibold text-body mb-2 mt-3" }, - "text": "$t:sirsoft-ecommerce.admin.product.modal.bulk_confirm.options_section|count={{_global.bulkConfirmData.options.length}}" + "text": "$t:sirsoft-ecommerce.admin.product.modal.bulk_confirm.options_section|count={{_global.bulkConfirmData.summary.optionCount}}" }, { "type": "basic", @@ -153,8 +156,15 @@ { "type": "basic", "name": "Span", + "if": "{{option.aggregate !== true}}", "text": "{{option.productName}} > {{option.optionName}}" }, + { + "type": "basic", + "name": "Span", + "if": "{{option.aggregate === true}}", + "text": "$t:sirsoft-ecommerce.admin.product.modal.bulk_confirm.options_aggregate|product={{option.productName}}|count={{option.optionCount}}" + }, { "type": "basic", "name": "Span", @@ -254,6 +264,7 @@ "props": { "type": "button", "className": "btn btn-primary", + "data-testid": "bulk-confirm-submit", "disabled": "{{_global.isProcessing || ((!_global.bulkConfirmData?.products || _global.bulkConfirmData.products.length === 0) && (!_global.bulkConfirmData?.options || _global.bulkConfirmData.options.length === 0))}}" }, "children": [ diff --git a/modules/_bundled/sirsoft-ecommerce/resources/layouts/admin/partials/admin_ecommerce_product_list/_modal_bulk_stock.json b/modules/_bundled/sirsoft-ecommerce/resources/layouts/admin/partials/admin_ecommerce_product_list/_modal_bulk_stock.json index b0183174..ee588c34 100644 --- a/modules/_bundled/sirsoft-ecommerce/resources/layouts/admin/partials/admin_ecommerce_product_list/_modal_bulk_stock.json +++ b/modules/_bundled/sirsoft-ecommerce/resources/layouts/admin/partials/admin_ecommerce_product_list/_modal_bulk_stock.json @@ -96,6 +96,7 @@ "name": "Input", "props": { "type": "number", + "data-testid": "bulk-stock-value", "placeholder": "0", "className": "input" }, @@ -187,6 +188,7 @@ "name": "Button", "props": { "type": "button", + "data-testid": "bulk-stock-apply", "className": "btn btn-primary" }, "text": "$t:sirsoft-ecommerce.admin.product.modal.common.change", diff --git a/modules/_bundled/sirsoft-ecommerce/resources/layouts/admin/partials/admin_ecommerce_product_list/_partial_filter_section.json b/modules/_bundled/sirsoft-ecommerce/resources/layouts/admin/partials/admin_ecommerce_product_list/_partial_filter_section.json index 6843a1c6..a08b7e2d 100644 --- a/modules/_bundled/sirsoft-ecommerce/resources/layouts/admin/partials/admin_ecommerce_product_list/_partial_filter_section.json +++ b/modules/_bundled/sirsoft-ecommerce/resources/layouts/admin/partials/admin_ecommerce_product_list/_partial_filter_section.json @@ -97,7 +97,7 @@ "name": "category1", "className": "select-composite w-36", "value": "{{_local.filter.category1}}", - "options": "{{_computed.category1Options || []}}" + "options": "{{_computed.category1Options ?? []}}" }, "responsive": { "portable": { @@ -128,7 +128,7 @@ "name": "category2", "className": "select-composite w-36", "value": "{{_local.filter.category2}}", - "options": "{{_computed.category2Options || []}}" + "options": "{{_computed.category2Options ?? []}}" }, "responsive": { "portable": { @@ -158,7 +158,7 @@ "name": "category3", "className": "select-composite w-36", "value": "{{_local.filter.category3}}", - "options": "{{_computed.category3Options || []}}" + "options": "{{_computed.category3Options ?? []}}" }, "responsive": { "portable": { @@ -187,7 +187,7 @@ "name": "category4", "className": "select-composite w-36", "value": "{{_local.filter.category4}}", - "options": "{{_computed.category4Options || []}}" + "options": "{{_computed.category4Options ?? []}}" }, "responsive": { "portable": { @@ -341,7 +341,7 @@ "name": "Icon", "props": { "name": "calendar", - "className": "w-4 h-4 text-gray-400 dark:text-gray-500" + "className": "text-base text-gray-400 dark:text-gray-500" } }, { @@ -1047,7 +1047,7 @@ "name": "brandId", "className": "w-48 px-3 py-2 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-lg", "value": "{{_local.filter.brandId || ''}}", - "options": "{{_computed.brandOptions || []}}", + "options": "{{_computed.brandOptions ?? []}}", "placeholder": "$t:common.select_placeholder" }, "actions": [ @@ -1632,7 +1632,7 @@ "name": "Icon", "props": { "name": "sliders", - "className": "w-4 h-4" + "className": "text-base" } }, { @@ -1653,7 +1653,7 @@ ] }, { - "id": "preset_buttons_dynamic", + "id": "preset_buttons_dynamic_{{preset.id}}", "type": "basic", "name": "Div", "iteration": { @@ -1661,7 +1661,7 @@ "item_var": "preset" }, "props": { - "className": "{{query.preset_id === String(preset.id) ? 'btn-preset ring-1 ring-gray-400 bg-gray-900 text-white dark:bg-white dark:text-gray-900' : 'btn-preset cursor-pointer'}}" + "className": "{{query.preset_id === String(preset.id) ? 'btn-preset ring-1 ring-gray-400 dark:ring-gray-500 bg-gray-900 text-white dark:bg-white dark:text-gray-900' : 'btn-preset cursor-pointer'}}" }, "text": "{{preset.name}}", "actions": [ @@ -1701,6 +1701,9 @@ } } }, + { + "actionRef": "resetExpandedOptionState" + }, { "comment": "audit:allow layout-list-context-navigate-merge-query 프리셋 적용 — 프리셋은 조건 세트를 통째로 갈아끼우는 것이므로 병합하면 직전 조건이 섞인다. 주문 목록의 프리셋 규약과 동일.", "handler": "navigate", @@ -1850,7 +1853,7 @@ "name": "Icon", "props": { "name": "search", - "className": "w-4 h-4 absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 dark:text-gray-500" + "className": "text-base absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 dark:text-gray-500" } }, { @@ -2033,7 +2036,7 @@ "name": "Icon", "props": { "name": "filter", - "className": "w-4 h-4" + "className": "text-base" } }, { @@ -2046,7 +2049,7 @@ "name": "Icon", "props": { "name": "{{_local.showDetailFilter ? 'chevron-up' : 'chevron-down'}}", - "className": "w-4 h-4" + "className": "text-base" } } ], @@ -2171,7 +2174,7 @@ "name": "Icon", "props": { "name": "sliders", - "className": "w-4 h-4" + "className": "text-base" } }, { @@ -2287,6 +2290,9 @@ } } }, + { + "actionRef": "resetExpandedOptionState" + }, { "comment": "audit:allow layout-list-context-navigate-merge-query 필터 초기화 — 걸려 있던 조건을 전부 비우는 것이 이 버튼의 목적이므로 병합하지 않는다.", "handler": "navigate", @@ -2382,7 +2388,7 @@ "name": "Icon", "props": { "name": "sliders", - "className": "w-4 h-4" + "className": "text-base" } }, { diff --git a/modules/_bundled/sirsoft-ecommerce/resources/layouts/admin/partials/admin_ecommerce_product_list/_partial_product_datagrid.json b/modules/_bundled/sirsoft-ecommerce/resources/layouts/admin/partials/admin_ecommerce_product_list/_partial_product_datagrid.json index c905a9e1..f7430ad6 100644 --- a/modules/_bundled/sirsoft-ecommerce/resources/layouts/admin/partials/admin_ecommerce_product_list/_partial_product_datagrid.json +++ b/modules/_bundled/sirsoft-ecommerce/resources/layouts/admin/partials/admin_ecommerce_product_list/_partial_product_datagrid.json @@ -41,7 +41,7 @@ "name": "Icon", "props": { "name": "photo", - "className": "w-6 h-6 text-gray-400 dark:text-gray-500" + "className": "text-2xl text-gray-400 dark:text-gray-500" }, "if": "{{!row.thumbnail_url}}" } @@ -305,7 +305,7 @@ "name": "Icon", "props": { "name": "exclamation-triangle", - "className": "w-4 h-4 text-orange-500 dark:text-orange-400 cursor-help" + "className": "text-base text-orange-500 dark:text-orange-400 cursor-help" } }, { @@ -597,7 +597,9 @@ "currencyDisplayColumns": "{{_computed.currencyDisplayColumns || []}}", "selectedOptionIds": "{{_local.selectedOptionIds || []}}", "visibleColumns": "{{_local.visibleColumns || []}}", - "optionFieldErrors": "{{_local.optionFieldErrors || []}}" + "optionFieldErrors": "{{_local.optionFieldErrors || []}}", + "optionsLoadingIds": "{{_local.optionsLoadingIds ?? []}}", + "optionsErrorIds": "{{_local.optionsErrorIds ?? []}}" }, "expandChildren": [ { @@ -608,8 +610,57 @@ }, "children": [ { + "id": "options_loading_{{row.id}}", "type": "basic", "name": "Div", + "if": "{{(optionsLoadingIds || []).some(id => id + '' === row.id + '')}}", + "props": { + "className": "text-center py-4 text-gray-500 dark:text-gray-400" + }, + "text": "$t:sirsoft-ecommerce.admin.product.table.options.loading" + }, + { + "id": "options_error_{{row.id}}", + "type": "basic", + "name": "Div", + "if": "{{(optionsErrorIds || []).some(id => id + '' === row.id + '')}}", + "props": { + "className": "flex items-center justify-center gap-3 py-4" + }, + "children": [ + { + "type": "basic", + "name": "Span", + "props": { + "className": "text-red-500 dark:text-red-400" + }, + "text": "$t:sirsoft-ecommerce.admin.product.table.options.load_failed" + }, + { + "type": "basic", + "name": "Button", + "props": { + "type": "button", + "className": "px-3 py-1 text-sm border border-gray-300 dark:border-gray-600 rounded text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700" + }, + "text": "$t:sirsoft-ecommerce.admin.product.table.options.retry", + "actions": [ + { + "event": "onClick", + "type": "click", + "handler": "sirsoft-ecommerce.retryExpandedOptions", + "params": { + "productId": "{{row.id}}" + } + } + ] + } + ] + }, + { + "type": "basic", + "name": "Div", + "if": "{{!(optionsLoadingIds || []).some(id => id + '' === row.id + '') && !(optionsErrorIds || []).some(id => id + '' === row.id + '')}}", "props": { "className": "overflow-x-auto" }, @@ -627,11 +678,12 @@ "item_var": "option" }, "props": { + "data-testid": "option-row-{{option.id}}", "className": "admin-card mb-3" }, "children": [ { - "id": "option_checkbox_portable", + "id": "option_checkbox_portable_{{option.id}}", "type": "basic", "name": "Div", "props": { @@ -1149,11 +1201,12 @@ "item_var": "option" }, "props": { + "data-testid": "option-row-{{option.id}}", "className": "align-top" }, "children": [ { - "id": "option_checkbox_cell", + "id": "option_checkbox_cell_{{option.id}}", "type": "basic", "name": "Td", "props": { @@ -1182,7 +1235,7 @@ ] }, { - "id": "option_default_cell", + "id": "option_default_cell_{{option.id}}", "type": "basic", "name": "Td", "props": { @@ -1421,6 +1474,7 @@ "type": "number", "min": "0", "value": "{{option.stock_quantity}}", + "data-testid": "option-stock-{{option.id}}", "disabled": "{{row.abilities?.can_update !== true}}", "className": "w-16 px-2 py-1 text-sm text-center border {{option.is_below_safe_stock ? 'border-red-300 dark:border-red-600 text-red-500 dark:text-red-400 font-medium' : 'border-gray-300 dark:border-gray-600 text-gray-900 dark:text-white'}} rounded bg-white dark:bg-gray-800 disabled:opacity-50 disabled:cursor-not-allowed" }, @@ -1515,7 +1569,7 @@ "id": "no_options_message_{{row.id}}", "type": "basic", "name": "Div", - "if": "{{!row.options || row.options.length === 0}}", + "if": "{{Array.isArray(row.options) && row.options.length === 0}}", "props": { "className": "text-center py-4 text-gray-500 dark:text-gray-400" }, @@ -1537,25 +1591,40 @@ { "event": "onExpandChange", "type": "change", - "handler": "setState", + "handler": "sirsoft-ecommerce.loadExpandedOptions", "params": { - "target": "local", "expandedRows": "{{$args[0]}}" } }, { "event": "onPageChange", "type": "change", - "handler": "navigate", - "params": { - "path": "/admin/ecommerce/products", - "replace": true, - "mergeQuery": true, - "query": { - "page": "{{$args[0]}}" + "handler": "sequence", + "actions": [ + { + "comment": "다른 페이지의 상품으로 교체되므로 펼침 상태를 초기화한다. 남겨 두면 같은 ID 가 새 페이지에 있을 때 옵션이 아직 없는 행이 펼쳐진 채로 빈 영역을 보인다.", + "handler": "setState", + "params": { + "target": "local", + "expandedRows": [], + "optionsLoadingIds": [], + "optionsErrorIds": [], + "selectedOptionIds": [] + } }, - "transition_overlay_target": "product_datagrid__body" - } + { + "handler": "navigate", + "params": { + "path": "/admin/ecommerce/products", + "replace": true, + "mergeQuery": true, + "query": { + "page": "{{$args[0]}}" + }, + "transition_overlay_target": "product_datagrid__body" + } + } + ] }, { "event": "onRowAction", diff --git a/modules/_bundled/sirsoft-ecommerce/resources/layouts/admin/partials/admin_ecommerce_settings/_tab_notification_definitions.json b/modules/_bundled/sirsoft-ecommerce/resources/layouts/admin/partials/admin_ecommerce_settings/_tab_notification_definitions.json index d14ea83a..39dd4012 100644 --- a/modules/_bundled/sirsoft-ecommerce/resources/layouts/admin/partials/admin_ecommerce_settings/_tab_notification_definitions.json +++ b/modules/_bundled/sirsoft-ecommerce/resources/layouts/admin/partials/admin_ecommerce_settings/_tab_notification_definitions.json @@ -145,7 +145,7 @@ }, "props": { "type": "button", - "className": "{{(_local.activeNotificationChannel ?? query.channel ?? (availableChannels?.data?.channels ?? [])[0]?.id ?? 'mail') === ch.id ? 'border-b-2 border-indigo-500 dark:border-indigo-400 text-indigo-600 dark:text-indigo-400' : 'text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300'}} px-4 py-2.5 text-sm font-medium whitespace-nowrap flex-center gap-2" + "className": "{{(query.channel ?? 'mail') === ch.id ? 'border-b-2 border-indigo-500 dark:border-indigo-400 text-indigo-600 dark:text-indigo-400' : 'text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300'}} px-4 py-2.5 text-sm font-medium whitespace-nowrap flex-center gap-2" }, "children": [ { @@ -168,13 +168,6 @@ "handler": "sequence", "params": { "actions": [ - { - "handler": "setState", - "params": { - "target": "local", - "activeNotificationChannel": "{{ch.id}}" - } - }, { "handler": "navigate", "params": { @@ -400,16 +393,16 @@ { "type": "basic", "name": "P", - "if": "{{(def.templates ?? []).find(t => t.channel === (_local.activeNotificationChannel ?? query.channel ?? 'mail'))}}", + "if": "{{(def.templates ?? []).find(t => t.channel === (query.channel ?? 'mail'))}}", "props": { "className": "text-label-subtle truncate" }, - "text": "{{(def.templates ?? []).find(t => t.channel === (_local.activeNotificationChannel ?? query.channel ?? 'mail'))?.subject?.[$locale] ?? ''}}" + "text": "{{(def.templates ?? []).find(t => t.channel === (query.channel ?? 'mail'))?.subject?.[$locale] ?? ''}}" }, { "type": "basic", "name": "P", - "if": "{{!(def.templates ?? []).find(t => t.channel === (_local.activeNotificationChannel ?? query.channel ?? 'mail'))}}", + "if": "{{!(def.templates ?? []).find(t => t.channel === (query.channel ?? 'mail'))}}", "props": { "className": "text-sm text-gray-400 dark:text-gray-500 italic" }, @@ -419,7 +412,7 @@ "id": "ecommerce_recipients_badge_{{defIdx}}", "type": "basic", "name": "Div", - "if": "{{((def.templates ?? []).find(t => t.channel === (_local.activeNotificationChannel ?? query.channel ?? 'mail'))?.recipients ?? []).length > 0}}", + "if": "{{((def.templates ?? []).find(t => t.channel === (query.channel ?? 'mail'))?.recipients ?? []).length > 0}}", "props": { "className": "flex-center flex-wrap gap-1.5 mt-2" }, @@ -436,7 +429,7 @@ "type": "basic", "name": "Span", "iteration": { - "source": "{{(def.templates ?? []).find(t => t.channel === (_local.activeNotificationChannel ?? query.channel ?? 'mail'))?.recipients ?? []}}", + "source": "{{(def.templates ?? []).find(t => t.channel === (query.channel ?? 'mail'))?.recipients ?? []}}", "item_var": "rcpt" }, "props": { @@ -505,7 +498,7 @@ "name": "notification_definition_row_footer", "props": { "definition": "{{def}}", - "activeChannel": "{{_local.activeNotificationChannel ?? query.channel ?? 'mail'}}" + "activeChannel": "{{query.channel ?? 'mail'}}" } } ] @@ -520,9 +513,9 @@ { "type": "composite", "name": "Toggle", - "if": "{{(def.templates ?? []).find(t => t.channel === (_local.activeNotificationChannel ?? query.channel ?? 'mail'))}}", + "if": "{{(def.templates ?? []).find(t => t.channel === (query.channel ?? 'mail'))}}", "props": { - "checked": "{{(def.templates ?? []).find(t => t.channel === (_local.activeNotificationChannel ?? query.channel ?? 'mail'))?.is_active ?? false}}", + "checked": "{{(def.templates ?? []).find(t => t.channel === (query.channel ?? 'mail'))?.is_active ?? false}}", "disabled": "{{_computed.isNotificationReadOnly}}" }, "actions": [ @@ -530,7 +523,7 @@ "type": "change", "handler": "apiCall", "auth_required": true, - "target": "{{'/api/admin/notification-templates/' + (def.templates ?? []).find(t => t.channel === (_local.activeNotificationChannel ?? query.channel ?? 'mail'))?.id + '/toggle-active'}}", + "target": "{{'/api/admin/notification-templates/' + (def.templates ?? []).find(t => t.channel === (query.channel ?? 'mail'))?.id + '/toggle-active'}}", "params": { "method": "PATCH" }, @@ -581,9 +574,9 @@ "params": { "target": "global", "ecommerce_notification_template_form_modal": { - "template": "{{(def.templates ?? []).find(t => t.channel === (_local.activeNotificationChannel ?? query.channel ?? 'mail'))}}", + "template": "{{(def.templates ?? []).find(t => t.channel === (query.channel ?? 'mail'))}}", "definition": "{{def}}", - "recipients": "{{JSON.parse(JSON.stringify((def.templates ?? []).find(t => t.channel === (_local.activeNotificationChannel ?? query.channel ?? 'mail'))?.recipients ?? []))}}", + "recipients": "{{JSON.parse(JSON.stringify((def.templates ?? []).find(t => t.channel === (query.channel ?? 'mail'))?.recipients ?? []))}}", "editLang": "{{$locale}}", "errors": null, "isSaving": false, @@ -604,7 +597,7 @@ { "type": "basic", "name": "Button", - "if": "{{!def.is_default || (def.templates ?? []).some(t => t.is_default === false)}}", + "if": "{{!def.is_default || def.has_customized_templates === true}}", "props": { "type": "button", "disabled": "{{_computed.isNotificationReadOnly}}", @@ -656,7 +649,7 @@ "type": "composite", "name": "HtmlContent", "props": { - "content": "{{(def.templates ?? []).find(t => t.channel === (_local.activeNotificationChannel ?? query.channel ?? 'mail'))?.body?.[$locale] ?? ''}}", + "content": "{{(def.templates ?? []).find(t => t.channel === (query.channel ?? 'mail'))?.body?.[$locale] ?? ''}}", "isHtml": true, "className": "prose dark:prose-invert prose-sm max-w-none bg-gray-50 dark:bg-gray-900/50 rounded-lg p-3 max-h-48 overflow-y-auto" } diff --git a/modules/_bundled/sirsoft-ecommerce/src/Http/Controllers/Admin/ProductController.php b/modules/_bundled/sirsoft-ecommerce/src/Http/Controllers/Admin/ProductController.php index 1cfa5a83..84ca4a18 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/Http/Controllers/Admin/ProductController.php +++ b/modules/_bundled/sirsoft-ecommerce/src/Http/Controllers/Admin/ProductController.php @@ -18,6 +18,7 @@ use Modules\Sirsoft\Ecommerce\Http\Requests\Admin\BulkUpdateStatusRequest; use Modules\Sirsoft\Ecommerce\Http\Requests\Admin\BulkUpdateStockRequest; use Modules\Sirsoft\Ecommerce\Http\Requests\Admin\ProductListRequest; use Modules\Sirsoft\Ecommerce\Http\Requests\Admin\ProductLogsRequest; +use Modules\Sirsoft\Ecommerce\Http\Requests\Admin\ProductOptionsListRequest; use Modules\Sirsoft\Ecommerce\Http\Requests\Admin\ProductShowForCopyRequest; use Modules\Sirsoft\Ecommerce\Http\Requests\Admin\ReorderProductImagesRequest; use Modules\Sirsoft\Ecommerce\Http\Requests\Admin\StoreProductRequest; @@ -71,6 +72,55 @@ class ProductController extends AdminBaseController } } + /** + * 여러 상품의 옵션을 한 번에 조회합니다 (비활성 옵션 포함). + * + * 목록에서 행을 펼칠 때 그 행들의 옵션을 채우기 위한 배치 엔드포인트입니다. 상품 목록이 + * 옵션을 더 이상 기본 적재하지 않으므로 이 경로가 옵션 상세의 공급자입니다. + * + * 배치는 **부분 성공**을 계약으로 합니다. 존재하지 않거나 권한 스코프 밖인 상품 ID 는 + * 404 를 만들지 않고 응답의 `product_ids` 에서 빠집니다 — 하나가 없다고 나머지 N-1 건의 + * 정상 조회를 버릴 이유가 없고, 배치에서 "무엇이 없었는지" 는 상태코드로 표현할 수 없기 + * 때문입니다. 단건 404 semantics 는 `GET admin/products/{identifier}` 가 제공합니다. + * 호출자는 요청한 ID 배열과 응답의 `product_ids` 를 대조해 제외된 ID 를 확정할 수 있습니다. + * + * @param ProductOptionsListRequest $request 상품 옵션 배치 조회 요청 + * @return JsonResponse 상품 ID 로 묶인 옵션 맵과 스코프를 통과한 상품 ID 목록 + */ + public function options(ProductOptionsListRequest $request): JsonResponse + { + try { + $result = $this->productService->getOptionsByProductIds( + $request->validated()['product_ids'] + ); + + $grouped = []; + foreach ($result['product_ids'] as $productId) { + $options = $result['options'][$productId] ?? []; + + $grouped[(string) $productId] = ProductOptionResource::collection($options)->resolve($request); + } + + return ResponseHelper::moduleSuccess( + 'sirsoft-ecommerce', + 'messages.products.options_fetch_success', + [ + // 빈 맵이 JSON 배열([])로 나가지 않도록 객체로 확정한다 + 'options' => (object) $grouped, + 'product_ids' => $result['product_ids'], + ] + ); + } catch (Exception $e) { + Log::error('상품 옵션 배치 조회 실패', ['message' => $e->getMessage()]); + + return ResponseHelper::moduleError( + 'sirsoft-ecommerce', + 'messages.products.options_fetch_failed', + 500 + ); + } + } + /** * 특정 상품의 상세 정보를 조회합니다. * diff --git a/modules/_bundled/sirsoft-ecommerce/src/Http/Requests/Admin/ProductListRequest.php b/modules/_bundled/sirsoft-ecommerce/src/Http/Requests/Admin/ProductListRequest.php index c1353a06..58f9180b 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/Http/Requests/Admin/ProductListRequest.php +++ b/modules/_bundled/sirsoft-ecommerce/src/Http/Requests/Admin/ProductListRequest.php @@ -39,7 +39,7 @@ class ProductListRequest extends FormRequest protected function prepareForValidation(): void { $merge = []; - foreach (['no_category', 'no_brand'] as $field) { + foreach (['no_category', 'no_brand', 'with_options'] as $field) { if (! $this->has($field)) { continue; } @@ -108,6 +108,9 @@ class ProductListRequest extends FormRequest // 배송정책 'shipping_policy_id' => ['nullable', 'integer'], + // 옵션 상세 포함 여부 (기본 false — 목록은 개수/재고합계 집계만 내려준다) + 'with_options' => ['nullable', 'boolean'], + // 정렬 및 페이지네이션 'sort_by' => ['nullable', 'in:created_at,updated_at,selling_price,stock_quantity,name'], 'sort_order' => ['nullable', 'in:asc,desc'], @@ -150,6 +153,7 @@ class ProductListRequest extends FormRequest 'min_stock.integer' => __('sirsoft-ecommerce::validation.list.min_stock.integer'), 'max_stock.integer' => __('sirsoft-ecommerce::validation.list.max_stock.integer'), 'shipping_policy_id.integer' => __('sirsoft-ecommerce::validation.list.shipping_policy_id.integer'), + 'with_options.boolean' => __('sirsoft-ecommerce::validation.list.with_options.boolean'), 'sort_by.in' => __('sirsoft-ecommerce::validation.list.sort_by.in'), 'sort_order.in' => __('sirsoft-ecommerce::validation.list.sort_order.in'), 'per_page.integer' => __('sirsoft-ecommerce::validation.list.per_page.integer'), diff --git a/modules/_bundled/sirsoft-ecommerce/src/Http/Requests/Admin/ProductOptionsListRequest.php b/modules/_bundled/sirsoft-ecommerce/src/Http/Requests/Admin/ProductOptionsListRequest.php new file mode 100644 index 00000000..eb8649c8 --- /dev/null +++ b/modules/_bundled/sirsoft-ecommerce/src/Http/Requests/Admin/ProductOptionsListRequest.php @@ -0,0 +1,58 @@ + 검증 규칙 + */ + public function rules(): array + { + return [ + 'product_ids' => ['required', 'array', 'min:1', 'max:100'], + 'product_ids.*' => ['integer', 'min:1'], + ]; + } + + /** + * 검증 오류 메시지 커스터마이징 + * + * @return array 오류 메시지 + */ + public function messages(): array + { + return [ + 'product_ids.required' => __('sirsoft-ecommerce::validation.options_list.product_ids.required'), + 'product_ids.array' => __('sirsoft-ecommerce::validation.options_list.product_ids.array'), + 'product_ids.min' => __('sirsoft-ecommerce::validation.options_list.product_ids.min'), + 'product_ids.max' => __('sirsoft-ecommerce::validation.options_list.product_ids.max'), + 'product_ids.*.integer' => __('sirsoft-ecommerce::validation.options_list.product_ids.integer'), + 'product_ids.*.min' => __('sirsoft-ecommerce::validation.options_list.product_ids.item_min'), + ]; + } +} diff --git a/modules/_bundled/sirsoft-ecommerce/src/Http/Requests/Admin/ShippingPolicyListRequest.php b/modules/_bundled/sirsoft-ecommerce/src/Http/Requests/Admin/ShippingPolicyListRequest.php index 4ab115e3..b5b43fa6 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/Http/Requests/Admin/ShippingPolicyListRequest.php +++ b/modules/_bundled/sirsoft-ecommerce/src/Http/Requests/Admin/ShippingPolicyListRequest.php @@ -6,7 +6,7 @@ use App\Extension\HookManager; use Illuminate\Foundation\Http\FormRequest; use Illuminate\Validation\Rule; use Modules\Sirsoft\Ecommerce\Enums\ChargePolicyEnum; -use Modules\Sirsoft\Ecommerce\Models\ShippingType; +use Modules\Sirsoft\Ecommerce\Repositories\Contracts\ShippingTypeRepositoryInterface; /** * 배송정책 목록 조회 요청 @@ -23,10 +23,36 @@ class ShippingPolicyListRequest extends FormRequest return true; } + /** + * 검증 전 쿼리 파라미터를 정규화합니다. + * + * 쿼리스트링 값은 항상 문자열로 도착한다. `boolean` 규칙은 `"true"`/`"false"` 를 받지 않으므로 + * 정규화하지 않으면 화면이 그 형태로 보낼 때 목록 전체가 422 가 된다. 해석할 수 없는 값은 + * 건드리지 않는다 — null 로 바꾸면 오타 파라미터가 "지정 안 함" 으로 조용히 통과한다. + * + * @return void + */ + protected function prepareForValidation(): void + { + if (! $this->has('with_country_settings')) { + return; + } + + $normalized = filter_var( + $this->input('with_country_settings'), + FILTER_VALIDATE_BOOLEAN, + FILTER_NULL_ON_FAILURE + ); + + if ($normalized !== null) { + $this->merge(['with_country_settings' => $normalized]); + } + } + /** * 유효성 검사 규칙 * - * @return array + * @return array 검증 규칙 */ public function rules(): array { @@ -36,7 +62,9 @@ class ShippingPolicyListRequest extends FormRequest // 배송방법 (다중선택) 'shipping_methods' => ['nullable', 'array'], - 'shipping_methods.*' => ['string', Rule::in(ShippingType::pluck('code')->toArray())], + 'shipping_methods.*' => ['string', Rule::in( + app(ShippingTypeRepositoryInterface::class)->getAll()->pluck('code')->toArray() + )], // 부과정책 (다중선택) 'charge_policies' => ['nullable', 'array'], @@ -49,6 +77,14 @@ class ShippingPolicyListRequest extends FormRequest // 사용여부 'is_active' => ['nullable', 'in:,true,false'], + // 국가별 설정 동반 조회 (기본 미포함) + // + // 배송정책 관리 목록 화면은 국가별 설정도 배송비 요약도 쓰지 않는다. 반면 상품 등록/ + // 수정 화면의 배송정책 선택기는 같은 엔드포인트를 쓰면서 선택한 정책의 국가 국기와 + // 배송비 요약을 그린다. 그래서 기본은 경량으로 두고, 필요한 화면만 이 값을 켠다 + // (정책당 국가 설정이 최대 수백 행이라 기본 포함 시 목록 한 페이지가 그만큼 커진다). + 'with_country_settings' => ['nullable', 'boolean'], + // 정렬 및 페이지네이션 'sort_by' => ['nullable', 'in:id,name,is_active,sort_order,created_at,updated_at'], 'sort_order' => ['nullable', 'in:asc,desc'], diff --git a/modules/_bundled/sirsoft-ecommerce/src/Http/Requests/User/UserOrderListRequest.php b/modules/_bundled/sirsoft-ecommerce/src/Http/Requests/User/UserOrderListRequest.php index 97916446..959eab10 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/Http/Requests/User/UserOrderListRequest.php +++ b/modules/_bundled/sirsoft-ecommerce/src/Http/Requests/User/UserOrderListRequest.php @@ -21,6 +21,26 @@ class UserOrderListRequest extends FormRequest return true; } + /** + * 검증 전에 쿼리 문자열 불리언을 정규화합니다. + * + * 쿼리 파라미터는 문자열로 도착하므로 `boolean` 규칙만으로는 `"true"`/`"false"` 를 통과시키지 + * 못한다. 해석 가능한 형태만 캐스팅하고, 알 수 없는 값은 그대로 둬서 422 로 걸리게 한다 + * (null 로 바꾸면 오타 파라미터가 "미지정" 으로 조용히 통과한다). + */ + protected function prepareForValidation(): void + { + if (! $this->has('with_items')) { + return; + } + + $normalized = filter_var($this->input('with_items'), FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE); + + if ($normalized !== null) { + $this->merge(['with_items' => $normalized]); + } + } + /** * 요청에 적용할 검증 규칙 * @@ -32,6 +52,10 @@ class UserOrderListRequest extends FormRequest 'page' => ['nullable', 'integer', 'min:1'], 'per_page' => ['nullable', 'integer', 'min:1', 'max:50'], 'status' => ['nullable', 'string', Rule::in(OrderStatusEnum::values())], + // 주문별 아이템 전량 포함 여부. 기본은 **미포함**(대표 1건 + 개수)이고, 아이템을 + // 순회하는 화면이 `with_items=1` 로 켠다 — 마이페이지 주문내역이 그렇게 요청한다 + // (`templates/_bundled/sirsoft-basic/layouts/mypage/orders.json`). + 'with_items' => ['nullable', 'boolean'], ]; } diff --git a/modules/_bundled/sirsoft-ecommerce/src/Http/Resources/OrderListResource.php b/modules/_bundled/sirsoft-ecommerce/src/Http/Resources/OrderListResource.php index c14e6c40..ec22752b 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/Http/Resources/OrderListResource.php +++ b/modules/_bundled/sirsoft-ecommerce/src/Http/Resources/OrderListResource.php @@ -4,6 +4,7 @@ namespace Modules\Sirsoft\Ecommerce\Http\Resources; use App\Http\Resources\BaseApiResource; use Illuminate\Http\Request; +use Illuminate\Http\Resources\MissingValue; use Modules\Sirsoft\Ecommerce\Http\Resources\Concerns\LocalizesCountryName; use Modules\Sirsoft\Ecommerce\Http\Resources\Traits\HasMultiCurrencyPrices; use Modules\Sirsoft\Ecommerce\Http\Resources\Traits\SummarizesAdditionalOptions; @@ -34,7 +35,9 @@ class OrderListResource extends BaseApiResource $paymentCurrency = $this->currency ?: (data_get($this->currency_snapshot, 'order_currency') ?: $orderCurrency); - return [ + // OrderCollection 은 이 배열을 `toArray()` 로 직접 받아 응답에 싣는다 — Laravel 이 응답 + // 직전에 수행하는 MissingValue 제거를 거치지 않으므로 여기서 걸러낸다. + return $this->withoutMissing([ 'id' => $this->id, 'order_number' => $this->order_number, 'order_status' => $this->order_status, @@ -45,7 +48,7 @@ class OrderListResource extends BaseApiResource 'payment_currency' => $paymentCurrency, 'is_cross_currency' => $paymentCurrency !== $orderCurrency, // 부분취소 파생 플래그 — 일부 옵션만 취소된 주문(별도 order_status 아님). 보조 뱃지 표시용. - 'is_partially_cancelled' => $this->whenLoaded('options', fn () => $this->resource->isPartiallyCancelled(), false), + 'is_partially_cancelled' => $this->resolveIsPartiallyCancelled(), // 금액 'total_amount' => $this->roundToOrderCurrency($this->total_amount, $orderCurrency), @@ -83,8 +86,7 @@ class OrderListResource extends BaseApiResource ]), // 첫 번째 옵션 (대표 상품 표시용) - 'first_option' => $this->whenLoaded('options', function () { - $firstOption = $this->options->first(); + 'first_option' => $this->whenFirstOptionAvailable(function ($firstOption) { $productName = $firstOption?->product_name; // 매직 프로퍼티(Eloquent accessor)를 reset() 에 직접 넘기면 PHP 8.3 에서 // "Indirect modification of overloaded property" 경고가 발생하므로 지역 변수로 받는다. @@ -104,7 +106,7 @@ class OrderListResource extends BaseApiResource 'additional_options_summary' => $this->summarizeAdditionalOptions($firstOption), ]; }), - 'options_count' => $this->whenLoaded('options', fn () => $this->options->count()), + 'options_count' => $this->resolveOptionsCount(), // 주문자/수령인 'address' => $this->whenLoaded('shippingAddress', fn () => [ @@ -120,9 +122,13 @@ class OrderListResource extends BaseApiResource 'payment_method_label' => $this->payment->payment_method ? $this->payment->paymentMethodLabel() : null, ]), - // 배송 (첫 번째) - 'shipping' => $this->whenLoaded('shippings', function () { - $shipping = $this->shippings->first(); + // 배송 (대표 1건) + // + // 목록 조회는 `firstShipping` 만 로드한다. 종전 호출자(전체 `shippings` 를 로드하는 + // 경로)도 그대로 동작하도록 두 관계를 모두 본다 — 어느 쪽도 로드돼 있지 않으면 + // 필드 자체를 싣지 않는다(빈 객체 누출 방지). + 'shipping' => $this->whenAnyShippingLoaded(function () { + $shipping = $this->resolveRepresentativeShipping(); return [ 'shipping_type' => $shipping?->shipping_type, @@ -137,7 +143,111 @@ class OrderListResource extends BaseApiResource // 권한 정보 (is_owner + abilities) ...$this->resourceMeta($request), - ]; + ]); + } + + /** + * 대표 옵션이 확보된 경우에만 콜백 결과를 반환합니다. + * + * 목록 경로는 `firstOption`(대표 1건)만 로드한다. 상세/일괄 경로처럼 `options` 전량이 + * 로드된 컬렉션도 같은 필드를 그리므로 두 경로 모두 받는다. 어느 쪽도 로드되지 않았으면 + * 필드 자체를 내보내지 않는다. + * + * @param callable $callback 대표 옵션을 받아 표시용 배열을 만드는 콜백 + * @return array|MissingValue 표시용 배열 또는 미충족 표식 + */ + private function whenFirstOptionAvailable(callable $callback): array|MissingValue + { + if ($this->resource->relationLoaded('firstOption')) { + return $callback($this->resource->firstOption); + } + + if ($this->resource->relationLoaded('options')) { + return $callback($this->resource->options->first()); + } + + return new MissingValue; + } + + /** + * 배송 관계가 어느 형태로든 로드돼 있을 때만 콜백 결과를 싣습니다. + * + * 목록은 대표 1건(`firstShipping`)만 로드하고, 전체를 로드하는 종전 경로도 남아 있다. + * 둘 다 없으면 `MissingValue` 를 돌려 필드를 통째로 뺀다 — 로드하지 않은 관계를 빈 값으로 + * 채우면 "배송 정보가 없다" 는 사실이 아닌 단언이 응답에 실린다. + * + * @param callable $callback 배송 표현을 만드는 콜백 + * @return array|MissingValue 배송 표현 또는 미충족 표식 + */ + private function whenAnyShippingLoaded(callable $callback): array|MissingValue + { + if ( + $this->resource->relationLoaded('firstShipping') + || $this->resource->relationLoaded('shippings') + ) { + return $callback(); + } + + return new MissingValue; + } + + /** + * 대표 배송 레코드를 반환합니다. + * + * @return OrderShipping|null 대표 배송 (없으면 null) + */ + private function resolveRepresentativeShipping(): ?OrderShipping + { + if ($this->resource->relationLoaded('firstShipping')) { + return $this->resource->firstShipping; + } + + return $this->resource->relationLoaded('shippings') + ? $this->resource->shippings->first() + : null; + } + + /** + * 전체 옵션 수를 반환합니다. + * + * 목록은 DB 집계(`options_count`)를 쓰고, 옵션이 전량 로드된 경로는 컬렉션 길이를 쓴다. + * 집계 별칭 유무는 값이 아니라 속성 키로 판정한다 — 옵션 0건의 `0` 과 "집계 안 함" 은 + * 값으로 구분되지 않는다. + * + * @return int|MissingValue 옵션 수 또는 미충족 표식 + */ + private function resolveOptionsCount(): int|MissingValue + { + if ($this->hasAggregate('options_count')) { + return (int) $this->aggregate('options_count', 0); + } + + if ($this->resource->relationLoaded('options')) { + return $this->resource->options->count(); + } + + return new MissingValue; + } + + /** + * 부분취소 여부를 반환합니다. + * + * 취소된 옵션이 있으면서 남은 옵션도 있는 상태를 뜻한다. 목록은 옵션을 순회하지 않고 + * 집계 두 개(전체 수 / 취소 수)로 같은 판정을 얻는다. + * + * @return bool 부분취소 여부 + */ + private function resolveIsPartiallyCancelled(): bool + { + if ($this->hasAggregate('options_count') && $this->hasAggregate('cancelled_options_count')) { + $cancelled = (int) $this->aggregate('cancelled_options_count', 0); + + return $cancelled > 0 && (int) $this->aggregate('options_count', 0) > $cancelled; + } + + return $this->resource->relationLoaded('options') + ? $this->resource->isPartiallyCancelled() + : false; } /** diff --git a/modules/_bundled/sirsoft-ecommerce/src/Http/Resources/ProductListResource.php b/modules/_bundled/sirsoft-ecommerce/src/Http/Resources/ProductListResource.php index ba6b4e90..30883bc7 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/Http/Resources/ProductListResource.php +++ b/modules/_bundled/sirsoft-ecommerce/src/Http/Resources/ProductListResource.php @@ -3,7 +3,9 @@ namespace Modules\Sirsoft\Ecommerce\Http\Resources; use App\Http\Resources\BaseApiResource; +use Illuminate\Database\Eloquent\Model; use Illuminate\Http\Request; +use Illuminate\Http\Resources\Json\JsonResource; use Modules\Sirsoft\Ecommerce\Http\Resources\Traits\HasMultiCurrencyPrices; /** @@ -44,9 +46,7 @@ class ProductListResource extends BaseApiResource 'stock_quantity' => $this->stock_quantity, 'safe_stock_quantity' => $this->safe_stock_quantity, 'is_below_safe_stock' => $this->isBelowSafeStock(), - 'option_stock_sum' => $this->relationLoaded('options') - ? $this->options->where('is_active', true)->sum('stock_quantity') - : $this->whenLoaded('activeOptions', fn () => $this->activeOptions->sum('stock_quantity')), + 'option_stock_sum' => $this->resolveActiveOptionStockSum(), // 상태 'sales_status' => $this->sales_status->value, @@ -83,12 +83,15 @@ class ProductListResource extends BaseApiResource 'max_purchase_qty' => $this->max_purchase_qty, // 옵션 + // 옵션 배열은 관계가 실제로 로드된 경우에만 실린다. 관리자 목록은 기본적으로 옵션을 + // 로드하지 않고(`?with_options=1` opt-in), 행을 펼칠 때 배치 엔드포인트로 가져간다. 'has_options' => $this->has_options, - 'options_count' => $this->relationLoaded('options') - ? $this->options->where('is_active', true)->count() - : $this->whenLoaded('activeOptions', fn () => $this->activeOptions->count()), - 'options' => ProductOptionResource::collection( - $this->relationLoaded('options') ? $this->options : $this->whenLoaded('activeOptions') + 'options_count' => $this->resolveActiveOptionCount(), + 'options_total_count' => $this->resolveTotalOptionCount(), + 'options' => $this->whenLoaded( + 'options', + fn () => ProductOptionResource::collection($this->options), + fn () => ProductOptionResource::collection($this->whenLoaded('activeOptions')) ), // 라벨 @@ -116,6 +119,89 @@ class ProductListResource extends BaseApiResource ]; } + /** + * 활성 옵션의 재고 합계를 반환합니다. + * + * 목록 쿼리가 붙인 DB 집계(`withSum`)를 우선 사용합니다. 옵션이 0건이면 SUM 은 `0` 이 아니라 + * `null` 을 돌려주므로 반드시 0 으로 확정합니다 — null 을 그대로 흘리면 화면의 재고 불일치 + * 비교(`stock_quantity !== option_stock_sum`)가 전 상품에서 오탐합니다. + * + * 집계가 없는 호출 경로(인기/신상품/위시리스트 등)는 종전의 관계 기반 계산을 유지합니다. + * + * @return mixed 재고 합계(int) 또는 계산 불가 시 MissingValue + */ + protected function resolveActiveOptionStockSum(): mixed + { + // 값이 null 인지가 아니라 **집계 컬럼이 붙었는지**로 판정한다. 옵션 0건이면 SUM 은 NULL 을 + // 돌려주므로 null 검사로는 "집계를 안 했다" 와 구분되지 않아, 옵션 없는 상품에서 필드가 + // 통째로 사라진다. + if ($this->hasAggregateAttribute('option_stock_sum')) { + return (int) $this->option_stock_sum; + } + + if ($this->relationLoaded('options')) { + return (int) $this->options->where('is_active', true)->sum('stock_quantity'); + } + + return $this->whenLoaded('activeOptions', fn () => (int) $this->activeOptions->sum('stock_quantity')); + } + + /** + * 활성 옵션 개수를 반환합니다. + * + * @return mixed 활성 옵션 수(int) 또는 계산 불가 시 MissingValue + */ + protected function resolveActiveOptionCount(): mixed + { + if ($this->hasAggregateAttribute('options_count')) { + return (int) $this->options_count; + } + + if ($this->relationLoaded('options')) { + return $this->options->where('is_active', true)->count(); + } + + return $this->whenLoaded('activeOptions', fn () => $this->activeOptions->count()); + } + + /** + * 비활성을 포함한 전체 옵션 개수를 반환합니다. + * + * 일괄 변경은 상품 ID 만 받아 서버에서 그 상품의 **모든** 옵션으로 전개하므로(비활성 포함), + * 확인 모달이 실제 적용 대상 수를 보여주려면 활성 개수가 아니라 이 값을 세야 합니다. + * + * @return mixed 전체 옵션 수(int) 또는 계산 불가 시 MissingValue + */ + protected function resolveTotalOptionCount(): mixed + { + if ($this->hasAggregateAttribute('options_total_count')) { + return (int) $this->options_total_count; + } + + return $this->whenLoaded('options', fn () => $this->options->count()); + } + + /** + * 목록 쿼리가 붙인 집계 컬럼이 이 행에 존재하는지 확인합니다. + * + * 컬렉션 경로에서는 이 리소스가 다른 리소스를 한 겹 감싼 형태로 들어온다 + * (`ProductCollection` 이 Laravel 규칙에 따라 `ProductResource` 로 먼저 감싼다). + * 그래서 내부 모델까지 풀어낸 뒤 확인한다 — `HasAbilityCheck` 와 같은 방식이다. + * + * @param string $key 집계 별칭 + * @return bool 컬럼 존재 여부 (값이 NULL 이어도 true) + */ + private function hasAggregateAttribute(string $key): bool + { + $model = $this->resource; + + while ($model instanceof JsonResource) { + $model = $model->resource; + } + + return $model instanceof Model && array_key_exists($key, $model->getAttributes()); + } + /** * 권한 체크 매핑을 반환합니다. * diff --git a/modules/_bundled/sirsoft-ecommerce/src/Http/Resources/ProductResource.php b/modules/_bundled/sirsoft-ecommerce/src/Http/Resources/ProductResource.php index b1afe24a..f2489d13 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/Http/Resources/ProductResource.php +++ b/modules/_bundled/sirsoft-ecommerce/src/Http/Resources/ProductResource.php @@ -131,6 +131,10 @@ class ProductResource extends BaseApiResource // 옵션 'has_options' => $this->has_options, 'option_groups' => $this->resource->getOptionGroupsForApi(), + // audit:allow list-resource-fake-relation-guard reason: 이 리소스는 단건 상세 전용이라 + // 목록 경로가 없다. `findWithOptions($id, $includeInactive)` 가 비활성 포함 여부에 따라 + // options / activeOptions 중 **하나만** 로드하므로, 두 관계를 함께 받는 이 분기는 + // 가짜 가드가 아니라 두 로드 방식의 정상 수용이다. 목록(ProductListResource)은 별도다. 'options' => ProductOptionResource::collection( $this->relationLoaded('options') ? $this->options : $this->whenLoaded('activeOptions') ), diff --git a/modules/_bundled/sirsoft-ecommerce/src/Http/Resources/ProductReviewResource.php b/modules/_bundled/sirsoft-ecommerce/src/Http/Resources/ProductReviewResource.php index 67c7d12b..7e1186ee 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/Http/Resources/ProductReviewResource.php +++ b/modules/_bundled/sirsoft-ecommerce/src/Http/Resources/ProductReviewResource.php @@ -57,7 +57,11 @@ class ProductReviewResource extends BaseApiResource // 이미지 'images' => ProductReviewImageResource::collection($this->whenLoaded('images')), - 'image_count' => $this->whenLoaded('images', fn () => $this->images->count()), + // 첨부 개수는 목록 쿼리의 DB 집계(`withCount('images as image_count')`)를 우선 쓴다. + // 관계 기반 계산만 두면, 이미지를 로드하지 않는 목록에서 개수까지 함께 사라진다. + 'image_count' => $this->image_count !== null + ? (int) $this->image_count + : $this->whenLoaded('images', fn () => $this->images->count()), // 주문 옵션 정보 'orderOption' => $this->whenLoaded('orderOption', fn () => [ diff --git a/modules/_bundled/sirsoft-ecommerce/src/Http/Resources/ShippingPolicyCollection.php b/modules/_bundled/sirsoft-ecommerce/src/Http/Resources/ShippingPolicyCollection.php index 8117d2c1..b4da4b37 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/Http/Resources/ShippingPolicyCollection.php +++ b/modules/_bundled/sirsoft-ecommerce/src/Http/Resources/ShippingPolicyCollection.php @@ -40,7 +40,7 @@ class ShippingPolicyCollection extends BaseApiCollection { $result = [ 'data' => $this->mapWithRowNumber(function ($shippingPolicy) { - return (new ShippingPolicyResource($shippingPolicy))->toArray(request()); + return (new ShippingPolicyResource($shippingPolicy))->toListArray(request()); }), 'abilities' => $this->resolveAbilitiesFromMap($this->abilityMap(), $request->user()), ]; @@ -70,7 +70,7 @@ class ShippingPolicyCollection extends BaseApiCollection { $result = [ 'data' => $this->mapWithRowNumber(function ($shippingPolicy) { - return (new ShippingPolicyResource($shippingPolicy))->toArray(request()); + return (new ShippingPolicyResource($shippingPolicy))->toListArray(request()); }), 'abilities' => $this->resolveAbilitiesFromMap($this->abilityMap(), request()->user()), 'statistics' => $statistics, diff --git a/modules/_bundled/sirsoft-ecommerce/src/Http/Resources/ShippingPolicyCountrySettingResource.php b/modules/_bundled/sirsoft-ecommerce/src/Http/Resources/ShippingPolicyCountrySettingResource.php index 8a42b538..53f59fde 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/Http/Resources/ShippingPolicyCountrySettingResource.php +++ b/modules/_bundled/sirsoft-ecommerce/src/Http/Resources/ShippingPolicyCountrySettingResource.php @@ -61,6 +61,45 @@ class ShippingPolicyCountrySettingResource extends BaseApiResource ]; } + /** + * 목록 표현으로 변환합니다 (배송정책 목록 / 상품 폼의 정책 선택기). + * + * 두 화면이 실제로 그리는 필드만 담는다 — 국가 칩, 배송방법, 부과정책, 배송비, 도서산간 + * 여부, 활성 여부. 상세 편집 폼이 쓰는 `ranges`·`api_*`·`extra_fee_settings`· + * `custom_shipping_name` 은 담지 않는다. 이들은 중첩 JSON 이라 정책당 국가 수만큼 + * 곱해지면 목록 응답의 대부분을 차지한다. + * + * `is_active` 를 담는 이유: 목록이 비활성 국가 설정에 "비활성" 배지를 그린다. 활성만 + * 내려보내면 그 배지가 영영 뜨지 않는다. + * + * @param Request $request 요청 + * @return array 목록용 국가별 설정 배열 + */ + public function toListArray(Request $request): array + { + return [ + 'id' => $this->id, + 'country_code' => $this->country_code, + + 'shipping_method' => $this->shipping_method, + 'shipping_method_label' => $this->resolveShippingMethodLabel(), + + 'currency_code' => $this->currency_code, + + 'charge_policy' => $this->charge_policy?->value, + 'charge_policy_label' => $this->charge_policy?->label(), + + 'base_fee' => $this->roundToCurrency($this->base_fee, $this->currency_code ?: $this->getDefaultCurrencyCode()), + 'free_threshold' => $this->free_threshold !== null + ? $this->roundToCurrency($this->free_threshold, $this->currency_code ?: $this->getDefaultCurrencyCode()) + : null, + + 'extra_fee_enabled' => $this->extra_fee_enabled, + + 'is_active' => $this->is_active, + ]; + } + /** * 계산 API 연동 설정을 응답용으로 변환합니다 (인증 토큰 마스킹). * diff --git a/modules/_bundled/sirsoft-ecommerce/src/Http/Resources/ShippingPolicyResource.php b/modules/_bundled/sirsoft-ecommerce/src/Http/Resources/ShippingPolicyResource.php index 334d97fe..b10e50d2 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/Http/Resources/ShippingPolicyResource.php +++ b/modules/_bundled/sirsoft-ecommerce/src/Http/Resources/ShippingPolicyResource.php @@ -13,12 +13,15 @@ class ShippingPolicyResource extends BaseApiResource /** * 리소스를 배열로 변환 * - * @param Request $request 요청 + * @param Request $request 요청 * @return array */ public function toArray(Request $request): array { - return [ + // 컬렉션(ShippingPolicyCollection)이 이 배열을 그대로 응답에 실어 Laravel 의 MissingValue + // 제거 단계를 거치지 않는다 — 미충족 조건부 필드를 직접 걸러낸다. 걸러내지 않으면 + // 국가별 설정을 싣지 않은 목록에서 `"country_settings": {}` 가 남는다. + return $this->withoutMissing([ 'id' => $this->id, // 기본 정보 @@ -26,13 +29,23 @@ class ShippingPolicyResource extends BaseApiResource 'name_localized' => $this->getLocalizedName(), // 국가별 설정 - 'country_settings' => ShippingPolicyCountrySettingResource::collection( - $this->whenLoaded('countrySettings') + // + // whenLoaded 는 `collection()` 안이 아니라 **바깥**에 둔다. 안에 두면 미로드 시 + // MissingValue 가 컬렉션 생성자로 들어가 직렬화 단계에서 터진다(관계를 항상 + // 로드하던 동안에는 드러나지 않던 경로다). + 'country_settings' => $this->whenLoaded( + 'countrySettings', + fn () => ShippingPolicyCountrySettingResource::collection($this->countrySettings) ), // 요약 정보 - 'fee_summary' => $this->getFeeSummary(), - 'countries_display' => $this->getCountriesWithFlags(), + // + // 두 메서드는 관계가 로드돼 있지 않으면 국가별 설정을 **행마다 다시 조회**한다. + // 목록에서 무조건 호출하면 한 페이지당 정책 수만큼 쿼리가 늘어난다(N+1). 관계가 + // 실려 있을 때만 계산하고, 그렇지 않으면 키를 내보내지 않는다 — 목록을 경량으로 + // 받은 화면은 애초에 이 값을 그리지 않는다. + 'fee_summary' => $this->whenLoaded('countrySettings', fn () => $this->getFeeSummary()), + 'countries_display' => $this->whenLoaded('countrySettings', fn () => $this->getCountriesWithFlags()), // 상태 'is_active' => $this->is_active, @@ -44,7 +57,54 @@ class ShippingPolicyResource extends BaseApiResource 'updated_at' => $this->formatDateTimeStringForUser($this->updated_at), ...$this->resourceMeta($request), - ]; + ]); + } + + /** + * 목록 표현으로 변환합니다. + * + * 국가별 설정은 목록이 그리는 컬럼만 실은 `listCountrySettings` 에서 가져오고, 그 경량 + * 표현(`toListArray`)으로 직렬화한다. 요약 2필드는 관계가 실려 있으므로 재조회 없이 + * 계산된다 — 목록이 이 값을 실제로 그리기 때문에 조건부로 두지 않는다. + * + * `with_country_settings=1` 로 전체 컬럼을 요청한 호출자에게는 종전 표현을 그대로 준다 + * (외부 연동 하위호환). + * + * @param Request $request 요청 + * @return array 목록용 배송정책 배열 + */ + public function toListArray(Request $request): array + { + if ($this->resource->relationLoaded('countrySettings')) { + return $this->toArray($request); + } + + return $this->withoutMissing([ + 'id' => $this->id, + + 'name' => $this->name, + 'name_localized' => $this->getLocalizedName(), + + 'country_settings' => $this->whenLoaded( + 'listCountrySettings', + fn () => $this->listCountrySettings + ->map(fn ($setting) => (new ShippingPolicyCountrySettingResource($setting))->toListArray($request)) + ->values() + ->all() + ), + + 'fee_summary' => $this->whenLoaded('listCountrySettings', fn () => $this->getFeeSummary()), + 'countries_display' => $this->whenLoaded('listCountrySettings', fn () => $this->getCountriesWithFlags()), + + 'is_active' => $this->is_active, + 'is_default' => $this->is_default, + 'sort_order' => $this->sort_order, + + 'created_at' => $this->formatDateTimeStringForUser($this->created_at), + 'updated_at' => $this->formatDateTimeStringForUser($this->updated_at), + + ...$this->resourceMeta($request), + ]); } /** diff --git a/modules/_bundled/sirsoft-ecommerce/src/Http/Resources/UserOrderListResource.php b/modules/_bundled/sirsoft-ecommerce/src/Http/Resources/UserOrderListResource.php index cec054eb..ee289165 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/Http/Resources/UserOrderListResource.php +++ b/modules/_bundled/sirsoft-ecommerce/src/Http/Resources/UserOrderListResource.php @@ -32,14 +32,17 @@ class UserOrderListResource extends BaseApiResource // 주문 시점 기준 통화 — 과거 주문의 *_formatted 는 설정 변경과 무관하게 이 통화로 고정 표기한다. $orderCurrency = $this->resolveOrderBaseCurrencyCode($this->resource); - return [ + // 컬렉션(UserOrderCollection)이 이 배열을 그대로 응답에 실어 Laravel 의 MissingValue + // 제거 단계를 거치지 않는다 — 미충족 조건부 필드를 직접 걸러낸다. 걸러내지 않으면 + // 아이템을 싣지 않은 경로에서 `"items": {}` 가 남는다. + return $this->withoutMissing([ 'id' => $this->id, 'order_number' => $this->order_number, 'status' => $this->order_status?->value, 'status_label' => $this->order_status?->label(), 'status_variant' => $this->order_status?->variant(), // 부분취소 파생 플래그 — 일부 옵션만 취소된 주문(별도 order_status 아님). 보조 뱃지 표시용. - 'is_partially_cancelled' => $this->whenLoaded('options', fn () => $this->resource->isPartiallyCancelled(), false), + 'is_partially_cancelled' => $this->resolveIsPartiallyCancelled(), // 배송국가 (마이페이지 주문 목록 표시 — D9, 항상 표시 KR 포함) 'recipient_country_code' => $this->whenLoaded('shippingAddress', fn () => $this->shippingAddress?->recipient_country_code), @@ -66,26 +69,12 @@ class UserOrderListResource extends BaseApiResource 'total_earned_points_amount_formatted' => $this->formatOrderCurrency($this->total_earned_points_amount, $orderCurrency), // 주문 옵션 (상품 정보) - 'items' => $this->whenLoaded('options', fn () => $this->options->map(fn ($option) => [ - // array_values()[0] 사용 — reset() 은 Eloquent 매직 프로퍼티에 직접 쓰면 - // PHP 8.3 "Indirect modification of overloaded property" 경고를 유발한다. - 'product_name' => is_array($option->product_name) - ? ($option->product_name[app()->getLocale()] ?? array_values($option->product_name)[0] ?? '') - : ($option->product_name ?? ''), - 'product_option_name' => is_array($option->product_option_name) - ? ($option->product_option_name[app()->getLocale()] ?? array_values($option->product_option_name)[0] ?? '') - : ($option->product_option_name ?? ''), - 'thumbnail_url' => $option->product_snapshot['thumbnail_url'] ?? null, - 'quantity' => $option->quantity, - 'unit_price_formatted' => $this->formatOrderCurrency($option->unit_price, $orderCurrency), - 'mc_unit_price' => $this->formatStoredMultiCurrency($option->mc_unit_price), - 'subtotal_price' => $this->roundToOrderCurrency($option->subtotal_price, $orderCurrency), - 'subtotal_price_formatted' => $this->formatOrderCurrency($option->subtotal_price, $orderCurrency), - 'mc_subtotal_price' => $this->formatStoredMultiCurrency($option->mc_subtotal_price), - // 추가옵션 요약 (스냅샷 기반 — 첫 1건 + "외 N건", custom_text 병기) - 'additional_options_summary' => $this->summarizeAdditionalOptions($option), - ])->toArray()), - 'item_count' => $this->whenLoaded('options', fn () => $this->options->count()), + // + // 기본은 대표 1건만 싣는다. 마이페이지 주문내역처럼 한 주문의 아이템을 전부 + // 나열하는 화면은 `?with_items=1` 로 켜서 전량을 받는다 — 화면이 켜는 쪽이지 + // 서버가 모두에게 전량을 안기는 쪽이 아니다. + 'items' => $this->resolveItems($orderCurrency), + 'item_count' => $this->resolveItemCount(), // 권한 메타 'abilities' => [ @@ -98,6 +87,109 @@ class UserOrderListResource extends BaseApiResource ) ) && PermissionHelper::check('sirsoft-ecommerce.user-orders.cancel'), ], + ]); + } + + /** + * 목록에 실을 아이템 배열을 반환합니다. + * + * 전량이 로드된 경로(`?with_items=1`)는 전부, 대표 1건만 로드된 기본 경로는 그 1건만 + * 담는다. 어느 쪽도 로드되지 않았으면 키를 내보내지 않는다 — 빈 배열은 "아이템이 없는 + * 주문" 이라는 사실 아닌 단언이 된다. + * + * @param string|null $orderCurrency 주문 시점 기준 통화 + * @return array>|\Illuminate\Http\Resources\MissingValue 아이템 배열 + */ + private function resolveItems(?string $orderCurrency) + { + if ($this->resource->relationLoaded('options')) { + return $this->resource->options + ->map(fn ($option) => $this->formatItem($option, $orderCurrency)) + ->values() + ->all(); + } + + if ($this->resource->relationLoaded('firstOption')) { + $representative = $this->resource->firstOption; + + return $representative + ? [$this->formatItem($representative, $orderCurrency)] + : []; + } + + return new \Illuminate\Http\Resources\MissingValue; + } + + /** + * 주문 아이템 1건을 표시용 배열로 변환합니다. + * + * @param \Modules\Sirsoft\Ecommerce\Models\OrderOption $option 주문 옵션 + * @param string|null $orderCurrency 주문 시점 기준 통화 + * @return array 표시용 아이템 배열 + */ + private function formatItem($option, ?string $orderCurrency): array + { + return [ + // array_values()[0] 사용 — reset() 은 Eloquent 매직 프로퍼티에 직접 쓰면 + // PHP 8.3 "Indirect modification of overloaded property" 경고를 유발한다. + 'product_name' => is_array($option->product_name) + ? ($option->product_name[app()->getLocale()] ?? array_values($option->product_name)[0] ?? '') + : ($option->product_name ?? ''), + 'product_option_name' => is_array($option->product_option_name) + ? ($option->product_option_name[app()->getLocale()] ?? array_values($option->product_option_name)[0] ?? '') + : ($option->product_option_name ?? ''), + 'thumbnail_url' => $option->product_snapshot['thumbnail_url'] ?? null, + 'quantity' => $option->quantity, + 'unit_price_formatted' => $this->formatOrderCurrency($option->unit_price, $orderCurrency), + 'mc_unit_price' => $this->formatStoredMultiCurrency($option->mc_unit_price), + 'subtotal_price' => $this->roundToOrderCurrency($option->subtotal_price, $orderCurrency), + 'subtotal_price_formatted' => $this->formatOrderCurrency($option->subtotal_price, $orderCurrency), + 'mc_subtotal_price' => $this->formatStoredMultiCurrency($option->mc_subtotal_price), + // 추가옵션 요약 (스냅샷 기반 — 첫 1건 + "외 N건", custom_text 병기) + 'additional_options_summary' => $this->summarizeAdditionalOptions($option), ]; } + + /** + * 전체 아이템 수를 반환합니다. + * + * 대표 1건만 실은 경로에서도 "외 N건" 을 그릴 수 있도록 DB 집계를 우선 쓴다. 집계 별칭 + * 유무는 값이 아니라 속성 키로 판정한다 — 0건의 `0` 과 "집계 안 함" 은 값으로 구분되지 + * 않는다. + * + * @return int|\Illuminate\Http\Resources\MissingValue 아이템 수 + */ + private function resolveItemCount() + { + if ($this->hasAggregate('options_count')) { + return (int) $this->aggregate('options_count', 0); + } + + if ($this->resource->relationLoaded('options')) { + return $this->resource->options->count(); + } + + return new \Illuminate\Http\Resources\MissingValue; + } + + /** + * 부분취소 여부를 반환합니다. + * + * 대표 1건만 실은 경로는 옵션을 순회할 수 없으므로 DB 집계 두 개(전체 수 / 취소 수)로 + * 같은 판정을 얻는다. + * + * @return bool 부분취소 여부 + */ + private function resolveIsPartiallyCancelled(): bool + { + if ($this->hasAggregate('options_count') && $this->hasAggregate('cancelled_options_count')) { + $cancelled = (int) $this->aggregate('cancelled_options_count', 0); + + return $cancelled > 0 && (int) $this->aggregate('options_count', 0) > $cancelled; + } + + return $this->resource->relationLoaded('options') + ? $this->resource->isPartiallyCancelled() + : false; + } } diff --git a/modules/_bundled/sirsoft-ecommerce/src/Models/Order.php b/modules/_bundled/sirsoft-ecommerce/src/Models/Order.php index cd2be24e..9b598bbb 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/Models/Order.php +++ b/modules/_bundled/sirsoft-ecommerce/src/Models/Order.php @@ -224,6 +224,21 @@ class Order extends Model return $this->hasMany(OrderOption::class, 'order_id'); } + /** + * 대표 주문 옵션 관계 (가장 먼저 담긴 1건) + * + * 목록 화면은 대표 상품 1건만 그린다. `options` 를 통째로 eager load 한 뒤 `first()` 로 + * 1건만 쓰면 한 페이지를 여는 것만으로 그 페이지 전 주문의 모든 옵션이 메모리에 올라온다. + * eager load 의 `limit(1)` 은 부모별이 아니라 배치 쿼리 전체에 걸리므로 쓸 수 없고, + * 관계 자체를 "가장 오래된 1건" 으로 좁히는 이 방식이라야 주문별 1건이 보장된다. + * + * @return HasOne 대표 주문 옵션과의 관계 + */ + public function firstOption(): HasOne + { + return $this->hasOne(OrderOption::class, 'order_id')->oldestOfMany(); + } + /** * 배송지 관계 (모든 주소 유형) * @@ -286,6 +301,21 @@ class Order extends Model return $this->hasMany(OrderShipping::class, 'order_id'); } + /** + * 대표 배송 관계 (가장 먼저 생성된 1건) + * + * 목록 화면은 배송 정보 중 대표 1건(배송유형·배송방법·택배사·송장번호)만 그린다. + * `shippings` 를 통째로 eager load 한 뒤 `first()` 로 1건만 쓰면, 분할 배송이 많은 주문일수록 + * 쓰지 않는 배송 행이 페이지 전체에 실린다. `firstOption` 과 같은 이유로 `oldestOfMany()` 를 + * 쓴다 — eager load 의 `limit(1)` 은 배치 쿼리 전체에 걸려 첫 주문만 대표를 갖게 된다. + * + * @return HasOne 대표 배송과의 관계 + */ + public function firstShipping(): HasOne + { + return $this->hasOne(OrderShipping::class, 'order_id')->oldestOfMany(); + } + /** * 세금계산서 관계 * diff --git a/modules/_bundled/sirsoft-ecommerce/src/Models/Product.php b/modules/_bundled/sirsoft-ecommerce/src/Models/Product.php index a565b8ae..642951ff 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/Models/Product.php +++ b/modules/_bundled/sirsoft-ecommerce/src/Models/Product.php @@ -178,10 +178,21 @@ class Product extends Model implements FulltextSearchable * * 대표 이미지(`is_thumbnail=true`) 가 없으면 첫 번째 이미지로 폴백. * + * `images` 가 이미 eager load 된 경우(목록 화면)는 로드된 컬렉션에서 고른다. 관계 빌더를 + * 다시 태우면 행마다 쿼리가 1~2회 더 나가 eager load 가 무의미해진다. 로드된 컬렉션은 + * 관계 정의의 `sort_order` 정렬을 그대로 가지므로 선택 결과는 재쿼리와 동일하다. + * * @return string|null 대표 이미지 download_url 또는 이미지가 없으면 null */ public function getThumbnailUrl(): ?string { + if ($this->relationLoaded('images')) { + $thumbnailImage = $this->images->firstWhere('is_thumbnail', true) + ?? $this->images->first(); + + return $thumbnailImage?->download_url; + } + $thumbnailImage = $this->images()->where('is_thumbnail', true)->first() ?? $this->images()->first(); diff --git a/modules/_bundled/sirsoft-ecommerce/src/Models/ShippingPolicy.php b/modules/_bundled/sirsoft-ecommerce/src/Models/ShippingPolicy.php index 72cf5529..90bd936c 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/Models/ShippingPolicy.php +++ b/modules/_bundled/sirsoft-ecommerce/src/Models/ShippingPolicy.php @@ -22,6 +22,31 @@ class ShippingPolicy extends Model 'sort_order' => ['label_key' => 'sirsoft-ecommerce::activity_log.fields.sort_order', 'type' => 'number'], ]; + /** + * 목록 경로가 국가별 설정에서 읽는 컬럼. + * + * 목록 화면이 그리는 10개 필드 + 그 값을 만드는 데 필요한 것만 남긴다. + * `shipping_policy_id` 는 eager load 가 부모와 자식을 짝지을 때 쓰므로 빠지면 관계가 + * 통째로 비고, `custom_shipping_name` 은 배송방법이 `custom` 일 때 라벨의 원본이며, + * `ranges` 는 구간제 배송비 요약이 읽는다. + * + * @var array + */ + public const LIST_COUNTRY_SETTING_COLUMNS = [ + 'id', + 'shipping_policy_id', + 'country_code', + 'shipping_method', + 'custom_shipping_name', + 'currency_code', + 'charge_policy', + 'base_fee', + 'free_threshold', + 'ranges', + 'extra_fee_enabled', + 'is_active', + ]; + protected $table = 'ecommerce_shipping_policies'; protected $fillable = [ @@ -50,6 +75,51 @@ class ShippingPolicy extends Model return $this->hasMany(ShippingPolicyCountrySetting::class); } + /** + * 목록 화면이 그리는 컬럼만 실은 국가별 설정 관계. + * + * 배송정책 목록과 상품 등록/수정의 배송정책 선택기는 국가 칩·배송방법·부과정책·배송비만 + * 그린다. 그런데 전체 컬럼을 실으면 행마다 `extra_fee_settings`(도서산간 지역 배열)와 + * `api_config`/`api_request_fields` 가 함께 딸려온다 — 화면이 그리지 않는 중첩 JSON 이 + * 정책당 국가 수만큼 응답에 실린다. + * + * `countrySettings` 를 좁히지 않고 **별도 관계**로 둔 이유: 배송비 계산 + * (`OrderCalculationService` → `getCountrySetting()`)은 `extra_fee_settings`·`api_config` + * 를 실제로 읽는다. 같은 이름을 좁히면 목록 경로가 남긴 부분 로드가 계산 경로로 흘러들어 + * 배송비가 조용히 틀려진다. + * + * `ranges` 는 뺄 수 없다 — 구간제/단위당 정책의 배송비 요약(`getFeeSummary`)이 읽는다. + * + * @return HasMany 목록용 컬럼만 선택한 ShippingPolicyCountrySetting 관계 + */ + public function listCountrySettings(): HasMany + { + return $this->hasMany(ShippingPolicyCountrySetting::class) + ->select(self::LIST_COUNTRY_SETTING_COLUMNS); + } + + /** + * 요약·목록 계산에 쓰는 활성 국가별 설정을 반환합니다. + * + * 로드된 관계가 있으면 그것을 쓰고, 없으면 조회한다. 어느 경로로 오든 **활성만** 남기므로 + * 같은 정책의 요약이 조회 경로에 따라 달라지지 않는다 — 목록은 비활성 배지를 그리려고 + * 비활성 행까지 싣기 때문에, 로더의 필터에 요약의 정확성을 맡길 수 없다. + * + * @return \Illuminate\Support\Collection 활성 국가별 설정 컬렉션 + */ + protected function resolveActiveCountrySettings() + { + if ($this->relationLoaded('countrySettings')) { + return $this->countrySettings->where('is_active', true)->values(); + } + + if ($this->relationLoaded('listCountrySettings')) { + return $this->listCountrySettings->where('is_active', true)->values(); + } + + return $this->countrySettings()->where('is_active', true)->get(); + } + /** * 현재 로케일의 정책명을 반환합니다 (다국어 fallback chain 적용). * @@ -73,9 +143,7 @@ class ShippingPolicy extends Model */ public function getFeeSummary(): string { - $settings = $this->relationLoaded('countrySettings') - ? $this->countrySettings - : $this->countrySettings()->where('is_active', true)->get(); + $settings = $this->resolveActiveCountrySettings(); if ($settings->isEmpty()) { return ''; @@ -98,9 +166,7 @@ class ShippingPolicy extends Model */ public function getDetailedFeeInfo(): array { - $settings = $this->relationLoaded('countrySettings') - ? $this->countrySettings - : $this->countrySettings()->where('is_active', true)->get(); + $settings = $this->resolveActiveCountrySettings(); return $settings->map(function (ShippingPolicyCountrySetting $setting) { return [ @@ -120,9 +186,7 @@ class ShippingPolicy extends Model */ public function getCountriesWithFlags(int $limit = 3): string { - $settings = $this->relationLoaded('countrySettings') - ? $this->countrySettings - : $this->countrySettings()->where('is_active', true)->get(); + $settings = $this->resolveActiveCountrySettings(); $countryCodes = $settings->pluck('country_code')->toArray(); diff --git a/modules/_bundled/sirsoft-ecommerce/src/Repositories/CartRepository.php b/modules/_bundled/sirsoft-ecommerce/src/Repositories/CartRepository.php index aa24d241..c50c80d6 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/Repositories/CartRepository.php +++ b/modules/_bundled/sirsoft-ecommerce/src/Repositories/CartRepository.php @@ -50,13 +50,32 @@ class CartRepository implements CartRepositoryInterface return $cart->delete(); } + /** + * 장바구니 조회의 상품 이미지 eager load 컬럼. + * + * 장바구니는 상품당 **대표 이미지 1장의 URL** 만 쓴다(BaseOrderItemResource::formatProductInfo). + * 대표 지정이 없으면 첫 이미지로 폴백하므로 `thumbnail` 관계로 바꾸면 그 폴백이 깨진다. + * 그래서 관계는 그대로 두고 선택 컬럼만 좁힌다 — `download_url` 은 `hash` 만 필요하고, + * 폴백 판정에 `is_thumbnail`/`sort_order`, eager load 매칭에 `id`/`product_id` 가 필요하다. + * 파일명·경로·용량·크기 등 나머지 컬럼은 장바구니 응답 어디에도 쓰이지 않는다. + * + * `product.shippingPolicy.countrySettings` 는 응답에 직렬화되지 않지만 의도적으로 유지한다. + * 배송비 계산(`OrderCalculationService`)이 이 관계를 읽고, 로드돼 있지 않으면 정책 인스턴스 + * 마다 `load('countrySettings')` 로 다시 조회한다(`:1884-1891`, `:1953-1955`). 장바구니 항목 + * 마다 정책 인스턴스가 따로 잡히므로 eager load 를 빼면 페이로드 대신 쿼리 수가 늘어난다 — + * 결제 금액 산출 경로라 그 교환은 하지 않는다. + * + * 활성 설정만 싣는 방식도 검토했으나 계산기의 지연 조회 경로가 `is_active` 를 거르지 않아 + * (`:1955`) 로드 여부에 따라 배송비가 달라지게 된다. 좁히려면 계산기 쪽 기준을 먼저 통일해야 + * 하며 그것은 이 변경의 범위 밖이다. + */ /** * {@inheritDoc} */ public function findByUserId(int $userId): Collection { return $this->model - ->with(['product.images', 'product.additionalOptions.values', 'product.shippingPolicy.countrySettings', 'productOption']) + ->with(['product.images:id,product_id,hash,is_thumbnail,sort_order', 'product.additionalOptions.values', 'product.shippingPolicy.countrySettings', 'productOption']) ->where('user_id', $userId) ->orderBy('created_at', 'desc') ->get(); @@ -68,7 +87,7 @@ class CartRepository implements CartRepositoryInterface public function findByCartKeyWithoutUser(string $cartKey): Collection { return $this->model - ->with(['product.images', 'product.additionalOptions.values', 'product.shippingPolicy.countrySettings', 'productOption']) + ->with(['product.images:id,product_id,hash,is_thumbnail,sort_order', 'product.additionalOptions.values', 'product.shippingPolicy.countrySettings', 'productOption']) ->where('cart_key', $cartKey) ->whereNull('user_id') ->orderBy('created_at', 'desc') @@ -127,7 +146,7 @@ class CartRepository implements CartRepositoryInterface public function findByIds(array $ids): Collection { return $this->model - ->with(['product.images', 'product.additionalOptions.values', 'product.shippingPolicy.countrySettings', 'productOption']) + ->with(['product.images:id,product_id,hash,is_thumbnail,sort_order', 'product.additionalOptions.values', 'product.shippingPolicy.countrySettings', 'productOption']) ->whereIn('id', $ids) ->get(); } diff --git a/modules/_bundled/sirsoft-ecommerce/src/Repositories/Contracts/ProductRepositoryInterface.php b/modules/_bundled/sirsoft-ecommerce/src/Repositories/Contracts/ProductRepositoryInterface.php index 4eed0e64..4a3e65b8 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/Repositories/Contracts/ProductRepositoryInterface.php +++ b/modules/_bundled/sirsoft-ecommerce/src/Repositories/Contracts/ProductRepositoryInterface.php @@ -48,6 +48,19 @@ interface ProductRepositoryInterface */ public function getListWithFilters(array $filters, int $perPage = 20): LengthAwarePaginator; + /** + * 여러 상품의 옵션을 상품 ID 로 묶어 한 번에 조회합니다 (비활성 옵션 포함). + * + * 목록에서 펼친 행들의 옵션을 가져오기 위한 배치 조회입니다. 권한 스코프를 통과하지 못했거나 + * 존재하지 않는 상품 ID 는 결과에서 빠지며, 반환된 `product_ids` 를 요청 배열과 대조하면 + * 어떤 ID 가 제외됐는지 알 수 있습니다. + * + * @param array $productIds 조회할 상품 ID 목록 + * @return array{product_ids: array, options: \Illuminate\Support\Collection|array} + * 스코프를 통과한 상품 ID(요청 순서 유지) 와 상품 ID 로 그룹핑된 옵션 + */ + public function getOptionsGroupedByProductIds(array $productIds): array; + /** * 상품 생성 * diff --git a/modules/_bundled/sirsoft-ecommerce/src/Repositories/CouponIssueRepository.php b/modules/_bundled/sirsoft-ecommerce/src/Repositories/CouponIssueRepository.php index 425f3ece..874dd4b5 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/Repositories/CouponIssueRepository.php +++ b/modules/_bundled/sirsoft-ecommerce/src/Repositories/CouponIssueRepository.php @@ -173,6 +173,11 @@ class CouponIssueRepository implements CouponIssueRepositoryInterface columns: ['*'], sort: [['column' => 'created_at', 'direction' => 'desc']], perPage: $perPage, + // audit:allow list-repository-eager-load-vs-resource reason: CouponIssueResource 는 + // coupon 관계를 직렬화하지 않지만, 이 목록은 훅 + // `sirsoft-ecommerce.user_coupon.filter_list_result` 로 확장에 그대로 전달된다. + // 제3자 확장이 쿠폰 원본 정의(쿠폰명·할인율·사용조건)를 읽고 있을 수 있어 현행 유지 + // (PO 결정 2026-08-04). 번들 템플릿에는 아직 대응 마이페이지 화면이 없다. relations: ['coupon'], ); } diff --git a/modules/_bundled/sirsoft-ecommerce/src/Repositories/OrderRepository.php b/modules/_bundled/sirsoft-ecommerce/src/Repositories/OrderRepository.php index b5333541..0f367e31 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/Repositories/OrderRepository.php +++ b/modules/_bundled/sirsoft-ecommerce/src/Repositories/OrderRepository.php @@ -381,6 +381,11 @@ class OrderRepository implements OrderRepositoryInterface 'ordered_at', ); + // 마이페이지 주문내역은 한 주문의 아이템을 전부 나열하므로 옵션 전량이 필요하다(실측: + // `partials/mypage/orders/_list.json` 의 `order.items` 순회). 관리자 목록은 대표 상품 + // 1건과 "외 N건" 만 그리므로 대표 1건 + 집계로 충분하다. + $withItems = ! empty($filters['with_items']); + return $this->paginateWithDeferredJoin( query: $query, columns: self::LIST_COLUMNS, @@ -388,10 +393,22 @@ class OrderRepository implements OrderRepositoryInterface perPage: $perPage, relations: [ 'user', - 'options', + $withItems ? 'options' : 'firstOption', 'shippingAddress', 'payment', - 'shippings', + // 목록은 대표 배송 1건의 표시 정보만 그린다. 택배사 이름까지 함께 로드해야 + // Resource 가 행마다 carrier 를 다시 조회하지 않는다. + 'firstShipping.carrier', + ], + withCount: $withItems ? [] : [ + // "외 N건" 표기용 전체 옵션 수. + 'options', + // 부분취소 뱃지 판정용 — 취소 옵션이 있으면서 남은 옵션도 있는 상태. + // 컬렉션을 순회하지 않고 DB 집계 두 개로 같은 판정을 얻는다. + 'options as cancelled_options_count' => fn ($optionQuery) => $optionQuery->where( + 'option_status', + OrderStatusEnum::CANCELLED->value, + ), ], ); } diff --git a/modules/_bundled/sirsoft-ecommerce/src/Repositories/ProductRepository.php b/modules/_bundled/sirsoft-ecommerce/src/Repositories/ProductRepository.php index f5570d4e..9e22da56 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/Repositories/ProductRepository.php +++ b/modules/_bundled/sirsoft-ecommerce/src/Repositories/ProductRepository.php @@ -83,7 +83,35 @@ class ProductRepository implements ProductRepositoryInterface PermissionHelper::applyPermissionScope($query, 'sirsoft-ecommerce.products.read'); // 지연 조인 outer 전용 관계 (inner 는 id 만 훑으므로 여기서 with() 를 걸지 않는다) - $listRelations = ['options', 'categories', 'images', 'brand', 'shippingPolicy']; + // + // 옵션은 기본적으로 로드하지 않는다. 목록 한 페이지(최대 100행)를 여는 것만으로 그 페이지 + // 전 상품의 옵션 행이 모두 적재·직렬화되기 때문이다. 목록이 실제로 쓰는 것은 개수와 재고 + // 합계뿐이라 아래 DB 집계로 대체하고, 옵션 상세는 행을 펼칠 때 배치 엔드포인트 + // (`GET admin/products/options`)로 가져간다. `?with_options=1` 은 종전 동작을 위한 opt-in. + $listRelations = ['categories', 'images', 'brand', 'shippingPolicy']; + + if (! empty($filters['with_options'])) { + $listRelations[] = 'options'; + } + + // 옵션 집계 — PHP 컬렉션 연산 대신 DB 집계로 계산한다. + // options_count : 활성 옵션 수 (화면 표시용) + // options_total_count : 비활성 포함 전체 옵션 수. 일괄 변경 확인 모달이 서버의 + // 적용 모집단(ProductOptionRepository::getIdsByProductIds — is_active + // 무필터)과 같은 수를 세려면 이쪽이어야 한다 + // 별칭을 생략하면 두 카운트의 기본 별칭이 `options_count` 로 같아져 뒤엣것이 앞엣것을 + // 조용히 덮으므로 `as` 로 반드시 구분한다. + $listWithCount = [ + 'options as options_count' => fn ($q) => $q->where('is_active', true), + 'options as options_total_count', + ]; + + // withSum 은 트레이트 인자가 없으므로 outer 전용 클로저로 붙인다. inner 에 붙으면 + // 건너뛸 행 전체에 대해 실행된다. + $listOuterUsing = fn (Builder $outer) => $outer->withSum( + ['options as option_stock_sum' => fn ($q) => $q->where('is_active', true)], + 'stock_quantity' + ); // 문자열 검색 if (! empty($filters['search_keyword'])) { @@ -130,6 +158,8 @@ class ProductRepository implements ProductRepositoryInterface sort: $this->resolveAdminSortSpec($filters), perPage: $perPage, relations: $listRelations, + withCount: $listWithCount, + outerUsing: $listOuterUsing, ); } @@ -156,9 +186,58 @@ class ProductRepository implements ProductRepositoryInterface sort: $this->resolveAdminSortSpec($filters), perPage: $perPage, relations: $listRelations, + withCount: $listWithCount, + outerUsing: $listOuterUsing, ); } + /** + * {@inheritDoc} + */ + public function getOptionsGroupedByProductIds(array $productIds): array + { + $requested = array_values(array_unique(array_map('intval', $productIds))); + + if ($requested === []) { + return ['product_ids' => [], 'options' => []]; + } + + // ① 권한 스코프를 적용한 상품 쿼리로 허용 ID 를 먼저 확정한다. 옵션 테이블을 바로 + // 조회하면 scope=self 사용자가 남의 상품 ID 를 실어보내는 것만으로 옵션을 읽을 수 있다. + $scoped = $this->model->newQuery(); + PermissionHelper::applyPermissionScope($scoped, 'sirsoft-ecommerce.products.read'); + + $allowedIds = $scoped->whereIn('id', $requested) + ->pluck('id') + ->map(fn ($id) => (int) $id) + ->all(); + + if ($allowedIds === []) { + return ['product_ids' => [], 'options' => []]; + } + + // 요청 순서를 유지한다 (프론트가 요청 배열과 그대로 대조할 수 있도록) + $allowedSet = array_flip($allowedIds); + $allowedIds = array_values(array_filter($requested, fn ($id) => isset($allowedSet[$id]))); + + // ② 옵션은 상품 수와 무관하게 쿼리 1개. 비활성 옵션도 포함한다 — 인라인 편집기가 + // `is_active` 토글을 다루므로 활성만 내려주면 비활성 옵션을 편집할 수 없게 된다. + // 정렬은 상품폼/activeOptions 와 같은 `sort_order` 기준으로 통일한다. + // + // `product` 를 함께 로드하는 이유: 옵션 리소스의 가격 필드는 상품 정가/판매가에 + // 조정액을 더해 만든다(ProductOption::getListPrice/getFinalPrice). 부모를 로드하지 + // 않으면 옵션 1건마다 상품 조회가 한 번씩 나가 옵션 수만큼 N+1 이 된다. + $options = ProductOption::with('product') + ->whereIn('product_id', $allowedIds) + ->orderBy('product_id') + ->orderBy('sort_order') + ->orderBy('id') + ->get() + ->groupBy('product_id'); + + return ['product_ids' => $allowedIds, 'options' => $options]; + } + /** * {@inheritDoc} */ @@ -570,6 +649,9 @@ class ProductRepository implements ProductRepositoryInterface $paginator = $this->model->newQuery() ->whereIn('id', $matchedIds ?: [0]) ->where('display_status', ProductDisplayStatus::VISIBLE->value) + // audit:allow list-repository-eager-load-vs-resource reason: 통합검색 결과는 Resource 가 + // 아니라 SearchProductsListener 가 직렬화한다 — 그 안에서 primaryCategory 를 행마다 + // 읽으므로(`$product->primaryCategory->first()`) 여기서 미리 로드하지 않으면 N+1 이 된다 ->with(['images', 'primaryCategory', 'brand', 'activeLabelAssignments.label']) ->withCount('visibleReviews as review_count') ->withAvg('visibleReviews as rating_avg', 'rating') diff --git a/modules/_bundled/sirsoft-ecommerce/src/Repositories/ProductReviewRepository.php b/modules/_bundled/sirsoft-ecommerce/src/Repositories/ProductReviewRepository.php index 6526e953..d7ad1c5f 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/Repositories/ProductReviewRepository.php +++ b/modules/_bundled/sirsoft-ecommerce/src/Repositories/ProductReviewRepository.php @@ -121,12 +121,30 @@ class ProductReviewRepository implements ProductReviewRepositoryInterface default => [['column' => 'created_at', 'direction' => 'desc']], }; + // `images` 는 관리자 목록에서 의도적으로 유지한다. 공개 리뷰 목록(`getPublicListByProduct`)도 + // 같은 이유로 유지하므로, 한쪽만 뺄 수 있다고 읽지 말 것. + // + // 소비처 실측: `admin_ecommerce_product_review_index.json` 의 확장 행이 썸네일을 최대 + // 3장 그리고(`:2199`, `:2237`, `:2275`), 클릭 시 미리보기 모달에 배열 전체를 넘긴다 + // (`:2218`, `:2256`, `:2294`). 목록에서 빼면 그 화면이 그대로 기능을 잃는다. + // + // 줄이려면 페이로드를 깎는 것이 아니라 확장 시 지연 로드하는 경로(상품 옵션과 같은 + // 방식)를 먼저 만들어야 하며, 그것은 이 변경의 범위 밖이다. return $this->paginateWithDeferredJoin( query: $query, columns: ['*'], sort: $sort, perPage: $perPage, - relations: ['user', 'product', 'images', 'orderOption.order', 'replyAdmin'], + // `product.images` 는 상품 썸네일 산출용이다. 미로드 시 `getThumbnailUrl()` 이 + // 행마다 관계를 최대 2회 재조회한다. 썸네일 조립 컬럼만 읽어 페이로드는 늘리지 않는다. + relations: [ + 'user', + 'product', + 'product.images:id,product_id,hash,is_thumbnail,sort_order', + 'images', + 'orderOption.order', + 'replyAdmin', + ], ); } @@ -190,7 +208,20 @@ class ProductReviewRepository implements ProductReviewRepositoryInterface columns: ['*'], sort: $sort, perPage: $perPage, + // 리뷰 첨부 이미지는 로드한다 — 상품 상세의 리뷰 탭이 실제로 그린다. + // + // 소비처 실측: `templates/_bundled/sirsoft-basic/layouts/partials/shop/detail/ + // _tab_reviews.json` 이 썸네일 3장을 인덱스로 직접 읽고(`review.images[0..2] + // .download_url`), 클릭 시 이미지 모달에 배열 전체를 넘긴다. 이 파셜은 + // `shop/show.json` 이 include 하므로 그 파일만 열어보면 참조가 보이지 않는다. + // + // 빼면 조용히 깨진다. 썸네일이 `(review.images ?? []).length > 0` 가드 뒤에 있어 + // 예외도 콘솔 경고도 없이 자리만 빈다. 게다가 바깥 컨테이너는 `image_count > 0` 로 + // 열리므로 "빈 상자" 가 남는다. + // + // 개수는 배열 길이 대신 DB 집계를 그대로 쓴다(Resource 가 집계를 우선한다). relations: ['user', 'images'], + withCount: ['images as image_count'], ); } diff --git a/modules/_bundled/sirsoft-ecommerce/src/Repositories/ProductWishlistRepository.php b/modules/_bundled/sirsoft-ecommerce/src/Repositories/ProductWishlistRepository.php index 3214ee27..7ca5cd66 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/Repositories/ProductWishlistRepository.php +++ b/modules/_bundled/sirsoft-ecommerce/src/Repositories/ProductWishlistRepository.php @@ -60,7 +60,15 @@ class ProductWishlistRepository implements ProductWishlistRepositoryInterface // 상품이 소프트 삭제되면 eager load 결과가 null 이 되어 카드가 렌더 실패한다. // 목록에서 아예 제외해 "빈 셀 0" 을 성립시킨다 (찜 행 자체는 보존). ->whereHas('product') - ->with(['product.brand', 'product.categories', 'product.activeLabelAssignments.label']) + // 대표 이미지는 로드된 컬렉션에서 고른다. 미로드 시 `getThumbnailUrl()` 이 행마다 + // 관계를 최대 2회 재조회하므로(대표 지정 조회 → 첫 이미지 폴백) 행 수에 비례해 늘어난다. + // 썸네일 URL 조립에 필요한 컬럼만 읽어 페이로드는 늘리지 않는다. + ->with([ + 'product.brand', + 'product.categories', + 'product.activeLabelAssignments.label', + 'product.images:id,product_id,hash,is_thumbnail,sort_order', + ]) ->orderByDesc('created_at') // 전순서 보장 — 한 번에 여러 건을 담았을 때의 created_at 동률 대비 ->orderByDesc('id') diff --git a/modules/_bundled/sirsoft-ecommerce/src/Repositories/ShippingPolicyRepository.php b/modules/_bundled/sirsoft-ecommerce/src/Repositories/ShippingPolicyRepository.php index 1136e207..2d9966c0 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/Repositories/ShippingPolicyRepository.php +++ b/modules/_bundled/sirsoft-ecommerce/src/Repositories/ShippingPolicyRepository.php @@ -38,7 +38,25 @@ class ShippingPolicyRepository implements ShippingPolicyRepositoryInterface */ public function getListWithFilters(array $filters, int $perPage = 20): LengthAwarePaginator { - $query = $this->model->newQuery()->with('countrySettings'); + // 국가별 설정은 목록이 실제로 그리는 컬럼만 싣는다. + // + // 이 목록의 소비자 둘(배송정책 관리 목록, 상품 등록/수정의 배송정책 선택기)은 국가 칩· + // 배송방법·부과정책·배송비·활성여부만 그린다. 종전에는 전체 컬럼을 실어 화면이 쓰지도 + // 않는 `extra_fee_settings`(도서산간 지역 배열)·`api_config`·`api_request_fields` 가 + // 정책당 국가 수만큼 응답에 실렸다. + // + // 관계를 좁히지 않고 **별도 관계**(`listCountrySettings`)를 쓰는 이유는 모델 docblock + // 참조 — `countrySettings` 를 좁히면 부분 로드가 배송비 계산 경로로 흘러든다. + // + // 활성 필터는 걸지 않는다. 목록이 비활성 국가 설정에 "비활성" 배지를 그리므로, 활성만 + // 실으면 그 배지가 영영 뜨지 않는다(기능 축소). 요약 계산은 모델이 로드된 컬렉션에서 + // 다시 활성만 걸러 쓰므로 요약값은 경로와 무관하게 같다. + $query = $this->model->newQuery()->with('listCountrySettings'); + + // 전체 컬럼이 필요한 외부 호출자를 위한 하위호환 경로 — 켜면 종전 응답 그대로다. + if (! empty($filters['with_country_settings'])) { + $query->with('countrySettings'); + } // 권한 스코프 필터링 PermissionHelper::applyPermissionScope($query, 'sirsoft-ecommerce.shipping-policies.read'); @@ -195,8 +213,14 @@ class ShippingPolicyRepository implements ShippingPolicyRepositoryInterface */ public function getActiveList(): Collection { + // 목록용 컬럼만 실은 국가별 설정을 함께 싣는다. + // + // 이 목록의 소비자(`ShippingPolicyController::activeList`)는 정책마다 + // `getCountriesWithFlags()` 와 `getFeeSummary()` 를 호출한다. 두 메서드는 관계가 + // 로드돼 있지 않으면 **행마다** 국가별 설정을 다시 조회하므로, 싣지 않으면 정책 수 + // × 2 쿼리가 된다 — 페이로드를 줄이려다 쿼리를 늘리는 맞바꿈이 된다. return $this->model - ->with('countrySettings') + ->with('listCountrySettings') ->active() ->orderBy('sort_order') ->orderBy('id') diff --git a/modules/_bundled/sirsoft-ecommerce/src/Services/ProductService.php b/modules/_bundled/sirsoft-ecommerce/src/Services/ProductService.php index a8c2b583..bf208622 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/Services/ProductService.php +++ b/modules/_bundled/sirsoft-ecommerce/src/Services/ProductService.php @@ -64,6 +64,21 @@ class ProductService return $this->repository->getListWithFilters($filters, $perPage); } + /** + * 여러 상품의 옵션을 상품 ID 로 묶어 한 번에 조회합니다 (비활성 옵션 포함). + * + * 상품 목록에서 펼친 행들의 옵션을 채우기 위한 배치 조회입니다. 상품 수와 무관하게 + * 쿼리 2개(허용 ID 확정 + 옵션 조회)로 끝납니다. + * + * @param array $productIds 조회할 상품 ID 목록 + * @return array{product_ids: array, options: \Illuminate\Support\Collection|array} + * 스코프를 통과한 상품 ID 와 상품 ID 로 그룹핑된 옵션 + */ + public function getOptionsByProductIds(array $productIds): array + { + return $this->repository->getOptionsGroupedByProductIds($productIds); + } + /** * 사용자(공개) 페이지용 상품 목록을 페이지네이션으로 조회합니다. * diff --git a/modules/_bundled/sirsoft-ecommerce/src/lang/en/messages.php b/modules/_bundled/sirsoft-ecommerce/src/lang/en/messages.php index 52650cd7..39bbab64 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/lang/en/messages.php +++ b/modules/_bundled/sirsoft-ecommerce/src/lang/en/messages.php @@ -69,6 +69,8 @@ return [ 'code_generation_failed' => 'Failed to generate product code.', 'logs_fetch_success' => 'Product activity logs retrieved successfully.', 'logs_fetch_failed' => 'Failed to retrieve product activity logs.', + 'options_fetch_success' => 'Product options retrieved successfully.', + 'options_fetch_failed' => 'Failed to retrieve product options.', ], 'presets' => [ 'created' => 'Search preset has been saved.', diff --git a/modules/_bundled/sirsoft-ecommerce/src/lang/en/validation.php b/modules/_bundled/sirsoft-ecommerce/src/lang/en/validation.php index 315979bb..d0442268 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/lang/en/validation.php +++ b/modules/_bundled/sirsoft-ecommerce/src/lang/en/validation.php @@ -128,6 +128,9 @@ return [ 'shipping_policy_id' => [ 'integer' => 'Shipping policy ID must be a number.', ], + 'with_options' => [ + 'boolean' => 'Include-options flag must be true or false.', + ], // Filters 'is_active' => [ 'boolean' => 'Active status must be true or false.', @@ -1914,4 +1917,16 @@ return [ 'currency_not_registered' => 'Currency (:currency) is not registered. Please add it in Language/Currency settings first.', 'earn_rate_required_when_enabled' => 'The default earn rate must be greater than 0 to use mileage.', ], + + // Product option batch fetch validation + 'options_list' => [ + 'product_ids' => [ + 'required' => 'Please select the products to look up.', + 'array' => 'The product ID list must be an array.', + 'min' => 'Please specify at least one product.', + 'max' => 'You can look up at most :max products at a time.', + 'integer' => 'Product ID must be a number.', + 'item_min' => 'Product ID must be at least 1.', + ], + ], ]; diff --git a/modules/_bundled/sirsoft-ecommerce/src/lang/ko/messages.php b/modules/_bundled/sirsoft-ecommerce/src/lang/ko/messages.php index 8ebc580f..e789ca36 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/lang/ko/messages.php +++ b/modules/_bundled/sirsoft-ecommerce/src/lang/ko/messages.php @@ -69,6 +69,8 @@ return [ 'code_generation_failed' => '상품코드 생성에 실패했습니다.', 'logs_fetch_success' => '상품 처리 이력을 조회했습니다.', 'logs_fetch_failed' => '상품 처리 이력 조회에 실패했습니다.', + 'options_fetch_success' => '상품 옵션을 조회했습니다.', + 'options_fetch_failed' => '상품 옵션 조회에 실패했습니다.', ], 'presets' => [ 'created' => '검색 프리셋이 저장되었습니다.', diff --git a/modules/_bundled/sirsoft-ecommerce/src/lang/ko/validation.php b/modules/_bundled/sirsoft-ecommerce/src/lang/ko/validation.php index b3912d60..7a2a0e41 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/lang/ko/validation.php +++ b/modules/_bundled/sirsoft-ecommerce/src/lang/ko/validation.php @@ -128,6 +128,9 @@ return [ 'shipping_policy_id' => [ 'integer' => '배송정책 ID는 숫자여야 합니다.', ], + 'with_options' => [ + 'boolean' => '옵션 포함 여부는 true 또는 false여야 합니다.', + ], // 필터 'is_active' => [ 'boolean' => '사용여부는 true 또는 false여야 합니다.', @@ -1914,4 +1917,16 @@ return [ 'currency_not_registered' => '등록되지 않은 통화(:currency)입니다. 언어/통화 설정에 먼저 추가하세요.', 'earn_rate_required_when_enabled' => '마일리지를 사용하려면 기본 적립률은 0보다 커야 합니다.', ], + + // 상품 옵션 배치 조회 검증 + 'options_list' => [ + 'product_ids' => [ + 'required' => '조회할 상품을 선택해주세요.', + 'array' => '상품 ID 목록은 배열이어야 합니다.', + 'min' => '조회할 상품을 1개 이상 지정해주세요.', + 'max' => '한 번에 조회할 수 있는 상품은 최대 :max개입니다.', + 'integer' => '상품 ID는 숫자여야 합니다.', + 'item_min' => '상품 ID는 1 이상이어야 합니다.', + ], + ], ]; diff --git a/modules/_bundled/sirsoft-ecommerce/src/routes/api.php b/modules/_bundled/sirsoft-ecommerce/src/routes/api.php index 2f259bcc..327427c1 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/routes/api.php +++ b/modules/_bundled/sirsoft-ecommerce/src/routes/api.php @@ -867,6 +867,15 @@ Route::prefix('admin')->middleware(['auth:sanctum', 'admin'])->group(function () ->middleware('permission:admin,sirsoft-ecommerce.products.create') ->name('admin.products.generate-code'); + // 여러 상품의 옵션 배치 조회 (목록에서 행을 펼칠 때) + // GET /api/modules/sirsoft-ecommerce/admin/products/options?product_ids[]=1&product_ids[]=2 + // + // `/{identifier}` 보다 반드시 위에 둔다 — identifier 제약이 [0-9a-zA-Z]+ 라 리터럴 + // "options" 를 삼켜 상품 상세로 잘못 라우팅된다 (generate-code / by-code 와 같은 규칙). + Route::get('/options', [AdminProductController::class, 'options']) + ->middleware('permission:admin,sirsoft-ecommerce.products.read') + ->name('admin.products.options'); + // 상품코드로 조회 // GET /api/modules/sirsoft-ecommerce/admin/products/by-code/{code} Route::get('/by-code/{code}', [AdminProductController::class, 'showByCode']) diff --git a/modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Admin/AdjacentListThumbnailQueryTest.php b/modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Admin/AdjacentListThumbnailQueryTest.php new file mode 100644 index 00000000..fb346e4a --- /dev/null +++ b/modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Admin/AdjacentListThumbnailQueryTest.php @@ -0,0 +1,160 @@ +queries = []; + + DB::listen(function ($query) { + $this->queries[] = $query->sql; + }); + } + + /** + * 상품 이미지 테이블을 읽은 쿼리 수를 셉니다. + * + * @return int 이미지 조회 쿼리 수 + */ + private function imageQueryCount(): int + { + $table = (new ProductImage)->getTable(); + + return count(array_filter( + $this->queries, + fn (string $sql) => str_contains($sql, $table), + )); + } + + /** + * 이미지가 있는 상품을 만듭니다. + * + * @param int $count 생성할 상품 수 + * @return array 생성된 상품 목록 + */ + private function makeProductsWithImages(int $count): array + { + $products = []; + + for ($i = 0; $i < $count; $i++) { + $product = Product::factory()->create(); + $this->makeImage($product, false, 1); + $this->makeImage($product, true, 2); + $products[] = $product; + } + + return $products; + } + + /** + * 상품 이미지를 1건 생성합니다. + * + * @param Product $product 대상 상품 + * @param bool $isThumbnail 대표 이미지 여부 + * @param int $sortOrder 정렬 순서 + * @return ProductImage 생성된 이미지 + */ + private function makeImage(Product $product, bool $isThumbnail, int $sortOrder): ProductImage + { + return ProductImage::create([ + 'product_id' => $product->id, + // hash 컬럼은 12자 제한 (URL용 고유 해시) + 'hash' => substr(md5($product->id.'-'.$sortOrder.'-'.($isThumbnail ? 't' : 'f')), 0, 12), + 'original_filename' => 'img'.$sortOrder.'.jpg', + 'stored_filename' => 'img'.$sortOrder.'.jpg', + 'disk' => 'public', + 'path' => 'products/img'.$sortOrder.'.jpg', + 'mime_type' => 'image/jpeg', + 'file_size' => 1024, + 'is_thumbnail' => $isThumbnail, + 'sort_order' => $sortOrder, + ]); + } + + /** + * @scenario surface=wishlist,observation=query_shape + * @effects thumbnail_query_count_does_not_grow_with_rows, thumbnail_url_falls_back_to_first_image_when_undesignated + */ + public function test_wishlist_thumbnail_query_count_does_not_grow_with_rows(): void + { + $user = User::factory()->create(); + + foreach ($this->makeProductsWithImages(5) as $product) { + ProductWishlist::create(['user_id' => $user->id, 'product_id' => $product->id]); + } + + $this->startCapture(); + $result = app(ProductWishlistRepositoryInterface::class)->getByUser($user->id, 20); + + // 존재를 먼저 확정한 뒤 쿼리 수를 단언한다. + $this->assertSame(5, $result->getCollection()->count()); + + // 대표 이미지를 실제로 읽어야 분기가 작동했는지 알 수 있다. + foreach ($result->getCollection() as $row) { + $this->assertNotNull($row->product->getThumbnailUrl()); + } + + $this->assertSame( + 1, + $this->imageQueryCount(), + '이미지 조회는 페이지당 1회여야 한다 — 행마다 재조회하면 행 수에 비례해 늘어난다', + ); + } + + /** + * @scenario surface=admin_product_review_list,observation=response_payload + * @effects product_thumbnail_columns_stay_pruned_without_touching_review_images + */ + public function test_admin_review_list_thumbnail_query_count_does_not_grow_with_rows(): void + { + $user = User::factory()->create(); + + foreach ($this->makeProductsWithImages(5) as $product) { + ProductReview::factory()->create([ + 'product_id' => $product->id, + 'user_id' => $user->id, + ]); + } + + $this->startCapture(); + $result = app(ProductReviewRepositoryInterface::class)->getListWithFilters([], 20); + + $this->assertSame(5, $result->getCollection()->count()); + + foreach ($result->getCollection() as $row) { + $this->assertNotNull($row->product?->getThumbnailUrl()); + } + + // 리뷰 자체의 첨부 이미지(`images`)도 같은 테이블이 아니므로 이 카운트에 섞이지 않는다. + $this->assertSame( + 1, + $this->imageQueryCount(), + '상품 이미지 조회는 페이지당 1회여야 한다', + ); + } +} diff --git a/modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Admin/OrderListDeferredJoinTest.php b/modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Admin/OrderListDeferredJoinTest.php index 01891fd2..7d5c1396 100644 --- a/modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Admin/OrderListDeferredJoinTest.php +++ b/modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Admin/OrderListDeferredJoinTest.php @@ -7,6 +7,11 @@ use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Illuminate\Pagination\Paginator; use Illuminate\Support\Facades\DB; use Modules\Sirsoft\Ecommerce\Database\Factories\OrderFactory; +use Modules\Sirsoft\Ecommerce\Database\Factories\OrderOptionFactory; +use Modules\Sirsoft\Ecommerce\Database\Factories\OrderShippingFactory; +use Modules\Sirsoft\Ecommerce\Enums\OrderStatusEnum; +use Modules\Sirsoft\Ecommerce\Models\Order; +use Modules\Sirsoft\Ecommerce\Models\OrderShipping; use Modules\Sirsoft\Ecommerce\Repositories\Contracts\OrderRepositoryInterface; use Modules\Sirsoft\Ecommerce\Tests\ModuleTestCase; @@ -77,6 +82,22 @@ class OrderListDeferredJoinTest extends ModuleTestCase } } + /** + * 옵션 테이블을 독립적으로 읽은 쿼리만 돌려줍니다. + * + * 주문 목록 SELECT 안에는 옵션 수 집계 서브쿼리가 들어 있어 단순 문자열 매칭으로는 + * 관계 로딩 쿼리와 구분되지 않는다. + * + * @return array 옵션 관계 로딩 쿼리 목록 + */ + private function optionRelationQueries(): array + { + return array_values(array_filter( + $this->queriesContaining('from `g7_ecommerce_order_options`'), + fn (string $sql) => ! str_contains($sql, 'from `g7_ecommerce_orders`'), + )); + } + public function test_offset_scan_reads_only_id_column(): void { OrderFactory::new()->count(25)->create(); @@ -122,8 +143,8 @@ class OrderListDeferredJoinTest extends ModuleTestCase $result = $this->listOrders(); // 관계는 outer 에서만 로드된다 — inner 에도 붙으면 같은 관계 쿼리가 2회 실행된다 - $this->assertCount(1, $this->queriesContaining('from `g7_ecommerce_order_options`')); - $this->assertTrue($result->getCollection()->first()->relationLoaded('options')); + $this->assertCount(1, $this->optionRelationQueries()); + $this->assertTrue($result->getCollection()->first()->relationLoaded('firstOption')); $this->assertTrue($result->getCollection()->first()->relationLoaded('shippingAddress')); } @@ -196,4 +217,298 @@ class OrderListDeferredJoinTest extends ModuleTestCase $this->assertCount(3, $response->json('data.data')); } + + /** + * @scenario surface=admin_list,option_profile=multiple + * + * @effects admin_list_loads_only_representative_option + */ + public function test_admin_list_loads_only_the_representative_option(): void + { + $order = OrderFactory::new()->create(); + OrderOptionFactory::new()->count(20)->create(['order_id' => $order->id]); + + $result = $this->listOrders(); + $row = $result->getCollection()->first(); + + // 화면은 대표 1건만 그린다 — 관계 자체가 1건으로 좁혀져야 한다. + // (`options` 를 로드한 뒤 first() 로 1건만 쓰면 20건이 모두 메모리에 올라온다) + $this->assertFalse($row->relationLoaded('options')); + $this->assertTrue($row->relationLoaded('firstOption')); + $this->assertNotNull($row->firstOption); + } + + /** + * 배송도 대표 1건만 로드되어야 합니다. + * + * 목록 Resource 는 배송 정보 중 대표 1건(배송유형·배송방법·택배사·송장번호)만 그리는데 + * 관계는 `shippings` 전체를 로드하고 있었다. 분할 배송이 많은 주문일수록 쓰지 않는 배송 행이 + * 페이지 전체에 실린다 — 옵션과 같은 결함이 배송에 그대로 남아 있던 것을 고정한다. + * + * @scenario surface=admin_list,option_profile=multiple + * + * @effects admin_list_loads_only_representative_shipping + */ + public function test_admin_list_loads_only_the_representative_shipping(): void + { + $order = OrderFactory::new()->create(); + OrderShippingFactory::new()->count(5)->create(['order_id' => $order->id]); + + // 배송 팩토리는 order_option 을 통해 다른 주문도 만든다 — 대상 주문 행을 명시적으로 찾는다. + $row = $this->listOrders([], 50)->getCollection()->firstWhere('id', $order->id); + + $this->assertNotNull($row, '대상 주문이 목록에 있어야 한다'); + $this->assertFalse($row->relationLoaded('shippings'), '목록은 배송 전체를 로드하면 안 된다'); + $this->assertTrue($row->relationLoaded('firstShipping')); + $this->assertNotNull($row->firstShipping); + + // 택배사도 함께 로드되어야 한다 — 아니면 Resource 가 행마다 carrier 를 다시 조회한다. + $this->assertTrue($row->firstShipping->relationLoaded('carrier')); + } + + /** + * 배송 관계 쿼리도 페이지당 1회로 고정되어야 합니다. + * + * @effects admin_list_shipping_query_count_is_constant + */ + public function test_shipping_query_count_stays_constant_as_rows_and_shipments_grow(): void + { + $targetIds = []; + foreach ([1, 4, 9] as $shipmentCount) { + $order = OrderFactory::new()->create(); + OrderShippingFactory::new()->count($shipmentCount)->create(['order_id' => $order->id]); + $targetIds[] = $order->id; + } + + $this->startCapture(); + $result = $this->listOrders([], 100); + + $shippingQueries = array_values(array_filter( + $this->queries, + fn ($sql) => str_contains($sql, (new OrderShipping)->getTable()) + && ! str_contains($sql, (new Order)->getTable()), + )); + + $this->assertCount(1, $shippingQueries, '배송 관계 쿼리는 페이지당 1회여야 한다'); + $this->assertSame( + 3, + $result->getCollection() + ->whereIn('id', $targetIds) + ->filter(fn ($order) => $order->firstShipping !== null) + ->count(), + ); + } + + /** + * @effects admin_list_option_query_count_is_constant + */ + public function test_option_query_count_stays_constant_as_rows_and_options_grow(): void + { + foreach ([2, 15, 30] as $optionCount) { + $order = OrderFactory::new()->create(); + OrderOptionFactory::new()->count($optionCount)->create(['order_id' => $order->id]); + } + + $this->startCapture(); + $result = $this->listOrders(); + + // 옵션 관계 쿼리는 페이지당 1회다 — 행 수·주문당 옵션 수가 늘어도 늘지 않는다 + $this->assertCount(1, $this->optionRelationQueries()); + + // 그리고 그 1회가 읽어 오는 것은 주문당 대표 1건뿐이다 + $this->assertSame(3, $result->getCollection()->count()); + $this->assertSame( + 3, + $result->getCollection()->filter(fn ($order) => $order->firstOption !== null)->count(), + ); + } + + /** + * @scenario surface=admin_list,option_profile=partially_cancelled + * + * @effects options_count_includes_cancelled, partially_cancelled_true_only_when_some_remain, first_option_display_fields_unchanged + */ + public function test_options_count_and_partial_cancel_come_from_aggregates(): void + { + $order = OrderFactory::new()->create(); + OrderOptionFactory::new()->count(2)->create([ + 'order_id' => $order->id, + 'option_status' => OrderStatusEnum::CANCELLED, + ]); + OrderOptionFactory::new()->count(3)->create([ + 'order_id' => $order->id, + 'option_status' => OrderStatusEnum::PENDING_ORDER, + ]); + + $response = $this->actingAs($this->adminUser) + ->getJson('/api/modules/sirsoft-ecommerce/admin/orders'); + + $response->assertOk(); + + $row = collect($response->json('data.data'))->firstWhere('id', $order->id); + + $this->assertSame(5, $row['options_count'], '전체 옵션 수는 취소분을 포함한다'); + $this->assertTrue($row['is_partially_cancelled'], '일부만 취소된 주문은 부분취소다'); + $this->assertNotNull($row['first_option']['product_name'] ?? null); + } + + /** + * @scenario surface=admin_list,option_profile=fully_cancelled + * + * @effects fully_cancelled_is_not_partially_cancelled + */ + public function test_fully_cancelled_order_is_not_flagged_as_partially_cancelled(): void + { + $order = OrderFactory::new()->create(); + OrderOptionFactory::new()->count(3)->create([ + 'order_id' => $order->id, + 'option_status' => OrderStatusEnum::CANCELLED, + ]); + + $response = $this->actingAs($this->adminUser) + ->getJson('/api/modules/sirsoft-ecommerce/admin/orders'); + + $row = collect($response->json('data.data'))->firstWhere('id', $order->id); + + $this->assertFalse($row['is_partially_cancelled']); + } + + /** + * @scenario surface=admin_list,option_profile=none + * + * @effects options_count_is_zero_not_missing_when_empty + */ + public function test_order_without_options_reports_zero_not_missing(): void + { + $order = OrderFactory::new()->create(); + + $response = $this->actingAs($this->adminUser) + ->getJson('/api/modules/sirsoft-ecommerce/admin/orders'); + + $row = collect($response->json('data.data'))->firstWhere('id', $order->id); + + // COUNT 는 0건에서 0 을 돌려준다 — 필드가 사라지거나 null 이 되면 화면의 "외 N건" + // 판정이 깨진다. 집계 유무를 값으로 판정하면 이 케이스가 조용히 틀린다. + $this->assertArrayHasKey('options_count', $row); + $this->assertSame(0, $row['options_count']); + $this->assertFalse($row['is_partially_cancelled']); + } + + /** + * @effects list_payload_has_no_empty_object_fields + */ + public function test_list_payload_has_no_unfulfilled_conditional_fields(): void + { + OrderFactory::new()->count(2)->create(); + + $response = $this->actingAs($this->adminUser) + ->getJson('/api/modules/sirsoft-ecommerce/admin/orders'); + + foreach ($response->json('data.data') as $row) { + foreach ($row as $key => $value) { + // 컬렉션이 toArray() 결과를 그대로 싣는 경로는 Laravel 의 MissingValue 제거를 + // 거치지 않는다 — 미충족 필드가 `{}` 로 응답에 남는다 + $this->assertFalse( + is_array($value) && $value === [], + "미충족 조건부 필드가 빈 객체로 남았다: {$key}", + ); + } + } + } + + /** + * 주문상품이 1건뿐이어도 대표 상품과 개수가 정확히 나온다. + * + * "외 N건" 은 개수가 1일 때 표시되지 않아야 한다 — 집계를 잘못 세면 1건짜리 주문에 + * "외 0건" 이 붙는다. + * + * @scenario surface=admin_list,option_profile=single + * + * @effects first_option_display_fields_unchanged, options_count_includes_cancelled + */ + public function test_single_option_order_reports_count_one(): void + { + $order = OrderFactory::new()->create(); + OrderOptionFactory::new()->create(['order_id' => $order->id]); + + $response = $this->actingAs($this->adminUser) + ->getJson('/api/modules/sirsoft-ecommerce/admin/orders'); + + $row = collect($response->json('data.data'))->firstWhere('id', $order->id); + + $this->assertSame(1, $row['options_count']); + $this->assertNotNull($row['first_option']['product_name'] ?? null); + $this->assertFalse($row['is_partially_cancelled']); + } + + /** + * 마이페이지도 주문상품이 1건인 주문을 그대로 나열한다. + * + * @scenario surface=my_page_list,option_profile=single + * + * @effects my_page_list_still_enumerates_every_item + */ + public function test_user_order_list_returns_single_item_order(): void + { + $customer = User::factory()->create(); + $order = OrderFactory::new()->create(['user_id' => $customer->id]); + OrderOptionFactory::new()->create(['order_id' => $order->id]); + + $response = $this->actingAs($customer) + ->getJson('/api/modules/sirsoft-ecommerce/user/orders'); + + $response->assertOk(); + + $row = collect($response->json('data.data'))->firstWhere('id', $order->id); + + $this->assertCount(1, $row['items']); + $this->assertSame(1, $row['item_count']); + } + + /** + * @scenario surface=my_page_list,option_profile=multiple + * + * @effects my_page_list_enumerates_every_item_when_requested + */ + public function test_user_order_list_enumerates_every_item_when_requested(): void + { + $customer = User::factory()->create(); + $order = OrderFactory::new()->create(['user_id' => $customer->id]); + OrderOptionFactory::new()->count(4)->create(['order_id' => $order->id]); + + // 마이페이지는 주문별 아이템을 전부 나열하므로 전량을 명시적으로 요청한다. + // 이 경로가 깨지면 화면에 주문마다 상품 한 줄만 남는다. + $response = $this->actingAs($customer) + ->getJson('/api/modules/sirsoft-ecommerce/user/orders?with_items=1'); + + $response->assertOk(); + + $row = collect($response->json('data.data'))->firstWhere('id', $order->id); + + $this->assertCount(4, $row['items']); + $this->assertSame(4, $row['item_count']); + } + + /** + * @scenario surface=my_page_list,option_profile=multiple + * + * @effects my_page_list_default_is_representative_only + */ + public function test_user_order_list_defaults_to_representative_item(): void + { + $customer = User::factory()->create(); + $order = OrderFactory::new()->create(['user_id' => $customer->id]); + OrderOptionFactory::new()->count(4)->create(['order_id' => $order->id]); + + // 요청하지 않은 호출자에게 전량을 안기지 않는다 — 개수는 집계로 그대로 제공된다. + $response = $this->actingAs($customer) + ->getJson('/api/modules/sirsoft-ecommerce/user/orders'); + + $response->assertOk(); + + $row = collect($response->json('data.data'))->firstWhere('id', $order->id); + + $this->assertCount(1, $row['items']); + $this->assertSame(4, $row['item_count']); + } } diff --git a/modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Admin/ProductControllerBulkUpdateTest.php b/modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Admin/ProductControllerBulkUpdateTest.php index 23e4f0a4..c43c5e47 100644 --- a/modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Admin/ProductControllerBulkUpdateTest.php +++ b/modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Admin/ProductControllerBulkUpdateTest.php @@ -304,6 +304,50 @@ class ProductControllerBulkUpdateTest extends ModuleTestCase $this->assertEquals(500, $this->option3->price_adjustment); } + /** + * 상품 통합 일괄 업데이트 - 상품 ID 만 보내도 비활성 옵션까지 전부 적용되어야 합니다. + * + * 목록이 옵션을 더 이상 기본 적재하지 않게 되면서, 화면은 펼치지 않은 상품에 대해 옵션 배열을 + * 갖고 있지 않다. 그래도 일괄 변경은 `ids` 만으로 서버가 그 상품의 **모든** 옵션으로 전개하므로 + * 적용 범위가 줄어들지 않는다는 것을 고정한다 — 확인 모달의 요약 수치가 활성 개수가 아니라 + * 전체 개수(`options_total_count`)를 세야 하는 근거이기도 하다. + * + * @scenario row_state=collapsed,option_profile=mixed + * + * @effects bulk_applies_to_inactive_options + */ + #[Test] + public function test_bulk_update_with_ids_only_applies_to_inactive_options_too(): void + { + // Given: product1 의 옵션 하나를 비활성으로 전환 (목록에서는 활성 1개만 세어진다) + $this->option2->update(['is_active' => false]); + + $data = [ + 'ids' => [$this->product1->id], + 'option_bulk_changes' => [ + 'stock_quantity' => [ + 'method' => 'set', + 'value' => 77, + ], + ], + ]; + + // When: 옵션 배열 없이 상품 ID 만으로 일괄 변경 + $response = $this->actingAs($this->adminUser) + ->patchJson('/api/modules/sirsoft-ecommerce/admin/products/bulk-update', $data); + + // Then: 비활성 옵션을 포함한 2건 전부에 적용된다 + $response->assertStatus(200); + $response->assertJsonPath('data.options_updated', 2); + + $this->option1->refresh(); + $this->option2->refresh(); + + $this->assertSame(77, (int) $this->option1->stock_quantity); + $this->assertSame(77, (int) $this->option2->stock_quantity, '비활성 옵션도 적용 대상이다'); + $this->assertFalse((bool) $this->option2->is_active, '적용이 활성 상태를 바꾸지는 않는다'); + } + /** * 상품 통합 일괄 업데이트 - option_items로 개별 옵션 수정 */ #[Test] diff --git a/modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Admin/ProductListOptionsLazyTest.php b/modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Admin/ProductListOptionsLazyTest.php new file mode 100644 index 00000000..b351c7bd --- /dev/null +++ b/modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Admin/ProductListOptionsLazyTest.php @@ -0,0 +1,430 @@ +adminUser = $this->createAdminUser(['sirsoft-ecommerce.products.read']); + } + + /** + * 옵션을 가진 상품을 만듭니다. + * + * @param array $options 생성할 옵션 정의 + * @return Product 생성된 상품 + */ + private function makeProductWithOptions(array $options): Product + { + $product = Product::factory()->create([ + 'stock_quantity' => 100, + 'has_options' => $options !== [], + ]); + + foreach ($options as $index => $spec) { + ProductOption::create([ + 'product_id' => $product->id, + 'option_code' => 'OPT-'.$product->id.'-'.$index, + 'option_values' => ['색상' => '값'.$index], + 'option_name' => '옵션'.$index, + 'price_adjustment' => 0, + 'stock_quantity' => $spec['stock'], + 'is_active' => $spec['active'], + 'sort_order' => $spec['sort'] ?? $index, + ]); + } + + return $product; + } + + /** + * 상품에 이미지를 추가합니다. + * + * @param Product $product 대상 상품 + * @param bool $isThumbnail 대표 이미지 여부 + * @param int $sortOrder 정렬 순서 + * @return ProductImage 생성된 이미지 + */ + private function makeImage(Product $product, bool $isThumbnail, int $sortOrder): ProductImage + { + return ProductImage::create([ + 'product_id' => $product->id, + // hash 컬럼은 12자 제한 (URL용 고유 해시) + 'hash' => substr(md5($product->id.'-'.$sortOrder.'-'.($isThumbnail ? 't' : 'f')), 0, 12), + 'original_filename' => 'img'.$sortOrder.'.jpg', + 'stored_filename' => 'img'.$sortOrder.'.jpg', + 'disk' => 'public', + 'path' => 'products/img'.$sortOrder.'.jpg', + 'mime_type' => 'image/jpeg', + 'file_size' => 1024, + 'is_thumbnail' => $isThumbnail, + 'sort_order' => $sortOrder, + ]); + } + + /** + * 쿼리 로그 수집을 (재)시작합니다. + * + * 리스너는 테스트당 1회만 등록한다. 매번 등록하면 리스너가 누적되어 같은 쿼리가 등록 수만큼 + * 중복 기록되고, 쿼리 수 단언이 구조와 무관하게 실패한다. + */ + private function startCapture(): void + { + $this->queries = []; + + if ($this->listening) { + return; + } + + $this->listening = true; + + DB::listen(function ($query) { + $this->queries[] = $query->sql; + }); + } + + /** + * 수집된 쿼리 중 특정 문자열을 포함한 것의 개수를 셉니다. + * + * @param string $needle 포함 문자열 + * @return int 일치 쿼리 수 + */ + private function countQueriesContaining(string $needle): int + { + return count(array_filter($this->queries, fn (string $sql) => str_contains($sql, $needle))); + } + + /** + * 목록의 특정 상품 행을 돌려줍니다. + * + * @param array $rows 응답 data.data + * @param int $productId 찾을 상품 ID + * @return array|null 해당 행 + */ + private function rowOf(array $rows, int $productId): ?array + { + foreach ($rows as $row) { + if ((int) $row['id'] === $productId) { + return $row; + } + } + + return null; + } + + // ==================== ① 기본 목록에서 옵션 배열 제외 ==================== + + /** + * @scenario row_state=collapsed,option_profile=active_only + * @effects list_response_omits_options + */ + public function test_list_omits_option_array_by_default(): void + { + $product = $this->makeProductWithOptions([ + ['stock' => 10, 'active' => true], + ['stock' => 5, 'active' => true], + ]); + + $response = $this->actingAs($this->adminUser) + ->getJson('/api/modules/sirsoft-ecommerce/admin/products'); + + $response->assertOk(); + + $row = $this->rowOf($response->json('data.data'), $product->id); + + $this->assertNotNull($row, '생성한 상품이 목록에 있어야 한다'); + $this->assertArrayNotHasKey('options', $row, '기본 목록 응답에 옵션 배열이 실리면 안 된다'); + $this->assertSame(2, $row['options_count']); + $this->assertSame(2, $row['options_total_count']); + $this->assertSame(15, $row['option_stock_sum']); + } + + /** + * 검색·필터가 걸린 목록도 같은 옵션 계약을 따른다 — 검색 분기가 별도 조회 경로를 타므로 + * 지연 로딩이 그 분기에서만 되돌아가는 회귀가 가능하다 (mp09). + * + * @scenario row_state=collapsed,option_profile=mixed + * @effects with_options_opt_in_restores, search_branch_shares_lazy_options + */ + public function test_with_options_opt_in_includes_option_array(): void + { + $product = $this->makeProductWithOptions([ + ['stock' => 10, 'active' => true], + ['stock' => 5, 'active' => false], + ]); + + $response = $this->actingAs($this->adminUser) + ->getJson('/api/modules/sirsoft-ecommerce/admin/products?with_options=1'); + + $response->assertOk(); + + $row = $this->rowOf($response->json('data.data'), $product->id); + + $this->assertNotNull($row); + $this->assertArrayHasKey('options', $row, '?with_options=1 은 종전 동작(옵션 포함)을 유지해야 한다'); + $this->assertCount(2, $row['options'], '비활성 옵션도 포함된다 (종전 동작)'); + + // opt-in 이어도 집계 값은 동일해야 한다 + $this->assertSame(1, $row['options_count']); + $this->assertSame(2, $row['options_total_count']); + $this->assertSame(10, $row['option_stock_sum']); + } + + public function test_with_options_accepts_boolean_string_variants(): void + { + $this->makeProductWithOptions([['stock' => 1, 'active' => true]]); + + foreach (['true', '1', 'false', '0'] as $value) { + $this->actingAs($this->adminUser) + ->getJson('/api/modules/sirsoft-ecommerce/admin/products?with_options='.$value) + ->assertOk(); + } + + $this->actingAs($this->adminUser) + ->getJson('/api/modules/sirsoft-ecommerce/admin/products?with_options=banana') + ->assertStatus(422) + ->assertJsonValidationErrors('with_options'); + } + + // ==================== ② 집계 정확성 (비활성 혼재 / 0건) ==================== + + /** + * @scenario row_state=collapsed,option_profile=mixed + * @effects options_total_count_matches_all + */ + public function test_aggregates_count_active_and_total_separately(): void + { + $mixed = $this->makeProductWithOptions([ + ['stock' => 7, 'active' => true], + ['stock' => 3, 'active' => true], + ['stock' => 100, 'active' => false], + ]); + + $response = $this->actingAs($this->adminUser) + ->getJson('/api/modules/sirsoft-ecommerce/admin/products'); + + $row = $this->rowOf($response->json('data.data'), $mixed->id); + + $this->assertSame(2, $row['options_count'], '활성 옵션만 센다'); + $this->assertSame(3, $row['options_total_count'], '비활성 포함 전체를 센다'); + $this->assertSame(10, $row['option_stock_sum'], '재고 합계는 활성 옵션만 더한다'); + } + + /** + * 옵션 0건에서 재고 합계는 null 이 아니라 0 이어야 합니다. + * + * SQL 의 SUM 은 대상 행이 없으면 NULL 을 돌려준다. 이 값을 그대로 흘리면 화면의 + * 재고 불일치 비교(`stock_quantity !== option_stock_sum`)가 전 상품에서 참이 되어 + * 경고 배지가 일제히 뜬다. + * + * @scenario row_state=collapsed,option_profile=none + * @effects option_stock_sum_is_zero_not_null + */ + public function test_option_stock_sum_is_zero_not_null_when_product_has_no_options(): void + { + $bare = $this->makeProductWithOptions([]); + + $response = $this->actingAs($this->adminUser) + ->getJson('/api/modules/sirsoft-ecommerce/admin/products'); + + $row = $this->rowOf($response->json('data.data'), $bare->id); + + $this->assertSame(0, $row['options_count']); + $this->assertSame(0, $row['options_total_count']); + $this->assertNotNull($row['option_stock_sum'], '옵션 0건에서도 null 이면 안 된다'); + $this->assertSame(0, $row['option_stock_sum']); + } + + /** + * DB 집계 값이 종전의 PHP 컬렉션 연산과 동일한지 같은 데이터로 대조합니다. + * + * 이 테스트가 "집계로 바꿔도 값이 같다" 를 증명하는 자리다. 픽스처는 활성/비활성 혼재, + * 재고 0, 옵션 0건을 모두 포함한다. + * + * @effects aggregates_equal_php_computation + */ + public function test_db_aggregates_equal_php_collection_computation(): void + { + $products = [ + $this->makeProductWithOptions([]), + $this->makeProductWithOptions([['stock' => 0, 'active' => true]]), + $this->makeProductWithOptions([ + ['stock' => 12, 'active' => true], + ['stock' => 8, 'active' => false], + ['stock' => 0, 'active' => true], + ]), + $this->makeProductWithOptions([ + ['stock' => 5, 'active' => false], + ['stock' => 6, 'active' => false], + ]), + ]; + + $response = $this->actingAs($this->adminUser) + ->getJson('/api/modules/sirsoft-ecommerce/admin/products'); + + $rows = $response->json('data.data'); + + foreach ($products as $product) { + $options = ProductOption::where('product_id', $product->id)->get(); + + $expectedActiveCount = $options->where('is_active', true)->count(); + $expectedTotalCount = $options->count(); + $expectedStockSum = (int) $options->where('is_active', true)->sum('stock_quantity'); + + $row = $this->rowOf($rows, $product->id); + + $this->assertSame($expectedActiveCount, $row['options_count'], "상품 {$product->id} 활성 옵션 수"); + $this->assertSame($expectedTotalCount, $row['options_total_count'], "상품 {$product->id} 전체 옵션 수"); + $this->assertSame($expectedStockSum, $row['option_stock_sum'], "상품 {$product->id} 활성 옵션 재고 합계"); + } + } + + // ==================== ⑤ 썸네일 URL 동치 ==================== + + /** + * eager load 된 컬렉션에서 고른 대표 이미지가 관계 재쿼리 결과와 같은지 3케이스로 확인합니다. + * + * 케이스: (a) 대표 이미지 지정 (b) 대표 없음 → sort_order 첫 이미지 (c) 이미지 없음 + * + * @effects thumbnail_url_identical_after_refactor + */ + public function test_thumbnail_url_is_identical_with_and_without_eager_load(): void + { + $withThumbnail = Product::factory()->create(); + $this->makeImage($withThumbnail, false, 1); + $this->makeImage($withThumbnail, true, 2); + + $withoutThumbnail = Product::factory()->create(); + $this->makeImage($withoutThumbnail, false, 2); + $this->makeImage($withoutThumbnail, false, 1); + + $withoutImages = Product::factory()->create(); + + foreach ([$withThumbnail, $withoutThumbnail, $withoutImages] as $product) { + $lazy = Product::find($product->id); + $expected = $lazy->getThumbnailUrl(); + + $eager = Product::with('images')->find($product->id); + $actual = $eager->getThumbnailUrl(); + + $this->assertSame($expected, $actual, "상품 {$product->id} 의 대표 이미지 URL 이 달라졌다"); + } + + // 대표 이미지가 있으면 sort_order 가 뒤라도 그것이 선택된다 + $eager = Product::with('images')->find($withThumbnail->id); + $this->assertNotNull($eager->getThumbnailUrl()); + } + + /** + * eager load 된 상품의 썸네일 조회는 추가 쿼리를 만들지 않아야 합니다. + */ + public function test_thumbnail_url_does_not_requery_when_images_are_loaded(): void + { + $product = Product::factory()->create(); + $this->makeImage($product, true, 1); + + $eager = Product::with('images')->find($product->id); + + $this->startCapture(); + $eager->getThumbnailUrl(); + + $this->assertSame( + 0, + $this->countQueriesContaining('ecommerce_product_images'), + 'eager load 된 이미지를 두고 관계 빌더를 다시 태우면 안 된다' + ); + } + + // ==================== ⑥ 쿼리 수 상수성 ==================== + + /** + * 행 수가 늘어도 옵션/이미지 쿼리 수가 늘지 않아야 합니다. + * + * 옵션은 관계 로드에서 집계로 바뀌었으므로 옵션 테이블 조회는 목록 SELECT 안의 상관 + * 서브쿼리로 흡수된다. 이미지는 eager load 1회. 어느 쪽도 행 수에 비례하면 안 된다. + * + * @effects query_count_constant_across_rows + */ + public function test_option_and_image_query_count_is_constant_across_row_counts(): void + { + for ($i = 0; $i < 5; $i++) { + $p = $this->makeProductWithOptions([ + ['stock' => 3, 'active' => true], + ['stock' => 4, 'active' => false], + ]); + $this->makeImage($p, true, 1); + } + + // 권한/설정 캐시를 데우기 위한 선행 요청 (측정 대상에서 제외) + $this->actingAs($this->adminUser) + ->getJson('/api/modules/sirsoft-ecommerce/admin/products?per_page=100') + ->assertOk(); + + $this->startCapture(); + $this->actingAs($this->adminUser) + ->getJson('/api/modules/sirsoft-ecommerce/admin/products?per_page=100') + ->assertOk(); + + $optionQueriesFor5 = $this->countQueriesContaining('ecommerce_product_options'); + $imageQueriesFor5 = $this->countQueriesContaining('ecommerce_product_images'); + + for ($i = 0; $i < 15; $i++) { + $p = $this->makeProductWithOptions([ + ['stock' => 3, 'active' => true], + ['stock' => 4, 'active' => false], + ]); + $this->makeImage($p, true, 1); + } + + $this->startCapture(); + $response = $this->actingAs($this->adminUser) + ->getJson('/api/modules/sirsoft-ecommerce/admin/products?per_page=100'); + $response->assertOk(); + + $optionQueriesFor20 = $this->countQueriesContaining('ecommerce_product_options'); + $imageQueriesFor20 = $this->countQueriesContaining('ecommerce_product_images'); + + $this->assertGreaterThanOrEqual(20, count($response->json('data.data')), '20건이 모두 한 페이지에 있어야 한다'); + + $this->assertSame( + $optionQueriesFor5, + $optionQueriesFor20, + "옵션 쿼리 수가 행 수에 비례한다 (5건: {$optionQueriesFor5}, 20건: {$optionQueriesFor20})" + ); + $this->assertSame( + $imageQueriesFor5, + $imageQueriesFor20, + "이미지 쿼리 수가 행 수에 비례한다 (5건: {$imageQueriesFor5}, 20건: {$imageQueriesFor20})" + ); + } +} diff --git a/modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Admin/ProductListPayloadSizeBenchmarkTest.php b/modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Admin/ProductListPayloadSizeBenchmarkTest.php new file mode 100644 index 00000000..a553e540 --- /dev/null +++ b/modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Admin/ProductListPayloadSizeBenchmarkTest.php @@ -0,0 +1,153 @@ +adminUser = $this->createAdminUser(['sirsoft-ecommerce.products.read']); + } + + /** + * 계측용 데이터셋을 만듭니다. + * + * 옵션은 상품당 20건씩 총 2,000행이라 개별 insert 로는 계측보다 시딩이 오래 걸린다. + * 벌크 insert 로 넣되 모델 캐스팅(JSON 컬럼)은 직접 인코딩한다. + */ + private function seedDataset(): void + { + $products = Product::factory()->count(self::PRODUCT_COUNT)->create([ + 'has_options' => true, + 'stock_quantity' => 100, + ]); + + $rows = []; + $now = now(); + + foreach ($products as $product) { + for ($i = 0; $i < self::OPTIONS_PER_PRODUCT; $i++) { + $rows[] = [ + 'product_id' => $product->id, + 'option_code' => 'OPT-'.$product->id.'-'.$i, + 'option_values' => json_encode(['색상' => '값'.$i], JSON_UNESCAPED_UNICODE), + 'option_name' => '옵션'.$i, + 'price_adjustment' => 0, + 'stock_quantity' => 10, + 'is_active' => true, + 'sort_order' => $i, + 'created_at' => $now, + 'updated_at' => $now, + ]; + } + } + + foreach (array_chunk($rows, 500) as $chunk) { + DB::table((new ProductOption)->getTable())->insert($chunk); + } + } + + /** + * 목록 응답을 한 번 호출해 바이트 수와 쿼리 수를 잽니다. + * + * @param string $url 호출 URL + * @return array{bytes: int, queries: int} 계측 결과 + */ + private function measure(string $url): array + { + // 워밍 — 권한/설정 캐시가 첫 호출에만 실리므로 계측에서 제외한다 + $this->actingAs($this->adminUser)->getJson($url)->assertOk(); + + $queries = 0; + DB::listen(function () use (&$queries) { + $queries++; + }); + + $response = $this->actingAs($this->adminUser)->getJson($url); + $response->assertOk(); + + return [ + 'bytes' => strlen($response->getContent()), + 'queries' => $queries, + ]; + } + + /** + * 목록 응답 크기가 옵션 전량 포함 대비 크게 줄고, 쿼리 수는 늘지 않는다. + * + * @effects list_payload_bytes_reduced, query_count_not_increased + */ + public function test_reports_payload_size_and_query_count(): void + { + $this->seedDataset(); + + $base = '/api/modules/sirsoft-ecommerce/admin/products?per_page=100'; + + $after = $this->measure($base); + $before = $this->measure($base.'&with_options=1'); + + // 남은 쿼리가 행 수에 비례하는지도 같은 데이터셋에서 본다. 옵션/이미지 조회는 + // 집계·eager load 로 흡수됐으므로, 여전히 비례하는 몫이 있다면 그것은 다른 원인이다. + $tenRows = $this->measure('/api/modules/sirsoft-ecommerce/admin/products?per_page=10'); + + $reduction = 100 - (int) round($after['bytes'] / $before['bytes'] * 100); + + fwrite(STDERR, sprintf( + "\n[#518 성능 지표] 상품 %d건 × 옵션 %d건 (per_page=100)\n". + " 옵션 포함(종전): %s bytes / 쿼리 %d회\n". + " 옵션 제외(현행): %s bytes / 쿼리 %d회\n". + " 응답 감소율: %d%%\n". + " [참고] 현행 per_page=10: %s bytes / 쿼리 %d회 — 행 수에 비례해 남는 몫 확인용\n", + self::PRODUCT_COUNT, + self::OPTIONS_PER_PRODUCT, + number_format($before['bytes']), + $before['queries'], + number_format($after['bytes']), + $after['queries'], + $reduction, + number_format($tenRows['bytes']), + $tenRows['queries'], + )); + + // 데이터에서 오는 성질만 단언한다 — 옵션 2,000행이 빠졌으므로 응답은 크게 줄어야 한다 + $this->assertLessThan( + $before['bytes'] / 2, + $after['bytes'], + '옵션을 뺀 응답이 절반 이하로 줄지 않았다' + ); + + // 집계로 대체했으므로 쿼리 수가 늘어서는 안 된다 (집계는 목록 SELECT 안의 서브쿼리) + $this->assertLessThanOrEqual( + $before['queries'], + $after['queries'], + '옵션을 뺐는데 쿼리 수가 늘었다 — 집계가 행마다 실행되고 있다' + ); + } +} diff --git a/modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Admin/ProductOptionsBatchFetchTest.php b/modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Admin/ProductOptionsBatchFetchTest.php new file mode 100644 index 00000000..a02c0f39 --- /dev/null +++ b/modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Admin/ProductOptionsBatchFetchTest.php @@ -0,0 +1,357 @@ +adminUser = $this->createAdminUser(['sirsoft-ecommerce.products.read']); + } + + /** + * 옵션을 가진 상품을 만듭니다. + * + * @param array $options 옵션 정의 + * @param int|null $createdBy 등록자 ID + * @return Product 생성된 상품 + */ + private function makeProductWithOptions(array $options, ?int $createdBy = null): Product + { + $product = Product::factory()->create([ + 'has_options' => $options !== [], + 'created_by' => $createdBy, + ]); + + foreach ($options as $index => $spec) { + ProductOption::create([ + 'product_id' => $product->id, + 'option_code' => 'OPT-'.$product->id.'-'.$index, + 'option_values' => ['색상' => '값'.$index], + 'option_name' => '옵션'.$index, + 'price_adjustment' => 0, + 'stock_quantity' => $spec['stock'] ?? 10, + 'is_active' => $spec['active'] ?? true, + 'sort_order' => $spec['sort'] ?? $index, + ]); + } + + return $product; + } + + /** + * 쿼리 로그 수집을 (재)시작합니다. + * + * 리스너는 테스트당 1회만 등록한다. 매번 등록하면 리스너가 누적되어 같은 쿼리가 등록 수만큼 + * 중복 기록되고, 쿼리 수 단언이 구조와 무관하게 실패한다. + */ + private function startCapture(): void + { + $this->queries = []; + + if ($this->listening) { + return; + } + + $this->listening = true; + + DB::listen(function ($query) { + $this->queries[] = $query->sql; + }); + } + + /** + * 상품 ID 목록으로 배치 조회를 호출합니다. + * + * @param array $productIds 요청할 상품 ID + * @return TestResponse 응답 + */ + private function fetchOptions(array $productIds, ?User $as = null) + { + $query = http_build_query(['product_ids' => $productIds]); + + return $this->actingAs($as ?? $this->adminUser)->getJson(self::ENDPOINT.'?'.$query); + } + + // ==================== 기본 계약 ==================== + + /** + * @scenario row_state=expanded,option_profile=active_only + */ + public function test_returns_options_keyed_by_product_id(): void + { + $first = $this->makeProductWithOptions([['stock' => 5], ['stock' => 6]]); + $second = $this->makeProductWithOptions([['stock' => 7]]); + + $response = $this->fetchOptions([$first->id, $second->id]); + + $response->assertOk(); + + $this->assertSame([$first->id, $second->id], $response->json('data.product_ids')); + $this->assertCount(2, $response->json('data.options.'.$first->id)); + $this->assertCount(1, $response->json('data.options.'.$second->id)); + } + + /** + * @scenario row_state=expanded,option_profile=mixed + */ + public function test_includes_inactive_options(): void + { + $product = $this->makeProductWithOptions([ + ['active' => true], + ['active' => false], + ]); + + $response = $this->fetchOptions([$product->id]); + + $response->assertOk(); + + $options = $response->json('data.options.'.$product->id); + + $this->assertCount(2, $options, '인라인 편집기가 is_active 를 다루므로 비활성 옵션도 내려와야 한다'); + $this->assertSame( + [true, false], + array_column($options, 'is_active') + ); + } + + public function test_options_are_ordered_by_sort_order(): void + { + $product = $this->makeProductWithOptions([ + ['sort' => 30], + ['sort' => 10], + ['sort' => 20], + ]); + + $response = $this->fetchOptions([$product->id]); + + $response->assertOk(); + + $this->assertSame( + [10, 20, 30], + array_column($response->json('data.options.'.$product->id), 'sort_order') + ); + } + + /** + * @scenario row_state=expanded,option_profile=none + */ + public function test_product_without_options_returns_empty_array(): void + { + $product = $this->makeProductWithOptions([]); + + $response = $this->fetchOptions([$product->id]); + + $response->assertOk(); + $this->assertSame([$product->id], $response->json('data.product_ids')); + $this->assertSame([], $response->json('data.options.'.$product->id)); + } + + /** + * 부분 성공 — 존재하지 않는 ID 는 404 가 아니라 맵/목록 양쪽에서 빠집니다. + * + * @effects missing_ids_resolved_as_empty + */ + public function test_missing_product_id_is_omitted_not_404(): void + { + $existing = $this->makeProductWithOptions([['stock' => 3]]); + $missingId = $existing->id + 999999; + + $response = $this->fetchOptions([$existing->id, $missingId]); + + $response->assertOk(); + $this->assertSame([$existing->id], $response->json('data.product_ids')); + $this->assertNull($response->json('data.options.'.$missingId)); + } + + // ==================== 권한 ==================== + + public function test_requires_products_read_permission(): void + { + $product = $this->makeProductWithOptions([['stock' => 1]]); + $withoutPermission = $this->createAdminUser([]); + + $this->fetchOptions([$product->id], $withoutPermission)->assertStatus(403); + } + + /** + * scope=self 사용자가 타인 상품 ID 를 실어 보내도 옵션이 새지 않아야 합니다. + * + * 옵션 테이블을 바로 조회하면 상품 소유권 검사가 통째로 우회된다. 허용 ID 를 상품 쿼리에서 + * 먼저 확정하는 이유다. + * + * @effects scope_filtered_ids_absent + */ + public function test_scope_self_user_cannot_read_other_users_product_options(): void + { + $owner = User::factory()->create(); + $scopedUser = $this->createScopeSelfAdmin(); + + $mine = $this->makeProductWithOptions([['stock' => 1]], createdBy: $scopedUser->id); + $theirs = $this->makeProductWithOptions([['stock' => 2]], createdBy: $owner->id); + + $response = $this->fetchOptions([$mine->id, $theirs->id], $scopedUser); + + $response->assertOk(); + $this->assertSame([$mine->id], $response->json('data.product_ids'), '타인 상품 ID 는 목록에서 빠져야 한다'); + $this->assertNull($response->json('data.options.'.$theirs->id), '타인 상품 옵션은 맵에도 없어야 한다'); + } + + // ==================== 검증 ==================== + + public function test_product_ids_is_required(): void + { + $this->actingAs($this->adminUser) + ->getJson(self::ENDPOINT) + ->assertStatus(422) + ->assertJsonValidationErrors('product_ids'); + } + + public function test_empty_product_ids_is_rejected(): void + { + $this->fetchOptions([]) + ->assertStatus(422) + ->assertJsonValidationErrors('product_ids'); + } + + public function test_more_than_one_hundred_product_ids_is_rejected(): void + { + $this->fetchOptions(range(1, 101)) + ->assertStatus(422) + ->assertJsonValidationErrors('product_ids'); + + // 상한(100)은 목록 per_page 상한과 같다 — 한 페이지를 전부 펼친 경우가 최대치 + $this->fetchOptions(range(1, 100))->assertOk(); + } + + public function test_non_integer_product_id_is_rejected(): void + { + $this->fetchOptions(['banana']) + ->assertStatus(422) + ->assertJsonValidationErrors('product_ids.0'); + } + + // ==================== 쿼리 수 ==================== + + /** + * 요청 상품 수가 늘어도 쿼리 수는 상수여야 합니다 (허용 ID 확정 1 + 옵션 조회 1). + * + * @effects query_count_constant_across_rows + */ + public function test_query_count_is_constant_regardless_of_product_count(): void + { + $ids = []; + for ($i = 0; $i < 10; $i++) { + $ids[] = $this->makeProductWithOptions([['stock' => 1], ['stock' => 2]])->id; + } + + // 권한 캐시 워밍 (측정 대상 제외) + $this->fetchOptions([$ids[0]])->assertOk(); + + $this->startCapture(); + $this->fetchOptions([$ids[0]])->assertOk(); + $singleCount = $this->countDomainQueries(); + + $this->startCapture(); + $this->fetchOptions($ids)->assertOk(); + $batchCount = $this->countDomainQueries(); + $batchSql = $this->domainQueries(); + + $this->assertSame( + $singleCount, + $batchCount, + "상품 수에 비례해 쿼리가 늘었다 (1건: {$singleCount}, 10건: {$batchCount})\n".implode("\n", $batchSql) + ); + } + + /** + * 상품/옵션 테이블을 건드린 쿼리 수를 셉니다. + * + * @return int 도메인 쿼리 수 + */ + private function countDomainQueries(): int + { + return count($this->domainQueries()); + } + + /** + * 상품/옵션 테이블을 건드린 쿼리 목록을 돌려줍니다. + * + * @return array 도메인 쿼리 SQL + */ + private function domainQueries(): array + { + return array_values(array_filter( + $this->queries, + fn (string $sql) => str_contains($sql, 'ecommerce_products') || str_contains($sql, 'ecommerce_product_options') + )); + } + + /** + * `sirsoft-ecommerce.products.read` 를 scope=self 로 가진 관리자를 만듭니다. + * + * @return User 생성된 사용자 + */ + private function createScopeSelfAdmin(): User + { + $user = $this->createAdminUser(['sirsoft-ecommerce.products.read']); + + $permission = Permission::where('identifier', 'sirsoft-ecommerce.products.read')->firstOrFail(); + $permission->update(['owner_key' => 'created_by', 'resource_route_key' => 'product']); + + /** @var Role $role */ + $role = $user->roles()->firstOrFail(); + $role->permissions()->syncWithoutDetaching([ + $permission->id => ['scope_type' => ScopeType::Self], + ]); + + $this->clearPermissionCache(); + + return $user; + } + + /** + * PermissionHelper 의 static 권한 캐시를 비웁니다. + * + * 권한/스코프를 테스트 중에 바꾸면 앞서 캐시된 판정이 그대로 재사용되어 변경이 반영되지 않습니다. + */ + private function clearPermissionCache(): void + { + $reflection = new \ReflectionClass(PermissionHelper::class); + $prop = $reflection->getProperty('permissionCache'); + $prop->setAccessible(true); + $prop->setValue(null, []); + } +} diff --git a/modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Admin/ShippingPolicyActiveListTest.php b/modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Admin/ShippingPolicyActiveListTest.php index c364fab3..b0569916 100644 --- a/modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Admin/ShippingPolicyActiveListTest.php +++ b/modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Admin/ShippingPolicyActiveListTest.php @@ -69,12 +69,19 @@ class ShippingPolicyActiveListTest extends ModuleTestCase } #[Test] + /** + * @scenario endpoint=active_list,opt_in=default + * @effects list_keeps_fields_the_screen_draws + */ public function test_active_filtered_response_includes_form_fields(): void { $this->makePolicy('활성정책', true); + // 기본 응답도 목록 표시용 국가별 설정·요약을 싣는다(관리 목록 화면이 국가 칩과 + // 배송비 요약 열을 그린다). `with_country_settings=true` 는 표시용을 넘어 **전체 컬럼** + // 이 필요한 호출자를 위한 하위호환 경로이며, 여기서는 그 경로의 응답 형태를 고정한다. $response = $this->actingAs($this->adminUser) - ->getJson($this->apiBase.'?is_active=true'); + ->getJson($this->apiBase.'?is_active=true&with_country_settings=true'); $response->assertOk() ->assertJsonStructure([ diff --git a/modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Admin/ShippingPolicyControllerTest.php b/modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Admin/ShippingPolicyControllerTest.php index 91a63cb2..7a76fa6d 100644 --- a/modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Admin/ShippingPolicyControllerTest.php +++ b/modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Admin/ShippingPolicyControllerTest.php @@ -166,7 +166,7 @@ class ShippingPolicyControllerTest extends ModuleTestCase ); // When: 목록 조회 - $response = $this->actingAs($this->adminUser)->getJson($this->apiBase); + $response = $this->actingAs($this->adminUser)->getJson($this->apiBase.'?with_country_settings=true'); // Then: 성공 응답 및 구조 확인 $response->assertOk() @@ -211,7 +211,7 @@ class ShippingPolicyControllerTest extends ModuleTestCase ); // When - $response = $this->actingAs($this->adminUser)->getJson($this->apiBase); + $response = $this->actingAs($this->adminUser)->getJson($this->apiBase.'?with_country_settings=true'); // Then: country_settings 배열 구조 확인 $response->assertOk(); @@ -284,7 +284,7 @@ class ShippingPolicyControllerTest extends ModuleTestCase // When: parcel만 필터 $response = $this->actingAs($this->adminUser)->getJson( - $this->apiBase.'?shipping_methods[]=parcel' + $this->apiBase.'?shipping_methods[]=parcel&with_country_settings=true' ); // Then: 택배 정책만 반환 @@ -313,7 +313,7 @@ class ShippingPolicyControllerTest extends ModuleTestCase // When: fixed만 필터 $response = $this->actingAs($this->adminUser)->getJson( - $this->apiBase.'?charge_policies[]=fixed' + $this->apiBase.'?charge_policies[]=fixed&with_country_settings=true' ); // Then: 고정 배송비 정책만 반환 @@ -342,7 +342,7 @@ class ShippingPolicyControllerTest extends ModuleTestCase // When: KR만 필터 $response = $this->actingAs($this->adminUser)->getJson( - $this->apiBase.'?countries[]=KR' + $this->apiBase.'?countries[]=KR&with_country_settings=true' ); // Then: 국내 정책만 반환 @@ -1376,6 +1376,9 @@ class ShippingPolicyControllerTest extends ModuleTestCase /** * 배송정책 상세 조회 - country_settings 포함 + * + * @scenario endpoint=detail,opt_in=default + * @effects detail_still_provides_full_country_settings */ #[Test] public function test_show_includes_country_settings_with_labels(): void diff --git a/modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Admin/ShippingPolicyListPayloadTest.php b/modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Admin/ShippingPolicyListPayloadTest.php new file mode 100644 index 00000000..b75d74e4 --- /dev/null +++ b/modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Admin/ShippingPolicyListPayloadTest.php @@ -0,0 +1,325 @@ +seed(ShippingTypeSeeder::class); + $this->adminUser = $this->createAdminUser([ + 'sirsoft-ecommerce.shipping-policies.read', + ]); + } + + /** + * 국가별 설정을 가진 배송정책을 만듭니다. + * + * @param string $name 정책 이름 + * @param array $countryCodes 활성 국가 코드 + * @param array $inactiveCodes 비활성 국가 코드 + * @return ShippingPolicy 생성된 정책 + */ + private function makePolicy(string $name, array $countryCodes, array $inactiveCodes = []): ShippingPolicy + { + $policy = ShippingPolicy::create([ + 'name' => ['ko' => $name, 'en' => $name], + 'is_active' => true, + 'is_default' => false, + 'sort_order' => 1, + ]); + + foreach ([[$countryCodes, true], [$inactiveCodes, false]] as [$codes, $isActive]) { + foreach ($codes as $code) { + $policy->countrySettings()->create([ + 'country_code' => $code, + 'shipping_method' => 'parcel', + 'currency_code' => 'KRW', + 'charge_policy' => 'fixed', + 'base_fee' => 3000, + 'is_active' => $isActive, + // 목록에서 빠져야 할 무거운 중첩 JSON + 'extra_fee_enabled' => true, + 'extra_fee_settings' => [ + ['region' => '제주', 'fee' => 3000], + ['region' => '울릉도', 'fee' => 5000], + ], + 'ranges' => ['tiers' => [['min' => 0, 'max' => 10, 'fee' => 2500, 'unit' => 'kg']]], + ]); + } + } + + return $policy; + } + + #[Test] + /** + * @scenario endpoint=admin_list,opt_in=default + * @effects list_omits_nested_country_setting_blocks, list_keeps_fields_the_screen_draws + */ + public function test_list_carries_only_the_fields_the_screen_draws(): void + { + $this->makePolicy('정책A', ['KR', 'US']); + + $row = $this->actingAs($this->adminUser) + ->getJson($this->apiBase) + ->assertOk() + ->json('data.data.0'); + + $this->assertArrayHasKey( + 'country_settings', + $row, + '목록 데이터그리드가 국가 칩과 국가별 설정 목록을 그린다 — 빼면 열이 빈다' + ); + + $setting = $row['country_settings'][0]; + + foreach (self::LIST_KEYS as $key) { + $this->assertArrayHasKey($key, $setting, "목록이 그리는 필드 {$key} 가 빠졌다"); + } + + foreach (self::DETAIL_ONLY_KEYS as $key) { + $this->assertArrayNotHasKey( + $key, + $setting, + "{$key} 는 목록이 그리지 않는다 — 정책당 국가 수만큼 곱해져 응답에 실리면 안 된다" + ); + } + } + + #[Test] + /** + * @scenario endpoint=admin_list,opt_in=default + * @effects list_keeps_fields_the_screen_draws + */ + public function test_list_still_computes_summaries_without_opt_in(): void + { + // 요약 2필드는 목록이 열로 그린다. 관계를 조건부로 두면 열이 비고, 관계 없이 계산하면 + // 행마다 재조회(N+1)가 된다 — 경량 관계를 항상 싣는 이유다. + $this->makePolicy('정책A', ['KR', 'US']); + + $row = $this->actingAs($this->adminUser) + ->getJson($this->apiBase) + ->assertOk() + ->json('data.data.0'); + + $this->assertNotSame('', $row['fee_summary'], '배송비 요약 열이 비면 안 된다'); + $this->assertNotSame('', $row['countries_display'], '국가 표시 열이 비면 안 된다'); + } + + #[Test] + /** + * @scenario endpoint=admin_list,opt_in=default + * @effects list_includes_inactive_country_settings + */ + public function test_list_includes_inactive_country_settings(): void + { + // 목록은 비활성 국가 설정에 "비활성" 배지를 그린다(`{{!cs.is_active}}`). + // 활성만 실으면 그 배지가 영영 뜨지 않는다. + $this->makePolicy('정책A', ['KR'], ['US']); + + $row = $this->actingAs($this->adminUser) + ->getJson($this->apiBase) + ->assertOk() + ->json('data.data.0'); + + $flags = collect($row['country_settings'])->pluck('is_active')->sort()->values()->all(); + + $this->assertSame([false, true], $flags, '비활성 국가 설정도 목록에 실려야 배지를 그릴 수 있다'); + } + + #[Test] + /** + * @scenario endpoint=admin_list,opt_in=default + * @effects summaries_count_only_active_settings_regardless_of_load_path + */ + public function test_summaries_count_only_active_settings_regardless_of_load_path(): void + { + // 목록은 비활성까지 싣지만 요약은 활성만 센다. 로더의 필터에 요약의 정확성을 맡기면 + // 같은 정책의 요약이 조회 경로(목록/상세/opt-in)에 따라 달라진다. + $policy = $this->makePolicy('정책A', ['KR'], ['US', 'CN']); + + $row = $this->actingAs($this->adminUser) + ->getJson($this->apiBase) + ->assertOk() + ->json('data.data.0'); + + $this->assertSame( + $policy->fresh()->getCountriesWithFlags(), + $row['countries_display'], + '로드된 컬렉션으로 계산한 값과 조회로 계산한 값이 같아야 한다' + ); + $this->assertStringNotContainsString('+2', $row['countries_display'], '비활성 2건이 요약에 세어지면 안 된다'); + } + + #[Test] + /** + * @scenario endpoint=admin_list,opt_in=with_country_settings + * @effects opt_in_restores_full_country_settings + */ + public function test_opt_in_restores_full_country_settings(): void + { + // 외부 연동 하위호환 — 전체 컬럼이 필요한 호출자는 종전 응답을 그대로 받는다. + $this->makePolicy('정책A', ['KR', 'US']); + + $row = $this->actingAs($this->adminUser) + ->getJson($this->apiBase.'?with_country_settings=true') + ->assertOk() + ->json('data.data.0'); + + $this->assertCount(2, $row['country_settings']); + $this->assertArrayHasKey('extra_fee_settings', $row['country_settings'][0]); + $this->assertArrayHasKey('ranges', $row['country_settings'][0]); + } + + #[Test] + /** + * @scenario endpoint=admin_list,opt_in=with_country_settings + * @effects opt_in_accepts_query_string_boolean_forms + */ + public function test_opt_in_accepts_query_string_boolean_forms(): void + { + $this->makePolicy('정책A', ['KR']); + + foreach (['true', '1'] as $truthy) { + $setting = $this->actingAs($this->adminUser) + ->getJson($this->apiBase.'?with_country_settings='.$truthy) + ->assertOk() + ->json('data.data.0.country_settings.0'); + + $this->assertArrayHasKey('api_config', $setting, "with_country_settings={$truthy} 가 참으로 해석되어야 한다"); + } + + foreach (['false', '0'] as $falsy) { + $setting = $this->actingAs($this->adminUser) + ->getJson($this->apiBase.'?with_country_settings='.$falsy) + ->assertOk() + ->json('data.data.0.country_settings.0'); + + $this->assertArrayNotHasKey('api_config', $setting, "with_country_settings={$falsy} 는 거짓이어야 한다"); + } + + // 해석 불가한 값은 통과시키지 않는다 (오타가 "지정 안 함" 으로 조용히 넘어가면 안 된다) + $this->actingAs($this->adminUser) + ->getJson($this->apiBase.'?with_country_settings=banana') + ->assertStatus(422); + } + + #[Test] + /** + * @scenario endpoint=admin_list,opt_in=default + * @effects country_setting_queries_do_not_grow_with_rows + */ + public function test_default_list_country_setting_queries_do_not_grow_with_rows(): void + { + $this->assertCountrySettingQueriesAreConstant($this->apiBase); + } + + #[Test] + /** + * @scenario endpoint=active_list,opt_in=default + * @effects country_setting_queries_do_not_grow_with_rows + */ + public function test_active_list_country_setting_queries_do_not_grow_with_rows(): void + { + // `activeList` 는 정책마다 getCountriesWithFlags()/getFeeSummary() 를 호출한다. + // 관계를 싣지 않으면 두 메서드가 행마다 재조회해 정책 수 × 2 쿼리가 된다 — + // 페이로드를 줄이려다 쿼리를 늘리는 맞바꿈이 실제로 한 번 발생했다. + $this->assertCountrySettingQueriesAreConstant($this->apiBase.'/active'); + } + + /** + * 국가별 설정 조회 쿼리 수가 정책 수에 비례하지 않음을 단언합니다. + * + * @param string $url 측정할 엔드포인트 + * @return void + */ + private function assertCountrySettingQueriesAreConstant(string $url): void + { + $this->makePolicy('A', ['KR', 'US']); + + // 권한/설정 캐시를 채워 측정에서 제외 + $this->actingAs($this->adminUser)->getJson($url); + + $measure = function () use ($url): int { + $count = 0; + DB::listen(function ($query) use (&$count) { + if (str_contains($query->sql, 'shipping_policy_country_settings')) { + $count++; + } + }); + + $this->actingAs($this->adminUser)->getJson($url)->assertOk(); + + return $count; + }; + + $withOne = $measure(); + + foreach (['B', 'C', 'D', 'E'] as $name) { + $this->makePolicy($name, ['KR', 'US']); + } + + $withFive = $measure(); + + $this->assertSame( + $withOne, + $withFive, + "{$url} — 정책 1건일 때 {$withOne}회, 5건일 때 {$withFive}회. 행 수에 비례하면 요약 메서드가 관계를 재조회하고 있다" + ); + } +} diff --git a/modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Public/CartControllerTest.php b/modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Public/CartControllerTest.php index 33bbf29c..5aab7688 100644 --- a/modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Public/CartControllerTest.php +++ b/modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Public/CartControllerTest.php @@ -3,6 +3,7 @@ namespace Modules\Sirsoft\Ecommerce\Tests\Feature\Http\Controllers\Public; use App\Models\User; +use Illuminate\Support\Facades\DB; use Modules\Sirsoft\Ecommerce\Database\Factories\CartFactory; use Modules\Sirsoft\Ecommerce\Database\Factories\ProductFactory; use Modules\Sirsoft\Ecommerce\Database\Factories\ProductOptionFactory; @@ -11,6 +12,7 @@ use Modules\Sirsoft\Ecommerce\Models\Cart; use Modules\Sirsoft\Ecommerce\Models\Product; use Modules\Sirsoft\Ecommerce\Models\ProductOption; use Modules\Sirsoft\Ecommerce\Models\ShippingPolicy; +use Modules\Sirsoft\Ecommerce\Services\EcommerceSettingsService; use Modules\Sirsoft\Ecommerce\Tests\ModuleTestCase; /** @@ -107,7 +109,7 @@ class CartControllerTest extends ModuleTestCase */ protected function enableIntlShipping(): void { - $settings = app(\Modules\Sirsoft\Ecommerce\Services\EcommerceSettingsService::class); + $settings = app(EcommerceSettingsService::class); $settings->setSetting('shipping.international_shipping_enabled', true); $settings->setSetting('shipping.default_country', 'KR'); $settings->setSetting('shipping.available_countries', [ @@ -770,4 +772,45 @@ class CartControllerTest extends ModuleTestCase && str_contains($m, '3') && ! str_contains($m, ':deleted_count')); } + + /** + * 장바구니 조회는 상품 이미지의 필요한 컬럼만 읽는다. + * + * 장바구니는 상품당 대표 이미지 1장의 URL 만 쓰는데, 종전에는 그 상품의 이미지 행을 + * 전 컬럼으로 불러왔다. 대표 지정이 없을 때 첫 이미지로 폴백하는 동작은 그대로 유지되므로 + * 관계는 두고 컬럼만 좁힌다. + * + * @scenario surface=cart,observation=query_shape + * @effects list_response_shape_is_unchanged, image_columns_stay_sufficient_for_thumbnail_assembly + */ + public function test_cart_loads_only_needed_product_image_columns(): void + { + $data = $this->createProductWithCountryFees(krFee: 3000, usFee: 2000); + $cartKey = 'ck_'.str_repeat('i', 32); + + $this->postJson('/api/modules/sirsoft-ecommerce/cart', [ + 'product_id' => $data['product']->id, + 'items' => [ + ['product_option_id' => $data['option']->id, 'quantity' => 1], + ], + ], ['X-Cart-Key' => $cartKey])->assertStatus(201); + + $selects = []; + DB::listen(function ($query) use (&$selects) { + if (str_contains($query->sql, 'g7_ecommerce_product_images')) { + $selects[] = $query->sql; + } + }); + + $response = $this->getJson('/api/modules/sirsoft-ecommerce/cart', ['X-Cart-Key' => $cartKey]); + $response->assertStatus(200); + + $this->assertNotEmpty($selects, '상품 이미지 조회 쿼리가 있어야 한다'); + + foreach ($selects as $sql) { + $this->assertStringNotContainsString('select *', $sql, '장바구니는 이미지 전 컬럼을 읽으면 안 된다'); + $this->assertStringContainsString('`hash`', $sql, 'download_url 생성에 필요한 hash 는 유지되어야 한다'); + $this->assertStringContainsString('`is_thumbnail`', $sql, '대표 이미지 판정 컬럼은 유지되어야 한다'); + } + } } diff --git a/modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Public/PublicProductReviewControllerTest.php b/modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Public/PublicProductReviewControllerTest.php index cc7fbbaa..3a630905 100644 --- a/modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Public/PublicProductReviewControllerTest.php +++ b/modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Public/PublicProductReviewControllerTest.php @@ -2,11 +2,15 @@ namespace Modules\Sirsoft\Ecommerce\Tests\Feature\Http\Controllers\Public; +use App\Enums\PermissionType; +use App\Models\Permission; +use App\Models\Role; use App\Models\User; use Modules\Sirsoft\Ecommerce\Enums\ReviewStatus; use Modules\Sirsoft\Ecommerce\Models\OrderOption; use Modules\Sirsoft\Ecommerce\Models\Product; use Modules\Sirsoft\Ecommerce\Models\ProductReview; +use Modules\Sirsoft\Ecommerce\Models\ProductReviewImage; use Modules\Sirsoft\Ecommerce\Tests\ModuleTestCase; use PHPUnit\Framework\Attributes\Test; @@ -30,14 +34,14 @@ class PublicProductReviewControllerTest extends ModuleTestCase $this->user = $this->createUser(); // 공개 상품/리뷰 API에 필요한 user 권한 부여 - $permission = \App\Models\Permission::firstOrCreate( + $permission = Permission::firstOrCreate( ['identifier' => 'sirsoft-ecommerce.user-products.read'], [ 'name' => ['ko' => '상품 조회', 'en' => 'Read Products'], - 'type' => \App\Enums\PermissionType::User, + 'type' => PermissionType::User, ] ); - $userRole = \App\Models\Role::where('identifier', 'user')->first(); + $userRole = Role::where('identifier', 'user')->first(); $userRole->permissions()->syncWithoutDetaching([$permission->id]); $this->product = Product::factory()->onSale()->create(); @@ -239,4 +243,66 @@ class PublicProductReviewControllerTest extends ModuleTestCase ->assertJsonPath('data.reviews.data', []) ->assertJsonPath('data.total_count', 0); } + + /** + * 공개 리뷰 목록은 첨부 이미지 배열과 개수를 함께 내려준다. + * + * 상품 상세의 리뷰 탭(`partials/shop/detail/_tab_reviews.json`)이 썸네일 3장을 인덱스로 + * 직접 읽고(`review.images[0..2].download_url`) 클릭 시 이미지 모달에 배열 전체를 넘긴다. + * 이 파셜은 `shop/show.json` 이 include 하므로 그 파일만 보면 참조가 드러나지 않는다. + * + * 배열을 빼면 조용히 깨진다 — 썸네일이 `(review.images ?? []).length > 0` 가드 뒤에 있어 + * 예외 없이 자리만 비고, 바깥 컨테이너는 `image_count > 0` 로 열려 "빈 상자" 가 남는다. + * 개수는 배열 길이가 아니라 DB 집계에서 온다. + * + * @scenario surface=public_product_review_list,observation=response_payload + * @effects public_list_carries_images_and_count_together, image_download_url_is_present_on_each_image, image_count_is_zero_not_missing_when_empty, no_public_single_review_endpoint_exists_as_fallback + */ + #[Test] + public function test_public_review_list_carries_images_and_count(): void + { + $review = ProductReview::factory()->create([ + 'product_id' => $this->product->id, + 'order_option_id' => $this->orderOption->id, + 'user_id' => $this->user->id, + 'status' => ReviewStatus::VISIBLE->value, + ]); + + ProductReviewImage::create([ + 'review_id' => $review->id, + 'hash' => substr(md5('rv-'.$review->id), 0, 12), + 'original_filename' => 'r.jpg', + 'stored_filename' => 'r.jpg', + 'disk' => 'public', + 'path' => 'reviews/r.jpg', + 'mime_type' => 'image/jpeg', + 'file_size' => 512, + 'sort_order' => 1, + ]); + + $response = $this->actingAs($this->user)->getJson( + "/api/modules/sirsoft-ecommerce/products/{$this->product->id}/reviews" + ); + + $response->assertStatus(200); + + $rows = $response->json('data.reviews.data'); + $this->assertNotEmpty($rows); + + $row = collect($rows)->firstWhere('id', $review->id); + $this->assertNotNull($row); + + $this->assertArrayHasKey( + 'images', + $row, + '리뷰 탭이 review.images[0..2] 를 직접 읽는다 — 빼면 썸네일 자리가 조용히 빈다' + ); + $this->assertCount(1, $row['images'], '첨부한 이미지가 그대로 실려야 한다'); + $this->assertArrayHasKey( + 'download_url', + $row['images'][0], + '화면이 download_url 로 썸네일을 그린다' + ); + $this->assertSame(1, $row['image_count'], '첨부 개수는 DB 집계에서 온다'); + } } diff --git a/modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/User/UserOrderControllerTest.php b/modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/User/UserOrderControllerTest.php index 68d281fd..6727679a 100644 --- a/modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/User/UserOrderControllerTest.php +++ b/modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/User/UserOrderControllerTest.php @@ -349,6 +349,134 @@ class UserOrderControllerTest extends ModuleTestCase ->assertJsonPath('data.pagination.total', 3); } + /** + * 기본 목록은 대표 아이템 1건 + 전체 개수만 싣는다. + * + * 주문마다 아이템을 전부 싣는 것이 기본이면, 그 값을 그리지 않는 호출자(모바일 목록, + * 외부 연동)까지 한 페이지를 여는 것만으로 주문 수 × 아이템 수를 받는다. 전량이 필요한 + * 화면은 `?with_items=1` 로 켠다. + * + * @scenario surface=my_page_list,option_profile=multiple + * @effects my_page_list_default_is_representative_only + */ + public function test_기본_목록은_대표_아이템_1건과_개수만_싣는다(): void + { + $user = $this->createUser(); + $this->actingAs($user); + + $order = Order::factory()->forUser($user)->pendingPayment()->create(); + OrderOption::factory()->count(3)->create(['order_id' => $order->id]); + + $response = $this->getJson('/api/modules/sirsoft-ecommerce/user/orders'); + + $response->assertStatus(200); + + $row = collect($response->json('data.data'))->firstWhere('id', $order->id); + + $this->assertNotNull($row, '생성한 주문이 목록에 있어야 한다'); + $this->assertIsArray($row['items'] ?? null, '대표 1건도 배열 형태로 실어야 화면 순회가 깨지지 않는다'); + $this->assertCount(1, $row['items'], '기본은 대표 1건이다'); + $this->assertSame(3, $row['item_count'], '"외 N건" 을 그리려면 전체 개수가 필요하다'); + } + + /** + * `?with_items=1` 은 전량이 필요한 화면(마이페이지 주문내역)을 위한 경로다. + * + * 화면(`partials/mypage/orders/_list.json`)이 `order.items` 를 순회하므로, 이 경로가 + * 깨지면 주문마다 상품 한 줄만 남는다. + * + * @scenario surface=my_page_list,option_profile=multiple + * @effects my_page_list_enumerates_every_item_when_requested + */ + public function test_with_items_1_이면_아이템을_전부_싣는다(): void + { + $user = $this->createUser(); + $this->actingAs($user); + + $order = Order::factory()->forUser($user)->pendingPayment()->create(); + OrderOption::factory()->count(3)->create(['order_id' => $order->id]); + + $response = $this->getJson('/api/modules/sirsoft-ecommerce/user/orders?with_items=1'); + + $response->assertStatus(200); + + $row = collect($response->json('data.data'))->firstWhere('id', $order->id); + + $this->assertNotNull($row); + $this->assertCount(3, $row['items'], 'with_items=1 이면 전량이다'); + $this->assertSame(3, $row['item_count']); + } + + /** + * 아이템 조회 쿼리 수가 주문 수에 비례하지 않는다. + * + * 대표 1건 축약이 관계 재조회로 구현되면 목록 한 페이지가 주문 수만큼 쿼리를 낸다 — + * 페이로드를 줄이려다 쿼리를 늘리는 맞바꿈이 된다. + * + * @scenario surface=my_page_list,option_profile=multiple + * @effects my_page_list_option_query_count_is_constant + */ + public function test_아이템_조회_쿼리수가_주문수에_비례하지_않는다(): void + { + $user = $this->createUser(); + $this->actingAs($user); + + $makeOrder = function () use ($user) { + $order = Order::factory()->forUser($user)->pendingPayment()->create(); + OrderOption::factory()->count(3)->create(['order_id' => $order->id]); + }; + + $makeOrder(); + + // 권한/설정 캐시를 채워 측정에서 제외 + $this->getJson('/api/modules/sirsoft-ecommerce/user/orders'); + + $measure = function (): int { + $count = 0; + \Illuminate\Support\Facades\DB::listen(function ($query) use (&$count) { + if (str_contains($query->sql, 'ecommerce_order_options')) { + $count++; + } + }); + + $this->getJson('/api/modules/sirsoft-ecommerce/user/orders')->assertStatus(200); + + return $count; + }; + + $withOne = $measure(); + + for ($i = 0; $i < 4; $i++) { + $makeOrder(); + } + + $withFive = $measure(); + + $this->assertSame( + $withOne, + $withFive, + "주문 1건일 때 {$withOne}회, 5건일 때 {$withFive}회 — 행 수에 비례하면 N+1 이다" + ); + } + + /** + * 해석할 수 없는 with_items 값은 조용히 무시하지 않고 422 로 돌려준다. + * + * null 로 정규화하면 오타 파라미터가 "미지정" 으로 통과해 호출자가 잘못을 알 수 없다. + * + * @scenario surface=my_page_list,option_profile=multiple + * @effects my_page_list_rejects_unparseable_with_items + */ + public function test_with_items_에_해석불가한_값이_오면_422(): void + { + $user = $this->createUser(); + $this->actingAs($user); + + $this->getJson('/api/modules/sirsoft-ecommerce/user/orders?with_items=maybe') + ->assertStatus(422) + ->assertJsonValidationErrors('with_items'); + } + /** * 현재 로케일 키가 없는 다국어(array) product_option_name 옵션이 포함된 주문도 * 500 없이 직렬화된다 (UserOrderListResource 의 reset(매직속성) 회귀). diff --git a/modules/_bundled/sirsoft-ecommerce/tests/Playwright/fixtures/ecommerce-auth.ts b/modules/_bundled/sirsoft-ecommerce/tests/Playwright/fixtures/ecommerce-auth.ts index e77f97cc..c3e023f5 100644 --- a/modules/_bundled/sirsoft-ecommerce/tests/Playwright/fixtures/ecommerce-auth.ts +++ b/modules/_bundled/sirsoft-ecommerce/tests/Playwright/fixtures/ecommerce-auth.ts @@ -29,6 +29,8 @@ type EcommerceAuthFixtures = { productManageToken: string; /** 주문 조회(`orders.read`) 권한 보유 토큰 (주문 상세 표시 검증용) */ ordersReadToken: string; + /** 배송정책 조회+수정 권한 보유 토큰 (배송정책 목록 표시 검증용) */ + shippingPolicyToken: string; /** 일반 쇼핑 사용자 토큰 (관리자 권한 없음 — 유저 화면 검증용) */ userToken: string; /** 구매 고객 토큰 (장바구니/추가옵션 등 쇼핑 플로우 검증용) */ @@ -58,6 +60,16 @@ export const test = base.extend({ ordersReadToken: async ({}, use) => { await use(issueToken('sirsoft-ecommerce.orders.read')); }, + shippingPolicyToken: async ({}, use) => { + await use( + issueToken( + 'sirsoft-ecommerce.shipping-policies.read', + 'sirsoft-ecommerce.shipping-policies.create', + 'sirsoft-ecommerce.shipping-policies.update', + 'sirsoft-ecommerce.shipping-policies.delete', + ), + ); + }, // userToken / customerToken 은 현재 둘 다 "관리자 권한 없는 인증 사용자" 로 동일하다. // 유저 화면(마이페이지/헤더)과 구매 플로우(장바구니/주문서)는 요구 권한이 갈릴 수 있어 // 호출부의 의도를 이름으로 남긴다 — 한쪽에 권한이 붙어도 다른 쪽이 영향받지 않는다. diff --git a/modules/_bundled/sirsoft-ecommerce/tests/Playwright/specs/admin/ecommerce-notification-definition-channel.spec.ts b/modules/_bundled/sirsoft-ecommerce/tests/Playwright/specs/admin/ecommerce-notification-definition-channel.spec.ts new file mode 100644 index 00000000..05ff6150 --- /dev/null +++ b/modules/_bundled/sirsoft-ecommerce/tests/Playwright/specs/admin/ecommerce-notification-definition-channel.spec.ts @@ -0,0 +1,188 @@ +/** + * 커머스 알림 정의 탭 채널 계약 — 목록 요청이 화면 채널을 나르는지 (#518 / 공개 #76). + * + * 결함(2026-08-05 브라우저 실측): 목록 프루닝으로 서버가 `template_channel` 을 받은 요청에만 + * `templates` 를 싣게 됐는데, 코어 설정 화면만 그 파라미터를 보내도록 고쳐지고 커머스 탭 + * 데이터소스는 빠졌다. 10건 전 행이 「이 채널에 대한 템플릿이 없습니다」로 표시되고 + * 제목·수신자·활성 토글·편집 모달 초기값이 통째로 비었다. + * + * 이 spec 이 브라우저 수준을 담당하는 이유: 구조 테스트(Vitest)는 레이아웃 JSON 에 + * `template_channel` 키가 있는지만 본다. 그 키가 **실제로 요청에 실려 나가는지**와 + * 그 응답으로 행이 채워지는지는 렌더까지 가야 드러난다 — 결함 당시 응답은 200 이었고 + * 행 수도 정상이었다. + * + * 시드 의존: 커머스 모듈 알림 정의(최소 1건). 커스터마이즈 축은 화면에서 만들고 되돌린다. + */ +import { test, expect, authenticatePage } from '../../fixtures/ecommerce-auth'; +import type { Page } from '@playwright/test'; + +const SETTINGS_URL = '/admin/ecommerce/settings?tab=notification_definitions'; +const DEFINITIONS_API = /\/api\/admin\/notification-definitions(\?|$)/; + +/** 템플릿이 비었을 때 행에 표시되는 문구 */ +const EMPTY_TEMPLATE_TEXT = '이 채널에 대한 템플릿이 없습니다'; + +/** + * 알림 정의 목록 응답을 기다리며 해당 채널의 설정 탭으로 이동합니다. + * + * @param page Playwright 페이지 + * @param channel 조회할 알림 채널 + * @returns 요청 URL 과 응답 본문 + */ +async function loadChannel(page: Page, channel: string) { + const pending = page.waitForResponse( + (res) => DEFINITIONS_API.test(res.url()) && res.request().method() === 'GET', + { timeout: 30_000 }, + ); + + await page.goto(`${SETTINGS_URL}&channel=${channel}`); + + const response = await pending; + + return { requestUrl: response.url(), payload: await response.json() }; +} + +/** + * 기본 상태(커스터마이즈 없음) 축의 공통 단언을 수행합니다. + * + * @param page Playwright 페이지 + * @param channel 검증 대상 채널 + */ +async function assertChannelIsCarried(page: Page, channel: string) { + const { requestUrl, payload } = await loadChannel(page, channel); + + expect( + requestUrl, + '화면이 보는 채널을 서버에 넘기지 않으면 응답에 templates 키 자체가 없다', + ).toContain(`template_channel=${channel}`); + + const rows = payload?.data?.data ?? []; + + test.skip(rows.length === 0, '커머스 알림 정의 시드가 없어 판정할 수 없다'); + + // 서버가 요청한 채널만 실었는지 — 다른 채널이 섞이면 프루닝이 무의미하다. + for (const row of rows) { + expect(row, 'templates 키가 응답에 있어야 한다').toHaveProperty('templates'); + + for (const template of row.templates ?? []) { + expect(template.channel, '요청하지 않은 채널이 실렸다').toBe(channel); + } + } + + // 렌더까지 확인 — 응답에 실렸어도 화면이 다른 채널을 찾으면 빈 문구가 남는다. + await expect( + page.getByText(EMPTY_TEMPLATE_TEXT).first(), + '템플릿이 실렸는데도 빈 문구가 보이면 화면과 요청의 채널이 어긋난 것이다', + ).toBeHidden(); +} + +/** + * 지정 채널의 첫 정의를 편집해 커스터마이즈 상태로 만든 뒤, + * 「기본값 복원」이 채널을 가리지 않고 노출되는지 확인하고 원상 복구합니다. + * + * @param page Playwright 페이지 + * @param channel 커스터마이즈를 수행할 채널 + */ +async function assertResetButtonSpansChannels(page: Page, channel: string) { + // 편집→저장→재조회→복원까지 도는 다단계 플로우라 기본 30초를 넘는다. + test.setTimeout(120_000); + + const { payload } = await loadChannel(page, channel); + + test.skip((payload?.data?.data ?? []).length === 0, '커머스 알림 정의 시드가 없어 판정할 수 없다'); + + const resetButton = page.getByRole('button', { name: '기본값 복원' }); + const initialCount = await resetButton.count(); + + await page.getByRole('button', { name: '편집' }).first().click(); + + const dialog = page.getByRole('dialog'); + const subject = dialog.getByRole('textbox').first(); + + await subject.fill(`${await subject.inputValue()} [E2E]`); + await dialog.getByRole('button', { name: '저장' }).click(); + await expect(dialog).toBeHidden({ timeout: 15_000 }); + + // 반대 채널로 옮겨도 복원 버튼이 보여야 한다. 화면이 templates 배열을 순회해 판정하면 + // 반대 채널 templates 에는 커스터마이즈가 없어 버튼이 사라진다. + const otherChannel = channel === 'mail' ? 'database' : 'mail'; + + try { + await loadChannel(page, otherChannel); + + await expect( + resetButton.first(), + '채널을 가리지 않는 판정은 서버 집계(has_customized_templates)가 유일한 출처다', + ).toBeVisible({ timeout: 15_000 }); + + expect( + await resetButton.count(), + '커스터마이즈한 정의에만 복원 버튼이 붙어야 한다', + ).toBeGreaterThan(initialCount); + } finally { + // 시드 상태로 되돌린다 — 남겨두면 다음 실행의 기준선이 달라진다. + await loadChannel(page, otherChannel); + + if ((await resetButton.count()) > 0) { + await resetButton.first().click(); + await page.getByRole('button', { name: '복원', exact: true }).click(); + await expect(resetButton).toHaveCount(initialCount, { timeout: 15_000 }); + } + } +} + +test.describe('커머스 알림 정의 탭 — 채널 계약 (#518)', () => { + /** + * @scenario channel=mail,template_state=default + * @effects ecommerce_notification_list_requests_template_channel, ecommerce_notification_channel_reads_single_source + */ + test('mail 채널: 목록 요청이 그 채널을 나르고 행에 템플릿이 채워진다', async ({ + page, + settingsToken, + }) => { + await authenticatePage(page, settingsToken); + await assertChannelIsCarried(page, 'mail'); + }); + + /** + * @scenario channel=database,template_state=default + * @effects ecommerce_notification_list_requests_template_channel, ecommerce_notification_channel_reads_single_source + */ + test('database 채널: 목록 요청이 그 채널을 나르고 행에 템플릿이 채워진다', async ({ + page, + settingsToken, + }) => { + await authenticatePage(page, settingsToken); + await assertChannelIsCarried(page, 'database'); + }); + + test.describe('커스터마이즈 상태', () => { + // 두 축 모두 같은 시드 정의를 편집·복원하므로 병렬로 돌면 서로의 기준선을 무너뜨린다. + // 이 블록 안에서만 직렬 — 부모 describe 에 걸면 기본 상태 2건까지 한 건 실패로 중단된다. + test.describe.configure({ mode: 'serial' }); + + /** + * @scenario channel=mail,template_state=customized + * @effects ecommerce_notification_reset_button_uses_server_aggregate + */ + test('mail 채널에서 커스터마이즈하면 database 화면에서도 기본값 복원이 보인다', async ({ + page, + settingsToken, + }) => { + await authenticatePage(page, settingsToken); + await assertResetButtonSpansChannels(page, 'mail'); + }); + + /** + * @scenario channel=database,template_state=customized + * @effects ecommerce_notification_reset_button_uses_server_aggregate + */ + test('database 채널에서 커스터마이즈하면 mail 화면에서도 기본값 복원이 보인다', async ({ + page, + settingsToken, + }) => { + await authenticatePage(page, settingsToken); + await assertResetButtonSpansChannels(page, 'database'); + }); + }); +}); diff --git a/modules/_bundled/sirsoft-ecommerce/tests/Playwright/specs/admin/product-list-lazy-options.spec.ts b/modules/_bundled/sirsoft-ecommerce/tests/Playwright/specs/admin/product-list-lazy-options.spec.ts new file mode 100644 index 00000000..fd051602 --- /dev/null +++ b/modules/_bundled/sirsoft-ecommerce/tests/Playwright/specs/admin/product-list-lazy-options.spec.ts @@ -0,0 +1,458 @@ +/** + * 관리자 상품목록 옵션 지연 로딩 — 목록을 여는 것만으로 옵션이 전부 실려 오지 않는지 (공개 이슈 #76). + * + * 결함: 상품목록 한 페이지를 여는 것만으로 그 페이지 모든 상품의 옵션 데이터가 응답에 실렸다. + * 옵션을 많이 쓰는 사이트에서는 상품 한 줄을 보여주려고 수백~수천 옵션 행을 로드·전송하게 된다. + * + * 수정: 목록은 요약(활성 옵션 수 / 전체 옵션 수 / 활성 옵션 재고 합계)만 내려주고, 옵션 상세는 + * 행을 펼칠 때 배치 엔드포인트가 공급한다. `?with_options=1` 로 종전 동작을 유지할 수 있다. + * + * 첫 describe 는 네트워크 관측으로 판정할 수 있는 축을 실측한다 — 응답 페이로드, 확장 시 요청 발생, + * 접었다 펴기의 재요청 부재. 두 번째 describe 는 UI 상호작용 축을 **저장까지** 실측한다. 목록이 + * 옵션을 싣지 않게 됐으므로 저장 후 재표시가 이 이슈의 핵심 위험이고, 그 실패는 브라우저에서만 + * 드러나기 때문이다. 실제 데이터는 테스트마다 전용 상품을 만들어 그것만 변경·삭제해 보호한다. + * + * @scenario mp-product-list-lazy-options + * @axes row_state=collapsed|expanded|re_expanded option_profile=none|mixed + * @effects list_response_omits_options, + * options_total_count_matches_all, + * no_fetch_when_total_count_zero, + * scope_filtered_ids_absent + */ +import { test, expect, authenticatePage } from '../../fixtures/ecommerce-auth'; + +const LIST_URL = '/admin/ecommerce/products'; +const LIST_API = '/api/modules/sirsoft-ecommerce/admin/products'; +const OPTIONS_API = '/api/modules/sirsoft-ecommerce/admin/products/options'; +const CATEGORY_API = '/api/modules/sirsoft-ecommerce/admin/categories'; + +/** + * 관리자 API 를 브라우저 컨텍스트에서 직접 호출합니다. + * + * @param page Playwright 페이지 + * @param url 호출할 경로 + * @returns 응답 본문 + */ +async function callApi(page: any, url: string): Promise { + return page.evaluate(async (target: string) => { + const res = await fetch(target, { + headers: { + Authorization: `Bearer ${localStorage.getItem('auth_token')}`, + Accept: 'application/json', + }, + }); + + return res.json(); + }, url); +} + +/** + * 브라우저 컨텍스트에서 쓰기 API 를 호출합니다. + * + * @param page Playwright 페이지 + * @param method HTTP 메서드 + * @param url 호출할 경로 + * @param body 요청 본문 (없으면 생략) + * @returns 응답 본문 + */ +async function writeApi(page: any, method: string, url: string, body?: any): Promise { + return page.evaluate( + async ({ method, target, payload }: { method: string; target: string; payload?: any }) => { + const res = await fetch(target, { + method, + headers: { + Authorization: `Bearer ${localStorage.getItem('auth_token')}`, + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: payload === undefined ? undefined : JSON.stringify(payload), + }); + + return res.json(); + }, + { method, target: url, payload: body }, + ); +} + +/** + * 이 spec 전용 상품을 옵션 2건과 함께 생성합니다. + * + * 저장 경로를 실측하려면 실제 쓰기가 필요하다. 사이트의 기존 상품을 건드리는 대신 전용 상품을 + * 만들고 그것만 변경한 뒤 삭제한다 — 실행마다 남의 데이터가 바뀌지 않는다. + * + * @param page Playwright 페이지 + * @returns 생성된 상품 (실패 시 null) + */ +async function createFixtureProduct(page: any): Promise { + const categories = await callApi(page, `${CATEGORY_API}?per_page=1`); + const categoryId = (categories?.data?.data ?? categories?.data ?? [])[0]?.id; + + if (!categoryId) { + return null; + } + + const suffix = `${Date.now()}`.slice(-9); + const created = await writeApi(page, 'POST', LIST_API, { + name: { ko: `E2E 지연로딩 픽스처 ${suffix}` }, + product_code: `E2E-LAZY-${suffix}`, + category_ids: [categoryId], + list_price: 10000, + selling_price: 10000, + stock_quantity: 30, + sales_status: 'on_sale', + display_status: 'visible', + tax_status: 'taxable', + options: [ + { + option_code: `E2E-OPT-A-${suffix}`, + option_name: { ko: '픽스처 옵션 A' }, + option_values: [{ key: { ko: '색상' }, value: { ko: '빨강' } }], + list_price: 10000, + selling_price: 10000, + stock_quantity: 10, + is_active: true, + }, + { + option_code: `E2E-OPT-B-${suffix}`, + option_name: { ko: '픽스처 옵션 B' }, + option_values: [{ key: { ko: '색상' }, value: { ko: '파랑' } }], + list_price: 10000, + selling_price: 10000, + stock_quantity: 20, + is_active: true, + }, + ], + }); + + return created?.data ?? null; +} + +test.describe('관리자 상품목록 옵션 지연 로딩 (#76)', () => { + // @scenario row_state=collapsed,option_profile=mixed + // @effects list_response_omits_options, options_total_count_matches_all + test('목록 응답에 옵션 배열이 없고 집계 필드만 실린다', async ({ page, productManageToken }) => { + await authenticatePage(page, productManageToken); + await page.goto(LIST_URL); + await page.waitForLoadState('domcontentloaded', { timeout: 30_000 }); + + const body = await callApi(page, `${LIST_API}?per_page=20`); + const rows = body?.data?.data ?? []; + + expect(rows.length).toBeGreaterThan(0); + + for (const row of rows) { + expect(row).not.toHaveProperty('options'); + expect(typeof row.options_count).toBe('number'); + expect(typeof row.options_total_count).toBe('number'); + expect(typeof row.option_stock_sum).toBe('number'); + // 옵션 0건에서도 null 이 아니라 0 이어야 한다 (재고 불일치 배지 오탐 가드) + expect(row.option_stock_sum).not.toBeNull(); + } + }); + + // @scenario row_state=collapsed,option_profile=mixed + // @effects list_response_omits_options + test('with_options=1 로는 종전처럼 옵션이 함께 온다', async ({ page, productManageToken }) => { + await authenticatePage(page, productManageToken); + await page.goto(LIST_URL); + await page.waitForLoadState('domcontentloaded', { timeout: 30_000 }); + + const body = await callApi(page, `${LIST_API}?per_page=20&with_options=1`); + const rows = body?.data?.data ?? []; + + expect(rows.length).toBeGreaterThan(0); + expect(rows.some((row: any) => Array.isArray(row.options))).toBe(true); + }); + + // @scenario row_state=expanded,option_profile=mixed + // @effects options_total_count_matches_all + test('옵션 배치 조회가 요청한 상품 ID 로 묶인 맵을 돌려준다', async ({ page, productManageToken }) => { + await authenticatePage(page, productManageToken); + await page.goto(LIST_URL); + await page.waitForLoadState('domcontentloaded', { timeout: 30_000 }); + + const list = await callApi(page, `${LIST_API}?per_page=20`); + const withOptions = (list?.data?.data ?? []).filter((row: any) => row.options_total_count > 0); + + test.skip(withOptions.length === 0, '옵션을 가진 상품이 시드에 없다'); + + const target = withOptions[0]; + const body = await callApi(page, `${OPTIONS_API}?product_ids[]=${target.id}`); + + expect(body?.data?.product_ids).toContain(target.id); + expect(Array.isArray(body?.data?.options?.[String(target.id)])).toBe(true); + // 배치는 비활성 옵션도 포함하므로 전체 옵션 수와 일치한다 + expect(body.data.options[String(target.id)].length).toBe(target.options_total_count); + }); + + // @scenario row_state=expanded,option_profile=none + // @effects no_fetch_when_total_count_zero + test('옵션 0건 상품은 빈 배열을 돌려준다 (부분 성공 계약)', async ({ page, productManageToken }) => { + await authenticatePage(page, productManageToken); + await page.goto(LIST_URL); + await page.waitForLoadState('domcontentloaded', { timeout: 30_000 }); + + const list = await callApi(page, `${LIST_API}?per_page=50`); + const withoutOptions = (list?.data?.data ?? []).filter((row: any) => row.options_total_count === 0); + + test.skip(withoutOptions.length === 0, '옵션이 없는 상품이 시드에 없다'); + + const target = withoutOptions[0]; + const body = await callApi(page, `${OPTIONS_API}?product_ids[]=${target.id}`); + + expect(body?.data?.product_ids).toContain(target.id); + expect(body.data.options[String(target.id)]).toEqual([]); + }); + + // @scenario row_state=expanded,option_profile=mixed + // @effects scope_filtered_ids_absent + test('존재하지 않는 상품 ID 는 404 가 아니라 응답에서 빠진다', async ({ page, productManageToken }) => { + await authenticatePage(page, productManageToken); + await page.goto(LIST_URL); + await page.waitForLoadState('domcontentloaded', { timeout: 30_000 }); + + const missingId = 999999999; + const body = await callApi(page, `${OPTIONS_API}?product_ids[]=${missingId}`); + + expect(body?.success).not.toBe(false); + expect(body?.data?.product_ids ?? []).not.toContain(missingId); + expect(body?.data?.options?.[String(missingId)]).toBeUndefined(); + }); + + // @scenario row_state=re_expanded,option_profile=mixed + // @effects no_fetch_when_total_count_zero + test('행을 펼치면 옵션 API 가 한 번 호출되고, 접었다 펴면 다시 호출되지 않는다', async ({ + page, + productManageToken, + }) => { + await authenticatePage(page, productManageToken); + + const optionCalls: string[] = []; + page.on('request', (request: any) => { + if (request.url().includes('/admin/products/options')) { + optionCalls.push(request.url()); + } + }); + + await page.goto(LIST_URL); + await page.waitForLoadState('domcontentloaded', { timeout: 30_000 }); + + // 확장 토글 — DataGrid 가 확장 가능한 행마다 렌더하는 버튼. data-testid 가 없으므로 + // 접근성 라벨로 잡는다 (컴포넌트가 `Expand row` / `Collapse row` 로 고정 노출한다). + const expandToggle = page.locator('button[aria-label="Expand row"]').first(); + + await expect(expandToggle).toBeVisible({ timeout: 30_000 }); + await expandToggle.click(); + await expect.poll(() => optionCalls.length, { timeout: 15_000 }).toBeGreaterThan(0); + + const afterFirstExpand = optionCalls.length; + + // 접기 → 다시 펼치기: 이미 채워진 행이므로 추가 요청이 없어야 한다 + await page.locator('button[aria-label="Collapse row"]').first().click(); + await page.locator('button[aria-label="Expand row"]').first().click(); + await page.waitForTimeout(1_000); + + expect(optionCalls.length).toBe(afterFirstExpand); + }); +}); + +/** + * UI 상호작용 축. + * + * 저장(PUT) 까지 브라우저에서 실행한다. 저장 이후의 재표시가 이 이슈의 핵심 위험이기 때문이다 — + * 목록이 옵션을 더 이상 싣지 않으므로, 저장 후 화면이 옵션을 다시 가져오지 못하면 펼친 행이 + * 빈 채로 남는다. 그 실패는 단위 테스트가 아니라 브라우저에서만 드러난다. + * + * 실제 데이터 보호는 "저장하지 않기" 가 아니라 **전용 상품을 만들어 그것만 건드리는** 방식으로 + * 한다. 각 테스트는 자신이 만든 상품에만 쓰기를 수행하고 종료 시 삭제한다. + */ +test.describe('옵션 지연 로딩 UI 상호작용', () => { + // @scenario row_state=expanded,option_profile=mixed + // @effects options_reloaded_after_bulk_save + test('행을 펼치면 옵션 행이 서버 값으로 렌더된다', async ({ page, productManageToken }) => { + await authenticatePage(page, productManageToken); + await page.goto(LIST_URL); + await page.waitForLoadState('domcontentloaded', { timeout: 30_000 }); + + const expandToggle = page.locator('button[aria-label="Expand row"]').first(); + await expect(expandToggle).toBeVisible({ timeout: 30_000 }); + await expandToggle.click(); + + // 옵션은 목록 응답에 없었고 배치 엔드포인트가 채운 값이다 — 렌더되면 지연 로딩이 성립한 것 + await expect(page.locator('[data-testid^="option-row-"]').first()).toBeVisible({ + timeout: 15_000, + }); + }); + + // @scenario row_state=collapsed,option_profile=mixed + // @effects aggregate_summary_equals_enumerated_summary + test('미펼침 상품을 체크하고 일괄 적용하면 확인 모달에 옵션 집계가 표시된다', async ({ + page, + productManageToken, + }) => { + await authenticatePage(page, productManageToken); + await page.goto(LIST_URL); + await page.waitForLoadState('domcontentloaded', { timeout: 30_000 }); + + // 옵션을 가진 상품이 있어야 집계 섹션이 나타난다 + const list = await callApi(page, `${LIST_API}?per_page=50`); + const rows = list?.data?.data ?? []; + const withOptions = rows.filter((row: any) => (row.options_total_count ?? 0) > 0); + test.skip(withOptions.length === 0, '옵션을 가진 상품이 시드에 없다'); + + // 행을 펼치지 않은 채로 선택만 한다 (집계 경로 — 개별 옵션 열거 없이 개수만 계산) + const rowCheckbox = page + .locator('tbody input[type="checkbox"], [role="row"] input[type="checkbox"]') + .first(); + await expect(rowCheckbox).toBeVisible({ timeout: 30_000 }); + await rowCheckbox.check(); + + // 옵션 대상 변경(재고 일괄)이 걸려 있어야 확인 모달이 옵션 섹션을 낸다. + // 이 단계는 전역 상태만 세팅하며 서버 요청을 보내지 않는다. + await page.getByTestId('product-bulk-stock-open').click(); + await page.getByTestId('bulk-stock-value').fill('1'); + await page.getByTestId('bulk-stock-apply').click(); + + const applyButton = page.getByTestId('product-bulk-apply'); + await expect(applyButton).toBeEnabled({ timeout: 15_000 }); + await applyButton.click(); + + await expect(page.getByTestId('bulk-confirm-options-section')).toBeVisible({ + timeout: 15_000, + }); + }); + + // @scenario row_state=expanded,option_profile=active_only + // @effects options_reloaded_after_bulk_save + test('인라인으로 옵션 재고를 고치고 저장하면 펼친 행이 저장된 값으로 다시 표시된다', async ({ + page, + productManageToken, + }) => { + await authenticatePage(page, productManageToken); + await page.goto(LIST_URL); + await page.waitForLoadState('domcontentloaded', { timeout: 30_000 }); + + const product = await createFixtureProduct(page); + test.skip(!product?.id, '전용 상품을 만들 카테고리가 없다'); + + try { + // 방금 만든 상품만 보이도록 좁힌다 (다른 상품을 건드리지 않기 위해서이기도 하다) + await page.goto( + `${LIST_URL}?search_field=all&search_keyword=${encodeURIComponent(product.product_code)}`, + ); + await page.waitForLoadState('domcontentloaded', { timeout: 30_000 }); + + // 워커가 병렬로 돌면 다른 테스트의 픽스처 상품도 목록에 있다. 항상 내 상품 행만 집는다. + const row = page + .locator('tr, [role="row"]') + .filter({ hasText: product.product_code }) + .first(); + await expect(row).toBeVisible({ timeout: 30_000 }); + await row.locator('button[aria-label="Expand row"]').first().click(); + + const optionId = (await callApi(page, `${OPTIONS_API}?product_ids[]=${product.id}`)) + ?.data?.options?.[String(product.id)]?.[0]?.id; + expect(optionId).toBeTruthy(); + + const stockInput = page.getByTestId(`option-stock-${optionId}`); + await expect(stockInput).toBeVisible({ timeout: 15_000 }); + + // 인라인 편집 → 그 옵션 체크 → 일괄 적용 → 확인 → 저장 + await stockInput.fill('77'); + await stockInput.blur(); + + const optionCheckbox = page + .locator(`[data-testid="option-row-${optionId}"] input[type="checkbox"]`) + .first(); + await expect(optionCheckbox).toBeVisible({ timeout: 15_000 }); + await optionCheckbox.check(); + + await page.getByTestId('product-bulk-apply').click(); + + const submit = page.getByTestId('bulk-confirm-submit'); + await expect(submit).toBeEnabled({ timeout: 15_000 }); + await submit.click(); + + // 저장 후 목록이 리페치되고, 펼쳐 둔 행의 옵션이 다시 로드되어야 한다. + // 목록 응답에는 옵션이 없으므로 재로드가 없으면 이 값은 화면에 나타날 수 없다. + await expect(page.getByTestId(`option-stock-${optionId}`)).toHaveValue('77', { + timeout: 20_000, + }); + + // 서버에도 실제로 저장됐는지 대조 (화면만 갱신되고 저장이 안 된 경우를 가른다) + const persisted = await callApi(page, `${OPTIONS_API}?product_ids[]=${product.id}`); + const saved = (persisted?.data?.options?.[String(product.id)] ?? []).find( + (option: any) => option.id === optionId, + ); + expect(saved?.stock_quantity).toBe(77); + } finally { + await writeApi(page, 'DELETE', `${LIST_API}/${product.id}`); + } + }); + + // @scenario row_state=collapsed,option_profile=active_only + // @effects aggregate_summary_equals_enumerated_summary, bulk_applies_to_inactive_options + test('미펼침 상품을 일괄 저장하면 펼치지 않은 옵션에도 적용된다', async ({ + page, + productManageToken, + }) => { + await authenticatePage(page, productManageToken); + await page.goto(LIST_URL); + await page.waitForLoadState('domcontentloaded', { timeout: 30_000 }); + + const product = await createFixtureProduct(page); + test.skip(!product?.id, '전용 상품을 만들 카테고리가 없다'); + + try { + await page.goto( + `${LIST_URL}?search_field=all&search_keyword=${encodeURIComponent(product.product_code)}`, + ); + await page.waitForLoadState('domcontentloaded', { timeout: 30_000 }); + + // 펼치지 않은 채로 상품만 체크한다 — 화면은 이 상품의 옵션 목록을 갖고 있지 않다. + // 워커가 병렬로 돌면 다른 테스트의 픽스처 상품도 목록에 있으므로 내 상품 행으로 한정한다. + const row = page + .locator('tr, [role="row"]') + .filter({ hasText: product.product_code }) + .first(); + await expect(row).toBeVisible({ timeout: 30_000 }); + + const rowCheckbox = row.locator('input[type="checkbox"]').first(); + await rowCheckbox.check(); + await expect(rowCheckbox).toBeChecked(); + + await page.getByTestId('product-bulk-stock-open').click(); + await page.getByTestId('bulk-stock-value').fill('5'); + await page.getByTestId('bulk-stock-apply').click(); + + await page.getByTestId('product-bulk-apply').click(); + + // 확인 모달은 개별 열거가 아니라 집계 엔트리로 같은 수치를 보여준다 + await expect(page.getByTestId('bulk-confirm-options-section')).toBeVisible({ + timeout: 15_000, + }); + + const submit = page.getByTestId('bulk-confirm-submit'); + await expect(submit).toBeEnabled({ timeout: 15_000 }); + await submit.click(); + + // 화면이 옵션을 갖고 있지 않았어도 서버가 ids 만으로 전 옵션에 전개해야 한다. + // 재고 일괄의 기본 방식은 '증가' 이므로 픽스처의 10/20 이 각각 15/25 가 되어야 한다. + await expect + .poll( + async () => { + const body = await callApi(page, `${OPTIONS_API}?product_ids[]=${product.id}`); + const options = body?.data?.options?.[String(product.id)] ?? []; + + return options + .map((option: any) => option.stock_quantity) + .sort((a: number, b: number) => a - b); + }, + { timeout: 20_000 }, + ) + .toEqual([15, 25]); + } finally { + await writeApi(page, 'DELETE', `${LIST_API}/${product.id}`); + } + }); +}); diff --git a/modules/_bundled/sirsoft-ecommerce/tests/Playwright/specs/admin/shipping-policy-list-country-settings.spec.ts b/modules/_bundled/sirsoft-ecommerce/tests/Playwright/specs/admin/shipping-policy-list-country-settings.spec.ts new file mode 100644 index 00000000..7f266bcb --- /dev/null +++ b/modules/_bundled/sirsoft-ecommerce/tests/Playwright/specs/admin/shipping-policy-list-country-settings.spec.ts @@ -0,0 +1,176 @@ +/** + * 배송정책 목록 — 국가 칩·배송비 요약이 화면에 남아 있는지 (공개 이슈 #76). + * + * 목록에서 국가별 설정을 통째로 빼면서, 이 값을 실제로 그리던 배송정책 관리 목록이 함께 비어 + * 버렸다. 응답만 보는 테스트는 "뺐다" 를 확인할 뿐 "그 값을 화면이 쓰고 있었다" 는 사실을 잡지 + * 못한다 — 그래서 브라우저에서 열어 열 내용이 실제로 채워지는지 본다. + * + * 최종 계약은 "제거" 가 아니라 "컬럼 축소" 다. 기본 응답이 목록 표시용 국가별 설정을 그대로 + * 싣고, `with_country_settings=1` 은 전체 컬럼이 필요한 외부 호출자를 위한 하위호환 경로다. + * 따라서 이 spec 은 두 가지를 함께 고정한다 — 화면이 opt-in 을 켜지 않는다는 것과, 그런데도 + * 국가 칩·배송비 요약이 채워진다는 것. 한쪽만 보면 어느 방향으로 되돌아가도 통과한다. + * + * @scenario surface=admin_shipping_policy_list + * @effects shipping_policy_list_renders_country_summary + */ +import { test, expect, authenticatePage } from '../../fixtures/ecommerce-auth'; + +const LIST_URL = '/admin/ecommerce/shipping-policies'; +const LIST_API = '/api/modules/sirsoft-ecommerce/admin/shipping-policies'; + +/** + * 이 spec 전용 배송정책을 국가 설정 2건과 함께 생성합니다. + * + * 시드에 국가 설정이 달린 정책이 없으면 검증이 skip 으로 빠져 아무것도 확인하지 못한다. + * 사이트의 기존 정책을 건드리는 대신 전용 정책을 만들고 종료 시 삭제한다. + * + * @param page Playwright 페이지 + * @returns 생성된 배송정책 (실패 시 null) + */ +async function createFixturePolicy(page: any): Promise { + const suffix = `${Date.now()}`.slice(-9); + + const created = await page.evaluate( + async ({ api, name }: { api: string; name: string }) => { + const res = await fetch(api, { + method: 'POST', + headers: { + Authorization: `Bearer ${localStorage.getItem('auth_token')}`, + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + name: { ko: name }, + is_active: true, + is_default: false, + sort_order: 0, + country_settings: [ + { country_code: 'KR', shipping_method: 'parcel', charge_policy: 'fixed', base_fee: 3000, extra_fee_enabled: false, is_active: true }, + { country_code: 'US', shipping_method: 'parcel', charge_policy: 'fixed', base_fee: 25000, extra_fee_enabled: false, is_active: true }, + ], + }), + }); + + return res.json(); + }, + { api: LIST_API, name: `E2E 국가요약 픽스처 ${suffix}` }, + ); + + return created?.data ?? null; +} + +test.describe('배송정책 목록 국가 요약 (#76)', () => { + test('목록 요청이 무거운 opt-in 을 켜지 않고도 국가별 설정을 받아온다', async ({ + page, + shippingPolicyToken, + }) => { + await authenticatePage(page, shippingPolicyToken); + + const requests: string[] = []; + page.on('request', (request) => { + if (request.url().includes(LIST_API)) { + requests.push(request.url()); + } + }); + + await page.goto(LIST_URL); + await page.waitForLoadState('domcontentloaded', { timeout: 30_000 }); + + // 목록 요청이 실제로 나갔는지부터 확정한다 — 요청이 0건이면 아래 부재 단언이 + // 아무것도 검증하지 못한 채 통과한다. + await expect.poll(() => requests.length, { timeout: 20_000 }).toBeGreaterThan(0); + + // 화면은 opt-in 을 켜지 않는다. 켜면 쓰지도 않는 구간 설정·도서산간 설정·계산 API + // 설정까지 정책당 국가 수만큼 되받아 프루닝 효과가 0 이 된다. + expect( + requests.filter((url) => /with_country_settings=(1|true)/.test(url)), + '목록 화면은 with_country_settings 를 보내지 않는다 (기본 응답이 표시용 필드를 공급한다)', + ).toHaveLength(0); + + // 그런데도 화면이 그리는 값은 응답에 들어 있어야 한다 — 이 둘이 함께 성립해야 + // "가볍게 줄였다" 가 "화면이 비었다" 로 뒤집히지 않는다. + const body = await page.evaluate(async (api) => { + const res = await fetch(`${api}?per_page=10`, { + headers: { + Authorization: `Bearer ${localStorage.getItem('auth_token')}`, + Accept: 'application/json', + }, + }); + + return res.json(); + }, LIST_API); + + const row = (body?.data?.data ?? [])[0]; + + expect(row, '배송정책이 한 건 이상 있어야 한다').toBeTruthy(); + expect(row).toHaveProperty('country_settings'); + expect(row).toHaveProperty('fee_summary'); + expect(row).toHaveProperty('countries_display'); + }); + + test('국가 칩과 배송비 요약 열이 비어 있지 않다', async ({ page, shippingPolicyToken }) => { + await authenticatePage(page, shippingPolicyToken); + await page.goto(LIST_URL); + await page.waitForLoadState('domcontentloaded', { timeout: 30_000 }); + + // 국가 설정이 달린 정책을 직접 만든다 — 시드에 없으면 검증이 통째로 비어 버린다. + const policy = await createFixturePolicy(page); + test.skip(!policy?.id, '배송정책을 만들 수 없다 (배송방법 코드 미등록 등)'); + + await page.reload(); + await page.waitForLoadState('domcontentloaded', { timeout: 30_000 }); + + try { + + // 존재를 먼저 확정한 뒤 내용을 단언한다 — 렌더 전에 통과하는 부재 단언을 피한다. + const rows = page.locator('tbody tr, [role="row"]'); + await expect(rows.first()).toBeVisible({ timeout: 30_000 }); + + // 화면과 같은 경로로 조회한다. opt-in 을 켜서 확인하면 정작 화면이 쓰는 기본 경로가 + // 비어 있어도 통과해 버린다 — 실제로 그렇게 비었던 것이 이 spec 이 생긴 이유다. + const body = await page.evaluate(async (api) => { + const res = await fetch(`${api}?per_page=10`, { + headers: { + Authorization: `Bearer ${localStorage.getItem('auth_token')}`, + Accept: 'application/json', + }, + }); + + return res.json(); + }, LIST_API); + + const policies = body?.data?.data ?? []; + + // 국가 설정이 달린 정책이 하나라도 있으면 요약 필드가 채워져야 한다. + const withCountries = policies.filter( + (policy: any) => (policy.country_settings ?? []).length > 0, + ); + + expect(withCountries.length, '국가 설정이 달린 배송정책이 있어야 한다').toBeGreaterThan(0); + + for (const policy of withCountries) { + expect(policy.countries_display, '국가 표시 값이 있어야 한다').toBeTruthy(); + expect(policy.fee_summary, '배송비 요약이 있어야 한다').toBeTruthy(); + } + + // 화면에도 요약 문자열이 그려져야 한다 (열이 '-' 로만 남으면 실패). + const summary = withCountries[0].fee_summary.split(' | ')[0]; + await expect(page.locator('tbody, [role="rowgroup"]')).toContainText(summary.slice(0, 12), { + timeout: 15_000, + }); + } finally { + await page.evaluate( + async ({ api, id }: { api: string; id: number }) => { + await fetch(`${api}/${id}`, { + method: 'DELETE', + headers: { + Authorization: `Bearer ${localStorage.getItem('auth_token')}`, + Accept: 'application/json', + }, + }); + }, + { api: LIST_API, id: policy.id }, + ); + } + }); +}); diff --git a/modules/_bundled/sirsoft-ecommerce/tests/scenarios/admin-order-list-representative-option.yaml b/modules/_bundled/sirsoft-ecommerce/tests/scenarios/admin-order-list-representative-option.yaml new file mode 100644 index 00000000..bdcfac3a --- /dev/null +++ b/modules/_bundled/sirsoft-ecommerce/tests/scenarios/admin-order-list-representative-option.yaml @@ -0,0 +1,59 @@ +feature: 관리자 주문 목록의 대표 주문상품 축약 (#518 / 공개 #76) + +description: | + 관리자 주문 목록은 대표 상품 1건과 "외 N건" 만 그리는데, 조회는 주문마다 주문상품 전량을 + 읽고 있었다. 한 주문에 상품을 여러 개 담는 쇼핑몰일수록 한 페이지를 여는 것만으로 + 주문 수 × 주문상품 수가 메모리에 올라온다. + + 관계 자체를 대표 1건으로 좁히고(`hasOne(...)->oldestOfMany()`), 개수와 부분취소 판정은 DB + 집계로 대체한다. eager load 의 `limit(1)` 은 부모별이 아니라 배치 쿼리 전체에 걸리므로 쓸 수 + 없다 — 그렇게 하면 첫 주문 하나만 대표 상품을 갖고 나머지는 빈칸이 된다. + + 마이페이지 주문내역은 주문상품을 전부 나열하는 화면이라(실측: `partials/mypage/orders/_list.json` + 의 `order.items` 순회) 같은 저장소 메서드를 쓰면서도 전량 로드를 유지한다. 관리자 목록 최적화가 + 그 화면의 표시 범위를 줄이면 그것은 성능 개선이 아니라 기능 삭제다. + +# axis 의미: +# 무엇이 실리는지를 가르는 축은 (소비 화면) × (주문상품 구성) 이다. +# option_profile 은 집계가 값으로 판정되면 틀리는 경계(0건)와 부분취소 판정 경계를 담는다. +axes: + surface: [admin_list, my_page_list] + option_profile: [none, single, multiple, partially_cancelled, fully_cancelled] + +exclusions: + - { surface: my_page_list, option_profile: partially_cancelled, reason: "부분취소 뱃지 판정은 관리자 목록과 같은 집계 경로를 쓴다 — 관리자 축이 이미 두 집계(전체/취소)의 조합을 전수 검증하므로 화면만 다른 중복 조합" } + - { surface: my_page_list, option_profile: fully_cancelled, reason: "동일 — 판정식이 관리자 목록과 같은 코드 경로다" } + - { surface: my_page_list, option_profile: none, reason: "주문상품 없는 주문은 마이페이지 진입 경로가 없다(주문 생성이 1건 이상을 강제)" } + +effects: + - admin_list_loads_only_representative_option + - admin_list_option_query_count_is_constant + # 분할배송도 옵션과 같은 함정(배치 전체 limit)이라 firstShipping 관계로 풀었다. + # 배송 축을 선언하지 않으면 매니페스트만 읽는 사람에게 이 축이 검증 범위 밖으로 보인다. + - admin_list_loads_only_representative_shipping + - admin_list_shipping_query_count_is_constant + - options_count_includes_cancelled + - options_count_is_zero_not_missing_when_empty + - partially_cancelled_true_only_when_some_remain + - fully_cancelled_is_not_partially_cancelled + - first_option_display_fields_unchanged + # 주문상품이 1건인 주문은 대표 1건 == 전량이라 기본 응답이 종전과 동일해야 한다. + # opt-in 축(when_requested)과 기본 축(default_is_representative_only)과는 다른 경계다. + - my_page_list_still_enumerates_every_item + - my_page_list_enumerates_every_item_when_requested + - my_page_list_default_is_representative_only + # 대표 1건 축약을 관계 재조회로 구현하면 페이로드를 줄이는 대신 쿼리가 행 수만큼 늘어난다. + # opt-in 파라미터는 해석 불가한 값을 null 로 정규화하지 않는다 — 오타가 "미지정" 으로 통과하면 + # 호출자는 전량을 요청했다고 믿은 채 대표 1건만 받는다. + - my_page_list_option_query_count_is_constant + - my_page_list_rejects_unparseable_with_items + - list_payload_has_no_empty_object_fields + +test_files: + - modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Admin/OrderListDeferredJoinTest.php + - modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/User/UserOrderControllerTest.php + +notes: | + E2E 미해당: 변경 범위가 백엔드 전용(Model 관계/Repository/Resource)이며 레이아웃 JSON 은 + 종전과 같은 필드(`row.first_option`, `row.options_count`, `row.is_partially_cancelled`)를 그대로 + 읽는다. 브라우저 경로에 새로 생긴 분기가 없다. diff --git a/modules/_bundled/sirsoft-ecommerce/tests/scenarios/cart-wishlist-image-column-pruning.yaml b/modules/_bundled/sirsoft-ecommerce/tests/scenarios/cart-wishlist-image-column-pruning.yaml new file mode 100644 index 00000000..576c9fea --- /dev/null +++ b/modules/_bundled/sirsoft-ecommerce/tests/scenarios/cart-wishlist-image-column-pruning.yaml @@ -0,0 +1,45 @@ +feature: 카트·찜목록의 상품 이미지 컬럼 한정 (#518 / 공개 #76) + +description: | + 카트·찜목록 조회가 `product.images` 를 전량 hydration 하고 있었다. 두 화면 모두 상품 + 썸네일 1장만 그리므로 응답에 배열이 직렬화되지도 않는데, DB 에서는 상품마다 이미지 전 행을 + 읽어 메모리에 올렸다. + + 관계를 빼는 대신 컬럼을 좁힌다(`images:id,product_id,hash,is_thumbnail,sort_order`). + 썸네일 URL 은 서버가 이 컬럼들로 조립하고, 대표 이미지가 지정되지 않은 상품은 첫 이미지로 + 폴백하므로 관계 자체를 `thumbnail` 단건으로 바꾸면 그 폴백이 깨진다. + + 찜목록은 반대로 eager load 를 **추가**했다. 미로드 상태에서 `getThumbnailUrl()` 이 행마다 + 관계를 최대 2회 재조회하고 있었기 때문이다. 두 화면 모두 응답 형태는 불변이고 쿼리·메모리만 + 줄어든다. + +# axis 의미: +# 무엇이 달라지는지를 가르는 축은 (화면) × (관측 지점) 이다. +# 응답 형태가 불변이라 "무엇이 빠졌는가" 로는 이 변경을 관측할 수 없다 — 조회 쪽(어떤 컬럼을 +# 몇 번 읽는가)과 소비 쪽(화면이 배열을 읽지 않는가)을 각각 봐야 한다. +# 대표 이미지 지정 상태(지정 / 미지정 폴백 / 없음)는 같은 경로의 픽스처 차이라 effects 로 둔다. +axes: + surface: [cart, wishlist] + observation: [query_shape, consumer_screen] + +effects: + - cart_and_wishlist_consume_thumbnail_only + - list_response_shape_is_unchanged + - thumbnail_query_count_does_not_grow_with_rows + - thumbnail_url_falls_back_to_first_image_when_undesignated + - image_columns_stay_sufficient_for_thumbnail_assembly + +test_files: + - modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Public/CartControllerTest.php + - modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Admin/AdjacentListThumbnailQueryTest.php + - modules/_bundled/sirsoft-ecommerce/resources/js/__tests__/layouts/cartWishlistImageConsumers.test.ts + +notes: | + `product.shippingPolicy.countrySettings` 는 이 축에서 좁히지 않는다. 배송비 계산기 + (`OrderCalculationService`)가 그 관계를 직접 읽고, 미로드 시 항목마다 재조회하므로 좁히면 + 페이로드 대신 쿼리가 늘어난다. 게다가 계산기가 `is_active` 를 거르지 않아 로드 경로에 따라 + 배송비가 달라질 수 있다 — 계산 경로를 함께 손대야 안전한 축이라 본 이슈에서 다루지 않는다 + (PO 결정, 2026-08-05). + + E2E 미해당: 응답 형태가 불변이라 브라우저 경로에 새 분기가 없다. 소비 사실 고정은 + 레이아웃 원문 검사(`cartWishlistImageConsumers.test.ts`)가 담당한다. diff --git a/modules/_bundled/sirsoft-ecommerce/tests/scenarios/ecommerce-notification-definition-channel-contract.yaml b/modules/_bundled/sirsoft-ecommerce/tests/scenarios/ecommerce-notification-definition-channel-contract.yaml new file mode 100644 index 00000000..d4225c00 --- /dev/null +++ b/modules/_bundled/sirsoft-ecommerce/tests/scenarios/ecommerce-notification-definition-channel-contract.yaml @@ -0,0 +1,45 @@ +feature: 커머스 알림 정의 탭의 채널 계약 (#518 / 공개 #76) + +description: | + 알림 정의 목록은 정의 하나당 (채널 × 로케일) 템플릿 본문을 전부 실었다. 화면은 한 번에 한 + 채널만 그리므로, 서버는 `template_channel` 로 지정된 채널의 템플릿만 싣도록 좁혔다. + + 그 좁힘은 **요청과 화면이 같은 채널을 볼 때만** 성립한다. 이 매니페스트가 고정하는 것은 + 두 종류의 어긋남이다. + + 1. 화면이 채널을 서버에 넘기지 않는 경우 — 응답에 `templates` 키 자체가 없어 전 행이 + 「이 채널에 대한 템플릿이 없습니다」가 된다. 제목·수신자·활성 토글·편집 모달 초기값이 + 통째로 빈다. `(def.templates ?? [])` 가드 뒤라 예외도 콘솔 경고도 없고 응답은 200, + 행 수도 정상이라 "템플릿을 아직 안 만들었나 보다" 로 읽힌다. + 2. 넘기는 채널과 화면이 찾는 채널이 다른 경우 — 서버는 A 채널을 실어 보내고 화면은 B 채널을 + 찾아 같은 증상이 난다. 채널 출처가 URL 하나여야 하는 이유다. + + 「기본값 복원」 노출은 채널을 가리지 않는 판정이다. templates 가 한 채널로 좁혀 실리므로 + 배열 순회로는 다른 채널의 커스터마이즈를 알 수 없다 — 서버 집계가 유일한 출처다. + +# axis 의미: +# 갈리는 입력은 (화면이 보는 채널) × (그 채널의 템플릿 커스터마이즈 상태) 다. +# 채널 전환 경로는 URL 만 바꾸므로 초기 진입과 같은 규칙이고, effects 로 검증한다. +axes: + channel: [mail, database] + template_state: [default, customized] + +effects: + - ecommerce_notification_channel_reads_single_source + - ecommerce_notification_list_requests_template_channel + - ecommerce_notification_reset_button_uses_server_aggregate + +test_files: + - modules/_bundled/sirsoft-ecommerce/resources/js/__tests__/layouts/admin-ecommerce-notification-channel-single-source.test.ts + - modules/_bundled/sirsoft-ecommerce/tests/Playwright/specs/admin/ecommerce-notification-definition-channel.spec.ts + +notes: | + 회귀 이력: 목록 프루닝 도입 시 코어 설정 화면만 `template_channel` 을 보내도록 고쳐지고 + 커머스 탭 데이터소스가 빠져, 10건 전 행이 빈 상태로 표시됐다 (2026-08-05 브라우저 실측). + + 그래서 단언은 소비와 전송을 **함께** 묶는다 — templates 를 읽는 표현식이 있는데 데이터소스가 + 채널을 넘기지 않으면 실패한다. 한쪽만 보면 "화면은 templates 를 안 쓴다" 는 잘못된 전제로 + 통과해 버린다. + + 활성 토글은 URL 을 `templates[...].id` 로 조립한다. 템플릿이 없으면 그 값이 `undefined` 가 + 되어 존재하지 않는 경로로 요청이 나가므로, 실제 템플릿 ID 로 나가는지까지 축에 포함한다. diff --git a/modules/_bundled/sirsoft-ecommerce/tests/scenarios/mp-product-list-lazy-options.yaml b/modules/_bundled/sirsoft-ecommerce/tests/scenarios/mp-product-list-lazy-options.yaml new file mode 100644 index 00000000..f2f1a4cd --- /dev/null +++ b/modules/_bundled/sirsoft-ecommerce/tests/scenarios/mp-product-list-lazy-options.yaml @@ -0,0 +1,67 @@ +feature: 관리자 상품목록 옵션 지연 로딩 (#518 / 공개 #76) + +description: | + 목록 한 페이지를 여는 것만으로 그 페이지 모든 상품의 옵션이 응답에 실리던 결함을 해소한다. + 목록은 요약(활성 옵션 수 / 전체 옵션 수 / 활성 옵션 재고 합계)만 내려주고, 옵션 상세는 행을 + 펼칠 때 배치 엔드포인트(`GET admin/products/options`)가 공급한다. + 하위호환을 위해 `?with_options=1` 로 종전 동작을 유지할 수 있다. + + 기능 축소가 없다는 것이 이 매트릭스의 핵심이다. 일괄 변경은 상품 ID 만으로도 서버가 그 상품의 + 모든 옵션(비활성 포함)으로 전개하므로, 펼치지 않은 상품에도 종전과 같은 범위로 적용된다. + 달라지는 것은 표시뿐이며, 확인 모달은 열거 대신 집계 엔트리로 같은 수치를 보여준다. + +# axis 의미: +# 이 기능이 실제로 갈리는 입력은 (행의 펼침 상태) × (그 상품의 옵션 구성) 두 축이다. +# 선택·일괄대상 조합은 mp12-bulk-update-selection.yaml 이 소유하고, 권한 스코프·목록 이동은 +# 행 상태와 교차하지 않으므로(어느 펼침 상태에서든 같은 규칙) effects 로 검증한다. +# 축을 늘려 1350 케이스를 만들면 전개는 되지만 어느 조합도 실제로 검증되지 않는다. +axes: + row_state: + - collapsed # 펼치지 않음 (options 미로드) + - expanding # 요청 진행 중 (스켈레톤) + - expanded # 로드 완료 + - re_expanded # 접었다 다시 펼침 (재요청 없어야 함) + - error # 로드 실패 (재시도 가능) + option_profile: + - none # 옵션 0건 + - active_only # 활성 옵션만 + - mixed # 활성 + 비활성 혼재 + +exclusions: + - row_state: [expanding, error] + option_profile: none + reason: | + 옵션 0건이면 전체 옵션 수가 0 이라 요청 자체가 발생하지 않는다(no_fetch_when_total_count_zero). + 진행 중·실패 상태가 성립할 수 없는 조합이다. + - row_state: re_expanded + option_profile: none + reason: 요청이 없었으므로 재펼침에서 확인할 캐시도 없다 (위와 같은 이유). + +effects: + - list_response_omits_options # 기본 목록 응답에 옵션 배열 부재 + - with_options_opt_in_restores # ?with_options=1 이면 종전대로 포함 + - options_total_count_matches_all # 전체 옵션 수는 비활성 포함 + - option_stock_sum_is_zero_not_null # 옵션 0건에서 재고 합계 0 (재고 배지 오탐 가드) + - aggregates_equal_php_computation # DB 집계 == 종전 PHP 컬렉션 연산 + - no_fetch_when_total_count_zero # 전체 옵션 수 0 이면 네트워크 미발생 + - no_refetch_on_re_expand # 이미 로드된 행은 재요청 없음 + - inflight_requests_merged # 중복 요청 병합 + - merge_reads_datasource_again # 병합 시 데이터소스 재조회 (대기 중 편집 보존) + - missing_ids_resolved_as_empty # 응답 누락 ID 도 빈 배열로 확정 + - scope_filtered_ids_absent # 권한 밖 상품 ID 는 맵/목록 양쪽에서 제외 + - selected_option_ids_hydrated # 체크된 상품의 옵션이 선택 상태에 합류 + - options_reloaded_after_bulk_save # 저장 후 리페치 → 펼친 행 옵션 재로드 + - aggregate_summary_equals_enumerated_summary # 전부 펼침 == 전부 미펼침 수치 + - bulk_applies_to_inactive_options # ids 만 보내도 비활성 포함 전 옵션 적용 + - query_count_constant_across_rows # 행 수가 늘어도 옵션/이미지 쿼리 수 불변 + - thumbnail_url_identical_after_refactor # 대표 이미지 선택 결과 불변 + - expanded_state_reset_on_list_change # 정렬·페이지·검색·프리셋 이동 시 펼침 상태 초기화 + +test_files: + - modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Admin/ProductListOptionsLazyTest.php + - modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Admin/ProductOptionsBatchFetchTest.php + - modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Admin/ProductControllerBulkUpdateTest.php + - modules/_bundled/sirsoft-ecommerce/resources/js/__tests__/handlers/expandedOptionsHandlers.test.ts + - modules/_bundled/sirsoft-ecommerce/resources/js/__tests__/handlers/bulkUpdateHandlers.test.ts + - modules/_bundled/sirsoft-ecommerce/resources/js/__tests__/layouts/productListOptionsLazy.test.ts + - modules/_bundled/sirsoft-ecommerce/tests/Playwright/specs/admin/product-list-lazy-options.spec.ts diff --git a/modules/_bundled/sirsoft-ecommerce/tests/scenarios/mp09-product-list-total.yaml b/modules/_bundled/sirsoft-ecommerce/tests/scenarios/mp09-product-list-total.yaml index 2f99a381..b02b9219 100644 --- a/modules/_bundled/sirsoft-ecommerce/tests/scenarios/mp09-product-list-total.yaml +++ b/modules/_bundled/sirsoft-ecommerce/tests/scenarios/mp09-product-list-total.yaml @@ -1,8 +1,8 @@ # audit:allow test-scenario-coverage reason: | -# cross product 자동 전개는 audit 실행 환경에 js-yaml 부재로 nested axes 를 읽지 못하는 -# 한계로 검출되지 않는다(동일 한계로 다른 번들 시나리오도 audit:allow). 본 변경은 -# ProductRepository/CouponRepository 의 보조필드 union 전환으로, PHPUnit Feature 가 -# search_field/매칭위치/동반필터 조합과 total==count 불변식을 커버하며 green. +# search_field × 매칭위치 × 동반필터 24 케이스는 docblock 마킹으로 열거되어 있지 않다 +# (A19① 소유, 이 매니페스트 작성 시점부터의 미결 상태). #518 은 이 매트릭스를 넓히지 않았고, +# 이번에 추가한 effect(search_branch_shares_lazy_options)만 ProductListOptionsLazyTest 에 +# 마킹해 실제로 검증한다. 기존 24 케이스 열거는 A19① 차원에서 해소할 항목이다. feature: 관리자 상품/쿠폰 목록 검색 시 total=0 오표시 수정 (A19① Scout total 정합) description: | @@ -25,6 +25,9 @@ axes: - none - sales_status # 검색어 + 판매상태 동시 +# 옵션 payload 형태(lazy / with_options)는 축으로 두지 않는다. 검색 분기와 교차해 결과가 갈리지 +# 않고, "검색 분기도 같은 지연 로딩 계약을 따른다" 는 것 자체가 검증 대상이라 effects 로 확인한다 (#518). + effects: - total_equals_count_for_aux_match # 보조필드 매칭 시 total==count (회귀 가드) - total_equals_count_for_name_match @@ -32,7 +35,9 @@ effects: - total_consistent_with_companion_filter - no_match_returns_zero_total # 매칭 없으면 total=0(정상) - coupon_creator_match_total_consistent + - search_branch_shares_lazy_options # 검색 분기도 옵션 집계/지연 로딩 계약을 따른다 (#518) test_files: - modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Admin/ProductControllerSearchTest.php - modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Admin/CouponListCreatorTest.php + - modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Admin/ProductListOptionsLazyTest.php diff --git a/modules/_bundled/sirsoft-ecommerce/tests/scenarios/mp12-bulk-update-selection.yaml b/modules/_bundled/sirsoft-ecommerce/tests/scenarios/mp12-bulk-update-selection.yaml index e0c5fa48..6664c49a 100644 --- a/modules/_bundled/sirsoft-ecommerce/tests/scenarios/mp12-bulk-update-selection.yaml +++ b/modules/_bundled/sirsoft-ecommerce/tests/scenarios/mp12-bulk-update-selection.yaml @@ -1,8 +1,10 @@ # audit:allow test-scenario-coverage reason: | -# cross product 자동 전개는 audit 실행 환경에 js-yaml 부재로 nested axes 를 읽지 못하는 -# 한계로 검출되지 않는다(동일 한계로 다른 번들 시나리오도 audit:allow). 본 변경(A27)은 -# selection × target × source cross product 를 bulkUpdateHandlers Vitest + -# 레이아웃 구조 회귀가 커버하며 green. +# selection × target × source 18 케이스는 docblock 마킹으로 열거되어 있지 않다 (MP12/A27 소유, +# 이 매니페스트 작성 시점부터의 미결 상태). #518 은 이 매트릭스를 넓히지 않았고, 이번에 추가한 +# effects 3종(aggregate_entry_for_unloaded_options / +# summary_count_matches_regardless_of_expansion / options_reloaded_after_save)만 +# bulkUpdateHandlers·expandedOptionsHandlers Vitest 에 마킹해 실제로 검증한다. +# 기존 18 케이스 열거는 MP12 차원에서 해소할 항목이다. feature: 일괄변경 전체선택 SSoT 통일 (MP12 / A27) description: | @@ -23,6 +25,10 @@ axes: - global_only # _global 만 (전체선택 경로) - both_global_priority # 둘 다 → global 우선 +# 펼침 여부(loaded / not_loaded / mixed)는 축으로 두지 않는다. 선택 소스(_local/_global) 결정과 +# 교차하지 않고, "펼침 여부와 무관하게 같은 수치·같은 적용 범위" 라는 것 자체가 검증 대상이라 +# effects 로 확인한다 (#518). 축으로 만들면 54 케이스가 되지만 어느 조합도 실제로 검증되지 않는다. + effects: - global_priority_in_confirm # buildConfirmData 가 _global 우선 - no_changes_hidden_on_all # 전체선택 시 no_changes 미노출 @@ -30,7 +36,13 @@ effects: - product_select_includes_options # 상품 체크 시 옵션 포함 - individual_still_works # 개별 선택 회귀 green - apply_sequence_order # setState(global)→build→openModal 순서 + - aggregate_entry_for_unloaded_options # 미펼침 상품은 집계 엔트리 1건으로 표현 (#518) + - summary_count_matches_regardless_of_expansion # 펼침 여부와 무관하게 optionCount 동일 (#518) + - options_reloaded_after_save # 저장 후 리페치 → 펼친 행 옵션 재로드 (#518) test_files: - modules/_bundled/sirsoft-ecommerce/resources/js/__tests__/handlers/bulkUpdateHandlers.test.ts - modules/_bundled/sirsoft-ecommerce/resources/js/__tests__/layouts/bulkConfirmModalBinding.test.tsx + - modules/_bundled/sirsoft-ecommerce/resources/js/__tests__/layouts/productListOptionsLazy.test.ts + - modules/_bundled/sirsoft-ecommerce/resources/js/__tests__/handlers/expandedOptionsHandlers.test.ts + - modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Admin/ProductControllerBulkUpdateTest.php diff --git a/modules/_bundled/sirsoft-ecommerce/tests/scenarios/review-list-image-payload-contract.yaml b/modules/_bundled/sirsoft-ecommerce/tests/scenarios/review-list-image-payload-contract.yaml new file mode 100644 index 00000000..1caadd73 --- /dev/null +++ b/modules/_bundled/sirsoft-ecommerce/tests/scenarios/review-list-image-payload-contract.yaml @@ -0,0 +1,50 @@ +feature: 리뷰 목록의 첨부 이미지 계약 고정 (#518 / 공개 #76) + +description: | + #518 원안은 리뷰 목록에서 `images` 를 빼고 대표 1장 + `image_count` 로 줄이려 했다. + 소비처를 재실측한 결과 그 판단이 빗나갔다 — 관리자·공개 두 목록 모두 첨부 이미지를 + 실제로 그린다. 착수하지 않는다 (PO 결정, 2026-08-05). + + 이 매니페스트는 "줄였다" 를 기록하는 것이 아니라 **줄일 수 없다는 사실을 고정**한다. + 제거 시 조용히 깨지기 때문이다: 썸네일이 `(review.images ?? []).length > 0` 가드 뒤에 + 있어 예외도 콘솔 경고도 나지 않고 자리만 비며, 바깥 컨테이너는 `image_count > 0` 으로 + 열려 빈 상자가 남는다. + + 공개 목록은 특히 위험하다. 참조가 `shop/show.json` 이 include 하는 파셜 + (`partials/shop/detail/_tab_reviews.json`) 안에 있어, 진입 레이아웃만 열면 소비 흔적이 + 보이지 않는다. 실제로 한 번 제거됐다가 회귀로 되돌린 이력이 있다. + + 대체 경로도 없다 — 공개 리뷰 단건 엔드포인트가 존재하지 않아 "갤러리는 상세에서" 가 + 성립하지 않는다. 줄이려면 상품 옵션(A축)처럼 펼칠 때 지연 로드하는 경로를 먼저 만들어야 + 하며, 그것은 별도 이슈다. + +# axis 의미: +# 계약이 깨지는 지점을 가르는 축은 (목록 화면) × (관측 지점) 이다. +# 응답만 봐도, 화면만 봐도 이 계약은 지켜지지 않는다 — 응답이 배열을 싣는다는 사실과 +# 화면이 그 배열을 그린다는 사실이 **동시에** 참이어야 "뺄 수 없다" 가 성립한다. +# 첨부 구성(0장 / 1장 / 3장 이상)은 같은 경로의 픽스처 차이라 effects 로 둔다. +axes: + surface: [public_product_review_list, admin_product_review_list] + observation: [response_payload, consumer_screen] + +effects: + - review_list_images_are_consumed_by_screen + - public_list_carries_images_and_count_together + - image_download_url_is_present_on_each_image + - image_count_is_zero_not_missing_when_empty + - no_public_single_review_endpoint_exists_as_fallback + - product_thumbnail_columns_stay_pruned_without_touching_review_images + +test_files: + - modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Public/PublicProductReviewControllerTest.php + - modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Admin/AdjacentListThumbnailQueryTest.php + - modules/_bundled/sirsoft-ecommerce/resources/js/__tests__/layouts/reviewImageConsumers.test.ts + +notes: | + 인접 개선은 유지된다 — 리뷰 목록이 상품 썸네일 산출용으로 읽던 `product.images` 는 + 컬럼을 한정해(`id,product_id,hash,is_thumbnail,sort_order`) `getThumbnailUrl()` 의 행당 + 재쿼리를 없앴다. 이것은 리뷰 첨부(`images`)와 다른 관계이며, 두 축을 혼동해 한쪽을 근거로 + 다른 쪽을 줄이지 말 것. + + E2E 미해당: 이 축은 응답·화면 모두 종전과 동일하게 유지한다는 계약이라 새 브라우저 경로가 + 없다. 소비 사실 고정은 레이아웃 원문 검사가, 응답 계약은 Feature 테스트가 담당한다. diff --git a/modules/_bundled/sirsoft-ecommerce/tests/scenarios/shipping-policy-list-country-setting-pruning.yaml b/modules/_bundled/sirsoft-ecommerce/tests/scenarios/shipping-policy-list-country-setting-pruning.yaml new file mode 100644 index 00000000..1cdc24dc --- /dev/null +++ b/modules/_bundled/sirsoft-ecommerce/tests/scenarios/shipping-policy-list-country-setting-pruning.yaml @@ -0,0 +1,54 @@ +feature: 배송정책 목록의 국가별 설정 필드 프루닝 (#518 / 공개 #76) + +description: | + 배송정책 목록은 정책마다 `countrySettings` 를 통째로 실었다. 정책 하나에 국가 설정이 + 최대 수백 건까지 달리고 각 행이 구간별 배송비 표(`ranges`)·도서산간 설정 + (`extra_fee_settings`)·계산 API 설정을 중첩 JSON 으로 물고 있어, 목록 한 페이지를 여는 + 것만으로 그 전부가 전송됐다. + + 관계 자체를 빼지는 않는다. 소비처 실측 결과 상품 등록/수정 화면의 배송정책 선택기가 + 국가 칩과 배송비 요약을 그 배열에서 직접 그리므로(`prunedListFieldConsumers.test.ts`), + 통째로 빼면 그 화면이 빈 채로 열린다. 대신 목록 전용 관계(`listCountrySettings`)로 + 컬럼을 한정해 화면이 그리는 값만 남기고 무거운 중첩 JSON 을 뺀다. + + 전체 설정이 필요한 외부 연동은 `?with_country_settings=1` 로 종전 페이로드를 받는다. + +# axis 의미: +# 무엇이 실리는지를 가르는 축은 (조회 경로) × (opt-in 여부) 다. +# 국가 설정 구성(활성만 / 비활성 혼재 / 0건)은 입력 축이 아니라 검증 항목이라 effects 로 둔다 — +# 같은 조회 경로에서 픽스처만 바꾸는 것이라 축으로 만들면 조합만 늘고 경로는 그대로다. +axes: + endpoint: [admin_list, active_list, detail] + opt_in: [default, with_country_settings] + +exclusions: + - { endpoint: detail, opt_in: with_country_settings, reason: "단건 조회는 언제나 전체 국가 설정을 싣는다 — opt-in 파라미터를 읽지 않으므로 두 값이 같은 경로로 수렴하는 중복 조합" } + - { endpoint: active_list, opt_in: with_country_settings, reason: "상품폼 전용 활성 목록은 폼이 그리는 필드가 고정이라 opt-in 을 노출하지 않는다" } + +effects: + - list_omits_nested_country_setting_blocks + - list_keeps_fields_the_screen_draws + - list_includes_inactive_country_settings + - summaries_count_only_active_settings_regardless_of_load_path + - opt_in_restores_full_country_settings + - opt_in_accepts_query_string_boolean_forms + - country_setting_queries_do_not_grow_with_rows + - detail_still_provides_full_country_settings + - product_form_selector_still_draws_country_chips + # 브라우저에서 국가 칩·배송비 요약 열이 실제로 채워지는지 — 컬럼 한정 관계로 바꾼 뒤에도 + # 화면이 빈칸이 되지 않는다는 것은 응답 단언만으로는 증명되지 않는다. + - shipping_policy_list_renders_country_summary + +test_files: + - modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Admin/ShippingPolicyListPayloadTest.php + - modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Admin/ShippingPolicyActiveListTest.php + - modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Admin/ShippingPolicyControllerTest.php + - modules/_bundled/sirsoft-ecommerce/resources/js/__tests__/layouts/prunedListFieldConsumers.test.ts + - modules/_bundled/sirsoft-ecommerce/tests/Playwright/specs/admin/shipping-policy-list-country-settings.spec.ts + +notes: | + 요약 문자열(`fee_summary` = "KR: 3000원 | US: $20", `countries_display` = 국기 이모지)은 + DB 집계로 대체하지 않는다. 국가코드·통화·금액을 조합한 표시 문자열이라 SQL 집계로 등가 + 산출이 불가능하고, 로드된 컬렉션에서 조립하는 편이 정확하다. 대신 활성 필터를 + `resolveActiveCountrySettings()` 한 곳으로 모아 목록/상세 어느 경로로 로드되든 같은 + 모집단을 세도록 고정했다(effect: summaries_count_only_active_settings_regardless_of_load_path). diff --git a/modules/_bundled/sirsoft-page/CHANGELOG.md b/modules/_bundled/sirsoft-page/CHANGELOG.md index a342c85a..7508bc3c 100644 --- a/modules/_bundled/sirsoft-page/CHANGELOG.md +++ b/modules/_bundled/sirsoft-page/CHANGELOG.md @@ -19,6 +19,7 @@ - 사이트맵을 만들 때 페이지를 한 번에 모두 메모리에 올리지 않고 나눠서 읽도록 바꿨습니다. 페이지가 많은 사이트에서 사이트맵 생성 중 메모리가 부족해 실패하던 문제가 줄어듭니다. - 페이지 목록을 뒤쪽 페이지에서도 빠르게 열 수 있도록 조회 방식을 바꿨습니다. 예전에는 페이지가 뒤로 갈수록 건너뛰는 항목의 본문까지 함께 읽어 느려졌지만, 이제 현재 페이지의 항목만 본문을 읽습니다. 화면에 보이는 내용은 이전과 동일합니다. (#74 @jiwonpapa 님께서 제보해주셨습니다.) +- 관리자 페이지 목록을 열 때 페이지 본문을 데이터베이스에서 읽지 않습니다. 목록은 제목·주소·발행 여부만 쓰는데 본문까지 함께 읽고 있었습니다. 본문이 긴 페이지가 많은 사이트일수록 목록을 여는 속도와 서버 메모리 사용이 개선되며, 화면에 보이는 내용은 이전과 동일합니다. (#76 @jordy-bitree 님께서 제보해주셨습니다.) - 개선된 사이트맵 생성 기능을 사용하기 위해 이 모듈은 이제 코어 7.0.6 이상이 필요합니다. - 첨부파일 저장 디스크 설정 항목을 제거했습니다. 설정 파일 자체가 그 디스크에 저장되는 구조라 값을 바꿔도 반영될 수 없는 항목이었습니다(기존에도 항상 기본값으로 동작). 저장 위치는 서버 파일 저장소 설정으로 관리합니다. (#81 @jiwonpapa 님께서 제보해주셨습니다.) diff --git a/modules/_bundled/sirsoft-page/src/Repositories/PageRepository.php b/modules/_bundled/sirsoft-page/src/Repositories/PageRepository.php index 399d72c5..699300d0 100644 --- a/modules/_bundled/sirsoft-page/src/Repositories/PageRepository.php +++ b/modules/_bundled/sirsoft-page/src/Repositories/PageRepository.php @@ -55,9 +55,15 @@ class PageRepository implements PageRepositoryInterface // 지연 조인: 본문 content(longText)는 목록에 쓰이지 않는데도 OFFSET 이 훑는 // 모든 행에서 함께 읽힌다. inner 는 id 만 훑고 본문은 이번 페이지에서만 읽는다. + // 목록이 실제로 쓰는 컬럼만 읽는다. 본문(`content`)은 longText 인데 목록 표현 + // (`PageResource::toListArray`)이 출력하지 않으므로, 이번 페이지 행에서도 읽을 이유가 없다. + // 응답 형태는 변하지 않고 DB/메모리 비용만 줄어든다. return $this->paginateWithDeferredJoin( query: $query, - columns: ['*'], + columns: [ + 'id', 'slug', 'title', 'published', 'published_at', 'current_version', + 'created_by', 'updated_by', 'created_at', 'updated_at', + ], sort: $this->resolveListSortSpec($filters), perPage: $perPage, relations: ['creator', 'updater'], diff --git a/modules/_bundled/sirsoft-page/tests/Feature/Admin/PageControllerTest.php b/modules/_bundled/sirsoft-page/tests/Feature/Admin/PageControllerTest.php index 125b6162..21ed966a 100644 --- a/modules/_bundled/sirsoft-page/tests/Feature/Admin/PageControllerTest.php +++ b/modules/_bundled/sirsoft-page/tests/Feature/Admin/PageControllerTest.php @@ -12,6 +12,7 @@ use App\Models\Permission; use App\Models\Role; use App\Models\User; use App\Seo\Contracts\SeoCacheManagerInterface; +use Illuminate\Support\Facades\DB; use Modules\Sirsoft\Page\Models\Page; use Modules\Sirsoft\Page\Models\PageAttachment; use Modules\Sirsoft\Page\Models\PageVersion; @@ -55,6 +56,107 @@ class PageControllerTest extends FeatureTestCase // ─── 목록 조회 (index) ───────────────────────────── + /** + * 목록 조회는 본문(content)을 DB 에서 읽지 않는다. + * + * 목록 표현(`PageResource::toListArray`)은 본문을 출력하지 않는데도 목록 SELECT 가 `*` 라 + * longText 본문까지 함께 읽혔다. 응답 형태는 그대로이고 읽는 컬럼만 줄어든다. + * + * @scenario endpoint=list,observation=executed_sql + * + * @effects list_query_omits_content_column, list_query_names_columns_explicitly, list_payload_shape_unchanged + */ + public function test_page_list_does_not_select_content_column(): void + { + Page::factory()->count(2)->create([ + 'created_by' => $this->adminUser->id, + 'updated_by' => $this->adminUser->id, + ]); + + $selects = []; + DB::listen(function ($query) use (&$selects) { + if (str_contains($query->sql, 'from `g7_pages`') && str_starts_with($query->sql, 'select')) { + $selects[] = $query->sql; + } + }); + + $response = $this->actingAs($this->adminUser) + ->getJson('/api/modules/sirsoft-page/admin/pages'); + + $response->assertStatus(200); + $this->assertNotEmpty($selects, '페이지 조회 쿼리가 있어야 한다'); + + foreach ($selects as $sql) { + $this->assertStringNotContainsString('`content`', $sql, '목록 조회가 본문 컬럼을 읽으면 안 된다'); + $this->assertStringNotContainsString('select *', $sql, '목록 조회는 컬럼을 명시해야 한다'); + } + + // 목록 화면이 쓰는 필드는 그대로 (기능 축소 아님) + $row = $response->json('data.data.0'); + foreach (['id', 'slug', 'title', 'published', 'current_version'] as $field) { + $this->assertArrayHasKey($field, $row, "목록이 쓰는 {$field} 가 사라졌다"); + } + $this->assertArrayNotHasKey('content', $row); + } + + /** + * 목록에서 뺀 본문은 단건 조회가 여전히 공급한다. + * + * @scenario endpoint=detail,observation=response_payload + * + * @effects detail_still_provides_content + */ + public function test_page_detail_still_provides_content(): void + { + $page = Page::factory()->create([ + 'created_by' => $this->adminUser->id, + 'updated_by' => $this->adminUser->id, + ]); + + $response = $this->actingAs($this->adminUser) + ->getJson("/api/modules/sirsoft-page/admin/pages/{$page->id}"); + + $response->assertStatus(200); + $this->assertArrayHasKey('content', $response->json('data')); + } + + /** + * 컬럼을 좁혀도 목록 응답의 형태는 달라지지 않는다. + * + * 이 항목은 응답이 바뀌지 않는 것이 정상이다. 그래서 "본문이 없다" 만 확인하면 컬럼을 과하게 + * 좁혀 다른 표시 필드까지 사라진 회귀를 놓친다 — 화면이 읽는 키 집합을 통째로 고정한다. + * + * @scenario endpoint=list,observation=response_payload + * + * @effects list_payload_shape_unchanged + */ + public function test_page_list_payload_shape_is_unchanged(): void + { + Page::factory()->create([ + 'created_by' => $this->adminUser->id, + 'updated_by' => $this->adminUser->id, + ]); + + $response = $this->actingAs($this->adminUser) + ->getJson('/api/modules/sirsoft-page/admin/pages'); + + $response->assertStatus(200); + + $row = $response->json('data.data.0'); + + foreach (['id', 'slug', 'title', 'published', 'current_version', 'created_at', 'updated_at'] as $field) { + $this->assertArrayHasKey($field, $row, "목록 응답에서 {$field} 가 사라졌다"); + } + + // 미충족 조건부 필드가 빈 객체로 남지 않는다 + foreach ($row as $key => $value) { + $this->assertFalse( + is_array($value) && $value === [] && in_array($key, ['created_at', 'updated_at'], true), + "조건부 필드가 빈 객체로 남았다: {$key}" + ); + } + } + /** * 페이지 목록을 조회할 수 있는지 확인 */ diff --git a/modules/_bundled/sirsoft-page/tests/ModuleTestCase.php b/modules/_bundled/sirsoft-page/tests/ModuleTestCase.php index 22da0770..807f3353 100644 --- a/modules/_bundled/sirsoft-page/tests/ModuleTestCase.php +++ b/modules/_bundled/sirsoft-page/tests/ModuleTestCase.php @@ -2,7 +2,11 @@ namespace Modules\Sirsoft\Page\Tests; +use App\Contracts\Extension\HookListenerInterface; use App\Enums\ExtensionStatus; +use App\Extension\HookListenerRegistrar; +use App\Extension\HookManager; +use App\Extension\ModuleManager; use App\Models\Module; use App\Models\Permission; use App\Models\Role; @@ -10,6 +14,7 @@ use App\Models\User; use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Schema; +use Modules\Sirsoft\Page\Providers\PageServiceProvider; use Tests\TestCase; /** @@ -59,7 +64,7 @@ abstract class ModuleTestCase extends TestCase $this->registerModuleInManager(); // 모듈 ServiceProvider 등록 (Repository 바인딩) - $this->app->register(\Modules\Sirsoft\Page\Providers\PageServiceProvider::class); + $this->app->register(PageServiceProvider::class); // 모듈 마이그레이션 실행 (pages 테이블 등) $this->runModuleMigrationIfNeeded(); @@ -86,13 +91,13 @@ abstract class ModuleTestCase extends TestCase $moduleClass = \Modules\Sirsoft\Page\Module::class; if (! class_exists($moduleClass)) { - require_once $this->getModuleBasePath() . '/module.php'; + require_once $this->getModuleBasePath().'/module.php'; } - $module = new $moduleClass(); + $module = new $moduleClass; - /** @var \App\Extension\ModuleManager $manager */ - $manager = $this->app->make(\App\Extension\ModuleManager::class); + /** @var ModuleManager $manager */ + $manager = $this->app->make(ModuleManager::class); $reflection = new \ReflectionClass($manager); $modulesProp = $reflection->getProperty('modules'); @@ -108,11 +113,11 @@ abstract class ModuleTestCase extends TestCase if (! class_exists($listenerClass)) { continue; } - if (! in_array(\App\Contracts\Extension\HookListenerInterface::class, class_implements($listenerClass), true)) { + if (! in_array(HookListenerInterface::class, class_implements($listenerClass), true)) { continue; } try { - \App\Extension\HookListenerRegistrar::register($listenerClass, 'sirsoft-page'); + HookListenerRegistrar::register($listenerClass, 'sirsoft-page'); } catch (\Throwable $e) { // 중복 등록 등 무해한 예외는 무시 } @@ -135,7 +140,7 @@ abstract class ModuleTestCase extends TestCase */ private function snapshotHookManager(): void { - $ref = new \ReflectionClass(\App\Extension\HookManager::class); + $ref = new \ReflectionClass(HookManager::class); $this->hookSnapshot = [ 'hooks' => $ref->getProperty('hooks')->getValue(), 'filters' => $ref->getProperty('filters')->getValue(), @@ -152,7 +157,7 @@ abstract class ModuleTestCase extends TestCase return; } - $ref = new \ReflectionClass(\App\Extension\HookManager::class); + $ref = new \ReflectionClass(HookManager::class); $ref->getProperty('hooks')->setValue(null, $this->hookSnapshot['hooks']); $ref->getProperty('filters')->setValue(null, $this->hookSnapshot['filters']); $ref->getProperty('dispatching')->setValue(null, $this->hookSnapshot['dispatching']); @@ -267,7 +272,7 @@ abstract class ModuleTestCase extends TestCase } $module = new $moduleClass; - $manager = $this->app->make(\App\Extension\ModuleManager::class); + $manager = $this->app->make(ModuleManager::class); $reflection = new \ReflectionClass($manager); $property = $reflection->getProperty('modules'); @@ -316,6 +321,13 @@ abstract class ModuleTestCase extends TestCase /** * 관리자 역할을 가진 사용자를 생성합니다. * + * 요청한 권한은 공용 `admin` 역할이 아니라 **이 사용자 전용 역할**에 부여한다. + * 공용 역할에 부여하면 같은 프로세스의 다른 테스트가 만든 관리자까지 그 권한을 + * 물려받는다. 첫 테스트의 `migrate:fresh` DDL 이 트랜잭션을 암묵 커밋시키므로 + * 그 테스트가 만든 역할-권한 연결은 롤백되지도 않는다. 결과적으로 "그 권한이 없는 + * 관리자" 를 전제로 한 검증이 단독 실행에서는 통과하고 다른 클래스와 함께 돌리면 + * 실패하는 순서 의존이 생긴다. + * * @param array $permissions 추가 권한 목록 * @return User */ @@ -326,6 +338,12 @@ abstract class ModuleTestCase extends TestCase $user->roles()->attach($adminRole->id); if (! empty($permissions)) { + $ownRole = Role::create([ + 'identifier' => 'test-admin-'.$user->id, + 'name' => ['ko' => '테스트 관리자 '.$user->id, 'en' => 'Test Admin '.$user->id], + ]); + $user->roles()->attach($ownRole->id); + foreach ($permissions as $permissionIdentifier) { $permission = Permission::firstOrCreate( ['identifier' => $permissionIdentifier], @@ -334,7 +352,7 @@ abstract class ModuleTestCase extends TestCase 'type' => 'admin', ] ); - $adminRole->permissions()->syncWithoutDetaching([$permission->id]); + $ownRole->permissions()->syncWithoutDetaching([$permission->id]); } } diff --git a/modules/_bundled/sirsoft-page/tests/scenarios/page-admin-list-column-pruning.yaml b/modules/_bundled/sirsoft-page/tests/scenarios/page-admin-list-column-pruning.yaml new file mode 100644 index 00000000..393ca468 --- /dev/null +++ b/modules/_bundled/sirsoft-page/tests/scenarios/page-admin-list-column-pruning.yaml @@ -0,0 +1,33 @@ +feature: 관리자 페이지 목록의 본문 컬럼 프루닝 (#518 / 공개 #76) + +description: | + 관리자 페이지 목록은 본문(`content`)을 출력하지 않는데도 조회가 `select *` 라 longText 본문을 + 행마다 읽었다. 응답에는 실리지 않으므로 화면은 그대로지만, DB 는 페이지 수만큼 오버플로 페이지를 + 읽고 그 결과가 PHP 메모리에 올라온다. + + 이 항목의 특징은 **응답 형태가 전혀 바뀌지 않는다**는 것이다. 그래서 "응답이 같다" 만 확인하면 + 아무것도 검증하지 않은 것과 같다 — 실제로 달라지는 것은 실행된 SQL 이므로 조회 컬럼을 직접 + 본다. + +# axis 의미: +# 달라지는 지점은 (응답 경로) × (관찰 대상) 이다. 응답 형태는 불변이므로 payload 만 보는 축은 +# 성립하지 않고, 실행 SQL 을 관찰 대상으로 함께 둔다. +axes: + endpoint: [list, detail] + observation: [response_payload, executed_sql] + +exclusions: + - { endpoint: detail, observation: executed_sql, reason: "단건 조회는 본문을 읽는 것이 정상 — 관찰 가치가 없다" } + +effects: + - list_query_omits_content_column + - list_query_names_columns_explicitly + - list_payload_shape_unchanged + - detail_still_provides_content + +test_files: + - modules/_bundled/sirsoft-page/tests/Feature/Admin/PageControllerTest.php + +notes: | + E2E 미해당: 응답 형태가 불변이고 레이아웃 JSON·컴포넌트 변경이 없다. 브라우저에서 관찰 가능한 + 차이가 존재하지 않으므로 spec 을 추가해도 검증하는 바가 없다. diff --git a/plugins/_bundled/sirsoft-message_bizppurio/resources/extensions/notification_tab_board.json b/plugins/_bundled/sirsoft-message_bizppurio/resources/extensions/notification_tab_board.json index a14f9948..a1c53c27 100644 --- a/plugins/_bundled/sirsoft-message_bizppurio/resources/extensions/notification_tab_board.json +++ b/plugins/_bundled/sirsoft-message_bizppurio/resources/extensions/notification_tab_board.json @@ -47,7 +47,7 @@ "id": "bizppurio_board_status_banner", "type": "basic", "name": "Div", - "if": "{{['sms','alimtalk'].includes(_local.activeNotificationChannel ?? query.channel ?? '') && ((availableChannels?.data?.channels ?? []).find(c => c.id === (_local.activeNotificationChannel ?? query.channel))?.readiness?.ready === false || (availableChannels?.data?.channels ?? []).find(c => c.id === (_local.activeNotificationChannel ?? query.channel))?.is_test_mode === true)}}", + "if": "{{['sms','alimtalk'].includes(query.channel ?? '') && ((availableChannels?.data?.channels ?? []).find(c => c.id === (query.channel))?.readiness?.ready === false || (availableChannels?.data?.channels ?? []).find(c => c.id === (query.channel))?.is_test_mode === true)}}", "props": { "className": "mt-4 mb-3 space-y-2" }, @@ -56,7 +56,7 @@ "id": "bizppurio_board_banner_not_ready", "type": "basic", "name": "Div", - "if": "{{(availableChannels?.data?.channels ?? []).find(c => c.id === (_local.activeNotificationChannel ?? query.channel))?.readiness?.ready === false}}", + "if": "{{(availableChannels?.data?.channels ?? []).find(c => c.id === (query.channel))?.readiness?.ready === false}}", "props": { "className": "flex-between gap-3 p-3 rounded-lg bg-red-50 dark:bg-red-900/30 border border-red-200 dark:border-red-700" }, @@ -111,7 +111,7 @@ "id": "bizppurio_board_banner_test_mode", "type": "basic", "name": "Div", - "if": "{{(availableChannels?.data?.channels ?? []).find(c => c.id === (_local.activeNotificationChannel ?? query.channel))?.is_test_mode === true}}", + "if": "{{(availableChannels?.data?.channels ?? []).find(c => c.id === (query.channel))?.is_test_mode === true}}", "props": { "className": "flex-center gap-2 p-3 rounded-lg bg-amber-50 dark:bg-amber-900/30 border border-amber-200 dark:border-amber-700" }, @@ -142,7 +142,7 @@ "comment": "알림톡 탭 상시 안내 박스 — 이 화면에서 무엇을 하는지(각 알림에 승인 템플릿을 연결). alimtalk 탭일 때만. 배너(빨강/노랑)와 구분되게 연한 파랑 정보 박스.", "type": "basic", "name": "Div", - "if": "{{(_local.activeNotificationChannel ?? query.channel) === 'alimtalk'}}", + "if": "{{(query.channel) === 'alimtalk'}}", "props": { "className": "flex items-center gap-2 mt-3 mb-1 p-3 rounded-lg bg-blue-50 dark:bg-blue-950 border border-blue-200 dark:border-blue-800" }, diff --git a/plugins/_bundled/sirsoft-message_bizppurio/resources/extensions/notification_tab_core.json b/plugins/_bundled/sirsoft-message_bizppurio/resources/extensions/notification_tab_core.json index a8b4fc52..56e5356c 100644 --- a/plugins/_bundled/sirsoft-message_bizppurio/resources/extensions/notification_tab_core.json +++ b/plugins/_bundled/sirsoft-message_bizppurio/resources/extensions/notification_tab_core.json @@ -47,7 +47,7 @@ "id": "bizppurio_status_banner", "type": "basic", "name": "Div", - "if": "{{['sms','alimtalk'].includes(_local.activeNotificationChannel ?? query.channel ?? '') && ((availableChannels?.data?.channels ?? []).find(c => c.id === (_local.activeNotificationChannel ?? query.channel))?.readiness?.ready === false || (availableChannels?.data?.channels ?? []).find(c => c.id === (_local.activeNotificationChannel ?? query.channel))?.is_test_mode === true)}}", + "if": "{{['sms','alimtalk'].includes(query.channel ?? '') && ((availableChannels?.data?.channels ?? []).find(c => c.id === (query.channel))?.readiness?.ready === false || (availableChannels?.data?.channels ?? []).find(c => c.id === (query.channel))?.is_test_mode === true)}}", "props": { "className": "mt-4 mb-3 space-y-2" }, @@ -56,7 +56,7 @@ "id": "bizppurio_banner_not_ready", "type": "basic", "name": "Div", - "if": "{{(availableChannels?.data?.channels ?? []).find(c => c.id === (_local.activeNotificationChannel ?? query.channel))?.readiness?.ready === false}}", + "if": "{{(availableChannels?.data?.channels ?? []).find(c => c.id === (query.channel))?.readiness?.ready === false}}", "props": { "className": "flex-between gap-3 p-3 rounded-lg bg-red-50 dark:bg-red-900/30 border border-red-200 dark:border-red-700" }, @@ -111,7 +111,7 @@ "id": "bizppurio_banner_test_mode", "type": "basic", "name": "Div", - "if": "{{(availableChannels?.data?.channels ?? []).find(c => c.id === (_local.activeNotificationChannel ?? query.channel))?.is_test_mode === true}}", + "if": "{{(availableChannels?.data?.channels ?? []).find(c => c.id === (query.channel))?.is_test_mode === true}}", "props": { "className": "flex-center gap-2 p-3 rounded-lg bg-amber-50 dark:bg-amber-900/30 border border-amber-200 dark:border-amber-700" }, @@ -142,7 +142,7 @@ "comment": "알림톡 탭 상시 안내 박스 — 이 화면에서 무엇을 하는지(각 알림에 승인 템플릿을 연결). alimtalk 탭일 때만. 배너(빨강/노랑)와 구분되게 연한 파랑 정보 박스.", "type": "basic", "name": "Div", - "if": "{{(_local.activeNotificationChannel ?? query.channel) === 'alimtalk'}}", + "if": "{{(query.channel) === 'alimtalk'}}", "props": { "className": "flex items-center gap-2 mt-3 mb-1 p-3 rounded-lg bg-blue-50 dark:bg-blue-950 border border-blue-200 dark:border-blue-800" }, diff --git a/plugins/_bundled/sirsoft-message_bizppurio/resources/extensions/notification_tab_ecommerce.json b/plugins/_bundled/sirsoft-message_bizppurio/resources/extensions/notification_tab_ecommerce.json index f2699a76..de4b21c9 100644 --- a/plugins/_bundled/sirsoft-message_bizppurio/resources/extensions/notification_tab_ecommerce.json +++ b/plugins/_bundled/sirsoft-message_bizppurio/resources/extensions/notification_tab_ecommerce.json @@ -47,7 +47,7 @@ "id": "bizppurio_ecommerce_status_banner", "type": "basic", "name": "Div", - "if": "{{['sms','alimtalk'].includes(_local.activeNotificationChannel ?? query.channel ?? '') && ((availableChannels?.data?.channels ?? []).find(c => c.id === (_local.activeNotificationChannel ?? query.channel))?.readiness?.ready === false || (availableChannels?.data?.channels ?? []).find(c => c.id === (_local.activeNotificationChannel ?? query.channel))?.is_test_mode === true)}}", + "if": "{{['sms','alimtalk'].includes(query.channel ?? '') && ((availableChannels?.data?.channels ?? []).find(c => c.id === (query.channel))?.readiness?.ready === false || (availableChannels?.data?.channels ?? []).find(c => c.id === (query.channel))?.is_test_mode === true)}}", "props": { "className": "mt-4 mb-3 space-y-2" }, @@ -56,7 +56,7 @@ "id": "bizppurio_ecommerce_banner_not_ready", "type": "basic", "name": "Div", - "if": "{{(availableChannels?.data?.channels ?? []).find(c => c.id === (_local.activeNotificationChannel ?? query.channel))?.readiness?.ready === false}}", + "if": "{{(availableChannels?.data?.channels ?? []).find(c => c.id === (query.channel))?.readiness?.ready === false}}", "props": { "className": "flex-between gap-3 p-3 rounded-lg bg-red-50 dark:bg-red-900/30 border border-red-200 dark:border-red-700" }, @@ -111,7 +111,7 @@ "id": "bizppurio_ecommerce_banner_test_mode", "type": "basic", "name": "Div", - "if": "{{(availableChannels?.data?.channels ?? []).find(c => c.id === (_local.activeNotificationChannel ?? query.channel))?.is_test_mode === true}}", + "if": "{{(availableChannels?.data?.channels ?? []).find(c => c.id === (query.channel))?.is_test_mode === true}}", "props": { "className": "flex-center gap-2 p-3 rounded-lg bg-amber-50 dark:bg-amber-900/30 border border-amber-200 dark:border-amber-700" }, @@ -142,7 +142,7 @@ "comment": "알림톡 탭 상시 안내 박스 — 이 화면에서 무엇을 하는지(각 알림에 승인 템플릿을 연결). alimtalk 탭일 때만. 배너(빨강/노랑)와 구분되게 연한 파랑 정보 박스.", "type": "basic", "name": "Div", - "if": "{{(_local.activeNotificationChannel ?? query.channel) === 'alimtalk'}}", + "if": "{{(query.channel) === 'alimtalk'}}", "props": { "className": "flex items-center gap-2 mt-3 mb-1 p-3 rounded-lg bg-blue-50 dark:bg-blue-950 border border-blue-200 dark:border-blue-800" }, diff --git a/plugins/_bundled/sirsoft-pay_kginicis/CHANGELOG.md b/plugins/_bundled/sirsoft-pay_kginicis/CHANGELOG.md index 5ae0434f..5b8d22d1 100644 --- a/plugins/_bundled/sirsoft-pay_kginicis/CHANGELOG.md +++ b/plugins/_bundled/sirsoft-pay_kginicis/CHANGELOG.md @@ -8,6 +8,7 @@ ### Fixed +- 이 플러그인이 설치된 사이트에서 관리자 주문 목록의 '일부취소' 표시와 적립예정 포인트·마일리지 사용 금액, 주문상품의 추가옵션 요약이 보이지 않던 문제를 수정했습니다. 결제 정보 칸에 테스트결제 표시를 덧붙이는 과정에서 목록 표 구성 전체를 옛 버전으로 되돌려 놓고 있었습니다. - 관리자 결제수단 설정에서 가상계좌·계좌이체·휴대폰결제에 PG사를 지정할 수 없던 문제를 수정했습니다. 해당 결제수단의 PG사 목록이 항상 비어 있어 기본 PG사만 사용할 수 있었고, 결제수단별로 다른 PG사를 지정하는 설정이 화면에서 불가능했습니다. - 카드결제를 거치지 않은 주문(무통장입금 등)의 완료 화면에서도 영수증 조회를 시도해 불필요한 요청이 나가던 문제를 수정했습니다. 화면에는 영향이 없었으나 이제 카드결제 주문에서만 조회합니다. (#81 @jiwonpapa 님께서 제보해주셨습니다.) - PC 결제창 요청의 최소 결제금액이 100원으로 제한되어, 모바일에서는 성립하는 소액 결제가 PC 에서만 거부되던 문제를 수정했습니다. 이제 PC·모바일이 동일한 기준을 사용합니다. (#81 @jiwonpapa 님께서 제보해주셨습니다.) diff --git a/plugins/_bundled/sirsoft-pay_kginicis/resources/extensions/admin_order_list_test_badge.json b/plugins/_bundled/sirsoft-pay_kginicis/resources/extensions/admin_order_list_test_badge.json index 56be6b1c..4500971f 100644 --- a/plugins/_bundled/sirsoft-pay_kginicis/resources/extensions/admin_order_list_test_badge.json +++ b/plugins/_bundled/sirsoft-pay_kginicis/resources/extensions/admin_order_list_test_badge.json @@ -26,7 +26,11 @@ "sortable": false, "required": false, "cellChildren": [ - { "type": "basic", "name": "Span", "text": "{{row.no}}" } + { + "type": "basic", + "name": "Span", + "text": "{{row.no}}" + } ] }, { @@ -36,7 +40,11 @@ "sortable": true, "required": true, "cellChildren": [ - { "type": "basic", "name": "Span", "text": "{{row.ordered_at_formatted}}" } + { + "type": "basic", + "name": "Span", + "text": "{{row.ordered_at_formatted}}" + } ] }, { @@ -49,10 +57,20 @@ { "type": "basic", "name": "Span", - "props": { "className": "font-medium text-blue-600 dark:text-blue-400 cursor-pointer hover:underline" }, + "props": { + "className": "font-medium text-blue-600 dark:text-blue-400 cursor-pointer hover:underline" + }, "text": "{{row.order_number}}", "actions": [ - { "type": "click", "handler": "navigate", "params": { "path": "/admin/ecommerce/orders/{{row.order_number}}", "mergeQuery": true, "query": {} } } + { + "type": "click", + "handler": "navigate", + "params": { + "path": "/admin/ecommerce/orders/{{row.order_number}}", + "mergeQuery": true, + "query": {} + } + } ] } ] @@ -67,30 +85,81 @@ { "type": "basic", "name": "Div", - "props": { "className": "flex items-start gap-2" }, + "props": { + "className": "flex items-start gap-2" + }, "children": [ { "type": "basic", "name": "Img", - "props": { "src": "{{row.first_option?.thumbnail_url}}", "alt": "{{row.first_option?.product_name}}", "className": "w-10 h-10 rounded object-cover flex-shrink-0 bg-gray-100 dark:bg-gray-700" }, + "props": { + "src": "{{row.first_option?.thumbnail_url}}", + "alt": "{{row.first_option?.product_name}}", + "className": "w-10 h-10 rounded object-cover flex-shrink-0 bg-gray-100 dark:bg-gray-700" + }, "if": "{{row.first_option?.thumbnail_url}}" }, { "type": "basic", "name": "Div", - "props": { "className": "space-y-0.5 min-w-0" }, + "props": { + "className": "space-y-0.5 min-w-0" + }, "children": [ { "type": "basic", "name": "Div", - "props": { "className": "font-medium" }, + "props": { + "className": "font-medium" + }, "children": [ - { "type": "basic", "name": "A", "props": { "className": "hover:underline hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-400 cursor-pointer", "href": "{{'\/admin\/ecommerce\/products\/' + row.first_option?.product_code + '\/edit'}}", "target": "_blank" }, "text": "{{row.first_option?.product_name}}", "if": "{{row.first_option?.product_code}}" }, - { "type": "basic", "name": "Span", "text": "{{row.first_option?.product_name}}", "if": "{{!row.first_option?.product_code}}" }, - { "type": "basic", "name": "Span", "props": { "className": "text-gray-500 dark:text-gray-400 font-normal ml-1" }, "text": "$t:defer:sirsoft-ecommerce.admin.order.product.more|count={{row.options_count - 1}}", "if": "{{row.options_count > 1}}" } + { + "type": "basic", + "name": "A", + "props": { + "className": "hover:underline hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-400 cursor-pointer", + "href": "{{'/admin/ecommerce/products/' + row.first_option?.product_code + '/edit'}}", + "target": "_blank" + }, + "text": "{{row.first_option?.product_name}}", + "if": "{{row.first_option?.product_code}}" + }, + { + "type": "basic", + "name": "Span", + "text": "{{row.first_option?.product_name}}", + "if": "{{!row.first_option?.product_code}}" + }, + { + "type": "basic", + "name": "Span", + "props": { + "className": "text-gray-500 dark:text-gray-400 font-normal ml-1" + }, + "text": "$t:defer:sirsoft-ecommerce.admin.order.product.more|count={{row.options_count - 1}}", + "if": "{{row.options_count > 1}}" + } ] }, - { "type": "basic", "name": "Div", "props": { "className": "text-tertiary" }, "text": "{{row.first_option?.product_option_name}}", "if": "{{row.first_option?.product_option_name}}" } + { + "type": "basic", + "name": "Div", + "props": { + "className": "text-tertiary" + }, + "text": "{{row.first_option?.product_option_name}}", + "if": "{{row.first_option?.product_option_name}}" + }, + { + "comment": "추가옵션 요약 — 첫 1건(직접입력 병기) + 외 N건 (Q-E2)", + "type": "basic", + "name": "Div", + "props": { + "className": "text-tertiary text-xs" + }, + "text": "{{row.first_option?.additional_options_summary?.label}}{{(row.first_option?.additional_options_summary?.extra_count ?? 0) > 0 ? ' 외 ' + row.first_option.additional_options_summary.extra_count + '건' : ''}}", + "if": "{{row.first_option?.additional_options_summary}}" + } ] } ] @@ -107,12 +176,63 @@ { "type": "basic", "name": "Div", - "props": { "className": "row-stack" }, + "props": { + "className": "stack-flush gap-0.5" + }, "children": [ - { "type": "basic", "name": "Span", "props": { "className": "font-medium" }, "text": "{{row.total_amount_formatted}}" }, - { "type": "basic", "name": "Span", "props": { "className": "text-xs text-gray-400 dark:text-gray-500" }, "text": "$t:defer:sirsoft-ecommerce.admin.order.column.shipping_fee_short|amount={{row.total_shipping_amount_formatted}}", "if": "{{row.total_shipping_amount > 0}}" }, - { "type": "basic", "name": "Span", "props": { "className": "text-xs text-green-600 dark:text-green-400" }, "text": "$t:defer:sirsoft-ecommerce.admin.order.column.paid_short|amount={{row.total_paid_amount_formatted}}", "if": "{{row.total_paid_amount > 0}}" }, - { "type": "basic", "name": "Span", "props": { "className": "text-xs text-red-600 dark:text-red-400" }, "text": "$t:defer:sirsoft-ecommerce.admin.order.column.unpaid_short|amount={{row.total_unpaid_amount_formatted}}", "if": "{{row.total_unpaid_amount > 0}}" } + { + "type": "basic", + "name": "Span", + "props": { + "className": "font-medium" + }, + "text": "{{row.total_amount_formatted}}" + }, + { + "type": "basic", + "name": "Span", + "props": { + "className": "text-xs text-gray-400 dark:text-gray-500" + }, + "text": "$t:defer:sirsoft-ecommerce.admin.order.column.shipping_fee_short|amount={{row.total_shipping_amount_formatted}}", + "if": "{{row.total_shipping_amount > 0}}" + }, + { + "type": "basic", + "name": "Span", + "props": { + "className": "text-xs text-green-600 dark:text-green-400" + }, + "text": "$t:defer:sirsoft-ecommerce.admin.order.column.paid_short|amount={{row.total_paid_amount_formatted}}", + "if": "{{row.total_paid_amount > 0}}" + }, + { + "type": "basic", + "name": "Span", + "props": { + "className": "text-xs text-red-600 dark:text-red-400" + }, + "text": "$t:defer:sirsoft-ecommerce.admin.order.column.unpaid_short|amount={{row.total_unpaid_amount_formatted}}", + "if": "{{row.total_unpaid_amount > 0}}" + }, + { + "type": "basic", + "name": "Span", + "props": { + "className": "text-xs text-purple-600 dark:text-purple-400" + }, + "text": "$t:defer:sirsoft-ecommerce.admin.order.column.mileage_used_short|amount={{row.total_points_used_amount_formatted}}", + "if": "{{row.total_points_used_amount > 0}}" + }, + { + "type": "basic", + "name": "Span", + "props": { + "className": "text-xs text-blue-600 dark:text-blue-400" + }, + "text": "$t:defer:sirsoft-ecommerce.admin.order.column.points_earned_short|amount={{row.total_earned_points_amount_formatted}}", + "if": "{{row.total_earned_points_amount > 0 && row.order_status !== 'cancelled' && !row.is_partially_cancelled}}" + } ] } ] @@ -124,7 +244,35 @@ "sortable": false, "required": false, "cellChildren": [ - { "type": "composite", "name": "StatusBadge", "props": { "status": "{{row.order_status_variant || 'default'}}", "label": "{{row.order_status_label}}", "showIcon": false } } + { + "comment": "주문상태 배지 묶음 — 상태 배지 아래에 부분취소 보조 배지를 세로로 배치(두 줄)", + "type": "basic", + "name": "Div", + "props": { + "className": "flex flex-col items-start gap-1" + }, + "children": [ + { + "type": "composite", + "name": "StatusBadge", + "props": { + "status": "{{row.order_status_variant || 'default'}}", + "label": "{{row.order_status_label}}", + "showIcon": false + } + }, + { + "comment": "부분취소 보조 배지 — 일부 옵션만 취소된 주문(별도 주문상태 아님, 파생 플래그)", + "type": "basic", + "name": "Span", + "if": "{{row.is_partially_cancelled}}", + "props": { + "className": "inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium whitespace-nowrap bg-orange-100 text-orange-800 dark:bg-orange-900/30 dark:text-orange-400" + }, + "text": "$t:sirsoft-ecommerce.admin.order.detail.partial_cancelled_badge" + } + ] + } ] }, { @@ -134,7 +282,15 @@ "sortable": false, "required": false, "cellChildren": [ - { "type": "basic", "name": "Span", "props": { "className": "text-gray-500 dark:text-gray-400" }, "text": "-", "if": "{{!row.address?.orderer_name && !row.user?.name}}" }, + { + "type": "basic", + "name": "Span", + "props": { + "className": "text-gray-500 dark:text-gray-400" + }, + "text": "-", + "if": "{{!row.address?.orderer_name && !row.user?.name}}" + }, { "type": "composite", "name": "ActionMenu", @@ -142,26 +298,78 @@ "props": { "position": "right", "items": [ - { "id": "view_member", "label": "$t:sirsoft-ecommerce.admin.order.action.view_member", "iconName": "user", "if": "{{!!row.user?.uuid}}" }, - { "id": "search_by_orderer_member", "label": "$t:sirsoft-ecommerce.admin.order.action.search_by_orderer", "iconName": "magnifying-glass", "if": "{{!!row.user?.uuid}}" }, - { "id": "search_by_orderer_guest", "label": "$t:sirsoft-ecommerce.admin.order.action.search_by_orderer", "iconName": "magnifying-glass", "if": "{{!row.user?.uuid}}" } + { + "id": "view_member", + "label": "$t:sirsoft-ecommerce.admin.order.action.view_member", + "iconName": "user", + "if": "{{!!row.user?.uuid}}" + }, + { + "id": "search_by_orderer_member", + "label": "$t:sirsoft-ecommerce.admin.order.action.search_by_orderer", + "iconName": "magnifying-glass", + "if": "{{!!row.user?.uuid}}" + }, + { + "id": "search_by_orderer_guest", + "label": "$t:sirsoft-ecommerce.admin.order.action.search_by_orderer", + "iconName": "magnifying-glass", + "if": "{{!row.user?.uuid}}" + } ] }, "children": [ { "type": "basic", "name": "Div", - "props": { "className": "flex-center gap-2 hover:bg-gray-50 dark:bg-gray-800 dark:hover:bg-gray-700 rounded-lg px-1 py-0.5 transition-colors" }, + "props": { + "className": "flex-center gap-2 hover:bg-gray-50 dark:bg-gray-800 dark:hover:bg-gray-700 rounded-lg px-1 py-0.5 transition-colors" + }, "children": [ - { "type": "basic", "name": "Icon", "if": "{{!!row.user?.uuid}}", "props": { "name": "fa-regular fa-user", "className": "text-gray-400 dark:text-gray-500 flex-shrink-0" } }, + { + "type": "basic", + "name": "Icon", + "if": "{{!!row.user?.uuid}}", + "props": { + "name": "fa-regular fa-user", + "className": "text-gray-400 dark:text-gray-500 flex-shrink-0" + } + }, { "type": "basic", "name": "Div", - "props": { "className": "min-w-0 flex-1" }, + "props": { + "className": "min-w-0 flex-1" + }, "children": [ - { "type": "basic", "name": "Span", "if": "{{!!row.user?.uuid}}", "props": { "className": "font-medium text-gray-900 dark:text-gray-100 truncate block" }, "text": "{{row.address?.orderer_name || row.user?.name}}" }, - { "type": "basic", "name": "Span", "if": "{{!row.user?.uuid}}", "props": { "className": "text-gray-600 dark:text-gray-400 truncate block" }, "text": "{{row.address?.orderer_name || row.user?.name}}" }, - { "type": "basic", "name": "Span", "comment": "첫구매 표시", "if": "{{row.is_first_order}}", "props": { "className": "text-xs text-emerald-500 dark:text-emerald-400" }, "text": "$t:sirsoft-ecommerce.admin.order.column.first_order" } + { + "type": "basic", + "name": "Span", + "if": "{{!!row.user?.uuid}}", + "props": { + "className": "font-medium text-gray-900 dark:text-gray-100 truncate block" + }, + "text": "{{row.address?.orderer_name || row.user?.name}}" + }, + { + "type": "basic", + "name": "Span", + "if": "{{!row.user?.uuid}}", + "props": { + "className": "text-gray-600 dark:text-gray-400 truncate block" + }, + "text": "{{row.address?.orderer_name || row.user?.name}}" + }, + { + "type": "basic", + "name": "Span", + "comment": "첫구매 표시", + "if": "{{row.is_first_order}}", + "props": { + "className": "text-xs text-emerald-500 dark:text-emerald-400" + }, + "text": "$t:sirsoft-ecommerce.admin.order.column.first_order" + } ] } ] @@ -173,19 +381,71 @@ "handler": "switch", "value": "{{$args[0]}}", "cases": { - "view_member": { "handler": "openWindow", "params": { "path": "/admin/users/{{row.user?.uuid}}" } }, + "view_member": { + "handler": "openWindow", + "params": { + "path": "/admin/users/{{row.user?.uuid}}" + } + }, "search_by_orderer_member": { "handler": "sequence", "actions": [ - { "handler": "setState", "params": { "target": "_local", "filter.ordererUuid": "{{row.user?.uuid}}", "filter.searchField": "all", "filter.searchKeyword": "", "ordererSearchResults": "{{[{ uuid: row.user?.uuid, name: row.user?.name || row.address?.orderer_name, email: row.user?.email || '' }]}}" } }, - { "comment": "이 주문자의 주문만 보기 — 앞선 setState 가 검색어를 비우므로 URL 의 검색 조건도 함께 비운다. 비우지 않으면 화면 입력창은 빈 채로 이전 검색어가 계속 적용된다 (#75).", "handler": "navigate", "params": { "path": "/admin/ecommerce/orders", "mergeQuery": true, "query": { "orderer_uuid": "{{row.user?.uuid}}", "search_field": "", "search_keyword": "", "page": 1 }, "replace": true, "transition_overlay_target": "order_datagrid__body" } } + { + "handler": "setState", + "params": { + "target": "_local", + "filter.ordererUuid": "{{row.user?.uuid}}", + "filter.searchField": "all", + "filter.searchKeyword": "", + "ordererSearchResults": "{{[{ uuid: row.user?.uuid, name: row.user?.name || row.address?.orderer_name, email: row.user?.email || '' }]}}" + } + }, + { + "comment": "이 주문자의 주문만 보기 — 앞선 setState 가 검색어를 비우므로 URL 의 검색 조건도 함께 비운다. 비우지 않으면 화면 입력창은 빈 채로 이전 검색어가 계속 적용된다 (#75).", + "handler": "navigate", + "params": { + "path": "/admin/ecommerce/orders", + "mergeQuery": true, + "query": { + "orderer_uuid": "{{row.user?.uuid}}", + "search_field": "", + "search_keyword": "", + "page": 1 + }, + "replace": true, + "transition_overlay_target": "order_datagrid__body" + } + } ] }, "search_by_orderer_guest": { "handler": "sequence", "actions": [ - { "handler": "setState", "params": { "target": "_local", "filter.searchField": "orderer_name", "filter.searchKeyword": "{{row.address?.orderer_name || row.user?.name}}", "filter.ordererUuid": "" } }, - { "comment": "비회원 주문자명으로 검색 — 회원 지정 필터(orderer_uuid)를 함께 비우지 않으면 이름 검색과 회원 필터가 겹쳐 결과가 비어 보인다 (#75).", "handler": "navigate", "params": { "path": "/admin/ecommerce/orders", "mergeQuery": true, "query": { "search_field": "orderer_name", "search_keyword": "{{row.address?.orderer_name || row.user?.name}}", "orderer_uuid": "", "page": 1 }, "replace": true, "transition_overlay_target": "order_datagrid__body" } } + { + "handler": "setState", + "params": { + "target": "_local", + "filter.searchField": "orderer_name", + "filter.searchKeyword": "{{row.address?.orderer_name || row.user?.name}}", + "filter.ordererUuid": "" + } + }, + { + "comment": "비회원 주문자명으로 검색 — 회원 지정 필터(orderer_uuid)를 함께 비우지 않으면 이름 검색과 회원 필터가 겹쳐 결과가 비어 보인다 (#75).", + "handler": "navigate", + "params": { + "path": "/admin/ecommerce/orders", + "mergeQuery": true, + "query": { + "search_field": "orderer_name", + "search_keyword": "{{row.address?.orderer_name || row.user?.name}}", + "orderer_uuid": "", + "page": 1 + }, + "replace": true, + "transition_overlay_target": "order_datagrid__body" + } + } ] } } @@ -204,7 +464,9 @@ { "type": "basic", "name": "Div", - "props": { "className": "row-stack" }, + "props": { + "className": "row-stack" + }, "children": [ { "type": "basic", @@ -215,7 +477,9 @@ "type": "basic", "name": "Span", "if": "{{(nicepay_test_map.data?.[row.order_number] === true) || (kginicis_test_map.data?.[row.order_number] === true) || (nhnkcp_test_map.data?.[row.order_number] === true)}}", - "props": { "className": "text-xs font-medium text-orange-500 dark:text-orange-400 leading-tight" }, + "props": { + "className": "text-xs font-medium text-orange-500 dark:text-orange-400 leading-tight" + }, "text": "($t:sirsoft-pay_kginicis.admin.test_mode_badge)" } ] @@ -229,7 +493,11 @@ "sortable": false, "required": false, "cellChildren": [ - { "type": "basic", "name": "Span", "text": "{{row.shipping?.shipping_type_label}}" } + { + "type": "basic", + "name": "Span", + "text": "{{row.shipping?.shipping_type_label}}" + } ] }, { @@ -242,10 +510,24 @@ { "type": "basic", "name": "Div", - "props": { "className": "flex-center gap-2" }, + "props": { + "className": "flex-center gap-2" + }, "children": [ - { "type": "basic", "name": "Span", "props": { "className": "fi fi-{{row.address?.recipient_country_code?.toLowerCase()}} flex-shrink-0" }, "if": "{{row.address?.recipient_country_code}}" }, - { "type": "basic", "name": "Span", "text": "{{$localized(row.address?.recipient_country_name) ?? row.address?.recipient_country_code}}", "if": "{{row.address?.recipient_country_code}}" } + { + "type": "basic", + "name": "Span", + "props": { + "className": "fi fi-{{row.address?.recipient_country_code?.toLowerCase()}} flex-shrink-0" + }, + "if": "{{row.address?.recipient_country_code}}" + }, + { + "type": "basic", + "name": "Span", + "text": "{{$localized(row.address?.recipient_country_name) ?? row.address?.recipient_country_code}}", + "if": "{{row.address?.recipient_country_code}}" + } ] } ] @@ -257,7 +539,11 @@ "sortable": false, "required": false, "cellChildren": [ - { "type": "basic", "name": "Span", "text": "{{row.order_device_label}}" } + { + "type": "basic", + "name": "Span", + "text": "{{row.order_device_label}}" + } ] } ] diff --git a/plugins/_bundled/sirsoft-pay_kginicis/tests/Unit/Extensions/AdminOrderListColumnSnapshotTest.php b/plugins/_bundled/sirsoft-pay_kginicis/tests/Unit/Extensions/AdminOrderListColumnSnapshotTest.php new file mode 100644 index 00000000..9af1fe1f --- /dev/null +++ b/plugins/_bundled/sirsoft-pay_kginicis/tests/Unit/Extensions/AdminOrderListColumnSnapshotTest.php @@ -0,0 +1,169 @@ +> 컬럼 목록 + */ + private function moduleColumns(): array + { + $path = $this->repoRoot().'/'.self::MODULE_LAYOUT_PATH; + + if (! is_file($path)) { + $this->markTestSkipped('이커머스 모듈 레이아웃을 찾을 수 없습니다: '.$path); + } + + $columns = $this->findDataGridColumns(json_decode(file_get_contents($path), true)); + + $this->assertNotNull($columns, '모듈 레이아웃에서 DataGrid 컬럼 정의를 찾지 못했습니다'); + + return $columns; + } + + /** + * 이 플러그인이 주입하는 컬럼 정의를 읽습니다. + * + * @return array> 컬럼 목록 + */ + private function pluginColumns(): array + { + $json = json_decode(file_get_contents($this->repoRoot().'/'.self::EXTENSION_PATH), true); + + foreach ($json['injections'] ?? [] as $injection) { + if (($injection['target_id'] ?? null) === 'order_datagrid') { + return $injection['props']['columns'] ?? []; + } + } + + $this->fail('order_datagrid 주입을 찾지 못했습니다'); + } + + /** + * 트리에서 DataGrid 의 columns 배열을 찾습니다. + * + * @param mixed $node 탐색 노드 + * @return array>|null 찾은 컬럼 목록 + */ + private function findDataGridColumns(mixed $node): ?array + { + if (! is_array($node)) { + return null; + } + + if (($node['name'] ?? null) === 'DataGrid' && isset($node['props']['columns'])) { + return $node['props']['columns']; + } + + foreach ($node as $child) { + $found = $this->findDataGridColumns($child); + + if ($found !== null) { + return $found; + } + } + + return null; + } + + /** + * 컬럼 목록에서 특정 field 의 정의를 찾습니다. + * + * @param array> $columns 컬럼 목록 + * @param string $field 찾을 field + * @return array|null 컬럼 정의 + */ + private function columnByField(array $columns, string $field): ?array + { + foreach ($columns as $column) { + if (($column['field'] ?? '') === $field) { + return $column; + } + } + + return null; + } + + public function test_column_set_matches_the_module_definition(): void + { + $this->assertSame( + array_column($this->moduleColumns(), 'field'), + array_column($this->pluginColumns(), 'field'), + '컬럼 구성(개수·순서)이 모듈 정의와 다릅니다 — 모듈에 컬럼이 추가/삭제되면 여기도 맞춰야 합니다' + ); + } + + public function test_only_the_payment_method_column_differs_from_the_module(): void + { + $module = $this->moduleColumns(); + + foreach ($this->pluginColumns() as $column) { + $field = $column['field'] ?? ''; + + if ($field === self::OWNED_COLUMN) { + continue; + } + + $this->assertSame( + json_encode($this->columnByField($module, $field), JSON_UNESCAPED_UNICODE), + json_encode($column, JSON_UNESCAPED_UNICODE), + "컬럼 \"{$field}\" 이 모듈 정의와 어긋났습니다 — 이 플러그인이 바꾸는 컬럼은 " + .self::OWNED_COLUMN.' 뿐이므로 나머지는 모듈 정의를 그대로 따라야 합니다' + ); + } + } + + public function test_payment_method_column_keeps_the_test_mode_badge(): void + { + $paymentMethod = $this->columnByField($this->pluginColumns(), self::OWNED_COLUMN); + + $this->assertNotNull($paymentMethod, 'payment_method 컬럼이 사라졌습니다'); + + $json = json_encode($paymentMethod, JSON_UNESCAPED_UNICODE); + + // 모듈 정의를 되돌려 붙이면서 이 플러그인의 본래 목적(테스트결제 배지)을 지우면 안 된다 + $this->assertStringContainsString('test_mode_badge', $json); + $this->assertStringContainsString('row.payment?.payment_method_label', $json); + } +} diff --git a/plugins/_bundled/sirsoft-pay_nhnkcp/CHANGELOG.md b/plugins/_bundled/sirsoft-pay_nhnkcp/CHANGELOG.md index b2b9dd68..2b27a455 100644 --- a/plugins/_bundled/sirsoft-pay_nhnkcp/CHANGELOG.md +++ b/plugins/_bundled/sirsoft-pay_nhnkcp/CHANGELOG.md @@ -8,6 +8,7 @@ ### Fixed +- 이 플러그인이 설치된 사이트에서 관리자 주문 목록의 '일부취소' 표시와 적립예정 포인트·마일리지 사용 금액, 주문상품의 추가옵션 요약이 보이지 않던 문제를 수정했습니다. 결제 정보 칸에 테스트결제 표시를 덧붙이는 과정에서 목록 표 구성 전체를 옛 버전으로 되돌려 놓고 있었습니다. - 관리자 결제수단 설정에서 가상계좌·계좌이체·휴대폰결제에 PG사를 지정할 수 없던 문제를 수정했습니다. 해당 결제수단의 PG사 목록이 항상 비어 있어 기본 PG사만 사용할 수 있었고, 결제수단별로 다른 PG사를 지정하는 설정이 화면에서 불가능했습니다. - 관리자 주문 목록에서 주문번호를 눌러 상세로 들어갈 때 걸어 둔 필터와 페이지가 사라지던 문제를 수정했습니다. (#75 @jiwonpapa 님께서 제보해주셨습니다.) - 관리자 주문 목록에서 '이 주문자의 주문 검색'을 실행하면 검색창은 비어 보이는데 직전 검색어가 계속 적용돼 결과가 어긋나던 문제를 수정했습니다. (#75 @jiwonpapa 님께서 제보해주셨습니다.) diff --git a/plugins/_bundled/sirsoft-pay_nhnkcp/resources/extensions/admin_order_list_test_badge.json b/plugins/_bundled/sirsoft-pay_nhnkcp/resources/extensions/admin_order_list_test_badge.json index 7c24dec4..5c6995d2 100644 --- a/plugins/_bundled/sirsoft-pay_nhnkcp/resources/extensions/admin_order_list_test_badge.json +++ b/plugins/_bundled/sirsoft-pay_nhnkcp/resources/extensions/admin_order_list_test_badge.json @@ -47,7 +47,11 @@ "sortable": false, "required": false, "cellChildren": [ - { "type": "basic", "name": "Span", "text": "{{row.no}}" } + { + "type": "basic", + "name": "Span", + "text": "{{row.no}}" + } ] }, { @@ -57,7 +61,11 @@ "sortable": true, "required": true, "cellChildren": [ - { "type": "basic", "name": "Span", "text": "{{row.ordered_at_formatted}}" } + { + "type": "basic", + "name": "Span", + "text": "{{row.ordered_at_formatted}}" + } ] }, { @@ -70,10 +78,20 @@ { "type": "basic", "name": "Span", - "props": { "className": "font-medium text-blue-600 dark:text-blue-400 cursor-pointer hover:underline" }, + "props": { + "className": "font-medium text-blue-600 dark:text-blue-400 cursor-pointer hover:underline" + }, "text": "{{row.order_number}}", "actions": [ - { "type": "click", "handler": "navigate", "params": { "path": "/admin/ecommerce/orders/{{row.order_number}}", "mergeQuery": true, "query": {} } } + { + "type": "click", + "handler": "navigate", + "params": { + "path": "/admin/ecommerce/orders/{{row.order_number}}", + "mergeQuery": true, + "query": {} + } + } ] } ] @@ -88,30 +106,81 @@ { "type": "basic", "name": "Div", - "props": { "className": "flex items-start gap-2" }, + "props": { + "className": "flex items-start gap-2" + }, "children": [ { "type": "basic", "name": "Img", - "props": { "src": "{{row.first_option?.thumbnail_url}}", "alt": "{{row.first_option?.product_name}}", "className": "w-10 h-10 rounded object-cover flex-shrink-0 bg-gray-100 dark:bg-gray-700" }, + "props": { + "src": "{{row.first_option?.thumbnail_url}}", + "alt": "{{row.first_option?.product_name}}", + "className": "w-10 h-10 rounded object-cover flex-shrink-0 bg-gray-100 dark:bg-gray-700" + }, "if": "{{row.first_option?.thumbnail_url}}" }, { "type": "basic", "name": "Div", - "props": { "className": "space-y-0.5 min-w-0" }, + "props": { + "className": "space-y-0.5 min-w-0" + }, "children": [ { "type": "basic", "name": "Div", - "props": { "className": "font-medium" }, + "props": { + "className": "font-medium" + }, "children": [ - { "type": "basic", "name": "A", "props": { "className": "hover:underline hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-400 cursor-pointer", "href": "{{'\/admin\/ecommerce\/products\/' + row.first_option?.product_code + '\/edit'}}", "target": "_blank" }, "text": "{{row.first_option?.product_name}}", "if": "{{row.first_option?.product_code}}" }, - { "type": "basic", "name": "Span", "text": "{{row.first_option?.product_name}}", "if": "{{!row.first_option?.product_code}}" }, - { "type": "basic", "name": "Span", "props": { "className": "text-gray-500 dark:text-gray-400 font-normal ml-1" }, "text": "$t:defer:sirsoft-ecommerce.admin.order.product.more|count={{row.options_count - 1}}", "if": "{{row.options_count > 1}}" } + { + "type": "basic", + "name": "A", + "props": { + "className": "hover:underline hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-400 cursor-pointer", + "href": "{{'/admin/ecommerce/products/' + row.first_option?.product_code + '/edit'}}", + "target": "_blank" + }, + "text": "{{row.first_option?.product_name}}", + "if": "{{row.first_option?.product_code}}" + }, + { + "type": "basic", + "name": "Span", + "text": "{{row.first_option?.product_name}}", + "if": "{{!row.first_option?.product_code}}" + }, + { + "type": "basic", + "name": "Span", + "props": { + "className": "text-gray-500 dark:text-gray-400 font-normal ml-1" + }, + "text": "$t:defer:sirsoft-ecommerce.admin.order.product.more|count={{row.options_count - 1}}", + "if": "{{row.options_count > 1}}" + } ] }, - { "type": "basic", "name": "Div", "props": { "className": "text-tertiary" }, "text": "{{row.first_option?.product_option_name}}", "if": "{{row.first_option?.product_option_name}}" } + { + "type": "basic", + "name": "Div", + "props": { + "className": "text-tertiary" + }, + "text": "{{row.first_option?.product_option_name}}", + "if": "{{row.first_option?.product_option_name}}" + }, + { + "comment": "추가옵션 요약 — 첫 1건(직접입력 병기) + 외 N건 (Q-E2)", + "type": "basic", + "name": "Div", + "props": { + "className": "text-tertiary text-xs" + }, + "text": "{{row.first_option?.additional_options_summary?.label}}{{(row.first_option?.additional_options_summary?.extra_count ?? 0) > 0 ? ' 외 ' + row.first_option.additional_options_summary.extra_count + '건' : ''}}", + "if": "{{row.first_option?.additional_options_summary}}" + } ] } ] @@ -128,12 +197,63 @@ { "type": "basic", "name": "Div", - "props": { "className": "row-stack" }, + "props": { + "className": "stack-flush gap-0.5" + }, "children": [ - { "type": "basic", "name": "Span", "props": { "className": "font-medium" }, "text": "{{row.total_amount_formatted}}" }, - { "type": "basic", "name": "Span", "props": { "className": "text-xs text-gray-400 dark:text-gray-500" }, "text": "$t:defer:sirsoft-ecommerce.admin.order.column.shipping_fee_short|amount={{row.total_shipping_amount_formatted}}", "if": "{{row.total_shipping_amount > 0}}" }, - { "type": "basic", "name": "Span", "props": { "className": "text-xs text-green-600 dark:text-green-400" }, "text": "$t:defer:sirsoft-ecommerce.admin.order.column.paid_short|amount={{row.total_paid_amount_formatted}}", "if": "{{row.total_paid_amount > 0}}" }, - { "type": "basic", "name": "Span", "props": { "className": "text-xs text-red-600 dark:text-red-400" }, "text": "$t:defer:sirsoft-ecommerce.admin.order.column.unpaid_short|amount={{row.total_unpaid_amount_formatted}}", "if": "{{row.total_unpaid_amount > 0}}" } + { + "type": "basic", + "name": "Span", + "props": { + "className": "font-medium" + }, + "text": "{{row.total_amount_formatted}}" + }, + { + "type": "basic", + "name": "Span", + "props": { + "className": "text-xs text-gray-400 dark:text-gray-500" + }, + "text": "$t:defer:sirsoft-ecommerce.admin.order.column.shipping_fee_short|amount={{row.total_shipping_amount_formatted}}", + "if": "{{row.total_shipping_amount > 0}}" + }, + { + "type": "basic", + "name": "Span", + "props": { + "className": "text-xs text-green-600 dark:text-green-400" + }, + "text": "$t:defer:sirsoft-ecommerce.admin.order.column.paid_short|amount={{row.total_paid_amount_formatted}}", + "if": "{{row.total_paid_amount > 0}}" + }, + { + "type": "basic", + "name": "Span", + "props": { + "className": "text-xs text-red-600 dark:text-red-400" + }, + "text": "$t:defer:sirsoft-ecommerce.admin.order.column.unpaid_short|amount={{row.total_unpaid_amount_formatted}}", + "if": "{{row.total_unpaid_amount > 0}}" + }, + { + "type": "basic", + "name": "Span", + "props": { + "className": "text-xs text-purple-600 dark:text-purple-400" + }, + "text": "$t:defer:sirsoft-ecommerce.admin.order.column.mileage_used_short|amount={{row.total_points_used_amount_formatted}}", + "if": "{{row.total_points_used_amount > 0}}" + }, + { + "type": "basic", + "name": "Span", + "props": { + "className": "text-xs text-blue-600 dark:text-blue-400" + }, + "text": "$t:defer:sirsoft-ecommerce.admin.order.column.points_earned_short|amount={{row.total_earned_points_amount_formatted}}", + "if": "{{row.total_earned_points_amount > 0 && row.order_status !== 'cancelled' && !row.is_partially_cancelled}}" + } ] } ] @@ -145,7 +265,35 @@ "sortable": false, "required": false, "cellChildren": [ - { "type": "composite", "name": "StatusBadge", "props": { "status": "{{row.order_status_variant || 'default'}}", "label": "{{row.order_status_label}}", "showIcon": false } } + { + "comment": "주문상태 배지 묶음 — 상태 배지 아래에 부분취소 보조 배지를 세로로 배치(두 줄)", + "type": "basic", + "name": "Div", + "props": { + "className": "flex flex-col items-start gap-1" + }, + "children": [ + { + "type": "composite", + "name": "StatusBadge", + "props": { + "status": "{{row.order_status_variant || 'default'}}", + "label": "{{row.order_status_label}}", + "showIcon": false + } + }, + { + "comment": "부분취소 보조 배지 — 일부 옵션만 취소된 주문(별도 주문상태 아님, 파생 플래그)", + "type": "basic", + "name": "Span", + "if": "{{row.is_partially_cancelled}}", + "props": { + "className": "inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium whitespace-nowrap bg-orange-100 text-orange-800 dark:bg-orange-900/30 dark:text-orange-400" + }, + "text": "$t:sirsoft-ecommerce.admin.order.detail.partial_cancelled_badge" + } + ] + } ] }, { @@ -155,7 +303,15 @@ "sortable": false, "required": false, "cellChildren": [ - { "type": "basic", "name": "Span", "props": { "className": "text-gray-500 dark:text-gray-400" }, "text": "-", "if": "{{!row.address?.orderer_name && !row.user?.name}}" }, + { + "type": "basic", + "name": "Span", + "props": { + "className": "text-gray-500 dark:text-gray-400" + }, + "text": "-", + "if": "{{!row.address?.orderer_name && !row.user?.name}}" + }, { "type": "composite", "name": "ActionMenu", @@ -163,26 +319,78 @@ "props": { "position": "right", "items": [ - { "id": "view_member", "label": "$t:sirsoft-ecommerce.admin.order.action.view_member", "iconName": "user", "if": "{{!!row.user?.uuid}}" }, - { "id": "search_by_orderer_member", "label": "$t:sirsoft-ecommerce.admin.order.action.search_by_orderer", "iconName": "magnifying-glass", "if": "{{!!row.user?.uuid}}" }, - { "id": "search_by_orderer_guest", "label": "$t:sirsoft-ecommerce.admin.order.action.search_by_orderer", "iconName": "magnifying-glass", "if": "{{!row.user?.uuid}}" } + { + "id": "view_member", + "label": "$t:sirsoft-ecommerce.admin.order.action.view_member", + "iconName": "user", + "if": "{{!!row.user?.uuid}}" + }, + { + "id": "search_by_orderer_member", + "label": "$t:sirsoft-ecommerce.admin.order.action.search_by_orderer", + "iconName": "magnifying-glass", + "if": "{{!!row.user?.uuid}}" + }, + { + "id": "search_by_orderer_guest", + "label": "$t:sirsoft-ecommerce.admin.order.action.search_by_orderer", + "iconName": "magnifying-glass", + "if": "{{!row.user?.uuid}}" + } ] }, "children": [ { "type": "basic", "name": "Div", - "props": { "className": "flex-center gap-2 hover:bg-gray-50 dark:bg-gray-800 dark:hover:bg-gray-700 rounded-lg px-1 py-0.5 transition-colors" }, + "props": { + "className": "flex-center gap-2 hover:bg-gray-50 dark:bg-gray-800 dark:hover:bg-gray-700 rounded-lg px-1 py-0.5 transition-colors" + }, "children": [ - { "type": "basic", "name": "Icon", "if": "{{!!row.user?.uuid}}", "props": { "name": "fa-regular fa-user", "className": "text-gray-400 dark:text-gray-500 flex-shrink-0" } }, + { + "type": "basic", + "name": "Icon", + "if": "{{!!row.user?.uuid}}", + "props": { + "name": "fa-regular fa-user", + "className": "text-gray-400 dark:text-gray-500 flex-shrink-0" + } + }, { "type": "basic", "name": "Div", - "props": { "className": "min-w-0 flex-1" }, + "props": { + "className": "min-w-0 flex-1" + }, "children": [ - { "type": "basic", "name": "Span", "if": "{{!!row.user?.uuid}}", "props": { "className": "font-medium text-gray-900 dark:text-gray-100 truncate block" }, "text": "{{row.address?.orderer_name || row.user?.name}}" }, - { "type": "basic", "name": "Span", "if": "{{!row.user?.uuid}}", "props": { "className": "text-gray-600 dark:text-gray-400 truncate block" }, "text": "{{row.address?.orderer_name || row.user?.name}}" }, - { "type": "basic", "name": "Span", "comment": "첫구매 표시", "if": "{{row.is_first_order}}", "props": { "className": "text-xs text-emerald-500 dark:text-emerald-400" }, "text": "$t:sirsoft-ecommerce.admin.order.column.first_order" } + { + "type": "basic", + "name": "Span", + "if": "{{!!row.user?.uuid}}", + "props": { + "className": "font-medium text-gray-900 dark:text-gray-100 truncate block" + }, + "text": "{{row.address?.orderer_name || row.user?.name}}" + }, + { + "type": "basic", + "name": "Span", + "if": "{{!row.user?.uuid}}", + "props": { + "className": "text-gray-600 dark:text-gray-400 truncate block" + }, + "text": "{{row.address?.orderer_name || row.user?.name}}" + }, + { + "type": "basic", + "name": "Span", + "comment": "첫구매 표시", + "if": "{{row.is_first_order}}", + "props": { + "className": "text-xs text-emerald-500 dark:text-emerald-400" + }, + "text": "$t:sirsoft-ecommerce.admin.order.column.first_order" + } ] } ] @@ -194,19 +402,71 @@ "handler": "switch", "value": "{{$args[0]}}", "cases": { - "view_member": { "handler": "openWindow", "params": { "path": "/admin/users/{{row.user?.uuid}}" } }, + "view_member": { + "handler": "openWindow", + "params": { + "path": "/admin/users/{{row.user?.uuid}}" + } + }, "search_by_orderer_member": { "handler": "sequence", "actions": [ - { "handler": "setState", "params": { "target": "_local", "filter.ordererUuid": "{{row.user?.uuid}}", "filter.searchField": "all", "filter.searchKeyword": "", "ordererSearchResults": "{{[{ uuid: row.user?.uuid, name: row.user?.name || row.address?.orderer_name, email: row.user?.email || '' }]}}" } }, - { "comment": "이 주문자의 주문만 보기 — 앞선 setState 가 검색어를 비우므로 URL 의 검색 조건도 함께 비운다. 비우지 않으면 화면 입력창은 빈 채로 이전 검색어가 계속 적용된다 (#75).", "handler": "navigate", "params": { "path": "/admin/ecommerce/orders", "mergeQuery": true, "query": { "orderer_uuid": "{{row.user?.uuid}}", "search_field": "", "search_keyword": "", "page": 1 }, "replace": true, "transition_overlay_target": "order_datagrid__body" } } + { + "handler": "setState", + "params": { + "target": "_local", + "filter.ordererUuid": "{{row.user?.uuid}}", + "filter.searchField": "all", + "filter.searchKeyword": "", + "ordererSearchResults": "{{[{ uuid: row.user?.uuid, name: row.user?.name || row.address?.orderer_name, email: row.user?.email || '' }]}}" + } + }, + { + "comment": "이 주문자의 주문만 보기 — 앞선 setState 가 검색어를 비우므로 URL 의 검색 조건도 함께 비운다. 비우지 않으면 화면 입력창은 빈 채로 이전 검색어가 계속 적용된다 (#75).", + "handler": "navigate", + "params": { + "path": "/admin/ecommerce/orders", + "mergeQuery": true, + "query": { + "orderer_uuid": "{{row.user?.uuid}}", + "search_field": "", + "search_keyword": "", + "page": 1 + }, + "replace": true, + "transition_overlay_target": "order_datagrid__body" + } + } ] }, "search_by_orderer_guest": { "handler": "sequence", "actions": [ - { "handler": "setState", "params": { "target": "_local", "filter.searchField": "orderer_name", "filter.searchKeyword": "{{row.address?.orderer_name || row.user?.name}}", "filter.ordererUuid": "" } }, - { "comment": "비회원 주문자명으로 검색 — 회원 지정 필터(orderer_uuid)를 함께 비우지 않으면 이름 검색과 회원 필터가 겹쳐 결과가 비어 보인다 (#75).", "handler": "navigate", "params": { "path": "/admin/ecommerce/orders", "mergeQuery": true, "query": { "search_field": "orderer_name", "search_keyword": "{{row.address?.orderer_name || row.user?.name}}", "orderer_uuid": "", "page": 1 }, "replace": true, "transition_overlay_target": "order_datagrid__body" } } + { + "handler": "setState", + "params": { + "target": "_local", + "filter.searchField": "orderer_name", + "filter.searchKeyword": "{{row.address?.orderer_name || row.user?.name}}", + "filter.ordererUuid": "" + } + }, + { + "comment": "비회원 주문자명으로 검색 — 회원 지정 필터(orderer_uuid)를 함께 비우지 않으면 이름 검색과 회원 필터가 겹쳐 결과가 비어 보인다 (#75).", + "handler": "navigate", + "params": { + "path": "/admin/ecommerce/orders", + "mergeQuery": true, + "query": { + "search_field": "orderer_name", + "search_keyword": "{{row.address?.orderer_name || row.user?.name}}", + "orderer_uuid": "", + "page": 1 + }, + "replace": true, + "transition_overlay_target": "order_datagrid__body" + } + } ] } } @@ -225,7 +485,9 @@ { "type": "basic", "name": "Div", - "props": { "className": "row-stack" }, + "props": { + "className": "row-stack" + }, "children": [ { "type": "basic", @@ -236,14 +498,18 @@ "type": "basic", "name": "Span", "if": "{{nhnkcp_test_map.data?.[row.order_number] === true}}", - "props": { "className": "text-xs font-medium text-orange-500 dark:text-orange-400 leading-tight" }, + "props": { + "className": "text-xs font-medium text-orange-500 dark:text-orange-400 leading-tight" + }, "text": "($t:sirsoft-pay_nhnkcp.admin.test_mode_badge)" }, { "type": "basic", "name": "Span", "if": "{{nicepay_test_map.data?.[row.order_number] === true}}", - "props": { "className": "text-xs font-medium text-orange-500 dark:text-orange-400 leading-tight" }, + "props": { + "className": "text-xs font-medium text-orange-500 dark:text-orange-400 leading-tight" + }, "text": "($t:sirsoft-pay_nicepayments.admin.test_mode_badge)" } ] @@ -257,7 +523,11 @@ "sortable": false, "required": false, "cellChildren": [ - { "type": "basic", "name": "Span", "text": "{{row.shipping?.shipping_type_label}}" } + { + "type": "basic", + "name": "Span", + "text": "{{row.shipping?.shipping_type_label}}" + } ] }, { @@ -270,10 +540,24 @@ { "type": "basic", "name": "Div", - "props": { "className": "flex-center gap-2" }, + "props": { + "className": "flex-center gap-2" + }, "children": [ - { "type": "basic", "name": "Span", "props": { "className": "fi fi-{{row.address?.recipient_country_code?.toLowerCase()}} flex-shrink-0" }, "if": "{{row.address?.recipient_country_code}}" }, - { "type": "basic", "name": "Span", "text": "{{$localized(row.address?.recipient_country_name) ?? row.address?.recipient_country_code}}", "if": "{{row.address?.recipient_country_code}}" } + { + "type": "basic", + "name": "Span", + "props": { + "className": "fi fi-{{row.address?.recipient_country_code?.toLowerCase()}} flex-shrink-0" + }, + "if": "{{row.address?.recipient_country_code}}" + }, + { + "type": "basic", + "name": "Span", + "text": "{{$localized(row.address?.recipient_country_name) ?? row.address?.recipient_country_code}}", + "if": "{{row.address?.recipient_country_code}}" + } ] } ] @@ -285,7 +569,11 @@ "sortable": false, "required": false, "cellChildren": [ - { "type": "basic", "name": "Span", "text": "{{row.order_device_label}}" } + { + "type": "basic", + "name": "Span", + "text": "{{row.order_device_label}}" + } ] } ] diff --git a/plugins/_bundled/sirsoft-pay_nhnkcp/tests/Unit/Extensions/AdminOrderListColumnSnapshotTest.php b/plugins/_bundled/sirsoft-pay_nhnkcp/tests/Unit/Extensions/AdminOrderListColumnSnapshotTest.php new file mode 100644 index 00000000..cea33460 --- /dev/null +++ b/plugins/_bundled/sirsoft-pay_nhnkcp/tests/Unit/Extensions/AdminOrderListColumnSnapshotTest.php @@ -0,0 +1,169 @@ +> 컬럼 목록 + */ + private function moduleColumns(): array + { + $path = $this->repoRoot().'/'.self::MODULE_LAYOUT_PATH; + + if (! is_file($path)) { + $this->markTestSkipped('이커머스 모듈 레이아웃을 찾을 수 없습니다: '.$path); + } + + $columns = $this->findDataGridColumns(json_decode(file_get_contents($path), true)); + + $this->assertNotNull($columns, '모듈 레이아웃에서 DataGrid 컬럼 정의를 찾지 못했습니다'); + + return $columns; + } + + /** + * 이 플러그인이 주입하는 컬럼 정의를 읽습니다. + * + * @return array> 컬럼 목록 + */ + private function pluginColumns(): array + { + $json = json_decode(file_get_contents($this->repoRoot().'/'.self::EXTENSION_PATH), true); + + foreach ($json['injections'] ?? [] as $injection) { + if (($injection['target_id'] ?? null) === 'order_datagrid') { + return $injection['props']['columns'] ?? []; + } + } + + $this->fail('order_datagrid 주입을 찾지 못했습니다'); + } + + /** + * 트리에서 DataGrid 의 columns 배열을 찾습니다. + * + * @param mixed $node 탐색 노드 + * @return array>|null 찾은 컬럼 목록 + */ + private function findDataGridColumns(mixed $node): ?array + { + if (! is_array($node)) { + return null; + } + + if (($node['name'] ?? null) === 'DataGrid' && isset($node['props']['columns'])) { + return $node['props']['columns']; + } + + foreach ($node as $child) { + $found = $this->findDataGridColumns($child); + + if ($found !== null) { + return $found; + } + } + + return null; + } + + /** + * 컬럼 목록에서 특정 field 의 정의를 찾습니다. + * + * @param array> $columns 컬럼 목록 + * @param string $field 찾을 field + * @return array|null 컬럼 정의 + */ + private function columnByField(array $columns, string $field): ?array + { + foreach ($columns as $column) { + if (($column['field'] ?? '') === $field) { + return $column; + } + } + + return null; + } + + public function test_column_set_matches_the_module_definition(): void + { + $this->assertSame( + array_column($this->moduleColumns(), 'field'), + array_column($this->pluginColumns(), 'field'), + '컬럼 구성(개수·순서)이 모듈 정의와 다릅니다 — 모듈에 컬럼이 추가/삭제되면 여기도 맞춰야 합니다' + ); + } + + public function test_only_the_payment_method_column_differs_from_the_module(): void + { + $module = $this->moduleColumns(); + + foreach ($this->pluginColumns() as $column) { + $field = $column['field'] ?? ''; + + if ($field === self::OWNED_COLUMN) { + continue; + } + + $this->assertSame( + json_encode($this->columnByField($module, $field), JSON_UNESCAPED_UNICODE), + json_encode($column, JSON_UNESCAPED_UNICODE), + "컬럼 \"{$field}\" 이 모듈 정의와 어긋났습니다 — 이 플러그인이 바꾸는 컬럼은 " + .self::OWNED_COLUMN.' 뿐이므로 나머지는 모듈 정의를 그대로 따라야 합니다' + ); + } + } + + public function test_payment_method_column_keeps_the_test_mode_badge(): void + { + $paymentMethod = $this->columnByField($this->pluginColumns(), self::OWNED_COLUMN); + + $this->assertNotNull($paymentMethod, 'payment_method 컬럼이 사라졌습니다'); + + $json = json_encode($paymentMethod, JSON_UNESCAPED_UNICODE); + + // 모듈 정의를 되돌려 붙이면서 이 플러그인의 본래 목적(테스트결제 배지)을 지우면 안 된다 + $this->assertStringContainsString('test_mode_badge', $json); + $this->assertStringContainsString('row.payment?.payment_method_label', $json); + } +} diff --git a/plugins/_bundled/sirsoft-pay_nicepayments/CHANGELOG.md b/plugins/_bundled/sirsoft-pay_nicepayments/CHANGELOG.md index c3edf3f5..e3fa1625 100644 --- a/plugins/_bundled/sirsoft-pay_nicepayments/CHANGELOG.md +++ b/plugins/_bundled/sirsoft-pay_nicepayments/CHANGELOG.md @@ -8,6 +8,7 @@ ### Fixed +- 이 플러그인이 설치된 사이트에서 관리자 주문 목록의 '일부취소' 표시와 적립예정 포인트·마일리지 사용 금액, 주문상품의 추가옵션 요약이 보이지 않던 문제를 수정했습니다. 결제 정보 칸에 테스트결제 표시를 덧붙이는 과정에서 목록 표 구성 전체를 옛 버전으로 되돌려 놓고 있었습니다. - 관리자 결제수단 설정에서 가상계좌·계좌이체·휴대폰결제에 PG사를 지정할 수 없던 문제를 수정했습니다. 해당 결제수단의 PG사 목록이 항상 비어 있어 기본 PG사만 사용할 수 있었고, 결제수단별로 다른 PG사를 지정하는 설정이 화면에서 불가능했습니다. - 관리자 주문 목록에서 주문번호를 눌러 상세로 들어갈 때 걸어 둔 필터와 페이지가 사라지던 문제를 수정했습니다. (#75 @jiwonpapa 님께서 제보해주셨습니다.) - 관리자 주문 목록에서 '이 주문자의 주문 검색'을 실행하면 검색창은 비어 보이는데 직전 검색어가 계속 적용돼 결과가 어긋나던 문제를 수정했습니다. (#75 @jiwonpapa 님께서 제보해주셨습니다.) diff --git a/plugins/_bundled/sirsoft-pay_nicepayments/resources/extensions/admin_order_list_test_badge.json b/plugins/_bundled/sirsoft-pay_nicepayments/resources/extensions/admin_order_list_test_badge.json index a440cf56..9fddd43b 100644 --- a/plugins/_bundled/sirsoft-pay_nicepayments/resources/extensions/admin_order_list_test_badge.json +++ b/plugins/_bundled/sirsoft-pay_nicepayments/resources/extensions/admin_order_list_test_badge.json @@ -25,7 +25,11 @@ "sortable": false, "required": false, "cellChildren": [ - { "type": "basic", "name": "Span", "text": "{{row.no}}" } + { + "type": "basic", + "name": "Span", + "text": "{{row.no}}" + } ] }, { @@ -35,7 +39,11 @@ "sortable": true, "required": true, "cellChildren": [ - { "type": "basic", "name": "Span", "text": "{{row.ordered_at_formatted}}" } + { + "type": "basic", + "name": "Span", + "text": "{{row.ordered_at_formatted}}" + } ] }, { @@ -48,10 +56,20 @@ { "type": "basic", "name": "Span", - "props": { "className": "font-medium text-blue-600 dark:text-blue-400 cursor-pointer hover:underline" }, + "props": { + "className": "font-medium text-blue-600 dark:text-blue-400 cursor-pointer hover:underline" + }, "text": "{{row.order_number}}", "actions": [ - { "type": "click", "handler": "navigate", "params": { "path": "/admin/ecommerce/orders/{{row.order_number}}", "mergeQuery": true, "query": {} } } + { + "type": "click", + "handler": "navigate", + "params": { + "path": "/admin/ecommerce/orders/{{row.order_number}}", + "mergeQuery": true, + "query": {} + } + } ] } ] @@ -66,30 +84,81 @@ { "type": "basic", "name": "Div", - "props": { "className": "flex items-start gap-2" }, + "props": { + "className": "flex items-start gap-2" + }, "children": [ { "type": "basic", "name": "Img", - "props": { "src": "{{row.first_option?.thumbnail_url}}", "alt": "{{row.first_option?.product_name}}", "className": "w-10 h-10 rounded object-cover flex-shrink-0 bg-gray-100 dark:bg-gray-700" }, + "props": { + "src": "{{row.first_option?.thumbnail_url}}", + "alt": "{{row.first_option?.product_name}}", + "className": "w-10 h-10 rounded object-cover flex-shrink-0 bg-gray-100 dark:bg-gray-700" + }, "if": "{{row.first_option?.thumbnail_url}}" }, { "type": "basic", "name": "Div", - "props": { "className": "space-y-0.5 min-w-0" }, + "props": { + "className": "space-y-0.5 min-w-0" + }, "children": [ { "type": "basic", "name": "Div", - "props": { "className": "font-medium" }, + "props": { + "className": "font-medium" + }, "children": [ - { "type": "basic", "name": "A", "props": { "className": "hover:underline hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-400 cursor-pointer", "href": "{{'\/admin\/ecommerce\/products\/' + row.first_option?.product_code + '\/edit'}}", "target": "_blank" }, "text": "{{row.first_option?.product_name}}", "if": "{{row.first_option?.product_code}}" }, - { "type": "basic", "name": "Span", "text": "{{row.first_option?.product_name}}", "if": "{{!row.first_option?.product_code}}" }, - { "type": "basic", "name": "Span", "props": { "className": "text-gray-500 dark:text-gray-400 font-normal ml-1" }, "text": "$t:defer:sirsoft-ecommerce.admin.order.product.more|count={{row.options_count - 1}}", "if": "{{row.options_count > 1}}" } + { + "type": "basic", + "name": "A", + "props": { + "className": "hover:underline hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-400 cursor-pointer", + "href": "{{'/admin/ecommerce/products/' + row.first_option?.product_code + '/edit'}}", + "target": "_blank" + }, + "text": "{{row.first_option?.product_name}}", + "if": "{{row.first_option?.product_code}}" + }, + { + "type": "basic", + "name": "Span", + "text": "{{row.first_option?.product_name}}", + "if": "{{!row.first_option?.product_code}}" + }, + { + "type": "basic", + "name": "Span", + "props": { + "className": "text-gray-500 dark:text-gray-400 font-normal ml-1" + }, + "text": "$t:defer:sirsoft-ecommerce.admin.order.product.more|count={{row.options_count - 1}}", + "if": "{{row.options_count > 1}}" + } ] }, - { "type": "basic", "name": "Div", "props": { "className": "text-tertiary" }, "text": "{{row.first_option?.product_option_name}}", "if": "{{row.first_option?.product_option_name}}" } + { + "type": "basic", + "name": "Div", + "props": { + "className": "text-tertiary" + }, + "text": "{{row.first_option?.product_option_name}}", + "if": "{{row.first_option?.product_option_name}}" + }, + { + "comment": "추가옵션 요약 — 첫 1건(직접입력 병기) + 외 N건 (Q-E2)", + "type": "basic", + "name": "Div", + "props": { + "className": "text-tertiary text-xs" + }, + "text": "{{row.first_option?.additional_options_summary?.label}}{{(row.first_option?.additional_options_summary?.extra_count ?? 0) > 0 ? ' 외 ' + row.first_option.additional_options_summary.extra_count + '건' : ''}}", + "if": "{{row.first_option?.additional_options_summary}}" + } ] } ] @@ -106,12 +175,63 @@ { "type": "basic", "name": "Div", - "props": { "className": "row-stack" }, + "props": { + "className": "stack-flush gap-0.5" + }, "children": [ - { "type": "basic", "name": "Span", "props": { "className": "font-medium" }, "text": "{{row.total_amount_formatted}}" }, - { "type": "basic", "name": "Span", "props": { "className": "text-xs text-gray-400 dark:text-gray-500" }, "text": "$t:defer:sirsoft-ecommerce.admin.order.column.shipping_fee_short|amount={{row.total_shipping_amount_formatted}}", "if": "{{row.total_shipping_amount > 0}}" }, - { "type": "basic", "name": "Span", "props": { "className": "text-xs text-green-600 dark:text-green-400" }, "text": "$t:defer:sirsoft-ecommerce.admin.order.column.paid_short|amount={{row.total_paid_amount_formatted}}", "if": "{{row.total_paid_amount > 0}}" }, - { "type": "basic", "name": "Span", "props": { "className": "text-xs text-red-600 dark:text-red-400" }, "text": "$t:defer:sirsoft-ecommerce.admin.order.column.unpaid_short|amount={{row.total_unpaid_amount_formatted}}", "if": "{{row.total_unpaid_amount > 0}}" } + { + "type": "basic", + "name": "Span", + "props": { + "className": "font-medium" + }, + "text": "{{row.total_amount_formatted}}" + }, + { + "type": "basic", + "name": "Span", + "props": { + "className": "text-xs text-gray-400 dark:text-gray-500" + }, + "text": "$t:defer:sirsoft-ecommerce.admin.order.column.shipping_fee_short|amount={{row.total_shipping_amount_formatted}}", + "if": "{{row.total_shipping_amount > 0}}" + }, + { + "type": "basic", + "name": "Span", + "props": { + "className": "text-xs text-green-600 dark:text-green-400" + }, + "text": "$t:defer:sirsoft-ecommerce.admin.order.column.paid_short|amount={{row.total_paid_amount_formatted}}", + "if": "{{row.total_paid_amount > 0}}" + }, + { + "type": "basic", + "name": "Span", + "props": { + "className": "text-xs text-red-600 dark:text-red-400" + }, + "text": "$t:defer:sirsoft-ecommerce.admin.order.column.unpaid_short|amount={{row.total_unpaid_amount_formatted}}", + "if": "{{row.total_unpaid_amount > 0}}" + }, + { + "type": "basic", + "name": "Span", + "props": { + "className": "text-xs text-purple-600 dark:text-purple-400" + }, + "text": "$t:defer:sirsoft-ecommerce.admin.order.column.mileage_used_short|amount={{row.total_points_used_amount_formatted}}", + "if": "{{row.total_points_used_amount > 0}}" + }, + { + "type": "basic", + "name": "Span", + "props": { + "className": "text-xs text-blue-600 dark:text-blue-400" + }, + "text": "$t:defer:sirsoft-ecommerce.admin.order.column.points_earned_short|amount={{row.total_earned_points_amount_formatted}}", + "if": "{{row.total_earned_points_amount > 0 && row.order_status !== 'cancelled' && !row.is_partially_cancelled}}" + } ] } ] @@ -123,7 +243,35 @@ "sortable": false, "required": false, "cellChildren": [ - { "type": "composite", "name": "StatusBadge", "props": { "status": "{{row.order_status_variant || 'default'}}", "label": "{{row.order_status_label}}", "showIcon": false } } + { + "comment": "주문상태 배지 묶음 — 상태 배지 아래에 부분취소 보조 배지를 세로로 배치(두 줄)", + "type": "basic", + "name": "Div", + "props": { + "className": "flex flex-col items-start gap-1" + }, + "children": [ + { + "type": "composite", + "name": "StatusBadge", + "props": { + "status": "{{row.order_status_variant || 'default'}}", + "label": "{{row.order_status_label}}", + "showIcon": false + } + }, + { + "comment": "부분취소 보조 배지 — 일부 옵션만 취소된 주문(별도 주문상태 아님, 파생 플래그)", + "type": "basic", + "name": "Span", + "if": "{{row.is_partially_cancelled}}", + "props": { + "className": "inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium whitespace-nowrap bg-orange-100 text-orange-800 dark:bg-orange-900/30 dark:text-orange-400" + }, + "text": "$t:sirsoft-ecommerce.admin.order.detail.partial_cancelled_badge" + } + ] + } ] }, { @@ -133,7 +281,15 @@ "sortable": false, "required": false, "cellChildren": [ - { "type": "basic", "name": "Span", "props": { "className": "text-gray-500 dark:text-gray-400" }, "text": "-", "if": "{{!row.address?.orderer_name && !row.user?.name}}" }, + { + "type": "basic", + "name": "Span", + "props": { + "className": "text-gray-500 dark:text-gray-400" + }, + "text": "-", + "if": "{{!row.address?.orderer_name && !row.user?.name}}" + }, { "type": "composite", "name": "ActionMenu", @@ -141,26 +297,78 @@ "props": { "position": "right", "items": [ - { "id": "view_member", "label": "$t:sirsoft-ecommerce.admin.order.action.view_member", "iconName": "user", "if": "{{!!row.user?.uuid}}" }, - { "id": "search_by_orderer_member", "label": "$t:sirsoft-ecommerce.admin.order.action.search_by_orderer", "iconName": "magnifying-glass", "if": "{{!!row.user?.uuid}}" }, - { "id": "search_by_orderer_guest", "label": "$t:sirsoft-ecommerce.admin.order.action.search_by_orderer", "iconName": "magnifying-glass", "if": "{{!row.user?.uuid}}" } + { + "id": "view_member", + "label": "$t:sirsoft-ecommerce.admin.order.action.view_member", + "iconName": "user", + "if": "{{!!row.user?.uuid}}" + }, + { + "id": "search_by_orderer_member", + "label": "$t:sirsoft-ecommerce.admin.order.action.search_by_orderer", + "iconName": "magnifying-glass", + "if": "{{!!row.user?.uuid}}" + }, + { + "id": "search_by_orderer_guest", + "label": "$t:sirsoft-ecommerce.admin.order.action.search_by_orderer", + "iconName": "magnifying-glass", + "if": "{{!row.user?.uuid}}" + } ] }, "children": [ { "type": "basic", "name": "Div", - "props": { "className": "flex-center gap-2 hover:bg-gray-50 dark:bg-gray-800 dark:hover:bg-gray-700 rounded-lg px-1 py-0.5 transition-colors" }, + "props": { + "className": "flex-center gap-2 hover:bg-gray-50 dark:bg-gray-800 dark:hover:bg-gray-700 rounded-lg px-1 py-0.5 transition-colors" + }, "children": [ - { "type": "basic", "name": "Icon", "if": "{{!!row.user?.uuid}}", "props": { "name": "fa-regular fa-user", "className": "text-gray-400 dark:text-gray-500 flex-shrink-0" } }, + { + "type": "basic", + "name": "Icon", + "if": "{{!!row.user?.uuid}}", + "props": { + "name": "fa-regular fa-user", + "className": "text-gray-400 dark:text-gray-500 flex-shrink-0" + } + }, { "type": "basic", "name": "Div", - "props": { "className": "min-w-0 flex-1" }, + "props": { + "className": "min-w-0 flex-1" + }, "children": [ - { "type": "basic", "name": "Span", "if": "{{!!row.user?.uuid}}", "props": { "className": "font-medium text-gray-900 dark:text-gray-100 truncate block" }, "text": "{{row.address?.orderer_name || row.user?.name}}" }, - { "type": "basic", "name": "Span", "if": "{{!row.user?.uuid}}", "props": { "className": "text-gray-600 dark:text-gray-400 truncate block" }, "text": "{{row.address?.orderer_name || row.user?.name}}" }, - { "type": "basic", "name": "Span", "comment": "첫구매 표시", "if": "{{row.is_first_order}}", "props": { "className": "text-xs text-emerald-500 dark:text-emerald-400" }, "text": "$t:sirsoft-ecommerce.admin.order.column.first_order" } + { + "type": "basic", + "name": "Span", + "if": "{{!!row.user?.uuid}}", + "props": { + "className": "font-medium text-gray-900 dark:text-gray-100 truncate block" + }, + "text": "{{row.address?.orderer_name || row.user?.name}}" + }, + { + "type": "basic", + "name": "Span", + "if": "{{!row.user?.uuid}}", + "props": { + "className": "text-gray-600 dark:text-gray-400 truncate block" + }, + "text": "{{row.address?.orderer_name || row.user?.name}}" + }, + { + "type": "basic", + "name": "Span", + "comment": "첫구매 표시", + "if": "{{row.is_first_order}}", + "props": { + "className": "text-xs text-emerald-500 dark:text-emerald-400" + }, + "text": "$t:sirsoft-ecommerce.admin.order.column.first_order" + } ] } ] @@ -172,19 +380,71 @@ "handler": "switch", "value": "{{$args[0]}}", "cases": { - "view_member": { "handler": "openWindow", "params": { "path": "/admin/users/{{row.user?.uuid}}" } }, + "view_member": { + "handler": "openWindow", + "params": { + "path": "/admin/users/{{row.user?.uuid}}" + } + }, "search_by_orderer_member": { "handler": "sequence", "actions": [ - { "handler": "setState", "params": { "target": "_local", "filter.ordererUuid": "{{row.user?.uuid}}", "filter.searchField": "all", "filter.searchKeyword": "", "ordererSearchResults": "{{[{ uuid: row.user?.uuid, name: row.user?.name || row.address?.orderer_name, email: row.user?.email || '' }]}}" } }, - { "comment": "이 주문자의 주문만 보기 — 앞선 setState 가 검색어를 비우므로 URL 의 검색 조건도 함께 비운다. 비우지 않으면 화면 입력창은 빈 채로 이전 검색어가 계속 적용된다 (#75).", "handler": "navigate", "params": { "path": "/admin/ecommerce/orders", "mergeQuery": true, "query": { "orderer_uuid": "{{row.user?.uuid}}", "search_field": "", "search_keyword": "", "page": 1 }, "replace": true, "transition_overlay_target": "order_datagrid__body" } } + { + "handler": "setState", + "params": { + "target": "_local", + "filter.ordererUuid": "{{row.user?.uuid}}", + "filter.searchField": "all", + "filter.searchKeyword": "", + "ordererSearchResults": "{{[{ uuid: row.user?.uuid, name: row.user?.name || row.address?.orderer_name, email: row.user?.email || '' }]}}" + } + }, + { + "comment": "이 주문자의 주문만 보기 — 앞선 setState 가 검색어를 비우므로 URL 의 검색 조건도 함께 비운다. 비우지 않으면 화면 입력창은 빈 채로 이전 검색어가 계속 적용된다 (#75).", + "handler": "navigate", + "params": { + "path": "/admin/ecommerce/orders", + "mergeQuery": true, + "query": { + "orderer_uuid": "{{row.user?.uuid}}", + "search_field": "", + "search_keyword": "", + "page": 1 + }, + "replace": true, + "transition_overlay_target": "order_datagrid__body" + } + } ] }, "search_by_orderer_guest": { "handler": "sequence", "actions": [ - { "handler": "setState", "params": { "target": "_local", "filter.searchField": "orderer_name", "filter.searchKeyword": "{{row.address?.orderer_name || row.user?.name}}", "filter.ordererUuid": "" } }, - { "comment": "비회원 주문자명으로 검색 — 회원 지정 필터(orderer_uuid)를 함께 비우지 않으면 이름 검색과 회원 필터가 겹쳐 결과가 비어 보인다 (#75).", "handler": "navigate", "params": { "path": "/admin/ecommerce/orders", "mergeQuery": true, "query": { "search_field": "orderer_name", "search_keyword": "{{row.address?.orderer_name || row.user?.name}}", "orderer_uuid": "", "page": 1 }, "replace": true, "transition_overlay_target": "order_datagrid__body" } } + { + "handler": "setState", + "params": { + "target": "_local", + "filter.searchField": "orderer_name", + "filter.searchKeyword": "{{row.address?.orderer_name || row.user?.name}}", + "filter.ordererUuid": "" + } + }, + { + "comment": "비회원 주문자명으로 검색 — 회원 지정 필터(orderer_uuid)를 함께 비우지 않으면 이름 검색과 회원 필터가 겹쳐 결과가 비어 보인다 (#75).", + "handler": "navigate", + "params": { + "path": "/admin/ecommerce/orders", + "mergeQuery": true, + "query": { + "search_field": "orderer_name", + "search_keyword": "{{row.address?.orderer_name || row.user?.name}}", + "orderer_uuid": "", + "page": 1 + }, + "replace": true, + "transition_overlay_target": "order_datagrid__body" + } + } ] } } @@ -203,7 +463,9 @@ { "type": "basic", "name": "Div", - "props": { "className": "row-stack" }, + "props": { + "className": "row-stack" + }, "children": [ { "type": "basic", @@ -214,7 +476,9 @@ "type": "basic", "name": "Span", "if": "{{nicepay_test_map.data?.[row.order_number] === true}}", - "props": { "className": "text-xs font-medium text-orange-500 dark:text-orange-400 leading-tight" }, + "props": { + "className": "text-xs font-medium text-orange-500 dark:text-orange-400 leading-tight" + }, "text": "($t:sirsoft-pay_nicepayments.admin.test_mode_badge)" } ] @@ -228,7 +492,11 @@ "sortable": false, "required": false, "cellChildren": [ - { "type": "basic", "name": "Span", "text": "{{row.shipping?.shipping_type_label}}" } + { + "type": "basic", + "name": "Span", + "text": "{{row.shipping?.shipping_type_label}}" + } ] }, { @@ -241,10 +509,24 @@ { "type": "basic", "name": "Div", - "props": { "className": "flex-center gap-2" }, + "props": { + "className": "flex-center gap-2" + }, "children": [ - { "type": "basic", "name": "Span", "props": { "className": "fi fi-{{row.address?.recipient_country_code?.toLowerCase()}} flex-shrink-0" }, "if": "{{row.address?.recipient_country_code}}" }, - { "type": "basic", "name": "Span", "text": "{{$localized(row.address?.recipient_country_name) ?? row.address?.recipient_country_code}}", "if": "{{row.address?.recipient_country_code}}" } + { + "type": "basic", + "name": "Span", + "props": { + "className": "fi fi-{{row.address?.recipient_country_code?.toLowerCase()}} flex-shrink-0" + }, + "if": "{{row.address?.recipient_country_code}}" + }, + { + "type": "basic", + "name": "Span", + "text": "{{$localized(row.address?.recipient_country_name) ?? row.address?.recipient_country_code}}", + "if": "{{row.address?.recipient_country_code}}" + } ] } ] @@ -256,7 +538,11 @@ "sortable": false, "required": false, "cellChildren": [ - { "type": "basic", "name": "Span", "text": "{{row.order_device_label}}" } + { + "type": "basic", + "name": "Span", + "text": "{{row.order_device_label}}" + } ] } ] diff --git a/plugins/_bundled/sirsoft-pay_nicepayments/tests/Unit/Extensions/AdminOrderListColumnSnapshotTest.php b/plugins/_bundled/sirsoft-pay_nicepayments/tests/Unit/Extensions/AdminOrderListColumnSnapshotTest.php new file mode 100644 index 00000000..850e1d81 --- /dev/null +++ b/plugins/_bundled/sirsoft-pay_nicepayments/tests/Unit/Extensions/AdminOrderListColumnSnapshotTest.php @@ -0,0 +1,169 @@ +> 컬럼 목록 + */ + private function moduleColumns(): array + { + $path = $this->repoRoot().'/'.self::MODULE_LAYOUT_PATH; + + if (! is_file($path)) { + $this->markTestSkipped('이커머스 모듈 레이아웃을 찾을 수 없습니다: '.$path); + } + + $columns = $this->findDataGridColumns(json_decode(file_get_contents($path), true)); + + $this->assertNotNull($columns, '모듈 레이아웃에서 DataGrid 컬럼 정의를 찾지 못했습니다'); + + return $columns; + } + + /** + * 이 플러그인이 주입하는 컬럼 정의를 읽습니다. + * + * @return array> 컬럼 목록 + */ + private function pluginColumns(): array + { + $json = json_decode(file_get_contents($this->repoRoot().'/'.self::EXTENSION_PATH), true); + + foreach ($json['injections'] ?? [] as $injection) { + if (($injection['target_id'] ?? null) === 'order_datagrid') { + return $injection['props']['columns'] ?? []; + } + } + + $this->fail('order_datagrid 주입을 찾지 못했습니다'); + } + + /** + * 트리에서 DataGrid 의 columns 배열을 찾습니다. + * + * @param mixed $node 탐색 노드 + * @return array>|null 찾은 컬럼 목록 + */ + private function findDataGridColumns(mixed $node): ?array + { + if (! is_array($node)) { + return null; + } + + if (($node['name'] ?? null) === 'DataGrid' && isset($node['props']['columns'])) { + return $node['props']['columns']; + } + + foreach ($node as $child) { + $found = $this->findDataGridColumns($child); + + if ($found !== null) { + return $found; + } + } + + return null; + } + + /** + * 컬럼 목록에서 특정 field 의 정의를 찾습니다. + * + * @param array> $columns 컬럼 목록 + * @param string $field 찾을 field + * @return array|null 컬럼 정의 + */ + private function columnByField(array $columns, string $field): ?array + { + foreach ($columns as $column) { + if (($column['field'] ?? '') === $field) { + return $column; + } + } + + return null; + } + + public function test_column_set_matches_the_module_definition(): void + { + $this->assertSame( + array_column($this->moduleColumns(), 'field'), + array_column($this->pluginColumns(), 'field'), + '컬럼 구성(개수·순서)이 모듈 정의와 다릅니다 — 모듈에 컬럼이 추가/삭제되면 여기도 맞춰야 합니다' + ); + } + + public function test_only_the_payment_method_column_differs_from_the_module(): void + { + $module = $this->moduleColumns(); + + foreach ($this->pluginColumns() as $column) { + $field = $column['field'] ?? ''; + + if ($field === self::OWNED_COLUMN) { + continue; + } + + $this->assertSame( + json_encode($this->columnByField($module, $field), JSON_UNESCAPED_UNICODE), + json_encode($column, JSON_UNESCAPED_UNICODE), + "컬럼 \"{$field}\" 이 모듈 정의와 어긋났습니다 — 이 플러그인이 바꾸는 컬럼은 " + .self::OWNED_COLUMN.' 뿐이므로 나머지는 모듈 정의를 그대로 따라야 합니다' + ); + } + } + + public function test_payment_method_column_keeps_the_test_mode_badge(): void + { + $paymentMethod = $this->columnByField($this->pluginColumns(), self::OWNED_COLUMN); + + $this->assertNotNull($paymentMethod, 'payment_method 컬럼이 사라졌습니다'); + + $json = json_encode($paymentMethod, JSON_UNESCAPED_UNICODE); + + // 모듈 정의를 되돌려 붙이면서 이 플러그인의 본래 목적(테스트결제 배지)을 지우면 안 된다 + $this->assertStringContainsString('test_mode_badge', $json); + $this->assertStringContainsString('row.payment?.payment_method_label', $json); + } +} diff --git a/public/build/core/layout-editor.min.js b/public/build/core/layout-editor.min.js index a2690cfb..6bfcb407 100644 --- a/public/build/core/layout-editor.min.js +++ b/public/build/core/layout-editor.min.js @@ -1,14 +1,14 @@ -var sM=Object.defineProperty;var aM=(dn,i,u)=>i in dn?sM(dn,i,{enumerable:!0,configurable:!0,writable:!0,value:u}):dn[i]=u;var Ti=(dn,i,u)=>aM(dn,typeof i!="symbol"?i+"":i,u);(function(dn,i,u,Cn){"use strict";function Lt(){const e=typeof window<"u"?window.G7Core?.__runtime:void 0;if(!e)throw new Error("[layout-editor] window.G7Core.__runtime 이 없습니다. 코어 번들(template-engine.min.js)이 편집기 번들보다 먼저 로드되어야 합니다.");return e}const Di=Lt();Di.TranslationProvider;const tt=Di.useTranslation;Di.TranslationReactContext;function rt(){return typeof window>"u"?null:window.__g7Devtools??null}function Sl(e,t){const n=rt();if(!n?.trackEditorState)return;const o={templateIdentifier:e.templateIdentifier,editMode:e.editMode,selectedRoutePath:e.selectedRoute?.path??null,selectedLayoutName:e.selectedRoute?.layoutName??null,routeTreeSize:e.routeTree.length,isRouteTreeCollapsed:e.isRouteTreeCollapsed,locale:e.locale,previewDevice:e.previewDevice,previewZoom:e.previewZoom,lastAction:t,timestamp:Date.now()};try{n.trackEditorState(o)}catch{}}function Fh(){const e=rt();if(e?.clearEditorStateData)try{e.clearEditorStateData()}catch{}}function Kh(e){const t=rt();if(t?.trackEditorSampleMatch)try{t.trackEditorSampleMatch(e)}catch{}}function Vh(){const e=rt();if(e?.clearEditorSampleData)try{e.clearEditorSampleData()}catch{}}function Uh(e){const t=rt();if(t?.trackEditorHistoryEntry)try{t.trackEditorHistoryEntry(e)}catch{}}function qh(){const e=rt();if(e?.clearEditorHistoryData)try{e.clearEditorHistoryData()}catch{}}function Cl(e){const t=rt();if(t?.trackEditorSelection)try{t.trackEditorSelection(e)}catch{}}function Gh(){const e=rt();if(e?.clearEditorSelectionData)try{e.clearEditorSelectionData()}catch{}}function sr(e){const t=rt();if(t?.trackEditorPropertyPatch)try{t.trackEditorPropertyPatch(e)}catch{}}function $n(e){const t=rt();if(t?.trackEditorDnd)try{t.trackEditorDnd(e)}catch{}}function Yh(){const e=rt();if(e?.clearEditorDndData)try{e.clearEditorDndData()}catch{}}function st(e){const t=rt();if(t?.trackEditorDocument)try{t.trackEditorDocument(e)}catch{}}function Xh(){const e=rt();if(e?.clearEditorDocumentData)try{e.clearEditorDocumentData()}catch{}}function Jh(e){const t=rt();if(t?.trackEditorSpecMerge)try{t.trackEditorSpecMerge(e)}catch{}}function Zh(){const e=rt();if(e?.clearEditorSpecMergeData)try{e.clearEditorSpecMergeData()}catch{}}function Ni(e){let t=5381;for(let n=0;n>>0;return t}function Et(e){const t=rt();if(t?.trackEditorI18n)try{t.trackEditorI18n(e)}catch{}}function Qh(){const e=rt();if(e?.clearEditorI18nData)try{e.clearEditorI18nData()}catch{}}function $l(e){const t=rt();if(t?.trackPageState)try{t.trackPageState(e)}catch{}}function Rh(){const e=rt();if(e?.clearPageStateData)try{e.clearPageStateData()}catch{}}const El="g7le.routeTree.collapsed";function ey(){if(typeof window>"u")return!1;try{return window.localStorage?.getItem(El)==="1"}catch{return!1}}function ty(e){if(!(typeof window>"u"))try{window.localStorage?.setItem(El,e?"1":"0")}catch{}}function ny(e,t){return{templateIdentifier:e,routeTree:[],routesError:null,selectedRoute:null,isRouteTreeCollapsed:ey(),editMode:"route",returnRoute:null,returnEditMode:null,locale:t,previewDevice:"desktop",previewZoom:1,previewCustomWidth:1024,previewColorScheme:"light",history:{canUndo:!1,canRedo:!1},isPaletteOpen:!1,availableStates:[],activeStateId:null,layoutVersions:{},extensionVersions:{}}}function oy(e,t){switch(t.type){case"SET_ROUTE_TREE":return{...e,routeTree:t.tree,routesError:null};case"SET_ROUTES_ERROR":return{...e,routesError:t.error};case"SELECT_ROUTE":return{...e,editMode:"route",selectedRoute:t.route,returnRoute:null,returnEditMode:null};case"TOGGLE_ROUTE_TREE":return{...e,isRouteTreeCollapsed:!e.isRouteTreeCollapsed};case"SET_LOCALE":return{...e,locale:t.locale};case"ENTER_BASE_EDIT":return{...e,editMode:"base",returnRoute:t.returnRoute!==void 0?t.returnRoute:e.selectedRoute,selectedRoute:{path:`__base__/${t.layoutName}`,layoutName:t.layoutName}};case"EXIT_BASE_EDIT":return{...e,editMode:"route",selectedRoute:e.returnRoute,returnRoute:null};case"ENTER_MODAL_EDIT":return{...e,editMode:"modal",returnRoute:t.returnRoute!==void 0?t.returnRoute:e.selectedRoute,selectedRoute:{path:`__modal__/${t.modalId}`,layoutName:t.hostLayout}};case"EXIT_MODAL_EDIT":return{...e,editMode:"route",selectedRoute:e.returnRoute,returnRoute:null};case"ENTER_EXTENSION_EDIT":return{...e,editMode:"extension",returnRoute:t.returnRoute!==void 0?t.returnRoute:e.selectedRoute,selectedRoute:{path:`__extension__/${t.extensionId}`,layoutName:t.extensionHost??null}};case"SET_RETURN_ROUTE":return e.editMode==="route"||e.returnRoute!==null?e:{...e,returnRoute:t.route};case"EXIT_EXTENSION_EDIT":return{...e,editMode:"route",selectedRoute:e.returnRoute,returnRoute:null};case"ENTER_ITERATION_ITEM_EDIT":return{...e,editMode:"iteration_item",returnRoute:t.returnRoute!==void 0?t.returnRoute:e.selectedRoute,returnEditMode:e.editMode,selectedRoute:{path:`__iteration__/${t.sourcePath}`,layoutName:t.hostLayout??e.selectedRoute?.layoutName??null}};case"EXIT_ITERATION_ITEM_EDIT":return{...e,editMode:e.returnEditMode??"route",selectedRoute:e.returnRoute,returnRoute:null,returnEditMode:null};case"SET_PREVIEW_DEVICE":return{...e,previewDevice:t.device};case"SET_PREVIEW_ZOOM":return{...e,previewZoom:t.zoom};case"SET_PREVIEW_CUSTOM_WIDTH":return{...e,previewCustomWidth:t.width};case"SET_PREVIEW_COLOR_SCHEME":return{...e,previewColorScheme:t.scheme};case"TOGGLE_PALETTE":return{...e,isPaletteOpen:!e.isPaletteOpen};case"SET_PALETTE_OPEN":return{...e,isPaletteOpen:t.open};case"SET_AVAILABLE_STATES":return{...e,availableStates:t.states,activeStateId:t.activeStateId};case"SET_ACTIVE_STATE":return{...e,activeStateId:t.activeStateId};case"SET_LAYOUT_VERSIONS":return{...e,layoutVersions:t.versions};case"SET_LAYOUT_VERSION":return{...e,layoutVersions:{...e.layoutVersions,[t.layoutName]:t.version}};case"SET_EXTENSION_VERSIONS":return{...e,extensionVersions:t.versions};case"SET_EXTENSION_VERSION":return{...e,extensionVersions:{...e.extensionVersions,[t.extensionId]:t.version}};default:return e}}function ry(e,t){const n=oy(e,t);return((n.selectedRoute?.path??null)!==(e.selectedRoute?.path??null)||n.editMode!==e.editMode)&&(n.availableStates.length>0||n.activeStateId!==null)?{...n,availableStates:[],activeStateId:null}:n}const Il=u.createContext(null);function iy(e,t){const n=ry(e,t);return Sl(n,t.type),n}function sy({templateIdentifier:e,initialLocale:t,children:n}){const[o,r]=u.useReducer(iy,void 0,()=>{const a=ny(e,t);return Sl(a,void 0),a});u.useEffect(()=>{ty(o.isRouteTreeCollapsed)},[o.isRouteTreeCollapsed]);const s=u.useMemo(()=>({state:o,dispatch:r}),[o]);return u.createElement(Il.Provider,{value:s},n)}function Je(){const e=u.useContext(Il);if(!e)throw new Error("useLayoutEditor must be used within LayoutEditorProvider");return e}const Al=u.createContext(null);function ay({value:e,children:t}){return u.createElement(Al.Provider,{value:e},t)}function Ft(){return u.useContext(Al)}const zi=u.createContext(null);let ly=1;function dy({children:e,t}){const[n,o]=u.useState([]),r=u.useRef([]),[s,a]=u.useState({}),l=u.useCallback(b=>{const x=b.id??`editor-modal-${ly++}`,k={id:x,content:b.content,ariaLabel:b.ariaLabel??"",closeOnBackdrop:b.closeOnBackdrop??!0,closeOnEscape:b.closeOnEscape??!0,width:b.width??"auto",height:b.height??"auto",maxHeightRatio:b.maxHeightRatio??.85,draggable:b.draggable??!1,avoidRect:b.avoidRect,onClose:b.onClose};return o(w=>{const S=[...w.filter(E=>E.id!==x),k];return r.current=S,S}),x},[]),d=u.useCallback(b=>{o(x=>{const k=x.find(_=>_.id===b),w=x.filter(_=>_.id!==b);return r.current=w,k?.onClose&&queueMicrotask(()=>k.onClose?.()),w})},[]),c=u.useCallback(()=>{const b=r.current[r.current.length-1];b&&d(b.id)},[d]),p=u.useCallback(()=>{o(b=>{for(let x=b.length-1;x>=0;x--){const k=b[x];k?.onClose&&queueMicrotask(()=>k.onClose?.())}return r.current=[],[]})},[]),f=u.useCallback((b,x)=>{a(k=>{const w=r.current.find(S=>S.id===b),_=x??w?.ariaLabel??"";return{...k,[b]:_}})},[]),y=u.useCallback(b=>{a(x=>{if(!(b in x))return x;const k={...x};return delete k[b],k})},[]),g=u.useCallback(b=>{y(b),d(b)},[y,d]),m=u.useCallback(()=>{a({}),p()},[p]),h=u.useMemo(()=>Object.keys(s),[s]),v=u.useMemo(()=>({open:l,close:g,closeTop:c,closeAll:m,stack:n,minimize:f,restore:y,minimizedStack:h}),[l,g,c,m,n,f,y,h]);return u.createElement(zi.Provider,{value:v},e,u.createElement(uy,{minimized:s,onRestore:y,t,key:"__min_bar"}))}function yo(){const e=u.useContext(zi);if(!e)throw new Error("useEditorModal must be used within EditorModalProvider");return e}function cy(){const e=u.useContext(zi);if(!e)return null;const{stack:t,close:n,minimizedStack:o}=e,r=u.useMemo(()=>new Set(o),[o]);return u.useEffect(()=>{if(t.length===0)return;const s=a=>{if(a.key!=="Escape"&&a.key!=="Esc")return;const l=t.filter(c=>!r.has(c.id)),d=l[l.length-1];d?.closeOnEscape&&(a.preventDefault(),a.stopPropagation(),n(d.id))};return window.addEventListener("keydown",s,!0),()=>window.removeEventListener("keydown",s,!0)},[t,n,r]),t.length===0?null:i.jsx(i.Fragment,{children:t.map((s,a)=>i.jsx(fy,{entry:s,depth:a,hidden:r.has(s.id),onClose:()=>n(s.id)},s.id))})}function uy({minimized:e,onRestore:t,t:n}){const o=n??(s=>s),r=Object.keys(e);return r.length===0?null:i.jsx("div",{className:"g7le-minimized-bar","data-testid":"g7le-minimized-bar",style:{position:"fixed",left:16,bottom:16,display:"flex",gap:8,zIndex:9300},children:r.map(s=>i.jsxs("button",{type:"button","data-testid":`g7le-minimized-${s}`,onClick:()=>t(s),style:{display:"flex",alignItems:"center",gap:6,padding:"8px 12px",background:"#1e293b",color:"#fff",border:"none",borderRadius:8,boxShadow:"0 8px 24px rgba(15, 23, 42, 0.3)",cursor:"pointer",fontSize:13},children:[i.jsx("span",{"aria-hidden":"true",children:"🗗"}),i.jsx("span",{children:e[s]||o("layout_editor.modal.minimized_default")}),i.jsx("span",{style:{opacity:.7},children:o("layout_editor.modal.restore")})]},s))})}function py(e,t,n){if(!e||typeof window>"u")return null;const o=window.innerWidth,r=window.innerHeight,s=16,a=e.left,l=o-(e.left+e.width),d=t+s;let c;if(l>=a&&l>=d)c=e.left+e.width+s;else if(a>=d)c=Math.max(s,e.left-s-t);else return null;c=Math.min(Math.max(s,c),Math.max(s,o-t-s));let p=e.top;return p=Math.min(Math.max(s,p),Math.max(s,r-n-s)),{left:c,top:p}}const Tl="clamp(16px, 6vh, 56px)";function fy({entry:e,depth:t,hidden:n,onClose:o}){const r=9100+t*10,s=e.maxHeightRatio>0?`${e.maxHeightRatio*100}vh`:void 0,a=e.draggable?s:`calc(100vh - (${Tl}) * 2)`,[l,d]=u.useState(null),c=u.useRef(null),p=u.useRef(null);u.useEffect(()=>{if(!e.draggable)return;const v=c.current;if(!v)return;const b=v.getBoundingClientRect(),x=py(e.avoidRect,b.width,b.height);x&&d(x)},[e.id,e.draggable]);const f=u.useCallback(v=>{if(!e.draggable)return;const b=v.target;if(!b.closest("[data-modal-drag-handle]")||b.closest("button, a, input, textarea, select"))return;const k=c.current,w=k?.getBoundingClientRect(),_=l?.left??w?.left??0,S=l?.top??w?.top??0;p.current={startX:v.clientX,startY:v.clientY,baseLeft:_,baseTop:S};const E=N=>{const $=p.current;if(!$)return;const A=window.innerWidth,z=window.innerHeight,D=k?.offsetWidth??0,O=k?.offsetHeight??0;let M=$.baseLeft+(N.clientX-$.startX),B=$.baseTop+(N.clientY-$.startY);M=Math.min(Math.max(0,M),Math.max(0,A-D)),B=Math.min(Math.max(0,B),Math.max(0,z-O)),d({left:M,top:B})},C=()=>{p.current=null,window.removeEventListener("pointermove",E),window.removeEventListener("pointerup",C)};window.addEventListener("pointermove",E),window.addEventListener("pointerup",C)},[e.draggable,l]),y=v=>{e.closeOnBackdrop&&v.target===v.currentTarget&&o()},g=e.draggable&&l!==null,m=e.draggable?{position:"fixed",inset:0,background:"transparent",pointerEvents:"none",zIndex:r,...n?{display:"none"}:{}}:{position:"fixed",inset:0,background:"rgba(15, 23, 42, 0.45)",display:n?"none":"flex",alignItems:"flex-start",justifyContent:"center",paddingTop:Tl,boxSizing:"border-box",zIndex:r},h={background:"#fff",borderRadius:10,boxShadow:"0 20px 60px rgba(15, 23, 42, 0.25)",width:e.width,height:e.height,maxHeight:a,maxWidth:"92vw",overflow:"hidden",display:"flex",flexDirection:"column",...e.draggable?{position:"absolute",pointerEvents:"auto",left:g?l.left:"50%",top:g?l.top:"50%",transform:g?void 0:"translate(-50%, -50%)"}:{}};return i.jsx("div",{className:"g7le-modal-backdrop",role:"presentation","data-testid":`g7le-modal-backdrop-${e.id}`,"data-draggable":e.draggable?"true":"false",onClick:y,style:m,children:i.jsx("div",{ref:c,className:"g7le-modal",role:"dialog","aria-modal":"true","aria-label":e.ariaLabel||void 0,"data-testid":`g7le-modal-${e.id}`,"data-positioned":g?"true":"false",onClick:v=>v.stopPropagation(),onPointerDown:e.draggable?f:void 0,style:h,children:e.content})})}function wt({baseStyle:e,hoverStyle:t,disabled:n,style:o,children:r,...s}){const[a,l]=u.useState(!1),d={outline:"none",...e,...o,...a&&!n?t:{}};return i.jsx("button",{type:"button",disabled:n,onMouseEnter:()=>l(!0),onMouseLeave:()=>l(!1),style:d,...s,children:r})}function gy({templateIdentifier:e,templateName:t,templateVersion:n,templateList:o,onSwitch:r}){const{t:s}=tt(),[a,l]=u.useState(!1),[d,c]=u.useState(!1),p=u.useRef(null);u.useEffect(()=>{if(!a)return;const v=x=>{p.current&&!p.current.contains(x.target)&&l(!1)},b=x=>{x.key==="Escape"&&l(!1)};return document.addEventListener("mousedown",v),document.addEventListener("keydown",b),()=>{document.removeEventListener("mousedown",v),document.removeEventListener("keydown",b)}},[a]);const f=o??[],y=f.some(v=>v.identifier!==e),g=!!r&&y,m=t||e,h=v=>{if(v===e){l(!1);return}l(!1),r?.(v)};return i.jsxs("div",{ref:p,className:"g7le-toolbar__template-switcher",style:{position:"relative",display:"inline-flex"},children:[i.jsxs("button",{type:"button",className:"g7le-toolbar__template","data-testid":"g7le-toolbar-template-label","data-can-switch":g?"true":"false","aria-haspopup":g?"listbox":void 0,"aria-expanded":g?a:void 0,disabled:!g,onClick:()=>g&&l(v=>!v),onMouseEnter:()=>c(!0),onMouseLeave:()=>c(!1),title:g?s("layout_editor.chrome.toolbar.switch_template"):m,style:{display:"inline-flex",alignItems:"center",gap:6,padding:"4px 8px",fontSize:13,fontWeight:600,color:"#0f172a",letterSpacing:.1,borderRadius:6,borderWidth:1,borderStyle:"solid",borderColor:g&&(d||a)?"#cbd5e1":"transparent",backgroundColor:g&&(d||a)?"#f1f5f9":"transparent",cursor:g?"pointer":"default",outline:"none",transition:"background-color 120ms, border-color 120ms"},children:[i.jsx("span",{className:"g7le-toolbar__template-name","data-testid":"g7le-toolbar-template-name",children:m}),n&&i.jsxs("span",{className:"g7le-toolbar__template-version","data-testid":"g7le-toolbar-template-version",style:{fontSize:11,fontWeight:500,color:"#64748b",padding:"1px 6px",borderRadius:999,background:"#f1f5f9",border:"1px solid #e2e8f0"},children:["v",n]}),g&&i.jsx("span",{className:"g7le-toolbar__template-caret","aria-hidden":"true",style:{fontSize:9,color:"#94a3b8",marginLeft:1},children:"▼"})]}),g&&a&&i.jsx("div",{className:"g7le-toolbar__template-menu","data-testid":"g7le-toolbar-template-menu",role:"listbox",style:{position:"absolute",top:"calc(100% + 4px)",left:0,minWidth:240,maxWidth:360,maxHeight:360,overflowY:"auto",background:"#ffffff",border:"1px solid #e2e8f0",borderRadius:8,boxShadow:"0 8px 24px rgba(15, 23, 42, 0.14)",zIndex:30,padding:4},children:f.map(v=>{const b=v.identifier===e;return i.jsxs("button",{type:"button",role:"option","aria-selected":b,"aria-current":b?"true":void 0,className:"g7le-toolbar__template-menu-item","data-testid":"g7le-toolbar-template-menu-item","data-identifier":v.identifier,"data-current":b?"true":"false",onClick:()=>h(v.identifier),style:{display:"flex",alignItems:"center",justifyContent:"space-between",gap:8,width:"100%",padding:"8px 10px",fontSize:13,color:"#1e293b",background:b?"#eff6ff":"transparent",border:"none",borderRadius:6,cursor:b?"default":"pointer",textAlign:"left",outline:"none"},onMouseEnter:x=>{b||(x.currentTarget.style.background="#f1f5f9")},onMouseLeave:x=>{x.currentTarget.style.background=b?"#eff6ff":"transparent"},children:[i.jsxs("span",{style:{display:"flex",alignItems:"center",gap:8,minWidth:0},children:[i.jsx("span",{"aria-hidden":"true",style:{width:12,flex:"0 0 auto",color:"#2563eb",fontSize:12,textAlign:"center"},children:b?"✓":""}),i.jsxs("span",{style:{display:"flex",flexDirection:"column",gap:2,minWidth:0},children:[i.jsx("span",{style:{fontWeight:600,color:b?"#1d4ed8":"#1e293b",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},children:v.name||v.identifier}),i.jsx("span",{style:{fontSize:11,color:"#94a3b8",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},children:v.identifier})]})]}),i.jsxs("span",{style:{display:"inline-flex",alignItems:"center",gap:6,flex:"0 0 auto"},children:[i.jsx("span",{style:{fontSize:10,fontWeight:600,color:v.type==="admin"?"#7c3aed":"#0891b2",textTransform:"uppercase",letterSpacing:.4},children:v.type}),v.version&&i.jsxs("span",{style:{fontSize:11,color:"#64748b"},children:["v",v.version]})]})]},v.identifier)})})]})}function hy(e={}){const{onSave:t,isDirty:n=!1,onExit:o,onEditCode:r,onManageImages:s,onManageTranslations:a,onPageSettings:l,onReset:d,onShowShortcuts:c,onPreview:p,onShowVersions:f,templateName:y,templateVersion:g,templateList:m,onSwitchTemplate:h}=e,[v,b]=u.useState(!1),[x,k]=u.useState(!1),{t:w}=tt(),{state:_,dispatch:S}=Je(),[E,C]=u.useState(null);u.useEffect(()=>{const I=()=>{const F=window.__g7LayoutEditorHistory;C(F?{undo:F.undo,redo:F.redo,canUndo:!!F.canUndo,canRedo:!!F.canRedo}:null)};I();const P=window.setInterval(I,250);return()=>window.clearInterval(P)},[]);const N=()=>{S({type:"TOGGLE_ROUTE_TREE"})},$=()=>_.editMode==="base"?w("layout_editor.chrome.toolbar.mode_badge.base"):_.editMode==="modal"?w("layout_editor.chrome.toolbar.mode_badge.modal"):_.editMode==="extension"?w("layout_editor.chrome.toolbar.mode_badge.extension"):_.editMode==="iteration_item"?w("layout_editor.chrome.toolbar.mode_badge.iteration_item"):null,A=()=>{_.editMode==="base"?S({type:"EXIT_BASE_EDIT"}):_.editMode==="modal"?S({type:"EXIT_MODAL_EDIT"}):_.editMode==="extension"?S({type:"EXIT_EXTENSION_EDIT"}):_.editMode==="iteration_item"&&S({type:"EXIT_ITERATION_ITEM_EDIT"})},z=$(),D=_.editMode!=="route",O={display:"inline-flex",alignItems:"center",gap:6,padding:"6px 12px",fontSize:13,fontWeight:500,borderRadius:6,borderWidth:1,borderStyle:"solid",borderColor:"transparent",backgroundColor:"transparent",color:"#475569",cursor:"pointer",transition:"background-color 120ms, color 120ms, border-color 120ms"},M={...O,backgroundColor:"#2563eb",color:"#ffffff",borderColor:"#2563eb",fontWeight:600},B={...O,borderColor:"#e2e8f0",backgroundColor:"#ffffff",color:"#0f172a"},T={backgroundColor:"#f1f5f9",color:"#0f172a",borderColor:"#cbd5e1"},H={backgroundColor:"#1d4ed8",borderColor:"#1d4ed8"},L={backgroundColor:"#f8fafc",borderColor:"#cbd5e1"};return i.jsxs("div",{className:"g7le-toolbar","data-testid":"g7le-toolbar",role:"toolbar",style:{display:"flex",alignItems:"center",gap:6,padding:"8px 12px",borderBottom:"1px solid #e2e8f0",background:"#ffffff",boxShadow:"0 1px 2px rgba(15, 23, 42, 0.04)"},children:[i.jsx("style",{children:"@keyframes g7le-spin { to { transform: rotate(360deg); } }.g7le-toolbar > * { flex-shrink: 0; white-space: nowrap; }"}),i.jsx("button",{type:"button",className:"g7le-toolbar__route-tree-toggle","data-testid":"g7le-toolbar-toggle-route-tree","aria-label":w("layout_editor.chrome.toolbar.toggle_route_tree"),title:w("layout_editor.chrome.toolbar.toggle_route_tree"),onClick:N,style:{...O,outline:"none",padding:"6px 10px",fontSize:16,lineHeight:1,color:"#64748b"},children:"☰"}),i.jsx(gy,{templateIdentifier:_.templateIdentifier,templateName:y,templateVersion:g,templateList:m,onSwitch:h}),z&&i.jsx("span",{className:"g7le-toolbar__mode-badge","data-testid":"g7le-toolbar-mode-badge","data-mode":_.editMode,style:{padding:"2px 8px",borderRadius:999,background:"#fef3c7",color:"#92400e",fontSize:11,fontWeight:600,border:"1px solid #fde68a"},children:z}),i.jsx("div",{style:{flex:1}}),i.jsxs(i.Fragment,{children:[i.jsx(wt,{"data-testid":"g7le-toolbar-undo","aria-label":w("layout_editor.chrome.toolbar.undo"),title:w("layout_editor.chrome.toolbar.undo"),disabled:!E?.canUndo,onClick:()=>E?.undo?.(),baseStyle:O,hoverStyle:T,style:{opacity:E?.canUndo?1:.4},children:"↶"}),i.jsx(wt,{"data-testid":"g7le-toolbar-redo","aria-label":w("layout_editor.chrome.toolbar.redo"),title:w("layout_editor.chrome.toolbar.redo"),disabled:!E?.canRedo,onClick:()=>E?.redo?.(),baseStyle:O,hoverStyle:T,style:{opacity:E?.canRedo?1:.4},children:"↷"}),i.jsxs(wt,{"data-testid":"g7le-toolbar-add-element","data-open":_.isPaletteOpen?"true":"false",disabled:!_.selectedRoute,onClick:()=>S({type:"TOGGLE_PALETTE"}),"aria-pressed":_.isPaletteOpen,baseStyle:O,hoverStyle:T,style:_.isPaletteOpen?{backgroundColor:"#eff6ff",color:"#1d4ed8",borderColor:"#93c5fd"}:void 0,children:["+ ",w("layout_editor.chrome.toolbar.add_element")]}),i.jsx(wt,{"data-testid":"g7le-toolbar-preview","data-previewing":x?"true":"false",disabled:!p||!_.selectedRoute?.layoutName||x,onClick:()=>{const I=p?.();I&&typeof I.then=="function"&&(k(!0),I.finally(()=>k(!1)))},baseStyle:O,hoverStyle:T,style:{opacity:x?.7:1},children:x?i.jsxs(i.Fragment,{children:[i.jsx("span",{"data-testid":"g7le-toolbar-preview-spinner","aria-hidden":"true",style:{display:"inline-block",width:12,height:12,border:"2px solid rgba(71,85,105,0.35)",borderTopColor:"#475569",borderRadius:"50%",animation:"g7le-spin 600ms linear infinite"}}),w("layout_editor.chrome.toolbar.previewing")]}):i.jsxs(i.Fragment,{children:["👁 ",w("layout_editor.chrome.toolbar.preview")]})}),i.jsxs(wt,{"data-testid":"g7le-toolbar-versions",disabled:!f||!(_.selectedRoute?.layoutName||_.selectedRoute?.path?.startsWith("__extension__/")),onClick:()=>f?.(),"aria-label":w("layout_editor.chrome.toolbar.version_history"),title:w("layout_editor.chrome.toolbar.version_history"),baseStyle:O,hoverStyle:T,children:["🕘 ",w("layout_editor.chrome.toolbar.version_history")]}),i.jsxs(wt,{"data-testid":"g7le-toolbar-edit-code",disabled:!r,onClick:()=>r?.(),baseStyle:O,hoverStyle:T,children:["⟨/⟩ ",w("layout_editor.chrome.toolbar.edit_code")]}),i.jsxs(wt,{"data-testid":"g7le-toolbar-images",disabled:!s||!_.selectedRoute,onClick:()=>s?.(),baseStyle:O,hoverStyle:T,children:["🖼 ",w("layout_editor.chrome.toolbar.images")]}),i.jsxs(wt,{"data-testid":"g7le-toolbar-translations",disabled:!a||!_.selectedRoute,onClick:()=>a?.(),baseStyle:O,hoverStyle:T,children:["🌐 ",w("layout_editor.chrome.toolbar.translations")]}),i.jsxs(wt,{"data-testid":"g7le-toolbar-page-settings",disabled:!l||!_.selectedRoute||_.editMode==="extension"||_.editMode==="iteration_item",onClick:()=>l?.(),baseStyle:O,hoverStyle:T,children:["⚙ ",w("layout_editor.chrome.toolbar.page_settings")]}),i.jsxs(wt,{"data-testid":"g7le-toolbar-reset",disabled:!d||!n,onClick:()=>d?.(),"aria-label":w("layout_editor.chrome.toolbar.reset"),title:w("layout_editor.chrome.toolbar.reset"),baseStyle:O,hoverStyle:T,style:{opacity:n?1:.4},children:["↺ ",w("layout_editor.chrome.toolbar.reset")]}),i.jsx(wt,{"data-testid":"g7le-toolbar-save","data-dirty":n?"true":"false","data-saving":v?"true":"false",disabled:!t||v,onClick:()=>{const I=t?.();I&&typeof I.then=="function"&&(b(!0),I.finally(()=>b(!1)))},baseStyle:M,hoverStyle:H,style:{opacity:v?.7:n?1:.7},children:v?i.jsxs(i.Fragment,{children:[i.jsx("span",{"data-testid":"g7le-toolbar-save-spinner","aria-hidden":"true",className:"g7le-toolbar__spinner",style:{display:"inline-block",width:12,height:12,border:"2px solid rgba(255,255,255,0.4)",borderTopColor:"#ffffff",borderRadius:"50%",animation:"g7le-spin 600ms linear infinite"}}),w("layout_editor.chrome.toolbar.saving")]}):i.jsxs(i.Fragment,{children:["💾 ",w("layout_editor.chrome.toolbar.save")]})}),i.jsx(wt,{"data-testid":"g7le-toolbar-shortcuts",disabled:!c,onClick:()=>c?.(),"aria-label":w("layout_editor.shortcuts.title"),title:w("layout_editor.shortcuts.title"),baseStyle:O,hoverStyle:T,children:"⌨"}),i.jsxs(wt,{"data-testid":"g7le-toolbar-exit",disabled:!o,onClick:()=>o?.(),baseStyle:B,hoverStyle:L,children:["✕ ",w("layout_editor.chrome.toolbar.exit")]})]}),D&&i.jsxs(wt,{"data-testid":"g7le-toolbar-exit-alt-mode",onClick:A,baseStyle:B,hoverStyle:L,children:["← ",_.editMode==="base"&&w("layout_editor.chrome.toolbar.exit_base_edit"),_.editMode==="modal"&&w("layout_editor.chrome.toolbar.exit_modal_edit"),_.editMode==="extension"&&w("layout_editor.chrome.toolbar.exit_extension_edit"),_.editMode==="iteration_item"&&w("layout_editor.chrome.toolbar.exit_iteration_item_edit")]})]})}function ar(){if(typeof window>"u")return null;try{return window.localStorage?.getItem("auth_token")??null}catch{return null}}function je(e){const t={Accept:"application/json",...e??{}},n=ar();return n&&(t.Authorization=`Bearer ${n}`),t}function Mi(e){return encodeURIComponent(e)}async function Dl(e,t){const n=`/api/admin/templates/${Mi(e)}/layout-attachments?layout_name=${encodeURIComponent(t)}`;try{const o=await fetch(n,{method:"GET",headers:je(),credentials:"same-origin"}),r=await o.json().catch(()=>null);return!o.ok||!r?.success?{ok:!1,status:o.status,message:Li(r,o.status)}:{ok:!0,data:Array.isArray(r?.data)?r.data:[]}}catch(o){return{ok:!1,status:0,message:o instanceof Error?o.message:"network error"}}}async function Nl(e,t,n){const o=`/api/admin/templates/${Mi(e)}/layout-attachments`,r=new FormData;r.append("file",n),r.append("layout_name",t);try{const s=await fetch(o,{method:"POST",headers:je(),body:r,credentials:"same-origin"}),a=await s.json().catch(()=>null);return!s.ok||!a?.success||!a?.data?.url?{ok:!1,status:s.status,message:Li(a,s.status)}:{ok:!0,data:a.data}}catch(s){return{ok:!1,status:0,message:s instanceof Error?s.message:"network error"}}}async function zl(e){const t=`/api/admin/templates/layout-attachments/${Mi(String(e))}`;try{const n=await fetch(t,{method:"DELETE",headers:je(),credentials:"same-origin"}),o=await n.json().catch(()=>null);return!n.ok||o&&o.success===!1?{ok:!1,status:n.status,message:Li(o,n.status)}:{ok:!0,data:!0}}catch(n){return{ok:!1,status:0,message:n instanceof Error?n.message:"network error"}}}function Li(e,t){if(e&&typeof e=="object"&&"message"in e){const n=e.message;if(typeof n=="string"&&n.length>0)return n}return`HTTP ${t}`}function yy(e){return e<1024?`${e} B`:e<1024*1024?`${Math.round(e/1024)} KB`:`${(e/(1024*1024)).toFixed(1)} MB`}function by(e){if(!e)return"";const t=e.match(/^(\d{4})-(\d{2})-(\d{2})/);return t?`${t[2]}-${t[3]}`:""}function Ml({templateIdentifier:e,layoutName:t,t:n,onSelect:o,onChanged:r,onClose:s}){const[a,l]=u.useState([]),[d,c]=u.useState("idle"),[p,f]=u.useState(!1),[y,g]=u.useState(null),m=u.useRef(null),h=u.useCallback(async()=>{c("loading"),g(null);const x=await Dl(e,t);x.ok?(l(x.data),c("idle")):(g(x.message),c("error"))},[e,t]);u.useEffect(()=>{h()},[h]);const v=async x=>{const k=x.target.files?.[0];if(!k)return;f(!0),g(null);const w=await Nl(e,t,k);w.ok?(l(_=>[w.data,..._.filter(S=>S.id!==w.data.id)]),r?.()):g(w.message),f(!1),m.current&&(m.current.value="")},b=async x=>{if(!(typeof window<"u"&&typeof window.confirm=="function"?window.confirm(n("layout_editor.attachment_manager.delete_confirm")):!0))return;const w=await zl(x.id);w.ok?(l(_=>_.filter(S=>S.id!==x.id)),r?.()):g(w.message)};return i.jsxs("div",{className:"g7le-attachment-manager","data-testid":"g7le-attachment-manager",style:my,children:[i.jsxs("div",{"data-modal-drag-handle":!0,style:vy,children:[i.jsx("span",{style:{fontSize:14,fontWeight:700},"data-testid":"g7le-attachment-manager-title",children:n("layout_editor.attachment_manager.title")}),i.jsx("button",{type:"button","aria-label":"close",onClick:s,style:_y,"data-testid":"g7le-attachment-manager-close",children:"✕"})]}),i.jsx("div",{style:xy,children:i.jsxs("label",{style:wy,"data-testid":"g7le-attachment-upload",children:[p?"…":`⬆ ${n("layout_editor.attachment_manager.upload")}`,i.jsx("input",{ref:m,type:"file",accept:"image/*",onChange:v,disabled:p,style:{display:"none"},"data-testid":"g7le-attachment-upload-input"})]})}),y&&i.jsx("div",{style:ky,"data-testid":"g7le-attachment-manager-error",children:y}),i.jsx("div",{style:Sy,children:d==="loading"?i.jsx("div",{style:Ll,"data-testid":"g7le-attachment-manager-loading",children:n("layout_editor.attachment_manager.loading")}):a.length===0&&d!=="error"?i.jsx("div",{style:Ll,"data-testid":"g7le-attachment-manager-empty",children:n("layout_editor.attachment_manager.empty")}):i.jsx("div",{style:Cy,"data-testid":"g7le-attachment-grid",children:a.map(x=>i.jsxs("div",{style:$y,"data-testid":`g7le-attachment-card-${x.id}`,children:[i.jsx("div",{style:{...Ey,backgroundImage:`url(${x.url})`}}),i.jsx("div",{style:Iy,title:x.original_name,children:x.original_name}),i.jsxs("div",{style:Ay,children:[yy(x.size),x.created_at?` · ${by(x.created_at)}`:""]}),i.jsxs("div",{style:Ty,children:[o&&i.jsx("button",{type:"button",onClick:()=>o(x.url),style:Dy,"data-testid":`g7le-attachment-use-${x.id}`,children:n("layout_editor.attachment_manager.use_as_bg")}),i.jsx("button",{type:"button",onClick:()=>b(x),style:Ny,"data-testid":`g7le-attachment-delete-${x.id}`,children:"🗑"})]})]},x.id))})}),i.jsx("div",{style:zy,children:i.jsx("button",{type:"button",onClick:s,style:My,"data-testid":"g7le-attachment-manager-footer-close",children:n("layout_editor.attachment_manager.close")})})]})}const my={display:"flex",flexDirection:"column",minHeight:0,flex:1},vy={display:"flex",alignItems:"center",justifyContent:"space-between",padding:"12px 16px",borderBottom:"1px solid #e2e8f0"},_y={border:"none",background:"transparent",fontSize:16,color:"#94a3b8",cursor:"pointer"},xy={padding:"10px 16px",borderBottom:"1px solid #f1f5f9",display:"flex",gap:8},wy={padding:"6px 12px",fontSize:13,border:"1px solid #2563eb",borderRadius:6,background:"#fff",color:"#2563eb",cursor:"pointer",display:"inline-flex",alignItems:"center"},ky={padding:"8px 16px",fontSize:12,color:"#b91c1c",background:"#fef2f2"},Sy={padding:16,overflow:"auto",flex:1,minHeight:120},Ll={fontSize:13,color:"#94a3b8",textAlign:"center",padding:"32px 0"},Cy={display:"grid",gridTemplateColumns:"repeat(auto-fill, minmax(140px, 1fr))",gap:12},$y={display:"flex",flexDirection:"column",gap:4,border:"1px solid #e2e8f0",borderRadius:8,padding:8},Ey={width:"100%",height:88,borderRadius:6,border:"1px solid #cbd5e1",backgroundSize:"cover",backgroundPosition:"center",backgroundColor:"#f8fafc"},Iy={fontSize:12,fontWeight:600,color:"#0f172a",whiteSpace:"nowrap",overflow:"hidden",textOverflow:"ellipsis"},Ay={fontSize:11,color:"#64748b"},Ty={display:"flex",gap:6,marginTop:2},Dy={flex:1,padding:"4px 6px",fontSize:11,border:"1px solid #2563eb",borderRadius:6,background:"#2563eb",color:"#fff",cursor:"pointer"},Ny={padding:"4px 8px",fontSize:12,border:"1px solid #fecaca",borderRadius:6,background:"#fff",color:"#dc2626",cursor:"pointer"},zy={padding:"10px 16px",borderTop:"1px solid #e2e8f0",display:"flex",justifyContent:"flex-end"},My={padding:"6px 14px",fontSize:13,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"};function lr(e){return encodeURIComponent(e)}async function Ly(e,t,n){const o=new URLSearchParams;t&&o.set("layout_name",t);const r=o.toString(),s=`/api/admin/templates/${lr(e)}/custom-translations`+(r?`?${r}`:"");try{const a=await fetch(s,{method:"GET",headers:je(),credentials:"same-origin"}),l=await a.json().catch(()=>null);return!a.ok||!l?.success?{ok:!1,status:a.status,message:dr(l,a.status)}:{ok:!0,data:Array.isArray(l?.data)?l.data:[]}}catch(a){return{ok:!1,status:0,message:a instanceof Error?a.message:"network error"}}}async function Py(e,t,n,o){const r=`/api/admin/templates/${lr(e)}/custom-translations/${t}`;try{const s=await fetch(r,{method:"PUT",headers:je({"Content-Type":"application/json"}),body:JSON.stringify({values:n,expected_lock_version:o}),credentials:"same-origin"}),a=await s.json().catch(()=>null);return!s.ok||!a?.success||!a?.data?{ok:!1,status:s.status,message:dr(a,s.status)}:{ok:!0,data:a.data}}catch(s){return{ok:!1,status:0,message:s instanceof Error?s.message:"network error"}}}async function Oy(e,t){const n=`/api/admin/templates/${lr(e)}/custom-translations/${t}`;try{const o=await fetch(n,{method:"DELETE",headers:je(),credentials:"same-origin"}),r=await o.json().catch(()=>null);return!o.ok||r&&r.success===!1?{ok:!1,status:o.status,message:dr(r,o.status)}:{ok:!0,data:!0}}catch(o){return{ok:!1,status:0,message:o instanceof Error?o.message:"network error"}}}async function By(e,t){const n=`/api/admin/templates/${lr(e)}/custom-translations`;try{const o=await fetch(n,{method:"DELETE",headers:je({"Content-Type":"application/json"}),body:JSON.stringify({ids:t}),credentials:"same-origin"}),r=await o.json().catch(()=>null);return!o.ok||!r?.success?{ok:!1,status:o.status,message:dr(r,o.status)}:{ok:!0,data:{deleted:typeof r?.data?.deleted=="number"?r.data.deleted:t.length}}}catch(o){return{ok:!1,status:0,message:o instanceof Error?o.message:"network error"}}}const Pl=/\$t:(custom\.[A-Za-z0-9_\-]+\.\d+)/g;function Wy(e){const t=new Set,n=o=>{if(typeof o=="string"){if(o.includes("$t:custom.")){let r;for(Pl.lastIndex=0;(r=Pl.exec(o))!==null;)t.add(r[1])}return}if(Array.isArray(o)){for(const r of o)n(r);return}if(o&&typeof o=="object")for(const r of Object.values(o))n(r)};return n(e),t}function dr(e,t){if(e&&typeof e=="object"&&"message"in e){const n=e.message;if(typeof n=="string"&&n.length>0)return n}return`HTTP ${t}`}const Pt=Lt().TranslationEngine,bo=Lt().createLogger,Pi=bo("EditorSpecLoader"),jy=["controls","componentCapabilities","actionRecipes","conditionRecipes","pageSettings","initActionRecipes","errorRecipes","computedRecipes","loadingComponents"],Hy=["actionRecipes","initActionRecipes","errorRecipes","computedRecipes","loadingComponents"];function Ol(e){if(typeof window>"u")return[];const n=window.G7Config?.[e];if(!Array.isArray(n))return[];const o=[];for(const r of n){const s=r?.identifier;typeof s=="string"&&s.length>0&&o.push(s)}return o}async function Fy(e,t,n,o){const s=`${n}/${e==="template"?"templates":e==="module"?"modules":"plugins"}/${encodeURIComponent(t)}/editor-spec`;try{const a=await o(s);if(!a.ok)return a.status!==404&&Pi.warn(`fetchOneSpec: HTTP ${a.status}`,s),null;const l=await a.json();return!l?.success||!l.data?null:l.data.spec??null}catch(a){return Pi.warn("fetchOneSpec: fetch failed",s,a),null}}function Ky(e){return Hy.includes(e)}function Vy(e,t){return e==null||typeof e!="object"?e:{...e,__source:t}}function Uy(e,t,n,o){const r=t[n];if(!r||typeof r!="object")return;const s=e[n]??{};if(o&&Ky(n)){const a={};for(const[l,d]of Object.entries(r))a[l]=Vy(d,o);e[n]={...s,...a};return}e[n]={...s,...r}}function qy(e,t){if(!t.nesting)return;const n=e.nesting??{},o=new Set(n.draggable??[]);for(const r of t.nesting.draggable??[])o.add(r);n.draggable=Array.from(o),n.containers={...n.containers??{},...t.nesting.containers??{}},e.nesting=n}function Gy(e,t){if(!t.componentPalette)return;const n=e.componentPalette??{},o=[...n.groups??[],...t.componentPalette.groups??[]];n.groups=o,n.entries={...n.entries??{},...t.componentPalette.entries??{}},e.componentPalette=n}function Yy(e,t){if(!t.states)return;const n=e.states??{},o=[...n.groups??[],...t.states.groups??[]];n.groups=o,e.states=n}function Xy(e,t){if(!Array.isArray(t.stateLabels)||t.stateLabels.length===0)return;const n=[...e.stateLabels??[],...t.stateLabels],o=new Map;for(const r of n)!r||typeof r.key!="string"||typeof r.scope!="string"||o.set(`${r.scope}:${r.key}`,r);e.stateLabels=Array.from(o.values())}function Jy(e,t){if(!t.actionChipCandidates)return;const n=e.actionChipCandidates??{};for(const o of["response","error","payload"]){const r=t.actionChipCandidates[o];!Array.isArray(r)||r.length===0||(n[o]=[...n[o]??[],...r])}e.actionChipCandidates=n}function Zy(e,t){if(!t.sampleData)return;const n=e.sampleData??{};n.byDataSourceId={...n.byDataSourceId??{},...t.sampleData.byDataSourceId??{}},n.byEndpointPattern={...n.byEndpointPattern??{},...t.sampleData.byEndpointPattern??{}},e.sampleData=n}function Bl(e,t,n){for(const o of jy)Uy(e,t,o,n);qy(e,t),Gy(e,t),Yy(e,t),Xy(e,t),Jy(e,t),Zy(e,t),t.version&&(e.version=t.version)}async function Qy(e){const t=e.apiBaseUrl??"/api",n=e.fetcher??(typeof fetch<"u"?fetch:void 0);if(!n)return Pi.warn("loadEditorSpecBundle: fetch unavailable, returning null spec"),{spec:null,sampleGlobalSources:[]};const o=e.activeModuleIdentifiers??Ol("activeModules"),r=e.activePluginIdentifiers??Ol("activePlugins"),s=[...o.map(p=>({kind:"module",id:p})),...r.map(p=>({kind:"plugin",id:p})),{kind:"template",id:e.templateIdentifier}],a=await Promise.all(s.map(p=>Fy(p.kind,p.id,t,n)));let l=null;const d=[],c={};if(e.coreSeed&&(l={},Bl(l,e.coreSeed,{kind:"core"})),s.forEach((p,f)=>{const y=a[f];if(!y)return;y.sampleGlobal&&typeof y.sampleGlobal=="object"&&d.push({id:p.id,kind:p.kind,sampleGlobal:y.sampleGlobal});const g=y.sampleData?.byDataSourceId;if(g&&typeof g=="object"){const m=p.kind==="template"?"template":`${p.kind}:${p.id}`;c[m]={...c[m]??{},...g}}l===null&&(l={}),Bl(l,y,{kind:p.kind,id:p.id})}),l!==null&&Object.keys(c).length>0){const p=l;p.sampleData={...p.sampleData??{},bySource:c}}return{spec:l,sampleGlobalSources:d}}function Oi(e,t){return e?.componentCapabilities?.[t]??null}function Ct(e,t){return e?.controls?.[t]??null}function Ry(e){return e?.initActionRecipes??{}}function eb(e){return e?.computedRecipes??{}}function tb(e){return e?.errorRecipes??{}}function nb(e){const t=e?.loadingComponents;return!t||typeof t!="object"?[]:Object.values(t).filter(n=>n!=null&&typeof n=="object"&&typeof n.name=="string")}function ob(e,t,n){const o=e?.stateLabels;if(!Array.isArray(o))return null;for(const r of o)if(r?.scope===t&&r?.key===n)return typeof r.label_key=="string"?r.label_key:null;return null}function Bi(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)}function rb(e){return e===null?"null":typeof e=="string"?e.length>40?`${e.slice(0,40)}…`:e:typeof e=="number"||typeof e=="boolean"?String(e):""}function ib(e){const t=e.find(n=>Bi(n));if(Bi(t))return Object.keys(t).slice(0,8)}function sb(e){return Object.keys(e).slice(0,8)}function ab(e){const t=Object.entries(e);return t.length===0?!1:t.some(([,n])=>n===null||typeof n!="object")}function Wi(e,t,n,o,r){if(Array.isArray(e)){r(t,"array",`[${e.length}]`,ib(e));return}if(Bi(e)){if(t&&ab(e)){const s=sb(e);r(t,"object",`{${s.length}}`,s)}if(n>=o)return;for(const[s,a]of Object.entries(e)){const l=t?`${t}.${s}`:s;Wi(a,l,n+1,o,r)}return}r(t,"scalar",rb(e))}function lb(e,t,n){const o=[];let r=e;for(const s of t){if(!r||(r=(Array.isArray(r.children)?r.children:[])[s],!r))break;const l=r.iteration;if(!(!l||l.source===void 0)&&(typeof l.index_var=="string"&&l.index_var.length>0&&o.push({name:l.index_var,kind:"index"}),typeof l.item_var=="string"&&l.item_var.length>0)){const d=typeof l.source=="string"?l.source:"",c=d&&n?.get(Wl(d))||[];o.push({name:l.item_var,kind:"item",itemFields:c})}}return o}function Wl(e){return e.replace(/^\{\{|\}\}$/g,"").trim()}function db(e){const t=new Map;for(const n of e)n.shape==="array"&&n.itemFields&&n.itemFields.length>0&&t.set(Wl(n.expression),n.itemFields);return t}function ji(e){const t=e.maxDepth??4,n=[];for(const o of e.dataSources??[])!o||typeof o.id!="string"||o.id.length===0||Wi(o.sample,"",0,t,(r,s,a,l)=>{const d=r?`{{${o.id}.${r}}}`:`{{${o.id}}}`;n.push({expression:d,source:"data_source",sourceId:o.id,path:r,shape:s,groupLabelKey:o.labelKey?.startsWith("$t:")?o.labelKey.slice(3):o.labelKey,preview:a,itemFields:l})});for(const o of e.states??[])!o||typeof o.scope!="string"||Wi(o.tree,"",0,t,(r,s,a,l)=>{if(!r)return;const d=ob(e.spec,o.scope,r)??void 0;n.push({expression:`{{${o.scope}.${r}}}`,source:o.scope,sourceId:o.scope,path:r,shape:s,labelKey:d?.startsWith("$t:")?d.slice(3):d,preview:a,itemFields:l})});for(const o of e.iterationVars??[])if(!(!o||typeof o.name!="string"||o.name.length===0)){if(o.kind==="index"){n.push({expression:`{{${o.name}}}`,source:"iteration",sourceId:o.name,path:"",shape:"scalar",preview:"0"});continue}for(const r of o.itemFields??[])n.push({expression:`{{${o.name}.${r}}}`,source:"iteration",sourceId:o.name,path:r,shape:"scalar",preview:""})}return n}const cb=["$localized"];function ub(e){const t=e.trim(),n=t.indexOf("(");if(n===-1||!t.endsWith(")"))return{fn:null,inner:t};const o=t.slice(0,n).trim();if(!cb.includes(o))return{fn:null,inner:t};const r=t.slice(n+1,-1);let s=0,a=null;for(let l=0;ln.shape===t)}function Ki(e,t){const n=t.trim().toLowerCase();return n===""?e:e.filter(o=>[o.resolvedLabel??"",o.sourceId,o.path,o.preview].join(" ").toLowerCase().includes(n))}function jl(e,t,n){const o={response:[{path:"data",labelKey:"layout_editor.action_chip.response_data",shape:"object"},{path:"message",labelKey:"layout_editor.action_chip.response_message",shape:"scalar"}],error:[{path:"status",labelKey:"layout_editor.action_chip.error_status",shape:"scalar"},{path:"message",labelKey:"layout_editor.action_chip.error_message",shape:"scalar"},{path:"errors",labelKey:"layout_editor.action_chip.error_errors",shape:"object"},{path:"data",labelKey:"layout_editor.action_chip.error_data",shape:"object"},{path:"statusText",labelKey:"layout_editor.action_chip.error_status_text",shape:"scalar"}],payload:[{path:"data",labelKey:"layout_editor.action_chip.payload_data",shape:"object"}]},r=e==="response"?"response":e==="error"?"error":"message",s=new Set,a=[];for(const d of o[e])s.has(d.path)||(s.add(d.path),a.push(d));const l=n?.[e];if(Array.isArray(l))for(const d of l)!d||typeof d.path!="string"||typeof d.labelKey!="string"||s.has(d.path)||(s.add(d.path),a.push({path:d.path,labelKey:d.labelKey,shape:d.shape??"scalar"}));return a.map(d=>({expression:`{{${r}.${d.path}}}`,source:"_local",sourceId:r,path:d.path,shape:d.shape,labelKey:d.labelKey,preview:t(d.labelKey)}))}const Vi=/\{\{(?:[^}]|\}(?!\}))*\}\}/g;function In(e){const t=[];if(typeof e!="string"||e.length===0)return t;let n=0,o=0;const r=new RegExp(Vi.source,"g");let s;for(;(s=r.exec(e))!==null;){const a=s.index,l=a+s[0].length;a>n&&t.push({kind:"literal",raw:e.slice(n,a),start:n,end:a}),t.push({kind:"binding",raw:s[0],start:a,end:l,parsed:Kt(s[0]),bindingIndex:o++}),n=l}return nt.kind==="binding")}function Ui(e){return typeof e!="string"?!1:new RegExp(Vi.source).test(e)}function fb(e){const t=new Set(e.map(o=>o.trim()));let n=0;for(;t.has(`p${n}`);)n+=1;return`p${n}`}function Hl(e,t,n){const o=typeof e=="string"?e:"",r=Math.max(0,Math.min(t,o.length)),s=o.slice(0,r),a=o.slice(r),l=s.length>0&&!/\s$/.test(s)?" ":"",d=a.length>0&&!/^\s/.test(a)?" ":"";return`${s}${l}{${n}}${d}${a}`}function qi(e,t){const n=typeof e=="string"?e:"";return An(n).includes(t)?n:n.length===0?`{${t}}`:/\s$/.test(n)?`${n}{${t}}`:`${n} {${t}}`}function Fl(e){return cr(e).map(t=>({bindingIndex:t.bindingIndex??0,expression:t.raw,parsed:t.parsed??null,isComplex:(t.parsed??null)===null}))}function ur(e){return typeof e!="string"?"":e.replace(new RegExp(Vi.source,"g")," ").replace(/\s+/g," ").trim()}const gb=/\$t:([a-zA-Z0-9._-]+)/g,Kl="custom.";function Gi(e,t){return typeof e!="string"||e.length===0?typeof e=="string"?e:"":t?In(e).map(n=>n.kind!=="literal"?n.raw:n.raw.replace(gb,(o,r)=>{if(r.startsWith(Kl))return o;const s=t(r);return s&&s!==r?s:o})).join(""):e}const Kn="(?:(?!\\|[a-zA-Z0-9_]+=).)*",hb=new RegExp(`^\\s*\\$t:([a-zA-Z0-9._-]+)((?:\\|[a-zA-Z0-9_]+=${Kn})*)`),yb=new RegExp(`\\|([a-zA-Z0-9_]+)=(${Kn})`,"g");function Vt(e){if(typeof e!="string")return"";const t=Kt(e);if(t)return t.path||t.sourceId;const n=e.trim().replace(/^\{\{\s*/,"").replace(/\s*\}\}$/,"");let o=-1;for(let l=0;l0){const l=n.slice(0,o).trim(),d=Kt(`{{${l}}}`);if(d)return d.path||d.sourceId;const c=l.replace(/\?\./g,".").replace(/\s*\?\?.*$/,"").trim();if(/^[A-Za-z_$][\w$.]*$/.test(c)){const p=c.split(".").filter(Boolean);return p.length>1&&p.slice(1).join(".")||c}}const r=n.replace(/\s+/g," ").trim(),s=r.replace(/\s*(\?\?|\|\|)\s*(\[\s*\]|\{\s*\}|'[^']*'|"[^"]*"|\d+|null|undefined)\s*$/,"").trim(),a=/\.([A-Za-z_$][\w$]*)\s*$/.exec(s);return a?a[1]:r}const Vl=/\$(?:core|module|plugin)_settings:[a-zA-Z0-9._:-]+/g;function Ul(e){return typeof e=="string"&&new RegExp(Vl.source).test(e)}function bb(e){return typeof e!="string"?"":e.replace(/^\$(?:core|module|plugin)_settings:/,"").split(/[.:]/).filter(Boolean).pop()||e}function ql(e){const t=[];for(const n of In(e)){if(n.kind==="binding"){t.push({kind:"chip",label:Vt(n.raw)});continue}let o=0;const r=n.raw,s=new RegExp(Vl.source,"g");let a;for(;(a=s.exec(r))!==null;)a.index>o&&t.push({kind:"text",label:r.slice(o,a.index)}),t.push({kind:"chip",label:bb(a[0])}),o=a.index+a[0].length;o{const m=`p${o.length}`;return o.push(g),r[m]=Vt(g),`{${m}}`};let a=e,l="";const d=hb.exec(e);if(d&&!d[1].startsWith(Kl)){const g=d[1],m=d[2]??"",h=e.slice(d[0].length),v=new Map,b=new RegExp(yb.source,"g");let x;for(;(x=b.exec(m))!==null;)x[2]&&x[2].includes("{{")&&v.set(x[1],x[2]);const k=t?t(g):"";if(k&&k!==g&&!k.startsWith("$t:"))l=k.replace(/\{\{?([a-zA-Z0-9_]+)\}?\}/g,(w,_)=>{const S=v.get(_);return S?s(S):w});else return n;a=h}const c=Gi(a,t),p=In(c).map(g=>g.kind==="binding"?s(g.raw):g.raw).join(""),f=`${l}${p}`;return/\$t:[a-zA-Z0-9._-]+/.test(f)||o.length===0?n:{chipValue:f,bindings:o,paramLabels:r,keyifiable:!0}}function Gl(e,t){return t.bindings.length?e+t.bindings.map((n,o)=>`|p${o}=${n}`).join(""):e}function Yl(e,t){const n=cr(t).map(r=>r.raw);if(n.length===0)return e;const o=n.map((r,s)=>`|p${s}=${r}`).join("");return`${e}${o}`}function Xl(e){if(typeof e!="string")return"";const t=In(e);let n=0;return t.map(o=>o.kind==="binding"?`{p${n++}}`:o.raw).join("")}const Jl=new RegExp(`^\\s*\\$t:([a-zA-Z0-9._-]+)((?:\\|[a-zA-Z0-9_]+=${Kn})+)\\s*$`),vb=new RegExp(`\\|([a-zA-Z0-9_]+)=(${Kn})`,"g");function mt(e){if(typeof e!="string")return null;const t=Jl.exec(e);if(!t)return null;const n=t[1],o=t[2],r=[],s=new RegExp(vb.source,"g");let a;for(;(a=s.exec(o))!==null;){const l=a[1],d=a[2];r.push({name:l,expression:d,parsed:Kt(d)})}return{key:n,params:r}}function Zl(e){return typeof e=="string"&&Jl.test(e)}function An(e){if(typeof e!="string")return[];const t=[],n=/\{\{?(p\d+)\}?\}/g;let o;for(;(o=n.exec(e))!==null;)t.push(o[1]);return t.sort()}function _b(e,t,n){if(typeof e!="string")return e;const o=new RegExp(`(\\|${t}=)${Kn}`);return o.test(e)?e.replace(o,`$1${n}`):e}function mo(e,t){if(typeof e!="string")return e;const n=new RegExp(`\\|${t}=${Kn}`);return e.replace(n,"")}function Ql(e,t){return typeof e!="string"?e:e.replace(new RegExp(`\\s*\\{\\{?${t}\\}?\\}\\s*`,"g")," ").replace(/\s+/g," ").trim()}function xb(e,t){const n=s=>cr(typeof s=="string"?s:"").map(a=>a.raw.replace(/\s+/g,"")).sort(),o=n(e),r=n(t);return o.length!==r.length?!1:o.every((s,a)=>s===r[a])}function Rl(e){return e.__source?.kind==="base"}function ed(e){return e.__source?.kind==="partial"}function wb(e){return e.__source?.kind==="route"}function pr(e,t){return e.__source?.kind!=="extension"?!1:t==null?!0:e.__source.extensionId!==t}function td(e){return e.type==="extension_point"}function Xi(e=[]){for(const t of e)if(t.iteration&&t.iteration.source!==void 0)return!0;return!1}function kb(e,t=[]){for(const n of t)if(n.iteration&&n.iteration.source!==void 0)return!0;return e.iteration&&e.iteration.source!==void 0?!0:nd(e)}function nd(e){if(typeof e.text=="string"&&od(e.text)&&!Sb(e.text))return!0;if(e.props&&typeof e.props=="object"){for(const t of Object.values(e.props))if(typeof t=="string"&&od(t))return!0}return!1}function od(e){return/\{\{[\s\S]*?\}\}/.test(e)}function Sb(e){const t=mt(e);return t!==null&&t.key.startsWith("custom.")}function Cb(e,t){return e??t??null}function Ji(e,t,n){if(td(e))return!0;switch(t){case"route":return Rl(e)||ed(e)||pr(e);case"base":return pr(e);case"modal":return pr(e);case"extension":return pr(e,n)?!0:Rl(e)||wb(e)||ed(e);default:return!1}}function et(e){return typeof e=="object"&&e!==null&&typeof e.responsive=="string"}function fr(e,t){return et(e)?et(t)&&e.responsive===t.responsive:!et(t)&&e===t}function at(e){const t=[];let n=!1;for(const o of e){if(et(o)){t.push("responsive",o.responsive),n=!0;continue}n?t.push("children",String(o)):(t.push(String(o)),n=!0)}return t.join(".")}function Qt(e){return Array.isArray(e.children)?e.children:[]}function gr(e,t){const n=e.responsive?.[t];return n&&Array.isArray(n.children)?n.children:[]}function We(e,t){if(!e)return null;if(t.length===0)return e;let n=e,o=Qt(e);for(const r of t){if(et(r)){o=gr(n,r.responsive);continue}if(r<0||r>=o.length||(n=o[r],!n))return null;o=Qt(n)}return n}function $b(e,t,n){if(!e||t.length===0)return t;let o=e,r=Qt(e),s;for(let a=0;a=2?t[t.length-2]:void 0;if(r!==void 0&&et(r)){const a=We(e,t.slice(0,-2));return a?{owner:a,children:gr(a,r.responsive),branchKey:r.responsive,index:o}:null}const s=We(e,t.slice(0,-1));return s?{owner:s,children:Qt(s),index:o}:null}function Zi(e,t){if(e.branchKey!==void 0){const n={...e.owner.responsive??{}};n[e.branchKey]={...n[e.branchKey]??{},children:t},e.owner.responsive=n}else e.owner.children=t}function Rt(e){const t={...e};t.props&&typeof t.props=="object"&&(t.props={...t.props});const n=Qt(e);if(n.length>0&&(t.children=n.map(Rt)),e.responsive&&typeof e.responsive=="object"){const o={};for(const r of Object.keys(e.responsive)){const s=e.responsive[r];if(!s||typeof s!="object"){o[r]=s;continue}const a={...s};Array.isArray(s.children)&&(a.children=s.children.map(Rt)),a.props&&typeof a.props=="object"&&(a.props={...a.props}),o[r]=a}t.responsive=o}return t}function Qi(e,t,n,o){const r=Rt(e),s=t.length>0?t[t.length-1]:void 0;if(s!==void 0&&et(s)){const p=We(r,t.slice(0,-1));if(!p)return r;const f=gr(p,s.responsive),y=Math.max(0,Math.min(n,f.length)),g=[...f];return g.splice(y,0,o),Zi({owner:p,branchKey:s.responsive},g),r}const a=We(r,t);if(!a)return r;const l=Qt(a),d=Math.max(0,Math.min(n,l.length)),c=[...l];return c.splice(d,0,o),a.children=c,r}function Ri(e,t){if(t.length===0)return e;const n=Rt(e),o=rd(n,t);if(!o||o.index<0||o.index>=o.children.length)return n;const r=[...o.children];return r.splice(o.index,1),Zi(o,r),n}function Eb(e,t,n,o){if(t.length===0||!n||et(t[t.length-1]))return e;const r=Rt(e),s=We(r,t);if(!s)return r;const a=s.responsive&&typeof s.responsive=="object"?s.responsive[n]:void 0;if(a&&Array.isArray(a.children))return r;const l=o&&s.responsive&&typeof s.responsive=="object"?s.responsive[o]:void 0,c=(l&&Array.isArray(l.children)?l.children:Qt(s)).map(Rt),p={...s.responsive??{}};return p[n]={...p[n]??{},children:c},s.responsive=p,r}function Ib(e,t,n){if(t.length===0||!n||et(t[t.length-1]))return e;const o=Rt(e),r=We(o,t);if(!r||!r.responsive||typeof r.responsive!="object")return o;const s=r.responsive[n];if(!s||!Array.isArray(s.children))return o;const a={...r.responsive},l={...a[n]};return delete l.children,Object.keys(l).length===0?delete a[n]:a[n]=l,r.responsive=Object.keys(a).length>0?a:void 0,o}function Ab(e,t,n,o){if(t.length===0)return e;const r=We(e,t);if(!r||Tb(t,n))return e;const s=Rt(r),a=Ri(e,t),l=id(n,t);let d=o;const c=t.slice(0,-1);if(sd(c,n)){const p=t[t.length-1];(typeof p=="number"?p:0)o){const s=[...e];return s[n]=r-1,s}return e}function sd(e,t){if(e.length!==t.length)return!1;for(let n=0;ne.length||sd(e,t)}function es(e){const t={...e};if(t.props&&typeof t.props=="object"&&(t.props={...t.props}),typeof t.id=="string"&&t.id.length>0){const o=typeof t.name=="string"?t.name:"node";t.id=`${o}_${Db()}`}const n=Qt(e);return n.length>0&&(t.children=n.map(es)),t}function Ot(e,t,n){if(t.length===0)return n(e);const o=Rt(e),r=We(o,t);if(!r)return o;const s=n(r),a=rd(o,t);if(!a||a.index<0||a.index>=a.children.length)return o;const l=[...a.children];return l[a.index]=s,Zi(a,l),o}function Db(){return Math.floor(Math.random()*2821109907456).toString(36).padStart(8,"0")}function Vn(e){if(!Array.isArray(e)||e.length===0)return[];const t=[];for(const n of e){const o=Nb(n);o.length>0&&t.push(...o)}return t}function Nb(e){if(!e||typeof e!="object")return[];const t=e.__source,n=e._fromBase===!0,o=t?.kind;if(o==="extension"||o==="partial")return[];if(o==="base"||n){const s=Array.isArray(e.children)?e.children:[];return Vn(s)}const r={...e};return delete r.__source,delete r._fromBase,delete r.__injectedProps,Array.isArray(r.children)&&(r.children=Vn(r.children)),[r]}function vo(e){if(!e||typeof e!="object")return e;const t=e.__editor,n=t&&typeof t=="object"&&!Array.isArray(t)?t.original:void 0,o=n&&typeof n=="object"&&!Array.isArray(n)?n:void 0,r=Array.isArray(e.components)?e.components:[],s=r.length>0,a=Vn(r),l=o?{...o}:{...e};if(typeof l.extends=="string"&&l.extends.length>0){delete l.components;const c=l.slots&&typeof l.slots=="object"&&!Array.isArray(l.slots)?l.slots:{},p=Object.keys(c);if(!s&&p.length>0){const f={};for(const[y,g]of Object.entries(c))f[y]=Array.isArray(g)?Vn(g):g;l.slots=f}else if(p.length===1)l.slots={[p[0]]:a};else if(p.length===0)l.slots={content:a};else{const f={};for(const[y,g]of Object.entries(c))f[y]=Array.isArray(g)?Vn(g):g;l.slots=f}}else l.components=a,delete l.slots;return delete l.lock_version,delete l.__editor,l}const ad="extension",Un="extensionless";function zb(){const e=globalThis?.G7Config;if(globalThis?.__g7AssetUrlMode===Un)return Un;const n=e?.assetUrlMode;return n===Un||n===ad?n:e?.settings?.general?.asset_url_mode===Un?Un:ad}function Mb(){return zb()===Un}function en(e,t,n,o){const r=e.replace(/\/+$/,""),s=t.replace(/^\.+/,""),a=Mb()?r:`${r}.${s}`,l=[];return o&&l.push(o.replace(/^[?&]+/,"")),n!=null&&n!==""&&l.push(`v=${n}`),l.length>0?`${a}?${l.join("&")}`:a}const ld=/^\s*\$t:([a-zA-Z0-9._-]+)\s*$/,hr="custom.",Lb=/\{\{.*?\}\}/;function Pb(e){return!!(!e||e.iteration&&typeof e.iteration=="object")}function dd(e,t){const n=e?.text,o=typeof n=="string"?n:"";if(Pb(e)||typeof n!="string")return{sourceState:"plain_text",editable:!1,customKey:null,displayValue:o};const r=mt(o);if(r&&r.key.startsWith(hr)){const a=t?t(r.key):r.key;return{sourceState:"custom_key",editable:!0,customKey:r.key,displayValue:a,isParamKey:!0}}const s=ld.exec(o);if(s){const a=s[1];if(a.startsWith(hr)){const d=t?t(a):a;return{sourceState:"custom_key",editable:!0,customKey:a,displayValue:d}}const l=t?t(a):"";return{sourceState:"plain_text",editable:!0,customKey:null,displayValue:l&&l!==a?l:""}}if(Lb.test(o)){const a=ur(o),l=ld.exec(a);if(l&&l[1].startsWith(hr)){const f=l[1],y=t?t(f):f;return{sourceState:"custom_key",editable:!0,customKey:f,displayValue:y}}const d=Gi(o,t),c=ur(d),p=/\$t:[a-zA-Z0-9._-]+/.test(c);if(c.length>0){const f=Yi(o,t);return{sourceState:"plain_with_binding",editable:!0,customKey:null,displayValue:p?"":c,chipValue:f.keyifiable?f.chipValue:void 0,chipParamLabels:f.keyifiable?f.paramLabels:void 0}}return{sourceState:"binding_expression",editable:!1,customKey:null,displayValue:o}}return{sourceState:"plain_text",editable:!0,customKey:null,displayValue:o}}function Ob(){const{state:e}=Je(),t=Ft(),n=e.templateIdentifier,o=e.locale,r=e.selectedRoute?.layoutName??null,s=u.useCallback(p=>{try{const y=Pt.getInstance().translate(p,{templateId:n,locale:o});return y&&y!==p?y:""}catch{return""}},[n,o]),a=u.useCallback(p=>dd(p,s),[s]),l=u.useCallback(p=>{Et({op:"inline_edit_cancel",componentPath:p?at(p):null,timestamp:Date.now()})},[]),d=u.useCallback(async(p,f)=>{if(!t)return{kind:"error",message:"no document context"};const g={children:t.document?.raw?.components??[]},m=We(g,p),h=dd(m,s);if(!h.editable)return Et({op:"inline_edit_blocked_binding",sourceState:h.sourceState,componentPath:at(p),timestamp:Date.now()}),{kind:"blocked"};if(h.customKey){if(f===h.displayValue)return{kind:"noop",translationKey:h.customKey};_o(n,o,h.customKey,f);const E=await yr(n,h.customKey,o,f);return E.kind==="error"?{kind:"error",message:E.message}:(await tn(n,o),Et({op:"inline_edit_update_value",sourceState:"custom_key",componentPath:at(p),translationKey:h.customKey,toLocale:o,valueLength:f.length,valueHash:Ni(f),timestamp:Date.now()}),{kind:"updated",translationKey:h.customKey})}const v=typeof m?.text=="string"?m.text:"",b=h.sourceState==="plain_with_binding",x=Gi(v,s),k=b?Xl(x):f,w=E=>b?Yl(E,v):E;t.patchLayout(E=>Ot({children:E},p,$=>({...$,text:f})).children??[]);const _=await qn(n,r,o,k);if(_.kind==="error"||!_.resource)return{kind:"error",message:_.message};const S=_.resource.translation_key;return _o(n,o,S,k),t.patchLayout(E=>Ot({children:E},p,$=>({...$,text:w(`$t:${S}`)})).children??[]),await tn(n,o),Et({op:"inline_edit_create_key",sourceState:h.sourceState,componentPath:at(p),translationKey:S,toLocale:o,valueLength:f.length,valueHash:Ni(f),timestamp:Date.now()}),{kind:"created",translationKey:S,id:_.resource.id}},[t,n,o,r,s]),c=u.useCallback(async(p,f)=>{if(!t)return{kind:"error",message:"no document context"};const g={children:t.document?.raw?.components??[]},m=We(g,p),h=typeof m?.text=="string"?m.text:"",v=mt(h);if(v&&v.key.startsWith(hr))return br(n,v.key,o,f),_o(n,o,v.key,f),t.markDirty?.(),{kind:"updated",translationKey:v.key};const b=Yi(h,s),x=ur(f.replace(/\{\{?p\d+\}?\}/g," ")).replace(/\s+/g," ").trim(),k=await qn(n,r,o,x);if(k.kind==="error"||!k.resource)return{kind:"error",message:k.message};const w=k.resource.translation_key,_=An(f),S=k.resource.values??{},E={[o]:f};for(const C of Object.keys(S)){if(C===o)continue;let N=S[C]??"";for(const $ of _)N=qi(N,$);E[C]=N}return xo(n,w,E),_o(n,o,w,f),t.patchLayout(C=>Ot({children:C},p,A=>({...A,text:Gl(`$t:${w}`,b)})).children??[]),t.markDirty?.(),await tn(n,o),_o(n,o,w,f),Et({op:"inline_edit_create_key",sourceState:"plain_with_binding",componentPath:at(p),translationKey:w,toLocale:o,valueLength:f.length,valueHash:Ni(f),timestamp:Date.now()}),{kind:"created",translationKey:w}},[t,n,o,r]);return{classify:a,commit:d,trackCancel:l,keyifyChipValue:c}}async function qn(e,t,n,o){const r=`/api/admin/templates/${encodeURIComponent(e)}/custom-translations`;try{const s=await fetch(r,{method:"POST",credentials:"same-origin",headers:je({"Content-Type":"application/json"}),body:JSON.stringify({layout_name:t,locale:n,value:o})}),a=await s.json().catch(()=>null);if(!s.ok)return{kind:"error",message:a?.message??`HTTP ${s.status}`};const l=a?.data;return!l||typeof l.translation_key!="string"?{kind:"error",message:"invalid response"}:{kind:"ok",resource:l}}catch(s){return{kind:"error",message:s instanceof Error?s.message:"network error"}}}async function yr(e,t,n,o){const r=await cn(e,t);if(!r)return{kind:"error",message:"custom key not found"};const s=`/api/admin/templates/${encodeURIComponent(e)}/custom-translations/${r.id}`,a={...r.values??{},[n]:o};try{const l=await fetch(s,{method:"PUT",credentials:"same-origin",headers:je({"Content-Type":"application/json"}),body:JSON.stringify({values:a,expected_lock_version:r.lock_version})}),d=await l.json().catch(()=>null);return l.status===409?{kind:"error",message:"concurrent_modification"}:l.ok?{kind:"ok"}:{kind:"error",message:d?.message??`HTTP ${l.status}`}}catch(l){return{kind:"error",message:l instanceof Error?l.message:"network error"}}}async function cn(e,t){const n=`/api/admin/templates/${encodeURIComponent(e)}/custom-translations`;try{const o=await fetch(n,{method:"GET",credentials:"same-origin",headers:je()});if(!o.ok)return null;const s=(await o.json().catch(()=>null))?.data;return Array.isArray(s)?s.find(a=>a.translation_key===t)??null:null}catch{return null}}function _o(e,t,n,o){try{Pt.getInstance().setTranslationValue(e,t,n,o)}catch{}if(typeof window<"u")try{window.dispatchEvent(new CustomEvent(kt,{detail:{templateIdentifier:e,locale:t}}))}catch{}}const kt="g7le:translations-refreshed";async function tn(e,t){if(typeof window>"u")return;const n=t??window.__g7EditorContentLocale;if(n){try{const o=Pt.getInstance();try{const r=await fetch(en(`/api/templates/${encodeURIComponent(e)}/config`,"json"),{headers:{Accept:"application/json"},credentials:"same-origin"}).then(a=>a.ok?a.json():null),s=r?.data?.cache_version??r?.cache_version;if(typeof s=="number"&&s>0){o.setCacheVersion(s);const a=window;a.G7Config&&(a.G7Config.cache_version=s)}}catch{}await o.loadTranslations(e,n,"/api",!0)}catch{}try{window.dispatchEvent(new CustomEvent(kt,{detail:{templateIdentifier:e,locale:n}}))}catch{}}}const nn=new Map;function br(e,t,n,o){const r=nn.get(t)??{};r[n]=o,nn.set(t,r);try{Pt.getInstance().setTranslationValue(e,n,t,o)}catch{}if(typeof window<"u")try{window.dispatchEvent(new CustomEvent(kt,{detail:{templateIdentifier:e,locale:n}}))}catch{}}function xo(e,t,n){for(const[o,r]of Object.entries(n))br(e,t,o,r)}function It(e,t){return nn.get(e)?.[t]}function Bb(e){const t=nn.get(e);return t?{...t}:void 0}function Wb(){return nn.size>0}function cd(e,t){for(const[n,o]of nn.entries())for(const[r,s]of Object.entries(o))try{e.setTranslationValue(t,r,n,s)}catch{}}async function jb(e){if(nn.size===0)return{ok:0,failed:[]};const t=Array.from(nn.entries());let n=0;const o=[];for(const[r,s]of t)try{const a=await cn(e,r);if(!a){o.push(r);continue}const l={...a.values??{},...s},d=`/api/admin/templates/${encodeURIComponent(e)}/custom-translations/${a.id}`;if(!(await fetch(d,{method:"PUT",credentials:"same-origin",headers:je({"Content-Type":"application/json"}),body:JSON.stringify({values:l,expected_lock_version:a.lock_version})})).ok){o.push(r);continue}n+=1}catch{o.push(r)}for(const[r]of t)o.includes(r)||nn.delete(r);try{await tn(e)}catch{}return{ok:n,failed:o}}function Gn(e,t){const n=`layout_editor.locale.${e}`,o=t(n);return o&&o!==n?o:e.toUpperCase()}function ud(){if(typeof window>"u")return[];const t=window.G7Config?.appConfig?.supportedLocales;return Array.isArray(t)?t.filter(n=>typeof n=="string"&&n.length>0):[]}function Hb({locales:e}={}){const{t}=tt(),{state:n,dispatch:o}=Je();u.useEffect(()=>{window.__g7EditorContentLocale=n.locale},[n.locale]);const r=e??ud(),s=r.includes(n.locale)?r:[n.locale,...r];if(s.length<=1)return i.jsxs("span",{className:"g7le-locale-switcher g7le-locale-switcher--single","data-testid":"g7le-locale-switcher-single",style:{fontSize:11,color:"#475569",padding:"0 4px"},children:[t("layout_editor.chrome.toolbar.locale_switcher_label"),": ",Gn(n.locale,t)]});const a=l=>{if(l===n.locale)return;const d=n.locale;o({type:"SET_LOCALE",locale:l}),Et({op:"locale_switch",fromLocale:d,toLocale:l,timestamp:Date.now()})};return i.jsxs("div",{className:"g7le-locale-switcher","data-testid":"g7le-locale-switcher",role:"group","aria-label":t("layout_editor.chrome.toolbar.locale_switcher_label"),style:{display:"inline-flex",alignItems:"center",gap:4},children:[i.jsx("span",{style:{fontSize:11,color:"#475569"},children:t("layout_editor.chrome.toolbar.locale_switcher_label")}),i.jsx("div",{style:{display:"inline-flex",gap:2},children:s.map(l=>{const d=l===n.locale;return i.jsx("button",{type:"button","data-testid":`g7le-locale-${l}`,"aria-pressed":d,onClick:()=>a(l),style:{padding:"2px 8px",fontSize:12,cursor:"pointer",background:d?"#0f172a":"#f8fafc",color:d?"#fff":"#0f172a",border:"1px solid #cbd5e1",borderRadius:4},children:Gn(l,t)},l)})})]})}const Fb=/\{\{?(p\d+)\}?\}/g;function Kb(e){const t=[];if(typeof e!="string"||e.length===0)return t;const n=new RegExp(Fb.source,"g");let o=0,r;for(;(r=n.exec(e))!==null;){const s=r.index,a=s+r[0].length;s>o&&t.push({kind:"text",raw:e.slice(o,s),start:o,end:s}),t.push({kind:"chip",raw:r[0],paramName:r[1],start:s,end:a}),o=a}return o{n.push({kind:"text",text:a}),r=!0};for(const a of t)a.kind==="text"?(o=a.raw,s(o),o=""):(r||s(""),n.push({kind:"chip",paramName:a.paramName}),r=!1);return r||s(""),n}function Ub(e,t,n,o){const r=[];return e.forEach((s,a)=>{if(s.kind==="chip"){if(s.paramName===t)return;r.push(`{${s.paramName}}`)}else{const l=s.text??"";if(a===n){const d=Math.max(0,Math.min(o,l.length));r.push(l.slice(0,d),`{${t}}`,l.slice(d))}else r.push(l)}}),r.join("")}function mr({value:e,onChange:t,t:n,paramLabels:o,onRequestInsert:r,onRemoveChip:s,testIdSuffix:a="pc",disabled:l=!1,hideInsertButton:d=!1,caretRef:c}){const p=u.useMemo(()=>Vb(e),[e]),f=u.useRef(null),[y,g]=u.useState(null),[m,h]=u.useState(null),[v,b]=u.useState(null),x=u.useMemo(()=>p.map(B=>B.kind==="chip"?`c:${B.paramName}`:"t").join("|"),[p]),k=u.useRef(null),w=u.useRef([]),_=u.useRef(!1);u.useLayoutEffect(()=>{const B=_.current;_.current=!1,!(!B&&k.current===x)&&(k.current=x,p.forEach((T,H)=>{if(T.kind!=="text")return;const L=w.current[H];L&&L.textContent!==(T.text??"")&&(L.textContent=T.text??"")}))},[x,p,e]);const S=()=>{const B=f.current;if(!B)return e;const T=[];for(const H of Array.from(B.querySelectorAll("[data-seg]")))H.dataset.seg==="chip"?T.push(`{${H.dataset.param??""}}`):T.push(H.textContent??"");return T.join("")},E=()=>{l||t(S())},C=u.useRef(null),N=u.useRef(!1),$=(B,T)=>{if(!l){B.preventDefault(),B.stopPropagation(),C.current={param:T,pointerId:B.pointerId},N.current=!1,g(T);try{B.currentTarget.setPointerCapture(B.pointerId)}catch{}}},A=B=>{const T=C.current;if(!T||B.pointerId!==T.pointerId)return;N.current=!0;const H=O(B.clientX,B.clientY);H&&(h({slotIdx:H.slotIdx,offset:H.offset}),b(Gb(f.current,H.slotIdx,H.offset)))},z=(B,T)=>{if(B.stopPropagation(),N.current){N.current=!1;return}s?.(T)},D=B=>{const T=C.current;if(!T||B.pointerId!==T.pointerId)return;try{B.currentTarget.releasePointerCapture(B.pointerId)}catch{}C.current=null,N.current&&typeof setTimeout=="function"&&setTimeout(()=>{N.current=!1},0);const H=O(B.clientX,B.clientY);if(g(null),h(null),b(null),!H)return;const L=f.current;if(!L)return;const P=Array.from(L.querySelectorAll("[data-seg]")).map(G=>G.dataset.seg==="chip"?{kind:"chip",paramName:G.dataset.param??""}:{kind:"text",text:G.textContent??""}),F=Ub(P,T.param,H.slotIdx,H.offset);F!==e&&(_.current=!0,t(F))},O=(B,T)=>{const H=f.current;if(!H)return null;const L=Array.from(H.querySelectorAll("[data-seg]")),I=H.ownerDocument,P=I.elementFromPoint(B,T);let F=P?.closest('[data-seg="text"]');if(F)return{slotIdx:L.indexOf(F),offset:qb(I,F,B,T)};const G=P?.closest('[data-seg="chip"]');if(G){const Y=L.indexOf(G),Z=G.getBoundingClientRect();if(B=0){const se=L[Y-1];return{slotIdx:Y-1,offset:(se.textContent??"").length}}if(Y+1({s:Y,i:Z})).filter(Y=>Y.s.dataset.seg==="text").pop();return V?{slotIdx:V.i,offset:(V.s.textContent??"").length}:null},M=()=>{l||!r||r(pd(f.current,e.length))};return u.useEffect(()=>{if(c)return c.current=()=>pd(f.current,e.length),()=>{c.current=null}},[c,e.length]),i.jsxs("div",{style:{display:"flex",flexDirection:"column",gap:4},children:[i.jsxs("div",{ref:f,"data-testid":`g7le-chip-input-${a}`,className:"g7le-chip-input",style:Yb,children:[p.map((B,T)=>B.kind==="chip"?i.jsxs("span",{"data-seg":"chip","data-param":B.paramName,"data-testid":`g7le-chip-${a}-${B.paramName}`,contentEditable:!1,onPointerDown:H=>$(H,B.paramName??""),onPointerMove:A,onPointerUp:D,title:n("layout_editor.translation.placeholder_locked"),style:{...Qb,...y===B.paramName?Rb:null,touchAction:"none"},children:[i.jsx("span",{"aria-hidden":"true",style:em,children:"⠿"}),i.jsx("span",{"aria-hidden":"true",style:{marginRight:3},children:"🔗"}),i.jsx("span",{style:tm,children:o?.[B.paramName??""]??B.paramName}),s&&!l&&i.jsx("button",{type:"button","data-testid":`g7le-chip-remove-${a}-${B.paramName}`,"aria-label":n("layout_editor.inline_binding.clear"),title:n("layout_editor.inline_binding.clear"),onPointerDown:H=>{H.stopPropagation(),H.preventDefault()},onClick:H=>z(H,B.paramName??""),style:nm,children:"✕"})]},`chip-${B.paramName}`):i.jsx("span",{ref:H=>{w.current[T]=H},"data-seg":"text",contentEditable:!l,suppressContentEditableWarning:!0,onInput:E,"data-testid":`g7le-chip-text-${a}-${T}`,style:{...Xb,...y?Jb:null,...m?.slotIdx===T?Zb:null}},`text-slot-${T}`)),v&&i.jsx("span",{"aria-hidden":"true","data-testid":`g7le-chip-caret-${a}`,style:{position:"absolute",left:v.left,top:v.top,width:2,height:v.height,background:"#2563eb",borderRadius:1,pointerEvents:"none",boxShadow:"0 0 0 1px rgba(37,99,235,0.3)"}})]}),i.jsxs("div",{style:{display:"flex",alignItems:"center",gap:6},children:[r&&!d&&i.jsxs("button",{type:"button","data-testid":`g7le-chip-insert-${a}`,onClick:M,disabled:l,style:om,children:["+ ",n("layout_editor.inline_binding.insert_data")]}),i.jsx("span",{style:rm,children:n("layout_editor.translation.placeholder_locked")})]})]})}function qb(e,t,n,o){const r=(t.textContent??"").length;try{const a=e;let l=null,d=0;if(typeof a.caretRangeFromPoint=="function"){const c=a.caretRangeFromPoint(n,o);c&&(l=c.startContainer,d=c.startOffset)}else if(typeof a.caretPositionFromPoint=="function"){const c=a.caretPositionFromPoint(n,o);c&&(l=c.offsetNode,d=c.offset)}if(l&&t.contains(l))return Math.max(0,Math.min(d,r))}catch{}const s=t.getBoundingClientRect();return n>s.left+s.width/2?r:0}function Gb(e,t,n){if(!e)return null;const r=Array.from(e.querySelectorAll("[data-seg]"))[t];if(!r)return null;const s=e.getBoundingClientRect();try{const d=r.firstChild,c=e.ownerDocument;if(d&&d.nodeType===Node.TEXT_NODE&&(d.textContent??"").length>0){const p=c.createRange(),f=Math.max(0,Math.min(n,(d.textContent??"").length));p.setStart(d,f),p.setEnd(d,f);const y=p.getClientRects(),g=y.length>0?y[0]:p.getBoundingClientRect();if(g&&(g.height>0||g.width>=0))return{left:g.left-s.left,top:g.top-s.top,height:g.height||r.getBoundingClientRect().height}}}catch{}const a=r.getBoundingClientRect();return{left:(n>0?a.right:a.left)-s.left,top:a.top-s.top,height:a.height||16}}function pd(e,t){if(!e)return t;try{const n=e.ownerDocument.defaultView?.getSelection();if(!n||n.rangeCount===0)return t;const o=n.getRangeAt(0);let r=o.startContainer,s=null;for(;r&&r!==e;){if(r instanceof HTMLElement&&r.dataset.seg==="text"){s=r;break}r=r.parentNode}if(!s||!e.contains(s))return t;const a=Array.from(e.querySelectorAll("[data-seg]"));let l=0;for(const d of a){if(d===s)break;l+=d.dataset.seg==="chip"?`{${d.dataset.param??""}}`.length:(d.textContent??"").length}return l+o.startOffset}catch{return t}}const Yb={position:"relative",display:"flex",flexWrap:"wrap",alignItems:"center",gap:2,minHeight:30,padding:"4px 8px",border:"1px solid #cbd5e1",borderRadius:4,background:"#fff",fontSize:13,lineHeight:1.6},Xb={outline:"none",minWidth:8,minHeight:"1.2em",whiteSpace:"pre-wrap",color:"#0f172a",display:"inline-block"},Jb={boxShadow:"inset 0 -2px 0 #93c5fd",background:"#eff6ff",borderRadius:2},Zb={background:"#dbeafe",boxShadow:"inset 0 -2px 0 #2563eb"},Qb={display:"inline-flex",alignItems:"center",gap:1,background:"linear-gradient(180deg,#eff6ff,#dbeafe)",color:"#1d4ed8",border:"1px dashed #60a5fa",borderRadius:9999,padding:"1px 8px 1px 4px",margin:"0 1px",fontSize:12,fontWeight:600,cursor:"grab",userSelect:"none",whiteSpace:"nowrap",boxShadow:"0 1px 2px rgba(37,99,235,0.18)",verticalAlign:"middle"},Rb={opacity:.5,borderStyle:"solid",cursor:"grabbing"},em={color:"#2563eb",opacity:.7,fontSize:11,marginRight:1,cursor:"grab"},tm={textDecoration:"none"},nm={marginLeft:4,width:14,height:14,lineHeight:"12px",padding:0,border:"none",borderRadius:9999,background:"rgba(29,78,216,0.12)",color:"#1d4ed8",fontSize:9,fontWeight:700,cursor:"pointer",display:"inline-flex",alignItems:"center",justifyContent:"center",flex:"0 0 auto"},om={fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#334155",padding:"3px 10px",cursor:"pointer"},rm={fontSize:10,color:"#94a3b8"};function fd(e){e&&(e.style.height="auto",e.style.height=`${Math.max(e.scrollHeight,28)}px`)}function im(e){fd(e)}function sm(e){const t=new Set;for(const n of e)for(const o of Object.keys(n.values??{}))t.add(o);return Array.from(t).sort()}function am({templateIdentifier:e,layoutName:t,t:n,referencedKeys:o,onClose:r}){const[s,a]=u.useState([]),[l,d]=u.useState("idle"),[c,p]=u.useState(null),[f,y]=u.useState("all"),[g,m]=u.useState(new Set),[h,v]=u.useState({}),[b,x]=u.useState(null),[k,w]=u.useState({}),_=u.useCallback(async()=>{d("loading"),p(null);const T=await Ly(e,t);T.ok?(a(T.data),v({}),m(new Set),w({}),d("idle")):(p(T.message),d("error"))},[e,t]);u.useEffect(()=>{_()},[_]);const S=u.useMemo(()=>sm(s),[s]),E=u.useMemo(()=>s.filter(T=>f==="all"?!0:T.status===f).slice().sort((T,H)=>{const L=T.updated_at?Date.parse(T.updated_at):0,I=H.updated_at?Date.parse(H.updated_at):0;return I!==L?I-L:H.id-T.id}),[s,f]),C=u.useMemo(()=>s.filter(T=>T.status==="orphaned"),[s]),N=u.useCallback(T=>{const H={...T.values??{}};for(const L of Object.keys(H)){const I=It(T.translation_key,L);I!==void 0&&(H[L]=I)}return H},[]),$=u.useCallback(T=>h[T.id]??N(T),[h,N]),A=u.useCallback((T,H,L)=>{v(I=>{const P=I[T]??s.find(F=>F.id===T)?.values??{};return{...I,[T]:{...P,[H]:L}}})},[s]),z=u.useCallback(async T=>{x(T.id),w(I=>({...I,[T.id]:""}));const H=$(T),L=await Py(e,T.id,H,T.lock_version);if(L.ok)a(I=>I.map(P=>P.id===T.id?L.data:P)),v(I=>{const P={...I};return delete P[T.id],P});else{const I=L.status===409?n("layout_editor.translation_manager.conflict"):L.message;w(P=>({...P,[T.id]:I}))}x(null)},[e,$,n]),D=u.useCallback(T=>typeof window<"u"&&typeof window.confirm=="function"?window.confirm(n("layout_editor.translation_manager.delete_confirm",{count:T})):!0,[n]),O=u.useCallback(async T=>{if(!D(1))return;const H=await Oy(e,T.id);H.ok?(a(L=>L.filter(I=>I.id!==T.id)),m(L=>{const I=new Set(L);return I.delete(T.id),I})):p(H.message)},[e,D]),M=u.useCallback(async T=>{if(T.length===0||!D(T.length))return;const H=await By(e,T);if(H.ok){const L=new Set(T);a(I=>I.filter(P=>!L.has(P.id))),m(new Set)}else p(H.message)},[e,D]),B=u.useCallback(T=>{m(H=>{const L=new Set(H);return L.has(T)?L.delete(T):L.add(T),L})},[]);return i.jsxs("div",{className:"g7le-translation-manager","data-testid":"g7le-translation-manager",style:lm,children:[i.jsxs("div",{"data-modal-drag-handle":!0,style:dm,children:[i.jsx("span",{style:{fontSize:14,fontWeight:700},"data-testid":"g7le-translation-manager-title",children:n("layout_editor.translation_manager.title")}),i.jsx("button",{type:"button","aria-label":"close",onClick:r,style:cm,"data-testid":"g7le-translation-manager-close",children:"✕"})]}),i.jsxs("div",{style:um,children:[i.jsx("div",{style:{display:"inline-flex",gap:4},role:"tablist","aria-label":n("layout_editor.translation_manager.filter_label"),children:["all","active","orphaned"].map(T=>i.jsx("button",{type:"button",role:"tab","aria-selected":f===T,"data-active":f===T?"true":"false",onClick:()=>y(T),style:f===T?pm:gd,"data-testid":`g7le-translation-filter-${T}`,children:n(`layout_editor.translation_manager.filter_${T}`)},T))}),i.jsx("div",{style:{flex:1}}),g.size>0&&i.jsxs("button",{type:"button",onClick:()=>{M(Array.from(g))},style:hd,"data-testid":"g7le-translation-bulk-delete",children:["🗑 ",n("layout_editor.translation_manager.delete_selected",{count:g.size})]}),C.length>0&&i.jsxs("button",{type:"button",onClick:()=>{M(C.map(T=>T.id))},style:hd,"data-testid":"g7le-translation-purge-orphaned",children:["🧹 ",n("layout_editor.translation_manager.purge_orphaned",{count:C.length})]})]}),c&&i.jsx("div",{style:fm,"data-testid":"g7le-translation-manager-error",children:c}),i.jsx("div",{style:gm,children:l==="loading"?i.jsx("div",{style:yd,"data-testid":"g7le-translation-manager-loading",children:n("layout_editor.translation_manager.loading")}):E.length===0&&l!=="error"?i.jsx("div",{style:yd,"data-testid":"g7le-translation-manager-empty",children:n("layout_editor.translation_manager.empty")}):i.jsx("div",{"data-testid":"g7le-translation-list",style:hm,children:E.map(T=>{const H=$(T),L=T.status==="orphaned",I=o?o.has(T.translation_key):null;return i.jsxs("div",{style:ym,"data-testid":`g7le-translation-row-${T.id}`,children:[i.jsxs("div",{style:bm,children:[i.jsx("input",{type:"checkbox",checked:g.has(T.id),onChange:()=>B(T.id),"aria-label":n("layout_editor.translation_manager.select_aria",{key:T.translation_key}),"data-testid":`g7le-translation-select-${T.id}`}),i.jsx("code",{style:mm,children:T.translation_key}),L&&i.jsx("span",{style:vm,"data-testid":`g7le-translation-badge-orphaned-${T.id}`,children:n("layout_editor.translation_manager.badge_orphaned")}),I===!0&&i.jsx("span",{style:_m,"data-testid":`g7le-translation-badge-live-inuse-${T.id}`,children:n("layout_editor.translation_manager.badge_live_in_use")}),I===!1&&i.jsx("span",{style:xm,"data-testid":`g7le-translation-badge-live-unused-${T.id}`,children:n("layout_editor.translation_manager.badge_live_unused")}),i.jsx("div",{style:{flex:1}}),i.jsx("button",{type:"button",onClick:()=>{O(T)},style:Im,"data-testid":`g7le-translation-delete-${T.id}`,children:"🗑"})]}),i.jsxs("div",{style:wm,children:[S.map(P=>{const F=H[P]??"",G=An(F).length>0;return i.jsxs("div",{style:km,"data-testid":`g7le-translation-field-${T.id}-${P}`,children:[i.jsxs("label",{style:Sm,title:P,children:[Gn(P,n),i.jsxs("span",{style:Cm,children:["(",P,")"]})]}),G?i.jsx("div",{style:{flex:1},"data-testid":`g7le-translation-chip-${T.id}-${P}`,children:i.jsx(mr,{value:F,onChange:V=>A(T.id,P,V),t:n,testIdSuffix:`${T.id}-${P}`})}):i.jsx("textarea",{value:F,rows:1,ref:im,onChange:V=>{A(T.id,P,V.currentTarget.value),fd(V.currentTarget)},style:$m,"data-testid":`g7le-translation-input-${T.id}-${P}`})]},P)}),i.jsxs("div",{style:{display:"flex",alignItems:"center",gap:8},children:[i.jsx("button",{type:"button",disabled:b===T.id,onClick:()=>{z(T)},style:Em,"data-testid":`g7le-translation-save-${T.id}`,children:b===T.id?n("layout_editor.translation_manager.saving"):n("layout_editor.translation_manager.save")}),k[T.id]?i.jsx("span",{style:{fontSize:11,color:"#dc2626"},"data-testid":`g7le-translation-row-error-${T.id}`,children:k[T.id]}):null]})]})]},T.id)})})}),i.jsx("div",{style:Am,children:i.jsx("button",{type:"button",onClick:r,style:Tm,"data-testid":"g7le-translation-manager-footer-close",children:n("layout_editor.translation_manager.close")})})]})}const lm={display:"flex",flexDirection:"column",minHeight:0,flex:1},dm={display:"flex",alignItems:"center",justifyContent:"space-between",padding:"12px 16px",borderBottom:"1px solid #e2e8f0"},cm={border:"none",background:"transparent",fontSize:16,color:"#94a3b8",cursor:"pointer"},um={padding:"10px 16px",borderBottom:"1px solid #f1f5f9",display:"flex",gap:8,alignItems:"center"},gd={padding:"4px 10px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},pm={...gd,borderColor:"#2563eb",background:"#eff6ff",color:"#1d4ed8",fontWeight:600},hd={padding:"4px 10px",fontSize:12,border:"1px solid #fecaca",borderRadius:6,background:"#fff",color:"#dc2626",cursor:"pointer"},fm={padding:"8px 16px",fontSize:12,color:"#b91c1c",background:"#fef2f2"},gm={padding:16,overflow:"auto",flex:1,minHeight:120,display:"flex",flexDirection:"column",gap:12},yd={fontSize:13,color:"#94a3b8",textAlign:"center",padding:"32px 0"},hm={display:"flex",flexDirection:"column",gap:20},ym={border:"1px solid #e2e8f0",borderRadius:8,padding:10,display:"flex",flexDirection:"column",gap:6},bm={display:"flex",alignItems:"center",gap:8},mm={fontSize:11,background:"#f1f5f9",padding:"2px 6px",borderRadius:4,color:"#0f172a"},vm={fontSize:10,color:"#b45309",background:"#fef3c7",padding:"1px 6px",borderRadius:4,fontWeight:600},_m={fontSize:10,color:"#15803d",background:"#dcfce7",padding:"1px 6px",borderRadius:4,fontWeight:600},xm={fontSize:10,color:"#9f1239",background:"#ffe4e6",padding:"1px 6px",borderRadius:4,fontWeight:600},wm={display:"flex",flexDirection:"column",gap:6},km={display:"flex",alignItems:"flex-start",gap:8},Sm={fontSize:12,color:"#475569",minWidth:88,fontWeight:600,paddingTop:4,display:"flex",flexDirection:"column",lineHeight:1.2},Cm={fontSize:10,color:"#94a3b8",fontWeight:400},$m={flex:1,padding:"4px 8px",fontSize:13,border:"1px solid #cbd5e1",borderRadius:4,color:"#0f172a",resize:"vertical",overflow:"hidden",minHeight:28,lineHeight:1.4,fontFamily:"inherit",boxSizing:"border-box"},Em={padding:"5px 12px",fontSize:12,border:"none",borderRadius:6,background:"#2563eb",color:"#fff",cursor:"pointer"},Im={padding:"4px 8px",fontSize:12,border:"1px solid #fecaca",borderRadius:6,background:"#fff",color:"#dc2626",cursor:"pointer"},Am={padding:"10px 16px",borderTop:"1px solid #e2e8f0",display:"flex",justifyContent:"flex-end"},Tm={padding:"6px 14px",fontSize:13,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"};function Dm(){const e=Ft(),{state:t}=Je(),n=e?.document?.raw??null,o=t.templateIdentifier??"",r=t.selectedRoute?.layoutName??"",s=u.useCallback((c,p)=>{const f=n?.[c];return f===void 0?p:f},[n]),a=u.useCallback((c,p,...f)=>{e&&(f.length>=1?e.patchDocumentRaw(c,p,f[0]):e.patchDocumentRaw(c,p))},[e]),l=u.useCallback(async(c,p)=>!o||!r?null:qn(o,r,c,p),[o,r]),d=u.useCallback(async(c,p,f)=>o?yr(o,c,p,f):null,[o]);return{raw:n,getValue:s,patch:a,createI18nKey:l,updateI18nKeyValue:d}}function Nm(e){const t=e.__source;return t&&(t.kind==="module"||t.kind==="plugin")&&t.identifier?`${t.kind}:${t.identifier}`:"template"}function bd(e,t){if(!t)return null;const n=Nm(e);if(n&&t.bySource){const a=t.bySource[n]?.[e.id];if(a!==void 0)return{value:a,match:{dataSourceId:e.id,resolvedBy:"spec_byId",sourceKey:n}}}const o=t.byDataSourceId?.[e.id];if(o!==void 0)return{value:o,match:{dataSourceId:e.id,resolvedBy:"spec_byId"}};const r=e.endpoint??"";if(t.byEndpointPattern&&typeof r=="string"&&r.length>0){for(const[s,a]of Object.entries(t.byEndpointPattern))if(zm(r,s))return{value:a,match:{dataSourceId:e.id,resolvedBy:"spec_byEndpoint"}}}return null}function ts(e,t){const n=bd(e,t);if(n)return n;const o=e.fallback?.data;return o!==void 0?{value:{data:Array.isArray(o)?o:[o]},match:{dataSourceId:e.id,resolvedBy:"fallback"}}:{value:{},match:{dataSourceId:e.id,resolvedBy:"inferred"}}}function zm(e,t){const n=t.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*");return new RegExp(`^${n}$`).test(e)}function Mm(e={}){const{spec:t,overrideSpec:n,onMatch:o}=e;return{has(r){return!0},resolve(r){const s=n?bd(r,n):null,{value:a,match:l}=s??ts(r,t);if(o)try{o(l)}catch{}return a}}}const md=Lt(),vd=md.DataBindingEngine,_d=md.dataBindingEngine;bo("PipeRegistry");function Lm(e){let t=null,n=0;for(let o=0;o0?e[o-1]:"",a=o{const a=t[r],l=a==null?[]:String(a).split(",").map(d=>d.trim()).filter(Boolean);return l.length===0?"":l.join(" ?? ")+(s?" ?? ":"")});return n=n.replace(/\{(\w+)\}/g,(o,r)=>{const s=t[r];return s===void 0?o:s}),n}function vr(e){const t=e.trim(),n=/^\{\{([\s\S]*)\}\}$/.exec(t);return n?n[1].trim():t}function wd(e){const t=e.trim();return/^\{\{[\s\S]*\}\}$/.test(t)?t:`{{ ${t} }}`}function kd(e,t){const n={};for(const[r,s]of Object.entries(t))n[r]=typeof s=="string"?vr(s):s;const o=Om(e.expr,n);return wd(o)}function Bm(e,t){if(typeof e!="string")return null;const n=Sd(vr(e));for(const o of t){const r=Wm(n,o.expr,o.params);if(r)return{recipeId:o.id,params:r}}return null}function Sd(e){return e.replace(/\s+/g," ").trim()}function Cd(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function Wm(e,t,n){const o=[],r=new Set;let s="";const a=/\{(\w+)(\*?)\}(\s*\?\?\s*)?/g;let l=0,d;const c=Sd(t);for(;(d=a.exec(c))!==null;){const g=c.slice(l,d.index);s+=Cd(g).replace(/\\ /g,"\\s*");const m=d[2]==="*",h=!!d[3];m?(r.add(d[1]),s+="(.*?)",h&&(s+="(?:\\s*\\?\\?\\s*)?")):(s+="(.+?)",h&&(s+="\\s*\\?\\?\\s*")),o.push(d[1]),l=a.lastIndex}s+=Cd(c.slice(l)).replace(/\\ /g,"\\s*");let p;try{p=new RegExp(`^${s}$`)}catch{return null}const f=p.exec(e);if(!f)return null;const y={};if(o.forEach((g,m)=>{let h=(f[m+1]??"").trim();r.has(g)&&(h=h.split(/\s*\?\?\s*/).map(v=>v.trim()).filter(Boolean).join(", ")),y[g]=h}),n.length>0){const g=new Set(n.map(m=>m.key));for(const m of Object.keys(y))g.has(m)||delete y[m]}return y}function jm(e){const t=`x.${e.field}`,n=Hm(e.value);switch(e.cmp){case"=":return`${t} === ${n}`;case"!=":return`${t} !== ${n}`;case">":return`${t} > ${n}`;case"<":return`${t} < ${n}`;case">=":return`${t} >= ${n}`;case"<=":return`${t} <= ${n}`;case"includes":return`(${t} ?? []).includes(${n})`;default:return`${t} === ${n}`}}function Hm(e){const t=e.trim();return t==="true"||t==="false"||t!==""&&!Number.isNaN(Number(t))||/^['"].*['"]$/.test(t)||/[.([{]/.test(t)?t:`'${t.replace(/'/g,"\\'")}'`}function ns(e){return!e||e.length===0?"true":e.map(jm).join(" && ")}function Fm(e){const n=`(${(e.source??"").trim()||"[]"} ?? [])`;let o;switch(e.op){case"count":o=`${n}.filter(x => ${ns(e.conditions)}).length`;break;case"sum":{const r=(e.sumField??"").trim()||"value";o=`${n}.filter(x => ${ns(e.conditions)}).reduce((s, x) => s + (x.${r} ?? 0), 0)`;break}case"filter":o=`${n}.filter(x => ${ns(e.conditions)})`;break;case"toOptions":{const r=(e.valueField??"").trim()||"value",s=(e.labelField??"").trim()||"label";o=`${n}.map(x => ({ value: x.${r}, label: x.${s} }))`;break}case"nth":{const r=(e.index??"").trim()||"0",s=(e.prop??"").trim();o=`${n}[${r}]${s?`?.${s}`:""}`;break}case"firstOf":{const r=(e.candidates??[]).map(l=>l.trim()).filter(Boolean),s=r.length>0?r.join(" ?? "):"''",a=(e.fallback??"").trim();o=a?`${s} ?? '${a.replace(/'/g,"\\'")}'`:s;break}case"literal":{const r=e.literalKind??"string",s=(e.literalValue??"").trim();r==="number"?o=s===""?"0":s:r==="boolean"?o=s==="true"?"true":"false":o=`'${s.replace(/'/g,"\\'")}'`;break}default:o="[]"}return wd(o)}function Km(e){if(typeof e!="string")return null;const t=vr(e).trim();let n=/^\((.+?)\s*\?\?\s*\[\]\)\.filter\(x => (.+)\)\.length$/.exec(t);return n?{key:"",op:"count",source:n[1].trim(),conditions:os(n[2])}:(n=/^\((.+?)\s*\?\?\s*\[\]\)\.filter\(x => (.+)\)\.reduce\(\(s, x\) => s \+ \(x\.(\w+(?:\.\w+)*) \?\? 0\), 0\)$/.exec(t),n?{key:"",op:"sum",source:n[1].trim(),conditions:os(n[2]),sumField:n[3]}:(n=/^\((.+?)\s*\?\?\s*\[\]\)\.filter\(x => (.+)\)$/.exec(t),n?{key:"",op:"filter",source:n[1].trim(),conditions:os(n[2])}:(n=/^\((.+?)\s*\?\?\s*\[\]\)\.map\(x => \(\{ value: x\.(\w+(?:\.\w+)*), label: x\.(\w+(?:\.\w+)*) \}\)\)$/.exec(t),n?{key:"",op:"toOptions",source:n[1].trim(),valueField:n[2],labelField:n[3]}:(n=/^\((.+?)\s*\?\?\s*\[\]\)\[(.+?)\](?:\?\.(\w+(?:\.\w+)*))?$/.exec(t),n?{key:"",op:"nth",source:n[1].trim(),index:n[2].trim(),prop:n[3]}:null))))}function os(e){const t=e.trim();if(t==="true")return[];const n=[];for(const o of Vm(t)){const r=Um(o.trim());r&&n.push(r)}return n}function Vm(e){const t=[];let n=0,o=0;for(let r=0;r=|<=|>|<)\s*(.+)$/.exec(e),t){const n={"===":"=","!==":"!=",">":">","<":"<",">=":">=","<=":"<="};return{field:t[1],cmp:n[t[2]]??"=",value:$d(t[3])}}return null}function $d(e){const t=e.trim(),n=/^'(.*)'$/.exec(t)||/^"(.*)"$/.exec(t);return n?n[1]:t}function Ed(e,t){const n=Bm(e,t);if(n)return{kind:"preset",recipeId:n.recipeId,params:n.params};const o=Km(e);return o?{kind:"custom",model:o}:{kind:"advanced"}}function qm(e){if(e==null)return"null";if(Array.isArray(e))return"list";switch(typeof e){case"number":return"number";case"boolean":return"boolean";case"string":return"string";default:return"object"}}function Id(e,t){if(typeof e!="string"||e.trim()==="")return{ok:!1,reason:"empty"};const n=vr(e);try{const o=Lm(n)?_d.evaluatePipeExpression(n,t??{},{skipCache:!0}):_d.evaluateExpression(n,t??{},{skipCache:!0});return{ok:!0,value:o,type:qm(o)}}catch(o){return{ok:!1,reason:o instanceof Error?o.message:String(o)}}}function Ad(e,t){const n=Array.isArray(e?.data_sources)?e.data_sources:[],o=[];for(const r of n){const s=r?.id;if(typeof s!="string"||s.length===0)continue;const{value:a}=ts(r,t?.sampleData),l=typeof r.label_key=="string"?r.label_key:void 0;o.push({id:s,sample:a,labelKey:l})}return o}function Td(e,t){const n=[];t?.sampleGlobal&&typeof t.sampleGlobal=="object"&&n.push({scope:"_global",tree:t.sampleGlobal});const o={},r={},s={},a=(l,d)=>{if(!(!d||typeof d!="object"||Array.isArray(d)))for(const[c,p]of Object.entries(d))if(p&&typeof p=="object"&&!Array.isArray(p)){const f=l[c]&&typeof l[c]=="object"&&!Array.isArray(l[c])?l[c]:l[c]={};a(f,p)}else c in l||(l[c]=p)};for(const l of t?.states?.groups??[])for(const d of l.items??[]){const c=d.initialState;c?.local&&a(o,c.local),c?.query&&a(r,c.query),c?.route&&a(s,c.route)}return Object.keys(o).length&&n.push({scope:"_local",tree:o}),Object.keys(r).length&&n.push({scope:"query",tree:r}),Object.keys(s).length&&n.push({scope:"route",tree:s}),e?.computed&&typeof e.computed=="object"&&!Array.isArray(e.computed)&&n.push({scope:"_computed",tree:e.computed}),n}function Dd(e,t){const n={};for(const r of t)r.scope!=="_computed"&&(n[r.scope]=r.tree);const o={};for(const[r,s]of Object.entries(e)){if(typeof s!="string"){o[r]=s;continue}const a=Id(s,n);o[r]=a.ok?a.value:s}return o}function Gm(e,t){const n=Ad(e,t),o=Td(e,t),r={};for(const s of n)s.sample!==void 0&&(r[s.id]=s.sample);for(const s of o)s.scope==="_computed"?r[s.scope]=Dd(s.tree,o):r[s.scope]=s.tree;return r}function Nd(e){const{raw:t,spec:n,evaluateComputed:o}=e;return u.useMemo(()=>{const r=Ad(t,n),s=Td(t,n);if(o){const a=s.findIndex(l=>l.scope==="_computed");if(a>=0){const l=Dd(s[a].tree,s);s[a]={scope:"_computed",tree:l}}}return ji({dataSources:r,states:s,spec:n??void 0})},[t,n,o])}function Ym(e){const{raw:t,spec:n,pageType:o}=e;return u.useMemo(()=>{const r=Array.isArray(t?.data_sources)?t.data_sources:[],s=[];for(const d of r){const c=d?.id;if(typeof c!="string"||c.length===0)continue;const{value:p}=ts(d,n?.sampleData),f=typeof d.label_key=="string"?d.label_key:void 0;s.push({id:c,sample:p,labelKey:f})}const a=[];n?.sampleGlobal&&typeof n.sampleGlobal=="object"&&a.push({scope:"_global",tree:n.sampleGlobal}),t?.computed&&typeof t.computed=="object"&&!Array.isArray(t.computed)&&a.push({scope:"_computed",tree:t.computed});const l=ji({dataSources:s,states:a,spec:n??void 0});if(typeof o=="string"&&o!=="")for(const d of["title","description"])l.push({expression:`{{_seo.${o}.${d}}}`,source:"_seo",sourceId:"_seo",path:`${o}.${d}`,shape:"scalar",preview:""});return l},[t,n,o])}function zd(e,t){const n=[],o=new Set,r=s=>{for(const a of s??[]){if(a.kind==="route"&&!a.isRedirect&&typeof a.path=="string"&&a.path.startsWith("/")&&!o.has(a.path)){o.add(a.path);const l=a.label.startsWith("$t:")?a.label.slice(3):a.label,d=a.label.startsWith("$t:")?t(l):l,c=d&&d!==l&&!d.startsWith("$t:")?d:"";n.push({value:a.path,label:c?`${c} (${a.path})`:a.path})}r(a.children)}};return r(e),n}function Md(e){const t=Array.isArray(e?.data_sources)?e.data_sources:[],n=[];for(const o of t){const r=o?.id;typeof r=="string"&&r.length>0&&n.push({value:r,label:r})}return n}function rs(e,t){const n=typeof e.id=="string"?e.id:"",o=typeof e.label_key=="string"?e.label_key:"";if(!o)return null;const r=o.startsWith("$t:")?o.slice(3):o,s=t(r);return s&&s!==r&&!s.startsWith("$t:")&&s!==n?s:null}function is(e,t){const n=e.__source;if(!n||typeof n!="object"||Array.isArray(n))return null;const o=n;if(o.kind!=="extension")return null;const r=o.extensionSourceType==="plugin"?t("layout_editor.data_sources.source.plugin"):t("layout_editor.data_sources.source.module"),s=typeof o.extensionIdentifier=="string"?o.extensionIdentifier:"",a=typeof o.extensionName=="string"?o.extensionName:"",l=a&&a!==s?`${a} (${s})`:s;return l?`${r}: ${l}`:r}function Xm(e,t,n){const o=Array.isArray(e?.data_sources)?e.data_sources:[],r=[],s=new Set;for(const a of o){const l=a?.id;typeof l!="string"||l.length===0||s.has(l)||(s.add(l),r.push({id:l,friendly:rs(a,t),source:is(a,n)}))}return r}function Ld(e){const t=e?.states?.groups??[],n=new Set;for(const o of t)for(const r of o.items??[]){for(const s of Object.keys(r.initialState?.local??{}))n.add(s);for(const s of Object.keys(r.initialState?.global??{}))n.add(s)}return Array.from(n).map(o=>({value:o,label:o}))}function Jm(e,t){const n=Array.isArray(e?.modals)?e.modals:[],o=[],r=new Set;for(const s of n){const a=s?.id;typeof a!="string"||a.length===0||r.has(a)||(r.add(a),o.push({value:a,label:Zm(s,a,t)}))}return o}function Zm(e,t,n){const r=e?.meta?.editor_label;if(typeof r=="string"&&r.length>0)return Pd(r,n);const s=e?.title;return typeof s=="string"&&s.length>0?Pd(s,n):t}function Pd(e,t){if(e.startsWith("$t:")){const n=e.slice(3),o=t?t(n):n;return o&&o!==n?o:n}return e}const Od=bo("ActionRecipeEngine"),Qm={api:"apiCall",nav:"navigate",showToast:"toast",setLocalState:"setState"},Rm=new Set(["comment"]);function wo(e){if(!e||typeof e!="object")return[];const t=[];for(const[n,o]of Object.entries(e)){if(Rm.has(n)||!o||typeof o!="object")continue;const r=o,s=r.build&&typeof r.build=="object"?r.build:typeof r.handler=="string"?{handler:r.handler}:void 0;!s||typeof s.handler!="string"||s.handler.length===0||t.push({id:n,label:typeof r.label=="string"?r.label:void 0,params:Array.isArray(r.params)?r.params:[],build:s})}return t}function on(e){const t=/^\{\{\s*([\w.]+)\s*\}\}$/.exec(e);return t?t[1]:null}const _r="...";function ev(e,t){return e.replace(/\{\{\s*([\w.]+)\s*\}\}/g,(n,o)=>{const r=t[o];return r==null?"":String(r)})}function ss(e,t){if(typeof e=="string"){const n=on(e);return n!==null?n in t?t[n]:void 0:e.includes("{{")?ev(e,t):e}if(Array.isArray(e))return e.map(n=>ss(n,t)).filter(n=>n!==void 0);if(e&&typeof e=="object"){const n={};for(const[o,r]of Object.entries(e)){if(o===_r&&typeof r=="string"){const d=on(r),c=d!==null?t[d]:void 0;if(c&&typeof c=="object"&&!Array.isArray(c))for(const[p,f]of Object.entries(c))f===void 0||p===""||(n[p]=f);continue}const s=ss(r,t);if(s===void 0||Array.isArray(s)&&s.length===0&&(o==="onSuccess"||o==="onError"))continue;const a=on(o),l=a!==null?a in t?String(t[a]):null:o;l===null||l===""||s&&typeof s=="object"&&!Array.isArray(s)&&Object.keys(s).length===0||(n[l]=s)}return n}return e}function tv(e){const t=e.handler;if(typeof t!="string")return e;const n=Qm[t];if(n)return Od.warn(`action recipe used forbidden handler "${t}" — corrected to "${n}" (core handler rules)`),{...e,handler:n};if(t==="apiCall"||t==="navigate"){const o=e.params;if(o&&typeof o=="object")for(const r of["target","onSuccess","onError"])r in o&&Od.warn(`action recipe build placed "${r}" inside params for handler "${t}" — should be top-level (core handler rules)`)}return e}function xr(e,t){const n=ss(e.build,t);if(typeof n.handler!="string"&&e.build.handler.includes("{{")&&(n.handler=e.build.handler),e.build.handler.includes("{{")){const o=new Set(e.params.filter(r=>r.required).map(r=>r.key));o.size>0&&Bd(e.build,n,o)}return tv(n)}function Bd(e,t,n){if(!e||typeof e!="object"||Array.isArray(e)||!t||typeof t!="object"||Array.isArray(t))return;const o=e,r=t;for(const[s,a]of Object.entries(o))if(s!=="handler"){if(typeof a=="string"){const l=on(a);l!==null&&n.has(l)&&!(s in r)&&(r[s]=a)}else if(a&&typeof a=="object"){if(!(s in r)||typeof r[s]!="object"||r[s]===null)if(JSON.stringify(a).includes("{{"))r[s]={};else continue;Bd(a,r[s],n)}}}function nv(e,t){if(!e||typeof e!="object")return null;const n=e.handler;if(typeof n!="string")return null;let o=null,r=-1;for(const s of t){const a=s.build.handler.includes("{{");if(!a&&s.build.handler!==n||a&&!ov(s.build,e))continue;const l=rv(s.build,e);if(l===null)continue;const d=Object.keys(l).length;d>r&&(r=d,o={recipeId:s.id,values:l})}return o}function ov(e,t){if(typeof t.handler=="string"&&t.handler===e.handler)return!0;const n=e.params;if(!n||typeof n!="object")return!1;const o=t.params;if(!o||typeof o!="object")return!1;const r=o,s=Object.entries(n).filter(([,a])=>typeof a=="string"&&on(a)!==null);return s.length===0?!1:s.every(([a])=>a in r)}function rv(e,t){const n={},o=(r,s)=>{if(typeof r=="string"){const a=on(r);return a!==null?(n[a]=s,!0):r.includes("{{")?typeof s=="string":r===s}if(Array.isArray(r))return Array.isArray(s);if(r&&typeof r=="object"){if(!s||typeof s!="object"||Array.isArray(s))return!1;const a=s,l=r[_r],d=typeof l=="string"?on(l):null,c=new Set(Object.keys(r).filter(p=>p!==_r));for(const[p,f]of Object.entries(r))if(p!==_r){if(typeof f=="string"&&on(f)!==null){const y=on(f);p in a&&(n[y]=a[p]);continue}if(!(p in a)){if(p==="onSuccess"||p==="onError"||p==="params"||p==="identity_target")continue;return!1}if(!o(f,a[p]))return!1}if(d!==null){const p={};for(const[f,y]of Object.entries(a))c.has(f)||(p[f]=y);Object.keys(p).length>0&&(n[d]=p)}return!0}return r===s};return o(e,t)?n:null}function Yn(e,t){const n=typeof e?.handler=="string"?e.handler:"",o=nv(e,t);return o?{kind:"preset",recipeId:o.recipeId,values:o.values,handler:n}:{kind:"advanced",handler:n}}function Wd(e,t,n){const o=e?.label,r=o?n?n(o):o:"",s=[],a=e?.params??[];for(const l of a){if(l.advanced||l.widget==="action-list")continue;const d=t[l.key];if(d==null||d==="")continue;const c=iv(d);if(c!==""&&(s.push(c),s.length>=2))break}return s.length===0?r:`${r} (${s.join(", ")})`}function iv(e){return typeof e=="string"?e:typeof e=="number"||typeof e=="boolean"?String(e):Array.isArray(e)?`[${e.length}]`:e&&typeof e=="object"?"{…}":""}function jd(e,t){const n={...e};return typeof t=="string"&&t.trim().length>0?n.if=t.trim():delete n.if,n}function as(e){const t=e?.if;return typeof t=="string"&&t.trim().length>0?t:null}const sv=/^\s*\$t:(custom\.[a-zA-Z0-9._-]+)\s*$/,av=/^\s*\$t:([a-zA-Z0-9._-]+)\s*$/,lv=/\{\{.*?\}\}/;function Hd(e,t){const n=typeof e=="string"?e:"",o=sv.exec(n);if(o){const a=o[1],l=t?t(a):"";return{customKey:a,binding:!1,paramKey:!1,displayValue:l}}const r=mt(n);if(r&&r.key.startsWith("custom.")){const a=t?t(r.key):"";return{customKey:r.key,binding:!1,paramKey:!0,displayValue:a}}if(lv.test(n))return{customKey:null,binding:!0,paramKey:!1,displayValue:n};const s=av.exec(n);if(s){const a=s[1],l=t?t(a):"";return{customKey:null,binding:!1,paramKey:!1,displayValue:l&&l!==a?l:""}}return{customKey:null,binding:!1,paramKey:!1,displayValue:n}}function dv(){const{state:e}=Je(),t=e.templateIdentifier,n=e.locale,o=e.selectedRoute?.layoutName??null,r=u.useCallback(l=>{try{const c=Pt.getInstance().translate(l,{templateId:t,locale:n});return c&&c!==l?c:""}catch{return""}},[t,n]),s=u.useCallback(l=>Hd(l,r),[r]),a=u.useCallback(async(l,d)=>{const c=Hd(l,r);if(c.binding)return{kind:"blocked"};if(c.customKey){if(d===c.displayValue)return{kind:"noop",customKey:c.customKey};const y=await yr(t,c.customKey,n,d);return y.kind==="error"?{kind:"error",message:y.message}:(await tn(t,n),{kind:"updated",customKey:c.customKey})}const p=await qn(t,o,n,d);if(p.kind==="error"||!p.resource)return{kind:"error",message:p.message};const f=p.resource.translation_key;return await tn(t,n),{kind:"created",customKey:f,token:`$t:${f}`}},[t,n,o,r]);return{locale:n,templateIdentifier:t,translate:r,classify:s,commitText:a}}async function ls(e,t,n,o,r,s,a,l,d,c){const p=c?Yi(o,c):null,f=!!(p&&p.keyifiable),y=d?d(o):o,g=f?p.chipValue:Xl(y),h=`p${f?p.bindings.length:cr(y).length}`,v=Math.max(0,Math.min(r,g.length)),b=Hl(g,v,h),x=ur(b).replace(/\{\{?p\d+\}?\}/g," ").replace(/\s+/g," ").trim(),k=x.length>0?x:b,w=await qn(e,t,n,k);if(w.kind==="error"||!w.resource)return{kind:"error",message:w.message};const _=w.resource.translation_key,S=An(b),E=w.resource.values??{},C={[n]:b};for(const z of Object.keys(E)){if(z===n)continue;let D=E[z]??"";for(const O of S)D=qi(D,O);C[z]=D}xo(e,_,C);const N=En(s,a,l);return{kind:"ok",text:`${f?Gl(`$t:${_}`,p):Yl(`$t:${_}`,y)}|${h}=${N}`,key:_}}async function wr(e,t,n,o,r,s,a){let l=mt(n);if(!l&&typeof n=="string"){const h=/^\s*\$t:(custom\.[A-Za-z0-9._-]+)\s*$/.exec(n);h&&(l={key:h[1],params:[]})}if(!l)return{kind:"error",message:"not a param key"};if(!l.key.startsWith("custom."))return{kind:"error",message:"not a custom param key"};const d=fb(l.params.map(h=>h.name)),c=En(r,s,a),p=`${n.trim()}|${d}=${c}`,f=await cn(e,l.key),y=Bb(l.key)??{},g=new Set([...Object.keys(f?.values??{}),...Object.keys(y),t]);if(g.size===0)return{kind:"error",message:"custom key not found"};const m={};for(const h of g){const v=It(l.key,h)??f?.values?.[h]??"";m[h]=h===t?Hl(v,o,d):qi(v,d)}return xo(e,l.key,m),{kind:"ok",text:p,paramName:d}}async function ko(e,t,n,o){return br(e,t,n,o),{kind:"ok"}}async function cv(e,t,n){const o=await cn(e,t);if(!o)return{kind:"error",message:"custom key not found"};const r={};for(const[s,a]of Object.entries(o.values??{})){const l=It(t,s)??a;r[s]=n(l)}xo(e,t,r);try{const s=Pt.getInstance();for(const[a,l]of Object.entries(r))s.setTranslationValue(e,a,t,l)}catch{}return{kind:"ok"}}function Fd(e,t,n){return cv(e,t,o=>Ql(o,n))}function Kd(e,t){if(!(typeof window>"u"))try{window.dispatchEvent(new CustomEvent(kt,{detail:{templateIdentifier:e,locale:t}}))}catch{}}async function kr(e,t,n,o){const r=await Fd(e,t,n);return Kd(e,o),r}const uv=/^\s*\$t:(custom\.[a-zA-Z0-9._-]+)\s*$/;function pv(e,t){const n=s=>{const a=[],l=/\{\{?(p\d+)\}?\}/g;let d;for(;(d=l.exec(s))!==null;)a.push(d[1]);return a},o=n(e),r=n(t);return o.length!==r.length?!0:o.some((s,a)=>s!==r[a])}function Vd(e){const t=e.text;if(typeof t!="string")return null;const n=uv.exec(t);if(n)return n[1];const o=mt(t);return o&&o.key.startsWith("custom.")?o.key:null}function ds(e){const t={},n=mt(typeof e=="string"?e:"");for(const o of n?.params??[])t[o.name]=Vt(o.expression);return t}function fv(e){return ds(typeof e.text=="string"?e.text:"")}function Ud({customKey:e,templateIdentifier:t,t:n,paramLabels:o,locales:r,onRemoveParam:s}){const a=r??ud(),l=Ft(),d=o??{},[c,p]=u.useState({status:"idle",values:{}}),[f,y]=u.useState(!1),[g,m]=u.useState(!1),[h,v]=u.useState(null),b=u.useRef({});u.useEffect(()=>{let $=!1;if(!e){p({status:"not_a_key",values:{}});return}return p({status:"loading",values:{}}),(async()=>{const A=await cn(t,e);if($)return;if(!A){p({status:"error",values:{}});return}const z={...A.values??{}};for(const D of Object.keys(z)){const O=It(e,D);O!==void 0&&(z[D]=O)}for(const D of a){const O=It(e,D);O!==void 0&&(z[D]=O)}b.current={...z},p({status:"loaded",id:A.id,lockVersion:A.lock_version,values:z})})(),()=>{$=!0}},[e,t]),u.useEffect(()=>{if(typeof window>"u"||!e)return;const $=A=>{const z=A.detail;z?.templateIdentifier&&z.templateIdentifier!==t||p(D=>{if(D.status!=="loaded")return D;let O=!1;const M={...D.values};for(const B of Object.keys(M)){const T=It(e,B);T!==void 0&&T!==M[B]&&(M[B]=T,O=!0)}return O?(b.current={...b.current,...M},{...D,values:M}):D})};return window.addEventListener(kt,$),()=>window.removeEventListener(kt,$)},[e,t]);const x=u.useCallback(($,A)=>{p(z=>{const D=z.values[$]??"",O={...z,values:{...z.values,[$]:A}};return e&&pv(D,A)&&(br(t,e,$,A),b.current={...b.current,[$]:A},l?.markDirty?.(),Kd(t,$)),O})},[e,t,l]),k=u.useCallback($=>{p(A=>{const z={};for(const[D,O]of Object.entries(A.values))z[D]=Ql(O,$);return b.current={...z},{...A,values:z}}),s?.($)},[s]),w=u.useMemo(()=>[...Object.values(c.values),...Object.values(b.current)].some(A=>An(A).length>0),[c.values]),_=u.useRef(null),S=u.useCallback(()=>{m(!0),_.current&&clearTimeout(_.current),_.current=setTimeout(()=>m(!1),1500)},[]);u.useEffect(()=>()=>{_.current&&clearTimeout(_.current)},[]);const E=u.useCallback(async()=>{if(m(!1),c.status!=="loaded"||c.id===void 0)return;const $=b.current;for(const[z,D]of Object.entries(c.values)){const O=$[z]??"";if(w){const M=An(O);if(M.length===0)continue;const B=An(D);if(!(M.length===B.length&&M.every((H,L)=>H===B[L]))){v(n("layout_editor.translation.placeholder_mismatch"));return}continue}if(Ui(O)&&!xb(O,D)){v(n("layout_editor.translation.token_mismatch"));return}}if(y(!0),v(null),w){xo(t,e??"",c.values),b.current={...c.values},l?.markDirty?.(),Et({op:"translation_field_update",translationKey:e,changedLocales:Object.keys(c.values),timestamp:Date.now()}),await new Promise(z=>setTimeout(z,0)),y(!1),S();return}const A=`/api/admin/templates/${encodeURIComponent(t)}/custom-translations/${c.id}`;try{const z=await fetch(A,{method:"PUT",credentials:"same-origin",headers:je({"Content-Type":"application/json"}),body:JSON.stringify({values:c.values,expected_lock_version:c.lockVersion??0})}),D=await z.json().catch(()=>null);if(z.status===409){v(n("layout_editor.translation.conflict"));return}if(!z.ok){v(D?.message??n("layout_editor.translation.save_failed"));return}const O=typeof D?.data?.lock_version=="number"?D.data.lock_version:(c.lockVersion??0)+1;p(M=>({...M,lockVersion:O})),b.current={...c.values},await tn(t),Et({op:"translation_field_update",translationKey:e,changedLocales:Object.keys(c.values),timestamp:Date.now()}),S()}catch(z){v(z instanceof Error?z.message:n("layout_editor.translation.save_failed"))}finally{y(!1)}},[c,t,e,n]);if(c.status==="not_a_key"||!e)return i.jsx("div",{"data-testid":"g7le-translation-not-a-key",style:cs,children:n("layout_editor.translation.not_a_key")});if(c.status==="loading"||c.status==="idle")return i.jsx("div",{"data-testid":"g7le-translation-loading",style:cs,children:n("layout_editor.translation.loading")});if(c.status==="error")return i.jsx("div",{"data-testid":"g7le-translation-error",style:cs,children:n("layout_editor.translation.load_failed")});const C=new Set([...a,...Object.keys(c.values)]),N=Array.from(C);return i.jsxs("div",{"data-testid":"g7le-translation-field",style:{display:"flex",flexDirection:"column",gap:8},children:[i.jsxs("div",{"data-testid":"g7le-translation-key",style:gv,children:[n("layout_editor.translation.key_label"),": ",i.jsx("code",{style:hv,children:e})]}),N.map($=>{const A=c.values[$]??"",z=A.trim().length===0;return i.jsxs("div",{className:"g7le-translation-row","data-testid":`g7le-translation-row-${$}`,style:yv,children:[i.jsxs("label",{htmlFor:`g7le-translation-input-${$}`,style:{fontSize:12,color:"#475569",minWidth:72,display:"inline-flex",alignItems:"center",gap:4},children:[Gn($,n),z&&i.jsx("span",{"data-testid":`g7le-translation-missing-${$}`,style:bv,children:n("layout_editor.translation.needs_translation")})]}),w?i.jsx("div",{style:{flex:1},"data-testid":`g7le-translation-chip-${$}`,children:i.jsx(mr,{value:A,onChange:D=>x($,D),t:n,paramLabels:d,testIdSuffix:$,onRemoveChip:s?k:void 0})}):i.jsx("input",{id:`g7le-translation-input-${$}`,type:"text","data-testid":`g7le-translation-input-${$}`,value:A,placeholder:z?n("layout_editor.translation.needs_translation"):"",onChange:D=>x($,D.currentTarget.value),onBlur:E,style:{flex:1,padding:"4px 8px",fontSize:13,border:"1px solid #cbd5e1",borderRadius:4,color:z?"#94a3b8":"#0f172a"}})]},$)}),i.jsxs("div",{style:{display:"flex",alignItems:"center",gap:8},children:[i.jsx("button",{type:"button","data-testid":"g7le-translation-save",disabled:f,onClick:E,style:mv,children:n(f?"layout_editor.translation.saving":"layout_editor.translation.save")}),g&&!f&&i.jsxs("span",{"data-testid":"g7le-translation-saved",style:{fontSize:11,color:"#16a34a",display:"inline-flex",alignItems:"center",gap:4},children:["✓ ",n("layout_editor.translation.saved")]}),h&&i.jsx("span",{"data-testid":"g7le-translation-save-error",style:{fontSize:11,color:"#dc2626"},children:h})]})]})}const cs={fontSize:12,color:"#94a3b8",padding:"16px 0",textAlign:"center"},gv={fontSize:12,color:"#475569"},hv={fontSize:11,background:"#f1f5f9",padding:"1px 4px",borderRadius:3,color:"#0f172a"},yv={display:"flex",alignItems:"center",gap:8},bv={fontSize:10,color:"#b45309",background:"#fef3c7",padding:"0 4px",borderRadius:3},mv={padding:"6px 12px",fontSize:12,border:"none",borderRadius:6,background:"#2563eb",color:"#fff",cursor:"pointer"};function Xn({anchorRef:e,open:t,onClose:n,children:o,placement:r="bottom",minWidth:s=240,maxWidth:a=360,margin:l=8,testid:d}){const c=u.useRef(null),[p,f]=u.useState(null),y=u.useCallback(()=>{const m=e.current,h=c.current;if(!m||!h)return;const v=m.getBoundingClientRect(),b=window.innerWidth,x=window.innerHeight,k=h.getBoundingClientRect(),w=Math.min(Math.max(k.width,s),a),_=k.height;let S=v.left;S+w+l>b&&(S=v.right-w),S=Math.min(Math.max(S,l),b-w-l);const E=x-v.bottom-l,C=v.top-l;let N,$;const A=r==="bottom",z=E>=Math.min(_,160),D=C>=Math.min(_,160);A&&z||A&&!D?(N=v.bottom+4,$=E):!A&&D||D?(N=v.top-4-Math.min(_,C),$=C):E>=C?(N=v.bottom+4,$=E):(N=l,$=C),f({left:S,top:N,maxHeight:Math.max($,120)})},[e,r,s,a,l]);if(u.useLayoutEffect(()=>{if(!t){f(null);return}y()},[t,y]),u.useEffect(()=>{if(!t)return;const m=()=>y();window.addEventListener("resize",m),window.addEventListener("scroll",m,!0);const h=b=>{const x=c.current,k=e.current,w=b.target;x?.contains(w)||k?.contains(w)||n()},v=b=>{b.key==="Escape"&&n()};return document.addEventListener("pointerdown",h,!0),document.addEventListener("keydown",v),()=>{window.removeEventListener("resize",m),window.removeEventListener("scroll",m,!0),document.removeEventListener("pointerdown",h,!0),document.removeEventListener("keydown",v)}},[t,y,n,e]),!t)return null;const g={...vv,minWidth:s,maxWidth:`min(${a}px, calc(100vw - ${l*2}px))`,...p?{left:p.left,top:p.top,maxHeight:p.maxHeight,visibility:"visible"}:{left:-9999,top:-9999,visibility:"hidden"}};return i.jsx("div",{ref:c,"data-testid":d,style:g,role:"dialog",children:o})}const vv={position:"fixed",zIndex:1e3,background:"#fff",border:"1px solid #cbd5e1",borderRadius:8,boxShadow:"0 4px 16px rgba(0,0,0,0.14)",padding:6,overflowY:"auto",boxSizing:"border-box"};function qd(e,t,n){if(!t)return n;const o=e(t);return!o||o===t||o.startsWith("$t:")?n:o}function $t({candidates:e,t,onSelect:n,testIdSuffix:o,defaultOpen:r=!1,floating:s=!0}){const[a,l]=u.useState(r),[d,c]=u.useState(""),p=u.useRef(null),f=u.useMemo(()=>Fi(e,"scalar").map(h=>({...h,resolvedLabel:qd(t,h.labelKey,h.path||h.sourceId)})),[e,t]),y=u.useMemo(()=>Ki(f,d),[f,d]),g=i.jsxs(i.Fragment,{children:[i.jsx("input",{type:"text",autoFocus:!0,"data-testid":`g7le-inline-binding-search-input-${o}`,value:d,placeholder:t("layout_editor.inline_binding.search_placeholder"),onChange:m=>c(m.target.value),style:kv}),i.jsx("div",{style:Sv,children:y.length===0?i.jsx("div",{style:Cv,children:t("layout_editor.inline_binding.no_results")}):y.map(m=>i.jsxs("button",{type:"button","data-testid":`g7le-inline-binding-candidate-${m.expression}`,onClick:()=>{n(m),l(!1),c("")},style:$v,children:[i.jsxs("span",{style:{display:"flex",flexDirection:"column",alignItems:"flex-start"},children:[i.jsx("span",{style:{fontSize:12},children:m.resolvedLabel}),i.jsxs("span",{style:Ev,children:[qd(t,m.groupLabelKey,m.sourceId)," · ",m.path||m.sourceId]})]}),i.jsx("span",{style:Iv,children:m.preview})]},m.expression))})]});return i.jsxs("div",{style:s?wv:{marginTop:4},children:[i.jsx("button",{ref:p,type:"button","data-testid":`g7le-inline-binding-search-toggle-${o}`,onClick:()=>l(m=>!m),title:t("layout_editor.inline_binding.search"),"aria-label":t("layout_editor.inline_binding.search"),"aria-expanded":a,style:_v,children:"🔍"}),s?i.jsx(Xn,{anchorRef:p,open:a,onClose:()=>l(!1),testid:`g7le-inline-binding-picker-${o}`,children:g}):a&&i.jsx("div",{"data-testid":`g7le-inline-binding-picker-${o}`,style:xv,children:g})]})}const _v={fontSize:13,lineHeight:1,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#334155",padding:"4px 8px",cursor:"pointer",textAlign:"center",whiteSpace:"nowrap"},xv={marginTop:4,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",padding:6},wv={display:"inline-block"},kv={width:"100%",padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,boxSizing:"border-box"},Sv={marginTop:4,maxHeight:200,overflowY:"auto",display:"flex",flexDirection:"column",gap:2},Cv={fontSize:11,color:"#94a3b8",padding:"8px 0",textAlign:"center"},$v={display:"flex",alignItems:"center",justifyContent:"space-between",gap:8,border:"none",background:"transparent",cursor:"pointer",padding:"6px 8px",borderRadius:4,textAlign:"left"},Ev={fontSize:10,color:"#94a3b8"},Iv={fontSize:10,color:"#64748b",fontFamily:"monospace",whiteSpace:"nowrap"},Av=["$localized"];function Jn(e){const t=e.trim();if(!t.startsWith("{{")||!t.endsWith("}}")||t.length<4)return null;let n=0;for(let o=0;oun(n).decomposed)}function So(e,t,n,o){switch(e.kind){case"leaf":Yd(e.text,t,n,o);return;case"conditional":So(e.then,t,n,o),o.push({kind:"ellipsis",text:"⋯"});return;case"fallback":So(e.primary,t,n,o);return;case"concat":e.parts.forEach(r=>So(r,t,n,o));return;default:o.push({kind:"text",text:e.source});return}}function Yd(e,t,n,o){const r=e.trim();if(/^\{\{([\s\S]*)\}\}$/.exec(r)&&!r.slice(2,-2).includes("}}")){o.push({kind:"chip",text:n(r)});return}if(r.startsWith("$t:")){const a=r.slice(3).split("|")[0];o.push({kind:"text",text:t(a)||a});return}for(const a of e.split(/(\{\{(?:[^}]|\}(?!\}))*\}\})/g))a!==""&&(/^\{\{[\s\S]*\}\}$/.test(a)?o.push({kind:"chip",text:n(a)}):o.push({kind:"text",text:a}))}function us(e,t,n){const o=[];if(typeof e!="string"||e==="")return o;if(Jn(e)===null&&/\{\{/.test(e)){for(const a of e.split(/(\{\{(?:[^}]|\}(?!\}))*\}\})/g))if(!(a===void 0||a===""))if(/^\{\{[\s\S]*\}\}$/.test(a)){const l=un(a);So(l.node,t,n,o)}else Yd(a,t,n,o);return o}const s=un(e);return So(s.node,t,n,o),o}function Tv(e){if(typeof e!="string"||e.trim()==="")return"empty";const t=e.trim();if(Jn(t)===null)return"simple";const n=un(e);return n.node.kind==="leaf"?"simple":n.decomposed?"expression":"raw"}function un(e){const t=typeof e=="string"?e:"",n=t.trim();if(n==="")return{node:{kind:"leaf",text:t},decomposed:!1,original:t};const o=Jn(n);if(o===null)return{node:{kind:"leaf",text:t},decomposed:!1,original:t};let r;try{r=Wv(o)}catch{r=null}if(r===null)return{node:{kind:"raw",source:o},decomposed:!1,original:t};if(r.kind==="leaf"||r.kind==="raw")return{node:r,decomposed:!1,original:t};if(r.kind==="fallback"&&r.op==="??"&&Dv(r.primary)&&Nv(r.fallback))return{node:{kind:"leaf",text:t},decomposed:!1,original:t};const s=Zn(r,!0);return Vv(o,s)?{node:r,decomposed:!0,original:t}:{node:{kind:"raw",source:o},decomposed:!1,original:t}}function Zn(e,t=!1){const n=Jd(e);return t?n:e.kind==="leaf"?e.text:`{{${n}}}`}function Co(e){return Zn({kind:"conditional",condition:{kind:"simple",left:"",op:"truthy",right:""},then:{kind:"leaf",text:typeof e=="string"?e:""},else:{kind:"leaf",text:""}},!1)}function ps(e){return Zn({kind:"fallback",op:"??",primary:{kind:"leaf",text:typeof e=="string"?e:""},fallback:{kind:"leaf",text:""}},!1)}function $o(e){switch(e.kind){case"leaf":return e.text;case"conditional":return $o(e.then);case"fallback":return $o(e.primary);case"concat":return e.parts.map($o).join("");default:return e.kind==="raw"?`{{${e.source}}}`:""}}function lM(e){return e}function Sr(e){if(typeof e!="string"||e.trim()==="")return"";if(Jn(e)===null&&/\{\{/.test(e)){for(const n of e.split(/(\{\{(?:[^}]|\}(?!\}))*\}\})/g))if(!(n===void 0||n===""))return/^\{\{[\s\S]*\}\}$/.test(n)?$o(un(n).node):n;return""}return $o(un(e).node)}function Dv(e){if(e.kind!=="leaf")return!1;const t=/^\{\{([\s\S]*)\}\}$/.exec(e.text.trim());return t?Qn(t[1].trim()):!1}function Nv(e){if(e.kind!=="leaf")return!1;const t=e.text.trim();return!(/\{\{/.test(t)||t.startsWith("$t:"))}function Cr(e){return`'${e.replace(/\\/g,"\\\\").replace(/'/g,"\\'")}'`}function zv(e){const t=[];let n=0,o=0;const r=e.length;for(;on&&t.push(Cr(e.slice(n,o)));let s=o+2;for(;s=r)break;const a=e.slice(o+2,s).trim();t.push(a),o=s+2,n=o;continue}o++}return nEo(t,"concat")).join(" + ");case"fallback":return`${Eo(e.primary,"fallback")} ${e.op} ${Eo(e.fallback,"fallback")}`;case"conditional":return`${Lv(e.condition)} ? ${Eo(e.then,"conditional")} : ${Eo(e.else,"conditional")}`;default:return""}}function Eo(e,t){const n=Jd(e);return e.kind==="leaf"||e.kind==="raw"?n:e.kind==="conditional"?t==="conditional"?n:`(${n})`:e.kind==="fallback"?t==="concat"?`(${n})`:n:(e.kind==="concat",n)}const Ov=["===","!==","??","||","&&","==","!=",">=","<="];function Zd(e){const t=[];let n=0;const o=e.length;for(;n=o)throw new Error("unterminated-binding-in-string");d+=e.slice(l,c+2),l=c+2;continue}if(e[l]===a){d+=a,l++;break}if(a==="`"&&e[l]==="$"&&e[l+1]==="{")throw new Error("template-literal-interpolation-unsupported");d+=e[l],l++}if(l>o||d[d.length-1]!==a)throw new Error("unterminated-string");t.push({kind:"string",value:d,start:n,end:l}),n=l;continue}if(r==="{"&&e[n+1]==="{"){let a=n+2;for(;a"||r==="<"){t.push({kind:"op",value:r,start:n,end:n+1}),n++;continue}if(r==="!"){t.push({kind:"op",value:"!",start:n,end:n+1}),n++;continue}if(r==="("){t.push({kind:"lparen",value:"(",start:n,end:n+1}),n++;continue}if(r===")"){t.push({kind:"rparen",value:")",start:n,end:n+1}),n++;continue}if(r==="["){t.push({kind:"lbracket",value:"[",start:n,end:n+1}),n++;continue}if(r==="]"){t.push({kind:"rbracket",value:"]",start:n,end:n+1}),n++;continue}if(r==="."){t.push({kind:"dot",value:".",start:n,end:n+1}),n++;continue}if(/[0-9]/.test(r)){let a=n;for(;a=n)return"";const o=this.toks[t].start,r=this.toks[n-1].end;return this.src.slice(o,r)}parseFallback(){let t=this.parseConcat(),n=this.peek();for(;n&&n.kind==="op"&&(n.value==="??"||n.value==="||");){const o=n.value;this.next();const r=this.parseConcat();t={kind:"fallback",op:o,primary:t,fallback:r},n=this.peek()}return t}parseConcat(){const t=this.parseUnaryOrPrimary(),n=[t];let o=this.peek();for(;o&&o.kind==="op"&&o.value==="+";)this.next(),n.push(this.parseUnaryOrPrimary()),o=this.peek();return n.length===1?t:{kind:"concat",parts:n}}parseUnaryOrPrimary(){return this.parsePrimary()}parsePrimary(){const t=this.peek();if(!t)throw new Error("unexpected-end");if(t.kind==="lparen"){this.next();const n=this.parseTernary(),o=this.next();if(!o||o.kind!=="rparen")throw new Error("expected-rparen");return n}if(t.kind==="string")return this.next(),{kind:"leaf",text:Rd(t.value)};if(t.kind==="tkey")return this.next(),{kind:"leaf",text:t.value};if(t.kind==="binding")return this.next(),{kind:"leaf",text:t.value};if(t.kind==="ident"&&Av.includes(t.value)&&this.toks[this.pos+1]?.kind==="lparen"){const n=this.tryParsePathWrapperCall(t.value);if(n!==null)return n;throw new Error("path-wrapper-non-simple-arg")}if(t.kind==="ident")return{kind:"leaf",text:`{{${this.parsePath()}}}`};if(t.kind==="number")return this.next(),{kind:"leaf",text:t.value};throw new Error(`unparseable-primary:${t.kind}`)}parsePath(){let t="";const n=this.next();if(!n||n.kind!=="ident")throw new Error("expected-ident");for(t+=n.value;;){const o=this.peek();if(!o)break;if(o.kind==="op"&&o.value==="?"&&this.toks[this.pos+1]?.kind==="dot"){this.next(),this.next();const r=this.next();if(!r||r.kind!=="ident")throw new Error("expected-ident-after-optional");t+="?."+r.value;continue}if(o.kind==="dot"){this.next();const r=this.next();if(!r||r.kind!=="ident")throw new Error("expected-ident-after-dot");t+="."+r.value;continue}if(o.kind==="lbracket"){this.next();const r=this.next(),s=this.next();if(!s||s.kind!=="rbracket")throw new Error("expected-rbracket");if(r&&(r.kind==="number"||r.kind==="string")){t+=`[${r.value}]`;continue}throw new Error("non-literal-index")}if(o.kind==="lparen")throw new Error("call-expression");break}return t}tryParsePathWrapperCall(t){const n=this.pos;this.next();const o=this.next();if(!o||o.kind!=="lparen")return this.pos=n,null;let r;try{if(this.peek()?.kind!=="ident")return this.pos=n,null;r=this.parsePath()}catch{return this.pos=n,null}const s=this.next();return!s||s.kind!=="rparen"?(this.pos=n,null):{kind:"leaf",text:`{{${t}(${r})}}`}}}function Wv(e){return new Bv(e).parse()}const jv=["===","!==","==","!=",">=","<=",">","<"];function Hv(e,t){const n=e.trim();if(n==="")return{kind:"raw",source:n};if(n===Xd)return{kind:"simple",left:"",op:"truthy",right:""};if(n.startsWith("!")){const s=n.slice(1).trim(),a=Qd(s);return Qn(a)?{kind:"simple",left:a,op:"falsy",right:""}:{kind:"raw",source:n}}const o=Fv(n);if(o){const{left:s,op:a,right:l}=o,d=s.trim(),c=l.trim();return Qn(d)&&(c==="''"||c==='""')?{kind:"simple",left:d,op:a,right:""}:Qn(d)&&Kv(c)?{kind:"simple",left:d,op:a,right:c}:{kind:"raw",source:n}}const r=Qd(n);return Qn(r)?{kind:"simple",left:r,op:"truthy",right:""}:{kind:"raw",source:n}}function Fv(e){let t=0,n=null;for(let o=0;o=o||!/[A-Za-z_$]/.test(t[n]))return!1;for(n++;n=o||!/[A-Za-z_$]/.test(t[n]))return!1;for(n++;n=2&&(t[0]==="'"||t[0]==='"'||t[0]==="`")?t.slice(1,-1).replace(/\\(['"`\\])/g,"$1"):t}function Vv(e,t){try{return $r(e)===$r(t)}catch{return!1}}function $r(e){const t=Jn(e);return t!==null?$r(t):Zd(e).map(o=>{if(o.kind==="string")return`S:${Rd(o.value)}`;if(o.kind==="binding"){const r=/^\{\{([\s\S]*)\}\}$/.exec(o.value);return`B:${r?$r(r[1].trim()):o.value.replace(/\s+/g,"")}`}return o.kind==="tkey"?`S:${o.value}`:o.kind==="op"?`O:${o.value}`:o.kind==="ident"||o.kind==="number"?`I:${o.value}`:o.kind==="dot"?".":o.kind==="lbracket"?"[":o.kind==="rbracket"?"]":o.kind==="lparen"||o.kind==="rparen"?"":`X:${o.value}`}).join("")}function Rn({active:e,testid:t}){return i.jsx("div",{"data-testid":t,"data-active":e?"true":"false","aria-hidden":"true",style:e?qv:Uv})}const Uv={height:0,margin:0,transition:"height 80ms"},qv={height:3,margin:"1px 0",borderRadius:2,background:"#2563eb",boxShadow:"0 0 0 1px #93c5fd"};function fs(e){const{length:t,onMove:n,canDrag:o,canDropAt:r}=e,[s,a]=u.useState(null),[l,d]=u.useState(null),c=u.useCallback(m=>{o&&!o(m)||(a(m),d(m))},[o]),p=u.useCallback(()=>{a(null),d(null)},[]),f=u.useCallback((m,h)=>{if(s===null)return;const v=h==="before"?m:m+1;if(r&&!r(s,v)){d(null);return}d(v)},[s,r]),y=u.useCallback(()=>{if(s!==null&&l!==null){const m=l>s?l-1:l;m!==s&&(!r||r(s,l))&&n(s,m)}a(null),d(null)},[s,l,n,r]),g=u.useCallback(m=>s!==null&&l===m&&m<=t,[s,l,t]);return{dragIndex:s,dropIndex:l,dragging:s!==null,onDragStart:c,onDragEnd:p,onDragOverItem:f,onDrop:y,isDropTarget:g}}const Gv={text:"",expression:"{{false ? '' : ''}}",fallback:"{{'' ?? ''}}",data:""};function ec(e,t){const n=In(e);let o=t;const r=n.map(s=>({id:o++,kind:s.kind==="literal"?"text":"expression",value:s.raw}));return r.length===0&&r.push({id:o++,kind:"text",value:""}),{segs:r,nextSeq:o}}function gs({value:e,onChange:t,t:n,candidates:o,testidPrefix:r="g7le-seg-value",renderLeafInput:s}){const a=u.useRef(0),l=u.useRef(e),d=u.useMemo(()=>{const k=ec(e,a.current);return a.current=k.nextSeq,k.segs},[]),[c,p]=u.useState(d),[f,y]=u.useState(!1);if(e!==l.current){l.current=e;const k=c.map(w=>w.value).join("");if(e!==k){const w=ec(e,a.current);a.current=w.nextSeq,p(w.segs)}}const g=u.useCallback(k=>{p(k);const w=k.map(_=>_.value).join("");l.current=w,t(w)},[t]),m=u.useCallback((k,w)=>{g(c.map(_=>_.id===k?{..._,value:w??""}:_))},[c,g]),h=u.useCallback(k=>{const w=c.filter(_=>_.id!==k);g(w.length>0?w:[{id:a.current++,kind:"text",value:""}])},[c,g]),v=u.useCallback(k=>{g([...c,{id:a.current++,kind:k,value:Gv[k]}])},[c,g]),b=u.useCallback((k,w)=>{if(k===w||k<0||w<0||k>=c.length||w>=c.length)return;const _=c.slice(),[S]=_.splice(k,1);_.splice(w,0,S),g(_)},[c,g]),x=fs({length:c.length,onMove:b});return i.jsxs("div",{"data-testid":r,style:Jv,children:[i.jsx("div",{style:Zv,children:n("layout_editor.value_tree.segmented_hint")}),c.map((k,w)=>i.jsxs(u.Fragment,{children:[i.jsx(Rn,{active:x.isDropTarget(w),testid:`${r}-dropline-${w}`}),i.jsx(Xv,{seg:k,index:w,t:n,candidates:o,testidPrefix:r,renderLeafInput:s,onValueChange:_=>m(k.id,_),onRemove:()=>h(k.id),dragging:x.dragIndex===w,onDragStartCard:()=>x.onDragStart(w),onDragEndCard:x.onDragEnd,onDragOverCard:_=>x.onDragOverItem(w,_),onDropCard:x.onDrop})]},k.id)),i.jsx(Rn,{active:x.isDropTarget(c.length),testid:`${r}-dropline-end`}),i.jsxs("div",{style:o_,"data-testid":`${r}-add-row`,children:[i.jsxs("button",{type:"button","data-testid":`${r}-add-text`,onClick:()=>v("text"),style:Er,children:["+ ",n("layout_editor.value_tree.segment.text")]}),i.jsxs("button",{type:"button","data-testid":`${r}-add-expression`,onClick:()=>v("expression"),style:Er,children:["+ ",n("layout_editor.value_tree.segment.expression")]}),i.jsxs("button",{type:"button","data-testid":`${r}-add-fallback`,onClick:()=>v("fallback"),style:Er,children:["+ ",n("layout_editor.value_tree.segment.fallback")]}),i.jsxs("button",{type:"button","data-testid":`${r}-add-data`,onClick:()=>v("data"),style:Er,children:["+ ",n("layout_editor.value_tree.segment.data")]})]}),i.jsxs("div",{style:s_,children:[i.jsxs("button",{type:"button","data-testid":`${r}-source-toggle`,"aria-expanded":f,onClick:()=>y(k=>!k),style:a_,children:[""," ",n("layout_editor.value_tree.show_source")," ",f?"▴":"▾"]}),f&&i.jsx("code",{"data-testid":`${r}-source-code`,style:l_,children:c.map(k=>k.value).join("")})]})]})}function Yv({t:e,candidates:t,onPick:n,testidPrefix:o}){return!t||t.length===0?i.jsx("div",{"data-testid":`${o}-data-empty`,style:i_,children:e("layout_editor.value_tree.segment.data_no_candidates")}):i.jsxs("div",{"data-testid":`${o}-data-picker`,style:{minWidth:0},children:[i.jsx("div",{style:r_,children:e("layout_editor.value_tree.segment.data_pick_hint")}),i.jsx($t,{candidates:t,t:e,onSelect:r=>{n(En(r.sourceId,r.path,"scalar"))},testIdSuffix:`${o}-data`,defaultOpen:!0,floating:!0})]})}function Xv({seg:e,index:t,t:n,candidates:o,testidPrefix:r,renderLeafInput:s,onValueChange:a,onRemove:l,dragging:d,onDragStartCard:c,onDragEndCard:p,onDragOverCard:f,onDropCard:y}){const g=e.kind==="text"?"layout_editor.value_tree.segment.text":e.kind==="data"?"layout_editor.value_tree.segment.data":e.kind==="fallback"?"layout_editor.value_tree.segment.fallback":"layout_editor.value_tree.segment.expression";return i.jsxs("div",{"data-testid":`${r}-card-${t}`,"data-seg-kind":e.kind,style:d?{...tc,opacity:.5}:tc,onDragOver:m=>{m.preventDefault();const h=m.currentTarget.getBoundingClientRect();f(m.clientY{m.preventDefault(),y()},children:[i.jsxs("div",{style:Qv,children:[i.jsx("span",{"data-testid":`${r}-drag-${t}`,style:t_,draggable:!0,onDragStart:c,onDragEnd:p,title:n("layout_editor.value_tree.drag_reorder"),"aria-label":n("layout_editor.value_tree.drag_reorder"),role:"button",children:"⠿"}),i.jsx("span",{style:Rv,children:n(g)}),i.jsx("button",{type:"button","data-testid":`${r}-remove-${t}`,onClick:l,title:n("layout_editor.value_tree.segment.remove"),"aria-label":n("layout_editor.value_tree.segment.remove"),style:n_,children:"✕"})]}),i.jsx("div",{style:e_,children:e.kind==="data"&&e.value.trim()===""?i.jsx(Yv,{t:n,candidates:o,onPick:a,testidPrefix:`${r}-field-${t}`}):s?s({value:e.value,onChange:a,t:n,candidates:o,testidPrefix:`${r}-field-${t}`}):i.jsx(vt,{value:e.value,onChange:a,t:n,candidates:o,testidPrefix:`${r}-field-${t}`,enableExpressionTree:!0,expressionSourceToggle:!1})})]})}const Jv={display:"flex",flexDirection:"column",gap:6,width:"100%",minWidth:0},Zv={fontSize:11,color:"#64748b"},tc={display:"flex",flexDirection:"column",gap:4,padding:"6px 8px",border:"1px solid #e2e8f0",borderLeft:"3px solid #6366f1",borderRadius:6,background:"#f8fafc",minWidth:0},Qv={display:"flex",alignItems:"center",gap:6,minWidth:0},Rv={fontSize:10,fontWeight:600,color:"#6366f1",flex:1,minWidth:0},e_={minWidth:0},t_={color:"#94a3b8",cursor:"grab",fontSize:14,userSelect:"none"},n_={border:"none",background:"transparent",color:"#94a3b8",cursor:"pointer",fontSize:12,padding:2,flexShrink:0},o_={display:"flex",flexWrap:"wrap",gap:6,marginTop:2},r_={fontSize:11,color:"#64748b",marginBottom:2},i_={fontSize:11,color:"#94a3b8",padding:"4px 0"},Er={padding:"4px 10px",fontSize:11,border:"1px dashed #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer",whiteSpace:"nowrap"},s_={display:"flex",flexDirection:"column",gap:4,marginTop:2},a_={alignSelf:"flex-start",padding:"3px 8px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#475569",cursor:"pointer",fontFamily:"monospace"},l_={fontSize:11,background:"#0f172a",color:"#e2e8f0",padding:"6px 8px",borderRadius:4,wordBreak:"break-all",fontFamily:"monospace"},d_=[{value:"truthy",labelKey:"layout_editor.value_tree.cond.op_truthy"},{value:"falsy",labelKey:"layout_editor.value_tree.cond.op_falsy"},{value:"===",labelKey:"layout_editor.value_tree.cond.op_eq"},{value:"!==",labelKey:"layout_editor.value_tree.cond.op_neq"},{value:">",labelKey:"layout_editor.value_tree.cond.op_gt"},{value:"<",labelKey:"layout_editor.value_tree.cond.op_lt"},{value:">=",labelKey:"layout_editor.value_tree.cond.op_gte"},{value:"<=",labelKey:"layout_editor.value_tree.cond.op_lte"}];function nc({node:e,onChange:t,t:n,candidates:o,testidPrefix:r="g7le-value-tree",depth:s=0,showSourceToggle:a=!0,renderLeafInput:l}){const[d,c]=u.useState(!1),p=s===0,f=i.jsx(eo,{node:e,onChange:t,t:n,candidates:o,testidPrefix:r,depth:s,renderLeafInput:l});return p?a?i.jsxs("div",{"data-testid":r,style:rc,children:[f,i.jsxs("div",{style:A_,children:[i.jsxs("button",{type:"button","data-testid":`${r}-source-toggle`,"aria-expanded":d,onClick:()=>c(y=>!y),style:T_,children:[""," ",n("layout_editor.value_tree.show_source")," ",d?"▴":"▾"]}),d&&i.jsx("code",{"data-testid":`${r}-source-code`,style:D_,children:Zn(e,!1)})]})]}):i.jsx("div",{"data-testid":r,style:rc,children:f}):f}function eo({node:e,onChange:t,t:n,candidates:o,testidPrefix:r,depth:s,renderLeafInput:a}){switch(e.kind){case"leaf":return i.jsx(gs,{value:e.text,onChange:l=>t({kind:"leaf",text:l}),t:n,candidates:o,testidPrefix:`${r}-leaf`,renderLeafInput:a});case"conditional":return i.jsx(c_,{node:e,onChange:t,t:n,candidates:o,testidPrefix:r,depth:s,renderLeafInput:a});case"fallback":return i.jsx(u_,{node:e,onChange:t,t:n,candidates:o,testidPrefix:r,depth:s,renderLeafInput:a});case"concat":return i.jsx(f_,{node:e,onChange:t,t:n,candidates:o,testidPrefix:r,depth:s,renderLeafInput:a});default:return i.jsxs("div",{"data-testid":`${r}-raw`,style:N_,children:[i.jsx("code",{style:ac,children:e.source??""}),i.jsxs("span",{style:lc,children:["🔒 ",n("layout_editor.value_tree.raw_code_only")]})]})}}function c_({node:e,onChange:t,t:n,candidates:o,testidPrefix:r,depth:s,renderLeafInput:a}){const l=u.useCallback(p=>t({...e,condition:p}),[e,t]),d=u.useCallback(p=>t({...e,then:p}),[e,t]),c=u.useCallback(p=>t({...e,else:p}),[e,t]);return i.jsxs("div",{"data-testid":`${r}-conditional`,style:hs(s),children:[i.jsx(g_,{condition:e.condition,onChange:l,t:n,candidates:o,testidPrefix:r}),i.jsx(Ir,{label:n("layout_editor.value_tree.cond.when_true"),testid:`${r}-then`,children:i.jsx(eo,{node:e.then,onChange:d,t:n,candidates:o,testidPrefix:`${r}-then`,depth:s+1,renderLeafInput:a})}),i.jsx(Ir,{label:n("layout_editor.value_tree.cond.when_false"),testid:`${r}-else`,children:i.jsx(eo,{node:e.else,onChange:c,t:n,candidates:o,testidPrefix:`${r}-else`,depth:s+1,renderLeafInput:a})})]})}function u_({node:e,onChange:t,t:n,candidates:o,testidPrefix:r,depth:s,renderLeafInput:a}){const l=u.useCallback(c=>t({...e,primary:c}),[e,t]),d=u.useCallback(c=>t({...e,fallback:c}),[e,t]);return i.jsxs("div",{"data-testid":`${r}-fallback`,style:hs(s),children:[i.jsx(Ir,{label:n("layout_editor.value_tree.fallback.primary"),testid:`${r}-primary`,children:i.jsx(eo,{node:e.primary,onChange:l,t:n,candidates:o,testidPrefix:`${r}-primary`,depth:s+1,renderLeafInput:a})}),i.jsx(Ir,{label:n(e.op==="??"?"layout_editor.value_tree.fallback.when_empty":"layout_editor.value_tree.fallback.when_falsy"),testid:`${r}-fallback-branch`,children:i.jsx(eo,{node:e.fallback,onChange:d,t:n,candidates:o,testidPrefix:`${r}-fallbackb`,depth:s+1,renderLeafInput:a})})]})}const p_={text:()=>({kind:"leaf",text:""}),data:()=>({kind:"leaf",text:""}),expression:()=>({kind:"conditional",condition:{kind:"simple",left:"",op:"truthy",right:""},then:{kind:"leaf",text:""},else:{kind:"leaf",text:""}}),fallback:()=>({kind:"fallback",op:"??",primary:{kind:"leaf",text:""},fallback:{kind:"leaf",text:""}})};function f_({node:e,onChange:t,t:n,candidates:o,testidPrefix:r,depth:s,renderLeafInput:a}){const l=u.useCallback((g,m)=>{const h=e.parts.slice();h[g]=m,t({...e,parts:h})},[e,t]),d=u.useCallback(g=>{g.length===0?t({kind:"leaf",text:""}):g.length===1?t(g[0]):t({...e,parts:g})},[e,t]),c=u.useCallback(g=>d(e.parts.filter((m,h)=>h!==g)),[e.parts,d]),p=u.useCallback(g=>d([...e.parts,p_[g]()]),[e.parts,d]),f=u.useCallback((g,m)=>{if(g===m||g<0||m<0||g>=e.parts.length||m>=e.parts.length)return;const h=e.parts.slice(),[v]=h.splice(g,1);h.splice(m,0,v),d(h)},[e.parts,d]),y=fs({length:e.parts.length,onMove:f});return i.jsxs("div",{"data-testid":`${r}-concat`,style:hs(s),children:[i.jsx("div",{style:k_,children:n("layout_editor.value_tree.concat.hint")}),e.parts.map((g,m)=>i.jsxs(u.Fragment,{children:[i.jsx(Rn,{active:y.isDropTarget(m),testid:`${r}-part-dropline-${m}`}),i.jsxs("div",{"data-testid":`${r}-part-card-${m}`,style:y.dragIndex===m?{...sc,opacity:.5}:sc,onDragOver:h=>{h.preventDefault();const v=h.currentTarget.getBoundingClientRect();y.onDragOverItem(m,h.clientY{h.preventDefault(),y.onDrop()},children:[i.jsxs("div",{style:S_,children:[i.jsx("span",{"data-testid":`${r}-part-handle-${m}`,style:E_,draggable:!0,onDragStart:()=>y.onDragStart(m),onDragEnd:y.onDragEnd,title:n("layout_editor.value_tree.drag_reorder"),"aria-label":n("layout_editor.value_tree.drag_reorder"),role:"button",children:"⠿"}),i.jsx("span",{style:C_,children:n("layout_editor.value_tree.concat.part",{n:m+1})}),i.jsx("button",{type:"button","data-testid":`${r}-part-remove-${m}`,onClick:()=>c(m),title:n("layout_editor.value_tree.segment.remove"),"aria-label":n("layout_editor.value_tree.segment.remove"),style:I_,children:"✕"})]}),i.jsx("div",{style:{minWidth:0},children:i.jsx(eo,{node:g,onChange:h=>l(m,h),t:n,candidates:o,testidPrefix:`${r}-part-${m}`,depth:s+1,renderLeafInput:a})})]})]},m)),i.jsx(Rn,{active:y.isDropTarget(e.parts.length),testid:`${r}-part-dropline-end`}),i.jsxs("div",{style:$_,"data-testid":`${r}-part-add-row`,children:[i.jsxs("button",{type:"button","data-testid":`${r}-part-add-text`,onClick:()=>p("text"),style:Ar,children:["+ ",n("layout_editor.value_tree.segment.text")]}),i.jsxs("button",{type:"button","data-testid":`${r}-part-add-expression`,onClick:()=>p("expression"),style:Ar,children:["+ ",n("layout_editor.value_tree.segment.expression")]}),i.jsxs("button",{type:"button","data-testid":`${r}-part-add-fallback`,onClick:()=>p("fallback"),style:Ar,children:["+ ",n("layout_editor.value_tree.segment.fallback")]}),i.jsxs("button",{type:"button","data-testid":`${r}-part-add-data`,onClick:()=>p("data"),style:Ar,children:["+ ",n("layout_editor.value_tree.segment.data")]})]})]})}function g_({condition:e,onChange:t,t:n,candidates:o,testidPrefix:r}){if(e.kind==="raw")return i.jsxs("div",{"data-testid":`${r}-cond-raw`,style:v_,children:[i.jsx("span",{style:ic,children:n("layout_editor.value_tree.cond.label")}),i.jsx("code",{style:ac,children:e.source}),i.jsxs("span",{style:lc,children:["🔒 ",n("layout_editor.value_tree.cond.code_only")]})]});const s=e,a=s.op!=="truthy"&&s.op!=="falsy";return i.jsxs("div",{"data-testid":`${r}-cond`,style:m_,children:[i.jsx("span",{style:ic,children:n("layout_editor.value_tree.cond.label")}),i.jsx(oc,{testid:`${r}-cond-left`,value:s.left,onCommit:l=>t({...s,left:l}),placeholder:n("layout_editor.value_tree.cond.field_placeholder"),candidates:o,t:n}),i.jsx("select",{"data-testid":`${r}-cond-op`,value:s.op,onChange:l=>t({...s,op:l.target.value}),style:w_,children:d_.map(l=>i.jsx("option",{value:l.value,children:n(l.labelKey)},l.value))}),a&&i.jsx(oc,{testid:`${r}-cond-right`,value:s.right,onCommit:l=>t({...s,right:l}),placeholder:n("layout_editor.value_tree.cond.value_placeholder"),candidates:o,t:n,quoteDataPick:!0})]})}function oc({testid:e,value:t,onCommit:n,placeholder:o,candidates:r,t:s,quoteDataPick:a=!1}){const[l,d]=u.useState(null),c=l!==null?l:t,p=()=>{l!==null&&(l!==t&&n(l),d(null))},f=(y,g)=>{const m=En(y,g,"scalar"),h=/^\{\{\s*([\s\S]*?)\s*\}\}$/.exec(m.trim());let v=h?h[1]:m;v=v.replace(/\s*\?\?\s*''$/,"").trim(),n(a?`'${v}'`:v),d(null)};return i.jsxs("span",{style:__,children:[i.jsx("input",{type:"text","data-testid":e,value:c,onChange:y=>d(y.target.value),onBlur:p,onKeyDown:y=>{y.key==="Enter"&&(y.preventDefault(),p())},placeholder:o,style:x_}),r&&r.length>0&&s&&i.jsx($t,{candidates:r,t:s,onSelect:y=>f(y.sourceId,y.path),testIdSuffix:`${e}-data`,floating:!0})]})}function Ir({label:e,testid:t,children:n}){return i.jsxs("div",{"data-testid":t,style:h_,children:[i.jsx("span",{style:y_,children:e}),i.jsx("div",{style:b_,children:n})]})}const rc={display:"flex",flexDirection:"column",gap:6,width:"100%",minWidth:0},hs=e=>({display:"flex",flexDirection:"column",gap:6,padding:"8px",border:"1px solid #e2e8f0",borderLeft:`3px solid ${e%2===0?"#6366f1":"#0ea5e9"}`,borderRadius:6,background:e%2===0?"#f8fafc":"#fefefe",minWidth:0}),h_={display:"flex",flexDirection:"column",gap:3,minWidth:0},y_={fontSize:11,fontWeight:600,color:"#475569"},b_={paddingLeft:8,minWidth:0},m_={display:"flex",flexWrap:"wrap",gap:4,alignItems:"center",minWidth:0},v_={display:"flex",flexWrap:"wrap",gap:4,alignItems:"center",minWidth:0},ic={fontSize:11,fontWeight:600,color:"#0f172a",whiteSpace:"nowrap"},__={display:"inline-flex",alignItems:"flex-start",gap:2,flex:"1 1 80px",minWidth:60},x_={flex:"1 1 80px",minWidth:60,padding:"4px 6px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6},w_={padding:"4px 6px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff"},k_={fontSize:11,color:"#94a3b8"},sc={display:"flex",flexDirection:"column",gap:4,padding:"6px 8px",border:"1px solid #e2e8f0",borderLeft:"3px solid #6366f1",borderRadius:6,background:"#fff",minWidth:0},S_={display:"flex",alignItems:"center",gap:6,minWidth:0},C_={fontSize:10,fontWeight:600,color:"#6366f1",flex:1,minWidth:0},$_={display:"flex",flexWrap:"wrap",gap:6,marginTop:2},E_={color:"#94a3b8",cursor:"grab",fontSize:14,userSelect:"none"},I_={border:"none",background:"transparent",color:"#94a3b8",cursor:"pointer",fontSize:12,padding:2,flexShrink:0},Ar={padding:"4px 10px",fontSize:11,border:"1px dashed #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer",whiteSpace:"nowrap"},A_={display:"flex",flexDirection:"column",gap:4,marginTop:2},T_={alignSelf:"flex-start",padding:"3px 8px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#475569",cursor:"pointer",fontFamily:"monospace"},D_={fontSize:11,background:"#0f172a",color:"#e2e8f0",padding:"6px 8px",borderRadius:4,wordBreak:"break-all",fontFamily:"monospace"},N_={display:"flex",flexDirection:"column",gap:2,minWidth:0},ac={fontSize:11,background:"#f1f5f9",padding:"3px 6px",borderRadius:4,color:"#0f172a",wordBreak:"break-all",fontFamily:"monospace"},lc={fontSize:10,color:"#64748b"};function vt({value:e,onChange:t,t:n,placeholder:o,testidPrefix:r="g7le-i18n-text-field",locales:s,candidates:a,enableExpressionTree:l=!1,expressionTreeCollapsible:d=!1,expressionSourceToggle:c=!0}){const{locale:p,templateIdentifier:f,commitText:y,classify:g,translate:m}=dv(),[h,v]=u.useState(null),[b,x]=u.useState(!1),[k,w]=u.useState(!1),[_,S]=u.useState(!1),[E,C]=u.useState(!1),[N,$]=u.useState(!1),[,A]=u.useState(0);u.useEffect(()=>{if(typeof window>"u")return;const T=H=>{const L=H.detail;L?.templateIdentifier&&L.templateIdentifier!==f||A(I=>I+1)};return window.addEventListener(kt,T),()=>window.removeEventListener(kt,T)},[f]);const z=g(e);u.useEffect(()=>{v(null),$(!1)},[p,e]);const D=h!==null?h:z.displayValue,O=u.useCallback(async()=>{if(h!==null){if(h===z.displayValue){v(null);return}x(!0);try{const T=await y(e,h);T.kind==="created"&&T.token?(t(T.token),Et({op:"prop_i18n_create_key",sourceState:"plain_text",translationKey:T.customKey,toLocale:p,valueLength:h.length,timestamp:Date.now()})):T.kind==="updated"&&Et({op:"prop_i18n_update_value",sourceState:"custom_key",translationKey:T.customKey,toLocale:p,valueLength:h.length,timestamp:Date.now()})}finally{x(!1),v(null)}}},[h,z.displayValue,y,e,t,p]),M=u.useCallback(T=>{typeof e=="string"&&(t(mo(e,T)),z.customKey&&kr(f,z.customKey,T,p))},[e,t,z.customKey,f,p]);if(z.binding){if(l&&typeof e=="string"){const T=Gd(e),H=un(e),L=!T&&Kt(e)!==null,I=(T||H.decomposed)&&!L;if(I&&d&&!_)return i.jsx(L_,{value:e,t:n,translate:m,onEdit:()=>S(!0),testidPrefix:`${r}-preview`});const F=I&&(d||T)?i.jsx(gs,{value:e,onChange:G=>t(G),t:n,candidates:a,testidPrefix:`${r}-seg`}):H.decomposed?i.jsx(nc,{node:H.node,onChange:G=>t(Zn(G,!1)),t:n,candidates:a,testidPrefix:`${r}-tree`,showSourceToggle:c}):null;if(F){if(!d)return F;const G=Sr(e),V=G?us(G,Y=>{const Z=m(Y);return Z&&Z!==Y&&!Z.startsWith("$t:")?Z:""},Y=>Vt(Y)):[];return i.jsxs("div",{"data-testid":`${r}-builder`,style:{display:"flex",flexDirection:"column",gap:6,width:"100%",minWidth:0},children:[i.jsxs("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"center",gap:6},children:[i.jsxs("button",{type:"button","data-testid":`${r}-to-plain`,onClick:()=>C(!0),style:F_,title:n("layout_editor.value_tree.to_plain"),"aria-label":n("layout_editor.value_tree.to_plain"),children:["↩ ",n("layout_editor.value_tree.to_plain")]}),i.jsxs("button",{type:"button","data-testid":`${r}-builder-collapse`,onClick:()=>S(!1),style:uc,title:n("layout_editor.value_tree.collapse"),"aria-label":n("layout_editor.value_tree.collapse"),children:["▴ ",n("layout_editor.value_tree.collapse")]})]}),E&&i.jsxs("div",{"data-testid":`${r}-to-plain-confirm`,style:K_,children:[i.jsx("p",{style:V_,children:n("layout_editor.value_tree.to_plain_lead")}),i.jsx("div",{style:U_,children:V.length===0?i.jsx("span",{style:{color:"#94a3b8"},children:n("layout_editor.prop_i18n.placeholder")}):V.map((Y,Z)=>Y.kind==="chip"?i.jsxs("span",{style:dc,children:["🔗 ",Y.text]},Z):i.jsx("span",{style:Y.kind==="ellipsis"?cc:void 0,children:Y.text},Z))}),i.jsxs("p",{style:q_,children:["⚠ ",n("layout_editor.value_tree.to_plain_warn")]}),i.jsxs("div",{style:{display:"flex",justifyContent:"flex-end",gap:6},children:[i.jsx("button",{type:"button","data-testid":`${r}-to-plain-confirm-cancel`,onClick:()=>C(!1),style:uc,children:n("layout_editor.value_tree.to_plain_cancel")}),i.jsx("button",{type:"button","data-testid":`${r}-to-plain-confirm-ok`,onClick:()=>{C(!1),S(!1),t(Sr(e)||void 0)},style:G_,children:n("layout_editor.value_tree.to_plain_ok")})]})]}),F]})}}return l&&typeof e=="string"&&a&&a.length>0&&Kt(e)!==null?i.jsx(P_,{value:e,onChange:t,t:n,candidates:a,testidPrefix:`${r}-data`}):i.jsxs("div",{"data-testid":`${r}-binding`,style:R_,children:[i.jsx("code",{style:ex,children:typeof e=="string"?e:""}),i.jsxs("span",{style:tx,children:["🔗 ",n("layout_editor.prop_i18n.bound_code_only")]})]})}if(!N&&typeof e=="string"&&Ul(e)){const T=ql(e);return i.jsxs("div",{"data-testid":`${r}-settings-ref`,style:J_,children:[i.jsx("div",{"data-testid":`${r}-settings-ref-chips`,style:Z_,children:T.map((H,L)=>H.kind==="chip"?i.jsxs("span",{style:Q_,children:["🔗 ",H.label]},L):i.jsx("span",{children:H.label},L))}),i.jsxs("button",{type:"button","data-testid":`${r}-settings-ref-edit`,onClick:()=>$(!0),style:pc,title:n("layout_editor.value_tree.edit"),"aria-label":n("layout_editor.value_tree.edit"),children:["✎ ",n("layout_editor.value_tree.edit")]})]})}const B=i.jsxs("button",{type:"button","data-testid":`${r}-toggle`,title:`${n("layout_editor.prop_i18n.edit_languages")} (${Gn(p,n)})`,"aria-label":`${n("layout_editor.prop_i18n.edit_languages")} (${Gn(p,n)})`,"aria-expanded":k,onClick:()=>w(T=>!T),style:pc,children:["🌐 ",z.customKey?k?"⌃":"⌄":"+"]});return i.jsxs("div",{"data-testid":r,style:{display:"flex",flexDirection:"column",gap:4,width:"100%"},children:[i.jsx("div",{style:Y_,children:z.paramKey&&z.customKey?i.jsx(z_,{customKey:z.customKey,value:typeof e=="string"?e:"",onChange:t,t:n,candidates:a,testidPrefix:r,showExprButton:l&&d,onToExpression:()=>{S(!0),t(Co(typeof e=="string"?e:""))},langToggle:B}):i.jsxs(i.Fragment,{children:[i.jsx("input",{type:"text","data-testid":`${r}-preview`,value:D,placeholder:o??n("layout_editor.prop_i18n.placeholder"),onChange:T=>v(T.target.value),onBlur:()=>{O(),N&&$(!1)},disabled:b,style:X_}),a&&a.length>0&&i.jsx(M_,{value:typeof e=="string"?e:"",customKey:z.customKey,onChange:t,t:n,candidates:a,testidPrefix:r}),l&&d&&i.jsx("button",{type:"button","data-testid":`${r}-to-expr`,onClick:()=>{S(!0),t(Co(typeof e=="string"?e:""))},title:n("layout_editor.value_tree.to_expression"),"aria-label":n("layout_editor.value_tree.to_expression"),style:Io,children:"ƒx"}),B]})}),k&&(z.customKey?i.jsx(Ud,{customKey:z.customKey,templateIdentifier:f,t:n,paramLabels:ds(e),locales:s,onRemoveParam:M},z.customKey):i.jsx("div",{"data-testid":`${r}-expand-hint`,style:nx,children:n("layout_editor.prop_i18n.create_first")}))]})}function z_({customKey:e,value:t,onChange:n,t:o,candidates:r,testidPrefix:s,showExprButton:a=!1,onToExpression:l,langToggle:d}){const{state:c}=Je(),p=c.templateIdentifier,f=c.locale,y=Ft(),[g,m]=u.useState(null),h=u.useRef(""),v=u.useRef(null),b=u.useRef(null),x=u.useRef(null),[k,w]=u.useState(null),[_,S]=u.useState(!1),E=ds(t),C=u.useCallback(async()=>{const D=It(e,f);if(D!==void 0){h.current=D,m(D);return}const M=(await cn(p,e))?.values?.[f]??"";h.current=M,m(M)},[e,p,f]);u.useEffect(()=>{let D=!1;return(async()=>{const O=It(e,f);if(O!==void 0){D||(h.current=O,m(O));return}const M=await cn(p,e);if(D)return;const B=M?.values?.[f]??"";h.current=B,m(B)})(),()=>{D=!0}},[e,p,f]),u.useEffect(()=>{if(typeof window>"u")return;const D=O=>{const M=O.detail;if(M?.templateIdentifier&&M.templateIdentifier!==p)return;const B=It(e,f);B!==void 0&&B!==g&&(h.current=B,m(B))};return window.addEventListener(kt,D),()=>window.removeEventListener(kt,D)},[e,p,f,g]);const N=u.useCallback(D=>{m(D),ko(p,e,f,D).then(()=>{y?.markDirty?.()})},[p,e,f,y]),$=u.useCallback(D=>{n(mo(t,D)),kr(p,e,D,f).then(()=>{C(),y?.markDirty?.()})},[t,n,p,e,f,C,y]),A=u.useCallback(async D=>{if(!(k===null||_)){S(!0);try{g!==null&&g!==h.current&&(await ko(p,e,f,g),h.current=g);const O=await wr(p,f,t,k,D.sourceId,D.path,"scalar");O.kind==="ok"&&(n(O.text),await C(),y?.markDirty?.())}finally{S(!1),w(null)}}},[k,_,g,t,p,e,f,n,C,y]),z=!!r&&r.length>0;return i.jsxs("div",{"data-testid":`${s}-chipfield-wrap`,style:{flex:1,minWidth:0,display:"flex",alignItems:"center",gap:4},children:[i.jsx("div",{ref:v,"data-testid":`${s}-chipfield`,style:{flex:1,minWidth:0},children:g===null?i.jsx("div",{"data-testid":`${s}-chipfield-loading`,style:{fontSize:11,color:"#94a3b8",padding:"4px 0"},children:o("layout_editor.translation.loading")}):i.jsx(mr,{value:g,onChange:N,t:o,paramLabels:E,testIdSuffix:`${s}-field`,onRequestInsert:z?D=>w(D):void 0,onRemoveChip:$,hideInsertButton:!0,caretRef:x})}),z&&i.jsx("button",{ref:b,type:"button","data-testid":`${s}-plus-data-btn`,onClick:()=>w(x.current?x.current():g?.length??0),title:o("layout_editor.inline_binding.insert_data"),"aria-label":o("layout_editor.inline_binding.insert_data"),style:Io,children:"+"}),a&&l&&i.jsx("button",{type:"button","data-testid":`${s}-to-expr`,onClick:l,title:o("layout_editor.value_tree.to_expression"),"aria-label":o("layout_editor.value_tree.to_expression"),style:Io,children:"ƒx"}),d,z&&i.jsx(Xn,{anchorRef:b,open:k!==null,onClose:()=>w(null),children:i.jsx("div",{"data-testid":`${s}-chipfield-picker`,children:i.jsx($t,{candidates:r,t:o,onSelect:D=>{A(D)},testIdSuffix:`${s}-field-insert`,defaultOpen:!0,floating:!1})})})]})}function M_({value:e,customKey:t,onChange:n,t:o,candidates:r,testidPrefix:s}){const{state:a}=Je(),l=a.templateIdentifier,d=a.selectedRoute?.layoutName??null,c=a.locale,p=Ft(),[f,y]=u.useState(!1),[g,m]=u.useState(!1),h=u.useRef(null),v=u.useCallback(k=>{const w=o(k);return w&&w!==k&&!w.startsWith("$t:")?w:""},[o]),b=u.useCallback(k=>k.replace(/\$t:[a-zA-Z0-9._-]+/g,w=>v(w.slice(3))).replace(/\s+/g," ").trim(),[v]),x=u.useCallback(async k=>{if(!g){m(!0);try{if(t){const w=await wr(l,c,e,Number.MAX_SAFE_INTEGER,k.sourceId,k.path,"scalar");w.kind==="ok"&&(n(w.text),p?.markDirty?.())}else{const w=await ls(l,d,c,e,e.length,k.sourceId,k.path,"scalar",b,_=>o(_));w.kind==="ok"&&(n(w.text),p?.markDirty?.())}}finally{m(!1),y(!1)}}},[g,t,l,d,c,e,n,p,o,b]);return i.jsxs("div",{"data-testid":`${s}-plus-data`,style:{display:"inline-block"},children:[i.jsx("button",{ref:h,type:"button","data-testid":`${s}-plus-data-btn`,disabled:g,onClick:()=>y(k=>!k),title:o("layout_editor.inline_binding.insert_data"),"aria-label":o("layout_editor.inline_binding.insert_data"),style:Io,children:"+"}),i.jsx(Xn,{anchorRef:h,open:f,onClose:()=>y(!1),children:i.jsx($t,{candidates:r,t:o,onSelect:k=>{x(k)},testIdSuffix:`${s}-plus-insert`,defaultOpen:!0,floating:!1})})]})}function L_({value:e,t,translate:n,onEdit:o,testidPrefix:r}){const s=us(e,a=>{const l=n(a);return l&&l!==a&&!l.startsWith("$t:")?l:""},a=>Vt(a));return i.jsxs("div",{"data-testid":r,style:W_,children:[i.jsx("div",{"data-testid":`${r}-text`,style:j_,children:s.length===0?i.jsx("span",{style:{color:"#94a3b8"},children:t("layout_editor.prop_i18n.placeholder")}):s.map((a,l)=>a.kind==="chip"?i.jsxs("span",{style:dc,children:["🔗 ",a.text]},l):i.jsx("span",{style:a.kind==="ellipsis"?cc:void 0,children:a.text},l))}),i.jsxs("button",{type:"button","data-testid":`${r}-edit`,onClick:o,style:H_,title:t("layout_editor.value_tree.edit"),"aria-label":t("layout_editor.value_tree.edit"),children:["✎ ",t("layout_editor.value_tree.edit")]})]})}function P_({value:e,onChange:t,t:n,candidates:o,testidPrefix:r}){const[s,a]=u.useState(!1),l=u.useRef(null),d=Vt(e);return i.jsxs("div",{"data-testid":r,style:{display:"flex",flexDirection:"column",gap:4,width:"100%",minWidth:0},children:[i.jsxs("div",{style:O_,children:[i.jsxs("span",{"data-testid":`${r}-chip`,style:B_,children:["🔗 ",d]}),i.jsx("button",{ref:l,type:"button","data-testid":`${r}-change`,onClick:()=>a(c=>!c),style:Io,title:n("layout_editor.value_tree.change_data"),"aria-label":n("layout_editor.value_tree.change_data"),children:n("layout_editor.value_tree.change_data")})]}),i.jsx(Xn,{anchorRef:l,open:s,onClose:()=>a(!1),children:i.jsx($t,{candidates:o,t:n,onSelect:c=>{const p=c.path?c.path.split(".").filter(Boolean):[],f=[c.sourceId,...p].join("?.");t(`{{${f}}}`),a(!1)},testIdSuffix:`${r}-pick`,defaultOpen:!0,floating:!1})})]})}const O_={display:"flex",alignItems:"center",gap:6,width:"100%",minWidth:0,flexWrap:"wrap"},B_={fontSize:11,background:"#eef2ff",color:"#4338ca",padding:"3px 8px",borderRadius:12,wordBreak:"break-all"},W_={display:"flex",alignItems:"center",gap:8,width:"100%",minWidth:0},j_={flex:1,minWidth:0,display:"flex",flexWrap:"wrap",alignItems:"center",gap:4,padding:"5px 8px",fontSize:12,color:"#334155",border:"1px solid #e2e8f0",borderRadius:6,background:"#f8fafc",overflow:"hidden"},dc={fontSize:11,background:"#eef2ff",color:"#4338ca",padding:"1px 6px",borderRadius:10,whiteSpace:"nowrap"},cc={color:"#94a3b8"},H_={padding:"4px 10px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#2563eb",cursor:"pointer",whiteSpace:"nowrap",flexShrink:0},uc={padding:"3px 10px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#475569",cursor:"pointer",whiteSpace:"nowrap"},F_={padding:"3px 10px",fontSize:11,border:"1px solid #fca5a5",borderRadius:6,background:"#fef2f2",color:"#b91c1c",cursor:"pointer",whiteSpace:"nowrap"},K_={display:"flex",flexDirection:"column",gap:6,padding:10,border:"1px solid #fca5a5",borderRadius:8,background:"#fffbeb"},V_={margin:0,fontSize:12,color:"#334155"},U_={padding:"5px 8px",fontSize:12,color:"#0f172a",background:"#fff",border:"1px solid #e2e8f0",borderRadius:6,wordBreak:"break-all"},q_={margin:0,fontSize:11,color:"#b45309"},G_={padding:"3px 12px",fontSize:11,border:"1px solid #dc2626",borderRadius:6,background:"#dc2626",color:"#fff",cursor:"pointer",whiteSpace:"nowrap"},Y_={display:"flex",gap:4,alignItems:"center",width:"100%",minWidth:0},X_={flex:1,padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,minWidth:0,width:"100%"},pc={padding:"4px 8px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#475569",cursor:"pointer",whiteSpace:"nowrap"},Io={padding:"4px 8px",fontSize:13,lineHeight:1,border:"1px solid #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#475569",cursor:"pointer",whiteSpace:"nowrap"},J_={display:"flex",alignItems:"center",gap:6,width:"100%",minWidth:0,flexWrap:"wrap"},Z_={flex:1,minWidth:0,display:"flex",flexWrap:"wrap",alignItems:"center",gap:4,fontSize:12,color:"#334155"},Q_={fontSize:11,background:"#eef2ff",color:"#4338ca",padding:"2px 8px",borderRadius:12,whiteSpace:"nowrap"},R_={display:"flex",flexDirection:"column",gap:2,width:"100%"},ex={fontSize:11,background:"#f1f5f9",padding:"3px 6px",borderRadius:4,color:"#0f172a",wordBreak:"break-all"},tx={fontSize:10,color:"#64748b"},nx={fontSize:11,color:"#94a3b8",padding:"6px 8px",border:"1px dashed #cbd5e1",borderRadius:6,background:"#f8fafc"};function ys({value:e,onChange:t,candidates:n,t:o}){const r=Array.isArray(e)?e:[],s=n??[],[a,l]=u.useState(!1),d=g=>s.find(m=>m.value===g)?.label??g,c=g=>d(g)!==g,p=g=>{const m=r.filter(h=>h!==g);t(m.length===0?void 0:m)},f=g=>{r.includes(g)||(t([...r,g]),l(!1))},y=s.filter(g=>!r.includes(g.value));return i.jsxs("div",{className:"g7le-widget g7le-widget--tag-input","data-testid":"g7le-widget-tag-input",style:ox,children:[i.jsxs("div",{style:rx,children:[r.map(g=>i.jsxs("span",{"data-testid":`g7le-tag-chip-${g}`,style:ix,children:[i.jsx("span",{style:sx,children:d(g)}),c(g)&&i.jsx("code",{"data-testid":`g7le-tag-chip-id-${g}`,style:ax,children:g}),i.jsx("button",{type:"button","aria-label":`remove ${g}`,"data-testid":`g7le-tag-remove-${g}`,onClick:()=>p(g),style:lx,children:"✕"})]},g)),i.jsx("button",{type:"button","data-testid":"g7le-tag-add",onClick:()=>l(g=>!g),disabled:y.length===0,style:dx,children:o("layout_editor.control.tag_add")})]}),a&&y.length>0&&i.jsx("div",{"data-testid":"g7le-tag-candidates",style:cx,children:y.map(g=>{const m=g.value!==g.label;return i.jsxs("button",{type:"button","data-testid":`g7le-tag-candidate-${g.value}`,onClick:()=>f(g.value),style:ux,children:[i.jsx("span",{style:px,children:g.label}),m&&i.jsx("code",{"data-testid":`g7le-tag-candidate-id-${g.value}`,style:fx,children:g.value})]},g.value)})})]})}const ox={display:"flex",flexDirection:"column",gap:6},rx={display:"flex",flexWrap:"wrap",gap:4,alignItems:"center"},ix={display:"inline-flex",alignItems:"center",gap:4,padding:"2px 6px",fontSize:12,background:"#e0e7ff",color:"#1d4ed8",borderRadius:12},sx={fontSize:12},ax={fontSize:10,color:"#4f46e5",background:"#c7d2fe",borderRadius:4,padding:"0 4px",fontFamily:"monospace"},lx={border:"none",background:"transparent",color:"#1d4ed8",cursor:"pointer",fontSize:10,padding:0,lineHeight:1},dx={padding:"2px 8px",fontSize:12,border:"1px dashed #94a3b8",borderRadius:12,background:"#fff",cursor:"pointer",color:"#475569"},cx={display:"flex",flexDirection:"column",maxHeight:180,overflowY:"auto",border:"1px solid #cbd5e1",borderRadius:6,background:"#fff"},ux={display:"flex",flexDirection:"column",gap:1,textAlign:"left",padding:"6px 10px",fontSize:12,border:"none",borderBottom:"1px solid #f1f5f9",background:"transparent",cursor:"pointer",color:"#0f172a"},px={fontSize:12,color:"#0f172a"},fx={fontSize:10,color:"#64748b",fontFamily:"monospace"},fc=new Map;function Xe(e,t){fc.set(e,t)}function Tn(e){return e?fc.get(e)??null:null}function Tr(e,t){const o=e("meta",{})?.[t];return typeof o=="string"?o:""}function gx({getValue:e,patch:t,t:n,fieldT:o,spec:r,permissionCandidates:s,bindingCandidates:a}){const l=o??n,d=Tr(e,"title"),c=Tr(e,"description"),p=Tr(e,"icon"),f=Tr(e,"editor_label"),y=e("permissions",[]),g=u.useCallback((b,x)=>{const k={...e("meta",{})??{}};x===void 0||x===""?delete k[b]:k[b]=x,t("meta",k)},[e,t]),m=u.useCallback(b=>{const x=Array.isArray(b)?b:[];x.length===0?t("permissions",void 0):t("permissions",x)},[t]),h=Tn("icon-picker"),v=Ct(r,"icon-picker")??Ct(r,"icon")??(r?.controls?Object.values(r.controls).find(b=>b&&typeof b=="object"&&b.widget==="icon-picker")??null:null);return i.jsxs("div",{className:"g7le-meta-form","data-testid":"g7le-meta-form",style:yx,children:[i.jsx(Ao,{label:n("layout_editor.page_settings.meta.title"),children:i.jsx(vt,{value:d,onChange:b=>g("title",b),t:l,testidPrefix:"g7le-meta-title",candidates:a,enableExpressionTree:!0,expressionTreeCollapsible:!0})}),i.jsx(Ao,{label:n("layout_editor.page_settings.meta.description"),children:i.jsx(vt,{value:c,onChange:b=>g("description",b),t:l,testidPrefix:"g7le-meta-description",candidates:a,enableExpressionTree:!0,expressionTreeCollapsible:!0})}),i.jsx(Ao,{label:n("layout_editor.page_settings.meta.icon"),children:h?i.jsx(hx,{icon:p,t:n,onChange:b=>g("icon",typeof b=="string"?b:void 0),IconWidget:h,iconControl:v??{widget:"icon-picker"}}):i.jsx("input",{type:"text","data-testid":"g7le-meta-icon",value:p,placeholder:n("layout_editor.page_settings.meta.icon_placeholder"),onChange:b=>g("icon",b.target.value),style:_x})}),i.jsx(Ao,{label:n("layout_editor.page_settings.meta.editor_label"),hint:n("layout_editor.page_settings.meta.editor_label_hint"),children:i.jsx(vt,{value:f,onChange:b=>g("editor_label",b),t:l,testidPrefix:"g7le-meta-editor-label",candidates:a,enableExpressionTree:!0,expressionTreeCollapsible:!0})}),i.jsx(Ao,{label:n("layout_editor.page_settings.meta.permissions"),hint:n("layout_editor.page_settings.meta.permissions_hint"),children:i.jsx("div",{"data-testid":"g7le-meta-permissions",children:i.jsx(ys,{control:{widget:"tag-input"},value:y,onChange:m,t:n,candidates:s})})})]})}function Ao({label:e,hint:t,children:n}){return i.jsxs("div",{className:"g7le-meta-field",style:bx,children:[i.jsx("label",{style:mx,children:e}),n,t?i.jsxs("p",{style:vx,children:["ⓘ ",t]}):null]})}function hx({icon:e,onChange:t,t:n,IconWidget:o,iconControl:r}){const[s,a]=u.useState(!1),l=e?/\bfa-/.test(e)?i.jsx("i",{className:e,"aria-hidden":"true"}):e:"📄";return i.jsxs("div",{"data-testid":"g7le-meta-icon",style:{display:"flex",flexDirection:"column",gap:6,minWidth:0},children:[i.jsxs("button",{type:"button","data-testid":"g7le-meta-icon-toggle","aria-expanded":s,onClick:()=>a(d=>!d),style:{display:"flex",alignItems:"center",gap:8,padding:"5px 8px",border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",cursor:"pointer",fontSize:12,color:"#475569",width:"fit-content"},children:[i.jsx("span",{style:{fontSize:16},"aria-hidden":"true",children:l}),i.jsx("span",{children:e||n("layout_editor.page_settings.meta.icon_placeholder")}),i.jsx("span",{"aria-hidden":"true",children:s?"▴":"▾"})]}),s?i.jsx(o,{control:r,value:e,onChange:d=>{t(d),a(!1)},t:n}):null]})}const yx={display:"flex",flexDirection:"column",gap:16,minWidth:0},bx={display:"flex",flexDirection:"column",gap:6,minWidth:0},mx={fontSize:13,fontWeight:600,color:"#0f172a"},vx={margin:0,fontSize:11,color:"#94a3b8"},_x={padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,minWidth:0,width:"100%"};function Dr({checked:e,onChange:t,label:n,testid:o,ariaLabel:r,disabled:s=!1}){return i.jsxs("label",{style:{...Cx,...s?{cursor:"not-allowed"}:{}},children:[i.jsx("button",{type:"button","data-testid":o,role:"switch","aria-checked":e,"aria-label":r,disabled:s,onClick:()=>t(!e),style:$x(e,s),children:i.jsx("span",{style:Ex(e)})}),n!=null?i.jsx("span",{style:Ix(s),children:n}):null]})}function Nr({disabled:e,children:t,testid:n,style:o}){return i.jsx("div",{"data-testid":n,"data-disabled":e?"true":"false","aria-disabled":e,style:{...o,...e?{opacity:.5,pointerEvents:"none",userSelect:"none"}:{}},children:t})}function xx({filled:e,sourceLabel:t,onRevert:n,revertLabel:o,hint:r,children:s,testid:a}){const l=e&&typeof n=="function",d=!e&&t!=null&&t!=="";return i.jsxs("div",{"data-testid":a,style:wx,children:[s,l?i.jsxs("button",{type:"button","data-testid":a?`${a}-revert`:void 0,onClick:n,title:r??o,"aria-label":o,style:Sx,children:["↩ ",o]}):d?i.jsx("span",{"data-testid":a?`${a}-source`:void 0,title:r,style:kx,children:t}):null]})}const wx={position:"relative",minWidth:0,width:"100%"},kx={position:"absolute",right:8,top:"50%",transform:"translateY(-50%)",fontSize:10,color:"#94a3b8",background:"#f1f5f9",borderRadius:4,padding:"1px 6px",pointerEvents:"none",maxWidth:"45%",overflow:"hidden",whiteSpace:"nowrap",textOverflow:"ellipsis"},Sx={position:"absolute",right:6,top:"50%",transform:"translateY(-50%)",fontSize:10,color:"#0369a1",background:"#e0f2fe",border:"1px solid #bae6fd",borderRadius:4,padding:"1px 6px",cursor:"pointer",whiteSpace:"nowrap"},Cx={display:"inline-flex",alignItems:"center",gap:8,cursor:"pointer"},$x=(e,t)=>({position:"relative",width:38,height:22,flexShrink:0,borderRadius:999,border:"none",padding:0,background:t?"#e2e8f0":e?"#2563eb":"#cbd5e1",cursor:t?"not-allowed":"pointer",transition:"background 0.15s ease"}),Ex=e=>({position:"absolute",top:2,left:e?18:2,width:18,height:18,borderRadius:"50%",background:"#fff",boxShadow:"0 1px 2px rgba(0,0,0,0.2)",transition:"left 0.15s ease"}),Ix=e=>({fontSize:13,fontWeight:600,color:e?"#94a3b8":"#0f172a"}),Ax=/\$(?:core|module|plugin)_settings:[a-zA-Z0-9._:-]+/;function Tx(e){const t=[];let n=!1;const o=s=>{t.push({kind:"text",text:s}),n=!0},r=(s,a,l)=>{n||o(""),t.push({kind:"chip",raw:s,text:a,removable:l}),n=!1};for(const s of Dx(e))s.kind==="text"?o(s.raw):r(s.raw,s.label,s.removable);return n||o(""),t}function Dx(e){const t=[];if(typeof e!="string"||e.length===0)return t;const n=e.length;let o=0,r=0;const s=a=>{a>r&&t.push({kind:"text",raw:e.slice(r,a),label:"",removable:!1})};for(;o=n){o++;continue}s(o);const l=e.slice(o,a+2);t.push({kind:"chip",raw:l,label:Vt(l),removable:!0}),o=a+2,r=o;continue}if(e[o]==="$"){const a=Ax.exec(e.slice(o));if(a&&a.index===0){s(o);const l=a[0],d=l.split(":").pop()||l;t.push({kind:"chip",raw:l,label:d,removable:!1}),o+=l.length,r=o;continue}}o++}return s(n),t}function Nx(e,t,n,o){const r=[];let s=!1;return e.forEach((a,l)=>{if(a.kind==="chip"){if(!s&&a.raw===t){s=!0;return}r.push(a.raw??"")}else{const d=a.text??"";if(l===n){const c=Math.max(0,Math.min(o,d.length));r.push(d.slice(0,c),t,d.slice(c))}else r.push(d)}}),r.join("")}function zx(e,t,n){const o=Math.max(0,Math.min(t,e.length));return e.slice(0,o)+n+e.slice(o)}function Mx(e,t){const n=e.indexOf(t);return n<0?e:e.slice(0,n)+e.slice(n+t.length)}function bs({value:e,onChange:t,t:n,candidates:o,onDone:r,testidPrefix:s="g7le-binding-chip-text",disabled:a=!1}){const l=u.useMemo(()=>Tx(e),[e]),d=u.useRef(null),c=!!o&&o.length>0,[p,f]=u.useState(null),[y,g]=u.useState(null),[m,h]=u.useState(null),[v,b]=u.useState(!1),x=u.useRef(0),k=u.useRef(null),w=u.useMemo(()=>l.map(L=>L.kind==="chip"?`c:${L.raw}`:"t").join("|"),[l]),_=u.useRef(null),S=u.useRef([]),E=u.useRef(!1);u.useLayoutEffect(()=>{const L=E.current;E.current=!1,!(!L&&_.current===w)&&(_.current=w,l.forEach((I,P)=>{if(I.kind!=="text")return;const F=S.current[P];F&&F.textContent!==(I.text??"")&&(F.textContent=I.text??"")}))},[w,l,e]);const C=u.useCallback(()=>{const L=d.current;if(!L)return e;const I=[];for(const P of Array.from(L.querySelectorAll("[data-seg]")))P.dataset.seg==="chip"?I.push(P.dataset.raw??""):I.push(P.textContent??"");return I.join("")},[e]),N=u.useCallback(()=>{a||t(C())},[a,t,C]),$=u.useRef(null),A=u.useRef(!1),z=(L,I)=>{if(!a){L.preventDefault(),L.stopPropagation(),$.current={raw:I,pointerId:L.pointerId},A.current=!1,f(I);try{L.currentTarget.setPointerCapture(L.pointerId)}catch{}}},D=L=>{const I=$.current;if(!I||L.pointerId!==I.pointerId)return;A.current=!0;const P=B(L.clientX,L.clientY);P&&(g({slotIdx:P.slotIdx,offset:P.offset}),h(Px(d.current,P.slotIdx,P.offset)))},O=(L,I)=>{if(L.stopPropagation(),A.current){A.current=!1;return}E.current=!0,t(Mx(e,I))},M=L=>{const I=$.current;if(!I||L.pointerId!==I.pointerId)return;try{L.currentTarget.releasePointerCapture(L.pointerId)}catch{}$.current=null,A.current&&typeof setTimeout=="function"&&setTimeout(()=>{A.current=!1},0);const P=B(L.clientX,L.clientY);if(f(null),g(null),h(null),!P)return;const F=d.current;if(!F)return;const V=Array.from(F.querySelectorAll("[data-seg]")).map(Z=>Z.dataset.seg==="chip"?{kind:"chip",raw:Z.dataset.raw??""}:{kind:"text",text:Z.textContent??""}),Y=Nx(V,I.raw,P.slotIdx,P.offset);Y!==e&&(E.current=!0,t(Y))},B=(L,I)=>{const P=d.current;if(!P)return null;const F=Array.from(P.querySelectorAll("[data-seg]")),G=P.ownerDocument,V=G.elementFromPoint(L,I),Y=V?.closest('[data-seg="text"]');if(Y)return{slotIdx:F.indexOf(Y),offset:Lx(G,Y,L,I)};const Z=V?.closest('[data-seg="chip"]');if(Z){const re=F.indexOf(Z),U=Z.getBoundingClientRect();if(L=0){const ee=F[re-1];return{slotIdx:re-1,offset:(ee.textContent??"").length}}if(re+1({s:re,i:U})).filter(re=>re.s.dataset.seg==="text").pop();return se?{slotIdx:se.i,offset:(se.s.textContent??"").length}:null},T=()=>{a||!c||(x.current=Ox(d.current,e.length),b(L=>!L))},H=L=>{const I=L.path?L.path.split(".").filter(Boolean):[],P=[L.sourceId,...I].join("?.");E.current=!0,t(zx(e,x.current,`{{${P}}}`)),b(!1)};return i.jsxs("div",{"data-testid":s,style:{display:"flex",flexDirection:"column",gap:4},children:[i.jsxs("div",{ref:d,"data-testid":`${s}-box`,className:"g7le-binding-chip-input",style:Bx,children:[l.map((L,I)=>L.kind==="chip"?i.jsxs("span",{"data-seg":"chip","data-raw":L.raw,"data-testid":`${s}-chip-${I}`,contentEditable:!1,onPointerDown:P=>z(P,L.raw??""),onPointerMove:D,onPointerUp:M,title:L.removable?n("layout_editor.value_tree.change_data"):n("layout_editor.prop_i18n.bound_code_only"),style:{...Fx,...p===L.raw?Kx:null,touchAction:"none"},children:[i.jsx("span",{"aria-hidden":"true",style:Vx,children:"⠿"}),i.jsx("span",{"aria-hidden":"true",style:{marginRight:3},children:"🔗"}),i.jsx("span",{children:L.text}),L.removable&&!a&&i.jsx("button",{type:"button","data-testid":`${s}-chip-remove-${I}`,"aria-label":n("layout_editor.inline_binding.clear"),title:n("layout_editor.inline_binding.clear"),onPointerDown:P=>{P.stopPropagation(),P.preventDefault()},onClick:P=>O(P,L.raw??""),style:Ux,children:"✕"})]},`chip-${L.raw}-${I}`):i.jsx("span",{ref:P=>{S.current[I]=P},"data-seg":"text",contentEditable:!a,suppressContentEditableWarning:!0,onInput:N,"data-testid":`${s}-text-${I}`,style:{...Wx,...p?jx:null,...y?.slotIdx===I?Hx:null}},`text-slot-${I}`)),m&&i.jsx("span",{"aria-hidden":"true","data-testid":`${s}-caret`,style:{position:"absolute",left:m.left,top:m.top,width:2,height:m.height,background:"#2563eb",borderRadius:1,pointerEvents:"none",boxShadow:"0 0 0 1px rgba(37,99,235,0.3)"}})]}),i.jsxs("div",{style:{display:"flex",alignItems:"center",gap:6,flexWrap:"wrap"},children:[r&&i.jsxs("button",{type:"button","data-testid":`${s}-done`,onClick:r,style:gc,title:n("layout_editor.value_tree.done"),"aria-label":n("layout_editor.value_tree.done"),children:["✓ ",n("layout_editor.value_tree.done")]}),c&&i.jsxs("div",{style:{position:"relative"},children:[i.jsxs("button",{ref:k,type:"button","data-testid":`${s}-insert`,onClick:T,disabled:a,style:gc,title:n("layout_editor.inline_binding.insert_data"),"aria-label":n("layout_editor.inline_binding.insert_data"),children:["+ ",n("layout_editor.inline_binding.insert_data")]}),i.jsx(Xn,{anchorRef:k,open:v,onClose:()=>b(!1),children:i.jsx($t,{candidates:o,t:n,onSelect:H,testIdSuffix:`${s}-insert`,defaultOpen:!0,floating:!1})})]}),i.jsx("span",{style:qx,children:n("layout_editor.translation.placeholder_locked")})]})]})}function Lx(e,t,n,o){const r=(t.textContent??"").length;try{const a=e;let l=null,d=0;if(typeof a.caretRangeFromPoint=="function"){const c=a.caretRangeFromPoint(n,o);c&&(l=c.startContainer,d=c.startOffset)}else if(typeof a.caretPositionFromPoint=="function"){const c=a.caretPositionFromPoint(n,o);c&&(l=c.offsetNode,d=c.offset)}if(l&&t.contains(l))return Math.max(0,Math.min(d,r))}catch{}const s=t.getBoundingClientRect();return n>s.left+s.width/2?r:0}function Px(e,t,n){if(!e)return null;const r=Array.from(e.querySelectorAll("[data-seg]"))[t];if(!r)return null;const s=e.getBoundingClientRect();try{const d=r.firstChild,c=e.ownerDocument;if(d&&d.nodeType===Node.TEXT_NODE&&(d.textContent??"").length>0){const p=c.createRange(),f=Math.max(0,Math.min(n,(d.textContent??"").length));p.setStart(d,f),p.setEnd(d,f);const y=p.getClientRects(),g=y.length>0?y[0]:p.getBoundingClientRect();if(g&&(g.height>0||g.width>=0))return{left:g.left-s.left,top:g.top-s.top,height:g.height||r.getBoundingClientRect().height}}}catch{}const a=r.getBoundingClientRect();return{left:(n>0?a.right:a.left)-s.left,top:a.top-s.top,height:a.height||16}}function Ox(e,t){if(!e)return t;try{const n=e.ownerDocument.defaultView?.getSelection();if(!n||n.rangeCount===0)return t;const o=n.getRangeAt(0);let r=o.startContainer,s=null;for(;r&&r!==e;){if(r instanceof HTMLElement&&r.dataset.seg==="text"){s=r;break}r=r.parentNode}if(!s||!e.contains(s))return t;const a=Array.from(e.querySelectorAll("[data-seg]"));let l=0;for(const d of a){if(d===s)break;l+=d.dataset.seg==="chip"?(d.dataset.raw??"").length:(d.textContent??"").length}return l+o.startOffset}catch{return t}}const Bx={position:"relative",display:"flex",flexWrap:"wrap",alignItems:"center",gap:2,minHeight:30,padding:"4px 8px",border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",fontSize:12,lineHeight:1.6,minWidth:0,width:"100%",boxSizing:"border-box"},Wx={outline:"none",minWidth:8,minHeight:"1.2em",whiteSpace:"pre-wrap",color:"#0f172a",display:"inline-block",wordBreak:"break-all"},jx={boxShadow:"inset 0 -2px 0 #93c5fd",background:"#eff6ff",borderRadius:2},Hx={background:"#dbeafe",boxShadow:"inset 0 -2px 0 #2563eb"},Fx={display:"inline-flex",alignItems:"center",gap:1,background:"linear-gradient(180deg,#eff6ff,#dbeafe)",color:"#1d4ed8",border:"1px dashed #60a5fa",borderRadius:9999,padding:"1px 8px 1px 4px",margin:"0 1px",fontSize:11,fontWeight:600,cursor:"grab",userSelect:"none",whiteSpace:"nowrap",boxShadow:"0 1px 2px rgba(37,99,235,0.18)",verticalAlign:"middle"},Kx={opacity:.5,borderStyle:"solid",cursor:"grabbing"},Vx={color:"#2563eb",opacity:.7,fontSize:11,marginRight:1,cursor:"grab"},Ux={marginLeft:4,width:14,height:14,lineHeight:"12px",padding:0,border:"none",borderRadius:9999,background:"rgba(29,78,216,0.12)",color:"#1d4ed8",fontSize:9,fontWeight:700,cursor:"pointer",display:"inline-flex",alignItems:"center",justifyContent:"center",flex:"0 0 auto"},gc={padding:"4px 8px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#475569",cursor:"pointer",whiteSpace:"nowrap"},qx={fontSize:10,color:"#94a3b8"},Gx=ql;function ms(e){return mb(e)}function Ve({value:e,onChange:t,t:n,candidates:o,placeholder:r,testidPrefix:s="g7le-data-chip-value",readOnly:a=!1,enableExpressionTree:l=!0}){const d=typeof e=="string"?e:"",c=!!o&&o.length>0,[p,f]=u.useState(!1),[y,g]=u.useState(!1),[m,h]=u.useState(!1),v=u.useCallback(E=>i.jsx(Ve,{value:E.value,onChange:C=>E.onChange(C),t:E.t,candidates:E.candidates,testidPrefix:E.testidPrefix,enableExpressionTree:!0}),[]),b=()=>i.jsx("div",{style:{display:"flex",gap:4,alignItems:"center",width:"100%",minWidth:0},children:i.jsx("input",{type:"text","data-testid":`${s}-input`,value:d,placeholder:r??n("layout_editor.page_settings.seo.value_placeholder"),readOnly:a,onChange:E=>t(E.target.value),onFocus:()=>f(!0),onBlur:()=>f(!1),style:{flex:1,padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,minWidth:0,width:"100%",background:a?"#f1f5f9":"#fff",color:a?"#64748b":"#0f172a"}})}),x=u.useCallback(E=>{const C=d?`${d} ${E.expression}`:E.expression;t(C)},[d,t]);if(!l||a)return i.jsxs("div",{"data-testid":s,style:pn,children:[b(),!a&&c&&i.jsx($t,{candidates:o,t:n,onSelect:x,testIdSuffix:`${s}-pick`})]});const k=Tv(d),w=Gd(d),_=Kt(d),S=E=>{const C=Sr(d),N=C?us(C,()=>"",$=>Vt($)):[];return i.jsxs(i.Fragment,{children:[i.jsx("div",{style:{display:"flex",justifyContent:"flex-start"},children:i.jsxs("button",{type:"button","data-testid":`${E}-to-plain`,onClick:()=>h(!0),style:Zx,title:n("layout_editor.value_tree.to_plain"),"aria-label":n("layout_editor.value_tree.to_plain"),children:["↩ ",n("layout_editor.value_tree.to_plain")]})}),m&&i.jsxs("div",{"data-testid":`${E}-to-plain-confirm`,style:Qx,children:[i.jsx("p",{style:Rx,children:n("layout_editor.value_tree.to_plain_lead")}),i.jsx("div",{style:e0,children:N.length===0?i.jsx("span",{style:{color:"#94a3b8"},children:n("layout_editor.page_settings.seo.value_placeholder")}):N.map(($,A)=>$.kind==="chip"?i.jsxs("span",{style:vs,children:["🔗 ",$.text]},A):i.jsx("span",{style:$.kind==="ellipsis"?{color:"#94a3b8"}:void 0,children:$.text},A))}),i.jsxs("p",{style:t0,children:["⚠ ",n("layout_editor.value_tree.to_plain_warn")]}),i.jsxs("div",{style:{display:"flex",justifyContent:"flex-end",gap:6},children:[i.jsx("button",{type:"button","data-testid":`${E}-to-plain-confirm-cancel`,onClick:()=>h(!1),style:Ut,children:n("layout_editor.value_tree.to_plain_cancel")}),i.jsx("button",{type:"button","data-testid":`${E}-to-plain-confirm-ok`,onClick:()=>{h(!1),t(Sr(d)||"")},style:n0,children:n("layout_editor.value_tree.to_plain_ok")})]})]})]})};if(!p&&(k==="expression"||w)){if(w)return i.jsxs("div",{"data-testid":s,style:pn,children:[!a&&S(`${s}-seg`),i.jsx(gs,{value:d,onChange:C=>t(C),t:n,candidates:o,testidPrefix:`${s}-seg`,renderLeafInput:v})]});const E=un(d);return i.jsxs("div",{"data-testid":s,style:pn,children:[!a&&S(`${s}-tree`),i.jsx(nc,{node:E.node,onChange:C=>t(Zn(C,!1)),t:n,candidates:o,testidPrefix:`${s}-tree`,renderLeafInput:v})]})}if(_&&!p&&!y)return i.jsx(Yx,{value:d,onChange:t,t:n,candidates:o,testidPrefix:s,onEditRaw:()=>g(!0)});if(!p&&!y&&ms(d)){const E=Gx(d);return i.jsx("div",{"data-testid":s,style:pn,children:i.jsxs("div",{style:hc,children:[i.jsx("div",{"data-testid":`${s}-chips`,style:Xx,children:E.map((C,N)=>C.kind==="chip"?i.jsxs("span",{style:vs,children:["🔗 ",C.label]},N):i.jsx("span",{children:C.label},N))}),i.jsxs("button",{type:"button","data-testid":`${s}-edit-raw`,onClick:()=>g(!0),style:Ut,title:n("layout_editor.value_tree.edit"),"aria-label":n("layout_editor.value_tree.edit"),children:["✎ ",n("layout_editor.value_tree.edit")]})]})})}return y&&ms(d)?i.jsxs("div",{"data-testid":s,style:pn,children:[i.jsx(bs,{value:d,onChange:t,t:n,candidates:o,disabled:a,onDone:()=>g(!1),testidPrefix:`${s}-chipedit`}),!a&&k!=="raw"&&i.jsxs("div",{style:{display:"flex",gap:4,flexWrap:"wrap"},children:[i.jsxs("button",{type:"button","data-testid":`${s}-to-expr`,onClick:()=>t(Co(d)),style:Ut,title:n("layout_editor.value_tree.to_expression"),"aria-label":n("layout_editor.value_tree.to_expression"),children:["ƒx ",n("layout_editor.value_tree.to_expression")]}),i.jsxs("button",{type:"button","data-testid":`${s}-to-fallback`,onClick:()=>t(ps(d)),style:Ut,title:n("layout_editor.value_tree.segment.fallback"),"aria-label":n("layout_editor.value_tree.segment.fallback"),children:["?? ",n("layout_editor.value_tree.segment.fallback")]})]}),k==="raw"&&i.jsxs("p",{"data-testid":`${s}-raw-hint`,style:_s,children:["ⓘ ",n("layout_editor.value_tree.raw_expression_hint")]})]}):!p&&ms(d)?i.jsxs("div",{"data-testid":s,style:pn,children:[i.jsx(bs,{value:d,onChange:t,t:n,candidates:o,disabled:a,onDone:()=>{g(!1),f(!1)},testidPrefix:`${s}-chipedit`}),!a&&k!=="raw"&&i.jsxs("div",{style:{display:"flex",gap:4,flexWrap:"wrap"},children:[i.jsxs("button",{type:"button","data-testid":`${s}-to-expr`,onClick:()=>t(Co(d)),style:Ut,title:n("layout_editor.value_tree.to_expression"),"aria-label":n("layout_editor.value_tree.to_expression"),children:["ƒx ",n("layout_editor.value_tree.to_expression")]}),i.jsxs("button",{type:"button","data-testid":`${s}-to-fallback`,onClick:()=>t(ps(d)),style:Ut,title:n("layout_editor.value_tree.segment.fallback"),"aria-label":n("layout_editor.value_tree.segment.fallback"),children:["?? ",n("layout_editor.value_tree.segment.fallback")]})]}),k==="raw"&&i.jsxs("p",{"data-testid":`${s}-raw-hint`,style:_s,children:["ⓘ ",n("layout_editor.value_tree.raw_expression_hint")]})]}):i.jsxs("div",{"data-testid":s,style:pn,children:[b(),i.jsxs("div",{style:Jx,children:[c&&i.jsx($t,{candidates:o,t:n,onSelect:x,testIdSuffix:`${s}-pick`}),k!=="raw"&&i.jsxs("div",{style:{display:"flex",gap:4},children:[i.jsxs("button",{type:"button","data-testid":`${s}-to-expr`,onClick:()=>t(Co(d)),style:Ut,title:n("layout_editor.value_tree.to_expression"),"aria-label":n("layout_editor.value_tree.to_expression"),children:["ƒx ",n("layout_editor.value_tree.to_expression")]}),i.jsxs("button",{type:"button","data-testid":`${s}-to-fallback`,onClick:()=>t(ps(d)),style:Ut,title:n("layout_editor.value_tree.segment.fallback"),"aria-label":n("layout_editor.value_tree.segment.fallback"),children:["?? ",n("layout_editor.value_tree.segment.fallback")]})]})]}),k==="raw"&&!p&&i.jsxs("p",{"data-testid":`${s}-raw-hint`,style:_s,children:["ⓘ ",n("layout_editor.value_tree.raw_expression_hint")]})]})}function Yx({value:e,onChange:t,t:n,candidates:o,testidPrefix:r,onEditRaw:s}){const[a,l]=u.useState(!1),d=u.useRef(null),c=Vt(e),p=!!o&&o.length>0,f=Kt(e)?.localeFn;return i.jsxs("div",{"data-testid":r,style:pn,children:[i.jsxs("div",{style:hc,children:[i.jsxs("span",{"data-testid":`${r}-chip`,style:vs,children:["🔗 ",c]}),p&&i.jsx("button",{ref:d,type:"button","data-testid":`${r}-change`,onClick:()=>l(y=>!y),style:Ut,title:n("layout_editor.value_tree.change_data"),"aria-label":n("layout_editor.value_tree.change_data"),children:n("layout_editor.value_tree.change_data")}),i.jsx("button",{type:"button","data-testid":`${r}-edit-raw`,onClick:s,style:Ut,title:n("layout_editor.value_tree.edit"),"aria-label":n("layout_editor.value_tree.edit"),children:"✎"})]}),p&&i.jsx(Xn,{anchorRef:d,open:a,onClose:()=>l(!1),children:i.jsx($t,{candidates:o,t:n,onSelect:y=>{if(f){const h=[y.sourceId,...y.path?y.path.split(".").filter(Boolean):[]].join(".");t(`{{${f}(${h})}}`),l(!1);return}const g=y.path?y.path.split(".").filter(Boolean):[],m=[y.sourceId,...g].join("?.");t(`{{${m}}}`),l(!1)},testIdSuffix:`${r}-pick`,defaultOpen:!0,floating:!1})})]})}const pn={display:"flex",flexDirection:"column",gap:4,width:"100%",minWidth:0},hc={display:"flex",alignItems:"center",gap:6,width:"100%",minWidth:0,flexWrap:"wrap"},Xx={flex:1,minWidth:0,display:"flex",flexWrap:"wrap",alignItems:"center",gap:4,padding:"5px 8px",fontSize:12,color:"#334155",border:"1px solid #e2e8f0",borderRadius:6,background:"#f8fafc"},vs={fontSize:11,background:"#eef2ff",color:"#4338ca",padding:"2px 8px",borderRadius:12,whiteSpace:"nowrap"},Ut={padding:"4px 8px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#475569",cursor:"pointer",whiteSpace:"nowrap"},Jx={display:"flex",flexWrap:"wrap",alignItems:"center",gap:6},_s={margin:"2px 0 0",fontSize:11,color:"#94a3b8",lineHeight:1.4},Zx={padding:"3px 10px",fontSize:11,border:"1px solid #fca5a5",borderRadius:6,background:"#fef2f2",color:"#b91c1c",cursor:"pointer",whiteSpace:"nowrap"},Qx={display:"flex",flexDirection:"column",gap:6,padding:10,border:"1px solid #fca5a5",borderRadius:8,background:"#fffbeb"},Rx={margin:0,fontSize:12,color:"#334155"},e0={padding:"5px 8px",fontSize:12,color:"#0f172a",background:"#fff",border:"1px solid #e2e8f0",borderRadius:6,wordBreak:"break-all",display:"flex",flexWrap:"wrap",gap:4,alignItems:"center"},t0={margin:0,fontSize:11,color:"#b45309"},n0={padding:"3px 12px",fontSize:11,border:"1px solid #dc2626",borderRadius:6,background:"#dc2626",color:"#fff",cursor:"pointer",whiteSpace:"nowrap"};let yc=0;function zr({value:e,onChange:t,keyField:n,valueField:o="content",t:r,candidates:s,testidPrefix:a="g7le-kv-chip-editor",keyPlaceholder:l,valuePlaceholder:d,addLabel:c,renderKeyInput:p}){const f=u.useMemo(()=>(Array.isArray(e)?e:[]).map(k=>({id:yc++,k:typeof k[n]=="string"?k[n]:"",v:typeof k[o]=="string"?k[o]:""})),[]),[y,g]=u.useState(f),m=u.useCallback(k=>{g(k);const w=k.filter(_=>_.k.trim()!=="").map(_=>({[n]:_.k,[o]:_.v}));t(w)},[n,o,t]),h=u.useCallback(()=>{m([...y,{id:yc++,k:"",v:""}])},[y,m]),v=u.useCallback(k=>{m(y.filter(w=>w.id!==k))},[y,m]),b=u.useCallback((k,w)=>{m(y.map(_=>_.id===k?{..._,k:w}:_))},[y,m]),x=u.useCallback((k,w)=>{m(y.map(_=>_.id===k?{..._,v:w}:_))},[y,m]);return i.jsxs("div",{"data-testid":a,style:{display:"flex",flexDirection:"column",gap:6},children:[y.map(k=>i.jsxs("div",{"data-testid":`${a}-row-${k.id}`,style:{display:"flex",gap:6,alignItems:"flex-start",border:"1px solid #e2e8f0",borderRadius:8,padding:8},children:[i.jsxs("div",{style:{flex:1,minWidth:0,display:"flex",flexDirection:"column",gap:6},children:[i.jsx("div",{style:{display:"flex",flexDirection:"column",gap:2,minWidth:0},children:p?i.jsx("div",{style:{minWidth:0},children:p({value:k.k,onChange:w=>b(k.id,w),testid:`${a}-key-${k.id}`,placeholder:l??r(`layout_editor.page_settings.seo.extra_${n}`)})}):i.jsx("input",{type:"text","data-testid":`${a}-key-${k.id}`,value:k.k,placeholder:l??r(`layout_editor.page_settings.seo.extra_${n}`),onChange:w=>b(k.id,w.target.value),style:{width:"100%",padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,minWidth:0,boxSizing:"border-box"}})}),i.jsx("div",{style:{minWidth:0},children:i.jsx(Ve,{value:k.v,onChange:w=>x(k.id,w),t:r,candidates:s,placeholder:d??r("layout_editor.page_settings.seo.extra_content"),testidPrefix:`${a}-value-${k.id}`})})]}),i.jsx("button",{type:"button","data-testid":`${a}-remove-${k.id}`,onClick:()=>v(k.id),title:r("layout_editor.page_settings.seo.remove"),"aria-label":r("layout_editor.page_settings.seo.remove"),style:{flex:"0 0 auto",padding:"4px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#64748b",cursor:"pointer"},children:"✕"})]},k.id)),i.jsxs("button",{type:"button","data-testid":`${a}-add`,onClick:h,style:{alignSelf:"flex-start",padding:"5px 10px",fontSize:12,border:"1px dashed #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#475569",cursor:"pointer"},children:["+ ",c??r("layout_editor.page_settings.seo.add_extra")]})]})}const o0=["website","article","product","profile"],r0=["summary","summary_large_image","app","player"];function i0(e){switch(e){case"core":return"layout_editor.page_settings.seo.og_source_core";case"inherited":return"layout_editor.page_settings.seo.og_source_inherited";case"filter":return"layout_editor.page_settings.seo.og_source_filter";case"layout":return null;default:return e?"layout_editor.page_settings.seo.og_source_module":null}}function bc(e){const t={};for(const n of e??[])t[n.key]=n;return t}function mc(e,t){return!!e&&t&&typeof e.sourceExpr=="string"&&e.sourceExpr!==""&&!e.overriddenByLayout&&!e.lockedByFilter}function vc({label:e,sourceExpr:t,onReplace:n,t:o,testid:r}){return i.jsxs("div",{"data-testid":r,style:h0,children:[i.jsxs("span",{"data-testid":`${r}-chip`,style:y0,title:t,children:["🔗 ",e||t]}),i.jsx("span",{"data-testid":`${r}-badge`,style:b0,children:o("layout_editor.page_settings.seo.auto_chip_source")}),i.jsx("button",{type:"button","data-testid":`${r}-replace`,onClick:n,style:m0,children:o("layout_editor.page_settings.seo.auto_chip_replace")})]})}function s0({og:e,twitter:t,onChangeOg:n,onChangeTwitter:o,ogPreview:r,twitterPreview:s,defaultsAvailable:a=!1,candidates:l,t:d,testidPrefix:c="g7le-seo-og"}){const p=e&&typeof e=="object"?e:{},f=t&&typeof t=="object"?t:{},[y,g]=u.useState(p.image_height!=null||p.image_type!=null||p.image_secure_url!=null),m=bc(r),h=bc(s),v=u.useCallback((N,$)=>{const A={...p};$===void 0||$===""||$===null?delete A[N]:A[N]=$,n(A)},[p,n]),b=u.useCallback((N,$)=>{const A={...f};$===void 0||$===""||$===null?delete A[N]:A[N]=$,o(A)},[f,o]),x=(N,$,A)=>!A||!a||!A.lockedByFilter?null:i.jsx("div",{style:{display:"flex",gap:6,alignItems:"center",marginTop:2},children:i.jsxs("span",{"data-testid":`${N}-locked-${$}`,style:p0,children:["🔒 ",d("layout_editor.page_settings.seo.og_locked")]})}),k=N=>{if(!N||!a||N.lockedByFilter||N.overriddenByLayout)return;const $=i0(N.source);return $?d($):void 0},w=(N,$)=>N&&a?N.overriddenByLayout===!0:$!=="",_=(N,$,A,z,D,O)=>{const M=O[$],B=!!M?.lockedByFilter,T=typeof z[$]=="string"?z[$]:"";return i.jsxs("div",{"data-testid":`${N}-${$}`,style:Dn,children:[i.jsx("label",{style:to,children:d(A)}),B?i.jsx("div",{"data-testid":`${N}-locked-value-${$}`,style:u0,children:String(M?.effectiveValue??"")}):mc(M,a)?i.jsx(vc,{label:M.label??"",sourceExpr:M.sourceExpr,onReplace:()=>D($,M.sourceExpr),t:d,testid:`${N}-${$}-auto`}):i.jsx(vt,{value:T,onChange:H=>D($,H??""),t:d,candidates:l,placeholder:a&&M?.effectiveValue!=null?String(M.effectiveValue):void 0,testidPrefix:`${N}-${$}-field`,enableExpressionTree:!0,expressionTreeCollapsible:!0}),x(N,$,M)]})},S=(N,$,A,z,D,O)=>{const M=O[$],B=!!M?.lockedByFilter,T=typeof z[$]=="string"?z[$]:"";return i.jsxs("div",{"data-testid":`${N}-${$}`,style:Dn,children:[i.jsx("label",{style:to,children:d(A)}),mc(M,a)?i.jsx(vc,{label:M.label??"",sourceExpr:M.sourceExpr,onReplace:()=>D($,M.sourceExpr),t:d,testid:`${N}-${$}-auto`}):i.jsx(Ve,{value:T,onChange:H=>D($,H),t:d,candidates:l,readOnly:B,placeholder:a&&M?.effectiveValue!=null?String(M.effectiveValue):void 0,testidPrefix:`${N}-${$}-field`}),x(N,$,M)]})},E=(N,$,A,z,D,O)=>{const M=O[$],B=!!M?.lockedByFilter,T=typeof z[$]=="string"?z[$]:"",H=w(M,T),L=H?T:"";return i.jsxs("div",{"data-testid":`${N}-${$}`,style:Dn,children:[i.jsx("label",{style:to,children:d(A)}),i.jsx(xx,{filled:H,sourceLabel:k(M),onRevert:B?void 0:()=>D($,void 0),revertLabel:d("layout_editor.page_settings.seo.revert_default"),testid:`${N}-${$}-overlay`,children:i.jsx("div",{"data-testid":`${N}-${$}-input`,style:{minWidth:0},children:i.jsx(Ve,{value:L,onChange:I=>D($,I),t:d,candidates:l,readOnly:B,placeholder:a&&M?.effectiveValue!=null?String(M.effectiveValue):void 0,testidPrefix:`${N}-${$}-chip`})})}),x(N,$,M)]})},C=(N,$,A,z,D,O,M)=>{const B=M[$],T=typeof D[$]=="string"?D[$]:"",H=w(B,T),L=k(B),I=H?T:"";return i.jsxs("div",{"data-testid":`${N}-${$}`,style:Dn,children:[i.jsxs("div",{style:l0,children:[i.jsx("label",{style:to,children:d(A)}),L?i.jsxs("span",{"data-testid":`${N}-${$}-source-inline`,style:d0,children:["· ",L]}):null]}),i.jsxs("select",{"data-testid":`${N}-${$}-select`,value:I,onChange:P=>O($,P.target.value),style:c0,children:[i.jsx("option",{value:"",children:a&&B?.effectiveValue!=null&&B.effectiveValue!==""?`${d("layout_editor.page_settings.seo.use_default")} (${String(B.effectiveValue)})`:d("layout_editor.page_settings.seo.use_default")}),z.map(P=>i.jsx("option",{value:P,children:P},P))]}),x(N,$,B)]})};return i.jsxs("div",{"data-testid":c,style:{display:"flex",flexDirection:"column",gap:12},children:[i.jsxs("div",{"data-testid":`${c}-og-section`,style:_c,children:[i.jsx("div",{style:xc,children:d("layout_editor.page_settings.seo.og_section")}),i.jsxs("p",{"data-testid":`${c}-intro`,style:a0,children:["ⓘ ",d("layout_editor.page_settings.seo.og_intro")]}),C(c,"type","layout_editor.page_settings.seo.og_type",o0,p,v,m),_(c,"title","layout_editor.page_settings.seo.og_title",p,v,m),_(c,"description","layout_editor.page_settings.seo.og_description",p,v,m),S(c,"image","layout_editor.page_settings.seo.og_image",p,v,m),_(c,"image_alt","layout_editor.page_settings.seo.og_image_alt",p,v,m),i.jsxs("div",{style:Dn,children:[i.jsxs("button",{type:"button","data-testid":`${c}-image-adv-toggle`,"aria-expanded":y,onClick:()=>g(N=>!N),style:f0,children:[y?"▾":"▸"," ",d("layout_editor.page_settings.seo.og_image_adv")]}),y?i.jsxs("div",{"data-testid":`${c}-image-adv`,style:g0,children:[E(c,"image_height","layout_editor.page_settings.seo.og_image_height",p,v,m),E(c,"image_type","layout_editor.page_settings.seo.og_image_type",p,v,m),E(c,"image_secure_url","layout_editor.page_settings.seo.og_image_secure_url",p,v,m)]}):null]}),E(c,"site_name","layout_editor.page_settings.seo.og_site_name",p,v,m),E(c,"locale","layout_editor.page_settings.seo.og_locale",p,v,m),i.jsxs("div",{"data-testid":`${c}-extra`,style:Dn,children:[i.jsx("label",{style:to,children:d("layout_editor.page_settings.seo.og_extra")}),i.jsx(zr,{value:Array.isArray(p.extra)?p.extra:[],onChange:N=>v("extra",N.length>0?N:void 0),keyField:"property",t:d,candidates:l,testidPrefix:`${c}-extra-editor`})]})]}),i.jsxs("div",{"data-testid":`${c}-tw-section`,style:_c,children:[i.jsx("div",{style:xc,children:d("layout_editor.page_settings.seo.tw_section")}),i.jsx("p",{style:{margin:"0 0 6px",fontSize:11,color:"#94a3b8"},children:d("layout_editor.page_settings.seo.tw_fallback_note")}),C("g7le-seo-tw","card","layout_editor.page_settings.seo.tw_card",r0,f,b,h),E("g7le-seo-tw","site","layout_editor.page_settings.seo.tw_site",f,b,h),E("g7le-seo-tw","creator","layout_editor.page_settings.seo.tw_creator",f,b,h),_("g7le-seo-tw","title","layout_editor.page_settings.seo.tw_title",f,b,h),_("g7le-seo-tw","description","layout_editor.page_settings.seo.tw_description",f,b,h),S("g7le-seo-tw","image","layout_editor.page_settings.seo.tw_image",f,b,h),_("g7le-seo-tw","image_alt","layout_editor.page_settings.seo.tw_image_alt",f,b,h),i.jsxs("div",{"data-testid":"g7le-seo-tw-extra",style:Dn,children:[i.jsx("label",{style:to,children:d("layout_editor.page_settings.seo.tw_extra")}),i.jsx(zr,{value:Array.isArray(f.extra)?f.extra:[],onChange:N=>b("extra",N.length>0?N:void 0),keyField:"name",t:d,candidates:l,testidPrefix:"g7le-seo-tw-extra-editor"})]})]})]})}const _c={border:"1px solid #e2e8f0",borderRadius:8,padding:12,display:"flex",flexDirection:"column",gap:10},xc={fontSize:13,fontWeight:600,color:"#0f172a"},a0={margin:0,fontSize:11,color:"#64748b",background:"#f8fafc",borderRadius:6,padding:"6px 8px"},Dn={display:"flex",flexDirection:"column",gap:4},to={fontSize:12,color:"#475569"},l0={display:"flex",alignItems:"center",gap:6},d0={fontSize:11,color:"#94a3b8"},c0={padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,width:"100%",minWidth:0,boxSizing:"border-box"},u0={padding:"5px 8px",fontSize:12,border:"1px solid #e2e8f0",borderRadius:6,background:"#f1f5f9",color:"#64748b"},p0={fontSize:11,color:"#7c3aed"},f0={alignSelf:"flex-start",padding:"2px 0",fontSize:12,fontWeight:600,color:"#475569",background:"transparent",border:"none",cursor:"pointer"},g0={display:"flex",flexDirection:"column",gap:8,paddingLeft:14,marginTop:4,minWidth:0},h0={display:"flex",alignItems:"center",gap:8,flexWrap:"wrap",minWidth:0},y0={display:"inline-flex",alignItems:"center",maxWidth:"100%",padding:"3px 8px",fontSize:12,color:"#1d4ed8",background:"#eff6ff",border:"1px solid #bfdbfe",borderRadius:6,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},b0={fontSize:11,color:"#94a3b8"},m0={padding:"2px 8px",fontSize:11,color:"#475569",background:"#fff",border:"1px solid #cbd5e1",borderRadius:6,cursor:"pointer"},v0=["WebPage","WebSite","ItemList","BreadcrumbList","Organization","Person","CollectionPage","AboutPage","ContactPage","SearchAction"];function Mr(e){const t=[],n=(o,r)=>{for(const[s,a]of Object.entries(o)){if(s==="@type"||s==="@context")continue;const l=r?`${r}.${s}`:s;a!==null&&typeof a=="object"&&!Array.isArray(a)?n(a,l):t.push({key:l,value:a==null?"":String(a)})}};return n(e,""),t}function wc(e,t){const n={"@type":e};for(const{key:o,value:r}of t){if(o.trim()==="")continue;const s=o.split(".");let a=n;for(let l=0;lp?Mr(e).map(D=>({id:xs++,...D})):[]),x=u.useCallback((D,O)=>{h(D),b(O),t(wc(D,O.map(M=>({key:M.key,value:M.value}))))},[t]),k=u.useCallback(()=>{const D=n,O=typeof D["@type"]=="string"?D["@type"]:m,M=Mr(D).map(B=>({id:xs++,key:B.key,value:o?.[B.key]?.expr??B.value}));return{type:O,rows:M}},[n,o,m]),w=u.useCallback(D=>{if(y(D),D){if(v.length===0&&n&&typeof n=="object"){const O=k();x(O.type,O.rows);return}t(wc(m,v.map(O=>({key:O.key,value:O.value}))))}else t(null)},[m,v,n,k,x,t]),_=u.useCallback(()=>{if(!n)return;const D=k();x(D.type,D.rows)},[n,k,x]),S=u.useCallback(()=>{x(m,[...v,{id:xs++,key:"",value:""}])},[m,v,x]),E=u.useCallback(D=>{x(m,v.filter(O=>O.id!==D))},[m,v,x]),C=u.useCallback((D,O)=>{x(m,v.map(M=>M.id===D?{...M,key:O}:M))},[m,v,x]),N=u.useCallback((D,O)=>{x(m,v.map(M=>M.id===D?{...M,value:O}:M))},[m,v,x]),$=u.useMemo(()=>n?Mr(n):[],[n]),A=u.useMemo(()=>s?Mr(s):[],[s]),z=v.some(D=>D.key.trim()!==""&&D.value.trim()==="");if(r){const D=s&&typeof s["@type"]=="string"?s["@type"]:"";return i.jsxs("div",{"data-testid":c,style:kc,children:[i.jsxs("div",{style:Sc,children:[i.jsx("span",{style:x0,children:"🔒"}),i.jsx("span",{style:{fontSize:13,fontWeight:600},children:d("layout_editor.page_settings.seo.sd_mode")}),i.jsx("span",{"data-testid":`${c}-locked-badge`,style:w0,children:d("layout_editor.page_settings.seo.sd_filter_locked")})]}),i.jsxs("div",{"data-testid":`${c}-locked`,style:k0,children:[i.jsxs("p",{style:{margin:"0 0 6px",fontSize:12,color:"#475569"},children:["ⓘ ",d("layout_editor.page_settings.seo.sd_filter_locked_note")]}),i.jsxs("div",{"data-testid":`${c}-filtered-block`,children:[D?i.jsxs("div",{style:{fontSize:12,fontWeight:600,marginBottom:4},children:["@type: ",D]}):null,A.map(O=>i.jsxs("div",{"data-testid":`${c}-filtered-row-${O.key}`,style:Cc,children:[i.jsx("code",{style:$c,children:O.key}),i.jsx("span",{style:Ec,children:O.value})]},O.key))]})]})]})}return i.jsxs("div",{"data-testid":c,style:kc,children:[i.jsx("div",{style:Sc,children:i.jsx(Dr,{checked:f,onChange:w,testid:`${c}-mode`,label:d("layout_editor.page_settings.seo.sd_manual_toggle")})}),!f&&i.jsxs("div",{"data-testid":`${c}-auto`,style:{marginTop:8},children:[i.jsxs("div",{"data-testid":`${c}-auto-preview`,style:S0,children:[i.jsxs("div",{style:{fontSize:12,fontWeight:600,marginBottom:6},children:[d("layout_editor.page_settings.seo.sd_auto_title"),n&&typeof n["@type"]=="string"?`: ${n["@type"]}`:"",a?i.jsxs("span",{style:{marginLeft:6,color:"#94a3b8",fontWeight:400},children:["(",a,")"]}):null]}),$.length===0?i.jsx("p",{style:{margin:0,fontSize:12,color:"#94a3b8"},children:d("layout_editor.page_settings.seo.sd_auto_empty")}):$.map(D=>{const O=o?.[D.key];return i.jsxs("div",{"data-testid":`${c}-auto-row-${D.key}`,style:Cc,children:[i.jsx("code",{style:$c,children:D.key}),O?i.jsxs("span",{"data-testid":`${c}-auto-chip-${D.key}`,style:C0,title:O.expr,children:["🔗 ",O.label||O.expr]}):i.jsx("span",{style:Ec,children:D.value})]},D.key)})]}),i.jsxs("p",{"data-testid":`${c}-replace-warn`,style:$0,children:["ⓘ ",d("layout_editor.page_settings.seo.sd_replace_pre_warn")]})]}),f&&i.jsxs("div",{"data-testid":`${c}-manual`,style:{marginTop:8},children:[i.jsxs("p",{"data-testid":`${c}-replace-warn`,style:E0,children:["⚠ ",d("layout_editor.page_settings.seo.sd_replace_warn")]}),i.jsxs("div",{style:{display:"flex",flexDirection:"column",gap:6},children:[i.jsx("label",{style:{fontSize:12,color:"#475569"},children:d("layout_editor.page_settings.seo.sd_type")}),i.jsx("input",{type:"text","data-testid":`${c}-type`,list:`${c}-type-options`,value:m,onChange:D=>x(D.target.value,v),style:I0}),i.jsx("datalist",{id:`${c}-type-options`,children:v0.map(D=>i.jsx("option",{value:D},D))})]}),i.jsx("div",{style:{marginTop:8,display:"flex",flexDirection:"column",gap:6},children:v.map(D=>i.jsxs("div",{"data-testid":`${c}-prop-${D.id}`,style:A0,children:[i.jsx("input",{type:"text","data-testid":`${c}-prop-key-${D.id}`,value:D.key,placeholder:d("layout_editor.page_settings.seo.sd_prop_key"),onChange:O=>C(D.id,O.target.value),style:{width:160,padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,minWidth:0}}),i.jsx("div",{style:{flex:1,minWidth:0},children:i.jsx(Ve,{value:D.value,onChange:O=>N(D.id,O),t:d,candidates:l,placeholder:d("layout_editor.page_settings.seo.sd_prop_value"),testidPrefix:`${c}-prop-value-${D.id}`})}),i.jsx("button",{type:"button","data-testid":`${c}-prop-remove-${D.id}`,onClick:()=>E(D.id),title:d("layout_editor.page_settings.seo.remove"),"aria-label":d("layout_editor.page_settings.seo.remove"),style:T0,children:"✕"})]},D.id))}),i.jsxs("div",{style:{marginTop:8,display:"flex",gap:8},children:[i.jsxs("button",{type:"button","data-testid":`${c}-add`,onClick:S,style:Ic,children:["+ ",d("layout_editor.page_settings.seo.sd_add_prop")]}),n?i.jsxs("button",{type:"button","data-testid":`${c}-seed-from-module`,onClick:_,style:Ic,children:["↩ ",d("layout_editor.page_settings.seo.sd_seed_from_module")]}):null]}),i.jsxs("p",{"data-testid":`${c}-empty-note`,style:D0,children:["ⓘ ",d("layout_editor.page_settings.seo.sd_empty_note"),z?i.jsxs("span",{style:{color:"#b45309"},children:[" ",d("layout_editor.page_settings.seo.sd_has_empty")]}):null]})]})]})}const kc={display:"flex",flexDirection:"column"},Sc={display:"flex",alignItems:"center",gap:8},x0={fontSize:14},w0={fontSize:11,padding:"2px 6px",borderRadius:4,background:"#f1f5f9",color:"#64748b"},k0={marginTop:8,border:"1px solid #fcd34d",borderRadius:8,padding:10,background:"#fffbeb"},S0={border:"1px solid #e2e8f0",borderRadius:8,padding:10,background:"#f8fafc"},Cc={display:"flex",gap:8,alignItems:"center",padding:"2px 0"},$c={fontSize:11,color:"#475569",background:"#fff",padding:"2px 6px",borderRadius:4,minWidth:120},Ec={fontSize:11,color:"#0f172a",fontFamily:"monospace",wordBreak:"break-all"},C0={display:"inline-flex",alignItems:"center",padding:"2px 8px",fontSize:11,color:"#1d4ed8",background:"#eff6ff",border:"1px solid #bfdbfe",borderRadius:6},$0={margin:"6px 0 0",fontSize:12,color:"#64748b"},E0={margin:"0 0 8px",fontSize:12,color:"#b45309",background:"#fffbeb",border:"1px solid #fcd34d",borderRadius:6,padding:"6px 8px"},I0={padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,width:"100%",minWidth:0},A0={display:"flex",gap:6,alignItems:"flex-start",border:"1px solid #e2e8f0",borderRadius:8,padding:8},T0={padding:"4px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#64748b",cursor:"pointer"},Ic={padding:"5px 10px",fontSize:12,border:"1px dashed #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#475569",cursor:"pointer"},D0={margin:"8px 0 0",fontSize:11,color:"#94a3b8"},Ac=new Set(["core_setting","setting","query","route"]);function N0({vars:e,onChange:t,varCandidates:n,gatingMet:o=!1,candidates:r,t:s,testidPrefix:a="g7le-seo-vars"}){const l=u.useMemo(()=>e&&typeof e=="object"?{...e}:{},[e]),d=u.useMemo(()=>Array.isArray(n)?n:[],[n]),c=u.useMemo(()=>o?d.filter(_=>Ac.has(_.source)):[],[d,o]),p=u.useMemo(()=>o?d.filter(_=>_.source==="data"):[],[d,o]);u.useMemo(()=>new Set(c.map(_=>_.name)),[c]);const f=u.useMemo(()=>new Set(p.map(_=>_.name)),[p]),y=u.useMemo(()=>{const _=new Set;for(const S of d)Ac.has(S.source)&&_.add(S.name);return _},[d]),g=u.useMemo(()=>Object.keys(l).filter(_=>!f.has(_)),[l,f]),m=u.useCallback((_,S)=>{const E={...l};S===void 0?delete E[_]:E[_]=S,t(E)},[l,t]),[h,v]=u.useState(""),b=h.trim()!==""&&y.has(h.trim()),x=h.trim()!==""&&Object.prototype.hasOwnProperty.call(l,h.trim()),k=h.trim()!==""&&!b&&!x&&!f.has(h.trim()),w=u.useCallback(()=>{const _=h.trim();_===""||y.has(_)||Object.prototype.hasOwnProperty.call(l,_)||(t({...l,[_]:""}),v(""))},[h,y,l,t]);return i.jsxs("div",{"data-testid":a,style:{display:"flex",flexDirection:"column",gap:10},children:[i.jsx("span",{"data-testid":"g7le-seo-var-list",hidden:!0,"data-gating":o?"1":"0"}),!o&&i.jsxs("div",{"data-testid":`${a}-precondition`,style:z0,children:["⚠ ",s("layout_editor.page_settings.seo.vars_precondition")]}),c.length>0&&i.jsxs("div",{"data-testid":`${a}-auto-group`,style:ws,children:[i.jsx("div",{style:ks,children:s("layout_editor.page_settings.seo.vars_auto_group")}),c.map(_=>i.jsxs("div",{"data-testid":`g7le-seo-var-auto-${_.name}`,style:M0,children:[i.jsx("span",{style:L0,children:"🔒"}),i.jsx("code",{style:Ss,children:_.name}),i.jsxs("span",{style:P0,children:["〔",s("layout_editor.page_settings.seo.vars_auto"),"·",_.source,"〕"]}),_.owner?.name?i.jsx("span",{style:O0,children:_.owner.name}):null]},_.name))]}),p.length>0&&i.jsxs("div",{"data-testid":`${a}-data-group`,style:ws,children:[i.jsx("div",{style:ks,children:s("layout_editor.page_settings.seo.vars_data_group")}),p.map(_=>{const S=l[_.name]??"",E=S.trim()==="";return i.jsxs("div",{"data-testid":`g7le-seo-var-data-${_.name}`,style:Tc,children:[i.jsxs("div",{style:{display:"flex",alignItems:"center",gap:6,marginBottom:4},children:[i.jsx("code",{style:Ss,children:_.name}),_.required?i.jsx("span",{"data-testid":`g7le-seo-var-required-${_.name}`,style:B0,children:"*"}):null]}),i.jsx(Ve,{value:S,onChange:C=>m(_.name,C??""),t:s,candidates:r,testidPrefix:`g7le-seo-var-data-field-${_.name}`}),_.required&&E?i.jsxs("p",{"data-testid":`g7le-seo-var-empty-warn-${_.name}`,style:Cs,children:["⚠ ",s("layout_editor.page_settings.seo.vars_required_empty")]}):null]},_.name)})]}),i.jsxs("div",{"data-testid":`${a}-custom-group`,style:ws,children:[i.jsx("div",{style:ks,children:s("layout_editor.page_settings.seo.vars_custom_group")}),g.map(_=>i.jsxs("div",{"data-testid":`g7le-seo-var-custom-${_}`,style:Tc,children:[i.jsxs("div",{style:{display:"flex",alignItems:"center",gap:6,marginBottom:4},children:[i.jsx("code",{style:Ss,children:_}),i.jsx("button",{type:"button","data-testid":`g7le-seo-var-custom-remove-${_}`,onClick:()=>m(_,void 0),title:s("layout_editor.page_settings.seo.remove"),"aria-label":s("layout_editor.page_settings.seo.remove"),style:{marginLeft:"auto",padding:"2px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#64748b",cursor:"pointer"},children:"✕"})]}),i.jsx(Ve,{value:l[_]??"",onChange:S=>m(_,S??""),t:s,candidates:r,testidPrefix:`g7le-seo-var-custom-field-${_}`})]},_)),i.jsxs("div",{"data-testid":"g7le-seo-var-add",style:{display:"flex",flexDirection:"column",gap:4,marginTop:6},children:[i.jsxs("div",{style:{display:"flex",gap:6},children:[i.jsx("input",{type:"text","data-testid":"g7le-seo-var-add-name",value:h,placeholder:s("layout_editor.page_settings.seo.vars_add_name"),onChange:_=>v(_.target.value),style:{flex:1,padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,minWidth:0}}),i.jsxs("button",{type:"button","data-testid":"g7le-seo-var-add-confirm",onClick:w,disabled:h.trim()===""||b||x,style:W0,children:["+ ",s("layout_editor.page_settings.seo.vars_add")]})]}),b?i.jsxs("p",{"data-testid":"g7le-seo-var-add-reserved-warn",style:Cs,children:["⚠ ",s("layout_editor.page_settings.seo.vars_name_reserved",{name:h.trim()})]}):null,x&&!b?i.jsxs("p",{"data-testid":"g7le-seo-var-add-duplicate-warn",style:Cs,children:["⚠ ",s("layout_editor.page_settings.seo.vars_name_duplicate",{name:h.trim()})]}):null,k?i.jsxs("p",{"data-testid":"g7le-seo-var-add-not-provided-hint",style:{margin:0,fontSize:11,color:"#94a3b8"},children:["ⓘ ",s("layout_editor.page_settings.seo.vars_name_not_provided",{name:h.trim()})]}):null]})]}),i.jsxs("p",{style:{margin:0,fontSize:11,color:"#94a3b8"},children:["ⓘ ",s("layout_editor.page_settings.seo.vars_value_hint")]})]})}const z0={fontSize:12,color:"#b45309",background:"#fffbeb",border:"1px solid #fcd34d",borderRadius:6,padding:"6px 8px"},ws={border:"1px solid #e2e8f0",borderRadius:8,padding:10,display:"flex",flexDirection:"column",gap:6},ks={fontSize:12,fontWeight:600,color:"#475569"},M0={display:"flex",alignItems:"center",gap:6,padding:"2px 0"},Tc={display:"flex",flexDirection:"column",border:"1px solid #f1f5f9",borderRadius:6,padding:8},L0={fontSize:13},Ss={fontSize:12,color:"#0f172a",background:"#f1f5f9",padding:"2px 6px",borderRadius:4},P0={fontSize:11,color:"#64748b"},O0={fontSize:11,color:"#94a3b8"},B0={color:"#dc2626",fontWeight:700},Cs={margin:"4px 0 0",fontSize:11,color:"#b45309"},W0={padding:"5px 10px",fontSize:12,border:"1px dashed #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#475569",cursor:"pointer",whiteSpace:"nowrap"};function j0({templateIdentifier:e,layout:t,url:n,locale:o,seedContext:r,routeParams:s,settingsSignature:a,t:l,debounceMs:d=400,fetchImpl:c,testidPrefix:p="g7le-seo-bot-preview"}){const[f,y]=u.useState(!0),[g,m]=u.useState(null),[h,v]=u.useState(!1),[b,x]=u.useState(null),k=u.useRef(null),w=u.useRef({templateIdentifier:e,layout:t,url:n,locale:o,seedContext:r,routeParams:s});w.current={templateIdentifier:e,layout:t,url:n,locale:o,seedContext:r,routeParams:s};const _=u.useCallback(async()=>{const N=c??(typeof fetch<"u"?fetch:void 0);if(!N)return;const{templateIdentifier:$,layout:A,url:z,locale:D,seedContext:O,routeParams:M}=w.current;v(!0),x(null);try{const B=`/api/admin/templates/${encodeURIComponent($)}/editor/seo-bot-preview`,T=await N(B,{method:"POST",credentials:"same-origin",headers:je({"Content-Type":"application/json"}),body:JSON.stringify({layout:A,url:z,locale:D,seed_context:O??{},route_params:M??{}})}),H=await T.json().catch(()=>null);if(!T.ok){x(H?.message??`HTTP ${T.status}`);return}const L=H?.data??null;m(L)}catch(B){x(B instanceof Error?B.message:String(B))}finally{v(!1)}},[c]);u.useEffect(()=>{if(f)return k.current&&clearTimeout(k.current),k.current=setTimeout(()=>{_()},d),()=>{k.current&&clearTimeout(k.current)}},[a,f,d,_]);const S=u.useCallback(()=>{k.current&&clearTimeout(k.current),_()},[_]),E=g?.html??null,C=g?.enabled??!0;return i.jsxs("div",{"data-testid":p,style:H0,children:[i.jsxs("div",{style:F0,children:[i.jsxs("button",{type:"button","data-testid":`${p}-toggle`,onClick:()=>y(N=>!N),"aria-expanded":f,style:K0,children:[f?"▾":"▸"," ",l("layout_editor.page_settings.seo.bot_preview_title")]}),f?i.jsxs("button",{type:"button","data-testid":`${p}-refresh`,onClick:S,disabled:h,style:V0,children:["⟳ ",l("layout_editor.page_settings.seo.bot_preview_refresh")]}):null]}),f&&i.jsxs("div",{style:{marginTop:8},children:[g&&!C?i.jsxs("p",{"data-testid":`${p}-disabled`,style:U0,children:["ⓘ ",l("layout_editor.page_settings.seo.bot_preview_disabled")]}):i.jsx("div",{"data-testid":`${p}-body`,style:q0,children:h&&!E?i.jsx("p",{style:$s,children:l("layout_editor.translation.loading")}):b?i.jsx("p",{"data-testid":`${p}-error`,style:{...$s,color:"#dc2626"},children:b}):E==null?i.jsx("p",{style:$s,children:l("layout_editor.page_settings.seo.bot_preview_empty")}):i.jsx("pre",{"data-testid":`${p}-code`,style:G0,children:E})}),i.jsxs("p",{"data-testid":`${p}-sample-note`,style:Y0,children:["ⓘ ",l("layout_editor.page_settings.seo.bot_preview_sample_note")]})]})]})}const H0={borderTop:"1px solid #e2e8f0",paddingTop:10,marginTop:12},F0={display:"flex",alignItems:"center",gap:8},K0={background:"none",border:"none",cursor:"pointer",fontSize:13,fontWeight:600,color:"#0f172a",padding:0},V0={marginLeft:"auto",padding:"4px 10px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},U0={margin:0,fontSize:12,color:"#64748b",background:"#f8fafc",border:"1px solid #e2e8f0",borderRadius:6,padding:"8px 10px"},q0={border:"1px solid #e2e8f0",borderRadius:8,background:"#0b1020",padding:0,minHeight:80,overflow:"hidden"},G0={margin:0,padding:12,fontSize:11,lineHeight:1.5,color:"#d1fae5",fontFamily:"monospace",whiteSpace:"pre-wrap",wordBreak:"break-word",maxHeight:360,overflow:"auto"},$s={margin:0,padding:12,fontSize:12,color:"#94a3b8"},Y0={margin:"8px 0 0",fontSize:11,color:"#94a3b8"};function Dc({option:e,testIdPrefix:t}){const{id:n,friendly:o,source:r}=e;return i.jsxs("span",{style:X0,children:[i.jsx("span",{"data-testid":t?`${t}-title`:void 0,style:J0,children:o??n}),o?i.jsx("span",{"data-testid":t?`${t}-id`:void 0,style:Z0,children:n}):null,r?i.jsx("span",{"data-testid":t?`${t}-source`:void 0,style:Q0,children:r}):null]})}const X0={display:"inline-flex",alignItems:"baseline",gap:4,flexWrap:"wrap",minWidth:0},J0={fontSize:12,color:"#0f172a"},Z0={fontSize:10,color:"#94a3b8",fontFamily:"ui-monospace, monospace"},Q0={fontSize:9,fontWeight:600,color:"#7c3aed",background:"#f5f3ff",border:"1px solid #ddd6fe",borderRadius:4,padding:"0 5px",whiteSpace:"nowrap"},R0=["always","hourly","daily","weekly","monthly","yearly","never"];function ew({getValue:e,patch:t,t:n,templateIdentifier:o,candidates:r,availableExtensions:s=[],dataSourceOptions:a=[],previewUrl:l="/",previewLocale:d="ko",seedContext:c,candidatesFetcher:p,ogPreviewFetcher:f,botFetchImpl:y,testidPrefix:g="g7le-seo"}){const m=e("meta",{}).seo,h=m&&typeof m=="object"?m:{},v=u.useCallback((K,ne)=>{const me={...e("meta",{})},Ce={...me.seo??{}};ne==null||ne===""?delete Ce[K]:Ce[K]=ne,me.seo=Ce,t("meta",me)},[e,t]),b=h.enabled===!0,x=typeof h.page_type=="string"?h.page_type:"",k=Array.isArray(h.extensions)?h.extensions:[],w=Array.isArray(h.data_sources)?h.data_sources:[],_=k.length>0&&x!=="",[S,E]=u.useState(null),[C,N]=u.useState(null),$=u.useCallback(async(K,ne)=>{if(p)return p(K,ne);if(typeof fetch>"u")return null;const me=new URLSearchParams;K&&me.set("page_type",K),Array.isArray(ne)&&ne.length>0&&me.set("extensions",JSON.stringify(ne.map(qe=>({type:qe.type,id:qe.id}))));const Ce=me.toString()?`?${me.toString()}`:"",ze=await fetch(en(`/api/admin/templates/${encodeURIComponent(o)}/editor/seo-candidates`,"json",null,Ce.replace(/^[?&]+/,"")),{credentials:"same-origin",headers:je()}),Se=await ze.json().catch(()=>null);return ze.ok?Se?.data??null:null},[p,o]),A=u.useMemo(()=>{const me=(e("__editor",{})??{}).original?.meta?.seo;return me&&typeof me=="object"?me:void 0},[e]),z=u.useRef(A);z.current=A;const D=u.useCallback(async K=>{if(f)return f(K);if(typeof fetch>"u")return null;const ne=await fetch(`/api/admin/templates/${encodeURIComponent(o)}/editor/seo-og-preview`,{method:"POST",credentials:"same-origin",headers:je({"Content-Type":"application/json"}),body:JSON.stringify({seo:K,own_seo:z.current??null,seed_context:c??{},route_params:{}})}),me=await ne.json().catch(()=>null);return ne.ok?me?.data??null:null},[f,o,c]),O=u.useMemo(()=>JSON.stringify(k),[k]),M=u.useRef(h);M.current=h,u.useEffect(()=>{let K=!1;return(async()=>{const ne=await $(x,k);K||E(ne)})(),()=>{K=!0}},[x,O]);const B=u.useMemo(()=>JSON.stringify({og:h.og??null,twitter:h.twitter??null,structured_data:h.structured_data??null}),[h.og,h.twitter,h.structured_data]),T=u.useRef(!1);u.useEffect(()=>{let K=!1;const ne=()=>{(async()=>{const Ce=await D(M.current);K||N(Ce)})()};if(!T.current)return T.current=!0,ne(),()=>{K=!0};const me=setTimeout(ne,180);return()=>{K=!0,clearTimeout(me)}},[x,O,B]);const H=S?.page_types??[],L=S?.toggle_settings??[],I=S?.vars??[],P=u.useMemo(()=>JSON.stringify(h),[h]),F=u.useMemo(()=>e("meta")?{meta:e("meta"),components:e("components",[])}:{meta:{},components:[]},[e]),G=u.useCallback(K=>{k.some(ne=>ne.type===K.type&&ne.id===K.id)||v("extensions",[...k,{type:K.type,id:K.id}])},[k,v]),V=u.useCallback(K=>{const ne=k.filter(me=>!(me.type===K.type&&me.id===K.id));v("extensions",ne.length>0?ne:void 0)},[k,v]),[Y,Z]=u.useState(!1),[se,re]=u.useState(""),U=u.useMemo(()=>{const K=se.trim().toLowerCase();return K?L.filter(ne=>`${ne.ref} ${ne.label??""}`.toLowerCase().includes(K)):L},[L,se]),ee=typeof h.toggle_setting=="string"?h.toggle_setting:"",fe=u.useCallback(K=>{const me=w.includes(K)?w.filter(Ce=>Ce!==K):[...w,K];v("data_sources",me.length>0?me:void 0)},[w,v]);return i.jsxs("div",{"data-testid":g,style:{display:"flex",flexDirection:"column",gap:14},children:[i.jsxs("div",{style:Bt,children:[i.jsx(Dr,{checked:b,onChange:K=>v("enabled",K),testid:`${g}-enabled`,label:n("layout_editor.page_settings.seo.enabled")}),b?null:i.jsxs("p",{"data-testid":`${g}-disabled-note`,style:no,children:["ⓘ ",n("layout_editor.page_settings.seo.disabled_note")]})]}),i.jsx(Nr,{disabled:!b,testid:`${g}-body`,style:{display:"flex",flexDirection:"column",gap:14},children:i.jsxs(i.Fragment,{children:[i.jsxs("div",{style:Bt,children:[i.jsx("label",{style:qt,children:n("layout_editor.page_settings.seo.page_type")}),H.length>0?i.jsxs("select",{"data-testid":`${g}-page-type`,value:x,onChange:K=>v("page_type",K.target.value),style:Lr,children:[i.jsx("option",{value:"",children:n("layout_editor.page_settings.seo.page_type_none")}),H.map(K=>i.jsxs("option",{value:K.value,children:[K.label??K.value,K.owner?.name?` (${K.owner.name})`:""]},K.value))]}):i.jsxs(i.Fragment,{children:[i.jsx("input",{type:"text","data-testid":`${g}-page-type-free`,value:x,placeholder:n("layout_editor.page_settings.seo.page_type_free_placeholder"),onChange:K=>v("page_type",K.target.value),style:Lr}),i.jsxs("p",{"data-testid":`${g}-page-type-free-note`,style:no,children:["ⓘ ",n("layout_editor.page_settings.seo.page_type_free_note")]})]})]}),i.jsxs("div",{style:Bt,children:[i.jsxs("label",{style:qt,children:["★ ",n("layout_editor.page_settings.seo.extensions")]}),i.jsxs("div",{"data-testid":`${g}-extensions`,style:{display:"flex",flexWrap:"wrap",gap:6},children:[k.map(K=>i.jsxs("span",{"data-testid":`${g}-extension-${K.type}-${K.id}`,style:Nc,children:[K.name??`${K.type}:${K.id}`,i.jsx("button",{type:"button","data-testid":`${g}-extension-remove-${K.type}-${K.id}`,onClick:()=>V(K),style:zc,children:"✕"})]},`${K.type}:${K.id}`)),i.jsxs("button",{type:"button","data-testid":`${g}-extension-add`,onClick:()=>Z(K=>!K),style:ow,children:["+ ",n("layout_editor.page_settings.seo.add_extension")]})]}),Y&&i.jsx("div",{"data-testid":`${g}-extension-picker`,style:Mc,children:s.length===0?i.jsx("p",{style:no,children:n("layout_editor.page_settings.seo.no_extension_candidate")}):s.map(K=>i.jsx("button",{type:"button","data-testid":`${g}-extension-option-${K.type}-${K.id}`,onClick:()=>{G(K),Z(!1)},style:Lc,children:K.name??`${K.type}:${K.id}`},`${K.type}:${K.id}`))})]}),!_&&i.jsxs("div",{"data-testid":`${g}-defaults-precondition`,style:nw,children:["⚠ ",n("layout_editor.page_settings.seo.defaults_precondition"),i.jsxs("div",{"data-testid":`${g}-defaults-missing`,style:{fontSize:11,marginTop:4},children:[k.length===0?n("layout_editor.page_settings.seo.missing_extensions"):"",x===""?` ${n("layout_editor.page_settings.seo.missing_page_type")}`:""]})]}),i.jsxs("div",{style:Bt,children:[i.jsx("label",{style:qt,children:n("layout_editor.page_settings.seo.toggle_setting")}),i.jsxs("div",{"data-testid":`${g}-toggle-setting`,children:[ee?i.jsxs("div",{style:{display:"flex",gap:6,alignItems:"center",marginBottom:4},children:[i.jsx("code",{style:Nc,children:ee}),i.jsx("button",{type:"button","data-testid":`${g}-toggle-setting-clear`,onClick:()=>v("toggle_setting",void 0),style:zc,children:"✕"})]}):null,i.jsx("input",{type:"text","data-testid":`${g}-toggle-setting-search`,value:se,placeholder:n("layout_editor.page_settings.seo.toggle_setting_search"),onChange:K=>re(K.target.value),style:Lr}),se.trim()!==""&&i.jsx("div",{"data-testid":`${g}-toggle-setting-results`,style:Mc,children:U.length===0?i.jsx("p",{style:no,children:n("layout_editor.inline_binding.no_results")}):U.map(K=>i.jsx("button",{type:"button","data-testid":`${g}-toggle-setting-option-${K.ref}`,onClick:()=>{v("toggle_setting",K.ref),re("")},style:Lc,children:K.label??K.ref},K.ref))})]})]}),i.jsxs("div",{style:Bt,children:[i.jsx("label",{style:qt,children:n("layout_editor.page_settings.seo.data_sources")}),i.jsx("p",{style:no,children:n("layout_editor.page_settings.seo.data_sources_hint")}),i.jsx("div",{"data-testid":`${g}-data-sources`,style:{display:"flex",flexDirection:"column",gap:6},children:a.length===0?i.jsx("span",{style:no,children:n("layout_editor.page_settings.seo.data_sources_empty")}):a.map(K=>{const ne=w.includes(K.id);return i.jsxs("label",{"data-testid":`${g}-data-source-${K.id}`,style:tw(ne),children:[i.jsx("input",{type:"checkbox","data-testid":`${g}-data-source-check-${K.id}`,checked:ne,onChange:()=>fe(K.id)}),i.jsx(Dc,{option:K,testIdPrefix:`${g}-data-source-${K.id}`})]},K.id)})})]}),i.jsxs("div",{style:Bt,children:[i.jsx("label",{style:qt,children:n("layout_editor.page_settings.seo.priority")}),i.jsx("input",{type:"range","data-testid":`${g}-priority`,min:0,max:1,step:.1,value:typeof h.priority=="number"?h.priority:.5,onChange:K=>v("priority",Number(K.target.value))}),i.jsx("span",{"data-testid":`${g}-priority-value`,style:{fontSize:12,color:"#475569"},children:typeof h.priority=="number"?h.priority:.5})]}),i.jsxs("div",{style:Bt,children:[i.jsx("label",{style:qt,children:n("layout_editor.page_settings.seo.changefreq")}),i.jsxs("select",{"data-testid":`${g}-changefreq`,value:typeof h.changefreq=="string"?h.changefreq:"",onChange:K=>v("changefreq",K.target.value),style:Lr,children:[i.jsx("option",{value:"",children:n("layout_editor.page_settings.seo.use_default")}),R0.map(K=>i.jsx("option",{value:K,children:K},K))]})]}),i.jsxs("div",{style:Bt,children:[i.jsx("label",{style:qt,children:n("layout_editor.page_settings.seo.title")}),i.jsx(vt,{value:typeof h.title=="string"?h.title:"",onChange:K=>v("title",K??""),t:n,candidates:r,testidPrefix:`${g}-title`,enableExpressionTree:!0,expressionTreeCollapsible:!0})]}),i.jsxs("div",{style:Bt,children:[i.jsx("label",{style:qt,children:n("layout_editor.page_settings.seo.description")}),i.jsx(vt,{value:typeof h.description=="string"?h.description:"",onChange:K=>v("description",K??""),t:n,candidates:r,testidPrefix:`${g}-description`,enableExpressionTree:!0,expressionTreeCollapsible:!0})]}),i.jsx(s0,{og:h.og,twitter:h.twitter,onChangeOg:K=>v("og",Object.keys(K).length>0?K:void 0),onChangeTwitter:K=>v("twitter",Object.keys(K).length>0?K:void 0),ogPreview:C?.og,twitterPreview:C?.twitter,defaultsAvailable:C?.defaultsAvailable??!1,candidates:r,t:n}),i.jsxs("div",{style:Bt,children:[i.jsx("label",{style:qt,children:n("layout_editor.page_settings.seo.structured_data")}),i.jsx(_0,{value:h.structured_data,onChange:K=>v("structured_data",K??void 0),autoBlock:C?.structured?.autoBlock,autoMeta:C?.structured?.autoMeta,lockedByFilter:C?.structured?.lockedByFilter,filteredBlock:C?.structured?.filteredBlock,pageType:x,candidates:r,t:n})]}),i.jsxs("div",{style:Bt,children:[i.jsx("label",{style:qt,children:n("layout_editor.page_settings.seo.vars")}),i.jsx(N0,{vars:h.vars,onChange:K=>v("vars",Object.keys(K).length>0?K:void 0),varCandidates:I,gatingMet:_,candidates:r,t:n})]}),i.jsx(j0,{templateIdentifier:o,layout:F,url:l,locale:d,seedContext:c,settingsSignature:P,t:n,fetchImpl:y})]})})]})}const Bt={display:"flex",flexDirection:"column",gap:4},qt={fontSize:12,fontWeight:600,color:"#334155"},Lr={padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,width:"100%",minWidth:0,boxSizing:"border-box"},no={margin:"4px 0 0",fontSize:11,color:"#94a3b8"},tw=e=>({display:"flex",alignItems:"center",gap:8,padding:"6px 10px",fontSize:12,border:`1px solid ${e?"#3b82f6":"#e2e8f0"}`,borderRadius:8,background:e?"#eff6ff":"#fff",cursor:"pointer",minWidth:0}),nw={fontSize:12,color:"#b45309",background:"#fffbeb",border:"1px solid #fcd34d",borderRadius:6,padding:"8px 10px"},Nc={display:"inline-flex",alignItems:"center",gap:4,fontSize:11,padding:"3px 8px",borderRadius:999,background:"#eef2ff",color:"#3730a3"},zc={border:"none",background:"transparent",cursor:"pointer",color:"#6366f1",fontSize:11,padding:0},ow={fontSize:11,padding:"3px 10px",borderRadius:999,border:"1px dashed #cbd5e1",background:"#f8fafc",color:"#475569",cursor:"pointer"},Mc={marginTop:4,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",padding:6,display:"flex",flexDirection:"column",gap:2},Lc={textAlign:"left",border:"none",background:"transparent",cursor:"pointer",padding:"5px 8px",borderRadius:4,fontSize:12,color:"#0f172a"},rw={setState:"state",loadFromLocalStorage:"state",saveToLocalStorage:"state",navigate:"nav",openWindow:"nav",navigateBack:"nav",navigateForward:"nav",replaceUrl:"nav",toast:"notify",openModal:"notify",closeModal:"notify",showAlert:"notify",setError:"notify",refetchDataSource:"data",appendDataSource:"data",updateDataSource:"data",apiCall:"data",scrollIntoView:"etc",login:"etc",logout:"etc",setLocale:"locale",emitEvent:"etc",conditions:"flow",sequence:"flow",parallel:"flow",switch:"flow",suppress:"flow",showErrorPage:"error"},iw={state:"layout_editor.action.group.state",nav:"layout_editor.action.group.nav",notify:"layout_editor.action.group.notify",data:"layout_editor.action.group.data",locale:"layout_editor.action.group.locale",etc:"layout_editor.action.group.etc",flow:"layout_editor.action.group.flow",error:"layout_editor.action.group.error",extension:"layout_editor.action.group.extension"},sw={init:["state","data","notify","nav","locale","etc","flow","error","extension"],component:["nav","notify","data","state","locale","etc","flow","error","extension"]};function aw(e,t){const n=e?.[t];if(n&&typeof n=="object"){const o=n.__source;if(o&&typeof o=="object")return o}return null}function lw(e,t){return!e||e.kind==="core"?t("layout_editor.action.source_core"):e.id??t(`layout_editor.action.source_${e.kind}`)}function Pc(e,t,n){return typeof e!="string"?n:e.startsWith("$t:")?t(e.slice(3)):e}function dw({recipes:e,t,onAdd:n,context:o="init",testIdPrefix:r="g7le-action-add"}){const[s,a]=u.useState(!1),[l,d]=u.useState(""),c=u.useMemo(()=>wo(e),[e]),p=u.useMemo(()=>{const f=sw[o],y=new Map;for(const m of c){const h=aw(e,m.id),b=h!=null&&h.kind!=="core"?"extension":rw[m.id]??"etc",x=y.get(b)??[];x.push({recipe:m,source:h}),y.set(b,x)}const g=l.trim().toLowerCase();return f.map(m=>{let h=y.get(m)??[];return g&&(h=h.filter(({recipe:v})=>Pc(v.label,t,v.id).toLowerCase().includes(g)||v.id.toLowerCase().includes(g)||v.build.handler.toLowerCase().includes(g))),{groupKey:m,items:h}}).filter(m=>m.items.length>0)},[c,e,o,l,t]);return c.length===0?i.jsx("div",{"data-testid":`${r}-empty`,style:vw,children:t("layout_editor.action.no_recipes")}):i.jsxs("div",{className:r,"data-testid":r,style:{minWidth:0},children:[i.jsxs("button",{type:"button","data-testid":`${r}-toggle`,onClick:()=>a(f=>!f),style:cw,children:[t("layout_editor.action.add_action")," ▾"]}),s?i.jsxs("div",{"data-testid":`${r}-list`,style:uw,children:[i.jsx("input",{type:"text","data-testid":`${r}-search`,value:l,placeholder:t("layout_editor.action.add_search"),onChange:f=>d(f.target.value),style:pw}),p.map(({groupKey:f,items:y})=>i.jsxs("div",{"data-testid":`${r}-group-${f}`,children:[i.jsxs("div",{style:fw,children:["━━ ",t(iw[f]??f)," ━━"]}),y.map(({recipe:g,source:m})=>i.jsxs("button",{type:"button","data-testid":`g7le-init-action-spec-${g.id}`,onClick:()=>{n(xr(g,{})),a(!1),d("")},style:gw,children:[i.jsx("span",{style:hw,children:Pc(g.label,t,g.id)}),i.jsxs("span",{"data-testid":`g7le-init-action-spec-source-${g.id}`,style:yw,children:["〔",lw(m,t),"〕"]}),i.jsx("code",{style:bw,children:g.build.handler})]},g.id))]},f)),p.length===0?i.jsx("div",{"data-testid":`${r}-no-match`,style:mw,children:t("layout_editor.action.add_no_match")}):null]}):null]})}const cw={padding:"6px 12px",fontSize:12,border:"1px dashed #94a3b8",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},uw={marginTop:6,border:"1px solid #cbd5e1",borderRadius:8,background:"#fff",maxHeight:320,overflowY:"auto",padding:8},pw={width:"100%",padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,marginBottom:6,boxSizing:"border-box"},fw={fontSize:11,fontWeight:700,color:"#94a3b8",padding:"6px 4px 2px"},gw={display:"flex",alignItems:"center",gap:6,width:"100%",textAlign:"left",padding:"6px 8px",fontSize:12,border:"none",background:"transparent",cursor:"pointer",borderRadius:6},hw={flex:1,minWidth:0,color:"#0f172a"},yw={fontSize:10,color:"#64748b",whiteSpace:"nowrap"},bw={fontSize:10,color:"#cbd5e1",fontFamily:"monospace",whiteSpace:"nowrap"},mw={fontSize:12,color:"#94a3b8",padding:"8px 4px"},vw={fontSize:12,color:"#94a3b8",padding:"8px 0"};function _w(e,t){return t.startsWith("$t:")?e(t.slice(3)):t}function xw({action:e,recipe:t,values:n={},t:o,missingRequired:r=!1,showCodeButton:s=!0,testIdPrefix:a="g7le-action-preview"}){const[l,d]=u.useState(!1),c=t?Wd(t,n,p=>_w(o,p)):typeof e.handler=="string"?e.handler:o("layout_editor.action.advanced_action");return i.jsxs("div",{className:a,"data-testid":a,style:ww,children:[r?i.jsxs("span",{"data-testid":`${a}-missing`,style:Sw,children:["⚠ ",o("layout_editor.action.summary_missing_required")]}):i.jsx("span",{"data-testid":`${a}-summary`,style:kw,children:c}),s?i.jsx("button",{type:"button","data-testid":`${a}-code-toggle`,onClick:()=>d(p=>!p),style:Cw,"aria-expanded":l,"aria-label":o("layout_editor.action.view_code"),children:""}):null,s&&l?i.jsx("pre",{"data-testid":`${a}-code`,style:$w,children:JSON.stringify(e,null,2)}):null]})}const ww={display:"flex",flexWrap:"wrap",alignItems:"center",gap:6,minWidth:0},kw={flex:1,minWidth:0,fontSize:12,color:"#475569"},Sw={flex:1,minWidth:0,fontSize:12,color:"#b45309"},Cw={border:"1px solid #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#475569",cursor:"pointer",fontSize:11,padding:"2px 6px",whiteSpace:"nowrap"},$w={flexBasis:"100%",margin:"6px 0 0",padding:8,background:"#0f172a",color:"#e2e8f0",borderRadius:6,fontSize:11,overflow:"auto"},Oc="__g7le_custom__";function Pr({value:e,onChange:t,candidates:n,placeholder:o,testid:r,t:s,bindingCandidates:a}){const l=e==null?"":String(e),d=Array.isArray(n)&&n.length>0,c=d&&n.some(y=>y.value===l),[p,f]=u.useState(!d||l!==""&&!c);return u.useEffect(()=>{l!==""&&d&&!c&&f(!0)},[l,d,c]),d&&!p?i.jsxs("div",{style:Hc,children:[i.jsxs("select",{className:"g7le-widget g7le-widget--picker","data-testid":r,value:c?l:"",onChange:y=>{const g=y.target.value;if(g===Oc){f(!0);return}t(g===""?void 0:g)},style:{...Wc,flex:1,minWidth:0},children:[i.jsx("option",{value:"",children:s("layout_editor.control.default")}),n.map(y=>i.jsx("option",{value:y.value,children:y.label},y.value)),i.jsx("option",{value:Oc,children:s("layout_editor.recipe.custom_input")})]}),i.jsx("button",{type:"button","data-testid":`${r}-to-custom`,title:s("layout_editor.recipe.custom_input"),"aria-label":s("layout_editor.recipe.custom_input"),onClick:()=>f(!0),style:Fc,children:"✎"})]}):i.jsxs("div",{style:Hc,children:[i.jsx("div",{style:{flex:1,minWidth:0},children:i.jsx(Ve,{value:l,onChange:y=>t(y===""?void 0:y),t:s,candidates:a,placeholder:o,testidPrefix:`${r}-input`})}),d&&i.jsx("button",{type:"button","data-testid":`${r}-pick`,title:s("layout_editor.recipe.pick_from_list"),onClick:()=>{f(!1),t(void 0)},style:Fc,children:"≡"})]})}function Ew({value:e,onChange:t,candidates:n,t:o,bindingCandidates:r}){return i.jsx(Pr,{value:e,onChange:t,candidates:n,placeholder:o("layout_editor.recipe.page_placeholder"),testid:"g7le-widget-page-picker",t:o,bindingCandidates:r})}function Bc({value:e,onChange:t,candidates:n,t:o,bindingCandidates:r}){return i.jsx(Pr,{value:e,onChange:t,candidates:n,placeholder:o("layout_editor.recipe.datasource_placeholder"),testid:"g7le-widget-datasource-picker",t:o,bindingCandidates:r})}function Es({value:e,onChange:t,candidates:n,t:o,bindingCandidates:r}){return i.jsx(Pr,{value:e,onChange:t,candidates:n,placeholder:o("layout_editor.recipe.state_key_placeholder"),testid:"g7le-widget-state-key-picker",t:o,bindingCandidates:r})}function Iw({value:e,onChange:t,candidates:n,t:o,bindingCandidates:r}){return i.jsx(Pr,{value:e,onChange:t,candidates:n,placeholder:o("layout_editor.recipe.modal_placeholder"),testid:"g7le-widget-modal-picker",t:o,bindingCandidates:r})}function Aw({value:e,onChange:t,t:n,bindingCandidates:o}){const r=typeof e=="string"?e:e==null?"":String(e);return i.jsx("div",{className:"g7le-widget g7le-widget--i18n-text","data-testid":"g7le-widget-i18n-text",style:{flex:1,minWidth:100,width:"100%"},children:i.jsx(vt,{value:r,onChange:s=>t(s===""?void 0:s),t:n,placeholder:n("layout_editor.recipe.i18n_text_placeholder"),testidPrefix:"g7le-widget-i18n-text-field",candidates:o,enableExpressionTree:!0,expressionTreeCollapsible:!0})})}function Tw({value:e,onChange:t,candidates:n,t:o}){const r=e==null?"":String(e),s=r.split(",").map(d=>d.trim()).filter(Boolean),a=Array.isArray(n)&&n.length>0,l=d=>{if(!d)return;const c=[...s,d];t(c.join(", "))};return i.jsxs("div",{className:"g7le-widget g7le-widget--binding-list","data-testid":"g7le-widget-binding-list",style:{display:"flex",flexDirection:"column",gap:4,width:"100%"},children:[i.jsx("input",{type:"text","data-testid":"g7le-widget-binding-list-input",value:r,placeholder:o("layout_editor.recipe.datasource_placeholder"),onChange:d=>t(d.target.value===""?void 0:d.target.value),style:jc}),a?i.jsxs("select",{className:"g7le-widget g7le-widget--picker","data-testid":"g7le-widget-binding-list-add",value:"",onChange:d=>{l(d.target.value),d.target.value=""},style:{...Wc,minWidth:0},children:[i.jsx("option",{value:"",children:o("layout_editor.recipe.pick_from_list")}),n.map(d=>i.jsx("option",{value:d.value,children:d.label},d.value))]}):null]})}function Dw({value:e,onChange:t}){const n=e==null?"":String(e);return i.jsx("input",{type:"text",className:"g7le-widget g7le-widget--text","data-testid":"g7le-widget-text",value:n,onChange:o=>t(o.target.value===""?void 0:o.target.value),style:jc})}const Wc={padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",minWidth:140,width:"100%"},jc={flex:1,padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,minWidth:100,width:"100%"},Hc={display:"flex",gap:4,alignItems:"flex-start",width:"100%"},Fc={padding:"4px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#64748b",cursor:"pointer"};function Nw(e){if(e==null)return"null";if(Array.isArray(e))return"list";switch(typeof e){case"number":return"number";case"boolean":return"boolean";case"string":return"string";default:return"object"}}function Kc(e){switch(e){case"string":return"";case"number":return 0;case"boolean":return!1;case"null":return null;case"list":return[];case"object":return{};default:return null}}function Is(e){return/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(e)}function zw(e,t,n){return t.includes(n)?"self":e.includes(n)?"inherited":"self"}function Mw(e){const t=e.state&&typeof e.state=="object"&&Object.keys(e.state).length>0,n=e.initLocal&&typeof e.initLocal=="object"?e.initLocal:void 0;if(!t)return{initLocal:n??{},migrated:!1};const o={...e.state};if(n)for(const[r,s]of Object.entries(n))o[r]=s;return{initLocal:o,migrated:!0}}const Vc=[{kind:"string",labelKey:"layout_editor.initstate.kind_string"},{kind:"number",labelKey:"layout_editor.initstate.kind_number"},{kind:"boolean",labelKey:"layout_editor.initstate.kind_boolean"},{kind:"null",labelKey:"layout_editor.initstate.kind_null"},{kind:"list",labelKey:"layout_editor.initstate.kind_list"},{kind:"object",labelKey:"layout_editor.initstate.kind_object"}];function Nn({value:e,onChange:t,t:n,path:o,scope:r,depth:s=0,renderStringLeaf:a}){const l=Nw(e),d=c=>{t(Kc(c))};return i.jsxs("div",{className:"g7le-initstate-value",style:{paddingLeft:s>0?12:0,minWidth:0},children:[i.jsxs("div",{style:Pw,children:[i.jsx("select",{"data-testid":`g7le-initstate-type-${o}`,value:l,onChange:c=>d(c.target.value),style:Uc,children:Vc.map(({kind:c,labelKey:p})=>i.jsx("option",{value:c,children:n(p)},c))}),l==="string"?a?i.jsx("div",{style:{flex:1,minWidth:0},children:a({value:String(e??""),onChange:c=>t(c),testidPrefix:`g7le-initstate-value-${o}`})}):i.jsx("input",{type:"text","data-testid":`g7le-initstate-value-${o}`,value:String(e??""),onChange:c=>t(c.target.value),style:qc}):l==="number"?i.jsx("input",{type:"number","data-testid":`g7le-initstate-value-${o}`,value:typeof e=="number"?e:"",onChange:c=>{const p=parseFloat(c.target.value);t(Number.isNaN(p)?0:p)},style:qc}):l==="boolean"?i.jsx(Dr,{checked:!!e,onChange:t,testid:`g7le-initstate-value-${o}`,label:n(e?"layout_editor.overlay.on":"layout_editor.overlay.off")}):l==="null"?i.jsx("span",{"data-testid":`g7le-initstate-value-${o}`,style:Ow,children:"null"}):null]}),l==="list"?i.jsxs("div",{style:Gc,children:[e.map((c,p)=>i.jsxs("div",{style:Bw,children:[i.jsxs("span",{style:Ww,children:["[",p,"]"]}),i.jsx("div",{style:{flex:1,minWidth:0},children:i.jsx(Nn,{value:c,onChange:f=>{const y=e.slice();y[p]=f,t(y)},t:n,path:`${o}.${p}`,scope:r,depth:s+1,renderStringLeaf:a})}),i.jsx("button",{type:"button","data-testid":`g7le-initstate-item-remove-${o}.${p}`,onClick:()=>t(e.filter((f,y)=>y!==p)),style:Yc,children:"✕"})]},p)),i.jsxs("button",{type:"button","data-testid":`g7le-initstate-item-add-${o}`,onClick:()=>t([...e,""]),style:Xc,children:["+ ",n("layout_editor.initstate.add_item")]})]}):null,l==="object"?i.jsx(Lw,{value:e,onChange:t,t:n,path:o,scope:r,depth:s+1,renderStringLeaf:a}):null]})}function Lw({value:e,onChange:t,t:n,path:o,scope:r,depth:s,renderStringLeaf:a}){const[l,d]=u.useState(""),[c,p]=u.useState("string"),[f,y]=u.useState(null),g=Object.entries(e),m=()=>{const h=l.trim();if(!h){y(n("layout_editor.initstate.name_required"));return}if(!Is(h)){y(n("layout_editor.initstate.name_invalid"));return}if(h in e){y(n("layout_editor.initstate.name_duplicate"));return}t({...e,[h]:Kc(c)}),d(""),y(null)};return i.jsxs("div",{style:Gc,children:[g.map(([h,v])=>i.jsxs("div",{style:jw,children:[i.jsx("code",{style:Hw,children:h}),i.jsx("div",{style:{flex:1,minWidth:0},children:i.jsx(Nn,{value:v,onChange:b=>t({...e,[h]:b}),t:n,path:`${o}.${h}`,scope:r,depth:s,renderStringLeaf:a})}),i.jsx("button",{type:"button","data-testid":`g7le-initstate-subremove-${o}.${h}`,onClick:()=>{const b={...e};delete b[h],t(b)},style:Yc,children:"✕"})]},h)),i.jsxs("div",{style:Fw,children:[i.jsx("input",{type:"text","data-testid":`g7le-initstate-subkey-input-${o}`,value:l,placeholder:n("layout_editor.initstate.subkey_name"),onChange:h=>{d(h.target.value),f&&y(null)},onKeyDown:h=>{h.key==="Enter"&&(h.preventDefault(),m())},"aria-invalid":f?!0:void 0,style:{...Kw,...f?Vw:null}}),i.jsx("select",{"data-testid":`g7le-initstate-subkey-kind-${o}`,value:c,onChange:h=>p(h.target.value),"aria-label":n("layout_editor.initstate.value_kind"),style:Uc,children:Vc.map(({kind:h,labelKey:v})=>i.jsx("option",{value:h,children:n(v)},h))}),i.jsxs("button",{type:"button","data-testid":`g7le-initstate-subkey-add-${o}`,onClick:m,style:Xc,children:["+ ",n("layout_editor.initstate.add_subkey")]})]}),f?i.jsx("p",{"data-testid":`g7le-initstate-subkey-error-${o}`,style:Uw,children:f}):null]})}const Pw={display:"flex",gap:6,alignItems:"center",minWidth:0},Uc={padding:"4px 6px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,color:"#64748b"},qc={flex:1,minWidth:0,padding:"4px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,boxSizing:"border-box"},Ow={fontSize:12,color:"#94a3b8",fontFamily:"monospace"},Gc={display:"flex",flexDirection:"column",gap:6,marginTop:6,paddingLeft:8,borderLeft:"2px solid #e2e8f0"},Bw={display:"flex",gap:6,alignItems:"flex-start",minWidth:0},Ww={fontSize:11,color:"#94a3b8",minWidth:28,paddingTop:4},jw={display:"flex",gap:6,alignItems:"flex-start",minWidth:0},Hw={fontSize:11,color:"#475569",fontFamily:"monospace",minWidth:72,paddingTop:4},Yc={border:"none",background:"transparent",color:"#94a3b8",cursor:"pointer",fontSize:12},Xc={alignSelf:"flex-start",padding:"2px 8px",fontSize:11,border:"1px dashed #94a3b8",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},Fw={display:"flex",gap:6,alignItems:"center"},Kw={padding:"4px 8px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6},Vw={border:"1px solid #dc2626"},Uw={margin:"2px 0 0",fontSize:11,color:"#dc2626"},qw=new Set(["text","data-chip","number"]),Gw=new Set(["select","toggle","page-picker","datasource-picker","state-key-picker","modal-picker","component-target-picker","tag-input","locale-picker","i18n-text","segmented","slider","color","image"]);function Yw(e,t,n){return typeof e!="string"?n:e.startsWith("$t:")?t(e.slice(3)):e}function Xw(e,t){if(e==="page-picker")return t.pageCandidates;if(e==="datasource-picker")return t.dataSourceCandidates;if(e==="state-key-picker")return t.stateKeyCandidates;if(e==="modal-picker")return t.modalCandidates}function Jw({param:e,value:t,t:n,pools:o,onChange:r,testIdPrefix:s,renderActionList:a,siblingValues:l}){const d=Yw(e.label,n,e.key);if(e.advanced)return i.jsxs("div",{"data-testid":`${s}-locked-${e.key}`,style:sk,children:[i.jsx("span",{style:ak,children:d}),i.jsxs("span",{style:lk,children:["[",n("layout_editor.action.advanced"),"] ",n("layout_editor.init_actions.advanced_param_hint")]})]});if(e.widget==="action-list"&&a){const f=Array.isArray(t)?t.filter(y=>!!y&&typeof y=="object"):[];return i.jsxs("div",{"data-testid":`${s}-param-${e.key}`,style:{display:"flex",flexDirection:"column",gap:4},children:[i.jsx("span",{style:oo,children:d}),a(f,y=>r(y.length===0?void 0:y),`${s}-${e.key}`,e.key)]})}if(e.widget==="branch-list"&&a){const f=Array.isArray(t)?t.filter(y=>!!y&&typeof y=="object"):[];return i.jsxs("div",{"data-testid":`${s}-param-${e.key}`,style:{display:"flex",flexDirection:"column",gap:4},children:[i.jsx("span",{style:oo,children:d}),i.jsx(Zw,{branches:f,onChange:y=>r(y.length===0?void 0:y),t:n,pools:o,renderActionList:a,testIdPrefix:`${s}-${e.key}`})]})}if(e.widget==="key-value")return i.jsxs("div",{"data-testid":`${s}-param-${e.key}`,style:{display:"flex",flexDirection:"column",gap:4},children:[i.jsx("span",{style:oo,children:d}),i.jsx(nk,{value:t,onChange:r,t:n,candidates:o.bindingCandidates,testIdPrefix:`${s}-kv-${e.key}`})]});if(e.widget==="state-key-value")return i.jsxs("div",{"data-testid":`${s}-param-${e.key}`,style:{display:"flex",flexDirection:"column",gap:4},children:[i.jsx("span",{style:oo,children:d}),i.jsx(ok,{value:t,onChange:r,t:n,candidates:o.bindingCandidates,stateKeyCandidates:o.stateKeyCandidates,param:e,siblingTarget:l?.target,testIdPrefix:`${s}-skv-${e.key}`})]});if(qw.has(e.widget??"")||!Gw.has(e.widget??""))return i.jsxs("div",{"data-testid":`${s}-param-${e.key}`,style:Zc,children:[i.jsx("span",{style:oo,children:d}),i.jsx("div",{style:{flex:1,minWidth:0},children:i.jsx(Ve,{value:typeof t=="string"?t:t==null?"":String(t),onChange:f=>r(f===""?void 0:f),t:n,candidates:o.bindingCandidates,placeholder:e.widget==="number"?n("layout_editor.action_param.number_placeholder"):void 0,testidPrefix:`${s}-chip-${e.key}`})})]});const p=Tn(e.widget);return i.jsxs("div",{"data-testid":`${s}-param-${e.key}`,style:Zc,children:[i.jsx("span",{style:oo,children:d}),i.jsx("div",{style:{flex:1,minWidth:0},children:p?i.jsx(p,{control:{...e},value:t,onChange:r,t:n,candidates:Xw(e.widget,o),bindingCandidates:o.bindingCandidates}):i.jsx(Ve,{value:typeof t=="string"?t:t==null?"":String(t),onChange:f=>r(f===""?void 0:f),t:n,candidates:o.bindingCandidates,testidPrefix:`${s}-chip-${e.key}`})})]})}function Zw({branches:e,onChange:t,t:n,pools:o,renderActionList:r,testIdPrefix:s}){const a=y=>Array.isArray(y)?y.filter(g=>!!g&&typeof g=="object"):y&&typeof y=="object"?[y]:[],l=y=>{if(y.length!==0)return y.length===1?y[0]:y},d=(y,g)=>{const m=e.slice();m[y]=g,t(m)},c=y=>{t(e.filter((g,m)=>m!==y))},p=(y,g)=>{if(g<0||g>=e.length)return;const m=e.slice(),[h]=m.splice(y,1);m.splice(g,0,h),t(m)},f=()=>{t([...e,{if:"",then:void 0}])};return i.jsxs("div",{"data-testid":`${s}-branches`,style:{display:"flex",flexDirection:"column",gap:8},children:[e.map((y,g)=>{const m=typeof y.if=="string"?y.if:"",h=a(y.then);return i.jsxs("div",{"data-testid":`${s}-branch-${g}`,style:Qw,children:[i.jsxs("div",{style:Rw,children:[i.jsxs("span",{style:ek,children:[n(m?"layout_editor.action_recipe.conditions.branch_when":"layout_editor.action_recipe.conditions.branch_else")," ",g+1]}),i.jsxs("span",{style:{display:"flex",gap:4},children:[i.jsx("button",{type:"button","data-testid":`${s}-branch-${g}-up`,onClick:()=>p(g,g-1),disabled:g===0,style:As,title:n("layout_editor.action_recipe.conditions.move_up"),children:"↑"}),i.jsx("button",{type:"button","data-testid":`${s}-branch-${g}-down`,onClick:()=>p(g,g+1),disabled:g===e.length-1,style:As,title:n("layout_editor.action_recipe.conditions.move_down"),children:"↓"}),i.jsx("button",{type:"button","data-testid":`${s}-branch-${g}-remove`,onClick:()=>c(g),style:As,title:n("layout_editor.action_recipe.conditions.remove_branch"),children:"✕"})]})]}),i.jsxs("div",{style:{display:"flex",flexDirection:"column",gap:3},children:[i.jsx("span",{style:Jc,children:n("layout_editor.action_recipe.conditions.param_if")}),i.jsx(Ve,{value:m,onChange:v=>d(g,{...y,if:v}),t:n,candidates:o.bindingCandidates,testidPrefix:`${s}-branch-${g}-if`})]}),i.jsxs("div",{style:{display:"flex",flexDirection:"column",gap:3},children:[i.jsx("span",{style:Jc,children:n("layout_editor.action_recipe.conditions.param_then")}),r(h,v=>d(g,{...y,then:l(v)}),`${s}-branch-${g}-then`)]})]},g)}),i.jsxs("button",{type:"button","data-testid":`${s}-add-branch`,onClick:f,style:tk,children:["+ ",n("layout_editor.action_recipe.conditions.add_branch")]})]})}const Qw={border:"1px solid #e2e8f0",borderRadius:6,padding:8,display:"flex",flexDirection:"column",gap:6,background:"#f8fafc"},Rw={display:"flex",justifyContent:"space-between",alignItems:"center"},ek={fontSize:12,fontWeight:700,color:"#475569"},Jc={fontSize:11,color:"#64748b"},As={border:"1px solid #cbd5e1",background:"#fff",borderRadius:4,width:22,height:22,cursor:"pointer",fontSize:12,lineHeight:"1",color:"#475569"},tk={border:"1px dashed #94a3b8",background:"#fff",borderRadius:6,padding:"6px 10px",cursor:"pointer",fontSize:12,color:"#475569",alignSelf:"flex-start"};function nk({value:e,onChange:t,t:n,candidates:o,testIdPrefix:r,renderKeyInput:s}){const a=e&&typeof e=="object"&&!Array.isArray(e)?Object.entries(e).map(([l,d])=>({key:l,value:typeof d=="string"?d:d==null?"":String(d)})):[];return i.jsx(zr,{value:a,onChange:l=>{const d={};for(const c of l)c.key.trim()!==""&&(d[c.key]=c.value??"");t(Object.keys(d).length===0?void 0:d)},keyField:"key",valueField:"value",t:n,candidates:o,testidPrefix:r,keyPlaceholder:n("layout_editor.action_param.key_placeholder"),valuePlaceholder:n("layout_editor.action_param.value_placeholder"),addLabel:n("layout_editor.action_param.add_pair"),renderKeyInput:s})}function ok({value:e,onChange:t,t:n,candidates:o,stateKeyCandidates:r,param:s,siblingTarget:a,testIdPrefix:l}){const d=e&&typeof e=="object"&&!Array.isArray(e)?Object.entries(e):[],c=typeof a=="string"?a:"";if((c.startsWith("_local.")||c.startsWith("global.")||c.startsWith("$parent._local.")||c.startsWith("$root._local."))&&d.length===1&&d[0][0]==="value"){const b=d[0][1];return i.jsx("div",{"data-testid":`${l}-path-value`,style:{display:"flex",flexDirection:"column",gap:4},children:i.jsx(Nn,{value:b,onChange:x=>t(x===void 0?void 0:{value:x}),t:n,path:"value",scope:`${l}-path-value-val`,renderStringLeaf:x=>i.jsx(Ve,{value:x.value,onChange:x.onChange,t:n,candidates:o,testidPrefix:x.testidPrefix})})})}const y=(b,x)=>{const k={};for(const[w,_]of d)k[w]=w===b?x:_;t(Object.keys(k).length===0?void 0:k)},g=b=>{const x={};for(const[k,w]of d)k!==b&&(x[k]=w);t(Object.keys(x).length===0?void 0:x)},[m,h]=u.useState(""),v=()=>{const b=m.trim();if(b===""||d.some(([k])=>k===b))return;const x={};for(const[k,w]of d)x[k]=w;x[b]="",t(x),h("")};return i.jsxs("div",{"data-testid":l,style:{display:"flex",flexDirection:"column",gap:6},children:[d.map(([b,x])=>i.jsxs("div",{"data-testid":`${l}-row-${b}`,style:{display:"flex",gap:6,alignItems:"flex-start",border:"1px solid #e2e8f0",borderRadius:8,padding:8},children:[i.jsxs("div",{style:{flex:1,minWidth:0,display:"flex",flexDirection:"column",gap:6},children:[i.jsx("div",{style:{minWidth:0},children:i.jsx(Es,{control:{...s},value:b,onChange:k=>{const w=k==null?"":String(k);if(w===""||w===b||d.some(([S])=>S===w))return;const _={};for(const[S,E]of d)_[S===b?w:S]=E;t(_)},t:n,candidates:r})}),i.jsx("div",{style:{minWidth:0},children:i.jsx(Nn,{value:x,onChange:k=>y(b,k),t:n,path:b,scope:`${l}-val`,renderStringLeaf:k=>i.jsx(Ve,{value:k.value,onChange:k.onChange,t:n,candidates:o,testidPrefix:k.testidPrefix})})})]}),i.jsx("button",{type:"button","data-testid":`${l}-remove-${b}`,onClick:()=>g(b),title:n("layout_editor.page_settings.seo.remove"),"aria-label":n("layout_editor.page_settings.seo.remove"),style:{flex:"0 0 auto",padding:"4px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#64748b",cursor:"pointer"},children:"✕"})]},b)),i.jsxs("div",{style:{display:"flex",gap:6,alignItems:"center"},children:[i.jsx("div",{style:{flex:1,minWidth:0},children:i.jsx(Es,{control:{...s},value:m,onChange:b=>h(b==null?"":String(b)),t:n,candidates:r})}),i.jsxs("button",{type:"button","data-testid":`${l}-add`,onClick:v,style:{flex:"0 0 auto",padding:"5px 10px",fontSize:12,border:"1px dashed #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#475569",cursor:"pointer"},children:["+ ",n("layout_editor.action_param.add_pair")]})]})]})}function Ts({raw:e,recipe:t,values:n,t:o,pools:r,onChange:s,testIdPrefix:a,renderActionList:l}){const d=(c,p)=>{const f={...n,[c]:p},y=xr(t,f),g=as(e);s(g?jd(y,g):y)};return i.jsx(i.Fragment,{children:t.params.filter(c=>rk(c,n)).map(c=>i.jsx(Jw,{param:c,value:n[c.key],t:o,pools:r,onChange:p=>d(c.key,p),testIdPrefix:a,renderActionList:l,siblingValues:n},c.key))})}function rk(e,t){const n=e.dependsOn;return!n||typeof n!="object"||typeof n.param!="string"?!0:t[n.param]===n.equals}function ik({raw:e,t,onChange:n,testIdPrefix:o,candidates:r}){const[s,a]=u.useState(!1);return i.jsxs("div",{"data-testid":`${o}-if`,style:dk,children:[i.jsxs("button",{type:"button",onClick:()=>a(l=>!l),style:ck,children:["▸ ",t("layout_editor.init_actions.if_label")]}),s?i.jsx("div",{"data-testid":`${o}-if-input`,style:{minWidth:0},children:i.jsx(Ve,{value:as(e)??"",onChange:l=>n(jd(e,l)),t,candidates:r,placeholder:t("layout_editor.init_actions.if_placeholder"),testidPrefix:`${o}-if-chip`})}):null]})}const Zc={display:"flex",alignItems:"center",gap:8,minWidth:0},oo={fontSize:11,color:"#64748b",minWidth:80},sk={display:"flex",alignItems:"center",gap:8,padding:"4px 0"},ak={fontSize:11,color:"#64748b",minWidth:80},lk={fontSize:11,color:"#92400e"},dk={display:"flex",flexDirection:"column",gap:4},ck={textAlign:"left",border:"none",background:"transparent",color:"#475569",cursor:"pointer",fontSize:11,padding:0};function uk(e,t,n){if(!t)return n("layout_editor.action.source_core");const o=e?.[t],r=o&&typeof o=="object"&&o.__source?o.__source:null;return!r||r.kind==="core"?n("layout_editor.action.source_core"):r.id??n(`layout_editor.action.source_${r.kind}`)}function pk(e,t,n){const o=Yn(e,t);if(o.kind==="preset"){const r=t.find(s=>s.id===o.recipeId);return Wd(r??null,o.values,s=>n(s.startsWith("$t:")?s.slice(3):s))}return typeof e.handler=="string"?e.handler:n("layout_editor.action_list.unknown")}function Gt({actions:e,onChange:t,t:n,recipes:o,chipContext:r,actionChipCandidates:s,addContext:a="init",renderAddPicker:l,renderEditor:d,candidatePools:c,showSourceBadge:p=!1,canDrag:f,canDropAt:y,isLocked:g,hideAddPicker:m=!1,testIdPrefix:h="g7le-action-list"}){const v=u.useMemo(()=>wo(o),[o]),b=u.useMemo(()=>{if(r==null||r==="init")return c;const O=jl(r,n,s??null);if(O.length===0)return c;const M=c??{},B=new Set((M.bindingCandidates??[]).map(H=>H.expression)),T=O.filter(H=>!B.has(H.expression));return T.length===0?c:{...M,bindingCandidates:[...T,...M.bindingCandidates??[]]}},[r,s,c,n]),[x,k]=u.useState(null),[w,_]=u.useState(null),S=u.useCallback((O,M)=>{if(O===M||O<0||M<0||O>=e.length||M>=e.length)return;const B=e.slice(),[T]=B.splice(O,1);B.splice(M,0,T),t(B)},[e,t]),E=fs({length:e.length,onMove:S,canDrag:f,canDropAt:y}),C=u.useCallback(O=>{t(e.filter((M,B)=>B!==O))},[e,t]),N=u.useCallback((O,M)=>{const B=e.slice();B[O]=M,t(B)},[e,t]),$=u.useCallback(O=>{t([...e,O])},[e,t]),A=u.useCallback((O,M)=>{const B=Yn(O,v),T=B.kind==="preset"?v.find(L=>L.id===B.recipeId)??null:null;if(!T)return null;const H=B.kind==="preset"?B.values:{};return i.jsxs("div",{style:yk,children:[i.jsx(Ts,{raw:O,recipe:T,values:H,t:n,pools:b??{},onChange:M,testIdPrefix:`${h}-edit`,renderActionList:(L,I,P,F)=>{const G=F==="onSuccess"?"response":F==="onError"?"error":void 0;return i.jsx(Gt,{actions:L,onChange:I,t:n,recipes:o,chipContext:G,actionChipCandidates:G?s:void 0,candidatePools:G?c:b,addContext:a,showSourceBadge:p,testIdPrefix:P})}}),i.jsx(ik,{raw:O,t:n,onChange:M,testIdPrefix:`${h}-edit`,candidates:b?.bindingCandidates})]})},[v,n,b,c,s,h,o,a,p]),z=d??(o?A:void 0),D=m?null:l?l($):o?i.jsx(dw,{recipes:o,t:n,onAdd:$,context:a,testIdPrefix:`${h}-add-picker`}):null;return i.jsxs("div",{className:h,"data-testid":h,children:[i.jsxs("ul",{style:{listStyle:"none",margin:0,padding:0,display:"flex",flexDirection:"column",gap:6},children:[e.map((O,M)=>{const B=Yn(O,v),T=B.kind==="preset"?B.recipeId:null,H=T?v.find(G=>G.id===T)??null:null,L=H===null,I=g?.(M)??!1,P=as(O)!==null,F=z?z(O,G=>N(M,G)):null;return i.jsxs(u.Fragment,{children:[I?null:i.jsx("li",{"aria-hidden":"true",style:{listStyle:"none",margin:0,padding:0},children:i.jsx(Rn,{active:E.isDropTarget(M),testid:`${h}-dropline-${M}`})}),i.jsxs("li",{"data-testid":`${h}-item-${M}`,"data-drag-source":E.dragIndex===M?"true":void 0,style:{border:"1px solid #e2e8f0",borderRadius:8,padding:"8px 10px",...E.dragIndex===M?{opacity:.5}:{}},onDragOver:E.dragging&&!I?G=>{G.preventDefault();const V=G.currentTarget.getBoundingClientRect();E.onDragOverItem(M,G.clientY{G.preventDefault(),E.onDrop()}:void 0,children:[i.jsxs("div",{style:{display:"flex",alignItems:"center",gap:8},children:[I?i.jsx("span",{style:{fontSize:12},children:"🔒"}):i.jsx("span",{"data-testid":`${h}-drag-${M}`,style:{color:"#94a3b8",cursor:"grab",fontSize:14,userSelect:"none"},draggable:!0,onDragStart:()=>E.onDragStart(M),onDragEnd:E.onDragEnd,"aria-label":n("layout_editor.action_list.drag_reorder"),role:"button",children:"⠿"}),i.jsx("span",{style:{flex:1,minWidth:0,fontSize:13},"data-testid":`${h}-summary-${M}`,children:pk(O,v,n)}),p?i.jsxs("span",{"data-testid":`${h}-source-${M}`,style:fk,children:["〔",uk(o,T,n),"〕"]}):null,P?i.jsxs("span",{"data-testid":`${h}-cond-badge-${M}`,style:gk,children:["〔",n("layout_editor.init_actions.cond_badge"),"〕"]}):null,L?i.jsxs("span",{"data-testid":`${h}-advanced-${M}`,style:hk,children:["[",n("layout_editor.action.advanced"),"]"]}):null,!I&&F?i.jsxs("button",{type:"button","data-testid":`${h}-edit-${M}`,onClick:()=>_(w===M?null:M),"aria-label":n("layout_editor.init_actions.edit"),title:n("layout_editor.init_actions.edit"),style:w===M?bk:Qc,children:["✎ ",n("layout_editor.init_actions.edit")]}):null,i.jsx("button",{type:"button","data-testid":`${h}-code-${M}`,onClick:()=>k(x===M?null:M),"aria-label":n("layout_editor.action_list.view_code"),title:n("layout_editor.action_list.view_code"),style:x===M?mk:Ds,children:""}),I?null:i.jsx("button",{type:"button","data-testid":`${h}-remove-${M}`,onClick:()=>C(M),"aria-label":n("layout_editor.action_list.remove"),title:n("layout_editor.action_list.remove"),style:vk,children:"✕"})]}),H?i.jsx(xw,{action:O,recipe:H,values:B.kind==="preset"?B.values:{},t:n,showCodeButton:!1,testIdPrefix:`${h}-preview-${M}`}):null,w===M&&!I&&F?i.jsx("div",{"data-testid":`${h}-edit-body-${M}`,children:F}):null,d&&w!==M?d(O,G=>N(M,G)):null,x===M?i.jsx("pre",{"data-testid":`${h}-code-view-${M}`,style:{margin:"8px 0 0",padding:8,background:"#0f172a",color:"#e2e8f0",borderRadius:6,fontSize:12,overflow:"auto"},children:JSON.stringify(O,null,2)}):null]})]},M)}),i.jsx("li",{"aria-hidden":"true",style:{listStyle:"none",margin:0,padding:0},children:i.jsx(Rn,{active:E.isDropTarget(e.length),testid:`${h}-dropline-end`})})]}),D?i.jsx("div",{style:{marginTop:8},children:D}):null]})}const fk={fontSize:10,color:"#64748b",whiteSpace:"nowrap"},gk={fontSize:10,color:"#7c3aed",whiteSpace:"nowrap"},hk={fontSize:10,color:"#92400e",whiteSpace:"nowrap"},yk={display:"flex",flexDirection:"column",gap:6,paddingTop:6,borderTop:"1px dashed #e2e8f0",marginTop:6},Qc={flex:"0 0 auto",padding:"3px 8px",fontSize:11,fontWeight:500,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer",whiteSpace:"nowrap",lineHeight:1.4},bk={...Qc,background:"#eff6ff",borderColor:"#bfdbfe",color:"#2563eb"},Ds={flex:"0 0 auto",width:26,height:24,display:"inline-flex",alignItems:"center",justifyContent:"center",fontSize:11,border:"1px solid #e2e8f0",borderRadius:6,background:"#fff",color:"#64748b",cursor:"pointer",fontFamily:"ui-monospace, monospace"},mk={...Ds,background:"#0f172a",borderColor:"#0f172a",color:"#e2e8f0"},vk={...Ds,color:"#dc2626",borderColor:"#fecaca",fontFamily:"inherit"};function Rc(e){const t=e.__source;return t&&typeof t=="object"?t:null}function _k({actions:e,onChange:t,recipes:n,t:o,bindingCandidates:r,pageCandidates:s,dataSourceCandidates:a,stateKeyCandidates:l,modalCandidates:d}){const c=u.useMemo(()=>({pageCandidates:s,dataSourceCandidates:a,stateKeyCandidates:l,modalCandidates:d,bindingCandidates:r}),[s,a,l,d,r]),p=u.useMemo(()=>e.filter(h=>Rc(h)?.kind==="base"),[e]),f=u.useMemo(()=>e.filter(h=>Rc(h)?.kind!=="base"),[e]),y=p.length>0,g=u.useCallback(h=>{t(y?[...p,...h]:h)},[t,y,p]),m=u.useCallback(()=>{},[]);return i.jsxs("div",{className:"g7le-init-actions-form","data-testid":"g7le-init-actions-form",style:xk,children:[i.jsx("p",{style:wk,children:o("layout_editor.page_settings.init_actions.heading")}),y?i.jsxs(i.Fragment,{children:[i.jsxs("div",{"data-testid":"g7le-init-action-group-base",style:eu,children:[i.jsx("div",{style:tu,children:o("layout_editor.init_actions.group_base")}),i.jsx(Gt,{actions:p,onChange:m,t:o,recipes:n,candidatePools:c,addContext:"init",showSourceBadge:!0,isLocked:()=>!0,hideAddPicker:!0,testIdPrefix:"g7le-init-action-base"}),i.jsxs("p",{"data-testid":"g7le-init-action-base-hint",style:kk,children:["ⓘ ",o("layout_editor.init_actions.base_readonly_hint")]})]}),i.jsxs("div",{"data-testid":"g7le-init-action-group-self",style:eu,children:[i.jsx("div",{style:tu,children:o("layout_editor.init_actions.group_self")}),f.length===0?i.jsx("p",{style:nu,children:o("layout_editor.init_actions.empty_self")}):null,i.jsx(Gt,{actions:f,onChange:g,t:o,recipes:n,candidatePools:c,addContext:"init",showSourceBadge:!0,testIdPrefix:"g7le-init-action-self"})]}),i.jsxs("p",{"data-testid":"g7le-init-action-merge-order",style:Sk,children:["ⓘ ",o("layout_editor.init_actions.merge_order")]})]}):i.jsxs(i.Fragment,{children:[e.length===0?i.jsx("p",{"data-testid":"g7le-init-action-empty",style:nu,children:o("layout_editor.init_actions.empty")}):null,i.jsx(Gt,{actions:e,onChange:t,t:o,recipes:n,candidatePools:c,addContext:"init",showSourceBadge:!0,testIdPrefix:"g7le-init-action"})]})]})}const xk={display:"flex",flexDirection:"column",gap:12,minWidth:0},wk={margin:0,fontSize:13,fontWeight:700,color:"#0f172a"},eu={border:"1px solid #e2e8f0",borderRadius:10,padding:10,display:"flex",flexDirection:"column",gap:8},tu={fontSize:12,fontWeight:700,color:"#475569"},kk={margin:0,fontSize:11,color:"#64748b"},nu={margin:0,fontSize:12,color:"#94a3b8"},Sk={margin:0,fontSize:11,color:"#64748b"};function Ck(e){return e==="spinner"?new Set(["spinner","page"]):new Set(["skeleton"])}function ou(e,t,n){return typeof e!="string"?n:e.startsWith("$t:")?t(e.slice(3)):e}function ru({candidates:e,value:t,onSelect:n,styleKind:o,t:r,testIdPrefix:s="g7le-loading-component-picker"}){const a=yo(),l=()=>{const d=a.open({ariaLabel:r("layout_editor.overlay.loading_picker.select"),width:520,maxHeightRatio:.7,content:i.jsx($k,{candidates:e,value:t,styleKind:o,t:r,testIdPrefix:s,onPick:c=>{n(c),a.close(d)},onClose:()=>a.close(d)})})};return i.jsx("div",{className:s,"data-testid":s,style:{minWidth:0},children:i.jsxs("div",{style:Ek,children:[i.jsx("span",{"data-testid":`${s}-current`,style:Ik,children:t&&t.length>0?t:r("layout_editor.overlay.loading_picker.none")}),i.jsxs("button",{type:"button","data-testid":`${s}-open`,onClick:l,style:Ak,children:["🔍 ",r("layout_editor.overlay.loading_picker.select")]})]})})}function $k({candidates:e,value:t,styleKind:n,t:o,testIdPrefix:r,onPick:s,onClose:a}){const[l,d]=u.useState(""),c=Ck(n),p=u.useMemo(()=>{const f=l.trim().toLowerCase();return e.filter(y=>c.has(y.role)).filter(y=>f?ou(y.label,o,y.name).toLowerCase().includes(f)||y.name.toLowerCase().includes(f):!0)},[e,n,l,o]);return i.jsxs("div",{"data-testid":`${r}-modal`,style:Tk,children:[i.jsxs("div",{style:Dk,children:[i.jsxs("span",{style:Nk,children:["🔍 ",o("layout_editor.overlay.loading_picker.select")]}),i.jsx("button",{type:"button","data-testid":`${r}-close`,onClick:a,"aria-label":o("layout_editor.overlay.loading_picker.select"),style:zk,children:"✕"})]}),i.jsx("input",{type:"text","data-testid":`${r}-search`,value:l,placeholder:o("layout_editor.overlay.loading_picker.search"),onChange:f=>d(f.target.value),style:Mk,autoFocus:!0}),p.length===0?i.jsx("p",{"data-testid":`${r}-empty`,style:Hk,children:o("layout_editor.overlay.loading_picker.empty")}):i.jsx("ul",{style:Lk,children:p.map(f=>i.jsx("li",{children:i.jsxs("button",{type:"button","data-testid":`${r}-item-${f.name}`,onClick:()=>s(f.name),style:{...Pk,...t===f.name?Ok:{}},children:[i.jsx("span",{style:Bk,children:ou(f.label,o,f.name)}),i.jsx("code",{style:Wk,children:f.name}),i.jsx("span",{style:jk,children:f.role})]})},f.name))})]})}const Ek={display:"flex",alignItems:"center",gap:6,minWidth:0},Ik={flex:1,minWidth:0,fontSize:12,color:"#0f172a",fontFamily:"monospace"},Ak={padding:"4px 10px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#475569",cursor:"pointer",whiteSpace:"nowrap"},Tk={display:"flex",flexDirection:"column",minHeight:0,padding:16,boxSizing:"border-box",fontFamily:"system-ui, -apple-system, sans-serif"},Dk={display:"flex",alignItems:"center",gap:8,paddingBottom:8,borderBottom:"1px solid #e2e8f0",marginBottom:10},Nk={flex:1,fontSize:14,fontWeight:700,color:"#0f172a",minWidth:0},zk={border:"none",background:"transparent",color:"#64748b",cursor:"pointer",fontSize:15,lineHeight:1,padding:4},Mk={width:"100%",padding:"6px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,marginBottom:8,boxSizing:"border-box"},Lk={listStyle:"none",margin:0,padding:0,display:"flex",flexDirection:"column",gap:2,overflowY:"auto",minHeight:0},Pk={display:"flex",alignItems:"center",gap:6,width:"100%",textAlign:"left",padding:"8px 10px",fontSize:12,border:"1px solid transparent",background:"transparent",cursor:"pointer",borderRadius:6},Ok={border:"1px solid #3b82f6",background:"#eff6ff"},Bk={flex:1,minWidth:0,color:"#0f172a"},Wk={fontSize:10,color:"#94a3b8",fontFamily:"monospace"},jk={fontSize:10,color:"#64748b",background:"#f1f5f9",borderRadius:4,padding:"0 4px"},Hk={margin:0,fontSize:12,color:"#94a3b8",padding:"8px 4px"};function iu(e){return e===!0?{enabled:!0}:e===!1||e===void 0?{}:e}const Fk=["opaque","blur","fade","skeleton","spinner"];function Kk({value:e,patch:t,t:n,baseValue:o,ownValue:r,loadingComponents:s=[],progressiveDataSources:a=[],bindingCandidates:l}){const d=iu(e),c=d.enabled??!1,p=!!o&&Object.keys(o).length>0,f=Tn("component-target-picker"),[y,g]=u.useState((d.wait_for??[]).length>0),m=u.useCallback(w=>{const _={...iu(e)};w(_),t(_)},[e,t]),h=r&&typeof r=="object"?r:e&&typeof e=="object"?e:{},v=u.useCallback(w=>p&&o!==void 0&&w in o&&!(w in h),[p,o,h]),b=u.useCallback(w=>{o&&m(_=>{_[w]=o[w]})},[o,m]),x=u.useCallback(w=>m(_=>{_.enabled=w}),[m]),k=d.target!==void 0?"region":"full";return i.jsxs("div",{className:"g7le-overlay-form","data-testid":"g7le-overlay-form",style:Vk,children:[p?i.jsxs("p",{"data-testid":"g7le-overlay-inherit-banner",style:Uk,children:["ⓘ ",n("layout_editor.overlay.inherit_banner")]}):null,i.jsxs("div",{style:Br,children:[i.jsx(Ns,{label:n("layout_editor.overlay.enabled_label"),inherited:v("enabled"),hasInheritance:p,onOverride:()=>b("enabled"),testidKey:"enabled",t:n}),i.jsx(Nr,{disabled:v("enabled"),children:i.jsx(Dr,{checked:c,onChange:x,testid:"g7le-overlay-enabled",ariaLabel:n("layout_editor.overlay.enabled_label"),label:n(c?"layout_editor.overlay.on":"layout_editor.overlay.off")})}),i.jsxs("p",{style:Wr,children:["ⓘ ",n("layout_editor.overlay.enabled_hint")]})]}),i.jsx(Nr,{disabled:!c,testid:"g7le-overlay-body",style:{display:"flex",flexDirection:"column",gap:16,minWidth:0},children:i.jsxs(i.Fragment,{children:[i.jsxs("div",{style:Br,children:[i.jsx(Ns,{label:n("layout_editor.overlay.scope_label"),inherited:v("target"),hasInheritance:p,onOverride:()=>b("target"),testidKey:"target",t:n}),i.jsxs("div",{"data-testid":"g7le-overlay-scope",style:zs,children:[i.jsx(Or,{active:k==="full",onClick:()=>m(w=>{delete w.target,delete w.fallback_target}),testid:"g7le-overlay-scope-full",children:n("layout_editor.overlay.scope_full")}),i.jsx(Or,{active:k==="region",onClick:()=>m(w=>{w.target=w.target??""}),testid:"g7le-overlay-scope-region",children:n("layout_editor.overlay.scope_region")})]}),i.jsx(Nr,{disabled:k!=="region",testid:"g7le-overlay-region-box",style:Xk,children:i.jsxs(i.Fragment,{children:[i.jsx(zn,{label:n("layout_editor.overlay.target_label"),children:i.jsx("div",{"data-testid":"g7le-overlay-target-picker",children:f?i.jsx(f,{control:{label:n("layout_editor.overlay.target_label")},value:d.target??"",onChange:w=>m(_=>{_.target=typeof w=="string"?w:""}),t:n}):i.jsx("input",{type:"text","data-testid":"g7le-overlay-target-input",value:d.target??"",onChange:w=>m(_=>{_.target=w.target.value}),style:Qk})})}),i.jsx(zn,{label:n("layout_editor.overlay.fallback_label"),children:i.jsx("div",{"data-testid":"g7le-overlay-fallback-picker",children:f?i.jsx(f,{control:{label:n("layout_editor.overlay.fallback_label")},value:d.fallback_target??"",onChange:w=>m(_=>{const S=typeof w=="string"?w:"";S===""?delete _.fallback_target:_.fallback_target=S}),t:n}):null})}),i.jsxs("p",{style:Wr,children:["ⓘ ",n("layout_editor.overlay.fallback_hint")]})]})})]}),i.jsxs("div",{style:Br,children:[i.jsx(Ns,{label:n("layout_editor.overlay.style_label"),inherited:v("style"),hasInheritance:p,onOverride:()=>b("style"),testidKey:"style",t:n}),i.jsx("div",{"data-testid":"g7le-overlay-style",style:zs,children:Fk.map(w=>i.jsx(Or,{active:(d.style??"opaque")===w,onClick:()=>m(_=>{_.style=w}),testid:`g7le-overlay-style-${w}`,children:n(`layout_editor.overlay.style_${w}`)},w))}),d.style==="skeleton"?i.jsxs("div",{style:su,children:[i.jsx(zn,{label:n("layout_editor.overlay.skeleton_component"),children:i.jsx(ru,{candidates:s,value:d.skeleton?.component,styleKind:"skeleton",onSelect:w=>m(_=>{_.skeleton={..._.skeleton,component:w}}),t:n,testIdPrefix:"g7le-overlay-skeleton-component"})}),i.jsx(zn,{label:n("layout_editor.overlay.skeleton_animation"),children:i.jsx("div",{"data-testid":"g7le-overlay-skeleton-anim",style:zs,children:["wave","pulse","none"].map(w=>i.jsx(Or,{active:(d.skeleton?.animation??"wave")===w,onClick:()=>m(_=>{_.skeleton={..._.skeleton,animation:w}}),testid:`g7le-overlay-skeleton-anim-${w}`,children:n(`layout_editor.overlay.anim_${w}`)},w))})}),i.jsx(zn,{label:n("layout_editor.overlay.skeleton_count"),children:i.jsx("input",{type:"number","data-testid":"g7le-overlay-skeleton-count",value:d.skeleton?.iteration_count??"",onChange:w=>m(_=>{const S=parseInt(w.target.value,10);_.skeleton={..._.skeleton,iteration_count:Number.isNaN(S)?void 0:S}}),style:Rk})})]}):null,d.style==="spinner"?i.jsxs("div",{style:su,children:[i.jsx(zn,{label:n("layout_editor.overlay.spinner_component"),children:i.jsx(ru,{candidates:s,value:d.spinner?.component,styleKind:"spinner",onSelect:w=>m(_=>{_.spinner={..._.spinner,component:w}}),t:n,testIdPrefix:"g7le-overlay-spinner-component"})}),i.jsx(zn,{label:n("layout_editor.overlay.spinner_text"),children:i.jsx("div",{"data-testid":"g7le-overlay-spinner-text",style:{minWidth:0},children:i.jsx(vt,{value:d.spinner?.text??"",onChange:w=>m(_=>{_.spinner={..._.spinner,text:w===void 0||w===""?void 0:w}}),t:n,candidates:l,enableExpressionTree:!0,expressionTreeCollapsible:!0,testidPrefix:"g7le-overlay-spinner-text-field"})})})]}):null]}),i.jsxs("div",{style:Br,children:[i.jsxs("button",{type:"button","data-testid":"g7le-overlay-waitfor-toggle","aria-expanded":y,onClick:()=>g(w=>!w),style:n1,children:[y?"▾":"▸"," ",n("layout_editor.overlay.waitfor_label")]}),y?i.jsxs("div",{"data-testid":"g7le-overlay-waitfor-body",style:o1,children:[i.jsxs("p",{"data-testid":"g7le-overlay-waitfor-hint",style:Wr,children:["ⓘ ",n("layout_editor.overlay.waitfor_hint")]}),a.length===0?i.jsx("p",{"data-testid":"g7le-overlay-waitfor-empty",style:Wr,children:n("layout_editor.overlay.waitfor_empty")}):i.jsx("div",{"data-testid":"g7le-overlay-waitfor",style:{display:"flex",flexWrap:"wrap",gap:6},children:a.map(w=>{const _=(d.wait_for??[]).includes(w.id);return i.jsxs("label",{"data-testid":`g7le-overlay-waitfor-${w.id}`,style:e1(_),children:[i.jsx("input",{type:"checkbox",checked:_,onChange:()=>m(S=>{const E=new Set(S.wait_for??[]);E.has(w.id)?E.delete(w.id):E.add(w.id);const C=Array.from(E);C.length===0?delete S.wait_for:S.wait_for=C})}),i.jsx(Dc,{option:w,testIdPrefix:`g7le-overlay-waitfor-${w.id}`})]},w.id)})})]}):null]}),p?i.jsxs("button",{type:"button","data-testid":"g7le-overlay-reset-inherit",onClick:()=>t(void 0),style:t1,children:["⟲ ",n("layout_editor.overlay.reset_inherit")]}):null]})})]})}function Or({active:e,onClick:t,children:n,testid:o}){return i.jsx("button",{type:"button","data-testid":o,"aria-pressed":e,onClick:t,style:{...Gk,...e?Yk:{}},children:n})}function Ns({label:e,inherited:t,hasInheritance:n,onOverride:o,testidKey:r,t:s}){return i.jsxs("div",{style:r1,children:[i.jsx("label",{style:qk,children:e}),n?t?i.jsxs(i.Fragment,{children:[i.jsxs("span",{"data-testid":`g7le-overlay-inherited-${r}`,style:i1,children:["〔",s("layout_editor.overlay.inherited_badge"),"〕"]}),i.jsx("button",{type:"button","data-testid":`g7le-overlay-override-${r}`,onClick:o,style:a1,children:s("layout_editor.overlay.override_field")})]}):i.jsxs("span",{"data-testid":`g7le-overlay-overridden-${r}`,style:s1,children:["〔",s("layout_editor.overlay.overridden_badge"),"〕"]}):null]})}function zn({label:e,children:t}){return i.jsxs("div",{style:Jk,children:[i.jsx("span",{style:Zk,children:e}),i.jsx("div",{style:{flex:1,minWidth:0},children:t})]})}const Vk={display:"flex",flexDirection:"column",gap:16,minWidth:0},Uk={margin:0,fontSize:12,color:"#1d4ed8",background:"#eff6ff",padding:"8px 10px",borderRadius:6},Br={display:"flex",flexDirection:"column",gap:6,minWidth:0},qk={fontSize:13,fontWeight:600,color:"#0f172a"},Wr={margin:0,fontSize:11,color:"#94a3b8"},zs={display:"flex",gap:4,flexWrap:"wrap"},Gk={padding:"4px 10px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},Yk={border:"1px solid #3b82f6",background:"#eff6ff",color:"#1d4ed8"},Xk={display:"flex",flexDirection:"column",gap:8,padding:8,border:"1px dashed #e2e8f0",borderRadius:8},su={display:"flex",flexDirection:"column",gap:8,padding:8,border:"1px dashed #e2e8f0",borderRadius:8},Jk={display:"flex",alignItems:"center",gap:8,minWidth:0},Zk={fontSize:11,color:"#64748b",minWidth:90},Qk={width:"100%",padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,boxSizing:"border-box"},Rk={width:80,padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6},e1=e=>({display:"inline-flex",alignItems:"center",gap:4,padding:"4px 8px",fontSize:12,border:`1px solid ${e?"#3b82f6":"#cbd5e1"}`,borderRadius:12,background:e?"#eff6ff":"#fff",cursor:"pointer"}),t1={alignSelf:"flex-start",padding:"6px 12px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},n1={alignSelf:"flex-start",padding:"4px 0",fontSize:13,fontWeight:600,color:"#0f172a",background:"transparent",border:"none",cursor:"pointer"},o1={display:"flex",flexDirection:"column",gap:6,paddingLeft:14,minWidth:0},r1={display:"flex",alignItems:"center",gap:8,flexWrap:"wrap",minWidth:0},i1={fontSize:11,color:"#64748b",background:"#f1f5f9",borderRadius:4,padding:"1px 6px"},s1={fontSize:11,color:"#0369a1",background:"#e0f2fe",borderRadius:4,padding:"1px 6px"},a1={padding:"2px 8px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},l1="g7le:computed-effect-preview",d1={number:"layout_editor.computed.type_number",string:"layout_editor.computed.type_string",boolean:"layout_editor.computed.type_boolean",list:"layout_editor.computed.type_list",object:"layout_editor.computed.type_object",null:"layout_editor.computed.type_null"};function c1(e,t,n){if(t==="list"){const o=e,r=o.slice(0,2).map(s=>JSON.stringify(s)).join(", ");return o.length<=2?`[${r}]`:`[${r}, ${n("layout_editor.computed.list_more",{count:o.length})}]`}return t==="object"?JSON.stringify(e):t==="boolean"?e?"true":"false":t==="null"?n("layout_editor.computed.value_none"):String(e)}function au({expr:e,computedKey:t,sampleContext:n,t:o,isEffect:r=!1,testIdPrefix:s="g7le-computed-preview"}){const a=Id(e,n),l=()=>{typeof window>"u"||!t||window.dispatchEvent(new CustomEvent(l1,{detail:{key:t}}))};return a.ok?i.jsxs("div",{className:s,"data-testid":s,style:u1,children:[i.jsx("span",{style:p1,children:o("layout_editor.computed.preview_label")}),i.jsx("span",{"data-testid":`${s}-value`,style:f1,children:c1(a.value,a.type,o)}),i.jsxs("span",{"data-testid":`${s}-type`,style:g1,children:["(",o(d1[a.type]),")"]}),r&&a.type==="boolean"?i.jsxs("button",{type:"button","data-testid":`${s}-effect`,onClick:l,style:h1,children:["▷ ",o("layout_editor.computed.effect_preview")]}):null]}):i.jsxs("div",{"data-testid":`${s}-error`,style:y1,children:["⚠ ",o("layout_editor.computed.preview_error")]})}const u1={display:"flex",alignItems:"center",flexWrap:"wrap",gap:6,minWidth:0},p1={fontSize:11,color:"#94a3b8"},f1={fontSize:12,color:"#0f172a",fontFamily:"monospace",wordBreak:"break-all"},g1={fontSize:10,color:"#64748b"},h1={padding:"2px 8px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#475569",cursor:"pointer"},y1={fontSize:12,color:"#b45309"},b1=[{op:"count",labelKey:"layout_editor.computed.op_count"},{op:"sum",labelKey:"layout_editor.computed.op_sum"},{op:"filter",labelKey:"layout_editor.computed.op_filter"},{op:"toOptions",labelKey:"layout_editor.computed.op_to_options"},{op:"nth",labelKey:"layout_editor.computed.op_nth"},{op:"firstOf",labelKey:"layout_editor.computed.op_first_of"},{op:"literal",labelKey:"layout_editor.computed.op_literal"}],m1=["=","!=",">","<",">=","<=","includes"],v1=new Set(["count","sum","filter"]);function lu({model:e,onChange:t,t:n,dataSourceCandidates:o,bindingCandidates:r,existingKeys:s=[]}){const a=u.useCallback(g=>{const m={...e};g(m),t(m)},[e,t]),l=e.conditions??[],d=Tn("datasource-picker"),c=e.key!==""&&s.includes(e.key),p=()=>a(g=>{g.conditions=[...g.conditions??[],{field:"",cmp:"=",value:""}]}),f=(g,m)=>a(h=>{const v=(h.conditions??[]).slice(),b={...v[g]};m(b),v[g]=b,h.conditions=v}),y=g=>a(m=>{m.conditions=(m.conditions??[]).filter((h,v)=>v!==g)});return i.jsxs("div",{className:"g7le-computed-custom","data-testid":"g7le-computed-custom",style:_1,children:[i.jsx(Ms,{label:`① ${n("layout_editor.computed.custom_step1")}`,children:i.jsx("div",{"data-testid":"g7le-computed-custom-source",children:d?i.jsx(d,{control:{},value:e.source,onChange:g=>a(m=>{m.source=typeof g=="string"?g:void 0}),t:n,candidates:o}):i.jsx("input",{type:"text",value:e.source??"",onChange:g=>a(m=>{m.source=g.target.value}),style:ro})})}),i.jsxs(Ms,{label:`② ${n("layout_editor.computed.custom_step2")}`,children:[i.jsx("select",{"data-testid":"g7le-computed-custom-op",value:e.op,onChange:g=>a(m=>{m.op=g.target.value}),style:ro,children:b1.map(({op:g,labelKey:m})=>i.jsx("option",{value:g,children:n(m)},g))}),e.op==="sum"?i.jsx("div",{"data-testid":"g7le-computed-custom-sumfield",style:{minWidth:0},children:i.jsx(Ve,{value:e.sumField??"",onChange:g=>a(m=>{m.sumField=g}),t:n,candidates:r,placeholder:n("layout_editor.computed.custom_sum_field"),testidPrefix:"g7le-computed-custom-sumfield-chip"})}):null,e.op==="toOptions"?i.jsxs("div",{style:{display:"flex",gap:6},children:[i.jsx("div",{"data-testid":"g7le-computed-custom-valuefield",style:{flex:1,minWidth:0},children:i.jsx(Ve,{value:e.valueField??"",onChange:g=>a(m=>{m.valueField=g}),t:n,candidates:r,placeholder:n("layout_editor.computed.custom_value_field"),testidPrefix:"g7le-computed-custom-valuefield-chip"})}),i.jsx("div",{"data-testid":"g7le-computed-custom-labelfield",style:{flex:1,minWidth:0},children:i.jsx(Ve,{value:e.labelField??"",onChange:g=>a(m=>{m.labelField=g}),t:n,candidates:r,placeholder:n("layout_editor.computed.custom_label_field"),testidPrefix:"g7le-computed-custom-labelfield-chip"})})]}):null,e.op==="nth"?i.jsxs("div",{style:{display:"flex",gap:6},children:[i.jsx("input",{type:"text","data-testid":"g7le-computed-custom-index",value:e.index??"",placeholder:"0",onChange:g=>a(m=>{m.index=g.target.value}),style:ro}),i.jsx("div",{"data-testid":"g7le-computed-custom-prop",style:{flex:1,minWidth:0},children:i.jsx(Ve,{value:e.prop??"",onChange:g=>a(m=>{m.prop=g}),t:n,candidates:r,placeholder:n("layout_editor.computed.custom_prop"),testidPrefix:"g7le-computed-custom-prop-chip"})})]}):null,e.op==="literal"?i.jsxs("div",{style:{display:"flex",gap:6},children:[i.jsxs("select",{"data-testid":"g7le-computed-custom-literalkind",value:e.literalKind??"string",onChange:g=>a(m=>{m.literalKind=g.target.value}),style:ro,children:[i.jsx("option",{value:"string",children:n("layout_editor.computed.kind_string")}),i.jsx("option",{value:"number",children:n("layout_editor.computed.kind_number")}),i.jsx("option",{value:"boolean",children:n("layout_editor.computed.kind_boolean")})]}),i.jsx("div",{"data-testid":"g7le-computed-custom-literalvalue",style:{flex:1,minWidth:0},children:i.jsx(Ve,{value:e.literalValue??"",onChange:g=>a(m=>{m.literalValue=g}),t:n,candidates:r,testidPrefix:"g7le-computed-custom-literalvalue-chip"})})]}):null,e.op==="firstOf"?i.jsxs("div",{style:{display:"flex",flexDirection:"column",gap:6},children:[i.jsx("input",{type:"text","data-testid":"g7le-computed-custom-candidates",value:(e.candidates??[]).join(", "),placeholder:n("layout_editor.computed.custom_candidates"),onChange:g=>a(m=>{m.candidates=g.target.value.split(",").map(h=>h.trim()).filter(Boolean)}),style:ro}),i.jsx("div",{"data-testid":"g7le-computed-custom-fallback",style:{minWidth:0},children:i.jsx(Ve,{value:e.fallback??"",onChange:g=>a(m=>{m.fallback=g}),t:n,candidates:r,placeholder:n("layout_editor.computed.custom_fallback"),testidPrefix:"g7le-computed-custom-fallback-chip"})})]}):null,v1.has(e.op)?i.jsxs("div",{style:S1,children:[l.map((g,m)=>i.jsxs("div",{"data-testid":`g7le-computed-custom-cond-${m}`,style:C1,children:[i.jsx("div",{"data-testid":`g7le-computed-custom-cond-field-${m}`,style:{flex:1,minWidth:0},children:i.jsx(Ve,{value:g.field,onChange:h=>f(m,v=>{v.field=h}),t:n,candidates:r,placeholder:n("layout_editor.computed.cond_field"),testidPrefix:`g7le-computed-custom-cond-field-${m}-chip`})}),i.jsx("select",{value:g.cmp,onChange:h=>f(m,v=>{v.cmp=h.target.value}),style:$1,children:m1.map(h=>i.jsx("option",{value:h,children:h},h))}),i.jsx("div",{"data-testid":`g7le-computed-custom-cond-value-${m}`,style:{flex:1,minWidth:0},children:i.jsx(Ve,{value:g.value,onChange:h=>f(m,v=>{v.value=h}),t:n,candidates:r,placeholder:n("layout_editor.computed.cond_value"),testidPrefix:`g7le-computed-custom-cond-value-${m}-chip`})}),i.jsx("button",{type:"button","data-testid":`g7le-computed-custom-cond-remove-${m}`,onClick:()=>y(m),style:E1,children:"✕"})]},m)),i.jsxs("button",{type:"button","data-testid":"g7le-computed-custom-cond-add",onClick:p,style:I1,children:["+ ",n("layout_editor.computed.cond_add")]})]}):null]}),i.jsxs(Ms,{label:`③ ${n("layout_editor.computed.custom_step3")}`,children:[i.jsx("input",{type:"text","data-testid":"g7le-computed-custom-key",value:e.key,onChange:g=>a(m=>{m.key=g.target.value}),style:{...ro,...c?k1:{}}}),c?i.jsxs("p",{"data-testid":"g7le-computed-custom-key-dup",style:A1,children:["⚠ ",n("layout_editor.computed.key_duplicate")]}):null]})]})}function Ms({label:e,children:t}){return i.jsxs("div",{style:x1,children:[i.jsx("div",{style:w1,children:e}),i.jsx("div",{style:{display:"flex",flexDirection:"column",gap:6,minWidth:0},children:t})]})}function Ls(e){return Fm(e)}const _1={display:"flex",flexDirection:"column",gap:12,padding:10,border:"1px solid #cbd5e1",borderRadius:8,background:"#fff"},x1={display:"flex",flexDirection:"column",gap:4,minWidth:0},w1={fontSize:12,fontWeight:600,color:"#475569"},ro={padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,minWidth:0,width:"100%",boxSizing:"border-box"},k1={border:"1px solid #ef4444"},S1={display:"flex",flexDirection:"column",gap:6,marginTop:4},C1={display:"flex",gap:4,alignItems:"center",minWidth:0},$1={padding:"4px 6px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6},E1={border:"none",background:"transparent",color:"#94a3b8",cursor:"pointer",fontSize:12},I1={alignSelf:"flex-start",padding:"2px 8px",fontSize:11,border:"1px dashed #94a3b8",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},A1={margin:0,fontSize:11,color:"#b45309"};function Ps(e,t,n){return typeof e!="string"?n:e.startsWith("$t:")?t(e.slice(3)):e}function T1(e){if(e&&typeof e=="object"){const t=e.__source;if(t&&typeof t=="object")return t}return null}function D1({computed:e,onChange:t,recipes:n,t:o,sampleContext:r,dataSourceCandidates:s,bindingCandidates:a,computedSource:l={},effectKeys:d=[],onRevert:c}){const p=u.useMemo(()=>xd(n),[n]),[f,y]=u.useState(!1),[g,m]=u.useState(null),h=Object.keys(e),v=u.useCallback((_,S)=>{t({...e,[_]:S})},[e,t]),b=u.useCallback(_=>{const S={...e};delete S[_],t(S)},[e,t]),x=u.useCallback((_,S,E)=>{const C={...e};_&&_!==S&&delete C[_],C[S]=E,t(C)},[e,t]),k=u.useCallback(_=>{const S=kd(_,{});let E=_.id,C=1;for(;E in e;)E=`${_.id}${++C}`;t({...e,[E]:S}),y(!1)},[e,t]),w=u.useCallback(()=>{!g||!g.key||(t({...e,[g.key]:Ls(g)}),m(null),y(!1))},[g,e,t]);return i.jsxs("div",{className:"g7le-computed-form","data-testid":"g7le-computed-form",style:L1,children:[i.jsx("p",{style:P1,children:o("layout_editor.page_settings.computed.heading")}),h.length>0?h.map(_=>i.jsx(N1,{computedKey:_,expr:e[_],recipes:p,rawRecipes:n,t:o,sampleContext:r,dataSourceCandidates:s,bindingCandidates:a,source:l[_],isEffect:d.includes(_),existingKeys:h,onPatchExpr:S=>v(_,S),onRename:(S,E)=>x(_,S,E),onRemove:()=>b(_),onRevert:c?()=>c(_):void 0},_)):i.jsx("p",{"data-testid":"g7le-computed-empty",style:iS,children:o("layout_editor.computed.empty")}),i.jsxs("div",{style:{minWidth:0},children:[i.jsxs("button",{type:"button","data-testid":"g7le-computed-add",onClick:()=>y(_=>!_),style:Z1,children:["+ ",o("layout_editor.computed.add")," ▾"]}),f?i.jsxs("div",{"data-testid":"g7le-computed-add-list",style:Q1,children:[["common","more"].map(_=>{const S=p.filter(E=>(E.group??"more")===_);return S.length===0?null:i.jsxs("div",{children:[i.jsxs("div",{style:R1,children:["━━ ",o(`layout_editor.computed.group_${_}`)," ━━"]}),S.map(E=>{const C=T1(n?.[E.id]);return i.jsxs("button",{type:"button","data-testid":`g7le-computed-preset-${E.id}`,onClick:()=>k(E),style:eS,children:[i.jsx("span",{style:tS,children:Ps(E.label,o,E.id)}),C&&C.kind!=="core"?i.jsxs("span",{"data-testid":`g7le-computed-preset-source-${E.id}`,style:nS,children:["〔",C.id??C.kind,"〕"]}):null]},E.id)})]},_)}),i.jsxs("div",{style:{borderTop:"1px solid #e2e8f0",marginTop:6,paddingTop:6},children:[i.jsxs("button",{type:"button","data-testid":"g7le-computed-custom-open",onClick:()=>m(g?null:{key:"",op:"count",source:"",conditions:[]}),style:oS,children:["⚙ ",o("layout_editor.computed.custom_open")," ▾"]}),g?i.jsxs("div",{style:{marginTop:6},children:[i.jsx(lu,{model:g,onChange:m,t:o,dataSourceCandidates:s,bindingCandidates:a,existingKeys:h}),i.jsx("div",{"data-testid":"g7le-computed-custom-preview",style:{marginTop:6},children:i.jsx(au,{expr:Ls(g),sampleContext:r,t:o,testIdPrefix:"g7le-computed-custom-preview-eval"})}),i.jsx("button",{type:"button","data-testid":"g7le-computed-custom-commit",disabled:!g.key||h.includes(g.key),onClick:w,style:rS,children:o("layout_editor.computed.custom_add")})]}):null]})]}):null]})]})}function N1({computedKey:e,expr:t,recipes:n,rawRecipes:o,t:r,sampleContext:s,dataSourceCandidates:a,bindingCandidates:l,source:d,isEffect:c,existingKeys:p,onPatchExpr:f,onRename:y,onRemove:g,onRevert:m}){const[h,v]=u.useState(!1),[b,x]=u.useState(!1),k=u.useMemo(()=>Ed(t,n),[t,n]),w=d==="base",_=d==="route-override",S=k.kind==="advanced",E=k.kind==="preset"?n.find($=>$.id===k.recipeId)??null:null,C=E?Ps(E.label,r,e):e,N=($,A)=>{if(k.kind!=="preset"||!E)return;const z={...k.params,[$]:A};f(kd(E,z))};return i.jsxs("div",{"data-testid":`g7le-computed-item-${e}`,style:O1,children:[i.jsxs("div",{style:B1,children:[i.jsx("span",{style:W1,children:C}),i.jsx("code",{style:j1,children:e}),w?i.jsxs("span",{"data-testid":`g7le-computed-source-${e}`,style:H1,children:["〔",r("layout_editor.computed.source_base"),"〕"]}):null,_?i.jsxs("span",{"data-testid":`g7le-computed-overridden-${e}`,style:F1,children:["〔",r("layout_editor.computed.source_overridden"),"〕"]}):null,S?i.jsxs("span",{"data-testid":"g7le-computed-advanced",style:K1,children:["[",r("layout_editor.action.advanced"),"]"]}):null,i.jsx("span",{style:{flex:1}}),i.jsx("button",{type:"button","data-testid":`g7le-computed-code-${e}`,onClick:()=>x($=>!$),style:Os,"aria-label":r("layout_editor.value_tree.show_source"),title:r("layout_editor.value_tree.show_source"),children:""}),S?null:i.jsx("button",{type:"button","data-testid":`g7le-computed-edit-${e}`,onClick:()=>v($=>!$),style:Os,children:r("layout_editor.init_actions.edit")}),i.jsx("button",{type:"button","data-testid":`g7le-computed-remove-${e}`,onClick:g,style:Os,"aria-label":r("layout_editor.init_actions.remove"),children:"✕"})]}),b?i.jsx("pre",{"data-testid":`g7le-computed-code-block-${e}`,style:sS,children:t}):null,i.jsx("div",{"data-testid":`g7le-computed-preview-${e}`,children:i.jsx(au,{expr:t,computedKey:e,sampleContext:s,t:r,isEffect:c,testIdPrefix:`g7le-computed-preview-${e}-eval`})}),_&&m?i.jsxs("div",{style:V1,children:[i.jsxs("span",{style:U1,children:["ⓘ ",r("layout_editor.computed.overridden_hint")]}),i.jsx("button",{type:"button","data-testid":`g7le-computed-revert-${e}`,onClick:m,style:q1,children:r("layout_editor.computed.revert")})]}):null,h&&!S?i.jsxs("div",{style:G1,children:[w?i.jsxs("p",{"data-testid":`g7le-computed-override-notice-${e}`,style:Y1,children:["⚠ ",r("layout_editor.computed.override_notice")]}):null,k.kind==="preset"&&E?i.jsx(i.Fragment,{children:E.params.map($=>i.jsx(M1,{param:$,value:k.params[$.key]??"",t:r,dataSourceCandidates:a,bindingCandidates:l,onChange:A=>N($.key,A)},$.key))}):k.kind==="custom"?i.jsx(lu,{model:{...k.model,key:e},onChange:$=>y($.key,Ls($)),t:r,dataSourceCandidates:a,bindingCandidates:l,existingKeys:p.filter($=>$!==e)}):null]}):null]})}const z1=new Set(["datasource-picker","binding-picker","select","toggle","state-key-picker","page-picker"]);function M1({param:e,value:t,t:n,dataSourceCandidates:o,bindingCandidates:r,onChange:s}){const a=Ps(e.label,n,e.key),l=e.widget??"",c=z1.has(l)?Tn(e.widget):null,p=l==="datasource-picker"||l==="binding-picker"?o:void 0;return i.jsxs("div",{"data-testid":`g7le-computed-param-${e.key}`,style:X1,children:[i.jsx("span",{style:J1,children:a}),i.jsx("div",{style:{flex:1,minWidth:0},children:c?i.jsx(c,{control:{...e},value:t,onChange:f=>s(typeof f=="string"?f:""),t:n,candidates:p}):i.jsx(Ve,{value:t,onChange:f=>s(f),t:n,candidates:r,testidPrefix:`g7le-computed-param-${e.key}-chip`})})]})}const L1={display:"flex",flexDirection:"column",gap:12,minWidth:0},P1={margin:0,fontSize:13,fontWeight:700,color:"#0f172a"},O1={border:"1px solid #e2e8f0",borderRadius:8,padding:"8px 10px",display:"flex",flexDirection:"column",gap:6,background:"#f8fafc"},B1={display:"flex",alignItems:"center",gap:6},W1={fontSize:13,fontWeight:600,color:"#0f172a"},j1={fontSize:10,color:"#64748b",fontFamily:"monospace"},H1={fontSize:10,color:"#0891b2",whiteSpace:"nowrap"},F1={fontSize:10,color:"#7c3aed",whiteSpace:"nowrap"},K1={fontSize:10,color:"#92400e",whiteSpace:"nowrap"},V1={display:"flex",alignItems:"center",gap:8,marginTop:4,minWidth:0},U1={fontSize:11,color:"#7c3aed",flex:1,minWidth:0},q1={border:"1px solid #c4b5fd",borderRadius:6,background:"#f5f3ff",color:"#6d28d9",cursor:"pointer",fontSize:11,padding:"2px 8px",whiteSpace:"nowrap"},Os={border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer",fontSize:11,padding:"2px 6px"},G1={display:"flex",flexDirection:"column",gap:6,paddingTop:6,borderTop:"1px dashed #e2e8f0"},Y1={margin:0,fontSize:11,color:"#b45309",background:"#fef3c7",padding:"6px 8px",borderRadius:6},X1={display:"flex",alignItems:"center",gap:8,minWidth:0},J1={fontSize:11,color:"#64748b",minWidth:90},Z1={padding:"6px 12px",fontSize:12,border:"1px dashed #94a3b8",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},Q1={marginTop:6,border:"1px solid #cbd5e1",borderRadius:8,background:"#fff",padding:8,maxHeight:380,overflowY:"auto"},R1={fontSize:11,fontWeight:700,color:"#94a3b8",padding:"6px 4px 2px"},eS={display:"flex",alignItems:"center",gap:6,width:"100%",textAlign:"left",padding:"6px 8px",fontSize:12,border:"none",background:"transparent",cursor:"pointer",borderRadius:6},tS={flex:1,minWidth:0,color:"#0f172a"},nS={fontSize:10,color:"#64748b",whiteSpace:"nowrap"},oS={padding:"6px 10px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#475569",cursor:"pointer"},rS={marginTop:6,padding:"6px 12px",fontSize:12,border:"1px solid #3b82f6",borderRadius:6,background:"#3b82f6",color:"#fff",cursor:"pointer"},iS={margin:0,fontSize:12,color:"#94a3b8"},sS={margin:"2px 0 0",padding:"6px 8px",fontSize:11,fontFamily:"monospace",color:"#0f172a",background:"#f1f5f9",border:"1px solid #e2e8f0",borderRadius:6,whiteSpace:"pre-wrap",wordBreak:"break-all",overflowX:"auto"};function aS(e,t){return t==="any"?!0:e===null||typeof e!="object"?!1:t==="object"?!Array.isArray(e):!0}function Bs({value:e,onChange:t,onValidityChange:n,t:o,shape:r="any",emptyValue:s=void 0,placeholder:a,minHeight:l=80,testidPrefix:d="g7le-json-block",shapeErrorKey:c,invalidErrorKey:p,ariaLabel:f,id:y,validate:g}){const[m,h]=u.useState(()=>jr(e)),[v,b]=u.useState(null),x=u.useRef(!1),k=u.useRef(e);u.useEffect(()=>{if(!x.current&&!Object.is(e,k.current)){if(jr(e)===jr(k.current)){k.current=e;return}h(jr(e)),b(null),k.current=e,n?.(!0)}},[e]);const w=_=>{if(x.current=!0,h(_),_.trim()===""){b(null),k.current=s,n?.(!0),t(s);return}let S;try{S=JSON.parse(_)}catch{b(o(p??"layout_editor.json_block.invalid")),n?.(!1);return}if(!aS(S,r)){b(o(c??"layout_editor.json_block.invalid")),n?.(!1);return}const E=g?.(S)??null;if(E){b(E),n?.(!1);return}b(null),k.current=S,n?.(!0),t(S)};return i.jsxs("div",{className:"g7le-json-block",style:lS,children:[i.jsx("textarea",{id:y,"data-testid":d,style:{...dS,minHeight:l},value:m,spellCheck:!1,"aria-label":f,"aria-invalid":v?!0:void 0,placeholder:a,onChange:_=>w(_.target.value),onBlur:()=>{x.current=!1}}),v?i.jsx("div",{"data-testid":`${d}-error`,style:cS,children:v}):null]})}function jr(e){if(e===void 0)return"";try{return JSON.stringify(e,null,2)??""}catch{return""}}const lS={display:"flex",flexDirection:"column",gap:4,minWidth:0},dS={width:"100%",boxSizing:"border-box",fontFamily:"ui-monospace, SFMono-Regular, Menlo, monospace",fontSize:12,lineHeight:1.5,color:"#0f172a",background:"#fff",border:"1px solid #cbd5e1",borderRadius:6,padding:"6px 8px",resize:"vertical"},cS={fontSize:11,color:"#dc2626"},uS=[{kind:"string",labelKey:"layout_editor.initstate.kind_string"},{kind:"number",labelKey:"layout_editor.initstate.kind_number"},{kind:"boolean",labelKey:"layout_editor.initstate.kind_boolean"},{kind:"null",labelKey:"layout_editor.initstate.kind_null"},{kind:"list",labelKey:"layout_editor.initstate.kind_list"},{kind:"object",labelKey:"layout_editor.initstate.kind_object"}],Ws={local:"initLocal",global:"initGlobal",isolated:"initIsolated"};function pS({raw:e,own:t,patch:n,t:o,isolatedOrphan:r={}}){const s=u.useMemo(()=>{const f=t?.state&&typeof t.state=="object"?t.state:void 0,y=e?.state&&typeof e.state=="object"?e.state:void 0;if(!(!f&&!y))return{...y??{},...f??{}}},[t,e]),{initLocal:a,migrated:l}=u.useMemo(()=>Mw({initLocal:e?.initLocal,state:s}),[e,s]),d=f=>{if(f==="local")return a;const y=e?.[Ws[f]];return y&&typeof y=="object"?y:{}},c=f=>{if(f==="local"){const y=t?.initLocal??{},g=s?Object.keys(s):[];return Array.from(new Set([...Object.keys(y),...g]))}return Object.keys(t?.[Ws[f]]??{})},p=u.useCallback((f,y)=>{const g=Ws[f];f==="local"&&l&&n("state",void 0),n(g,Object.keys(y).length===0?void 0:y)},[n,l]);return i.jsxs("div",{className:"g7le-initstate-form","data-testid":"g7le-initstate-form",style:gS,children:[i.jsx("p",{style:hS,children:o("layout_editor.page_settings.initstate.heading")}),l?i.jsxs("p",{"data-testid":"g7le-initstate-legacy-state",style:yS,children:["ⓘ ",o("layout_editor.initstate.legacy_state")]}):null,i.jsx(js,{scope:"local",label:o("layout_editor.initstate.section_local"),value:d("local"),ownKeys:c("local"),patchSection:p,t:o}),i.jsx(js,{scope:"global",label:o("layout_editor.initstate.section_global"),value:d("global"),ownKeys:c("global"),patchSection:p,t:o}),i.jsx(js,{scope:"isolated",label:o("layout_editor.initstate.section_isolated"),description:o("layout_editor.initstate.section_isolated_desc"),value:d("isolated"),ownKeys:c("isolated"),patchSection:p,t:o,orphan:r}),i.jsxs("p",{style:MS,children:["ⓘ ",o("layout_editor.initstate.isolated_dynamic_hint")]})]})}function js({scope:e,label:t,description:n,value:o,ownKeys:r,patchSection:s,t:a,orphan:l={}}){const[d,c]=u.useState(""),[p,f]=u.useState("string"),[y,g]=u.useState(null),[m,h]=u.useState(!1),v=Object.keys(o),b=v,x=(_,S)=>{s(e,{...o,[_]:S})},k=_=>{const S={...o};delete S[_],s(e,S)},w=()=>{const _=d.trim();if(!_){g(a("layout_editor.initstate.name_required"));return}if(!Is(_)){g(a("layout_editor.initstate.name_invalid"));return}if(_ in o){g(a("layout_editor.initstate.name_duplicate"));return}const S={string:"",number:0,boolean:!1,null:null,list:[],object:{}};s(e,{...o,[_]:S[p]}),c(""),g(null)};return i.jsxs("div",{"data-testid":`g7le-initstate-section-${e}`,style:bS,children:[i.jsxs("div",{style:mS,children:[i.jsx("div",{style:vS,children:t}),i.jsx("button",{type:"button","data-testid":`g7le-initstate-${e}-mode-toggle`,onClick:()=>h(_=>!_),style:xS,children:a(m?"layout_editor.json_block.mode_block":"layout_editor.json_block.mode_code")})]}),n?i.jsxs("p",{style:_S,children:["ⓘ ",n]}):null,m?i.jsx(Bs,{value:o,shape:"object",emptyValue:{},onChange:_=>s(e,_??{}),t:a,testidPrefix:`g7le-initstate-${e}-code`,placeholder:`{ +var dM=Object.defineProperty;var cM=(dn,i,u)=>i in dn?dM(dn,i,{enumerable:!0,configurable:!0,writable:!0,value:u}):dn[i]=u;var Ti=(dn,i,u)=>cM(dn,typeof i!="symbol"?i+"":i,u);(function(dn,i,u,Cn){"use strict";function Lt(){const e=typeof window<"u"?window.G7Core?.__runtime:void 0;if(!e)throw new Error("[layout-editor] window.G7Core.__runtime 이 없습니다. 코어 번들(template-engine.min.js)이 편집기 번들보다 먼저 로드되어야 합니다.");return e}const Di=Lt();Di.TranslationProvider;const tt=Di.useTranslation;Di.TranslationReactContext;function rt(){return typeof window>"u"?null:window.__g7Devtools??null}function Sl(e,t){const n=rt();if(!n?.trackEditorState)return;const o={templateIdentifier:e.templateIdentifier,editMode:e.editMode,selectedRoutePath:e.selectedRoute?.path??null,selectedLayoutName:e.selectedRoute?.layoutName??null,routeTreeSize:e.routeTree.length,isRouteTreeCollapsed:e.isRouteTreeCollapsed,locale:e.locale,previewDevice:e.previewDevice,previewZoom:e.previewZoom,lastAction:t,timestamp:Date.now()};try{n.trackEditorState(o)}catch{}}function Vh(){const e=rt();if(e?.clearEditorStateData)try{e.clearEditorStateData()}catch{}}function Uh(e){const t=rt();if(t?.trackEditorSampleMatch)try{t.trackEditorSampleMatch(e)}catch{}}function qh(){const e=rt();if(e?.clearEditorSampleData)try{e.clearEditorSampleData()}catch{}}function Gh(e){const t=rt();if(t?.trackEditorHistoryEntry)try{t.trackEditorHistoryEntry(e)}catch{}}function Yh(){const e=rt();if(e?.clearEditorHistoryData)try{e.clearEditorHistoryData()}catch{}}function Cl(e){const t=rt();if(t?.trackEditorSelection)try{t.trackEditorSelection(e)}catch{}}function Xh(){const e=rt();if(e?.clearEditorSelectionData)try{e.clearEditorSelectionData()}catch{}}function sr(e){const t=rt();if(t?.trackEditorPropertyPatch)try{t.trackEditorPropertyPatch(e)}catch{}}function $n(e){const t=rt();if(t?.trackEditorDnd)try{t.trackEditorDnd(e)}catch{}}function Jh(){const e=rt();if(e?.clearEditorDndData)try{e.clearEditorDndData()}catch{}}function st(e){const t=rt();if(t?.trackEditorDocument)try{t.trackEditorDocument(e)}catch{}}function Zh(){const e=rt();if(e?.clearEditorDocumentData)try{e.clearEditorDocumentData()}catch{}}function Qh(e){const t=rt();if(t?.trackEditorSpecMerge)try{t.trackEditorSpecMerge(e)}catch{}}function Rh(){const e=rt();if(e?.clearEditorSpecMergeData)try{e.clearEditorSpecMergeData()}catch{}}function Ni(e){let t=5381;for(let n=0;n>>0;return t}function Et(e){const t=rt();if(t?.trackEditorI18n)try{t.trackEditorI18n(e)}catch{}}function ey(){const e=rt();if(e?.clearEditorI18nData)try{e.clearEditorI18nData()}catch{}}function $l(e){const t=rt();if(t?.trackPageState)try{t.trackPageState(e)}catch{}}function ty(){const e=rt();if(e?.clearPageStateData)try{e.clearPageStateData()}catch{}}const El="g7le.routeTree.collapsed";function ny(){if(typeof window>"u")return!1;try{return window.localStorage?.getItem(El)==="1"}catch{return!1}}function oy(e){if(!(typeof window>"u"))try{window.localStorage?.setItem(El,e?"1":"0")}catch{}}function ry(e,t){return{templateIdentifier:e,routeTree:[],routesError:null,selectedRoute:null,isRouteTreeCollapsed:ny(),editMode:"route",returnRoute:null,returnEditMode:null,locale:t,previewDevice:"desktop",previewZoom:1,previewCustomWidth:1024,previewColorScheme:"light",history:{canUndo:!1,canRedo:!1},isPaletteOpen:!1,availableStates:[],activeStateId:null,layoutVersions:{},extensionVersions:{}}}function iy(e,t){switch(t.type){case"SET_ROUTE_TREE":return{...e,routeTree:t.tree,routesError:null};case"SET_ROUTES_ERROR":return{...e,routesError:t.error};case"SELECT_ROUTE":return{...e,editMode:"route",selectedRoute:t.route,returnRoute:null,returnEditMode:null};case"TOGGLE_ROUTE_TREE":return{...e,isRouteTreeCollapsed:!e.isRouteTreeCollapsed};case"SET_LOCALE":return{...e,locale:t.locale};case"ENTER_BASE_EDIT":return{...e,editMode:"base",returnRoute:t.returnRoute!==void 0?t.returnRoute:e.selectedRoute,selectedRoute:{path:`__base__/${t.layoutName}`,layoutName:t.layoutName}};case"EXIT_BASE_EDIT":return{...e,editMode:"route",selectedRoute:e.returnRoute,returnRoute:null};case"ENTER_MODAL_EDIT":return{...e,editMode:"modal",returnRoute:t.returnRoute!==void 0?t.returnRoute:e.selectedRoute,selectedRoute:{path:`__modal__/${t.modalId}`,layoutName:t.hostLayout}};case"EXIT_MODAL_EDIT":return{...e,editMode:"route",selectedRoute:e.returnRoute,returnRoute:null};case"ENTER_EXTENSION_EDIT":return{...e,editMode:"extension",returnRoute:t.returnRoute!==void 0?t.returnRoute:e.selectedRoute,selectedRoute:{path:`__extension__/${t.extensionId}`,layoutName:t.extensionHost??null}};case"SET_RETURN_ROUTE":return e.editMode==="route"||e.returnRoute!==null?e:{...e,returnRoute:t.route};case"EXIT_EXTENSION_EDIT":return{...e,editMode:"route",selectedRoute:e.returnRoute,returnRoute:null};case"ENTER_ITERATION_ITEM_EDIT":return{...e,editMode:"iteration_item",returnRoute:t.returnRoute!==void 0?t.returnRoute:e.selectedRoute,returnEditMode:e.editMode,selectedRoute:{path:`__iteration__/${t.sourcePath}`,layoutName:t.hostLayout??e.selectedRoute?.layoutName??null}};case"EXIT_ITERATION_ITEM_EDIT":return{...e,editMode:e.returnEditMode??"route",selectedRoute:e.returnRoute,returnRoute:null,returnEditMode:null};case"SET_PREVIEW_DEVICE":return{...e,previewDevice:t.device};case"SET_PREVIEW_ZOOM":return{...e,previewZoom:t.zoom};case"SET_PREVIEW_CUSTOM_WIDTH":return{...e,previewCustomWidth:t.width};case"SET_PREVIEW_COLOR_SCHEME":return{...e,previewColorScheme:t.scheme};case"TOGGLE_PALETTE":return{...e,isPaletteOpen:!e.isPaletteOpen};case"SET_PALETTE_OPEN":return{...e,isPaletteOpen:t.open};case"SET_AVAILABLE_STATES":return{...e,availableStates:t.states,activeStateId:t.activeStateId};case"SET_ACTIVE_STATE":return{...e,activeStateId:t.activeStateId};case"SET_LAYOUT_VERSIONS":return{...e,layoutVersions:t.versions};case"SET_LAYOUT_VERSION":return{...e,layoutVersions:{...e.layoutVersions,[t.layoutName]:t.version}};case"SET_EXTENSION_VERSIONS":return{...e,extensionVersions:t.versions};case"SET_EXTENSION_VERSION":return{...e,extensionVersions:{...e.extensionVersions,[t.extensionId]:t.version}};default:return e}}function sy(e,t){const n=iy(e,t);return((n.selectedRoute?.path??null)!==(e.selectedRoute?.path??null)||n.editMode!==e.editMode)&&(n.availableStates.length>0||n.activeStateId!==null)?{...n,availableStates:[],activeStateId:null}:n}const Il=u.createContext(null);function ay(e,t){const n=sy(e,t);return Sl(n,t.type),n}function ly({templateIdentifier:e,initialLocale:t,children:n}){const[o,r]=u.useReducer(ay,void 0,()=>{const a=ry(e,t);return Sl(a,void 0),a});u.useEffect(()=>{oy(o.isRouteTreeCollapsed)},[o.isRouteTreeCollapsed]);const s=u.useMemo(()=>({state:o,dispatch:r}),[o]);return u.createElement(Il.Provider,{value:s},n)}function Je(){const e=u.useContext(Il);if(!e)throw new Error("useLayoutEditor must be used within LayoutEditorProvider");return e}const Al=u.createContext(null);function dy({value:e,children:t}){return u.createElement(Al.Provider,{value:e},t)}function Ft(){return u.useContext(Al)}const zi=u.createContext(null);let cy=1;function uy({children:e,t}){const[n,o]=u.useState([]),r=u.useRef([]),[s,a]=u.useState({}),l=u.useCallback(b=>{const x=b.id??`editor-modal-${cy++}`,k={id:x,content:b.content,ariaLabel:b.ariaLabel??"",closeOnBackdrop:b.closeOnBackdrop??!0,closeOnEscape:b.closeOnEscape??!0,width:b.width??"auto",height:b.height??"auto",maxHeightRatio:b.maxHeightRatio??.85,draggable:b.draggable??!1,avoidRect:b.avoidRect,onClose:b.onClose};return o(w=>{const C=[...w.filter(E=>E.id!==x),k];return r.current=C,C}),x},[]),d=u.useCallback(b=>{o(x=>{const k=x.find(_=>_.id===b),w=x.filter(_=>_.id!==b);return r.current=w,k?.onClose&&queueMicrotask(()=>k.onClose?.()),w})},[]),c=u.useCallback(()=>{const b=r.current[r.current.length-1];b&&d(b.id)},[d]),p=u.useCallback(()=>{o(b=>{for(let x=b.length-1;x>=0;x--){const k=b[x];k?.onClose&&queueMicrotask(()=>k.onClose?.())}return r.current=[],[]})},[]),f=u.useCallback((b,x)=>{a(k=>{const w=r.current.find(C=>C.id===b),_=x??w?.ariaLabel??"";return{...k,[b]:_}})},[]),y=u.useCallback(b=>{a(x=>{if(!(b in x))return x;const k={...x};return delete k[b],k})},[]),g=u.useCallback(b=>{y(b),d(b)},[y,d]),m=u.useCallback(()=>{a({}),p()},[p]),h=u.useMemo(()=>Object.keys(s),[s]),v=u.useMemo(()=>({open:l,close:g,closeTop:c,closeAll:m,stack:n,minimize:f,restore:y,minimizedStack:h}),[l,g,c,m,n,f,y,h]);return u.createElement(zi.Provider,{value:v},e,u.createElement(fy,{minimized:s,onRestore:y,t,key:"__min_bar"}))}function yo(){const e=u.useContext(zi);if(!e)throw new Error("useEditorModal must be used within EditorModalProvider");return e}function py(){const e=u.useContext(zi);if(!e)return null;const{stack:t,close:n,minimizedStack:o}=e,r=u.useMemo(()=>new Set(o),[o]);return u.useEffect(()=>{if(t.length===0)return;const s=a=>{if(a.key!=="Escape"&&a.key!=="Esc")return;const l=t.filter(c=>!r.has(c.id)),d=l[l.length-1];d?.closeOnEscape&&(a.preventDefault(),a.stopPropagation(),n(d.id))};return window.addEventListener("keydown",s,!0),()=>window.removeEventListener("keydown",s,!0)},[t,n,r]),t.length===0?null:i.jsx(i.Fragment,{children:t.map((s,a)=>i.jsx(hy,{entry:s,depth:a,hidden:r.has(s.id),onClose:()=>n(s.id)},s.id))})}function fy({minimized:e,onRestore:t,t:n}){const o=n??(s=>s),r=Object.keys(e);return r.length===0?null:i.jsx("div",{className:"g7le-minimized-bar","data-testid":"g7le-minimized-bar",style:{position:"fixed",left:16,bottom:16,display:"flex",gap:8,zIndex:9300},children:r.map(s=>i.jsxs("button",{type:"button","data-testid":`g7le-minimized-${s}`,onClick:()=>t(s),style:{display:"flex",alignItems:"center",gap:6,padding:"8px 12px",background:"#1e293b",color:"#fff",border:"none",borderRadius:8,boxShadow:"0 8px 24px rgba(15, 23, 42, 0.3)",cursor:"pointer",fontSize:13},children:[i.jsx("span",{"aria-hidden":"true",children:"🗗"}),i.jsx("span",{children:e[s]||o("layout_editor.modal.minimized_default")}),i.jsx("span",{style:{opacity:.7},children:o("layout_editor.modal.restore")})]},s))})}function gy(e,t,n){if(!e||typeof window>"u")return null;const o=window.innerWidth,r=window.innerHeight,s=16,a=e.left,l=o-(e.left+e.width),d=t+s;let c;if(l>=a&&l>=d)c=e.left+e.width+s;else if(a>=d)c=Math.max(s,e.left-s-t);else return null;c=Math.min(Math.max(s,c),Math.max(s,o-t-s));let p=e.top;return p=Math.min(Math.max(s,p),Math.max(s,r-n-s)),{left:c,top:p}}const Tl="clamp(16px, 6vh, 56px)";function hy({entry:e,depth:t,hidden:n,onClose:o}){const r=9100+t*10,s=e.maxHeightRatio>0?`${e.maxHeightRatio*100}vh`:void 0,a=e.draggable?s:`calc(100vh - (${Tl}) * 2)`,[l,d]=u.useState(null),c=u.useRef(null),p=u.useRef(null);u.useEffect(()=>{if(!e.draggable)return;const v=c.current;if(!v)return;const b=v.getBoundingClientRect(),x=gy(e.avoidRect,b.width,b.height);x&&d(x)},[e.id,e.draggable]);const f=u.useCallback(v=>{if(!e.draggable)return;const b=v.target;if(!b.closest("[data-modal-drag-handle]")||b.closest("button, a, input, textarea, select"))return;const k=c.current,w=k?.getBoundingClientRect(),_=l?.left??w?.left??0,C=l?.top??w?.top??0;p.current={startX:v.clientX,startY:v.clientY,baseLeft:_,baseTop:C};const E=T=>{const $=p.current;if(!$)return;const A=window.innerWidth,z=window.innerHeight,D=k?.offsetWidth??0,O=k?.offsetHeight??0;let M=$.baseLeft+(T.clientX-$.startX),B=$.baseTop+(T.clientY-$.startY);M=Math.min(Math.max(0,M),Math.max(0,A-D)),B=Math.min(Math.max(0,B),Math.max(0,z-O)),d({left:M,top:B})},S=()=>{p.current=null,window.removeEventListener("pointermove",E),window.removeEventListener("pointerup",S)};window.addEventListener("pointermove",E),window.addEventListener("pointerup",S)},[e.draggable,l]),y=v=>{e.closeOnBackdrop&&v.target===v.currentTarget&&o()},g=e.draggable&&l!==null,m=e.draggable?{position:"fixed",inset:0,background:"transparent",pointerEvents:"none",zIndex:r,...n?{display:"none"}:{}}:{position:"fixed",inset:0,background:"rgba(15, 23, 42, 0.45)",display:n?"none":"flex",alignItems:"flex-start",justifyContent:"center",paddingTop:Tl,boxSizing:"border-box",zIndex:r},h={background:"#fff",borderRadius:10,boxShadow:"0 20px 60px rgba(15, 23, 42, 0.25)",width:e.width,height:e.height,maxHeight:a,maxWidth:"92vw",overflow:"hidden",display:"flex",flexDirection:"column",...e.draggable?{position:"absolute",pointerEvents:"auto",left:g?l.left:"50%",top:g?l.top:"50%",transform:g?void 0:"translate(-50%, -50%)"}:{}};return i.jsx("div",{className:"g7le-modal-backdrop",role:"presentation","data-testid":`g7le-modal-backdrop-${e.id}`,"data-draggable":e.draggable?"true":"false",onClick:y,style:m,children:i.jsx("div",{ref:c,className:"g7le-modal",role:"dialog","aria-modal":"true","aria-label":e.ariaLabel||void 0,"data-testid":`g7le-modal-${e.id}`,"data-positioned":g?"true":"false",onClick:v=>v.stopPropagation(),onPointerDown:e.draggable?f:void 0,style:h,children:e.content})})}function wt({baseStyle:e,hoverStyle:t,disabled:n,style:o,children:r,...s}){const[a,l]=u.useState(!1),d={outline:"none",...e,...o,...a&&!n?t:{}};return i.jsx("button",{type:"button",disabled:n,onMouseEnter:()=>l(!0),onMouseLeave:()=>l(!1),style:d,...s,children:r})}function yy({templateIdentifier:e,templateName:t,templateVersion:n,templateList:o,onSwitch:r}){const{t:s}=tt(),[a,l]=u.useState(!1),[d,c]=u.useState(!1),p=u.useRef(null);u.useEffect(()=>{if(!a)return;const v=x=>{p.current&&!p.current.contains(x.target)&&l(!1)},b=x=>{x.key==="Escape"&&l(!1)};return document.addEventListener("mousedown",v),document.addEventListener("keydown",b),()=>{document.removeEventListener("mousedown",v),document.removeEventListener("keydown",b)}},[a]);const f=o??[],y=f.some(v=>v.identifier!==e),g=!!r&&y,m=t||e,h=v=>{if(v===e){l(!1);return}l(!1),r?.(v)};return i.jsxs("div",{ref:p,className:"g7le-toolbar__template-switcher",style:{position:"relative",display:"inline-flex"},children:[i.jsxs("button",{type:"button",className:"g7le-toolbar__template","data-testid":"g7le-toolbar-template-label","data-can-switch":g?"true":"false","aria-haspopup":g?"listbox":void 0,"aria-expanded":g?a:void 0,disabled:!g,onClick:()=>g&&l(v=>!v),onMouseEnter:()=>c(!0),onMouseLeave:()=>c(!1),title:g?s("layout_editor.chrome.toolbar.switch_template"):m,style:{display:"inline-flex",alignItems:"center",gap:6,padding:"4px 8px",fontSize:13,fontWeight:600,color:"#0f172a",letterSpacing:.1,borderRadius:6,borderWidth:1,borderStyle:"solid",borderColor:g&&(d||a)?"#cbd5e1":"transparent",backgroundColor:g&&(d||a)?"#f1f5f9":"transparent",cursor:g?"pointer":"default",outline:"none",transition:"background-color 120ms, border-color 120ms"},children:[i.jsx("span",{className:"g7le-toolbar__template-name","data-testid":"g7le-toolbar-template-name",children:m}),n&&i.jsxs("span",{className:"g7le-toolbar__template-version","data-testid":"g7le-toolbar-template-version",style:{fontSize:11,fontWeight:500,color:"#64748b",padding:"1px 6px",borderRadius:999,background:"#f1f5f9",border:"1px solid #e2e8f0"},children:["v",n]}),g&&i.jsx("span",{className:"g7le-toolbar__template-caret","aria-hidden":"true",style:{fontSize:9,color:"#94a3b8",marginLeft:1},children:"▼"})]}),g&&a&&i.jsx("div",{className:"g7le-toolbar__template-menu","data-testid":"g7le-toolbar-template-menu",role:"listbox",style:{position:"absolute",top:"calc(100% + 4px)",left:0,minWidth:240,maxWidth:360,maxHeight:360,overflowY:"auto",background:"#ffffff",border:"1px solid #e2e8f0",borderRadius:8,boxShadow:"0 8px 24px rgba(15, 23, 42, 0.14)",zIndex:30,padding:4},children:f.map(v=>{const b=v.identifier===e;return i.jsxs("button",{type:"button",role:"option","aria-selected":b,"aria-current":b?"true":void 0,className:"g7le-toolbar__template-menu-item","data-testid":"g7le-toolbar-template-menu-item","data-identifier":v.identifier,"data-current":b?"true":"false",onClick:()=>h(v.identifier),style:{display:"flex",alignItems:"center",justifyContent:"space-between",gap:8,width:"100%",padding:"8px 10px",fontSize:13,color:"#1e293b",background:b?"#eff6ff":"transparent",border:"none",borderRadius:6,cursor:b?"default":"pointer",textAlign:"left",outline:"none"},onMouseEnter:x=>{b||(x.currentTarget.style.background="#f1f5f9")},onMouseLeave:x=>{x.currentTarget.style.background=b?"#eff6ff":"transparent"},children:[i.jsxs("span",{style:{display:"flex",alignItems:"center",gap:8,minWidth:0},children:[i.jsx("span",{"aria-hidden":"true",style:{width:12,flex:"0 0 auto",color:"#2563eb",fontSize:12,textAlign:"center"},children:b?"✓":""}),i.jsxs("span",{style:{display:"flex",flexDirection:"column",gap:2,minWidth:0},children:[i.jsx("span",{style:{fontWeight:600,color:b?"#1d4ed8":"#1e293b",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},children:v.name||v.identifier}),i.jsx("span",{style:{fontSize:11,color:"#94a3b8",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},children:v.identifier})]})]}),i.jsxs("span",{style:{display:"inline-flex",alignItems:"center",gap:6,flex:"0 0 auto"},children:[i.jsx("span",{style:{fontSize:10,fontWeight:600,color:v.type==="admin"?"#7c3aed":"#0891b2",textTransform:"uppercase",letterSpacing:.4},children:v.type}),v.version&&i.jsxs("span",{style:{fontSize:11,color:"#64748b"},children:["v",v.version]})]})]},v.identifier)})})]})}function by(e={}){const{onSave:t,isDirty:n=!1,onExit:o,onEditCode:r,onManageImages:s,onManageTranslations:a,onPageSettings:l,onReset:d,onShowShortcuts:c,onPreview:p,onShowVersions:f,templateName:y,templateVersion:g,templateList:m,onSwitchTemplate:h}=e,[v,b]=u.useState(!1),[x,k]=u.useState(!1),{t:w}=tt(),{state:_,dispatch:C}=Je(),[E,S]=u.useState(null);u.useEffect(()=>{const I=()=>{const F=window.__g7LayoutEditorHistory;S(F?{undo:F.undo,redo:F.redo,canUndo:!!F.canUndo,canRedo:!!F.canRedo}:null)};I();const P=window.setInterval(I,250);return()=>window.clearInterval(P)},[]);const T=()=>{C({type:"TOGGLE_ROUTE_TREE"})},$=()=>_.editMode==="base"?w("layout_editor.chrome.toolbar.mode_badge.base"):_.editMode==="modal"?w("layout_editor.chrome.toolbar.mode_badge.modal"):_.editMode==="extension"?w("layout_editor.chrome.toolbar.mode_badge.extension"):_.editMode==="iteration_item"?w("layout_editor.chrome.toolbar.mode_badge.iteration_item"):null,A=()=>{_.editMode==="base"?C({type:"EXIT_BASE_EDIT"}):_.editMode==="modal"?C({type:"EXIT_MODAL_EDIT"}):_.editMode==="extension"?C({type:"EXIT_EXTENSION_EDIT"}):_.editMode==="iteration_item"&&C({type:"EXIT_ITERATION_ITEM_EDIT"})},z=$(),D=_.editMode!=="route",O={display:"inline-flex",alignItems:"center",gap:6,padding:"6px 12px",fontSize:13,fontWeight:500,borderRadius:6,borderWidth:1,borderStyle:"solid",borderColor:"transparent",backgroundColor:"transparent",color:"#475569",cursor:"pointer",transition:"background-color 120ms, color 120ms, border-color 120ms"},M={...O,backgroundColor:"#2563eb",color:"#ffffff",borderColor:"#2563eb",fontWeight:600},B={...O,borderColor:"#e2e8f0",backgroundColor:"#ffffff",color:"#0f172a"},N={backgroundColor:"#f1f5f9",color:"#0f172a",borderColor:"#cbd5e1"},H={backgroundColor:"#1d4ed8",borderColor:"#1d4ed8"},L={backgroundColor:"#f8fafc",borderColor:"#cbd5e1"};return i.jsxs("div",{className:"g7le-toolbar","data-testid":"g7le-toolbar",role:"toolbar",style:{display:"flex",alignItems:"center",gap:6,padding:"8px 12px",borderBottom:"1px solid #e2e8f0",background:"#ffffff",boxShadow:"0 1px 2px rgba(15, 23, 42, 0.04)"},children:[i.jsx("style",{children:"@keyframes g7le-spin { to { transform: rotate(360deg); } }.g7le-toolbar > * { flex-shrink: 0; white-space: nowrap; }"}),i.jsx("button",{type:"button",className:"g7le-toolbar__route-tree-toggle","data-testid":"g7le-toolbar-toggle-route-tree","aria-label":w("layout_editor.chrome.toolbar.toggle_route_tree"),title:w("layout_editor.chrome.toolbar.toggle_route_tree"),onClick:T,style:{...O,outline:"none",padding:"6px 10px",fontSize:16,lineHeight:1,color:"#64748b"},children:"☰"}),i.jsx(yy,{templateIdentifier:_.templateIdentifier,templateName:y,templateVersion:g,templateList:m,onSwitch:h}),z&&i.jsx("span",{className:"g7le-toolbar__mode-badge","data-testid":"g7le-toolbar-mode-badge","data-mode":_.editMode,style:{padding:"2px 8px",borderRadius:999,background:"#fef3c7",color:"#92400e",fontSize:11,fontWeight:600,border:"1px solid #fde68a"},children:z}),i.jsx("div",{style:{flex:1}}),i.jsxs(i.Fragment,{children:[i.jsx(wt,{"data-testid":"g7le-toolbar-undo","aria-label":w("layout_editor.chrome.toolbar.undo"),title:w("layout_editor.chrome.toolbar.undo"),disabled:!E?.canUndo,onClick:()=>E?.undo?.(),baseStyle:O,hoverStyle:N,style:{opacity:E?.canUndo?1:.4},children:"↶"}),i.jsx(wt,{"data-testid":"g7le-toolbar-redo","aria-label":w("layout_editor.chrome.toolbar.redo"),title:w("layout_editor.chrome.toolbar.redo"),disabled:!E?.canRedo,onClick:()=>E?.redo?.(),baseStyle:O,hoverStyle:N,style:{opacity:E?.canRedo?1:.4},children:"↷"}),i.jsxs(wt,{"data-testid":"g7le-toolbar-add-element","data-open":_.isPaletteOpen?"true":"false",disabled:!_.selectedRoute,onClick:()=>C({type:"TOGGLE_PALETTE"}),"aria-pressed":_.isPaletteOpen,baseStyle:O,hoverStyle:N,style:_.isPaletteOpen?{backgroundColor:"#eff6ff",color:"#1d4ed8",borderColor:"#93c5fd"}:void 0,children:["+ ",w("layout_editor.chrome.toolbar.add_element")]}),i.jsx(wt,{"data-testid":"g7le-toolbar-preview","data-previewing":x?"true":"false",disabled:!p||!_.selectedRoute?.layoutName||x,onClick:()=>{const I=p?.();I&&typeof I.then=="function"&&(k(!0),I.finally(()=>k(!1)))},baseStyle:O,hoverStyle:N,style:{opacity:x?.7:1},children:x?i.jsxs(i.Fragment,{children:[i.jsx("span",{"data-testid":"g7le-toolbar-preview-spinner","aria-hidden":"true",style:{display:"inline-block",width:12,height:12,border:"2px solid rgba(71,85,105,0.35)",borderTopColor:"#475569",borderRadius:"50%",animation:"g7le-spin 600ms linear infinite"}}),w("layout_editor.chrome.toolbar.previewing")]}):i.jsxs(i.Fragment,{children:["👁 ",w("layout_editor.chrome.toolbar.preview")]})}),i.jsxs(wt,{"data-testid":"g7le-toolbar-versions",disabled:!f||!(_.selectedRoute?.layoutName||_.selectedRoute?.path?.startsWith("__extension__/")),onClick:()=>f?.(),"aria-label":w("layout_editor.chrome.toolbar.version_history"),title:w("layout_editor.chrome.toolbar.version_history"),baseStyle:O,hoverStyle:N,children:["🕘 ",w("layout_editor.chrome.toolbar.version_history")]}),i.jsxs(wt,{"data-testid":"g7le-toolbar-edit-code",disabled:!r,onClick:()=>r?.(),baseStyle:O,hoverStyle:N,children:["⟨/⟩ ",w("layout_editor.chrome.toolbar.edit_code")]}),i.jsxs(wt,{"data-testid":"g7le-toolbar-images",disabled:!s||!_.selectedRoute,onClick:()=>s?.(),baseStyle:O,hoverStyle:N,children:["🖼 ",w("layout_editor.chrome.toolbar.images")]}),i.jsxs(wt,{"data-testid":"g7le-toolbar-translations",disabled:!a||!_.selectedRoute,onClick:()=>a?.(),baseStyle:O,hoverStyle:N,children:["🌐 ",w("layout_editor.chrome.toolbar.translations")]}),i.jsxs(wt,{"data-testid":"g7le-toolbar-page-settings",disabled:!l||!_.selectedRoute||_.editMode==="extension"||_.editMode==="iteration_item",onClick:()=>l?.(),baseStyle:O,hoverStyle:N,children:["⚙ ",w("layout_editor.chrome.toolbar.page_settings")]}),i.jsxs(wt,{"data-testid":"g7le-toolbar-reset",disabled:!d||!n,onClick:()=>d?.(),"aria-label":w("layout_editor.chrome.toolbar.reset"),title:w("layout_editor.chrome.toolbar.reset"),baseStyle:O,hoverStyle:N,style:{opacity:n?1:.4},children:["↺ ",w("layout_editor.chrome.toolbar.reset")]}),i.jsx(wt,{"data-testid":"g7le-toolbar-save","data-dirty":n?"true":"false","data-saving":v?"true":"false",disabled:!t||v,onClick:()=>{const I=t?.();I&&typeof I.then=="function"&&(b(!0),I.finally(()=>b(!1)))},baseStyle:M,hoverStyle:H,style:{opacity:v?.7:n?1:.7},children:v?i.jsxs(i.Fragment,{children:[i.jsx("span",{"data-testid":"g7le-toolbar-save-spinner","aria-hidden":"true",className:"g7le-toolbar__spinner",style:{display:"inline-block",width:12,height:12,border:"2px solid rgba(255,255,255,0.4)",borderTopColor:"#ffffff",borderRadius:"50%",animation:"g7le-spin 600ms linear infinite"}}),w("layout_editor.chrome.toolbar.saving")]}):i.jsxs(i.Fragment,{children:["💾 ",w("layout_editor.chrome.toolbar.save")]})}),i.jsx(wt,{"data-testid":"g7le-toolbar-shortcuts",disabled:!c,onClick:()=>c?.(),"aria-label":w("layout_editor.shortcuts.title"),title:w("layout_editor.shortcuts.title"),baseStyle:O,hoverStyle:N,children:"⌨"}),i.jsxs(wt,{"data-testid":"g7le-toolbar-exit",disabled:!o,onClick:()=>o?.(),baseStyle:B,hoverStyle:L,children:["✕ ",w("layout_editor.chrome.toolbar.exit")]})]}),D&&i.jsxs(wt,{"data-testid":"g7le-toolbar-exit-alt-mode",onClick:A,baseStyle:B,hoverStyle:L,children:["← ",_.editMode==="base"&&w("layout_editor.chrome.toolbar.exit_base_edit"),_.editMode==="modal"&&w("layout_editor.chrome.toolbar.exit_modal_edit"),_.editMode==="extension"&&w("layout_editor.chrome.toolbar.exit_extension_edit"),_.editMode==="iteration_item"&&w("layout_editor.chrome.toolbar.exit_iteration_item_edit")]})]})}function ar(){if(typeof window>"u")return null;try{return window.localStorage?.getItem("auth_token")??null}catch{return null}}function je(e){const t={Accept:"application/json",...e??{}},n=ar();return n&&(t.Authorization=`Bearer ${n}`),t}function Mi(e){return encodeURIComponent(e)}async function Dl(e,t){const n=`/api/admin/templates/${Mi(e)}/layout-attachments?layout_name=${encodeURIComponent(t)}`;try{const o=await fetch(n,{method:"GET",headers:je(),credentials:"same-origin"}),r=await o.json().catch(()=>null);return!o.ok||!r?.success?{ok:!1,status:o.status,message:Li(r,o.status)}:{ok:!0,data:Array.isArray(r?.data)?r.data:[]}}catch(o){return{ok:!1,status:0,message:o instanceof Error?o.message:"network error"}}}async function Nl(e,t,n){const o=`/api/admin/templates/${Mi(e)}/layout-attachments`,r=new FormData;r.append("file",n),r.append("layout_name",t);try{const s=await fetch(o,{method:"POST",headers:je(),body:r,credentials:"same-origin"}),a=await s.json().catch(()=>null);return!s.ok||!a?.success||!a?.data?.url?{ok:!1,status:s.status,message:Li(a,s.status)}:{ok:!0,data:a.data}}catch(s){return{ok:!1,status:0,message:s instanceof Error?s.message:"network error"}}}async function zl(e){const t=`/api/admin/templates/layout-attachments/${Mi(String(e))}`;try{const n=await fetch(t,{method:"DELETE",headers:je(),credentials:"same-origin"}),o=await n.json().catch(()=>null);return!n.ok||o&&o.success===!1?{ok:!1,status:n.status,message:Li(o,n.status)}:{ok:!0,data:!0}}catch(n){return{ok:!1,status:0,message:n instanceof Error?n.message:"network error"}}}function Li(e,t){if(e&&typeof e=="object"&&"message"in e){const n=e.message;if(typeof n=="string"&&n.length>0)return n}return`HTTP ${t}`}function my(e){return e<1024?`${e} B`:e<1024*1024?`${Math.round(e/1024)} KB`:`${(e/(1024*1024)).toFixed(1)} MB`}function vy(e){if(!e)return"";const t=e.match(/^(\d{4})-(\d{2})-(\d{2})/);return t?`${t[2]}-${t[3]}`:""}function Ml({templateIdentifier:e,layoutName:t,t:n,onSelect:o,onChanged:r,onClose:s}){const[a,l]=u.useState([]),[d,c]=u.useState("idle"),[p,f]=u.useState(!1),[y,g]=u.useState(null),m=u.useRef(null),h=u.useCallback(async()=>{c("loading"),g(null);const x=await Dl(e,t);x.ok?(l(x.data),c("idle")):(g(x.message),c("error"))},[e,t]);u.useEffect(()=>{h()},[h]);const v=async x=>{const k=x.target.files?.[0];if(!k)return;f(!0),g(null);const w=await Nl(e,t,k);w.ok?(l(_=>[w.data,..._.filter(C=>C.id!==w.data.id)]),r?.()):g(w.message),f(!1),m.current&&(m.current.value="")},b=async x=>{if(!(typeof window<"u"&&typeof window.confirm=="function"?window.confirm(n("layout_editor.attachment_manager.delete_confirm")):!0))return;const w=await zl(x.id);w.ok?(l(_=>_.filter(C=>C.id!==x.id)),r?.()):g(w.message)};return i.jsxs("div",{className:"g7le-attachment-manager","data-testid":"g7le-attachment-manager",style:_y,children:[i.jsxs("div",{"data-modal-drag-handle":!0,style:xy,children:[i.jsx("span",{style:{fontSize:14,fontWeight:700},"data-testid":"g7le-attachment-manager-title",children:n("layout_editor.attachment_manager.title")}),i.jsx("button",{type:"button","aria-label":"close",onClick:s,style:wy,"data-testid":"g7le-attachment-manager-close",children:"✕"})]}),i.jsx("div",{style:ky,children:i.jsxs("label",{style:Sy,"data-testid":"g7le-attachment-upload",children:[p?"…":`⬆ ${n("layout_editor.attachment_manager.upload")}`,i.jsx("input",{ref:m,type:"file",accept:"image/*",onChange:v,disabled:p,style:{display:"none"},"data-testid":"g7le-attachment-upload-input"})]})}),y&&i.jsx("div",{style:Cy,"data-testid":"g7le-attachment-manager-error",children:y}),i.jsx("div",{style:$y,children:d==="loading"?i.jsx("div",{style:Ll,"data-testid":"g7le-attachment-manager-loading",children:n("layout_editor.attachment_manager.loading")}):a.length===0&&d!=="error"?i.jsx("div",{style:Ll,"data-testid":"g7le-attachment-manager-empty",children:n("layout_editor.attachment_manager.empty")}):i.jsx("div",{style:Ey,"data-testid":"g7le-attachment-grid",children:a.map(x=>i.jsxs("div",{style:Iy,"data-testid":`g7le-attachment-card-${x.id}`,children:[i.jsx("div",{style:{...Ay,backgroundImage:`url(${x.url})`}}),i.jsx("div",{style:Ty,title:x.original_name,children:x.original_name}),i.jsxs("div",{style:Dy,children:[my(x.size),x.created_at?` · ${vy(x.created_at)}`:""]}),i.jsxs("div",{style:Ny,children:[o&&i.jsx("button",{type:"button",onClick:()=>o(x.url),style:zy,"data-testid":`g7le-attachment-use-${x.id}`,children:n("layout_editor.attachment_manager.use_as_bg")}),i.jsx("button",{type:"button",onClick:()=>b(x),style:My,"data-testid":`g7le-attachment-delete-${x.id}`,children:"🗑"})]})]},x.id))})}),i.jsx("div",{style:Ly,children:i.jsx("button",{type:"button",onClick:s,style:Py,"data-testid":"g7le-attachment-manager-footer-close",children:n("layout_editor.attachment_manager.close")})})]})}const _y={display:"flex",flexDirection:"column",minHeight:0,flex:1},xy={display:"flex",alignItems:"center",justifyContent:"space-between",padding:"12px 16px",borderBottom:"1px solid #e2e8f0"},wy={border:"none",background:"transparent",fontSize:16,color:"#94a3b8",cursor:"pointer"},ky={padding:"10px 16px",borderBottom:"1px solid #f1f5f9",display:"flex",gap:8},Sy={padding:"6px 12px",fontSize:13,border:"1px solid #2563eb",borderRadius:6,background:"#fff",color:"#2563eb",cursor:"pointer",display:"inline-flex",alignItems:"center"},Cy={padding:"8px 16px",fontSize:12,color:"#b91c1c",background:"#fef2f2"},$y={padding:16,overflow:"auto",flex:1,minHeight:120},Ll={fontSize:13,color:"#94a3b8",textAlign:"center",padding:"32px 0"},Ey={display:"grid",gridTemplateColumns:"repeat(auto-fill, minmax(140px, 1fr))",gap:12},Iy={display:"flex",flexDirection:"column",gap:4,border:"1px solid #e2e8f0",borderRadius:8,padding:8},Ay={width:"100%",height:88,borderRadius:6,border:"1px solid #cbd5e1",backgroundSize:"cover",backgroundPosition:"center",backgroundColor:"#f8fafc"},Ty={fontSize:12,fontWeight:600,color:"#0f172a",whiteSpace:"nowrap",overflow:"hidden",textOverflow:"ellipsis"},Dy={fontSize:11,color:"#64748b"},Ny={display:"flex",gap:6,marginTop:2},zy={flex:1,padding:"4px 6px",fontSize:11,border:"1px solid #2563eb",borderRadius:6,background:"#2563eb",color:"#fff",cursor:"pointer"},My={padding:"4px 8px",fontSize:12,border:"1px solid #fecaca",borderRadius:6,background:"#fff",color:"#dc2626",cursor:"pointer"},Ly={padding:"10px 16px",borderTop:"1px solid #e2e8f0",display:"flex",justifyContent:"flex-end"},Py={padding:"6px 14px",fontSize:13,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"};function lr(e){return encodeURIComponent(e)}async function Oy(e,t,n){const o=new URLSearchParams;t&&o.set("layout_name",t);const r=o.toString(),s=`/api/admin/templates/${lr(e)}/custom-translations`+(r?`?${r}`:"");try{const a=await fetch(s,{method:"GET",headers:je(),credentials:"same-origin"}),l=await a.json().catch(()=>null);return!a.ok||!l?.success?{ok:!1,status:a.status,message:dr(l,a.status)}:{ok:!0,data:Array.isArray(l?.data)?l.data:[]}}catch(a){return{ok:!1,status:0,message:a instanceof Error?a.message:"network error"}}}async function By(e,t,n,o){const r=`/api/admin/templates/${lr(e)}/custom-translations/${t}`;try{const s=await fetch(r,{method:"PUT",headers:je({"Content-Type":"application/json"}),body:JSON.stringify({values:n,expected_lock_version:o}),credentials:"same-origin"}),a=await s.json().catch(()=>null);return!s.ok||!a?.success||!a?.data?{ok:!1,status:s.status,message:dr(a,s.status)}:{ok:!0,data:a.data}}catch(s){return{ok:!1,status:0,message:s instanceof Error?s.message:"network error"}}}async function Wy(e,t){const n=`/api/admin/templates/${lr(e)}/custom-translations/${t}`;try{const o=await fetch(n,{method:"DELETE",headers:je(),credentials:"same-origin"}),r=await o.json().catch(()=>null);return!o.ok||r&&r.success===!1?{ok:!1,status:o.status,message:dr(r,o.status)}:{ok:!0,data:!0}}catch(o){return{ok:!1,status:0,message:o instanceof Error?o.message:"network error"}}}async function jy(e,t){const n=`/api/admin/templates/${lr(e)}/custom-translations`;try{const o=await fetch(n,{method:"DELETE",headers:je({"Content-Type":"application/json"}),body:JSON.stringify({ids:t}),credentials:"same-origin"}),r=await o.json().catch(()=>null);return!o.ok||!r?.success?{ok:!1,status:o.status,message:dr(r,o.status)}:{ok:!0,data:{deleted:typeof r?.data?.deleted=="number"?r.data.deleted:t.length}}}catch(o){return{ok:!1,status:0,message:o instanceof Error?o.message:"network error"}}}const Pl=/\$t:(custom\.[A-Za-z0-9_\-]+\.\d+)/g;function Hy(e){const t=new Set,n=o=>{if(typeof o=="string"){if(o.includes("$t:custom.")){let r;for(Pl.lastIndex=0;(r=Pl.exec(o))!==null;)t.add(r[1])}return}if(Array.isArray(o)){for(const r of o)n(r);return}if(o&&typeof o=="object")for(const r of Object.values(o))n(r)};return n(e),t}function dr(e,t){if(e&&typeof e=="object"&&"message"in e){const n=e.message;if(typeof n=="string"&&n.length>0)return n}return`HTTP ${t}`}const Pt=Lt().TranslationEngine,bo=Lt().createLogger,Pi=bo("EditorSpecLoader"),Fy=["controls","componentCapabilities","actionRecipes","conditionRecipes","pageSettings","initActionRecipes","errorRecipes","computedRecipes","loadingComponents"],Ky=["actionRecipes","initActionRecipes","errorRecipes","computedRecipes","loadingComponents"];function Ol(e){if(typeof window>"u")return[];const n=window.G7Config?.[e];if(!Array.isArray(n))return[];const o=[];for(const r of n){const s=r?.identifier;typeof s=="string"&&s.length>0&&o.push(s)}return o}async function Vy(e,t,n,o){const s=`${n}/${e==="template"?"templates":e==="module"?"modules":"plugins"}/${encodeURIComponent(t)}/editor-spec`;try{const a=await o(s);if(!a.ok)return a.status!==404&&Pi.warn(`fetchOneSpec: HTTP ${a.status}`,s),null;const l=await a.json();return!l?.success||!l.data?null:l.data.spec??null}catch(a){return Pi.warn("fetchOneSpec: fetch failed",s,a),null}}function Uy(e){return Ky.includes(e)}function qy(e,t){return e==null||typeof e!="object"?e:{...e,__source:t}}function Gy(e,t,n,o){const r=t[n];if(!r||typeof r!="object")return;const s=e[n]??{};if(o&&Uy(n)){const a={};for(const[l,d]of Object.entries(r))a[l]=qy(d,o);e[n]={...s,...a};return}e[n]={...s,...r}}function Yy(e,t){if(!t.nesting)return;const n=e.nesting??{},o=new Set(n.draggable??[]);for(const r of t.nesting.draggable??[])o.add(r);n.draggable=Array.from(o),n.containers={...n.containers??{},...t.nesting.containers??{}},e.nesting=n}function Xy(e,t){if(!t.componentPalette)return;const n=e.componentPalette??{},o=[...n.groups??[],...t.componentPalette.groups??[]];n.groups=o,n.entries={...n.entries??{},...t.componentPalette.entries??{}},e.componentPalette=n}function Jy(e,t){if(!t.states)return;const n=e.states??{},o=[...n.groups??[],...t.states.groups??[]];n.groups=o,e.states=n}function Zy(e,t){if(!Array.isArray(t.stateLabels)||t.stateLabels.length===0)return;const n=[...e.stateLabels??[],...t.stateLabels],o=new Map;for(const r of n)!r||typeof r.key!="string"||typeof r.scope!="string"||o.set(`${r.scope}:${r.key}`,r);e.stateLabels=Array.from(o.values())}function Qy(e,t){if(!t.actionChipCandidates)return;const n=e.actionChipCandidates??{};for(const o of["response","error","payload"]){const r=t.actionChipCandidates[o];!Array.isArray(r)||r.length===0||(n[o]=[...n[o]??[],...r])}e.actionChipCandidates=n}function Ry(e,t){if(!t.sampleData)return;const n=e.sampleData??{};n.byDataSourceId={...n.byDataSourceId??{},...t.sampleData.byDataSourceId??{}},n.byEndpointPattern={...n.byEndpointPattern??{},...t.sampleData.byEndpointPattern??{}},e.sampleData=n}function Bl(e,t,n){for(const o of Fy)Gy(e,t,o,n);Yy(e,t),Xy(e,t),Jy(e,t),Zy(e,t),Qy(e,t),Ry(e,t),t.version&&(e.version=t.version)}async function eb(e){const t=e.apiBaseUrl??"/api",n=e.fetcher??(typeof fetch<"u"?fetch:void 0);if(!n)return Pi.warn("loadEditorSpecBundle: fetch unavailable, returning null spec"),{spec:null,sampleGlobalSources:[]};const o=e.activeModuleIdentifiers??Ol("activeModules"),r=e.activePluginIdentifiers??Ol("activePlugins"),s=[...o.map(p=>({kind:"module",id:p})),...r.map(p=>({kind:"plugin",id:p})),{kind:"template",id:e.templateIdentifier}],a=await Promise.all(s.map(p=>Vy(p.kind,p.id,t,n)));let l=null;const d=[],c={};if(e.coreSeed&&(l={},Bl(l,e.coreSeed,{kind:"core"})),s.forEach((p,f)=>{const y=a[f];if(!y)return;y.sampleGlobal&&typeof y.sampleGlobal=="object"&&d.push({id:p.id,kind:p.kind,sampleGlobal:y.sampleGlobal});const g=y.sampleData?.byDataSourceId;if(g&&typeof g=="object"){const m=p.kind==="template"?"template":`${p.kind}:${p.id}`;c[m]={...c[m]??{},...g}}l===null&&(l={}),Bl(l,y,{kind:p.kind,id:p.id})}),l!==null&&Object.keys(c).length>0){const p=l;p.sampleData={...p.sampleData??{},bySource:c}}return{spec:l,sampleGlobalSources:d}}function Oi(e,t){return e?.componentCapabilities?.[t]??null}function Ct(e,t){return e?.controls?.[t]??null}function tb(e){return e?.initActionRecipes??{}}function nb(e){return e?.computedRecipes??{}}function ob(e){return e?.errorRecipes??{}}function rb(e){const t=e?.loadingComponents;return!t||typeof t!="object"?[]:Object.values(t).filter(n=>n!=null&&typeof n=="object"&&typeof n.name=="string")}function ib(e,t,n){const o=e?.stateLabels;if(!Array.isArray(o))return null;for(const r of o)if(r?.scope===t&&r?.key===n)return typeof r.label_key=="string"?r.label_key:null;return null}function Bi(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)}function sb(e){return e===null?"null":typeof e=="string"?e.length>40?`${e.slice(0,40)}…`:e:typeof e=="number"||typeof e=="boolean"?String(e):""}function ab(e){const t=e.find(n=>Bi(n));if(Bi(t))return Object.keys(t).slice(0,8)}function lb(e){return Object.keys(e).slice(0,8)}function db(e){const t=Object.entries(e);return t.length===0?!1:t.some(([,n])=>n===null||typeof n!="object")}function Wi(e,t,n,o,r){if(Array.isArray(e)){r(t,"array",`[${e.length}]`,ab(e));return}if(Bi(e)){if(t&&db(e)){const s=lb(e);r(t,"object",`{${s.length}}`,s)}if(n>=o)return;for(const[s,a]of Object.entries(e)){const l=t?`${t}.${s}`:s;Wi(a,l,n+1,o,r)}return}r(t,"scalar",sb(e))}function cb(e,t,n){const o=[];let r=e;for(const s of t){if(!r||(r=(Array.isArray(r.children)?r.children:[])[s],!r))break;const l=r.iteration;if(!(!l||l.source===void 0)&&(typeof l.index_var=="string"&&l.index_var.length>0&&o.push({name:l.index_var,kind:"index"}),typeof l.item_var=="string"&&l.item_var.length>0)){const d=typeof l.source=="string"?l.source:"",c=d&&n?.get(Wl(d))||[];o.push({name:l.item_var,kind:"item",itemFields:c})}}return o}function Wl(e){return e.replace(/^\{\{|\}\}$/g,"").trim()}function ub(e){const t=new Map;for(const n of e)n.shape==="array"&&n.itemFields&&n.itemFields.length>0&&t.set(Wl(n.expression),n.itemFields);return t}function ji(e){const t=e.maxDepth??4,n=[];for(const o of e.dataSources??[])!o||typeof o.id!="string"||o.id.length===0||Wi(o.sample,"",0,t,(r,s,a,l)=>{const d=r?`{{${o.id}.${r}}}`:`{{${o.id}}}`;n.push({expression:d,source:"data_source",sourceId:o.id,path:r,shape:s,groupLabelKey:o.labelKey?.startsWith("$t:")?o.labelKey.slice(3):o.labelKey,preview:a,itemFields:l})});for(const o of e.states??[])!o||typeof o.scope!="string"||Wi(o.tree,"",0,t,(r,s,a,l)=>{if(!r)return;const d=ib(e.spec,o.scope,r)??void 0;n.push({expression:`{{${o.scope}.${r}}}`,source:o.scope,sourceId:o.scope,path:r,shape:s,labelKey:d?.startsWith("$t:")?d.slice(3):d,preview:a,itemFields:l})});for(const o of e.iterationVars??[])if(!(!o||typeof o.name!="string"||o.name.length===0)){if(o.kind==="index"){n.push({expression:`{{${o.name}}}`,source:"iteration",sourceId:o.name,path:"",shape:"scalar",preview:"0"});continue}for(const r of o.itemFields??[])n.push({expression:`{{${o.name}.${r}}}`,source:"iteration",sourceId:o.name,path:r,shape:"scalar",preview:""})}return n}const pb=["$localized"];function fb(e){const t=e.trim(),n=t.indexOf("(");if(n===-1||!t.endsWith(")"))return{fn:null,inner:t};const o=t.slice(0,n).trim();if(!pb.includes(o))return{fn:null,inner:t};const r=t.slice(n+1,-1);let s=0,a=null;for(let l=0;ln.shape===t)}function Ki(e,t){const n=t.trim().toLowerCase();return n===""?e:e.filter(o=>[o.resolvedLabel??"",o.sourceId,o.path,o.preview].join(" ").toLowerCase().includes(n))}function jl(e,t,n){const o={response:[{path:"data",labelKey:"layout_editor.action_chip.response_data",shape:"object"},{path:"message",labelKey:"layout_editor.action_chip.response_message",shape:"scalar"}],error:[{path:"status",labelKey:"layout_editor.action_chip.error_status",shape:"scalar"},{path:"message",labelKey:"layout_editor.action_chip.error_message",shape:"scalar"},{path:"errors",labelKey:"layout_editor.action_chip.error_errors",shape:"object"},{path:"data",labelKey:"layout_editor.action_chip.error_data",shape:"object"},{path:"statusText",labelKey:"layout_editor.action_chip.error_status_text",shape:"scalar"}],payload:[{path:"data",labelKey:"layout_editor.action_chip.payload_data",shape:"object"}]},r=e==="response"?"response":e==="error"?"error":"message",s=new Set,a=[];for(const d of o[e])s.has(d.path)||(s.add(d.path),a.push(d));const l=n?.[e];if(Array.isArray(l))for(const d of l)!d||typeof d.path!="string"||typeof d.labelKey!="string"||s.has(d.path)||(s.add(d.path),a.push({path:d.path,labelKey:d.labelKey,shape:d.shape??"scalar"}));return a.map(d=>({expression:`{{${r}.${d.path}}}`,source:"_local",sourceId:r,path:d.path,shape:d.shape,labelKey:d.labelKey,preview:t(d.labelKey)}))}const Vi=/\{\{(?:[^}]|\}(?!\}))*\}\}/g;function In(e){const t=[];if(typeof e!="string"||e.length===0)return t;let n=0,o=0;const r=new RegExp(Vi.source,"g");let s;for(;(s=r.exec(e))!==null;){const a=s.index,l=a+s[0].length;a>n&&t.push({kind:"literal",raw:e.slice(n,a),start:n,end:a}),t.push({kind:"binding",raw:s[0],start:a,end:l,parsed:Kt(s[0]),bindingIndex:o++}),n=l}return nt.kind==="binding")}function Ui(e){return typeof e!="string"?!1:new RegExp(Vi.source).test(e)}function hb(e){const t=new Set(e.map(o=>o.trim()));let n=0;for(;t.has(`p${n}`);)n+=1;return`p${n}`}function Hl(e,t,n){const o=typeof e=="string"?e:"",r=Math.max(0,Math.min(t,o.length)),s=o.slice(0,r),a=o.slice(r),l=s.length>0&&!/\s$/.test(s)?" ":"",d=a.length>0&&!/^\s/.test(a)?" ":"";return`${s}${l}{${n}}${d}${a}`}function qi(e,t){const n=typeof e=="string"?e:"";return An(n).includes(t)?n:n.length===0?`{${t}}`:/\s$/.test(n)?`${n}{${t}}`:`${n} {${t}}`}function Fl(e){return cr(e).map(t=>({bindingIndex:t.bindingIndex??0,expression:t.raw,parsed:t.parsed??null,isComplex:(t.parsed??null)===null}))}function ur(e){return typeof e!="string"?"":e.replace(new RegExp(Vi.source,"g")," ").replace(/\s+/g," ").trim()}const yb=/\$t:([a-zA-Z0-9._-]+)/g,Kl="custom.";function Gi(e,t){return typeof e!="string"||e.length===0?typeof e=="string"?e:"":t?In(e).map(n=>n.kind!=="literal"?n.raw:n.raw.replace(yb,(o,r)=>{if(r.startsWith(Kl))return o;const s=t(r);return s&&s!==r?s:o})).join(""):e}const Kn="(?:(?!\\|[a-zA-Z0-9_]+=).)*",bb=new RegExp(`^\\s*\\$t:([a-zA-Z0-9._-]+)((?:\\|[a-zA-Z0-9_]+=${Kn})*)`),mb=new RegExp(`\\|([a-zA-Z0-9_]+)=(${Kn})`,"g");function Vt(e){if(typeof e!="string")return"";const t=Kt(e);if(t)return t.path||t.sourceId;const n=e.trim().replace(/^\{\{\s*/,"").replace(/\s*\}\}$/,"");let o=-1;for(let l=0;l0){const l=n.slice(0,o).trim(),d=Kt(`{{${l}}}`);if(d)return d.path||d.sourceId;const c=l.replace(/\?\./g,".").replace(/\s*\?\?.*$/,"").trim();if(/^[A-Za-z_$][\w$.]*$/.test(c)){const p=c.split(".").filter(Boolean);return p.length>1&&p.slice(1).join(".")||c}}const r=n.replace(/\s+/g," ").trim(),s=r.replace(/\s*(\?\?|\|\|)\s*(\[\s*\]|\{\s*\}|'[^']*'|"[^"]*"|\d+|null|undefined)\s*$/,"").trim(),a=/\.([A-Za-z_$][\w$]*)\s*$/.exec(s);return a?a[1]:r}const Vl=/\$(?:core|module|plugin)_settings:[a-zA-Z0-9._:-]+/g;function Ul(e){return typeof e=="string"&&new RegExp(Vl.source).test(e)}function vb(e){return typeof e!="string"?"":e.replace(/^\$(?:core|module|plugin)_settings:/,"").split(/[.:]/).filter(Boolean).pop()||e}function ql(e){const t=[];for(const n of In(e)){if(n.kind==="binding"){t.push({kind:"chip",label:Vt(n.raw)});continue}let o=0;const r=n.raw,s=new RegExp(Vl.source,"g");let a;for(;(a=s.exec(r))!==null;)a.index>o&&t.push({kind:"text",label:r.slice(o,a.index)}),t.push({kind:"chip",label:vb(a[0])}),o=a.index+a[0].length;o{const m=`p${o.length}`;return o.push(g),r[m]=Vt(g),`{${m}}`};let a=e,l="";const d=bb.exec(e);if(d&&!d[1].startsWith(Kl)){const g=d[1],m=d[2]??"",h=e.slice(d[0].length),v=new Map,b=new RegExp(mb.source,"g");let x;for(;(x=b.exec(m))!==null;)x[2]&&x[2].includes("{{")&&v.set(x[1],x[2]);const k=t?t(g):"";if(k&&k!==g&&!k.startsWith("$t:"))l=k.replace(/\{\{?([a-zA-Z0-9_]+)\}?\}/g,(w,_)=>{const C=v.get(_);return C?s(C):w});else return n;a=h}const c=Gi(a,t),p=In(c).map(g=>g.kind==="binding"?s(g.raw):g.raw).join(""),f=`${l}${p}`;return/\$t:[a-zA-Z0-9._-]+/.test(f)||o.length===0?n:{chipValue:f,bindings:o,paramLabels:r,keyifiable:!0}}function Gl(e,t){return t.bindings.length?e+t.bindings.map((n,o)=>`|p${o}=${n}`).join(""):e}function Yl(e,t){const n=cr(t).map(r=>r.raw);if(n.length===0)return e;const o=n.map((r,s)=>`|p${s}=${r}`).join("");return`${e}${o}`}function Xl(e){if(typeof e!="string")return"";const t=In(e);let n=0;return t.map(o=>o.kind==="binding"?`{p${n++}}`:o.raw).join("")}const Jl=new RegExp(`^\\s*\\$t:([a-zA-Z0-9._-]+)((?:\\|[a-zA-Z0-9_]+=${Kn})+)\\s*$`),xb=new RegExp(`\\|([a-zA-Z0-9_]+)=(${Kn})`,"g");function mt(e){if(typeof e!="string")return null;const t=Jl.exec(e);if(!t)return null;const n=t[1],o=t[2],r=[],s=new RegExp(xb.source,"g");let a;for(;(a=s.exec(o))!==null;){const l=a[1],d=a[2];r.push({name:l,expression:d,parsed:Kt(d)})}return{key:n,params:r}}function Zl(e){return typeof e=="string"&&Jl.test(e)}function An(e){if(typeof e!="string")return[];const t=[],n=/\{\{?(p\d+)\}?\}/g;let o;for(;(o=n.exec(e))!==null;)t.push(o[1]);return t.sort()}function wb(e,t,n){if(typeof e!="string")return e;const o=new RegExp(`(\\|${t}=)${Kn}`);return o.test(e)?e.replace(o,`$1${n}`):e}function mo(e,t){if(typeof e!="string")return e;const n=new RegExp(`\\|${t}=${Kn}`);return e.replace(n,"")}function Ql(e,t){return typeof e!="string"?e:e.replace(new RegExp(`\\s*\\{\\{?${t}\\}?\\}\\s*`,"g")," ").replace(/\s+/g," ").trim()}function kb(e,t){const n=s=>cr(typeof s=="string"?s:"").map(a=>a.raw.replace(/\s+/g,"")).sort(),o=n(e),r=n(t);return o.length!==r.length?!1:o.every((s,a)=>s===r[a])}function Rl(e){return e.__source?.kind==="base"}function ed(e){return e.__source?.kind==="partial"}function Sb(e){return e.__source?.kind==="route"}function pr(e,t){return e.__source?.kind!=="extension"?!1:t==null?!0:e.__source.extensionId!==t}function td(e){return e.type==="extension_point"}function Xi(e=[]){for(const t of e)if(t.iteration&&t.iteration.source!==void 0)return!0;return!1}function Cb(e,t=[]){for(const n of t)if(n.iteration&&n.iteration.source!==void 0)return!0;return e.iteration&&e.iteration.source!==void 0?!0:nd(e)}function nd(e){if(typeof e.text=="string"&&od(e.text)&&!$b(e.text))return!0;if(e.props&&typeof e.props=="object"){for(const t of Object.values(e.props))if(typeof t=="string"&&od(t))return!0}return!1}function od(e){return/\{\{[\s\S]*?\}\}/.test(e)}function $b(e){const t=mt(e);return t!==null&&t.key.startsWith("custom.")}function Eb(e,t){return e??t??null}function Ji(e,t,n){if(td(e))return!0;switch(t){case"route":return Rl(e)||ed(e)||pr(e);case"base":return pr(e);case"modal":return pr(e);case"extension":return pr(e,n)?!0:Rl(e)||Sb(e)||ed(e);default:return!1}}function et(e){return typeof e=="object"&&e!==null&&typeof e.responsive=="string"}function fr(e,t){return et(e)?et(t)&&e.responsive===t.responsive:!et(t)&&e===t}function at(e){const t=[];let n=!1;for(const o of e){if(et(o)){t.push("responsive",o.responsive),n=!0;continue}n?t.push("children",String(o)):(t.push(String(o)),n=!0)}return t.join(".")}function Qt(e){return Array.isArray(e.children)?e.children:[]}function gr(e,t){const n=e.responsive?.[t];return n&&Array.isArray(n.children)?n.children:[]}function We(e,t){if(!e)return null;if(t.length===0)return e;let n=e,o=Qt(e);for(const r of t){if(et(r)){o=gr(n,r.responsive);continue}if(r<0||r>=o.length||(n=o[r],!n))return null;o=Qt(n)}return n}function Ib(e,t,n){if(!e||t.length===0)return t;let o=e,r=Qt(e),s;for(let a=0;a=2?t[t.length-2]:void 0;if(r!==void 0&&et(r)){const a=We(e,t.slice(0,-2));return a?{owner:a,children:gr(a,r.responsive),branchKey:r.responsive,index:o}:null}const s=We(e,t.slice(0,-1));return s?{owner:s,children:Qt(s),index:o}:null}function Zi(e,t){if(e.branchKey!==void 0){const n={...e.owner.responsive??{}};n[e.branchKey]={...n[e.branchKey]??{},children:t},e.owner.responsive=n}else e.owner.children=t}function Rt(e){const t={...e};t.props&&typeof t.props=="object"&&(t.props={...t.props});const n=Qt(e);if(n.length>0&&(t.children=n.map(Rt)),e.responsive&&typeof e.responsive=="object"){const o={};for(const r of Object.keys(e.responsive)){const s=e.responsive[r];if(!s||typeof s!="object"){o[r]=s;continue}const a={...s};Array.isArray(s.children)&&(a.children=s.children.map(Rt)),a.props&&typeof a.props=="object"&&(a.props={...a.props}),o[r]=a}t.responsive=o}return t}function Qi(e,t,n,o){const r=Rt(e),s=t.length>0?t[t.length-1]:void 0;if(s!==void 0&&et(s)){const p=We(r,t.slice(0,-1));if(!p)return r;const f=gr(p,s.responsive),y=Math.max(0,Math.min(n,f.length)),g=[...f];return g.splice(y,0,o),Zi({owner:p,branchKey:s.responsive},g),r}const a=We(r,t);if(!a)return r;const l=Qt(a),d=Math.max(0,Math.min(n,l.length)),c=[...l];return c.splice(d,0,o),a.children=c,r}function Ri(e,t){if(t.length===0)return e;const n=Rt(e),o=rd(n,t);if(!o||o.index<0||o.index>=o.children.length)return n;const r=[...o.children];return r.splice(o.index,1),Zi(o,r),n}function Ab(e,t,n,o){if(t.length===0||!n||et(t[t.length-1]))return e;const r=Rt(e),s=We(r,t);if(!s)return r;const a=s.responsive&&typeof s.responsive=="object"?s.responsive[n]:void 0;if(a&&Array.isArray(a.children))return r;const l=o&&s.responsive&&typeof s.responsive=="object"?s.responsive[o]:void 0,c=(l&&Array.isArray(l.children)?l.children:Qt(s)).map(Rt),p={...s.responsive??{}};return p[n]={...p[n]??{},children:c},s.responsive=p,r}function Tb(e,t,n){if(t.length===0||!n||et(t[t.length-1]))return e;const o=Rt(e),r=We(o,t);if(!r||!r.responsive||typeof r.responsive!="object")return o;const s=r.responsive[n];if(!s||!Array.isArray(s.children))return o;const a={...r.responsive},l={...a[n]};return delete l.children,Object.keys(l).length===0?delete a[n]:a[n]=l,r.responsive=Object.keys(a).length>0?a:void 0,o}function Db(e,t,n,o){if(t.length===0)return e;const r=We(e,t);if(!r||Nb(t,n))return e;const s=Rt(r),a=Ri(e,t),l=id(n,t);let d=o;const c=t.slice(0,-1);if(sd(c,n)){const p=t[t.length-1];(typeof p=="number"?p:0)o){const s=[...e];return s[n]=r-1,s}return e}function sd(e,t){if(e.length!==t.length)return!1;for(let n=0;ne.length||sd(e,t)}function es(e){const t={...e};if(t.props&&typeof t.props=="object"&&(t.props={...t.props}),typeof t.id=="string"&&t.id.length>0){const o=typeof t.name=="string"?t.name:"node";t.id=`${o}_${zb()}`}const n=Qt(e);return n.length>0&&(t.children=n.map(es)),t}function Ot(e,t,n){if(t.length===0)return n(e);const o=Rt(e),r=We(o,t);if(!r)return o;const s=n(r),a=rd(o,t);if(!a||a.index<0||a.index>=a.children.length)return o;const l=[...a.children];return l[a.index]=s,Zi(a,l),o}function zb(){return Math.floor(Math.random()*2821109907456).toString(36).padStart(8,"0")}function Vn(e){if(!Array.isArray(e)||e.length===0)return[];const t=[];for(const n of e){const o=Mb(n);o.length>0&&t.push(...o)}return t}function Mb(e){if(!e||typeof e!="object")return[];const t=e.__source,n=e._fromBase===!0,o=t?.kind;if(o==="extension"||o==="partial")return[];if(o==="base"||n){const s=Array.isArray(e.children)?e.children:[];return Vn(s)}const r={...e};return delete r.__source,delete r._fromBase,delete r.__injectedProps,Array.isArray(r.children)&&(r.children=Vn(r.children)),[r]}function vo(e){if(!e||typeof e!="object")return e;const t=e.__editor,n=t&&typeof t=="object"&&!Array.isArray(t)?t.original:void 0,o=n&&typeof n=="object"&&!Array.isArray(n)?n:void 0,r=Array.isArray(e.components)?e.components:[],s=r.length>0,a=Vn(r),l=o?{...o}:{...e};if(typeof l.extends=="string"&&l.extends.length>0){delete l.components;const c=l.slots&&typeof l.slots=="object"&&!Array.isArray(l.slots)?l.slots:{},p=Object.keys(c);if(!s&&p.length>0){const f={};for(const[y,g]of Object.entries(c))f[y]=Array.isArray(g)?Vn(g):g;l.slots=f}else if(p.length===1)l.slots={[p[0]]:a};else if(p.length===0)l.slots={content:a};else{const f={};for(const[y,g]of Object.entries(c))f[y]=Array.isArray(g)?Vn(g):g;l.slots=f}}else l.components=a,delete l.slots;return delete l.lock_version,delete l.__editor,l}const ad="extension",Un="extensionless";function Lb(){const e=globalThis?.G7Config;if(globalThis?.__g7AssetUrlMode===Un)return Un;const n=e?.assetUrlMode;return n===Un||n===ad?n:e?.settings?.general?.asset_url_mode===Un?Un:ad}function Pb(){return Lb()===Un}function en(e,t,n,o){const r=e.replace(/\/+$/,""),s=t.replace(/^\.+/,""),a=Pb()?r:`${r}.${s}`,l=[];return o&&l.push(o.replace(/^[?&]+/,"")),n!=null&&n!==""&&l.push(`v=${n}`),l.length>0?`${a}?${l.join("&")}`:a}const ld=/^\s*\$t:([a-zA-Z0-9._-]+)\s*$/,hr="custom.",Ob=/\{\{.*?\}\}/;function Bb(e){return!!(!e||e.iteration&&typeof e.iteration=="object")}function dd(e,t){const n=e?.text,o=typeof n=="string"?n:"";if(Bb(e)||typeof n!="string")return{sourceState:"plain_text",editable:!1,customKey:null,displayValue:o};const r=mt(o);if(r&&r.key.startsWith(hr)){const a=t?t(r.key):r.key;return{sourceState:"custom_key",editable:!0,customKey:r.key,displayValue:a,isParamKey:!0}}const s=ld.exec(o);if(s){const a=s[1];if(a.startsWith(hr)){const d=t?t(a):a;return{sourceState:"custom_key",editable:!0,customKey:a,displayValue:d}}const l=t?t(a):"";return{sourceState:"plain_text",editable:!0,customKey:null,displayValue:l&&l!==a?l:""}}if(Ob.test(o)){const a=ur(o),l=ld.exec(a);if(l&&l[1].startsWith(hr)){const f=l[1],y=t?t(f):f;return{sourceState:"custom_key",editable:!0,customKey:f,displayValue:y}}const d=Gi(o,t),c=ur(d),p=/\$t:[a-zA-Z0-9._-]+/.test(c);if(c.length>0){const f=Yi(o,t);return{sourceState:"plain_with_binding",editable:!0,customKey:null,displayValue:p?"":c,chipValue:f.keyifiable?f.chipValue:void 0,chipParamLabels:f.keyifiable?f.paramLabels:void 0}}return{sourceState:"binding_expression",editable:!1,customKey:null,displayValue:o}}return{sourceState:"plain_text",editable:!0,customKey:null,displayValue:o}}function Wb(){const{state:e}=Je(),t=Ft(),n=e.templateIdentifier,o=e.locale,r=e.selectedRoute?.layoutName??null,s=u.useCallback(p=>{try{const y=Pt.getInstance().translate(p,{templateId:n,locale:o});return y&&y!==p?y:""}catch{return""}},[n,o]),a=u.useCallback(p=>dd(p,s),[s]),l=u.useCallback(p=>{Et({op:"inline_edit_cancel",componentPath:p?at(p):null,timestamp:Date.now()})},[]),d=u.useCallback(async(p,f)=>{if(!t)return{kind:"error",message:"no document context"};const g={children:t.document?.raw?.components??[]},m=We(g,p),h=dd(m,s);if(!h.editable)return Et({op:"inline_edit_blocked_binding",sourceState:h.sourceState,componentPath:at(p),timestamp:Date.now()}),{kind:"blocked"};if(h.customKey){if(f===h.displayValue)return{kind:"noop",translationKey:h.customKey};_o(n,o,h.customKey,f);const E=await yr(n,h.customKey,o,f);return E.kind==="error"?{kind:"error",message:E.message}:(await tn(n,o),Et({op:"inline_edit_update_value",sourceState:"custom_key",componentPath:at(p),translationKey:h.customKey,toLocale:o,valueLength:f.length,valueHash:Ni(f),timestamp:Date.now()}),{kind:"updated",translationKey:h.customKey})}const v=typeof m?.text=="string"?m.text:"",b=h.sourceState==="plain_with_binding",x=Gi(v,s),k=b?Xl(x):f,w=E=>b?Yl(E,v):E;t.patchLayout(E=>Ot({children:E},p,$=>({...$,text:f})).children??[]);const _=await qn(n,r,o,k);if(_.kind==="error"||!_.resource)return{kind:"error",message:_.message};const C=_.resource.translation_key;return _o(n,o,C,k),t.patchLayout(E=>Ot({children:E},p,$=>({...$,text:w(`$t:${C}`)})).children??[]),await tn(n,o),Et({op:"inline_edit_create_key",sourceState:h.sourceState,componentPath:at(p),translationKey:C,toLocale:o,valueLength:f.length,valueHash:Ni(f),timestamp:Date.now()}),{kind:"created",translationKey:C,id:_.resource.id}},[t,n,o,r,s]),c=u.useCallback(async(p,f)=>{if(!t)return{kind:"error",message:"no document context"};const g={children:t.document?.raw?.components??[]},m=We(g,p),h=typeof m?.text=="string"?m.text:"",v=mt(h);if(v&&v.key.startsWith(hr))return br(n,v.key,o,f),_o(n,o,v.key,f),t.markDirty?.(),{kind:"updated",translationKey:v.key};const b=Yi(h,s),x=ur(f.replace(/\{\{?p\d+\}?\}/g," ")).replace(/\s+/g," ").trim(),k=await qn(n,r,o,x);if(k.kind==="error"||!k.resource)return{kind:"error",message:k.message};const w=k.resource.translation_key,_=An(f),C=k.resource.values??{},E={[o]:f};for(const S of Object.keys(C)){if(S===o)continue;let T=C[S]??"";for(const $ of _)T=qi(T,$);E[S]=T}return xo(n,w,E),_o(n,o,w,f),t.patchLayout(S=>Ot({children:S},p,A=>({...A,text:Gl(`$t:${w}`,b)})).children??[]),t.markDirty?.(),await tn(n,o),_o(n,o,w,f),Et({op:"inline_edit_create_key",sourceState:"plain_with_binding",componentPath:at(p),translationKey:w,toLocale:o,valueLength:f.length,valueHash:Ni(f),timestamp:Date.now()}),{kind:"created",translationKey:w}},[t,n,o,r]);return{classify:a,commit:d,trackCancel:l,keyifyChipValue:c}}async function qn(e,t,n,o){const r=`/api/admin/templates/${encodeURIComponent(e)}/custom-translations`;try{const s=await fetch(r,{method:"POST",credentials:"same-origin",headers:je({"Content-Type":"application/json"}),body:JSON.stringify({layout_name:t,locale:n,value:o})}),a=await s.json().catch(()=>null);if(!s.ok)return{kind:"error",message:a?.message??`HTTP ${s.status}`};const l=a?.data;return!l||typeof l.translation_key!="string"?{kind:"error",message:"invalid response"}:{kind:"ok",resource:l}}catch(s){return{kind:"error",message:s instanceof Error?s.message:"network error"}}}async function yr(e,t,n,o){const r=await cn(e,t);if(!r)return{kind:"error",message:"custom key not found"};const s=`/api/admin/templates/${encodeURIComponent(e)}/custom-translations/${r.id}`,a={...r.values??{},[n]:o};try{const l=await fetch(s,{method:"PUT",credentials:"same-origin",headers:je({"Content-Type":"application/json"}),body:JSON.stringify({values:a,expected_lock_version:r.lock_version})}),d=await l.json().catch(()=>null);return l.status===409?{kind:"error",message:"concurrent_modification"}:l.ok?{kind:"ok"}:{kind:"error",message:d?.message??`HTTP ${l.status}`}}catch(l){return{kind:"error",message:l instanceof Error?l.message:"network error"}}}async function cn(e,t){const n=`/api/admin/templates/${encodeURIComponent(e)}/custom-translations`;try{const o=await fetch(n,{method:"GET",credentials:"same-origin",headers:je()});if(!o.ok)return null;const s=(await o.json().catch(()=>null))?.data;return Array.isArray(s)?s.find(a=>a.translation_key===t)??null:null}catch{return null}}function _o(e,t,n,o){try{Pt.getInstance().setTranslationValue(e,t,n,o)}catch{}if(typeof window<"u")try{window.dispatchEvent(new CustomEvent(kt,{detail:{templateIdentifier:e,locale:t}}))}catch{}}const kt="g7le:translations-refreshed";async function tn(e,t){if(typeof window>"u")return;const n=t??window.__g7EditorContentLocale;if(n){try{const o=Pt.getInstance();try{const r=await fetch(en(`/api/templates/${encodeURIComponent(e)}/config`,"json"),{headers:{Accept:"application/json"},credentials:"same-origin"}).then(a=>a.ok?a.json():null),s=r?.data?.cache_version??r?.cache_version;if(typeof s=="number"&&s>0){o.setCacheVersion(s);const a=window;a.G7Config&&(a.G7Config.cache_version=s)}}catch{}await o.loadTranslations(e,n,"/api",!0)}catch{}try{window.dispatchEvent(new CustomEvent(kt,{detail:{templateIdentifier:e,locale:n}}))}catch{}}}const nn=new Map;function br(e,t,n,o){const r=nn.get(t)??{};r[n]=o,nn.set(t,r);try{Pt.getInstance().setTranslationValue(e,n,t,o)}catch{}if(typeof window<"u")try{window.dispatchEvent(new CustomEvent(kt,{detail:{templateIdentifier:e,locale:n}}))}catch{}}function xo(e,t,n){for(const[o,r]of Object.entries(n))br(e,t,o,r)}function It(e,t){return nn.get(e)?.[t]}function jb(e){const t=nn.get(e);return t?{...t}:void 0}function Hb(){return nn.size>0}function cd(e,t){for(const[n,o]of nn.entries())for(const[r,s]of Object.entries(o))try{e.setTranslationValue(t,r,n,s)}catch{}}async function Fb(e){if(nn.size===0)return{ok:0,failed:[]};const t=Array.from(nn.entries());let n=0;const o=[];for(const[r,s]of t)try{const a=await cn(e,r);if(!a){o.push(r);continue}const l={...a.values??{},...s},d=`/api/admin/templates/${encodeURIComponent(e)}/custom-translations/${a.id}`;if(!(await fetch(d,{method:"PUT",credentials:"same-origin",headers:je({"Content-Type":"application/json"}),body:JSON.stringify({values:l,expected_lock_version:a.lock_version})})).ok){o.push(r);continue}n+=1}catch{o.push(r)}for(const[r]of t)o.includes(r)||nn.delete(r);try{await tn(e)}catch{}return{ok:n,failed:o}}function Gn(e,t){const n=`layout_editor.locale.${e}`,o=t(n);return o&&o!==n?o:e.toUpperCase()}function ud(){if(typeof window>"u")return[];const t=window.G7Config?.appConfig?.supportedLocales;return Array.isArray(t)?t.filter(n=>typeof n=="string"&&n.length>0):[]}function Kb({locales:e}={}){const{t}=tt(),{state:n,dispatch:o}=Je();u.useEffect(()=>{window.__g7EditorContentLocale=n.locale},[n.locale]);const r=e??ud(),s=r.includes(n.locale)?r:[n.locale,...r];if(s.length<=1)return i.jsxs("span",{className:"g7le-locale-switcher g7le-locale-switcher--single","data-testid":"g7le-locale-switcher-single",style:{fontSize:11,color:"#475569",padding:"0 4px"},children:[t("layout_editor.chrome.toolbar.locale_switcher_label"),": ",Gn(n.locale,t)]});const a=l=>{if(l===n.locale)return;const d=n.locale;o({type:"SET_LOCALE",locale:l}),Et({op:"locale_switch",fromLocale:d,toLocale:l,timestamp:Date.now()})};return i.jsxs("div",{className:"g7le-locale-switcher","data-testid":"g7le-locale-switcher",role:"group","aria-label":t("layout_editor.chrome.toolbar.locale_switcher_label"),style:{display:"inline-flex",alignItems:"center",gap:4},children:[i.jsx("span",{style:{fontSize:11,color:"#475569"},children:t("layout_editor.chrome.toolbar.locale_switcher_label")}),i.jsx("div",{style:{display:"inline-flex",gap:2},children:s.map(l=>{const d=l===n.locale;return i.jsx("button",{type:"button","data-testid":`g7le-locale-${l}`,"aria-pressed":d,onClick:()=>a(l),style:{padding:"2px 8px",fontSize:12,cursor:"pointer",background:d?"#0f172a":"#f8fafc",color:d?"#fff":"#0f172a",border:"1px solid #cbd5e1",borderRadius:4},children:Gn(l,t)},l)})})]})}const Vb=/\{\{?(p\d+)\}?\}/g;function Ub(e){const t=[];if(typeof e!="string"||e.length===0)return t;const n=new RegExp(Vb.source,"g");let o=0,r;for(;(r=n.exec(e))!==null;){const s=r.index,a=s+r[0].length;s>o&&t.push({kind:"text",raw:e.slice(o,s),start:o,end:s}),t.push({kind:"chip",raw:r[0],paramName:r[1],start:s,end:a}),o=a}return o{n.push({kind:"text",text:a}),r=!0};for(const a of t)a.kind==="text"?(o=a.raw,s(o),o=""):(r||s(""),n.push({kind:"chip",paramName:a.paramName}),r=!1);return r||s(""),n}function Gb(e,t,n,o){const r=[];return e.forEach((s,a)=>{if(s.kind==="chip"){if(s.paramName===t)return;r.push(`{${s.paramName}}`)}else{const l=s.text??"";if(a===n){const d=Math.max(0,Math.min(o,l.length));r.push(l.slice(0,d),`{${t}}`,l.slice(d))}else r.push(l)}}),r.join("")}function mr({value:e,onChange:t,t:n,paramLabels:o,onRequestInsert:r,onRemoveChip:s,testIdSuffix:a="pc",disabled:l=!1,hideInsertButton:d=!1,caretRef:c}){const p=u.useMemo(()=>qb(e),[e]),f=u.useRef(null),[y,g]=u.useState(null),[m,h]=u.useState(null),[v,b]=u.useState(null),x=u.useMemo(()=>p.map(B=>B.kind==="chip"?`c:${B.paramName}`:"t").join("|"),[p]),k=u.useRef(null),w=u.useRef([]),_=u.useRef(!1);u.useLayoutEffect(()=>{const B=_.current;_.current=!1,!(!B&&k.current===x)&&(k.current=x,p.forEach((N,H)=>{if(N.kind!=="text")return;const L=w.current[H];L&&L.textContent!==(N.text??"")&&(L.textContent=N.text??"")}))},[x,p,e]);const C=()=>{const B=f.current;if(!B)return e;const N=[];for(const H of Array.from(B.querySelectorAll("[data-seg]")))H.dataset.seg==="chip"?N.push(`{${H.dataset.param??""}}`):N.push(H.textContent??"");return N.join("")},E=()=>{l||t(C())},S=u.useRef(null),T=u.useRef(!1),$=(B,N)=>{if(!l){B.preventDefault(),B.stopPropagation(),S.current={param:N,pointerId:B.pointerId},T.current=!1,g(N);try{B.currentTarget.setPointerCapture(B.pointerId)}catch{}}},A=B=>{const N=S.current;if(!N||B.pointerId!==N.pointerId)return;T.current=!0;const H=O(B.clientX,B.clientY);H&&(h({slotIdx:H.slotIdx,offset:H.offset}),b(Xb(f.current,H.slotIdx,H.offset)))},z=(B,N)=>{if(B.stopPropagation(),T.current){T.current=!1;return}s?.(N)},D=B=>{const N=S.current;if(!N||B.pointerId!==N.pointerId)return;try{B.currentTarget.releasePointerCapture(B.pointerId)}catch{}S.current=null,T.current&&typeof setTimeout=="function"&&setTimeout(()=>{T.current=!1},0);const H=O(B.clientX,B.clientY);if(g(null),h(null),b(null),!H)return;const L=f.current;if(!L)return;const P=Array.from(L.querySelectorAll("[data-seg]")).map(G=>G.dataset.seg==="chip"?{kind:"chip",paramName:G.dataset.param??""}:{kind:"text",text:G.textContent??""}),F=Gb(P,N.param,H.slotIdx,H.offset);F!==e&&(_.current=!0,t(F))},O=(B,N)=>{const H=f.current;if(!H)return null;const L=Array.from(H.querySelectorAll("[data-seg]")),I=H.ownerDocument,P=I.elementFromPoint(B,N);let F=P?.closest('[data-seg="text"]');if(F)return{slotIdx:L.indexOf(F),offset:Yb(I,F,B,N)};const G=P?.closest('[data-seg="chip"]');if(G){const Y=L.indexOf(G),Z=G.getBoundingClientRect();if(B=0){const se=L[Y-1];return{slotIdx:Y-1,offset:(se.textContent??"").length}}if(Y+1({s:Y,i:Z})).filter(Y=>Y.s.dataset.seg==="text").pop();return V?{slotIdx:V.i,offset:(V.s.textContent??"").length}:null},M=()=>{l||!r||r(pd(f.current,e.length))};return u.useEffect(()=>{if(c)return c.current=()=>pd(f.current,e.length),()=>{c.current=null}},[c,e.length]),i.jsxs("div",{style:{display:"flex",flexDirection:"column",gap:4},children:[i.jsxs("div",{ref:f,"data-testid":`g7le-chip-input-${a}`,className:"g7le-chip-input",style:Jb,children:[p.map((B,N)=>B.kind==="chip"?i.jsxs("span",{"data-seg":"chip","data-param":B.paramName,"data-testid":`g7le-chip-${a}-${B.paramName}`,contentEditable:!1,onPointerDown:H=>$(H,B.paramName??""),onPointerMove:A,onPointerUp:D,title:n("layout_editor.translation.placeholder_locked"),style:{...em,...y===B.paramName?tm:null,touchAction:"none"},children:[i.jsx("span",{"aria-hidden":"true",style:nm,children:"⠿"}),i.jsx("span",{"aria-hidden":"true",style:{marginRight:3},children:"🔗"}),i.jsx("span",{style:om,children:o?.[B.paramName??""]??B.paramName}),s&&!l&&i.jsx("button",{type:"button","data-testid":`g7le-chip-remove-${a}-${B.paramName}`,"aria-label":n("layout_editor.inline_binding.clear"),title:n("layout_editor.inline_binding.clear"),onPointerDown:H=>{H.stopPropagation(),H.preventDefault()},onClick:H=>z(H,B.paramName??""),style:rm,children:"✕"})]},`chip-${B.paramName}`):i.jsx("span",{ref:H=>{w.current[N]=H},"data-seg":"text",contentEditable:!l,suppressContentEditableWarning:!0,onInput:E,"data-testid":`g7le-chip-text-${a}-${N}`,style:{...Zb,...y?Qb:null,...m?.slotIdx===N?Rb:null}},`text-slot-${N}`)),v&&i.jsx("span",{"aria-hidden":"true","data-testid":`g7le-chip-caret-${a}`,style:{position:"absolute",left:v.left,top:v.top,width:2,height:v.height,background:"#2563eb",borderRadius:1,pointerEvents:"none",boxShadow:"0 0 0 1px rgba(37,99,235,0.3)"}})]}),i.jsxs("div",{style:{display:"flex",alignItems:"center",gap:6},children:[r&&!d&&i.jsxs("button",{type:"button","data-testid":`g7le-chip-insert-${a}`,onClick:M,disabled:l,style:im,children:["+ ",n("layout_editor.inline_binding.insert_data")]}),i.jsx("span",{style:sm,children:n("layout_editor.translation.placeholder_locked")})]})]})}function Yb(e,t,n,o){const r=(t.textContent??"").length;try{const a=e;let l=null,d=0;if(typeof a.caretRangeFromPoint=="function"){const c=a.caretRangeFromPoint(n,o);c&&(l=c.startContainer,d=c.startOffset)}else if(typeof a.caretPositionFromPoint=="function"){const c=a.caretPositionFromPoint(n,o);c&&(l=c.offsetNode,d=c.offset)}if(l&&t.contains(l))return Math.max(0,Math.min(d,r))}catch{}const s=t.getBoundingClientRect();return n>s.left+s.width/2?r:0}function Xb(e,t,n){if(!e)return null;const r=Array.from(e.querySelectorAll("[data-seg]"))[t];if(!r)return null;const s=e.getBoundingClientRect();try{const d=r.firstChild,c=e.ownerDocument;if(d&&d.nodeType===Node.TEXT_NODE&&(d.textContent??"").length>0){const p=c.createRange(),f=Math.max(0,Math.min(n,(d.textContent??"").length));p.setStart(d,f),p.setEnd(d,f);const y=p.getClientRects(),g=y.length>0?y[0]:p.getBoundingClientRect();if(g&&(g.height>0||g.width>=0))return{left:g.left-s.left,top:g.top-s.top,height:g.height||r.getBoundingClientRect().height}}}catch{}const a=r.getBoundingClientRect();return{left:(n>0?a.right:a.left)-s.left,top:a.top-s.top,height:a.height||16}}function pd(e,t){if(!e)return t;try{const n=e.ownerDocument.defaultView?.getSelection();if(!n||n.rangeCount===0)return t;const o=n.getRangeAt(0);let r=o.startContainer,s=null;for(;r&&r!==e;){if(r instanceof HTMLElement&&r.dataset.seg==="text"){s=r;break}r=r.parentNode}if(!s||!e.contains(s))return t;const a=Array.from(e.querySelectorAll("[data-seg]"));let l=0;for(const d of a){if(d===s)break;l+=d.dataset.seg==="chip"?`{${d.dataset.param??""}}`.length:(d.textContent??"").length}return l+o.startOffset}catch{return t}}const Jb={position:"relative",display:"flex",flexWrap:"wrap",alignItems:"center",gap:2,minHeight:30,padding:"4px 8px",border:"1px solid #cbd5e1",borderRadius:4,background:"#fff",fontSize:13,lineHeight:1.6},Zb={outline:"none",minWidth:8,minHeight:"1.2em",whiteSpace:"pre-wrap",color:"#0f172a",display:"inline-block"},Qb={boxShadow:"inset 0 -2px 0 #93c5fd",background:"#eff6ff",borderRadius:2},Rb={background:"#dbeafe",boxShadow:"inset 0 -2px 0 #2563eb"},em={display:"inline-flex",alignItems:"center",gap:1,background:"linear-gradient(180deg,#eff6ff,#dbeafe)",color:"#1d4ed8",border:"1px dashed #60a5fa",borderRadius:9999,padding:"1px 8px 1px 4px",margin:"0 1px",fontSize:12,fontWeight:600,cursor:"grab",userSelect:"none",whiteSpace:"nowrap",boxShadow:"0 1px 2px rgba(37,99,235,0.18)",verticalAlign:"middle"},tm={opacity:.5,borderStyle:"solid",cursor:"grabbing"},nm={color:"#2563eb",opacity:.7,fontSize:11,marginRight:1,cursor:"grab"},om={textDecoration:"none"},rm={marginLeft:4,width:14,height:14,lineHeight:"12px",padding:0,border:"none",borderRadius:9999,background:"rgba(29,78,216,0.12)",color:"#1d4ed8",fontSize:9,fontWeight:700,cursor:"pointer",display:"inline-flex",alignItems:"center",justifyContent:"center",flex:"0 0 auto"},im={fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#334155",padding:"3px 10px",cursor:"pointer"},sm={fontSize:10,color:"#94a3b8"};function fd(e){e&&(e.style.height="auto",e.style.height=`${Math.max(e.scrollHeight,28)}px`)}function am(e){fd(e)}function lm(e){const t=new Set;for(const n of e)for(const o of Object.keys(n.values??{}))t.add(o);return Array.from(t).sort()}function dm({templateIdentifier:e,layoutName:t,t:n,referencedKeys:o,onClose:r}){const[s,a]=u.useState([]),[l,d]=u.useState("idle"),[c,p]=u.useState(null),[f,y]=u.useState("all"),[g,m]=u.useState(new Set),[h,v]=u.useState({}),[b,x]=u.useState(null),[k,w]=u.useState({}),_=u.useCallback(async()=>{d("loading"),p(null);const N=await Oy(e,t);N.ok?(a(N.data),v({}),m(new Set),w({}),d("idle")):(p(N.message),d("error"))},[e,t]);u.useEffect(()=>{_()},[_]);const C=u.useMemo(()=>lm(s),[s]),E=u.useMemo(()=>s.filter(N=>f==="all"?!0:N.status===f).slice().sort((N,H)=>{const L=N.updated_at?Date.parse(N.updated_at):0,I=H.updated_at?Date.parse(H.updated_at):0;return I!==L?I-L:H.id-N.id}),[s,f]),S=u.useMemo(()=>s.filter(N=>N.status==="orphaned"),[s]),T=u.useCallback(N=>{const H={...N.values??{}};for(const L of Object.keys(H)){const I=It(N.translation_key,L);I!==void 0&&(H[L]=I)}return H},[]),$=u.useCallback(N=>h[N.id]??T(N),[h,T]),A=u.useCallback((N,H,L)=>{v(I=>{const P=I[N]??s.find(F=>F.id===N)?.values??{};return{...I,[N]:{...P,[H]:L}}})},[s]),z=u.useCallback(async N=>{x(N.id),w(I=>({...I,[N.id]:""}));const H=$(N),L=await By(e,N.id,H,N.lock_version);if(L.ok)a(I=>I.map(P=>P.id===N.id?L.data:P)),v(I=>{const P={...I};return delete P[N.id],P});else{const I=L.status===409?n("layout_editor.translation_manager.conflict"):L.message;w(P=>({...P,[N.id]:I}))}x(null)},[e,$,n]),D=u.useCallback(N=>typeof window<"u"&&typeof window.confirm=="function"?window.confirm(n("layout_editor.translation_manager.delete_confirm",{count:N})):!0,[n]),O=u.useCallback(async N=>{if(!D(1))return;const H=await Wy(e,N.id);H.ok?(a(L=>L.filter(I=>I.id!==N.id)),m(L=>{const I=new Set(L);return I.delete(N.id),I})):p(H.message)},[e,D]),M=u.useCallback(async N=>{if(N.length===0||!D(N.length))return;const H=await jy(e,N);if(H.ok){const L=new Set(N);a(I=>I.filter(P=>!L.has(P.id))),m(new Set)}else p(H.message)},[e,D]),B=u.useCallback(N=>{m(H=>{const L=new Set(H);return L.has(N)?L.delete(N):L.add(N),L})},[]);return i.jsxs("div",{className:"g7le-translation-manager","data-testid":"g7le-translation-manager",style:cm,children:[i.jsxs("div",{"data-modal-drag-handle":!0,style:um,children:[i.jsx("span",{style:{fontSize:14,fontWeight:700},"data-testid":"g7le-translation-manager-title",children:n("layout_editor.translation_manager.title")}),i.jsx("button",{type:"button","aria-label":"close",onClick:r,style:pm,"data-testid":"g7le-translation-manager-close",children:"✕"})]}),i.jsxs("div",{style:fm,children:[i.jsx("div",{style:{display:"inline-flex",gap:4},role:"tablist","aria-label":n("layout_editor.translation_manager.filter_label"),children:["all","active","orphaned"].map(N=>i.jsx("button",{type:"button",role:"tab","aria-selected":f===N,"data-active":f===N?"true":"false",onClick:()=>y(N),style:f===N?gm:gd,"data-testid":`g7le-translation-filter-${N}`,children:n(`layout_editor.translation_manager.filter_${N}`)},N))}),i.jsx("div",{style:{flex:1}}),g.size>0&&i.jsxs("button",{type:"button",onClick:()=>{M(Array.from(g))},style:hd,"data-testid":"g7le-translation-bulk-delete",children:["🗑 ",n("layout_editor.translation_manager.delete_selected",{count:g.size})]}),S.length>0&&i.jsxs("button",{type:"button",onClick:()=>{M(S.map(N=>N.id))},style:hd,"data-testid":"g7le-translation-purge-orphaned",children:["🧹 ",n("layout_editor.translation_manager.purge_orphaned",{count:S.length})]})]}),c&&i.jsx("div",{style:hm,"data-testid":"g7le-translation-manager-error",children:c}),i.jsx("div",{style:ym,children:l==="loading"?i.jsx("div",{style:yd,"data-testid":"g7le-translation-manager-loading",children:n("layout_editor.translation_manager.loading")}):E.length===0&&l!=="error"?i.jsx("div",{style:yd,"data-testid":"g7le-translation-manager-empty",children:n("layout_editor.translation_manager.empty")}):i.jsx("div",{"data-testid":"g7le-translation-list",style:bm,children:E.map(N=>{const H=$(N),L=N.status==="orphaned",I=o?o.has(N.translation_key):null;return i.jsxs("div",{style:mm,"data-testid":`g7le-translation-row-${N.id}`,children:[i.jsxs("div",{style:vm,children:[i.jsx("input",{type:"checkbox",checked:g.has(N.id),onChange:()=>B(N.id),"aria-label":n("layout_editor.translation_manager.select_aria",{key:N.translation_key}),"data-testid":`g7le-translation-select-${N.id}`}),i.jsx("code",{style:_m,children:N.translation_key}),L&&i.jsx("span",{style:xm,"data-testid":`g7le-translation-badge-orphaned-${N.id}`,children:n("layout_editor.translation_manager.badge_orphaned")}),I===!0&&i.jsx("span",{style:wm,"data-testid":`g7le-translation-badge-live-inuse-${N.id}`,children:n("layout_editor.translation_manager.badge_live_in_use")}),I===!1&&i.jsx("span",{style:km,"data-testid":`g7le-translation-badge-live-unused-${N.id}`,children:n("layout_editor.translation_manager.badge_live_unused")}),i.jsx("div",{style:{flex:1}}),i.jsx("button",{type:"button",onClick:()=>{O(N)},style:Tm,"data-testid":`g7le-translation-delete-${N.id}`,children:"🗑"})]}),i.jsxs("div",{style:Sm,children:[C.map(P=>{const F=H[P]??"",G=An(F).length>0;return i.jsxs("div",{style:Cm,"data-testid":`g7le-translation-field-${N.id}-${P}`,children:[i.jsxs("label",{style:$m,title:P,children:[Gn(P,n),i.jsxs("span",{style:Em,children:["(",P,")"]})]}),G?i.jsx("div",{style:{flex:1},"data-testid":`g7le-translation-chip-${N.id}-${P}`,children:i.jsx(mr,{value:F,onChange:V=>A(N.id,P,V),t:n,testIdSuffix:`${N.id}-${P}`})}):i.jsx("textarea",{value:F,rows:1,ref:am,onChange:V=>{A(N.id,P,V.currentTarget.value),fd(V.currentTarget)},style:Im,"data-testid":`g7le-translation-input-${N.id}-${P}`})]},P)}),i.jsxs("div",{style:{display:"flex",alignItems:"center",gap:8},children:[i.jsx("button",{type:"button",disabled:b===N.id,onClick:()=>{z(N)},style:Am,"data-testid":`g7le-translation-save-${N.id}`,children:b===N.id?n("layout_editor.translation_manager.saving"):n("layout_editor.translation_manager.save")}),k[N.id]?i.jsx("span",{style:{fontSize:11,color:"#dc2626"},"data-testid":`g7le-translation-row-error-${N.id}`,children:k[N.id]}):null]})]})]},N.id)})})}),i.jsx("div",{style:Dm,children:i.jsx("button",{type:"button",onClick:r,style:Nm,"data-testid":"g7le-translation-manager-footer-close",children:n("layout_editor.translation_manager.close")})})]})}const cm={display:"flex",flexDirection:"column",minHeight:0,flex:1},um={display:"flex",alignItems:"center",justifyContent:"space-between",padding:"12px 16px",borderBottom:"1px solid #e2e8f0"},pm={border:"none",background:"transparent",fontSize:16,color:"#94a3b8",cursor:"pointer"},fm={padding:"10px 16px",borderBottom:"1px solid #f1f5f9",display:"flex",gap:8,alignItems:"center"},gd={padding:"4px 10px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},gm={...gd,borderColor:"#2563eb",background:"#eff6ff",color:"#1d4ed8",fontWeight:600},hd={padding:"4px 10px",fontSize:12,border:"1px solid #fecaca",borderRadius:6,background:"#fff",color:"#dc2626",cursor:"pointer"},hm={padding:"8px 16px",fontSize:12,color:"#b91c1c",background:"#fef2f2"},ym={padding:16,overflow:"auto",flex:1,minHeight:120,display:"flex",flexDirection:"column",gap:12},yd={fontSize:13,color:"#94a3b8",textAlign:"center",padding:"32px 0"},bm={display:"flex",flexDirection:"column",gap:20},mm={border:"1px solid #e2e8f0",borderRadius:8,padding:10,display:"flex",flexDirection:"column",gap:6},vm={display:"flex",alignItems:"center",gap:8},_m={fontSize:11,background:"#f1f5f9",padding:"2px 6px",borderRadius:4,color:"#0f172a"},xm={fontSize:10,color:"#b45309",background:"#fef3c7",padding:"1px 6px",borderRadius:4,fontWeight:600},wm={fontSize:10,color:"#15803d",background:"#dcfce7",padding:"1px 6px",borderRadius:4,fontWeight:600},km={fontSize:10,color:"#9f1239",background:"#ffe4e6",padding:"1px 6px",borderRadius:4,fontWeight:600},Sm={display:"flex",flexDirection:"column",gap:6},Cm={display:"flex",alignItems:"flex-start",gap:8},$m={fontSize:12,color:"#475569",minWidth:88,fontWeight:600,paddingTop:4,display:"flex",flexDirection:"column",lineHeight:1.2},Em={fontSize:10,color:"#94a3b8",fontWeight:400},Im={flex:1,padding:"4px 8px",fontSize:13,border:"1px solid #cbd5e1",borderRadius:4,color:"#0f172a",resize:"vertical",overflow:"hidden",minHeight:28,lineHeight:1.4,fontFamily:"inherit",boxSizing:"border-box"},Am={padding:"5px 12px",fontSize:12,border:"none",borderRadius:6,background:"#2563eb",color:"#fff",cursor:"pointer"},Tm={padding:"4px 8px",fontSize:12,border:"1px solid #fecaca",borderRadius:6,background:"#fff",color:"#dc2626",cursor:"pointer"},Dm={padding:"10px 16px",borderTop:"1px solid #e2e8f0",display:"flex",justifyContent:"flex-end"},Nm={padding:"6px 14px",fontSize:13,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"};function zm(){const e=Ft(),{state:t}=Je(),n=e?.document?.raw??null,o=t.templateIdentifier??"",r=t.selectedRoute?.layoutName??"",s=u.useCallback((c,p)=>{const f=n?.[c];return f===void 0?p:f},[n]),a=u.useCallback((c,p,...f)=>{e&&(f.length>=1?e.patchDocumentRaw(c,p,f[0]):e.patchDocumentRaw(c,p))},[e]),l=u.useCallback(async(c,p)=>!o||!r?null:qn(o,r,c,p),[o,r]),d=u.useCallback(async(c,p,f)=>o?yr(o,c,p,f):null,[o]);return{raw:n,getValue:s,patch:a,createI18nKey:l,updateI18nKeyValue:d}}function Mm(e){const t=e.__source;return t&&(t.kind==="module"||t.kind==="plugin")&&t.identifier?`${t.kind}:${t.identifier}`:"template"}function bd(e,t){if(!t)return null;const n=Mm(e);if(n&&t.bySource){const a=t.bySource[n]?.[e.id];if(a!==void 0)return{value:a,match:{dataSourceId:e.id,resolvedBy:"spec_byId",sourceKey:n}}}const o=t.byDataSourceId?.[e.id];if(o!==void 0)return{value:o,match:{dataSourceId:e.id,resolvedBy:"spec_byId"}};const r=e.endpoint??"";if(t.byEndpointPattern&&typeof r=="string"&&r.length>0){for(const[s,a]of Object.entries(t.byEndpointPattern))if(Lm(r,s))return{value:a,match:{dataSourceId:e.id,resolvedBy:"spec_byEndpoint"}}}return null}function ts(e,t){const n=bd(e,t);if(n)return n;const o=e.fallback?.data;return o!==void 0?{value:{data:Array.isArray(o)?o:[o]},match:{dataSourceId:e.id,resolvedBy:"fallback"}}:{value:{},match:{dataSourceId:e.id,resolvedBy:"inferred"}}}function Lm(e,t){const n=t.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*");return new RegExp(`^${n}$`).test(e)}function Pm(e={}){const{spec:t,overrideSpec:n,onMatch:o}=e;return{has(r){return!0},resolve(r){const s=n?bd(r,n):null,{value:a,match:l}=s??ts(r,t);if(o)try{o(l)}catch{}return a}}}const md=Lt(),vd=md.DataBindingEngine,_d=md.dataBindingEngine;bo("PipeRegistry");function Om(e){let t=null,n=0;for(let o=0;o0?e[o-1]:"",a=o{const a=t[r],l=a==null?[]:String(a).split(",").map(d=>d.trim()).filter(Boolean);return l.length===0?"":l.join(" ?? ")+(s?" ?? ":"")});return n=n.replace(/\{(\w+)\}/g,(o,r)=>{const s=t[r];return s===void 0?o:s}),n}function vr(e){const t=e.trim(),n=/^\{\{([\s\S]*)\}\}$/.exec(t);return n?n[1].trim():t}function wd(e){const t=e.trim();return/^\{\{[\s\S]*\}\}$/.test(t)?t:`{{ ${t} }}`}function kd(e,t){const n={};for(const[r,s]of Object.entries(t))n[r]=typeof s=="string"?vr(s):s;const o=Wm(e.expr,n);return wd(o)}function jm(e,t){if(typeof e!="string")return null;const n=Sd(vr(e));for(const o of t){const r=Hm(n,o.expr,o.params);if(r)return{recipeId:o.id,params:r}}return null}function Sd(e){return e.replace(/\s+/g," ").trim()}function Cd(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function Hm(e,t,n){const o=[],r=new Set;let s="";const a=/\{(\w+)(\*?)\}(\s*\?\?\s*)?/g;let l=0,d;const c=Sd(t);for(;(d=a.exec(c))!==null;){const g=c.slice(l,d.index);s+=Cd(g).replace(/\\ /g,"\\s*");const m=d[2]==="*",h=!!d[3];m?(r.add(d[1]),s+="(.*?)",h&&(s+="(?:\\s*\\?\\?\\s*)?")):(s+="(.+?)",h&&(s+="\\s*\\?\\?\\s*")),o.push(d[1]),l=a.lastIndex}s+=Cd(c.slice(l)).replace(/\\ /g,"\\s*");let p;try{p=new RegExp(`^${s}$`)}catch{return null}const f=p.exec(e);if(!f)return null;const y={};if(o.forEach((g,m)=>{let h=(f[m+1]??"").trim();r.has(g)&&(h=h.split(/\s*\?\?\s*/).map(v=>v.trim()).filter(Boolean).join(", ")),y[g]=h}),n.length>0){const g=new Set(n.map(m=>m.key));for(const m of Object.keys(y))g.has(m)||delete y[m]}return y}function Fm(e){const t=`x.${e.field}`,n=Km(e.value);switch(e.cmp){case"=":return`${t} === ${n}`;case"!=":return`${t} !== ${n}`;case">":return`${t} > ${n}`;case"<":return`${t} < ${n}`;case">=":return`${t} >= ${n}`;case"<=":return`${t} <= ${n}`;case"includes":return`(${t} ?? []).includes(${n})`;default:return`${t} === ${n}`}}function Km(e){const t=e.trim();return t==="true"||t==="false"||t!==""&&!Number.isNaN(Number(t))||/^['"].*['"]$/.test(t)||/[.([{]/.test(t)?t:`'${t.replace(/'/g,"\\'")}'`}function ns(e){return!e||e.length===0?"true":e.map(Fm).join(" && ")}function Vm(e){const n=`(${(e.source??"").trim()||"[]"} ?? [])`;let o;switch(e.op){case"count":o=`${n}.filter(x => ${ns(e.conditions)}).length`;break;case"sum":{const r=(e.sumField??"").trim()||"value";o=`${n}.filter(x => ${ns(e.conditions)}).reduce((s, x) => s + (x.${r} ?? 0), 0)`;break}case"filter":o=`${n}.filter(x => ${ns(e.conditions)})`;break;case"toOptions":{const r=(e.valueField??"").trim()||"value",s=(e.labelField??"").trim()||"label";o=`${n}.map(x => ({ value: x.${r}, label: x.${s} }))`;break}case"nth":{const r=(e.index??"").trim()||"0",s=(e.prop??"").trim();o=`${n}[${r}]${s?`?.${s}`:""}`;break}case"firstOf":{const r=(e.candidates??[]).map(l=>l.trim()).filter(Boolean),s=r.length>0?r.join(" ?? "):"''",a=(e.fallback??"").trim();o=a?`${s} ?? '${a.replace(/'/g,"\\'")}'`:s;break}case"literal":{const r=e.literalKind??"string",s=(e.literalValue??"").trim();r==="number"?o=s===""?"0":s:r==="boolean"?o=s==="true"?"true":"false":o=`'${s.replace(/'/g,"\\'")}'`;break}default:o="[]"}return wd(o)}function Um(e){if(typeof e!="string")return null;const t=vr(e).trim();let n=/^\((.+?)\s*\?\?\s*\[\]\)\.filter\(x => (.+)\)\.length$/.exec(t);return n?{key:"",op:"count",source:n[1].trim(),conditions:os(n[2])}:(n=/^\((.+?)\s*\?\?\s*\[\]\)\.filter\(x => (.+)\)\.reduce\(\(s, x\) => s \+ \(x\.(\w+(?:\.\w+)*) \?\? 0\), 0\)$/.exec(t),n?{key:"",op:"sum",source:n[1].trim(),conditions:os(n[2]),sumField:n[3]}:(n=/^\((.+?)\s*\?\?\s*\[\]\)\.filter\(x => (.+)\)$/.exec(t),n?{key:"",op:"filter",source:n[1].trim(),conditions:os(n[2])}:(n=/^\((.+?)\s*\?\?\s*\[\]\)\.map\(x => \(\{ value: x\.(\w+(?:\.\w+)*), label: x\.(\w+(?:\.\w+)*) \}\)\)$/.exec(t),n?{key:"",op:"toOptions",source:n[1].trim(),valueField:n[2],labelField:n[3]}:(n=/^\((.+?)\s*\?\?\s*\[\]\)\[(.+?)\](?:\?\.(\w+(?:\.\w+)*))?$/.exec(t),n?{key:"",op:"nth",source:n[1].trim(),index:n[2].trim(),prop:n[3]}:null))))}function os(e){const t=e.trim();if(t==="true")return[];const n=[];for(const o of qm(t)){const r=Gm(o.trim());r&&n.push(r)}return n}function qm(e){const t=[];let n=0,o=0;for(let r=0;r=|<=|>|<)\s*(.+)$/.exec(e),t){const n={"===":"=","!==":"!=",">":">","<":"<",">=":">=","<=":"<="};return{field:t[1],cmp:n[t[2]]??"=",value:$d(t[3])}}return null}function $d(e){const t=e.trim(),n=/^'(.*)'$/.exec(t)||/^"(.*)"$/.exec(t);return n?n[1]:t}function Ed(e,t){const n=jm(e,t);if(n)return{kind:"preset",recipeId:n.recipeId,params:n.params};const o=Um(e);return o?{kind:"custom",model:o}:{kind:"advanced"}}function Ym(e){if(e==null)return"null";if(Array.isArray(e))return"list";switch(typeof e){case"number":return"number";case"boolean":return"boolean";case"string":return"string";default:return"object"}}function Id(e,t){if(typeof e!="string"||e.trim()==="")return{ok:!1,reason:"empty"};const n=vr(e);try{const o=Om(n)?_d.evaluatePipeExpression(n,t??{},{skipCache:!0}):_d.evaluateExpression(n,t??{},{skipCache:!0});return{ok:!0,value:o,type:Ym(o)}}catch(o){return{ok:!1,reason:o instanceof Error?o.message:String(o)}}}function Ad(e,t){const n=Array.isArray(e?.data_sources)?e.data_sources:[],o=[];for(const r of n){const s=r?.id;if(typeof s!="string"||s.length===0)continue;const{value:a}=ts(r,t?.sampleData),l=typeof r.label_key=="string"?r.label_key:void 0;o.push({id:s,sample:a,labelKey:l})}return o}function Td(e,t){const n=[];t?.sampleGlobal&&typeof t.sampleGlobal=="object"&&n.push({scope:"_global",tree:t.sampleGlobal});const o={},r={},s={},a=(l,d)=>{if(!(!d||typeof d!="object"||Array.isArray(d)))for(const[c,p]of Object.entries(d))if(p&&typeof p=="object"&&!Array.isArray(p)){const f=l[c]&&typeof l[c]=="object"&&!Array.isArray(l[c])?l[c]:l[c]={};a(f,p)}else c in l||(l[c]=p)};for(const l of t?.states?.groups??[])for(const d of l.items??[]){const c=d.initialState;c?.local&&a(o,c.local),c?.query&&a(r,c.query),c?.route&&a(s,c.route)}return Object.keys(o).length&&n.push({scope:"_local",tree:o}),Object.keys(r).length&&n.push({scope:"query",tree:r}),Object.keys(s).length&&n.push({scope:"route",tree:s}),e?.computed&&typeof e.computed=="object"&&!Array.isArray(e.computed)&&n.push({scope:"_computed",tree:e.computed}),n}function Dd(e,t){const n={};for(const r of t)r.scope!=="_computed"&&(n[r.scope]=r.tree);const o={};for(const[r,s]of Object.entries(e)){if(typeof s!="string"){o[r]=s;continue}const a=Id(s,n);o[r]=a.ok?a.value:s}return o}function Xm(e,t){const n=Ad(e,t),o=Td(e,t),r={};for(const s of n)s.sample!==void 0&&(r[s.id]=s.sample);for(const s of o)s.scope==="_computed"?r[s.scope]=Dd(s.tree,o):r[s.scope]=s.tree;return r}function Nd(e){const{raw:t,spec:n,evaluateComputed:o}=e;return u.useMemo(()=>{const r=Ad(t,n),s=Td(t,n);if(o){const a=s.findIndex(l=>l.scope==="_computed");if(a>=0){const l=Dd(s[a].tree,s);s[a]={scope:"_computed",tree:l}}}return ji({dataSources:r,states:s,spec:n??void 0})},[t,n,o])}function Jm(e){const{raw:t,spec:n,pageType:o}=e;return u.useMemo(()=>{const r=Array.isArray(t?.data_sources)?t.data_sources:[],s=[];for(const d of r){const c=d?.id;if(typeof c!="string"||c.length===0)continue;const{value:p}=ts(d,n?.sampleData),f=typeof d.label_key=="string"?d.label_key:void 0;s.push({id:c,sample:p,labelKey:f})}const a=[];n?.sampleGlobal&&typeof n.sampleGlobal=="object"&&a.push({scope:"_global",tree:n.sampleGlobal}),t?.computed&&typeof t.computed=="object"&&!Array.isArray(t.computed)&&a.push({scope:"_computed",tree:t.computed});const l=ji({dataSources:s,states:a,spec:n??void 0});if(typeof o=="string"&&o!=="")for(const d of["title","description"])l.push({expression:`{{_seo.${o}.${d}}}`,source:"_seo",sourceId:"_seo",path:`${o}.${d}`,shape:"scalar",preview:""});return l},[t,n,o])}function zd(e,t){const n=[],o=new Set,r=s=>{for(const a of s??[]){if(a.kind==="route"&&!a.isRedirect&&typeof a.path=="string"&&a.path.startsWith("/")&&!o.has(a.path)){o.add(a.path);const l=a.label.startsWith("$t:")?a.label.slice(3):a.label,d=a.label.startsWith("$t:")?t(l):l,c=d&&d!==l&&!d.startsWith("$t:")?d:"";n.push({value:a.path,label:c?`${c} (${a.path})`:a.path})}r(a.children)}};return r(e),n}function Md(e){const t=Array.isArray(e?.data_sources)?e.data_sources:[],n=[];for(const o of t){const r=o?.id;typeof r=="string"&&r.length>0&&n.push({value:r,label:r})}return n}function rs(e,t){const n=typeof e.id=="string"?e.id:"",o=typeof e.label_key=="string"?e.label_key:"";if(!o)return null;const r=o.startsWith("$t:")?o.slice(3):o,s=t(r);return s&&s!==r&&!s.startsWith("$t:")&&s!==n?s:null}function is(e,t){const n=e.__source;if(!n||typeof n!="object"||Array.isArray(n))return null;const o=n;if(o.kind!=="extension")return null;const r=o.extensionSourceType==="plugin"?t("layout_editor.data_sources.source.plugin"):t("layout_editor.data_sources.source.module"),s=typeof o.extensionIdentifier=="string"?o.extensionIdentifier:"",a=typeof o.extensionName=="string"?o.extensionName:"",l=a&&a!==s?`${a} (${s})`:s;return l?`${r}: ${l}`:r}function Zm(e,t,n){const o=Array.isArray(e?.data_sources)?e.data_sources:[],r=[],s=new Set;for(const a of o){const l=a?.id;typeof l!="string"||l.length===0||s.has(l)||(s.add(l),r.push({id:l,friendly:rs(a,t),source:is(a,n)}))}return r}function Ld(e){const t=e?.states?.groups??[],n=new Set;for(const o of t)for(const r of o.items??[]){for(const s of Object.keys(r.initialState?.local??{}))n.add(s);for(const s of Object.keys(r.initialState?.global??{}))n.add(s)}return Array.from(n).map(o=>({value:o,label:o}))}function Qm(e,t){const n=Array.isArray(e?.modals)?e.modals:[],o=[],r=new Set;for(const s of n){const a=s?.id;typeof a!="string"||a.length===0||r.has(a)||(r.add(a),o.push({value:a,label:Rm(s,a,t)}))}return o}function Rm(e,t,n){const r=e?.meta?.editor_label;if(typeof r=="string"&&r.length>0)return Pd(r,n);const s=e?.title;return typeof s=="string"&&s.length>0?Pd(s,n):t}function Pd(e,t){if(e.startsWith("$t:")){const n=e.slice(3),o=t?t(n):n;return o&&o!==n?o:n}return e}const Od=bo("ActionRecipeEngine"),ev={api:"apiCall",nav:"navigate",showToast:"toast",setLocalState:"setState"},tv=new Set(["comment"]);function wo(e){if(!e||typeof e!="object")return[];const t=[];for(const[n,o]of Object.entries(e)){if(tv.has(n)||!o||typeof o!="object")continue;const r=o,s=r.build&&typeof r.build=="object"?r.build:typeof r.handler=="string"?{handler:r.handler}:void 0;!s||typeof s.handler!="string"||s.handler.length===0||t.push({id:n,label:typeof r.label=="string"?r.label:void 0,params:Array.isArray(r.params)?r.params:[],build:s})}return t}function on(e){const t=/^\{\{\s*([\w.]+)\s*\}\}$/.exec(e);return t?t[1]:null}const _r="...";function nv(e,t){return e.replace(/\{\{\s*([\w.]+)\s*\}\}/g,(n,o)=>{const r=t[o];return r==null?"":String(r)})}function ss(e,t){if(typeof e=="string"){const n=on(e);return n!==null?n in t?t[n]:void 0:e.includes("{{")?nv(e,t):e}if(Array.isArray(e))return e.map(n=>ss(n,t)).filter(n=>n!==void 0);if(e&&typeof e=="object"){const n={};for(const[o,r]of Object.entries(e)){if(o===_r&&typeof r=="string"){const d=on(r),c=d!==null?t[d]:void 0;if(c&&typeof c=="object"&&!Array.isArray(c))for(const[p,f]of Object.entries(c))f===void 0||p===""||(n[p]=f);continue}const s=ss(r,t);if(s===void 0||Array.isArray(s)&&s.length===0&&(o==="onSuccess"||o==="onError"))continue;const a=on(o),l=a!==null?a in t?String(t[a]):null:o;l===null||l===""||s&&typeof s=="object"&&!Array.isArray(s)&&Object.keys(s).length===0||(n[l]=s)}return n}return e}function ov(e){const t=e.handler;if(typeof t!="string")return e;const n=ev[t];if(n)return Od.warn(`action recipe used forbidden handler "${t}" — corrected to "${n}" (core handler rules)`),{...e,handler:n};if(t==="apiCall"||t==="navigate"){const o=e.params;if(o&&typeof o=="object")for(const r of["target","onSuccess","onError"])r in o&&Od.warn(`action recipe build placed "${r}" inside params for handler "${t}" — should be top-level (core handler rules)`)}return e}function xr(e,t){const n=ss(e.build,t);if(typeof n.handler!="string"&&e.build.handler.includes("{{")&&(n.handler=e.build.handler),e.build.handler.includes("{{")){const o=new Set(e.params.filter(r=>r.required).map(r=>r.key));o.size>0&&Bd(e.build,n,o)}return ov(n)}function Bd(e,t,n){if(!e||typeof e!="object"||Array.isArray(e)||!t||typeof t!="object"||Array.isArray(t))return;const o=e,r=t;for(const[s,a]of Object.entries(o))if(s!=="handler"){if(typeof a=="string"){const l=on(a);l!==null&&n.has(l)&&!(s in r)&&(r[s]=a)}else if(a&&typeof a=="object"){if(!(s in r)||typeof r[s]!="object"||r[s]===null)if(JSON.stringify(a).includes("{{"))r[s]={};else continue;Bd(a,r[s],n)}}}function rv(e,t){if(!e||typeof e!="object")return null;const n=e.handler;if(typeof n!="string")return null;let o=null,r=-1;for(const s of t){const a=s.build.handler.includes("{{");if(!a&&s.build.handler!==n||a&&!iv(s.build,e))continue;const l=sv(s.build,e);if(l===null)continue;const d=Object.keys(l).length;d>r&&(r=d,o={recipeId:s.id,values:l})}return o}function iv(e,t){if(typeof t.handler=="string"&&t.handler===e.handler)return!0;const n=e.params;if(!n||typeof n!="object")return!1;const o=t.params;if(!o||typeof o!="object")return!1;const r=o,s=Object.entries(n).filter(([,a])=>typeof a=="string"&&on(a)!==null);return s.length===0?!1:s.every(([a])=>a in r)}function sv(e,t){const n={},o=(r,s)=>{if(typeof r=="string"){const a=on(r);return a!==null?(n[a]=s,!0):r.includes("{{")?typeof s=="string":r===s}if(Array.isArray(r))return Array.isArray(s);if(r&&typeof r=="object"){if(!s||typeof s!="object"||Array.isArray(s))return!1;const a=s,l=r[_r],d=typeof l=="string"?on(l):null,c=new Set(Object.keys(r).filter(p=>p!==_r));for(const[p,f]of Object.entries(r))if(p!==_r){if(typeof f=="string"&&on(f)!==null){const y=on(f);p in a&&(n[y]=a[p]);continue}if(!(p in a)){if(p==="onSuccess"||p==="onError"||p==="params"||p==="identity_target")continue;return!1}if(!o(f,a[p]))return!1}if(d!==null){const p={};for(const[f,y]of Object.entries(a))c.has(f)||(p[f]=y);Object.keys(p).length>0&&(n[d]=p)}return!0}return r===s};return o(e,t)?n:null}function Yn(e,t){const n=typeof e?.handler=="string"?e.handler:"",o=rv(e,t);return o?{kind:"preset",recipeId:o.recipeId,values:o.values,handler:n}:{kind:"advanced",handler:n}}function Wd(e,t,n){const o=e?.label,r=o?n?n(o):o:"",s=[],a=e?.params??[];for(const l of a){if(l.advanced||l.widget==="action-list")continue;const d=t[l.key];if(d==null||d==="")continue;const c=av(d);if(c!==""&&(s.push(c),s.length>=2))break}return s.length===0?r:`${r} (${s.join(", ")})`}function av(e){return typeof e=="string"?e:typeof e=="number"||typeof e=="boolean"?String(e):Array.isArray(e)?`[${e.length}]`:e&&typeof e=="object"?"{…}":""}function jd(e,t){const n={...e};return typeof t=="string"&&t.trim().length>0?n.if=t.trim():delete n.if,n}function as(e){const t=e?.if;return typeof t=="string"&&t.trim().length>0?t:null}const lv=/^\s*\$t:(custom\.[a-zA-Z0-9._-]+)\s*$/,dv=/^\s*\$t:([a-zA-Z0-9._-]+)\s*$/,cv=/\{\{.*?\}\}/;function Hd(e,t){const n=typeof e=="string"?e:"",o=lv.exec(n);if(o){const a=o[1],l=t?t(a):"";return{customKey:a,binding:!1,paramKey:!1,displayValue:l}}const r=mt(n);if(r&&r.key.startsWith("custom.")){const a=t?t(r.key):"";return{customKey:r.key,binding:!1,paramKey:!0,displayValue:a}}if(cv.test(n))return{customKey:null,binding:!0,paramKey:!1,displayValue:n};const s=dv.exec(n);if(s){const a=s[1],l=t?t(a):"";return{customKey:null,binding:!1,paramKey:!1,displayValue:l&&l!==a?l:""}}return{customKey:null,binding:!1,paramKey:!1,displayValue:n}}function uv(){const{state:e}=Je(),t=e.templateIdentifier,n=e.locale,o=e.selectedRoute?.layoutName??null,r=u.useCallback(l=>{try{const c=Pt.getInstance().translate(l,{templateId:t,locale:n});return c&&c!==l?c:""}catch{return""}},[t,n]),s=u.useCallback(l=>Hd(l,r),[r]),a=u.useCallback(async(l,d)=>{const c=Hd(l,r);if(c.binding)return{kind:"blocked"};if(c.customKey){if(d===c.displayValue)return{kind:"noop",customKey:c.customKey};const y=await yr(t,c.customKey,n,d);return y.kind==="error"?{kind:"error",message:y.message}:(await tn(t,n),{kind:"updated",customKey:c.customKey})}const p=await qn(t,o,n,d);if(p.kind==="error"||!p.resource)return{kind:"error",message:p.message};const f=p.resource.translation_key;return await tn(t,n),{kind:"created",customKey:f,token:`$t:${f}`}},[t,n,o,r]);return{locale:n,templateIdentifier:t,translate:r,classify:s,commitText:a}}async function ls(e,t,n,o,r,s,a,l,d,c){const p=c?Yi(o,c):null,f=!!(p&&p.keyifiable),y=d?d(o):o,g=f?p.chipValue:Xl(y),h=`p${f?p.bindings.length:cr(y).length}`,v=Math.max(0,Math.min(r,g.length)),b=Hl(g,v,h),x=ur(b).replace(/\{\{?p\d+\}?\}/g," ").replace(/\s+/g," ").trim(),k=x.length>0?x:b,w=await qn(e,t,n,k);if(w.kind==="error"||!w.resource)return{kind:"error",message:w.message};const _=w.resource.translation_key,C=An(b),E=w.resource.values??{},S={[n]:b};for(const z of Object.keys(E)){if(z===n)continue;let D=E[z]??"";for(const O of C)D=qi(D,O);S[z]=D}xo(e,_,S);const T=En(s,a,l);return{kind:"ok",text:`${f?Gl(`$t:${_}`,p):Yl(`$t:${_}`,y)}|${h}=${T}`,key:_}}async function wr(e,t,n,o,r,s,a){let l=mt(n);if(!l&&typeof n=="string"){const h=/^\s*\$t:(custom\.[A-Za-z0-9._-]+)\s*$/.exec(n);h&&(l={key:h[1],params:[]})}if(!l)return{kind:"error",message:"not a param key"};if(!l.key.startsWith("custom."))return{kind:"error",message:"not a custom param key"};const d=hb(l.params.map(h=>h.name)),c=En(r,s,a),p=`${n.trim()}|${d}=${c}`,f=await cn(e,l.key),y=jb(l.key)??{},g=new Set([...Object.keys(f?.values??{}),...Object.keys(y),t]);if(g.size===0)return{kind:"error",message:"custom key not found"};const m={};for(const h of g){const v=It(l.key,h)??f?.values?.[h]??"";m[h]=h===t?Hl(v,o,d):qi(v,d)}return xo(e,l.key,m),{kind:"ok",text:p,paramName:d}}async function ko(e,t,n,o){return br(e,t,n,o),{kind:"ok"}}async function pv(e,t,n){const o=await cn(e,t);if(!o)return{kind:"error",message:"custom key not found"};const r={};for(const[s,a]of Object.entries(o.values??{})){const l=It(t,s)??a;r[s]=n(l)}xo(e,t,r);try{const s=Pt.getInstance();for(const[a,l]of Object.entries(r))s.setTranslationValue(e,a,t,l)}catch{}return{kind:"ok"}}function Fd(e,t,n){return pv(e,t,o=>Ql(o,n))}function Kd(e,t){if(!(typeof window>"u"))try{window.dispatchEvent(new CustomEvent(kt,{detail:{templateIdentifier:e,locale:t}}))}catch{}}async function kr(e,t,n,o){const r=await Fd(e,t,n);return Kd(e,o),r}const fv=/^\s*\$t:(custom\.[a-zA-Z0-9._-]+)\s*$/;function gv(e,t){const n=s=>{const a=[],l=/\{\{?(p\d+)\}?\}/g;let d;for(;(d=l.exec(s))!==null;)a.push(d[1]);return a},o=n(e),r=n(t);return o.length!==r.length?!0:o.some((s,a)=>s!==r[a])}function Vd(e){const t=e.text;if(typeof t!="string")return null;const n=fv.exec(t);if(n)return n[1];const o=mt(t);return o&&o.key.startsWith("custom.")?o.key:null}function ds(e){const t={},n=mt(typeof e=="string"?e:"");for(const o of n?.params??[])t[o.name]=Vt(o.expression);return t}function hv(e){return ds(typeof e.text=="string"?e.text:"")}function Ud({customKey:e,templateIdentifier:t,t:n,paramLabels:o,locales:r,onRemoveParam:s}){const a=r??ud(),l=Ft(),d=o??{},[c,p]=u.useState({status:"idle",values:{}}),[f,y]=u.useState(!1),[g,m]=u.useState(!1),[h,v]=u.useState(null),b=u.useRef({});u.useEffect(()=>{let $=!1;if(!e){p({status:"not_a_key",values:{}});return}return p({status:"loading",values:{}}),(async()=>{const A=await cn(t,e);if($)return;if(!A){p({status:"error",values:{}});return}const z={...A.values??{}};for(const D of Object.keys(z)){const O=It(e,D);O!==void 0&&(z[D]=O)}for(const D of a){const O=It(e,D);O!==void 0&&(z[D]=O)}b.current={...z},p({status:"loaded",id:A.id,lockVersion:A.lock_version,values:z})})(),()=>{$=!0}},[e,t]),u.useEffect(()=>{if(typeof window>"u"||!e)return;const $=A=>{const z=A.detail;z?.templateIdentifier&&z.templateIdentifier!==t||p(D=>{if(D.status!=="loaded")return D;let O=!1;const M={...D.values};for(const B of Object.keys(M)){const N=It(e,B);N!==void 0&&N!==M[B]&&(M[B]=N,O=!0)}return O?(b.current={...b.current,...M},{...D,values:M}):D})};return window.addEventListener(kt,$),()=>window.removeEventListener(kt,$)},[e,t]);const x=u.useCallback(($,A)=>{p(z=>{const D=z.values[$]??"",O={...z,values:{...z.values,[$]:A}};return e&&gv(D,A)&&(br(t,e,$,A),b.current={...b.current,[$]:A},l?.markDirty?.(),Kd(t,$)),O})},[e,t,l]),k=u.useCallback($=>{p(A=>{const z={};for(const[D,O]of Object.entries(A.values))z[D]=Ql(O,$);return b.current={...z},{...A,values:z}}),s?.($)},[s]),w=u.useMemo(()=>[...Object.values(c.values),...Object.values(b.current)].some(A=>An(A).length>0),[c.values]),_=u.useRef(null),C=u.useCallback(()=>{m(!0),_.current&&clearTimeout(_.current),_.current=setTimeout(()=>m(!1),1500)},[]);u.useEffect(()=>()=>{_.current&&clearTimeout(_.current)},[]);const E=u.useCallback(async()=>{if(m(!1),c.status!=="loaded"||c.id===void 0)return;const $=b.current;for(const[z,D]of Object.entries(c.values)){const O=$[z]??"";if(w){const M=An(O);if(M.length===0)continue;const B=An(D);if(!(M.length===B.length&&M.every((H,L)=>H===B[L]))){v(n("layout_editor.translation.placeholder_mismatch"));return}continue}if(Ui(O)&&!kb(O,D)){v(n("layout_editor.translation.token_mismatch"));return}}if(y(!0),v(null),w){xo(t,e??"",c.values),b.current={...c.values},l?.markDirty?.(),Et({op:"translation_field_update",translationKey:e,changedLocales:Object.keys(c.values),timestamp:Date.now()}),await new Promise(z=>setTimeout(z,0)),y(!1),C();return}const A=`/api/admin/templates/${encodeURIComponent(t)}/custom-translations/${c.id}`;try{const z=await fetch(A,{method:"PUT",credentials:"same-origin",headers:je({"Content-Type":"application/json"}),body:JSON.stringify({values:c.values,expected_lock_version:c.lockVersion??0})}),D=await z.json().catch(()=>null);if(z.status===409){v(n("layout_editor.translation.conflict"));return}if(!z.ok){v(D?.message??n("layout_editor.translation.save_failed"));return}const O=typeof D?.data?.lock_version=="number"?D.data.lock_version:(c.lockVersion??0)+1;p(M=>({...M,lockVersion:O})),b.current={...c.values},await tn(t),Et({op:"translation_field_update",translationKey:e,changedLocales:Object.keys(c.values),timestamp:Date.now()}),C()}catch(z){v(z instanceof Error?z.message:n("layout_editor.translation.save_failed"))}finally{y(!1)}},[c,t,e,n]);if(c.status==="not_a_key"||!e)return i.jsx("div",{"data-testid":"g7le-translation-not-a-key",style:cs,children:n("layout_editor.translation.not_a_key")});if(c.status==="loading"||c.status==="idle")return i.jsx("div",{"data-testid":"g7le-translation-loading",style:cs,children:n("layout_editor.translation.loading")});if(c.status==="error")return i.jsx("div",{"data-testid":"g7le-translation-error",style:cs,children:n("layout_editor.translation.load_failed")});const S=new Set([...a,...Object.keys(c.values)]),T=Array.from(S);return i.jsxs("div",{"data-testid":"g7le-translation-field",style:{display:"flex",flexDirection:"column",gap:8},children:[i.jsxs("div",{"data-testid":"g7le-translation-key",style:yv,children:[n("layout_editor.translation.key_label"),": ",i.jsx("code",{style:bv,children:e})]}),T.map($=>{const A=c.values[$]??"",z=A.trim().length===0;return i.jsxs("div",{className:"g7le-translation-row","data-testid":`g7le-translation-row-${$}`,style:mv,children:[i.jsxs("label",{htmlFor:`g7le-translation-input-${$}`,style:{fontSize:12,color:"#475569",minWidth:72,display:"inline-flex",alignItems:"center",gap:4},children:[Gn($,n),z&&i.jsx("span",{"data-testid":`g7le-translation-missing-${$}`,style:vv,children:n("layout_editor.translation.needs_translation")})]}),w?i.jsx("div",{style:{flex:1},"data-testid":`g7le-translation-chip-${$}`,children:i.jsx(mr,{value:A,onChange:D=>x($,D),t:n,paramLabels:d,testIdSuffix:$,onRemoveChip:s?k:void 0})}):i.jsx("input",{id:`g7le-translation-input-${$}`,type:"text","data-testid":`g7le-translation-input-${$}`,value:A,placeholder:z?n("layout_editor.translation.needs_translation"):"",onChange:D=>x($,D.currentTarget.value),onBlur:E,style:{flex:1,padding:"4px 8px",fontSize:13,border:"1px solid #cbd5e1",borderRadius:4,color:z?"#94a3b8":"#0f172a"}})]},$)}),i.jsxs("div",{style:{display:"flex",alignItems:"center",gap:8},children:[i.jsx("button",{type:"button","data-testid":"g7le-translation-save",disabled:f,onClick:E,style:_v,children:n(f?"layout_editor.translation.saving":"layout_editor.translation.save")}),g&&!f&&i.jsxs("span",{"data-testid":"g7le-translation-saved",style:{fontSize:11,color:"#16a34a",display:"inline-flex",alignItems:"center",gap:4},children:["✓ ",n("layout_editor.translation.saved")]}),h&&i.jsx("span",{"data-testid":"g7le-translation-save-error",style:{fontSize:11,color:"#dc2626"},children:h})]})]})}const cs={fontSize:12,color:"#94a3b8",padding:"16px 0",textAlign:"center"},yv={fontSize:12,color:"#475569"},bv={fontSize:11,background:"#f1f5f9",padding:"1px 4px",borderRadius:3,color:"#0f172a"},mv={display:"flex",alignItems:"center",gap:8},vv={fontSize:10,color:"#b45309",background:"#fef3c7",padding:"0 4px",borderRadius:3},_v={padding:"6px 12px",fontSize:12,border:"none",borderRadius:6,background:"#2563eb",color:"#fff",cursor:"pointer"};function Xn({anchorRef:e,open:t,onClose:n,children:o,placement:r="bottom",minWidth:s=240,maxWidth:a=360,margin:l=8,testid:d}){const c=u.useRef(null),[p,f]=u.useState(null),y=u.useCallback(()=>{const m=e.current,h=c.current;if(!m||!h)return;const v=m.getBoundingClientRect(),b=window.innerWidth,x=window.innerHeight,k=h.getBoundingClientRect(),w=Math.min(Math.max(k.width,s),a),_=k.height;let C=v.left;C+w+l>b&&(C=v.right-w),C=Math.min(Math.max(C,l),b-w-l);const E=x-v.bottom-l,S=v.top-l;let T,$;const A=r==="bottom",z=E>=Math.min(_,160),D=S>=Math.min(_,160);A&&z||A&&!D?(T=v.bottom+4,$=E):!A&&D||D?(T=v.top-4-Math.min(_,S),$=S):E>=S?(T=v.bottom+4,$=E):(T=l,$=S),f({left:C,top:T,maxHeight:Math.max($,120)})},[e,r,s,a,l]);if(u.useLayoutEffect(()=>{if(!t){f(null);return}y()},[t,y]),u.useEffect(()=>{if(!t)return;const m=()=>y();window.addEventListener("resize",m),window.addEventListener("scroll",m,!0);const h=b=>{const x=c.current,k=e.current,w=b.target;x?.contains(w)||k?.contains(w)||n()},v=b=>{b.key==="Escape"&&n()};return document.addEventListener("pointerdown",h,!0),document.addEventListener("keydown",v),()=>{window.removeEventListener("resize",m),window.removeEventListener("scroll",m,!0),document.removeEventListener("pointerdown",h,!0),document.removeEventListener("keydown",v)}},[t,y,n,e]),!t)return null;const g={...xv,minWidth:s,maxWidth:`min(${a}px, calc(100vw - ${l*2}px))`,...p?{left:p.left,top:p.top,maxHeight:p.maxHeight,visibility:"visible"}:{left:-9999,top:-9999,visibility:"hidden"}};return i.jsx("div",{ref:c,"data-testid":d,style:g,role:"dialog",children:o})}const xv={position:"fixed",zIndex:1e3,background:"#fff",border:"1px solid #cbd5e1",borderRadius:8,boxShadow:"0 4px 16px rgba(0,0,0,0.14)",padding:6,overflowY:"auto",boxSizing:"border-box"};function qd(e,t,n){if(!t)return n;const o=e(t);return!o||o===t||o.startsWith("$t:")?n:o}function $t({candidates:e,t,onSelect:n,testIdSuffix:o,defaultOpen:r=!1,floating:s=!0}){const[a,l]=u.useState(r),[d,c]=u.useState(""),p=u.useRef(null),f=u.useMemo(()=>Fi(e,"scalar").map(h=>({...h,resolvedLabel:qd(t,h.labelKey,h.path||h.sourceId)})),[e,t]),y=u.useMemo(()=>Ki(f,d),[f,d]),g=i.jsxs(i.Fragment,{children:[i.jsx("input",{type:"text",autoFocus:!0,"data-testid":`g7le-inline-binding-search-input-${o}`,value:d,placeholder:t("layout_editor.inline_binding.search_placeholder"),onChange:m=>c(m.target.value),style:Cv}),i.jsx("div",{style:$v,children:y.length===0?i.jsx("div",{style:Ev,children:t("layout_editor.inline_binding.no_results")}):y.map(m=>i.jsxs("button",{type:"button","data-testid":`g7le-inline-binding-candidate-${m.expression}`,onClick:()=>{n(m),l(!1),c("")},style:Iv,children:[i.jsxs("span",{style:{display:"flex",flexDirection:"column",alignItems:"flex-start"},children:[i.jsx("span",{style:{fontSize:12},children:m.resolvedLabel}),i.jsxs("span",{style:Av,children:[qd(t,m.groupLabelKey,m.sourceId)," · ",m.path||m.sourceId]})]}),i.jsx("span",{style:Tv,children:m.preview})]},m.expression))})]});return i.jsxs("div",{style:s?Sv:{marginTop:4},children:[i.jsx("button",{ref:p,type:"button","data-testid":`g7le-inline-binding-search-toggle-${o}`,onClick:()=>l(m=>!m),title:t("layout_editor.inline_binding.search"),"aria-label":t("layout_editor.inline_binding.search"),"aria-expanded":a,style:wv,children:"🔍"}),s?i.jsx(Xn,{anchorRef:p,open:a,onClose:()=>l(!1),testid:`g7le-inline-binding-picker-${o}`,children:g}):a&&i.jsx("div",{"data-testid":`g7le-inline-binding-picker-${o}`,style:kv,children:g})]})}const wv={fontSize:13,lineHeight:1,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#334155",padding:"4px 8px",cursor:"pointer",textAlign:"center",whiteSpace:"nowrap"},kv={marginTop:4,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",padding:6},Sv={display:"inline-block"},Cv={width:"100%",padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,boxSizing:"border-box"},$v={marginTop:4,maxHeight:200,overflowY:"auto",display:"flex",flexDirection:"column",gap:2},Ev={fontSize:11,color:"#94a3b8",padding:"8px 0",textAlign:"center"},Iv={display:"flex",alignItems:"center",justifyContent:"space-between",gap:8,border:"none",background:"transparent",cursor:"pointer",padding:"6px 8px",borderRadius:4,textAlign:"left"},Av={fontSize:10,color:"#94a3b8"},Tv={fontSize:10,color:"#64748b",fontFamily:"monospace",whiteSpace:"nowrap"},Dv=["$localized"];function Jn(e){const t=e.trim();if(!t.startsWith("{{")||!t.endsWith("}}")||t.length<4)return null;let n=0;for(let o=0;oun(n).decomposed)}function So(e,t,n,o){switch(e.kind){case"leaf":Yd(e.text,t,n,o);return;case"conditional":So(e.then,t,n,o),o.push({kind:"ellipsis",text:"⋯"});return;case"fallback":So(e.primary,t,n,o);return;case"concat":e.parts.forEach(r=>So(r,t,n,o));return;default:o.push({kind:"text",text:e.source});return}}function Yd(e,t,n,o){const r=e.trim();if(/^\{\{([\s\S]*)\}\}$/.exec(r)&&!r.slice(2,-2).includes("}}")){o.push({kind:"chip",text:n(r)});return}if(r.startsWith("$t:")){const a=r.slice(3).split("|")[0];o.push({kind:"text",text:t(a)||a});return}for(const a of e.split(/(\{\{(?:[^}]|\}(?!\}))*\}\})/g))a!==""&&(/^\{\{[\s\S]*\}\}$/.test(a)?o.push({kind:"chip",text:n(a)}):o.push({kind:"text",text:a}))}function us(e,t,n){const o=[];if(typeof e!="string"||e==="")return o;if(Jn(e)===null&&/\{\{/.test(e)){for(const a of e.split(/(\{\{(?:[^}]|\}(?!\}))*\}\})/g))if(!(a===void 0||a===""))if(/^\{\{[\s\S]*\}\}$/.test(a)){const l=un(a);So(l.node,t,n,o)}else Yd(a,t,n,o);return o}const s=un(e);return So(s.node,t,n,o),o}function Nv(e){if(typeof e!="string"||e.trim()==="")return"empty";const t=e.trim();if(Jn(t)===null)return"simple";const n=un(e);return n.node.kind==="leaf"?"simple":n.decomposed?"expression":"raw"}function un(e){const t=typeof e=="string"?e:"",n=t.trim();if(n==="")return{node:{kind:"leaf",text:t},decomposed:!1,original:t};const o=Jn(n);if(o===null)return{node:{kind:"leaf",text:t},decomposed:!1,original:t};let r;try{r=Hv(o)}catch{r=null}if(r===null)return{node:{kind:"raw",source:o},decomposed:!1,original:t};if(r.kind==="leaf"||r.kind==="raw")return{node:r,decomposed:!1,original:t};if(r.kind==="fallback"&&r.op==="??"&&zv(r.primary)&&Mv(r.fallback))return{node:{kind:"leaf",text:t},decomposed:!1,original:t};const s=Zn(r,!0);return qv(o,s)?{node:r,decomposed:!0,original:t}:{node:{kind:"raw",source:o},decomposed:!1,original:t}}function Zn(e,t=!1){const n=Jd(e);return t?n:e.kind==="leaf"?e.text:`{{${n}}}`}function Co(e){return Zn({kind:"conditional",condition:{kind:"simple",left:"",op:"truthy",right:""},then:{kind:"leaf",text:typeof e=="string"?e:""},else:{kind:"leaf",text:""}},!1)}function ps(e){return Zn({kind:"fallback",op:"??",primary:{kind:"leaf",text:typeof e=="string"?e:""},fallback:{kind:"leaf",text:""}},!1)}function $o(e){switch(e.kind){case"leaf":return e.text;case"conditional":return $o(e.then);case"fallback":return $o(e.primary);case"concat":return e.parts.map($o).join("");default:return e.kind==="raw"?`{{${e.source}}}`:""}}function uM(e){return e}function Sr(e){if(typeof e!="string"||e.trim()==="")return"";if(Jn(e)===null&&/\{\{/.test(e)){for(const n of e.split(/(\{\{(?:[^}]|\}(?!\}))*\}\})/g))if(!(n===void 0||n===""))return/^\{\{[\s\S]*\}\}$/.test(n)?$o(un(n).node):n;return""}return $o(un(e).node)}function zv(e){if(e.kind!=="leaf")return!1;const t=/^\{\{([\s\S]*)\}\}$/.exec(e.text.trim());return t?Qn(t[1].trim()):!1}function Mv(e){if(e.kind!=="leaf")return!1;const t=e.text.trim();return!(/\{\{/.test(t)||t.startsWith("$t:"))}function Cr(e){return`'${e.replace(/\\/g,"\\\\").replace(/'/g,"\\'")}'`}function Lv(e){const t=[];let n=0,o=0;const r=e.length;for(;on&&t.push(Cr(e.slice(n,o)));let s=o+2;for(;s=r)break;const a=e.slice(o+2,s).trim();t.push(a),o=s+2,n=o;continue}o++}return nEo(t,"concat")).join(" + ");case"fallback":return`${Eo(e.primary,"fallback")} ${e.op} ${Eo(e.fallback,"fallback")}`;case"conditional":return`${Ov(e.condition)} ? ${Eo(e.then,"conditional")} : ${Eo(e.else,"conditional")}`;default:return""}}function Eo(e,t){const n=Jd(e);return e.kind==="leaf"||e.kind==="raw"?n:e.kind==="conditional"?t==="conditional"?n:`(${n})`:e.kind==="fallback"?t==="concat"?`(${n})`:n:(e.kind==="concat",n)}const Wv=["===","!==","??","||","&&","==","!=",">=","<="];function Zd(e){const t=[];let n=0;const o=e.length;for(;n=o)throw new Error("unterminated-binding-in-string");d+=e.slice(l,c+2),l=c+2;continue}if(e[l]===a){d+=a,l++;break}if(a==="`"&&e[l]==="$"&&e[l+1]==="{")throw new Error("template-literal-interpolation-unsupported");d+=e[l],l++}if(l>o||d[d.length-1]!==a)throw new Error("unterminated-string");t.push({kind:"string",value:d,start:n,end:l}),n=l;continue}if(r==="{"&&e[n+1]==="{"){let a=n+2;for(;a"||r==="<"){t.push({kind:"op",value:r,start:n,end:n+1}),n++;continue}if(r==="!"){t.push({kind:"op",value:"!",start:n,end:n+1}),n++;continue}if(r==="("){t.push({kind:"lparen",value:"(",start:n,end:n+1}),n++;continue}if(r===")"){t.push({kind:"rparen",value:")",start:n,end:n+1}),n++;continue}if(r==="["){t.push({kind:"lbracket",value:"[",start:n,end:n+1}),n++;continue}if(r==="]"){t.push({kind:"rbracket",value:"]",start:n,end:n+1}),n++;continue}if(r==="."){t.push({kind:"dot",value:".",start:n,end:n+1}),n++;continue}if(/[0-9]/.test(r)){let a=n;for(;a=n)return"";const o=this.toks[t].start,r=this.toks[n-1].end;return this.src.slice(o,r)}parseFallback(){let t=this.parseConcat(),n=this.peek();for(;n&&n.kind==="op"&&(n.value==="??"||n.value==="||");){const o=n.value;this.next();const r=this.parseConcat();t={kind:"fallback",op:o,primary:t,fallback:r},n=this.peek()}return t}parseConcat(){const t=this.parseUnaryOrPrimary(),n=[t];let o=this.peek();for(;o&&o.kind==="op"&&o.value==="+";)this.next(),n.push(this.parseUnaryOrPrimary()),o=this.peek();return n.length===1?t:{kind:"concat",parts:n}}parseUnaryOrPrimary(){return this.parsePrimary()}parsePrimary(){const t=this.peek();if(!t)throw new Error("unexpected-end");if(t.kind==="lparen"){this.next();const n=this.parseTernary(),o=this.next();if(!o||o.kind!=="rparen")throw new Error("expected-rparen");return n}if(t.kind==="string")return this.next(),{kind:"leaf",text:Rd(t.value)};if(t.kind==="tkey")return this.next(),{kind:"leaf",text:t.value};if(t.kind==="binding")return this.next(),{kind:"leaf",text:t.value};if(t.kind==="ident"&&Dv.includes(t.value)&&this.toks[this.pos+1]?.kind==="lparen"){const n=this.tryParsePathWrapperCall(t.value);if(n!==null)return n;throw new Error("path-wrapper-non-simple-arg")}if(t.kind==="ident")return{kind:"leaf",text:`{{${this.parsePath()}}}`};if(t.kind==="number")return this.next(),{kind:"leaf",text:t.value};throw new Error(`unparseable-primary:${t.kind}`)}parsePath(){let t="";const n=this.next();if(!n||n.kind!=="ident")throw new Error("expected-ident");for(t+=n.value;;){const o=this.peek();if(!o)break;if(o.kind==="op"&&o.value==="?"&&this.toks[this.pos+1]?.kind==="dot"){this.next(),this.next();const r=this.next();if(!r||r.kind!=="ident")throw new Error("expected-ident-after-optional");t+="?."+r.value;continue}if(o.kind==="dot"){this.next();const r=this.next();if(!r||r.kind!=="ident")throw new Error("expected-ident-after-dot");t+="."+r.value;continue}if(o.kind==="lbracket"){this.next();const r=this.next(),s=this.next();if(!s||s.kind!=="rbracket")throw new Error("expected-rbracket");if(r&&(r.kind==="number"||r.kind==="string")){t+=`[${r.value}]`;continue}throw new Error("non-literal-index")}if(o.kind==="lparen")throw new Error("call-expression");break}return t}tryParsePathWrapperCall(t){const n=this.pos;this.next();const o=this.next();if(!o||o.kind!=="lparen")return this.pos=n,null;let r;try{if(this.peek()?.kind!=="ident")return this.pos=n,null;r=this.parsePath()}catch{return this.pos=n,null}const s=this.next();return!s||s.kind!=="rparen"?(this.pos=n,null):{kind:"leaf",text:`{{${t}(${r})}}`}}}function Hv(e){return new jv(e).parse()}const Fv=["===","!==","==","!=",">=","<=",">","<"];function Kv(e,t){const n=e.trim();if(n==="")return{kind:"raw",source:n};if(n===Xd)return{kind:"simple",left:"",op:"truthy",right:""};if(n.startsWith("!")){const s=n.slice(1).trim(),a=Qd(s);return Qn(a)?{kind:"simple",left:a,op:"falsy",right:""}:{kind:"raw",source:n}}const o=Vv(n);if(o){const{left:s,op:a,right:l}=o,d=s.trim(),c=l.trim();return Qn(d)&&(c==="''"||c==='""')?{kind:"simple",left:d,op:a,right:""}:Qn(d)&&Uv(c)?{kind:"simple",left:d,op:a,right:c}:{kind:"raw",source:n}}const r=Qd(n);return Qn(r)?{kind:"simple",left:r,op:"truthy",right:""}:{kind:"raw",source:n}}function Vv(e){let t=0,n=null;for(let o=0;o=o||!/[A-Za-z_$]/.test(t[n]))return!1;for(n++;n=o||!/[A-Za-z_$]/.test(t[n]))return!1;for(n++;n=2&&(t[0]==="'"||t[0]==='"'||t[0]==="`")?t.slice(1,-1).replace(/\\(['"`\\])/g,"$1"):t}function qv(e,t){try{return $r(e)===$r(t)}catch{return!1}}function $r(e){const t=Jn(e);return t!==null?$r(t):Zd(e).map(o=>{if(o.kind==="string")return`S:${Rd(o.value)}`;if(o.kind==="binding"){const r=/^\{\{([\s\S]*)\}\}$/.exec(o.value);return`B:${r?$r(r[1].trim()):o.value.replace(/\s+/g,"")}`}return o.kind==="tkey"?`S:${o.value}`:o.kind==="op"?`O:${o.value}`:o.kind==="ident"||o.kind==="number"?`I:${o.value}`:o.kind==="dot"?".":o.kind==="lbracket"?"[":o.kind==="rbracket"?"]":o.kind==="lparen"||o.kind==="rparen"?"":`X:${o.value}`}).join("")}function Rn({active:e,testid:t}){return i.jsx("div",{"data-testid":t,"data-active":e?"true":"false","aria-hidden":"true",style:e?Yv:Gv})}const Gv={height:0,margin:0,transition:"height 80ms"},Yv={height:3,margin:"1px 0",borderRadius:2,background:"#2563eb",boxShadow:"0 0 0 1px #93c5fd"};function fs(e){const{length:t,onMove:n,canDrag:o,canDropAt:r}=e,[s,a]=u.useState(null),[l,d]=u.useState(null),c=u.useCallback(m=>{o&&!o(m)||(a(m),d(m))},[o]),p=u.useCallback(()=>{a(null),d(null)},[]),f=u.useCallback((m,h)=>{if(s===null)return;const v=h==="before"?m:m+1;if(r&&!r(s,v)){d(null);return}d(v)},[s,r]),y=u.useCallback(()=>{if(s!==null&&l!==null){const m=l>s?l-1:l;m!==s&&(!r||r(s,l))&&n(s,m)}a(null),d(null)},[s,l,n,r]),g=u.useCallback(m=>s!==null&&l===m&&m<=t,[s,l,t]);return{dragIndex:s,dropIndex:l,dragging:s!==null,onDragStart:c,onDragEnd:p,onDragOverItem:f,onDrop:y,isDropTarget:g}}const Xv={text:"",expression:"{{false ? '' : ''}}",fallback:"{{'' ?? ''}}",data:""};function ec(e,t){const n=In(e);let o=t;const r=n.map(s=>({id:o++,kind:s.kind==="literal"?"text":"expression",value:s.raw}));return r.length===0&&r.push({id:o++,kind:"text",value:""}),{segs:r,nextSeq:o}}function gs({value:e,onChange:t,t:n,candidates:o,testidPrefix:r="g7le-seg-value",renderLeafInput:s}){const a=u.useRef(0),l=u.useRef(e),d=u.useMemo(()=>{const k=ec(e,a.current);return a.current=k.nextSeq,k.segs},[]),[c,p]=u.useState(d),[f,y]=u.useState(!1);if(e!==l.current){l.current=e;const k=c.map(w=>w.value).join("");if(e!==k){const w=ec(e,a.current);a.current=w.nextSeq,p(w.segs)}}const g=u.useCallback(k=>{p(k);const w=k.map(_=>_.value).join("");l.current=w,t(w)},[t]),m=u.useCallback((k,w)=>{g(c.map(_=>_.id===k?{..._,value:w??""}:_))},[c,g]),h=u.useCallback(k=>{const w=c.filter(_=>_.id!==k);g(w.length>0?w:[{id:a.current++,kind:"text",value:""}])},[c,g]),v=u.useCallback(k=>{g([...c,{id:a.current++,kind:k,value:Xv[k]}])},[c,g]),b=u.useCallback((k,w)=>{if(k===w||k<0||w<0||k>=c.length||w>=c.length)return;const _=c.slice(),[C]=_.splice(k,1);_.splice(w,0,C),g(_)},[c,g]),x=fs({length:c.length,onMove:b});return i.jsxs("div",{"data-testid":r,style:Qv,children:[i.jsx("div",{style:Rv,children:n("layout_editor.value_tree.segmented_hint")}),c.map((k,w)=>i.jsxs(u.Fragment,{children:[i.jsx(Rn,{active:x.isDropTarget(w),testid:`${r}-dropline-${w}`}),i.jsx(Zv,{seg:k,index:w,t:n,candidates:o,testidPrefix:r,renderLeafInput:s,onValueChange:_=>m(k.id,_),onRemove:()=>h(k.id),dragging:x.dragIndex===w,onDragStartCard:()=>x.onDragStart(w),onDragEndCard:x.onDragEnd,onDragOverCard:_=>x.onDragOverItem(w,_),onDropCard:x.onDrop})]},k.id)),i.jsx(Rn,{active:x.isDropTarget(c.length),testid:`${r}-dropline-end`}),i.jsxs("div",{style:i_,"data-testid":`${r}-add-row`,children:[i.jsxs("button",{type:"button","data-testid":`${r}-add-text`,onClick:()=>v("text"),style:Er,children:["+ ",n("layout_editor.value_tree.segment.text")]}),i.jsxs("button",{type:"button","data-testid":`${r}-add-expression`,onClick:()=>v("expression"),style:Er,children:["+ ",n("layout_editor.value_tree.segment.expression")]}),i.jsxs("button",{type:"button","data-testid":`${r}-add-fallback`,onClick:()=>v("fallback"),style:Er,children:["+ ",n("layout_editor.value_tree.segment.fallback")]}),i.jsxs("button",{type:"button","data-testid":`${r}-add-data`,onClick:()=>v("data"),style:Er,children:["+ ",n("layout_editor.value_tree.segment.data")]})]}),i.jsxs("div",{style:l_,children:[i.jsxs("button",{type:"button","data-testid":`${r}-source-toggle`,"aria-expanded":f,onClick:()=>y(k=>!k),style:d_,children:[""," ",n("layout_editor.value_tree.show_source")," ",f?"▴":"▾"]}),f&&i.jsx("code",{"data-testid":`${r}-source-code`,style:c_,children:c.map(k=>k.value).join("")})]})]})}function Jv({t:e,candidates:t,onPick:n,testidPrefix:o}){return!t||t.length===0?i.jsx("div",{"data-testid":`${o}-data-empty`,style:a_,children:e("layout_editor.value_tree.segment.data_no_candidates")}):i.jsxs("div",{"data-testid":`${o}-data-picker`,style:{minWidth:0},children:[i.jsx("div",{style:s_,children:e("layout_editor.value_tree.segment.data_pick_hint")}),i.jsx($t,{candidates:t,t:e,onSelect:r=>{n(En(r.sourceId,r.path,"scalar"))},testIdSuffix:`${o}-data`,defaultOpen:!0,floating:!0})]})}function Zv({seg:e,index:t,t:n,candidates:o,testidPrefix:r,renderLeafInput:s,onValueChange:a,onRemove:l,dragging:d,onDragStartCard:c,onDragEndCard:p,onDragOverCard:f,onDropCard:y}){const g=e.kind==="text"?"layout_editor.value_tree.segment.text":e.kind==="data"?"layout_editor.value_tree.segment.data":e.kind==="fallback"?"layout_editor.value_tree.segment.fallback":"layout_editor.value_tree.segment.expression";return i.jsxs("div",{"data-testid":`${r}-card-${t}`,"data-seg-kind":e.kind,style:d?{...tc,opacity:.5}:tc,onDragOver:m=>{m.preventDefault();const h=m.currentTarget.getBoundingClientRect();f(m.clientY{m.preventDefault(),y()},children:[i.jsxs("div",{style:e_,children:[i.jsx("span",{"data-testid":`${r}-drag-${t}`,style:o_,draggable:!0,onDragStart:c,onDragEnd:p,title:n("layout_editor.value_tree.drag_reorder"),"aria-label":n("layout_editor.value_tree.drag_reorder"),role:"button",children:"⠿"}),i.jsx("span",{style:t_,children:n(g)}),i.jsx("button",{type:"button","data-testid":`${r}-remove-${t}`,onClick:l,title:n("layout_editor.value_tree.segment.remove"),"aria-label":n("layout_editor.value_tree.segment.remove"),style:r_,children:"✕"})]}),i.jsx("div",{style:n_,children:e.kind==="data"&&e.value.trim()===""?i.jsx(Jv,{t:n,candidates:o,onPick:a,testidPrefix:`${r}-field-${t}`}):s?s({value:e.value,onChange:a,t:n,candidates:o,testidPrefix:`${r}-field-${t}`}):i.jsx(vt,{value:e.value,onChange:a,t:n,candidates:o,testidPrefix:`${r}-field-${t}`,enableExpressionTree:!0,expressionSourceToggle:!1})})]})}const Qv={display:"flex",flexDirection:"column",gap:6,width:"100%",minWidth:0},Rv={fontSize:11,color:"#64748b"},tc={display:"flex",flexDirection:"column",gap:4,padding:"6px 8px",border:"1px solid #e2e8f0",borderLeft:"3px solid #6366f1",borderRadius:6,background:"#f8fafc",minWidth:0},e_={display:"flex",alignItems:"center",gap:6,minWidth:0},t_={fontSize:10,fontWeight:600,color:"#6366f1",flex:1,minWidth:0},n_={minWidth:0},o_={color:"#94a3b8",cursor:"grab",fontSize:14,userSelect:"none"},r_={border:"none",background:"transparent",color:"#94a3b8",cursor:"pointer",fontSize:12,padding:2,flexShrink:0},i_={display:"flex",flexWrap:"wrap",gap:6,marginTop:2},s_={fontSize:11,color:"#64748b",marginBottom:2},a_={fontSize:11,color:"#94a3b8",padding:"4px 0"},Er={padding:"4px 10px",fontSize:11,border:"1px dashed #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer",whiteSpace:"nowrap"},l_={display:"flex",flexDirection:"column",gap:4,marginTop:2},d_={alignSelf:"flex-start",padding:"3px 8px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#475569",cursor:"pointer",fontFamily:"monospace"},c_={fontSize:11,background:"#0f172a",color:"#e2e8f0",padding:"6px 8px",borderRadius:4,wordBreak:"break-all",fontFamily:"monospace"},u_=[{value:"truthy",labelKey:"layout_editor.value_tree.cond.op_truthy"},{value:"falsy",labelKey:"layout_editor.value_tree.cond.op_falsy"},{value:"===",labelKey:"layout_editor.value_tree.cond.op_eq"},{value:"!==",labelKey:"layout_editor.value_tree.cond.op_neq"},{value:">",labelKey:"layout_editor.value_tree.cond.op_gt"},{value:"<",labelKey:"layout_editor.value_tree.cond.op_lt"},{value:">=",labelKey:"layout_editor.value_tree.cond.op_gte"},{value:"<=",labelKey:"layout_editor.value_tree.cond.op_lte"}];function nc({node:e,onChange:t,t:n,candidates:o,testidPrefix:r="g7le-value-tree",depth:s=0,showSourceToggle:a=!0,renderLeafInput:l}){const[d,c]=u.useState(!1),p=s===0,f=i.jsx(eo,{node:e,onChange:t,t:n,candidates:o,testidPrefix:r,depth:s,renderLeafInput:l});return p?a?i.jsxs("div",{"data-testid":r,style:rc,children:[f,i.jsxs("div",{style:D_,children:[i.jsxs("button",{type:"button","data-testid":`${r}-source-toggle`,"aria-expanded":d,onClick:()=>c(y=>!y),style:N_,children:[""," ",n("layout_editor.value_tree.show_source")," ",d?"▴":"▾"]}),d&&i.jsx("code",{"data-testid":`${r}-source-code`,style:z_,children:Zn(e,!1)})]})]}):i.jsx("div",{"data-testid":r,style:rc,children:f}):f}function eo({node:e,onChange:t,t:n,candidates:o,testidPrefix:r,depth:s,renderLeafInput:a}){switch(e.kind){case"leaf":return i.jsx(gs,{value:e.text,onChange:l=>t({kind:"leaf",text:l}),t:n,candidates:o,testidPrefix:`${r}-leaf`,renderLeafInput:a});case"conditional":return i.jsx(p_,{node:e,onChange:t,t:n,candidates:o,testidPrefix:r,depth:s,renderLeafInput:a});case"fallback":return i.jsx(f_,{node:e,onChange:t,t:n,candidates:o,testidPrefix:r,depth:s,renderLeafInput:a});case"concat":return i.jsx(h_,{node:e,onChange:t,t:n,candidates:o,testidPrefix:r,depth:s,renderLeafInput:a});default:return i.jsxs("div",{"data-testid":`${r}-raw`,style:M_,children:[i.jsx("code",{style:ac,children:e.source??""}),i.jsxs("span",{style:lc,children:["🔒 ",n("layout_editor.value_tree.raw_code_only")]})]})}}function p_({node:e,onChange:t,t:n,candidates:o,testidPrefix:r,depth:s,renderLeafInput:a}){const l=u.useCallback(p=>t({...e,condition:p}),[e,t]),d=u.useCallback(p=>t({...e,then:p}),[e,t]),c=u.useCallback(p=>t({...e,else:p}),[e,t]);return i.jsxs("div",{"data-testid":`${r}-conditional`,style:hs(s),children:[i.jsx(y_,{condition:e.condition,onChange:l,t:n,candidates:o,testidPrefix:r}),i.jsx(Ir,{label:n("layout_editor.value_tree.cond.when_true"),testid:`${r}-then`,children:i.jsx(eo,{node:e.then,onChange:d,t:n,candidates:o,testidPrefix:`${r}-then`,depth:s+1,renderLeafInput:a})}),i.jsx(Ir,{label:n("layout_editor.value_tree.cond.when_false"),testid:`${r}-else`,children:i.jsx(eo,{node:e.else,onChange:c,t:n,candidates:o,testidPrefix:`${r}-else`,depth:s+1,renderLeafInput:a})})]})}function f_({node:e,onChange:t,t:n,candidates:o,testidPrefix:r,depth:s,renderLeafInput:a}){const l=u.useCallback(c=>t({...e,primary:c}),[e,t]),d=u.useCallback(c=>t({...e,fallback:c}),[e,t]);return i.jsxs("div",{"data-testid":`${r}-fallback`,style:hs(s),children:[i.jsx(Ir,{label:n("layout_editor.value_tree.fallback.primary"),testid:`${r}-primary`,children:i.jsx(eo,{node:e.primary,onChange:l,t:n,candidates:o,testidPrefix:`${r}-primary`,depth:s+1,renderLeafInput:a})}),i.jsx(Ir,{label:n(e.op==="??"?"layout_editor.value_tree.fallback.when_empty":"layout_editor.value_tree.fallback.when_falsy"),testid:`${r}-fallback-branch`,children:i.jsx(eo,{node:e.fallback,onChange:d,t:n,candidates:o,testidPrefix:`${r}-fallbackb`,depth:s+1,renderLeafInput:a})})]})}const g_={text:()=>({kind:"leaf",text:""}),data:()=>({kind:"leaf",text:""}),expression:()=>({kind:"conditional",condition:{kind:"simple",left:"",op:"truthy",right:""},then:{kind:"leaf",text:""},else:{kind:"leaf",text:""}}),fallback:()=>({kind:"fallback",op:"??",primary:{kind:"leaf",text:""},fallback:{kind:"leaf",text:""}})};function h_({node:e,onChange:t,t:n,candidates:o,testidPrefix:r,depth:s,renderLeafInput:a}){const l=u.useCallback((g,m)=>{const h=e.parts.slice();h[g]=m,t({...e,parts:h})},[e,t]),d=u.useCallback(g=>{g.length===0?t({kind:"leaf",text:""}):g.length===1?t(g[0]):t({...e,parts:g})},[e,t]),c=u.useCallback(g=>d(e.parts.filter((m,h)=>h!==g)),[e.parts,d]),p=u.useCallback(g=>d([...e.parts,g_[g]()]),[e.parts,d]),f=u.useCallback((g,m)=>{if(g===m||g<0||m<0||g>=e.parts.length||m>=e.parts.length)return;const h=e.parts.slice(),[v]=h.splice(g,1);h.splice(m,0,v),d(h)},[e.parts,d]),y=fs({length:e.parts.length,onMove:f});return i.jsxs("div",{"data-testid":`${r}-concat`,style:hs(s),children:[i.jsx("div",{style:C_,children:n("layout_editor.value_tree.concat.hint")}),e.parts.map((g,m)=>i.jsxs(u.Fragment,{children:[i.jsx(Rn,{active:y.isDropTarget(m),testid:`${r}-part-dropline-${m}`}),i.jsxs("div",{"data-testid":`${r}-part-card-${m}`,style:y.dragIndex===m?{...sc,opacity:.5}:sc,onDragOver:h=>{h.preventDefault();const v=h.currentTarget.getBoundingClientRect();y.onDragOverItem(m,h.clientY{h.preventDefault(),y.onDrop()},children:[i.jsxs("div",{style:$_,children:[i.jsx("span",{"data-testid":`${r}-part-handle-${m}`,style:A_,draggable:!0,onDragStart:()=>y.onDragStart(m),onDragEnd:y.onDragEnd,title:n("layout_editor.value_tree.drag_reorder"),"aria-label":n("layout_editor.value_tree.drag_reorder"),role:"button",children:"⠿"}),i.jsx("span",{style:E_,children:n("layout_editor.value_tree.concat.part",{n:m+1})}),i.jsx("button",{type:"button","data-testid":`${r}-part-remove-${m}`,onClick:()=>c(m),title:n("layout_editor.value_tree.segment.remove"),"aria-label":n("layout_editor.value_tree.segment.remove"),style:T_,children:"✕"})]}),i.jsx("div",{style:{minWidth:0},children:i.jsx(eo,{node:g,onChange:h=>l(m,h),t:n,candidates:o,testidPrefix:`${r}-part-${m}`,depth:s+1,renderLeafInput:a})})]})]},m)),i.jsx(Rn,{active:y.isDropTarget(e.parts.length),testid:`${r}-part-dropline-end`}),i.jsxs("div",{style:I_,"data-testid":`${r}-part-add-row`,children:[i.jsxs("button",{type:"button","data-testid":`${r}-part-add-text`,onClick:()=>p("text"),style:Ar,children:["+ ",n("layout_editor.value_tree.segment.text")]}),i.jsxs("button",{type:"button","data-testid":`${r}-part-add-expression`,onClick:()=>p("expression"),style:Ar,children:["+ ",n("layout_editor.value_tree.segment.expression")]}),i.jsxs("button",{type:"button","data-testid":`${r}-part-add-fallback`,onClick:()=>p("fallback"),style:Ar,children:["+ ",n("layout_editor.value_tree.segment.fallback")]}),i.jsxs("button",{type:"button","data-testid":`${r}-part-add-data`,onClick:()=>p("data"),style:Ar,children:["+ ",n("layout_editor.value_tree.segment.data")]})]})]})}function y_({condition:e,onChange:t,t:n,candidates:o,testidPrefix:r}){if(e.kind==="raw")return i.jsxs("div",{"data-testid":`${r}-cond-raw`,style:x_,children:[i.jsx("span",{style:ic,children:n("layout_editor.value_tree.cond.label")}),i.jsx("code",{style:ac,children:e.source}),i.jsxs("span",{style:lc,children:["🔒 ",n("layout_editor.value_tree.cond.code_only")]})]});const s=e,a=s.op!=="truthy"&&s.op!=="falsy";return i.jsxs("div",{"data-testid":`${r}-cond`,style:__,children:[i.jsx("span",{style:ic,children:n("layout_editor.value_tree.cond.label")}),i.jsx(oc,{testid:`${r}-cond-left`,value:s.left,onCommit:l=>t({...s,left:l}),placeholder:n("layout_editor.value_tree.cond.field_placeholder"),candidates:o,t:n}),i.jsx("select",{"data-testid":`${r}-cond-op`,value:s.op,onChange:l=>t({...s,op:l.target.value}),style:S_,children:u_.map(l=>i.jsx("option",{value:l.value,children:n(l.labelKey)},l.value))}),a&&i.jsx(oc,{testid:`${r}-cond-right`,value:s.right,onCommit:l=>t({...s,right:l}),placeholder:n("layout_editor.value_tree.cond.value_placeholder"),candidates:o,t:n,quoteDataPick:!0})]})}function oc({testid:e,value:t,onCommit:n,placeholder:o,candidates:r,t:s,quoteDataPick:a=!1}){const[l,d]=u.useState(null),c=l!==null?l:t,p=()=>{l!==null&&(l!==t&&n(l),d(null))},f=(y,g)=>{const m=En(y,g,"scalar"),h=/^\{\{\s*([\s\S]*?)\s*\}\}$/.exec(m.trim());let v=h?h[1]:m;v=v.replace(/\s*\?\?\s*''$/,"").trim(),n(a?`'${v}'`:v),d(null)};return i.jsxs("span",{style:w_,children:[i.jsx("input",{type:"text","data-testid":e,value:c,onChange:y=>d(y.target.value),onBlur:p,onKeyDown:y=>{y.key==="Enter"&&(y.preventDefault(),p())},placeholder:o,style:k_}),r&&r.length>0&&s&&i.jsx($t,{candidates:r,t:s,onSelect:y=>f(y.sourceId,y.path),testIdSuffix:`${e}-data`,floating:!0})]})}function Ir({label:e,testid:t,children:n}){return i.jsxs("div",{"data-testid":t,style:b_,children:[i.jsx("span",{style:m_,children:e}),i.jsx("div",{style:v_,children:n})]})}const rc={display:"flex",flexDirection:"column",gap:6,width:"100%",minWidth:0},hs=e=>({display:"flex",flexDirection:"column",gap:6,padding:"8px",border:"1px solid #e2e8f0",borderLeft:`3px solid ${e%2===0?"#6366f1":"#0ea5e9"}`,borderRadius:6,background:e%2===0?"#f8fafc":"#fefefe",minWidth:0}),b_={display:"flex",flexDirection:"column",gap:3,minWidth:0},m_={fontSize:11,fontWeight:600,color:"#475569"},v_={paddingLeft:8,minWidth:0},__={display:"flex",flexWrap:"wrap",gap:4,alignItems:"center",minWidth:0},x_={display:"flex",flexWrap:"wrap",gap:4,alignItems:"center",minWidth:0},ic={fontSize:11,fontWeight:600,color:"#0f172a",whiteSpace:"nowrap"},w_={display:"inline-flex",alignItems:"flex-start",gap:2,flex:"1 1 80px",minWidth:60},k_={flex:"1 1 80px",minWidth:60,padding:"4px 6px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6},S_={padding:"4px 6px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff"},C_={fontSize:11,color:"#94a3b8"},sc={display:"flex",flexDirection:"column",gap:4,padding:"6px 8px",border:"1px solid #e2e8f0",borderLeft:"3px solid #6366f1",borderRadius:6,background:"#fff",minWidth:0},$_={display:"flex",alignItems:"center",gap:6,minWidth:0},E_={fontSize:10,fontWeight:600,color:"#6366f1",flex:1,minWidth:0},I_={display:"flex",flexWrap:"wrap",gap:6,marginTop:2},A_={color:"#94a3b8",cursor:"grab",fontSize:14,userSelect:"none"},T_={border:"none",background:"transparent",color:"#94a3b8",cursor:"pointer",fontSize:12,padding:2,flexShrink:0},Ar={padding:"4px 10px",fontSize:11,border:"1px dashed #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer",whiteSpace:"nowrap"},D_={display:"flex",flexDirection:"column",gap:4,marginTop:2},N_={alignSelf:"flex-start",padding:"3px 8px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#475569",cursor:"pointer",fontFamily:"monospace"},z_={fontSize:11,background:"#0f172a",color:"#e2e8f0",padding:"6px 8px",borderRadius:4,wordBreak:"break-all",fontFamily:"monospace"},M_={display:"flex",flexDirection:"column",gap:2,minWidth:0},ac={fontSize:11,background:"#f1f5f9",padding:"3px 6px",borderRadius:4,color:"#0f172a",wordBreak:"break-all",fontFamily:"monospace"},lc={fontSize:10,color:"#64748b"};function vt({value:e,onChange:t,t:n,placeholder:o,testidPrefix:r="g7le-i18n-text-field",locales:s,candidates:a,enableExpressionTree:l=!1,expressionTreeCollapsible:d=!1,expressionSourceToggle:c=!0}){const{locale:p,templateIdentifier:f,commitText:y,classify:g,translate:m}=uv(),[h,v]=u.useState(null),[b,x]=u.useState(!1),[k,w]=u.useState(!1),[_,C]=u.useState(!1),[E,S]=u.useState(!1),[T,$]=u.useState(!1),[,A]=u.useState(0);u.useEffect(()=>{if(typeof window>"u")return;const N=H=>{const L=H.detail;L?.templateIdentifier&&L.templateIdentifier!==f||A(I=>I+1)};return window.addEventListener(kt,N),()=>window.removeEventListener(kt,N)},[f]);const z=g(e);u.useEffect(()=>{v(null),$(!1)},[p,e]);const D=h!==null?h:z.displayValue,O=u.useCallback(async()=>{if(h!==null){if(h===z.displayValue){v(null);return}x(!0);try{const N=await y(e,h);N.kind==="created"&&N.token?(t(N.token),Et({op:"prop_i18n_create_key",sourceState:"plain_text",translationKey:N.customKey,toLocale:p,valueLength:h.length,timestamp:Date.now()})):N.kind==="updated"&&Et({op:"prop_i18n_update_value",sourceState:"custom_key",translationKey:N.customKey,toLocale:p,valueLength:h.length,timestamp:Date.now()})}finally{x(!1),v(null)}}},[h,z.displayValue,y,e,t,p]),M=u.useCallback(N=>{typeof e=="string"&&(t(mo(e,N)),z.customKey&&kr(f,z.customKey,N,p))},[e,t,z.customKey,f,p]);if(z.binding){if(l&&typeof e=="string"){const N=Gd(e),H=un(e),L=!N&&Kt(e)!==null,I=(N||H.decomposed)&&!L;if(I&&d&&!_)return i.jsx(O_,{value:e,t:n,translate:m,onEdit:()=>C(!0),testidPrefix:`${r}-preview`});const F=I&&(d||N)?i.jsx(gs,{value:e,onChange:G=>t(G),t:n,candidates:a,testidPrefix:`${r}-seg`}):H.decomposed?i.jsx(nc,{node:H.node,onChange:G=>t(Zn(G,!1)),t:n,candidates:a,testidPrefix:`${r}-tree`,showSourceToggle:c}):null;if(F){if(!d)return F;const G=Sr(e),V=G?us(G,Y=>{const Z=m(Y);return Z&&Z!==Y&&!Z.startsWith("$t:")?Z:""},Y=>Vt(Y)):[];return i.jsxs("div",{"data-testid":`${r}-builder`,style:{display:"flex",flexDirection:"column",gap:6,width:"100%",minWidth:0},children:[i.jsxs("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"center",gap:6},children:[i.jsxs("button",{type:"button","data-testid":`${r}-to-plain`,onClick:()=>S(!0),style:V_,title:n("layout_editor.value_tree.to_plain"),"aria-label":n("layout_editor.value_tree.to_plain"),children:["↩ ",n("layout_editor.value_tree.to_plain")]}),i.jsxs("button",{type:"button","data-testid":`${r}-builder-collapse`,onClick:()=>C(!1),style:uc,title:n("layout_editor.value_tree.collapse"),"aria-label":n("layout_editor.value_tree.collapse"),children:["▴ ",n("layout_editor.value_tree.collapse")]})]}),E&&i.jsxs("div",{"data-testid":`${r}-to-plain-confirm`,style:U_,children:[i.jsx("p",{style:q_,children:n("layout_editor.value_tree.to_plain_lead")}),i.jsx("div",{style:G_,children:V.length===0?i.jsx("span",{style:{color:"#94a3b8"},children:n("layout_editor.prop_i18n.placeholder")}):V.map((Y,Z)=>Y.kind==="chip"?i.jsxs("span",{style:dc,children:["🔗 ",Y.text]},Z):i.jsx("span",{style:Y.kind==="ellipsis"?cc:void 0,children:Y.text},Z))}),i.jsxs("p",{style:Y_,children:["⚠ ",n("layout_editor.value_tree.to_plain_warn")]}),i.jsxs("div",{style:{display:"flex",justifyContent:"flex-end",gap:6},children:[i.jsx("button",{type:"button","data-testid":`${r}-to-plain-confirm-cancel`,onClick:()=>S(!1),style:uc,children:n("layout_editor.value_tree.to_plain_cancel")}),i.jsx("button",{type:"button","data-testid":`${r}-to-plain-confirm-ok`,onClick:()=>{S(!1),C(!1),t(Sr(e)||void 0)},style:X_,children:n("layout_editor.value_tree.to_plain_ok")})]})]}),F]})}}return l&&typeof e=="string"&&a&&a.length>0&&Kt(e)!==null?i.jsx(B_,{value:e,onChange:t,t:n,candidates:a,testidPrefix:`${r}-data`}):i.jsxs("div",{"data-testid":`${r}-binding`,style:tx,children:[i.jsx("code",{style:nx,children:typeof e=="string"?e:""}),i.jsxs("span",{style:ox,children:["🔗 ",n("layout_editor.prop_i18n.bound_code_only")]})]})}if(!T&&typeof e=="string"&&Ul(e)){const N=ql(e);return i.jsxs("div",{"data-testid":`${r}-settings-ref`,style:Q_,children:[i.jsx("div",{"data-testid":`${r}-settings-ref-chips`,style:R_,children:N.map((H,L)=>H.kind==="chip"?i.jsxs("span",{style:ex,children:["🔗 ",H.label]},L):i.jsx("span",{children:H.label},L))}),i.jsxs("button",{type:"button","data-testid":`${r}-settings-ref-edit`,onClick:()=>$(!0),style:pc,title:n("layout_editor.value_tree.edit"),"aria-label":n("layout_editor.value_tree.edit"),children:["✎ ",n("layout_editor.value_tree.edit")]})]})}const B=i.jsxs("button",{type:"button","data-testid":`${r}-toggle`,title:`${n("layout_editor.prop_i18n.edit_languages")} (${Gn(p,n)})`,"aria-label":`${n("layout_editor.prop_i18n.edit_languages")} (${Gn(p,n)})`,"aria-expanded":k,onClick:()=>w(N=>!N),style:pc,children:["🌐 ",z.customKey?k?"⌃":"⌄":"+"]});return i.jsxs("div",{"data-testid":r,style:{display:"flex",flexDirection:"column",gap:4,width:"100%"},children:[i.jsx("div",{style:J_,children:z.paramKey&&z.customKey?i.jsx(L_,{customKey:z.customKey,value:typeof e=="string"?e:"",onChange:t,t:n,candidates:a,testidPrefix:r,showExprButton:l&&d,onToExpression:()=>{C(!0),t(Co(typeof e=="string"?e:""))},langToggle:B}):i.jsxs(i.Fragment,{children:[i.jsx("input",{type:"text","data-testid":`${r}-preview`,value:D,placeholder:o??n("layout_editor.prop_i18n.placeholder"),onChange:N=>v(N.target.value),onBlur:()=>{O(),T&&$(!1)},disabled:b,style:Z_}),a&&a.length>0&&i.jsx(P_,{value:typeof e=="string"?e:"",customKey:z.customKey,onChange:t,t:n,candidates:a,testidPrefix:r}),l&&d&&i.jsx("button",{type:"button","data-testid":`${r}-to-expr`,onClick:()=>{C(!0),t(Co(typeof e=="string"?e:""))},title:n("layout_editor.value_tree.to_expression"),"aria-label":n("layout_editor.value_tree.to_expression"),style:Io,children:"ƒx"}),B]})}),k&&(z.customKey?i.jsx(Ud,{customKey:z.customKey,templateIdentifier:f,t:n,paramLabels:ds(e),locales:s,onRemoveParam:M},z.customKey):i.jsx("div",{"data-testid":`${r}-expand-hint`,style:rx,children:n("layout_editor.prop_i18n.create_first")}))]})}function L_({customKey:e,value:t,onChange:n,t:o,candidates:r,testidPrefix:s,showExprButton:a=!1,onToExpression:l,langToggle:d}){const{state:c}=Je(),p=c.templateIdentifier,f=c.locale,y=Ft(),[g,m]=u.useState(null),h=u.useRef(""),v=u.useRef(null),b=u.useRef(null),x=u.useRef(null),[k,w]=u.useState(null),[_,C]=u.useState(!1),E=ds(t),S=u.useCallback(async()=>{const D=It(e,f);if(D!==void 0){h.current=D,m(D);return}const M=(await cn(p,e))?.values?.[f]??"";h.current=M,m(M)},[e,p,f]);u.useEffect(()=>{let D=!1;return(async()=>{const O=It(e,f);if(O!==void 0){D||(h.current=O,m(O));return}const M=await cn(p,e);if(D)return;const B=M?.values?.[f]??"";h.current=B,m(B)})(),()=>{D=!0}},[e,p,f]),u.useEffect(()=>{if(typeof window>"u")return;const D=O=>{const M=O.detail;if(M?.templateIdentifier&&M.templateIdentifier!==p)return;const B=It(e,f);B!==void 0&&B!==g&&(h.current=B,m(B))};return window.addEventListener(kt,D),()=>window.removeEventListener(kt,D)},[e,p,f,g]);const T=u.useCallback(D=>{m(D),ko(p,e,f,D).then(()=>{y?.markDirty?.()})},[p,e,f,y]),$=u.useCallback(D=>{n(mo(t,D)),kr(p,e,D,f).then(()=>{S(),y?.markDirty?.()})},[t,n,p,e,f,S,y]),A=u.useCallback(async D=>{if(!(k===null||_)){C(!0);try{g!==null&&g!==h.current&&(await ko(p,e,f,g),h.current=g);const O=await wr(p,f,t,k,D.sourceId,D.path,"scalar");O.kind==="ok"&&(n(O.text),await S(),y?.markDirty?.())}finally{C(!1),w(null)}}},[k,_,g,t,p,e,f,n,S,y]),z=!!r&&r.length>0;return i.jsxs("div",{"data-testid":`${s}-chipfield-wrap`,style:{flex:1,minWidth:0,display:"flex",alignItems:"center",gap:4},children:[i.jsx("div",{ref:v,"data-testid":`${s}-chipfield`,style:{flex:1,minWidth:0},children:g===null?i.jsx("div",{"data-testid":`${s}-chipfield-loading`,style:{fontSize:11,color:"#94a3b8",padding:"4px 0"},children:o("layout_editor.translation.loading")}):i.jsx(mr,{value:g,onChange:T,t:o,paramLabels:E,testIdSuffix:`${s}-field`,onRequestInsert:z?D=>w(D):void 0,onRemoveChip:$,hideInsertButton:!0,caretRef:x})}),z&&i.jsx("button",{ref:b,type:"button","data-testid":`${s}-plus-data-btn`,onClick:()=>w(x.current?x.current():g?.length??0),title:o("layout_editor.inline_binding.insert_data"),"aria-label":o("layout_editor.inline_binding.insert_data"),style:Io,children:"+"}),a&&l&&i.jsx("button",{type:"button","data-testid":`${s}-to-expr`,onClick:l,title:o("layout_editor.value_tree.to_expression"),"aria-label":o("layout_editor.value_tree.to_expression"),style:Io,children:"ƒx"}),d,z&&i.jsx(Xn,{anchorRef:b,open:k!==null,onClose:()=>w(null),children:i.jsx("div",{"data-testid":`${s}-chipfield-picker`,children:i.jsx($t,{candidates:r,t:o,onSelect:D=>{A(D)},testIdSuffix:`${s}-field-insert`,defaultOpen:!0,floating:!1})})})]})}function P_({value:e,customKey:t,onChange:n,t:o,candidates:r,testidPrefix:s}){const{state:a}=Je(),l=a.templateIdentifier,d=a.selectedRoute?.layoutName??null,c=a.locale,p=Ft(),[f,y]=u.useState(!1),[g,m]=u.useState(!1),h=u.useRef(null),v=u.useCallback(k=>{const w=o(k);return w&&w!==k&&!w.startsWith("$t:")?w:""},[o]),b=u.useCallback(k=>k.replace(/\$t:[a-zA-Z0-9._-]+/g,w=>v(w.slice(3))).replace(/\s+/g," ").trim(),[v]),x=u.useCallback(async k=>{if(!g){m(!0);try{if(t){const w=await wr(l,c,e,Number.MAX_SAFE_INTEGER,k.sourceId,k.path,"scalar");w.kind==="ok"&&(n(w.text),p?.markDirty?.())}else{const w=await ls(l,d,c,e,e.length,k.sourceId,k.path,"scalar",b,_=>o(_));w.kind==="ok"&&(n(w.text),p?.markDirty?.())}}finally{m(!1),y(!1)}}},[g,t,l,d,c,e,n,p,o,b]);return i.jsxs("div",{"data-testid":`${s}-plus-data`,style:{display:"inline-block"},children:[i.jsx("button",{ref:h,type:"button","data-testid":`${s}-plus-data-btn`,disabled:g,onClick:()=>y(k=>!k),title:o("layout_editor.inline_binding.insert_data"),"aria-label":o("layout_editor.inline_binding.insert_data"),style:Io,children:"+"}),i.jsx(Xn,{anchorRef:h,open:f,onClose:()=>y(!1),children:i.jsx($t,{candidates:r,t:o,onSelect:k=>{x(k)},testIdSuffix:`${s}-plus-insert`,defaultOpen:!0,floating:!1})})]})}function O_({value:e,t,translate:n,onEdit:o,testidPrefix:r}){const s=us(e,a=>{const l=n(a);return l&&l!==a&&!l.startsWith("$t:")?l:""},a=>Vt(a));return i.jsxs("div",{"data-testid":r,style:H_,children:[i.jsx("div",{"data-testid":`${r}-text`,style:F_,children:s.length===0?i.jsx("span",{style:{color:"#94a3b8"},children:t("layout_editor.prop_i18n.placeholder")}):s.map((a,l)=>a.kind==="chip"?i.jsxs("span",{style:dc,children:["🔗 ",a.text]},l):i.jsx("span",{style:a.kind==="ellipsis"?cc:void 0,children:a.text},l))}),i.jsxs("button",{type:"button","data-testid":`${r}-edit`,onClick:o,style:K_,title:t("layout_editor.value_tree.edit"),"aria-label":t("layout_editor.value_tree.edit"),children:["✎ ",t("layout_editor.value_tree.edit")]})]})}function B_({value:e,onChange:t,t:n,candidates:o,testidPrefix:r}){const[s,a]=u.useState(!1),l=u.useRef(null),d=Vt(e);return i.jsxs("div",{"data-testid":r,style:{display:"flex",flexDirection:"column",gap:4,width:"100%",minWidth:0},children:[i.jsxs("div",{style:W_,children:[i.jsxs("span",{"data-testid":`${r}-chip`,style:j_,children:["🔗 ",d]}),i.jsx("button",{ref:l,type:"button","data-testid":`${r}-change`,onClick:()=>a(c=>!c),style:Io,title:n("layout_editor.value_tree.change_data"),"aria-label":n("layout_editor.value_tree.change_data"),children:n("layout_editor.value_tree.change_data")})]}),i.jsx(Xn,{anchorRef:l,open:s,onClose:()=>a(!1),children:i.jsx($t,{candidates:o,t:n,onSelect:c=>{const p=c.path?c.path.split(".").filter(Boolean):[],f=[c.sourceId,...p].join("?.");t(`{{${f}}}`),a(!1)},testIdSuffix:`${r}-pick`,defaultOpen:!0,floating:!1})})]})}const W_={display:"flex",alignItems:"center",gap:6,width:"100%",minWidth:0,flexWrap:"wrap"},j_={fontSize:11,background:"#eef2ff",color:"#4338ca",padding:"3px 8px",borderRadius:12,wordBreak:"break-all"},H_={display:"flex",alignItems:"center",gap:8,width:"100%",minWidth:0},F_={flex:1,minWidth:0,display:"flex",flexWrap:"wrap",alignItems:"center",gap:4,padding:"5px 8px",fontSize:12,color:"#334155",border:"1px solid #e2e8f0",borderRadius:6,background:"#f8fafc",overflow:"hidden"},dc={fontSize:11,background:"#eef2ff",color:"#4338ca",padding:"1px 6px",borderRadius:10,whiteSpace:"nowrap"},cc={color:"#94a3b8"},K_={padding:"4px 10px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#2563eb",cursor:"pointer",whiteSpace:"nowrap",flexShrink:0},uc={padding:"3px 10px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#475569",cursor:"pointer",whiteSpace:"nowrap"},V_={padding:"3px 10px",fontSize:11,border:"1px solid #fca5a5",borderRadius:6,background:"#fef2f2",color:"#b91c1c",cursor:"pointer",whiteSpace:"nowrap"},U_={display:"flex",flexDirection:"column",gap:6,padding:10,border:"1px solid #fca5a5",borderRadius:8,background:"#fffbeb"},q_={margin:0,fontSize:12,color:"#334155"},G_={padding:"5px 8px",fontSize:12,color:"#0f172a",background:"#fff",border:"1px solid #e2e8f0",borderRadius:6,wordBreak:"break-all"},Y_={margin:0,fontSize:11,color:"#b45309"},X_={padding:"3px 12px",fontSize:11,border:"1px solid #dc2626",borderRadius:6,background:"#dc2626",color:"#fff",cursor:"pointer",whiteSpace:"nowrap"},J_={display:"flex",gap:4,alignItems:"center",width:"100%",minWidth:0},Z_={flex:1,padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,minWidth:0,width:"100%"},pc={padding:"4px 8px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#475569",cursor:"pointer",whiteSpace:"nowrap"},Io={padding:"4px 8px",fontSize:13,lineHeight:1,border:"1px solid #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#475569",cursor:"pointer",whiteSpace:"nowrap"},Q_={display:"flex",alignItems:"center",gap:6,width:"100%",minWidth:0,flexWrap:"wrap"},R_={flex:1,minWidth:0,display:"flex",flexWrap:"wrap",alignItems:"center",gap:4,fontSize:12,color:"#334155"},ex={fontSize:11,background:"#eef2ff",color:"#4338ca",padding:"2px 8px",borderRadius:12,whiteSpace:"nowrap"},tx={display:"flex",flexDirection:"column",gap:2,width:"100%"},nx={fontSize:11,background:"#f1f5f9",padding:"3px 6px",borderRadius:4,color:"#0f172a",wordBreak:"break-all"},ox={fontSize:10,color:"#64748b"},rx={fontSize:11,color:"#94a3b8",padding:"6px 8px",border:"1px dashed #cbd5e1",borderRadius:6,background:"#f8fafc"};function ys({value:e,onChange:t,candidates:n,t:o}){const r=Array.isArray(e)?e:[],s=n??[],[a,l]=u.useState(!1),d=g=>s.find(m=>m.value===g)?.label??g,c=g=>d(g)!==g,p=g=>{const m=r.filter(h=>h!==g);t(m.length===0?void 0:m)},f=g=>{r.includes(g)||(t([...r,g]),l(!1))},y=s.filter(g=>!r.includes(g.value));return i.jsxs("div",{className:"g7le-widget g7le-widget--tag-input","data-testid":"g7le-widget-tag-input",style:ix,children:[i.jsxs("div",{style:sx,children:[r.map(g=>i.jsxs("span",{"data-testid":`g7le-tag-chip-${g}`,style:ax,children:[i.jsx("span",{style:lx,children:d(g)}),c(g)&&i.jsx("code",{"data-testid":`g7le-tag-chip-id-${g}`,style:dx,children:g}),i.jsx("button",{type:"button","aria-label":`remove ${g}`,"data-testid":`g7le-tag-remove-${g}`,onClick:()=>p(g),style:cx,children:"✕"})]},g)),i.jsx("button",{type:"button","data-testid":"g7le-tag-add",onClick:()=>l(g=>!g),disabled:y.length===0,style:ux,children:o("layout_editor.control.tag_add")})]}),a&&y.length>0&&i.jsx("div",{"data-testid":"g7le-tag-candidates",style:px,children:y.map(g=>{const m=g.value!==g.label;return i.jsxs("button",{type:"button","data-testid":`g7le-tag-candidate-${g.value}`,onClick:()=>f(g.value),style:fx,children:[i.jsx("span",{style:gx,children:g.label}),m&&i.jsx("code",{"data-testid":`g7le-tag-candidate-id-${g.value}`,style:hx,children:g.value})]},g.value)})})]})}const ix={display:"flex",flexDirection:"column",gap:6},sx={display:"flex",flexWrap:"wrap",gap:4,alignItems:"center"},ax={display:"inline-flex",alignItems:"center",gap:4,padding:"2px 6px",fontSize:12,background:"#e0e7ff",color:"#1d4ed8",borderRadius:12},lx={fontSize:12},dx={fontSize:10,color:"#4f46e5",background:"#c7d2fe",borderRadius:4,padding:"0 4px",fontFamily:"monospace"},cx={border:"none",background:"transparent",color:"#1d4ed8",cursor:"pointer",fontSize:10,padding:0,lineHeight:1},ux={padding:"2px 8px",fontSize:12,border:"1px dashed #94a3b8",borderRadius:12,background:"#fff",cursor:"pointer",color:"#475569"},px={display:"flex",flexDirection:"column",maxHeight:180,overflowY:"auto",border:"1px solid #cbd5e1",borderRadius:6,background:"#fff"},fx={display:"flex",flexDirection:"column",gap:1,textAlign:"left",padding:"6px 10px",fontSize:12,border:"none",borderBottom:"1px solid #f1f5f9",background:"transparent",cursor:"pointer",color:"#0f172a"},gx={fontSize:12,color:"#0f172a"},hx={fontSize:10,color:"#64748b",fontFamily:"monospace"},fc=new Map;function Xe(e,t){fc.set(e,t)}function Tn(e){return e?fc.get(e)??null:null}function Tr(e,t){const o=e("meta",{})?.[t];return typeof o=="string"?o:""}function yx({getValue:e,patch:t,t:n,fieldT:o,spec:r,permissionCandidates:s,bindingCandidates:a}){const l=o??n,d=Tr(e,"title"),c=Tr(e,"description"),p=Tr(e,"icon"),f=Tr(e,"editor_label"),y=e("permissions",[]),g=u.useCallback((b,x)=>{const k={...e("meta",{})??{}};x===void 0||x===""?delete k[b]:k[b]=x,t("meta",k)},[e,t]),m=u.useCallback(b=>{const x=Array.isArray(b)?b:[];x.length===0?t("permissions",void 0):t("permissions",x)},[t]),h=Tn("icon-picker"),v=Ct(r,"icon-picker")??Ct(r,"icon")??(r?.controls?Object.values(r.controls).find(b=>b&&typeof b=="object"&&b.widget==="icon-picker")??null:null);return i.jsxs("div",{className:"g7le-meta-form","data-testid":"g7le-meta-form",style:mx,children:[i.jsx(Ao,{label:n("layout_editor.page_settings.meta.title"),children:i.jsx(vt,{value:d,onChange:b=>g("title",b),t:l,testidPrefix:"g7le-meta-title",candidates:a,enableExpressionTree:!0,expressionTreeCollapsible:!0})}),i.jsx(Ao,{label:n("layout_editor.page_settings.meta.description"),children:i.jsx(vt,{value:c,onChange:b=>g("description",b),t:l,testidPrefix:"g7le-meta-description",candidates:a,enableExpressionTree:!0,expressionTreeCollapsible:!0})}),i.jsx(Ao,{label:n("layout_editor.page_settings.meta.icon"),children:h?i.jsx(bx,{icon:p,t:n,onChange:b=>g("icon",typeof b=="string"?b:void 0),IconWidget:h,iconControl:v??{widget:"icon-picker"}}):i.jsx("input",{type:"text","data-testid":"g7le-meta-icon",value:p,placeholder:n("layout_editor.page_settings.meta.icon_placeholder"),onChange:b=>g("icon",b.target.value),style:wx})}),i.jsx(Ao,{label:n("layout_editor.page_settings.meta.editor_label"),hint:n("layout_editor.page_settings.meta.editor_label_hint"),children:i.jsx(vt,{value:f,onChange:b=>g("editor_label",b),t:l,testidPrefix:"g7le-meta-editor-label",candidates:a,enableExpressionTree:!0,expressionTreeCollapsible:!0})}),i.jsx(Ao,{label:n("layout_editor.page_settings.meta.permissions"),hint:n("layout_editor.page_settings.meta.permissions_hint"),children:i.jsx("div",{"data-testid":"g7le-meta-permissions",children:i.jsx(ys,{control:{widget:"tag-input"},value:y,onChange:m,t:n,candidates:s})})})]})}function Ao({label:e,hint:t,children:n}){return i.jsxs("div",{className:"g7le-meta-field",style:vx,children:[i.jsx("label",{style:_x,children:e}),n,t?i.jsxs("p",{style:xx,children:["ⓘ ",t]}):null]})}function bx({icon:e,onChange:t,t:n,IconWidget:o,iconControl:r}){const[s,a]=u.useState(!1),l=e?/\bfa-/.test(e)?i.jsx("i",{className:e,"aria-hidden":"true"}):e:"📄";return i.jsxs("div",{"data-testid":"g7le-meta-icon",style:{display:"flex",flexDirection:"column",gap:6,minWidth:0},children:[i.jsxs("button",{type:"button","data-testid":"g7le-meta-icon-toggle","aria-expanded":s,onClick:()=>a(d=>!d),style:{display:"flex",alignItems:"center",gap:8,padding:"5px 8px",border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",cursor:"pointer",fontSize:12,color:"#475569",width:"fit-content"},children:[i.jsx("span",{style:{fontSize:16},"aria-hidden":"true",children:l}),i.jsx("span",{children:e||n("layout_editor.page_settings.meta.icon_placeholder")}),i.jsx("span",{"aria-hidden":"true",children:s?"▴":"▾"})]}),s?i.jsx(o,{control:r,value:e,onChange:d=>{t(d),a(!1)},t:n}):null]})}const mx={display:"flex",flexDirection:"column",gap:16,minWidth:0},vx={display:"flex",flexDirection:"column",gap:6,minWidth:0},_x={fontSize:13,fontWeight:600,color:"#0f172a"},xx={margin:0,fontSize:11,color:"#94a3b8"},wx={padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,minWidth:0,width:"100%"};function Dr({checked:e,onChange:t,label:n,testid:o,ariaLabel:r,disabled:s=!1}){return i.jsxs("label",{style:{...Ex,...s?{cursor:"not-allowed"}:{}},children:[i.jsx("button",{type:"button","data-testid":o,role:"switch","aria-checked":e,"aria-label":r,disabled:s,onClick:()=>t(!e),style:Ix(e,s),children:i.jsx("span",{style:Ax(e)})}),n!=null?i.jsx("span",{style:Tx(s),children:n}):null]})}function Nr({disabled:e,children:t,testid:n,style:o}){return i.jsx("div",{"data-testid":n,"data-disabled":e?"true":"false","aria-disabled":e,style:{...o,...e?{opacity:.5,pointerEvents:"none",userSelect:"none"}:{}},children:t})}function kx({filled:e,sourceLabel:t,onRevert:n,revertLabel:o,hint:r,children:s,testid:a}){const l=e&&typeof n=="function",d=!e&&t!=null&&t!=="";return i.jsxs("div",{"data-testid":a,style:Sx,children:[s,l?i.jsxs("button",{type:"button","data-testid":a?`${a}-revert`:void 0,onClick:n,title:r??o,"aria-label":o,style:$x,children:["↩ ",o]}):d?i.jsx("span",{"data-testid":a?`${a}-source`:void 0,title:r,style:Cx,children:t}):null]})}const Sx={position:"relative",minWidth:0,width:"100%"},Cx={position:"absolute",right:8,top:"50%",transform:"translateY(-50%)",fontSize:10,color:"#94a3b8",background:"#f1f5f9",borderRadius:4,padding:"1px 6px",pointerEvents:"none",maxWidth:"45%",overflow:"hidden",whiteSpace:"nowrap",textOverflow:"ellipsis"},$x={position:"absolute",right:6,top:"50%",transform:"translateY(-50%)",fontSize:10,color:"#0369a1",background:"#e0f2fe",border:"1px solid #bae6fd",borderRadius:4,padding:"1px 6px",cursor:"pointer",whiteSpace:"nowrap"},Ex={display:"inline-flex",alignItems:"center",gap:8,cursor:"pointer"},Ix=(e,t)=>({position:"relative",width:38,height:22,flexShrink:0,borderRadius:999,border:"none",padding:0,background:t?"#e2e8f0":e?"#2563eb":"#cbd5e1",cursor:t?"not-allowed":"pointer",transition:"background 0.15s ease"}),Ax=e=>({position:"absolute",top:2,left:e?18:2,width:18,height:18,borderRadius:"50%",background:"#fff",boxShadow:"0 1px 2px rgba(0,0,0,0.2)",transition:"left 0.15s ease"}),Tx=e=>({fontSize:13,fontWeight:600,color:e?"#94a3b8":"#0f172a"}),Dx=/\$(?:core|module|plugin)_settings:[a-zA-Z0-9._:-]+/;function Nx(e){const t=[];let n=!1;const o=s=>{t.push({kind:"text",text:s}),n=!0},r=(s,a,l)=>{n||o(""),t.push({kind:"chip",raw:s,text:a,removable:l}),n=!1};for(const s of zx(e))s.kind==="text"?o(s.raw):r(s.raw,s.label,s.removable);return n||o(""),t}function zx(e){const t=[];if(typeof e!="string"||e.length===0)return t;const n=e.length;let o=0,r=0;const s=a=>{a>r&&t.push({kind:"text",raw:e.slice(r,a),label:"",removable:!1})};for(;o=n){o++;continue}s(o);const l=e.slice(o,a+2);t.push({kind:"chip",raw:l,label:Vt(l),removable:!0}),o=a+2,r=o;continue}if(e[o]==="$"){const a=Dx.exec(e.slice(o));if(a&&a.index===0){s(o);const l=a[0],d=l.split(":").pop()||l;t.push({kind:"chip",raw:l,label:d,removable:!1}),o+=l.length,r=o;continue}}o++}return s(n),t}function Mx(e,t,n,o){const r=[];let s=!1;return e.forEach((a,l)=>{if(a.kind==="chip"){if(!s&&a.raw===t){s=!0;return}r.push(a.raw??"")}else{const d=a.text??"";if(l===n){const c=Math.max(0,Math.min(o,d.length));r.push(d.slice(0,c),t,d.slice(c))}else r.push(d)}}),r.join("")}function Lx(e,t,n){const o=Math.max(0,Math.min(t,e.length));return e.slice(0,o)+n+e.slice(o)}function Px(e,t){const n=e.indexOf(t);return n<0?e:e.slice(0,n)+e.slice(n+t.length)}function bs({value:e,onChange:t,t:n,candidates:o,onDone:r,testidPrefix:s="g7le-binding-chip-text",disabled:a=!1}){const l=u.useMemo(()=>Nx(e),[e]),d=u.useRef(null),c=!!o&&o.length>0,[p,f]=u.useState(null),[y,g]=u.useState(null),[m,h]=u.useState(null),[v,b]=u.useState(!1),x=u.useRef(0),k=u.useRef(null),w=u.useMemo(()=>l.map(L=>L.kind==="chip"?`c:${L.raw}`:"t").join("|"),[l]),_=u.useRef(null),C=u.useRef([]),E=u.useRef(!1);u.useLayoutEffect(()=>{const L=E.current;E.current=!1,!(!L&&_.current===w)&&(_.current=w,l.forEach((I,P)=>{if(I.kind!=="text")return;const F=C.current[P];F&&F.textContent!==(I.text??"")&&(F.textContent=I.text??"")}))},[w,l,e]);const S=u.useCallback(()=>{const L=d.current;if(!L)return e;const I=[];for(const P of Array.from(L.querySelectorAll("[data-seg]")))P.dataset.seg==="chip"?I.push(P.dataset.raw??""):I.push(P.textContent??"");return I.join("")},[e]),T=u.useCallback(()=>{a||t(S())},[a,t,S]),$=u.useRef(null),A=u.useRef(!1),z=(L,I)=>{if(!a){L.preventDefault(),L.stopPropagation(),$.current={raw:I,pointerId:L.pointerId},A.current=!1,f(I);try{L.currentTarget.setPointerCapture(L.pointerId)}catch{}}},D=L=>{const I=$.current;if(!I||L.pointerId!==I.pointerId)return;A.current=!0;const P=B(L.clientX,L.clientY);P&&(g({slotIdx:P.slotIdx,offset:P.offset}),h(Bx(d.current,P.slotIdx,P.offset)))},O=(L,I)=>{if(L.stopPropagation(),A.current){A.current=!1;return}E.current=!0,t(Px(e,I))},M=L=>{const I=$.current;if(!I||L.pointerId!==I.pointerId)return;try{L.currentTarget.releasePointerCapture(L.pointerId)}catch{}$.current=null,A.current&&typeof setTimeout=="function"&&setTimeout(()=>{A.current=!1},0);const P=B(L.clientX,L.clientY);if(f(null),g(null),h(null),!P)return;const F=d.current;if(!F)return;const V=Array.from(F.querySelectorAll("[data-seg]")).map(Z=>Z.dataset.seg==="chip"?{kind:"chip",raw:Z.dataset.raw??""}:{kind:"text",text:Z.textContent??""}),Y=Mx(V,I.raw,P.slotIdx,P.offset);Y!==e&&(E.current=!0,t(Y))},B=(L,I)=>{const P=d.current;if(!P)return null;const F=Array.from(P.querySelectorAll("[data-seg]")),G=P.ownerDocument,V=G.elementFromPoint(L,I),Y=V?.closest('[data-seg="text"]');if(Y)return{slotIdx:F.indexOf(Y),offset:Ox(G,Y,L,I)};const Z=V?.closest('[data-seg="chip"]');if(Z){const re=F.indexOf(Z),U=Z.getBoundingClientRect();if(L=0){const ee=F[re-1];return{slotIdx:re-1,offset:(ee.textContent??"").length}}if(re+1({s:re,i:U})).filter(re=>re.s.dataset.seg==="text").pop();return se?{slotIdx:se.i,offset:(se.s.textContent??"").length}:null},N=()=>{a||!c||(x.current=Wx(d.current,e.length),b(L=>!L))},H=L=>{const I=L.path?L.path.split(".").filter(Boolean):[],P=[L.sourceId,...I].join("?.");E.current=!0,t(Lx(e,x.current,`{{${P}}}`)),b(!1)};return i.jsxs("div",{"data-testid":s,style:{display:"flex",flexDirection:"column",gap:4},children:[i.jsxs("div",{ref:d,"data-testid":`${s}-box`,className:"g7le-binding-chip-input",style:jx,children:[l.map((L,I)=>L.kind==="chip"?i.jsxs("span",{"data-seg":"chip","data-raw":L.raw,"data-testid":`${s}-chip-${I}`,contentEditable:!1,onPointerDown:P=>z(P,L.raw??""),onPointerMove:D,onPointerUp:M,title:L.removable?n("layout_editor.value_tree.change_data"):n("layout_editor.prop_i18n.bound_code_only"),style:{...Vx,...p===L.raw?Ux:null,touchAction:"none"},children:[i.jsx("span",{"aria-hidden":"true",style:qx,children:"⠿"}),i.jsx("span",{"aria-hidden":"true",style:{marginRight:3},children:"🔗"}),i.jsx("span",{children:L.text}),L.removable&&!a&&i.jsx("button",{type:"button","data-testid":`${s}-chip-remove-${I}`,"aria-label":n("layout_editor.inline_binding.clear"),title:n("layout_editor.inline_binding.clear"),onPointerDown:P=>{P.stopPropagation(),P.preventDefault()},onClick:P=>O(P,L.raw??""),style:Gx,children:"✕"})]},`chip-${L.raw}-${I}`):i.jsx("span",{ref:P=>{C.current[I]=P},"data-seg":"text",contentEditable:!a,suppressContentEditableWarning:!0,onInput:T,"data-testid":`${s}-text-${I}`,style:{...Hx,...p?Fx:null,...y?.slotIdx===I?Kx:null}},`text-slot-${I}`)),m&&i.jsx("span",{"aria-hidden":"true","data-testid":`${s}-caret`,style:{position:"absolute",left:m.left,top:m.top,width:2,height:m.height,background:"#2563eb",borderRadius:1,pointerEvents:"none",boxShadow:"0 0 0 1px rgba(37,99,235,0.3)"}})]}),i.jsxs("div",{style:{display:"flex",alignItems:"center",gap:6,flexWrap:"wrap"},children:[r&&i.jsxs("button",{type:"button","data-testid":`${s}-done`,onClick:r,style:gc,title:n("layout_editor.value_tree.done"),"aria-label":n("layout_editor.value_tree.done"),children:["✓ ",n("layout_editor.value_tree.done")]}),c&&i.jsxs("div",{style:{position:"relative"},children:[i.jsxs("button",{ref:k,type:"button","data-testid":`${s}-insert`,onClick:N,disabled:a,style:gc,title:n("layout_editor.inline_binding.insert_data"),"aria-label":n("layout_editor.inline_binding.insert_data"),children:["+ ",n("layout_editor.inline_binding.insert_data")]}),i.jsx(Xn,{anchorRef:k,open:v,onClose:()=>b(!1),children:i.jsx($t,{candidates:o,t:n,onSelect:H,testIdSuffix:`${s}-insert`,defaultOpen:!0,floating:!1})})]}),i.jsx("span",{style:Yx,children:n("layout_editor.translation.placeholder_locked")})]})]})}function Ox(e,t,n,o){const r=(t.textContent??"").length;try{const a=e;let l=null,d=0;if(typeof a.caretRangeFromPoint=="function"){const c=a.caretRangeFromPoint(n,o);c&&(l=c.startContainer,d=c.startOffset)}else if(typeof a.caretPositionFromPoint=="function"){const c=a.caretPositionFromPoint(n,o);c&&(l=c.offsetNode,d=c.offset)}if(l&&t.contains(l))return Math.max(0,Math.min(d,r))}catch{}const s=t.getBoundingClientRect();return n>s.left+s.width/2?r:0}function Bx(e,t,n){if(!e)return null;const r=Array.from(e.querySelectorAll("[data-seg]"))[t];if(!r)return null;const s=e.getBoundingClientRect();try{const d=r.firstChild,c=e.ownerDocument;if(d&&d.nodeType===Node.TEXT_NODE&&(d.textContent??"").length>0){const p=c.createRange(),f=Math.max(0,Math.min(n,(d.textContent??"").length));p.setStart(d,f),p.setEnd(d,f);const y=p.getClientRects(),g=y.length>0?y[0]:p.getBoundingClientRect();if(g&&(g.height>0||g.width>=0))return{left:g.left-s.left,top:g.top-s.top,height:g.height||r.getBoundingClientRect().height}}}catch{}const a=r.getBoundingClientRect();return{left:(n>0?a.right:a.left)-s.left,top:a.top-s.top,height:a.height||16}}function Wx(e,t){if(!e)return t;try{const n=e.ownerDocument.defaultView?.getSelection();if(!n||n.rangeCount===0)return t;const o=n.getRangeAt(0);let r=o.startContainer,s=null;for(;r&&r!==e;){if(r instanceof HTMLElement&&r.dataset.seg==="text"){s=r;break}r=r.parentNode}if(!s||!e.contains(s))return t;const a=Array.from(e.querySelectorAll("[data-seg]"));let l=0;for(const d of a){if(d===s)break;l+=d.dataset.seg==="chip"?(d.dataset.raw??"").length:(d.textContent??"").length}return l+o.startOffset}catch{return t}}const jx={position:"relative",display:"flex",flexWrap:"wrap",alignItems:"center",gap:2,minHeight:30,padding:"4px 8px",border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",fontSize:12,lineHeight:1.6,minWidth:0,width:"100%",boxSizing:"border-box"},Hx={outline:"none",minWidth:8,minHeight:"1.2em",whiteSpace:"pre-wrap",color:"#0f172a",display:"inline-block",wordBreak:"break-all"},Fx={boxShadow:"inset 0 -2px 0 #93c5fd",background:"#eff6ff",borderRadius:2},Kx={background:"#dbeafe",boxShadow:"inset 0 -2px 0 #2563eb"},Vx={display:"inline-flex",alignItems:"center",gap:1,background:"linear-gradient(180deg,#eff6ff,#dbeafe)",color:"#1d4ed8",border:"1px dashed #60a5fa",borderRadius:9999,padding:"1px 8px 1px 4px",margin:"0 1px",fontSize:11,fontWeight:600,cursor:"grab",userSelect:"none",whiteSpace:"nowrap",boxShadow:"0 1px 2px rgba(37,99,235,0.18)",verticalAlign:"middle"},Ux={opacity:.5,borderStyle:"solid",cursor:"grabbing"},qx={color:"#2563eb",opacity:.7,fontSize:11,marginRight:1,cursor:"grab"},Gx={marginLeft:4,width:14,height:14,lineHeight:"12px",padding:0,border:"none",borderRadius:9999,background:"rgba(29,78,216,0.12)",color:"#1d4ed8",fontSize:9,fontWeight:700,cursor:"pointer",display:"inline-flex",alignItems:"center",justifyContent:"center",flex:"0 0 auto"},gc={padding:"4px 8px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#475569",cursor:"pointer",whiteSpace:"nowrap"},Yx={fontSize:10,color:"#94a3b8"},Xx=ql;function ms(e){return _b(e)}function Ve({value:e,onChange:t,t:n,candidates:o,placeholder:r,testidPrefix:s="g7le-data-chip-value",readOnly:a=!1,enableExpressionTree:l=!0}){const d=typeof e=="string"?e:"",c=!!o&&o.length>0,[p,f]=u.useState(!1),[y,g]=u.useState(!1),[m,h]=u.useState(!1),v=u.useCallback(E=>i.jsx(Ve,{value:E.value,onChange:S=>E.onChange(S),t:E.t,candidates:E.candidates,testidPrefix:E.testidPrefix,enableExpressionTree:!0}),[]),b=()=>i.jsx("div",{style:{display:"flex",gap:4,alignItems:"center",width:"100%",minWidth:0},children:i.jsx("input",{type:"text","data-testid":`${s}-input`,value:d,placeholder:r??n("layout_editor.page_settings.seo.value_placeholder"),readOnly:a,onChange:E=>t(E.target.value),onFocus:()=>f(!0),onBlur:()=>f(!1),style:{flex:1,padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,minWidth:0,width:"100%",background:a?"#f1f5f9":"#fff",color:a?"#64748b":"#0f172a"}})}),x=u.useCallback(E=>{const S=d?`${d} ${E.expression}`:E.expression;t(S)},[d,t]);if(!l||a)return i.jsxs("div",{"data-testid":s,style:pn,children:[b(),!a&&c&&i.jsx($t,{candidates:o,t:n,onSelect:x,testIdSuffix:`${s}-pick`})]});const k=Nv(d),w=Gd(d),_=Kt(d),C=E=>{const S=Sr(d),T=S?us(S,()=>"",$=>Vt($)):[];return i.jsxs(i.Fragment,{children:[i.jsx("div",{style:{display:"flex",justifyContent:"flex-start"},children:i.jsxs("button",{type:"button","data-testid":`${E}-to-plain`,onClick:()=>h(!0),style:Rx,title:n("layout_editor.value_tree.to_plain"),"aria-label":n("layout_editor.value_tree.to_plain"),children:["↩ ",n("layout_editor.value_tree.to_plain")]})}),m&&i.jsxs("div",{"data-testid":`${E}-to-plain-confirm`,style:e0,children:[i.jsx("p",{style:t0,children:n("layout_editor.value_tree.to_plain_lead")}),i.jsx("div",{style:n0,children:T.length===0?i.jsx("span",{style:{color:"#94a3b8"},children:n("layout_editor.page_settings.seo.value_placeholder")}):T.map(($,A)=>$.kind==="chip"?i.jsxs("span",{style:vs,children:["🔗 ",$.text]},A):i.jsx("span",{style:$.kind==="ellipsis"?{color:"#94a3b8"}:void 0,children:$.text},A))}),i.jsxs("p",{style:o0,children:["⚠ ",n("layout_editor.value_tree.to_plain_warn")]}),i.jsxs("div",{style:{display:"flex",justifyContent:"flex-end",gap:6},children:[i.jsx("button",{type:"button","data-testid":`${E}-to-plain-confirm-cancel`,onClick:()=>h(!1),style:Ut,children:n("layout_editor.value_tree.to_plain_cancel")}),i.jsx("button",{type:"button","data-testid":`${E}-to-plain-confirm-ok`,onClick:()=>{h(!1),t(Sr(d)||"")},style:r0,children:n("layout_editor.value_tree.to_plain_ok")})]})]})]})};if(!p&&(k==="expression"||w)){if(w)return i.jsxs("div",{"data-testid":s,style:pn,children:[!a&&C(`${s}-seg`),i.jsx(gs,{value:d,onChange:S=>t(S),t:n,candidates:o,testidPrefix:`${s}-seg`,renderLeafInput:v})]});const E=un(d);return i.jsxs("div",{"data-testid":s,style:pn,children:[!a&&C(`${s}-tree`),i.jsx(nc,{node:E.node,onChange:S=>t(Zn(S,!1)),t:n,candidates:o,testidPrefix:`${s}-tree`,renderLeafInput:v})]})}if(_&&!p&&!y)return i.jsx(Jx,{value:d,onChange:t,t:n,candidates:o,testidPrefix:s,onEditRaw:()=>g(!0)});if(!p&&!y&&ms(d)){const E=Xx(d);return i.jsx("div",{"data-testid":s,style:pn,children:i.jsxs("div",{style:hc,children:[i.jsx("div",{"data-testid":`${s}-chips`,style:Zx,children:E.map((S,T)=>S.kind==="chip"?i.jsxs("span",{style:vs,children:["🔗 ",S.label]},T):i.jsx("span",{children:S.label},T))}),i.jsxs("button",{type:"button","data-testid":`${s}-edit-raw`,onClick:()=>g(!0),style:Ut,title:n("layout_editor.value_tree.edit"),"aria-label":n("layout_editor.value_tree.edit"),children:["✎ ",n("layout_editor.value_tree.edit")]})]})})}return y&&ms(d)?i.jsxs("div",{"data-testid":s,style:pn,children:[i.jsx(bs,{value:d,onChange:t,t:n,candidates:o,disabled:a,onDone:()=>g(!1),testidPrefix:`${s}-chipedit`}),!a&&k!=="raw"&&i.jsxs("div",{style:{display:"flex",gap:4,flexWrap:"wrap"},children:[i.jsxs("button",{type:"button","data-testid":`${s}-to-expr`,onClick:()=>t(Co(d)),style:Ut,title:n("layout_editor.value_tree.to_expression"),"aria-label":n("layout_editor.value_tree.to_expression"),children:["ƒx ",n("layout_editor.value_tree.to_expression")]}),i.jsxs("button",{type:"button","data-testid":`${s}-to-fallback`,onClick:()=>t(ps(d)),style:Ut,title:n("layout_editor.value_tree.segment.fallback"),"aria-label":n("layout_editor.value_tree.segment.fallback"),children:["?? ",n("layout_editor.value_tree.segment.fallback")]})]}),k==="raw"&&i.jsxs("p",{"data-testid":`${s}-raw-hint`,style:_s,children:["ⓘ ",n("layout_editor.value_tree.raw_expression_hint")]})]}):!p&&ms(d)?i.jsxs("div",{"data-testid":s,style:pn,children:[i.jsx(bs,{value:d,onChange:t,t:n,candidates:o,disabled:a,onDone:()=>{g(!1),f(!1)},testidPrefix:`${s}-chipedit`}),!a&&k!=="raw"&&i.jsxs("div",{style:{display:"flex",gap:4,flexWrap:"wrap"},children:[i.jsxs("button",{type:"button","data-testid":`${s}-to-expr`,onClick:()=>t(Co(d)),style:Ut,title:n("layout_editor.value_tree.to_expression"),"aria-label":n("layout_editor.value_tree.to_expression"),children:["ƒx ",n("layout_editor.value_tree.to_expression")]}),i.jsxs("button",{type:"button","data-testid":`${s}-to-fallback`,onClick:()=>t(ps(d)),style:Ut,title:n("layout_editor.value_tree.segment.fallback"),"aria-label":n("layout_editor.value_tree.segment.fallback"),children:["?? ",n("layout_editor.value_tree.segment.fallback")]})]}),k==="raw"&&i.jsxs("p",{"data-testid":`${s}-raw-hint`,style:_s,children:["ⓘ ",n("layout_editor.value_tree.raw_expression_hint")]})]}):i.jsxs("div",{"data-testid":s,style:pn,children:[b(),i.jsxs("div",{style:Qx,children:[c&&i.jsx($t,{candidates:o,t:n,onSelect:x,testIdSuffix:`${s}-pick`}),k!=="raw"&&i.jsxs("div",{style:{display:"flex",gap:4},children:[i.jsxs("button",{type:"button","data-testid":`${s}-to-expr`,onClick:()=>t(Co(d)),style:Ut,title:n("layout_editor.value_tree.to_expression"),"aria-label":n("layout_editor.value_tree.to_expression"),children:["ƒx ",n("layout_editor.value_tree.to_expression")]}),i.jsxs("button",{type:"button","data-testid":`${s}-to-fallback`,onClick:()=>t(ps(d)),style:Ut,title:n("layout_editor.value_tree.segment.fallback"),"aria-label":n("layout_editor.value_tree.segment.fallback"),children:["?? ",n("layout_editor.value_tree.segment.fallback")]})]})]}),k==="raw"&&!p&&i.jsxs("p",{"data-testid":`${s}-raw-hint`,style:_s,children:["ⓘ ",n("layout_editor.value_tree.raw_expression_hint")]})]})}function Jx({value:e,onChange:t,t:n,candidates:o,testidPrefix:r,onEditRaw:s}){const[a,l]=u.useState(!1),d=u.useRef(null),c=Vt(e),p=!!o&&o.length>0,f=Kt(e)?.localeFn;return i.jsxs("div",{"data-testid":r,style:pn,children:[i.jsxs("div",{style:hc,children:[i.jsxs("span",{"data-testid":`${r}-chip`,style:vs,children:["🔗 ",c]}),p&&i.jsx("button",{ref:d,type:"button","data-testid":`${r}-change`,onClick:()=>l(y=>!y),style:Ut,title:n("layout_editor.value_tree.change_data"),"aria-label":n("layout_editor.value_tree.change_data"),children:n("layout_editor.value_tree.change_data")}),i.jsx("button",{type:"button","data-testid":`${r}-edit-raw`,onClick:s,style:Ut,title:n("layout_editor.value_tree.edit"),"aria-label":n("layout_editor.value_tree.edit"),children:"✎"})]}),p&&i.jsx(Xn,{anchorRef:d,open:a,onClose:()=>l(!1),children:i.jsx($t,{candidates:o,t:n,onSelect:y=>{if(f){const h=[y.sourceId,...y.path?y.path.split(".").filter(Boolean):[]].join(".");t(`{{${f}(${h})}}`),l(!1);return}const g=y.path?y.path.split(".").filter(Boolean):[],m=[y.sourceId,...g].join("?.");t(`{{${m}}}`),l(!1)},testIdSuffix:`${r}-pick`,defaultOpen:!0,floating:!1})})]})}const pn={display:"flex",flexDirection:"column",gap:4,width:"100%",minWidth:0},hc={display:"flex",alignItems:"center",gap:6,width:"100%",minWidth:0,flexWrap:"wrap"},Zx={flex:1,minWidth:0,display:"flex",flexWrap:"wrap",alignItems:"center",gap:4,padding:"5px 8px",fontSize:12,color:"#334155",border:"1px solid #e2e8f0",borderRadius:6,background:"#f8fafc"},vs={fontSize:11,background:"#eef2ff",color:"#4338ca",padding:"2px 8px",borderRadius:12,whiteSpace:"nowrap"},Ut={padding:"4px 8px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#475569",cursor:"pointer",whiteSpace:"nowrap"},Qx={display:"flex",flexWrap:"wrap",alignItems:"center",gap:6},_s={margin:"2px 0 0",fontSize:11,color:"#94a3b8",lineHeight:1.4},Rx={padding:"3px 10px",fontSize:11,border:"1px solid #fca5a5",borderRadius:6,background:"#fef2f2",color:"#b91c1c",cursor:"pointer",whiteSpace:"nowrap"},e0={display:"flex",flexDirection:"column",gap:6,padding:10,border:"1px solid #fca5a5",borderRadius:8,background:"#fffbeb"},t0={margin:0,fontSize:12,color:"#334155"},n0={padding:"5px 8px",fontSize:12,color:"#0f172a",background:"#fff",border:"1px solid #e2e8f0",borderRadius:6,wordBreak:"break-all",display:"flex",flexWrap:"wrap",gap:4,alignItems:"center"},o0={margin:0,fontSize:11,color:"#b45309"},r0={padding:"3px 12px",fontSize:11,border:"1px solid #dc2626",borderRadius:6,background:"#dc2626",color:"#fff",cursor:"pointer",whiteSpace:"nowrap"};let yc=0;function zr({value:e,onChange:t,keyField:n,valueField:o="content",t:r,candidates:s,testidPrefix:a="g7le-kv-chip-editor",keyPlaceholder:l,valuePlaceholder:d,addLabel:c,renderKeyInput:p}){const f=u.useMemo(()=>(Array.isArray(e)?e:[]).map(k=>({id:yc++,k:typeof k[n]=="string"?k[n]:"",v:typeof k[o]=="string"?k[o]:""})),[]),[y,g]=u.useState(f),m=u.useCallback(k=>{g(k);const w=k.filter(_=>_.k.trim()!=="").map(_=>({[n]:_.k,[o]:_.v}));t(w)},[n,o,t]),h=u.useCallback(()=>{m([...y,{id:yc++,k:"",v:""}])},[y,m]),v=u.useCallback(k=>{m(y.filter(w=>w.id!==k))},[y,m]),b=u.useCallback((k,w)=>{m(y.map(_=>_.id===k?{..._,k:w}:_))},[y,m]),x=u.useCallback((k,w)=>{m(y.map(_=>_.id===k?{..._,v:w}:_))},[y,m]);return i.jsxs("div",{"data-testid":a,style:{display:"flex",flexDirection:"column",gap:6},children:[y.map(k=>i.jsxs("div",{"data-testid":`${a}-row-${k.id}`,style:{display:"flex",gap:6,alignItems:"flex-start",border:"1px solid #e2e8f0",borderRadius:8,padding:8},children:[i.jsxs("div",{style:{flex:1,minWidth:0,display:"flex",flexDirection:"column",gap:6},children:[i.jsx("div",{style:{display:"flex",flexDirection:"column",gap:2,minWidth:0},children:p?i.jsx("div",{style:{minWidth:0},children:p({value:k.k,onChange:w=>b(k.id,w),testid:`${a}-key-${k.id}`,placeholder:l??r(`layout_editor.page_settings.seo.extra_${n}`)})}):i.jsx("input",{type:"text","data-testid":`${a}-key-${k.id}`,value:k.k,placeholder:l??r(`layout_editor.page_settings.seo.extra_${n}`),onChange:w=>b(k.id,w.target.value),style:{width:"100%",padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,minWidth:0,boxSizing:"border-box"}})}),i.jsx("div",{style:{minWidth:0},children:i.jsx(Ve,{value:k.v,onChange:w=>x(k.id,w),t:r,candidates:s,placeholder:d??r("layout_editor.page_settings.seo.extra_content"),testidPrefix:`${a}-value-${k.id}`})})]}),i.jsx("button",{type:"button","data-testid":`${a}-remove-${k.id}`,onClick:()=>v(k.id),title:r("layout_editor.page_settings.seo.remove"),"aria-label":r("layout_editor.page_settings.seo.remove"),style:{flex:"0 0 auto",padding:"4px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#64748b",cursor:"pointer"},children:"✕"})]},k.id)),i.jsxs("button",{type:"button","data-testid":`${a}-add`,onClick:h,style:{alignSelf:"flex-start",padding:"5px 10px",fontSize:12,border:"1px dashed #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#475569",cursor:"pointer"},children:["+ ",c??r("layout_editor.page_settings.seo.add_extra")]})]})}const i0=["website","article","product","profile"],s0=["summary","summary_large_image","app","player"];function a0(e){switch(e){case"core":return"layout_editor.page_settings.seo.og_source_core";case"inherited":return"layout_editor.page_settings.seo.og_source_inherited";case"filter":return"layout_editor.page_settings.seo.og_source_filter";case"layout":return null;default:return e?"layout_editor.page_settings.seo.og_source_module":null}}function bc(e){const t={};for(const n of e??[])t[n.key]=n;return t}function mc(e,t){return!!e&&t&&typeof e.sourceExpr=="string"&&e.sourceExpr!==""&&!e.overriddenByLayout&&!e.lockedByFilter}function vc({label:e,sourceExpr:t,onReplace:n,t:o,testid:r}){return i.jsxs("div",{"data-testid":r,style:b0,children:[i.jsxs("span",{"data-testid":`${r}-chip`,style:m0,title:t,children:["🔗 ",e||t]}),i.jsx("span",{"data-testid":`${r}-badge`,style:v0,children:o("layout_editor.page_settings.seo.auto_chip_source")}),i.jsx("button",{type:"button","data-testid":`${r}-replace`,onClick:n,style:_0,children:o("layout_editor.page_settings.seo.auto_chip_replace")})]})}function l0({og:e,twitter:t,onChangeOg:n,onChangeTwitter:o,ogPreview:r,twitterPreview:s,defaultsAvailable:a=!1,candidates:l,t:d,testidPrefix:c="g7le-seo-og"}){const p=e&&typeof e=="object"?e:{},f=t&&typeof t=="object"?t:{},[y,g]=u.useState(p.image_height!=null||p.image_type!=null||p.image_secure_url!=null),m=bc(r),h=bc(s),v=u.useCallback((T,$)=>{const A={...p};$===void 0||$===""||$===null?delete A[T]:A[T]=$,n(A)},[p,n]),b=u.useCallback((T,$)=>{const A={...f};$===void 0||$===""||$===null?delete A[T]:A[T]=$,o(A)},[f,o]),x=(T,$,A)=>!A||!a||!A.lockedByFilter?null:i.jsx("div",{style:{display:"flex",gap:6,alignItems:"center",marginTop:2},children:i.jsxs("span",{"data-testid":`${T}-locked-${$}`,style:g0,children:["🔒 ",d("layout_editor.page_settings.seo.og_locked")]})}),k=T=>{if(!T||!a||T.lockedByFilter||T.overriddenByLayout)return;const $=a0(T.source);return $?d($):void 0},w=(T,$)=>T&&a?T.overriddenByLayout===!0:$!=="",_=(T,$,A,z,D,O)=>{const M=O[$],B=!!M?.lockedByFilter,N=typeof z[$]=="string"?z[$]:"";return i.jsxs("div",{"data-testid":`${T}-${$}`,style:Dn,children:[i.jsx("label",{style:to,children:d(A)}),B?i.jsx("div",{"data-testid":`${T}-locked-value-${$}`,style:f0,children:String(M?.effectiveValue??"")}):mc(M,a)?i.jsx(vc,{label:M.label??"",sourceExpr:M.sourceExpr,onReplace:()=>D($,M.sourceExpr),t:d,testid:`${T}-${$}-auto`}):i.jsx(vt,{value:N,onChange:H=>D($,H??""),t:d,candidates:l,placeholder:a&&M?.effectiveValue!=null?String(M.effectiveValue):void 0,testidPrefix:`${T}-${$}-field`,enableExpressionTree:!0,expressionTreeCollapsible:!0}),x(T,$,M)]})},C=(T,$,A,z,D,O)=>{const M=O[$],B=!!M?.lockedByFilter,N=typeof z[$]=="string"?z[$]:"";return i.jsxs("div",{"data-testid":`${T}-${$}`,style:Dn,children:[i.jsx("label",{style:to,children:d(A)}),mc(M,a)?i.jsx(vc,{label:M.label??"",sourceExpr:M.sourceExpr,onReplace:()=>D($,M.sourceExpr),t:d,testid:`${T}-${$}-auto`}):i.jsx(Ve,{value:N,onChange:H=>D($,H),t:d,candidates:l,readOnly:B,placeholder:a&&M?.effectiveValue!=null?String(M.effectiveValue):void 0,testidPrefix:`${T}-${$}-field`}),x(T,$,M)]})},E=(T,$,A,z,D,O)=>{const M=O[$],B=!!M?.lockedByFilter,N=typeof z[$]=="string"?z[$]:"",H=w(M,N),L=H?N:"";return i.jsxs("div",{"data-testid":`${T}-${$}`,style:Dn,children:[i.jsx("label",{style:to,children:d(A)}),i.jsx(kx,{filled:H,sourceLabel:k(M),onRevert:B?void 0:()=>D($,void 0),revertLabel:d("layout_editor.page_settings.seo.revert_default"),testid:`${T}-${$}-overlay`,children:i.jsx("div",{"data-testid":`${T}-${$}-input`,style:{minWidth:0},children:i.jsx(Ve,{value:L,onChange:I=>D($,I),t:d,candidates:l,readOnly:B,placeholder:a&&M?.effectiveValue!=null?String(M.effectiveValue):void 0,testidPrefix:`${T}-${$}-chip`})})}),x(T,$,M)]})},S=(T,$,A,z,D,O,M)=>{const B=M[$],N=typeof D[$]=="string"?D[$]:"",H=w(B,N),L=k(B),I=H?N:"";return i.jsxs("div",{"data-testid":`${T}-${$}`,style:Dn,children:[i.jsxs("div",{style:c0,children:[i.jsx("label",{style:to,children:d(A)}),L?i.jsxs("span",{"data-testid":`${T}-${$}-source-inline`,style:u0,children:["· ",L]}):null]}),i.jsxs("select",{"data-testid":`${T}-${$}-select`,value:I,onChange:P=>O($,P.target.value),style:p0,children:[i.jsx("option",{value:"",children:a&&B?.effectiveValue!=null&&B.effectiveValue!==""?`${d("layout_editor.page_settings.seo.use_default")} (${String(B.effectiveValue)})`:d("layout_editor.page_settings.seo.use_default")}),z.map(P=>i.jsx("option",{value:P,children:P},P))]}),x(T,$,B)]})};return i.jsxs("div",{"data-testid":c,style:{display:"flex",flexDirection:"column",gap:12},children:[i.jsxs("div",{"data-testid":`${c}-og-section`,style:_c,children:[i.jsx("div",{style:xc,children:d("layout_editor.page_settings.seo.og_section")}),i.jsxs("p",{"data-testid":`${c}-intro`,style:d0,children:["ⓘ ",d("layout_editor.page_settings.seo.og_intro")]}),S(c,"type","layout_editor.page_settings.seo.og_type",i0,p,v,m),_(c,"title","layout_editor.page_settings.seo.og_title",p,v,m),_(c,"description","layout_editor.page_settings.seo.og_description",p,v,m),C(c,"image","layout_editor.page_settings.seo.og_image",p,v,m),_(c,"image_alt","layout_editor.page_settings.seo.og_image_alt",p,v,m),i.jsxs("div",{style:Dn,children:[i.jsxs("button",{type:"button","data-testid":`${c}-image-adv-toggle`,"aria-expanded":y,onClick:()=>g(T=>!T),style:h0,children:[y?"▾":"▸"," ",d("layout_editor.page_settings.seo.og_image_adv")]}),y?i.jsxs("div",{"data-testid":`${c}-image-adv`,style:y0,children:[E(c,"image_height","layout_editor.page_settings.seo.og_image_height",p,v,m),E(c,"image_type","layout_editor.page_settings.seo.og_image_type",p,v,m),E(c,"image_secure_url","layout_editor.page_settings.seo.og_image_secure_url",p,v,m)]}):null]}),E(c,"site_name","layout_editor.page_settings.seo.og_site_name",p,v,m),E(c,"locale","layout_editor.page_settings.seo.og_locale",p,v,m),i.jsxs("div",{"data-testid":`${c}-extra`,style:Dn,children:[i.jsx("label",{style:to,children:d("layout_editor.page_settings.seo.og_extra")}),i.jsx(zr,{value:Array.isArray(p.extra)?p.extra:[],onChange:T=>v("extra",T.length>0?T:void 0),keyField:"property",t:d,candidates:l,testidPrefix:`${c}-extra-editor`})]})]}),i.jsxs("div",{"data-testid":`${c}-tw-section`,style:_c,children:[i.jsx("div",{style:xc,children:d("layout_editor.page_settings.seo.tw_section")}),i.jsx("p",{style:{margin:"0 0 6px",fontSize:11,color:"#94a3b8"},children:d("layout_editor.page_settings.seo.tw_fallback_note")}),S("g7le-seo-tw","card","layout_editor.page_settings.seo.tw_card",s0,f,b,h),E("g7le-seo-tw","site","layout_editor.page_settings.seo.tw_site",f,b,h),E("g7le-seo-tw","creator","layout_editor.page_settings.seo.tw_creator",f,b,h),_("g7le-seo-tw","title","layout_editor.page_settings.seo.tw_title",f,b,h),_("g7le-seo-tw","description","layout_editor.page_settings.seo.tw_description",f,b,h),C("g7le-seo-tw","image","layout_editor.page_settings.seo.tw_image",f,b,h),_("g7le-seo-tw","image_alt","layout_editor.page_settings.seo.tw_image_alt",f,b,h),i.jsxs("div",{"data-testid":"g7le-seo-tw-extra",style:Dn,children:[i.jsx("label",{style:to,children:d("layout_editor.page_settings.seo.tw_extra")}),i.jsx(zr,{value:Array.isArray(f.extra)?f.extra:[],onChange:T=>b("extra",T.length>0?T:void 0),keyField:"name",t:d,candidates:l,testidPrefix:"g7le-seo-tw-extra-editor"})]})]})]})}const _c={border:"1px solid #e2e8f0",borderRadius:8,padding:12,display:"flex",flexDirection:"column",gap:10},xc={fontSize:13,fontWeight:600,color:"#0f172a"},d0={margin:0,fontSize:11,color:"#64748b",background:"#f8fafc",borderRadius:6,padding:"6px 8px"},Dn={display:"flex",flexDirection:"column",gap:4},to={fontSize:12,color:"#475569"},c0={display:"flex",alignItems:"center",gap:6},u0={fontSize:11,color:"#94a3b8"},p0={padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,width:"100%",minWidth:0,boxSizing:"border-box"},f0={padding:"5px 8px",fontSize:12,border:"1px solid #e2e8f0",borderRadius:6,background:"#f1f5f9",color:"#64748b"},g0={fontSize:11,color:"#7c3aed"},h0={alignSelf:"flex-start",padding:"2px 0",fontSize:12,fontWeight:600,color:"#475569",background:"transparent",border:"none",cursor:"pointer"},y0={display:"flex",flexDirection:"column",gap:8,paddingLeft:14,marginTop:4,minWidth:0},b0={display:"flex",alignItems:"center",gap:8,flexWrap:"wrap",minWidth:0},m0={display:"inline-flex",alignItems:"center",maxWidth:"100%",padding:"3px 8px",fontSize:12,color:"#1d4ed8",background:"#eff6ff",border:"1px solid #bfdbfe",borderRadius:6,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},v0={fontSize:11,color:"#94a3b8"},_0={padding:"2px 8px",fontSize:11,color:"#475569",background:"#fff",border:"1px solid #cbd5e1",borderRadius:6,cursor:"pointer"},x0=["WebPage","WebSite","ItemList","BreadcrumbList","Organization","Person","CollectionPage","AboutPage","ContactPage","SearchAction"];function Mr(e){const t=[],n=(o,r)=>{for(const[s,a]of Object.entries(o)){if(s==="@type"||s==="@context")continue;const l=r?`${r}.${s}`:s;a!==null&&typeof a=="object"&&!Array.isArray(a)?n(a,l):t.push({key:l,value:a==null?"":String(a)})}};return n(e,""),t}function wc(e,t){const n={"@type":e};for(const{key:o,value:r}of t){if(o.trim()==="")continue;const s=o.split(".");let a=n;for(let l=0;lp?Mr(e).map(D=>({id:xs++,...D})):[]),x=u.useCallback((D,O)=>{h(D),b(O),t(wc(D,O.map(M=>({key:M.key,value:M.value}))))},[t]),k=u.useCallback(()=>{const D=n,O=typeof D["@type"]=="string"?D["@type"]:m,M=Mr(D).map(B=>({id:xs++,key:B.key,value:o?.[B.key]?.expr??B.value}));return{type:O,rows:M}},[n,o,m]),w=u.useCallback(D=>{if(y(D),D){if(v.length===0&&n&&typeof n=="object"){const O=k();x(O.type,O.rows);return}t(wc(m,v.map(O=>({key:O.key,value:O.value}))))}else t(null)},[m,v,n,k,x,t]),_=u.useCallback(()=>{if(!n)return;const D=k();x(D.type,D.rows)},[n,k,x]),C=u.useCallback(()=>{x(m,[...v,{id:xs++,key:"",value:""}])},[m,v,x]),E=u.useCallback(D=>{x(m,v.filter(O=>O.id!==D))},[m,v,x]),S=u.useCallback((D,O)=>{x(m,v.map(M=>M.id===D?{...M,key:O}:M))},[m,v,x]),T=u.useCallback((D,O)=>{x(m,v.map(M=>M.id===D?{...M,value:O}:M))},[m,v,x]),$=u.useMemo(()=>n?Mr(n):[],[n]),A=u.useMemo(()=>s?Mr(s):[],[s]),z=v.some(D=>D.key.trim()!==""&&D.value.trim()==="");if(r){const D=s&&typeof s["@type"]=="string"?s["@type"]:"";return i.jsxs("div",{"data-testid":c,style:kc,children:[i.jsxs("div",{style:Sc,children:[i.jsx("span",{style:k0,children:"🔒"}),i.jsx("span",{style:{fontSize:13,fontWeight:600},children:d("layout_editor.page_settings.seo.sd_mode")}),i.jsx("span",{"data-testid":`${c}-locked-badge`,style:S0,children:d("layout_editor.page_settings.seo.sd_filter_locked")})]}),i.jsxs("div",{"data-testid":`${c}-locked`,style:C0,children:[i.jsxs("p",{style:{margin:"0 0 6px",fontSize:12,color:"#475569"},children:["ⓘ ",d("layout_editor.page_settings.seo.sd_filter_locked_note")]}),i.jsxs("div",{"data-testid":`${c}-filtered-block`,children:[D?i.jsxs("div",{style:{fontSize:12,fontWeight:600,marginBottom:4},children:["@type: ",D]}):null,A.map(O=>i.jsxs("div",{"data-testid":`${c}-filtered-row-${O.key}`,style:Cc,children:[i.jsx("code",{style:$c,children:O.key}),i.jsx("span",{style:Ec,children:O.value})]},O.key))]})]})]})}return i.jsxs("div",{"data-testid":c,style:kc,children:[i.jsx("div",{style:Sc,children:i.jsx(Dr,{checked:f,onChange:w,testid:`${c}-mode`,label:d("layout_editor.page_settings.seo.sd_manual_toggle")})}),!f&&i.jsxs("div",{"data-testid":`${c}-auto`,style:{marginTop:8},children:[i.jsxs("div",{"data-testid":`${c}-auto-preview`,style:$0,children:[i.jsxs("div",{style:{fontSize:12,fontWeight:600,marginBottom:6},children:[d("layout_editor.page_settings.seo.sd_auto_title"),n&&typeof n["@type"]=="string"?`: ${n["@type"]}`:"",a?i.jsxs("span",{style:{marginLeft:6,color:"#94a3b8",fontWeight:400},children:["(",a,")"]}):null]}),$.length===0?i.jsx("p",{style:{margin:0,fontSize:12,color:"#94a3b8"},children:d("layout_editor.page_settings.seo.sd_auto_empty")}):$.map(D=>{const O=o?.[D.key];return i.jsxs("div",{"data-testid":`${c}-auto-row-${D.key}`,style:Cc,children:[i.jsx("code",{style:$c,children:D.key}),O?i.jsxs("span",{"data-testid":`${c}-auto-chip-${D.key}`,style:E0,title:O.expr,children:["🔗 ",O.label||O.expr]}):i.jsx("span",{style:Ec,children:D.value})]},D.key)})]}),i.jsxs("p",{"data-testid":`${c}-replace-warn`,style:I0,children:["ⓘ ",d("layout_editor.page_settings.seo.sd_replace_pre_warn")]})]}),f&&i.jsxs("div",{"data-testid":`${c}-manual`,style:{marginTop:8},children:[i.jsxs("p",{"data-testid":`${c}-replace-warn`,style:A0,children:["⚠ ",d("layout_editor.page_settings.seo.sd_replace_warn")]}),i.jsxs("div",{style:{display:"flex",flexDirection:"column",gap:6},children:[i.jsx("label",{style:{fontSize:12,color:"#475569"},children:d("layout_editor.page_settings.seo.sd_type")}),i.jsx("input",{type:"text","data-testid":`${c}-type`,list:`${c}-type-options`,value:m,onChange:D=>x(D.target.value,v),style:T0}),i.jsx("datalist",{id:`${c}-type-options`,children:x0.map(D=>i.jsx("option",{value:D},D))})]}),i.jsx("div",{style:{marginTop:8,display:"flex",flexDirection:"column",gap:6},children:v.map(D=>i.jsxs("div",{"data-testid":`${c}-prop-${D.id}`,style:D0,children:[i.jsx("input",{type:"text","data-testid":`${c}-prop-key-${D.id}`,value:D.key,placeholder:d("layout_editor.page_settings.seo.sd_prop_key"),onChange:O=>S(D.id,O.target.value),style:{width:160,padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,minWidth:0}}),i.jsx("div",{style:{flex:1,minWidth:0},children:i.jsx(Ve,{value:D.value,onChange:O=>T(D.id,O),t:d,candidates:l,placeholder:d("layout_editor.page_settings.seo.sd_prop_value"),testidPrefix:`${c}-prop-value-${D.id}`})}),i.jsx("button",{type:"button","data-testid":`${c}-prop-remove-${D.id}`,onClick:()=>E(D.id),title:d("layout_editor.page_settings.seo.remove"),"aria-label":d("layout_editor.page_settings.seo.remove"),style:N0,children:"✕"})]},D.id))}),i.jsxs("div",{style:{marginTop:8,display:"flex",gap:8},children:[i.jsxs("button",{type:"button","data-testid":`${c}-add`,onClick:C,style:Ic,children:["+ ",d("layout_editor.page_settings.seo.sd_add_prop")]}),n?i.jsxs("button",{type:"button","data-testid":`${c}-seed-from-module`,onClick:_,style:Ic,children:["↩ ",d("layout_editor.page_settings.seo.sd_seed_from_module")]}):null]}),i.jsxs("p",{"data-testid":`${c}-empty-note`,style:z0,children:["ⓘ ",d("layout_editor.page_settings.seo.sd_empty_note"),z?i.jsxs("span",{style:{color:"#b45309"},children:[" ",d("layout_editor.page_settings.seo.sd_has_empty")]}):null]})]})]})}const kc={display:"flex",flexDirection:"column"},Sc={display:"flex",alignItems:"center",gap:8},k0={fontSize:14},S0={fontSize:11,padding:"2px 6px",borderRadius:4,background:"#f1f5f9",color:"#64748b"},C0={marginTop:8,border:"1px solid #fcd34d",borderRadius:8,padding:10,background:"#fffbeb"},$0={border:"1px solid #e2e8f0",borderRadius:8,padding:10,background:"#f8fafc"},Cc={display:"flex",gap:8,alignItems:"center",padding:"2px 0"},$c={fontSize:11,color:"#475569",background:"#fff",padding:"2px 6px",borderRadius:4,minWidth:120},Ec={fontSize:11,color:"#0f172a",fontFamily:"monospace",wordBreak:"break-all"},E0={display:"inline-flex",alignItems:"center",padding:"2px 8px",fontSize:11,color:"#1d4ed8",background:"#eff6ff",border:"1px solid #bfdbfe",borderRadius:6},I0={margin:"6px 0 0",fontSize:12,color:"#64748b"},A0={margin:"0 0 8px",fontSize:12,color:"#b45309",background:"#fffbeb",border:"1px solid #fcd34d",borderRadius:6,padding:"6px 8px"},T0={padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,width:"100%",minWidth:0},D0={display:"flex",gap:6,alignItems:"flex-start",border:"1px solid #e2e8f0",borderRadius:8,padding:8},N0={padding:"4px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#64748b",cursor:"pointer"},Ic={padding:"5px 10px",fontSize:12,border:"1px dashed #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#475569",cursor:"pointer"},z0={margin:"8px 0 0",fontSize:11,color:"#94a3b8"},Ac=new Set(["core_setting","setting","query","route"]);function M0({vars:e,onChange:t,varCandidates:n,gatingMet:o=!1,candidates:r,t:s,testidPrefix:a="g7le-seo-vars"}){const l=u.useMemo(()=>e&&typeof e=="object"?{...e}:{},[e]),d=u.useMemo(()=>Array.isArray(n)?n:[],[n]),c=u.useMemo(()=>o?d.filter(_=>Ac.has(_.source)):[],[d,o]),p=u.useMemo(()=>o?d.filter(_=>_.source==="data"):[],[d,o]);u.useMemo(()=>new Set(c.map(_=>_.name)),[c]);const f=u.useMemo(()=>new Set(p.map(_=>_.name)),[p]),y=u.useMemo(()=>{const _=new Set;for(const C of d)Ac.has(C.source)&&_.add(C.name);return _},[d]),g=u.useMemo(()=>Object.keys(l).filter(_=>!f.has(_)),[l,f]),m=u.useCallback((_,C)=>{const E={...l};C===void 0?delete E[_]:E[_]=C,t(E)},[l,t]),[h,v]=u.useState(""),b=h.trim()!==""&&y.has(h.trim()),x=h.trim()!==""&&Object.prototype.hasOwnProperty.call(l,h.trim()),k=h.trim()!==""&&!b&&!x&&!f.has(h.trim()),w=u.useCallback(()=>{const _=h.trim();_===""||y.has(_)||Object.prototype.hasOwnProperty.call(l,_)||(t({...l,[_]:""}),v(""))},[h,y,l,t]);return i.jsxs("div",{"data-testid":a,style:{display:"flex",flexDirection:"column",gap:10},children:[i.jsx("span",{"data-testid":"g7le-seo-var-list",hidden:!0,"data-gating":o?"1":"0"}),!o&&i.jsxs("div",{"data-testid":`${a}-precondition`,style:L0,children:["⚠ ",s("layout_editor.page_settings.seo.vars_precondition")]}),c.length>0&&i.jsxs("div",{"data-testid":`${a}-auto-group`,style:ws,children:[i.jsx("div",{style:ks,children:s("layout_editor.page_settings.seo.vars_auto_group")}),c.map(_=>i.jsxs("div",{"data-testid":`g7le-seo-var-auto-${_.name}`,style:P0,children:[i.jsx("span",{style:O0,children:"🔒"}),i.jsx("code",{style:Ss,children:_.name}),i.jsxs("span",{style:B0,children:["〔",s("layout_editor.page_settings.seo.vars_auto"),"·",_.source,"〕"]}),_.owner?.name?i.jsx("span",{style:W0,children:_.owner.name}):null]},_.name))]}),p.length>0&&i.jsxs("div",{"data-testid":`${a}-data-group`,style:ws,children:[i.jsx("div",{style:ks,children:s("layout_editor.page_settings.seo.vars_data_group")}),p.map(_=>{const C=l[_.name]??"",E=C.trim()==="";return i.jsxs("div",{"data-testid":`g7le-seo-var-data-${_.name}`,style:Tc,children:[i.jsxs("div",{style:{display:"flex",alignItems:"center",gap:6,marginBottom:4},children:[i.jsx("code",{style:Ss,children:_.name}),_.required?i.jsx("span",{"data-testid":`g7le-seo-var-required-${_.name}`,style:j0,children:"*"}):null]}),i.jsx(Ve,{value:C,onChange:S=>m(_.name,S??""),t:s,candidates:r,testidPrefix:`g7le-seo-var-data-field-${_.name}`}),_.required&&E?i.jsxs("p",{"data-testid":`g7le-seo-var-empty-warn-${_.name}`,style:Cs,children:["⚠ ",s("layout_editor.page_settings.seo.vars_required_empty")]}):null]},_.name)})]}),i.jsxs("div",{"data-testid":`${a}-custom-group`,style:ws,children:[i.jsx("div",{style:ks,children:s("layout_editor.page_settings.seo.vars_custom_group")}),g.map(_=>i.jsxs("div",{"data-testid":`g7le-seo-var-custom-${_}`,style:Tc,children:[i.jsxs("div",{style:{display:"flex",alignItems:"center",gap:6,marginBottom:4},children:[i.jsx("code",{style:Ss,children:_}),i.jsx("button",{type:"button","data-testid":`g7le-seo-var-custom-remove-${_}`,onClick:()=>m(_,void 0),title:s("layout_editor.page_settings.seo.remove"),"aria-label":s("layout_editor.page_settings.seo.remove"),style:{marginLeft:"auto",padding:"2px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#64748b",cursor:"pointer"},children:"✕"})]}),i.jsx(Ve,{value:l[_]??"",onChange:C=>m(_,C??""),t:s,candidates:r,testidPrefix:`g7le-seo-var-custom-field-${_}`})]},_)),i.jsxs("div",{"data-testid":"g7le-seo-var-add",style:{display:"flex",flexDirection:"column",gap:4,marginTop:6},children:[i.jsxs("div",{style:{display:"flex",gap:6},children:[i.jsx("input",{type:"text","data-testid":"g7le-seo-var-add-name",value:h,placeholder:s("layout_editor.page_settings.seo.vars_add_name"),onChange:_=>v(_.target.value),style:{flex:1,padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,minWidth:0}}),i.jsxs("button",{type:"button","data-testid":"g7le-seo-var-add-confirm",onClick:w,disabled:h.trim()===""||b||x,style:H0,children:["+ ",s("layout_editor.page_settings.seo.vars_add")]})]}),b?i.jsxs("p",{"data-testid":"g7le-seo-var-add-reserved-warn",style:Cs,children:["⚠ ",s("layout_editor.page_settings.seo.vars_name_reserved",{name:h.trim()})]}):null,x&&!b?i.jsxs("p",{"data-testid":"g7le-seo-var-add-duplicate-warn",style:Cs,children:["⚠ ",s("layout_editor.page_settings.seo.vars_name_duplicate",{name:h.trim()})]}):null,k?i.jsxs("p",{"data-testid":"g7le-seo-var-add-not-provided-hint",style:{margin:0,fontSize:11,color:"#94a3b8"},children:["ⓘ ",s("layout_editor.page_settings.seo.vars_name_not_provided",{name:h.trim()})]}):null]})]}),i.jsxs("p",{style:{margin:0,fontSize:11,color:"#94a3b8"},children:["ⓘ ",s("layout_editor.page_settings.seo.vars_value_hint")]})]})}const L0={fontSize:12,color:"#b45309",background:"#fffbeb",border:"1px solid #fcd34d",borderRadius:6,padding:"6px 8px"},ws={border:"1px solid #e2e8f0",borderRadius:8,padding:10,display:"flex",flexDirection:"column",gap:6},ks={fontSize:12,fontWeight:600,color:"#475569"},P0={display:"flex",alignItems:"center",gap:6,padding:"2px 0"},Tc={display:"flex",flexDirection:"column",border:"1px solid #f1f5f9",borderRadius:6,padding:8},O0={fontSize:13},Ss={fontSize:12,color:"#0f172a",background:"#f1f5f9",padding:"2px 6px",borderRadius:4},B0={fontSize:11,color:"#64748b"},W0={fontSize:11,color:"#94a3b8"},j0={color:"#dc2626",fontWeight:700},Cs={margin:"4px 0 0",fontSize:11,color:"#b45309"},H0={padding:"5px 10px",fontSize:12,border:"1px dashed #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#475569",cursor:"pointer",whiteSpace:"nowrap"};function F0({templateIdentifier:e,layout:t,url:n,locale:o,seedContext:r,routeParams:s,settingsSignature:a,t:l,debounceMs:d=400,fetchImpl:c,testidPrefix:p="g7le-seo-bot-preview"}){const[f,y]=u.useState(!0),[g,m]=u.useState(null),[h,v]=u.useState(!1),[b,x]=u.useState(null),k=u.useRef(null),w=u.useRef({templateIdentifier:e,layout:t,url:n,locale:o,seedContext:r,routeParams:s});w.current={templateIdentifier:e,layout:t,url:n,locale:o,seedContext:r,routeParams:s};const _=u.useCallback(async()=>{const T=c??(typeof fetch<"u"?fetch:void 0);if(!T)return;const{templateIdentifier:$,layout:A,url:z,locale:D,seedContext:O,routeParams:M}=w.current;v(!0),x(null);try{const B=`/api/admin/templates/${encodeURIComponent($)}/editor/seo-bot-preview`,N=await T(B,{method:"POST",credentials:"same-origin",headers:je({"Content-Type":"application/json"}),body:JSON.stringify({layout:A,url:z,locale:D,seed_context:O??{},route_params:M??{}})}),H=await N.json().catch(()=>null);if(!N.ok){x(H?.message??`HTTP ${N.status}`);return}const L=H?.data??null;m(L)}catch(B){x(B instanceof Error?B.message:String(B))}finally{v(!1)}},[c]);u.useEffect(()=>{if(f)return k.current&&clearTimeout(k.current),k.current=setTimeout(()=>{_()},d),()=>{k.current&&clearTimeout(k.current)}},[a,f,d,_]);const C=u.useCallback(()=>{k.current&&clearTimeout(k.current),_()},[_]),E=g?.html??null,S=g?.enabled??!0;return i.jsxs("div",{"data-testid":p,style:K0,children:[i.jsxs("div",{style:V0,children:[i.jsxs("button",{type:"button","data-testid":`${p}-toggle`,onClick:()=>y(T=>!T),"aria-expanded":f,style:U0,children:[f?"▾":"▸"," ",l("layout_editor.page_settings.seo.bot_preview_title")]}),f?i.jsxs("button",{type:"button","data-testid":`${p}-refresh`,onClick:C,disabled:h,style:q0,children:["⟳ ",l("layout_editor.page_settings.seo.bot_preview_refresh")]}):null]}),f&&i.jsxs("div",{style:{marginTop:8},children:[g&&!S?i.jsxs("p",{"data-testid":`${p}-disabled`,style:G0,children:["ⓘ ",l("layout_editor.page_settings.seo.bot_preview_disabled")]}):i.jsx("div",{"data-testid":`${p}-body`,style:Y0,children:h&&!E?i.jsx("p",{style:$s,children:l("layout_editor.translation.loading")}):b?i.jsx("p",{"data-testid":`${p}-error`,style:{...$s,color:"#dc2626"},children:b}):E==null?i.jsx("p",{style:$s,children:l("layout_editor.page_settings.seo.bot_preview_empty")}):i.jsx("pre",{"data-testid":`${p}-code`,style:X0,children:E})}),i.jsxs("p",{"data-testid":`${p}-sample-note`,style:J0,children:["ⓘ ",l("layout_editor.page_settings.seo.bot_preview_sample_note")]})]})]})}const K0={borderTop:"1px solid #e2e8f0",paddingTop:10,marginTop:12},V0={display:"flex",alignItems:"center",gap:8},U0={background:"none",border:"none",cursor:"pointer",fontSize:13,fontWeight:600,color:"#0f172a",padding:0},q0={marginLeft:"auto",padding:"4px 10px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},G0={margin:0,fontSize:12,color:"#64748b",background:"#f8fafc",border:"1px solid #e2e8f0",borderRadius:6,padding:"8px 10px"},Y0={border:"1px solid #e2e8f0",borderRadius:8,background:"#0b1020",padding:0,minHeight:80,overflow:"hidden"},X0={margin:0,padding:12,fontSize:11,lineHeight:1.5,color:"#d1fae5",fontFamily:"monospace",whiteSpace:"pre-wrap",wordBreak:"break-word",maxHeight:360,overflow:"auto"},$s={margin:0,padding:12,fontSize:12,color:"#94a3b8"},J0={margin:"8px 0 0",fontSize:11,color:"#94a3b8"};function Dc({option:e,testIdPrefix:t}){const{id:n,friendly:o,source:r}=e;return i.jsxs("span",{style:Z0,children:[i.jsx("span",{"data-testid":t?`${t}-title`:void 0,style:Q0,children:o??n}),o?i.jsx("span",{"data-testid":t?`${t}-id`:void 0,style:R0,children:n}):null,r?i.jsx("span",{"data-testid":t?`${t}-source`:void 0,style:ew,children:r}):null]})}const Z0={display:"inline-flex",alignItems:"baseline",gap:4,flexWrap:"wrap",minWidth:0},Q0={fontSize:12,color:"#0f172a"},R0={fontSize:10,color:"#94a3b8",fontFamily:"ui-monospace, monospace"},ew={fontSize:9,fontWeight:600,color:"#7c3aed",background:"#f5f3ff",border:"1px solid #ddd6fe",borderRadius:4,padding:"0 5px",whiteSpace:"nowrap"},tw=["always","hourly","daily","weekly","monthly","yearly","never"];function nw({getValue:e,patch:t,t:n,templateIdentifier:o,candidates:r,availableExtensions:s=[],dataSourceOptions:a=[],previewUrl:l="/",previewLocale:d="ko",seedContext:c,candidatesFetcher:p,ogPreviewFetcher:f,botFetchImpl:y,testidPrefix:g="g7le-seo"}){const m=e("meta",{}).seo,h=m&&typeof m=="object"?m:{},v=u.useCallback((K,ne)=>{const me={...e("meta",{})},Ce={...me.seo??{}};ne==null||ne===""?delete Ce[K]:Ce[K]=ne,me.seo=Ce,t("meta",me)},[e,t]),b=h.enabled===!0,x=typeof h.page_type=="string"?h.page_type:"",k=Array.isArray(h.extensions)?h.extensions:[],w=Array.isArray(h.data_sources)?h.data_sources:[],_=k.length>0&&x!=="",[C,E]=u.useState(null),[S,T]=u.useState(null),$=u.useCallback(async(K,ne)=>{if(p)return p(K,ne);if(typeof fetch>"u")return null;const me=new URLSearchParams;K&&me.set("page_type",K),Array.isArray(ne)&&ne.length>0&&me.set("extensions",JSON.stringify(ne.map(qe=>({type:qe.type,id:qe.id}))));const Ce=me.toString()?`?${me.toString()}`:"",ze=await fetch(en(`/api/admin/templates/${encodeURIComponent(o)}/editor/seo-candidates`,"json",null,Ce.replace(/^[?&]+/,"")),{credentials:"same-origin",headers:je()}),Se=await ze.json().catch(()=>null);return ze.ok?Se?.data??null:null},[p,o]),A=u.useMemo(()=>{const me=(e("__editor",{})??{}).original?.meta?.seo;return me&&typeof me=="object"?me:void 0},[e]),z=u.useRef(A);z.current=A;const D=u.useCallback(async K=>{if(f)return f(K);if(typeof fetch>"u")return null;const ne=await fetch(`/api/admin/templates/${encodeURIComponent(o)}/editor/seo-og-preview`,{method:"POST",credentials:"same-origin",headers:je({"Content-Type":"application/json"}),body:JSON.stringify({seo:K,own_seo:z.current??null,seed_context:c??{},route_params:{}})}),me=await ne.json().catch(()=>null);return ne.ok?me?.data??null:null},[f,o,c]),O=u.useMemo(()=>JSON.stringify(k),[k]),M=u.useRef(h);M.current=h,u.useEffect(()=>{let K=!1;return(async()=>{const ne=await $(x,k);K||E(ne)})(),()=>{K=!0}},[x,O]);const B=u.useMemo(()=>JSON.stringify({og:h.og??null,twitter:h.twitter??null,structured_data:h.structured_data??null}),[h.og,h.twitter,h.structured_data]),N=u.useRef(!1);u.useEffect(()=>{let K=!1;const ne=()=>{(async()=>{const Ce=await D(M.current);K||T(Ce)})()};if(!N.current)return N.current=!0,ne(),()=>{K=!0};const me=setTimeout(ne,180);return()=>{K=!0,clearTimeout(me)}},[x,O,B]);const H=C?.page_types??[],L=C?.toggle_settings??[],I=C?.vars??[],P=u.useMemo(()=>JSON.stringify(h),[h]),F=u.useMemo(()=>e("meta")?{meta:e("meta"),components:e("components",[])}:{meta:{},components:[]},[e]),G=u.useCallback(K=>{k.some(ne=>ne.type===K.type&&ne.id===K.id)||v("extensions",[...k,{type:K.type,id:K.id}])},[k,v]),V=u.useCallback(K=>{const ne=k.filter(me=>!(me.type===K.type&&me.id===K.id));v("extensions",ne.length>0?ne:void 0)},[k,v]),[Y,Z]=u.useState(!1),[se,re]=u.useState(""),U=u.useMemo(()=>{const K=se.trim().toLowerCase();return K?L.filter(ne=>`${ne.ref} ${ne.label??""}`.toLowerCase().includes(K)):L},[L,se]),ee=typeof h.toggle_setting=="string"?h.toggle_setting:"",fe=u.useCallback(K=>{const me=w.includes(K)?w.filter(Ce=>Ce!==K):[...w,K];v("data_sources",me.length>0?me:void 0)},[w,v]);return i.jsxs("div",{"data-testid":g,style:{display:"flex",flexDirection:"column",gap:14},children:[i.jsxs("div",{style:Bt,children:[i.jsx(Dr,{checked:b,onChange:K=>v("enabled",K),testid:`${g}-enabled`,label:n("layout_editor.page_settings.seo.enabled")}),b?null:i.jsxs("p",{"data-testid":`${g}-disabled-note`,style:no,children:["ⓘ ",n("layout_editor.page_settings.seo.disabled_note")]})]}),i.jsx(Nr,{disabled:!b,testid:`${g}-body`,style:{display:"flex",flexDirection:"column",gap:14},children:i.jsxs(i.Fragment,{children:[i.jsxs("div",{style:Bt,children:[i.jsx("label",{style:qt,children:n("layout_editor.page_settings.seo.page_type")}),H.length>0?i.jsxs("select",{"data-testid":`${g}-page-type`,value:x,onChange:K=>v("page_type",K.target.value),style:Lr,children:[i.jsx("option",{value:"",children:n("layout_editor.page_settings.seo.page_type_none")}),H.map(K=>i.jsxs("option",{value:K.value,children:[K.label??K.value,K.owner?.name?` (${K.owner.name})`:""]},K.value))]}):i.jsxs(i.Fragment,{children:[i.jsx("input",{type:"text","data-testid":`${g}-page-type-free`,value:x,placeholder:n("layout_editor.page_settings.seo.page_type_free_placeholder"),onChange:K=>v("page_type",K.target.value),style:Lr}),i.jsxs("p",{"data-testid":`${g}-page-type-free-note`,style:no,children:["ⓘ ",n("layout_editor.page_settings.seo.page_type_free_note")]})]})]}),i.jsxs("div",{style:Bt,children:[i.jsxs("label",{style:qt,children:["★ ",n("layout_editor.page_settings.seo.extensions")]}),i.jsxs("div",{"data-testid":`${g}-extensions`,style:{display:"flex",flexWrap:"wrap",gap:6},children:[k.map(K=>i.jsxs("span",{"data-testid":`${g}-extension-${K.type}-${K.id}`,style:Nc,children:[K.name??`${K.type}:${K.id}`,i.jsx("button",{type:"button","data-testid":`${g}-extension-remove-${K.type}-${K.id}`,onClick:()=>V(K),style:zc,children:"✕"})]},`${K.type}:${K.id}`)),i.jsxs("button",{type:"button","data-testid":`${g}-extension-add`,onClick:()=>Z(K=>!K),style:iw,children:["+ ",n("layout_editor.page_settings.seo.add_extension")]})]}),Y&&i.jsx("div",{"data-testid":`${g}-extension-picker`,style:Mc,children:s.length===0?i.jsx("p",{style:no,children:n("layout_editor.page_settings.seo.no_extension_candidate")}):s.map(K=>i.jsx("button",{type:"button","data-testid":`${g}-extension-option-${K.type}-${K.id}`,onClick:()=>{G(K),Z(!1)},style:Lc,children:K.name??`${K.type}:${K.id}`},`${K.type}:${K.id}`))})]}),!_&&i.jsxs("div",{"data-testid":`${g}-defaults-precondition`,style:rw,children:["⚠ ",n("layout_editor.page_settings.seo.defaults_precondition"),i.jsxs("div",{"data-testid":`${g}-defaults-missing`,style:{fontSize:11,marginTop:4},children:[k.length===0?n("layout_editor.page_settings.seo.missing_extensions"):"",x===""?` ${n("layout_editor.page_settings.seo.missing_page_type")}`:""]})]}),i.jsxs("div",{style:Bt,children:[i.jsx("label",{style:qt,children:n("layout_editor.page_settings.seo.toggle_setting")}),i.jsxs("div",{"data-testid":`${g}-toggle-setting`,children:[ee?i.jsxs("div",{style:{display:"flex",gap:6,alignItems:"center",marginBottom:4},children:[i.jsx("code",{style:Nc,children:ee}),i.jsx("button",{type:"button","data-testid":`${g}-toggle-setting-clear`,onClick:()=>v("toggle_setting",void 0),style:zc,children:"✕"})]}):null,i.jsx("input",{type:"text","data-testid":`${g}-toggle-setting-search`,value:se,placeholder:n("layout_editor.page_settings.seo.toggle_setting_search"),onChange:K=>re(K.target.value),style:Lr}),se.trim()!==""&&i.jsx("div",{"data-testid":`${g}-toggle-setting-results`,style:Mc,children:U.length===0?i.jsx("p",{style:no,children:n("layout_editor.inline_binding.no_results")}):U.map(K=>i.jsx("button",{type:"button","data-testid":`${g}-toggle-setting-option-${K.ref}`,onClick:()=>{v("toggle_setting",K.ref),re("")},style:Lc,children:K.label??K.ref},K.ref))})]})]}),i.jsxs("div",{style:Bt,children:[i.jsx("label",{style:qt,children:n("layout_editor.page_settings.seo.data_sources")}),i.jsx("p",{style:no,children:n("layout_editor.page_settings.seo.data_sources_hint")}),i.jsx("div",{"data-testid":`${g}-data-sources`,style:{display:"flex",flexDirection:"column",gap:6},children:a.length===0?i.jsx("span",{style:no,children:n("layout_editor.page_settings.seo.data_sources_empty")}):a.map(K=>{const ne=w.includes(K.id);return i.jsxs("label",{"data-testid":`${g}-data-source-${K.id}`,style:ow(ne),children:[i.jsx("input",{type:"checkbox","data-testid":`${g}-data-source-check-${K.id}`,checked:ne,onChange:()=>fe(K.id)}),i.jsx(Dc,{option:K,testIdPrefix:`${g}-data-source-${K.id}`})]},K.id)})})]}),i.jsxs("div",{style:Bt,children:[i.jsx("label",{style:qt,children:n("layout_editor.page_settings.seo.priority")}),i.jsx("input",{type:"range","data-testid":`${g}-priority`,min:0,max:1,step:.1,value:typeof h.priority=="number"?h.priority:.5,onChange:K=>v("priority",Number(K.target.value))}),i.jsx("span",{"data-testid":`${g}-priority-value`,style:{fontSize:12,color:"#475569"},children:typeof h.priority=="number"?h.priority:.5})]}),i.jsxs("div",{style:Bt,children:[i.jsx("label",{style:qt,children:n("layout_editor.page_settings.seo.changefreq")}),i.jsxs("select",{"data-testid":`${g}-changefreq`,value:typeof h.changefreq=="string"?h.changefreq:"",onChange:K=>v("changefreq",K.target.value),style:Lr,children:[i.jsx("option",{value:"",children:n("layout_editor.page_settings.seo.use_default")}),tw.map(K=>i.jsx("option",{value:K,children:K},K))]})]}),i.jsxs("div",{style:Bt,children:[i.jsx("label",{style:qt,children:n("layout_editor.page_settings.seo.title")}),i.jsx(vt,{value:typeof h.title=="string"?h.title:"",onChange:K=>v("title",K??""),t:n,candidates:r,testidPrefix:`${g}-title`,enableExpressionTree:!0,expressionTreeCollapsible:!0})]}),i.jsxs("div",{style:Bt,children:[i.jsx("label",{style:qt,children:n("layout_editor.page_settings.seo.description")}),i.jsx(vt,{value:typeof h.description=="string"?h.description:"",onChange:K=>v("description",K??""),t:n,candidates:r,testidPrefix:`${g}-description`,enableExpressionTree:!0,expressionTreeCollapsible:!0})]}),i.jsx(l0,{og:h.og,twitter:h.twitter,onChangeOg:K=>v("og",Object.keys(K).length>0?K:void 0),onChangeTwitter:K=>v("twitter",Object.keys(K).length>0?K:void 0),ogPreview:S?.og,twitterPreview:S?.twitter,defaultsAvailable:S?.defaultsAvailable??!1,candidates:r,t:n}),i.jsxs("div",{style:Bt,children:[i.jsx("label",{style:qt,children:n("layout_editor.page_settings.seo.structured_data")}),i.jsx(w0,{value:h.structured_data,onChange:K=>v("structured_data",K??void 0),autoBlock:S?.structured?.autoBlock,autoMeta:S?.structured?.autoMeta,lockedByFilter:S?.structured?.lockedByFilter,filteredBlock:S?.structured?.filteredBlock,pageType:x,candidates:r,t:n})]}),i.jsxs("div",{style:Bt,children:[i.jsx("label",{style:qt,children:n("layout_editor.page_settings.seo.vars")}),i.jsx(M0,{vars:h.vars,onChange:K=>v("vars",Object.keys(K).length>0?K:void 0),varCandidates:I,gatingMet:_,candidates:r,t:n})]}),i.jsx(F0,{templateIdentifier:o,layout:F,url:l,locale:d,seedContext:c,settingsSignature:P,t:n,fetchImpl:y})]})})]})}const Bt={display:"flex",flexDirection:"column",gap:4},qt={fontSize:12,fontWeight:600,color:"#334155"},Lr={padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,width:"100%",minWidth:0,boxSizing:"border-box"},no={margin:"4px 0 0",fontSize:11,color:"#94a3b8"},ow=e=>({display:"flex",alignItems:"center",gap:8,padding:"6px 10px",fontSize:12,border:`1px solid ${e?"#3b82f6":"#e2e8f0"}`,borderRadius:8,background:e?"#eff6ff":"#fff",cursor:"pointer",minWidth:0}),rw={fontSize:12,color:"#b45309",background:"#fffbeb",border:"1px solid #fcd34d",borderRadius:6,padding:"8px 10px"},Nc={display:"inline-flex",alignItems:"center",gap:4,fontSize:11,padding:"3px 8px",borderRadius:999,background:"#eef2ff",color:"#3730a3"},zc={border:"none",background:"transparent",cursor:"pointer",color:"#6366f1",fontSize:11,padding:0},iw={fontSize:11,padding:"3px 10px",borderRadius:999,border:"1px dashed #cbd5e1",background:"#f8fafc",color:"#475569",cursor:"pointer"},Mc={marginTop:4,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",padding:6,display:"flex",flexDirection:"column",gap:2},Lc={textAlign:"left",border:"none",background:"transparent",cursor:"pointer",padding:"5px 8px",borderRadius:4,fontSize:12,color:"#0f172a"},sw={setState:"state",loadFromLocalStorage:"state",saveToLocalStorage:"state",navigate:"nav",openWindow:"nav",navigateBack:"nav",navigateForward:"nav",replaceUrl:"nav",toast:"notify",openModal:"notify",closeModal:"notify",showAlert:"notify",setError:"notify",refetchDataSource:"data",appendDataSource:"data",updateDataSource:"data",apiCall:"data",scrollIntoView:"etc",login:"etc",logout:"etc",setLocale:"locale",emitEvent:"etc",conditions:"flow",sequence:"flow",parallel:"flow",switch:"flow",suppress:"flow",showErrorPage:"error"},aw={state:"layout_editor.action.group.state",nav:"layout_editor.action.group.nav",notify:"layout_editor.action.group.notify",data:"layout_editor.action.group.data",locale:"layout_editor.action.group.locale",etc:"layout_editor.action.group.etc",flow:"layout_editor.action.group.flow",error:"layout_editor.action.group.error",extension:"layout_editor.action.group.extension"},lw={init:["state","data","notify","nav","locale","etc","flow","error","extension"],component:["nav","notify","data","state","locale","etc","flow","error","extension"]};function dw(e,t){const n=e?.[t];if(n&&typeof n=="object"){const o=n.__source;if(o&&typeof o=="object")return o}return null}function cw(e,t){return!e||e.kind==="core"?t("layout_editor.action.source_core"):e.id??t(`layout_editor.action.source_${e.kind}`)}function Pc(e,t,n){return typeof e!="string"?n:e.startsWith("$t:")?t(e.slice(3)):e}function uw({recipes:e,t,onAdd:n,context:o="init",testIdPrefix:r="g7le-action-add"}){const[s,a]=u.useState(!1),[l,d]=u.useState(""),c=u.useMemo(()=>wo(e),[e]),p=u.useMemo(()=>{const f=lw[o],y=new Map;for(const m of c){const h=dw(e,m.id),b=h!=null&&h.kind!=="core"?"extension":sw[m.id]??"etc",x=y.get(b)??[];x.push({recipe:m,source:h}),y.set(b,x)}const g=l.trim().toLowerCase();return f.map(m=>{let h=y.get(m)??[];return g&&(h=h.filter(({recipe:v})=>Pc(v.label,t,v.id).toLowerCase().includes(g)||v.id.toLowerCase().includes(g)||v.build.handler.toLowerCase().includes(g))),{groupKey:m,items:h}}).filter(m=>m.items.length>0)},[c,e,o,l,t]);return c.length===0?i.jsx("div",{"data-testid":`${r}-empty`,style:xw,children:t("layout_editor.action.no_recipes")}):i.jsxs("div",{className:r,"data-testid":r,style:{minWidth:0},children:[i.jsxs("button",{type:"button","data-testid":`${r}-toggle`,onClick:()=>a(f=>!f),style:pw,children:[t("layout_editor.action.add_action")," ▾"]}),s?i.jsxs("div",{"data-testid":`${r}-list`,style:fw,children:[i.jsx("input",{type:"text","data-testid":`${r}-search`,value:l,placeholder:t("layout_editor.action.add_search"),onChange:f=>d(f.target.value),style:gw}),p.map(({groupKey:f,items:y})=>i.jsxs("div",{"data-testid":`${r}-group-${f}`,children:[i.jsxs("div",{style:hw,children:["━━ ",t(aw[f]??f)," ━━"]}),y.map(({recipe:g,source:m})=>i.jsxs("button",{type:"button","data-testid":`g7le-init-action-spec-${g.id}`,onClick:()=>{n(xr(g,{})),a(!1),d("")},style:yw,children:[i.jsx("span",{style:bw,children:Pc(g.label,t,g.id)}),i.jsxs("span",{"data-testid":`g7le-init-action-spec-source-${g.id}`,style:mw,children:["〔",cw(m,t),"〕"]}),i.jsx("code",{style:vw,children:g.build.handler})]},g.id))]},f)),p.length===0?i.jsx("div",{"data-testid":`${r}-no-match`,style:_w,children:t("layout_editor.action.add_no_match")}):null]}):null]})}const pw={padding:"6px 12px",fontSize:12,border:"1px dashed #94a3b8",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},fw={marginTop:6,border:"1px solid #cbd5e1",borderRadius:8,background:"#fff",maxHeight:320,overflowY:"auto",padding:8},gw={width:"100%",padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,marginBottom:6,boxSizing:"border-box"},hw={fontSize:11,fontWeight:700,color:"#94a3b8",padding:"6px 4px 2px"},yw={display:"flex",alignItems:"center",gap:6,width:"100%",textAlign:"left",padding:"6px 8px",fontSize:12,border:"none",background:"transparent",cursor:"pointer",borderRadius:6},bw={flex:1,minWidth:0,color:"#0f172a"},mw={fontSize:10,color:"#64748b",whiteSpace:"nowrap"},vw={fontSize:10,color:"#cbd5e1",fontFamily:"monospace",whiteSpace:"nowrap"},_w={fontSize:12,color:"#94a3b8",padding:"8px 4px"},xw={fontSize:12,color:"#94a3b8",padding:"8px 0"};function ww(e,t){return t.startsWith("$t:")?e(t.slice(3)):t}function kw({action:e,recipe:t,values:n={},t:o,missingRequired:r=!1,showCodeButton:s=!0,testIdPrefix:a="g7le-action-preview"}){const[l,d]=u.useState(!1),c=t?Wd(t,n,p=>ww(o,p)):typeof e.handler=="string"?e.handler:o("layout_editor.action.advanced_action");return i.jsxs("div",{className:a,"data-testid":a,style:Sw,children:[r?i.jsxs("span",{"data-testid":`${a}-missing`,style:$w,children:["⚠ ",o("layout_editor.action.summary_missing_required")]}):i.jsx("span",{"data-testid":`${a}-summary`,style:Cw,children:c}),s?i.jsx("button",{type:"button","data-testid":`${a}-code-toggle`,onClick:()=>d(p=>!p),style:Ew,"aria-expanded":l,"aria-label":o("layout_editor.action.view_code"),children:""}):null,s&&l?i.jsx("pre",{"data-testid":`${a}-code`,style:Iw,children:JSON.stringify(e,null,2)}):null]})}const Sw={display:"flex",flexWrap:"wrap",alignItems:"center",gap:6,minWidth:0},Cw={flex:1,minWidth:0,fontSize:12,color:"#475569"},$w={flex:1,minWidth:0,fontSize:12,color:"#b45309"},Ew={border:"1px solid #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#475569",cursor:"pointer",fontSize:11,padding:"2px 6px",whiteSpace:"nowrap"},Iw={flexBasis:"100%",margin:"6px 0 0",padding:8,background:"#0f172a",color:"#e2e8f0",borderRadius:6,fontSize:11,overflow:"auto"},Oc="__g7le_custom__";function Pr({value:e,onChange:t,candidates:n,placeholder:o,testid:r,t:s,bindingCandidates:a}){const l=e==null?"":String(e),d=Array.isArray(n)&&n.length>0,c=d&&n.some(y=>y.value===l),[p,f]=u.useState(!d||l!==""&&!c);return u.useEffect(()=>{l!==""&&d&&!c&&f(!0)},[l,d,c]),d&&!p?i.jsxs("div",{style:Hc,children:[i.jsxs("select",{className:"g7le-widget g7le-widget--picker","data-testid":r,value:c?l:"",onChange:y=>{const g=y.target.value;if(g===Oc){f(!0);return}t(g===""?void 0:g)},style:{...Wc,flex:1,minWidth:0},children:[i.jsx("option",{value:"",children:s("layout_editor.control.default")}),n.map(y=>i.jsx("option",{value:y.value,children:y.label},y.value)),i.jsx("option",{value:Oc,children:s("layout_editor.recipe.custom_input")})]}),i.jsx("button",{type:"button","data-testid":`${r}-to-custom`,title:s("layout_editor.recipe.custom_input"),"aria-label":s("layout_editor.recipe.custom_input"),onClick:()=>f(!0),style:Fc,children:"✎"})]}):i.jsxs("div",{style:Hc,children:[i.jsx("div",{style:{flex:1,minWidth:0},children:i.jsx(Ve,{value:l,onChange:y=>t(y===""?void 0:y),t:s,candidates:a,placeholder:o,testidPrefix:`${r}-input`})}),d&&i.jsx("button",{type:"button","data-testid":`${r}-pick`,title:s("layout_editor.recipe.pick_from_list"),onClick:()=>{f(!1),t(void 0)},style:Fc,children:"≡"})]})}function Aw({value:e,onChange:t,candidates:n,t:o,bindingCandidates:r}){return i.jsx(Pr,{value:e,onChange:t,candidates:n,placeholder:o("layout_editor.recipe.page_placeholder"),testid:"g7le-widget-page-picker",t:o,bindingCandidates:r})}function Bc({value:e,onChange:t,candidates:n,t:o,bindingCandidates:r}){return i.jsx(Pr,{value:e,onChange:t,candidates:n,placeholder:o("layout_editor.recipe.datasource_placeholder"),testid:"g7le-widget-datasource-picker",t:o,bindingCandidates:r})}function Es({value:e,onChange:t,candidates:n,t:o,bindingCandidates:r}){return i.jsx(Pr,{value:e,onChange:t,candidates:n,placeholder:o("layout_editor.recipe.state_key_placeholder"),testid:"g7le-widget-state-key-picker",t:o,bindingCandidates:r})}function Tw({value:e,onChange:t,candidates:n,t:o,bindingCandidates:r}){return i.jsx(Pr,{value:e,onChange:t,candidates:n,placeholder:o("layout_editor.recipe.modal_placeholder"),testid:"g7le-widget-modal-picker",t:o,bindingCandidates:r})}function Dw({value:e,onChange:t,t:n,bindingCandidates:o}){const r=typeof e=="string"?e:e==null?"":String(e);return i.jsx("div",{className:"g7le-widget g7le-widget--i18n-text","data-testid":"g7le-widget-i18n-text",style:{flex:1,minWidth:100,width:"100%"},children:i.jsx(vt,{value:r,onChange:s=>t(s===""?void 0:s),t:n,placeholder:n("layout_editor.recipe.i18n_text_placeholder"),testidPrefix:"g7le-widget-i18n-text-field",candidates:o,enableExpressionTree:!0,expressionTreeCollapsible:!0})})}function Nw({value:e,onChange:t,candidates:n,t:o}){const r=e==null?"":String(e),s=r.split(",").map(d=>d.trim()).filter(Boolean),a=Array.isArray(n)&&n.length>0,l=d=>{if(!d)return;const c=[...s,d];t(c.join(", "))};return i.jsxs("div",{className:"g7le-widget g7le-widget--binding-list","data-testid":"g7le-widget-binding-list",style:{display:"flex",flexDirection:"column",gap:4,width:"100%"},children:[i.jsx("input",{type:"text","data-testid":"g7le-widget-binding-list-input",value:r,placeholder:o("layout_editor.recipe.datasource_placeholder"),onChange:d=>t(d.target.value===""?void 0:d.target.value),style:jc}),a?i.jsxs("select",{className:"g7le-widget g7le-widget--picker","data-testid":"g7le-widget-binding-list-add",value:"",onChange:d=>{l(d.target.value),d.target.value=""},style:{...Wc,minWidth:0},children:[i.jsx("option",{value:"",children:o("layout_editor.recipe.pick_from_list")}),n.map(d=>i.jsx("option",{value:d.value,children:d.label},d.value))]}):null]})}function zw({value:e,onChange:t}){const n=e==null?"":String(e);return i.jsx("input",{type:"text",className:"g7le-widget g7le-widget--text","data-testid":"g7le-widget-text",value:n,onChange:o=>t(o.target.value===""?void 0:o.target.value),style:jc})}const Wc={padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",minWidth:140,width:"100%"},jc={flex:1,padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,minWidth:100,width:"100%"},Hc={display:"flex",gap:4,alignItems:"flex-start",width:"100%"},Fc={padding:"4px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#64748b",cursor:"pointer"};function Mw(e){if(e==null)return"null";if(Array.isArray(e))return"list";switch(typeof e){case"number":return"number";case"boolean":return"boolean";case"string":return"string";default:return"object"}}function Kc(e){switch(e){case"string":return"";case"number":return 0;case"boolean":return!1;case"null":return null;case"list":return[];case"object":return{};default:return null}}function Is(e){return/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(e)}function Lw(e,t,n){return t.includes(n)?"self":e.includes(n)?"inherited":"self"}function Pw(e){const t=e.state&&typeof e.state=="object"&&Object.keys(e.state).length>0,n=e.initLocal&&typeof e.initLocal=="object"?e.initLocal:void 0;if(!t)return{initLocal:n??{},migrated:!1};const o={...e.state};if(n)for(const[r,s]of Object.entries(n))o[r]=s;return{initLocal:o,migrated:!0}}const Vc=[{kind:"string",labelKey:"layout_editor.initstate.kind_string"},{kind:"number",labelKey:"layout_editor.initstate.kind_number"},{kind:"boolean",labelKey:"layout_editor.initstate.kind_boolean"},{kind:"null",labelKey:"layout_editor.initstate.kind_null"},{kind:"list",labelKey:"layout_editor.initstate.kind_list"},{kind:"object",labelKey:"layout_editor.initstate.kind_object"}];function Nn({value:e,onChange:t,t:n,path:o,scope:r,depth:s=0,renderStringLeaf:a}){const l=Mw(e),d=c=>{t(Kc(c))};return i.jsxs("div",{className:"g7le-initstate-value",style:{paddingLeft:s>0?12:0,minWidth:0},children:[i.jsxs("div",{style:Bw,children:[i.jsx("select",{"data-testid":`g7le-initstate-type-${o}`,value:l,onChange:c=>d(c.target.value),style:Uc,children:Vc.map(({kind:c,labelKey:p})=>i.jsx("option",{value:c,children:n(p)},c))}),l==="string"?a?i.jsx("div",{style:{flex:1,minWidth:0},children:a({value:String(e??""),onChange:c=>t(c),testidPrefix:`g7le-initstate-value-${o}`})}):i.jsx("input",{type:"text","data-testid":`g7le-initstate-value-${o}`,value:String(e??""),onChange:c=>t(c.target.value),style:qc}):l==="number"?i.jsx("input",{type:"number","data-testid":`g7le-initstate-value-${o}`,value:typeof e=="number"?e:"",onChange:c=>{const p=parseFloat(c.target.value);t(Number.isNaN(p)?0:p)},style:qc}):l==="boolean"?i.jsx(Dr,{checked:!!e,onChange:t,testid:`g7le-initstate-value-${o}`,label:n(e?"layout_editor.overlay.on":"layout_editor.overlay.off")}):l==="null"?i.jsx("span",{"data-testid":`g7le-initstate-value-${o}`,style:Ww,children:"null"}):null]}),l==="list"?i.jsxs("div",{style:Gc,children:[e.map((c,p)=>i.jsxs("div",{style:jw,children:[i.jsxs("span",{style:Hw,children:["[",p,"]"]}),i.jsx("div",{style:{flex:1,minWidth:0},children:i.jsx(Nn,{value:c,onChange:f=>{const y=e.slice();y[p]=f,t(y)},t:n,path:`${o}.${p}`,scope:r,depth:s+1,renderStringLeaf:a})}),i.jsx("button",{type:"button","data-testid":`g7le-initstate-item-remove-${o}.${p}`,onClick:()=>t(e.filter((f,y)=>y!==p)),style:Yc,children:"✕"})]},p)),i.jsxs("button",{type:"button","data-testid":`g7le-initstate-item-add-${o}`,onClick:()=>t([...e,""]),style:Xc,children:["+ ",n("layout_editor.initstate.add_item")]})]}):null,l==="object"?i.jsx(Ow,{value:e,onChange:t,t:n,path:o,scope:r,depth:s+1,renderStringLeaf:a}):null]})}function Ow({value:e,onChange:t,t:n,path:o,scope:r,depth:s,renderStringLeaf:a}){const[l,d]=u.useState(""),[c,p]=u.useState("string"),[f,y]=u.useState(null),g=Object.entries(e),m=()=>{const h=l.trim();if(!h){y(n("layout_editor.initstate.name_required"));return}if(!Is(h)){y(n("layout_editor.initstate.name_invalid"));return}if(h in e){y(n("layout_editor.initstate.name_duplicate"));return}t({...e,[h]:Kc(c)}),d(""),y(null)};return i.jsxs("div",{style:Gc,children:[g.map(([h,v])=>i.jsxs("div",{style:Fw,children:[i.jsx("code",{style:Kw,children:h}),i.jsx("div",{style:{flex:1,minWidth:0},children:i.jsx(Nn,{value:v,onChange:b=>t({...e,[h]:b}),t:n,path:`${o}.${h}`,scope:r,depth:s,renderStringLeaf:a})}),i.jsx("button",{type:"button","data-testid":`g7le-initstate-subremove-${o}.${h}`,onClick:()=>{const b={...e};delete b[h],t(b)},style:Yc,children:"✕"})]},h)),i.jsxs("div",{style:Vw,children:[i.jsx("input",{type:"text","data-testid":`g7le-initstate-subkey-input-${o}`,value:l,placeholder:n("layout_editor.initstate.subkey_name"),onChange:h=>{d(h.target.value),f&&y(null)},onKeyDown:h=>{h.key==="Enter"&&(h.preventDefault(),m())},"aria-invalid":f?!0:void 0,style:{...Uw,...f?qw:null}}),i.jsx("select",{"data-testid":`g7le-initstate-subkey-kind-${o}`,value:c,onChange:h=>p(h.target.value),"aria-label":n("layout_editor.initstate.value_kind"),style:Uc,children:Vc.map(({kind:h,labelKey:v})=>i.jsx("option",{value:h,children:n(v)},h))}),i.jsxs("button",{type:"button","data-testid":`g7le-initstate-subkey-add-${o}`,onClick:m,style:Xc,children:["+ ",n("layout_editor.initstate.add_subkey")]})]}),f?i.jsx("p",{"data-testid":`g7le-initstate-subkey-error-${o}`,style:Gw,children:f}):null]})}const Bw={display:"flex",gap:6,alignItems:"center",minWidth:0},Uc={padding:"4px 6px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,color:"#64748b"},qc={flex:1,minWidth:0,padding:"4px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,boxSizing:"border-box"},Ww={fontSize:12,color:"#94a3b8",fontFamily:"monospace"},Gc={display:"flex",flexDirection:"column",gap:6,marginTop:6,paddingLeft:8,borderLeft:"2px solid #e2e8f0"},jw={display:"flex",gap:6,alignItems:"flex-start",minWidth:0},Hw={fontSize:11,color:"#94a3b8",minWidth:28,paddingTop:4},Fw={display:"flex",gap:6,alignItems:"flex-start",minWidth:0},Kw={fontSize:11,color:"#475569",fontFamily:"monospace",minWidth:72,paddingTop:4},Yc={border:"none",background:"transparent",color:"#94a3b8",cursor:"pointer",fontSize:12},Xc={alignSelf:"flex-start",padding:"2px 8px",fontSize:11,border:"1px dashed #94a3b8",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},Vw={display:"flex",gap:6,alignItems:"center"},Uw={padding:"4px 8px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6},qw={border:"1px solid #dc2626"},Gw={margin:"2px 0 0",fontSize:11,color:"#dc2626"},Yw=new Set(["text","data-chip","number"]),Xw=new Set(["select","toggle","page-picker","datasource-picker","state-key-picker","modal-picker","component-target-picker","tag-input","locale-picker","i18n-text","segmented","slider","color","image"]);function Jw(e,t,n){return typeof e!="string"?n:e.startsWith("$t:")?t(e.slice(3)):e}function Zw(e,t){if(e==="page-picker")return t.pageCandidates;if(e==="datasource-picker")return t.dataSourceCandidates;if(e==="state-key-picker")return t.stateKeyCandidates;if(e==="modal-picker")return t.modalCandidates}function Qw({param:e,value:t,t:n,pools:o,onChange:r,testIdPrefix:s,renderActionList:a,siblingValues:l}){const d=Jw(e.label,n,e.key);if(e.advanced)return i.jsxs("div",{"data-testid":`${s}-locked-${e.key}`,style:lk,children:[i.jsx("span",{style:dk,children:d}),i.jsxs("span",{style:ck,children:["[",n("layout_editor.action.advanced"),"] ",n("layout_editor.init_actions.advanced_param_hint")]})]});if(e.widget==="action-list"&&a){const f=Array.isArray(t)?t.filter(y=>!!y&&typeof y=="object"):[];return i.jsxs("div",{"data-testid":`${s}-param-${e.key}`,style:{display:"flex",flexDirection:"column",gap:4},children:[i.jsx("span",{style:oo,children:d}),a(f,y=>r(y.length===0?void 0:y),`${s}-${e.key}`,e.key)]})}if(e.widget==="branch-list"&&a){const f=Array.isArray(t)?t.filter(y=>!!y&&typeof y=="object"):[];return i.jsxs("div",{"data-testid":`${s}-param-${e.key}`,style:{display:"flex",flexDirection:"column",gap:4},children:[i.jsx("span",{style:oo,children:d}),i.jsx(Rw,{branches:f,onChange:y=>r(y.length===0?void 0:y),t:n,pools:o,renderActionList:a,testIdPrefix:`${s}-${e.key}`})]})}if(e.widget==="key-value")return i.jsxs("div",{"data-testid":`${s}-param-${e.key}`,style:{display:"flex",flexDirection:"column",gap:4},children:[i.jsx("span",{style:oo,children:d}),i.jsx(rk,{value:t,onChange:r,t:n,candidates:o.bindingCandidates,testIdPrefix:`${s}-kv-${e.key}`})]});if(e.widget==="state-key-value")return i.jsxs("div",{"data-testid":`${s}-param-${e.key}`,style:{display:"flex",flexDirection:"column",gap:4},children:[i.jsx("span",{style:oo,children:d}),i.jsx(ik,{value:t,onChange:r,t:n,candidates:o.bindingCandidates,stateKeyCandidates:o.stateKeyCandidates,param:e,siblingTarget:l?.target,testIdPrefix:`${s}-skv-${e.key}`})]});if(Yw.has(e.widget??"")||!Xw.has(e.widget??""))return i.jsxs("div",{"data-testid":`${s}-param-${e.key}`,style:Zc,children:[i.jsx("span",{style:oo,children:d}),i.jsx("div",{style:{flex:1,minWidth:0},children:i.jsx(Ve,{value:typeof t=="string"?t:t==null?"":String(t),onChange:f=>r(f===""?void 0:f),t:n,candidates:o.bindingCandidates,placeholder:e.widget==="number"?n("layout_editor.action_param.number_placeholder"):void 0,testidPrefix:`${s}-chip-${e.key}`})})]});const p=Tn(e.widget);return i.jsxs("div",{"data-testid":`${s}-param-${e.key}`,style:Zc,children:[i.jsx("span",{style:oo,children:d}),i.jsx("div",{style:{flex:1,minWidth:0},children:p?i.jsx(p,{control:{...e},value:t,onChange:r,t:n,candidates:Zw(e.widget,o),bindingCandidates:o.bindingCandidates}):i.jsx(Ve,{value:typeof t=="string"?t:t==null?"":String(t),onChange:f=>r(f===""?void 0:f),t:n,candidates:o.bindingCandidates,testidPrefix:`${s}-chip-${e.key}`})})]})}function Rw({branches:e,onChange:t,t:n,pools:o,renderActionList:r,testIdPrefix:s}){const a=y=>Array.isArray(y)?y.filter(g=>!!g&&typeof g=="object"):y&&typeof y=="object"?[y]:[],l=y=>{if(y.length!==0)return y.length===1?y[0]:y},d=(y,g)=>{const m=e.slice();m[y]=g,t(m)},c=y=>{t(e.filter((g,m)=>m!==y))},p=(y,g)=>{if(g<0||g>=e.length)return;const m=e.slice(),[h]=m.splice(y,1);m.splice(g,0,h),t(m)},f=()=>{t([...e,{if:"",then:void 0}])};return i.jsxs("div",{"data-testid":`${s}-branches`,style:{display:"flex",flexDirection:"column",gap:8},children:[e.map((y,g)=>{const m=typeof y.if=="string"?y.if:"",h=a(y.then);return i.jsxs("div",{"data-testid":`${s}-branch-${g}`,style:ek,children:[i.jsxs("div",{style:tk,children:[i.jsxs("span",{style:nk,children:[n(m?"layout_editor.action_recipe.conditions.branch_when":"layout_editor.action_recipe.conditions.branch_else")," ",g+1]}),i.jsxs("span",{style:{display:"flex",gap:4},children:[i.jsx("button",{type:"button","data-testid":`${s}-branch-${g}-up`,onClick:()=>p(g,g-1),disabled:g===0,style:As,title:n("layout_editor.action_recipe.conditions.move_up"),children:"↑"}),i.jsx("button",{type:"button","data-testid":`${s}-branch-${g}-down`,onClick:()=>p(g,g+1),disabled:g===e.length-1,style:As,title:n("layout_editor.action_recipe.conditions.move_down"),children:"↓"}),i.jsx("button",{type:"button","data-testid":`${s}-branch-${g}-remove`,onClick:()=>c(g),style:As,title:n("layout_editor.action_recipe.conditions.remove_branch"),children:"✕"})]})]}),i.jsxs("div",{style:{display:"flex",flexDirection:"column",gap:3},children:[i.jsx("span",{style:Jc,children:n("layout_editor.action_recipe.conditions.param_if")}),i.jsx(Ve,{value:m,onChange:v=>d(g,{...y,if:v}),t:n,candidates:o.bindingCandidates,testidPrefix:`${s}-branch-${g}-if`})]}),i.jsxs("div",{style:{display:"flex",flexDirection:"column",gap:3},children:[i.jsx("span",{style:Jc,children:n("layout_editor.action_recipe.conditions.param_then")}),r(h,v=>d(g,{...y,then:l(v)}),`${s}-branch-${g}-then`)]})]},g)}),i.jsxs("button",{type:"button","data-testid":`${s}-add-branch`,onClick:f,style:ok,children:["+ ",n("layout_editor.action_recipe.conditions.add_branch")]})]})}const ek={border:"1px solid #e2e8f0",borderRadius:6,padding:8,display:"flex",flexDirection:"column",gap:6,background:"#f8fafc"},tk={display:"flex",justifyContent:"space-between",alignItems:"center"},nk={fontSize:12,fontWeight:700,color:"#475569"},Jc={fontSize:11,color:"#64748b"},As={border:"1px solid #cbd5e1",background:"#fff",borderRadius:4,width:22,height:22,cursor:"pointer",fontSize:12,lineHeight:"1",color:"#475569"},ok={border:"1px dashed #94a3b8",background:"#fff",borderRadius:6,padding:"6px 10px",cursor:"pointer",fontSize:12,color:"#475569",alignSelf:"flex-start"};function rk({value:e,onChange:t,t:n,candidates:o,testIdPrefix:r,renderKeyInput:s}){const a=e&&typeof e=="object"&&!Array.isArray(e)?Object.entries(e).map(([l,d])=>({key:l,value:typeof d=="string"?d:d==null?"":String(d)})):[];return i.jsx(zr,{value:a,onChange:l=>{const d={};for(const c of l)c.key.trim()!==""&&(d[c.key]=c.value??"");t(Object.keys(d).length===0?void 0:d)},keyField:"key",valueField:"value",t:n,candidates:o,testidPrefix:r,keyPlaceholder:n("layout_editor.action_param.key_placeholder"),valuePlaceholder:n("layout_editor.action_param.value_placeholder"),addLabel:n("layout_editor.action_param.add_pair"),renderKeyInput:s})}function ik({value:e,onChange:t,t:n,candidates:o,stateKeyCandidates:r,param:s,siblingTarget:a,testIdPrefix:l}){const d=e&&typeof e=="object"&&!Array.isArray(e)?Object.entries(e):[],c=typeof a=="string"?a:"";if((c.startsWith("_local.")||c.startsWith("global.")||c.startsWith("$parent._local.")||c.startsWith("$root._local."))&&d.length===1&&d[0][0]==="value"){const b=d[0][1];return i.jsx("div",{"data-testid":`${l}-path-value`,style:{display:"flex",flexDirection:"column",gap:4},children:i.jsx(Nn,{value:b,onChange:x=>t(x===void 0?void 0:{value:x}),t:n,path:"value",scope:`${l}-path-value-val`,renderStringLeaf:x=>i.jsx(Ve,{value:x.value,onChange:x.onChange,t:n,candidates:o,testidPrefix:x.testidPrefix})})})}const y=(b,x)=>{const k={};for(const[w,_]of d)k[w]=w===b?x:_;t(Object.keys(k).length===0?void 0:k)},g=b=>{const x={};for(const[k,w]of d)k!==b&&(x[k]=w);t(Object.keys(x).length===0?void 0:x)},[m,h]=u.useState(""),v=()=>{const b=m.trim();if(b===""||d.some(([k])=>k===b))return;const x={};for(const[k,w]of d)x[k]=w;x[b]="",t(x),h("")};return i.jsxs("div",{"data-testid":l,style:{display:"flex",flexDirection:"column",gap:6},children:[d.map(([b,x])=>i.jsxs("div",{"data-testid":`${l}-row-${b}`,style:{display:"flex",gap:6,alignItems:"flex-start",border:"1px solid #e2e8f0",borderRadius:8,padding:8},children:[i.jsxs("div",{style:{flex:1,minWidth:0,display:"flex",flexDirection:"column",gap:6},children:[i.jsx("div",{style:{minWidth:0},children:i.jsx(Es,{control:{...s},value:b,onChange:k=>{const w=k==null?"":String(k);if(w===""||w===b||d.some(([C])=>C===w))return;const _={};for(const[C,E]of d)_[C===b?w:C]=E;t(_)},t:n,candidates:r})}),i.jsx("div",{style:{minWidth:0},children:i.jsx(Nn,{value:x,onChange:k=>y(b,k),t:n,path:b,scope:`${l}-val`,renderStringLeaf:k=>i.jsx(Ve,{value:k.value,onChange:k.onChange,t:n,candidates:o,testidPrefix:k.testidPrefix})})})]}),i.jsx("button",{type:"button","data-testid":`${l}-remove-${b}`,onClick:()=>g(b),title:n("layout_editor.page_settings.seo.remove"),"aria-label":n("layout_editor.page_settings.seo.remove"),style:{flex:"0 0 auto",padding:"4px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#64748b",cursor:"pointer"},children:"✕"})]},b)),i.jsxs("div",{style:{display:"flex",gap:6,alignItems:"center"},children:[i.jsx("div",{style:{flex:1,minWidth:0},children:i.jsx(Es,{control:{...s},value:m,onChange:b=>h(b==null?"":String(b)),t:n,candidates:r})}),i.jsxs("button",{type:"button","data-testid":`${l}-add`,onClick:v,style:{flex:"0 0 auto",padding:"5px 10px",fontSize:12,border:"1px dashed #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#475569",cursor:"pointer"},children:["+ ",n("layout_editor.action_param.add_pair")]})]})]})}function Ts({raw:e,recipe:t,values:n,t:o,pools:r,onChange:s,testIdPrefix:a,renderActionList:l}){const d=(c,p)=>{const f={...n,[c]:p},y=xr(t,f),g=as(e);s(g?jd(y,g):y)};return i.jsx(i.Fragment,{children:t.params.filter(c=>sk(c,n)).map(c=>i.jsx(Qw,{param:c,value:n[c.key],t:o,pools:r,onChange:p=>d(c.key,p),testIdPrefix:a,renderActionList:l,siblingValues:n},c.key))})}function sk(e,t){const n=e.dependsOn;return!n||typeof n!="object"||typeof n.param!="string"?!0:t[n.param]===n.equals}function ak({raw:e,t,onChange:n,testIdPrefix:o,candidates:r}){const[s,a]=u.useState(!1);return i.jsxs("div",{"data-testid":`${o}-if`,style:uk,children:[i.jsxs("button",{type:"button",onClick:()=>a(l=>!l),style:pk,children:["▸ ",t("layout_editor.init_actions.if_label")]}),s?i.jsx("div",{"data-testid":`${o}-if-input`,style:{minWidth:0},children:i.jsx(Ve,{value:as(e)??"",onChange:l=>n(jd(e,l)),t,candidates:r,placeholder:t("layout_editor.init_actions.if_placeholder"),testidPrefix:`${o}-if-chip`})}):null]})}const Zc={display:"flex",alignItems:"center",gap:8,minWidth:0},oo={fontSize:11,color:"#64748b",minWidth:80},lk={display:"flex",alignItems:"center",gap:8,padding:"4px 0"},dk={fontSize:11,color:"#64748b",minWidth:80},ck={fontSize:11,color:"#92400e"},uk={display:"flex",flexDirection:"column",gap:4},pk={textAlign:"left",border:"none",background:"transparent",color:"#475569",cursor:"pointer",fontSize:11,padding:0};function fk(e,t,n){if(!t)return n("layout_editor.action.source_core");const o=e?.[t],r=o&&typeof o=="object"&&o.__source?o.__source:null;return!r||r.kind==="core"?n("layout_editor.action.source_core"):r.id??n(`layout_editor.action.source_${r.kind}`)}function gk(e,t,n){const o=Yn(e,t);if(o.kind==="preset"){const r=t.find(s=>s.id===o.recipeId);return Wd(r??null,o.values,s=>n(s.startsWith("$t:")?s.slice(3):s))}return typeof e.handler=="string"?e.handler:n("layout_editor.action_list.unknown")}function Gt({actions:e,onChange:t,t:n,recipes:o,chipContext:r,actionChipCandidates:s,addContext:a="init",renderAddPicker:l,renderEditor:d,candidatePools:c,showSourceBadge:p=!1,canDrag:f,canDropAt:y,isLocked:g,hideAddPicker:m=!1,testIdPrefix:h="g7le-action-list"}){const v=u.useMemo(()=>wo(o),[o]),b=u.useMemo(()=>{if(r==null||r==="init")return c;const O=jl(r,n,s??null);if(O.length===0)return c;const M=c??{},B=new Set((M.bindingCandidates??[]).map(H=>H.expression)),N=O.filter(H=>!B.has(H.expression));return N.length===0?c:{...M,bindingCandidates:[...N,...M.bindingCandidates??[]]}},[r,s,c,n]),[x,k]=u.useState(null),[w,_]=u.useState(null),C=u.useCallback((O,M)=>{if(O===M||O<0||M<0||O>=e.length||M>=e.length)return;const B=e.slice(),[N]=B.splice(O,1);B.splice(M,0,N),t(B)},[e,t]),E=fs({length:e.length,onMove:C,canDrag:f,canDropAt:y}),S=u.useCallback(O=>{t(e.filter((M,B)=>B!==O))},[e,t]),T=u.useCallback((O,M)=>{const B=e.slice();B[O]=M,t(B)},[e,t]),$=u.useCallback(O=>{t([...e,O])},[e,t]),A=u.useCallback((O,M)=>{const B=Yn(O,v),N=B.kind==="preset"?v.find(L=>L.id===B.recipeId)??null:null;if(!N)return null;const H=B.kind==="preset"?B.values:{};return i.jsxs("div",{style:mk,children:[i.jsx(Ts,{raw:O,recipe:N,values:H,t:n,pools:b??{},onChange:M,testIdPrefix:`${h}-edit`,renderActionList:(L,I,P,F)=>{const G=F==="onSuccess"?"response":F==="onError"?"error":void 0;return i.jsx(Gt,{actions:L,onChange:I,t:n,recipes:o,chipContext:G,actionChipCandidates:G?s:void 0,candidatePools:G?c:b,addContext:a,showSourceBadge:p,testIdPrefix:P})}}),i.jsx(ak,{raw:O,t:n,onChange:M,testIdPrefix:`${h}-edit`,candidates:b?.bindingCandidates})]})},[v,n,b,c,s,h,o,a,p]),z=d??(o?A:void 0),D=m?null:l?l($):o?i.jsx(uw,{recipes:o,t:n,onAdd:$,context:a,testIdPrefix:`${h}-add-picker`}):null;return i.jsxs("div",{className:h,"data-testid":h,children:[i.jsxs("ul",{style:{listStyle:"none",margin:0,padding:0,display:"flex",flexDirection:"column",gap:6},children:[e.map((O,M)=>{const B=Yn(O,v),N=B.kind==="preset"?B.recipeId:null,H=N?v.find(G=>G.id===N)??null:null,L=H===null,I=g?.(M)??!1,P=as(O)!==null,F=z?z(O,G=>T(M,G)):null;return i.jsxs(u.Fragment,{children:[I?null:i.jsx("li",{"aria-hidden":"true",style:{listStyle:"none",margin:0,padding:0},children:i.jsx(Rn,{active:E.isDropTarget(M),testid:`${h}-dropline-${M}`})}),i.jsxs("li",{"data-testid":`${h}-item-${M}`,"data-drag-source":E.dragIndex===M?"true":void 0,style:{border:"1px solid #e2e8f0",borderRadius:8,padding:"8px 10px",...E.dragIndex===M?{opacity:.5}:{}},onDragOver:E.dragging&&!I?G=>{G.preventDefault();const V=G.currentTarget.getBoundingClientRect();E.onDragOverItem(M,G.clientY{G.preventDefault(),E.onDrop()}:void 0,children:[i.jsxs("div",{style:{display:"flex",alignItems:"center",gap:8},children:[I?i.jsx("span",{style:{fontSize:12},children:"🔒"}):i.jsx("span",{"data-testid":`${h}-drag-${M}`,style:{color:"#94a3b8",cursor:"grab",fontSize:14,userSelect:"none"},draggable:!0,onDragStart:()=>E.onDragStart(M),onDragEnd:E.onDragEnd,"aria-label":n("layout_editor.action_list.drag_reorder"),role:"button",children:"⠿"}),i.jsx("span",{style:{flex:1,minWidth:0,fontSize:13},"data-testid":`${h}-summary-${M}`,children:gk(O,v,n)}),p?i.jsxs("span",{"data-testid":`${h}-source-${M}`,style:hk,children:["〔",fk(o,N,n),"〕"]}):null,P?i.jsxs("span",{"data-testid":`${h}-cond-badge-${M}`,style:yk,children:["〔",n("layout_editor.init_actions.cond_badge"),"〕"]}):null,L?i.jsxs("span",{"data-testid":`${h}-advanced-${M}`,style:bk,children:["[",n("layout_editor.action.advanced"),"]"]}):null,!I&&F?i.jsxs("button",{type:"button","data-testid":`${h}-edit-${M}`,onClick:()=>_(w===M?null:M),"aria-label":n("layout_editor.init_actions.edit"),title:n("layout_editor.init_actions.edit"),style:w===M?vk:Qc,children:["✎ ",n("layout_editor.init_actions.edit")]}):null,i.jsx("button",{type:"button","data-testid":`${h}-code-${M}`,onClick:()=>k(x===M?null:M),"aria-label":n("layout_editor.action_list.view_code"),title:n("layout_editor.action_list.view_code"),style:x===M?_k:Ds,children:""}),I?null:i.jsx("button",{type:"button","data-testid":`${h}-remove-${M}`,onClick:()=>S(M),"aria-label":n("layout_editor.action_list.remove"),title:n("layout_editor.action_list.remove"),style:xk,children:"✕"})]}),H?i.jsx(kw,{action:O,recipe:H,values:B.kind==="preset"?B.values:{},t:n,showCodeButton:!1,testIdPrefix:`${h}-preview-${M}`}):null,w===M&&!I&&F?i.jsx("div",{"data-testid":`${h}-edit-body-${M}`,children:F}):null,d&&w!==M?d(O,G=>T(M,G)):null,x===M?i.jsx("pre",{"data-testid":`${h}-code-view-${M}`,style:{margin:"8px 0 0",padding:8,background:"#0f172a",color:"#e2e8f0",borderRadius:6,fontSize:12,overflow:"auto"},children:JSON.stringify(O,null,2)}):null]})]},M)}),i.jsx("li",{"aria-hidden":"true",style:{listStyle:"none",margin:0,padding:0},children:i.jsx(Rn,{active:E.isDropTarget(e.length),testid:`${h}-dropline-end`})})]}),D?i.jsx("div",{style:{marginTop:8},children:D}):null]})}const hk={fontSize:10,color:"#64748b",whiteSpace:"nowrap"},yk={fontSize:10,color:"#7c3aed",whiteSpace:"nowrap"},bk={fontSize:10,color:"#92400e",whiteSpace:"nowrap"},mk={display:"flex",flexDirection:"column",gap:6,paddingTop:6,borderTop:"1px dashed #e2e8f0",marginTop:6},Qc={flex:"0 0 auto",padding:"3px 8px",fontSize:11,fontWeight:500,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer",whiteSpace:"nowrap",lineHeight:1.4},vk={...Qc,background:"#eff6ff",borderColor:"#bfdbfe",color:"#2563eb"},Ds={flex:"0 0 auto",width:26,height:24,display:"inline-flex",alignItems:"center",justifyContent:"center",fontSize:11,border:"1px solid #e2e8f0",borderRadius:6,background:"#fff",color:"#64748b",cursor:"pointer",fontFamily:"ui-monospace, monospace"},_k={...Ds,background:"#0f172a",borderColor:"#0f172a",color:"#e2e8f0"},xk={...Ds,color:"#dc2626",borderColor:"#fecaca",fontFamily:"inherit"};function Rc(e){const t=e.__source;return t&&typeof t=="object"?t:null}function wk({actions:e,onChange:t,recipes:n,t:o,bindingCandidates:r,pageCandidates:s,dataSourceCandidates:a,stateKeyCandidates:l,modalCandidates:d}){const c=u.useMemo(()=>({pageCandidates:s,dataSourceCandidates:a,stateKeyCandidates:l,modalCandidates:d,bindingCandidates:r}),[s,a,l,d,r]),p=u.useMemo(()=>e.filter(h=>Rc(h)?.kind==="base"),[e]),f=u.useMemo(()=>e.filter(h=>Rc(h)?.kind!=="base"),[e]),y=p.length>0,g=u.useCallback(h=>{t(y?[...p,...h]:h)},[t,y,p]),m=u.useCallback(()=>{},[]);return i.jsxs("div",{className:"g7le-init-actions-form","data-testid":"g7le-init-actions-form",style:kk,children:[i.jsx("p",{style:Sk,children:o("layout_editor.page_settings.init_actions.heading")}),y?i.jsxs(i.Fragment,{children:[i.jsxs("div",{"data-testid":"g7le-init-action-group-base",style:eu,children:[i.jsx("div",{style:tu,children:o("layout_editor.init_actions.group_base")}),i.jsx(Gt,{actions:p,onChange:m,t:o,recipes:n,candidatePools:c,addContext:"init",showSourceBadge:!0,isLocked:()=>!0,hideAddPicker:!0,testIdPrefix:"g7le-init-action-base"}),i.jsxs("p",{"data-testid":"g7le-init-action-base-hint",style:Ck,children:["ⓘ ",o("layout_editor.init_actions.base_readonly_hint")]})]}),i.jsxs("div",{"data-testid":"g7le-init-action-group-self",style:eu,children:[i.jsx("div",{style:tu,children:o("layout_editor.init_actions.group_self")}),f.length===0?i.jsx("p",{style:nu,children:o("layout_editor.init_actions.empty_self")}):null,i.jsx(Gt,{actions:f,onChange:g,t:o,recipes:n,candidatePools:c,addContext:"init",showSourceBadge:!0,testIdPrefix:"g7le-init-action-self"})]}),i.jsxs("p",{"data-testid":"g7le-init-action-merge-order",style:$k,children:["ⓘ ",o("layout_editor.init_actions.merge_order")]})]}):i.jsxs(i.Fragment,{children:[e.length===0?i.jsx("p",{"data-testid":"g7le-init-action-empty",style:nu,children:o("layout_editor.init_actions.empty")}):null,i.jsx(Gt,{actions:e,onChange:t,t:o,recipes:n,candidatePools:c,addContext:"init",showSourceBadge:!0,testIdPrefix:"g7le-init-action"})]})]})}const kk={display:"flex",flexDirection:"column",gap:12,minWidth:0},Sk={margin:0,fontSize:13,fontWeight:700,color:"#0f172a"},eu={border:"1px solid #e2e8f0",borderRadius:10,padding:10,display:"flex",flexDirection:"column",gap:8},tu={fontSize:12,fontWeight:700,color:"#475569"},Ck={margin:0,fontSize:11,color:"#64748b"},nu={margin:0,fontSize:12,color:"#94a3b8"},$k={margin:0,fontSize:11,color:"#64748b"};function Ek(e){return e==="spinner"?new Set(["spinner","page"]):new Set(["skeleton"])}function ou(e,t,n){return typeof e!="string"?n:e.startsWith("$t:")?t(e.slice(3)):e}function ru({candidates:e,value:t,onSelect:n,styleKind:o,t:r,testIdPrefix:s="g7le-loading-component-picker"}){const a=yo(),l=()=>{const d=a.open({ariaLabel:r("layout_editor.overlay.loading_picker.select"),width:520,maxHeightRatio:.7,content:i.jsx(Ik,{candidates:e,value:t,styleKind:o,t:r,testIdPrefix:s,onPick:c=>{n(c),a.close(d)},onClose:()=>a.close(d)})})};return i.jsx("div",{className:s,"data-testid":s,style:{minWidth:0},children:i.jsxs("div",{style:Ak,children:[i.jsx("span",{"data-testid":`${s}-current`,style:Tk,children:t&&t.length>0?t:r("layout_editor.overlay.loading_picker.none")}),i.jsxs("button",{type:"button","data-testid":`${s}-open`,onClick:l,style:Dk,children:["🔍 ",r("layout_editor.overlay.loading_picker.select")]})]})})}function Ik({candidates:e,value:t,styleKind:n,t:o,testIdPrefix:r,onPick:s,onClose:a}){const[l,d]=u.useState(""),c=Ek(n),p=u.useMemo(()=>{const f=l.trim().toLowerCase();return e.filter(y=>c.has(y.role)).filter(y=>f?ou(y.label,o,y.name).toLowerCase().includes(f)||y.name.toLowerCase().includes(f):!0)},[e,n,l,o]);return i.jsxs("div",{"data-testid":`${r}-modal`,style:Nk,children:[i.jsxs("div",{style:zk,children:[i.jsxs("span",{style:Mk,children:["🔍 ",o("layout_editor.overlay.loading_picker.select")]}),i.jsx("button",{type:"button","data-testid":`${r}-close`,onClick:a,"aria-label":o("layout_editor.overlay.loading_picker.select"),style:Lk,children:"✕"})]}),i.jsx("input",{type:"text","data-testid":`${r}-search`,value:l,placeholder:o("layout_editor.overlay.loading_picker.search"),onChange:f=>d(f.target.value),style:Pk,autoFocus:!0}),p.length===0?i.jsx("p",{"data-testid":`${r}-empty`,style:Kk,children:o("layout_editor.overlay.loading_picker.empty")}):i.jsx("ul",{style:Ok,children:p.map(f=>i.jsx("li",{children:i.jsxs("button",{type:"button","data-testid":`${r}-item-${f.name}`,onClick:()=>s(f.name),style:{...Bk,...t===f.name?Wk:{}},children:[i.jsx("span",{style:jk,children:ou(f.label,o,f.name)}),i.jsx("code",{style:Hk,children:f.name}),i.jsx("span",{style:Fk,children:f.role})]})},f.name))})]})}const Ak={display:"flex",alignItems:"center",gap:6,minWidth:0},Tk={flex:1,minWidth:0,fontSize:12,color:"#0f172a",fontFamily:"monospace"},Dk={padding:"4px 10px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#475569",cursor:"pointer",whiteSpace:"nowrap"},Nk={display:"flex",flexDirection:"column",minHeight:0,padding:16,boxSizing:"border-box",fontFamily:"system-ui, -apple-system, sans-serif"},zk={display:"flex",alignItems:"center",gap:8,paddingBottom:8,borderBottom:"1px solid #e2e8f0",marginBottom:10},Mk={flex:1,fontSize:14,fontWeight:700,color:"#0f172a",minWidth:0},Lk={border:"none",background:"transparent",color:"#64748b",cursor:"pointer",fontSize:15,lineHeight:1,padding:4},Pk={width:"100%",padding:"6px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,marginBottom:8,boxSizing:"border-box"},Ok={listStyle:"none",margin:0,padding:0,display:"flex",flexDirection:"column",gap:2,overflowY:"auto",minHeight:0},Bk={display:"flex",alignItems:"center",gap:6,width:"100%",textAlign:"left",padding:"8px 10px",fontSize:12,border:"1px solid transparent",background:"transparent",cursor:"pointer",borderRadius:6},Wk={border:"1px solid #3b82f6",background:"#eff6ff"},jk={flex:1,minWidth:0,color:"#0f172a"},Hk={fontSize:10,color:"#94a3b8",fontFamily:"monospace"},Fk={fontSize:10,color:"#64748b",background:"#f1f5f9",borderRadius:4,padding:"0 4px"},Kk={margin:0,fontSize:12,color:"#94a3b8",padding:"8px 4px"};function iu(e){return e===!0?{enabled:!0}:e===!1||e===void 0?{}:e}const Vk=["opaque","blur","fade","skeleton","spinner"];function Uk({value:e,patch:t,t:n,baseValue:o,ownValue:r,loadingComponents:s=[],progressiveDataSources:a=[],bindingCandidates:l}){const d=iu(e),c=d.enabled??!1,p=!!o&&Object.keys(o).length>0,f=Tn("component-target-picker"),[y,g]=u.useState((d.wait_for??[]).length>0),m=u.useCallback(w=>{const _={...iu(e)};w(_),t(_)},[e,t]),h=r&&typeof r=="object"?r:e&&typeof e=="object"?e:{},v=u.useCallback(w=>p&&o!==void 0&&w in o&&!(w in h),[p,o,h]),b=u.useCallback(w=>{o&&m(_=>{_[w]=o[w]})},[o,m]),x=u.useCallback(w=>m(_=>{_.enabled=w}),[m]),k=d.target!==void 0?"region":"full";return i.jsxs("div",{className:"g7le-overlay-form","data-testid":"g7le-overlay-form",style:qk,children:[p?i.jsxs("p",{"data-testid":"g7le-overlay-inherit-banner",style:Gk,children:["ⓘ ",n("layout_editor.overlay.inherit_banner")]}):null,i.jsxs("div",{style:Br,children:[i.jsx(Ns,{label:n("layout_editor.overlay.enabled_label"),inherited:v("enabled"),hasInheritance:p,onOverride:()=>b("enabled"),testidKey:"enabled",t:n}),i.jsx(Nr,{disabled:v("enabled"),children:i.jsx(Dr,{checked:c,onChange:x,testid:"g7le-overlay-enabled",ariaLabel:n("layout_editor.overlay.enabled_label"),label:n(c?"layout_editor.overlay.on":"layout_editor.overlay.off")})}),i.jsxs("p",{style:Wr,children:["ⓘ ",n("layout_editor.overlay.enabled_hint")]})]}),i.jsx(Nr,{disabled:!c,testid:"g7le-overlay-body",style:{display:"flex",flexDirection:"column",gap:16,minWidth:0},children:i.jsxs(i.Fragment,{children:[i.jsxs("div",{style:Br,children:[i.jsx(Ns,{label:n("layout_editor.overlay.scope_label"),inherited:v("target"),hasInheritance:p,onOverride:()=>b("target"),testidKey:"target",t:n}),i.jsxs("div",{"data-testid":"g7le-overlay-scope",style:zs,children:[i.jsx(Or,{active:k==="full",onClick:()=>m(w=>{delete w.target,delete w.fallback_target}),testid:"g7le-overlay-scope-full",children:n("layout_editor.overlay.scope_full")}),i.jsx(Or,{active:k==="region",onClick:()=>m(w=>{w.target=w.target??""}),testid:"g7le-overlay-scope-region",children:n("layout_editor.overlay.scope_region")})]}),i.jsx(Nr,{disabled:k!=="region",testid:"g7le-overlay-region-box",style:Zk,children:i.jsxs(i.Fragment,{children:[i.jsx(zn,{label:n("layout_editor.overlay.target_label"),children:i.jsx("div",{"data-testid":"g7le-overlay-target-picker",children:f?i.jsx(f,{control:{label:n("layout_editor.overlay.target_label")},value:d.target??"",onChange:w=>m(_=>{_.target=typeof w=="string"?w:""}),t:n}):i.jsx("input",{type:"text","data-testid":"g7le-overlay-target-input",value:d.target??"",onChange:w=>m(_=>{_.target=w.target.value}),style:e1})})}),i.jsx(zn,{label:n("layout_editor.overlay.fallback_label"),children:i.jsx("div",{"data-testid":"g7le-overlay-fallback-picker",children:f?i.jsx(f,{control:{label:n("layout_editor.overlay.fallback_label")},value:d.fallback_target??"",onChange:w=>m(_=>{const C=typeof w=="string"?w:"";C===""?delete _.fallback_target:_.fallback_target=C}),t:n}):null})}),i.jsxs("p",{style:Wr,children:["ⓘ ",n("layout_editor.overlay.fallback_hint")]})]})})]}),i.jsxs("div",{style:Br,children:[i.jsx(Ns,{label:n("layout_editor.overlay.style_label"),inherited:v("style"),hasInheritance:p,onOverride:()=>b("style"),testidKey:"style",t:n}),i.jsx("div",{"data-testid":"g7le-overlay-style",style:zs,children:Vk.map(w=>i.jsx(Or,{active:(d.style??"opaque")===w,onClick:()=>m(_=>{_.style=w}),testid:`g7le-overlay-style-${w}`,children:n(`layout_editor.overlay.style_${w}`)},w))}),d.style==="skeleton"?i.jsxs("div",{style:su,children:[i.jsx(zn,{label:n("layout_editor.overlay.skeleton_component"),children:i.jsx(ru,{candidates:s,value:d.skeleton?.component,styleKind:"skeleton",onSelect:w=>m(_=>{_.skeleton={..._.skeleton,component:w}}),t:n,testIdPrefix:"g7le-overlay-skeleton-component"})}),i.jsx(zn,{label:n("layout_editor.overlay.skeleton_animation"),children:i.jsx("div",{"data-testid":"g7le-overlay-skeleton-anim",style:zs,children:["wave","pulse","none"].map(w=>i.jsx(Or,{active:(d.skeleton?.animation??"wave")===w,onClick:()=>m(_=>{_.skeleton={..._.skeleton,animation:w}}),testid:`g7le-overlay-skeleton-anim-${w}`,children:n(`layout_editor.overlay.anim_${w}`)},w))})}),i.jsx(zn,{label:n("layout_editor.overlay.skeleton_count"),children:i.jsx("input",{type:"number","data-testid":"g7le-overlay-skeleton-count",value:d.skeleton?.iteration_count??"",onChange:w=>m(_=>{const C=parseInt(w.target.value,10);_.skeleton={..._.skeleton,iteration_count:Number.isNaN(C)?void 0:C}}),style:t1})})]}):null,d.style==="spinner"?i.jsxs("div",{style:su,children:[i.jsx(zn,{label:n("layout_editor.overlay.spinner_component"),children:i.jsx(ru,{candidates:s,value:d.spinner?.component,styleKind:"spinner",onSelect:w=>m(_=>{_.spinner={..._.spinner,component:w}}),t:n,testIdPrefix:"g7le-overlay-spinner-component"})}),i.jsx(zn,{label:n("layout_editor.overlay.spinner_text"),children:i.jsx("div",{"data-testid":"g7le-overlay-spinner-text",style:{minWidth:0},children:i.jsx(vt,{value:d.spinner?.text??"",onChange:w=>m(_=>{_.spinner={..._.spinner,text:w===void 0||w===""?void 0:w}}),t:n,candidates:l,enableExpressionTree:!0,expressionTreeCollapsible:!0,testidPrefix:"g7le-overlay-spinner-text-field"})})})]}):null]}),i.jsxs("div",{style:Br,children:[i.jsxs("button",{type:"button","data-testid":"g7le-overlay-waitfor-toggle","aria-expanded":y,onClick:()=>g(w=>!w),style:r1,children:[y?"▾":"▸"," ",n("layout_editor.overlay.waitfor_label")]}),y?i.jsxs("div",{"data-testid":"g7le-overlay-waitfor-body",style:i1,children:[i.jsxs("p",{"data-testid":"g7le-overlay-waitfor-hint",style:Wr,children:["ⓘ ",n("layout_editor.overlay.waitfor_hint")]}),a.length===0?i.jsx("p",{"data-testid":"g7le-overlay-waitfor-empty",style:Wr,children:n("layout_editor.overlay.waitfor_empty")}):i.jsx("div",{"data-testid":"g7le-overlay-waitfor",style:{display:"flex",flexWrap:"wrap",gap:6},children:a.map(w=>{const _=(d.wait_for??[]).includes(w.id);return i.jsxs("label",{"data-testid":`g7le-overlay-waitfor-${w.id}`,style:n1(_),children:[i.jsx("input",{type:"checkbox",checked:_,onChange:()=>m(C=>{const E=new Set(C.wait_for??[]);E.has(w.id)?E.delete(w.id):E.add(w.id);const S=Array.from(E);S.length===0?delete C.wait_for:C.wait_for=S})}),i.jsx(Dc,{option:w,testIdPrefix:`g7le-overlay-waitfor-${w.id}`})]},w.id)})})]}):null]}),p?i.jsxs("button",{type:"button","data-testid":"g7le-overlay-reset-inherit",onClick:()=>t(void 0),style:o1,children:["⟲ ",n("layout_editor.overlay.reset_inherit")]}):null]})})]})}function Or({active:e,onClick:t,children:n,testid:o}){return i.jsx("button",{type:"button","data-testid":o,"aria-pressed":e,onClick:t,style:{...Xk,...e?Jk:{}},children:n})}function Ns({label:e,inherited:t,hasInheritance:n,onOverride:o,testidKey:r,t:s}){return i.jsxs("div",{style:s1,children:[i.jsx("label",{style:Yk,children:e}),n?t?i.jsxs(i.Fragment,{children:[i.jsxs("span",{"data-testid":`g7le-overlay-inherited-${r}`,style:a1,children:["〔",s("layout_editor.overlay.inherited_badge"),"〕"]}),i.jsx("button",{type:"button","data-testid":`g7le-overlay-override-${r}`,onClick:o,style:d1,children:s("layout_editor.overlay.override_field")})]}):i.jsxs("span",{"data-testid":`g7le-overlay-overridden-${r}`,style:l1,children:["〔",s("layout_editor.overlay.overridden_badge"),"〕"]}):null]})}function zn({label:e,children:t}){return i.jsxs("div",{style:Qk,children:[i.jsx("span",{style:Rk,children:e}),i.jsx("div",{style:{flex:1,minWidth:0},children:t})]})}const qk={display:"flex",flexDirection:"column",gap:16,minWidth:0},Gk={margin:0,fontSize:12,color:"#1d4ed8",background:"#eff6ff",padding:"8px 10px",borderRadius:6},Br={display:"flex",flexDirection:"column",gap:6,minWidth:0},Yk={fontSize:13,fontWeight:600,color:"#0f172a"},Wr={margin:0,fontSize:11,color:"#94a3b8"},zs={display:"flex",gap:4,flexWrap:"wrap"},Xk={padding:"4px 10px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},Jk={border:"1px solid #3b82f6",background:"#eff6ff",color:"#1d4ed8"},Zk={display:"flex",flexDirection:"column",gap:8,padding:8,border:"1px dashed #e2e8f0",borderRadius:8},su={display:"flex",flexDirection:"column",gap:8,padding:8,border:"1px dashed #e2e8f0",borderRadius:8},Qk={display:"flex",alignItems:"center",gap:8,minWidth:0},Rk={fontSize:11,color:"#64748b",minWidth:90},e1={width:"100%",padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,boxSizing:"border-box"},t1={width:80,padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6},n1=e=>({display:"inline-flex",alignItems:"center",gap:4,padding:"4px 8px",fontSize:12,border:`1px solid ${e?"#3b82f6":"#cbd5e1"}`,borderRadius:12,background:e?"#eff6ff":"#fff",cursor:"pointer"}),o1={alignSelf:"flex-start",padding:"6px 12px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},r1={alignSelf:"flex-start",padding:"4px 0",fontSize:13,fontWeight:600,color:"#0f172a",background:"transparent",border:"none",cursor:"pointer"},i1={display:"flex",flexDirection:"column",gap:6,paddingLeft:14,minWidth:0},s1={display:"flex",alignItems:"center",gap:8,flexWrap:"wrap",minWidth:0},a1={fontSize:11,color:"#64748b",background:"#f1f5f9",borderRadius:4,padding:"1px 6px"},l1={fontSize:11,color:"#0369a1",background:"#e0f2fe",borderRadius:4,padding:"1px 6px"},d1={padding:"2px 8px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},c1="g7le:computed-effect-preview",u1={number:"layout_editor.computed.type_number",string:"layout_editor.computed.type_string",boolean:"layout_editor.computed.type_boolean",list:"layout_editor.computed.type_list",object:"layout_editor.computed.type_object",null:"layout_editor.computed.type_null"};function p1(e,t,n){if(t==="list"){const o=e,r=o.slice(0,2).map(s=>JSON.stringify(s)).join(", ");return o.length<=2?`[${r}]`:`[${r}, ${n("layout_editor.computed.list_more",{count:o.length})}]`}return t==="object"?JSON.stringify(e):t==="boolean"?e?"true":"false":t==="null"?n("layout_editor.computed.value_none"):String(e)}function au({expr:e,computedKey:t,sampleContext:n,t:o,isEffect:r=!1,testIdPrefix:s="g7le-computed-preview"}){const a=Id(e,n),l=()=>{typeof window>"u"||!t||window.dispatchEvent(new CustomEvent(c1,{detail:{key:t}}))};return a.ok?i.jsxs("div",{className:s,"data-testid":s,style:f1,children:[i.jsx("span",{style:g1,children:o("layout_editor.computed.preview_label")}),i.jsx("span",{"data-testid":`${s}-value`,style:h1,children:p1(a.value,a.type,o)}),i.jsxs("span",{"data-testid":`${s}-type`,style:y1,children:["(",o(u1[a.type]),")"]}),r&&a.type==="boolean"?i.jsxs("button",{type:"button","data-testid":`${s}-effect`,onClick:l,style:b1,children:["▷ ",o("layout_editor.computed.effect_preview")]}):null]}):i.jsxs("div",{"data-testid":`${s}-error`,style:m1,children:["⚠ ",o("layout_editor.computed.preview_error")]})}const f1={display:"flex",alignItems:"center",flexWrap:"wrap",gap:6,minWidth:0},g1={fontSize:11,color:"#94a3b8"},h1={fontSize:12,color:"#0f172a",fontFamily:"monospace",wordBreak:"break-all"},y1={fontSize:10,color:"#64748b"},b1={padding:"2px 8px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#475569",cursor:"pointer"},m1={fontSize:12,color:"#b45309"},v1=[{op:"count",labelKey:"layout_editor.computed.op_count"},{op:"sum",labelKey:"layout_editor.computed.op_sum"},{op:"filter",labelKey:"layout_editor.computed.op_filter"},{op:"toOptions",labelKey:"layout_editor.computed.op_to_options"},{op:"nth",labelKey:"layout_editor.computed.op_nth"},{op:"firstOf",labelKey:"layout_editor.computed.op_first_of"},{op:"literal",labelKey:"layout_editor.computed.op_literal"}],_1=["=","!=",">","<",">=","<=","includes"],x1=new Set(["count","sum","filter"]);function lu({model:e,onChange:t,t:n,dataSourceCandidates:o,bindingCandidates:r,existingKeys:s=[]}){const a=u.useCallback(g=>{const m={...e};g(m),t(m)},[e,t]),l=e.conditions??[],d=Tn("datasource-picker"),c=e.key!==""&&s.includes(e.key),p=()=>a(g=>{g.conditions=[...g.conditions??[],{field:"",cmp:"=",value:""}]}),f=(g,m)=>a(h=>{const v=(h.conditions??[]).slice(),b={...v[g]};m(b),v[g]=b,h.conditions=v}),y=g=>a(m=>{m.conditions=(m.conditions??[]).filter((h,v)=>v!==g)});return i.jsxs("div",{className:"g7le-computed-custom","data-testid":"g7le-computed-custom",style:w1,children:[i.jsx(Ms,{label:`① ${n("layout_editor.computed.custom_step1")}`,children:i.jsx("div",{"data-testid":"g7le-computed-custom-source",children:d?i.jsx(d,{control:{},value:e.source,onChange:g=>a(m=>{m.source=typeof g=="string"?g:void 0}),t:n,candidates:o}):i.jsx("input",{type:"text",value:e.source??"",onChange:g=>a(m=>{m.source=g.target.value}),style:ro})})}),i.jsxs(Ms,{label:`② ${n("layout_editor.computed.custom_step2")}`,children:[i.jsx("select",{"data-testid":"g7le-computed-custom-op",value:e.op,onChange:g=>a(m=>{m.op=g.target.value}),style:ro,children:v1.map(({op:g,labelKey:m})=>i.jsx("option",{value:g,children:n(m)},g))}),e.op==="sum"?i.jsx("div",{"data-testid":"g7le-computed-custom-sumfield",style:{minWidth:0},children:i.jsx(Ve,{value:e.sumField??"",onChange:g=>a(m=>{m.sumField=g}),t:n,candidates:r,placeholder:n("layout_editor.computed.custom_sum_field"),testidPrefix:"g7le-computed-custom-sumfield-chip"})}):null,e.op==="toOptions"?i.jsxs("div",{style:{display:"flex",gap:6},children:[i.jsx("div",{"data-testid":"g7le-computed-custom-valuefield",style:{flex:1,minWidth:0},children:i.jsx(Ve,{value:e.valueField??"",onChange:g=>a(m=>{m.valueField=g}),t:n,candidates:r,placeholder:n("layout_editor.computed.custom_value_field"),testidPrefix:"g7le-computed-custom-valuefield-chip"})}),i.jsx("div",{"data-testid":"g7le-computed-custom-labelfield",style:{flex:1,minWidth:0},children:i.jsx(Ve,{value:e.labelField??"",onChange:g=>a(m=>{m.labelField=g}),t:n,candidates:r,placeholder:n("layout_editor.computed.custom_label_field"),testidPrefix:"g7le-computed-custom-labelfield-chip"})})]}):null,e.op==="nth"?i.jsxs("div",{style:{display:"flex",gap:6},children:[i.jsx("input",{type:"text","data-testid":"g7le-computed-custom-index",value:e.index??"",placeholder:"0",onChange:g=>a(m=>{m.index=g.target.value}),style:ro}),i.jsx("div",{"data-testid":"g7le-computed-custom-prop",style:{flex:1,minWidth:0},children:i.jsx(Ve,{value:e.prop??"",onChange:g=>a(m=>{m.prop=g}),t:n,candidates:r,placeholder:n("layout_editor.computed.custom_prop"),testidPrefix:"g7le-computed-custom-prop-chip"})})]}):null,e.op==="literal"?i.jsxs("div",{style:{display:"flex",gap:6},children:[i.jsxs("select",{"data-testid":"g7le-computed-custom-literalkind",value:e.literalKind??"string",onChange:g=>a(m=>{m.literalKind=g.target.value}),style:ro,children:[i.jsx("option",{value:"string",children:n("layout_editor.computed.kind_string")}),i.jsx("option",{value:"number",children:n("layout_editor.computed.kind_number")}),i.jsx("option",{value:"boolean",children:n("layout_editor.computed.kind_boolean")})]}),i.jsx("div",{"data-testid":"g7le-computed-custom-literalvalue",style:{flex:1,minWidth:0},children:i.jsx(Ve,{value:e.literalValue??"",onChange:g=>a(m=>{m.literalValue=g}),t:n,candidates:r,testidPrefix:"g7le-computed-custom-literalvalue-chip"})})]}):null,e.op==="firstOf"?i.jsxs("div",{style:{display:"flex",flexDirection:"column",gap:6},children:[i.jsx("input",{type:"text","data-testid":"g7le-computed-custom-candidates",value:(e.candidates??[]).join(", "),placeholder:n("layout_editor.computed.custom_candidates"),onChange:g=>a(m=>{m.candidates=g.target.value.split(",").map(h=>h.trim()).filter(Boolean)}),style:ro}),i.jsx("div",{"data-testid":"g7le-computed-custom-fallback",style:{minWidth:0},children:i.jsx(Ve,{value:e.fallback??"",onChange:g=>a(m=>{m.fallback=g}),t:n,candidates:r,placeholder:n("layout_editor.computed.custom_fallback"),testidPrefix:"g7le-computed-custom-fallback-chip"})})]}):null,x1.has(e.op)?i.jsxs("div",{style:$1,children:[l.map((g,m)=>i.jsxs("div",{"data-testid":`g7le-computed-custom-cond-${m}`,style:E1,children:[i.jsx("div",{"data-testid":`g7le-computed-custom-cond-field-${m}`,style:{flex:1,minWidth:0},children:i.jsx(Ve,{value:g.field,onChange:h=>f(m,v=>{v.field=h}),t:n,candidates:r,placeholder:n("layout_editor.computed.cond_field"),testidPrefix:`g7le-computed-custom-cond-field-${m}-chip`})}),i.jsx("select",{value:g.cmp,onChange:h=>f(m,v=>{v.cmp=h.target.value}),style:I1,children:_1.map(h=>i.jsx("option",{value:h,children:h},h))}),i.jsx("div",{"data-testid":`g7le-computed-custom-cond-value-${m}`,style:{flex:1,minWidth:0},children:i.jsx(Ve,{value:g.value,onChange:h=>f(m,v=>{v.value=h}),t:n,candidates:r,placeholder:n("layout_editor.computed.cond_value"),testidPrefix:`g7le-computed-custom-cond-value-${m}-chip`})}),i.jsx("button",{type:"button","data-testid":`g7le-computed-custom-cond-remove-${m}`,onClick:()=>y(m),style:A1,children:"✕"})]},m)),i.jsxs("button",{type:"button","data-testid":"g7le-computed-custom-cond-add",onClick:p,style:T1,children:["+ ",n("layout_editor.computed.cond_add")]})]}):null]}),i.jsxs(Ms,{label:`③ ${n("layout_editor.computed.custom_step3")}`,children:[i.jsx("input",{type:"text","data-testid":"g7le-computed-custom-key",value:e.key,onChange:g=>a(m=>{m.key=g.target.value}),style:{...ro,...c?C1:{}}}),c?i.jsxs("p",{"data-testid":"g7le-computed-custom-key-dup",style:D1,children:["⚠ ",n("layout_editor.computed.key_duplicate")]}):null]})]})}function Ms({label:e,children:t}){return i.jsxs("div",{style:k1,children:[i.jsx("div",{style:S1,children:e}),i.jsx("div",{style:{display:"flex",flexDirection:"column",gap:6,minWidth:0},children:t})]})}function Ls(e){return Vm(e)}const w1={display:"flex",flexDirection:"column",gap:12,padding:10,border:"1px solid #cbd5e1",borderRadius:8,background:"#fff"},k1={display:"flex",flexDirection:"column",gap:4,minWidth:0},S1={fontSize:12,fontWeight:600,color:"#475569"},ro={padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,minWidth:0,width:"100%",boxSizing:"border-box"},C1={border:"1px solid #ef4444"},$1={display:"flex",flexDirection:"column",gap:6,marginTop:4},E1={display:"flex",gap:4,alignItems:"center",minWidth:0},I1={padding:"4px 6px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6},A1={border:"none",background:"transparent",color:"#94a3b8",cursor:"pointer",fontSize:12},T1={alignSelf:"flex-start",padding:"2px 8px",fontSize:11,border:"1px dashed #94a3b8",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},D1={margin:0,fontSize:11,color:"#b45309"};function Ps(e,t,n){return typeof e!="string"?n:e.startsWith("$t:")?t(e.slice(3)):e}function N1(e){if(e&&typeof e=="object"){const t=e.__source;if(t&&typeof t=="object")return t}return null}function z1({computed:e,onChange:t,recipes:n,t:o,sampleContext:r,dataSourceCandidates:s,bindingCandidates:a,computedSource:l={},effectKeys:d=[],onRevert:c}){const p=u.useMemo(()=>xd(n),[n]),[f,y]=u.useState(!1),[g,m]=u.useState(null),h=Object.keys(e),v=u.useCallback((_,C)=>{t({...e,[_]:C})},[e,t]),b=u.useCallback(_=>{const C={...e};delete C[_],t(C)},[e,t]),x=u.useCallback((_,C,E)=>{const S={...e};_&&_!==C&&delete S[_],S[C]=E,t(S)},[e,t]),k=u.useCallback(_=>{const C=kd(_,{});let E=_.id,S=1;for(;E in e;)E=`${_.id}${++S}`;t({...e,[E]:C}),y(!1)},[e,t]),w=u.useCallback(()=>{!g||!g.key||(t({...e,[g.key]:Ls(g)}),m(null),y(!1))},[g,e,t]);return i.jsxs("div",{className:"g7le-computed-form","data-testid":"g7le-computed-form",style:O1,children:[i.jsx("p",{style:B1,children:o("layout_editor.page_settings.computed.heading")}),h.length>0?h.map(_=>i.jsx(M1,{computedKey:_,expr:e[_],recipes:p,rawRecipes:n,t:o,sampleContext:r,dataSourceCandidates:s,bindingCandidates:a,source:l[_],isEffect:d.includes(_),existingKeys:h,onPatchExpr:C=>v(_,C),onRename:(C,E)=>x(_,C,E),onRemove:()=>b(_),onRevert:c?()=>c(_):void 0},_)):i.jsx("p",{"data-testid":"g7le-computed-empty",style:aS,children:o("layout_editor.computed.empty")}),i.jsxs("div",{style:{minWidth:0},children:[i.jsxs("button",{type:"button","data-testid":"g7le-computed-add",onClick:()=>y(_=>!_),style:R1,children:["+ ",o("layout_editor.computed.add")," ▾"]}),f?i.jsxs("div",{"data-testid":"g7le-computed-add-list",style:eS,children:[["common","more"].map(_=>{const C=p.filter(E=>(E.group??"more")===_);return C.length===0?null:i.jsxs("div",{children:[i.jsxs("div",{style:tS,children:["━━ ",o(`layout_editor.computed.group_${_}`)," ━━"]}),C.map(E=>{const S=N1(n?.[E.id]);return i.jsxs("button",{type:"button","data-testid":`g7le-computed-preset-${E.id}`,onClick:()=>k(E),style:nS,children:[i.jsx("span",{style:oS,children:Ps(E.label,o,E.id)}),S&&S.kind!=="core"?i.jsxs("span",{"data-testid":`g7le-computed-preset-source-${E.id}`,style:rS,children:["〔",S.id??S.kind,"〕"]}):null]},E.id)})]},_)}),i.jsxs("div",{style:{borderTop:"1px solid #e2e8f0",marginTop:6,paddingTop:6},children:[i.jsxs("button",{type:"button","data-testid":"g7le-computed-custom-open",onClick:()=>m(g?null:{key:"",op:"count",source:"",conditions:[]}),style:iS,children:["⚙ ",o("layout_editor.computed.custom_open")," ▾"]}),g?i.jsxs("div",{style:{marginTop:6},children:[i.jsx(lu,{model:g,onChange:m,t:o,dataSourceCandidates:s,bindingCandidates:a,existingKeys:h}),i.jsx("div",{"data-testid":"g7le-computed-custom-preview",style:{marginTop:6},children:i.jsx(au,{expr:Ls(g),sampleContext:r,t:o,testIdPrefix:"g7le-computed-custom-preview-eval"})}),i.jsx("button",{type:"button","data-testid":"g7le-computed-custom-commit",disabled:!g.key||h.includes(g.key),onClick:w,style:sS,children:o("layout_editor.computed.custom_add")})]}):null]})]}):null]})]})}function M1({computedKey:e,expr:t,recipes:n,rawRecipes:o,t:r,sampleContext:s,dataSourceCandidates:a,bindingCandidates:l,source:d,isEffect:c,existingKeys:p,onPatchExpr:f,onRename:y,onRemove:g,onRevert:m}){const[h,v]=u.useState(!1),[b,x]=u.useState(!1),k=u.useMemo(()=>Ed(t,n),[t,n]),w=d==="base",_=d==="route-override",C=k.kind==="advanced",E=k.kind==="preset"?n.find($=>$.id===k.recipeId)??null:null,S=E?Ps(E.label,r,e):e,T=($,A)=>{if(k.kind!=="preset"||!E)return;const z={...k.params,[$]:A};f(kd(E,z))};return i.jsxs("div",{"data-testid":`g7le-computed-item-${e}`,style:W1,children:[i.jsxs("div",{style:j1,children:[i.jsx("span",{style:H1,children:S}),i.jsx("code",{style:F1,children:e}),w?i.jsxs("span",{"data-testid":`g7le-computed-source-${e}`,style:K1,children:["〔",r("layout_editor.computed.source_base"),"〕"]}):null,_?i.jsxs("span",{"data-testid":`g7le-computed-overridden-${e}`,style:V1,children:["〔",r("layout_editor.computed.source_overridden"),"〕"]}):null,C?i.jsxs("span",{"data-testid":"g7le-computed-advanced",style:U1,children:["[",r("layout_editor.action.advanced"),"]"]}):null,i.jsx("span",{style:{flex:1}}),i.jsx("button",{type:"button","data-testid":`g7le-computed-code-${e}`,onClick:()=>x($=>!$),style:Os,"aria-label":r("layout_editor.value_tree.show_source"),title:r("layout_editor.value_tree.show_source"),children:""}),C?null:i.jsx("button",{type:"button","data-testid":`g7le-computed-edit-${e}`,onClick:()=>v($=>!$),style:Os,children:r("layout_editor.init_actions.edit")}),i.jsx("button",{type:"button","data-testid":`g7le-computed-remove-${e}`,onClick:g,style:Os,"aria-label":r("layout_editor.init_actions.remove"),children:"✕"})]}),b?i.jsx("pre",{"data-testid":`g7le-computed-code-block-${e}`,style:lS,children:t}):null,i.jsx("div",{"data-testid":`g7le-computed-preview-${e}`,children:i.jsx(au,{expr:t,computedKey:e,sampleContext:s,t:r,isEffect:c,testIdPrefix:`g7le-computed-preview-${e}-eval`})}),_&&m?i.jsxs("div",{style:q1,children:[i.jsxs("span",{style:G1,children:["ⓘ ",r("layout_editor.computed.overridden_hint")]}),i.jsx("button",{type:"button","data-testid":`g7le-computed-revert-${e}`,onClick:m,style:Y1,children:r("layout_editor.computed.revert")})]}):null,h&&!C?i.jsxs("div",{style:X1,children:[w?i.jsxs("p",{"data-testid":`g7le-computed-override-notice-${e}`,style:J1,children:["⚠ ",r("layout_editor.computed.override_notice")]}):null,k.kind==="preset"&&E?i.jsx(i.Fragment,{children:E.params.map($=>i.jsx(P1,{param:$,value:k.params[$.key]??"",t:r,dataSourceCandidates:a,bindingCandidates:l,onChange:A=>T($.key,A)},$.key))}):k.kind==="custom"?i.jsx(lu,{model:{...k.model,key:e},onChange:$=>y($.key,Ls($)),t:r,dataSourceCandidates:a,bindingCandidates:l,existingKeys:p.filter($=>$!==e)}):null]}):null]})}const L1=new Set(["datasource-picker","binding-picker","select","toggle","state-key-picker","page-picker"]);function P1({param:e,value:t,t:n,dataSourceCandidates:o,bindingCandidates:r,onChange:s}){const a=Ps(e.label,n,e.key),l=e.widget??"",c=L1.has(l)?Tn(e.widget):null,p=l==="datasource-picker"||l==="binding-picker"?o:void 0;return i.jsxs("div",{"data-testid":`g7le-computed-param-${e.key}`,style:Z1,children:[i.jsx("span",{style:Q1,children:a}),i.jsx("div",{style:{flex:1,minWidth:0},children:c?i.jsx(c,{control:{...e},value:t,onChange:f=>s(typeof f=="string"?f:""),t:n,candidates:p}):i.jsx(Ve,{value:t,onChange:f=>s(f),t:n,candidates:r,testidPrefix:`g7le-computed-param-${e.key}-chip`})})]})}const O1={display:"flex",flexDirection:"column",gap:12,minWidth:0},B1={margin:0,fontSize:13,fontWeight:700,color:"#0f172a"},W1={border:"1px solid #e2e8f0",borderRadius:8,padding:"8px 10px",display:"flex",flexDirection:"column",gap:6,background:"#f8fafc"},j1={display:"flex",alignItems:"center",gap:6},H1={fontSize:13,fontWeight:600,color:"#0f172a"},F1={fontSize:10,color:"#64748b",fontFamily:"monospace"},K1={fontSize:10,color:"#0891b2",whiteSpace:"nowrap"},V1={fontSize:10,color:"#7c3aed",whiteSpace:"nowrap"},U1={fontSize:10,color:"#92400e",whiteSpace:"nowrap"},q1={display:"flex",alignItems:"center",gap:8,marginTop:4,minWidth:0},G1={fontSize:11,color:"#7c3aed",flex:1,minWidth:0},Y1={border:"1px solid #c4b5fd",borderRadius:6,background:"#f5f3ff",color:"#6d28d9",cursor:"pointer",fontSize:11,padding:"2px 8px",whiteSpace:"nowrap"},Os={border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer",fontSize:11,padding:"2px 6px"},X1={display:"flex",flexDirection:"column",gap:6,paddingTop:6,borderTop:"1px dashed #e2e8f0"},J1={margin:0,fontSize:11,color:"#b45309",background:"#fef3c7",padding:"6px 8px",borderRadius:6},Z1={display:"flex",alignItems:"center",gap:8,minWidth:0},Q1={fontSize:11,color:"#64748b",minWidth:90},R1={padding:"6px 12px",fontSize:12,border:"1px dashed #94a3b8",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},eS={marginTop:6,border:"1px solid #cbd5e1",borderRadius:8,background:"#fff",padding:8,maxHeight:380,overflowY:"auto"},tS={fontSize:11,fontWeight:700,color:"#94a3b8",padding:"6px 4px 2px"},nS={display:"flex",alignItems:"center",gap:6,width:"100%",textAlign:"left",padding:"6px 8px",fontSize:12,border:"none",background:"transparent",cursor:"pointer",borderRadius:6},oS={flex:1,minWidth:0,color:"#0f172a"},rS={fontSize:10,color:"#64748b",whiteSpace:"nowrap"},iS={padding:"6px 10px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#475569",cursor:"pointer"},sS={marginTop:6,padding:"6px 12px",fontSize:12,border:"1px solid #3b82f6",borderRadius:6,background:"#3b82f6",color:"#fff",cursor:"pointer"},aS={margin:0,fontSize:12,color:"#94a3b8"},lS={margin:"2px 0 0",padding:"6px 8px",fontSize:11,fontFamily:"monospace",color:"#0f172a",background:"#f1f5f9",border:"1px solid #e2e8f0",borderRadius:6,whiteSpace:"pre-wrap",wordBreak:"break-all",overflowX:"auto"};function dS(e,t){return t==="any"?!0:e===null||typeof e!="object"?!1:t==="object"?!Array.isArray(e):!0}function Bs({value:e,onChange:t,onValidityChange:n,t:o,shape:r="any",emptyValue:s=void 0,placeholder:a,minHeight:l=80,testidPrefix:d="g7le-json-block",shapeErrorKey:c,invalidErrorKey:p,ariaLabel:f,id:y,validate:g}){const[m,h]=u.useState(()=>jr(e)),[v,b]=u.useState(null),x=u.useRef(!1),k=u.useRef(e);u.useEffect(()=>{if(!x.current&&!Object.is(e,k.current)){if(jr(e)===jr(k.current)){k.current=e;return}h(jr(e)),b(null),k.current=e,n?.(!0)}},[e]);const w=_=>{if(x.current=!0,h(_),_.trim()===""){b(null),k.current=s,n?.(!0),t(s);return}let C;try{C=JSON.parse(_)}catch{b(o(p??"layout_editor.json_block.invalid")),n?.(!1);return}if(!dS(C,r)){b(o(c??"layout_editor.json_block.invalid")),n?.(!1);return}const E=g?.(C)??null;if(E){b(E),n?.(!1);return}b(null),k.current=C,n?.(!0),t(C)};return i.jsxs("div",{className:"g7le-json-block",style:cS,children:[i.jsx("textarea",{id:y,"data-testid":d,style:{...uS,minHeight:l},value:m,spellCheck:!1,"aria-label":f,"aria-invalid":v?!0:void 0,placeholder:a,onChange:_=>w(_.target.value),onBlur:()=>{x.current=!1}}),v?i.jsx("div",{"data-testid":`${d}-error`,style:pS,children:v}):null]})}function jr(e){if(e===void 0)return"";try{return JSON.stringify(e,null,2)??""}catch{return""}}const cS={display:"flex",flexDirection:"column",gap:4,minWidth:0},uS={width:"100%",boxSizing:"border-box",fontFamily:"ui-monospace, SFMono-Regular, Menlo, monospace",fontSize:12,lineHeight:1.5,color:"#0f172a",background:"#fff",border:"1px solid #cbd5e1",borderRadius:6,padding:"6px 8px",resize:"vertical"},pS={fontSize:11,color:"#dc2626"},fS=[{kind:"string",labelKey:"layout_editor.initstate.kind_string"},{kind:"number",labelKey:"layout_editor.initstate.kind_number"},{kind:"boolean",labelKey:"layout_editor.initstate.kind_boolean"},{kind:"null",labelKey:"layout_editor.initstate.kind_null"},{kind:"list",labelKey:"layout_editor.initstate.kind_list"},{kind:"object",labelKey:"layout_editor.initstate.kind_object"}],Ws={local:"initLocal",global:"initGlobal",isolated:"initIsolated"};function gS({raw:e,own:t,patch:n,t:o,isolatedOrphan:r={}}){const s=u.useMemo(()=>{const f=t?.state&&typeof t.state=="object"?t.state:void 0,y=e?.state&&typeof e.state=="object"?e.state:void 0;if(!(!f&&!y))return{...y??{},...f??{}}},[t,e]),{initLocal:a,migrated:l}=u.useMemo(()=>Pw({initLocal:e?.initLocal,state:s}),[e,s]),d=f=>{if(f==="local")return a;const y=e?.[Ws[f]];return y&&typeof y=="object"?y:{}},c=f=>{if(f==="local"){const y=t?.initLocal??{},g=s?Object.keys(s):[];return Array.from(new Set([...Object.keys(y),...g]))}return Object.keys(t?.[Ws[f]]??{})},p=u.useCallback((f,y)=>{const g=Ws[f];f==="local"&&l&&n("state",void 0),n(g,Object.keys(y).length===0?void 0:y)},[n,l]);return i.jsxs("div",{className:"g7le-initstate-form","data-testid":"g7le-initstate-form",style:yS,children:[i.jsx("p",{style:bS,children:o("layout_editor.page_settings.initstate.heading")}),l?i.jsxs("p",{"data-testid":"g7le-initstate-legacy-state",style:mS,children:["ⓘ ",o("layout_editor.initstate.legacy_state")]}):null,i.jsx(js,{scope:"local",label:o("layout_editor.initstate.section_local"),value:d("local"),ownKeys:c("local"),patchSection:p,t:o}),i.jsx(js,{scope:"global",label:o("layout_editor.initstate.section_global"),value:d("global"),ownKeys:c("global"),patchSection:p,t:o}),i.jsx(js,{scope:"isolated",label:o("layout_editor.initstate.section_isolated"),description:o("layout_editor.initstate.section_isolated_desc"),value:d("isolated"),ownKeys:c("isolated"),patchSection:p,t:o,orphan:r}),i.jsxs("p",{style:PS,children:["ⓘ ",o("layout_editor.initstate.isolated_dynamic_hint")]})]})}function js({scope:e,label:t,description:n,value:o,ownKeys:r,patchSection:s,t:a,orphan:l={}}){const[d,c]=u.useState(""),[p,f]=u.useState("string"),[y,g]=u.useState(null),[m,h]=u.useState(!1),v=Object.keys(o),b=v,x=(_,C)=>{s(e,{...o,[_]:C})},k=_=>{const C={...o};delete C[_],s(e,C)},w=()=>{const _=d.trim();if(!_){g(a("layout_editor.initstate.name_required"));return}if(!Is(_)){g(a("layout_editor.initstate.name_invalid"));return}if(_ in o){g(a("layout_editor.initstate.name_duplicate"));return}const C={string:"",number:0,boolean:!1,null:null,list:[],object:{}};s(e,{...o,[_]:C[p]}),c(""),g(null)};return i.jsxs("div",{"data-testid":`g7le-initstate-section-${e}`,style:vS,children:[i.jsxs("div",{style:_S,children:[i.jsx("div",{style:xS,children:t}),i.jsx("button",{type:"button","data-testid":`g7le-initstate-${e}-mode-toggle`,onClick:()=>h(_=>!_),style:kS,children:a(m?"layout_editor.json_block.mode_block":"layout_editor.json_block.mode_code")})]}),n?i.jsxs("p",{style:wS,children:["ⓘ ",n]}):null,m?i.jsx(Bs,{value:o,shape:"object",emptyValue:{},onChange:_=>s(e,_??{}),t:a,testidPrefix:`g7le-initstate-${e}-code`,placeholder:`{ "keyword": "", "page": 1 -}`,shapeErrorKey:"layout_editor.json_block.must_be_object",validate:_=>{const S=Object.keys(_??{}).filter(E=>!Is(E));return S.length?a("layout_editor.json_block.invalid_keys",{keys:S.join(", ")}):null}}):i.jsxs(i.Fragment,{children:[v.length===0?i.jsx("p",{"data-testid":`g7le-initstate-${e}-empty`,style:zS,children:a("layout_editor.initstate.no_values")}):v.map(_=>{const E=zw(b,r,_)==="inherited",C=e==="isolated"&&l[_];return i.jsx(fS,{scope:e,itemKey:_,value:o[_],inherited:E,isOrphan:!!C,onChangeValue:N=>x(_,N),onRemove:()=>k(_),t:a},_)}),i.jsxs("div",{style:ES,children:[i.jsx("input",{type:"text","data-testid":`g7le-initstate-${e}-add-name`,value:d,placeholder:a("layout_editor.initstate.value_name"),onChange:_=>{c(_.target.value),y&&g(null)},onKeyDown:_=>{_.key==="Enter"&&(_.preventDefault(),w())},"aria-invalid":y?!0:void 0,style:{...IS,...y?AS:null}}),i.jsx("select",{"data-testid":`g7le-initstate-${e}-add-kind`,value:p,onChange:_=>f(_.target.value),"aria-label":a("layout_editor.initstate.value_kind"),style:DS,children:uS.map(_=>i.jsx("option",{value:_.kind,children:a(_.labelKey)},_.kind))}),i.jsxs("button",{type:"button","data-testid":`g7le-initstate-${e}-add`,onClick:w,style:NS,children:["+ ",a("layout_editor.initstate.add_value")]})]}),y?i.jsx("p",{"data-testid":`g7le-initstate-${e}-add-error`,style:TS,children:y}):null]})]})}function fS({scope:e,itemKey:t,value:n,inherited:o,isOrphan:r,onChangeValue:s,onRemove:a,t:l}){const[d,c]=u.useState(!1);let p;try{p=JSON.stringify(n,null,2)??String(n)}catch{p=String(n)}return i.jsxs("div",{"data-testid":`g7le-initstate-${e}-item-${t}`,style:wS,children:[i.jsxs("div",{style:kS,children:[o?i.jsx("span",{"data-testid":`g7le-initstate-inherited-${t}`,style:SS,children:"🔗"}):null,i.jsx("code",{style:CS,children:t}),i.jsx("span",{style:{flex:1}}),i.jsx("button",{type:"button","data-testid":`g7le-initstate-code-${t}`,onClick:()=>c(f=>!f),style:Hs,"aria-label":l("layout_editor.value_tree.show_source"),title:l("layout_editor.value_tree.show_source"),children:""}),o?i.jsx("button",{type:"button","data-testid":`g7le-initstate-revert-${t}`,onClick:a,style:Hs,children:l("layout_editor.initstate.revert")}):i.jsx("button",{type:"button","data-testid":`g7le-initstate-remove-${t}`,onClick:a,style:Hs,"aria-label":l("layout_editor.init_actions.remove"),children:"✕"})]}),d?i.jsx("pre",{"data-testid":`g7le-initstate-code-block-${t}`,style:LS,children:p}):null,i.jsx(Nn,{value:n,onChange:s,t:l,path:t,scope:e}),r?i.jsxs("p",{"data-testid":`g7le-initstate-isolated-orphan-${t}`,style:$S,children:["⚠ ",l("layout_editor.initstate.isolated_orphan")]}):null]})}const gS={display:"flex",flexDirection:"column",gap:14,minWidth:0},hS={margin:0,fontSize:13,fontWeight:700,color:"#0f172a"},yS={margin:0,fontSize:11,color:"#1d4ed8",background:"#eff6ff",padding:"6px 8px",borderRadius:6},bS={display:"flex",flexDirection:"column",gap:8,padding:10,border:"1px solid #e2e8f0",borderRadius:10,minWidth:0},mS={display:"flex",alignItems:"center",justifyContent:"space-between",gap:8},vS={fontSize:12,fontWeight:700,color:"#475569"},_S={margin:0,fontSize:11,color:"#94a3b8"},xS={padding:"2px 8px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},wS={display:"flex",flexDirection:"column",gap:4,padding:"6px 8px",border:"1px solid #f1f5f9",borderRadius:6,minWidth:0},kS={display:"flex",alignItems:"center",gap:6},SS={fontSize:12},CS={fontSize:12,color:"#0f172a",fontFamily:"monospace"},Hs={border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer",fontSize:11,padding:"2px 6px"},$S={margin:0,fontSize:11,color:"#b45309",background:"#fef3c7",padding:"4px 6px",borderRadius:6},ES={display:"flex",gap:6,alignItems:"center"},IS={padding:"4px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6},AS={border:"1px solid #dc2626"},TS={margin:0,fontSize:11,color:"#dc2626"},DS={padding:"4px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#0f172a"},NS={padding:"2px 10px",fontSize:11,border:"1px dashed #94a3b8",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},zS={margin:0,fontSize:12,color:"#94a3b8"},MS={margin:0,fontSize:11,color:"#94a3b8"},LS={margin:"2px 0",padding:"6px 8px",fontSize:11,fontFamily:"monospace",color:"#0f172a",background:"#f1f5f9",border:"1px solid #e2e8f0",borderRadius:6,whiteSpace:"pre-wrap",wordBreak:"break-all",overflowX:"auto"},PS={self:"layout_editor.error_rows.source_self",inherited:"layout_editor.error_rows.source_inherited",template:"layout_editor.error_rows.source_template",none:"layout_editor.error_rows.source_none"};function Fs({value:e,onChange:t,t:n,codes:o,mode:r="local",sourceOf:s,renderActionList:a,testIdPrefix:l="g7le-error-rows"}){const d=o??Object.keys(e),c=u.useCallback((f,y)=>{t({...e,[f]:y})},[e,t]),p=u.useCallback(f=>{const y={...e};delete y[f],t(y)},[e,t]);return i.jsx("div",{className:l,"data-testid":l,children:d.map(f=>{const y=e[f],g=y!==void 0,m=r==="badge"&&s?s(f):null;return i.jsxs("div",{"data-testid":`${l}-row-${f}`,style:{border:"1px solid #e2e8f0",borderRadius:8,padding:"8px 10px",marginBottom:6},children:[i.jsxs("div",{style:{display:"flex",alignItems:"center",gap:8},children:[i.jsx("strong",{"data-testid":`${l}-code-${f}`,style:{fontSize:13},children:f==="default"?n("layout_editor.error_rows.default"):f}),m?i.jsxs("span",{"data-testid":`${l}-source-${f}`,style:{fontSize:11,padding:"2px 6px",borderRadius:4,background:"#f1f5f9",color:"#475569"},children:["〔",n(PS[m]),"〕"]}):null,g?i.jsx("button",{type:"button","data-testid":`${l}-clear-${f}`,onClick:()=>p(f),style:{marginLeft:"auto",fontSize:12},children:n("layout_editor.error_rows.clear")}):i.jsx("span",{style:{marginLeft:"auto",fontSize:12,color:"#94a3b8"},children:n("layout_editor.error_rows.no_action")})]}),m==="inherited"||m==="template"?i.jsxs("div",{"data-testid":`${l}-override-${f}`,style:{marginTop:6,display:"flex",alignItems:"flex-start",gap:8},children:[i.jsxs("p",{style:{margin:0,flex:1,fontSize:12,color:"#64748b"},children:["ⓘ ",n(m==="inherited"?"layout_editor.error_rows.override_inherited":"layout_editor.error_rows.override_template")]}),g?i.jsx("button",{type:"button","data-testid":`${l}-revert-${f}`,onClick:()=>p(f),style:{fontSize:12,padding:"2px 8px",border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer",whiteSpace:"nowrap"},children:n("layout_editor.error_rows.revert")}):i.jsx("button",{type:"button","data-testid":`${l}-override-here-${f}`,onClick:()=>c(f,{}),style:{fontSize:12,padding:"2px 8px",border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer",whiteSpace:"nowrap"},children:n("layout_editor.error_rows.override_here")})]}):null,f==="default"?i.jsxs("p",{"data-testid":`${l}-default-warn-${f}`,style:{margin:"6px 0 0",fontSize:12,color:"#b45309"},children:["⚠ ",n("layout_editor.error_rows.default_warning")]}):null,i.jsx("div",{style:{marginTop:6},children:a?a(f,y,h=>c(f,h)):i.jsx("pre",{"data-testid":`${l}-json-${f}`,style:{margin:0,fontSize:12,color:"#475569"},children:g?JSON.stringify(y):""})})]},f)})})}function OS(e){return[...e.filter(n=>n!=="maintenance"),"default"]}function BS({value:e,onChange:t,t:n,errorConfigCodes:o=[],ownCodes:r=[],templateCodes:s=[],recipes:a,pageCandidates:l,modalCandidates:d,dataSourceCandidates:c,stateKeyCandidates:p}){const f=u.useMemo(()=>wo(a),[a]),y=u.useMemo(()=>OS(o),[o]),g=f.length>0,m=u.useMemo(()=>jl("error",n),[n]),h=u.useMemo(()=>({pageCandidates:l,dataSourceCandidates:c,stateKeyCandidates:p,modalCandidates:d,bindingCandidates:m}),[l,c,p,d,m]),v=u.useCallback(b=>r.includes(b)?"self":b in e?"inherited":s.includes(b)?"template":"none",[r,e,s]);return g?i.jsxs("div",{className:"g7le-error-form","data-testid":"g7le-error-form",style:du,children:[i.jsx("p",{style:cu,children:n("layout_editor.page_settings.error.heading")}),i.jsxs("p",{style:jS,children:["ⓘ ",n("layout_editor.error.intro")]}),i.jsx(Fs,{value:e,onChange:t,t:n,codes:y,mode:"badge",sourceOf:v,testIdPrefix:"g7le-error-rows",renderActionList:(b,x,k)=>i.jsx(WS,{code:b,action:x,recipes:a,normalized:f,t:n,pools:h,onPatch:k})})]}):i.jsxs("div",{className:"g7le-error-form","data-testid":"g7le-error-form",style:du,children:[i.jsx("p",{style:cu,children:n("layout_editor.page_settings.error.heading")}),i.jsxs("p",{"data-testid":"g7le-error-degrade",style:HS,children:["ⓘ ",n("layout_editor.error.degrade")]}),i.jsx(Fs,{value:e,onChange:t,t:n,codes:y,mode:"badge",sourceOf:v,testIdPrefix:"g7le-error-rows"})]})}function WS({code:e,action:t,recipes:n,normalized:o,t:r,pools:s,onPatch:a}){const[l,d]=u.useState(!1),c=Array.isArray(t)?t[0]:t,p=typeof c?.handler=="string"?c.handler:"",f=o.find(x=>x.id===p)??null,y=c?Yn(c,o):null,g=y?.kind==="preset"?y.values:{},m=p==="sequence"||p==="parallel",h=x=>{if(x===""){a({});return}const k=o.find(w=>w.id===x);k&&a(xr(k,{}))},v=["","showErrorPage","navigate","openModal","toast","setState","sequence","parallel"],b=m&&c?Array.isArray(c.params?.actions)?c.params.actions:[]:[];return i.jsxs("div",{"data-testid":`g7le-error-action-${e}`,style:FS,children:[i.jsxs("div",{style:{display:"flex",alignItems:"center",gap:6},children:[i.jsx("select",{"data-testid":`g7le-error-handler-${e}`,value:p,onChange:x=>h(x.target.value),style:{...KS,flex:1},children:v.map(x=>i.jsx("option",{value:x,children:r(x===""?"layout_editor.error.handler_none":`layout_editor.error.handler_${x}`)},x))}),p!==""?i.jsx("button",{type:"button","data-testid":`g7le-error-code-${e}`,onClick:()=>d(x=>!x),"aria-label":r("layout_editor.action_list.view_code"),title:r("layout_editor.action_list.view_code"),style:l?VS:uu,children:""}):null]}),m?i.jsx("div",{"data-testid":`g7le-error-actions-${e}`,children:i.jsx(Gt,{actions:b,onChange:x=>a({handler:p,params:{actions:x}}),t:r,recipes:n,candidatePools:s,chipContext:"error",testIdPrefix:`g7le-error-seq-${e}`})}):f&&c?i.jsx(Ts,{raw:c,recipe:f,values:g,t:r,pools:s,onChange:x=>a(x),testIdPrefix:`g7le-error-action-${e}-edit`}):null,l&&c?i.jsx("pre",{"data-testid":`g7le-error-code-view-${e}`,style:US,children:JSON.stringify(t,null,2)}):null]})}const du={display:"flex",flexDirection:"column",gap:12,minWidth:0},cu={margin:0,fontSize:13,fontWeight:700,color:"#0f172a"},jS={margin:0,fontSize:11,color:"#94a3b8"},HS={margin:0,fontSize:12,color:"#b45309",background:"#fef3c7",padding:"8px 10px",borderRadius:6},FS={display:"flex",flexDirection:"column",gap:6,minWidth:0},KS={padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6},uu={flex:"0 0 auto",width:26,height:26,display:"inline-flex",alignItems:"center",justifyContent:"center",fontSize:11,border:"1px solid #e2e8f0",borderRadius:6,background:"#fff",color:"#64748b",cursor:"pointer",fontFamily:"ui-monospace, monospace"},VS={...uu,background:"#0f172a",borderColor:"#0f172a",color:"#e2e8f0"},US={margin:"4px 0 0",padding:8,background:"#0f172a",color:"#e2e8f0",borderRadius:6,fontSize:12,overflow:"auto",fontFamily:"ui-monospace, monospace"};function Hr(e){const t=e.trim(),n=/^\{\{([\s\S]*)\}\}$/.exec(t);return n?n[1].trim():t}function qS(e){let t=e;for(;t.length>=2&&t[0]==="("&&t[t.length-1]===")";){let n=0,o=!0;for(let r=0;r!!o&&typeof o.value=="string"&&typeof o.expr=="string").map(o=>({value:o.value,label:typeof o.label=="string"?o.label:void 0,params:Array.isArray(o.params)?o.params:[],expr:Hr(o.expr),aliases:pu(o.aliases)}));const n=[];for(const[o,r]of Object.entries(e)){if(o==="comment"||o==="operators"||!r||typeof r!="object")continue;const s=r;typeof s.expr=="string"&&n.push({value:o,label:typeof s.label=="string"?s.label:void 0,params:Array.isArray(s.params)?s.params:[],expr:Hr(s.expr),aliases:pu(s.aliases)})}return n}function pu(e){if(!Array.isArray(e))return[];const t=[];for(const n of e){if(typeof n!="string")continue;const o=Hr(n);o.trim().length>0&&t.push(o)}return t}function YS(e){const t=e.params.length*1e3;return Fr(e.expr).replace(/\{\w+\}/g,"").length-t}function XS(e){return e.map((t,n)=>({op:t,idx:n,score:YS(t)})).sort((t,n)=>n.score-t.score||t.idx-n.idx).map(t=>t.op)}function JS(e,t){return e.replace(/\{(\w+)\}/g,(n,o)=>{const r=t[o];return r===void 0?n:r})}function ZS(e,t){const n=t.find(o=>o.value===e.operator);return n?JS(n.expr,e.params??{}):null}function fu(e,t,n){const o=e.map(a=>ZS(a,n)).filter(a=>typeof a=="string"&&a.trim().length>0);if(o.length===0)return"";if(o.length===1)return`{{ ${o[0]} }}`;const r=t==="or"?" || ":" && ";return`{{ ${o.map(a=>`(${a})`).join(r)} }}`}function gu(e,t){const n=Fr(e);for(const o of XS(t)){if(o.params.length===0){for(const r of[o.expr,...o.aliases])if(Fr(r)===n)return{operator:o.value,params:{}};continue}for(const r of[o.expr,...o.aliases]){const s=QS(o,r,n);if(s)return s}}return null}function QS(e,t,n){const o=t.trim(),r=[];let s="",a=0;for(;a=|<=|>|<|\?|:|\(|\)|!/.test(y))return null;return{operator:e.value,params:c}}function RS(e){const t=new Set,n=/(['"])\{(\w+)\}\1/g;let o;for(;(o=n.exec(e))!==null;)t.add(o[2]);return t}function eC(e,t){if(typeof e!="string"||e.trim()==="")return null;const n=Hr(e),o=gu(n,t);if(o)return{clauses:[o],combinator:"and"};const r=n.includes(") || (")?"or":(n.includes(") && ("),"and"),s=r==="or"?") || (":") && (";if(!n.startsWith("(")||!n.endsWith(")")||!n.includes(s))return null;const l=n.slice(1,-1).split(s),d=[];for(const c of l){const p=gu(c.trim(),t);if(!p)return null;d.push(p)}return{clauses:d,combinator:r}}const hu=Lt(),Mn=hu.responsiveManager,To=hu.BREAKPOINT_PRESETS;function yu(e){const t=e.groupPrefixes;return{group:typeof e.group=="string"?e.group:void 0,apply:e.apply&&typeof e.apply=="object"?e.apply:void 0,options:Array.isArray(e.options)?e.options:void 0,scale:Array.isArray(e.scale)?e.scale:void 0,groupPrefixes:Array.isArray(t)?t.filter(n=>typeof n=="string"&&n.length>0):void 0,groupTokens:Array.isArray(e.groupTokens)?e.groupTokens.filter(n=>typeof n=="string"&&n.length>0):void 0}}function bu(e){const t={...e??{}},n=t.style&&typeof t.style=="object"&&!Array.isArray(t.style)?{...t.style}:{};return t.style=n,{props:t,style:n}}function Kr(e){return typeof e!="string"?[]:e.split(/\s+/).filter(t=>t.length>0)}function mu(e,t=!1){const n=new Set,o=[],r=s=>{if(s){for(const a of s.tokens??[])n.add(a);s.tokenTemplate&&s.tokenTemplate.includes("{value}")&&o.push(s.tokenTemplate.split("{value}")[0]??"")}};r(e.apply);for(const s of e.options??[])r(s.apply);for(const s of e.groupPrefixes??[])o.push(s);for(const s of e.groupTokens??[])n.add(s);return s=>{if(t){if(!Yt(s))return!1;const a=qs(s);return n.has(a)?!0:o.some(l=>l.length>0&&a.startsWith(l))}return Yt(s)?!1:n.has(s)?!0:o.some(a=>a.length>0&&s.startsWith(a))}}function tC(e,t,n,o,r=!1){const s=mu(t,r),a=Kr(e.className).filter(p=>!s(p)),l=[];if(Array.isArray(n.tokens))l.push(...n.tokens);else if(n.tokenTemplate&&n.tokenTemplate.includes("{value}")&&o!=null){const p=String(o).split(/\s+/).filter(f=>f.length>0);for(const f of p)l.push(n.tokenTemplate.replace("{value}",f))}const d=r?l.map(Su):l,c=[...a,...d];c.length===0?delete e.className:e.className=c.join(" ")}const Ks={url:"backgroundImage",size:"backgroundSize",repeat:"backgroundRepeat",position:"backgroundPosition"};function nC(e){return!e||typeof e!="object"||Array.isArray(e)?!1:Object.keys(Ks).some(t=>t in e)}function oC(e){return/^url\(/i.test(e.trim())?e:`url(${e})`}function vu(e){const t=/^url\(\s*(['"]?)(.*?)\1\s*\)$/i.exec(e.trim());return t?t[2]:e}function rC(e){return e.includes("backgroundImage")}function Vs(e,t){if(e!=null){if(typeof e=="object"){const n=e[t];return typeof n=="string"||typeof n=="number"?n:void 0}return e}}function iC(e){const t=e.backgroundImage,n=Vs(e.backgroundSize,"size"),o=Vs(e.backgroundRepeat,"repeat"),r=Vs(e.backgroundPosition,"position");let s;if(typeof t=="string"?s=vu(t):t&&typeof t=="object"?s=t.url:s=void 0,s===void 0&&n===void 0&&o===void 0&&r===void 0)return;const a={};return s!==void 0&&(a.url=typeof s=="string"?vu(s):s),n!==void 0&&(a.size=n),o!==void 0&&(a.repeat=o),r!==void 0&&(a.position=r),a}function sC(e,t,n){const o=(r,s)=>{s==null||s===""?delete e[r]:e[r]=s};if(Array.isArray(t.props)){if(!t.values&&nC(n)){for(const r of t.props){const s=Object.keys(Ks).find(d=>Ks[d]===r);let a=s?n[s]:void 0;a!==null&&typeof a=="object"&&(a=void 0);const l=r==="backgroundImage"&&typeof a=="string"&&a!==""?oC(a):a;o(r,l)}return}for(const r of t.props){const s=t.values&&r in t.values?t.values[r]:n;o(r,s)}return}typeof t.prop=="string"&&o(t.prop,t.value!==void 0?t.value:n)}function aC(e,t,n){if(typeof t.varName!="string")return;const o=t.value!==void 0?t.value:n;o==null||o===""?delete e[t.varName]:e[t.varName]=o}function lC(e,t,n){if(typeof t.propKey!="string")return;const o=t.value!==void 0?t.value:n;o==null||o===""?delete e[t.propKey]:e[t.propKey]=o}function fn(e,t,n,o=Wt){const r=yu(t),s=o.colorScheme==="dark";let a,l=!1;if(r.options){const y=r.options.find(g=>g.value===n);l=y!==void 0,a=y?.apply??r.apply}else a=r.apply;const d=n==null||n===""||l&&a===void 0,p=(d?r.apply:a)?.type==="classToken"||(r.options?.some(y=>y.apply?.type==="classToken")??!1);if(s&&!p)return e;const f=o.breakpoint!=="base"&&p?(()=>{const y=e.props??{};return typeof y.className=="string"?y.className:void 0})():void 0;return ku(e,o,(y,g)=>{if(f!==void 0&&typeof y.className!="string"&&(y.className=f),d){const m=mu(r,s),h=Kr(y.className).filter(v=>!m(v));if(h.length===0?delete y.className:y.className=h.join(" "),!s&&r.apply){const v=r.apply;if(v.type==="styleProp")for(const b of v.props??(v.prop?[v.prop]:[]))delete g[b];else v.type==="cssVar"&&v.varName?delete g[v.varName]:v.type==="propValue"&&v.propKey&&delete y[v.propKey]}}else if(a)switch(a.type){case"classToken":tC(y,r,a,n,s);break;case"styleProp":sC(g,a,n);break;case"cssVar":aC(g,a,n);break;case"propValue":lC(y,a,n);break}})}function Vr(e,t){const n=Kr(e);return t?n.filter(Yt).map(qs):n.filter(o=>!Yt(o))}function dC(e,t,n,o){const r=new Set(t.filter(l=>typeof l=="string"&&l.length>0)),s=l=>o?Yt(l)?r.has(qs(l)):!1:Yt(l)?!1:r.has(l),a=Kr(e).filter(l=>!s(l));return n&&n.length>0&&a.push(o?Su(n):n),a.join(" ").trim()}function _u(e,t,n,o){if(e.options&&e.options.some(s=>s.apply)){const s=Vr(t.className,o),a=[];for(const l of e.options)l.apply&&uC(l.apply,s,n,t)&&a.push(l.value);if(a.length>0)return{value:a[0],matched:!0,conflict:a.length>1?!0:void 0};if(!e.apply)return{value:void 0,matched:!1}}const r=e.apply;if(!r)return{value:void 0,matched:!1};switch(r.type){case"styleProp":{if(!r.values&&Array.isArray(r.props)&&rC(r.props)){const l=iC(n);return l===void 0?{value:void 0,matched:!1}:{value:l,matched:!0}}const s=r.prop??r.props?.[0];if(!s)return{value:void 0,matched:!1};const a=n[s];return a===void 0?{value:void 0,matched:!1}:{value:a,matched:!0}}case"cssVar":{if(!r.varName)return{value:void 0,matched:!1};const s=n[r.varName];return s===void 0?{value:void 0,matched:!1}:{value:s,matched:!0}}case"propValue":{if(!r.propKey)return{value:void 0,matched:!1};const s=t[r.propKey];return s===void 0?{value:void 0,matched:!1}:{value:s,matched:!0}}case"classToken":{const s=Vr(t.className,o);if(r.tokenTemplate&&r.tokenTemplate.includes("{value}")){const[a,l]=r.tokenTemplate.split("{value}");if((a??"")===""&&(l??"")===""&&e.groupPrefixes?.length){const d=s.filter(c=>e.groupPrefixes.some(p=>c.startsWith(p)));return d.length>0?{value:d.join(" "),matched:!0}:{value:void 0,matched:!1}}for(const d of s)if(d.startsWith(a)&&d.endsWith(l??""))return{value:d.slice(a.length,l?d.length-l.length:void 0),matched:!0}}return Array.isArray(r.tokens)&&r.tokens.every(a=>s.includes(a))?{value:!0,matched:!0}:{value:void 0,matched:!1}}default:return{value:void 0,matched:!1}}}function xu(e){return e.style&&typeof e.style=="object"&&!Array.isArray(e.style)?e.style:{}}function Do(e,t,n=Wt){const o=yu(t),r=n.colorScheme==="dark";if(r&&!Us(o.apply)&&!o.options?.some(m=>Us(m.apply)))return{value:void 0,matched:!1,darkReadonly:!0};const s=yC(e,n),a=xu(s),l=_u(o,s,a,r);if(n.breakpoint==="base")return l;const d=l.matched?l.value:void 0,c=e.props??{},p=xu(c),f=_u(o,c,p,r),y=l.matched&&l.value!==void 0,g=y&&!cC(l.value,f.value);return{value:y?l.value:f.value,matched:g?l.matched:f.matched,conflict:g?l.conflict:f.conflict,scopedValue:g?l.value:void 0,baseFallback:g?void 0:f.value,scopedRawValue:d}}function cC(e,t){if(e===t)return!0;if(e===null||t===null||typeof e!="object"||typeof t!="object")return!1;try{return JSON.stringify(e)===JSON.stringify(t)}catch{return!1}}function uC(e,t,n,o){switch(e.type){case"classToken":return Array.isArray(e.tokens)&&e.tokens.length>0?e.tokens.every(r=>t.includes(r)):!1;case"styleProp":{const r=e.prop??e.props?.[0];if(!r)return!1;const s=e.value;return s!==void 0?n[r]===s:n[r]!==void 0}case"cssVar":return e.varName?n[e.varName]!==void 0:!1;case"propValue":return e.propKey?e.value!==void 0?o[e.propKey]===e.value:o[e.propKey]!==void 0:!1;default:return!1}}const Wt={colorScheme:"base",breakpoint:"base"},No="dark:";function pC(e){return e==="base"?!0:Mn.parseRange(e)!==null}function wu(e){return e in To}function Ur(e,t,n){if(e==="custom"){if(typeof n=="number"){const o=t.responsive;if(o&&typeof o=="object"){const r=Mn.getMatchingKey(o,n);if(r)return r}}return"base"}return e}function fC(e){return!!e&&typeof e=="object"&&Array.isArray(e.children)}function gC(e,t,n){let o,r,s;if(e!=="custom"&&e in To){o=e;const l=To[e];r=l.min,s=l.max}else if(e!=="custom"){const l=Mn.parseRange(e);if(l)o=e,r=l.min,s=l.max;else{const d=typeof n=="number"?n:Mn.getWidth();o=`${d}-${d}`,r=d,s=d}}else{const l=typeof n=="number"?n:Mn.getWidth();r=l,s=l,o=`${l}-${l}`}const a=t.responsive;if(a&&typeof a=="object"){let l=null,d=1/0;for(const c of Object.keys(a)){if(!fC(a[c]))continue;const p=Mn.parseRange(c);if(p){if(p.min===r&&p.max===s)return{mode:"merge",key:c};if(p.min<=r&&p.max>=s){const f=p.max-p.min;ft.length>0):[]}function vC(e,t){if(t.colorScheme==="dark")return ku(e,t,l=>{const d=qr(l.className).filter(c=>!Yt(c));d.length===0?delete l.className:l.className=d.join(" ")});if(t.breakpoint==="base")return e;const o=t.breakpoint;if(!e.responsive?.[o])return e;const r={...e.responsive},s={...r[o]};delete s.props,Object.keys(s).length===0?delete r[o]:r[o]=s;const a={...e};return Object.keys(r).length===0?delete a.responsive:a.responsive=r,a}function Gs(e,t){if(t.colorScheme==="dark"){const c=t.breakpoint==="base"?e.props:e.responsive?.[t.breakpoint]?.props;return qr(c?.className).some(Yt)}if(t.breakpoint==="base")return!1;const o=e.responsive?.[t.breakpoint];if(!o)return!1;const r=o.props;if(Object.keys(o).filter(c=>c!=="props").length>0)return!0;if(!r)return!1;if(Object.keys(r).filter(c=>c!=="className").length>0)return!0;const l=new Set(qr(e.props?.className).filter(c=>!Yt(c))),d=qr(r.className).filter(c=>!Yt(c));return d.length!==l.size?!0:d.some(c=>!l.has(c))}function Cu(e,t,n=""){return typeof e!="string"?n:e.startsWith("$t:")?t(e.slice(3)):e}function $u(e,t,n){if(n.breakpoint!=="base")return mC(e,n,t.trim());const o={...e};if(o.props&&typeof o.props=="object"&&"if"in o.props){const r={...o.props};delete r.if,o.props=r}return t.trim()===""?delete o.if:o.if=t,o}function Eu({node:e,spec:t,t:n,onPatchNode:o,dataSourceCandidates:r,stateKeyCandidates:s,scope:a=Wt}){const l=u.useMemo(()=>GS(t?.conditionRecipes),[t]),d=a.breakpoint!=="base"?bC(e,a):e.if??e.props?.if,c=u.useMemo(()=>eC(d,l),[d,l]),p=c?.clauses??[],f=c?.combinator??"and",y=typeof d=="string"&&d.trim()!==""&&c===null;if(l.length===0)return i.jsx("div",{"data-testid":"g7le-condition-no-recipes",style:_C,children:n("layout_editor.visibility.no_recipes")});const g=(k,w)=>{o($u(e,fu(k,w,l),a))},m=(k,w)=>{g(p.map((_,S)=>S===k?w:_),f)},h=k=>{g(p.filter((w,_)=>_!==k),f)},v=k=>{const w=l[0];g([...p,{operator:w.value,params:{}}],k)},b=k=>{if(k==="datasource-picker")return r;if(k==="state-key-picker")return s};if(y)return i.jsxs("div",{className:"g7le-condition-advanced","data-testid":"g7le-condition-advanced",style:{display:"flex",flexDirection:"column",gap:8},children:[i.jsx("div",{style:AC,children:n("layout_editor.visibility.advanced_preserved")}),i.jsx("code",{"data-testid":"g7le-condition-advanced-expr",style:Au,children:String(d)}),i.jsx("button",{type:"button","data-testid":"g7le-condition-clear-advanced",onClick:()=>o($u(e,"",a)),style:TC,children:n("layout_editor.visibility.clear")})]});const x=fu(p,f,l);return i.jsxs("div",{className:"g7le-condition-builder","data-testid":"g7le-condition-builder",style:{display:"flex",flexDirection:"column",gap:10},children:[i.jsx("div",{style:xC,children:n("layout_editor.visibility.show_when")}),p.map((k,w)=>{const _=l.find(S=>S.value===k.operator)??l[0];return i.jsxs("div",{className:"g7le-condition-clause","data-testid":`g7le-condition-clause-${w}`,style:wC,children:[i.jsxs("div",{style:{display:"flex",gap:6,alignItems:"center"},children:[w>0&&i.jsx("span",{"data-testid":`g7le-condition-combinator-${w}`,style:SC,children:n(f==="or"?"layout_editor.visibility.or":"layout_editor.visibility.and")}),i.jsx("select",{"data-testid":`g7le-condition-select-${w}`,value:k.operator,onChange:S=>m(w,{operator:S.target.value,params:{}}),style:kC,children:l.map(S=>i.jsx("option",{value:S.value,children:Cu(S.label,n,S.value)},S.value))}),i.jsx("button",{type:"button","data-testid":`g7le-condition-remove-${w}`,"aria-label":n("layout_editor.visibility.remove"),onClick:()=>h(w),style:CC,children:"✕"})]}),_.params.map(S=>{const E=Tn(S.widget);return i.jsxs("div",{className:"g7le-condition-param","data-testid":`g7le-condition-param-${w}-${S.key}`,style:$C,children:[i.jsx("span",{style:EC,children:Cu(S.label,n,S.key)}),i.jsx("div",{style:{flex:1},children:E?i.jsx(E,{control:{...S},value:k.params[S.key],onChange:C=>m(w,{operator:k.operator,params:{...k.params,[S.key]:C===void 0?"":String(C)}}),t:n,candidates:b(S.widget)}):i.jsx("span",{style:{fontSize:11,color:"#94a3b8"},children:n("layout_editor.property_modal.unsupported_widget")})})]},S.key)})]},w)}),i.jsxs("div",{style:{display:"flex",gap:6},children:[i.jsxs("button",{type:"button","data-testid":"g7le-condition-add-and",onClick:()=>v("and"),style:Iu,children:["+ ",n("layout_editor.visibility.and")]}),i.jsxs("button",{type:"button","data-testid":"g7le-condition-add-or",onClick:()=>v("or"),style:Iu,children:["+ ",n("layout_editor.visibility.or")]})]}),x!==""&&i.jsxs("div",{"data-testid":"g7le-condition-preview",style:IC,children:[i.jsx("span",{style:{fontSize:11,color:"#64748b"},children:n("layout_editor.visibility.preview")}),i.jsx("code",{style:Au,children:x})]})]})}const _C={fontSize:12,color:"#94a3b8",padding:"16px 0",textAlign:"center"},xC={fontSize:12,fontWeight:600,color:"#0f172a"},wC={border:"1px solid #e2e8f0",borderRadius:8,padding:10,display:"flex",flexDirection:"column",gap:8,background:"#f8fafc"},kC={flex:1,padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff"},SC={fontSize:11,fontWeight:700,color:"#2563eb",background:"#dbeafe",padding:"2px 8px",borderRadius:999},CC={border:"none",background:"transparent",color:"#94a3b8",cursor:"pointer",fontSize:12},Iu={padding:"5px 12px",fontSize:12,border:"1px dashed #94a3b8",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},$C={display:"flex",alignItems:"center",gap:8},EC={fontSize:11,color:"#64748b",minWidth:64},IC={display:"flex",flexDirection:"column",gap:4,padding:10,background:"#f1f5f9",borderRadius:6},Au={fontSize:11,color:"#0f172a",fontFamily:"monospace",wordBreak:"break-all"},AC={fontSize:11,color:"#92400e",background:"#fef3c7",padding:"6px 8px",borderRadius:6},TC={padding:"5px 12px",fontSize:12,border:"1px solid #fecaca",borderRadius:6,background:"#fff",color:"#dc2626",cursor:"pointer",alignSelf:"flex-start"};function DC(e){return typeof e=="string"?e:e===void 0?"":JSON.stringify(e)}function NC(e){const t=e.trim();if(t==="")return"";if(/^-?\d+(\.\d+)?$/.test(t)||t==="true"||t==="false"||t==="null")try{return JSON.parse(t)}catch{return e}return e}function Tu(e){return!e||typeof e!="object"||Array.isArray(e)?[]:Object.entries(e).map(([t,n])=>({key:t,value:DC(n)}))}function Ys(e){const t={};for(const n of e){const o=n.key.trim();o!==""&&(t[o]=NC(n.value))}return t}function Du(e){return!e||typeof e!="object"||Array.isArray(e)?!1:Object.values(e).some(t=>t!==null&&typeof t=="object")}function zC(e){const t={};return typeof e.if=="string"&&e.if.trim().length>0&&(t.if=e.if),t}function MC(e,t){const n={...t},o=e.if;return typeof o=="string"&&o.trim().length>0?n.if=o:delete n.if,n}const LC=["api","static","route_params","query_params","websocket"],PC=["GET","POST","PUT","PATCH","DELETE"],OC=["none","optional","required"],BC=["progressive","blocking","background"],WC=["application/json","multipart/form-data"],jC=["public","private","presence"];function HC(e){if(!e||typeof e!="object")return{own:[],inherited:[]};const t=e.__editor,n=t&&typeof t=="object"&&!Array.isArray(t)?t.original:void 0,o=n&&typeof n=="object"&&!Array.isArray(n)?n.data_sources:e.data_sources,r=Array.isArray(o)?o.filter(c=>c&&typeof c=="object"):[],s=new Set(r.map(c=>typeof c.id=="string"?c.id:"").filter(Boolean)),a=e.data_sources,d=(Array.isArray(a)?a.filter(c=>c&&typeof c=="object"):[]).filter(c=>typeof c.id=="string"&&!s.has(c.id));return{own:r,inherited:d}}function FC(e,t){const n=(o,r="")=>typeof o=="string"?o:r;return{index:t,id:n(e.id),type:n(e.type,"api"),label_key:n(e.label_key),endpoint:n(e.endpoint),method:n(e.method,"GET"),auth_mode:n(e.auth_mode,"none"),loading_strategy:n(e.loading_strategy,"progressive"),auto_fetch:e.auto_fetch!==!1,paramRows:Tu(e.params),paramsRaw:Du(e.params),paramsRawValue:e.params&&typeof e.params=="object"&&!Array.isArray(e.params)?e.params:{},paramsRawValid:!0,fallbackEnabled:e.fallback!==void 0,fallbackValue:e.fallback,if:n(e.if),contentType:n(e.contentType),refetchOnMount:e.refetchOnMount===!0,onSuccess:Array.isArray(e.onSuccess)?e.onSuccess.filter(o=>!!o&&typeof o=="object"):[],onError:Array.isArray(e.onError)?e.onError.filter(o=>!!o&&typeof o=="object"):[],errorHandling:e.errorHandling&&typeof e.errorHandling=="object"&&!Array.isArray(e.errorHandling)?e.errorHandling:{},channel:n(e.channel),event:n(e.event),channel_type:n(e.channel_type,"public"),target_source:n(e.target_source),onReceive:Array.isArray(e.onReceive)?e.onReceive.filter(o=>!!o&&typeof o=="object"):[]}}function KC(){return{index:-1,id:"",type:"api",label_key:"",endpoint:"",method:"GET",auth_mode:"none",loading_strategy:"progressive",auto_fetch:!0,paramRows:[],paramsRaw:!1,paramsRawValue:{},paramsRawValid:!0,fallbackEnabled:!1,fallbackValue:void 0,if:"",contentType:"",refetchOnMount:!1,onSuccess:[],onError:[],errorHandling:{},channel:"",event:"",channel_type:"public",target_source:"",onReceive:[]}}function VC(e,t,n,o){const r=e.id.trim();if(!r)return{error:o("layout_editor.data_sources.error.id_required")};if(!/^[A-Za-z_][\w]*$/.test(r))return{error:o("layout_editor.data_sources.error.id_invalid")};if(n.has(r))return{error:o("layout_editor.data_sources.error.id_duplicate",{id:r})};const s=t?{...t}:{};if(s.id=r,s.type=e.type,e.label_key.trim()?s.label_key=e.label_key.trim():delete s.label_key,e.type==="api"||e.type==="websocket"?(e.endpoint.trim()?s.endpoint=e.endpoint.trim():delete s.endpoint,s.method=e.method,s.auth_mode=e.auth_mode,s.loading_strategy=e.loading_strategy):(delete s.endpoint,delete s.method),s.auto_fetch=e.auto_fetch,e.paramsRaw){if(!e.paramsRawValid)return{error:o("layout_editor.data_sources.error.params_json")};const a=e.paramsRawValue;a&&typeof a=="object"&&Object.keys(a).length>0?s.params=a:delete s.params}else{const a=Ys(e.paramRows);Object.keys(a).length>0?s.params=a:delete s.params}return e.fallbackEnabled?s.fallback=e.fallbackValue===void 0?{}:e.fallbackValue:delete s.fallback,e.if.trim()?s.if=e.if.trim():delete s.if,e.type==="api"&&e.contentType?s.contentType=e.contentType:delete s.contentType,e.refetchOnMount?s.refetchOnMount=!0:delete s.refetchOnMount,e.onSuccess.length>0?s.onSuccess=e.onSuccess:delete s.onSuccess,e.onError.length>0?s.onError=e.onError:delete s.onError,Object.keys(e.errorHandling).length>0?s.errorHandling=e.errorHandling:delete s.errorHandling,e.type==="websocket"?(e.channel?s.channel=e.channel:delete s.channel,e.event?s.event=e.event:delete s.event,s.channel_type=e.channel_type,e.target_source?s.target_source=e.target_source:delete s.target_source,e.onReceive.length>0?s.onReceive=e.onReceive:delete s.onReceive):(delete s.channel,delete s.event,delete s.channel_type,delete s.target_source,delete s.onReceive),{entry:s}}const jt={width:"100%",padding:"6px 8px",fontSize:13,border:"1px solid #cbd5e1",borderRadius:6,outline:"none",boxSizing:"border-box",fontFamily:"inherit",color:"#0f172a",background:"#ffffff"},ct={display:"block",fontSize:12,fontWeight:600,color:"#475569",marginBottom:4},ut={marginBottom:12},rn={padding:"6px 12px",fontSize:13,fontWeight:500,borderRadius:6,border:"1px solid #cbd5e1",background:"#ffffff",color:"#0f172a",cursor:"pointer"},Nu={...rn,background:"#2563eb",borderColor:"#2563eb",color:"#ffffff",fontWeight:600},zo={marginTop:12,padding:10,border:"1px solid #e2e8f0",borderRadius:8,background:"#f8fafc"},Mo={fontSize:12,fontWeight:700,color:"#475569",marginBottom:8},UC={margin:"0 0 6px",fontSize:11,color:"#94a3b8"},zu={marginTop:8},Mu={alignSelf:"flex-start",padding:"2px 0",fontSize:11,border:"none",background:"none",color:"#64748b",cursor:"pointer"},Lu={margin:0,padding:8,background:"#0f172a",color:"#e2e8f0",borderRadius:6,fontSize:11,lineHeight:1.5,overflow:"auto",fontFamily:"ui-monospace, monospace"};function qC({code:e,action:t,recipes:n,pools:o,t:r,onPatch:s}){const[a,l]=u.useState(!1),d=u.useMemo(()=>wo(n),[n]),c=Array.isArray(t)?t[0]:t,p=typeof c?.handler=="string"?c.handler:"",f=d.find(x=>x.id===p)??null,y=c?Yn(c,d):null,g=y?.kind==="preset"?y.values:{},m=p==="sequence"||p==="parallel",h=["","showErrorPage","navigate","openModal","toast","setState","sequence","parallel"],v=x=>{if(x===""){s({});return}const k=d.find(w=>w.id===x);s(k?xr(k,{}):{handler:x})},b=m&&c?Array.isArray(c.params?.actions)?c.params.actions:[]:[];return i.jsxs("div",{"data-testid":`g7le-ds-errorhandling-${e}`,style:{display:"flex",flexDirection:"column",gap:6},children:[i.jsxs("div",{style:{display:"flex",alignItems:"center",gap:6},children:[i.jsx("select",{"data-testid":`g7le-ds-eh-handler-${e}`,style:{...jt,flex:1},value:p,onChange:x=>v(x.target.value),children:h.map(x=>i.jsx("option",{value:x,children:r(x===""?"layout_editor.error.handler_none":`layout_editor.error.handler_${x}`)},x))}),p!==""?i.jsx("button",{type:"button","data-testid":`g7le-ds-eh-code-${e}`,onClick:()=>l(x=>!x),"aria-label":r("layout_editor.action_list.view_code"),title:r("layout_editor.action_list.view_code"),style:a?GC:Pu,children:""}):null]}),m?i.jsx("div",{"data-testid":`g7le-ds-eh-actions-${e}`,children:i.jsx(Gt,{actions:b,onChange:x=>s({handler:p,params:{actions:x}}),t:r,recipes:n,candidatePools:o,chipContext:"error",testIdPrefix:`g7le-ds-eh-seq-${e}`})}):f&&c?i.jsx(Ts,{raw:c,recipe:f,values:g,t:r,pools:o,onChange:x=>s(x),testIdPrefix:`g7le-ds-eh-action-${e}-edit`}):null,a&&c?i.jsx("pre",{"data-testid":`g7le-ds-eh-code-view-${e}`,style:YC,children:JSON.stringify(t,null,2)}):null]})}const Pu={flex:"0 0 auto",width:26,height:26,display:"inline-flex",alignItems:"center",justifyContent:"center",fontSize:11,border:"1px solid #e2e8f0",borderRadius:6,background:"#fff",color:"#64748b",cursor:"pointer",fontFamily:"ui-monospace, monospace"},GC={...Pu,background:"#0f172a",borderColor:"#0f172a",color:"#e2e8f0"},YC={margin:"4px 0 0",padding:8,background:"#0f172a",color:"#e2e8f0",borderRadius:6,fontSize:12,overflow:"auto",fontFamily:"ui-monospace, monospace"};function XC({raw:e,onChange:t,t:n,resolveLabel:o,onClose:r,actionRecipes:s,errorRecipes:a,conditionSpec:l,bindingCandidates:d,pageCandidates:c,dataSourceCandidates:p,stateKeyCandidates:f}){const y=o??n,g=u.useMemo(()=>HC(e),[e]),m=g.inherited,[h,v]=u.useState(g.own),[b,x]=u.useState(null),[k,w]=u.useState(null),[_,S]=u.useState(!1),[E,C]=u.useState(!1),[N,$]=u.useState(()=>new Set),A=u.useCallback(I=>{$(P=>{const F=new Set(P);return F.has(I)?F.delete(I):F.add(I),F})},[]),z=u.useMemo(()=>({pageCandidates:c,dataSourceCandidates:p,stateKeyCandidates:f,bindingCandidates:d}),[c,p,f,d]),D=u.useCallback(I=>{v(I),t([...m,...I],I)},[m,t]),O=u.useCallback(()=>{w(null),x(KC())},[]),M=u.useCallback((I,P)=>{w(null),x(FC(I,P))},[]),B=u.useCallback(I=>{const P=h.filter((F,G)=>G!==I);D(P)},[h,D]),T=u.useCallback(()=>{x(null),w(null)},[]),H=u.useCallback(()=>{if(!b)return;const I=new Set;h.forEach((V,Y)=>{Y!==b.index&&typeof V.id=="string"&&I.add(V.id)}),m.forEach(V=>{typeof V.id=="string"&&I.add(V.id)});const P=b.index>=0?h[b.index]??null:null,F=VC(b,P,I,n);if("error"in F){w(F.error);return}const G=b.index>=0?h.map((V,Y)=>Y===b.index?F.entry:V):[...h,F.entry];D(G),x(null),w(null)},[b,h,m,D,n]),L=b?.type==="api"||b?.type==="websocket";return i.jsxs("div",{className:"g7le-data-sources","data-testid":"g7le-data-sources-panel",style:{flex:"1 1 auto",minHeight:0,display:"flex",flexDirection:"column",fontSize:13,color:"#0f172a",boxSizing:"border-box"},children:[i.jsxs("div",{className:"g7le-data-sources__header",style:{flex:"0 0 auto",padding:"16px 16px 12px",borderBottom:"1px solid #e2e8f0"},children:[i.jsxs("div",{style:{display:"flex",alignItems:"center",justifyContent:"space-between",marginBottom:8},children:[i.jsx("h2",{style:{margin:0,fontSize:16,fontWeight:700},children:n("layout_editor.data_sources.title")}),i.jsx("button",{type:"button","data-testid":"g7le-data-sources-close",onClick:r,"aria-label":n("layout_editor.data_sources.close"),style:{...rn,padding:"4px 10px"},children:"✕"})]}),i.jsx("p",{style:{margin:0,fontSize:12,color:"#64748b",lineHeight:1.5},children:n("layout_editor.data_sources.description")})]}),i.jsxs("div",{className:"g7le-data-sources__body",style:{flex:"1 1 auto",minHeight:0,overflowY:"auto",padding:16},children:[b&&i.jsxs("div",{className:"g7le-data-sources__form","data-testid":"g7le-data-sources-form",children:[i.jsx("div",{style:{fontSize:13,fontWeight:700,marginBottom:12},children:b.index>=0?n("layout_editor.data_sources.form.edit_title"):n("layout_editor.data_sources.form.add_title")}),i.jsxs("div",{style:ut,children:[i.jsx("label",{style:ct,htmlFor:"g7le-ds-id",children:n("layout_editor.data_sources.field.id")}),i.jsx("input",{id:"g7le-ds-id","data-testid":"g7le-data-sources-field-id",style:jt,value:b.id,onChange:I=>x({...b,id:I.target.value}),placeholder:"products"})]}),i.jsxs("div",{style:ut,children:[i.jsx("label",{style:ct,htmlFor:"g7le-ds-label",children:n("layout_editor.data_sources.field.label_key")}),i.jsx(vt,{value:b.label_key,onChange:I=>x({...b,label_key:I??""}),t:n,placeholder:n("layout_editor.data_sources.field.label_key_placeholder"),testidPrefix:"g7le-data-sources-field-label",candidates:d,enableExpressionTree:!0,expressionTreeCollapsible:!0}),i.jsx("div",{style:{fontSize:11,color:"#94a3b8",marginTop:4},children:n("layout_editor.data_sources.field.label_key_hint")})]}),i.jsxs("div",{style:ut,children:[i.jsx("label",{style:ct,htmlFor:"g7le-ds-type",children:n("layout_editor.data_sources.field.type")}),i.jsx("select",{id:"g7le-ds-type","data-testid":"g7le-data-sources-field-type",style:jt,value:b.type,onChange:I=>x({...b,type:I.target.value}),children:LC.map(I=>i.jsx("option",{value:I,children:n(`layout_editor.data_sources.ds_type.${I}`)},I))})]}),L&&i.jsxs(i.Fragment,{children:[i.jsxs("div",{style:ut,children:[i.jsx("label",{style:ct,children:n("layout_editor.data_sources.field.endpoint")}),i.jsx("div",{"data-testid":"g7le-data-sources-field-endpoint",children:i.jsx(Ve,{value:b.endpoint,onChange:I=>x({...b,endpoint:I}),t:n,candidates:d,placeholder:"/api/shop/products/{{route.id}}",testidPrefix:"g7le-ds-endpoint"})})]}),i.jsxs("div",{style:{display:"flex",gap:12},children:[i.jsxs("div",{style:{...ut,flex:1},children:[i.jsx("label",{style:ct,htmlFor:"g7le-ds-method",children:n("layout_editor.data_sources.field.method")}),i.jsx("select",{id:"g7le-ds-method","data-testid":"g7le-data-sources-field-method",style:jt,value:b.method,onChange:I=>x({...b,method:I.target.value}),children:PC.map(I=>i.jsx("option",{value:I,children:I},I))})]}),i.jsxs("div",{style:{...ut,flex:1},children:[i.jsx("label",{style:ct,htmlFor:"g7le-ds-auth",children:n("layout_editor.data_sources.field.auth_mode")}),i.jsx("select",{id:"g7le-ds-auth","data-testid":"g7le-data-sources-field-auth",style:jt,value:b.auth_mode,onChange:I=>x({...b,auth_mode:I.target.value}),children:OC.map(I=>i.jsx("option",{value:I,children:n(`layout_editor.data_sources.auth_mode.${I}`)},I))})]})]}),i.jsxs("div",{style:ut,children:[i.jsx("label",{style:ct,htmlFor:"g7le-ds-loading",children:n("layout_editor.data_sources.field.loading_strategy")}),i.jsx("select",{id:"g7le-ds-loading","data-testid":"g7le-data-sources-field-loading",style:jt,value:b.loading_strategy,onChange:I=>x({...b,loading_strategy:I.target.value}),children:BC.map(I=>i.jsx("option",{value:I,children:n(`layout_editor.data_sources.loading_strategy.${I}`)},I))})]}),b.type==="api"&&i.jsxs("div",{style:ut,children:[i.jsx("label",{style:ct,htmlFor:"g7le-ds-contenttype",children:n("layout_editor.data_sources.field.content_type")}),i.jsx("select",{id:"g7le-ds-contenttype","data-testid":"g7le-ds-contenttype",style:jt,value:b.contentType||"application/json",onChange:I=>x({...b,contentType:I.target.value}),children:WC.map(I=>i.jsx("option",{value:I,children:I},I))})]})]}),i.jsx("div",{style:ut,children:i.jsxs("label",{style:{...ct,display:"flex",alignItems:"center",gap:8,cursor:"pointer"},children:[i.jsx("input",{type:"checkbox","data-testid":"g7le-data-sources-field-autofetch",checked:b.auto_fetch,onChange:I=>x({...b,auto_fetch:I.target.checked})}),n("layout_editor.data_sources.field.auto_fetch")]})}),i.jsx("div",{style:ut,children:i.jsxs("label",{style:{...ct,display:"flex",alignItems:"center",gap:8,cursor:"pointer"},children:[i.jsx("input",{type:"checkbox","data-testid":"g7le-ds-refetch",checked:b.refetchOnMount,onChange:I=>x({...b,refetchOnMount:I.target.checked})}),n("layout_editor.data_sources.field.refetch_on_mount")]})}),i.jsxs("div",{style:ut,children:[i.jsxs("div",{style:{display:"flex",alignItems:"center",justifyContent:"space-between"},children:[i.jsx("label",{style:ct,htmlFor:"g7le-ds-params",children:n("layout_editor.data_sources.field.params")}),i.jsx("button",{type:"button","data-testid":"g7le-ds-params-mode-toggle",onClick:()=>{if(b.paramsRaw){if(b.paramsRawValid){const I=b.paramsRawValue;I&&typeof I=="object"&&!Array.isArray(I)&&!Du(I)&&x({...b,paramsRaw:!1,paramRows:Tu(I)})}}else{const I=Ys(b.paramRows);x({...b,paramsRaw:!0,paramsRawValue:I,paramsRawValid:!0})}},style:{...rn,padding:"2px 8px",fontSize:11},children:b.paramsRaw?n("layout_editor.data_sources.field.params_mode_block"):n("layout_editor.data_sources.field.params_mode_code")})]}),b.paramsRaw?i.jsx(Bs,{id:"g7le-ds-params",value:b.paramsRawValue,shape:"object",emptyValue:{},minHeight:64,testidPrefix:"g7le-data-sources-field-params",placeholder:`{ +}`,shapeErrorKey:"layout_editor.json_block.must_be_object",validate:_=>{const C=Object.keys(_??{}).filter(E=>!Is(E));return C.length?a("layout_editor.json_block.invalid_keys",{keys:C.join(", ")}):null}}):i.jsxs(i.Fragment,{children:[v.length===0?i.jsx("p",{"data-testid":`g7le-initstate-${e}-empty`,style:LS,children:a("layout_editor.initstate.no_values")}):v.map(_=>{const E=Lw(b,r,_)==="inherited",S=e==="isolated"&&l[_];return i.jsx(hS,{scope:e,itemKey:_,value:o[_],inherited:E,isOrphan:!!S,onChangeValue:T=>x(_,T),onRemove:()=>k(_),t:a},_)}),i.jsxs("div",{style:AS,children:[i.jsx("input",{type:"text","data-testid":`g7le-initstate-${e}-add-name`,value:d,placeholder:a("layout_editor.initstate.value_name"),onChange:_=>{c(_.target.value),y&&g(null)},onKeyDown:_=>{_.key==="Enter"&&(_.preventDefault(),w())},"aria-invalid":y?!0:void 0,style:{...TS,...y?DS:null}}),i.jsx("select",{"data-testid":`g7le-initstate-${e}-add-kind`,value:p,onChange:_=>f(_.target.value),"aria-label":a("layout_editor.initstate.value_kind"),style:zS,children:fS.map(_=>i.jsx("option",{value:_.kind,children:a(_.labelKey)},_.kind))}),i.jsxs("button",{type:"button","data-testid":`g7le-initstate-${e}-add`,onClick:w,style:MS,children:["+ ",a("layout_editor.initstate.add_value")]})]}),y?i.jsx("p",{"data-testid":`g7le-initstate-${e}-add-error`,style:NS,children:y}):null]})]})}function hS({scope:e,itemKey:t,value:n,inherited:o,isOrphan:r,onChangeValue:s,onRemove:a,t:l}){const[d,c]=u.useState(!1);let p;try{p=JSON.stringify(n,null,2)??String(n)}catch{p=String(n)}return i.jsxs("div",{"data-testid":`g7le-initstate-${e}-item-${t}`,style:SS,children:[i.jsxs("div",{style:CS,children:[o?i.jsx("span",{"data-testid":`g7le-initstate-inherited-${t}`,style:$S,children:"🔗"}):null,i.jsx("code",{style:ES,children:t}),i.jsx("span",{style:{flex:1}}),i.jsx("button",{type:"button","data-testid":`g7le-initstate-code-${t}`,onClick:()=>c(f=>!f),style:Hs,"aria-label":l("layout_editor.value_tree.show_source"),title:l("layout_editor.value_tree.show_source"),children:""}),o?i.jsx("button",{type:"button","data-testid":`g7le-initstate-revert-${t}`,onClick:a,style:Hs,children:l("layout_editor.initstate.revert")}):i.jsx("button",{type:"button","data-testid":`g7le-initstate-remove-${t}`,onClick:a,style:Hs,"aria-label":l("layout_editor.init_actions.remove"),children:"✕"})]}),d?i.jsx("pre",{"data-testid":`g7le-initstate-code-block-${t}`,style:OS,children:p}):null,i.jsx(Nn,{value:n,onChange:s,t:l,path:t,scope:e}),r?i.jsxs("p",{"data-testid":`g7le-initstate-isolated-orphan-${t}`,style:IS,children:["⚠ ",l("layout_editor.initstate.isolated_orphan")]}):null]})}const yS={display:"flex",flexDirection:"column",gap:14,minWidth:0},bS={margin:0,fontSize:13,fontWeight:700,color:"#0f172a"},mS={margin:0,fontSize:11,color:"#1d4ed8",background:"#eff6ff",padding:"6px 8px",borderRadius:6},vS={display:"flex",flexDirection:"column",gap:8,padding:10,border:"1px solid #e2e8f0",borderRadius:10,minWidth:0},_S={display:"flex",alignItems:"center",justifyContent:"space-between",gap:8},xS={fontSize:12,fontWeight:700,color:"#475569"},wS={margin:0,fontSize:11,color:"#94a3b8"},kS={padding:"2px 8px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},SS={display:"flex",flexDirection:"column",gap:4,padding:"6px 8px",border:"1px solid #f1f5f9",borderRadius:6,minWidth:0},CS={display:"flex",alignItems:"center",gap:6},$S={fontSize:12},ES={fontSize:12,color:"#0f172a",fontFamily:"monospace"},Hs={border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer",fontSize:11,padding:"2px 6px"},IS={margin:0,fontSize:11,color:"#b45309",background:"#fef3c7",padding:"4px 6px",borderRadius:6},AS={display:"flex",gap:6,alignItems:"center"},TS={padding:"4px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6},DS={border:"1px solid #dc2626"},NS={margin:0,fontSize:11,color:"#dc2626"},zS={padding:"4px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#0f172a"},MS={padding:"2px 10px",fontSize:11,border:"1px dashed #94a3b8",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},LS={margin:0,fontSize:12,color:"#94a3b8"},PS={margin:0,fontSize:11,color:"#94a3b8"},OS={margin:"2px 0",padding:"6px 8px",fontSize:11,fontFamily:"monospace",color:"#0f172a",background:"#f1f5f9",border:"1px solid #e2e8f0",borderRadius:6,whiteSpace:"pre-wrap",wordBreak:"break-all",overflowX:"auto"},BS={self:"layout_editor.error_rows.source_self",inherited:"layout_editor.error_rows.source_inherited",template:"layout_editor.error_rows.source_template",none:"layout_editor.error_rows.source_none"};function Fs({value:e,onChange:t,t:n,codes:o,mode:r="local",sourceOf:s,renderActionList:a,testIdPrefix:l="g7le-error-rows"}){const d=o??Object.keys(e),c=u.useCallback((f,y)=>{t({...e,[f]:y})},[e,t]),p=u.useCallback(f=>{const y={...e};delete y[f],t(y)},[e,t]);return i.jsx("div",{className:l,"data-testid":l,children:d.map(f=>{const y=e[f],g=y!==void 0,m=r==="badge"&&s?s(f):null;return i.jsxs("div",{"data-testid":`${l}-row-${f}`,style:{border:"1px solid #e2e8f0",borderRadius:8,padding:"8px 10px",marginBottom:6},children:[i.jsxs("div",{style:{display:"flex",alignItems:"center",gap:8},children:[i.jsx("strong",{"data-testid":`${l}-code-${f}`,style:{fontSize:13},children:f==="default"?n("layout_editor.error_rows.default"):f}),m?i.jsxs("span",{"data-testid":`${l}-source-${f}`,style:{fontSize:11,padding:"2px 6px",borderRadius:4,background:"#f1f5f9",color:"#475569"},children:["〔",n(BS[m]),"〕"]}):null,g?i.jsx("button",{type:"button","data-testid":`${l}-clear-${f}`,onClick:()=>p(f),style:{marginLeft:"auto",fontSize:12},children:n("layout_editor.error_rows.clear")}):i.jsx("span",{style:{marginLeft:"auto",fontSize:12,color:"#94a3b8"},children:n("layout_editor.error_rows.no_action")})]}),m==="inherited"||m==="template"?i.jsxs("div",{"data-testid":`${l}-override-${f}`,style:{marginTop:6,display:"flex",alignItems:"flex-start",gap:8},children:[i.jsxs("p",{style:{margin:0,flex:1,fontSize:12,color:"#64748b"},children:["ⓘ ",n(m==="inherited"?"layout_editor.error_rows.override_inherited":"layout_editor.error_rows.override_template")]}),g?i.jsx("button",{type:"button","data-testid":`${l}-revert-${f}`,onClick:()=>p(f),style:{fontSize:12,padding:"2px 8px",border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer",whiteSpace:"nowrap"},children:n("layout_editor.error_rows.revert")}):i.jsx("button",{type:"button","data-testid":`${l}-override-here-${f}`,onClick:()=>c(f,{}),style:{fontSize:12,padding:"2px 8px",border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer",whiteSpace:"nowrap"},children:n("layout_editor.error_rows.override_here")})]}):null,f==="default"?i.jsxs("p",{"data-testid":`${l}-default-warn-${f}`,style:{margin:"6px 0 0",fontSize:12,color:"#b45309"},children:["⚠ ",n("layout_editor.error_rows.default_warning")]}):null,i.jsx("div",{style:{marginTop:6},children:a?a(f,y,h=>c(f,h)):i.jsx("pre",{"data-testid":`${l}-json-${f}`,style:{margin:0,fontSize:12,color:"#475569"},children:g?JSON.stringify(y):""})})]},f)})})}function WS(e){return[...e.filter(n=>n!=="maintenance"),"default"]}function jS({value:e,onChange:t,t:n,errorConfigCodes:o=[],ownCodes:r=[],templateCodes:s=[],recipes:a,pageCandidates:l,modalCandidates:d,dataSourceCandidates:c,stateKeyCandidates:p}){const f=u.useMemo(()=>wo(a),[a]),y=u.useMemo(()=>WS(o),[o]),g=f.length>0,m=u.useMemo(()=>jl("error",n),[n]),h=u.useMemo(()=>({pageCandidates:l,dataSourceCandidates:c,stateKeyCandidates:p,modalCandidates:d,bindingCandidates:m}),[l,c,p,d,m]),v=u.useCallback(b=>r.includes(b)?"self":b in e?"inherited":s.includes(b)?"template":"none",[r,e,s]);return g?i.jsxs("div",{className:"g7le-error-form","data-testid":"g7le-error-form",style:du,children:[i.jsx("p",{style:cu,children:n("layout_editor.page_settings.error.heading")}),i.jsxs("p",{style:FS,children:["ⓘ ",n("layout_editor.error.intro")]}),i.jsx(Fs,{value:e,onChange:t,t:n,codes:y,mode:"badge",sourceOf:v,testIdPrefix:"g7le-error-rows",renderActionList:(b,x,k)=>i.jsx(HS,{code:b,action:x,recipes:a,normalized:f,t:n,pools:h,onPatch:k})})]}):i.jsxs("div",{className:"g7le-error-form","data-testid":"g7le-error-form",style:du,children:[i.jsx("p",{style:cu,children:n("layout_editor.page_settings.error.heading")}),i.jsxs("p",{"data-testid":"g7le-error-degrade",style:KS,children:["ⓘ ",n("layout_editor.error.degrade")]}),i.jsx(Fs,{value:e,onChange:t,t:n,codes:y,mode:"badge",sourceOf:v,testIdPrefix:"g7le-error-rows"})]})}function HS({code:e,action:t,recipes:n,normalized:o,t:r,pools:s,onPatch:a}){const[l,d]=u.useState(!1),c=Array.isArray(t)?t[0]:t,p=typeof c?.handler=="string"?c.handler:"",f=o.find(x=>x.id===p)??null,y=c?Yn(c,o):null,g=y?.kind==="preset"?y.values:{},m=p==="sequence"||p==="parallel",h=x=>{if(x===""){a({});return}const k=o.find(w=>w.id===x);k&&a(xr(k,{}))},v=["","showErrorPage","navigate","openModal","toast","setState","sequence","parallel"],b=m&&c?Array.isArray(c.params?.actions)?c.params.actions:[]:[];return i.jsxs("div",{"data-testid":`g7le-error-action-${e}`,style:VS,children:[i.jsxs("div",{style:{display:"flex",alignItems:"center",gap:6},children:[i.jsx("select",{"data-testid":`g7le-error-handler-${e}`,value:p,onChange:x=>h(x.target.value),style:{...US,flex:1},children:v.map(x=>i.jsx("option",{value:x,children:r(x===""?"layout_editor.error.handler_none":`layout_editor.error.handler_${x}`)},x))}),p!==""?i.jsx("button",{type:"button","data-testid":`g7le-error-code-${e}`,onClick:()=>d(x=>!x),"aria-label":r("layout_editor.action_list.view_code"),title:r("layout_editor.action_list.view_code"),style:l?qS:uu,children:""}):null]}),m?i.jsx("div",{"data-testid":`g7le-error-actions-${e}`,children:i.jsx(Gt,{actions:b,onChange:x=>a({handler:p,params:{actions:x}}),t:r,recipes:n,candidatePools:s,chipContext:"error",testIdPrefix:`g7le-error-seq-${e}`})}):f&&c?i.jsx(Ts,{raw:c,recipe:f,values:g,t:r,pools:s,onChange:x=>a(x),testIdPrefix:`g7le-error-action-${e}-edit`}):null,l&&c?i.jsx("pre",{"data-testid":`g7le-error-code-view-${e}`,style:GS,children:JSON.stringify(t,null,2)}):null]})}const du={display:"flex",flexDirection:"column",gap:12,minWidth:0},cu={margin:0,fontSize:13,fontWeight:700,color:"#0f172a"},FS={margin:0,fontSize:11,color:"#94a3b8"},KS={margin:0,fontSize:12,color:"#b45309",background:"#fef3c7",padding:"8px 10px",borderRadius:6},VS={display:"flex",flexDirection:"column",gap:6,minWidth:0},US={padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6},uu={flex:"0 0 auto",width:26,height:26,display:"inline-flex",alignItems:"center",justifyContent:"center",fontSize:11,border:"1px solid #e2e8f0",borderRadius:6,background:"#fff",color:"#64748b",cursor:"pointer",fontFamily:"ui-monospace, monospace"},qS={...uu,background:"#0f172a",borderColor:"#0f172a",color:"#e2e8f0"},GS={margin:"4px 0 0",padding:8,background:"#0f172a",color:"#e2e8f0",borderRadius:6,fontSize:12,overflow:"auto",fontFamily:"ui-monospace, monospace"};function Hr(e){const t=e.trim(),n=/^\{\{([\s\S]*)\}\}$/.exec(t);return n?n[1].trim():t}function YS(e){let t=e;for(;t.length>=2&&t[0]==="("&&t[t.length-1]===")";){let n=0,o=!0;for(let r=0;r!!o&&typeof o.value=="string"&&typeof o.expr=="string").map(o=>({value:o.value,label:typeof o.label=="string"?o.label:void 0,params:Array.isArray(o.params)?o.params:[],expr:Hr(o.expr),aliases:pu(o.aliases)}));const n=[];for(const[o,r]of Object.entries(e)){if(o==="comment"||o==="operators"||!r||typeof r!="object")continue;const s=r;typeof s.expr=="string"&&n.push({value:o,label:typeof s.label=="string"?s.label:void 0,params:Array.isArray(s.params)?s.params:[],expr:Hr(s.expr),aliases:pu(s.aliases)})}return n}function pu(e){if(!Array.isArray(e))return[];const t=[];for(const n of e){if(typeof n!="string")continue;const o=Hr(n);o.trim().length>0&&t.push(o)}return t}function JS(e){const t=e.params.length*1e3;return Fr(e.expr).replace(/\{\w+\}/g,"").length-t}function ZS(e){return e.map((t,n)=>({op:t,idx:n,score:JS(t)})).sort((t,n)=>n.score-t.score||t.idx-n.idx).map(t=>t.op)}function QS(e,t){return e.replace(/\{(\w+)\}/g,(n,o)=>{const r=t[o];return r===void 0?n:r})}function RS(e,t){const n=t.find(o=>o.value===e.operator);return n?QS(n.expr,e.params??{}):null}function fu(e,t,n){const o=e.map(a=>RS(a,n)).filter(a=>typeof a=="string"&&a.trim().length>0);if(o.length===0)return"";if(o.length===1)return`{{ ${o[0]} }}`;const r=t==="or"?" || ":" && ";return`{{ ${o.map(a=>`(${a})`).join(r)} }}`}function gu(e,t){const n=Fr(e);for(const o of ZS(t)){if(o.params.length===0){for(const r of[o.expr,...o.aliases])if(Fr(r)===n)return{operator:o.value,params:{}};continue}for(const r of[o.expr,...o.aliases]){const s=eC(o,r,n);if(s)return s}}return null}function eC(e,t,n){const o=t.trim(),r=[];let s="",a=0;for(;a=|<=|>|<|\?|:|\(|\)|!/.test(y))return null;return{operator:e.value,params:c}}function tC(e){const t=new Set,n=/(['"])\{(\w+)\}\1/g;let o;for(;(o=n.exec(e))!==null;)t.add(o[2]);return t}function nC(e,t){if(typeof e!="string"||e.trim()==="")return null;const n=Hr(e),o=gu(n,t);if(o)return{clauses:[o],combinator:"and"};const r=n.includes(") || (")?"or":(n.includes(") && ("),"and"),s=r==="or"?") || (":") && (";if(!n.startsWith("(")||!n.endsWith(")")||!n.includes(s))return null;const l=n.slice(1,-1).split(s),d=[];for(const c of l){const p=gu(c.trim(),t);if(!p)return null;d.push(p)}return{clauses:d,combinator:r}}const hu=Lt(),Mn=hu.responsiveManager,To=hu.BREAKPOINT_PRESETS;function yu(e){const t=e.groupPrefixes;return{group:typeof e.group=="string"?e.group:void 0,apply:e.apply&&typeof e.apply=="object"?e.apply:void 0,options:Array.isArray(e.options)?e.options:void 0,scale:Array.isArray(e.scale)?e.scale:void 0,groupPrefixes:Array.isArray(t)?t.filter(n=>typeof n=="string"&&n.length>0):void 0,groupTokens:Array.isArray(e.groupTokens)?e.groupTokens.filter(n=>typeof n=="string"&&n.length>0):void 0}}function bu(e){const t={...e??{}},n=t.style&&typeof t.style=="object"&&!Array.isArray(t.style)?{...t.style}:{};return t.style=n,{props:t,style:n}}function Kr(e){return typeof e!="string"?[]:e.split(/\s+/).filter(t=>t.length>0)}function mu(e,t=!1){const n=new Set,o=[],r=s=>{if(s){for(const a of s.tokens??[])n.add(a);s.tokenTemplate&&s.tokenTemplate.includes("{value}")&&o.push(s.tokenTemplate.split("{value}")[0]??"")}};r(e.apply);for(const s of e.options??[])r(s.apply);for(const s of e.groupPrefixes??[])o.push(s);for(const s of e.groupTokens??[])n.add(s);return s=>{if(t){if(!Yt(s))return!1;const a=qs(s);return n.has(a)?!0:o.some(l=>l.length>0&&a.startsWith(l))}return Yt(s)?!1:n.has(s)?!0:o.some(a=>a.length>0&&s.startsWith(a))}}function oC(e,t,n,o,r=!1){const s=mu(t,r),a=Kr(e.className).filter(p=>!s(p)),l=[];if(Array.isArray(n.tokens))l.push(...n.tokens);else if(n.tokenTemplate&&n.tokenTemplate.includes("{value}")&&o!=null){const p=String(o).split(/\s+/).filter(f=>f.length>0);for(const f of p)l.push(n.tokenTemplate.replace("{value}",f))}const d=r?l.map(Su):l,c=[...a,...d];c.length===0?delete e.className:e.className=c.join(" ")}const Ks={url:"backgroundImage",size:"backgroundSize",repeat:"backgroundRepeat",position:"backgroundPosition"};function rC(e){return!e||typeof e!="object"||Array.isArray(e)?!1:Object.keys(Ks).some(t=>t in e)}function iC(e){return/^url\(/i.test(e.trim())?e:`url(${e})`}function vu(e){const t=/^url\(\s*(['"]?)(.*?)\1\s*\)$/i.exec(e.trim());return t?t[2]:e}function sC(e){return e.includes("backgroundImage")}function Vs(e,t){if(e!=null){if(typeof e=="object"){const n=e[t];return typeof n=="string"||typeof n=="number"?n:void 0}return e}}function aC(e){const t=e.backgroundImage,n=Vs(e.backgroundSize,"size"),o=Vs(e.backgroundRepeat,"repeat"),r=Vs(e.backgroundPosition,"position");let s;if(typeof t=="string"?s=vu(t):t&&typeof t=="object"?s=t.url:s=void 0,s===void 0&&n===void 0&&o===void 0&&r===void 0)return;const a={};return s!==void 0&&(a.url=typeof s=="string"?vu(s):s),n!==void 0&&(a.size=n),o!==void 0&&(a.repeat=o),r!==void 0&&(a.position=r),a}function lC(e,t,n){const o=(r,s)=>{s==null||s===""?delete e[r]:e[r]=s};if(Array.isArray(t.props)){if(!t.values&&rC(n)){for(const r of t.props){const s=Object.keys(Ks).find(d=>Ks[d]===r);let a=s?n[s]:void 0;a!==null&&typeof a=="object"&&(a=void 0);const l=r==="backgroundImage"&&typeof a=="string"&&a!==""?iC(a):a;o(r,l)}return}for(const r of t.props){const s=t.values&&r in t.values?t.values[r]:n;o(r,s)}return}typeof t.prop=="string"&&o(t.prop,t.value!==void 0?t.value:n)}function dC(e,t,n){if(typeof t.varName!="string")return;const o=t.value!==void 0?t.value:n;o==null||o===""?delete e[t.varName]:e[t.varName]=o}function cC(e,t,n){if(typeof t.propKey!="string")return;const o=t.value!==void 0?t.value:n;o==null||o===""?delete e[t.propKey]:e[t.propKey]=o}function fn(e,t,n,o=Wt){const r=yu(t),s=o.colorScheme==="dark";let a,l=!1;if(r.options){const y=r.options.find(g=>g.value===n);l=y!==void 0,a=y?.apply??r.apply}else a=r.apply;const d=n==null||n===""||l&&a===void 0,p=(d?r.apply:a)?.type==="classToken"||(r.options?.some(y=>y.apply?.type==="classToken")??!1);if(s&&!p)return e;const f=o.breakpoint!=="base"&&p?(()=>{const y=e.props??{};return typeof y.className=="string"?y.className:void 0})():void 0;return ku(e,o,(y,g)=>{if(f!==void 0&&typeof y.className!="string"&&(y.className=f),d){const m=mu(r,s),h=Kr(y.className).filter(v=>!m(v));if(h.length===0?delete y.className:y.className=h.join(" "),!s&&r.apply){const v=r.apply;if(v.type==="styleProp")for(const b of v.props??(v.prop?[v.prop]:[]))delete g[b];else v.type==="cssVar"&&v.varName?delete g[v.varName]:v.type==="propValue"&&v.propKey&&delete y[v.propKey]}}else if(a)switch(a.type){case"classToken":oC(y,r,a,n,s);break;case"styleProp":lC(g,a,n);break;case"cssVar":dC(g,a,n);break;case"propValue":cC(y,a,n);break}})}function Vr(e,t){const n=Kr(e);return t?n.filter(Yt).map(qs):n.filter(o=>!Yt(o))}function uC(e,t,n,o){const r=new Set(t.filter(l=>typeof l=="string"&&l.length>0)),s=l=>o?Yt(l)?r.has(qs(l)):!1:Yt(l)?!1:r.has(l),a=Kr(e).filter(l=>!s(l));return n&&n.length>0&&a.push(o?Su(n):n),a.join(" ").trim()}function _u(e,t,n,o){if(e.options&&e.options.some(s=>s.apply)){const s=Vr(t.className,o),a=[];for(const l of e.options)l.apply&&fC(l.apply,s,n,t)&&a.push(l.value);if(a.length>0)return{value:a[0],matched:!0,conflict:a.length>1?!0:void 0};if(!e.apply)return{value:void 0,matched:!1}}const r=e.apply;if(!r)return{value:void 0,matched:!1};switch(r.type){case"styleProp":{if(!r.values&&Array.isArray(r.props)&&sC(r.props)){const l=aC(n);return l===void 0?{value:void 0,matched:!1}:{value:l,matched:!0}}const s=r.prop??r.props?.[0];if(!s)return{value:void 0,matched:!1};const a=n[s];return a===void 0?{value:void 0,matched:!1}:{value:a,matched:!0}}case"cssVar":{if(!r.varName)return{value:void 0,matched:!1};const s=n[r.varName];return s===void 0?{value:void 0,matched:!1}:{value:s,matched:!0}}case"propValue":{if(!r.propKey)return{value:void 0,matched:!1};const s=t[r.propKey];return s===void 0?{value:void 0,matched:!1}:{value:s,matched:!0}}case"classToken":{const s=Vr(t.className,o);if(r.tokenTemplate&&r.tokenTemplate.includes("{value}")){const[a,l]=r.tokenTemplate.split("{value}");if((a??"")===""&&(l??"")===""&&e.groupPrefixes?.length){const d=s.filter(c=>e.groupPrefixes.some(p=>c.startsWith(p)));return d.length>0?{value:d.join(" "),matched:!0}:{value:void 0,matched:!1}}for(const d of s)if(d.startsWith(a)&&d.endsWith(l??""))return{value:d.slice(a.length,l?d.length-l.length:void 0),matched:!0}}return Array.isArray(r.tokens)&&r.tokens.every(a=>s.includes(a))?{value:!0,matched:!0}:{value:void 0,matched:!1}}default:return{value:void 0,matched:!1}}}function xu(e){return e.style&&typeof e.style=="object"&&!Array.isArray(e.style)?e.style:{}}function Do(e,t,n=Wt){const o=yu(t),r=n.colorScheme==="dark";if(r&&!Us(o.apply)&&!o.options?.some(m=>Us(m.apply)))return{value:void 0,matched:!1,darkReadonly:!0};const s=mC(e,n),a=xu(s),l=_u(o,s,a,r);if(n.breakpoint==="base")return l;const d=l.matched?l.value:void 0,c=e.props??{},p=xu(c),f=_u(o,c,p,r),y=l.matched&&l.value!==void 0,g=y&&!pC(l.value,f.value);return{value:y?l.value:f.value,matched:g?l.matched:f.matched,conflict:g?l.conflict:f.conflict,scopedValue:g?l.value:void 0,baseFallback:g?void 0:f.value,scopedRawValue:d}}function pC(e,t){if(e===t)return!0;if(e===null||t===null||typeof e!="object"||typeof t!="object")return!1;try{return JSON.stringify(e)===JSON.stringify(t)}catch{return!1}}function fC(e,t,n,o){switch(e.type){case"classToken":return Array.isArray(e.tokens)&&e.tokens.length>0?e.tokens.every(r=>t.includes(r)):!1;case"styleProp":{const r=e.prop??e.props?.[0];if(!r)return!1;const s=e.value;return s!==void 0?n[r]===s:n[r]!==void 0}case"cssVar":return e.varName?n[e.varName]!==void 0:!1;case"propValue":return e.propKey?e.value!==void 0?o[e.propKey]===e.value:o[e.propKey]!==void 0:!1;default:return!1}}const Wt={colorScheme:"base",breakpoint:"base"},No="dark:";function gC(e){return e==="base"?!0:Mn.parseRange(e)!==null}function wu(e){return e in To}function Ur(e,t,n){if(e==="custom"){if(typeof n=="number"){const o=t.responsive;if(o&&typeof o=="object"){const r=Mn.getMatchingKey(o,n);if(r)return r}}return"base"}return e}function hC(e){return!!e&&typeof e=="object"&&Array.isArray(e.children)}function yC(e,t,n){let o,r,s;if(e!=="custom"&&e in To){o=e;const l=To[e];r=l.min,s=l.max}else if(e!=="custom"){const l=Mn.parseRange(e);if(l)o=e,r=l.min,s=l.max;else{const d=typeof n=="number"?n:Mn.getWidth();o=`${d}-${d}`,r=d,s=d}}else{const l=typeof n=="number"?n:Mn.getWidth();r=l,s=l,o=`${l}-${l}`}const a=t.responsive;if(a&&typeof a=="object"){let l=null,d=1/0;for(const c of Object.keys(a)){if(!hC(a[c]))continue;const p=Mn.parseRange(c);if(p){if(p.min===r&&p.max===s)return{mode:"merge",key:c};if(p.min<=r&&p.max>=s){const f=p.max-p.min;ft.length>0):[]}function xC(e,t){if(t.colorScheme==="dark")return ku(e,t,l=>{const d=qr(l.className).filter(c=>!Yt(c));d.length===0?delete l.className:l.className=d.join(" ")});if(t.breakpoint==="base")return e;const o=t.breakpoint;if(!e.responsive?.[o])return e;const r={...e.responsive},s={...r[o]};delete s.props,Object.keys(s).length===0?delete r[o]:r[o]=s;const a={...e};return Object.keys(r).length===0?delete a.responsive:a.responsive=r,a}function Gs(e,t){if(t.colorScheme==="dark"){const c=t.breakpoint==="base"?e.props:e.responsive?.[t.breakpoint]?.props;return qr(c?.className).some(Yt)}if(t.breakpoint==="base")return!1;const o=e.responsive?.[t.breakpoint];if(!o)return!1;const r=o.props;if(Object.keys(o).filter(c=>c!=="props").length>0)return!0;if(!r)return!1;if(Object.keys(r).filter(c=>c!=="className").length>0)return!0;const l=new Set(qr(e.props?.className).filter(c=>!Yt(c))),d=qr(r.className).filter(c=>!Yt(c));return d.length!==l.size?!0:d.some(c=>!l.has(c))}function Cu(e,t,n=""){return typeof e!="string"?n:e.startsWith("$t:")?t(e.slice(3)):e}function $u(e,t,n){if(n.breakpoint!=="base")return _C(e,n,t.trim());const o={...e};if(o.props&&typeof o.props=="object"&&"if"in o.props){const r={...o.props};delete r.if,o.props=r}return t.trim()===""?delete o.if:o.if=t,o}function Eu({node:e,spec:t,t:n,onPatchNode:o,dataSourceCandidates:r,stateKeyCandidates:s,scope:a=Wt}){const l=u.useMemo(()=>XS(t?.conditionRecipes),[t]),d=a.breakpoint!=="base"?vC(e,a):e.if??e.props?.if,c=u.useMemo(()=>nC(d,l),[d,l]),p=c?.clauses??[],f=c?.combinator??"and",y=typeof d=="string"&&d.trim()!==""&&c===null;if(l.length===0)return i.jsx("div",{"data-testid":"g7le-condition-no-recipes",style:wC,children:n("layout_editor.visibility.no_recipes")});const g=(k,w)=>{o($u(e,fu(k,w,l),a))},m=(k,w)=>{g(p.map((_,C)=>C===k?w:_),f)},h=k=>{g(p.filter((w,_)=>_!==k),f)},v=k=>{const w=l[0];g([...p,{operator:w.value,params:{}}],k)},b=k=>{if(k==="datasource-picker")return r;if(k==="state-key-picker")return s};if(y)return i.jsxs("div",{className:"g7le-condition-advanced","data-testid":"g7le-condition-advanced",style:{display:"flex",flexDirection:"column",gap:8},children:[i.jsx("div",{style:DC,children:n("layout_editor.visibility.advanced_preserved")}),i.jsx("code",{"data-testid":"g7le-condition-advanced-expr",style:Au,children:String(d)}),i.jsx("button",{type:"button","data-testid":"g7le-condition-clear-advanced",onClick:()=>o($u(e,"",a)),style:NC,children:n("layout_editor.visibility.clear")})]});const x=fu(p,f,l);return i.jsxs("div",{className:"g7le-condition-builder","data-testid":"g7le-condition-builder",style:{display:"flex",flexDirection:"column",gap:10},children:[i.jsx("div",{style:kC,children:n("layout_editor.visibility.show_when")}),p.map((k,w)=>{const _=l.find(C=>C.value===k.operator)??l[0];return i.jsxs("div",{className:"g7le-condition-clause","data-testid":`g7le-condition-clause-${w}`,style:SC,children:[i.jsxs("div",{style:{display:"flex",gap:6,alignItems:"center"},children:[w>0&&i.jsx("span",{"data-testid":`g7le-condition-combinator-${w}`,style:$C,children:n(f==="or"?"layout_editor.visibility.or":"layout_editor.visibility.and")}),i.jsx("select",{"data-testid":`g7le-condition-select-${w}`,value:k.operator,onChange:C=>m(w,{operator:C.target.value,params:{}}),style:CC,children:l.map(C=>i.jsx("option",{value:C.value,children:Cu(C.label,n,C.value)},C.value))}),i.jsx("button",{type:"button","data-testid":`g7le-condition-remove-${w}`,"aria-label":n("layout_editor.visibility.remove"),onClick:()=>h(w),style:EC,children:"✕"})]}),_.params.map(C=>{const E=Tn(C.widget);return i.jsxs("div",{className:"g7le-condition-param","data-testid":`g7le-condition-param-${w}-${C.key}`,style:IC,children:[i.jsx("span",{style:AC,children:Cu(C.label,n,C.key)}),i.jsx("div",{style:{flex:1},children:E?i.jsx(E,{control:{...C},value:k.params[C.key],onChange:S=>m(w,{operator:k.operator,params:{...k.params,[C.key]:S===void 0?"":String(S)}}),t:n,candidates:b(C.widget)}):i.jsx("span",{style:{fontSize:11,color:"#94a3b8"},children:n("layout_editor.property_modal.unsupported_widget")})})]},C.key)})]},w)}),i.jsxs("div",{style:{display:"flex",gap:6},children:[i.jsxs("button",{type:"button","data-testid":"g7le-condition-add-and",onClick:()=>v("and"),style:Iu,children:["+ ",n("layout_editor.visibility.and")]}),i.jsxs("button",{type:"button","data-testid":"g7le-condition-add-or",onClick:()=>v("or"),style:Iu,children:["+ ",n("layout_editor.visibility.or")]})]}),x!==""&&i.jsxs("div",{"data-testid":"g7le-condition-preview",style:TC,children:[i.jsx("span",{style:{fontSize:11,color:"#64748b"},children:n("layout_editor.visibility.preview")}),i.jsx("code",{style:Au,children:x})]})]})}const wC={fontSize:12,color:"#94a3b8",padding:"16px 0",textAlign:"center"},kC={fontSize:12,fontWeight:600,color:"#0f172a"},SC={border:"1px solid #e2e8f0",borderRadius:8,padding:10,display:"flex",flexDirection:"column",gap:8,background:"#f8fafc"},CC={flex:1,padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff"},$C={fontSize:11,fontWeight:700,color:"#2563eb",background:"#dbeafe",padding:"2px 8px",borderRadius:999},EC={border:"none",background:"transparent",color:"#94a3b8",cursor:"pointer",fontSize:12},Iu={padding:"5px 12px",fontSize:12,border:"1px dashed #94a3b8",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},IC={display:"flex",alignItems:"center",gap:8},AC={fontSize:11,color:"#64748b",minWidth:64},TC={display:"flex",flexDirection:"column",gap:4,padding:10,background:"#f1f5f9",borderRadius:6},Au={fontSize:11,color:"#0f172a",fontFamily:"monospace",wordBreak:"break-all"},DC={fontSize:11,color:"#92400e",background:"#fef3c7",padding:"6px 8px",borderRadius:6},NC={padding:"5px 12px",fontSize:12,border:"1px solid #fecaca",borderRadius:6,background:"#fff",color:"#dc2626",cursor:"pointer",alignSelf:"flex-start"};function zC(e){return typeof e=="string"?e:e===void 0?"":JSON.stringify(e)}function MC(e){const t=e.trim();if(t==="")return"";if(/^-?\d+(\.\d+)?$/.test(t)||t==="true"||t==="false"||t==="null")try{return JSON.parse(t)}catch{return e}return e}function Tu(e){return!e||typeof e!="object"||Array.isArray(e)?[]:Object.entries(e).map(([t,n])=>({key:t,value:zC(n)}))}function Ys(e){const t={};for(const n of e){const o=n.key.trim();o!==""&&(t[o]=MC(n.value))}return t}function Du(e){return!e||typeof e!="object"||Array.isArray(e)?!1:Object.values(e).some(t=>t!==null&&typeof t=="object")}function LC(e){const t={};return typeof e.if=="string"&&e.if.trim().length>0&&(t.if=e.if),t}function PC(e,t){const n={...t},o=e.if;return typeof o=="string"&&o.trim().length>0?n.if=o:delete n.if,n}const OC=["api","static","route_params","query_params","websocket"],BC=["GET","POST","PUT","PATCH","DELETE"],WC=["none","optional","required"],jC=["progressive","blocking","background"],HC=["application/json","multipart/form-data"],FC=["public","private","presence"];function KC(e){if(!e||typeof e!="object")return{own:[],inherited:[]};const t=e.__editor,n=t&&typeof t=="object"&&!Array.isArray(t)?t.original:void 0,o=n&&typeof n=="object"&&!Array.isArray(n)?n.data_sources:e.data_sources,r=Array.isArray(o)?o.filter(c=>c&&typeof c=="object"):[],s=new Set(r.map(c=>typeof c.id=="string"?c.id:"").filter(Boolean)),a=e.data_sources,d=(Array.isArray(a)?a.filter(c=>c&&typeof c=="object"):[]).filter(c=>typeof c.id=="string"&&!s.has(c.id));return{own:r,inherited:d}}function VC(e,t){const n=(o,r="")=>typeof o=="string"?o:r;return{index:t,id:n(e.id),type:n(e.type,"api"),label_key:n(e.label_key),endpoint:n(e.endpoint),method:n(e.method,"GET"),auth_mode:n(e.auth_mode,"none"),loading_strategy:n(e.loading_strategy,"progressive"),auto_fetch:e.auto_fetch!==!1,paramRows:Tu(e.params),paramsRaw:Du(e.params),paramsRawValue:e.params&&typeof e.params=="object"&&!Array.isArray(e.params)?e.params:{},paramsRawValid:!0,fallbackEnabled:e.fallback!==void 0,fallbackValue:e.fallback,if:n(e.if),contentType:n(e.contentType),refetchOnMount:e.refetchOnMount===!0,onSuccess:Array.isArray(e.onSuccess)?e.onSuccess.filter(o=>!!o&&typeof o=="object"):[],onError:Array.isArray(e.onError)?e.onError.filter(o=>!!o&&typeof o=="object"):[],errorHandling:e.errorHandling&&typeof e.errorHandling=="object"&&!Array.isArray(e.errorHandling)?e.errorHandling:{},channel:n(e.channel),event:n(e.event),channel_type:n(e.channel_type,"public"),target_source:n(e.target_source),onReceive:Array.isArray(e.onReceive)?e.onReceive.filter(o=>!!o&&typeof o=="object"):[]}}function UC(){return{index:-1,id:"",type:"api",label_key:"",endpoint:"",method:"GET",auth_mode:"none",loading_strategy:"progressive",auto_fetch:!0,paramRows:[],paramsRaw:!1,paramsRawValue:{},paramsRawValid:!0,fallbackEnabled:!1,fallbackValue:void 0,if:"",contentType:"",refetchOnMount:!1,onSuccess:[],onError:[],errorHandling:{},channel:"",event:"",channel_type:"public",target_source:"",onReceive:[]}}function qC(e,t,n,o){const r=e.id.trim();if(!r)return{error:o("layout_editor.data_sources.error.id_required")};if(!/^[A-Za-z_][\w]*$/.test(r))return{error:o("layout_editor.data_sources.error.id_invalid")};if(n.has(r))return{error:o("layout_editor.data_sources.error.id_duplicate",{id:r})};const s=t?{...t}:{};if(s.id=r,s.type=e.type,e.label_key.trim()?s.label_key=e.label_key.trim():delete s.label_key,e.type==="api"||e.type==="websocket"?(e.endpoint.trim()?s.endpoint=e.endpoint.trim():delete s.endpoint,s.method=e.method,s.auth_mode=e.auth_mode,s.loading_strategy=e.loading_strategy):(delete s.endpoint,delete s.method),s.auto_fetch=e.auto_fetch,e.paramsRaw){if(!e.paramsRawValid)return{error:o("layout_editor.data_sources.error.params_json")};const a=e.paramsRawValue;a&&typeof a=="object"&&Object.keys(a).length>0?s.params=a:delete s.params}else{const a=Ys(e.paramRows);Object.keys(a).length>0?s.params=a:delete s.params}return e.fallbackEnabled?s.fallback=e.fallbackValue===void 0?{}:e.fallbackValue:delete s.fallback,e.if.trim()?s.if=e.if.trim():delete s.if,e.type==="api"&&e.contentType?s.contentType=e.contentType:delete s.contentType,e.refetchOnMount?s.refetchOnMount=!0:delete s.refetchOnMount,e.onSuccess.length>0?s.onSuccess=e.onSuccess:delete s.onSuccess,e.onError.length>0?s.onError=e.onError:delete s.onError,Object.keys(e.errorHandling).length>0?s.errorHandling=e.errorHandling:delete s.errorHandling,e.type==="websocket"?(e.channel?s.channel=e.channel:delete s.channel,e.event?s.event=e.event:delete s.event,s.channel_type=e.channel_type,e.target_source?s.target_source=e.target_source:delete s.target_source,e.onReceive.length>0?s.onReceive=e.onReceive:delete s.onReceive):(delete s.channel,delete s.event,delete s.channel_type,delete s.target_source,delete s.onReceive),{entry:s}}const jt={width:"100%",padding:"6px 8px",fontSize:13,border:"1px solid #cbd5e1",borderRadius:6,outline:"none",boxSizing:"border-box",fontFamily:"inherit",color:"#0f172a",background:"#ffffff"},ct={display:"block",fontSize:12,fontWeight:600,color:"#475569",marginBottom:4},ut={marginBottom:12},rn={padding:"6px 12px",fontSize:13,fontWeight:500,borderRadius:6,border:"1px solid #cbd5e1",background:"#ffffff",color:"#0f172a",cursor:"pointer"},Nu={...rn,background:"#2563eb",borderColor:"#2563eb",color:"#ffffff",fontWeight:600},zo={marginTop:12,padding:10,border:"1px solid #e2e8f0",borderRadius:8,background:"#f8fafc"},Mo={fontSize:12,fontWeight:700,color:"#475569",marginBottom:8},GC={margin:"0 0 6px",fontSize:11,color:"#94a3b8"},zu={marginTop:8},Mu={alignSelf:"flex-start",padding:"2px 0",fontSize:11,border:"none",background:"none",color:"#64748b",cursor:"pointer"},Lu={margin:0,padding:8,background:"#0f172a",color:"#e2e8f0",borderRadius:6,fontSize:11,lineHeight:1.5,overflow:"auto",fontFamily:"ui-monospace, monospace"};function YC({code:e,action:t,recipes:n,pools:o,t:r,onPatch:s}){const[a,l]=u.useState(!1),d=u.useMemo(()=>wo(n),[n]),c=Array.isArray(t)?t[0]:t,p=typeof c?.handler=="string"?c.handler:"",f=d.find(x=>x.id===p)??null,y=c?Yn(c,d):null,g=y?.kind==="preset"?y.values:{},m=p==="sequence"||p==="parallel",h=["","showErrorPage","navigate","openModal","toast","setState","sequence","parallel"],v=x=>{if(x===""){s({});return}const k=d.find(w=>w.id===x);s(k?xr(k,{}):{handler:x})},b=m&&c?Array.isArray(c.params?.actions)?c.params.actions:[]:[];return i.jsxs("div",{"data-testid":`g7le-ds-errorhandling-${e}`,style:{display:"flex",flexDirection:"column",gap:6},children:[i.jsxs("div",{style:{display:"flex",alignItems:"center",gap:6},children:[i.jsx("select",{"data-testid":`g7le-ds-eh-handler-${e}`,style:{...jt,flex:1},value:p,onChange:x=>v(x.target.value),children:h.map(x=>i.jsx("option",{value:x,children:r(x===""?"layout_editor.error.handler_none":`layout_editor.error.handler_${x}`)},x))}),p!==""?i.jsx("button",{type:"button","data-testid":`g7le-ds-eh-code-${e}`,onClick:()=>l(x=>!x),"aria-label":r("layout_editor.action_list.view_code"),title:r("layout_editor.action_list.view_code"),style:a?XC:Pu,children:""}):null]}),m?i.jsx("div",{"data-testid":`g7le-ds-eh-actions-${e}`,children:i.jsx(Gt,{actions:b,onChange:x=>s({handler:p,params:{actions:x}}),t:r,recipes:n,candidatePools:o,chipContext:"error",testIdPrefix:`g7le-ds-eh-seq-${e}`})}):f&&c?i.jsx(Ts,{raw:c,recipe:f,values:g,t:r,pools:o,onChange:x=>s(x),testIdPrefix:`g7le-ds-eh-action-${e}-edit`}):null,a&&c?i.jsx("pre",{"data-testid":`g7le-ds-eh-code-view-${e}`,style:JC,children:JSON.stringify(t,null,2)}):null]})}const Pu={flex:"0 0 auto",width:26,height:26,display:"inline-flex",alignItems:"center",justifyContent:"center",fontSize:11,border:"1px solid #e2e8f0",borderRadius:6,background:"#fff",color:"#64748b",cursor:"pointer",fontFamily:"ui-monospace, monospace"},XC={...Pu,background:"#0f172a",borderColor:"#0f172a",color:"#e2e8f0"},JC={margin:"4px 0 0",padding:8,background:"#0f172a",color:"#e2e8f0",borderRadius:6,fontSize:12,overflow:"auto",fontFamily:"ui-monospace, monospace"};function ZC({raw:e,onChange:t,t:n,resolveLabel:o,onClose:r,actionRecipes:s,errorRecipes:a,conditionSpec:l,bindingCandidates:d,pageCandidates:c,dataSourceCandidates:p,stateKeyCandidates:f}){const y=o??n,g=u.useMemo(()=>KC(e),[e]),m=g.inherited,[h,v]=u.useState(g.own),[b,x]=u.useState(null),[k,w]=u.useState(null),[_,C]=u.useState(!1),[E,S]=u.useState(!1),[T,$]=u.useState(()=>new Set),A=u.useCallback(I=>{$(P=>{const F=new Set(P);return F.has(I)?F.delete(I):F.add(I),F})},[]),z=u.useMemo(()=>({pageCandidates:c,dataSourceCandidates:p,stateKeyCandidates:f,bindingCandidates:d}),[c,p,f,d]),D=u.useCallback(I=>{v(I),t([...m,...I],I)},[m,t]),O=u.useCallback(()=>{w(null),x(UC())},[]),M=u.useCallback((I,P)=>{w(null),x(VC(I,P))},[]),B=u.useCallback(I=>{const P=h.filter((F,G)=>G!==I);D(P)},[h,D]),N=u.useCallback(()=>{x(null),w(null)},[]),H=u.useCallback(()=>{if(!b)return;const I=new Set;h.forEach((V,Y)=>{Y!==b.index&&typeof V.id=="string"&&I.add(V.id)}),m.forEach(V=>{typeof V.id=="string"&&I.add(V.id)});const P=b.index>=0?h[b.index]??null:null,F=qC(b,P,I,n);if("error"in F){w(F.error);return}const G=b.index>=0?h.map((V,Y)=>Y===b.index?F.entry:V):[...h,F.entry];D(G),x(null),w(null)},[b,h,m,D,n]),L=b?.type==="api"||b?.type==="websocket";return i.jsxs("div",{className:"g7le-data-sources","data-testid":"g7le-data-sources-panel",style:{flex:"1 1 auto",minHeight:0,display:"flex",flexDirection:"column",fontSize:13,color:"#0f172a",boxSizing:"border-box"},children:[i.jsxs("div",{className:"g7le-data-sources__header",style:{flex:"0 0 auto",padding:"16px 16px 12px",borderBottom:"1px solid #e2e8f0"},children:[i.jsxs("div",{style:{display:"flex",alignItems:"center",justifyContent:"space-between",marginBottom:8},children:[i.jsx("h2",{style:{margin:0,fontSize:16,fontWeight:700},children:n("layout_editor.data_sources.title")}),i.jsx("button",{type:"button","data-testid":"g7le-data-sources-close",onClick:r,"aria-label":n("layout_editor.data_sources.close"),style:{...rn,padding:"4px 10px"},children:"✕"})]}),i.jsx("p",{style:{margin:0,fontSize:12,color:"#64748b",lineHeight:1.5},children:n("layout_editor.data_sources.description")})]}),i.jsxs("div",{className:"g7le-data-sources__body",style:{flex:"1 1 auto",minHeight:0,overflowY:"auto",padding:16},children:[b&&i.jsxs("div",{className:"g7le-data-sources__form","data-testid":"g7le-data-sources-form",children:[i.jsx("div",{style:{fontSize:13,fontWeight:700,marginBottom:12},children:b.index>=0?n("layout_editor.data_sources.form.edit_title"):n("layout_editor.data_sources.form.add_title")}),i.jsxs("div",{style:ut,children:[i.jsx("label",{style:ct,htmlFor:"g7le-ds-id",children:n("layout_editor.data_sources.field.id")}),i.jsx("input",{id:"g7le-ds-id","data-testid":"g7le-data-sources-field-id",style:jt,value:b.id,onChange:I=>x({...b,id:I.target.value}),placeholder:"products"})]}),i.jsxs("div",{style:ut,children:[i.jsx("label",{style:ct,htmlFor:"g7le-ds-label",children:n("layout_editor.data_sources.field.label_key")}),i.jsx(vt,{value:b.label_key,onChange:I=>x({...b,label_key:I??""}),t:n,placeholder:n("layout_editor.data_sources.field.label_key_placeholder"),testidPrefix:"g7le-data-sources-field-label",candidates:d,enableExpressionTree:!0,expressionTreeCollapsible:!0}),i.jsx("div",{style:{fontSize:11,color:"#94a3b8",marginTop:4},children:n("layout_editor.data_sources.field.label_key_hint")})]}),i.jsxs("div",{style:ut,children:[i.jsx("label",{style:ct,htmlFor:"g7le-ds-type",children:n("layout_editor.data_sources.field.type")}),i.jsx("select",{id:"g7le-ds-type","data-testid":"g7le-data-sources-field-type",style:jt,value:b.type,onChange:I=>x({...b,type:I.target.value}),children:OC.map(I=>i.jsx("option",{value:I,children:n(`layout_editor.data_sources.ds_type.${I}`)},I))})]}),L&&i.jsxs(i.Fragment,{children:[i.jsxs("div",{style:ut,children:[i.jsx("label",{style:ct,children:n("layout_editor.data_sources.field.endpoint")}),i.jsx("div",{"data-testid":"g7le-data-sources-field-endpoint",children:i.jsx(Ve,{value:b.endpoint,onChange:I=>x({...b,endpoint:I}),t:n,candidates:d,placeholder:"/api/shop/products/{{route.id}}",testidPrefix:"g7le-ds-endpoint"})})]}),i.jsxs("div",{style:{display:"flex",gap:12},children:[i.jsxs("div",{style:{...ut,flex:1},children:[i.jsx("label",{style:ct,htmlFor:"g7le-ds-method",children:n("layout_editor.data_sources.field.method")}),i.jsx("select",{id:"g7le-ds-method","data-testid":"g7le-data-sources-field-method",style:jt,value:b.method,onChange:I=>x({...b,method:I.target.value}),children:BC.map(I=>i.jsx("option",{value:I,children:I},I))})]}),i.jsxs("div",{style:{...ut,flex:1},children:[i.jsx("label",{style:ct,htmlFor:"g7le-ds-auth",children:n("layout_editor.data_sources.field.auth_mode")}),i.jsx("select",{id:"g7le-ds-auth","data-testid":"g7le-data-sources-field-auth",style:jt,value:b.auth_mode,onChange:I=>x({...b,auth_mode:I.target.value}),children:WC.map(I=>i.jsx("option",{value:I,children:n(`layout_editor.data_sources.auth_mode.${I}`)},I))})]})]}),i.jsxs("div",{style:ut,children:[i.jsx("label",{style:ct,htmlFor:"g7le-ds-loading",children:n("layout_editor.data_sources.field.loading_strategy")}),i.jsx("select",{id:"g7le-ds-loading","data-testid":"g7le-data-sources-field-loading",style:jt,value:b.loading_strategy,onChange:I=>x({...b,loading_strategy:I.target.value}),children:jC.map(I=>i.jsx("option",{value:I,children:n(`layout_editor.data_sources.loading_strategy.${I}`)},I))})]}),b.type==="api"&&i.jsxs("div",{style:ut,children:[i.jsx("label",{style:ct,htmlFor:"g7le-ds-contenttype",children:n("layout_editor.data_sources.field.content_type")}),i.jsx("select",{id:"g7le-ds-contenttype","data-testid":"g7le-ds-contenttype",style:jt,value:b.contentType||"application/json",onChange:I=>x({...b,contentType:I.target.value}),children:HC.map(I=>i.jsx("option",{value:I,children:I},I))})]})]}),i.jsx("div",{style:ut,children:i.jsxs("label",{style:{...ct,display:"flex",alignItems:"center",gap:8,cursor:"pointer"},children:[i.jsx("input",{type:"checkbox","data-testid":"g7le-data-sources-field-autofetch",checked:b.auto_fetch,onChange:I=>x({...b,auto_fetch:I.target.checked})}),n("layout_editor.data_sources.field.auto_fetch")]})}),i.jsx("div",{style:ut,children:i.jsxs("label",{style:{...ct,display:"flex",alignItems:"center",gap:8,cursor:"pointer"},children:[i.jsx("input",{type:"checkbox","data-testid":"g7le-ds-refetch",checked:b.refetchOnMount,onChange:I=>x({...b,refetchOnMount:I.target.checked})}),n("layout_editor.data_sources.field.refetch_on_mount")]})}),i.jsxs("div",{style:ut,children:[i.jsxs("div",{style:{display:"flex",alignItems:"center",justifyContent:"space-between"},children:[i.jsx("label",{style:ct,htmlFor:"g7le-ds-params",children:n("layout_editor.data_sources.field.params")}),i.jsx("button",{type:"button","data-testid":"g7le-ds-params-mode-toggle",onClick:()=>{if(b.paramsRaw){if(b.paramsRawValid){const I=b.paramsRawValue;I&&typeof I=="object"&&!Array.isArray(I)&&!Du(I)&&x({...b,paramsRaw:!1,paramRows:Tu(I)})}}else{const I=Ys(b.paramRows);x({...b,paramsRaw:!0,paramsRawValue:I,paramsRawValid:!0})}},style:{...rn,padding:"2px 8px",fontSize:11},children:b.paramsRaw?n("layout_editor.data_sources.field.params_mode_block"):n("layout_editor.data_sources.field.params_mode_code")})]}),b.paramsRaw?i.jsx(Bs,{id:"g7le-ds-params",value:b.paramsRawValue,shape:"object",emptyValue:{},minHeight:64,testidPrefix:"g7le-data-sources-field-params",placeholder:`{ "page": "{{query.page ?? 1}}" -}`,shapeErrorKey:"layout_editor.data_sources.error.params_object",invalidErrorKey:"layout_editor.data_sources.error.params_json",t:n,onChange:I=>x(P=>P&&{...P,paramsRawValue:I??{},paramsRawValid:!0}),onValidityChange:I=>x(P=>P&&{...P,paramsRawValid:I})}):i.jsxs("div",{"data-testid":"g7le-data-sources-field-params",children:[b.paramRows.length===0&&i.jsx("p",{"data-testid":"g7le-ds-params-empty",style:UC,children:n("layout_editor.data_sources.field.params_empty")}),i.jsx(zr,{value:b.paramRows.map(I=>({key:I.key,value:I.value})),onChange:I=>x({...b,paramRows:I.map(P=>({key:P.key??"",value:P.value??""}))}),keyField:"key",valueField:"value",t:n,candidates:d,testidPrefix:"g7le-ds-params-kv",keyPlaceholder:n("layout_editor.data_sources.field.param_key_placeholder"),valuePlaceholder:"{{query.page ?? 1}}",addLabel:n("layout_editor.data_sources.field.param_add")}),(()=>{const I=Ys(b.paramRows);return Object.keys(I).length===0?null:i.jsxs("div",{style:zu,children:[i.jsxs("button",{type:"button","data-testid":"g7le-ds-params-preview-toggle","aria-expanded":_,onClick:()=>S(P=>!P),style:Mu,children:[_?"▴":"▾"," ",n("layout_editor.data_sources.field.params_preview")]}),_&&i.jsx("pre",{"data-testid":"g7le-ds-params-preview",style:{...Lu,marginTop:4},children:JSON.stringify(I,null,2)})]})})()]}),i.jsx("div",{style:{fontSize:11,color:"#94a3b8",marginTop:4},children:n("layout_editor.data_sources.field.params_hint")})]}),i.jsxs("div",{style:ut,children:[i.jsx("label",{style:ct,children:n("layout_editor.data_sources.field.fallback")}),b.fallbackEnabled?i.jsxs("div",{"data-testid":"g7le-data-sources-field-fallback",style:{display:"flex",flexDirection:"column",gap:6},children:[i.jsx(Nn,{value:b.fallbackValue===void 0?{}:b.fallbackValue,onChange:I=>x({...b,fallbackValue:I}),t:n,path:"fallback",scope:"ds-fallback",renderStringLeaf:I=>i.jsx(Ve,{value:I.value,onChange:I.onChange,t:n,candidates:d,testidPrefix:I.testidPrefix})}),(()=>{const I=b.fallbackValue===void 0?{}:b.fallbackValue;return i.jsxs("div",{style:zu,children:[i.jsxs("button",{type:"button","data-testid":"g7le-ds-fallback-preview-toggle","aria-expanded":E,onClick:()=>C(P=>!P),style:Mu,children:[E?"▴":"▾"," ",n("layout_editor.data_sources.field.fallback_preview")]}),E&&i.jsx("pre",{"data-testid":"g7le-ds-fallback-preview",style:{...Lu,marginTop:4},children:JSON.stringify(I,null,2)})]})})(),i.jsx("button",{type:"button","data-testid":"g7le-data-sources-field-fallback-clear",onClick:()=>x({...b,fallbackEnabled:!1,fallbackValue:void 0}),style:{...rn,padding:"2px 8px",fontSize:11,alignSelf:"flex-start",color:"#dc2626",borderColor:"#fecaca"},children:n("layout_editor.data_sources.field.fallback_clear")})]}):i.jsxs("button",{type:"button","data-testid":"g7le-data-sources-field-fallback-add",onClick:()=>x({...b,fallbackEnabled:!0,fallbackValue:{}}),style:{...rn,padding:"4px 10px",display:"block"},children:["+ ",n("layout_editor.data_sources.field.fallback_add")]})]}),b.type==="websocket"&&i.jsxs("div",{"data-testid":"g7le-ds-ws-section",style:zo,children:[i.jsx("div",{style:Mo,children:n("layout_editor.data_sources.section.websocket")}),i.jsxs("div",{style:ut,children:[i.jsx("label",{style:ct,children:n("layout_editor.data_sources.field.channel")}),i.jsx("input",{"data-testid":"g7le-ds-ws-channel",style:jt,value:b.channel,onChange:I=>x({...b,channel:I.target.value}),placeholder:"core.admin.dashboard"})]}),i.jsxs("div",{style:ut,children:[i.jsx("label",{style:ct,children:n("layout_editor.data_sources.field.event")}),i.jsx("input",{"data-testid":"g7le-ds-ws-event",style:jt,value:b.event,onChange:I=>x({...b,event:I.target.value}),placeholder:"dashboard.stats.updated"})]}),i.jsxs("div",{style:ut,children:[i.jsx("label",{style:ct,children:n("layout_editor.data_sources.field.channel_type")}),i.jsx("select",{"data-testid":"g7le-ds-ws-channeltype",style:jt,value:b.channel_type,onChange:I=>x({...b,channel_type:I.target.value}),children:jC.map(I=>i.jsx("option",{value:I,children:I},I))})]}),i.jsxs("div",{style:ut,children:[i.jsx("label",{style:ct,children:n("layout_editor.data_sources.field.target_source")}),i.jsx("input",{"data-testid":"g7le-ds-ws-target",style:jt,value:b.target_source,onChange:I=>x({...b,target_source:I.target.value}),list:"g7le-ds-ws-target-list"}),i.jsx("datalist",{id:"g7le-ds-ws-target-list",children:h.filter(I=>I.id!==b.id).map(I=>typeof I.id=="string"?i.jsx("option",{value:I.id},I.id):null)})]}),i.jsxs("div",{style:ut,children:[i.jsx("label",{style:ct,children:n("layout_editor.data_sources.field.on_receive")}),i.jsx("div",{"data-testid":"g7le-ds-ws-onreceive",children:i.jsx(Gt,{actions:b.onReceive,onChange:I=>x({...b,onReceive:I}),t:n,recipes:s,candidatePools:z,chipContext:"payload",actionChipCandidates:l?.actionChipCandidates??null,testIdPrefix:"g7le-ds-onreceive-list"})})]})]}),i.jsxs("div",{style:zo,children:[i.jsx("div",{style:Mo,children:n("layout_editor.data_sources.section.on_success")}),i.jsx("div",{"data-testid":"g7le-ds-onsuccess",children:i.jsx(Gt,{actions:b.onSuccess,onChange:I=>x({...b,onSuccess:I}),t:n,recipes:s,candidatePools:z,chipContext:"response",actionChipCandidates:l?.actionChipCandidates??null,testIdPrefix:"g7le-ds-onsuccess-list"})})]}),i.jsxs("div",{style:zo,children:[i.jsx("div",{style:Mo,children:n("layout_editor.data_sources.section.on_error")}),i.jsx("div",{"data-testid":"g7le-ds-onerror",children:i.jsx(Gt,{actions:b.onError,onChange:I=>x({...b,onError:I}),t:n,recipes:s,candidatePools:z,chipContext:"error",actionChipCandidates:l?.actionChipCandidates??null,testIdPrefix:"g7le-ds-onerror-list"})})]}),i.jsxs("div",{style:zo,children:[i.jsx("div",{style:Mo,children:n("layout_editor.data_sources.section.error_handling")}),i.jsxs("div",{"data-testid":"g7le-ds-errorhandling",children:[i.jsx(Fs,{value:b.errorHandling,onChange:I=>x({...b,errorHandling:I}),t:n,mode:"local",testIdPrefix:"g7le-ds-eh",renderActionList:(I,P,F)=>i.jsx(qC,{code:I,action:P,recipes:a,pools:z,t:n,onPatch:F})}),i.jsxs("button",{type:"button","data-testid":"g7le-ds-errorhandling-add",onClick:()=>{const I=Object.keys(b.errorHandling),P=["403","404","500","422","default"].find(F=>!I.includes(F))??"default";x({...b,errorHandling:{...b.errorHandling,[P]:{handler:"toast"}}})},style:{...rn,padding:"4px 10px",marginTop:6},children:["+ ",n("layout_editor.data_sources.add_error_code")]})]})]}),i.jsxs("div",{style:zo,children:[i.jsx("div",{style:Mo,children:n("layout_editor.data_sources.section.load_condition")}),i.jsx("div",{"data-testid":"g7le-ds-loadcondition",children:i.jsx(Eu,{node:zC({if:b.if||void 0}),spec:l??null,t:n,dataSourceCandidates:p,stateKeyCandidates:f,onPatchNode:I=>{const P=MC(I,{});x({...b,if:typeof P.if=="string"?P.if:""})}})})]})]}),!b&&i.jsxs("button",{type:"button","data-testid":"g7le-data-sources-add",onClick:O,style:{...Nu,marginBottom:16},children:["+ ",n("layout_editor.data_sources.add")]}),i.jsxs("div",{className:"g7le-data-sources__list","data-testid":"g7le-data-sources-list",style:{display:"flex",flexDirection:"column",gap:8},children:[h.length===0&&!b&&i.jsx("div",{"data-testid":"g7le-data-sources-empty",style:{fontSize:12,color:"#94a3b8",padding:"12px 0",textAlign:"center"},children:n("layout_editor.data_sources.empty")}),h.map((I,P)=>{const F=typeof I.id=="string"?I.id:`#${P}`,G=typeof I.endpoint=="string"?I.endpoint:"",V=typeof I.label_key=="string"?I.label_key:"",Y=rs(I,y);return i.jsxs("div",{"data-testid":"g7le-data-sources-item","data-ds-id":F,style:{border:"1px solid #e2e8f0",borderRadius:8,padding:"10px 12px",display:"flex",alignItems:"center",gap:8,background:"#ffffff"},children:[i.jsxs("div",{style:{flex:1,minWidth:0},children:[i.jsxs("div",{"data-testid":"g7le-data-sources-item-title",style:{fontWeight:600,fontSize:13,display:"flex",alignItems:"baseline",gap:6,flexWrap:"wrap"},children:[i.jsx("span",{children:Y??F}),Y&&i.jsx("span",{"data-testid":"g7le-data-sources-item-id",style:{fontSize:11,fontWeight:400,color:"#94a3b8"},children:F}),(()=>{const Z=is(I,n);return Z?i.jsx("span",{"data-testid":"g7le-data-sources-ext-badge",style:{fontSize:10,fontWeight:600,color:"#7c3aed",background:"#f5f3ff",border:"1px solid #ddd6fe",borderRadius:4,padding:"1px 6px"},children:Z}):null})()]}),G&&i.jsx("div",{style:{fontSize:11,color:"#94a3b8",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},children:G}),!V&&i.jsx("div",{"data-testid":"g7le-data-sources-nolabel",style:{fontSize:11,color:"#f59e0b"},children:n("layout_editor.data_sources.no_label")})]}),i.jsx("button",{type:"button","data-testid":"g7le-data-sources-edit",onClick:()=>M(I,P),style:{...rn,padding:"4px 10px"},children:n("layout_editor.data_sources.edit")}),i.jsx("button",{type:"button","data-testid":"g7le-data-sources-remove",onClick:()=>B(P),style:{...rn,padding:"4px 10px",color:"#dc2626",borderColor:"#fecaca"},children:n("layout_editor.data_sources.remove")})]},`${F}-${P}`)})]}),m.length>0&&i.jsxs("div",{style:{marginTop:16},children:[i.jsx("div",{style:{fontSize:12,fontWeight:600,color:"#64748b",textTransform:"uppercase",letterSpacing:.4,marginBottom:8},children:n("layout_editor.data_sources.inherited_title")}),i.jsx("div",{style:{display:"flex",flexDirection:"column",gap:6},children:m.map((I,P)=>{const F=typeof I.id=="string"?I.id:`#${P}`,G=N.has(F),V=[],Y=(Z,se)=>{const re=I[Z];re!=null&&V.push([se,typeof re=="object"?JSON.stringify(re):String(re)])};return Y("type",n("layout_editor.data_sources.field.type")),Y("endpoint",n("layout_editor.data_sources.field.endpoint")),Y("method",n("layout_editor.data_sources.field.method")),Y("auth_mode",n("layout_editor.data_sources.field.auth_mode")),Y("loading_strategy",n("layout_editor.data_sources.field.loading_strategy")),Y("auto_fetch",n("layout_editor.data_sources.field.auto_fetch")),Y("params",n("layout_editor.data_sources.field.params")),i.jsxs("div",{"data-testid":"g7le-data-sources-inherited-item","data-ds-id":F,"data-expanded":G?"true":"false",style:{border:"1px dashed #e2e8f0",borderRadius:6,fontSize:12,color:"#64748b",background:"#f8fafc",overflow:"hidden"},children:[i.jsxs("button",{type:"button","data-testid":"g7le-data-sources-inherited-toggle",onClick:()=>A(F),"aria-expanded":G,style:{width:"100%",display:"flex",alignItems:"center",gap:8,padding:"8px 12px",background:"transparent",border:"none",cursor:"pointer",color:"inherit",fontSize:12,textAlign:"left",outline:"none"},children:[i.jsx("span",{style:{flex:"0 0 auto",width:12,transition:"transform 120ms",transform:G?"rotate(90deg)":"none"},children:"▶"}),(()=>{const Z=rs(I,y),se=is(I,n);return i.jsxs("span",{style:{flex:1,display:"inline-flex",alignItems:"baseline",gap:6,flexWrap:"wrap"},children:[i.jsx("span",{style:{fontWeight:600},children:Z??F}),Z&&i.jsx("span",{style:{fontSize:11,fontWeight:400,color:"#94a3b8"},children:F}),se&&i.jsx("span",{"data-testid":"g7le-data-sources-ext-badge",style:{fontSize:10,fontWeight:600,color:"#7c3aed",background:"#f5f3ff",border:"1px solid #ddd6fe",borderRadius:4,padding:"1px 6px"},children:se})]})})()]}),G&&i.jsxs("div",{"data-testid":"g7le-data-sources-inherited-info",style:{padding:"0 12px 10px 32px",display:"flex",flexDirection:"column",gap:3},children:[V.length===0&&i.jsx("span",{style:{color:"#94a3b8"},children:"—"}),V.map(([Z,se])=>i.jsxs("div",{style:{display:"flex",gap:6},children:[i.jsxs("span",{style:{flex:"0 0 auto",color:"#94a3b8"},children:[Z,":"]}),i.jsx("span",{style:{flex:1,minWidth:0,wordBreak:"break-all",color:"#475569"},children:se})]},Z))]})]},`inh-${F}-${P}`)})})]})]}),b&&i.jsxs("div",{className:"g7le-data-sources__footer",style:{flex:"0 0 auto",padding:"12px 16px",borderTop:"1px solid #e2e8f0",background:"#ffffff"},children:[k&&i.jsx("div",{"data-testid":"g7le-data-sources-form-error",role:"alert",style:{fontSize:12,color:"#dc2626",marginBottom:10},children:k}),i.jsxs("div",{style:{display:"flex",gap:8,justifyContent:"flex-end"},children:[i.jsx("button",{type:"button","data-testid":"g7le-data-sources-form-cancel",onClick:T,style:rn,children:n("layout_editor.data_sources.form.cancel")}),i.jsx("button",{type:"button","data-testid":"g7le-data-sources-form-submit",onClick:H,style:Nu,children:n("layout_editor.data_sources.form.save")})]})]})]})}function JC(e){const t=e?.globalHeaders;return Array.isArray(t)?t:[]}function ZC(e){const t=e?.scripts;return Array.isArray(t)?t:[]}function QC(e){const t=JC(e.raw),n=ZC(e.raw);return i.jsxs("div",{className:"g7le-data-source-tab","data-testid":"g7le-data-source-tab",style:RC,children:[i.jsx(XC,{...e}),i.jsxs("div",{style:Ou,"data-testid":"g7le-global-headers",children:[i.jsx("div",{style:Bu,children:e.t("layout_editor.data_sources.global_headers")}),t.length===0?i.jsx("p",{"data-testid":"g7le-global-header-empty",style:Ku,children:e.t("layout_editor.data_sources.global_headers_empty")}):t.map((o,r)=>i.jsxs("div",{"data-testid":`g7le-global-header-${r}`,style:Wu,children:[i.jsx("code",{style:ju,children:o.pattern??"*"}),i.jsx("span",{style:e$,children:"→"}),i.jsx("span",{style:Hu,children:Object.keys(o.headers??{}).join(", ")})]},r)),i.jsxs("p",{style:Fu,children:["ⓘ ",e.t("layout_editor.data_sources.global_headers_hint")]})]}),i.jsxs("div",{style:Ou,"data-testid":"g7le-scripts",children:[i.jsx("div",{style:Bu,children:e.t("layout_editor.data_sources.scripts")}),n.length===0?i.jsx("p",{"data-testid":"g7le-script-empty",style:Ku,children:e.t("layout_editor.data_sources.scripts_empty")}):n.map((o,r)=>i.jsxs("div",{"data-testid":`g7le-script-${o.id??r}`,style:Wu,children:[i.jsx("code",{style:ju,children:o.id??`#${r}`}),i.jsx("span",{style:Hu,children:o.src??""}),o.loading?i.jsx("span",{style:t$,children:o.loading}):null]},o.id??r)),i.jsxs("p",{style:Fu,children:["ⓘ ",e.t("layout_editor.data_sources.scripts_hint")]})]})]})}const RC={display:"flex",flexDirection:"column",gap:16,minWidth:0},Ou={borderTop:"1px solid #e2e8f0",paddingTop:12,display:"flex",flexDirection:"column",gap:6},Bu={fontSize:12,fontWeight:700,color:"#475569"},Wu={display:"flex",alignItems:"center",gap:8,fontSize:12,padding:"4px 8px",border:"1px solid #f1f5f9",borderRadius:6},ju={fontSize:11,color:"#0f172a",fontFamily:"monospace"},e$={color:"#94a3b8"},Hu={flex:1,minWidth:0,color:"#475569",wordBreak:"break-all"},t$={fontSize:10,color:"#64748b",background:"#f1f5f9",borderRadius:4,padding:"0 4px"},Fu={margin:0,fontSize:11,color:"#94a3b8"},Ku={margin:0,fontSize:12,color:"#94a3b8"},n$=["meta","seo","init","overlay","computed","state","error","data"],o$=["401","403","404","500","503"],r$={meta:"layout_editor.page_settings.tab.meta",seo:"layout_editor.page_settings.tab.seo",init:"layout_editor.page_settings.tab.init",overlay:"layout_editor.page_settings.tab.overlay",computed:"layout_editor.page_settings.tab.computed",state:"layout_editor.page_settings.tab.state",error:"layout_editor.page_settings.tab.error",data:"layout_editor.page_settings.tab.data"};async function i$(e){if(typeof fetch!="function"||!e)return[];try{const t=await fetch(en(`/api/admin/templates/${encodeURIComponent(e)}/editor/seo-candidates`,"json"),{credentials:"same-origin",headers:je()});if(!t.ok)return[];const o=(await t.json().catch(()=>null))?.data?.extensions;if(!Array.isArray(o))return[];const r=[];for(const s of o)if(s&&typeof s=="object"){const a=s.type,l=s.id,d=s.label;typeof a=="string"&&typeof l=="string"&&l.length>0&&r.push({type:a,id:l,name:typeof d=="string"?d:l})}return r}catch{return[]}}function s$(e,t){let n=0;for(const o of e)Yn(o,t).kind==="advanced"&&(n+=1);return n}function a$(e,t){let n=0;for(const o of Object.keys(e))Ed(e[o],t).kind==="advanced"&&(n+=1);return n}function l$(e,t,n){const o=e("meta",{})??{},r=o.title;if(typeof r=="string"&&r.length>0){if(r.startsWith("$t:")){const a=n(r.slice(3));if(a&&a!==r.slice(3))return a}else if(!/\{\{[\s\S]*\}\}/.test(r))return r}const s=o.editor_label;return typeof s=="string"&&s.length>0?s.startsWith("$t:")?n(s.slice(3)):s:t||""}function d$({templateIdentifier:e,spec:t,t:n,resolveLabel:o,permissionCandidates:r,onClose:s,initialTab:a="meta",extensionsFetcher:l}){const{state:d}=Je(),{raw:c,getValue:p,patch:f}=Dm(),[y,g]=u.useState(a),m=d.selectedRoute?.path??"",h=u.useMemo(()=>zd(d.routeTree,o),[d.routeTree,o]),v=u.useMemo(()=>Md(c),[c]),b=u.useMemo(()=>Ld(t),[t]),x=u.useMemo(()=>Jm(c,o),[c,o]),k=Nd({raw:c,spec:t}),w=u.useMemo(()=>Ry(t),[t]),_=u.useMemo(()=>eb(t),[t]),S=u.useMemo(()=>tb(t),[t]),E=u.useMemo(()=>nb(t),[t]),C=u.useMemo(()=>wo(w),[w]),N=u.useMemo(()=>xd(_),[_]),$=p("init_actions",void 0)??p("initActions",[]),A=p("computed",{}),z=p("__computedSource",{}),D=p("errorHandling",{}),O=p("transition_overlay",void 0),M=(p("__editor",{})??{}).original,B=u.useMemo(()=>{if(M===void 0)return;const U=M.transition_overlay;return U&&typeof U=="object"&&!Array.isArray(U)?U:{}},[M]),T=u.useMemo(()=>{const U=O;if(!U||typeof U!="object"||Array.isArray(U))return;const ee=B??{},fe={};for(const[K,ne]of Object.entries(U))K in ee||(fe[K]=ne);return Object.keys(fe).length>0?fe:void 0},[O,B]),[H,L]=u.useState([]);u.useEffect(()=>{let U=!1;const ee=l??i$;return(async()=>{const fe=await ee(e);U||L(fe)})(),()=>{U=!0}},[e,l]);const I=(p("meta",{})??{}).seo?.page_type,P=Ym({raw:c,spec:t,pageType:typeof I=="string"?I:null}),F=u.useMemo(()=>Gm(c,t),[c,t]),G=u.useMemo(()=>({meta:0,seo:0,init:s$($,C),overlay:0,computed:a$(A,N),state:0,error:0,data:0}),[$,C,A,N]),V=u.useMemo(()=>Object.values(G).reduce((U,ee)=>U+ee,0),[G]),Y=u.useMemo(()=>Xm(c,o,n),[c,o,n]),Z=u.useCallback(U=>{const fe={...M?.computed??{}};delete fe[U];const K={...A};delete K[U],f("computed",K,Object.keys(fe).length===0?void 0:fe)},[M,A,f]),se=l$(p,m,n),re=()=>{switch(y){case"meta":return i.jsx(gx,{getValue:p,patch:f,t:n,fieldT:o,spec:t,permissionCandidates:r,bindingCandidates:k});case"seo":return i.jsx(ew,{getValue:p,patch:f,t:n,templateIdentifier:e,candidates:P,availableExtensions:H,dataSourceOptions:Y,previewUrl:m||"/",seedContext:F});case"init":return i.jsx(_k,{actions:$,onChange:U=>f("init_actions",U),recipes:w,t:o,bindingCandidates:k,pageCandidates:h,dataSourceCandidates:v,stateKeyCandidates:b,modalCandidates:x});case"overlay":return i.jsx(Kk,{value:O,ownValue:B,baseValue:T,patch:U=>f("transition_overlay",U),t:o,loadingComponents:E,progressiveDataSources:Y,bindingCandidates:k});case"computed":return i.jsx(D1,{computed:A,onChange:U=>f("computed",U),recipes:_,t:o,sampleContext:F,dataSourceCandidates:v,bindingCandidates:k,computedSource:z,onRevert:Z});case"state":return i.jsx(pS,{raw:c,own:M?{initLocal:M.initLocal,initGlobal:M.initGlobal,initIsolated:M.initIsolated,state:M.state}:void 0,patch:f,t:n});case"error":return i.jsx(BS,{value:D,onChange:U=>f("errorHandling",U),t:o,errorConfigCodes:o$,templateCodes:Object.keys(D),ownCodes:Object.keys(M?.errorHandling??{}),recipes:S,pageCandidates:h,modalCandidates:x,dataSourceCandidates:v,stateKeyCandidates:b});case"data":return i.jsx(QC,{raw:c,t:n,resolveLabel:o,onChange:(U,ee)=>f("data_sources",U,ee),onClose:s,actionRecipes:w,errorRecipes:S,conditionSpec:t,bindingCandidates:k,pageCandidates:h,dataSourceCandidates:v,stateKeyCandidates:b});default:return i.jsx("div",{})}};return i.jsxs("div",{className:"g7le-page-settings","data-testid":"g7le-page-settings",style:c$,children:[i.jsxs("div",{style:u$,children:[i.jsx("span",{"data-testid":"g7le-page-settings-title",style:p$,children:n("layout_editor.page_settings.title",{name:se})}),i.jsx("button",{type:"button","data-testid":"g7le-page-settings-close-x",onClick:s,"aria-label":n("layout_editor.page_settings.close"),style:f$,children:"✕"})]}),i.jsx("div",{role:"tablist",style:g$,children:n$.map(U=>{const ee=G[U];return i.jsxs("button",{type:"button",role:"tab","aria-selected":y===U,"data-testid":`g7le-page-settings-tab-${U}`,"data-active":y===U?"true":"false",onClick:()=>g(U),style:y===U?h$:Vu,children:[n(r$[U]),ee>0?i.jsxs("span",{"data-testid":`g7le-page-settings-tab-badge-${U}`,style:y$,children:["(",ee,")"]}):null]},U)})}),V>0?i.jsxs("p",{"data-testid":"g7le-page-settings-advanced-summary",style:b$,children:["⚠ ",n("layout_editor.page_settings.advanced_summary",{count:V})," ⓘ"]}):null,i.jsx("div",{"data-testid":"g7le-page-settings-panel",style:m$,children:re()}),i.jsx("div",{style:v$,children:i.jsx("button",{type:"button","data-testid":"g7le-page-settings-close",onClick:s,style:_$,children:n("layout_editor.page_settings.close")})})]})}const c$={display:"flex",flexDirection:"column",minWidth:0,maxWidth:"100%",padding:"20px 24px",boxSizing:"border-box",flex:1,minHeight:0,maxHeight:"100%",overflow:"hidden",fontFamily:"system-ui, -apple-system, sans-serif"},u$={display:"flex",alignItems:"center",gap:8,paddingBottom:8,borderBottom:"1px solid #e2e8f0"},p$={flex:1,fontSize:15,fontWeight:700,color:"#0f172a",minWidth:0},f$={border:"none",background:"transparent",color:"#64748b",cursor:"pointer",fontSize:16,lineHeight:1,padding:4},g$={display:"flex",gap:2,paddingTop:8,borderBottom:"1px solid #e2e8f0",flexWrap:"nowrap",overflowX:"auto",overflowY:"hidden",flexShrink:0,minWidth:0},Vu={border:"none",borderBottomWidth:"2px",borderBottomStyle:"solid",borderBottomColor:"transparent",background:"transparent",color:"#64748b",cursor:"pointer",fontSize:12,padding:"6px 10px",whiteSpace:"nowrap"},h$={...Vu,color:"#2563eb",borderBottomColor:"#2563eb",fontWeight:600},y$={marginLeft:4,fontSize:10,color:"#b45309"},b$={margin:0,marginTop:8,fontSize:11,color:"#b45309",background:"#fffbeb",padding:"6px 8px",borderRadius:6},m$={flex:1,overflowY:"auto",paddingTop:12,minWidth:0,minHeight:0},v$={display:"flex",justifyContent:"flex-end",paddingTop:8,borderTop:"1px solid #e2e8f0"},_$={border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer",fontSize:13,padding:"6px 16px"};function Xs(e,t){const n=`/api/admin/templates/${encodeURIComponent(e)}`;return t.kind==="layout"?`${n}/layouts/${t.layoutName}/versions`:`${n}/layout-extensions/${encodeURIComponent(t.extensionId)}/versions`}function x$(e,t,n){const[o,r]=u.useState([]),[s,a]=u.useState(!1),[l,d]=u.useState(null),[c,p]=u.useState(null),f=u.useCallback(async()=>{if(!t){r([]),d(null);return}a(!0),d(null);try{const m=Xs(e,t),h=await fetch(m,{credentials:"same-origin",headers:je()}),v=await h.json().catch(()=>null);if(!h.ok){d(v?.message??`HTTP ${h.status}`),r([]);return}const b=v?.data;r(Array.isArray(b)?b:[])}catch(m){d(m instanceof Error?m.message:"network error"),r([])}finally{a(!1)}},[e,t]),y=u.useCallback(async m=>{if(!t)return{kind:"not_found"};p(m);try{const h=`${Xs(e,t)}/${m}/restore`,v=await fetch(h,{method:"POST",credentials:"same-origin",headers:je({"Content-Type":"application/json"})}),b=await v.json().catch(()=>null);if(v.status===404)return{kind:"not_found"};if(!v.ok)return{kind:"network_error",message:b?.message??`HTTP ${v.status}`};const x=typeof b?.data?.version=="number"?b.data.version:-1;return await n?.(x>=0?x:void 0),{kind:"success",newVersion:x}}catch(h){return{kind:"network_error",message:h instanceof Error?h.message:"network error"}}finally{p(null)}},[e,t,n]),g=u.useCallback(async m=>{if(!t)return{kind:"not_found"};try{const h=`${Xs(e,t)}/${m}`,v=await fetch(h,{credentials:"same-origin",headers:je()}),b=await v.json().catch(()=>null);if(v.status===404)return{kind:"not_found"};if(!v.ok)return{kind:"network_error",message:b?.message??`HTTP ${v.status}`};const x=b?.data;if(!x)return{kind:"not_found"};if(t.kind==="extension"&&!x.full_content){const k=x.content;if(typeof k=="string")try{const w=JSON.parse(k);w&&typeof w=="object"&&(x.full_content=w)}catch{}}return{kind:"success",detail:x}}catch(h){return{kind:"network_error",message:h instanceof Error?h.message:"network error"}}},[e,t]);return{versions:o,isLoading:s,error:l,restoringId:c,loadVersions:f,restore:y,loadVersionDetail:g}}function w$(e,t){const n=e.length,o=t.length,r=Array.from({length:n+1},()=>new Array(o+1).fill(0));for(let s=n-1;s>=0;s--)for(let a=o-1;a>=0;a--)e[s]===t[a]?r[s][a]=r[s+1][a+1]+1:r[s][a]=Math.max(r[s+1][a],r[s][a+1]);return r}function k$(e,t,n){const o=[];let r=0,s=0;const a=e.length,l=t.length;for(;r=n[r][s+1]?(o.push({kind:"remove",oldIndex:r,content:e[r]}),r++):(o.push({kind:"add",newIndex:s,content:t[s]}),s++);for(;rx(P=>P&&{...P,paramsRawValue:I??{},paramsRawValid:!0}),onValidityChange:I=>x(P=>P&&{...P,paramsRawValid:I})}):i.jsxs("div",{"data-testid":"g7le-data-sources-field-params",children:[b.paramRows.length===0&&i.jsx("p",{"data-testid":"g7le-ds-params-empty",style:GC,children:n("layout_editor.data_sources.field.params_empty")}),i.jsx(zr,{value:b.paramRows.map(I=>({key:I.key,value:I.value})),onChange:I=>x({...b,paramRows:I.map(P=>({key:P.key??"",value:P.value??""}))}),keyField:"key",valueField:"value",t:n,candidates:d,testidPrefix:"g7le-ds-params-kv",keyPlaceholder:n("layout_editor.data_sources.field.param_key_placeholder"),valuePlaceholder:"{{query.page ?? 1}}",addLabel:n("layout_editor.data_sources.field.param_add")}),(()=>{const I=Ys(b.paramRows);return Object.keys(I).length===0?null:i.jsxs("div",{style:zu,children:[i.jsxs("button",{type:"button","data-testid":"g7le-ds-params-preview-toggle","aria-expanded":_,onClick:()=>C(P=>!P),style:Mu,children:[_?"▴":"▾"," ",n("layout_editor.data_sources.field.params_preview")]}),_&&i.jsx("pre",{"data-testid":"g7le-ds-params-preview",style:{...Lu,marginTop:4},children:JSON.stringify(I,null,2)})]})})()]}),i.jsx("div",{style:{fontSize:11,color:"#94a3b8",marginTop:4},children:n("layout_editor.data_sources.field.params_hint")})]}),i.jsxs("div",{style:ut,children:[i.jsx("label",{style:ct,children:n("layout_editor.data_sources.field.fallback")}),b.fallbackEnabled?i.jsxs("div",{"data-testid":"g7le-data-sources-field-fallback",style:{display:"flex",flexDirection:"column",gap:6},children:[i.jsx(Nn,{value:b.fallbackValue===void 0?{}:b.fallbackValue,onChange:I=>x({...b,fallbackValue:I}),t:n,path:"fallback",scope:"ds-fallback",renderStringLeaf:I=>i.jsx(Ve,{value:I.value,onChange:I.onChange,t:n,candidates:d,testidPrefix:I.testidPrefix})}),(()=>{const I=b.fallbackValue===void 0?{}:b.fallbackValue;return i.jsxs("div",{style:zu,children:[i.jsxs("button",{type:"button","data-testid":"g7le-ds-fallback-preview-toggle","aria-expanded":E,onClick:()=>S(P=>!P),style:Mu,children:[E?"▴":"▾"," ",n("layout_editor.data_sources.field.fallback_preview")]}),E&&i.jsx("pre",{"data-testid":"g7le-ds-fallback-preview",style:{...Lu,marginTop:4},children:JSON.stringify(I,null,2)})]})})(),i.jsx("button",{type:"button","data-testid":"g7le-data-sources-field-fallback-clear",onClick:()=>x({...b,fallbackEnabled:!1,fallbackValue:void 0}),style:{...rn,padding:"2px 8px",fontSize:11,alignSelf:"flex-start",color:"#dc2626",borderColor:"#fecaca"},children:n("layout_editor.data_sources.field.fallback_clear")})]}):i.jsxs("button",{type:"button","data-testid":"g7le-data-sources-field-fallback-add",onClick:()=>x({...b,fallbackEnabled:!0,fallbackValue:{}}),style:{...rn,padding:"4px 10px",display:"block"},children:["+ ",n("layout_editor.data_sources.field.fallback_add")]})]}),b.type==="websocket"&&i.jsxs("div",{"data-testid":"g7le-ds-ws-section",style:zo,children:[i.jsx("div",{style:Mo,children:n("layout_editor.data_sources.section.websocket")}),i.jsxs("div",{style:ut,children:[i.jsx("label",{style:ct,children:n("layout_editor.data_sources.field.channel")}),i.jsx("input",{"data-testid":"g7le-ds-ws-channel",style:jt,value:b.channel,onChange:I=>x({...b,channel:I.target.value}),placeholder:"core.admin.dashboard"})]}),i.jsxs("div",{style:ut,children:[i.jsx("label",{style:ct,children:n("layout_editor.data_sources.field.event")}),i.jsx("input",{"data-testid":"g7le-ds-ws-event",style:jt,value:b.event,onChange:I=>x({...b,event:I.target.value}),placeholder:"dashboard.stats.updated"})]}),i.jsxs("div",{style:ut,children:[i.jsx("label",{style:ct,children:n("layout_editor.data_sources.field.channel_type")}),i.jsx("select",{"data-testid":"g7le-ds-ws-channeltype",style:jt,value:b.channel_type,onChange:I=>x({...b,channel_type:I.target.value}),children:FC.map(I=>i.jsx("option",{value:I,children:I},I))})]}),i.jsxs("div",{style:ut,children:[i.jsx("label",{style:ct,children:n("layout_editor.data_sources.field.target_source")}),i.jsx("input",{"data-testid":"g7le-ds-ws-target",style:jt,value:b.target_source,onChange:I=>x({...b,target_source:I.target.value}),list:"g7le-ds-ws-target-list"}),i.jsx("datalist",{id:"g7le-ds-ws-target-list",children:h.filter(I=>I.id!==b.id).map(I=>typeof I.id=="string"?i.jsx("option",{value:I.id},I.id):null)})]}),i.jsxs("div",{style:ut,children:[i.jsx("label",{style:ct,children:n("layout_editor.data_sources.field.on_receive")}),i.jsx("div",{"data-testid":"g7le-ds-ws-onreceive",children:i.jsx(Gt,{actions:b.onReceive,onChange:I=>x({...b,onReceive:I}),t:n,recipes:s,candidatePools:z,chipContext:"payload",actionChipCandidates:l?.actionChipCandidates??null,testIdPrefix:"g7le-ds-onreceive-list"})})]})]}),i.jsxs("div",{style:zo,children:[i.jsx("div",{style:Mo,children:n("layout_editor.data_sources.section.on_success")}),i.jsx("div",{"data-testid":"g7le-ds-onsuccess",children:i.jsx(Gt,{actions:b.onSuccess,onChange:I=>x({...b,onSuccess:I}),t:n,recipes:s,candidatePools:z,chipContext:"response",actionChipCandidates:l?.actionChipCandidates??null,testIdPrefix:"g7le-ds-onsuccess-list"})})]}),i.jsxs("div",{style:zo,children:[i.jsx("div",{style:Mo,children:n("layout_editor.data_sources.section.on_error")}),i.jsx("div",{"data-testid":"g7le-ds-onerror",children:i.jsx(Gt,{actions:b.onError,onChange:I=>x({...b,onError:I}),t:n,recipes:s,candidatePools:z,chipContext:"error",actionChipCandidates:l?.actionChipCandidates??null,testIdPrefix:"g7le-ds-onerror-list"})})]}),i.jsxs("div",{style:zo,children:[i.jsx("div",{style:Mo,children:n("layout_editor.data_sources.section.error_handling")}),i.jsxs("div",{"data-testid":"g7le-ds-errorhandling",children:[i.jsx(Fs,{value:b.errorHandling,onChange:I=>x({...b,errorHandling:I}),t:n,mode:"local",testIdPrefix:"g7le-ds-eh",renderActionList:(I,P,F)=>i.jsx(YC,{code:I,action:P,recipes:a,pools:z,t:n,onPatch:F})}),i.jsxs("button",{type:"button","data-testid":"g7le-ds-errorhandling-add",onClick:()=>{const I=Object.keys(b.errorHandling),P=["403","404","500","422","default"].find(F=>!I.includes(F))??"default";x({...b,errorHandling:{...b.errorHandling,[P]:{handler:"toast"}}})},style:{...rn,padding:"4px 10px",marginTop:6},children:["+ ",n("layout_editor.data_sources.add_error_code")]})]})]}),i.jsxs("div",{style:zo,children:[i.jsx("div",{style:Mo,children:n("layout_editor.data_sources.section.load_condition")}),i.jsx("div",{"data-testid":"g7le-ds-loadcondition",children:i.jsx(Eu,{node:LC({if:b.if||void 0}),spec:l??null,t:n,dataSourceCandidates:p,stateKeyCandidates:f,onPatchNode:I=>{const P=PC(I,{});x({...b,if:typeof P.if=="string"?P.if:""})}})})]})]}),!b&&i.jsxs("button",{type:"button","data-testid":"g7le-data-sources-add",onClick:O,style:{...Nu,marginBottom:16},children:["+ ",n("layout_editor.data_sources.add")]}),i.jsxs("div",{className:"g7le-data-sources__list","data-testid":"g7le-data-sources-list",style:{display:"flex",flexDirection:"column",gap:8},children:[h.length===0&&!b&&i.jsx("div",{"data-testid":"g7le-data-sources-empty",style:{fontSize:12,color:"#94a3b8",padding:"12px 0",textAlign:"center"},children:n("layout_editor.data_sources.empty")}),h.map((I,P)=>{const F=typeof I.id=="string"?I.id:`#${P}`,G=typeof I.endpoint=="string"?I.endpoint:"",V=typeof I.label_key=="string"?I.label_key:"",Y=rs(I,y);return i.jsxs("div",{"data-testid":"g7le-data-sources-item","data-ds-id":F,style:{border:"1px solid #e2e8f0",borderRadius:8,padding:"10px 12px",display:"flex",alignItems:"center",gap:8,background:"#ffffff"},children:[i.jsxs("div",{style:{flex:1,minWidth:0},children:[i.jsxs("div",{"data-testid":"g7le-data-sources-item-title",style:{fontWeight:600,fontSize:13,display:"flex",alignItems:"baseline",gap:6,flexWrap:"wrap"},children:[i.jsx("span",{children:Y??F}),Y&&i.jsx("span",{"data-testid":"g7le-data-sources-item-id",style:{fontSize:11,fontWeight:400,color:"#94a3b8"},children:F}),(()=>{const Z=is(I,n);return Z?i.jsx("span",{"data-testid":"g7le-data-sources-ext-badge",style:{fontSize:10,fontWeight:600,color:"#7c3aed",background:"#f5f3ff",border:"1px solid #ddd6fe",borderRadius:4,padding:"1px 6px"},children:Z}):null})()]}),G&&i.jsx("div",{style:{fontSize:11,color:"#94a3b8",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},children:G}),!V&&i.jsx("div",{"data-testid":"g7le-data-sources-nolabel",style:{fontSize:11,color:"#f59e0b"},children:n("layout_editor.data_sources.no_label")})]}),i.jsx("button",{type:"button","data-testid":"g7le-data-sources-edit",onClick:()=>M(I,P),style:{...rn,padding:"4px 10px"},children:n("layout_editor.data_sources.edit")}),i.jsx("button",{type:"button","data-testid":"g7le-data-sources-remove",onClick:()=>B(P),style:{...rn,padding:"4px 10px",color:"#dc2626",borderColor:"#fecaca"},children:n("layout_editor.data_sources.remove")})]},`${F}-${P}`)})]}),m.length>0&&i.jsxs("div",{style:{marginTop:16},children:[i.jsx("div",{style:{fontSize:12,fontWeight:600,color:"#64748b",textTransform:"uppercase",letterSpacing:.4,marginBottom:8},children:n("layout_editor.data_sources.inherited_title")}),i.jsx("div",{style:{display:"flex",flexDirection:"column",gap:6},children:m.map((I,P)=>{const F=typeof I.id=="string"?I.id:`#${P}`,G=T.has(F),V=[],Y=(Z,se)=>{const re=I[Z];re!=null&&V.push([se,typeof re=="object"?JSON.stringify(re):String(re)])};return Y("type",n("layout_editor.data_sources.field.type")),Y("endpoint",n("layout_editor.data_sources.field.endpoint")),Y("method",n("layout_editor.data_sources.field.method")),Y("auth_mode",n("layout_editor.data_sources.field.auth_mode")),Y("loading_strategy",n("layout_editor.data_sources.field.loading_strategy")),Y("auto_fetch",n("layout_editor.data_sources.field.auto_fetch")),Y("params",n("layout_editor.data_sources.field.params")),i.jsxs("div",{"data-testid":"g7le-data-sources-inherited-item","data-ds-id":F,"data-expanded":G?"true":"false",style:{border:"1px dashed #e2e8f0",borderRadius:6,fontSize:12,color:"#64748b",background:"#f8fafc",overflow:"hidden"},children:[i.jsxs("button",{type:"button","data-testid":"g7le-data-sources-inherited-toggle",onClick:()=>A(F),"aria-expanded":G,style:{width:"100%",display:"flex",alignItems:"center",gap:8,padding:"8px 12px",background:"transparent",border:"none",cursor:"pointer",color:"inherit",fontSize:12,textAlign:"left",outline:"none"},children:[i.jsx("span",{style:{flex:"0 0 auto",width:12,transition:"transform 120ms",transform:G?"rotate(90deg)":"none"},children:"▶"}),(()=>{const Z=rs(I,y),se=is(I,n);return i.jsxs("span",{style:{flex:1,display:"inline-flex",alignItems:"baseline",gap:6,flexWrap:"wrap"},children:[i.jsx("span",{style:{fontWeight:600},children:Z??F}),Z&&i.jsx("span",{style:{fontSize:11,fontWeight:400,color:"#94a3b8"},children:F}),se&&i.jsx("span",{"data-testid":"g7le-data-sources-ext-badge",style:{fontSize:10,fontWeight:600,color:"#7c3aed",background:"#f5f3ff",border:"1px solid #ddd6fe",borderRadius:4,padding:"1px 6px"},children:se})]})})()]}),G&&i.jsxs("div",{"data-testid":"g7le-data-sources-inherited-info",style:{padding:"0 12px 10px 32px",display:"flex",flexDirection:"column",gap:3},children:[V.length===0&&i.jsx("span",{style:{color:"#94a3b8"},children:"—"}),V.map(([Z,se])=>i.jsxs("div",{style:{display:"flex",gap:6},children:[i.jsxs("span",{style:{flex:"0 0 auto",color:"#94a3b8"},children:[Z,":"]}),i.jsx("span",{style:{flex:1,minWidth:0,wordBreak:"break-all",color:"#475569"},children:se})]},Z))]})]},`inh-${F}-${P}`)})})]})]}),b&&i.jsxs("div",{className:"g7le-data-sources__footer",style:{flex:"0 0 auto",padding:"12px 16px",borderTop:"1px solid #e2e8f0",background:"#ffffff"},children:[k&&i.jsx("div",{"data-testid":"g7le-data-sources-form-error",role:"alert",style:{fontSize:12,color:"#dc2626",marginBottom:10},children:k}),i.jsxs("div",{style:{display:"flex",gap:8,justifyContent:"flex-end"},children:[i.jsx("button",{type:"button","data-testid":"g7le-data-sources-form-cancel",onClick:N,style:rn,children:n("layout_editor.data_sources.form.cancel")}),i.jsx("button",{type:"button","data-testid":"g7le-data-sources-form-submit",onClick:H,style:Nu,children:n("layout_editor.data_sources.form.save")})]})]})]})}function QC(e){const t=e?.globalHeaders;return Array.isArray(t)?t:[]}function RC(e){const t=e?.scripts;return Array.isArray(t)?t:[]}function e$(e){const t=QC(e.raw),n=RC(e.raw);return i.jsxs("div",{className:"g7le-data-source-tab","data-testid":"g7le-data-source-tab",style:t$,children:[i.jsx(ZC,{...e}),i.jsxs("div",{style:Ou,"data-testid":"g7le-global-headers",children:[i.jsx("div",{style:Bu,children:e.t("layout_editor.data_sources.global_headers")}),t.length===0?i.jsx("p",{"data-testid":"g7le-global-header-empty",style:Ku,children:e.t("layout_editor.data_sources.global_headers_empty")}):t.map((o,r)=>i.jsxs("div",{"data-testid":`g7le-global-header-${r}`,style:Wu,children:[i.jsx("code",{style:ju,children:o.pattern??"*"}),i.jsx("span",{style:n$,children:"→"}),i.jsx("span",{style:Hu,children:Object.keys(o.headers??{}).join(", ")})]},r)),i.jsxs("p",{style:Fu,children:["ⓘ ",e.t("layout_editor.data_sources.global_headers_hint")]})]}),i.jsxs("div",{style:Ou,"data-testid":"g7le-scripts",children:[i.jsx("div",{style:Bu,children:e.t("layout_editor.data_sources.scripts")}),n.length===0?i.jsx("p",{"data-testid":"g7le-script-empty",style:Ku,children:e.t("layout_editor.data_sources.scripts_empty")}):n.map((o,r)=>i.jsxs("div",{"data-testid":`g7le-script-${o.id??r}`,style:Wu,children:[i.jsx("code",{style:ju,children:o.id??`#${r}`}),i.jsx("span",{style:Hu,children:o.src??""}),o.loading?i.jsx("span",{style:o$,children:o.loading}):null]},o.id??r)),i.jsxs("p",{style:Fu,children:["ⓘ ",e.t("layout_editor.data_sources.scripts_hint")]})]})]})}const t$={display:"flex",flexDirection:"column",gap:16,minWidth:0},Ou={borderTop:"1px solid #e2e8f0",paddingTop:12,display:"flex",flexDirection:"column",gap:6},Bu={fontSize:12,fontWeight:700,color:"#475569"},Wu={display:"flex",alignItems:"center",gap:8,fontSize:12,padding:"4px 8px",border:"1px solid #f1f5f9",borderRadius:6},ju={fontSize:11,color:"#0f172a",fontFamily:"monospace"},n$={color:"#94a3b8"},Hu={flex:1,minWidth:0,color:"#475569",wordBreak:"break-all"},o$={fontSize:10,color:"#64748b",background:"#f1f5f9",borderRadius:4,padding:"0 4px"},Fu={margin:0,fontSize:11,color:"#94a3b8"},Ku={margin:0,fontSize:12,color:"#94a3b8"},r$=["meta","seo","init","overlay","computed","state","error","data"],i$=["401","403","404","500","503"],s$={meta:"layout_editor.page_settings.tab.meta",seo:"layout_editor.page_settings.tab.seo",init:"layout_editor.page_settings.tab.init",overlay:"layout_editor.page_settings.tab.overlay",computed:"layout_editor.page_settings.tab.computed",state:"layout_editor.page_settings.tab.state",error:"layout_editor.page_settings.tab.error",data:"layout_editor.page_settings.tab.data"};async function a$(e){if(typeof fetch!="function"||!e)return[];try{const t=await fetch(en(`/api/admin/templates/${encodeURIComponent(e)}/editor/seo-candidates`,"json"),{credentials:"same-origin",headers:je()});if(!t.ok)return[];const o=(await t.json().catch(()=>null))?.data?.extensions;if(!Array.isArray(o))return[];const r=[];for(const s of o)if(s&&typeof s=="object"){const a=s.type,l=s.id,d=s.label;typeof a=="string"&&typeof l=="string"&&l.length>0&&r.push({type:a,id:l,name:typeof d=="string"?d:l})}return r}catch{return[]}}function l$(e,t){let n=0;for(const o of e)Yn(o,t).kind==="advanced"&&(n+=1);return n}function d$(e,t){let n=0;for(const o of Object.keys(e))Ed(e[o],t).kind==="advanced"&&(n+=1);return n}function c$(e,t,n){const o=e("meta",{})??{},r=o.title;if(typeof r=="string"&&r.length>0){if(r.startsWith("$t:")){const a=n(r.slice(3));if(a&&a!==r.slice(3))return a}else if(!/\{\{[\s\S]*\}\}/.test(r))return r}const s=o.editor_label;return typeof s=="string"&&s.length>0?s.startsWith("$t:")?n(s.slice(3)):s:t||""}function u$({templateIdentifier:e,spec:t,t:n,resolveLabel:o,permissionCandidates:r,onClose:s,initialTab:a="meta",extensionsFetcher:l}){const{state:d}=Je(),{raw:c,getValue:p,patch:f}=zm(),[y,g]=u.useState(a),m=d.selectedRoute?.path??"",h=u.useMemo(()=>zd(d.routeTree,o),[d.routeTree,o]),v=u.useMemo(()=>Md(c),[c]),b=u.useMemo(()=>Ld(t),[t]),x=u.useMemo(()=>Qm(c,o),[c,o]),k=Nd({raw:c,spec:t}),w=u.useMemo(()=>tb(t),[t]),_=u.useMemo(()=>nb(t),[t]),C=u.useMemo(()=>ob(t),[t]),E=u.useMemo(()=>rb(t),[t]),S=u.useMemo(()=>wo(w),[w]),T=u.useMemo(()=>xd(_),[_]),$=p("init_actions",void 0)??p("initActions",[]),A=p("computed",{}),z=p("__computedSource",{}),D=p("errorHandling",{}),O=p("transition_overlay",void 0),M=(p("__editor",{})??{}).original,B=u.useMemo(()=>{if(M===void 0)return;const U=M.transition_overlay;return U&&typeof U=="object"&&!Array.isArray(U)?U:{}},[M]),N=u.useMemo(()=>{const U=O;if(!U||typeof U!="object"||Array.isArray(U))return;const ee=B??{},fe={};for(const[K,ne]of Object.entries(U))K in ee||(fe[K]=ne);return Object.keys(fe).length>0?fe:void 0},[O,B]),[H,L]=u.useState([]);u.useEffect(()=>{let U=!1;const ee=l??a$;return(async()=>{const fe=await ee(e);U||L(fe)})(),()=>{U=!0}},[e,l]);const I=(p("meta",{})??{}).seo?.page_type,P=Jm({raw:c,spec:t,pageType:typeof I=="string"?I:null}),F=u.useMemo(()=>Xm(c,t),[c,t]),G=u.useMemo(()=>({meta:0,seo:0,init:l$($,S),overlay:0,computed:d$(A,T),state:0,error:0,data:0}),[$,S,A,T]),V=u.useMemo(()=>Object.values(G).reduce((U,ee)=>U+ee,0),[G]),Y=u.useMemo(()=>Zm(c,o,n),[c,o,n]),Z=u.useCallback(U=>{const fe={...M?.computed??{}};delete fe[U];const K={...A};delete K[U],f("computed",K,Object.keys(fe).length===0?void 0:fe)},[M,A,f]),se=c$(p,m,n),re=()=>{switch(y){case"meta":return i.jsx(yx,{getValue:p,patch:f,t:n,fieldT:o,spec:t,permissionCandidates:r,bindingCandidates:k});case"seo":return i.jsx(nw,{getValue:p,patch:f,t:n,templateIdentifier:e,candidates:P,availableExtensions:H,dataSourceOptions:Y,previewUrl:m||"/",seedContext:F});case"init":return i.jsx(wk,{actions:$,onChange:U=>f("init_actions",U),recipes:w,t:o,bindingCandidates:k,pageCandidates:h,dataSourceCandidates:v,stateKeyCandidates:b,modalCandidates:x});case"overlay":return i.jsx(Uk,{value:O,ownValue:B,baseValue:N,patch:U=>f("transition_overlay",U),t:o,loadingComponents:E,progressiveDataSources:Y,bindingCandidates:k});case"computed":return i.jsx(z1,{computed:A,onChange:U=>f("computed",U),recipes:_,t:o,sampleContext:F,dataSourceCandidates:v,bindingCandidates:k,computedSource:z,onRevert:Z});case"state":return i.jsx(gS,{raw:c,own:M?{initLocal:M.initLocal,initGlobal:M.initGlobal,initIsolated:M.initIsolated,state:M.state}:void 0,patch:f,t:n});case"error":return i.jsx(jS,{value:D,onChange:U=>f("errorHandling",U),t:o,errorConfigCodes:i$,templateCodes:Object.keys(D),ownCodes:Object.keys(M?.errorHandling??{}),recipes:C,pageCandidates:h,modalCandidates:x,dataSourceCandidates:v,stateKeyCandidates:b});case"data":return i.jsx(e$,{raw:c,t:n,resolveLabel:o,onChange:(U,ee)=>f("data_sources",U,ee),onClose:s,actionRecipes:w,errorRecipes:C,conditionSpec:t,bindingCandidates:k,pageCandidates:h,dataSourceCandidates:v,stateKeyCandidates:b});default:return i.jsx("div",{})}};return i.jsxs("div",{className:"g7le-page-settings","data-testid":"g7le-page-settings",style:p$,children:[i.jsxs("div",{style:f$,children:[i.jsx("span",{"data-testid":"g7le-page-settings-title",style:g$,children:n("layout_editor.page_settings.title",{name:se})}),i.jsx("button",{type:"button","data-testid":"g7le-page-settings-close-x",onClick:s,"aria-label":n("layout_editor.page_settings.close"),style:h$,children:"✕"})]}),i.jsx("div",{role:"tablist",style:y$,children:r$.map(U=>{const ee=G[U];return i.jsxs("button",{type:"button",role:"tab","aria-selected":y===U,"data-testid":`g7le-page-settings-tab-${U}`,"data-active":y===U?"true":"false",onClick:()=>g(U),style:y===U?b$:Vu,children:[n(s$[U]),ee>0?i.jsxs("span",{"data-testid":`g7le-page-settings-tab-badge-${U}`,style:m$,children:["(",ee,")"]}):null]},U)})}),V>0?i.jsxs("p",{"data-testid":"g7le-page-settings-advanced-summary",style:v$,children:["⚠ ",n("layout_editor.page_settings.advanced_summary",{count:V})," ⓘ"]}):null,i.jsx("div",{"data-testid":"g7le-page-settings-panel",style:_$,children:re()}),i.jsx("div",{style:x$,children:i.jsx("button",{type:"button","data-testid":"g7le-page-settings-close",onClick:s,style:w$,children:n("layout_editor.page_settings.close")})})]})}const p$={display:"flex",flexDirection:"column",minWidth:0,maxWidth:"100%",padding:"20px 24px",boxSizing:"border-box",flex:1,minHeight:0,maxHeight:"100%",overflow:"hidden",fontFamily:"system-ui, -apple-system, sans-serif"},f$={display:"flex",alignItems:"center",gap:8,paddingBottom:8,borderBottom:"1px solid #e2e8f0"},g$={flex:1,fontSize:15,fontWeight:700,color:"#0f172a",minWidth:0},h$={border:"none",background:"transparent",color:"#64748b",cursor:"pointer",fontSize:16,lineHeight:1,padding:4},y$={display:"flex",gap:2,paddingTop:8,borderBottom:"1px solid #e2e8f0",flexWrap:"nowrap",overflowX:"auto",overflowY:"hidden",flexShrink:0,minWidth:0},Vu={border:"none",borderBottomWidth:"2px",borderBottomStyle:"solid",borderBottomColor:"transparent",background:"transparent",color:"#64748b",cursor:"pointer",fontSize:12,padding:"6px 10px",whiteSpace:"nowrap"},b$={...Vu,color:"#2563eb",borderBottomColor:"#2563eb",fontWeight:600},m$={marginLeft:4,fontSize:10,color:"#b45309"},v$={margin:0,marginTop:8,fontSize:11,color:"#b45309",background:"#fffbeb",padding:"6px 8px",borderRadius:6},_$={flex:1,overflowY:"auto",paddingTop:12,minWidth:0,minHeight:0},x$={display:"flex",justifyContent:"flex-end",paddingTop:8,borderTop:"1px solid #e2e8f0"},w$={border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer",fontSize:13,padding:"6px 16px"};function Xs(e,t){const n=`/api/admin/templates/${encodeURIComponent(e)}`;return t.kind==="layout"?`${n}/layouts/${t.layoutName}/versions`:`${n}/layout-extensions/${encodeURIComponent(t.extensionId)}/versions`}const Uu=100,qu=500;function k$(e,t,n){const[o,r]=u.useState([]),[s,a]=u.useState(!1),[l,d]=u.useState(!1),[c,p]=u.useState(null),[f,y]=u.useState(null),[g,m]=u.useState(Uu),[h,v]=u.useState(!1),b=u.useCallback(async(C,E)=>{if(!t){r([]),p(null),v(!1);return}E==="more"?d(!0):a(!0),p(null);try{const S=`${Xs(e,t)}?limit=${C}`,T=await fetch(S,{credentials:"same-origin",headers:je()}),$=await T.json().catch(()=>null);if(!T.ok){p($?.message??`HTTP ${T.status}`),E==="reload"&&r([]);return}const A=$?.data,z=Array.isArray(A)?A:[];r(z),v(z.length>=C&&C{await b(g,"reload")},[b,g]),k=u.useCallback(async()=>{const C=Math.min(g+Uu,qu);if(C<=g){v(!1);return}await b(C,"more")},[b,g]),w=u.useCallback(async C=>{if(!t)return{kind:"not_found"};y(C);try{const E=`${Xs(e,t)}/${C}/restore`,S=await fetch(E,{method:"POST",credentials:"same-origin",headers:je({"Content-Type":"application/json"})}),T=await S.json().catch(()=>null);if(S.status===404)return{kind:"not_found"};if(!S.ok)return{kind:"network_error",message:T?.message??`HTTP ${S.status}`};const $=typeof T?.data?.version=="number"?T.data.version:-1;return await n?.($>=0?$:void 0),{kind:"success",newVersion:$}}catch(E){return{kind:"network_error",message:E instanceof Error?E.message:"network error"}}finally{y(null)}},[e,t,n]),_=u.useCallback(async C=>{if(!t)return{kind:"not_found"};try{const E=`${Xs(e,t)}/${C}`,S=await fetch(E,{credentials:"same-origin",headers:je()}),T=await S.json().catch(()=>null);if(S.status===404)return{kind:"not_found"};if(!S.ok)return{kind:"network_error",message:T?.message??`HTTP ${S.status}`};const $=T?.data;if(!$)return{kind:"not_found"};if(t.kind==="extension"&&!$.full_content){const A=$.content;if(typeof A=="string")try{const z=JSON.parse(A);z&&typeof z=="object"&&($.full_content=z)}catch{}}return{kind:"success",detail:$}}catch(E){return{kind:"network_error",message:E instanceof Error?E.message:"network error"}}},[e,t]);return{versions:o,isLoading:s,isLoadingMore:l,error:c,restoringId:f,hasMore:h,loadVersions:x,loadMore:k,restore:w,loadVersionDetail:_}}function S$(e,t){const n=e.length,o=t.length,r=Array.from({length:n+1},()=>new Array(o+1).fill(0));for(let s=n-1;s>=0;s--)for(let a=o-1;a>=0;a--)e[s]===t[a]?r[s][a]=r[s+1][a+1]+1:r[s][a]=Math.max(r[s+1][a],r[s][a+1]);return r}function C$(e,t,n){const o=[];let r=0,s=0;const a=e.length,l=t.length;for(;r=n[r][s+1]?(o.push({kind:"remove",oldIndex:r,content:e[r]}),r++):(o.push({kind:"add",newIndex:s,content:t[s]}),s++);for(;r0&&t[t.length-1]===""&&t.pop(),t}const qu=4e3;function S$(e,t,n=3){const o=Uu(e),r=Uu(t);let s=0;const a=Math.min(o.length,r.length);for(;squ||p.length>qu)return{hunks:[],addedCount:-1,removedCount:-1,identical:!1};const f=w$(c,p),y=k$(c,p,f),g=[];let m=s,h=s,v=0,b=0;for(let w=0;w{const s=e.slice(r.start,r.end+1);let a=0,l=0,d=0,c=0;for(const g of s)g.oldLine!==null&&(a===0&&(a=g.oldLine),d++),g.newLine!==null&&(l===0&&(l=g.newLine),c++);const p=a||(d===0?0:1),f=l||(c===0?0:1);return{header:`@@ -${p},${d} +${f},${c} @@`,oldStart:p,oldCount:d,newStart:f,newCount:c,lines:s}})}function Gu(e){return JSON.stringify(Js(e),null,2)}function Js(e){if(Array.isArray(e))return e.map(Js);if(e&&typeof e=="object"){const t=e,n={};for(const o of Object.keys(t).sort())n[o]=Js(t[o]);return n}return e}const $$={display:"flex",flexDirection:"column",height:"100%",minHeight:0},E$={display:"flex",alignItems:"center",gap:8,padding:"12px 16px",borderBottom:"1px solid #e2e8f0",background:"#f8fafc"},I$={display:"inline-flex",alignItems:"center",gap:4,padding:"4px 10px",borderRadius:6,border:"1px solid #cbd5e1",background:"#ffffff",color:"#0f172a",fontSize:12,fontWeight:600,cursor:"pointer",outline:"none"},A$={marginLeft:"auto",border:"none",background:"transparent",fontSize:16,color:"#64748b",cursor:"pointer",outline:"none"},T$={display:"flex",alignItems:"center",gap:12,padding:"8px 16px",borderBottom:"1px solid #e2e8f0",background:"#ffffff",fontSize:12,fontWeight:600},D$={flex:1,minHeight:0,overflow:"auto",background:"#ffffff",fontFamily:'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace',fontSize:12,lineHeight:1.5},Yu={padding:"24px 12px",textAlign:"center",color:"#94a3b8",fontSize:13},N$={padding:"2px 12px",background:"#f1f5f9",color:"#64748b",borderTop:"1px solid #e2e8f0",borderBottom:"1px solid #e2e8f0",userSelect:"none"};function z$(e){return e==="add"?{row:{background:"#e6ffed"},gutter:{background:"#cdffd8",color:"#22863a"},sign:"+",signColor:"#22863a"}:e==="remove"?{row:{background:"#ffeef0"},gutter:{background:"#ffdce0",color:"#b31d28"},sign:"-",signColor:"#b31d28"}:{row:{background:"#ffffff"},gutter:{background:"#f8fafc",color:"#94a3b8"},sign:" ",signColor:"#94a3b8"}}const Xu={display:"inline-block",width:44,textAlign:"right",padding:"0 6px",color:"#94a3b8",userSelect:"none",borderRight:"1px solid #e2e8f0",whiteSpace:"nowrap"};function M$({oldVersion:e,newVersion:t,oldContent:n,newContent:o,t:r,onBack:s,onClose:a}){const l=u.useMemo(()=>{const c=Gu(n),p=Gu(o);return S$(c,p)},[n,o]),d=l.addedCount<0;return i.jsxs("div",{className:"g7le-version-diff","data-testid":"g7le-version-diff",style:$$,children:[i.jsxs("div",{"data-modal-drag-handle":!0,style:E$,children:[i.jsxs("button",{type:"button",onClick:s,style:I$,"data-testid":"g7le-version-diff-back",children:["← ",r("layout_editor.version_history.diff_back")]}),i.jsx("span",{style:{fontSize:14,fontWeight:700},"data-testid":"g7le-version-diff-title",children:r("layout_editor.version_history.diff_title",{old:e,new:t})}),i.jsx("button",{type:"button","aria-label":r("layout_editor.version_history.close"),onClick:a,style:A$,"data-testid":"g7le-version-diff-close",children:"✕"})]}),!d&&i.jsxs("div",{style:T$,"data-testid":"g7le-version-diff-summary",children:[i.jsx("span",{style:{color:"#16a34a"},"data-testid":"g7le-version-diff-added",children:r("layout_editor.version_history.added",{count:Math.max(0,l.addedCount)})}),i.jsx("span",{style:{color:"#dc2626"},"data-testid":"g7le-version-diff-removed",children:r("layout_editor.version_history.removed",{count:Math.max(0,l.removedCount)})})]}),i.jsx("div",{style:D$,children:d?i.jsx("div",{style:Yu,"data-testid":"g7le-version-diff-too-large",children:r("layout_editor.version_history.diff_too_large")}):l.identical?i.jsx("div",{style:Yu,"data-testid":"g7le-version-diff-identical",children:r("layout_editor.version_history.diff_identical")}):l.hunks.map((c,p)=>i.jsxs("div",{"data-testid":`g7le-version-diff-hunk-${p}`,children:[i.jsx("div",{style:N$,"data-testid":`g7le-version-diff-hunk-header-${p}`,children:c.header}),c.lines.map((f,y)=>{const g=z$(f.kind);return i.jsxs("div",{style:{display:"flex",alignItems:"stretch",...g.row},"data-kind":f.kind,"data-testid":`g7le-version-diff-line-${p}-${y}`,children:[i.jsx("span",{style:{...Xu,...g.gutter},"aria-hidden":"true",children:f.oldLine??""}),i.jsx("span",{style:{...Xu,...g.gutter},"aria-hidden":"true",children:f.newLine??""}),i.jsx("span",{style:{display:"inline-block",width:16,textAlign:"center",color:g.signColor,userSelect:"none",fontWeight:700},"aria-hidden":"true",children:g.sign}),i.jsx("span",{style:{whiteSpace:"pre-wrap",wordBreak:"break-word",flex:1,padding:"0 4px"},children:f.content})]},y)})]},p))})]})}const L$={display:"flex",flexDirection:"column",height:"100%",minHeight:0},P$={display:"flex",alignItems:"center",gap:8,padding:"12px 16px",borderBottom:"1px solid #e2e8f0",background:"#f8fafc"},O$={marginLeft:"auto",border:"none",background:"transparent",fontSize:16,color:"#64748b",cursor:"pointer",outline:"none"},B$={flex:1,minHeight:0,overflowY:"auto",padding:12},Ju={padding:"24px 12px",textAlign:"center",color:"#94a3b8",fontSize:13},W$={margin:"8px 16px 0",padding:"8px 12px",borderRadius:6,background:"#fef2f2",color:"#b91c1c",fontSize:12,border:"1px solid #fecaca"},j$={display:"flex",flexDirection:"column",gap:8},H$={display:"flex",alignItems:"center",gap:12,padding:"10px 12px",borderRadius:8,border:"1px solid #e2e8f0",background:"#ffffff"},F$={display:"inline-flex",alignItems:"center",justifyContent:"center",minWidth:44,padding:"2px 8px",borderRadius:999,background:"#eff6ff",color:"#1d4ed8",fontSize:12,fontWeight:700},K$={display:"inline-flex",alignItems:"center",padding:"1px 8px",borderRadius:999,background:"#0f172a",color:"#ffffff",fontSize:10,fontWeight:700,letterSpacing:.3},V$={display:"flex",alignItems:"center",gap:10,fontSize:11,fontWeight:600},U$={display:"flex",flexDirection:"column",gap:2,minWidth:0,flex:1},Zu={padding:"6px 12px",borderRadius:6,border:"1px solid #cbd5e1",background:"#ffffff",color:"#0f172a",fontSize:12,fontWeight:600,cursor:"pointer",outline:"none",whiteSpace:"nowrap"};function Qu(e){return e.full_content&&typeof e.full_content=="object"?e.full_content:{endpoint:e.endpoint,components:e.components,data_sources:e.data_sources,metadata:e.metadata}}function q$(e){if(!e)return"";const t=Date.parse(e);if(Number.isNaN(t))return e;try{return new Date(t).toLocaleString()}catch{return e}}function G$({templateIdentifier:e,target:t,t:n,onRestored:o,onClose:r}){const{versions:s,isLoading:a,error:l,restoringId:d,loadVersions:c,restore:p,loadVersionDetail:f}=x$(e,t,o),[y,g]=u.useState(null),[m,h]=u.useState(null),[v,b]=u.useState(null);u.useEffect(()=>{c()},[c]);const x=u.useCallback(async _=>{g(null),b(_.version);try{const S=await f(_.version);if(S.kind!=="success"){g(S.kind==="not_found"?n("layout_editor.version_history.restore_not_found"):S.message);return}const E=_.version-1,C=s.some($=>$.version===E);let N={};if(C){const $=await f(E);if($.kind==="success")N=Qu($.detail);else if($.kind==="network_error"){g($.message);return}}h({oldVersion:C?E:0,newVersion:_.version,oldContent:N,newContent:Qu(S.detail)})}finally{b(null)}},[f,s,n]),k=u.useCallback(async _=>{g(null);const S=n("layout_editor.version_history.restore_confirm");if(!(typeof window<"u"?window.confirm(S):!0))return;const C=await p(_);C.kind==="success"?r():C.kind==="not_found"?g(n("layout_editor.version_history.restore_not_found")):g(C.message)},[p,r,n]),w=y??l;return m?i.jsx(M$,{oldVersion:m.oldVersion,newVersion:m.newVersion,oldContent:m.oldContent,newContent:m.newContent,t:n,onBack:()=>h(null),onClose:r}):i.jsxs("div",{className:"g7le-version-history","data-testid":"g7le-version-history",style:L$,children:[i.jsxs("div",{"data-modal-drag-handle":!0,style:P$,children:[i.jsxs("span",{style:{fontSize:14,fontWeight:700},"data-testid":"g7le-version-history-title",children:["🕘 ",n("layout_editor.version_history.title")]}),i.jsx("button",{type:"button","aria-label":n("layout_editor.version_history.close"),onClick:r,style:O$,"data-testid":"g7le-version-history-close",children:"✕"})]}),w&&i.jsx("div",{style:W$,"data-testid":"g7le-version-history-error",children:w}),i.jsx("div",{style:B$,children:a?i.jsx("div",{style:Ju,"data-testid":"g7le-version-history-loading",children:n("layout_editor.version_history.loading")}):s.length===0?i.jsx("div",{style:Ju,"data-testid":"g7le-version-history-empty",children:n("layout_editor.version_history.empty")}):i.jsx("div",{"data-testid":"g7le-version-history-list",style:j$,children:s.map((_,S)=>{const E=_.changes_summary,C=E?.added_count??0,N=E?.removed_count??0,$=E?.char_diff??0,A=q$(_.created_at),z=d===_.id,D=S===0;return i.jsxs("div",{style:H$,"data-testid":`g7le-version-row-${_.id}`,children:[i.jsxs("span",{style:F$,"data-testid":`g7le-version-badge-${_.id}`,children:["v",_.version]}),i.jsxs("div",{style:U$,children:[i.jsxs("span",{style:{display:"flex",alignItems:"center",gap:8},children:[A&&i.jsx("span",{style:{fontSize:12,color:"#475569",fontWeight:600},children:A}),D&&i.jsx("span",{style:K$,"data-testid":`g7le-version-latest-${_.id}`,children:n("layout_editor.version_history.latest")})]}),i.jsxs("span",{style:V$,"data-testid":`g7le-version-summary-${_.id}`,children:[i.jsx("span",{style:{color:"#16a34a"},"data-testid":`g7le-version-added-${_.id}`,children:n("layout_editor.version_history.added",{count:C})}),i.jsx("span",{style:{color:"#dc2626"},"data-testid":`g7le-version-removed-${_.id}`,children:n("layout_editor.version_history.removed",{count:N})}),i.jsx("span",{style:{color:"#94a3b8",fontWeight:500},"data-testid":`g7le-version-chars-${_.id}`,children:n("layout_editor.version_history.chars",{diff:`${$>=0?"+":""}${$}`})})]}),i.jsx("span",{style:{fontSize:11,color:"#94a3b8"},"data-testid":`g7le-version-author-${_.id}`,children:n("layout_editor.version_history.saved_by",{name:_.created_by_name??n("layout_editor.version_history.unknown_author")})})]}),i.jsxs("div",{style:{display:"flex",alignItems:"center",gap:6},children:[i.jsx("button",{type:"button",onClick:()=>{x(_)},disabled:v!==null,style:{...Zu,background:"#f8fafc",opacity:v!==null?.6:1},"data-testid":`g7le-version-compare-${_.id}`,children:v===_.version?n("layout_editor.version_history.comparing"):n("layout_editor.version_history.compare")}),!D&&i.jsx("button",{type:"button",onClick:()=>{k(_.id)},disabled:z||d!==null,style:{...Zu,opacity:z||d!==null?.6:1},"data-testid":`g7le-version-restore-${_.id}`,children:n(z?"layout_editor.version_history.restoring":"layout_editor.version_history.restore")})]})]},_.id)})})})]})}const Ru=[{id:"copy",group:"clipboard",combos:[{key:"c",mod:!0}],labelKey:"layout_editor.shortcuts.action.copy",requiresSelection:!0},{id:"cut",group:"clipboard",combos:[{key:"x",mod:!0}],labelKey:"layout_editor.shortcuts.action.cut",requiresSelection:!0},{id:"paste",group:"clipboard",combos:[{key:"v",mod:!0}],labelKey:"layout_editor.shortcuts.action.paste"},{id:"openProps",group:"element",combos:[{key:"enter"}],labelKey:"layout_editor.shortcuts.action.open_props",requiresSelection:!0},{id:"delete",group:"element",combos:[{key:"delete"},{key:"backspace"}],labelKey:"layout_editor.shortcuts.action.delete",requiresSelection:!0},{id:"selectParent",group:"element",combos:[{key:"arrowup"}],labelKey:"layout_editor.shortcuts.action.select_parent",requiresSelection:!0},{id:"deselect",group:"element",combos:[{key:"escape"}],labelKey:"layout_editor.shortcuts.action.deselect"},{id:"addElement",group:"element",combos:[{key:"/",mod:!0}],labelKey:"layout_editor.shortcuts.action.add_element"},{id:"undo",group:"history",combos:[{key:"z",mod:!0}],labelKey:"layout_editor.shortcuts.action.undo"},{id:"redo",group:"history",combos:[{key:"z",mod:!0,shift:!0},{key:"y",mod:!0}],labelKey:"layout_editor.shortcuts.action.redo"},{id:"editCode",group:"view",combos:[{key:"e",mod:!0}],labelKey:"layout_editor.shortcuts.action.edit_code"},{id:"preview",group:"view",combos:[{key:"p",mod:!0}],labelKey:"layout_editor.shortcuts.action.preview"},{id:"translations",group:"view",combos:[{key:"l",mod:!0}],labelKey:"layout_editor.shortcuts.action.translations"},{id:"help",group:"view",combos:[{key:"?"}],labelKey:"layout_editor.shortcuts.action.help"},{id:"reset",group:"document",combos:[{key:"r",mod:!0,shift:!0}],labelKey:"layout_editor.shortcuts.action.reset"},{id:"save",group:"document",combos:[{key:"s",mod:!0}],labelKey:"layout_editor.shortcuts.action.save"},{id:"exit",group:"document",combos:[{key:"escape"}],labelKey:"layout_editor.shortcuts.action.exit"}];function Y$(e){return{key:(e.key||"").toLowerCase(),mod:e.ctrlKey||e.metaKey,shift:e.shiftKey,alt:e.altKey}}function X$(e,t){return e.key===t.key&&!!e.mod==!!t.mod&&!!e.shift==!!t.shift&&!!e.alt==!!t.alt}function J$(e){for(const t of Ru)if(t.combos.some(n=>X$(n,e)))return t;return null}function Z$(e,t){const n=[];return e.mod&&n.push(t?"⌘":"Ctrl"),e.shift&&n.push(t?"⇧":"Shift"),e.alt&&n.push(t?"⌥":"Alt"),n.push(Q$(e.key)),n.join(" + ")}function Q$(e){return{arrowup:"↑",arrowdown:"↓",arrowleft:"←",arrowright:"→",enter:"Enter",escape:"Esc",delete:"Delete",backspace:"Backspace"," ":"Space"}[e]??(e.length===1?e.toUpperCase():e)}const R$=["clipboard","element","history","view","document"],eE={clipboard:"layout_editor.shortcuts.group.clipboard",element:"layout_editor.shortcuts.group.element",history:"layout_editor.shortcuts.group.history",view:"layout_editor.shortcuts.group.view",document:"layout_editor.shortcuts.group.document"};function tE(){return typeof navigator>"u"?!1:/mac|iphone|ipad|ipod/i.test(navigator.platform||navigator.userAgent||"")}function nE({t:e,onClose:t,isMac:n}){const o=n??tE(),r=u.useMemo(()=>{const s=new Map;for(const a of Ru){const l=s.get(a.group)??[];l.push(a),s.set(a.group,l)}return s},[]);return i.jsxs("div",{className:"g7le-shortcut-help","data-testid":"g7le-shortcut-help",style:oE,children:[i.jsxs("div",{style:rE,children:[i.jsx("span",{style:iE,children:e("layout_editor.shortcuts.title")}),i.jsx("button",{type:"button","data-testid":"g7le-shortcut-help-close",onClick:t,"aria-label":e("layout_editor.shortcuts.close"),style:sE,children:"✕"})]}),i.jsx("div",{style:aE,children:R$.map(s=>{const a=r.get(s);return!a||a.length===0?null:i.jsxs("div",{style:lE,"data-testid":`g7le-shortcut-group-${s}`,children:[i.jsx("div",{style:dE,children:e(eE[s])}),i.jsx("table",{style:cE,children:i.jsx("tbody",{children:a.map(l=>i.jsxs("tr",{"data-testid":`g7le-shortcut-row-${l.id}`,children:[i.jsx("td",{style:uE,children:e(l.labelKey)}),i.jsx("td",{style:pE,children:l.combos.map((d,c)=>i.jsxs(u.Fragment,{children:[c>0&&i.jsx("span",{style:gE,children:e("layout_editor.shortcuts.or")}),i.jsx("kbd",{style:fE,children:Z$(d,o)})]},c))})]},l.id))})})]},s)})}),i.jsx("div",{style:hE,children:e("layout_editor.shortcuts.footnote")})]})}const oE={display:"flex",flexDirection:"column",padding:20,gap:16,width:"100%",boxSizing:"border-box"},rE={display:"flex",alignItems:"center",justifyContent:"space-between",paddingBottom:12,borderBottom:"1px solid #e2e8f0"},iE={fontSize:15,fontWeight:700,color:"#0f172a"},sE={width:28,height:28,border:"none",background:"transparent",color:"#64748b",cursor:"pointer",fontSize:14,borderRadius:6},aE={display:"grid",gridTemplateColumns:"1fr 1fr",columnGap:28,rowGap:18,alignItems:"start"},lE={display:"flex",flexDirection:"column",gap:6},dE={fontSize:12,fontWeight:700,color:"#2563eb"},cE={width:"100%",borderCollapse:"collapse"},uE={padding:"4px 0",fontSize:12.5,color:"#334155",textAlign:"left",whiteSpace:"nowrap",verticalAlign:"middle"},pE={padding:"4px 0",paddingLeft:12,textAlign:"right",verticalAlign:"middle"},fE={display:"inline-block",padding:"1px 6px",fontSize:11,fontFamily:"ui-monospace, monospace",color:"#0f172a",background:"#f1f5f9",border:"1px solid #cbd5e1",borderBottomWidth:2,borderRadius:5,whiteSpace:"nowrap"},gE={fontSize:11,color:"#94a3b8",margin:"0 4px"},hE={fontSize:11,color:"#94a3b8",paddingTop:12,borderTop:"1px solid #f1f5f9"};function yE(e){if(!e||!(e instanceof HTMLElement))return!1;const t=e.tagName;return!!(t==="INPUT"||t==="TEXTAREA"||t==="SELECT"||e.isContentEditable)}function bE(){return typeof document>"u"?!1:!!document.querySelector(".g7le-modal-backdrop, .g7le-modal")}function ep(e){const{handlers:t,hasSelection:n,enabled:o=!0}=e;u.useEffect(()=>{if(!o||typeof window>"u")return;const r=s=>{if(yE(s.target))return;const a=Y$(s),l=J$(a);if(!l||bE()||l.requiresSelection&&!n)return;if(l.key==="escape"||a.key==="escape"){if(n){const c=t.deselect;c&&(s.preventDefault(),c())}else{const c=t.exit;c&&(s.preventDefault(),c())}return}const d=t[l.id];d&&(s.preventDefault(),d())};return window.addEventListener("keydown",r),()=>window.removeEventListener("keydown",r)},[t,n,o])}function mE(e){const{result:t,onDismiss:n,onLoadLatest:o,onKeepMyChanges:r}=e,{t:s}=tt();if(u.useEffect(()=>{if(!t||t.kind==="concurrent_modification"||t.kind==="validation_failed")return;const a=setTimeout(n,5e3);return()=>clearTimeout(a)},[t,n]),!t)return null;if(t.kind==="success")return i.jsx(Lo,{tone:"success",testId:"g7le-save-banner-success",onDismiss:n,children:s("layout_editor.save.success")});if(t.kind==="validation_failed"){const a=t.errors??{},l=Object.entries(a);return i.jsxs(Lo,{tone:"error",testId:"g7le-save-banner-validation",onDismiss:n,children:[i.jsx("strong",{children:s("layout_editor.save.validation_failed")}),l.length>0&&i.jsx("ul",{style:tp,"data-testid":"g7le-save-banner-validation-errors",children:l.map(([d,c])=>i.jsxs("li",{children:[i.jsx("code",{style:np,children:d}),": ",c.join(", ")]},d))})]})}return t.kind==="blocked_inactive_extension"?i.jsxs(Lo,{tone:"warning",testId:"g7le-save-banner-blocked",onDismiss:n,children:[i.jsx("strong",{children:s("layout_editor.save.blocked_inactive_extension")}),t.blockedPaths.length>0&&i.jsx("ul",{style:tp,"data-testid":"g7le-save-banner-blocked-paths",children:t.blockedPaths.map(a=>i.jsx("li",{children:i.jsx("code",{style:np,children:a})},a))})]}):t.kind==="network_error"?i.jsxs(Lo,{tone:"error",testId:"g7le-save-banner-network",onDismiss:n,children:[i.jsx("strong",{children:s("layout_editor.save.network_error_title")}),i.jsx("div",{style:{marginTop:4,fontSize:12},children:t.message})]}):t.kind==="guard_no_document"?i.jsx(Lo,{tone:"neutral",testId:"g7le-save-banner-guard-no-document",onDismiss:n,children:s("layout_editor.save.guard_no_document")}):i.jsx("div",{role:"dialog","aria-modal":"true","aria-label":s("layout_editor.save.concurrent.title"),"data-testid":"g7le-save-banner-concurrent",style:wE,children:i.jsxs("div",{style:kE,children:[i.jsx("h2",{style:SE,children:s("layout_editor.save.concurrent.title")}),i.jsx("p",{style:CE,children:s("layout_editor.save.concurrent.message")}),i.jsx("div",{style:$E,children:s("layout_editor.save.concurrent.version_info",{current:String(t.currentVersion),yours:String(t.yourVersion)})}),i.jsxs("div",{style:EE,children:[i.jsx("button",{type:"button","data-testid":"g7le-save-banner-concurrent-load-latest",onClick:()=>{o?.(),n()},style:IE,children:s("layout_editor.save.concurrent.load_latest")}),i.jsx("button",{type:"button","data-testid":"g7le-save-banner-concurrent-keep-mine",onClick:()=>{r?.(),n()},style:AE,children:s("layout_editor.save.concurrent.view_my_changes")}),i.jsx("button",{type:"button","data-testid":"g7le-save-banner-concurrent-cancel",onClick:n,style:TE,children:s("layout_editor.save.concurrent.cancel")})]})]})})}function Lo(e){const{tone:t,testId:n,onDismiss:o,children:r}=e,s=vE[t];return i.jsxs("div",{role:"status","data-testid":n,style:{..._E,background:s.bg,borderColor:s.border,color:s.fg},children:[i.jsx("div",{style:{flex:1,minWidth:0},children:r}),i.jsx("button",{type:"button",onClick:o,"aria-label":"close","data-testid":`${n}-close`,style:xE,children:"×"})]})}const vE={success:{bg:"#dcfce7",border:"#86efac",fg:"#14532d"},error:{bg:"#fee2e2",border:"#fca5a5",fg:"#7f1d1d"},warning:{bg:"#fef3c7",border:"#fcd34d",fg:"#78350f"},neutral:{bg:"#f1f5f9",border:"#cbd5e1",fg:"#334155"}},_E={display:"flex",alignItems:"flex-start",gap:12,padding:"12px 16px",border:"1px solid",borderRadius:0,fontSize:13,lineHeight:1.5},xE={background:"transparent",border:"none",fontSize:18,cursor:"pointer",lineHeight:1,color:"inherit",padding:"0 4px",flexShrink:0},tp={margin:"8px 0 0",paddingLeft:20,fontSize:12},np={background:"rgba(0, 0, 0, 0.06)",padding:"1px 4px",borderRadius:3,fontFamily:"monospace"},wE={position:"fixed",inset:0,background:"rgba(15, 23, 42, 0.6)",display:"flex",alignItems:"center",justifyContent:"center",zIndex:1e4},kE={background:"#fff",borderRadius:8,padding:24,width:"min(480px, 90vw)",boxShadow:"0 10px 25px rgba(0,0,0,0.15)"},SE={margin:"0 0 8px",fontSize:16,fontWeight:600,color:"#0f172a"},CE={margin:"0 0 12px",color:"#334155",fontSize:13,lineHeight:1.5},$E={marginBottom:16,padding:"8px 12px",background:"#f8fafc",borderRadius:6,fontSize:12,color:"#64748b",fontFamily:"monospace"},EE={display:"flex",gap:8,justifyContent:"flex-end"},IE={padding:"8px 14px",background:"#2563eb",color:"#fff",border:"none",borderRadius:6,fontSize:13,fontWeight:600,cursor:"pointer"},AE={padding:"8px 14px",background:"#fff",color:"#334155",border:"1px solid #cbd5e1",borderRadius:6,fontSize:13,fontWeight:500,cursor:"pointer"},TE={padding:"8px 14px",background:"transparent",color:"#64748b",border:"none",borderRadius:6,fontSize:13,cursor:"pointer"};function op(e){if(!e)return null;const n=new URLSearchParams(e).get("route");return n&&n.length>0?n:null}function Zs(e,t,n){const o=`/admin/layout-editor/${encodeURIComponent(e)}`;if(!t||t.length===0)return o;if(t.startsWith("__")){let r=`${o}?edit=${encodeURIComponent(t)}`;return(t.startsWith("__modal__/")||t.startsWith("__iteration__/"))&&n&&(r+=`&host=${encodeURIComponent(n)}`),r}return`${o}?route=${encodeURIComponent(t)}`}function rp(e){if(!e)return null;const n=new URLSearchParams(e).get("host");return n&&n.length>0?n:null}function ip(e){if(!e)return null;const n=new URLSearchParams(e).get("edit");return n&&n.length>0&&n.startsWith("__")?n:null}function DE(e,t){const n=`/admin/templates/${encodeURIComponent(e)}/edit`;if(!t||t.length===0||t.startsWith("__"))return n;const o=t.replace(/^\*+/,"");return o.length===0?n:`${n}?route=${encodeURIComponent(o)}`}const NE="g7le-editor-route",zE=new Set;function Qs(e){return e.path.startsWith("__group__/")||e.path.startsWith("__conngroup__/")}function sp(e,t){const{t:n,translationEngine:o,translationContext:r,templateIdentifier:s}=t,a=Qs(e);if(!e.label.startsWith("$t:"))return e.label;const l=e.label.slice(3),d=l.indexOf("|"),c=d===-1?l:l.slice(0,d),p={};if(d!==-1)for(const y of l.slice(d+1).split("|")){const g=y.indexOf("=");g!==-1&&(p[y.slice(0,g)]=y.slice(g+1))}if((e.kind==="route"||e.kind==="modal")&&!a&&o&&r&&s!==r.templateId){const y={templateId:s,locale:r.locale},g=Object.keys(p).length>0?"|"+Object.entries(p).map(([h,v])=>`${h}=${v}`).join("|"):void 0,m=o.translate(c,y,g);if(m!==c)return m}const f=n(c,p);if(f===c&&typeof window<"u"){const y=window.G7Core?.t;if(typeof y=="function")try{const g=y(c,p);if(g!==c)return g}catch{}}if(f===c&&e.kind==="route"&&!a){const y=e.path;if(y&&y!=="/"){const g=y.split("/").filter(Boolean);return g[g.length-1]??y}return y==="/"?"/":c}return f}function ME(e){return e.trim().toLocaleLowerCase()}function ap(e,t){if(!t)return e;const n=e.toLocaleLowerCase(),o=[];let r=0,s=n.indexOf(t,r);if(s===-1)return e;let a=0;for(;s!==-1;)s>r&&o.push(e.slice(r,s)),o.push(i.jsx("mark",{className:"g7le-route-tree__highlight",style:{background:"#fde68a",color:"inherit",borderRadius:2,padding:"0 1px"},children:e.slice(s,s+t.length)},`m${a++}`)),r=s+t.length,s=n.indexOf(t,r);return r{const T=f.selectedRoute?.path??"";return T.startsWith("__extension__/")||T.startsWith("__modal__/")?T.slice(T.lastIndexOf("/")+1):null},[f.selectedRoute?.path]),h=u.useMemo(()=>!g||!m?!1:(e.children??[]).some(T=>T.kind==="extension"&&T.extensionId===m||T.kind==="modal"&&T.modalId===m),[g,m,e.children]),[v,b]=u.useState(!0);u.useEffect(()=>{h&&b(!1)},[h]);const x=f.editMode==="extension"?f.selectedRoute?.layoutName??null:null,k=e.kind==="extension"?dp(e,f.routeTree)??null:null,w=e.kind==="extension"&&o===e.path&&(x===null||k===null||x===k),_=f.editMode==="modal"&&o?.startsWith("__modal__/")?o.slice(o.lastIndexOf("/")+1):null,S=f.editMode==="modal"?f.selectedRoute?.layoutName??null:null,E=e.kind==="modal"&&!!_&&e.modalId===_&&(S===null||e.modalHostLayout==null||S===e.modalHostLayout),C=e.kind!=="extension"&&e.kind!=="modal"&&o===e.path||w||E||e.kind==="route"&&!y&&r===e.path,$=(e.children?.length??0)>0&&(!g||l||!v),A=!y&&!!e.layoutName&&s.has(e.layoutName),z=y?void 0:e.kind==="extension"?e.extensionId?f.extensionVersions[e.extensionId]:void 0:e.layoutName?f.layoutVersions[e.layoutName]:void 0,D=()=>{if(g){b(T=>!T);return}y||n(e)},O=sp(e,{t:d,translationEngine:c,translationContext:p,templateIdentifier:f.templateIdentifier}),M=y?null:LE(e),B={paddingLeft:12+t*14,paddingRight:12,paddingTop:4,paddingBottom:4,opacity:e.isHidden?.55:1,cursor:g?"pointer":y?"default":"pointer",background:C?"#eff6ff":"transparent",borderLeft:C?"2px solid #2563eb":"2px solid transparent",fontWeight:y||C?600:400,fontSize:y?11:13,color:y?"#64748b":C?"#1d4ed8":"#1e293b",textTransform:y?"uppercase":"none",letterSpacing:y?.6:void 0,transition:"background-color 100ms, color 100ms",display:"flex",flexDirection:"column",alignItems:"stretch",gap:2,userSelect:"none"};return i.jsxs("li",{children:[i.jsxs("div",{"data-testid":y?"g7le-route-tree-group":"g7le-route-tree-item","data-route-path":e.path,"data-route-kind":e.kind,"data-conn-collapsed":g?l?"false":String(v):void 0,role:g||!y?"button":void 0,tabIndex:g||!y?0:void 0,"aria-expanded":g?l?!0:!v:void 0,onClick:D,onKeyDown:T=>{g&&(T.key==="Enter"||T.key===" ")?(T.preventDefault(),b(H=>!H)):!y&&(T.key==="Enter"||T.key===" ")&&(T.preventDefault(),n(e))},style:B,children:[i.jsxs("span",{style:{display:"flex",alignItems:"center",gap:6,minWidth:0},children:[g&&i.jsx("span",{"aria-hidden":"true",style:{flex:"0 0 auto",fontSize:9,color:"#94a3b8",width:10,display:"inline-block"},children:l||!v?"▾":"▸"}),A&&i.jsx("span",{className:"g7le-route-tree__dirty-dot","data-testid":"g7le-route-tree-dirty",title:d("layout_editor.chrome.route_tree.badge.modified"),"aria-label":d("layout_editor.chrome.route_tree.badge.modified"),style:{width:7,height:7,borderRadius:"50%",background:"#f59e0b",flex:"0 0 auto"}}),y?O:ap(O,a),typeof z=="number"&&i.jsxs("span",{className:"g7le-route-tree__version-badge","data-testid":"g7le-route-tree-version",title:d("layout_editor.chrome.route_tree.badge.version_tooltip",{version:String(z)}),style:{flex:"0 0 auto",padding:"0 5px",fontSize:10,fontWeight:600,lineHeight:"16px",borderRadius:4,background:"#eef2ff",color:"#4f46e5",border:"1px solid #e0e7ff",letterSpacing:.2},children:["v",z]}),i.jsx(PE,{node:e})]}),M&&i.jsx("span",{className:"g7le-route-tree__layout-path","data-testid":"g7le-route-tree-layout-path",title:M,style:{fontSize:11,fontWeight:400,color:"#94a3b8",textTransform:"none",letterSpacing:0,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},children:ap(M,a)})]}),$&&i.jsx("ul",{style:{listStyle:"none",margin:0,padding:0},children:e.children.map(T=>i.jsx(lp,{node:T,depth:t+1,onSelect:n,selectedRoutePath:o,activeHostRoutePath:r,dirtyLayoutNames:s,query:a,forceExpand:l},T.path))})]})}function OE(e,t){if(!t||!t.startsWith("__"))return null;const n=t.startsWith("__modal__/"),o=t.startsWith("__extension__/");if(!n&&!o)return null;const r=t.slice(t.lastIndexOf("/")+1),s=a=>{for(const l of a){if(l.connectedHostRoutePath&&(n&&l.kind==="modal"&&l.modalId===r||o&&l.kind==="extension"&&l.extensionId===r))return l.connectedHostRoutePath;if(l.children&&l.children.length>0){const d=s(l.children);if(d)return d}}return null};return s(e)}function dp(e,t){if(e.connectedHostRoutePath){const n=cp(t,e.connectedHostRoutePath);if(n?.layoutName)return n.layoutName}if(e.extensionType==="overlay"&&e.extensionTargetName)return e.extensionTargetName}function cp(e,t){for(const n of e){if(n.kind==="route"&&n.path===t)return n;if(n.children&&n.children.length>0){const o=cp(n.children,t);if(o)return o}}return null}function up(e,t,n){const o=[];for(const r of e){const s=Qs(r),a=r.children&&r.children.length>0?up(r.children,t,n):[];if(s){a.length>0&&o.push({...r,children:a});continue}const l=sp(r,n).toLocaleLowerCase(),d=r.path.toLocaleLowerCase();(l.includes(t)||!d.startsWith("__")&&d.includes(t)||a.length>0)&&o.push({...r,children:a})}return o}function BE(){const{t:e,translationEngine:t,translationContext:n}=tt(),{state:o,dispatch:r}=Je(),a=Ft()?.dirtyLayoutNames??zE,[l,d]=u.useState(""),c=ME(l),p=u.useMemo(()=>c?up(o.routeTree,c,{t:e,translationEngine:t,translationContext:n,templateIdentifier:o.templateIdentifier}):o.routeTree,[c,o.routeTree,o.templateIdentifier,e,t,n]),f=h=>{if(h.kind==="base"&&h.layoutName){r({type:"ENTER_BASE_EDIT",layoutName:h.layoutName});return}if(h.kind==="modal"&&h.modalId&&h.modalHostLayout){r({type:"ENTER_MODAL_EDIT",modalId:h.modalId,hostLayout:h.modalHostLayout});return}if(h.kind==="extension"&&h.extensionId){const v=dp(h,o.routeTree);r({type:"ENTER_EXTENSION_EDIT",extensionId:h.extensionId,extensionHost:v});return}if(r({type:"SELECT_ROUTE",route:{path:h.path,layoutName:h.layoutName}}),typeof window<"u"&&window.history?.pushState){const v=Zs(o.templateIdentifier,h.path);`${window.location.pathname}${window.location.search}`!==v&&window.history.pushState({source:NE,routePath:h.path},"",v)}},y=o.selectedRoute?.path??null,g=u.useMemo(()=>OE(o.routeTree,y),[o.routeTree,y]);if(o.isRouteTreeCollapsed)return i.jsx("aside",{className:"g7le-route-tree g7le-route-tree--collapsed","data-testid":"g7le-route-tree-panel","data-collapsed":"true",style:{width:28,borderRight:"1px solid #e2e8f0",background:"#ffffff"}});const m=o.routeTree.length>0;return i.jsxs("aside",{className:"g7le-route-tree","data-testid":"g7le-route-tree-panel","data-collapsed":"false",style:{width:280,borderRight:"1px solid #e2e8f0",background:"#ffffff",position:"sticky",top:"var(--g7le-header-h, 0px)",alignSelf:"start",height:"calc(100vh - var(--g7le-header-h, 0px))",overflow:"hidden",minHeight:0,display:"flex",flexDirection:"column"},"aria-label":e("layout_editor.chrome.route_tree.panel_title"),children:[i.jsx("div",{className:"g7le-route-tree__title",style:{padding:"10px 14px",fontSize:12,fontWeight:600,color:"#475569",textTransform:"uppercase",letterSpacing:.6,borderBottom:"1px solid #e2e8f0",background:"#f8fafc",flex:"0 0 auto"},children:e("layout_editor.chrome.route_tree.panel_title")}),m&&i.jsxs("div",{className:"g7le-route-tree__search",style:{padding:"8px 10px",borderBottom:"1px solid #e2e8f0",background:"#ffffff",flex:"0 0 auto",position:"relative"},children:[i.jsx("input",{type:"text",className:"g7le-route-tree__search-input","data-testid":"g7le-route-tree-search",value:l,onChange:h=>d(h.target.value),placeholder:e("layout_editor.chrome.route_tree.search_placeholder"),"aria-label":e("layout_editor.chrome.route_tree.search_placeholder"),style:{width:"100%",boxSizing:"border-box",padding:l?"6px 26px 6px 10px":"6px 10px",fontSize:12,lineHeight:1.4,color:"#1e293b",border:"1px solid #cbd5e1",borderRadius:6,outline:"none",background:"#f8fafc"}}),l&&i.jsx("button",{type:"button",className:"g7le-route-tree__search-clear","data-testid":"g7le-route-tree-search-clear",onClick:()=>d(""),"aria-label":e("layout_editor.chrome.route_tree.search_clear"),title:e("layout_editor.chrome.route_tree.search_clear"),style:{position:"absolute",right:16,top:"50%",transform:"translateY(-50%)",width:18,height:18,display:"inline-flex",alignItems:"center",justifyContent:"center",border:"none",background:"transparent",color:"#94a3b8",cursor:"pointer",fontSize:14,lineHeight:1,padding:0},children:"✕"})]}),!m&&i.jsx("div",{className:"g7le-route-tree__empty","data-testid":"g7le-route-tree-empty",style:{padding:16,fontSize:12,color:"#94a3b8",textAlign:"center"},children:e("layout_editor.chrome.route_tree.empty")}),m&&c&&p.length===0&&i.jsx("div",{className:"g7le-route-tree__no-results","data-testid":"g7le-route-tree-no-results",style:{padding:16,fontSize:12,color:"#94a3b8",textAlign:"center"},children:e("layout_editor.chrome.route_tree.search_no_results")}),m&&p.length>0&&i.jsx("nav",{className:"g7le-route-tree__scroll","data-testid":"g7le-route-tree-scroll",style:{flex:"1 1 auto",minHeight:0,overflowY:"auto"},children:i.jsx("ul",{style:{listStyle:"none",margin:0,padding:0},children:p.map(h=>i.jsx(lp,{node:h,depth:0,onSelect:f,selectedRoutePath:y,activeHostRoutePath:g,dirtyLayoutNames:a,query:c,forceExpand:!!c},h.path))})})]})}const WE=Lt().DynamicRenderer,Rs=Lt(),jE=Rs.ResponsiveProvider;Rs.useResponsive,Rs.ResponsiveContext;const HE=Lt().ActionDispatcher,pp=Lt(),FE=pp.DataSourceManager;pp.dataSourceManager;function Gr(e,t,n){const o=t??{},r=o.message||o.error||`HTTP ${e}`,s=o?.data?.required_permissions??o?.required_permissions;let a;return e===401?a="unauthorized":e===403?a="forbidden":e===404?a="not_found":e>=500?a="server_error":a="unknown",{kind:a,status:e,message:String(r),requiredPermissions:s?String(s):void 0,source:n}}function io(e,t){return{kind:"network",status:0,message:e instanceof Error?e.message:String(e??"Unknown network error"),source:t}}function fp(e){if(typeof e!="object"||e===null)return!1;const t=e;return typeof t.kind=="string"&&typeof t.status=="number"&&typeof t.message=="string"}function Ze(e){if(!e)return[];const t=[],n=e.split(".");for(let o=0;o0&&(t.push({responsive:a}),o+=1);continue}const s=Number(r);Number.isFinite(s)&&Number.isInteger(s)&&s>=0&&t.push(s)}return t}function KE(e,t){if(!e)return{affordance:"none",targetPath:null};const n=VE(e);return n?UE(n)?{affordance:"dynamic_path",targetPath:n}:/^(https?:)?\/\//.test(n)?{affordance:"external_url",targetPath:n}:n.startsWith("/")?t?{affordance:t(n),targetPath:n}:{affordance:"route_not_in_tree",targetPath:n}:{affordance:"route_not_in_tree",targetPath:n}:{affordance:"none",targetPath:null}}function VE(e){const t=e.actions;if(Array.isArray(t)){for(const n of t)if(n&&typeof n=="object"&&n.handler==="navigate"){const o=n.params?.path;if(typeof o=="string"&&o.length>0)return o}}if(e.name==="A"||e.type==="A"){const n=e.props?.href;if(typeof n=="string"&&n.length>0)return n}return null}function UE(e){return/\{\{[\s\S]*?\}\}/.test(e)}function qE(e,t,n){const o=s=>!!(s?.iteration&&s.iteration.source!==void 0),r=s=>{if(!s)return s;const a=s.indexOf(".iteration."),l=s.indexOf(".sortable."),d=[a,l].filter(c=>c>=0).sort((c,p)=>c-p)[0];return d===void 0?s:s.slice(0,d)};return o(e)?{isIteration:!0,iterationSourcePath:r(n)}:n&&n.includes(".iteration.")?{isIteration:!0,iterationSourcePath:r(n)}:t.some(o)?{isIteration:!0,iterationSourcePath:r(n)}:{isIteration:!1,iterationSourcePath:null}}function GE(e,t){const n=a=>{const l=a?.iteration&&a.iteration.source;return typeof l=="string"&&l.length>0?l:null},o=n(e)??t.map(n).find(a=>a!==null)??null;if(o!==null){const a=XE(o);if(a!==null)return a}const r=[];if(typeof e.text=="string"&&r.push(...gp(e.text)),e.props&&typeof e.props=="object")for(const a of Object.values(e.props))typeof a=="string"&&r.push(...gp(a));return r.length===0?null:r.find(a=>!YE.has(a))??r[0]}const YE=new Set(["_local","_global","_computed","route","query","props","item","index","event","$event","$response","response"]);function gp(e){const t=[],n=/\{\{([\s\S]*?)\}\}/g;let o;for(;(o=n.exec(e))!==null;){const r=hp(o[1]);r!==null&&t.push(r)}return t}function XE(e){const t=e.replace(/\{\{|\}\}/g,"");return hp(t)}function hp(e){const t=/[A-Za-z_$][A-Za-z0-9_$]*/.exec(e.trim());return t?t[0]:null}function gn(e,t,n,o=[]){if(!e)return"none";if(td(e)||typeof e.slot=="string"||typeof e.__editorSlotName=="string")return"extension_point";if(t!=="route"&&Ji(e,t,n)){const s=e.__source?.kind;return s==="base"?"base":s==="partial"?"partial":s==="extension"?"extension":"base"}if(kb(e,o))return"data_bound";if(!Ji(e,t,n))return"none";const r=e.__source?.kind;return r==="base"?"base":r==="partial"?"partial":r==="extension"?"extension":"none"}function yp(e){if(!e)return null;if(e.__source?.kind==="extension"&&typeof e.__source.extensionId=="number")return e.__source.extensionId;const t=e.__injectedProps;if(Array.isArray(t)&&t.length>0){const n=t[0]?.extensionId;if(typeof n=="number")return n}return null}function JE(e,t){if(t<=0)return e;const n=e.split(".");let o=0;const r=[];for(let s=0;s=t)break;r.push(a);continue}if(a==="iteration"||a==="sortable"){if(o>=t)break;r.push(a);continue}r.push(a);const l=n[s-1];if((l===void 0||l==="children")&&(o+=1),o>=t)break}return r.join(".")}function bp(e,t,n,o){if(!e||!t)return t;const r=n(t),s=$b(e,r,o);return s.length===r.length?t:JE(t,s.length)}function mp(e){return e==="none"||e==="data_bound"}function ZE(e){const{rootNode:t,editMode:n,currentExtensionId:o,editableRootPath:r,pathParser:s=Ze,resolveRouteMatch:a}=e,[l,d]=u.useState(null),[c,p]=u.useState(null),f=u.useCallback(x=>{if(x===null||n==="route")return!0;if(n==="iteration_item"||n==="modal"){if(!r||r.length===0)return!0;const S=s(x);return vp(S,r)}const k=ea(t,x,s);if(!k)return!0;const w=ta(t,x,s),_=gn(k,n,o,w);return _==="none"||_==="data_bound"},[t,s,n,o,r]),y=u.useCallback((x,k)=>{const _=(k?.currentTarget?.dataset??k?.dataset)?.editorPath??null,S=bp(t,_,s,o);f(S)&&d(S)},[t,s,o,f]),g=u.useCallback((x,k)=>{if(x===null){p(null);return}const _=(k?.currentTarget?.dataset??k?.dataset)?.editorPath??null,S=bp(t,_,s,o);if(!f(S)){p(null);return}p(S)},[t,s,o,f]),m=u.useCallback(()=>{d(null)},[]),h=u.useMemo(()=>{const x=ea(t,l,s),k=ea(t,c,s),w=x?ta(t,l,s):[];let _=gn(x,n,o,w);(n==="iteration_item"||n==="modal")&&l!==null&&x&&r&&r.length>0&&vp(s(l),r)&&(nd(x)?_="data_bound":_==="data_bound"&&(_="none"));const{affordance:S,targetPath:E}=KE(x,a),C=_==="base"&&typeof x?.__source?.layout=="string"?x.__source.layout:null,N=_==="data_bound"&&x?GE(x,w):null,{isIteration:$,iterationSourcePath:A}=_==="data_bound"&&x?qE(x,w,l):{isIteration:!1,iterationSourcePath:null};return{selectedNode:x,hoverNode:k,selectedLockKind:_,selectedNavAffordance:S,selectedNavTargetPath:E,selectedBaseLayout:C,selectedDataSourceId:N,selectedIsIteration:$,selectedIterationSourcePath:A}},[t,l,c,n,o,r,s,a]),v=u.useRef(void 0);v.current!==l&&(v.current=l,Cl({op:l===null?"clear":"select",componentPath:l,componentName:_p(h.selectedNode),lockKind:h.selectedLockKind,navAffordance:h.selectedNavAffordance,sourceExtensionId:yp(h.selectedNode),timestamp:Date.now()}));const b=u.useRef(void 0);return b.current!==c&&(b.current=c,c!==null&&Cl({op:"hover",componentPath:c,componentName:_p(h.hoverNode),lockKind:gn(h.hoverNode,n,o,ta(t,c,s)),navAffordance:"none",sourceExtensionId:yp(h.hoverNode),timestamp:Date.now()})),{selectedPath:l,hoverPath:c,...h,handleSelect:y,handleHover:g,clearSelection:m}}function ea(e,t,n){if(!e||!t)return null;const o=n(t);return We(e,o)}function vp(e,t){if(e.length0?e.name:typeof e.type=="string"&&e.type.length>0?e.type:null:null}let xp=0;function wp(){return xp}function na(){xp+=1}function QE(e){if(typeof e!="string")return null;const t="__modal__/";if(!e.startsWith(t))return null;const n=e.slice(t.length);return n===""||n.includes("/")?null:n}function RE(e){if(typeof e!="string")return null;const t="__iteration__/";if(!e.startsWith(t))return null;const n=e.slice(t.length);return n!==""?n:null}function kp(){const{state:e}=Je(),t=e.templateIdentifier,n=e.selectedRoute?.layoutName??null,o=e.editMode,r=o==="modal"?QE(e.selectedRoute?.path):null,s=o==="iteration_item"?RE(e.selectedRoute?.path):null,[a,l]=u.useState(null),[d,c]=u.useState(!1),[p,f]=u.useState(null),[y,g]=u.useState(!1),[m,h]=u.useState(0),[v,b]=u.useState(0),x=u.useRef(new Map),[k,w]=u.useState(()=>new Set),_=u.useCallback((L,I)=>`${L}|${I??""}`,[]),S=u.useRef(null);u.useEffect(()=>{S.current=a},[a]);const E=u.useCallback((L,I)=>{const P=_(o,n);x.current.set(P,{raw:L,lockVersion:I}),w(F=>{if(F.has(P))return F;const G=new Set(F);return G.add(P),G})},[_,o,n]),C=u.useCallback(L=>{x.current.delete(L),w(I=>{if(!I.has(L))return I;const P=new Set(I);return P.delete(L),P})},[]),N=u.useCallback(L=>{const I=`|${L??""}`,P=[];for(const F of x.current.keys())F.endsWith(I)&&P.push(F);for(const F of P)x.current.delete(F);P.length>0&&w(F=>{let G=!1;const V=new Set(F);for(const Y of P)V.delete(Y)&&(G=!0);return G?V:F})},[]),$=u.useCallback(async()=>{if(!n){l(null),f(null),g(!1);return}const L=_(o,n),I=x.current.get(L);if(I){l({layoutName:n,raw:I.raw,lockVersion:I.lockVersion}),g(!0),f(null),c(!1),st({op:"load",layoutName:n,editMode:o,statusCode:0,isDirty:!0,timestamp:Date.now()});return}c(!0),f(null);try{const P=window.G7Config?.cache_version??0,F=en(`/api/layouts/${encodeURIComponent(t)}/${n}`,"json",`${P}.${wp()}`,"with_source_meta=1"),G=ar(),V={Accept:"application/json"};G&&(V.Authorization=`Bearer ${G}`);const Y=await fetch(F,{headers:V,credentials:"same-origin"}),Z=await Y.json().catch(()=>null);if(!Y.ok){f(Gr(Y.status,Z,"layout")),l(null),g(!1);return}const se=(Z&&(Z.data||Z))??{},re=typeof se.lock_version=="number"?se.lock_version:0;if(o==="modal"&&r){const ee=se,fe=Array.isArray(ee.modals)?ee.modals:[],K=fe.findIndex(ne=>ne&&(ne.id===r||ne.modal_id===r));if(K>=0){const ne=fe[K],me={...ne,props:{...ne.props??{},isOpen:!0}},ze=[...Array.isArray(ee.components)?ee.components:[],me],Se=[ze.length-1];l({layoutName:n,raw:{...ee,components:ze},lockVersion:re,modalContext:{modalId:r,modalIndex:K,hostRaw:ee,originalModalNode:ne,editIndexPath:Se}}),g(!1),st({op:"load",layoutName:n,editMode:o,statusCode:Y.status,timestamp:Date.now()});return}}if(o==="iteration_item"&&s){const ee=se,fe=Ze(s),K={children:ee.components??[]},ne=We(K,fe);if(ne&&Array.isArray(ne.children)){l({layoutName:n,raw:ee,lockVersion:re,iterationContext:{sourcePath:s,sourceIndexPath:fe,hostRaw:ee}}),g(!1),st({op:"load",layoutName:n,editMode:o,statusCode:Y.status,timestamp:Date.now()});return}}l({layoutName:n,raw:se,lockVersion:re}),g(!1),st({op:"load",layoutName:n,editMode:o,statusCode:Y.status,timestamp:Date.now()})}catch(P){f(io(P,"layout")),l(null),g(!1)}finally{c(!1)}},[t,n,o,r,s,_]);u.useEffect(()=>{$()},[$]);const A=u.useCallback(async()=>{C(_(o,n)),na(),b(L=>L+1),await $()},[C,_,o,n,$]),z=u.useCallback(()=>{C(_(o,n))},[C,_,o,n]),D=u.useCallback(L=>{l(I=>{if(!I)return I;const P=I.raw.components??[],F=L(P),G={...I.raw,components:F};return E(G,I.lockVersion),{...I,raw:G}}),g(!0),st({op:"patch",layoutName:n,editMode:o,isDirty:!0,timestamp:Date.now()})},[n,o,E]),O=u.useCallback(()=>{g(!0),st({op:"patch",layoutName:n,editMode:o,isDirty:!0,timestamp:Date.now()})},[n,o]),M=u.useCallback(L=>{l(I=>{if(!I)return I;const P={...I.raw,components:L};return E(P,I.lockVersion),{...I,raw:P}}),g(!0),st({op:"patch",layoutName:n,editMode:o,isDirty:!0,timestamp:Date.now()})},[n,o,E]),B=u.useCallback((L,I,...P)=>{const F=P.length>=1?P[0]:I;l(G=>{if(!G)return G;const V={...G.raw,[L]:I},Y=G.raw.__editor;if(Y&&typeof Y=="object"&&!Array.isArray(Y)){const Z=Y,se=Z.original;se&&typeof se=="object"&&!Array.isArray(se)&&(V.__editor={...Z,original:{...se,[L]:F}})}return E(V,G.lockVersion),{...G,raw:V}}),g(!0),st({op:"patch",layoutName:n,editMode:o,isDirty:!0,timestamp:Date.now()})},[n,o,E]),T=u.useCallback(async L=>{const I=S.current;if(!I)return st({op:"save_guard_result",layoutName:n,editMode:o,guardBlocked:!1,timestamp:Date.now()}),{kind:"guard_no_document"};if(L?.resolveActiveExtensions&&L.sessionAddedPaths?.length)try{const fe=await L.resolveActiveExtensions(),K=e7(I.raw.components,L.sessionAddedPaths,fe);if(K.length>0)return st({op:"save_guard_result",layoutName:n,editMode:o,guardBlocked:!0,guardBlockedPaths:K,timestamp:Date.now()}),{kind:"blocked_inactive_extension",blockedPaths:K}}catch{}if(Wb())try{await jb(t)}catch{}const P=`/api/admin/templates/${encodeURIComponent(t)}/layouts/${I.layoutName}`,F=ar(),G={Accept:"application/json","Content-Type":"application/json"};F&&(G.Authorization=`Bearer ${F}`);const V=!!I.modalContext,Y=!!I.iterationContext,Z=V||Y?"host_layout_modal_patch":"layout";st({op:"save",layoutName:I.layoutName,editMode:o,saveTarget:Z,endpoint:P,isDirty:y,timestamp:Date.now()});let se;if(V&&I.modalContext){const{modalIndex:fe,hostRaw:K,originalModalNode:ne,editIndexPath:me}=I.modalContext,Ce=Array.isArray(K.modals)?[...K.modals]:[],ze=Array.isArray(I.raw.components)?I.raw.components:[],Se=me&&me.length===1?me[0]:0,qe=ze.length>Se?ze[Se]:void 0,De=qe?Vn([qe]):[],Pe=De.length>0?{...De[0]}:void 0;if(fe>=0&&fe({...Me,children:ze})).children??[];se={...Se,components:Qe}}else se=vo(I.raw);const re=se;let U;try{U=await fetch(P,{method:"PUT",headers:G,credentials:"same-origin",body:JSON.stringify({content:JSON.stringify(re),expected_lock_version:I.lockVersion})})}catch(fe){const K=fe instanceof Error?fe.message:"network error";return st({op:"save_response",layoutName:I.layoutName,editMode:o,endpoint:P,statusCode:0,timestamp:Date.now()}),{kind:"network_error",message:K}}const ee=await U.json().catch(()=>null);if(st({op:"save_response",layoutName:I.layoutName,editMode:o,endpoint:P,statusCode:U.status,conflict:U.status===409?{currentVersion:ee?.current_version,yourVersion:ee?.your_version}:void 0,timestamp:Date.now()}),U.ok){const fe=typeof ee?.data?.lock_version=="number"?ee.data.lock_version:I.lockVersion+1;l({...I,lockVersion:fe}),g(!1),C(_(o,I.layoutName)),N(I.layoutName),na(),h(ne=>ne+1);const K=typeof ee?.data?.current_version=="number"?ee.data.current_version:void 0;return{kind:"success",newLockVersion:fe,newContentVersion:K,savedLayoutName:I.layoutName}}return U.status===409?{kind:"concurrent_modification",currentVersion:ee?.current_version??-1,yourVersion:ee?.your_version??I.lockVersion}:U.status===422?{kind:"validation_failed",status:422,errors:ee?.errors??null}:{kind:"network_error",message:ee?.message??`HTTP ${U.status}`}},[t,n,o,y,C,N,_,r,s]),H=u.useMemo(()=>{const L=new Set,I="route|";for(const P of k)if(P.startsWith(I)){const F=P.slice(I.length);F&&L.add(F)}return L},[k]);return{document:a,isLoading:d,error:p,isDirty:y,saveSuccessCounter:m,reloadCounter:v,dirtyKeys:k,dirtyLayoutNames:H,invalidateCurrentCache:z,reload:A,patchLayout:D,setLayoutComponents:M,patchDocumentRaw:B,save:T,markDirty:O}}function e7(e,t,n){if(!e||e.length===0)return[];const o=[],r=new Set([...n.moduleIds,...n.pluginIds]);for(const s of t){const a=t7(e,s);if(!a)continue;const l=a.__source;if(!l||l.kind!=="extension")continue;const d=String(l.extensionId??"");d.length!==0&&(r.has(d)||o.push(s))}return o}function t7(e,t){const n=Ze(t);return n.length===0?null:We({children:e},n)}function so(e,t){if(!e)return null;const n=e.getBoundingClientRect();if(n.width===0&&n.height===0)return null;if(!t)return{left:n.left,top:n.top,width:n.width,height:n.height,scale:1};const o=t.getBoundingClientRect();return{left:n.left-o.left,top:n.top-o.top,width:n.width,height:n.height,scale:1}}function ao(e,t){if(!e)return!1;if(!t)return!0;const n=t.getBoundingClientRect(),o=1;return!(e.left>=n.width-o||e.left+e.width<=o||e.top>=n.height-o||e.top+e.height<=o)}function Sp(e,t){if(typeof window>"u")return()=>{};const n=()=>{e()};window.addEventListener("scroll",n,!0),window.addEventListener("resize",n);let o=null;t&&typeof ResizeObserver<"u"&&(o=new ResizeObserver(()=>e()),o.observe(t));let r=null,s=null;const a=typeof requestAnimationFrame<"u",l=()=>{if(!a){e();return}s===null&&(s=requestAnimationFrame(()=>{s=null,e()}))};return t&&typeof MutationObserver<"u"&&(r=new MutationObserver(l),r.observe(t,{childList:!0,subtree:!0})),()=>{window.removeEventListener("scroll",n,!0),window.removeEventListener("resize",n),o&&(o.disconnect(),o=null),r&&(r.disconnect(),r=null),s!==null&&a&&typeof cancelAnimationFrame<"u"&&(cancelAnimationFrame(s),s=null)}}const Cp=44;function $p(e){if(!e)return"inside";const t=e.scale||1,n=e.width*t,o=e.height*t;return n"u"||typeof window.getComputedStyle!="function")return{kind:"unknown",display:"",flexDirection:"",flexWrap:""};let t=e,n=(window.getComputedStyle(t).display??"").toLowerCase();for(;n==="contents"&&t.parentElement;)t=t.parentElement,n=(window.getComputedStyle(t).display??"").toLowerCase();const o=window.getComputedStyle(t),r=(o.display??"").toLowerCase(),s=(o.flexDirection??"").toLowerCase(),a=(o.flexWrap??"").toLowerCase(),l=r==="flex"||r==="inline-flex";if(r==="grid"||r==="inline-grid"){const f=Array.from(e.children).filter(h=>h.offsetParent!==null||h.getClientRects().length>0);if(f.length<=1)return{kind:"grid_2d",display:r,flexDirection:s,flexWrap:a};const y=f.map(h=>h.getBoundingClientRect()),g=new Set(y.map(h=>Math.round(h.top))),m=new Set(y.map(h=>Math.round(h.left)));return g.size===1?{kind:"grid_single_row",display:r,flexDirection:s,flexWrap:a}:m.size===1?{kind:"grid_single_column",display:r,flexDirection:s,flexWrap:a}:{kind:"grid_2d",display:r,flexDirection:s,flexWrap:a}}if(!l)return{kind:"block",display:r,flexDirection:s,flexWrap:a};const c=a==="wrap"||a==="wrap-reverse";return s==="column"||s==="column-reverse"?{kind:c?"flex_column_wrap":"flex_column_single",display:r,flexDirection:s,flexWrap:a}:{kind:c?"flex_row_wrap":"flex_row_single",display:r,flexDirection:s,flexWrap:a}}function o7(e){const{parentEl:t,selectedPath:n,selectedEl:o,explicitFlow:r}=e;return u.useMemo(()=>{if(!n||n.length===0)return{points:[],flow:"unknown"};const s=r??Ap(t).kind,a=n.slice(0,-1),l=n[n.length-1]??0;if((s==="flex_row_wrap"||s==="flex_column_wrap"||s==="grid_2d")&&!!o){const p=r7(s,o,a,l);if(p)return{points:p,flow:s}}return{points:i7(s,a,l),flow:s}},[t,n,o,r])}function r7(e,t,n,o){let r=t.parentElement;for(;r&&Tp(r)==="contents";)r=r.parentElement;if(!r)return null;const s=[];if(Dp(r,s),s.length===0)return null;const a=t.getBoundingClientRect(),l=a.top+a.height/2,d=[],c=[],p=[];for(const v of s){const b=v.rect,x=b.top+b.height/2,k=Math.min(a.bottom,b.bottom)-Math.max(a.top,b.top),w=Math.min(a.height,b.height)||1;k/w>.4?d.push({rect:b,path:v.path}):xv.rect.left-b.rect.left),c.sort((v,b)=>v.rect.top-b.rect.top),p.sort((v,b)=>v.rect.top-b.rect.top);const f=d.findIndex(v=>Math.abs(v.rect.left-a.left)<1&&Math.abs(v.rect.top-a.top)<1),y={direction:"above",disabled:!0,insertion:null},g={direction:"below",disabled:!0,insertion:null},m={direction:"left",disabled:!0,insertion:null},h={direction:"right",disabled:!0,insertion:null};if(f>0){const v=d[f-1],b=Xr(v.path);b&&(m.disabled=!1,m.insertion=b)}else if(d.length>0){const v=d[0],b=Yr(v.path);b&&(m.disabled=!1,m.insertion=b)}if(f>=0&&f0){const v=d[d.length-1],b=Xr(v.path);b&&(h.disabled=!1,h.insertion=b)}if(c.length>0){const v=c[c.length-1],b=Xr(v.path);b&&(y.disabled=!1,y.insertion=b)}else if(s.length>0){const v=s[0],b=Yr(v.path);b&&(y.disabled=!1,y.insertion=b)}if(p.length>0){const v=p[0],b=Yr(v.path);b&&(g.disabled=!1,g.insertion=b)}else if(s.length>0){const v=s[s.length-1],b=Xr(v.path);b&&(g.disabled=!1,g.insertion=b)}else g.disabled=!1,g.insertion={parentPath:n,index:o+1};return y.disabled&&g.disabled&&m.disabled&&h.disabled?null:[y,g,m,h]}function Tp(e){return typeof window>"u"?"":(window.getComputedStyle(e).display??"").toLowerCase()}function Dp(e,t){const n=Array.from(e.children);for(const o of n){if(Tp(o)==="contents"){Dp(o,t);continue}const s=o.getAttribute("data-editor-path");if(s&&/\.iteration\.\d+(?:$|\.)/.test(s)||!s)continue;const a=o.getBoundingClientRect();a.width===0&&a.height===0||t.push({el:o,rect:a,path:Ze(s)})}}function Yr(e){if(!e||e.length===0)return null;const t=e[e.length-1];return typeof t!="number"?null:{parentPath:e.slice(0,-1),index:t}}function Xr(e){if(!e||e.length===0)return null;const t=e[e.length-1];return typeof t!="number"?null:{parentPath:e.slice(0,-1),index:t+1}}function i7(e,t,n){const o={direction:"above",disabled:!0,insertion:null},r={direction:"below",disabled:!0,insertion:null},s={direction:"left",disabled:!0,insertion:null},a={direction:"right",disabled:!0,insertion:null},l=d=>({parentPath:t,index:d});switch(e){case"block":case"unknown":{o.disabled=!1,o.insertion=l(n),r.disabled=!1,r.insertion=l(n+1);break}case"flex_row_single":case"grid_single_row":{s.disabled=!1,s.insertion=l(n),a.disabled=!1,a.insertion=l(n+1);break}case"flex_column_single":case"grid_single_column":{o.disabled=!1,o.insertion=l(n),r.disabled=!1,r.insertion=l(n+1);break}case"flex_row_wrap":case"grid_2d":{s.disabled=!1,s.insertion=l(n),a.disabled=!1,a.insertion=l(n+1),o.disabled=!1,o.insertion=l(0),r.disabled=!1,r.insertion=l(-1);break}case"flex_column_wrap":{o.disabled=!1,o.insertion=l(n),r.disabled=!1,r.insertion=l(n+1),s.disabled=!1,s.insertion=l(0),a.disabled=!1,a.insertion=l(-1);break}}return[o,r,s,a]}function s7(e=50){const t=u.useRef([]),n=u.useRef(-1),[,o]=u.useState(0),r=()=>{o(p=>(p+1)%1e6)},s=(p,f)=>{const y=t.current.length,g=n.current;Uh({op:p,actionKind:f?.actionKind,label:f?.label,stackSize:y,cursor:g,canUndo:g>0,canRedo:g>=0&&g{const y=t.current.slice(0,n.current+1);for(y.push(p);y.length>e;)y.shift();t.current=y,n.current=y.length-1,r(),s("push",p)},[e]),l=u.useCallback(()=>{if(n.current<=0)return null;n.current-=1,r();const p=t.current[n.current]??null;return s("undo",p??void 0),p},[]),d=u.useCallback(()=>{const p=n.current+1;if(p>=t.current.length)return null;n.current=p,r();const f=t.current[p]??null;return s("redo",f??void 0),f},[]),c=u.useCallback(()=>{t.current=[],n.current=-1,r(),s("clear")},[]);return{push:a,undo:l,redo:d,clear:c,canUndo:n.current>0,canRedo:n.current>=0&&n.current{if(!o||typeof window>"u")return;const r=s=>{if(a7(s.target)||!(s.ctrlKey||s.metaKey))return;const l=s.key.toLowerCase();if(l==="z"&&!s.shiftKey){s.preventDefault(),t();return}if(l==="z"&&s.shiftKey||l==="y"){s.preventDefault(),n();return}};return window.addEventListener("keydown",r),()=>{window.removeEventListener("keydown",r)}},[t,n,o])}const d7=20,Np=50,c7=90,Ln=120,u7=130,zp=80,p7=9e3,Mp=1e4;function f7(e){const t=e<0?0:e>Np?Np:e;return d7+t}function g7(e){const{anchor:t,open:n,onClose:o,onEditProps:r,onDuplicate:s,onDelete:a,onSeparateBranch:l,separateBranchLabel:d,onMergeBranch:c,mergeBranchLabel:p}=e,{t:f}=tt(),[y,g]=u.useState(null);if(!n||!t)return null;const m=[{index:0,testid:"g7le-context-menu-edit-props",label:f("layout_editor.context_menu.edit_props"),onSelect:r},{index:1,testid:"g7le-context-menu-duplicate",label:f("layout_editor.context_menu.duplicate"),onSelect:s}];return l?m.push({index:m.length,testid:"g7le-context-menu-separate-branch",label:d??f("layout_editor.context_menu.separate_branch"),onSelect:l,hint:f("layout_editor.context_menu.separate_branch_hint")}):c&&m.push({index:m.length,testid:"g7le-context-menu-merge-branch",label:p??f("layout_editor.context_menu.merge_branch"),onSelect:c,hint:f("layout_editor.context_menu.merge_branch_hint")}),m.push({index:m.length,testid:"g7le-context-menu-delete",label:f("layout_editor.context_menu.delete"),onSelect:a,danger:!0}),i.jsx("div",{className:"g7le-context-menu","data-testid":"g7le-context-menu",role:"menu",style:{position:"absolute",left:t.left,top:t.top,background:"#fff",border:"1px solid #cbd5e1",borderRadius:6,boxShadow:"0 4px 12px rgba(0,0,0,0.15)",zIndex:1200,minWidth:160,padding:4,pointerEvents:"auto"},onMouseLeave:()=>{g(null),o()},children:m.map(h=>{const v=y===h.index,b=h.danger?"#dc2626":"#0f172a",x=h.danger?"#fee2e2":"#e0e7ff",k=h.danger?"#b91c1c":"#1d4ed8";return i.jsx("button",{type:"button",role:"menuitem","data-testid":h.testid,title:h.hint,onClick:w=>{w.stopPropagation(),h.onSelect(),g(null),o()},onMouseEnter:()=>g(h.index),onFocus:()=>g(h.index),style:{...h7,color:v?k:b,background:v?x:"transparent"},children:h.label},h.testid)})})}const h7={display:"block",width:"100%",textAlign:"left",padding:"6px 12px",border:"none",fontSize:13,cursor:"pointer",borderRadius:4,pointerEvents:"auto",transition:"background-color 120ms ease, color 120ms ease"};function y7(e){const{hoverBox:t,selectedBox:n,lockKind:o,navAffordance:r,onEditProps:s,onDuplicate:a,onDelete:l,onSeparateBranch:d,separateBranchLabel:c,onMergeBranch:p,mergeBranchLabel:f,definedDeviceBranches:y,onJumpToDevice:g,onLinkEditDestination:m,onEditBase:h,onEditExtension:v,extensionLabel:b,baseLayoutLabel:x,dataSourceLabel:k,branchLabel:w,onEditIteration:_,resizeEnabledAxes:S,onResizeHandlePointerDown:E,selectedName:C,onSelectParent:N}=e,{t:$}=tt(),[A,z]=u.useState(!1),D=mp(o),O=A&&D,M=$p(n),B=M==="outside",T=B&&n?Ip(n):null,H=T?{left:T.right.left,top:T.above.top}:{right:-30,top:-30},L={top:-28,left:0},P=B&&!!C?{top:-50,left:0}:L,F=T?{top:T.below.top+28,left:0}:{top:"auto",bottom:-28},G=T?{top:T.below.top+56,left:0}:{top:"auto",bottom:-56};return i.jsxs("div",{className:"g7le-element-overlay","data-testid":"g7le-element-overlay",style:{position:"absolute",inset:0,pointerEvents:"none"},children:[t&&i.jsx("div",{"data-testid":"g7le-overlay-hover",style:{position:"absolute",left:t.left,top:t.top,width:t.width,height:t.height,border:"1px dashed #2563eb",pointerEvents:"none"}}),n&&i.jsxs("div",{"data-testid":"g7le-overlay-selected","data-lock-kind":o,style:{position:"absolute",left:n.left,top:n.top,width:n.width,height:n.height,border:o==="none"?"2px solid #2563eb":"2px dashed #f59e0b",pointerEvents:"none"},children:[w?i.jsxs("span",{"data-testid":"g7le-overlay-branch-badge",style:m7,children:["📱 ",w]}):null,C&&(N?i.jsxs("button",{type:"button","data-testid":"g7le-overlay-type-label","data-placement":B?"outside":"inside",title:$("layout_editor.overlay.select_parent"),"aria-label":$("layout_editor.overlay.select_parent"),onClick:V=>{V.preventDefault(),V.stopPropagation(),N()},style:B?_7:v7,children:[i.jsx("span",{"aria-hidden":!0,style:{marginRight:3,opacity:.85},children:"↑"}),C]}):i.jsx("span",{"data-testid":"g7le-overlay-type-label","data-placement":B?"outside":"inside",style:B?Pp:Lp,children:C})),D&&i.jsx("button",{type:"button","data-testid":"g7le-overlay-info-button","aria-label":"info",onClick:V=>{V.stopPropagation(),z(Y=>!Y)},"data-placement":M,style:{position:"absolute",...H,width:20,height:20,borderRadius:10,border:"1px solid #2563eb",background:"#fff",color:"#2563eb",fontSize:12,fontWeight:700,cursor:"pointer",pointerEvents:"auto",padding:0,lineHeight:"18px",zIndex:Ln},children:"ⓘ"}),o==="none"&&i.jsx(b7,{enabledAxes:S,onHandlePointerDown:E}),o==="base"&&h&&i.jsxs("button",{type:"button","data-testid":"g7le-overlay-edit-base",onClick:V=>{V.stopPropagation(),h()},style:{...Po,...L},children:["🔒 ",$("layout_editor.overlay.edit_common_layout"),x?i.jsx("span",{"data-testid":"g7le-overlay-base-layout-label",style:ra,children:x}):null]}),o==="extension"&&v&&i.jsxs("button",{type:"button","data-testid":"g7le-overlay-edit-extension",onClick:V=>{V.stopPropagation(),v()},style:{...Po,...L},children:["🔒 ",$("layout_editor.overlay.edit_extension"),b?i.jsx("span",{"data-testid":"g7le-overlay-extension-label",style:ra,children:b}):null]}),o==="data_bound"&&i.jsxs("span",{"data-testid":"g7le-overlay-data-bound-notice",style:{...Po,...P,pointerEvents:"none",cursor:"default"},children:[$("layout_editor.overlay.locked_data_bound"),k?i.jsx("span",{"data-testid":"g7le-overlay-data-source-label",style:ra,children:k}):null]}),o==="data_bound"&&_&&i.jsxs("button",{type:"button","data-testid":"g7le-overlay-edit-iteration",onClick:V=>{V.stopPropagation(),_()},style:{...Po,...F},children:["↳ ",$("layout_editor.overlay.edit_iteration_item")]}),r==="route_in_tree"&&i.jsx("button",{type:"button","data-testid":"g7le-overlay-link-edit-destination",onClick:V=>{V.stopPropagation(),m()},style:{...Po,...F},children:$("layout_editor.overlay.edit_destination_route")}),(d||p||g&&(y??[]).length>0)&&i.jsxs("div",{"data-testid":"g7le-overlay-branch-affordances",style:{position:"absolute",...G,display:"flex",flexDirection:"column",alignItems:"flex-start",gap:4,pointerEvents:"none"},children:[g&&(y??[]).map(V=>i.jsxs("button",{type:"button","data-testid":`g7le-overlay-jump-device-${V.key}`,title:$("layout_editor.overlay.jump_device_hint",{device:V.label}),onClick:Y=>{Y.stopPropagation(),g(V.key)},style:oa,children:["↗ ",$("layout_editor.overlay.jump_device",{device:V.label})]},V.key)),d&&i.jsxs("button",{type:"button","data-testid":"g7le-overlay-separate-branch",title:$("layout_editor.context_menu.separate_branch_hint"),onClick:V=>{V.stopPropagation(),d()},style:oa,children:["📱 ",c??$("layout_editor.context_menu.separate_branch")]}),p&&i.jsxs("button",{type:"button","data-testid":"g7le-overlay-merge-branch",title:$("layout_editor.context_menu.merge_branch_hint"),onClick:V=>{V.stopPropagation(),p()},style:oa,children:["↩️ ",f??$("layout_editor.context_menu.merge_branch")]})]})]}),i.jsx(g7,{anchor:n&&O?{left:n.left+n.width+8,top:n.top-4}:null,open:O,onClose:()=>z(!1),onEditProps:s,onDuplicate:a,onDelete:l,onSeparateBranch:d,separateBranchLabel:c,onMergeBranch:p,mergeBranchLabel:f})]})}function b7(e){const{enabledAxes:t,onHandlePointerDown:n}=e,o=t?.width??!1,r=t?.height??!1,s=[{key:"nw",style:{left:-4,top:-4},cursor:"nwse-resize",needsWidth:!0,needsHeight:!0},{key:"n",style:{left:"50%",top:-4,transform:"translateX(-50%)"},cursor:"ns-resize",needsWidth:!1,needsHeight:!0},{key:"ne",style:{right:-4,top:-4},cursor:"nesw-resize",needsWidth:!0,needsHeight:!0},{key:"e",style:{right:-4,top:"50%",transform:"translateY(-50%)"},cursor:"ew-resize",needsWidth:!0,needsHeight:!1},{key:"se",style:{right:-4,bottom:-4},cursor:"nwse-resize",needsWidth:!0,needsHeight:!0},{key:"s",style:{left:"50%",bottom:-4,transform:"translateX(-50%)"},cursor:"ns-resize",needsWidth:!1,needsHeight:!0},{key:"sw",style:{left:-4,bottom:-4},cursor:"nesw-resize",needsWidth:!0,needsHeight:!0},{key:"w",style:{left:-4,top:"50%",transform:"translateY(-50%)"},cursor:"ew-resize",needsWidth:!0,needsHeight:!1}];return i.jsx(i.Fragment,{children:s.map(a=>{const l=!a.needsWidth||o,d=!a.needsHeight||r,c=l&&d&&!!n;return!l||!d?null:i.jsx("span",{"data-testid":`g7le-resize-handle-${a.key}`,"data-resize-handle-active":c?"true":"false",onPointerDown:c?p=>{p.preventDefault(),p.stopPropagation(),n?.(a.key,{clientX:p.clientX,clientY:p.clientY})}:void 0,style:{position:"absolute",...a.style,width:8,height:8,background:"#fff",border:"1px solid #2563eb",cursor:a.cursor,pointerEvents:c?"auto":"none",zIndex:Ln}},a.key)})})}const Po={position:"absolute",top:-28,left:0,background:"#fff",border:"1px solid #2563eb",color:"#2563eb",padding:"2px 6px",fontSize:11,borderRadius:4,cursor:"pointer",pointerEvents:"auto",whiteSpace:"nowrap",zIndex:Ln},oa={background:"#fff",border:"1px solid #2563eb",color:"#2563eb",padding:"2px 6px",fontSize:11,borderRadius:4,cursor:"pointer",pointerEvents:"auto",whiteSpace:"nowrap",zIndex:Ln},ra={marginLeft:6,padding:"0 5px",borderRadius:3,background:"#eef2ff",border:"1px solid #c7d2fe",fontSize:10,fontWeight:600,color:"#3730a3"},m7={position:"absolute",top:0,right:0,transform:"translateY(-100%)",background:"rgba(124,58,237,0.92)",color:"#fff",padding:"1px 6px",borderRadius:"3px 3px 0 0",fontSize:10,fontWeight:600,whiteSpace:"nowrap",pointerEvents:"none"},Lp={position:"absolute",top:0,left:0,background:"rgba(37,99,235,0.55)",color:"#fff",padding:"1px 6px",fontSize:10,fontWeight:600,lineHeight:"14px",borderBottomRightRadius:4,letterSpacing:"0.02em",whiteSpace:"nowrap",pointerEvents:"none",userSelect:"none",maxWidth:"100%",overflow:"hidden",textOverflow:"ellipsis"},Pp={position:"absolute",bottom:"100%",left:0,marginBottom:2,background:"rgba(37,99,235,0.55)",color:"#fff",padding:"1px 6px",fontSize:10,fontWeight:600,lineHeight:"14px",borderRadius:4,letterSpacing:"0.02em",whiteSpace:"nowrap",pointerEvents:"none",userSelect:"none"},v7={...Lp,pointerEvents:"auto",cursor:"pointer",background:"rgba(37,99,235,0.82)",border:"none",display:"inline-flex",alignItems:"center",font:"inherit",fontSize:10,fontWeight:600,zIndex:Ln},_7={...Pp,pointerEvents:"auto",cursor:"pointer",background:"rgba(37,99,235,0.82)",border:"none",display:"inline-flex",alignItems:"center",font:"inherit",fontSize:10,fontWeight:600,zIndex:Ln},x7={above:{top:-30,left:"50%",transform:"translateX(-50%)"},below:{bottom:-30,left:"50%",transform:"translateX(-50%)"},left:{left:-30,top:"50%",transform:"translateY(-50%)"},right:{right:-30,top:"50%",transform:"translateY(-50%)"}};function w7(e){const{selectedBox:t,points:n,onAddRequest:o}=e,{t:r}=tt();if(!t||n.length===0)return null;const s=$p(t),l=s==="outside"?Ip(t):null;return i.jsx("div",{className:"g7le-insertion-affordances","data-testid":"g7le-insertion-affordances",style:{position:"absolute",left:t.left,top:t.top,width:t.width,height:t.height,pointerEvents:"none"},children:n.map(d=>{const c=l?{left:l[d.direction].left,top:l[d.direction].top}:x7[d.direction],p=r(`layout_editor.insertion.${d.direction}`);return i.jsx("button",{type:"button",disabled:d.disabled,"data-testid":`g7le-insertion-${d.direction}`,"data-disabled":d.disabled?"true":"false","data-placement":s,"aria-label":p,title:p,onClick:f=>{f.stopPropagation(),d.disabled||o(d)},style:{position:"absolute",...c,width:24,height:24,borderRadius:12,border:"1px solid #2563eb",background:d.disabled?"#cbd5e1":"#fff",color:d.disabled?"#94a3b8":"#2563eb",cursor:d.disabled?"not-allowed":"pointer",opacity:d.disabled?.4:1,fontSize:16,fontWeight:700,lineHeight:"20px",padding:0,pointerEvents:"auto",boxShadow:"0 1px 3px rgba(0,0,0,0.15)",zIndex:Ln},children:"+"},d.direction)})})}const Jr=typeof window<"u"&&typeof window.document<"u"&&typeof window.document.createElement<"u";function lo(e){const t=Object.prototype.toString.call(e);return t==="[object Window]"||t==="[object global]"}function ia(e){return"nodeType"in e}function _t(e){var t,n;return e?lo(e)?e:ia(e)&&(t=(n=e.ownerDocument)==null?void 0:n.defaultView)!=null?t:window:window}function sa(e){const{Document:t}=_t(e);return e instanceof t}function Oo(e){return lo(e)?!1:e instanceof _t(e).HTMLElement}function Op(e){return e instanceof _t(e).SVGElement}function co(e){return e?lo(e)?e.document:ia(e)?sa(e)?e:Oo(e)||Op(e)?e.ownerDocument:document:document:document}const sn=Jr?u.useLayoutEffect:u.useEffect;function Zr(e){const t=u.useRef(e);return sn(()=>{t.current=e}),u.useCallback(function(){for(var n=arguments.length,o=new Array(n),r=0;r{e.current=setInterval(o,r)},[]),n=u.useCallback(()=>{e.current!==null&&(clearInterval(e.current),e.current=null)},[]);return[t,n]}function Bo(e,t){t===void 0&&(t=[e]);const n=u.useRef(e);return sn(()=>{n.current!==e&&(n.current=e)},t),n}function Wo(e,t){const n=u.useRef();return u.useMemo(()=>{const o=e(n.current);return n.current=o,o},[...t])}function Qr(e){const t=Zr(e),n=u.useRef(null),o=u.useCallback(r=>{r!==n.current&&t?.(r,n.current),n.current=r},[]);return[n,o]}function Rr(e){const t=u.useRef();return u.useEffect(()=>{t.current=e},[e]),t.current}let aa={};function ei(e,t){return u.useMemo(()=>{if(t)return t;const n=aa[e]==null?0:aa[e]+1;return aa[e]=n,e+"-"+n},[e,t])}function Bp(e){return function(t){for(var n=arguments.length,o=new Array(n>1?n-1:0),r=1;r{const l=Object.entries(a);for(const[d,c]of l){const p=s[d];p!=null&&(s[d]=p+e*c)}return s},{...t})}}const uo=Bp(1),ti=Bp(-1);function S7(e){return"clientX"in e&&"clientY"in e}function la(e){if(!e)return!1;const{KeyboardEvent:t}=_t(e.target);return t&&e instanceof t}function C7(e){if(!e)return!1;const{TouchEvent:t}=_t(e.target);return t&&e instanceof t}function ni(e){if(C7(e)){if(e.touches&&e.touches.length){const{clientX:t,clientY:n}=e.touches[0];return{x:t,y:n}}else if(e.changedTouches&&e.changedTouches.length){const{clientX:t,clientY:n}=e.changedTouches[0];return{x:t,y:n}}}return S7(e)?{x:e.clientX,y:e.clientY}:null}const jo=Object.freeze({Translate:{toString(e){if(!e)return;const{x:t,y:n}=e;return"translate3d("+(t?Math.round(t):0)+"px, "+(n?Math.round(n):0)+"px, 0)"}},Scale:{toString(e){if(!e)return;const{scaleX:t,scaleY:n}=e;return"scaleX("+t+") scaleY("+n+")"}},Transform:{toString(e){if(e)return[jo.Translate.toString(e),jo.Scale.toString(e)].join(" ")}},Transition:{toString(e){let{property:t,duration:n,easing:o}=e;return t+" "+n+"ms "+o}}}),Wp="a,frame,iframe,input:not([type=hidden]):not(:disabled),select:not(:disabled),textarea:not(:disabled),button:not(:disabled),*[tabindex]";function $7(e){return e.matches(Wp)?e:e.querySelector(Wp)}const E7={display:"none"};function I7(e){let{id:t,value:n}=e;return u.createElement("div",{id:t,style:E7},n)}function A7(e){let{id:t,announcement:n,ariaLiveType:o="assertive"}=e;const r={position:"fixed",top:0,left:0,width:1,height:1,margin:-1,border:0,padding:0,overflow:"hidden",clip:"rect(0 0 0 0)",clipPath:"inset(100%)",whiteSpace:"nowrap"};return u.createElement("div",{id:t,style:r,role:"status","aria-live":o,"aria-atomic":!0},n)}function T7(){const[e,t]=u.useState("");return{announce:u.useCallback(o=>{o!=null&&t(o)},[]),announcement:e}}const jp=u.createContext(null);function D7(e){const t=u.useContext(jp);u.useEffect(()=>{if(!t)throw new Error("useDndMonitor must be used within a children of ");return t(e)},[e,t])}function N7(){const[e]=u.useState(()=>new Set),t=u.useCallback(o=>(e.add(o),()=>e.delete(o)),[e]);return[u.useCallback(o=>{let{type:r,event:s}=o;e.forEach(a=>{var l;return(l=a[r])==null?void 0:l.call(a,s)})},[e]),t]}const z7={draggable:` +`);return t.length>0&&t[t.length-1]===""&&t.pop(),t}const Yu=4e3;function $$(e,t,n=3){const o=Gu(e),r=Gu(t);let s=0;const a=Math.min(o.length,r.length);for(;sYu||p.length>Yu)return{hunks:[],addedCount:-1,removedCount:-1,identical:!1};const f=S$(c,p),y=C$(c,p,f),g=[];let m=s,h=s,v=0,b=0;for(let w=0;w{const s=e.slice(r.start,r.end+1);let a=0,l=0,d=0,c=0;for(const g of s)g.oldLine!==null&&(a===0&&(a=g.oldLine),d++),g.newLine!==null&&(l===0&&(l=g.newLine),c++);const p=a||(d===0?0:1),f=l||(c===0?0:1);return{header:`@@ -${p},${d} +${f},${c} @@`,oldStart:p,oldCount:d,newStart:f,newCount:c,lines:s}})}function Xu(e){return JSON.stringify(Js(e),null,2)}function Js(e){if(Array.isArray(e))return e.map(Js);if(e&&typeof e=="object"){const t=e,n={};for(const o of Object.keys(t).sort())n[o]=Js(t[o]);return n}return e}const I$={display:"flex",flexDirection:"column",height:"100%",minHeight:0},A$={display:"flex",alignItems:"center",gap:8,padding:"12px 16px",borderBottom:"1px solid #e2e8f0",background:"#f8fafc"},T$={display:"inline-flex",alignItems:"center",gap:4,padding:"4px 10px",borderRadius:6,border:"1px solid #cbd5e1",background:"#ffffff",color:"#0f172a",fontSize:12,fontWeight:600,cursor:"pointer",outline:"none"},D$={marginLeft:"auto",border:"none",background:"transparent",fontSize:16,color:"#64748b",cursor:"pointer",outline:"none"},N$={display:"flex",alignItems:"center",gap:12,padding:"8px 16px",borderBottom:"1px solid #e2e8f0",background:"#ffffff",fontSize:12,fontWeight:600},z$={flex:1,minHeight:0,overflow:"auto",background:"#ffffff",fontFamily:'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace',fontSize:12,lineHeight:1.5},Ju={padding:"24px 12px",textAlign:"center",color:"#94a3b8",fontSize:13},M$={padding:"2px 12px",background:"#f1f5f9",color:"#64748b",borderTop:"1px solid #e2e8f0",borderBottom:"1px solid #e2e8f0",userSelect:"none"};function L$(e){return e==="add"?{row:{background:"#e6ffed"},gutter:{background:"#cdffd8",color:"#22863a"},sign:"+",signColor:"#22863a"}:e==="remove"?{row:{background:"#ffeef0"},gutter:{background:"#ffdce0",color:"#b31d28"},sign:"-",signColor:"#b31d28"}:{row:{background:"#ffffff"},gutter:{background:"#f8fafc",color:"#94a3b8"},sign:" ",signColor:"#94a3b8"}}const Zu={display:"inline-block",width:44,textAlign:"right",padding:"0 6px",color:"#94a3b8",userSelect:"none",borderRight:"1px solid #e2e8f0",whiteSpace:"nowrap"};function P$({oldVersion:e,newVersion:t,oldContent:n,newContent:o,t:r,onBack:s,onClose:a}){const l=u.useMemo(()=>{const c=Xu(n),p=Xu(o);return $$(c,p)},[n,o]),d=l.addedCount<0;return i.jsxs("div",{className:"g7le-version-diff","data-testid":"g7le-version-diff",style:I$,children:[i.jsxs("div",{"data-modal-drag-handle":!0,style:A$,children:[i.jsxs("button",{type:"button",onClick:s,style:T$,"data-testid":"g7le-version-diff-back",children:["← ",r("layout_editor.version_history.diff_back")]}),i.jsx("span",{style:{fontSize:14,fontWeight:700},"data-testid":"g7le-version-diff-title",children:r("layout_editor.version_history.diff_title",{old:e,new:t})}),i.jsx("button",{type:"button","aria-label":r("layout_editor.version_history.close"),onClick:a,style:D$,"data-testid":"g7le-version-diff-close",children:"✕"})]}),!d&&i.jsxs("div",{style:N$,"data-testid":"g7le-version-diff-summary",children:[i.jsx("span",{style:{color:"#16a34a"},"data-testid":"g7le-version-diff-added",children:r("layout_editor.version_history.added",{count:Math.max(0,l.addedCount)})}),i.jsx("span",{style:{color:"#dc2626"},"data-testid":"g7le-version-diff-removed",children:r("layout_editor.version_history.removed",{count:Math.max(0,l.removedCount)})})]}),i.jsx("div",{style:z$,children:d?i.jsx("div",{style:Ju,"data-testid":"g7le-version-diff-too-large",children:r("layout_editor.version_history.diff_too_large")}):l.identical?i.jsx("div",{style:Ju,"data-testid":"g7le-version-diff-identical",children:r("layout_editor.version_history.diff_identical")}):l.hunks.map((c,p)=>i.jsxs("div",{"data-testid":`g7le-version-diff-hunk-${p}`,children:[i.jsx("div",{style:M$,"data-testid":`g7le-version-diff-hunk-header-${p}`,children:c.header}),c.lines.map((f,y)=>{const g=L$(f.kind);return i.jsxs("div",{style:{display:"flex",alignItems:"stretch",...g.row},"data-kind":f.kind,"data-testid":`g7le-version-diff-line-${p}-${y}`,children:[i.jsx("span",{style:{...Zu,...g.gutter},"aria-hidden":"true",children:f.oldLine??""}),i.jsx("span",{style:{...Zu,...g.gutter},"aria-hidden":"true",children:f.newLine??""}),i.jsx("span",{style:{display:"inline-block",width:16,textAlign:"center",color:g.signColor,userSelect:"none",fontWeight:700},"aria-hidden":"true",children:g.sign}),i.jsx("span",{style:{whiteSpace:"pre-wrap",wordBreak:"break-word",flex:1,padding:"0 4px"},children:f.content})]},y)})]},p))})]})}const O$={display:"flex",flexDirection:"column",height:"100%",minHeight:0},B$={display:"flex",alignItems:"center",gap:8,padding:"12px 16px",borderBottom:"1px solid #e2e8f0",background:"#f8fafc"},W$={marginLeft:"auto",border:"none",background:"transparent",fontSize:16,color:"#64748b",cursor:"pointer",outline:"none"},j$={flex:1,minHeight:0,overflowY:"auto",padding:12},Qu={padding:"24px 12px",textAlign:"center",color:"#94a3b8",fontSize:13},H$={margin:"8px 16px 0",padding:"8px 12px",borderRadius:6,background:"#fef2f2",color:"#b91c1c",fontSize:12,border:"1px solid #fecaca"},F$={display:"flex",flexDirection:"column",gap:8},K$={display:"flex",alignItems:"center",gap:12,padding:"10px 12px",borderRadius:8,border:"1px solid #e2e8f0",background:"#ffffff"},V$={display:"inline-flex",alignItems:"center",justifyContent:"center",minWidth:44,padding:"2px 8px",borderRadius:999,background:"#eff6ff",color:"#1d4ed8",fontSize:12,fontWeight:700},U$={display:"inline-flex",alignItems:"center",padding:"1px 8px",borderRadius:999,background:"#0f172a",color:"#ffffff",fontSize:10,fontWeight:700,letterSpacing:.3},q$={display:"flex",alignItems:"center",gap:10,fontSize:11,fontWeight:600},G$={display:"flex",flexDirection:"column",gap:2,minWidth:0,flex:1},Ru={padding:"6px 12px",borderRadius:6,border:"1px solid #cbd5e1",background:"#ffffff",color:"#0f172a",fontSize:12,fontWeight:600,cursor:"pointer",outline:"none",whiteSpace:"nowrap"};function ep(e){return e.full_content&&typeof e.full_content=="object"?e.full_content:{endpoint:e.endpoint,components:e.components,data_sources:e.data_sources,metadata:e.metadata}}function Y$(e){if(!e)return"";const t=Date.parse(e);if(Number.isNaN(t))return e;try{return new Date(t).toLocaleString()}catch{return e}}function X$({templateIdentifier:e,target:t,t:n,onRestored:o,onClose:r}){const{versions:s,isLoading:a,isLoadingMore:l,error:d,restoringId:c,hasMore:p,loadVersions:f,loadMore:y,restore:g,loadVersionDetail:m}=k$(e,t,o),[h,v]=u.useState(null),[b,x]=u.useState(null),[k,w]=u.useState(null);u.useEffect(()=>{f()},[f]);const _=u.useCallback(async S=>{v(null),w(S.version);try{const T=await m(S.version);if(T.kind!=="success"){v(T.kind==="not_found"?n("layout_editor.version_history.restore_not_found"):T.message);return}const $=S.version-1,A=s.some(D=>D.version===$);let z={};if(A){const D=await m($);if(D.kind==="success")z=ep(D.detail);else if(D.kind==="network_error"){v(D.message);return}}x({oldVersion:A?$:0,newVersion:S.version,oldContent:z,newContent:ep(T.detail)})}finally{w(null)}},[m,s,n]),C=u.useCallback(async S=>{v(null);const T=n("layout_editor.version_history.restore_confirm");if(!(typeof window<"u"?window.confirm(T):!0))return;const A=await g(S);A.kind==="success"?r():A.kind==="not_found"?v(n("layout_editor.version_history.restore_not_found")):v(A.message)},[g,r,n]),E=h??d;return b?i.jsx(P$,{oldVersion:b.oldVersion,newVersion:b.newVersion,oldContent:b.oldContent,newContent:b.newContent,t:n,onBack:()=>x(null),onClose:r}):i.jsxs("div",{className:"g7le-version-history","data-testid":"g7le-version-history",style:O$,children:[i.jsxs("div",{"data-modal-drag-handle":!0,style:B$,children:[i.jsxs("span",{style:{fontSize:14,fontWeight:700},"data-testid":"g7le-version-history-title",children:["🕘 ",n("layout_editor.version_history.title")]}),i.jsx("button",{type:"button","aria-label":n("layout_editor.version_history.close"),onClick:r,style:W$,"data-testid":"g7le-version-history-close",children:"✕"})]}),E&&i.jsx("div",{style:H$,"data-testid":"g7le-version-history-error",children:E}),i.jsx("div",{style:j$,children:a?i.jsx("div",{style:Qu,"data-testid":"g7le-version-history-loading",children:n("layout_editor.version_history.loading")}):s.length===0?i.jsx("div",{style:Qu,"data-testid":"g7le-version-history-empty",children:n("layout_editor.version_history.empty")}):i.jsxs("div",{"data-testid":"g7le-version-history-list",style:F$,children:[s.map((S,T)=>{const $=S.changes_summary,A=$?.added_count??0,z=$?.removed_count??0,D=$?.char_diff??0,O=Y$(S.created_at),M=c===S.id,B=T===0;return i.jsxs("div",{style:K$,"data-testid":`g7le-version-row-${S.id}`,children:[i.jsxs("span",{style:V$,"data-testid":`g7le-version-badge-${S.id}`,children:["v",S.version]}),i.jsxs("div",{style:G$,children:[i.jsxs("span",{style:{display:"flex",alignItems:"center",gap:8},children:[O&&i.jsx("span",{style:{fontSize:12,color:"#475569",fontWeight:600},children:O}),B&&i.jsx("span",{style:U$,"data-testid":`g7le-version-latest-${S.id}`,children:n("layout_editor.version_history.latest")})]}),i.jsxs("span",{style:q$,"data-testid":`g7le-version-summary-${S.id}`,children:[i.jsx("span",{style:{color:"#16a34a"},"data-testid":`g7le-version-added-${S.id}`,children:n("layout_editor.version_history.added",{count:A})}),i.jsx("span",{style:{color:"#dc2626"},"data-testid":`g7le-version-removed-${S.id}`,children:n("layout_editor.version_history.removed",{count:z})}),i.jsx("span",{style:{color:"#94a3b8",fontWeight:500},"data-testid":`g7le-version-chars-${S.id}`,children:n("layout_editor.version_history.chars",{diff:`${D>=0?"+":""}${D}`})})]}),i.jsx("span",{style:{fontSize:11,color:"#94a3b8"},"data-testid":`g7le-version-author-${S.id}`,children:n("layout_editor.version_history.saved_by",{name:S.created_by_name??n("layout_editor.version_history.unknown_author")})})]}),i.jsxs("div",{style:{display:"flex",alignItems:"center",gap:6},children:[i.jsx("button",{type:"button",onClick:()=>{_(S)},disabled:k!==null,style:{...Ru,background:"#f8fafc",opacity:k!==null?.6:1},"data-testid":`g7le-version-compare-${S.id}`,children:k===S.version?n("layout_editor.version_history.comparing"):n("layout_editor.version_history.compare")}),!B&&i.jsx("button",{type:"button",onClick:()=>{C(S.id)},disabled:M||c!==null,style:{...Ru,opacity:M||c!==null?.6:1},"data-testid":`g7le-version-restore-${S.id}`,children:n(M?"layout_editor.version_history.restoring":"layout_editor.version_history.restore")})]})]},S.id)}),p&&i.jsx("button",{type:"button","data-testid":"g7le-version-history-load-more",style:J$,disabled:l,onClick:()=>{y()},children:n(l?"layout_editor.version_history.loading":"layout_editor.version_history.load_more")})]})})]})}const J$={width:"100%",marginTop:8,padding:"8px 12px",fontSize:12,color:"#475569",background:"#f8fafc",border:"1px solid #e2e8f0",borderRadius:6,cursor:"pointer"},tp=[{id:"copy",group:"clipboard",combos:[{key:"c",mod:!0}],labelKey:"layout_editor.shortcuts.action.copy",requiresSelection:!0},{id:"cut",group:"clipboard",combos:[{key:"x",mod:!0}],labelKey:"layout_editor.shortcuts.action.cut",requiresSelection:!0},{id:"paste",group:"clipboard",combos:[{key:"v",mod:!0}],labelKey:"layout_editor.shortcuts.action.paste"},{id:"openProps",group:"element",combos:[{key:"enter"}],labelKey:"layout_editor.shortcuts.action.open_props",requiresSelection:!0},{id:"delete",group:"element",combos:[{key:"delete"},{key:"backspace"}],labelKey:"layout_editor.shortcuts.action.delete",requiresSelection:!0},{id:"selectParent",group:"element",combos:[{key:"arrowup"}],labelKey:"layout_editor.shortcuts.action.select_parent",requiresSelection:!0},{id:"deselect",group:"element",combos:[{key:"escape"}],labelKey:"layout_editor.shortcuts.action.deselect"},{id:"addElement",group:"element",combos:[{key:"/",mod:!0}],labelKey:"layout_editor.shortcuts.action.add_element"},{id:"undo",group:"history",combos:[{key:"z",mod:!0}],labelKey:"layout_editor.shortcuts.action.undo"},{id:"redo",group:"history",combos:[{key:"z",mod:!0,shift:!0},{key:"y",mod:!0}],labelKey:"layout_editor.shortcuts.action.redo"},{id:"editCode",group:"view",combos:[{key:"e",mod:!0}],labelKey:"layout_editor.shortcuts.action.edit_code"},{id:"preview",group:"view",combos:[{key:"p",mod:!0}],labelKey:"layout_editor.shortcuts.action.preview"},{id:"translations",group:"view",combos:[{key:"l",mod:!0}],labelKey:"layout_editor.shortcuts.action.translations"},{id:"help",group:"view",combos:[{key:"?"}],labelKey:"layout_editor.shortcuts.action.help"},{id:"reset",group:"document",combos:[{key:"r",mod:!0,shift:!0}],labelKey:"layout_editor.shortcuts.action.reset"},{id:"save",group:"document",combos:[{key:"s",mod:!0}],labelKey:"layout_editor.shortcuts.action.save"},{id:"exit",group:"document",combos:[{key:"escape"}],labelKey:"layout_editor.shortcuts.action.exit"}];function Z$(e){return{key:(e.key||"").toLowerCase(),mod:e.ctrlKey||e.metaKey,shift:e.shiftKey,alt:e.altKey}}function Q$(e,t){return e.key===t.key&&!!e.mod==!!t.mod&&!!e.shift==!!t.shift&&!!e.alt==!!t.alt}function R$(e){for(const t of tp)if(t.combos.some(n=>Q$(n,e)))return t;return null}function eE(e,t){const n=[];return e.mod&&n.push(t?"⌘":"Ctrl"),e.shift&&n.push(t?"⇧":"Shift"),e.alt&&n.push(t?"⌥":"Alt"),n.push(tE(e.key)),n.join(" + ")}function tE(e){return{arrowup:"↑",arrowdown:"↓",arrowleft:"←",arrowright:"→",enter:"Enter",escape:"Esc",delete:"Delete",backspace:"Backspace"," ":"Space"}[e]??(e.length===1?e.toUpperCase():e)}const nE=["clipboard","element","history","view","document"],oE={clipboard:"layout_editor.shortcuts.group.clipboard",element:"layout_editor.shortcuts.group.element",history:"layout_editor.shortcuts.group.history",view:"layout_editor.shortcuts.group.view",document:"layout_editor.shortcuts.group.document"};function rE(){return typeof navigator>"u"?!1:/mac|iphone|ipad|ipod/i.test(navigator.platform||navigator.userAgent||"")}function iE({t:e,onClose:t,isMac:n}){const o=n??rE(),r=u.useMemo(()=>{const s=new Map;for(const a of tp){const l=s.get(a.group)??[];l.push(a),s.set(a.group,l)}return s},[]);return i.jsxs("div",{className:"g7le-shortcut-help","data-testid":"g7le-shortcut-help",style:sE,children:[i.jsxs("div",{style:aE,children:[i.jsx("span",{style:lE,children:e("layout_editor.shortcuts.title")}),i.jsx("button",{type:"button","data-testid":"g7le-shortcut-help-close",onClick:t,"aria-label":e("layout_editor.shortcuts.close"),style:dE,children:"✕"})]}),i.jsx("div",{style:cE,children:nE.map(s=>{const a=r.get(s);return!a||a.length===0?null:i.jsxs("div",{style:uE,"data-testid":`g7le-shortcut-group-${s}`,children:[i.jsx("div",{style:pE,children:e(oE[s])}),i.jsx("table",{style:fE,children:i.jsx("tbody",{children:a.map(l=>i.jsxs("tr",{"data-testid":`g7le-shortcut-row-${l.id}`,children:[i.jsx("td",{style:gE,children:e(l.labelKey)}),i.jsx("td",{style:hE,children:l.combos.map((d,c)=>i.jsxs(u.Fragment,{children:[c>0&&i.jsx("span",{style:bE,children:e("layout_editor.shortcuts.or")}),i.jsx("kbd",{style:yE,children:eE(d,o)})]},c))})]},l.id))})})]},s)})}),i.jsx("div",{style:mE,children:e("layout_editor.shortcuts.footnote")})]})}const sE={display:"flex",flexDirection:"column",padding:20,gap:16,width:"100%",boxSizing:"border-box"},aE={display:"flex",alignItems:"center",justifyContent:"space-between",paddingBottom:12,borderBottom:"1px solid #e2e8f0"},lE={fontSize:15,fontWeight:700,color:"#0f172a"},dE={width:28,height:28,border:"none",background:"transparent",color:"#64748b",cursor:"pointer",fontSize:14,borderRadius:6},cE={display:"grid",gridTemplateColumns:"1fr 1fr",columnGap:28,rowGap:18,alignItems:"start"},uE={display:"flex",flexDirection:"column",gap:6},pE={fontSize:12,fontWeight:700,color:"#2563eb"},fE={width:"100%",borderCollapse:"collapse"},gE={padding:"4px 0",fontSize:12.5,color:"#334155",textAlign:"left",whiteSpace:"nowrap",verticalAlign:"middle"},hE={padding:"4px 0",paddingLeft:12,textAlign:"right",verticalAlign:"middle"},yE={display:"inline-block",padding:"1px 6px",fontSize:11,fontFamily:"ui-monospace, monospace",color:"#0f172a",background:"#f1f5f9",border:"1px solid #cbd5e1",borderBottomWidth:2,borderRadius:5,whiteSpace:"nowrap"},bE={fontSize:11,color:"#94a3b8",margin:"0 4px"},mE={fontSize:11,color:"#94a3b8",paddingTop:12,borderTop:"1px solid #f1f5f9"};function vE(e){if(!e||!(e instanceof HTMLElement))return!1;const t=e.tagName;return!!(t==="INPUT"||t==="TEXTAREA"||t==="SELECT"||e.isContentEditable)}function _E(){return typeof document>"u"?!1:!!document.querySelector(".g7le-modal-backdrop, .g7le-modal")}function np(e){const{handlers:t,hasSelection:n,enabled:o=!0}=e;u.useEffect(()=>{if(!o||typeof window>"u")return;const r=s=>{if(vE(s.target))return;const a=Z$(s),l=R$(a);if(!l||_E()||l.requiresSelection&&!n)return;if(l.key==="escape"||a.key==="escape"){if(n){const c=t.deselect;c&&(s.preventDefault(),c())}else{const c=t.exit;c&&(s.preventDefault(),c())}return}const d=t[l.id];d&&(s.preventDefault(),d())};return window.addEventListener("keydown",r),()=>window.removeEventListener("keydown",r)},[t,n,o])}function xE(e){const{result:t,onDismiss:n,onLoadLatest:o,onKeepMyChanges:r}=e,{t:s}=tt();if(u.useEffect(()=>{if(!t||t.kind==="concurrent_modification"||t.kind==="validation_failed")return;const a=setTimeout(n,5e3);return()=>clearTimeout(a)},[t,n]),!t)return null;if(t.kind==="success")return i.jsx(Lo,{tone:"success",testId:"g7le-save-banner-success",onDismiss:n,children:s("layout_editor.save.success")});if(t.kind==="validation_failed"){const a=t.errors??{},l=Object.entries(a);return i.jsxs(Lo,{tone:"error",testId:"g7le-save-banner-validation",onDismiss:n,children:[i.jsx("strong",{children:s("layout_editor.save.validation_failed")}),l.length>0&&i.jsx("ul",{style:op,"data-testid":"g7le-save-banner-validation-errors",children:l.map(([d,c])=>i.jsxs("li",{children:[i.jsx("code",{style:rp,children:d}),": ",c.join(", ")]},d))})]})}return t.kind==="blocked_inactive_extension"?i.jsxs(Lo,{tone:"warning",testId:"g7le-save-banner-blocked",onDismiss:n,children:[i.jsx("strong",{children:s("layout_editor.save.blocked_inactive_extension")}),t.blockedPaths.length>0&&i.jsx("ul",{style:op,"data-testid":"g7le-save-banner-blocked-paths",children:t.blockedPaths.map(a=>i.jsx("li",{children:i.jsx("code",{style:rp,children:a})},a))})]}):t.kind==="network_error"?i.jsxs(Lo,{tone:"error",testId:"g7le-save-banner-network",onDismiss:n,children:[i.jsx("strong",{children:s("layout_editor.save.network_error_title")}),i.jsx("div",{style:{marginTop:4,fontSize:12},children:t.message})]}):t.kind==="guard_no_document"?i.jsx(Lo,{tone:"neutral",testId:"g7le-save-banner-guard-no-document",onDismiss:n,children:s("layout_editor.save.guard_no_document")}):i.jsx("div",{role:"dialog","aria-modal":"true","aria-label":s("layout_editor.save.concurrent.title"),"data-testid":"g7le-save-banner-concurrent",style:CE,children:i.jsxs("div",{style:$E,children:[i.jsx("h2",{style:EE,children:s("layout_editor.save.concurrent.title")}),i.jsx("p",{style:IE,children:s("layout_editor.save.concurrent.message")}),i.jsx("div",{style:AE,children:s("layout_editor.save.concurrent.version_info",{current:String(t.currentVersion),yours:String(t.yourVersion)})}),i.jsxs("div",{style:TE,children:[i.jsx("button",{type:"button","data-testid":"g7le-save-banner-concurrent-load-latest",onClick:()=>{o?.(),n()},style:DE,children:s("layout_editor.save.concurrent.load_latest")}),i.jsx("button",{type:"button","data-testid":"g7le-save-banner-concurrent-keep-mine",onClick:()=>{r?.(),n()},style:NE,children:s("layout_editor.save.concurrent.view_my_changes")}),i.jsx("button",{type:"button","data-testid":"g7le-save-banner-concurrent-cancel",onClick:n,style:zE,children:s("layout_editor.save.concurrent.cancel")})]})]})})}function Lo(e){const{tone:t,testId:n,onDismiss:o,children:r}=e,s=wE[t];return i.jsxs("div",{role:"status","data-testid":n,style:{...kE,background:s.bg,borderColor:s.border,color:s.fg},children:[i.jsx("div",{style:{flex:1,minWidth:0},children:r}),i.jsx("button",{type:"button",onClick:o,"aria-label":"close","data-testid":`${n}-close`,style:SE,children:"×"})]})}const wE={success:{bg:"#dcfce7",border:"#86efac",fg:"#14532d"},error:{bg:"#fee2e2",border:"#fca5a5",fg:"#7f1d1d"},warning:{bg:"#fef3c7",border:"#fcd34d",fg:"#78350f"},neutral:{bg:"#f1f5f9",border:"#cbd5e1",fg:"#334155"}},kE={display:"flex",alignItems:"flex-start",gap:12,padding:"12px 16px",border:"1px solid",borderRadius:0,fontSize:13,lineHeight:1.5},SE={background:"transparent",border:"none",fontSize:18,cursor:"pointer",lineHeight:1,color:"inherit",padding:"0 4px",flexShrink:0},op={margin:"8px 0 0",paddingLeft:20,fontSize:12},rp={background:"rgba(0, 0, 0, 0.06)",padding:"1px 4px",borderRadius:3,fontFamily:"monospace"},CE={position:"fixed",inset:0,background:"rgba(15, 23, 42, 0.6)",display:"flex",alignItems:"center",justifyContent:"center",zIndex:1e4},$E={background:"#fff",borderRadius:8,padding:24,width:"min(480px, 90vw)",boxShadow:"0 10px 25px rgba(0,0,0,0.15)"},EE={margin:"0 0 8px",fontSize:16,fontWeight:600,color:"#0f172a"},IE={margin:"0 0 12px",color:"#334155",fontSize:13,lineHeight:1.5},AE={marginBottom:16,padding:"8px 12px",background:"#f8fafc",borderRadius:6,fontSize:12,color:"#64748b",fontFamily:"monospace"},TE={display:"flex",gap:8,justifyContent:"flex-end"},DE={padding:"8px 14px",background:"#2563eb",color:"#fff",border:"none",borderRadius:6,fontSize:13,fontWeight:600,cursor:"pointer"},NE={padding:"8px 14px",background:"#fff",color:"#334155",border:"1px solid #cbd5e1",borderRadius:6,fontSize:13,fontWeight:500,cursor:"pointer"},zE={padding:"8px 14px",background:"transparent",color:"#64748b",border:"none",borderRadius:6,fontSize:13,cursor:"pointer"};function ip(e){if(!e)return null;const n=new URLSearchParams(e).get("route");return n&&n.length>0?n:null}function Zs(e,t,n){const o=`/admin/layout-editor/${encodeURIComponent(e)}`;if(!t||t.length===0)return o;if(t.startsWith("__")){let r=`${o}?edit=${encodeURIComponent(t)}`;return(t.startsWith("__modal__/")||t.startsWith("__iteration__/"))&&n&&(r+=`&host=${encodeURIComponent(n)}`),r}return`${o}?route=${encodeURIComponent(t)}`}function sp(e){if(!e)return null;const n=new URLSearchParams(e).get("host");return n&&n.length>0?n:null}function ap(e){if(!e)return null;const n=new URLSearchParams(e).get("edit");return n&&n.length>0&&n.startsWith("__")?n:null}function ME(e,t){const n=`/admin/templates/${encodeURIComponent(e)}/edit`;if(!t||t.length===0||t.startsWith("__"))return n;const o=t.replace(/^\*+/,"");return o.length===0?n:`${n}?route=${encodeURIComponent(o)}`}const LE="g7le-editor-route",PE=new Set;function Qs(e){return e.path.startsWith("__group__/")||e.path.startsWith("__conngroup__/")}function lp(e,t){const{t:n,translationEngine:o,translationContext:r,templateIdentifier:s}=t,a=Qs(e);if(!e.label.startsWith("$t:"))return e.label;const l=e.label.slice(3),d=l.indexOf("|"),c=d===-1?l:l.slice(0,d),p={};if(d!==-1)for(const y of l.slice(d+1).split("|")){const g=y.indexOf("=");g!==-1&&(p[y.slice(0,g)]=y.slice(g+1))}if((e.kind==="route"||e.kind==="modal")&&!a&&o&&r&&s!==r.templateId){const y={templateId:s,locale:r.locale},g=Object.keys(p).length>0?"|"+Object.entries(p).map(([h,v])=>`${h}=${v}`).join("|"):void 0,m=o.translate(c,y,g);if(m!==c)return m}const f=n(c,p);if(f===c&&typeof window<"u"){const y=window.G7Core?.t;if(typeof y=="function")try{const g=y(c,p);if(g!==c)return g}catch{}}if(f===c&&e.kind==="route"&&!a){const y=e.path;if(y&&y!=="/"){const g=y.split("/").filter(Boolean);return g[g.length-1]??y}return y==="/"?"/":c}return f}function OE(e){return e.trim().toLocaleLowerCase()}function dp(e,t){if(!t)return e;const n=e.toLocaleLowerCase(),o=[];let r=0,s=n.indexOf(t,r);if(s===-1)return e;let a=0;for(;s!==-1;)s>r&&o.push(e.slice(r,s)),o.push(i.jsx("mark",{className:"g7le-route-tree__highlight",style:{background:"#fde68a",color:"inherit",borderRadius:2,padding:"0 1px"},children:e.slice(s,s+t.length)},`m${a++}`)),r=s+t.length,s=n.indexOf(t,r);return r{const N=f.selectedRoute?.path??"";return N.startsWith("__extension__/")||N.startsWith("__modal__/")?N.slice(N.lastIndexOf("/")+1):null},[f.selectedRoute?.path]),h=u.useMemo(()=>!g||!m?!1:(e.children??[]).some(N=>N.kind==="extension"&&N.extensionId===m||N.kind==="modal"&&N.modalId===m),[g,m,e.children]),[v,b]=u.useState(!0);u.useEffect(()=>{h&&b(!1)},[h]);const x=f.editMode==="extension"?f.selectedRoute?.layoutName??null:null,k=e.kind==="extension"?up(e,f.routeTree)??null:null,w=e.kind==="extension"&&o===e.path&&(x===null||k===null||x===k),_=f.editMode==="modal"&&o?.startsWith("__modal__/")?o.slice(o.lastIndexOf("/")+1):null,C=f.editMode==="modal"?f.selectedRoute?.layoutName??null:null,E=e.kind==="modal"&&!!_&&e.modalId===_&&(C===null||e.modalHostLayout==null||C===e.modalHostLayout),S=e.kind!=="extension"&&e.kind!=="modal"&&o===e.path||w||E||e.kind==="route"&&!y&&r===e.path,$=(e.children?.length??0)>0&&(!g||l||!v),A=!y&&!!e.layoutName&&s.has(e.layoutName),z=y?void 0:e.kind==="extension"?e.extensionId?f.extensionVersions[e.extensionId]:void 0:e.layoutName?f.layoutVersions[e.layoutName]:void 0,D=()=>{if(g){b(N=>!N);return}y||n(e)},O=lp(e,{t:d,translationEngine:c,translationContext:p,templateIdentifier:f.templateIdentifier}),M=y?null:BE(e),B={paddingLeft:12+t*14,paddingRight:12,paddingTop:4,paddingBottom:4,opacity:e.isHidden?.55:1,cursor:g?"pointer":y?"default":"pointer",background:S?"#eff6ff":"transparent",borderLeft:S?"2px solid #2563eb":"2px solid transparent",fontWeight:y||S?600:400,fontSize:y?11:13,color:y?"#64748b":S?"#1d4ed8":"#1e293b",textTransform:y?"uppercase":"none",letterSpacing:y?.6:void 0,transition:"background-color 100ms, color 100ms",display:"flex",flexDirection:"column",alignItems:"stretch",gap:2,userSelect:"none"};return i.jsxs("li",{children:[i.jsxs("div",{"data-testid":y?"g7le-route-tree-group":"g7le-route-tree-item","data-route-path":e.path,"data-route-kind":e.kind,"data-conn-collapsed":g?l?"false":String(v):void 0,role:g||!y?"button":void 0,tabIndex:g||!y?0:void 0,"aria-expanded":g?l?!0:!v:void 0,onClick:D,onKeyDown:N=>{g&&(N.key==="Enter"||N.key===" ")?(N.preventDefault(),b(H=>!H)):!y&&(N.key==="Enter"||N.key===" ")&&(N.preventDefault(),n(e))},style:B,children:[i.jsxs("span",{style:{display:"flex",alignItems:"center",gap:6,minWidth:0},children:[g&&i.jsx("span",{"aria-hidden":"true",style:{flex:"0 0 auto",fontSize:9,color:"#94a3b8",width:10,display:"inline-block"},children:l||!v?"▾":"▸"}),A&&i.jsx("span",{className:"g7le-route-tree__dirty-dot","data-testid":"g7le-route-tree-dirty",title:d("layout_editor.chrome.route_tree.badge.modified"),"aria-label":d("layout_editor.chrome.route_tree.badge.modified"),style:{width:7,height:7,borderRadius:"50%",background:"#f59e0b",flex:"0 0 auto"}}),y?O:dp(O,a),typeof z=="number"&&i.jsxs("span",{className:"g7le-route-tree__version-badge","data-testid":"g7le-route-tree-version",title:d("layout_editor.chrome.route_tree.badge.version_tooltip",{version:String(z)}),style:{flex:"0 0 auto",padding:"0 5px",fontSize:10,fontWeight:600,lineHeight:"16px",borderRadius:4,background:"#eef2ff",color:"#4f46e5",border:"1px solid #e0e7ff",letterSpacing:.2},children:["v",z]}),i.jsx(WE,{node:e})]}),M&&i.jsx("span",{className:"g7le-route-tree__layout-path","data-testid":"g7le-route-tree-layout-path",title:M,style:{fontSize:11,fontWeight:400,color:"#94a3b8",textTransform:"none",letterSpacing:0,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},children:dp(M,a)})]}),$&&i.jsx("ul",{style:{listStyle:"none",margin:0,padding:0},children:e.children.map(N=>i.jsx(cp,{node:N,depth:t+1,onSelect:n,selectedRoutePath:o,activeHostRoutePath:r,dirtyLayoutNames:s,query:a,forceExpand:l},N.path))})]})}function jE(e,t){if(!t||!t.startsWith("__"))return null;const n=t.startsWith("__modal__/"),o=t.startsWith("__extension__/");if(!n&&!o)return null;const r=t.slice(t.lastIndexOf("/")+1),s=a=>{for(const l of a){if(l.connectedHostRoutePath&&(n&&l.kind==="modal"&&l.modalId===r||o&&l.kind==="extension"&&l.extensionId===r))return l.connectedHostRoutePath;if(l.children&&l.children.length>0){const d=s(l.children);if(d)return d}}return null};return s(e)}function up(e,t){if(e.connectedHostRoutePath){const n=pp(t,e.connectedHostRoutePath);if(n?.layoutName)return n.layoutName}if(e.extensionType==="overlay"&&e.extensionTargetName)return e.extensionTargetName}function pp(e,t){for(const n of e){if(n.kind==="route"&&n.path===t)return n;if(n.children&&n.children.length>0){const o=pp(n.children,t);if(o)return o}}return null}function fp(e,t,n){const o=[];for(const r of e){const s=Qs(r),a=r.children&&r.children.length>0?fp(r.children,t,n):[];if(s){a.length>0&&o.push({...r,children:a});continue}const l=lp(r,n).toLocaleLowerCase(),d=r.path.toLocaleLowerCase();(l.includes(t)||!d.startsWith("__")&&d.includes(t)||a.length>0)&&o.push({...r,children:a})}return o}function HE(){const{t:e,translationEngine:t,translationContext:n}=tt(),{state:o,dispatch:r}=Je(),a=Ft()?.dirtyLayoutNames??PE,[l,d]=u.useState(""),c=OE(l),p=u.useMemo(()=>c?fp(o.routeTree,c,{t:e,translationEngine:t,translationContext:n,templateIdentifier:o.templateIdentifier}):o.routeTree,[c,o.routeTree,o.templateIdentifier,e,t,n]),f=h=>{if(h.kind==="base"&&h.layoutName){r({type:"ENTER_BASE_EDIT",layoutName:h.layoutName});return}if(h.kind==="modal"&&h.modalId&&h.modalHostLayout){r({type:"ENTER_MODAL_EDIT",modalId:h.modalId,hostLayout:h.modalHostLayout});return}if(h.kind==="extension"&&h.extensionId){const v=up(h,o.routeTree);r({type:"ENTER_EXTENSION_EDIT",extensionId:h.extensionId,extensionHost:v});return}if(r({type:"SELECT_ROUTE",route:{path:h.path,layoutName:h.layoutName}}),typeof window<"u"&&window.history?.pushState){const v=Zs(o.templateIdentifier,h.path);`${window.location.pathname}${window.location.search}`!==v&&window.history.pushState({source:LE,routePath:h.path},"",v)}},y=o.selectedRoute?.path??null,g=u.useMemo(()=>jE(o.routeTree,y),[o.routeTree,y]);if(o.isRouteTreeCollapsed)return i.jsx("aside",{className:"g7le-route-tree g7le-route-tree--collapsed","data-testid":"g7le-route-tree-panel","data-collapsed":"true",style:{width:28,borderRight:"1px solid #e2e8f0",background:"#ffffff"}});const m=o.routeTree.length>0;return i.jsxs("aside",{className:"g7le-route-tree","data-testid":"g7le-route-tree-panel","data-collapsed":"false",style:{width:280,borderRight:"1px solid #e2e8f0",background:"#ffffff",position:"sticky",top:"var(--g7le-header-h, 0px)",alignSelf:"start",height:"calc(100vh - var(--g7le-header-h, 0px))",overflow:"hidden",minHeight:0,display:"flex",flexDirection:"column"},"aria-label":e("layout_editor.chrome.route_tree.panel_title"),children:[i.jsx("div",{className:"g7le-route-tree__title",style:{padding:"10px 14px",fontSize:12,fontWeight:600,color:"#475569",textTransform:"uppercase",letterSpacing:.6,borderBottom:"1px solid #e2e8f0",background:"#f8fafc",flex:"0 0 auto"},children:e("layout_editor.chrome.route_tree.panel_title")}),m&&i.jsxs("div",{className:"g7le-route-tree__search",style:{padding:"8px 10px",borderBottom:"1px solid #e2e8f0",background:"#ffffff",flex:"0 0 auto",position:"relative"},children:[i.jsx("input",{type:"text",className:"g7le-route-tree__search-input","data-testid":"g7le-route-tree-search",value:l,onChange:h=>d(h.target.value),placeholder:e("layout_editor.chrome.route_tree.search_placeholder"),"aria-label":e("layout_editor.chrome.route_tree.search_placeholder"),style:{width:"100%",boxSizing:"border-box",padding:l?"6px 26px 6px 10px":"6px 10px",fontSize:12,lineHeight:1.4,color:"#1e293b",border:"1px solid #cbd5e1",borderRadius:6,outline:"none",background:"#f8fafc"}}),l&&i.jsx("button",{type:"button",className:"g7le-route-tree__search-clear","data-testid":"g7le-route-tree-search-clear",onClick:()=>d(""),"aria-label":e("layout_editor.chrome.route_tree.search_clear"),title:e("layout_editor.chrome.route_tree.search_clear"),style:{position:"absolute",right:16,top:"50%",transform:"translateY(-50%)",width:18,height:18,display:"inline-flex",alignItems:"center",justifyContent:"center",border:"none",background:"transparent",color:"#94a3b8",cursor:"pointer",fontSize:14,lineHeight:1,padding:0},children:"✕"})]}),!m&&i.jsx("div",{className:"g7le-route-tree__empty","data-testid":"g7le-route-tree-empty",style:{padding:16,fontSize:12,color:"#94a3b8",textAlign:"center"},children:e("layout_editor.chrome.route_tree.empty")}),m&&c&&p.length===0&&i.jsx("div",{className:"g7le-route-tree__no-results","data-testid":"g7le-route-tree-no-results",style:{padding:16,fontSize:12,color:"#94a3b8",textAlign:"center"},children:e("layout_editor.chrome.route_tree.search_no_results")}),m&&p.length>0&&i.jsx("nav",{className:"g7le-route-tree__scroll","data-testid":"g7le-route-tree-scroll",style:{flex:"1 1 auto",minHeight:0,overflowY:"auto"},children:i.jsx("ul",{style:{listStyle:"none",margin:0,padding:0},children:p.map(h=>i.jsx(cp,{node:h,depth:0,onSelect:f,selectedRoutePath:y,activeHostRoutePath:g,dirtyLayoutNames:a,query:c,forceExpand:!!c},h.path))})})]})}const FE=Lt().DynamicRenderer,Rs=Lt(),KE=Rs.ResponsiveProvider;Rs.useResponsive,Rs.ResponsiveContext;const VE=Lt().ActionDispatcher,gp=Lt(),UE=gp.DataSourceManager;gp.dataSourceManager;function Gr(e,t,n){const o=t??{},r=o.message||o.error||`HTTP ${e}`,s=o?.data?.required_permissions??o?.required_permissions;let a;return e===401?a="unauthorized":e===403?a="forbidden":e===404?a="not_found":e>=500?a="server_error":a="unknown",{kind:a,status:e,message:String(r),requiredPermissions:s?String(s):void 0,source:n}}function io(e,t){return{kind:"network",status:0,message:e instanceof Error?e.message:String(e??"Unknown network error"),source:t}}function hp(e){if(typeof e!="object"||e===null)return!1;const t=e;return typeof t.kind=="string"&&typeof t.status=="number"&&typeof t.message=="string"}function Ze(e){if(!e)return[];const t=[],n=e.split(".");for(let o=0;o0&&(t.push({responsive:a}),o+=1);continue}const s=Number(r);Number.isFinite(s)&&Number.isInteger(s)&&s>=0&&t.push(s)}return t}function qE(e,t){if(!e)return{affordance:"none",targetPath:null};const n=GE(e);return n?YE(n)?{affordance:"dynamic_path",targetPath:n}:/^(https?:)?\/\//.test(n)?{affordance:"external_url",targetPath:n}:n.startsWith("/")?t?{affordance:t(n),targetPath:n}:{affordance:"route_not_in_tree",targetPath:n}:{affordance:"route_not_in_tree",targetPath:n}:{affordance:"none",targetPath:null}}function GE(e){const t=e.actions;if(Array.isArray(t)){for(const n of t)if(n&&typeof n=="object"&&n.handler==="navigate"){const o=n.params?.path;if(typeof o=="string"&&o.length>0)return o}}if(e.name==="A"||e.type==="A"){const n=e.props?.href;if(typeof n=="string"&&n.length>0)return n}return null}function YE(e){return/\{\{[\s\S]*?\}\}/.test(e)}function XE(e,t,n){const o=s=>!!(s?.iteration&&s.iteration.source!==void 0),r=s=>{if(!s)return s;const a=s.indexOf(".iteration."),l=s.indexOf(".sortable."),d=[a,l].filter(c=>c>=0).sort((c,p)=>c-p)[0];return d===void 0?s:s.slice(0,d)};return o(e)?{isIteration:!0,iterationSourcePath:r(n)}:n&&n.includes(".iteration.")?{isIteration:!0,iterationSourcePath:r(n)}:t.some(o)?{isIteration:!0,iterationSourcePath:r(n)}:{isIteration:!1,iterationSourcePath:null}}function JE(e,t){const n=a=>{const l=a?.iteration&&a.iteration.source;return typeof l=="string"&&l.length>0?l:null},o=n(e)??t.map(n).find(a=>a!==null)??null;if(o!==null){const a=QE(o);if(a!==null)return a}const r=[];if(typeof e.text=="string"&&r.push(...yp(e.text)),e.props&&typeof e.props=="object")for(const a of Object.values(e.props))typeof a=="string"&&r.push(...yp(a));return r.length===0?null:r.find(a=>!ZE.has(a))??r[0]}const ZE=new Set(["_local","_global","_computed","route","query","props","item","index","event","$event","$response","response"]);function yp(e){const t=[],n=/\{\{([\s\S]*?)\}\}/g;let o;for(;(o=n.exec(e))!==null;){const r=bp(o[1]);r!==null&&t.push(r)}return t}function QE(e){const t=e.replace(/\{\{|\}\}/g,"");return bp(t)}function bp(e){const t=/[A-Za-z_$][A-Za-z0-9_$]*/.exec(e.trim());return t?t[0]:null}function gn(e,t,n,o=[]){if(!e)return"none";if(td(e)||typeof e.slot=="string"||typeof e.__editorSlotName=="string")return"extension_point";if(t!=="route"&&Ji(e,t,n)){const s=e.__source?.kind;return s==="base"?"base":s==="partial"?"partial":s==="extension"?"extension":"base"}if(Cb(e,o))return"data_bound";if(!Ji(e,t,n))return"none";const r=e.__source?.kind;return r==="base"?"base":r==="partial"?"partial":r==="extension"?"extension":"none"}function mp(e){if(!e)return null;if(e.__source?.kind==="extension"&&typeof e.__source.extensionId=="number")return e.__source.extensionId;const t=e.__injectedProps;if(Array.isArray(t)&&t.length>0){const n=t[0]?.extensionId;if(typeof n=="number")return n}return null}function RE(e,t){if(t<=0)return e;const n=e.split(".");let o=0;const r=[];for(let s=0;s=t)break;r.push(a);continue}if(a==="iteration"||a==="sortable"){if(o>=t)break;r.push(a);continue}r.push(a);const l=n[s-1];if((l===void 0||l==="children")&&(o+=1),o>=t)break}return r.join(".")}function vp(e,t,n,o){if(!e||!t)return t;const r=n(t),s=Ib(e,r,o);return s.length===r.length?t:RE(t,s.length)}function _p(e){return e==="none"||e==="data_bound"}function e7(e){const{rootNode:t,editMode:n,currentExtensionId:o,editableRootPath:r,pathParser:s=Ze,resolveRouteMatch:a}=e,[l,d]=u.useState(null),[c,p]=u.useState(null),f=u.useCallback(x=>{if(x===null||n==="route")return!0;if(n==="iteration_item"||n==="modal"){if(!r||r.length===0)return!0;const C=s(x);return xp(C,r)}const k=ea(t,x,s);if(!k)return!0;const w=ta(t,x,s),_=gn(k,n,o,w);return _==="none"||_==="data_bound"},[t,s,n,o,r]),y=u.useCallback((x,k)=>{const _=(k?.currentTarget?.dataset??k?.dataset)?.editorPath??null,C=vp(t,_,s,o);f(C)&&d(C)},[t,s,o,f]),g=u.useCallback((x,k)=>{if(x===null){p(null);return}const _=(k?.currentTarget?.dataset??k?.dataset)?.editorPath??null,C=vp(t,_,s,o);if(!f(C)){p(null);return}p(C)},[t,s,o,f]),m=u.useCallback(()=>{d(null)},[]),h=u.useMemo(()=>{const x=ea(t,l,s),k=ea(t,c,s),w=x?ta(t,l,s):[];let _=gn(x,n,o,w);(n==="iteration_item"||n==="modal")&&l!==null&&x&&r&&r.length>0&&xp(s(l),r)&&(nd(x)?_="data_bound":_==="data_bound"&&(_="none"));const{affordance:C,targetPath:E}=qE(x,a),S=_==="base"&&typeof x?.__source?.layout=="string"?x.__source.layout:null,T=_==="data_bound"&&x?JE(x,w):null,{isIteration:$,iterationSourcePath:A}=_==="data_bound"&&x?XE(x,w,l):{isIteration:!1,iterationSourcePath:null};return{selectedNode:x,hoverNode:k,selectedLockKind:_,selectedNavAffordance:C,selectedNavTargetPath:E,selectedBaseLayout:S,selectedDataSourceId:T,selectedIsIteration:$,selectedIterationSourcePath:A}},[t,l,c,n,o,r,s,a]),v=u.useRef(void 0);v.current!==l&&(v.current=l,Cl({op:l===null?"clear":"select",componentPath:l,componentName:wp(h.selectedNode),lockKind:h.selectedLockKind,navAffordance:h.selectedNavAffordance,sourceExtensionId:mp(h.selectedNode),timestamp:Date.now()}));const b=u.useRef(void 0);return b.current!==c&&(b.current=c,c!==null&&Cl({op:"hover",componentPath:c,componentName:wp(h.hoverNode),lockKind:gn(h.hoverNode,n,o,ta(t,c,s)),navAffordance:"none",sourceExtensionId:mp(h.hoverNode),timestamp:Date.now()})),{selectedPath:l,hoverPath:c,...h,handleSelect:y,handleHover:g,clearSelection:m}}function ea(e,t,n){if(!e||!t)return null;const o=n(t);return We(e,o)}function xp(e,t){if(e.length0?e.name:typeof e.type=="string"&&e.type.length>0?e.type:null:null}let kp=0;function Sp(){return kp}function na(){kp+=1}function t7(e){if(typeof e!="string")return null;const t="__modal__/";if(!e.startsWith(t))return null;const n=e.slice(t.length);return n===""||n.includes("/")?null:n}function n7(e){if(typeof e!="string")return null;const t="__iteration__/";if(!e.startsWith(t))return null;const n=e.slice(t.length);return n!==""?n:null}function Cp(){const{state:e}=Je(),t=e.templateIdentifier,n=e.selectedRoute?.layoutName??null,o=e.editMode,r=o==="modal"?t7(e.selectedRoute?.path):null,s=o==="iteration_item"?n7(e.selectedRoute?.path):null,[a,l]=u.useState(null),[d,c]=u.useState(!1),[p,f]=u.useState(null),[y,g]=u.useState(!1),[m,h]=u.useState(0),[v,b]=u.useState(0),x=u.useRef(new Map),[k,w]=u.useState(()=>new Set),_=u.useCallback((L,I)=>`${L}|${I??""}`,[]),C=u.useRef(null);u.useEffect(()=>{C.current=a},[a]);const E=u.useCallback((L,I)=>{const P=_(o,n);x.current.set(P,{raw:L,lockVersion:I}),w(F=>{if(F.has(P))return F;const G=new Set(F);return G.add(P),G})},[_,o,n]),S=u.useCallback(L=>{x.current.delete(L),w(I=>{if(!I.has(L))return I;const P=new Set(I);return P.delete(L),P})},[]),T=u.useCallback(L=>{const I=`|${L??""}`,P=[];for(const F of x.current.keys())F.endsWith(I)&&P.push(F);for(const F of P)x.current.delete(F);P.length>0&&w(F=>{let G=!1;const V=new Set(F);for(const Y of P)V.delete(Y)&&(G=!0);return G?V:F})},[]),$=u.useCallback(async()=>{if(!n){l(null),f(null),g(!1);return}const L=_(o,n),I=x.current.get(L);if(I){l({layoutName:n,raw:I.raw,lockVersion:I.lockVersion}),g(!0),f(null),c(!1),st({op:"load",layoutName:n,editMode:o,statusCode:0,isDirty:!0,timestamp:Date.now()});return}c(!0),f(null);try{const P=window.G7Config?.cache_version??0,F=en(`/api/layouts/${encodeURIComponent(t)}/${n}`,"json",`${P}.${Sp()}`,"with_source_meta=1"),G=ar(),V={Accept:"application/json"};G&&(V.Authorization=`Bearer ${G}`);const Y=await fetch(F,{headers:V,credentials:"same-origin"}),Z=await Y.json().catch(()=>null);if(!Y.ok){f(Gr(Y.status,Z,"layout")),l(null),g(!1);return}const se=(Z&&(Z.data||Z))??{},re=typeof se.lock_version=="number"?se.lock_version:0;if(o==="modal"&&r){const ee=se,fe=Array.isArray(ee.modals)?ee.modals:[],K=fe.findIndex(ne=>ne&&(ne.id===r||ne.modal_id===r));if(K>=0){const ne=fe[K],me={...ne,props:{...ne.props??{},isOpen:!0}},ze=[...Array.isArray(ee.components)?ee.components:[],me],Se=[ze.length-1];l({layoutName:n,raw:{...ee,components:ze},lockVersion:re,modalContext:{modalId:r,modalIndex:K,hostRaw:ee,originalModalNode:ne,editIndexPath:Se}}),g(!1),st({op:"load",layoutName:n,editMode:o,statusCode:Y.status,timestamp:Date.now()});return}}if(o==="iteration_item"&&s){const ee=se,fe=Ze(s),K={children:ee.components??[]},ne=We(K,fe);if(ne&&Array.isArray(ne.children)){l({layoutName:n,raw:ee,lockVersion:re,iterationContext:{sourcePath:s,sourceIndexPath:fe,hostRaw:ee}}),g(!1),st({op:"load",layoutName:n,editMode:o,statusCode:Y.status,timestamp:Date.now()});return}}l({layoutName:n,raw:se,lockVersion:re}),g(!1),st({op:"load",layoutName:n,editMode:o,statusCode:Y.status,timestamp:Date.now()})}catch(P){f(io(P,"layout")),l(null),g(!1)}finally{c(!1)}},[t,n,o,r,s,_]);u.useEffect(()=>{$()},[$]);const A=u.useCallback(async()=>{S(_(o,n)),na(),b(L=>L+1),await $()},[S,_,o,n,$]),z=u.useCallback(()=>{S(_(o,n))},[S,_,o,n]),D=u.useCallback(L=>{l(I=>{if(!I)return I;const P=I.raw.components??[],F=L(P),G={...I.raw,components:F};return E(G,I.lockVersion),{...I,raw:G}}),g(!0),st({op:"patch",layoutName:n,editMode:o,isDirty:!0,timestamp:Date.now()})},[n,o,E]),O=u.useCallback(()=>{g(!0),st({op:"patch",layoutName:n,editMode:o,isDirty:!0,timestamp:Date.now()})},[n,o]),M=u.useCallback(L=>{l(I=>{if(!I)return I;const P={...I.raw,components:L};return E(P,I.lockVersion),{...I,raw:P}}),g(!0),st({op:"patch",layoutName:n,editMode:o,isDirty:!0,timestamp:Date.now()})},[n,o,E]),B=u.useCallback((L,I,...P)=>{const F=P.length>=1?P[0]:I;l(G=>{if(!G)return G;const V={...G.raw,[L]:I},Y=G.raw.__editor;if(Y&&typeof Y=="object"&&!Array.isArray(Y)){const Z=Y,se=Z.original;se&&typeof se=="object"&&!Array.isArray(se)&&(V.__editor={...Z,original:{...se,[L]:F}})}return E(V,G.lockVersion),{...G,raw:V}}),g(!0),st({op:"patch",layoutName:n,editMode:o,isDirty:!0,timestamp:Date.now()})},[n,o,E]),N=u.useCallback(async L=>{const I=C.current;if(!I)return st({op:"save_guard_result",layoutName:n,editMode:o,guardBlocked:!1,timestamp:Date.now()}),{kind:"guard_no_document"};if(L?.resolveActiveExtensions&&L.sessionAddedPaths?.length)try{const fe=await L.resolveActiveExtensions(),K=o7(I.raw.components,L.sessionAddedPaths,fe);if(K.length>0)return st({op:"save_guard_result",layoutName:n,editMode:o,guardBlocked:!0,guardBlockedPaths:K,timestamp:Date.now()}),{kind:"blocked_inactive_extension",blockedPaths:K}}catch{}if(Hb())try{await Fb(t)}catch{}const P=`/api/admin/templates/${encodeURIComponent(t)}/layouts/${I.layoutName}`,F=ar(),G={Accept:"application/json","Content-Type":"application/json"};F&&(G.Authorization=`Bearer ${F}`);const V=!!I.modalContext,Y=!!I.iterationContext,Z=V||Y?"host_layout_modal_patch":"layout";st({op:"save",layoutName:I.layoutName,editMode:o,saveTarget:Z,endpoint:P,isDirty:y,timestamp:Date.now()});let se;if(V&&I.modalContext){const{modalIndex:fe,hostRaw:K,originalModalNode:ne,editIndexPath:me}=I.modalContext,Ce=Array.isArray(K.modals)?[...K.modals]:[],ze=Array.isArray(I.raw.components)?I.raw.components:[],Se=me&&me.length===1?me[0]:0,qe=ze.length>Se?ze[Se]:void 0,De=qe?Vn([qe]):[],Pe=De.length>0?{...De[0]}:void 0;if(fe>=0&&fe({...Me,children:ze})).children??[];se={...Se,components:Qe}}else se=vo(I.raw);const re=se;let U;try{U=await fetch(P,{method:"PUT",headers:G,credentials:"same-origin",body:JSON.stringify({content:JSON.stringify(re),expected_lock_version:I.lockVersion})})}catch(fe){const K=fe instanceof Error?fe.message:"network error";return st({op:"save_response",layoutName:I.layoutName,editMode:o,endpoint:P,statusCode:0,timestamp:Date.now()}),{kind:"network_error",message:K}}const ee=await U.json().catch(()=>null);if(st({op:"save_response",layoutName:I.layoutName,editMode:o,endpoint:P,statusCode:U.status,conflict:U.status===409?{currentVersion:ee?.current_version,yourVersion:ee?.your_version}:void 0,timestamp:Date.now()}),U.ok){const fe=typeof ee?.data?.lock_version=="number"?ee.data.lock_version:I.lockVersion+1;l({...I,lockVersion:fe}),g(!1),S(_(o,I.layoutName)),T(I.layoutName),na(),h(ne=>ne+1);const K=typeof ee?.data?.current_version=="number"?ee.data.current_version:void 0;return{kind:"success",newLockVersion:fe,newContentVersion:K,savedLayoutName:I.layoutName}}return U.status===409?{kind:"concurrent_modification",currentVersion:ee?.current_version??-1,yourVersion:ee?.your_version??I.lockVersion}:U.status===422?{kind:"validation_failed",status:422,errors:ee?.errors??null}:{kind:"network_error",message:ee?.message??`HTTP ${U.status}`}},[t,n,o,y,S,T,_,r,s]),H=u.useMemo(()=>{const L=new Set,I="route|";for(const P of k)if(P.startsWith(I)){const F=P.slice(I.length);F&&L.add(F)}return L},[k]);return{document:a,isLoading:d,error:p,isDirty:y,saveSuccessCounter:m,reloadCounter:v,dirtyKeys:k,dirtyLayoutNames:H,invalidateCurrentCache:z,reload:A,patchLayout:D,setLayoutComponents:M,patchDocumentRaw:B,save:N,markDirty:O}}function o7(e,t,n){if(!e||e.length===0)return[];const o=[],r=new Set([...n.moduleIds,...n.pluginIds]);for(const s of t){const a=r7(e,s);if(!a)continue;const l=a.__source;if(!l||l.kind!=="extension")continue;const d=String(l.extensionId??"");d.length!==0&&(r.has(d)||o.push(s))}return o}function r7(e,t){const n=Ze(t);return n.length===0?null:We({children:e},n)}function so(e,t){if(!e)return null;const n=e.getBoundingClientRect();if(n.width===0&&n.height===0)return null;if(!t)return{left:n.left,top:n.top,width:n.width,height:n.height,scale:1};const o=t.getBoundingClientRect();return{left:n.left-o.left,top:n.top-o.top,width:n.width,height:n.height,scale:1}}function ao(e,t){if(!e)return!1;if(!t)return!0;const n=t.getBoundingClientRect(),o=1;return!(e.left>=n.width-o||e.left+e.width<=o||e.top>=n.height-o||e.top+e.height<=o)}function $p(e,t){if(typeof window>"u")return()=>{};const n=()=>{e()};window.addEventListener("scroll",n,!0),window.addEventListener("resize",n);let o=null;t&&typeof ResizeObserver<"u"&&(o=new ResizeObserver(()=>e()),o.observe(t));let r=null,s=null;const a=typeof requestAnimationFrame<"u",l=()=>{if(!a){e();return}s===null&&(s=requestAnimationFrame(()=>{s=null,e()}))};return t&&typeof MutationObserver<"u"&&(r=new MutationObserver(l),r.observe(t,{childList:!0,subtree:!0})),()=>{window.removeEventListener("scroll",n,!0),window.removeEventListener("resize",n),o&&(o.disconnect(),o=null),r&&(r.disconnect(),r=null),s!==null&&a&&typeof cancelAnimationFrame<"u"&&(cancelAnimationFrame(s),s=null)}}const Ep=44;function Ip(e){if(!e)return"inside";const t=e.scale||1,n=e.width*t,o=e.height*t;return n"u"||typeof window.getComputedStyle!="function")return{kind:"unknown",display:"",flexDirection:"",flexWrap:""};let t=e,n=(window.getComputedStyle(t).display??"").toLowerCase();for(;n==="contents"&&t.parentElement;)t=t.parentElement,n=(window.getComputedStyle(t).display??"").toLowerCase();const o=window.getComputedStyle(t),r=(o.display??"").toLowerCase(),s=(o.flexDirection??"").toLowerCase(),a=(o.flexWrap??"").toLowerCase(),l=r==="flex"||r==="inline-flex";if(r==="grid"||r==="inline-grid"){const f=Array.from(e.children).filter(h=>h.offsetParent!==null||h.getClientRects().length>0);if(f.length<=1)return{kind:"grid_2d",display:r,flexDirection:s,flexWrap:a};const y=f.map(h=>h.getBoundingClientRect()),g=new Set(y.map(h=>Math.round(h.top))),m=new Set(y.map(h=>Math.round(h.left)));return g.size===1?{kind:"grid_single_row",display:r,flexDirection:s,flexWrap:a}:m.size===1?{kind:"grid_single_column",display:r,flexDirection:s,flexWrap:a}:{kind:"grid_2d",display:r,flexDirection:s,flexWrap:a}}if(!l)return{kind:"block",display:r,flexDirection:s,flexWrap:a};const c=a==="wrap"||a==="wrap-reverse";return s==="column"||s==="column-reverse"?{kind:c?"flex_column_wrap":"flex_column_single",display:r,flexDirection:s,flexWrap:a}:{kind:c?"flex_row_wrap":"flex_row_single",display:r,flexDirection:s,flexWrap:a}}function s7(e){const{parentEl:t,selectedPath:n,selectedEl:o,explicitFlow:r}=e;return u.useMemo(()=>{if(!n||n.length===0)return{points:[],flow:"unknown"};const s=r??Dp(t).kind,a=n.slice(0,-1),l=n[n.length-1]??0;if((s==="flex_row_wrap"||s==="flex_column_wrap"||s==="grid_2d")&&!!o){const p=a7(s,o,a,l);if(p)return{points:p,flow:s}}return{points:l7(s,a,l),flow:s}},[t,n,o,r])}function a7(e,t,n,o){let r=t.parentElement;for(;r&&Np(r)==="contents";)r=r.parentElement;if(!r)return null;const s=[];if(zp(r,s),s.length===0)return null;const a=t.getBoundingClientRect(),l=a.top+a.height/2,d=[],c=[],p=[];for(const v of s){const b=v.rect,x=b.top+b.height/2,k=Math.min(a.bottom,b.bottom)-Math.max(a.top,b.top),w=Math.min(a.height,b.height)||1;k/w>.4?d.push({rect:b,path:v.path}):xv.rect.left-b.rect.left),c.sort((v,b)=>v.rect.top-b.rect.top),p.sort((v,b)=>v.rect.top-b.rect.top);const f=d.findIndex(v=>Math.abs(v.rect.left-a.left)<1&&Math.abs(v.rect.top-a.top)<1),y={direction:"above",disabled:!0,insertion:null},g={direction:"below",disabled:!0,insertion:null},m={direction:"left",disabled:!0,insertion:null},h={direction:"right",disabled:!0,insertion:null};if(f>0){const v=d[f-1],b=Xr(v.path);b&&(m.disabled=!1,m.insertion=b)}else if(d.length>0){const v=d[0],b=Yr(v.path);b&&(m.disabled=!1,m.insertion=b)}if(f>=0&&f0){const v=d[d.length-1],b=Xr(v.path);b&&(h.disabled=!1,h.insertion=b)}if(c.length>0){const v=c[c.length-1],b=Xr(v.path);b&&(y.disabled=!1,y.insertion=b)}else if(s.length>0){const v=s[0],b=Yr(v.path);b&&(y.disabled=!1,y.insertion=b)}if(p.length>0){const v=p[0],b=Yr(v.path);b&&(g.disabled=!1,g.insertion=b)}else if(s.length>0){const v=s[s.length-1],b=Xr(v.path);b&&(g.disabled=!1,g.insertion=b)}else g.disabled=!1,g.insertion={parentPath:n,index:o+1};return y.disabled&&g.disabled&&m.disabled&&h.disabled?null:[y,g,m,h]}function Np(e){return typeof window>"u"?"":(window.getComputedStyle(e).display??"").toLowerCase()}function zp(e,t){const n=Array.from(e.children);for(const o of n){if(Np(o)==="contents"){zp(o,t);continue}const s=o.getAttribute("data-editor-path");if(s&&/\.iteration\.\d+(?:$|\.)/.test(s)||!s)continue;const a=o.getBoundingClientRect();a.width===0&&a.height===0||t.push({el:o,rect:a,path:Ze(s)})}}function Yr(e){if(!e||e.length===0)return null;const t=e[e.length-1];return typeof t!="number"?null:{parentPath:e.slice(0,-1),index:t}}function Xr(e){if(!e||e.length===0)return null;const t=e[e.length-1];return typeof t!="number"?null:{parentPath:e.slice(0,-1),index:t+1}}function l7(e,t,n){const o={direction:"above",disabled:!0,insertion:null},r={direction:"below",disabled:!0,insertion:null},s={direction:"left",disabled:!0,insertion:null},a={direction:"right",disabled:!0,insertion:null},l=d=>({parentPath:t,index:d});switch(e){case"block":case"unknown":{o.disabled=!1,o.insertion=l(n),r.disabled=!1,r.insertion=l(n+1);break}case"flex_row_single":case"grid_single_row":{s.disabled=!1,s.insertion=l(n),a.disabled=!1,a.insertion=l(n+1);break}case"flex_column_single":case"grid_single_column":{o.disabled=!1,o.insertion=l(n),r.disabled=!1,r.insertion=l(n+1);break}case"flex_row_wrap":case"grid_2d":{s.disabled=!1,s.insertion=l(n),a.disabled=!1,a.insertion=l(n+1),o.disabled=!1,o.insertion=l(0),r.disabled=!1,r.insertion=l(-1);break}case"flex_column_wrap":{o.disabled=!1,o.insertion=l(n),r.disabled=!1,r.insertion=l(n+1),s.disabled=!1,s.insertion=l(0),a.disabled=!1,a.insertion=l(-1);break}}return[o,r,s,a]}function d7(e=50){const t=u.useRef([]),n=u.useRef(-1),[,o]=u.useState(0),r=()=>{o(p=>(p+1)%1e6)},s=(p,f)=>{const y=t.current.length,g=n.current;Gh({op:p,actionKind:f?.actionKind,label:f?.label,stackSize:y,cursor:g,canUndo:g>0,canRedo:g>=0&&g{const y=t.current.slice(0,n.current+1);for(y.push(p);y.length>e;)y.shift();t.current=y,n.current=y.length-1,r(),s("push",p)},[e]),l=u.useCallback(()=>{if(n.current<=0)return null;n.current-=1,r();const p=t.current[n.current]??null;return s("undo",p??void 0),p},[]),d=u.useCallback(()=>{const p=n.current+1;if(p>=t.current.length)return null;n.current=p,r();const f=t.current[p]??null;return s("redo",f??void 0),f},[]),c=u.useCallback(()=>{t.current=[],n.current=-1,r(),s("clear")},[]);return{push:a,undo:l,redo:d,clear:c,canUndo:n.current>0,canRedo:n.current>=0&&n.current{if(!o||typeof window>"u")return;const r=s=>{if(c7(s.target)||!(s.ctrlKey||s.metaKey))return;const l=s.key.toLowerCase();if(l==="z"&&!s.shiftKey){s.preventDefault(),t();return}if(l==="z"&&s.shiftKey||l==="y"){s.preventDefault(),n();return}};return window.addEventListener("keydown",r),()=>{window.removeEventListener("keydown",r)}},[t,n,o])}const p7=20,Mp=50,f7=90,Ln=120,g7=130,Lp=80,h7=9e3,Pp=1e4;function y7(e){const t=e<0?0:e>Mp?Mp:e;return p7+t}function b7(e){const{anchor:t,open:n,onClose:o,onEditProps:r,onDuplicate:s,onDelete:a,onSeparateBranch:l,separateBranchLabel:d,onMergeBranch:c,mergeBranchLabel:p}=e,{t:f}=tt(),[y,g]=u.useState(null);if(!n||!t)return null;const m=[{index:0,testid:"g7le-context-menu-edit-props",label:f("layout_editor.context_menu.edit_props"),onSelect:r},{index:1,testid:"g7le-context-menu-duplicate",label:f("layout_editor.context_menu.duplicate"),onSelect:s}];return l?m.push({index:m.length,testid:"g7le-context-menu-separate-branch",label:d??f("layout_editor.context_menu.separate_branch"),onSelect:l,hint:f("layout_editor.context_menu.separate_branch_hint")}):c&&m.push({index:m.length,testid:"g7le-context-menu-merge-branch",label:p??f("layout_editor.context_menu.merge_branch"),onSelect:c,hint:f("layout_editor.context_menu.merge_branch_hint")}),m.push({index:m.length,testid:"g7le-context-menu-delete",label:f("layout_editor.context_menu.delete"),onSelect:a,danger:!0}),i.jsx("div",{className:"g7le-context-menu","data-testid":"g7le-context-menu",role:"menu",style:{position:"absolute",left:t.left,top:t.top,background:"#fff",border:"1px solid #cbd5e1",borderRadius:6,boxShadow:"0 4px 12px rgba(0,0,0,0.15)",zIndex:1200,minWidth:160,padding:4,pointerEvents:"auto"},onMouseLeave:()=>{g(null),o()},children:m.map(h=>{const v=y===h.index,b=h.danger?"#dc2626":"#0f172a",x=h.danger?"#fee2e2":"#e0e7ff",k=h.danger?"#b91c1c":"#1d4ed8";return i.jsx("button",{type:"button",role:"menuitem","data-testid":h.testid,title:h.hint,onClick:w=>{w.stopPropagation(),h.onSelect(),g(null),o()},onMouseEnter:()=>g(h.index),onFocus:()=>g(h.index),style:{...m7,color:v?k:b,background:v?x:"transparent"},children:h.label},h.testid)})})}const m7={display:"block",width:"100%",textAlign:"left",padding:"6px 12px",border:"none",fontSize:13,cursor:"pointer",borderRadius:4,pointerEvents:"auto",transition:"background-color 120ms ease, color 120ms ease"};function v7(e){const{hoverBox:t,selectedBox:n,lockKind:o,navAffordance:r,onEditProps:s,onDuplicate:a,onDelete:l,onSeparateBranch:d,separateBranchLabel:c,onMergeBranch:p,mergeBranchLabel:f,definedDeviceBranches:y,onJumpToDevice:g,onLinkEditDestination:m,onEditBase:h,onEditExtension:v,extensionLabel:b,baseLayoutLabel:x,dataSourceLabel:k,branchLabel:w,onEditIteration:_,resizeEnabledAxes:C,onResizeHandlePointerDown:E,selectedName:S,onSelectParent:T}=e,{t:$}=tt(),[A,z]=u.useState(!1),D=_p(o),O=A&&D,M=Ip(n),B=M==="outside",N=B&&n?Tp(n):null,H=N?{left:N.right.left,top:N.above.top}:{right:-30,top:-30},L={top:-28,left:0},P=B&&!!S?{top:-50,left:0}:L,F=N?{top:N.below.top+28,left:0}:{top:"auto",bottom:-28},G=N?{top:N.below.top+56,left:0}:{top:"auto",bottom:-56};return i.jsxs("div",{className:"g7le-element-overlay","data-testid":"g7le-element-overlay",style:{position:"absolute",inset:0,pointerEvents:"none"},children:[t&&i.jsx("div",{"data-testid":"g7le-overlay-hover",style:{position:"absolute",left:t.left,top:t.top,width:t.width,height:t.height,border:"1px dashed #2563eb",pointerEvents:"none"}}),n&&i.jsxs("div",{"data-testid":"g7le-overlay-selected","data-lock-kind":o,style:{position:"absolute",left:n.left,top:n.top,width:n.width,height:n.height,border:o==="none"?"2px solid #2563eb":"2px dashed #f59e0b",pointerEvents:"none"},children:[w?i.jsxs("span",{"data-testid":"g7le-overlay-branch-badge",style:x7,children:["📱 ",w]}):null,S&&(T?i.jsxs("button",{type:"button","data-testid":"g7le-overlay-type-label","data-placement":B?"outside":"inside",title:$("layout_editor.overlay.select_parent"),"aria-label":$("layout_editor.overlay.select_parent"),onClick:V=>{V.preventDefault(),V.stopPropagation(),T()},style:B?k7:w7,children:[i.jsx("span",{"aria-hidden":!0,style:{marginRight:3,opacity:.85},children:"↑"}),S]}):i.jsx("span",{"data-testid":"g7le-overlay-type-label","data-placement":B?"outside":"inside",style:B?Bp:Op,children:S})),D&&i.jsx("button",{type:"button","data-testid":"g7le-overlay-info-button","aria-label":"info",onClick:V=>{V.stopPropagation(),z(Y=>!Y)},"data-placement":M,style:{position:"absolute",...H,width:20,height:20,borderRadius:10,border:"1px solid #2563eb",background:"#fff",color:"#2563eb",fontSize:12,fontWeight:700,cursor:"pointer",pointerEvents:"auto",padding:0,lineHeight:"18px",zIndex:Ln},children:"ⓘ"}),o==="none"&&i.jsx(_7,{enabledAxes:C,onHandlePointerDown:E}),o==="base"&&h&&i.jsxs("button",{type:"button","data-testid":"g7le-overlay-edit-base",onClick:V=>{V.stopPropagation(),h()},style:{...Po,...L},children:["🔒 ",$("layout_editor.overlay.edit_common_layout"),x?i.jsx("span",{"data-testid":"g7le-overlay-base-layout-label",style:ra,children:x}):null]}),o==="extension"&&v&&i.jsxs("button",{type:"button","data-testid":"g7le-overlay-edit-extension",onClick:V=>{V.stopPropagation(),v()},style:{...Po,...L},children:["🔒 ",$("layout_editor.overlay.edit_extension"),b?i.jsx("span",{"data-testid":"g7le-overlay-extension-label",style:ra,children:b}):null]}),o==="data_bound"&&i.jsxs("span",{"data-testid":"g7le-overlay-data-bound-notice",style:{...Po,...P,pointerEvents:"none",cursor:"default"},children:[$("layout_editor.overlay.locked_data_bound"),k?i.jsx("span",{"data-testid":"g7le-overlay-data-source-label",style:ra,children:k}):null]}),o==="data_bound"&&_&&i.jsxs("button",{type:"button","data-testid":"g7le-overlay-edit-iteration",onClick:V=>{V.stopPropagation(),_()},style:{...Po,...F},children:["↳ ",$("layout_editor.overlay.edit_iteration_item")]}),r==="route_in_tree"&&i.jsx("button",{type:"button","data-testid":"g7le-overlay-link-edit-destination",onClick:V=>{V.stopPropagation(),m()},style:{...Po,...F},children:$("layout_editor.overlay.edit_destination_route")}),(d||p||g&&(y??[]).length>0)&&i.jsxs("div",{"data-testid":"g7le-overlay-branch-affordances",style:{position:"absolute",...G,display:"flex",flexDirection:"column",alignItems:"flex-start",gap:4,pointerEvents:"none"},children:[g&&(y??[]).map(V=>i.jsxs("button",{type:"button","data-testid":`g7le-overlay-jump-device-${V.key}`,title:$("layout_editor.overlay.jump_device_hint",{device:V.label}),onClick:Y=>{Y.stopPropagation(),g(V.key)},style:oa,children:["↗ ",$("layout_editor.overlay.jump_device",{device:V.label})]},V.key)),d&&i.jsxs("button",{type:"button","data-testid":"g7le-overlay-separate-branch",title:$("layout_editor.context_menu.separate_branch_hint"),onClick:V=>{V.stopPropagation(),d()},style:oa,children:["📱 ",c??$("layout_editor.context_menu.separate_branch")]}),p&&i.jsxs("button",{type:"button","data-testid":"g7le-overlay-merge-branch",title:$("layout_editor.context_menu.merge_branch_hint"),onClick:V=>{V.stopPropagation(),p()},style:oa,children:["↩️ ",f??$("layout_editor.context_menu.merge_branch")]})]})]}),i.jsx(b7,{anchor:n&&O?{left:n.left+n.width+8,top:n.top-4}:null,open:O,onClose:()=>z(!1),onEditProps:s,onDuplicate:a,onDelete:l,onSeparateBranch:d,separateBranchLabel:c,onMergeBranch:p,mergeBranchLabel:f})]})}function _7(e){const{enabledAxes:t,onHandlePointerDown:n}=e,o=t?.width??!1,r=t?.height??!1,s=[{key:"nw",style:{left:-4,top:-4},cursor:"nwse-resize",needsWidth:!0,needsHeight:!0},{key:"n",style:{left:"50%",top:-4,transform:"translateX(-50%)"},cursor:"ns-resize",needsWidth:!1,needsHeight:!0},{key:"ne",style:{right:-4,top:-4},cursor:"nesw-resize",needsWidth:!0,needsHeight:!0},{key:"e",style:{right:-4,top:"50%",transform:"translateY(-50%)"},cursor:"ew-resize",needsWidth:!0,needsHeight:!1},{key:"se",style:{right:-4,bottom:-4},cursor:"nwse-resize",needsWidth:!0,needsHeight:!0},{key:"s",style:{left:"50%",bottom:-4,transform:"translateX(-50%)"},cursor:"ns-resize",needsWidth:!1,needsHeight:!0},{key:"sw",style:{left:-4,bottom:-4},cursor:"nesw-resize",needsWidth:!0,needsHeight:!0},{key:"w",style:{left:-4,top:"50%",transform:"translateY(-50%)"},cursor:"ew-resize",needsWidth:!0,needsHeight:!1}];return i.jsx(i.Fragment,{children:s.map(a=>{const l=!a.needsWidth||o,d=!a.needsHeight||r,c=l&&d&&!!n;return!l||!d?null:i.jsx("span",{"data-testid":`g7le-resize-handle-${a.key}`,"data-resize-handle-active":c?"true":"false",onPointerDown:c?p=>{p.preventDefault(),p.stopPropagation(),n?.(a.key,{clientX:p.clientX,clientY:p.clientY})}:void 0,style:{position:"absolute",...a.style,width:8,height:8,background:"#fff",border:"1px solid #2563eb",cursor:a.cursor,pointerEvents:c?"auto":"none",zIndex:Ln}},a.key)})})}const Po={position:"absolute",top:-28,left:0,background:"#fff",border:"1px solid #2563eb",color:"#2563eb",padding:"2px 6px",fontSize:11,borderRadius:4,cursor:"pointer",pointerEvents:"auto",whiteSpace:"nowrap",zIndex:Ln},oa={background:"#fff",border:"1px solid #2563eb",color:"#2563eb",padding:"2px 6px",fontSize:11,borderRadius:4,cursor:"pointer",pointerEvents:"auto",whiteSpace:"nowrap",zIndex:Ln},ra={marginLeft:6,padding:"0 5px",borderRadius:3,background:"#eef2ff",border:"1px solid #c7d2fe",fontSize:10,fontWeight:600,color:"#3730a3"},x7={position:"absolute",top:0,right:0,transform:"translateY(-100%)",background:"rgba(124,58,237,0.92)",color:"#fff",padding:"1px 6px",borderRadius:"3px 3px 0 0",fontSize:10,fontWeight:600,whiteSpace:"nowrap",pointerEvents:"none"},Op={position:"absolute",top:0,left:0,background:"rgba(37,99,235,0.55)",color:"#fff",padding:"1px 6px",fontSize:10,fontWeight:600,lineHeight:"14px",borderBottomRightRadius:4,letterSpacing:"0.02em",whiteSpace:"nowrap",pointerEvents:"none",userSelect:"none",maxWidth:"100%",overflow:"hidden",textOverflow:"ellipsis"},Bp={position:"absolute",bottom:"100%",left:0,marginBottom:2,background:"rgba(37,99,235,0.55)",color:"#fff",padding:"1px 6px",fontSize:10,fontWeight:600,lineHeight:"14px",borderRadius:4,letterSpacing:"0.02em",whiteSpace:"nowrap",pointerEvents:"none",userSelect:"none"},w7={...Op,pointerEvents:"auto",cursor:"pointer",background:"rgba(37,99,235,0.82)",border:"none",display:"inline-flex",alignItems:"center",font:"inherit",fontSize:10,fontWeight:600,zIndex:Ln},k7={...Bp,pointerEvents:"auto",cursor:"pointer",background:"rgba(37,99,235,0.82)",border:"none",display:"inline-flex",alignItems:"center",font:"inherit",fontSize:10,fontWeight:600,zIndex:Ln},S7={above:{top:-30,left:"50%",transform:"translateX(-50%)"},below:{bottom:-30,left:"50%",transform:"translateX(-50%)"},left:{left:-30,top:"50%",transform:"translateY(-50%)"},right:{right:-30,top:"50%",transform:"translateY(-50%)"}};function C7(e){const{selectedBox:t,points:n,onAddRequest:o}=e,{t:r}=tt();if(!t||n.length===0)return null;const s=Ip(t),l=s==="outside"?Tp(t):null;return i.jsx("div",{className:"g7le-insertion-affordances","data-testid":"g7le-insertion-affordances",style:{position:"absolute",left:t.left,top:t.top,width:t.width,height:t.height,pointerEvents:"none"},children:n.map(d=>{const c=l?{left:l[d.direction].left,top:l[d.direction].top}:S7[d.direction],p=r(`layout_editor.insertion.${d.direction}`);return i.jsx("button",{type:"button",disabled:d.disabled,"data-testid":`g7le-insertion-${d.direction}`,"data-disabled":d.disabled?"true":"false","data-placement":s,"aria-label":p,title:p,onClick:f=>{f.stopPropagation(),d.disabled||o(d)},style:{position:"absolute",...c,width:24,height:24,borderRadius:12,border:"1px solid #2563eb",background:d.disabled?"#cbd5e1":"#fff",color:d.disabled?"#94a3b8":"#2563eb",cursor:d.disabled?"not-allowed":"pointer",opacity:d.disabled?.4:1,fontSize:16,fontWeight:700,lineHeight:"20px",padding:0,pointerEvents:"auto",boxShadow:"0 1px 3px rgba(0,0,0,0.15)",zIndex:Ln},children:"+"},d.direction)})})}const Jr=typeof window<"u"&&typeof window.document<"u"&&typeof window.document.createElement<"u";function lo(e){const t=Object.prototype.toString.call(e);return t==="[object Window]"||t==="[object global]"}function ia(e){return"nodeType"in e}function _t(e){var t,n;return e?lo(e)?e:ia(e)&&(t=(n=e.ownerDocument)==null?void 0:n.defaultView)!=null?t:window:window}function sa(e){const{Document:t}=_t(e);return e instanceof t}function Oo(e){return lo(e)?!1:e instanceof _t(e).HTMLElement}function Wp(e){return e instanceof _t(e).SVGElement}function co(e){return e?lo(e)?e.document:ia(e)?sa(e)?e:Oo(e)||Wp(e)?e.ownerDocument:document:document:document}const sn=Jr?u.useLayoutEffect:u.useEffect;function Zr(e){const t=u.useRef(e);return sn(()=>{t.current=e}),u.useCallback(function(){for(var n=arguments.length,o=new Array(n),r=0;r{e.current=setInterval(o,r)},[]),n=u.useCallback(()=>{e.current!==null&&(clearInterval(e.current),e.current=null)},[]);return[t,n]}function Bo(e,t){t===void 0&&(t=[e]);const n=u.useRef(e);return sn(()=>{n.current!==e&&(n.current=e)},t),n}function Wo(e,t){const n=u.useRef();return u.useMemo(()=>{const o=e(n.current);return n.current=o,o},[...t])}function Qr(e){const t=Zr(e),n=u.useRef(null),o=u.useCallback(r=>{r!==n.current&&t?.(r,n.current),n.current=r},[]);return[n,o]}function Rr(e){const t=u.useRef();return u.useEffect(()=>{t.current=e},[e]),t.current}let aa={};function ei(e,t){return u.useMemo(()=>{if(t)return t;const n=aa[e]==null?0:aa[e]+1;return aa[e]=n,e+"-"+n},[e,t])}function jp(e){return function(t){for(var n=arguments.length,o=new Array(n>1?n-1:0),r=1;r{const l=Object.entries(a);for(const[d,c]of l){const p=s[d];p!=null&&(s[d]=p+e*c)}return s},{...t})}}const uo=jp(1),ti=jp(-1);function E7(e){return"clientX"in e&&"clientY"in e}function la(e){if(!e)return!1;const{KeyboardEvent:t}=_t(e.target);return t&&e instanceof t}function I7(e){if(!e)return!1;const{TouchEvent:t}=_t(e.target);return t&&e instanceof t}function ni(e){if(I7(e)){if(e.touches&&e.touches.length){const{clientX:t,clientY:n}=e.touches[0];return{x:t,y:n}}else if(e.changedTouches&&e.changedTouches.length){const{clientX:t,clientY:n}=e.changedTouches[0];return{x:t,y:n}}}return E7(e)?{x:e.clientX,y:e.clientY}:null}const jo=Object.freeze({Translate:{toString(e){if(!e)return;const{x:t,y:n}=e;return"translate3d("+(t?Math.round(t):0)+"px, "+(n?Math.round(n):0)+"px, 0)"}},Scale:{toString(e){if(!e)return;const{scaleX:t,scaleY:n}=e;return"scaleX("+t+") scaleY("+n+")"}},Transform:{toString(e){if(e)return[jo.Translate.toString(e),jo.Scale.toString(e)].join(" ")}},Transition:{toString(e){let{property:t,duration:n,easing:o}=e;return t+" "+n+"ms "+o}}}),Hp="a,frame,iframe,input:not([type=hidden]):not(:disabled),select:not(:disabled),textarea:not(:disabled),button:not(:disabled),*[tabindex]";function A7(e){return e.matches(Hp)?e:e.querySelector(Hp)}const T7={display:"none"};function D7(e){let{id:t,value:n}=e;return u.createElement("div",{id:t,style:T7},n)}function N7(e){let{id:t,announcement:n,ariaLiveType:o="assertive"}=e;const r={position:"fixed",top:0,left:0,width:1,height:1,margin:-1,border:0,padding:0,overflow:"hidden",clip:"rect(0 0 0 0)",clipPath:"inset(100%)",whiteSpace:"nowrap"};return u.createElement("div",{id:t,style:r,role:"status","aria-live":o,"aria-atomic":!0},n)}function z7(){const[e,t]=u.useState("");return{announce:u.useCallback(o=>{o!=null&&t(o)},[]),announcement:e}}const Fp=u.createContext(null);function M7(e){const t=u.useContext(Fp);u.useEffect(()=>{if(!t)throw new Error("useDndMonitor must be used within a children of ");return t(e)},[e,t])}function L7(){const[e]=u.useState(()=>new Set),t=u.useCallback(o=>(e.add(o),()=>e.delete(o)),[e]);return[u.useCallback(o=>{let{type:r,event:s}=o;e.forEach(a=>{var l;return(l=a[r])==null?void 0:l.call(a,s)})},[e]),t]}const P7={draggable:` To pick up a draggable item, press the space bar. While dragging, use the arrow keys to move the item. Press space again to drop the item in its new position, or press escape to cancel. - `},M7={onDragStart(e){let{active:t}=e;return"Picked up draggable item "+t.id+"."},onDragOver(e){let{active:t,over:n}=e;return n?"Draggable item "+t.id+" was moved over droppable area "+n.id+".":"Draggable item "+t.id+" is no longer over a droppable area."},onDragEnd(e){let{active:t,over:n}=e;return n?"Draggable item "+t.id+" was dropped over droppable area "+n.id:"Draggable item "+t.id+" was dropped."},onDragCancel(e){let{active:t}=e;return"Dragging was cancelled. Draggable item "+t.id+" was dropped."}};function L7(e){let{announcements:t=M7,container:n,hiddenTextDescribedById:o,screenReaderInstructions:r=z7}=e;const{announce:s,announcement:a}=T7(),l=ei("DndLiveRegion"),[d,c]=u.useState(!1);if(u.useEffect(()=>{c(!0)},[]),D7(u.useMemo(()=>({onDragStart(f){let{active:y}=f;s(t.onDragStart({active:y}))},onDragMove(f){let{active:y,over:g}=f;t.onDragMove&&s(t.onDragMove({active:y,over:g}))},onDragOver(f){let{active:y,over:g}=f;s(t.onDragOver({active:y,over:g}))},onDragEnd(f){let{active:y,over:g}=f;s(t.onDragEnd({active:y,over:g}))},onDragCancel(f){let{active:y,over:g}=f;s(t.onDragCancel({active:y,over:g}))}}),[s,t])),!d)return null;const p=u.createElement(u.Fragment,null,u.createElement(I7,{id:o,value:r.draggable}),u.createElement(A7,{id:l,announcement:a}));return n?Cn.createPortal(p,n):p}var it;(function(e){e.DragStart="dragStart",e.DragMove="dragMove",e.DragEnd="dragEnd",e.DragCancel="dragCancel",e.DragOver="dragOver",e.RegisterDroppable="registerDroppable",e.SetDroppableDisabled="setDroppableDisabled",e.UnregisterDroppable="unregisterDroppable"})(it||(it={}));function oi(){}function P7(e,t){return u.useMemo(()=>({sensor:e,options:t??{}}),[e,t])}function O7(){for(var e=arguments.length,t=new Array(e),n=0;n[...t].filter(o=>o!=null),[...t])}const Ht=Object.freeze({x:0,y:0});function B7(e,t){return Math.sqrt(Math.pow(e.x-t.x,2)+Math.pow(e.y-t.y,2))}function W7(e,t){const n=ni(e);if(!n)return"0 0";const o={x:(n.x-t.left)/t.width*100,y:(n.y-t.top)/t.height*100};return o.x+"% "+o.y+"%"}function j7(e,t){let{data:{value:n}}=e,{data:{value:o}}=t;return n-o}function H7(e,t){let{data:{value:n}}=e,{data:{value:o}}=t;return o-n}function F7(e){let{left:t,top:n,height:o,width:r}=e;return[{x:t,y:n},{x:t+r,y:n},{x:t,y:n+o},{x:t+r,y:n+o}]}function K7(e,t){if(!e||e.length===0)return null;const[n]=e;return n[t]}function V7(e,t){const n=Math.max(t.top,e.top),o=Math.max(t.left,e.left),r=Math.min(t.left+t.width,e.left+e.width),s=Math.min(t.top+t.height,e.top+e.height),a=r-o,l=s-n;if(o{let{collisionRect:t,droppableRects:n,droppableContainers:o}=e;const r=[];for(const s of o){const{id:a}=s,l=n.get(a);if(l){const d=V7(l,t);d>0&&r.push({id:a,data:{droppableContainer:s,value:d}})}}return r.sort(H7)};function q7(e,t){const{top:n,left:o,bottom:r,right:s}=t;return n<=e.y&&e.y<=r&&o<=e.x&&e.x<=s}const G7=e=>{let{droppableContainers:t,droppableRects:n,pointerCoordinates:o}=e;if(!o)return[];const r=[];for(const s of t){const{id:a}=s,l=n.get(a);if(l&&q7(o,l)){const c=F7(l).reduce((f,y)=>f+B7(o,y),0),p=Number((c/4).toFixed(4));r.push({id:a,data:{droppableContainer:s,value:p}})}}return r.sort(j7)};function Y7(e,t,n){return{...e,scaleX:t&&n?t.width/n.width:1,scaleY:t&&n?t.height/n.height:1}}function Hp(e,t){return e&&t?{x:e.left-t.left,y:e.top-t.top}:Ht}function X7(e){return function(n){for(var o=arguments.length,r=new Array(o>1?o-1:0),s=1;s({...a,top:a.top+e*l.y,bottom:a.bottom+e*l.y,left:a.left+e*l.x,right:a.right+e*l.x}),{...n})}}const J7=X7(1);function Fp(e){if(e.startsWith("matrix3d(")){const t=e.slice(9,-1).split(/, /);return{x:+t[12],y:+t[13],scaleX:+t[0],scaleY:+t[5]}}else if(e.startsWith("matrix(")){const t=e.slice(7,-1).split(/, /);return{x:+t[4],y:+t[5],scaleX:+t[0],scaleY:+t[3]}}return null}function Z7(e,t,n){const o=Fp(t);if(!o)return e;const{scaleX:r,scaleY:s,x:a,y:l}=o,d=e.left-a-(1-r)*parseFloat(n),c=e.top-l-(1-s)*parseFloat(n.slice(n.indexOf(" ")+1)),p=r?e.width/r:e.width,f=s?e.height/s:e.height;return{width:p,height:f,top:c,right:d+p,bottom:c+f,left:d}}const Q7={ignoreTransform:!1};function Ho(e,t){t===void 0&&(t=Q7);let n=e.getBoundingClientRect();if(t.ignoreTransform){const{transform:c,transformOrigin:p}=_t(e).getComputedStyle(e);c&&(n=Z7(n,c,p))}const{top:o,left:r,width:s,height:a,bottom:l,right:d}=n;return{top:o,left:r,width:s,height:a,bottom:l,right:d}}function Kp(e){return Ho(e,{ignoreTransform:!0})}function R7(e){const t=e.innerWidth,n=e.innerHeight;return{top:0,left:0,right:t,bottom:n,width:t,height:n}}function e2(e,t){return t===void 0&&(t=_t(e).getComputedStyle(e)),t.position==="fixed"}function t2(e,t){t===void 0&&(t=_t(e).getComputedStyle(e));const n=/(auto|scroll|overlay)/;return["overflow","overflowX","overflowY"].some(r=>{const s=t[r];return typeof s=="string"?n.test(s):!1})}function da(e,t){const n=[];function o(r){if(t!=null&&n.length>=t||!r)return n;if(sa(r)&&r.scrollingElement!=null&&!n.includes(r.scrollingElement))return n.push(r.scrollingElement),n;if(!Oo(r)||Op(r)||n.includes(r))return n;const s=_t(e).getComputedStyle(r);return r!==e&&t2(r,s)&&n.push(r),e2(r,s)?n:o(r.parentNode)}return e?o(e):n}function Vp(e){const[t]=da(e,1);return t??null}function ca(e){return!Jr||!e?null:lo(e)?e:ia(e)?sa(e)||e===co(e).scrollingElement?window:Oo(e)?e:null:null}function Up(e){return lo(e)?e.scrollX:e.scrollLeft}function qp(e){return lo(e)?e.scrollY:e.scrollTop}function ua(e){return{x:Up(e),y:qp(e)}}var lt;(function(e){e[e.Forward=1]="Forward",e[e.Backward=-1]="Backward"})(lt||(lt={}));function Gp(e){return!Jr||!e?!1:e===document.scrollingElement}function Yp(e){const t={x:0,y:0},n=Gp(e)?{height:window.innerHeight,width:window.innerWidth}:{height:e.clientHeight,width:e.clientWidth},o={x:e.scrollWidth-n.width,y:e.scrollHeight-n.height},r=e.scrollTop<=t.y,s=e.scrollLeft<=t.x,a=e.scrollTop>=o.y,l=e.scrollLeft>=o.x;return{isTop:r,isLeft:s,isBottom:a,isRight:l,maxScroll:o,minScroll:t}}const n2={x:.2,y:.2};function o2(e,t,n,o,r){let{top:s,left:a,right:l,bottom:d}=n;o===void 0&&(o=10),r===void 0&&(r=n2);const{isTop:c,isBottom:p,isLeft:f,isRight:y}=Yp(e),g={x:0,y:0},m={x:0,y:0},h={height:t.height*r.y,width:t.width*r.x};return!c&&s<=t.top+h.height?(g.y=lt.Backward,m.y=o*Math.abs((t.top+h.height-s)/h.height)):!p&&d>=t.bottom-h.height&&(g.y=lt.Forward,m.y=o*Math.abs((t.bottom-h.height-d)/h.height)),!y&&l>=t.right-h.width?(g.x=lt.Forward,m.x=o*Math.abs((t.right-h.width-l)/h.width)):!f&&a<=t.left+h.width&&(g.x=lt.Backward,m.x=o*Math.abs((t.left+h.width-a)/h.width)),{direction:g,speed:m}}function r2(e){if(e===document.scrollingElement){const{innerWidth:s,innerHeight:a}=window;return{top:0,left:0,right:s,bottom:a,width:s,height:a}}const{top:t,left:n,right:o,bottom:r}=e.getBoundingClientRect();return{top:t,left:n,right:o,bottom:r,width:e.clientWidth,height:e.clientHeight}}function Xp(e){return e.reduce((t,n)=>uo(t,ua(n)),Ht)}function i2(e){return e.reduce((t,n)=>t+Up(n),0)}function s2(e){return e.reduce((t,n)=>t+qp(n),0)}function Jp(e,t){if(t===void 0&&(t=Ho),!e)return;const{top:n,left:o,bottom:r,right:s}=t(e);Vp(e)&&(r<=0||s<=0||n>=window.innerHeight||o>=window.innerWidth)&&e.scrollIntoView({block:"center",inline:"center"})}const a2=[["x",["left","right"],i2],["y",["top","bottom"],s2]];class pa{constructor(t,n){this.rect=void 0,this.width=void 0,this.height=void 0,this.top=void 0,this.bottom=void 0,this.right=void 0,this.left=void 0;const o=da(n),r=Xp(o);this.rect={...t},this.width=t.width,this.height=t.height;for(const[s,a,l]of a2)for(const d of a)Object.defineProperty(this,d,{get:()=>{const c=l(o),p=r[s]-c;return this.rect[d]+p},enumerable:!0});Object.defineProperty(this,"rect",{enumerable:!1})}}class Fo{constructor(t){this.target=void 0,this.listeners=[],this.removeAll=()=>{this.listeners.forEach(n=>{var o;return(o=this.target)==null?void 0:o.removeEventListener(...n)})},this.target=t}add(t,n,o){var r;(r=this.target)==null||r.addEventListener(t,n,o),this.listeners.push([t,n,o])}}function l2(e){const{EventTarget:t}=_t(e);return e instanceof t?e:co(e)}function fa(e,t){const n=Math.abs(e.x),o=Math.abs(e.y);return typeof t=="number"?Math.sqrt(n**2+o**2)>t:"x"in t&&"y"in t?n>t.x&&o>t.y:"x"in t?n>t.x:"y"in t?o>t.y:!1}var At;(function(e){e.Click="click",e.DragStart="dragstart",e.Keydown="keydown",e.ContextMenu="contextmenu",e.Resize="resize",e.SelectionChange="selectionchange",e.VisibilityChange="visibilitychange"})(At||(At={}));function Zp(e){e.preventDefault()}function d2(e){e.stopPropagation()}var Ke;(function(e){e.Space="Space",e.Down="ArrowDown",e.Right="ArrowRight",e.Left="ArrowLeft",e.Up="ArrowUp",e.Esc="Escape",e.Enter="Enter",e.Tab="Tab"})(Ke||(Ke={}));const Qp={start:[Ke.Space,Ke.Enter],cancel:[Ke.Esc],end:[Ke.Space,Ke.Enter,Ke.Tab]},c2=(e,t)=>{let{currentCoordinates:n}=t;switch(e.code){case Ke.Right:return{...n,x:n.x+25};case Ke.Left:return{...n,x:n.x-25};case Ke.Down:return{...n,y:n.y+25};case Ke.Up:return{...n,y:n.y-25}}};class Rp{constructor(t){this.props=void 0,this.autoScrollEnabled=!1,this.referenceCoordinates=void 0,this.listeners=void 0,this.windowListeners=void 0,this.props=t;const{event:{target:n}}=t;this.props=t,this.listeners=new Fo(co(n)),this.windowListeners=new Fo(_t(n)),this.handleKeyDown=this.handleKeyDown.bind(this),this.handleCancel=this.handleCancel.bind(this),this.attach()}attach(){this.handleStart(),this.windowListeners.add(At.Resize,this.handleCancel),this.windowListeners.add(At.VisibilityChange,this.handleCancel),setTimeout(()=>this.listeners.add(At.Keydown,this.handleKeyDown))}handleStart(){const{activeNode:t,onStart:n}=this.props,o=t.node.current;o&&Jp(o),n(Ht)}handleKeyDown(t){if(la(t)){const{active:n,context:o,options:r}=this.props,{keyboardCodes:s=Qp,coordinateGetter:a=c2,scrollBehavior:l="smooth"}=r,{code:d}=t;if(s.end.includes(d)){this.handleEnd(t);return}if(s.cancel.includes(d)){this.handleCancel(t);return}const{collisionRect:c}=o.current,p=c?{x:c.left,y:c.top}:Ht;this.referenceCoordinates||(this.referenceCoordinates=p);const f=a(t,{active:n,context:o.current,currentCoordinates:p});if(f){const y=ti(f,p),g={x:0,y:0},{scrollableAncestors:m}=o.current;for(const h of m){const v=t.code,{isTop:b,isRight:x,isLeft:k,isBottom:w,maxScroll:_,minScroll:S}=Yp(h),E=r2(h),C={x:Math.min(v===Ke.Right?E.right-E.width/2:E.right,Math.max(v===Ke.Right?E.left:E.left+E.width/2,f.x)),y:Math.min(v===Ke.Down?E.bottom-E.height/2:E.bottom,Math.max(v===Ke.Down?E.top:E.top+E.height/2,f.y))},N=v===Ke.Right&&!x||v===Ke.Left&&!k,$=v===Ke.Down&&!w||v===Ke.Up&&!b;if(N&&C.x!==f.x){const A=h.scrollLeft+y.x,z=v===Ke.Right&&A<=_.x||v===Ke.Left&&A>=S.x;if(z&&!y.y){h.scrollTo({left:A,behavior:l});return}z?g.x=h.scrollLeft-A:g.x=v===Ke.Right?h.scrollLeft-_.x:h.scrollLeft-S.x,g.x&&h.scrollBy({left:-g.x,behavior:l});break}else if($&&C.y!==f.y){const A=h.scrollTop+y.y,z=v===Ke.Down&&A<=_.y||v===Ke.Up&&A>=S.y;if(z&&!y.x){h.scrollTo({top:A,behavior:l});return}z?g.y=h.scrollTop-A:g.y=v===Ke.Down?h.scrollTop-_.y:h.scrollTop-S.y,g.y&&h.scrollBy({top:-g.y,behavior:l});break}}this.handleMove(t,uo(ti(f,this.referenceCoordinates),g))}}}handleMove(t,n){const{onMove:o}=this.props;t.preventDefault(),o(n)}handleEnd(t){const{onEnd:n}=this.props;t.preventDefault(),this.detach(),n()}handleCancel(t){const{onCancel:n}=this.props;t.preventDefault(),this.detach(),n()}detach(){this.listeners.removeAll(),this.windowListeners.removeAll()}}Rp.activators=[{eventName:"onKeyDown",handler:(e,t,n)=>{let{keyboardCodes:o=Qp,onActivation:r}=t,{active:s}=n;const{code:a}=e.nativeEvent;if(o.start.includes(a)){const l=s.activatorNode.current;return l&&e.target!==l?!1:(e.preventDefault(),r?.({event:e.nativeEvent}),!0)}return!1}}];function ef(e){return!!(e&&"distance"in e)}function tf(e){return!!(e&&"delay"in e)}class ga{constructor(t,n,o){var r;o===void 0&&(o=l2(t.event.target)),this.props=void 0,this.events=void 0,this.autoScrollEnabled=!0,this.document=void 0,this.activated=!1,this.initialCoordinates=void 0,this.timeoutId=null,this.listeners=void 0,this.documentListeners=void 0,this.windowListeners=void 0,this.props=t,this.events=n;const{event:s}=t,{target:a}=s;this.props=t,this.events=n,this.document=co(a),this.documentListeners=new Fo(this.document),this.listeners=new Fo(o),this.windowListeners=new Fo(_t(a)),this.initialCoordinates=(r=ni(s))!=null?r:Ht,this.handleStart=this.handleStart.bind(this),this.handleMove=this.handleMove.bind(this),this.handleEnd=this.handleEnd.bind(this),this.handleCancel=this.handleCancel.bind(this),this.handleKeydown=this.handleKeydown.bind(this),this.removeTextSelection=this.removeTextSelection.bind(this),this.attach()}attach(){const{events:t,props:{options:{activationConstraint:n,bypassActivationConstraint:o}}}=this;if(this.listeners.add(t.move.name,this.handleMove,{passive:!1}),this.listeners.add(t.end.name,this.handleEnd),t.cancel&&this.listeners.add(t.cancel.name,this.handleCancel),this.windowListeners.add(At.Resize,this.handleCancel),this.windowListeners.add(At.DragStart,Zp),this.windowListeners.add(At.VisibilityChange,this.handleCancel),this.windowListeners.add(At.ContextMenu,Zp),this.documentListeners.add(At.Keydown,this.handleKeydown),n){if(o!=null&&o({event:this.props.event,activeNode:this.props.activeNode,options:this.props.options}))return this.handleStart();if(tf(n)){this.timeoutId=setTimeout(this.handleStart,n.delay),this.handlePending(n);return}if(ef(n)){this.handlePending(n);return}}this.handleStart()}detach(){this.listeners.removeAll(),this.windowListeners.removeAll(),setTimeout(this.documentListeners.removeAll,50),this.timeoutId!==null&&(clearTimeout(this.timeoutId),this.timeoutId=null)}handlePending(t,n){const{active:o,onPending:r}=this.props;r(o,t,this.initialCoordinates,n)}handleStart(){const{initialCoordinates:t}=this,{onStart:n}=this.props;t&&(this.activated=!0,this.documentListeners.add(At.Click,d2,{capture:!0}),this.removeTextSelection(),this.documentListeners.add(At.SelectionChange,this.removeTextSelection),n(t))}handleMove(t){var n;const{activated:o,initialCoordinates:r,props:s}=this,{onMove:a,options:{activationConstraint:l}}=s;if(!r)return;const d=(n=ni(t))!=null?n:Ht,c=ti(r,d);if(!o&&l){if(ef(l)){if(l.tolerance!=null&&fa(c,l.tolerance))return this.handleCancel();if(fa(c,l.distance))return this.handleStart()}if(tf(l)&&fa(c,l.tolerance))return this.handleCancel();this.handlePending(l,c);return}t.cancelable&&t.preventDefault(),a(d)}handleEnd(){const{onAbort:t,onEnd:n}=this.props;this.detach(),this.activated||t(this.props.active),n()}handleCancel(){const{onAbort:t,onCancel:n}=this.props;this.detach(),this.activated||t(this.props.active),n()}handleKeydown(t){t.code===Ke.Esc&&this.handleCancel()}removeTextSelection(){var t;(t=this.document.getSelection())==null||t.removeAllRanges()}}const u2={cancel:{name:"pointercancel"},move:{name:"pointermove"},end:{name:"pointerup"}};class ha extends ga{constructor(t){const{event:n}=t,o=co(n.target);super(t,u2,o)}}ha.activators=[{eventName:"onPointerDown",handler:(e,t)=>{let{nativeEvent:n}=e,{onActivation:o}=t;return!n.isPrimary||n.button!==0?!1:(o?.({event:n}),!0)}}];const p2={move:{name:"mousemove"},end:{name:"mouseup"}};var ya;(function(e){e[e.RightClick=2]="RightClick"})(ya||(ya={}));class f2 extends ga{constructor(t){super(t,p2,co(t.event.target))}}f2.activators=[{eventName:"onMouseDown",handler:(e,t)=>{let{nativeEvent:n}=e,{onActivation:o}=t;return n.button===ya.RightClick?!1:(o?.({event:n}),!0)}}];const ba={cancel:{name:"touchcancel"},move:{name:"touchmove"},end:{name:"touchend"}};class g2 extends ga{constructor(t){super(t,ba)}static setup(){return window.addEventListener(ba.move.name,t,{capture:!1,passive:!1}),function(){window.removeEventListener(ba.move.name,t)};function t(){}}}g2.activators=[{eventName:"onTouchStart",handler:(e,t)=>{let{nativeEvent:n}=e,{onActivation:o}=t;const{touches:r}=n;return r.length>1?!1:(o?.({event:n}),!0)}}];var Ko;(function(e){e[e.Pointer=0]="Pointer",e[e.DraggableRect=1]="DraggableRect"})(Ko||(Ko={}));var ri;(function(e){e[e.TreeOrder=0]="TreeOrder",e[e.ReversedTreeOrder=1]="ReversedTreeOrder"})(ri||(ri={}));function h2(e){let{acceleration:t,activator:n=Ko.Pointer,canScroll:o,draggingRect:r,enabled:s,interval:a=5,order:l=ri.TreeOrder,pointerCoordinates:d,scrollableAncestors:c,scrollableAncestorRects:p,delta:f,threshold:y}=e;const g=b2({delta:f,disabled:!s}),[m,h]=k7(),v=u.useRef({x:0,y:0}),b=u.useRef({x:0,y:0}),x=u.useMemo(()=>{switch(n){case Ko.Pointer:return d?{top:d.y,bottom:d.y,left:d.x,right:d.x}:null;case Ko.DraggableRect:return r}},[n,r,d]),k=u.useRef(null),w=u.useCallback(()=>{const S=k.current;if(!S)return;const E=v.current.x*b.current.x,C=v.current.y*b.current.y;S.scrollBy(E,C)},[]),_=u.useMemo(()=>l===ri.TreeOrder?[...c].reverse():c,[l,c]);u.useEffect(()=>{if(!s||!c.length||!x){h();return}for(const S of _){if(o?.(S)===!1)continue;const E=c.indexOf(S),C=p[E];if(!C)continue;const{direction:N,speed:$}=o2(S,C,x,t,y);for(const A of["x","y"])g[A][N[A]]||($[A]=0,N[A]=0);if($.x>0||$.y>0){h(),k.current=S,m(w,a),v.current=$,b.current=N;return}}v.current={x:0,y:0},b.current={x:0,y:0},h()},[t,w,o,h,s,a,JSON.stringify(x),JSON.stringify(g),m,c,_,p,JSON.stringify(y)])}const y2={x:{[lt.Backward]:!1,[lt.Forward]:!1},y:{[lt.Backward]:!1,[lt.Forward]:!1}};function b2(e){let{delta:t,disabled:n}=e;const o=Rr(t);return Wo(r=>{if(n||!o||!r)return y2;const s={x:Math.sign(t.x-o.x),y:Math.sign(t.y-o.y)};return{x:{[lt.Backward]:r.x[lt.Backward]||s.x===-1,[lt.Forward]:r.x[lt.Forward]||s.x===1},y:{[lt.Backward]:r.y[lt.Backward]||s.y===-1,[lt.Forward]:r.y[lt.Forward]||s.y===1}}},[n,t,o])}function m2(e,t){const n=t!=null?e.get(t):void 0,o=n?n.node.current:null;return Wo(r=>{var s;return t==null?null:(s=o??r)!=null?s:null},[o,t])}function v2(e,t){return u.useMemo(()=>e.reduce((n,o)=>{const{sensor:r}=o,s=r.activators.map(a=>({eventName:a.eventName,handler:t(a.handler,o)}));return[...n,...s]},[]),[e,t])}var Vo;(function(e){e[e.Always=0]="Always",e[e.BeforeDragging=1]="BeforeDragging",e[e.WhileDragging=2]="WhileDragging"})(Vo||(Vo={}));var ma;(function(e){e.Optimized="optimized"})(ma||(ma={}));const nf=new Map;function _2(e,t){let{dragging:n,dependencies:o,config:r}=t;const[s,a]=u.useState(null),{frequency:l,measure:d,strategy:c}=r,p=u.useRef(e),f=v(),y=Bo(f),g=u.useCallback(function(b){b===void 0&&(b=[]),!y.current&&a(x=>x===null?b:x.concat(b.filter(k=>!x.includes(k))))},[y]),m=u.useRef(null),h=Wo(b=>{if(f&&!n)return nf;if(!b||b===nf||p.current!==e||s!=null){const x=new Map;for(let k of e){if(!k)continue;if(s&&s.length>0&&!s.includes(k.id)&&k.rect.current){x.set(k.id,k.rect.current);continue}const w=k.node.current,_=w?new pa(d(w),w):null;k.rect.current=_,_&&x.set(k.id,_)}return x}return b},[e,s,n,f,d]);return u.useEffect(()=>{p.current=e},[e]),u.useEffect(()=>{f||g()},[n,f]),u.useEffect(()=>{s&&s.length>0&&a(null)},[JSON.stringify(s)]),u.useEffect(()=>{f||typeof l!="number"||m.current!==null||(m.current=setTimeout(()=>{g(),m.current=null},l))},[l,f,g,...o]),{droppableRects:h,measureDroppableContainers:g,measuringScheduled:s!=null};function v(){switch(c){case Vo.Always:return!1;case Vo.BeforeDragging:return n;default:return!n}}}function va(e,t){return Wo(n=>e?n||(typeof t=="function"?t(e):e):null,[t,e])}function x2(e,t){return va(e,t)}function w2(e){let{callback:t,disabled:n}=e;const o=Zr(t),r=u.useMemo(()=>{if(n||typeof window>"u"||typeof window.MutationObserver>"u")return;const{MutationObserver:s}=window;return new s(o)},[o,n]);return u.useEffect(()=>()=>r?.disconnect(),[r]),r}function ii(e){let{callback:t,disabled:n}=e;const o=Zr(t),r=u.useMemo(()=>{if(n||typeof window>"u"||typeof window.ResizeObserver>"u")return;const{ResizeObserver:s}=window;return new s(o)},[n]);return u.useEffect(()=>()=>r?.disconnect(),[r]),r}function k2(e){return new pa(Ho(e),e)}function of(e,t,n){t===void 0&&(t=k2);const[o,r]=u.useState(null);function s(){r(d=>{if(!e)return null;if(e.isConnected===!1){var c;return(c=d??n)!=null?c:null}const p=t(e);return JSON.stringify(d)===JSON.stringify(p)?d:p})}const a=w2({callback(d){if(e)for(const c of d){const{type:p,target:f}=c;if(p==="childList"&&f instanceof HTMLElement&&f.contains(e)){s();break}}}}),l=ii({callback:s});return sn(()=>{s(),e?(l?.observe(e),a?.observe(document.body,{childList:!0,subtree:!0})):(l?.disconnect(),a?.disconnect())},[e]),o}function S2(e){const t=va(e);return Hp(e,t)}const rf=[];function C2(e){const t=u.useRef(e),n=Wo(o=>e?o&&o!==rf&&e&&t.current&&e.parentNode===t.current.parentNode?o:da(e):rf,[e]);return u.useEffect(()=>{t.current=e},[e]),n}function $2(e){const[t,n]=u.useState(null),o=u.useRef(e),r=u.useCallback(s=>{const a=ca(s.target);a&&n(l=>l?(l.set(a,ua(a)),new Map(l)):null)},[]);return u.useEffect(()=>{const s=o.current;if(e!==s){a(s);const l=e.map(d=>{const c=ca(d);return c?(c.addEventListener("scroll",r,{passive:!0}),[c,ua(c)]):null}).filter(d=>d!=null);n(l.length?new Map(l):null),o.current=e}return()=>{a(e),a(s)};function a(l){l.forEach(d=>{const c=ca(d);c?.removeEventListener("scroll",r)})}},[r,e]),u.useMemo(()=>e.length?t?Array.from(t.values()).reduce((s,a)=>uo(s,a),Ht):Xp(e):Ht,[e,t])}function sf(e,t){t===void 0&&(t=[]);const n=u.useRef(null);return u.useEffect(()=>{n.current=null},t),u.useEffect(()=>{const o=e!==Ht;o&&!n.current&&(n.current=e),!o&&n.current&&(n.current=null)},[e]),n.current?ti(e,n.current):Ht}function E2(e){u.useEffect(()=>{if(!Jr)return;const t=e.map(n=>{let{sensor:o}=n;return o.setup==null?void 0:o.setup()});return()=>{for(const n of t)n?.()}},e.map(t=>{let{sensor:n}=t;return n}))}function I2(e,t){return u.useMemo(()=>e.reduce((n,o)=>{let{eventName:r,handler:s}=o;return n[r]=a=>{s(a,t)},n},{}),[e,t])}function af(e){return u.useMemo(()=>e?R7(e):null,[e])}const lf=[];function A2(e,t){t===void 0&&(t=Ho);const[n]=e,o=af(n?_t(n):null),[r,s]=u.useState(lf);function a(){s(()=>e.length?e.map(d=>Gp(d)?o:new pa(t(d),d)):lf)}const l=ii({callback:a});return sn(()=>{l?.disconnect(),a(),e.forEach(d=>l?.observe(d))},[e]),r}function df(e){if(!e)return null;if(e.children.length>1)return e;const t=e.children[0];return Oo(t)?t:e}function T2(e){let{measure:t}=e;const[n,o]=u.useState(null),r=u.useCallback(c=>{for(const{target:p}of c)if(Oo(p)){o(f=>{const y=t(p);return f?{...f,width:y.width,height:y.height}:y});break}},[t]),s=ii({callback:r}),a=u.useCallback(c=>{const p=df(c);s?.disconnect(),p&&s?.observe(p),o(p?t(p):null)},[t,s]),[l,d]=Qr(a);return u.useMemo(()=>({nodeRef:l,rect:n,setRef:d}),[n,l,d])}const D2=[{sensor:ha,options:{}},{sensor:Rp,options:{}}],N2={current:{}},si={draggable:{measure:Kp},droppable:{measure:Kp,strategy:Vo.WhileDragging,frequency:ma.Optimized},dragOverlay:{measure:Ho}};class Uo extends Map{get(t){var n;return t!=null&&(n=super.get(t))!=null?n:void 0}toArray(){return Array.from(this.values())}getEnabled(){return this.toArray().filter(t=>{let{disabled:n}=t;return!n})}getNodeFor(t){var n,o;return(n=(o=this.get(t))==null?void 0:o.node.current)!=null?n:void 0}}const z2={activatorEvent:null,active:null,activeNode:null,activeNodeRect:null,collisions:null,containerNodeRect:null,draggableNodes:new Map,droppableRects:new Map,droppableContainers:new Uo,over:null,dragOverlay:{nodeRef:{current:null},rect:null,setRef:oi},scrollableAncestors:[],scrollableAncestorRects:[],measuringConfiguration:si,measureDroppableContainers:oi,windowRect:null,measuringScheduled:!1},cf={activatorEvent:null,activators:[],active:null,activeNodeRect:null,ariaDescribedById:{draggable:""},dispatch:oi,draggableNodes:new Map,over:null,measureDroppableContainers:oi},qo=u.createContext(cf),uf=u.createContext(z2);function M2(){return{draggable:{active:null,initialCoordinates:{x:0,y:0},nodes:new Map,translate:{x:0,y:0}},droppable:{containers:new Uo}}}function L2(e,t){switch(t.type){case it.DragStart:return{...e,draggable:{...e.draggable,initialCoordinates:t.initialCoordinates,active:t.active}};case it.DragMove:return e.draggable.active==null?e:{...e,draggable:{...e.draggable,translate:{x:t.coordinates.x-e.draggable.initialCoordinates.x,y:t.coordinates.y-e.draggable.initialCoordinates.y}}};case it.DragEnd:case it.DragCancel:return{...e,draggable:{...e.draggable,active:null,initialCoordinates:{x:0,y:0},translate:{x:0,y:0}}};case it.RegisterDroppable:{const{element:n}=t,{id:o}=n,r=new Uo(e.droppable.containers);return r.set(o,n),{...e,droppable:{...e.droppable,containers:r}}}case it.SetDroppableDisabled:{const{id:n,key:o,disabled:r}=t,s=e.droppable.containers.get(n);if(!s||o!==s.key)return e;const a=new Uo(e.droppable.containers);return a.set(n,{...s,disabled:r}),{...e,droppable:{...e.droppable,containers:a}}}case it.UnregisterDroppable:{const{id:n,key:o}=t,r=e.droppable.containers.get(n);if(!r||o!==r.key)return e;const s=new Uo(e.droppable.containers);return s.delete(n),{...e,droppable:{...e.droppable,containers:s}}}default:return e}}function P2(e){let{disabled:t}=e;const{active:n,activatorEvent:o,draggableNodes:r}=u.useContext(qo),s=Rr(o),a=Rr(n?.id);return u.useEffect(()=>{if(!t&&!o&&s&&a!=null){if(!la(s)||document.activeElement===s.target)return;const l=r.get(a);if(!l)return;const{activatorNode:d,node:c}=l;if(!d.current&&!c.current)return;requestAnimationFrame(()=>{for(const p of[d.current,c.current]){if(!p)continue;const f=$7(p);if(f){f.focus();break}}})}},[o,t,r,a,s]),null}function pf(e,t){let{transform:n,...o}=t;return e!=null&&e.length?e.reduce((r,s)=>s({transform:r,...o}),n):n}function O2(e){return u.useMemo(()=>({draggable:{...si.draggable,...e?.draggable},droppable:{...si.droppable,...e?.droppable},dragOverlay:{...si.dragOverlay,...e?.dragOverlay}}),[e?.draggable,e?.droppable,e?.dragOverlay])}function B2(e){let{activeNode:t,measure:n,initialRect:o,config:r=!0}=e;const s=u.useRef(!1),{x:a,y:l}=typeof r=="boolean"?{x:r,y:r}:r;sn(()=>{if(!a&&!l||!t){s.current=!1;return}if(s.current||!o)return;const c=t?.node.current;if(!c||c.isConnected===!1)return;const p=n(c),f=Hp(p,o);if(a||(f.x=0),l||(f.y=0),s.current=!0,Math.abs(f.x)>0||Math.abs(f.y)>0){const y=Vp(c);y&&y.scrollBy({top:f.y,left:f.x})}},[t,a,l,o,n])}const ai=u.createContext({...Ht,scaleX:1,scaleY:1});var hn;(function(e){e[e.Uninitialized=0]="Uninitialized",e[e.Initializing=1]="Initializing",e[e.Initialized=2]="Initialized"})(hn||(hn={}));const W2=u.memo(function(t){var n,o,r,s;let{id:a,accessibility:l,autoScroll:d=!0,children:c,sensors:p=D2,collisionDetection:f=U7,measuring:y,modifiers:g,...m}=t;const h=u.useReducer(L2,void 0,M2),[v,b]=h,[x,k]=N7(),[w,_]=u.useState(hn.Uninitialized),S=w===hn.Initialized,{draggable:{active:E,nodes:C,translate:N},droppable:{containers:$}}=v,A=E!=null?C.get(E):null,z=u.useRef({initial:null,translated:null}),D=u.useMemo(()=>{var J;return E!=null?{id:E,data:(J=A?.data)!=null?J:N2,rect:z}:null},[E,A]),O=u.useRef(null),[M,B]=u.useState(null),[T,H]=u.useState(null),L=Bo(m,Object.values(m)),I=ei("DndDescribedBy",a),P=u.useMemo(()=>$.getEnabled(),[$]),F=O2(y),{droppableRects:G,measureDroppableContainers:V,measuringScheduled:Y}=_2(P,{dragging:S,dependencies:[N.x,N.y],config:F.droppable}),Z=m2(C,E),se=u.useMemo(()=>T?ni(T):null,[T]),re=ue(),U=x2(Z,F.draggable.measure);B2({activeNode:E!=null?C.get(E):null,config:re.layoutShiftCompensation,initialRect:U,measure:F.draggable.measure});const ee=of(Z,F.draggable.measure,U),fe=of(Z?Z.parentElement:null),K=u.useRef({activatorEvent:null,active:null,activeNode:Z,collisionRect:null,collisions:null,droppableRects:G,draggableNodes:C,draggingNode:null,draggingNodeRect:null,droppableContainers:$,over:null,scrollableAncestors:[],scrollAdjustedTranslate:null}),ne=$.getNodeFor((n=K.current.over)==null?void 0:n.id),me=T2({measure:F.dragOverlay.measure}),Ce=(o=me.nodeRef.current)!=null?o:Z,ze=S?(r=me.rect)!=null?r:ee:null,Se=!!(me.nodeRef.current&&me.rect),qe=S2(Se?null:ee),De=af(Ce?_t(Ce):null),Pe=C2(S?ne??Z:null),Qe=A2(Pe),Me=pf(g,{transform:{x:N.x-qe.x,y:N.y-qe.y,scaleX:1,scaleY:1},activatorEvent:T,active:D,activeNodeRect:ee,containerNodeRect:fe,draggingNodeRect:ze,over:K.current.over,overlayNodeRect:me.rect,scrollableAncestors:Pe,scrollableAncestorRects:Qe,windowRect:De}),Re=se?uo(se,N):null,ve=$2(Pe),te=sf(ve),he=sf(ve,[ee]),Q=uo(Me,te),ie=ze?J7(ze,Me):null,_e=D&&ie?f({active:D,collisionRect:ie,droppableRects:G,droppableContainers:P,pointerCoordinates:Re}):null,ge=K7(_e,"id"),[Ae,Ne]=u.useState(null),Le=Se?Me:uo(Me,he),q=Y7(Le,(s=Ae?.rect)!=null?s:null,ee),ae=u.useRef(null),pe=u.useCallback((J,le)=>{let{sensor:Ie,options:dt}=le;if(O.current==null)return;const pt=C.get(O.current);if(!pt)return;const ot=J.nativeEvent,Oe=new Ie({active:O.current,activeNode:pt,event:ot,options:dt,context:K,onAbort(Ye){if(!C.get(Ye))return;const{onDragAbort:Dt}=L.current,Nt={id:Ye};Dt?.(Nt),x({type:"onDragAbort",event:Nt})},onPending(Ye,Tt,Dt,Nt){if(!C.get(Ye))return;const{onDragPending:wn}=L.current,Zt={id:Ye,constraint:Tt,initialCoordinates:Dt,offset:Nt};wn?.(Zt),x({type:"onDragPending",event:Zt})},onStart(Ye){const Tt=O.current;if(Tt==null)return;const Dt=C.get(Tt);if(!Dt)return;const{onDragStart:Nt}=L.current,xn={activatorEvent:ot,active:{id:Tt,data:Dt.data,rect:z}};Cn.unstable_batchedUpdates(()=>{Nt?.(xn),_(hn.Initializing),b({type:it.DragStart,initialCoordinates:Ye,active:Tt}),x({type:"onDragStart",event:xn}),B(ae.current),H(ot)})},onMove(Ye){b({type:it.DragMove,coordinates:Ye})},onEnd:He(it.DragEnd),onCancel:He(it.DragCancel)});ae.current=Oe;function He(Ye){return async function(){const{active:Dt,collisions:Nt,over:xn,scrollAdjustedTranslate:wn}=K.current;let Zt=null;if(Dt&&wn){const{cancelDrop:Hn}=L.current;Zt={activatorEvent:ot,active:Dt,collisions:Nt,delta:wn,over:xn},Ye===it.DragEnd&&typeof Hn=="function"&&await Promise.resolve(Hn(Zt))&&(Ye=it.DragCancel)}O.current=null,Cn.unstable_batchedUpdates(()=>{b({type:Ye}),_(hn.Uninitialized),Ne(null),B(null),H(null),ae.current=null;const Hn=Ye===it.DragEnd?"onDragEnd":"onDragCancel";if(Zt){const ln=L.current[Hn];ln?.(Zt),x({type:Hn,event:Zt})}})}}},[C]),$e=u.useCallback((J,le)=>(Ie,dt)=>{const pt=Ie.nativeEvent,ot=C.get(dt);if(O.current!==null||!ot||pt.dndKit||pt.defaultPrevented)return;const Oe={active:ot};J(Ie,le.options,Oe)===!0&&(pt.dndKit={capturedBy:le.sensor},O.current=dt,pe(Ie,le))},[C,pe]),be=v2(p,$e);E2(p),sn(()=>{ee&&w===hn.Initializing&&_(hn.Initialized)},[ee,w]),u.useEffect(()=>{const{onDragMove:J}=L.current,{active:le,activatorEvent:Ie,collisions:dt,over:pt}=K.current;if(!le||!Ie)return;const ot={active:le,activatorEvent:Ie,collisions:dt,delta:{x:Q.x,y:Q.y},over:pt};Cn.unstable_batchedUpdates(()=>{J?.(ot),x({type:"onDragMove",event:ot})})},[Q.x,Q.y]),u.useEffect(()=>{const{active:J,activatorEvent:le,collisions:Ie,droppableContainers:dt,scrollAdjustedTranslate:pt}=K.current;if(!J||O.current==null||!le||!pt)return;const{onDragOver:ot}=L.current,Oe=dt.get(ge),He=Oe&&Oe.rect.current?{id:Oe.id,rect:Oe.rect.current,data:Oe.data,disabled:Oe.disabled}:null,Ye={active:J,activatorEvent:le,collisions:Ie,delta:{x:pt.x,y:pt.y},over:He};Cn.unstable_batchedUpdates(()=>{Ne(He),ot?.(Ye),x({type:"onDragOver",event:Ye})})},[ge]),sn(()=>{K.current={activatorEvent:T,active:D,activeNode:Z,collisionRect:ie,collisions:_e,droppableRects:G,draggableNodes:C,draggingNode:Ce,draggingNodeRect:ze,droppableContainers:$,over:Ae,scrollableAncestors:Pe,scrollAdjustedTranslate:Q},z.current={initial:ze,translated:ie}},[D,Z,_e,ie,C,Ce,ze,G,$,Ae,Pe,Q]),h2({...re,delta:N,draggingRect:ie,pointerCoordinates:Re,scrollableAncestors:Pe,scrollableAncestorRects:Qe});const j=u.useMemo(()=>({active:D,activeNode:Z,activeNodeRect:ee,activatorEvent:T,collisions:_e,containerNodeRect:fe,dragOverlay:me,draggableNodes:C,droppableContainers:$,droppableRects:G,over:Ae,measureDroppableContainers:V,scrollableAncestors:Pe,scrollableAncestorRects:Qe,measuringConfiguration:F,measuringScheduled:Y,windowRect:De}),[D,Z,ee,T,_e,fe,me,C,$,G,Ae,V,Pe,Qe,F,Y,De]),oe=u.useMemo(()=>({activatorEvent:T,activators:be,active:D,activeNodeRect:ee,ariaDescribedById:{draggable:I},dispatch:b,draggableNodes:C,over:Ae,measureDroppableContainers:V}),[T,be,D,ee,b,I,C,Ae,V]);return u.createElement(jp.Provider,{value:k},u.createElement(qo.Provider,{value:oe},u.createElement(uf.Provider,{value:j},u.createElement(ai.Provider,{value:q},c)),u.createElement(P2,{disabled:l?.restoreFocus===!1})),u.createElement(L7,{...l,hiddenTextDescribedById:I}));function ue(){const J=M?.autoScrollEnabled===!1,le=typeof d=="object"?d.enabled===!1:d===!1,Ie=S&&!J&&!le;return typeof d=="object"?{...d,enabled:Ie}:{enabled:Ie}}}),j2=u.createContext(null),ff="button",H2="Draggable";function F2(e){let{id:t,data:n,disabled:o=!1,attributes:r}=e;const s=ei(H2),{activators:a,activatorEvent:l,active:d,activeNodeRect:c,ariaDescribedById:p,draggableNodes:f,over:y}=u.useContext(qo),{role:g=ff,roleDescription:m="draggable",tabIndex:h=0}=r??{},v=d?.id===t,b=u.useContext(v?ai:j2),[x,k]=Qr(),[w,_]=Qr(),S=I2(a,t),E=Bo(n);sn(()=>(f.set(t,{id:t,key:s,node:x,activatorNode:w,data:E}),()=>{const N=f.get(t);N&&N.key===s&&f.delete(t)}),[f,t]);const C=u.useMemo(()=>({role:g,tabIndex:h,"aria-disabled":o,"aria-pressed":v&&g===ff?!0:void 0,"aria-roledescription":m,"aria-describedby":p.draggable}),[o,g,h,v,m,p.draggable]);return{active:d,activatorEvent:l,activeNodeRect:c,attributes:C,isDragging:v,listeners:o?void 0:S,node:x,over:y,setNodeRef:k,setActivatorNodeRef:_,transform:b}}function K2(){return u.useContext(uf)}const V2="Droppable",U2={timeout:25};function q2(e){let{data:t,disabled:n=!1,id:o,resizeObserverConfig:r}=e;const s=ei(V2),{active:a,dispatch:l,over:d,measureDroppableContainers:c}=u.useContext(qo),p=u.useRef({disabled:n}),f=u.useRef(!1),y=u.useRef(null),g=u.useRef(null),{disabled:m,updateMeasurementsFor:h,timeout:v}={...U2,...r},b=Bo(h??o),x=u.useCallback(()=>{if(!f.current){f.current=!0;return}g.current!=null&&clearTimeout(g.current),g.current=setTimeout(()=>{c(Array.isArray(b.current)?b.current:[b.current]),g.current=null},v)},[v]),k=ii({callback:x,disabled:m||!a}),w=u.useCallback((C,N)=>{k&&(N&&(k.unobserve(N),f.current=!1),C&&k.observe(C))},[k]),[_,S]=Qr(w),E=Bo(t);return u.useEffect(()=>{!k||!_.current||(k.disconnect(),f.current=!1,k.observe(_.current))},[_,k]),u.useEffect(()=>(l({type:it.RegisterDroppable,element:{id:o,key:s,disabled:n,node:_,rect:y,data:E}}),()=>l({type:it.UnregisterDroppable,key:s,id:o})),[o]),u.useEffect(()=>{n!==p.current.disabled&&(l({type:it.SetDroppableDisabled,id:o,key:s,disabled:n}),p.current.disabled=n)},[o,s,n,l]),{active:a,rect:y,isOver:d?.id===o,node:_,over:d,setNodeRef:S}}function G2(e){let{animation:t,children:n}=e;const[o,r]=u.useState(null),[s,a]=u.useState(null),l=Rr(n);return!n&&!o&&l&&r(l),sn(()=>{if(!s)return;const d=o?.key,c=o?.props.id;if(d==null||c==null){r(null);return}Promise.resolve(t(c,s)).then(()=>{r(null)})},[t,o,s]),u.createElement(u.Fragment,null,n,o?u.cloneElement(o,{ref:a}):null)}const Y2={x:0,y:0,scaleX:1,scaleY:1};function X2(e){let{children:t}=e;return u.createElement(qo.Provider,{value:cf},u.createElement(ai.Provider,{value:Y2},t))}const J2={position:"fixed",touchAction:"none"},Z2=e=>la(e)?"transform 250ms ease":void 0,Q2=u.forwardRef((e,t)=>{let{as:n,activatorEvent:o,adjustScale:r,children:s,className:a,rect:l,style:d,transform:c,transition:p=Z2}=e;if(!l)return null;const f=r?c:{...c,scaleX:1,scaleY:1},y={...J2,width:l.width,height:l.height,top:l.top,left:l.left,transform:jo.Transform.toString(f),transformOrigin:r&&o?W7(o,l):void 0,transition:typeof p=="function"?p(o):p,...d};return u.createElement(n,{className:a,style:y,ref:t},s)}),R2={duration:250,easing:"ease",keyframes:e=>{let{transform:{initial:t,final:n}}=e;return[{transform:jo.Transform.toString(t)},{transform:jo.Transform.toString(n)}]},sideEffects:(e=>t=>{let{active:n,dragOverlay:o}=t;const r={},{styles:s,className:a}=e;if(s!=null&&s.active)for(const[l,d]of Object.entries(s.active))d!==void 0&&(r[l]=n.node.style.getPropertyValue(l),n.node.style.setProperty(l,d));if(s!=null&&s.dragOverlay)for(const[l,d]of Object.entries(s.dragOverlay))d!==void 0&&o.node.style.setProperty(l,d);return a!=null&&a.active&&n.node.classList.add(a.active),a!=null&&a.dragOverlay&&o.node.classList.add(a.dragOverlay),function(){for(const[d,c]of Object.entries(r))n.node.style.setProperty(d,c);a!=null&&a.active&&n.node.classList.remove(a.active)}})({styles:{active:{opacity:"0"}}})};function eI(e){let{config:t,draggableNodes:n,droppableContainers:o,measuringConfiguration:r}=e;return Zr((s,a)=>{if(t===null)return;const l=n.get(s);if(!l)return;const d=l.node.current;if(!d)return;const c=df(a);if(!c)return;const{transform:p}=_t(a).getComputedStyle(a),f=Fp(p);if(!f)return;const y=typeof t=="function"?t:tI(t);return Jp(d,r.draggable.measure),y({active:{id:s,data:l.data,node:d,rect:r.draggable.measure(d)},draggableNodes:n,dragOverlay:{node:a,rect:r.dragOverlay.measure(c)},droppableContainers:o,measuringConfiguration:r,transform:f})})}function tI(e){const{duration:t,easing:n,sideEffects:o,keyframes:r}={...R2,...e};return s=>{let{active:a,dragOverlay:l,transform:d,...c}=s;if(!t)return;const p={x:l.rect.left-a.rect.left,y:l.rect.top-a.rect.top},f={scaleX:d.scaleX!==1?a.rect.width*d.scaleX/l.rect.width:1,scaleY:d.scaleY!==1?a.rect.height*d.scaleY/l.rect.height:1},y={x:d.x-p.x,y:d.y-p.y,...f},g=r({...c,active:a,dragOverlay:l,transform:{initial:d,final:y}}),[m]=g,h=g[g.length-1];if(JSON.stringify(m)===JSON.stringify(h))return;const v=o?.({active:a,dragOverlay:l,...c}),b=l.node.animate(g,{duration:t,easing:n,fill:"forwards"});return new Promise(x=>{b.onfinish=()=>{v?.(),x()}})}}let gf=0;function nI(e){return u.useMemo(()=>{if(e!=null)return gf++,gf},[e])}const oI=u.memo(e=>{let{adjustScale:t=!1,children:n,dropAnimation:o,style:r,transition:s,modifiers:a,wrapperElement:l="div",className:d,zIndex:c=999}=e;const{activatorEvent:p,active:f,activeNodeRect:y,containerNodeRect:g,draggableNodes:m,droppableContainers:h,dragOverlay:v,over:b,measuringConfiguration:x,scrollableAncestors:k,scrollableAncestorRects:w,windowRect:_}=K2(),S=u.useContext(ai),E=nI(f?.id),C=pf(a,{activatorEvent:p,active:f,activeNodeRect:y,containerNodeRect:g,draggingNodeRect:v.rect,over:b,overlayNodeRect:v.rect,scrollableAncestors:k,scrollableAncestorRects:w,transform:S,windowRect:_}),N=va(y),$=eI({config:o,draggableNodes:m,droppableContainers:h,measuringConfiguration:x}),A=N?v.setRef:void 0;return u.createElement(X2,null,u.createElement(G2,{animation:$},f&&E?u.createElement(Q2,{key:E,id:f.id,ref:A,as:l,activatorEvent:p,adjustScale:t,className:d,transition:s,rect:N,style:{zIndex:c,...r},transform:C},n):null))});function Go(e,t){return!t||!Array.isArray(t.draggable)?!1:t.draggable.includes(e)}function rI({draggedComponentName:e,targetContainerName:t,nesting:n}){if(!n||!Go(e,n))return!1;const o=n.containers;if(!o||typeof o!="object")return!1;const r=o[t];return!r||!Array.isArray(r.accepts)?!1:r.accepts.includes(e)}function hf(e,t){if(!t?.containers)return!1;const n=t.containers[e];return!!(n&&Array.isArray(n.accepts)&&n.accepts.length>0)}function iI(e,t,n,o,r){if(!n||n.length===0)return{parentPath:[],index:r};if(e&&hf(e,o))return{parentPath:n,index:t};const s=n.slice(0,-1),a=n[n.length-1]??0;return{parentPath:s,index:a+1}}function yf(e,t){return e===t||e.startsWith(`${t}.`)}function bf(e,t){if(e==="")return/^\d+$/.test(t)?Number(t):null;const n=`${e}.children.`;if(!t.startsWith(n))return null;const o=t.slice(n.length);return/^\d+$/.test(o)?Number(o):null}function sI(e,t){const n=[];return e.querySelectorAll("[data-editor-path]").forEach(o=>{if(o.hasAttribute("data-dnd-placeholder")||o.closest("[data-dnd-placeholder]"))return;const r=o.dataset.editorPath;typeof r=="string"&&n.push({path:t(r),domPath:r,rect:o.getBoundingClientRect()})}),n}const aI=16;function lI(e){const{frameEl:t,draggedPath:n,acceptsContainer:o,allowsNestSlot:r,includeContainer:s}=e,a=e.normalizeContainerPath??(g=>g),l=t.getBoundingClientRect(),d=sI(t,a),c=new Map;for(const g of d)c.has(g.path)||c.set(g.path,g.domPath);const p=a(n),f=new Set([""]);for(const g of d){const m=cI(g.path);m!==null&&f.add(m)}for(const g of d)f.add(g.path);const y=[];for(const g of f){const m=g==="",h=m?null:c.get(g)??g,v=m?t:t.querySelector(`[data-editor-path="${_f(h)}"]`);if(!v&&!m||!m&&yf(g,p))continue;const b=g;if(!m&&s&&!s(b))continue;const x=S=>{const E=c.get(S);if(!E||typeof window>"u")return!1;const C=t.querySelector(`[data-editor-path="${_f(E)}"]`);return C?(window.getComputedStyle(C).display??"").toLowerCase()==="contents":!1},k=d.filter(S=>bf(g,S.path)!==null).filter(S=>!yf(S.path,p)).filter(S=>!x(S.path)).map(S=>({origIndex:bf(g,S.path),rect:dI(S.rect,S.path,d)})).filter(S=>S.rect.width>0||S.rect.height>0).sort((S,E)=>S.origIndex-E.origIndex),w=Ap(m?t:v).kind,_=w==="flex_row_single"||w==="flex_row_wrap"||w==="grid_single_row"||w==="grid_2d";if(k.length>0){if(o(b)){k.forEach(E=>{y.push(mf(b,E.origIndex,E.rect,l,_,"before"))});const S=k[k.length-1];y.push(mf(b,S.origIndex+1,S.rect,l,_,"after"))}}else if(!m&&r(b)&&v){const S=v.getBoundingClientRect();S.width>0&&S.height>0&&y.push({id:`slot:${b}:0`,kind:"nest",containerPath:b,index:0,box:vf(S,l),orientation:"area"})}else m&&y.push({id:"slot::0",kind:"gap",containerPath:"",index:0,box:vf(l,l),orientation:"area"})}return y.filter(g=>ao(g.box,t))}function mf(e,t,n,o,r,s){const a=aI;let l;if(r)l={left:(s==="before"?n.left:n.right)-a/2,top:n.top,width:a,height:n.height};else{const d=s==="before"?n.top:n.bottom;l={left:n.left,top:d-a/2,width:n.width,height:a}}return{id:`slot:${e}:${t}`,kind:"gap",containerPath:e,index:t,box:{left:l.left-o.left,top:l.top-o.top,width:l.width,height:l.height,scale:1},orientation:r?"vertical":"horizontal"}}function dI(e,t,n){if(e.width>0||e.height>0)return e;const o=`${t}.children.`;let r=1/0,s=1/0,a=-1/0,l=-1/0;for(const d of n)d.path.startsWith(o)&&(d.rect.width===0&&d.rect.height===0||(r=Math.min(r,d.rect.left),s=Math.min(s,d.rect.top),a=Math.max(a,d.rect.right),l=Math.max(l,d.rect.bottom)));return r===1/0?e:{left:r,top:s,width:a-r,height:l-s,right:a,bottom:l,x:r,y:s,toJSON:()=>({})}}function vf(e,t){return{left:e.left-t.left,top:e.top-t.top,width:e.width,height:e.height,scale:1}}function cI(e){if(e==="")return null;const t=e.lastIndexOf(".children.");return t<0?"":e.slice(0,t)}function uI(e){if(!e.startsWith("slot:"))return null;const t=e.slice(5),n=t.lastIndexOf(":");if(n<0)return null;const o=t.slice(0,n),r=Number(t.slice(n+1));return Number.isFinite(r)?{containerPath:o,index:r}:null}function _f(e){return typeof CSS<"u"&&typeof CSS.escape=="function"?CSS.escape(e):e.replace(/[^a-zA-Z0-9_\-.]/g,t=>`\\${t}`)}function _a(e){return typeof e=="string"&&e.length>0?e:null}function xa(e,t){return t&&e.startsWith(`${t}.`)?t:e}function xf(e){if(typeof e!="string")return null;const t=uI(e);return t?{containerPath:t.containerPath,index:t.index}:null}function wa(e,t){return t?e.startsWith(`${t}.iteration.`):!1}function ka(e,t,n){return t?n==="iteration_item"?wa(e,t):e===t||e.startsWith(`${t}.`):!1}function pI(e){const{frameEl:t,nesting:n,editMode:o,currentExtensionId:r,editableRootPath:s,components:a,patchLayout:l,pushHistory:d,onMovePath:c,selectedPath:p,showBranchBoundaryHint:f}=e,y=(o==="iteration_item"||o==="modal")&&s&&s.length>0?at(s):null,[g,m]=u.useState(null),[h,v]=u.useState(null),[b,x]=u.useState(null),k=u.useRef({components:a,nesting:n,editMode:o,currentExtensionId:r,selectedPath:p,editableRootSourcePath:y});k.current={components:a,nesting:n,editMode:o,currentExtensionId:r,selectedPath:p,editableRootSourcePath:y};const w=u.useRef(null),_=u.useCallback(M=>({children:M}),[]),S=u.useCallback((M,B)=>{const T=We(_(M),B);return T?typeof T.name=="string"?T.name:typeof T.type=="string"?T.type:null:null},[_]),E=u.useCallback(M=>B=>{const T=k.current;if(B==="")return Go(M,T.nesting);const H=Ze(B),L=We(_(T.components),H);if(!L)return!1;const I=typeof L.name=="string"?L.name:typeof L.type=="string"?L.type:"";if(!I)return!1;const P=!!T.editableRootSourcePath&&(B===T.editableRootSourcePath||B.startsWith(`${T.editableRootSourcePath}.`));if(T.editableRootSourcePath&&!P)return!1;if(!P){const F=Sa(_(T.components),H);if(gn(L,T.editMode,T.currentExtensionId,F)!=="none")return!1}return hf(I,T.nesting)?rI({draggedComponentName:M,targetContainerName:I,nesting:T.nesting}):!1},[_]),C=u.useCallback(M=>{if(M==="")return!0;const B=k.current,T=Ze(M),H=w.current;if(H&&H.length>0){const P=H.slice(0,-1);if(li(T,P))return!0}const L=We(_(B.components),T);return L?(typeof L.name=="string"?L.name:typeof L.type=="string"?L.type:"").length>0:!1},[_]),N=u.useCallback(M=>{const B=E(M),T=k.current,H=w.current,L=H?H.slice(0,-1):null,I=new Set;if(L)for(let V=L.length;V>=0;V--)I.add(di(L.slice(0,V)));const P=di(H??[]),F=V=>!!H&&(V===P||V.startsWith(`${P}.`)),G=(V,Y)=>{for(let Z=0;ZV===""?!0:F(V)?!1:I.has(V),allowsNestSlot:V=>{if(!B(V))return!1;if(L){const Y=Ze(V);if(li(Y,L))return!1}return C(V)}}},[E,C,_]),$=u.useCallback(M=>{const B=_a(M.active.id),T=k.current;if(!B)return;const H=xa(B,T.selectedPath),L=Ze(H),I=We(_(T.components),L),P=I&&typeof I.name=="string"?I.name:I&&typeof I.type=="string"?I.type:null;m(H),v(P),x(null),w.current=I?L:null;let F="allowed";if(!P||!Go(P,T.nesting))F="denied_no_draggable";else if(I){const G=Sa(_(T.components),L),V=gn(I,T.editMode,T.currentExtensionId,G);T.editableRootSourcePath&&!ka(H,T.editableRootSourcePath,T.editMode)||Xi(G)&&!wa(H,T.editableRootSourcePath)?F="denied_data_bound":V==="base"||V==="partial"?F="denied_base_locked":(V==="extension"||V==="extension_point")&&(F="denied_extension_locked")}$n({source:"drag",draggedComponentName:P,targetContainerName:null,targetContainerPath:null,decision:F,result:F==="allowed"?"completed":"denied",timestamp:Date.now()})},[_]),A=u.useCallback(M=>{if(!t)return;const B=_a(M.active.id);if(!B)return;const T=h;if(!T){x(null);return}const H=k.current,L=xa(B,H.selectedPath),I=Ze(L),P=We(_(H.components),I);if(!P||!Go(T,H.nesting)){x(null);return}const F=Sa(_(H.components),I);if(H.editableRootSourcePath&&!ka(L,H.editableRootSourcePath,H.editMode)){x(null);return}const G=gn(P,H.editMode,H.currentExtensionId,F),V=wa(L,H.editableRootSourcePath);if(Xi(F)&&!V||G!=="none"&&G!=="data_bound"&&!V){x(null);return}x(xf(M.over?.id))},[t,h,_]),z=u.useCallback(()=>{m(null),v(null),x(null),w.current=null},[]),D=u.useCallback(M=>{const B=_a(M.active.id),T=h,H=k.current,L=B?xa(B,H.selectedPath):null;if(!t||!L||!T){z();return}const I=xf(M.over?.id),P=Ze(L);if(!I){$n({source:"drag",draggedComponentName:T,targetContainerName:null,targetContainerPath:null,decision:"denied_no_accepts",result:"cancelled",timestamp:Date.now()}),z();return}const F=I.containerPath===""?[]:Ze(I.containerPath),G=I.containerPath===""?null:S(H.components,F);if(!fI(P.slice(0,-1),F)){$n({source:"drag",draggedComponentName:T,targetContainerName:G,targetContainerPath:I.containerPath||null,decision:"denied_responsive_branch_boundary",result:"denied",timestamp:Date.now()}),f?.(),z();return}if(H.editableRootSourcePath&&!ka(I.containerPath,H.editableRootSourcePath,H.editMode)){$n({source:"drag",draggedComponentName:T,targetContainerName:G,targetContainerPath:I.containerPath||null,decision:"denied_data_bound",result:"denied",timestamp:Date.now()}),z();return}const V=P.slice(0,-1),Y=P[P.length-1]??-1;if(li(V,F)&&(I.index===Y||I.index===Y+1)){$n({source:"drag",draggedComponentName:T,targetContainerName:G,targetContainerPath:I.containerPath||null,decision:"allowed",insertionIndex:I.index,result:"cancelled",timestamp:Date.now()}),z();return}const re=li(V,F),U=I.index;let ee=[];l(me=>{const Se=Ab({children:me},P,F,U).children??[];return ee=Se,Se}),d(ee,`move ${T}`);const fe=id(F,P),K=re&&Y{z()},[z]);return{activeDragPath:g,activeDragName:h,activeDropZone:b,onDragStart:$,onDragMove:A,onDragEnd:D,onDragCancel:O,buildSlotPredicates:N}}function Sa(e,t){const n=[];let o=e,r=Array.isArray(e.children)?e.children:[];for(let s=0;s0?at(s):null,{t:m}=tt(),[h,v]=u.useState(!1),b=u.useRef(null),x=u.useCallback(()=>{v(!0),b.current&&clearTimeout(b.current),b.current=setTimeout(()=>v(!1),2600)},[]);u.useEffect(()=>()=>{b.current&&clearTimeout(b.current)},[]);const k=pI({frameEl:t,nesting:n,editMode:o,currentExtensionId:r,editableRootPath:s,components:a,patchLayout:l,pushHistory:d,onMovePath:p,selectedPath:y,showBranchBoundaryHint:x}),w=O7(P7(ha,{activationConstraint:{distance:8}})),[_,S]=u.useState([]),E=u.useCallback(()=>{if(!t){S([]);return}if(k.activeDragPath!==null)return;const L={children:a},I=t.querySelectorAll("[data-editor-path]"),P=[],F=new Map;I.forEach(G=>{const V=G.dataset.editorPath;if(typeof V!="string"||o!=="iteration_item"&&g&&!yI(V,g,o))return;const Y=gI(V);if(Y!==null&&!Yo(V,g)){if(/\.iteration\.\d+$/.test(V)){const K=so(G,t);if(K&&ao(K,t)){const ne=F.get(Y)??[];ne.push(K),F.set(Y,ne)}}return}if(Yo(V,g)&&/\.iteration\.\d+$/.test(V))return;const Z=Ze(V),se=We(L,Z);if(!se||se.iteration&&se.iteration.source!==void 0)return;const re=typeof se.name=="string"?se.name:typeof se.type=="string"?se.type:null;if(!re||!Go(re,n))return;const U=kf(L,Z);if(Xi(U)&&!Yo(V,g))return;const ee=gn(se,o,r,U);if(ee!=="none"&&ee!=="data_bound"&&!Yo(V,g))return;const fe=so(G,t);fe&&ao(fe,t)&&P.push({path:V,name:re,box:fe})});for(const[G,V]of F){const Y=Ze(G),Z=We(L,Y);if(!Z)continue;const se=typeof Z.name=="string"?Z.name:typeof Z.type=="string"?Z.type:null;if(!se)continue;const re=kf(L,Y),U=gn(Z,o,r,re);if(U!=="none"&&U!=="data_bound")continue;const ee=bI(V);ee&&ao(ee,t)&&P.push({path:G,name:se,box:ee,isIterationGroup:!0})}S(P)},[t,a,n,o,r,g,k.activeDragPath]);u.useEffect(()=>{E()},[E]),u.useEffect(()=>{if(t)return Sp(E,t)},[t,E]);const C=k.activeDragPath!==null,N=k.activeDragName,$=k.activeDragPath,[A,z]=u.useState(null),D=u.useRef(null);u.useEffect(()=>{if(!$||!t){$||(D.current=null,z(null));return}if(D.current===$)return;D.current=$;const L=t.querySelector(`[data-editor-path="${wf($)}"]`);if(L){const re=L.getBoundingClientRect();z({node:L.cloneNode(!0),width:re.width,height:re.height});return}const I=Array.from(t.querySelectorAll(`[data-editor-path^="${wf($)}.iteration."]`)).filter(re=>/\.iteration\.\d+$/.test(re.dataset.editorPath??""));if(I.length===0){z(null);return}let P=1/0,F=1/0,G=-1/0,V=-1/0;for(const re of I){const U=re.getBoundingClientRect();P=Math.min(P,U.left),F=Math.min(F,U.top),G=Math.max(G,U.right),V=Math.max(V,U.bottom)}const Y=G-P,Z=V-F,se=document.createElement("div");se.style.position="relative",se.style.width=`${Y}px`,se.style.height=`${Z}px`;for(const re of I){const U=re.getBoundingClientRect(),ee=re.cloneNode(!0);ee.style.position="absolute",ee.style.left=`${U.left-P}px`,ee.style.top=`${U.top-F}px`,ee.style.width=`${U.width}px`,ee.style.height=`${U.height}px`,ee.style.margin="0",se.appendChild(ee)}z({node:se,width:Y,height:Z})},[$,t]);const[O,M]=u.useState([]),B=u.useRef(null),T=u.useRef(k.buildSlotPredicates);if(T.current=k.buildSlotPredicates,u.useEffect(()=>{if(!$||!N||!t){B.current=null,M(F=>F.length===0?F:[]);return}if(B.current===$)return;B.current=$;const{acceptsContainer:L,allowsNestSlot:I,includeContainer:P}=T.current(N);M(lI({frameEl:t,draggedPath:$,acceptsContainer:L,allowsNestSlot:I,includeContainer:P,normalizeContainerPath:F=>hI(F)}))},[$,N,t]),!t)return null;const H=k.activeDropZone?`slot:${k.activeDropZone.containerPath}:${k.activeDropZone.index}`:null;return i.jsxs(W2,{sensors:w,collisionDetection:G7,onDragStart:k.onDragStart,onDragMove:k.onDragMove,onDragEnd:k.onDragEnd,onDragCancel:k.onDragCancel,children:[_.map(L=>i.jsx(xI,{path:L.path,name:L.name,box:L.box,dimmed:C&&k.activeDragPath!==L.path,isIterationGroup:L.isIterationGroup,onSelect:c,onRequestInlineEdit:f},L.path)),C&&O.map(L=>i.jsx(_I,{slot:L,active:L.id===H},L.id)),Cn.createPortal(i.jsx(oI,{dropAnimation:null,zIndex:Mp,children:N?A?i.jsx(vI,{name:N,node:A.node,width:A.width,height:A.height}):i.jsx("div",{className:"g7le-dnd-drag-ghost","data-testid":"g7le-dnd-drag-ghost",style:{padding:"4px 10px",background:"rgba(79,70,229,0.95)",color:"#fff",fontSize:12,fontWeight:600,borderRadius:4,pointerEvents:"none",whiteSpace:"nowrap"},children:`<${N}>`}):null}),document.body),h&&Cn.createPortal(i.jsxs("div",{"data-testid":"g7le-branch-boundary-hint",role:"status",style:{position:"absolute",left:"50%",top:12,transform:"translateX(-50%)",background:"rgba(245,158,11,0.97)",color:"#fff",fontSize:13,fontWeight:600,padding:"8px 16px",borderRadius:8,boxShadow:"0 4px 14px rgba(0,0,0,0.2)",pointerEvents:"none",whiteSpace:"nowrap",zIndex:Mp},children:["⚠️ ",m("layout_editor.overlay.branch_boundary_denied")]}),t)]})}function vI(e){const{node:t,width:n,height:o}=e,r=u.useRef(null);return u.useEffect(()=>{const s=r.current;if(s)return s.replaceChildren(),s.appendChild(t.cloneNode(!0)),()=>{s.replaceChildren()}},[t]),i.jsx("div",{ref:r,className:"g7le-dnd-drag-ghost","data-testid":"g7le-dnd-drag-ghost",style:{width:n,height:o,opacity:.7,pointerEvents:"none",boxShadow:"0 6px 18px rgba(0,0,0,0.25)",borderRadius:4,overflow:"hidden",cursor:"grabbing"}})}function _I(e){const{slot:t,active:n}=e,{setNodeRef:o}=q2({id:t.id}),r=t.orientation!=="area";return i.jsx("div",{ref:o,"data-testid":`g7le-dnd-slot-${t.id}`,"data-dnd-slot-id":t.id,style:{position:"absolute",left:t.box.left,top:t.box.top,width:t.box.width,height:t.box.height,pointerEvents:"auto",zIndex:p7,boxSizing:"border-box",...r?{background:n?"rgba(79,70,229,0.15)":"transparent",...n?t.orientation==="vertical"?{borderLeft:"2px solid #4f46e5"}:{borderTop:"2px solid #4f46e5"}:{}}:{border:n?"2px dashed #4f46e5":"1px dashed transparent",background:n?"rgba(79,70,229,0.08)":"transparent",borderRadius:4}}})}function wf(e){return typeof CSS<"u"&&typeof CSS.escape=="function"?CSS.escape(e):e.replace(/[^a-zA-Z0-9_\-.]/g,t=>`\\${t}`)}function xI(e){const{path:t,box:n,dimmed:o,isIterationGroup:r,onSelect:s,onRequestInlineEdit:a}=e,{attributes:l,listeners:d,setNodeRef:c,isDragging:p}=F2({id:t}),f=(t.match(/\.children\./g)||[]).length,y=f7(f);return i.jsx("div",{ref:c,"data-testid":`g7le-dnd-handle-${t}`,"data-dnd-handle-path":t,"data-dnd-iteration-group":r?"true":void 0,...d,...l,onClick:g=>{if(g.stopPropagation(),!r&&typeof document<"u"&&typeof document.elementsFromPoint=="function"){const m=document.elementsFromPoint(g.clientX,g.clientY);for(const h of m){const b=h.closest?.("[data-editor-path]")?.dataset?.editorPath;if(typeof b=="string"&&(b===t||b.startsWith(`${t}.`))){s(b);return}}}s(t)},onDoubleClick:g=>{g.stopPropagation(),g.preventDefault(),a?.(t)},style:{position:"absolute",left:n.left,top:n.top,width:n.width,height:n.height,cursor:"grab",pointerEvents:"auto",background:p?"rgba(79,70,229,0.06)":"transparent",...r?{border:"1px dashed rgba(79,70,229,0.5)",borderRadius:4}:{},opacity:o?.6:1,zIndex:y,touchAction:"none"}})}function kf(e,t){const n=[];let o=e,r=Array.isArray(e.children)?e.children:[];for(let s=0;s0?[o]:[];const r=[],s=(a,l)=>{a.forEach((d,c)=>{const p=[...l,c];if(!Ji(d,t,n)){r.push(p);return}const y=Array.isArray(d.children)?d.children:[];y.length>0&&s(y,p)})};return s(e,[]),r}function kI(e){return at(e)}function ci(e){return typeof CSS<"u"&&typeof CSS.escape=="function"?CSS.escape(e):e.replace(/["\\]/g,"\\$&")}function SI({frameEl:e,components:t,editMode:n,currentExtensionId:o,editableRootPath:r}){const[s,a]=u.useState(null),[l]=u.useState(()=>`g7le-dim-mask-${Math.floor(Math.random()*1e9)}`);if(u.useEffect(()=>{if(!e){a(null);return}let g=0;const m=()=>{const x=wI(t,n,o,r);if(x.length===0){a(null);return}const k=e.getBoundingClientRect(),w={left:0,top:0,width:k.width,height:k.height},_=[];for(const S of x){const E=kI(S),C=e.querySelector(`[data-editor-path="${ci(E)}"]`);let N=null;if(C){const $=C.getBoundingClientRect();$.width>0&&$.height>0&&(N={left:$.left,top:$.top,width:$.width,height:$.height})}if(!N){const $=Array.from(e.querySelectorAll(`[data-editor-path^="${ci(E)}.iteration."], [data-editor-path^="${ci(E)}.sortable."], [data-editor-path^="${ci(E)}.children."]`));let A=1/0,z=1/0,D=-1/0,O=-1/0;for(const M of $){const B=M.getBoundingClientRect();B.width<=0||B.height<=0||(A=Math.min(A,B.left),z=Math.min(z,B.top),D=Math.max(D,B.right),O=Math.max(O,B.bottom))}if(D>A&&O>z&&(N={left:A,top:z,width:D-A,height:O-z}),N&&$.length>0){const M=$[0].closest('[role="dialog"]');if(M&&e.contains(M)){const B=M.getBoundingClientRect();B.width>0&&B.height>0&&(N={left:B.left,top:B.top,width:B.width,height:B.height})}}}N&&_.push({left:N.left-k.left,top:N.top-k.top,width:N.width,height:N.height})}if(_.length===0){a(null);return}a({frame:w,holes:_})},h=()=>{g&&cancelAnimationFrame(g),g=requestAnimationFrame(m)};h();const v=typeof ResizeObserver<"u"?new ResizeObserver(h):null;v?.observe(e);const b=typeof MutationObserver<"u"?new MutationObserver(h):null;return b?.observe(e,{childList:!0,subtree:!0,attributes:!0}),window.addEventListener("scroll",h,!0),window.addEventListener("resize",h),()=>{g&&cancelAnimationFrame(g),v?.disconnect(),b?.disconnect(),window.removeEventListener("scroll",h,!0),window.removeEventListener("resize",h)}},[e,t,n,o,r?.join(".")??""]),!s||s.holes.length===0)return null;const{frame:d,holes:c}=s,p=n==="modal"||n==="iteration_item";let f=[];if(p){let g=1/0,m=1/0,h=-1/0,v=-1/0;for(const S of c)g=Math.min(g,S.left),m=Math.min(m,S.top),h=Math.max(h,S.left+S.width),v=Math.max(v,S.top+S.height);const b=(S,E,C)=>Math.max(E,Math.min(C,S)),x=b(g,0,d.width),k=b(m,0,d.height),w=b(h,0,d.width),_=b(v,0,d.height);f=[{left:0,top:0,width:d.width,height:k},{left:0,top:_,width:d.width,height:d.height-_},{left:0,top:k,width:x,height:_-k},{left:w,top:k,width:d.width-w,height:_-k}].filter(S=>S.width>0&&S.height>0)}const y=g=>{g.preventDefault(),g.stopPropagation()};return i.jsxs(i.Fragment,{children:[f.map((g,m)=>i.jsx("div",{className:"g7le-source-lock-block","data-testid":"g7le-source-lock-block",onClickCapture:y,onPointerDownCapture:y,onMouseDownCapture:y,onDragStartCapture:y,style:{position:"absolute",left:g.left,top:g.top,width:g.width,height:g.height,zIndex:zp,pointerEvents:"auto",cursor:"not-allowed",background:"transparent"}},m)),i.jsxs("svg",{className:"g7le-source-lock-dim","data-testid":"g7le-source-lock-dim",width:d.width,height:d.height,style:{position:"absolute",left:0,top:0,width:d.width,height:d.height,zIndex:zp,pointerEvents:"none"},children:[i.jsx("defs",{children:i.jsxs("mask",{id:l,children:[i.jsx("rect",{x:0,y:0,width:d.width,height:d.height,fill:"white"}),c.map((g,m)=>i.jsx("rect",{"data-testid":"g7le-source-lock-dim-hole",x:g.left,y:g.top,width:g.width,height:g.height,rx:4,fill:"black"},m))]})}),i.jsx("rect",{"data-testid":"g7le-source-lock-dim-box",x:0,y:0,width:d.width,height:d.height,fill:"rgba(15, 23, 42, 0.45)",mask:`url(#${l})`})]})]})}function Sf(e){const t={type:e.type??"basic",name:e.name},n={};if(e.props)for(const[o,r]of Object.entries(e.props))o!=="children"&&r?.default!==void 0&&(n[o]=r.default);return Object.keys(n).length>0&&(t.props=n),t}function CI(e){if(!e?.components)return[];const t=[];for(const[n,o]of Object.entries(e.components))if(Array.isArray(o))for(const r of o)t.push({...r,type:r.type??n});return t}function $I(e,t,n,o,r){const s=CI(e);let a=null;if(n&&t?.containers){const l=t.containers[n];l&&Array.isArray(l.accepts)?a=new Set(l.accepts):a=new Set}else!n&&t?.draggable&&(a=new Set(t.draggable));return s.filter(l=>{if(a&&!a.has(l.name))return!1;if(o){const d=o.toLowerCase(),c=r(l).toLowerCase(),p=l.name.toLowerCase();if(!c.includes(d)&&!p.includes(d)&&!`<${p}>`.includes(d)&&!(l.description??"").toLowerCase().includes(d))return!1}return!0})}function EI(e,t){const n={key:"all",label:"layout_editor.palette.category.all",entries:e,isI18nKey:!0};if(t?.groups&&t.groups.length>0){const a=new Map(e.map(p=>[p.name,p])),l=new Set,d=[];for(const p of t.groups){const f=[];for(const y of p.components??[]){const g=a.get(y);g&&(f.push(g),l.add(y))}f.length!==0&&d.push({key:p.kind??p.label,label:p.label,entries:f,isI18nKey:p.label.startsWith("$t:")||/^[a-z_.]+$/i.test(p.label)})}const c=e.filter(p=>!l.has(p.name));return c.length>0&&d.push({key:"other",label:"layout_editor.palette.category.other",entries:c,isI18nKey:!0}),[n,...d]}const o=new Map;for(const a of e){const l=(a.type??"basic").toLowerCase(),d=o.get(l)??[];d.push(a),o.set(l,d)}const r=["basic","composite","layout"],s=[];for(const a of r){const l=o.get(a);l&&l.length>0&&(s.push({key:a,label:`layout_editor.palette.category.fallback.${a}`,entries:l,isI18nKey:!0}),o.delete(a))}for(const[a,l]of o.entries())s.push({key:a,label:a,entries:l,isI18nKey:!1});return[n,...s]}function Cf(e){const{manifest:t,nesting:n,componentPalette:o,targetParentPath:r,targetContainerName:s,onInsert:a,insertionIndex:l,onClose:d,editorTemplateId:c,editorLocale:p}=e,{t:f,translationEngine:y}=tt(),g=u.useMemo(()=>{if(!c||!y)return f;const A=p??null;return z=>{const D={templateId:c,locale:A??"ko"},O=y.translate(z,D);return O!==z?O:f(z)}},[c,p,y,f]),[m,h]=u.useState(""),[v,b]=u.useState("all"),x=o?.entries??null,k=u.useMemo(()=>A=>{const z=x?.[A.name];return z?.label?ui(z.label,g):A.description??A.name},[x,g]),w=u.useMemo(()=>$I(t,n,s,m,k),[t,n,s,m,k]),_=u.useMemo(()=>EI(w,o??null),[w,o]),S=_.find(A=>A.key===v)??_[0]??null,E=!n||!n.draggable&&!n.containers,C=w.length===0,N=S?.entries??[],$=A=>{const z=x?.[A.name],D=!!z?.defaultNode&&typeof z.defaultNode=="object";if(z?.requiresDefaultNode&&!D)return;const O=D?JSON.parse(JSON.stringify(z.defaultNode)):Sf(A),M=r??[];a(O,M,l),$n({source:"palette",draggedComponentName:A.name,targetContainerName:s,targetContainerPath:M.length===0?null:M.join("/"),decision:"allowed",insertionIndex:l,result:"completed",timestamp:Date.now()}),d()};return i.jsxs("div",{className:"g7le-palette","data-testid":"g7le-palette",style:DI,children:[i.jsxs("div",{style:NI,children:[i.jsx("strong",{style:{fontSize:16},children:f("layout_editor.palette.title")}),i.jsx("button",{type:"button",onClick:d,"aria-label":"close","data-testid":"g7le-palette-close",style:zI,children:"×"})]}),i.jsxs("div",{style:MI,children:[i.jsx("aside",{style:LI,"data-testid":"g7le-palette-sidebar",children:i.jsx("nav",{children:i.jsx("ul",{style:PI,children:_.map(A=>{const z=S?.key===A.key,D=A.isI18nKey?ui(A.label,g):A.label;return i.jsx("li",{children:i.jsxs("button",{type:"button","data-testid":`g7le-palette-category-${A.key}`,"data-active":z?"true":"false",onClick:()=>b(A.key),style:{...OI,background:z?"#eff6ff":"transparent",color:z?"#1d4ed8":"#334155",fontWeight:z?600:500},children:[i.jsx("span",{children:D}),i.jsx("span",{style:HI,children:A.entries.length})]})},A.key)})})})}),i.jsxs("main",{style:BI,children:[i.jsx("input",{type:"text",placeholder:f("layout_editor.palette.search_placeholder"),value:m,onChange:A=>h(A.target.value),"data-testid":"g7le-palette-search",style:WI}),i.jsx("div",{style:jI,children:E?i.jsx("div",{style:$f,"data-testid":"g7le-palette-spec-missing",children:f("layout_editor.palette.spec_missing")}):C||N.length===0?i.jsx("div",{style:$f,"data-testid":"g7le-palette-empty",children:f("layout_editor.palette.empty")}):i.jsx("div",{"data-testid":`g7le-palette-group-${S?.key??"all"}`,style:{marginBottom:16},children:i.jsx("div",{style:FI,children:N.map(A=>i.jsx(II,{entry:A,entrySpec:x?.[A.name]??null,onClick:$,t:f,entryT:g},A.name))})})})]})]})]})}function II(e){const{entry:t,entrySpec:n,onClick:o,t:r,entryT:s}=e,a=n?.label?ui(n.label,s):t.description??t.name,l=(t.type??"").toLowerCase(),d=l==="basic"||l==="composite"||l==="layout",c=!!n?.defaultNode&&typeof n.defaultNode=="object",p=!!n?.requiresDefaultNode&&!c,f=!c,y=p?r("layout_editor.palette.requires_default_node_missing"):n?.label?ui(n.label,s):t.name;return i.jsxs("button",{type:"button",onClick:()=>o(t),disabled:p,"aria-disabled":p||void 0,"data-testid":`g7le-palette-item-${t.name}`,"data-component-type":l||void 0,"data-blocked":p?"true":void 0,"data-default-node-missing":f?"true":void 0,title:y,style:p?VI:KI,children:[i.jsx("div",{style:qI,"aria-hidden":"true",children:TI(t)}),i.jsx("div",{style:GI,children:a}),i.jsx("div",{"data-testid":`g7le-palette-item-${t.name}-tag`,style:YI,children:`<${t.name}>`}),d&&i.jsx("div",{"data-testid":`g7le-palette-item-${t.name}-badge`,style:AI(l),children:l}),f&&i.jsx("div",{"data-testid":`g7le-palette-item-${t.name}-default-missing-badge`,style:UI,children:r("layout_editor.palette.default_node_missing_badge")})]})}function ui(e,t){return e.startsWith("$t:")?t(e.slice(3)):t(e)}function AI(e){const n={basic:{bg:"#dbeafe",fg:"#1e40af"},composite:{bg:"#dcfce7",fg:"#14532d"},layout:{bg:"#fef3c7",fg:"#854d0e"}}[e]??{bg:"#e2e8f0",fg:"#334155"};return{fontSize:9,fontWeight:600,color:n.fg,background:n.bg,padding:"1px 6px",borderRadius:999,textTransform:"uppercase",letterSpacing:.4,lineHeight:1.6}}function TI(e){const t=e.name;return{Button:"🔘",A:"🔗",Img:"🖼",Icon:"✨",Hr:"➖",P:"📄",Span:"T",H1:"H₁",H2:"H₂",H3:"H₃",H4:"H₄",Ul:"☰",Li:"•",Nav:"🧭",Div:"▢",Container:"▢",Flex:"⇆",Grid:"▦",SectionLayout:"▤",ThreeColumnLayout:"⫼",Form:"📝",Input:"⎕",Textarea:"▭",Select:"⏷",Checkbox:"☑",PasswordInput:"🔒",Label:"🏷",DataGrid:"▦",Pagination:"↔",SearchBar:"🔍",ProductCard:"🛍",ImageGallery:"🖼",Avatar:"👤",UserInfo:"👥",HtmlContent:"⟨/⟩",HtmlEditor:"✏",RichTextEditor:"✏",TabNavigation:"◰",QuantitySelector:"#",ProductImageViewer:"🖼",FileUploader:"📎",AvatarUploader:"📷",SocialLoginButtons:"🌐",PostReactions:"❤"}[t]??"◻"}const DI={display:"flex",flexDirection:"column",width:"100%",height:"100%",minHeight:0,fontFamily:'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Apple SD Gothic Neo", "Noto Sans KR", sans-serif'},NI={display:"flex",justifyContent:"space-between",alignItems:"center",padding:"14px 20px",borderBottom:"1px solid #e2e8f0",flexShrink:0},zI={background:"transparent",border:"none",fontSize:24,cursor:"pointer",lineHeight:1,color:"#64748b",padding:"0 4px"},MI={display:"flex",flex:1,minHeight:0},LI={width:180,flexShrink:0,borderRight:"1px solid #e2e8f0",padding:"14px 8px",background:"#f8fafc",overflowY:"auto"},PI={listStyle:"none",margin:0,padding:0,display:"flex",flexDirection:"column",gap:4},OI={display:"flex",width:"100%",alignItems:"center",justifyContent:"space-between",textAlign:"left",padding:"8px 12px",border:"none",borderRadius:6,fontSize:13,cursor:"pointer",transition:"background-color 120ms"},BI={flex:1,display:"flex",flexDirection:"column",minHeight:0,padding:"14px 20px"},WI={width:"100%",padding:"8px 10px",border:"1px solid #cbd5e1",borderRadius:6,marginBottom:12,fontSize:13,flexShrink:0,boxSizing:"border-box"},jI={flex:1,overflowY:"auto",minHeight:0},HI={fontSize:11,color:"#94a3b8",marginLeft:8,fontWeight:500},FI={display:"grid",gridTemplateColumns:"repeat(auto-fill, minmax(96px, 1fr))",gap:10},KI={display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",gap:6,padding:"14px 6px",background:"#fff",border:"1px solid #e2e8f0",borderRadius:8,cursor:"pointer",fontSize:12,minHeight:84,transition:"border-color 120ms, box-shadow 120ms"},VI={display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",gap:6,padding:"14px 6px",background:"#f8fafc",border:"1px dashed #cbd5e1",borderRadius:8,cursor:"not-allowed",fontSize:12,minHeight:84,color:"#94a3b8",opacity:.65},UI={fontSize:9,fontWeight:600,color:"#92400e",background:"#fef3c7",padding:"1px 6px",borderRadius:999,textTransform:"none",letterSpacing:0,lineHeight:1.6},qI={fontSize:22,lineHeight:1,color:"#475569"},GI={fontSize:12,fontWeight:500,color:"#0f172a",textAlign:"center",wordBreak:"break-word"},YI={fontFamily:'ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace',fontSize:10,color:"#64748b",textAlign:"center",wordBreak:"break-all"},$f={padding:24,color:"#64748b",fontSize:13,textAlign:"center"};function XI(e){const t=[],n=r=>{if(Array.isArray(r))for(const s of r)s&&typeof s=="object"&&t.push(s)};n(e.children);const o=e.responsive;if(o&&typeof o=="object")for(const r of Object.values(o))r&&typeof r=="object"&&n(r.children);return t}function Ef(e){const t=[],n=o=>{const r="isolatedState"in o&&o.isolatedState!=null,s=o.isolatedScopeId;(r||typeof s=="string"&&s.length>0)&&t.push({scopeId:typeof s=="string"?s:void 0,initialState:o.isolatedState&&typeof o.isolatedState=="object"?o.isolatedState:void 0});for(const a of XI(o))n(a)};if(Array.isArray(e))for(const o of e)o&&typeof o=="object"&&n(o);return t}const JI=["scroll","selector","wizard","slider"];function ZI(e,t){const n=new Set;for(const o of e)o.scopeId&&n.add(o.scopeId);for(const o of t){n.add(o);for(const r of JI)n.add(`${o}-${r}`)}return Array.from(n)}function QI(e,t,n){const o=t.label;return typeof o=="string"?o.startsWith("$t:")?n(o.slice(3)):o:e}function pi({controlKey:e,control:t,node:n,t:o,onPatch:r,candidates:s,bindingCandidates:a,scope:l=Wt}){const d=Tn(t.widget),c=QI(e,t,o),p=u.useMemo(()=>Do(n,t,l),[n,t,l]),f=t.widget==="i18n-text"&&t.apply?.type==="propValue",y=t.widget==="text"&&t.apply?.type==="propValue",g=p.darkReadonly===!0||l.colorScheme==="dark"&&t.apply!==void 0&&!Us(t.apply),m=t.apply,h=l.colorScheme==="dark"&&!g&&m?.type==="classToken"&&typeof m.tokenTemplate=="string"&&m.tokenTemplate.includes("{value}"),v=p.scopedValue===void 0&&p.baseFallback!==void 0,b=u.useCallback(x=>{if(g)return;const k=fn(n,t,x,l);r(k)},[n,t,r,l,g]);return i.jsxs("div",{className:"g7le-control-row","data-testid":`g7le-control-${e}`,style:RI,children:[i.jsxs("span",{className:"g7le-control-label",style:eA,children:[c,p.conflict&&i.jsx("span",{"data-testid":`g7le-control-conflict-${e}`,title:o("layout_editor.property_modal.value_conflict"),style:tA,children:"!"})]}),i.jsx("div",{className:"g7le-control-widget",style:{flex:1,minWidth:0,opacity:v?.45:1},"data-placeholder":v?"true":void 0,children:f?i.jsx(vt,{value:typeof p.value=="string"?p.value:"",onChange:x=>b(x),t:o,testidPrefix:`g7le-prop-i18n-${e}`,candidates:a,enableExpressionTree:!0,expressionTreeCollapsible:!0}):y?i.jsx(Ve,{value:typeof p.value=="string"?p.value:"",onChange:x=>b(x===""?void 0:x),t:o,testidPrefix:`g7le-prop-value-${e}`,candidates:a}):g?i.jsx("span",{"data-testid":`g7le-control-dark-readonly-${e}`,style:oA,children:o("layout_editor.property_modal.dark_code_only")}):d?i.jsx(d,{control:t,value:p.value,onChange:b,t:o,candidates:s,bindingCandidates:a,freeValueDisabled:h}):i.jsx("span",{"data-testid":`g7le-control-unsupported-${e}`,style:nA,children:o("layout_editor.property_modal.unsupported_widget")})})]})}const RI={display:"flex",alignItems:"center",gap:12,padding:"6px 0",minHeight:32,minWidth:0},eA={fontSize:12,color:"#475569",minWidth:88,display:"inline-flex",alignItems:"center",gap:4},tA={display:"inline-flex",width:14,height:14,borderRadius:7,background:"#f59e0b",color:"#fff",fontSize:10,alignItems:"center",justifyContent:"center",fontWeight:700},nA={fontSize:11,color:"#94a3b8",fontStyle:"italic"},oA={fontSize:11,color:"#94a3b8",fontStyle:"italic"};function fi(e,t,n){return typeof e=="string"?e.startsWith("$t:")?n(e.slice(3)):e:t}function rA(e,t){const n=e.props??{};return t.key in n?n[t.key]:t.default}function iA({spec:e,node:t,t:n,onPatchProp:o}){const r=e.groups??[];return i.jsx("div",{className:"g7le-composite-settings","data-testid":"g7le-composite-settings",style:aA,children:r.map((s,a)=>i.jsxs("div",{className:"g7le-settings-group",style:lA,children:[i.jsx("div",{style:dA,children:fi(s.label,n("layout_editor.property_modal.settings_group",{index:a+1}),n)}),(s.fields??[]).map(l=>i.jsxs("div",{className:"g7le-settings-field","data-testid":`g7le-setting-${l.key}`,style:cA,children:[i.jsx("span",{style:uA,children:fi(l.label,l.key,n)}),i.jsx("div",{style:{flex:1},children:sA(l,t,n,o)})]},l.key))]},a))})}function sA(e,t,n,o){const r=rA(t,e);switch(e.type){case"number":return i.jsx("input",{type:"number","data-testid":`g7le-setting-input-${e.key}`,value:typeof r=="number"?r:"",min:e.min,max:e.max,onChange:s=>o(e.key,s.target.value===""?void 0:Number(s.target.value)),style:Ca});case"text":return i.jsx("input",{type:"text","data-testid":`g7le-setting-input-${e.key}`,value:typeof r=="string"?r:"",onChange:s=>o(e.key,s.target.value===""?void 0:s.target.value),style:Ca});case"toggle":return i.jsx("input",{type:"checkbox","data-testid":`g7le-setting-input-${e.key}`,checked:r===!0,onChange:s=>o(e.key,s.target.checked)});case"checkbox-group":{const s=Array.isArray(r)?r:[];return i.jsx("div",{style:pA,"data-testid":`g7le-setting-input-${e.key}`,children:(e.options??[]).map(a=>{const l=s.includes(a.value);return i.jsxs("label",{style:fA,children:[i.jsx("input",{type:"checkbox",checked:l,onChange:d=>{const c=d.target.checked?[...s,a.value]:s.filter(p=>p!==a.value);o(e.key,c)}}),fi(a.label,String(a.value),n)]},String(a.value))})})}default:return i.jsxs("select",{"data-testid":`g7le-setting-input-${e.key}`,value:r==null?"":String(r),onChange:s=>{const a=s.target.value;if(a===""){o(e.key,void 0);return}const l=(e.options??[]).find(d=>String(d.value)===a);o(e.key,l?l.value:a)},style:Ca,children:[i.jsx("option",{value:"",children:"—"}),(e.options??[]).map(s=>i.jsx("option",{value:String(s.value),children:fi(s.label,String(s.value),n)},String(s.value)))]})}}const aA={display:"flex",flexDirection:"column",gap:16},lA={display:"flex",flexDirection:"column",gap:6},dA={fontSize:12,fontWeight:700,color:"#0f172a",borderBottom:"1px solid #e2e8f0",paddingBottom:4},cA={display:"flex",alignItems:"center",gap:12,minHeight:30},uA={fontSize:12,color:"#475569",minWidth:96},Ca={padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,width:"100%",boxSizing:"border-box"},pA={display:"flex",flexWrap:"wrap",gap:10},fA={display:"inline-flex",alignItems:"center",gap:4,fontSize:12,color:"#0f172a"},gA=["data_binding","isolatedScopeId","parentFormContextProp","skipBindingKeys","isolatedState","component_layout","sortable","itemTemplate"],hA=/\{\{|\$switch|\$get|\|\s*(date|number|truncate|currency|relative)\b/;function gi(e,t=0){return t>6?!1:typeof e=="string"?hA.test(e):Array.isArray(e)?e.some(n=>gi(n,t+1)):e&&typeof e=="object"?Object.values(e).some(n=>gi(n,t+1)):!1}function yA(e){if(Zl(e))return!1;const t=Fl(e);return t.length===0?!1:t.some(n=>n.isComplex)}function If(e){const t=[],n=e.props??{};for(const o of gA)o in e&&e[o]!==void 0?t.push({key:o,kind:"developer_prop"}):o in n&&n[o]!==void 0&&t.push({key:`props.${o}`,kind:"developer_prop"});typeof e.text=="string"?yA(e.text)&&t.push({key:"text",kind:"complex_expression"}):gi(e.text)&&t.push({key:"text",kind:"complex_expression"});for(const[o,r]of Object.entries(n))o==="style"||o==="className"||gi(r)&&t.push({key:`props.${o}`,kind:"complex_expression"});return t}function bA(e){return If(e).length>0}const mA=new Set(["permissions","blur_until_loaded","comment"]);function vA({node:e,advanced:t,t:n,onPatch:o,permissionCandidates:r}){const s=u.useMemo(()=>If(e),[e]),a=(l,d)=>{const c={...e};d===void 0?delete c[l]:c[l]=d,o(c)};return i.jsxs("div",{className:"g7le-advanced-props","data-testid":"g7le-advanced-props",style:_A,children:[t.map(l=>{if(!mA.has(l))return i.jsxs("div",{"data-testid":`g7le-advanced-deferred-${l}`,style:wA,children:[n("layout_editor.property_modal.advanced_deferred"),":",l]},l);if(l==="permissions")return i.jsxs("div",{className:"g7le-advanced-group","data-testid":"g7le-advanced-permissions",style:$a,children:[i.jsx("div",{style:Ea,children:n("layout_editor.property_modal.advanced.permissions")}),i.jsx(ys,{control:{widget:"tag-input"},value:e.permissions,onChange:d=>a("permissions",d),t:n,candidates:r})]},l);if(l==="blur_until_loaded"){const d=e.blur_until_loaded===!0||e.blur_until_loaded!=null&&e.blur_until_loaded!==!1;return i.jsxs("div",{className:"g7le-advanced-group","data-testid":"g7le-advanced-blur",style:$a,children:[i.jsx("div",{style:Ea,children:n("layout_editor.property_modal.advanced.blur_until_loaded")}),i.jsxs("label",{style:{display:"inline-flex",alignItems:"center",gap:6,fontSize:12},children:[i.jsx("input",{type:"checkbox","data-testid":"g7le-advanced-blur-toggle",checked:!!d,onChange:c=>a("blur_until_loaded",c.target.checked?!0:void 0)}),n("layout_editor.property_modal.advanced.blur_hint")]})]},l)}return i.jsxs("div",{className:"g7le-advanced-group","data-testid":"g7le-advanced-comment",style:$a,children:[i.jsx("div",{style:Ea,children:n("layout_editor.property_modal.advanced.comment")}),i.jsx("input",{type:"text","data-testid":"g7le-advanced-comment-input",value:typeof e.comment=="string"?e.comment:"",onChange:d=>a("comment",d.target.value===""?void 0:d.target.value),style:xA})]},l)}),s.length>0&&i.jsxs("div",{className:"g7le-advanced-preserved","data-testid":"g7le-advanced-preserved",style:kA,children:[i.jsx("div",{style:SA,children:n("layout_editor.property_modal.advanced_preserved_title",{count:s.length})}),i.jsx("ul",{style:CA,children:s.map(l=>i.jsxs("li",{"data-testid":`g7le-advanced-preserved-${l.key}`,style:$A,children:[i.jsx("code",{style:{fontSize:11},children:l.key}),i.jsx("span",{style:{fontSize:11,color:"#94a3b8"},children:l.kind==="developer_prop"?n("layout_editor.property_modal.advanced_kind_developer"):n("layout_editor.property_modal.advanced_kind_expression")})]},l.key))})]})]})}const _A={display:"flex",flexDirection:"column",gap:14},$a={display:"flex",flexDirection:"column",gap:6},Ea={fontSize:12,fontWeight:700,color:"#0f172a"},xA={padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,width:"100%",boxSizing:"border-box"},wA={fontSize:11,color:"#94a3b8",fontStyle:"italic"},kA={borderTop:"1px dashed #cbd5e1",paddingTop:10,marginTop:4},SA={fontSize:11,color:"#b45309",marginBottom:6},CA={listStyle:"none",margin:0,padding:0,display:"flex",flexDirection:"column",gap:4},$A={display:"flex",justifyContent:"space-between",gap:8,padding:"3px 0"},EA={onClick:"click",onHover:"mouseenter",onChange:"change",onSubmit:"submit",onMount:"mount",onUnmount:"unmount",onFocus:"focus",onBlur:"blur"};function Ia(e){return EA[e]??e.replace(/^on/,"").toLowerCase()}function Af(e){const t=e.actions;if(Array.isArray(t))return t.filter(r=>!!r&&typeof r=="object");const n=e.props??{};if(Array.isArray(n.actions))return n.actions.filter(r=>!!r&&typeof r=="object");const o=n.events;if(o&&typeof o=="object"){const r=[];for(const[s,a]of Object.entries(o)){if(!Array.isArray(a))continue;const l=Ia(s);for(const d of a)d&&typeof d=="object"&&r.push("type"in d?d:{type:l,...d})}return r}return[]}function IA(e,t){const n=Ia(t);return Af(e).filter(o=>o.type===n||t==="onClick"&&o.type===void 0)}function AA(e,t,n){const o=Ia(t),r=Af(e).filter(d=>!(d.type===o||t==="onClick"&&d.type===void 0)),s=n.map(d=>"type"in d?d:{type:o,...d}),a={...e};if(a.props&&typeof a.props=="object"){const d={...a.props};let c=!1;"actions"in d&&(delete d.actions,c=!0),"events"in d&&(delete d.events,c=!0),c&&(a.props=d)}const l=[...r,...s];return l.length===0?delete a.actions:a.actions=l,a}function TA({node:e,spec:t,capability:n,t:o,onPatchNode:r,pageCandidates:s,dataSourceCandidates:a,stateKeyCandidates:l,bindingCandidates:d}){const c=t?.actionRecipes,p=u.useMemo(()=>({pageCandidates:s,dataSourceCandidates:a,stateKeyCandidates:l,bindingCandidates:d}),[s,a,l,d]),f=Array.isArray(n?.events)?n.events:[];return f.length===0?i.jsx("div",{"data-testid":"g7le-action-no-events",style:Tf,children:o("layout_editor.action.no_events")}):!c||Object.keys(c).length===0?i.jsx("div",{"data-testid":"g7le-action-no-recipes",style:Tf,children:o("layout_editor.action.no_recipes")}):i.jsx("div",{className:"g7le-action-editor","data-testid":"g7le-action-editor",style:{display:"flex",flexDirection:"column",gap:14},children:f.map(y=>i.jsxs("div",{className:"g7le-action-event-slot","data-testid":`g7le-action-event-${y}`,children:[i.jsx("div",{style:DA,children:o(`layout_editor.action.event.${y}`)}),i.jsx(Gt,{actions:IA(e,y),onChange:g=>r(AA(e,y,g)),t:o,recipes:c,candidatePools:p,actionChipCandidates:t?.actionChipCandidates??null,addContext:"component",showSourceBadge:!0,testIdPrefix:`g7le-action-slot-${y}`})]},y))})}const Tf={fontSize:12,color:"#94a3b8",padding:"16px 0",textAlign:"center"},DA={fontSize:12,fontWeight:700,color:"#475569",marginBottom:6};function Aa(e,t,n){if(!t)return n;const o=e(t);return!o||o===t||o.startsWith("$t:")?n:o}function NA(e,t){return Aa(t,e.labelKey,e.path||e.sourceId)}function zA({node:e,spec:t,candidates:n,t:o,onPatchNode:r}){const a=(e.props??{})[t.propKey],l=typeof a=="string"?a:void 0,d=u.useMemo(()=>Kt(a),[a]),c=l!==void 0&&l!==""&&d===null,[p,f]=u.useState(!1),[y,g]=u.useState(""),m=Aa(o,t.label,t.propKey),h=o(t.shape==="array"?"layout_editor.binding.shape_array":t.shape==="object"?"layout_editor.binding.shape_object":"layout_editor.binding.shape_scalar"),v=u.useMemo(()=>Fi(n,t.shape).map(_=>({..._,resolvedLabel:NA(_,o)})),[n,t.shape,o]),b=u.useMemo(()=>Ki(v,y),[v,y]),x=w=>{const _={...e.props??{}};w===void 0?delete _[t.propKey]:_[t.propKey]=w,r({...e,props:_})},k=w=>{x(En(w.sourceId,w.path,t.shape)),f(!1),g("")};return i.jsxs("div",{"data-testid":`g7le-binding-row-${t.propKey}`,style:OA,children:[i.jsxs("div",{style:BA,children:[i.jsx("span",{style:{fontWeight:600,fontSize:12},children:m}),i.jsx("span",{style:WA,children:h}),t.required&&i.jsx("span",{"data-testid":`g7le-binding-required-${t.propKey}`,style:jA,children:o("layout_editor.binding.required")})]}),c?i.jsxs("div",{"data-testid":`g7le-binding-complex-${t.propKey}`,style:VA,children:[o("layout_editor.binding.complex"),i.jsx("code",{style:Df,children:l})]}):d?i.jsxs("div",{style:HA,children:[i.jsx("code",{"data-testid":`g7le-binding-expr-${t.propKey}`,style:Df,children:l}),i.jsx("button",{type:"button","data-testid":`g7le-binding-clear-${t.propKey}`,onClick:()=>x(void 0),style:KA,children:o("layout_editor.binding.clear")})]}):i.jsx("div",{"data-testid":`g7le-binding-empty-${t.propKey}`,style:FA,children:o("layout_editor.binding.not_connected")}),d&&(t.shape==="array"||t.shape==="object")&&t.itemFields&&t.itemFields.length>0&&i.jsxs("div",{style:UA,children:[o("layout_editor.binding.item_fields"),": ",t.itemFields.join(", ")]}),!c&&i.jsxs("div",{style:{marginTop:4},children:[i.jsxs("button",{type:"button","data-testid":`g7le-binding-search-toggle-${t.propKey}`,onClick:()=>f(w=>!w),style:qA,children:["🔍 ",o("layout_editor.binding.search")]}),p&&i.jsxs("div",{"data-testid":`g7le-binding-picker-${t.propKey}`,style:GA,children:[i.jsx("input",{type:"text",autoFocus:!0,"data-testid":`g7le-binding-search-input-${t.propKey}`,value:y,placeholder:o("layout_editor.binding.search_placeholder"),onChange:w=>g(w.target.value),style:YA}),i.jsx("div",{style:XA,children:b.length===0?i.jsx("div",{style:JA,children:o("layout_editor.binding.no_results")}):b.map(w=>i.jsxs("button",{type:"button","data-testid":`g7le-binding-candidate-${w.expression}`,onClick:()=>k(w),style:ZA,children:[i.jsxs("span",{style:{display:"flex",flexDirection:"column",alignItems:"flex-start"},children:[i.jsx("span",{style:{fontSize:12},children:w.resolvedLabel}),i.jsxs("span",{style:QA,children:[Aa(o,w.groupLabelKey,w.sourceId)," · ",w.path||w.sourceId]})]}),i.jsx("span",{style:RA,children:w.preview})]},w.expression))})]})]})]})}function MA({node:e,dataProps:t,candidates:n,t:o,onPatchNode:r}){return!Array.isArray(t)||t.length===0?null:i.jsxs("div",{"data-testid":"g7le-data-binding-section",style:LA,children:[i.jsxs("div",{style:PA,children:["🔗 ",o("layout_editor.binding.section_title")]}),t.map(s=>i.jsx(zA,{node:e,spec:s,candidates:n,t:o,onPatchNode:r},s.propKey)),i.jsx("div",{style:e6})]})}const LA={display:"flex",flexDirection:"column",marginBottom:8},PA={fontSize:12,fontWeight:700,color:"#0f172a",marginBottom:6},OA={display:"flex",flexDirection:"column",gap:4,padding:"8px 0"},BA={display:"flex",alignItems:"center",gap:6},WA={fontSize:10,padding:"1px 6px",borderRadius:4,background:"#e0e7ff",color:"#3730a3"},jA={fontSize:10,padding:"1px 6px",borderRadius:4,background:"#fef3c7",color:"#92400e"},HA={display:"flex",alignItems:"center",gap:6},FA={fontSize:11,color:"#94a3b8"},Df={fontSize:11,fontFamily:"monospace",background:"#f1f5f9",padding:"2px 6px",borderRadius:4,color:"#0f172a",wordBreak:"break-all"},KA={fontSize:11,border:"1px solid #e2e8f0",borderRadius:4,background:"#fff",color:"#dc2626",padding:"2px 8px",cursor:"pointer"},VA={display:"flex",flexDirection:"column",gap:2,fontSize:11,color:"#64748b"},UA={fontSize:10,color:"#94a3b8"},qA={fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#334155",padding:"4px 10px",cursor:"pointer",textAlign:"left",width:"100%"},GA={marginTop:4,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",padding:6},YA={width:"100%",padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,boxSizing:"border-box"},XA={marginTop:4,maxHeight:200,overflowY:"auto",display:"flex",flexDirection:"column",gap:2},JA={fontSize:11,color:"#94a3b8",padding:"8px 0",textAlign:"center"},ZA={display:"flex",alignItems:"center",justifyContent:"space-between",gap:8,border:"none",background:"transparent",cursor:"pointer",padding:"6px 8px",borderRadius:4,textAlign:"left"},QA={fontSize:10,color:"#94a3b8"},RA={fontSize:10,color:"#64748b",fontFamily:"monospace",whiteSpace:"nowrap"},e6={borderTop:"1px solid #e2e8f0",marginTop:4};function t6({node:e,candidates:t,t:n,onPatchNode:o,templateIdentifier:r}){const s=typeof e.text=="string"?e.text:"",{state:a}=Je(),l=r??a.templateIdentifier,d=a.selectedRoute?.layoutName??null,c=a.locale,[p,f]=u.useState(!1),y=/^\s*\$t:custom\.[A-Za-z0-9._-]+\s*$/.test(s),m=Zl(s)&&(mt(s)?.key.startsWith("custom.")??!1)||y,h=u.useMemo(()=>m?mt(s):null,[m,s]),v=u.useMemo(()=>m?[]:Fl(s),[m,s]),b=N=>{o({...e,text:N})},x=()=>{if(!(typeof window>"u"))try{window.dispatchEvent(new CustomEvent(kt,{detail:{templateIdentifier:l,locale:c}}))}catch{}},k=(N,$)=>{const A=En($.sourceId,$.path,"scalar");b(_b(s,N,A))},w=N=>{const $=mt(s);b(mo(s,N));const A=$?.key;l&&A&&Fd(l,A,N).then(x)},_=N=>{const $=n(N);return $&&$!==N&&!$.startsWith("$t:")?$:""},S=N=>N.replace(/\$t:[a-zA-Z0-9._-]+/g,$=>_($.slice(3))).replace(/\s+/g," ").trim(),E=N=>{p||(f(!0),ls(l,d,c,s,s.length,N.sourceId,N.path,"scalar",S,$=>n($)).then($=>{$.kind==="ok"&&(b($.text),x())}).finally(()=>f(!1)))},C=N=>{p||!h&&!y||(f(!0),wr(l,c,s,Number.MAX_SAFE_INTEGER,N.sourceId,N.path,"scalar").then($=>{$.kind==="ok"&&(b($.text),x())}).finally(()=>f(!1)))};if(m){const N=h?.params??[];return i.jsxs("div",{"data-testid":"g7le-inline-binding-section",style:Nf,children:[i.jsxs("div",{style:zf,children:["🔗 ",n("layout_editor.inline_binding.section_title")]}),N.length===0?i.jsx("div",{"data-testid":"g7le-inline-binding-none",style:Pf,children:n("layout_editor.inline_binding.none")}):N.map($=>i.jsx("div",{"data-testid":`g7le-inline-binding-param-${$.name}`,style:Mf,children:$.parsed===null?i.jsxs("div",{"data-testid":`g7le-inline-binding-param-complex-${$.name}`,style:o6,children:[n("layout_editor.inline_binding.complex"),i.jsx("code",{style:Ta,children:$.expression})]}):i.jsxs(i.Fragment,{children:[i.jsxs("div",{style:Lf,children:[i.jsx("code",{"data-testid":`g7le-inline-binding-param-expr-${$.name}`,style:Ta,children:$.expression}),i.jsx("button",{type:"button","data-testid":`g7le-inline-binding-param-clear-${$.name}`,onClick:()=>w($.name),style:n6,children:n("layout_editor.inline_binding.clear")})]}),i.jsx($t,{candidates:t,t:n,onSelect:A=>k($.name,A),testIdSuffix:`param-${$.name}`})]})},$.name)),i.jsxs("div",{"data-testid":"g7le-inline-binding-param-append",style:Of,children:[i.jsx("div",{style:Da,children:n("layout_editor.inline_binding.append_hint")}),i.jsx($t,{candidates:t,t:n,onSelect:C,testIdSuffix:"param-append"})]}),i.jsx("div",{"data-testid":"g7le-inline-binding-param-hint",style:Da,children:n("layout_editor.inline_binding.param_hint")}),i.jsx("div",{style:Bf})]})}return i.jsxs("div",{"data-testid":"g7le-inline-binding-section",style:Nf,children:[i.jsxs("div",{style:zf,children:["🔗 ",n("layout_editor.inline_binding.section_title")]}),v.length===0?i.jsx("div",{"data-testid":"g7le-inline-binding-none",style:Pf,children:n("layout_editor.inline_binding.none")}):v.map(N=>i.jsx("div",{"data-testid":`g7le-inline-binding-row-${N.bindingIndex}`,style:Mf,children:i.jsx("div",{style:Lf,children:i.jsx("code",{"data-testid":`g7le-inline-binding-expr-${N.bindingIndex}`,style:Ta,children:N.expression})})},N.bindingIndex)),i.jsxs("div",{"data-testid":"g7le-inline-binding-append",style:Of,children:[i.jsx("div",{style:Da,children:n("layout_editor.inline_binding.append_hint")}),i.jsx($t,{candidates:t,t:n,onSelect:E,testIdSuffix:"append"})]}),i.jsx("div",{style:Bf})]})}const Nf={display:"flex",flexDirection:"column",marginBottom:8},zf={fontSize:12,fontWeight:700,color:"#0f172a",marginBottom:6},Mf={display:"flex",flexDirection:"column",gap:4,padding:"8px 0"},Lf={display:"flex",alignItems:"center",gap:6},Pf={fontSize:11,color:"#94a3b8"},Ta={fontSize:11,fontFamily:"monospace",background:"#f1f5f9",padding:"2px 6px",borderRadius:4,color:"#0f172a",wordBreak:"break-all"},n6={fontSize:11,border:"1px solid #e2e8f0",borderRadius:4,background:"#fff",color:"#dc2626",padding:"2px 8px",cursor:"pointer"},o6={display:"flex",flexDirection:"column",gap:2,fontSize:11,color:"#64748b"},Of={display:"flex",flexDirection:"column",gap:2,padding:"8px 0 0"},Da={fontSize:11,color:"#475569",fontWeight:600},Bf={borderTop:"1px solid #e2e8f0",marginTop:4};function Wf(e,t,n){if(!t)return n;const o=e(t);return!o||o===t||o.startsWith("$t:")?n:o}function r6(e,t){return Wf(t,e.labelKey,e.path||e.sourceId)}function i6(e){const t=e.iteration;return t&&typeof t=="object"?t:null}function s6({node:e,candidates:t,t:n,onPatchNode:o}){const r=i6(e),s=r?.source,a=typeof s=="string"?s:void 0,l=u.useMemo(()=>Kt(s),[s]),d=a!==void 0&&a!==""&&l===null,[c,p]=u.useState(!1),[f,y]=u.useState(""),g=u.useMemo(()=>Fi(t,"array").map(w=>({...w,resolvedLabel:r6(w,n)})),[t,n]),m=u.useMemo(()=>Ki(g,f),[g,f]);if(!r)return null;const h=typeof r.item_var=="string"?r.item_var:void 0,v=typeof r.index_var=="string"?r.index_var:void 0,b=k=>{const w={...r};k===void 0?delete w.source:w.source=k,o({...e,iteration:w})},x=k=>{b(En(k.sourceId,k.path,"array")),p(!1),y("")};return i.jsxs("div",{"data-testid":"g7le-iteration-binding-section",style:a6,children:[i.jsxs("div",{style:l6,children:["🔁 ",n("layout_editor.binding.iteration_title")]}),i.jsx("div",{style:d6,children:n("layout_editor.binding.iteration_hint")}),i.jsxs("div",{style:c6,children:[i.jsxs("div",{style:u6,children:[i.jsx("span",{style:{fontWeight:600,fontSize:12},children:n("layout_editor.binding.iteration_label")}),i.jsx("span",{style:p6,children:n("layout_editor.binding.shape_array")})]}),d?i.jsxs("div",{"data-testid":"g7le-iteration-binding-complex",style:y6,children:[n("layout_editor.binding.complex"),i.jsx("code",{style:jf,children:a})]}):l?i.jsxs("div",{style:f6,children:[i.jsx("code",{"data-testid":"g7le-iteration-binding-expr",style:jf,children:a}),i.jsx("button",{type:"button","data-testid":"g7le-iteration-binding-clear",onClick:()=>b(void 0),style:h6,children:n("layout_editor.binding.clear")})]}):i.jsx("div",{"data-testid":"g7le-iteration-binding-empty",style:g6,children:n("layout_editor.binding.not_connected")}),(h||v)&&i.jsxs("div",{"data-testid":"g7le-iteration-vars",style:b6,children:[h&&i.jsxs("span",{children:[n("layout_editor.binding.iteration_item_var"),": ",i.jsx("code",{style:Hf,children:h})]}),v&&i.jsxs("span",{children:[n("layout_editor.binding.iteration_index_var"),": ",i.jsx("code",{style:Hf,children:v})]})]}),!d&&i.jsxs("div",{style:{marginTop:4},children:[i.jsxs("button",{type:"button","data-testid":"g7le-iteration-binding-search-toggle",onClick:()=>p(k=>!k),style:m6,children:["🔍 ",n("layout_editor.binding.search")]}),c&&i.jsxs("div",{"data-testid":"g7le-iteration-binding-picker",style:v6,children:[i.jsx("input",{type:"text",autoFocus:!0,"data-testid":"g7le-iteration-binding-search-input",value:f,placeholder:n("layout_editor.binding.search_placeholder"),onChange:k=>y(k.target.value),style:_6}),i.jsx("div",{style:x6,children:m.length===0?i.jsx("div",{style:w6,children:n("layout_editor.binding.no_results")}):m.map(k=>i.jsxs("button",{type:"button","data-testid":`g7le-iteration-binding-candidate-${k.expression}`,onClick:()=>x(k),style:k6,children:[i.jsxs("span",{style:{display:"flex",flexDirection:"column",alignItems:"flex-start"},children:[i.jsx("span",{style:{fontSize:12},children:k.resolvedLabel}),i.jsxs("span",{style:S6,children:[Wf(n,k.groupLabelKey,k.sourceId)," · ",k.path||k.sourceId]})]}),i.jsx("span",{style:C6,children:k.preview})]},k.expression))})]})]})]}),i.jsx("div",{style:$6})]})}const a6={display:"flex",flexDirection:"column",marginBottom:8},l6={fontSize:12,fontWeight:700,color:"#0f172a",marginBottom:4},d6={fontSize:10,color:"#94a3b8",marginBottom:6,lineHeight:1.4},c6={display:"flex",flexDirection:"column",gap:4,padding:"8px 0"},u6={display:"flex",alignItems:"center",gap:6},p6={fontSize:10,padding:"1px 6px",borderRadius:4,background:"#e0e7ff",color:"#3730a3"},f6={display:"flex",alignItems:"center",gap:6},g6={fontSize:11,color:"#94a3b8"},jf={fontSize:11,fontFamily:"monospace",background:"#f1f5f9",padding:"2px 6px",borderRadius:4,color:"#0f172a",wordBreak:"break-all"},h6={fontSize:11,border:"1px solid #e2e8f0",borderRadius:4,background:"#fff",color:"#dc2626",padding:"2px 8px",cursor:"pointer"},y6={display:"flex",flexDirection:"column",gap:2,fontSize:11,color:"#64748b"},b6={display:"flex",gap:12,fontSize:10,color:"#94a3b8",marginTop:2},Hf={fontSize:10,fontFamily:"monospace",background:"#f1f5f9",padding:"1px 4px",borderRadius:3,color:"#475569"},m6={fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#334155",padding:"4px 10px",cursor:"pointer",textAlign:"left",width:"100%"},v6={marginTop:4,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",padding:6},_6={width:"100%",padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,boxSizing:"border-box"},x6={marginTop:4,maxHeight:200,overflowY:"auto",display:"flex",flexDirection:"column",gap:2},w6={fontSize:11,color:"#94a3b8",padding:"8px 0",textAlign:"center"},k6={display:"flex",alignItems:"center",justifyContent:"space-between",gap:8,border:"none",background:"transparent",cursor:"pointer",padding:"6px 8px",borderRadius:4,textAlign:"left"},S6={fontSize:10,color:"#94a3b8"},C6={fontSize:10,color:"#64748b",fontFamily:"monospace",whiteSpace:"nowrap"},$6={borderTop:"1px solid #e2e8f0",marginTop:4},E6=new Set(["Span","P","H1","H2","H3","H4","H5","H6","A","Button","Li","Label"]);function I6(e,t){if(!e||e.iteration&&typeof e.iteration=="object")return!1;const n=t?.textBinding;if(n===!1)return!1;if(n===!0)return!0;const o=typeof e.name=="string"?e.name:"";return E6.has(o)?!0:typeof e.text=="string"}const yn={direction:"flexDirection",justify:"flexJustify",align:"flexAlign",wrap:"flexWrap",gap:"flexGap",enable:"flexEnable"},hi={grow:"flexItemGrow",selfAlign:"flexItemAlign",order:"flexItemOrder"};function Ff(e){const t=e?.flexEditor;return t==="container"||t==="item"||t==="auto"?t:null}function A6(e){if(!e||typeof window>"u"||typeof window.getComputedStyle!="function")return!1;const t=(window.getComputedStyle(e).display??"").toLowerCase();return t==="flex"||t==="inline-flex"}function T6(e,t,n=Wt){if(!e||!t)return!1;const o=Do(e,t,n),r=t.options,s=Array.isArray(r)?t.onValue??r[0]?.value:void 0,a=l=>s!==void 0?l===s:l!==void 0&&l!==!1;return n.breakpoint!=="base"?a(o.scopedRawValue):o.matched&&a(o.value)}function D6(e,t,n=!1,o=!1,r=Wt){if(e==="container")return{showContainer:!0,showEnableButton:!1,showDisableButton:!1};if(e==="item")return{showContainer:!1,showEnableButton:!1,showDisableButton:!1};if(e==="auto"){const s=r.breakpoint==="base";return!o&&!s?{showContainer:!1,showEnableButton:!1,showDisableButton:!1}:(o?n:A6(t))?{showContainer:!0,showEnableButton:!1,showDisableButton:o}:{showContainer:!1,showEnableButton:!0,showDisableButton:!1}}return{showContainer:!1,showEnableButton:!1,showDisableButton:!1}}const N6={[yn.direction]:"layout_editor.flex.direction",[yn.justify]:"layout_editor.flex.justify",[yn.align]:"layout_editor.flex.align",[yn.wrap]:"layout_editor.flex.wrap",[yn.gap]:"layout_editor.flex.gap"},z6={[hi.grow]:"layout_editor.flex.item_grow",[hi.selfAlign]:"layout_editor.flex.item_align",[hi.order]:"layout_editor.flex.item_order"};function M6({node:e,spec:t,capability:n,t:o,onPatchNode:r,liveElement:s,scope:a=Wt}){const l=Ff(n),d=Ct(t,yn.enable),c=T6(e,d,a),{showContainer:p,showEnableButton:f,showDisableButton:y}=D6(l,s,c,!!d,a),g=Object.values(yn).filter(_=>_!==yn.enable&&Ct(t,_)),m=Object.values(hi).filter(_=>Ct(t,_)),h=d?.options,v=d?.onValue??h?.[0]?.value??!0,b=Array.isArray(h)&&h.length>=2?h[1].value:void 0,x=()=>{if(!d)return;const _=fn(e,d,v,a);r(_)},k=()=>{if(!d)return;const _=a.breakpoint!=="base"&&b!==void 0?b:void 0,S=fn(e,d,_,a);r(S)},w=(_,S,E)=>i.jsx("div",{"data-testid":E,style:{display:"flex",flexDirection:"column"},children:_.map(C=>{const N=Ct(t,C);if(!N)return null;const $=N.label?N:{...N,label:`$t:${S[C]}`};return i.jsx(pi,{controlKey:C,control:$,node:e,t:o,onPatch:r,scope:a},C)})});return i.jsxs("div",{className:"g7le-flex-editor","data-testid":"g7le-flex-editor",style:{display:"flex",flexDirection:"column",gap:12},children:[f&&i.jsx("button",{type:"button","data-testid":"g7le-flex-enable",onClick:x,style:L6,children:o("layout_editor.flex.make_flex")}),p&&g.length>0&&i.jsxs("div",{className:"g7le-flex-container-section","data-testid":"g7le-flex-container-section",children:[i.jsx("div",{style:Kf,children:o("layout_editor.flex.container_title")}),w(g,N6,"g7le-flex-container-controls"),y&&i.jsx("button",{type:"button","data-testid":"g7le-flex-disable",onClick:k,style:P6,children:o("layout_editor.flex.unmake_flex")})]}),l==="item"&&m.length>0&&i.jsxs("div",{className:"g7le-flex-item-section","data-testid":"g7le-flex-item-section",children:[i.jsx("div",{style:Kf,children:o("layout_editor.flex.item_title")}),w(m,z6,"g7le-flex-item-controls")]}),!p&&!f&&l!=="item"&&i.jsx("div",{"data-testid":"g7le-flex-empty",style:O6,children:o("layout_editor.flex.not_flex")})]})}const Kf={fontSize:12,fontWeight:700,color:"#0f172a",marginBottom:6},L6={padding:"8px 12px",fontSize:12,border:"1px dashed #2563eb",borderRadius:6,background:"#eff6ff",color:"#2563eb",cursor:"pointer"},P6={marginTop:8,padding:"6px 10px",fontSize:12,border:"1px solid #e2e8f0",borderRadius:6,background:"#f8fafc",color:"#64748b",cursor:"pointer"},O6={fontSize:12,color:"#94a3b8",padding:"16px 0",textAlign:"center"},Vf=["desktop","tablet","mobile","portable"],Na=1280,B6=820,W6=390,j6=1920,H6=320;function F6(e){const t=[],n=new Set,o=r=>{if(!r||typeof r!="object")return;const s=r.responsive;if(s&&typeof s=="object")for(const a of Object.keys(s)){n.has(a)||(n.add(a),t.push(a));const l=s[a]?.children;if(Array.isArray(l))for(const d of l)o(d)}if(Array.isArray(r.children))for(const a of r.children)o(a)};if(Array.isArray(e))for(const r of e)o(r);return t}function K6(e){const t=[...Vf],n=new Set(Vf);for(const o of F6(e))!n.has(o)&&!t.includes(o)&&t.push(o);return t}function V6(e,t){const n=e?.responsive;if(!n||typeof n!="object")return[];const o=[];for(const r of Object.keys(n)){const s=n[r];!s||!Array.isArray(s.children)||r!==t&&o.push(r)}return o}function U6(e){switch(e){case"desktop":return Na;case"tablet":return B6;case"mobile":return W6}const t=e in To?To[e]:Mn.parseRange(e);return!t||!Number.isFinite(t.max)?Na:Math.round(Math.max(H6,Math.min(j6,t.max)))}function Uf(){const t=Ft()?.document?.raw?.components;return u.useMemo(()=>K6(t??null),[t])}const q6={desktop:"layout_editor.property_modal.scope.device_desktop",tablet:"layout_editor.property_modal.scope.device_tablet",mobile:"layout_editor.property_modal.scope.device_mobile",portable:"layout_editor.property_modal.scope.device_portable"},G6=[{key:"base",labelKey:"layout_editor.property_modal.scope.scheme_light"},{key:"dark",labelKey:"layout_editor.property_modal.scope.scheme_dark"}];function qf({scope:e,onChange:t,node:n,t:o,showColorScheme:r=!0,onClearScope:s}){const[a,l]=u.useState(!1),[d,c]=u.useState(""),[p,f]=u.useState(""),[y,g]=u.useState(null),m=Uf(),h=u.useMemo(()=>{const k=["base",...m],w=new Set(k);for(const _ of Object.keys(n.responsive??{}))_!=="base"&&!w.has(_)&&(w.add(_),k.push(_));return e.breakpoint!=="base"&&!w.has(e.breakpoint)&&k.push(e.breakpoint),k},[m,n.responsive,e.breakpoint]),v=k=>t({...e,colorScheme:k}),b=k=>t({...e,breakpoint:k}),x=()=>{const k=d.trim(),w=p.trim();if(k===""&&w===""){g(o("layout_editor.property_modal.scope.custom_invalid"));return}const _=`${k}-${w}`;if(!pC(_)){g(o("layout_editor.property_modal.scope.custom_invalid"));return}g(null),l(!1),c(""),f(""),b(_)};return i.jsxs("div",{className:"g7le-style-scope-tabs","data-testid":"g7le-style-scope-tabs",style:Y6,children:[r&&i.jsx("div",{className:"g7le-style-scope-scheme","data-testid":"g7le-style-scope-scheme",role:"tablist","aria-label":o("layout_editor.property_modal.scope.scheme_light"),style:Gf,children:G6.map(k=>{const w=e.colorScheme===k.key;return i.jsxs("button",{type:"button",role:"tab","aria-selected":w,"data-testid":`g7le-style-scheme-${k.key}`,"data-active":w?"true":"false",onClick:()=>v(k.key),style:Yf(w),children:[o(k.labelKey),k.key==="dark"&&Gs(n,{colorScheme:"dark",breakpoint:e.breakpoint})&&i.jsx("span",{"data-testid":"g7le-style-scheme-dark-dot",style:Xf})]},k.key)})}),i.jsxs("div",{className:"g7le-style-scope-device","data-testid":"g7le-style-scope-device",role:"tablist","aria-label":o("layout_editor.property_modal.scope.device_base"),style:Gf,children:[h.map(k=>{const w=e.breakpoint===k,_=k==="base"||wu(k),S=k==="base"?"layout_editor.property_modal.scope.device_base":q6[k],E=S?o(S):k,C=_?`g7le-style-bp-${k}`:`g7le-style-bp-custom-${k}`;return i.jsxs("button",{type:"button",role:"tab","aria-selected":w,"data-testid":C,"data-active":w?"true":"false",onClick:()=>b(k),style:Yf(w),children:[E,Gs(n,{colorScheme:"base",breakpoint:k})&&i.jsx("span",{"data-testid":`${C}-dot`,style:Xf})]},String(k))}),i.jsxs("button",{type:"button","data-testid":"g7le-style-bp-add-custom",onClick:()=>l(k=>!k),style:Z6,children:["+ ",o("layout_editor.property_modal.scope.add_custom")]})]}),a&&i.jsxs("div",{className:"g7le-style-scope-custom-input","data-testid":"g7le-style-scope-custom-input",style:X6,children:[i.jsx("input",{type:"number","data-testid":"g7le-style-custom-min",placeholder:o("layout_editor.property_modal.scope.custom_min"),value:d,onChange:k=>c(k.target.value),style:Jf}),i.jsx("span",{style:{color:"#94a3b8"},children:"—"}),i.jsx("input",{type:"number","data-testid":"g7le-style-custom-max",placeholder:o("layout_editor.property_modal.scope.custom_max"),value:p,onChange:k=>f(k.target.value),style:Jf}),i.jsx("button",{type:"button","data-testid":"g7le-style-custom-confirm",onClick:x,style:Q6,children:o("layout_editor.property_modal.scope.add_custom")})]}),y&&i.jsx("div",{"data-testid":"g7le-style-custom-error",style:R6,children:y}),e.breakpoint!=="base"&&!wu(e.breakpoint)&&i.jsx("div",{"data-testid":"g7le-style-custom-priority-note",style:eT,children:o("layout_editor.property_modal.scope.custom_priority_note")}),s&&Gs(n,e)&&i.jsxs("button",{type:"button","data-testid":"g7le-style-scope-reset",onClick:s,style:J6,children:["↺ ",o("layout_editor.property_modal.scope.reset_to_base")]})]})}const Y6={display:"flex",flexDirection:"column",gap:0,padding:"0 0 8px",borderBottom:"1px solid #e2e8f0",marginBottom:8},X6={display:"flex",gap:6,alignItems:"center",marginTop:4},Gf={display:"flex",gap:2,flexWrap:"wrap",borderBottom:"1px solid #e2e8f0",margin:"0 -16px",padding:"0 12px"};function Yf(e){return{display:"inline-flex",alignItems:"center",gap:4,padding:"6px 12px",fontSize:12,border:"none",borderBottom:`2px solid ${e?"#2563eb":"transparent"}`,background:"transparent",color:e?"#2563eb":"#64748b",cursor:"pointer",fontWeight:e?700:500,marginBottom:-1}}const Xf={display:"inline-block",width:5,height:5,borderRadius:3,background:"#f59e0b"},J6={alignSelf:"flex-start",marginTop:2,padding:"3px 8px",fontSize:10,border:"1px solid #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#475569",cursor:"pointer"},Z6={padding:"6px 12px",fontSize:11,border:"none",borderBottom:"2px dashed #cbd5e1",background:"transparent",color:"#475569",cursor:"pointer",marginBottom:-1},Jf={width:70,padding:"4px 6px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6},Q6={padding:"4px 10px",fontSize:11,border:"none",borderRadius:6,background:"#2563eb",color:"#fff",cursor:"pointer"},R6={fontSize:11,color:"#dc2626"},eT={fontSize:10,color:"#94a3b8"},Zf=new Map;function Pn(e,t){Zf.set(e,t)}function tT(e){return e?Zf.get(e)??null:null}const za={id:{label:"$t:layout_editor.core_props.id.label",widget:"core-id",placeholder:"$t:layout_editor.core_props.id.placeholder",apply:{type:"propValue",propKey:"id"}},dataKey:{label:"$t:layout_editor.core_props.dataKey.label",widget:"core-datakey",placeholder:"$t:layout_editor.core_props.dataKey.placeholder",apply:{type:"nodeKey",nodeKey:"dataKey"}},isolatedState:{label:"$t:layout_editor.core_props.isolatedState.label",widget:"toggle",apply:{type:"nodeKey",nodeKey:"isolatedState"}},isolatedScopeId:{label:"$t:layout_editor.core_props.isolatedScopeId.label",widget:"core-scopeid",placeholder:"$t:layout_editor.core_props.isolatedScopeId.placeholder",apply:{type:"nodeKey",nodeKey:"isolatedScopeId"}}},nT=["id"];function oT(e){return e===!1?[]:Array.isArray(e)?e.filter(t=>t in za):[...nT]}function Qf(e){const t=e.props?.id??e.id;return typeof t=="string"&&t.length>0?`${t}-scope`:"scope"}function rT({node:e,onPatchNode:t,t:n,scopeIdCandidates:o=[],usedScopeIds:r=[]}){const s=e,a="isolatedState"in s&&s.isolatedState!=null,l=typeof s.isolatedScopeId=="string"?s.isolatedScopeId:"",[d,c]=u.useState(""),p=()=>{const h={...e};a?(delete h.isolatedState,delete h.isolatedScopeId):(h.isolatedState={},h.isolatedScopeId=Qf(e)),t(h)},f=h=>{const v={...e};v.isolatedScopeId=h.trim()===""?Qf(e):h,t(v)},y=h=>{const v={...e};v.isolatedState=h,t(v)},g=u.useMemo(()=>{const h=d.trim().toLowerCase();return o.filter(v=>!h||v.toLowerCase().includes(h))},[o,d]),m=l!==""&&r.includes(l);return i.jsxs("div",{className:"g7le-isolated-scope","data-testid":"g7le-isolated-scope-control",style:iT,children:[i.jsx("div",{style:sT,children:n("layout_editor.core_props.isolatedState.label")}),i.jsxs("div",{style:aT,children:[i.jsx("button",{type:"button","data-testid":"g7le-isolated-toggle",role:"switch","aria-checked":a,onClick:p,style:lT(a),children:n(a?"layout_editor.overlay.on":"layout_editor.overlay.off")}),a?null:i.jsx("span",{"data-testid":"g7le-isolated-off-hint",style:dT,children:n("layout_editor.core_props.isolatedState.off_hint")})]}),a?i.jsxs(i.Fragment,{children:[i.jsxs("div",{style:Rf,children:[i.jsx("label",{style:eg,children:n("layout_editor.core_props.isolatedScopeId.label")}),i.jsx("input",{type:"text","data-testid":"g7le-isolated-scopeid-input",value:l,placeholder:n("layout_editor.core_props.isolatedScopeId.placeholder"),onChange:h=>f(h.target.value),style:cT}),g.length>0?i.jsxs("div",{"data-testid":"g7le-isolated-scopeid-picker",style:uT,children:[i.jsx("input",{type:"text","data-testid":"g7le-isolated-scopeid-search",value:d,placeholder:n("layout_editor.core_props.isolatedScopeId.search"),onChange:h=>c(h.target.value),style:pT}),i.jsx("div",{style:fT,children:g.map(h=>i.jsx("button",{type:"button","data-testid":`g7le-isolated-scopeid-candidate-${h}`,onClick:()=>{f(h),c("")},style:gT,children:h},h))})]}):null,m?i.jsxs("p",{"data-testid":"g7le-isolated-scopeid-dup",style:hT,children:["ⓘ ",n("layout_editor.core_props.isolatedScopeId.duplicate")]}):null]}),i.jsxs("div",{style:Rf,children:[i.jsx("label",{style:eg,children:n("layout_editor.core_props.isolatedState.initial_label")}),i.jsx("div",{"data-testid":"g7le-isolated-initial-value",children:i.jsx(Nn,{value:s.isolatedState??{},onChange:y,t:n,path:"isolatedState",scope:"isolated-node"})})]})]}):null]})}const iT={display:"flex",flexDirection:"column",gap:8,padding:10,border:"1px solid #e2e8f0",borderRadius:8,minWidth:0},sT={fontSize:12,fontWeight:700,color:"#0f172a"},aT={display:"flex",gap:8,alignItems:"center"},lT=e=>({padding:"4px 12px",fontSize:11,border:`1px solid ${e?"#3b82f6":"#cbd5e1"}`,borderRadius:14,background:e?"#eff6ff":"#fff",color:e?"#1d4ed8":"#64748b",cursor:"pointer"}),dT={fontSize:11,color:"#94a3b8"},Rf={display:"flex",flexDirection:"column",gap:4,minWidth:0},eg={fontSize:11,color:"#64748b"},cT={padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,boxSizing:"border-box",width:"100%"},uT={border:"1px solid #e2e8f0",borderRadius:6,padding:4,background:"#fff"},pT={width:"100%",padding:"4px 6px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,marginBottom:4,boxSizing:"border-box"},fT={display:"flex",flexDirection:"column",maxHeight:120,overflowY:"auto"},gT={textAlign:"left",padding:"4px 6px",fontSize:12,border:"none",background:"transparent",cursor:"pointer",borderRadius:4,color:"#0f172a"},hT={margin:0,fontSize:11,color:"#64748b"},yT={borderTop:"2px solid #c7d2fe",background:"#eef2ff",padding:"10px 12px",display:"flex",flexDirection:"column",gap:8},tg={fontSize:13,fontWeight:700,color:"#3730a3",display:"flex",alignItems:"center",gap:6},bT={fontSize:11,fontWeight:600,color:"#4338ca",background:"#e0e7ff",border:"1px solid #c7d2fe",borderRadius:4,padding:"1px 6px"},Ma={fontSize:11,color:"#6366f1"},mT={alignSelf:"flex-start",fontSize:12,fontWeight:600,color:"#fff",background:"#4f46e5",border:"none",borderRadius:4,padding:"4px 10px",cursor:"pointer"},vT={opacity:.5,cursor:"not-allowed"},_T={fontSize:11,color:"#dc2626"};function xT({entry:e,t,onSave:n}){const[o,r]=u.useState(()=>e.props),[s,a]=u.useState(!0),[l,d]=u.useState(null),[c,p]=u.useState(!1),[f,y]=u.useState(!1),g=e.extensionName??e.extensionIdentifier??`#${e.extensionId}`,m=async()=>{if(!s){d(t("layout_editor.property_modal.injected_props.invalid_json"));return}d(null),p(!0),y(!1);try{await n(o),y(!0)}catch(h){d(h instanceof Error?h.message:String(h))}finally{p(!1)}};return i.jsxs("div",{"data-testid":`g7le-injected-props-entry-${e.extensionId}`,style:{display:"flex",flexDirection:"column",gap:6},children:[i.jsx("div",{style:tg,children:i.jsx("span",{style:bT,"data-testid":"g7le-injected-props-origin-badge",children:t("layout_editor.property_modal.injected_props.origin",{source:g})})}),i.jsx(Bs,{value:o,shape:"object",emptyValue:{},testidPrefix:"g7le-injected-props-json",minHeight:80,invalidErrorKey:"layout_editor.property_modal.injected_props.invalid_json",shapeErrorKey:"layout_editor.property_modal.injected_props.invalid_json",t,onChange:h=>{r(h??{}),y(!1)},onValidityChange:h=>{a(h),h&&d(null),y(!1)}}),l&&i.jsx("div",{style:_T,"data-testid":"g7le-injected-props-error",children:l}),f&&i.jsx("div",{style:{...Ma,color:"#16a34a"},"data-testid":"g7le-injected-props-saved",children:t("layout_editor.property_modal.injected_props.saved")}),i.jsx("div",{style:Ma,children:t("layout_editor.property_modal.injected_props.save_target")}),i.jsx("button",{type:"button","data-testid":"g7le-injected-props-save",style:{...mT,...c||!s?vT:null},disabled:c||!s,onClick:m,children:c?t("layout_editor.property_modal.injected_props.saving"):t("layout_editor.property_modal.injected_props.save_button",{source:g})})]})}function wT({injectedProps:e,t,onSaveInjectedProps:n}){return!Array.isArray(e)||e.length===0?null:i.jsxs("div",{style:yT,"data-testid":"g7le-injected-props-section",children:[i.jsxs("div",{style:tg,children:["🧩 ",t("layout_editor.property_modal.injected_props.title")]}),i.jsx("div",{style:Ma,children:t("layout_editor.property_modal.injected_props.help")}),e.map(o=>i.jsx(xT,{entry:o,t,onSave:r=>n(o.extensionId,r)},o.extensionId))]})}function kT(e,t){const n=e?.components??{};for(const o of Object.values(n)){const r=o?.find(s=>s.name===t);if(r)return r}return null}function ST({node:e,spec:t,manifest:n,t:o,onPatchNode:r,onClose:s,onDelete:a,permissionCandidates:l,pageCandidates:d,dataSourceCandidates:c,stateKeyCandidates:p,bindingCandidates:f,liveElement:y,scope:g=Wt,onScopeChange:m,templateIdentifier:h,initialTab:v,onSaveInjectedProps:b,branchLabel:x,isolatedScopeIdCandidates:k,usedScopeIds:w}){const _=e.__injectedProps,S=Array.isArray(_)&&_.length>0&&!!b,E=ve=>{m?.(ve)},C=()=>{r(vC(e,g))},N=typeof e.name=="string"?e.name:"",$=Oi(t,N),z=kT(n,N)?.settings,D=!!(z?.groups&&z.groups.length>0),O=Array.isArray($?.propControls)?$.propControls:[],M=oT($?.coreProps),B=new Set(M.map(ve=>za[ve].apply?.propKey)),T=O.filter(ve=>{const he=Ct(t,ve)?.apply?.propKey;return!(he!==void 0&&B.has(he))}),H=typeof $?.nodeEditor?.kind=="string"?$.nodeEditor.kind:void 0,L=$?.nodeEditor?.params,I=H?tT(H):null,P=!!I,F=Array.isArray($?.dataProps)?$.dataProps:[],G=F.length>0,V=!!(e.iteration&&typeof e.iteration=="object"),Y=I6(e,$),Z=M.length>0||T.length>0||P||G||Y||V,se=Array.isArray($?.styleControls)?$.styleControls:[],re=Array.isArray($?.advanced)?$.advanced:[],ee=(Array.isArray($?.events)?$.events:[]).length>0,fe=Ff($),K=se.length>0||fe!==null,me=!($?.visibilityCondition===!1)&&(Z||K||ee||D||re.length>0),Ce=u.useMemo(()=>{const ve=[];return D&&ve.push("settings"),Z&&ve.push("props"),K&&ve.push("style"),ee&&ve.push("action"),me&&ve.push("visibility"),ve.push("translation"),re.length>0&&ve.push("advanced"),ve},[D,Z,K,ee,me,re.length]),ze=T.length>0||P||G||V,[Se,qe]=u.useState(v&&Ce.includes(v)?v:D?"settings":ze?"props":K?"style":Z?"props":Ce[0]??"style"),De=bA(e),Pe=!Z&&!K&&!D&&!ee&&!me&&re.length===0,Qe=$?.label?$.label.startsWith("$t:")?o($.label.slice(3)):$.label:N||o("layout_editor.property_modal.element"),Me=(ve,te)=>{const he={...e.props??{}};te===void 0?delete he[ve]:he[ve]=te,r({...e,props:he})},Re={settings:o("layout_editor.property_modal.tab.settings"),props:o("layout_editor.property_modal.tab.props"),style:o("layout_editor.property_modal.tab.style"),action:o("layout_editor.property_modal.tab.action"),visibility:o("layout_editor.property_modal.tab.visibility"),translation:o("layout_editor.property_modal.tab.translation"),advanced:o("layout_editor.property_modal.tab.advanced")};return i.jsxs("div",{className:"g7le-property-modal","data-testid":"g7le-property-modal",style:CT,children:[i.jsxs("div",{"data-modal-drag-handle":!0,"data-testid":"g7le-property-modal-header",style:$T,children:[i.jsx("span",{"data-testid":"g7le-property-modal-title",style:{fontSize:14,fontWeight:700},children:D&&Se==="settings"?o("layout_editor.property_modal.settings_title",{name:Qe}):o("layout_editor.property_modal.title",{name:Qe})}),i.jsx("button",{type:"button","data-testid":"g7le-property-modal-close","aria-label":"close",onClick:s,style:ET,children:"✕"})]}),De&&i.jsx("div",{"data-testid":"g7le-property-modal-advanced-badge",style:IT,children:o("layout_editor.property_modal.advanced_badge")}),x&&i.jsx("div",{"data-testid":"g7le-property-modal-branch-badge",style:AT,children:o("layout_editor.property_modal.branch_badge",{device:x})}),i.jsx("div",{style:TT,role:"tablist",children:Ce.map(ve=>i.jsx("button",{type:"button",role:"tab","aria-selected":Se===ve,"data-testid":`g7le-property-tab-${ve}`,"data-active":Se===ve?"true":"false",onClick:()=>qe(ve),style:{...DT,color:Se===ve?"#2563eb":"#64748b",borderBottomColor:Se===ve?"#2563eb":"transparent"},children:Re[ve]},ve))}),i.jsxs("div",{style:NT,"data-testid":"g7le-property-modal-body",children:[Pe&&!S&&i.jsx("div",{"data-testid":"g7le-property-modal-no-editable",style:og,children:o("layout_editor.property_modal.no_editable_props")}),S&&i.jsx(wT,{injectedProps:_,t:o,onSaveInjectedProps:b}),Se==="settings"&&z&&i.jsx(iA,{spec:z,node:e,t:o,onPatchProp:Me}),Se==="props"&&i.jsxs("div",{"data-testid":"g7le-property-props-tab",style:{display:"flex",flexDirection:"column"},children:[V&&i.jsx(s6,{node:e,candidates:f??[],t:o,onPatchNode:r}),G&&i.jsx(MA,{node:e,dataProps:F,candidates:f??[],t:o,onPatchNode:r}),Y&&i.jsx(t6,{node:e,candidates:f??[],t:o,onPatchNode:r,templateIdentifier:h}),P&&I&&i.jsx(I,{node:e,params:L,spec:t,manifest:n,t:o,onPatchNode:r,templateIdentifier:h,candidates:f}),M.map(ve=>i.jsx(pi,{controlKey:`core-${ve}`,control:za[ve],node:e,t:o,onPatch:r,scope:Wt,bindingCandidates:f},`core-${ve}`)),T.map(ve=>{const te=Ct(t,ve);return te?i.jsx(pi,{controlKey:ve,control:te,node:e,t:o,onPatch:r,scope:Wt,bindingCandidates:f},ve):null}),i.jsx(rT,{node:e,onPatchNode:r,t:o,scopeIdCandidates:k,usedScopeIds:w})]}),Se==="style"&&i.jsxs("div",{"data-testid":"g7le-property-style-tab",style:{display:"flex",flexDirection:"column"},children:[i.jsx("div",{style:ng,children:i.jsx(qf,{scope:g,onChange:E,node:e,t:o,onClearScope:C})}),fe!==null&&i.jsx(M6,{node:e,spec:t,capability:$,t:o,onPatchNode:r,liveElement:y,scope:g}),se.map(ve=>{const te=Ct(t,ve);return te?i.jsx(pi,{controlKey:ve,control:te,node:e,t:o,onPatch:r,scope:g},ve):null})]}),Se==="action"&&i.jsx(TA,{node:e,spec:t,capability:$,t:o,onPatchNode:r,pageCandidates:d,dataSourceCandidates:c,stateKeyCandidates:p,bindingCandidates:f}),Se==="visibility"&&i.jsxs("div",{"data-testid":"g7le-property-visibility-tab",style:{display:"flex",flexDirection:"column"},children:[i.jsx("div",{style:ng,children:i.jsx(qf,{scope:g,onChange:E,node:e,t:o,showColorScheme:!1})}),i.jsx(Eu,{node:e,spec:t,t:o,onPatchNode:r,dataSourceCandidates:c,stateKeyCandidates:p,scope:g})]}),Se==="translation"&&(h?i.jsx(Ud,{customKey:Vd(e),templateIdentifier:h,t:o,paramLabels:fv(e),onRemoveParam:ve=>{const te=typeof e.text=="string"?e.text:"";r({...e,text:mo(te,ve)});const he=Vd(e);h&&he&&kr(h,he,ve)}}):i.jsx("div",{"data-testid":"g7le-property-translation-deferred",style:og,children:o("layout_editor.translation.not_a_key")})),Se==="advanced"&&i.jsx(vA,{node:e,advanced:re,t:o,onPatch:r,permissionCandidates:l})]}),i.jsxs("div",{style:zT,children:[i.jsx("span",{"data-testid":"g7le-property-modal-node-id",style:{fontSize:11,color:"#94a3b8"},children:typeof e.id=="string"||typeof e.id=="number"?`ID: ${e.id}`:""}),i.jsxs("div",{style:{display:"flex",gap:8},children:[i.jsx("button",{type:"button","data-testid":"g7le-property-modal-delete",onClick:a,style:MT,children:o("layout_editor.property_modal.delete_element")}),i.jsx("button",{type:"button","data-testid":"g7le-property-modal-done",onClick:s,style:LT,children:o("layout_editor.property_modal.close")})]})]})]})}const CT={display:"flex",flexDirection:"column",width:420,maxWidth:"92vw"},$T={display:"flex",alignItems:"center",justifyContent:"space-between",padding:"12px 16px",borderBottom:"1px solid #e2e8f0",cursor:"move",userSelect:"none"},ET={border:"none",background:"transparent",fontSize:14,cursor:"pointer",color:"#64748b"},IT={margin:"8px 16px 0",padding:"6px 10px",fontSize:11,background:"#fef3c7",color:"#92400e",borderRadius:6},AT={margin:"8px 16px 0",padding:"6px 10px",fontSize:11,background:"#dbeafe",color:"#1e40af",borderRadius:6},TT={display:"flex",gap:2,padding:"0 12px",borderBottom:"1px solid #e2e8f0",overflowX:"auto"},DT={padding:"8px 10px",fontSize:12,border:"none",borderBottom:"2px solid transparent",background:"transparent",cursor:"pointer",whiteSpace:"nowrap"},NT={padding:16,overflowY:"auto",maxHeight:"60vh"},ng={position:"sticky",top:-16,zIndex:2,background:"#fff",margin:"-16px -16px 0",padding:"6px 16px 0"},og={fontSize:12,color:"#94a3b8",padding:"16px 0",textAlign:"center"},zT={display:"flex",alignItems:"center",justifyContent:"space-between",padding:"12px 16px",borderTop:"1px solid #e2e8f0"},MT={padding:"6px 12px",fontSize:12,border:"1px solid #fecaca",borderRadius:6,background:"#fff",color:"#dc2626",cursor:"pointer"},LT={padding:"6px 12px",fontSize:12,border:"none",borderRadius:6,background:"#2563eb",color:"#fff",cursor:"pointer"},La="g7le:component-target-picked",Pa="g7le:component-target-pick-request";let PT=1;function OT({value:e,onChange:t,t:n,control:o}){const r=yo(),s=e==null?"":String(e),a=o?.allowManualInput!==!1,l=u.useCallback(()=>{if(typeof window>"u")return;const d=`pick-${PT++}`,p=r.stack[r.stack.length-1]?.id;p&&r.minimize(p,n("layout_editor.target_picker.minimized_hint"));const f=y=>{const g=y.detail;g?.requestId===d&&(window.removeEventListener(La,f),p&&r.restore(p),!g.cancelled&&typeof g.id=="string"&&t(g.id))};window.addEventListener(La,f),window.dispatchEvent(new CustomEvent(Pa,{detail:{requestId:d}}))},[r,t,n]);return i.jsx("div",{className:"g7le-component-target-picker","data-testid":"g7le-component-target-picker",children:i.jsxs("div",{style:{display:"flex",gap:6,alignItems:"center"},children:[a?i.jsx("input",{type:"text",className:"g7le-widget g7le-widget--text","data-testid":"g7le-component-target-input",value:s,placeholder:n("layout_editor.target_picker.id_placeholder"),onChange:d=>t(d.target.value===""?void 0:d.target.value),style:{flex:1,minWidth:0,padding:"6px 8px",border:"1px solid #cbd5e1",borderRadius:6,fontSize:13}}):i.jsx("span",{"data-testid":"g7le-component-target-value",style:{flex:1,minWidth:0},children:s||n("layout_editor.target_picker.none")}),i.jsxs("button",{type:"button","data-testid":"g7le-component-target-pick",onClick:l,style:{display:"flex",alignItems:"center",gap:4,padding:"6px 10px",background:"#1e293b",color:"#fff",border:"none",borderRadius:6,cursor:"pointer",fontSize:13,whiteSpace:"nowrap"},children:[i.jsx("span",{"aria-hidden":"true",children:"🎯"}),n("layout_editor.target_picker.select_area")]})]})})}async function BT(e,t,n,o,r=fetch){const s=`/api/admin/templates/${encodeURIComponent(e)}/layout-extensions/${t}`;let a;try{a=await r(s,{headers:je(),credentials:"same-origin"})}catch(b){return{kind:"error",message:b instanceof Error?b.message:String(b)}}if(a.status===404)return{kind:"not_found"};if(!a.ok)return{kind:"error",message:`HTTP ${a.status}`};const l=await a.json().catch(()=>null),d=(l&&(l.data||l))??{};let c={};const p=d.content;if(typeof p=="string")try{c=JSON.parse(p)}catch{return{kind:"error",message:"invalid extension content"}}else p&&typeof p=="object"&&(c=p);const f=typeof d.lock_version=="number"?d.lock_version:0,y=Array.isArray(c.injections)?[...c.injections]:[],g=y.findIndex(b=>b&&b.position==="inject_props"&&b.target_id===n);if(g<0)return{kind:"injection_not_found"};y[g]={...y[g],props:o};const m={...c,injections:y};let h;try{h=await r(s,{method:"PUT",headers:je({"Content-Type":"application/json"}),credentials:"same-origin",body:JSON.stringify({content:m,expected_lock_version:f})})}catch(b){return{kind:"error",message:b instanceof Error?b.message:String(b)}}const v=await h.json().catch(()=>null);return h.ok?{kind:"success",newLockVersion:typeof v?.data?.lock_version=="number"?v.data.lock_version:f+1}:h.status===409?{kind:"conflict",currentVersion:v?.current_version??-1,yourVersion:v?.your_version??f}:{kind:"error",message:v?.message??`HTTP ${h.status}`}}const rg=new Map;function Oa(e,t){rg.set(e,t)}function WT(e){return e?rg.get(e)??null:null}const ig="g7le.clipboard";function sg(){try{return typeof window>"u"||!window.sessionStorage?null:window.sessionStorage}catch{return null}}function jT(e){const t=JSON.parse(JSON.stringify(e)),n=o=>{for(const r of Object.keys(o))r.startsWith("__")&&delete o[r];Array.isArray(o.children)&&o.children.forEach(n)};return n(t),t}function ag(e,t=0){const n=sg();if(!n)return!1;try{const o={node:jT(e),copiedAt:t};return n.setItem(ig,JSON.stringify(o)),!0}catch{return!1}}function HT(){const e=sg();if(!e)return null;try{const t=e.getItem(ig);if(!t)return null;const n=JSON.parse(t);return!n||typeof n.node!="object"||n.node===null?null:JSON.parse(JSON.stringify(n.node))}catch{return null}}const Xo={nw:{width:-1,height:-1},n:{width:0,height:-1},ne:{width:1,height:-1},e:{width:1,height:0},se:{width:1,height:1},s:{width:0,height:1},sw:{width:-1,height:1},w:{width:-1,height:0}};function Ba(e){if(typeof e=="number")return e;if(typeof e=="string"){const t=e.match(/^(-?\d+(?:\.\d+)?)px$/);if(t)return Number(t[1])}return null}function FT(e,t){const n=Array.isArray(e.options)?e.options:[],o=Array.isArray(e.scale)?e.scale:[],r=n.length>0?n.map(l=>l.value):o;if(r.length===0)return;let s=r[0],a=1/0;return r.forEach((l,d)=>{const c=Ba(l)??d*40,p=Math.abs(c-t);p{if(!t)return;const m=Xo[y];if(m.width!==0&&!n||m.height!==0&&!o)return;const h=t.props?.style??{},v=s?.()??null,b=Ba(h.width)??(v?Math.round(v.width):0),x=Ba(h.height)??(v?Math.round(v.height):0);p.current={handle:y,startX:g.clientX,startY:g.clientY,startW:b,startH:x,node:t};const k=m.width!==0&&m.height!==0?"both":m.width!==0?"width":"height";d?.(k);let w=null;const _=E=>{const C=p.current;if(!C)return;const N=r||1,$=(E.clientX-C.startX)/N*Xo[C.handle].width,A=(E.clientY-C.startY)/N*Xo[C.handle].height;let z=C.node;if(Xo[C.handle].width!==0&&n){const D=lg(n,C.startW+$);z=fn(z,n,D)}if(Xo[C.handle].height!==0&&o){const D=lg(o,C.startH+A);z=fn(z,o,D)}w=z,a(z,k)},S=()=>{p.current=null,d?.(null),window.removeEventListener("pointermove",_),window.removeEventListener("pointerup",S),w&&l?.(w,k)};window.addEventListener("pointermove",_),window.addEventListener("pointerup",S)},[t,n,o,r,a,l,d]);return{enabledAxes:c,onHandlePointerDown:f}}const dg=320,cg=1920,UT=1024;function Wa(e){return Number.isFinite(e)?Math.round(Math.max(dg,Math.min(cg,e))):UT}function ja(e={}){const{state:t,dispatch:n}=Je(),{previewDevice:o,previewZoom:r,previewCustomWidth:s,previewColorScheme:a}=t,l=o==="custom"?Wa(s):U6(o),d=u.useMemo(()=>{const g=e.availableWidth??l,m=l*r;return r>=1||m<=g?r:Math.min(r,g/l)},[l,r,e.availableWidth]),c=u.useCallback(g=>{n({type:"SET_PREVIEW_DEVICE",device:g})},[n]),p=u.useCallback(g=>{const m=Math.max(.5,Math.min(1,g));n({type:"SET_PREVIEW_ZOOM",zoom:m})},[n]),f=u.useCallback(g=>{n({type:"SET_PREVIEW_CUSTOM_WIDTH",width:Wa(g)})},[n]),y=u.useCallback(g=>{n({type:"SET_PREVIEW_COLOR_SCHEME",scheme:g})},[n]);return{device:o,deviceWidth:l,zoom:r,scale:d,customWidth:Wa(s),setDevice:c,setZoom:p,setCustomWidth:f,colorScheme:a,setColorScheme:y}}const yi="g7le-dnd-handle-";function qT(e){if(typeof e!="string"||!e.startsWith(yi))return null;const t=e.slice(yi.length);return t.length>0?t:null}function GT(e){return typeof CSS<"u"&&typeof CSS.escape=="function"?CSS.escape(e):e.replace(/["\\\]]/g,"\\$&")}function YT(e,t,n,o){const r=o?.handleFallback??!1;if(e instanceof HTMLElement){const s=e.closest("[data-editor-path]");if(s)return s;const a=e.closest('[data-testid^="g7le-"]');if(a&&!(a.getAttribute("data-testid")??"").startsWith(yi))return null;if(r){const l=e.closest(`[data-testid^="${yi}"]`);if(l&&t){const d=qT(l.getAttribute("data-testid"));if(d){const c=t.querySelector(`[data-editor-path="${GT(d)}"]`);if(c)return c}}}}if(r&&n&&typeof document<"u"&&typeof document.elementsFromPoint=="function")for(const s of document.elementsFromPoint(n.x,n.y)){const a=s.closest?.("[data-editor-path]");if(a)return a}return null}function XT(e,t,n){if(typeof e=="string"&&e.trim().length>0)return{color:e};const o=typeof n?.color=="string"&&n.color.length>0,r=typeof t=="string"&&t.trim().length>0;return o||r?{}:{color:"#0f172a"}}const JT="#ffffff",ZT="#0f172a";function QT(e){const t=e.trim(),n=t.match(/^rgba?\(\s*([\d.]+)[\s,]+([\d.]+)[\s,]+([\d.]+)/i);if(n)return[Number(n[1]),Number(n[2]),Number(n[3])];const o=t.replace(/^#/,"");return/^[0-9a-f]{3}$/i.test(o)?[parseInt(o[0]+o[0],16),parseInt(o[1]+o[1],16),parseInt(o[2]+o[2],16)]:/^[0-9a-f]{6}$/i.test(o)?[parseInt(o.slice(0,2),16),parseInt(o.slice(2,4),16),parseInt(o.slice(4,6),16)]:null}function RT(e){if(!e||/transparent/i.test(e))return!1;const t=e.trim().match(/^oklch\(\s*([\d.]+)(%?)/i);if(t)return(t[2]==="%"?Number(t[1])/100:Number(t[1]))>=.62;const n=QT(e);if(!n)return!1;const[o,r,s]=n;return .299*o+.587*r+.114*s>=180}function e4(e,t,n){const o=typeof n?.color=="string"?n.color:null;return RT(e??o??t)?ZT:JT}function t4({box:e,initialValue:t,isCustomKey:n,t:o,onCommit:r,onCancel:s,mirrorClassName:a,mirrorStyle:l,nodeEffectiveColor:d,onOpenTranslations:c}){const p=u.useRef(null),f=u.useRef(!1),[y,g]=u.useState(t.trim().length>0),[m,h]=u.useState(null);u.useEffect(()=>{const w=p.current;if(w){w.textContent=t,w.focus();try{const _=window.document.createRange();_.selectNodeContents(w);const S=window.getSelection();S?.removeAllRanges(),S?.addRange(_)}catch{}f.current=!1}},[t]),u.useEffect(()=>{const w=p.current;if(!w||typeof window.getComputedStyle!="function"){h(null);return}try{h(window.getComputedStyle(w).color||null)}catch{h(null)}},[a,l]);const v=()=>(p.current?.textContent??"").trim(),b=()=>{f.current||(f.current=!0,r(v()))},x=()=>{f.current||(f.current=!0,s())},k=w=>{w.key==="Enter"&&!w.shiftKey?(w.preventDefault(),b()):w.key==="Escape"&&(w.preventDefault(),x())};return i.jsxs("div",{className:"g7le-inline-text-editor","data-testid":"g7le-inline-text-editor","data-custom-key":n?"true":"false",style:{position:"absolute",left:e.left,top:e.top,minWidth:Math.max(e.width,60),minHeight:e.height,pointerEvents:"auto",zIndex:1e4},children:[i.jsx("div",{ref:p,role:"textbox","aria-label":o("layout_editor.inline_edit.aria_label"),"aria-multiline":"false","data-testid":"g7le-inline-text-editable",className:a,contentEditable:!0,suppressContentEditableWarning:!0,spellCheck:!1,onInput:()=>g(v().length>0),onKeyDown:k,onBlur:b,style:{outline:"2px solid #2563eb",outlineOffset:1,borderRadius:3,background:e4(d,m,l),padding:"2px 4px",minHeight:e.height,whiteSpace:"pre-wrap",wordBreak:"break-word",boxShadow:"0 2px 8px rgba(37, 99, 235, 0.25)",...l??{},...XT(d,a,l)}}),i.jsxs("div",{className:"g7le-inline-text-editor__hint","data-testid":"g7le-inline-text-editor-hint",style:{marginTop:2,display:"inline-flex",alignItems:"center",gap:4,whiteSpace:"nowrap"},children:[c?i.jsxs("button",{type:"button","data-testid":"g7le-inline-text-editor-open-translations",onMouseDown:w=>w.preventDefault(),onClick:w=>{w.preventDefault(),w.stopPropagation(),c()},title:o("layout_editor.inline_edit.open_translations"),style:{padding:"2px 8px",fontSize:11,background:n?"#eff6ff":"#f0fdf4",color:n?"#1d4ed8":"#15803d",border:`1px solid ${n?"#bfdbfe":"#bbf7d0"}`,borderRadius:4,display:"inline-flex",alignItems:"center",gap:4,cursor:"pointer",whiteSpace:"nowrap"},children:[i.jsx("span",{"aria-hidden":"true",children:"🌐"}),i.jsx("span",{children:o(n?"layout_editor.inline_edit.existing_key_hint":"layout_editor.inline_edit.new_key_hint")}),i.jsxs("span",{"aria-hidden":"true",style:{opacity:.7},children:["· ",o("layout_editor.inline_edit.open_translations")," ›"]})]}):i.jsx("span",{style:{padding:"2px 6px",fontSize:11,background:n?"#eff6ff":"#f0fdf4",color:n?"#1d4ed8":"#15803d",border:`1px solid ${n?"#bfdbfe":"#bbf7d0"}`,borderRadius:4,display:"inline-flex",alignItems:"center",gap:4,whiteSpace:"nowrap"},children:n?i.jsxs(i.Fragment,{children:["⌐ ",o("layout_editor.inline_edit.existing_key_hint")]}):i.jsxs(i.Fragment,{children:["✎ ",o("layout_editor.inline_edit.new_key_hint")]})}),!y&&i.jsx("span",{"data-testid":"g7le-inline-text-editor-empty-warn",style:{color:"#b91c1c",marginLeft:4},children:o("layout_editor.inline_edit.empty_warning")})]})]})}function n4({box:e,node:t,customKey:n,initialChipValue:o,chipParamLabels:r,onKeyify:s,templateIdentifier:a,locale:l,t:d,candidates:c,onInsertBinding:p,onRemoveBinding:f,onCommit:y,onCancel:g}){const[m,h]=u.useState(null),v=u.useRef(""),[b,x]=u.useState(!1),k=u.useRef(!1),w=u.useRef(null),_=Ft(),S=u.useRef(()=>{}),[E,C]=u.useState(null),[N,$]=u.useState(!1),A=u.useRef(n??null);u.useEffect(()=>{n&&A.current!==n&&(A.current=n)},[n]);const z=u.useMemo(()=>{if(!n&&r)return r;const L={},I=mt(typeof t.text=="string"?t.text:"");for(const P of I?.params??[])L[P.name]=Vt(P.expression);return L},[t.text,n,r]);u.useEffect(()=>{let L=!1;if(!n){const I=o??"";return v.current=I,h(I),()=>{L=!0}}return(async()=>{const I=It(n,l);if(I!==void 0){v.current=I,h(I);return}const P=await cn(a,n);if(L)return;const F=P?.values?.[l]??"";v.current=F,h(F)})(),()=>{L=!0}},[a,n,l,o]);const D=async()=>{if(k.current)return;k.current=!0;const L=m??"";if(L===v.current){y();return}x(!0);let I=A.current;if(!I){if(!s){x(!1),k.current=!1;return}if(I=await s(L),x(!1),!I){k.current=!1;return}A.current=I,y();return}const P=await ko(a,I,l,L);if(x(!1),P.kind==="error"){k.current=!1;return}_?.markDirty?.(),y()},O=()=>{k.current||(k.current=!0,g())},M=L=>{C(L)},B=async L=>{if(!(E===null||!p||N)){$(!0);try{const I=A.current;I&&m!==null&&m!==v.current&&(await ko(a,I,l,m),v.current=m);const P=await p(E,L);P!==null&&(h(P),v.current=P,_?.markDirty?.())}finally{$(!1),C(null)}}},T=async L=>{if(!(!f||N)){$(!0);try{const I=A.current;I&&m!==null&&m!==v.current&&(await ko(a,I,l,m),v.current=m);const P=await f(L);P!==null&&(h(P),v.current=P,_?.markDirty?.())}finally{$(!1)}}},H=L=>{L.key==="Enter"&&!L.shiftKey?(L.preventDefault(),D()):L.key==="Escape"&&(L.preventDefault(),O())};return S.current=()=>{D()},u.useEffect(()=>{const L=P=>{const F=w.current;if(!F)return;const G=P.target;if(G&&F.contains(G))return;const V=G;V&&V.closest&&V.closest('[data-testid="g7le-inline-toolbar"], .g7le-inline-toolbar')||S.current()},I=w.current?.ownerDocument??document;return I.addEventListener("pointerdown",L,!0),()=>I.removeEventListener("pointerdown",L,!0)},[]),i.jsxs("div",{ref:w,className:"g7le-inline-param-chip-editor","data-testid":"g7le-inline-param-chip-editor",onKeyDown:H,style:{position:"absolute",left:e.left,top:e.top,minWidth:Math.max(e.width,120),pointerEvents:"auto",zIndex:1e4,background:"#fff",border:"2px solid #2563eb",borderRadius:4,boxShadow:"0 2px 8px rgba(37,99,235,0.25)",padding:4},children:[m===null?i.jsx("div",{"data-testid":"g7le-inline-param-chip-loading",style:{fontSize:12,color:"#94a3b8",padding:4},children:d("layout_editor.translation.loading")}):i.jsx(mr,{value:m,onChange:h,t:d,paramLabels:z,testIdSuffix:"inline",onRequestInsert:p?M:void 0,onRemoveChip:f?L=>{T(L)}:void 0}),E!==null&&p&&i.jsx("div",{"data-testid":"g7le-inline-param-chip-insert-picker",style:{marginTop:4},children:i.jsx($t,{candidates:c??[],t:d,onSelect:L=>{B(L)},testIdSuffix:"inline-insert",defaultOpen:!0,floating:!1})}),i.jsxs("div",{style:{display:"flex",alignItems:"center",gap:6,marginTop:4},children:[i.jsx("button",{type:"button","data-testid":"g7le-inline-param-chip-save",disabled:b,onMouseDown:L=>L.preventDefault(),onClick:()=>{D()},style:{fontSize:11,border:"none",borderRadius:6,background:"#2563eb",color:"#fff",padding:"4px 12px",cursor:"pointer"},children:d(b?"layout_editor.translation.saving":"layout_editor.translation.save")}),i.jsx("span",{style:{fontSize:10,color:"#94a3b8"},children:d("layout_editor.translation.placeholder_locked")})]})]})}const o4=["fontWeight","fontBold","fontStyle","fontItalic","textDecoration","textUnderline","textAlign","fontSize","textColor"],r4={fontWeight:"B",fontBold:"B",fontStyle:"I",fontItalic:"I",textDecoration:"U",textUnderline:"U",textAlign:"≡",fontSize:"T",textColor:"A"},i4=[["fontWeight","fontBold"],["fontItalic","fontStyle"],["textUnderline","textDecoration"]];function ug({node:e,spec:t,t:n,onApplyControl:o,box:r,scope:s=Wt}){const a=typeof e.name=="string"?e.name:"",l=Oi(t,a),d=Array.isArray(l?.styleControls)?l.styleControls:[];let c=o4.filter(p=>d.includes(p)&&Ct(t,p)!==null);for(const p of i4){const f=p.filter(y=>c.includes(y));if(f.length>1){const y=f[0];c=c.filter(g=>!p.includes(g)||g===y)}}return c.length===0?null:i.jsx("div",{className:"g7le-inline-toolbar","data-testid":"g7le-inline-toolbar",role:"toolbar","aria-label":n("layout_editor.inline_edit.toolbar_label"),style:{position:"absolute",left:r.left,top:r.top-40,display:"inline-flex",gap:2,padding:4,background:"#0f172a",borderRadius:8,boxShadow:"0 4px 12px rgba(15, 23, 42, 0.35)",pointerEvents:"auto",zIndex:10001},children:c.map(p=>i.jsx(a4,{controlKey:p,node:e,spec:t,t:n,scope:s,onApplyControl:o},p))})}const s4={fontWeight:"bold",fontBold:"bold",fontStyle:"italic",fontItalic:"italic",textDecoration:"underline",textUnderline:"underline"};function pg(e,t){const n=r=>(r.split(/[-\s]/).pop()??r)===t||r===t;return e.find(r=>{const s=typeof r.value=="string"?r.value:"",a=Array.isArray(r.apply?.tokens)?r.apply.tokens:[];return n(s)||a.some(n)})?.value}function a4({controlKey:e,node:t,spec:n,t:o,scope:r,onApplyControl:s}){const[a,l]=u.useState(!1),d=Ct(n,e);if(!d)return null;const c=d.options,p=Array.isArray(c),f=Do(t,d,r),y=s4[e],g=y&&Array.isArray(c)?pg(c,y):void 0,m=!y,h=g!==void 0?f.value===g:p&&f.matched&&f.value!==void 0&&f.value!=="",v=o(`layout_editor.inline_edit.format.${e}`),b=_=>typeof _.label=="string"?_.label.startsWith("$t:")?o(_.label.slice(3)):_.label:String(_.value??""),x=()=>{s(e,_=>{const S=Do(_,d,r),E=Array.isArray(c)?pg(c,y):void 0,C=E!==void 0&&S.value===E?void 0:E;return fn(_,d,C,r)})},k=_=>{s(e,S=>{const C=Do(S,d,r).value===_?void 0:_;return fn(S,d,C,r)}),l(!1)},w=_=>{if(_.preventDefault(),_.stopPropagation(),!m&&Array.isArray(c)){x();return}l(S=>!S)};return i.jsxs("div",{style:{position:"relative",display:"inline-flex"},children:[i.jsxs("button",{type:"button","data-testid":`g7le-inline-format-${e}`,"data-active":h?"true":"false","aria-label":v,"aria-pressed":p?h:void 0,title:v,onMouseDown:_=>_.preventDefault(),onClick:w,style:{display:"inline-flex",alignItems:"center",gap:5,height:28,padding:"0 9px",fontSize:12,border:"none",borderRadius:5,cursor:"pointer",background:h?"#2563eb":"transparent",color:"#ffffff",whiteSpace:"nowrap"},children:[i.jsx("span",{"aria-hidden":"true",style:{fontWeight:e==="fontWeight"||e==="fontBold"?700:600,fontStyle:e==="fontStyle"||e==="fontItalic"?"italic":"normal",textDecoration:e==="textDecoration"||e==="textUnderline"?"underline":"none"},children:r4[e]}),i.jsx("span",{children:v}),m&&i.jsx("span",{"aria-hidden":"true",style:{fontSize:9,opacity:.8},children:"▾"})]}),m&&a&&i.jsx("div",{"data-testid":`g7le-inline-popover-${e}`,role:"listbox",onMouseDown:_=>_.preventDefault(),style:{position:"absolute",top:32,left:0,minWidth:140,maxHeight:240,overflowY:"auto",background:"#0f172a",border:"1px solid #334155",borderRadius:8,boxShadow:"0 8px 24px rgba(15,23,42,0.45)",padding:4,zIndex:10002},children:Array.isArray(c)&&c.length>0?c.map((_,S)=>{const E=f.value===_.value;return i.jsxs("button",{type:"button",role:"option","aria-selected":E,"data-testid":`g7le-inline-option-${e}-${String(_.value??S)}`,onMouseDown:C=>C.preventDefault(),onClick:C=>{C.preventDefault(),C.stopPropagation(),k(_.value)},style:{display:"flex",alignItems:"center",gap:8,width:"100%",padding:"6px 8px",fontSize:12,textAlign:"left",border:"none",borderRadius:5,cursor:"pointer",background:E?"#2563eb":"transparent",color:"#ffffff"},children:[typeof _.swatch=="string"&&i.jsx("span",{"aria-hidden":"true",style:{width:14,height:14,borderRadius:3,background:_.swatch,border:"1px solid rgba(255,255,255,0.3)"}}),i.jsx("span",{children:b(_)}),E&&i.jsx("span",{"aria-hidden":"true",style:{marginLeft:"auto"},children:"✓"})]},String(_.value??S))}):i.jsx(l4,{testid:`g7le-inline-freeinput-${e}`,placeholder:o(`layout_editor.inline_edit.prompt.${e}`),initial:typeof f.value=="string"?f.value:"",onSubmit:_=>{s(e,S=>fn(S,d,_,r)),l(!1)}})})]})}function l4({testid:e,placeholder:t,initial:n,onSubmit:o}){const[r,s]=u.useState(n);return i.jsx("input",{type:"text","data-testid":e,value:r,placeholder:t,autoFocus:!0,onMouseDown:a=>a.stopPropagation(),onChange:a=>s(a.currentTarget.value),onKeyDown:a=>{a.key==="Enter"&&(a.preventDefault(),o(r.trim()))},style:{width:130,padding:"6px 8px",fontSize:12,border:"1px solid #334155",borderRadius:5,background:"#1e293b",color:"#ffffff"}})}function d4(e){const{frameEl:t,manifest:n,nesting:o,componentPalette:r,spec:s=null,permissionCandidates:a,resolveRouteMatch:l,onNavigateToDestination:d,onSessionAddedPathsChange:c,onEditProps:p}=e,{state:f,dispatch:y}=Je(),{t:g}=tt(),m=ja(),h=u.useCallback((W,X)=>{const R=window.G7Core;if(R&&typeof R.t=="function"){const de=R.t(W,X);if(de&&de!==W&&!de.startsWith("$t:"))return de}return g(W,X)},[g]),v=Ft(),b=s7(50),x=yo(),k=u.useRef([]),w=u.useMemo(()=>v?.document?.raw?.components??[],[v?.document?.raw?.components]),_=u.useMemo(()=>w.length===0?null:{children:w},[w]),S=u.useMemo(()=>{if(f.editMode!=="extension")return;const W=/^__extension__\/(\d+)/.exec(f.selectedRoute?.path??"");return W?Number(W[1]):void 0},[f.editMode,f.selectedRoute?.path]),E=u.useMemo(()=>{if(f.editMode==="iteration_item"){const W=v?.document?.iterationContext?.sourceIndexPath;return Array.isArray(W)&&W.length>0?W:null}if(f.editMode==="modal"){const W=v?.document?.modalContext?.editIndexPath;return Array.isArray(W)&&W.length>0?W:null}return null},[f.editMode,v?.document?.iterationContext?.sourceIndexPath,v?.document?.modalContext?.editIndexPath]),C=ZE({rootNode:_,editMode:f.editMode,currentExtensionId:S,editableRootPath:E,pathParser:Ze,resolveRouteMatch:l}),[N,$]=u.useState(null),[A,z]=u.useState(null),[D,O]=u.useState(null),M=u.useRef(null);M.current=D;const[B,T]=u.useState(null),H=u.useRef(null),L=u.useRef(null);L.current=C.selectedPath;const I=Ob(),[P,F]=u.useState(null),G=u.useCallback(W=>{if(!t)return;const X=Ze(W);if(!X)return;const R={children:Me.current.components},de=We(R,X),ye=I.classify(de);if(!ye.editable){Et({op:"inline_edit_blocked_binding",sourceState:ye.sourceState,componentPath:at(X),timestamp:Date.now()});return}const we=po(t,W);if(!we)return;const Te=de?.props??{},ke=gg(t,W);F({path:X,domPath:W,box:we,initialValue:ye.displayValue,isCustomKey:ye.sourceState==="custom_key",isParamKey:ye.isParamKey===!0,customKey:ye.customKey,chipValue:ye.chipValue,chipParamLabels:ye.chipParamLabels,mirrorClassName:typeof Te.className=="string"?Te.className:void 0,mirrorStyle:Te.style??void 0,nodeEffectiveColor:ke}),Et({op:"inline_edit_enter",sourceState:ye.sourceState,componentPath:at(X),timestamp:Date.now()})},[t,I]),V=u.useCallback(()=>{if(!t){$(null),z(null);return}z(po(t,C.selectedPath)),$(po(t,C.hoverPath))},[t,C.selectedPath,C.hoverPath,w]);u.useEffect(()=>{if(V(),typeof window<"u"&&typeof window.requestAnimationFrame=="function"){const W=window.requestAnimationFrame(()=>V());return()=>window.cancelAnimationFrame(W)}},[V]),u.useEffect(()=>{if(t)return Sp(V,t)},[t,V]);const Y=u.useCallback((W,X)=>{if(typeof window>"u")return;window.dispatchEvent(new CustomEvent(La,{detail:X===null?{requestId:W,cancelled:!0}:{requestId:W,id:X}})),O(null),T(null);const R=H.current;R?C.handleSelect("",{dataset:{editorPath:R}}):C.clearSelection(),H.current=null},[C]);u.useEffect(()=>{if(typeof window>"u")return;const W=X=>{const R=X.detail;if(typeof R?.requestId!="string")return;const de=M.current;de&&de!==R.requestId&&Y(de,null),H.current=L.current,O(R.requestId)};return window.addEventListener(Pa,W),()=>window.removeEventListener(Pa,W)},[Y]),u.useEffect(()=>{if(!D)return;const W=X=>{X.key==="Escape"&&(X.preventDefault(),Y(D,null))};return window.addEventListener("keydown",W,!0),()=>window.removeEventListener("keydown",W,!0)},[D,Y]),u.useEffect(()=>{if(!t)return;const W=f.editMode==="iteration_item"||f.editMode==="modal",X=(xe,Ee)=>YT(xe,t,Ee?{x:Ee.clientX,y:Ee.clientY}:void 0,{handleFallback:W}),R=xe=>{const Ee=xe.dataset.editorPath??"",Fe=Ee?Ze(Ee):null,zt={children:Me.current.components},gt=Fe?We(zt,Fe):null,Mt=gt&&typeof gt.props?.id=="string"?gt.props.id:typeof gt?.id=="string"?gt.id:"";return Mt.startsWith("auto_")?"":Mt},de=xe=>{const Ee=X(xe.target,xe);if(!Ee)return;const Fe=M.current;if(Fe){xe.preventDefault(),xe.stopPropagation();const zt=R(Ee);zt&&Y(Fe,zt);return}xe.preventDefault(),xe.stopPropagation(),C.handleSelect(Ee.dataset.editorId??"",{currentTarget:Ee,dataset:Ee.dataset})},ye=xe=>{const Ee=X(xe.target,xe);if(M.current){if(!Ee){T(null);return}const Fe=po(t,Ee.dataset.editorPath??null);if(!Fe){T(null);return}T({box:Fe,hasId:R(Ee)!==""});return}if(!Ee){C.handleHover(null,{});return}C.handleHover(Ee.dataset.editorId??"",{currentTarget:Ee,dataset:Ee.dataset})},we=()=>{if(M.current){T(null);return}C.handleHover(null,{})},Te=xe=>{const Ee=X(xe.target,xe);if(!Ee)return;const Fe=Ee.dataset.editorPath??"";Fe&&(xe.preventDefault(),xe.stopPropagation(),G(Fe))},ke=t.parentElement??t;return ke.addEventListener("click",de,!0),ke.addEventListener("mousemove",ye,!0),ke.addEventListener("mouseleave",we,!0),ke.addEventListener("dblclick",Te,!0),()=>{ke.removeEventListener("click",de,!0),ke.removeEventListener("mousemove",ye,!0),ke.removeEventListener("mouseleave",we,!0),ke.removeEventListener("dblclick",Te,!0)}},[t,C,G,f.editMode,Y]);const Z=u.useMemo(()=>{const W=C.selectedPath;if(!W)return null;const X=W.match(/^(.*)\.children\.\d+$/);return X?X[1]:null},[C.selectedPath]),se=u.useCallback(()=>{Z&&C.handleSelect("",{dataset:{editorPath:Z}})},[Z,C]),re=u.useMemo(()=>!t||!C.selectedPath?null:t.querySelector(`[data-editor-path="${an(C.selectedPath)}"]`),[t,C.selectedPath]),U=u.useMemo(()=>C.selectedPath?Ze(C.selectedPath):null,[C.selectedPath]),ee=u.useMemo(()=>{if(!U)return null;const W=U.find(et);if(!W)return null;const{i18nKey:X,raw:R}=hC(W.responsive);return X?g(X):g("layout_editor.overlay.branch_custom",{range:R})},[U,g]),fe=u.useMemo(()=>C.selectedPath?C.selectedIsIteration&&C.selectedIterationSourcePath?C.selectedIterationSourcePath:C.selectedPath:null,[C.selectedPath,C.selectedIsIteration,C.selectedIterationSourcePath]),K=u.useMemo(()=>fe?Ze(fe):null,[fe]),ne=u.useMemo(()=>!t||!fe?null:t.querySelector(`[data-editor-path="${an(fe)}"]`),[t,fe]),me=u.useMemo(()=>{if(ne?.parentElement)return ne.parentElement;if(!t||!fe)return null;const W=fe.match(/^(.*)\.children\.\d+$/);return W?t.querySelector(`[data-editor-path="${an(W[1])}"]`):null},[ne,t,fe]),Ce=u.useMemo(()=>{const W=C.selectedNode;return W&&(typeof W.name=="string"?W.name:typeof W.type=="string"?W.type:"")||null},[C.selectedNode]),ze=u.useMemo(()=>{if(C.selectedLockKind!=="extension")return null;const W=C.selectedNode?.__source;if(!W||W.kind!=="extension")return null;const X=W.extensionName?.trim(),R=W.extensionIdentifier?.trim();return X&&R?`${X} (${R})`:X||R||W.extensionSourceType||null},[C.selectedLockKind,C.selectedNode]),Se=o7({parentEl:me,selectedPath:K,selectedEl:ne}),qe=u.useMemo(()=>!t||!fe||fe===C.selectedPath?A:po(t,fe),[t,fe,A,C.selectedPath,w]),De=u.useRef(null),Pe=u.useCallback(()=>{const W=De.current;W&&(x.close(W),De.current=null)},[x]),Qe=u.useRef(()=>{}),Me=u.useRef({manifest:n,nesting:o,componentPalette:r,components:w});Me.current={manifest:n,nesting:o,componentPalette:r,components:w};const Re=u.useCallback(W=>{const X=W.insertion?.parentPath??[],R=W.insertion?.index??0,de=Me.current,ye=fg(de.components,X);Pe();const we=x.open({ariaLabel:h("layout_editor.palette.title"),width:880,maxHeightRatio:.82,content:u.createElement(Cf,{manifest:de.manifest,nesting:de.nesting,componentPalette:de.componentPalette??null,targetParentPath:X,targetContainerName:ye,onInsert:(Te,ke,xe)=>{Qe.current(Te,ke,xe)},insertionIndex:R,onClose:()=>Pe(),editorTemplateId:f.templateIdentifier,editorLocale:f.locale}),onClose:()=>{De.current=null}});De.current=we},[x,Pe]),ve=u.useMemo(()=>{if(!f.isPaletteOpen)return null;if(!U)return{parentPath:[],index:w.length};if(C.selectedLockKind!=="none"){const de=K;return!de||de.length===0?{parentPath:[],index:w.length}:{parentPath:de.slice(0,-1),index:(de[de.length-1]??0)+1}}const X=C.selectedNode,R=Array.isArray(X?.children)?X.children.length:0;return iI(X?.name,R,U,o,w.length)},[f.isPaletteOpen,U,K,C.selectedLockKind,C.selectedNode,w.length,o]),te=u.useCallback(()=>{y({type:"SET_PALETTE_OPEN",open:!1})},[y]),he=u.useRef(null);u.useEffect(()=>{if(!f.isPaletteOpen){const ye=he.current;ye&&(x.close(ye),he.current=null);return}if(!ve||he.current)return;const W=ve,X=Me.current,R=fg(X.components,W.parentPath),de=x.open({ariaLabel:h("layout_editor.palette.title"),width:880,maxHeightRatio:.82,content:u.createElement(Cf,{manifest:X.manifest,nesting:X.nesting,componentPalette:X.componentPalette??null,targetParentPath:W.parentPath,targetContainerName:R,onInsert:(ye,we,Te)=>{Qe.current(ye,we,Te),te()},insertionIndex:W.index,onClose:()=>te(),editorTemplateId:f.templateIdentifier,editorLocale:f.locale}),onClose:()=>{he.current=null,y({type:"SET_PALETTE_OPEN",open:!1})}});he.current=de},[f.isPaletteOpen,ve,x,te,y]);const Q=u.useRef({layoutName:null,pushed:!1});u.useEffect(()=>{const W=v?.document?.layoutName??null;if(!W){Q.current={layoutName:null,pushed:!1};return}Q.current.layoutName!==W&&(b.clear(),b.push({actionKind:"inline_text_edit",label:"baseline",snapshot:w}),Q.current={layoutName:W,pushed:!0})},[v?.document?.layoutName,w,b]);const ie=u.useRef(0);u.useEffect(()=>{const W=v?.saveSuccessCounter??0;W!==ie.current&&(ie.current=W,W!==0&&(b.clear(),b.push({actionKind:"inline_text_edit",label:"save_baseline",snapshot:w}),k.current=[],c?.([])))},[v?.saveSuccessCounter,w,b,c]);const _e=u.useRef(0);u.useEffect(()=>{const W=v?.reloadCounter??0;W!==_e.current&&(_e.current=W,W!==0&&(Q.current={layoutName:null,pushed:!1},k.current=[],c?.([])))},[v?.reloadCounter,c]);const ge=u.useCallback((W,X,R)=>{if(!v)return;let de=[];v.patchLayout(ke=>{const xe={children:ke},Ee=Ha(xe,X),Fe=R<0?Array.isArray(Ee?.children)?Ee.children.length:0:R,gt=Qi(xe,X,Fe,W).children??[];return de=gt,gt}),b.push({actionKind:"insert",label:`insert ${W.name??W.type??""}`,snapshot:de});const ye=Ha({children:de},X),we=R<0?Array.isArray(ye?.children)?ye.children.length-1:0:R,Te=at([...X,we]);if(k.current=[...k.current,Te],c?.(k.current),t){const ke=`[data-editor-path="${an(Te)}"]`,xe=()=>{const Ee=t.querySelector(ke);Ee&&typeof Ee.scrollIntoView=="function"&&Ee.scrollIntoView({behavior:"smooth",block:"center",inline:"nearest"})};typeof window<"u"&&typeof window.requestAnimationFrame=="function"?window.requestAnimationFrame(()=>window.requestAnimationFrame(xe)):setTimeout(xe,32)}},[v,b,c,t]);Qe.current=ge;const Ae=u.useCallback(()=>{if(!v||!C.selectedNode||!U)return;const W=es(C.selectedNode);let X=[];v.patchLayout(R=>{const de={children:R},ye=U.slice(0,-1),we=U[U.length-1]??0,ke=Qi(de,ye,we+1,W).children??[];return X=ke,ke}),b.push({actionKind:"insert",label:"duplicate",snapshot:X})},[v,C.selectedNode,U,b]),Ne=u.useCallback(()=>{if(!v||!U)return;let W=[];v.patchLayout(X=>{const ye=Ri({children:X},U).children??[];return W=ye,ye}),b.push({actionKind:"remove",label:"remove",snapshot:W}),C.clearSelection()},[v,U,b,C]),Le=u.useMemo(()=>{if(!C.selectedNode||!U||U.some(et))return null;const W=C.selectedNode;if(!Array.isArray(W.children))return null;const X=gC(f.previewDevice,W,f.previewCustomWidth);return{mode:X.mode,key:X.key,sourceKey:X.sourceKey}},[C.selectedNode,U,f.previewDevice,f.previewCustomWidth]),q=u.useCallback(()=>{if(!v||!U||Le?.mode!=="separate")return;const W=Le.key,X=Le.sourceKey;let R=[];v.patchLayout(de=>(R=Eb({children:de},U,W,X).children??[],R)),b.push({actionKind:"insert",label:"separate_branch",snapshot:R})},[v,U,Le,b]),ae=u.useCallback(()=>{if(!v||!U||Le?.mode!=="merge")return;const W=Le.key;let X=[];v.patchLayout(R=>(X=Ib({children:R},U,W).children??[],X)),b.push({actionKind:"remove",label:"merge_branch",snapshot:X})},[v,U,Le,b]),pe=u.useCallback(W=>{switch(W){case"mobile":return g("layout_editor.context_menu.device_mobile");case"portable":return g("layout_editor.context_menu.device_portable");case"tablet":return g("layout_editor.context_menu.device_tablet");case"desktop":return g("layout_editor.context_menu.device_desktop");default:return g("layout_editor.context_menu.device_custom",{range:W})}},[g]),$e=u.useMemo(()=>{if(!Le)return null;const W=pe(Le.key);return{separate:g("layout_editor.context_menu.separate_branch_device",{device:W}),merge:g("layout_editor.context_menu.merge_branch_device",{device:W})}},[Le,g,pe]),be=u.useMemo(()=>{if(!C.selectedNode||!U)return[];if(U.some(et))return[];const W=Ur(f.previewDevice,C.selectedNode,f.previewCustomWidth);return V6(C.selectedNode,W).map(X=>({key:X,label:pe(X)}))},[C.selectedNode,U,f.previewDevice,f.previewCustomWidth,pe]),j=u.useCallback(W=>{if(W==="desktop"||W==="tablet"||W==="mobile"||W==="portable"){m.setDevice(W);return}m.setDevice(W)},[m]),oe=u.useCallback(()=>{C.selectedNode&&ag(C.selectedNode)},[C.selectedNode]),ue=u.useCallback(()=>{C.selectedNode&&(ag(C.selectedNode),Ne())},[C.selectedNode,Ne]),J=u.useCallback(()=>{if(!v)return;const W=HT();if(!W)return;const X=es(W);if(U&&U.length>0){const R=U.slice(0,-1),de=(U[U.length-1]??0)+1;ge(X,R,de)}else ge(X,[],w.length)},[v,U,ge,w.length]),le=u.useCallback((W,X)=>{b.push({actionKind:"move",label:X,snapshot:W})},[b]),Ie=u.useCallback(()=>{if(C.selectedNavAffordance==="route_in_tree"&&C.selectedNavTargetPath){if(d){d(C.selectedNavTargetPath);return}y({type:"SELECT_ROUTE",route:{path:C.selectedNavTargetPath,layoutName:null}})}},[C.selectedNavAffordance,C.selectedNavTargetPath,d,y]),dt=u.useCallback(()=>{const W=Cb(C.selectedBaseLayout,f.selectedRoute?.layoutName);W&&y({type:"ENTER_BASE_EDIT",layoutName:W})},[C.selectedBaseLayout,f.selectedRoute?.layoutName,y]),pt=u.useCallback(()=>{const W=C.selectedNode?.__source?.extensionId;W!=null&&y({type:"ENTER_EXTENSION_EDIT",extensionId:String(W)})},[C.selectedNode,y]),ot=u.useCallback(()=>{const W=C.selectedIterationSourcePath;W&&y({type:"ENTER_ITERATION_ITEM_EDIT",sourcePath:W})},[C.selectedIterationSourcePath,y]),Oe=u.useRef(null),He=u.useRef(null),Ye=u.useRef(null),Tt=u.useRef({colorScheme:"base",breakpoint:"base"}),Dt=Oe.current!==null?x.stack.some(W=>W.id===Oe.current):!1,Nt=u.useMemo(()=>zd(f.routeTree,h),[f.routeTree,h]),xn=u.useMemo(()=>Md(v?.document?.raw),[v?.document?.raw]),wn=u.useMemo(()=>Ld(s),[s?.states]),Zt=u.useMemo(()=>{const W=v?.document?.raw,X=Ef(W?.components),R=W?.initIsolated,de=R&&typeof R=="object"&&!Array.isArray(R)?Object.keys(R):[];return ZI(X,de)},[v?.document?.raw]),Hn=u.useMemo(()=>{const W=v?.document?.raw;return Ef(W?.components).map(X=>X.scopeId).filter(X=>typeof X=="string"&&X.length>0)},[v?.document?.raw]),ln=Nd({raw:v?.document?.raw,spec:s}),Ii=u.useCallback((W,X)=>{if(!v)return;const R={children:Me.current.components},de=We(R,W);if(!de)return;Tt.current={colorScheme:f.previewColorScheme==="dark"?"dark":"base",breakpoint:Ur(f.previewDevice,de,f.previewCustomWidth)};const ye=t?.getBoundingClientRect(),we=A&&ye?{left:ye.left+A.left,top:ye.top+A.top,width:A.width,height:A.height}:void 0,Te=re,ke=W.find(et),xe=ke?pe(ke.responsive):null,Ee=W.filter(kn=>typeof kn=="number"),Fe=lb({children:Me.current.components},Ee,db(ln)),zt=Fe.length>0?[...ji({iterationVars:Fe}),...ln]:ln,gt=(kn,iM)=>u.createElement(ST,{key:`pem-${at(He.current??[])}`,node:kn,spec:s,manifest:Me.current.manifest,t:h,permissionCandidates:a,pageCandidates:Nt,dataSourceCandidates:xn,stateKeyCandidates:wn,bindingCandidates:zt,liveElement:Te,templateIdentifier:f.templateIdentifier,initialTab:iM,branchLabel:xe,isolatedScopeIdCandidates:Zt,usedScopeIds:Hn,onSaveInjectedProps:async(bt,Sn)=>{const Ai=typeof kn.id=="string"?kn.id:"",Fn=await BT(f.templateIdentifier,bt,Ai,Sn);if(st({op:"save",layoutName:`extension:${bt}`,editMode:f.editMode,saveTarget:"layout_extension",endpoint:`/api/admin/templates/${f.templateIdentifier}/layout-extensions/${bt}`,isDirty:!0,timestamp:Date.now()}),Fn.kind!=="success")throw new Error(Fn.kind==="conflict"?h("layout_editor.save.conflict"):Fn.kind==="injection_not_found"?"injection not found":Fn.kind==="not_found"?"extension not found":Fn.message??"save failed")},scope:Tt.current,onScopeChange:bt=>{Tt.current=bt;const Sn=Oe.current;Sn&&x.open({id:Sn,ariaLabel:h("layout_editor.property_modal.title"),width:420,draggable:!0,avoidRect:we,content:gt(kn),onClose:()=>{Oe.current=null,He.current=null}})},onPatchNode:bt=>{let Sn=[];v.patchLayout(Fn=>(Sn=Ot({children:Fn},W,()=>bt).children??[],Sn)),b.push({actionKind:"inline_text_edit",label:"edit_props",snapshot:Sn}),sr({source:"style_control",componentPath:at(W),componentName:typeof bt.name=="string"?bt.name:null,patchKey:null,timestamp:Date.now()});const Ai=Oe.current;Ai&&x.open({id:Ai,ariaLabel:h("layout_editor.property_modal.title"),width:420,draggable:!0,avoidRect:we,content:gt(bt),onClose:()=>{Oe.current=null,He.current=null}})},onDelete:()=>{const bt=Oe.current;bt&&x.close(bt),Ne()},onClose:()=>{const bt=Oe.current;bt&&x.close(bt)}});He.current=W;const Mt=x.open({ariaLabel:h("layout_editor.property_modal.title"),width:420,draggable:!0,avoidRect:we,content:gt(de,X),onClose:()=>{Oe.current=null,He.current=null,Ye.current=null}});Oe.current=Mt,Ye.current=kn=>{x.open({id:Mt,ariaLabel:h("layout_editor.property_modal.title"),width:420,draggable:!0,avoidRect:we,content:gt(kn),onClose:()=>{Oe.current=null,He.current=null,Ye.current=null}})}},[v,s,h,a,Nt,xn,wn,ln,re,b,x,Ne,t,A,f.previewColorScheme,f.previewDevice,f.previewCustomWidth,pe]);u.useEffect(()=>{const W=Ye.current,X=He.current;if(!W||!X)return;const R={children:Me.current.components},de=We(R,X);de?W(de):Oe.current&&x.close(Oe.current)},[w]);const Fz=()=>{if(!Dt)return null;const X={position:"absolute",background:"rgba(15, 23, 42, 0.45)",pointerEvents:"auto",zIndex:c7},R=ke=>{ke.preventDefault(),ke.stopPropagation()};if(!A)return i.jsx("div",{"data-testid":"g7le-edit-lock-full",onClick:R,onMouseMove:R,style:{...X,inset:0}});const{left:de,top:ye,width:we,height:Te}=A;return i.jsxs("div",{"data-testid":"g7le-edit-lock",style:{position:"absolute",inset:0,pointerEvents:"none"},children:[i.jsx("div",{"data-testid":"g7le-edit-lock-top",onClick:R,onMouseMove:R,style:{...X,left:0,top:0,right:0,height:Math.max(0,ye)}}),i.jsx("div",{"data-testid":"g7le-edit-lock-bottom",onClick:R,onMouseMove:R,style:{...X,left:0,top:ye+Te,right:0,bottom:0}}),i.jsx("div",{"data-testid":"g7le-edit-lock-left",onClick:R,onMouseMove:R,style:{...X,left:0,top:ye,width:Math.max(0,de),height:Te}}),i.jsx("div",{"data-testid":"g7le-edit-lock-right",onClick:R,onMouseMove:R,style:{...X,left:de+we,top:ye,right:0,height:Te}})]})},ml=u.useCallback(()=>{if(U&&mp(C.selectedLockKind)){if(p&&C.selectedPath){p(C.selectedPath);return}Ii(U)}},[U,C.selectedPath,C.selectedLockKind,p,Ii]),{scale:vl}=m,_l=typeof C.selectedNode?.name=="string"?C.selectedNode.name:"",Ph=s?.componentCapabilities?.[_l]??null,Oh=Array.isArray(Ph?.styleControls)?Ph.styleControls:[],Kz=Oh.includes("width")?Ct(s,"width"):null,Vz=Oh.includes("height")?Ct(s,"height"):null,Uz=u.useCallback((W,X)=>{if(!v||!U)return;let R=[];v.patchLayout(de=>(R=Ot({children:de},U,()=>W).children??[],R)),sr({source:"resize",componentPath:at(U),componentName:_l||null,patchKey:X==="height"?"height":"width",resizeAxis:X,timestamp:Date.now()})},[v,U,_l]),qz=u.useCallback((W,X)=>{if(!v||!U)return;let R=[];v.patchLayout(de=>(R=Ot({children:de},U,()=>W).children??[],R)),b.push({actionKind:"inline_text_edit",label:`resize ${X}`,snapshot:R})},[v,U,b]),Gz=u.useCallback(()=>{if(!re)return null;const W=re.getBoundingClientRect(),X=vl||1;return{width:W.width/X,height:W.height/X}},[re,vl]),ir=VT({node:C.selectedNode,widthControl:Kz,heightControl:Vz,scale:vl,measureStartSize:Gz,onResize:Uz,onResizeEnd:qz}),Yz=u.useCallback((W,X)=>{ir.onHandlePointerDown(W,X)},[ir]),xl=u.useCallback(()=>{const W=b.undo();!W||!v||v.setLayoutComponents(W.snapshot)},[b,v]),wl=u.useCallback(()=>{const W=b.redo();!W||!v||v.setLayoutComponents(W.snapshot)},[b,v]);l7({onUndo:xl,onRedo:wl});const Xz=u.useMemo(()=>({copy:oe,cut:ue,paste:J,delete:Ne,openProps:ml,selectParent:Z?se:void 0,deselect:C.clearSelection}),[oe,ue,J,Ne,ml,Z,se,C.clearSelection]);ep({handlers:Xz,hasSelection:!!C.selectedPath}),u.useEffect(()=>(window.__g7LayoutEditorHasSelection=!!C.selectedPath,()=>{try{delete window.__g7LayoutEditorHasSelection}catch{}}),[C.selectedPath]),u.useEffect(()=>(window.__g7LayoutEditorHistory={undo:xl,redo:wl,canUndo:b.canUndo,canRedo:b.canRedo},()=>{try{delete window.__g7LayoutEditorHistory}catch{}}),[xl,wl,b.canUndo,b.canRedo]);const Jz=u.useCallback(W=>{const X=P;F(null),X&&(async()=>{if((await I.commit(X.path,W)).kind==="created"&&v){const de=v.document?.raw?.components??[];b.push({actionKind:"inline_text_edit",label:"inline_create_key",snapshot:de})}})()},[P,I,v,b]),Bh=u.useCallback(()=>{const W=P;F(null),I.trackCancel(W?.path??null)},[P,I]),Zz=u.useCallback(()=>{F(null)},[]),Qz=u.useCallback(async W=>{const X=P;if(!X)return null;const R=await I.keyifyChipValue(X.path,W);if(R.kind!=="created"||!R.translationKey)return null;const de=v?.document?.raw?.components??[];return b.push({actionKind:"inline_text_edit",label:"inline_keyify_chip",snapshot:de}),R.translationKey},[P,I,v,b]),Rz=u.useCallback(()=>{const W=P;W&&(F(null),C.handleSelect("",{dataset:{editorPath:W.domPath}}),Ii(W.path,"translation"))},[P,C,Ii]),xt=u.useMemo(()=>{if(!P)return null;const W=v?.document?.raw?.components??w;return We({children:W},P.path)},[P,v?.document,w]),eM=u.useCallback(async(W,X)=>{const R=P;if(!v||!R)return null;const de=typeof xt?.text=="string"?xt.text:"";let ye;if(R.isParamKey)ye=await wr(f.templateIdentifier,f.locale,de,W,X.sourceId,X.path,"scalar");else{const Fe=zt=>zt.replace(/\$t:[a-zA-Z0-9._-]+/g,gt=>{const Mt=h(gt.slice(3));return Mt&&Mt!==gt.slice(3)&&!Mt.startsWith("$t:")?Mt:""}).replace(/\s+/g," ").trim();ye=await ls(f.templateIdentifier,f.selectedRoute?.layoutName??null,f.locale,de,W,X.sourceId,X.path,"scalar",Fe,h)}if(ye.kind==="error")return null;const we=R.path;let Te=[];v.patchLayout(Fe=>(Te=Ot({children:Fe},we,Mt=>({...Mt,text:ye.text})).children??[],Te)),b.push({actionKind:"inline_text_edit",label:"inline_insert_binding",snapshot:Te});const ke=ye.kind==="ok"&&"key"in ye?ye.key:void 0;!R.isParamKey&&ke&&F(Fe=>Fe&&Fe.path===we?{...Fe,isParamKey:!0,customKey:ke,chipValue:void 0,chipParamLabels:void 0}:Fe);try{window.dispatchEvent(new CustomEvent(kt,{detail:{templateIdentifier:f.templateIdentifier,locale:f.locale}}))}catch{}const xe=mt(ye.text),Ee=R.customKey??xe?.key??"";return Ee?It(Ee,f.locale)??null:null},[v,P,xt,f.templateIdentifier,f.locale,f.selectedRoute,b,h]),tM=u.useCallback(async W=>{const X=P;if(!v||!X)return null;const R=typeof xt?.text=="string"?xt.text:"",de=mt(R),ye=X.customKey??de?.key??null,we=mo(R,W),Te=X.path;let ke=[];return v.patchLayout(xe=>(ke=Ot({children:xe},Te,zt=>({...zt,text:we})).children??[],ke)),b.push({actionKind:"inline_text_edit",label:"inline_remove_binding",snapshot:ke}),ye&&await kr(f.templateIdentifier,ye,W,f.locale),ye?It(ye,f.locale)??null:null},[v,P,xt,f.templateIdentifier,f.locale,b]),[kl,Wh]=u.useState({}),nM=["fontSize","color","fontStyle","textDecoration","textAlign","letterSpacing","lineHeight","fontFamily"];u.useEffect(()=>{if(!P||!t||typeof MutationObserver>"u"){Wh({});return}const W=`[data-editor-path="${an(P.domPath)}"]`,X=()=>{const ye=t.querySelector(W);if(!ye)return;const we={};for(const ke of nM){const xe=ye.style.getPropertyValue(ke.replace(/([A-Z])/g,"-$1").toLowerCase());xe&&(we[ke]=xe)}const Te=gg(t,P.domPath)??void 0;Wh({className:ye.className,style:we,effectiveColor:Te})};X();const R=t.querySelector(W);if(!R)return;const de=new MutationObserver(X);return de.observe(R,{attributes:!0,attributeFilter:["class","style"]}),de.observe(t,{childList:!0,subtree:!0}),()=>de.disconnect()},[P?.path?at(P.path):null,t]);const jh=u.useCallback((W,X)=>{if(!v||!P)return;const R=P.path;let de=[],ye=null;v.patchLayout(we=>(de=Ot({children:we},R,xe=>{const Ee=X(xe);return ye=typeof Ee.name=="string"?Ee.name:null,Ee}).children??[],de)),b.push({actionKind:"inline_text_edit",label:`inline_format ${W}`,snapshot:de}),sr({source:"style_control",componentPath:at(R),componentName:ye,patchKey:W,timestamp:Date.now()})},[v,P,b]),ft=u.useMemo(()=>{const W=C.selectedPath;if(!W||!_)return null;const X=[];let R=W;for(;R;){X.push(R);const de=R.match(/^(.*)\.children\.\d+$/);R=de?de[1]:null}for(const de of X){const ye=Ze(de);if(!ye)continue;const we=We(_,ye),Te=typeof we?.name=="string"?we.name:null;if(!Te)continue;const ke=Oi(s,Te),xe=ke?.canvasOverlay?.kind;if(typeof xe!="string"||!xe||!we)continue;const Ee=ke.canvasOverlay.params&&typeof ke.canvasOverlay.params=="object"?ke.canvasOverlay.params:void 0,Fe=de===W?null:W.slice(de.length+1);return{kind:xe,params:Ee,tablePath:de,tableNode:we,selectedCellRel:Fe}}return null},[C.selectedPath,_,s]),ho=ft?WT(ft.kind):null,Hh=u.useMemo(()=>!ho||!t||!ft?null:po(t,ft.tablePath),[ho,t,ft,A,w]),oM=u.useMemo(()=>{if(!ho||!t||!ft)return[];const W=ft.tablePath,X=an(W),R=[];return t.querySelectorAll(`[data-editor-path^="${X}.children."]`).forEach(we=>{const Te=we.dataset.editorPath??"";if(!Te.startsWith(W+"."))return;const ke=Te.slice(W.length+1),xe=so(we,t);xe&&R.push({path:ke,top:xe.top,left:xe.left,width:xe.width,height:xe.height})}),t.querySelectorAll(`[data-editor-item-path^="${X}."]`).forEach(we=>{const Te=we.dataset.editorItemPath??"";if(!Te.startsWith(W+"."))return;const ke=Te.slice(W.length+1),xe=so(we,t);xe&&R.push({path:ke,top:xe.top,left:xe.left,width:xe.width,height:xe.height})}),R},[ho,t,ft,A,w]),rM=u.useCallback(W=>{const X=ft?Ze(ft.tablePath):null;if(!v||!X)return;let R=[];v.patchLayout(de=>(R=Ot({children:de},X,()=>W).children??[],R)),b.push({actionKind:"inline_text_edit",label:"inplace_edit",snapshot:R}),sr({source:"style_control",componentPath:ft?.tablePath??"",componentName:typeof W.name=="string"?W.name:null,patchKey:"canvas_inplace",timestamp:Date.now()})},[v,U,b]);return t?i.jsxs("div",{className:"g7le-editor-canvas-overlay","data-testid":"g7le-editor-canvas-overlay",style:{position:"absolute",inset:0,pointerEvents:"none"},children:[!D&&Fz(),D&&i.jsxs("div",{"data-testid":"g7le-canvas-pick-banner",style:{position:"absolute",top:8,left:"50%",transform:"translateX(-50%)",zIndex:9999,display:"flex",alignItems:"center",gap:12,padding:"8px 14px",background:"#1e293b",color:"#fff",borderRadius:8,fontSize:13,boxShadow:"0 4px 12px rgba(0,0,0,0.25)",pointerEvents:"auto"},children:[i.jsxs("span",{children:["🎯 ",h("layout_editor.target_picker.canvas_pick_hint")]}),i.jsx("button",{type:"button","data-testid":"g7le-canvas-pick-cancel",onClick:()=>Y(D,null),style:{border:"1px solid #475569",borderRadius:6,background:"transparent",color:"#cbd5e1",cursor:"pointer",fontSize:12,padding:"3px 10px"},children:h("layout_editor.target_picker.cancel")})]}),f.editMode!=="route"&&i.jsx(SI,{frameEl:t,components:w,editMode:f.editMode,currentExtensionId:S,editableRootPath:E}),P&&((P.isParamKey&&P.customKey||P.chipValue)&&xt?i.jsxs(i.Fragment,{children:[xt&&i.jsx(ug,{node:xt,spec:s,t:h,onApplyControl:jh,box:P.box,scope:{colorScheme:f.previewColorScheme==="dark"?"dark":"base",breakpoint:Ur(f.previewDevice,xt,f.previewCustomWidth)}}),i.jsx(n4,{box:P.box,node:xt,customKey:P.customKey??null,initialChipValue:P.chipValue,chipParamLabels:P.chipParamLabels,onKeyify:Qz,templateIdentifier:f.templateIdentifier,locale:f.locale,t:h,candidates:ln,onInsertBinding:eM,onRemoveBinding:tM,onCommit:Zz,onCancel:Bh})]}):i.jsxs(i.Fragment,{children:[xt&&i.jsx(ug,{node:xt,spec:s,t:h,onApplyControl:jh,box:P.box,scope:{colorScheme:f.previewColorScheme==="dark"?"dark":"base",breakpoint:xt?Ur(f.previewDevice,xt,f.previewCustomWidth):"base"}}),i.jsx(t4,{box:P.box,initialValue:P.initialValue,isCustomKey:P.isCustomKey,t:h,onCommit:Jz,onCancel:Bh,mirrorClassName:kl.className,mirrorStyle:kl.style,nodeEffectiveColor:kl.effectiveColor??P.nodeEffectiveColor,onOpenTranslations:Rz})]})),!D&&i.jsx(y7,{hoverBox:N,selectedBox:A,lockKind:C.selectedLockKind,navAffordance:C.selectedNavAffordance,onEditProps:ml,onDuplicate:Ae,onDelete:Ne,onSeparateBranch:Le?.mode==="separate"?q:void 0,separateBranchLabel:$e?.separate,onMergeBranch:Le?.mode==="merge"?ae:void 0,mergeBranchLabel:$e?.merge,definedDeviceBranches:be,onJumpToDevice:be.length>0?j:void 0,onLinkEditDestination:Ie,onEditBase:dt,onEditExtension:pt,extensionLabel:ze,baseLayoutLabel:C.selectedBaseLayout,dataSourceLabel:C.selectedDataSourceId,branchLabel:ee,onEditIteration:C.selectedIsIteration&&f.editMode!=="iteration_item"?ot:void 0,resizeEnabledAxes:ir.enabledAxes,onResizeHandlePointerDown:ir.enabledAxes.width||ir.enabledAxes.height?Yz:void 0,selectedName:Ce,onSelectParent:Z?se:void 0}),D&&B&&i.jsx("div",{"data-testid":"g7le-canvas-pick-hover","data-pick-has-id":B.hasId?"true":"false",style:{position:"absolute",left:B.box.left,top:B.box.top,width:B.box.width,height:B.box.height,pointerEvents:"none",zIndex:9998,boxSizing:"border-box",border:B.hasId?"2px solid #2563eb":"2px dashed #94a3b8",background:B.hasId?"rgba(37,99,235,0.10)":"repeating-linear-gradient(45deg, rgba(148,163,184,0.18) 0, rgba(148,163,184,0.18) 6px, transparent 6px, transparent 12px)",cursor:B.hasId?"pointer":"not-allowed"},children:!B.hasId&&i.jsx("span",{"data-testid":"g7le-canvas-pick-hover-hint",style:{position:"absolute",top:0,left:0,transform:"translateY(-100%)",background:"#475569",color:"#fff",fontSize:11,lineHeight:1.4,padding:"2px 6px",borderRadius:4,whiteSpace:"nowrap"},children:h("layout_editor.target_picker.needs_id")})}),!D&&i.jsx(w7,{selectedBox:qe,points:Se.points,onAddRequest:Re}),ho&&ft&&Hh&&C.selectedLockKind==="none"&&i.jsx(ho,{node:ft.tableNode,params:ft.params,nodeBox:Hh,cellBoxes:oM,selectedCellPath:ft.selectedCellRel??void 0,colorScheme:f.previewColorScheme==="dark"?"dark":"light",t:h,onPatchNode:rM,onInsertChild:ge,onRemoveChild:W=>{const X=Ze(W);if(!v||!X)return;let R=[];v.patchLayout(de=>(R=Ri({children:de},X).children??[],R)),b.push({actionKind:"remove",label:"inplace_remove",snapshot:R})},onRequestInlineEdit:W=>{ft&&G(`${ft.tablePath}.${W}`)}}),v&&i.jsx(mI,{frameEl:t,nesting:o,editMode:f.editMode,currentExtensionId:S,editableRootPath:E,components:w,patchLayout:v.patchLayout,pushHistory:le,selectedPath:C.selectedPath,onSelectPath:W=>C.handleSelect("",{dataset:{editorPath:W}}),onMovePath:W=>C.handleSelect("",{dataset:{editorPath:W}}),onRequestInlineEdit:G})]}):null}function fg(e,t){if(t.length===0)return null;const o=Ha({children:e},t);return o&&typeof o.name=="string"?o.name:null}function Ha(e,t){return We(e,t)}function an(e){return typeof CSS<"u"&&typeof CSS.escape=="function"?CSS.escape(e):e.replace(/[^a-zA-Z0-9_\-.]/g,t=>`\\${t}`)}function gg(e,t){if(!t)return null;const n=e.querySelector(`[data-editor-path="${an(t)}"]`);if(!n||typeof window.getComputedStyle!="function")return null;let o=n;const r=n.ownerDocument.createTreeWalker(n,NodeFilter.SHOW_TEXT);let s=r.nextNode();for(;s;){if((s.textContent??"").trim().length>0){const a=s.parentElement;if(a&&n.contains(a)){o=a;break}}s=r.nextNode()}try{return window.getComputedStyle(o).color||null}catch{return null}}function po(e,t){if(!t)return null;const n=e.querySelector(`[data-editor-path="${an(t)}"]`);if(n){const c=so(n,e);return c&&ao(c,e)?c:null}const o=e.querySelectorAll(`[data-editor-path^="${an(t)}.iteration."]`);let r=1/0,s=1/0,a=-1/0,l=-1/0,d=!1;return o.forEach(c=>{const p=c.dataset.editorPath??"";if(!new RegExp("\\.iteration\\.\\d+$").test(p))return;const f=so(c,e);f&&ao(f,e)&&(d=!0,r=Math.min(r,f.left),s=Math.min(s,f.top),a=Math.max(a,f.left+f.width),l=Math.max(l,f.top+f.height))}),d?{left:r,top:s,width:a-r,height:l-s,scale:1}:null}function c4({isEditMode:e,editorSpec:t,sampleOverride:n}){const o=t?.sampleData;return u.useMemo(()=>Mm({spec:o,overrideSpec:n,onMatch:r=>{Kh(r)}}),[e,o,n])}const u4=Lt().ComponentRegistry;function p4(e){return e.split(/[-_]/).map(t=>t.charAt(0).toUpperCase()+t.slice(1).toLowerCase()).join("")}async function f4(e){const t=typeof window<"u"?window.localStorage?.getItem("auth_token"):null,n={Accept:"application/json"};t&&(n.Authorization=`Bearer ${t}`);const o=await fetch(`/api/admin/templates/${encodeURIComponent(e)}/editor-assets`,{headers:n,credentials:"same-origin"});if(!o.ok){const s=await o.json().catch(()=>null);throw Gr(o.status,s,"assets")}const r=await o.json();return r.data||r}async function g4(e){try{if(typeof fetch!="function")return 0;const t=await fetch(en(`/api/templates/${encodeURIComponent(e)}/config`,"json"),{credentials:"same-origin",headers:{Accept:"application/json"}});if(!t.ok)return 0;const n=await t.json().catch(()=>null),o=n?.data?.cache_version??n?.cache_version;return typeof o=="number"&&o>0?o:0}catch{return 0}}function h4(e){return new Promise((t,n)=>{if(typeof document>"u"){n(new Error("document undefined"));return}if(document.querySelector(`script[data-g7le-asset="${e}"]`)){t();return}const r=document.createElement("script");r.src=e,r.async=!1,r.setAttribute("data-g7le-asset",e),r.addEventListener("load",()=>t()),r.addEventListener("error",()=>n(new Error(`script load failed: ${e}`))),document.head.appendChild(r)})}async function y4(e){if(typeof document>"u")return;const t=`[data-g7le-asset="${e}"]`;if(document.querySelector(`link${t}, style${t}`))return;if(e.includes("/api/admin/"))try{const r=typeof window<"u"?window.localStorage?.getItem("auth_token"):null,s={Accept:"text/css"};r&&(s.Authorization=`Bearer ${r}`);const a=await fetch(e,{headers:s,credentials:"same-origin"});if(a.ok){const l=await a.text(),d=document.createElement("style");d.textContent=l,d.setAttribute("data-g7le-asset",e),document.head.appendChild(d);return}}catch{}const o=document.createElement("link");o.rel="stylesheet",o.href=e,o.setAttribute("data-g7le-asset",e),document.head.appendChild(o)}function b4(e,t,n="user"){const[o,r]=u.useState({componentRegistry:null,translationEngine:null,isReady:!1,error:null});return u.useEffect(()=>{let s=!1;return(async()=>{r(a=>({...a,isReady:!1,error:null}));try{const a=await f4(e);if(s)return;for(const y of a.css??[])if(await y4(y),s)return;const l=p4(e);if(!(typeof window<"u"&&typeof window[l]=="object")){for(const y of a.js??[])if(await h4(y),s)return}const c=u4.createIsolatedInstance();if(await c.loadComponents(e,n),s)return;const p=await g4(e);if(s)return;const f=new Pt({defaultLocale:t});if(p>0&&f.setCacheVersion(p),await f.loadTranslations(e,t),s)return;cd(f,e);try{const y=Pt.getInstance();p>y.getCacheVersion()&&y.setCacheVersion(p),await y.loadTranslations(e,t),cd(y,e)}catch{}if(s)return;r({componentRegistry:c,translationEngine:f,isReady:!0,error:null})}catch(a){if(s)return;const l=fp(a)?a:io(a,"assets");r({componentRegistry:null,translationEngine:null,isReady:!1,error:l})}})(),()=>{s=!0}},[e,t,n]),o}const hg=Lt().AuthManager;function m4(e){switch(e){case"unauthorized":return{icon:"🔒",tone:"blue",titleKey:"layout_editor.access_error.unauthorized.title",messageKey:"layout_editor.access_error.unauthorized.message",showRequiredPermissions:!0};case"forbidden":return{icon:"⛔",tone:"rose",titleKey:"layout_editor.access_error.forbidden.title",messageKey:"layout_editor.access_error.forbidden.message",showRequiredPermissions:!0};case"not_found":return{icon:"🗂️",tone:"slate",titleKey:"layout_editor.access_error.not_found.title",messageKey:"layout_editor.access_error.not_found.message",showRequiredPermissions:!1};case"server_error":return{icon:"🛠️",tone:"amber",titleKey:"layout_editor.access_error.server_error.title",messageKey:"layout_editor.access_error.server_error.message",showRequiredPermissions:!1};case"network":return{icon:"📡",tone:"amber",titleKey:"layout_editor.access_error.network.title",messageKey:"layout_editor.access_error.network.message",showRequiredPermissions:!1};default:return{icon:"⚠️",tone:"slate",titleKey:"layout_editor.access_error.unknown.title",messageKey:"layout_editor.access_error.unknown.message",showRequiredPermissions:!1}}}const v4={blue:{bg:"#eff6ff",border:"#bfdbfe",iconBg:"#dbeafe"},rose:{bg:"#fff1f2",border:"#fecdd3",iconBg:"#ffe4e6"},amber:{bg:"#fffbeb",border:"#fde68a",iconBg:"#fef3c7"},slate:{bg:"#f8fafc",border:"#e2e8f0",iconBg:"#f1f5f9"}};function _4({error:e}){return u.useEffect(()=>{if(e.kind!=="unauthorized"||typeof window>"u")return;const t=hg.getInstance(),n=`${window.location.pathname}${window.location.search}`,o=t.getLoginRedirectUrl("admin",n,"session_expired"),r=window.setTimeout(()=>{window.location.href=o},600);return()=>window.clearTimeout(r)},[e.kind]),null}function Fa({error:e}){const{t}=tt(),n=m4(e.kind),o=v4[n.tone],r=()=>{typeof window<"u"&&(window.location.href="/admin")},s=()=>{typeof window<"u"&&window.location.reload()},a=()=>{if(typeof window>"u")return;const l=hg.getInstance(),d=`${window.location.pathname}${window.location.search}`;window.location.href=l.getLoginRedirectUrl("admin",d)};return i.jsxs("div",{className:"g7le-access-error","data-testid":"g7le-access-error","data-error-kind":e.kind,style:{flex:1,display:"flex",alignItems:"center",justifyContent:"center",padding:24,background:"#f1f5f9"},children:[i.jsx(_4,{error:e}),i.jsxs("div",{style:{maxWidth:480,width:"100%",background:o.bg,border:`1px solid ${o.border}`,borderRadius:12,padding:"28px 32px",boxShadow:"0 2px 8px rgba(15, 23, 42, 0.05)",textAlign:"center"},children:[i.jsx("div",{style:{width:64,height:64,margin:"0 auto 16px",borderRadius:"50%",background:o.iconBg,display:"flex",alignItems:"center",justifyContent:"center",fontSize:32,lineHeight:1},"aria-hidden":"true",children:n.icon}),i.jsx("h2",{"data-testid":"g7le-access-error-title",style:{margin:"0 0 8px",fontSize:18,fontWeight:700,color:"#0f172a"},children:t(n.titleKey)}),i.jsx("p",{"data-testid":"g7le-access-error-message",style:{margin:"0 0 16px",fontSize:13,lineHeight:1.6,color:"#475569"},children:t(n.messageKey)}),n.showRequiredPermissions&&e.requiredPermissions&&i.jsxs("div",{"data-testid":"g7le-access-error-permissions",style:{display:"inline-flex",flexWrap:"wrap",gap:6,justifyContent:"center",padding:"8px 12px",marginBottom:16,background:"rgba(15, 23, 42, 0.04)",borderRadius:8,fontSize:12,color:"#475569"},children:[i.jsx("span",{style:{fontWeight:600},children:t("layout_editor.access_error.required_permission_label")}),e.requiredPermissions.split(/[,\s]+/).filter(Boolean).map(l=>i.jsx("code",{style:{padding:"2px 6px",background:"#fff",border:"1px solid #e2e8f0",borderRadius:4,fontFamily:"ui-monospace, SFMono-Regular, Menlo, monospace",fontSize:11,color:"#1e293b"},children:l},l))]}),e.message&&i.jsxs("details",{"data-testid":"g7le-access-error-detail",style:{marginBottom:16,fontSize:11,color:"#94a3b8"},children:[i.jsx("summary",{style:{cursor:"pointer"},children:t("layout_editor.access_error.detail_summary")}),i.jsxs("pre",{style:{marginTop:8,padding:8,background:"#fff",border:"1px solid #e2e8f0",borderRadius:6,overflowX:"auto",whiteSpace:"pre-wrap",wordBreak:"break-word",textAlign:"left",fontSize:11,color:"#475569"},children:[e.status>0?`HTTP ${e.status} -`:"",e.message]})]}),i.jsxs("div",{style:{display:"flex",justifyContent:"center",gap:8,flexWrap:"wrap"},children:[e.kind==="unauthorized"&&i.jsx("button",{type:"button","data-testid":"g7le-access-error-action-signin",onClick:a,style:Ka,children:t("layout_editor.access_error.action.sign_in")}),e.kind==="forbidden"&&i.jsx("button",{type:"button","data-testid":"g7le-access-error-action-home",onClick:r,style:Ka,children:t("layout_editor.access_error.action.go_home")}),(e.kind==="not_found"||e.kind==="server_error"||e.kind==="network"||e.kind==="unknown")&&i.jsx("button",{type:"button","data-testid":"g7le-access-error-action-retry",onClick:s,style:Ka,children:t("layout_editor.access_error.action.retry")}),e.kind!=="unauthorized"&&i.jsx("button",{type:"button","data-testid":"g7le-access-error-action-home-secondary",onClick:r,style:x4,children:t("layout_editor.access_error.action.go_admin_home")})]})]})]})}const Ka={padding:"8px 16px",background:"#2563eb",color:"#fff",border:"1px solid #1d4ed8",borderRadius:6,fontSize:13,fontWeight:600,cursor:"pointer"},x4={padding:"8px 16px",background:"#fff",color:"#475569",border:"1px solid #cbd5e1",borderRadius:6,fontSize:13,fontWeight:500,cursor:"pointer"};function w4(e){const t=window;if(typeof t>"u")return{getSnapshot:()=>({globalState:{},localState:{}}),restore:()=>{}};const n=t.G7Core||{},o={templateApp:t.__templateApp,g7CoreState:n.state,g7CoreDispatch:n.dispatch,g7CoreData:n.data,g7CoreModal:n.modal,g7CoreToast:n.toast};let r={...e.initialGlobalState??{}};r._local||(r._local={});const s=[],a=()=>{const c={globalState:{...r},localState:{...r._local??{}}};for(const p of s)try{p(r)}catch{}e.onChange?.(c)},l={},d={getGlobalState:()=>r,setGlobalState:(c,p)=>{typeof c=="function"?r=c(r):r={...r,...c},a()},onGlobalStateChange:c=>(s.push(c),()=>{const p=s.indexOf(c);p>=0&&s.splice(p,1)}),get globalState(){return r},getActionDispatcher:()=>e.actionDispatcher,getDataSource:c=>l[c]??null,setDataSource:(c,p)=>{l[c]=p},refetchDataSource:async c=>null,updateDataSourceItem:(c,p,f)=>l[c]??null,getLocale:()=>e.locale,changeLocale:c=>{},getRouter:()=>({navigate:()=>{},replace:()=>{},push:()=>{}}),updateQueryParams:()=>{}};return t.__templateApp=d,n&&typeof n.dispatch=="function"&&(n.dispatch=(c,p)=>{try{return e.actionDispatcher.executeAction(c,p??{})}catch{return}}),n&&typeof n.modal=="object"&&n.modal!==null&&(n.modal={...n.modal,open:()=>{},close:()=>{},closeAll:()=>{}}),n&&typeof n.toast=="object"&&n.toast!==null&&(n.toast={...n.toast,push:()=>{},dismiss:()=>{}}),{getSnapshot:()=>({globalState:{...r},localState:{...r._local??{}}}),restore:()=>{t.__templateApp===d&&(t.__templateApp=o.templateApp),t.G7Core&&(o.g7CoreState!==void 0&&(t.G7Core.state=o.g7CoreState),o.g7CoreDispatch!==void 0&&(t.G7Core.dispatch=o.g7CoreDispatch),o.g7CoreData!==void 0&&(t.G7Core.data=o.g7CoreData),o.g7CoreModal!==void 0&&(t.G7Core.modal=o.g7CoreModal),o.g7CoreToast!==void 0&&(t.G7Core.toast=o.g7CoreToast)),s.length=0}}}const k4=new Set(["id","userId","postId","orderId","productId","categoryId","commentId","noticeId","inquiryId","addressId","wishlistId","notificationId","roleId","menuId"]);function S4(e){if(!e)return{};const t={},n=/:([a-zA-Z_][a-zA-Z0-9_]*)/g;let o;for(;(o=n.exec(e))!==null;){const r=o[1];t[r]=C4(r)}return t}function C4(e){return k4.has(e)||/Id$|_id$/.test(e)?"1":"sample"}const $4=bo("LayoutEditor");function Jo(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function bi(e){if(Array.isArray(e))return e.map(t=>bi(t));if(Jo(e)){const t={};for(const[n,o]of Object.entries(e))t[n]=bi(o);return t}return e}function yg(e,t="",n=new Set){for(const[o,r]of Object.entries(e)){const s=t?`${t}.${o}`:o;Jo(r)?yg(r,s,n):n.add(s)}return n}function bg(e,t,n,o,r,s=""){for(const[a,l]of Object.entries(t)){const d=s?`${s}.${a}`:a;if(n.has(d)){r(`[LayoutEditor] sampleGlobal key conflict: extension '${o}' override of core key '${d}' ignored`);continue}const c=e[a];Jo(l)&&Jo(c)?bg(c,l,n,o,r,d):e[a]=bi(l)}}function Va(e){const t=e.warn??(r=>$4.warn(r)),n=bi(e.coreSeed??{});e.isGuestOnly&&"currentUser"in n&&delete n.currentUser;const o=yg(n);for(const r of e.sources)Jo(r.sampleGlobal)&&bg(n,r.sampleGlobal,o,r.id,t);return e.isGuestOnly&&"currentUser"in n&&delete n.currentUser,n}function E4(e){const t={};if(!e||typeof e!="object"||Array.isArray(e))return t;for(const[n,o]of Object.entries(e))n.startsWith("$")&&(t[n]=o);return t}function I4(e){const t={};if(!e||typeof e!="object"||Array.isArray(e))return t;for(const[n,o]of Object.entries(e))n.startsWith("$")||(t[n]=o);return t}function A4(e){if(typeof e!="string")return null;const t=/^\s*\{\{\s*([\s\S]*?)\s*\}\}\s*$/.exec(e);if(!t)return null;let n=t[1];const o=n.indexOf("??");return o>=0&&(n=n.slice(0,o)),n=n.replace(/\?\./g,".").replace(/\s+/g,""),n===""||!/^[A-Za-z_$][A-Za-z0-9_$]*(\.[A-Za-z_$][A-Za-z0-9_$]*)*$/.test(n)?null:n.split(".")}function T4(e,t){const n=A4(t);if(!n||n.length===0)return e;let o=e;for(let a=0;ao.map(r=>{if(!r||typeof r!="object")return r;const s=r;let a=s;if(typeof s.slot=="string"&&!t.has(s.slot)){const{slot:l,...d}=s;a={...d,__editorSlotName:l}}if(Array.isArray(a.children)){const l=n(a.children);l!==a.children&&(a={...a,children:l})}return a});return n(e)}function St(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function Xt(e){if(Array.isArray(e))return e.map(t=>Xt(t));if(St(e)){const t={};for(const[n,o]of Object.entries(e))t[n]=Xt(o);return t}return e}function mi(e,t){for(const[n,o]of Object.entries(t)){const r=e[n];St(o)&&St(r)?mi(r,o):e[n]=Xt(o)}}function z4(e){const t=[];let n="";for(let o=0;o0&&(t.push(n),n="");const s=e[o+1];if(s==="'"||s==='"'){const l=e.indexOf(`${s}]`,o+2);if(l!==-1){t.push(e.slice(o+2,l)),o=l+1;continue}}const a=e.indexOf("]",o+1);if(a!==-1){t.push(e.slice(o+1,a)),o=a;continue}n+=r}else r==="."?n.length>0&&(t.push(n),n=""):n+=r}return n.length>0&&t.push(n),t.filter(o=>o.length>0)}function Ua(e,t,n){const o=z4(t);if(o.length===0)return;let r=e;for(let s=0;s0?t:void 0}const Ga="g7le:editor-t-ready",_g="data-g7le-vh-expanded";function L4(e,t){if(!e||!t)return;const n=t;e.querySelectorAll("*").forEach(r=>{const s=window.getComputedStyle(r),a=parseFloat(s.height),l=Number.isFinite(a)&&Math.abs(a-n)<=2,d=parseFloat(s.minHeight),c=Number.isFinite(d)&&Math.abs(d-n)<=2,p=s.overflowY==="auto"||s.overflowY==="scroll"||s.overflowY==="hidden"||s.overflow==="auto"||s.overflow==="scroll"||s.overflow==="hidden";l&&p?(r.style.setProperty("height","auto","important"),c&&r.style.setProperty("min-height","0","important"),r.style.setProperty("overflow","visible","important"),r.setAttribute(_g,"1")):(s.overflowY==="auto"||s.overflowY==="scroll")&&r.scrollHeight>r.clientHeight+4&&r.clientHeight>120&&(r.style.setProperty("overflow","visible","important"),r.style.setProperty("height","auto","important"),r.setAttribute(_g,"1"))})}const P4=[];function O4(e={}){const{manifest:t=null,nesting:n=null,componentPalette:o=null,spec:r=null,permissionCandidates:s,sampleGlobalSources:a=P4,resolveRouteMatch:l,onNavigateToDestination:d,onSessionAddedPathsChange:c}=e,{state:p}=Je(),f=Ft(),y=kp(),{document:g,isLoading:m,error:h}=f??y,v=u.useRef(null),[b,x]=u.useState(null),{deviceWidth:k,scale:w,colorScheme:_}=ja(),S=u.useMemo(()=>p.activeStateId?p.availableStates.find(Q=>Q.id===p.activeStateId)??null:null,[p.activeStateId,p.availableStates]),E=u.useMemo(()=>vg(S),[S]),C=c4({isEditMode:!0,editorSpec:r??void 0,sampleOverride:E}),{t:N}=tt(),{componentRegistry:$,translationEngine:A,isReady:z,error:D}=b4(p.templateIdentifier,p.locale),O=u.useRef(null);O.current===null&&(O.current=new vd);const M=u.useRef(null);if(M.current===null){M.current=new HE({navigate:()=>{}}),M.current.setPreviewMode(!0);try{const ie=window.__templateApp?.getActionDispatcher?.();if(ie&&typeof ie.getRegisteredHandlers=="function"){const _e=ie.getRegisteredHandlers();for(const ge of _e){if(!ge.includes("."))continue;const Ae=typeof ie.getHandler=="function"?ie.getHandler(ge):void 0;typeof Ae=="function"&&M.current.registerHandler(ge,Ae,{category:"plugin",source:"preview-canvas-cloned-from-host"})}}}catch{}}u.useEffect(()=>{if(!z)return;const Q=M.current;if(Q)try{const ie=window.G7Core?.getActionDispatcher?.();if(!ie||typeof ie.getRegisteredHandlers!="function")return;const _e=ie.getRegisteredHandlers();for(const ge of _e){if(!ge.includes(".")||typeof Q.getHandler=="function"&&Q.getHandler(ge))continue;const Ae=typeof ie.getHandler=="function"?ie.getHandler(ge):void 0;typeof Ae=="function"&&Q.registerHandler(ge,Ae,{category:"plugin",source:"preview-canvas-iife-reclone"})}}catch{}},[z]);const B=u.useMemo(()=>new FE({sampleProvider:C,actionDispatcher:M.current}),[C]),T=u.useMemo(()=>g?.raw?.meta?.guest_only===!0,[g]),H=u.useMemo(()=>Va({sources:a,isGuestOnly:T}),[a,T]),L=u.useMemo(()=>{const Q=B4(qa(S),A,{templateId:p.templateIdentifier,locale:p.locale}),ie=H._local,{global:_e,local:ge}=M4({globalBaseline:H,localBaseline:ie??{},patch:S?.initialState??null,formErrors:Q});return{..._e,_local:ge}},[H,S,A,p.templateIdentifier,p.locale]),I=u.useMemo(()=>!S||!(!!S.initialState?.local||!!qa(S))?null:{...L._local??{},_forceLocalInit:S.id,_merge:"deep"},[S,L]),P=Q=>typeof Q=="object"&&Q!==null&&!Array.isArray(Q),F=u.useMemo(()=>P(S?.initialState?.query)?S.initialState.query:null,[S]),G=u.useMemo(()=>P(S?.initialState?.route)?S.initialState.route:null,[S]),[V,Y]=u.useState(()=>Va({sources:[],isGuestOnly:!1})),Z=u.useRef(null);u.useEffect(()=>{const Q={...L},ie=w4({actionDispatcher:M.current,locale:p.locale,initialGlobalState:Q,onChange:ge=>{Y(ge.globalState)}});Z.current=ie;const _e=window.__templateApp;return _e?.setGlobalState&&M.current.setGlobalStateUpdater((ge,Ae)=>{_e.setGlobalState(ge,Ae)}),Y(Q),$l({kind:"applyPatch",activeStateId:p.activeStateId,routePath:p.selectedRoute?.path??null,hasInitialPatch:!!S?.initialState,hasFormErrors:!!qa(S),hasSampleOverride:!!vg(S)}),()=>{ie.restore(),Z.current=null}},[p.locale,L]),u.useEffect(()=>{if(!A)return;const Q=window.G7Core;if(!Q||typeof Q.t!="function")return;const ie=Q.t,_e=p.templateIdentifier,ge=p.locale;return Q.t=(Ae,Ne)=>{try{const Le=Ne?"|"+Object.entries(Ne).map(([ae,pe])=>`${ae}=${pe}`).join("|"):void 0,q=A.translate(Ae,{templateId:_e,locale:ge},Le);if(q!==Ae&&!q.startsWith("$t:"))return q}catch{}return ie(Ae,Ne)},typeof window<"u"&&window.dispatchEvent(new CustomEvent(Ga)),()=>{window.G7Core?.t&&window.G7Core.t!==ie&&(window.G7Core.t=ie)}},[A,p.templateIdentifier,p.locale]);const se=u.useMemo(()=>{const Q=g?.raw?.components??[];return p.editMode!=="base"?Q:N4(Q)},[g,p.editMode]),re=g?.raw?.__editability,U=g?.raw?.__extensionId,ee=u.useMemo(()=>p.editMode!=="extension"||re!=="ok"?[]:typeof U!="number"?[]:W4(se,U),[p.editMode,re,U,se]),[fe,K]=u.useState(null),[ne,me]=u.useState(!1),Ce=g?.layoutName??null,ze=u.useMemo(()=>{const Q=g?.raw?.scripts??[];return Array.isArray(Q)?Q.map(ie=>ie&&typeof ie.src=="string"?ie.src:"").join("|"):""},[g]);u.useEffect(()=>{me(!1)},[Ce,ze]),u.useEffect(()=>{let Q=!1;const ie=g?.raw?.scripts??[];if(!Array.isArray(ie)||ie.length===0){me(!0);return}const _e=[];for(const ge of ie){if(!ge?.src||typeof ge.src!="string")continue;const Ae=ge.id?`script#${CSS.escape(ge.id)}, script[data-g7le-canvas-script="${ge.src}"]`:`script[data-g7le-canvas-script="${ge.src}"], script[src="${ge.src}"]`,Ne=window.document.querySelector(Ae);if(Ne){const Le=Ne.hasAttribute("data-g7le-canvas-script");if(Ne.hasAttribute("data-g7le-script-loaded")||!Le)continue;_e.push(new Promise(ae=>{if(Ne.hasAttribute("data-g7le-script-loaded")){ae();return}Ne.addEventListener("load",()=>ae()),Ne.addEventListener("error",()=>ae())}));continue}_e.push(new Promise(Le=>{const q=window.document.createElement("script");q.src=ge.src,q.async=!1,ge.id&&(q.id=ge.id),q.setAttribute("data-g7le-canvas-script",ge.src);const ae=()=>{q.setAttribute("data-g7le-script-loaded","1"),Le()};q.addEventListener("load",ae),q.addEventListener("error",ae),window.document.head.appendChild(q)}))}if(_e.length===0){me(!0);return}return Promise.all(_e).then(()=>{Q||requestAnimationFrame(()=>{requestAnimationFrame(()=>{Q||me(!0)})})}),()=>{Q=!0}},[g]);const Se=u.useMemo(()=>g?.raw?.data_sources??[],[g]),qe=u.useMemo(()=>g?.raw?.computed??{},[g]),[De,Pe]=u.useState({}),Qe=u.useMemo(()=>{if(p.editMode!=="iteration_item")return De;const Q=g?.iterationContext;if(!Q)return De;const ie=g?.raw?.components??[],_e=We({children:ie},Q.sourceIndexPath),ge=_e&&_e.iteration&&typeof _e.iteration.source=="string"?_e.iteration.source:null;return ge?T4(De,ge):De},[p.editMode,De,g]),[Me,Re]=u.useState(0);u.useEffect(()=>{if(typeof window>"u")return;const Q=()=>Re(ie=>ie+1);return window.addEventListener(kt,Q),()=>window.removeEventListener(kt,Q)},[]);const ve=u.useMemo(()=>S4(p.selectedRoute?.path??null),[p.selectedRoute?.path]);if(u.useEffect(()=>{let Q=!1;return(async()=>{if(Se.length===0){Q||Pe({});return}try{const ie=await B.fetchDataSources(Se,ve,new URLSearchParams,void 0,void 0);Q||Pe(ie)}catch{Q||Pe({})}})(),()=>{Q=!0}},[Se,B,ve]),u.useEffect(()=>{const Q=b;if(!Q||typeof window>"u")return;let ie=0,_e=0,ge=0;const Ae=()=>L4(Q,window.innerHeight);ie=window.requestAnimationFrame(()=>{_e=window.requestAnimationFrame(Ae)});let Ne=null;return typeof MutationObserver<"u"&&(Ne=new MutationObserver(()=>{ge||(ge=window.requestAnimationFrame(()=>{ge=0,Ae()}))}),Ne.observe(Q,{childList:!0,subtree:!0})),()=>{window.cancelAnimationFrame(ie),window.cancelAnimationFrame(_e),ge&&window.cancelAnimationFrame(ge),Ne&&Ne.disconnect()}},[b,se,ne,k,De]),u.useEffect(()=>{const Q=b;if(!Q||typeof window>"u"||p.editMode!=="extension"||re!=="ok"||ee.length===0){K(null);return}let ie=0,_e=0;const ge=ae=>{if(!ae)return!1;const pe=ae.getBoundingClientRect();return pe.width>0&&pe.height>0},Ae=ae=>{const pe=Q.querySelector(`[data-editor-path="${ae}"]`);if(ge(pe))return!0;const $e=Q.querySelectorAll(`[data-editor-path^="${ae}.children."]`);for(const be of Array.from($e))if(ge(be))return!0;return!1},Ne=()=>{K(ee.some(Ae))};ie=window.requestAnimationFrame(()=>{_e=window.requestAnimationFrame(Ne)});let Le=null,q=0;return typeof MutationObserver<"u"&&(Le=new MutationObserver(()=>{q||(q=window.requestAnimationFrame(()=>{q=0,Ne()}))}),Le.observe(Q,{childList:!0,subtree:!0})),()=>{window.cancelAnimationFrame(ie),window.cancelAnimationFrame(_e),q&&window.cancelAnimationFrame(q),Le&&Le.disconnect()}},[b,p.editMode,re,ee,ne,De]),u.useEffect(()=>{if(typeof window>"u"||p.editMode!=="base")return;const Q=window.document,ie="g7le-base-slot-style";if(Q.getElementById(ie))return;const _e=Q.createElement("style");_e.id=ie;const ge=N("layout_editor.preview.slot_area_label");return _e.textContent=`.g7le-preview-frame [data-editor-slot]{position:relative;min-height:96px;outline:2px dashed #94a3b8;outline-offset:-2px;}.g7le-preview-frame [data-editor-slot]::after{content:'${ge.replace(/'/g,"\\'")} · ' attr(data-editor-slot);position:absolute;top:8px;left:50%;transform:translateX(-50%);font-size:12px;line-height:1;color:#64748b;background:rgba(241,245,249,0.92);border:1px solid #cbd5e1;border-radius:9999px;padding:4px 12px;pointer-events:none;white-space:nowrap;}`,Q.head.appendChild(_e),()=>{Q.getElementById(ie)?.remove()}},[p.editMode,N]),!(p.editMode==="extension"||p.editMode==="iteration_item")&&(p.selectedRoute?.layoutName===null||p.selectedRoute===null))return i.jsx("div",{className:"g7le-preview-canvas g7le-preview-canvas--empty","data-testid":"g7le-preview-empty",style:Zo,children:N("layout_editor.preview.empty")});if(m)return i.jsx("div",{className:"g7le-preview-canvas g7le-preview-canvas--loading","data-testid":"g7le-preview-loading",style:Zo,children:N("layout_editor.preview.loading")});if(h)return i.jsx(Fa,{error:h});if(D)return i.jsx(Fa,{error:D});if(!z||$===null||A===null)return i.jsx("div",{className:"g7le-preview-canvas g7le-preview-canvas--assets-loading","data-testid":"g7le-preview-assets-loading",style:Zo,children:N("layout_editor.preview.assets_loading")});if(se.length===0)return i.jsx("div",{className:"g7le-preview-canvas g7le-preview-canvas--empty-layout","data-testid":"g7le-preview-empty-layout",style:Zo,children:N("layout_editor.preview.empty_layout")});const he=p.editMode==="extension"?re==="no-injection"?"no-injection":re==="ok"&&ee.length>0&&fe!==!0?"gated-no-state":null:null;return ne?i.jsx("div",{className:"g7le-preview-canvas","data-testid":"g7le-preview-canvas",style:{overflow:"visible",background:"#f1f5f9",padding:24,display:"flex",justifyContent:"center",alignItems:"flex-start",minHeight:"calc(100vh - var(--g7le-header-h, 0px))",boxSizing:"border-box"},children:i.jsxs("div",{className:"g7le-preview-frame-wrapper","data-testid":"g7le-preview-frame-wrapper",style:{position:"relative",width:k,transform:`scale(${w})`,transformOrigin:"top center"},children:[i.jsx("div",{className:_==="dark"?"g7le-preview-frame g7le-preview-dark":"g7le-preview-frame","data-testid":"g7le-preview-frame","data-color-scheme":_,ref:Q=>{v.current=Q,x(Q)},style:{width:k,background:"#fff",border:"1px solid #cbd5e1",boxShadow:"0 1px 3px rgba(0,0,0,0.05)",minHeight:400,position:"relative",transform:"translateZ(0)",overflow:"hidden"},children:i.jsx(jE,{overrideWidth:k,children:se.map((Q,ie)=>i.jsx(WE,{componentDef:Q,dataContext:{...Qe,...E4(V),_global:I4(V),_computedDefinitions:qe,...I?{_localInit:I,_local:{...De._local??{},...V._local??{}}}:{},route:j4({...De.route??{},...ve,path:p.selectedRoute?.path??""},G),query:{...De.query??{},...F??{}}},translationContext:{templateId:p.templateIdentifier,locale:p.locale},registry:$,bindingEngine:O.current,translationEngine:Pt.getInstance(),actionDispatcher:M.current,isEditMode:!0,isRootRenderer:ie===0,componentPath:`${ie}`,onComponentSelect:(_e,ge)=>{},onComponentHover:(_e,ge)=>{}},`${Q.id??`preview-${ie}`}:t${Me}${p.editMode==="extension"?`:s${ne?1:0}`:""}`))})}),i.jsx("div",{className:"g7le-overlay-layer","data-testid":"g7le-overlay-layer",style:{position:"absolute",left:0,top:0,width:k,height:b?b.getBoundingClientRect().height/(w||1):"100%",overflow:"visible",pointerEvents:"none"},children:i.jsx(d4,{frameEl:b,manifest:t,nesting:n,componentPalette:o,spec:r,permissionCandidates:s,resolveRouteMatch:l,onNavigateToDestination:d,onSessionAddedPathsChange:c})}),he!==null&&i.jsx("div",{className:"g7le-preview-canvas--extension-fallback","data-testid":"g7le-preview-extension-fallback","data-fallback-reason":he,style:{position:"absolute",left:0,top:0,right:0,bottom:0,minHeight:b?b.getBoundingClientRect().height/(w||1):400,background:"#f1f5f9",display:"flex",alignItems:"flex-start",justifyContent:"center",padding:24,boxSizing:"border-box",pointerEvents:"auto",zIndex:50},children:i.jsxs("div",{className:"g7le-extension-fallback-card",style:{maxWidth:440,textAlign:"center",position:"sticky",top:"calc(40vh - var(--g7le-header-h, 0px))"},children:[i.jsx("p",{className:"g7le-extension-fallback-title",style:{fontWeight:600,marginBottom:8,color:"#0f172a"},children:N(he==="no-injection"?"layout_editor.preview.extension_no_injection_title":"layout_editor.preview.extension_gated_title")}),i.jsx("p",{className:"g7le-extension-fallback-desc",style:{color:"#475569",lineHeight:1.6},children:he==="no-injection"?N("layout_editor.preview.extension_no_injection_desc"):p.availableStates.length>1?N("layout_editor.preview.extension_gated_desc_with_states"):N("layout_editor.preview.extension_gated_desc")})]})})]})}):i.jsx("div",{className:"g7le-preview-canvas g7le-preview-canvas--scripts-loading","data-testid":"g7le-preview-scripts-loading",style:Zo,children:N("layout_editor.preview.loading")})}function B4(e,t,n){if(!e)return;if(!t)return e;const o=s=>{if(typeof s=="string"&&s.startsWith("$t:"))try{const a=s.slice(3),l=a.indexOf("|");if(l===-1)return t.translate(a,n);const d=a.slice(0,l),c=a.slice(l);return t.translate(d,n,c)}catch{return s}return Array.isArray(s)?s.map(o):s},r={};for(const[s,a]of Object.entries(e))r[s]=o(a);return r}function W4(e,t){const n=[],o=(r,s)=>{Array.isArray(r)&&r.forEach((a,l)=>{if(!a||typeof a!="object")return;const d=s?`${s}.children.${l}`:`${l}`,c=a.__source;if(c?.kind==="extension"&&c.extensionId===t){n.push(d);return}Array.isArray(a.children)&&o(a.children,d)})};return o(e,""),n}function j4(e,t){if(!t)return e;const n={...e};for(const[o,r]of Object.entries(t))r===null?delete n[o]:n[o]=r;return n}const Zo={flex:1,display:"flex",alignItems:"center",justifyContent:"center",padding:24,background:"#f1f5f9",color:"#64748b",fontSize:13},H4={desktop:"layout_editor.device.desktop",tablet:"layout_editor.device.tablet",mobile:"layout_editor.device.mobile",portable:"layout_editor.device.portable"};function F4(){const{device:e,zoom:t,customWidth:n,setDevice:o,setZoom:r,setCustomWidth:s,colorScheme:a,setColorScheme:l}=ja(),{t:d}=tt(),c=Uf();return i.jsxs("div",{className:"g7le-device-toolbar","data-testid":"g7le-device-toolbar",style:{display:"inline-flex",alignItems:"center",gap:8,padding:"4px 8px",background:"#fff",border:"1px solid #cbd5e1",borderRadius:6},children:[i.jsxs("div",{role:"group","aria-label":d("layout_editor.device.group_label"),children:[c.map(p=>{const f=e===p,y=H4[p],g=y?d(y):p;return i.jsx("button",{type:"button","data-testid":`g7le-device-${p}`,"aria-pressed":f,onClick:()=>o(p),style:{padding:"2px 8px",marginRight:2,fontSize:12,cursor:"pointer",background:f?"#0f172a":"#f8fafc",color:f?"#fff":"#0f172a",border:"1px solid #cbd5e1",borderRadius:4},children:g},p)}),(()=>{const p=e==="custom";return i.jsx("button",{type:"button","data-testid":"g7le-device-custom","aria-pressed":p,onClick:()=>o("custom"),style:{padding:"2px 8px",marginRight:2,fontSize:12,cursor:"pointer",background:p?"#0f172a":"#f8fafc",color:p?"#fff":"#0f172a",border:"1px solid #cbd5e1",borderRadius:4},children:d("layout_editor.device.custom")})})()]}),e==="custom"&&i.jsxs("div",{style:{display:"inline-flex",alignItems:"center",gap:4},children:[i.jsx("label",{htmlFor:"g7le-custom-width-input",style:{fontSize:11,color:"#475569"},children:d("layout_editor.device.custom_width_label")}),i.jsx("input",{id:"g7le-custom-width-input",type:"number",min:dg,max:cg,step:10,value:n,"data-testid":"g7le-custom-width-input","aria-label":d("layout_editor.device.custom_width_label"),onChange:p=>{const f=parseInt(p.currentTarget.value,10);Number.isFinite(f)&&s(f)},style:{width:72,padding:"2px 6px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:4}}),i.jsx("span",{style:{fontSize:11,color:"#475569"},children:"px"})]}),i.jsx("div",{role:"group","aria-label":d("layout_editor.toolbar.color_scheme_group"),style:{display:"inline-flex",gap:2},children:["light","dark"].map(p=>{const f=a===p;return i.jsx("button",{type:"button","data-testid":`g7le-toolbar-scheme-${p}`,"aria-pressed":f,onClick:()=>l(p),style:{padding:"2px 8px",fontSize:12,cursor:"pointer",background:f?"#0f172a":"#f8fafc",color:f?"#fff":"#0f172a",border:"1px solid #cbd5e1",borderRadius:4},children:d(p==="light"?"layout_editor.toolbar.color_scheme_light":"layout_editor.toolbar.color_scheme_dark")},p)})}),i.jsxs("div",{style:{display:"inline-flex",alignItems:"center",gap:4},children:[i.jsx("span",{style:{fontSize:11,color:"#475569"},children:d("layout_editor.zoom.label")}),i.jsx("input",{type:"range",min:.5,max:1,step:.05,value:t,"data-testid":"g7le-zoom-slider","aria-label":d("layout_editor.zoom.aria_label"),onChange:p=>r(parseFloat(p.currentTarget.value)),style:{width:100}}),i.jsxs("span",{style:{fontSize:11,color:"#475569",minWidth:36,textAlign:"right"},children:[Math.round(t*100),"%"]})]}),i.jsx(Hb,{})]})}function K4(){const{t:e}=tt(),{state:t,dispatch:n}=Je(),{availableStates:o,activeStateId:r}=t,[,s]=u.useState(0);u.useEffect(()=>{if(typeof window>"u")return;const p=()=>s(y=>y+1),f=window.requestAnimationFrame(p);return window.addEventListener(Ga,p),()=>{window.cancelAnimationFrame(f),window.removeEventListener(Ga,p)}},[]);const a=u.useCallback((p,f)=>{if(!p)return f;if(!p.startsWith("$t:"))return p;const y=p.slice(3),g=window.G7Core;if(g&&typeof g.t=="function"){const h=g.t(y);if(h&&h!==y&&!h.startsWith("$t:"))return h}const m=e(y);return m&&m!==y?m:f},[e]);if(!Array.isArray(o)||o.length<=1)return null;const l=p=>{const f=p.target.value;n({type:"SET_ACTIVE_STATE",activeStateId:f}),$l({kind:"change",activeStateId:f,routePath:t.selectedRoute?.path??null,availableCount:o.length})},d=o.find(p=>p.id===r)??o[0],c=d?.description?a(d.description,""):"";return i.jsxs("div",{className:"g7le-state-switcher","data-testid":"g7le-state-switcher","data-active-state":r??"",style:{display:"inline-flex",alignItems:"center",gap:6,padding:"4px 8px",background:"#fff",border:"1px solid #cbd5e1",borderRadius:6},children:[i.jsx("span",{style:{fontSize:12,fontWeight:600,color:"#475569",whiteSpace:"nowrap"},children:e("layout_editor.toolbar.state")}),i.jsx("select",{"data-testid":"g7le-state-switcher-select","aria-label":e("layout_editor.toolbar.state"),value:r??o[0].id,onChange:l,style:{fontSize:12,padding:"2px 6px",border:"1px solid #cbd5e1",borderRadius:4,background:"#fff",color:"#0f172a",cursor:"pointer",outline:"none"},children:o.map(p=>i.jsx("option",{value:p.id,children:a(p.label,p.id)},p.id))}),c&&i.jsx("span",{"data-testid":"g7le-state-switcher-description",style:{fontSize:11,color:"#94a3b8",whiteSpace:"nowrap"},children:c})]})}function V4(e,t,n){if(e==="extension"){if(typeof n=="string"&&n.length>0){const o=Ya(n,"__base__/");return o?{kind:"base",match:o}:{kind:"route",match:n}}return null}if(!t)return null;switch(e){case"route":return{kind:"route",match:t};case"base":{const o=Ya(t,"__base__/");return o?{kind:"base",match:o}:null}case"modal":{const o=Ya(t,"__modal__/");return o?{kind:"modal",match:o}:null}default:return null}}function U4(e,t){if(!t||!Array.isArray(e)||e.length===0)return[];const n=[];for(const o of e)if(G4(o.scope,t))for(const r of o.items??[])r&&typeof r.id=="string"&&r.id.length>0&&n.push(r);return n}function q4(e){if(e.length===0)return null;const t=e.find(n=>n.default===!0);return t?t.id:e[0].id}function G4(e,t){if(!e||e.kind!==t.kind)return!1;const n=e.match;return typeof n!="string"||n.length===0?!1:t.kind==="route"?Y4(t.match,n):n===t.match}function Y4(e,t){if(t===e)return!0;if(!t.includes("*"))return!1;const n=t.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*/g,"[^/]+");return new RegExp(`^${n}$`).test(e)}function Ya(e,t){return e.startsWith(t)?e.slice(t.length):""}const X4="__conngroup__/modals/",J4="__conngroup__/extensions/";function Z4(e){return e.meta?.editor_label?{label:e.meta.editor_label,labelSource:"editor_label"}:e.meta?.title?{label:e.meta.title,labelSource:"title"}:{label:e.path,labelSource:"path"}}function Q4(e){switch(e.source.kind){case"template":return"template";case"module":return`module:${e.source.identifier??""}`;case"plugin":return`plugin:${e.source.identifier??""}`;case"core":return null;default:return null}}function R4(e,t){if(e.meta?.parent&&t.has(e.meta.parent))return e.meta.parent;const n=e.path.split("/").filter(Boolean);for(let o=n.length-1;o>=1;o--){const r="/"+n.slice(0,o).join("/");if(r!==e.path&&t.has(r))return r}return null}function eD(e){const t=new Map;for(const n of e)n.layout&&t.set(n.layout,(t.get(n.layout)??0)+1);return t}function tD(e){const t=new Map;for(const n of e){if(!n.hostLayout)continue;const o=t.get(n.hostLayout)??[];o.push(n),t.set(n.hostLayout,o)}return t}function nD(e){const t=new Map;for(const n of e)for(const o of n.extensions)for(const r of o.hostLayouts??[]){if(!r)continue;const s=t.get(r)??[];s.push({extension:o,group:n}),t.set(r,s)}return t}function oD(e,t){return{path:`__modal__/${e.hostLayout}/${e.modalId}`,layoutName:e.hostLayout,label:e.label??e.modalId,labelSource:e.label?"editor_label":"path",source:{kind:"core",identifier:null},kind:"modal",modalId:e.modalId,modalHostLayout:e.hostLayout,connectedHostRoutePath:t,children:[]}}function rD(e,t){const{extension:n,group:o}=e,r=o.sourceType==="plugin"?"🔌":"🧩";return{path:`__extension__/${n.id}`,layoutName:null,label:`${r} ${o.sourceLabel} · ${n.targetName}`,labelSource:"path",source:{kind:o.sourceType,identifier:o.sourceIdentifier},kind:"extension",extensionId:String(n.id),extensionType:n.extensionType,extensionPriority:n.priority,extensionTargetName:n.targetName,isModified:n.isModified,isInactive:!n.isActive,connectedHostRoutePath:t,children:[]}}function xg(e,t,n){if(e.kind!=="route"&&e.kind!=="base"||!e.layoutName)return;const o=[],r=t.get(e.layoutName)??[];r.length>0&&o.push({path:`${X4}${e.path}`,layoutName:null,label:`$t:layout_editor.chrome.route_tree.connected.modals|count=${r.length}`,labelSource:"editor_label",source:{kind:"core",identifier:null},kind:"route",children:r.map(a=>oD(a,e.path))});const s=n.get(e.layoutName)??[];s.length>0&&o.push({path:`${J4}${e.path}`,layoutName:null,label:`$t:layout_editor.chrome.route_tree.connected.extensions|count=${s.length}`,labelSource:"editor_label",source:{kind:"core",identifier:null},kind:"route",children:s.map(a=>rD(a,e.path))}),o.length>0&&(e.children=[...o,...e.children??[]])}function iD(e){const t=[],n=new Set(e.routes.map(o=>o.path));for(const o of e.routes){const r=Q4(o);if(!r)continue;const{label:s,labelSource:a}=Z4(o),l=R4(o,n),d={path:o.path,layoutName:o.layout??null,label:s,labelSource:a,icon:o.meta?.icon??null,source:o.source,isRedirect:!!o.redirect,isHidden:!!o.meta?.hidden,kind:"route",children:[]};t.push({node:d,parentKey:l,groupKey:r})}return t}function sD(e,t){if(e==="template")return"$t:layout_editor.chrome.route_tree.group.template";if(e.startsWith("module:")){const n=e.slice(7);return`$t:layout_editor.chrome.route_tree.group.module|name=${t.moduleDisplayNames[n]??n}`}if(e.startsWith("plugin:")){const n=e.slice(7);return`$t:layout_editor.chrome.route_tree.group.plugin|name=${t.pluginDisplayNames[n]??n}`}return e}function aD(e,t){eD(e.routes);const n=tD(e.modals),o=nD(e.extensionGroups??[]),r=new Map;for(const d of t)r.has(d.groupKey)||r.set(d.groupKey,[]),r.get(d.groupKey).push(d);const s=Array.from(r.keys()).sort((d,c)=>{const p=g=>g==="template"?1:g.startsWith("module:")?2:g.startsWith("plugin:")?3:4,f=p(d),y=p(c);return f!==y?f-y:d.localeCompare(c)}),a=[];if(e.baseLayouts.length>0){const d=e.baseLayouts.map(c=>({path:`__base__/${c.layoutName}`,layoutName:c.layoutName,label:c.label??c.layoutName,labelSource:c.label?"editor_label":"path",source:{kind:"core",identifier:null},kind:"base",children:[]}));for(const c of d)xg(c,n,o);a.push({path:"__group__/base",layoutName:null,label:"$t:layout_editor.chrome.route_tree.group.base",labelSource:"editor_label",source:{kind:"core",identifier:null},kind:"route",children:d})}for(const d of s){const c=r.get(d),p=new Map;for(const y of c)p.set(y.node.path,y);const f=[];for(const y of c)if(y.parentKey&&p.has(y.parentKey)){const g=p.get(y.parentKey);g.node.children=g.node.children??[],g.node.children.push(y.node)}else f.push(y.node);for(const y of c)xg(y.node,n,o);a.push({path:`__group__/${d}`,layoutName:null,label:sD(d,e),labelSource:"editor_label",source:{kind:"core",identifier:null},kind:"route",children:f})}if(e.modals.length>0){const d=e.modals.map(c=>({path:`__modal__/${c.hostLayout}/${c.modalId}`,layoutName:c.hostLayout,label:c.label??c.modalId,labelSource:c.label?"editor_label":"path",source:{kind:"core",identifier:null},kind:"modal",modalId:c.modalId,modalHostLayout:c.hostLayout,children:[]}));a.push({path:"__group__/modal",layoutName:null,label:"$t:layout_editor.chrome.route_tree.group.modal",labelSource:"editor_label",source:{kind:"core",identifier:null},kind:"route",children:d})}const l=e.extensionGroups??[];if(l.length>0){const d=l.map(c=>{const p=c.sourceType==="plugin"?"🔌":"🧩",f=c.extensions.map(y=>({path:`__extension__/${y.id}`,layoutName:null,label:`${c.sourceLabel} · ${y.targetName}`,labelSource:"path",source:{kind:c.sourceType,identifier:c.sourceIdentifier},kind:"extension",extensionId:String(y.id),extensionType:y.extensionType,extensionPriority:y.priority,extensionTargetName:y.targetName,isModified:y.isModified,isInactive:!y.isActive,children:[]}));return{path:`__extgroup__/${c.sourceType}/${c.sourceIdentifier}`,layoutName:null,label:`${p} ${c.sourceLabel}`,labelSource:"path",source:{kind:c.sourceType,identifier:c.sourceIdentifier},kind:"route",children:f}});a.push({path:"__group__/extension",layoutName:null,label:"$t:layout_editor.chrome.route_tree.group.extension",labelSource:"editor_label",source:{kind:"core",identifier:null},kind:"route",children:d})}return a}function lD(e){const t=iD(e);return aD(e,t)}const On=bo("useEditorRoutes");function dD(e,t){if(typeof e=="string")return e.length>0?e:null;if(e&&typeof e=="object"){const n=e[t];if(typeof n=="string"&&n.length>0)return n;if(typeof e.en=="string"&&e.en.length>0)return e.en;for(const o of Object.values(e))if(typeof o=="string"&&o.length>0)return o}return null}function wg(e,t){const n={};if(typeof window>"u")return n;const r=window.G7Config?.[e];if(!Array.isArray(r))return n;for(const s of r)s&&typeof s.identifier=="string"&&(n[s.identifier]=dD(s.display_name,t)??s.identifier);return n}function cD(e){const t=new vd,n=typeof window<"u"?window.G7Config:void 0,o={_global:{settings:n?.settings??{},plugins:n?.plugins??{},modules:n?.modules??{},appConfig:n?.appConfig??{}}};return e.map(r=>{const s={...r};if(typeof s.path=="string"&&s.path.includes("{{")){const a=s.path;let l=t.resolveBindings(s.path,o);l=l.replace(/\/\/+/g,"/")||"/",l.includes("{{")&&(On.warn("편집기 route path 표현식 평가 실패 — fallback 적용",{originalPath:a}),l=a.replace(/\{\{[^}]+\}\}/g,"").replace(/\/\/+/g,"/")||"/"),s.path=l}if(typeof s.redirect=="string"&&s.redirect.includes("{{")){const a=s.redirect;let l=t.resolveBindings(a,o);l=l.replace(/\/\/+/g,"/")||"/",l.includes("{{")&&(On.warn("편집기 route redirect 표현식 평가 실패 — fallback 적용",{originalRedirect:a}),l=a.replace(/\{\{[^}]+\}\}/g,"").replace(/\/\/+/g,"/")||"/"),s.redirect=l}return s})}function uD(e){const{dispatch:t}=Je(),{translationEngine:n,translationContext:o}=tt(),{templateIdentifier:r,cacheVersion:s}=e;u.useEffect(()=>{let a=!1;const l=s??(typeof window<"u"?window.G7Config?.cache_version:void 0)??"",d=l!==""?`?v=${encodeURIComponent(String(l))}`:"",c=en(`/api/admin/templates/${encodeURIComponent(r)}/editor/routes`,"json",null,d.replace(/^[?&]+/,"")),p=o?.locale??"ko",f=n&&r!==o?.templateId?n.loadTranslations(r,p).then(()=>{On.log("편집 대상 lang 로드 완료",{targetTemplate:r,locale:p})}).catch(h=>{On.warn("편집 대상 lang 로드 실패 — 라우트 라벨 fallback 사용",h)}):Promise.resolve(),y=(()=>{if(typeof fetch!="function")return Promise.reject(io(new Error("fetch unavailable"),"routes"));try{const h=fetch(c,{credentials:"same-origin",headers:je()});return!h||typeof h.then!="function"?Promise.reject(io(new Error("fetch returned non-Promise (test stub?)"),"routes")):h.then(async v=>{if(!v.ok){const b=await v.json().catch(()=>null);throw Gr(v.status,b,"routes")}return v.json()})}catch(h){return Promise.reject(h)}})(),g=`/api/admin/templates/${encodeURIComponent(r)}/layout-extensions${d}`,m=(()=>{if(typeof fetch!="function")return Promise.resolve(null);try{const h=fetch(g,{credentials:"same-origin",headers:je()});return!h||typeof h.then!="function"?Promise.resolve(null):h.then(async v=>v.ok?await v.json():null).catch(()=>null)}catch{return Promise.resolve(null)}})();return Promise.all([y,f,m]).then(([h,,v])=>{if(a)return;if(!h||!h.success||!Array.isArray(h.data?.routes)){On.warn("routes 응답 형식이 비정상 — empty tree 로 둠",h),t({type:"SET_ROUTES_ERROR",error:{kind:"unknown",status:200,message:"routes 응답 형식이 비정상입니다.",source:"routes"}});return}const b=cD(h.data.routes),x=wg("activeModules",p),k=wg("activePlugins",p),w=(h.data.base_layouts??[]).map(A=>({layoutName:A.layout_name,label:A.label??void 0})),_=(h.data.modals??[]).map(A=>({modalId:A.modal_id,hostLayout:A.host_layout,label:A.label??void 0})),S=(v?.success&&Array.isArray(v.data)?v.data:[]).map(A=>({sourceIdentifier:A.source_identifier,sourceType:A.source_type,sourceLabel:A.source_label,extensions:(A.extensions??[]).map(z=>({id:z.id,extensionType:z.extension_type,targetName:z.target_name,priority:z.priority,isActive:z.is_active,isModified:z.is_modified,hostLayouts:Array.isArray(z.host_layouts)?z.host_layouts:[],currentVersion:typeof z.current_version=="number"?z.current_version:void 0}))})),E=lD({routes:b,modals:_,baseLayouts:w,extensionGroups:S,moduleDisplayNames:x,pluginDisplayNames:k});t({type:"SET_ROUTE_TREE",tree:E});const C=h.data.layout_versions,N={};if(C&&typeof C=="object"&&!Array.isArray(C))for(const[A,z]of Object.entries(C))typeof z=="number"&&Number.isFinite(z)&&(N[A]=z);t({type:"SET_LAYOUT_VERSIONS",versions:N});const $={};for(const A of S)for(const z of A.extensions){const D=z.currentVersion;typeof D=="number"&&Number.isFinite(D)&&($[String(z.id)]=D)}t({type:"SET_EXTENSION_VERSIONS",versions:$}),On.log("routes 로드 완료",{count:b.length,groups:E.length})}).catch(h=>{if(a)return;On.error("routes 로드 실패",h);const v=fp(h)?h:io(h,"routes");t({type:"SET_ROUTES_ERROR",error:v})}),()=>{a=!0}},[r,s,t,n,o?.locale,o?.templateId])}function kg(e,t,n,o=[]){const r=t?Sg(o,t):null,s=r!==null?{path:r.path,layoutName:r.layoutName}:void 0;if(e.startsWith("__base__/")){const a=e.slice(9);if(a)return n({type:"ENTER_BASE_EDIT",layoutName:a,returnRoute:s}),!0}else if(e.startsWith("__modal__/")){const a=e.slice(10);return a&&!a.includes("/")&&t?(n({type:"ENTER_MODAL_EDIT",modalId:a,hostLayout:t,returnRoute:s}),!0):!1}else if(e.startsWith("__extension__/")){const a=e.slice(14);if(a&&!a.includes("/"))return n({type:"ENTER_EXTENSION_EDIT",extensionId:a,returnRoute:s}),!0}else if(e.startsWith("__iteration__/")){const a=e.slice(14);return a&&t?(n({type:"ENTER_ITERATION_ITEM_EDIT",sourcePath:a,hostLayout:t,returnRoute:s}),!0):!1}return!1}function Sg(e,t){for(const n of e){if(n.kind==="route"&&n.layoutName===t)return n;if(n.children&&n.children.length>0){const o=Sg(n.children,t);if(o)return o}}return null}function Xa(e,t){for(const n of e){if(n.kind==="route"&&n.path===t)return n;if(n.children&&n.children.length>0){const o=Xa(n.children,t);if(o)return o}}return null}function pD(e){const{state:t,dispatch:n}=Je(),o=u.useRef(!1),r=u.useRef(t.editMode);r.current=t.editMode,u.useEffect(()=>{if(o.current||t.routeTree.length===0||typeof window>"u")return;const a=ip(window.location.search);if(a){const c=rp(window.location.search),p=kg(a,c,n,t.routeTree);if(o.current=!0,p)return}const l=op(window.location.search);if(!l){o.current=!0;return}const d=Xa(t.routeTree,l);d&&n({type:"SELECT_ROUTE",route:{path:d.path,layoutName:d.layoutName}}),o.current=!0},[t.routeTree,n]),u.useEffect(()=>{if(typeof window>"u")return;const a=()=>{const l=ip(window.location.search);if(l){const p=rp(window.location.search);if(kg(l,p,n,t.routeTree))return}if(r.current!=="route")switch(r.current){case"base":n({type:"EXIT_BASE_EDIT"});return;case"modal":n({type:"EXIT_MODAL_EDIT"});return;case"extension":n({type:"EXIT_EXTENSION_EDIT"});return;case"iteration_item":n({type:"EXIT_ITERATION_ITEM_EDIT"});return}const d=op(window.location.search);if(!d){n({type:"SELECT_ROUTE",route:null});return}const c=Xa(t.routeTree,d);c&&n({type:"SELECT_ROUTE",route:{path:c.path,layoutName:c.layoutName}})};return window.addEventListener("popstate",a),()=>{window.removeEventListener("popstate",a)}},[t.routeTree,n]);const s=u.useRef("route");u.useEffect(()=>{if(typeof window>"u"||!window.history?.replaceState||t.routeTree.length===0)return;const a=Zs(e,t.selectedRoute?.path??null,t.selectedRoute?.layoutName??null),l=`${window.location.pathname}${window.location.search}`,d=s.current;if(s.current=t.editMode,l===a)return;d==="route"&&t.editMode!=="route"&&window.history.pushState?window.history.pushState(window.history.state,"",a):window.history.replaceState(window.history.state,"",a)},[t.selectedRoute,t.editMode,t.routeTree,e])}function fD(){u.useEffect(()=>{if(typeof document>"u")return;const e=document.documentElement;let t=e.classList.contains("dark");const n=()=>{e.classList.contains("dark")&&(t=!0,e.classList.remove("dark"))};n();let o=null;return typeof MutationObserver<"u"&&(o=new MutationObserver(n),o.observe(e,{attributes:!0,attributeFilter:["class"]})),()=>{o?.disconnect(),t&&e.classList.add("dark")}},[])}function gD(){u.useEffect(()=>{if(typeof document>"u"||!document.body)return;const e=document.body,t=()=>{e.style.overflow==="hidden"&&(e.style.overflow=""),e.style.overflowY==="hidden"&&(e.style.overflowY="")};t();let n=null;return typeof MutationObserver<"u"&&(n=new MutationObserver(t),n.observe(e,{attributes:!0,attributeFilter:["style"]})),()=>{n?.disconnect()}},[])}function hD(e){if(typeof e!="string")return null;const t="__extension__/";if(!e.startsWith(t))return null;const n=e.slice(t.length);return n===""||n.includes("/")?null:n}function yD(e){if(Array.isArray(e.components))return e.components;if(Array.isArray(e.injections)){const t=[];return e.injections.forEach((n,o)=>{if(n&&Array.isArray(n.components))for(const r of n.components)t.push({...r,__injectionIndex:o})}),t}return[]}function bD(e,t){if(Array.isArray(e.components)){const n=t.map(o=>Ja(o));return{...e,components:n}}if(Array.isArray(e.injections)){const n=e.injections.map(o=>({...o}));n.forEach(o=>{Array.isArray(o.components)&&(o.components=[])});for(const o of t){const r=o.__injectionIndex,s=typeof r=="number"&&r>=0&&rJa(n))),t}function Cg(e,t){const n=[],o=r=>{if(Array.isArray(r))for(const s of r){if(!s||typeof s!="object")continue;const a=s.__source;if(a?.kind==="extension"&&a.extensionId===t){n.push(s);continue}Array.isArray(s.children)&&o(s.children)}};return o(e),n}function $g(e,t){const o=Cg(e,t)[0]?.__source;return o&&o.kind==="extension"&&o.extensionId===t?o:{kind:"extension",extensionId:t}}function Za(e,t){for(const n of e){const o=n?.__source;if(o?.kind==="extension"&&o.extensionId===t||Array.isArray(n.children)&&Za(n.children,t))return!0}return!1}function mD(){const{state:e}=Je(),t=e.templateIdentifier,n=e.editMode==="extension"?hD(e.selectedRoute?.path):null,o=e.editMode==="extension"&&typeof e.selectedRoute?.layoutName=="string"?e.selectedRoute.layoutName:null,[r,s]=u.useState(null),[a,l]=u.useState(!1),[d,c]=u.useState(null),[p,f]=u.useState(!1),[y,g]=u.useState(0),[m,h]=u.useState(null),v=u.useRef({}),b=u.useRef({kind:"extension",extensionId:0}),x=u.useRef(null);u.useEffect(()=>{x.current=r},[r]),u.useEffect(()=>{h(null)},[n]);const k=u.useCallback(async A=>{const z=window.G7Config?.cache_version??0,D=en(`/api/layouts/${encodeURIComponent(t)}/${A}`,"json",`${z}.${wp()}`,"with_source_meta=1"),O=ar(),M={Accept:"application/json"};O&&(M.Authorization=`Bearer ${O}`);const B=await fetch(D,{headers:M,credentials:"same-origin"});if(!B.ok)return null;const T=await B.json().catch(()=>null);return(T&&(T.data||T))??{}},[t]),w=u.useCallback(async()=>{if(!n){s(null),c(null),f(!1);return}l(!0),c(null);try{const A=window.G7Config?.cache_version??0,z=`/api/admin/templates/${encodeURIComponent(t)}/layout-extensions/${encodeURIComponent(n)}?v=${A}`,D=await fetch(z,{headers:je(),credentials:"same-origin"}),O=await D.json().catch(()=>null);if(!D.ok){c(Gr(D.status,O,"layout")),s(null),f(!1);return}const M=(O&&(O.data||O))??{};let B={};const T=M.content;if(typeof T=="string")try{B=JSON.parse(T)}catch{B={}}else T&&typeof T=="object"&&(B=T);const H=M.extension_type==="overlay"?"overlay":"extension_point",L=typeof M.lock_version=="number"?M.lock_version:0,I=Array.isArray(M?.host_layouts)?M.host_layouts.filter(re=>typeof re=="string"):[],P=o??(I.length===1?I[0]:m&&I.includes(m)?m:null),F=Number(n);v.current=B;let V=yD(B),Y={},Z="pending";const se={kind:"extension",extensionId:F};if(b.current=se,P){const re=await k(P);if(re){Y=re;const U=Array.isArray(re.components)?re.components:[],ee=$g(U,F);if(b.current=ee,Za(U,F))V=U,Z="ok";else{const K=(Array.isArray(re.modals)?re.modals:[]).filter(ne=>ne&&typeof ne=="object"&&Za([ne],F));if(K.length>0){const ne=K.map(me=>({...me,props:{...me.props??{},isOpen:!0}}));V=[...U,...ne],b.current=$g(K,F),Z="ok"}else V=U,Z="no-injection"}}}s({extensionId:F,content:B,extensionType:H,components:V,lockVersion:L,hostLayouts:I,hostLayoutName:P,hostRaw:Y,editability:Z}),f(!1),st({op:"load",layoutName:`extension:${n}`,editMode:"extension",statusCode:D.status,timestamp:Date.now()})}catch(A){c(io(A,"layout")),s(null),f(!1)}finally{l(!1)}},[t,n,m,o,k]);u.useEffect(()=>{w()},[w]);const _=u.useCallback(A=>{h(A)},[]),S=u.useCallback(A=>{s(z=>z&&{...z,components:A}),f(!0),st({op:"patch",layoutName:`extension:${x.current?.extensionId??""}`,editMode:"extension",isDirty:!0,timestamp:Date.now()})},[]),E=u.useCallback(A=>{s(z=>z&&{...z,components:A(z.components)}),f(!0)},[]),C=u.useCallback(async()=>{const A=x.current;if(!A)return{kind:"guard_no_document"};const z=`/api/admin/templates/${encodeURIComponent(t)}/layout-extensions/${A.extensionId}`,D=Cg(A.components,A.extensionId),O=D.length>0?D:A.components,M=bD(v.current,O);st({op:"save",layoutName:`extension:${A.extensionId}`,editMode:"extension",saveTarget:"layout_extension",endpoint:z,isDirty:p,timestamp:Date.now()});let B;try{B=await fetch(z,{method:"PUT",headers:je({"Content-Type":"application/json"}),credentials:"same-origin",body:JSON.stringify({content:M,expected_lock_version:A.lockVersion})})}catch(H){return{kind:"network_error",message:H instanceof Error?H.message:"network error"}}const T=await B.json().catch(()=>null);if(st({op:"save_response",layoutName:`extension:${A.extensionId}`,editMode:"extension",endpoint:z,statusCode:B.status,conflict:B.status===409?{currentVersion:T?.errors?.current_version??T?.current_version,yourVersion:T?.errors?.your_version??T?.your_version}:void 0,timestamp:Date.now()}),B.ok){const H=T?.data??{},L=typeof H.lock_version=="number"?H.lock_version:A.lockVersion+1;s(P=>P&&{...P,lockVersion:L}),f(!1),g(P=>P+1);const I=typeof H.current_version=="number"?H.current_version:void 0;return{kind:"success",newLockVersion:L,newContentVersion:I,savedExtensionId:String(A.extensionId)}}return B.status===409?{kind:"concurrent_modification",currentVersion:T?.current_version??-1,yourVersion:T?.your_version??A.lockVersion}:B.status===422?{kind:"validation_failed",status:422,errors:T?.errors??null}:{kind:"network_error",message:T?.message??`HTTP ${B.status}`}},[t,p]),N=u.useCallback(async()=>{na(),await w()},[w]),$=!!r&&r.hostLayouts.length>1&&r.hostLayoutName===null;return{document:r,isLoading:a,error:d,isDirty:p,saveSuccessCounter:y,setLayoutComponents:S,patchLayout:E,save:C,reload:N,needsHostPicker:$,selectHost:_}}const Eg=new Set;function vD(e,t,n){const r=e&&Object.keys(e).length>0?e:t,s=Array.isArray(r.data_sources)?r.data_sources:[],a=Array.isArray(t.data_sources)?t.data_sources:[],l=new Set,d=[];for(const c of[...s,...a]){const p=c&&typeof c=="object"?c.id:void 0,f=typeof p=="string"?p:`__anon_${d.length}`;l.has(f)||(l.add(f),d.push(c))}return{...r,components:n,...d.length>0?{data_sources:d}:{},lock_version:void 0}}function _D(e){return{document:e.document?{layoutName:`extension:${e.document.extensionId}`,raw:{...vD(e.document.hostRaw,e.document.content,e.document.components),__editability:e.document.editability,__extensionId:e.document.extensionId},lockVersion:e.document.lockVersion}:null,isLoading:e.isLoading,error:e.error,isDirty:e.isDirty,saveSuccessCounter:e.saveSuccessCounter,reload:e.reload,patchLayout:e.patchLayout,setLayoutComponents:e.setLayoutComponents,patchDocumentRaw:()=>{},dirtyKeys:Eg,dirtyLayoutNames:Eg,invalidateCurrentCache:()=>{},reloadCounter:0,save:()=>e.save(),markDirty:()=>{}}}function xD({hostLayouts:e,extensionLabel:t,onSelect:n}){const{t:o}=tt();return i.jsx("div",{className:"g7le-host-picker__backdrop","data-testid":"g7le-host-picker",role:"dialog","aria-modal":"true","aria-label":o("layout_editor.host_picker.title"),style:{position:"fixed",inset:0,zIndex:9500,display:"flex",alignItems:"center",justifyContent:"center",background:"rgba(15, 23, 42, 0.55)"},children:i.jsxs("div",{className:"g7le-host-picker__panel",style:{width:"min(520px, 92vw)",maxHeight:"80vh",overflow:"auto",background:"#ffffff",color:"#0f172a",borderRadius:12,boxShadow:"0 20px 48px rgba(15, 23, 42, 0.28)",padding:"20px 22px"},children:[i.jsx("h2",{className:"g7le-host-picker__title",style:{margin:"0 0 6px",fontSize:16,fontWeight:700},children:o("layout_editor.host_picker.title")}),i.jsx("p",{className:"g7le-host-picker__desc",style:{margin:"0 0 16px",fontSize:13,color:"#475569",lineHeight:1.6},children:t?o("layout_editor.host_picker.description_with_label",{label:t}):o("layout_editor.host_picker.description")}),i.jsx("ul",{className:"g7le-host-picker__list",style:{listStyle:"none",margin:0,padding:0,display:"grid",gap:8},children:e.map(r=>i.jsx("li",{children:i.jsxs("button",{type:"button",className:"g7le-host-picker__item","data-testid":"g7le-host-picker-item","data-host":r,onClick:()=>n(r),style:{width:"100%",textAlign:"left",padding:"12px 14px",border:"1px solid #e2e8f0",borderRadius:8,background:"#f8fafc",cursor:"pointer",fontSize:13,fontWeight:600,color:"#0f172a",display:"flex",alignItems:"center",gap:8},children:[i.jsx("span",{"aria-hidden":!0,style:{fontSize:14},children:"🖼"}),i.jsx("span",{style:{flex:1,minWidth:0,overflow:"hidden",textOverflow:"ellipsis"},children:r}),i.jsx("span",{"aria-hidden":!0,style:{color:"#94a3b8"},children:"→"})]})},r))})]})})}function wD(e,t){const[n,o]=u.useState(!1),[r,s]=u.useState(null),a=u.useCallback(async l=>{if(!t||!l)return{kind:"no_document"};o(!0),s(null);try{const d=vo(l),c=`/api/admin/templates/${encodeURIComponent(e)}/layouts/${t}/preview`,p=await fetch(c,{method:"POST",credentials:"same-origin",headers:je({"Content-Type":"application/json"}),body:JSON.stringify({content:JSON.stringify(d)})}),f=await p.json().catch(()=>null);if(!p.ok){const h=f?.message??`HTTP ${p.status}`;return s(h),{kind:"network_error",message:h}}const y=f?.data,g=typeof y?.token=="string"?y.token:"",m=typeof y?.preview_url=="string"&&y.preview_url?y.preview_url:g?`/preview/${g}`:"";return m?(window.G7Core?.dispatch?.({handler:"openWindow",params:{path:m}}),{kind:"success",previewUrl:m,token:g}):(s("invalid preview response"),{kind:"network_error",message:"invalid preview response"})}catch(d){const c=d instanceof Error?d.message:"network error";return s(c),{kind:"network_error",message:c}}finally{o(!1)}},[e,t]);return{isCreating:n,error:r,createPreview:a}}const ce="layout_editor.action_recipe",bn={setState:{label:`$t:${ce}.set_state.label`,params:[{key:"target",label:`$t:${ce}.set_state.param_target`,widget:"select",options:[{value:"local"},{value:"global"}]},{key:"state",label:`$t:${ce}.set_state.param_state`,widget:"state-key-value"},{key:"merge",label:`$t:${ce}.set_state.param_merge`,widget:"toggle"}],build:{handler:"setState",params:{target:"{{target}}",merge:"{{merge}}","...":"{{state}}"}}},loadFromLocalStorage:{label:`$t:${ce}.load_local.label`,params:[{key:"key",label:`$t:${ce}.load_local.param_key`,widget:"text",required:!0},{key:"stateKey",label:`$t:${ce}.load_local.param_state_key`,widget:"state-key-picker"},{key:"defaultValue",label:`$t:${ce}.load_local.param_default`,widget:"text"}],build:{handler:"loadFromLocalStorage",params:{key:"{{key}}",stateKey:"{{stateKey}}",defaultValue:"{{defaultValue}}"}}},saveToLocalStorage:{label:`$t:${ce}.save_local.label`,params:[{key:"key",label:`$t:${ce}.save_local.param_key`,widget:"text",required:!0},{key:"value",label:`$t:${ce}.save_local.param_value`,widget:"data-chip"}],build:{handler:"saveToLocalStorage",params:{key:"{{key}}",value:"{{value}}"}}},navigate:{label:`$t:${ce}.navigate.label`,params:[{key:"path",label:`$t:${ce}.navigate.param_path`,widget:"page-picker",required:!0},{key:"query",label:`$t:${ce}.navigate.param_query`,widget:"key-value"},{key:"mergeQuery",label:`$t:${ce}.navigate.param_merge_query`,widget:"toggle"},{key:"scroll",label:`$t:${ce}.navigate.param_scroll`,widget:"select",options:[{value:"top"},{value:"preserve"},{value:"none"}]},{key:"replace",label:`$t:${ce}.navigate.param_replace`,widget:"toggle"},{key:"transition_overlay_target",label:`$t:${ce}.navigate.param_overlay_target`,widget:"component-target-picker",dependsOn:{param:"replace",equals:!0}}],build:{handler:"navigate",params:{path:"{{path}}",query:"{{query}}",mergeQuery:"{{mergeQuery}}",scroll:"{{scroll}}",replace:"{{replace}}",transition_overlay_target:"{{transition_overlay_target}}"}}},openWindow:{label:`$t:${ce}.open_window.label`,params:[{key:"target",label:`$t:${ce}.open_window.param_target`,widget:"page-picker",required:!0},{key:"windowTarget",label:`$t:${ce}.open_window.param_window_target`,widget:"select",options:[{value:"_blank"},{value:"_self"}]}],build:{handler:"openWindow",target:"{{target}}",params:{windowTarget:"{{windowTarget}}"}}},navigateBack:{label:`$t:${ce}.navigate_back.label`,params:[],build:{handler:"navigateBack"}},navigateForward:{label:`$t:${ce}.navigate_forward.label`,params:[],build:{handler:"navigateForward"}},replaceUrl:{label:`$t:${ce}.replace_url.label`,params:[{key:"path",label:`$t:${ce}.replace_url.param_path`,widget:"page-picker"},{key:"query",label:`$t:${ce}.replace_url.param_query`,widget:"key-value"}],build:{handler:"replaceUrl",params:{path:"{{path}}",query:"{{query}}"}}},toast:{label:`$t:${ce}.toast.label`,params:[{key:"message",label:`$t:${ce}.toast.param_message`,widget:"i18n-text",required:!0},{key:"type",label:`$t:${ce}.toast.param_type`,widget:"select",options:[{value:"info"},{value:"success"},{value:"warning"},{value:"error"}]},{key:"duration",label:`$t:${ce}.toast.param_duration`,widget:"number"},{key:"icon",label:`$t:${ce}.toast.param_icon`,widget:"text"}],build:{handler:"toast",params:{message:"{{message}}",type:"{{type}}",duration:"{{duration}}",icon:"{{icon}}"}}},openModal:{label:`$t:${ce}.open_modal.label`,params:[{key:"target",label:`$t:${ce}.open_modal.param_target`,widget:"modal-picker",required:!0}],build:{handler:"openModal",target:"{{target}}"}},closeModal:{label:`$t:${ce}.close_modal.label`,params:[],build:{handler:"closeModal"}},showAlert:{label:`$t:${ce}.show_alert.label`,params:[{key:"target",label:`$t:${ce}.show_alert.param_message`,widget:"i18n-text",required:!0}],build:{handler:"showAlert",target:"{{target}}"}},setError:{label:`$t:${ce}.set_error.label`,params:[{key:"target",label:`$t:${ce}.set_error.param_message`,widget:"i18n-text"},{key:"stateTarget",label:`$t:${ce}.set_error.param_state_target`,widget:"select",options:[{value:"local"},{value:"global"}]}],build:{handler:"setError",target:"{{target}}",params:{stateTarget:"{{stateTarget}}"}}},refetchDataSource:{label:`$t:${ce}.refetch.label`,params:[{key:"dataSourceId",label:`$t:${ce}.refetch.param_data_source`,widget:"datasource-picker",required:!0},{key:"sync",label:`$t:${ce}.refetch.param_sync`,widget:"toggle"}],build:{handler:"refetchDataSource",params:{dataSourceId:"{{dataSourceId}}",sync:"{{sync}}"}}},appendDataSource:{label:`$t:${ce}.append_data.label`,params:[{key:"dataSourceId",label:`$t:${ce}.append_data.param_data_source`,widget:"datasource-picker",required:!0},{key:"dataPath",label:`$t:${ce}.append_data.param_data_path`,widget:"text"},{key:"newData",label:`$t:${ce}.append_data.param_new_data`,widget:"data-chip"}],build:{handler:"appendDataSource",params:{dataSourceId:"{{dataSourceId}}",dataPath:"{{dataPath}}",newData:"{{newData}}"}}},updateDataSource:{label:`$t:${ce}.update_data.label`,params:[{key:"dataSourceId",label:`$t:${ce}.update_data.param_data_source`,widget:"datasource-picker",required:!0},{key:"data",label:`$t:${ce}.update_data.param_data`,widget:"data-chip"},{key:"merge",label:`$t:${ce}.update_data.param_merge`,widget:"toggle"}],build:{handler:"updateDataSource",params:{dataSourceId:"{{dataSourceId}}",data:"{{data}}",merge:"{{merge}}"}}},scrollIntoView:{label:`$t:${ce}.scroll.label`,params:[{key:"selector",label:`$t:${ce}.scroll.param_selector`,widget:"text",required:!0},{key:"behavior",label:`$t:${ce}.scroll.param_behavior`,widget:"select",options:[{value:"smooth"},{value:"auto"}]},{key:"block",label:`$t:${ce}.scroll.param_block`,widget:"select",options:[{value:"start"},{value:"center"},{value:"end"},{value:"nearest"}]}],build:{handler:"scrollIntoView",params:{selector:"{{selector}}",behavior:"{{behavior}}",block:"{{block}}"}}},login:{label:`$t:${ce}.login.label`,params:[{key:"body",label:`$t:${ce}.login.param_body`,widget:"key-value"}],build:{handler:"login",params:{body:"{{body}}"}}},logout:{label:`$t:${ce}.logout.label`,params:[{key:"target",label:`$t:${ce}.logout.param_target`,widget:"text"}],build:{handler:"logout",target:"{{target}}"}},setLocale:{label:`$t:${ce}.set_locale.label`,params:[{key:"target",label:`$t:${ce}.set_locale.param_target`,widget:"locale-picker",required:!0}],build:{handler:"setLocale",target:"{{target}}"}},emitEvent:{label:`$t:${ce}.emit_event.label`,params:[{key:"event",label:`$t:${ce}.emit_event.param_event`,widget:"text",required:!0},{key:"data",label:`$t:${ce}.emit_event.param_data`,widget:"data-chip"}],build:{handler:"emitEvent",params:{event:"{{event}}",data:"{{data}}"}}},apiCall:{label:`$t:${ce}.api_call.label`,params:[{key:"target",label:`$t:${ce}.api_call.param_target`,widget:"page-picker",required:!0},{key:"method",label:`$t:${ce}.api_call.param_method`,widget:"select",options:[{value:"GET"},{value:"POST"},{value:"PUT"},{value:"PATCH"},{value:"DELETE"}]},{key:"body",label:`$t:${ce}.api_call.param_body`,widget:"key-value"},{key:"query",label:`$t:${ce}.api_call.param_query`,widget:"key-value"},{key:"identity_target_email",label:`$t:${ce}.api_call.param_identity_target_email`,widget:"data-chip"},{key:"identity_target_phone",label:`$t:${ce}.api_call.param_identity_target_phone`,widget:"data-chip"},{key:"onSuccess",label:`$t:${ce}.api_call.param_on_success`,widget:"action-list"},{key:"onError",label:`$t:${ce}.api_call.param_on_error`,widget:"action-list"}],build:{handler:"apiCall",target:"{{target}}",identity_target:{email:"{{identity_target_email}}",phone:"{{identity_target_phone}}"},onSuccess:"{{onSuccess}}",onError:"{{onError}}",params:{method:"{{method}}",body:"{{body}}",query:"{{query}}"}}},conditions:{label:`$t:${ce}.conditions.label`,params:[{key:"branches",label:`$t:${ce}.conditions.param_branches`,widget:"branch-list"}],build:{handler:"conditions",conditions:"{{branches}}"}},sequence:{label:`$t:${ce}.sequence.label`,params:[{key:"actions",label:`$t:${ce}.sequence.param_actions`,widget:"action-list"}],build:{handler:"sequence",params:{actions:"{{actions}}"}}},parallel:{label:`$t:${ce}.parallel.label`,params:[{key:"actions",label:`$t:${ce}.parallel.param_actions`,widget:"action-list"}],build:{handler:"parallel",params:{actions:"{{actions}}"}}},switch:{label:`$t:${ce}.switch.label`,params:[{key:"value",label:`$t:${ce}.switch.param_value`,widget:"data-chip"},{key:"cases",label:`$t:${ce}.switch.param_cases`,widget:"action-list",advanced:!0}],build:{handler:"switch",params:{value:"{{value}}",cases:"{{cases}}"}}},suppress:{label:`$t:${ce}.suppress.label`,params:[],build:{handler:"suppress"}}};function kD(){return{actionRecipes:bn,initActionRecipes:bn,errorRecipes:{navigate:bn.navigate,openModal:bn.openModal,toast:bn.toast,setState:bn.setState,sequence:bn.sequence,parallel:bn.parallel,showErrorPage:{label:`$t:${ce}.show_error_page.label`,params:[{key:"target",label:`$t:${ce}.show_error_page.param_target`,widget:"select",options:[{value:"content"},{value:"full"}]}],build:{handler:"showErrorPage",params:{target:"{{target}}"}}}}}}function Qa(e,t){return typeof e.label=="string"?e.label.startsWith("$t:")?t(e.label.slice(3)):e.label:String(e.value)}function vi(e){return Array.isArray(e.options)?e.options:[]}function SD({control:e,value:t,onChange:n,t:o}){const r=vi(e);return i.jsx("div",{className:"g7le-widget g7le-widget--segmented","data-testid":"g7le-widget-segmented",style:PD,children:r.map(s=>{const a=s.value===t;return i.jsx("button",{type:"button","data-testid":`g7le-segment-${String(s.value)}`,"data-active":a?"true":"false",onClick:()=>n(a?void 0:s.value),style:{...VD,background:a?"#2563eb":"#fff",color:a?"#fff":"#0f172a",borderColor:a?"#2563eb":"#cbd5e1"},children:Qa(s,o)},String(s.value))})})}function CD({control:e,value:t,onChange:n,t:o}){const r=Array.isArray(e.scale)?e.scale:[],s=r.findIndex(l=>l===t),a=t!=null;return i.jsxs("div",{className:"g7le-widget g7le-widget--slider","data-testid":"g7le-widget-slider",style:Ag,children:[i.jsxs("label",{style:Ra,children:[i.jsx("input",{type:"checkbox","data-testid":"g7le-slider-enabled",checked:a,onChange:l=>{l.target.checked?n(r.length>0?r[Math.max(0,s===-1?0:s)]:void 0):n(void 0)}}),i.jsx("span",{style:{fontSize:12,color:"#64748b"},children:o("layout_editor.control.use")})]}),i.jsx("input",{type:"range","data-testid":"g7le-slider-range",min:0,max:Math.max(0,r.length-1),step:1,disabled:!a||r.length===0,value:s===-1?0:s,onChange:l=>{const d=Number(l.target.value);n(r[d])},style:{flex:1}}),i.jsx("span",{"data-testid":"g7le-slider-value",style:{fontSize:12,color:"#0f172a",minWidth:48,textAlign:"right"},children:a?String(t):o("layout_editor.control.default")})]})}function $D({control:e,value:t,onChange:n,t:o}){const r=vi(e);return i.jsxs("select",{className:"g7le-widget g7le-widget--select","data-testid":"g7le-widget-select",value:t==null?"":String(t),onChange:s=>{const a=s.target.value;if(a===""){n(void 0);return}const l=r.find(d=>String(d.value)===a);n(l?l.value:a)},style:UD,children:[i.jsx("option",{value:"",children:o("layout_editor.control.default")}),r.map(s=>i.jsx("option",{value:String(s.value),children:Qa(s,o)},String(s.value)))]})}function ED({control:e,value:t,onChange:n}){const o=vi(e),r=o[1]?.value??!0,s=o[0]?.value??void 0,a=t===r;return i.jsx("button",{type:"button",className:"g7le-widget g7le-widget--toggle","data-testid":"g7le-widget-toggle",role:"switch","aria-checked":a,onClick:()=>n(a?s:r),style:{...qD,background:a?"#2563eb":"#cbd5e1"},children:i.jsx("span",{style:{...GD,transform:a?"translateX(18px)":"translateX(0)"}})})}function ID({control:e,value:t,onChange:n,t:o,freeValueDisabled:r}){const s=vi(e),a=t==null?"":String(t),[l,d]=u.useState(a);u.useEffect(()=>{d(a)},[a]);const c=p=>{const f=p.trim();if(f===""){n(void 0);return}n(f)};return i.jsxs("div",{className:"g7le-widget g7le-widget--dimension","data-testid":"g7le-widget-dimension",style:OD,children:[r?i.jsx("div",{"data-testid":"g7le-dimension-free-disabled",style:FD,children:o("layout_editor.property_modal.dark_preset_only")}):i.jsxs("div",{style:BD,children:[i.jsx("input",{type:"text","data-testid":"g7le-dimension-input",value:l,placeholder:o("layout_editor.control.dimension.placeholder"),onChange:p=>d(p.target.value),onBlur:p=>c(p.target.value),onKeyDown:p=>{p.key==="Enter"&&(p.preventDefault(),c(p.target.value))},style:WD}),i.jsx("button",{type:"button","data-testid":"g7le-dimension-clear",title:o("layout_editor.control.default"),onClick:()=>{d(""),n(void 0)},style:jD,children:o("layout_editor.control.default")})]}),s.length>0&&i.jsx("div",{style:HD,"data-testid":"g7le-dimension-chips",children:s.map(p=>{const f=String(p.value),y=f===a;return i.jsx("button",{type:"button","data-testid":`g7le-dimension-chip-${f}`,"data-active":y?"true":"false",onClick:()=>{d(f),n(f)},style:{...KD,background:y?"#2563eb":"#fff",color:y?"#fff":"#0f172a",borderColor:y?"#2563eb":"#cbd5e1"},children:Qa(p,o)},f)})})]})}function AD(e){return[{key:"t",infix:`${e}t-`,labelKey:"layout_editor.control.spacing.top"},{key:"r",infix:`${e}r-`,labelKey:"layout_editor.control.spacing.right"},{key:"b",infix:`${e}b-`,labelKey:"layout_editor.control.spacing.bottom"},{key:"l",infix:`${e}l-`,labelKey:"layout_editor.control.spacing.left"}]}function TD(e,t,n,o){const r={all:-1,sides:{}};if(typeof e!="string"||e.trim()==="")return r;const s=e.split(/\s+/).filter(a=>a.length>0);for(const a of s){const l=n.find(d=>a.startsWith(d.infix));if(l){r.sides[l.key]=o.indexOf(a.slice(l.infix.length));continue}if(a.startsWith(`${t}x-`)){const d=o.indexOf(a.slice(`${t}x-`.length));r.sides.l=d,r.sides.r=d;continue}if(a.startsWith(`${t}y-`)){const d=o.indexOf(a.slice(`${t}y-`.length));r.sides.t=d,r.sides.b=d;continue}a.startsWith(`${t}-`)&&(r.all=o.indexOf(a.slice(`${t}-`.length)))}return r}function DD({control:e,value:t,onChange:n,t:o}){const r=typeof e.spacingPrefix=="string"?e.spacingPrefix:"p",s=(Array.isArray(e.scale)?e.scale:[]).map(b=>String(b)),a=AD(r),l=TD(t,r,a,s),d=Object.values(l.sides).some(b=>b>=0),c=l.all>=0||d,[p,f]=u.useState(d);u.useEffect(()=>{d&&f(!0)},[d]);const y=Math.min(4,Math.max(0,s.length-1)),g=b=>{const x=[];for(const k of a){const w=b[k.key];w!==void 0&&w>=0&&x.push(`${k.infix}${s[w]}`)}n(x.length>0?x.join(" "):void 0)},m=b=>{n(b>=0?`${r}-${s[Math.max(0,Math.min(b,s.length-1))]}`:void 0)},h=()=>({...l.sides}),v=(b,x,k,w,_)=>{const S=k>=0;return i.jsxs("div",{style:LD,"data-testid":b,children:[i.jsxs("label",{style:{...Ra,minWidth:64},children:[i.jsx("input",{type:"checkbox","data-testid":`${b}-enabled`,checked:S,onChange:E=>_(E.target.checked)}),i.jsx("span",{style:{fontSize:12,color:"#0f172a"},children:o(x)})]}),i.jsx("input",{type:"range","data-testid":`${b}-range`,min:0,max:Math.max(0,s.length-1),step:1,disabled:!S||s.length===0,value:k<0?0:k,onChange:E=>w(Number(E.target.value)),style:{flex:1}}),i.jsx("span",{"data-testid":`${b}-value`,style:{fontSize:12,color:S?"#0f172a":"#94a3b8",minWidth:28,textAlign:"right"},children:S?s[k]:"–"})]},b)};return i.jsxs("div",{className:"g7le-widget g7le-widget--spacing","data-testid":"g7le-widget-spacing",style:ND,children:[i.jsxs("div",{style:zD,children:[i.jsxs("div",{style:MD,"data-testid":"g7le-spacing-mode",children:[i.jsx("button",{type:"button","data-testid":"g7le-spacing-mode-all","data-active":p?"false":"true",onClick:()=>{f(!1),d&&n(void 0)},style:{...Ig,background:p?"#fff":"#2563eb",color:p?"#0f172a":"#fff",borderColor:p?"#cbd5e1":"#2563eb"},children:o("layout_editor.control.spacing.mode_all")}),i.jsx("button",{type:"button","data-testid":"g7le-spacing-mode-sides","data-active":p?"true":"false",onClick:()=>{if(f(!0),l.all>=0){const b={};for(const x of a)b[x.key]=l.all;g(b)}},style:{...Ig,background:p?"#2563eb":"#fff",color:p?"#fff":"#0f172a",borderColor:p?"#2563eb":"#cbd5e1"},children:o("layout_editor.control.spacing.mode_sides")})]}),i.jsx("span",{"data-testid":"g7le-spacing-value",style:{fontSize:11,color:"#64748b",minWidth:80,textAlign:"right"},children:c?String(t):o("layout_editor.control.default")})]}),p?i.jsx("div",{"data-testid":"g7le-spacing-sides",style:{display:"flex",flexDirection:"column",gap:4},children:a.map(b=>v(`g7le-spacing-side-${b.key}`,b.labelKey,l.sides[b.key]??-1,x=>{const k=h();k[b.key]=x,g(k)},x=>{const k=h();x?k[b.key]=l.sides[b.key]>=0?l.sides[b.key]:y:delete k[b.key],g(k)}))}):i.jsxs("div",{style:Ag,children:[i.jsxs("label",{style:{...Ra,minWidth:64},children:[i.jsx("input",{type:"checkbox","data-testid":"g7le-spacing-all-enabled",checked:l.all>=0,onChange:b=>m(b.target.checked?y:-1)}),i.jsx("span",{style:{fontSize:12,color:"#64748b"},children:o("layout_editor.control.spacing.all")})]}),i.jsx("input",{type:"range","data-testid":"g7le-spacing-all-range",min:0,max:Math.max(0,s.length-1),step:1,disabled:l.all<0||s.length===0,value:l.all<0?0:l.all,onChange:b=>m(Number(b.target.value)),style:{flex:1}}),i.jsx("span",{"data-testid":"g7le-spacing-all-value",style:{fontSize:12,color:l.all>=0?"#0f172a":"#94a3b8",minWidth:28,textAlign:"right"},children:l.all>=0?s[l.all]:"–"})]})]})}const ND={display:"flex",flexDirection:"column",gap:6,width:"100%"},zD={display:"flex",alignItems:"center",justifyContent:"space-between",gap:8},MD={display:"inline-flex",gap:0,border:"1px solid #cbd5e1",borderRadius:6,overflow:"hidden"},Ig={padding:"3px 10px",fontSize:11,border:"none",borderRight:"1px solid #e2e8f0",cursor:"pointer"},LD={display:"flex",alignItems:"center",gap:8,width:"100%"},PD={display:"inline-flex",gap:0,border:"1px solid #cbd5e1",borderRadius:6,overflow:"hidden"},OD={display:"flex",flexDirection:"column",gap:6,width:"100%"},BD={display:"flex",gap:6,alignItems:"center"},WD={flex:1,padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,minWidth:80},jD={padding:"4px 8px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#64748b",cursor:"pointer",whiteSpace:"nowrap"},HD={display:"flex",flexWrap:"wrap",gap:4},FD={fontSize:11,color:"#94a3b8",fontStyle:"italic",padding:"2px 0"},KD={padding:"3px 9px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:999,background:"#fff",cursor:"pointer"},VD={padding:"4px 10px",fontSize:12,border:"none",borderRight:"1px solid #e2e8f0",cursor:"pointer"},Ag={display:"flex",alignItems:"center",gap:8,width:"100%"},Ra={display:"inline-flex",alignItems:"center",gap:4},UD={padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",minWidth:120},qD={position:"relative",width:38,height:20,borderRadius:10,border:"none",cursor:"pointer",padding:2,display:"inline-flex",alignItems:"center",transition:"background 120ms ease"},GD={display:"block",width:16,height:16,borderRadius:8,background:"#fff",transition:"transform 120ms ease"},YD=["#0f172a","#334155","#64748b","#94a3b8","#e2e8f0","#ffffff","#dc2626","#ea580c","#d97706","#16a34a","#0891b2","#2563eb","#7c3aed","#db2777"],Tg=/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;function XD(e){return Array.isArray(e.options)?e.options.filter(t=>typeof t.value=="string").map(t=>({value:t.value,swatch:typeof t.swatch=="string"?t.swatch:void 0,label:typeof t.label=="string"?t.label:void 0})):[]}function JD({control:e,value:t,onChange:n,t:o,freeValueDisabled:r}){const s=XD(e),a=s.length>0,l=typeof t=="string"&&s.some(g=>g.value===t)?t:"",d=typeof t=="string"&&Tg.test(t)?t:"",[c,p]=u.useState(d);u.useEffect(()=>{p(d)},[d]);const f=g=>{const m=g.trim();if(m===""){n(void 0);return}const h=m.startsWith("#")?m:`#${m}`;Tg.test(h)&&n(h)},y=YD;return i.jsxs("div",{className:"g7le-widget g7le-widget--color","data-testid":"g7le-widget-color",style:ZD,children:[r?i.jsx("div",{"data-testid":"g7le-color-free-disabled",style:oN,children:o("layout_editor.property_modal.dark_preset_only")}):i.jsxs("div",{style:QD,children:[i.jsxs("label",{style:RD,children:[i.jsx("span",{"data-testid":"g7le-color-swatch",style:{...eN,background:d||"transparent",backgroundImage:d?void 0:"linear-gradient(45deg,#e2e8f0 25%,transparent 25%,transparent 75%,#e2e8f0 75%),linear-gradient(45deg,#e2e8f0 25%,#fff 25%,#fff 75%,#e2e8f0 75%)",backgroundSize:"8px 8px",backgroundPosition:"0 0, 4px 4px"}}),i.jsx("input",{type:"color","data-testid":"g7le-color-native",value:d||"#000000",onChange:g=>n(g.target.value),style:{width:0,height:0,opacity:0,position:"absolute"}})]}),i.jsx("input",{type:"text","data-testid":"g7le-color-hex",value:c,placeholder:o("layout_editor.control.color_hex_placeholder"),onChange:g=>p(g.target.value),onBlur:g=>f(g.target.value),onKeyDown:g=>{g.key==="Enter"&&f(g.target.value)},style:tN}),i.jsx("button",{type:"button","data-testid":"g7le-color-clear",onClick:()=>n(void 0),style:nN,children:o("layout_editor.control.default")})]}),a?i.jsx("div",{style:Dg,"data-testid":"g7le-color-token-presets",children:s.map(g=>{const m=l===g.value,h=g.label?g.label.startsWith("$t:")?o(g.label.slice(3)):g.label:g.value;return i.jsx("button",{type:"button","aria-label":h,title:h,"data-testid":`g7le-color-token-${g.value}`,"data-active":m?"true":"false",onClick:()=>n(m?void 0:g.value),style:{...Ng,background:g.swatch||"#e2e8f0",outline:m?"2px solid #2563eb":"none"}},g.value)})}):!r&&i.jsx("div",{style:Dg,"data-testid":"g7le-color-presets",children:y.map(g=>i.jsx("button",{type:"button","aria-label":g,"data-testid":`g7le-color-preset-${g}`,onClick:()=>n(g),style:{...Ng,background:g,outline:d.toLowerCase()===g.toLowerCase()?"2px solid #2563eb":"none"}},g))})]})}const ZD={display:"flex",flexDirection:"column",gap:8},QD={display:"flex",alignItems:"center",gap:6},RD={position:"relative",display:"inline-flex",cursor:"pointer"},eN={width:24,height:24,borderRadius:4,border:"1px solid #cbd5e1",display:"inline-block"},tN={flex:1,padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,fontFamily:"monospace"},nN={padding:"4px 8px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",cursor:"pointer",color:"#64748b"},Dg={display:"grid",gridTemplateColumns:"repeat(7, 1fr)",gap:4},Ng={width:"100%",height:18,borderRadius:4,border:"1px solid #cbd5e1",cursor:"pointer",padding:0},oN={fontSize:11,color:"#94a3b8",fontStyle:"italic",padding:"2px 0"},Qo=[{value:"fill",labelKey:"layout_editor.control.background_image.mode_fill",size:"cover",repeat:"no-repeat"},{value:"fit",labelKey:"layout_editor.control.background_image.mode_fit",size:"contain",repeat:"no-repeat"},{value:"tile",labelKey:"layout_editor.control.background_image.mode_tile",size:"auto",repeat:"repeat"}];function rN(e){return e&&typeof e=="object"&&!Array.isArray(e)?e:{}}function zg(e){return Qo.find(n=>n.size===e.size&&n.repeat===e.repeat)?.value??"fill"}function iN({value:e,onChange:t,t:n}){const o=rN(e),{state:r}=Je(),s=yo(),[a,l]=u.useState(!1),[d,c]=u.useState(null),[p,f]=u.useState(o.url??""),[y,g]=u.useState([]),m=u.useRef(null),h=r.templateIdentifier,v=r.selectedRoute?.layoutName??"";u.useEffect(()=>{f(o.url??"")},[o.url]);const b=u.useCallback(async()=>{if(!h||!v){g([]);return}const E=await Dl(h,v);E.ok&&g(E.data)},[h,v]);u.useEffect(()=>{b()},[b]);const x=E=>{if(!E){t(void 0);return}const C=Qo.find(N=>N.value===zg(o))??Qo[0];t({url:E,size:o.size??C.size,repeat:o.repeat??C.repeat,position:o.position??"center"})},k=E=>{const C=Qo.find(N=>N.value===E);!C||!o.url||t({url:o.url,size:C.size,repeat:C.repeat,position:o.position??"center"})},w=async E=>{const C=E.target.files?.[0];if(!C)return;l(!0),c(null);const N=await Nl(h,v,C);N.ok?(x(N.data.url),b()):c(n("layout_editor.control.background_image.upload_failed")),l(!1),m.current&&(m.current.value="")},_=async E=>{(await zl(E.id)).ok&&g(N=>N.filter($=>$.id!==E.id))},S=()=>{const E=s.open({ariaLabel:n("layout_editor.attachment_manager.title"),width:720,maxHeightRatio:.82,content:u.createElement(Ml,{templateIdentifier:h,layoutName:v,t:n,onSelect:C=>{x(C),s.close(E)},onChanged:()=>{b()},onClose:()=>s.close(E)})})};return i.jsxs("div",{className:"g7le-widget g7le-widget--image","data-testid":"g7le-widget-image",style:sN,children:[o.url&&i.jsx("div",{"data-testid":"g7le-image-preview",style:{...aN,backgroundImage:`url(${o.url})`,backgroundSize:o.size??"cover",backgroundRepeat:o.repeat??"no-repeat",backgroundPosition:o.position??"center"}}),i.jsxs("div",{style:lN,children:[i.jsxs("label",{style:dN,children:[a?"…":n("layout_editor.control.background_image.upload"),i.jsx("input",{ref:m,type:"file",accept:"image/*","data-testid":"g7le-image-file",onChange:w,disabled:a,style:{display:"none"}})]}),i.jsx("button",{type:"button","data-testid":"g7le-image-clear",onClick:()=>t(void 0),style:cN,children:n("layout_editor.control.background_image.clear")})]}),i.jsx("input",{type:"text","data-testid":"g7le-image-url",placeholder:"https://… / URL",value:p,onChange:E=>f(E.target.value),onBlur:E=>x(E.target.value.trim()||void 0),style:uN}),i.jsx("div",{style:pN,"data-testid":"g7le-image-modes",children:Qo.map(E=>{const C=zg(o)===E.value;return i.jsx("button",{type:"button","data-testid":`g7le-image-mode-${E.value}`,"data-active":C?"true":"false",disabled:!o.url,onClick:()=>k(E.value),style:{...fN,background:C&&o.url?"#2563eb":"#fff",color:C&&o.url?"#fff":"#0f172a"},children:n(E.labelKey)},E.value)})}),i.jsxs("div",{style:gN,"data-testid":"g7le-image-gallery",children:[i.jsxs("div",{style:hN,children:[i.jsx("span",{style:{fontSize:11,color:"#64748b"},children:n("layout_editor.attachment_manager.recent")}),i.jsxs("button",{type:"button","data-testid":"g7le-image-manage",onClick:S,style:yN,children:["🖼 ",n("layout_editor.attachment_manager.manage_link")]})]}),y.length>0?i.jsx("div",{style:bN,"data-testid":"g7le-image-thumbs",children:y.slice(0,8).map(E=>i.jsxs("div",{style:mN,"data-testid":`g7le-image-thumb-${E.id}`,children:[i.jsx("button",{type:"button",title:E.original_name,onClick:()=>x(E.url),style:{...vN,backgroundImage:`url(${E.url})`},"data-testid":`g7le-image-thumb-use-${E.id}`}),i.jsx("button",{type:"button",title:n("layout_editor.attachment_manager.delete"),onClick:()=>_(E),style:_N,"data-testid":`g7le-image-thumb-del-${E.id}`,children:"✕"})]},E.id))}):i.jsx("span",{style:{fontSize:11,color:"#94a3b8"},children:n("layout_editor.attachment_manager.empty")})]}),d&&i.jsx("span",{"data-testid":"g7le-image-error",style:{fontSize:11,color:"#dc2626"},children:d})]})}const sN={display:"flex",flexDirection:"column",gap:6},aN={width:"100%",height:80,borderRadius:6,border:"1px solid #cbd5e1"},lN={display:"flex",gap:6},dN={padding:"5px 10px",fontSize:12,border:"1px solid #2563eb",borderRadius:6,background:"#fff",color:"#2563eb",cursor:"pointer",display:"inline-flex",alignItems:"center"},cN={padding:"5px 10px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#64748b",cursor:"pointer"},uN={padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6},pN={display:"inline-flex",border:"1px solid #cbd5e1",borderRadius:6,overflow:"hidden"},fN={padding:"4px 10px",fontSize:12,border:"none",borderRight:"1px solid #e2e8f0",cursor:"pointer"},gN={display:"flex",flexDirection:"column",gap:4,border:"1px solid #e2e8f0",borderRadius:6,padding:6,marginTop:2},hN={display:"flex",alignItems:"center",justifyContent:"space-between"},yN={fontSize:11,color:"#2563eb",background:"transparent",border:"none",cursor:"pointer",padding:0},bN={display:"flex",gap:6,flexWrap:"wrap"},mN={position:"relative",width:44,height:44},vN={width:44,height:44,borderRadius:6,border:"1px solid #cbd5e1",backgroundSize:"cover",backgroundPosition:"center",cursor:"pointer",padding:0},_N={position:"absolute",top:-6,right:-6,width:16,height:16,borderRadius:"50%",border:"none",background:"#dc2626",color:"#fff",fontSize:9,lineHeight:"16px",cursor:"pointer",padding:0};function xN(e){return Array.isArray(e)&&e.every(t=>t!==null&&typeof t=="object"&&!Array.isArray(t))}function wN(e){return typeof e=="string"&&/\{\{.*\}\}/.test(e)}function kN({value:e,onChange:t,t:n,bindingCandidates:o}){if(wN(e))return i.jsx("div",{className:"g7le-widget g7le-widget--options-list","data-testid":"g7le-widget-options-list-bound",style:DN,children:n("layout_editor.list_editor.bound_degraded")});const r=xN(e)?e:[],s=y=>{t(y.length===0?void 0:y)},a=(y,g)=>{const m=r.map((h,v)=>v===y?{...h,...g}:h);s(m)},l=y=>{s(r.filter((g,m)=>m!==y))},d=(y,g)=>{const m=y+g;if(m<0||m>=r.length)return;const h=[...r];[h[y],h[m]]=[h[m],h[y]],s(h)},c=()=>{s([...r,{value:"",label:""}])},p=y=>typeof y.label=="string"?y.label:y.label==null?"":String(y.label),f=y=>y.value===void 0||y.value===null?"":String(y.value);return i.jsxs("div",{className:"g7le-widget g7le-widget--options-list","data-testid":"g7le-widget-options-list",style:SN,children:[r.length===0&&i.jsx("div",{"data-testid":"g7le-options-empty",style:TN,children:n("layout_editor.list_editor.empty")}),r.map((y,g)=>i.jsxs("div",{"data-testid":`g7le-options-row-${g}`,style:CN,children:[i.jsxs("div",{style:Mg,children:[i.jsx("span",{style:Lg,children:n("layout_editor.list_editor.option_value")}),i.jsx("input",{type:"text","data-testid":`g7le-options-value-${g}`,value:f(y),placeholder:n("layout_editor.list_editor.option_value"),onChange:m=>a(g,{value:m.target.value}),style:EN})]}),i.jsxs("div",{style:Mg,children:[i.jsx("span",{style:Lg,children:n("layout_editor.list_editor.option_label")}),i.jsx("div",{"data-testid":`g7le-options-label-${g}`,style:{flex:1,minWidth:0},children:i.jsx(vt,{value:p(y),onChange:m=>a(g,{label:m??""}),t:n,placeholder:n("layout_editor.list_editor.option_label"),testidPrefix:`g7le-options-label-i18n-${g}`,candidates:o,enableExpressionTree:!0,expressionTreeCollapsible:!0})})]}),i.jsxs("div",{style:$N,children:[i.jsx("button",{type:"button","data-testid":`g7le-options-up-${g}`,title:n("layout_editor.list_editor.move_up"),disabled:g===0,onClick:()=>d(g,-1),style:Pg,children:"↑"}),i.jsx("button",{type:"button","data-testid":`g7le-options-down-${g}`,title:n("layout_editor.list_editor.move_down"),disabled:g===r.length-1,onClick:()=>d(g,1),style:Pg,children:"↓"}),i.jsx("button",{type:"button","data-testid":`g7le-options-remove-${g}`,title:n("layout_editor.list_editor.remove"),onClick:()=>l(g),style:IN,children:"✕"})]})]},g)),i.jsx("button",{type:"button","data-testid":"g7le-options-add",onClick:c,style:AN,children:n("layout_editor.list_editor.add")})]})}const SN={display:"flex",flexDirection:"column",gap:8,width:"100%"},CN={display:"flex",flexDirection:"column",gap:4,width:"100%",minWidth:0,paddingBottom:6,borderBottom:"1px solid #f1f5f9"},Mg={display:"flex",gap:6,alignItems:"center",width:"100%",minWidth:0},Lg={flexShrink:0,width:36,fontSize:11,color:"#94a3b8",textAlign:"right"},$N={display:"flex",gap:4,alignItems:"center",justifyContent:"flex-end"},EN={flex:1,minWidth:0,padding:"4px 6px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6},Pg={padding:"2px 6px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},IN={padding:"2px 6px",fontSize:12,border:"1px solid #fecaca",borderRadius:6,background:"#fff",color:"#dc2626",cursor:"pointer"},AN={padding:"4px 8px",fontSize:12,border:"1px dashed #94a3b8",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer",alignSelf:"flex-start"},TN={fontSize:11,color:"#94a3b8",fontStyle:"italic"},DN={fontSize:11,color:"#b45309",fontStyle:"italic",padding:"4px 2px"},Og=/[^A-Za-z0-9\-_:.]/g;function NN(e){return typeof e=="string"&&Ui(e)}function Bg(e){return Ui(e)?In(e).map(t=>t.kind==="binding"?t.raw:t.raw.replace(Og,"")).join(""):e.replace(Og,"")}const zN={width:"100%",padding:"6px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:4,fontFamily:"monospace"},MN={marginTop:4,fontSize:11,color:"#94a3b8"},LN={display:"flex",gap:6,alignItems:"center"},PN={flexShrink:0,padding:"5px 8px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:4,background:"#f8fafc",cursor:"pointer",whiteSpace:"nowrap"};function ON({value:e,onChange:t,t:n,bindingCandidates:o}){const r=e==null?"":String(e),s=NN(e),a=!!o&&o.length>0,[l,d]=u.useState(!1),c=p=>{const f=Bg(p);t(f===""?void 0:f)};return s||l?i.jsxs("div",{"data-testid":"g7le-core-id-chip",children:[i.jsx(bs,{value:r,onChange:c,t:n,candidates:o,onDone:()=>d(!1),testidPrefix:"g7le-core-id-chip-input"}),i.jsx("div",{style:MN,children:n("layout_editor.core_props.id.binding_hint")})]}):i.jsx("div",{children:i.jsxs("div",{style:LN,children:[i.jsx("input",{type:"text",className:"g7le-widget g7le-widget--core-id","data-testid":"g7le-widget-core-id",value:r,onChange:p=>{const f=Bg(p.target.value);t(f===""?void 0:f)},spellCheck:!1,autoComplete:"off",style:zN}),a&&i.jsxs("button",{type:"button","data-testid":"g7le-core-id-add-data",onClick:()=>d(!0),style:PN,title:n("layout_editor.core_props.id.add_data"),children:["🔗 ",n("layout_editor.core_props.id.add_data")]})]})})}const BN=/[^A-Za-z0-9.\-_]/g;function WN(e){return typeof e=="string"&&/\{\{[\s\S]*?\}\}/.test(e)}function jN(e){return e.replace(BN,"")}const Wg={width:"100%",padding:"6px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:4,fontFamily:"monospace",boxSizing:"border-box"},HN={...Wg,background:"#f1f5f9",color:"#64748b",cursor:"not-allowed"},jg={marginTop:4,fontSize:11,color:"#94a3b8"};function FN({value:e,onChange:t,t:n}){const o=e==null?"":String(e);return WN(e)?i.jsxs("div",{"data-testid":"g7le-core-datakey-bound",children:[i.jsx("input",{type:"text",className:"g7le-widget g7le-widget--core-datakey-bound","data-testid":"g7le-widget-core-datakey",value:o,readOnly:!0,disabled:!0,style:HN}),i.jsx("div",{style:jg,children:n("layout_editor.core_props.dataKey.bound_degraded")})]}):i.jsxs("div",{children:[i.jsx("input",{type:"text",className:"g7le-widget g7le-widget--core-datakey","data-testid":"g7le-widget-core-datakey",value:o,placeholder:n("layout_editor.core_props.dataKey.placeholder"),onChange:s=>{const a=jN(s.target.value);t(a===""?void 0:a)},spellCheck:!1,autoComplete:"off",style:Wg}),i.jsx("div",{style:jg,children:n("layout_editor.core_props.dataKey.hint")})]})}let Hg=!1;function KN(){Hg||(Xe("segmented",SD),Xe("slider",CD),Xe("select",$D),Xe("dimension",ID),Xe("spacing",DD),Xe("toggle",ED),Xe("color",JD),Xe("image",iN),Xe("tag-input",ys),Xe("page-picker",Ew),Xe("datasource-picker",Bc),Xe("binding-picker",Bc),Xe("binding-list",Tw),Xe("state-key-picker",Es),Xe("modal-picker",Iw),Xe("i18n-text",Aw),Xe("text",Dw),Xe("options-list",kN),Xe("core-id",ON),Xe("core-datakey",FN),Xe("component-target-picker",OT),Hg=!0)}function Ro(e){return Array.isArray(e.children)?e.children:[]}function er(e){if(typeof e.text=="string")return[];let t=null;const n=(o,r)=>{const s=Ro(o);for(let a=0;ad===o?tr(l,r,n):l);return{...e,children:a}}function Fg(e,t){const n=r=>typeof r.props?.[t]=="string";if(n(e))return[];const o=(r,s)=>{const a=Ro(r);for(let l=0;lc===r?Kg(d,s,n,o):d);return{...e,children:l}}function VN(e){return Array.isArray(e.children)?e.children:[]}function UN(e){const t=e?.itemFields;if(!Array.isArray(t))return[{kind:"text"}];const n=[];for(const o of t){if(!o||typeof o!="object")continue;const r=o;r.kind==="text"?n.push({kind:"text",label:typeof r.label=="string"?r.label:void 0}):r.kind==="prop"&&typeof r.prop=="string"&&r.prop.length>0&&n.push({kind:"prop",prop:r.prop,label:typeof r.label=="string"?r.label:void 0})}return n.length>0?n:[{kind:"text"}]}function el(e,t){if(!(typeof e!="string"||e.length===0))return e.startsWith("$t:")?t(e.slice(3)):e}function Vg({node:e,params:t,spec:n,manifest:o,t:r,onPatchNode:s,templateIdentifier:a,candidates:l}){const d=typeof t?.childComponent=="string"?t.childComponent:null,c=t?.childTemplate&&typeof t.childTemplate=="object"?t.childTemplate:null,p=UN(t),f=el(typeof t?.childLabel=="string"?t.childLabel:void 0,r)??d??"",y=VN(e),g=u.useCallback(_=>{s({...e,children:_})},[e,s]),m=u.useCallback(()=>{if(c)return JSON.parse(JSON.stringify(c));if(!d)return null;const _=n?.componentPalette?.entries?.[d];if(_?.defaultNode&&typeof _.defaultNode=="object")return JSON.parse(JSON.stringify(_.defaultNode));const S=o?.components??{};let E=null;for(const C of Object.values(S))if(Array.isArray(C)){const N=C.find($=>$.name===d);if(N){E=N;break}}return E?Sf(E):{type:"basic",name:d}},[c,d,n,o]),h=u.useCallback(()=>{const _=m();if(_){if(_.text==="")_.text=r("layout_editor.list_editor.new_item");else{const S=er(_);if(S!==null&&S.length>0){const E=_i(_,S);if(E&&E.text===""){const C=tr(_,S,r("layout_editor.list_editor.new_item"));g([...y,C]);return}}}g([...y,_])}},[m,g,y,r]),v=u.useCallback(_=>{g(y.filter((S,E)=>E!==_))},[g,y]),b=u.useCallback((_,S)=>{const E=_+S;if(E<0||E>=y.length)return;const C=[...y];[C[_],C[E]]=[C[E],C[_]],g(C)},[g,y]),x=u.useCallback((_,S)=>{if(typeof S!="string"||S.length===0)return;const E=y[_];if(!E)return;const C=er(E);if(C===null)return;const N=y.map(($,A)=>A===_?tr($,C,S):$);g(N)},[y,g]),k=u.useCallback((_,S,E)=>{if(typeof E!="string"||E.length===0)return;const C=y[_];if(!C)return;const N=Fg(C,S);if(N===null)return;const $=y.map((A,z)=>z===_?Kg(A,N,S,E):A);g($)},[y,g]);if(!d&&!c)return i.jsx("div",{className:"g7le-node-editor g7le-node-editor--children","data-testid":"g7le-children-editor-misconfigured",style:Gg,children:r("layout_editor.list_editor.no_child_component")});const w=p.length>1||p.some(_=>_.label);return i.jsxs("div",{className:"g7le-node-editor g7le-node-editor--children","data-testid":"g7le-children-editor",style:qN,children:[i.jsx("div",{style:GN,children:r("layout_editor.list_editor.items_title")}),y.length===0&&i.jsx("div",{"data-testid":"g7le-children-empty",style:Gg,children:r("layout_editor.list_editor.empty")}),y.map((_,S)=>{const E=[];for(const N of p)if(N.kind==="text"){const $=er(_),A=$!==null?_i(_,$):null;A&&typeof A.text=="string"&&E.push({key:"text",caption:el(N.label,r)??(w?r("layout_editor.list_editor.item_label"):void 0),value:A.text,onChange:z=>x(S,z),testid:`g7le-children-i18n-${S}`})}else if(N.kind==="prop"&&N.prop){const $=Fg(_,N.prop);if($!==null){const z=_i(_,$)?.props?.[N.prop];E.push({key:`prop-${N.prop}`,caption:el(N.label,r)??N.prop,value:typeof z=="string"?z:"",onChange:D=>k(S,N.prop,D),testid:`g7le-children-prop-${N.prop}-${S}`})}}const C=typeof _?.name=="string"?_.name:r("layout_editor.list_editor.item");return i.jsxs("div",{"data-testid":`g7le-children-row-${S}`,style:YN,children:[E.length>0?E.map(N=>i.jsx("div",{style:Ug,children:i.jsxs("div",{"data-testid":N.key==="text"?`g7le-children-text-${S}`:`g7le-children-field-${N.key}-${S}`,style:XN,children:[N.caption!==void 0&&i.jsx("div",{"data-testid":`g7le-children-caption-${N.key}-${S}`,style:JN,children:N.caption}),i.jsx(vt,{value:N.value,onChange:N.onChange,t:r,placeholder:r("layout_editor.list_editor.item_text"),testidPrefix:N.testid,candidates:l,enableExpressionTree:!0,expressionTreeCollapsible:!0})]})},N.key)):i.jsx("div",{style:Ug,children:i.jsx("span",{"data-testid":`g7le-children-label-${S}`,style:QN,children:C})}),i.jsxs("div",{style:ZN,children:[i.jsx("button",{type:"button","data-testid":`g7le-children-up-${S}`,title:r("layout_editor.list_editor.move_up"),disabled:S===0,onClick:()=>b(S,-1),style:qg,children:"↑"}),i.jsx("button",{type:"button","data-testid":`g7le-children-down-${S}`,title:r("layout_editor.list_editor.move_down"),disabled:S===y.length-1,onClick:()=>b(S,1),style:qg,children:"↓"}),i.jsx("button",{type:"button","data-testid":`g7le-children-remove-${S}`,title:r("layout_editor.list_editor.remove"),onClick:()=>v(S),style:RN,children:"✕"})]})]},S)}),i.jsx("button",{type:"button","data-testid":"g7le-children-add",onClick:h,style:e8,children:r("layout_editor.list_editor.add_child",{component:f})})]})}const qN={display:"flex",flexDirection:"column",gap:6,width:"100%",marginBottom:12},GN={fontSize:12,fontWeight:700,color:"#0f172a",marginBottom:2},YN={display:"flex",flexDirection:"column",gap:4,width:"100%",minWidth:0,paddingBottom:6,borderBottom:"1px solid #f1f5f9"},Ug={display:"flex",gap:4,alignItems:"flex-start",width:"100%",minWidth:0},XN={flex:1,minWidth:0},JN={fontSize:11,color:"#64748b",marginBottom:2},ZN={display:"flex",gap:4,alignItems:"center",justifyContent:"flex-end"},QN={flex:1,minWidth:0,padding:"4px 6px",fontSize:12,color:"#475569",fontStyle:"italic",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},qg={padding:"2px 6px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},RN={padding:"2px 6px",fontSize:12,border:"1px solid #fecaca",borderRadius:6,background:"#fff",color:"#dc2626",cursor:"pointer"},e8={padding:"4px 8px",fontSize:12,border:"1px dashed #94a3b8",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer",alignSelf:"flex-start"},Gg={fontSize:11,color:"#94a3b8",fontStyle:"italic"},fo={rowContainer:"Tbody",row:"Tr",cell:"Td",headerCell:"Th",colSpanProp:"colSpan",rowSpanProp:"rowSpan"};function ht(e){return{rowContainer:e?.rowContainer??fo.rowContainer,row:e?.row??fo.row,cell:e?.cell??fo.cell,headerCell:e?.headerCell??fo.headerCell,colSpanProp:e?.colSpanProp??fo.colSpanProp,rowSpanProp:e?.rowSpanProp??fo.rowSpanProp}}function Bn(e){return e?Array.isArray(e.children)?e.children:[]:[]}function Yg(e,t){const n=e.props?.[t],o=typeof n=="number"?n:typeof n=="string"?parseInt(n,10):NaN;return Number.isFinite(o)&&o>=1?Math.floor(o):1}function Be(e,t){const n=ht(t),o=[],r=Bn(e),s=[];r.forEach((f,y)=>{(Bn(f).some(h=>h.name===n.row)||f.name===n.rowContainer||f.name==="Thead"||f.name==="Tfoot")&&s.push({container:f,containerIndex:y,isHeader:f.name==="Thead"})}),s.length===0&&r.some(f=>f.name===n.row)&&s.push({container:e,containerIndex:-1,isHeader:!1});const a=[],l=[];let d=0,c=0;const p=f=>{for(;a.length<=f;)a.push([]);return a[f]};s.forEach((f,y)=>{Bn(f.container).filter(m=>m.name===n.row).forEach((m,h)=>{const v=[],b=p(d);let x=0;Bn(m).filter(w=>w.name===n.cell||w.name===n.headerCell).forEach((w,_)=>{for(;b[x];)x++;const S=Yg(w,n.colSpanProp),E=Yg(w,n.rowSpanProp),C={cell:w,sectionIndex:y,rowIndexInSection:h,cellIndexInRow:_,gridRow:d,gridCol:x,colSpan:S,rowSpan:E,isHeader:w.name===n.headerCell};v.push(C);for(let N=0;N=0?(a.push(s.containerIndex),l=Bn(e)[s.containerIndex]??e):l=e;let d=-1,c=-1,p=null;const f=Bn(l);for(let m=0;m=r.gridRow&&t=r.gridCol&&nr){s=!0;break}if(s)break}s||t.add(r)}const n=Array.from(t).sort((r,s)=>r-s),o=[];for(let r=0;re.rows.find(l=>(l.cells[0]?.gridRow??-1)===a)?.sectionIndex??e.rows[a]?.sectionIndex??0,o=new Set([0,t]);for(let a=1;aa){l=!0;break}if(l)break}l||o.add(a)}const r=Array.from(o).sort((a,l)=>a-l),s=[];for(let a=0;at>=a.start&&t=o.length?{ok:!1,reason:"boundary"}:o[r].sectionIndex!==o[s].sectionIndex?{ok:!1,reason:"section"}:{ok:!0}}function wi(e,t,n){const o=Xg(e),r=o.findIndex(a=>t>=a.start&&t=o.length?{ok:!1,reason:"boundary"}:{ok:!0}}function Ge(e){return e?Array.isArray(e.children)?e.children:[]:[]}function mn(e,t,n){const o={...e.props??{}};n<=1?delete o[t]:o[t]=n;const r={...e,props:o};return Object.keys(o).length===0&&delete r.props,r}function ki(e,t,n=!1){const o={type:"basic",name:n?e.headerCell:e.cell},r=t?.props?.className;return typeof r=="string"&&r&&(o.props={className:r}),o.text="",o}function nr(e,t){return e.name===t.cell||e.name===t.headerCell}function nt(e,t,n,o){const r=t.sections[n];if(!r)return e;if(r.containerIndex===-1)return o(e);const s=Ge(e).map((a,l)=>l===r.containerIndex?o(a):a);return{...e,children:s}}function yt(e,t,n,o){let r=-1;const s=Ge(e).map(a=>a.name===t&&(r++,r===n)?o(a):a);return{...e,children:s}}function Si(e,t,n){let o=-1;const r=Ge(e);for(let s=0;s0?nt(e,r,r.sections.length-1,x=>({...x,children:[...Ge(x),b]})):{...e,children:[...Ge(e),b]}}const s=Math.max(-1,Math.min(n,r.rows.length-1)),a=s<0?r.rows[0]:r.rows[s],l=a.cells[0]?.gridRow??s,d=s<0?0:l+1,c=a.sectionIndex,p=s<0?0:a.rowIndexInSection+1,f=[],y=[];let g=0;for(;g0?Ue(r,d-1,g):null;b&&b.gridRow+b.rowSpan>d&&b.rowSpan>1?(y.push(b),g+=b.colSpan):(f.push(ki(o)),g+=1)}let m=e;for(const b of y)m=nt(m,r,b.sectionIndex,x=>yt(x,o.row,b.rowIndexInSection,k=>({...k,children:Ge(k).map((w,_)=>_===b.cellIndexInRow?mn(w,o.rowSpanProp,b.rowSpan+1):w)})));const h={type:"basic",name:o.row,children:f},v=Be(m,t);return m=nt(m,v,c,b=>{const x=Ge(b);let k=0,w=x.length;for(let _=0;_=r.rows.length||r.rows.length<=1)return e;const s=r.rows[n],a=s.cells[0]?.gridRow??n;let l=e;const d=new Set;for(let p=0;pyt(y,o.row,f.rowIndexInSection,g=>({...g,children:Ge(g).map((m,h)=>h===f.cellIndexInRow?mn(m,o.rowSpanProp,f.rowSpan-1):m)})))),p+=f?f.colSpan:1}const c=Be(l,t);return l=nt(l,c,s.sectionIndex,p=>{let f=-1;const y=Ge(p).filter(g=>g.name===o.row?(f++,f!==s.rowIndexInSection):!0);return{...p,children:y}}),l}function Qg(e,t,n,o){const r=ht(t),s=Be(e,t);if(!xi(s,n,o).ok)return e;const a=Jg(s),l=a.findIndex(h=>n>=h.start&&ns.rows.filter(v=>{const b=v.cells[0]?.gridRow??-1;return v.sectionIndex===f&&b>=h.start&&bv.rowIndexInSection),g=y(c),m=y(p);return g.length===0||m.length===0?e:nt(e,s,f,h=>{const v=[...Ge(h)],b=[];v.forEach((M,B)=>{M.name===r.row&&b.push(B)});const x=g.map(M=>b[M]).filter(M=>M!==void 0),k=m.map(M=>b[M]).filter(M=>M!==void 0);if(x.length===0||k.length===0)return h;const w=x.map(M=>v[M]),_=k.map(M=>v[M]),S=Math.min(...x)n>=m.start&&nw.gridCol>=f.start&&w.gridColw.gridCol>=y.start&&w.gridColw.cellIndexInRow),x=v.map(w=>w.cellIndexInRow),k=Be(g,t);g=nt(g,k,m.sectionIndex,w=>yt(w,r.row,m.rowIndexInSection,_=>{const S=[...Ge(_)],E=b.map(M=>Si(_,r,M)).filter(M=>M>=0),C=x.map(M=>Si(_,r,M)).filter(M=>M>=0);if(E.length===0||C.length===0)return _;const N=E.map(M=>S[M]),$=C.map(M=>S[M]),A=Math.min(...E,...C),z=Math.max(...E,...C),D=[...$,...N],O=[...S.slice(0,A),...D,...S.slice(z+1)];return{..._,children:O}}))}return g}function eh(e,t,n){const o=ht(t),r=Be(e,t);if(r.rows.length===0||r.colCount===0)return nl(e,t,-1);const s=Math.max(0,Math.min(n+1,r.colCount));let a=e;const l=new Set;for(const d of r.rows){const c=d.cells[0]?.gridRow??0,p=s>0?Ue(r,c,s-1):null,f=s1){if(!l.has(p.cell)){l.add(p.cell);const h=Be(a,t);a=nt(a,h,p.sectionIndex,v=>yt(v,o.row,p.rowIndexInSection,b=>({...b,children:Ge(b).map((x,k)=>k===p.cellIndexInRow?mn(x,o.colSpanProp,p.colSpan+1):x)})))}continue}if(p&&p.gridRow0&&d.cells.every(h=>h.isHeader),m=Be(a,t);a=nt(a,m,d.sectionIndex,h=>yt(h,o.row,d.rowIndexInSection,v=>{const b=[...Ge(v)];let x;if(y<0)x=b.findIndex(k=>nr(k,o)),x<0&&(x=b.length);else{const k=Si(v,o,y);x=k<0?b.length:k+1}return b.splice(x,0,ki(o,void 0,g)),{...v,children:b}}))}return a}function th(e,t,n){const o=ht(t),r=Be(e,t);if(n<0||n>=r.colCount||r.colCount<=1)return e;let s=e;const a=new Set;for(const l of r.rows){const d=l.cells[0]?.gridRow??0,c=Ue(r,d,n);if(!(!c||a.has(c.cell)||c.gridRow1){const p=Be(s,t);s=nt(s,p,c.sectionIndex,f=>yt(f,o.row,c.rowIndexInSection,y=>({...y,children:Ge(y).map((g,m)=>m===c.cellIndexInRow?mn(g,o.colSpanProp,c.colSpan-1):g)})))}else{const p=Be(s,t);s=nt(s,p,c.sectionIndex,f=>yt(f,o.row,c.rowIndexInSection,y=>{let g=-1;const m=Ge(y).filter(h=>nr(h,o)?(g++,g!==c.cellIndexInRow):!0);return{...y,children:m}}))}}return s}function ol(e,t,n,o,r,s){const a=ht(t),l=Be(e,t),d=Math.min(n,r),c=Math.max(n,r),p=Math.min(o,s),f=Math.max(o,s);if(d===c&&p===f)return{table:e,ok:!1,reason:"single"};if(!l.valid)return{table:e,ok:!1,reason:"invalid-grid"};const y=new Set;for(let b=d;b<=c;b++)for(let x=p;x<=f;x++){const k=Ue(l,b,x);if(!k)return{table:e,ok:!1,reason:"hole"};if(k.gridRowc||k.gridCol+k.colSpan-1>f)return{table:e,ok:!1,reason:"not-rectangular"};y.add(k)}const g=Ue(l,d,p),m=Array.from(y).filter(b=>b!==g);let h=nt(e,l,g.sectionIndex,b=>yt(b,a.row,g.rowIndexInSection,x=>({...x,children:Ge(x).map((k,w)=>{if(w!==g.cellIndexInRow)return k;let _=mn(k,a.colSpanProp,f-p+1);return _=mn(_,a.rowSpanProp,c-d+1),_})})));const v=new Map;for(const b of m){const x=`${b.sectionIndex}:${b.rowIndexInSection}`,k=v.get(x)??[];k.push(b),v.set(x,k)}for(const[b,x]of v){const k=parseInt(b.split(":")[0],10),w=x[0].rowIndexInSection,_=new Set(x.map(E=>E.cellIndexInRow)),S=Be(h,t);h=nt(h,S,k,E=>yt(E,a.row,w,C=>{let N=-1;const $=Ge(C).filter(A=>nr(A,a)?(N++,!_.has(N)):!0);return{...C,children:$}}))}return{table:h,ok:!0}}function nh(e,t,n,o){const r=ht(t),s=Be(e,t),a=Ue(s,n,o);if(!a||a.colSpan<=1&&a.rowSpan<=1)return e;const l=a.gridRow,d=a.gridCol,c=a.gridRow+a.rowSpan-1,p=a.gridCol+a.colSpan-1,f=a.isHeader,y=typeof a.cell.props?.className=="string"?a.cell.props.className:void 0;let g=nt(e,s,a.sectionIndex,m=>yt(m,r.row,a.rowIndexInSection,h=>({...h,children:Ge(h).map((v,b)=>{if(b!==a.cellIndexInRow)return v;let x=mn(v,r.colSpanProp,1);return x=mn(x,r.rowSpanProp,1),x})})));for(let m=l;m<=c;m++)for(let h=d;h<=p;h++){if(m===l&&h===d)continue;const v=Be(g,t),b=v.rows.find(w=>w.cells.some(_=>_.gridRow===m))??v.rows[m];if(!b)continue;let x=-1;for(const w of b.cells)w.gridRow===m&&w.gridColyt(w,r.row,b.rowIndexInSection,_=>{const S=[...Ge(_)];let E;if(x<0)E=S.findIndex(C=>nr(C,r)),E<0&&(E=S.length);else{const C=Si(_,r,x);E=C<0?S.length:C+1}return S.splice(E,0,ki(r,k,f)),{..._,children:S}}))}return g}function Ci(e,t,n,o,r){const s=ht(t),a=Be(e,t),l=Ue(a,n,o);return l?nt(e,a,l.sectionIndex,d=>yt(d,s.row,l.rowIndexInSection,c=>({...c,children:Ge(c).map((p,f)=>{if(f!==l.cellIndexInRow)return p;const y={...p.props??{}};r.trim()===""?delete y.className:y.className=r;const g={...p,props:y};return Object.keys(y).length===0&&delete g.props,g})}))):e}function Jt(e,t,n,o,r,s){const a=ht(t),l=Be(e,t),d=Ue(l,n,o);return d?nt(e,l,d.sectionIndex,c=>yt(c,a.row,d.rowIndexInSection,p=>({...p,children:Ge(p).map((f,y)=>{if(y!==d.cellIndexInRow)return f;const g={...f.props??{}},m={...g.style??{}};!s||s.trim()===""?delete m[r]:m[r]=s,Object.keys(m).length===0?delete g.style:g.style=m;const h={...f,props:g};return Object.keys(g).length===0&&delete h.props,h})}))):e}function go(e,t,n,o,r,s,a){const l=ht(t),d=Be(e,t),c=Ue(d,n,o);if(!c)return e;const p=a==="dark";return nt(e,d,c.sectionIndex,f=>yt(f,l.row,c.rowIndexInSection,y=>({...y,children:Ge(y).map((g,m)=>{if(m!==c.cellIndexInRow)return g;const h={...g.props??{}},v=typeof h.className=="string"?h.className:"",b=dC(v,s,r,p);b===""?delete h.className:h.className=b;const x={...g,props:h};return Object.keys(h).length===0&&delete x.props,x})})))}function oh(e,t,n,o,r){const s=ht(t),a=Be(e,t),l=Ue(a,n,o);return l?nt(e,a,l.sectionIndex,d=>yt(d,s.row,l.rowIndexInSection,c=>({...c,children:Ge(c).map((p,f)=>f===l.cellIndexInRow?{...p,text:r}:p)}))):e}function rh(e,t,n,o,r){const s=ht(t),a=Be(e,t),l=Ue(a,n,o);return l?nt(e,a,l.sectionIndex,d=>yt(d,s.row,l.rowIndexInSection,c=>({...c,children:Ge(c).map((p,f)=>f===l.cellIndexInRow?r:p)}))):e}const ih=/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;function t8(e){return e.split(/\s+/).filter(Boolean)}function sh(e,t){for(const n of t)if(e===n||/^-?\d+$/.test(e.slice(n.length+1))&&e.startsWith(n+"-"))return!0;return!1}function ah({className:e,catalog:t,t:n,onChange:o,colorScheme:r="light",onPresetToken:s,colorStyle:a,onColorStyle:l,disabled:d}){const c=t?.sides??[],p=t?.widths??[],f=t?.colors??[],y=r==="dark",g=u.useMemo(()=>c.map($=>$.prefix),[c]),m=u.useMemo(()=>new Set(f.map($=>$.token)),[f]),h=u.useMemo(()=>t8(e),[e]),v=u.useMemo(()=>Vr(e,y),[e,y]),b=u.useMemo(()=>v.find($=>m.has($))??null,[v,m]),x=u.useMemo(()=>{const $=new Set;for(const A of c)h.some(D=>D===A.prefix||D.startsWith(A.prefix+"-")&&/^-?\d+$/.test(D.slice(A.prefix.length+1)))&&$.add(A.value);return $},[h,c]),k=u.useMemo(()=>{const $=h.find(z=>sh(z,g));if(!$)return"none";const A=p.filter(z=>z.value!=="none");for(const z of c){if($===z.prefix)return A.find(D=>(D.suffix??"")==="")?.value??"thin";if($.startsWith(z.prefix+"-")){const D=$.slice(z.prefix.length),O=A.find(M=>(M.suffix??"")===D);if(O)return O.value}}return"thin"},[h,g,c,p]);if(!t||c.length===0||p.length===0)return null;function w($){return $.filter(A=>!sh(A,g))}function _($,A){const z=w(h),D=p.find(B=>B.value===A);if(!D||A==="none"||$.size===0)return z.join(" ").trim();const O=D.suffix??"",M=[...z];for(const B of c)$.has(B.value)&&M.push(O?`${B.prefix}${O}`:B.prefix);return M.join(" ").trim()}const S=$=>{const A=new Set(x);A.has($)?A.delete($):($==="all"?A.clear():A.delete("all"),A.add($));const z=k==="none"?p.find(D=>D.value!=="none")?.value??"thin":k;o(_(A,z))},E=$=>{const A=x.size===0?new Set(["all"]):x;o(_(A,$))},C=$=>{s?.($)},N=($,A)=>$&&$.startsWith("$t:")?n($.slice(3)):$||A;return i.jsxs("div",{className:"g7le-cell-border","data-testid":"g7le-cell-border",style:n8,children:[i.jsx("div",{style:o8,children:i.jsx("span",{style:r8,children:n("layout_editor.table_editor.cell_border")})}),i.jsx("div",{style:rl,"data-testid":"g7le-cell-border-widths",children:p.map($=>i.jsx("button",{type:"button",disabled:d,"data-testid":`g7le-cell-border-width-${$.value}`,"aria-pressed":k===$.value,onClick:()=>E($.value),style:k===$.value?lh:il,children:N($.label,$.value)},$.value))}),k!=="none"&&i.jsx("div",{style:rl,"data-testid":"g7le-cell-border-sides",children:c.map($=>i.jsx("button",{type:"button",disabled:d,"data-testid":`g7le-cell-border-side-${$.value}`,"aria-pressed":x.has($.value),onClick:()=>S($.value),style:x.has($.value)?lh:il,children:N($.label,$.value)},$.value))}),k!=="none"&&(f.length>0||l)&&i.jsx(i.Fragment,{children:i.jsxs("div",{style:rl,"data-testid":"g7le-cell-border-colors",children:[f.map($=>{const A=b===$.token;return i.jsx("button",{type:"button",disabled:d,title:N($.label,$.value),"data-testid":`g7le-cell-border-color-${$.value}`,"aria-pressed":A,onClick:()=>C($.token),style:{...dh,background:$.swatch??"#fff",outline:A?"2px solid #2563eb":"1px solid #cbd5e1"}},$.value)}),l&&(()=>{const $=!!a&&f.some(z=>z.swatch&&z.swatch.toLowerCase()===a.toLowerCase()),A=!y&&!!a&&!$;return i.jsx("label",{title:n(y?"layout_editor.table_editor.dark_free_color_hint":"layout_editor.table_inplace.custom_color"),"data-testid":"g7le-cell-border-color-custom",style:{...dh,position:"relative",overflow:"hidden",display:"inline-flex",alignItems:"center",justifyContent:"center",background:A&&ih.test(a)?a:"conic-gradient(red, yellow, lime, aqua, blue, magenta, red)",outline:A?"2px solid #2563eb":"1px solid #cbd5e1",opacity:y?.4:1,cursor:y?"not-allowed":"pointer"},children:i.jsx("input",{type:"color",disabled:d||y,"data-testid":"g7le-cell-border-color-picker",value:a&&ih.test(a)?a:"#000000",onChange:z=>l(z.target.value),style:{position:"absolute",width:0,height:0,opacity:0}})})})()]})})]})}const n8={display:"flex",flexDirection:"column",gap:6},o8={display:"flex",alignItems:"center",justifyContent:"space-between",gap:8},r8={fontSize:12,fontWeight:600,color:"#475569"},rl={display:"flex",gap:4,flexWrap:"wrap",alignItems:"center"},il={padding:"3px 8px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},lh={...il,border:"1px solid #2563eb",background:"#eff6ff",color:"#1d4ed8",fontWeight:600},dh={width:22,height:22,borderRadius:5,padding:0,cursor:"pointer",boxSizing:"border-box"},ch=/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;function uh({colorStyle:e,className:t,catalog:n,t:o,colorScheme:r="light",onPresetToken:s,onCustomColor:a,onClear:l,disabled:d}){const c=n?.colors??[],p=r==="dark",f=e??"",y=Vr(t??"",p),g=c.find(x=>x.token&&y.includes(x.token))?.token??null,m=(x,k)=>x&&x.startsWith("$t:")?o(x.slice(3)):x||k,h=!g&&(p||f.trim()===""),v=!!f&&c.some(x=>x.swatch&&x.swatch.toLowerCase()===f.toLowerCase()),b=!p&&!!f&&!v;return i.jsxs("div",{className:"g7le-cell-fill","data-testid":"g7le-cell-fill",style:i8,children:[i.jsx("div",{style:s8,children:i.jsx("span",{style:a8,children:o("layout_editor.table_editor.cell_fill")})}),i.jsxs("div",{style:l8,"data-testid":"g7le-cell-fill-colors",children:[i.jsx("button",{type:"button",disabled:d,title:o("layout_editor.table_editor.cell_fill_none"),"data-testid":"g7le-cell-fill-none","aria-pressed":h,onClick:()=>l?.(),style:{...sl,background:"repeating-conic-gradient(#e2e8f0 0% 25%, #ffffff 0% 50%) 50% / 10px 10px",outline:h?"2px solid #2563eb":"1px solid #cbd5e1"}}),c.map(x=>{const k=!!x.token&&g===x.token,w=!p&&!!x.swatch&&f.toLowerCase()===(x.swatch??"").toLowerCase(),_=k||w,S=d||p&&!x.token;return i.jsx("button",{type:"button",disabled:S,title:m(x.label,x.value),"data-testid":`g7le-cell-fill-color-${x.value}`,"aria-pressed":_,onClick:()=>{x.token&&s?s(x.token):!p&&x.swatch&&a&&a(x.swatch)},style:{...sl,background:x.swatch??"#fff",outline:_?"2px solid #2563eb":"1px solid #cbd5e1",opacity:S?.4:1,cursor:S?"not-allowed":"pointer"}},x.value)}),a&&i.jsx("label",{title:o(p?"layout_editor.table_editor.dark_free_color_hint":"layout_editor.table_editor.cell_fill_custom"),"data-testid":"g7le-cell-fill-custom",style:{...sl,position:"relative",overflow:"hidden",display:"inline-flex",alignItems:"center",justifyContent:"center",background:b&&ch.test(f)?f:"conic-gradient(red, yellow, lime, aqua, blue, magenta, red)",outline:b?"2px solid #2563eb":"1px solid #cbd5e1",opacity:p?.4:1,cursor:p?"not-allowed":"pointer"},children:i.jsx("input",{type:"color",disabled:d||p,"data-testid":"g7le-cell-fill-color-picker",value:f&&ch.test(f)?f:"#ffffff",onChange:x=>a(x.target.value),style:{position:"absolute",width:0,height:0,opacity:0}})})]})]})}const i8={display:"flex",flexDirection:"column",gap:6},s8={display:"flex",alignItems:"center",justifyContent:"space-between",gap:8},a8={fontSize:12,fontWeight:600,color:"#475569"},l8={display:"flex",gap:4,flexWrap:"wrap",alignItems:"center"},sl={width:22,height:22,borderRadius:5,padding:0,cursor:"pointer",boxSizing:"border-box"};function d8(e){const t=/^(\d+)px$/.exec(e.trim());return t?parseInt(t[1],10):null}function ph({paddingStyle:e,catalog:t,t:n,onChange:o,disabled:r}){const s=t?.steps??[],a=e??"",l=d8(a),d=(h,v)=>h&&h.startsWith("$t:")?n(h.slice(3)):h||v,c=s.some(h=>(h.px??0)===(l??-1)),p=l!==null&&!c?String(l):"",[f,y]=u.useState(p);u.useEffect(()=>{y(p)},[e]);const g=h=>{o(h&&h>0?`${h}px`:""),y("")},m=h=>{const v=parseInt(h,10);Number.isFinite(v)&&v>=0&&o(v>0?`${v}px`:"")};return i.jsxs("div",{className:"g7le-cell-padding","data-testid":"g7le-cell-padding",style:c8,children:[i.jsx("div",{style:u8,children:n("layout_editor.table_editor.cell_padding")}),i.jsxs("div",{style:p8,"data-testid":"g7le-cell-padding-steps",children:[s.map(h=>{const v=(h.px??0)===(l??-1)||!h.px&&a.trim()==="";return i.jsx("button",{type:"button",disabled:r,"data-testid":`g7le-cell-padding-step-${h.value}`,"aria-pressed":v,onClick:()=>g(h.px),style:v?f8:fh,children:d(h.label,h.value)},h.value)}),i.jsxs("span",{style:{display:"inline-flex",alignItems:"center",gap:3},children:[i.jsx("input",{type:"number",min:0,disabled:r,"data-testid":"g7le-cell-padding-free",value:f,placeholder:n("layout_editor.table_editor.cell_padding_custom"),onChange:h=>y(h.target.value),onBlur:()=>f.trim()!==""&&m(f),onKeyDown:h=>{h.key==="Enter"&&f.trim()!==""&&m(f)},style:g8}),i.jsx("span",{style:{fontSize:11,color:"#94a3b8"},children:"px"})]})]})]})}const c8={display:"flex",flexDirection:"column",gap:6},u8={fontSize:12,fontWeight:600,color:"#475569"},p8={display:"flex",gap:4,flexWrap:"wrap",alignItems:"center"},fh={padding:"3px 8px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},f8={...fh,border:"1px solid #2563eb",background:"#eff6ff",color:"#1d4ed8",fontWeight:600},g8={width:48,padding:"3px 5px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,boxSizing:"border-box"};function gh({colorScheme:e,onChange:t,t:n,disabled:o}){const r=e==="dark";return i.jsxs("div",{className:"g7le-color-scheme-tabs","data-testid":"g7le-color-scheme-tabs",style:h8,children:[i.jsxs("div",{style:y8,children:[i.jsx("span",{style:b8,children:n("layout_editor.table_editor.cell_color")}),i.jsxs("div",{style:m8,children:[i.jsx("button",{type:"button",disabled:o,"data-testid":"g7le-color-scheme-light","aria-pressed":!r,onClick:()=>t("light"),style:r?al:hh,children:n("layout_editor.property_modal.scope.scheme_light")}),i.jsx("button",{type:"button",disabled:o,"data-testid":"g7le-color-scheme-dark","aria-pressed":r,onClick:()=>t("dark"),style:r?hh:al,children:n("layout_editor.property_modal.scope.scheme_dark")})]})]}),r&&i.jsx("div",{style:v8,"data-testid":"g7le-color-scheme-dark-hint",children:n("layout_editor.table_editor.dark_free_color_hint")})]})}const h8={display:"flex",flexDirection:"column",gap:3},y8={display:"flex",alignItems:"center",justifyContent:"space-between",gap:8},b8={fontSize:12,fontWeight:700,color:"#0f172a"},m8={display:"flex",gap:2},al={padding:"2px 9px",fontSize:11,border:"none",borderBottom:"2px solid transparent",background:"transparent",color:"#94a3b8",cursor:"pointer"},hh={...al,color:"#1d4ed8",borderBottom:"2px solid #2563eb",fontWeight:600},v8={fontSize:10,color:"#94a3b8",fontStyle:"italic"},ll="dark:";function yh(e){return typeof e=="string"?e.split(/\s+/).filter(Boolean):[]}function bh(e,t){return t==="dark"?e.filter(n=>n.startsWith(ll)).map(n=>n.slice(ll.length)):e.filter(n=>!n.startsWith(ll))}const _8={none:0,thin:1,medium:2,thick:4},mh={top:{w:"borderTopWidth",s:"borderTopStyle",c:"borderTopColor"},bottom:{w:"borderBottomWidth",s:"borderBottomStyle",c:"borderBottomColor"},left:{w:"borderLeftWidth",s:"borderLeftStyle",c:"borderLeftColor"},right:{w:"borderRightWidth",s:"borderRightStyle",c:"borderRightColor"}},vh=["top","bottom","left","right"];function x8(e,t,n="light"){if(!e||!t)return{};const o=t.sides??[],r=t.widths??[],s=t.colors??[],a=yh(e.props?.className);if(a.length===0&&!e.props?.style)return{};const l=[...r].filter(b=>b.value!=="none").sort((b,x)=>(x.suffix??"").length-(b.suffix??"").length),d={};for(const b of o)for(const x of l){const k=x.suffix?`${b.prefix}${x.suffix}`:b.prefix;if(a.includes(k)){d[b.value]=x.value;break}}const c=e.props?.style??{},p=bh(a,n),f=s.find(b=>p.includes(b.token))?.swatch??"",g=(n==="light"&&typeof c.borderColor=="string"?c.borderColor:"")||f||"#94a3b8",m=b=>{const x=mh[b]?.c,k=x?c[x]:void 0;return typeof k=="string"&&k?k:g},h={},v=(b,x)=>{const k=mh[b];if(!k)return;const w=_8[x]??1;h[k.w]=`${w}px`,h[k.s]=w>0?"solid":"none",w>0&&(h[k.c]=m(b))};if(d.all)for(const b of vh)v(b,d.all);for(const b of vh)d[b]&&v(b,d[b]);return h}function w8(e,t,n="light"){if(!e?.props)return{};const o=e.props.style??{},r={},s=yh(e.props.className),a=t?.colors?.find(c=>c.token&&bh(s,n).includes(c.token))?.swatch??"",d=(n==="light"&&typeof o.backgroundColor=="string"?o.backgroundColor:"")||a;return d&&(r.backgroundColor=d),typeof o.padding=="string"&&o.padding&&(r.padding=o.padding),r}const k8=/^\s*\$t:(custom\.[a-zA-Z0-9._-]+)\s*$/,S8=/^\s*\$t:([a-zA-Z0-9._-]+)\s*$/,C8=/\{\{.*?\}\}/;function _h(e,t,n){const o=e?.text;if(typeof o!="string")return{display:"",editable:!1,isKey:!1};const r=mt(o);if(r&&r.key.startsWith("custom.")){let a="";try{const l=Pt.getInstance().translate(r.key,{templateId:t,locale:n});a=l&&l!==r.key?l:""}catch{a=""}return{display:a,editable:!0,isKey:!0}}if(C8.test(o))return{display:o,editable:!1,isKey:!1};const s=S8.exec(o);if(s){const a=s[1];let l="";try{const d=Pt.getInstance().translate(a,{templateId:t,locale:n});l=d&&d!==a?d:""}catch{l=""}return{display:l,editable:!0,isKey:!0}}return{display:o,editable:!0,isKey:!1}}function dl(e){const t=er(e);return t===null?{node:null,path:null}:{node:_i(e,t),path:t}}function $8(e){if(!e||typeof e.text!="string")return null;const t=k8.exec(e.text);if(t)return t[1];const n=mt(e.text);return n&&n.key.startsWith("custom.")?n.key:null}function E8({node:e,params:t,t:n,onPatchNode:o,templateIdentifier:r,candidates:s}){const{state:a}=Je(),l=a.selectedRoute?.layoutName??null,d=a.locale,c=r??a.templateIdentifier,p=u.useMemo(()=>t&&typeof t=="object"?t:null,[t]);u.useMemo(()=>ht(p),[p]);const f=u.useMemo(()=>Be(e,p),[e,p]),y=u.useMemo(()=>{const q=p?.cellBorder;return q&&typeof q=="object"?q:null},[p]),g=u.useMemo(()=>{const q=p?.cellBackground;return q&&typeof q=="object"?q:null},[p]),m=u.useMemo(()=>{const q=p?.cellPadding;return q&&typeof q=="object"?q:null},[p]),[h,v]=u.useState(null),[b,x]=u.useState(null),[k,w]=u.useState({}),[_,S]=u.useState(!1),[E,C]=u.useState(null),[N,$]=u.useState(null),[A,z]=u.useState("light"),D=u.useCallback(q=>o(q),[o]),O=u.useMemo(()=>h?Ue(f,h.r,h.c):null,[f,h]),M=N!==null?N:h?.c??null,B=E!==null?E:h?.r??null,T=u.useMemo(()=>{const q=(y?.sides??[]).find($e=>$e.value==="all")??(y?.sides??[])[0],ae=(y?.widths??[]).find($e=>$e.value!=="none");if(!q||!ae)return[];const pe=ae.suffix??"";return[pe?`${q.prefix}${pe}`:q.prefix]},[y]),H=u.useCallback((q,ae,pe)=>{if(T.length===0)return q;const $e=(y?.sides??[]).map(J=>J.prefix),be=Ue(Be(q,p),ae,pe),oe=(typeof be?.cell?.props?.className=="string"?be.cell.props.className:"").split(/\s+/).filter(Boolean);return oe.some(J=>$e.some(le=>J===le||J.startsWith(le+"-")&&/^-?\d+$/.test(J.slice(le.length+1))))?q:Ci(q,p,ae,pe,[...oe,...T].join(" ").trim())},[T,y,p]),L=u.useCallback(q=>{if(!h)return;const ae=Math.min(h.r,b?.r??h.r),pe=Math.max(h.r,b?.r??h.r),$e=Math.min(h.c,b?.c??h.c),be=Math.max(h.c,b?.c??h.c),j=new Set,oe=[];for(let J=ae;J<=pe;J++)for(let le=$e;le<=be;le++)j.add(`${J}:${le}`),oe.push({r:J,c:le});let ue=e;for(const J of oe){if(ue=H(ue,J.r,J.c),ue=Jt(ue,p,J.r,J.c,"borderColor",q),J.r>0&&!j.has(`${J.r-1}:${J.c}`)){const le=Ue(Be(ue,p),J.r-1,J.c);le&&(ue=Jt(ue,p,le.gridRow,le.gridCol,"borderBottomColor",q))}if(J.c>0&&!j.has(`${J.r}:${J.c-1}`)){const le=Ue(Be(ue,p),J.r,J.c-1);le&&(ue=Jt(ue,p,le.gridRow,le.gridCol,"borderRightColor",q))}}D(ue)},[h,b,e,p,D,H]),I=u.useCallback(q=>L(q),[L]),P=u.useMemo(()=>(y?.colors??[]).map(q=>q.token).filter(Boolean),[y]),F=u.useCallback(q=>{if(!h||P.length===0)return;const ae=Math.min(h.r,b?.r??h.r),pe=Math.max(h.r,b?.r??h.r),$e=Math.min(h.c,b?.c??h.c),be=Math.max(h.c,b?.c??h.c);let j=e;for(let oe=ae;oe<=pe;oe++)for(let ue=$e;ue<=be;ue++)j=H(j,oe,ue),j=go(j,p,oe,ue,q,P,A);D(j)},[h,b,e,p,D,H,P,A]),G=u.useMemo(()=>{if(A==="dark")return"";const q=O?.cell.props?.style;return typeof q?.borderColor=="string"?q.borderColor:""},[O,A]),V=u.useCallback((q,ae)=>{if(!h)return;const pe=Math.min(h.r,b?.r??h.r),$e=Math.max(h.r,b?.r??h.r),be=Math.min(h.c,b?.c??h.c),j=Math.max(h.c,b?.c??h.c);let oe=e;for(let ue=pe;ue<=$e;ue++)for(let J=be;J<=j;J++)oe=Jt(oe,p,ue,J,q,ae);D(oe)},[h,b,e,p,D]),Y=u.useMemo(()=>(g?.colors??[]).map(q=>q.token).filter(q=>!!q),[g]),Z=u.useCallback(()=>h?{r0:Math.min(h.r,b?.r??h.r),r1:Math.max(h.r,b?.r??h.r),c0:Math.min(h.c,b?.c??h.c),c1:Math.max(h.c,b?.c??h.c)}:null,[h,b]),se=u.useCallback(q=>{const ae=Z();if(!ae||Y.length===0)return;let pe=e;for(let $e=ae.r0;$e<=ae.r1;$e++)for(let be=ae.c0;be<=ae.c1;be++)pe=go(pe,p,$e,be,q,Y,A);D(pe)},[Z,e,p,D,Y,A]),re=u.useCallback(q=>V("backgroundColor",q),[V]),U=u.useCallback(()=>{const q=Z();if(!q)return;let ae=e;for(let pe=q.r0;pe<=q.r1;pe++)for(let $e=q.c0;$e<=q.c1;$e++)ae=go(ae,p,pe,$e,void 0,Y,A),A==="light"&&(ae=Jt(ae,p,pe,$e,"backgroundColor",void 0));D(ae)},[Z,e,p,D,Y,A]),ee=u.useCallback(q=>V("padding",q),[V]),fe=u.useMemo(()=>{if(A==="dark")return"";const q=O?.cell.props?.style;return typeof q?.backgroundColor=="string"?q.backgroundColor:""},[O,A]),K=u.useMemo(()=>typeof O?.cell.props?.className=="string"?O.cell.props.className:"",[O]),ne=u.useMemo(()=>{const q=O?.cell.props?.style;return typeof q?.padding=="string"?q.padding:""},[O]),me=u.useCallback(q=>q==="section"?n("layout_editor.table_editor.move_blocked_section"):"",[n]),Ce=u.useCallback((q,ae)=>xi(f,q,ae),[f]),ze=u.useCallback((q,ae)=>wi(f,q,ae),[f]),Se=u.useCallback(q=>D(nl(e,p,q)),[e,p,D]),qe=u.useCallback(q=>{D(Zg(e,p,q)),v(null),x(null)},[e,p,D]),De=u.useCallback((q,ae)=>D(Qg(e,p,q,ae)),[e,p,D]),Pe=u.useCallback(q=>D(eh(e,p,q)),[e,p,D]),Qe=u.useCallback(q=>{D(th(e,p,q)),v(null),x(null)},[e,p,D]),Me=u.useCallback((q,ae)=>D(Rg(e,p,q,ae)),[e,p,D]),Re=u.useCallback(()=>{if(!h||!b)return;const q=ol(e,p,h.r,h.c,b.r,b.c);q.ok&&(D(q.table),v({r:Math.min(h.r,b.r),c:Math.min(h.c,b.c)}),x(null))},[e,p,h,b,D]),ve=u.useCallback(()=>{h&&(D(nh(e,p,h.r,h.c)),x(null))},[e,p,h,D]),te=u.useCallback(q=>{if(!h)return;const ae=Math.min(h.r,b?.r??h.r),pe=Math.max(h.r,b?.r??h.r),$e=Math.min(h.c,b?.c??h.c),be=Math.max(h.c,b?.c??h.c);let j=e;for(let oe=ae;oe<=pe;oe++)for(let ue=$e;ue<=be;ue++)j=Ci(j,p,oe,ue,q);D(j)},[e,p,h,b,D]),he=u.useCallback(async(q,ae)=>{const pe=`${q}:${ae}`,$e=k[pe];if($e===void 0)return;const be=Ue(f,q,ae);if(!be)return;const{node:j,path:oe}=dl(be.cell);if(oe===null)return;const ue=_h(j,c,d);if(ue.editable){if($e===ue.display){w(J=>{const le={...J};return delete le[pe],le});return}S(!0);try{const J=$8(j);if(J)await yr(c,J,d,$e),await tn(c,d);else{const le=await qn(c,l,d,$e);if(le.kind==="ok"&&le.resource){const Ie=`$t:${le.resource.translation_key}`;if(oe.length===0)D(oh(e,p,q,ae,Ie));else{const dt=tr(be.cell,oe,Ie);D(rh(e,p,q,ae,dt))}await tn(c,d)}}}finally{S(!1),w(J=>{const le={...J};return delete le[pe],le})}}},[k,f,c,d,l,e,p,D]),Q=u.useMemo(()=>{if(!O)return null;const{node:q,path:ae}=dl(O.cell);return ae===null?null:{value:typeof q?.text=="string"?q.text:"",editable:!0}},[O]),ie=u.useCallback(q=>{if(typeof q!="string"||q.length===0||!h)return;const ae=Ue(f,h.r,h.c);if(!ae)return;const pe=er(ae.cell);if(pe!==null)if(pe.length===0)D(oh(e,p,h.r,h.c,q));else{const $e=tr(ae.cell,pe,q);D(rh(e,p,h.r,h.c,$e))}},[h,f,e,p,D]),_e=u.useCallback((q,ae,pe)=>{pe?x({r:q,c:ae}):(v({r:q,c:ae}),x(null))},[]),ge=u.useMemo(()=>Array.from({length:f.colCount},(q,ae)=>ae),[f.colCount]),Ae=!!(h&&b&&(h.r!==b.r||h.c!==b.c)),Ne=!!(O&&(O.colSpan>1||O.rowSpan>1)),Le=O&&typeof O.cell.props?.className=="string"?O.cell.props.className:"";return i.jsxs("div",{className:"g7le-node-editor g7le-node-editor--table","data-testid":"g7le-table-editor",style:I8,children:[i.jsx("div",{style:A8,children:n("layout_editor.table_editor.title")}),i.jsx("div",{style:{overflowX:"auto"},children:i.jsxs("table",{"data-testid":"g7le-table-editor-grid",style:T8,children:[i.jsx("thead",{children:i.jsxs("tr",{children:[i.jsx("th",{style:D8}),ge.map(q=>{const ae=ze(q,-1),pe=ze(q,1),$e=M===q;return i.jsx("th",{style:N8,"data-testid":`g7le-table-colgutter-${q}`,onMouseEnter:()=>$(q),onMouseLeave:()=>$(be=>be===q?null:be),children:i.jsxs("div",{style:{...M8,visibility:$e?"visible":"hidden"},children:[i.jsx("button",{type:"button",title:ae.ok?n("layout_editor.table_editor.move_col_left"):me(ae.reason)||n("layout_editor.table_editor.move_col_left"),disabled:!ae.ok||_,onClick:()=>Me(q,-1),"data-testid":`g7le-table-col-left-${q}`,style:ae.ok?vn:$i,children:"←"}),i.jsx("button",{type:"button",title:n("layout_editor.table_editor.add_col"),disabled:_,onClick:()=>Pe(q),"data-testid":`g7le-table-col-add-${q}`,style:vn,children:"+"}),i.jsx("button",{type:"button",title:n("layout_editor.table_editor.remove_col"),disabled:f.colCount<=1||_,onClick:()=>Qe(q),"data-testid":`g7le-table-col-remove-${q}`,style:xh,children:"✕"}),i.jsx("button",{type:"button",title:pe.ok?n("layout_editor.table_editor.move_col_right"):me(pe.reason)||n("layout_editor.table_editor.move_col_right"),disabled:!pe.ok||_,onClick:()=>Me(q,1),"data-testid":`g7le-table-col-right-${q}`,style:pe.ok?vn:$i,children:"→"})]})},q)})]})}),i.jsx("tbody",{children:f.rows.map((q,ae)=>{const pe=q.cells[0]?.gridRow??ae,$e=B===pe;return i.jsxs("tr",{"data-testid":`g7le-table-row-${pe}`,onMouseEnter:()=>C(pe),onMouseLeave:()=>C(be=>be===pe?null:be),children:[i.jsx("td",{style:z8,"data-testid":`g7le-table-rowgutter-${pe}`,children:(()=>{const be=Ce(pe,-1),j=Ce(pe,1);return i.jsxs("div",{style:{...L8,visibility:$e?"visible":"hidden"},children:[i.jsx("button",{type:"button",title:be.ok?n("layout_editor.table_editor.move_row_up"):me(be.reason)||n("layout_editor.table_editor.move_row_up"),disabled:!be.ok||_,onClick:()=>De(pe,-1),"data-testid":`g7le-table-row-up-${pe}`,style:be.ok?vn:$i,children:"↑"}),i.jsx("button",{type:"button",title:n("layout_editor.table_editor.add_row"),disabled:_,onClick:()=>Se(pe),"data-testid":`g7le-table-row-add-${pe}`,style:vn,children:"+"}),i.jsx("button",{type:"button",title:n("layout_editor.table_editor.remove_row"),disabled:f.rows.length<=1||_,onClick:()=>qe(pe),"data-testid":`g7le-table-row-remove-${pe}`,style:xh,children:"✕"}),i.jsx("button",{type:"button",title:j.ok?n("layout_editor.table_editor.move_row_down"):me(j.reason)||n("layout_editor.table_editor.move_row_down"),disabled:!j.ok||_,onClick:()=>De(pe,1),"data-testid":`g7le-table-row-down-${pe}`,style:j.ok?vn:$i,children:"↓"})]})})()}),q.cells.map(be=>{const j=`${be.gridRow}:${be.gridCol}`,{node:oe,path:ue}=dl(be.cell),J=_h(oe,c,d),le=ue!==null&&J.editable,Ie=ue===null,dt=k[j],pt=dt!==void 0?dt:J.display,ot=h&&h.r===be.gridRow&&h.c===be.gridCol,Oe=h&&b&&be.gridRow>=Math.min(h.r,b.r)&&be.gridRow<=Math.max(h.r,b.r)&&be.gridCol>=Math.min(h.c,b.c)&&be.gridCol<=Math.max(h.c,b.c);return i.jsx("td",{colSpan:be.colSpan,rowSpan:be.rowSpan,"data-testid":`g7le-table-cell-${be.gridRow}-${be.gridCol}`,onClick:He=>_e(be.gridRow,be.gridCol,He.shiftKey),style:{...P8,...x8(be.cell,y,A),...w8(be.cell,g,A),...be.isHeader?O8:{},...ot?B8:{},...Oe&&!ot?W8:{}},children:le?i.jsx("input",{type:"text","data-testid":`g7le-table-cell-input-${be.gridRow}-${be.gridCol}`,value:pt,placeholder:n("layout_editor.table_editor.cell_text"),disabled:_,onMouseDown:He=>{He.shiftKey&&He.preventDefault(),_e(be.gridRow,be.gridCol,He.shiftKey)},onClick:He=>He.stopPropagation(),onChange:He=>w(Ye=>({...Ye,[j]:He.target.value})),onBlur:()=>{he(be.gridRow,be.gridCol)},style:j8}):Ie?i.jsx("span",{"data-testid":`g7le-table-cell-struct-${be.gridRow}-${be.gridCol}`,style:wh,children:n("layout_editor.table_editor.structural_cell")}):i.jsx("span",{style:wh,children:J.display})},j)})]},ae)})})]})}),i.jsxs("button",{type:"button","data-testid":"g7le-table-add-row-bottom",onClick:()=>Se(f.rows.length-1),disabled:_,style:H8,children:["+ ",n("layout_editor.table_editor.add_row")]}),i.jsxs("div",{style:F8,"data-testid":"g7le-table-cell-tools",children:[i.jsx("button",{type:"button","data-testid":"g7le-table-merge",onClick:Re,disabled:!Ae||_,style:kh,children:n("layout_editor.table_editor.merge_cells")}),i.jsx("button",{type:"button","data-testid":"g7le-table-unmerge",onClick:ve,disabled:!Ne||_,style:kh,children:n("layout_editor.table_editor.unmerge_cells")})]}),O&&Q&&i.jsxs("div",{style:V8,"data-testid":"g7le-table-cell-i18n-section",children:[i.jsx("div",{style:U8,children:n("layout_editor.table_editor.cell_i18n_label")}),i.jsx(vt,{value:Q.value,onChange:ie,t:n,placeholder:n("layout_editor.table_editor.cell_text"),testidPrefix:"g7le-table-cell-i18n",candidates:s,enableExpressionTree:!0,expressionTreeCollapsible:!0})]}),O&&i.jsxs("div",{style:K8,"data-testid":"g7le-table-cell-color-section",children:[i.jsx(gh,{colorScheme:A,onChange:z,t:n,disabled:_}),y&&i.jsx("div",{style:cl,"data-testid":"g7le-table-cell-border-row",children:i.jsx(ah,{className:Le,catalog:y,t:n,onChange:te,colorScheme:A,onPresetToken:F,colorStyle:G,onColorStyle:I,disabled:_})}),i.jsx("div",{style:cl,"data-testid":"g7le-table-cell-fill-row",children:i.jsx(uh,{colorStyle:fe,className:K,catalog:g,t:n,colorScheme:A,onPresetToken:se,onCustomColor:re,onClear:U,disabled:_})})]}),O&&i.jsx("div",{style:cl,"data-testid":"g7le-table-cell-padding-row",children:i.jsx(ph,{paddingStyle:ne,catalog:m,t:n,onChange:ee,disabled:_})}),O&&!y&&i.jsxs("label",{style:q8,"data-testid":"g7le-table-cell-border-row",children:[i.jsx("span",{style:G8,children:n("layout_editor.table_editor.cell_border")}),i.jsx("input",{type:"text","data-testid":"g7le-table-cell-border-input",value:Le,placeholder:n("layout_editor.table_editor.cell_class_placeholder"),onChange:q=>te(q.target.value),disabled:_,style:Y8})]}),!f.valid&&i.jsx("div",{"data-testid":"g7le-table-grid-warning",style:X8,children:n("layout_editor.table_editor.grid_invalid")}),i.jsx("div",{style:J8,children:n("layout_editor.table_editor.select_hint")})]})}const I8={display:"flex",flexDirection:"column",gap:8,width:"100%",marginBottom:12},A8={fontSize:12,fontWeight:700,color:"#0f172a"},T8={borderCollapse:"collapse",width:"auto"},D8={width:28,background:"transparent",border:"none"},N8={padding:2,border:"none",background:"transparent"},z8={padding:2,border:"none",background:"transparent",verticalAlign:"middle"},M8={display:"flex",gap:2,justifyContent:"center"},L8={display:"grid",gridTemplateColumns:"repeat(2, 16px)",gap:2,justifyContent:"center"},vn={width:16,height:16,lineHeight:"14px",padding:0,fontSize:10,border:"1px solid #cbd5e1",borderRadius:4,background:"#fff",color:"#475569",cursor:"pointer"},xh={...vn,border:"1px solid #fecaca",color:"#dc2626"},$i={...vn,cursor:"not-allowed",opacity:.4,color:"#94a3b8",borderColor:"#e2e8f0"},P8={border:"1px solid #cbd5e1",padding:0,width:64,minWidth:64,maxWidth:120,height:24,background:"#fff",cursor:"pointer"},O8={background:"#f1f5f9",fontWeight:600},B8={outline:"2px solid #2563eb",outlineOffset:-2},W8={background:"#dbeafe"},j8={width:"100%",boxSizing:"border-box",padding:"2px 5px",fontSize:11,height:22,lineHeight:"18px",border:"none",background:"transparent",outline:"none"},wh={display:"block",padding:"2px 5px",fontSize:10,color:"#64748b",fontStyle:"italic",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap",maxWidth:110},H8={padding:"4px 8px",fontSize:12,border:"1px dashed #94a3b8",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer",alignSelf:"flex-start"},F8={display:"flex",gap:6},kh={padding:"4px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},K8={display:"flex",flexDirection:"column",gap:8,borderTop:"1px solid #f1f5f9",paddingTop:8},V8={display:"flex",flexDirection:"column",gap:6,borderTop:"1px solid #f1f5f9",paddingTop:8},U8={fontSize:12,fontWeight:700,color:"#0f172a"},cl={display:"flex",flexDirection:"column",gap:6},q8={display:"flex",alignItems:"center",gap:6},G8={fontSize:12,color:"#475569",minWidth:60},Y8={flex:1,minWidth:0,padding:"4px 6px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6},X8={fontSize:11,color:"#b45309",background:"#fffbeb",border:"1px solid #fde68a",borderRadius:6,padding:"4px 6px"},J8={fontSize:11,color:"#94a3b8"},Z8=/\{\{.*?\}\}/;function Q8(e,t,n){const r=(e.props??{})[t];return Array.isArray(r)?r:r===void 0&&n?JSON.parse(JSON.stringify(n)):[]}function R8(e){return typeof e=="string"&&Z8.test(e)}function ul(e,t,n){return e?e.startsWith("$t:")?t(e.slice(3)):e:n}function ez(e,t,n){return typeof e=="object"&&e!==null?e[t.key]:n?e:void 0}function tz(e,t,n,o){if(typeof e=="object"&&e!==null){const r={...e};return o===void 0?delete r[t.key]:r[t.key]=o,r}return n?o:{[t.key]:o}}function Sh({node:e,params:t,t:n,onPatchNode:o,templateIdentifier:r,candidates:s}){const a=typeof t?.arrayProp=="string"?t.arrayProp:null,l=Array.isArray(t?.fields)?t.fields:[],d=ul(typeof t?.itemLabel=="string"?t.itemLabel:void 0,n,n("layout_editor.array_editor.item")),c=t?.newItem,p=c&&typeof c=="object"?c:null,f=typeof c=="string"||typeof c=="number"?c:null,y=(l.find(S=>S.primary)??l[0])?.key,g=(e.props??{})[a??""],m=Array.isArray(t?.defaultItems)?t.defaultItems:null,h=a?Q8(e,a,m):[],v=u.useCallback(S=>{if(!a)return;const E={...e.props??{}};E[a]=S,o({...e,props:E})},[a,e,o]),b=u.useCallback(()=>{if(p)return JSON.parse(JSON.stringify(p));if(f!==null)return f;const S={};for(const E of l)(E.widget??"text")==="boolean"?S[E.key]=!1:S[E.key]="";return S},[p,f,l]),x=u.useCallback(()=>{v([...h,b()])},[v,h,b]),k=u.useCallback(S=>{v(h.filter((E,C)=>C!==S))},[v,h]),w=u.useCallback((S,E)=>{const C=S+E;if(C<0||C>=h.length)return;const N=[...h];[N[S],N[C]]=[N[C],N[S]],v(N)},[v,h]),_=u.useCallback((S,E,C)=>{const N=E.key===y,$=h.map((A,z)=>z===S?tz(A,E,N,C):A);v($)},[h,v,y]);return a?R8(g)||g!==void 0&&!Array.isArray(g)?i.jsx("div",{className:"g7le-node-editor g7le-node-editor--array","data-testid":"g7le-array-editor-bound",style:cz,children:n("layout_editor.array_editor.bound_degraded")}):i.jsxs("div",{className:"g7le-node-editor g7le-node-editor--array","data-testid":"g7le-array-editor",style:nz,children:[i.jsx("div",{style:oz,children:n("layout_editor.array_editor.items_title")}),h.length===0&&i.jsx("div",{"data-testid":"g7le-array-empty",style:$h,children:n("layout_editor.array_editor.empty")}),h.map((S,E)=>i.jsxs("div",{"data-testid":`g7le-array-row-${E}`,style:rz,children:[i.jsxs("div",{style:iz,children:[i.jsxs("span",{style:sz,children:["#",E+1]}),i.jsx("div",{style:{flex:1}}),i.jsx("button",{type:"button","data-testid":`g7le-array-up-${E}`,title:n("layout_editor.array_editor.move_up"),disabled:E===0,onClick:()=>w(E,-1),style:Ch,children:"↑"}),i.jsx("button",{type:"button","data-testid":`g7le-array-down-${E}`,title:n("layout_editor.array_editor.move_down"),disabled:E===h.length-1,onClick:()=>w(E,1),style:Ch,children:"↓"}),i.jsx("button",{type:"button","data-testid":`g7le-array-remove-${E}`,title:n("layout_editor.array_editor.remove"),onClick:()=>k(E),style:lz,children:"✕"})]}),l.map(C=>{const N=C.widget??"text",$=C.key===y,A=ul(C.label,n,C.key),z=ez(S,C,$),D=`g7le-array-field-${E}-${C.key}`;if(N==="boolean")return i.jsxs("label",{style:Wn,children:[i.jsx("span",{style:jn,children:A}),i.jsx("input",{type:"checkbox","data-testid":D,checked:z===!0,onChange:M=>_(E,C,M.target.checked)})]},C.key);if(N==="select"){const M=Array.isArray(C.options)?C.options:[];return i.jsxs("label",{style:Wn,children:[i.jsx("span",{style:jn,children:A}),i.jsxs("select",{"data-testid":D,value:z==null?"":String(z),onChange:B=>_(E,C,B.target.value===""?void 0:B.target.value),style:or,children:[i.jsx("option",{value:"",children:"—"}),M.map((B,T)=>i.jsx("option",{value:String(B.value),children:ul(B.label,n,String(B.value))},T))]})]},C.key)}if(N==="i18n-text"){const M=typeof z=="string"?z:z==null?"":String(z);return i.jsxs("label",{style:Wn,children:[i.jsx("span",{style:jn,children:A}),i.jsx("div",{"data-testid":D,style:{flex:1,minWidth:0},children:i.jsx(vt,{value:M,onChange:B=>_(E,C,B===""?void 0:B),t:n,placeholder:A,testidPrefix:`${D}-i18n`,candidates:s,enableExpressionTree:!0,expressionTreeCollapsible:!0})})]},C.key)}if(N==="number"){const M=z==null?"":String(z);return i.jsxs("label",{style:Wn,children:[i.jsx("span",{style:jn,children:A}),i.jsx("input",{type:"number","data-testid":D,value:M,onChange:B=>{const T=B.target.value;_(E,C,T===""?void 0:Number(T))},style:or})]},C.key)}if(N==="color"){const M=typeof z=="string"?z:"",B=/^#[0-9a-fA-F]{6}$/.test(M)?M:"#000000";return i.jsxs("label",{style:Wn,children:[i.jsx("span",{style:jn,children:A}),i.jsx("input",{type:"color","data-testid":`${D}-swatch`,value:B,onChange:T=>_(E,C,T.target.value),style:az}),i.jsx("input",{type:"text","data-testid":D,value:M,placeholder:"#7C3AED",onChange:T=>_(E,C,T.target.value===""?void 0:T.target.value),style:or})]},C.key)}if(N==="number-list"){const M=Array.isArray(z)?z.join(", "):"";return i.jsxs("label",{style:Wn,children:[i.jsx("span",{style:jn,children:A}),i.jsx("input",{type:"text","data-testid":D,value:M,placeholder:n("layout_editor.array_editor.number_list_placeholder"),onChange:B=>{const T=B.target.value.split(",").map(H=>H.trim()).filter(H=>H!=="").map(H=>Number(H)).filter(H=>!Number.isNaN(H));_(E,C,T)},style:or})]},C.key)}const O=z==null?"":String(z);return i.jsxs("label",{style:Wn,children:[i.jsx("span",{style:jn,children:A}),i.jsx("input",{type:"text","data-testid":D,value:O,placeholder:N==="icon"?n("layout_editor.array_editor.icon_placeholder"):A,onChange:M=>_(E,C,M.target.value===""?void 0:M.target.value),style:or})]},C.key)})]},E)),i.jsx("button",{type:"button","data-testid":"g7le-array-add",onClick:x,style:dz,children:n("layout_editor.array_editor.add",{item:d})})]}):i.jsx("div",{className:"g7le-node-editor g7le-node-editor--array","data-testid":"g7le-array-editor-misconfigured",style:$h,children:n("layout_editor.array_editor.no_array_prop")})}const nz={display:"flex",flexDirection:"column",gap:8,width:"100%",marginBottom:12},oz={fontSize:12,fontWeight:700,color:"#0f172a",marginBottom:2},rz={display:"flex",flexDirection:"column",gap:4,padding:8,border:"1px solid #e2e8f0",borderRadius:8,background:"#f8fafc"},iz={display:"flex",gap:4,alignItems:"center"},sz={fontSize:11,fontWeight:700,color:"#64748b"},Wn={display:"flex",flexWrap:"wrap",gap:6,alignItems:"center"},jn={fontSize:11,color:"#475569",minWidth:64,flexShrink:0},or={flex:1,minWidth:0,padding:"4px 6px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6},az={width:32,height:28,padding:0,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",cursor:"pointer",flexShrink:0},Ch={padding:"2px 6px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},lz={padding:"2px 6px",fontSize:12,border:"1px solid #fecaca",borderRadius:6,background:"#fff",color:"#dc2626",cursor:"pointer"},dz={padding:"4px 8px",fontSize:12,border:"1px dashed #94a3b8",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer",alignSelf:"flex-start"},$h={fontSize:11,color:"#94a3b8",fontStyle:"italic"},cz={fontSize:11,color:"#b45309",fontStyle:"italic",padding:"4px 2px"};function uz(e,t){return e?e.startsWith("$t:")?t(e.slice(3)):e:null}function pz(e){const{params:t,t:n}=e,o=Array.isArray(t?.groups)?t.groups:[];return o.length===0?i.jsx("div",{className:"g7le-node-editor g7le-node-editor--array-group","data-testid":"g7le-array-group-misconfigured",style:yz,children:n("layout_editor.array_editor.no_array_prop")}):i.jsx("div",{className:"g7le-node-editor g7le-node-editor--array-group","data-testid":"g7le-array-group-editor",style:fz,children:o.map((r,s)=>{const a=uz(r.title,n),{title:l,...d}=r;return i.jsxs("div",{"data-testid":`g7le-array-group-${r.arrayProp??s}`,style:gz,children:[a&&i.jsx("div",{style:hz,children:a}),i.jsx(Sh,{...e,params:d})]},r.arrayProp??s)})})}const fz={display:"flex",flexDirection:"column",gap:16,width:"100%",marginBottom:12},gz={display:"flex",flexDirection:"column",gap:4},hz={fontSize:12,fontWeight:700,color:"#1e293b",borderBottom:"1px solid #e2e8f0",paddingBottom:4},yz={fontSize:11,color:"#94a3b8",fontStyle:"italic"},bz=/\{\{.*?\}\}/;function mz(e){return typeof e=="string"&&bz.test(e)}function vz(e,t,n){return e?e.startsWith("$t:")?t(e.slice(3)):e:n}function _z(e){const{node:t,params:n,t:o,onPatchNode:r}=e,s=typeof n?.arrayProp=="string"?n.arrayProp:null,a=typeof n?.idField=="string"?n.idField:"id",l=typeof n?.cellChildrenProp=="string"?n.cellChildrenProp:"cellChildren",d=typeof n?.cellChildComponent=="string"?n.cellChildComponent:"Div",c=vz(typeof n?.itemLabel=="string"?n.itemLabel:void 0,o,o("layout_editor.array_editor.item")),[p,f]=u.useState(null),y=s?(t.props??{})[s]:void 0,g=Array.isArray(y)?y:[],m=u.useCallback(S=>{if(!s)return;const E={...t.props??{}};E[s]=S,r({...t,props:E})},[s,t,r]),h=u.useCallback(()=>{const S=n?.newItem;return S&&typeof S=="object"?JSON.parse(JSON.stringify(S)):{[a]:"",[l]:[]}},[n,a,l]),v=u.useCallback(()=>{m([...g,h()])},[m,g,h]),b=u.useCallback(S=>{m(g.filter((E,C)=>C!==S)),f(null)},[m,g]),x=u.useCallback((S,E)=>{const C=S+E;if(C<0||C>=g.length)return;const N=[...g];[N[S],N[C]]=[N[C],N[S]],m(N)},[m,g]),k=u.useCallback((S,E)=>{const C=g.map((N,$)=>$===S?{...N,[a]:E}:N);m(C)},[g,m,a]),w=u.useCallback(S=>({type:"composite",name:"CardGridCell",children:Array.isArray(S[l])?S[l]:[]}),[l]),_=u.useCallback(S=>E=>{const C=Array.isArray(E.children)?E.children:[],N=g.map(($,A)=>A===S?{...$,[l]:C}:$);m(N)},[g,m,l]);return s?mz(y)||y!==void 0&&!Array.isArray(y)?i.jsx("div",{className:"g7le-node-editor g7le-node-editor--cell-tree","data-testid":"g7le-cell-tree-bound",style:Dz,children:o("layout_editor.array_editor.bound_degraded")}):i.jsxs("div",{className:"g7le-node-editor g7le-node-editor--cell-tree","data-testid":"g7le-cell-tree-editor",style:xz,children:[i.jsx("div",{style:wz,children:o("layout_editor.cell_tree_editor.columns_title")}),g.length===0&&i.jsx("div",{"data-testid":"g7le-cell-tree-empty",style:Ih,children:o("layout_editor.array_editor.empty")}),g.map((S,E)=>{const C=typeof S[a]=="string"?S[a]:"",N=p===E;return i.jsxs("div",{"data-testid":`g7le-cell-tree-row-${E}`,style:kz,children:[i.jsxs("div",{style:Sz,children:[i.jsxs("span",{style:Cz,children:["#",E+1]}),i.jsx("input",{type:"text","data-testid":`g7le-cell-tree-id-${E}`,value:C,placeholder:o("layout_editor.cell_tree_editor.id_placeholder"),onChange:$=>k(E,$.target.value),style:$z}),i.jsx("button",{type:"button","data-testid":`g7le-cell-tree-up-${E}`,title:o("layout_editor.array_editor.move_up"),disabled:E===0,onClick:()=>x(E,-1),style:Eh,children:"↑"}),i.jsx("button",{type:"button","data-testid":`g7le-cell-tree-down-${E}`,title:o("layout_editor.array_editor.move_down"),disabled:E===g.length-1,onClick:()=>x(E,1),style:Eh,children:"↓"}),i.jsx("button",{type:"button","data-testid":`g7le-cell-tree-remove-${E}`,title:o("layout_editor.array_editor.remove"),onClick:()=>b(E),style:Ez,children:"✕"})]}),i.jsx("button",{type:"button","data-testid":`g7le-cell-tree-toggle-${E}`,onClick:()=>f(N?null:E),style:Iz,children:o(N?"layout_editor.cell_tree_editor.collapse_cells":"layout_editor.cell_tree_editor.expand_cells")}),N&&i.jsx("div",{"data-testid":`g7le-cell-tree-cells-${E}`,style:Az,children:i.jsx(Vg,{...e,node:w(S),params:{childComponent:d},onPatchNode:_(E)})})]},E)}),i.jsx("button",{type:"button","data-testid":"g7le-cell-tree-add",onClick:v,style:Tz,children:o("layout_editor.cell_tree_editor.add_column",{item:c})})]}):i.jsx("div",{className:"g7le-node-editor g7le-node-editor--cell-tree","data-testid":"g7le-cell-tree-misconfigured",style:Ih,children:o("layout_editor.array_editor.no_array_prop")})}const xz={display:"flex",flexDirection:"column",gap:8,width:"100%",marginBottom:12},wz={fontSize:12,fontWeight:700,color:"#0f172a",marginBottom:2},kz={display:"flex",flexDirection:"column",gap:6,padding:8,border:"1px solid #e2e8f0",borderRadius:8,background:"#f8fafc"},Sz={display:"flex",gap:4,alignItems:"center"},Cz={fontSize:11,fontWeight:700,color:"#64748b",flexShrink:0},$z={flex:1,minWidth:0,padding:"4px 6px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6},Eh={padding:"2px 6px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},Ez={padding:"2px 6px",fontSize:12,border:"1px solid #fecaca",borderRadius:6,background:"#fff",color:"#dc2626",cursor:"pointer"},Iz={padding:"3px 8px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#2563eb",cursor:"pointer",alignSelf:"flex-start"},Az={paddingLeft:8,borderLeft:"2px solid #e2e8f0"},Tz={padding:"4px 8px",fontSize:12,border:"1px dashed #94a3b8",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer",alignSelf:"flex-start"},Ih={fontSize:11,color:"#94a3b8",fontStyle:"italic"},Dz={fontSize:11,color:"#b45309",fontStyle:"italic",padding:"4px 2px"};function Nz(e,t,n){const o=Be(e,t);if(!n||n.length===0)return{measured:[],grid:o};const r=[];for(const s of o.rows)for(const a of s.cells){const d=tl(e,a,t).map(p=>`children.${p}`).join("."),c=n.find(p=>p.path===d||p.path.endsWith("."+d));c&&r.push({ref:a,box:{top:c.top,left:c.left,width:c.width,height:c.height}})}return{measured:r,grid:o}}const pl=26,fl=50,gl=6,Ah=44,Th=18,Dh="g7le-table-inplace-empty-cell-style";function zz(){if(typeof document>"u"||document.getElementById(Dh))return;const e=document.createElement("style");e.id=Dh,e.textContent=".g7le-preview-frame td[data-editor-name],.g7le-preview-frame th[data-editor-name]{min-width:56px;height:38px;}.g7le-preview-frame table[data-editor-name]{min-width:120px;}",document.head.appendChild(e)}function Mz({node:e,params:t,nodeBox:n,cellBoxes:o,selectedCellPath:r,colorScheme:s,t:a,onPatchNode:l,onRequestInlineEdit:d}){const c=u.useMemo(()=>t&&typeof t=="object"?t:null,[t]),{measured:p,grid:f}=u.useMemo(()=>Nz(e,c,o),[e,c,o]),[y,g]=u.useState(null),[m,h]=u.useState(null),v=u.useMemo(()=>{if(!r)return null;for(const j of f.rows)for(const oe of j.cells){const ue=tl(e,oe,c).map(J=>`children.${J}`).join(".");if(r===ue||r.startsWith(ue+"."))return{r:oe.gridRow,c:oe.gridCol}}return null},[r,f,e,c]),b=u.useMemo(()=>y&&Ue(f,y.r,y.c)?y:v,[y,v,f]),[x,k]=u.useState(!1),[w,_]=u.useState(s==="dark"?"dark":"light"),[S,E]=u.useState(null),[C,N]=u.useState(null),$=u.useRef(null),A=u.useCallback(()=>{$.current!==null&&(clearTimeout($.current),$.current=null)},[]),z=u.useCallback(()=>{A(),$.current=setTimeout(()=>{E(null),N(null),$.current=null},240)},[A]);u.useEffect(()=>()=>A(),[A]),u.useEffect(()=>{zz()},[]);const D=u.useCallback(j=>l(j),[l]),O=u.useMemo(()=>b?Ue(f,b.r,b.c):null,[f,b]),M=u.useMemo(()=>O&&typeof O.cell.props?.className=="string"?O.cell.props.className:"",[O]),B=u.useCallback(j=>D(nl(e,c,j)),[e,c,D]),T=u.useCallback(j=>D(Zg(e,c,j)),[e,c,D]),H=u.useCallback((j,oe)=>D(Qg(e,c,j,oe)),[e,c,D]),L=u.useCallback(j=>D(eh(e,c,j)),[e,c,D]),I=u.useCallback(j=>D(th(e,c,j)),[e,c,D]),P=u.useCallback((j,oe)=>D(Rg(e,c,j,oe)),[e,c,D]),F=u.useCallback(j=>{if(!O)return;const oe=O.gridRow,ue=O.gridCol,J=j==="down"?oe+O.rowSpan:oe+O.rowSpan-1,le=j==="right"?ue+O.colSpan:ue+O.colSpan-1;if(J>=f.rows.length||le>=f.colCount)return;const Ie=ol(e,c,oe,ue,J,le);Ie.ok&&D(Ie.table)},[O,f,e,c,D]),G=!!(O&&O.gridCol+O.colSpan!b||!m||!Ue(f,m.r,m.c)?null:{r0:Math.min(b.r,m.r),c0:Math.min(b.c,m.c),r1:Math.max(b.r,m.r),c1:Math.max(b.c,m.c)},[b,m,f]),Z=u.useMemo(()=>{if(!Y)return[];const j=new Set,oe=[];for(const ue of f.rows)for(const J of ue.cells)if(J.gridRow>=Y.r0&&J.gridRow<=Y.r1&&J.gridCol>=Y.c0&&J.gridCol<=Y.c1){const le=`${J.gridRow}:${J.gridCol}`;j.has(le)||(j.add(le),oe.push({r:J.gridRow,c:J.gridCol}))}return oe},[Y,f]),se=Z.length>1,re=u.useCallback(()=>{if(!Y)return;const j=ol(e,c,Y.r0,Y.c0,Y.r1,Y.c1);j.ok&&(D(j.table),h(null))},[Y,e,c,D]),U=u.useCallback(()=>{b&&D(nh(e,c,b.r,b.c))},[e,c,b,D]),ee=u.useMemo(()=>{const j=c?.cellBorder;return j&&typeof j=="object"?j:null},[c]),fe=u.useMemo(()=>{const j=c?.cellBackground;return j&&typeof j=="object"?j:null},[c]),K=u.useMemo(()=>{const j=c?.cellPadding;return j&&typeof j=="object"?j:null},[c]),ne=u.useMemo(()=>{if(x){const j=new Set,oe=[];for(const ue of f.rows)for(const J of ue.cells){const le=`${J.gridRow}:${J.gridCol}`;j.has(le)||(j.add(le),oe.push({r:J.gridRow,c:J.gridCol}))}return oe}return se?Z:b?[{r:b.r,c:b.c}]:[]},[x,se,Z,b,f]),me=u.useCallback(j=>{if(ne.length===0)return;let oe=e;for(const ue of ne){const J=(ee?.colors??[]).map(Ie=>Ie.token),le=j.split(/\s+/).filter(Ie=>Ie&&!J.includes(Ie)).join(" ");oe=Ci(oe,c,ue.r,ue.c,le)}D(oe)},[e,c,ne,ee,D]),Ce=u.useMemo(()=>{const j=(ee?.sides??[]).find(J=>J.value==="all")??(ee?.sides??[])[0],oe=(ee?.widths??[]).find(J=>J.value!=="none");if(!j||!oe)return[];const ue=oe.suffix??"";return[ue?`${j.prefix}${ue}`:j.prefix]},[ee]),ze=u.useCallback((j,oe,ue)=>{if(Ce.length===0)return j;const J=(ee?.sides??[]).map(Oe=>Oe.prefix),le=Ue(Be(j,c),oe,ue),dt=(typeof le?.cell?.props?.className=="string"?le.cell.props.className:"").split(/\s+/).filter(Boolean);if(dt.some(Oe=>J.some(He=>Oe===He||Oe.startsWith(He+"-")&&/^-?\d+$/.test(Oe.slice(He.length+1)))))return j;const ot=[...dt,...Ce].join(" ").trim();return Ci(j,c,oe,ue,ot)},[Ce,ee,c]),Se=u.useCallback((j,oe)=>{if(j.length===0)return;const ue=new Set(j.map(le=>`${le.r}:${le.c}`));let J=e;for(const le of j){if(J=ze(J,le.r,le.c),J=Jt(J,c,le.r,le.c,"borderColor",oe),le.r>0&&!ue.has(`${le.r-1}:${le.c}`)){const Ie=Ue(Be(J,c),le.r-1,le.c);Ie&&(J=Jt(J,c,Ie.gridRow,Ie.gridCol,"borderBottomColor",oe))}if(le.c>0&&!ue.has(`${le.r}:${le.c-1}`)){const Ie=Ue(Be(J,c),le.r,le.c-1);Ie&&(J=Jt(J,c,Ie.gridRow,Ie.gridCol,"borderRightColor",oe))}}D(J)},[e,c,D,ze]),qe=u.useMemo(()=>(ee?.colors??[]).map(j=>j.token).filter(Boolean),[ee]),De=u.useCallback(j=>{if(ne.length===0||qe.length===0)return;let oe=e;for(const ue of ne)oe=ze(oe,ue.r,ue.c),oe=go(oe,c,ue.r,ue.c,j,qe,w);D(oe)},[ne,e,c,D,ze,qe,w]),Pe=u.useCallback(j=>Se(ne,j),[Se,ne]),Qe=u.useMemo(()=>{if(w==="dark")return"";const j=O?.cell.props?.style;return typeof j?.borderColor=="string"?j.borderColor:""},[O,w]),Me=u.useCallback((j,oe)=>{if(ne.length===0)return;let ue=e;for(const J of ne)ue=Jt(ue,c,J.r,J.c,j,oe);D(ue)},[ne,e,c,D]),Re=u.useMemo(()=>(fe?.colors??[]).map(j=>j.token).filter(j=>!!j),[fe]),ve=u.useCallback(j=>{if(ne.length===0||Re.length===0)return;let oe=e;for(const ue of ne)oe=go(oe,c,ue.r,ue.c,j,Re,w);D(oe)},[ne,e,c,D,Re,w]),te=u.useCallback(j=>Me("backgroundColor",j),[Me]),he=u.useCallback(()=>{if(ne.length===0)return;let j=e;for(const oe of ne)j=go(j,c,oe.r,oe.c,void 0,Re,w),w==="light"&&(j=Jt(j,c,oe.r,oe.c,"backgroundColor",void 0));D(j)},[ne,e,c,D,Re,w]),Q=u.useCallback(j=>Me("padding",j),[Me]),ie=u.useMemo(()=>{if(w==="dark")return"";const j=O?.cell.props?.style;return typeof j?.backgroundColor=="string"?j.backgroundColor:""},[O,w]),_e=u.useMemo(()=>typeof O?.cell.props?.className=="string"?O.cell.props.className:"",[O]),ge=u.useMemo(()=>{const j=O?.cell.props?.style;return typeof j?.padding=="string"?j.padding:""},[O]),Ae=u.useCallback(j=>{const oe=j.currentTarget,ue=oe.style.pointerEvents;oe.style.pointerEvents="none";const J=document.elementFromPoint(j.clientX,j.clientY);oe.style.pointerEvents=ue;const le=J?.closest?.("[data-dnd-handle-path]");le&&le.dispatchEvent(new PointerEvent("pointerdown",{bubbles:!0,cancelable:!0,clientX:j.clientX,clientY:j.clientY,pointerId:j.pointerId,pointerType:j.pointerType,button:j.button,isPrimary:!0}))},[]),Ne=u.useMemo(()=>{const j=new Map;for(const oe of p)j.has(oe.ref.gridCol)||j.set(oe.ref.gridCol,oe.box);return j},[p]),Le=u.useMemo(()=>{const j=new Map;for(const oe of p)j.has(oe.ref.gridRow)||j.set(oe.ref.gridRow,oe.box);return j},[p]);if(p.length===0)return null;const q=!!(O&&(O.colSpan>1||O.rowSpan>1)),ae=C!==null?C:b?.c??null,pe=S!==null?S:b?.r??null,$e=n.top-pl-gl-Ah,be=n.left-fl-gl-Ah;return i.jsxs("div",{className:"g7le-table-inplace","data-testid":"g7le-table-inplace",style:{position:"absolute",inset:0,pointerEvents:"none",zIndex:u7},children:[p.map(j=>{const oe=b&&b.r===j.ref.gridRow&&b.c===j.ref.gridCol,ue=Y&&j.ref.gridRow>=Y.r0&&j.ref.gridRow<=Y.r1&&j.ref.gridCol>=Y.c0&&j.ref.gridCol<=Y.c1;return i.jsx("div",{"data-testid":`g7le-inplace-cell-${j.ref.gridRow}-${j.ref.gridCol}`,role:"button",tabIndex:-1,"aria-label":a("layout_editor.table_inplace.select_cell"),onMouseEnter:()=>{A(),E(j.ref.gridRow),N(j.ref.gridCol)},onMouseLeave:()=>{z()},onPointerDown:J=>Ae(J),onClick:J=>{if(J.stopPropagation(),J.shiftKey&&(y||b))h({r:j.ref.gridRow,c:j.ref.gridCol});else{g({r:j.ref.gridRow,c:j.ref.gridCol}),h(null);const le=tl(e,j.ref,c).map(Ie=>`children.${Ie}`).join(".");d?.(le)}},style:{position:"absolute",top:j.box.top,left:j.box.left,width:j.box.width,height:j.box.height,minWidth:Th,minHeight:Th,boxSizing:"border-box",pointerEvents:"auto",cursor:"pointer",outline:oe?"2px solid #2563eb":"none",outlineOffset:-2,background:ue&&!oe?"rgba(37,99,235,0.14)":"transparent"}},`cell-${j.ref.gridRow}-${j.ref.gridCol}`)}),i.jsx("div",{"data-testid":"g7le-inplace-col-rail",onMouseEnter:A,onMouseLeave:z,style:{position:"absolute",top:$e,left:n.left,width:n.width,height:pl,pointerEvents:"none"},children:ae!==null&&Ne.has(ae)&&(()=>{const j=ae,oe=Ne.get(j),ue=wi(f,j,-1),J=wi(f,j,1),le=oe.left-n.left;return i.jsxs("div",{"data-testid":`g7le-inplace-colgutter-${j}`,onMouseEnter:()=>{A(),N(j)},onMouseLeave:z,style:{position:"absolute",top:0,left:le,width:oe.width,minWidth:96,height:pl,display:"flex",gap:3,justifyContent:"center",alignItems:"center",pointerEvents:"auto"},children:[i.jsx("button",{type:"button",title:a("layout_editor.table_inplace.move_col_left"),disabled:!ue.ok,onClick:()=>P(j,-1),"data-testid":`g7le-inplace-col-left-${j}`,style:ue.ok?_n:Ei,children:"←"}),i.jsx("button",{type:"button",title:a("layout_editor.table_inplace.add_col"),onClick:()=>L(j),"data-testid":`g7le-inplace-col-add-${j}`,style:_n,children:"+"}),i.jsx("button",{type:"button",title:a("layout_editor.table_inplace.remove_col"),disabled:f.colCount<=1,onClick:()=>I(j),"data-testid":`g7le-inplace-col-remove-${j}`,style:Nh,children:"✕"}),i.jsx("button",{type:"button",title:a("layout_editor.table_inplace.move_col_right"),disabled:!J.ok,onClick:()=>P(j,1),"data-testid":`g7le-inplace-col-right-${j}`,style:J.ok?_n:Ei,children:"→"})]},`colgut-${j}`)})()}),i.jsx("div",{"data-testid":"g7le-inplace-row-rail",style:{position:"absolute",top:n.top,left:be,width:fl,height:n.height,pointerEvents:"none"},children:pe!==null&&Le.has(pe)&&(()=>{const j=pe,oe=Le.get(j),ue=xi(f,j,-1),J=xi(f,j,1),le=oe.top-n.top;return i.jsxs("div",{"data-testid":`g7le-inplace-rowgutter-${j}`,onMouseEnter:()=>{A(),E(j)},onMouseLeave:z,style:{position:"absolute",top:le,left:0,width:fl,height:oe.height,minHeight:48,display:"grid",gridTemplateColumns:"repeat(2, 22px)",gridAutoRows:"22px",gap:3,justifyContent:"center",alignContent:"center",pointerEvents:"auto"},children:[i.jsx("button",{type:"button",title:a("layout_editor.table_inplace.move_row_up"),disabled:!ue.ok,onClick:()=>H(j,-1),"data-testid":`g7le-inplace-row-up-${j}`,style:ue.ok?_n:Ei,children:"↑"}),i.jsx("button",{type:"button",title:a("layout_editor.table_inplace.add_row"),onClick:()=>B(j),"data-testid":`g7le-inplace-row-add-${j}`,style:_n,children:"+"}),i.jsx("button",{type:"button",title:a("layout_editor.table_inplace.remove_row"),disabled:f.rows.length<=1,onClick:()=>T(j),"data-testid":`g7le-inplace-row-remove-${j}`,style:Nh,children:"✕"}),i.jsx("button",{type:"button",title:a("layout_editor.table_inplace.move_row_down"),disabled:!J.ok,onClick:()=>H(j,1),"data-testid":`g7le-inplace-row-down-${j}`,style:J.ok?_n:Ei,children:"↓"})]},`rowgut-${j}`)})()}),i.jsxs("div",{"data-testid":"g7le-inplace-corner-add",style:{position:"absolute",top:n.top+n.height+gl,left:n.left,display:"flex",flexDirection:"column",gap:6,pointerEvents:"auto",minWidth:200,maxWidth:Math.max(240,n.width),background:"#fff",border:"1px solid #e2e8f0",borderRadius:8,padding:8,boxShadow:"0 2px 8px rgba(0,0,0,0.12)"},children:[i.jsxs("div",{style:{display:"flex",gap:6,flexWrap:"wrap"},children:[i.jsxs("button",{type:"button","data-testid":"g7le-inplace-add-row-bottom",onClick:()=>B(f.rows.length-1),style:zh,children:["+ ",a("layout_editor.table_inplace.add_row")]}),i.jsxs("button",{type:"button","data-testid":"g7le-inplace-add-col-right",onClick:()=>L(f.colCount-1),style:zh,children:["+ ",a("layout_editor.table_inplace.add_col")]})]}),!b&&i.jsx("div",{"data-testid":"g7le-inplace-select-hint",style:{fontSize:11,color:"#94a3b8",borderTop:"1px solid #f1f5f9",paddingTop:6},children:a("layout_editor.table_inplace.select_cell_hint")}),b&&i.jsxs("div",{style:{display:"flex",flexDirection:"column",gap:6,borderTop:"1px solid #f1f5f9",paddingTop:6},children:[se&&i.jsx("div",{"data-testid":"g7le-inplace-range-count",style:{fontSize:11,color:"#2563eb"},children:a("layout_editor.table_inplace.range_selected",{count:Z.length})}),i.jsxs("div",{style:{display:"flex",gap:6,flexWrap:"wrap",alignItems:"center"},children:[se?i.jsx("button",{type:"button","data-testid":"g7le-inplace-merge-range",onClick:re,style:rr,children:a("layout_editor.table_inplace.merge_range")}):i.jsxs(i.Fragment,{children:[i.jsx("button",{type:"button","data-testid":"g7le-inplace-merge-right",disabled:!G,onClick:()=>F("right"),style:G?rr:hl,children:a("layout_editor.table_inplace.merge_right")}),i.jsx("button",{type:"button","data-testid":"g7le-inplace-merge-down",disabled:!V,onClick:()=>F("down"),style:V?rr:hl,children:a("layout_editor.table_inplace.merge_down")})]}),i.jsx("button",{type:"button","data-testid":"g7le-inplace-unmerge",disabled:!q,onClick:U,style:q?rr:hl,children:a("layout_editor.table_inplace.unmerge")})]}),i.jsxs("div",{"data-testid":"g7le-inplace-cell-color",style:{borderTop:"1px solid #f1f5f9",paddingTop:6,display:"flex",flexDirection:"column",gap:6},children:[i.jsxs("label",{style:{display:"flex",alignItems:"center",gap:6,fontSize:11,color:"#475569",cursor:"pointer"},children:[i.jsx("input",{type:"checkbox","data-testid":"g7le-inplace-border-all-toggle",checked:x,onChange:j=>k(j.target.checked)}),a("layout_editor.table_inplace.border_apply_all")]}),i.jsx(gh,{colorScheme:w,onChange:_,t:a}),ee&&i.jsx("div",{"data-testid":"g7le-inplace-cell-border",children:i.jsx(ah,{className:x?"":M,catalog:ee,t:a,onChange:me,colorScheme:w,onPresetToken:De,colorStyle:x?"":Qe,onColorStyle:Pe})}),i.jsx("div",{"data-testid":"g7le-inplace-cell-fill",children:i.jsx(uh,{colorStyle:x?"":ie,className:x?"":_e,catalog:fe,t:a,colorScheme:w,onPresetToken:ve,onCustomColor:te,onClear:he})})]}),i.jsx("div",{"data-testid":"g7le-inplace-cell-padding",style:{borderTop:"1px solid #f1f5f9",paddingTop:6},children:i.jsx(ph,{paddingStyle:x?"":ge,catalog:K,t:a,onChange:Q})})]})]})]})}const _n={width:22,height:22,lineHeight:"20px",padding:0,fontSize:13,border:"1px solid #cbd5e1",borderRadius:5,background:"#fff",color:"#475569",cursor:"pointer",boxShadow:"0 1px 3px rgba(0,0,0,0.18)"},Nh={..._n,border:"1px solid #fecaca",color:"#dc2626"},Ei={..._n,cursor:"not-allowed",opacity:.4,color:"#94a3b8",borderColor:"#e2e8f0"},rr={padding:"4px 10px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer",boxShadow:"0 1px 2px rgba(0,0,0,0.12)"},hl={...rr,cursor:"not-allowed",opacity:.4},zh={padding:"3px 8px",fontSize:11,border:"1px dashed #94a3b8",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"};let Mh=!1;function Lz(){Mh||(Pn("children",Vg),Pn("table",E8),Pn("array",Sh),Pn("array-group",pz),Pn("array-cell-tree",_z),Oa("table",Mz),Mh=!0)}function Pz(){if(typeof window>"u")return;const e=window.G7Core;if(!e)return;const t=e.layoutEditor,n=Array.isArray(t?.__queue)?t.__queue:[],o=Array.isArray(t?.__readyCallbacks)?t.__readyCallbacks:[];e.layoutEditor={registerWidget:Xe,registerNodeEditor:Pn,registerCanvasOverlay:Oa,onReady:r=>{typeof r=="function"&&r()}};for(const r of n){const[s,...a]=r;try{s==="widget"?Xe(a[0],a[1]):s==="nodeEditor"?Pn(a[0],a[1]):s==="canvasOverlay"&&Oa(a[0],a[1])}catch{}}for(const r of o)try{r()}catch{}}const Oz=1280;KN(),Lz(),Pz();async function Bz(e){if(typeof fetch!="function")return[];try{const t=await fetch(en(`/api/admin/templates/${encodeURIComponent(e)}/editor/permission-candidates`,"json"),{credentials:"same-origin",headers:je()});if(!t.ok)return[];const o=(await t.json().catch(()=>null))?.data?.permissions;if(!Array.isArray(o))return[];const r=[];for(const s of o)if(s&&typeof s=="object"){const a=s.key,l=s.name;typeof a=="string"&&a.length>0&&r.push({value:a,label:typeof l=="string"&&l?l:a})}return r}catch{return[]}}function Wz({templateIdentifier:e}){uD({templateIdentifier:e}),pD(e);const{state:t,dispatch:n}=Je(),{t:o}=tt(),r=yo(),s=kp(),a=mD(),l=t.selectedRoute?.layoutName??null,d=wD(e,l),c=t.editMode==="extension",p=u.useMemo(()=>c?_D(a):s,[c,a,s]),[f,y]=u.useState(null),[g,m]=u.useState(null),[h,v]=u.useState([]),[b,x]=u.useState("user"),[k,w]=u.useState(null),[_,S]=u.useState(null),[E,C]=u.useState([]);u.useEffect(()=>{let te=!1;return(async()=>{try{const he=await fetch(en(`/api/templates/${encodeURIComponent(e)}/components`,"json"),{credentials:"same-origin",headers:{Accept:"application/json"}});if(!he.ok)return;const Q=await he.json().catch(()=>null);!te&&Q&&typeof Q=="object"&&y(Q)}catch{}})(),()=>{te=!0}},[e]),u.useEffect(()=>{let te=!1;return(async()=>{const{spec:he,sampleGlobalSources:Q}=await Qy({templateIdentifier:e,coreSeed:kD()});te||(m(he),v(Q),Hz(e,he,Q))})(),()=>{te=!0}},[e]),u.useEffect(()=>{let te=!1;return(async()=>{if(typeof fetch=="function")try{const he=await fetch(`/api/admin/templates/${encodeURIComponent(e)}`,{credentials:"same-origin",headers:je()});if(!he.ok)return;const ie=(await he.json().catch(()=>null))?.data,_e=ie?.type;te||((_e==="admin"||_e==="user")&&x(_e),typeof ie?.name=="string"&&ie.name&&w(ie.name),typeof ie?.version=="string"&&ie.version&&S(ie.version))}catch{}})(),()=>{te=!0}},[e]),u.useEffect(()=>{let te=!1;return(async()=>{if(typeof fetch=="function")try{const he=await fetch("/api/admin/templates/?per_page=100&page=1",{credentials:"same-origin",headers:je()});if(!he.ok)return;const ie=(await he.json().catch(()=>null))?.data?.data;if(!te&&Array.isArray(ie)){const _e=[];for(const ge of ie){if(!ge||typeof ge!="object")continue;const Ae=ge.identifier;if(typeof Ae!="string"||!Ae||ge.is_pending===!0)continue;const Le=ge.type==="admin"?"admin":"user",q=ge.name,ae=ge.version;_e.push({identifier:Ae,name:typeof q=="string"?q:Ae,version:typeof ae=="string"?ae:"",type:Le})}C(_e)}}catch{}})(),()=>{te=!0}},[e]);const N=g?.nesting??null,$=t.selectedRoute?.path??null,A=t.editMode,z=t.selectedRoute?.layoutName??(c?a.document?.hostLayoutName??null:null),D=t.routeTree,O=u.useMemo(()=>{if(A!=="extension"||!z)return null;const te=bl(D,z);return te||`__base__/${z}`},[A,z,D]),M=c&&t.returnRoute===null&&!!z;u.useEffect(()=>{if(!M||!z)return;const te=bl(D,z);te&&n({type:"SET_RETURN_ROUTE",route:{path:te,layoutName:z}})},[M,z,D,n]),u.useEffect(()=>{const te=V4(A,$,O),he=U4(g?.states?.groups,te);n({type:"SET_AVAILABLE_STATES",states:he,activeStateId:q4(he)})},[g,A,$,O,n]);const[B,T]=u.useState([]);u.useEffect(()=>{let te=!1;return(async()=>{const he=await Bz(e);te||T(he)})(),()=>{te=!0}},[e]),fD(),gD();const H=u.useCallback(te=>{const he=t.routeTree,Q=te.split("?")[0]?.split("#")[0]??te;return jz(he,Q)?"route_in_tree":"route_not_in_tree"},[t.routeTree]),L=u.useCallback(te=>{const he=te.split("?")[0]?.split("#")[0]??te,Q=yl(t.routeTree,he);Q&&(p.isDirty&&!(!(typeof window<"u")||window.confirm(o("layout_editor.chrome.dirty_guard.message")))||n({type:"SELECT_ROUTE",route:{path:Q.path,layoutName:Q.layoutName}}))},[t.routeTree,p.isDirty,n]),I=u.useRef([]),P=u.useCallback(te=>{I.current=te},[]),[F,G]=u.useState(null),V=u.useCallback(()=>G(null),[]),Y=u.useRef(null),Z=u.useRef(null);u.useEffect(()=>{const te=Z.current,he=Y.current;if(!te||!he||typeof ResizeObserver>"u")return;const Q=()=>{he.style.setProperty("--g7le-header-h",`${Math.round(te.getBoundingClientRect().height)}px`)};Q();const ie=new ResizeObserver(Q);return ie.observe(te),()=>ie.disconnect()},[]);const se=u.useCallback(()=>{p.reload()},[p]),re=u.useCallback(()=>{p.isDirty&&!(!(typeof window<"u")||window.confirm(o("layout_editor.chrome.dirty_guard.message")))||window.G7Core?.dispatch?.({handler:"openWindow",params:{path:`/admin/templates/${b}`,target:"_self"}})},[p.isDirty,o,b]),U=u.useCallback(te=>{if(!te||te===e||p.isDirty&&!(typeof window<"u"?window.confirm(o("layout_editor.chrome.dirty_guard.message")):!0))return;const he=Zs(te);window.G7Core?.dispatch?.({handler:"openWindow",params:{path:he,target:"_self"}})},[e,p.isDirty,o]),ee=u.useCallback(()=>{const te=DE(e,t.selectedRoute?.path??null);window.G7Core?.dispatch?.({handler:"openWindow",params:{path:te}})},[e,t.selectedRoute]),fe=u.useCallback(()=>{const te=t.selectedRoute?.layoutName??"";if(!te)return;const he=r.open({ariaLabel:o("layout_editor.attachment_manager.title"),width:720,maxHeightRatio:.82,content:u.createElement(Ml,{templateIdentifier:e,layoutName:te,t:o,onClose:()=>r.close(he)})})},[r,e,t.selectedRoute,o]),K=u.useCallback(()=>{const te=t.selectedRoute?.layoutName??"";if(!te)return;const he=Wy(s.document?.raw??{}),Q=r.open({ariaLabel:o("layout_editor.translation_manager.title"),width:720,maxHeightRatio:.82,content:u.createElement(am,{templateIdentifier:e,layoutName:te,t:o,referencedKeys:he,onClose:()=>r.close(Q)})})},[r,e,t.selectedRoute,o,s.document]),ne=u.useCallback((te,he)=>{const Q=window.G7Core;if(Q&&typeof Q.t=="function"){const ie=Q.t(te,he);if(ie&&ie!==te&&!ie.startsWith("$t:"))return ie}return o(te,he)},[o]),me=u.useCallback(()=>{if(!t.selectedRoute||t.editMode==="extension"||t.editMode==="iteration_item")return;const te=r.open({ariaLabel:o("layout_editor.page_settings.title",{name:t.selectedRoute.path??""}),width:1080,maxHeightRatio:.88,closeOnEscape:!1,content:u.createElement(d$,{templateIdentifier:e,spec:g,t:o,resolveLabel:ne,permissionCandidates:B,onClose:()=>r.close(te)})})},[r,t.selectedRoute,t.editMode,o,ne,e,g,B]),Ce=u.useCallback(async()=>{const te=await d.createPreview(s.document?.raw??null);if(te.kind!=="success"){const he=te.kind==="network_error"?te.message:o("layout_editor.preview_action.no_document");typeof window<"u"&&window.alert(he)}},[d,s.document,o]),ze=u.useCallback(()=>{const te=t.selectedRoute?.layoutName??"",he=t.selectedRoute?.path??"",Q=t.editMode==="extension"&&he.startsWith("__extension__/")?he.slice(14):null,ie=Q?{kind:"extension",extensionId:Q}:te?{kind:"layout",layoutName:te}:null;if(!ie)return;const _e=r.open({ariaLabel:o("layout_editor.version_history.title"),width:640,maxHeightRatio:.82,content:u.createElement(G$,{templateIdentifier:e,target:ie,t:o,onRestored:async ge=>{typeof ge=="number"&&(ie.kind==="extension"?n({type:"SET_EXTENSION_VERSION",extensionId:ie.extensionId,version:ge}):n({type:"SET_LAYOUT_VERSION",layoutName:ie.layoutName,version:ge})),await p.reload()},onClose:()=>r.close(_e)})})},[r,e,t.selectedRoute,t.editMode,o,p,n]),Se=p.dirtyKeys.size>0||p.isDirty;u.useEffect(()=>{if(!Se)return;const te=he=>{he.preventDefault(),he.returnValue=""};return window.addEventListener("beforeunload",te),()=>window.removeEventListener("beforeunload",te)},[Se]);const qe=u.useCallback(()=>{!p.isDirty||!(!(typeof window<"u")||window.confirm(o("layout_editor.chrome.reset_guard.message")))||p.reload()},[p,o]),De=u.useCallback(async()=>{const te=await p.save({sessionAddedPaths:I.current});G(te),te.kind==="success"&&typeof te.newContentVersion=="number"&&(te.savedExtensionId?n({type:"SET_EXTENSION_VERSION",extensionId:te.savedExtensionId,version:te.newContentVersion}):te.savedLayoutName&&n({type:"SET_LAYOUT_VERSION",layoutName:te.savedLayoutName,version:te.newContentVersion}))},[p,n]),Pe=u.useCallback(()=>{const te=r.open({ariaLabel:o("layout_editor.shortcuts.title"),width:560,maxHeightRatio:.85,content:u.createElement(nE,{t:o,onClose:()=>r.close(te)})})},[r,o]),Qe=u.useMemo(()=>({save:()=>{De()},exit:re,editCode:ee,reset:qe,translations:K,addElement:()=>{t.selectedRoute&&n({type:"TOGGLE_PALETTE"})},help:Pe,preview:()=>{t.selectedRoute?.layoutName&&Ce()}}),[De,re,ee,qe,K,t.selectedRoute,n,Pe,Ce]),[Me,Re]=u.useState(!1);u.useEffect(()=>{const te=window.setInterval(()=>{Re(!!window.__g7LayoutEditorHasSelection)},200);return()=>window.clearInterval(te)},[]),ep({handlers:Qe,hasSelection:Me});const ve=p;return t.routesError?i.jsx("div",{className:"g7le-chrome g7le-chrome--routes-error","data-testid":"g7le-chrome-routes-error",style:{position:"fixed",inset:0,display:"flex",background:"#f8fafc",color:"#0f172a",fontFamily:'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Apple SD Gothic Neo", "Noto Sans KR", sans-serif',fontSize:13,lineHeight:1.5,zIndex:9e3},children:i.jsx(Fa,{error:t.routesError})}):i.jsx("div",{className:"g7le-chrome","data-testid":"g7le-chrome",ref:Y,style:{position:"static",minHeight:"100vh",minWidth:Oz,background:"#f8fafc",color:"#0f172a",fontFamily:'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Apple SD Gothic Neo", "Noto Sans KR", sans-serif',fontSize:13,lineHeight:1.5,zIndex:9e3},children:i.jsxs(ay,{value:ve,children:[c&&a.needsHostPicker&&a.document&&i.jsx(xD,{hostLayouts:a.document.hostLayouts,extensionLabel:null,onSelect:a.selectHost}),i.jsxs("div",{className:"g7le-chrome__header","data-testid":"g7le-chrome-header",ref:Z,style:{position:"sticky",top:0,zIndex:20,background:"#f8fafc"},children:[i.jsx(hy,{onSave:()=>{De()},isDirty:p.isDirty,onExit:re,onEditCode:ee,onManageImages:fe,onManageTranslations:K,onPageSettings:me,onReset:qe,onShowShortcuts:Pe,onPreview:Ce,onShowVersions:ze,templateName:k,templateVersion:_,templateList:E,onSwitchTemplate:U}),i.jsx(mE,{result:F,onDismiss:V,onLoadLatest:se}),i.jsxs("div",{className:"g7le-chrome__device-toolbar","data-testid":"g7le-chrome-device-toolbar",style:{display:"flex",justifyContent:"center",padding:"6px 12px",background:"#f1f5f9",borderBottom:"1px solid #e2e8f0"},children:[i.jsx(F4,{}),i.jsx(K4,{})]})]}),i.jsxs("div",{className:"g7le-chrome__body",style:{display:"grid",gridTemplateColumns:"auto 1fr",alignItems:"stretch"},children:[i.jsx(BE,{}),i.jsx(O4,{manifest:f,nesting:N,componentPalette:g?.componentPalette??null,spec:g,permissionCandidates:B,sampleGlobalSources:h,resolveRouteMatch:H,onNavigateToDestination:L,onSessionAddedPathsChange:P})]}),i.jsx(cy,{})]})})}function yl(e,t){for(const n of e){if(n.kind==="route"&&n.path===t)return n;if(n.children&&n.children.length>0){const o=yl(n.children,t);if(o)return o}}return null}function jz(e,t){return yl(e,t)!==null}function bl(e,t){for(const n of e){if(n.kind==="route"&&n.layoutName===t&&typeof n.path=="string")return n.path;if(n.children&&n.children.length>0){const o=bl(n.children,t);if(o)return o}}return null}function Hz(e,t,n){const o=[];Va({sources:n,isGuestOnly:!1,warn:s=>{const a=/extension '([^']+)' override of core key '([^']+)'/.exec(s);a&&o.push(`${a[1]}:${a[2]}`)}});const r=t?.sampleData?.byDataSourceId?Object.keys(t.sampleData.byDataSourceId).length:0;Jh({templateIdentifier:e,mergedSources:[...n.map(s=>({kind:s.kind,id:s.id}))],blockCounts:{controls:t?.controls?Object.keys(t.controls).length:0,componentCapabilities:t?.componentCapabilities?Object.keys(t.componentCapabilities).length:0,actionRecipes:t?.actionRecipes?Object.keys(t.actionRecipes).length:0,conditionRecipes:t?.conditionRecipes?Object.keys(t.conditionRecipes).length:0,paletteGroups:t?.componentPalette?.groups?.length??0,stateGroups:t?.states?.groups?.length??0,sampleDataIds:r},sampleGlobalSourceCount:n.length,sampleGlobalConflicts:o,timestamp:Date.now()})}function Lh({templateIdentifier:e,initialLocale:t}){return u.useEffect(()=>()=>{Fh(),Vh(),qh(),Gh(),Yh(),Xh(),Zh(),Qh(),Rh()},[]),i.jsx(sy,{templateIdentifier:e,initialLocale:t,children:i.jsx(dy,{children:i.jsx(Wz,{templateIdentifier:e})})})}if(typeof window<"u"){const e=window.G7Core||(window.G7Core={});if(e.__LayoutEditorChrome=Lh,typeof e.__onChromeReady=="function")try{e.__onChromeReady()}catch{}}dn.LayoutEditorChrome=Lh,Object.defineProperty(dn,Symbol.toStringTag,{value:"Module"})})(this.G7LayoutEditor=this.G7LayoutEditor||{},ReactJSXRuntime,React,ReactDOM); + `},O7={onDragStart(e){let{active:t}=e;return"Picked up draggable item "+t.id+"."},onDragOver(e){let{active:t,over:n}=e;return n?"Draggable item "+t.id+" was moved over droppable area "+n.id+".":"Draggable item "+t.id+" is no longer over a droppable area."},onDragEnd(e){let{active:t,over:n}=e;return n?"Draggable item "+t.id+" was dropped over droppable area "+n.id:"Draggable item "+t.id+" was dropped."},onDragCancel(e){let{active:t}=e;return"Dragging was cancelled. Draggable item "+t.id+" was dropped."}};function B7(e){let{announcements:t=O7,container:n,hiddenTextDescribedById:o,screenReaderInstructions:r=P7}=e;const{announce:s,announcement:a}=z7(),l=ei("DndLiveRegion"),[d,c]=u.useState(!1);if(u.useEffect(()=>{c(!0)},[]),M7(u.useMemo(()=>({onDragStart(f){let{active:y}=f;s(t.onDragStart({active:y}))},onDragMove(f){let{active:y,over:g}=f;t.onDragMove&&s(t.onDragMove({active:y,over:g}))},onDragOver(f){let{active:y,over:g}=f;s(t.onDragOver({active:y,over:g}))},onDragEnd(f){let{active:y,over:g}=f;s(t.onDragEnd({active:y,over:g}))},onDragCancel(f){let{active:y,over:g}=f;s(t.onDragCancel({active:y,over:g}))}}),[s,t])),!d)return null;const p=u.createElement(u.Fragment,null,u.createElement(D7,{id:o,value:r.draggable}),u.createElement(N7,{id:l,announcement:a}));return n?Cn.createPortal(p,n):p}var it;(function(e){e.DragStart="dragStart",e.DragMove="dragMove",e.DragEnd="dragEnd",e.DragCancel="dragCancel",e.DragOver="dragOver",e.RegisterDroppable="registerDroppable",e.SetDroppableDisabled="setDroppableDisabled",e.UnregisterDroppable="unregisterDroppable"})(it||(it={}));function oi(){}function W7(e,t){return u.useMemo(()=>({sensor:e,options:t??{}}),[e,t])}function j7(){for(var e=arguments.length,t=new Array(e),n=0;n[...t].filter(o=>o!=null),[...t])}const Ht=Object.freeze({x:0,y:0});function H7(e,t){return Math.sqrt(Math.pow(e.x-t.x,2)+Math.pow(e.y-t.y,2))}function F7(e,t){const n=ni(e);if(!n)return"0 0";const o={x:(n.x-t.left)/t.width*100,y:(n.y-t.top)/t.height*100};return o.x+"% "+o.y+"%"}function K7(e,t){let{data:{value:n}}=e,{data:{value:o}}=t;return n-o}function V7(e,t){let{data:{value:n}}=e,{data:{value:o}}=t;return o-n}function U7(e){let{left:t,top:n,height:o,width:r}=e;return[{x:t,y:n},{x:t+r,y:n},{x:t,y:n+o},{x:t+r,y:n+o}]}function q7(e,t){if(!e||e.length===0)return null;const[n]=e;return n[t]}function G7(e,t){const n=Math.max(t.top,e.top),o=Math.max(t.left,e.left),r=Math.min(t.left+t.width,e.left+e.width),s=Math.min(t.top+t.height,e.top+e.height),a=r-o,l=s-n;if(o{let{collisionRect:t,droppableRects:n,droppableContainers:o}=e;const r=[];for(const s of o){const{id:a}=s,l=n.get(a);if(l){const d=G7(l,t);d>0&&r.push({id:a,data:{droppableContainer:s,value:d}})}}return r.sort(V7)};function X7(e,t){const{top:n,left:o,bottom:r,right:s}=t;return n<=e.y&&e.y<=r&&o<=e.x&&e.x<=s}const J7=e=>{let{droppableContainers:t,droppableRects:n,pointerCoordinates:o}=e;if(!o)return[];const r=[];for(const s of t){const{id:a}=s,l=n.get(a);if(l&&X7(o,l)){const c=U7(l).reduce((f,y)=>f+H7(o,y),0),p=Number((c/4).toFixed(4));r.push({id:a,data:{droppableContainer:s,value:p}})}}return r.sort(K7)};function Z7(e,t,n){return{...e,scaleX:t&&n?t.width/n.width:1,scaleY:t&&n?t.height/n.height:1}}function Kp(e,t){return e&&t?{x:e.left-t.left,y:e.top-t.top}:Ht}function Q7(e){return function(n){for(var o=arguments.length,r=new Array(o>1?o-1:0),s=1;s({...a,top:a.top+e*l.y,bottom:a.bottom+e*l.y,left:a.left+e*l.x,right:a.right+e*l.x}),{...n})}}const R7=Q7(1);function Vp(e){if(e.startsWith("matrix3d(")){const t=e.slice(9,-1).split(/, /);return{x:+t[12],y:+t[13],scaleX:+t[0],scaleY:+t[5]}}else if(e.startsWith("matrix(")){const t=e.slice(7,-1).split(/, /);return{x:+t[4],y:+t[5],scaleX:+t[0],scaleY:+t[3]}}return null}function e2(e,t,n){const o=Vp(t);if(!o)return e;const{scaleX:r,scaleY:s,x:a,y:l}=o,d=e.left-a-(1-r)*parseFloat(n),c=e.top-l-(1-s)*parseFloat(n.slice(n.indexOf(" ")+1)),p=r?e.width/r:e.width,f=s?e.height/s:e.height;return{width:p,height:f,top:c,right:d+p,bottom:c+f,left:d}}const t2={ignoreTransform:!1};function Ho(e,t){t===void 0&&(t=t2);let n=e.getBoundingClientRect();if(t.ignoreTransform){const{transform:c,transformOrigin:p}=_t(e).getComputedStyle(e);c&&(n=e2(n,c,p))}const{top:o,left:r,width:s,height:a,bottom:l,right:d}=n;return{top:o,left:r,width:s,height:a,bottom:l,right:d}}function Up(e){return Ho(e,{ignoreTransform:!0})}function n2(e){const t=e.innerWidth,n=e.innerHeight;return{top:0,left:0,right:t,bottom:n,width:t,height:n}}function o2(e,t){return t===void 0&&(t=_t(e).getComputedStyle(e)),t.position==="fixed"}function r2(e,t){t===void 0&&(t=_t(e).getComputedStyle(e));const n=/(auto|scroll|overlay)/;return["overflow","overflowX","overflowY"].some(r=>{const s=t[r];return typeof s=="string"?n.test(s):!1})}function da(e,t){const n=[];function o(r){if(t!=null&&n.length>=t||!r)return n;if(sa(r)&&r.scrollingElement!=null&&!n.includes(r.scrollingElement))return n.push(r.scrollingElement),n;if(!Oo(r)||Wp(r)||n.includes(r))return n;const s=_t(e).getComputedStyle(r);return r!==e&&r2(r,s)&&n.push(r),o2(r,s)?n:o(r.parentNode)}return e?o(e):n}function qp(e){const[t]=da(e,1);return t??null}function ca(e){return!Jr||!e?null:lo(e)?e:ia(e)?sa(e)||e===co(e).scrollingElement?window:Oo(e)?e:null:null}function Gp(e){return lo(e)?e.scrollX:e.scrollLeft}function Yp(e){return lo(e)?e.scrollY:e.scrollTop}function ua(e){return{x:Gp(e),y:Yp(e)}}var lt;(function(e){e[e.Forward=1]="Forward",e[e.Backward=-1]="Backward"})(lt||(lt={}));function Xp(e){return!Jr||!e?!1:e===document.scrollingElement}function Jp(e){const t={x:0,y:0},n=Xp(e)?{height:window.innerHeight,width:window.innerWidth}:{height:e.clientHeight,width:e.clientWidth},o={x:e.scrollWidth-n.width,y:e.scrollHeight-n.height},r=e.scrollTop<=t.y,s=e.scrollLeft<=t.x,a=e.scrollTop>=o.y,l=e.scrollLeft>=o.x;return{isTop:r,isLeft:s,isBottom:a,isRight:l,maxScroll:o,minScroll:t}}const i2={x:.2,y:.2};function s2(e,t,n,o,r){let{top:s,left:a,right:l,bottom:d}=n;o===void 0&&(o=10),r===void 0&&(r=i2);const{isTop:c,isBottom:p,isLeft:f,isRight:y}=Jp(e),g={x:0,y:0},m={x:0,y:0},h={height:t.height*r.y,width:t.width*r.x};return!c&&s<=t.top+h.height?(g.y=lt.Backward,m.y=o*Math.abs((t.top+h.height-s)/h.height)):!p&&d>=t.bottom-h.height&&(g.y=lt.Forward,m.y=o*Math.abs((t.bottom-h.height-d)/h.height)),!y&&l>=t.right-h.width?(g.x=lt.Forward,m.x=o*Math.abs((t.right-h.width-l)/h.width)):!f&&a<=t.left+h.width&&(g.x=lt.Backward,m.x=o*Math.abs((t.left+h.width-a)/h.width)),{direction:g,speed:m}}function a2(e){if(e===document.scrollingElement){const{innerWidth:s,innerHeight:a}=window;return{top:0,left:0,right:s,bottom:a,width:s,height:a}}const{top:t,left:n,right:o,bottom:r}=e.getBoundingClientRect();return{top:t,left:n,right:o,bottom:r,width:e.clientWidth,height:e.clientHeight}}function Zp(e){return e.reduce((t,n)=>uo(t,ua(n)),Ht)}function l2(e){return e.reduce((t,n)=>t+Gp(n),0)}function d2(e){return e.reduce((t,n)=>t+Yp(n),0)}function Qp(e,t){if(t===void 0&&(t=Ho),!e)return;const{top:n,left:o,bottom:r,right:s}=t(e);qp(e)&&(r<=0||s<=0||n>=window.innerHeight||o>=window.innerWidth)&&e.scrollIntoView({block:"center",inline:"center"})}const c2=[["x",["left","right"],l2],["y",["top","bottom"],d2]];class pa{constructor(t,n){this.rect=void 0,this.width=void 0,this.height=void 0,this.top=void 0,this.bottom=void 0,this.right=void 0,this.left=void 0;const o=da(n),r=Zp(o);this.rect={...t},this.width=t.width,this.height=t.height;for(const[s,a,l]of c2)for(const d of a)Object.defineProperty(this,d,{get:()=>{const c=l(o),p=r[s]-c;return this.rect[d]+p},enumerable:!0});Object.defineProperty(this,"rect",{enumerable:!1})}}class Fo{constructor(t){this.target=void 0,this.listeners=[],this.removeAll=()=>{this.listeners.forEach(n=>{var o;return(o=this.target)==null?void 0:o.removeEventListener(...n)})},this.target=t}add(t,n,o){var r;(r=this.target)==null||r.addEventListener(t,n,o),this.listeners.push([t,n,o])}}function u2(e){const{EventTarget:t}=_t(e);return e instanceof t?e:co(e)}function fa(e,t){const n=Math.abs(e.x),o=Math.abs(e.y);return typeof t=="number"?Math.sqrt(n**2+o**2)>t:"x"in t&&"y"in t?n>t.x&&o>t.y:"x"in t?n>t.x:"y"in t?o>t.y:!1}var At;(function(e){e.Click="click",e.DragStart="dragstart",e.Keydown="keydown",e.ContextMenu="contextmenu",e.Resize="resize",e.SelectionChange="selectionchange",e.VisibilityChange="visibilitychange"})(At||(At={}));function Rp(e){e.preventDefault()}function p2(e){e.stopPropagation()}var Ke;(function(e){e.Space="Space",e.Down="ArrowDown",e.Right="ArrowRight",e.Left="ArrowLeft",e.Up="ArrowUp",e.Esc="Escape",e.Enter="Enter",e.Tab="Tab"})(Ke||(Ke={}));const ef={start:[Ke.Space,Ke.Enter],cancel:[Ke.Esc],end:[Ke.Space,Ke.Enter,Ke.Tab]},f2=(e,t)=>{let{currentCoordinates:n}=t;switch(e.code){case Ke.Right:return{...n,x:n.x+25};case Ke.Left:return{...n,x:n.x-25};case Ke.Down:return{...n,y:n.y+25};case Ke.Up:return{...n,y:n.y-25}}};class tf{constructor(t){this.props=void 0,this.autoScrollEnabled=!1,this.referenceCoordinates=void 0,this.listeners=void 0,this.windowListeners=void 0,this.props=t;const{event:{target:n}}=t;this.props=t,this.listeners=new Fo(co(n)),this.windowListeners=new Fo(_t(n)),this.handleKeyDown=this.handleKeyDown.bind(this),this.handleCancel=this.handleCancel.bind(this),this.attach()}attach(){this.handleStart(),this.windowListeners.add(At.Resize,this.handleCancel),this.windowListeners.add(At.VisibilityChange,this.handleCancel),setTimeout(()=>this.listeners.add(At.Keydown,this.handleKeyDown))}handleStart(){const{activeNode:t,onStart:n}=this.props,o=t.node.current;o&&Qp(o),n(Ht)}handleKeyDown(t){if(la(t)){const{active:n,context:o,options:r}=this.props,{keyboardCodes:s=ef,coordinateGetter:a=f2,scrollBehavior:l="smooth"}=r,{code:d}=t;if(s.end.includes(d)){this.handleEnd(t);return}if(s.cancel.includes(d)){this.handleCancel(t);return}const{collisionRect:c}=o.current,p=c?{x:c.left,y:c.top}:Ht;this.referenceCoordinates||(this.referenceCoordinates=p);const f=a(t,{active:n,context:o.current,currentCoordinates:p});if(f){const y=ti(f,p),g={x:0,y:0},{scrollableAncestors:m}=o.current;for(const h of m){const v=t.code,{isTop:b,isRight:x,isLeft:k,isBottom:w,maxScroll:_,minScroll:C}=Jp(h),E=a2(h),S={x:Math.min(v===Ke.Right?E.right-E.width/2:E.right,Math.max(v===Ke.Right?E.left:E.left+E.width/2,f.x)),y:Math.min(v===Ke.Down?E.bottom-E.height/2:E.bottom,Math.max(v===Ke.Down?E.top:E.top+E.height/2,f.y))},T=v===Ke.Right&&!x||v===Ke.Left&&!k,$=v===Ke.Down&&!w||v===Ke.Up&&!b;if(T&&S.x!==f.x){const A=h.scrollLeft+y.x,z=v===Ke.Right&&A<=_.x||v===Ke.Left&&A>=C.x;if(z&&!y.y){h.scrollTo({left:A,behavior:l});return}z?g.x=h.scrollLeft-A:g.x=v===Ke.Right?h.scrollLeft-_.x:h.scrollLeft-C.x,g.x&&h.scrollBy({left:-g.x,behavior:l});break}else if($&&S.y!==f.y){const A=h.scrollTop+y.y,z=v===Ke.Down&&A<=_.y||v===Ke.Up&&A>=C.y;if(z&&!y.x){h.scrollTo({top:A,behavior:l});return}z?g.y=h.scrollTop-A:g.y=v===Ke.Down?h.scrollTop-_.y:h.scrollTop-C.y,g.y&&h.scrollBy({top:-g.y,behavior:l});break}}this.handleMove(t,uo(ti(f,this.referenceCoordinates),g))}}}handleMove(t,n){const{onMove:o}=this.props;t.preventDefault(),o(n)}handleEnd(t){const{onEnd:n}=this.props;t.preventDefault(),this.detach(),n()}handleCancel(t){const{onCancel:n}=this.props;t.preventDefault(),this.detach(),n()}detach(){this.listeners.removeAll(),this.windowListeners.removeAll()}}tf.activators=[{eventName:"onKeyDown",handler:(e,t,n)=>{let{keyboardCodes:o=ef,onActivation:r}=t,{active:s}=n;const{code:a}=e.nativeEvent;if(o.start.includes(a)){const l=s.activatorNode.current;return l&&e.target!==l?!1:(e.preventDefault(),r?.({event:e.nativeEvent}),!0)}return!1}}];function nf(e){return!!(e&&"distance"in e)}function of(e){return!!(e&&"delay"in e)}class ga{constructor(t,n,o){var r;o===void 0&&(o=u2(t.event.target)),this.props=void 0,this.events=void 0,this.autoScrollEnabled=!0,this.document=void 0,this.activated=!1,this.initialCoordinates=void 0,this.timeoutId=null,this.listeners=void 0,this.documentListeners=void 0,this.windowListeners=void 0,this.props=t,this.events=n;const{event:s}=t,{target:a}=s;this.props=t,this.events=n,this.document=co(a),this.documentListeners=new Fo(this.document),this.listeners=new Fo(o),this.windowListeners=new Fo(_t(a)),this.initialCoordinates=(r=ni(s))!=null?r:Ht,this.handleStart=this.handleStart.bind(this),this.handleMove=this.handleMove.bind(this),this.handleEnd=this.handleEnd.bind(this),this.handleCancel=this.handleCancel.bind(this),this.handleKeydown=this.handleKeydown.bind(this),this.removeTextSelection=this.removeTextSelection.bind(this),this.attach()}attach(){const{events:t,props:{options:{activationConstraint:n,bypassActivationConstraint:o}}}=this;if(this.listeners.add(t.move.name,this.handleMove,{passive:!1}),this.listeners.add(t.end.name,this.handleEnd),t.cancel&&this.listeners.add(t.cancel.name,this.handleCancel),this.windowListeners.add(At.Resize,this.handleCancel),this.windowListeners.add(At.DragStart,Rp),this.windowListeners.add(At.VisibilityChange,this.handleCancel),this.windowListeners.add(At.ContextMenu,Rp),this.documentListeners.add(At.Keydown,this.handleKeydown),n){if(o!=null&&o({event:this.props.event,activeNode:this.props.activeNode,options:this.props.options}))return this.handleStart();if(of(n)){this.timeoutId=setTimeout(this.handleStart,n.delay),this.handlePending(n);return}if(nf(n)){this.handlePending(n);return}}this.handleStart()}detach(){this.listeners.removeAll(),this.windowListeners.removeAll(),setTimeout(this.documentListeners.removeAll,50),this.timeoutId!==null&&(clearTimeout(this.timeoutId),this.timeoutId=null)}handlePending(t,n){const{active:o,onPending:r}=this.props;r(o,t,this.initialCoordinates,n)}handleStart(){const{initialCoordinates:t}=this,{onStart:n}=this.props;t&&(this.activated=!0,this.documentListeners.add(At.Click,p2,{capture:!0}),this.removeTextSelection(),this.documentListeners.add(At.SelectionChange,this.removeTextSelection),n(t))}handleMove(t){var n;const{activated:o,initialCoordinates:r,props:s}=this,{onMove:a,options:{activationConstraint:l}}=s;if(!r)return;const d=(n=ni(t))!=null?n:Ht,c=ti(r,d);if(!o&&l){if(nf(l)){if(l.tolerance!=null&&fa(c,l.tolerance))return this.handleCancel();if(fa(c,l.distance))return this.handleStart()}if(of(l)&&fa(c,l.tolerance))return this.handleCancel();this.handlePending(l,c);return}t.cancelable&&t.preventDefault(),a(d)}handleEnd(){const{onAbort:t,onEnd:n}=this.props;this.detach(),this.activated||t(this.props.active),n()}handleCancel(){const{onAbort:t,onCancel:n}=this.props;this.detach(),this.activated||t(this.props.active),n()}handleKeydown(t){t.code===Ke.Esc&&this.handleCancel()}removeTextSelection(){var t;(t=this.document.getSelection())==null||t.removeAllRanges()}}const g2={cancel:{name:"pointercancel"},move:{name:"pointermove"},end:{name:"pointerup"}};class ha extends ga{constructor(t){const{event:n}=t,o=co(n.target);super(t,g2,o)}}ha.activators=[{eventName:"onPointerDown",handler:(e,t)=>{let{nativeEvent:n}=e,{onActivation:o}=t;return!n.isPrimary||n.button!==0?!1:(o?.({event:n}),!0)}}];const h2={move:{name:"mousemove"},end:{name:"mouseup"}};var ya;(function(e){e[e.RightClick=2]="RightClick"})(ya||(ya={}));class y2 extends ga{constructor(t){super(t,h2,co(t.event.target))}}y2.activators=[{eventName:"onMouseDown",handler:(e,t)=>{let{nativeEvent:n}=e,{onActivation:o}=t;return n.button===ya.RightClick?!1:(o?.({event:n}),!0)}}];const ba={cancel:{name:"touchcancel"},move:{name:"touchmove"},end:{name:"touchend"}};class b2 extends ga{constructor(t){super(t,ba)}static setup(){return window.addEventListener(ba.move.name,t,{capture:!1,passive:!1}),function(){window.removeEventListener(ba.move.name,t)};function t(){}}}b2.activators=[{eventName:"onTouchStart",handler:(e,t)=>{let{nativeEvent:n}=e,{onActivation:o}=t;const{touches:r}=n;return r.length>1?!1:(o?.({event:n}),!0)}}];var Ko;(function(e){e[e.Pointer=0]="Pointer",e[e.DraggableRect=1]="DraggableRect"})(Ko||(Ko={}));var ri;(function(e){e[e.TreeOrder=0]="TreeOrder",e[e.ReversedTreeOrder=1]="ReversedTreeOrder"})(ri||(ri={}));function m2(e){let{acceleration:t,activator:n=Ko.Pointer,canScroll:o,draggingRect:r,enabled:s,interval:a=5,order:l=ri.TreeOrder,pointerCoordinates:d,scrollableAncestors:c,scrollableAncestorRects:p,delta:f,threshold:y}=e;const g=_2({delta:f,disabled:!s}),[m,h]=$7(),v=u.useRef({x:0,y:0}),b=u.useRef({x:0,y:0}),x=u.useMemo(()=>{switch(n){case Ko.Pointer:return d?{top:d.y,bottom:d.y,left:d.x,right:d.x}:null;case Ko.DraggableRect:return r}},[n,r,d]),k=u.useRef(null),w=u.useCallback(()=>{const C=k.current;if(!C)return;const E=v.current.x*b.current.x,S=v.current.y*b.current.y;C.scrollBy(E,S)},[]),_=u.useMemo(()=>l===ri.TreeOrder?[...c].reverse():c,[l,c]);u.useEffect(()=>{if(!s||!c.length||!x){h();return}for(const C of _){if(o?.(C)===!1)continue;const E=c.indexOf(C),S=p[E];if(!S)continue;const{direction:T,speed:$}=s2(C,S,x,t,y);for(const A of["x","y"])g[A][T[A]]||($[A]=0,T[A]=0);if($.x>0||$.y>0){h(),k.current=C,m(w,a),v.current=$,b.current=T;return}}v.current={x:0,y:0},b.current={x:0,y:0},h()},[t,w,o,h,s,a,JSON.stringify(x),JSON.stringify(g),m,c,_,p,JSON.stringify(y)])}const v2={x:{[lt.Backward]:!1,[lt.Forward]:!1},y:{[lt.Backward]:!1,[lt.Forward]:!1}};function _2(e){let{delta:t,disabled:n}=e;const o=Rr(t);return Wo(r=>{if(n||!o||!r)return v2;const s={x:Math.sign(t.x-o.x),y:Math.sign(t.y-o.y)};return{x:{[lt.Backward]:r.x[lt.Backward]||s.x===-1,[lt.Forward]:r.x[lt.Forward]||s.x===1},y:{[lt.Backward]:r.y[lt.Backward]||s.y===-1,[lt.Forward]:r.y[lt.Forward]||s.y===1}}},[n,t,o])}function x2(e,t){const n=t!=null?e.get(t):void 0,o=n?n.node.current:null;return Wo(r=>{var s;return t==null?null:(s=o??r)!=null?s:null},[o,t])}function w2(e,t){return u.useMemo(()=>e.reduce((n,o)=>{const{sensor:r}=o,s=r.activators.map(a=>({eventName:a.eventName,handler:t(a.handler,o)}));return[...n,...s]},[]),[e,t])}var Vo;(function(e){e[e.Always=0]="Always",e[e.BeforeDragging=1]="BeforeDragging",e[e.WhileDragging=2]="WhileDragging"})(Vo||(Vo={}));var ma;(function(e){e.Optimized="optimized"})(ma||(ma={}));const rf=new Map;function k2(e,t){let{dragging:n,dependencies:o,config:r}=t;const[s,a]=u.useState(null),{frequency:l,measure:d,strategy:c}=r,p=u.useRef(e),f=v(),y=Bo(f),g=u.useCallback(function(b){b===void 0&&(b=[]),!y.current&&a(x=>x===null?b:x.concat(b.filter(k=>!x.includes(k))))},[y]),m=u.useRef(null),h=Wo(b=>{if(f&&!n)return rf;if(!b||b===rf||p.current!==e||s!=null){const x=new Map;for(let k of e){if(!k)continue;if(s&&s.length>0&&!s.includes(k.id)&&k.rect.current){x.set(k.id,k.rect.current);continue}const w=k.node.current,_=w?new pa(d(w),w):null;k.rect.current=_,_&&x.set(k.id,_)}return x}return b},[e,s,n,f,d]);return u.useEffect(()=>{p.current=e},[e]),u.useEffect(()=>{f||g()},[n,f]),u.useEffect(()=>{s&&s.length>0&&a(null)},[JSON.stringify(s)]),u.useEffect(()=>{f||typeof l!="number"||m.current!==null||(m.current=setTimeout(()=>{g(),m.current=null},l))},[l,f,g,...o]),{droppableRects:h,measureDroppableContainers:g,measuringScheduled:s!=null};function v(){switch(c){case Vo.Always:return!1;case Vo.BeforeDragging:return n;default:return!n}}}function va(e,t){return Wo(n=>e?n||(typeof t=="function"?t(e):e):null,[t,e])}function S2(e,t){return va(e,t)}function C2(e){let{callback:t,disabled:n}=e;const o=Zr(t),r=u.useMemo(()=>{if(n||typeof window>"u"||typeof window.MutationObserver>"u")return;const{MutationObserver:s}=window;return new s(o)},[o,n]);return u.useEffect(()=>()=>r?.disconnect(),[r]),r}function ii(e){let{callback:t,disabled:n}=e;const o=Zr(t),r=u.useMemo(()=>{if(n||typeof window>"u"||typeof window.ResizeObserver>"u")return;const{ResizeObserver:s}=window;return new s(o)},[n]);return u.useEffect(()=>()=>r?.disconnect(),[r]),r}function $2(e){return new pa(Ho(e),e)}function sf(e,t,n){t===void 0&&(t=$2);const[o,r]=u.useState(null);function s(){r(d=>{if(!e)return null;if(e.isConnected===!1){var c;return(c=d??n)!=null?c:null}const p=t(e);return JSON.stringify(d)===JSON.stringify(p)?d:p})}const a=C2({callback(d){if(e)for(const c of d){const{type:p,target:f}=c;if(p==="childList"&&f instanceof HTMLElement&&f.contains(e)){s();break}}}}),l=ii({callback:s});return sn(()=>{s(),e?(l?.observe(e),a?.observe(document.body,{childList:!0,subtree:!0})):(l?.disconnect(),a?.disconnect())},[e]),o}function E2(e){const t=va(e);return Kp(e,t)}const af=[];function I2(e){const t=u.useRef(e),n=Wo(o=>e?o&&o!==af&&e&&t.current&&e.parentNode===t.current.parentNode?o:da(e):af,[e]);return u.useEffect(()=>{t.current=e},[e]),n}function A2(e){const[t,n]=u.useState(null),o=u.useRef(e),r=u.useCallback(s=>{const a=ca(s.target);a&&n(l=>l?(l.set(a,ua(a)),new Map(l)):null)},[]);return u.useEffect(()=>{const s=o.current;if(e!==s){a(s);const l=e.map(d=>{const c=ca(d);return c?(c.addEventListener("scroll",r,{passive:!0}),[c,ua(c)]):null}).filter(d=>d!=null);n(l.length?new Map(l):null),o.current=e}return()=>{a(e),a(s)};function a(l){l.forEach(d=>{const c=ca(d);c?.removeEventListener("scroll",r)})}},[r,e]),u.useMemo(()=>e.length?t?Array.from(t.values()).reduce((s,a)=>uo(s,a),Ht):Zp(e):Ht,[e,t])}function lf(e,t){t===void 0&&(t=[]);const n=u.useRef(null);return u.useEffect(()=>{n.current=null},t),u.useEffect(()=>{const o=e!==Ht;o&&!n.current&&(n.current=e),!o&&n.current&&(n.current=null)},[e]),n.current?ti(e,n.current):Ht}function T2(e){u.useEffect(()=>{if(!Jr)return;const t=e.map(n=>{let{sensor:o}=n;return o.setup==null?void 0:o.setup()});return()=>{for(const n of t)n?.()}},e.map(t=>{let{sensor:n}=t;return n}))}function D2(e,t){return u.useMemo(()=>e.reduce((n,o)=>{let{eventName:r,handler:s}=o;return n[r]=a=>{s(a,t)},n},{}),[e,t])}function df(e){return u.useMemo(()=>e?n2(e):null,[e])}const cf=[];function N2(e,t){t===void 0&&(t=Ho);const[n]=e,o=df(n?_t(n):null),[r,s]=u.useState(cf);function a(){s(()=>e.length?e.map(d=>Xp(d)?o:new pa(t(d),d)):cf)}const l=ii({callback:a});return sn(()=>{l?.disconnect(),a(),e.forEach(d=>l?.observe(d))},[e]),r}function uf(e){if(!e)return null;if(e.children.length>1)return e;const t=e.children[0];return Oo(t)?t:e}function z2(e){let{measure:t}=e;const[n,o]=u.useState(null),r=u.useCallback(c=>{for(const{target:p}of c)if(Oo(p)){o(f=>{const y=t(p);return f?{...f,width:y.width,height:y.height}:y});break}},[t]),s=ii({callback:r}),a=u.useCallback(c=>{const p=uf(c);s?.disconnect(),p&&s?.observe(p),o(p?t(p):null)},[t,s]),[l,d]=Qr(a);return u.useMemo(()=>({nodeRef:l,rect:n,setRef:d}),[n,l,d])}const M2=[{sensor:ha,options:{}},{sensor:tf,options:{}}],L2={current:{}},si={draggable:{measure:Up},droppable:{measure:Up,strategy:Vo.WhileDragging,frequency:ma.Optimized},dragOverlay:{measure:Ho}};class Uo extends Map{get(t){var n;return t!=null&&(n=super.get(t))!=null?n:void 0}toArray(){return Array.from(this.values())}getEnabled(){return this.toArray().filter(t=>{let{disabled:n}=t;return!n})}getNodeFor(t){var n,o;return(n=(o=this.get(t))==null?void 0:o.node.current)!=null?n:void 0}}const P2={activatorEvent:null,active:null,activeNode:null,activeNodeRect:null,collisions:null,containerNodeRect:null,draggableNodes:new Map,droppableRects:new Map,droppableContainers:new Uo,over:null,dragOverlay:{nodeRef:{current:null},rect:null,setRef:oi},scrollableAncestors:[],scrollableAncestorRects:[],measuringConfiguration:si,measureDroppableContainers:oi,windowRect:null,measuringScheduled:!1},pf={activatorEvent:null,activators:[],active:null,activeNodeRect:null,ariaDescribedById:{draggable:""},dispatch:oi,draggableNodes:new Map,over:null,measureDroppableContainers:oi},qo=u.createContext(pf),ff=u.createContext(P2);function O2(){return{draggable:{active:null,initialCoordinates:{x:0,y:0},nodes:new Map,translate:{x:0,y:0}},droppable:{containers:new Uo}}}function B2(e,t){switch(t.type){case it.DragStart:return{...e,draggable:{...e.draggable,initialCoordinates:t.initialCoordinates,active:t.active}};case it.DragMove:return e.draggable.active==null?e:{...e,draggable:{...e.draggable,translate:{x:t.coordinates.x-e.draggable.initialCoordinates.x,y:t.coordinates.y-e.draggable.initialCoordinates.y}}};case it.DragEnd:case it.DragCancel:return{...e,draggable:{...e.draggable,active:null,initialCoordinates:{x:0,y:0},translate:{x:0,y:0}}};case it.RegisterDroppable:{const{element:n}=t,{id:o}=n,r=new Uo(e.droppable.containers);return r.set(o,n),{...e,droppable:{...e.droppable,containers:r}}}case it.SetDroppableDisabled:{const{id:n,key:o,disabled:r}=t,s=e.droppable.containers.get(n);if(!s||o!==s.key)return e;const a=new Uo(e.droppable.containers);return a.set(n,{...s,disabled:r}),{...e,droppable:{...e.droppable,containers:a}}}case it.UnregisterDroppable:{const{id:n,key:o}=t,r=e.droppable.containers.get(n);if(!r||o!==r.key)return e;const s=new Uo(e.droppable.containers);return s.delete(n),{...e,droppable:{...e.droppable,containers:s}}}default:return e}}function W2(e){let{disabled:t}=e;const{active:n,activatorEvent:o,draggableNodes:r}=u.useContext(qo),s=Rr(o),a=Rr(n?.id);return u.useEffect(()=>{if(!t&&!o&&s&&a!=null){if(!la(s)||document.activeElement===s.target)return;const l=r.get(a);if(!l)return;const{activatorNode:d,node:c}=l;if(!d.current&&!c.current)return;requestAnimationFrame(()=>{for(const p of[d.current,c.current]){if(!p)continue;const f=A7(p);if(f){f.focus();break}}})}},[o,t,r,a,s]),null}function gf(e,t){let{transform:n,...o}=t;return e!=null&&e.length?e.reduce((r,s)=>s({transform:r,...o}),n):n}function j2(e){return u.useMemo(()=>({draggable:{...si.draggable,...e?.draggable},droppable:{...si.droppable,...e?.droppable},dragOverlay:{...si.dragOverlay,...e?.dragOverlay}}),[e?.draggable,e?.droppable,e?.dragOverlay])}function H2(e){let{activeNode:t,measure:n,initialRect:o,config:r=!0}=e;const s=u.useRef(!1),{x:a,y:l}=typeof r=="boolean"?{x:r,y:r}:r;sn(()=>{if(!a&&!l||!t){s.current=!1;return}if(s.current||!o)return;const c=t?.node.current;if(!c||c.isConnected===!1)return;const p=n(c),f=Kp(p,o);if(a||(f.x=0),l||(f.y=0),s.current=!0,Math.abs(f.x)>0||Math.abs(f.y)>0){const y=qp(c);y&&y.scrollBy({top:f.y,left:f.x})}},[t,a,l,o,n])}const ai=u.createContext({...Ht,scaleX:1,scaleY:1});var hn;(function(e){e[e.Uninitialized=0]="Uninitialized",e[e.Initializing=1]="Initializing",e[e.Initialized=2]="Initialized"})(hn||(hn={}));const F2=u.memo(function(t){var n,o,r,s;let{id:a,accessibility:l,autoScroll:d=!0,children:c,sensors:p=M2,collisionDetection:f=Y7,measuring:y,modifiers:g,...m}=t;const h=u.useReducer(B2,void 0,O2),[v,b]=h,[x,k]=L7(),[w,_]=u.useState(hn.Uninitialized),C=w===hn.Initialized,{draggable:{active:E,nodes:S,translate:T},droppable:{containers:$}}=v,A=E!=null?S.get(E):null,z=u.useRef({initial:null,translated:null}),D=u.useMemo(()=>{var J;return E!=null?{id:E,data:(J=A?.data)!=null?J:L2,rect:z}:null},[E,A]),O=u.useRef(null),[M,B]=u.useState(null),[N,H]=u.useState(null),L=Bo(m,Object.values(m)),I=ei("DndDescribedBy",a),P=u.useMemo(()=>$.getEnabled(),[$]),F=j2(y),{droppableRects:G,measureDroppableContainers:V,measuringScheduled:Y}=k2(P,{dragging:C,dependencies:[T.x,T.y],config:F.droppable}),Z=x2(S,E),se=u.useMemo(()=>N?ni(N):null,[N]),re=ue(),U=S2(Z,F.draggable.measure);H2({activeNode:E!=null?S.get(E):null,config:re.layoutShiftCompensation,initialRect:U,measure:F.draggable.measure});const ee=sf(Z,F.draggable.measure,U),fe=sf(Z?Z.parentElement:null),K=u.useRef({activatorEvent:null,active:null,activeNode:Z,collisionRect:null,collisions:null,droppableRects:G,draggableNodes:S,draggingNode:null,draggingNodeRect:null,droppableContainers:$,over:null,scrollableAncestors:[],scrollAdjustedTranslate:null}),ne=$.getNodeFor((n=K.current.over)==null?void 0:n.id),me=z2({measure:F.dragOverlay.measure}),Ce=(o=me.nodeRef.current)!=null?o:Z,ze=C?(r=me.rect)!=null?r:ee:null,Se=!!(me.nodeRef.current&&me.rect),qe=E2(Se?null:ee),De=df(Ce?_t(Ce):null),Pe=I2(C?ne??Z:null),Qe=N2(Pe),Me=gf(g,{transform:{x:T.x-qe.x,y:T.y-qe.y,scaleX:1,scaleY:1},activatorEvent:N,active:D,activeNodeRect:ee,containerNodeRect:fe,draggingNodeRect:ze,over:K.current.over,overlayNodeRect:me.rect,scrollableAncestors:Pe,scrollableAncestorRects:Qe,windowRect:De}),Re=se?uo(se,T):null,ve=A2(Pe),te=lf(ve),he=lf(ve,[ee]),Q=uo(Me,te),ie=ze?R7(ze,Me):null,_e=D&&ie?f({active:D,collisionRect:ie,droppableRects:G,droppableContainers:P,pointerCoordinates:Re}):null,ge=q7(_e,"id"),[Ae,Ne]=u.useState(null),Le=Se?Me:uo(Me,he),q=Z7(Le,(s=Ae?.rect)!=null?s:null,ee),ae=u.useRef(null),pe=u.useCallback((J,le)=>{let{sensor:Ie,options:dt}=le;if(O.current==null)return;const pt=S.get(O.current);if(!pt)return;const ot=J.nativeEvent,Oe=new Ie({active:O.current,activeNode:pt,event:ot,options:dt,context:K,onAbort(Ye){if(!S.get(Ye))return;const{onDragAbort:Dt}=L.current,Nt={id:Ye};Dt?.(Nt),x({type:"onDragAbort",event:Nt})},onPending(Ye,Tt,Dt,Nt){if(!S.get(Ye))return;const{onDragPending:wn}=L.current,Zt={id:Ye,constraint:Tt,initialCoordinates:Dt,offset:Nt};wn?.(Zt),x({type:"onDragPending",event:Zt})},onStart(Ye){const Tt=O.current;if(Tt==null)return;const Dt=S.get(Tt);if(!Dt)return;const{onDragStart:Nt}=L.current,xn={activatorEvent:ot,active:{id:Tt,data:Dt.data,rect:z}};Cn.unstable_batchedUpdates(()=>{Nt?.(xn),_(hn.Initializing),b({type:it.DragStart,initialCoordinates:Ye,active:Tt}),x({type:"onDragStart",event:xn}),B(ae.current),H(ot)})},onMove(Ye){b({type:it.DragMove,coordinates:Ye})},onEnd:He(it.DragEnd),onCancel:He(it.DragCancel)});ae.current=Oe;function He(Ye){return async function(){const{active:Dt,collisions:Nt,over:xn,scrollAdjustedTranslate:wn}=K.current;let Zt=null;if(Dt&&wn){const{cancelDrop:Hn}=L.current;Zt={activatorEvent:ot,active:Dt,collisions:Nt,delta:wn,over:xn},Ye===it.DragEnd&&typeof Hn=="function"&&await Promise.resolve(Hn(Zt))&&(Ye=it.DragCancel)}O.current=null,Cn.unstable_batchedUpdates(()=>{b({type:Ye}),_(hn.Uninitialized),Ne(null),B(null),H(null),ae.current=null;const Hn=Ye===it.DragEnd?"onDragEnd":"onDragCancel";if(Zt){const ln=L.current[Hn];ln?.(Zt),x({type:Hn,event:Zt})}})}}},[S]),$e=u.useCallback((J,le)=>(Ie,dt)=>{const pt=Ie.nativeEvent,ot=S.get(dt);if(O.current!==null||!ot||pt.dndKit||pt.defaultPrevented)return;const Oe={active:ot};J(Ie,le.options,Oe)===!0&&(pt.dndKit={capturedBy:le.sensor},O.current=dt,pe(Ie,le))},[S,pe]),be=w2(p,$e);T2(p),sn(()=>{ee&&w===hn.Initializing&&_(hn.Initialized)},[ee,w]),u.useEffect(()=>{const{onDragMove:J}=L.current,{active:le,activatorEvent:Ie,collisions:dt,over:pt}=K.current;if(!le||!Ie)return;const ot={active:le,activatorEvent:Ie,collisions:dt,delta:{x:Q.x,y:Q.y},over:pt};Cn.unstable_batchedUpdates(()=>{J?.(ot),x({type:"onDragMove",event:ot})})},[Q.x,Q.y]),u.useEffect(()=>{const{active:J,activatorEvent:le,collisions:Ie,droppableContainers:dt,scrollAdjustedTranslate:pt}=K.current;if(!J||O.current==null||!le||!pt)return;const{onDragOver:ot}=L.current,Oe=dt.get(ge),He=Oe&&Oe.rect.current?{id:Oe.id,rect:Oe.rect.current,data:Oe.data,disabled:Oe.disabled}:null,Ye={active:J,activatorEvent:le,collisions:Ie,delta:{x:pt.x,y:pt.y},over:He};Cn.unstable_batchedUpdates(()=>{Ne(He),ot?.(Ye),x({type:"onDragOver",event:Ye})})},[ge]),sn(()=>{K.current={activatorEvent:N,active:D,activeNode:Z,collisionRect:ie,collisions:_e,droppableRects:G,draggableNodes:S,draggingNode:Ce,draggingNodeRect:ze,droppableContainers:$,over:Ae,scrollableAncestors:Pe,scrollAdjustedTranslate:Q},z.current={initial:ze,translated:ie}},[D,Z,_e,ie,S,Ce,ze,G,$,Ae,Pe,Q]),m2({...re,delta:T,draggingRect:ie,pointerCoordinates:Re,scrollableAncestors:Pe,scrollableAncestorRects:Qe});const j=u.useMemo(()=>({active:D,activeNode:Z,activeNodeRect:ee,activatorEvent:N,collisions:_e,containerNodeRect:fe,dragOverlay:me,draggableNodes:S,droppableContainers:$,droppableRects:G,over:Ae,measureDroppableContainers:V,scrollableAncestors:Pe,scrollableAncestorRects:Qe,measuringConfiguration:F,measuringScheduled:Y,windowRect:De}),[D,Z,ee,N,_e,fe,me,S,$,G,Ae,V,Pe,Qe,F,Y,De]),oe=u.useMemo(()=>({activatorEvent:N,activators:be,active:D,activeNodeRect:ee,ariaDescribedById:{draggable:I},dispatch:b,draggableNodes:S,over:Ae,measureDroppableContainers:V}),[N,be,D,ee,b,I,S,Ae,V]);return u.createElement(Fp.Provider,{value:k},u.createElement(qo.Provider,{value:oe},u.createElement(ff.Provider,{value:j},u.createElement(ai.Provider,{value:q},c)),u.createElement(W2,{disabled:l?.restoreFocus===!1})),u.createElement(B7,{...l,hiddenTextDescribedById:I}));function ue(){const J=M?.autoScrollEnabled===!1,le=typeof d=="object"?d.enabled===!1:d===!1,Ie=C&&!J&&!le;return typeof d=="object"?{...d,enabled:Ie}:{enabled:Ie}}}),K2=u.createContext(null),hf="button",V2="Draggable";function U2(e){let{id:t,data:n,disabled:o=!1,attributes:r}=e;const s=ei(V2),{activators:a,activatorEvent:l,active:d,activeNodeRect:c,ariaDescribedById:p,draggableNodes:f,over:y}=u.useContext(qo),{role:g=hf,roleDescription:m="draggable",tabIndex:h=0}=r??{},v=d?.id===t,b=u.useContext(v?ai:K2),[x,k]=Qr(),[w,_]=Qr(),C=D2(a,t),E=Bo(n);sn(()=>(f.set(t,{id:t,key:s,node:x,activatorNode:w,data:E}),()=>{const T=f.get(t);T&&T.key===s&&f.delete(t)}),[f,t]);const S=u.useMemo(()=>({role:g,tabIndex:h,"aria-disabled":o,"aria-pressed":v&&g===hf?!0:void 0,"aria-roledescription":m,"aria-describedby":p.draggable}),[o,g,h,v,m,p.draggable]);return{active:d,activatorEvent:l,activeNodeRect:c,attributes:S,isDragging:v,listeners:o?void 0:C,node:x,over:y,setNodeRef:k,setActivatorNodeRef:_,transform:b}}function q2(){return u.useContext(ff)}const G2="Droppable",Y2={timeout:25};function X2(e){let{data:t,disabled:n=!1,id:o,resizeObserverConfig:r}=e;const s=ei(G2),{active:a,dispatch:l,over:d,measureDroppableContainers:c}=u.useContext(qo),p=u.useRef({disabled:n}),f=u.useRef(!1),y=u.useRef(null),g=u.useRef(null),{disabled:m,updateMeasurementsFor:h,timeout:v}={...Y2,...r},b=Bo(h??o),x=u.useCallback(()=>{if(!f.current){f.current=!0;return}g.current!=null&&clearTimeout(g.current),g.current=setTimeout(()=>{c(Array.isArray(b.current)?b.current:[b.current]),g.current=null},v)},[v]),k=ii({callback:x,disabled:m||!a}),w=u.useCallback((S,T)=>{k&&(T&&(k.unobserve(T),f.current=!1),S&&k.observe(S))},[k]),[_,C]=Qr(w),E=Bo(t);return u.useEffect(()=>{!k||!_.current||(k.disconnect(),f.current=!1,k.observe(_.current))},[_,k]),u.useEffect(()=>(l({type:it.RegisterDroppable,element:{id:o,key:s,disabled:n,node:_,rect:y,data:E}}),()=>l({type:it.UnregisterDroppable,key:s,id:o})),[o]),u.useEffect(()=>{n!==p.current.disabled&&(l({type:it.SetDroppableDisabled,id:o,key:s,disabled:n}),p.current.disabled=n)},[o,s,n,l]),{active:a,rect:y,isOver:d?.id===o,node:_,over:d,setNodeRef:C}}function J2(e){let{animation:t,children:n}=e;const[o,r]=u.useState(null),[s,a]=u.useState(null),l=Rr(n);return!n&&!o&&l&&r(l),sn(()=>{if(!s)return;const d=o?.key,c=o?.props.id;if(d==null||c==null){r(null);return}Promise.resolve(t(c,s)).then(()=>{r(null)})},[t,o,s]),u.createElement(u.Fragment,null,n,o?u.cloneElement(o,{ref:a}):null)}const Z2={x:0,y:0,scaleX:1,scaleY:1};function Q2(e){let{children:t}=e;return u.createElement(qo.Provider,{value:pf},u.createElement(ai.Provider,{value:Z2},t))}const R2={position:"fixed",touchAction:"none"},eI=e=>la(e)?"transform 250ms ease":void 0,tI=u.forwardRef((e,t)=>{let{as:n,activatorEvent:o,adjustScale:r,children:s,className:a,rect:l,style:d,transform:c,transition:p=eI}=e;if(!l)return null;const f=r?c:{...c,scaleX:1,scaleY:1},y={...R2,width:l.width,height:l.height,top:l.top,left:l.left,transform:jo.Transform.toString(f),transformOrigin:r&&o?F7(o,l):void 0,transition:typeof p=="function"?p(o):p,...d};return u.createElement(n,{className:a,style:y,ref:t},s)}),nI={duration:250,easing:"ease",keyframes:e=>{let{transform:{initial:t,final:n}}=e;return[{transform:jo.Transform.toString(t)},{transform:jo.Transform.toString(n)}]},sideEffects:(e=>t=>{let{active:n,dragOverlay:o}=t;const r={},{styles:s,className:a}=e;if(s!=null&&s.active)for(const[l,d]of Object.entries(s.active))d!==void 0&&(r[l]=n.node.style.getPropertyValue(l),n.node.style.setProperty(l,d));if(s!=null&&s.dragOverlay)for(const[l,d]of Object.entries(s.dragOverlay))d!==void 0&&o.node.style.setProperty(l,d);return a!=null&&a.active&&n.node.classList.add(a.active),a!=null&&a.dragOverlay&&o.node.classList.add(a.dragOverlay),function(){for(const[d,c]of Object.entries(r))n.node.style.setProperty(d,c);a!=null&&a.active&&n.node.classList.remove(a.active)}})({styles:{active:{opacity:"0"}}})};function oI(e){let{config:t,draggableNodes:n,droppableContainers:o,measuringConfiguration:r}=e;return Zr((s,a)=>{if(t===null)return;const l=n.get(s);if(!l)return;const d=l.node.current;if(!d)return;const c=uf(a);if(!c)return;const{transform:p}=_t(a).getComputedStyle(a),f=Vp(p);if(!f)return;const y=typeof t=="function"?t:rI(t);return Qp(d,r.draggable.measure),y({active:{id:s,data:l.data,node:d,rect:r.draggable.measure(d)},draggableNodes:n,dragOverlay:{node:a,rect:r.dragOverlay.measure(c)},droppableContainers:o,measuringConfiguration:r,transform:f})})}function rI(e){const{duration:t,easing:n,sideEffects:o,keyframes:r}={...nI,...e};return s=>{let{active:a,dragOverlay:l,transform:d,...c}=s;if(!t)return;const p={x:l.rect.left-a.rect.left,y:l.rect.top-a.rect.top},f={scaleX:d.scaleX!==1?a.rect.width*d.scaleX/l.rect.width:1,scaleY:d.scaleY!==1?a.rect.height*d.scaleY/l.rect.height:1},y={x:d.x-p.x,y:d.y-p.y,...f},g=r({...c,active:a,dragOverlay:l,transform:{initial:d,final:y}}),[m]=g,h=g[g.length-1];if(JSON.stringify(m)===JSON.stringify(h))return;const v=o?.({active:a,dragOverlay:l,...c}),b=l.node.animate(g,{duration:t,easing:n,fill:"forwards"});return new Promise(x=>{b.onfinish=()=>{v?.(),x()}})}}let yf=0;function iI(e){return u.useMemo(()=>{if(e!=null)return yf++,yf},[e])}const sI=u.memo(e=>{let{adjustScale:t=!1,children:n,dropAnimation:o,style:r,transition:s,modifiers:a,wrapperElement:l="div",className:d,zIndex:c=999}=e;const{activatorEvent:p,active:f,activeNodeRect:y,containerNodeRect:g,draggableNodes:m,droppableContainers:h,dragOverlay:v,over:b,measuringConfiguration:x,scrollableAncestors:k,scrollableAncestorRects:w,windowRect:_}=q2(),C=u.useContext(ai),E=iI(f?.id),S=gf(a,{activatorEvent:p,active:f,activeNodeRect:y,containerNodeRect:g,draggingNodeRect:v.rect,over:b,overlayNodeRect:v.rect,scrollableAncestors:k,scrollableAncestorRects:w,transform:C,windowRect:_}),T=va(y),$=oI({config:o,draggableNodes:m,droppableContainers:h,measuringConfiguration:x}),A=T?v.setRef:void 0;return u.createElement(Q2,null,u.createElement(J2,{animation:$},f&&E?u.createElement(tI,{key:E,id:f.id,ref:A,as:l,activatorEvent:p,adjustScale:t,className:d,transition:s,rect:T,style:{zIndex:c,...r},transform:S},n):null))});function Go(e,t){return!t||!Array.isArray(t.draggable)?!1:t.draggable.includes(e)}function aI({draggedComponentName:e,targetContainerName:t,nesting:n}){if(!n||!Go(e,n))return!1;const o=n.containers;if(!o||typeof o!="object")return!1;const r=o[t];return!r||!Array.isArray(r.accepts)?!1:r.accepts.includes(e)}function bf(e,t){if(!t?.containers)return!1;const n=t.containers[e];return!!(n&&Array.isArray(n.accepts)&&n.accepts.length>0)}function lI(e,t,n,o,r){if(!n||n.length===0)return{parentPath:[],index:r};if(e&&bf(e,o))return{parentPath:n,index:t};const s=n.slice(0,-1),a=n[n.length-1]??0;return{parentPath:s,index:a+1}}function mf(e,t){return e===t||e.startsWith(`${t}.`)}function vf(e,t){if(e==="")return/^\d+$/.test(t)?Number(t):null;const n=`${e}.children.`;if(!t.startsWith(n))return null;const o=t.slice(n.length);return/^\d+$/.test(o)?Number(o):null}function dI(e,t){const n=[];return e.querySelectorAll("[data-editor-path]").forEach(o=>{if(o.hasAttribute("data-dnd-placeholder")||o.closest("[data-dnd-placeholder]"))return;const r=o.dataset.editorPath;typeof r=="string"&&n.push({path:t(r),domPath:r,rect:o.getBoundingClientRect()})}),n}const cI=16;function uI(e){const{frameEl:t,draggedPath:n,acceptsContainer:o,allowsNestSlot:r,includeContainer:s}=e,a=e.normalizeContainerPath??(g=>g),l=t.getBoundingClientRect(),d=dI(t,a),c=new Map;for(const g of d)c.has(g.path)||c.set(g.path,g.domPath);const p=a(n),f=new Set([""]);for(const g of d){const m=fI(g.path);m!==null&&f.add(m)}for(const g of d)f.add(g.path);const y=[];for(const g of f){const m=g==="",h=m?null:c.get(g)??g,v=m?t:t.querySelector(`[data-editor-path="${wf(h)}"]`);if(!v&&!m||!m&&mf(g,p))continue;const b=g;if(!m&&s&&!s(b))continue;const x=C=>{const E=c.get(C);if(!E||typeof window>"u")return!1;const S=t.querySelector(`[data-editor-path="${wf(E)}"]`);return S?(window.getComputedStyle(S).display??"").toLowerCase()==="contents":!1},k=d.filter(C=>vf(g,C.path)!==null).filter(C=>!mf(C.path,p)).filter(C=>!x(C.path)).map(C=>({origIndex:vf(g,C.path),rect:pI(C.rect,C.path,d)})).filter(C=>C.rect.width>0||C.rect.height>0).sort((C,E)=>C.origIndex-E.origIndex),w=Dp(m?t:v).kind,_=w==="flex_row_single"||w==="flex_row_wrap"||w==="grid_single_row"||w==="grid_2d";if(k.length>0){if(o(b)){k.forEach(E=>{y.push(_f(b,E.origIndex,E.rect,l,_,"before"))});const C=k[k.length-1];y.push(_f(b,C.origIndex+1,C.rect,l,_,"after"))}}else if(!m&&r(b)&&v){const C=v.getBoundingClientRect();C.width>0&&C.height>0&&y.push({id:`slot:${b}:0`,kind:"nest",containerPath:b,index:0,box:xf(C,l),orientation:"area"})}else m&&y.push({id:"slot::0",kind:"gap",containerPath:"",index:0,box:xf(l,l),orientation:"area"})}return y.filter(g=>ao(g.box,t))}function _f(e,t,n,o,r,s){const a=cI;let l;if(r)l={left:(s==="before"?n.left:n.right)-a/2,top:n.top,width:a,height:n.height};else{const d=s==="before"?n.top:n.bottom;l={left:n.left,top:d-a/2,width:n.width,height:a}}return{id:`slot:${e}:${t}`,kind:"gap",containerPath:e,index:t,box:{left:l.left-o.left,top:l.top-o.top,width:l.width,height:l.height,scale:1},orientation:r?"vertical":"horizontal"}}function pI(e,t,n){if(e.width>0||e.height>0)return e;const o=`${t}.children.`;let r=1/0,s=1/0,a=-1/0,l=-1/0;for(const d of n)d.path.startsWith(o)&&(d.rect.width===0&&d.rect.height===0||(r=Math.min(r,d.rect.left),s=Math.min(s,d.rect.top),a=Math.max(a,d.rect.right),l=Math.max(l,d.rect.bottom)));return r===1/0?e:{left:r,top:s,width:a-r,height:l-s,right:a,bottom:l,x:r,y:s,toJSON:()=>({})}}function xf(e,t){return{left:e.left-t.left,top:e.top-t.top,width:e.width,height:e.height,scale:1}}function fI(e){if(e==="")return null;const t=e.lastIndexOf(".children.");return t<0?"":e.slice(0,t)}function gI(e){if(!e.startsWith("slot:"))return null;const t=e.slice(5),n=t.lastIndexOf(":");if(n<0)return null;const o=t.slice(0,n),r=Number(t.slice(n+1));return Number.isFinite(r)?{containerPath:o,index:r}:null}function wf(e){return typeof CSS<"u"&&typeof CSS.escape=="function"?CSS.escape(e):e.replace(/[^a-zA-Z0-9_\-.]/g,t=>`\\${t}`)}function _a(e){return typeof e=="string"&&e.length>0?e:null}function xa(e,t){return t&&e.startsWith(`${t}.`)?t:e}function kf(e){if(typeof e!="string")return null;const t=gI(e);return t?{containerPath:t.containerPath,index:t.index}:null}function wa(e,t){return t?e.startsWith(`${t}.iteration.`):!1}function ka(e,t,n){return t?n==="iteration_item"?wa(e,t):e===t||e.startsWith(`${t}.`):!1}function hI(e){const{frameEl:t,nesting:n,editMode:o,currentExtensionId:r,editableRootPath:s,components:a,patchLayout:l,pushHistory:d,onMovePath:c,selectedPath:p,showBranchBoundaryHint:f}=e,y=(o==="iteration_item"||o==="modal")&&s&&s.length>0?at(s):null,[g,m]=u.useState(null),[h,v]=u.useState(null),[b,x]=u.useState(null),k=u.useRef({components:a,nesting:n,editMode:o,currentExtensionId:r,selectedPath:p,editableRootSourcePath:y});k.current={components:a,nesting:n,editMode:o,currentExtensionId:r,selectedPath:p,editableRootSourcePath:y};const w=u.useRef(null),_=u.useCallback(M=>({children:M}),[]),C=u.useCallback((M,B)=>{const N=We(_(M),B);return N?typeof N.name=="string"?N.name:typeof N.type=="string"?N.type:null:null},[_]),E=u.useCallback(M=>B=>{const N=k.current;if(B==="")return Go(M,N.nesting);const H=Ze(B),L=We(_(N.components),H);if(!L)return!1;const I=typeof L.name=="string"?L.name:typeof L.type=="string"?L.type:"";if(!I)return!1;const P=!!N.editableRootSourcePath&&(B===N.editableRootSourcePath||B.startsWith(`${N.editableRootSourcePath}.`));if(N.editableRootSourcePath&&!P)return!1;if(!P){const F=Sa(_(N.components),H);if(gn(L,N.editMode,N.currentExtensionId,F)!=="none")return!1}return bf(I,N.nesting)?aI({draggedComponentName:M,targetContainerName:I,nesting:N.nesting}):!1},[_]),S=u.useCallback(M=>{if(M==="")return!0;const B=k.current,N=Ze(M),H=w.current;if(H&&H.length>0){const P=H.slice(0,-1);if(li(N,P))return!0}const L=We(_(B.components),N);return L?(typeof L.name=="string"?L.name:typeof L.type=="string"?L.type:"").length>0:!1},[_]),T=u.useCallback(M=>{const B=E(M),N=k.current,H=w.current,L=H?H.slice(0,-1):null,I=new Set;if(L)for(let V=L.length;V>=0;V--)I.add(di(L.slice(0,V)));const P=di(H??[]),F=V=>!!H&&(V===P||V.startsWith(`${P}.`)),G=(V,Y)=>{for(let Z=0;ZV===""?!0:F(V)?!1:I.has(V),allowsNestSlot:V=>{if(!B(V))return!1;if(L){const Y=Ze(V);if(li(Y,L))return!1}return S(V)}}},[E,S,_]),$=u.useCallback(M=>{const B=_a(M.active.id),N=k.current;if(!B)return;const H=xa(B,N.selectedPath),L=Ze(H),I=We(_(N.components),L),P=I&&typeof I.name=="string"?I.name:I&&typeof I.type=="string"?I.type:null;m(H),v(P),x(null),w.current=I?L:null;let F="allowed";if(!P||!Go(P,N.nesting))F="denied_no_draggable";else if(I){const G=Sa(_(N.components),L),V=gn(I,N.editMode,N.currentExtensionId,G);N.editableRootSourcePath&&!ka(H,N.editableRootSourcePath,N.editMode)||Xi(G)&&!wa(H,N.editableRootSourcePath)?F="denied_data_bound":V==="base"||V==="partial"?F="denied_base_locked":(V==="extension"||V==="extension_point")&&(F="denied_extension_locked")}$n({source:"drag",draggedComponentName:P,targetContainerName:null,targetContainerPath:null,decision:F,result:F==="allowed"?"completed":"denied",timestamp:Date.now()})},[_]),A=u.useCallback(M=>{if(!t)return;const B=_a(M.active.id);if(!B)return;const N=h;if(!N){x(null);return}const H=k.current,L=xa(B,H.selectedPath),I=Ze(L),P=We(_(H.components),I);if(!P||!Go(N,H.nesting)){x(null);return}const F=Sa(_(H.components),I);if(H.editableRootSourcePath&&!ka(L,H.editableRootSourcePath,H.editMode)){x(null);return}const G=gn(P,H.editMode,H.currentExtensionId,F),V=wa(L,H.editableRootSourcePath);if(Xi(F)&&!V||G!=="none"&&G!=="data_bound"&&!V){x(null);return}x(kf(M.over?.id))},[t,h,_]),z=u.useCallback(()=>{m(null),v(null),x(null),w.current=null},[]),D=u.useCallback(M=>{const B=_a(M.active.id),N=h,H=k.current,L=B?xa(B,H.selectedPath):null;if(!t||!L||!N){z();return}const I=kf(M.over?.id),P=Ze(L);if(!I){$n({source:"drag",draggedComponentName:N,targetContainerName:null,targetContainerPath:null,decision:"denied_no_accepts",result:"cancelled",timestamp:Date.now()}),z();return}const F=I.containerPath===""?[]:Ze(I.containerPath),G=I.containerPath===""?null:C(H.components,F);if(!yI(P.slice(0,-1),F)){$n({source:"drag",draggedComponentName:N,targetContainerName:G,targetContainerPath:I.containerPath||null,decision:"denied_responsive_branch_boundary",result:"denied",timestamp:Date.now()}),f?.(),z();return}if(H.editableRootSourcePath&&!ka(I.containerPath,H.editableRootSourcePath,H.editMode)){$n({source:"drag",draggedComponentName:N,targetContainerName:G,targetContainerPath:I.containerPath||null,decision:"denied_data_bound",result:"denied",timestamp:Date.now()}),z();return}const V=P.slice(0,-1),Y=P[P.length-1]??-1;if(li(V,F)&&(I.index===Y||I.index===Y+1)){$n({source:"drag",draggedComponentName:N,targetContainerName:G,targetContainerPath:I.containerPath||null,decision:"allowed",insertionIndex:I.index,result:"cancelled",timestamp:Date.now()}),z();return}const re=li(V,F),U=I.index;let ee=[];l(me=>{const Se=Db({children:me},P,F,U).children??[];return ee=Se,Se}),d(ee,`move ${N}`);const fe=id(F,P),K=re&&Y{z()},[z]);return{activeDragPath:g,activeDragName:h,activeDropZone:b,onDragStart:$,onDragMove:A,onDragEnd:D,onDragCancel:O,buildSlotPredicates:T}}function Sa(e,t){const n=[];let o=e,r=Array.isArray(e.children)?e.children:[];for(let s=0;s0?at(s):null,{t:m}=tt(),[h,v]=u.useState(!1),b=u.useRef(null),x=u.useCallback(()=>{v(!0),b.current&&clearTimeout(b.current),b.current=setTimeout(()=>v(!1),2600)},[]);u.useEffect(()=>()=>{b.current&&clearTimeout(b.current)},[]);const k=hI({frameEl:t,nesting:n,editMode:o,currentExtensionId:r,editableRootPath:s,components:a,patchLayout:l,pushHistory:d,onMovePath:p,selectedPath:y,showBranchBoundaryHint:x}),w=j7(W7(ha,{activationConstraint:{distance:8}})),[_,C]=u.useState([]),E=u.useCallback(()=>{if(!t){C([]);return}if(k.activeDragPath!==null)return;const L={children:a},I=t.querySelectorAll("[data-editor-path]"),P=[],F=new Map;I.forEach(G=>{const V=G.dataset.editorPath;if(typeof V!="string"||o!=="iteration_item"&&g&&!vI(V,g,o))return;const Y=bI(V);if(Y!==null&&!Yo(V,g)){if(/\.iteration\.\d+$/.test(V)){const K=so(G,t);if(K&&ao(K,t)){const ne=F.get(Y)??[];ne.push(K),F.set(Y,ne)}}return}if(Yo(V,g)&&/\.iteration\.\d+$/.test(V))return;const Z=Ze(V),se=We(L,Z);if(!se||se.iteration&&se.iteration.source!==void 0)return;const re=typeof se.name=="string"?se.name:typeof se.type=="string"?se.type:null;if(!re||!Go(re,n))return;const U=Cf(L,Z);if(Xi(U)&&!Yo(V,g))return;const ee=gn(se,o,r,U);if(ee!=="none"&&ee!=="data_bound"&&!Yo(V,g))return;const fe=so(G,t);fe&&ao(fe,t)&&P.push({path:V,name:re,box:fe})});for(const[G,V]of F){const Y=Ze(G),Z=We(L,Y);if(!Z)continue;const se=typeof Z.name=="string"?Z.name:typeof Z.type=="string"?Z.type:null;if(!se)continue;const re=Cf(L,Y),U=gn(Z,o,r,re);if(U!=="none"&&U!=="data_bound")continue;const ee=_I(V);ee&&ao(ee,t)&&P.push({path:G,name:se,box:ee,isIterationGroup:!0})}C(P)},[t,a,n,o,r,g,k.activeDragPath]);u.useEffect(()=>{E()},[E]),u.useEffect(()=>{if(t)return $p(E,t)},[t,E]);const S=k.activeDragPath!==null,T=k.activeDragName,$=k.activeDragPath,[A,z]=u.useState(null),D=u.useRef(null);u.useEffect(()=>{if(!$||!t){$||(D.current=null,z(null));return}if(D.current===$)return;D.current=$;const L=t.querySelector(`[data-editor-path="${Sf($)}"]`);if(L){const re=L.getBoundingClientRect();z({node:L.cloneNode(!0),width:re.width,height:re.height});return}const I=Array.from(t.querySelectorAll(`[data-editor-path^="${Sf($)}.iteration."]`)).filter(re=>/\.iteration\.\d+$/.test(re.dataset.editorPath??""));if(I.length===0){z(null);return}let P=1/0,F=1/0,G=-1/0,V=-1/0;for(const re of I){const U=re.getBoundingClientRect();P=Math.min(P,U.left),F=Math.min(F,U.top),G=Math.max(G,U.right),V=Math.max(V,U.bottom)}const Y=G-P,Z=V-F,se=document.createElement("div");se.style.position="relative",se.style.width=`${Y}px`,se.style.height=`${Z}px`;for(const re of I){const U=re.getBoundingClientRect(),ee=re.cloneNode(!0);ee.style.position="absolute",ee.style.left=`${U.left-P}px`,ee.style.top=`${U.top-F}px`,ee.style.width=`${U.width}px`,ee.style.height=`${U.height}px`,ee.style.margin="0",se.appendChild(ee)}z({node:se,width:Y,height:Z})},[$,t]);const[O,M]=u.useState([]),B=u.useRef(null),N=u.useRef(k.buildSlotPredicates);if(N.current=k.buildSlotPredicates,u.useEffect(()=>{if(!$||!T||!t){B.current=null,M(F=>F.length===0?F:[]);return}if(B.current===$)return;B.current=$;const{acceptsContainer:L,allowsNestSlot:I,includeContainer:P}=N.current(T);M(uI({frameEl:t,draggedPath:$,acceptsContainer:L,allowsNestSlot:I,includeContainer:P,normalizeContainerPath:F=>mI(F)}))},[$,T,t]),!t)return null;const H=k.activeDropZone?`slot:${k.activeDropZone.containerPath}:${k.activeDropZone.index}`:null;return i.jsxs(F2,{sensors:w,collisionDetection:J7,onDragStart:k.onDragStart,onDragMove:k.onDragMove,onDragEnd:k.onDragEnd,onDragCancel:k.onDragCancel,children:[_.map(L=>i.jsx(SI,{path:L.path,name:L.name,box:L.box,dimmed:S&&k.activeDragPath!==L.path,isIterationGroup:L.isIterationGroup,onSelect:c,onRequestInlineEdit:f},L.path)),S&&O.map(L=>i.jsx(kI,{slot:L,active:L.id===H},L.id)),Cn.createPortal(i.jsx(sI,{dropAnimation:null,zIndex:Pp,children:T?A?i.jsx(wI,{name:T,node:A.node,width:A.width,height:A.height}):i.jsx("div",{className:"g7le-dnd-drag-ghost","data-testid":"g7le-dnd-drag-ghost",style:{padding:"4px 10px",background:"rgba(79,70,229,0.95)",color:"#fff",fontSize:12,fontWeight:600,borderRadius:4,pointerEvents:"none",whiteSpace:"nowrap"},children:`<${T}>`}):null}),document.body),h&&Cn.createPortal(i.jsxs("div",{"data-testid":"g7le-branch-boundary-hint",role:"status",style:{position:"absolute",left:"50%",top:12,transform:"translateX(-50%)",background:"rgba(245,158,11,0.97)",color:"#fff",fontSize:13,fontWeight:600,padding:"8px 16px",borderRadius:8,boxShadow:"0 4px 14px rgba(0,0,0,0.2)",pointerEvents:"none",whiteSpace:"nowrap",zIndex:Pp},children:["⚠️ ",m("layout_editor.overlay.branch_boundary_denied")]}),t)]})}function wI(e){const{node:t,width:n,height:o}=e,r=u.useRef(null);return u.useEffect(()=>{const s=r.current;if(s)return s.replaceChildren(),s.appendChild(t.cloneNode(!0)),()=>{s.replaceChildren()}},[t]),i.jsx("div",{ref:r,className:"g7le-dnd-drag-ghost","data-testid":"g7le-dnd-drag-ghost",style:{width:n,height:o,opacity:.7,pointerEvents:"none",boxShadow:"0 6px 18px rgba(0,0,0,0.25)",borderRadius:4,overflow:"hidden",cursor:"grabbing"}})}function kI(e){const{slot:t,active:n}=e,{setNodeRef:o}=X2({id:t.id}),r=t.orientation!=="area";return i.jsx("div",{ref:o,"data-testid":`g7le-dnd-slot-${t.id}`,"data-dnd-slot-id":t.id,style:{position:"absolute",left:t.box.left,top:t.box.top,width:t.box.width,height:t.box.height,pointerEvents:"auto",zIndex:h7,boxSizing:"border-box",...r?{background:n?"rgba(79,70,229,0.15)":"transparent",...n?t.orientation==="vertical"?{borderLeft:"2px solid #4f46e5"}:{borderTop:"2px solid #4f46e5"}:{}}:{border:n?"2px dashed #4f46e5":"1px dashed transparent",background:n?"rgba(79,70,229,0.08)":"transparent",borderRadius:4}}})}function Sf(e){return typeof CSS<"u"&&typeof CSS.escape=="function"?CSS.escape(e):e.replace(/[^a-zA-Z0-9_\-.]/g,t=>`\\${t}`)}function SI(e){const{path:t,box:n,dimmed:o,isIterationGroup:r,onSelect:s,onRequestInlineEdit:a}=e,{attributes:l,listeners:d,setNodeRef:c,isDragging:p}=U2({id:t}),f=(t.match(/\.children\./g)||[]).length,y=y7(f);return i.jsx("div",{ref:c,"data-testid":`g7le-dnd-handle-${t}`,"data-dnd-handle-path":t,"data-dnd-iteration-group":r?"true":void 0,...d,...l,onClick:g=>{if(g.stopPropagation(),!r&&typeof document<"u"&&typeof document.elementsFromPoint=="function"){const m=document.elementsFromPoint(g.clientX,g.clientY);for(const h of m){const b=h.closest?.("[data-editor-path]")?.dataset?.editorPath;if(typeof b=="string"&&(b===t||b.startsWith(`${t}.`))){s(b);return}}}s(t)},onDoubleClick:g=>{g.stopPropagation(),g.preventDefault(),a?.(t)},style:{position:"absolute",left:n.left,top:n.top,width:n.width,height:n.height,cursor:"grab",pointerEvents:"auto",background:p?"rgba(79,70,229,0.06)":"transparent",...r?{border:"1px dashed rgba(79,70,229,0.5)",borderRadius:4}:{},opacity:o?.6:1,zIndex:y,touchAction:"none"}})}function Cf(e,t){const n=[];let o=e,r=Array.isArray(e.children)?e.children:[];for(let s=0;s0?[o]:[];const r=[],s=(a,l)=>{a.forEach((d,c)=>{const p=[...l,c];if(!Ji(d,t,n)){r.push(p);return}const y=Array.isArray(d.children)?d.children:[];y.length>0&&s(y,p)})};return s(e,[]),r}function $I(e){return at(e)}function ci(e){return typeof CSS<"u"&&typeof CSS.escape=="function"?CSS.escape(e):e.replace(/["\\]/g,"\\$&")}function EI({frameEl:e,components:t,editMode:n,currentExtensionId:o,editableRootPath:r}){const[s,a]=u.useState(null),[l]=u.useState(()=>`g7le-dim-mask-${Math.floor(Math.random()*1e9)}`);if(u.useEffect(()=>{if(!e){a(null);return}let g=0;const m=()=>{const x=CI(t,n,o,r);if(x.length===0){a(null);return}const k=e.getBoundingClientRect(),w={left:0,top:0,width:k.width,height:k.height},_=[];for(const C of x){const E=$I(C),S=e.querySelector(`[data-editor-path="${ci(E)}"]`);let T=null;if(S){const $=S.getBoundingClientRect();$.width>0&&$.height>0&&(T={left:$.left,top:$.top,width:$.width,height:$.height})}if(!T){const $=Array.from(e.querySelectorAll(`[data-editor-path^="${ci(E)}.iteration."], [data-editor-path^="${ci(E)}.sortable."], [data-editor-path^="${ci(E)}.children."]`));let A=1/0,z=1/0,D=-1/0,O=-1/0;for(const M of $){const B=M.getBoundingClientRect();B.width<=0||B.height<=0||(A=Math.min(A,B.left),z=Math.min(z,B.top),D=Math.max(D,B.right),O=Math.max(O,B.bottom))}if(D>A&&O>z&&(T={left:A,top:z,width:D-A,height:O-z}),T&&$.length>0){const M=$[0].closest('[role="dialog"]');if(M&&e.contains(M)){const B=M.getBoundingClientRect();B.width>0&&B.height>0&&(T={left:B.left,top:B.top,width:B.width,height:B.height})}}}T&&_.push({left:T.left-k.left,top:T.top-k.top,width:T.width,height:T.height})}if(_.length===0){a(null);return}a({frame:w,holes:_})},h=()=>{g&&cancelAnimationFrame(g),g=requestAnimationFrame(m)};h();const v=typeof ResizeObserver<"u"?new ResizeObserver(h):null;v?.observe(e);const b=typeof MutationObserver<"u"?new MutationObserver(h):null;return b?.observe(e,{childList:!0,subtree:!0,attributes:!0}),window.addEventListener("scroll",h,!0),window.addEventListener("resize",h),()=>{g&&cancelAnimationFrame(g),v?.disconnect(),b?.disconnect(),window.removeEventListener("scroll",h,!0),window.removeEventListener("resize",h)}},[e,t,n,o,r?.join(".")??""]),!s||s.holes.length===0)return null;const{frame:d,holes:c}=s,p=n==="modal"||n==="iteration_item";let f=[];if(p){let g=1/0,m=1/0,h=-1/0,v=-1/0;for(const C of c)g=Math.min(g,C.left),m=Math.min(m,C.top),h=Math.max(h,C.left+C.width),v=Math.max(v,C.top+C.height);const b=(C,E,S)=>Math.max(E,Math.min(S,C)),x=b(g,0,d.width),k=b(m,0,d.height),w=b(h,0,d.width),_=b(v,0,d.height);f=[{left:0,top:0,width:d.width,height:k},{left:0,top:_,width:d.width,height:d.height-_},{left:0,top:k,width:x,height:_-k},{left:w,top:k,width:d.width-w,height:_-k}].filter(C=>C.width>0&&C.height>0)}const y=g=>{g.preventDefault(),g.stopPropagation()};return i.jsxs(i.Fragment,{children:[f.map((g,m)=>i.jsx("div",{className:"g7le-source-lock-block","data-testid":"g7le-source-lock-block",onClickCapture:y,onPointerDownCapture:y,onMouseDownCapture:y,onDragStartCapture:y,style:{position:"absolute",left:g.left,top:g.top,width:g.width,height:g.height,zIndex:Lp,pointerEvents:"auto",cursor:"not-allowed",background:"transparent"}},m)),i.jsxs("svg",{className:"g7le-source-lock-dim","data-testid":"g7le-source-lock-dim",width:d.width,height:d.height,style:{position:"absolute",left:0,top:0,width:d.width,height:d.height,zIndex:Lp,pointerEvents:"none"},children:[i.jsx("defs",{children:i.jsxs("mask",{id:l,children:[i.jsx("rect",{x:0,y:0,width:d.width,height:d.height,fill:"white"}),c.map((g,m)=>i.jsx("rect",{"data-testid":"g7le-source-lock-dim-hole",x:g.left,y:g.top,width:g.width,height:g.height,rx:4,fill:"black"},m))]})}),i.jsx("rect",{"data-testid":"g7le-source-lock-dim-box",x:0,y:0,width:d.width,height:d.height,fill:"rgba(15, 23, 42, 0.45)",mask:`url(#${l})`})]})]})}function $f(e){const t={type:e.type??"basic",name:e.name},n={};if(e.props)for(const[o,r]of Object.entries(e.props))o!=="children"&&r?.default!==void 0&&(n[o]=r.default);return Object.keys(n).length>0&&(t.props=n),t}function II(e){if(!e?.components)return[];const t=[];for(const[n,o]of Object.entries(e.components))if(Array.isArray(o))for(const r of o)t.push({...r,type:r.type??n});return t}function AI(e,t,n,o,r){const s=II(e);let a=null;if(n&&t?.containers){const l=t.containers[n];l&&Array.isArray(l.accepts)?a=new Set(l.accepts):a=new Set}else!n&&t?.draggable&&(a=new Set(t.draggable));return s.filter(l=>{if(a&&!a.has(l.name))return!1;if(o){const d=o.toLowerCase(),c=r(l).toLowerCase(),p=l.name.toLowerCase();if(!c.includes(d)&&!p.includes(d)&&!`<${p}>`.includes(d)&&!(l.description??"").toLowerCase().includes(d))return!1}return!0})}function TI(e,t){const n={key:"all",label:"layout_editor.palette.category.all",entries:e,isI18nKey:!0};if(t?.groups&&t.groups.length>0){const a=new Map(e.map(p=>[p.name,p])),l=new Set,d=[];for(const p of t.groups){const f=[];for(const y of p.components??[]){const g=a.get(y);g&&(f.push(g),l.add(y))}f.length!==0&&d.push({key:p.kind??p.label,label:p.label,entries:f,isI18nKey:p.label.startsWith("$t:")||/^[a-z_.]+$/i.test(p.label)})}const c=e.filter(p=>!l.has(p.name));return c.length>0&&d.push({key:"other",label:"layout_editor.palette.category.other",entries:c,isI18nKey:!0}),[n,...d]}const o=new Map;for(const a of e){const l=(a.type??"basic").toLowerCase(),d=o.get(l)??[];d.push(a),o.set(l,d)}const r=["basic","composite","layout"],s=[];for(const a of r){const l=o.get(a);l&&l.length>0&&(s.push({key:a,label:`layout_editor.palette.category.fallback.${a}`,entries:l,isI18nKey:!0}),o.delete(a))}for(const[a,l]of o.entries())s.push({key:a,label:a,entries:l,isI18nKey:!1});return[n,...s]}function Ef(e){const{manifest:t,nesting:n,componentPalette:o,targetParentPath:r,targetContainerName:s,onInsert:a,insertionIndex:l,onClose:d,editorTemplateId:c,editorLocale:p}=e,{t:f,translationEngine:y}=tt(),g=u.useMemo(()=>{if(!c||!y)return f;const A=p??null;return z=>{const D={templateId:c,locale:A??"ko"},O=y.translate(z,D);return O!==z?O:f(z)}},[c,p,y,f]),[m,h]=u.useState(""),[v,b]=u.useState("all"),x=o?.entries??null,k=u.useMemo(()=>A=>{const z=x?.[A.name];return z?.label?ui(z.label,g):A.description??A.name},[x,g]),w=u.useMemo(()=>AI(t,n,s,m,k),[t,n,s,m,k]),_=u.useMemo(()=>TI(w,o??null),[w,o]),C=_.find(A=>A.key===v)??_[0]??null,E=!n||!n.draggable&&!n.containers,S=w.length===0,T=C?.entries??[],$=A=>{const z=x?.[A.name],D=!!z?.defaultNode&&typeof z.defaultNode=="object";if(z?.requiresDefaultNode&&!D)return;const O=D?JSON.parse(JSON.stringify(z.defaultNode)):$f(A),M=r??[];a(O,M,l),$n({source:"palette",draggedComponentName:A.name,targetContainerName:s,targetContainerPath:M.length===0?null:M.join("/"),decision:"allowed",insertionIndex:l,result:"completed",timestamp:Date.now()}),d()};return i.jsxs("div",{className:"g7le-palette","data-testid":"g7le-palette",style:MI,children:[i.jsxs("div",{style:LI,children:[i.jsx("strong",{style:{fontSize:16},children:f("layout_editor.palette.title")}),i.jsx("button",{type:"button",onClick:d,"aria-label":"close","data-testid":"g7le-palette-close",style:PI,children:"×"})]}),i.jsxs("div",{style:OI,children:[i.jsx("aside",{style:BI,"data-testid":"g7le-palette-sidebar",children:i.jsx("nav",{children:i.jsx("ul",{style:WI,children:_.map(A=>{const z=C?.key===A.key,D=A.isI18nKey?ui(A.label,g):A.label;return i.jsx("li",{children:i.jsxs("button",{type:"button","data-testid":`g7le-palette-category-${A.key}`,"data-active":z?"true":"false",onClick:()=>b(A.key),style:{...jI,background:z?"#eff6ff":"transparent",color:z?"#1d4ed8":"#334155",fontWeight:z?600:500},children:[i.jsx("span",{children:D}),i.jsx("span",{style:VI,children:A.entries.length})]})},A.key)})})})}),i.jsxs("main",{style:HI,children:[i.jsx("input",{type:"text",placeholder:f("layout_editor.palette.search_placeholder"),value:m,onChange:A=>h(A.target.value),"data-testid":"g7le-palette-search",style:FI}),i.jsx("div",{style:KI,children:E?i.jsx("div",{style:If,"data-testid":"g7le-palette-spec-missing",children:f("layout_editor.palette.spec_missing")}):S||T.length===0?i.jsx("div",{style:If,"data-testid":"g7le-palette-empty",children:f("layout_editor.palette.empty")}):i.jsx("div",{"data-testid":`g7le-palette-group-${C?.key??"all"}`,style:{marginBottom:16},children:i.jsx("div",{style:UI,children:T.map(A=>i.jsx(DI,{entry:A,entrySpec:x?.[A.name]??null,onClick:$,t:f,entryT:g},A.name))})})})]})]})]})}function DI(e){const{entry:t,entrySpec:n,onClick:o,t:r,entryT:s}=e,a=n?.label?ui(n.label,s):t.description??t.name,l=(t.type??"").toLowerCase(),d=l==="basic"||l==="composite"||l==="layout",c=!!n?.defaultNode&&typeof n.defaultNode=="object",p=!!n?.requiresDefaultNode&&!c,f=!c,y=p?r("layout_editor.palette.requires_default_node_missing"):n?.label?ui(n.label,s):t.name;return i.jsxs("button",{type:"button",onClick:()=>o(t),disabled:p,"aria-disabled":p||void 0,"data-testid":`g7le-palette-item-${t.name}`,"data-component-type":l||void 0,"data-blocked":p?"true":void 0,"data-default-node-missing":f?"true":void 0,title:y,style:p?GI:qI,children:[i.jsx("div",{style:XI,"aria-hidden":"true",children:zI(t)}),i.jsx("div",{style:JI,children:a}),i.jsx("div",{"data-testid":`g7le-palette-item-${t.name}-tag`,style:ZI,children:`<${t.name}>`}),d&&i.jsx("div",{"data-testid":`g7le-palette-item-${t.name}-badge`,style:NI(l),children:l}),f&&i.jsx("div",{"data-testid":`g7le-palette-item-${t.name}-default-missing-badge`,style:YI,children:r("layout_editor.palette.default_node_missing_badge")})]})}function ui(e,t){return e.startsWith("$t:")?t(e.slice(3)):t(e)}function NI(e){const n={basic:{bg:"#dbeafe",fg:"#1e40af"},composite:{bg:"#dcfce7",fg:"#14532d"},layout:{bg:"#fef3c7",fg:"#854d0e"}}[e]??{bg:"#e2e8f0",fg:"#334155"};return{fontSize:9,fontWeight:600,color:n.fg,background:n.bg,padding:"1px 6px",borderRadius:999,textTransform:"uppercase",letterSpacing:.4,lineHeight:1.6}}function zI(e){const t=e.name;return{Button:"🔘",A:"🔗",Img:"🖼",Icon:"✨",Hr:"➖",P:"📄",Span:"T",H1:"H₁",H2:"H₂",H3:"H₃",H4:"H₄",Ul:"☰",Li:"•",Nav:"🧭",Div:"▢",Container:"▢",Flex:"⇆",Grid:"▦",SectionLayout:"▤",ThreeColumnLayout:"⫼",Form:"📝",Input:"⎕",Textarea:"▭",Select:"⏷",Checkbox:"☑",PasswordInput:"🔒",Label:"🏷",DataGrid:"▦",Pagination:"↔",SearchBar:"🔍",ProductCard:"🛍",ImageGallery:"🖼",Avatar:"👤",UserInfo:"👥",HtmlContent:"⟨/⟩",HtmlEditor:"✏",RichTextEditor:"✏",TabNavigation:"◰",QuantitySelector:"#",ProductImageViewer:"🖼",FileUploader:"📎",AvatarUploader:"📷",SocialLoginButtons:"🌐",PostReactions:"❤"}[t]??"◻"}const MI={display:"flex",flexDirection:"column",width:"100%",height:"100%",minHeight:0,fontFamily:'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Apple SD Gothic Neo", "Noto Sans KR", sans-serif'},LI={display:"flex",justifyContent:"space-between",alignItems:"center",padding:"14px 20px",borderBottom:"1px solid #e2e8f0",flexShrink:0},PI={background:"transparent",border:"none",fontSize:24,cursor:"pointer",lineHeight:1,color:"#64748b",padding:"0 4px"},OI={display:"flex",flex:1,minHeight:0},BI={width:180,flexShrink:0,borderRight:"1px solid #e2e8f0",padding:"14px 8px",background:"#f8fafc",overflowY:"auto"},WI={listStyle:"none",margin:0,padding:0,display:"flex",flexDirection:"column",gap:4},jI={display:"flex",width:"100%",alignItems:"center",justifyContent:"space-between",textAlign:"left",padding:"8px 12px",border:"none",borderRadius:6,fontSize:13,cursor:"pointer",transition:"background-color 120ms"},HI={flex:1,display:"flex",flexDirection:"column",minHeight:0,padding:"14px 20px"},FI={width:"100%",padding:"8px 10px",border:"1px solid #cbd5e1",borderRadius:6,marginBottom:12,fontSize:13,flexShrink:0,boxSizing:"border-box"},KI={flex:1,overflowY:"auto",minHeight:0},VI={fontSize:11,color:"#94a3b8",marginLeft:8,fontWeight:500},UI={display:"grid",gridTemplateColumns:"repeat(auto-fill, minmax(96px, 1fr))",gap:10},qI={display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",gap:6,padding:"14px 6px",background:"#fff",border:"1px solid #e2e8f0",borderRadius:8,cursor:"pointer",fontSize:12,minHeight:84,transition:"border-color 120ms, box-shadow 120ms"},GI={display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",gap:6,padding:"14px 6px",background:"#f8fafc",border:"1px dashed #cbd5e1",borderRadius:8,cursor:"not-allowed",fontSize:12,minHeight:84,color:"#94a3b8",opacity:.65},YI={fontSize:9,fontWeight:600,color:"#92400e",background:"#fef3c7",padding:"1px 6px",borderRadius:999,textTransform:"none",letterSpacing:0,lineHeight:1.6},XI={fontSize:22,lineHeight:1,color:"#475569"},JI={fontSize:12,fontWeight:500,color:"#0f172a",textAlign:"center",wordBreak:"break-word"},ZI={fontFamily:'ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace',fontSize:10,color:"#64748b",textAlign:"center",wordBreak:"break-all"},If={padding:24,color:"#64748b",fontSize:13,textAlign:"center"};function QI(e){const t=[],n=r=>{if(Array.isArray(r))for(const s of r)s&&typeof s=="object"&&t.push(s)};n(e.children);const o=e.responsive;if(o&&typeof o=="object")for(const r of Object.values(o))r&&typeof r=="object"&&n(r.children);return t}function Af(e){const t=[],n=o=>{const r="isolatedState"in o&&o.isolatedState!=null,s=o.isolatedScopeId;(r||typeof s=="string"&&s.length>0)&&t.push({scopeId:typeof s=="string"?s:void 0,initialState:o.isolatedState&&typeof o.isolatedState=="object"?o.isolatedState:void 0});for(const a of QI(o))n(a)};if(Array.isArray(e))for(const o of e)o&&typeof o=="object"&&n(o);return t}const RI=["scroll","selector","wizard","slider"];function eA(e,t){const n=new Set;for(const o of e)o.scopeId&&n.add(o.scopeId);for(const o of t){n.add(o);for(const r of RI)n.add(`${o}-${r}`)}return Array.from(n)}function tA(e,t,n){const o=t.label;return typeof o=="string"?o.startsWith("$t:")?n(o.slice(3)):o:e}function pi({controlKey:e,control:t,node:n,t:o,onPatch:r,candidates:s,bindingCandidates:a,scope:l=Wt}){const d=Tn(t.widget),c=tA(e,t,o),p=u.useMemo(()=>Do(n,t,l),[n,t,l]),f=t.widget==="i18n-text"&&t.apply?.type==="propValue",y=t.widget==="text"&&t.apply?.type==="propValue",g=p.darkReadonly===!0||l.colorScheme==="dark"&&t.apply!==void 0&&!Us(t.apply),m=t.apply,h=l.colorScheme==="dark"&&!g&&m?.type==="classToken"&&typeof m.tokenTemplate=="string"&&m.tokenTemplate.includes("{value}"),v=p.scopedValue===void 0&&p.baseFallback!==void 0,b=u.useCallback(x=>{if(g)return;const k=fn(n,t,x,l);r(k)},[n,t,r,l,g]);return i.jsxs("div",{className:"g7le-control-row","data-testid":`g7le-control-${e}`,style:nA,children:[i.jsxs("span",{className:"g7le-control-label",style:oA,children:[c,p.conflict&&i.jsx("span",{"data-testid":`g7le-control-conflict-${e}`,title:o("layout_editor.property_modal.value_conflict"),style:rA,children:"!"})]}),i.jsx("div",{className:"g7le-control-widget",style:{flex:1,minWidth:0,opacity:v?.45:1},"data-placeholder":v?"true":void 0,children:f?i.jsx(vt,{value:typeof p.value=="string"?p.value:"",onChange:x=>b(x),t:o,testidPrefix:`g7le-prop-i18n-${e}`,candidates:a,enableExpressionTree:!0,expressionTreeCollapsible:!0}):y?i.jsx(Ve,{value:typeof p.value=="string"?p.value:"",onChange:x=>b(x===""?void 0:x),t:o,testidPrefix:`g7le-prop-value-${e}`,candidates:a}):g?i.jsx("span",{"data-testid":`g7le-control-dark-readonly-${e}`,style:sA,children:o("layout_editor.property_modal.dark_code_only")}):d?i.jsx(d,{control:t,value:p.value,onChange:b,t:o,candidates:s,bindingCandidates:a,freeValueDisabled:h}):i.jsx("span",{"data-testid":`g7le-control-unsupported-${e}`,style:iA,children:o("layout_editor.property_modal.unsupported_widget")})})]})}const nA={display:"flex",alignItems:"center",gap:12,padding:"6px 0",minHeight:32,minWidth:0},oA={fontSize:12,color:"#475569",minWidth:88,display:"inline-flex",alignItems:"center",gap:4},rA={display:"inline-flex",width:14,height:14,borderRadius:7,background:"#f59e0b",color:"#fff",fontSize:10,alignItems:"center",justifyContent:"center",fontWeight:700},iA={fontSize:11,color:"#94a3b8",fontStyle:"italic"},sA={fontSize:11,color:"#94a3b8",fontStyle:"italic"};function fi(e,t,n){return typeof e=="string"?e.startsWith("$t:")?n(e.slice(3)):e:t}function aA(e,t){const n=e.props??{};return t.key in n?n[t.key]:t.default}function lA({spec:e,node:t,t:n,onPatchProp:o}){const r=e.groups??[];return i.jsx("div",{className:"g7le-composite-settings","data-testid":"g7le-composite-settings",style:cA,children:r.map((s,a)=>i.jsxs("div",{className:"g7le-settings-group",style:uA,children:[i.jsx("div",{style:pA,children:fi(s.label,n("layout_editor.property_modal.settings_group",{index:a+1}),n)}),(s.fields??[]).map(l=>i.jsxs("div",{className:"g7le-settings-field","data-testid":`g7le-setting-${l.key}`,style:fA,children:[i.jsx("span",{style:gA,children:fi(l.label,l.key,n)}),i.jsx("div",{style:{flex:1},children:dA(l,t,n,o)})]},l.key))]},a))})}function dA(e,t,n,o){const r=aA(t,e);switch(e.type){case"number":return i.jsx("input",{type:"number","data-testid":`g7le-setting-input-${e.key}`,value:typeof r=="number"?r:"",min:e.min,max:e.max,onChange:s=>o(e.key,s.target.value===""?void 0:Number(s.target.value)),style:Ca});case"text":return i.jsx("input",{type:"text","data-testid":`g7le-setting-input-${e.key}`,value:typeof r=="string"?r:"",onChange:s=>o(e.key,s.target.value===""?void 0:s.target.value),style:Ca});case"toggle":return i.jsx("input",{type:"checkbox","data-testid":`g7le-setting-input-${e.key}`,checked:r===!0,onChange:s=>o(e.key,s.target.checked)});case"checkbox-group":{const s=Array.isArray(r)?r:[];return i.jsx("div",{style:hA,"data-testid":`g7le-setting-input-${e.key}`,children:(e.options??[]).map(a=>{const l=s.includes(a.value);return i.jsxs("label",{style:yA,children:[i.jsx("input",{type:"checkbox",checked:l,onChange:d=>{const c=d.target.checked?[...s,a.value]:s.filter(p=>p!==a.value);o(e.key,c)}}),fi(a.label,String(a.value),n)]},String(a.value))})})}default:return i.jsxs("select",{"data-testid":`g7le-setting-input-${e.key}`,value:r==null?"":String(r),onChange:s=>{const a=s.target.value;if(a===""){o(e.key,void 0);return}const l=(e.options??[]).find(d=>String(d.value)===a);o(e.key,l?l.value:a)},style:Ca,children:[i.jsx("option",{value:"",children:"—"}),(e.options??[]).map(s=>i.jsx("option",{value:String(s.value),children:fi(s.label,String(s.value),n)},String(s.value)))]})}}const cA={display:"flex",flexDirection:"column",gap:16},uA={display:"flex",flexDirection:"column",gap:6},pA={fontSize:12,fontWeight:700,color:"#0f172a",borderBottom:"1px solid #e2e8f0",paddingBottom:4},fA={display:"flex",alignItems:"center",gap:12,minHeight:30},gA={fontSize:12,color:"#475569",minWidth:96},Ca={padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,width:"100%",boxSizing:"border-box"},hA={display:"flex",flexWrap:"wrap",gap:10},yA={display:"inline-flex",alignItems:"center",gap:4,fontSize:12,color:"#0f172a"},bA=["data_binding","isolatedScopeId","parentFormContextProp","skipBindingKeys","isolatedState","component_layout","sortable","itemTemplate"],mA=/\{\{|\$switch|\$get|\|\s*(date|number|truncate|currency|relative)\b/;function gi(e,t=0){return t>6?!1:typeof e=="string"?mA.test(e):Array.isArray(e)?e.some(n=>gi(n,t+1)):e&&typeof e=="object"?Object.values(e).some(n=>gi(n,t+1)):!1}function vA(e){if(Zl(e))return!1;const t=Fl(e);return t.length===0?!1:t.some(n=>n.isComplex)}function Tf(e){const t=[],n=e.props??{};for(const o of bA)o in e&&e[o]!==void 0?t.push({key:o,kind:"developer_prop"}):o in n&&n[o]!==void 0&&t.push({key:`props.${o}`,kind:"developer_prop"});typeof e.text=="string"?vA(e.text)&&t.push({key:"text",kind:"complex_expression"}):gi(e.text)&&t.push({key:"text",kind:"complex_expression"});for(const[o,r]of Object.entries(n))o==="style"||o==="className"||gi(r)&&t.push({key:`props.${o}`,kind:"complex_expression"});return t}function _A(e){return Tf(e).length>0}const xA=new Set(["permissions","blur_until_loaded","comment"]);function wA({node:e,advanced:t,t:n,onPatch:o,permissionCandidates:r}){const s=u.useMemo(()=>Tf(e),[e]),a=(l,d)=>{const c={...e};d===void 0?delete c[l]:c[l]=d,o(c)};return i.jsxs("div",{className:"g7le-advanced-props","data-testid":"g7le-advanced-props",style:kA,children:[t.map(l=>{if(!xA.has(l))return i.jsxs("div",{"data-testid":`g7le-advanced-deferred-${l}`,style:CA,children:[n("layout_editor.property_modal.advanced_deferred"),":",l]},l);if(l==="permissions")return i.jsxs("div",{className:"g7le-advanced-group","data-testid":"g7le-advanced-permissions",style:$a,children:[i.jsx("div",{style:Ea,children:n("layout_editor.property_modal.advanced.permissions")}),i.jsx(ys,{control:{widget:"tag-input"},value:e.permissions,onChange:d=>a("permissions",d),t:n,candidates:r})]},l);if(l==="blur_until_loaded"){const d=e.blur_until_loaded===!0||e.blur_until_loaded!=null&&e.blur_until_loaded!==!1;return i.jsxs("div",{className:"g7le-advanced-group","data-testid":"g7le-advanced-blur",style:$a,children:[i.jsx("div",{style:Ea,children:n("layout_editor.property_modal.advanced.blur_until_loaded")}),i.jsxs("label",{style:{display:"inline-flex",alignItems:"center",gap:6,fontSize:12},children:[i.jsx("input",{type:"checkbox","data-testid":"g7le-advanced-blur-toggle",checked:!!d,onChange:c=>a("blur_until_loaded",c.target.checked?!0:void 0)}),n("layout_editor.property_modal.advanced.blur_hint")]})]},l)}return i.jsxs("div",{className:"g7le-advanced-group","data-testid":"g7le-advanced-comment",style:$a,children:[i.jsx("div",{style:Ea,children:n("layout_editor.property_modal.advanced.comment")}),i.jsx("input",{type:"text","data-testid":"g7le-advanced-comment-input",value:typeof e.comment=="string"?e.comment:"",onChange:d=>a("comment",d.target.value===""?void 0:d.target.value),style:SA})]},l)}),s.length>0&&i.jsxs("div",{className:"g7le-advanced-preserved","data-testid":"g7le-advanced-preserved",style:$A,children:[i.jsx("div",{style:EA,children:n("layout_editor.property_modal.advanced_preserved_title",{count:s.length})}),i.jsx("ul",{style:IA,children:s.map(l=>i.jsxs("li",{"data-testid":`g7le-advanced-preserved-${l.key}`,style:AA,children:[i.jsx("code",{style:{fontSize:11},children:l.key}),i.jsx("span",{style:{fontSize:11,color:"#94a3b8"},children:l.kind==="developer_prop"?n("layout_editor.property_modal.advanced_kind_developer"):n("layout_editor.property_modal.advanced_kind_expression")})]},l.key))})]})]})}const kA={display:"flex",flexDirection:"column",gap:14},$a={display:"flex",flexDirection:"column",gap:6},Ea={fontSize:12,fontWeight:700,color:"#0f172a"},SA={padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,width:"100%",boxSizing:"border-box"},CA={fontSize:11,color:"#94a3b8",fontStyle:"italic"},$A={borderTop:"1px dashed #cbd5e1",paddingTop:10,marginTop:4},EA={fontSize:11,color:"#b45309",marginBottom:6},IA={listStyle:"none",margin:0,padding:0,display:"flex",flexDirection:"column",gap:4},AA={display:"flex",justifyContent:"space-between",gap:8,padding:"3px 0"},TA={onClick:"click",onHover:"mouseenter",onChange:"change",onSubmit:"submit",onMount:"mount",onUnmount:"unmount",onFocus:"focus",onBlur:"blur"};function Ia(e){return TA[e]??e.replace(/^on/,"").toLowerCase()}function Df(e){const t=e.actions;if(Array.isArray(t))return t.filter(r=>!!r&&typeof r=="object");const n=e.props??{};if(Array.isArray(n.actions))return n.actions.filter(r=>!!r&&typeof r=="object");const o=n.events;if(o&&typeof o=="object"){const r=[];for(const[s,a]of Object.entries(o)){if(!Array.isArray(a))continue;const l=Ia(s);for(const d of a)d&&typeof d=="object"&&r.push("type"in d?d:{type:l,...d})}return r}return[]}function DA(e,t){const n=Ia(t);return Df(e).filter(o=>o.type===n||t==="onClick"&&o.type===void 0)}function NA(e,t,n){const o=Ia(t),r=Df(e).filter(d=>!(d.type===o||t==="onClick"&&d.type===void 0)),s=n.map(d=>"type"in d?d:{type:o,...d}),a={...e};if(a.props&&typeof a.props=="object"){const d={...a.props};let c=!1;"actions"in d&&(delete d.actions,c=!0),"events"in d&&(delete d.events,c=!0),c&&(a.props=d)}const l=[...r,...s];return l.length===0?delete a.actions:a.actions=l,a}function zA({node:e,spec:t,capability:n,t:o,onPatchNode:r,pageCandidates:s,dataSourceCandidates:a,stateKeyCandidates:l,bindingCandidates:d}){const c=t?.actionRecipes,p=u.useMemo(()=>({pageCandidates:s,dataSourceCandidates:a,stateKeyCandidates:l,bindingCandidates:d}),[s,a,l,d]),f=Array.isArray(n?.events)?n.events:[];return f.length===0?i.jsx("div",{"data-testid":"g7le-action-no-events",style:Nf,children:o("layout_editor.action.no_events")}):!c||Object.keys(c).length===0?i.jsx("div",{"data-testid":"g7le-action-no-recipes",style:Nf,children:o("layout_editor.action.no_recipes")}):i.jsx("div",{className:"g7le-action-editor","data-testid":"g7le-action-editor",style:{display:"flex",flexDirection:"column",gap:14},children:f.map(y=>i.jsxs("div",{className:"g7le-action-event-slot","data-testid":`g7le-action-event-${y}`,children:[i.jsx("div",{style:MA,children:o(`layout_editor.action.event.${y}`)}),i.jsx(Gt,{actions:DA(e,y),onChange:g=>r(NA(e,y,g)),t:o,recipes:c,candidatePools:p,actionChipCandidates:t?.actionChipCandidates??null,addContext:"component",showSourceBadge:!0,testIdPrefix:`g7le-action-slot-${y}`})]},y))})}const Nf={fontSize:12,color:"#94a3b8",padding:"16px 0",textAlign:"center"},MA={fontSize:12,fontWeight:700,color:"#475569",marginBottom:6};function Aa(e,t,n){if(!t)return n;const o=e(t);return!o||o===t||o.startsWith("$t:")?n:o}function LA(e,t){return Aa(t,e.labelKey,e.path||e.sourceId)}function PA({node:e,spec:t,candidates:n,t:o,onPatchNode:r}){const a=(e.props??{})[t.propKey],l=typeof a=="string"?a:void 0,d=u.useMemo(()=>Kt(a),[a]),c=l!==void 0&&l!==""&&d===null,[p,f]=u.useState(!1),[y,g]=u.useState(""),m=Aa(o,t.label,t.propKey),h=o(t.shape==="array"?"layout_editor.binding.shape_array":t.shape==="object"?"layout_editor.binding.shape_object":"layout_editor.binding.shape_scalar"),v=u.useMemo(()=>Fi(n,t.shape).map(_=>({..._,resolvedLabel:LA(_,o)})),[n,t.shape,o]),b=u.useMemo(()=>Ki(v,y),[v,y]),x=w=>{const _={...e.props??{}};w===void 0?delete _[t.propKey]:_[t.propKey]=w,r({...e,props:_})},k=w=>{x(En(w.sourceId,w.path,t.shape)),f(!1),g("")};return i.jsxs("div",{"data-testid":`g7le-binding-row-${t.propKey}`,style:jA,children:[i.jsxs("div",{style:HA,children:[i.jsx("span",{style:{fontWeight:600,fontSize:12},children:m}),i.jsx("span",{style:FA,children:h}),t.required&&i.jsx("span",{"data-testid":`g7le-binding-required-${t.propKey}`,style:KA,children:o("layout_editor.binding.required")})]}),c?i.jsxs("div",{"data-testid":`g7le-binding-complex-${t.propKey}`,style:GA,children:[o("layout_editor.binding.complex"),i.jsx("code",{style:zf,children:l})]}):d?i.jsxs("div",{style:VA,children:[i.jsx("code",{"data-testid":`g7le-binding-expr-${t.propKey}`,style:zf,children:l}),i.jsx("button",{type:"button","data-testid":`g7le-binding-clear-${t.propKey}`,onClick:()=>x(void 0),style:qA,children:o("layout_editor.binding.clear")})]}):i.jsx("div",{"data-testid":`g7le-binding-empty-${t.propKey}`,style:UA,children:o("layout_editor.binding.not_connected")}),d&&(t.shape==="array"||t.shape==="object")&&t.itemFields&&t.itemFields.length>0&&i.jsxs("div",{style:YA,children:[o("layout_editor.binding.item_fields"),": ",t.itemFields.join(", ")]}),!c&&i.jsxs("div",{style:{marginTop:4},children:[i.jsxs("button",{type:"button","data-testid":`g7le-binding-search-toggle-${t.propKey}`,onClick:()=>f(w=>!w),style:XA,children:["🔍 ",o("layout_editor.binding.search")]}),p&&i.jsxs("div",{"data-testid":`g7le-binding-picker-${t.propKey}`,style:JA,children:[i.jsx("input",{type:"text",autoFocus:!0,"data-testid":`g7le-binding-search-input-${t.propKey}`,value:y,placeholder:o("layout_editor.binding.search_placeholder"),onChange:w=>g(w.target.value),style:ZA}),i.jsx("div",{style:QA,children:b.length===0?i.jsx("div",{style:RA,children:o("layout_editor.binding.no_results")}):b.map(w=>i.jsxs("button",{type:"button","data-testid":`g7le-binding-candidate-${w.expression}`,onClick:()=>k(w),style:e6,children:[i.jsxs("span",{style:{display:"flex",flexDirection:"column",alignItems:"flex-start"},children:[i.jsx("span",{style:{fontSize:12},children:w.resolvedLabel}),i.jsxs("span",{style:t6,children:[Aa(o,w.groupLabelKey,w.sourceId)," · ",w.path||w.sourceId]})]}),i.jsx("span",{style:n6,children:w.preview})]},w.expression))})]})]})]})}function OA({node:e,dataProps:t,candidates:n,t:o,onPatchNode:r}){return!Array.isArray(t)||t.length===0?null:i.jsxs("div",{"data-testid":"g7le-data-binding-section",style:BA,children:[i.jsxs("div",{style:WA,children:["🔗 ",o("layout_editor.binding.section_title")]}),t.map(s=>i.jsx(PA,{node:e,spec:s,candidates:n,t:o,onPatchNode:r},s.propKey)),i.jsx("div",{style:o6})]})}const BA={display:"flex",flexDirection:"column",marginBottom:8},WA={fontSize:12,fontWeight:700,color:"#0f172a",marginBottom:6},jA={display:"flex",flexDirection:"column",gap:4,padding:"8px 0"},HA={display:"flex",alignItems:"center",gap:6},FA={fontSize:10,padding:"1px 6px",borderRadius:4,background:"#e0e7ff",color:"#3730a3"},KA={fontSize:10,padding:"1px 6px",borderRadius:4,background:"#fef3c7",color:"#92400e"},VA={display:"flex",alignItems:"center",gap:6},UA={fontSize:11,color:"#94a3b8"},zf={fontSize:11,fontFamily:"monospace",background:"#f1f5f9",padding:"2px 6px",borderRadius:4,color:"#0f172a",wordBreak:"break-all"},qA={fontSize:11,border:"1px solid #e2e8f0",borderRadius:4,background:"#fff",color:"#dc2626",padding:"2px 8px",cursor:"pointer"},GA={display:"flex",flexDirection:"column",gap:2,fontSize:11,color:"#64748b"},YA={fontSize:10,color:"#94a3b8"},XA={fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#334155",padding:"4px 10px",cursor:"pointer",textAlign:"left",width:"100%"},JA={marginTop:4,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",padding:6},ZA={width:"100%",padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,boxSizing:"border-box"},QA={marginTop:4,maxHeight:200,overflowY:"auto",display:"flex",flexDirection:"column",gap:2},RA={fontSize:11,color:"#94a3b8",padding:"8px 0",textAlign:"center"},e6={display:"flex",alignItems:"center",justifyContent:"space-between",gap:8,border:"none",background:"transparent",cursor:"pointer",padding:"6px 8px",borderRadius:4,textAlign:"left"},t6={fontSize:10,color:"#94a3b8"},n6={fontSize:10,color:"#64748b",fontFamily:"monospace",whiteSpace:"nowrap"},o6={borderTop:"1px solid #e2e8f0",marginTop:4};function r6({node:e,candidates:t,t:n,onPatchNode:o,templateIdentifier:r}){const s=typeof e.text=="string"?e.text:"",{state:a}=Je(),l=r??a.templateIdentifier,d=a.selectedRoute?.layoutName??null,c=a.locale,[p,f]=u.useState(!1),y=/^\s*\$t:custom\.[A-Za-z0-9._-]+\s*$/.test(s),m=Zl(s)&&(mt(s)?.key.startsWith("custom.")??!1)||y,h=u.useMemo(()=>m?mt(s):null,[m,s]),v=u.useMemo(()=>m?[]:Fl(s),[m,s]),b=T=>{o({...e,text:T})},x=()=>{if(!(typeof window>"u"))try{window.dispatchEvent(new CustomEvent(kt,{detail:{templateIdentifier:l,locale:c}}))}catch{}},k=(T,$)=>{const A=En($.sourceId,$.path,"scalar");b(wb(s,T,A))},w=T=>{const $=mt(s);b(mo(s,T));const A=$?.key;l&&A&&Fd(l,A,T).then(x)},_=T=>{const $=n(T);return $&&$!==T&&!$.startsWith("$t:")?$:""},C=T=>T.replace(/\$t:[a-zA-Z0-9._-]+/g,$=>_($.slice(3))).replace(/\s+/g," ").trim(),E=T=>{p||(f(!0),ls(l,d,c,s,s.length,T.sourceId,T.path,"scalar",C,$=>n($)).then($=>{$.kind==="ok"&&(b($.text),x())}).finally(()=>f(!1)))},S=T=>{p||!h&&!y||(f(!0),wr(l,c,s,Number.MAX_SAFE_INTEGER,T.sourceId,T.path,"scalar").then($=>{$.kind==="ok"&&(b($.text),x())}).finally(()=>f(!1)))};if(m){const T=h?.params??[];return i.jsxs("div",{"data-testid":"g7le-inline-binding-section",style:Mf,children:[i.jsxs("div",{style:Lf,children:["🔗 ",n("layout_editor.inline_binding.section_title")]}),T.length===0?i.jsx("div",{"data-testid":"g7le-inline-binding-none",style:Bf,children:n("layout_editor.inline_binding.none")}):T.map($=>i.jsx("div",{"data-testid":`g7le-inline-binding-param-${$.name}`,style:Pf,children:$.parsed===null?i.jsxs("div",{"data-testid":`g7le-inline-binding-param-complex-${$.name}`,style:s6,children:[n("layout_editor.inline_binding.complex"),i.jsx("code",{style:Ta,children:$.expression})]}):i.jsxs(i.Fragment,{children:[i.jsxs("div",{style:Of,children:[i.jsx("code",{"data-testid":`g7le-inline-binding-param-expr-${$.name}`,style:Ta,children:$.expression}),i.jsx("button",{type:"button","data-testid":`g7le-inline-binding-param-clear-${$.name}`,onClick:()=>w($.name),style:i6,children:n("layout_editor.inline_binding.clear")})]}),i.jsx($t,{candidates:t,t:n,onSelect:A=>k($.name,A),testIdSuffix:`param-${$.name}`})]})},$.name)),i.jsxs("div",{"data-testid":"g7le-inline-binding-param-append",style:Wf,children:[i.jsx("div",{style:Da,children:n("layout_editor.inline_binding.append_hint")}),i.jsx($t,{candidates:t,t:n,onSelect:S,testIdSuffix:"param-append"})]}),i.jsx("div",{"data-testid":"g7le-inline-binding-param-hint",style:Da,children:n("layout_editor.inline_binding.param_hint")}),i.jsx("div",{style:jf})]})}return i.jsxs("div",{"data-testid":"g7le-inline-binding-section",style:Mf,children:[i.jsxs("div",{style:Lf,children:["🔗 ",n("layout_editor.inline_binding.section_title")]}),v.length===0?i.jsx("div",{"data-testid":"g7le-inline-binding-none",style:Bf,children:n("layout_editor.inline_binding.none")}):v.map(T=>i.jsx("div",{"data-testid":`g7le-inline-binding-row-${T.bindingIndex}`,style:Pf,children:i.jsx("div",{style:Of,children:i.jsx("code",{"data-testid":`g7le-inline-binding-expr-${T.bindingIndex}`,style:Ta,children:T.expression})})},T.bindingIndex)),i.jsxs("div",{"data-testid":"g7le-inline-binding-append",style:Wf,children:[i.jsx("div",{style:Da,children:n("layout_editor.inline_binding.append_hint")}),i.jsx($t,{candidates:t,t:n,onSelect:E,testIdSuffix:"append"})]}),i.jsx("div",{style:jf})]})}const Mf={display:"flex",flexDirection:"column",marginBottom:8},Lf={fontSize:12,fontWeight:700,color:"#0f172a",marginBottom:6},Pf={display:"flex",flexDirection:"column",gap:4,padding:"8px 0"},Of={display:"flex",alignItems:"center",gap:6},Bf={fontSize:11,color:"#94a3b8"},Ta={fontSize:11,fontFamily:"monospace",background:"#f1f5f9",padding:"2px 6px",borderRadius:4,color:"#0f172a",wordBreak:"break-all"},i6={fontSize:11,border:"1px solid #e2e8f0",borderRadius:4,background:"#fff",color:"#dc2626",padding:"2px 8px",cursor:"pointer"},s6={display:"flex",flexDirection:"column",gap:2,fontSize:11,color:"#64748b"},Wf={display:"flex",flexDirection:"column",gap:2,padding:"8px 0 0"},Da={fontSize:11,color:"#475569",fontWeight:600},jf={borderTop:"1px solid #e2e8f0",marginTop:4};function Hf(e,t,n){if(!t)return n;const o=e(t);return!o||o===t||o.startsWith("$t:")?n:o}function a6(e,t){return Hf(t,e.labelKey,e.path||e.sourceId)}function l6(e){const t=e.iteration;return t&&typeof t=="object"?t:null}function d6({node:e,candidates:t,t:n,onPatchNode:o}){const r=l6(e),s=r?.source,a=typeof s=="string"?s:void 0,l=u.useMemo(()=>Kt(s),[s]),d=a!==void 0&&a!==""&&l===null,[c,p]=u.useState(!1),[f,y]=u.useState(""),g=u.useMemo(()=>Fi(t,"array").map(w=>({...w,resolvedLabel:a6(w,n)})),[t,n]),m=u.useMemo(()=>Ki(g,f),[g,f]);if(!r)return null;const h=typeof r.item_var=="string"?r.item_var:void 0,v=typeof r.index_var=="string"?r.index_var:void 0,b=k=>{const w={...r};k===void 0?delete w.source:w.source=k,o({...e,iteration:w})},x=k=>{b(En(k.sourceId,k.path,"array")),p(!1),y("")};return i.jsxs("div",{"data-testid":"g7le-iteration-binding-section",style:c6,children:[i.jsxs("div",{style:u6,children:["🔁 ",n("layout_editor.binding.iteration_title")]}),i.jsx("div",{style:p6,children:n("layout_editor.binding.iteration_hint")}),i.jsxs("div",{style:f6,children:[i.jsxs("div",{style:g6,children:[i.jsx("span",{style:{fontWeight:600,fontSize:12},children:n("layout_editor.binding.iteration_label")}),i.jsx("span",{style:h6,children:n("layout_editor.binding.shape_array")})]}),d?i.jsxs("div",{"data-testid":"g7le-iteration-binding-complex",style:v6,children:[n("layout_editor.binding.complex"),i.jsx("code",{style:Ff,children:a})]}):l?i.jsxs("div",{style:y6,children:[i.jsx("code",{"data-testid":"g7le-iteration-binding-expr",style:Ff,children:a}),i.jsx("button",{type:"button","data-testid":"g7le-iteration-binding-clear",onClick:()=>b(void 0),style:m6,children:n("layout_editor.binding.clear")})]}):i.jsx("div",{"data-testid":"g7le-iteration-binding-empty",style:b6,children:n("layout_editor.binding.not_connected")}),(h||v)&&i.jsxs("div",{"data-testid":"g7le-iteration-vars",style:_6,children:[h&&i.jsxs("span",{children:[n("layout_editor.binding.iteration_item_var"),": ",i.jsx("code",{style:Kf,children:h})]}),v&&i.jsxs("span",{children:[n("layout_editor.binding.iteration_index_var"),": ",i.jsx("code",{style:Kf,children:v})]})]}),!d&&i.jsxs("div",{style:{marginTop:4},children:[i.jsxs("button",{type:"button","data-testid":"g7le-iteration-binding-search-toggle",onClick:()=>p(k=>!k),style:x6,children:["🔍 ",n("layout_editor.binding.search")]}),c&&i.jsxs("div",{"data-testid":"g7le-iteration-binding-picker",style:w6,children:[i.jsx("input",{type:"text",autoFocus:!0,"data-testid":"g7le-iteration-binding-search-input",value:f,placeholder:n("layout_editor.binding.search_placeholder"),onChange:k=>y(k.target.value),style:k6}),i.jsx("div",{style:S6,children:m.length===0?i.jsx("div",{style:C6,children:n("layout_editor.binding.no_results")}):m.map(k=>i.jsxs("button",{type:"button","data-testid":`g7le-iteration-binding-candidate-${k.expression}`,onClick:()=>x(k),style:$6,children:[i.jsxs("span",{style:{display:"flex",flexDirection:"column",alignItems:"flex-start"},children:[i.jsx("span",{style:{fontSize:12},children:k.resolvedLabel}),i.jsxs("span",{style:E6,children:[Hf(n,k.groupLabelKey,k.sourceId)," · ",k.path||k.sourceId]})]}),i.jsx("span",{style:I6,children:k.preview})]},k.expression))})]})]})]}),i.jsx("div",{style:A6})]})}const c6={display:"flex",flexDirection:"column",marginBottom:8},u6={fontSize:12,fontWeight:700,color:"#0f172a",marginBottom:4},p6={fontSize:10,color:"#94a3b8",marginBottom:6,lineHeight:1.4},f6={display:"flex",flexDirection:"column",gap:4,padding:"8px 0"},g6={display:"flex",alignItems:"center",gap:6},h6={fontSize:10,padding:"1px 6px",borderRadius:4,background:"#e0e7ff",color:"#3730a3"},y6={display:"flex",alignItems:"center",gap:6},b6={fontSize:11,color:"#94a3b8"},Ff={fontSize:11,fontFamily:"monospace",background:"#f1f5f9",padding:"2px 6px",borderRadius:4,color:"#0f172a",wordBreak:"break-all"},m6={fontSize:11,border:"1px solid #e2e8f0",borderRadius:4,background:"#fff",color:"#dc2626",padding:"2px 8px",cursor:"pointer"},v6={display:"flex",flexDirection:"column",gap:2,fontSize:11,color:"#64748b"},_6={display:"flex",gap:12,fontSize:10,color:"#94a3b8",marginTop:2},Kf={fontSize:10,fontFamily:"monospace",background:"#f1f5f9",padding:"1px 4px",borderRadius:3,color:"#475569"},x6={fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#334155",padding:"4px 10px",cursor:"pointer",textAlign:"left",width:"100%"},w6={marginTop:4,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",padding:6},k6={width:"100%",padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,boxSizing:"border-box"},S6={marginTop:4,maxHeight:200,overflowY:"auto",display:"flex",flexDirection:"column",gap:2},C6={fontSize:11,color:"#94a3b8",padding:"8px 0",textAlign:"center"},$6={display:"flex",alignItems:"center",justifyContent:"space-between",gap:8,border:"none",background:"transparent",cursor:"pointer",padding:"6px 8px",borderRadius:4,textAlign:"left"},E6={fontSize:10,color:"#94a3b8"},I6={fontSize:10,color:"#64748b",fontFamily:"monospace",whiteSpace:"nowrap"},A6={borderTop:"1px solid #e2e8f0",marginTop:4},T6=new Set(["Span","P","H1","H2","H3","H4","H5","H6","A","Button","Li","Label"]);function D6(e,t){if(!e||e.iteration&&typeof e.iteration=="object")return!1;const n=t?.textBinding;if(n===!1)return!1;if(n===!0)return!0;const o=typeof e.name=="string"?e.name:"";return T6.has(o)?!0:typeof e.text=="string"}const yn={direction:"flexDirection",justify:"flexJustify",align:"flexAlign",wrap:"flexWrap",gap:"flexGap",enable:"flexEnable"},hi={grow:"flexItemGrow",selfAlign:"flexItemAlign",order:"flexItemOrder"};function Vf(e){const t=e?.flexEditor;return t==="container"||t==="item"||t==="auto"?t:null}function N6(e){if(!e||typeof window>"u"||typeof window.getComputedStyle!="function")return!1;const t=(window.getComputedStyle(e).display??"").toLowerCase();return t==="flex"||t==="inline-flex"}function z6(e,t,n=Wt){if(!e||!t)return!1;const o=Do(e,t,n),r=t.options,s=Array.isArray(r)?t.onValue??r[0]?.value:void 0,a=l=>s!==void 0?l===s:l!==void 0&&l!==!1;return n.breakpoint!=="base"?a(o.scopedRawValue):o.matched&&a(o.value)}function M6(e,t,n=!1,o=!1,r=Wt){if(e==="container")return{showContainer:!0,showEnableButton:!1,showDisableButton:!1};if(e==="item")return{showContainer:!1,showEnableButton:!1,showDisableButton:!1};if(e==="auto"){const s=r.breakpoint==="base";return!o&&!s?{showContainer:!1,showEnableButton:!1,showDisableButton:!1}:(o?n:N6(t))?{showContainer:!0,showEnableButton:!1,showDisableButton:o}:{showContainer:!1,showEnableButton:!0,showDisableButton:!1}}return{showContainer:!1,showEnableButton:!1,showDisableButton:!1}}const L6={[yn.direction]:"layout_editor.flex.direction",[yn.justify]:"layout_editor.flex.justify",[yn.align]:"layout_editor.flex.align",[yn.wrap]:"layout_editor.flex.wrap",[yn.gap]:"layout_editor.flex.gap"},P6={[hi.grow]:"layout_editor.flex.item_grow",[hi.selfAlign]:"layout_editor.flex.item_align",[hi.order]:"layout_editor.flex.item_order"};function O6({node:e,spec:t,capability:n,t:o,onPatchNode:r,liveElement:s,scope:a=Wt}){const l=Vf(n),d=Ct(t,yn.enable),c=z6(e,d,a),{showContainer:p,showEnableButton:f,showDisableButton:y}=M6(l,s,c,!!d,a),g=Object.values(yn).filter(_=>_!==yn.enable&&Ct(t,_)),m=Object.values(hi).filter(_=>Ct(t,_)),h=d?.options,v=d?.onValue??h?.[0]?.value??!0,b=Array.isArray(h)&&h.length>=2?h[1].value:void 0,x=()=>{if(!d)return;const _=fn(e,d,v,a);r(_)},k=()=>{if(!d)return;const _=a.breakpoint!=="base"&&b!==void 0?b:void 0,C=fn(e,d,_,a);r(C)},w=(_,C,E)=>i.jsx("div",{"data-testid":E,style:{display:"flex",flexDirection:"column"},children:_.map(S=>{const T=Ct(t,S);if(!T)return null;const $=T.label?T:{...T,label:`$t:${C[S]}`};return i.jsx(pi,{controlKey:S,control:$,node:e,t:o,onPatch:r,scope:a},S)})});return i.jsxs("div",{className:"g7le-flex-editor","data-testid":"g7le-flex-editor",style:{display:"flex",flexDirection:"column",gap:12},children:[f&&i.jsx("button",{type:"button","data-testid":"g7le-flex-enable",onClick:x,style:B6,children:o("layout_editor.flex.make_flex")}),p&&g.length>0&&i.jsxs("div",{className:"g7le-flex-container-section","data-testid":"g7le-flex-container-section",children:[i.jsx("div",{style:Uf,children:o("layout_editor.flex.container_title")}),w(g,L6,"g7le-flex-container-controls"),y&&i.jsx("button",{type:"button","data-testid":"g7le-flex-disable",onClick:k,style:W6,children:o("layout_editor.flex.unmake_flex")})]}),l==="item"&&m.length>0&&i.jsxs("div",{className:"g7le-flex-item-section","data-testid":"g7le-flex-item-section",children:[i.jsx("div",{style:Uf,children:o("layout_editor.flex.item_title")}),w(m,P6,"g7le-flex-item-controls")]}),!p&&!f&&l!=="item"&&i.jsx("div",{"data-testid":"g7le-flex-empty",style:j6,children:o("layout_editor.flex.not_flex")})]})}const Uf={fontSize:12,fontWeight:700,color:"#0f172a",marginBottom:6},B6={padding:"8px 12px",fontSize:12,border:"1px dashed #2563eb",borderRadius:6,background:"#eff6ff",color:"#2563eb",cursor:"pointer"},W6={marginTop:8,padding:"6px 10px",fontSize:12,border:"1px solid #e2e8f0",borderRadius:6,background:"#f8fafc",color:"#64748b",cursor:"pointer"},j6={fontSize:12,color:"#94a3b8",padding:"16px 0",textAlign:"center"},qf=["desktop","tablet","mobile","portable"],Na=1280,H6=820,F6=390,K6=1920,V6=320;function U6(e){const t=[],n=new Set,o=r=>{if(!r||typeof r!="object")return;const s=r.responsive;if(s&&typeof s=="object")for(const a of Object.keys(s)){n.has(a)||(n.add(a),t.push(a));const l=s[a]?.children;if(Array.isArray(l))for(const d of l)o(d)}if(Array.isArray(r.children))for(const a of r.children)o(a)};if(Array.isArray(e))for(const r of e)o(r);return t}function q6(e){const t=[...qf],n=new Set(qf);for(const o of U6(e))!n.has(o)&&!t.includes(o)&&t.push(o);return t}function G6(e,t){const n=e?.responsive;if(!n||typeof n!="object")return[];const o=[];for(const r of Object.keys(n)){const s=n[r];!s||!Array.isArray(s.children)||r!==t&&o.push(r)}return o}function Y6(e){switch(e){case"desktop":return Na;case"tablet":return H6;case"mobile":return F6}const t=e in To?To[e]:Mn.parseRange(e);return!t||!Number.isFinite(t.max)?Na:Math.round(Math.max(V6,Math.min(K6,t.max)))}function Gf(){const t=Ft()?.document?.raw?.components;return u.useMemo(()=>q6(t??null),[t])}const X6={desktop:"layout_editor.property_modal.scope.device_desktop",tablet:"layout_editor.property_modal.scope.device_tablet",mobile:"layout_editor.property_modal.scope.device_mobile",portable:"layout_editor.property_modal.scope.device_portable"},J6=[{key:"base",labelKey:"layout_editor.property_modal.scope.scheme_light"},{key:"dark",labelKey:"layout_editor.property_modal.scope.scheme_dark"}];function Yf({scope:e,onChange:t,node:n,t:o,showColorScheme:r=!0,onClearScope:s}){const[a,l]=u.useState(!1),[d,c]=u.useState(""),[p,f]=u.useState(""),[y,g]=u.useState(null),m=Gf(),h=u.useMemo(()=>{const k=["base",...m],w=new Set(k);for(const _ of Object.keys(n.responsive??{}))_!=="base"&&!w.has(_)&&(w.add(_),k.push(_));return e.breakpoint!=="base"&&!w.has(e.breakpoint)&&k.push(e.breakpoint),k},[m,n.responsive,e.breakpoint]),v=k=>t({...e,colorScheme:k}),b=k=>t({...e,breakpoint:k}),x=()=>{const k=d.trim(),w=p.trim();if(k===""&&w===""){g(o("layout_editor.property_modal.scope.custom_invalid"));return}const _=`${k}-${w}`;if(!gC(_)){g(o("layout_editor.property_modal.scope.custom_invalid"));return}g(null),l(!1),c(""),f(""),b(_)};return i.jsxs("div",{className:"g7le-style-scope-tabs","data-testid":"g7le-style-scope-tabs",style:Z6,children:[r&&i.jsx("div",{className:"g7le-style-scope-scheme","data-testid":"g7le-style-scope-scheme",role:"tablist","aria-label":o("layout_editor.property_modal.scope.scheme_light"),style:Xf,children:J6.map(k=>{const w=e.colorScheme===k.key;return i.jsxs("button",{type:"button",role:"tab","aria-selected":w,"data-testid":`g7le-style-scheme-${k.key}`,"data-active":w?"true":"false",onClick:()=>v(k.key),style:Jf(w),children:[o(k.labelKey),k.key==="dark"&&Gs(n,{colorScheme:"dark",breakpoint:e.breakpoint})&&i.jsx("span",{"data-testid":"g7le-style-scheme-dark-dot",style:Zf})]},k.key)})}),i.jsxs("div",{className:"g7le-style-scope-device","data-testid":"g7le-style-scope-device",role:"tablist","aria-label":o("layout_editor.property_modal.scope.device_base"),style:Xf,children:[h.map(k=>{const w=e.breakpoint===k,_=k==="base"||wu(k),C=k==="base"?"layout_editor.property_modal.scope.device_base":X6[k],E=C?o(C):k,S=_?`g7le-style-bp-${k}`:`g7le-style-bp-custom-${k}`;return i.jsxs("button",{type:"button",role:"tab","aria-selected":w,"data-testid":S,"data-active":w?"true":"false",onClick:()=>b(k),style:Jf(w),children:[E,Gs(n,{colorScheme:"base",breakpoint:k})&&i.jsx("span",{"data-testid":`${S}-dot`,style:Zf})]},String(k))}),i.jsxs("button",{type:"button","data-testid":"g7le-style-bp-add-custom",onClick:()=>l(k=>!k),style:eT,children:["+ ",o("layout_editor.property_modal.scope.add_custom")]})]}),a&&i.jsxs("div",{className:"g7le-style-scope-custom-input","data-testid":"g7le-style-scope-custom-input",style:Q6,children:[i.jsx("input",{type:"number","data-testid":"g7le-style-custom-min",placeholder:o("layout_editor.property_modal.scope.custom_min"),value:d,onChange:k=>c(k.target.value),style:Qf}),i.jsx("span",{style:{color:"#94a3b8"},children:"—"}),i.jsx("input",{type:"number","data-testid":"g7le-style-custom-max",placeholder:o("layout_editor.property_modal.scope.custom_max"),value:p,onChange:k=>f(k.target.value),style:Qf}),i.jsx("button",{type:"button","data-testid":"g7le-style-custom-confirm",onClick:x,style:tT,children:o("layout_editor.property_modal.scope.add_custom")})]}),y&&i.jsx("div",{"data-testid":"g7le-style-custom-error",style:nT,children:y}),e.breakpoint!=="base"&&!wu(e.breakpoint)&&i.jsx("div",{"data-testid":"g7le-style-custom-priority-note",style:oT,children:o("layout_editor.property_modal.scope.custom_priority_note")}),s&&Gs(n,e)&&i.jsxs("button",{type:"button","data-testid":"g7le-style-scope-reset",onClick:s,style:R6,children:["↺ ",o("layout_editor.property_modal.scope.reset_to_base")]})]})}const Z6={display:"flex",flexDirection:"column",gap:0,padding:"0 0 8px",borderBottom:"1px solid #e2e8f0",marginBottom:8},Q6={display:"flex",gap:6,alignItems:"center",marginTop:4},Xf={display:"flex",gap:2,flexWrap:"wrap",borderBottom:"1px solid #e2e8f0",margin:"0 -16px",padding:"0 12px"};function Jf(e){return{display:"inline-flex",alignItems:"center",gap:4,padding:"6px 12px",fontSize:12,border:"none",borderBottom:`2px solid ${e?"#2563eb":"transparent"}`,background:"transparent",color:e?"#2563eb":"#64748b",cursor:"pointer",fontWeight:e?700:500,marginBottom:-1}}const Zf={display:"inline-block",width:5,height:5,borderRadius:3,background:"#f59e0b"},R6={alignSelf:"flex-start",marginTop:2,padding:"3px 8px",fontSize:10,border:"1px solid #cbd5e1",borderRadius:6,background:"#f8fafc",color:"#475569",cursor:"pointer"},eT={padding:"6px 12px",fontSize:11,border:"none",borderBottom:"2px dashed #cbd5e1",background:"transparent",color:"#475569",cursor:"pointer",marginBottom:-1},Qf={width:70,padding:"4px 6px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6},tT={padding:"4px 10px",fontSize:11,border:"none",borderRadius:6,background:"#2563eb",color:"#fff",cursor:"pointer"},nT={fontSize:11,color:"#dc2626"},oT={fontSize:10,color:"#94a3b8"},Rf=new Map;function Pn(e,t){Rf.set(e,t)}function rT(e){return e?Rf.get(e)??null:null}const za={id:{label:"$t:layout_editor.core_props.id.label",widget:"core-id",placeholder:"$t:layout_editor.core_props.id.placeholder",apply:{type:"propValue",propKey:"id"}},dataKey:{label:"$t:layout_editor.core_props.dataKey.label",widget:"core-datakey",placeholder:"$t:layout_editor.core_props.dataKey.placeholder",apply:{type:"nodeKey",nodeKey:"dataKey"}},isolatedState:{label:"$t:layout_editor.core_props.isolatedState.label",widget:"toggle",apply:{type:"nodeKey",nodeKey:"isolatedState"}},isolatedScopeId:{label:"$t:layout_editor.core_props.isolatedScopeId.label",widget:"core-scopeid",placeholder:"$t:layout_editor.core_props.isolatedScopeId.placeholder",apply:{type:"nodeKey",nodeKey:"isolatedScopeId"}}},iT=["id"];function sT(e){return e===!1?[]:Array.isArray(e)?e.filter(t=>t in za):[...iT]}function eg(e){const t=e.props?.id??e.id;return typeof t=="string"&&t.length>0?`${t}-scope`:"scope"}function aT({node:e,onPatchNode:t,t:n,scopeIdCandidates:o=[],usedScopeIds:r=[]}){const s=e,a="isolatedState"in s&&s.isolatedState!=null,l=typeof s.isolatedScopeId=="string"?s.isolatedScopeId:"",[d,c]=u.useState(""),p=()=>{const h={...e};a?(delete h.isolatedState,delete h.isolatedScopeId):(h.isolatedState={},h.isolatedScopeId=eg(e)),t(h)},f=h=>{const v={...e};v.isolatedScopeId=h.trim()===""?eg(e):h,t(v)},y=h=>{const v={...e};v.isolatedState=h,t(v)},g=u.useMemo(()=>{const h=d.trim().toLowerCase();return o.filter(v=>!h||v.toLowerCase().includes(h))},[o,d]),m=l!==""&&r.includes(l);return i.jsxs("div",{className:"g7le-isolated-scope","data-testid":"g7le-isolated-scope-control",style:lT,children:[i.jsx("div",{style:dT,children:n("layout_editor.core_props.isolatedState.label")}),i.jsxs("div",{style:cT,children:[i.jsx("button",{type:"button","data-testid":"g7le-isolated-toggle",role:"switch","aria-checked":a,onClick:p,style:uT(a),children:n(a?"layout_editor.overlay.on":"layout_editor.overlay.off")}),a?null:i.jsx("span",{"data-testid":"g7le-isolated-off-hint",style:pT,children:n("layout_editor.core_props.isolatedState.off_hint")})]}),a?i.jsxs(i.Fragment,{children:[i.jsxs("div",{style:tg,children:[i.jsx("label",{style:ng,children:n("layout_editor.core_props.isolatedScopeId.label")}),i.jsx("input",{type:"text","data-testid":"g7le-isolated-scopeid-input",value:l,placeholder:n("layout_editor.core_props.isolatedScopeId.placeholder"),onChange:h=>f(h.target.value),style:fT}),g.length>0?i.jsxs("div",{"data-testid":"g7le-isolated-scopeid-picker",style:gT,children:[i.jsx("input",{type:"text","data-testid":"g7le-isolated-scopeid-search",value:d,placeholder:n("layout_editor.core_props.isolatedScopeId.search"),onChange:h=>c(h.target.value),style:hT}),i.jsx("div",{style:yT,children:g.map(h=>i.jsx("button",{type:"button","data-testid":`g7le-isolated-scopeid-candidate-${h}`,onClick:()=>{f(h),c("")},style:bT,children:h},h))})]}):null,m?i.jsxs("p",{"data-testid":"g7le-isolated-scopeid-dup",style:mT,children:["ⓘ ",n("layout_editor.core_props.isolatedScopeId.duplicate")]}):null]}),i.jsxs("div",{style:tg,children:[i.jsx("label",{style:ng,children:n("layout_editor.core_props.isolatedState.initial_label")}),i.jsx("div",{"data-testid":"g7le-isolated-initial-value",children:i.jsx(Nn,{value:s.isolatedState??{},onChange:y,t:n,path:"isolatedState",scope:"isolated-node"})})]})]}):null]})}const lT={display:"flex",flexDirection:"column",gap:8,padding:10,border:"1px solid #e2e8f0",borderRadius:8,minWidth:0},dT={fontSize:12,fontWeight:700,color:"#0f172a"},cT={display:"flex",gap:8,alignItems:"center"},uT=e=>({padding:"4px 12px",fontSize:11,border:`1px solid ${e?"#3b82f6":"#cbd5e1"}`,borderRadius:14,background:e?"#eff6ff":"#fff",color:e?"#1d4ed8":"#64748b",cursor:"pointer"}),pT={fontSize:11,color:"#94a3b8"},tg={display:"flex",flexDirection:"column",gap:4,minWidth:0},ng={fontSize:11,color:"#64748b"},fT={padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,boxSizing:"border-box",width:"100%"},gT={border:"1px solid #e2e8f0",borderRadius:6,padding:4,background:"#fff"},hT={width:"100%",padding:"4px 6px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,marginBottom:4,boxSizing:"border-box"},yT={display:"flex",flexDirection:"column",maxHeight:120,overflowY:"auto"},bT={textAlign:"left",padding:"4px 6px",fontSize:12,border:"none",background:"transparent",cursor:"pointer",borderRadius:4,color:"#0f172a"},mT={margin:0,fontSize:11,color:"#64748b"},vT={borderTop:"2px solid #c7d2fe",background:"#eef2ff",padding:"10px 12px",display:"flex",flexDirection:"column",gap:8},og={fontSize:13,fontWeight:700,color:"#3730a3",display:"flex",alignItems:"center",gap:6},_T={fontSize:11,fontWeight:600,color:"#4338ca",background:"#e0e7ff",border:"1px solid #c7d2fe",borderRadius:4,padding:"1px 6px"},Ma={fontSize:11,color:"#6366f1"},xT={alignSelf:"flex-start",fontSize:12,fontWeight:600,color:"#fff",background:"#4f46e5",border:"none",borderRadius:4,padding:"4px 10px",cursor:"pointer"},wT={opacity:.5,cursor:"not-allowed"},kT={fontSize:11,color:"#dc2626"};function ST({entry:e,t,onSave:n}){const[o,r]=u.useState(()=>e.props),[s,a]=u.useState(!0),[l,d]=u.useState(null),[c,p]=u.useState(!1),[f,y]=u.useState(!1),g=e.extensionName??e.extensionIdentifier??`#${e.extensionId}`,m=async()=>{if(!s){d(t("layout_editor.property_modal.injected_props.invalid_json"));return}d(null),p(!0),y(!1);try{await n(o),y(!0)}catch(h){d(h instanceof Error?h.message:String(h))}finally{p(!1)}};return i.jsxs("div",{"data-testid":`g7le-injected-props-entry-${e.extensionId}`,style:{display:"flex",flexDirection:"column",gap:6},children:[i.jsx("div",{style:og,children:i.jsx("span",{style:_T,"data-testid":"g7le-injected-props-origin-badge",children:t("layout_editor.property_modal.injected_props.origin",{source:g})})}),i.jsx(Bs,{value:o,shape:"object",emptyValue:{},testidPrefix:"g7le-injected-props-json",minHeight:80,invalidErrorKey:"layout_editor.property_modal.injected_props.invalid_json",shapeErrorKey:"layout_editor.property_modal.injected_props.invalid_json",t,onChange:h=>{r(h??{}),y(!1)},onValidityChange:h=>{a(h),h&&d(null),y(!1)}}),l&&i.jsx("div",{style:kT,"data-testid":"g7le-injected-props-error",children:l}),f&&i.jsx("div",{style:{...Ma,color:"#16a34a"},"data-testid":"g7le-injected-props-saved",children:t("layout_editor.property_modal.injected_props.saved")}),i.jsx("div",{style:Ma,children:t("layout_editor.property_modal.injected_props.save_target")}),i.jsx("button",{type:"button","data-testid":"g7le-injected-props-save",style:{...xT,...c||!s?wT:null},disabled:c||!s,onClick:m,children:c?t("layout_editor.property_modal.injected_props.saving"):t("layout_editor.property_modal.injected_props.save_button",{source:g})})]})}function CT({injectedProps:e,t,onSaveInjectedProps:n}){return!Array.isArray(e)||e.length===0?null:i.jsxs("div",{style:vT,"data-testid":"g7le-injected-props-section",children:[i.jsxs("div",{style:og,children:["🧩 ",t("layout_editor.property_modal.injected_props.title")]}),i.jsx("div",{style:Ma,children:t("layout_editor.property_modal.injected_props.help")}),e.map(o=>i.jsx(ST,{entry:o,t,onSave:r=>n(o.extensionId,r)},o.extensionId))]})}function $T(e,t){const n=e?.components??{};for(const o of Object.values(n)){const r=o?.find(s=>s.name===t);if(r)return r}return null}function ET({node:e,spec:t,manifest:n,t:o,onPatchNode:r,onClose:s,onDelete:a,permissionCandidates:l,pageCandidates:d,dataSourceCandidates:c,stateKeyCandidates:p,bindingCandidates:f,liveElement:y,scope:g=Wt,onScopeChange:m,templateIdentifier:h,initialTab:v,onSaveInjectedProps:b,branchLabel:x,isolatedScopeIdCandidates:k,usedScopeIds:w}){const _=e.__injectedProps,C=Array.isArray(_)&&_.length>0&&!!b,E=ve=>{m?.(ve)},S=()=>{r(xC(e,g))},T=typeof e.name=="string"?e.name:"",$=Oi(t,T),z=$T(n,T)?.settings,D=!!(z?.groups&&z.groups.length>0),O=Array.isArray($?.propControls)?$.propControls:[],M=sT($?.coreProps),B=new Set(M.map(ve=>za[ve].apply?.propKey)),N=O.filter(ve=>{const he=Ct(t,ve)?.apply?.propKey;return!(he!==void 0&&B.has(he))}),H=typeof $?.nodeEditor?.kind=="string"?$.nodeEditor.kind:void 0,L=$?.nodeEditor?.params,I=H?rT(H):null,P=!!I,F=Array.isArray($?.dataProps)?$.dataProps:[],G=F.length>0,V=!!(e.iteration&&typeof e.iteration=="object"),Y=D6(e,$),Z=M.length>0||N.length>0||P||G||Y||V,se=Array.isArray($?.styleControls)?$.styleControls:[],re=Array.isArray($?.advanced)?$.advanced:[],ee=(Array.isArray($?.events)?$.events:[]).length>0,fe=Vf($),K=se.length>0||fe!==null,me=!($?.visibilityCondition===!1)&&(Z||K||ee||D||re.length>0),Ce=u.useMemo(()=>{const ve=[];return D&&ve.push("settings"),Z&&ve.push("props"),K&&ve.push("style"),ee&&ve.push("action"),me&&ve.push("visibility"),ve.push("translation"),re.length>0&&ve.push("advanced"),ve},[D,Z,K,ee,me,re.length]),ze=N.length>0||P||G||V,[Se,qe]=u.useState(v&&Ce.includes(v)?v:D?"settings":ze?"props":K?"style":Z?"props":Ce[0]??"style"),De=_A(e),Pe=!Z&&!K&&!D&&!ee&&!me&&re.length===0,Qe=$?.label?$.label.startsWith("$t:")?o($.label.slice(3)):$.label:T||o("layout_editor.property_modal.element"),Me=(ve,te)=>{const he={...e.props??{}};te===void 0?delete he[ve]:he[ve]=te,r({...e,props:he})},Re={settings:o("layout_editor.property_modal.tab.settings"),props:o("layout_editor.property_modal.tab.props"),style:o("layout_editor.property_modal.tab.style"),action:o("layout_editor.property_modal.tab.action"),visibility:o("layout_editor.property_modal.tab.visibility"),translation:o("layout_editor.property_modal.tab.translation"),advanced:o("layout_editor.property_modal.tab.advanced")};return i.jsxs("div",{className:"g7le-property-modal","data-testid":"g7le-property-modal",style:IT,children:[i.jsxs("div",{"data-modal-drag-handle":!0,"data-testid":"g7le-property-modal-header",style:AT,children:[i.jsx("span",{"data-testid":"g7le-property-modal-title",style:{fontSize:14,fontWeight:700},children:D&&Se==="settings"?o("layout_editor.property_modal.settings_title",{name:Qe}):o("layout_editor.property_modal.title",{name:Qe})}),i.jsx("button",{type:"button","data-testid":"g7le-property-modal-close","aria-label":"close",onClick:s,style:TT,children:"✕"})]}),De&&i.jsx("div",{"data-testid":"g7le-property-modal-advanced-badge",style:DT,children:o("layout_editor.property_modal.advanced_badge")}),x&&i.jsx("div",{"data-testid":"g7le-property-modal-branch-badge",style:NT,children:o("layout_editor.property_modal.branch_badge",{device:x})}),i.jsx("div",{style:zT,role:"tablist",children:Ce.map(ve=>i.jsx("button",{type:"button",role:"tab","aria-selected":Se===ve,"data-testid":`g7le-property-tab-${ve}`,"data-active":Se===ve?"true":"false",onClick:()=>qe(ve),style:{...MT,color:Se===ve?"#2563eb":"#64748b",borderBottomColor:Se===ve?"#2563eb":"transparent"},children:Re[ve]},ve))}),i.jsxs("div",{style:LT,"data-testid":"g7le-property-modal-body",children:[Pe&&!C&&i.jsx("div",{"data-testid":"g7le-property-modal-no-editable",style:ig,children:o("layout_editor.property_modal.no_editable_props")}),C&&i.jsx(CT,{injectedProps:_,t:o,onSaveInjectedProps:b}),Se==="settings"&&z&&i.jsx(lA,{spec:z,node:e,t:o,onPatchProp:Me}),Se==="props"&&i.jsxs("div",{"data-testid":"g7le-property-props-tab",style:{display:"flex",flexDirection:"column"},children:[V&&i.jsx(d6,{node:e,candidates:f??[],t:o,onPatchNode:r}),G&&i.jsx(OA,{node:e,dataProps:F,candidates:f??[],t:o,onPatchNode:r}),Y&&i.jsx(r6,{node:e,candidates:f??[],t:o,onPatchNode:r,templateIdentifier:h}),P&&I&&i.jsx(I,{node:e,params:L,spec:t,manifest:n,t:o,onPatchNode:r,templateIdentifier:h,candidates:f}),M.map(ve=>i.jsx(pi,{controlKey:`core-${ve}`,control:za[ve],node:e,t:o,onPatch:r,scope:Wt,bindingCandidates:f},`core-${ve}`)),N.map(ve=>{const te=Ct(t,ve);return te?i.jsx(pi,{controlKey:ve,control:te,node:e,t:o,onPatch:r,scope:Wt,bindingCandidates:f},ve):null}),i.jsx(aT,{node:e,onPatchNode:r,t:o,scopeIdCandidates:k,usedScopeIds:w})]}),Se==="style"&&i.jsxs("div",{"data-testid":"g7le-property-style-tab",style:{display:"flex",flexDirection:"column"},children:[i.jsx("div",{style:rg,children:i.jsx(Yf,{scope:g,onChange:E,node:e,t:o,onClearScope:S})}),fe!==null&&i.jsx(O6,{node:e,spec:t,capability:$,t:o,onPatchNode:r,liveElement:y,scope:g}),se.map(ve=>{const te=Ct(t,ve);return te?i.jsx(pi,{controlKey:ve,control:te,node:e,t:o,onPatch:r,scope:g},ve):null})]}),Se==="action"&&i.jsx(zA,{node:e,spec:t,capability:$,t:o,onPatchNode:r,pageCandidates:d,dataSourceCandidates:c,stateKeyCandidates:p,bindingCandidates:f}),Se==="visibility"&&i.jsxs("div",{"data-testid":"g7le-property-visibility-tab",style:{display:"flex",flexDirection:"column"},children:[i.jsx("div",{style:rg,children:i.jsx(Yf,{scope:g,onChange:E,node:e,t:o,showColorScheme:!1})}),i.jsx(Eu,{node:e,spec:t,t:o,onPatchNode:r,dataSourceCandidates:c,stateKeyCandidates:p,scope:g})]}),Se==="translation"&&(h?i.jsx(Ud,{customKey:Vd(e),templateIdentifier:h,t:o,paramLabels:hv(e),onRemoveParam:ve=>{const te=typeof e.text=="string"?e.text:"";r({...e,text:mo(te,ve)});const he=Vd(e);h&&he&&kr(h,he,ve)}}):i.jsx("div",{"data-testid":"g7le-property-translation-deferred",style:ig,children:o("layout_editor.translation.not_a_key")})),Se==="advanced"&&i.jsx(wA,{node:e,advanced:re,t:o,onPatch:r,permissionCandidates:l})]}),i.jsxs("div",{style:PT,children:[i.jsx("span",{"data-testid":"g7le-property-modal-node-id",style:{fontSize:11,color:"#94a3b8"},children:typeof e.id=="string"||typeof e.id=="number"?`ID: ${e.id}`:""}),i.jsxs("div",{style:{display:"flex",gap:8},children:[i.jsx("button",{type:"button","data-testid":"g7le-property-modal-delete",onClick:a,style:OT,children:o("layout_editor.property_modal.delete_element")}),i.jsx("button",{type:"button","data-testid":"g7le-property-modal-done",onClick:s,style:BT,children:o("layout_editor.property_modal.close")})]})]})]})}const IT={display:"flex",flexDirection:"column",width:420,maxWidth:"92vw"},AT={display:"flex",alignItems:"center",justifyContent:"space-between",padding:"12px 16px",borderBottom:"1px solid #e2e8f0",cursor:"move",userSelect:"none"},TT={border:"none",background:"transparent",fontSize:14,cursor:"pointer",color:"#64748b"},DT={margin:"8px 16px 0",padding:"6px 10px",fontSize:11,background:"#fef3c7",color:"#92400e",borderRadius:6},NT={margin:"8px 16px 0",padding:"6px 10px",fontSize:11,background:"#dbeafe",color:"#1e40af",borderRadius:6},zT={display:"flex",gap:2,padding:"0 12px",borderBottom:"1px solid #e2e8f0",overflowX:"auto"},MT={padding:"8px 10px",fontSize:12,border:"none",borderBottom:"2px solid transparent",background:"transparent",cursor:"pointer",whiteSpace:"nowrap"},LT={padding:16,overflowY:"auto",maxHeight:"60vh"},rg={position:"sticky",top:-16,zIndex:2,background:"#fff",margin:"-16px -16px 0",padding:"6px 16px 0"},ig={fontSize:12,color:"#94a3b8",padding:"16px 0",textAlign:"center"},PT={display:"flex",alignItems:"center",justifyContent:"space-between",padding:"12px 16px",borderTop:"1px solid #e2e8f0"},OT={padding:"6px 12px",fontSize:12,border:"1px solid #fecaca",borderRadius:6,background:"#fff",color:"#dc2626",cursor:"pointer"},BT={padding:"6px 12px",fontSize:12,border:"none",borderRadius:6,background:"#2563eb",color:"#fff",cursor:"pointer"},La="g7le:component-target-picked",Pa="g7le:component-target-pick-request";let WT=1;function jT({value:e,onChange:t,t:n,control:o}){const r=yo(),s=e==null?"":String(e),a=o?.allowManualInput!==!1,l=u.useCallback(()=>{if(typeof window>"u")return;const d=`pick-${WT++}`,p=r.stack[r.stack.length-1]?.id;p&&r.minimize(p,n("layout_editor.target_picker.minimized_hint"));const f=y=>{const g=y.detail;g?.requestId===d&&(window.removeEventListener(La,f),p&&r.restore(p),!g.cancelled&&typeof g.id=="string"&&t(g.id))};window.addEventListener(La,f),window.dispatchEvent(new CustomEvent(Pa,{detail:{requestId:d}}))},[r,t,n]);return i.jsx("div",{className:"g7le-component-target-picker","data-testid":"g7le-component-target-picker",children:i.jsxs("div",{style:{display:"flex",gap:6,alignItems:"center"},children:[a?i.jsx("input",{type:"text",className:"g7le-widget g7le-widget--text","data-testid":"g7le-component-target-input",value:s,placeholder:n("layout_editor.target_picker.id_placeholder"),onChange:d=>t(d.target.value===""?void 0:d.target.value),style:{flex:1,minWidth:0,padding:"6px 8px",border:"1px solid #cbd5e1",borderRadius:6,fontSize:13}}):i.jsx("span",{"data-testid":"g7le-component-target-value",style:{flex:1,minWidth:0},children:s||n("layout_editor.target_picker.none")}),i.jsxs("button",{type:"button","data-testid":"g7le-component-target-pick",onClick:l,style:{display:"flex",alignItems:"center",gap:4,padding:"6px 10px",background:"#1e293b",color:"#fff",border:"none",borderRadius:6,cursor:"pointer",fontSize:13,whiteSpace:"nowrap"},children:[i.jsx("span",{"aria-hidden":"true",children:"🎯"}),n("layout_editor.target_picker.select_area")]})]})})}async function HT(e,t,n,o,r=fetch){const s=`/api/admin/templates/${encodeURIComponent(e)}/layout-extensions/${t}`;let a;try{a=await r(s,{headers:je(),credentials:"same-origin"})}catch(b){return{kind:"error",message:b instanceof Error?b.message:String(b)}}if(a.status===404)return{kind:"not_found"};if(!a.ok)return{kind:"error",message:`HTTP ${a.status}`};const l=await a.json().catch(()=>null),d=(l&&(l.data||l))??{};let c={};const p=d.content;if(typeof p=="string")try{c=JSON.parse(p)}catch{return{kind:"error",message:"invalid extension content"}}else p&&typeof p=="object"&&(c=p);const f=typeof d.lock_version=="number"?d.lock_version:0,y=Array.isArray(c.injections)?[...c.injections]:[],g=y.findIndex(b=>b&&b.position==="inject_props"&&b.target_id===n);if(g<0)return{kind:"injection_not_found"};y[g]={...y[g],props:o};const m={...c,injections:y};let h;try{h=await r(s,{method:"PUT",headers:je({"Content-Type":"application/json"}),credentials:"same-origin",body:JSON.stringify({content:m,expected_lock_version:f})})}catch(b){return{kind:"error",message:b instanceof Error?b.message:String(b)}}const v=await h.json().catch(()=>null);return h.ok?{kind:"success",newLockVersion:typeof v?.data?.lock_version=="number"?v.data.lock_version:f+1}:h.status===409?{kind:"conflict",currentVersion:v?.current_version??-1,yourVersion:v?.your_version??f}:{kind:"error",message:v?.message??`HTTP ${h.status}`}}const sg=new Map;function Oa(e,t){sg.set(e,t)}function FT(e){return e?sg.get(e)??null:null}const ag="g7le.clipboard";function lg(){try{return typeof window>"u"||!window.sessionStorage?null:window.sessionStorage}catch{return null}}function KT(e){const t=JSON.parse(JSON.stringify(e)),n=o=>{for(const r of Object.keys(o))r.startsWith("__")&&delete o[r];Array.isArray(o.children)&&o.children.forEach(n)};return n(t),t}function dg(e,t=0){const n=lg();if(!n)return!1;try{const o={node:KT(e),copiedAt:t};return n.setItem(ag,JSON.stringify(o)),!0}catch{return!1}}function VT(){const e=lg();if(!e)return null;try{const t=e.getItem(ag);if(!t)return null;const n=JSON.parse(t);return!n||typeof n.node!="object"||n.node===null?null:JSON.parse(JSON.stringify(n.node))}catch{return null}}const Xo={nw:{width:-1,height:-1},n:{width:0,height:-1},ne:{width:1,height:-1},e:{width:1,height:0},se:{width:1,height:1},s:{width:0,height:1},sw:{width:-1,height:1},w:{width:-1,height:0}};function Ba(e){if(typeof e=="number")return e;if(typeof e=="string"){const t=e.match(/^(-?\d+(?:\.\d+)?)px$/);if(t)return Number(t[1])}return null}function UT(e,t){const n=Array.isArray(e.options)?e.options:[],o=Array.isArray(e.scale)?e.scale:[],r=n.length>0?n.map(l=>l.value):o;if(r.length===0)return;let s=r[0],a=1/0;return r.forEach((l,d)=>{const c=Ba(l)??d*40,p=Math.abs(c-t);p{if(!t)return;const m=Xo[y];if(m.width!==0&&!n||m.height!==0&&!o)return;const h=t.props?.style??{},v=s?.()??null,b=Ba(h.width)??(v?Math.round(v.width):0),x=Ba(h.height)??(v?Math.round(v.height):0);p.current={handle:y,startX:g.clientX,startY:g.clientY,startW:b,startH:x,node:t};const k=m.width!==0&&m.height!==0?"both":m.width!==0?"width":"height";d?.(k);let w=null;const _=E=>{const S=p.current;if(!S)return;const T=r||1,$=(E.clientX-S.startX)/T*Xo[S.handle].width,A=(E.clientY-S.startY)/T*Xo[S.handle].height;let z=S.node;if(Xo[S.handle].width!==0&&n){const D=cg(n,S.startW+$);z=fn(z,n,D)}if(Xo[S.handle].height!==0&&o){const D=cg(o,S.startH+A);z=fn(z,o,D)}w=z,a(z,k)},C=()=>{p.current=null,d?.(null),window.removeEventListener("pointermove",_),window.removeEventListener("pointerup",C),w&&l?.(w,k)};window.addEventListener("pointermove",_),window.addEventListener("pointerup",C)},[t,n,o,r,a,l,d]);return{enabledAxes:c,onHandlePointerDown:f}}const ug=320,pg=1920,YT=1024;function Wa(e){return Number.isFinite(e)?Math.round(Math.max(ug,Math.min(pg,e))):YT}function ja(e={}){const{state:t,dispatch:n}=Je(),{previewDevice:o,previewZoom:r,previewCustomWidth:s,previewColorScheme:a}=t,l=o==="custom"?Wa(s):Y6(o),d=u.useMemo(()=>{const g=e.availableWidth??l,m=l*r;return r>=1||m<=g?r:Math.min(r,g/l)},[l,r,e.availableWidth]),c=u.useCallback(g=>{n({type:"SET_PREVIEW_DEVICE",device:g})},[n]),p=u.useCallback(g=>{const m=Math.max(.5,Math.min(1,g));n({type:"SET_PREVIEW_ZOOM",zoom:m})},[n]),f=u.useCallback(g=>{n({type:"SET_PREVIEW_CUSTOM_WIDTH",width:Wa(g)})},[n]),y=u.useCallback(g=>{n({type:"SET_PREVIEW_COLOR_SCHEME",scheme:g})},[n]);return{device:o,deviceWidth:l,zoom:r,scale:d,customWidth:Wa(s),setDevice:c,setZoom:p,setCustomWidth:f,colorScheme:a,setColorScheme:y}}const yi="g7le-dnd-handle-";function XT(e){if(typeof e!="string"||!e.startsWith(yi))return null;const t=e.slice(yi.length);return t.length>0?t:null}function JT(e){return typeof CSS<"u"&&typeof CSS.escape=="function"?CSS.escape(e):e.replace(/["\\\]]/g,"\\$&")}function ZT(e,t,n,o){const r=o?.handleFallback??!1;if(e instanceof HTMLElement){const s=e.closest("[data-editor-path]");if(s)return s;const a=e.closest('[data-testid^="g7le-"]');if(a&&!(a.getAttribute("data-testid")??"").startsWith(yi))return null;if(r){const l=e.closest(`[data-testid^="${yi}"]`);if(l&&t){const d=XT(l.getAttribute("data-testid"));if(d){const c=t.querySelector(`[data-editor-path="${JT(d)}"]`);if(c)return c}}}}if(r&&n&&typeof document<"u"&&typeof document.elementsFromPoint=="function")for(const s of document.elementsFromPoint(n.x,n.y)){const a=s.closest?.("[data-editor-path]");if(a)return a}return null}function QT(e,t,n){if(typeof e=="string"&&e.trim().length>0)return{color:e};const o=typeof n?.color=="string"&&n.color.length>0,r=typeof t=="string"&&t.trim().length>0;return o||r?{}:{color:"#0f172a"}}const RT="#ffffff",e4="#0f172a";function t4(e){const t=e.trim(),n=t.match(/^rgba?\(\s*([\d.]+)[\s,]+([\d.]+)[\s,]+([\d.]+)/i);if(n)return[Number(n[1]),Number(n[2]),Number(n[3])];const o=t.replace(/^#/,"");return/^[0-9a-f]{3}$/i.test(o)?[parseInt(o[0]+o[0],16),parseInt(o[1]+o[1],16),parseInt(o[2]+o[2],16)]:/^[0-9a-f]{6}$/i.test(o)?[parseInt(o.slice(0,2),16),parseInt(o.slice(2,4),16),parseInt(o.slice(4,6),16)]:null}function n4(e){if(!e||/transparent/i.test(e))return!1;const t=e.trim().match(/^oklch\(\s*([\d.]+)(%?)/i);if(t)return(t[2]==="%"?Number(t[1])/100:Number(t[1]))>=.62;const n=t4(e);if(!n)return!1;const[o,r,s]=n;return .299*o+.587*r+.114*s>=180}function o4(e,t,n){const o=typeof n?.color=="string"?n.color:null;return n4(e??o??t)?e4:RT}function r4({box:e,initialValue:t,isCustomKey:n,t:o,onCommit:r,onCancel:s,mirrorClassName:a,mirrorStyle:l,nodeEffectiveColor:d,onOpenTranslations:c}){const p=u.useRef(null),f=u.useRef(!1),[y,g]=u.useState(t.trim().length>0),[m,h]=u.useState(null);u.useEffect(()=>{const w=p.current;if(w){w.textContent=t,w.focus();try{const _=window.document.createRange();_.selectNodeContents(w);const C=window.getSelection();C?.removeAllRanges(),C?.addRange(_)}catch{}f.current=!1}},[t]),u.useEffect(()=>{const w=p.current;if(!w||typeof window.getComputedStyle!="function"){h(null);return}try{h(window.getComputedStyle(w).color||null)}catch{h(null)}},[a,l]);const v=()=>(p.current?.textContent??"").trim(),b=()=>{f.current||(f.current=!0,r(v()))},x=()=>{f.current||(f.current=!0,s())},k=w=>{w.key==="Enter"&&!w.shiftKey?(w.preventDefault(),b()):w.key==="Escape"&&(w.preventDefault(),x())};return i.jsxs("div",{className:"g7le-inline-text-editor","data-testid":"g7le-inline-text-editor","data-custom-key":n?"true":"false",style:{position:"absolute",left:e.left,top:e.top,minWidth:Math.max(e.width,60),minHeight:e.height,pointerEvents:"auto",zIndex:1e4},children:[i.jsx("div",{ref:p,role:"textbox","aria-label":o("layout_editor.inline_edit.aria_label"),"aria-multiline":"false","data-testid":"g7le-inline-text-editable",className:a,contentEditable:!0,suppressContentEditableWarning:!0,spellCheck:!1,onInput:()=>g(v().length>0),onKeyDown:k,onBlur:b,style:{outline:"2px solid #2563eb",outlineOffset:1,borderRadius:3,background:o4(d,m,l),padding:"2px 4px",minHeight:e.height,whiteSpace:"pre-wrap",wordBreak:"break-word",boxShadow:"0 2px 8px rgba(37, 99, 235, 0.25)",...l??{},...QT(d,a,l)}}),i.jsxs("div",{className:"g7le-inline-text-editor__hint","data-testid":"g7le-inline-text-editor-hint",style:{marginTop:2,display:"inline-flex",alignItems:"center",gap:4,whiteSpace:"nowrap"},children:[c?i.jsxs("button",{type:"button","data-testid":"g7le-inline-text-editor-open-translations",onMouseDown:w=>w.preventDefault(),onClick:w=>{w.preventDefault(),w.stopPropagation(),c()},title:o("layout_editor.inline_edit.open_translations"),style:{padding:"2px 8px",fontSize:11,background:n?"#eff6ff":"#f0fdf4",color:n?"#1d4ed8":"#15803d",border:`1px solid ${n?"#bfdbfe":"#bbf7d0"}`,borderRadius:4,display:"inline-flex",alignItems:"center",gap:4,cursor:"pointer",whiteSpace:"nowrap"},children:[i.jsx("span",{"aria-hidden":"true",children:"🌐"}),i.jsx("span",{children:o(n?"layout_editor.inline_edit.existing_key_hint":"layout_editor.inline_edit.new_key_hint")}),i.jsxs("span",{"aria-hidden":"true",style:{opacity:.7},children:["· ",o("layout_editor.inline_edit.open_translations")," ›"]})]}):i.jsx("span",{style:{padding:"2px 6px",fontSize:11,background:n?"#eff6ff":"#f0fdf4",color:n?"#1d4ed8":"#15803d",border:`1px solid ${n?"#bfdbfe":"#bbf7d0"}`,borderRadius:4,display:"inline-flex",alignItems:"center",gap:4,whiteSpace:"nowrap"},children:n?i.jsxs(i.Fragment,{children:["⌐ ",o("layout_editor.inline_edit.existing_key_hint")]}):i.jsxs(i.Fragment,{children:["✎ ",o("layout_editor.inline_edit.new_key_hint")]})}),!y&&i.jsx("span",{"data-testid":"g7le-inline-text-editor-empty-warn",style:{color:"#b91c1c",marginLeft:4},children:o("layout_editor.inline_edit.empty_warning")})]})]})}function i4({box:e,node:t,customKey:n,initialChipValue:o,chipParamLabels:r,onKeyify:s,templateIdentifier:a,locale:l,t:d,candidates:c,onInsertBinding:p,onRemoveBinding:f,onCommit:y,onCancel:g}){const[m,h]=u.useState(null),v=u.useRef(""),[b,x]=u.useState(!1),k=u.useRef(!1),w=u.useRef(null),_=Ft(),C=u.useRef(()=>{}),[E,S]=u.useState(null),[T,$]=u.useState(!1),A=u.useRef(n??null);u.useEffect(()=>{n&&A.current!==n&&(A.current=n)},[n]);const z=u.useMemo(()=>{if(!n&&r)return r;const L={},I=mt(typeof t.text=="string"?t.text:"");for(const P of I?.params??[])L[P.name]=Vt(P.expression);return L},[t.text,n,r]);u.useEffect(()=>{let L=!1;if(!n){const I=o??"";return v.current=I,h(I),()=>{L=!0}}return(async()=>{const I=It(n,l);if(I!==void 0){v.current=I,h(I);return}const P=await cn(a,n);if(L)return;const F=P?.values?.[l]??"";v.current=F,h(F)})(),()=>{L=!0}},[a,n,l,o]);const D=async()=>{if(k.current)return;k.current=!0;const L=m??"";if(L===v.current){y();return}x(!0);let I=A.current;if(!I){if(!s){x(!1),k.current=!1;return}if(I=await s(L),x(!1),!I){k.current=!1;return}A.current=I,y();return}const P=await ko(a,I,l,L);if(x(!1),P.kind==="error"){k.current=!1;return}_?.markDirty?.(),y()},O=()=>{k.current||(k.current=!0,g())},M=L=>{S(L)},B=async L=>{if(!(E===null||!p||T)){$(!0);try{const I=A.current;I&&m!==null&&m!==v.current&&(await ko(a,I,l,m),v.current=m);const P=await p(E,L);P!==null&&(h(P),v.current=P,_?.markDirty?.())}finally{$(!1),S(null)}}},N=async L=>{if(!(!f||T)){$(!0);try{const I=A.current;I&&m!==null&&m!==v.current&&(await ko(a,I,l,m),v.current=m);const P=await f(L);P!==null&&(h(P),v.current=P,_?.markDirty?.())}finally{$(!1)}}},H=L=>{L.key==="Enter"&&!L.shiftKey?(L.preventDefault(),D()):L.key==="Escape"&&(L.preventDefault(),O())};return C.current=()=>{D()},u.useEffect(()=>{const L=P=>{const F=w.current;if(!F)return;const G=P.target;if(G&&F.contains(G))return;const V=G;V&&V.closest&&V.closest('[data-testid="g7le-inline-toolbar"], .g7le-inline-toolbar')||C.current()},I=w.current?.ownerDocument??document;return I.addEventListener("pointerdown",L,!0),()=>I.removeEventListener("pointerdown",L,!0)},[]),i.jsxs("div",{ref:w,className:"g7le-inline-param-chip-editor","data-testid":"g7le-inline-param-chip-editor",onKeyDown:H,style:{position:"absolute",left:e.left,top:e.top,minWidth:Math.max(e.width,120),pointerEvents:"auto",zIndex:1e4,background:"#fff",border:"2px solid #2563eb",borderRadius:4,boxShadow:"0 2px 8px rgba(37,99,235,0.25)",padding:4},children:[m===null?i.jsx("div",{"data-testid":"g7le-inline-param-chip-loading",style:{fontSize:12,color:"#94a3b8",padding:4},children:d("layout_editor.translation.loading")}):i.jsx(mr,{value:m,onChange:h,t:d,paramLabels:z,testIdSuffix:"inline",onRequestInsert:p?M:void 0,onRemoveChip:f?L=>{N(L)}:void 0}),E!==null&&p&&i.jsx("div",{"data-testid":"g7le-inline-param-chip-insert-picker",style:{marginTop:4},children:i.jsx($t,{candidates:c??[],t:d,onSelect:L=>{B(L)},testIdSuffix:"inline-insert",defaultOpen:!0,floating:!1})}),i.jsxs("div",{style:{display:"flex",alignItems:"center",gap:6,marginTop:4},children:[i.jsx("button",{type:"button","data-testid":"g7le-inline-param-chip-save",disabled:b,onMouseDown:L=>L.preventDefault(),onClick:()=>{D()},style:{fontSize:11,border:"none",borderRadius:6,background:"#2563eb",color:"#fff",padding:"4px 12px",cursor:"pointer"},children:d(b?"layout_editor.translation.saving":"layout_editor.translation.save")}),i.jsx("span",{style:{fontSize:10,color:"#94a3b8"},children:d("layout_editor.translation.placeholder_locked")})]})]})}const s4=["fontWeight","fontBold","fontStyle","fontItalic","textDecoration","textUnderline","textAlign","fontSize","textColor"],a4={fontWeight:"B",fontBold:"B",fontStyle:"I",fontItalic:"I",textDecoration:"U",textUnderline:"U",textAlign:"≡",fontSize:"T",textColor:"A"},l4=[["fontWeight","fontBold"],["fontItalic","fontStyle"],["textUnderline","textDecoration"]];function fg({node:e,spec:t,t:n,onApplyControl:o,box:r,scope:s=Wt}){const a=typeof e.name=="string"?e.name:"",l=Oi(t,a),d=Array.isArray(l?.styleControls)?l.styleControls:[];let c=s4.filter(p=>d.includes(p)&&Ct(t,p)!==null);for(const p of l4){const f=p.filter(y=>c.includes(y));if(f.length>1){const y=f[0];c=c.filter(g=>!p.includes(g)||g===y)}}return c.length===0?null:i.jsx("div",{className:"g7le-inline-toolbar","data-testid":"g7le-inline-toolbar",role:"toolbar","aria-label":n("layout_editor.inline_edit.toolbar_label"),style:{position:"absolute",left:r.left,top:r.top-40,display:"inline-flex",gap:2,padding:4,background:"#0f172a",borderRadius:8,boxShadow:"0 4px 12px rgba(15, 23, 42, 0.35)",pointerEvents:"auto",zIndex:10001},children:c.map(p=>i.jsx(c4,{controlKey:p,node:e,spec:t,t:n,scope:s,onApplyControl:o},p))})}const d4={fontWeight:"bold",fontBold:"bold",fontStyle:"italic",fontItalic:"italic",textDecoration:"underline",textUnderline:"underline"};function gg(e,t){const n=r=>(r.split(/[-\s]/).pop()??r)===t||r===t;return e.find(r=>{const s=typeof r.value=="string"?r.value:"",a=Array.isArray(r.apply?.tokens)?r.apply.tokens:[];return n(s)||a.some(n)})?.value}function c4({controlKey:e,node:t,spec:n,t:o,scope:r,onApplyControl:s}){const[a,l]=u.useState(!1),d=Ct(n,e);if(!d)return null;const c=d.options,p=Array.isArray(c),f=Do(t,d,r),y=d4[e],g=y&&Array.isArray(c)?gg(c,y):void 0,m=!y,h=g!==void 0?f.value===g:p&&f.matched&&f.value!==void 0&&f.value!=="",v=o(`layout_editor.inline_edit.format.${e}`),b=_=>typeof _.label=="string"?_.label.startsWith("$t:")?o(_.label.slice(3)):_.label:String(_.value??""),x=()=>{s(e,_=>{const C=Do(_,d,r),E=Array.isArray(c)?gg(c,y):void 0,S=E!==void 0&&C.value===E?void 0:E;return fn(_,d,S,r)})},k=_=>{s(e,C=>{const S=Do(C,d,r).value===_?void 0:_;return fn(C,d,S,r)}),l(!1)},w=_=>{if(_.preventDefault(),_.stopPropagation(),!m&&Array.isArray(c)){x();return}l(C=>!C)};return i.jsxs("div",{style:{position:"relative",display:"inline-flex"},children:[i.jsxs("button",{type:"button","data-testid":`g7le-inline-format-${e}`,"data-active":h?"true":"false","aria-label":v,"aria-pressed":p?h:void 0,title:v,onMouseDown:_=>_.preventDefault(),onClick:w,style:{display:"inline-flex",alignItems:"center",gap:5,height:28,padding:"0 9px",fontSize:12,border:"none",borderRadius:5,cursor:"pointer",background:h?"#2563eb":"transparent",color:"#ffffff",whiteSpace:"nowrap"},children:[i.jsx("span",{"aria-hidden":"true",style:{fontWeight:e==="fontWeight"||e==="fontBold"?700:600,fontStyle:e==="fontStyle"||e==="fontItalic"?"italic":"normal",textDecoration:e==="textDecoration"||e==="textUnderline"?"underline":"none"},children:a4[e]}),i.jsx("span",{children:v}),m&&i.jsx("span",{"aria-hidden":"true",style:{fontSize:9,opacity:.8},children:"▾"})]}),m&&a&&i.jsx("div",{"data-testid":`g7le-inline-popover-${e}`,role:"listbox",onMouseDown:_=>_.preventDefault(),style:{position:"absolute",top:32,left:0,minWidth:140,maxHeight:240,overflowY:"auto",background:"#0f172a",border:"1px solid #334155",borderRadius:8,boxShadow:"0 8px 24px rgba(15,23,42,0.45)",padding:4,zIndex:10002},children:Array.isArray(c)&&c.length>0?c.map((_,C)=>{const E=f.value===_.value;return i.jsxs("button",{type:"button",role:"option","aria-selected":E,"data-testid":`g7le-inline-option-${e}-${String(_.value??C)}`,onMouseDown:S=>S.preventDefault(),onClick:S=>{S.preventDefault(),S.stopPropagation(),k(_.value)},style:{display:"flex",alignItems:"center",gap:8,width:"100%",padding:"6px 8px",fontSize:12,textAlign:"left",border:"none",borderRadius:5,cursor:"pointer",background:E?"#2563eb":"transparent",color:"#ffffff"},children:[typeof _.swatch=="string"&&i.jsx("span",{"aria-hidden":"true",style:{width:14,height:14,borderRadius:3,background:_.swatch,border:"1px solid rgba(255,255,255,0.3)"}}),i.jsx("span",{children:b(_)}),E&&i.jsx("span",{"aria-hidden":"true",style:{marginLeft:"auto"},children:"✓"})]},String(_.value??C))}):i.jsx(u4,{testid:`g7le-inline-freeinput-${e}`,placeholder:o(`layout_editor.inline_edit.prompt.${e}`),initial:typeof f.value=="string"?f.value:"",onSubmit:_=>{s(e,C=>fn(C,d,_,r)),l(!1)}})})]})}function u4({testid:e,placeholder:t,initial:n,onSubmit:o}){const[r,s]=u.useState(n);return i.jsx("input",{type:"text","data-testid":e,value:r,placeholder:t,autoFocus:!0,onMouseDown:a=>a.stopPropagation(),onChange:a=>s(a.currentTarget.value),onKeyDown:a=>{a.key==="Enter"&&(a.preventDefault(),o(r.trim()))},style:{width:130,padding:"6px 8px",fontSize:12,border:"1px solid #334155",borderRadius:5,background:"#1e293b",color:"#ffffff"}})}function p4(e){const{frameEl:t,manifest:n,nesting:o,componentPalette:r,spec:s=null,permissionCandidates:a,resolveRouteMatch:l,onNavigateToDestination:d,onSessionAddedPathsChange:c,onEditProps:p}=e,{state:f,dispatch:y}=Je(),{t:g}=tt(),m=ja(),h=u.useCallback((W,X)=>{const R=window.G7Core;if(R&&typeof R.t=="function"){const de=R.t(W,X);if(de&&de!==W&&!de.startsWith("$t:"))return de}return g(W,X)},[g]),v=Ft(),b=d7(50),x=yo(),k=u.useRef([]),w=u.useMemo(()=>v?.document?.raw?.components??[],[v?.document?.raw?.components]),_=u.useMemo(()=>w.length===0?null:{children:w},[w]),C=u.useMemo(()=>{if(f.editMode!=="extension")return;const W=/^__extension__\/(\d+)/.exec(f.selectedRoute?.path??"");return W?Number(W[1]):void 0},[f.editMode,f.selectedRoute?.path]),E=u.useMemo(()=>{if(f.editMode==="iteration_item"){const W=v?.document?.iterationContext?.sourceIndexPath;return Array.isArray(W)&&W.length>0?W:null}if(f.editMode==="modal"){const W=v?.document?.modalContext?.editIndexPath;return Array.isArray(W)&&W.length>0?W:null}return null},[f.editMode,v?.document?.iterationContext?.sourceIndexPath,v?.document?.modalContext?.editIndexPath]),S=e7({rootNode:_,editMode:f.editMode,currentExtensionId:C,editableRootPath:E,pathParser:Ze,resolveRouteMatch:l}),[T,$]=u.useState(null),[A,z]=u.useState(null),[D,O]=u.useState(null),M=u.useRef(null);M.current=D;const[B,N]=u.useState(null),H=u.useRef(null),L=u.useRef(null);L.current=S.selectedPath;const I=Wb(),[P,F]=u.useState(null),G=u.useCallback(W=>{if(!t)return;const X=Ze(W);if(!X)return;const R={children:Me.current.components},de=We(R,X),ye=I.classify(de);if(!ye.editable){Et({op:"inline_edit_blocked_binding",sourceState:ye.sourceState,componentPath:at(X),timestamp:Date.now()});return}const we=po(t,W);if(!we)return;const Te=de?.props??{},ke=yg(t,W);F({path:X,domPath:W,box:we,initialValue:ye.displayValue,isCustomKey:ye.sourceState==="custom_key",isParamKey:ye.isParamKey===!0,customKey:ye.customKey,chipValue:ye.chipValue,chipParamLabels:ye.chipParamLabels,mirrorClassName:typeof Te.className=="string"?Te.className:void 0,mirrorStyle:Te.style??void 0,nodeEffectiveColor:ke}),Et({op:"inline_edit_enter",sourceState:ye.sourceState,componentPath:at(X),timestamp:Date.now()})},[t,I]),V=u.useCallback(()=>{if(!t){$(null),z(null);return}z(po(t,S.selectedPath)),$(po(t,S.hoverPath))},[t,S.selectedPath,S.hoverPath,w]);u.useEffect(()=>{if(V(),typeof window<"u"&&typeof window.requestAnimationFrame=="function"){const W=window.requestAnimationFrame(()=>V());return()=>window.cancelAnimationFrame(W)}},[V]),u.useEffect(()=>{if(t)return $p(V,t)},[t,V]);const Y=u.useCallback((W,X)=>{if(typeof window>"u")return;window.dispatchEvent(new CustomEvent(La,{detail:X===null?{requestId:W,cancelled:!0}:{requestId:W,id:X}})),O(null),N(null);const R=H.current;R?S.handleSelect("",{dataset:{editorPath:R}}):S.clearSelection(),H.current=null},[S]);u.useEffect(()=>{if(typeof window>"u")return;const W=X=>{const R=X.detail;if(typeof R?.requestId!="string")return;const de=M.current;de&&de!==R.requestId&&Y(de,null),H.current=L.current,O(R.requestId)};return window.addEventListener(Pa,W),()=>window.removeEventListener(Pa,W)},[Y]),u.useEffect(()=>{if(!D)return;const W=X=>{X.key==="Escape"&&(X.preventDefault(),Y(D,null))};return window.addEventListener("keydown",W,!0),()=>window.removeEventListener("keydown",W,!0)},[D,Y]),u.useEffect(()=>{if(!t)return;const W=f.editMode==="iteration_item"||f.editMode==="modal",X=(xe,Ee)=>ZT(xe,t,Ee?{x:Ee.clientX,y:Ee.clientY}:void 0,{handleFallback:W}),R=xe=>{const Ee=xe.dataset.editorPath??"",Fe=Ee?Ze(Ee):null,zt={children:Me.current.components},gt=Fe?We(zt,Fe):null,Mt=gt&&typeof gt.props?.id=="string"?gt.props.id:typeof gt?.id=="string"?gt.id:"";return Mt.startsWith("auto_")?"":Mt},de=xe=>{const Ee=X(xe.target,xe);if(!Ee)return;const Fe=M.current;if(Fe){xe.preventDefault(),xe.stopPropagation();const zt=R(Ee);zt&&Y(Fe,zt);return}xe.preventDefault(),xe.stopPropagation(),S.handleSelect(Ee.dataset.editorId??"",{currentTarget:Ee,dataset:Ee.dataset})},ye=xe=>{const Ee=X(xe.target,xe);if(M.current){if(!Ee){N(null);return}const Fe=po(t,Ee.dataset.editorPath??null);if(!Fe){N(null);return}N({box:Fe,hasId:R(Ee)!==""});return}if(!Ee){S.handleHover(null,{});return}S.handleHover(Ee.dataset.editorId??"",{currentTarget:Ee,dataset:Ee.dataset})},we=()=>{if(M.current){N(null);return}S.handleHover(null,{})},Te=xe=>{const Ee=X(xe.target,xe);if(!Ee)return;const Fe=Ee.dataset.editorPath??"";Fe&&(xe.preventDefault(),xe.stopPropagation(),G(Fe))},ke=t.parentElement??t;return ke.addEventListener("click",de,!0),ke.addEventListener("mousemove",ye,!0),ke.addEventListener("mouseleave",we,!0),ke.addEventListener("dblclick",Te,!0),()=>{ke.removeEventListener("click",de,!0),ke.removeEventListener("mousemove",ye,!0),ke.removeEventListener("mouseleave",we,!0),ke.removeEventListener("dblclick",Te,!0)}},[t,S,G,f.editMode,Y]);const Z=u.useMemo(()=>{const W=S.selectedPath;if(!W)return null;const X=W.match(/^(.*)\.children\.\d+$/);return X?X[1]:null},[S.selectedPath]),se=u.useCallback(()=>{Z&&S.handleSelect("",{dataset:{editorPath:Z}})},[Z,S]),re=u.useMemo(()=>!t||!S.selectedPath?null:t.querySelector(`[data-editor-path="${an(S.selectedPath)}"]`),[t,S.selectedPath]),U=u.useMemo(()=>S.selectedPath?Ze(S.selectedPath):null,[S.selectedPath]),ee=u.useMemo(()=>{if(!U)return null;const W=U.find(et);if(!W)return null;const{i18nKey:X,raw:R}=bC(W.responsive);return X?g(X):g("layout_editor.overlay.branch_custom",{range:R})},[U,g]),fe=u.useMemo(()=>S.selectedPath?S.selectedIsIteration&&S.selectedIterationSourcePath?S.selectedIterationSourcePath:S.selectedPath:null,[S.selectedPath,S.selectedIsIteration,S.selectedIterationSourcePath]),K=u.useMemo(()=>fe?Ze(fe):null,[fe]),ne=u.useMemo(()=>!t||!fe?null:t.querySelector(`[data-editor-path="${an(fe)}"]`),[t,fe]),me=u.useMemo(()=>{if(ne?.parentElement)return ne.parentElement;if(!t||!fe)return null;const W=fe.match(/^(.*)\.children\.\d+$/);return W?t.querySelector(`[data-editor-path="${an(W[1])}"]`):null},[ne,t,fe]),Ce=u.useMemo(()=>{const W=S.selectedNode;return W&&(typeof W.name=="string"?W.name:typeof W.type=="string"?W.type:"")||null},[S.selectedNode]),ze=u.useMemo(()=>{if(S.selectedLockKind!=="extension")return null;const W=S.selectedNode?.__source;if(!W||W.kind!=="extension")return null;const X=W.extensionName?.trim(),R=W.extensionIdentifier?.trim();return X&&R?`${X} (${R})`:X||R||W.extensionSourceType||null},[S.selectedLockKind,S.selectedNode]),Se=s7({parentEl:me,selectedPath:K,selectedEl:ne}),qe=u.useMemo(()=>!t||!fe||fe===S.selectedPath?A:po(t,fe),[t,fe,A,S.selectedPath,w]),De=u.useRef(null),Pe=u.useCallback(()=>{const W=De.current;W&&(x.close(W),De.current=null)},[x]),Qe=u.useRef(()=>{}),Me=u.useRef({manifest:n,nesting:o,componentPalette:r,components:w});Me.current={manifest:n,nesting:o,componentPalette:r,components:w};const Re=u.useCallback(W=>{const X=W.insertion?.parentPath??[],R=W.insertion?.index??0,de=Me.current,ye=hg(de.components,X);Pe();const we=x.open({ariaLabel:h("layout_editor.palette.title"),width:880,maxHeightRatio:.82,content:u.createElement(Ef,{manifest:de.manifest,nesting:de.nesting,componentPalette:de.componentPalette??null,targetParentPath:X,targetContainerName:ye,onInsert:(Te,ke,xe)=>{Qe.current(Te,ke,xe)},insertionIndex:R,onClose:()=>Pe(),editorTemplateId:f.templateIdentifier,editorLocale:f.locale}),onClose:()=>{De.current=null}});De.current=we},[x,Pe]),ve=u.useMemo(()=>{if(!f.isPaletteOpen)return null;if(!U)return{parentPath:[],index:w.length};if(S.selectedLockKind!=="none"){const de=K;return!de||de.length===0?{parentPath:[],index:w.length}:{parentPath:de.slice(0,-1),index:(de[de.length-1]??0)+1}}const X=S.selectedNode,R=Array.isArray(X?.children)?X.children.length:0;return lI(X?.name,R,U,o,w.length)},[f.isPaletteOpen,U,K,S.selectedLockKind,S.selectedNode,w.length,o]),te=u.useCallback(()=>{y({type:"SET_PALETTE_OPEN",open:!1})},[y]),he=u.useRef(null);u.useEffect(()=>{if(!f.isPaletteOpen){const ye=he.current;ye&&(x.close(ye),he.current=null);return}if(!ve||he.current)return;const W=ve,X=Me.current,R=hg(X.components,W.parentPath),de=x.open({ariaLabel:h("layout_editor.palette.title"),width:880,maxHeightRatio:.82,content:u.createElement(Ef,{manifest:X.manifest,nesting:X.nesting,componentPalette:X.componentPalette??null,targetParentPath:W.parentPath,targetContainerName:R,onInsert:(ye,we,Te)=>{Qe.current(ye,we,Te),te()},insertionIndex:W.index,onClose:()=>te(),editorTemplateId:f.templateIdentifier,editorLocale:f.locale}),onClose:()=>{he.current=null,y({type:"SET_PALETTE_OPEN",open:!1})}});he.current=de},[f.isPaletteOpen,ve,x,te,y]);const Q=u.useRef({layoutName:null,pushed:!1});u.useEffect(()=>{const W=v?.document?.layoutName??null;if(!W){Q.current={layoutName:null,pushed:!1};return}Q.current.layoutName!==W&&(b.clear(),b.push({actionKind:"inline_text_edit",label:"baseline",snapshot:w}),Q.current={layoutName:W,pushed:!0})},[v?.document?.layoutName,w,b]);const ie=u.useRef(0);u.useEffect(()=>{const W=v?.saveSuccessCounter??0;W!==ie.current&&(ie.current=W,W!==0&&(b.clear(),b.push({actionKind:"inline_text_edit",label:"save_baseline",snapshot:w}),k.current=[],c?.([])))},[v?.saveSuccessCounter,w,b,c]);const _e=u.useRef(0);u.useEffect(()=>{const W=v?.reloadCounter??0;W!==_e.current&&(_e.current=W,W!==0&&(Q.current={layoutName:null,pushed:!1},k.current=[],c?.([])))},[v?.reloadCounter,c]);const ge=u.useCallback((W,X,R)=>{if(!v)return;let de=[];v.patchLayout(ke=>{const xe={children:ke},Ee=Ha(xe,X),Fe=R<0?Array.isArray(Ee?.children)?Ee.children.length:0:R,gt=Qi(xe,X,Fe,W).children??[];return de=gt,gt}),b.push({actionKind:"insert",label:`insert ${W.name??W.type??""}`,snapshot:de});const ye=Ha({children:de},X),we=R<0?Array.isArray(ye?.children)?ye.children.length-1:0:R,Te=at([...X,we]);if(k.current=[...k.current,Te],c?.(k.current),t){const ke=`[data-editor-path="${an(Te)}"]`,xe=()=>{const Ee=t.querySelector(ke);Ee&&typeof Ee.scrollIntoView=="function"&&Ee.scrollIntoView({behavior:"smooth",block:"center",inline:"nearest"})};typeof window<"u"&&typeof window.requestAnimationFrame=="function"?window.requestAnimationFrame(()=>window.requestAnimationFrame(xe)):setTimeout(xe,32)}},[v,b,c,t]);Qe.current=ge;const Ae=u.useCallback(()=>{if(!v||!S.selectedNode||!U)return;const W=es(S.selectedNode);let X=[];v.patchLayout(R=>{const de={children:R},ye=U.slice(0,-1),we=U[U.length-1]??0,ke=Qi(de,ye,we+1,W).children??[];return X=ke,ke}),b.push({actionKind:"insert",label:"duplicate",snapshot:X})},[v,S.selectedNode,U,b]),Ne=u.useCallback(()=>{if(!v||!U)return;let W=[];v.patchLayout(X=>{const ye=Ri({children:X},U).children??[];return W=ye,ye}),b.push({actionKind:"remove",label:"remove",snapshot:W}),S.clearSelection()},[v,U,b,S]),Le=u.useMemo(()=>{if(!S.selectedNode||!U||U.some(et))return null;const W=S.selectedNode;if(!Array.isArray(W.children))return null;const X=yC(f.previewDevice,W,f.previewCustomWidth);return{mode:X.mode,key:X.key,sourceKey:X.sourceKey}},[S.selectedNode,U,f.previewDevice,f.previewCustomWidth]),q=u.useCallback(()=>{if(!v||!U||Le?.mode!=="separate")return;const W=Le.key,X=Le.sourceKey;let R=[];v.patchLayout(de=>(R=Ab({children:de},U,W,X).children??[],R)),b.push({actionKind:"insert",label:"separate_branch",snapshot:R})},[v,U,Le,b]),ae=u.useCallback(()=>{if(!v||!U||Le?.mode!=="merge")return;const W=Le.key;let X=[];v.patchLayout(R=>(X=Tb({children:R},U,W).children??[],X)),b.push({actionKind:"remove",label:"merge_branch",snapshot:X})},[v,U,Le,b]),pe=u.useCallback(W=>{switch(W){case"mobile":return g("layout_editor.context_menu.device_mobile");case"portable":return g("layout_editor.context_menu.device_portable");case"tablet":return g("layout_editor.context_menu.device_tablet");case"desktop":return g("layout_editor.context_menu.device_desktop");default:return g("layout_editor.context_menu.device_custom",{range:W})}},[g]),$e=u.useMemo(()=>{if(!Le)return null;const W=pe(Le.key);return{separate:g("layout_editor.context_menu.separate_branch_device",{device:W}),merge:g("layout_editor.context_menu.merge_branch_device",{device:W})}},[Le,g,pe]),be=u.useMemo(()=>{if(!S.selectedNode||!U)return[];if(U.some(et))return[];const W=Ur(f.previewDevice,S.selectedNode,f.previewCustomWidth);return G6(S.selectedNode,W).map(X=>({key:X,label:pe(X)}))},[S.selectedNode,U,f.previewDevice,f.previewCustomWidth,pe]),j=u.useCallback(W=>{if(W==="desktop"||W==="tablet"||W==="mobile"||W==="portable"){m.setDevice(W);return}m.setDevice(W)},[m]),oe=u.useCallback(()=>{S.selectedNode&&dg(S.selectedNode)},[S.selectedNode]),ue=u.useCallback(()=>{S.selectedNode&&(dg(S.selectedNode),Ne())},[S.selectedNode,Ne]),J=u.useCallback(()=>{if(!v)return;const W=VT();if(!W)return;const X=es(W);if(U&&U.length>0){const R=U.slice(0,-1),de=(U[U.length-1]??0)+1;ge(X,R,de)}else ge(X,[],w.length)},[v,U,ge,w.length]),le=u.useCallback((W,X)=>{b.push({actionKind:"move",label:X,snapshot:W})},[b]),Ie=u.useCallback(()=>{if(S.selectedNavAffordance==="route_in_tree"&&S.selectedNavTargetPath){if(d){d(S.selectedNavTargetPath);return}y({type:"SELECT_ROUTE",route:{path:S.selectedNavTargetPath,layoutName:null}})}},[S.selectedNavAffordance,S.selectedNavTargetPath,d,y]),dt=u.useCallback(()=>{const W=Eb(S.selectedBaseLayout,f.selectedRoute?.layoutName);W&&y({type:"ENTER_BASE_EDIT",layoutName:W})},[S.selectedBaseLayout,f.selectedRoute?.layoutName,y]),pt=u.useCallback(()=>{const W=S.selectedNode?.__source?.extensionId;W!=null&&y({type:"ENTER_EXTENSION_EDIT",extensionId:String(W)})},[S.selectedNode,y]),ot=u.useCallback(()=>{const W=S.selectedIterationSourcePath;W&&y({type:"ENTER_ITERATION_ITEM_EDIT",sourcePath:W})},[S.selectedIterationSourcePath,y]),Oe=u.useRef(null),He=u.useRef(null),Ye=u.useRef(null),Tt=u.useRef({colorScheme:"base",breakpoint:"base"}),Dt=Oe.current!==null?x.stack.some(W=>W.id===Oe.current):!1,Nt=u.useMemo(()=>zd(f.routeTree,h),[f.routeTree,h]),xn=u.useMemo(()=>Md(v?.document?.raw),[v?.document?.raw]),wn=u.useMemo(()=>Ld(s),[s?.states]),Zt=u.useMemo(()=>{const W=v?.document?.raw,X=Af(W?.components),R=W?.initIsolated,de=R&&typeof R=="object"&&!Array.isArray(R)?Object.keys(R):[];return eA(X,de)},[v?.document?.raw]),Hn=u.useMemo(()=>{const W=v?.document?.raw;return Af(W?.components).map(X=>X.scopeId).filter(X=>typeof X=="string"&&X.length>0)},[v?.document?.raw]),ln=Nd({raw:v?.document?.raw,spec:s}),Ii=u.useCallback((W,X)=>{if(!v)return;const R={children:Me.current.components},de=We(R,W);if(!de)return;Tt.current={colorScheme:f.previewColorScheme==="dark"?"dark":"base",breakpoint:Ur(f.previewDevice,de,f.previewCustomWidth)};const ye=t?.getBoundingClientRect(),we=A&&ye?{left:ye.left+A.left,top:ye.top+A.top,width:A.width,height:A.height}:void 0,Te=re,ke=W.find(et),xe=ke?pe(ke.responsive):null,Ee=W.filter(kn=>typeof kn=="number"),Fe=cb({children:Me.current.components},Ee,ub(ln)),zt=Fe.length>0?[...ji({iterationVars:Fe}),...ln]:ln,gt=(kn,lM)=>u.createElement(ET,{key:`pem-${at(He.current??[])}`,node:kn,spec:s,manifest:Me.current.manifest,t:h,permissionCandidates:a,pageCandidates:Nt,dataSourceCandidates:xn,stateKeyCandidates:wn,bindingCandidates:zt,liveElement:Te,templateIdentifier:f.templateIdentifier,initialTab:lM,branchLabel:xe,isolatedScopeIdCandidates:Zt,usedScopeIds:Hn,onSaveInjectedProps:async(bt,Sn)=>{const Ai=typeof kn.id=="string"?kn.id:"",Fn=await HT(f.templateIdentifier,bt,Ai,Sn);if(st({op:"save",layoutName:`extension:${bt}`,editMode:f.editMode,saveTarget:"layout_extension",endpoint:`/api/admin/templates/${f.templateIdentifier}/layout-extensions/${bt}`,isDirty:!0,timestamp:Date.now()}),Fn.kind!=="success")throw new Error(Fn.kind==="conflict"?h("layout_editor.save.conflict"):Fn.kind==="injection_not_found"?"injection not found":Fn.kind==="not_found"?"extension not found":Fn.message??"save failed")},scope:Tt.current,onScopeChange:bt=>{Tt.current=bt;const Sn=Oe.current;Sn&&x.open({id:Sn,ariaLabel:h("layout_editor.property_modal.title"),width:420,draggable:!0,avoidRect:we,content:gt(kn),onClose:()=>{Oe.current=null,He.current=null}})},onPatchNode:bt=>{let Sn=[];v.patchLayout(Fn=>(Sn=Ot({children:Fn},W,()=>bt).children??[],Sn)),b.push({actionKind:"inline_text_edit",label:"edit_props",snapshot:Sn}),sr({source:"style_control",componentPath:at(W),componentName:typeof bt.name=="string"?bt.name:null,patchKey:null,timestamp:Date.now()});const Ai=Oe.current;Ai&&x.open({id:Ai,ariaLabel:h("layout_editor.property_modal.title"),width:420,draggable:!0,avoidRect:we,content:gt(bt),onClose:()=>{Oe.current=null,He.current=null}})},onDelete:()=>{const bt=Oe.current;bt&&x.close(bt),Ne()},onClose:()=>{const bt=Oe.current;bt&&x.close(bt)}});He.current=W;const Mt=x.open({ariaLabel:h("layout_editor.property_modal.title"),width:420,draggable:!0,avoidRect:we,content:gt(de,X),onClose:()=>{Oe.current=null,He.current=null,Ye.current=null}});Oe.current=Mt,Ye.current=kn=>{x.open({id:Mt,ariaLabel:h("layout_editor.property_modal.title"),width:420,draggable:!0,avoidRect:we,content:gt(kn),onClose:()=>{Oe.current=null,He.current=null,Ye.current=null}})}},[v,s,h,a,Nt,xn,wn,ln,re,b,x,Ne,t,A,f.previewColorScheme,f.previewDevice,f.previewCustomWidth,pe]);u.useEffect(()=>{const W=Ye.current,X=He.current;if(!W||!X)return;const R={children:Me.current.components},de=We(R,X);de?W(de):Oe.current&&x.close(Oe.current)},[w]);const Uz=()=>{if(!Dt)return null;const X={position:"absolute",background:"rgba(15, 23, 42, 0.45)",pointerEvents:"auto",zIndex:f7},R=ke=>{ke.preventDefault(),ke.stopPropagation()};if(!A)return i.jsx("div",{"data-testid":"g7le-edit-lock-full",onClick:R,onMouseMove:R,style:{...X,inset:0}});const{left:de,top:ye,width:we,height:Te}=A;return i.jsxs("div",{"data-testid":"g7le-edit-lock",style:{position:"absolute",inset:0,pointerEvents:"none"},children:[i.jsx("div",{"data-testid":"g7le-edit-lock-top",onClick:R,onMouseMove:R,style:{...X,left:0,top:0,right:0,height:Math.max(0,ye)}}),i.jsx("div",{"data-testid":"g7le-edit-lock-bottom",onClick:R,onMouseMove:R,style:{...X,left:0,top:ye+Te,right:0,bottom:0}}),i.jsx("div",{"data-testid":"g7le-edit-lock-left",onClick:R,onMouseMove:R,style:{...X,left:0,top:ye,width:Math.max(0,de),height:Te}}),i.jsx("div",{"data-testid":"g7le-edit-lock-right",onClick:R,onMouseMove:R,style:{...X,left:de+we,top:ye,right:0,height:Te}})]})},ml=u.useCallback(()=>{if(U&&_p(S.selectedLockKind)){if(p&&S.selectedPath){p(S.selectedPath);return}Ii(U)}},[U,S.selectedPath,S.selectedLockKind,p,Ii]),{scale:vl}=m,_l=typeof S.selectedNode?.name=="string"?S.selectedNode.name:"",Bh=s?.componentCapabilities?.[_l]??null,Wh=Array.isArray(Bh?.styleControls)?Bh.styleControls:[],qz=Wh.includes("width")?Ct(s,"width"):null,Gz=Wh.includes("height")?Ct(s,"height"):null,Yz=u.useCallback((W,X)=>{if(!v||!U)return;let R=[];v.patchLayout(de=>(R=Ot({children:de},U,()=>W).children??[],R)),sr({source:"resize",componentPath:at(U),componentName:_l||null,patchKey:X==="height"?"height":"width",resizeAxis:X,timestamp:Date.now()})},[v,U,_l]),Xz=u.useCallback((W,X)=>{if(!v||!U)return;let R=[];v.patchLayout(de=>(R=Ot({children:de},U,()=>W).children??[],R)),b.push({actionKind:"inline_text_edit",label:`resize ${X}`,snapshot:R})},[v,U,b]),Jz=u.useCallback(()=>{if(!re)return null;const W=re.getBoundingClientRect(),X=vl||1;return{width:W.width/X,height:W.height/X}},[re,vl]),ir=GT({node:S.selectedNode,widthControl:qz,heightControl:Gz,scale:vl,measureStartSize:Jz,onResize:Yz,onResizeEnd:Xz}),Zz=u.useCallback((W,X)=>{ir.onHandlePointerDown(W,X)},[ir]),xl=u.useCallback(()=>{const W=b.undo();!W||!v||v.setLayoutComponents(W.snapshot)},[b,v]),wl=u.useCallback(()=>{const W=b.redo();!W||!v||v.setLayoutComponents(W.snapshot)},[b,v]);u7({onUndo:xl,onRedo:wl});const Qz=u.useMemo(()=>({copy:oe,cut:ue,paste:J,delete:Ne,openProps:ml,selectParent:Z?se:void 0,deselect:S.clearSelection}),[oe,ue,J,Ne,ml,Z,se,S.clearSelection]);np({handlers:Qz,hasSelection:!!S.selectedPath}),u.useEffect(()=>(window.__g7LayoutEditorHasSelection=!!S.selectedPath,()=>{try{delete window.__g7LayoutEditorHasSelection}catch{}}),[S.selectedPath]),u.useEffect(()=>(window.__g7LayoutEditorHistory={undo:xl,redo:wl,canUndo:b.canUndo,canRedo:b.canRedo},()=>{try{delete window.__g7LayoutEditorHistory}catch{}}),[xl,wl,b.canUndo,b.canRedo]);const Rz=u.useCallback(W=>{const X=P;F(null),X&&(async()=>{if((await I.commit(X.path,W)).kind==="created"&&v){const de=v.document?.raw?.components??[];b.push({actionKind:"inline_text_edit",label:"inline_create_key",snapshot:de})}})()},[P,I,v,b]),jh=u.useCallback(()=>{const W=P;F(null),I.trackCancel(W?.path??null)},[P,I]),eM=u.useCallback(()=>{F(null)},[]),tM=u.useCallback(async W=>{const X=P;if(!X)return null;const R=await I.keyifyChipValue(X.path,W);if(R.kind!=="created"||!R.translationKey)return null;const de=v?.document?.raw?.components??[];return b.push({actionKind:"inline_text_edit",label:"inline_keyify_chip",snapshot:de}),R.translationKey},[P,I,v,b]),nM=u.useCallback(()=>{const W=P;W&&(F(null),S.handleSelect("",{dataset:{editorPath:W.domPath}}),Ii(W.path,"translation"))},[P,S,Ii]),xt=u.useMemo(()=>{if(!P)return null;const W=v?.document?.raw?.components??w;return We({children:W},P.path)},[P,v?.document,w]),oM=u.useCallback(async(W,X)=>{const R=P;if(!v||!R)return null;const de=typeof xt?.text=="string"?xt.text:"";let ye;if(R.isParamKey)ye=await wr(f.templateIdentifier,f.locale,de,W,X.sourceId,X.path,"scalar");else{const Fe=zt=>zt.replace(/\$t:[a-zA-Z0-9._-]+/g,gt=>{const Mt=h(gt.slice(3));return Mt&&Mt!==gt.slice(3)&&!Mt.startsWith("$t:")?Mt:""}).replace(/\s+/g," ").trim();ye=await ls(f.templateIdentifier,f.selectedRoute?.layoutName??null,f.locale,de,W,X.sourceId,X.path,"scalar",Fe,h)}if(ye.kind==="error")return null;const we=R.path;let Te=[];v.patchLayout(Fe=>(Te=Ot({children:Fe},we,Mt=>({...Mt,text:ye.text})).children??[],Te)),b.push({actionKind:"inline_text_edit",label:"inline_insert_binding",snapshot:Te});const ke=ye.kind==="ok"&&"key"in ye?ye.key:void 0;!R.isParamKey&&ke&&F(Fe=>Fe&&Fe.path===we?{...Fe,isParamKey:!0,customKey:ke,chipValue:void 0,chipParamLabels:void 0}:Fe);try{window.dispatchEvent(new CustomEvent(kt,{detail:{templateIdentifier:f.templateIdentifier,locale:f.locale}}))}catch{}const xe=mt(ye.text),Ee=R.customKey??xe?.key??"";return Ee?It(Ee,f.locale)??null:null},[v,P,xt,f.templateIdentifier,f.locale,f.selectedRoute,b,h]),rM=u.useCallback(async W=>{const X=P;if(!v||!X)return null;const R=typeof xt?.text=="string"?xt.text:"",de=mt(R),ye=X.customKey??de?.key??null,we=mo(R,W),Te=X.path;let ke=[];return v.patchLayout(xe=>(ke=Ot({children:xe},Te,zt=>({...zt,text:we})).children??[],ke)),b.push({actionKind:"inline_text_edit",label:"inline_remove_binding",snapshot:ke}),ye&&await kr(f.templateIdentifier,ye,W,f.locale),ye?It(ye,f.locale)??null:null},[v,P,xt,f.templateIdentifier,f.locale,b]),[kl,Hh]=u.useState({}),iM=["fontSize","color","fontStyle","textDecoration","textAlign","letterSpacing","lineHeight","fontFamily"];u.useEffect(()=>{if(!P||!t||typeof MutationObserver>"u"){Hh({});return}const W=`[data-editor-path="${an(P.domPath)}"]`,X=()=>{const ye=t.querySelector(W);if(!ye)return;const we={};for(const ke of iM){const xe=ye.style.getPropertyValue(ke.replace(/([A-Z])/g,"-$1").toLowerCase());xe&&(we[ke]=xe)}const Te=yg(t,P.domPath)??void 0;Hh({className:ye.className,style:we,effectiveColor:Te})};X();const R=t.querySelector(W);if(!R)return;const de=new MutationObserver(X);return de.observe(R,{attributes:!0,attributeFilter:["class","style"]}),de.observe(t,{childList:!0,subtree:!0}),()=>de.disconnect()},[P?.path?at(P.path):null,t]);const Fh=u.useCallback((W,X)=>{if(!v||!P)return;const R=P.path;let de=[],ye=null;v.patchLayout(we=>(de=Ot({children:we},R,xe=>{const Ee=X(xe);return ye=typeof Ee.name=="string"?Ee.name:null,Ee}).children??[],de)),b.push({actionKind:"inline_text_edit",label:`inline_format ${W}`,snapshot:de}),sr({source:"style_control",componentPath:at(R),componentName:ye,patchKey:W,timestamp:Date.now()})},[v,P,b]),ft=u.useMemo(()=>{const W=S.selectedPath;if(!W||!_)return null;const X=[];let R=W;for(;R;){X.push(R);const de=R.match(/^(.*)\.children\.\d+$/);R=de?de[1]:null}for(const de of X){const ye=Ze(de);if(!ye)continue;const we=We(_,ye),Te=typeof we?.name=="string"?we.name:null;if(!Te)continue;const ke=Oi(s,Te),xe=ke?.canvasOverlay?.kind;if(typeof xe!="string"||!xe||!we)continue;const Ee=ke.canvasOverlay.params&&typeof ke.canvasOverlay.params=="object"?ke.canvasOverlay.params:void 0,Fe=de===W?null:W.slice(de.length+1);return{kind:xe,params:Ee,tablePath:de,tableNode:we,selectedCellRel:Fe}}return null},[S.selectedPath,_,s]),ho=ft?FT(ft.kind):null,Kh=u.useMemo(()=>!ho||!t||!ft?null:po(t,ft.tablePath),[ho,t,ft,A,w]),sM=u.useMemo(()=>{if(!ho||!t||!ft)return[];const W=ft.tablePath,X=an(W),R=[];return t.querySelectorAll(`[data-editor-path^="${X}.children."]`).forEach(we=>{const Te=we.dataset.editorPath??"";if(!Te.startsWith(W+"."))return;const ke=Te.slice(W.length+1),xe=so(we,t);xe&&R.push({path:ke,top:xe.top,left:xe.left,width:xe.width,height:xe.height})}),t.querySelectorAll(`[data-editor-item-path^="${X}."]`).forEach(we=>{const Te=we.dataset.editorItemPath??"";if(!Te.startsWith(W+"."))return;const ke=Te.slice(W.length+1),xe=so(we,t);xe&&R.push({path:ke,top:xe.top,left:xe.left,width:xe.width,height:xe.height})}),R},[ho,t,ft,A,w]),aM=u.useCallback(W=>{const X=ft?Ze(ft.tablePath):null;if(!v||!X)return;let R=[];v.patchLayout(de=>(R=Ot({children:de},X,()=>W).children??[],R)),b.push({actionKind:"inline_text_edit",label:"inplace_edit",snapshot:R}),sr({source:"style_control",componentPath:ft?.tablePath??"",componentName:typeof W.name=="string"?W.name:null,patchKey:"canvas_inplace",timestamp:Date.now()})},[v,U,b]);return t?i.jsxs("div",{className:"g7le-editor-canvas-overlay","data-testid":"g7le-editor-canvas-overlay",style:{position:"absolute",inset:0,pointerEvents:"none"},children:[!D&&Uz(),D&&i.jsxs("div",{"data-testid":"g7le-canvas-pick-banner",style:{position:"absolute",top:8,left:"50%",transform:"translateX(-50%)",zIndex:9999,display:"flex",alignItems:"center",gap:12,padding:"8px 14px",background:"#1e293b",color:"#fff",borderRadius:8,fontSize:13,boxShadow:"0 4px 12px rgba(0,0,0,0.25)",pointerEvents:"auto"},children:[i.jsxs("span",{children:["🎯 ",h("layout_editor.target_picker.canvas_pick_hint")]}),i.jsx("button",{type:"button","data-testid":"g7le-canvas-pick-cancel",onClick:()=>Y(D,null),style:{border:"1px solid #475569",borderRadius:6,background:"transparent",color:"#cbd5e1",cursor:"pointer",fontSize:12,padding:"3px 10px"},children:h("layout_editor.target_picker.cancel")})]}),f.editMode!=="route"&&i.jsx(EI,{frameEl:t,components:w,editMode:f.editMode,currentExtensionId:C,editableRootPath:E}),P&&((P.isParamKey&&P.customKey||P.chipValue)&&xt?i.jsxs(i.Fragment,{children:[xt&&i.jsx(fg,{node:xt,spec:s,t:h,onApplyControl:Fh,box:P.box,scope:{colorScheme:f.previewColorScheme==="dark"?"dark":"base",breakpoint:Ur(f.previewDevice,xt,f.previewCustomWidth)}}),i.jsx(i4,{box:P.box,node:xt,customKey:P.customKey??null,initialChipValue:P.chipValue,chipParamLabels:P.chipParamLabels,onKeyify:tM,templateIdentifier:f.templateIdentifier,locale:f.locale,t:h,candidates:ln,onInsertBinding:oM,onRemoveBinding:rM,onCommit:eM,onCancel:jh})]}):i.jsxs(i.Fragment,{children:[xt&&i.jsx(fg,{node:xt,spec:s,t:h,onApplyControl:Fh,box:P.box,scope:{colorScheme:f.previewColorScheme==="dark"?"dark":"base",breakpoint:xt?Ur(f.previewDevice,xt,f.previewCustomWidth):"base"}}),i.jsx(r4,{box:P.box,initialValue:P.initialValue,isCustomKey:P.isCustomKey,t:h,onCommit:Rz,onCancel:jh,mirrorClassName:kl.className,mirrorStyle:kl.style,nodeEffectiveColor:kl.effectiveColor??P.nodeEffectiveColor,onOpenTranslations:nM})]})),!D&&i.jsx(v7,{hoverBox:T,selectedBox:A,lockKind:S.selectedLockKind,navAffordance:S.selectedNavAffordance,onEditProps:ml,onDuplicate:Ae,onDelete:Ne,onSeparateBranch:Le?.mode==="separate"?q:void 0,separateBranchLabel:$e?.separate,onMergeBranch:Le?.mode==="merge"?ae:void 0,mergeBranchLabel:$e?.merge,definedDeviceBranches:be,onJumpToDevice:be.length>0?j:void 0,onLinkEditDestination:Ie,onEditBase:dt,onEditExtension:pt,extensionLabel:ze,baseLayoutLabel:S.selectedBaseLayout,dataSourceLabel:S.selectedDataSourceId,branchLabel:ee,onEditIteration:S.selectedIsIteration&&f.editMode!=="iteration_item"?ot:void 0,resizeEnabledAxes:ir.enabledAxes,onResizeHandlePointerDown:ir.enabledAxes.width||ir.enabledAxes.height?Zz:void 0,selectedName:Ce,onSelectParent:Z?se:void 0}),D&&B&&i.jsx("div",{"data-testid":"g7le-canvas-pick-hover","data-pick-has-id":B.hasId?"true":"false",style:{position:"absolute",left:B.box.left,top:B.box.top,width:B.box.width,height:B.box.height,pointerEvents:"none",zIndex:9998,boxSizing:"border-box",border:B.hasId?"2px solid #2563eb":"2px dashed #94a3b8",background:B.hasId?"rgba(37,99,235,0.10)":"repeating-linear-gradient(45deg, rgba(148,163,184,0.18) 0, rgba(148,163,184,0.18) 6px, transparent 6px, transparent 12px)",cursor:B.hasId?"pointer":"not-allowed"},children:!B.hasId&&i.jsx("span",{"data-testid":"g7le-canvas-pick-hover-hint",style:{position:"absolute",top:0,left:0,transform:"translateY(-100%)",background:"#475569",color:"#fff",fontSize:11,lineHeight:1.4,padding:"2px 6px",borderRadius:4,whiteSpace:"nowrap"},children:h("layout_editor.target_picker.needs_id")})}),!D&&i.jsx(C7,{selectedBox:qe,points:Se.points,onAddRequest:Re}),ho&&ft&&Kh&&S.selectedLockKind==="none"&&i.jsx(ho,{node:ft.tableNode,params:ft.params,nodeBox:Kh,cellBoxes:sM,selectedCellPath:ft.selectedCellRel??void 0,colorScheme:f.previewColorScheme==="dark"?"dark":"light",t:h,onPatchNode:aM,onInsertChild:ge,onRemoveChild:W=>{const X=Ze(W);if(!v||!X)return;let R=[];v.patchLayout(de=>(R=Ri({children:de},X).children??[],R)),b.push({actionKind:"remove",label:"inplace_remove",snapshot:R})},onRequestInlineEdit:W=>{ft&&G(`${ft.tablePath}.${W}`)}}),v&&i.jsx(xI,{frameEl:t,nesting:o,editMode:f.editMode,currentExtensionId:C,editableRootPath:E,components:w,patchLayout:v.patchLayout,pushHistory:le,selectedPath:S.selectedPath,onSelectPath:W=>S.handleSelect("",{dataset:{editorPath:W}}),onMovePath:W=>S.handleSelect("",{dataset:{editorPath:W}}),onRequestInlineEdit:G})]}):null}function hg(e,t){if(t.length===0)return null;const o=Ha({children:e},t);return o&&typeof o.name=="string"?o.name:null}function Ha(e,t){return We(e,t)}function an(e){return typeof CSS<"u"&&typeof CSS.escape=="function"?CSS.escape(e):e.replace(/[^a-zA-Z0-9_\-.]/g,t=>`\\${t}`)}function yg(e,t){if(!t)return null;const n=e.querySelector(`[data-editor-path="${an(t)}"]`);if(!n||typeof window.getComputedStyle!="function")return null;let o=n;const r=n.ownerDocument.createTreeWalker(n,NodeFilter.SHOW_TEXT);let s=r.nextNode();for(;s;){if((s.textContent??"").trim().length>0){const a=s.parentElement;if(a&&n.contains(a)){o=a;break}}s=r.nextNode()}try{return window.getComputedStyle(o).color||null}catch{return null}}function po(e,t){if(!t)return null;const n=e.querySelector(`[data-editor-path="${an(t)}"]`);if(n){const c=so(n,e);return c&&ao(c,e)?c:null}const o=e.querySelectorAll(`[data-editor-path^="${an(t)}.iteration."]`);let r=1/0,s=1/0,a=-1/0,l=-1/0,d=!1;return o.forEach(c=>{const p=c.dataset.editorPath??"";if(!new RegExp("\\.iteration\\.\\d+$").test(p))return;const f=so(c,e);f&&ao(f,e)&&(d=!0,r=Math.min(r,f.left),s=Math.min(s,f.top),a=Math.max(a,f.left+f.width),l=Math.max(l,f.top+f.height))}),d?{left:r,top:s,width:a-r,height:l-s,scale:1}:null}function f4({isEditMode:e,editorSpec:t,sampleOverride:n}){const o=t?.sampleData;return u.useMemo(()=>Pm({spec:o,overrideSpec:n,onMatch:r=>{Uh(r)}}),[e,o,n])}const g4=Lt().ComponentRegistry;function h4(e){return e.split(/[-_]/).map(t=>t.charAt(0).toUpperCase()+t.slice(1).toLowerCase()).join("")}async function y4(e){const t=typeof window<"u"?window.localStorage?.getItem("auth_token"):null,n={Accept:"application/json"};t&&(n.Authorization=`Bearer ${t}`);const o=await fetch(`/api/admin/templates/${encodeURIComponent(e)}/editor-assets`,{headers:n,credentials:"same-origin"});if(!o.ok){const s=await o.json().catch(()=>null);throw Gr(o.status,s,"assets")}const r=await o.json();return r.data||r}async function b4(e){try{if(typeof fetch!="function")return 0;const t=await fetch(en(`/api/templates/${encodeURIComponent(e)}/config`,"json"),{credentials:"same-origin",headers:{Accept:"application/json"}});if(!t.ok)return 0;const n=await t.json().catch(()=>null),o=n?.data?.cache_version??n?.cache_version;return typeof o=="number"&&o>0?o:0}catch{return 0}}function m4(e){return new Promise((t,n)=>{if(typeof document>"u"){n(new Error("document undefined"));return}if(document.querySelector(`script[data-g7le-asset="${e}"]`)){t();return}const r=document.createElement("script");r.src=e,r.async=!1,r.setAttribute("data-g7le-asset",e),r.addEventListener("load",()=>t()),r.addEventListener("error",()=>n(new Error(`script load failed: ${e}`))),document.head.appendChild(r)})}async function v4(e){if(typeof document>"u")return;const t=`[data-g7le-asset="${e}"]`;if(document.querySelector(`link${t}, style${t}`))return;if(e.includes("/api/admin/"))try{const r=typeof window<"u"?window.localStorage?.getItem("auth_token"):null,s={Accept:"text/css"};r&&(s.Authorization=`Bearer ${r}`);const a=await fetch(e,{headers:s,credentials:"same-origin"});if(a.ok){const l=await a.text(),d=document.createElement("style");d.textContent=l,d.setAttribute("data-g7le-asset",e),document.head.appendChild(d);return}}catch{}const o=document.createElement("link");o.rel="stylesheet",o.href=e,o.setAttribute("data-g7le-asset",e),document.head.appendChild(o)}function _4(e,t,n="user"){const[o,r]=u.useState({componentRegistry:null,translationEngine:null,isReady:!1,error:null});return u.useEffect(()=>{let s=!1;return(async()=>{r(a=>({...a,isReady:!1,error:null}));try{const a=await y4(e);if(s)return;for(const y of a.css??[])if(await v4(y),s)return;const l=h4(e);if(!(typeof window<"u"&&typeof window[l]=="object")){for(const y of a.js??[])if(await m4(y),s)return}const c=g4.createIsolatedInstance();if(await c.loadComponents(e,n),s)return;const p=await b4(e);if(s)return;const f=new Pt({defaultLocale:t});if(p>0&&f.setCacheVersion(p),await f.loadTranslations(e,t),s)return;cd(f,e);try{const y=Pt.getInstance();p>y.getCacheVersion()&&y.setCacheVersion(p),await y.loadTranslations(e,t),cd(y,e)}catch{}if(s)return;r({componentRegistry:c,translationEngine:f,isReady:!0,error:null})}catch(a){if(s)return;const l=hp(a)?a:io(a,"assets");r({componentRegistry:null,translationEngine:null,isReady:!1,error:l})}})(),()=>{s=!0}},[e,t,n]),o}const bg=Lt().AuthManager;function x4(e){switch(e){case"unauthorized":return{icon:"🔒",tone:"blue",titleKey:"layout_editor.access_error.unauthorized.title",messageKey:"layout_editor.access_error.unauthorized.message",showRequiredPermissions:!0};case"forbidden":return{icon:"⛔",tone:"rose",titleKey:"layout_editor.access_error.forbidden.title",messageKey:"layout_editor.access_error.forbidden.message",showRequiredPermissions:!0};case"not_found":return{icon:"🗂️",tone:"slate",titleKey:"layout_editor.access_error.not_found.title",messageKey:"layout_editor.access_error.not_found.message",showRequiredPermissions:!1};case"server_error":return{icon:"🛠️",tone:"amber",titleKey:"layout_editor.access_error.server_error.title",messageKey:"layout_editor.access_error.server_error.message",showRequiredPermissions:!1};case"network":return{icon:"📡",tone:"amber",titleKey:"layout_editor.access_error.network.title",messageKey:"layout_editor.access_error.network.message",showRequiredPermissions:!1};default:return{icon:"⚠️",tone:"slate",titleKey:"layout_editor.access_error.unknown.title",messageKey:"layout_editor.access_error.unknown.message",showRequiredPermissions:!1}}}const w4={blue:{bg:"#eff6ff",border:"#bfdbfe",iconBg:"#dbeafe"},rose:{bg:"#fff1f2",border:"#fecdd3",iconBg:"#ffe4e6"},amber:{bg:"#fffbeb",border:"#fde68a",iconBg:"#fef3c7"},slate:{bg:"#f8fafc",border:"#e2e8f0",iconBg:"#f1f5f9"}};function k4({error:e}){return u.useEffect(()=>{if(e.kind!=="unauthorized"||typeof window>"u")return;const t=bg.getInstance(),n=`${window.location.pathname}${window.location.search}`,o=t.getLoginRedirectUrl("admin",n,"session_expired"),r=window.setTimeout(()=>{window.location.href=o},600);return()=>window.clearTimeout(r)},[e.kind]),null}function Fa({error:e}){const{t}=tt(),n=x4(e.kind),o=w4[n.tone],r=()=>{typeof window<"u"&&(window.location.href="/admin")},s=()=>{typeof window<"u"&&window.location.reload()},a=()=>{if(typeof window>"u")return;const l=bg.getInstance(),d=`${window.location.pathname}${window.location.search}`;window.location.href=l.getLoginRedirectUrl("admin",d)};return i.jsxs("div",{className:"g7le-access-error","data-testid":"g7le-access-error","data-error-kind":e.kind,style:{flex:1,display:"flex",alignItems:"center",justifyContent:"center",padding:24,background:"#f1f5f9"},children:[i.jsx(k4,{error:e}),i.jsxs("div",{style:{maxWidth:480,width:"100%",background:o.bg,border:`1px solid ${o.border}`,borderRadius:12,padding:"28px 32px",boxShadow:"0 2px 8px rgba(15, 23, 42, 0.05)",textAlign:"center"},children:[i.jsx("div",{style:{width:64,height:64,margin:"0 auto 16px",borderRadius:"50%",background:o.iconBg,display:"flex",alignItems:"center",justifyContent:"center",fontSize:32,lineHeight:1},"aria-hidden":"true",children:n.icon}),i.jsx("h2",{"data-testid":"g7le-access-error-title",style:{margin:"0 0 8px",fontSize:18,fontWeight:700,color:"#0f172a"},children:t(n.titleKey)}),i.jsx("p",{"data-testid":"g7le-access-error-message",style:{margin:"0 0 16px",fontSize:13,lineHeight:1.6,color:"#475569"},children:t(n.messageKey)}),n.showRequiredPermissions&&e.requiredPermissions&&i.jsxs("div",{"data-testid":"g7le-access-error-permissions",style:{display:"inline-flex",flexWrap:"wrap",gap:6,justifyContent:"center",padding:"8px 12px",marginBottom:16,background:"rgba(15, 23, 42, 0.04)",borderRadius:8,fontSize:12,color:"#475569"},children:[i.jsx("span",{style:{fontWeight:600},children:t("layout_editor.access_error.required_permission_label")}),e.requiredPermissions.split(/[,\s]+/).filter(Boolean).map(l=>i.jsx("code",{style:{padding:"2px 6px",background:"#fff",border:"1px solid #e2e8f0",borderRadius:4,fontFamily:"ui-monospace, SFMono-Regular, Menlo, monospace",fontSize:11,color:"#1e293b"},children:l},l))]}),e.message&&i.jsxs("details",{"data-testid":"g7le-access-error-detail",style:{marginBottom:16,fontSize:11,color:"#94a3b8"},children:[i.jsx("summary",{style:{cursor:"pointer"},children:t("layout_editor.access_error.detail_summary")}),i.jsxs("pre",{style:{marginTop:8,padding:8,background:"#fff",border:"1px solid #e2e8f0",borderRadius:6,overflowX:"auto",whiteSpace:"pre-wrap",wordBreak:"break-word",textAlign:"left",fontSize:11,color:"#475569"},children:[e.status>0?`HTTP ${e.status} +`:"",e.message]})]}),i.jsxs("div",{style:{display:"flex",justifyContent:"center",gap:8,flexWrap:"wrap"},children:[e.kind==="unauthorized"&&i.jsx("button",{type:"button","data-testid":"g7le-access-error-action-signin",onClick:a,style:Ka,children:t("layout_editor.access_error.action.sign_in")}),e.kind==="forbidden"&&i.jsx("button",{type:"button","data-testid":"g7le-access-error-action-home",onClick:r,style:Ka,children:t("layout_editor.access_error.action.go_home")}),(e.kind==="not_found"||e.kind==="server_error"||e.kind==="network"||e.kind==="unknown")&&i.jsx("button",{type:"button","data-testid":"g7le-access-error-action-retry",onClick:s,style:Ka,children:t("layout_editor.access_error.action.retry")}),e.kind!=="unauthorized"&&i.jsx("button",{type:"button","data-testid":"g7le-access-error-action-home-secondary",onClick:r,style:S4,children:t("layout_editor.access_error.action.go_admin_home")})]})]})]})}const Ka={padding:"8px 16px",background:"#2563eb",color:"#fff",border:"1px solid #1d4ed8",borderRadius:6,fontSize:13,fontWeight:600,cursor:"pointer"},S4={padding:"8px 16px",background:"#fff",color:"#475569",border:"1px solid #cbd5e1",borderRadius:6,fontSize:13,fontWeight:500,cursor:"pointer"};function C4(e){const t=window;if(typeof t>"u")return{getSnapshot:()=>({globalState:{},localState:{}}),restore:()=>{}};const n=t.G7Core||{},o={templateApp:t.__templateApp,g7CoreState:n.state,g7CoreDispatch:n.dispatch,g7CoreData:n.data,g7CoreModal:n.modal,g7CoreToast:n.toast};let r={...e.initialGlobalState??{}};r._local||(r._local={});const s=[],a=()=>{const c={globalState:{...r},localState:{...r._local??{}}};for(const p of s)try{p(r)}catch{}e.onChange?.(c)},l={},d={getGlobalState:()=>r,setGlobalState:(c,p)=>{typeof c=="function"?r=c(r):r={...r,...c},a()},onGlobalStateChange:c=>(s.push(c),()=>{const p=s.indexOf(c);p>=0&&s.splice(p,1)}),get globalState(){return r},getActionDispatcher:()=>e.actionDispatcher,getDataSource:c=>l[c]??null,setDataSource:(c,p)=>{l[c]=p},refetchDataSource:async c=>null,updateDataSourceItem:(c,p,f)=>l[c]??null,getLocale:()=>e.locale,changeLocale:c=>{},getRouter:()=>({navigate:()=>{},replace:()=>{},push:()=>{}}),updateQueryParams:()=>{}};return t.__templateApp=d,n&&typeof n.dispatch=="function"&&(n.dispatch=(c,p)=>{try{return e.actionDispatcher.executeAction(c,p??{})}catch{return}}),n&&typeof n.modal=="object"&&n.modal!==null&&(n.modal={...n.modal,open:()=>{},close:()=>{},closeAll:()=>{}}),n&&typeof n.toast=="object"&&n.toast!==null&&(n.toast={...n.toast,push:()=>{},dismiss:()=>{}}),{getSnapshot:()=>({globalState:{...r},localState:{...r._local??{}}}),restore:()=>{t.__templateApp===d&&(t.__templateApp=o.templateApp),t.G7Core&&(o.g7CoreState!==void 0&&(t.G7Core.state=o.g7CoreState),o.g7CoreDispatch!==void 0&&(t.G7Core.dispatch=o.g7CoreDispatch),o.g7CoreData!==void 0&&(t.G7Core.data=o.g7CoreData),o.g7CoreModal!==void 0&&(t.G7Core.modal=o.g7CoreModal),o.g7CoreToast!==void 0&&(t.G7Core.toast=o.g7CoreToast)),s.length=0}}}const $4=new Set(["id","userId","postId","orderId","productId","categoryId","commentId","noticeId","inquiryId","addressId","wishlistId","notificationId","roleId","menuId"]);function E4(e){if(!e)return{};const t={},n=/:([a-zA-Z_][a-zA-Z0-9_]*)/g;let o;for(;(o=n.exec(e))!==null;){const r=o[1];t[r]=I4(r)}return t}function I4(e){return $4.has(e)||/Id$|_id$/.test(e)?"1":"sample"}const A4=bo("LayoutEditor");function Jo(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function bi(e){if(Array.isArray(e))return e.map(t=>bi(t));if(Jo(e)){const t={};for(const[n,o]of Object.entries(e))t[n]=bi(o);return t}return e}function mg(e,t="",n=new Set){for(const[o,r]of Object.entries(e)){const s=t?`${t}.${o}`:o;Jo(r)?mg(r,s,n):n.add(s)}return n}function vg(e,t,n,o,r,s=""){for(const[a,l]of Object.entries(t)){const d=s?`${s}.${a}`:a;if(n.has(d)){r(`[LayoutEditor] sampleGlobal key conflict: extension '${o}' override of core key '${d}' ignored`);continue}const c=e[a];Jo(l)&&Jo(c)?vg(c,l,n,o,r,d):e[a]=bi(l)}}function Va(e){const t=e.warn??(r=>A4.warn(r)),n=bi(e.coreSeed??{});e.isGuestOnly&&"currentUser"in n&&delete n.currentUser;const o=mg(n);for(const r of e.sources)Jo(r.sampleGlobal)&&vg(n,r.sampleGlobal,o,r.id,t);return e.isGuestOnly&&"currentUser"in n&&delete n.currentUser,n}function T4(e){const t={};if(!e||typeof e!="object"||Array.isArray(e))return t;for(const[n,o]of Object.entries(e))n.startsWith("$")&&(t[n]=o);return t}function D4(e){const t={};if(!e||typeof e!="object"||Array.isArray(e))return t;for(const[n,o]of Object.entries(e))n.startsWith("$")||(t[n]=o);return t}function N4(e){if(typeof e!="string")return null;const t=/^\s*\{\{\s*([\s\S]*?)\s*\}\}\s*$/.exec(e);if(!t)return null;let n=t[1];const o=n.indexOf("??");return o>=0&&(n=n.slice(0,o)),n=n.replace(/\?\./g,".").replace(/\s+/g,""),n===""||!/^[A-Za-z_$][A-Za-z0-9_$]*(\.[A-Za-z_$][A-Za-z0-9_$]*)*$/.test(n)?null:n.split(".")}function z4(e,t){const n=N4(t);if(!n||n.length===0)return e;let o=e;for(let a=0;ao.map(r=>{if(!r||typeof r!="object")return r;const s=r;let a=s;if(typeof s.slot=="string"&&!t.has(s.slot)){const{slot:l,...d}=s;a={...d,__editorSlotName:l}}if(Array.isArray(a.children)){const l=n(a.children);l!==a.children&&(a={...a,children:l})}return a});return n(e)}function St(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function Xt(e){if(Array.isArray(e))return e.map(t=>Xt(t));if(St(e)){const t={};for(const[n,o]of Object.entries(e))t[n]=Xt(o);return t}return e}function mi(e,t){for(const[n,o]of Object.entries(t)){const r=e[n];St(o)&&St(r)?mi(r,o):e[n]=Xt(o)}}function P4(e){const t=[];let n="";for(let o=0;o0&&(t.push(n),n="");const s=e[o+1];if(s==="'"||s==='"'){const l=e.indexOf(`${s}]`,o+2);if(l!==-1){t.push(e.slice(o+2,l)),o=l+1;continue}}const a=e.indexOf("]",o+1);if(a!==-1){t.push(e.slice(o+1,a)),o=a;continue}n+=r}else r==="."?n.length>0&&(t.push(n),n=""):n+=r}return n.length>0&&t.push(n),t.filter(o=>o.length>0)}function Ua(e,t,n){const o=P4(t);if(o.length===0)return;let r=e;for(let s=0;s0?t:void 0}const Ga="g7le:editor-t-ready",wg="data-g7le-vh-expanded";function B4(e,t){if(!e||!t)return;const n=t;e.querySelectorAll("*").forEach(r=>{const s=window.getComputedStyle(r),a=parseFloat(s.height),l=Number.isFinite(a)&&Math.abs(a-n)<=2,d=parseFloat(s.minHeight),c=Number.isFinite(d)&&Math.abs(d-n)<=2,p=s.overflowY==="auto"||s.overflowY==="scroll"||s.overflowY==="hidden"||s.overflow==="auto"||s.overflow==="scroll"||s.overflow==="hidden";l&&p?(r.style.setProperty("height","auto","important"),c&&r.style.setProperty("min-height","0","important"),r.style.setProperty("overflow","visible","important"),r.setAttribute(wg,"1")):(s.overflowY==="auto"||s.overflowY==="scroll")&&r.scrollHeight>r.clientHeight+4&&r.clientHeight>120&&(r.style.setProperty("overflow","visible","important"),r.style.setProperty("height","auto","important"),r.setAttribute(wg,"1"))})}const W4=[];function j4(e={}){const{manifest:t=null,nesting:n=null,componentPalette:o=null,spec:r=null,permissionCandidates:s,sampleGlobalSources:a=W4,resolveRouteMatch:l,onNavigateToDestination:d,onSessionAddedPathsChange:c}=e,{state:p}=Je(),f=Ft(),y=Cp(),{document:g,isLoading:m,error:h}=f??y,v=u.useRef(null),[b,x]=u.useState(null),{deviceWidth:k,scale:w,colorScheme:_}=ja(),C=u.useMemo(()=>p.activeStateId?p.availableStates.find(Q=>Q.id===p.activeStateId)??null:null,[p.activeStateId,p.availableStates]),E=u.useMemo(()=>xg(C),[C]),S=f4({isEditMode:!0,editorSpec:r??void 0,sampleOverride:E}),{t:T}=tt(),{componentRegistry:$,translationEngine:A,isReady:z,error:D}=_4(p.templateIdentifier,p.locale),O=u.useRef(null);O.current===null&&(O.current=new vd);const M=u.useRef(null);if(M.current===null){M.current=new VE({navigate:()=>{}}),M.current.setPreviewMode(!0);try{const ie=window.__templateApp?.getActionDispatcher?.();if(ie&&typeof ie.getRegisteredHandlers=="function"){const _e=ie.getRegisteredHandlers();for(const ge of _e){if(!ge.includes("."))continue;const Ae=typeof ie.getHandler=="function"?ie.getHandler(ge):void 0;typeof Ae=="function"&&M.current.registerHandler(ge,Ae,{category:"plugin",source:"preview-canvas-cloned-from-host"})}}}catch{}}u.useEffect(()=>{if(!z)return;const Q=M.current;if(Q)try{const ie=window.G7Core?.getActionDispatcher?.();if(!ie||typeof ie.getRegisteredHandlers!="function")return;const _e=ie.getRegisteredHandlers();for(const ge of _e){if(!ge.includes(".")||typeof Q.getHandler=="function"&&Q.getHandler(ge))continue;const Ae=typeof ie.getHandler=="function"?ie.getHandler(ge):void 0;typeof Ae=="function"&&Q.registerHandler(ge,Ae,{category:"plugin",source:"preview-canvas-iife-reclone"})}}catch{}},[z]);const B=u.useMemo(()=>new UE({sampleProvider:S,actionDispatcher:M.current}),[S]),N=u.useMemo(()=>g?.raw?.meta?.guest_only===!0,[g]),H=u.useMemo(()=>Va({sources:a,isGuestOnly:N}),[a,N]),L=u.useMemo(()=>{const Q=H4(qa(C),A,{templateId:p.templateIdentifier,locale:p.locale}),ie=H._local,{global:_e,local:ge}=O4({globalBaseline:H,localBaseline:ie??{},patch:C?.initialState??null,formErrors:Q});return{..._e,_local:ge}},[H,C,A,p.templateIdentifier,p.locale]),I=u.useMemo(()=>!C||!(!!C.initialState?.local||!!qa(C))?null:{...L._local??{},_forceLocalInit:C.id,_merge:"deep"},[C,L]),P=Q=>typeof Q=="object"&&Q!==null&&!Array.isArray(Q),F=u.useMemo(()=>P(C?.initialState?.query)?C.initialState.query:null,[C]),G=u.useMemo(()=>P(C?.initialState?.route)?C.initialState.route:null,[C]),[V,Y]=u.useState(()=>Va({sources:[],isGuestOnly:!1})),Z=u.useRef(null);u.useEffect(()=>{const Q={...L},ie=C4({actionDispatcher:M.current,locale:p.locale,initialGlobalState:Q,onChange:ge=>{Y(ge.globalState)}});Z.current=ie;const _e=window.__templateApp;return _e?.setGlobalState&&M.current.setGlobalStateUpdater((ge,Ae)=>{_e.setGlobalState(ge,Ae)}),Y(Q),$l({kind:"applyPatch",activeStateId:p.activeStateId,routePath:p.selectedRoute?.path??null,hasInitialPatch:!!C?.initialState,hasFormErrors:!!qa(C),hasSampleOverride:!!xg(C)}),()=>{ie.restore(),Z.current=null}},[p.locale,L]),u.useEffect(()=>{if(!A)return;const Q=window.G7Core;if(!Q||typeof Q.t!="function")return;const ie=Q.t,_e=p.templateIdentifier,ge=p.locale;return Q.t=(Ae,Ne)=>{try{const Le=Ne?"|"+Object.entries(Ne).map(([ae,pe])=>`${ae}=${pe}`).join("|"):void 0,q=A.translate(Ae,{templateId:_e,locale:ge},Le);if(q!==Ae&&!q.startsWith("$t:"))return q}catch{}return ie(Ae,Ne)},typeof window<"u"&&window.dispatchEvent(new CustomEvent(Ga)),()=>{window.G7Core?.t&&window.G7Core.t!==ie&&(window.G7Core.t=ie)}},[A,p.templateIdentifier,p.locale]);const se=u.useMemo(()=>{const Q=g?.raw?.components??[];return p.editMode!=="base"?Q:L4(Q)},[g,p.editMode]),re=g?.raw?.__editability,U=g?.raw?.__extensionId,ee=u.useMemo(()=>p.editMode!=="extension"||re!=="ok"?[]:typeof U!="number"?[]:F4(se,U),[p.editMode,re,U,se]),[fe,K]=u.useState(null),[ne,me]=u.useState(!1),Ce=g?.layoutName??null,ze=u.useMemo(()=>{const Q=g?.raw?.scripts??[];return Array.isArray(Q)?Q.map(ie=>ie&&typeof ie.src=="string"?ie.src:"").join("|"):""},[g]);u.useEffect(()=>{me(!1)},[Ce,ze]),u.useEffect(()=>{let Q=!1;const ie=g?.raw?.scripts??[];if(!Array.isArray(ie)||ie.length===0){me(!0);return}const _e=[];for(const ge of ie){if(!ge?.src||typeof ge.src!="string")continue;const Ae=ge.id?`script#${CSS.escape(ge.id)}, script[data-g7le-canvas-script="${ge.src}"]`:`script[data-g7le-canvas-script="${ge.src}"], script[src="${ge.src}"]`,Ne=window.document.querySelector(Ae);if(Ne){const Le=Ne.hasAttribute("data-g7le-canvas-script");if(Ne.hasAttribute("data-g7le-script-loaded")||!Le)continue;_e.push(new Promise(ae=>{if(Ne.hasAttribute("data-g7le-script-loaded")){ae();return}Ne.addEventListener("load",()=>ae()),Ne.addEventListener("error",()=>ae())}));continue}_e.push(new Promise(Le=>{const q=window.document.createElement("script");q.src=ge.src,q.async=!1,ge.id&&(q.id=ge.id),q.setAttribute("data-g7le-canvas-script",ge.src);const ae=()=>{q.setAttribute("data-g7le-script-loaded","1"),Le()};q.addEventListener("load",ae),q.addEventListener("error",ae),window.document.head.appendChild(q)}))}if(_e.length===0){me(!0);return}return Promise.all(_e).then(()=>{Q||requestAnimationFrame(()=>{requestAnimationFrame(()=>{Q||me(!0)})})}),()=>{Q=!0}},[g]);const Se=u.useMemo(()=>g?.raw?.data_sources??[],[g]),qe=u.useMemo(()=>g?.raw?.computed??{},[g]),[De,Pe]=u.useState({}),Qe=u.useMemo(()=>{if(p.editMode!=="iteration_item")return De;const Q=g?.iterationContext;if(!Q)return De;const ie=g?.raw?.components??[],_e=We({children:ie},Q.sourceIndexPath),ge=_e&&_e.iteration&&typeof _e.iteration.source=="string"?_e.iteration.source:null;return ge?z4(De,ge):De},[p.editMode,De,g]),[Me,Re]=u.useState(0);u.useEffect(()=>{if(typeof window>"u")return;const Q=()=>Re(ie=>ie+1);return window.addEventListener(kt,Q),()=>window.removeEventListener(kt,Q)},[]);const ve=u.useMemo(()=>E4(p.selectedRoute?.path??null),[p.selectedRoute?.path]);if(u.useEffect(()=>{let Q=!1;return(async()=>{if(Se.length===0){Q||Pe({});return}try{const ie=await B.fetchDataSources(Se,ve,new URLSearchParams,void 0,void 0);Q||Pe(ie)}catch{Q||Pe({})}})(),()=>{Q=!0}},[Se,B,ve]),u.useEffect(()=>{const Q=b;if(!Q||typeof window>"u")return;let ie=0,_e=0,ge=0;const Ae=()=>B4(Q,window.innerHeight);ie=window.requestAnimationFrame(()=>{_e=window.requestAnimationFrame(Ae)});let Ne=null;return typeof MutationObserver<"u"&&(Ne=new MutationObserver(()=>{ge||(ge=window.requestAnimationFrame(()=>{ge=0,Ae()}))}),Ne.observe(Q,{childList:!0,subtree:!0})),()=>{window.cancelAnimationFrame(ie),window.cancelAnimationFrame(_e),ge&&window.cancelAnimationFrame(ge),Ne&&Ne.disconnect()}},[b,se,ne,k,De]),u.useEffect(()=>{const Q=b;if(!Q||typeof window>"u"||p.editMode!=="extension"||re!=="ok"||ee.length===0){K(null);return}let ie=0,_e=0;const ge=ae=>{if(!ae)return!1;const pe=ae.getBoundingClientRect();return pe.width>0&&pe.height>0},Ae=ae=>{const pe=Q.querySelector(`[data-editor-path="${ae}"]`);if(ge(pe))return!0;const $e=Q.querySelectorAll(`[data-editor-path^="${ae}.children."]`);for(const be of Array.from($e))if(ge(be))return!0;return!1},Ne=()=>{K(ee.some(Ae))};ie=window.requestAnimationFrame(()=>{_e=window.requestAnimationFrame(Ne)});let Le=null,q=0;return typeof MutationObserver<"u"&&(Le=new MutationObserver(()=>{q||(q=window.requestAnimationFrame(()=>{q=0,Ne()}))}),Le.observe(Q,{childList:!0,subtree:!0})),()=>{window.cancelAnimationFrame(ie),window.cancelAnimationFrame(_e),q&&window.cancelAnimationFrame(q),Le&&Le.disconnect()}},[b,p.editMode,re,ee,ne,De]),u.useEffect(()=>{if(typeof window>"u"||p.editMode!=="base")return;const Q=window.document,ie="g7le-base-slot-style";if(Q.getElementById(ie))return;const _e=Q.createElement("style");_e.id=ie;const ge=T("layout_editor.preview.slot_area_label");return _e.textContent=`.g7le-preview-frame [data-editor-slot]{position:relative;min-height:96px;outline:2px dashed #94a3b8;outline-offset:-2px;}.g7le-preview-frame [data-editor-slot]::after{content:'${ge.replace(/'/g,"\\'")} · ' attr(data-editor-slot);position:absolute;top:8px;left:50%;transform:translateX(-50%);font-size:12px;line-height:1;color:#64748b;background:rgba(241,245,249,0.92);border:1px solid #cbd5e1;border-radius:9999px;padding:4px 12px;pointer-events:none;white-space:nowrap;}`,Q.head.appendChild(_e),()=>{Q.getElementById(ie)?.remove()}},[p.editMode,T]),!(p.editMode==="extension"||p.editMode==="iteration_item")&&(p.selectedRoute?.layoutName===null||p.selectedRoute===null))return i.jsx("div",{className:"g7le-preview-canvas g7le-preview-canvas--empty","data-testid":"g7le-preview-empty",style:Zo,children:T("layout_editor.preview.empty")});if(m)return i.jsx("div",{className:"g7le-preview-canvas g7le-preview-canvas--loading","data-testid":"g7le-preview-loading",style:Zo,children:T("layout_editor.preview.loading")});if(h)return i.jsx(Fa,{error:h});if(D)return i.jsx(Fa,{error:D});if(!z||$===null||A===null)return i.jsx("div",{className:"g7le-preview-canvas g7le-preview-canvas--assets-loading","data-testid":"g7le-preview-assets-loading",style:Zo,children:T("layout_editor.preview.assets_loading")});if(se.length===0)return i.jsx("div",{className:"g7le-preview-canvas g7le-preview-canvas--empty-layout","data-testid":"g7le-preview-empty-layout",style:Zo,children:T("layout_editor.preview.empty_layout")});const he=p.editMode==="extension"?re==="no-injection"?"no-injection":re==="ok"&&ee.length>0&&fe!==!0?"gated-no-state":null:null;return ne?i.jsx("div",{className:"g7le-preview-canvas","data-testid":"g7le-preview-canvas",style:{overflow:"visible",background:"#f1f5f9",padding:24,display:"flex",justifyContent:"center",alignItems:"flex-start",minHeight:"calc(100vh - var(--g7le-header-h, 0px))",boxSizing:"border-box"},children:i.jsxs("div",{className:"g7le-preview-frame-wrapper","data-testid":"g7le-preview-frame-wrapper",style:{position:"relative",width:k,transform:`scale(${w})`,transformOrigin:"top center"},children:[i.jsx("div",{className:_==="dark"?"g7le-preview-frame g7le-preview-dark":"g7le-preview-frame","data-testid":"g7le-preview-frame","data-color-scheme":_,ref:Q=>{v.current=Q,x(Q)},style:{width:k,background:"#fff",border:"1px solid #cbd5e1",boxShadow:"0 1px 3px rgba(0,0,0,0.05)",minHeight:400,position:"relative",transform:"translateZ(0)",overflow:"hidden"},children:i.jsx(KE,{overrideWidth:k,children:se.map((Q,ie)=>i.jsx(FE,{componentDef:Q,dataContext:{...Qe,...T4(V),_global:D4(V),_computedDefinitions:qe,...I?{_localInit:I,_local:{...De._local??{},...V._local??{}}}:{},route:K4({...De.route??{},...ve,path:p.selectedRoute?.path??""},G),query:{...De.query??{},...F??{}}},translationContext:{templateId:p.templateIdentifier,locale:p.locale},registry:$,bindingEngine:O.current,translationEngine:Pt.getInstance(),actionDispatcher:M.current,isEditMode:!0,isRootRenderer:ie===0,componentPath:`${ie}`,onComponentSelect:(_e,ge)=>{},onComponentHover:(_e,ge)=>{}},`${Q.id??`preview-${ie}`}:t${Me}${p.editMode==="extension"?`:s${ne?1:0}`:""}`))})}),i.jsx("div",{className:"g7le-overlay-layer","data-testid":"g7le-overlay-layer",style:{position:"absolute",left:0,top:0,width:k,height:b?b.getBoundingClientRect().height/(w||1):"100%",overflow:"visible",pointerEvents:"none"},children:i.jsx(p4,{frameEl:b,manifest:t,nesting:n,componentPalette:o,spec:r,permissionCandidates:s,resolveRouteMatch:l,onNavigateToDestination:d,onSessionAddedPathsChange:c})}),he!==null&&i.jsx("div",{className:"g7le-preview-canvas--extension-fallback","data-testid":"g7le-preview-extension-fallback","data-fallback-reason":he,style:{position:"absolute",left:0,top:0,right:0,bottom:0,minHeight:b?b.getBoundingClientRect().height/(w||1):400,background:"#f1f5f9",display:"flex",alignItems:"flex-start",justifyContent:"center",padding:24,boxSizing:"border-box",pointerEvents:"auto",zIndex:50},children:i.jsxs("div",{className:"g7le-extension-fallback-card",style:{maxWidth:440,textAlign:"center",position:"sticky",top:"calc(40vh - var(--g7le-header-h, 0px))"},children:[i.jsx("p",{className:"g7le-extension-fallback-title",style:{fontWeight:600,marginBottom:8,color:"#0f172a"},children:T(he==="no-injection"?"layout_editor.preview.extension_no_injection_title":"layout_editor.preview.extension_gated_title")}),i.jsx("p",{className:"g7le-extension-fallback-desc",style:{color:"#475569",lineHeight:1.6},children:he==="no-injection"?T("layout_editor.preview.extension_no_injection_desc"):p.availableStates.length>1?T("layout_editor.preview.extension_gated_desc_with_states"):T("layout_editor.preview.extension_gated_desc")})]})})]})}):i.jsx("div",{className:"g7le-preview-canvas g7le-preview-canvas--scripts-loading","data-testid":"g7le-preview-scripts-loading",style:Zo,children:T("layout_editor.preview.loading")})}function H4(e,t,n){if(!e)return;if(!t)return e;const o=s=>{if(typeof s=="string"&&s.startsWith("$t:"))try{const a=s.slice(3),l=a.indexOf("|");if(l===-1)return t.translate(a,n);const d=a.slice(0,l),c=a.slice(l);return t.translate(d,n,c)}catch{return s}return Array.isArray(s)?s.map(o):s},r={};for(const[s,a]of Object.entries(e))r[s]=o(a);return r}function F4(e,t){const n=[],o=(r,s)=>{Array.isArray(r)&&r.forEach((a,l)=>{if(!a||typeof a!="object")return;const d=s?`${s}.children.${l}`:`${l}`,c=a.__source;if(c?.kind==="extension"&&c.extensionId===t){n.push(d);return}Array.isArray(a.children)&&o(a.children,d)})};return o(e,""),n}function K4(e,t){if(!t)return e;const n={...e};for(const[o,r]of Object.entries(t))r===null?delete n[o]:n[o]=r;return n}const Zo={flex:1,display:"flex",alignItems:"center",justifyContent:"center",padding:24,background:"#f1f5f9",color:"#64748b",fontSize:13},V4={desktop:"layout_editor.device.desktop",tablet:"layout_editor.device.tablet",mobile:"layout_editor.device.mobile",portable:"layout_editor.device.portable"};function U4(){const{device:e,zoom:t,customWidth:n,setDevice:o,setZoom:r,setCustomWidth:s,colorScheme:a,setColorScheme:l}=ja(),{t:d}=tt(),c=Gf();return i.jsxs("div",{className:"g7le-device-toolbar","data-testid":"g7le-device-toolbar",style:{display:"inline-flex",alignItems:"center",gap:8,padding:"4px 8px",background:"#fff",border:"1px solid #cbd5e1",borderRadius:6},children:[i.jsxs("div",{role:"group","aria-label":d("layout_editor.device.group_label"),children:[c.map(p=>{const f=e===p,y=V4[p],g=y?d(y):p;return i.jsx("button",{type:"button","data-testid":`g7le-device-${p}`,"aria-pressed":f,onClick:()=>o(p),style:{padding:"2px 8px",marginRight:2,fontSize:12,cursor:"pointer",background:f?"#0f172a":"#f8fafc",color:f?"#fff":"#0f172a",border:"1px solid #cbd5e1",borderRadius:4},children:g},p)}),(()=>{const p=e==="custom";return i.jsx("button",{type:"button","data-testid":"g7le-device-custom","aria-pressed":p,onClick:()=>o("custom"),style:{padding:"2px 8px",marginRight:2,fontSize:12,cursor:"pointer",background:p?"#0f172a":"#f8fafc",color:p?"#fff":"#0f172a",border:"1px solid #cbd5e1",borderRadius:4},children:d("layout_editor.device.custom")})})()]}),e==="custom"&&i.jsxs("div",{style:{display:"inline-flex",alignItems:"center",gap:4},children:[i.jsx("label",{htmlFor:"g7le-custom-width-input",style:{fontSize:11,color:"#475569"},children:d("layout_editor.device.custom_width_label")}),i.jsx("input",{id:"g7le-custom-width-input",type:"number",min:ug,max:pg,step:10,value:n,"data-testid":"g7le-custom-width-input","aria-label":d("layout_editor.device.custom_width_label"),onChange:p=>{const f=parseInt(p.currentTarget.value,10);Number.isFinite(f)&&s(f)},style:{width:72,padding:"2px 6px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:4}}),i.jsx("span",{style:{fontSize:11,color:"#475569"},children:"px"})]}),i.jsx("div",{role:"group","aria-label":d("layout_editor.toolbar.color_scheme_group"),style:{display:"inline-flex",gap:2},children:["light","dark"].map(p=>{const f=a===p;return i.jsx("button",{type:"button","data-testid":`g7le-toolbar-scheme-${p}`,"aria-pressed":f,onClick:()=>l(p),style:{padding:"2px 8px",fontSize:12,cursor:"pointer",background:f?"#0f172a":"#f8fafc",color:f?"#fff":"#0f172a",border:"1px solid #cbd5e1",borderRadius:4},children:d(p==="light"?"layout_editor.toolbar.color_scheme_light":"layout_editor.toolbar.color_scheme_dark")},p)})}),i.jsxs("div",{style:{display:"inline-flex",alignItems:"center",gap:4},children:[i.jsx("span",{style:{fontSize:11,color:"#475569"},children:d("layout_editor.zoom.label")}),i.jsx("input",{type:"range",min:.5,max:1,step:.05,value:t,"data-testid":"g7le-zoom-slider","aria-label":d("layout_editor.zoom.aria_label"),onChange:p=>r(parseFloat(p.currentTarget.value)),style:{width:100}}),i.jsxs("span",{style:{fontSize:11,color:"#475569",minWidth:36,textAlign:"right"},children:[Math.round(t*100),"%"]})]}),i.jsx(Kb,{})]})}function q4(){const{t:e}=tt(),{state:t,dispatch:n}=Je(),{availableStates:o,activeStateId:r}=t,[,s]=u.useState(0);u.useEffect(()=>{if(typeof window>"u")return;const p=()=>s(y=>y+1),f=window.requestAnimationFrame(p);return window.addEventListener(Ga,p),()=>{window.cancelAnimationFrame(f),window.removeEventListener(Ga,p)}},[]);const a=u.useCallback((p,f)=>{if(!p)return f;if(!p.startsWith("$t:"))return p;const y=p.slice(3),g=window.G7Core;if(g&&typeof g.t=="function"){const h=g.t(y);if(h&&h!==y&&!h.startsWith("$t:"))return h}const m=e(y);return m&&m!==y?m:f},[e]);if(!Array.isArray(o)||o.length<=1)return null;const l=p=>{const f=p.target.value;n({type:"SET_ACTIVE_STATE",activeStateId:f}),$l({kind:"change",activeStateId:f,routePath:t.selectedRoute?.path??null,availableCount:o.length})},d=o.find(p=>p.id===r)??o[0],c=d?.description?a(d.description,""):"";return i.jsxs("div",{className:"g7le-state-switcher","data-testid":"g7le-state-switcher","data-active-state":r??"",style:{display:"inline-flex",alignItems:"center",gap:6,padding:"4px 8px",background:"#fff",border:"1px solid #cbd5e1",borderRadius:6},children:[i.jsx("span",{style:{fontSize:12,fontWeight:600,color:"#475569",whiteSpace:"nowrap"},children:e("layout_editor.toolbar.state")}),i.jsx("select",{"data-testid":"g7le-state-switcher-select","aria-label":e("layout_editor.toolbar.state"),value:r??o[0].id,onChange:l,style:{fontSize:12,padding:"2px 6px",border:"1px solid #cbd5e1",borderRadius:4,background:"#fff",color:"#0f172a",cursor:"pointer",outline:"none"},children:o.map(p=>i.jsx("option",{value:p.id,children:a(p.label,p.id)},p.id))}),c&&i.jsx("span",{"data-testid":"g7le-state-switcher-description",style:{fontSize:11,color:"#94a3b8",whiteSpace:"nowrap"},children:c})]})}function G4(e,t,n){if(e==="extension"){if(typeof n=="string"&&n.length>0){const o=Ya(n,"__base__/");return o?{kind:"base",match:o}:{kind:"route",match:n}}return null}if(!t)return null;switch(e){case"route":return{kind:"route",match:t};case"base":{const o=Ya(t,"__base__/");return o?{kind:"base",match:o}:null}case"modal":{const o=Ya(t,"__modal__/");return o?{kind:"modal",match:o}:null}default:return null}}function Y4(e,t){if(!t||!Array.isArray(e)||e.length===0)return[];const n=[];for(const o of e)if(J4(o.scope,t))for(const r of o.items??[])r&&typeof r.id=="string"&&r.id.length>0&&n.push(r);return n}function X4(e){if(e.length===0)return null;const t=e.find(n=>n.default===!0);return t?t.id:e[0].id}function J4(e,t){if(!e||e.kind!==t.kind)return!1;const n=e.match;return typeof n!="string"||n.length===0?!1:t.kind==="route"?Z4(t.match,n):n===t.match}function Z4(e,t){if(t===e)return!0;if(!t.includes("*"))return!1;const n=t.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*/g,"[^/]+");return new RegExp(`^${n}$`).test(e)}function Ya(e,t){return e.startsWith(t)?e.slice(t.length):""}const Q4="__conngroup__/modals/",R4="__conngroup__/extensions/";function eD(e){return e.meta?.editor_label?{label:e.meta.editor_label,labelSource:"editor_label"}:e.meta?.title?{label:e.meta.title,labelSource:"title"}:{label:e.path,labelSource:"path"}}function tD(e){switch(e.source.kind){case"template":return"template";case"module":return`module:${e.source.identifier??""}`;case"plugin":return`plugin:${e.source.identifier??""}`;case"core":return null;default:return null}}function nD(e,t){if(e.meta?.parent&&t.has(e.meta.parent))return e.meta.parent;const n=e.path.split("/").filter(Boolean);for(let o=n.length-1;o>=1;o--){const r="/"+n.slice(0,o).join("/");if(r!==e.path&&t.has(r))return r}return null}function oD(e){const t=new Map;for(const n of e)n.layout&&t.set(n.layout,(t.get(n.layout)??0)+1);return t}function rD(e){const t=new Map;for(const n of e){if(!n.hostLayout)continue;const o=t.get(n.hostLayout)??[];o.push(n),t.set(n.hostLayout,o)}return t}function iD(e){const t=new Map;for(const n of e)for(const o of n.extensions)for(const r of o.hostLayouts??[]){if(!r)continue;const s=t.get(r)??[];s.push({extension:o,group:n}),t.set(r,s)}return t}function sD(e,t){return{path:`__modal__/${e.hostLayout}/${e.modalId}`,layoutName:e.hostLayout,label:e.label??e.modalId,labelSource:e.label?"editor_label":"path",source:{kind:"core",identifier:null},kind:"modal",modalId:e.modalId,modalHostLayout:e.hostLayout,connectedHostRoutePath:t,children:[]}}function aD(e,t){const{extension:n,group:o}=e,r=o.sourceType==="plugin"?"🔌":"🧩";return{path:`__extension__/${n.id}`,layoutName:null,label:`${r} ${o.sourceLabel} · ${n.targetName}`,labelSource:"path",source:{kind:o.sourceType,identifier:o.sourceIdentifier},kind:"extension",extensionId:String(n.id),extensionType:n.extensionType,extensionPriority:n.priority,extensionTargetName:n.targetName,isModified:n.isModified,isInactive:!n.isActive,connectedHostRoutePath:t,children:[]}}function kg(e,t,n){if(e.kind!=="route"&&e.kind!=="base"||!e.layoutName)return;const o=[],r=t.get(e.layoutName)??[];r.length>0&&o.push({path:`${Q4}${e.path}`,layoutName:null,label:`$t:layout_editor.chrome.route_tree.connected.modals|count=${r.length}`,labelSource:"editor_label",source:{kind:"core",identifier:null},kind:"route",children:r.map(a=>sD(a,e.path))});const s=n.get(e.layoutName)??[];s.length>0&&o.push({path:`${R4}${e.path}`,layoutName:null,label:`$t:layout_editor.chrome.route_tree.connected.extensions|count=${s.length}`,labelSource:"editor_label",source:{kind:"core",identifier:null},kind:"route",children:s.map(a=>aD(a,e.path))}),o.length>0&&(e.children=[...o,...e.children??[]])}function lD(e){const t=[],n=new Set(e.routes.map(o=>o.path));for(const o of e.routes){const r=tD(o);if(!r)continue;const{label:s,labelSource:a}=eD(o),l=nD(o,n),d={path:o.path,layoutName:o.layout??null,label:s,labelSource:a,icon:o.meta?.icon??null,source:o.source,isRedirect:!!o.redirect,isHidden:!!o.meta?.hidden,kind:"route",children:[]};t.push({node:d,parentKey:l,groupKey:r})}return t}function dD(e,t){if(e==="template")return"$t:layout_editor.chrome.route_tree.group.template";if(e.startsWith("module:")){const n=e.slice(7);return`$t:layout_editor.chrome.route_tree.group.module|name=${t.moduleDisplayNames[n]??n}`}if(e.startsWith("plugin:")){const n=e.slice(7);return`$t:layout_editor.chrome.route_tree.group.plugin|name=${t.pluginDisplayNames[n]??n}`}return e}function cD(e,t){oD(e.routes);const n=rD(e.modals),o=iD(e.extensionGroups??[]),r=new Map;for(const d of t)r.has(d.groupKey)||r.set(d.groupKey,[]),r.get(d.groupKey).push(d);const s=Array.from(r.keys()).sort((d,c)=>{const p=g=>g==="template"?1:g.startsWith("module:")?2:g.startsWith("plugin:")?3:4,f=p(d),y=p(c);return f!==y?f-y:d.localeCompare(c)}),a=[];if(e.baseLayouts.length>0){const d=e.baseLayouts.map(c=>({path:`__base__/${c.layoutName}`,layoutName:c.layoutName,label:c.label??c.layoutName,labelSource:c.label?"editor_label":"path",source:{kind:"core",identifier:null},kind:"base",children:[]}));for(const c of d)kg(c,n,o);a.push({path:"__group__/base",layoutName:null,label:"$t:layout_editor.chrome.route_tree.group.base",labelSource:"editor_label",source:{kind:"core",identifier:null},kind:"route",children:d})}for(const d of s){const c=r.get(d),p=new Map;for(const y of c)p.set(y.node.path,y);const f=[];for(const y of c)if(y.parentKey&&p.has(y.parentKey)){const g=p.get(y.parentKey);g.node.children=g.node.children??[],g.node.children.push(y.node)}else f.push(y.node);for(const y of c)kg(y.node,n,o);a.push({path:`__group__/${d}`,layoutName:null,label:dD(d,e),labelSource:"editor_label",source:{kind:"core",identifier:null},kind:"route",children:f})}if(e.modals.length>0){const d=e.modals.map(c=>({path:`__modal__/${c.hostLayout}/${c.modalId}`,layoutName:c.hostLayout,label:c.label??c.modalId,labelSource:c.label?"editor_label":"path",source:{kind:"core",identifier:null},kind:"modal",modalId:c.modalId,modalHostLayout:c.hostLayout,children:[]}));a.push({path:"__group__/modal",layoutName:null,label:"$t:layout_editor.chrome.route_tree.group.modal",labelSource:"editor_label",source:{kind:"core",identifier:null},kind:"route",children:d})}const l=e.extensionGroups??[];if(l.length>0){const d=l.map(c=>{const p=c.sourceType==="plugin"?"🔌":"🧩",f=c.extensions.map(y=>({path:`__extension__/${y.id}`,layoutName:null,label:`${c.sourceLabel} · ${y.targetName}`,labelSource:"path",source:{kind:c.sourceType,identifier:c.sourceIdentifier},kind:"extension",extensionId:String(y.id),extensionType:y.extensionType,extensionPriority:y.priority,extensionTargetName:y.targetName,isModified:y.isModified,isInactive:!y.isActive,children:[]}));return{path:`__extgroup__/${c.sourceType}/${c.sourceIdentifier}`,layoutName:null,label:`${p} ${c.sourceLabel}`,labelSource:"path",source:{kind:c.sourceType,identifier:c.sourceIdentifier},kind:"route",children:f}});a.push({path:"__group__/extension",layoutName:null,label:"$t:layout_editor.chrome.route_tree.group.extension",labelSource:"editor_label",source:{kind:"core",identifier:null},kind:"route",children:d})}return a}function uD(e){const t=lD(e);return cD(e,t)}const On=bo("useEditorRoutes");function pD(e,t){if(typeof e=="string")return e.length>0?e:null;if(e&&typeof e=="object"){const n=e[t];if(typeof n=="string"&&n.length>0)return n;if(typeof e.en=="string"&&e.en.length>0)return e.en;for(const o of Object.values(e))if(typeof o=="string"&&o.length>0)return o}return null}function Sg(e,t){const n={};if(typeof window>"u")return n;const r=window.G7Config?.[e];if(!Array.isArray(r))return n;for(const s of r)s&&typeof s.identifier=="string"&&(n[s.identifier]=pD(s.display_name,t)??s.identifier);return n}function fD(e){const t=new vd,n=typeof window<"u"?window.G7Config:void 0,o={_global:{settings:n?.settings??{},plugins:n?.plugins??{},modules:n?.modules??{},appConfig:n?.appConfig??{}}};return e.map(r=>{const s={...r};if(typeof s.path=="string"&&s.path.includes("{{")){const a=s.path;let l=t.resolveBindings(s.path,o);l=l.replace(/\/\/+/g,"/")||"/",l.includes("{{")&&(On.warn("편집기 route path 표현식 평가 실패 — fallback 적용",{originalPath:a}),l=a.replace(/\{\{[^}]+\}\}/g,"").replace(/\/\/+/g,"/")||"/"),s.path=l}if(typeof s.redirect=="string"&&s.redirect.includes("{{")){const a=s.redirect;let l=t.resolveBindings(a,o);l=l.replace(/\/\/+/g,"/")||"/",l.includes("{{")&&(On.warn("편집기 route redirect 표현식 평가 실패 — fallback 적용",{originalRedirect:a}),l=a.replace(/\{\{[^}]+\}\}/g,"").replace(/\/\/+/g,"/")||"/"),s.redirect=l}return s})}function gD(e){const{dispatch:t}=Je(),{translationEngine:n,translationContext:o}=tt(),{templateIdentifier:r,cacheVersion:s}=e;u.useEffect(()=>{let a=!1;const l=s??(typeof window<"u"?window.G7Config?.cache_version:void 0)??"",d=l!==""?`?v=${encodeURIComponent(String(l))}`:"",c=en(`/api/admin/templates/${encodeURIComponent(r)}/editor/routes`,"json",null,d.replace(/^[?&]+/,"")),p=o?.locale??"ko",f=n&&r!==o?.templateId?n.loadTranslations(r,p).then(()=>{On.log("편집 대상 lang 로드 완료",{targetTemplate:r,locale:p})}).catch(h=>{On.warn("편집 대상 lang 로드 실패 — 라우트 라벨 fallback 사용",h)}):Promise.resolve(),y=(()=>{if(typeof fetch!="function")return Promise.reject(io(new Error("fetch unavailable"),"routes"));try{const h=fetch(c,{credentials:"same-origin",headers:je()});return!h||typeof h.then!="function"?Promise.reject(io(new Error("fetch returned non-Promise (test stub?)"),"routes")):h.then(async v=>{if(!v.ok){const b=await v.json().catch(()=>null);throw Gr(v.status,b,"routes")}return v.json()})}catch(h){return Promise.reject(h)}})(),g=`/api/admin/templates/${encodeURIComponent(r)}/layout-extensions${d}`,m=(()=>{if(typeof fetch!="function")return Promise.resolve(null);try{const h=fetch(g,{credentials:"same-origin",headers:je()});return!h||typeof h.then!="function"?Promise.resolve(null):h.then(async v=>v.ok?await v.json():null).catch(()=>null)}catch{return Promise.resolve(null)}})();return Promise.all([y,f,m]).then(([h,,v])=>{if(a)return;if(!h||!h.success||!Array.isArray(h.data?.routes)){On.warn("routes 응답 형식이 비정상 — empty tree 로 둠",h),t({type:"SET_ROUTES_ERROR",error:{kind:"unknown",status:200,message:"routes 응답 형식이 비정상입니다.",source:"routes"}});return}const b=fD(h.data.routes),x=Sg("activeModules",p),k=Sg("activePlugins",p),w=(h.data.base_layouts??[]).map(A=>({layoutName:A.layout_name,label:A.label??void 0})),_=(h.data.modals??[]).map(A=>({modalId:A.modal_id,hostLayout:A.host_layout,label:A.label??void 0})),C=(v?.success&&Array.isArray(v.data)?v.data:[]).map(A=>({sourceIdentifier:A.source_identifier,sourceType:A.source_type,sourceLabel:A.source_label,extensions:(A.extensions??[]).map(z=>({id:z.id,extensionType:z.extension_type,targetName:z.target_name,priority:z.priority,isActive:z.is_active,isModified:z.is_modified,hostLayouts:Array.isArray(z.host_layouts)?z.host_layouts:[],currentVersion:typeof z.current_version=="number"?z.current_version:void 0}))})),E=uD({routes:b,modals:_,baseLayouts:w,extensionGroups:C,moduleDisplayNames:x,pluginDisplayNames:k});t({type:"SET_ROUTE_TREE",tree:E});const S=h.data.layout_versions,T={};if(S&&typeof S=="object"&&!Array.isArray(S))for(const[A,z]of Object.entries(S))typeof z=="number"&&Number.isFinite(z)&&(T[A]=z);t({type:"SET_LAYOUT_VERSIONS",versions:T});const $={};for(const A of C)for(const z of A.extensions){const D=z.currentVersion;typeof D=="number"&&Number.isFinite(D)&&($[String(z.id)]=D)}t({type:"SET_EXTENSION_VERSIONS",versions:$}),On.log("routes 로드 완료",{count:b.length,groups:E.length})}).catch(h=>{if(a)return;On.error("routes 로드 실패",h);const v=hp(h)?h:io(h,"routes");t({type:"SET_ROUTES_ERROR",error:v})}),()=>{a=!0}},[r,s,t,n,o?.locale,o?.templateId])}function Cg(e,t,n,o=[]){const r=t?$g(o,t):null,s=r!==null?{path:r.path,layoutName:r.layoutName}:void 0;if(e.startsWith("__base__/")){const a=e.slice(9);if(a)return n({type:"ENTER_BASE_EDIT",layoutName:a,returnRoute:s}),!0}else if(e.startsWith("__modal__/")){const a=e.slice(10);return a&&!a.includes("/")&&t?(n({type:"ENTER_MODAL_EDIT",modalId:a,hostLayout:t,returnRoute:s}),!0):!1}else if(e.startsWith("__extension__/")){const a=e.slice(14);if(a&&!a.includes("/"))return n({type:"ENTER_EXTENSION_EDIT",extensionId:a,returnRoute:s}),!0}else if(e.startsWith("__iteration__/")){const a=e.slice(14);return a&&t?(n({type:"ENTER_ITERATION_ITEM_EDIT",sourcePath:a,hostLayout:t,returnRoute:s}),!0):!1}return!1}function $g(e,t){for(const n of e){if(n.kind==="route"&&n.layoutName===t)return n;if(n.children&&n.children.length>0){const o=$g(n.children,t);if(o)return o}}return null}function Xa(e,t){for(const n of e){if(n.kind==="route"&&n.path===t)return n;if(n.children&&n.children.length>0){const o=Xa(n.children,t);if(o)return o}}return null}function hD(e){const{state:t,dispatch:n}=Je(),o=u.useRef(!1),r=u.useRef(t.editMode);r.current=t.editMode,u.useEffect(()=>{if(o.current||t.routeTree.length===0||typeof window>"u")return;const a=ap(window.location.search);if(a){const c=sp(window.location.search),p=Cg(a,c,n,t.routeTree);if(o.current=!0,p)return}const l=ip(window.location.search);if(!l){o.current=!0;return}const d=Xa(t.routeTree,l);d&&n({type:"SELECT_ROUTE",route:{path:d.path,layoutName:d.layoutName}}),o.current=!0},[t.routeTree,n]),u.useEffect(()=>{if(typeof window>"u")return;const a=()=>{const l=ap(window.location.search);if(l){const p=sp(window.location.search);if(Cg(l,p,n,t.routeTree))return}if(r.current!=="route")switch(r.current){case"base":n({type:"EXIT_BASE_EDIT"});return;case"modal":n({type:"EXIT_MODAL_EDIT"});return;case"extension":n({type:"EXIT_EXTENSION_EDIT"});return;case"iteration_item":n({type:"EXIT_ITERATION_ITEM_EDIT"});return}const d=ip(window.location.search);if(!d){n({type:"SELECT_ROUTE",route:null});return}const c=Xa(t.routeTree,d);c&&n({type:"SELECT_ROUTE",route:{path:c.path,layoutName:c.layoutName}})};return window.addEventListener("popstate",a),()=>{window.removeEventListener("popstate",a)}},[t.routeTree,n]);const s=u.useRef("route");u.useEffect(()=>{if(typeof window>"u"||!window.history?.replaceState||t.routeTree.length===0)return;const a=Zs(e,t.selectedRoute?.path??null,t.selectedRoute?.layoutName??null),l=`${window.location.pathname}${window.location.search}`,d=s.current;if(s.current=t.editMode,l===a)return;d==="route"&&t.editMode!=="route"&&window.history.pushState?window.history.pushState(window.history.state,"",a):window.history.replaceState(window.history.state,"",a)},[t.selectedRoute,t.editMode,t.routeTree,e])}function yD(){u.useEffect(()=>{if(typeof document>"u")return;const e=document.documentElement;let t=e.classList.contains("dark");const n=()=>{e.classList.contains("dark")&&(t=!0,e.classList.remove("dark"))};n();let o=null;return typeof MutationObserver<"u"&&(o=new MutationObserver(n),o.observe(e,{attributes:!0,attributeFilter:["class"]})),()=>{o?.disconnect(),t&&e.classList.add("dark")}},[])}function bD(){u.useEffect(()=>{if(typeof document>"u"||!document.body)return;const e=document.body,t=()=>{e.style.overflow==="hidden"&&(e.style.overflow=""),e.style.overflowY==="hidden"&&(e.style.overflowY="")};t();let n=null;return typeof MutationObserver<"u"&&(n=new MutationObserver(t),n.observe(e,{attributes:!0,attributeFilter:["style"]})),()=>{n?.disconnect()}},[])}function mD(e){if(typeof e!="string")return null;const t="__extension__/";if(!e.startsWith(t))return null;const n=e.slice(t.length);return n===""||n.includes("/")?null:n}function vD(e){if(Array.isArray(e.components))return e.components;if(Array.isArray(e.injections)){const t=[];return e.injections.forEach((n,o)=>{if(n&&Array.isArray(n.components))for(const r of n.components)t.push({...r,__injectionIndex:o})}),t}return[]}function _D(e,t){if(Array.isArray(e.components)){const n=t.map(o=>Ja(o));return{...e,components:n}}if(Array.isArray(e.injections)){const n=e.injections.map(o=>({...o}));n.forEach(o=>{Array.isArray(o.components)&&(o.components=[])});for(const o of t){const r=o.__injectionIndex,s=typeof r=="number"&&r>=0&&rJa(n))),t}function Eg(e,t){const n=[],o=r=>{if(Array.isArray(r))for(const s of r){if(!s||typeof s!="object")continue;const a=s.__source;if(a?.kind==="extension"&&a.extensionId===t){n.push(s);continue}Array.isArray(s.children)&&o(s.children)}};return o(e),n}function Ig(e,t){const o=Eg(e,t)[0]?.__source;return o&&o.kind==="extension"&&o.extensionId===t?o:{kind:"extension",extensionId:t}}function Za(e,t){for(const n of e){const o=n?.__source;if(o?.kind==="extension"&&o.extensionId===t||Array.isArray(n.children)&&Za(n.children,t))return!0}return!1}function xD(){const{state:e}=Je(),t=e.templateIdentifier,n=e.editMode==="extension"?mD(e.selectedRoute?.path):null,o=e.editMode==="extension"&&typeof e.selectedRoute?.layoutName=="string"?e.selectedRoute.layoutName:null,[r,s]=u.useState(null),[a,l]=u.useState(!1),[d,c]=u.useState(null),[p,f]=u.useState(!1),[y,g]=u.useState(0),[m,h]=u.useState(null),v=u.useRef({}),b=u.useRef({kind:"extension",extensionId:0}),x=u.useRef(null);u.useEffect(()=>{x.current=r},[r]),u.useEffect(()=>{h(null)},[n]);const k=u.useCallback(async A=>{const z=window.G7Config?.cache_version??0,D=en(`/api/layouts/${encodeURIComponent(t)}/${A}`,"json",`${z}.${Sp()}`,"with_source_meta=1"),O=ar(),M={Accept:"application/json"};O&&(M.Authorization=`Bearer ${O}`);const B=await fetch(D,{headers:M,credentials:"same-origin"});if(!B.ok)return null;const N=await B.json().catch(()=>null);return(N&&(N.data||N))??{}},[t]),w=u.useCallback(async()=>{if(!n){s(null),c(null),f(!1);return}l(!0),c(null);try{const A=window.G7Config?.cache_version??0,z=`/api/admin/templates/${encodeURIComponent(t)}/layout-extensions/${encodeURIComponent(n)}?v=${A}`,D=await fetch(z,{headers:je(),credentials:"same-origin"}),O=await D.json().catch(()=>null);if(!D.ok){c(Gr(D.status,O,"layout")),s(null),f(!1);return}const M=(O&&(O.data||O))??{};let B={};const N=M.content;if(typeof N=="string")try{B=JSON.parse(N)}catch{B={}}else N&&typeof N=="object"&&(B=N);const H=M.extension_type==="overlay"?"overlay":"extension_point",L=typeof M.lock_version=="number"?M.lock_version:0,I=Array.isArray(M?.host_layouts)?M.host_layouts.filter(re=>typeof re=="string"):[],P=o??(I.length===1?I[0]:m&&I.includes(m)?m:null),F=Number(n);v.current=B;let V=vD(B),Y={},Z="pending";const se={kind:"extension",extensionId:F};if(b.current=se,P){const re=await k(P);if(re){Y=re;const U=Array.isArray(re.components)?re.components:[],ee=Ig(U,F);if(b.current=ee,Za(U,F))V=U,Z="ok";else{const K=(Array.isArray(re.modals)?re.modals:[]).filter(ne=>ne&&typeof ne=="object"&&Za([ne],F));if(K.length>0){const ne=K.map(me=>({...me,props:{...me.props??{},isOpen:!0}}));V=[...U,...ne],b.current=Ig(K,F),Z="ok"}else V=U,Z="no-injection"}}}s({extensionId:F,content:B,extensionType:H,components:V,lockVersion:L,hostLayouts:I,hostLayoutName:P,hostRaw:Y,editability:Z}),f(!1),st({op:"load",layoutName:`extension:${n}`,editMode:"extension",statusCode:D.status,timestamp:Date.now()})}catch(A){c(io(A,"layout")),s(null),f(!1)}finally{l(!1)}},[t,n,m,o,k]);u.useEffect(()=>{w()},[w]);const _=u.useCallback(A=>{h(A)},[]),C=u.useCallback(A=>{s(z=>z&&{...z,components:A}),f(!0),st({op:"patch",layoutName:`extension:${x.current?.extensionId??""}`,editMode:"extension",isDirty:!0,timestamp:Date.now()})},[]),E=u.useCallback(A=>{s(z=>z&&{...z,components:A(z.components)}),f(!0)},[]),S=u.useCallback(async()=>{const A=x.current;if(!A)return{kind:"guard_no_document"};const z=`/api/admin/templates/${encodeURIComponent(t)}/layout-extensions/${A.extensionId}`,D=Eg(A.components,A.extensionId),O=D.length>0?D:A.components,M=_D(v.current,O);st({op:"save",layoutName:`extension:${A.extensionId}`,editMode:"extension",saveTarget:"layout_extension",endpoint:z,isDirty:p,timestamp:Date.now()});let B;try{B=await fetch(z,{method:"PUT",headers:je({"Content-Type":"application/json"}),credentials:"same-origin",body:JSON.stringify({content:M,expected_lock_version:A.lockVersion})})}catch(H){return{kind:"network_error",message:H instanceof Error?H.message:"network error"}}const N=await B.json().catch(()=>null);if(st({op:"save_response",layoutName:`extension:${A.extensionId}`,editMode:"extension",endpoint:z,statusCode:B.status,conflict:B.status===409?{currentVersion:N?.errors?.current_version??N?.current_version,yourVersion:N?.errors?.your_version??N?.your_version}:void 0,timestamp:Date.now()}),B.ok){const H=N?.data??{},L=typeof H.lock_version=="number"?H.lock_version:A.lockVersion+1;s(P=>P&&{...P,lockVersion:L}),f(!1),g(P=>P+1);const I=typeof H.current_version=="number"?H.current_version:void 0;return{kind:"success",newLockVersion:L,newContentVersion:I,savedExtensionId:String(A.extensionId)}}return B.status===409?{kind:"concurrent_modification",currentVersion:N?.current_version??-1,yourVersion:N?.your_version??A.lockVersion}:B.status===422?{kind:"validation_failed",status:422,errors:N?.errors??null}:{kind:"network_error",message:N?.message??`HTTP ${B.status}`}},[t,p]),T=u.useCallback(async()=>{na(),await w()},[w]),$=!!r&&r.hostLayouts.length>1&&r.hostLayoutName===null;return{document:r,isLoading:a,error:d,isDirty:p,saveSuccessCounter:y,setLayoutComponents:C,patchLayout:E,save:S,reload:T,needsHostPicker:$,selectHost:_}}const Ag=new Set;function wD(e,t,n){const r=e&&Object.keys(e).length>0?e:t,s=Array.isArray(r.data_sources)?r.data_sources:[],a=Array.isArray(t.data_sources)?t.data_sources:[],l=new Set,d=[];for(const c of[...s,...a]){const p=c&&typeof c=="object"?c.id:void 0,f=typeof p=="string"?p:`__anon_${d.length}`;l.has(f)||(l.add(f),d.push(c))}return{...r,components:n,...d.length>0?{data_sources:d}:{},lock_version:void 0}}function kD(e){return{document:e.document?{layoutName:`extension:${e.document.extensionId}`,raw:{...wD(e.document.hostRaw,e.document.content,e.document.components),__editability:e.document.editability,__extensionId:e.document.extensionId},lockVersion:e.document.lockVersion}:null,isLoading:e.isLoading,error:e.error,isDirty:e.isDirty,saveSuccessCounter:e.saveSuccessCounter,reload:e.reload,patchLayout:e.patchLayout,setLayoutComponents:e.setLayoutComponents,patchDocumentRaw:()=>{},dirtyKeys:Ag,dirtyLayoutNames:Ag,invalidateCurrentCache:()=>{},reloadCounter:0,save:()=>e.save(),markDirty:()=>{}}}function SD({hostLayouts:e,extensionLabel:t,onSelect:n}){const{t:o}=tt();return i.jsx("div",{className:"g7le-host-picker__backdrop","data-testid":"g7le-host-picker",role:"dialog","aria-modal":"true","aria-label":o("layout_editor.host_picker.title"),style:{position:"fixed",inset:0,zIndex:9500,display:"flex",alignItems:"center",justifyContent:"center",background:"rgba(15, 23, 42, 0.55)"},children:i.jsxs("div",{className:"g7le-host-picker__panel",style:{width:"min(520px, 92vw)",maxHeight:"80vh",overflow:"auto",background:"#ffffff",color:"#0f172a",borderRadius:12,boxShadow:"0 20px 48px rgba(15, 23, 42, 0.28)",padding:"20px 22px"},children:[i.jsx("h2",{className:"g7le-host-picker__title",style:{margin:"0 0 6px",fontSize:16,fontWeight:700},children:o("layout_editor.host_picker.title")}),i.jsx("p",{className:"g7le-host-picker__desc",style:{margin:"0 0 16px",fontSize:13,color:"#475569",lineHeight:1.6},children:t?o("layout_editor.host_picker.description_with_label",{label:t}):o("layout_editor.host_picker.description")}),i.jsx("ul",{className:"g7le-host-picker__list",style:{listStyle:"none",margin:0,padding:0,display:"grid",gap:8},children:e.map(r=>i.jsx("li",{children:i.jsxs("button",{type:"button",className:"g7le-host-picker__item","data-testid":"g7le-host-picker-item","data-host":r,onClick:()=>n(r),style:{width:"100%",textAlign:"left",padding:"12px 14px",border:"1px solid #e2e8f0",borderRadius:8,background:"#f8fafc",cursor:"pointer",fontSize:13,fontWeight:600,color:"#0f172a",display:"flex",alignItems:"center",gap:8},children:[i.jsx("span",{"aria-hidden":!0,style:{fontSize:14},children:"🖼"}),i.jsx("span",{style:{flex:1,minWidth:0,overflow:"hidden",textOverflow:"ellipsis"},children:r}),i.jsx("span",{"aria-hidden":!0,style:{color:"#94a3b8"},children:"→"})]})},r))})]})})}function CD(e,t){const[n,o]=u.useState(!1),[r,s]=u.useState(null),a=u.useCallback(async l=>{if(!t||!l)return{kind:"no_document"};o(!0),s(null);try{const d=vo(l),c=`/api/admin/templates/${encodeURIComponent(e)}/layouts/${t}/preview`,p=await fetch(c,{method:"POST",credentials:"same-origin",headers:je({"Content-Type":"application/json"}),body:JSON.stringify({content:JSON.stringify(d)})}),f=await p.json().catch(()=>null);if(!p.ok){const h=f?.message??`HTTP ${p.status}`;return s(h),{kind:"network_error",message:h}}const y=f?.data,g=typeof y?.token=="string"?y.token:"",m=typeof y?.preview_url=="string"&&y.preview_url?y.preview_url:g?`/preview/${g}`:"";return m?(window.G7Core?.dispatch?.({handler:"openWindow",params:{path:m}}),{kind:"success",previewUrl:m,token:g}):(s("invalid preview response"),{kind:"network_error",message:"invalid preview response"})}catch(d){const c=d instanceof Error?d.message:"network error";return s(c),{kind:"network_error",message:c}}finally{o(!1)}},[e,t]);return{isCreating:n,error:r,createPreview:a}}const ce="layout_editor.action_recipe",bn={setState:{label:`$t:${ce}.set_state.label`,params:[{key:"target",label:`$t:${ce}.set_state.param_target`,widget:"select",options:[{value:"local"},{value:"global"}]},{key:"state",label:`$t:${ce}.set_state.param_state`,widget:"state-key-value"},{key:"merge",label:`$t:${ce}.set_state.param_merge`,widget:"toggle"}],build:{handler:"setState",params:{target:"{{target}}",merge:"{{merge}}","...":"{{state}}"}}},loadFromLocalStorage:{label:`$t:${ce}.load_local.label`,params:[{key:"key",label:`$t:${ce}.load_local.param_key`,widget:"text",required:!0},{key:"stateKey",label:`$t:${ce}.load_local.param_state_key`,widget:"state-key-picker"},{key:"defaultValue",label:`$t:${ce}.load_local.param_default`,widget:"text"}],build:{handler:"loadFromLocalStorage",params:{key:"{{key}}",stateKey:"{{stateKey}}",defaultValue:"{{defaultValue}}"}}},saveToLocalStorage:{label:`$t:${ce}.save_local.label`,params:[{key:"key",label:`$t:${ce}.save_local.param_key`,widget:"text",required:!0},{key:"value",label:`$t:${ce}.save_local.param_value`,widget:"data-chip"}],build:{handler:"saveToLocalStorage",params:{key:"{{key}}",value:"{{value}}"}}},navigate:{label:`$t:${ce}.navigate.label`,params:[{key:"path",label:`$t:${ce}.navigate.param_path`,widget:"page-picker",required:!0},{key:"query",label:`$t:${ce}.navigate.param_query`,widget:"key-value"},{key:"mergeQuery",label:`$t:${ce}.navigate.param_merge_query`,widget:"toggle"},{key:"scroll",label:`$t:${ce}.navigate.param_scroll`,widget:"select",options:[{value:"top"},{value:"preserve"},{value:"none"}]},{key:"replace",label:`$t:${ce}.navigate.param_replace`,widget:"toggle"},{key:"transition_overlay_target",label:`$t:${ce}.navigate.param_overlay_target`,widget:"component-target-picker",dependsOn:{param:"replace",equals:!0}}],build:{handler:"navigate",params:{path:"{{path}}",query:"{{query}}",mergeQuery:"{{mergeQuery}}",scroll:"{{scroll}}",replace:"{{replace}}",transition_overlay_target:"{{transition_overlay_target}}"}}},openWindow:{label:`$t:${ce}.open_window.label`,params:[{key:"target",label:`$t:${ce}.open_window.param_target`,widget:"page-picker",required:!0},{key:"windowTarget",label:`$t:${ce}.open_window.param_window_target`,widget:"select",options:[{value:"_blank"},{value:"_self"}]}],build:{handler:"openWindow",target:"{{target}}",params:{windowTarget:"{{windowTarget}}"}}},navigateBack:{label:`$t:${ce}.navigate_back.label`,params:[],build:{handler:"navigateBack"}},navigateForward:{label:`$t:${ce}.navigate_forward.label`,params:[],build:{handler:"navigateForward"}},replaceUrl:{label:`$t:${ce}.replace_url.label`,params:[{key:"path",label:`$t:${ce}.replace_url.param_path`,widget:"page-picker"},{key:"query",label:`$t:${ce}.replace_url.param_query`,widget:"key-value"}],build:{handler:"replaceUrl",params:{path:"{{path}}",query:"{{query}}"}}},toast:{label:`$t:${ce}.toast.label`,params:[{key:"message",label:`$t:${ce}.toast.param_message`,widget:"i18n-text",required:!0},{key:"type",label:`$t:${ce}.toast.param_type`,widget:"select",options:[{value:"info"},{value:"success"},{value:"warning"},{value:"error"}]},{key:"duration",label:`$t:${ce}.toast.param_duration`,widget:"number"},{key:"icon",label:`$t:${ce}.toast.param_icon`,widget:"text"}],build:{handler:"toast",params:{message:"{{message}}",type:"{{type}}",duration:"{{duration}}",icon:"{{icon}}"}}},openModal:{label:`$t:${ce}.open_modal.label`,params:[{key:"target",label:`$t:${ce}.open_modal.param_target`,widget:"modal-picker",required:!0}],build:{handler:"openModal",target:"{{target}}"}},closeModal:{label:`$t:${ce}.close_modal.label`,params:[],build:{handler:"closeModal"}},showAlert:{label:`$t:${ce}.show_alert.label`,params:[{key:"target",label:`$t:${ce}.show_alert.param_message`,widget:"i18n-text",required:!0}],build:{handler:"showAlert",target:"{{target}}"}},setError:{label:`$t:${ce}.set_error.label`,params:[{key:"target",label:`$t:${ce}.set_error.param_message`,widget:"i18n-text"},{key:"stateTarget",label:`$t:${ce}.set_error.param_state_target`,widget:"select",options:[{value:"local"},{value:"global"}]}],build:{handler:"setError",target:"{{target}}",params:{stateTarget:"{{stateTarget}}"}}},refetchDataSource:{label:`$t:${ce}.refetch.label`,params:[{key:"dataSourceId",label:`$t:${ce}.refetch.param_data_source`,widget:"datasource-picker",required:!0},{key:"sync",label:`$t:${ce}.refetch.param_sync`,widget:"toggle"}],build:{handler:"refetchDataSource",params:{dataSourceId:"{{dataSourceId}}",sync:"{{sync}}"}}},appendDataSource:{label:`$t:${ce}.append_data.label`,params:[{key:"dataSourceId",label:`$t:${ce}.append_data.param_data_source`,widget:"datasource-picker",required:!0},{key:"dataPath",label:`$t:${ce}.append_data.param_data_path`,widget:"text"},{key:"newData",label:`$t:${ce}.append_data.param_new_data`,widget:"data-chip"}],build:{handler:"appendDataSource",params:{dataSourceId:"{{dataSourceId}}",dataPath:"{{dataPath}}",newData:"{{newData}}"}}},updateDataSource:{label:`$t:${ce}.update_data.label`,params:[{key:"dataSourceId",label:`$t:${ce}.update_data.param_data_source`,widget:"datasource-picker",required:!0},{key:"data",label:`$t:${ce}.update_data.param_data`,widget:"data-chip"},{key:"merge",label:`$t:${ce}.update_data.param_merge`,widget:"toggle"}],build:{handler:"updateDataSource",params:{dataSourceId:"{{dataSourceId}}",data:"{{data}}",merge:"{{merge}}"}}},scrollIntoView:{label:`$t:${ce}.scroll.label`,params:[{key:"selector",label:`$t:${ce}.scroll.param_selector`,widget:"text",required:!0},{key:"behavior",label:`$t:${ce}.scroll.param_behavior`,widget:"select",options:[{value:"smooth"},{value:"auto"}]},{key:"block",label:`$t:${ce}.scroll.param_block`,widget:"select",options:[{value:"start"},{value:"center"},{value:"end"},{value:"nearest"}]}],build:{handler:"scrollIntoView",params:{selector:"{{selector}}",behavior:"{{behavior}}",block:"{{block}}"}}},login:{label:`$t:${ce}.login.label`,params:[{key:"body",label:`$t:${ce}.login.param_body`,widget:"key-value"}],build:{handler:"login",params:{body:"{{body}}"}}},logout:{label:`$t:${ce}.logout.label`,params:[{key:"target",label:`$t:${ce}.logout.param_target`,widget:"text"}],build:{handler:"logout",target:"{{target}}"}},setLocale:{label:`$t:${ce}.set_locale.label`,params:[{key:"target",label:`$t:${ce}.set_locale.param_target`,widget:"locale-picker",required:!0}],build:{handler:"setLocale",target:"{{target}}"}},emitEvent:{label:`$t:${ce}.emit_event.label`,params:[{key:"event",label:`$t:${ce}.emit_event.param_event`,widget:"text",required:!0},{key:"data",label:`$t:${ce}.emit_event.param_data`,widget:"data-chip"}],build:{handler:"emitEvent",params:{event:"{{event}}",data:"{{data}}"}}},apiCall:{label:`$t:${ce}.api_call.label`,params:[{key:"target",label:`$t:${ce}.api_call.param_target`,widget:"page-picker",required:!0},{key:"method",label:`$t:${ce}.api_call.param_method`,widget:"select",options:[{value:"GET"},{value:"POST"},{value:"PUT"},{value:"PATCH"},{value:"DELETE"}]},{key:"body",label:`$t:${ce}.api_call.param_body`,widget:"key-value"},{key:"query",label:`$t:${ce}.api_call.param_query`,widget:"key-value"},{key:"identity_target_email",label:`$t:${ce}.api_call.param_identity_target_email`,widget:"data-chip"},{key:"identity_target_phone",label:`$t:${ce}.api_call.param_identity_target_phone`,widget:"data-chip"},{key:"onSuccess",label:`$t:${ce}.api_call.param_on_success`,widget:"action-list"},{key:"onError",label:`$t:${ce}.api_call.param_on_error`,widget:"action-list"}],build:{handler:"apiCall",target:"{{target}}",identity_target:{email:"{{identity_target_email}}",phone:"{{identity_target_phone}}"},onSuccess:"{{onSuccess}}",onError:"{{onError}}",params:{method:"{{method}}",body:"{{body}}",query:"{{query}}"}}},conditions:{label:`$t:${ce}.conditions.label`,params:[{key:"branches",label:`$t:${ce}.conditions.param_branches`,widget:"branch-list"}],build:{handler:"conditions",conditions:"{{branches}}"}},sequence:{label:`$t:${ce}.sequence.label`,params:[{key:"actions",label:`$t:${ce}.sequence.param_actions`,widget:"action-list"}],build:{handler:"sequence",params:{actions:"{{actions}}"}}},parallel:{label:`$t:${ce}.parallel.label`,params:[{key:"actions",label:`$t:${ce}.parallel.param_actions`,widget:"action-list"}],build:{handler:"parallel",params:{actions:"{{actions}}"}}},switch:{label:`$t:${ce}.switch.label`,params:[{key:"value",label:`$t:${ce}.switch.param_value`,widget:"data-chip"},{key:"cases",label:`$t:${ce}.switch.param_cases`,widget:"action-list",advanced:!0}],build:{handler:"switch",params:{value:"{{value}}",cases:"{{cases}}"}}},suppress:{label:`$t:${ce}.suppress.label`,params:[],build:{handler:"suppress"}}};function $D(){return{actionRecipes:bn,initActionRecipes:bn,errorRecipes:{navigate:bn.navigate,openModal:bn.openModal,toast:bn.toast,setState:bn.setState,sequence:bn.sequence,parallel:bn.parallel,showErrorPage:{label:`$t:${ce}.show_error_page.label`,params:[{key:"target",label:`$t:${ce}.show_error_page.param_target`,widget:"select",options:[{value:"content"},{value:"full"}]}],build:{handler:"showErrorPage",params:{target:"{{target}}"}}}}}}function Qa(e,t){return typeof e.label=="string"?e.label.startsWith("$t:")?t(e.label.slice(3)):e.label:String(e.value)}function vi(e){return Array.isArray(e.options)?e.options:[]}function ED({control:e,value:t,onChange:n,t:o}){const r=vi(e);return i.jsx("div",{className:"g7le-widget g7le-widget--segmented","data-testid":"g7le-widget-segmented",style:WD,children:r.map(s=>{const a=s.value===t;return i.jsx("button",{type:"button","data-testid":`g7le-segment-${String(s.value)}`,"data-active":a?"true":"false",onClick:()=>n(a?void 0:s.value),style:{...GD,background:a?"#2563eb":"#fff",color:a?"#fff":"#0f172a",borderColor:a?"#2563eb":"#cbd5e1"},children:Qa(s,o)},String(s.value))})})}function ID({control:e,value:t,onChange:n,t:o}){const r=Array.isArray(e.scale)?e.scale:[],s=r.findIndex(l=>l===t),a=t!=null;return i.jsxs("div",{className:"g7le-widget g7le-widget--slider","data-testid":"g7le-widget-slider",style:Dg,children:[i.jsxs("label",{style:Ra,children:[i.jsx("input",{type:"checkbox","data-testid":"g7le-slider-enabled",checked:a,onChange:l=>{l.target.checked?n(r.length>0?r[Math.max(0,s===-1?0:s)]:void 0):n(void 0)}}),i.jsx("span",{style:{fontSize:12,color:"#64748b"},children:o("layout_editor.control.use")})]}),i.jsx("input",{type:"range","data-testid":"g7le-slider-range",min:0,max:Math.max(0,r.length-1),step:1,disabled:!a||r.length===0,value:s===-1?0:s,onChange:l=>{const d=Number(l.target.value);n(r[d])},style:{flex:1}}),i.jsx("span",{"data-testid":"g7le-slider-value",style:{fontSize:12,color:"#0f172a",minWidth:48,textAlign:"right"},children:a?String(t):o("layout_editor.control.default")})]})}function AD({control:e,value:t,onChange:n,t:o}){const r=vi(e);return i.jsxs("select",{className:"g7le-widget g7le-widget--select","data-testid":"g7le-widget-select",value:t==null?"":String(t),onChange:s=>{const a=s.target.value;if(a===""){n(void 0);return}const l=r.find(d=>String(d.value)===a);n(l?l.value:a)},style:YD,children:[i.jsx("option",{value:"",children:o("layout_editor.control.default")}),r.map(s=>i.jsx("option",{value:String(s.value),children:Qa(s,o)},String(s.value)))]})}function TD({control:e,value:t,onChange:n}){const o=vi(e),r=o[1]?.value??!0,s=o[0]?.value??void 0,a=t===r;return i.jsx("button",{type:"button",className:"g7le-widget g7le-widget--toggle","data-testid":"g7le-widget-toggle",role:"switch","aria-checked":a,onClick:()=>n(a?s:r),style:{...XD,background:a?"#2563eb":"#cbd5e1"},children:i.jsx("span",{style:{...JD,transform:a?"translateX(18px)":"translateX(0)"}})})}function DD({control:e,value:t,onChange:n,t:o,freeValueDisabled:r}){const s=vi(e),a=t==null?"":String(t),[l,d]=u.useState(a);u.useEffect(()=>{d(a)},[a]);const c=p=>{const f=p.trim();if(f===""){n(void 0);return}n(f)};return i.jsxs("div",{className:"g7le-widget g7le-widget--dimension","data-testid":"g7le-widget-dimension",style:jD,children:[r?i.jsx("div",{"data-testid":"g7le-dimension-free-disabled",style:UD,children:o("layout_editor.property_modal.dark_preset_only")}):i.jsxs("div",{style:HD,children:[i.jsx("input",{type:"text","data-testid":"g7le-dimension-input",value:l,placeholder:o("layout_editor.control.dimension.placeholder"),onChange:p=>d(p.target.value),onBlur:p=>c(p.target.value),onKeyDown:p=>{p.key==="Enter"&&(p.preventDefault(),c(p.target.value))},style:FD}),i.jsx("button",{type:"button","data-testid":"g7le-dimension-clear",title:o("layout_editor.control.default"),onClick:()=>{d(""),n(void 0)},style:KD,children:o("layout_editor.control.default")})]}),s.length>0&&i.jsx("div",{style:VD,"data-testid":"g7le-dimension-chips",children:s.map(p=>{const f=String(p.value),y=f===a;return i.jsx("button",{type:"button","data-testid":`g7le-dimension-chip-${f}`,"data-active":y?"true":"false",onClick:()=>{d(f),n(f)},style:{...qD,background:y?"#2563eb":"#fff",color:y?"#fff":"#0f172a",borderColor:y?"#2563eb":"#cbd5e1"},children:Qa(p,o)},f)})})]})}function ND(e){return[{key:"t",infix:`${e}t-`,labelKey:"layout_editor.control.spacing.top"},{key:"r",infix:`${e}r-`,labelKey:"layout_editor.control.spacing.right"},{key:"b",infix:`${e}b-`,labelKey:"layout_editor.control.spacing.bottom"},{key:"l",infix:`${e}l-`,labelKey:"layout_editor.control.spacing.left"}]}function zD(e,t,n,o){const r={all:-1,sides:{}};if(typeof e!="string"||e.trim()==="")return r;const s=e.split(/\s+/).filter(a=>a.length>0);for(const a of s){const l=n.find(d=>a.startsWith(d.infix));if(l){r.sides[l.key]=o.indexOf(a.slice(l.infix.length));continue}if(a.startsWith(`${t}x-`)){const d=o.indexOf(a.slice(`${t}x-`.length));r.sides.l=d,r.sides.r=d;continue}if(a.startsWith(`${t}y-`)){const d=o.indexOf(a.slice(`${t}y-`.length));r.sides.t=d,r.sides.b=d;continue}a.startsWith(`${t}-`)&&(r.all=o.indexOf(a.slice(`${t}-`.length)))}return r}function MD({control:e,value:t,onChange:n,t:o}){const r=typeof e.spacingPrefix=="string"?e.spacingPrefix:"p",s=(Array.isArray(e.scale)?e.scale:[]).map(b=>String(b)),a=ND(r),l=zD(t,r,a,s),d=Object.values(l.sides).some(b=>b>=0),c=l.all>=0||d,[p,f]=u.useState(d);u.useEffect(()=>{d&&f(!0)},[d]);const y=Math.min(4,Math.max(0,s.length-1)),g=b=>{const x=[];for(const k of a){const w=b[k.key];w!==void 0&&w>=0&&x.push(`${k.infix}${s[w]}`)}n(x.length>0?x.join(" "):void 0)},m=b=>{n(b>=0?`${r}-${s[Math.max(0,Math.min(b,s.length-1))]}`:void 0)},h=()=>({...l.sides}),v=(b,x,k,w,_)=>{const C=k>=0;return i.jsxs("div",{style:BD,"data-testid":b,children:[i.jsxs("label",{style:{...Ra,minWidth:64},children:[i.jsx("input",{type:"checkbox","data-testid":`${b}-enabled`,checked:C,onChange:E=>_(E.target.checked)}),i.jsx("span",{style:{fontSize:12,color:"#0f172a"},children:o(x)})]}),i.jsx("input",{type:"range","data-testid":`${b}-range`,min:0,max:Math.max(0,s.length-1),step:1,disabled:!C||s.length===0,value:k<0?0:k,onChange:E=>w(Number(E.target.value)),style:{flex:1}}),i.jsx("span",{"data-testid":`${b}-value`,style:{fontSize:12,color:C?"#0f172a":"#94a3b8",minWidth:28,textAlign:"right"},children:C?s[k]:"–"})]},b)};return i.jsxs("div",{className:"g7le-widget g7le-widget--spacing","data-testid":"g7le-widget-spacing",style:LD,children:[i.jsxs("div",{style:PD,children:[i.jsxs("div",{style:OD,"data-testid":"g7le-spacing-mode",children:[i.jsx("button",{type:"button","data-testid":"g7le-spacing-mode-all","data-active":p?"false":"true",onClick:()=>{f(!1),d&&n(void 0)},style:{...Tg,background:p?"#fff":"#2563eb",color:p?"#0f172a":"#fff",borderColor:p?"#cbd5e1":"#2563eb"},children:o("layout_editor.control.spacing.mode_all")}),i.jsx("button",{type:"button","data-testid":"g7le-spacing-mode-sides","data-active":p?"true":"false",onClick:()=>{if(f(!0),l.all>=0){const b={};for(const x of a)b[x.key]=l.all;g(b)}},style:{...Tg,background:p?"#2563eb":"#fff",color:p?"#fff":"#0f172a",borderColor:p?"#2563eb":"#cbd5e1"},children:o("layout_editor.control.spacing.mode_sides")})]}),i.jsx("span",{"data-testid":"g7le-spacing-value",style:{fontSize:11,color:"#64748b",minWidth:80,textAlign:"right"},children:c?String(t):o("layout_editor.control.default")})]}),p?i.jsx("div",{"data-testid":"g7le-spacing-sides",style:{display:"flex",flexDirection:"column",gap:4},children:a.map(b=>v(`g7le-spacing-side-${b.key}`,b.labelKey,l.sides[b.key]??-1,x=>{const k=h();k[b.key]=x,g(k)},x=>{const k=h();x?k[b.key]=l.sides[b.key]>=0?l.sides[b.key]:y:delete k[b.key],g(k)}))}):i.jsxs("div",{style:Dg,children:[i.jsxs("label",{style:{...Ra,minWidth:64},children:[i.jsx("input",{type:"checkbox","data-testid":"g7le-spacing-all-enabled",checked:l.all>=0,onChange:b=>m(b.target.checked?y:-1)}),i.jsx("span",{style:{fontSize:12,color:"#64748b"},children:o("layout_editor.control.spacing.all")})]}),i.jsx("input",{type:"range","data-testid":"g7le-spacing-all-range",min:0,max:Math.max(0,s.length-1),step:1,disabled:l.all<0||s.length===0,value:l.all<0?0:l.all,onChange:b=>m(Number(b.target.value)),style:{flex:1}}),i.jsx("span",{"data-testid":"g7le-spacing-all-value",style:{fontSize:12,color:l.all>=0?"#0f172a":"#94a3b8",minWidth:28,textAlign:"right"},children:l.all>=0?s[l.all]:"–"})]})]})}const LD={display:"flex",flexDirection:"column",gap:6,width:"100%"},PD={display:"flex",alignItems:"center",justifyContent:"space-between",gap:8},OD={display:"inline-flex",gap:0,border:"1px solid #cbd5e1",borderRadius:6,overflow:"hidden"},Tg={padding:"3px 10px",fontSize:11,border:"none",borderRight:"1px solid #e2e8f0",cursor:"pointer"},BD={display:"flex",alignItems:"center",gap:8,width:"100%"},WD={display:"inline-flex",gap:0,border:"1px solid #cbd5e1",borderRadius:6,overflow:"hidden"},jD={display:"flex",flexDirection:"column",gap:6,width:"100%"},HD={display:"flex",gap:6,alignItems:"center"},FD={flex:1,padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,minWidth:80},KD={padding:"4px 8px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#64748b",cursor:"pointer",whiteSpace:"nowrap"},VD={display:"flex",flexWrap:"wrap",gap:4},UD={fontSize:11,color:"#94a3b8",fontStyle:"italic",padding:"2px 0"},qD={padding:"3px 9px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:999,background:"#fff",cursor:"pointer"},GD={padding:"4px 10px",fontSize:12,border:"none",borderRight:"1px solid #e2e8f0",cursor:"pointer"},Dg={display:"flex",alignItems:"center",gap:8,width:"100%"},Ra={display:"inline-flex",alignItems:"center",gap:4},YD={padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",minWidth:120},XD={position:"relative",width:38,height:20,borderRadius:10,border:"none",cursor:"pointer",padding:2,display:"inline-flex",alignItems:"center",transition:"background 120ms ease"},JD={display:"block",width:16,height:16,borderRadius:8,background:"#fff",transition:"transform 120ms ease"},ZD=["#0f172a","#334155","#64748b","#94a3b8","#e2e8f0","#ffffff","#dc2626","#ea580c","#d97706","#16a34a","#0891b2","#2563eb","#7c3aed","#db2777"],Ng=/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;function QD(e){return Array.isArray(e.options)?e.options.filter(t=>typeof t.value=="string").map(t=>({value:t.value,swatch:typeof t.swatch=="string"?t.swatch:void 0,label:typeof t.label=="string"?t.label:void 0})):[]}function RD({control:e,value:t,onChange:n,t:o,freeValueDisabled:r}){const s=QD(e),a=s.length>0,l=typeof t=="string"&&s.some(g=>g.value===t)?t:"",d=typeof t=="string"&&Ng.test(t)?t:"",[c,p]=u.useState(d);u.useEffect(()=>{p(d)},[d]);const f=g=>{const m=g.trim();if(m===""){n(void 0);return}const h=m.startsWith("#")?m:`#${m}`;Ng.test(h)&&n(h)},y=ZD;return i.jsxs("div",{className:"g7le-widget g7le-widget--color","data-testid":"g7le-widget-color",style:eN,children:[r?i.jsx("div",{"data-testid":"g7le-color-free-disabled",style:sN,children:o("layout_editor.property_modal.dark_preset_only")}):i.jsxs("div",{style:tN,children:[i.jsxs("label",{style:nN,children:[i.jsx("span",{"data-testid":"g7le-color-swatch",style:{...oN,background:d||"transparent",backgroundImage:d?void 0:"linear-gradient(45deg,#e2e8f0 25%,transparent 25%,transparent 75%,#e2e8f0 75%),linear-gradient(45deg,#e2e8f0 25%,#fff 25%,#fff 75%,#e2e8f0 75%)",backgroundSize:"8px 8px",backgroundPosition:"0 0, 4px 4px"}}),i.jsx("input",{type:"color","data-testid":"g7le-color-native",value:d||"#000000",onChange:g=>n(g.target.value),style:{width:0,height:0,opacity:0,position:"absolute"}})]}),i.jsx("input",{type:"text","data-testid":"g7le-color-hex",value:c,placeholder:o("layout_editor.control.color_hex_placeholder"),onChange:g=>p(g.target.value),onBlur:g=>f(g.target.value),onKeyDown:g=>{g.key==="Enter"&&f(g.target.value)},style:rN}),i.jsx("button",{type:"button","data-testid":"g7le-color-clear",onClick:()=>n(void 0),style:iN,children:o("layout_editor.control.default")})]}),a?i.jsx("div",{style:zg,"data-testid":"g7le-color-token-presets",children:s.map(g=>{const m=l===g.value,h=g.label?g.label.startsWith("$t:")?o(g.label.slice(3)):g.label:g.value;return i.jsx("button",{type:"button","aria-label":h,title:h,"data-testid":`g7le-color-token-${g.value}`,"data-active":m?"true":"false",onClick:()=>n(m?void 0:g.value),style:{...Mg,background:g.swatch||"#e2e8f0",outline:m?"2px solid #2563eb":"none"}},g.value)})}):!r&&i.jsx("div",{style:zg,"data-testid":"g7le-color-presets",children:y.map(g=>i.jsx("button",{type:"button","aria-label":g,"data-testid":`g7le-color-preset-${g}`,onClick:()=>n(g),style:{...Mg,background:g,outline:d.toLowerCase()===g.toLowerCase()?"2px solid #2563eb":"none"}},g))})]})}const eN={display:"flex",flexDirection:"column",gap:8},tN={display:"flex",alignItems:"center",gap:6},nN={position:"relative",display:"inline-flex",cursor:"pointer"},oN={width:24,height:24,borderRadius:4,border:"1px solid #cbd5e1",display:"inline-block"},rN={flex:1,padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,fontFamily:"monospace"},iN={padding:"4px 8px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",cursor:"pointer",color:"#64748b"},zg={display:"grid",gridTemplateColumns:"repeat(7, 1fr)",gap:4},Mg={width:"100%",height:18,borderRadius:4,border:"1px solid #cbd5e1",cursor:"pointer",padding:0},sN={fontSize:11,color:"#94a3b8",fontStyle:"italic",padding:"2px 0"},Qo=[{value:"fill",labelKey:"layout_editor.control.background_image.mode_fill",size:"cover",repeat:"no-repeat"},{value:"fit",labelKey:"layout_editor.control.background_image.mode_fit",size:"contain",repeat:"no-repeat"},{value:"tile",labelKey:"layout_editor.control.background_image.mode_tile",size:"auto",repeat:"repeat"}];function aN(e){return e&&typeof e=="object"&&!Array.isArray(e)?e:{}}function Lg(e){return Qo.find(n=>n.size===e.size&&n.repeat===e.repeat)?.value??"fill"}function lN({value:e,onChange:t,t:n}){const o=aN(e),{state:r}=Je(),s=yo(),[a,l]=u.useState(!1),[d,c]=u.useState(null),[p,f]=u.useState(o.url??""),[y,g]=u.useState([]),m=u.useRef(null),h=r.templateIdentifier,v=r.selectedRoute?.layoutName??"";u.useEffect(()=>{f(o.url??"")},[o.url]);const b=u.useCallback(async()=>{if(!h||!v){g([]);return}const E=await Dl(h,v);E.ok&&g(E.data)},[h,v]);u.useEffect(()=>{b()},[b]);const x=E=>{if(!E){t(void 0);return}const S=Qo.find(T=>T.value===Lg(o))??Qo[0];t({url:E,size:o.size??S.size,repeat:o.repeat??S.repeat,position:o.position??"center"})},k=E=>{const S=Qo.find(T=>T.value===E);!S||!o.url||t({url:o.url,size:S.size,repeat:S.repeat,position:o.position??"center"})},w=async E=>{const S=E.target.files?.[0];if(!S)return;l(!0),c(null);const T=await Nl(h,v,S);T.ok?(x(T.data.url),b()):c(n("layout_editor.control.background_image.upload_failed")),l(!1),m.current&&(m.current.value="")},_=async E=>{(await zl(E.id)).ok&&g(T=>T.filter($=>$.id!==E.id))},C=()=>{const E=s.open({ariaLabel:n("layout_editor.attachment_manager.title"),width:720,maxHeightRatio:.82,content:u.createElement(Ml,{templateIdentifier:h,layoutName:v,t:n,onSelect:S=>{x(S),s.close(E)},onChanged:()=>{b()},onClose:()=>s.close(E)})})};return i.jsxs("div",{className:"g7le-widget g7le-widget--image","data-testid":"g7le-widget-image",style:dN,children:[o.url&&i.jsx("div",{"data-testid":"g7le-image-preview",style:{...cN,backgroundImage:`url(${o.url})`,backgroundSize:o.size??"cover",backgroundRepeat:o.repeat??"no-repeat",backgroundPosition:o.position??"center"}}),i.jsxs("div",{style:uN,children:[i.jsxs("label",{style:pN,children:[a?"…":n("layout_editor.control.background_image.upload"),i.jsx("input",{ref:m,type:"file",accept:"image/*","data-testid":"g7le-image-file",onChange:w,disabled:a,style:{display:"none"}})]}),i.jsx("button",{type:"button","data-testid":"g7le-image-clear",onClick:()=>t(void 0),style:fN,children:n("layout_editor.control.background_image.clear")})]}),i.jsx("input",{type:"text","data-testid":"g7le-image-url",placeholder:"https://… / URL",value:p,onChange:E=>f(E.target.value),onBlur:E=>x(E.target.value.trim()||void 0),style:gN}),i.jsx("div",{style:hN,"data-testid":"g7le-image-modes",children:Qo.map(E=>{const S=Lg(o)===E.value;return i.jsx("button",{type:"button","data-testid":`g7le-image-mode-${E.value}`,"data-active":S?"true":"false",disabled:!o.url,onClick:()=>k(E.value),style:{...yN,background:S&&o.url?"#2563eb":"#fff",color:S&&o.url?"#fff":"#0f172a"},children:n(E.labelKey)},E.value)})}),i.jsxs("div",{style:bN,"data-testid":"g7le-image-gallery",children:[i.jsxs("div",{style:mN,children:[i.jsx("span",{style:{fontSize:11,color:"#64748b"},children:n("layout_editor.attachment_manager.recent")}),i.jsxs("button",{type:"button","data-testid":"g7le-image-manage",onClick:C,style:vN,children:["🖼 ",n("layout_editor.attachment_manager.manage_link")]})]}),y.length>0?i.jsx("div",{style:_N,"data-testid":"g7le-image-thumbs",children:y.slice(0,8).map(E=>i.jsxs("div",{style:xN,"data-testid":`g7le-image-thumb-${E.id}`,children:[i.jsx("button",{type:"button",title:E.original_name,onClick:()=>x(E.url),style:{...wN,backgroundImage:`url(${E.url})`},"data-testid":`g7le-image-thumb-use-${E.id}`}),i.jsx("button",{type:"button",title:n("layout_editor.attachment_manager.delete"),onClick:()=>_(E),style:kN,"data-testid":`g7le-image-thumb-del-${E.id}`,children:"✕"})]},E.id))}):i.jsx("span",{style:{fontSize:11,color:"#94a3b8"},children:n("layout_editor.attachment_manager.empty")})]}),d&&i.jsx("span",{"data-testid":"g7le-image-error",style:{fontSize:11,color:"#dc2626"},children:d})]})}const dN={display:"flex",flexDirection:"column",gap:6},cN={width:"100%",height:80,borderRadius:6,border:"1px solid #cbd5e1"},uN={display:"flex",gap:6},pN={padding:"5px 10px",fontSize:12,border:"1px solid #2563eb",borderRadius:6,background:"#fff",color:"#2563eb",cursor:"pointer",display:"inline-flex",alignItems:"center"},fN={padding:"5px 10px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#64748b",cursor:"pointer"},gN={padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6},hN={display:"inline-flex",border:"1px solid #cbd5e1",borderRadius:6,overflow:"hidden"},yN={padding:"4px 10px",fontSize:12,border:"none",borderRight:"1px solid #e2e8f0",cursor:"pointer"},bN={display:"flex",flexDirection:"column",gap:4,border:"1px solid #e2e8f0",borderRadius:6,padding:6,marginTop:2},mN={display:"flex",alignItems:"center",justifyContent:"space-between"},vN={fontSize:11,color:"#2563eb",background:"transparent",border:"none",cursor:"pointer",padding:0},_N={display:"flex",gap:6,flexWrap:"wrap"},xN={position:"relative",width:44,height:44},wN={width:44,height:44,borderRadius:6,border:"1px solid #cbd5e1",backgroundSize:"cover",backgroundPosition:"center",cursor:"pointer",padding:0},kN={position:"absolute",top:-6,right:-6,width:16,height:16,borderRadius:"50%",border:"none",background:"#dc2626",color:"#fff",fontSize:9,lineHeight:"16px",cursor:"pointer",padding:0};function SN(e){return Array.isArray(e)&&e.every(t=>t!==null&&typeof t=="object"&&!Array.isArray(t))}function CN(e){return typeof e=="string"&&/\{\{.*\}\}/.test(e)}function $N({value:e,onChange:t,t:n,bindingCandidates:o}){if(CN(e))return i.jsx("div",{className:"g7le-widget g7le-widget--options-list","data-testid":"g7le-widget-options-list-bound",style:MN,children:n("layout_editor.list_editor.bound_degraded")});const r=SN(e)?e:[],s=y=>{t(y.length===0?void 0:y)},a=(y,g)=>{const m=r.map((h,v)=>v===y?{...h,...g}:h);s(m)},l=y=>{s(r.filter((g,m)=>m!==y))},d=(y,g)=>{const m=y+g;if(m<0||m>=r.length)return;const h=[...r];[h[y],h[m]]=[h[m],h[y]],s(h)},c=()=>{s([...r,{value:"",label:""}])},p=y=>typeof y.label=="string"?y.label:y.label==null?"":String(y.label),f=y=>y.value===void 0||y.value===null?"":String(y.value);return i.jsxs("div",{className:"g7le-widget g7le-widget--options-list","data-testid":"g7le-widget-options-list",style:EN,children:[r.length===0&&i.jsx("div",{"data-testid":"g7le-options-empty",style:zN,children:n("layout_editor.list_editor.empty")}),r.map((y,g)=>i.jsxs("div",{"data-testid":`g7le-options-row-${g}`,style:IN,children:[i.jsxs("div",{style:Pg,children:[i.jsx("span",{style:Og,children:n("layout_editor.list_editor.option_value")}),i.jsx("input",{type:"text","data-testid":`g7le-options-value-${g}`,value:f(y),placeholder:n("layout_editor.list_editor.option_value"),onChange:m=>a(g,{value:m.target.value}),style:TN})]}),i.jsxs("div",{style:Pg,children:[i.jsx("span",{style:Og,children:n("layout_editor.list_editor.option_label")}),i.jsx("div",{"data-testid":`g7le-options-label-${g}`,style:{flex:1,minWidth:0},children:i.jsx(vt,{value:p(y),onChange:m=>a(g,{label:m??""}),t:n,placeholder:n("layout_editor.list_editor.option_label"),testidPrefix:`g7le-options-label-i18n-${g}`,candidates:o,enableExpressionTree:!0,expressionTreeCollapsible:!0})})]}),i.jsxs("div",{style:AN,children:[i.jsx("button",{type:"button","data-testid":`g7le-options-up-${g}`,title:n("layout_editor.list_editor.move_up"),disabled:g===0,onClick:()=>d(g,-1),style:Bg,children:"↑"}),i.jsx("button",{type:"button","data-testid":`g7le-options-down-${g}`,title:n("layout_editor.list_editor.move_down"),disabled:g===r.length-1,onClick:()=>d(g,1),style:Bg,children:"↓"}),i.jsx("button",{type:"button","data-testid":`g7le-options-remove-${g}`,title:n("layout_editor.list_editor.remove"),onClick:()=>l(g),style:DN,children:"✕"})]})]},g)),i.jsx("button",{type:"button","data-testid":"g7le-options-add",onClick:c,style:NN,children:n("layout_editor.list_editor.add")})]})}const EN={display:"flex",flexDirection:"column",gap:8,width:"100%"},IN={display:"flex",flexDirection:"column",gap:4,width:"100%",minWidth:0,paddingBottom:6,borderBottom:"1px solid #f1f5f9"},Pg={display:"flex",gap:6,alignItems:"center",width:"100%",minWidth:0},Og={flexShrink:0,width:36,fontSize:11,color:"#94a3b8",textAlign:"right"},AN={display:"flex",gap:4,alignItems:"center",justifyContent:"flex-end"},TN={flex:1,minWidth:0,padding:"4px 6px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6},Bg={padding:"2px 6px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},DN={padding:"2px 6px",fontSize:12,border:"1px solid #fecaca",borderRadius:6,background:"#fff",color:"#dc2626",cursor:"pointer"},NN={padding:"4px 8px",fontSize:12,border:"1px dashed #94a3b8",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer",alignSelf:"flex-start"},zN={fontSize:11,color:"#94a3b8",fontStyle:"italic"},MN={fontSize:11,color:"#b45309",fontStyle:"italic",padding:"4px 2px"},Wg=/[^A-Za-z0-9\-_:.]/g;function LN(e){return typeof e=="string"&&Ui(e)}function jg(e){return Ui(e)?In(e).map(t=>t.kind==="binding"?t.raw:t.raw.replace(Wg,"")).join(""):e.replace(Wg,"")}const PN={width:"100%",padding:"6px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:4,fontFamily:"monospace"},ON={marginTop:4,fontSize:11,color:"#94a3b8"},BN={display:"flex",gap:6,alignItems:"center"},WN={flexShrink:0,padding:"5px 8px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:4,background:"#f8fafc",cursor:"pointer",whiteSpace:"nowrap"};function jN({value:e,onChange:t,t:n,bindingCandidates:o}){const r=e==null?"":String(e),s=LN(e),a=!!o&&o.length>0,[l,d]=u.useState(!1),c=p=>{const f=jg(p);t(f===""?void 0:f)};return s||l?i.jsxs("div",{"data-testid":"g7le-core-id-chip",children:[i.jsx(bs,{value:r,onChange:c,t:n,candidates:o,onDone:()=>d(!1),testidPrefix:"g7le-core-id-chip-input"}),i.jsx("div",{style:ON,children:n("layout_editor.core_props.id.binding_hint")})]}):i.jsx("div",{children:i.jsxs("div",{style:BN,children:[i.jsx("input",{type:"text",className:"g7le-widget g7le-widget--core-id","data-testid":"g7le-widget-core-id",value:r,onChange:p=>{const f=jg(p.target.value);t(f===""?void 0:f)},spellCheck:!1,autoComplete:"off",style:PN}),a&&i.jsxs("button",{type:"button","data-testid":"g7le-core-id-add-data",onClick:()=>d(!0),style:WN,title:n("layout_editor.core_props.id.add_data"),children:["🔗 ",n("layout_editor.core_props.id.add_data")]})]})})}const HN=/[^A-Za-z0-9.\-_]/g;function FN(e){return typeof e=="string"&&/\{\{[\s\S]*?\}\}/.test(e)}function KN(e){return e.replace(HN,"")}const Hg={width:"100%",padding:"6px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:4,fontFamily:"monospace",boxSizing:"border-box"},VN={...Hg,background:"#f1f5f9",color:"#64748b",cursor:"not-allowed"},Fg={marginTop:4,fontSize:11,color:"#94a3b8"};function UN({value:e,onChange:t,t:n}){const o=e==null?"":String(e);return FN(e)?i.jsxs("div",{"data-testid":"g7le-core-datakey-bound",children:[i.jsx("input",{type:"text",className:"g7le-widget g7le-widget--core-datakey-bound","data-testid":"g7le-widget-core-datakey",value:o,readOnly:!0,disabled:!0,style:VN}),i.jsx("div",{style:Fg,children:n("layout_editor.core_props.dataKey.bound_degraded")})]}):i.jsxs("div",{children:[i.jsx("input",{type:"text",className:"g7le-widget g7le-widget--core-datakey","data-testid":"g7le-widget-core-datakey",value:o,placeholder:n("layout_editor.core_props.dataKey.placeholder"),onChange:s=>{const a=KN(s.target.value);t(a===""?void 0:a)},spellCheck:!1,autoComplete:"off",style:Hg}),i.jsx("div",{style:Fg,children:n("layout_editor.core_props.dataKey.hint")})]})}let Kg=!1;function qN(){Kg||(Xe("segmented",ED),Xe("slider",ID),Xe("select",AD),Xe("dimension",DD),Xe("spacing",MD),Xe("toggle",TD),Xe("color",RD),Xe("image",lN),Xe("tag-input",ys),Xe("page-picker",Aw),Xe("datasource-picker",Bc),Xe("binding-picker",Bc),Xe("binding-list",Nw),Xe("state-key-picker",Es),Xe("modal-picker",Tw),Xe("i18n-text",Dw),Xe("text",zw),Xe("options-list",$N),Xe("core-id",jN),Xe("core-datakey",UN),Xe("component-target-picker",jT),Kg=!0)}function Ro(e){return Array.isArray(e.children)?e.children:[]}function er(e){if(typeof e.text=="string")return[];let t=null;const n=(o,r)=>{const s=Ro(o);for(let a=0;ad===o?tr(l,r,n):l);return{...e,children:a}}function Vg(e,t){const n=r=>typeof r.props?.[t]=="string";if(n(e))return[];const o=(r,s)=>{const a=Ro(r);for(let l=0;lc===r?Ug(d,s,n,o):d);return{...e,children:l}}function GN(e){return Array.isArray(e.children)?e.children:[]}function YN(e){const t=e?.itemFields;if(!Array.isArray(t))return[{kind:"text"}];const n=[];for(const o of t){if(!o||typeof o!="object")continue;const r=o;r.kind==="text"?n.push({kind:"text",label:typeof r.label=="string"?r.label:void 0}):r.kind==="prop"&&typeof r.prop=="string"&&r.prop.length>0&&n.push({kind:"prop",prop:r.prop,label:typeof r.label=="string"?r.label:void 0})}return n.length>0?n:[{kind:"text"}]}function el(e,t){if(!(typeof e!="string"||e.length===0))return e.startsWith("$t:")?t(e.slice(3)):e}function qg({node:e,params:t,spec:n,manifest:o,t:r,onPatchNode:s,templateIdentifier:a,candidates:l}){const d=typeof t?.childComponent=="string"?t.childComponent:null,c=t?.childTemplate&&typeof t.childTemplate=="object"?t.childTemplate:null,p=YN(t),f=el(typeof t?.childLabel=="string"?t.childLabel:void 0,r)??d??"",y=GN(e),g=u.useCallback(_=>{s({...e,children:_})},[e,s]),m=u.useCallback(()=>{if(c)return JSON.parse(JSON.stringify(c));if(!d)return null;const _=n?.componentPalette?.entries?.[d];if(_?.defaultNode&&typeof _.defaultNode=="object")return JSON.parse(JSON.stringify(_.defaultNode));const C=o?.components??{};let E=null;for(const S of Object.values(C))if(Array.isArray(S)){const T=S.find($=>$.name===d);if(T){E=T;break}}return E?$f(E):{type:"basic",name:d}},[c,d,n,o]),h=u.useCallback(()=>{const _=m();if(_){if(_.text==="")_.text=r("layout_editor.list_editor.new_item");else{const C=er(_);if(C!==null&&C.length>0){const E=_i(_,C);if(E&&E.text===""){const S=tr(_,C,r("layout_editor.list_editor.new_item"));g([...y,S]);return}}}g([...y,_])}},[m,g,y,r]),v=u.useCallback(_=>{g(y.filter((C,E)=>E!==_))},[g,y]),b=u.useCallback((_,C)=>{const E=_+C;if(E<0||E>=y.length)return;const S=[...y];[S[_],S[E]]=[S[E],S[_]],g(S)},[g,y]),x=u.useCallback((_,C)=>{if(typeof C!="string"||C.length===0)return;const E=y[_];if(!E)return;const S=er(E);if(S===null)return;const T=y.map(($,A)=>A===_?tr($,S,C):$);g(T)},[y,g]),k=u.useCallback((_,C,E)=>{if(typeof E!="string"||E.length===0)return;const S=y[_];if(!S)return;const T=Vg(S,C);if(T===null)return;const $=y.map((A,z)=>z===_?Ug(A,T,C,E):A);g($)},[y,g]);if(!d&&!c)return i.jsx("div",{className:"g7le-node-editor g7le-node-editor--children","data-testid":"g7le-children-editor-misconfigured",style:Xg,children:r("layout_editor.list_editor.no_child_component")});const w=p.length>1||p.some(_=>_.label);return i.jsxs("div",{className:"g7le-node-editor g7le-node-editor--children","data-testid":"g7le-children-editor",style:XN,children:[i.jsx("div",{style:JN,children:r("layout_editor.list_editor.items_title")}),y.length===0&&i.jsx("div",{"data-testid":"g7le-children-empty",style:Xg,children:r("layout_editor.list_editor.empty")}),y.map((_,C)=>{const E=[];for(const T of p)if(T.kind==="text"){const $=er(_),A=$!==null?_i(_,$):null;A&&typeof A.text=="string"&&E.push({key:"text",caption:el(T.label,r)??(w?r("layout_editor.list_editor.item_label"):void 0),value:A.text,onChange:z=>x(C,z),testid:`g7le-children-i18n-${C}`})}else if(T.kind==="prop"&&T.prop){const $=Vg(_,T.prop);if($!==null){const z=_i(_,$)?.props?.[T.prop];E.push({key:`prop-${T.prop}`,caption:el(T.label,r)??T.prop,value:typeof z=="string"?z:"",onChange:D=>k(C,T.prop,D),testid:`g7le-children-prop-${T.prop}-${C}`})}}const S=typeof _?.name=="string"?_.name:r("layout_editor.list_editor.item");return i.jsxs("div",{"data-testid":`g7le-children-row-${C}`,style:ZN,children:[E.length>0?E.map(T=>i.jsx("div",{style:Gg,children:i.jsxs("div",{"data-testid":T.key==="text"?`g7le-children-text-${C}`:`g7le-children-field-${T.key}-${C}`,style:QN,children:[T.caption!==void 0&&i.jsx("div",{"data-testid":`g7le-children-caption-${T.key}-${C}`,style:RN,children:T.caption}),i.jsx(vt,{value:T.value,onChange:T.onChange,t:r,placeholder:r("layout_editor.list_editor.item_text"),testidPrefix:T.testid,candidates:l,enableExpressionTree:!0,expressionTreeCollapsible:!0})]})},T.key)):i.jsx("div",{style:Gg,children:i.jsx("span",{"data-testid":`g7le-children-label-${C}`,style:t8,children:S})}),i.jsxs("div",{style:e8,children:[i.jsx("button",{type:"button","data-testid":`g7le-children-up-${C}`,title:r("layout_editor.list_editor.move_up"),disabled:C===0,onClick:()=>b(C,-1),style:Yg,children:"↑"}),i.jsx("button",{type:"button","data-testid":`g7le-children-down-${C}`,title:r("layout_editor.list_editor.move_down"),disabled:C===y.length-1,onClick:()=>b(C,1),style:Yg,children:"↓"}),i.jsx("button",{type:"button","data-testid":`g7le-children-remove-${C}`,title:r("layout_editor.list_editor.remove"),onClick:()=>v(C),style:n8,children:"✕"})]})]},C)}),i.jsx("button",{type:"button","data-testid":"g7le-children-add",onClick:h,style:o8,children:r("layout_editor.list_editor.add_child",{component:f})})]})}const XN={display:"flex",flexDirection:"column",gap:6,width:"100%",marginBottom:12},JN={fontSize:12,fontWeight:700,color:"#0f172a",marginBottom:2},ZN={display:"flex",flexDirection:"column",gap:4,width:"100%",minWidth:0,paddingBottom:6,borderBottom:"1px solid #f1f5f9"},Gg={display:"flex",gap:4,alignItems:"flex-start",width:"100%",minWidth:0},QN={flex:1,minWidth:0},RN={fontSize:11,color:"#64748b",marginBottom:2},e8={display:"flex",gap:4,alignItems:"center",justifyContent:"flex-end"},t8={flex:1,minWidth:0,padding:"4px 6px",fontSize:12,color:"#475569",fontStyle:"italic",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},Yg={padding:"2px 6px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},n8={padding:"2px 6px",fontSize:12,border:"1px solid #fecaca",borderRadius:6,background:"#fff",color:"#dc2626",cursor:"pointer"},o8={padding:"4px 8px",fontSize:12,border:"1px dashed #94a3b8",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer",alignSelf:"flex-start"},Xg={fontSize:11,color:"#94a3b8",fontStyle:"italic"},fo={rowContainer:"Tbody",row:"Tr",cell:"Td",headerCell:"Th",colSpanProp:"colSpan",rowSpanProp:"rowSpan"};function ht(e){return{rowContainer:e?.rowContainer??fo.rowContainer,row:e?.row??fo.row,cell:e?.cell??fo.cell,headerCell:e?.headerCell??fo.headerCell,colSpanProp:e?.colSpanProp??fo.colSpanProp,rowSpanProp:e?.rowSpanProp??fo.rowSpanProp}}function Bn(e){return e?Array.isArray(e.children)?e.children:[]:[]}function Jg(e,t){const n=e.props?.[t],o=typeof n=="number"?n:typeof n=="string"?parseInt(n,10):NaN;return Number.isFinite(o)&&o>=1?Math.floor(o):1}function Be(e,t){const n=ht(t),o=[],r=Bn(e),s=[];r.forEach((f,y)=>{(Bn(f).some(h=>h.name===n.row)||f.name===n.rowContainer||f.name==="Thead"||f.name==="Tfoot")&&s.push({container:f,containerIndex:y,isHeader:f.name==="Thead"})}),s.length===0&&r.some(f=>f.name===n.row)&&s.push({container:e,containerIndex:-1,isHeader:!1});const a=[],l=[];let d=0,c=0;const p=f=>{for(;a.length<=f;)a.push([]);return a[f]};s.forEach((f,y)=>{Bn(f.container).filter(m=>m.name===n.row).forEach((m,h)=>{const v=[],b=p(d);let x=0;Bn(m).filter(w=>w.name===n.cell||w.name===n.headerCell).forEach((w,_)=>{for(;b[x];)x++;const C=Jg(w,n.colSpanProp),E=Jg(w,n.rowSpanProp),S={cell:w,sectionIndex:y,rowIndexInSection:h,cellIndexInRow:_,gridRow:d,gridCol:x,colSpan:C,rowSpan:E,isHeader:w.name===n.headerCell};v.push(S);for(let T=0;T=0?(a.push(s.containerIndex),l=Bn(e)[s.containerIndex]??e):l=e;let d=-1,c=-1,p=null;const f=Bn(l);for(let m=0;m=r.gridRow&&t=r.gridCol&&nr){s=!0;break}if(s)break}s||t.add(r)}const n=Array.from(t).sort((r,s)=>r-s),o=[];for(let r=0;re.rows.find(l=>(l.cells[0]?.gridRow??-1)===a)?.sectionIndex??e.rows[a]?.sectionIndex??0,o=new Set([0,t]);for(let a=1;aa){l=!0;break}if(l)break}l||o.add(a)}const r=Array.from(o).sort((a,l)=>a-l),s=[];for(let a=0;at>=a.start&&t=o.length?{ok:!1,reason:"boundary"}:o[r].sectionIndex!==o[s].sectionIndex?{ok:!1,reason:"section"}:{ok:!0}}function wi(e,t,n){const o=Zg(e),r=o.findIndex(a=>t>=a.start&&t=o.length?{ok:!1,reason:"boundary"}:{ok:!0}}function Ge(e){return e?Array.isArray(e.children)?e.children:[]:[]}function mn(e,t,n){const o={...e.props??{}};n<=1?delete o[t]:o[t]=n;const r={...e,props:o};return Object.keys(o).length===0&&delete r.props,r}function ki(e,t,n=!1){const o={type:"basic",name:n?e.headerCell:e.cell},r=t?.props?.className;return typeof r=="string"&&r&&(o.props={className:r}),o.text="",o}function nr(e,t){return e.name===t.cell||e.name===t.headerCell}function nt(e,t,n,o){const r=t.sections[n];if(!r)return e;if(r.containerIndex===-1)return o(e);const s=Ge(e).map((a,l)=>l===r.containerIndex?o(a):a);return{...e,children:s}}function yt(e,t,n,o){let r=-1;const s=Ge(e).map(a=>a.name===t&&(r++,r===n)?o(a):a);return{...e,children:s}}function Si(e,t,n){let o=-1;const r=Ge(e);for(let s=0;s0?nt(e,r,r.sections.length-1,x=>({...x,children:[...Ge(x),b]})):{...e,children:[...Ge(e),b]}}const s=Math.max(-1,Math.min(n,r.rows.length-1)),a=s<0?r.rows[0]:r.rows[s],l=a.cells[0]?.gridRow??s,d=s<0?0:l+1,c=a.sectionIndex,p=s<0?0:a.rowIndexInSection+1,f=[],y=[];let g=0;for(;g0?Ue(r,d-1,g):null;b&&b.gridRow+b.rowSpan>d&&b.rowSpan>1?(y.push(b),g+=b.colSpan):(f.push(ki(o)),g+=1)}let m=e;for(const b of y)m=nt(m,r,b.sectionIndex,x=>yt(x,o.row,b.rowIndexInSection,k=>({...k,children:Ge(k).map((w,_)=>_===b.cellIndexInRow?mn(w,o.rowSpanProp,b.rowSpan+1):w)})));const h={type:"basic",name:o.row,children:f},v=Be(m,t);return m=nt(m,v,c,b=>{const x=Ge(b);let k=0,w=x.length;for(let _=0;_=r.rows.length||r.rows.length<=1)return e;const s=r.rows[n],a=s.cells[0]?.gridRow??n;let l=e;const d=new Set;for(let p=0;pyt(y,o.row,f.rowIndexInSection,g=>({...g,children:Ge(g).map((m,h)=>h===f.cellIndexInRow?mn(m,o.rowSpanProp,f.rowSpan-1):m)})))),p+=f?f.colSpan:1}const c=Be(l,t);return l=nt(l,c,s.sectionIndex,p=>{let f=-1;const y=Ge(p).filter(g=>g.name===o.row?(f++,f!==s.rowIndexInSection):!0);return{...p,children:y}}),l}function eh(e,t,n,o){const r=ht(t),s=Be(e,t);if(!xi(s,n,o).ok)return e;const a=Qg(s),l=a.findIndex(h=>n>=h.start&&ns.rows.filter(v=>{const b=v.cells[0]?.gridRow??-1;return v.sectionIndex===f&&b>=h.start&&bv.rowIndexInSection),g=y(c),m=y(p);return g.length===0||m.length===0?e:nt(e,s,f,h=>{const v=[...Ge(h)],b=[];v.forEach((M,B)=>{M.name===r.row&&b.push(B)});const x=g.map(M=>b[M]).filter(M=>M!==void 0),k=m.map(M=>b[M]).filter(M=>M!==void 0);if(x.length===0||k.length===0)return h;const w=x.map(M=>v[M]),_=k.map(M=>v[M]),C=Math.min(...x)n>=m.start&&nw.gridCol>=f.start&&w.gridColw.gridCol>=y.start&&w.gridColw.cellIndexInRow),x=v.map(w=>w.cellIndexInRow),k=Be(g,t);g=nt(g,k,m.sectionIndex,w=>yt(w,r.row,m.rowIndexInSection,_=>{const C=[...Ge(_)],E=b.map(M=>Si(_,r,M)).filter(M=>M>=0),S=x.map(M=>Si(_,r,M)).filter(M=>M>=0);if(E.length===0||S.length===0)return _;const T=E.map(M=>C[M]),$=S.map(M=>C[M]),A=Math.min(...E,...S),z=Math.max(...E,...S),D=[...$,...T],O=[...C.slice(0,A),...D,...C.slice(z+1)];return{..._,children:O}}))}return g}function nh(e,t,n){const o=ht(t),r=Be(e,t);if(r.rows.length===0||r.colCount===0)return nl(e,t,-1);const s=Math.max(0,Math.min(n+1,r.colCount));let a=e;const l=new Set;for(const d of r.rows){const c=d.cells[0]?.gridRow??0,p=s>0?Ue(r,c,s-1):null,f=s1){if(!l.has(p.cell)){l.add(p.cell);const h=Be(a,t);a=nt(a,h,p.sectionIndex,v=>yt(v,o.row,p.rowIndexInSection,b=>({...b,children:Ge(b).map((x,k)=>k===p.cellIndexInRow?mn(x,o.colSpanProp,p.colSpan+1):x)})))}continue}if(p&&p.gridRow0&&d.cells.every(h=>h.isHeader),m=Be(a,t);a=nt(a,m,d.sectionIndex,h=>yt(h,o.row,d.rowIndexInSection,v=>{const b=[...Ge(v)];let x;if(y<0)x=b.findIndex(k=>nr(k,o)),x<0&&(x=b.length);else{const k=Si(v,o,y);x=k<0?b.length:k+1}return b.splice(x,0,ki(o,void 0,g)),{...v,children:b}}))}return a}function oh(e,t,n){const o=ht(t),r=Be(e,t);if(n<0||n>=r.colCount||r.colCount<=1)return e;let s=e;const a=new Set;for(const l of r.rows){const d=l.cells[0]?.gridRow??0,c=Ue(r,d,n);if(!(!c||a.has(c.cell)||c.gridRow1){const p=Be(s,t);s=nt(s,p,c.sectionIndex,f=>yt(f,o.row,c.rowIndexInSection,y=>({...y,children:Ge(y).map((g,m)=>m===c.cellIndexInRow?mn(g,o.colSpanProp,c.colSpan-1):g)})))}else{const p=Be(s,t);s=nt(s,p,c.sectionIndex,f=>yt(f,o.row,c.rowIndexInSection,y=>{let g=-1;const m=Ge(y).filter(h=>nr(h,o)?(g++,g!==c.cellIndexInRow):!0);return{...y,children:m}}))}}return s}function ol(e,t,n,o,r,s){const a=ht(t),l=Be(e,t),d=Math.min(n,r),c=Math.max(n,r),p=Math.min(o,s),f=Math.max(o,s);if(d===c&&p===f)return{table:e,ok:!1,reason:"single"};if(!l.valid)return{table:e,ok:!1,reason:"invalid-grid"};const y=new Set;for(let b=d;b<=c;b++)for(let x=p;x<=f;x++){const k=Ue(l,b,x);if(!k)return{table:e,ok:!1,reason:"hole"};if(k.gridRowc||k.gridCol+k.colSpan-1>f)return{table:e,ok:!1,reason:"not-rectangular"};y.add(k)}const g=Ue(l,d,p),m=Array.from(y).filter(b=>b!==g);let h=nt(e,l,g.sectionIndex,b=>yt(b,a.row,g.rowIndexInSection,x=>({...x,children:Ge(x).map((k,w)=>{if(w!==g.cellIndexInRow)return k;let _=mn(k,a.colSpanProp,f-p+1);return _=mn(_,a.rowSpanProp,c-d+1),_})})));const v=new Map;for(const b of m){const x=`${b.sectionIndex}:${b.rowIndexInSection}`,k=v.get(x)??[];k.push(b),v.set(x,k)}for(const[b,x]of v){const k=parseInt(b.split(":")[0],10),w=x[0].rowIndexInSection,_=new Set(x.map(E=>E.cellIndexInRow)),C=Be(h,t);h=nt(h,C,k,E=>yt(E,a.row,w,S=>{let T=-1;const $=Ge(S).filter(A=>nr(A,a)?(T++,!_.has(T)):!0);return{...S,children:$}}))}return{table:h,ok:!0}}function rh(e,t,n,o){const r=ht(t),s=Be(e,t),a=Ue(s,n,o);if(!a||a.colSpan<=1&&a.rowSpan<=1)return e;const l=a.gridRow,d=a.gridCol,c=a.gridRow+a.rowSpan-1,p=a.gridCol+a.colSpan-1,f=a.isHeader,y=typeof a.cell.props?.className=="string"?a.cell.props.className:void 0;let g=nt(e,s,a.sectionIndex,m=>yt(m,r.row,a.rowIndexInSection,h=>({...h,children:Ge(h).map((v,b)=>{if(b!==a.cellIndexInRow)return v;let x=mn(v,r.colSpanProp,1);return x=mn(x,r.rowSpanProp,1),x})})));for(let m=l;m<=c;m++)for(let h=d;h<=p;h++){if(m===l&&h===d)continue;const v=Be(g,t),b=v.rows.find(w=>w.cells.some(_=>_.gridRow===m))??v.rows[m];if(!b)continue;let x=-1;for(const w of b.cells)w.gridRow===m&&w.gridColyt(w,r.row,b.rowIndexInSection,_=>{const C=[...Ge(_)];let E;if(x<0)E=C.findIndex(S=>nr(S,r)),E<0&&(E=C.length);else{const S=Si(_,r,x);E=S<0?C.length:S+1}return C.splice(E,0,ki(r,k,f)),{..._,children:C}}))}return g}function Ci(e,t,n,o,r){const s=ht(t),a=Be(e,t),l=Ue(a,n,o);return l?nt(e,a,l.sectionIndex,d=>yt(d,s.row,l.rowIndexInSection,c=>({...c,children:Ge(c).map((p,f)=>{if(f!==l.cellIndexInRow)return p;const y={...p.props??{}};r.trim()===""?delete y.className:y.className=r;const g={...p,props:y};return Object.keys(y).length===0&&delete g.props,g})}))):e}function Jt(e,t,n,o,r,s){const a=ht(t),l=Be(e,t),d=Ue(l,n,o);return d?nt(e,l,d.sectionIndex,c=>yt(c,a.row,d.rowIndexInSection,p=>({...p,children:Ge(p).map((f,y)=>{if(y!==d.cellIndexInRow)return f;const g={...f.props??{}},m={...g.style??{}};!s||s.trim()===""?delete m[r]:m[r]=s,Object.keys(m).length===0?delete g.style:g.style=m;const h={...f,props:g};return Object.keys(g).length===0&&delete h.props,h})}))):e}function go(e,t,n,o,r,s,a){const l=ht(t),d=Be(e,t),c=Ue(d,n,o);if(!c)return e;const p=a==="dark";return nt(e,d,c.sectionIndex,f=>yt(f,l.row,c.rowIndexInSection,y=>({...y,children:Ge(y).map((g,m)=>{if(m!==c.cellIndexInRow)return g;const h={...g.props??{}},v=typeof h.className=="string"?h.className:"",b=uC(v,s,r,p);b===""?delete h.className:h.className=b;const x={...g,props:h};return Object.keys(h).length===0&&delete x.props,x})})))}function ih(e,t,n,o,r){const s=ht(t),a=Be(e,t),l=Ue(a,n,o);return l?nt(e,a,l.sectionIndex,d=>yt(d,s.row,l.rowIndexInSection,c=>({...c,children:Ge(c).map((p,f)=>f===l.cellIndexInRow?{...p,text:r}:p)}))):e}function sh(e,t,n,o,r){const s=ht(t),a=Be(e,t),l=Ue(a,n,o);return l?nt(e,a,l.sectionIndex,d=>yt(d,s.row,l.rowIndexInSection,c=>({...c,children:Ge(c).map((p,f)=>f===l.cellIndexInRow?r:p)}))):e}const ah=/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;function r8(e){return e.split(/\s+/).filter(Boolean)}function lh(e,t){for(const n of t)if(e===n||/^-?\d+$/.test(e.slice(n.length+1))&&e.startsWith(n+"-"))return!0;return!1}function dh({className:e,catalog:t,t:n,onChange:o,colorScheme:r="light",onPresetToken:s,colorStyle:a,onColorStyle:l,disabled:d}){const c=t?.sides??[],p=t?.widths??[],f=t?.colors??[],y=r==="dark",g=u.useMemo(()=>c.map($=>$.prefix),[c]),m=u.useMemo(()=>new Set(f.map($=>$.token)),[f]),h=u.useMemo(()=>r8(e),[e]),v=u.useMemo(()=>Vr(e,y),[e,y]),b=u.useMemo(()=>v.find($=>m.has($))??null,[v,m]),x=u.useMemo(()=>{const $=new Set;for(const A of c)h.some(D=>D===A.prefix||D.startsWith(A.prefix+"-")&&/^-?\d+$/.test(D.slice(A.prefix.length+1)))&&$.add(A.value);return $},[h,c]),k=u.useMemo(()=>{const $=h.find(z=>lh(z,g));if(!$)return"none";const A=p.filter(z=>z.value!=="none");for(const z of c){if($===z.prefix)return A.find(D=>(D.suffix??"")==="")?.value??"thin";if($.startsWith(z.prefix+"-")){const D=$.slice(z.prefix.length),O=A.find(M=>(M.suffix??"")===D);if(O)return O.value}}return"thin"},[h,g,c,p]);if(!t||c.length===0||p.length===0)return null;function w($){return $.filter(A=>!lh(A,g))}function _($,A){const z=w(h),D=p.find(B=>B.value===A);if(!D||A==="none"||$.size===0)return z.join(" ").trim();const O=D.suffix??"",M=[...z];for(const B of c)$.has(B.value)&&M.push(O?`${B.prefix}${O}`:B.prefix);return M.join(" ").trim()}const C=$=>{const A=new Set(x);A.has($)?A.delete($):($==="all"?A.clear():A.delete("all"),A.add($));const z=k==="none"?p.find(D=>D.value!=="none")?.value??"thin":k;o(_(A,z))},E=$=>{const A=x.size===0?new Set(["all"]):x;o(_(A,$))},S=$=>{s?.($)},T=($,A)=>$&&$.startsWith("$t:")?n($.slice(3)):$||A;return i.jsxs("div",{className:"g7le-cell-border","data-testid":"g7le-cell-border",style:i8,children:[i.jsx("div",{style:s8,children:i.jsx("span",{style:a8,children:n("layout_editor.table_editor.cell_border")})}),i.jsx("div",{style:rl,"data-testid":"g7le-cell-border-widths",children:p.map($=>i.jsx("button",{type:"button",disabled:d,"data-testid":`g7le-cell-border-width-${$.value}`,"aria-pressed":k===$.value,onClick:()=>E($.value),style:k===$.value?ch:il,children:T($.label,$.value)},$.value))}),k!=="none"&&i.jsx("div",{style:rl,"data-testid":"g7le-cell-border-sides",children:c.map($=>i.jsx("button",{type:"button",disabled:d,"data-testid":`g7le-cell-border-side-${$.value}`,"aria-pressed":x.has($.value),onClick:()=>C($.value),style:x.has($.value)?ch:il,children:T($.label,$.value)},$.value))}),k!=="none"&&(f.length>0||l)&&i.jsx(i.Fragment,{children:i.jsxs("div",{style:rl,"data-testid":"g7le-cell-border-colors",children:[f.map($=>{const A=b===$.token;return i.jsx("button",{type:"button",disabled:d,title:T($.label,$.value),"data-testid":`g7le-cell-border-color-${$.value}`,"aria-pressed":A,onClick:()=>S($.token),style:{...uh,background:$.swatch??"#fff",outline:A?"2px solid #2563eb":"1px solid #cbd5e1"}},$.value)}),l&&(()=>{const $=!!a&&f.some(z=>z.swatch&&z.swatch.toLowerCase()===a.toLowerCase()),A=!y&&!!a&&!$;return i.jsx("label",{title:n(y?"layout_editor.table_editor.dark_free_color_hint":"layout_editor.table_inplace.custom_color"),"data-testid":"g7le-cell-border-color-custom",style:{...uh,position:"relative",overflow:"hidden",display:"inline-flex",alignItems:"center",justifyContent:"center",background:A&&ah.test(a)?a:"conic-gradient(red, yellow, lime, aqua, blue, magenta, red)",outline:A?"2px solid #2563eb":"1px solid #cbd5e1",opacity:y?.4:1,cursor:y?"not-allowed":"pointer"},children:i.jsx("input",{type:"color",disabled:d||y,"data-testid":"g7le-cell-border-color-picker",value:a&&ah.test(a)?a:"#000000",onChange:z=>l(z.target.value),style:{position:"absolute",width:0,height:0,opacity:0}})})})()]})})]})}const i8={display:"flex",flexDirection:"column",gap:6},s8={display:"flex",alignItems:"center",justifyContent:"space-between",gap:8},a8={fontSize:12,fontWeight:600,color:"#475569"},rl={display:"flex",gap:4,flexWrap:"wrap",alignItems:"center"},il={padding:"3px 8px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},ch={...il,border:"1px solid #2563eb",background:"#eff6ff",color:"#1d4ed8",fontWeight:600},uh={width:22,height:22,borderRadius:5,padding:0,cursor:"pointer",boxSizing:"border-box"},ph=/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;function fh({colorStyle:e,className:t,catalog:n,t:o,colorScheme:r="light",onPresetToken:s,onCustomColor:a,onClear:l,disabled:d}){const c=n?.colors??[],p=r==="dark",f=e??"",y=Vr(t??"",p),g=c.find(x=>x.token&&y.includes(x.token))?.token??null,m=(x,k)=>x&&x.startsWith("$t:")?o(x.slice(3)):x||k,h=!g&&(p||f.trim()===""),v=!!f&&c.some(x=>x.swatch&&x.swatch.toLowerCase()===f.toLowerCase()),b=!p&&!!f&&!v;return i.jsxs("div",{className:"g7le-cell-fill","data-testid":"g7le-cell-fill",style:l8,children:[i.jsx("div",{style:d8,children:i.jsx("span",{style:c8,children:o("layout_editor.table_editor.cell_fill")})}),i.jsxs("div",{style:u8,"data-testid":"g7le-cell-fill-colors",children:[i.jsx("button",{type:"button",disabled:d,title:o("layout_editor.table_editor.cell_fill_none"),"data-testid":"g7le-cell-fill-none","aria-pressed":h,onClick:()=>l?.(),style:{...sl,background:"repeating-conic-gradient(#e2e8f0 0% 25%, #ffffff 0% 50%) 50% / 10px 10px",outline:h?"2px solid #2563eb":"1px solid #cbd5e1"}}),c.map(x=>{const k=!!x.token&&g===x.token,w=!p&&!!x.swatch&&f.toLowerCase()===(x.swatch??"").toLowerCase(),_=k||w,C=d||p&&!x.token;return i.jsx("button",{type:"button",disabled:C,title:m(x.label,x.value),"data-testid":`g7le-cell-fill-color-${x.value}`,"aria-pressed":_,onClick:()=>{x.token&&s?s(x.token):!p&&x.swatch&&a&&a(x.swatch)},style:{...sl,background:x.swatch??"#fff",outline:_?"2px solid #2563eb":"1px solid #cbd5e1",opacity:C?.4:1,cursor:C?"not-allowed":"pointer"}},x.value)}),a&&i.jsx("label",{title:o(p?"layout_editor.table_editor.dark_free_color_hint":"layout_editor.table_editor.cell_fill_custom"),"data-testid":"g7le-cell-fill-custom",style:{...sl,position:"relative",overflow:"hidden",display:"inline-flex",alignItems:"center",justifyContent:"center",background:b&&ph.test(f)?f:"conic-gradient(red, yellow, lime, aqua, blue, magenta, red)",outline:b?"2px solid #2563eb":"1px solid #cbd5e1",opacity:p?.4:1,cursor:p?"not-allowed":"pointer"},children:i.jsx("input",{type:"color",disabled:d||p,"data-testid":"g7le-cell-fill-color-picker",value:f&&ph.test(f)?f:"#ffffff",onChange:x=>a(x.target.value),style:{position:"absolute",width:0,height:0,opacity:0}})})]})]})}const l8={display:"flex",flexDirection:"column",gap:6},d8={display:"flex",alignItems:"center",justifyContent:"space-between",gap:8},c8={fontSize:12,fontWeight:600,color:"#475569"},u8={display:"flex",gap:4,flexWrap:"wrap",alignItems:"center"},sl={width:22,height:22,borderRadius:5,padding:0,cursor:"pointer",boxSizing:"border-box"};function p8(e){const t=/^(\d+)px$/.exec(e.trim());return t?parseInt(t[1],10):null}function gh({paddingStyle:e,catalog:t,t:n,onChange:o,disabled:r}){const s=t?.steps??[],a=e??"",l=p8(a),d=(h,v)=>h&&h.startsWith("$t:")?n(h.slice(3)):h||v,c=s.some(h=>(h.px??0)===(l??-1)),p=l!==null&&!c?String(l):"",[f,y]=u.useState(p);u.useEffect(()=>{y(p)},[e]);const g=h=>{o(h&&h>0?`${h}px`:""),y("")},m=h=>{const v=parseInt(h,10);Number.isFinite(v)&&v>=0&&o(v>0?`${v}px`:"")};return i.jsxs("div",{className:"g7le-cell-padding","data-testid":"g7le-cell-padding",style:f8,children:[i.jsx("div",{style:g8,children:n("layout_editor.table_editor.cell_padding")}),i.jsxs("div",{style:h8,"data-testid":"g7le-cell-padding-steps",children:[s.map(h=>{const v=(h.px??0)===(l??-1)||!h.px&&a.trim()==="";return i.jsx("button",{type:"button",disabled:r,"data-testid":`g7le-cell-padding-step-${h.value}`,"aria-pressed":v,onClick:()=>g(h.px),style:v?y8:hh,children:d(h.label,h.value)},h.value)}),i.jsxs("span",{style:{display:"inline-flex",alignItems:"center",gap:3},children:[i.jsx("input",{type:"number",min:0,disabled:r,"data-testid":"g7le-cell-padding-free",value:f,placeholder:n("layout_editor.table_editor.cell_padding_custom"),onChange:h=>y(h.target.value),onBlur:()=>f.trim()!==""&&m(f),onKeyDown:h=>{h.key==="Enter"&&f.trim()!==""&&m(f)},style:b8}),i.jsx("span",{style:{fontSize:11,color:"#94a3b8"},children:"px"})]})]})]})}const f8={display:"flex",flexDirection:"column",gap:6},g8={fontSize:12,fontWeight:600,color:"#475569"},h8={display:"flex",gap:4,flexWrap:"wrap",alignItems:"center"},hh={padding:"3px 8px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},y8={...hh,border:"1px solid #2563eb",background:"#eff6ff",color:"#1d4ed8",fontWeight:600},b8={width:48,padding:"3px 5px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,boxSizing:"border-box"};function yh({colorScheme:e,onChange:t,t:n,disabled:o}){const r=e==="dark";return i.jsxs("div",{className:"g7le-color-scheme-tabs","data-testid":"g7le-color-scheme-tabs",style:m8,children:[i.jsxs("div",{style:v8,children:[i.jsx("span",{style:_8,children:n("layout_editor.table_editor.cell_color")}),i.jsxs("div",{style:x8,children:[i.jsx("button",{type:"button",disabled:o,"data-testid":"g7le-color-scheme-light","aria-pressed":!r,onClick:()=>t("light"),style:r?al:bh,children:n("layout_editor.property_modal.scope.scheme_light")}),i.jsx("button",{type:"button",disabled:o,"data-testid":"g7le-color-scheme-dark","aria-pressed":r,onClick:()=>t("dark"),style:r?bh:al,children:n("layout_editor.property_modal.scope.scheme_dark")})]})]}),r&&i.jsx("div",{style:w8,"data-testid":"g7le-color-scheme-dark-hint",children:n("layout_editor.table_editor.dark_free_color_hint")})]})}const m8={display:"flex",flexDirection:"column",gap:3},v8={display:"flex",alignItems:"center",justifyContent:"space-between",gap:8},_8={fontSize:12,fontWeight:700,color:"#0f172a"},x8={display:"flex",gap:2},al={padding:"2px 9px",fontSize:11,border:"none",borderBottom:"2px solid transparent",background:"transparent",color:"#94a3b8",cursor:"pointer"},bh={...al,color:"#1d4ed8",borderBottom:"2px solid #2563eb",fontWeight:600},w8={fontSize:10,color:"#94a3b8",fontStyle:"italic"},ll="dark:";function mh(e){return typeof e=="string"?e.split(/\s+/).filter(Boolean):[]}function vh(e,t){return t==="dark"?e.filter(n=>n.startsWith(ll)).map(n=>n.slice(ll.length)):e.filter(n=>!n.startsWith(ll))}const k8={none:0,thin:1,medium:2,thick:4},_h={top:{w:"borderTopWidth",s:"borderTopStyle",c:"borderTopColor"},bottom:{w:"borderBottomWidth",s:"borderBottomStyle",c:"borderBottomColor"},left:{w:"borderLeftWidth",s:"borderLeftStyle",c:"borderLeftColor"},right:{w:"borderRightWidth",s:"borderRightStyle",c:"borderRightColor"}},xh=["top","bottom","left","right"];function S8(e,t,n="light"){if(!e||!t)return{};const o=t.sides??[],r=t.widths??[],s=t.colors??[],a=mh(e.props?.className);if(a.length===0&&!e.props?.style)return{};const l=[...r].filter(b=>b.value!=="none").sort((b,x)=>(x.suffix??"").length-(b.suffix??"").length),d={};for(const b of o)for(const x of l){const k=x.suffix?`${b.prefix}${x.suffix}`:b.prefix;if(a.includes(k)){d[b.value]=x.value;break}}const c=e.props?.style??{},p=vh(a,n),f=s.find(b=>p.includes(b.token))?.swatch??"",g=(n==="light"&&typeof c.borderColor=="string"?c.borderColor:"")||f||"#94a3b8",m=b=>{const x=_h[b]?.c,k=x?c[x]:void 0;return typeof k=="string"&&k?k:g},h={},v=(b,x)=>{const k=_h[b];if(!k)return;const w=k8[x]??1;h[k.w]=`${w}px`,h[k.s]=w>0?"solid":"none",w>0&&(h[k.c]=m(b))};if(d.all)for(const b of xh)v(b,d.all);for(const b of xh)d[b]&&v(b,d[b]);return h}function C8(e,t,n="light"){if(!e?.props)return{};const o=e.props.style??{},r={},s=mh(e.props.className),a=t?.colors?.find(c=>c.token&&vh(s,n).includes(c.token))?.swatch??"",d=(n==="light"&&typeof o.backgroundColor=="string"?o.backgroundColor:"")||a;return d&&(r.backgroundColor=d),typeof o.padding=="string"&&o.padding&&(r.padding=o.padding),r}const $8=/^\s*\$t:(custom\.[a-zA-Z0-9._-]+)\s*$/,E8=/^\s*\$t:([a-zA-Z0-9._-]+)\s*$/,I8=/\{\{.*?\}\}/;function wh(e,t,n){const o=e?.text;if(typeof o!="string")return{display:"",editable:!1,isKey:!1};const r=mt(o);if(r&&r.key.startsWith("custom.")){let a="";try{const l=Pt.getInstance().translate(r.key,{templateId:t,locale:n});a=l&&l!==r.key?l:""}catch{a=""}return{display:a,editable:!0,isKey:!0}}if(I8.test(o))return{display:o,editable:!1,isKey:!1};const s=E8.exec(o);if(s){const a=s[1];let l="";try{const d=Pt.getInstance().translate(a,{templateId:t,locale:n});l=d&&d!==a?d:""}catch{l=""}return{display:l,editable:!0,isKey:!0}}return{display:o,editable:!0,isKey:!1}}function dl(e){const t=er(e);return t===null?{node:null,path:null}:{node:_i(e,t),path:t}}function A8(e){if(!e||typeof e.text!="string")return null;const t=$8.exec(e.text);if(t)return t[1];const n=mt(e.text);return n&&n.key.startsWith("custom.")?n.key:null}function T8({node:e,params:t,t:n,onPatchNode:o,templateIdentifier:r,candidates:s}){const{state:a}=Je(),l=a.selectedRoute?.layoutName??null,d=a.locale,c=r??a.templateIdentifier,p=u.useMemo(()=>t&&typeof t=="object"?t:null,[t]);u.useMemo(()=>ht(p),[p]);const f=u.useMemo(()=>Be(e,p),[e,p]),y=u.useMemo(()=>{const q=p?.cellBorder;return q&&typeof q=="object"?q:null},[p]),g=u.useMemo(()=>{const q=p?.cellBackground;return q&&typeof q=="object"?q:null},[p]),m=u.useMemo(()=>{const q=p?.cellPadding;return q&&typeof q=="object"?q:null},[p]),[h,v]=u.useState(null),[b,x]=u.useState(null),[k,w]=u.useState({}),[_,C]=u.useState(!1),[E,S]=u.useState(null),[T,$]=u.useState(null),[A,z]=u.useState("light"),D=u.useCallback(q=>o(q),[o]),O=u.useMemo(()=>h?Ue(f,h.r,h.c):null,[f,h]),M=T!==null?T:h?.c??null,B=E!==null?E:h?.r??null,N=u.useMemo(()=>{const q=(y?.sides??[]).find($e=>$e.value==="all")??(y?.sides??[])[0],ae=(y?.widths??[]).find($e=>$e.value!=="none");if(!q||!ae)return[];const pe=ae.suffix??"";return[pe?`${q.prefix}${pe}`:q.prefix]},[y]),H=u.useCallback((q,ae,pe)=>{if(N.length===0)return q;const $e=(y?.sides??[]).map(J=>J.prefix),be=Ue(Be(q,p),ae,pe),oe=(typeof be?.cell?.props?.className=="string"?be.cell.props.className:"").split(/\s+/).filter(Boolean);return oe.some(J=>$e.some(le=>J===le||J.startsWith(le+"-")&&/^-?\d+$/.test(J.slice(le.length+1))))?q:Ci(q,p,ae,pe,[...oe,...N].join(" ").trim())},[N,y,p]),L=u.useCallback(q=>{if(!h)return;const ae=Math.min(h.r,b?.r??h.r),pe=Math.max(h.r,b?.r??h.r),$e=Math.min(h.c,b?.c??h.c),be=Math.max(h.c,b?.c??h.c),j=new Set,oe=[];for(let J=ae;J<=pe;J++)for(let le=$e;le<=be;le++)j.add(`${J}:${le}`),oe.push({r:J,c:le});let ue=e;for(const J of oe){if(ue=H(ue,J.r,J.c),ue=Jt(ue,p,J.r,J.c,"borderColor",q),J.r>0&&!j.has(`${J.r-1}:${J.c}`)){const le=Ue(Be(ue,p),J.r-1,J.c);le&&(ue=Jt(ue,p,le.gridRow,le.gridCol,"borderBottomColor",q))}if(J.c>0&&!j.has(`${J.r}:${J.c-1}`)){const le=Ue(Be(ue,p),J.r,J.c-1);le&&(ue=Jt(ue,p,le.gridRow,le.gridCol,"borderRightColor",q))}}D(ue)},[h,b,e,p,D,H]),I=u.useCallback(q=>L(q),[L]),P=u.useMemo(()=>(y?.colors??[]).map(q=>q.token).filter(Boolean),[y]),F=u.useCallback(q=>{if(!h||P.length===0)return;const ae=Math.min(h.r,b?.r??h.r),pe=Math.max(h.r,b?.r??h.r),$e=Math.min(h.c,b?.c??h.c),be=Math.max(h.c,b?.c??h.c);let j=e;for(let oe=ae;oe<=pe;oe++)for(let ue=$e;ue<=be;ue++)j=H(j,oe,ue),j=go(j,p,oe,ue,q,P,A);D(j)},[h,b,e,p,D,H,P,A]),G=u.useMemo(()=>{if(A==="dark")return"";const q=O?.cell.props?.style;return typeof q?.borderColor=="string"?q.borderColor:""},[O,A]),V=u.useCallback((q,ae)=>{if(!h)return;const pe=Math.min(h.r,b?.r??h.r),$e=Math.max(h.r,b?.r??h.r),be=Math.min(h.c,b?.c??h.c),j=Math.max(h.c,b?.c??h.c);let oe=e;for(let ue=pe;ue<=$e;ue++)for(let J=be;J<=j;J++)oe=Jt(oe,p,ue,J,q,ae);D(oe)},[h,b,e,p,D]),Y=u.useMemo(()=>(g?.colors??[]).map(q=>q.token).filter(q=>!!q),[g]),Z=u.useCallback(()=>h?{r0:Math.min(h.r,b?.r??h.r),r1:Math.max(h.r,b?.r??h.r),c0:Math.min(h.c,b?.c??h.c),c1:Math.max(h.c,b?.c??h.c)}:null,[h,b]),se=u.useCallback(q=>{const ae=Z();if(!ae||Y.length===0)return;let pe=e;for(let $e=ae.r0;$e<=ae.r1;$e++)for(let be=ae.c0;be<=ae.c1;be++)pe=go(pe,p,$e,be,q,Y,A);D(pe)},[Z,e,p,D,Y,A]),re=u.useCallback(q=>V("backgroundColor",q),[V]),U=u.useCallback(()=>{const q=Z();if(!q)return;let ae=e;for(let pe=q.r0;pe<=q.r1;pe++)for(let $e=q.c0;$e<=q.c1;$e++)ae=go(ae,p,pe,$e,void 0,Y,A),A==="light"&&(ae=Jt(ae,p,pe,$e,"backgroundColor",void 0));D(ae)},[Z,e,p,D,Y,A]),ee=u.useCallback(q=>V("padding",q),[V]),fe=u.useMemo(()=>{if(A==="dark")return"";const q=O?.cell.props?.style;return typeof q?.backgroundColor=="string"?q.backgroundColor:""},[O,A]),K=u.useMemo(()=>typeof O?.cell.props?.className=="string"?O.cell.props.className:"",[O]),ne=u.useMemo(()=>{const q=O?.cell.props?.style;return typeof q?.padding=="string"?q.padding:""},[O]),me=u.useCallback(q=>q==="section"?n("layout_editor.table_editor.move_blocked_section"):"",[n]),Ce=u.useCallback((q,ae)=>xi(f,q,ae),[f]),ze=u.useCallback((q,ae)=>wi(f,q,ae),[f]),Se=u.useCallback(q=>D(nl(e,p,q)),[e,p,D]),qe=u.useCallback(q=>{D(Rg(e,p,q)),v(null),x(null)},[e,p,D]),De=u.useCallback((q,ae)=>D(eh(e,p,q,ae)),[e,p,D]),Pe=u.useCallback(q=>D(nh(e,p,q)),[e,p,D]),Qe=u.useCallback(q=>{D(oh(e,p,q)),v(null),x(null)},[e,p,D]),Me=u.useCallback((q,ae)=>D(th(e,p,q,ae)),[e,p,D]),Re=u.useCallback(()=>{if(!h||!b)return;const q=ol(e,p,h.r,h.c,b.r,b.c);q.ok&&(D(q.table),v({r:Math.min(h.r,b.r),c:Math.min(h.c,b.c)}),x(null))},[e,p,h,b,D]),ve=u.useCallback(()=>{h&&(D(rh(e,p,h.r,h.c)),x(null))},[e,p,h,D]),te=u.useCallback(q=>{if(!h)return;const ae=Math.min(h.r,b?.r??h.r),pe=Math.max(h.r,b?.r??h.r),$e=Math.min(h.c,b?.c??h.c),be=Math.max(h.c,b?.c??h.c);let j=e;for(let oe=ae;oe<=pe;oe++)for(let ue=$e;ue<=be;ue++)j=Ci(j,p,oe,ue,q);D(j)},[e,p,h,b,D]),he=u.useCallback(async(q,ae)=>{const pe=`${q}:${ae}`,$e=k[pe];if($e===void 0)return;const be=Ue(f,q,ae);if(!be)return;const{node:j,path:oe}=dl(be.cell);if(oe===null)return;const ue=wh(j,c,d);if(ue.editable){if($e===ue.display){w(J=>{const le={...J};return delete le[pe],le});return}C(!0);try{const J=A8(j);if(J)await yr(c,J,d,$e),await tn(c,d);else{const le=await qn(c,l,d,$e);if(le.kind==="ok"&&le.resource){const Ie=`$t:${le.resource.translation_key}`;if(oe.length===0)D(ih(e,p,q,ae,Ie));else{const dt=tr(be.cell,oe,Ie);D(sh(e,p,q,ae,dt))}await tn(c,d)}}}finally{C(!1),w(J=>{const le={...J};return delete le[pe],le})}}},[k,f,c,d,l,e,p,D]),Q=u.useMemo(()=>{if(!O)return null;const{node:q,path:ae}=dl(O.cell);return ae===null?null:{value:typeof q?.text=="string"?q.text:"",editable:!0}},[O]),ie=u.useCallback(q=>{if(typeof q!="string"||q.length===0||!h)return;const ae=Ue(f,h.r,h.c);if(!ae)return;const pe=er(ae.cell);if(pe!==null)if(pe.length===0)D(ih(e,p,h.r,h.c,q));else{const $e=tr(ae.cell,pe,q);D(sh(e,p,h.r,h.c,$e))}},[h,f,e,p,D]),_e=u.useCallback((q,ae,pe)=>{pe?x({r:q,c:ae}):(v({r:q,c:ae}),x(null))},[]),ge=u.useMemo(()=>Array.from({length:f.colCount},(q,ae)=>ae),[f.colCount]),Ae=!!(h&&b&&(h.r!==b.r||h.c!==b.c)),Ne=!!(O&&(O.colSpan>1||O.rowSpan>1)),Le=O&&typeof O.cell.props?.className=="string"?O.cell.props.className:"";return i.jsxs("div",{className:"g7le-node-editor g7le-node-editor--table","data-testid":"g7le-table-editor",style:D8,children:[i.jsx("div",{style:N8,children:n("layout_editor.table_editor.title")}),i.jsx("div",{style:{overflowX:"auto"},children:i.jsxs("table",{"data-testid":"g7le-table-editor-grid",style:z8,children:[i.jsx("thead",{children:i.jsxs("tr",{children:[i.jsx("th",{style:M8}),ge.map(q=>{const ae=ze(q,-1),pe=ze(q,1),$e=M===q;return i.jsx("th",{style:L8,"data-testid":`g7le-table-colgutter-${q}`,onMouseEnter:()=>$(q),onMouseLeave:()=>$(be=>be===q?null:be),children:i.jsxs("div",{style:{...O8,visibility:$e?"visible":"hidden"},children:[i.jsx("button",{type:"button",title:ae.ok?n("layout_editor.table_editor.move_col_left"):me(ae.reason)||n("layout_editor.table_editor.move_col_left"),disabled:!ae.ok||_,onClick:()=>Me(q,-1),"data-testid":`g7le-table-col-left-${q}`,style:ae.ok?vn:$i,children:"←"}),i.jsx("button",{type:"button",title:n("layout_editor.table_editor.add_col"),disabled:_,onClick:()=>Pe(q),"data-testid":`g7le-table-col-add-${q}`,style:vn,children:"+"}),i.jsx("button",{type:"button",title:n("layout_editor.table_editor.remove_col"),disabled:f.colCount<=1||_,onClick:()=>Qe(q),"data-testid":`g7le-table-col-remove-${q}`,style:kh,children:"✕"}),i.jsx("button",{type:"button",title:pe.ok?n("layout_editor.table_editor.move_col_right"):me(pe.reason)||n("layout_editor.table_editor.move_col_right"),disabled:!pe.ok||_,onClick:()=>Me(q,1),"data-testid":`g7le-table-col-right-${q}`,style:pe.ok?vn:$i,children:"→"})]})},q)})]})}),i.jsx("tbody",{children:f.rows.map((q,ae)=>{const pe=q.cells[0]?.gridRow??ae,$e=B===pe;return i.jsxs("tr",{"data-testid":`g7le-table-row-${pe}`,onMouseEnter:()=>S(pe),onMouseLeave:()=>S(be=>be===pe?null:be),children:[i.jsx("td",{style:P8,"data-testid":`g7le-table-rowgutter-${pe}`,children:(()=>{const be=Ce(pe,-1),j=Ce(pe,1);return i.jsxs("div",{style:{...B8,visibility:$e?"visible":"hidden"},children:[i.jsx("button",{type:"button",title:be.ok?n("layout_editor.table_editor.move_row_up"):me(be.reason)||n("layout_editor.table_editor.move_row_up"),disabled:!be.ok||_,onClick:()=>De(pe,-1),"data-testid":`g7le-table-row-up-${pe}`,style:be.ok?vn:$i,children:"↑"}),i.jsx("button",{type:"button",title:n("layout_editor.table_editor.add_row"),disabled:_,onClick:()=>Se(pe),"data-testid":`g7le-table-row-add-${pe}`,style:vn,children:"+"}),i.jsx("button",{type:"button",title:n("layout_editor.table_editor.remove_row"),disabled:f.rows.length<=1||_,onClick:()=>qe(pe),"data-testid":`g7le-table-row-remove-${pe}`,style:kh,children:"✕"}),i.jsx("button",{type:"button",title:j.ok?n("layout_editor.table_editor.move_row_down"):me(j.reason)||n("layout_editor.table_editor.move_row_down"),disabled:!j.ok||_,onClick:()=>De(pe,1),"data-testid":`g7le-table-row-down-${pe}`,style:j.ok?vn:$i,children:"↓"})]})})()}),q.cells.map(be=>{const j=`${be.gridRow}:${be.gridCol}`,{node:oe,path:ue}=dl(be.cell),J=wh(oe,c,d),le=ue!==null&&J.editable,Ie=ue===null,dt=k[j],pt=dt!==void 0?dt:J.display,ot=h&&h.r===be.gridRow&&h.c===be.gridCol,Oe=h&&b&&be.gridRow>=Math.min(h.r,b.r)&&be.gridRow<=Math.max(h.r,b.r)&&be.gridCol>=Math.min(h.c,b.c)&&be.gridCol<=Math.max(h.c,b.c);return i.jsx("td",{colSpan:be.colSpan,rowSpan:be.rowSpan,"data-testid":`g7le-table-cell-${be.gridRow}-${be.gridCol}`,onClick:He=>_e(be.gridRow,be.gridCol,He.shiftKey),style:{...W8,...S8(be.cell,y,A),...C8(be.cell,g,A),...be.isHeader?j8:{},...ot?H8:{},...Oe&&!ot?F8:{}},children:le?i.jsx("input",{type:"text","data-testid":`g7le-table-cell-input-${be.gridRow}-${be.gridCol}`,value:pt,placeholder:n("layout_editor.table_editor.cell_text"),disabled:_,onMouseDown:He=>{He.shiftKey&&He.preventDefault(),_e(be.gridRow,be.gridCol,He.shiftKey)},onClick:He=>He.stopPropagation(),onChange:He=>w(Ye=>({...Ye,[j]:He.target.value})),onBlur:()=>{he(be.gridRow,be.gridCol)},style:K8}):Ie?i.jsx("span",{"data-testid":`g7le-table-cell-struct-${be.gridRow}-${be.gridCol}`,style:Sh,children:n("layout_editor.table_editor.structural_cell")}):i.jsx("span",{style:Sh,children:J.display})},j)})]},ae)})})]})}),i.jsxs("button",{type:"button","data-testid":"g7le-table-add-row-bottom",onClick:()=>Se(f.rows.length-1),disabled:_,style:V8,children:["+ ",n("layout_editor.table_editor.add_row")]}),i.jsxs("div",{style:U8,"data-testid":"g7le-table-cell-tools",children:[i.jsx("button",{type:"button","data-testid":"g7le-table-merge",onClick:Re,disabled:!Ae||_,style:Ch,children:n("layout_editor.table_editor.merge_cells")}),i.jsx("button",{type:"button","data-testid":"g7le-table-unmerge",onClick:ve,disabled:!Ne||_,style:Ch,children:n("layout_editor.table_editor.unmerge_cells")})]}),O&&Q&&i.jsxs("div",{style:G8,"data-testid":"g7le-table-cell-i18n-section",children:[i.jsx("div",{style:Y8,children:n("layout_editor.table_editor.cell_i18n_label")}),i.jsx(vt,{value:Q.value,onChange:ie,t:n,placeholder:n("layout_editor.table_editor.cell_text"),testidPrefix:"g7le-table-cell-i18n",candidates:s,enableExpressionTree:!0,expressionTreeCollapsible:!0})]}),O&&i.jsxs("div",{style:q8,"data-testid":"g7le-table-cell-color-section",children:[i.jsx(yh,{colorScheme:A,onChange:z,t:n,disabled:_}),y&&i.jsx("div",{style:cl,"data-testid":"g7le-table-cell-border-row",children:i.jsx(dh,{className:Le,catalog:y,t:n,onChange:te,colorScheme:A,onPresetToken:F,colorStyle:G,onColorStyle:I,disabled:_})}),i.jsx("div",{style:cl,"data-testid":"g7le-table-cell-fill-row",children:i.jsx(fh,{colorStyle:fe,className:K,catalog:g,t:n,colorScheme:A,onPresetToken:se,onCustomColor:re,onClear:U,disabled:_})})]}),O&&i.jsx("div",{style:cl,"data-testid":"g7le-table-cell-padding-row",children:i.jsx(gh,{paddingStyle:ne,catalog:m,t:n,onChange:ee,disabled:_})}),O&&!y&&i.jsxs("label",{style:X8,"data-testid":"g7le-table-cell-border-row",children:[i.jsx("span",{style:J8,children:n("layout_editor.table_editor.cell_border")}),i.jsx("input",{type:"text","data-testid":"g7le-table-cell-border-input",value:Le,placeholder:n("layout_editor.table_editor.cell_class_placeholder"),onChange:q=>te(q.target.value),disabled:_,style:Z8})]}),!f.valid&&i.jsx("div",{"data-testid":"g7le-table-grid-warning",style:Q8,children:n("layout_editor.table_editor.grid_invalid")}),i.jsx("div",{style:R8,children:n("layout_editor.table_editor.select_hint")})]})}const D8={display:"flex",flexDirection:"column",gap:8,width:"100%",marginBottom:12},N8={fontSize:12,fontWeight:700,color:"#0f172a"},z8={borderCollapse:"collapse",width:"auto"},M8={width:28,background:"transparent",border:"none"},L8={padding:2,border:"none",background:"transparent"},P8={padding:2,border:"none",background:"transparent",verticalAlign:"middle"},O8={display:"flex",gap:2,justifyContent:"center"},B8={display:"grid",gridTemplateColumns:"repeat(2, 16px)",gap:2,justifyContent:"center"},vn={width:16,height:16,lineHeight:"14px",padding:0,fontSize:10,border:"1px solid #cbd5e1",borderRadius:4,background:"#fff",color:"#475569",cursor:"pointer"},kh={...vn,border:"1px solid #fecaca",color:"#dc2626"},$i={...vn,cursor:"not-allowed",opacity:.4,color:"#94a3b8",borderColor:"#e2e8f0"},W8={border:"1px solid #cbd5e1",padding:0,width:64,minWidth:64,maxWidth:120,height:24,background:"#fff",cursor:"pointer"},j8={background:"#f1f5f9",fontWeight:600},H8={outline:"2px solid #2563eb",outlineOffset:-2},F8={background:"#dbeafe"},K8={width:"100%",boxSizing:"border-box",padding:"2px 5px",fontSize:11,height:22,lineHeight:"18px",border:"none",background:"transparent",outline:"none"},Sh={display:"block",padding:"2px 5px",fontSize:10,color:"#64748b",fontStyle:"italic",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap",maxWidth:110},V8={padding:"4px 8px",fontSize:12,border:"1px dashed #94a3b8",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer",alignSelf:"flex-start"},U8={display:"flex",gap:6},Ch={padding:"4px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},q8={display:"flex",flexDirection:"column",gap:8,borderTop:"1px solid #f1f5f9",paddingTop:8},G8={display:"flex",flexDirection:"column",gap:6,borderTop:"1px solid #f1f5f9",paddingTop:8},Y8={fontSize:12,fontWeight:700,color:"#0f172a"},cl={display:"flex",flexDirection:"column",gap:6},X8={display:"flex",alignItems:"center",gap:6},J8={fontSize:12,color:"#475569",minWidth:60},Z8={flex:1,minWidth:0,padding:"4px 6px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6},Q8={fontSize:11,color:"#b45309",background:"#fffbeb",border:"1px solid #fde68a",borderRadius:6,padding:"4px 6px"},R8={fontSize:11,color:"#94a3b8"},ez=/\{\{.*?\}\}/;function tz(e,t,n){const r=(e.props??{})[t];return Array.isArray(r)?r:r===void 0&&n?JSON.parse(JSON.stringify(n)):[]}function nz(e){return typeof e=="string"&&ez.test(e)}function ul(e,t,n){return e?e.startsWith("$t:")?t(e.slice(3)):e:n}function oz(e,t,n){return typeof e=="object"&&e!==null?e[t.key]:n?e:void 0}function rz(e,t,n,o){if(typeof e=="object"&&e!==null){const r={...e};return o===void 0?delete r[t.key]:r[t.key]=o,r}return n?o:{[t.key]:o}}function $h({node:e,params:t,t:n,onPatchNode:o,templateIdentifier:r,candidates:s}){const a=typeof t?.arrayProp=="string"?t.arrayProp:null,l=Array.isArray(t?.fields)?t.fields:[],d=ul(typeof t?.itemLabel=="string"?t.itemLabel:void 0,n,n("layout_editor.array_editor.item")),c=t?.newItem,p=c&&typeof c=="object"?c:null,f=typeof c=="string"||typeof c=="number"?c:null,y=(l.find(C=>C.primary)??l[0])?.key,g=(e.props??{})[a??""],m=Array.isArray(t?.defaultItems)?t.defaultItems:null,h=a?tz(e,a,m):[],v=u.useCallback(C=>{if(!a)return;const E={...e.props??{}};E[a]=C,o({...e,props:E})},[a,e,o]),b=u.useCallback(()=>{if(p)return JSON.parse(JSON.stringify(p));if(f!==null)return f;const C={};for(const E of l)(E.widget??"text")==="boolean"?C[E.key]=!1:C[E.key]="";return C},[p,f,l]),x=u.useCallback(()=>{v([...h,b()])},[v,h,b]),k=u.useCallback(C=>{v(h.filter((E,S)=>S!==C))},[v,h]),w=u.useCallback((C,E)=>{const S=C+E;if(S<0||S>=h.length)return;const T=[...h];[T[C],T[S]]=[T[S],T[C]],v(T)},[v,h]),_=u.useCallback((C,E,S)=>{const T=E.key===y,$=h.map((A,z)=>z===C?rz(A,E,T,S):A);v($)},[h,v,y]);return a?nz(g)||g!==void 0&&!Array.isArray(g)?i.jsx("div",{className:"g7le-node-editor g7le-node-editor--array","data-testid":"g7le-array-editor-bound",style:fz,children:n("layout_editor.array_editor.bound_degraded")}):i.jsxs("div",{className:"g7le-node-editor g7le-node-editor--array","data-testid":"g7le-array-editor",style:iz,children:[i.jsx("div",{style:sz,children:n("layout_editor.array_editor.items_title")}),h.length===0&&i.jsx("div",{"data-testid":"g7le-array-empty",style:Ih,children:n("layout_editor.array_editor.empty")}),h.map((C,E)=>i.jsxs("div",{"data-testid":`g7le-array-row-${E}`,style:az,children:[i.jsxs("div",{style:lz,children:[i.jsxs("span",{style:dz,children:["#",E+1]}),i.jsx("div",{style:{flex:1}}),i.jsx("button",{type:"button","data-testid":`g7le-array-up-${E}`,title:n("layout_editor.array_editor.move_up"),disabled:E===0,onClick:()=>w(E,-1),style:Eh,children:"↑"}),i.jsx("button",{type:"button","data-testid":`g7le-array-down-${E}`,title:n("layout_editor.array_editor.move_down"),disabled:E===h.length-1,onClick:()=>w(E,1),style:Eh,children:"↓"}),i.jsx("button",{type:"button","data-testid":`g7le-array-remove-${E}`,title:n("layout_editor.array_editor.remove"),onClick:()=>k(E),style:uz,children:"✕"})]}),l.map(S=>{const T=S.widget??"text",$=S.key===y,A=ul(S.label,n,S.key),z=oz(C,S,$),D=`g7le-array-field-${E}-${S.key}`;if(T==="boolean")return i.jsxs("label",{style:Wn,children:[i.jsx("span",{style:jn,children:A}),i.jsx("input",{type:"checkbox","data-testid":D,checked:z===!0,onChange:M=>_(E,S,M.target.checked)})]},S.key);if(T==="select"){const M=Array.isArray(S.options)?S.options:[];return i.jsxs("label",{style:Wn,children:[i.jsx("span",{style:jn,children:A}),i.jsxs("select",{"data-testid":D,value:z==null?"":String(z),onChange:B=>_(E,S,B.target.value===""?void 0:B.target.value),style:or,children:[i.jsx("option",{value:"",children:"—"}),M.map((B,N)=>i.jsx("option",{value:String(B.value),children:ul(B.label,n,String(B.value))},N))]})]},S.key)}if(T==="i18n-text"){const M=typeof z=="string"?z:z==null?"":String(z);return i.jsxs("label",{style:Wn,children:[i.jsx("span",{style:jn,children:A}),i.jsx("div",{"data-testid":D,style:{flex:1,minWidth:0},children:i.jsx(vt,{value:M,onChange:B=>_(E,S,B===""?void 0:B),t:n,placeholder:A,testidPrefix:`${D}-i18n`,candidates:s,enableExpressionTree:!0,expressionTreeCollapsible:!0})})]},S.key)}if(T==="number"){const M=z==null?"":String(z);return i.jsxs("label",{style:Wn,children:[i.jsx("span",{style:jn,children:A}),i.jsx("input",{type:"number","data-testid":D,value:M,onChange:B=>{const N=B.target.value;_(E,S,N===""?void 0:Number(N))},style:or})]},S.key)}if(T==="color"){const M=typeof z=="string"?z:"",B=/^#[0-9a-fA-F]{6}$/.test(M)?M:"#000000";return i.jsxs("label",{style:Wn,children:[i.jsx("span",{style:jn,children:A}),i.jsx("input",{type:"color","data-testid":`${D}-swatch`,value:B,onChange:N=>_(E,S,N.target.value),style:cz}),i.jsx("input",{type:"text","data-testid":D,value:M,placeholder:"#7C3AED",onChange:N=>_(E,S,N.target.value===""?void 0:N.target.value),style:or})]},S.key)}if(T==="number-list"){const M=Array.isArray(z)?z.join(", "):"";return i.jsxs("label",{style:Wn,children:[i.jsx("span",{style:jn,children:A}),i.jsx("input",{type:"text","data-testid":D,value:M,placeholder:n("layout_editor.array_editor.number_list_placeholder"),onChange:B=>{const N=B.target.value.split(",").map(H=>H.trim()).filter(H=>H!=="").map(H=>Number(H)).filter(H=>!Number.isNaN(H));_(E,S,N)},style:or})]},S.key)}const O=z==null?"":String(z);return i.jsxs("label",{style:Wn,children:[i.jsx("span",{style:jn,children:A}),i.jsx("input",{type:"text","data-testid":D,value:O,placeholder:T==="icon"?n("layout_editor.array_editor.icon_placeholder"):A,onChange:M=>_(E,S,M.target.value===""?void 0:M.target.value),style:or})]},S.key)})]},E)),i.jsx("button",{type:"button","data-testid":"g7le-array-add",onClick:x,style:pz,children:n("layout_editor.array_editor.add",{item:d})})]}):i.jsx("div",{className:"g7le-node-editor g7le-node-editor--array","data-testid":"g7le-array-editor-misconfigured",style:Ih,children:n("layout_editor.array_editor.no_array_prop")})}const iz={display:"flex",flexDirection:"column",gap:8,width:"100%",marginBottom:12},sz={fontSize:12,fontWeight:700,color:"#0f172a",marginBottom:2},az={display:"flex",flexDirection:"column",gap:4,padding:8,border:"1px solid #e2e8f0",borderRadius:8,background:"#f8fafc"},lz={display:"flex",gap:4,alignItems:"center"},dz={fontSize:11,fontWeight:700,color:"#64748b"},Wn={display:"flex",flexWrap:"wrap",gap:6,alignItems:"center"},jn={fontSize:11,color:"#475569",minWidth:64,flexShrink:0},or={flex:1,minWidth:0,padding:"4px 6px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6},cz={width:32,height:28,padding:0,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",cursor:"pointer",flexShrink:0},Eh={padding:"2px 6px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},uz={padding:"2px 6px",fontSize:12,border:"1px solid #fecaca",borderRadius:6,background:"#fff",color:"#dc2626",cursor:"pointer"},pz={padding:"4px 8px",fontSize:12,border:"1px dashed #94a3b8",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer",alignSelf:"flex-start"},Ih={fontSize:11,color:"#94a3b8",fontStyle:"italic"},fz={fontSize:11,color:"#b45309",fontStyle:"italic",padding:"4px 2px"};function gz(e,t){return e?e.startsWith("$t:")?t(e.slice(3)):e:null}function hz(e){const{params:t,t:n}=e,o=Array.isArray(t?.groups)?t.groups:[];return o.length===0?i.jsx("div",{className:"g7le-node-editor g7le-node-editor--array-group","data-testid":"g7le-array-group-misconfigured",style:vz,children:n("layout_editor.array_editor.no_array_prop")}):i.jsx("div",{className:"g7le-node-editor g7le-node-editor--array-group","data-testid":"g7le-array-group-editor",style:yz,children:o.map((r,s)=>{const a=gz(r.title,n),{title:l,...d}=r;return i.jsxs("div",{"data-testid":`g7le-array-group-${r.arrayProp??s}`,style:bz,children:[a&&i.jsx("div",{style:mz,children:a}),i.jsx($h,{...e,params:d})]},r.arrayProp??s)})})}const yz={display:"flex",flexDirection:"column",gap:16,width:"100%",marginBottom:12},bz={display:"flex",flexDirection:"column",gap:4},mz={fontSize:12,fontWeight:700,color:"#1e293b",borderBottom:"1px solid #e2e8f0",paddingBottom:4},vz={fontSize:11,color:"#94a3b8",fontStyle:"italic"},_z=/\{\{.*?\}\}/;function xz(e){return typeof e=="string"&&_z.test(e)}function wz(e,t,n){return e?e.startsWith("$t:")?t(e.slice(3)):e:n}function kz(e){const{node:t,params:n,t:o,onPatchNode:r}=e,s=typeof n?.arrayProp=="string"?n.arrayProp:null,a=typeof n?.idField=="string"?n.idField:"id",l=typeof n?.cellChildrenProp=="string"?n.cellChildrenProp:"cellChildren",d=typeof n?.cellChildComponent=="string"?n.cellChildComponent:"Div",c=wz(typeof n?.itemLabel=="string"?n.itemLabel:void 0,o,o("layout_editor.array_editor.item")),[p,f]=u.useState(null),y=s?(t.props??{})[s]:void 0,g=Array.isArray(y)?y:[],m=u.useCallback(C=>{if(!s)return;const E={...t.props??{}};E[s]=C,r({...t,props:E})},[s,t,r]),h=u.useCallback(()=>{const C=n?.newItem;return C&&typeof C=="object"?JSON.parse(JSON.stringify(C)):{[a]:"",[l]:[]}},[n,a,l]),v=u.useCallback(()=>{m([...g,h()])},[m,g,h]),b=u.useCallback(C=>{m(g.filter((E,S)=>S!==C)),f(null)},[m,g]),x=u.useCallback((C,E)=>{const S=C+E;if(S<0||S>=g.length)return;const T=[...g];[T[C],T[S]]=[T[S],T[C]],m(T)},[m,g]),k=u.useCallback((C,E)=>{const S=g.map((T,$)=>$===C?{...T,[a]:E}:T);m(S)},[g,m,a]),w=u.useCallback(C=>({type:"composite",name:"CardGridCell",children:Array.isArray(C[l])?C[l]:[]}),[l]),_=u.useCallback(C=>E=>{const S=Array.isArray(E.children)?E.children:[],T=g.map(($,A)=>A===C?{...$,[l]:S}:$);m(T)},[g,m,l]);return s?xz(y)||y!==void 0&&!Array.isArray(y)?i.jsx("div",{className:"g7le-node-editor g7le-node-editor--cell-tree","data-testid":"g7le-cell-tree-bound",style:Mz,children:o("layout_editor.array_editor.bound_degraded")}):i.jsxs("div",{className:"g7le-node-editor g7le-node-editor--cell-tree","data-testid":"g7le-cell-tree-editor",style:Sz,children:[i.jsx("div",{style:Cz,children:o("layout_editor.cell_tree_editor.columns_title")}),g.length===0&&i.jsx("div",{"data-testid":"g7le-cell-tree-empty",style:Th,children:o("layout_editor.array_editor.empty")}),g.map((C,E)=>{const S=typeof C[a]=="string"?C[a]:"",T=p===E;return i.jsxs("div",{"data-testid":`g7le-cell-tree-row-${E}`,style:$z,children:[i.jsxs("div",{style:Ez,children:[i.jsxs("span",{style:Iz,children:["#",E+1]}),i.jsx("input",{type:"text","data-testid":`g7le-cell-tree-id-${E}`,value:S,placeholder:o("layout_editor.cell_tree_editor.id_placeholder"),onChange:$=>k(E,$.target.value),style:Az}),i.jsx("button",{type:"button","data-testid":`g7le-cell-tree-up-${E}`,title:o("layout_editor.array_editor.move_up"),disabled:E===0,onClick:()=>x(E,-1),style:Ah,children:"↑"}),i.jsx("button",{type:"button","data-testid":`g7le-cell-tree-down-${E}`,title:o("layout_editor.array_editor.move_down"),disabled:E===g.length-1,onClick:()=>x(E,1),style:Ah,children:"↓"}),i.jsx("button",{type:"button","data-testid":`g7le-cell-tree-remove-${E}`,title:o("layout_editor.array_editor.remove"),onClick:()=>b(E),style:Tz,children:"✕"})]}),i.jsx("button",{type:"button","data-testid":`g7le-cell-tree-toggle-${E}`,onClick:()=>f(T?null:E),style:Dz,children:o(T?"layout_editor.cell_tree_editor.collapse_cells":"layout_editor.cell_tree_editor.expand_cells")}),T&&i.jsx("div",{"data-testid":`g7le-cell-tree-cells-${E}`,style:Nz,children:i.jsx(qg,{...e,node:w(C),params:{childComponent:d},onPatchNode:_(E)})})]},E)}),i.jsx("button",{type:"button","data-testid":"g7le-cell-tree-add",onClick:v,style:zz,children:o("layout_editor.cell_tree_editor.add_column",{item:c})})]}):i.jsx("div",{className:"g7le-node-editor g7le-node-editor--cell-tree","data-testid":"g7le-cell-tree-misconfigured",style:Th,children:o("layout_editor.array_editor.no_array_prop")})}const Sz={display:"flex",flexDirection:"column",gap:8,width:"100%",marginBottom:12},Cz={fontSize:12,fontWeight:700,color:"#0f172a",marginBottom:2},$z={display:"flex",flexDirection:"column",gap:6,padding:8,border:"1px solid #e2e8f0",borderRadius:8,background:"#f8fafc"},Ez={display:"flex",gap:4,alignItems:"center"},Iz={fontSize:11,fontWeight:700,color:"#64748b",flexShrink:0},Az={flex:1,minWidth:0,padding:"4px 6px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6},Ah={padding:"2px 6px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"},Tz={padding:"2px 6px",fontSize:12,border:"1px solid #fecaca",borderRadius:6,background:"#fff",color:"#dc2626",cursor:"pointer"},Dz={padding:"3px 8px",fontSize:11,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#2563eb",cursor:"pointer",alignSelf:"flex-start"},Nz={paddingLeft:8,borderLeft:"2px solid #e2e8f0"},zz={padding:"4px 8px",fontSize:12,border:"1px dashed #94a3b8",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer",alignSelf:"flex-start"},Th={fontSize:11,color:"#94a3b8",fontStyle:"italic"},Mz={fontSize:11,color:"#b45309",fontStyle:"italic",padding:"4px 2px"};function Lz(e,t,n){const o=Be(e,t);if(!n||n.length===0)return{measured:[],grid:o};const r=[];for(const s of o.rows)for(const a of s.cells){const d=tl(e,a,t).map(p=>`children.${p}`).join("."),c=n.find(p=>p.path===d||p.path.endsWith("."+d));c&&r.push({ref:a,box:{top:c.top,left:c.left,width:c.width,height:c.height}})}return{measured:r,grid:o}}const pl=26,fl=50,gl=6,Dh=44,Nh=18,zh="g7le-table-inplace-empty-cell-style";function Pz(){if(typeof document>"u"||document.getElementById(zh))return;const e=document.createElement("style");e.id=zh,e.textContent=".g7le-preview-frame td[data-editor-name],.g7le-preview-frame th[data-editor-name]{min-width:56px;height:38px;}.g7le-preview-frame table[data-editor-name]{min-width:120px;}",document.head.appendChild(e)}function Oz({node:e,params:t,nodeBox:n,cellBoxes:o,selectedCellPath:r,colorScheme:s,t:a,onPatchNode:l,onRequestInlineEdit:d}){const c=u.useMemo(()=>t&&typeof t=="object"?t:null,[t]),{measured:p,grid:f}=u.useMemo(()=>Lz(e,c,o),[e,c,o]),[y,g]=u.useState(null),[m,h]=u.useState(null),v=u.useMemo(()=>{if(!r)return null;for(const j of f.rows)for(const oe of j.cells){const ue=tl(e,oe,c).map(J=>`children.${J}`).join(".");if(r===ue||r.startsWith(ue+"."))return{r:oe.gridRow,c:oe.gridCol}}return null},[r,f,e,c]),b=u.useMemo(()=>y&&Ue(f,y.r,y.c)?y:v,[y,v,f]),[x,k]=u.useState(!1),[w,_]=u.useState(s==="dark"?"dark":"light"),[C,E]=u.useState(null),[S,T]=u.useState(null),$=u.useRef(null),A=u.useCallback(()=>{$.current!==null&&(clearTimeout($.current),$.current=null)},[]),z=u.useCallback(()=>{A(),$.current=setTimeout(()=>{E(null),T(null),$.current=null},240)},[A]);u.useEffect(()=>()=>A(),[A]),u.useEffect(()=>{Pz()},[]);const D=u.useCallback(j=>l(j),[l]),O=u.useMemo(()=>b?Ue(f,b.r,b.c):null,[f,b]),M=u.useMemo(()=>O&&typeof O.cell.props?.className=="string"?O.cell.props.className:"",[O]),B=u.useCallback(j=>D(nl(e,c,j)),[e,c,D]),N=u.useCallback(j=>D(Rg(e,c,j)),[e,c,D]),H=u.useCallback((j,oe)=>D(eh(e,c,j,oe)),[e,c,D]),L=u.useCallback(j=>D(nh(e,c,j)),[e,c,D]),I=u.useCallback(j=>D(oh(e,c,j)),[e,c,D]),P=u.useCallback((j,oe)=>D(th(e,c,j,oe)),[e,c,D]),F=u.useCallback(j=>{if(!O)return;const oe=O.gridRow,ue=O.gridCol,J=j==="down"?oe+O.rowSpan:oe+O.rowSpan-1,le=j==="right"?ue+O.colSpan:ue+O.colSpan-1;if(J>=f.rows.length||le>=f.colCount)return;const Ie=ol(e,c,oe,ue,J,le);Ie.ok&&D(Ie.table)},[O,f,e,c,D]),G=!!(O&&O.gridCol+O.colSpan!b||!m||!Ue(f,m.r,m.c)?null:{r0:Math.min(b.r,m.r),c0:Math.min(b.c,m.c),r1:Math.max(b.r,m.r),c1:Math.max(b.c,m.c)},[b,m,f]),Z=u.useMemo(()=>{if(!Y)return[];const j=new Set,oe=[];for(const ue of f.rows)for(const J of ue.cells)if(J.gridRow>=Y.r0&&J.gridRow<=Y.r1&&J.gridCol>=Y.c0&&J.gridCol<=Y.c1){const le=`${J.gridRow}:${J.gridCol}`;j.has(le)||(j.add(le),oe.push({r:J.gridRow,c:J.gridCol}))}return oe},[Y,f]),se=Z.length>1,re=u.useCallback(()=>{if(!Y)return;const j=ol(e,c,Y.r0,Y.c0,Y.r1,Y.c1);j.ok&&(D(j.table),h(null))},[Y,e,c,D]),U=u.useCallback(()=>{b&&D(rh(e,c,b.r,b.c))},[e,c,b,D]),ee=u.useMemo(()=>{const j=c?.cellBorder;return j&&typeof j=="object"?j:null},[c]),fe=u.useMemo(()=>{const j=c?.cellBackground;return j&&typeof j=="object"?j:null},[c]),K=u.useMemo(()=>{const j=c?.cellPadding;return j&&typeof j=="object"?j:null},[c]),ne=u.useMemo(()=>{if(x){const j=new Set,oe=[];for(const ue of f.rows)for(const J of ue.cells){const le=`${J.gridRow}:${J.gridCol}`;j.has(le)||(j.add(le),oe.push({r:J.gridRow,c:J.gridCol}))}return oe}return se?Z:b?[{r:b.r,c:b.c}]:[]},[x,se,Z,b,f]),me=u.useCallback(j=>{if(ne.length===0)return;let oe=e;for(const ue of ne){const J=(ee?.colors??[]).map(Ie=>Ie.token),le=j.split(/\s+/).filter(Ie=>Ie&&!J.includes(Ie)).join(" ");oe=Ci(oe,c,ue.r,ue.c,le)}D(oe)},[e,c,ne,ee,D]),Ce=u.useMemo(()=>{const j=(ee?.sides??[]).find(J=>J.value==="all")??(ee?.sides??[])[0],oe=(ee?.widths??[]).find(J=>J.value!=="none");if(!j||!oe)return[];const ue=oe.suffix??"";return[ue?`${j.prefix}${ue}`:j.prefix]},[ee]),ze=u.useCallback((j,oe,ue)=>{if(Ce.length===0)return j;const J=(ee?.sides??[]).map(Oe=>Oe.prefix),le=Ue(Be(j,c),oe,ue),dt=(typeof le?.cell?.props?.className=="string"?le.cell.props.className:"").split(/\s+/).filter(Boolean);if(dt.some(Oe=>J.some(He=>Oe===He||Oe.startsWith(He+"-")&&/^-?\d+$/.test(Oe.slice(He.length+1)))))return j;const ot=[...dt,...Ce].join(" ").trim();return Ci(j,c,oe,ue,ot)},[Ce,ee,c]),Se=u.useCallback((j,oe)=>{if(j.length===0)return;const ue=new Set(j.map(le=>`${le.r}:${le.c}`));let J=e;for(const le of j){if(J=ze(J,le.r,le.c),J=Jt(J,c,le.r,le.c,"borderColor",oe),le.r>0&&!ue.has(`${le.r-1}:${le.c}`)){const Ie=Ue(Be(J,c),le.r-1,le.c);Ie&&(J=Jt(J,c,Ie.gridRow,Ie.gridCol,"borderBottomColor",oe))}if(le.c>0&&!ue.has(`${le.r}:${le.c-1}`)){const Ie=Ue(Be(J,c),le.r,le.c-1);Ie&&(J=Jt(J,c,Ie.gridRow,Ie.gridCol,"borderRightColor",oe))}}D(J)},[e,c,D,ze]),qe=u.useMemo(()=>(ee?.colors??[]).map(j=>j.token).filter(Boolean),[ee]),De=u.useCallback(j=>{if(ne.length===0||qe.length===0)return;let oe=e;for(const ue of ne)oe=ze(oe,ue.r,ue.c),oe=go(oe,c,ue.r,ue.c,j,qe,w);D(oe)},[ne,e,c,D,ze,qe,w]),Pe=u.useCallback(j=>Se(ne,j),[Se,ne]),Qe=u.useMemo(()=>{if(w==="dark")return"";const j=O?.cell.props?.style;return typeof j?.borderColor=="string"?j.borderColor:""},[O,w]),Me=u.useCallback((j,oe)=>{if(ne.length===0)return;let ue=e;for(const J of ne)ue=Jt(ue,c,J.r,J.c,j,oe);D(ue)},[ne,e,c,D]),Re=u.useMemo(()=>(fe?.colors??[]).map(j=>j.token).filter(j=>!!j),[fe]),ve=u.useCallback(j=>{if(ne.length===0||Re.length===0)return;let oe=e;for(const ue of ne)oe=go(oe,c,ue.r,ue.c,j,Re,w);D(oe)},[ne,e,c,D,Re,w]),te=u.useCallback(j=>Me("backgroundColor",j),[Me]),he=u.useCallback(()=>{if(ne.length===0)return;let j=e;for(const oe of ne)j=go(j,c,oe.r,oe.c,void 0,Re,w),w==="light"&&(j=Jt(j,c,oe.r,oe.c,"backgroundColor",void 0));D(j)},[ne,e,c,D,Re,w]),Q=u.useCallback(j=>Me("padding",j),[Me]),ie=u.useMemo(()=>{if(w==="dark")return"";const j=O?.cell.props?.style;return typeof j?.backgroundColor=="string"?j.backgroundColor:""},[O,w]),_e=u.useMemo(()=>typeof O?.cell.props?.className=="string"?O.cell.props.className:"",[O]),ge=u.useMemo(()=>{const j=O?.cell.props?.style;return typeof j?.padding=="string"?j.padding:""},[O]),Ae=u.useCallback(j=>{const oe=j.currentTarget,ue=oe.style.pointerEvents;oe.style.pointerEvents="none";const J=document.elementFromPoint(j.clientX,j.clientY);oe.style.pointerEvents=ue;const le=J?.closest?.("[data-dnd-handle-path]");le&&le.dispatchEvent(new PointerEvent("pointerdown",{bubbles:!0,cancelable:!0,clientX:j.clientX,clientY:j.clientY,pointerId:j.pointerId,pointerType:j.pointerType,button:j.button,isPrimary:!0}))},[]),Ne=u.useMemo(()=>{const j=new Map;for(const oe of p)j.has(oe.ref.gridCol)||j.set(oe.ref.gridCol,oe.box);return j},[p]),Le=u.useMemo(()=>{const j=new Map;for(const oe of p)j.has(oe.ref.gridRow)||j.set(oe.ref.gridRow,oe.box);return j},[p]);if(p.length===0)return null;const q=!!(O&&(O.colSpan>1||O.rowSpan>1)),ae=S!==null?S:b?.c??null,pe=C!==null?C:b?.r??null,$e=n.top-pl-gl-Dh,be=n.left-fl-gl-Dh;return i.jsxs("div",{className:"g7le-table-inplace","data-testid":"g7le-table-inplace",style:{position:"absolute",inset:0,pointerEvents:"none",zIndex:g7},children:[p.map(j=>{const oe=b&&b.r===j.ref.gridRow&&b.c===j.ref.gridCol,ue=Y&&j.ref.gridRow>=Y.r0&&j.ref.gridRow<=Y.r1&&j.ref.gridCol>=Y.c0&&j.ref.gridCol<=Y.c1;return i.jsx("div",{"data-testid":`g7le-inplace-cell-${j.ref.gridRow}-${j.ref.gridCol}`,role:"button",tabIndex:-1,"aria-label":a("layout_editor.table_inplace.select_cell"),onMouseEnter:()=>{A(),E(j.ref.gridRow),T(j.ref.gridCol)},onMouseLeave:()=>{z()},onPointerDown:J=>Ae(J),onClick:J=>{if(J.stopPropagation(),J.shiftKey&&(y||b))h({r:j.ref.gridRow,c:j.ref.gridCol});else{g({r:j.ref.gridRow,c:j.ref.gridCol}),h(null);const le=tl(e,j.ref,c).map(Ie=>`children.${Ie}`).join(".");d?.(le)}},style:{position:"absolute",top:j.box.top,left:j.box.left,width:j.box.width,height:j.box.height,minWidth:Nh,minHeight:Nh,boxSizing:"border-box",pointerEvents:"auto",cursor:"pointer",outline:oe?"2px solid #2563eb":"none",outlineOffset:-2,background:ue&&!oe?"rgba(37,99,235,0.14)":"transparent"}},`cell-${j.ref.gridRow}-${j.ref.gridCol}`)}),i.jsx("div",{"data-testid":"g7le-inplace-col-rail",onMouseEnter:A,onMouseLeave:z,style:{position:"absolute",top:$e,left:n.left,width:n.width,height:pl,pointerEvents:"none"},children:ae!==null&&Ne.has(ae)&&(()=>{const j=ae,oe=Ne.get(j),ue=wi(f,j,-1),J=wi(f,j,1),le=oe.left-n.left;return i.jsxs("div",{"data-testid":`g7le-inplace-colgutter-${j}`,onMouseEnter:()=>{A(),T(j)},onMouseLeave:z,style:{position:"absolute",top:0,left:le,width:oe.width,minWidth:96,height:pl,display:"flex",gap:3,justifyContent:"center",alignItems:"center",pointerEvents:"auto"},children:[i.jsx("button",{type:"button",title:a("layout_editor.table_inplace.move_col_left"),disabled:!ue.ok,onClick:()=>P(j,-1),"data-testid":`g7le-inplace-col-left-${j}`,style:ue.ok?_n:Ei,children:"←"}),i.jsx("button",{type:"button",title:a("layout_editor.table_inplace.add_col"),onClick:()=>L(j),"data-testid":`g7le-inplace-col-add-${j}`,style:_n,children:"+"}),i.jsx("button",{type:"button",title:a("layout_editor.table_inplace.remove_col"),disabled:f.colCount<=1,onClick:()=>I(j),"data-testid":`g7le-inplace-col-remove-${j}`,style:Mh,children:"✕"}),i.jsx("button",{type:"button",title:a("layout_editor.table_inplace.move_col_right"),disabled:!J.ok,onClick:()=>P(j,1),"data-testid":`g7le-inplace-col-right-${j}`,style:J.ok?_n:Ei,children:"→"})]},`colgut-${j}`)})()}),i.jsx("div",{"data-testid":"g7le-inplace-row-rail",style:{position:"absolute",top:n.top,left:be,width:fl,height:n.height,pointerEvents:"none"},children:pe!==null&&Le.has(pe)&&(()=>{const j=pe,oe=Le.get(j),ue=xi(f,j,-1),J=xi(f,j,1),le=oe.top-n.top;return i.jsxs("div",{"data-testid":`g7le-inplace-rowgutter-${j}`,onMouseEnter:()=>{A(),E(j)},onMouseLeave:z,style:{position:"absolute",top:le,left:0,width:fl,height:oe.height,minHeight:48,display:"grid",gridTemplateColumns:"repeat(2, 22px)",gridAutoRows:"22px",gap:3,justifyContent:"center",alignContent:"center",pointerEvents:"auto"},children:[i.jsx("button",{type:"button",title:a("layout_editor.table_inplace.move_row_up"),disabled:!ue.ok,onClick:()=>H(j,-1),"data-testid":`g7le-inplace-row-up-${j}`,style:ue.ok?_n:Ei,children:"↑"}),i.jsx("button",{type:"button",title:a("layout_editor.table_inplace.add_row"),onClick:()=>B(j),"data-testid":`g7le-inplace-row-add-${j}`,style:_n,children:"+"}),i.jsx("button",{type:"button",title:a("layout_editor.table_inplace.remove_row"),disabled:f.rows.length<=1,onClick:()=>N(j),"data-testid":`g7le-inplace-row-remove-${j}`,style:Mh,children:"✕"}),i.jsx("button",{type:"button",title:a("layout_editor.table_inplace.move_row_down"),disabled:!J.ok,onClick:()=>H(j,1),"data-testid":`g7le-inplace-row-down-${j}`,style:J.ok?_n:Ei,children:"↓"})]},`rowgut-${j}`)})()}),i.jsxs("div",{"data-testid":"g7le-inplace-corner-add",style:{position:"absolute",top:n.top+n.height+gl,left:n.left,display:"flex",flexDirection:"column",gap:6,pointerEvents:"auto",minWidth:200,maxWidth:Math.max(240,n.width),background:"#fff",border:"1px solid #e2e8f0",borderRadius:8,padding:8,boxShadow:"0 2px 8px rgba(0,0,0,0.12)"},children:[i.jsxs("div",{style:{display:"flex",gap:6,flexWrap:"wrap"},children:[i.jsxs("button",{type:"button","data-testid":"g7le-inplace-add-row-bottom",onClick:()=>B(f.rows.length-1),style:Lh,children:["+ ",a("layout_editor.table_inplace.add_row")]}),i.jsxs("button",{type:"button","data-testid":"g7le-inplace-add-col-right",onClick:()=>L(f.colCount-1),style:Lh,children:["+ ",a("layout_editor.table_inplace.add_col")]})]}),!b&&i.jsx("div",{"data-testid":"g7le-inplace-select-hint",style:{fontSize:11,color:"#94a3b8",borderTop:"1px solid #f1f5f9",paddingTop:6},children:a("layout_editor.table_inplace.select_cell_hint")}),b&&i.jsxs("div",{style:{display:"flex",flexDirection:"column",gap:6,borderTop:"1px solid #f1f5f9",paddingTop:6},children:[se&&i.jsx("div",{"data-testid":"g7le-inplace-range-count",style:{fontSize:11,color:"#2563eb"},children:a("layout_editor.table_inplace.range_selected",{count:Z.length})}),i.jsxs("div",{style:{display:"flex",gap:6,flexWrap:"wrap",alignItems:"center"},children:[se?i.jsx("button",{type:"button","data-testid":"g7le-inplace-merge-range",onClick:re,style:rr,children:a("layout_editor.table_inplace.merge_range")}):i.jsxs(i.Fragment,{children:[i.jsx("button",{type:"button","data-testid":"g7le-inplace-merge-right",disabled:!G,onClick:()=>F("right"),style:G?rr:hl,children:a("layout_editor.table_inplace.merge_right")}),i.jsx("button",{type:"button","data-testid":"g7le-inplace-merge-down",disabled:!V,onClick:()=>F("down"),style:V?rr:hl,children:a("layout_editor.table_inplace.merge_down")})]}),i.jsx("button",{type:"button","data-testid":"g7le-inplace-unmerge",disabled:!q,onClick:U,style:q?rr:hl,children:a("layout_editor.table_inplace.unmerge")})]}),i.jsxs("div",{"data-testid":"g7le-inplace-cell-color",style:{borderTop:"1px solid #f1f5f9",paddingTop:6,display:"flex",flexDirection:"column",gap:6},children:[i.jsxs("label",{style:{display:"flex",alignItems:"center",gap:6,fontSize:11,color:"#475569",cursor:"pointer"},children:[i.jsx("input",{type:"checkbox","data-testid":"g7le-inplace-border-all-toggle",checked:x,onChange:j=>k(j.target.checked)}),a("layout_editor.table_inplace.border_apply_all")]}),i.jsx(yh,{colorScheme:w,onChange:_,t:a}),ee&&i.jsx("div",{"data-testid":"g7le-inplace-cell-border",children:i.jsx(dh,{className:x?"":M,catalog:ee,t:a,onChange:me,colorScheme:w,onPresetToken:De,colorStyle:x?"":Qe,onColorStyle:Pe})}),i.jsx("div",{"data-testid":"g7le-inplace-cell-fill",children:i.jsx(fh,{colorStyle:x?"":ie,className:x?"":_e,catalog:fe,t:a,colorScheme:w,onPresetToken:ve,onCustomColor:te,onClear:he})})]}),i.jsx("div",{"data-testid":"g7le-inplace-cell-padding",style:{borderTop:"1px solid #f1f5f9",paddingTop:6},children:i.jsx(gh,{paddingStyle:x?"":ge,catalog:K,t:a,onChange:Q})})]})]})]})}const _n={width:22,height:22,lineHeight:"20px",padding:0,fontSize:13,border:"1px solid #cbd5e1",borderRadius:5,background:"#fff",color:"#475569",cursor:"pointer",boxShadow:"0 1px 3px rgba(0,0,0,0.18)"},Mh={..._n,border:"1px solid #fecaca",color:"#dc2626"},Ei={..._n,cursor:"not-allowed",opacity:.4,color:"#94a3b8",borderColor:"#e2e8f0"},rr={padding:"4px 10px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer",boxShadow:"0 1px 2px rgba(0,0,0,0.12)"},hl={...rr,cursor:"not-allowed",opacity:.4},Lh={padding:"3px 8px",fontSize:11,border:"1px dashed #94a3b8",borderRadius:6,background:"#fff",color:"#475569",cursor:"pointer"};let Ph=!1;function Bz(){Ph||(Pn("children",qg),Pn("table",T8),Pn("array",$h),Pn("array-group",hz),Pn("array-cell-tree",kz),Oa("table",Oz),Ph=!0)}function Wz(){if(typeof window>"u")return;const e=window.G7Core;if(!e)return;const t=e.layoutEditor,n=Array.isArray(t?.__queue)?t.__queue:[],o=Array.isArray(t?.__readyCallbacks)?t.__readyCallbacks:[];e.layoutEditor={registerWidget:Xe,registerNodeEditor:Pn,registerCanvasOverlay:Oa,onReady:r=>{typeof r=="function"&&r()}};for(const r of n){const[s,...a]=r;try{s==="widget"?Xe(a[0],a[1]):s==="nodeEditor"?Pn(a[0],a[1]):s==="canvasOverlay"&&Oa(a[0],a[1])}catch{}}for(const r of o)try{r()}catch{}}const jz=1280;qN(),Bz(),Wz();async function Hz(e){if(typeof fetch!="function")return[];try{const t=await fetch(en(`/api/admin/templates/${encodeURIComponent(e)}/editor/permission-candidates`,"json"),{credentials:"same-origin",headers:je()});if(!t.ok)return[];const o=(await t.json().catch(()=>null))?.data?.permissions;if(!Array.isArray(o))return[];const r=[];for(const s of o)if(s&&typeof s=="object"){const a=s.key,l=s.name;typeof a=="string"&&a.length>0&&r.push({value:a,label:typeof l=="string"&&l?l:a})}return r}catch{return[]}}function Fz({templateIdentifier:e}){gD({templateIdentifier:e}),hD(e);const{state:t,dispatch:n}=Je(),{t:o}=tt(),r=yo(),s=Cp(),a=xD(),l=t.selectedRoute?.layoutName??null,d=CD(e,l),c=t.editMode==="extension",p=u.useMemo(()=>c?kD(a):s,[c,a,s]),[f,y]=u.useState(null),[g,m]=u.useState(null),[h,v]=u.useState([]),[b,x]=u.useState("user"),[k,w]=u.useState(null),[_,C]=u.useState(null),[E,S]=u.useState([]);u.useEffect(()=>{let te=!1;return(async()=>{try{const he=await fetch(en(`/api/templates/${encodeURIComponent(e)}/components`,"json"),{credentials:"same-origin",headers:{Accept:"application/json"}});if(!he.ok)return;const Q=await he.json().catch(()=>null);!te&&Q&&typeof Q=="object"&&y(Q)}catch{}})(),()=>{te=!0}},[e]),u.useEffect(()=>{let te=!1;return(async()=>{const{spec:he,sampleGlobalSources:Q}=await eb({templateIdentifier:e,coreSeed:$D()});te||(m(he),v(Q),Vz(e,he,Q))})(),()=>{te=!0}},[e]),u.useEffect(()=>{let te=!1;return(async()=>{if(typeof fetch=="function")try{const he=await fetch(`/api/admin/templates/${encodeURIComponent(e)}`,{credentials:"same-origin",headers:je()});if(!he.ok)return;const ie=(await he.json().catch(()=>null))?.data,_e=ie?.type;te||((_e==="admin"||_e==="user")&&x(_e),typeof ie?.name=="string"&&ie.name&&w(ie.name),typeof ie?.version=="string"&&ie.version&&C(ie.version))}catch{}})(),()=>{te=!0}},[e]),u.useEffect(()=>{let te=!1;return(async()=>{if(typeof fetch=="function")try{const he=await fetch("/api/admin/templates/?per_page=100&page=1",{credentials:"same-origin",headers:je()});if(!he.ok)return;const ie=(await he.json().catch(()=>null))?.data?.data;if(!te&&Array.isArray(ie)){const _e=[];for(const ge of ie){if(!ge||typeof ge!="object")continue;const Ae=ge.identifier;if(typeof Ae!="string"||!Ae||ge.is_pending===!0)continue;const Le=ge.type==="admin"?"admin":"user",q=ge.name,ae=ge.version;_e.push({identifier:Ae,name:typeof q=="string"?q:Ae,version:typeof ae=="string"?ae:"",type:Le})}S(_e)}}catch{}})(),()=>{te=!0}},[e]);const T=g?.nesting??null,$=t.selectedRoute?.path??null,A=t.editMode,z=t.selectedRoute?.layoutName??(c?a.document?.hostLayoutName??null:null),D=t.routeTree,O=u.useMemo(()=>{if(A!=="extension"||!z)return null;const te=bl(D,z);return te||`__base__/${z}`},[A,z,D]),M=c&&t.returnRoute===null&&!!z;u.useEffect(()=>{if(!M||!z)return;const te=bl(D,z);te&&n({type:"SET_RETURN_ROUTE",route:{path:te,layoutName:z}})},[M,z,D,n]),u.useEffect(()=>{const te=G4(A,$,O),he=Y4(g?.states?.groups,te);n({type:"SET_AVAILABLE_STATES",states:he,activeStateId:X4(he)})},[g,A,$,O,n]);const[B,N]=u.useState([]);u.useEffect(()=>{let te=!1;return(async()=>{const he=await Hz(e);te||N(he)})(),()=>{te=!0}},[e]),yD(),bD();const H=u.useCallback(te=>{const he=t.routeTree,Q=te.split("?")[0]?.split("#")[0]??te;return Kz(he,Q)?"route_in_tree":"route_not_in_tree"},[t.routeTree]),L=u.useCallback(te=>{const he=te.split("?")[0]?.split("#")[0]??te,Q=yl(t.routeTree,he);Q&&(p.isDirty&&!(!(typeof window<"u")||window.confirm(o("layout_editor.chrome.dirty_guard.message")))||n({type:"SELECT_ROUTE",route:{path:Q.path,layoutName:Q.layoutName}}))},[t.routeTree,p.isDirty,n]),I=u.useRef([]),P=u.useCallback(te=>{I.current=te},[]),[F,G]=u.useState(null),V=u.useCallback(()=>G(null),[]),Y=u.useRef(null),Z=u.useRef(null);u.useEffect(()=>{const te=Z.current,he=Y.current;if(!te||!he||typeof ResizeObserver>"u")return;const Q=()=>{he.style.setProperty("--g7le-header-h",`${Math.round(te.getBoundingClientRect().height)}px`)};Q();const ie=new ResizeObserver(Q);return ie.observe(te),()=>ie.disconnect()},[]);const se=u.useCallback(()=>{p.reload()},[p]),re=u.useCallback(()=>{p.isDirty&&!(!(typeof window<"u")||window.confirm(o("layout_editor.chrome.dirty_guard.message")))||window.G7Core?.dispatch?.({handler:"openWindow",params:{path:`/admin/templates/${b}`,target:"_self"}})},[p.isDirty,o,b]),U=u.useCallback(te=>{if(!te||te===e||p.isDirty&&!(typeof window<"u"?window.confirm(o("layout_editor.chrome.dirty_guard.message")):!0))return;const he=Zs(te);window.G7Core?.dispatch?.({handler:"openWindow",params:{path:he,target:"_self"}})},[e,p.isDirty,o]),ee=u.useCallback(()=>{const te=ME(e,t.selectedRoute?.path??null);window.G7Core?.dispatch?.({handler:"openWindow",params:{path:te}})},[e,t.selectedRoute]),fe=u.useCallback(()=>{const te=t.selectedRoute?.layoutName??"";if(!te)return;const he=r.open({ariaLabel:o("layout_editor.attachment_manager.title"),width:720,maxHeightRatio:.82,content:u.createElement(Ml,{templateIdentifier:e,layoutName:te,t:o,onClose:()=>r.close(he)})})},[r,e,t.selectedRoute,o]),K=u.useCallback(()=>{const te=t.selectedRoute?.layoutName??"";if(!te)return;const he=Hy(s.document?.raw??{}),Q=r.open({ariaLabel:o("layout_editor.translation_manager.title"),width:720,maxHeightRatio:.82,content:u.createElement(dm,{templateIdentifier:e,layoutName:te,t:o,referencedKeys:he,onClose:()=>r.close(Q)})})},[r,e,t.selectedRoute,o,s.document]),ne=u.useCallback((te,he)=>{const Q=window.G7Core;if(Q&&typeof Q.t=="function"){const ie=Q.t(te,he);if(ie&&ie!==te&&!ie.startsWith("$t:"))return ie}return o(te,he)},[o]),me=u.useCallback(()=>{if(!t.selectedRoute||t.editMode==="extension"||t.editMode==="iteration_item")return;const te=r.open({ariaLabel:o("layout_editor.page_settings.title",{name:t.selectedRoute.path??""}),width:1080,maxHeightRatio:.88,closeOnEscape:!1,content:u.createElement(u$,{templateIdentifier:e,spec:g,t:o,resolveLabel:ne,permissionCandidates:B,onClose:()=>r.close(te)})})},[r,t.selectedRoute,t.editMode,o,ne,e,g,B]),Ce=u.useCallback(async()=>{const te=await d.createPreview(s.document?.raw??null);if(te.kind!=="success"){const he=te.kind==="network_error"?te.message:o("layout_editor.preview_action.no_document");typeof window<"u"&&window.alert(he)}},[d,s.document,o]),ze=u.useCallback(()=>{const te=t.selectedRoute?.layoutName??"",he=t.selectedRoute?.path??"",Q=t.editMode==="extension"&&he.startsWith("__extension__/")?he.slice(14):null,ie=Q?{kind:"extension",extensionId:Q}:te?{kind:"layout",layoutName:te}:null;if(!ie)return;const _e=r.open({ariaLabel:o("layout_editor.version_history.title"),width:640,maxHeightRatio:.82,content:u.createElement(X$,{templateIdentifier:e,target:ie,t:o,onRestored:async ge=>{typeof ge=="number"&&(ie.kind==="extension"?n({type:"SET_EXTENSION_VERSION",extensionId:ie.extensionId,version:ge}):n({type:"SET_LAYOUT_VERSION",layoutName:ie.layoutName,version:ge})),await p.reload()},onClose:()=>r.close(_e)})})},[r,e,t.selectedRoute,t.editMode,o,p,n]),Se=p.dirtyKeys.size>0||p.isDirty;u.useEffect(()=>{if(!Se)return;const te=he=>{he.preventDefault(),he.returnValue=""};return window.addEventListener("beforeunload",te),()=>window.removeEventListener("beforeunload",te)},[Se]);const qe=u.useCallback(()=>{!p.isDirty||!(!(typeof window<"u")||window.confirm(o("layout_editor.chrome.reset_guard.message")))||p.reload()},[p,o]),De=u.useCallback(async()=>{const te=await p.save({sessionAddedPaths:I.current});G(te),te.kind==="success"&&typeof te.newContentVersion=="number"&&(te.savedExtensionId?n({type:"SET_EXTENSION_VERSION",extensionId:te.savedExtensionId,version:te.newContentVersion}):te.savedLayoutName&&n({type:"SET_LAYOUT_VERSION",layoutName:te.savedLayoutName,version:te.newContentVersion}))},[p,n]),Pe=u.useCallback(()=>{const te=r.open({ariaLabel:o("layout_editor.shortcuts.title"),width:560,maxHeightRatio:.85,content:u.createElement(iE,{t:o,onClose:()=>r.close(te)})})},[r,o]),Qe=u.useMemo(()=>({save:()=>{De()},exit:re,editCode:ee,reset:qe,translations:K,addElement:()=>{t.selectedRoute&&n({type:"TOGGLE_PALETTE"})},help:Pe,preview:()=>{t.selectedRoute?.layoutName&&Ce()}}),[De,re,ee,qe,K,t.selectedRoute,n,Pe,Ce]),[Me,Re]=u.useState(!1);u.useEffect(()=>{const te=window.setInterval(()=>{Re(!!window.__g7LayoutEditorHasSelection)},200);return()=>window.clearInterval(te)},[]),np({handlers:Qe,hasSelection:Me});const ve=p;return t.routesError?i.jsx("div",{className:"g7le-chrome g7le-chrome--routes-error","data-testid":"g7le-chrome-routes-error",style:{position:"fixed",inset:0,display:"flex",background:"#f8fafc",color:"#0f172a",fontFamily:'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Apple SD Gothic Neo", "Noto Sans KR", sans-serif',fontSize:13,lineHeight:1.5,zIndex:9e3},children:i.jsx(Fa,{error:t.routesError})}):i.jsx("div",{className:"g7le-chrome","data-testid":"g7le-chrome",ref:Y,style:{position:"static",minHeight:"100vh",minWidth:jz,background:"#f8fafc",color:"#0f172a",fontFamily:'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Apple SD Gothic Neo", "Noto Sans KR", sans-serif',fontSize:13,lineHeight:1.5,zIndex:9e3},children:i.jsxs(dy,{value:ve,children:[c&&a.needsHostPicker&&a.document&&i.jsx(SD,{hostLayouts:a.document.hostLayouts,extensionLabel:null,onSelect:a.selectHost}),i.jsxs("div",{className:"g7le-chrome__header","data-testid":"g7le-chrome-header",ref:Z,style:{position:"sticky",top:0,zIndex:20,background:"#f8fafc"},children:[i.jsx(by,{onSave:()=>{De()},isDirty:p.isDirty,onExit:re,onEditCode:ee,onManageImages:fe,onManageTranslations:K,onPageSettings:me,onReset:qe,onShowShortcuts:Pe,onPreview:Ce,onShowVersions:ze,templateName:k,templateVersion:_,templateList:E,onSwitchTemplate:U}),i.jsx(xE,{result:F,onDismiss:V,onLoadLatest:se}),i.jsxs("div",{className:"g7le-chrome__device-toolbar","data-testid":"g7le-chrome-device-toolbar",style:{display:"flex",justifyContent:"center",padding:"6px 12px",background:"#f1f5f9",borderBottom:"1px solid #e2e8f0"},children:[i.jsx(U4,{}),i.jsx(q4,{})]})]}),i.jsxs("div",{className:"g7le-chrome__body",style:{display:"grid",gridTemplateColumns:"auto 1fr",alignItems:"stretch"},children:[i.jsx(HE,{}),i.jsx(j4,{manifest:f,nesting:T,componentPalette:g?.componentPalette??null,spec:g,permissionCandidates:B,sampleGlobalSources:h,resolveRouteMatch:H,onNavigateToDestination:L,onSessionAddedPathsChange:P})]}),i.jsx(py,{})]})})}function yl(e,t){for(const n of e){if(n.kind==="route"&&n.path===t)return n;if(n.children&&n.children.length>0){const o=yl(n.children,t);if(o)return o}}return null}function Kz(e,t){return yl(e,t)!==null}function bl(e,t){for(const n of e){if(n.kind==="route"&&n.layoutName===t&&typeof n.path=="string")return n.path;if(n.children&&n.children.length>0){const o=bl(n.children,t);if(o)return o}}return null}function Vz(e,t,n){const o=[];Va({sources:n,isGuestOnly:!1,warn:s=>{const a=/extension '([^']+)' override of core key '([^']+)'/.exec(s);a&&o.push(`${a[1]}:${a[2]}`)}});const r=t?.sampleData?.byDataSourceId?Object.keys(t.sampleData.byDataSourceId).length:0;Qh({templateIdentifier:e,mergedSources:[...n.map(s=>({kind:s.kind,id:s.id}))],blockCounts:{controls:t?.controls?Object.keys(t.controls).length:0,componentCapabilities:t?.componentCapabilities?Object.keys(t.componentCapabilities).length:0,actionRecipes:t?.actionRecipes?Object.keys(t.actionRecipes).length:0,conditionRecipes:t?.conditionRecipes?Object.keys(t.conditionRecipes).length:0,paletteGroups:t?.componentPalette?.groups?.length??0,stateGroups:t?.states?.groups?.length??0,sampleDataIds:r},sampleGlobalSourceCount:n.length,sampleGlobalConflicts:o,timestamp:Date.now()})}function Oh({templateIdentifier:e,initialLocale:t}){return u.useEffect(()=>()=>{Vh(),qh(),Yh(),Xh(),Jh(),Zh(),Rh(),ey(),ty()},[]),i.jsx(ly,{templateIdentifier:e,initialLocale:t,children:i.jsx(uy,{children:i.jsx(Fz,{templateIdentifier:e})})})}if(typeof window<"u"){const e=window.G7Core||(window.G7Core={});if(e.__LayoutEditorChrome=Oh,typeof e.__onChromeReady=="function")try{e.__onChromeReady()}catch{}}dn.LayoutEditorChrome=Oh,Object.defineProperty(dn,Symbol.toStringTag,{value:"Module"})})(this.G7LayoutEditor=this.G7LayoutEditor||{},ReactJSXRuntime,React,ReactDOM); //# sourceMappingURL=layout-editor.min.js.map diff --git a/public/build/core/template-engine.min.js b/public/build/core/template-engine.min.js index a9626fc3..783d7b14 100644 --- a/public/build/core/template-engine.min.js +++ b/public/build/core/template-engine.min.js @@ -1,20 +1,20 @@ -var ET=Object.defineProperty;var _T=(Qt,Xr,Jr)=>Xr in Qt?ET(Qt,Xr,{enumerable:!0,configurable:!0,writable:!0,value:Jr}):Qt[Xr]=Jr;var $=(Qt,Xr,Jr)=>_T(Qt,typeof Xr!="symbol"?Xr+"":Xr,Jr);(function(Qt){"use strict";function Xr(o,e){for(var n=0;na[i]})}}}return Object.freeze(Object.defineProperty(o,Symbol.toStringTag,{value:"Module"}))}function Jr(o){return o&&o.__esModule&&Object.prototype.hasOwnProperty.call(o,"default")?o.default:o}var td={exports:{}},Ye={};var Fg;function WS(){if(Fg)return Ye;Fg=1;var o=Symbol.for("react.transitional.element"),e=Symbol.for("react.portal"),n=Symbol.for("react.fragment"),a=Symbol.for("react.strict_mode"),i=Symbol.for("react.profiler"),l=Symbol.for("react.consumer"),c=Symbol.for("react.context"),d=Symbol.for("react.forward_ref"),f=Symbol.for("react.suspense"),g=Symbol.for("react.memo"),m=Symbol.for("react.lazy"),y=Symbol.for("react.activity"),S=Symbol.iterator;function v(N){return N===null||typeof N!="object"?null:(N=S&&N[S]||N["@@iterator"],typeof N=="function"?N:null)}var _={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},A=Object.assign,x={};function O(N,R,de){this.props=N,this.context=R,this.refs=x,this.updater=de||_}O.prototype.isReactComponent={},O.prototype.setState=function(N,R){if(typeof N!="object"&&typeof N!="function"&&N!=null)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,N,R,"setState")},O.prototype.forceUpdate=function(N){this.updater.enqueueForceUpdate(this,N,"forceUpdate")};function M(){}M.prototype=O.prototype;function T(N,R,de){this.props=N,this.context=R,this.refs=x,this.updater=de||_}var D=T.prototype=new M;D.constructor=T,A(D,O.prototype),D.isPureReactComponent=!0;var z=Array.isArray;function P(){}var q={H:null,A:null,T:null,S:null},W=Object.prototype.hasOwnProperty;function he(N,R,de){var fe=de.ref;return{$$typeof:o,type:N,key:R,ref:fe!==void 0?fe:null,props:de}}function Ee(N,R){return he(N.type,R,N.props)}function be(N){return typeof N=="object"&&N!==null&&N.$$typeof===o}function Ue(N){var R={"=":"=0",":":"=2"};return"$"+N.replace(/[=:]/g,function(de){return R[de]})}var Fe=/\/+/g;function Ge(N,R){return typeof N=="object"&&N!==null&&N.key!=null?Ue(""+N.key):R.toString(36)}function wt(N){switch(N.status){case"fulfilled":return N.value;case"rejected":throw N.reason;default:switch(typeof N.status=="string"?N.then(P,P):(N.status="pending",N.then(function(R){N.status==="pending"&&(N.status="fulfilled",N.value=R)},function(R){N.status==="pending"&&(N.status="rejected",N.reason=R)})),N.status){case"fulfilled":return N.value;case"rejected":throw N.reason}}throw N}function J(N,R,de,fe,xe){var ee=typeof N;(ee==="undefined"||ee==="boolean")&&(N=null);var _e=!1;if(N===null)_e=!0;else switch(ee){case"bigint":case"string":case"number":_e=!0;break;case"object":switch(N.$$typeof){case o:case e:_e=!0;break;case m:return _e=N._init,J(_e(N._payload),R,de,fe,xe)}}if(_e)return xe=xe(N),_e=fe===""?"."+Ge(N,0):fe,z(xe)?(de="",_e!=null&&(de=_e.replace(Fe,"$&/")+"/"),J(xe,R,de,"",function(Ie){return Ie})):xe!=null&&(be(xe)&&(xe=Ee(xe,de+(xe.key==null||N&&N.key===xe.key?"":(""+xe.key).replace(Fe,"$&/")+"/")+_e)),R.push(xe)),1;_e=0;var Be=fe===""?".":fe+":";if(z(N))for(var ke=0;ke>>1,pe=J[te];if(0>>1;tei(de,Me))fei(xe,de)?(J[te]=xe,J[fe]=Me,te=fe):(J[te]=de,J[R]=Me,te=R);else if(fei(xe,Me))J[te]=xe,J[fe]=Me,te=fe;else break e}}return ue}function i(J,ue){var Me=J.sortIndex-ue.sortIndex;return Me!==0?Me:J.id-ue.id}if(o.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var l=performance;o.unstable_now=function(){return l.now()}}else{var c=Date,d=c.now();o.unstable_now=function(){return c.now()-d}}var f=[],g=[],m=1,y=null,S=3,v=!1,_=!1,A=!1,x=!1,O=typeof setTimeout=="function"?setTimeout:null,M=typeof clearTimeout=="function"?clearTimeout:null,T=typeof setImmediate<"u"?setImmediate:null;function D(J){for(var ue=n(g);ue!==null;){if(ue.callback===null)a(g);else if(ue.startTime<=J)a(g),ue.sortIndex=ue.expirationTime,e(f,ue);else break;ue=n(g)}}function z(J){if(A=!1,D(J),!_)if(n(f)!==null)_=!0,P||(P=!0,Ue());else{var ue=n(g);ue!==null&&wt(z,ue.startTime-J)}}var P=!1,q=-1,W=5,he=-1;function Ee(){return x?!0:!(o.unstable_now()-heJ&&Ee());){var te=y.callback;if(typeof te=="function"){y.callback=null,S=y.priorityLevel;var pe=te(y.expirationTime<=J);if(J=o.unstable_now(),typeof pe=="function"){y.callback=pe,D(J),ue=!0;break t}y===n(f)&&a(f),D(J)}else a(f);y=n(f)}if(y!==null)ue=!0;else{var N=n(g);N!==null&&wt(z,N.startTime-J),ue=!1}}break e}finally{y=null,S=Me,v=!1}ue=void 0}}finally{ue?Ue():P=!1}}}var Ue;if(typeof T=="function")Ue=function(){T(be)};else if(typeof MessageChannel<"u"){var Fe=new MessageChannel,Ge=Fe.port2;Fe.port1.onmessage=be,Ue=function(){Ge.postMessage(null)}}else Ue=function(){O(be,0)};function wt(J,ue){q=O(function(){J(o.unstable_now())},ue)}o.unstable_IdlePriority=5,o.unstable_ImmediatePriority=1,o.unstable_LowPriority=4,o.unstable_NormalPriority=3,o.unstable_Profiling=null,o.unstable_UserBlockingPriority=2,o.unstable_cancelCallback=function(J){J.callback=null},o.unstable_forceFrameRate=function(J){0>J||125te?(J.sortIndex=Me,e(g,J),n(f)===null&&J===n(g)&&(A?(M(q),q=-1):A=!0,wt(z,Me-te))):(J.sortIndex=pe,e(f,J),_||v||(_=!0,P||(P=!0,Ue()))),J},o.unstable_shouldYield=Ee,o.unstable_wrapCallback=function(J){var ue=S;return function(){var Me=S;S=ue;try{return J.apply(this,arguments)}finally{S=Me}}}})(id)),id}var Yg;function XS(){return Yg||(Yg=1,ad.exports=YS()),ad.exports}var sd={exports:{}},_n={};var Xg;function JS(){if(Xg)return _n;Xg=1;var o=nd();function e(f){var g="https://react.dev/errors/"+f;if(1"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(o)}catch(e){console.error(e)}}return o(),sd.exports=JS(),sd.exports}var Zg;function QS(){if(Zg)return Zs;Zg=1;var o=XS(),e=nd(),n=Qg();function a(t){var r="https://react.dev/errors/"+t;if(1pe||(t.current=te[pe],te[pe]=null,pe--)}function de(t,r){pe++,te[pe]=t.current,t.current=r}var fe=N(null),xe=N(null),ee=N(null),_e=N(null);function Be(t,r){switch(de(ee,r),de(xe,t),de(fe,null),r.nodeType){case 9:case 11:t=(t=r.documentElement)&&(t=t.namespaceURI)?pS(t):0;break;default:if(t=r.tagName,r=r.namespaceURI)r=pS(r),t=mS(r,t);else switch(t){case"svg":t=1;break;case"math":t=2;break;default:t=0}}R(fe),de(fe,t)}function ke(){R(fe),R(xe),R(ee)}function Ie(t){t.memoizedState!==null&&de(_e,t);var r=fe.current,s=mS(r,t.type);r!==s&&(de(xe,t),de(fe,s))}function Ct(t){xe.current===t&&(R(fe),R(xe)),_e.current===t&&(R(_e),Ol._currentValue=Me)}var ot,Ot;function vt(t){if(ot===void 0)try{throw Error()}catch(s){var r=s.stack.trim().match(/\n( *(at )?)/);ot=r&&r[1]||"",Ot=-1Xr in Yt?oT(Yt,Xr,{enumerable:!0,configurable:!0,writable:!0,value:Jr}):Yt[Xr]=Jr;var $=(Yt,Xr,Jr)=>lT(Yt,typeof Xr!="symbol"?Xr+"":Xr,Jr);(function(Yt){"use strict";function Xr(o,e){for(var n=0;na[i]})}}}return Object.freeze(Object.defineProperty(o,Symbol.toStringTag,{value:"Module"}))}function Jr(o){return o&&o.__esModule&&Object.prototype.hasOwnProperty.call(o,"default")?o.default:o}var Ju={exports:{}},We={};var qg;function BS(){if(qg)return We;qg=1;var o=Symbol.for("react.transitional.element"),e=Symbol.for("react.portal"),n=Symbol.for("react.fragment"),a=Symbol.for("react.strict_mode"),i=Symbol.for("react.profiler"),l=Symbol.for("react.consumer"),c=Symbol.for("react.context"),d=Symbol.for("react.forward_ref"),f=Symbol.for("react.suspense"),g=Symbol.for("react.memo"),m=Symbol.for("react.lazy"),b=Symbol.for("react.activity"),S=Symbol.iterator;function v(H){return H===null||typeof H!="object"?null:(H=S&&H[S]||H["@@iterator"],typeof H=="function"?H:null)}var _={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},A=Object.assign,x={};function L(H,T,ce){this.props=H,this.context=T,this.refs=x,this.updater=ce||_}L.prototype.isReactComponent={},L.prototype.setState=function(H,T){if(typeof H!="object"&&typeof H!="function"&&H!=null)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,H,T,"setState")},L.prototype.forceUpdate=function(H){this.updater.enqueueForceUpdate(this,H,"forceUpdate")};function M(){}M.prototype=L.prototype;function R(H,T,ce){this.props=H,this.context=T,this.refs=x,this.updater=ce||_}var D=R.prototype=new M;D.constructor=R,A(D,L.prototype),D.isPureReactComponent=!0;var B=Array.isArray;function q(){}var P={H:null,A:null,T:null,S:null},K=Object.prototype.hasOwnProperty;function ge(H,T,ce){var de=ce.ref;return{$$typeof:o,type:H,key:T,ref:de!==void 0?de:null,props:ce}}function Ee(H,T){return ge(H.type,T,H.props)}function Se(H){return typeof H=="object"&&H!==null&&H.$$typeof===o}function Ue(H){var T={"=":"=0",":":"=2"};return"$"+H.replace(/[=:]/g,function(ce){return T[ce]})}var Ve=/\/+/g;function qe(H,T){return typeof H=="object"&&H!==null&&H.key!=null?Ue(""+H.key):T.toString(36)}function St(H){switch(H.status){case"fulfilled":return H.value;case"rejected":throw H.reason;default:switch(typeof H.status=="string"?H.then(q,q):(H.status="pending",H.then(function(T){H.status==="pending"&&(H.status="fulfilled",H.value=T)},function(T){H.status==="pending"&&(H.status="rejected",H.reason=T)})),H.status){case"fulfilled":return H.value;case"rejected":throw H.reason}}throw H}function J(H,T,ce,de,ye){var ie=typeof H;(ie==="undefined"||ie==="boolean")&&(H=null);var xe=!1;if(H===null)xe=!0;else switch(ie){case"bigint":case"string":case"number":xe=!0;break;case"object":switch(H.$$typeof){case o:case e:xe=!0;break;case m:return xe=H._init,J(xe(H._payload),T,ce,de,ye)}}if(xe)return ye=ye(H),xe=de===""?"."+qe(H,0):de,B(ye)?(ce="",xe!=null&&(ce=xe.replace(Ve,"$&/")+"/"),J(ye,T,ce,"",function($e){return $e})):ye!=null&&(Se(ye)&&(ye=Ee(ye,ce+(ye.key==null||H&&H.key===ye.key?"":(""+ye.key).replace(Ve,"$&/")+"/")+xe)),T.push(ye)),1;xe=0;var _e=de===""?".":de+":";if(B(H))for(var Te=0;Te>>1,pe=J[Z];if(0>>1;Zi(ce,Le))dei(ye,ce)?(J[Z]=ye,J[de]=Le,Z=de):(J[Z]=ce,J[T]=Le,Z=T);else if(dei(ye,Le))J[Z]=ye,J[de]=Le,Z=de;else break e}}return fe}function i(J,fe){var Le=J.sortIndex-fe.sortIndex;return Le!==0?Le:J.id-fe.id}if(o.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var l=performance;o.unstable_now=function(){return l.now()}}else{var c=Date,d=c.now();o.unstable_now=function(){return c.now()-d}}var f=[],g=[],m=1,b=null,S=3,v=!1,_=!1,A=!1,x=!1,L=typeof setTimeout=="function"?setTimeout:null,M=typeof clearTimeout=="function"?clearTimeout:null,R=typeof setImmediate<"u"?setImmediate:null;function D(J){for(var fe=n(g);fe!==null;){if(fe.callback===null)a(g);else if(fe.startTime<=J)a(g),fe.sortIndex=fe.expirationTime,e(f,fe);else break;fe=n(g)}}function B(J){if(A=!1,D(J),!_)if(n(f)!==null)_=!0,q||(q=!0,Ue());else{var fe=n(g);fe!==null&&St(B,fe.startTime-J)}}var q=!1,P=-1,K=5,ge=-1;function Ee(){return x?!0:!(o.unstable_now()-geJ&&Ee());){var Z=b.callback;if(typeof Z=="function"){b.callback=null,S=b.priorityLevel;var pe=Z(b.expirationTime<=J);if(J=o.unstable_now(),typeof pe=="function"){b.callback=pe,D(J),fe=!0;break t}b===n(f)&&a(f),D(J)}else a(f);b=n(f)}if(b!==null)fe=!0;else{var H=n(g);H!==null&&St(B,H.startTime-J),fe=!1}}break e}finally{b=null,S=Le,v=!1}fe=void 0}}finally{fe?Ue():q=!1}}}var Ue;if(typeof R=="function")Ue=function(){R(Se)};else if(typeof MessageChannel<"u"){var Ve=new MessageChannel,qe=Ve.port2;Ve.port1.onmessage=Se,Ue=function(){qe.postMessage(null)}}else Ue=function(){L(Se,0)};function St(J,fe){P=L(function(){J(o.unstable_now())},fe)}o.unstable_IdlePriority=5,o.unstable_ImmediatePriority=1,o.unstable_LowPriority=4,o.unstable_NormalPriority=3,o.unstable_Profiling=null,o.unstable_UserBlockingPriority=2,o.unstable_cancelCallback=function(J){J.callback=null},o.unstable_forceFrameRate=function(J){0>J||125Z?(J.sortIndex=Le,e(g,J),n(f)===null&&J===n(g)&&(A?(M(P),P=-1):A=!0,St(B,Le-Z))):(J.sortIndex=pe,e(f,J),_||v||(_=!0,q||(q=!0,Ue()))),J},o.unstable_shouldYield=Ee,o.unstable_wrapCallback=function(J){var fe=S;return function(){var Le=S;S=fe;try{return J.apply(this,arguments)}finally{S=Le}}}})(td)),td}var Fg;function qS(){return Fg||(Fg=1,ed.exports=PS()),ed.exports}var nd={exports:{}},wn={};var Kg;function GS(){if(Kg)return wn;Kg=1;var o=Qu();function e(f){var g="https://react.dev/errors/"+f;if(1"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(o)}catch(e){console.error(e)}}return o(),nd.exports=GS(),nd.exports}var Xg;function VS(){if(Xg)return Xs;Xg=1;var o=qS(),e=Qu(),n=Yg();function a(t){var r="https://react.dev/errors/"+t;if(1pe||(t.current=Z[pe],Z[pe]=null,pe--)}function ce(t,r){pe++,Z[pe]=t.current,t.current=r}var de=H(null),ye=H(null),ie=H(null),xe=H(null);function _e(t,r){switch(ce(ie,r),ce(ye,t),ce(de,null),r.nodeType){case 9:case 11:t=(t=r.documentElement)&&(t=t.namespaceURI)?oS(t):0;break;default:if(t=r.tagName,r=r.namespaceURI)r=oS(r),t=lS(r,t);else switch(t){case"svg":t=1;break;case"math":t=2;break;default:t=0}}T(de),ce(de,t)}function Te(){T(de),T(ye),T(ie)}function $e(t){t.memoizedState!==null&&ce(xe,t);var r=de.current,s=lS(r,t.type);r!==s&&(ce(ye,t),ce(de,s))}function ht(t){ye.current===t&&(T(de),T(ye)),xe.current===t&&(T(xe),Tl._currentValue=Le)}var wt,kt;function ft(t){if(wt===void 0)try{throw Error()}catch(s){var r=s.stack.trim().match(/\n( *(at )?)/);wt=r&&r[1]||"",kt=-1)":-1h||U[u]!==X[h]){var ae=` -`+U[u].replace(" at new "," at ");return t.displayName&&ae.includes("")&&(ae=ae.replace("",t.displayName)),ae}while(1<=u&&0<=h);break}}}finally{_t=!1,Error.prepareStackTrace=s}return(s=t?t.displayName||t.name:"")?vt(s):""}function nt(t,r){switch(t.tag){case 26:case 27:case 5:return vt(t.type);case 16:return vt("Lazy");case 13:return t.child!==r&&r!==null?vt("Suspense Fallback"):vt("Suspense");case 19:return vt("SuspenseList");case 0:case 15:return Oe(t.type,!1);case 11:return Oe(t.type.render,!1);case 1:return Oe(t.type,!0);case 31:return vt("Activity");default:return""}}function sn(t){try{var r="",s=null;do r+=nt(t,s),s=t,t=t.return;while(t);return r}catch(u){return` +`);for(h=u=0;uh||j[u]!==X[h]){var re=` +`+j[u].replace(" at new "," at ");return t.displayName&&re.includes("")&&(re=re.replace("",t.displayName)),re}while(1<=u&&0<=h);break}}}finally{nn=!1,Error.prepareStackTrace=s}return(s=t?t.displayName||t.name:"")?ft(s):""}function Nt(t,r){switch(t.tag){case 26:case 27:case 5:return ft(t.type);case 16:return ft("Lazy");case 13:return t.child!==r&&r!==null?ft("Suspense Fallback"):ft("Suspense");case 19:return ft("SuspenseList");case 0:case 15:return Ft(t.type,!1);case 11:return Ft(t.type.render,!1);case 1:return Ft(t.type,!0);case 31:return ft("Activity");default:return""}}function Br(t){try{var r="",s=null;do r+=Nt(t,s),s=t,t=t.return;while(t);return r}catch(u){return` Error generating stack: `+u.message+` -`+u.stack}}var Mt=Object.prototype.hasOwnProperty,Vt=o.unstable_scheduleCallback,on=o.unstable_cancelCallback,ln=o.unstable_shouldYield,Br=o.unstable_requestPaint,Ft=o.unstable_now,Ci=o.unstable_getCurrentPriorityLevel,na=o.unstable_ImmediatePriority,yn=o.unstable_UserBlockingPriority,On=o.unstable_NormalPriority,rt=o.unstable_LowPriority,Hn=o.unstable_IdlePriority,bn=o.log,ra=o.unstable_setDisableYieldValue,sr=null,cn=null;function Tn(t){if(typeof bn=="function"&&ra(t),cn&&typeof cn.setStrictMode=="function")try{cn.setStrictMode(sr,t)}catch{}}var un=Math.clz32?Math.clz32:ce,V=Math.log,se=Math.LN2;function ce(t){return t>>>=0,t===0?32:31-(V(t)/se|0)|0}var Z=256,ve=262144,ge=4194304;function we(t){var r=t&42;if(r!==0)return r;switch(t&-t){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return t&261888;case 262144:case 524288:case 1048576:case 2097152:return t&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return t&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return t}}function Ae(t,r,s){var u=t.pendingLanes;if(u===0)return 0;var h=0,p=t.suspendedLanes,C=t.pingedLanes;t=t.warmLanes;var k=u&134217727;return k!==0?(u=k&~p,u!==0?h=we(u):(C&=k,C!==0?h=we(C):s||(s=k&~t,s!==0&&(h=we(s))))):(k=u&~p,k!==0?h=we(k):C!==0?h=we(C):s||(s=u&~t,s!==0&&(h=we(s)))),h===0?0:r!==0&&r!==h&&(r&p)===0&&(p=h&-h,s=r&-r,p>=s||p===32&&(s&4194048)!==0)?r:h}function Ke(t,r){return(t.pendingLanes&~(t.suspendedLanes&~t.pingedLanes)&r)===0}function Te(t,r){switch(t){case 1:case 2:case 4:case 8:case 64:return r+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return r+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function $e(){var t=ge;return ge<<=1,(ge&62914560)===0&&(ge=4194304),t}function Re(t){for(var r=[],s=0;31>s;s++)r.push(t);return r}function et(t,r){t.pendingLanes|=r,r!==268435456&&(t.suspendedLanes=0,t.pingedLanes=0,t.warmLanes=0)}function je(t,r,s,u,h,p){var C=t.pendingLanes;t.pendingLanes=s,t.suspendedLanes=0,t.pingedLanes=0,t.warmLanes=0,t.expiredLanes&=s,t.entangledLanes&=s,t.errorRecoveryDisabledLanes&=s,t.shellSuspendCounter=0;var k=t.entanglements,U=t.expirationTimes,X=t.hiddenUpdates;for(s=C&~s;0"u")return null;try{return t.activeElement||t.body}catch{return t.body}}var Lf=/[\n"\\]/g;function zn(t){return t.replace(Lf,function(r){return"\\"+r.charCodeAt(0).toString(16)+" "})}function vs(t,r,s,u,h,p,C,k){t.name="",C!=null&&typeof C!="function"&&typeof C!="symbol"&&typeof C!="boolean"?t.type=C:t.removeAttribute("type"),r!=null?C==="number"?(r===0&&t.value===""||t.value!=r)&&(t.value=""+kn(r)):t.value!==""+kn(r)&&(t.value=""+kn(r)):C!=="submit"&&C!=="reset"||t.removeAttribute("value"),r!=null?ia(t,C,kn(r)):s!=null?ia(t,C,kn(s)):u!=null&&t.removeAttribute("value"),h==null&&p!=null&&(t.defaultChecked=!!p),h!=null&&(t.checked=h&&typeof h!="function"&&typeof h!="symbol"),k!=null&&typeof k!="function"&&typeof k!="symbol"&&typeof k!="boolean"?t.name=""+kn(k):t.removeAttribute("name")}function Ai(t,r,s,u,h,p,C,k){if(p!=null&&typeof p!="function"&&typeof p!="symbol"&&typeof p!="boolean"&&(t.type=p),r!=null||s!=null){if(!(p!=="submit"&&p!=="reset"||r!=null)){Uo(t);return}s=s!=null?""+kn(s):"",r=r!=null?""+kn(r):s,k||r===t.value||(t.value=r),t.defaultValue=r}u=u??h,u=typeof u!="function"&&typeof u!="symbol"&&!!u,t.checked=k?t.checked:!!u,t.defaultChecked=!!u,C!=null&&typeof C!="function"&&typeof C!="symbol"&&typeof C!="boolean"&&(t.name=C),Uo(t)}function ia(t,r,s){r==="number"&&ja(t.ownerDocument)===t||t.defaultValue===""+s||(t.defaultValue=""+s)}function za(t,r,s,u){if(t=t.options,r){r={};for(var h=0;h"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),Vo=!1;if(kr)try{var Ti={};Object.defineProperty(Ti,"passive",{get:function(){Vo=!0}}),window.addEventListener("test",Ti,Ti),window.removeEventListener("test",Ti,Ti)}catch{Vo=!1}var Vr=null,Fo=null,ws=null;function Pc(){if(ws)return ws;var t,r=Fo,s=r.length,u,h="value"in Vr?Vr.value:Vr.textContent,p=h.length;for(t=0;t=re),We=" ",dt=!1;function pt(t,r){switch(t){case"keyup":return L.indexOf(r.keyCode)!==-1;case"keydown":return r.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function Pt(t){return t=t.detail,typeof t=="object"&&"data"in t?t.data:null}var Or=!1;function Xf(t,r){switch(t){case"compositionend":return Pt(r);case"keypress":return r.which!==32?null:(dt=!0,We);case"textInput":return t=r.data,t===We&&dt?null:t;default:return null}}function Wc(t,r){if(Or)return t==="compositionend"||!I&&pt(t,r)?(t=Pc(),ws=Fo=Vr=null,Or=!1,t):null;switch(t){case"paste":return null;case"keypress":if(!(r.ctrlKey||r.altKey||r.metaKey)||r.ctrlKey&&r.altKey){if(r.char&&1=r)return{node:s,offset:r-t};t=u}e:{for(;s;){if(s.nextSibling){s=s.nextSibling;break e}s=s.parentNode}s=void 0}s=My(s)}}function Ny(t,r){return t&&r?t===r?!0:t&&t.nodeType===3?!1:r&&r.nodeType===3?Ny(t,r.parentNode):"contains"in t?t.contains(r):t.compareDocumentPosition?!!(t.compareDocumentPosition(r)&16):!1:!1}function Iy(t){t=t!=null&&t.ownerDocument!=null&&t.ownerDocument.defaultView!=null?t.ownerDocument.defaultView:window;for(var r=ja(t.document);r instanceof t.HTMLIFrameElement;){try{var s=typeof r.contentWindow.location.href=="string"}catch{s=!1}if(s)t=r.contentWindow;else break;r=ja(t.document)}return r}function th(t){var r=t&&t.nodeName&&t.nodeName.toLowerCase();return r&&(r==="input"&&(t.type==="text"||t.type==="search"||t.type==="tel"||t.type==="url"||t.type==="password")||r==="textarea"||t.contentEditable==="true")}var nx=kr&&"documentMode"in document&&11>=document.documentMode,As=null,nh=null,nl=null,rh=!1;function Hy(t,r,s){var u=s.window===s?s.document:s.nodeType===9?s:s.ownerDocument;rh||As==null||As!==ja(u)||(u=As,"selectionStart"in u&&th(u)?u={start:u.selectionStart,end:u.selectionEnd}:(u=(u.ownerDocument&&u.ownerDocument.defaultView||window).getSelection(),u={anchorNode:u.anchorNode,anchorOffset:u.anchorOffset,focusNode:u.focusNode,focusOffset:u.focusOffset}),nl&&tl(nl,u)||(nl=u,u=Uu(nh,"onSelect"),0>=C,h-=C,Fr=1<<32-un(r)+h|s<Qe?(ct=Ne,Ne=null):ct=Ne.sibling;var yt=Q(K,Ne,Y[Qe],oe);if(yt===null){Ne===null&&(Ne=ct);break}t&&Ne&&yt.alternate===null&&r(K,Ne),G=p(yt,G,Qe),mt===null?Pe=yt:mt.sibling=yt,mt=yt,Ne=ct}if(Qe===Y.length)return s(K,Ne),ft&&ca(K,Qe),Pe;if(Ne===null){for(;QeQe?(ct=Ne,Ne=null):ct=Ne.sibling;var di=Q(K,Ne,yt.value,oe);if(di===null){Ne===null&&(Ne=ct);break}t&&Ne&&di.alternate===null&&r(K,Ne),G=p(di,G,Qe),mt===null?Pe=di:mt.sibling=di,mt=di,Ne=ct}if(yt.done)return s(K,Ne),ft&&ca(K,Qe),Pe;if(Ne===null){for(;!yt.done;Qe++,yt=Y.next())yt=le(K,yt.value,oe),yt!==null&&(G=p(yt,G,Qe),mt===null?Pe=yt:mt.sibling=yt,mt=yt);return ft&&ca(K,Qe),Pe}for(Ne=u(Ne);!yt.done;Qe++,yt=Y.next())yt=ne(Ne,K,Qe,yt.value,oe),yt!==null&&(t&&yt.alternate!==null&&Ne.delete(yt.key===null?Qe:yt.key),G=p(yt,G,Qe),mt===null?Pe=yt:mt.sibling=yt,mt=yt);return t&&Ne.forEach(function(CT){return r(K,CT)}),ft&&ca(K,Qe),Pe}function Dt(K,G,Y,oe){if(typeof Y=="object"&&Y!==null&&Y.type===A&&Y.key===null&&(Y=Y.props.children),typeof Y=="object"&&Y!==null){switch(Y.$$typeof){case v:e:{for(var Pe=Y.key;G!==null;){if(G.key===Pe){if(Pe=Y.type,Pe===A){if(G.tag===7){s(K,G.sibling),oe=h(G,Y.props.children),oe.return=K,K=oe;break e}}else if(G.elementType===Pe||typeof Pe=="object"&&Pe!==null&&Pe.$$typeof===W&&qi(Pe)===G.type){s(K,G.sibling),oe=h(G,Y.props),ll(oe,Y),oe.return=K,K=oe;break e}s(K,G);break}else r(K,G);G=G.sibling}Y.type===A?(oe=ji(Y.props.children,K.mode,oe,Y.key),oe.return=K,K=oe):(oe=ru(Y.type,Y.key,Y.props,null,K.mode,oe),ll(oe,Y),oe.return=K,K=oe)}return C(K);case _:e:{for(Pe=Y.key;G!==null;){if(G.key===Pe)if(G.tag===4&&G.stateNode.containerInfo===Y.containerInfo&&G.stateNode.implementation===Y.implementation){s(K,G.sibling),oe=h(G,Y.children||[]),oe.return=K,K=oe;break e}else{s(K,G);break}else r(K,G);G=G.sibling}oe=uh(Y,K.mode,oe),oe.return=K,K=oe}return C(K);case W:return Y=qi(Y),Dt(K,G,Y,oe)}if(wt(Y))return De(K,G,Y,oe);if(Ue(Y)){if(Pe=Ue(Y),typeof Pe!="function")throw Error(a(150));return Y=Pe.call(Y),qe(K,G,Y,oe)}if(typeof Y.then=="function")return Dt(K,G,uu(Y),oe);if(Y.$$typeof===T)return Dt(K,G,su(K,Y),oe);du(K,Y)}return typeof Y=="string"&&Y!==""||typeof Y=="number"||typeof Y=="bigint"?(Y=""+Y,G!==null&&G.tag===6?(s(K,G.sibling),oe=h(G,Y),oe.return=K,K=oe):(s(K,G),oe=ch(Y,K.mode,oe),oe.return=K,K=oe),C(K)):s(K,G)}return function(K,G,Y,oe){try{ol=0;var Pe=Dt(K,G,Y,oe);return Is=null,Pe}catch(Ne){if(Ne===Ns||Ne===lu)throw Ne;var mt=Xn(29,Ne,null,K.mode);return mt.lanes=oe,mt.return=K,mt}}}var Vi=sb(!0),ob=sb(!1),Wa=!1;function Ch(t){t.updateQueue={baseState:t.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function Eh(t,r){t=t.updateQueue,r.updateQueue===t&&(r.updateQueue={baseState:t.baseState,firstBaseUpdate:t.firstBaseUpdate,lastBaseUpdate:t.lastBaseUpdate,shared:t.shared,callbacks:null})}function Ya(t){return{lane:t,tag:0,payload:null,callback:null,next:null}}function Xa(t,r,s){var u=t.updateQueue;if(u===null)return null;if(u=u.shared,(St&2)!==0){var h=u.pending;return h===null?r.next=r:(r.next=h.next,h.next=r),u.pending=r,r=nu(t),Gy(t,null,s),r}return tu(t,u,r,s),nu(t)}function cl(t,r,s){if(r=r.updateQueue,r!==null&&(r=r.shared,(s&4194048)!==0)){var u=r.lanes;u&=t.pendingLanes,s|=u,r.lanes=s,Se(t,s)}}function _h(t,r){var s=t.updateQueue,u=t.alternate;if(u!==null&&(u=u.updateQueue,s===u)){var h=null,p=null;if(s=s.firstBaseUpdate,s!==null){do{var C={lane:s.lane,tag:s.tag,payload:s.payload,callback:null,next:null};p===null?h=p=C:p=p.next=C,s=s.next}while(s!==null);p===null?h=p=r:p=p.next=r}else h=p=r;s={baseState:u.baseState,firstBaseUpdate:h,lastBaseUpdate:p,shared:u.shared,callbacks:u.callbacks},t.updateQueue=s;return}t=s.lastBaseUpdate,t===null?s.firstBaseUpdate=r:t.next=r,s.lastBaseUpdate=r}var Ah=!1;function ul(){if(Ah){var t=$s;if(t!==null)throw t}}function dl(t,r,s,u){Ah=!1;var h=t.updateQueue;Wa=!1;var p=h.firstBaseUpdate,C=h.lastBaseUpdate,k=h.shared.pending;if(k!==null){h.shared.pending=null;var U=k,X=U.next;U.next=null,C===null?p=X:C.next=X,C=U;var ae=t.alternate;ae!==null&&(ae=ae.updateQueue,k=ae.lastBaseUpdate,k!==C&&(k===null?ae.firstBaseUpdate=X:k.next=X,ae.lastBaseUpdate=U))}if(p!==null){var le=h.baseState;C=0,ae=X=U=null,k=p;do{var Q=k.lane&-536870913,ne=Q!==k.lane;if(ne?(lt&Q)===Q:(u&Q)===Q){Q!==0&&Q===Ms&&(Ah=!0),ae!==null&&(ae=ae.next={lane:0,tag:k.tag,payload:k.payload,callback:null,next:null});e:{var De=t,qe=k;Q=r;var Dt=s;switch(qe.tag){case 1:if(De=qe.payload,typeof De=="function"){le=De.call(Dt,le,Q);break e}le=De;break e;case 3:De.flags=De.flags&-65537|128;case 0:if(De=qe.payload,Q=typeof De=="function"?De.call(Dt,le,Q):De,Q==null)break e;le=y({},le,Q);break e;case 2:Wa=!0}}Q=k.callback,Q!==null&&(t.flags|=64,ne&&(t.flags|=8192),ne=h.callbacks,ne===null?h.callbacks=[Q]:ne.push(Q))}else ne={lane:Q,tag:k.tag,payload:k.payload,callback:k.callback,next:null},ae===null?(X=ae=ne,U=le):ae=ae.next=ne,C|=Q;if(k=k.next,k===null){if(k=h.shared.pending,k===null)break;ne=k,k=ne.next,ne.next=null,h.lastBaseUpdate=ne,h.shared.pending=null}}while(!0);ae===null&&(U=le),h.baseState=U,h.firstBaseUpdate=X,h.lastBaseUpdate=ae,p===null&&(h.shared.lanes=0),ti|=C,t.lanes=C,t.memoizedState=le}}function lb(t,r){if(typeof t!="function")throw Error(a(191,t));t.call(r)}function cb(t,r){var s=t.callbacks;if(s!==null)for(t.callbacks=null,t=0;tp?p:8;var C=J.T,k={};J.T=k,Gh(t,!1,r,s);try{var U=h(),X=J.S;if(X!==null&&X(k,U),U!==null&&typeof U=="object"&&typeof U.then=="function"){var ae=dx(U,u);gl(t,r,ae,tr(t))}else gl(t,r,u,tr(t))}catch(le){gl(t,r,{then:function(){},status:"rejected",reason:le},tr())}finally{ue.p=p,C!==null&&k.types!==null&&(C.types=k.types),J.T=C}}function yx(){}function Bh(t,r,s,u){if(t.tag!==5)throw Error(a(476));var h=Pb(t).queue;Ub(t,h,r,Me,s===null?yx:function(){return Bb(t),s(u)})}function Pb(t){var r=t.memoizedState;if(r!==null)return r;r={memoizedState:Me,baseState:Me,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:ha,lastRenderedState:Me},next:null};var s={};return r.next={memoizedState:s,baseState:s,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:ha,lastRenderedState:s},next:null},t.memoizedState=r,t=t.alternate,t!==null&&(t.memoizedState=r),r}function Bb(t){var r=Pb(t);r.next===null&&(r=t.alternate.memoizedState),gl(t,r.next.queue,{},tr())}function qh(){return wn(Ol)}function qb(){return Jt().memoizedState}function Gb(){return Jt().memoizedState}function bx(t){for(var r=t.return;r!==null;){switch(r.tag){case 24:case 3:var s=tr();t=Ya(s);var u=Xa(r,t,s);u!==null&&(Gn(u,r,s),cl(u,r,s)),r={cache:bh()},t.payload=r;return}r=r.return}}function vx(t,r,s){var u=tr();s={lane:u,revertLane:0,gesture:null,action:s,hasEagerState:!1,eagerState:null,next:null},wu(t)?Fb(r,s):(s=oh(t,r,s,u),s!==null&&(Gn(s,t,u),Kb(s,r,u)))}function Vb(t,r,s){var u=tr();gl(t,r,s,u)}function gl(t,r,s,u){var h={lane:u,revertLane:0,gesture:null,action:s,hasEagerState:!1,eagerState:null,next:null};if(wu(t))Fb(r,h);else{var p=t.alternate;if(t.lanes===0&&(p===null||p.lanes===0)&&(p=r.lastRenderedReducer,p!==null))try{var C=r.lastRenderedState,k=p(C,s);if(h.hasEagerState=!0,h.eagerState=k,Yn(k,C))return tu(t,r,h,0),Lt===null&&eu(),!1}catch{}if(s=oh(t,r,h,u),s!==null)return Gn(s,t,u),Kb(s,r,u),!0}return!1}function Gh(t,r,s,u){if(u={lane:2,revertLane:Cg(),gesture:null,action:u,hasEagerState:!1,eagerState:null,next:null},wu(t)){if(r)throw Error(a(479))}else r=oh(t,s,u,2),r!==null&&Gn(r,t,2)}function wu(t){var r=t.alternate;return t===Je||r!==null&&r===Je}function Fb(t,r){js=gu=!0;var s=t.pending;s===null?r.next=r:(r.next=s.next,s.next=r),t.pending=r}function Kb(t,r,s){if((s&4194048)!==0){var u=r.lanes;u&=t.pendingLanes,s|=u,r.lanes=s,Se(t,s)}}var pl={readContext:wn,use:yu,useCallback:Kt,useContext:Kt,useEffect:Kt,useImperativeHandle:Kt,useLayoutEffect:Kt,useInsertionEffect:Kt,useMemo:Kt,useReducer:Kt,useRef:Kt,useState:Kt,useDebugValue:Kt,useDeferredValue:Kt,useTransition:Kt,useSyncExternalStore:Kt,useId:Kt,useHostTransitionStatus:Kt,useFormState:Kt,useActionState:Kt,useOptimistic:Kt,useMemoCache:Kt,useCacheRefresh:Kt};pl.useEffectEvent=Kt;var Wb={readContext:wn,use:yu,useCallback:function(t,r){return Mn().memoizedState=[t,r===void 0?null:r],t},useContext:wn,useEffect:Ob,useImperativeHandle:function(t,r,s){s=s!=null?s.concat([t]):null,vu(4194308,4,Nb.bind(null,r,t),s)},useLayoutEffect:function(t,r){return vu(4194308,4,t,r)},useInsertionEffect:function(t,r){vu(4,2,t,r)},useMemo:function(t,r){var s=Mn();r=r===void 0?null:r;var u=t();if(Fi){Tn(!0);try{t()}finally{Tn(!1)}}return s.memoizedState=[u,r],u},useReducer:function(t,r,s){var u=Mn();if(s!==void 0){var h=s(r);if(Fi){Tn(!0);try{s(r)}finally{Tn(!1)}}}else h=r;return u.memoizedState=u.baseState=h,t={pending:null,lanes:0,dispatch:null,lastRenderedReducer:t,lastRenderedState:h},u.queue=t,t=t.dispatch=vx.bind(null,Je,t),[u.memoizedState,t]},useRef:function(t){var r=Mn();return t={current:t},r.memoizedState=t},useState:function(t){t=Hh(t);var r=t.queue,s=Vb.bind(null,Je,r);return r.dispatch=s,[t.memoizedState,s]},useDebugValue:Uh,useDeferredValue:function(t,r){var s=Mn();return Ph(s,t,r)},useTransition:function(){var t=Hh(!1);return t=Ub.bind(null,Je,t.queue,!0,!1),Mn().memoizedState=t,[!1,t]},useSyncExternalStore:function(t,r,s){var u=Je,h=Mn();if(ft){if(s===void 0)throw Error(a(407));s=s()}else{if(s=r(),Lt===null)throw Error(a(349));(lt&127)!==0||pb(u,r,s)}h.memoizedState=s;var p={value:s,getSnapshot:r};return h.queue=p,Ob(yb.bind(null,u,p,t),[t]),u.flags|=2048,Us(9,{destroy:void 0},mb.bind(null,u,p,s,r),null),s},useId:function(){var t=Mn(),r=Lt.identifierPrefix;if(ft){var s=Kr,u=Fr;s=(u&~(1<<32-un(u)-1)).toString(32)+s,r="_"+r+"R_"+s,s=pu++,0<\/script>",p=p.removeChild(p.firstChild);break;case"select":p=typeof u.is=="string"?C.createElement("select",{is:u.is}):C.createElement("select"),u.multiple?p.multiple=!0:u.size&&(p.size=u.size);break;default:p=typeof u.is=="string"?C.createElement(h,{is:u.is}):C.createElement(h)}}p[Ut]=r,p[Rn]=u;e:for(C=r.child;C!==null;){if(C.tag===5||C.tag===6)p.appendChild(C.stateNode);else if(C.tag!==4&&C.tag!==27&&C.child!==null){C.child.return=C,C=C.child;continue}if(C===r)break e;for(;C.sibling===null;){if(C.return===null||C.return===r)break e;C=C.return}C.sibling.return=C.return,C=C.sibling}r.stateNode=p;e:switch(En(p,h,u),h){case"button":case"input":case"select":case"textarea":u=!!u.autoFocus;break e;case"img":u=!0;break e;default:u=!1}u&&pa(r)}}return Nt(r),ag(r,r.type,t===null?null:t.memoizedProps,r.pendingProps,s),null;case 6:if(t&&r.stateNode!=null)t.memoizedProps!==u&&pa(r);else{if(typeof u!="string"&&r.stateNode===null)throw Error(a(166));if(t=ee.current,Os(r)){if(t=r.stateNode,s=r.memoizedProps,u=null,h=Sn,h!==null)switch(h.tag){case 27:case 5:u=h.memoizedProps}t[Ut]=r,t=!!(t.nodeValue===s||u!==null&&u.suppressHydrationWarning===!0||hS(t.nodeValue,s)),t||Fa(r,!0)}else t=Pu(t).createTextNode(u),t[Ut]=r,r.stateNode=t}return Nt(r),null;case 31:if(s=r.memoizedState,t===null||t.memoizedState!==null){if(u=Os(r),s!==null){if(t===null){if(!u)throw Error(a(318));if(t=r.memoizedState,t=t!==null?t.dehydrated:null,!t)throw Error(a(557));t[Ut]=r}else zi(),(r.flags&128)===0&&(r.memoizedState=null),r.flags|=4;Nt(r),t=!1}else s=gh(),t!==null&&t.memoizedState!==null&&(t.memoizedState.hydrationErrors=s),t=!0;if(!t)return r.flags&256?(Qn(r),r):(Qn(r),null);if((r.flags&128)!==0)throw Error(a(558))}return Nt(r),null;case 13:if(u=r.memoizedState,t===null||t.memoizedState!==null&&t.memoizedState.dehydrated!==null){if(h=Os(r),u!==null&&u.dehydrated!==null){if(t===null){if(!h)throw Error(a(318));if(h=r.memoizedState,h=h!==null?h.dehydrated:null,!h)throw Error(a(317));h[Ut]=r}else zi(),(r.flags&128)===0&&(r.memoizedState=null),r.flags|=4;Nt(r),h=!1}else h=gh(),t!==null&&t.memoizedState!==null&&(t.memoizedState.hydrationErrors=h),h=!0;if(!h)return r.flags&256?(Qn(r),r):(Qn(r),null)}return Qn(r),(r.flags&128)!==0?(r.lanes=s,r):(s=u!==null,t=t!==null&&t.memoizedState!==null,s&&(u=r.child,h=null,u.alternate!==null&&u.alternate.memoizedState!==null&&u.alternate.memoizedState.cachePool!==null&&(h=u.alternate.memoizedState.cachePool.pool),p=null,u.memoizedState!==null&&u.memoizedState.cachePool!==null&&(p=u.memoizedState.cachePool.pool),p!==h&&(u.flags|=2048)),s!==t&&s&&(r.child.flags|=8192),xu(r,r.updateQueue),Nt(r),null);case 4:return ke(),t===null&&xg(r.stateNode.containerInfo),Nt(r),null;case 10:return da(r.type),Nt(r),null;case 19:if(R(Xt),u=r.memoizedState,u===null)return Nt(r),null;if(h=(r.flags&128)!==0,p=u.rendering,p===null)if(h)yl(u,!1);else{if(Wt!==0||t!==null&&(t.flags&128)!==0)for(t=r.child;t!==null;){if(p=hu(t),p!==null){for(r.flags|=128,yl(u,!1),t=p.updateQueue,r.updateQueue=t,xu(r,t),r.subtreeFlags=0,t=s,s=r.child;s!==null;)Vy(s,t),s=s.sibling;return de(Xt,Xt.current&1|2),ft&&ca(r,u.treeForkCount),r.child}t=t.sibling}u.tail!==null&&Ft()>Ou&&(r.flags|=128,h=!0,yl(u,!1),r.lanes=4194304)}else{if(!h)if(t=hu(p),t!==null){if(r.flags|=128,h=!0,t=t.updateQueue,r.updateQueue=t,xu(r,t),yl(u,!0),u.tail===null&&u.tailMode==="hidden"&&!p.alternate&&!ft)return Nt(r),null}else 2*Ft()-u.renderingStartTime>Ou&&s!==536870912&&(r.flags|=128,h=!0,yl(u,!1),r.lanes=4194304);u.isBackwards?(p.sibling=r.child,r.child=p):(t=u.last,t!==null?t.sibling=p:r.child=p,u.last=p)}return u.tail!==null?(t=u.tail,u.rendering=t,u.tail=t.sibling,u.renderingStartTime=Ft(),t.sibling=null,s=Xt.current,de(Xt,h?s&1|2:s&1),ft&&ca(r,u.treeForkCount),t):(Nt(r),null);case 22:case 23:return Qn(r),Th(),u=r.memoizedState!==null,t!==null?t.memoizedState!==null!==u&&(r.flags|=8192):u&&(r.flags|=8192),u?(s&536870912)!==0&&(r.flags&128)===0&&(Nt(r),r.subtreeFlags&6&&(r.flags|=8192)):Nt(r),s=r.updateQueue,s!==null&&xu(r,s.retryQueue),s=null,t!==null&&t.memoizedState!==null&&t.memoizedState.cachePool!==null&&(s=t.memoizedState.cachePool.pool),u=null,r.memoizedState!==null&&r.memoizedState.cachePool!==null&&(u=r.memoizedState.cachePool.pool),u!==s&&(r.flags|=2048),t!==null&&R(Bi),null;case 24:return s=null,t!==null&&(s=t.memoizedState.cache),r.memoizedState.cache!==s&&(r.flags|=2048),da(en),Nt(r),null;case 25:return null;case 30:return null}throw Error(a(156,r.tag))}function _x(t,r){switch(fh(r),r.tag){case 1:return t=r.flags,t&65536?(r.flags=t&-65537|128,r):null;case 3:return da(en),ke(),t=r.flags,(t&65536)!==0&&(t&128)===0?(r.flags=t&-65537|128,r):null;case 26:case 27:case 5:return Ct(r),null;case 31:if(r.memoizedState!==null){if(Qn(r),r.alternate===null)throw Error(a(340));zi()}return t=r.flags,t&65536?(r.flags=t&-65537|128,r):null;case 13:if(Qn(r),t=r.memoizedState,t!==null&&t.dehydrated!==null){if(r.alternate===null)throw Error(a(340));zi()}return t=r.flags,t&65536?(r.flags=t&-65537|128,r):null;case 19:return R(Xt),null;case 4:return ke(),null;case 10:return da(r.type),null;case 22:case 23:return Qn(r),Th(),t!==null&&R(Bi),t=r.flags,t&65536?(r.flags=t&-65537|128,r):null;case 24:return da(en),null;case 25:return null;default:return null}}function bv(t,r){switch(fh(r),r.tag){case 3:da(en),ke();break;case 26:case 27:case 5:Ct(r);break;case 4:ke();break;case 31:r.memoizedState!==null&&Qn(r);break;case 13:Qn(r);break;case 19:R(Xt);break;case 10:da(r.type);break;case 22:case 23:Qn(r),Th(),t!==null&&R(Bi);break;case 24:da(en)}}function bl(t,r){try{var s=r.updateQueue,u=s!==null?s.lastEffect:null;if(u!==null){var h=u.next;s=h;do{if((s.tag&t)===t){u=void 0;var p=s.create,C=s.inst;u=p(),C.destroy=u}s=s.next}while(s!==h)}}catch(k){xt(r,r.return,k)}}function Za(t,r,s){try{var u=r.updateQueue,h=u!==null?u.lastEffect:null;if(h!==null){var p=h.next;u=p;do{if((u.tag&t)===t){var C=u.inst,k=C.destroy;if(k!==void 0){C.destroy=void 0,h=r;var U=s,X=k;try{X()}catch(ae){xt(h,U,ae)}}}u=u.next}while(u!==p)}}catch(ae){xt(r,r.return,ae)}}function vv(t){var r=t.updateQueue;if(r!==null){var s=t.stateNode;try{cb(r,s)}catch(u){xt(t,t.return,u)}}}function Sv(t,r,s){s.props=Ki(t.type,t.memoizedProps),s.state=t.memoizedState;try{s.componentWillUnmount()}catch(u){xt(t,r,u)}}function vl(t,r){try{var s=t.ref;if(s!==null){switch(t.tag){case 26:case 27:case 5:var u=t.stateNode;break;case 30:u=t.stateNode;break;default:u=t.stateNode}typeof s=="function"?t.refCleanup=s(u):s.current=u}}catch(h){xt(t,r,h)}}function Wr(t,r){var s=t.ref,u=t.refCleanup;if(s!==null)if(typeof u=="function")try{u()}catch(h){xt(t,r,h)}finally{t.refCleanup=null,t=t.alternate,t!=null&&(t.refCleanup=null)}else if(typeof s=="function")try{s(null)}catch(h){xt(t,r,h)}else s.current=null}function wv(t){var r=t.type,s=t.memoizedProps,u=t.stateNode;try{e:switch(r){case"button":case"input":case"select":case"textarea":s.autoFocus&&u.focus();break e;case"img":s.src?u.src=s.src:s.srcSet&&(u.srcset=s.srcSet)}}catch(h){xt(t,t.return,h)}}function ig(t,r,s){try{var u=t.stateNode;Fx(u,t.type,s,r),u[Rn]=r}catch(h){xt(t,t.return,h)}}function Cv(t){return t.tag===5||t.tag===3||t.tag===26||t.tag===27&&si(t.type)||t.tag===4}function sg(t){e:for(;;){for(;t.sibling===null;){if(t.return===null||Cv(t.return))return null;t=t.return}for(t.sibling.return=t.return,t=t.sibling;t.tag!==5&&t.tag!==6&&t.tag!==18;){if(t.tag===27&&si(t.type)||t.flags&2||t.child===null||t.tag===4)continue e;t.child.return=t,t=t.child}if(!(t.flags&2))return t.stateNode}}function og(t,r,s){var u=t.tag;if(u===5||u===6)t=t.stateNode,r?(s.nodeType===9?s.body:s.nodeName==="HTML"?s.ownerDocument.body:s).insertBefore(t,r):(r=s.nodeType===9?s.body:s.nodeName==="HTML"?s.ownerDocument.body:s,r.appendChild(t),s=s._reactRootContainer,s!=null||r.onclick!==null||(r.onclick=Rr));else if(u!==4&&(u===27&&si(t.type)&&(s=t.stateNode,r=null),t=t.child,t!==null))for(og(t,r,s),t=t.sibling;t!==null;)og(t,r,s),t=t.sibling}function Tu(t,r,s){var u=t.tag;if(u===5||u===6)t=t.stateNode,r?s.insertBefore(t,r):s.appendChild(t);else if(u!==4&&(u===27&&si(t.type)&&(s=t.stateNode),t=t.child,t!==null))for(Tu(t,r,s),t=t.sibling;t!==null;)Tu(t,r,s),t=t.sibling}function Ev(t){var r=t.stateNode,s=t.memoizedProps;try{for(var u=t.type,h=r.attributes;h.length;)r.removeAttributeNode(h[0]);En(r,u,s),r[Ut]=t,r[Rn]=s}catch(p){xt(t,t.return,p)}}var ma=!1,rn=!1,lg=!1,_v=typeof WeakSet=="function"?WeakSet:Set,pn=null;function Ax(t,r){if(t=t.containerInfo,kg=Wu,t=Iy(t),th(t)){if("selectionStart"in t)var s={start:t.selectionStart,end:t.selectionEnd};else e:{s=(s=t.ownerDocument)&&s.defaultView||window;var u=s.getSelection&&s.getSelection();if(u&&u.rangeCount!==0){s=u.anchorNode;var h=u.anchorOffset,p=u.focusNode;u=u.focusOffset;try{s.nodeType,p.nodeType}catch{s=null;break e}var C=0,k=-1,U=-1,X=0,ae=0,le=t,Q=null;t:for(;;){for(var ne;le!==s||h!==0&&le.nodeType!==3||(k=C+h),le!==p||u!==0&&le.nodeType!==3||(U=C+u),le.nodeType===3&&(C+=le.nodeValue.length),(ne=le.firstChild)!==null;)Q=le,le=ne;for(;;){if(le===t)break t;if(Q===s&&++X===h&&(k=C),Q===p&&++ae===u&&(U=C),(ne=le.nextSibling)!==null)break;le=Q,Q=le.parentNode}le=ne}s=k===-1||U===-1?null:{start:k,end:U}}else s=null}s=s||{start:0,end:0}}else s=null;for(Dg={focusedElem:t,selectionRange:s},Wu=!1,pn=r;pn!==null;)if(r=pn,t=r.child,(r.subtreeFlags&1028)!==0&&t!==null)t.return=r,pn=t;else for(;pn!==null;){switch(r=pn,p=r.alternate,t=r.flags,r.tag){case 0:if((t&4)!==0&&(t=r.updateQueue,t=t!==null?t.events:null,t!==null))for(s=0;s title"))),En(p,u,s),p[Ut]=t,Zt(p),u=p;break e;case"link":var C=DS("link","href",h).get(u+(s.href||""));if(C){for(var k=0;kDt&&(C=Dt,Dt=qe,qe=C);var K=$y(k,qe),G=$y(k,Dt);if(K&&G&&(ne.rangeCount!==1||ne.anchorNode!==K.node||ne.anchorOffset!==K.offset||ne.focusNode!==G.node||ne.focusOffset!==G.offset)){var Y=le.createRange();Y.setStart(K.node,K.offset),ne.removeAllRanges(),qe>Dt?(ne.addRange(Y),ne.extend(G.node,G.offset)):(Y.setEnd(G.node,G.offset),ne.addRange(Y))}}}}for(le=[],ne=k;ne=ne.parentNode;)ne.nodeType===1&&le.push({element:ne,left:ne.scrollLeft,top:ne.scrollTop});for(typeof k.focus=="function"&&k.focus(),k=0;ks?32:s,J.T=null,s=pg,pg=null;var p=ri,C=wa;if(fn=0,Vs=ri=null,wa=0,(St&6)!==0)throw Error(a(331));var k=St;if(St|=4,Nv(p.current),Lv(p,p.current,C,s),St=k,Al(0,!1),cn&&typeof cn.onPostCommitFiberRoot=="function")try{cn.onPostCommitFiberRoot(sr,p)}catch{}return!0}finally{ue.p=h,J.T=u,Zv(t,r)}}function tS(t,r,s){r=cr(s,r),r=Wh(t.stateNode,r,2),t=Xa(t,r,2),t!==null&&(et(t,2),Yr(t))}function xt(t,r,s){if(t.tag===3)tS(t,t,s);else for(;r!==null;){if(r.tag===3){tS(r,t,s);break}else if(r.tag===1){var u=r.stateNode;if(typeof r.type.getDerivedStateFromError=="function"||typeof u.componentDidCatch=="function"&&(ni===null||!ni.has(u))){t=cr(s,t),s=nv(2),u=Xa(r,s,2),u!==null&&(rv(s,u,r,t),et(u,2),Yr(u));break}}r=r.return}}function vg(t,r,s){var u=t.pingCache;if(u===null){u=t.pingCache=new Rx;var h=new Set;u.set(r,h)}else h=u.get(r),h===void 0&&(h=new Set,u.set(r,h));h.has(s)||(dg=!0,h.add(s),t=Mx.bind(null,t,r,s),r.then(t,t))}function Mx(t,r,s){var u=t.pingCache;u!==null&&u.delete(r),t.pingedLanes|=t.suspendedLanes&s,t.warmLanes&=~s,Lt===t&&(lt&s)===s&&(Wt===4||Wt===3&&(lt&62914560)===lt&&300>Ft()-Du?(St&2)===0&&Fs(t,0):fg|=s,Gs===lt&&(Gs=0)),Yr(t)}function nS(t,r){r===0&&(r=$e()),t=Hi(t,r),t!==null&&(et(t,r),Yr(t))}function $x(t){var r=t.memoizedState,s=0;r!==null&&(s=r.retryLane),nS(t,s)}function Nx(t,r){var s=0;switch(t.tag){case 31:case 13:var u=t.stateNode,h=t.memoizedState;h!==null&&(s=h.retryLane);break;case 19:u=t.stateNode;break;case 22:u=t.stateNode._retryCache;break;default:throw Error(a(314))}u!==null&&u.delete(r),nS(t,s)}function Ix(t,r){return Vt(t,r)}var Hu=null,Ws=null,Sg=!1,ju=!1,wg=!1,ii=0;function Yr(t){t!==Ws&&t.next===null&&(Ws===null?Hu=Ws=t:Ws=Ws.next=t),ju=!0,Sg||(Sg=!0,jx())}function Al(t,r){if(!wg&&ju){wg=!0;do for(var s=!1,u=Hu;u!==null;){if(t!==0){var h=u.pendingLanes;if(h===0)var p=0;else{var C=u.suspendedLanes,k=u.pingedLanes;p=(1<<31-un(42|t)+1)-1,p&=h&~(C&~k),p=p&201326741?p&201326741|1:p?p|2:0}p!==0&&(s=!0,sS(u,p))}else p=lt,p=Ae(u,u===Lt?p:0,u.cancelPendingCommit!==null||u.timeoutHandle!==-1),(p&3)===0||Ke(u,p)||(s=!0,sS(u,p));u=u.next}while(s);wg=!1}}function Hx(){rS()}function rS(){ju=Sg=!1;var t=0;ii!==0&&Wx()&&(t=ii);for(var r=Ft(),s=null,u=Hu;u!==null;){var h=u.next,p=aS(u,r);p===0?(u.next=null,s===null?Hu=h:s.next=h,h===null&&(Ws=s)):(s=u,(t!==0||(p&3)!==0)&&(ju=!0)),u=h}fn!==0&&fn!==5||Al(t),ii!==0&&(ii=0)}function aS(t,r){for(var s=t.suspendedLanes,u=t.pingedLanes,h=t.expirationTimes,p=t.pendingLanes&-62914561;0k)break;var ae=U.transferSize,le=U.initiatorType;ae&&gS(le)&&(U=U.responseEnd,C+=ae*(U"u"?null:document;function xS(t,r,s){var u=Ys;if(u&&typeof r=="string"&&r){var h=zn(r);h='link[rel="'+t+'"][href="'+h+'"]',typeof s=="string"&&(h+='[crossorigin="'+s+'"]'),AS.has(h)||(AS.add(h),t={rel:t,crossOrigin:s,href:r},u.querySelector(h)===null&&(r=u.createElement("link"),En(r,"link",t),Zt(r),u.head.appendChild(r)))}}function rT(t){Ca.D(t),xS("dns-prefetch",t,null)}function aT(t,r){Ca.C(t,r),xS("preconnect",t,r)}function iT(t,r,s){Ca.L(t,r,s);var u=Ys;if(u&&t&&r){var h='link[rel="preload"][as="'+zn(r)+'"]';r==="image"&&s&&s.imageSrcSet?(h+='[imagesrcset="'+zn(s.imageSrcSet)+'"]',typeof s.imageSizes=="string"&&(h+='[imagesizes="'+zn(s.imageSizes)+'"]')):h+='[href="'+zn(t)+'"]';var p=h;switch(r){case"style":p=Xs(t);break;case"script":p=Js(t)}pr.has(p)||(t=y({rel:"preload",href:r==="image"&&s&&s.imageSrcSet?void 0:t,as:r},s),pr.set(p,t),u.querySelector(h)!==null||r==="style"&&u.querySelector(kl(p))||r==="script"&&u.querySelector(Dl(p))||(r=u.createElement("link"),En(r,"link",t),Zt(r),u.head.appendChild(r)))}}function sT(t,r){Ca.m(t,r);var s=Ys;if(s&&t){var u=r&&typeof r.as=="string"?r.as:"script",h='link[rel="modulepreload"][as="'+zn(u)+'"][href="'+zn(t)+'"]',p=h;switch(u){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":p=Js(t)}if(!pr.has(p)&&(t=y({rel:"modulepreload",href:t},r),pr.set(p,t),s.querySelector(h)===null)){switch(u){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":if(s.querySelector(Dl(p)))return}u=s.createElement("link"),En(u,"link",t),Zt(u),s.head.appendChild(u)}}}function oT(t,r,s){Ca.S(t,r,s);var u=Ys;if(u&&t){var h=Ia(u).hoistableStyles,p=Xs(t);r=r||"default";var C=h.get(p);if(!C){var k={loading:0,preload:null};if(C=u.querySelector(kl(p)))k.loading=5;else{t=y({rel:"stylesheet",href:t,"data-precedence":r},s),(s=pr.get(p))&&Hg(t,s);var U=C=u.createElement("link");Zt(U),En(U,"link",t),U._p=new Promise(function(X,ae){U.onload=X,U.onerror=ae}),U.addEventListener("load",function(){k.loading|=1}),U.addEventListener("error",function(){k.loading|=2}),k.loading|=4,qu(C,r,u)}C={type:"stylesheet",instance:C,count:1,state:k},h.set(p,C)}}}function lT(t,r){Ca.X(t,r);var s=Ys;if(s&&t){var u=Ia(s).hoistableScripts,h=Js(t),p=u.get(h);p||(p=s.querySelector(Dl(h)),p||(t=y({src:t,async:!0},r),(r=pr.get(h))&&jg(t,r),p=s.createElement("script"),Zt(p),En(p,"link",t),s.head.appendChild(p)),p={type:"script",instance:p,count:1,state:null},u.set(h,p))}}function cT(t,r){Ca.M(t,r);var s=Ys;if(s&&t){var u=Ia(s).hoistableScripts,h=Js(t),p=u.get(h);p||(p=s.querySelector(Dl(h)),p||(t=y({src:t,async:!0,type:"module"},r),(r=pr.get(h))&&jg(t,r),p=s.createElement("script"),Zt(p),En(p,"link",t),s.head.appendChild(p)),p={type:"script",instance:p,count:1,state:null},u.set(h,p))}}function TS(t,r,s,u){var h=(h=ee.current)?Bu(h):null;if(!h)throw Error(a(446));switch(t){case"meta":case"title":return null;case"style":return typeof s.precedence=="string"&&typeof s.href=="string"?(r=Xs(s.href),s=Ia(h).hoistableStyles,u=s.get(r),u||(u={type:"style",instance:null,count:0,state:null},s.set(r,u)),u):{type:"void",instance:null,count:0,state:null};case"link":if(s.rel==="stylesheet"&&typeof s.href=="string"&&typeof s.precedence=="string"){t=Xs(s.href);var p=Ia(h).hoistableStyles,C=p.get(t);if(C||(h=h.ownerDocument||h,C={type:"stylesheet",instance:null,count:0,state:{loading:0,preload:null}},p.set(t,C),(p=h.querySelector(kl(t)))&&!p._p&&(C.instance=p,C.state.loading=5),pr.has(t)||(s={rel:"preload",as:"style",href:s.href,crossOrigin:s.crossOrigin,integrity:s.integrity,media:s.media,hrefLang:s.hrefLang,referrerPolicy:s.referrerPolicy},pr.set(t,s),p||uT(h,t,s,C.state))),r&&u===null)throw Error(a(528,""));return C}if(r&&u!==null)throw Error(a(529,""));return null;case"script":return r=s.async,s=s.src,typeof s=="string"&&r&&typeof r!="function"&&typeof r!="symbol"?(r=Js(s),s=Ia(h).hoistableScripts,u=s.get(r),u||(u={type:"script",instance:null,count:0,state:null},s.set(r,u)),u):{type:"void",instance:null,count:0,state:null};default:throw Error(a(444,t))}}function Xs(t){return'href="'+zn(t)+'"'}function kl(t){return'link[rel="stylesheet"]['+t+"]"}function RS(t){return y({},t,{"data-precedence":t.precedence,precedence:null})}function uT(t,r,s,u){t.querySelector('link[rel="preload"][as="style"]['+r+"]")?u.loading=1:(r=t.createElement("link"),u.preload=r,r.addEventListener("load",function(){return u.loading|=1}),r.addEventListener("error",function(){return u.loading|=2}),En(r,"link",s),Zt(r),t.head.appendChild(r))}function Js(t){return'[src="'+zn(t)+'"]'}function Dl(t){return"script[async]"+t}function kS(t,r,s){if(r.count++,r.instance===null)switch(r.type){case"style":var u=t.querySelector('style[data-href~="'+zn(s.href)+'"]');if(u)return r.instance=u,Zt(u),u;var h=y({},s,{"data-href":s.href,"data-precedence":s.precedence,href:null,precedence:null});return u=(t.ownerDocument||t).createElement("style"),Zt(u),En(u,"style",h),qu(u,s.precedence,t),r.instance=u;case"stylesheet":h=Xs(s.href);var p=t.querySelector(kl(h));if(p)return r.state.loading|=4,r.instance=p,Zt(p),p;u=RS(s),(h=pr.get(h))&&Hg(u,h),p=(t.ownerDocument||t).createElement("link"),Zt(p);var C=p;return C._p=new Promise(function(k,U){C.onload=k,C.onerror=U}),En(p,"link",u),r.state.loading|=4,qu(p,s.precedence,t),r.instance=p;case"script":return p=Js(s.src),(h=t.querySelector(Dl(p)))?(r.instance=h,Zt(h),h):(u=s,(h=pr.get(p))&&(u=y({},s),jg(u,h)),t=t.ownerDocument||t,h=t.createElement("script"),Zt(h),En(h,"link",u),t.head.appendChild(h),r.instance=h);case"void":return null;default:throw Error(a(443,r.type))}else r.type==="stylesheet"&&(r.state.loading&4)===0&&(u=r.instance,r.state.loading|=4,qu(u,s.precedence,t));return r.instance}function qu(t,r,s){for(var u=s.querySelectorAll('link[rel="stylesheet"][data-precedence],style[data-precedence]'),h=u.length?u[u.length-1]:null,p=h,C=0;C title"):null)}function dT(t,r,s){if(s===1||r.itemProp!=null)return!1;switch(t){case"meta":case"title":return!0;case"style":if(typeof r.precedence!="string"||typeof r.href!="string"||r.href==="")break;return!0;case"link":if(typeof r.rel!="string"||typeof r.href!="string"||r.href===""||r.onLoad||r.onError)break;return r.rel==="stylesheet"?(t=r.disabled,typeof r.precedence=="string"&&t==null):!0;case"script":if(r.async&&typeof r.async!="function"&&typeof r.async!="symbol"&&!r.onLoad&&!r.onError&&r.src&&typeof r.src=="string")return!0}return!1}function LS(t){return!(t.type==="stylesheet"&&(t.state.loading&3)===0)}function fT(t,r,s,u){if(s.type==="stylesheet"&&(typeof u.media!="string"||matchMedia(u.media).matches!==!1)&&(s.state.loading&4)===0){if(s.instance===null){var h=Xs(u.href),p=r.querySelector(kl(h));if(p){r=p._p,r!==null&&typeof r=="object"&&typeof r.then=="function"&&(t.count++,t=Vu.bind(t),r.then(t,t)),s.state.loading|=4,s.instance=p,Zt(p);return}p=r.ownerDocument||r,u=RS(u),(h=pr.get(h))&&Hg(u,h),p=p.createElement("link"),Zt(p);var C=p;C._p=new Promise(function(k,U){C.onload=k,C.onerror=U}),En(p,"link",u),s.instance=p}t.stylesheets===null&&(t.stylesheets=new Map),t.stylesheets.set(s,r),(r=s.state.preload)&&(s.state.loading&3)===0&&(t.count++,s=Vu.bind(t),r.addEventListener("load",s),r.addEventListener("error",s))}}var zg=0;function hT(t,r){return t.stylesheets&&t.count===0&&Ku(t,t.stylesheets),0zg?50:800)+r);return t.unsuspend=s,function(){t.unsuspend=null,clearTimeout(u),clearTimeout(h)}}:null}function Vu(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)Ku(this,this.stylesheets);else if(this.unsuspend){var t=this.unsuspend;this.unsuspend=null,t()}}}var Fu=null;function Ku(t,r){t.stylesheets=null,t.unsuspend!==null&&(t.count++,Fu=new Map,r.forEach(gT,t),Fu=null,Vu.call(t))}function gT(t,r){if(!(r.state.loading&4)){var s=Fu.get(t);if(s)var u=s.get(null);else{s=new Map,Fu.set(t,s);for(var h=t.querySelectorAll("link[data-precedence],style[data-precedence]"),p=0;p"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(o)}catch(e){console.error(e)}}return o(),rd.exports=QS(),rd.exports}var od=ZS();const ld=Jr(od),eo=[],ew=50;let cd=!1;function tp(){try{return window.G7Core?.devTools}catch{return}}function Xi(o,e,n){const a=tp();if(cd&&a?.isEnabled?.()){a.trackLog?.(o,e,n);return}cd||(o==="error"||o==="warn")&&eo.length{n.isDebugEnabled()&&console.log(e,...a),Xi("log",o,a)},warn:(...a)=>{n.isDebugEnabled()&&console.warn(e,...a),Xi("warn",o,a)},error:(...a)=>{n.isDebugEnabled()&&console.error(e,...a),Xi("error",o,a)}}}to.getInstance();const Ji=ht("networkResilience"),np=2,rp=300,ap=2e3,tw=15e3;function dd(o){return o?.name==="AbortError"}function ip(o){return dd(o)?!1:o instanceof TypeError}let Nl=!1,sp=!1;function fd(){return Nl}function nw(){typeof window>"u"||sp||(sp=!0,window.addEventListener("pagehide",()=>{Nl=!0}),window.addEventListener("pageshow",o=>{o.persisted&&(Nl=!1)}),window.addEventListener("beforeunload",()=>{Nl=!0}))}function op(o,e,n){const a=Math.min(e*Math.pow(2,o),n),i=a*.25*(Math.random()*2-1);return Math.max(0,Math.round(a+i))}function lp(o){return new Promise(e=>setTimeout(e,o))}async function Il(o,e={}){const{retries:n=np,baseDelayMs:a=rp,maxDelayMs:i=ap,timeoutMs:l=tw,label:c=o,init:d}=e,f=n+1;let g;for(let m=0;m0?new AbortController:null;try{const _={...d};if(v){const x=d?.signal;x&&(x.aborted?v.abort():x.addEventListener("abort",()=>v.abort(),{once:!0})),_.signal=v.signal,S=setTimeout(()=>{y=!0,v.abort()},l)}return await fetch(o,_)}catch(_){if(g=_,dd(_)&&!y||!(y||ip(_)))throw _;if(fd())throw Ji.warn(`Document unloading, aborting retries: ${c}`),_;if(m===f-1)throw Ji.warn(`All ${f} attempts failed: ${c}`,_),_;const O=op(m,a,i);Ji.warn(`Network failure (attempt ${m+1}/${f}), retrying in ${O}ms: ${c}`),await lp(O)}finally{S!==void 0&&clearTimeout(S)}}throw g}async function cp(o,e={},n={}){const{retries:a=np,baseDelayMs:i=rp,maxDelayMs:l=ap,label:c=o}=n,d=a+1;for(let f=0;f{const i=document.createElement("script");i.src=o,i.async=!1;for(const[l,c]of Object.entries(e))l==="id"?i.id=c:i.setAttribute(l,c);i.onload=()=>n(),i.onerror=()=>a(new Error(`Failed to load script: ${o}`)),document.head.appendChild(i)})}const up="extension",Qi="extensionless",aw="file";function iw(){const o=globalThis?.G7Config;if(globalThis?.__g7AssetUrlMode===Qi)return Qi;const n=o?.assetUrlMode;return n===Qi||n===up?n:o?.settings?.general?.asset_url_mode===Qi?Qi:up}function dp(){return iw()===Qi}function Nr(o,e,n,a){const i=o.replace(/\/+$/,""),l=e.replace(/^\.+/,""),c=dp()?i:`${i}.${l}`,d=[];return a&&d.push(a.replace(/^[?&]+/,"")),n!=null&&n!==""&&d.push(`v=${n}`),d.length>0?`${c}?${d.join("&")}`:c}function Ea(o){if(!o||!dp())return o;const e=globalThis?.location?.origin,n=e&&o.startsWith(e)?o.slice(e.length):o;if(!n.startsWith("/api/"))return o;const[a,i]=sw(n),l=a.match(/^\/api\/(modules|plugins)\/bundle\.(js|css)$/);if(l)return fp(`/api/${l[1]}/bundle/${l[2]}`,i);const c=a.match(/^\/api\/(templates|modules|plugins)\/assets\/([^/]+)\/(.+)$/);if(c){const[,f,g,m]=c,y=`${aw}=${encodeURIComponent(decodeURIComponent(m))}`;return`/api/${f}/assets/${g}?${y}${i?`&${i}`:""}`}const d=a.match(/^(\/api\/.+)\.(json|js|css)$/);return d?fp(d[1],i):o}function sw(o){const e=o.indexOf("?");return e===-1?[o,""]:[o.slice(0,e),o.slice(e+1)]}function fp(o,e){return e?`${o}?${e}`:o}const Ir=ht("ComponentRegistry");class hn extends Error{constructor(n,a,i){super(n);$(this,"code");$(this,"details");this.code=a,this.details=i,this.name="ComponentRegistryError"}}const Wn=class Wn{constructor(){$(this,"registry",{});$(this,"manifest",null);$(this,"loadingState","idle");$(this,"error",null);$(this,"templateId",null);$(this,"templateType",null)}static getInstance(){return Wn.instance||(Wn.instance=new Wn),Wn.instance}static createIsolatedInstance(){return new Wn}static resetInstance(){Wn.instance=null}async loadComponents(e,n){if(this.loadingState==="loading")throw new hn("Components are already being loaded","LOADING_IN_PROGRESS");if(this.loadingState==="loaded"&&this.templateId===e&&this.templateType===n){Ir.log("Components already loaded for template:",e,n);return}this.loadingState="loading",this.templateId=e,this.templateType=n,this.error=null;try{await this.loadManifest(),await this.loadComponentBundle(),this.loadingState="loaded",Ir.log("Successfully loaded components:",Object.keys(this.registry).length)}catch(a){throw this.loadingState="error",this.error=a instanceof Error?a:new Error(String(a)),new hn(`Failed to load components: ${this.error.message}`,"LOAD_FAILED",{originalError:this.error})}}async loadManifest(){try{if(!this.templateId)throw new hn("Template ID not set","TEMPLATE_ID_NOT_SET");const e=`${this.templateId}:${this.templateType}`;if(Wn.manifestCache.has(e)){this.manifest=Wn.manifestCache.get(e),Ir.log("Manifest loaded from cache:",this.manifest.templateId);return}const n=Nr(`/api/templates/${this.templateId}/components`,"json"),a=await Il(n,{label:"components.json"});if(!a.ok)throw new hn(`Failed to fetch manifest: ${a.status} ${a.statusText}`,"MANIFEST_FETCH_FAILED",{status:a.status,statusText:a.statusText});const i=await a.json();this.validateManifest(i),this.manifest=i,Wn.manifestCache.set(e,i),Ir.log("Manifest loaded and cached:",i.templateId)}catch(e){throw e instanceof hn?e:new hn("Failed to load component manifest","MANIFEST_LOAD_FAILED",{originalError:e})}}validateManifest(e){if(!e.version)throw new hn("Manifest missing required field: version","MANIFEST_INVALID",{field:"version"});if(!e.templateId)throw new hn("Manifest missing required field: templateId","MANIFEST_INVALID",{field:"templateId"});if(!e.components||typeof e.components!="object")throw new hn("Manifest missing required field: components","MANIFEST_INVALID",{field:"components"});const n=["basic","composite","layout"];for(const a of n){if(!Array.isArray(e.components[a]))throw new hn(`Manifest field 'components.${a}' must be an array`,"MANIFEST_INVALID",{field:`components.${a}`,value:e.components[a]});e.components[a].forEach((i,l)=>{if(!i.name||typeof i.name!="string")throw new hn(`Invalid component metadata at ${a}[${l}]: missing or invalid 'name'`,"MANIFEST_INVALID",{type:a,index:l,metadata:i});if(!i.type||typeof i.type!="string")throw new hn(`Invalid component metadata at ${a}[${l}]: missing or invalid 'type'`,"MANIFEST_INVALID",{type:a,index:l,metadata:i})})}Ir.log("Manifest validation passed")}async loadComponentBundle(){try{if(!this.templateId)throw new hn("Template ID not set","TEMPLATE_ID_NOT_SET");const e=this.getGlobalVariableName(),n=window[e];if(!n||typeof n!="object")throw new hn(`Component bundle not loaded. Expected global variable: ${e}. Ensure admin.blade.php includes the IIFE bundle script.`,"BUNDLE_NOT_LOADED",{expectedVariable:e});await this.registerComponentsFromManifest(n),Ir.log("Component bundle loaded from global variable:",e)}catch(e){throw e instanceof hn?e:new hn("Failed to load component bundle","BUNDLE_LOAD_FAILED",{originalError:e})}}getGlobalVariableName(){if(!this.templateId)throw new hn("Template ID not set","TEMPLATE_ID_NOT_SET");return this.templateId.split(/[-_]/).map(e=>e.charAt(0).toUpperCase()+e.slice(1).toLowerCase()).join("")}async registerComponentsFromManifest(e){if(!this.manifest)throw new hn("Manifest not loaded","MANIFEST_NOT_LOADED");const n=["basic","composite","layout"];for(const a of n){const i=this.manifest.components[a]||[];for(const l of i){const c=l.name,d=e[c];if(!d){Ir.warn(`Component '${c}' not found in bundle`);continue}this.registerComponent(c,d,l)}}}registerComponent(e,n,a){this.registry[e]&&Ir.warn(`Component '${e}' already registered, overwriting`);const i=Xe.memo(n);this.registry[e]={component:i,metadata:a},Ir.log(`[ComponentRegistry] Registered component: ${e} (${a.type})`)}getComponent(e){const n=this.registry[e];return n?n.component:null}getMetadata(e){const n=this.registry[e];return n?n.metadata:null}hasComponent(e){return e in this.registry}getComponentsByType(e){return Object.entries(this.registry).filter(([n,a])=>a.metadata.type===e).map(([n,a])=>n)}getAllComponents(){return Object.keys(this.registry)}getComponentMap(){const e={};for(const[n,a]of Object.entries(this.registry))e[n]=a.component;return e}getLoadingState(){return this.loadingState}getError(){return this.error}getTemplateId(){return this.templateId}getManifest(){return this.manifest}clear(){this.registry={},this.manifest=null,this.loadingState="idle",this.error=null,this.templateId=null,this.templateType=null,Ir.log("Registry cleared")}};$(Wn,"instance",null),$(Wn,"manifestCache",new Map);let mr=Wn;const hp=ht("TranslationEngine");class hd extends Error{constructor(n,a,i){super(n);$(this,"key");$(this,"locale");this.key=a,this.locale=i,this.name="TranslationError"}}const In=class In{constructor(e={}){$(this,"cache",new Map);$(this,"translations",new Map);$(this,"options");$(this,"cacheVersion",0);this.options={defaultLocale:e.defaultLocale||"ko",fallbackLocale:e.fallbackLocale||"en",cacheTTL:e.cacheTTL||3e5}}static getInstance(e={}){return In.instance||(In.instance=new In(e)),In.instance}static resetInstance(){In.instance=null}setCacheVersion(e){this.cacheVersion!==e&&(hp.log("Cache version updated:",this.cacheVersion,"->",e),this.cacheVersion=e,this.cache.clear())}getCacheVersion(){return this.cacheVersion}async loadTranslations(e,n,a="/api",i=!1){const l=`${e}:${n}`;if(!i){const c=this.getFromCache(l);if(c)return this.translations.set(l,c),c}try{const c=[];i&&c.push(`_=${Date.now()}`);const d=Nr(`${a}/templates/${e}/lang/${n}`,"json",this.cacheVersion>0?this.cacheVersion:null,c.length>0?c.join("&"):void 0),f=await fetch(d);if(!f.ok)throw new hd(`Failed to load translations: ${f.statusText}`,void 0,n);const m=await f.json();return this.saveToCache(l,m),this.translations.set(l,m),m}catch(c){throw new hd(`Failed to fetch translations for ${n}: ${c instanceof Error?c.message:String(c)}`,void 0,n)}}resolveTranslations(e,n,a){let i=e;const l=5;let c=0;for(;c"="+this.translate(m,n,void 0,a)),i===d)break;c++}return i.replace(In.TRANSLATION_PATTERN,(d,f,g)=>{const{cleanedParams:m,trailing:y}=this.separateTrailingText(g);return this.translate(f,n,m,a)+y})}translate(e,n,a,i){const l=this.getTranslation(e,n),c=a&&this.cleanParamsStr(a);if(c){const d=this.parseParams(c,i);return this.replaceParams(l,d)}return l}getTranslation(e,n){const{templateId:a,locale:i}=n,l=`${a}:${i}`,c=this.translations.get(l);if(c){const d=this.getNestedValue(c,e);if(d!==null)return d}if(i!==this.options.fallbackLocale){const d=`${a}:${this.options.fallbackLocale}`,f=this.translations.get(d);if(f){const g=this.getNestedValue(f,e);if(g!==null)return g}}return e}getNestedValue(e,n){const a=n.split(".");let i=e;for(const l of a){if(i==null||typeof i!="object")return null;i=i[l]}return typeof i=="string"?i:null}setTranslationValue(e,n,a,i){const l=`${e}:${n}`,c=this.translations.get(l)??{},d=a.split(".");let f=c;for(let g=0;g(l.push(f),`__PLACEHOLDER_${l.length-1}__`)).matchAll(In.PARAM_PATTERN);for(const f of d){const[,g,m]=f,y=m.replace(/__PLACEHOLDER_(\d+)__/g,(S,v)=>l[parseInt(v,10)]);a[g.trim()]=this.resolveParamValue(y.trim(),n)}return a}resolveParamValue(e,n){if(e.startsWith("{{")&&e.endsWith("}}")){const a=e.slice(2,-2).trim();if(/[|&()[\]!?:+\-*/%<>=\s]/.test(a)&&n)try{const l=Sd.evaluateExpression(a,n);return String(l??"")}catch(l){return hp.error("Expression evaluation failed:",a,l),""}else{const l=this.getNestedDataValue(n,a);return String(l??"")}}return e}separateTrailingText(e){if(!e)return{cleanedParams:void 0,trailing:""};if(e.trimEnd().endsWith("}}"))return{cleanedParams:e,trailing:""};if(!(e.startsWith("|")?e.slice(1):e).includes("="))return{cleanedParams:void 0,trailing:e};const a=e.match(/(\s+[^\p{L}\w=|&\s][^\p{L}\w=]*\s*)$/u);return a?{cleanedParams:e.slice(0,-a[1].length),trailing:a[1]}:{cleanedParams:e,trailing:""}}cleanParamsStr(e){return this.separateTrailingText(e).cleanedParams||""}getNestedDataValue(e,n){if(!e)return;const a=n.split(".");let i=e;for(const l of a){if(i==null)return;i=i[l]}return i}replaceParams(e,n){let a=e;for(const[i,l]of Object.entries(n)){const c=new RegExp(`\\{\\{${i}\\}\\}`,"g");a=a.replace(c,String(l));const d=new RegExp(`\\{${i}\\}`,"g");a=a.replace(d,String(l))}return a}getFromCache(e){const n=this.cache.get(e);return n?Date.now()-n.timestamp>this.options.cacheTTL?(this.cache.delete(e),null):n.data:null}saveToCache(e,n){this.cache.set(e,{data:n,timestamp:Date.now()})}clearCache(){this.cache.clear(),this.translations.clear()}pruneCache(){const e=Date.now(),n=[];for(const[a,i]of this.cache.entries())e-i.timestamp>this.options.cacheTTL&&n.push(a);for(const a of n)this.cache.delete(a)}getCacheStats(){const e=Date.now();let n=0;for(const a of this.cache.values())e-a.timestamp>this.options.cacheTTL&&n++;return{size:this.cache.size,expired:n}}};$(In,"instance",null),$(In,"TRANSLATION_PATTERN",/\$t:(?:defer:)?([a-zA-Z0-9._-]+)(\|(?:(?!\$t:).)+)?/g),$(In,"NESTED_TRANSLATION_PARAM_PATTERN",/=(\$t:([a-zA-Z0-9._-]+))(?=[|&\s]|$)/g),$(In,"PARAM_PATTERN",/([^=|&]+)=([^|&]+)/g);let _a=In,gd=null;function ow(o){return gd||(gd=new _a(o)),gd}const gp=Object.freeze(Object.defineProperty({__proto__:null,TranslationEngine:_a,TranslationError:hd,getTranslationEngine:ow},Symbol.toStringTag,{value:"Module"})),Zi=ht("PipeRegistry");function pp(o,e){const n=o.getFullYear(),a=String(o.getMonth()+1).padStart(2,"0"),i=String(o.getDate()).padStart(2,"0"),l=String(o.getHours()).padStart(2,"0"),c=String(o.getMinutes()).padStart(2,"0"),d=String(o.getSeconds()).padStart(2,"0");return e.replace("YYYY",String(n)).replace("YY",String(n).slice(-2)).replace("MM",a).replace("M",String(o.getMonth()+1)).replace("DD",i).replace("D",String(o.getDate())).replace("HH",l).replace("H",String(o.getHours())).replace("mm",c).replace("m",String(o.getMinutes())).replace("ss",d).replace("s",String(o.getSeconds()))}function lw(o,e="ko"){const a=new Date().getTime()-o.getTime(),i=Math.floor(a/1e3),l=Math.floor(i/60),c=Math.floor(l/60),d=Math.floor(c/24),f=Math.floor(d/7),g=Math.floor(d/30),m=Math.floor(d/365);return e==="ko"?i<60?"방금 전":l<60?`${l}분 전`:c<24?`${c}시간 전`:d<7?`${d}일 전`:f<4?`${f}주 전`:g<12?`${g}개월 전`:`${m}년 전`:i<60?"just now":l<60?`${l} minute${l===1?"":"s"} ago`:c<24?`${c} hour${c===1?"":"s"} ago`:d<7?`${d} day${d===1?"":"s"} ago`:f<4?`${f} week${f===1?"":"s"} ago`:g<12?`${g} month${g===1?"":"s"} ago`:`${m} year${m===1?"":"s"} ago`}function pd(o){if(o==null)return null;if(o instanceof Date)return o;if(typeof o=="number")return new Date(o);if(typeof o=="string"){const e=new Date(o);return isNaN(e.getTime())?null:e}return null}const no={date:{fn:(o,e="YYYY-MM-DD")=>{const n=pd(o);return n?pp(n,e):""},description:"날짜 포맷 (기본: YYYY-MM-DD)"},datetime:{fn:(o,e="YYYY-MM-DD HH:mm")=>{const n=pd(o);return n?pp(n,e):""},description:"날짜+시간 포맷 (기본: YYYY-MM-DD HH:mm)"},relativeTime:{fn:(o,e="ko")=>{const n=pd(o);return n?lw(n,e):""},description:'상대 시간 표시 (예: "3분 전")'},number:{fn:(o,e)=>{const n=Number(o);if(isNaN(n))return String(o??"");const a={};return e!==void 0&&(a.minimumFractionDigits=e,a.maximumFractionDigits=e),n.toLocaleString(void 0,a)},description:"숫자 포맷 (천단위 구분, 선택적 소수점)"},truncate:{fn:(o,e=100,n="...")=>typeof o!="string"?String(o??""):o.length<=e?o:o.slice(0,e)+n,description:'문자열 자르기 (기본: 100자, 접미사: "...")'},uppercase:{fn:o=>typeof o!="string"?String(o??""):o.toUpperCase(),description:"대문자 변환"},lowercase:{fn:o=>typeof o!="string"?String(o??""):o.toLowerCase(),description:"소문자 변환"},stripHtml:{fn:o=>typeof o!="string"?String(o??""):o.replace(/<[^>]*>/g,""),description:"HTML 태그 제거"},default:{fn:(o,e="")=>o==null||o===""?e:o,description:"기본값 설정 (null, undefined, 빈문자열 시)"},fallback:{fn:(o,e)=>o??e,description:"폴백 값 설정 (null, undefined 시만)"},first:{fn:o=>{if(Array.isArray(o))return o[0]},description:"배열의 첫 번째 요소"},last:{fn:o=>{if(Array.isArray(o))return o[o.length-1]},description:"배열의 마지막 요소"},join:{fn:(o,e=", ")=>Array.isArray(o)?o.join(e):"",description:'배열을 문자열로 결합 (기본 구분자: ", ")'},length:{fn:o=>Array.isArray(o)||typeof o=="string"?o.length:0,description:"배열/문자열 길이"},filterBy:{fn:(o,e,n)=>Array.isArray(o)?Array.isArray(e)?o.filter(a=>{const i=n?a?.[n]:a;return e.includes(i)}):o:[],description:"배열 필터링 (allowList에 포함된 항목만 반환)"},keys:{fn:o=>o==null||typeof o!="object"?[]:Object.keys(o),description:"객체의 키 배열"},values:{fn:o=>o==null||typeof o!="object"?[]:Object.values(o),description:"객체의 값 배열"},json:{fn:(o,e)=>{try{return JSON.stringify(o,null,e)}catch{return""}},description:"JSON 문자열로 변환"},localized:{fn:(o,e)=>o==null?"":typeof o=="string"?o:typeof o!="object"?String(o):o[e||"ko"]||o.ko||o.en||Object.values(o)[0]||"",description:"다국어 객체에서 로케일에 맞는 값 추출"}},es=new Map,Si=class Si{static getInstance(){return Si.instance||(Si.instance=new Si),Si.instance}register(e,n,a){if(no[e]){Zi.warn(`[PipeRegistry] 내장 파이프를 덮어쓸 수 없습니다: ${e}`);return}es.set(e,{fn:n,description:a}),Zi.log(`[PipeRegistry] 커스텀 파이프 등록됨: ${e}`)}unregister(e){if(no[e])return Zi.warn(`[PipeRegistry] 내장 파이프는 해제할 수 없습니다: ${e}`),!1;const n=es.delete(e);return n&&Zi.log(`[PipeRegistry] 커스텀 파이프 해제됨: ${e}`),n}get(e){const n=no[e];return n?n.fn:es.get(e)?.fn}has(e){return!!no[e]||es.has(e)}execute(e,n,a=[]){const i=this.get(e);if(!i)return Zi.warn(`[PipeRegistry] 알 수 없는 파이프: ${e}`),n;try{return i(n,...a)}catch(l){return Zi.error(`[PipeRegistry] 파이프 실행 오류 (${e}):`,l),n}}list(){const e=[];for(const[n,a]of Object.entries(no))e.push({name:n,description:a.description,type:"built-in"});for(const[n,a]of es.entries())e.push({name:n,description:a.description,type:"custom"});return e.sort((n,a)=>n.name.localeCompare(a.name))}clearCustomPipes(){es.clear()}};$(Si,"instance",null);let Hl=Si;Hl.getInstance();function cw(o){const e=o.trim(),n=e.indexOf("(");if(n===-1)return{name:e,args:[]};const a=e.slice(0,n).trim(),i=e.slice(n+1,-1);if(!i.trim())return{name:a,args:[]};const l=[];let c="",d=null,f=0;for(let g=0;g0?i[g-1]:"")==="\\"){c+=m;continue}if((m==='"'||m==="'")&&!d){d=m;continue}if(m===d){d=null;continue}if(d){c+=m;continue}if(m==="("||m==="["||m==="{"){f++,c+=m;continue}if(m===")"||m==="]"||m==="}"){f--,c+=m;continue}if(m===","&&f===0){l.push(mp(c.trim())),c="";continue}c+=m}return c.trim()&&l.push(mp(c.trim())),{name:a,args:l}}function mp(o){if(o==="null")return null;if(o==="undefined")return;if(o==="true")return!0;if(o==="false")return!1;const e=Number(o);return!isNaN(e)&&o!==""?e:o}function uw(o){const e=[];let n="",a=null,i=0;for(let l=0;l0?o[l-1]:"",f=l0?o[a-1]:"",c=a0){a--;continue}return null}}return a===0?e.trim():null}function fw(o){const e=[];if(typeof o!="string")return e;for(let n=0;n0){i--;continue}if(o[c+1]==="}"){l=c;break}break}}l!==-1&&(e.push({start:n,end:l+2,expr:o.slice(n+2,l)}),n=l+1)}return e}function ro(o){return/[?:|&!+\-*/<>=()[\]{}]/.test(o)}function yp(o){const e=typeof o=="string"?o.trim():"";return e===""?"empty":md.has(e)?"literal":Vn(e)?"pipe":ro(e)?"expression":"path"}function yd(o,e,n,a){const i=typeof o=="string"?o.trim():"",l=yp(i),c=a?.skipCache?{skipCache:!0}:void 0;if(l==="empty"){a?.onEmpty?.(o);return}if(l==="literal")return md.get(i);try{return l==="pipe"?n.evaluatePipeExpression(i,e,c,a?.trackingInfo):l==="expression"?n.evaluateExpression(i,e,a?.trackingInfo):n.resolve(i,e,c,a?.trackingInfo)}catch(d){if(a?.onError)return a.onError(d,i);throw d}}const nr="raw:";function bp(o){return o.startsWith(nr)?o.slice(nr.length):o}const ao="﷐",fi="﷑",xa="﷒";function io(o){return ao+o+fi}function jl(o){return o.length>=2&&o.charCodeAt(0)===64976&&o.charCodeAt(o.length-1)===64977}function zl(o){return o.includes(ao)}function bd(o){return o.slice(1,-1)}function ts(o){if(typeof o=="string")return io(o);if(Array.isArray(o))return o.map(ts);if(o&&typeof o=="object"){const e={};for(const[n,a]of Object.entries(o))e[n]=ts(a);return e}return o}function Ta(o){if(typeof o=="string")return jl(o)?bd(o):zl(o)||o.includes(fi)?o.split(ao).join("").split(fi).join(""):o;if(Array.isArray(o)){let e=!1;const n=o.map(a=>{const i=Ta(a);return i!==a&&(e=!0),i});return e?n:o}if(o&&typeof o=="object"){if(o.$$typeof!==void 0)return o;let e=!1;const n={};for(const[a,i]of Object.entries(o)){const l=Ta(i);l!==i&&(e=!0),n[a]=l}return e?n:o}return o}const Ra=ht("DataBindingEngine"),hw=/^[$A-Za-z_][$A-Za-z0-9_]*$/,gw=new Set(["break","case","catch","class","const","continue","debugger","default","delete","do","else","enum","export","extends","false","finally","for","function","if","implements","import","in","instanceof","interface","let","new","null","package","private","protected","public","return","static","super","switch","this","throw","true","try","typeof","var","void","while","with","yield","arguments","eval"]);function so(){try{return window.G7Core?.devTools}catch{return}}class vd extends Error{constructor(n,a,i){super(n);$(this,"path");$(this,"context");this.path=a,this.context=i,this.name="DataBindingError"}}const ta=class ta{constructor(e){$(this,"cache",new Map);$(this,"expressionFnCache",new Map);$(this,"renderCycleCache",new Map);$(this,"currentRenderCycleId",0);$(this,"defaultOptions",{defaultValue:void 0,nullSafe:!0,detectCircular:!0,maxDepth:10});e&&(this.defaultOptions={...this.defaultOptions,...e})}startRenderCycle(){this.currentRenderCycleId++,this.renderCycleCache.clear()}isRenderCycleCacheActive(){return this.currentRenderCycleId>0}getFromRenderCycleCache(e){if(this.isRenderCycleCacheActive())return this.renderCycleCache.get(e)}saveToRenderCycleCache(e,n){this.isRenderCycleCacheActive()&&this.renderCycleCache.set(e,n)}resolveBindings(e,n,a,i){const l={...this.defaultOptions,...a},c=so(),d=n._computed&&!n.$computed?{...n,$computed:n._computed}:n,f=(S,v)=>{const _=v.trim();let A=!1,x=_;if(_.startsWith(nr)&&(A=!0,x=_.slice(nr.length)),Vn(x))try{const q=this.evaluatePipeExpression(x,d,l,{...i,method:"resolveBindings",displayExpression:`{{${_}}}`}),W=this.formatValue(q);return A?io(W):W}catch(q){return Ra.error("Pipe expression evaluation failed:",x,q),S}if(ro(x)){const q=c?.isEnabled()?performance.now():0;try{let W,he=!1;if(l.skipCache)W=this.evaluateExpression(x,d);else{const be=`expr:${x}`,Ue=this.getFromRenderCycleCache(be);Ue!==void 0?(W=Ue,he=!0):(W=this.evaluateExpression(x,d),this.saveToRenderCycleCache(be,W))}if(c?.isEnabled()){const be=performance.now()-q;c.trackExpressionEval({expression:`{{${_}}}`,result:this.sanitizeResultForTracking(W),resultType:this.getResultType(W),componentId:i?.componentId,componentName:i?.componentName,propName:i?.propName,fromCache:he,duration:be,method:"resolveBindings",skipCache:l.skipCache})}const Ee=this.formatValue(W);return A?io(Ee):Ee}catch(W){return Ra.error("Expression evaluation failed:",x,W),S}}if(l.skipCache){const q=c?.isEnabled()?performance.now():0,W=this.resolvePath(x,d,l);if(c?.isEnabled()){const Ee=performance.now()-q;c.trackExpressionEval({expression:`{{${_}}}`,result:this.sanitizeResultForTracking(W),resultType:this.getResultType(W),componentId:i?.componentId,componentName:i?.componentName,propName:i?.propName,fromCache:!1,duration:Ee,method:"resolveBindings",skipCache:!0})}const he=this.formatValue(W);return A?io(he):he}const M=x.startsWith("_global")||x.startsWith("_local")||x.startsWith("_isolated")||x.startsWith("$parent"),T=c?.isEnabled()?performance.now():0;let D,z=!1;if(M){const q=this.getFromRenderCycleCache(x);q!==void 0?(D=q,z=!0):(D=this.resolvePath(x,d,l),this.saveToRenderCycleCache(x,D))}else{const q=this.getFromCache(x);q!==void 0?(D=q,z=!0):(D=this.resolvePath(x,d,l),this.saveToCache(x,D))}if(c?.isEnabled()){const q=performance.now()-T;c.trackExpressionEval({expression:`{{${_}}}`,result:this.sanitizeResultForTracking(D),resultType:this.getResultType(D),componentId:i?.componentId,componentName:i?.componentName,propName:i?.propName,fromCache:z,duration:q,method:"resolveBindings",skipCache:l.skipCache})}const P=this.formatValue(D);return A?io(P):P},g=fw(e);if(g.length===0)return e;let m="",y=0;for(const S of g)m+=e.slice(y,S.start),m+=f(e.slice(S.start,S.end),S.expr),y=S.end;return m+e.slice(y)}evaluatePipeExpression(e,n,a,i){const l={...this.defaultOptions,...a},c=so(),d=c?.isEnabled()?performance.now():0,f=n._computed&&!n.$computed?{...n,$computed:n._computed}:n,[g,m]=uw(e);let y,S=!1;const v=ro(g),_=g.startsWith("_global")||g.startsWith("_local")||g.startsWith("_isolated")||g.startsWith("$parent");if(l.skipCache)v?y=this.evaluateExpression(g,f):y=this.resolvePath(g,f,l);else if(v){const x=this.getFromRenderCycleCache(`expr:${g}`);x!==void 0?(y=x,S=!0):(y=this.evaluateExpression(g,f),this.saveToRenderCycleCache(`expr:${g}`,y))}else if(_){const x=this.getFromRenderCycleCache(g);x!==void 0?(y=x,S=!0):(y=this.resolvePath(g,f,l),this.saveToRenderCycleCache(g,y))}else{const x=this.getFromCache(g);x!==void 0?(y=x,S=!0):(y=this.resolvePath(g,f,l),this.saveToCache(g,y))}const A=dw(y,m);if(c?.isEnabled()){const x=performance.now()-d;c.trackExpressionEval({expression:i?.displayExpression??`{{${e}}}`,result:this.sanitizeResultForTracking(A),resultType:this.getResultType(A),componentId:i?.componentId,componentName:i?.componentName,propName:i?.propName,fromCache:S,duration:x,method:i?.method??"evaluatePipeExpression",skipCache:l.skipCache})}return A}resolve(e,n,a,i){const l={...this.defaultOptions,...a},c=so(),d=c?.isEnabled()?performance.now():0;if(l.skipCache){const y=this.resolvePath(e,n,l);return c?.isEnabled()&&c.trackExpressionEval({expression:e,result:this.sanitizeResultForTracking(y),resultType:this.getResultType(y),componentId:i?.componentId,componentName:i?.componentName,propName:i?.propName,fromCache:!1,duration:performance.now()-d,method:"resolve",skipCache:!0}),y}const f=e.startsWith("_global")||e.startsWith("_local")||e.startsWith("_isolated")||e.startsWith("$parent");let g=!1;if(f){const y=this.getFromRenderCycleCache(e);if(y!==void 0)return g=!0,c?.isEnabled()&&c.trackExpressionEval({expression:e,result:this.sanitizeResultForTracking(y),resultType:this.getResultType(y),componentId:i?.componentId,componentName:i?.componentName,propName:i?.propName,fromCache:!0,duration:performance.now()-d,method:"resolve",skipCache:l.skipCache}),y}else{const y=this.getFromCache(e);if(y!==void 0)return g=!0,c?.isEnabled()&&c.trackExpressionEval({expression:e,result:this.sanitizeResultForTracking(y),resultType:this.getResultType(y),componentId:i?.componentId,componentName:i?.componentName,propName:i?.propName,fromCache:!0,duration:performance.now()-d,method:"resolve",skipCache:l.skipCache}),y}const m=this.resolvePath(e,n,l);return f?this.saveToRenderCycleCache(e,m):this.saveToCache(e,m),c?.isEnabled()&&c.trackExpressionEval({expression:e,result:this.sanitizeResultForTracking(m),resultType:this.getResultType(m),componentId:i?.componentId,componentName:i?.componentName,propName:i?.propName,fromCache:g,duration:performance.now()-d,method:"resolve",skipCache:l.skipCache}),m}resolvePath(e,n,a,i=0,l=new WeakSet){if(typeof e!="string"||e.trim()===""){Ra.warn("resolvePath: 빈 경로가 전달되었습니다 — undefined 로 해석합니다.");return}if(a.maxDepth&&i>a.maxDepth){if(a.detectCircular)throw new vd(`Maximum depth exceeded (${a.maxDepth}). Possible circular reference.`,e,n);return a.defaultValue}const c=this.parsePath(e);let d=n;for(let f=0;fta.CACHE_EXPIRY){this.cache.delete(e),a?.isEnabled()&&a.recordCacheMiss();return}return a?.isEnabled()&&a.recordCacheHit(),n.value}saveToCache(e,n){this.cache.set(e,{value:n,timestamp:Date.now()})}isActionDefinition(e){return typeof e.handler!="string"?!1:e.params!==void 0||Array.isArray(e.actions)||typeof e.target=="string"||e.onSuccess!==void 0||e.onError!==void 0}resolveObject(e,n,a){if(this.isSwitchExpression(e)){const d=e;return this.resolveSwitch(d,n,a)}if(this.isActionDefinition(e))return{...e};if("iteration"in e)return{...e};const i={},c=[...["cellChildren","expandChildren","expandContext","render"],...a?.skipBindingKeys||[]];for(const[d,f]of Object.entries(e)){if(c.includes(d)){i[d]=f;continue}try{this.resolveObjectEntry(i,d,f,n,a)}catch(g){Ra.warn(`resolveObject: 값 해석 실패 (key: ${d}):`,g),i[d]=void 0}}return i}resolveObjectEntry(e,n,a,i,l){if(typeof a=="string"){const c=Aa(a);if(c!==null){const d=c.trim();let f=!1,g=d;d.startsWith(nr)&&(f=!0,g=d.slice(nr.length));let m;switch(yp(g)){case"empty":Ra.warn(`resolveObject: 빈 바인딩 \`{{}}\` (key: ${n}) — undefined 로 해석합니다.`),m=void 0;break;case"literal":m=md.get(g);break;case"pipe":m=this.evaluatePipeExpression(g,i,l);break;case"expression":m=this.evaluateExpression(g,i,l);break;default:m=this.resolve(g,i,l)}e[n]=f&&m!=null?ts(m):m}else e[n]=this.resolveBindings(a,i,l)}else Array.isArray(a)?e[n]=a.map(c=>typeof c=="string"?this.resolveBindings(c,i,l):typeof c=="object"&&c!==null?this.resolveObject(c,i,l):c):a&&typeof a=="object"?e[n]=this.resolveObject(a,i,l):e[n]=a}clearCache(){this.cache.clear(),this.expressionFnCache.clear()}invalidateCacheByKeys(e){for(const n of this.cache.keys())for(const a of e)if(n===a||n.startsWith(`${a}.`)||n.startsWith(`${a}[`)){this.cache.delete(n);break}}pruneCache(){const e=Date.now();for(const[n,a]of this.cache.entries())e-a.timestamp>ta.CACHE_EXPIRY&&this.cache.delete(n)}getCacheStats(){const e=Date.now();let n=0;for(const a of this.cache.values())e-a.timestamp>ta.CACHE_EXPIRY&&n++;return{size:this.cache.size,expired:n}}evaluateExpression(e,n,a){const i=so(),l=i?.isEnabled()?performance.now():0;i?.isEnabled()&&i.trackBindingEval();try{let c=this.preprocessOptionalChaining(e);c=this.preprocessTranslationTokens(c);const d=this.extractVariablesFromExpression(c),f={...n};for(const D of d)D in f||(f[D]=void 0);n._computed&&!f.$computed&&(f.$computed=n._computed);let g=n.$templateId,m=n.$locale;if(typeof window<"u"&&(!g||!m)){const D=window.__templateApp?.getConfig?.();g??(g=D?.templateId),m??(m=D?.locale)}const y=m||"ko";f.$localized=(D,z)=>{if(D==null&&!z)return"";if(typeof D=="string")return D;if(D&&typeof D=="object"&&D[y])return D[y];if(z&&typeof z=="string"){const P=f.$t?f.$t(z):z;if(P&&P!==z)return P}return D&&typeof D=="object"?D.ko||D.en||Object.values(D)[0]||"":D==null?"":String(D)};const S=typeof window<"u"?window.G7Core:void 0;f.$uuid=()=>S?.uuid?S.uuid():typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,D=>{const z=Math.random()*16|0;return(D==="x"?z:z&3|8).toString(16)});const v={templateId:g||"",locale:y};f.$t=D=>{if(!D||typeof D!="string")return"";try{return _a.getInstance().translate(D,v)}catch(z){return Ra.warn("$t() translation failed for key:",D,z),D}},f.$get=(D,z,P=void 0)=>{if(D==null)return P;const q=Array.isArray(z)?z:[z];if(q.length===0)return D;let W=D;for(const he of q){if(W==null||he==null)return P;W=W[he]}return W??P};const _=[],A=[];for(const[D,z]of Object.entries(f))!hw.test(D)||gw.has(D)||(_.push(D),A.push(z));const x=`${c}|${_.join(",")}`;let O=this.expressionFnCache.get(x);const M=!!O;O||(O=new Function(..._,`return (${c});`),this.expressionFnCache.set(x,O));const T=O(...A);if(i?.isEnabled()){const D=performance.now()-l;i.trackExpressionEval({expression:e,result:this.sanitizeResultForTracking(T),resultType:this.getResultType(T),componentId:a?.componentId,componentName:a?.componentName,propName:a?.propName,fromCache:M,duration:D,method:"evaluateExpression",skipCache:a?.skipCache})}return T}catch(c){throw new Error(`Failed to evaluate expression "${e}": ${c instanceof Error?c.message:String(c)}`)}}getResultType(e){return e===null?"null":e===void 0?"undefined":Array.isArray(e)?"array":typeof e}sanitizeResultForTracking(e){if(e==null||typeof e!="object")return e;try{if(Array.isArray(e))return e.length>10?`[Array(${e.length})]`:e.slice(0,10);const n=Object.keys(e);return n.length>20?`{Object(${n.length} keys)}`:(JSON.stringify(e),e)}catch{return"[Complex Object]"}}preprocessTranslationTokens(e){return e=e.replace(/(['"])(\$t:([a-zA-Z_][a-zA-Z0-9_.\-]*))\1/g,(n,a,i,l)=>`$t('${l}')`),e.replace(/(?(n.push(l),`__STRING_LITERAL_${n.length-1}__`));const i=[];return a=a.replace(/\$t:[a-zA-Z_][a-zA-Z0-9_.\-]*(?:\|[^'"\s,)]+)?/g,l=>(i.push(l),`__TRANSLATION_TOKEN_${i.length-1}__`)),a=a.replace(/([a-zA-Z_$][a-zA-Z0-9_$]*)\.(?!\?)/g,"$1?."),a=a.replace(/__TRANSLATION_TOKEN_(\d+)__/g,(l,c)=>i[parseInt(c,10)]),a=a.replace(/__STRING_LITERAL_(\d+)__/g,(l,c)=>n[parseInt(c,10)]),a}extractVariablesFromExpression(e){const n=new Set(["true","false","null","undefined","NaN","Infinity","if","else","for","while","do","switch","case","break","continue","return","function","class","const","let","var","new","delete","typeof","instanceof","this","super","import","export","default","try","catch","finally","throw","Math","Date","JSON","Array","Object","String","Number","Boolean","RegExp","Set","Map","WeakSet","WeakMap","Symbol","Promise","BigInt","Error","parseInt","parseFloat","isNaN","isFinite","encodeURI","decodeURI","encodeURIComponent","decodeURIComponent"]),a=/(?{n!==null&&clearTimeout(n),n=setTimeout(()=>{o(...a)},e)})}class mw{constructor(){$(this,"subscribers",new Set);$(this,"currentWidth",1024);$(this,"debouncedHandler",null);$(this,"isInitialized",!1);this.initialize()}initialize(){typeof window>"u"||(this.currentWidth=window.innerWidth,this.isInitialized=!0,this.debouncedHandler=pw(()=>{this.currentWidth=window.innerWidth,this.notifySubscribers()},150),window.addEventListener("resize",this.debouncedHandler))}notifySubscribers(){this.subscribers.forEach(e=>{e(this.currentWidth)})}subscribe(e){return this.subscribers.add(e),e(this.currentWidth),()=>{this.subscribers.delete(e)}}getWidth(){return this.currentWidth}parseRange(e){if(Pl.has(e))return Pl.get(e)??null;let n=null;if(Ul[e])n={...Ul[e]};else{const a=e.match(/^(-?\d*)-(-?\d*)$/);if(a){const[,i,l]=a,c=i===""?0:parseInt(i,10),d=l===""?1/0:parseInt(l,10);!isNaN(c)&&!isNaN(d)&&c<=d&&(n={min:c,max:d})}}return Pl.set(e,n),n}getMatchingKey(e,n){const a=[];for(const i of Object.keys(e)){const l=this.parseRange(i);l&&n>=l.min&&n<=l.max&&a.push({key:i,range:l,isPreset:!!Ul[i]})}return a.length===0?null:(a.sort((i,l)=>{if(i.isPreset!==l.isPreset)return i.isPreset?1:-1;const c=i.range.max-i.range.min,d=l.range.max-l.range.min;return c-d}),a[0].key)}matches(e){const n=this.parseRange(e);return n?this.currentWidth>=n.min&&this.currentWidth<=n.max:!1}clearSubscribers(){this.subscribers.clear()}destroy(){this.debouncedHandler&&typeof window<"u"&&window.removeEventListener("resize",this.debouncedHandler),this.clearSubscribers(),Pl.clear()}_setWidthForTesting(e){this.currentWidth=e,this.notifySubscribers()}}const hi=new mw,oo=ht("ConditionEvaluator");function yw(o,e,n,a){return yd(bp(o),e,n,{skipCache:!0,onEmpty:()=>{oo.warn(`${a}: 빈 바인딩 \`{{}}\` — undefined 로 해석합니다.`)}})}function vp(o,e,n){if(!o)return!0;try{const a=Aa(o);let i;if(a!==null?i=yw(a,e,n,"evaluateStringCondition"):i=n.resolveBindings(o,e,{skipCache:!0}),typeof i=="string"){const l=i.toLowerCase().trim();if(l==="false"||l==="0"||l===""||l==="null"||l==="undefined")return!1}return!!i}catch(a){return oo.warn(`evaluateStringCondition: 조건 평가 실패: ${o}`,a),!1}}function Bl(o,e,n){if(typeof o=="string")return vp(o,e,n);if("and"in o){if(!Array.isArray(o.and)||o.and.length===0)return oo.warn("evaluateConditionExpression: AND 그룹이 비어있습니다"),!0;for(const a of o.and)if(!Bl(a,e,n))return!1;return!0}if("or"in o){if(!Array.isArray(o.or)||o.or.length===0)return oo.warn("evaluateConditionExpression: OR 그룹이 비어있습니다"),!1;for(const a of o.or)if(Bl(a,e,n))return!0;return!1}return oo.warn("evaluateConditionExpression: 알 수 없는 조건 형식",o),!1}function Sp(o,e,n){if(!Array.isArray(o)||o.length===0)return{matched:!1,branchIndex:-1};for(let a=0;a{rr.warn(`evaluateIfCondition: 빈 바인딩 \`{{}}\` (컴포넌트: ${a||"unknown"}) — undefined 로 해석합니다.`)}}):l=n.resolveBindings(o,e,{skipCache:!0}),typeof l=="string"){const c=l.toLowerCase().trim();if(c==="false"||c==="0"||c===""||c==="null"||c==="undefined")return!1}return!!l}catch(i){return rr.warn(`evaluateIfCondition: 조건 평가 실패 (컴포넌트: ${a||"unknown"}):`,i),!1}}function ns(o,e,n,a){if(o.if!==void 0)return _p(o.if,e,n,a);if(o.condition!==void 0)return _p(o.condition,e,n,a);if(o.conditions===void 0)return!0;const i=o.conditions;try{return bw(i)?Bl(i,e,n):vw(i)?Sp(i,e,n).matched:(rr.warn(`evaluateRenderCondition: 알 수 없는 conditions 형식 (컴포넌트: ${a||"unknown"})`),!0)}catch(l){return rr.warn(`evaluateRenderCondition: conditions 평가 실패 (컴포넌트: ${a||"unknown"}):`,l),!1}}function Ap(o){if(!o.responsive)return o;const e=hi.getWidth(),n=hi.getMatchingKey(o.responsive,e);if(!n)return o;const a=o.responsive[n];return{...o,props:{...o.props,...a.props},children:a.children??o.children,text:a.text??o.text,if:a.if??o.if,iteration:a.iteration??o.iteration}}function lo(o,e,n,a,i){if(!o||o.length===0)return[];const l=i?.bindingEngine??new Dn,c=i?.translationEngine??_a.getInstance(),d=i?.translationContext??{templateId:"",locale:"ko"},f=ww(e,i?.componentContext),g=(v,_)=>{if(typeof v=="string")return jl(v)||zl(v)||!/\$t:[a-zA-Z0-9._-]+/.test(v)?v:c.resolveTranslations(v,d,_);if(Array.isArray(v))return v.map(A=>g(A,_));if(v&&typeof v=="object"){const A={};for(const[x,O]of Object.entries(v))A[x]=g(O,_);return A}return v},m=(v,_)=>{if(typeof v=="string"){if(jl(v))return bd(v);if(zl(v)){const x=[],O=v.replace(new RegExp(`${ao}([^${fi}]*)${fi}`,"g"),(T,D)=>(x.push(D),`${xa}${x.length-1}${xa}`));let M=O;return/\$t:[a-zA-Z0-9._-]+/.test(O)&&(M=c.resolveTranslations(O,d,_)),M.replace(new RegExp(`${xa}(\\d+)${xa}`,"g"),(T,D)=>x[parseInt(D)])}if(v.startsWith("$t:defer:")){const x="$t:"+v.slice(9);return c.resolveTranslations(x,d,_)}if(v.startsWith("$t:"))return c.resolveTranslations(v,d,_);if(/\$t:[a-zA-Z0-9._-]+/.test(v)){const x=v.trim();if(!(x.startsWith("{")&&x.endsWith("}")||x.startsWith("[")&&x.endsWith("]"))&&!v.includes("{{"))return c.resolveTranslations(v,d,_)}const A=Ep(v);if(A!==null){let x=!1,O=A;A.startsWith(nr)&&(x=!0,O=A.slice(nr.length));let M=!1,T=yd(O,_,l,{skipCache:!0,onError:D=>{rr.warn("renderItemChildren: 표현식 평가 실패:",D),M=!0},onEmpty:()=>{rr.warn("renderItemChildren: 빈 바인딩 `{{}}` 은 해석하지 않습니다.")}});return M?void 0:(x||(T=g(T,_)),x&&T!=null?ts(T):T)}if(v.includes("{{")){const x=l.resolveBindings(v,_,{skipCache:!0});return typeof x=="string"&&/\$t:[a-zA-Z0-9._-]+/.test(x)?c.resolveTranslations(x,d,_):x}return v}if(Array.isArray(v))return v.map(A=>m(A,_));if(v&&typeof v=="object"){if(l.isSwitchExpression(v))return l.resolveSwitch(v,_,{skipCache:!0});if(l.isActionDefinition(v))return{...v};const A={};for(const[x,O]of Object.entries(v))A[x]=m(O,_);return A}return v},y=(v,_)=>{if(!v)return{};const A={};for(const[x,O]of Object.entries(v))A[x]=m(O,_);return A},S=(v,_,A)=>{const x=Ap(v),O=x.iteration;if(!O)return[];const M=wd(O.source,A,l);if(!Array.isArray(M))return rr.warn(`renderItemChildren: iteration.source가 배열이 아닙니다: ${O.source}`),[];const T=wp();if(T?.isEnabled()){const z=`${_}-iteration`;T.trackIteration(z,O.source,O.item_var,O.index_var,M.length)}const D=n[x.name];return D?M.flatMap((z,P)=>{const q={...A,[O.item_var]:z,[`${O.item_var}_index`]:P,...O.index_var?{[O.index_var]:P}:{}},W=ns({if:x.if,condition:x.condition,conditions:x.conditions},q,l,x.id);if(x.if&&T?.isEnabled()){const wt=`${_}-iter-${P}-if`;T.trackIfCondition(wt,x.if,W,x.name)}if(!W)return[];const he=x.id?String(Ta(m(x.id,q))):void 0,Ee=`${_}-iter-${P}`,be=i?.getRemountKey?i.getRemountKey(he,Ee):Ee,Ue=y(x.props,q),Fe=Cd(Ue,x.actions,q,{actionDispatcher:i?.actionDispatcher,componentContext:i?.componentContext});let Ge=null;return x.text!==void 0?Ge=m(x.text,q):x.children&&x.children.length>0&&(Ge=lo(x.children,q,n,be,i)),T?.isEnabled()&&T.trackRender(x.name),[Xe.createElement(D,{key:be,...Ta(Fe)},Ta(Ge))]}):(rr.warn(`renderItemChildren: 컴포넌트를 찾을 수 없습니다: ${x.name}`),[])};return o.flatMap((v,_)=>{const A=Ap(v),x=A.id?String(Ta(m(A.id,f))):void 0,O=a?`${a}-${x||_}`:x||`child-${_}`,M=i?.getRemountKey?i.getRemountKey(x,O):O;if(A.iteration)return S(A,M,f);const T=ns({if:A.if,condition:A.condition,conditions:A.conditions},f,l,A.id),D=wp();if(A.if&&D?.isEnabled()){const he=`${M}-if`;D.trackIfCondition(he,A.if,T,A.name)}if(!T)return[];const z=n[A.name];if(!z)return rr.warn(`renderItemChildren: 컴포넌트를 찾을 수 없습니다: ${A.name}`),[];const P=y(A.props,f),q=Cd(P,A.actions,f,{actionDispatcher:i?.actionDispatcher,componentContext:i?.componentContext});let W=null;return A.text!==void 0?W=m(A.text,f):A.children&&A.children.length>0&&(W=lo(A.children,f,n,M,i)),D?.isEnabled()&&D.trackRender(A.name),[Xe.createElement(z,{key:M,...Ta(q)},Ta(W))]})}function rs(o,e,n,a){let i,l;return n instanceof Dn?(i=n,l=a):(i=Cp,l=n),i.resolveBindings(o,e,l)}function Cw(o,e,n,a){const i=n??Cp,l={skipCache:!0,...a};let c;try{c=i.resolveBindings(o.key,e,{skipCache:l.skipCache})?.toString()?.trim()??""}catch(g){rr.warn("resolveClassMap: key 평가 실패:",o.key,g),c=""}let d="";c&&o.variants&&c in o.variants?d=o.variants[c]:o.default&&(d=o.default);const f=[];return o.base?.trim()&&f.push(o.base.trim()),d?.trim()&&f.push(d.trim()),f.join(" ")}function xp(o,e){return function(){return o.apply(e,arguments)}}const{toString:Ew}=Object.prototype,{getPrototypeOf:as}=Object,{iterator:co,toStringTag:Tp}=Symbol,ql=(({hasOwnProperty:o})=>(e,n)=>o.call(e,n))(Object.prototype),uo=(o,e)=>{let n=o;const a=[];for(;n!=null&&n!==Object.prototype;){if(a.indexOf(n)!==-1)return!1;if(a.push(n),ql(n,e))return!0;n=as(n)}return!1},_w=(o,e)=>o!=null&&uo(o,e)?o[e]:void 0,Ed=(o=>e=>{const n=Ew.call(e);return o[n]||(o[n]=n.slice(8,-1).toLowerCase())})(Object.create(null)),yr=o=>(o=o.toLowerCase(),e=>Ed(e)===o),Gl=o=>e=>typeof e===o,{isArray:gi}=Array,is=Gl("undefined");function ss(o){return o!==null&&!is(o)&&o.constructor!==null&&!is(o.constructor)&&$n(o.constructor.isBuffer)&&o.constructor.isBuffer(o)}const Rp=yr("ArrayBuffer");function Aw(o){let e;return typeof ArrayBuffer<"u"&&ArrayBuffer.isView?e=ArrayBuffer.isView(o):e=o&&o.buffer&&Rp(o.buffer),e}const xw=Gl("string"),$n=Gl("function"),kp=Gl("number"),os=o=>o!==null&&typeof o=="object",Tw=o=>o===!0||o===!1,Vl=o=>{if(!os(o))return!1;const e=as(o);return(e===null||e===Object.prototype||as(e)===null)&&!uo(o,Tp)&&!uo(o,co)},Rw=o=>{if(!os(o)||ss(o))return!1;try{return Object.keys(o).length===0&&Object.getPrototypeOf(o)===Object.prototype}catch{return!1}},kw=yr("Date"),Dw=yr("File"),Ow=o=>!!(o&&typeof o.uri<"u"),Lw=o=>o&&typeof o.getParts<"u",Mw=yr("Blob"),$w=yr("FileList"),Nw=o=>os(o)&&$n(o.pipe);function Iw(){return typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:typeof global<"u"?global:{}}const Dp=Iw(),Op=typeof Dp.FormData<"u"?Dp.FormData:void 0,Hw=o=>{if(!o)return!1;if(Op&&o instanceof Op)return!0;const e=as(o);if(!e||e===Object.prototype||!$n(o.append))return!1;const n=Ed(o);return n==="formdata"||n==="object"&&$n(o.toString)&&o.toString()==="[object FormData]"},jw=yr("URLSearchParams"),[zw,Uw,Pw,Bw]=["ReadableStream","Request","Response","Headers"].map(yr),qw=o=>o.trim?o.trim():o.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,"");function fo(o,e,{allOwnKeys:n=!1}={}){if(o===null||typeof o>"u")return;let a,i;if(typeof o!="object"&&(o=[o]),gi(o))for(a=0,i=o.length;a0;)if(i=n[a],e===i.toLowerCase())return i;return null}const pi=typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:global,Mp=o=>!is(o)&&o!==pi;function _d(...o){const{caseless:e,skipUndefined:n}=Mp(this)&&this||{},a={},i=(l,c)=>{if(c==="__proto__"||c==="constructor"||c==="prototype")return;const d=e&&typeof c=="string"&&Lp(a,c)||c,f=ql(a,d)?a[d]:void 0;Vl(f)&&Vl(l)?a[d]=_d(f,l):Vl(l)?a[d]=_d({},l):gi(l)?a[d]=l.slice():(!n||!is(l))&&(a[d]=l)};for(let l=0,c=o.length;l(fo(e,(i,l)=>{n&&$n(i)?Object.defineProperty(o,l,{__proto__:null,value:xp(i,n),writable:!0,enumerable:!0,configurable:!0}):Object.defineProperty(o,l,{__proto__:null,value:i,writable:!0,enumerable:!0,configurable:!0})},{allOwnKeys:a}),o),Vw=o=>(o.charCodeAt(0)===65279&&(o=o.slice(1)),o),Fw=(o,e,n,a)=>{o.prototype=Object.create(e.prototype,a),Object.defineProperty(o.prototype,"constructor",{__proto__:null,value:o,writable:!0,enumerable:!1,configurable:!0}),Object.defineProperty(o,"super",{__proto__:null,value:e.prototype}),n&&Object.assign(o.prototype,n)},Kw=(o,e,n,a)=>{let i,l,c;const d={};if(e=e||{},o==null)return e;do{for(i=Object.getOwnPropertyNames(o),l=i.length;l-- >0;)c=i[l],(!a||a(c,o,e))&&!d[c]&&(e[c]=o[c],d[c]=!0);o=n!==!1&&as(o)}while(o&&(!n||n(o,e))&&o!==Object.prototype);return e},Ww=(o,e,n)=>{o=String(o),(n===void 0||n>o.length)&&(n=o.length),n-=e.length;const a=o.indexOf(e,n);return a!==-1&&a===n},Yw=o=>{if(!o)return null;if(gi(o))return o;let e=o.length;if(!kp(e))return null;const n=new Array(e);for(;e-- >0;)n[e]=o[e];return n},Xw=(o=>e=>o&&e instanceof o)(typeof Uint8Array<"u"&&as(Uint8Array)),Jw=(o,e)=>{const a=(o&&o[co]).call(o);let i;for(;(i=a.next())&&!i.done;){const l=i.value;e.call(o,l[0],l[1])}},Qw=(o,e)=>{let n;const a=[];for(;(n=o.exec(e))!==null;)a.push(n);return a},Zw=yr("HTMLFormElement"),e0=o=>o.toLowerCase().replace(/[-_\s]([a-z\d])(\w*)/g,function(n,a,i){return a.toUpperCase()+i}),{propertyIsEnumerable:t0}=Object.prototype,n0=yr("RegExp"),$p=(o,e)=>{const n=Object.getOwnPropertyDescriptors(o),a={};fo(n,(i,l)=>{let c;(c=e(i,l,o))!==!1&&(a[l]=c||i)}),Object.defineProperties(o,a)},r0=o=>{$p(o,(e,n)=>{if($n(o)&&["arguments","caller","callee"].includes(n))return!1;const a=o[n];if($n(a)){if(e.enumerable=!1,"writable"in e){e.writable=!1;return}e.set||(e.set=()=>{throw Error("Can not rewrite read-only method '"+n+"'")})}})},a0=(o,e)=>{const n={},a=i=>{i.forEach(l=>{n[l]=!0})};return gi(o)?a(o):a(String(o).split(e)),n},i0=()=>{},s0=(o,e)=>o!=null&&Number.isFinite(o=+o)?o:e;function o0(o){return!!(o&&$n(o.append)&&o[Tp]==="FormData"&&o[co])}const l0=o=>{const e=new WeakSet,n=a=>{if(os(a)){if(e.has(a))return;if(ss(a))return a;if(!("toJSON"in a)){e.add(a);const i=gi(a)?[]:{};return fo(a,(l,c)=>{const d=n(l);!is(d)&&(i[c]=d)}),e.delete(a),i}}return a};return n(o)},c0=yr("AsyncFunction"),u0=o=>o&&(os(o)||$n(o))&&$n(o.then)&&$n(o.catch),Np=((o,e)=>o?setImmediate:e?((n,a)=>(pi.addEventListener("message",({source:i,data:l})=>{i===pi&&l===n&&a.length&&a.shift()()},!1),i=>{a.push(i),pi.postMessage(n,"*")}))(`axios@${Math.random()}`,[]):n=>setTimeout(n))(typeof setImmediate=="function",$n(pi.postMessage)),d0=typeof queueMicrotask<"u"?queueMicrotask.bind(pi):typeof process<"u"&&process.nextTick||Np,Ip=o=>o!=null&&$n(o[co]),F={isArray:gi,isArrayBuffer:Rp,isBuffer:ss,isFormData:Hw,isArrayBufferView:Aw,isString:xw,isNumber:kp,isBoolean:Tw,isObject:os,isPlainObject:Vl,isEmptyObject:Rw,isReadableStream:zw,isRequest:Uw,isResponse:Pw,isHeaders:Bw,isUndefined:is,isDate:kw,isFile:Dw,isReactNativeBlob:Ow,isReactNative:Lw,isBlob:Mw,isRegExp:n0,isFunction:$n,isStream:Nw,isURLSearchParams:jw,isTypedArray:Xw,isFileList:$w,forEach:fo,merge:_d,extend:Gw,trim:qw,stripBOM:Vw,inherits:Fw,toFlatObject:Kw,kindOf:Ed,kindOfTest:yr,endsWith:Ww,toArray:Yw,forEachEntry:Jw,matchAll:Qw,isHTMLForm:Zw,hasOwnProperty:ql,hasOwnProp:ql,hasOwnInPrototypeChain:uo,getSafeProp:_w,reduceDescriptors:$p,freezeMethods:r0,toObjectSet:a0,toCamelCase:e0,noop:i0,toFiniteNumber:s0,findKey:Lp,global:pi,isContextDefined:Mp,isSpecCompliantForm:o0,toJSONObject:l0,isAsyncFn:c0,isThenable:u0,setImmediate:Np,asap:d0,isIterable:Ip,isSafeIterable:o=>o!=null&&uo(o,co)&&Ip(o)},f0=F.toObjectSet(["age","authorization","content-length","content-type","etag","expires","from","host","if-modified-since","if-unmodified-since","last-modified","location","max-forwards","proxy-authorization","referer","retry-after","user-agent"]),h0=o=>{const e={};let n,a,i;return o&&o.split(` -`).forEach(function(c){i=c.indexOf(":"),n=c.substring(0,i).trim().toLowerCase(),a=c.substring(i+1).trim(),!(!n||e[n]&&f0[n])&&(n==="set-cookie"?e[n]?e[n].push(a):e[n]=[a]:e[n]=e[n]?e[n]+", "+a:a)}),e};function g0(o){let e=0,n=o.length;for(;ee;){const a=o.charCodeAt(n-1);if(a!==9&&a!==32)break;n-=1}return e===0&&n===o.length?o:o.slice(e,n)}const p0=new RegExp("[\\u0000-\\u0008\\u000a-\\u001f\\u007f]+","g"),m0=new RegExp("[^\\u0009\\u0020-\\u007e\\u0080-\\u00ff]+","g");function Ad(o,e){return F.isArray(o)?o.map(n=>Ad(n,e)):g0(String(o).replace(e,""))}const y0=o=>Ad(o,p0),b0=o=>Ad(o,m0);function Hp(o){const e=Object.create(null);return F.forEach(o.toJSON(),(n,a)=>{e[a]=b0(n)}),e}const jp=Symbol("internals");function ho(o){return o&&String(o).trim().toLowerCase()}function Fl(o){return o===!1||o==null?o:F.isArray(o)?o.map(Fl):y0(String(o))}function v0(o){const e=Object.create(null),n=/([^\s,;=]+)\s*(?:=\s*([^,;]+))?/g;let a;for(;a=n.exec(o);)e[a[1]]=a[2];return e}const S0=o=>/^[-_a-zA-Z0-9^`|~,!#$%&'*+.]+$/.test(o.trim());function xd(o,e,n,a,i){if(F.isFunction(a))return a.call(this,e,n);if(i&&(e=n),!!F.isString(e)){if(F.isString(a))return e.indexOf(a)!==-1;if(F.isRegExp(a))return a.test(e)}}function w0(o){return o.trim().toLowerCase().replace(/([a-z\d])(\w*)/g,(e,n,a)=>n.toUpperCase()+a)}function C0(o,e){const n=F.toCamelCase(" "+e);["get","set","has"].forEach(a=>{Object.defineProperty(o,a+n,{__proto__:null,value:function(i,l,c){return this[a].call(this,e,i,l,c)},configurable:!0})})}let An=class{constructor(e){e&&this.set(e)}set(e,n,a){const i=this;function l(d,f,g){const m=ho(f);if(!m)return;const y=F.findKey(i,m);(!y||i[y]===void 0||g===!0||g===void 0&&i[y]!==!1)&&(i[y||f]=Fl(d))}const c=(d,f)=>F.forEach(d,(g,m)=>l(g,m,f));if(F.isPlainObject(e)||e instanceof this.constructor)c(e,n);else if(F.isString(e)&&(e=e.trim())&&!S0(e))c(h0(e),n);else if(F.isObject(e)&&F.isSafeIterable(e)){let d=Object.create(null),f,g;for(const m of e){if(!F.isArray(m))throw new TypeError("Object iterator must return a key-value pair");g=m[0],F.hasOwnProp(d,g)?(f=d[g],d[g]=F.isArray(f)?[...f,m[1]]:[f,m[1]]):d[g]=m[1]}c(d,n)}else e!=null&&l(n,e,a);return this}get(e,n){if(e=ho(e),e){const a=F.findKey(this,e);if(a){const i=this[a];if(!n)return i;if(n===!0)return v0(i);if(F.isFunction(n))return n.call(this,i,a);if(F.isRegExp(n))return n.exec(i);throw new TypeError("parser must be boolean|regexp|function")}}}has(e,n){if(e=ho(e),e){const a=F.findKey(this,e);return!!(a&&this[a]!==void 0&&(!n||xd(this,this[a],a,n)))}return!1}delete(e,n){const a=this;let i=!1;function l(c){if(c=ho(c),c){const d=F.findKey(a,c);d&&(!n||xd(a,a[d],d,n))&&(delete a[d],i=!0)}}return F.isArray(e)?e.forEach(l):l(e),i}clear(e){const n=Object.keys(this);let a=n.length,i=!1;for(;a--;){const l=n[a];(!e||xd(this,this[l],l,e,!0))&&(delete this[l],i=!0)}return i}normalize(e){const n=this,a={};return F.forEach(this,(i,l)=>{const c=F.findKey(a,l);if(c){n[c]=Fl(i),delete n[l];return}const d=e?w0(l):String(l).trim();d!==l&&delete n[l],n[d]=Fl(i),a[d]=!0}),this}concat(...e){return this.constructor.concat(this,...e)}toJSON(e){const n=Object.create(null);return F.forEach(this,(a,i)=>{a!=null&&a!==!1&&(n[i]=e&&F.isArray(a)?a.join(", "):a)}),n}[Symbol.iterator](){return Object.entries(this.toJSON())[Symbol.iterator]()}toString(){return Object.entries(this.toJSON()).map(([e,n])=>e+": "+n).join(` -`)}getSetCookie(){return this.get("set-cookie")||[]}get[Symbol.toStringTag](){return"AxiosHeaders"}static from(e){return e instanceof this?e:new this(e)}static concat(e,...n){const a=new this(e);return n.forEach(i=>a.set(i)),a}static accessor(e){const a=(this[jp]=this[jp]={accessors:{}}).accessors,i=this.prototype;function l(c){const d=ho(c);a[d]||(C0(i,c),a[d]=!0)}return F.isArray(e)?e.forEach(l):l(e),this}};An.accessor(["Content-Type","Content-Length","Accept","Accept-Encoding","User-Agent","Authorization"]),F.reduceDescriptors(An.prototype,({value:o},e)=>{let n=e[0].toUpperCase()+e.slice(1);return{get:()=>o,set(a){this[n]=a}}}),F.freezeMethods(An);const E0="[REDACTED ****]";function _0(o){if(F.hasOwnProp(o,"toJSON"))return!0;let e=Object.getPrototypeOf(o);for(;e&&e!==Object.prototype;){if(F.hasOwnProp(e,"toJSON"))return!0;e=Object.getPrototypeOf(e)}return!1}function A0(o,e){const n=new Set(e.map(l=>String(l).toLowerCase())),a=[],i=l=>{if(l===null||typeof l!="object"||F.isBuffer(l))return l;if(a.indexOf(l)!==-1)return;l instanceof An&&(l=l.toJSON()),a.push(l);let c;if(F.isArray(l))c=[],l.forEach((d,f)=>{const g=i(d);F.isUndefined(g)||(c[f]=g)});else{if(!F.isPlainObject(l)&&_0(l))return a.pop(),l;c=Object.create(null);for(const[d,f]of Object.entries(l)){const g=n.has(d.toLowerCase())?E0:i(f);F.isUndefined(g)||(c[d]=g)}}return a.pop(),c};return i(o)}let Ce=class FS extends Error{static from(e,n,a,i,l,c){const d=new FS(e.message,n||e.code,a,i,l);return Object.defineProperty(d,"cause",{__proto__:null,value:e,writable:!0,enumerable:!1,configurable:!0}),d.name=e.name,e.status!=null&&d.status==null&&(d.status=e.status),c&&Object.assign(d,c),d}constructor(e,n,a,i,l){super(e),Object.defineProperty(this,"message",{__proto__:null,value:e,enumerable:!0,writable:!0,configurable:!0}),this.name="AxiosError",this.isAxiosError=!0,n&&(this.code=n),a&&(this.config=a),i&&(this.request=i),l&&(this.response=l,this.status=l.status)}toJSON(){const e=this.config,n=e&&F.hasOwnProp(e,"redact")?e.redact:void 0,a=F.isArray(n)&&n.length>0?A0(e,n):F.toJSONObject(e);return{message:this.message,name:this.name,description:this.description,number:this.number,fileName:this.fileName,lineNumber:this.lineNumber,columnNumber:this.columnNumber,stack:this.stack,config:a,code:this.code,status:this.status}}};Ce.ERR_BAD_OPTION_VALUE="ERR_BAD_OPTION_VALUE",Ce.ERR_BAD_OPTION="ERR_BAD_OPTION",Ce.ECONNABORTED="ECONNABORTED",Ce.ETIMEDOUT="ETIMEDOUT",Ce.ECONNREFUSED="ECONNREFUSED",Ce.ERR_NETWORK="ERR_NETWORK",Ce.ERR_FR_TOO_MANY_REDIRECTS="ERR_FR_TOO_MANY_REDIRECTS",Ce.ERR_DEPRECATED="ERR_DEPRECATED",Ce.ERR_BAD_RESPONSE="ERR_BAD_RESPONSE",Ce.ERR_BAD_REQUEST="ERR_BAD_REQUEST",Ce.ERR_CANCELED="ERR_CANCELED",Ce.ERR_NOT_SUPPORT="ERR_NOT_SUPPORT",Ce.ERR_INVALID_URL="ERR_INVALID_URL",Ce.ERR_FORM_DATA_DEPTH_EXCEEDED="ERR_FORM_DATA_DEPTH_EXCEEDED";const x0=null,zp=100;function Td(o){return F.isPlainObject(o)||F.isArray(o)}function Up(o){return F.endsWith(o,"[]")?o.slice(0,-2):o}function Rd(o,e,n){return o?o.concat(e).map(function(i,l){return i=Up(i),!n&&l?"["+i+"]":i}).join(n?".":""):e}function T0(o){return F.isArray(o)&&!o.some(Td)}const R0=F.toFlatObject(F,{},null,function(e){return/^is[A-Z]/.test(e)});function Kl(o,e,n){if(!F.isObject(o))throw new TypeError("target must be an object");e=e||new FormData,n=F.toFlatObject(n,{metaTokens:!0,dots:!1,indexes:!1},!1,function(M,T){return!F.isUndefined(T[M])});const a=n.metaTokens,i=n.visitor||_,l=n.dots,c=n.indexes,d=n.Blob||typeof Blob<"u"&&Blob,f=n.maxDepth===void 0?zp:n.maxDepth,g=d&&F.isSpecCompliantForm(e),m=[];if(!F.isFunction(i))throw new TypeError("visitor must be a function");function y(O){if(O===null)return"";if(F.isDate(O))return O.toISOString();if(F.isBoolean(O))return O.toString();if(!g&&F.isBlob(O))throw new Ce("Blob is not supported. Use a Buffer instead.");if(F.isArrayBuffer(O)||F.isTypedArray(O)){if(g&&typeof d=="function")return new d([O]);if(typeof Buffer<"u")return Buffer.from(O);throw new Ce("Blob is not supported. Use a Buffer instead.",Ce.ERR_NOT_SUPPORT)}return O}function S(O){if(O>f)throw new Ce("Object is too deeply nested ("+O+" levels). Max depth: "+f,Ce.ERR_FORM_DATA_DEPTH_EXCEEDED)}function v(O,M){if(f===1/0)return JSON.stringify(O);const T=[];return JSON.stringify(O,function(z,P){if(!F.isObject(P))return P;for(;T.length&&T[T.length-1]!==this;)T.pop();return T.push(P),S(M+T.length-1),P})}function _(O,M,T){let D=O;if(F.isReactNative(e)&&F.isReactNativeBlob(O))return e.append(Rd(T,M,l),y(O)),!1;if(O&&!T&&typeof O=="object"){if(F.endsWith(M,"{}"))M=a?M:M.slice(0,-2),O=v(O,1);else if(F.isArray(O)&&T0(O)||(F.isFileList(O)||F.endsWith(M,"[]"))&&(D=F.toArray(O)))return M=Up(M),D.forEach(function(P,q){!(F.isUndefined(P)||P===null)&&e.append(c===!0?Rd([M],q,l):c===null?M:M+"[]",y(P))}),!1}return Td(O)?!0:(e.append(Rd(T,M,l),y(O)),!1)}const A=Object.assign(R0,{defaultVisitor:_,convertValue:y,isVisitable:Td});function x(O,M,T=0){if(!F.isUndefined(O)){if(S(T),m.indexOf(O)!==-1)throw new Error("Circular reference detected in "+M.join("."));m.push(O),F.forEach(O,function(z,P){(!(F.isUndefined(z)||z===null)&&i.call(e,z,F.isString(P)?P.trim():P,M,A))===!0&&x(z,M?M.concat(P):[P],T+1)}),m.pop()}}if(!F.isObject(o))throw new TypeError("data must be an object");return x(o),e}function Pp(o){const e={"!":"%21","'":"%27","(":"%28",")":"%29","~":"%7E","%20":"+"};return encodeURIComponent(o).replace(/[!'()~]|%20/g,function(a){return e[a]})}function kd(o,e){this._pairs=[],o&&Kl(o,this,e)}const Bp=kd.prototype;Bp.append=function(e,n){this._pairs.push([e,n])},Bp.toString=function(e){const n=e?a=>e.call(this,a,Pp):Pp;return this._pairs.map(function(i){return n(i[0])+"="+n(i[1])},"").join("&")};function k0(o){return encodeURIComponent(o).replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,"+")}function qp(o,e,n){if(!e)return o;o=o||"";const a=F.isFunction(n)?{serialize:n}:n,i=F.getSafeProp(a,"encode")||k0,l=F.getSafeProp(a,"serialize");let c;if(l?c=l(e,a):c=F.isURLSearchParams(e)?e.toString():new kd(e,a).toString(i),c){const d=o.indexOf("#");d!==-1&&(o=o.slice(0,d)),o+=(o.indexOf("?")===-1?"?":"&")+c}return o}class Gp{constructor(){this.handlers=[]}use(e,n,a){return this.handlers.push({fulfilled:e,rejected:n,synchronous:a?a.synchronous:!1,runWhen:a?a.runWhen:null}),this.handlers.length-1}eject(e){this.handlers[e]&&(this.handlers[e]=null)}clear(){this.handlers&&(this.handlers=[])}forEach(e){F.forEach(this.handlers,function(a){a!==null&&e(a)})}}const Dd={silentJSONParsing:!0,forcedJSONParsing:!0,clarifyTimeoutError:!1,legacyInterceptorReqResOrdering:!0,advertiseZstdAcceptEncoding:!1,validateStatusUndefinedResolves:!0},D0={isBrowser:!0,classes:{URLSearchParams:typeof URLSearchParams<"u"?URLSearchParams:kd,FormData:typeof FormData<"u"?FormData:null,Blob:typeof Blob<"u"?Blob:null},protocols:["http","https","file","blob","url","data"]},Od=typeof window<"u"&&typeof document<"u",Ld=typeof navigator=="object"&&navigator||void 0,O0=Od&&(!Ld||["ReactNative","NativeScript","NS"].indexOf(Ld.product)<0),L0=typeof WorkerGlobalScope<"u"&&self instanceof WorkerGlobalScope&&typeof self.importScripts=="function",M0=Od&&window.location.href||"http://localhost",mn={...Object.freeze(Object.defineProperty({__proto__:null,hasBrowserEnv:Od,hasStandardBrowserEnv:O0,hasStandardBrowserWebWorkerEnv:L0,navigator:Ld,origin:M0},Symbol.toStringTag,{value:"Module"})),...D0};function $0(o,e){return Kl(o,new mn.classes.URLSearchParams,{visitor:function(n,a,i,l){return mn.isNode&&F.isBuffer(n)?(this.append(a,n.toString("base64")),!1):l.defaultVisitor.apply(this,arguments)},...e})}const Vp=zp;function Fp(o){if(o>Vp)throw new Ce("FormData field is too deeply nested ("+o+" levels). Max depth: "+Vp,Ce.ERR_FORM_DATA_DEPTH_EXCEEDED)}function N0(o){const e=[],n=/\w+|\[(\w*)]/g;let a;for(;(a=n.exec(o))!==null;)Fp(e.length),e.push(a[0]==="[]"?"":a[1]||a[0]);return e}function I0(o){const e={},n=Object.keys(o);let a;const i=n.length;let l;for(a=0;a=n.length;return c=!c&&F.isArray(i)?i.length:c,f?(F.hasOwnProp(i,c)?i[c]=F.isArray(i[c])?i[c].concat(a):[i[c],a]:i[c]=a,!d):((!F.hasOwnProp(i,c)||!F.isObject(i[c]))&&(i[c]=[]),e(n,a,i[c],l)&&F.isArray(i[c])&&(i[c]=I0(i[c])),!d)}if(F.isFormData(o)&&F.isFunction(o.entries)){const n={};return F.forEachEntry(o,(a,i)=>{e(N0(a),i,n,0)}),n}return null}const ls=(o,e)=>o!=null&&F.hasOwnProp(o,e)?o[e]:void 0;function H0(o,e,n){if(F.isString(o))try{return(e||JSON.parse)(o),F.trim(o)}catch(a){if(a.name!=="SyntaxError")throw a}return(n||JSON.stringify)(o)}const go={transitional:Dd,adapter:["xhr","http","fetch"],transformRequest:[function(e,n){const a=n.getContentType()||"",i=a.indexOf("application/json")>-1,l=F.isObject(e);if(l&&F.isHTMLForm(e)&&(e=new FormData(e)),F.isFormData(e))return i?JSON.stringify(Kp(e)):e;if(F.isArrayBuffer(e)||F.isBuffer(e)||F.isStream(e)||F.isFile(e)||F.isBlob(e)||F.isReadableStream(e))return e;if(F.isArrayBufferView(e))return e.buffer;if(F.isURLSearchParams(e))return n.setContentType("application/x-www-form-urlencoded;charset=utf-8",!1),e.toString();let d;if(l){const f=ls(this,"formSerializer");if(a.indexOf("application/x-www-form-urlencoded")>-1)return $0(e,f).toString();if((d=F.isFileList(e))||a.indexOf("multipart/form-data")>-1){const g=ls(this,"env"),m=g&&g.FormData;return Kl(d?{"files[]":e}:e,m&&new m,f)}}return l||i?(n.setContentType("application/json",!1),H0(e)):e}],transformResponse:[function(e){const n=ls(this,"transitional")||go.transitional,a=n&&n.forcedJSONParsing,i=ls(this,"responseType"),l=i==="json";if(F.isResponse(e)||F.isReadableStream(e))return e;if(e&&F.isString(e)&&(a&&!i||l)){const d=!(n&&n.silentJSONParsing)&&l;try{return JSON.parse(e,ls(this,"parseReviver"))}catch(f){if(d)throw f.name==="SyntaxError"?Ce.from(f,Ce.ERR_BAD_RESPONSE,this,null,ls(this,"response")):f}}return e}],timeout:0,xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN",maxContentLength:-1,maxBodyLength:-1,env:{FormData:mn.classes.FormData,Blob:mn.classes.Blob},validateStatus:function(e){return e>=200&&e<300},headers:{common:{Accept:"application/json, text/plain, */*","Content-Type":void 0}}};F.forEach(["delete","get","head","post","put","patch","query"],o=>{go.headers[o]={}});function Md(o,e){const n=this||go,a=e||n,i=An.from(a.headers);let l=a.data;return F.forEach(o,function(d){l=d.call(n,l,i.normalize(),e?e.status:void 0)}),i.normalize(),l}function Wp(o){return!!(o&&o.__CANCEL__)}let po=class extends Ce{constructor(e,n,a){super(e??"canceled",Ce.ERR_CANCELED,n,a),this.name="CanceledError",this.__CANCEL__=!0}};function Yp(o,e,n){const a=n.config.validateStatus;!n.status||!a||a(n.status)?o(n):e(new Ce("Request failed with status code "+n.status,n.status>=400&&n.status<500?Ce.ERR_BAD_REQUEST:Ce.ERR_BAD_RESPONSE,n.config,n.request,n))}function j0(o){const e=/^([-+\w]{1,25}):(?:\/\/)?/.exec(o);return e&&e[1]||""}function z0(o,e){o=o||10;const n=new Array(o),a=new Array(o);let i=0,l=0,c;return e=e!==void 0?e:1e3,function(f){const g=Date.now(),m=a[l];c||(c=g),n[i]=f,a[i]=g;let y=l,S=0;for(;y!==i;)S+=n[y++],y=y%o;if(i=(i+1)%o,i===l&&(l=(l+1)%o),g-c{n=m,i=null,l&&(clearTimeout(l),l=null),o(...g)};return[(...g)=>{const m=Date.now(),y=m-n;y>=a?c(g,m):(i=g,l||(l=setTimeout(()=>{l=null,c(i)},a-y)))},()=>i&&c(i)]}const Wl=(o,e,n=3)=>{let a=0;const i=z0(50,250);return U0(l=>{if(!l||typeof l.loaded!="number")return;const c=l.loaded,d=l.lengthComputable?l.total:void 0,f=d!=null?Math.min(c,d):c,g=Math.max(0,f-a),m=i(g);a=Math.max(a,f);const y={loaded:f,total:d,progress:d?f/d:void 0,bytes:g,rate:m||void 0,estimated:m&&d?(d-f)/m:void 0,event:l,lengthComputable:d!=null,[e?"download":"upload"]:!0};o(y)},n)},Xp=(o,e)=>{const n=o!=null;return[a=>e[0]({lengthComputable:n,total:o,loaded:a}),e[1]]},Jp=o=>(...e)=>F.asap(()=>o(...e)),P0=mn.hasStandardBrowserEnv?((o,e)=>n=>(n=new URL(n,mn.origin),o.protocol===n.protocol&&o.host===n.host&&(e||o.port===n.port)))(new URL(mn.origin),mn.navigator&&/(msie|trident)/i.test(mn.navigator.userAgent)):()=>!0,B0=mn.hasStandardBrowserEnv?{write(o,e,n,a,i,l,c){if(typeof document>"u")return;const d=[`${o}=${encodeURIComponent(e)}`];F.isNumber(n)&&d.push(`expires=${new Date(n).toUTCString()}`),F.isString(a)&&d.push(`path=${a}`),F.isString(i)&&d.push(`domain=${i}`),l===!0&&d.push("secure"),F.isString(c)&&d.push(`SameSite=${c}`),document.cookie=d.join("; ")},read(o){if(typeof document>"u")return null;const e=document.cookie.split(";");for(let n=0;no instanceof An?{...o}:o;function mi(o,e){o=o||{},e=e||{};const n=Object.create(null);Object.defineProperty(n,"hasOwnProperty",{__proto__:null,value:Object.prototype.hasOwnProperty,enumerable:!1,writable:!0,configurable:!0});function a(m,y,S,v){return F.isPlainObject(m)&&F.isPlainObject(y)?F.merge.call({caseless:v},m,y):F.isPlainObject(y)?F.merge({},y):F.isArray(y)?y.slice():y}function i(m,y,S,v){if(F.isUndefined(y)){if(!F.isUndefined(m))return a(void 0,m,S,v)}else return a(m,y,S,v)}function l(m,y){if(!F.isUndefined(y))return a(void 0,y)}function c(m,y){if(F.isUndefined(y)){if(!F.isUndefined(m))return a(void 0,m)}else return a(void 0,y)}function d(m){const y=F.hasOwnProp(e,"transitional")?e.transitional:void 0;if(!F.isUndefined(y))if(F.isPlainObject(y)){if(F.hasOwnProp(y,m))return y[m]}else return;const S=F.hasOwnProp(o,"transitional")?o.transitional:void 0;if(F.isPlainObject(S)&&F.hasOwnProp(S,m))return S[m]}function f(m,y,S){if(F.hasOwnProp(e,S))return a(m,y);if(F.hasOwnProp(o,S))return a(void 0,m)}const g={url:l,method:l,data:l,baseURL:c,transformRequest:c,transformResponse:c,paramsSerializer:c,timeout:c,timeoutMessage:c,withCredentials:c,withXSRFToken:c,adapter:c,responseType:c,xsrfCookieName:c,xsrfHeaderName:c,onUploadProgress:c,onDownloadProgress:c,decompress:c,maxContentLength:c,maxBodyLength:c,beforeRedirect:c,transport:c,httpAgent:c,httpsAgent:c,cancelToken:c,socketPath:c,allowedSocketPaths:c,responseEncoding:c,validateStatus:f,headers:(m,y,S)=>i(em(m),em(y),S,!0)};return F.forEach(Object.keys({...o,...e}),function(y){if(y==="__proto__"||y==="constructor"||y==="prototype")return;const S=F.hasOwnProp(g,y)?g[y]:i,v=F.hasOwnProp(o,y)?o[y]:void 0,_=F.hasOwnProp(e,y)?e[y]:void 0,A=S(v,_,y);F.isUndefined(A)&&S!==f||(n[y]=A)}),F.hasOwnProp(e,"validateStatus")&&F.isUndefined(e.validateStatus)&&d("validateStatusUndefinedResolves")===!1&&(F.hasOwnProp(o,"validateStatus")?n.validateStatus=a(void 0,o.validateStatus):delete n.validateStatus),n}const Y0=["content-type","content-length"];function X0(o,e,n){if(n!=="content-only"){o.set(e);return}Object.entries(e||{}).forEach(([a,i])=>{Y0.includes(a.toLowerCase())&&o.set(a,i)})}const J0=o=>encodeURIComponent(o).replace(/%([0-9A-F]{2})/gi,(e,n)=>String.fromCharCode(parseInt(n,16)));function tm(o){const e=mi({},o),n=S=>F.hasOwnProp(e,S)?e[S]:void 0,a=n("data");let i=n("withXSRFToken");const l=n("xsrfHeaderName"),c=n("xsrfCookieName");let d=n("headers");const f=n("auth"),g=n("baseURL"),m=n("allowAbsoluteUrls"),y=n("url");if(e.headers=d=An.from(d),e.url=qp(Zp(g,y,m,e),n("params"),n("paramsSerializer")),f){const S=F.getSafeProp(f,"username")||"",v=F.getSafeProp(f,"password")||"";try{d.set("Authorization","Basic "+btoa(S+":"+(v?J0(v):"")))}catch(_){throw Ce.from(_,Ce.ERR_BAD_OPTION_VALUE,o)}}if(F.isFormData(a)&&(mn.hasStandardBrowserEnv||mn.hasStandardBrowserWebWorkerEnv||F.isReactNative(a)?d.setContentType(void 0):F.isFunction(a.getHeaders)&&X0(d,a.getHeaders(),n("formDataHeaderPolicy"))),mn.hasStandardBrowserEnv&&(F.isFunction(i)&&(i=i(e)),i===!0||i==null&&P0(e.url))){const v=l&&c&&B0.read(c);v&&d.set(l,v)}return e}const Q0=typeof XMLHttpRequest<"u"&&function(o){return new Promise(function(n,a){const i=tm(o);let l=i.data;const c=An.from(i.headers).normalize();let{responseType:d,onUploadProgress:f,onDownloadProgress:g}=i,m,y,S,v,_;function A(){v&&v(),_&&_(),i.cancelToken&&i.cancelToken.unsubscribe(m),i.signal&&i.signal.removeEventListener("abort",m)}let x=new XMLHttpRequest;x.open(i.method.toUpperCase(),i.url,!0),x.timeout=i.timeout;function O(){if(!x)return;const T=An.from("getAllResponseHeaders"in x&&x.getAllResponseHeaders()),z={data:!d||d==="text"||d==="json"?x.responseText:x.response,status:x.status,statusText:x.statusText,headers:T,config:o,request:x};Yp(function(q){n(q),A()},function(q){a(q),A()},z),x=null}"onloadend"in x?x.onloadend=O:x.onreadystatechange=function(){!x||x.readyState!==4||x.status===0&&!(x.responseURL&&x.responseURL.startsWith("file:"))||setTimeout(O)},x.onabort=function(){x&&(a(new Ce("Request aborted",Ce.ECONNABORTED,o,x)),A(),x=null)},x.onerror=function(D){const z=D&&D.message?D.message:"Network Error",P=new Ce(z,Ce.ERR_NETWORK,o,x);P.event=D||null,a(P),A(),x=null},x.ontimeout=function(){let D=i.timeout?"timeout of "+i.timeout+"ms exceeded":"timeout exceeded";const z=i.transitional||Dd;i.timeoutErrorMessage&&(D=i.timeoutErrorMessage),a(new Ce(D,z.clarifyTimeoutError?Ce.ETIMEDOUT:Ce.ECONNABORTED,o,x)),A(),x=null},l===void 0&&c.setContentType(null),"setRequestHeader"in x&&F.forEach(Hp(c),function(D,z){x.setRequestHeader(z,D)}),F.isUndefined(i.withCredentials)||(x.withCredentials=!!i.withCredentials),d&&d!=="json"&&(x.responseType=i.responseType),g&&([S,_]=Wl(g,!0),x.addEventListener("progress",S)),f&&x.upload&&([y,v]=Wl(f),x.upload.addEventListener("progress",y),x.upload.addEventListener("loadend",v)),(i.cancelToken||i.signal)&&(m=T=>{x&&(a(!T||T.type?new po(null,o,x):T),x.abort(),A(),x=null)},i.cancelToken&&i.cancelToken.subscribe(m),i.signal&&(i.signal.aborted?m():i.signal.addEventListener("abort",m)));const M=j0(i.url);if(M&&!mn.protocols.includes(M)){a(new Ce("Unsupported protocol "+M+":",Ce.ERR_BAD_REQUEST,o)),A();return}x.send(l||null)})},Z0=(o,e)=>{if(o=o?o.filter(Boolean):[],!e&&!o.length)return;const n=new AbortController;let a=!1;const i=function(f){if(!a){a=!0,c();const g=f instanceof Error?f:this.reason;n.abort(g instanceof Ce?g:new po(g instanceof Error?g.message:g))}};let l=e&&setTimeout(()=>{l=null,i(new Ce(`timeout of ${e}ms exceeded`,Ce.ETIMEDOUT))},e);const c=()=>{o&&(l&&clearTimeout(l),l=null,o.forEach(f=>{f.unsubscribe?f.unsubscribe(i):f.removeEventListener("abort",i)}),o=null)};o.forEach(f=>f.addEventListener("abort",i,{once:!0}));const{signal:d}=n;return d.unsubscribe=()=>F.asap(c),d},eC=function*(o,e){let n=o.byteLength;if(n{const i=tC(o,e);let l=0,c,d=f=>{c||(c=!0,a&&a(f))};return new ReadableStream({async pull(f){try{const{done:g,value:m}=await i.next();if(g){d(),f.close();return}let y=m.byteLength;if(n){let S=l+=y;n(S)}f.enqueue(new Uint8Array(m))}catch(g){throw d(g),g}},cancel(f){return d(f),i.return()}},{highWaterMark:2})},Yl=o=>o>=48&&o<=57||o>=65&&o<=70||o>=97&&o<=102,rC=(o,e,n)=>e+2v>=2&&a.charCodeAt(v-2)===37&&a.charCodeAt(v-1)===51&&(a.charCodeAt(v)===68||a.charCodeAt(v)===100);g>=0&&(a.charCodeAt(g)===61?(f++,g--):m(g)&&(f++,g-=3)),f===1&&g>=0&&(a.charCodeAt(g)===61||m(g))&&f++;const S=Math.floor(c/4)*3-(f||0);return S>0?S:0}let l=0;for(let c=0,d=a.length;c=55296&&f<=56319&&c+1=56320&&g<=57343?(l+=4,c++):l+=3}else l+=3}return l}const $d="1.18.1",rm=64*1024,{isFunction:Xl}=F,iC=o=>encodeURIComponent(o).replace(/%([0-9A-F]{2})/gi,(e,n)=>String.fromCharCode(parseInt(n,16))),am=o=>{if(!F.isString(o))return o;try{return decodeURIComponent(o)}catch{return o}},im=(o,...e)=>{try{return!!o(...e)}catch{return!1}},sC=o=>{const e=o.indexOf("://");let n=o;return e!==-1&&(n=n.slice(e+3)),n.includes("@")||n.includes(":")},oC=o=>{const e=F.global!==void 0&&F.global!==null?F.global:globalThis,{ReadableStream:n,TextEncoder:a}=e;o=F.merge.call({skipUndefined:!0},{Request:e.Request,Response:e.Response},o);const{fetch:i,Request:l,Response:c}=o,d=i?Xl(i):typeof fetch=="function",f=Xl(l),g=Xl(c);if(!d)return!1;const m=d&&Xl(n),y=d&&(typeof a=="function"?(O=>M=>O.encode(M))(new a):async O=>new Uint8Array(await new l(O).arrayBuffer())),S=f&&m&&im(()=>{let O=!1;const M=new l(mn.origin,{body:new n,method:"POST",get duplex(){return O=!0,"half"}}),T=M.headers.has("Content-Type");return M.body!=null&&M.body.cancel(),O&&!T}),v=g&&m&&im(()=>F.isReadableStream(new c("").body)),_={stream:v&&(O=>O.body)};d&&["text","arrayBuffer","blob","formData","stream"].forEach(O=>{!_[O]&&(_[O]=(M,T)=>{let D=M&&M[O];if(D)return D.call(M);throw new Ce(`Response type '${O}' is not supported`,Ce.ERR_NOT_SUPPORT,T)})});const A=async O=>{if(O==null)return 0;if(F.isBlob(O))return O.size;if(F.isSpecCompliantForm(O))return(await new l(mn.origin,{method:"POST",body:O}).arrayBuffer()).byteLength;if(F.isArrayBufferView(O)||F.isArrayBuffer(O))return O.byteLength;if(F.isURLSearchParams(O)&&(O=O+""),F.isString(O))return(await y(O)).byteLength},x=async(O,M)=>{const T=F.toFiniteNumber(O.getContentLength());return T??A(M)};return async O=>{let{url:M,method:T,data:D,signal:z,cancelToken:P,timeout:q,onDownloadProgress:W,onUploadProgress:he,responseType:Ee,headers:be,withCredentials:Ue="same-origin",fetchOptions:Fe,maxContentLength:Ge,maxBodyLength:wt}=tm(O);const J=F.isNumber(Ge)&&Ge>-1,ue=F.isNumber(wt)&&wt>-1,Me=ee=>F.hasOwnProp(O,ee)?O[ee]:void 0;let te=i||fetch;Ee=Ee?(Ee+"").toLowerCase():"text";let pe=Z0([z,P&&P.toAbortSignal()],q),N=null;const R=pe&&pe.unsubscribe&&(()=>{pe.unsubscribe()});let de,fe=null;const xe=()=>new Ce("Request body larger than maxBodyLength limit",Ce.ERR_BAD_REQUEST,O,N);try{let ee;const _e=Me("auth");if(_e){const Oe=F.getSafeProp(_e,"username")||"",nt=F.getSafeProp(_e,"password")||"";ee={username:Oe,password:nt}}if(sC(M)){const Oe=new URL(M,mn.origin);if(!ee&&(Oe.username||Oe.password)){const nt=am(Oe.username),sn=am(Oe.password);ee={username:nt,password:sn}}(Oe.username||Oe.password)&&(Oe.username="",Oe.password="",M=Oe.href)}if(ee&&(be.delete("authorization"),be.set("Authorization","Basic "+btoa(iC((ee.username||"")+":"+(ee.password||""))))),J&&typeof M=="string"&&M.startsWith("data:")&&aC(M)>Ge)throw new Ce("maxContentLength size of "+Ge+" exceeded",Ce.ERR_BAD_RESPONSE,O,N);if(ue&&T!=="get"&&T!=="head"){const Oe=await A(D);if(typeof Oe=="number"&&isFinite(Oe)&&(de=Oe,Oe>wt))throw xe()}const Be=ue&&(F.isReadableStream(D)||F.isStream(D)),ke=(Oe,nt,sn)=>nm(Oe,rm,Mt=>{if(ue&&Mt>wt)throw fe=xe();nt&&nt(Mt)},sn);if(S&&T!=="get"&&T!=="head"&&(he||Be)){if(de=de??await x(be,D),de!==0||Be){let Oe=new l(M,{method:"POST",body:D,duplex:"half"}),nt;if(F.isFormData(D)&&(nt=Oe.headers.get("content-type"))&&be.setContentType(nt),Oe.body){const[sn,Mt]=he&&Xp(de,Wl(Jp(he)))||[];D=ke(Oe.body,sn,Mt)}}}else if(Be&&!f&&m&&T!=="get"&&T!=="head")D=ke(D);else if(Be&&f&&!S&&T!=="get"&&T!=="head")throw new Ce("Stream request bodies are not supported by the current fetch implementation",Ce.ERR_NOT_SUPPORT,O,N);F.isString(Ue)||(Ue=Ue?"include":"omit");const Ie=f&&"credentials"in l.prototype;if(F.isFormData(D)){const Oe=be.getContentType();Oe&&/^multipart\/form-data/i.test(Oe)&&!/boundary=/i.test(Oe)&&be.delete("content-type")}be.set("User-Agent","axios/"+$d,!1);const Ct={...Fe,signal:pe,method:T.toUpperCase(),headers:Hp(be.normalize()),body:D,duplex:"half",credentials:Ie?Ue:void 0};N=f&&new l(M,Ct);let ot=await(f?te(N,Fe):te(M,Ct));const Ot=An.from(ot.headers);if(J){const Oe=F.toFiniteNumber(Ot.getContentLength());if(Oe!=null&&Oe>Ge)throw new Ce("maxContentLength size of "+Ge+" exceeded",Ce.ERR_BAD_RESPONSE,O,N)}const vt=v&&(Ee==="stream"||Ee==="response");if(v&&ot.body&&(W||J||vt&&R)){const Oe={};["status","statusText","headers"].forEach(ln=>{Oe[ln]=ot[ln]});const nt=F.toFiniteNumber(Ot.getContentLength()),[sn,Mt]=W&&Xp(nt,Wl(Jp(W),!0))||[];let Vt=0;const on=ln=>{if(J&&(Vt=ln,Vt>Ge))throw new Ce("maxContentLength size of "+Ge+" exceeded",Ce.ERR_BAD_RESPONSE,O,N);sn&&sn(ln)};ot=new c(nm(ot.body,rm,on,()=>{Mt&&Mt(),R&&R()}),Oe)}Ee=Ee||"text";let _t=await _[F.findKey(_,Ee)||"text"](ot,O);if(J&&!v&&!vt){let Oe;if(_t!=null&&(typeof _t.byteLength=="number"?Oe=_t.byteLength:typeof _t.size=="number"?Oe=_t.size:typeof _t=="string"&&(Oe=typeof a=="function"?new a().encode(_t).byteLength:_t.length)),typeof Oe=="number"&&Oe>Ge)throw new Ce("maxContentLength size of "+Ge+" exceeded",Ce.ERR_BAD_RESPONSE,O,N)}return!vt&&R&&R(),await new Promise((Oe,nt)=>{Yp(Oe,nt,{data:_t,headers:An.from(ot.headers),status:ot.status,statusText:ot.statusText,config:O,request:N})})}catch(ee){if(R&&R(),pe&&pe.aborted&&pe.reason instanceof Ce){const _e=pe.reason;throw _e.config=O,N&&(_e.request=N),ee!==_e&&Object.defineProperty(_e,"cause",{__proto__:null,value:ee,writable:!0,enumerable:!1,configurable:!0}),_e}if(fe)throw N&&!fe.request&&(fe.request=N),fe;if(ee instanceof Ce)throw N&&!ee.request&&(ee.request=N),ee;if(ee&&ee.name==="TypeError"&&/Load failed|fetch/i.test(ee.message)){const _e=new Ce("Network Error",Ce.ERR_NETWORK,O,N,ee&&ee.response);throw Object.defineProperty(_e,"cause",{__proto__:null,value:ee.cause||ee,writable:!0,enumerable:!1,configurable:!0}),_e}throw Ce.from(ee,ee&&ee.code,O,N,ee&&ee.response)}}},lC=new Map,sm=o=>{let e=o&&o.env||{};const{fetch:n,Request:a,Response:i}=e,l=[a,i,n];let c=l.length,d=c,f,g,m=lC;for(;d--;)f=l[d],g=m.get(f),g===void 0&&m.set(f,g=d?new Map:oC(e)),m=g;return g};sm();const Nd={http:x0,xhr:Q0,fetch:{get:sm}};F.forEach(Nd,(o,e)=>{if(o){try{Object.defineProperty(o,"name",{__proto__:null,value:e})}catch{}Object.defineProperty(o,"adapterName",{__proto__:null,value:e})}});const om=o=>`- ${o}`,cC=o=>F.isFunction(o)||o===null||o===!1;function uC(o,e){o=F.isArray(o)?o:[o];const{length:n}=o;let a,i;const l={};for(let c=0;c`adapter ${f} `+(g===!1?"is not supported by the environment":"is not available in the build"));let d=n?c.length>1?`since : -`+c.map(om).join(` -`):" "+om(c[0]):"as no adapter specified";throw new Ce("There is no suitable adapter to dispatch the request "+d,Ce.ERR_NOT_SUPPORT)}return i}const lm={getAdapter:uC,adapters:Nd};function Id(o){if(o.cancelToken&&o.cancelToken.throwIfRequested(),o.signal&&o.signal.aborted)throw new po(null,o)}function cm(o){return Id(o),o.headers=An.from(o.headers),o.data=Md.call(o,o.transformRequest),["post","put","patch"].indexOf(o.method)!==-1&&o.headers.setContentType("application/x-www-form-urlencoded",!1),lm.getAdapter(o.adapter||go.adapter,o)(o).then(function(a){Id(o),o.response=a;try{a.data=Md.call(o,o.transformResponse,a)}finally{delete o.response}return a.headers=An.from(a.headers),a},function(a){if(!Wp(a)&&(Id(o),a&&a.response)){o.response=a.response;try{a.response.data=Md.call(o,o.transformResponse,a.response)}finally{delete o.response}a.response.headers=An.from(a.response.headers)}return Promise.reject(a)})}const Jl={};["object","boolean","number","function","string","symbol"].forEach((o,e)=>{Jl[o]=function(a){return typeof a===o||"a"+(e<1?"n ":" ")+o}});const um={};Jl.transitional=function(e,n,a){function i(l,c){return"[Axios v"+$d+"] Transitional option '"+l+"'"+c+(a?". "+a:"")}return(l,c,d)=>{if(e===!1)throw new Ce(i(c," has been removed"+(n?" in "+n:"")),Ce.ERR_DEPRECATED);return n&&!um[c]&&(um[c]=!0,console.warn(i(c," has been deprecated since v"+n+" and will be removed in the near future"))),e?e(l,c,d):!0}},Jl.spelling=function(e){return(n,a)=>(console.warn(`${a} is likely a misspelling of ${e}`),!0)};function dC(o,e,n){if(typeof o!="object"||o===null)throw new Ce("options must be an object",Ce.ERR_BAD_OPTION_VALUE);const a=Object.keys(o);let i=a.length;for(;i-- >0;){const l=a[i],c=Object.prototype.hasOwnProperty.call(e,l)?e[l]:void 0;if(c){const d=o[l],f=d===void 0||c(d,l,o);if(f!==!0)throw new Ce("option "+l+" must be "+f,Ce.ERR_BAD_OPTION_VALUE);continue}if(n!==!0)throw new Ce("Unknown option "+l,Ce.ERR_BAD_OPTION)}}const Ql={assertOptions:dC,validators:Jl},xn=Ql.validators;let yi=class{constructor(e){this.defaults=e||{},this.interceptors={request:new Gp,response:new Gp}}async request(e,n){try{return await this._request(e,n)}catch(a){if(a instanceof Error){let i={};Error.captureStackTrace?Error.captureStackTrace(i):i=new Error;const l=(()=>{if(!i.stack)return"";const c=i.stack.indexOf(` +`+u.stack}}var fn=Object.prototype.hasOwnProperty,rn=o.unstable_scheduleCallback,hn=o.unstable_cancelCallback,Fn=o.unstable_shouldYield,Pr=o.unstable_requestPaint,Pt=o.unstable_now,wi=o.unstable_getCurrentPriorityLevel,na=o.unstable_ImmediatePriority,gn=o.unstable_UserBlockingPriority,Rn=o.unstable_NormalPriority,tt=o.unstable_LowPriority,$n=o.unstable_IdlePriority,pn=o.log,ra=o.unstable_setDisableYieldValue,ir=null,an=null;function En(t){if(typeof pn=="function"&&ra(t),an&&typeof an.setStrictMode=="function")try{an.setStrictMode(ir,t)}catch{}}var sn=Math.clz32?Math.clz32:ue,V=Math.log,se=Math.LN2;function ue(t){return t>>>=0,t===0?32:31-(V(t)/se|0)|0}var ee=256,ve=262144,he=4194304;function Ce(t){var r=t&42;if(r!==0)return r;switch(t&-t){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return t&261888;case 262144:case 524288:case 1048576:case 2097152:return t&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return t&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return t}}function Ae(t,r,s){var u=t.pendingLanes;if(u===0)return 0;var h=0,p=t.suspendedLanes,w=t.pingedLanes;t=t.warmLanes;var k=u&134217727;return k!==0?(u=k&~p,u!==0?h=Ce(u):(w&=k,w!==0?h=Ce(w):s||(s=k&~t,s!==0&&(h=Ce(s))))):(k=u&~p,k!==0?h=Ce(k):w!==0?h=Ce(w):s||(s=u&~t,s!==0&&(h=Ce(s)))),h===0?0:r!==0&&r!==h&&(r&p)===0&&(p=h&-h,s=r&-r,p>=s||p===32&&(s&4194048)!==0)?r:h}function Fe(t,r){return(t.pendingLanes&~(t.suspendedLanes&~t.pingedLanes)&r)===0}function Re(t,r){switch(t){case 1:case 2:case 4:case 8:case 64:return r+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return r+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function Ne(){var t=he;return he<<=1,(he&62914560)===0&&(he=4194304),t}function ke(t){for(var r=[],s=0;31>s;s++)r.push(t);return r}function Ze(t,r){t.pendingLanes|=r,r!==268435456&&(t.suspendedLanes=0,t.pingedLanes=0,t.warmLanes=0)}function ze(t,r,s,u,h,p){var w=t.pendingLanes;t.pendingLanes=s,t.suspendedLanes=0,t.pingedLanes=0,t.warmLanes=0,t.expiredLanes&=s,t.entangledLanes&=s,t.errorRecoveryDisabledLanes&=s,t.shellSuspendCounter=0;var k=t.entanglements,j=t.expirationTimes,X=t.hiddenUpdates;for(s=w&~s;0"u")return null;try{return t.activeElement||t.body}catch{return t.body}}var kf=/[\n"\\]/g;function In(t){return t.replace(kf,function(r){return"\\"+r.charCodeAt(0).toString(16)+" "})}function ms(t,r,s,u,h,p,w,k){t.name="",w!=null&&typeof w!="function"&&typeof w!="symbol"&&typeof w!="boolean"?t.type=w:t.removeAttribute("type"),r!=null?w==="number"?(r===0&&t.value===""||t.value!=r)&&(t.value=""+An(r)):t.value!==""+An(r)&&(t.value=""+An(r)):w!=="submit"&&w!=="reset"||t.removeAttribute("value"),r!=null?ia(t,w,An(r)):s!=null?ia(t,w,An(s)):u!=null&&t.removeAttribute("value"),h==null&&p!=null&&(t.defaultChecked=!!p),h!=null&&(t.checked=h&&typeof h!="function"&&typeof h!="symbol"),k!=null&&typeof k!="function"&&typeof k!="symbol"&&typeof k!="boolean"?t.name=""+An(k):t.removeAttribute("name")}function _i(t,r,s,u,h,p,w,k){if(p!=null&&typeof p!="function"&&typeof p!="symbol"&&typeof p!="boolean"&&(t.type=p),r!=null||s!=null){if(!(p!=="submit"&&p!=="reset"||r!=null)){Io(t);return}s=s!=null?""+An(s):"",r=r!=null?""+An(r):s,k||r===t.value||(t.value=r),t.defaultValue=r}u=u??h,u=typeof u!="function"&&typeof u!="symbol"&&!!u,t.checked=k?t.checked:!!u,t.defaultChecked=!!u,w!=null&&typeof w!="function"&&typeof w!="symbol"&&typeof w!="boolean"&&(t.name=w),Io(t)}function ia(t,r,s){r==="number"&&za(t.ownerDocument)===t||t.defaultValue===""+s||(t.defaultValue=""+s)}function ja(t,r,s,u){if(t=t.options,r){r={};for(var h=0;h"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),Bo=!1;if(Rr)try{var xi={};Object.defineProperty(xi,"passive",{get:function(){Bo=!0}}),window.addEventListener("test",xi,xi),window.removeEventListener("test",xi,xi)}catch{Bo=!1}var Vr=null,Po=null,bs=null;function Hc(){if(bs)return bs;var t,r=Po,s=r.length,u,h="value"in Vr?Vr.value:Vr.textContent,p=h.length;for(t=0;t=ne),Ke=" ",lt=!1;function gt(t,r){switch(t){case"keyup":return O.indexOf(r.keyCode)!==-1;case"keydown":return r.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function zt(t){return t=t.detail,typeof t=="object"&&"data"in t?t.data:null}var Dr=!1;function Kf(t,r){switch(t){case"compositionend":return zt(r);case"keypress":return r.which!==32?null:(lt=!0,Ke);case"textInput":return t=r.data,t===Ke&<?null:t;default:return null}}function Gc(t,r){if(Dr)return t==="compositionend"||!I&>(t,r)?(t=Hc(),bs=Po=Vr=null,Dr=!1,t):null;switch(t){case"paste":return null;case"keypress":if(!(r.ctrlKey||r.altKey||r.metaKey)||r.ctrlKey&&r.altKey){if(r.char&&1=r)return{node:s,offset:r-t};t=u}e:{for(;s;){if(s.nextSibling){s=s.nextSibling;break e}s=s.parentNode}s=void 0}s=Ay(s)}}function Ty(t,r){return t&&r?t===r?!0:t&&t.nodeType===3?!1:r&&r.nodeType===3?Ty(t,r.parentNode):"contains"in t?t.contains(r):t.compareDocumentPosition?!!(t.compareDocumentPosition(r)&16):!1:!1}function Ry(t){t=t!=null&&t.ownerDocument!=null&&t.ownerDocument.defaultView!=null?t.ownerDocument.defaultView:window;for(var r=za(t.document);r instanceof t.HTMLIFrameElement;){try{var s=typeof r.contentWindow.location.href=="string"}catch{s=!1}if(s)t=r.contentWindow;else break;r=za(t.document)}return r}function Qf(t){var r=t&&t.nodeName&&t.nodeName.toLowerCase();return r&&(r==="input"&&(t.type==="text"||t.type==="search"||t.type==="tel"||t.type==="url"||t.type==="password")||r==="textarea"||t.contentEditable==="true")}var UA=Rr&&"documentMode"in document&&11>=document.documentMode,Cs=null,Zf=null,Qo=null,eh=!1;function ky(t,r,s){var u=s.window===s?s.document:s.nodeType===9?s:s.ownerDocument;eh||Cs==null||Cs!==za(u)||(u=Cs,"selectionStart"in u&&Qf(u)?u={start:u.selectionStart,end:u.selectionEnd}:(u=(u.ownerDocument&&u.ownerDocument.defaultView||window).getSelection(),u={anchorNode:u.anchorNode,anchorOffset:u.anchorOffset,focusNode:u.focusNode,focusOffset:u.focusOffset}),Qo&&Jo(Qo,u)||(Qo=u,u=Iu(Zf,"onSelect"),0>=w,h-=w,Fr=1<<32-sn(r)+h|s<Je?(st=Ie,Ie=null):st=Ie.sibling;var mt=Q(F,Ie,Y[Je],oe);if(mt===null){Ie===null&&(Ie=st);break}t&&Ie&&mt.alternate===null&&r(F,Ie),G=p(mt,G,Je),pt===null?Be=mt:pt.sibling=mt,pt=mt,Ie=st}if(Je===Y.length)return s(F,Ie),ct&&ca(F,Je),Be;if(Ie===null){for(;JeJe?(st=Ie,Ie=null):st=Ie.sibling;var di=Q(F,Ie,mt.value,oe);if(di===null){Ie===null&&(Ie=st);break}t&&Ie&&di.alternate===null&&r(F,Ie),G=p(di,G,Je),pt===null?Be=di:pt.sibling=di,pt=di,Ie=st}if(mt.done)return s(F,Ie),ct&&ca(F,Je),Be;if(Ie===null){for(;!mt.done;Je++,mt=Y.next())mt=le(F,mt.value,oe),mt!==null&&(G=p(mt,G,Je),pt===null?Be=mt:pt.sibling=mt,pt=mt);return ct&&ca(F,Je),Be}for(Ie=u(Ie);!mt.done;Je++,mt=Y.next())mt=te(Ie,F,Je,mt.value,oe),mt!==null&&(t&&mt.alternate!==null&&Ie.delete(mt.key===null?Je:mt.key),G=p(mt,G,Je),pt===null?Be=mt:pt.sibling=mt,pt=mt);return t&&Ie.forEach(function(sT){return r(F,sT)}),ct&&ca(F,Je),Be}function Tt(F,G,Y,oe){if(typeof Y=="object"&&Y!==null&&Y.type===A&&Y.key===null&&(Y=Y.props.children),typeof Y=="object"&&Y!==null){switch(Y.$$typeof){case v:e:{for(var Be=Y.key;G!==null;){if(G.key===Be){if(Be=Y.type,Be===A){if(G.tag===7){s(F,G.sibling),oe=h(G,Y.props.children),oe.return=F,F=oe;break e}}else if(G.elementType===Be||typeof Be=="object"&&Be!==null&&Be.$$typeof===K&&Pi(Be)===G.type){s(F,G.sibling),oe=h(G,Y.props),al(oe,Y),oe.return=F,F=oe;break e}s(F,G);break}else r(F,G);G=G.sibling}Y.type===A?(oe=Hi(Y.props.children,F.mode,oe,Y.key),oe.return=F,F=oe):(oe=Zc(Y.type,Y.key,Y.props,null,F.mode,oe),al(oe,Y),oe.return=F,F=oe)}return w(F);case _:e:{for(Be=Y.key;G!==null;){if(G.key===Be)if(G.tag===4&&G.stateNode.containerInfo===Y.containerInfo&&G.stateNode.implementation===Y.implementation){s(F,G.sibling),oe=h(G,Y.children||[]),oe.return=F,F=oe;break e}else{s(F,G);break}else r(F,G);G=G.sibling}oe=oh(Y,F.mode,oe),oe.return=F,F=oe}return w(F);case K:return Y=Pi(Y),Tt(F,G,Y,oe)}if(St(Y))return De(F,G,Y,oe);if(Ue(Y)){if(Be=Ue(Y),typeof Be!="function")throw Error(a(150));return Y=Be.call(Y),Pe(F,G,Y,oe)}if(typeof Y.then=="function")return Tt(F,G,su(Y),oe);if(Y.$$typeof===R)return Tt(F,G,nu(F,Y),oe);ou(F,Y)}return typeof Y=="string"&&Y!==""||typeof Y=="number"||typeof Y=="bigint"?(Y=""+Y,G!==null&&G.tag===6?(s(F,G.sibling),oe=h(G,Y),oe.return=F,F=oe):(s(F,G),oe=sh(Y,F.mode,oe),oe.return=F,F=oe),w(F)):s(F,G)}return function(F,G,Y,oe){try{rl=0;var Be=Tt(F,G,Y,oe);return Ms=null,Be}catch(Ie){if(Ie===Ls||Ie===au)throw Ie;var pt=Wn(29,Ie,null,F.mode);return pt.lanes=oe,pt.return=F,pt}}}var Gi=Qy(!0),Zy=Qy(!1),Wa=!1;function vh(t){t.updateQueue={baseState:t.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function Sh(t,r){t=t.updateQueue,r.updateQueue===t&&(r.updateQueue={baseState:t.baseState,firstBaseUpdate:t.firstBaseUpdate,lastBaseUpdate:t.lastBaseUpdate,shared:t.shared,callbacks:null})}function Ya(t){return{lane:t,tag:0,payload:null,callback:null,next:null}}function Xa(t,r,s){var u=t.updateQueue;if(u===null)return null;if(u=u.shared,(bt&2)!==0){var h=u.pending;return h===null?r.next=r:(r.next=h.next,h.next=r),u.pending=r,r=Qc(t),Iy(t,null,s),r}return Jc(t,u,r,s),Qc(t)}function il(t,r,s){if(r=r.updateQueue,r!==null&&(r=r.shared,(s&4194048)!==0)){var u=r.lanes;u&=t.pendingLanes,s|=u,r.lanes=s,we(t,s)}}function wh(t,r){var s=t.updateQueue,u=t.alternate;if(u!==null&&(u=u.updateQueue,s===u)){var h=null,p=null;if(s=s.firstBaseUpdate,s!==null){do{var w={lane:s.lane,tag:s.tag,payload:s.payload,callback:null,next:null};p===null?h=p=w:p=p.next=w,s=s.next}while(s!==null);p===null?h=p=r:p=p.next=r}else h=p=r;s={baseState:u.baseState,firstBaseUpdate:h,lastBaseUpdate:p,shared:u.shared,callbacks:u.callbacks},t.updateQueue=s;return}t=s.lastBaseUpdate,t===null?s.firstBaseUpdate=r:t.next=r,s.lastBaseUpdate=r}var Ch=!1;function sl(){if(Ch){var t=Os;if(t!==null)throw t}}function ol(t,r,s,u){Ch=!1;var h=t.updateQueue;Wa=!1;var p=h.firstBaseUpdate,w=h.lastBaseUpdate,k=h.shared.pending;if(k!==null){h.shared.pending=null;var j=k,X=j.next;j.next=null,w===null?p=X:w.next=X,w=j;var re=t.alternate;re!==null&&(re=re.updateQueue,k=re.lastBaseUpdate,k!==w&&(k===null?re.firstBaseUpdate=X:k.next=X,re.lastBaseUpdate=j))}if(p!==null){var le=h.baseState;w=0,re=X=j=null,k=p;do{var Q=k.lane&-536870913,te=Q!==k.lane;if(te?(it&Q)===Q:(u&Q)===Q){Q!==0&&Q===Ds&&(Ch=!0),re!==null&&(re=re.next={lane:0,tag:k.tag,payload:k.payload,callback:null,next:null});e:{var De=t,Pe=k;Q=r;var Tt=s;switch(Pe.tag){case 1:if(De=Pe.payload,typeof De=="function"){le=De.call(Tt,le,Q);break e}le=De;break e;case 3:De.flags=De.flags&-65537|128;case 0:if(De=Pe.payload,Q=typeof De=="function"?De.call(Tt,le,Q):De,Q==null)break e;le=b({},le,Q);break e;case 2:Wa=!0}}Q=k.callback,Q!==null&&(t.flags|=64,te&&(t.flags|=8192),te=h.callbacks,te===null?h.callbacks=[Q]:te.push(Q))}else te={lane:Q,tag:k.tag,payload:k.payload,callback:k.callback,next:null},re===null?(X=re=te,j=le):re=re.next=te,w|=Q;if(k=k.next,k===null){if(k=h.shared.pending,k===null)break;te=k,k=te.next,te.next=null,h.lastBaseUpdate=te,h.shared.pending=null}}while(!0);re===null&&(j=le),h.baseState=j,h.firstBaseUpdate=X,h.lastBaseUpdate=re,p===null&&(h.shared.lanes=0),ti|=w,t.lanes=w,t.memoizedState=le}}function eb(t,r){if(typeof t!="function")throw Error(a(191,t));t.call(r)}function tb(t,r){var s=t.callbacks;if(s!==null)for(t.callbacks=null,t=0;tp?p:8;var w=J.T,k={};J.T=k,Bh(t,!1,r,s);try{var j=h(),X=J.S;if(X!==null&&X(k,j),j!==null&&typeof j=="object"&&typeof j.then=="function"){var re=YA(j,u);ul(t,r,re,Zn(t))}else ul(t,r,u,Zn(t))}catch(le){ul(t,r,{then:function(){},status:"rejected",reason:le},Zn())}finally{fe.p=p,w!==null&&k.types!==null&&(w.types=k.types),J.T=w}}function tx(){}function jh(t,r,s,u){if(t.tag!==5)throw Error(a(476));var h=Mb(t).queue;Lb(t,h,r,Le,s===null?tx:function(){return $b(t),s(u)})}function Mb(t){var r=t.memoizedState;if(r!==null)return r;r={memoizedState:Le,baseState:Le,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:ha,lastRenderedState:Le},next:null};var s={};return r.next={memoizedState:s,baseState:s,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:ha,lastRenderedState:s},next:null},t.memoizedState=r,t=t.alternate,t!==null&&(t.memoizedState=r),r}function $b(t){var r=Mb(t);r.next===null&&(r=t.alternate.memoizedState),ul(t,r.next.queue,{},Zn())}function Uh(){return bn(Tl)}function Nb(){return Wt().memoizedState}function Ib(){return Wt().memoizedState}function nx(t){for(var r=t.return;r!==null;){switch(r.tag){case 24:case 3:var s=Zn();t=Ya(s);var u=Xa(r,t,s);u!==null&&(Bn(u,r,s),il(u,r,s)),r={cache:ph()},t.payload=r;return}r=r.return}}function rx(t,r,s){var u=Zn();s={lane:u,revertLane:0,gesture:null,action:s,hasEagerState:!1,eagerState:null,next:null},yu(t)?zb(r,s):(s=ah(t,r,s,u),s!==null&&(Bn(s,t,u),jb(s,r,u)))}function Hb(t,r,s){var u=Zn();ul(t,r,s,u)}function ul(t,r,s,u){var h={lane:u,revertLane:0,gesture:null,action:s,hasEagerState:!1,eagerState:null,next:null};if(yu(t))zb(r,h);else{var p=t.alternate;if(t.lanes===0&&(p===null||p.lanes===0)&&(p=r.lastRenderedReducer,p!==null))try{var w=r.lastRenderedState,k=p(w,s);if(h.hasEagerState=!0,h.eagerState=k,Kn(k,w))return Jc(t,r,h,0),Rt===null&&Xc(),!1}catch{}if(s=ah(t,r,h,u),s!==null)return Bn(s,t,u),jb(s,r,u),!0}return!1}function Bh(t,r,s,u){if(u={lane:2,revertLane:vg(),gesture:null,action:u,hasEagerState:!1,eagerState:null,next:null},yu(t)){if(r)throw Error(a(479))}else r=ah(t,s,u,2),r!==null&&Bn(r,t,2)}function yu(t){var r=t.alternate;return t===Xe||r!==null&&r===Xe}function zb(t,r){Ns=uu=!0;var s=t.pending;s===null?r.next=r:(r.next=s.next,s.next=r),t.pending=r}function jb(t,r,s){if((s&4194048)!==0){var u=r.lanes;u&=t.pendingLanes,s|=u,r.lanes=s,we(t,s)}}var dl={readContext:bn,use:hu,useCallback:qt,useContext:qt,useEffect:qt,useImperativeHandle:qt,useLayoutEffect:qt,useInsertionEffect:qt,useMemo:qt,useReducer:qt,useRef:qt,useState:qt,useDebugValue:qt,useDeferredValue:qt,useTransition:qt,useSyncExternalStore:qt,useId:qt,useHostTransitionStatus:qt,useFormState:qt,useActionState:qt,useOptimistic:qt,useMemoCache:qt,useCacheRefresh:qt};dl.useEffectEvent=qt;var Ub={readContext:bn,use:hu,useCallback:function(t,r){return Dn().memoizedState=[t,r===void 0?null:r],t},useContext:bn,useEffect:Eb,useImperativeHandle:function(t,r,s){s=s!=null?s.concat([t]):null,pu(4194308,4,Tb.bind(null,r,t),s)},useLayoutEffect:function(t,r){return pu(4194308,4,t,r)},useInsertionEffect:function(t,r){pu(4,2,t,r)},useMemo:function(t,r){var s=Dn();r=r===void 0?null:r;var u=t();if(Vi){En(!0);try{t()}finally{En(!1)}}return s.memoizedState=[u,r],u},useReducer:function(t,r,s){var u=Dn();if(s!==void 0){var h=s(r);if(Vi){En(!0);try{s(r)}finally{En(!1)}}}else h=r;return u.memoizedState=u.baseState=h,t={pending:null,lanes:0,dispatch:null,lastRenderedReducer:t,lastRenderedState:h},u.queue=t,t=t.dispatch=rx.bind(null,Xe,t),[u.memoizedState,t]},useRef:function(t){var r=Dn();return t={current:t},r.memoizedState=t},useState:function(t){t=$h(t);var r=t.queue,s=Hb.bind(null,Xe,r);return r.dispatch=s,[t.memoizedState,s]},useDebugValue:Hh,useDeferredValue:function(t,r){var s=Dn();return zh(s,t,r)},useTransition:function(){var t=$h(!1);return t=Lb.bind(null,Xe,t.queue,!0,!1),Dn().memoizedState=t,[!1,t]},useSyncExternalStore:function(t,r,s){var u=Xe,h=Dn();if(ct){if(s===void 0)throw Error(a(407));s=s()}else{if(s=r(),Rt===null)throw Error(a(349));(it&127)!==0||ob(u,r,s)}h.memoizedState=s;var p={value:s,getSnapshot:r};return h.queue=p,Eb(cb.bind(null,u,p,t),[t]),u.flags|=2048,Hs(9,{destroy:void 0},lb.bind(null,u,p,s,r),null),s},useId:function(){var t=Dn(),r=Rt.identifierPrefix;if(ct){var s=Kr,u=Fr;s=(u&~(1<<32-sn(u)-1)).toString(32)+s,r="_"+r+"R_"+s,s=du++,0<\/script>",p=p.removeChild(p.firstChild);break;case"select":p=typeof u.is=="string"?w.createElement("select",{is:u.is}):w.createElement("select"),u.multiple?p.multiple=!0:u.size&&(p.size=u.size);break;default:p=typeof u.is=="string"?w.createElement(h,{is:u.is}):w.createElement(h)}}p[Ht]=r,p[_n]=u;e:for(w=r.child;w!==null;){if(w.tag===5||w.tag===6)p.appendChild(w.stateNode);else if(w.tag!==4&&w.tag!==27&&w.child!==null){w.child.return=w,w=w.child;continue}if(w===r)break e;for(;w.sibling===null;){if(w.return===null||w.return===r)break e;w=w.return}w.sibling.return=w.return,w=w.sibling}r.stateNode=p;e:switch(Sn(p,h,u),h){case"button":case"input":case"select":case"textarea":u=!!u.autoFocus;break e;case"img":u=!0;break e;default:u=!1}u&&pa(r)}}return Ot(r),tg(r,r.type,t===null?null:t.memoizedProps,r.pendingProps,s),null;case 6:if(t&&r.stateNode!=null)t.memoizedProps!==u&&pa(r);else{if(typeof u!="string"&&r.stateNode===null)throw Error(a(166));if(t=ie.current,Rs(r)){if(t=r.stateNode,s=r.memoizedProps,u=null,h=yn,h!==null)switch(h.tag){case 27:case 5:u=h.memoizedProps}t[Ht]=r,t=!!(t.nodeValue===s||u!==null&&u.suppressHydrationWarning===!0||iS(t.nodeValue,s)),t||Fa(r,!0)}else t=Hu(t).createTextNode(u),t[Ht]=r,r.stateNode=t}return Ot(r),null;case 31:if(s=r.memoizedState,t===null||t.memoizedState!==null){if(u=Rs(r),s!==null){if(t===null){if(!u)throw Error(a(318));if(t=r.memoizedState,t=t!==null?t.dehydrated:null,!t)throw Error(a(557));t[Ht]=r}else zi(),(r.flags&128)===0&&(r.memoizedState=null),r.flags|=4;Ot(r),t=!1}else s=dh(),t!==null&&t.memoizedState!==null&&(t.memoizedState.hydrationErrors=s),t=!0;if(!t)return r.flags&256?(Xn(r),r):(Xn(r),null);if((r.flags&128)!==0)throw Error(a(558))}return Ot(r),null;case 13:if(u=r.memoizedState,t===null||t.memoizedState!==null&&t.memoizedState.dehydrated!==null){if(h=Rs(r),u!==null&&u.dehydrated!==null){if(t===null){if(!h)throw Error(a(318));if(h=r.memoizedState,h=h!==null?h.dehydrated:null,!h)throw Error(a(317));h[Ht]=r}else zi(),(r.flags&128)===0&&(r.memoizedState=null),r.flags|=4;Ot(r),h=!1}else h=dh(),t!==null&&t.memoizedState!==null&&(t.memoizedState.hydrationErrors=h),h=!0;if(!h)return r.flags&256?(Xn(r),r):(Xn(r),null)}return Xn(r),(r.flags&128)!==0?(r.lanes=s,r):(s=u!==null,t=t!==null&&t.memoizedState!==null,s&&(u=r.child,h=null,u.alternate!==null&&u.alternate.memoizedState!==null&&u.alternate.memoizedState.cachePool!==null&&(h=u.alternate.memoizedState.cachePool.pool),p=null,u.memoizedState!==null&&u.memoizedState.cachePool!==null&&(p=u.memoizedState.cachePool.pool),p!==h&&(u.flags|=2048)),s!==t&&s&&(r.child.flags|=8192),Cu(r,r.updateQueue),Ot(r),null);case 4:return Te(),t===null&&Eg(r.stateNode.containerInfo),Ot(r),null;case 10:return da(r.type),Ot(r),null;case 19:if(T(Kt),u=r.memoizedState,u===null)return Ot(r),null;if(h=(r.flags&128)!==0,p=u.rendering,p===null)if(h)hl(u,!1);else{if(Gt!==0||t!==null&&(t.flags&128)!==0)for(t=r.child;t!==null;){if(p=cu(t),p!==null){for(r.flags|=128,hl(u,!1),t=p.updateQueue,r.updateQueue=t,Cu(r,t),r.subtreeFlags=0,t=s,s=r.child;s!==null;)Hy(s,t),s=s.sibling;return ce(Kt,Kt.current&1|2),ct&&ca(r,u.treeForkCount),r.child}t=t.sibling}u.tail!==null&&Pt()>Tu&&(r.flags|=128,h=!0,hl(u,!1),r.lanes=4194304)}else{if(!h)if(t=cu(p),t!==null){if(r.flags|=128,h=!0,t=t.updateQueue,r.updateQueue=t,Cu(r,t),hl(u,!0),u.tail===null&&u.tailMode==="hidden"&&!p.alternate&&!ct)return Ot(r),null}else 2*Pt()-u.renderingStartTime>Tu&&s!==536870912&&(r.flags|=128,h=!0,hl(u,!1),r.lanes=4194304);u.isBackwards?(p.sibling=r.child,r.child=p):(t=u.last,t!==null?t.sibling=p:r.child=p,u.last=p)}return u.tail!==null?(t=u.tail,u.rendering=t,u.tail=t.sibling,u.renderingStartTime=Pt(),t.sibling=null,s=Kt.current,ce(Kt,h?s&1|2:s&1),ct&&ca(r,u.treeForkCount),t):(Ot(r),null);case 22:case 23:return Xn(r),_h(),u=r.memoizedState!==null,t!==null?t.memoizedState!==null!==u&&(r.flags|=8192):u&&(r.flags|=8192),u?(s&536870912)!==0&&(r.flags&128)===0&&(Ot(r),r.subtreeFlags&6&&(r.flags|=8192)):Ot(r),s=r.updateQueue,s!==null&&Cu(r,s.retryQueue),s=null,t!==null&&t.memoizedState!==null&&t.memoizedState.cachePool!==null&&(s=t.memoizedState.cachePool.pool),u=null,r.memoizedState!==null&&r.memoizedState.cachePool!==null&&(u=r.memoizedState.cachePool.pool),u!==s&&(r.flags|=2048),t!==null&&T(Bi),null;case 24:return s=null,t!==null&&(s=t.memoizedState.cache),r.memoizedState.cache!==s&&(r.flags|=2048),da(Jt),Ot(r),null;case 25:return null;case 30:return null}throw Error(a(156,r.tag))}function lx(t,r){switch(ch(r),r.tag){case 1:return t=r.flags,t&65536?(r.flags=t&-65537|128,r):null;case 3:return da(Jt),Te(),t=r.flags,(t&65536)!==0&&(t&128)===0?(r.flags=t&-65537|128,r):null;case 26:case 27:case 5:return ht(r),null;case 31:if(r.memoizedState!==null){if(Xn(r),r.alternate===null)throw Error(a(340));zi()}return t=r.flags,t&65536?(r.flags=t&-65537|128,r):null;case 13:if(Xn(r),t=r.memoizedState,t!==null&&t.dehydrated!==null){if(r.alternate===null)throw Error(a(340));zi()}return t=r.flags,t&65536?(r.flags=t&-65537|128,r):null;case 19:return T(Kt),null;case 4:return Te(),null;case 10:return da(r.type),null;case 22:case 23:return Xn(r),_h(),t!==null&&T(Bi),t=r.flags,t&65536?(r.flags=t&-65537|128,r):null;case 24:return da(Jt),null;case 25:return null;default:return null}}function uv(t,r){switch(ch(r),r.tag){case 3:da(Jt),Te();break;case 26:case 27:case 5:ht(r);break;case 4:Te();break;case 31:r.memoizedState!==null&&Xn(r);break;case 13:Xn(r);break;case 19:T(Kt);break;case 10:da(r.type);break;case 22:case 23:Xn(r),_h(),t!==null&&T(Bi);break;case 24:da(Jt)}}function gl(t,r){try{var s=r.updateQueue,u=s!==null?s.lastEffect:null;if(u!==null){var h=u.next;s=h;do{if((s.tag&t)===t){u=void 0;var p=s.create,w=s.inst;u=p(),w.destroy=u}s=s.next}while(s!==h)}}catch(k){Et(r,r.return,k)}}function Za(t,r,s){try{var u=r.updateQueue,h=u!==null?u.lastEffect:null;if(h!==null){var p=h.next;u=p;do{if((u.tag&t)===t){var w=u.inst,k=w.destroy;if(k!==void 0){w.destroy=void 0,h=r;var j=s,X=k;try{X()}catch(re){Et(h,j,re)}}}u=u.next}while(u!==p)}}catch(re){Et(r,r.return,re)}}function dv(t){var r=t.updateQueue;if(r!==null){var s=t.stateNode;try{tb(r,s)}catch(u){Et(t,t.return,u)}}}function fv(t,r,s){s.props=Fi(t.type,t.memoizedProps),s.state=t.memoizedState;try{s.componentWillUnmount()}catch(u){Et(t,r,u)}}function pl(t,r){try{var s=t.ref;if(s!==null){switch(t.tag){case 26:case 27:case 5:var u=t.stateNode;break;case 30:u=t.stateNode;break;default:u=t.stateNode}typeof s=="function"?t.refCleanup=s(u):s.current=u}}catch(h){Et(t,r,h)}}function Wr(t,r){var s=t.ref,u=t.refCleanup;if(s!==null)if(typeof u=="function")try{u()}catch(h){Et(t,r,h)}finally{t.refCleanup=null,t=t.alternate,t!=null&&(t.refCleanup=null)}else if(typeof s=="function")try{s(null)}catch(h){Et(t,r,h)}else s.current=null}function hv(t){var r=t.type,s=t.memoizedProps,u=t.stateNode;try{e:switch(r){case"button":case"input":case"select":case"textarea":s.autoFocus&&u.focus();break e;case"img":s.src?u.src=s.src:s.srcSet&&(u.srcset=s.srcSet)}}catch(h){Et(t,t.return,h)}}function ng(t,r,s){try{var u=t.stateNode;Dx(u,t.type,s,r),u[_n]=r}catch(h){Et(t,t.return,h)}}function gv(t){return t.tag===5||t.tag===3||t.tag===26||t.tag===27&&si(t.type)||t.tag===4}function rg(t){e:for(;;){for(;t.sibling===null;){if(t.return===null||gv(t.return))return null;t=t.return}for(t.sibling.return=t.return,t=t.sibling;t.tag!==5&&t.tag!==6&&t.tag!==18;){if(t.tag===27&&si(t.type)||t.flags&2||t.child===null||t.tag===4)continue e;t.child.return=t,t=t.child}if(!(t.flags&2))return t.stateNode}}function ag(t,r,s){var u=t.tag;if(u===5||u===6)t=t.stateNode,r?(s.nodeType===9?s.body:s.nodeName==="HTML"?s.ownerDocument.body:s).insertBefore(t,r):(r=s.nodeType===9?s.body:s.nodeName==="HTML"?s.ownerDocument.body:s,r.appendChild(t),s=s._reactRootContainer,s!=null||r.onclick!==null||(r.onclick=Tr));else if(u!==4&&(u===27&&si(t.type)&&(s=t.stateNode,r=null),t=t.child,t!==null))for(ag(t,r,s),t=t.sibling;t!==null;)ag(t,r,s),t=t.sibling}function Eu(t,r,s){var u=t.tag;if(u===5||u===6)t=t.stateNode,r?s.insertBefore(t,r):s.appendChild(t);else if(u!==4&&(u===27&&si(t.type)&&(s=t.stateNode),t=t.child,t!==null))for(Eu(t,r,s),t=t.sibling;t!==null;)Eu(t,r,s),t=t.sibling}function pv(t){var r=t.stateNode,s=t.memoizedProps;try{for(var u=t.type,h=r.attributes;h.length;)r.removeAttributeNode(h[0]);Sn(r,u,s),r[Ht]=t,r[_n]=s}catch(p){Et(t,t.return,p)}}var ma=!1,en=!1,ig=!1,mv=typeof WeakSet=="function"?WeakSet:Set,dn=null;function cx(t,r){if(t=t.containerInfo,xg=Gu,t=Ry(t),Qf(t)){if("selectionStart"in t)var s={start:t.selectionStart,end:t.selectionEnd};else e:{s=(s=t.ownerDocument)&&s.defaultView||window;var u=s.getSelection&&s.getSelection();if(u&&u.rangeCount!==0){s=u.anchorNode;var h=u.anchorOffset,p=u.focusNode;u=u.focusOffset;try{s.nodeType,p.nodeType}catch{s=null;break e}var w=0,k=-1,j=-1,X=0,re=0,le=t,Q=null;t:for(;;){for(var te;le!==s||h!==0&&le.nodeType!==3||(k=w+h),le!==p||u!==0&&le.nodeType!==3||(j=w+u),le.nodeType===3&&(w+=le.nodeValue.length),(te=le.firstChild)!==null;)Q=le,le=te;for(;;){if(le===t)break t;if(Q===s&&++X===h&&(k=w),Q===p&&++re===u&&(j=w),(te=le.nextSibling)!==null)break;le=Q,Q=le.parentNode}le=te}s=k===-1||j===-1?null:{start:k,end:j}}else s=null}s=s||{start:0,end:0}}else s=null;for(Tg={focusedElem:t,selectionRange:s},Gu=!1,dn=r;dn!==null;)if(r=dn,t=r.child,(r.subtreeFlags&1028)!==0&&t!==null)t.return=r,dn=t;else for(;dn!==null;){switch(r=dn,p=r.alternate,t=r.flags,r.tag){case 0:if((t&4)!==0&&(t=r.updateQueue,t=t!==null?t.events:null,t!==null))for(s=0;s title"))),Sn(p,u,s),p[Ht]=t,Xt(p),u=p;break e;case"link":var w=CS("link","href",h).get(u+(s.href||""));if(w){for(var k=0;kTt&&(w=Tt,Tt=Pe,Pe=w);var F=xy(k,Pe),G=xy(k,Tt);if(F&&G&&(te.rangeCount!==1||te.anchorNode!==F.node||te.anchorOffset!==F.offset||te.focusNode!==G.node||te.focusOffset!==G.offset)){var Y=le.createRange();Y.setStart(F.node,F.offset),te.removeAllRanges(),Pe>Tt?(te.addRange(Y),te.extend(G.node,G.offset)):(Y.setEnd(G.node,G.offset),te.addRange(Y))}}}}for(le=[],te=k;te=te.parentNode;)te.nodeType===1&&le.push({element:te,left:te.scrollLeft,top:te.scrollTop});for(typeof k.focus=="function"&&k.focus(),k=0;ks?32:s,J.T=null,s=fg,fg=null;var p=ri,w=wa;if(ln=0,Ps=ri=null,wa=0,(bt&6)!==0)throw Error(a(331));var k=bt;if(bt|=4,Tv(p.current),_v(p,p.current,w,s),bt=k,wl(0,!1),an&&typeof an.onPostCommitFiberRoot=="function")try{an.onPostCommitFiberRoot(ir,p)}catch{}return!0}finally{fe.p=h,J.T=u,Vv(t,r)}}function Kv(t,r,s){r=lr(s,r),r=Vh(t.stateNode,r,2),t=Xa(t,r,2),t!==null&&(Ze(t,2),Yr(t))}function Et(t,r,s){if(t.tag===3)Kv(t,t,s);else for(;r!==null;){if(r.tag===3){Kv(r,t,s);break}else if(r.tag===1){var u=r.stateNode;if(typeof r.type.getDerivedStateFromError=="function"||typeof u.componentDidCatch=="function"&&(ni===null||!ni.has(u))){t=lr(s,t),s=Wb(2),u=Xa(r,s,2),u!==null&&(Yb(s,u,r,t),Ze(u,2),Yr(u));break}}r=r.return}}function mg(t,r,s){var u=t.pingCache;if(u===null){u=t.pingCache=new fx;var h=new Set;u.set(r,h)}else h=u.get(r),h===void 0&&(h=new Set,u.set(r,h));h.has(s)||(lg=!0,h.add(s),t=yx.bind(null,t,r,s),r.then(t,t))}function yx(t,r,s){var u=t.pingCache;u!==null&&u.delete(r),t.pingedLanes|=t.suspendedLanes&s,t.warmLanes&=~s,Rt===t&&(it&s)===s&&(Gt===4||Gt===3&&(it&62914560)===it&&300>Pt()-xu?(bt&2)===0&&qs(t,0):cg|=s,Bs===it&&(Bs=0)),Yr(t)}function Wv(t,r){r===0&&(r=Ne()),t=Ii(t,r),t!==null&&(Ze(t,r),Yr(t))}function bx(t){var r=t.memoizedState,s=0;r!==null&&(s=r.retryLane),Wv(t,s)}function vx(t,r){var s=0;switch(t.tag){case 31:case 13:var u=t.stateNode,h=t.memoizedState;h!==null&&(s=h.retryLane);break;case 19:u=t.stateNode;break;case 22:u=t.stateNode._retryCache;break;default:throw Error(a(314))}u!==null&&u.delete(r),Wv(t,s)}function Sx(t,r){return rn(t,r)}var Mu=null,Vs=null,yg=!1,$u=!1,bg=!1,ii=0;function Yr(t){t!==Vs&&t.next===null&&(Vs===null?Mu=Vs=t:Vs=Vs.next=t),$u=!0,yg||(yg=!0,Cx())}function wl(t,r){if(!bg&&$u){bg=!0;do for(var s=!1,u=Mu;u!==null;){if(t!==0){var h=u.pendingLanes;if(h===0)var p=0;else{var w=u.suspendedLanes,k=u.pingedLanes;p=(1<<31-sn(42|t)+1)-1,p&=h&~(w&~k),p=p&201326741?p&201326741|1:p?p|2:0}p!==0&&(s=!0,Qv(u,p))}else p=it,p=Ae(u,u===Rt?p:0,u.cancelPendingCommit!==null||u.timeoutHandle!==-1),(p&3)===0||Fe(u,p)||(s=!0,Qv(u,p));u=u.next}while(s);bg=!1}}function wx(){Yv()}function Yv(){$u=yg=!1;var t=0;ii!==0&&Lx()&&(t=ii);for(var r=Pt(),s=null,u=Mu;u!==null;){var h=u.next,p=Xv(u,r);p===0?(u.next=null,s===null?Mu=h:s.next=h,h===null&&(Vs=s)):(s=u,(t!==0||(p&3)!==0)&&($u=!0)),u=h}ln!==0&&ln!==5||wl(t),ii!==0&&(ii=0)}function Xv(t,r){for(var s=t.suspendedLanes,u=t.pingedLanes,h=t.expirationTimes,p=t.pendingLanes&-62914561;0k)break;var re=j.transferSize,le=j.initiatorType;re&&sS(le)&&(j=j.responseEnd,w+=re*(j"u"?null:document;function bS(t,r,s){var u=Fs;if(u&&typeof r=="string"&&r){var h=In(r);h='link[rel="'+t+'"][href="'+h+'"]',typeof s=="string"&&(h+='[crossorigin="'+s+'"]'),yS.has(h)||(yS.add(h),t={rel:t,crossOrigin:s,href:r},u.querySelector(h)===null&&(r=u.createElement("link"),Sn(r,"link",t),Xt(r),u.head.appendChild(r)))}}function Bx(t){Ca.D(t),bS("dns-prefetch",t,null)}function Px(t,r){Ca.C(t,r),bS("preconnect",t,r)}function qx(t,r,s){Ca.L(t,r,s);var u=Fs;if(u&&t&&r){var h='link[rel="preload"][as="'+In(r)+'"]';r==="image"&&s&&s.imageSrcSet?(h+='[imagesrcset="'+In(s.imageSrcSet)+'"]',typeof s.imageSizes=="string"&&(h+='[imagesizes="'+In(s.imageSizes)+'"]')):h+='[href="'+In(t)+'"]';var p=h;switch(r){case"style":p=Ks(t);break;case"script":p=Ws(t)}gr.has(p)||(t=b({rel:"preload",href:r==="image"&&s&&s.imageSrcSet?void 0:t,as:r},s),gr.set(p,t),u.querySelector(h)!==null||r==="style"&&u.querySelector(Al(p))||r==="script"&&u.querySelector(xl(p))||(r=u.createElement("link"),Sn(r,"link",t),Xt(r),u.head.appendChild(r)))}}function Gx(t,r){Ca.m(t,r);var s=Fs;if(s&&t){var u=r&&typeof r.as=="string"?r.as:"script",h='link[rel="modulepreload"][as="'+In(u)+'"][href="'+In(t)+'"]',p=h;switch(u){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":p=Ws(t)}if(!gr.has(p)&&(t=b({rel:"modulepreload",href:t},r),gr.set(p,t),s.querySelector(h)===null)){switch(u){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":if(s.querySelector(xl(p)))return}u=s.createElement("link"),Sn(u,"link",t),Xt(u),s.head.appendChild(u)}}}function Vx(t,r,s){Ca.S(t,r,s);var u=Fs;if(u&&t){var h=Ia(u).hoistableStyles,p=Ks(t);r=r||"default";var w=h.get(p);if(!w){var k={loading:0,preload:null};if(w=u.querySelector(Al(p)))k.loading=5;else{t=b({rel:"stylesheet",href:t,"data-precedence":r},s),(s=gr.get(p))&&$g(t,s);var j=w=u.createElement("link");Xt(j),Sn(j,"link",t),j._p=new Promise(function(X,re){j.onload=X,j.onerror=re}),j.addEventListener("load",function(){k.loading|=1}),j.addEventListener("error",function(){k.loading|=2}),k.loading|=4,ju(w,r,u)}w={type:"stylesheet",instance:w,count:1,state:k},h.set(p,w)}}}function Fx(t,r){Ca.X(t,r);var s=Fs;if(s&&t){var u=Ia(s).hoistableScripts,h=Ws(t),p=u.get(h);p||(p=s.querySelector(xl(h)),p||(t=b({src:t,async:!0},r),(r=gr.get(h))&&Ng(t,r),p=s.createElement("script"),Xt(p),Sn(p,"link",t),s.head.appendChild(p)),p={type:"script",instance:p,count:1,state:null},u.set(h,p))}}function Kx(t,r){Ca.M(t,r);var s=Fs;if(s&&t){var u=Ia(s).hoistableScripts,h=Ws(t),p=u.get(h);p||(p=s.querySelector(xl(h)),p||(t=b({src:t,async:!0,type:"module"},r),(r=gr.get(h))&&Ng(t,r),p=s.createElement("script"),Xt(p),Sn(p,"link",t),s.head.appendChild(p)),p={type:"script",instance:p,count:1,state:null},u.set(h,p))}}function vS(t,r,s,u){var h=(h=ie.current)?zu(h):null;if(!h)throw Error(a(446));switch(t){case"meta":case"title":return null;case"style":return typeof s.precedence=="string"&&typeof s.href=="string"?(r=Ks(s.href),s=Ia(h).hoistableStyles,u=s.get(r),u||(u={type:"style",instance:null,count:0,state:null},s.set(r,u)),u):{type:"void",instance:null,count:0,state:null};case"link":if(s.rel==="stylesheet"&&typeof s.href=="string"&&typeof s.precedence=="string"){t=Ks(s.href);var p=Ia(h).hoistableStyles,w=p.get(t);if(w||(h=h.ownerDocument||h,w={type:"stylesheet",instance:null,count:0,state:{loading:0,preload:null}},p.set(t,w),(p=h.querySelector(Al(t)))&&!p._p&&(w.instance=p,w.state.loading=5),gr.has(t)||(s={rel:"preload",as:"style",href:s.href,crossOrigin:s.crossOrigin,integrity:s.integrity,media:s.media,hrefLang:s.hrefLang,referrerPolicy:s.referrerPolicy},gr.set(t,s),p||Wx(h,t,s,w.state))),r&&u===null)throw Error(a(528,""));return w}if(r&&u!==null)throw Error(a(529,""));return null;case"script":return r=s.async,s=s.src,typeof s=="string"&&r&&typeof r!="function"&&typeof r!="symbol"?(r=Ws(s),s=Ia(h).hoistableScripts,u=s.get(r),u||(u={type:"script",instance:null,count:0,state:null},s.set(r,u)),u):{type:"void",instance:null,count:0,state:null};default:throw Error(a(444,t))}}function Ks(t){return'href="'+In(t)+'"'}function Al(t){return'link[rel="stylesheet"]['+t+"]"}function SS(t){return b({},t,{"data-precedence":t.precedence,precedence:null})}function Wx(t,r,s,u){t.querySelector('link[rel="preload"][as="style"]['+r+"]")?u.loading=1:(r=t.createElement("link"),u.preload=r,r.addEventListener("load",function(){return u.loading|=1}),r.addEventListener("error",function(){return u.loading|=2}),Sn(r,"link",s),Xt(r),t.head.appendChild(r))}function Ws(t){return'[src="'+In(t)+'"]'}function xl(t){return"script[async]"+t}function wS(t,r,s){if(r.count++,r.instance===null)switch(r.type){case"style":var u=t.querySelector('style[data-href~="'+In(s.href)+'"]');if(u)return r.instance=u,Xt(u),u;var h=b({},s,{"data-href":s.href,"data-precedence":s.precedence,href:null,precedence:null});return u=(t.ownerDocument||t).createElement("style"),Xt(u),Sn(u,"style",h),ju(u,s.precedence,t),r.instance=u;case"stylesheet":h=Ks(s.href);var p=t.querySelector(Al(h));if(p)return r.state.loading|=4,r.instance=p,Xt(p),p;u=SS(s),(h=gr.get(h))&&$g(u,h),p=(t.ownerDocument||t).createElement("link"),Xt(p);var w=p;return w._p=new Promise(function(k,j){w.onload=k,w.onerror=j}),Sn(p,"link",u),r.state.loading|=4,ju(p,s.precedence,t),r.instance=p;case"script":return p=Ws(s.src),(h=t.querySelector(xl(p)))?(r.instance=h,Xt(h),h):(u=s,(h=gr.get(p))&&(u=b({},s),Ng(u,h)),t=t.ownerDocument||t,h=t.createElement("script"),Xt(h),Sn(h,"link",u),t.head.appendChild(h),r.instance=h);case"void":return null;default:throw Error(a(443,r.type))}else r.type==="stylesheet"&&(r.state.loading&4)===0&&(u=r.instance,r.state.loading|=4,ju(u,s.precedence,t));return r.instance}function ju(t,r,s){for(var u=s.querySelectorAll('link[rel="stylesheet"][data-precedence],style[data-precedence]'),h=u.length?u[u.length-1]:null,p=h,w=0;w title"):null)}function Yx(t,r,s){if(s===1||r.itemProp!=null)return!1;switch(t){case"meta":case"title":return!0;case"style":if(typeof r.precedence!="string"||typeof r.href!="string"||r.href==="")break;return!0;case"link":if(typeof r.rel!="string"||typeof r.href!="string"||r.href===""||r.onLoad||r.onError)break;return r.rel==="stylesheet"?(t=r.disabled,typeof r.precedence=="string"&&t==null):!0;case"script":if(r.async&&typeof r.async!="function"&&typeof r.async!="symbol"&&!r.onLoad&&!r.onError&&r.src&&typeof r.src=="string")return!0}return!1}function _S(t){return!(t.type==="stylesheet"&&(t.state.loading&3)===0)}function Xx(t,r,s,u){if(s.type==="stylesheet"&&(typeof u.media!="string"||matchMedia(u.media).matches!==!1)&&(s.state.loading&4)===0){if(s.instance===null){var h=Ks(u.href),p=r.querySelector(Al(h));if(p){r=p._p,r!==null&&typeof r=="object"&&typeof r.then=="function"&&(t.count++,t=Bu.bind(t),r.then(t,t)),s.state.loading|=4,s.instance=p,Xt(p);return}p=r.ownerDocument||r,u=SS(u),(h=gr.get(h))&&$g(u,h),p=p.createElement("link"),Xt(p);var w=p;w._p=new Promise(function(k,j){w.onload=k,w.onerror=j}),Sn(p,"link",u),s.instance=p}t.stylesheets===null&&(t.stylesheets=new Map),t.stylesheets.set(s,r),(r=s.state.preload)&&(s.state.loading&3)===0&&(t.count++,s=Bu.bind(t),r.addEventListener("load",s),r.addEventListener("error",s))}}var Ig=0;function Jx(t,r){return t.stylesheets&&t.count===0&&qu(t,t.stylesheets),0Ig?50:800)+r);return t.unsuspend=s,function(){t.unsuspend=null,clearTimeout(u),clearTimeout(h)}}:null}function Bu(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)qu(this,this.stylesheets);else if(this.unsuspend){var t=this.unsuspend;this.unsuspend=null,t()}}}var Pu=null;function qu(t,r){t.stylesheets=null,t.unsuspend!==null&&(t.count++,Pu=new Map,r.forEach(Qx,t),Pu=null,Bu.call(t))}function Qx(t,r){if(!(r.state.loading&4)){var s=Pu.get(t);if(s)var u=s.get(null);else{s=new Map,Pu.set(t,s);for(var h=t.querySelectorAll("link[data-precedence],style[data-precedence]"),p=0;p"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(o)}catch(e){console.error(e)}}return o(),Zu.exports=VS(),Zu.exports}var rd=FS();const ad=Jr(rd),Js=[],KS=50;let id=!1;function Qg(){try{return window.G7Core?.devTools}catch{return}}function Yi(o,e,n){const a=Qg();if(id&&a?.isEnabled?.()){a.trackLog?.(o,e,n);return}id||(o==="error"||o==="warn")&&Js.length{n.isDebugEnabled()&&console.log(e,...a),Yi("log",o,a)},warn:(...a)=>{n.isDebugEnabled()&&console.warn(e,...a),Yi("warn",o,a)},error:(...a)=>{n.isDebugEnabled()&&console.error(e,...a),Yi("error",o,a)}}}Qs.getInstance();const Xi=ut("networkResilience"),Zg=2,ep=300,tp=2e3,WS=15e3;function od(o){return o?.name==="AbortError"}function np(o){return od(o)?!1:o instanceof TypeError}let Ol=!1,rp=!1;function ld(){return Ol}function YS(){typeof window>"u"||rp||(rp=!0,window.addEventListener("pagehide",()=>{Ol=!0}),window.addEventListener("pageshow",o=>{o.persisted&&(Ol=!1)}),window.addEventListener("beforeunload",()=>{Ol=!0}))}function ap(o,e,n){const a=Math.min(e*Math.pow(2,o),n),i=a*.25*(Math.random()*2-1);return Math.max(0,Math.round(a+i))}function ip(o){return new Promise(e=>setTimeout(e,o))}async function Ll(o,e={}){const{retries:n=Zg,baseDelayMs:a=ep,maxDelayMs:i=tp,timeoutMs:l=WS,label:c=o,init:d}=e,f=n+1;let g;for(let m=0;m0?new AbortController:null;try{const _={...d};if(v){const x=d?.signal;x&&(x.aborted?v.abort():x.addEventListener("abort",()=>v.abort(),{once:!0})),_.signal=v.signal,S=setTimeout(()=>{b=!0,v.abort()},l)}return await fetch(o,_)}catch(_){if(g=_,od(_)&&!b||!(b||np(_)))throw _;if(ld())throw Xi.warn(`Document unloading, aborting retries: ${c}`),_;if(m===f-1)throw Xi.warn(`All ${f} attempts failed: ${c}`,_),_;const L=ap(m,a,i);Xi.warn(`Network failure (attempt ${m+1}/${f}), retrying in ${L}ms: ${c}`),await ip(L)}finally{S!==void 0&&clearTimeout(S)}}throw g}async function sp(o,e={},n={}){const{retries:a=Zg,baseDelayMs:i=ep,maxDelayMs:l=tp,label:c=o}=n,d=a+1;for(let f=0;f{const i=document.createElement("script");i.src=o,i.async=!1;for(const[l,c]of Object.entries(e))l==="id"?i.id=c:i.setAttribute(l,c);i.onload=()=>n(),i.onerror=()=>a(new Error(`Failed to load script: ${o}`)),document.head.appendChild(i)})}const op="extension",Ji="extensionless",JS="file";function QS(){const o=globalThis?.G7Config;if(globalThis?.__g7AssetUrlMode===Ji)return Ji;const n=o?.assetUrlMode;return n===Ji||n===op?n:o?.settings?.general?.asset_url_mode===Ji?Ji:op}function lp(){return QS()===Ji}function $r(o,e,n,a){const i=o.replace(/\/+$/,""),l=e.replace(/^\.+/,""),c=lp()?i:`${i}.${l}`,d=[];return a&&d.push(a.replace(/^[?&]+/,"")),n!=null&&n!==""&&d.push(`v=${n}`),d.length>0?`${c}?${d.join("&")}`:c}function Ea(o){if(!o||!lp())return o;const e=globalThis?.location?.origin,n=e&&o.startsWith(e)?o.slice(e.length):o;if(!n.startsWith("/api/"))return o;const[a,i]=ZS(n),l=a.match(/^\/api\/(modules|plugins)\/bundle\.(js|css)$/);if(l)return cp(`/api/${l[1]}/bundle/${l[2]}`,i);const c=a.match(/^\/api\/(templates|modules|plugins)\/assets\/([^/]+)\/(.+)$/);if(c){const[,f,g,m]=c,b=`${JS}=${encodeURIComponent(decodeURIComponent(m))}`;return`/api/${f}/assets/${g}?${b}${i?`&${i}`:""}`}const d=a.match(/^(\/api\/.+)\.(json|js|css)$/);return d?cp(d[1],i):o}function ZS(o){const e=o.indexOf("?");return e===-1?[o,""]:[o.slice(0,e),o.slice(e+1)]}function cp(o,e){return e?`${o}?${e}`:o}const Nr=ut("ComponentRegistry");class cn extends Error{constructor(n,a,i){super(n);$(this,"code");$(this,"details");this.code=a,this.details=i,this.name="ComponentRegistryError"}}const Vn=class Vn{constructor(){$(this,"registry",{});$(this,"manifest",null);$(this,"loadingState","idle");$(this,"error",null);$(this,"templateId",null);$(this,"templateType",null)}static getInstance(){return Vn.instance||(Vn.instance=new Vn),Vn.instance}static createIsolatedInstance(){return new Vn}static resetInstance(){Vn.instance=null}async loadComponents(e,n){if(this.loadingState==="loading")throw new cn("Components are already being loaded","LOADING_IN_PROGRESS");if(this.loadingState==="loaded"&&this.templateId===e&&this.templateType===n){Nr.log("Components already loaded for template:",e,n);return}this.loadingState="loading",this.templateId=e,this.templateType=n,this.error=null;try{await this.loadManifest(),await this.loadComponentBundle(),this.loadingState="loaded",Nr.log("Successfully loaded components:",Object.keys(this.registry).length)}catch(a){throw this.loadingState="error",this.error=a instanceof Error?a:new Error(String(a)),new cn(`Failed to load components: ${this.error.message}`,"LOAD_FAILED",{originalError:this.error})}}async loadManifest(){try{if(!this.templateId)throw new cn("Template ID not set","TEMPLATE_ID_NOT_SET");const e=`${this.templateId}:${this.templateType}`;if(Vn.manifestCache.has(e)){this.manifest=Vn.manifestCache.get(e),Nr.log("Manifest loaded from cache:",this.manifest.templateId);return}const n=$r(`/api/templates/${this.templateId}/components`,"json"),a=await Ll(n,{label:"components.json"});if(!a.ok)throw new cn(`Failed to fetch manifest: ${a.status} ${a.statusText}`,"MANIFEST_FETCH_FAILED",{status:a.status,statusText:a.statusText});const i=await a.json();this.validateManifest(i),this.manifest=i,Vn.manifestCache.set(e,i),Nr.log("Manifest loaded and cached:",i.templateId)}catch(e){throw e instanceof cn?e:new cn("Failed to load component manifest","MANIFEST_LOAD_FAILED",{originalError:e})}}validateManifest(e){if(!e.version)throw new cn("Manifest missing required field: version","MANIFEST_INVALID",{field:"version"});if(!e.templateId)throw new cn("Manifest missing required field: templateId","MANIFEST_INVALID",{field:"templateId"});if(!e.components||typeof e.components!="object")throw new cn("Manifest missing required field: components","MANIFEST_INVALID",{field:"components"});const n=["basic","composite","layout"];for(const a of n){if(!Array.isArray(e.components[a]))throw new cn(`Manifest field 'components.${a}' must be an array`,"MANIFEST_INVALID",{field:`components.${a}`,value:e.components[a]});e.components[a].forEach((i,l)=>{if(!i.name||typeof i.name!="string")throw new cn(`Invalid component metadata at ${a}[${l}]: missing or invalid 'name'`,"MANIFEST_INVALID",{type:a,index:l,metadata:i});if(!i.type||typeof i.type!="string")throw new cn(`Invalid component metadata at ${a}[${l}]: missing or invalid 'type'`,"MANIFEST_INVALID",{type:a,index:l,metadata:i})})}Nr.log("Manifest validation passed")}async loadComponentBundle(){try{if(!this.templateId)throw new cn("Template ID not set","TEMPLATE_ID_NOT_SET");const e=this.getGlobalVariableName(),n=window[e];if(!n||typeof n!="object")throw new cn(`Component bundle not loaded. Expected global variable: ${e}. Ensure admin.blade.php includes the IIFE bundle script.`,"BUNDLE_NOT_LOADED",{expectedVariable:e});await this.registerComponentsFromManifest(n),Nr.log("Component bundle loaded from global variable:",e)}catch(e){throw e instanceof cn?e:new cn("Failed to load component bundle","BUNDLE_LOAD_FAILED",{originalError:e})}}getGlobalVariableName(){if(!this.templateId)throw new cn("Template ID not set","TEMPLATE_ID_NOT_SET");return this.templateId.split(/[-_]/).map(e=>e.charAt(0).toUpperCase()+e.slice(1).toLowerCase()).join("")}async registerComponentsFromManifest(e){if(!this.manifest)throw new cn("Manifest not loaded","MANIFEST_NOT_LOADED");const n=["basic","composite","layout"];for(const a of n){const i=this.manifest.components[a]||[];for(const l of i){const c=l.name,d=e[c];if(!d){Nr.warn(`Component '${c}' not found in bundle`);continue}this.registerComponent(c,d,l)}}}registerComponent(e,n,a){this.registry[e]&&Nr.warn(`Component '${e}' already registered, overwriting`);const i=Ye.memo(n);this.registry[e]={component:i,metadata:a},Nr.log(`[ComponentRegistry] Registered component: ${e} (${a.type})`)}getComponent(e){const n=this.registry[e];return n?n.component:null}getMetadata(e){const n=this.registry[e];return n?n.metadata:null}hasComponent(e){return e in this.registry}getComponentsByType(e){return Object.entries(this.registry).filter(([n,a])=>a.metadata.type===e).map(([n,a])=>n)}getAllComponents(){return Object.keys(this.registry)}getComponentMap(){const e={};for(const[n,a]of Object.entries(this.registry))e[n]=a.component;return e}getLoadingState(){return this.loadingState}getError(){return this.error}getTemplateId(){return this.templateId}getManifest(){return this.manifest}clear(){this.registry={},this.manifest=null,this.loadingState="idle",this.error=null,this.templateId=null,this.templateType=null,Nr.log("Registry cleared")}};$(Vn,"instance",null),$(Vn,"manifestCache",new Map);let pr=Vn;const up=ut("TranslationEngine");class cd extends Error{constructor(n,a,i){super(n);$(this,"key");$(this,"locale");this.key=a,this.locale=i,this.name="TranslationError"}}const Mn=class Mn{constructor(e={}){$(this,"cache",new Map);$(this,"translations",new Map);$(this,"options");$(this,"cacheVersion",0);this.options={defaultLocale:e.defaultLocale||"ko",fallbackLocale:e.fallbackLocale||"en",cacheTTL:e.cacheTTL||3e5}}static getInstance(e={}){return Mn.instance||(Mn.instance=new Mn(e)),Mn.instance}static resetInstance(){Mn.instance=null}setCacheVersion(e){this.cacheVersion!==e&&(up.log("Cache version updated:",this.cacheVersion,"->",e),this.cacheVersion=e,this.cache.clear())}getCacheVersion(){return this.cacheVersion}async loadTranslations(e,n,a="/api",i=!1){const l=`${e}:${n}`;if(!i){const c=this.getFromCache(l);if(c)return this.translations.set(l,c),c}try{const c=[];i&&c.push(`_=${Date.now()}`);const d=$r(`${a}/templates/${e}/lang/${n}`,"json",this.cacheVersion>0?this.cacheVersion:null,c.length>0?c.join("&"):void 0),f=await fetch(d);if(!f.ok)throw new cd(`Failed to load translations: ${f.statusText}`,void 0,n);const m=await f.json();return this.saveToCache(l,m),this.translations.set(l,m),m}catch(c){throw new cd(`Failed to fetch translations for ${n}: ${c instanceof Error?c.message:String(c)}`,void 0,n)}}resolveTranslations(e,n,a){let i=e;const l=5;let c=0;for(;c"="+this.translate(m,n,void 0,a)),i===d)break;c++}return i.replace(Mn.TRANSLATION_PATTERN,(d,f,g)=>{const{cleanedParams:m,trailing:b}=this.separateTrailingText(g);return this.translate(f,n,m,a)+b})}translate(e,n,a,i){const l=this.getTranslation(e,n),c=a&&this.cleanParamsStr(a);if(c){const d=this.parseParams(c,i);return this.replaceParams(l,d)}return l}getTranslation(e,n){const{templateId:a,locale:i}=n,l=`${a}:${i}`,c=this.translations.get(l);if(c){const d=this.getNestedValue(c,e);if(d!==null)return d}if(i!==this.options.fallbackLocale){const d=`${a}:${this.options.fallbackLocale}`,f=this.translations.get(d);if(f){const g=this.getNestedValue(f,e);if(g!==null)return g}}return e}getNestedValue(e,n){const a=n.split(".");let i=e;for(const l of a){if(i==null||typeof i!="object")return null;i=i[l]}return typeof i=="string"?i:null}setTranslationValue(e,n,a,i){const l=`${e}:${n}`,c=this.translations.get(l)??{},d=a.split(".");let f=c;for(let g=0;g(l.push(f),`__PLACEHOLDER_${l.length-1}__`)).matchAll(Mn.PARAM_PATTERN);for(const f of d){const[,g,m]=f,b=m.replace(/__PLACEHOLDER_(\d+)__/g,(S,v)=>l[parseInt(v,10)]);a[g.trim()]=this.resolveParamValue(b.trim(),n)}return a}resolveParamValue(e,n){if(e.startsWith("{{")&&e.endsWith("}}")){const a=e.slice(2,-2).trim();if(/[|&()[\]!?:+\-*/%<>=\s]/.test(a)&&n)try{const l=md.evaluateExpression(a,n);return String(l??"")}catch(l){return up.error("Expression evaluation failed:",a,l),""}else{const l=this.getNestedDataValue(n,a);return String(l??"")}}return e}separateTrailingText(e){if(!e)return{cleanedParams:void 0,trailing:""};if(e.trimEnd().endsWith("}}"))return{cleanedParams:e,trailing:""};if(!(e.startsWith("|")?e.slice(1):e).includes("="))return{cleanedParams:void 0,trailing:e};const a=e.match(/(\s+[^\p{L}\w=|&\s][^\p{L}\w=]*\s*)$/u);return a?{cleanedParams:e.slice(0,-a[1].length),trailing:a[1]}:{cleanedParams:e,trailing:""}}cleanParamsStr(e){return this.separateTrailingText(e).cleanedParams||""}getNestedDataValue(e,n){if(!e)return;const a=n.split(".");let i=e;for(const l of a){if(i==null)return;i=i[l]}return i}replaceParams(e,n){let a=e;for(const[i,l]of Object.entries(n)){const c=new RegExp(`\\{\\{${i}\\}\\}`,"g");a=a.replace(c,String(l));const d=new RegExp(`\\{${i}\\}`,"g");a=a.replace(d,String(l))}return a}getFromCache(e){const n=this.cache.get(e);return n?Date.now()-n.timestamp>this.options.cacheTTL?(this.cache.delete(e),null):n.data:null}saveToCache(e,n){this.cache.set(e,{data:n,timestamp:Date.now()})}clearCache(){this.cache.clear(),this.translations.clear()}pruneCache(){const e=Date.now(),n=[];for(const[a,i]of this.cache.entries())e-i.timestamp>this.options.cacheTTL&&n.push(a);for(const a of n)this.cache.delete(a)}getCacheStats(){const e=Date.now();let n=0;for(const a of this.cache.values())e-a.timestamp>this.options.cacheTTL&&n++;return{size:this.cache.size,expired:n}}};$(Mn,"instance",null),$(Mn,"TRANSLATION_PATTERN",/\$t:(?:defer:)?([a-zA-Z0-9._-]+)(\|(?:(?!\$t:).)+)?/g),$(Mn,"NESTED_TRANSLATION_PARAM_PATTERN",/=(\$t:([a-zA-Z0-9._-]+))(?=[|&\s]|$)/g),$(Mn,"PARAM_PATTERN",/([^=|&]+)=([^|&]+)/g);let _a=Mn,ud=null;function ew(o){return ud||(ud=new _a(o)),ud}const dp=Object.freeze(Object.defineProperty({__proto__:null,TranslationEngine:_a,TranslationError:cd,getTranslationEngine:ew},Symbol.toStringTag,{value:"Module"})),Qi=ut("PipeRegistry");function fp(o,e){const n=o.getFullYear(),a=String(o.getMonth()+1).padStart(2,"0"),i=String(o.getDate()).padStart(2,"0"),l=String(o.getHours()).padStart(2,"0"),c=String(o.getMinutes()).padStart(2,"0"),d=String(o.getSeconds()).padStart(2,"0");return e.replace("YYYY",String(n)).replace("YY",String(n).slice(-2)).replace("MM",a).replace("M",String(o.getMonth()+1)).replace("DD",i).replace("D",String(o.getDate())).replace("HH",l).replace("H",String(o.getHours())).replace("mm",c).replace("m",String(o.getMinutes())).replace("ss",d).replace("s",String(o.getSeconds()))}function tw(o,e="ko"){const a=new Date().getTime()-o.getTime(),i=Math.floor(a/1e3),l=Math.floor(i/60),c=Math.floor(l/60),d=Math.floor(c/24),f=Math.floor(d/7),g=Math.floor(d/30),m=Math.floor(d/365);return e==="ko"?i<60?"방금 전":l<60?`${l}분 전`:c<24?`${c}시간 전`:d<7?`${d}일 전`:f<4?`${f}주 전`:g<12?`${g}개월 전`:`${m}년 전`:i<60?"just now":l<60?`${l} minute${l===1?"":"s"} ago`:c<24?`${c} hour${c===1?"":"s"} ago`:d<7?`${d} day${d===1?"":"s"} ago`:f<4?`${f} week${f===1?"":"s"} ago`:g<12?`${g} month${g===1?"":"s"} ago`:`${m} year${m===1?"":"s"} ago`}function dd(o){if(o==null)return null;if(o instanceof Date)return o;if(typeof o=="number")return new Date(o);if(typeof o=="string"){const e=new Date(o);return isNaN(e.getTime())?null:e}return null}const Zs={date:{fn:(o,e="YYYY-MM-DD")=>{const n=dd(o);return n?fp(n,e):""},description:"날짜 포맷 (기본: YYYY-MM-DD)"},datetime:{fn:(o,e="YYYY-MM-DD HH:mm")=>{const n=dd(o);return n?fp(n,e):""},description:"날짜+시간 포맷 (기본: YYYY-MM-DD HH:mm)"},relativeTime:{fn:(o,e="ko")=>{const n=dd(o);return n?tw(n,e):""},description:'상대 시간 표시 (예: "3분 전")'},number:{fn:(o,e)=>{const n=Number(o);if(isNaN(n))return String(o??"");const a={};return e!==void 0&&(a.minimumFractionDigits=e,a.maximumFractionDigits=e),n.toLocaleString(void 0,a)},description:"숫자 포맷 (천단위 구분, 선택적 소수점)"},truncate:{fn:(o,e=100,n="...")=>typeof o!="string"?String(o??""):o.length<=e?o:o.slice(0,e)+n,description:'문자열 자르기 (기본: 100자, 접미사: "...")'},uppercase:{fn:o=>typeof o!="string"?String(o??""):o.toUpperCase(),description:"대문자 변환"},lowercase:{fn:o=>typeof o!="string"?String(o??""):o.toLowerCase(),description:"소문자 변환"},stripHtml:{fn:o=>typeof o!="string"?String(o??""):o.replace(/<[^>]*>/g,""),description:"HTML 태그 제거"},default:{fn:(o,e="")=>o==null||o===""?e:o,description:"기본값 설정 (null, undefined, 빈문자열 시)"},fallback:{fn:(o,e)=>o??e,description:"폴백 값 설정 (null, undefined 시만)"},first:{fn:o=>{if(Array.isArray(o))return o[0]},description:"배열의 첫 번째 요소"},last:{fn:o=>{if(Array.isArray(o))return o[o.length-1]},description:"배열의 마지막 요소"},join:{fn:(o,e=", ")=>Array.isArray(o)?o.join(e):"",description:'배열을 문자열로 결합 (기본 구분자: ", ")'},length:{fn:o=>Array.isArray(o)||typeof o=="string"?o.length:0,description:"배열/문자열 길이"},filterBy:{fn:(o,e,n)=>Array.isArray(o)?Array.isArray(e)?o.filter(a=>{const i=n?a?.[n]:a;return e.includes(i)}):o:[],description:"배열 필터링 (allowList에 포함된 항목만 반환)"},keys:{fn:o=>o==null||typeof o!="object"?[]:Object.keys(o),description:"객체의 키 배열"},values:{fn:o=>o==null||typeof o!="object"?[]:Object.values(o),description:"객체의 값 배열"},json:{fn:(o,e)=>{try{return JSON.stringify(o,null,e)}catch{return""}},description:"JSON 문자열로 변환"},localized:{fn:(o,e)=>o==null?"":typeof o=="string"?o:typeof o!="object"?String(o):o[e||"ko"]||o.ko||o.en||Object.values(o)[0]||"",description:"다국어 객체에서 로케일에 맞는 값 추출"}},Zi=new Map,vi=class vi{static getInstance(){return vi.instance||(vi.instance=new vi),vi.instance}register(e,n,a){if(Zs[e]){Qi.warn(`[PipeRegistry] 내장 파이프를 덮어쓸 수 없습니다: ${e}`);return}Zi.set(e,{fn:n,description:a}),Qi.log(`[PipeRegistry] 커스텀 파이프 등록됨: ${e}`)}unregister(e){if(Zs[e])return Qi.warn(`[PipeRegistry] 내장 파이프는 해제할 수 없습니다: ${e}`),!1;const n=Zi.delete(e);return n&&Qi.log(`[PipeRegistry] 커스텀 파이프 해제됨: ${e}`),n}get(e){const n=Zs[e];return n?n.fn:Zi.get(e)?.fn}has(e){return!!Zs[e]||Zi.has(e)}execute(e,n,a=[]){const i=this.get(e);if(!i)return Qi.warn(`[PipeRegistry] 알 수 없는 파이프: ${e}`),n;try{return i(n,...a)}catch(l){return Qi.error(`[PipeRegistry] 파이프 실행 오류 (${e}):`,l),n}}list(){const e=[];for(const[n,a]of Object.entries(Zs))e.push({name:n,description:a.description,type:"built-in"});for(const[n,a]of Zi.entries())e.push({name:n,description:a.description,type:"custom"});return e.sort((n,a)=>n.name.localeCompare(a.name))}clearCustomPipes(){Zi.clear()}};$(vi,"instance",null);let Ml=vi;Ml.getInstance();function nw(o){const e=o.trim(),n=e.indexOf("(");if(n===-1)return{name:e,args:[]};const a=e.slice(0,n).trim(),i=e.slice(n+1,-1);if(!i.trim())return{name:a,args:[]};const l=[];let c="",d=null,f=0;for(let g=0;g0?i[g-1]:"")==="\\"){c+=m;continue}if((m==='"'||m==="'")&&!d){d=m;continue}if(m===d){d=null;continue}if(d){c+=m;continue}if(m==="("||m==="["||m==="{"){f++,c+=m;continue}if(m===")"||m==="]"||m==="}"){f--,c+=m;continue}if(m===","&&f===0){l.push(hp(c.trim())),c="";continue}c+=m}return c.trim()&&l.push(hp(c.trim())),{name:a,args:l}}function hp(o){if(o==="null")return null;if(o==="undefined")return;if(o==="true")return!0;if(o==="false")return!1;const e=Number(o);return!isNaN(e)&&o!==""?e:o}function rw(o){const e=[];let n="",a=null,i=0;for(let l=0;l0?o[l-1]:"",f=l0?o[a-1]:"",c=a0){a--;continue}return null}}return a===0?e.trim():null}function iw(o){const e=[];if(typeof o!="string")return e;for(let n=0;n0){i--;continue}if(o[c+1]==="}"){l=c;break}break}}l!==-1&&(e.push({start:n,end:l+2,expr:o.slice(n+2,l)}),n=l+1)}return e}function eo(o){return/[?:|&!+\-*/<>=()[\]{}]/.test(o)}function gp(o){const e=typeof o=="string"?o.trim():"";return e===""?"empty":fd.has(e)?"literal":Pn(e)?"pipe":eo(e)?"expression":"path"}function hd(o,e,n,a){const i=typeof o=="string"?o.trim():"",l=gp(i),c=a?.skipCache?{skipCache:!0}:void 0;if(l==="empty"){a?.onEmpty?.(o);return}if(l==="literal")return fd.get(i);try{return l==="pipe"?n.evaluatePipeExpression(i,e,c,a?.trackingInfo):l==="expression"?n.evaluateExpression(i,e,a?.trackingInfo):n.resolve(i,e,c,a?.trackingInfo)}catch(d){if(a?.onError)return a.onError(d,i);throw d}}const er="raw:";function pp(o){return o.startsWith(er)?o.slice(er.length):o}const to="﷐",fi="﷑",xa="﷒";function no(o){return to+o+fi}function $l(o){return o.length>=2&&o.charCodeAt(0)===64976&&o.charCodeAt(o.length-1)===64977}function Nl(o){return o.includes(to)}function gd(o){return o.slice(1,-1)}function es(o){if(typeof o=="string")return no(o);if(Array.isArray(o))return o.map(es);if(o&&typeof o=="object"){const e={};for(const[n,a]of Object.entries(o))e[n]=es(a);return e}return o}function Ta(o){if(typeof o=="string")return $l(o)?gd(o):Nl(o)||o.includes(fi)?o.split(to).join("").split(fi).join(""):o;if(Array.isArray(o)){let e=!1;const n=o.map(a=>{const i=Ta(a);return i!==a&&(e=!0),i});return e?n:o}if(o&&typeof o=="object"){if(o.$$typeof!==void 0)return o;let e=!1;const n={};for(const[a,i]of Object.entries(o)){const l=Ta(i);l!==i&&(e=!0),n[a]=l}return e?n:o}return o}const Ra=ut("DataBindingEngine"),sw=/^[$A-Za-z_][$A-Za-z0-9_]*$/,ow=new Set(["break","case","catch","class","const","continue","debugger","default","delete","do","else","enum","export","extends","false","finally","for","function","if","implements","import","in","instanceof","interface","let","new","null","package","private","protected","public","return","static","super","switch","this","throw","true","try","typeof","var","void","while","with","yield","arguments","eval"]);function ro(){try{return window.G7Core?.devTools}catch{return}}class pd extends Error{constructor(n,a,i){super(n);$(this,"path");$(this,"context");this.path=a,this.context=i,this.name="DataBindingError"}}const ta=class ta{constructor(e){$(this,"cache",new Map);$(this,"expressionFnCache",new Map);$(this,"renderCycleCache",new Map);$(this,"currentRenderCycleId",0);$(this,"defaultOptions",{defaultValue:void 0,nullSafe:!0,detectCircular:!0,maxDepth:10});e&&(this.defaultOptions={...this.defaultOptions,...e})}startRenderCycle(){this.currentRenderCycleId++,this.renderCycleCache.clear()}isRenderCycleCacheActive(){return this.currentRenderCycleId>0}getFromRenderCycleCache(e){if(this.isRenderCycleCacheActive())return this.renderCycleCache.get(e)}saveToRenderCycleCache(e,n){this.isRenderCycleCacheActive()&&this.renderCycleCache.set(e,n)}resolveBindings(e,n,a,i){const l={...this.defaultOptions,...a},c=ro(),d=n._computed&&!n.$computed?{...n,$computed:n._computed}:n,f=(S,v)=>{const _=v.trim();let A=!1,x=_;if(_.startsWith(er)&&(A=!0,x=_.slice(er.length)),Pn(x))try{const P=this.evaluatePipeExpression(x,d,l,{...i,method:"resolveBindings",displayExpression:`{{${_}}}`}),K=this.formatValue(P);return A?no(K):K}catch(P){return Ra.error("Pipe expression evaluation failed:",x,P),S}if(eo(x)){const P=c?.isEnabled()?performance.now():0;try{let K,ge=!1;if(l.skipCache)K=this.evaluateExpression(x,d);else{const Se=`expr:${x}`,Ue=this.getFromRenderCycleCache(Se);Ue!==void 0?(K=Ue,ge=!0):(K=this.evaluateExpression(x,d),this.saveToRenderCycleCache(Se,K))}if(c?.isEnabled()){const Se=performance.now()-P;c.trackExpressionEval({expression:`{{${_}}}`,result:this.sanitizeResultForTracking(K),resultType:this.getResultType(K),componentId:i?.componentId,componentName:i?.componentName,propName:i?.propName,fromCache:ge,duration:Se,method:"resolveBindings",skipCache:l.skipCache})}const Ee=this.formatValue(K);return A?no(Ee):Ee}catch(K){return Ra.error("Expression evaluation failed:",x,K),S}}if(l.skipCache){const P=c?.isEnabled()?performance.now():0,K=this.resolvePath(x,d,l);if(c?.isEnabled()){const Ee=performance.now()-P;c.trackExpressionEval({expression:`{{${_}}}`,result:this.sanitizeResultForTracking(K),resultType:this.getResultType(K),componentId:i?.componentId,componentName:i?.componentName,propName:i?.propName,fromCache:!1,duration:Ee,method:"resolveBindings",skipCache:!0})}const ge=this.formatValue(K);return A?no(ge):ge}const M=x.startsWith("_global")||x.startsWith("_local")||x.startsWith("_isolated")||x.startsWith("$parent"),R=c?.isEnabled()?performance.now():0;let D,B=!1;if(M){const P=this.getFromRenderCycleCache(x);P!==void 0?(D=P,B=!0):(D=this.resolvePath(x,d,l),this.saveToRenderCycleCache(x,D))}else{const P=this.getFromCache(x);P!==void 0?(D=P,B=!0):(D=this.resolvePath(x,d,l),this.saveToCache(x,D))}if(c?.isEnabled()){const P=performance.now()-R;c.trackExpressionEval({expression:`{{${_}}}`,result:this.sanitizeResultForTracking(D),resultType:this.getResultType(D),componentId:i?.componentId,componentName:i?.componentName,propName:i?.propName,fromCache:B,duration:P,method:"resolveBindings",skipCache:l.skipCache})}const q=this.formatValue(D);return A?no(q):q},g=iw(e);if(g.length===0)return e;let m="",b=0;for(const S of g)m+=e.slice(b,S.start),m+=f(e.slice(S.start,S.end),S.expr),b=S.end;return m+e.slice(b)}evaluatePipeExpression(e,n,a,i){const l={...this.defaultOptions,...a},c=ro(),d=c?.isEnabled()?performance.now():0,f=n._computed&&!n.$computed?{...n,$computed:n._computed}:n,[g,m]=rw(e);let b,S=!1;const v=eo(g),_=g.startsWith("_global")||g.startsWith("_local")||g.startsWith("_isolated")||g.startsWith("$parent");if(l.skipCache)v?b=this.evaluateExpression(g,f):b=this.resolvePath(g,f,l);else if(v){const x=this.getFromRenderCycleCache(`expr:${g}`);x!==void 0?(b=x,S=!0):(b=this.evaluateExpression(g,f),this.saveToRenderCycleCache(`expr:${g}`,b))}else if(_){const x=this.getFromRenderCycleCache(g);x!==void 0?(b=x,S=!0):(b=this.resolvePath(g,f,l),this.saveToRenderCycleCache(g,b))}else{const x=this.getFromCache(g);x!==void 0?(b=x,S=!0):(b=this.resolvePath(g,f,l),this.saveToCache(g,b))}const A=aw(b,m);if(c?.isEnabled()){const x=performance.now()-d;c.trackExpressionEval({expression:i?.displayExpression??`{{${e}}}`,result:this.sanitizeResultForTracking(A),resultType:this.getResultType(A),componentId:i?.componentId,componentName:i?.componentName,propName:i?.propName,fromCache:S,duration:x,method:i?.method??"evaluatePipeExpression",skipCache:l.skipCache})}return A}resolve(e,n,a,i){const l={...this.defaultOptions,...a},c=ro(),d=c?.isEnabled()?performance.now():0;if(l.skipCache){const b=this.resolvePath(e,n,l);return c?.isEnabled()&&c.trackExpressionEval({expression:e,result:this.sanitizeResultForTracking(b),resultType:this.getResultType(b),componentId:i?.componentId,componentName:i?.componentName,propName:i?.propName,fromCache:!1,duration:performance.now()-d,method:"resolve",skipCache:!0}),b}const f=e.startsWith("_global")||e.startsWith("_local")||e.startsWith("_isolated")||e.startsWith("$parent");let g=!1;if(f){const b=this.getFromRenderCycleCache(e);if(b!==void 0)return g=!0,c?.isEnabled()&&c.trackExpressionEval({expression:e,result:this.sanitizeResultForTracking(b),resultType:this.getResultType(b),componentId:i?.componentId,componentName:i?.componentName,propName:i?.propName,fromCache:!0,duration:performance.now()-d,method:"resolve",skipCache:l.skipCache}),b}else{const b=this.getFromCache(e);if(b!==void 0)return g=!0,c?.isEnabled()&&c.trackExpressionEval({expression:e,result:this.sanitizeResultForTracking(b),resultType:this.getResultType(b),componentId:i?.componentId,componentName:i?.componentName,propName:i?.propName,fromCache:!0,duration:performance.now()-d,method:"resolve",skipCache:l.skipCache}),b}const m=this.resolvePath(e,n,l);return f?this.saveToRenderCycleCache(e,m):this.saveToCache(e,m),c?.isEnabled()&&c.trackExpressionEval({expression:e,result:this.sanitizeResultForTracking(m),resultType:this.getResultType(m),componentId:i?.componentId,componentName:i?.componentName,propName:i?.propName,fromCache:g,duration:performance.now()-d,method:"resolve",skipCache:l.skipCache}),m}resolvePath(e,n,a,i=0,l=new WeakSet){if(typeof e!="string"||e.trim()===""){Ra.warn("resolvePath: 빈 경로가 전달되었습니다 — undefined 로 해석합니다.");return}if(a.maxDepth&&i>a.maxDepth){if(a.detectCircular)throw new pd(`Maximum depth exceeded (${a.maxDepth}). Possible circular reference.`,e,n);return a.defaultValue}const c=this.parsePath(e);let d=n;for(let f=0;fta.CACHE_EXPIRY){this.cache.delete(e),a?.isEnabled()&&a.recordCacheMiss();return}return a?.isEnabled()&&a.recordCacheHit(),n.value}saveToCache(e,n){this.cache.set(e,{value:n,timestamp:Date.now()})}isActionDefinition(e){return typeof e.handler!="string"?!1:e.params!==void 0||Array.isArray(e.actions)||typeof e.target=="string"||e.onSuccess!==void 0||e.onError!==void 0}resolveObject(e,n,a){if(this.isSwitchExpression(e)){const d=e;return this.resolveSwitch(d,n,a)}if(this.isActionDefinition(e))return{...e};if("iteration"in e)return{...e};const i={},c=[...["cellChildren","expandChildren","expandContext","render"],...a?.skipBindingKeys||[]];for(const[d,f]of Object.entries(e)){if(c.includes(d)){i[d]=f;continue}try{this.resolveObjectEntry(i,d,f,n,a)}catch(g){Ra.warn(`resolveObject: 값 해석 실패 (key: ${d}):`,g),i[d]=void 0}}return i}resolveObjectEntry(e,n,a,i,l){if(typeof a=="string"){const c=Aa(a);if(c!==null){const d=c.trim();let f=!1,g=d;d.startsWith(er)&&(f=!0,g=d.slice(er.length));let m;switch(gp(g)){case"empty":Ra.warn(`resolveObject: 빈 바인딩 \`{{}}\` (key: ${n}) — undefined 로 해석합니다.`),m=void 0;break;case"literal":m=fd.get(g);break;case"pipe":m=this.evaluatePipeExpression(g,i,l);break;case"expression":m=this.evaluateExpression(g,i,l);break;default:m=this.resolve(g,i,l)}e[n]=f&&m!=null?es(m):m}else e[n]=this.resolveBindings(a,i,l)}else Array.isArray(a)?e[n]=a.map(c=>typeof c=="string"?this.resolveBindings(c,i,l):typeof c=="object"&&c!==null?this.resolveObject(c,i,l):c):a&&typeof a=="object"?e[n]=this.resolveObject(a,i,l):e[n]=a}clearCache(){this.cache.clear(),this.expressionFnCache.clear()}invalidateCacheByKeys(e){for(const n of this.cache.keys())for(const a of e)if(n===a||n.startsWith(`${a}.`)||n.startsWith(`${a}[`)){this.cache.delete(n);break}}pruneCache(){const e=Date.now();for(const[n,a]of this.cache.entries())e-a.timestamp>ta.CACHE_EXPIRY&&this.cache.delete(n)}getCacheStats(){const e=Date.now();let n=0;for(const a of this.cache.values())e-a.timestamp>ta.CACHE_EXPIRY&&n++;return{size:this.cache.size,expired:n}}evaluateExpression(e,n,a){const i=ro(),l=i?.isEnabled()?performance.now():0;i?.isEnabled()&&i.trackBindingEval();try{let c=this.preprocessOptionalChaining(e);c=this.preprocessTranslationTokens(c);const d=this.extractVariablesFromExpression(c),f={...n};for(const D of d)D in f||(f[D]=void 0);n._computed&&!f.$computed&&(f.$computed=n._computed);let g=n.$templateId,m=n.$locale;if(typeof window<"u"&&(!g||!m)){const D=window.__templateApp?.getConfig?.();g??(g=D?.templateId),m??(m=D?.locale)}const b=m||"ko";f.$localized=(D,B)=>{if(D==null&&!B)return"";if(typeof D=="string")return D;if(D&&typeof D=="object"&&D[b])return D[b];if(B&&typeof B=="string"){const q=f.$t?f.$t(B):B;if(q&&q!==B)return q}return D&&typeof D=="object"?D.ko||D.en||Object.values(D)[0]||"":D==null?"":String(D)};const S=typeof window<"u"?window.G7Core:void 0;f.$uuid=()=>S?.uuid?S.uuid():typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,D=>{const B=Math.random()*16|0;return(D==="x"?B:B&3|8).toString(16)});const v={templateId:g||"",locale:b};f.$t=D=>{if(!D||typeof D!="string")return"";try{return _a.getInstance().translate(D,v)}catch(B){return Ra.warn("$t() translation failed for key:",D,B),D}},f.$get=(D,B,q=void 0)=>{if(D==null)return q;const P=Array.isArray(B)?B:[B];if(P.length===0)return D;let K=D;for(const ge of P){if(K==null||ge==null)return q;K=K[ge]}return K??q};const _=[],A=[];for(const[D,B]of Object.entries(f))!sw.test(D)||ow.has(D)||(_.push(D),A.push(B));const x=`${c}|${_.join(",")}`;let L=this.expressionFnCache.get(x);const M=!!L;L||(L=new Function(..._,`return (${c});`),this.expressionFnCache.set(x,L));const R=L(...A);if(i?.isEnabled()){const D=performance.now()-l;i.trackExpressionEval({expression:e,result:this.sanitizeResultForTracking(R),resultType:this.getResultType(R),componentId:a?.componentId,componentName:a?.componentName,propName:a?.propName,fromCache:M,duration:D,method:"evaluateExpression",skipCache:a?.skipCache})}return R}catch(c){throw new Error(`Failed to evaluate expression "${e}": ${c instanceof Error?c.message:String(c)}`)}}getResultType(e){return e===null?"null":e===void 0?"undefined":Array.isArray(e)?"array":typeof e}sanitizeResultForTracking(e){if(e==null||typeof e!="object")return e;try{if(Array.isArray(e))return e.length>10?`[Array(${e.length})]`:e.slice(0,10);const n=Object.keys(e);return n.length>20?`{Object(${n.length} keys)}`:(JSON.stringify(e),e)}catch{return"[Complex Object]"}}preprocessTranslationTokens(e){return e=e.replace(/(['"])(\$t:([a-zA-Z_][a-zA-Z0-9_.\-]*))\1/g,(n,a,i,l)=>`$t('${l}')`),e.replace(/(?(n.push(l),`__STRING_LITERAL_${n.length-1}__`));const i=[];return a=a.replace(/\$t:[a-zA-Z_][a-zA-Z0-9_.\-]*(?:\|[^'"\s,)]+)?/g,l=>(i.push(l),`__TRANSLATION_TOKEN_${i.length-1}__`)),a=a.replace(/([a-zA-Z_$][a-zA-Z0-9_$]*)\.(?!\?)/g,"$1?."),a=a.replace(/__TRANSLATION_TOKEN_(\d+)__/g,(l,c)=>i[parseInt(c,10)]),a=a.replace(/__STRING_LITERAL_(\d+)__/g,(l,c)=>n[parseInt(c,10)]),a}extractVariablesFromExpression(e){const n=new Set(["true","false","null","undefined","NaN","Infinity","if","else","for","while","do","switch","case","break","continue","return","function","class","const","let","var","new","delete","typeof","instanceof","this","super","import","export","default","try","catch","finally","throw","Math","Date","JSON","Array","Object","String","Number","Boolean","RegExp","Set","Map","WeakSet","WeakMap","Symbol","Promise","BigInt","Error","parseInt","parseFloat","isNaN","isFinite","encodeURI","decodeURI","encodeURIComponent","decodeURIComponent"]),a=/(?{n!==null&&clearTimeout(n),n=setTimeout(()=>{o(...a)},e)})}class cw{constructor(){$(this,"subscribers",new Set);$(this,"currentWidth",1024);$(this,"debouncedHandler",null);$(this,"isInitialized",!1);this.initialize()}initialize(){typeof window>"u"||(this.currentWidth=window.innerWidth,this.isInitialized=!0,this.debouncedHandler=lw(()=>{this.currentWidth=window.innerWidth,this.notifySubscribers()},150),window.addEventListener("resize",this.debouncedHandler))}notifySubscribers(){this.subscribers.forEach(e=>{e(this.currentWidth)})}subscribe(e){return this.subscribers.add(e),e(this.currentWidth),()=>{this.subscribers.delete(e)}}getWidth(){return this.currentWidth}parseRange(e){if(Hl.has(e))return Hl.get(e)??null;let n=null;if(Il[e])n={...Il[e]};else{const a=e.match(/^(-?\d*)-(-?\d*)$/);if(a){const[,i,l]=a,c=i===""?0:parseInt(i,10),d=l===""?1/0:parseInt(l,10);!isNaN(c)&&!isNaN(d)&&c<=d&&(n={min:c,max:d})}}return Hl.set(e,n),n}getMatchingKey(e,n){const a=[];for(const i of Object.keys(e)){const l=this.parseRange(i);l&&n>=l.min&&n<=l.max&&a.push({key:i,range:l,isPreset:!!Il[i]})}return a.length===0?null:(a.sort((i,l)=>{if(i.isPreset!==l.isPreset)return i.isPreset?1:-1;const c=i.range.max-i.range.min,d=l.range.max-l.range.min;return c-d}),a[0].key)}matches(e){const n=this.parseRange(e);return n?this.currentWidth>=n.min&&this.currentWidth<=n.max:!1}clearSubscribers(){this.subscribers.clear()}destroy(){this.debouncedHandler&&typeof window<"u"&&window.removeEventListener("resize",this.debouncedHandler),this.clearSubscribers(),Hl.clear()}_setWidthForTesting(e){this.currentWidth=e,this.notifySubscribers()}}const hi=new cw,ao=ut("ConditionEvaluator");function uw(o,e,n,a){return hd(pp(o),e,n,{skipCache:!0,onEmpty:()=>{ao.warn(`${a}: 빈 바인딩 \`{{}}\` — undefined 로 해석합니다.`)}})}function mp(o,e,n){if(!o)return!0;try{const a=Aa(o);let i;if(a!==null?i=uw(a,e,n,"evaluateStringCondition"):i=n.resolveBindings(o,e,{skipCache:!0}),typeof i=="string"){const l=i.toLowerCase().trim();if(l==="false"||l==="0"||l===""||l==="null"||l==="undefined")return!1}return!!i}catch(a){return ao.warn(`evaluateStringCondition: 조건 평가 실패: ${o}`,a),!1}}function zl(o,e,n){if(typeof o=="string")return mp(o,e,n);if("and"in o){if(!Array.isArray(o.and)||o.and.length===0)return ao.warn("evaluateConditionExpression: AND 그룹이 비어있습니다"),!0;for(const a of o.and)if(!zl(a,e,n))return!1;return!0}if("or"in o){if(!Array.isArray(o.or)||o.or.length===0)return ao.warn("evaluateConditionExpression: OR 그룹이 비어있습니다"),!1;for(const a of o.or)if(zl(a,e,n))return!0;return!1}return ao.warn("evaluateConditionExpression: 알 수 없는 조건 형식",o),!1}function yp(o,e,n){if(!Array.isArray(o)||o.length===0)return{matched:!1,branchIndex:-1};for(let a=0;a{tr.warn(`evaluateIfCondition: 빈 바인딩 \`{{}}\` (컴포넌트: ${a||"unknown"}) — undefined 로 해석합니다.`)}}):l=n.resolveBindings(o,e,{skipCache:!0}),typeof l=="string"){const c=l.toLowerCase().trim();if(c==="false"||c==="0"||c===""||c==="null"||c==="undefined")return!1}return!!l}catch(i){return tr.warn(`evaluateIfCondition: 조건 평가 실패 (컴포넌트: ${a||"unknown"}):`,i),!1}}function ts(o,e,n,a){if(o.if!==void 0)return wp(o.if,e,n,a);if(o.condition!==void 0)return wp(o.condition,e,n,a);if(o.conditions===void 0)return!0;const i=o.conditions;try{return dw(i)?zl(i,e,n):fw(i)?yp(i,e,n).matched:(tr.warn(`evaluateRenderCondition: 알 수 없는 conditions 형식 (컴포넌트: ${a||"unknown"})`),!0)}catch(l){return tr.warn(`evaluateRenderCondition: conditions 평가 실패 (컴포넌트: ${a||"unknown"}):`,l),!1}}function Cp(o){if(!o.responsive)return o;const e=hi.getWidth(),n=hi.getMatchingKey(o.responsive,e);if(!n)return o;const a=o.responsive[n];return{...o,props:{...o.props,...a.props},children:a.children??o.children,text:a.text??o.text,if:a.if??o.if,iteration:a.iteration??o.iteration}}function io(o,e,n,a,i){if(!o||o.length===0)return[];const l=i?.bindingEngine??new xn,c=i?.translationEngine??_a.getInstance(),d=i?.translationContext??{templateId:"",locale:"ko"},f=gw(e,i?.componentContext),g=(v,_)=>{if(typeof v=="string")return $l(v)||Nl(v)||!/\$t:[a-zA-Z0-9._-]+/.test(v)?v:c.resolveTranslations(v,d,_);if(Array.isArray(v))return v.map(A=>g(A,_));if(v&&typeof v=="object"){const A={};for(const[x,L]of Object.entries(v))A[x]=g(L,_);return A}return v},m=(v,_)=>{if(typeof v=="string"){if($l(v))return gd(v);if(Nl(v)){const x=[],L=v.replace(new RegExp(`${to}([^${fi}]*)${fi}`,"g"),(R,D)=>(x.push(D),`${xa}${x.length-1}${xa}`));let M=L;return/\$t:[a-zA-Z0-9._-]+/.test(L)&&(M=c.resolveTranslations(L,d,_)),M.replace(new RegExp(`${xa}(\\d+)${xa}`,"g"),(R,D)=>x[parseInt(D)])}if(v.startsWith("$t:defer:")){const x="$t:"+v.slice(9);return c.resolveTranslations(x,d,_)}if(v.startsWith("$t:"))return c.resolveTranslations(v,d,_);if(/\$t:[a-zA-Z0-9._-]+/.test(v)){const x=v.trim();if(!(x.startsWith("{")&&x.endsWith("}")||x.startsWith("[")&&x.endsWith("]"))&&!v.includes("{{"))return c.resolveTranslations(v,d,_)}const A=Sp(v);if(A!==null){let x=!1,L=A;A.startsWith(er)&&(x=!0,L=A.slice(er.length));let M=!1,R=hd(L,_,l,{skipCache:!0,onError:D=>{tr.warn("renderItemChildren: 표현식 평가 실패:",D),M=!0},onEmpty:()=>{tr.warn("renderItemChildren: 빈 바인딩 `{{}}` 은 해석하지 않습니다.")}});return M?void 0:(x||(R=g(R,_)),x&&R!=null?es(R):R)}if(v.includes("{{")){const x=l.resolveBindings(v,_,{skipCache:!0});return typeof x=="string"&&/\$t:[a-zA-Z0-9._-]+/.test(x)?c.resolveTranslations(x,d,_):x}return v}if(Array.isArray(v))return v.map(A=>m(A,_));if(v&&typeof v=="object"){if(l.isSwitchExpression(v))return l.resolveSwitch(v,_,{skipCache:!0});if(l.isActionDefinition(v))return{...v};const A={};for(const[x,L]of Object.entries(v))A[x]=m(L,_);return A}return v},b=(v,_)=>{if(!v)return{};const A={};for(const[x,L]of Object.entries(v))A[x]=m(L,_);return A},S=(v,_,A)=>{const x=Cp(v),L=x.iteration;if(!L)return[];const M=yd(L.source,A,l);if(!Array.isArray(M))return tr.warn(`renderItemChildren: iteration.source가 배열이 아닙니다: ${L.source}`),[];const R=bp();if(R?.isEnabled()){const B=`${_}-iteration`;R.trackIteration(B,L.source,L.item_var,L.index_var,M.length)}const D=n[x.name];return D?M.flatMap((B,q)=>{const P={...A,[L.item_var]:B,[`${L.item_var}_index`]:q,...L.index_var?{[L.index_var]:q}:{}},K=ts({if:x.if,condition:x.condition,conditions:x.conditions},P,l,x.id);if(x.if&&R?.isEnabled()){const St=`${_}-iter-${q}-if`;R.trackIfCondition(St,x.if,K,x.name)}if(!K)return[];const ge=x.id?String(Ta(m(x.id,P))):void 0,Ee=`${_}-iter-${q}`,Se=i?.getRemountKey?i.getRemountKey(ge,Ee):Ee,Ue=b(x.props,P),Ve=bd(Ue,x.actions,P,{actionDispatcher:i?.actionDispatcher,componentContext:i?.componentContext});let qe=null;return x.text!==void 0?qe=m(x.text,P):x.children&&x.children.length>0&&(qe=io(x.children,P,n,Se,i)),R?.isEnabled()&&R.trackRender(x.name),[Ye.createElement(D,{key:Se,...Ta(Ve)},Ta(qe))]}):(tr.warn(`renderItemChildren: 컴포넌트를 찾을 수 없습니다: ${x.name}`),[])};return o.flatMap((v,_)=>{const A=Cp(v),x=A.id?String(Ta(m(A.id,f))):void 0,L=a?`${a}-${x||_}`:x||`child-${_}`,M=i?.getRemountKey?i.getRemountKey(x,L):L;if(A.iteration)return S(A,M,f);const R=ts({if:A.if,condition:A.condition,conditions:A.conditions},f,l,A.id),D=bp();if(A.if&&D?.isEnabled()){const ge=`${M}-if`;D.trackIfCondition(ge,A.if,R,A.name)}if(!R)return[];const B=n[A.name];if(!B)return tr.warn(`renderItemChildren: 컴포넌트를 찾을 수 없습니다: ${A.name}`),[];const q=b(A.props,f),P=bd(q,A.actions,f,{actionDispatcher:i?.actionDispatcher,componentContext:i?.componentContext});let K=null;return A.text!==void 0?K=m(A.text,f):A.children&&A.children.length>0&&(K=io(A.children,f,n,M,i)),D?.isEnabled()&&D.trackRender(A.name),[Ye.createElement(B,{key:M,...Ta(P)},Ta(K))]})}function ns(o,e,n,a){let i,l;return n instanceof xn?(i=n,l=a):(i=vp,l=n),i.resolveBindings(o,e,l)}function pw(o,e,n,a){const i=n??vp,l={skipCache:!0,...a};let c;try{c=i.resolveBindings(o.key,e,{skipCache:l.skipCache})?.toString()?.trim()??""}catch(g){tr.warn("resolveClassMap: key 평가 실패:",o.key,g),c=""}let d="";c&&o.variants&&c in o.variants?d=o.variants[c]:o.default&&(d=o.default);const f=[];return o.base?.trim()&&f.push(o.base.trim()),d?.trim()&&f.push(d.trim()),f.join(" ")}function Ep(o,e){return function(){return o.apply(e,arguments)}}const{toString:mw}=Object.prototype,{getPrototypeOf:jl}=Object,{iterator:Ul,toStringTag:_p}=Symbol,Bl=(o=>e=>{const n=mw.call(e);return o[n]||(o[n]=n.slice(8,-1).toLowerCase())})(Object.create(null)),mr=o=>(o=o.toLowerCase(),e=>Bl(e)===o),Pl=o=>e=>typeof e===o,{isArray:rs}=Array,as=Pl("undefined");function so(o){return o!==null&&!as(o)&&o.constructor!==null&&!as(o.constructor)&&On(o.constructor.isBuffer)&&o.constructor.isBuffer(o)}const Ap=mr("ArrayBuffer");function yw(o){let e;return typeof ArrayBuffer<"u"&&ArrayBuffer.isView?e=ArrayBuffer.isView(o):e=o&&o.buffer&&Ap(o.buffer),e}const bw=Pl("string"),On=Pl("function"),xp=Pl("number"),oo=o=>o!==null&&typeof o=="object",vw=o=>o===!0||o===!1,ql=o=>{if(Bl(o)!=="object")return!1;const e=jl(o);return(e===null||e===Object.prototype||Object.getPrototypeOf(e)===null)&&!(_p in o)&&!(Ul in o)},Sw=o=>{if(!oo(o)||so(o))return!1;try{return Object.keys(o).length===0&&Object.getPrototypeOf(o)===Object.prototype}catch{return!1}},ww=mr("Date"),Cw=mr("File"),Ew=o=>!!(o&&typeof o.uri<"u"),_w=o=>o&&typeof o.getParts<"u",Aw=mr("Blob"),xw=mr("FileList"),Tw=o=>oo(o)&&On(o.pipe);function Rw(){return typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:typeof global<"u"?global:{}}const Tp=Rw(),Rp=typeof Tp.FormData<"u"?Tp.FormData:void 0,kw=o=>{if(!o)return!1;if(Rp&&o instanceof Rp)return!0;const e=jl(o);if(!e||e===Object.prototype||!On(o.append))return!1;const n=Bl(o);return n==="formdata"||n==="object"&&On(o.toString)&&o.toString()==="[object FormData]"},Dw=mr("URLSearchParams"),[Ow,Lw,Mw,$w]=["ReadableStream","Request","Response","Headers"].map(mr),Nw=o=>o.trim?o.trim():o.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,"");function lo(o,e,{allOwnKeys:n=!1}={}){if(o===null||typeof o>"u")return;let a,i;if(typeof o!="object"&&(o=[o]),rs(o))for(a=0,i=o.length;a0;)if(i=n[a],e===i.toLowerCase())return i;return null}const gi=typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:global,Dp=o=>!as(o)&&o!==gi;function vd(...o){const{caseless:e,skipUndefined:n}=Dp(this)&&this||{},a={},i=(l,c)=>{if(c==="__proto__"||c==="constructor"||c==="prototype")return;const d=e&&kp(a,c)||c,f=Sd(a,d)?a[d]:void 0;ql(f)&&ql(l)?a[d]=vd(f,l):ql(l)?a[d]=vd({},l):rs(l)?a[d]=l.slice():(!n||!as(l))&&(a[d]=l)};for(let l=0,c=o.length;l(lo(e,(i,l)=>{n&&On(i)?Object.defineProperty(o,l,{__proto__:null,value:Ep(i,n),writable:!0,enumerable:!0,configurable:!0}):Object.defineProperty(o,l,{__proto__:null,value:i,writable:!0,enumerable:!0,configurable:!0})},{allOwnKeys:a}),o),Hw=o=>(o.charCodeAt(0)===65279&&(o=o.slice(1)),o),zw=(o,e,n,a)=>{o.prototype=Object.create(e.prototype,a),Object.defineProperty(o.prototype,"constructor",{__proto__:null,value:o,writable:!0,enumerable:!1,configurable:!0}),Object.defineProperty(o,"super",{__proto__:null,value:e.prototype}),n&&Object.assign(o.prototype,n)},jw=(o,e,n,a)=>{let i,l,c;const d={};if(e=e||{},o==null)return e;do{for(i=Object.getOwnPropertyNames(o),l=i.length;l-- >0;)c=i[l],(!a||a(c,o,e))&&!d[c]&&(e[c]=o[c],d[c]=!0);o=n!==!1&&jl(o)}while(o&&(!n||n(o,e))&&o!==Object.prototype);return e},Uw=(o,e,n)=>{o=String(o),(n===void 0||n>o.length)&&(n=o.length),n-=e.length;const a=o.indexOf(e,n);return a!==-1&&a===n},Bw=o=>{if(!o)return null;if(rs(o))return o;let e=o.length;if(!xp(e))return null;const n=new Array(e);for(;e-- >0;)n[e]=o[e];return n},Pw=(o=>e=>o&&e instanceof o)(typeof Uint8Array<"u"&&jl(Uint8Array)),qw=(o,e)=>{const a=(o&&o[Ul]).call(o);let i;for(;(i=a.next())&&!i.done;){const l=i.value;e.call(o,l[0],l[1])}},Gw=(o,e)=>{let n;const a=[];for(;(n=o.exec(e))!==null;)a.push(n);return a},Vw=mr("HTMLFormElement"),Fw=o=>o.toLowerCase().replace(/[-_\s]([a-z\d])(\w*)/g,function(n,a,i){return a.toUpperCase()+i}),Sd=(({hasOwnProperty:o})=>(e,n)=>o.call(e,n))(Object.prototype),Kw=mr("RegExp"),Op=(o,e)=>{const n=Object.getOwnPropertyDescriptors(o),a={};lo(n,(i,l)=>{let c;(c=e(i,l,o))!==!1&&(a[l]=c||i)}),Object.defineProperties(o,a)},Ww=o=>{Op(o,(e,n)=>{if(On(o)&&["arguments","caller","callee"].includes(n))return!1;const a=o[n];if(On(a)){if(e.enumerable=!1,"writable"in e){e.writable=!1;return}e.set||(e.set=()=>{throw Error("Can not rewrite read-only method '"+n+"'")})}})},Yw=(o,e)=>{const n={},a=i=>{i.forEach(l=>{n[l]=!0})};return rs(o)?a(o):a(String(o).split(e)),n},Xw=()=>{},Jw=(o,e)=>o!=null&&Number.isFinite(o=+o)?o:e;function Qw(o){return!!(o&&On(o.append)&&o[_p]==="FormData"&&o[Ul])}const Zw=o=>{const e=new WeakSet,n=a=>{if(oo(a)){if(e.has(a))return;if(so(a))return a;if(!("toJSON"in a)){e.add(a);const i=rs(a)?[]:{};return lo(a,(l,c)=>{const d=n(l);!as(d)&&(i[c]=d)}),e.delete(a),i}}return a};return n(o)},e0=mr("AsyncFunction"),t0=o=>o&&(oo(o)||On(o))&&On(o.then)&&On(o.catch),Lp=((o,e)=>o?setImmediate:e?((n,a)=>(gi.addEventListener("message",({source:i,data:l})=>{i===gi&&l===n&&a.length&&a.shift()()},!1),i=>{a.push(i),gi.postMessage(n,"*")}))(`axios@${Math.random()}`,[]):n=>setTimeout(n))(typeof setImmediate=="function",On(gi.postMessage)),n0=typeof queueMicrotask<"u"?queueMicrotask.bind(gi):typeof process<"u"&&process.nextTick||Lp,W={isArray:rs,isArrayBuffer:Ap,isBuffer:so,isFormData:kw,isArrayBufferView:yw,isString:bw,isNumber:xp,isBoolean:vw,isObject:oo,isPlainObject:ql,isEmptyObject:Sw,isReadableStream:Ow,isRequest:Lw,isResponse:Mw,isHeaders:$w,isUndefined:as,isDate:ww,isFile:Cw,isReactNativeBlob:Ew,isReactNative:_w,isBlob:Aw,isRegExp:Kw,isFunction:On,isStream:Tw,isURLSearchParams:Dw,isTypedArray:Pw,isFileList:xw,forEach:lo,merge:vd,extend:Iw,trim:Nw,stripBOM:Hw,inherits:zw,toFlatObject:jw,kindOf:Bl,kindOfTest:mr,endsWith:Uw,toArray:Bw,forEachEntry:qw,matchAll:Gw,isHTMLForm:Vw,hasOwnProperty:Sd,hasOwnProp:Sd,reduceDescriptors:Op,freezeMethods:Ww,toObjectSet:Yw,toCamelCase:Fw,noop:Xw,toFiniteNumber:Jw,findKey:kp,global:gi,isContextDefined:Dp,isSpecCompliantForm:Qw,toJSONObject:Zw,isAsyncFn:e0,isThenable:t0,setImmediate:Lp,asap:n0,isIterable:o=>o!=null&&On(o[Ul])},r0=W.toObjectSet(["age","authorization","content-length","content-type","etag","expires","from","host","if-modified-since","if-unmodified-since","last-modified","location","max-forwards","proxy-authorization","referer","retry-after","user-agent"]),a0=o=>{const e={};let n,a,i;return o&&o.split(` +`).forEach(function(c){i=c.indexOf(":"),n=c.substring(0,i).trim().toLowerCase(),a=c.substring(i+1).trim(),!(!n||e[n]&&r0[n])&&(n==="set-cookie"?e[n]?e[n].push(a):e[n]=[a]:e[n]=e[n]?e[n]+", "+a:a)}),e};function i0(o){let e=0,n=o.length;for(;ee;){const a=o.charCodeAt(n-1);if(a!==9&&a!==32)break;n-=1}return e===0&&n===o.length?o:o.slice(e,n)}const s0=new RegExp("[\\u0000-\\u0008\\u000a-\\u001f\\u007f]+","g"),o0=new RegExp("[^\\u0009\\u0020-\\u007e\\u0080-\\u00ff]+","g");function wd(o,e){return W.isArray(o)?o.map(n=>wd(n,e)):i0(String(o).replace(e,""))}const l0=o=>wd(o,s0),c0=o=>wd(o,o0);function Mp(o){const e=Object.create(null);return W.forEach(o.toJSON(),(n,a)=>{e[a]=c0(n)}),e}const $p=Symbol("internals");function co(o){return o&&String(o).trim().toLowerCase()}function Gl(o){return o===!1||o==null?o:W.isArray(o)?o.map(Gl):l0(String(o))}function u0(o){const e=Object.create(null),n=/([^\s,;=]+)\s*(?:=\s*([^,;]+))?/g;let a;for(;a=n.exec(o);)e[a[1]]=a[2];return e}const d0=o=>/^[-_a-zA-Z0-9^`|~,!#$%&'*+.]+$/.test(o.trim());function Cd(o,e,n,a,i){if(W.isFunction(a))return a.call(this,e,n);if(i&&(e=n),!!W.isString(e)){if(W.isString(a))return e.indexOf(a)!==-1;if(W.isRegExp(a))return a.test(e)}}function f0(o){return o.trim().toLowerCase().replace(/([a-z\d])(\w*)/g,(e,n,a)=>n.toUpperCase()+a)}function h0(o,e){const n=W.toCamelCase(" "+e);["get","set","has"].forEach(a=>{Object.defineProperty(o,a+n,{__proto__:null,value:function(i,l,c){return this[a].call(this,e,i,l,c)},configurable:!0})})}let Tn=class{constructor(e){e&&this.set(e)}set(e,n,a){const i=this;function l(d,f,g){const m=co(f);if(!m)throw new Error("header name must be a non-empty string");const b=W.findKey(i,m);(!b||i[b]===void 0||g===!0||g===void 0&&i[b]!==!1)&&(i[b||f]=Gl(d))}const c=(d,f)=>W.forEach(d,(g,m)=>l(g,m,f));if(W.isPlainObject(e)||e instanceof this.constructor)c(e,n);else if(W.isString(e)&&(e=e.trim())&&!d0(e))c(a0(e),n);else if(W.isObject(e)&&W.isIterable(e)){let d={},f,g;for(const m of e){if(!W.isArray(m))throw TypeError("Object iterator must return a key-value pair");d[g=m[0]]=(f=d[g])?W.isArray(f)?[...f,m[1]]:[f,m[1]]:m[1]}c(d,n)}else e!=null&&l(n,e,a);return this}get(e,n){if(e=co(e),e){const a=W.findKey(this,e);if(a){const i=this[a];if(!n)return i;if(n===!0)return u0(i);if(W.isFunction(n))return n.call(this,i,a);if(W.isRegExp(n))return n.exec(i);throw new TypeError("parser must be boolean|regexp|function")}}}has(e,n){if(e=co(e),e){const a=W.findKey(this,e);return!!(a&&this[a]!==void 0&&(!n||Cd(this,this[a],a,n)))}return!1}delete(e,n){const a=this;let i=!1;function l(c){if(c=co(c),c){const d=W.findKey(a,c);d&&(!n||Cd(a,a[d],d,n))&&(delete a[d],i=!0)}}return W.isArray(e)?e.forEach(l):l(e),i}clear(e){const n=Object.keys(this);let a=n.length,i=!1;for(;a--;){const l=n[a];(!e||Cd(this,this[l],l,e,!0))&&(delete this[l],i=!0)}return i}normalize(e){const n=this,a={};return W.forEach(this,(i,l)=>{const c=W.findKey(a,l);if(c){n[c]=Gl(i),delete n[l];return}const d=e?f0(l):String(l).trim();d!==l&&delete n[l],n[d]=Gl(i),a[d]=!0}),this}concat(...e){return this.constructor.concat(this,...e)}toJSON(e){const n=Object.create(null);return W.forEach(this,(a,i)=>{a!=null&&a!==!1&&(n[i]=e&&W.isArray(a)?a.join(", "):a)}),n}[Symbol.iterator](){return Object.entries(this.toJSON())[Symbol.iterator]()}toString(){return Object.entries(this.toJSON()).map(([e,n])=>e+": "+n).join(` +`)}getSetCookie(){return this.get("set-cookie")||[]}get[Symbol.toStringTag](){return"AxiosHeaders"}static from(e){return e instanceof this?e:new this(e)}static concat(e,...n){const a=new this(e);return n.forEach(i=>a.set(i)),a}static accessor(e){const a=(this[$p]=this[$p]={accessors:{}}).accessors,i=this.prototype;function l(c){const d=co(c);a[d]||(h0(i,c),a[d]=!0)}return W.isArray(e)?e.forEach(l):l(e),this}};Tn.accessor(["Content-Type","Content-Length","Accept","Accept-Encoding","User-Agent","Authorization"]),W.reduceDescriptors(Tn.prototype,({value:o},e)=>{let n=e[0].toUpperCase()+e.slice(1);return{get:()=>o,set(a){this[n]=a}}}),W.freezeMethods(Tn);const g0="[REDACTED ****]";function p0(o){if(W.hasOwnProp(o,"toJSON"))return!0;let e=Object.getPrototypeOf(o);for(;e&&e!==Object.prototype;){if(W.hasOwnProp(e,"toJSON"))return!0;e=Object.getPrototypeOf(e)}return!1}function m0(o,e){const n=new Set(e.map(l=>String(l).toLowerCase())),a=[],i=l=>{if(l===null||typeof l!="object"||W.isBuffer(l))return l;if(a.indexOf(l)!==-1)return;l instanceof Tn&&(l=l.toJSON()),a.push(l);let c;if(W.isArray(l))c=[],l.forEach((d,f)=>{const g=i(d);W.isUndefined(g)||(c[f]=g)});else{if(!W.isPlainObject(l)&&p0(l))return a.pop(),l;c=Object.create(null);for(const[d,f]of Object.entries(l)){const g=n.has(d.toLowerCase())?g0:i(f);W.isUndefined(g)||(c[d]=g)}}return a.pop(),c};return i(o)}let Oe=class zS extends Error{static from(e,n,a,i,l,c){const d=new zS(e.message,n||e.code,a,i,l);return d.cause=e,d.name=e.name,e.status!=null&&d.status==null&&(d.status=e.status),c&&Object.assign(d,c),d}constructor(e,n,a,i,l){super(e),Object.defineProperty(this,"message",{__proto__:null,value:e,enumerable:!0,writable:!0,configurable:!0}),this.name="AxiosError",this.isAxiosError=!0,n&&(this.code=n),a&&(this.config=a),i&&(this.request=i),l&&(this.response=l,this.status=l.status)}toJSON(){const e=this.config,n=e&&W.hasOwnProp(e,"redact")?e.redact:void 0,a=W.isArray(n)&&n.length>0?m0(e,n):W.toJSONObject(e);return{message:this.message,name:this.name,description:this.description,number:this.number,fileName:this.fileName,lineNumber:this.lineNumber,columnNumber:this.columnNumber,stack:this.stack,config:a,code:this.code,status:this.status}}};Oe.ERR_BAD_OPTION_VALUE="ERR_BAD_OPTION_VALUE",Oe.ERR_BAD_OPTION="ERR_BAD_OPTION",Oe.ECONNABORTED="ECONNABORTED",Oe.ETIMEDOUT="ETIMEDOUT",Oe.ECONNREFUSED="ECONNREFUSED",Oe.ERR_NETWORK="ERR_NETWORK",Oe.ERR_FR_TOO_MANY_REDIRECTS="ERR_FR_TOO_MANY_REDIRECTS",Oe.ERR_DEPRECATED="ERR_DEPRECATED",Oe.ERR_BAD_RESPONSE="ERR_BAD_RESPONSE",Oe.ERR_BAD_REQUEST="ERR_BAD_REQUEST",Oe.ERR_CANCELED="ERR_CANCELED",Oe.ERR_NOT_SUPPORT="ERR_NOT_SUPPORT",Oe.ERR_INVALID_URL="ERR_INVALID_URL",Oe.ERR_FORM_DATA_DEPTH_EXCEEDED="ERR_FORM_DATA_DEPTH_EXCEEDED";const y0=null;function Ed(o){return W.isPlainObject(o)||W.isArray(o)}function Np(o){return W.endsWith(o,"[]")?o.slice(0,-2):o}function _d(o,e,n){return o?o.concat(e).map(function(i,l){return i=Np(i),!n&&l?"["+i+"]":i}).join(n?".":""):e}function b0(o){return W.isArray(o)&&!o.some(Ed)}const v0=W.toFlatObject(W,{},null,function(e){return/^is[A-Z]/.test(e)});function Vl(o,e,n){if(!W.isObject(o))throw new TypeError("target must be an object");e=e||new FormData,n=W.toFlatObject(n,{metaTokens:!0,dots:!1,indexes:!1},!1,function(x,L){return!W.isUndefined(L[x])});const a=n.metaTokens,i=n.visitor||b,l=n.dots,c=n.indexes,d=n.Blob||typeof Blob<"u"&&Blob,f=n.maxDepth===void 0?100:n.maxDepth,g=d&&W.isSpecCompliantForm(e);if(!W.isFunction(i))throw new TypeError("visitor must be a function");function m(A){if(A===null)return"";if(W.isDate(A))return A.toISOString();if(W.isBoolean(A))return A.toString();if(!g&&W.isBlob(A))throw new Oe("Blob is not supported. Use a Buffer instead.");return W.isArrayBuffer(A)||W.isTypedArray(A)?g&&typeof Blob=="function"?new Blob([A]):Buffer.from(A):A}function b(A,x,L){let M=A;if(W.isReactNative(e)&&W.isReactNativeBlob(A))return e.append(_d(L,x,l),m(A)),!1;if(A&&!L&&typeof A=="object"){if(W.endsWith(x,"{}"))x=a?x:x.slice(0,-2),A=JSON.stringify(A);else if(W.isArray(A)&&b0(A)||(W.isFileList(A)||W.endsWith(x,"[]"))&&(M=W.toArray(A)))return x=Np(x),M.forEach(function(D,B){!(W.isUndefined(D)||D===null)&&e.append(c===!0?_d([x],B,l):c===null?x:x+"[]",m(D))}),!1}return Ed(A)?!0:(e.append(_d(L,x,l),m(A)),!1)}const S=[],v=Object.assign(v0,{defaultVisitor:b,convertValue:m,isVisitable:Ed});function _(A,x,L=0){if(!W.isUndefined(A)){if(L>f)throw new Oe("Object is too deeply nested ("+L+" levels). Max depth: "+f,Oe.ERR_FORM_DATA_DEPTH_EXCEEDED);if(S.indexOf(A)!==-1)throw Error("Circular reference detected in "+x.join("."));S.push(A),W.forEach(A,function(R,D){(!(W.isUndefined(R)||R===null)&&i.call(e,R,W.isString(D)?D.trim():D,x,v))===!0&&_(R,x?x.concat(D):[D],L+1)}),S.pop()}}if(!W.isObject(o))throw new TypeError("data must be an object");return _(o),e}function Ip(o){const e={"!":"%21","'":"%27","(":"%28",")":"%29","~":"%7E","%20":"+"};return encodeURIComponent(o).replace(/[!'()~]|%20/g,function(a){return e[a]})}function Ad(o,e){this._pairs=[],o&&Vl(o,this,e)}const Hp=Ad.prototype;Hp.append=function(e,n){this._pairs.push([e,n])},Hp.toString=function(e){const n=e?function(a){return e.call(this,a,Ip)}:Ip;return this._pairs.map(function(i){return n(i[0])+"="+n(i[1])},"").join("&")};function S0(o){return encodeURIComponent(o).replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,"+")}function zp(o,e,n){if(!e)return o;const a=n&&n.encode||S0,i=W.isFunction(n)?{serialize:n}:n,l=i&&i.serialize;let c;if(l?c=l(e,i):c=W.isURLSearchParams(e)?e.toString():new Ad(e,i).toString(a),c){const d=o.indexOf("#");d!==-1&&(o=o.slice(0,d)),o+=(o.indexOf("?")===-1?"?":"&")+c}return o}class jp{constructor(){this.handlers=[]}use(e,n,a){return this.handlers.push({fulfilled:e,rejected:n,synchronous:a?a.synchronous:!1,runWhen:a?a.runWhen:null}),this.handlers.length-1}eject(e){this.handlers[e]&&(this.handlers[e]=null)}clear(){this.handlers&&(this.handlers=[])}forEach(e){W.forEach(this.handlers,function(a){a!==null&&e(a)})}}const xd={silentJSONParsing:!0,forcedJSONParsing:!0,clarifyTimeoutError:!1,legacyInterceptorReqResOrdering:!0},w0={isBrowser:!0,classes:{URLSearchParams:typeof URLSearchParams<"u"?URLSearchParams:Ad,FormData:typeof FormData<"u"?FormData:null,Blob:typeof Blob<"u"?Blob:null},protocols:["http","https","file","blob","url","data"]},Td=typeof window<"u"&&typeof document<"u",Rd=typeof navigator=="object"&&navigator||void 0,C0=Td&&(!Rd||["ReactNative","NativeScript","NS"].indexOf(Rd.product)<0),E0=typeof WorkerGlobalScope<"u"&&self instanceof WorkerGlobalScope&&typeof self.importScripts=="function",_0=Td&&window.location.href||"http://localhost",Cn={...Object.freeze(Object.defineProperty({__proto__:null,hasBrowserEnv:Td,hasStandardBrowserEnv:C0,hasStandardBrowserWebWorkerEnv:E0,navigator:Rd,origin:_0},Symbol.toStringTag,{value:"Module"})),...w0};function A0(o,e){return Vl(o,new Cn.classes.URLSearchParams,{visitor:function(n,a,i,l){return Cn.isNode&&W.isBuffer(n)?(this.append(a,n.toString("base64")),!1):l.defaultVisitor.apply(this,arguments)},...e})}function x0(o){return W.matchAll(/\w+|\[(\w*)]/g,o).map(e=>e[0]==="[]"?"":e[1]||e[0])}function T0(o){const e={},n=Object.keys(o);let a;const i=n.length;let l;for(a=0;a=n.length;return c=!c&&W.isArray(i)?i.length:c,f?(W.hasOwnProp(i,c)?i[c]=W.isArray(i[c])?i[c].concat(a):[i[c],a]:i[c]=a,!d):((!W.hasOwnProp(i,c)||!W.isObject(i[c]))&&(i[c]=[]),e(n,a,i[c],l)&&W.isArray(i[c])&&(i[c]=T0(i[c])),!d)}if(W.isFormData(o)&&W.isFunction(o.entries)){const n={};return W.forEachEntry(o,(a,i)=>{e(x0(a),i,n,0)}),n}return null}const is=(o,e)=>o!=null&&W.hasOwnProp(o,e)?o[e]:void 0;function R0(o,e,n){if(W.isString(o))try{return(e||JSON.parse)(o),W.trim(o)}catch(a){if(a.name!=="SyntaxError")throw a}return(n||JSON.stringify)(o)}const uo={transitional:xd,adapter:["xhr","http","fetch"],transformRequest:[function(e,n){const a=n.getContentType()||"",i=a.indexOf("application/json")>-1,l=W.isObject(e);if(l&&W.isHTMLForm(e)&&(e=new FormData(e)),W.isFormData(e))return i?JSON.stringify(Up(e)):e;if(W.isArrayBuffer(e)||W.isBuffer(e)||W.isStream(e)||W.isFile(e)||W.isBlob(e)||W.isReadableStream(e))return e;if(W.isArrayBufferView(e))return e.buffer;if(W.isURLSearchParams(e))return n.setContentType("application/x-www-form-urlencoded;charset=utf-8",!1),e.toString();let d;if(l){const f=is(this,"formSerializer");if(a.indexOf("application/x-www-form-urlencoded")>-1)return A0(e,f).toString();if((d=W.isFileList(e))||a.indexOf("multipart/form-data")>-1){const g=is(this,"env"),m=g&&g.FormData;return Vl(d?{"files[]":e}:e,m&&new m,f)}}return l||i?(n.setContentType("application/json",!1),R0(e)):e}],transformResponse:[function(e){const n=is(this,"transitional")||uo.transitional,a=n&&n.forcedJSONParsing,i=is(this,"responseType"),l=i==="json";if(W.isResponse(e)||W.isReadableStream(e))return e;if(e&&W.isString(e)&&(a&&!i||l)){const d=!(n&&n.silentJSONParsing)&&l;try{return JSON.parse(e,is(this,"parseReviver"))}catch(f){if(d)throw f.name==="SyntaxError"?Oe.from(f,Oe.ERR_BAD_RESPONSE,this,null,is(this,"response")):f}}return e}],timeout:0,xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN",maxContentLength:-1,maxBodyLength:-1,env:{FormData:Cn.classes.FormData,Blob:Cn.classes.Blob},validateStatus:function(e){return e>=200&&e<300},headers:{common:{Accept:"application/json, text/plain, */*","Content-Type":void 0}}};W.forEach(["delete","get","head","post","put","patch","query"],o=>{uo.headers[o]={}});function kd(o,e){const n=this||uo,a=e||n,i=Tn.from(a.headers);let l=a.data;return W.forEach(o,function(d){l=d.call(n,l,i.normalize(),e?e.status:void 0)}),i.normalize(),l}function Bp(o){return!!(o&&o.__CANCEL__)}let fo=class extends Oe{constructor(e,n,a){super(e??"canceled",Oe.ERR_CANCELED,n,a),this.name="CanceledError",this.__CANCEL__=!0}};function Pp(o,e,n){const a=n.config.validateStatus;!n.status||!a||a(n.status)?o(n):e(new Oe("Request failed with status code "+n.status,n.status>=400&&n.status<500?Oe.ERR_BAD_REQUEST:Oe.ERR_BAD_RESPONSE,n.config,n.request,n))}function k0(o){const e=/^([-+\w]{1,25}):(?:\/\/)?/.exec(o);return e&&e[1]||""}function D0(o,e){o=o||10;const n=new Array(o),a=new Array(o);let i=0,l=0,c;return e=e!==void 0?e:1e3,function(f){const g=Date.now(),m=a[l];c||(c=g),n[i]=f,a[i]=g;let b=l,S=0;for(;b!==i;)S+=n[b++],b=b%o;if(i=(i+1)%o,i===l&&(l=(l+1)%o),g-c{n=m,i=null,l&&(clearTimeout(l),l=null),o(...g)};return[(...g)=>{const m=Date.now(),b=m-n;b>=a?c(g,m):(i=g,l||(l=setTimeout(()=>{l=null,c(i)},a-b)))},()=>i&&c(i)]}const Fl=(o,e,n=3)=>{let a=0;const i=D0(50,250);return O0(l=>{if(!l||typeof l.loaded!="number")return;const c=l.loaded,d=l.lengthComputable?l.total:void 0,f=d!=null?Math.min(c,d):c,g=Math.max(0,f-a),m=i(g);a=Math.max(a,f);const b={loaded:f,total:d,progress:d?f/d:void 0,bytes:g,rate:m||void 0,estimated:m&&d?(d-f)/m:void 0,event:l,lengthComputable:d!=null,[e?"download":"upload"]:!0};o(b)},n)},qp=(o,e)=>{const n=o!=null;return[a=>e[0]({lengthComputable:n,total:o,loaded:a}),e[1]]},Gp=o=>(...e)=>W.asap(()=>o(...e)),L0=Cn.hasStandardBrowserEnv?((o,e)=>n=>(n=new URL(n,Cn.origin),o.protocol===n.protocol&&o.host===n.host&&(e||o.port===n.port)))(new URL(Cn.origin),Cn.navigator&&/(msie|trident)/i.test(Cn.navigator.userAgent)):()=>!0,M0=Cn.hasStandardBrowserEnv?{write(o,e,n,a,i,l,c){if(typeof document>"u")return;const d=[`${o}=${encodeURIComponent(e)}`];W.isNumber(n)&&d.push(`expires=${new Date(n).toUTCString()}`),W.isString(a)&&d.push(`path=${a}`),W.isString(i)&&d.push(`domain=${i}`),l===!0&&d.push("secure"),W.isString(c)&&d.push(`SameSite=${c}`),document.cookie=d.join("; ")},read(o){if(typeof document>"u")return null;const e=document.cookie.split(";");for(let n=0;no instanceof Tn?{...o}:o;function pi(o,e){e=e||{};const n=Object.create(null);Object.defineProperty(n,"hasOwnProperty",{__proto__:null,value:Object.prototype.hasOwnProperty,enumerable:!1,writable:!0,configurable:!0});function a(g,m,b,S){return W.isPlainObject(g)&&W.isPlainObject(m)?W.merge.call({caseless:S},g,m):W.isPlainObject(m)?W.merge({},m):W.isArray(m)?m.slice():m}function i(g,m,b,S){if(W.isUndefined(m)){if(!W.isUndefined(g))return a(void 0,g,b,S)}else return a(g,m,b,S)}function l(g,m){if(!W.isUndefined(m))return a(void 0,m)}function c(g,m){if(W.isUndefined(m)){if(!W.isUndefined(g))return a(void 0,g)}else return a(void 0,m)}function d(g,m,b){if(W.hasOwnProp(e,b))return a(g,m);if(W.hasOwnProp(o,b))return a(void 0,g)}const f={url:l,method:l,data:l,baseURL:c,transformRequest:c,transformResponse:c,paramsSerializer:c,timeout:c,timeoutMessage:c,withCredentials:c,withXSRFToken:c,adapter:c,responseType:c,xsrfCookieName:c,xsrfHeaderName:c,onUploadProgress:c,onDownloadProgress:c,decompress:c,maxContentLength:c,maxBodyLength:c,beforeRedirect:c,transport:c,httpAgent:c,httpsAgent:c,cancelToken:c,socketPath:c,allowedSocketPaths:c,responseEncoding:c,validateStatus:d,headers:(g,m,b)=>i(Fp(g),Fp(m),b,!0)};return W.forEach(Object.keys({...o,...e}),function(m){if(m==="__proto__"||m==="constructor"||m==="prototype")return;const b=W.hasOwnProp(f,m)?f[m]:i,S=W.hasOwnProp(o,m)?o[m]:void 0,v=W.hasOwnProp(e,m)?e[m]:void 0,_=b(S,v,m);W.isUndefined(_)&&b!==d||(n[m]=_)}),n}const I0=["content-type","content-length"];function H0(o,e,n){if(n!=="content-only"){o.set(e);return}Object.entries(e).forEach(([a,i])=>{I0.includes(a.toLowerCase())&&o.set(a,i)})}const z0=o=>encodeURIComponent(o).replace(/%([0-9A-F]{2})/gi,(e,n)=>String.fromCharCode(parseInt(n,16))),Kp=o=>{const e=pi({},o),n=S=>W.hasOwnProp(e,S)?e[S]:void 0,a=n("data");let i=n("withXSRFToken");const l=n("xsrfHeaderName"),c=n("xsrfCookieName");let d=n("headers");const f=n("auth"),g=n("baseURL"),m=n("allowAbsoluteUrls"),b=n("url");if(e.headers=d=Tn.from(d),e.url=zp(Vp(g,b,m),o.params,o.paramsSerializer),f&&d.set("Authorization","Basic "+btoa((f.username||"")+":"+(f.password?z0(f.password):""))),W.isFormData(a)&&(Cn.hasStandardBrowserEnv||Cn.hasStandardBrowserWebWorkerEnv?d.setContentType(void 0):W.isFunction(a.getHeaders)&&H0(d,a.getHeaders(),n("formDataHeaderPolicy"))),Cn.hasStandardBrowserEnv&&(W.isFunction(i)&&(i=i(e)),i===!0||i==null&&L0(e.url))){const v=l&&c&&M0.read(c);v&&d.set(l,v)}return e},j0=typeof XMLHttpRequest<"u"&&function(o){return new Promise(function(n,a){const i=Kp(o);let l=i.data;const c=Tn.from(i.headers).normalize();let{responseType:d,onUploadProgress:f,onDownloadProgress:g}=i,m,b,S,v,_;function A(){v&&v(),_&&_(),i.cancelToken&&i.cancelToken.unsubscribe(m),i.signal&&i.signal.removeEventListener("abort",m)}let x=new XMLHttpRequest;x.open(i.method.toUpperCase(),i.url,!0),x.timeout=i.timeout;function L(){if(!x)return;const R=Tn.from("getAllResponseHeaders"in x&&x.getAllResponseHeaders()),B={data:!d||d==="text"||d==="json"?x.responseText:x.response,status:x.status,statusText:x.statusText,headers:R,config:o,request:x};Pp(function(P){n(P),A()},function(P){a(P),A()},B),x=null}"onloadend"in x?x.onloadend=L:x.onreadystatechange=function(){!x||x.readyState!==4||x.status===0&&!(x.responseURL&&x.responseURL.startsWith("file:"))||setTimeout(L)},x.onabort=function(){x&&(a(new Oe("Request aborted",Oe.ECONNABORTED,o,x)),A(),x=null)},x.onerror=function(D){const B=D&&D.message?D.message:"Network Error",q=new Oe(B,Oe.ERR_NETWORK,o,x);q.event=D||null,a(q),A(),x=null},x.ontimeout=function(){let D=i.timeout?"timeout of "+i.timeout+"ms exceeded":"timeout exceeded";const B=i.transitional||xd;i.timeoutErrorMessage&&(D=i.timeoutErrorMessage),a(new Oe(D,B.clarifyTimeoutError?Oe.ETIMEDOUT:Oe.ECONNABORTED,o,x)),A(),x=null},l===void 0&&c.setContentType(null),"setRequestHeader"in x&&W.forEach(Mp(c),function(D,B){x.setRequestHeader(B,D)}),W.isUndefined(i.withCredentials)||(x.withCredentials=!!i.withCredentials),d&&d!=="json"&&(x.responseType=i.responseType),g&&([S,_]=Fl(g,!0),x.addEventListener("progress",S)),f&&x.upload&&([b,v]=Fl(f),x.upload.addEventListener("progress",b),x.upload.addEventListener("loadend",v)),(i.cancelToken||i.signal)&&(m=R=>{x&&(a(!R||R.type?new fo(null,o,x):R),x.abort(),A(),x=null)},i.cancelToken&&i.cancelToken.subscribe(m),i.signal&&(i.signal.aborted?m():i.signal.addEventListener("abort",m)));const M=k0(i.url);if(M&&!Cn.protocols.includes(M)){a(new Oe("Unsupported protocol "+M+":",Oe.ERR_BAD_REQUEST,o));return}x.send(l||null)})},U0=(o,e)=>{if(o=o?o.filter(Boolean):[],!e&&!o.length)return;const n=new AbortController;let a=!1;const i=function(f){if(!a){a=!0,c();const g=f instanceof Error?f:this.reason;n.abort(g instanceof Oe?g:new fo(g instanceof Error?g.message:g))}};let l=e&&setTimeout(()=>{l=null,i(new Oe(`timeout of ${e}ms exceeded`,Oe.ETIMEDOUT))},e);const c=()=>{o&&(l&&clearTimeout(l),l=null,o.forEach(f=>{f.unsubscribe?f.unsubscribe(i):f.removeEventListener("abort",i)}),o=null)};o.forEach(f=>f.addEventListener("abort",i));const{signal:d}=n;return d.unsubscribe=()=>W.asap(c),d},B0=function*(o,e){let n=o.byteLength;if(n{const i=P0(o,e);let l=0,c,d=f=>{c||(c=!0,a&&a(f))};return new ReadableStream({async pull(f){try{const{done:g,value:m}=await i.next();if(g){d(),f.close();return}let b=m.byteLength;if(n){let S=l+=b;n(S)}f.enqueue(new Uint8Array(m))}catch(g){throw d(g),g}},cancel(f){return d(f),i.return()}},{highWaterMark:2})};function G0(o){if(!o||typeof o!="string"||!o.startsWith("data:"))return 0;const e=o.indexOf(",");if(e<0)return 0;const n=o.slice(5,e),a=o.slice(e+1);if(/;base64/i.test(n)){let c=a.length;const d=a.length;for(let v=0;v=48&&_<=57||_>=65&&_<=70||_>=97&&_<=102)&&(A>=48&&A<=57||A>=65&&A<=70||A>=97&&A<=102)&&(c-=2,v+=2)}let f=0,g=d-1;const m=v=>v>=2&&a.charCodeAt(v-2)===37&&a.charCodeAt(v-1)===51&&(a.charCodeAt(v)===68||a.charCodeAt(v)===100);g>=0&&(a.charCodeAt(g)===61?(f++,g--):m(g)&&(f++,g-=3)),f===1&&g>=0&&(a.charCodeAt(g)===61||m(g))&&f++;const S=Math.floor(c/4)*3-(f||0);return S>0?S:0}if(typeof Buffer<"u"&&typeof Buffer.byteLength=="function")return Buffer.byteLength(a,"utf8");let l=0;for(let c=0,d=a.length;c=55296&&f<=56319&&c+1=56320&&g<=57343?(l+=4,c++):l+=3}else l+=3}return l}const Dd="1.16.1",Yp=64*1024,{isFunction:Kl}=W,Xp=(o,...e)=>{try{return!!o(...e)}catch{return!1}},V0=o=>{const e=W.global!==void 0&&W.global!==null?W.global:globalThis,{ReadableStream:n,TextEncoder:a}=e;o=W.merge.call({skipUndefined:!0},{Request:e.Request,Response:e.Response},o);const{fetch:i,Request:l,Response:c}=o,d=i?Kl(i):typeof fetch=="function",f=Kl(l),g=Kl(c);if(!d)return!1;const m=d&&Kl(n),b=d&&(typeof a=="function"?(L=>M=>L.encode(M))(new a):async L=>new Uint8Array(await new l(L).arrayBuffer())),S=f&&m&&Xp(()=>{let L=!1;const M=new l(Cn.origin,{body:new n,method:"POST",get duplex(){return L=!0,"half"}}),R=M.headers.has("Content-Type");return M.body!=null&&M.body.cancel(),L&&!R}),v=g&&m&&Xp(()=>W.isReadableStream(new c("").body)),_={stream:v&&(L=>L.body)};d&&["text","arrayBuffer","blob","formData","stream"].forEach(L=>{!_[L]&&(_[L]=(M,R)=>{let D=M&&M[L];if(D)return D.call(M);throw new Oe(`Response type '${L}' is not supported`,Oe.ERR_NOT_SUPPORT,R)})});const A=async L=>{if(L==null)return 0;if(W.isBlob(L))return L.size;if(W.isSpecCompliantForm(L))return(await new l(Cn.origin,{method:"POST",body:L}).arrayBuffer()).byteLength;if(W.isArrayBufferView(L)||W.isArrayBuffer(L))return L.byteLength;if(W.isURLSearchParams(L)&&(L=L+""),W.isString(L))return(await b(L)).byteLength},x=async(L,M)=>{const R=W.toFiniteNumber(L.getContentLength());return R??A(M)};return async L=>{let{url:M,method:R,data:D,signal:B,cancelToken:q,timeout:P,onDownloadProgress:K,onUploadProgress:ge,responseType:Ee,headers:Se,withCredentials:Ue="same-origin",fetchOptions:Ve,maxContentLength:qe,maxBodyLength:St}=Kp(L);const J=W.isNumber(qe)&&qe>-1,fe=W.isNumber(St)&&St>-1;let Le=i||fetch;Ee=Ee?(Ee+"").toLowerCase():"text";let Z=U0([B,q&&q.toAbortSignal()],P),pe=null;const H=Z&&Z.unsubscribe&&(()=>{Z.unsubscribe()});let T;try{if(J&&typeof M=="string"&&M.startsWith("data:")&&G0(M)>qe)throw new Oe("maxContentLength size of "+qe+" exceeded",Oe.ERR_BAD_RESPONSE,L,pe);if(fe&&R!=="get"&&R!=="head"){const _e=await x(Se,D);if(typeof _e=="number"&&isFinite(_e)&&_e>St)throw new Oe("Request body larger than maxBodyLength limit",Oe.ERR_BAD_REQUEST,L,pe)}if(ge&&S&&R!=="get"&&R!=="head"&&(T=await x(Se,D))!==0){let _e=new l(M,{method:"POST",body:D,duplex:"half"}),Te;if(W.isFormData(D)&&(Te=_e.headers.get("content-type"))&&Se.setContentType(Te),_e.body){const[$e,ht]=qp(T,Fl(Gp(ge)));D=Wp(_e.body,Yp,$e,ht)}}W.isString(Ue)||(Ue=Ue?"include":"omit");const ce=f&&"credentials"in l.prototype;if(W.isFormData(D)){const _e=Se.getContentType();_e&&/^multipart\/form-data/i.test(_e)&&!/boundary=/i.test(_e)&&Se.delete("content-type")}Se.set("User-Agent","axios/"+Dd,!1);const de={...Ve,signal:Z,method:R.toUpperCase(),headers:Mp(Se.normalize()),body:D,duplex:"half",credentials:ce?Ue:void 0};pe=f&&new l(M,de);let ye=await(f?Le(pe,Ve):Le(M,de));if(J){const _e=W.toFiniteNumber(ye.headers.get("content-length"));if(_e!=null&&_e>qe)throw new Oe("maxContentLength size of "+qe+" exceeded",Oe.ERR_BAD_RESPONSE,L,pe)}const ie=v&&(Ee==="stream"||Ee==="response");if(v&&ye.body&&(K||J||ie&&H)){const _e={};["status","statusText","headers"].forEach(ft=>{_e[ft]=ye[ft]});const Te=W.toFiniteNumber(ye.headers.get("content-length")),[$e,ht]=K&&qp(Te,Fl(Gp(K),!0))||[];let wt=0;const kt=ft=>{if(J&&(wt=ft,wt>qe))throw new Oe("maxContentLength size of "+qe+" exceeded",Oe.ERR_BAD_RESPONSE,L,pe);$e&&$e(ft)};ye=new c(Wp(ye.body,Yp,kt,()=>{ht&&ht(),H&&H()}),_e)}Ee=Ee||"text";let xe=await _[W.findKey(_,Ee)||"text"](ye,L);if(J&&!v&&!ie){let _e;if(xe!=null&&(typeof xe.byteLength=="number"?_e=xe.byteLength:typeof xe.size=="number"?_e=xe.size:typeof xe=="string"&&(_e=typeof a=="function"?new a().encode(xe).byteLength:xe.length)),typeof _e=="number"&&_e>qe)throw new Oe("maxContentLength size of "+qe+" exceeded",Oe.ERR_BAD_RESPONSE,L,pe)}return!ie&&H&&H(),await new Promise((_e,Te)=>{Pp(_e,Te,{data:xe,headers:Tn.from(ye.headers),status:ye.status,statusText:ye.statusText,config:L,request:pe})})}catch(ce){if(H&&H(),Z&&Z.aborted&&Z.reason instanceof Oe){const de=Z.reason;throw de.config=L,pe&&(de.request=pe),ce!==de&&(de.cause=ce),de}throw ce&&ce.name==="TypeError"&&/Load failed|fetch/i.test(ce.message)?Object.assign(new Oe("Network Error",Oe.ERR_NETWORK,L,pe,ce&&ce.response),{cause:ce.cause||ce}):Oe.from(ce,ce&&ce.code,L,pe,ce&&ce.response)}}},F0=new Map,Jp=o=>{let e=o&&o.env||{};const{fetch:n,Request:a,Response:i}=e,l=[a,i,n];let c=l.length,d=c,f,g,m=F0;for(;d--;)f=l[d],g=m.get(f),g===void 0&&m.set(f,g=d?new Map:V0(e)),m=g;return g};Jp();const Od={http:y0,xhr:j0,fetch:{get:Jp}};W.forEach(Od,(o,e)=>{if(o){try{Object.defineProperty(o,"name",{__proto__:null,value:e})}catch{}Object.defineProperty(o,"adapterName",{__proto__:null,value:e})}});const Qp=o=>`- ${o}`,K0=o=>W.isFunction(o)||o===null||o===!1;function W0(o,e){o=W.isArray(o)?o:[o];const{length:n}=o;let a,i;const l={};for(let c=0;c`adapter ${f} `+(g===!1?"is not supported by the environment":"is not available in the build"));let d=n?c.length>1?`since : +`+c.map(Qp).join(` +`):" "+Qp(c[0]):"as no adapter specified";throw new Oe("There is no suitable adapter to dispatch the request "+d,"ERR_NOT_SUPPORT")}return i}const Zp={getAdapter:W0,adapters:Od};function Ld(o){if(o.cancelToken&&o.cancelToken.throwIfRequested(),o.signal&&o.signal.aborted)throw new fo(null,o)}function em(o){return Ld(o),o.headers=Tn.from(o.headers),o.data=kd.call(o,o.transformRequest),["post","put","patch"].indexOf(o.method)!==-1&&o.headers.setContentType("application/x-www-form-urlencoded",!1),Zp.getAdapter(o.adapter||uo.adapter,o)(o).then(function(a){Ld(o),o.response=a;try{a.data=kd.call(o,o.transformResponse,a)}finally{delete o.response}return a.headers=Tn.from(a.headers),a},function(a){if(!Bp(a)&&(Ld(o),a&&a.response)){o.response=a.response;try{a.response.data=kd.call(o,o.transformResponse,a.response)}finally{delete o.response}a.response.headers=Tn.from(a.response.headers)}return Promise.reject(a)})}const Wl={};["object","boolean","number","function","string","symbol"].forEach((o,e)=>{Wl[o]=function(a){return typeof a===o||"a"+(e<1?"n ":" ")+o}});const tm={};Wl.transitional=function(e,n,a){function i(l,c){return"[Axios v"+Dd+"] Transitional option '"+l+"'"+c+(a?". "+a:"")}return(l,c,d)=>{if(e===!1)throw new Oe(i(c," has been removed"+(n?" in "+n:"")),Oe.ERR_DEPRECATED);return n&&!tm[c]&&(tm[c]=!0,console.warn(i(c," has been deprecated since v"+n+" and will be removed in the near future"))),e?e(l,c,d):!0}},Wl.spelling=function(e){return(n,a)=>(console.warn(`${a} is likely a misspelling of ${e}`),!0)};function Y0(o,e,n){if(typeof o!="object")throw new Oe("options must be an object",Oe.ERR_BAD_OPTION_VALUE);const a=Object.keys(o);let i=a.length;for(;i-- >0;){const l=a[i],c=Object.prototype.hasOwnProperty.call(e,l)?e[l]:void 0;if(c){const d=o[l],f=d===void 0||c(d,l,o);if(f!==!0)throw new Oe("option "+l+" must be "+f,Oe.ERR_BAD_OPTION_VALUE);continue}if(n!==!0)throw new Oe("Unknown option "+l,Oe.ERR_BAD_OPTION)}}const Yl={assertOptions:Y0,validators:Wl},nr=Yl.validators;let mi=class{constructor(e){this.defaults=e||{},this.interceptors={request:new jp,response:new jp}}async request(e,n){try{return await this._request(e,n)}catch(a){if(a instanceof Error){let i={};Error.captureStackTrace?Error.captureStackTrace(i):i=new Error;const l=(()=>{if(!i.stack)return"";const c=i.stack.indexOf(` `);return c===-1?"":i.stack.slice(c+1)})();try{if(!a.stack)a.stack=l;else if(l){const c=l.indexOf(` `),d=c===-1?-1:l.indexOf(` `,c+1),f=d===-1?"":l.slice(d+1);String(a.stack).endsWith(f)||(a.stack+=` -`+l)}}catch{}}throw a}}_request(e,n){typeof e=="string"?(n=n||{},n.url=e):n=e||{},n=mi(this.defaults,n);const{transitional:a,paramsSerializer:i,headers:l}=n;a!==void 0&&Ql.assertOptions(a,{silentJSONParsing:xn.transitional(xn.boolean),forcedJSONParsing:xn.transitional(xn.boolean),clarifyTimeoutError:xn.transitional(xn.boolean),legacyInterceptorReqResOrdering:xn.transitional(xn.boolean),advertiseZstdAcceptEncoding:xn.transitional(xn.boolean),validateStatusUndefinedResolves:xn.transitional(xn.boolean)},!1),i!=null&&(F.isFunction(i)?n.paramsSerializer={serialize:i}:Ql.assertOptions(i,{encode:xn.function,serialize:xn.function},!0)),n.allowAbsoluteUrls!==void 0||(this.defaults.allowAbsoluteUrls!==void 0?n.allowAbsoluteUrls=this.defaults.allowAbsoluteUrls:n.allowAbsoluteUrls=!0),Ql.assertOptions(n,{baseUrl:xn.spelling("baseURL"),withXsrfToken:xn.spelling("withXSRFToken")},!0),n.method=(n.method||this.defaults.method||"get").toLowerCase();let c=l&&F.merge(l.common,l[n.method]);l&&F.forEach(["delete","get","head","post","put","patch","query","common"],_=>{delete l[_]}),n.headers=An.concat(c,l);const d=[];let f=!0;this.interceptors.request.forEach(function(A){if(typeof A.runWhen=="function"&&A.runWhen(n)===!1)return;f=f&&A.synchronous;const x=n.transitional||Dd;x&&x.legacyInterceptorReqResOrdering?d.unshift(A.fulfilled,A.rejected):d.push(A.fulfilled,A.rejected)});const g=[];this.interceptors.response.forEach(function(A){g.push(A.fulfilled,A.rejected)});let m,y=0,S;if(!f){const _=[cm.bind(this),void 0];for(_.unshift(...d),_.push(...g),S=_.length,m=Promise.resolve(n);y{if(!a._listeners)return;let l=a._listeners.length;for(;l-- >0;)a._listeners[l](i);a._listeners=null}),this.promise.then=i=>{let l;const c=new Promise(d=>{a.subscribe(d),l=d}).then(i);return c.cancel=function(){a.unsubscribe(l)},c},e(function(l,c,d){a.reason||(a.reason=new po(l,c,d),n(a.reason))})}throwIfRequested(){if(this.reason)throw this.reason}subscribe(e){if(this.reason){e(this.reason);return}this._listeners?this._listeners.push(e):this._listeners=[e]}unsubscribe(e){if(!this._listeners)return;const n=this._listeners.indexOf(e);n!==-1&&this._listeners.splice(n,1)}toAbortSignal(){const e=new AbortController,n=a=>{e.abort(a)};return this.subscribe(n),e.signal.unsubscribe=()=>this.unsubscribe(n),e.signal}static source(){let e;return{token:new KS(function(i){e=i}),cancel:e}}};function hC(o){return function(n){return o.apply(null,n)}}function gC(o){return F.isObject(o)&&o.isAxiosError===!0}const Hd={Continue:100,SwitchingProtocols:101,Processing:102,EarlyHints:103,Ok:200,Created:201,Accepted:202,NonAuthoritativeInformation:203,NoContent:204,ResetContent:205,PartialContent:206,MultiStatus:207,AlreadyReported:208,ImUsed:226,MultipleChoices:300,MovedPermanently:301,Found:302,SeeOther:303,NotModified:304,UseProxy:305,Unused:306,TemporaryRedirect:307,PermanentRedirect:308,BadRequest:400,Unauthorized:401,PaymentRequired:402,Forbidden:403,NotFound:404,MethodNotAllowed:405,NotAcceptable:406,ProxyAuthenticationRequired:407,RequestTimeout:408,Conflict:409,Gone:410,LengthRequired:411,PreconditionFailed:412,PayloadTooLarge:413,UriTooLong:414,UnsupportedMediaType:415,RangeNotSatisfiable:416,ExpectationFailed:417,ImATeapot:418,MisdirectedRequest:421,UnprocessableEntity:422,Locked:423,FailedDependency:424,TooEarly:425,UpgradeRequired:426,PreconditionRequired:428,TooManyRequests:429,RequestHeaderFieldsTooLarge:431,UnavailableForLegalReasons:451,InternalServerError:500,NotImplemented:501,BadGateway:502,ServiceUnavailable:503,GatewayTimeout:504,HttpVersionNotSupported:505,VariantAlsoNegotiates:506,InsufficientStorage:507,LoopDetected:508,NotExtended:510,NetworkAuthenticationRequired:511,WebServerIsDown:521,ConnectionTimedOut:522,OriginIsUnreachable:523,TimeoutOccurred:524,SslHandshakeFailed:525,InvalidSslCertificate:526};Object.entries(Hd).forEach(([o,e])=>{Hd[e]=o});function dm(o){const e=new yi(o),n=xp(yi.prototype.request,e);return F.extend(n,yi.prototype,e,{allOwnKeys:!0}),F.extend(n,e,null,{allOwnKeys:!0}),n.create=function(i){return dm(mi(o,i))},n}const Yt=dm(go);Yt.Axios=yi,Yt.CanceledError=po,Yt.CancelToken=fC,Yt.isCancel=Wp,Yt.VERSION=$d,Yt.toFormData=Kl,Yt.AxiosError=Ce,Yt.Cancel=Yt.CanceledError,Yt.all=function(e){return Promise.all(e)},Yt.spread=hC,Yt.isAxiosError=gC,Yt.mergeConfig=mi,Yt.AxiosHeaders=An,Yt.formToJSON=o=>Kp(F.isHTMLForm(o)?new FormData(o):o),Yt.getAdapter=lm.getAdapter,Yt.HttpStatusCode=Hd,Yt.default=Yt;const{Axios:OT,AxiosError:LT,CanceledError:MT,isCancel:$T,CancelToken:NT,VERSION:IT,all:HT,Cancel:jT,isAxiosError:zT,spread:UT,toFormData:PT,AxiosHeaders:BT,HttpStatusCode:qT,formToJSON:GT,getAdapter:VT,mergeConfig:FT,create:KT}=Yt,jd="g7.identity.redirectStash",Zl=ht("IdentityGuardInterceptor");class Et{static setLauncher(e){this.launcher=e}static hasLauncher(){return this.launcher!==null}static markDomainNoticeShown(){this.domainNoticeShown=!0}static consumeDomainNoticeShown(){const e=this.domainNoticeShown;return this.domainNoticeShown=!1,e}static resolveDeferred(e){const n=this.deferredResolver;if(!n){Zl.warn("IdentityGuardInterceptor.resolveDeferred 호출됐지만 대기 중인 launcher 가 없습니다. 모달이 launcher 외부에서 열렸는지 확인하세요.");return}this.deferredResolver=null,n(e)}static createDeferred(){if(this.deferredResolver){const e=this.deferredResolver;this.deferredResolver=null,e({status:"cancelled"})}return new Promise(e=>{this.deferredResolver=e})}static isIdentityRequired(e,n){return e===428&&typeof n=="object"&&n!==null&&n.error_code==="identity_verification_required"}static async handle(e,n,a){this.domainNoticeShown=!1;const i=this.launcher??mC,c=!!(a&&(a.email||a.phone))?{...e.verification,target:a}:e.verification,d=await i(c);if(d.status!=="verified")return null;const f=c.return_request;if(!f)return null;const g=pC(f.url,d.token);return fetch(g,{method:f.method,headers:n?.headers??{Accept:"application/json","Content-Type":"application/json"},body:n?.body,credentials:n?.credentials??"same-origin"})}static redirectExternally(e){const n=e.redirect_url;if(!n)return Zl.error("redirectExternally 호출 시 verification.redirect_url 이 없습니다. payload:",e),Promise.resolve({status:"failed",failureCode:"MISSING_REDIRECT_URL",reason:"verification.redirect_url is required for external_redirect flow"});if(typeof window<"u"){const a={return_url:window.location.href,payload:e,stashed_at:Date.now()};try{window.sessionStorage?.setItem(jd,JSON.stringify(a))}catch(i){Zl.warn("sessionStorage 접근 실패 — stash 없이 redirect 합니다.",i)}window.location.href=n}return new Promise(()=>{})}static reset(){if(this.deferredResolver){const e=this.deferredResolver;this.deferredResolver=null;try{e({status:"cancelled"})}catch{}}this.launcher=null,this.domainNoticeShown=!1}}$(Et,"launcher",null),$(Et,"deferredResolver",null),$(Et,"domainNoticeShown",!1);function pC(o,e){if(!e)return o;try{const n=typeof window<"u"?window.location.origin:"http://localhost",a=new URL(o,n);return a.searchParams.set("verification_token",e),/^https?:\/\//i.test(o)?a.toString():`${a.pathname}${a.search}${a.hash}`}catch{const n=o.includes("?")?"&":"?";return`${o}${n}verification_token=${encodeURIComponent(e)}`}}const mC=async o=>{if(typeof window>"u")return{status:"cancelled"};if(o.render_hint==="external_redirect"||o.redirect_url)return Et.redirectExternally(o);const e=window.G7Core;if(!e?.dispatch)return console.error("[IdentityGuardInterceptor] defaultLauncher: G7Core 가 초기화되지 않아 본인인증 흐름을 시작할 수 없습니다."),{status:"failed",failureCode:"G7_NOT_READY"};try{await e.dispatch({handler:"toast",params:{message:"본인 확인이 필요합니다.",variant:"warning"}})}catch{}const n=window.location.href,a={return_url:n,payload:o,stashed_at:Date.now()};try{window.sessionStorage?.setItem(jd,JSON.stringify(a))}catch{}const i=`/identity/challenge?return=${encodeURIComponent(n)}`;try{await e.dispatch({handler:"navigate",params:{path:i}})}catch(l){Zl.warn("navigate 실패 — window.location 으로 폴백",l),window.location.href=i}return new Promise(()=>{})},yC=ht("ApiClient");function zd(){return typeof window<"u"&&window.__G7_DEVTOOLS__?window.__G7_DEVTOOLS__:null}class bC{constructor(e={}){$(this,"client");$(this,"config");$(this,"TOKEN_KEY","auth_token");this.config={baseURL:e.baseURL||"/api",timeout:e.timeout||3e4,onTokenExpired:e.onTokenExpired,onUnauthorized:e.onUnauthorized,onError:e.onError},this.client=Yt.create({baseURL:this.config.baseURL,timeout:this.config.timeout,headers:{"Content-Type":"application/json",Accept:"application/json"},paramsSerializer:{serialize:n=>{const a=[];for(const[i,l]of Object.entries(n))if(l!=null)if(Array.isArray(l)){const c=i.endsWith("[]")?i:`${i}[]`;for(const d of l)a.push(`${encodeURIComponent(c)}=${encodeURIComponent(String(d))}`)}else a.push(`${encodeURIComponent(i)}=${encodeURIComponent(String(l))}`);return a.join("&")}}}),this.setupInterceptors()}setToken(e){typeof window<"u"&&localStorage.setItem(this.TOKEN_KEY,e)}getToken(){return typeof window<"u"?localStorage.getItem(this.TOKEN_KEY):null}removeToken(){typeof window<"u"&&localStorage.removeItem(this.TOKEN_KEY)}setupInterceptors(){this.client.interceptors.request.use(e=>{e.url?.startsWith("/api")&&e.baseURL==="/api"&&(e.baseURL="");const n=this.getToken();if(n&&e.headers&&(e.headers.Authorization=`Bearer ${n}`),typeof window<"u"&&e.headers){const i=localStorage.getItem("g7_locale");i&&(e.headers["Accept-Language"]=i)}const a=zd();if(a?.isEnabled()){const i=this.buildFullUrl(e),l=(e.method||"GET").toUpperCase(),c=a.trackRequest(i,l,{requestBody:e.data});e._devToolsRequestId=c}return e},e=>Promise.reject(e)),this.client.interceptors.response.use(e=>{const n=e.config,a=n._devToolsRequestId;if(a){const i=zd();i?.isEnabled()&&i.completeRequest(a,e.status,e.data),n._devToolsRequestId=null}return e},async e=>{const n=e.config,a=n?.url||"",l=["/auth/","/layouts/"].some(c=>a.includes(c));if(e.response?.status===401&&!n?._retry&&!l){if(n._retry=!0,await br.getInstance().refreshToken()){const f=this.getToken();return f&&(n.headers.Authorization=`Bearer ${f}`),n._devToolsRequestId=null,this.client(n)}return this.completeDevToolsRequest(n,e),this.removeToken(),this.config.onUnauthorized&&this.config.onUnauthorized(),Promise.reject(e)}if(e.response?.status===401&&l)return this.completeDevToolsRequest(n,e),Promise.reject(e);if(Et.isIdentityRequired(e.response?.status,e.response?.data)){this.completeDevToolsRequest(n,e);const c={},d=n?.headers??{};for(const[S,v]of Object.entries(d))typeof v=="string"&&(c[S]=v);let f;n?.data!==void 0&&n?.data!==null&&(f=typeof n.data=="string"?n.data:JSON.stringify(n.data),c["Content-Type"]=c["Content-Type"]??"application/json");const g=n?.identity_target,m=await Et.handle(e.response.data,{headers:c,body:f,credentials:"same-origin"},g);if(!m)return Promise.reject(e);let y=null;try{y=await m.json()}catch{y=null}return m.ok?{...e.response,status:m.status,data:y}:Promise.reject(Object.assign(new Error("replay failed"),{response:{status:m.status,data:y}}))}return e.response?.status===403?(this.completeDevToolsRequest(n,e),this.callOnError(e),Promise.reject(e)):(this.completeDevToolsRequest(n,e),e.response&&this.callOnError(e),Promise.reject(e))})}async get(e,n){return(await this.client.get(e,n)).data}async post(e,n,a){return(await this.client.post(e,n,a)).data}async put(e,n,a){return(await this.client.put(e,n,a)).data}async patch(e,n,a){return(await this.client.patch(e,n,a)).data}async delete(e,n){return(await this.client.delete(e,n)).data}getInstance(){return this.client}setOnUnauthorized(e){this.config.onUnauthorized=e}setOnError(e){this.config.onError=e}completeDevToolsRequest(e,n){if(!e?._devToolsRequestId)return;const a=zd();if(!a?.isEnabled())return;const i=e._devToolsRequestId;e._devToolsRequestId=null,n.response?a.completeRequest(i,n.response.status,n.response.data):n.request?a.failRequest(i,n.message||"Network error"):a.failRequest(i,n.message||"Request error")}buildFullUrl(e){let n=e.url||"";if(e.baseURL&&!n.startsWith("http")&&(n=`${e.baseURL}${n.startsWith("/")?"":"/"}${n}`),e.params&&typeof e.params=="object"){const a=e.params,i=[];for(const[l,c]of Object.entries(a))if(c!=null)if(Array.isArray(c)){const d=l.endsWith("[]")?l:`${l}[]`;for(const f of c)i.push(`${encodeURIComponent(d)}=${encodeURIComponent(String(f))}`)}else i.push(`${encodeURIComponent(l)}=${encodeURIComponent(String(c))}`);i.length>0&&(n+=(n.includes("?")?"&":"?")+i.join("&"))}return n}callOnError(e){if(!this.config.onError)return;const n={status:e.response?.status||0,message:e.response?.data?.message||e.message||"Unknown error",data:e.response?.data,statusText:e.response?.statusText};try{this.config.onError(n)}catch(a){yC.error("Error in onError handler:",a)}}}let Ud=null;function Hr(){return Ud||(Ud=new bC),Ud}const mo=ht("AuthManager");function cs(o,e,n,a){try{window.G7Core?.devTools?.trackAuthEvent?.(o,e,n,a)}catch{}}const ec={admin:{type:"admin",loginPath:"/admin/login",defaultPath:"/admin",userEndpoint:"/admin/auth/user",loginEndpoint:"/auth/admin/login",logoutEndpoint:"/admin/auth/logout",refreshEndpoint:"/admin/auth/refresh"},user:{type:"user",loginPath:"/login",defaultPath:"/",userEndpoint:"/auth/user",loginEndpoint:"/auth/login",logoutEndpoint:"/auth/logout",refreshEndpoint:"/auth/refresh"}},Ar=class Ar{constructor(){$(this,"state");$(this,"config");$(this,"isRefreshing",!1);$(this,"refreshPromise",null);$(this,"eventHandlers",new Map);this.state={isAuthenticated:!1,user:null,type:null},this.config=new Map,this.config.set("admin",ec.admin),this.config.set("user",ec.user)}static getInstance(){return Ar.instance||(Ar.instance=new Ar),Ar.instance}on(e,n){this.eventHandlers.has(e)||this.eventHandlers.set(e,[]),this.eventHandlers.get(e).push(n)}emit(e,...n){const a=this.eventHandlers.get(e);a&&a.forEach(i=>i(...n))}isAuthenticated(){return this.state.isAuthenticated}getUser(){return this.state.user}getAuthType(){return this.state.type}async checkAuth(e){const n=Hr();if(!n.getToken())return this.clearState(),!1;const i=this.config.get(e);if(!i)return mo.error(`Unknown auth type: ${e}`),!1;try{const l=await n.get(i.userEndpoint);return l.success&&l.data?(this.state={isAuthenticated:!0,user:l.data,type:e},this.emit("authStateChange",this.state),!0):(this.clearState(),!1)}catch(l){return l.response?.status===401&&await this.refreshToken()?this.checkAuth(e):(this.clearState(),!1)}}async preloadAuth(e){try{return await this.checkAuth(e)}catch(n){return mo.warn(`Preload auth failed for type ${e}:`,n),!1}}async login(e,n,a){const i=Hr();if(!this.config.get(e))throw new Error(`Unknown auth type: ${e}`);const c=e==="admin"?ec.admin.loginEndpoint:ec.user.loginEndpoint;try{const d=a?.headers?{headers:a.headers}:void 0,f=await i.post(c,n,d);if(f.success&&f.data){i.setToken(f.data.token);const g=f.data.user.language,m=localStorage.getItem(Ar.LOCALE_STORAGE_KEY),y=g&&g!==m;if(g)try{localStorage.setItem(Ar.LOCALE_STORAGE_KEY,g)}catch(S){mo.warn("Failed to save user language to localStorage:",S)}return this.state={isAuthenticated:!0,user:f.data.user,type:e},this.emit("login",this.state),this.emit("authStateChange",this.state),cs("login",!0,void 0,{userId:f.data.user.uuid,email:f.data.user.email,type:e}),y&&window.__templateApp&&window.__templateApp.changeLocale(g),f.data.user}throw new Error("Login failed")}catch(d){this.clearState();const f=d.response?.data?.message,g=new Error(f||d.message||"Login failed");throw g.response=d.response,g.status=d.response?.status,cs("login",!1,g.message,{type:e,status:d.response?.status}),g}}async logout(){const e=Hr(),n=this.state.type?this.config.get(this.state.type):null;try{n&&await e.post(n.logoutEndpoint)}catch(a){mo.warn("Logout API call failed:",a)}finally{e.removeToken();const a={...this.state};if(this.clearState(),this.emit("logout",a),this.emit("authStateChange",this.state),cs("logout",!0,void 0,{previousType:a.type}),n&&a.type){const i=window.location.pathname+window.location.search;window.location.href=this.getLoginRedirectUrl(a.type,i)}}}async refreshToken(){if(this.isRefreshing&&this.refreshPromise)return this.refreshPromise;this.isRefreshing=!0,this.refreshPromise=this.doRefreshToken();try{return await this.refreshPromise}finally{this.isRefreshing=!1,this.refreshPromise=null}}async doRefreshToken(){const e=this.state.type;if(!e)return!1;const n=this.config.get(e);if(!n)return!1;const a=Hr();try{const i=await a.post(n.refreshEndpoint);return i.success&&i.data?.token?(a.setToken(i.data.token),this.emit("tokenRefreshed"),cs("token-refresh",!0,void 0,{type:e}),!0):(cs("token-refresh",!1,"No token in response",{type:e}),!1)}catch(i){return mo.error("Token refresh failed:",i),cs("token-refresh",!1,i instanceof Error?i.message:"Unknown error",{type:e}),!1}}getLoginRedirectUrl(e,n,a){const i=this.config.get(e);if(!i)return"/login";const l=encodeURIComponent(n);let c=`${i.loginPath}?redirect=${l}`;return a&&(c+=`&reason=${encodeURIComponent(a)}`),c}updateConfig(e,n){if(n.loginPath!==void 0){const i=n.loginPath;if(!i.startsWith("/")||i.startsWith("//"))throw new Error(`AuthManager.updateConfig: loginPath must be a same-origin path starting with '/' (got: ${i})`)}const a=this.config.get(e);a&&this.config.set(e,{...a,...n})}getRedirectUrl(e){const a=this.config.get(e)?.defaultPath||"/",l=new URLSearchParams(window.location.search).get("redirect");if(l)try{const c=decodeURIComponent(l);if(c.startsWith("/"))return c}catch{}return a}getConfig(e){return this.config.get(e)}clearState(){this.state={isAuthenticated:!1,user:null,type:null}}static resetInstance(){Ar.instance=null}};$(Ar,"instance"),$(Ar,"LOCALE_STORAGE_KEY","g7_locale");let br=Ar;const fm={401:{handler:"navigate",params:{path:"{{auth.loginPath}}"}},403:{handler:"toast",params:{type:"error",message:"{{error.message}}"}},404:{handler:"toast",params:{type:"error",message:"{{error.message}}"}},422:{handler:"toast",params:{type:"error",message:"{{error.message}}"}},default:{handler:"toast",params:{type:"error",message:"{{error.message}}"}}},Qr={findHandler(o,e){if(o){if(o[e])return o[e];if(o[String(e)])return o[String(e)];if(o.default)return o.default}},normalizeOnError(o){return o?Array.isArray(o)?o:[o]:[]},injectErrorCode(o,e,n){if(o.handler!=="showErrorPage"||o.params?.errorCode!==void 0)return o;const a=typeof e=="string"?parseInt(e,10):e,i=isNaN(a)?n:a;return i===void 0?o:{...o,params:{...o.params,errorCode:i}}}},vr=ht("ErrorHandlingResolver"),Ma=class Ma{constructor(){$(this,"templateErrorHandling",null);$(this,"layoutErrorHandling",null);$(this,"executeAction",null)}static getInstance(){return Ma.instance||(Ma.instance=new Ma),Ma.instance}static resetInstance(){Ma.instance=null}setTemplateConfig(e){this.templateErrorHandling=e,vr.log("Template config set:",e)}setLayoutConfig(e){this.layoutErrorHandling=e,vr.log("Layout config set:",e)}setActionExecutor(e){this.executeAction=e}resolve(e,n={}){const{errorHandling:a,onError:i}=n;vr.log("Resolving error:",e,{hasActionErrorHandling:!!a,hasActionOnError:!!i,hasLayoutConfig:!!this.layoutErrorHandling,hasTemplateConfig:!!this.templateErrorHandling});let l=Qr.findHandler(a,e);if(l){const c=this.getMatchedKey(a,e);return{handler:Qr.injectErrorCode(l,c,e),level:"action",matchedKey:c}}if(i){const c=Qr.normalizeOnError(i);if(c.length>0)return{handler:c.length===1?c[0]:{handler:"sequence",actions:c},level:"action",matchedKey:"onError"}}if(l=Qr.findHandler(this.layoutErrorHandling||void 0,e),l){const c=this.getMatchedKey(this.layoutErrorHandling,e);return{handler:Qr.injectErrorCode(l,c,e),level:"layout",matchedKey:c}}if(l=Qr.findHandler(this.templateErrorHandling||void 0,e),l){const c=this.getMatchedKey(this.templateErrorHandling,e);return{handler:Qr.injectErrorCode(l,c,e),level:"template",matchedKey:c}}if(l=Qr.findHandler(fm,e),l){const c=this.getMatchedKey(fm,e);return{handler:Qr.injectErrorCode(l,c,e),level:"system",matchedKey:c}}return{handler:null,level:null,matchedKey:null}}async execute(e,n){if(!this.executeAction){vr.error("Action executor is not set");return}vr.log("Executing handler:",e.handler,{errorContext:n});try{return await this.executeAction(e,{error:n})}catch(a){throw vr.error("Failed to execute handler:",a),a}}async resolveAndExecute(e,n,a={}){const i=this.resolve(e,a);if(vr.log("Resolve result:",{errorCode:e,handler:i.handler?.handler,level:i.level,matchedKey:i.matchedKey}),!i.handler)return vr.warn("No handler found for error:",e),{handled:!1};try{return{handled:!0,result:await this.execute(i.handler,n)}}catch(l){return vr.error("Handler execution failed:",l),{handled:!1}}}getMatchedKey(e,n){return e[n]?n:e[String(n)]?String(n):"default"}clearLayoutConfig(){this.layoutErrorHandling=null,vr.log("Layout config cleared")}clearAllConfig(){this.templateErrorHandling=null,this.layoutErrorHandling=null,vr.log("All config cleared")}getConfigStatus(){return{hasTemplate:this.templateErrorHandling!==null,hasLayout:this.layoutErrorHandling!==null,hasExecutor:this.executeAction!==null}}};$(Ma,"instance",null);let Pd=Ma;function Sr(){return Pd.getInstance()}var Bd={exports:{}},yo={};var hm;function vC(){if(hm)return yo;hm=1;var o=Symbol.for("react.transitional.element"),e=Symbol.for("react.fragment");function n(a,i,l){var c=null;if(l!==void 0&&(c=""+l),i.key!==void 0&&(c=""+i.key),"key"in i){l={};for(var d in i)d!=="key"&&(l[d]=i[d])}else l=i;return i=l.ref,{$$typeof:o,type:a,key:c,ref:i!==void 0?i:null,props:l}}return yo.Fragment=e,yo.jsx=n,yo.jsxs=n,yo}var gm;function SC(){return gm||(gm=1,Bd.exports=vC()),Bd.exports}var gt=SC();const wC=Xr({__proto__:null,default:Jr(gt)},[gt]),qd=ht("ParentContextProvider"),pm=H.createContext(null);let tc=null;function Gd(){tc?(qd.log("[triggerModalParentUpdate] 모달 부모 컨텍스트 업데이트 트리거"),tc()):qd.warn("[triggerModalParentUpdate] Provider가 아직 마운트되지 않음")}const mm=({children:o})=>{const[e,n]=H.useState(0),a=H.useRef(!0),i=H.useCallback(()=>{const c=window.__g7LayoutContextStack||[];return c[c.length-1]?.dataContext},[]);H.useEffect(()=>(a.current=!0,tc=()=>{a.current&&(n(c=>c+1),qd.log("[ParentContextProvider] 버전 업데이트됨"))},()=>{a.current=!1,tc=null}),[]);const l={version:e,getParentDataContext:i};return gt.jsx(pm.Provider,{value:l,children:o})};function CC(){return H.useContext(pm)}const j=ht("ActionDispatcher");function EC(o,e,n,a){return n||(ip(o)||dd(o)?"$t:core.errors.network_request_failed":a||`Failed to execute action: ${e}`)}const ym=new Set(["navigate","navigateBack","navigateForward","replaceUrl","refresh","logout"]),_C=new Set(["redirect"]);function Fn(){try{return window.G7Core?.devTools}catch{return}}class Bt extends Error{constructor(n,a,i){super(n);$(this,"action");$(this,"originalError");$(this,"unknownHandler",!1);this.action=a,this.originalError=i,this.name="ActionError"}}const bs=class bs{constructor(e={},n,a){$(this,"bindingEngine");$(this,"translationEngine");$(this,"translationContext");$(this,"customHandlers",new Map);$(this,"defaultContext");$(this,"globalStateUpdater");$(this,"errorHandlingSetup",!1);$(this,"debounceTimers",new Map);$(this,"pendingDebounceFlushers",new Map);$(this,"debounceAccumulatedValues",new Map);$(this,"globalHeaders",[]);$(this,"namedActions",{});$(this,"previewMode",!1);$(this,"intervals",new Map);this.bindingEngine=new Dn,this.translationEngine=n,this.translationContext=a,this.defaultContext=e,this.registerDefaultHandlers(),this.setupErrorHandling()}setGlobalHeaders(e){this.globalHeaders=e||[]}setNamedActions(e){this.namedActions=e||{};const n=Fn();n?.isEnabled?.()&&n.setNamedActionDefinitions?.(this.namedActions),j.log("[setNamedActions] registered:",Object.keys(this.namedActions))}getNamedActions(){return this.namedActions}setPreviewMode(e){this.previewMode=e,j.log("Preview mode:",e?"enabled":"disabled")}isPreviewMode(){return this.previewMode}static getPreviewSuppressedHandlers(){return ym}static getPreviewSuppressedLayoutFeatures(){return _C}resolveActionRef(e){if(!e.actionRef)return e;const n=this.namedActions[e.actionRef];if(!n)return j.warn(`[resolveActionRef] named action not found: "${e.actionRef}"`),e;const a=Fn();a?.isEnabled?.()&&a.trackNamedActionRef?.({actionRefName:e.actionRef,resolvedHandler:n.handler,timestamp:Date.now()});const{actionRef:i,type:l,key:c,event:d,...f}=e,g={...n,...Object.fromEntries(Object.entries(f).filter(([,m])=>m!==void 0)),type:l??n.type};return c!==void 0&&(g.key=c),d!==void 0&&(g.event=d),g}matchesPattern(e,n){if(n==="*")return!0;const a=n.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*");return new RegExp(`^${a}$`).test(e)}getMatchingGlobalHeaders(e,n){const a={};for(const i of this.globalHeaders)this.matchesPattern(e,i.pattern)&&Object.entries(i.headers).forEach(([l,c])=>{const d=rs(c,n,{skipCache:!0});d!=null&&d!==""&&(a[l]=String(d))});return a}setupErrorHandling(){if(this.errorHandlingSetup)return;Sr().setActionExecutor(async(n,a)=>{const i={type:"click",handler:n.handler,target:n.target,params:n.params,actions:n.actions},l={data:{...this.defaultContext.data,error:a.error}};return await this.dispatchAction(i,l)}),this.errorHandlingSetup=!0}registerDefaultHandlers(){this.registerHandler("refetchDataSource",async(n,a)=>{const i=n.params?.dataSourceId,l=n.params?.sync;if(!i){j.warn("refetchDataSource: dataSourceId is required");return}if(typeof window<"u"&&window.G7Core?.dataSource?.refetch){const c=window.G7Core?.state?.get?.()||{},d={...a.state?._global,...c},f=window.G7Core?.state?.getLocal?.()||{},g=a.data?._local||a.state||{},m={...f,...g},y=a.isolatedContext?.state;j.log("[refetchDataSource] localStateOverride:",m),await window.G7Core.dataSource.refetch(i,{...l?{sync:!0}:{},...d?{globalStateOverride:d}:{},...m?{localStateOverride:m}:{},...y?{isolatedStateOverride:y}:{}})}else j.warn("refetchDataSource: G7Core.dataSource.refetch is not available")}),this.registerHandler("appendDataSource",async(n,a)=>{const{dataSourceId:i,dataPath:l,newData:c}=n.params||{};if(!i){j.warn("appendDataSource: dataSourceId is required");return}if(c===void 0){j.warn("appendDataSource: newData is required");return}if(typeof window<"u"&&window.G7Core?.dataSource?.updateData){if(!Array.isArray(c)){j.warn("appendDataSource: newData must be an array, got:",typeof c,c);return}await window.G7Core.dataSource.updateData(i,l||null,c,"append")}else j.warn("appendDataSource: G7Core.dataSource.updateData is not available")}),this.registerHandler("updateDataSource",async(n,a)=>{const{dataSourceId:i,data:l,merge:c=!1}=n.params||{};if(!i){j.warn("updateDataSource: dataSourceId is required");return}if(l===void 0){j.warn("updateDataSource: data is required");return}typeof window<"u"&&window.G7Core?.dataSource?.set?(j.log(`[updateDataSource] Updating dataSource '${i}' with:`,l),window.G7Core.dataSource.set(i,l,{merge:c})):j.warn("updateDataSource: G7Core.dataSource.set is not available")}),this.registerHandler("scrollIntoView",async(n,a)=>{const{selector:i,behavior:l="smooth",block:c="nearest",inline:d="nearest",waitForElement:f=!1,timeout:g=2e3,delay:m=0,retryCount:y=0,retryInterval:S=50,scrollContainer:v}=n.params||{};if(!i){j.warn("scrollIntoView: selector is required");return}if(typeof window>"u"||typeof document>"u"){j.warn("scrollIntoView: window/document is not available");return}let _=null;if(f){if(_=await new Promise(A=>{const x=document.querySelector(i);if(x){A(x);return}const O=setTimeout(()=>{M.disconnect(),A(null)},g),M=new MutationObserver(()=>{const T=document.querySelector(i);T&&(clearTimeout(O),M.disconnect(),A(T))});M.observe(document.body,{childList:!0,subtree:!0})}),!_){j.warn(`scrollIntoView: element not found for selector "${i}" after ${g}ms timeout`);return}}else{m>0&&await new Promise(x=>setTimeout(x,m)),_=document.querySelector(i);let A=0;for(;!_&&AsetTimeout(x,S)),_=document.querySelector(i),A++;if(!_){j.warn(`scrollIntoView: element not found for selector "${i}" after ${A+1} attempts`);return}}if(v){const A=document.querySelector(v);if(!A){j.warn(`scrollIntoView: container not found for selector "${v}"`);return}const x=_.getBoundingClientRect(),O=A.getBoundingClientRect(),M=x.top-O.top+A.scrollTop,T=M+x.height;let D;switch(c){case"start":D=M;break;case"center":D=M-A.clientHeight/2+x.height/2;break;case"end":D=T-A.clientHeight;break;default:const z=x.topO.bottom;if(z)D=M;else if(P)D=T-A.clientHeight;else return;break}A.scrollTo({top:Math.max(0,D),behavior:l});return}_.scrollIntoView({behavior:l,block:c,inline:d})}),this.registerHandler("reloadExtensions",async(n,a)=>{if(typeof window>"u"){j.warn("reloadExtensions: window is not available");return}const i=window.__templateApp;if(!i){j.warn("reloadExtensions: TemplateApp not initialized");return}if(typeof i.reloadExtensionState=="function")try{await i.reloadExtensionState()}catch(d){throw j.error("reloadExtensions: reloadExtensionState failed",d),d}else j.warn("reloadExtensions: TemplateApp.reloadExtensionState unavailable");const{moduleInfo:l,pluginInfo:c}=n.params||{};if(l)try{await this.executeAction({handler:"reloadModuleHandlers",params:n.params},a)}catch(d){j.error("reloadExtensions: reloadModuleHandlers failed",d)}if(c)try{await this.executeAction({handler:"reloadPluginHandlers",params:n.params},a)}catch(d){j.error("reloadExtensions: reloadPluginHandlers failed",d)}j.log("reloadExtensions: done")}),this.registerHandler("reloadRoutes",async(n,a)=>{if(typeof window>"u"){j.warn("reloadRoutes: window is not available");return}const i=window.__templateApp;if(!i){j.warn("reloadRoutes: TemplateApp not initialized");return}if(typeof i.reloadExtensionState=="function")try{await i.reloadExtensionState(),j.log("reloadRoutes: delegated to reloadExtensionState")}catch(l){throw j.error("reloadRoutes: Failed to reload routes",l),l}else{const l=i.getRouter?.();l&&(await l.loadRoutes(),j.log("reloadRoutes: Routes reloaded (legacy fallback)"))}}),this.registerHandler("refresh",async(n,a)=>{if(typeof window>"u"){j.warn("refresh: window is not available");return}const i=Number(n.params?.delayMs??0);i>0&&await new Promise(l=>setTimeout(l,i)),window.location.reload()}),this.registerHandler("remount",async(n,a)=>{const{componentId:i}=n.params||{};if(!i){j.warn("remount: componentId parameter is required");return}if(!this.globalStateUpdater){j.warn("remount: globalStateUpdater is not set");return}const d=(window.G7Core?.state?.get()||{})._global?._remountKeys||{},f=d[i]||0;this.globalStateUpdater({_remountKeys:{...d,[i]:f+1}}),j.log(`remount: ${i} key incremented to ${f+1}`)}),this.registerHandler("reloadTranslations",async(n,a)=>{if(typeof window>"u"){j.warn("reloadTranslations: window is not available");return}const i=window.__templateApp;if(!i){j.warn("reloadTranslations: TemplateApp not initialized");return}if(typeof i.reloadExtensionState=="function")try{await i.reloadExtensionState(),j.log("reloadTranslations: delegated to reloadExtensionState")}catch(l){throw j.error("reloadTranslations: Failed to reload translations",l),l}}),this.registerHandler("reloadModuleHandlers",async(n,a)=>{if(typeof window>"u"){j.warn("reloadModuleHandlers: window is not available");return}const{moduleInfo:i,action:l}=n.params||{};if(!i){j.warn("reloadModuleHandlers: moduleInfo is required");return}const c=i.data||i;if(!c.identifier){j.warn("reloadModuleHandlers: moduleInfo with identifier is required");return}if(!l||l!=="add"&&l!=="remove"){j.warn('reloadModuleHandlers: action must be "add" or "remove"');return}const d=window.G7Config;if(!d){j.warn("reloadModuleHandlers: G7Config not available");return}const f=c.identifier;try{if(l==="add"){if(c.assets&&(d.moduleAssets=d.moduleAssets||{},d.moduleAssets[f]=c.assets,j.log(`reloadModuleHandlers: Added assets for ${f}`),c.assets.js)){const g=c.assets.js,m=`module-${f}`;if(document.getElementById(m)){j.warn(`reloadModuleHandlers: Script ${m} already loaded`);return}const y=document.createElement("script");if(y.id=m,y.src=g,y.async=!0,await new Promise((S,v)=>{y.onload=()=>{j.log(`reloadModuleHandlers: Script loaded successfully for ${f}`),S()},y.onerror=()=>{j.error(`reloadModuleHandlers: Failed to load script for ${f}`),v(new Error(`Failed to load module script: ${g}`))},document.head.appendChild(y)}),c.assets.css){const S=c.assets.css,v=`module-css-${f}`;if(!document.getElementById(v)){const _=document.createElement("link");_.id=v,_.rel="stylesheet",_.href=S,document.head.appendChild(_),j.log(`reloadModuleHandlers: CSS loaded for ${f}`)}}}}else if(l==="remove"){d.moduleAssets&&d.moduleAssets[f]&&(delete d.moduleAssets[f],j.log(`reloadModuleHandlers: Removed assets for ${f}`));const g=`module-${f}`,m=document.getElementById(g);m&&(m.remove(),j.log(`reloadModuleHandlers: Removed script for ${f}`));const y=`module-css-${f}`,S=document.getElementById(y);S&&(S.remove(),j.log(`reloadModuleHandlers: Removed CSS for ${f}`))}}catch(g){throw j.error(`reloadModuleHandlers: Failed to ${l} module assets`,g),g}}),this.registerHandler("reloadPluginHandlers",async(n,a)=>{if(typeof window>"u"){j.warn("reloadPluginHandlers: window is not available");return}const{pluginInfo:i,action:l}=n.params||{};if(!i){j.warn("reloadPluginHandlers: pluginInfo is required");return}const c=i.data||i;if(!c.identifier){j.warn("reloadPluginHandlers: pluginInfo with identifier is required");return}if(!l||l!=="add"&&l!=="remove"){j.warn('reloadPluginHandlers: action must be "add" or "remove"');return}const d=window.G7Config;if(!d){j.warn("reloadPluginHandlers: G7Config not available");return}const f=c.identifier;try{if(l==="add"){if(c.assets&&(d.pluginAssets=d.pluginAssets||{},d.pluginAssets[f]=c.assets,j.log(`reloadPluginHandlers: Added assets for ${f}`),c.assets.js)){const g=c.assets.js,m=`plugin-${f}`;if(document.getElementById(m)){j.warn(`reloadPluginHandlers: Script ${m} already loaded`);return}const y=document.createElement("script");if(y.id=m,y.src=g,y.async=!0,await new Promise((S,v)=>{y.onload=()=>{j.log(`reloadPluginHandlers: Script loaded successfully for ${f}`),S()},y.onerror=()=>{j.error(`reloadPluginHandlers: Failed to load script for ${f}`),v(new Error(`Failed to load plugin script: ${g}`))},document.head.appendChild(y)}),c.assets.css){const S=c.assets.css,v=`plugin-css-${f}`;if(!document.getElementById(v)){const _=document.createElement("link");_.id=v,_.rel="stylesheet",_.href=S,document.head.appendChild(_),j.log(`reloadPluginHandlers: CSS loaded for ${f}`)}}}}else if(l==="remove"){d.pluginAssets&&d.pluginAssets[f]&&(delete d.pluginAssets[f],j.log(`reloadPluginHandlers: Removed assets for ${f}`));const g=`plugin-${f}`,m=document.getElementById(g);m&&(m.remove(),j.log(`reloadPluginHandlers: Removed script for ${f}`));const y=`plugin-css-${f}`,S=document.getElementById(y);S&&(S.remove(),j.log(`reloadPluginHandlers: Removed CSS for ${f}`))}}catch(g){throw j.error(`reloadPluginHandlers: Failed to ${l} plugin assets`,g),g}});let e=!1;this.registerHandler("showErrorPage",async(n,a)=>{if(e){j.log("showErrorPage: Already active, skipping duplicate call");return}if(e=!0,typeof window>"u"){e=!1,j.warn("showErrorPage: window is not available");return}const i=window.__templateApp;if(!i){e=!1,j.warn("showErrorPage: TemplateApp not initialized");return}const l=i.getErrorPageHandler?.();if(!l){e=!1,j.warn("showErrorPage: ErrorPageHandler not available");return}const c=n.params?.errorCode||500,d=n.params?.target||"content";let f=n.params?.containerId;f||(f=d==="full"?"app":"main_content"),j.log("showErrorPage:",{errorCode:c,target:d,containerId:f});const g=(m,y=6e4)=>new Promise((S,v)=>{const _=document.getElementById(m);if(_){S(_);return}j.log(`showErrorPage: Waiting for container #${m}...`);let A;const x=new MutationObserver((O,M)=>{const T=document.getElementById(m);T&&(j.log(`showErrorPage: Container #${m} found`),M.disconnect(),clearTimeout(A),S(T))});x.observe(document.body,{childList:!0,subtree:!0}),A=setTimeout(()=>{x.disconnect(),v(new Error(`Container #${m} not found within ${y}ms`))},y)});try{await g(f);const m=Sr(),y=m.layoutErrorHandling;m.clearLayoutConfig();try{await l.renderError(c,f)||j.warn(`showErrorPage: Failed to render error page for code ${c}`)}finally{m.setLayoutConfig(y)}}catch(m){throw j.error("showErrorPage: Error rendering error page:",m),m}finally{e=!1}}),this.registerHandler("emitEvent",async(n,a)=>{const i=n.params?.event,l=n.params?.data;if(!i){j.warn("emitEvent: event parameter is required");return}if(typeof window>"u"){j.warn("emitEvent: window is not available");return}const c=window.G7Core;if(!c?.componentEvent?.emit){j.warn("emitEvent: G7Core.componentEvent is not available");return}try{j.log(`emitEvent: Emitting "${i}"`,l);const d={...l,_context:{data:a.data,state:a.state}},f=await c.componentEvent.emit(i,d);if(!f||f.length===0?j.warn(`emitEvent: No listeners found for "${i}"`):j.log(`emitEvent: Event "${i}" completed with ${f.length} listener(s)`,f),this.globalStateUpdater){const g=(c?.state?.get()||{})._local||{},m={event:i,success:!0,data:f.length===1?f[0]:f,listeners:f.length};this.globalStateUpdater({_local:{...g,_eventResult:m}});const y=a.state&&typeof a.state=="object"&&!Array.isArray(a.state)?a.state:g;window.__g7SequenceLocalSync={...y,_eventResult:m}}}catch(d){if(j.error(`emitEvent: Event "${i}" failed`,d),this.globalStateUpdater){const f=c?.state?.get()||{};this.globalStateUpdater({_local:{...f._local,_eventResult:{event:i,success:!1,error:d instanceof Error?d.message:String(d)}}})}throw d}}),this.registerHandler("updateProductField",async(n,a)=>{const{productId:i,field:l,value:c}=n.params||{};if(!i||!l){j.warn("updateProductField: productId and field are required");return}if(typeof window>"u"){j.warn("updateProductField: window is not available");return}const d=window.G7Core;if(!d?.state?.get||!this.globalStateUpdater){j.warn("updateProductField: G7Core.state or globalStateUpdater is not available");return}try{const f=d.state.get()||{},m=(f.products?.data?.data||[]).map(S=>S.id===i?{...S,[l]:c,_modified:!0}:S),y=new Set(f._local?.modifiedProductIds||[]);y.add(i),this.globalStateUpdater({products:{...f.products,data:{...f.products?.data,data:m}},_local:{...f._local,modifiedProductIds:Array.from(y)}}),j.log(`updateProductField: Updated product ${i}, field: ${l}, value:`,c)}catch(f){throw j.error("updateProductField: Error updating product field",f),f}}),this.registerHandler("updateOptionField",async(n,a)=>{const{productId:i,optionId:l,field:c,value:d}=n.params||{};if(!i||!l||!c){j.warn("updateOptionField: productId, optionId and field are required");return}if(typeof window>"u"){j.warn("updateOptionField: window is not available");return}const f=window.G7Core;if(!f?.state?.get||!this.globalStateUpdater){j.warn("updateOptionField: G7Core.state or globalStateUpdater is not available");return}try{const g=f.state.get()||{},y=(g.products?.data?.data||[]).map(v=>{if(v.id===i&&v.options){const _=v.options.map(A=>A.id===l?{...A,[c]:d,_modified:!0}:A);return{...v,options:_,_modified:!0}}return v}),S=new Set(g._local?.modifiedProductIds||[]);S.add(i),this.globalStateUpdater({products:{...g.products,data:{...g.products?.data,data:y}},_local:{...g._local,modifiedProductIds:Array.from(S)}}),j.log(`updateOptionField: Updated product ${i} option ${l}, field: ${c}, value:`,d)}catch(g){throw j.error("updateOptionField: Error updating option field",g),g}}),this.registerHandler("setLocale",async(n,a)=>{const i=n.target;if(!i||typeof i!="string"){j.warn("setLocale: Invalid locale:",i);return}const l=window.__templateApp;if(l&&typeof l.changeLocale=="function")try{await l.changeLocale(i),j.log("setLocale: Locale changed to",i)}catch(c){j.error("setLocale: Failed to change locale:",c),window.location.reload()}else{j.warn("setLocale: TemplateApp not found, falling back to page reload");try{localStorage.setItem("g7_locale",i)}catch{}window.location.reload()}}),this.registerHandler("suppress",async()=>{j.log("suppress: Error intentionally suppressed")}),this.registerBuiltInHandlerMetadata()}registerBuiltInHandlerMetadata(){const e=Fn();if(!e?.isEnabled())return;const n=[{name:"refetchDataSource",description:"데이터 소스를 다시 fetch합니다"},{name:"appendDataSource",description:"데이터 소스에 새 데이터를 병합합니다 (무한 스크롤용)"},{name:"updateDataSource",description:"API 응답으로 데이터 소스를 직접 업데이트합니다 (refetch 대체)"},{name:"reloadRoutes",description:"라우트를 다시 로드합니다"},{name:"refresh",description:"현재 페이지를 새로고침합니다"},{name:"remount",description:"컴포넌트를 리마운트합니다"},{name:"reloadTranslations",description:"다국어 파일을 다시 로드합니다"},{name:"showErrorPage",description:"에러 페이지를 표시합니다"},{name:"emitEvent",description:"이벤트를 발생시킵니다"},{name:"updateProductField",description:"상품 필드를 인라인 수정합니다"},{name:"updateOptionField",description:"상품 옵션 필드를 인라인 수정합니다"},{name:"setLocale",description:"언어를 변경합니다 (DB 저장 + UI 리렌더링)"}];for(const a of n)e.trackHandlerRegistration(a.name,"built-in",a.description)}createHandler(e,n,a){j.log("createHandler called for:",e.handler,e.type);const i=window.__g7DevTools,l=Date.now(),c=a?.state?{...a.state}:null,d=`handler_${e.handler}_${l}`;return async f=>{if(j.log("Handler invoked for:",e.handler,"event:",f.type),i?.isEnabled?.()&&c){const m=window.G7Core?.state?.getLocal?.()??a?.state??{},y=Date.now()-l;for(const S of Object.keys(c)){const v=c[S],_=m[S];v!==_&&y>100&&(i.trackStaleClosureWarning?.({type:"event-handler-stale",location:`createHandler(${e.handler})`,capturedPath:`_local.${S}`,capturedValue:v,capturedAt:l,currentValue:_,actionId:d,stackTrace:new Error().stack}),j.warn(`[Stale Closure] _local.${S} changed after handler creation:`,`captured="${v}" → current="${_}" (${y}ms ago)`))}}try{f.type!=="change"&&f.preventDefault();let g={};if(f.type==="submit"&&f.target instanceof HTMLFormElement){const S=f.target;new FormData(S).forEach((_,A)=>{g[A]=_})}const m={...this.defaultContext,data:{...n,form:g,_local:a?.state||{},$event:f},event:f,...a&&{state:a.state,setState:a.setState},isolatedContext:a?.isolatedContext};if(e.confirm){let S=this.resolveValue(e.confirm,m.data);if(this.translationEngine&&this.translationContext&&S.startsWith("$t:")&&(S=this.translationEngine.resolveTranslations(S,this.translationContext,m.data)),!confirm(S))return}const y=window.__g7ActionContext;window.__g7ActionContext={...a,data:m.data};try{await this.executeAction(e,m)}finally{window.__g7ActionContext=y}}catch(g){if(j.error("Action execution failed:",g),e.onError){const m=g instanceof Bt&&g.originalError?g.originalError:g,y={...this.defaultContext,data:{...n,error:m,_local:a?.state||{},$event:f},event:f,...a&&{state:a.state,setState:a.setState}},S=Array.isArray(e.onError)?e.onError:[e.onError];for(const v of S)await this.executeAction(v,y)}}}}generateActionId(e,n){const a=e.handler,i=n||"no-target",l=Date.now(),c=Math.random().toString(36).substring(2,9);return`${a}_${i.replace(/[^a-zA-Z0-9]/g,"_")}_${l}_${c}`}async executeAction(e,n){if(e=this.resolveActionRef(e),typeof e.handler=="string"&&e.handler.includes("{{")){const f=this.evaluateExpression(e.handler,n.data);e={...e,handler:f==null?"":String(f)}}const a=Fn(),i=a?.isEnabled()?`action_${Date.now()}_${Math.random().toString(36).substring(2,11)}`:void 0,l=performance.now();let c;if(i&&a&&a.logAction({id:i,type:e.handler,params:this.sanitizeForDevTools(e.params),context:this.sanitizeForDevTools({hasState:!!n.state,hasSetState:!!n.setState,dataKeys:n.data?Object.keys(n.data):[]}),startTime:l,status:"started"}),e.if!==void 0)try{const f=this.resolveValue(e.if,n.data),g=f===!0||f==="true"||f&&f!=="false"&&f!=="0"&&f!==!1;if(j.log("[executeAction] if condition result:",e.handler,g),!g)return i&&a&&a.logAction({id:i,type:e.handler,startTime:l,endTime:performance.now(),duration:performance.now()-l,status:"success",result:{skipped:!0,reason:"if condition false"}}),{success:!0,data:void 0}}catch(f){return j.error("[executeAction] if condition error:",e.handler,f),i&&a&&a.logAction({id:i,type:e.handler,startTime:l,endTime:performance.now(),duration:performance.now()-l,status:"error",error:{name:"ConditionEvaluationError",message:f instanceof Error?f.message:String(f),stack:f instanceof Error?f.stack:void 0}}),{success:!0,data:void 0}}let d;try{const f=this.resolveParams(e.params,n.data);i&&(c=this.sanitizeForDevTools(f));const g=e.target?this.resolveValue(e.target,n.data):void 0;if(e.handler==="apiCall"&&n.setState){d=this.generateActionId(e,g);const y=n.state?.loadingActions||{};n.setState({loadingActions:{...y,[d]:!0}})}if(this.previewMode&&ym.has(e.handler))return j.warn(`Preview mode: "${e.handler}" suppressed`,g||f),i&&a&&a.logAction({id:i,type:e.handler,startTime:l,endTime:performance.now(),duration:performance.now()-l,status:"skipped",metadata:{reason:"preview_mode_suppressed"}}),{success:!0,data:void 0};let m;switch(e.handler){case"ensureIdentityVerified":m=await this.handleEnsureIdentityVerified(f);break;case"resolveIdentityChallenge":m=this.handleResolveIdentityChallenge(f);break;case"navigate":const y=f.path||g;m=await this.handleNavigate(y,f,n);break;case"navigateBack":m=await this.handleNavigateBack();break;case"navigateForward":m=await this.handleNavigateForward();break;case"openWindow":{const _=f.path||g;m=await this.handleOpenWindow(_,f);break}case"replaceUrl":{const _=f.path||g||window.location.pathname;m=await this.handleReplaceUrl(_,f);break}case"apiCall":if(e.onSuccess&&a?.isEnabled()&&i){const _=window.G7Core,A={};if(_?.state?.get){const x=_.state.get();A._global=x}n.state&&(A._local=n.state),a.registerStateCaptureForHandler?.(i,["_global","_local"],A)}const S=e.auth_mode??(e.auth_required?"required":"none"),v=e.identity_target?this.resolveParams(e.identity_target,n.data):void 0;m=await this.handleApiCall(g,f,n,S,v);break;case"login":m=await this.handleLogin(g,f,n);break;case"logout":m=await this.handleLogout(g,n);break;case"setState":j.log("[executeAction] setState resolvedParams:",f),m=await this.handleSetState(e.render!==void 0?{...f,__render:e.render}:f,n);break;case"setError":m=await this.handleSetError(g,f,n);break;case"openModal":m=await this.handleOpenModal(g,n);break;case"closeModal":m=await this.handleCloseModal(n);break;case"showAlert":m=await this.handleShowAlert(g,n);break;case"toast":m=await this.handleToast(f,n);break;case"switch":m=await this.handleSwitch(e,n);break;case"conditions":m=await this.handleConditions(e,n);break;case"sequence":m=await this.handleSequence(e,n);break;case"parallel":m=await this.handleParallel(e,n);break;case"startInterval":m=this.handleStartInterval(f,n);break;case"stopInterval":m=this.handleStopInterval(f);break;case"loadScript":m=await this.handleLoadScript(f,e,n);break;case"callExternal":m=await this.handleCallExternal(f,e,n);break;case"callExternalEmbed":m=await this.handleCallExternalEmbed(f,e,n);break;case"saveToLocalStorage":m=await this.handleSaveToLocalStorage(f,n);break;case"loadFromLocalStorage":m=await this.handleLoadFromLocalStorage(f,n);break;default:m=await this.handleCustomAction({...e,target:g,params:f},n);break}if(e.resultTo&&m!==void 0){const{target:y,key:S,merge:v}=e.resultTo,_=this.resolveValue(S,n.data),A=v==="replace"?"replace":v==="shallow"?"shallow":"deep";if(y==="_local"&&n.setState){const x=this.buildNestedUpdate(_,m),O=A!=="deep"?{...x,__mergeMode:A}:x;n.setState(O),j.log(`[resultTo] Saved to _local.${_} (merge=${A}):`,m)}else if(y==="_local"&&this.globalStateUpdater){const M=(window.G7Core?.state?.get()||{})._local||{},T=this.buildNestedUpdate(_,m);let D;A==="replace"?D=T:A==="shallow"?D={...M,...T}:D=this.deepMergeWithState(T,M),this.globalStateUpdater({_local:D}),j.log(`[resultTo] Saved to _local.${_} via globalStateUpdater (merge=${A}):`,m)}else if(y==="_global"&&this.globalStateUpdater){const x=this.buildNestedUpdate(_,m);this.globalStateUpdater(x),j.log(`[resultTo] Saved to _global.${_}:`,m)}else if(y==="_isolated"&&n.isolatedContext){const x=this.buildNestedUpdate(_,m);n.isolatedContext.mergeState(x,A),j.log(`[resultTo] Saved to _isolated.${_} (merge=${A}):`,m)}else j.warn(`[resultTo] Cannot save result: target=${y}, setState=${!!n.setState}, globalStateUpdater=${!!this.globalStateUpdater}, isolatedContext=${!!n.isolatedContext}`)}if(e.onSuccess){if(a?.isEnabled()&&i&&e.handler==="apiCall"){const v=window.G7Core,_={};v?.state?.get&&(_._global=v.state.get()),n.state&&(_._local=n.state),a.detectStaleClosure?.(i,`${e.handler} → onSuccess`,_,"callback-state-capture",i)}const y={...n,data:{...n.data,result:m,response:m}},S=Array.isArray(e.onSuccess)?e.onSuccess:[e.onSuccess];S.length>1?await this.handleSequence({handler:"sequence",type:"click",actions:S},y):S.length===1&&await this.executeAction(S[0],y)}return i&&a&&a.logAction({id:i,type:e.handler,params:this.sanitizeForDevTools(e.params),resolvedParams:c,startTime:l,endTime:performance.now(),duration:performance.now()-l,status:"success",result:this.sanitizeForDevTools(m)}),{success:!0,data:m}}catch(f){const g=f instanceof Bt?f:new Bt(`Failed to execute action: ${e.handler}`,e,f instanceof Error?f:void 0),m=g.originalError?.response||{},y=m.data||{},S=g.originalError?.status||m.status||500;let v=EC(g.originalError??f,e.handler,y.message,g.message);this.translationEngine&&this.translationContext&&v.startsWith("$t:")&&(v=this.translationEngine.resolveTranslations(v,this.translationContext));const _={status:S,message:v,errors:y.errors||m.errors,data:y,statusText:g.originalError?.statusText,error_code:y.error_code??m.error_code};if(g.unknownHandler)throw j.warn(`Unknown action handler "${e.handler}" — skipped (extension not loaded?). Not surfaced to the user.`),g;const x=Sr().resolve(S,{errorHandling:e.errorHandling,onError:e.onError});if(x.handler){if(a?.isEnabled()&&i&&e.handler==="apiCall"){const M=window.G7Core,T={};M?.state?.get&&(T._global=M.state.get()),n.state&&(T._local=n.state),a.detectStaleClosure?.(i,`${e.handler} → onError`,T,"callback-state-capture",i)}const O={...n,data:{...n.data,error:_}};try{const M={type:"click",handler:x.handler.handler,target:x.handler.target,params:x.handler.params,actions:x.handler.actions};return await this.executeAction(M,O),{success:!1,error:g}}catch(M){return j.error("Error executing error handler:",M),{success:!1,error:g}}}throw i&&a&&a.logAction({id:i,type:e.handler,params:this.sanitizeForDevTools(e.params),resolvedParams:c,startTime:l,endTime:performance.now(),duration:performance.now()-l,status:"error",error:{name:g.name,message:g.message,stack:g.stack}}),g}finally{if(e.handler==="apiCall"&&n.setState&&d){const f=n.state?.loadingActions||{},{[d]:g,...m}=f;n.setState({loadingActions:m})}}}async handleNavigate(e,n,a){let i=e;if(n.query||n.mergeQuery===!0)if(n.mergeQuery===!0)i=this.buildMergedQueryPath(e,n.query??{});else{const c=new URLSearchParams;for(const[f,g]of Object.entries(n.query))if(g!=null&&g!=="")if(Array.isArray(g)){const m=f.endsWith("[]")?f:`${f}[]`;for(const y of g)y!=null&&y!==""&&c.append(m,String(y))}else c.set(f,String(g));const d=c.toString();d&&(i=`${e}?${d}`)}if(j.log("handleNavigate:",{target:e,params:n,finalPath:i,replace:n.replace,windowLocationSearch:window.location.search}),n.replace===!0){const c=window.G7Core;if(c?.updateQueryParams){const d=typeof n.transition_overlay_target=="string"?n.transition_overlay_target:void 0;await c.updateQueryParams(i,d?{transitionOverlayTarget:d}:void 0),j.log("handleNavigate: Used updateQueryParams for replace mode",d?{transitionOverlayTarget:d}:void 0),this.applyScrollOption(n.scroll,n.scrollBehavior,"top");return}j.warn("handleNavigate: G7Core.updateQueryParams not available, falling back to React Router")}const l=this.resolveNavigateFallbackAction(i,n);if(l){j.warn(`handleNavigate: No route matched, falling back to "${l.handler}"`),await this.dispatchAction({type:"click",handler:l.handler,params:l.params},a),this.applyScrollOption(n.scroll,n.scrollBehavior,"top");return}if(!a.navigate)throw new Bt("Navigate function is not provided in context");a.navigate(i,{replace:n.replace===!0}),this.applyScrollOption(n.scroll,n.scrollBehavior,"top")}resolveNavigateFallbackAction(e,n){const a=n.fallback;if(a===!1||n.replace===!0)return null;const l=window.__templateApp?.getRouter?.();if(!l||typeof l.match!="function"||typeof l.getRoutes=="function"&&l.getRoutes().length===0)return null;const c=e.split("?")[0];return l.match(c)?null:this.resolveNavigateFallback(a,e,n)}resolveNavigateFallback(e,n,a){return e==null?{handler:"openWindow",params:{path:n,target:"_self"}}:typeof e=="string"?{handler:e,params:{path:n}}:typeof e=="object"&&typeof e.handler=="string"?{handler:e.handler,params:{path:n,...e.params||{}}}:(j.warn("resolveNavigateFallback: unrecognized fallback option, using openWindow",e),{handler:"openWindow",params:{path:n,target:"_blank"}})}applyScrollOption(e,n,a){const i=e===void 0?a:e;if(i==="preserve")return;const l=n==="smooth"?"smooth":"instant",c=y=>{if(typeof y!="object"||y===null)return!1;const S=y;return"container"in S||"to"in S||"block"in S||"offset"in S},d=(y,S,v=0)=>{y===window?window.scrollTo({top:S,left:v,behavior:l}):y.scrollTo({top:S,left:v,behavior:l})},f=(y,S,v,_)=>{if(S===window){y.scrollIntoView({behavior:l,block:v}),_&&window.scrollBy({top:-_,left:0,behavior:l});return}const A=S,x=y.getBoundingClientRect(),O=A.getBoundingClientRect(),M=A.scrollTop+(x.top-O.top);let T;v==="center"?T=M-(A.clientHeight-y.clientHeight)/2:v==="end"?T=M-(A.clientHeight-y.clientHeight):T=M,T-=_,A.scrollTo({top:Math.max(0,T),left:0,behavior:l})},g=()=>{window.scrollTo({top:0,left:0,behavior:l});const y=document.getElementById("app")??document.body;if(!y)return;y.querySelectorAll("*").forEach(v=>{if(v.scrollTop===0&&v.scrollLeft===0)return;const _=window.getComputedStyle(v),A=_.overflowY,x=_.overflowX;(A==="auto"||A==="scroll"||x==="auto"||x==="scroll")&&v.scrollTo({top:0,left:0,behavior:l})})},m=()=>{try{if(c(i)){const y=i,S=y.block??"start",v=typeof y.offset=="number"?y.offset:0;let _=window;if(typeof y.container=="string"&&y.container.length>0){const x=document.querySelector(y.container);if(!x){j.warn(`applyScrollOption: container not found: ${y.container}`);return}_=x}const A=y.to??"top";if(A==="top"){_===window?g():d(_,0);return}if(typeof A=="number"){d(_,A-v);return}if(typeof A=="object"&&A!==null&&("x"in A||"y"in A)){const{x=0,y:O=0}=A;d(_,O-v,x);return}if(typeof A=="string"&&(A.startsWith("#")||A.startsWith("."))){const x=document.querySelector(A);x?f(x,_,S,v):j.warn(`applyScrollOption: target element not found: ${A}`);return}j.warn('applyScrollOption: invalid "to" value in extended form',A);return}if(i==="top"){g();return}if(typeof i=="number"){window.scrollTo({top:i,left:0,behavior:l});return}if(typeof i=="object"&&i!==null&&("x"in i||"y"in i)){const{x:y=0,y:S=0}=i;window.scrollTo({top:S,left:y,behavior:l});return}if(typeof i=="string"&&(i.startsWith("#")||i.startsWith("."))){const y=document.querySelector(i);y&&y.scrollIntoView({behavior:l,block:"start"});return}}catch(y){j.warn("applyScrollOption: failed to apply scroll",y)}};typeof window.requestAnimationFrame=="function"?window.requestAnimationFrame(m):m()}async handleOpenWindow(e,n){let a=e;if(n.query){const l=new URLSearchParams;for(const[d,f]of Object.entries(n.query))f!=null&&f!==""&&l.set(d,String(f));const c=l.toString();c&&(a=`${e}?${c}`)}const i=n.target==="_self"?"_self":"_blank";j.log("handleOpenWindow:",{target:e,params:n,finalPath:a,windowTarget:i}),i==="_self"?window.location.assign(a):window.open(a,"_blank")}async handleNavigateBack(){j.log("handleNavigateBack"),window.history.back()}async handleNavigateForward(){j.log("handleNavigateForward"),window.history.forward()}async handleReplaceUrl(e,n){let a=e;if(n.query||n.mergeQuery===!0)if(n.mergeQuery===!0)a=this.buildMergedQueryPath(e,n.query??{});else{const i=new URLSearchParams;for(const[c,d]of Object.entries(n.query))if(d!=null&&d!=="")if(Array.isArray(d)){const f=c.endsWith("[]")?c:`${c}[]`;for(const g of d)g!=null&&g!==""&&i.append(f,String(g))}else i.set(c,String(d));const l=i.toString();l&&(a=`${e}?${l}`)}j.log("handleReplaceUrl:",{target:e,params:n,finalPath:a}),window.history.replaceState(null,"",a),this.applyScrollOption(n.scroll,n.scrollBehavior,"preserve")}buildMergedQueryPath(e,n){const a=new URLSearchParams(window.location.search);for(const[c,d]of Object.entries(n))if(d==null||d==="")a.delete(c);else if(Array.isArray(d)){const f=c.endsWith("[]")?c:`${c}[]`;a.delete(c),a.delete(f);for(const g of d)g!=null&&g!==""&&a.append(f,String(g))}else a.set(c,String(d));const i=a.toString(),l=e.split("?")[0];return i?`${l}?${i}`:l}async ensureCsrfToken(){try{await fetch("/sanctum/csrf-cookie",{credentials:"include"})}catch(e){throw new Bt("Failed to fetch CSRF token",void 0,e instanceof Error?e:void 0)}}getCsrfTokenFromCookie(){const a=`; ${document.cookie}`.split("; XSRF-TOKEN=");if(a.length===2){const i=a.pop()?.split(";").shift();return i?decodeURIComponent(i):null}return null}buildQueryString(e){const n=new URLSearchParams;for(const[a,i]of Object.entries(e))if(!(i==null||i===""))if(Array.isArray(i))for(const l of i)l!=null&&l!==""&&n.append(`${a}[]`,String(l));else typeof i=="object"?n.append(a,JSON.stringify(i)):n.append(a,String(i));return n.toString()}async handleEnsureIdentityVerified(e){const n=typeof e.purpose=="string"?e.purpose:"sensitive_action",a=e.target&&typeof e.target=="object"?e.target:void 0;return await Et.handle({success:!1,error_code:"identity_verification_required",message:"",verification:{policy_key:e.policy_key??"",purpose:n,provider_id:e.provider_id??null,render_hint:e.render_hint??null,return_request:null}},void 0,a)!==null}handleResolveIdentityChallenge(e){const n=typeof e.result=="string"?e.result:"cancelled";let a;switch(n){case"verified":{const i=typeof e.token=="string"?e.token:"";if(!i){j.warn("resolveIdentityChallenge: result=verified 인데 token 이 비어있습니다. failed 로 강등합니다."),a={status:"failed",failureCode:"MISSING_TOKEN"};break}a={status:"verified",token:i,providerData:e.providerData&&typeof e.providerData=="object"?e.providerData:void 0};break}case"pending":{const i=typeof e.pollUrl=="string"?e.pollUrl:"",l=typeof e.expiresAt=="string"?e.expiresAt:"";if(!i||!l){j.warn("resolveIdentityChallenge: result=pending 인데 pollUrl/expiresAt 누락 — failed 로 강등합니다."),a={status:"failed",failureCode:"MALFORMED_PENDING"};break}a={status:"pending",pollUrl:i,pollIntervalMs:typeof e.pollIntervalMs=="number"?e.pollIntervalMs:void 0,expiresAt:l};break}case"failed":a={status:"failed",failureCode:typeof e.failureCode=="string"?e.failureCode:"UNKNOWN",reason:typeof e.reason=="string"?e.reason:void 0};break;default:a={status:"cancelled"};break}return Et.resolveDeferred(a),!0}async handleApiCall(e,n,a,i="none",l){const{method:c="GET",body:d,headers:f,contentType:g}=n;c!=="GET"&&c!=="HEAD"&&await this.ensureCsrfToken();const m=this.getCsrfTokenFromCookie();let y={};if(i==="required"||i==="optional"){const W=Hr().getToken();W&&(y={Authorization:`Bearer ${W}`})}let S=e;const v=n.query||(c==="GET"?d:null);if(v&&typeof v=="object"){const q=this.buildQueryString(v);if(q){const W=e.includes("?")?"&":"?";S=`${e}${W}${q}`}}const _=window.G7Core?.state?.getGlobal?.()||a.state?._global||{},A=window.G7Core?.state?.getLocal?.()||a.state?._local||{},x={_global:_,_local:A},O=this.getMatchingGlobalHeaders(e,x),M={};if(typeof window<"u"){const q=localStorage.getItem("g7_locale");q&&(M["Accept-Language"]=q)}const T=g==="multipart/form-data",D={method:c,headers:{...T?{}:{"Content-Type":"application/json"},Accept:"application/json",...M,...m&&{"X-XSRF-TOKEN":m},...y,...O,...f},credentials:"include"};if(d&&c!=="GET")if(T){const q=new FormData;for(const[W,he]of Object.entries(d))he instanceof File||he instanceof Blob?q.append(W,he):he!=null&&q.append(W,typeof he=="object"?JSON.stringify(he):String(he));D.body=q}else D.body=JSON.stringify(d);const z=Fn();let P=null;z?.isEnabled()&&(P=z.trackRequest(S,c));try{let q=await fetch(S,D),W;try{W=await q.json()}catch{W=null}if(Et.isIdentityRequired(q.status,W)){const he=await Et.handle(W,{body:D.body,headers:D.headers,credentials:D.credentials},l);if(he){q=he;try{W=await q.json()}catch{W=null}}}if(P&&z?.isEnabled()&&(z.completeRequest(P,q.status,W),P=null),!q.ok){const he=W||{},Ee=new Error(he.message||`API call failed: ${q.statusText}`);throw Ee.response=he,Ee.status=q.status,Ee.statusText=q.statusText,new Bt(he.message||`API call failed: ${q.statusText}`,void 0,Ee)}return W}catch(q){throw P&&z?.isEnabled()&&z.failRequest(P,q instanceof Error?q.message:String(q)),q}}async handleLogin(e,n,a){const{body:i}=n;if(!i||!i.email||!i.password)throw new Bt("Login requires email and password in body params");const l=e==="user"?"user":"admin",c=l==="admin"?"/api/auth/admin/login":"/api/auth/login",d=window.G7Core?.state?.getGlobal?.()||a.state?._global||{},f=window.G7Core?.state?.getLocal?.()||a.state?._local||{},g={_global:d,_local:f},m=this.getMatchingGlobalHeaders(c,g),y=br.getInstance();try{const S=Object.keys(m).length>0?{headers:m}:void 0;return{user:await y.login(l,{email:i.email,password:i.password},S)}}catch(S){const v=S.message||"Login failed",_=new Error(v);throw _.response=S.response?.data||{},_.status=S.status||S.response?.status||500,new Bt(v,void 0,_)}}async handleLogout(e,n){await br.getInstance().logout()}async handleSetState(e,n){j.log("[handleSetState] START, params:",e);const{target:a="component",scope:i,merge:l,__render:c,...d}=e,f=Fn(),g=window.G7Core;if(typeof a=="string"&&(a.startsWith("$parent.")||a.startsWith("$root.")))return this.handleParentScopeSetState(a,d,l,n);const m=n.state&&Object.keys(n.state).length>0?n.state:g?.state?.get()||{},y=a==="global"?"_global":a.startsWith("_local.")?a:"_local",S=y==="_global"?m._global:y.startsWith("_local.")?this.getNestedProperty(m._local||{},y.slice(7)):m._local,v=f?.isEnabled()?f.startStateChange(y,S,d,{actionId:n.actionId,handlerType:"setState",source:`target=${a}`}):void 0,_=l==="replace"?"replace":l==="shallow"?"shallow":"deep",A=_!=="deep"?{...d,__mergeMode:_,...v?{__setStateId:v}:{}}:{...d,...v?{__setStateId:v}:{}};if(a==="global"){if(!this.globalStateUpdater){j.warn("Global state updater is not set"),v&&f&&f.completeStateChange(v);return}const x=g?.state?.get()||{},{__setStateId:O,__mergeMode:M,...T}=A,D=_==="deep"?this.deepMergeWithState(T,x):T,z={...D,...v?{__setStateId:v}:{},..._!=="deep"?{__mergeMode:_}:{}};return j.log("setState global:",z),this.globalStateUpdater(z,{render:c}),v&&f&&setTimeout(()=>f.completeStateChange(v),0),{__target:"global",..._!=="deep"?{__mergeMode:_}:{},...D}}else if(a==="isolated"){const x=n.isolatedContext;if(x){const{__mergeMode:O,__setStateId:M,...T}=A;return x.mergeState(T,_),j.log("[handleSetState] isolated state updated:",T,"mergeMode:",_),v&&f&&setTimeout(()=>f.completeStateChange(v),0),{__target:"isolated",...T}}else if(j.warn('[handleSetState] isolated target used but no IsolatedStateContext found. Make sure the component has "isolatedState" attribute. Falling back to local state.'),n.setState){const M=window.__g7PendingLocalState||n.state||{},T=_==="deep"?this.deepMergeWithState(A,M):A;return n.setState(T),v&&f&&setTimeout(()=>f.completeStateChange(v),0),T}else throw v&&f&&f.completeStateChange(v),new Bt("isolated target used but no IsolatedStateContext found and no setState function in context")}else if(a==="local"||a==="component"||a==="_local"){if(i==="parent"||i==="root"){const O=window.__g7LayoutContextStack||[];if(O.length>0){const M=i==="parent"?O[O.length-1]:O[0];if(M?.setState){const{__mergeMode:T,__setStateId:D,...z}=A,P=_==="deep"?this.deepMergeWithState(z,M.state||{}):z;return j.log(`[handleSetState] scope=${i}: 타겟 컨텍스트에 상태 업데이트`,P),M.setState(P),v&&f&&setTimeout(()=>f.completeStateChange(v),0),P}}j.warn(`[handleSetState] scope=${i}: 레이아웃 컨텍스트 스택이 비어있습니다. current로 폴백합니다.`)}const x=n.setState&&!n._isDispatchFallbackContext;if(j.log("[handleSetState] isRealComponentContext:",x,"context.setState:",!!n.setState,"_isDispatchFallbackContext:",n._isDispatchFallbackContext),x){j.log("[handleSetState] Using COMPONENT setState path"),j.log("[handleSetState] resolvedPayload:",A),j.log("[handleSetState] context.state:",n.state),j.log("[handleSetState] mergeMode:",_);const M=window.__g7PendingLocalState||n.state||{},T=_==="deep"?this.deepMergeWithState(A,{}):A;j.log("[handleSetState] convertedPayload (변경 필드만, dot notation 변환):",T),n.setState(T);const D=_==="deep"?this.deepMergeWithState(A,M):_==="shallow"?{...n.state||{},...A}:A,{__mergeMode:z,__setStateId:P,...q}=D;window.__g7PendingLocalState=q,j.log("[handleSetState] __g7PendingLocalState updated:",q),this.globalStateUpdater&&(this.globalStateUpdater({_local:q},{render:!1}),j.log("[handleSetState] _global._local synced (render:false):",q));const{__mergeMode:W,__setStateId:he,...Ee}=A;if(_==="replace")window.__g7ForcedLocalFields=Ee;else{const be=window.__g7ForcedLocalFields||{};window.__g7ForcedLocalFields=this.deepMergeWithState(Ee,be)}return j.log("[handleSetState] __g7ForcedLocalFields updated:",Ee),v&&f&&setTimeout(()=>f.completeStateChange(v),0),{__target:"local",...D}}else if(this.globalStateUpdater){j.log("[handleSetState] Using GLOBAL STATE UPDATER path for _local");const M=window.__g7PendingLocalState||m._local||{};j.log("[handleSetState] currentLocal (with pending):",M);const T=_==="deep"?this.deepMergeWithState(A,M):A;return j.log("[handleSetState] finalLocal (merged):",T),this.globalStateUpdater({_local:T},{render:c}),window.__g7PendingLocalState=T,j.log("[handleSetState] __g7PendingLocalState updated for globalStateUpdater path"),v&&f&&setTimeout(()=>f.completeStateChange(v),0),{__target:"local",...T}}else throw v&&f&&f.completeStateChange(v),new Bt("setState function is not provided in context and globalStateUpdater is not set")}else if(a.startsWith("_local.")){const x=a.slice(7),{__mergeMode:O,__setStateId:M,...T}=A,D=T.value!==void 0?T.value:Object.values(T)[0];if(n.setState){const P=window.__g7PendingLocalState||n.state||{},q=this.createNestedUpdate(x,D,P),W={...P,...q};return n.setState(q),v&&f&&setTimeout(()=>f.completeStateChange(v),0),W}else throw v&&f&&f.completeStateChange(v),new Bt("setState function is not provided in context")}else{if(!n.setState)throw v&&f&&f.completeStateChange(v),new Bt("setState function is not provided in context");const O=window.__g7PendingLocalState||n.state||{},M=_==="deep"?this.deepMergeWithState(A,O):A;return n.setState(M),v&&f&&setTimeout(()=>f.completeStateChange(v),0),M}}async handleParentScopeSetState(e,n,a,i){const l=Fn(),c=window.G7Core,d=a==="replace"?"replace":a==="shallow"?"shallow":"deep",f=e.startsWith("$parent."),g=f?"$parent.":"$root.",m=e.slice(g.length);let y,S;if(m==="_local"||m==="_global")y=m,S=void 0;else if(m.startsWith("_local."))y="_local",S=m.slice(7);else if(m.startsWith("_global."))y="_global",S=m.slice(8);else{j.warn(`[handleParentScopeSetState] 지원하지 않는 타겟 형식: ${e}. _local 또는 _global으로 시작해야 합니다.`);return}const v=window.__g7LayoutContextStack||[];if(v.length===0){j.warn(`[handleParentScopeSetState] 레이아웃 컨텍스트 스택이 비어있습니다. target=${e}`);return}const _=f?v[v.length-1]:v[0];if(!_){j.warn(`[handleParentScopeSetState] 타겟 컨텍스트를 찾을 수 없습니다. target=${e}`);return}const A=y==="_global"?_.state?._global:_.state?._local,x=l?.isEnabled()?l.startStateChange(e,A,n,{actionId:i.actionId,handlerType:"setState",source:`target=${e}`}):void 0,{__mergeMode:O,__setStateId:M,...T}=n;if(y==="_global"){if(!this.globalStateUpdater){j.warn("[handleParentScopeSetState] globalStateUpdater가 설정되지 않았습니다."),x&&l&&l.completeStateChange(x);return}const D=c?.state?.get()||{};if(S){const z=T.value!==void 0?T.value:Object.values(T)[0],P=this.createNestedUpdate(S,z,D),q={...D,...P};j.log(`[handleParentScopeSetState] ${e}: 전역 상태 중첩 경로 업데이트`,q),this.globalStateUpdater(q)}else{const z=d==="deep"?this.deepMergeWithState(T,D):T;j.log(`[handleParentScopeSetState] ${e}: 전역 상태 업데이트 (mergeMode=${d})`,z),this.globalStateUpdater(d!=="deep"?{...z,__mergeMode:d}:z)}return x&&l&&setTimeout(()=>l.completeStateChange(x),0),{__target:e,...T}}else{if(!_.setState){j.warn(`[handleParentScopeSetState] 타겟 컨텍스트에 setState가 없습니다. target=${e}`),x&&l&&l.completeStateChange(x);return}const D=_.state||{};if(S){const z=T.value!==void 0?T.value:Object.values(T)[0],P=this.createNestedUpdate(S,z,D),q={...D,...P};return j.log(`[handleParentScopeSetState] ${e}: 로컬 상태 중첩 경로 업데이트`,P),_.setState(P),_.state=q,_.dataContext&&(_.dataContext._local=q),Gd(),x&&l&&setTimeout(()=>l.completeStateChange(x),0),q}else{const z=d!=="deep"?{...T,__mergeMode:d}:T,P=d==="replace"?T:d==="shallow"?{...D,...T}:this.deepMergeWithState(T,D);return j.log(`[handleParentScopeSetState] ${e}: 로컬 상태 업데이트 (mergeMode=${d})`,P),_.setState(z),_.state=P,_.dataContext&&(_.dataContext._local=P),Gd(),x&&l&&setTimeout(()=>l.completeStateChange(x),0),P}}}deepMergeWithState(e,n){const a=["errors"];let i=e;if("..."in e){const c=e["..."];if(c&&typeof c=="object"&&!Array.isArray(c)){const{"...":d,...f}=e;i={...c,...f}}else{const{"...":d,...f}=e;i=f}}const l={...n};for(const[c,d]of Object.entries(i)){if(a.includes(c)){l[c]=d;continue}if(c.includes(".")&&c!=="..."){const f=this.createNestedUpdate(c,d,l);this.deepMergeInto(l,f,l);continue}if(d!==null&&typeof d=="object"&&!Array.isArray(d)&&Object.getPrototypeOf(d)!==Object.prototype&&Object.getPrototypeOf(d)!==null){l[c]=d;continue}d!==null&&typeof d=="object"&&!Array.isArray(d)&&n[c]!==null&&typeof n[c]=="object"&&!Array.isArray(n[c])?l[c]=this.deepMergeWithState(d,n[c]):l[c]=d}return l}deepMergeInto(e,n,a){for(const[i,l]of Object.entries(n)){if(l===null){e[i]=l;continue}if(e[i]===null){e[i]=l;continue}typeof l=="object"&&!Array.isArray(l)&&e[i]!==void 0&&e[i]!==null&&typeof e[i]=="object"&&!Array.isArray(e[i])?(e[i]===a[i]&&(e[i]={...e[i]}),this.deepMergeInto(e[i],l,a[i]||{})):typeof l=="object"&&!Array.isArray(l)&&e[i]===void 0&&a[i]!==null&&typeof a[i]=="object"&&!Array.isArray(a[i])?e[i]={...a[i],...l}:e[i]=l}}createNestedUpdate(e,n,a){const i=e.split(".");if(i.length===1)return{[i[0]]:n};const l={};let c=l,d=a;for(let f=0;f{this.globalStateUpdater({_local:v})};m.push({state:y,setState:S,dataContext:{_local:y||n.data?._local,_global:l?n.data?._global:n.data?._global||i?.state?.get?.(),_computed:n.data?._computed}}),window.__g7LayoutContextStack=m,j.log(`[handleOpenModal] 레이아웃 컨텍스트 스택에 push, 스택 크기: ${m.length}, state:`,y,l?"(direct)":"(fallback via G7Core)")}else j.warn(`[handleOpenModal] 컨텍스트 스택에 push 실패 - setState: ${!!n.setState}, state: ${n.state!==void 0}, G7Core: ${!!i?.state?.getLocal}`);const f=[...a,e];this.globalStateUpdater({modalStack:f,activeModal:e});const g=Fn();if(g?.isEnabled()){const m=a.length>0?a[a.length-1]:void 0;g.trackModalOpen?.({modalId:e,modalName:e,scopeType:"isolated",parentModalId:m,initialState:n.state?{...n.state}:{}})}}async handleCloseModal(e){if(!this.globalStateUpdater){j.warn("Global state updater is not set for closeModal");return}const n=e?.data?._global?.modalStack||[],a=n.length>0?n[n.length-1]:null,i=n.slice(0,-1),l=i.length>0?i[i.length-1]:null,c=window.__g7LayoutContextStack||[];if(c.length>0){const d=c[c.length-1];d&&(d.dataContext=void 0),c.pop(),window.__g7LayoutContextStack=c,j.log(`[handleCloseModal] 레이아웃 컨텍스트 스택에서 pop, 스택 크기: ${c.length}`)}if(a){const d=Fn();d?.isEnabled()&&d.trackModalClose?.(a,e?.state)}this.globalStateUpdater({modalStack:i,activeModal:l})}async handleShowAlert(e,n){let a=e;this.translationEngine&&this.translationContext&&e.startsWith("$t:")&&(a=this.translationEngine.resolveTranslations(e,this.translationContext,n.data)),alert(a)}async handleToast(e,n){const{type:a="info",message:i,icon:l,duration:c}=e;if(a==="error"){const f=n.data?.error;if((f?.error_code==="identity_verification_required"||f?.data?.error_code==="identity_verification_required")&&Et.consumeDomainNoticeShown()){j.log("[Toast] IDV 도메인 안내 표출됨 — 중복 가드 토스트 1건 skip");return}}let d=i;if(this.translationEngine&&this.translationContext&&i?.startsWith("$t:")&&(d=this.translationEngine.resolveTranslations(i,this.translationContext,n.data)),this.globalStateUpdater){const g={id:`toast_${Date.now()}_${Math.random().toString(36).substring(2,9)}`,type:a,message:d,...l&&{icon:l},...c&&{duration:c}},v=[...window.G7Core?.state?.get()?.toasts||[],g];this.globalStateUpdater({toasts:v})}else j.log(`[Toast ${a}] ${d}`)}async handleSwitch(e,n){const{cases:a}=e;if(!a){j.warn("switch handler requires cases property");return}const i=this.resolveParams(e.params,n.data);let l;if(i?.value!==void 0?(l=i.value,j.log("switch handler: using params.value as case key:",l)):(l=n.data?.$args?.[0],j.log("switch handler: using $args[0] as case key:",l)),l==null){if(a.default)return j.log("switch handler: case key is undefined, using default case"),await this.executeAction(a.default,n);j.warn("switch handler: case key is not defined and no default case");return}const c=String(l);let d=a[c];if(!d&&a.default&&(j.log(`switch handler: no case found for key "${c}", using default case`),d=a.default),!d){j.warn(`switch handler: no case found for key "${c}" and no default case`);return}return await this.executeAction(d,n)}async handleConditions(e,n){const{conditions:a}=e;if(!a||!Array.isArray(a)){j.warn("conditions handler requires conditions array");return}const i=n.data||{},l=Sp(a,i,this.bindingEngine);if(!l.matched){j.log("conditions handler: no matching branch found");return}const c=a[l.branchIndex];if(j.log(`conditions handler: matched branch ${l.branchIndex}`),!c.then){j.warn('conditions handler: matched branch has no "then" action');return}return Array.isArray(c.then)?await this.handleSequence({...e,actions:c.then},n):await this.executeAction(c.then,n)}handleStartInterval(e,n){const a=typeof e.id=="string"?e.id:"",i=Number(e.intervalMs??1e3),l=Array.isArray(e.actions)?e.actions:[];if(a===""||!l.length||i<=0)return j.warn("startInterval requires non-empty id, positive intervalMs, and actions array"),{success:!1};const c=this.intervals.get(a);c&&(clearInterval(c),this.intervals.delete(a));const d=setInterval(()=>{for(const f of l)this.executeAction(f,n).catch(g=>{j.error(`[startInterval:${a}] tick action failed`,g)})},i);return this.intervals.set(a,d),{success:!0,id:a}}handleStopInterval(e){const n=typeof e.id=="string"?e.id:"";if(n==="")return j.warn("stopInterval requires id parameter"),{success:!1};const a=this.intervals.get(n);return a&&(clearInterval(a),this.intervals.delete(n)),{success:!0,id:n}}stopAllIntervals(){for(const e of this.intervals.values())clearInterval(e);this.intervals.clear()}async handleSequence(e,n){const a=e.actions||e.params?.actions;if(!a||!Array.isArray(a)||a.length===0)return j.warn("sequence handler requires non-empty actions array"),[];const i=Fn(),l=i?.isEnabled()&&i.startSequenceExecution?.({eventType:e.type})||"";window.__g7SequenceLocalSync=void 0;const c=[];let d,f=n.state?{...n.state}:{},g=n.isolatedContext?.state?{...n.isolatedContext.state}:{},m=n.data?._computed||{};const y=window.G7Core,S=()=>({_global:y?.state?.get()||{},_local:{...f},_isolated:Object.keys(g).length>0?{...g}:void 0});for(let v=0;v0){const T={},D={...n.data,_local:f,_computed:T,$computed:T,_isolated:g};for(const[z,P]of Object.entries(M))if(typeof P=="string"){const q=P.trim();if(q.startsWith("{{")&&q.endsWith("}}"))try{const W=q.slice(2,-2).trim();T[z]=Vn(W)?this.bindingEngine.evaluatePipeExpression(W,D,{skipCache:!0}):this.bindingEngine.evaluateExpression(W,D,{skipCache:!0})}catch{T[z]=m[z]}}m=T,j.log("[handleSequence] _computed recalculated after setState:",m)}}if(_.handler!=="setState"){const M=window.__g7SequenceLocalSync;if(M&&M!==f){f=M,window.__g7SequenceLocalSync=void 0,j.log("[handleSequence] currentState synchronized from __g7SequenceLocalSync after custom handler:",_.handler);const T=n.data?._computedDefinitions;if(T&&Object.keys(T).length>0){const D={},z={...n.data,_local:f,_computed:D,$computed:D,_isolated:g};for(const[P,q]of Object.entries(T))if(typeof q=="string"){const W=q.trim();if(W.startsWith("{{")&&W.endsWith("}}"))try{const he=W.slice(2,-2).trim();D[P]=Vn(he)?this.bindingEngine.evaluatePipeExpression(he,z,{skipCache:!0}):this.bindingEngine.evaluateExpression(he,z,{skipCache:!0})}catch{D[P]=m[P]}}m=D,j.log("[handleSequence] _computed recalculated after custom handler:",m)}}}l&&i?.isEnabled()&&i.captureSequenceActionAfter?.(l,v,S(),Date.now()-A,O.data)}catch(O){throw l&&i?.isEnabled()&&(i.captureSequenceActionAfter?.(l,v,S(),Date.now()-A,void 0,O),i.endSequenceExecution?.(l,O)),j.error(`sequence handler: action[${v}] failed:`,O),O}}return window.__g7SequenceLocalSync=void 0,l&&i?.isEnabled()&&i.endSequenceExecution?.(l),c}async handleParallel(e,n){const a=e.actions||e.params?.actions;if(!a||!Array.isArray(a)||a.length===0)return j.warn("parallel handler requires non-empty actions array"),[];j.log(`parallel handler: executing ${a.length} actions in parallel`);const i=n.isolatedContext?{...n.isolatedContext,state:{...n.isolatedContext.state}}:null,l=a.map((f,g)=>{const m={...n,isolatedContext:i};return this.executeAction(f,m).catch(y=>{throw j.error(`parallel handler: action[${g}] failed:`,y),y})}),c=await Promise.allSettled(l),d=c.filter(f=>f.status==="rejected").length;return d>0&&j.warn(`parallel handler: ${d}/${a.length} actions failed`),c}async handleLoadScript(e,n,a){const{src:i,id:l,async:c=!0,defer:d=!1}=e;if(!i)throw new Bt('loadScript handler requires "src" parameter',n);const f=l||`script_${i.replace(/[^a-zA-Z0-9]/g,"_")}`;return bs.loadedScripts.has(f)?(j.log(`loadScript: script already loaded, skipping: ${f}`),n.onLoad&&await this.executeAction(n.onLoad,a),!0):document.getElementById(f)?(j.log(`loadScript: script element already exists: ${f}`),bs.loadedScripts.add(f),n.onLoad&&await this.executeAction(n.onLoad,a),!0):(j.log(`loadScript: loading script: ${i}`),new Promise((m,y)=>{const S=document.createElement("script");S.id=f,S.src=i,S.async=c,S.defer=d,S.onload=async()=>{if(j.log(`loadScript: script loaded successfully: ${f}`),bs.loadedScripts.add(f),n.onLoad)try{await this.executeAction(n.onLoad,a)}catch(v){j.error("loadScript: onLoad action failed:",v)}m(!0)},S.onerror=v=>{j.error(`loadScript: failed to load script: ${i}`,v),y(new Bt(`Failed to load script: ${i}`,n))},document.head.appendChild(S)}))}async handleCallExternal(e,n,a){const i=e.constructor,l=e.args||{},c=e.method,d=e.methodArgs||[],f=e.callbackEvent,g=e.embedTarget,m=e.callbackSetState,y=e.callbackAction;if(!i)throw new Bt('callExternal handler requires "constructor" parameter',n);const S=this.getNestedProperty(window,i);if(!S||typeof S!="function")throw new Bt(`Constructor not found or not a function: ${i}. Make sure the script is loaded first using loadScript handler.`,n);j.log(`callExternal: calling constructor ${i}`);const v={...l};for(const[A,x]of Object.entries(l))x===!0&&(v[A]=O=>{if(j.log(`callExternal: callback triggered for ${A}`,O),f&&typeof window<"u"&&window.G7Core?.componentEvent&&window.G7Core.componentEvent.emit(f,O),m&&a.setState){const M=z=>{const P={};for(const[q,W]of Object.entries(z))typeof W=="string"?P[q]=this.getNestedProperty(O,W):typeof W=="object"&&W!==null&&(P[q]=M(W));return P},T=M(m);j.log("callExternal: callbackSetState mapping result",T);const D=this.deepMergeWithState(T,a.state||{});j.log("callExternal: merged with existing state",D),a.setState(D)}else f&&a.setState&&a.setState({[`${f.replace(/:/g,"_")}_result`]:O});if(y){const M={...a,data:{...a.data,$event:O}},T=Array.isArray(y)?y:[y];for(const D of T)try{this.executeAction(D,M)}catch(z){j.error("callExternal: callbackAction failed:",z)}}});const _=new S(v);if(c&&typeof _[c]=="function"){if(j.log(`callExternal: calling method ${c}`),c==="embed"&&g){const A=document.querySelector(g);if(A)return _[c](A,...d);throw new Bt(`Embed target element not found: ${g}`,n)}return _[c](...d)}return _}async handleCallExternalEmbed(e,n,a){const i=e.constructor,l=e.args||{},c=e.callbackSetState,d=e.callbackEvent,f=e.layerClassName,g=e.callbackAction;if(!i)throw new Bt('callExternalEmbed handler requires "constructor" parameter',n);const m=this.getNestedProperty(window,i);if(!m||typeof m!="function")throw new Bt(`Constructor not found or not a function: ${i}. Make sure the script is loaded first.`,n);j.log(`callExternalEmbed: creating layer for ${i}`);const{layer:y,closeLayer:S}=this.createEmbedLayer(f),v={...l};for(const[A,x]of Object.entries(l))x===!0&&(v[A]=O=>{if(j.log(`callExternalEmbed: callback triggered for ${A}`,O),S(),d&&typeof window<"u"&&window.G7Core?.componentEvent&&window.G7Core.componentEvent.emit(d,O),c&&a.setState){const M=z=>{const P={};for(const[q,W]of Object.entries(z))typeof W=="string"?P[q]=this.getNestedProperty(O,W):typeof W=="object"&&W!==null&&(P[q]=M(W));return P},T=M(c);j.log("callExternalEmbed: callbackSetState mapping result",T);const D=this.deepMergeWithState(T,a.state||{});j.log("callExternalEmbed: merged with existing state",D),a.setState(D)}if(g){const M={...a,data:{...a.data,$event:O}},T=Array.isArray(g)?g:[g];for(const D of T)try{this.executeAction(D,M)}catch(z){j.error("callExternalEmbed: callbackAction failed:",z)}}});const _=new m(v);if(typeof _.embed=="function")_.embed(y),j.log("callExternalEmbed: embedded in layer");else throw S(),new Bt(`Constructor ${i} does not have embed method`,n);return _}isImeComposing(e){return e?.isComposing===!0||e?.keyCode===229}createEmbedLayer(e){const n=document.createElement("div");n.id="g7-embed-overlay",n.style.cssText=` +`+l)}}catch{}}throw a}}_request(e,n){typeof e=="string"?(n=n||{},n.url=e):n=e||{},n=pi(this.defaults,n);const{transitional:a,paramsSerializer:i,headers:l}=n;a!==void 0&&Yl.assertOptions(a,{silentJSONParsing:nr.transitional(nr.boolean),forcedJSONParsing:nr.transitional(nr.boolean),clarifyTimeoutError:nr.transitional(nr.boolean),legacyInterceptorReqResOrdering:nr.transitional(nr.boolean)},!1),i!=null&&(W.isFunction(i)?n.paramsSerializer={serialize:i}:Yl.assertOptions(i,{encode:nr.function,serialize:nr.function},!0)),n.allowAbsoluteUrls!==void 0||(this.defaults.allowAbsoluteUrls!==void 0?n.allowAbsoluteUrls=this.defaults.allowAbsoluteUrls:n.allowAbsoluteUrls=!0),Yl.assertOptions(n,{baseUrl:nr.spelling("baseURL"),withXsrfToken:nr.spelling("withXSRFToken")},!0),n.method=(n.method||this.defaults.method||"get").toLowerCase();let c=l&&W.merge(l.common,l[n.method]);l&&W.forEach(["delete","get","head","post","put","patch","query","common"],_=>{delete l[_]}),n.headers=Tn.concat(c,l);const d=[];let f=!0;this.interceptors.request.forEach(function(A){if(typeof A.runWhen=="function"&&A.runWhen(n)===!1)return;f=f&&A.synchronous;const x=n.transitional||xd;x&&x.legacyInterceptorReqResOrdering?d.unshift(A.fulfilled,A.rejected):d.push(A.fulfilled,A.rejected)});const g=[];this.interceptors.response.forEach(function(A){g.push(A.fulfilled,A.rejected)});let m,b=0,S;if(!f){const _=[em.bind(this),void 0];for(_.unshift(...d),_.push(...g),S=_.length,m=Promise.resolve(n);b{if(!a._listeners)return;let l=a._listeners.length;for(;l-- >0;)a._listeners[l](i);a._listeners=null}),this.promise.then=i=>{let l;const c=new Promise(d=>{a.subscribe(d),l=d}).then(i);return c.cancel=function(){a.unsubscribe(l)},c},e(function(l,c,d){a.reason||(a.reason=new fo(l,c,d),n(a.reason))})}throwIfRequested(){if(this.reason)throw this.reason}subscribe(e){if(this.reason){e(this.reason);return}this._listeners?this._listeners.push(e):this._listeners=[e]}unsubscribe(e){if(!this._listeners)return;const n=this._listeners.indexOf(e);n!==-1&&this._listeners.splice(n,1)}toAbortSignal(){const e=new AbortController,n=a=>{e.abort(a)};return this.subscribe(n),e.signal.unsubscribe=()=>this.unsubscribe(n),e.signal}static source(){let e;return{token:new jS(function(i){e=i}),cancel:e}}};function J0(o){return function(n){return o.apply(null,n)}}function Q0(o){return W.isObject(o)&&o.isAxiosError===!0}const Md={Continue:100,SwitchingProtocols:101,Processing:102,EarlyHints:103,Ok:200,Created:201,Accepted:202,NonAuthoritativeInformation:203,NoContent:204,ResetContent:205,PartialContent:206,MultiStatus:207,AlreadyReported:208,ImUsed:226,MultipleChoices:300,MovedPermanently:301,Found:302,SeeOther:303,NotModified:304,UseProxy:305,Unused:306,TemporaryRedirect:307,PermanentRedirect:308,BadRequest:400,Unauthorized:401,PaymentRequired:402,Forbidden:403,NotFound:404,MethodNotAllowed:405,NotAcceptable:406,ProxyAuthenticationRequired:407,RequestTimeout:408,Conflict:409,Gone:410,LengthRequired:411,PreconditionFailed:412,PayloadTooLarge:413,UriTooLong:414,UnsupportedMediaType:415,RangeNotSatisfiable:416,ExpectationFailed:417,ImATeapot:418,MisdirectedRequest:421,UnprocessableEntity:422,Locked:423,FailedDependency:424,TooEarly:425,UpgradeRequired:426,PreconditionRequired:428,TooManyRequests:429,RequestHeaderFieldsTooLarge:431,UnavailableForLegalReasons:451,InternalServerError:500,NotImplemented:501,BadGateway:502,ServiceUnavailable:503,GatewayTimeout:504,HttpVersionNotSupported:505,VariantAlsoNegotiates:506,InsufficientStorage:507,LoopDetected:508,NotExtended:510,NetworkAuthenticationRequired:511,WebServerIsDown:521,ConnectionTimedOut:522,OriginIsUnreachable:523,TimeoutOccurred:524,SslHandshakeFailed:525,InvalidSslCertificate:526};Object.entries(Md).forEach(([o,e])=>{Md[e]=o});function nm(o){const e=new mi(o),n=Ep(mi.prototype.request,e);return W.extend(n,mi.prototype,e,{allOwnKeys:!0}),W.extend(n,e,null,{allOwnKeys:!0}),n.create=function(i){return nm(pi(o,i))},n}const Vt=nm(uo);Vt.Axios=mi,Vt.CanceledError=fo,Vt.CancelToken=X0,Vt.isCancel=Bp,Vt.VERSION=Dd,Vt.toFormData=Vl,Vt.AxiosError=Oe,Vt.Cancel=Vt.CanceledError,Vt.all=function(e){return Promise.all(e)},Vt.spread=J0,Vt.isAxiosError=Q0,Vt.mergeConfig=pi,Vt.AxiosHeaders=Tn,Vt.formToJSON=o=>Up(W.isHTMLForm(o)?new FormData(o):o),Vt.getAdapter=Zp.getAdapter,Vt.HttpStatusCode=Md,Vt.default=Vt;const{Axios:pT,AxiosError:mT,CanceledError:yT,isCancel:bT,CancelToken:vT,VERSION:ST,all:wT,Cancel:CT,isAxiosError:ET,spread:_T,toFormData:AT,AxiosHeaders:xT,HttpStatusCode:TT,formToJSON:RT,getAdapter:kT,mergeConfig:DT,create:OT}=Vt,$d="g7.identity.redirectStash",Xl=ut("IdentityGuardInterceptor");class vt{static setLauncher(e){this.launcher=e}static hasLauncher(){return this.launcher!==null}static markDomainNoticeShown(){this.domainNoticeShown=!0}static consumeDomainNoticeShown(){const e=this.domainNoticeShown;return this.domainNoticeShown=!1,e}static resolveDeferred(e){const n=this.deferredResolver;if(!n){Xl.warn("IdentityGuardInterceptor.resolveDeferred 호출됐지만 대기 중인 launcher 가 없습니다. 모달이 launcher 외부에서 열렸는지 확인하세요.");return}this.deferredResolver=null,n(e)}static createDeferred(){if(this.deferredResolver){const e=this.deferredResolver;this.deferredResolver=null,e({status:"cancelled"})}return new Promise(e=>{this.deferredResolver=e})}static isIdentityRequired(e,n){return e===428&&typeof n=="object"&&n!==null&&n.error_code==="identity_verification_required"}static async handle(e,n,a){this.domainNoticeShown=!1;const i=this.launcher??eC,c=!!(a&&(a.email||a.phone))?{...e.verification,target:a}:e.verification,d=await i(c);if(d.status!=="verified")return null;const f=c.return_request;if(!f)return null;const g=Z0(f.url,d.token);return fetch(g,{method:f.method,headers:n?.headers??{Accept:"application/json","Content-Type":"application/json"},body:n?.body,credentials:n?.credentials??"same-origin"})}static redirectExternally(e){const n=e.redirect_url;if(!n)return Xl.error("redirectExternally 호출 시 verification.redirect_url 이 없습니다. payload:",e),Promise.resolve({status:"failed",failureCode:"MISSING_REDIRECT_URL",reason:"verification.redirect_url is required for external_redirect flow"});if(typeof window<"u"){const a={return_url:window.location.href,payload:e,stashed_at:Date.now()};try{window.sessionStorage?.setItem($d,JSON.stringify(a))}catch(i){Xl.warn("sessionStorage 접근 실패 — stash 없이 redirect 합니다.",i)}window.location.href=n}return new Promise(()=>{})}static reset(){if(this.deferredResolver){const e=this.deferredResolver;this.deferredResolver=null;try{e({status:"cancelled"})}catch{}}this.launcher=null,this.domainNoticeShown=!1}}$(vt,"launcher",null),$(vt,"deferredResolver",null),$(vt,"domainNoticeShown",!1);function Z0(o,e){if(!e)return o;try{const n=typeof window<"u"?window.location.origin:"http://localhost",a=new URL(o,n);return a.searchParams.set("verification_token",e),/^https?:\/\//i.test(o)?a.toString():`${a.pathname}${a.search}${a.hash}`}catch{const n=o.includes("?")?"&":"?";return`${o}${n}verification_token=${encodeURIComponent(e)}`}}const eC=async o=>{if(typeof window>"u")return{status:"cancelled"};if(o.render_hint==="external_redirect"||o.redirect_url)return vt.redirectExternally(o);const e=window.G7Core;if(!e?.dispatch)return console.error("[IdentityGuardInterceptor] defaultLauncher: G7Core 가 초기화되지 않아 본인인증 흐름을 시작할 수 없습니다."),{status:"failed",failureCode:"G7_NOT_READY"};try{await e.dispatch({handler:"toast",params:{message:"본인 확인이 필요합니다.",variant:"warning"}})}catch{}const n=window.location.href,a={return_url:n,payload:o,stashed_at:Date.now()};try{window.sessionStorage?.setItem($d,JSON.stringify(a))}catch{}const i=`/identity/challenge?return=${encodeURIComponent(n)}`;try{await e.dispatch({handler:"navigate",params:{path:i}})}catch(l){Xl.warn("navigate 실패 — window.location 으로 폴백",l),window.location.href=i}return new Promise(()=>{})},tC=ut("ApiClient");function Nd(){return typeof window<"u"&&window.__G7_DEVTOOLS__?window.__G7_DEVTOOLS__:null}class nC{constructor(e={}){$(this,"client");$(this,"config");$(this,"TOKEN_KEY","auth_token");this.config={baseURL:e.baseURL||"/api",timeout:e.timeout||3e4,onTokenExpired:e.onTokenExpired,onUnauthorized:e.onUnauthorized,onError:e.onError},this.client=Vt.create({baseURL:this.config.baseURL,timeout:this.config.timeout,headers:{"Content-Type":"application/json",Accept:"application/json"},paramsSerializer:{serialize:n=>{const a=[];for(const[i,l]of Object.entries(n))if(l!=null)if(Array.isArray(l)){const c=i.endsWith("[]")?i:`${i}[]`;for(const d of l)a.push(`${encodeURIComponent(c)}=${encodeURIComponent(String(d))}`)}else a.push(`${encodeURIComponent(i)}=${encodeURIComponent(String(l))}`);return a.join("&")}}}),this.setupInterceptors()}setToken(e){typeof window<"u"&&localStorage.setItem(this.TOKEN_KEY,e)}getToken(){return typeof window<"u"?localStorage.getItem(this.TOKEN_KEY):null}removeToken(){typeof window<"u"&&localStorage.removeItem(this.TOKEN_KEY)}setupInterceptors(){this.client.interceptors.request.use(e=>{e.url?.startsWith("/api")&&e.baseURL==="/api"&&(e.baseURL="");const n=this.getToken();if(n&&e.headers&&(e.headers.Authorization=`Bearer ${n}`),typeof window<"u"&&e.headers){const i=localStorage.getItem("g7_locale");i&&(e.headers["Accept-Language"]=i)}const a=Nd();if(a?.isEnabled()){const i=this.buildFullUrl(e),l=(e.method||"GET").toUpperCase(),c=a.trackRequest(i,l,{requestBody:e.data});e._devToolsRequestId=c}return e},e=>Promise.reject(e)),this.client.interceptors.response.use(e=>{const n=e.config,a=n._devToolsRequestId;if(a){const i=Nd();i?.isEnabled()&&i.completeRequest(a,e.status,e.data),n._devToolsRequestId=null}return e},async e=>{const n=e.config,a=n?.url||"",l=["/auth/","/layouts/"].some(c=>a.includes(c));if(e.response?.status===401&&!n?._retry&&!l){if(n._retry=!0,await yr.getInstance().refreshToken()){const f=this.getToken();return f&&(n.headers.Authorization=`Bearer ${f}`),n._devToolsRequestId=null,this.client(n)}return this.completeDevToolsRequest(n,e),this.removeToken(),this.config.onUnauthorized&&this.config.onUnauthorized(),Promise.reject(e)}if(e.response?.status===401&&l)return this.completeDevToolsRequest(n,e),Promise.reject(e);if(vt.isIdentityRequired(e.response?.status,e.response?.data)){this.completeDevToolsRequest(n,e);const c={},d=n?.headers??{};for(const[S,v]of Object.entries(d))typeof v=="string"&&(c[S]=v);let f;n?.data!==void 0&&n?.data!==null&&(f=typeof n.data=="string"?n.data:JSON.stringify(n.data),c["Content-Type"]=c["Content-Type"]??"application/json");const g=n?.identity_target,m=await vt.handle(e.response.data,{headers:c,body:f,credentials:"same-origin"},g);if(!m)return Promise.reject(e);let b=null;try{b=await m.json()}catch{b=null}return m.ok?{...e.response,status:m.status,data:b}:Promise.reject(Object.assign(new Error("replay failed"),{response:{status:m.status,data:b}}))}return e.response?.status===403?(this.completeDevToolsRequest(n,e),this.callOnError(e),Promise.reject(e)):(this.completeDevToolsRequest(n,e),e.response&&this.callOnError(e),Promise.reject(e))})}async get(e,n){return(await this.client.get(e,n)).data}async post(e,n,a){return(await this.client.post(e,n,a)).data}async put(e,n,a){return(await this.client.put(e,n,a)).data}async patch(e,n,a){return(await this.client.patch(e,n,a)).data}async delete(e,n){return(await this.client.delete(e,n)).data}getInstance(){return this.client}setOnUnauthorized(e){this.config.onUnauthorized=e}setOnError(e){this.config.onError=e}completeDevToolsRequest(e,n){if(!e?._devToolsRequestId)return;const a=Nd();if(!a?.isEnabled())return;const i=e._devToolsRequestId;e._devToolsRequestId=null,n.response?a.completeRequest(i,n.response.status,n.response.data):n.request?a.failRequest(i,n.message||"Network error"):a.failRequest(i,n.message||"Request error")}buildFullUrl(e){let n=e.url||"";if(e.baseURL&&!n.startsWith("http")&&(n=`${e.baseURL}${n.startsWith("/")?"":"/"}${n}`),e.params&&typeof e.params=="object"){const a=e.params,i=[];for(const[l,c]of Object.entries(a))if(c!=null)if(Array.isArray(c)){const d=l.endsWith("[]")?l:`${l}[]`;for(const f of c)i.push(`${encodeURIComponent(d)}=${encodeURIComponent(String(f))}`)}else i.push(`${encodeURIComponent(l)}=${encodeURIComponent(String(c))}`);i.length>0&&(n+=(n.includes("?")?"&":"?")+i.join("&"))}return n}callOnError(e){if(!this.config.onError)return;const n={status:e.response?.status||0,message:e.response?.data?.message||e.message||"Unknown error",data:e.response?.data,statusText:e.response?.statusText};try{this.config.onError(n)}catch(a){tC.error("Error in onError handler:",a)}}}let Id=null;function Ir(){return Id||(Id=new nC),Id}const ho=ut("AuthManager");function ss(o,e,n,a){try{window.G7Core?.devTools?.trackAuthEvent?.(o,e,n,a)}catch{}}const Jl={admin:{type:"admin",loginPath:"/admin/login",defaultPath:"/admin",userEndpoint:"/admin/auth/user",loginEndpoint:"/auth/admin/login",logoutEndpoint:"/admin/auth/logout",refreshEndpoint:"/admin/auth/refresh"},user:{type:"user",loginPath:"/login",defaultPath:"/",userEndpoint:"/auth/user",loginEndpoint:"/auth/login",logoutEndpoint:"/auth/logout",refreshEndpoint:"/auth/refresh"}},_r=class _r{constructor(){$(this,"state");$(this,"config");$(this,"isRefreshing",!1);$(this,"refreshPromise",null);$(this,"eventHandlers",new Map);this.state={isAuthenticated:!1,user:null,type:null},this.config=new Map,this.config.set("admin",Jl.admin),this.config.set("user",Jl.user)}static getInstance(){return _r.instance||(_r.instance=new _r),_r.instance}on(e,n){this.eventHandlers.has(e)||this.eventHandlers.set(e,[]),this.eventHandlers.get(e).push(n)}emit(e,...n){const a=this.eventHandlers.get(e);a&&a.forEach(i=>i(...n))}isAuthenticated(){return this.state.isAuthenticated}getUser(){return this.state.user}getAuthType(){return this.state.type}async checkAuth(e){const n=Ir();if(!n.getToken())return this.clearState(),!1;const i=this.config.get(e);if(!i)return ho.error(`Unknown auth type: ${e}`),!1;try{const l=await n.get(i.userEndpoint);return l.success&&l.data?(this.state={isAuthenticated:!0,user:l.data,type:e},this.emit("authStateChange",this.state),!0):(this.clearState(),!1)}catch(l){return l.response?.status===401&&await this.refreshToken()?this.checkAuth(e):(this.clearState(),!1)}}async preloadAuth(e){try{return await this.checkAuth(e)}catch(n){return ho.warn(`Preload auth failed for type ${e}:`,n),!1}}async login(e,n,a){const i=Ir();if(!this.config.get(e))throw new Error(`Unknown auth type: ${e}`);const c=e==="admin"?Jl.admin.loginEndpoint:Jl.user.loginEndpoint;try{const d=a?.headers?{headers:a.headers}:void 0,f=await i.post(c,n,d);if(f.success&&f.data){i.setToken(f.data.token);const g=f.data.user.language,m=localStorage.getItem(_r.LOCALE_STORAGE_KEY),b=g&&g!==m;if(g)try{localStorage.setItem(_r.LOCALE_STORAGE_KEY,g)}catch(S){ho.warn("Failed to save user language to localStorage:",S)}return this.state={isAuthenticated:!0,user:f.data.user,type:e},this.emit("login",this.state),this.emit("authStateChange",this.state),ss("login",!0,void 0,{userId:f.data.user.uuid,email:f.data.user.email,type:e}),b&&window.__templateApp&&window.__templateApp.changeLocale(g),f.data.user}throw new Error("Login failed")}catch(d){this.clearState();const f=d.response?.data?.message,g=new Error(f||d.message||"Login failed");throw g.response=d.response,g.status=d.response?.status,ss("login",!1,g.message,{type:e,status:d.response?.status}),g}}async logout(){const e=Ir(),n=this.state.type?this.config.get(this.state.type):null;try{n&&await e.post(n.logoutEndpoint)}catch(a){ho.warn("Logout API call failed:",a)}finally{e.removeToken();const a={...this.state};if(this.clearState(),this.emit("logout",a),this.emit("authStateChange",this.state),ss("logout",!0,void 0,{previousType:a.type}),n&&a.type){const i=window.location.pathname+window.location.search;window.location.href=this.getLoginRedirectUrl(a.type,i)}}}async refreshToken(){if(this.isRefreshing&&this.refreshPromise)return this.refreshPromise;this.isRefreshing=!0,this.refreshPromise=this.doRefreshToken();try{return await this.refreshPromise}finally{this.isRefreshing=!1,this.refreshPromise=null}}async doRefreshToken(){const e=this.state.type;if(!e)return!1;const n=this.config.get(e);if(!n)return!1;const a=Ir();try{const i=await a.post(n.refreshEndpoint);return i.success&&i.data?.token?(a.setToken(i.data.token),this.emit("tokenRefreshed"),ss("token-refresh",!0,void 0,{type:e}),!0):(ss("token-refresh",!1,"No token in response",{type:e}),!1)}catch(i){return ho.error("Token refresh failed:",i),ss("token-refresh",!1,i instanceof Error?i.message:"Unknown error",{type:e}),!1}}getLoginRedirectUrl(e,n,a){const i=this.config.get(e);if(!i)return"/login";const l=encodeURIComponent(n);let c=`${i.loginPath}?redirect=${l}`;return a&&(c+=`&reason=${encodeURIComponent(a)}`),c}updateConfig(e,n){if(n.loginPath!==void 0){const i=n.loginPath;if(!i.startsWith("/")||i.startsWith("//"))throw new Error(`AuthManager.updateConfig: loginPath must be a same-origin path starting with '/' (got: ${i})`)}const a=this.config.get(e);a&&this.config.set(e,{...a,...n})}getRedirectUrl(e){const a=this.config.get(e)?.defaultPath||"/",l=new URLSearchParams(window.location.search).get("redirect");if(l)try{const c=decodeURIComponent(l);if(c.startsWith("/"))return c}catch{}return a}getConfig(e){return this.config.get(e)}clearState(){this.state={isAuthenticated:!1,user:null,type:null}}static resetInstance(){_r.instance=null}};$(_r,"instance"),$(_r,"LOCALE_STORAGE_KEY","g7_locale");let yr=_r;const rm={401:{handler:"navigate",params:{path:"{{auth.loginPath}}"}},403:{handler:"toast",params:{type:"error",message:"{{error.message}}"}},404:{handler:"toast",params:{type:"error",message:"{{error.message}}"}},422:{handler:"toast",params:{type:"error",message:"{{error.message}}"}},default:{handler:"toast",params:{type:"error",message:"{{error.message}}"}}},Qr={findHandler(o,e){if(o){if(o[e])return o[e];if(o[String(e)])return o[String(e)];if(o.default)return o.default}},normalizeOnError(o){return o?Array.isArray(o)?o:[o]:[]},injectErrorCode(o,e,n){if(o.handler!=="showErrorPage"||o.params?.errorCode!==void 0)return o;const a=typeof e=="string"?parseInt(e,10):e,i=isNaN(a)?n:a;return i===void 0?o:{...o,params:{...o.params,errorCode:i}}}},br=ut("ErrorHandlingResolver"),Ma=class Ma{constructor(){$(this,"templateErrorHandling",null);$(this,"layoutErrorHandling",null);$(this,"executeAction",null)}static getInstance(){return Ma.instance||(Ma.instance=new Ma),Ma.instance}static resetInstance(){Ma.instance=null}setTemplateConfig(e){this.templateErrorHandling=e,br.log("Template config set:",e)}setLayoutConfig(e){this.layoutErrorHandling=e,br.log("Layout config set:",e)}setActionExecutor(e){this.executeAction=e}resolve(e,n={}){const{errorHandling:a,onError:i}=n;br.log("Resolving error:",e,{hasActionErrorHandling:!!a,hasActionOnError:!!i,hasLayoutConfig:!!this.layoutErrorHandling,hasTemplateConfig:!!this.templateErrorHandling});let l=Qr.findHandler(a,e);if(l){const c=this.getMatchedKey(a,e);return{handler:Qr.injectErrorCode(l,c,e),level:"action",matchedKey:c}}if(i){const c=Qr.normalizeOnError(i);if(c.length>0)return{handler:c.length===1?c[0]:{handler:"sequence",actions:c},level:"action",matchedKey:"onError"}}if(l=Qr.findHandler(this.layoutErrorHandling||void 0,e),l){const c=this.getMatchedKey(this.layoutErrorHandling,e);return{handler:Qr.injectErrorCode(l,c,e),level:"layout",matchedKey:c}}if(l=Qr.findHandler(this.templateErrorHandling||void 0,e),l){const c=this.getMatchedKey(this.templateErrorHandling,e);return{handler:Qr.injectErrorCode(l,c,e),level:"template",matchedKey:c}}if(l=Qr.findHandler(rm,e),l){const c=this.getMatchedKey(rm,e);return{handler:Qr.injectErrorCode(l,c,e),level:"system",matchedKey:c}}return{handler:null,level:null,matchedKey:null}}async execute(e,n){if(!this.executeAction){br.error("Action executor is not set");return}br.log("Executing handler:",e.handler,{errorContext:n});try{return await this.executeAction(e,{error:n})}catch(a){throw br.error("Failed to execute handler:",a),a}}async resolveAndExecute(e,n,a={}){const i=this.resolve(e,a);if(br.log("Resolve result:",{errorCode:e,handler:i.handler?.handler,level:i.level,matchedKey:i.matchedKey}),!i.handler)return br.warn("No handler found for error:",e),{handled:!1};try{return{handled:!0,result:await this.execute(i.handler,n)}}catch(l){return br.error("Handler execution failed:",l),{handled:!1}}}getMatchedKey(e,n){return e[n]?n:e[String(n)]?String(n):"default"}clearLayoutConfig(){this.layoutErrorHandling=null,br.log("Layout config cleared")}clearAllConfig(){this.templateErrorHandling=null,this.layoutErrorHandling=null,br.log("All config cleared")}getConfigStatus(){return{hasTemplate:this.templateErrorHandling!==null,hasLayout:this.layoutErrorHandling!==null,hasExecutor:this.executeAction!==null}}};$(Ma,"instance",null);let Hd=Ma;function vr(){return Hd.getInstance()}var zd={exports:{}},go={};var am;function rC(){if(am)return go;am=1;var o=Symbol.for("react.transitional.element"),e=Symbol.for("react.fragment");function n(a,i,l){var c=null;if(l!==void 0&&(c=""+l),i.key!==void 0&&(c=""+i.key),"key"in i){l={};for(var d in i)d!=="key"&&(l[d]=i[d])}else l=i;return i=l.ref,{$$typeof:o,type:a,key:c,ref:i!==void 0?i:null,props:l}}return go.Fragment=e,go.jsx=n,go.jsxs=n,go}var im;function aC(){return im||(im=1,zd.exports=rC()),zd.exports}var dt=aC();const iC=Xr({__proto__:null,default:Jr(dt)},[dt]),jd=ut("ParentContextProvider"),sm=N.createContext(null);let Ql=null;function Ud(){Ql?(jd.log("[triggerModalParentUpdate] 모달 부모 컨텍스트 업데이트 트리거"),Ql()):jd.warn("[triggerModalParentUpdate] Provider가 아직 마운트되지 않음")}const om=({children:o})=>{const[e,n]=N.useState(0),a=N.useRef(!0),i=N.useCallback(()=>{const c=window.__g7LayoutContextStack||[];return c[c.length-1]?.dataContext},[]);N.useEffect(()=>(a.current=!0,Ql=()=>{a.current&&(n(c=>c+1),jd.log("[ParentContextProvider] 버전 업데이트됨"))},()=>{a.current=!1,Ql=null}),[]);const l={version:e,getParentDataContext:i};return dt.jsx(sm.Provider,{value:l,children:o})};function sC(){return N.useContext(sm)}const z=ut("ActionDispatcher");function oC(o,e,n,a){return n||(np(o)||od(o)?"$t:core.errors.network_request_failed":a||`Failed to execute action: ${e}`)}const lm=new Set(["navigate","navigateBack","navigateForward","replaceUrl","refresh","logout"]),lC=new Set(["redirect"]);function qn(){try{return window.G7Core?.devTools}catch{return}}class jt extends Error{constructor(n,a,i){super(n);$(this,"action");$(this,"originalError");$(this,"unknownHandler",!1);this.action=a,this.originalError=i,this.name="ActionError"}}const ps=class ps{constructor(e={},n,a){$(this,"bindingEngine");$(this,"translationEngine");$(this,"translationContext");$(this,"customHandlers",new Map);$(this,"defaultContext");$(this,"globalStateUpdater");$(this,"errorHandlingSetup",!1);$(this,"debounceTimers",new Map);$(this,"pendingDebounceFlushers",new Map);$(this,"debounceAccumulatedValues",new Map);$(this,"globalHeaders",[]);$(this,"namedActions",{});$(this,"previewMode",!1);$(this,"intervals",new Map);this.bindingEngine=new xn,this.translationEngine=n,this.translationContext=a,this.defaultContext=e,this.registerDefaultHandlers(),this.setupErrorHandling()}setGlobalHeaders(e){this.globalHeaders=e||[]}setNamedActions(e){this.namedActions=e||{};const n=qn();n?.isEnabled?.()&&n.setNamedActionDefinitions?.(this.namedActions),z.log("[setNamedActions] registered:",Object.keys(this.namedActions))}getNamedActions(){return this.namedActions}setPreviewMode(e){this.previewMode=e,z.log("Preview mode:",e?"enabled":"disabled")}isPreviewMode(){return this.previewMode}static getPreviewSuppressedHandlers(){return lm}static getPreviewSuppressedLayoutFeatures(){return lC}resolveActionRef(e){if(!e.actionRef)return e;const n=this.namedActions[e.actionRef];if(!n)return z.warn(`[resolveActionRef] named action not found: "${e.actionRef}"`),e;const a=qn();a?.isEnabled?.()&&a.trackNamedActionRef?.({actionRefName:e.actionRef,resolvedHandler:n.handler,timestamp:Date.now()});const{actionRef:i,type:l,key:c,event:d,...f}=e,g={...n,...Object.fromEntries(Object.entries(f).filter(([,m])=>m!==void 0)),type:l??n.type};return c!==void 0&&(g.key=c),d!==void 0&&(g.event=d),g}matchesPattern(e,n){if(n==="*")return!0;const a=n.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*");return new RegExp(`^${a}$`).test(e)}getMatchingGlobalHeaders(e,n){const a={};for(const i of this.globalHeaders)this.matchesPattern(e,i.pattern)&&Object.entries(i.headers).forEach(([l,c])=>{const d=ns(c,n,{skipCache:!0});d!=null&&d!==""&&(a[l]=String(d))});return a}setupErrorHandling(){if(this.errorHandlingSetup)return;vr().setActionExecutor(async(n,a)=>{const i={type:"click",handler:n.handler,target:n.target,params:n.params,actions:n.actions},l={data:{...this.defaultContext.data,error:a.error}};return await this.dispatchAction(i,l)}),this.errorHandlingSetup=!0}registerDefaultHandlers(){this.registerHandler("refetchDataSource",async(n,a)=>{const i=n.params?.dataSourceId,l=n.params?.sync;if(!i){z.warn("refetchDataSource: dataSourceId is required");return}if(typeof window<"u"&&window.G7Core?.dataSource?.refetch){const c=window.G7Core?.state?.get?.()||{},d={...a.state?._global,...c},f=window.G7Core?.state?.getLocal?.()||{},g=a.data?._local||a.state||{},m={...f,...g},b=a.isolatedContext?.state;z.log("[refetchDataSource] localStateOverride:",m),await window.G7Core.dataSource.refetch(i,{...l?{sync:!0}:{},...d?{globalStateOverride:d}:{},...m?{localStateOverride:m}:{},...b?{isolatedStateOverride:b}:{}})}else z.warn("refetchDataSource: G7Core.dataSource.refetch is not available")}),this.registerHandler("appendDataSource",async(n,a)=>{const{dataSourceId:i,dataPath:l,newData:c}=n.params||{};if(!i){z.warn("appendDataSource: dataSourceId is required");return}if(c===void 0){z.warn("appendDataSource: newData is required");return}if(typeof window<"u"&&window.G7Core?.dataSource?.updateData){if(!Array.isArray(c)){z.warn("appendDataSource: newData must be an array, got:",typeof c,c);return}await window.G7Core.dataSource.updateData(i,l||null,c,"append")}else z.warn("appendDataSource: G7Core.dataSource.updateData is not available")}),this.registerHandler("updateDataSource",async(n,a)=>{const{dataSourceId:i,data:l,merge:c=!1}=n.params||{};if(!i){z.warn("updateDataSource: dataSourceId is required");return}if(l===void 0){z.warn("updateDataSource: data is required");return}typeof window<"u"&&window.G7Core?.dataSource?.set?(z.log(`[updateDataSource] Updating dataSource '${i}' with:`,l),window.G7Core.dataSource.set(i,l,{merge:c})):z.warn("updateDataSource: G7Core.dataSource.set is not available")}),this.registerHandler("scrollIntoView",async(n,a)=>{const{selector:i,behavior:l="smooth",block:c="nearest",inline:d="nearest",waitForElement:f=!1,timeout:g=2e3,delay:m=0,retryCount:b=0,retryInterval:S=50,scrollContainer:v}=n.params||{};if(!i){z.warn("scrollIntoView: selector is required");return}if(typeof window>"u"||typeof document>"u"){z.warn("scrollIntoView: window/document is not available");return}let _=null;if(f){if(_=await new Promise(A=>{const x=document.querySelector(i);if(x){A(x);return}const L=setTimeout(()=>{M.disconnect(),A(null)},g),M=new MutationObserver(()=>{const R=document.querySelector(i);R&&(clearTimeout(L),M.disconnect(),A(R))});M.observe(document.body,{childList:!0,subtree:!0})}),!_){z.warn(`scrollIntoView: element not found for selector "${i}" after ${g}ms timeout`);return}}else{m>0&&await new Promise(x=>setTimeout(x,m)),_=document.querySelector(i);let A=0;for(;!_&&AsetTimeout(x,S)),_=document.querySelector(i),A++;if(!_){z.warn(`scrollIntoView: element not found for selector "${i}" after ${A+1} attempts`);return}}if(v){const A=document.querySelector(v);if(!A){z.warn(`scrollIntoView: container not found for selector "${v}"`);return}const x=_.getBoundingClientRect(),L=A.getBoundingClientRect(),M=x.top-L.top+A.scrollTop,R=M+x.height;let D;switch(c){case"start":D=M;break;case"center":D=M-A.clientHeight/2+x.height/2;break;case"end":D=R-A.clientHeight;break;default:const B=x.topL.bottom;if(B)D=M;else if(q)D=R-A.clientHeight;else return;break}A.scrollTo({top:Math.max(0,D),behavior:l});return}_.scrollIntoView({behavior:l,block:c,inline:d})}),this.registerHandler("reloadExtensions",async(n,a)=>{if(typeof window>"u"){z.warn("reloadExtensions: window is not available");return}const i=window.__templateApp;if(!i){z.warn("reloadExtensions: TemplateApp not initialized");return}if(typeof i.reloadExtensionState=="function")try{await i.reloadExtensionState()}catch(d){throw z.error("reloadExtensions: reloadExtensionState failed",d),d}else z.warn("reloadExtensions: TemplateApp.reloadExtensionState unavailable");const{moduleInfo:l,pluginInfo:c}=n.params||{};if(l)try{await this.executeAction({handler:"reloadModuleHandlers",params:n.params},a)}catch(d){z.error("reloadExtensions: reloadModuleHandlers failed",d)}if(c)try{await this.executeAction({handler:"reloadPluginHandlers",params:n.params},a)}catch(d){z.error("reloadExtensions: reloadPluginHandlers failed",d)}z.log("reloadExtensions: done")}),this.registerHandler("reloadRoutes",async(n,a)=>{if(typeof window>"u"){z.warn("reloadRoutes: window is not available");return}const i=window.__templateApp;if(!i){z.warn("reloadRoutes: TemplateApp not initialized");return}if(typeof i.reloadExtensionState=="function")try{await i.reloadExtensionState(),z.log("reloadRoutes: delegated to reloadExtensionState")}catch(l){throw z.error("reloadRoutes: Failed to reload routes",l),l}else{const l=i.getRouter?.();l&&(await l.loadRoutes(),z.log("reloadRoutes: Routes reloaded (legacy fallback)"))}}),this.registerHandler("refresh",async(n,a)=>{if(typeof window>"u"){z.warn("refresh: window is not available");return}const i=Number(n.params?.delayMs??0);i>0&&await new Promise(l=>setTimeout(l,i)),window.location.reload()}),this.registerHandler("remount",async(n,a)=>{const{componentId:i}=n.params||{};if(!i){z.warn("remount: componentId parameter is required");return}if(!this.globalStateUpdater){z.warn("remount: globalStateUpdater is not set");return}const d=(window.G7Core?.state?.get()||{})._global?._remountKeys||{},f=d[i]||0;this.globalStateUpdater({_remountKeys:{...d,[i]:f+1}}),z.log(`remount: ${i} key incremented to ${f+1}`)}),this.registerHandler("reloadTranslations",async(n,a)=>{if(typeof window>"u"){z.warn("reloadTranslations: window is not available");return}const i=window.__templateApp;if(!i){z.warn("reloadTranslations: TemplateApp not initialized");return}if(typeof i.reloadExtensionState=="function")try{await i.reloadExtensionState(),z.log("reloadTranslations: delegated to reloadExtensionState")}catch(l){throw z.error("reloadTranslations: Failed to reload translations",l),l}}),this.registerHandler("reloadModuleHandlers",async(n,a)=>{if(typeof window>"u"){z.warn("reloadModuleHandlers: window is not available");return}const{moduleInfo:i,action:l}=n.params||{};if(!i){z.warn("reloadModuleHandlers: moduleInfo is required");return}const c=i.data||i;if(!c.identifier){z.warn("reloadModuleHandlers: moduleInfo with identifier is required");return}if(!l||l!=="add"&&l!=="remove"){z.warn('reloadModuleHandlers: action must be "add" or "remove"');return}const d=window.G7Config;if(!d){z.warn("reloadModuleHandlers: G7Config not available");return}const f=c.identifier;try{if(l==="add"){if(c.assets&&(d.moduleAssets=d.moduleAssets||{},d.moduleAssets[f]=c.assets,z.log(`reloadModuleHandlers: Added assets for ${f}`),c.assets.js)){const g=c.assets.js,m=`module-${f}`;if(document.getElementById(m)){z.warn(`reloadModuleHandlers: Script ${m} already loaded`);return}const b=document.createElement("script");if(b.id=m,b.src=g,b.async=!0,await new Promise((S,v)=>{b.onload=()=>{z.log(`reloadModuleHandlers: Script loaded successfully for ${f}`),S()},b.onerror=()=>{z.error(`reloadModuleHandlers: Failed to load script for ${f}`),v(new Error(`Failed to load module script: ${g}`))},document.head.appendChild(b)}),c.assets.css){const S=c.assets.css,v=`module-css-${f}`;if(!document.getElementById(v)){const _=document.createElement("link");_.id=v,_.rel="stylesheet",_.href=S,document.head.appendChild(_),z.log(`reloadModuleHandlers: CSS loaded for ${f}`)}}}}else if(l==="remove"){d.moduleAssets&&d.moduleAssets[f]&&(delete d.moduleAssets[f],z.log(`reloadModuleHandlers: Removed assets for ${f}`));const g=`module-${f}`,m=document.getElementById(g);m&&(m.remove(),z.log(`reloadModuleHandlers: Removed script for ${f}`));const b=`module-css-${f}`,S=document.getElementById(b);S&&(S.remove(),z.log(`reloadModuleHandlers: Removed CSS for ${f}`))}}catch(g){throw z.error(`reloadModuleHandlers: Failed to ${l} module assets`,g),g}}),this.registerHandler("reloadPluginHandlers",async(n,a)=>{if(typeof window>"u"){z.warn("reloadPluginHandlers: window is not available");return}const{pluginInfo:i,action:l}=n.params||{};if(!i){z.warn("reloadPluginHandlers: pluginInfo is required");return}const c=i.data||i;if(!c.identifier){z.warn("reloadPluginHandlers: pluginInfo with identifier is required");return}if(!l||l!=="add"&&l!=="remove"){z.warn('reloadPluginHandlers: action must be "add" or "remove"');return}const d=window.G7Config;if(!d){z.warn("reloadPluginHandlers: G7Config not available");return}const f=c.identifier;try{if(l==="add"){if(c.assets&&(d.pluginAssets=d.pluginAssets||{},d.pluginAssets[f]=c.assets,z.log(`reloadPluginHandlers: Added assets for ${f}`),c.assets.js)){const g=c.assets.js,m=`plugin-${f}`;if(document.getElementById(m)){z.warn(`reloadPluginHandlers: Script ${m} already loaded`);return}const b=document.createElement("script");if(b.id=m,b.src=g,b.async=!0,await new Promise((S,v)=>{b.onload=()=>{z.log(`reloadPluginHandlers: Script loaded successfully for ${f}`),S()},b.onerror=()=>{z.error(`reloadPluginHandlers: Failed to load script for ${f}`),v(new Error(`Failed to load plugin script: ${g}`))},document.head.appendChild(b)}),c.assets.css){const S=c.assets.css,v=`plugin-css-${f}`;if(!document.getElementById(v)){const _=document.createElement("link");_.id=v,_.rel="stylesheet",_.href=S,document.head.appendChild(_),z.log(`reloadPluginHandlers: CSS loaded for ${f}`)}}}}else if(l==="remove"){d.pluginAssets&&d.pluginAssets[f]&&(delete d.pluginAssets[f],z.log(`reloadPluginHandlers: Removed assets for ${f}`));const g=`plugin-${f}`,m=document.getElementById(g);m&&(m.remove(),z.log(`reloadPluginHandlers: Removed script for ${f}`));const b=`plugin-css-${f}`,S=document.getElementById(b);S&&(S.remove(),z.log(`reloadPluginHandlers: Removed CSS for ${f}`))}}catch(g){throw z.error(`reloadPluginHandlers: Failed to ${l} plugin assets`,g),g}});let e=!1;this.registerHandler("showErrorPage",async(n,a)=>{if(e){z.log("showErrorPage: Already active, skipping duplicate call");return}if(e=!0,typeof window>"u"){e=!1,z.warn("showErrorPage: window is not available");return}const i=window.__templateApp;if(!i){e=!1,z.warn("showErrorPage: TemplateApp not initialized");return}const l=i.getErrorPageHandler?.();if(!l){e=!1,z.warn("showErrorPage: ErrorPageHandler not available");return}const c=n.params?.errorCode||500,d=n.params?.target||"content";let f=n.params?.containerId;f||(f=d==="full"?"app":"main_content"),z.log("showErrorPage:",{errorCode:c,target:d,containerId:f});const g=(m,b=6e4)=>new Promise((S,v)=>{const _=document.getElementById(m);if(_){S(_);return}z.log(`showErrorPage: Waiting for container #${m}...`);let A;const x=new MutationObserver((L,M)=>{const R=document.getElementById(m);R&&(z.log(`showErrorPage: Container #${m} found`),M.disconnect(),clearTimeout(A),S(R))});x.observe(document.body,{childList:!0,subtree:!0}),A=setTimeout(()=>{x.disconnect(),v(new Error(`Container #${m} not found within ${b}ms`))},b)});try{await g(f);const m=vr(),b=m.layoutErrorHandling;m.clearLayoutConfig();try{await l.renderError(c,f)||z.warn(`showErrorPage: Failed to render error page for code ${c}`)}finally{m.setLayoutConfig(b)}}catch(m){throw z.error("showErrorPage: Error rendering error page:",m),m}finally{e=!1}}),this.registerHandler("emitEvent",async(n,a)=>{const i=n.params?.event,l=n.params?.data;if(!i){z.warn("emitEvent: event parameter is required");return}if(typeof window>"u"){z.warn("emitEvent: window is not available");return}const c=window.G7Core;if(!c?.componentEvent?.emit){z.warn("emitEvent: G7Core.componentEvent is not available");return}try{z.log(`emitEvent: Emitting "${i}"`,l);const d={...l,_context:{data:a.data,state:a.state}},f=await c.componentEvent.emit(i,d);if(!f||f.length===0?z.warn(`emitEvent: No listeners found for "${i}"`):z.log(`emitEvent: Event "${i}" completed with ${f.length} listener(s)`,f),this.globalStateUpdater){const g=(c?.state?.get()||{})._local||{},m={event:i,success:!0,data:f.length===1?f[0]:f,listeners:f.length};this.globalStateUpdater({_local:{...g,_eventResult:m}});const b=a.state&&typeof a.state=="object"&&!Array.isArray(a.state)?a.state:g;window.__g7SequenceLocalSync={...b,_eventResult:m}}}catch(d){if(z.error(`emitEvent: Event "${i}" failed`,d),this.globalStateUpdater){const f=c?.state?.get()||{};this.globalStateUpdater({_local:{...f._local,_eventResult:{event:i,success:!1,error:d instanceof Error?d.message:String(d)}}})}throw d}}),this.registerHandler("updateProductField",async(n,a)=>{const{productId:i,field:l,value:c}=n.params||{};if(!i||!l){z.warn("updateProductField: productId and field are required");return}if(typeof window>"u"){z.warn("updateProductField: window is not available");return}const d=window.G7Core;if(!d?.state?.get||!this.globalStateUpdater){z.warn("updateProductField: G7Core.state or globalStateUpdater is not available");return}try{const f=d.state.get()||{},m=(f.products?.data?.data||[]).map(S=>S.id===i?{...S,[l]:c,_modified:!0}:S),b=new Set(f._local?.modifiedProductIds||[]);b.add(i),this.globalStateUpdater({products:{...f.products,data:{...f.products?.data,data:m}},_local:{...f._local,modifiedProductIds:Array.from(b)}}),z.log(`updateProductField: Updated product ${i}, field: ${l}, value:`,c)}catch(f){throw z.error("updateProductField: Error updating product field",f),f}}),this.registerHandler("updateOptionField",async(n,a)=>{const{productId:i,optionId:l,field:c,value:d}=n.params||{};if(!i||!l||!c){z.warn("updateOptionField: productId, optionId and field are required");return}if(typeof window>"u"){z.warn("updateOptionField: window is not available");return}const f=window.G7Core;if(!f?.state?.get||!this.globalStateUpdater){z.warn("updateOptionField: G7Core.state or globalStateUpdater is not available");return}try{const g=f.state.get()||{},b=(g.products?.data?.data||[]).map(v=>{if(v.id===i&&v.options){const _=v.options.map(A=>A.id===l?{...A,[c]:d,_modified:!0}:A);return{...v,options:_,_modified:!0}}return v}),S=new Set(g._local?.modifiedProductIds||[]);S.add(i),this.globalStateUpdater({products:{...g.products,data:{...g.products?.data,data:b}},_local:{...g._local,modifiedProductIds:Array.from(S)}}),z.log(`updateOptionField: Updated product ${i} option ${l}, field: ${c}, value:`,d)}catch(g){throw z.error("updateOptionField: Error updating option field",g),g}}),this.registerHandler("setLocale",async(n,a)=>{const i=n.target;if(!i||typeof i!="string"){z.warn("setLocale: Invalid locale:",i);return}const l=window.__templateApp;if(l&&typeof l.changeLocale=="function")try{await l.changeLocale(i),z.log("setLocale: Locale changed to",i)}catch(c){z.error("setLocale: Failed to change locale:",c),window.location.reload()}else{z.warn("setLocale: TemplateApp not found, falling back to page reload");try{localStorage.setItem("g7_locale",i)}catch{}window.location.reload()}}),this.registerHandler("suppress",async()=>{z.log("suppress: Error intentionally suppressed")}),this.registerBuiltInHandlerMetadata()}registerBuiltInHandlerMetadata(){const e=qn();if(!e?.isEnabled())return;const n=[{name:"refetchDataSource",description:"데이터 소스를 다시 fetch합니다"},{name:"appendDataSource",description:"데이터 소스에 새 데이터를 병합합니다 (무한 스크롤용)"},{name:"updateDataSource",description:"API 응답으로 데이터 소스를 직접 업데이트합니다 (refetch 대체)"},{name:"reloadRoutes",description:"라우트를 다시 로드합니다"},{name:"refresh",description:"현재 페이지를 새로고침합니다"},{name:"remount",description:"컴포넌트를 리마운트합니다"},{name:"reloadTranslations",description:"다국어 파일을 다시 로드합니다"},{name:"showErrorPage",description:"에러 페이지를 표시합니다"},{name:"emitEvent",description:"이벤트를 발생시킵니다"},{name:"updateProductField",description:"상품 필드를 인라인 수정합니다"},{name:"updateOptionField",description:"상품 옵션 필드를 인라인 수정합니다"},{name:"setLocale",description:"언어를 변경합니다 (DB 저장 + UI 리렌더링)"}];for(const a of n)e.trackHandlerRegistration(a.name,"built-in",a.description)}createHandler(e,n,a){z.log("createHandler called for:",e.handler,e.type);const i=window.__g7DevTools,l=Date.now(),c=a?.state?{...a.state}:null,d=`handler_${e.handler}_${l}`;return async f=>{if(z.log("Handler invoked for:",e.handler,"event:",f.type),i?.isEnabled?.()&&c){const m=window.G7Core?.state?.getLocal?.()??a?.state??{},b=Date.now()-l;for(const S of Object.keys(c)){const v=c[S],_=m[S];v!==_&&b>100&&(i.trackStaleClosureWarning?.({type:"event-handler-stale",location:`createHandler(${e.handler})`,capturedPath:`_local.${S}`,capturedValue:v,capturedAt:l,currentValue:_,actionId:d,stackTrace:new Error().stack}),z.warn(`[Stale Closure] _local.${S} changed after handler creation:`,`captured="${v}" → current="${_}" (${b}ms ago)`))}}try{f.type!=="change"&&f.preventDefault();let g={};if(f.type==="submit"&&f.target instanceof HTMLFormElement){const S=f.target;new FormData(S).forEach((_,A)=>{g[A]=_})}const m={...this.defaultContext,data:{...n,form:g,_local:a?.state||{},$event:f},event:f,...a&&{state:a.state,setState:a.setState},isolatedContext:a?.isolatedContext};if(e.confirm){let S=this.resolveValue(e.confirm,m.data);if(this.translationEngine&&this.translationContext&&S.startsWith("$t:")&&(S=this.translationEngine.resolveTranslations(S,this.translationContext,m.data)),!confirm(S))return}const b=window.__g7ActionContext;window.__g7ActionContext={...a,data:m.data};try{await this.executeAction(e,m)}finally{window.__g7ActionContext=b}}catch(g){if(z.error("Action execution failed:",g),e.onError){const m=g instanceof jt&&g.originalError?g.originalError:g,b={...this.defaultContext,data:{...n,error:m,_local:a?.state||{},$event:f},event:f,...a&&{state:a.state,setState:a.setState}},S=Array.isArray(e.onError)?e.onError:[e.onError];for(const v of S)await this.executeAction(v,b)}}}}generateActionId(e,n){const a=e.handler,i=n||"no-target",l=Date.now(),c=Math.random().toString(36).substring(2,9);return`${a}_${i.replace(/[^a-zA-Z0-9]/g,"_")}_${l}_${c}`}async executeAction(e,n){if(e=this.resolveActionRef(e),typeof e.handler=="string"&&e.handler.includes("{{")){const f=this.evaluateExpression(e.handler,n.data);e={...e,handler:f==null?"":String(f)}}const a=qn(),i=a?.isEnabled()?`action_${Date.now()}_${Math.random().toString(36).substring(2,11)}`:void 0,l=performance.now();let c;if(i&&a&&a.logAction({id:i,type:e.handler,params:this.sanitizeForDevTools(e.params),context:this.sanitizeForDevTools({hasState:!!n.state,hasSetState:!!n.setState,dataKeys:n.data?Object.keys(n.data):[]}),startTime:l,status:"started"}),e.if!==void 0)try{const f=this.resolveValue(e.if,n.data),g=f===!0||f==="true"||f&&f!=="false"&&f!=="0"&&f!==!1;if(z.log("[executeAction] if condition result:",e.handler,g),!g)return i&&a&&a.logAction({id:i,type:e.handler,startTime:l,endTime:performance.now(),duration:performance.now()-l,status:"success",result:{skipped:!0,reason:"if condition false"}}),{success:!0,data:void 0}}catch(f){return z.error("[executeAction] if condition error:",e.handler,f),i&&a&&a.logAction({id:i,type:e.handler,startTime:l,endTime:performance.now(),duration:performance.now()-l,status:"error",error:{name:"ConditionEvaluationError",message:f instanceof Error?f.message:String(f),stack:f instanceof Error?f.stack:void 0}}),{success:!0,data:void 0}}let d;try{const f=this.resolveParams(e.params,n.data);i&&(c=this.sanitizeForDevTools(f));const g=e.target?this.resolveValue(e.target,n.data):void 0;if(e.handler==="apiCall"&&n.setState){d=this.generateActionId(e,g);const b=n.state?.loadingActions||{};n.setState({loadingActions:{...b,[d]:!0}})}if(this.previewMode&&lm.has(e.handler))return z.warn(`Preview mode: "${e.handler}" suppressed`,g||f),i&&a&&a.logAction({id:i,type:e.handler,startTime:l,endTime:performance.now(),duration:performance.now()-l,status:"skipped",metadata:{reason:"preview_mode_suppressed"}}),{success:!0,data:void 0};let m;switch(e.handler){case"ensureIdentityVerified":m=await this.handleEnsureIdentityVerified(f);break;case"resolveIdentityChallenge":m=this.handleResolveIdentityChallenge(f);break;case"navigate":const b=f.path||g;m=await this.handleNavigate(b,f,n);break;case"navigateBack":m=await this.handleNavigateBack();break;case"navigateForward":m=await this.handleNavigateForward();break;case"openWindow":{const _=f.path||g;m=await this.handleOpenWindow(_,f);break}case"replaceUrl":{const _=f.path||g||window.location.pathname;m=await this.handleReplaceUrl(_,f);break}case"apiCall":if(e.onSuccess&&a?.isEnabled()&&i){const _=window.G7Core,A={};if(_?.state?.get){const x=_.state.get();A._global=x}n.state&&(A._local=n.state),a.registerStateCaptureForHandler?.(i,["_global","_local"],A)}const S=e.auth_mode??(e.auth_required?"required":"none"),v=e.identity_target?this.resolveParams(e.identity_target,n.data):void 0;m=await this.handleApiCall(g,f,n,S,v);break;case"login":m=await this.handleLogin(g,f,n);break;case"logout":m=await this.handleLogout(g,n);break;case"setState":z.log("[executeAction] setState resolvedParams:",f),m=await this.handleSetState(e.render!==void 0?{...f,__render:e.render}:f,n);break;case"setError":m=await this.handleSetError(g,f,n);break;case"openModal":m=await this.handleOpenModal(g,n);break;case"closeModal":m=await this.handleCloseModal(n);break;case"showAlert":m=await this.handleShowAlert(g,n);break;case"toast":m=await this.handleToast(f,n);break;case"switch":m=await this.handleSwitch(e,n);break;case"conditions":m=await this.handleConditions(e,n);break;case"sequence":m=await this.handleSequence(e,n);break;case"parallel":m=await this.handleParallel(e,n);break;case"startInterval":m=this.handleStartInterval(f,n);break;case"stopInterval":m=this.handleStopInterval(f);break;case"loadScript":m=await this.handleLoadScript(f,e,n);break;case"callExternal":m=await this.handleCallExternal(f,e,n);break;case"callExternalEmbed":m=await this.handleCallExternalEmbed(f,e,n);break;case"saveToLocalStorage":m=await this.handleSaveToLocalStorage(f,n);break;case"loadFromLocalStorage":m=await this.handleLoadFromLocalStorage(f,n);break;default:m=await this.handleCustomAction({...e,target:g,params:f},n);break}if(e.resultTo&&m!==void 0){const{target:b,key:S,merge:v}=e.resultTo,_=this.resolveValue(S,n.data),A=v==="replace"?"replace":v==="shallow"?"shallow":"deep";if(b==="_local"&&n.setState){const x=this.buildNestedUpdate(_,m),L=A!=="deep"?{...x,__mergeMode:A}:x;n.setState(L),z.log(`[resultTo] Saved to _local.${_} (merge=${A}):`,m)}else if(b==="_local"&&this.globalStateUpdater){const M=(window.G7Core?.state?.get()||{})._local||{},R=this.buildNestedUpdate(_,m);let D;A==="replace"?D=R:A==="shallow"?D={...M,...R}:D=this.deepMergeWithState(R,M),this.globalStateUpdater({_local:D}),z.log(`[resultTo] Saved to _local.${_} via globalStateUpdater (merge=${A}):`,m)}else if(b==="_global"&&this.globalStateUpdater){const x=this.buildNestedUpdate(_,m);this.globalStateUpdater(x),z.log(`[resultTo] Saved to _global.${_}:`,m)}else if(b==="_isolated"&&n.isolatedContext){const x=this.buildNestedUpdate(_,m);n.isolatedContext.mergeState(x,A),z.log(`[resultTo] Saved to _isolated.${_} (merge=${A}):`,m)}else z.warn(`[resultTo] Cannot save result: target=${b}, setState=${!!n.setState}, globalStateUpdater=${!!this.globalStateUpdater}, isolatedContext=${!!n.isolatedContext}`)}if(e.onSuccess){if(a?.isEnabled()&&i&&e.handler==="apiCall"){const v=window.G7Core,_={};v?.state?.get&&(_._global=v.state.get()),n.state&&(_._local=n.state),a.detectStaleClosure?.(i,`${e.handler} → onSuccess`,_,"callback-state-capture",i)}const b={...n,data:{...n.data,result:m,response:m}},S=Array.isArray(e.onSuccess)?e.onSuccess:[e.onSuccess];S.length>1?await this.handleSequence({handler:"sequence",type:"click",actions:S},b):S.length===1&&await this.executeAction(S[0],b)}return i&&a&&a.logAction({id:i,type:e.handler,params:this.sanitizeForDevTools(e.params),resolvedParams:c,startTime:l,endTime:performance.now(),duration:performance.now()-l,status:"success",result:this.sanitizeForDevTools(m)}),{success:!0,data:m}}catch(f){const g=f instanceof jt?f:new jt(`Failed to execute action: ${e.handler}`,e,f instanceof Error?f:void 0),m=g.originalError?.response||{},b=m.data||{},S=g.originalError?.status||m.status||500;let v=oC(g.originalError??f,e.handler,b.message,g.message);this.translationEngine&&this.translationContext&&v.startsWith("$t:")&&(v=this.translationEngine.resolveTranslations(v,this.translationContext));const _={status:S,message:v,errors:b.errors||m.errors,data:b,statusText:g.originalError?.statusText,error_code:b.error_code??m.error_code};if(g.unknownHandler)throw z.warn(`Unknown action handler "${e.handler}" — skipped (extension not loaded?). Not surfaced to the user.`),g;const x=vr().resolve(S,{errorHandling:e.errorHandling,onError:e.onError});if(x.handler){if(a?.isEnabled()&&i&&e.handler==="apiCall"){const M=window.G7Core,R={};M?.state?.get&&(R._global=M.state.get()),n.state&&(R._local=n.state),a.detectStaleClosure?.(i,`${e.handler} → onError`,R,"callback-state-capture",i)}const L={...n,data:{...n.data,error:_}};try{const M={type:"click",handler:x.handler.handler,target:x.handler.target,params:x.handler.params,actions:x.handler.actions};return await this.executeAction(M,L),{success:!1,error:g}}catch(M){return z.error("Error executing error handler:",M),{success:!1,error:g}}}throw i&&a&&a.logAction({id:i,type:e.handler,params:this.sanitizeForDevTools(e.params),resolvedParams:c,startTime:l,endTime:performance.now(),duration:performance.now()-l,status:"error",error:{name:g.name,message:g.message,stack:g.stack}}),g}finally{if(e.handler==="apiCall"&&n.setState&&d){const f=n.state?.loadingActions||{},{[d]:g,...m}=f;n.setState({loadingActions:m})}}}async handleNavigate(e,n,a){let i=e;if(n.query||n.mergeQuery===!0)if(n.mergeQuery===!0)i=this.buildMergedQueryPath(e,n.query??{});else{const c=new URLSearchParams;for(const[f,g]of Object.entries(n.query))if(g!=null&&g!=="")if(Array.isArray(g)){const m=f.endsWith("[]")?f:`${f}[]`;for(const b of g)b!=null&&b!==""&&c.append(m,String(b))}else c.set(f,String(g));const d=c.toString();d&&(i=`${e}?${d}`)}if(z.log("handleNavigate:",{target:e,params:n,finalPath:i,replace:n.replace,windowLocationSearch:window.location.search}),n.replace===!0){const c=window.G7Core;if(c?.updateQueryParams){const d=typeof n.transition_overlay_target=="string"?n.transition_overlay_target:void 0;await c.updateQueryParams(i,d?{transitionOverlayTarget:d}:void 0),z.log("handleNavigate: Used updateQueryParams for replace mode",d?{transitionOverlayTarget:d}:void 0),this.applyScrollOption(n.scroll,n.scrollBehavior,"top");return}z.warn("handleNavigate: G7Core.updateQueryParams not available, falling back to React Router")}const l=this.resolveNavigateFallbackAction(i,n);if(l){z.warn(`handleNavigate: No route matched, falling back to "${l.handler}"`),await this.dispatchAction({type:"click",handler:l.handler,params:l.params},a),this.applyScrollOption(n.scroll,n.scrollBehavior,"top");return}if(!a.navigate)throw new jt("Navigate function is not provided in context");a.navigate(i,{replace:n.replace===!0}),this.applyScrollOption(n.scroll,n.scrollBehavior,"top")}resolveNavigateFallbackAction(e,n){const a=n.fallback;if(a===!1||n.replace===!0)return null;const l=window.__templateApp?.getRouter?.();if(!l||typeof l.match!="function"||typeof l.getRoutes=="function"&&l.getRoutes().length===0)return null;const c=e.split("?")[0];return l.match(c)?null:this.resolveNavigateFallback(a,e,n)}resolveNavigateFallback(e,n,a){return e==null?{handler:"openWindow",params:{path:n,target:"_self"}}:typeof e=="string"?{handler:e,params:{path:n}}:typeof e=="object"&&typeof e.handler=="string"?{handler:e.handler,params:{path:n,...e.params||{}}}:(z.warn("resolveNavigateFallback: unrecognized fallback option, using openWindow",e),{handler:"openWindow",params:{path:n,target:"_blank"}})}applyScrollOption(e,n,a){const i=e===void 0?a:e;if(i==="preserve")return;const l=n==="smooth"?"smooth":"instant",c=b=>{if(typeof b!="object"||b===null)return!1;const S=b;return"container"in S||"to"in S||"block"in S||"offset"in S},d=(b,S,v=0)=>{b===window?window.scrollTo({top:S,left:v,behavior:l}):b.scrollTo({top:S,left:v,behavior:l})},f=(b,S,v,_)=>{if(S===window){b.scrollIntoView({behavior:l,block:v}),_&&window.scrollBy({top:-_,left:0,behavior:l});return}const A=S,x=b.getBoundingClientRect(),L=A.getBoundingClientRect(),M=A.scrollTop+(x.top-L.top);let R;v==="center"?R=M-(A.clientHeight-b.clientHeight)/2:v==="end"?R=M-(A.clientHeight-b.clientHeight):R=M,R-=_,A.scrollTo({top:Math.max(0,R),left:0,behavior:l})},g=()=>{window.scrollTo({top:0,left:0,behavior:l});const b=document.getElementById("app")??document.body;if(!b)return;b.querySelectorAll("*").forEach(v=>{if(v.scrollTop===0&&v.scrollLeft===0)return;const _=window.getComputedStyle(v),A=_.overflowY,x=_.overflowX;(A==="auto"||A==="scroll"||x==="auto"||x==="scroll")&&v.scrollTo({top:0,left:0,behavior:l})})},m=()=>{try{if(c(i)){const b=i,S=b.block??"start",v=typeof b.offset=="number"?b.offset:0;let _=window;if(typeof b.container=="string"&&b.container.length>0){const x=document.querySelector(b.container);if(!x){z.warn(`applyScrollOption: container not found: ${b.container}`);return}_=x}const A=b.to??"top";if(A==="top"){_===window?g():d(_,0);return}if(typeof A=="number"){d(_,A-v);return}if(typeof A=="object"&&A!==null&&("x"in A||"y"in A)){const{x=0,y:L=0}=A;d(_,L-v,x);return}if(typeof A=="string"&&(A.startsWith("#")||A.startsWith("."))){const x=document.querySelector(A);x?f(x,_,S,v):z.warn(`applyScrollOption: target element not found: ${A}`);return}z.warn('applyScrollOption: invalid "to" value in extended form',A);return}if(i==="top"){g();return}if(typeof i=="number"){window.scrollTo({top:i,left:0,behavior:l});return}if(typeof i=="object"&&i!==null&&("x"in i||"y"in i)){const{x:b=0,y:S=0}=i;window.scrollTo({top:S,left:b,behavior:l});return}if(typeof i=="string"&&(i.startsWith("#")||i.startsWith("."))){const b=document.querySelector(i);b&&b.scrollIntoView({behavior:l,block:"start"});return}}catch(b){z.warn("applyScrollOption: failed to apply scroll",b)}};typeof window.requestAnimationFrame=="function"?window.requestAnimationFrame(m):m()}async handleOpenWindow(e,n){let a=e;if(n.query){const l=new URLSearchParams;for(const[d,f]of Object.entries(n.query))f!=null&&f!==""&&l.set(d,String(f));const c=l.toString();c&&(a=`${e}?${c}`)}const i=n.target==="_self"?"_self":"_blank";z.log("handleOpenWindow:",{target:e,params:n,finalPath:a,windowTarget:i}),i==="_self"?window.location.assign(a):window.open(a,"_blank")}async handleNavigateBack(){z.log("handleNavigateBack"),window.history.back()}async handleNavigateForward(){z.log("handleNavigateForward"),window.history.forward()}async handleReplaceUrl(e,n){let a=e;if(n.query||n.mergeQuery===!0)if(n.mergeQuery===!0)a=this.buildMergedQueryPath(e,n.query??{});else{const i=new URLSearchParams;for(const[c,d]of Object.entries(n.query))if(d!=null&&d!=="")if(Array.isArray(d)){const f=c.endsWith("[]")?c:`${c}[]`;for(const g of d)g!=null&&g!==""&&i.append(f,String(g))}else i.set(c,String(d));const l=i.toString();l&&(a=`${e}?${l}`)}z.log("handleReplaceUrl:",{target:e,params:n,finalPath:a}),window.history.replaceState(null,"",a),this.applyScrollOption(n.scroll,n.scrollBehavior,"preserve")}buildMergedQueryPath(e,n){const a=new URLSearchParams(window.location.search);for(const[c,d]of Object.entries(n))if(d==null||d==="")a.delete(c);else if(Array.isArray(d)){const f=c.endsWith("[]")?c:`${c}[]`;a.delete(c),a.delete(f);for(const g of d)g!=null&&g!==""&&a.append(f,String(g))}else a.set(c,String(d));const i=a.toString(),l=e.split("?")[0];return i?`${l}?${i}`:l}async ensureCsrfToken(){try{await fetch("/sanctum/csrf-cookie",{credentials:"include"})}catch(e){throw new jt("Failed to fetch CSRF token",void 0,e instanceof Error?e:void 0)}}getCsrfTokenFromCookie(){const a=`; ${document.cookie}`.split("; XSRF-TOKEN=");if(a.length===2){const i=a.pop()?.split(";").shift();return i?decodeURIComponent(i):null}return null}buildQueryString(e){const n=new URLSearchParams;for(const[a,i]of Object.entries(e))if(!(i==null||i===""))if(Array.isArray(i))for(const l of i)l!=null&&l!==""&&n.append(`${a}[]`,String(l));else typeof i=="object"?n.append(a,JSON.stringify(i)):n.append(a,String(i));return n.toString()}async handleEnsureIdentityVerified(e){const n=typeof e.purpose=="string"?e.purpose:"sensitive_action",a=e.target&&typeof e.target=="object"?e.target:void 0;return await vt.handle({success:!1,error_code:"identity_verification_required",message:"",verification:{policy_key:e.policy_key??"",purpose:n,provider_id:e.provider_id??null,render_hint:e.render_hint??null,return_request:null}},void 0,a)!==null}handleResolveIdentityChallenge(e){const n=typeof e.result=="string"?e.result:"cancelled";let a;switch(n){case"verified":{const i=typeof e.token=="string"?e.token:"";if(!i){z.warn("resolveIdentityChallenge: result=verified 인데 token 이 비어있습니다. failed 로 강등합니다."),a={status:"failed",failureCode:"MISSING_TOKEN"};break}a={status:"verified",token:i,providerData:e.providerData&&typeof e.providerData=="object"?e.providerData:void 0};break}case"pending":{const i=typeof e.pollUrl=="string"?e.pollUrl:"",l=typeof e.expiresAt=="string"?e.expiresAt:"";if(!i||!l){z.warn("resolveIdentityChallenge: result=pending 인데 pollUrl/expiresAt 누락 — failed 로 강등합니다."),a={status:"failed",failureCode:"MALFORMED_PENDING"};break}a={status:"pending",pollUrl:i,pollIntervalMs:typeof e.pollIntervalMs=="number"?e.pollIntervalMs:void 0,expiresAt:l};break}case"failed":a={status:"failed",failureCode:typeof e.failureCode=="string"?e.failureCode:"UNKNOWN",reason:typeof e.reason=="string"?e.reason:void 0};break;default:a={status:"cancelled"};break}return vt.resolveDeferred(a),!0}async handleApiCall(e,n,a,i="none",l){const{method:c="GET",body:d,headers:f,contentType:g}=n;c!=="GET"&&c!=="HEAD"&&await this.ensureCsrfToken();const m=this.getCsrfTokenFromCookie();let b={};if(i==="required"||i==="optional"){const K=Ir().getToken();K&&(b={Authorization:`Bearer ${K}`})}let S=e;const v=n.query||(c==="GET"?d:null);if(v&&typeof v=="object"){const P=this.buildQueryString(v);if(P){const K=e.includes("?")?"&":"?";S=`${e}${K}${P}`}}const _=window.G7Core?.state?.getGlobal?.()||a.state?._global||{},A=window.G7Core?.state?.getLocal?.()||a.state?._local||{},x={_global:_,_local:A},L=this.getMatchingGlobalHeaders(e,x),M={};if(typeof window<"u"){const P=localStorage.getItem("g7_locale");P&&(M["Accept-Language"]=P)}const R=g==="multipart/form-data",D={method:c,headers:{...R?{}:{"Content-Type":"application/json"},Accept:"application/json",...M,...m&&{"X-XSRF-TOKEN":m},...b,...L,...f},credentials:"include"};if(d&&c!=="GET")if(R){const P=new FormData;for(const[K,ge]of Object.entries(d))ge instanceof File||ge instanceof Blob?P.append(K,ge):ge!=null&&P.append(K,typeof ge=="object"?JSON.stringify(ge):String(ge));D.body=P}else D.body=JSON.stringify(d);const B=qn();let q=null;B?.isEnabled()&&(q=B.trackRequest(S,c));try{let P=await fetch(S,D),K;try{K=await P.json()}catch{K=null}if(vt.isIdentityRequired(P.status,K)){const ge=await vt.handle(K,{body:D.body,headers:D.headers,credentials:D.credentials},l);if(ge){P=ge;try{K=await P.json()}catch{K=null}}}if(q&&B?.isEnabled()&&(B.completeRequest(q,P.status,K),q=null),!P.ok){const ge=K||{},Ee=new Error(ge.message||`API call failed: ${P.statusText}`);throw Ee.response=ge,Ee.status=P.status,Ee.statusText=P.statusText,new jt(ge.message||`API call failed: ${P.statusText}`,void 0,Ee)}return K}catch(P){throw q&&B?.isEnabled()&&B.failRequest(q,P instanceof Error?P.message:String(P)),P}}async handleLogin(e,n,a){const{body:i}=n;if(!i||!i.email||!i.password)throw new jt("Login requires email and password in body params");const l=e==="user"?"user":"admin",c=l==="admin"?"/api/auth/admin/login":"/api/auth/login",d=window.G7Core?.state?.getGlobal?.()||a.state?._global||{},f=window.G7Core?.state?.getLocal?.()||a.state?._local||{},g={_global:d,_local:f},m=this.getMatchingGlobalHeaders(c,g),b=yr.getInstance();try{const S=Object.keys(m).length>0?{headers:m}:void 0;return{user:await b.login(l,{email:i.email,password:i.password},S)}}catch(S){const v=S.message||"Login failed",_=new Error(v);throw _.response=S.response?.data||{},_.status=S.status||S.response?.status||500,new jt(v,void 0,_)}}async handleLogout(e,n){await yr.getInstance().logout()}async handleSetState(e,n){z.log("[handleSetState] START, params:",e);const{target:a="component",scope:i,merge:l,__render:c,...d}=e,f=qn(),g=window.G7Core;if(typeof a=="string"&&(a.startsWith("$parent.")||a.startsWith("$root.")))return this.handleParentScopeSetState(a,d,l,n);const m=n.state&&Object.keys(n.state).length>0?n.state:g?.state?.get()||{},b=a==="global"?"_global":a.startsWith("_local.")?a:"_local",S=b==="_global"?m._global:b.startsWith("_local.")?this.getNestedProperty(m._local||{},b.slice(7)):m._local,v=f?.isEnabled()?f.startStateChange(b,S,d,{actionId:n.actionId,handlerType:"setState",source:`target=${a}`}):void 0,_=l==="replace"?"replace":l==="shallow"?"shallow":"deep",A=_!=="deep"?{...d,__mergeMode:_,...v?{__setStateId:v}:{}}:{...d,...v?{__setStateId:v}:{}};if(a==="global"){if(!this.globalStateUpdater){z.warn("Global state updater is not set"),v&&f&&f.completeStateChange(v);return}const x=g?.state?.get()||{},{__setStateId:L,__mergeMode:M,...R}=A,D=_==="deep"?this.deepMergeWithState(R,x):R,B={...D,...v?{__setStateId:v}:{},..._!=="deep"?{__mergeMode:_}:{}};return z.log("setState global:",B),this.globalStateUpdater(B,{render:c}),v&&f&&setTimeout(()=>f.completeStateChange(v),0),{__target:"global",..._!=="deep"?{__mergeMode:_}:{},...D}}else if(a==="isolated"){const x=n.isolatedContext;if(x){const{__mergeMode:L,__setStateId:M,...R}=A;return x.mergeState(R,_),z.log("[handleSetState] isolated state updated:",R,"mergeMode:",_),v&&f&&setTimeout(()=>f.completeStateChange(v),0),{__target:"isolated",...R}}else if(z.warn('[handleSetState] isolated target used but no IsolatedStateContext found. Make sure the component has "isolatedState" attribute. Falling back to local state.'),n.setState){const M=window.__g7PendingLocalState||n.state||{},R=_==="deep"?this.deepMergeWithState(A,M):A;return n.setState(R),v&&f&&setTimeout(()=>f.completeStateChange(v),0),R}else throw v&&f&&f.completeStateChange(v),new jt("isolated target used but no IsolatedStateContext found and no setState function in context")}else if(a==="local"||a==="component"||a==="_local"){if(i==="parent"||i==="root"){const L=window.__g7LayoutContextStack||[];if(L.length>0){const M=i==="parent"?L[L.length-1]:L[0];if(M?.setState){const{__mergeMode:R,__setStateId:D,...B}=A,q=_==="deep"?this.deepMergeWithState(B,M.state||{}):B;return z.log(`[handleSetState] scope=${i}: 타겟 컨텍스트에 상태 업데이트`,q),M.setState(q),v&&f&&setTimeout(()=>f.completeStateChange(v),0),q}}z.warn(`[handleSetState] scope=${i}: 레이아웃 컨텍스트 스택이 비어있습니다. current로 폴백합니다.`)}const x=n.setState&&!n._isDispatchFallbackContext;if(z.log("[handleSetState] isRealComponentContext:",x,"context.setState:",!!n.setState,"_isDispatchFallbackContext:",n._isDispatchFallbackContext),x){z.log("[handleSetState] Using COMPONENT setState path"),z.log("[handleSetState] resolvedPayload:",A),z.log("[handleSetState] context.state:",n.state),z.log("[handleSetState] mergeMode:",_);const M=window.__g7PendingLocalState||n.state||{},R=_==="deep"?this.deepMergeWithState(A,{}):A;z.log("[handleSetState] convertedPayload (변경 필드만, dot notation 변환):",R),n.setState(R);const D=_==="deep"?this.deepMergeWithState(A,M):_==="shallow"?{...n.state||{},...A}:A,{__mergeMode:B,__setStateId:q,...P}=D;window.__g7PendingLocalState=P,z.log("[handleSetState] __g7PendingLocalState updated:",P),this.globalStateUpdater&&(this.globalStateUpdater({_local:P},{render:!1}),z.log("[handleSetState] _global._local synced (render:false):",P));const{__mergeMode:K,__setStateId:ge,...Ee}=A;if(_==="replace")window.__g7ForcedLocalFields=Ee;else{const Se=window.__g7ForcedLocalFields||{};window.__g7ForcedLocalFields=this.deepMergeWithState(Ee,Se)}return z.log("[handleSetState] __g7ForcedLocalFields updated:",Ee),v&&f&&setTimeout(()=>f.completeStateChange(v),0),{__target:"local",...D}}else if(this.globalStateUpdater){z.log("[handleSetState] Using GLOBAL STATE UPDATER path for _local");const M=window.__g7PendingLocalState||m._local||{};z.log("[handleSetState] currentLocal (with pending):",M);const R=_==="deep"?this.deepMergeWithState(A,M):A;return z.log("[handleSetState] finalLocal (merged):",R),this.globalStateUpdater({_local:R},{render:c}),window.__g7PendingLocalState=R,z.log("[handleSetState] __g7PendingLocalState updated for globalStateUpdater path"),v&&f&&setTimeout(()=>f.completeStateChange(v),0),{__target:"local",...R}}else throw v&&f&&f.completeStateChange(v),new jt("setState function is not provided in context and globalStateUpdater is not set")}else if(a.startsWith("_local.")){const x=a.slice(7),{__mergeMode:L,__setStateId:M,...R}=A,D=R.value!==void 0?R.value:Object.values(R)[0];if(n.setState){const q=window.__g7PendingLocalState||n.state||{},P=this.createNestedUpdate(x,D,q),K={...q,...P};return n.setState(P),v&&f&&setTimeout(()=>f.completeStateChange(v),0),K}else throw v&&f&&f.completeStateChange(v),new jt("setState function is not provided in context")}else{if(!n.setState)throw v&&f&&f.completeStateChange(v),new jt("setState function is not provided in context");const L=window.__g7PendingLocalState||n.state||{},M=_==="deep"?this.deepMergeWithState(A,L):A;return n.setState(M),v&&f&&setTimeout(()=>f.completeStateChange(v),0),M}}async handleParentScopeSetState(e,n,a,i){const l=qn(),c=window.G7Core,d=a==="replace"?"replace":a==="shallow"?"shallow":"deep",f=e.startsWith("$parent."),g=f?"$parent.":"$root.",m=e.slice(g.length);let b,S;if(m==="_local"||m==="_global")b=m,S=void 0;else if(m.startsWith("_local."))b="_local",S=m.slice(7);else if(m.startsWith("_global."))b="_global",S=m.slice(8);else{z.warn(`[handleParentScopeSetState] 지원하지 않는 타겟 형식: ${e}. _local 또는 _global으로 시작해야 합니다.`);return}const v=window.__g7LayoutContextStack||[];if(v.length===0){z.warn(`[handleParentScopeSetState] 레이아웃 컨텍스트 스택이 비어있습니다. target=${e}`);return}const _=f?v[v.length-1]:v[0];if(!_){z.warn(`[handleParentScopeSetState] 타겟 컨텍스트를 찾을 수 없습니다. target=${e}`);return}const A=b==="_global"?_.state?._global:_.state?._local,x=l?.isEnabled()?l.startStateChange(e,A,n,{actionId:i.actionId,handlerType:"setState",source:`target=${e}`}):void 0,{__mergeMode:L,__setStateId:M,...R}=n;if(b==="_global"){if(!this.globalStateUpdater){z.warn("[handleParentScopeSetState] globalStateUpdater가 설정되지 않았습니다."),x&&l&&l.completeStateChange(x);return}const D=c?.state?.get()||{};if(S){const B=R.value!==void 0?R.value:Object.values(R)[0],q=this.createNestedUpdate(S,B,D),P={...D,...q};z.log(`[handleParentScopeSetState] ${e}: 전역 상태 중첩 경로 업데이트`,P),this.globalStateUpdater(P)}else{const B=d==="deep"?this.deepMergeWithState(R,D):R;z.log(`[handleParentScopeSetState] ${e}: 전역 상태 업데이트 (mergeMode=${d})`,B),this.globalStateUpdater(d!=="deep"?{...B,__mergeMode:d}:B)}return x&&l&&setTimeout(()=>l.completeStateChange(x),0),{__target:e,...R}}else{if(!_.setState){z.warn(`[handleParentScopeSetState] 타겟 컨텍스트에 setState가 없습니다. target=${e}`),x&&l&&l.completeStateChange(x);return}const D=_.state||{};if(S){const B=R.value!==void 0?R.value:Object.values(R)[0],q=this.createNestedUpdate(S,B,D),P={...D,...q};return z.log(`[handleParentScopeSetState] ${e}: 로컬 상태 중첩 경로 업데이트`,q),_.setState(q),_.state=P,_.dataContext&&(_.dataContext._local=P),Ud(),x&&l&&setTimeout(()=>l.completeStateChange(x),0),P}else{const B=d!=="deep"?{...R,__mergeMode:d}:R,q=d==="replace"?R:d==="shallow"?{...D,...R}:this.deepMergeWithState(R,D);return z.log(`[handleParentScopeSetState] ${e}: 로컬 상태 업데이트 (mergeMode=${d})`,q),_.setState(B),_.state=q,_.dataContext&&(_.dataContext._local=q),Ud(),x&&l&&setTimeout(()=>l.completeStateChange(x),0),q}}}deepMergeWithState(e,n){const a=["errors"];let i=e;if("..."in e){const c=e["..."];if(c&&typeof c=="object"&&!Array.isArray(c)){const{"...":d,...f}=e;i={...c,...f}}else{const{"...":d,...f}=e;i=f}}const l={...n};for(const[c,d]of Object.entries(i)){if(a.includes(c)){l[c]=d;continue}if(c.includes(".")&&c!=="..."){const f=this.createNestedUpdate(c,d,l);this.deepMergeInto(l,f,l);continue}if(d!==null&&typeof d=="object"&&!Array.isArray(d)&&Object.getPrototypeOf(d)!==Object.prototype&&Object.getPrototypeOf(d)!==null){l[c]=d;continue}d!==null&&typeof d=="object"&&!Array.isArray(d)&&n[c]!==null&&typeof n[c]=="object"&&!Array.isArray(n[c])?l[c]=this.deepMergeWithState(d,n[c]):l[c]=d}return l}deepMergeInto(e,n,a){for(const[i,l]of Object.entries(n)){if(l===null){e[i]=l;continue}if(e[i]===null){e[i]=l;continue}typeof l=="object"&&!Array.isArray(l)&&e[i]!==void 0&&e[i]!==null&&typeof e[i]=="object"&&!Array.isArray(e[i])?(e[i]===a[i]&&(e[i]={...e[i]}),this.deepMergeInto(e[i],l,a[i]||{})):typeof l=="object"&&!Array.isArray(l)&&e[i]===void 0&&a[i]!==null&&typeof a[i]=="object"&&!Array.isArray(a[i])?e[i]={...a[i],...l}:e[i]=l}}createNestedUpdate(e,n,a){const i=e.split(".");if(i.length===1)return{[i[0]]:n};const l={};let c=l,d=a;for(let f=0;f{this.globalStateUpdater({_local:v})};m.push({state:b,setState:S,dataContext:{_local:b||n.data?._local,_global:l?n.data?._global:n.data?._global||i?.state?.get?.(),_computed:n.data?._computed}}),window.__g7LayoutContextStack=m,z.log(`[handleOpenModal] 레이아웃 컨텍스트 스택에 push, 스택 크기: ${m.length}, state:`,b,l?"(direct)":"(fallback via G7Core)")}else z.warn(`[handleOpenModal] 컨텍스트 스택에 push 실패 - setState: ${!!n.setState}, state: ${n.state!==void 0}, G7Core: ${!!i?.state?.getLocal}`);const f=[...a,e];this.globalStateUpdater({modalStack:f,activeModal:e});const g=qn();if(g?.isEnabled()){const m=a.length>0?a[a.length-1]:void 0;g.trackModalOpen?.({modalId:e,modalName:e,scopeType:"isolated",parentModalId:m,initialState:n.state?{...n.state}:{}})}}async handleCloseModal(e){if(!this.globalStateUpdater){z.warn("Global state updater is not set for closeModal");return}const n=e?.data?._global?.modalStack||[],a=n.length>0?n[n.length-1]:null,i=n.slice(0,-1),l=i.length>0?i[i.length-1]:null,c=window.__g7LayoutContextStack||[];if(c.length>0){const d=c[c.length-1];d&&(d.dataContext=void 0),c.pop(),window.__g7LayoutContextStack=c,z.log(`[handleCloseModal] 레이아웃 컨텍스트 스택에서 pop, 스택 크기: ${c.length}`)}if(a){const d=qn();d?.isEnabled()&&d.trackModalClose?.(a,e?.state)}this.globalStateUpdater({modalStack:i,activeModal:l})}async handleShowAlert(e,n){let a=e;this.translationEngine&&this.translationContext&&e.startsWith("$t:")&&(a=this.translationEngine.resolveTranslations(e,this.translationContext,n.data)),alert(a)}async handleToast(e,n){const{type:a="info",message:i,icon:l,duration:c}=e;if(a==="error"){const f=n.data?.error;if((f?.error_code==="identity_verification_required"||f?.data?.error_code==="identity_verification_required")&&vt.consumeDomainNoticeShown()){z.log("[Toast] IDV 도메인 안내 표출됨 — 중복 가드 토스트 1건 skip");return}}let d=i;if(this.translationEngine&&this.translationContext&&i?.startsWith("$t:")&&(d=this.translationEngine.resolveTranslations(i,this.translationContext,n.data)),this.globalStateUpdater){const g={id:`toast_${Date.now()}_${Math.random().toString(36).substring(2,9)}`,type:a,message:d,...l&&{icon:l},...c&&{duration:c}},v=[...window.G7Core?.state?.get()?.toasts||[],g];this.globalStateUpdater({toasts:v})}else z.log(`[Toast ${a}] ${d}`)}async handleSwitch(e,n){const{cases:a}=e;if(!a){z.warn("switch handler requires cases property");return}const i=this.resolveParams(e.params,n.data);let l;if(i?.value!==void 0?(l=i.value,z.log("switch handler: using params.value as case key:",l)):(l=n.data?.$args?.[0],z.log("switch handler: using $args[0] as case key:",l)),l==null){if(a.default)return z.log("switch handler: case key is undefined, using default case"),await this.executeAction(a.default,n);z.warn("switch handler: case key is not defined and no default case");return}const c=String(l);let d=a[c];if(!d&&a.default&&(z.log(`switch handler: no case found for key "${c}", using default case`),d=a.default),!d){z.warn(`switch handler: no case found for key "${c}" and no default case`);return}return await this.executeAction(d,n)}async handleConditions(e,n){const{conditions:a}=e;if(!a||!Array.isArray(a)){z.warn("conditions handler requires conditions array");return}const i=n.data||{},l=yp(a,i,this.bindingEngine);if(!l.matched){z.log("conditions handler: no matching branch found");return}const c=a[l.branchIndex];if(z.log(`conditions handler: matched branch ${l.branchIndex}`),!c.then){z.warn('conditions handler: matched branch has no "then" action');return}return Array.isArray(c.then)?await this.handleSequence({...e,actions:c.then},n):await this.executeAction(c.then,n)}handleStartInterval(e,n){const a=typeof e.id=="string"?e.id:"",i=Number(e.intervalMs??1e3),l=Array.isArray(e.actions)?e.actions:[];if(a===""||!l.length||i<=0)return z.warn("startInterval requires non-empty id, positive intervalMs, and actions array"),{success:!1};const c=this.intervals.get(a);c&&(clearInterval(c),this.intervals.delete(a));const d=setInterval(()=>{for(const f of l)this.executeAction(f,n).catch(g=>{z.error(`[startInterval:${a}] tick action failed`,g)})},i);return this.intervals.set(a,d),{success:!0,id:a}}handleStopInterval(e){const n=typeof e.id=="string"?e.id:"";if(n==="")return z.warn("stopInterval requires id parameter"),{success:!1};const a=this.intervals.get(n);return a&&(clearInterval(a),this.intervals.delete(n)),{success:!0,id:n}}stopAllIntervals(){for(const e of this.intervals.values())clearInterval(e);this.intervals.clear()}async handleSequence(e,n){const a=e.actions||e.params?.actions;if(!a||!Array.isArray(a)||a.length===0)return z.warn("sequence handler requires non-empty actions array"),[];const i=qn(),l=i?.isEnabled()&&i.startSequenceExecution?.({eventType:e.type})||"";window.__g7SequenceLocalSync=void 0;const c=[];let d,f=n.state?{...n.state}:{},g=n.isolatedContext?.state?{...n.isolatedContext.state}:{},m=n.data?._computed||{};const b=window.G7Core,S=()=>({_global:b?.state?.get()||{},_local:{...f},_isolated:Object.keys(g).length>0?{...g}:void 0});for(let v=0;v0){const R={},D={...n.data,_local:f,_computed:R,$computed:R,_isolated:g};for(const[B,q]of Object.entries(M))if(typeof q=="string"){const P=q.trim();if(P.startsWith("{{")&&P.endsWith("}}"))try{const K=P.slice(2,-2).trim();R[B]=Pn(K)?this.bindingEngine.evaluatePipeExpression(K,D,{skipCache:!0}):this.bindingEngine.evaluateExpression(K,D,{skipCache:!0})}catch{R[B]=m[B]}}m=R,z.log("[handleSequence] _computed recalculated after setState:",m)}}if(_.handler!=="setState"){const M=window.__g7SequenceLocalSync;if(M&&M!==f){f=M,window.__g7SequenceLocalSync=void 0,z.log("[handleSequence] currentState synchronized from __g7SequenceLocalSync after custom handler:",_.handler);const R=n.data?._computedDefinitions;if(R&&Object.keys(R).length>0){const D={},B={...n.data,_local:f,_computed:D,$computed:D,_isolated:g};for(const[q,P]of Object.entries(R))if(typeof P=="string"){const K=P.trim();if(K.startsWith("{{")&&K.endsWith("}}"))try{const ge=K.slice(2,-2).trim();D[q]=Pn(ge)?this.bindingEngine.evaluatePipeExpression(ge,B,{skipCache:!0}):this.bindingEngine.evaluateExpression(ge,B,{skipCache:!0})}catch{D[q]=m[q]}}m=D,z.log("[handleSequence] _computed recalculated after custom handler:",m)}}}l&&i?.isEnabled()&&i.captureSequenceActionAfter?.(l,v,S(),Date.now()-A,L.data)}catch(L){throw l&&i?.isEnabled()&&(i.captureSequenceActionAfter?.(l,v,S(),Date.now()-A,void 0,L),i.endSequenceExecution?.(l,L)),z.error(`sequence handler: action[${v}] failed:`,L),L}}return window.__g7SequenceLocalSync=void 0,l&&i?.isEnabled()&&i.endSequenceExecution?.(l),c}async handleParallel(e,n){const a=e.actions||e.params?.actions;if(!a||!Array.isArray(a)||a.length===0)return z.warn("parallel handler requires non-empty actions array"),[];z.log(`parallel handler: executing ${a.length} actions in parallel`);const i=n.isolatedContext?{...n.isolatedContext,state:{...n.isolatedContext.state}}:null,l=a.map((f,g)=>{const m={...n,isolatedContext:i};return this.executeAction(f,m).catch(b=>{throw z.error(`parallel handler: action[${g}] failed:`,b),b})}),c=await Promise.allSettled(l),d=c.filter(f=>f.status==="rejected").length;return d>0&&z.warn(`parallel handler: ${d}/${a.length} actions failed`),c}async handleLoadScript(e,n,a){const{src:i,id:l,async:c=!0,defer:d=!1}=e;if(!i)throw new jt('loadScript handler requires "src" parameter',n);const f=l||`script_${i.replace(/[^a-zA-Z0-9]/g,"_")}`;return ps.loadedScripts.has(f)?(z.log(`loadScript: script already loaded, skipping: ${f}`),n.onLoad&&await this.executeAction(n.onLoad,a),!0):document.getElementById(f)?(z.log(`loadScript: script element already exists: ${f}`),ps.loadedScripts.add(f),n.onLoad&&await this.executeAction(n.onLoad,a),!0):(z.log(`loadScript: loading script: ${i}`),new Promise((m,b)=>{const S=document.createElement("script");S.id=f,S.src=i,S.async=c,S.defer=d,S.onload=async()=>{if(z.log(`loadScript: script loaded successfully: ${f}`),ps.loadedScripts.add(f),n.onLoad)try{await this.executeAction(n.onLoad,a)}catch(v){z.error("loadScript: onLoad action failed:",v)}m(!0)},S.onerror=v=>{z.error(`loadScript: failed to load script: ${i}`,v),b(new jt(`Failed to load script: ${i}`,n))},document.head.appendChild(S)}))}async handleCallExternal(e,n,a){const i=e.constructor,l=e.args||{},c=e.method,d=e.methodArgs||[],f=e.callbackEvent,g=e.embedTarget,m=e.callbackSetState,b=e.callbackAction;if(!i)throw new jt('callExternal handler requires "constructor" parameter',n);const S=this.getNestedProperty(window,i);if(!S||typeof S!="function")throw new jt(`Constructor not found or not a function: ${i}. Make sure the script is loaded first using loadScript handler.`,n);z.log(`callExternal: calling constructor ${i}`);const v={...l};for(const[A,x]of Object.entries(l))x===!0&&(v[A]=L=>{if(z.log(`callExternal: callback triggered for ${A}`,L),f&&typeof window<"u"&&window.G7Core?.componentEvent&&window.G7Core.componentEvent.emit(f,L),m&&a.setState){const M=B=>{const q={};for(const[P,K]of Object.entries(B))typeof K=="string"?q[P]=this.getNestedProperty(L,K):typeof K=="object"&&K!==null&&(q[P]=M(K));return q},R=M(m);z.log("callExternal: callbackSetState mapping result",R);const D=this.deepMergeWithState(R,a.state||{});z.log("callExternal: merged with existing state",D),a.setState(D)}else f&&a.setState&&a.setState({[`${f.replace(/:/g,"_")}_result`]:L});if(b){const M={...a,data:{...a.data,$event:L}},R=Array.isArray(b)?b:[b];for(const D of R)try{this.executeAction(D,M)}catch(B){z.error("callExternal: callbackAction failed:",B)}}});const _=new S(v);if(c&&typeof _[c]=="function"){if(z.log(`callExternal: calling method ${c}`),c==="embed"&&g){const A=document.querySelector(g);if(A)return _[c](A,...d);throw new jt(`Embed target element not found: ${g}`,n)}return _[c](...d)}return _}async handleCallExternalEmbed(e,n,a){const i=e.constructor,l=e.args||{},c=e.callbackSetState,d=e.callbackEvent,f=e.layerClassName,g=e.callbackAction;if(!i)throw new jt('callExternalEmbed handler requires "constructor" parameter',n);const m=this.getNestedProperty(window,i);if(!m||typeof m!="function")throw new jt(`Constructor not found or not a function: ${i}. Make sure the script is loaded first.`,n);z.log(`callExternalEmbed: creating layer for ${i}`);const{layer:b,closeLayer:S}=this.createEmbedLayer(f),v={...l};for(const[A,x]of Object.entries(l))x===!0&&(v[A]=L=>{if(z.log(`callExternalEmbed: callback triggered for ${A}`,L),S(),d&&typeof window<"u"&&window.G7Core?.componentEvent&&window.G7Core.componentEvent.emit(d,L),c&&a.setState){const M=B=>{const q={};for(const[P,K]of Object.entries(B))typeof K=="string"?q[P]=this.getNestedProperty(L,K):typeof K=="object"&&K!==null&&(q[P]=M(K));return q},R=M(c);z.log("callExternalEmbed: callbackSetState mapping result",R);const D=this.deepMergeWithState(R,a.state||{});z.log("callExternalEmbed: merged with existing state",D),a.setState(D)}if(g){const M={...a,data:{...a.data,$event:L}},R=Array.isArray(g)?g:[g];for(const D of R)try{this.executeAction(D,M)}catch(B){z.error("callExternalEmbed: callbackAction failed:",B)}}});const _=new m(v);if(typeof _.embed=="function")_.embed(b),z.log("callExternalEmbed: embedded in layer");else throw S(),new jt(`Constructor ${i} does not have embed method`,n);return _}isImeComposing(e){return e?.isComposing===!0||e?.keyCode===229}createEmbedLayer(e){const n=document.createElement("div");n.id="g7-embed-overlay",n.style.cssText=` position: fixed; inset: 0; background-color: rgba(0, 0, 0, 0.5); @@ -47,11 +47,11 @@ Error generating stack: `+u.message+` justify-content: center; color: #666; transition: background-color 0.2s; - `,i.onmouseenter=()=>{i.style.backgroundColor="rgba(0, 0, 0, 0.2)"},i.onmouseleave=()=>{i.style.backgroundColor="rgba(0, 0, 0, 0.1)"};const l=()=>{n.parentNode&&n.parentNode.removeChild(n)};i.onclick=l;const c=d=>{this.isImeComposing(d)||d.key==="Escape"&&(l(),document.removeEventListener("keydown",c))};return document.addEventListener("keydown",c),n.onclick=d=>{d.target===n&&l()},a.appendChild(i),n.appendChild(a),document.body.appendChild(n),{overlay:n,layer:a,closeLayer:l}}getNestedProperty(e,n){return n.split(".").reduce((a,i)=>a&&a[i]!==void 0?a[i]:void 0,e)}async handleSaveToLocalStorage(e,n){const{key:a,value:i}=e;if(!a)return j.error("saveToLocalStorage: key parameter is required"),!1;try{const l=typeof i=="string"?i:JSON.stringify(i);return localStorage.setItem(a,l),j.log(`saveToLocalStorage: saved ${a}`,i),!0}catch(l){return j.error("saveToLocalStorage: failed to save",{key:a,error:l}),!1}}async handleLoadFromLocalStorage(e,n){const{key:a,target:i,stateKey:l,defaultValue:c}=e;if(!a)return j.error("loadFromLocalStorage: key parameter is required"),c;try{const d=localStorage.getItem(a);if(d===null)return j.log(`loadFromLocalStorage: no value found for ${a}, using default`),l&&n.setState&&n.setState({[l]:c}),c;let f;try{f=JSON.parse(d)}catch{f=d}return j.log(`loadFromLocalStorage: loaded ${a}`,f),l&&n.setState&&n.setState({[l]:f}),f}catch(d){return j.error("loadFromLocalStorage: failed to load",{key:a,error:d}),c}}async handleCustomAction(e,n){const a=this.customHandlers.get(e.handler);if(!a){if(this.previewMode){j.warn(`[Preview] Unknown action handler "${e.handler}" — skipped (preview mode)`);return}const i=new Bt(`Unknown action handler: ${e.handler}`,e);throw i.unknownHandler=!0,i}return await a(e,n)}resolveParams(e,n){if(!e)return{};const a={};for(const[i,l]of Object.entries(e))i.includes("{{")&&j.warn(`[resolveParams] setState params의 키에 표현식이 포함되어 있습니다: "${i}". 키는 해석되지 않습니다. 배열 항목 수정은 .map()/.filter() 패턴을 사용하세요.`),typeof l=="string"&&l.includes("{{")?a[i]=this.evaluateExpression(l,n):typeof l=="string"?a[i]=l:Array.isArray(l)?a[i]=l.map(c=>typeof c=="string"&&c.includes("{{")?this.evaluateExpression(c,n):c):typeof l=="object"&&l!==null&&!Array.isArray(l)&&Object.getPrototypeOf(l)!==Object.prototype&&Object.getPrototypeOf(l)!==null?a[i]=l:typeof l=="object"&&l!==null?a[i]=this.resolveParams(l,n):a[i]=l;return a}resolveValue(e,n){return n?this.bindingEngine.resolveBindings(e,n,{skipCache:!0}):e}evaluateExpressions(e,n){let a={};for(const[i,l]of Object.entries(e))if(i==="..."){let c=l;typeof l=="string"&&l.includes("{{")&&(c=this.evaluateExpression(l,n)),c&&typeof c=="object"&&!Array.isArray(c)&&(a={...a,...c})}else typeof l=="string"&&l.includes("{{")?a[i]=this.evaluateExpression(l,n):typeof l=="object"&&l!==null&&!Array.isArray(l)?a[i]=this.evaluateExpressions(l,n):a[i]=l;return a}evaluateExpressionsIfNeeded(e,n){const a={};for(const[i,l]of Object.entries(e))typeof l=="string"&&l.includes("{{")?a[i]=this.evaluateExpression(l,n):a[i]=l;return a}evaluateExpression(e,n){if(!n)return e;const a=Aa(e);if(a===null){let c=n;if(e.includes("_global")){const f=window.G7Core?.state?.get()?._global;f&&(c={...n,_global:f})}if(e.includes("_computed")||e.includes("$computed")){const f=window.__g7ActionContext?.computedRef?.current;f&&Object.keys(f).length>0&&(c={...c,_computed:f,$computed:f})}return this.bindingEngine.resolveBindings(e,c,{skipCache:!0})}let i=a;i=i.replace(/\$args\.(\d+)/g,"$args[$1]");let l=n;if(i.includes("_global")){const d=window.G7Core?.state?.get()?._global;d&&(l={...n,_global:d})}if(i.includes("_computed")||i.includes("$computed")){const d=window.__g7ActionContext?.computedRef?.current;d&&Object.keys(d).length>0&&(l={...l,_computed:d,$computed:d})}i.includes("_global.modules")&&(j.log("[evaluateExpression] expression:",i),j.log("[evaluateExpression] dataContext._global:",l._global),j.log("[evaluateExpression] dataContext._global?.modules:",l._global?.modules));try{const c=Vn(i)?this.bindingEngine.evaluatePipeExpression(i,l,{skipCache:!0}):this.bindingEngine.evaluateExpression(i,l);return i.includes("_global.modules")&&j.log("[evaluateExpression] result:",c),typeof c=="string"&&c.startsWith("$t:")&&this.translationEngine&&this.translationContext?this.translationEngine.resolveTranslations(c,this.translationContext,n):c}catch(c){return j.error("Expression evaluation failed:",e,c),e}}setDefaultContext(e){this.defaultContext={...this.defaultContext,...e}}setGlobalStateUpdater(e){this.globalStateUpdater=e}getGlobalStateUpdater(){return this.globalStateUpdater}async dispatchAction(e,n){const a={...this.defaultContext,...n,data:{...this.defaultContext.data,...n?.data}};if(e.confirm){let i=this.resolveValue(e.confirm,a.data);if(this.translationEngine&&this.translationContext&&i.startsWith("$t:")&&(i=this.translationEngine.resolveTranslations(i,this.translationContext,a.data)),!confirm(i))return{success:!1}}try{return await this.executeAction(e,a)}catch(i){if(j.error("dispatchAction failed:",i),e.onError){const l=i instanceof Bt&&i.originalError?{message:i.originalError.message,response:i.originalError.response}:{message:i instanceof Error?i.message:String(i)},c={...a,data:{...a.data,error:l}},d=Array.isArray(e.onError)?e.onError:[e.onError];for(const f of d)await this.executeAction(f,c)}return{success:!1,error:i instanceof Error?i:new Error(String(i))}}}registerHandler(e,n,a){this.customHandlers.set(e,n);const i=Fn();i?.isEnabled()&&i.trackHandlerRegistration(e,a?.category??"custom",a?.description,a?.source)}unregisterHandler(e){this.customHandlers.delete(e);const n=Fn();n?.isEnabled()&&n.trackHandlerUnregistration(e)}getRegisteredHandlers(){return Array.from(this.customHandlers.keys())}getHandler(e){return this.customHandlers.get(e)}normalizeDebounceConfig(e){return typeof e=="number"?{delay:e,leading:!1,trailing:!0}:{delay:e.delay,leading:e.leading??!1,trailing:e.trailing??!0}}extractEventData(e){const n=e.target;if(n&&!("tagName"in n)){const l={type:e.type??"custom",target:{value:n.value,name:n.name??""}};return e._changedKeys&&(l._changedKeys=e._changedKeys),l}const i=n?{value:n.value,name:n.name??"",checked:n.checked??!1,type:n.type??"",tagName:n.tagName}:{};if(e.type==="scroll"&&n){const l=n;i.scrollHeight=l.scrollHeight??0,i.scrollTop=l.scrollTop??0,i.clientHeight=l.clientHeight??0,i.scrollLeft=l.scrollLeft??0,i.clientWidth=l.clientWidth??0,i.scrollWidth=l.scrollWidth??0}return{type:e.type,target:n?i:null}}executeDebouncedAction(e,n,a,i,l){const c={...a,$event:n,$args:l},d={type:n.type,target:n.target,preventDefault:()=>{},stopPropagation:()=>{}};this.createHandler(e,c,i)(d)}handleDebouncedAction(e,n,a,i,l,c){const d=this.normalizeDebounceConfig(e.debounce),f=l||`default-${e.handler}-${Date.now()}`,g=this.extractEventData(n),m=g._changedKeys;if(m&&Array.isArray(m)&&g?.target?.value&&typeof g.target.value=="object"&&!Array.isArray(g.target.value)){const _=this.debounceAccumulatedValues.get(f);if(_){const A={..._};for(const x of m)A[x]=g.target.value[x];g.target.value=A}this.debounceAccumulatedValues.set(f,g.target.value)}const y=this.debounceTimers.get(f);y&&(clearTimeout(y),this.debounceTimers.delete(f));const S=Fn();S?.isEnabled()&&S.trackAction({handler:typeof e.handler=="string"?e.handler:String(e.handler),type:e.type,status:"pending",params:e.params,debounce:{delay:d.delay,status:"pending",scheduledAt:Date.now()}});const v=!this.debounceTimers.has(f+"_leading");if(d.leading&&v&&(this.debounceTimers.set(f+"_leading",setTimeout(()=>{},0)),this.executeDebouncedAction(e,g,a,i,c),S?.isEnabled()&&S.trackAction({handler:typeof e.handler=="string"?e.handler:String(e.handler),type:e.type,status:"success",params:e.params,debounce:{delay:d.delay,status:"executed",executedAt:Date.now(),mode:"leading"}})),d.trailing){const _=()=>{this.executeDebouncedAction(e,g,a,i,c),S?.isEnabled()&&S.trackAction({handler:typeof e.handler=="string"?e.handler:String(e.handler),type:e.type,status:"success",params:e.params,debounce:{delay:d.delay,status:"executed",executedAt:Date.now(),mode:"trailing"}})},A=setTimeout(()=>{this.debounceTimers.delete(f),this.debounceTimers.delete(f+"_leading"),this.pendingDebounceFlushers.delete(f),this.debounceAccumulatedValues.delete(f),_()},d.delay);this.debounceTimers.set(f,A),this.pendingDebounceFlushers.set(f,_)}}clearDebounceTimers(e){if(e)for(const[n,a]of this.debounceTimers)n.startsWith(e+"-")&&(clearTimeout(a),this.debounceTimers.delete(n),this.pendingDebounceFlushers.delete(n),this.debounceAccumulatedValues.delete(n));else{for(const n of this.debounceTimers.values())clearTimeout(n);this.debounceTimers.clear(),this.pendingDebounceFlushers.clear(),this.debounceAccumulatedValues.clear()}}debouncedCall(e,n,a){const i=this.debounceTimers.get(e);i&&(clearTimeout(i),this.debounceTimers.delete(e));const l=setTimeout(()=>{this.debounceTimers.delete(e),this.pendingDebounceFlushers.delete(e),a()},n);this.debounceTimers.set(e,l),this.pendingDebounceFlushers.set(e,a)}flushPendingDebounceTimers(){if(this.pendingDebounceFlushers.size===0)return;const e=new Map(this.pendingDebounceFlushers);for(const[n,a]of e){const i=this.debounceTimers.get(n);i&&(clearTimeout(i),this.debounceTimers.delete(n)),this.debounceTimers.delete(n+"_leading"),this.pendingDebounceFlushers.delete(n),a()}this.globalStateUpdater&&this.globalStateUpdater({})}bindActionsToProps(e,n,a){const i={...e};if(e.actions&&Array.isArray(e.actions)){const l=new Map;for(const m of e.actions){const y=this.resolveActionRef(m),S=y.event||this.getEventHandlerName(y.type);l.has(S)||l.set(S,[]),l.get(S).push(y)}const c=e.name||e.id||"unknown",d=l.has("onDrop"),f=l.has("onDragOver"),g=l.has("onDragEnter");d&&!f&&(i.onDragOver=m=>{m.preventDefault()}),d&&!g&&(i.onDragEnter=m=>{m.preventDefault()});for(const[m,y]of l)i[m]=(...S)=>{for(const v of y){const _=S[0],A=_&&typeof _=="object"&&"preventDefault"in _;if(A&&(m==="onDragOver"||m==="onDrop")&&_.preventDefault(),A&&v.key&&"key"in _){if(this.isImeComposing(_)){j.log("Key filter skipped (IME composing):",v.key);continue}if(_.key!==v.key){j.log("Key filter not matched:",v.key,"actual:",_.key);continue}j.log("Key filter matched:",v.key)}if(v.debounce){const x=`${c}-${v.handler}-${m}`,O=_&&typeof _=="object"&&!("preventDefault"in _)&&"target"in _&&_.target!==null;let M;if(A)M=_;else if(O){const D={type:"custom",target:_.target,preventDefault:()=>{},stopPropagation:()=>{}};_._changedKeys&&(D._changedKeys=_._changedKeys),M=D}else M=new Event("custom");const T=v.event?{...n,$args:S}:n;this.handleDebouncedAction(v,M,T,a,x,S)}else if(this.flushPendingDebounceTimers(),v.event){const x={...n,$args:S},O=A?_:new Event("custom");this.createHandler(v,x,a)(O)}else if(A)this.createHandler(v,n,a)(_);else if(_&&typeof _=="object"&&!("preventDefault"in _)&&"target"in _&&_.target!==null){const O={type:"custom",target:_.target,preventDefault:()=>{},stopPropagation:()=>{}};this.createHandler(v,n,a)(O)}}}}return i}getEventHandlerName(e){return{click:"onClick",change:"onChange",input:"onInput",submit:"onSubmit",focus:"onFocus",blur:"onBlur",keydown:"onKeyDown",keyup:"onKeyUp",keypress:"onKeyPress",mousedown:"onMouseDown",mouseup:"onMouseUp",mouseenter:"onMouseEnter",mouseleave:"onMouseLeave",scroll:"onScroll",dragstart:"onDragStart",drag:"onDrag",dragend:"onDragEnd",dragenter:"onDragEnter",dragover:"onDragOver",dragleave:"onDragLeave",drop:"onDrop"}[e]||`on${e.charAt(0).toUpperCase()}${e.slice(1)}`}sanitizeForDevTools(e,n=5){const a=new WeakSet,i=(l,c)=>{if(l==null||typeof l=="string"||typeof l=="number"||typeof l=="boolean")return l;if(c>n)return"[Max Depth Exceeded]";if(typeof l=="function")return`[Function: ${l.name||"anonymous"}]`;if(l instanceof Element)return`[Element: ${l.tagName}${l.className?"."+l.className.split(" ").join("."):""}]`;if(l instanceof Event)return`[Event: ${l.type}]`;if(Array.isArray(l)){if(a.has(l))return"[Circular Array]";a.add(l);const d=l.slice(0,10).map(f=>i(f,c+1));return l.length>10&&d.push(`... ${l.length-10} more items`),d}if(typeof l=="object"){if(a.has(l))return"[Circular Reference]";a.add(l);const d={},f=Object.keys(l),g=f.slice(0,20);for(const m of g)try{d[m]=i(l[m],c+1)}catch{d[m]="[Error accessing property]"}return f.length>20&&(d["..."]=`${f.length-20} more properties`),d}return String(l)};return i(e,0)}};$(bs,"loadedScripts",new Set);let bo=bs,nc=null;function bm(o){return nc||(nc=new bo(o)),nc}function AC(o){nc=o}const vm=ht("TransitionManager");class xC{constructor(){$(this,"isPending",!1);$(this,"subscribers",new Set)}setPending(e){this.isPending!==e&&(vm.log("setPending:",{from:this.isPending,to:e,stack:new Error().stack}),this.isPending=e,this.notifySubscribers())}getIsPending(){return this.isPending}subscribe(e){return this.subscribers.add(e),()=>{this.subscribers.delete(e)}}notifySubscribers(){vm.log("notifySubscribers:",{isPending:this.isPending,subscriberCount:this.subscribers.size}),this.subscribers.forEach(e=>{e(this.isPending)})}clearSubscribers(){this.subscribers.clear()}}const jr=new xC,rc=ht("TransitionContext"),Sm=H.createContext({isTransitioning:!1}),Vd=({children:o})=>{const[e,n]=H.useState(()=>{const i=jr.getIsPending();return rc.log("Initial state:",i),i});H.useEffect(()=>{rc.log("Subscribing to TransitionManager");const i=jr.subscribe(l=>{rc.log("Received isPending update:",l),n(l)});return()=>{rc.log("Unsubscribing from TransitionManager"),i()}},[]);const a=H.useMemo(()=>({isTransitioning:e}),[e]);return gt.jsx(Sm.Provider,{value:a,children:o})},wm=()=>H.useContext(Sm),ac="__g7LocalInitTracking";function Fd(){const o=globalThis;return o[ac]||(o[ac]={hash:"",timestamp:void 0,consumed:void 0}),o[ac]}function TC(){const o=globalThis;o[ac]={hash:"",timestamp:void 0,consumed:void 0}}function RC(o){Fd().consumed=o}function kC(o){return Fd().consumed===o}function DC(o){return o.globalTrackedKey!==o.trackingKey?"apply":o.instanceHandledKey===o.trackingKey?"skip":"prune"}function OC(o,e){return typeof o=="number"&&typeof e=="number"?Math.max(o,e):e??o}function LC(o,e){if(e===void 0)return o;if(!(o!==null&&typeof o=="object"&&e!==null&&typeof e=="object"&&!kC(o)))return e;const{_forceLocalInit:a,...i}=o,{_forceLocalInit:l,...c}=e,d={...i,...c},f=OC(a,l);return f!==void 0&&(d._forceLocalInit=f),d}function MC(o){return o<768?"mobile":o<1024?"tablet":"desktop"}function Cm(o){const e=MC(o);return{width:o,isMobile:e==="mobile",isTablet:e==="tablet",isDesktop:e==="desktop",matchedPreset:e}}const Kd=H.createContext(Cm(1024)),ic=({children:o,overrideWidth:e})=>{const[n,a]=H.useState(()=>hi.getWidth());H.useEffect(()=>{const c=hi.subscribe(d=>{a(d)});return()=>{c()}},[]);const i=e??n,l=H.useMemo(()=>Cm(i),[i]);return gt.jsx(Kd.Provider,{value:l,children:o})},Wd=()=>H.useContext(Kd);window.G7Core?.createLogger?.("Core:State");function $C(o,e,n,a){const{syncOnControlledChange:i=!0,isEqual:l}=a??{},c=o!==void 0;H.useRef(c);const[d,f]=H.useState(o!==void 0?o:e),g=d,m=H.useRef(o),y=H.useRef(!1);H.useEffect(()=>{if(y.current){m.current=o;return}i&&c&&o!==m.current&&(l?!l(d,o):d!==o)&&f(o),m.current=o},[o,c,i,l,d]);const S=H.useCallback(v=>{const _=typeof v=="function"?v(g):v;(l?l(g,_):g===_)||(y.current=!0,f(_),n?.(_),requestAnimationFrame(()=>{y.current=!1}))},[g,c,n,l]);return[g,S]}function NC(o,e){return o===e?!0:o.length!==e.length?!1:o.every((n,a)=>n===e[a])}function Em(o,e){if(o===e)return!0;const n=Object.keys(o),a=Object.keys(e);return n.length!==a.length?!1:n.every(i=>o[i]===e[i])}const _m=H.createContext({}),IC=({children:o,value:e})=>gt.jsx(_m.Provider,{value:e,children:o}),HC=()=>H.useContext(_m),jC=(o,e)=>{if(!o)return;const n=e.split(".");let a=o;for(const i of n){if(a==null)return;a=a[i]}return a},zC=(o,e,n)=>{const a=e.split(".");if(a.length===1)return{...o,[a[0]]:n};const i={...o};let l=i;for(let c=0;c{us.warn("IsolatedStateContext not available: setState called outside IsolatedStateProvider")},getState:()=>{},mergeState:()=>{us.warn("IsolatedStateContext not available: mergeState called outside IsolatedStateProvider")},stateRef:{current:{}},scopeId:""},Am=H.createContext(UC);let PC=0;function BC(){return`isolated-${++PC}-${Date.now().toString(36)}`}function xm(o,e){if(e==null)return o;if(typeof e!="object"||Array.isArray(e))return e;const n={...o};for(const a of Object.keys(e)){const i=e[a],l=o?.[a];i!==null&&typeof i=="object"&&!Array.isArray(i)&&l!==null&&typeof l=="object"&&!Array.isArray(l)?n[a]=xm(l,i):n[a]=i}return n}function qC(o,e){return e?e.split(".").reduce((n,a)=>n?.[a],o):o}function GC(o,e,n){const a=e.split("."),i={...o};let l=i;for(let c=0;c{const i=H.useRef(e||BC()).current,[l,c]=H.useState(o),d=H.useRef(l);d.current=l;const f=H.useCallback(v=>qC(d.current,v),[]),g=H.useCallback((v,_)=>{c(A=>{const x=GC(A,v,_);return us.log(`[${i}] setState: ${v} =`,_),x})},[i]),m=H.useCallback((v,_="deep")=>{c(A=>{let x;return _==="replace"?x=v:_==="shallow"?x={...A,...v}:x=xm(A,v),us.log(`[${i}] mergeState (${_}):`,v),x})},[i]),y=H.useMemo(()=>({state:l,setState:g,getState:f,mergeState:m,stateRef:d,scopeId:i}),[l,g,f,m,i]),S=H.useMemo(()=>({state:d.current,setState:g,getState:f,mergeState:m}),[g,f,m]);return H.useEffect(()=>{const v=window.G7Core,_=window.__g7IsolatedStates||{};return window.__g7IsolatedStates||(window.__g7IsolatedStates=_),_[i]=S,v&&(v._isolatedStates||(v._isolatedStates={}),v._isolatedStates[i]=l),v?.devTools?.isEnabled?.()&&v.devTools.emit?.("isolated:created",{id:i,state:l}),us.log(`[${i}] IsolatedStateProvider mounted with initial state:`,l),()=>{delete _[i],v?._isolatedStates&&delete v._isolatedStates[i],v?.devTools?.isEnabled?.()&&v.devTools.emit?.("isolated:destroyed",{id:i}),us.log(`[${i}] IsolatedStateProvider unmounted`)}},[i,S]),H.useEffect(()=>{const v=window.G7Core,_=window.__g7IsolatedStates;_?.[i]&&(_[i].state=l),v?._isolatedStates&&(v._isolatedStates[i]=l),v?.devTools?.isEnabled?.()&&v.devTools.emit?.("isolated:updated",{id:i,state:l})},[l,i]),gt.jsx(Am.Provider,{value:y,children:n})},FC=()=>{const o=H.useContext(Am);return o.scopeId?o:null};var ka=Qg();function KC(){for(var o=arguments.length,e=new Array(o),n=0;na=>{e.forEach(i=>i(a))},e)}const sc=typeof window<"u"&&typeof window.document<"u"&&typeof window.document.createElement<"u";function ds(o){const e=Object.prototype.toString.call(o);return e==="[object Window]"||e==="[object global]"}function Yd(o){return"nodeType"in o}function Nn(o){var e,n;return o?ds(o)?o:Yd(o)&&(e=(n=o.ownerDocument)==null?void 0:n.defaultView)!=null?e:window:window}function Xd(o){const{Document:e}=Nn(o);return o instanceof e}function vo(o){return ds(o)?!1:o instanceof Nn(o).HTMLElement}function Tm(o){return o instanceof Nn(o).SVGElement}function fs(o){return o?ds(o)?o.document:Yd(o)?Xd(o)?o:vo(o)||Tm(o)?o.ownerDocument:document:document:document}const zr=sc?H.useLayoutEffect:H.useEffect;function Jd(o){const e=H.useRef(o);return zr(()=>{e.current=o}),H.useCallback(function(){for(var n=arguments.length,a=new Array(n),i=0;i{o.current=setInterval(a,i)},[]),n=H.useCallback(()=>{o.current!==null&&(clearInterval(o.current),o.current=null)},[]);return[e,n]}function So(o,e){e===void 0&&(e=[o]);const n=H.useRef(o);return zr(()=>{n.current!==o&&(n.current=o)},e),n}function wo(o,e){const n=H.useRef();return H.useMemo(()=>{const a=o(n.current);return n.current=a,a},[...e])}function oc(o){const e=Jd(o),n=H.useRef(null),a=H.useCallback(i=>{i!==n.current&&e?.(i,n.current),n.current=i},[]);return[n,a]}function Qd(o){const e=H.useRef();return H.useEffect(()=>{e.current=o},[o]),e.current}let Zd={};function Co(o,e){return H.useMemo(()=>{if(e)return e;const n=Zd[o]==null?0:Zd[o]+1;return Zd[o]=n,o+"-"+n},[o,e])}function Rm(o){return function(e){for(var n=arguments.length,a=new Array(n>1?n-1:0),i=1;i{const d=Object.entries(c);for(const[f,g]of d){const m=l[f];m!=null&&(l[f]=m+o*g)}return l},{...e})}}const hs=Rm(1),Eo=Rm(-1);function YC(o){return"clientX"in o&&"clientY"in o}function ef(o){if(!o)return!1;const{KeyboardEvent:e}=Nn(o.target);return e&&o instanceof e}function XC(o){if(!o)return!1;const{TouchEvent:e}=Nn(o.target);return e&&o instanceof e}function tf(o){if(XC(o)){if(o.touches&&o.touches.length){const{clientX:e,clientY:n}=o.touches[0];return{x:e,y:n}}else if(o.changedTouches&&o.changedTouches.length){const{clientX:e,clientY:n}=o.changedTouches[0];return{x:e,y:n}}}return YC(o)?{x:o.clientX,y:o.clientY}:null}const _o=Object.freeze({Translate:{toString(o){if(!o)return;const{x:e,y:n}=o;return"translate3d("+(e?Math.round(e):0)+"px, "+(n?Math.round(n):0)+"px, 0)"}},Scale:{toString(o){if(!o)return;const{scaleX:e,scaleY:n}=o;return"scaleX("+e+") scaleY("+n+")"}},Transform:{toString(o){if(o)return[_o.Translate.toString(o),_o.Scale.toString(o)].join(" ")}},Transition:{toString(o){let{property:e,duration:n,easing:a}=o;return e+" "+n+"ms "+a}}}),km="a,frame,iframe,input:not([type=hidden]):not(:disabled),select:not(:disabled),textarea:not(:disabled),button:not(:disabled),*[tabindex]";function JC(o){return o.matches(km)?o:o.querySelector(km)}const QC={display:"none"};function ZC(o){let{id:e,value:n}=o;return Xe.createElement("div",{id:e,style:QC},n)}function eE(o){let{id:e,announcement:n,ariaLiveType:a="assertive"}=o;const i={position:"fixed",top:0,left:0,width:1,height:1,margin:-1,border:0,padding:0,overflow:"hidden",clip:"rect(0 0 0 0)",clipPath:"inset(100%)",whiteSpace:"nowrap"};return Xe.createElement("div",{id:e,style:i,role:"status","aria-live":a,"aria-atomic":!0},n)}function tE(){const[o,e]=H.useState("");return{announce:H.useCallback(a=>{a!=null&&e(a)},[]),announcement:o}}const Dm=H.createContext(null);function nE(o){const e=H.useContext(Dm);H.useEffect(()=>{if(!e)throw new Error("useDndMonitor must be used within a children of ");return e(o)},[o,e])}function rE(){const[o]=H.useState(()=>new Set),e=H.useCallback(a=>(o.add(a),()=>o.delete(a)),[o]);return[H.useCallback(a=>{let{type:i,event:l}=a;o.forEach(c=>{var d;return(d=c[i])==null?void 0:d.call(c,l)})},[o]),e]}const aE={draggable:` + `,i.onmouseenter=()=>{i.style.backgroundColor="rgba(0, 0, 0, 0.2)"},i.onmouseleave=()=>{i.style.backgroundColor="rgba(0, 0, 0, 0.1)"};const l=()=>{n.parentNode&&n.parentNode.removeChild(n)};i.onclick=l;const c=d=>{this.isImeComposing(d)||d.key==="Escape"&&(l(),document.removeEventListener("keydown",c))};return document.addEventListener("keydown",c),n.onclick=d=>{d.target===n&&l()},a.appendChild(i),n.appendChild(a),document.body.appendChild(n),{overlay:n,layer:a,closeLayer:l}}getNestedProperty(e,n){return n.split(".").reduce((a,i)=>a&&a[i]!==void 0?a[i]:void 0,e)}async handleSaveToLocalStorage(e,n){const{key:a,value:i}=e;if(!a)return z.error("saveToLocalStorage: key parameter is required"),!1;try{const l=typeof i=="string"?i:JSON.stringify(i);return localStorage.setItem(a,l),z.log(`saveToLocalStorage: saved ${a}`,i),!0}catch(l){return z.error("saveToLocalStorage: failed to save",{key:a,error:l}),!1}}async handleLoadFromLocalStorage(e,n){const{key:a,target:i,stateKey:l,defaultValue:c}=e;if(!a)return z.error("loadFromLocalStorage: key parameter is required"),c;try{const d=localStorage.getItem(a);if(d===null)return z.log(`loadFromLocalStorage: no value found for ${a}, using default`),l&&n.setState&&n.setState({[l]:c}),c;let f;try{f=JSON.parse(d)}catch{f=d}return z.log(`loadFromLocalStorage: loaded ${a}`,f),l&&n.setState&&n.setState({[l]:f}),f}catch(d){return z.error("loadFromLocalStorage: failed to load",{key:a,error:d}),c}}async handleCustomAction(e,n){const a=this.customHandlers.get(e.handler);if(!a){if(this.previewMode){z.warn(`[Preview] Unknown action handler "${e.handler}" — skipped (preview mode)`);return}const i=new jt(`Unknown action handler: ${e.handler}`,e);throw i.unknownHandler=!0,i}return await a(e,n)}resolveParams(e,n){if(!e)return{};const a={};for(const[i,l]of Object.entries(e))i.includes("{{")&&z.warn(`[resolveParams] setState params의 키에 표현식이 포함되어 있습니다: "${i}". 키는 해석되지 않습니다. 배열 항목 수정은 .map()/.filter() 패턴을 사용하세요.`),typeof l=="string"&&l.includes("{{")?a[i]=this.evaluateExpression(l,n):typeof l=="string"?a[i]=l:Array.isArray(l)?a[i]=l.map(c=>typeof c=="string"&&c.includes("{{")?this.evaluateExpression(c,n):c):typeof l=="object"&&l!==null&&!Array.isArray(l)&&Object.getPrototypeOf(l)!==Object.prototype&&Object.getPrototypeOf(l)!==null?a[i]=l:typeof l=="object"&&l!==null?a[i]=this.resolveParams(l,n):a[i]=l;return a}resolveValue(e,n){return n?this.bindingEngine.resolveBindings(e,n,{skipCache:!0}):e}evaluateExpressions(e,n){let a={};for(const[i,l]of Object.entries(e))if(i==="..."){let c=l;typeof l=="string"&&l.includes("{{")&&(c=this.evaluateExpression(l,n)),c&&typeof c=="object"&&!Array.isArray(c)&&(a={...a,...c})}else typeof l=="string"&&l.includes("{{")?a[i]=this.evaluateExpression(l,n):typeof l=="object"&&l!==null&&!Array.isArray(l)?a[i]=this.evaluateExpressions(l,n):a[i]=l;return a}evaluateExpressionsIfNeeded(e,n){const a={};for(const[i,l]of Object.entries(e))typeof l=="string"&&l.includes("{{")?a[i]=this.evaluateExpression(l,n):a[i]=l;return a}evaluateExpression(e,n){if(!n)return e;const a=Aa(e);if(a===null){let c=n;if(e.includes("_global")){const f=window.G7Core?.state?.get()?._global;f&&(c={...n,_global:f})}if(e.includes("_computed")||e.includes("$computed")){const f=window.__g7ActionContext?.computedRef?.current;f&&Object.keys(f).length>0&&(c={...c,_computed:f,$computed:f})}return this.bindingEngine.resolveBindings(e,c,{skipCache:!0})}let i=a;i=i.replace(/\$args\.(\d+)/g,"$args[$1]");let l=n;if(i.includes("_global")){const d=window.G7Core?.state?.get()?._global;d&&(l={...n,_global:d})}if(i.includes("_computed")||i.includes("$computed")){const d=window.__g7ActionContext?.computedRef?.current;d&&Object.keys(d).length>0&&(l={...l,_computed:d,$computed:d})}i.includes("_global.modules")&&(z.log("[evaluateExpression] expression:",i),z.log("[evaluateExpression] dataContext._global:",l._global),z.log("[evaluateExpression] dataContext._global?.modules:",l._global?.modules));try{const c=Pn(i)?this.bindingEngine.evaluatePipeExpression(i,l,{skipCache:!0}):this.bindingEngine.evaluateExpression(i,l);return i.includes("_global.modules")&&z.log("[evaluateExpression] result:",c),typeof c=="string"&&c.startsWith("$t:")&&this.translationEngine&&this.translationContext?this.translationEngine.resolveTranslations(c,this.translationContext,n):c}catch(c){return z.error("Expression evaluation failed:",e,c),e}}setDefaultContext(e){this.defaultContext={...this.defaultContext,...e}}setGlobalStateUpdater(e){this.globalStateUpdater=e}getGlobalStateUpdater(){return this.globalStateUpdater}async dispatchAction(e,n){const a={...this.defaultContext,...n,data:{...this.defaultContext.data,...n?.data}};if(e.confirm){let i=this.resolveValue(e.confirm,a.data);if(this.translationEngine&&this.translationContext&&i.startsWith("$t:")&&(i=this.translationEngine.resolveTranslations(i,this.translationContext,a.data)),!confirm(i))return{success:!1}}try{return await this.executeAction(e,a)}catch(i){if(z.error("dispatchAction failed:",i),e.onError){const l=i instanceof jt&&i.originalError?{message:i.originalError.message,response:i.originalError.response}:{message:i instanceof Error?i.message:String(i)},c={...a,data:{...a.data,error:l}},d=Array.isArray(e.onError)?e.onError:[e.onError];for(const f of d)await this.executeAction(f,c)}return{success:!1,error:i instanceof Error?i:new Error(String(i))}}}registerHandler(e,n,a){this.customHandlers.set(e,n);const i=qn();i?.isEnabled()&&i.trackHandlerRegistration(e,a?.category??"custom",a?.description,a?.source)}unregisterHandler(e){this.customHandlers.delete(e);const n=qn();n?.isEnabled()&&n.trackHandlerUnregistration(e)}getRegisteredHandlers(){return Array.from(this.customHandlers.keys())}getHandler(e){return this.customHandlers.get(e)}normalizeDebounceConfig(e){return typeof e=="number"?{delay:e,leading:!1,trailing:!0}:{delay:e.delay,leading:e.leading??!1,trailing:e.trailing??!0}}extractEventData(e){const n=e.target;if(n&&!("tagName"in n)){const l={type:e.type??"custom",target:{value:n.value,name:n.name??""}};return e._changedKeys&&(l._changedKeys=e._changedKeys),l}const i=n?{value:n.value,name:n.name??"",checked:n.checked??!1,type:n.type??"",tagName:n.tagName}:{};if(e.type==="scroll"&&n){const l=n;i.scrollHeight=l.scrollHeight??0,i.scrollTop=l.scrollTop??0,i.clientHeight=l.clientHeight??0,i.scrollLeft=l.scrollLeft??0,i.clientWidth=l.clientWidth??0,i.scrollWidth=l.scrollWidth??0}return{type:e.type,target:n?i:null}}executeDebouncedAction(e,n,a,i,l){const c={...a,$event:n,$args:l},d={type:n.type,target:n.target,preventDefault:()=>{},stopPropagation:()=>{}};this.createHandler(e,c,i)(d)}handleDebouncedAction(e,n,a,i,l,c){const d=this.normalizeDebounceConfig(e.debounce),f=l||`default-${e.handler}-${Date.now()}`,g=this.extractEventData(n),m=g._changedKeys;if(m&&Array.isArray(m)&&g?.target?.value&&typeof g.target.value=="object"&&!Array.isArray(g.target.value)){const _=this.debounceAccumulatedValues.get(f);if(_){const A={..._};for(const x of m)A[x]=g.target.value[x];g.target.value=A}this.debounceAccumulatedValues.set(f,g.target.value)}const b=this.debounceTimers.get(f);b&&(clearTimeout(b),this.debounceTimers.delete(f));const S=qn();S?.isEnabled()&&S.trackAction({handler:typeof e.handler=="string"?e.handler:String(e.handler),type:e.type,status:"pending",params:e.params,debounce:{delay:d.delay,status:"pending",scheduledAt:Date.now()}});const v=!this.debounceTimers.has(f+"_leading");if(d.leading&&v&&(this.debounceTimers.set(f+"_leading",setTimeout(()=>{},0)),this.executeDebouncedAction(e,g,a,i,c),S?.isEnabled()&&S.trackAction({handler:typeof e.handler=="string"?e.handler:String(e.handler),type:e.type,status:"success",params:e.params,debounce:{delay:d.delay,status:"executed",executedAt:Date.now(),mode:"leading"}})),d.trailing){const _=()=>{this.executeDebouncedAction(e,g,a,i,c),S?.isEnabled()&&S.trackAction({handler:typeof e.handler=="string"?e.handler:String(e.handler),type:e.type,status:"success",params:e.params,debounce:{delay:d.delay,status:"executed",executedAt:Date.now(),mode:"trailing"}})},A=setTimeout(()=>{this.debounceTimers.delete(f),this.debounceTimers.delete(f+"_leading"),this.pendingDebounceFlushers.delete(f),this.debounceAccumulatedValues.delete(f),_()},d.delay);this.debounceTimers.set(f,A),this.pendingDebounceFlushers.set(f,_)}}clearDebounceTimers(e){if(e)for(const[n,a]of this.debounceTimers)n.startsWith(e+"-")&&(clearTimeout(a),this.debounceTimers.delete(n),this.pendingDebounceFlushers.delete(n),this.debounceAccumulatedValues.delete(n));else{for(const n of this.debounceTimers.values())clearTimeout(n);this.debounceTimers.clear(),this.pendingDebounceFlushers.clear(),this.debounceAccumulatedValues.clear()}}debouncedCall(e,n,a){const i=this.debounceTimers.get(e);i&&(clearTimeout(i),this.debounceTimers.delete(e));const l=setTimeout(()=>{this.debounceTimers.delete(e),this.pendingDebounceFlushers.delete(e),a()},n);this.debounceTimers.set(e,l),this.pendingDebounceFlushers.set(e,a)}flushPendingDebounceTimers(){if(this.pendingDebounceFlushers.size===0)return;const e=new Map(this.pendingDebounceFlushers);for(const[n,a]of e){const i=this.debounceTimers.get(n);i&&(clearTimeout(i),this.debounceTimers.delete(n)),this.debounceTimers.delete(n+"_leading"),this.pendingDebounceFlushers.delete(n),a()}this.globalStateUpdater&&this.globalStateUpdater({})}bindActionsToProps(e,n,a){const i={...e};if(e.actions&&Array.isArray(e.actions)){const l=new Map;for(const m of e.actions){const b=this.resolveActionRef(m),S=b.event||this.getEventHandlerName(b.type);l.has(S)||l.set(S,[]),l.get(S).push(b)}const c=e.name||e.id||"unknown",d=l.has("onDrop"),f=l.has("onDragOver"),g=l.has("onDragEnter");d&&!f&&(i.onDragOver=m=>{m.preventDefault()}),d&&!g&&(i.onDragEnter=m=>{m.preventDefault()});for(const[m,b]of l)i[m]=(...S)=>{for(const v of b){const _=S[0],A=_&&typeof _=="object"&&"preventDefault"in _;if(A&&(m==="onDragOver"||m==="onDrop")&&_.preventDefault(),A&&v.key&&"key"in _){if(this.isImeComposing(_)){z.log("Key filter skipped (IME composing):",v.key);continue}if(_.key!==v.key){z.log("Key filter not matched:",v.key,"actual:",_.key);continue}z.log("Key filter matched:",v.key)}if(v.debounce){const x=`${c}-${v.handler}-${m}`,L=_&&typeof _=="object"&&!("preventDefault"in _)&&"target"in _&&_.target!==null;let M;if(A)M=_;else if(L){const D={type:"custom",target:_.target,preventDefault:()=>{},stopPropagation:()=>{}};_._changedKeys&&(D._changedKeys=_._changedKeys),M=D}else M=new Event("custom");const R=v.event?{...n,$args:S}:n;this.handleDebouncedAction(v,M,R,a,x,S)}else if(this.flushPendingDebounceTimers(),v.event){const x={...n,$args:S},L=A?_:new Event("custom");this.createHandler(v,x,a)(L)}else if(A)this.createHandler(v,n,a)(_);else if(_&&typeof _=="object"&&!("preventDefault"in _)&&"target"in _&&_.target!==null){const L={type:"custom",target:_.target,preventDefault:()=>{},stopPropagation:()=>{}};this.createHandler(v,n,a)(L)}}}}return i}getEventHandlerName(e){return{click:"onClick",change:"onChange",input:"onInput",submit:"onSubmit",focus:"onFocus",blur:"onBlur",keydown:"onKeyDown",keyup:"onKeyUp",keypress:"onKeyPress",mousedown:"onMouseDown",mouseup:"onMouseUp",mouseenter:"onMouseEnter",mouseleave:"onMouseLeave",scroll:"onScroll",dragstart:"onDragStart",drag:"onDrag",dragend:"onDragEnd",dragenter:"onDragEnter",dragover:"onDragOver",dragleave:"onDragLeave",drop:"onDrop"}[e]||`on${e.charAt(0).toUpperCase()}${e.slice(1)}`}sanitizeForDevTools(e,n=5){const a=new WeakSet,i=(l,c)=>{if(l==null||typeof l=="string"||typeof l=="number"||typeof l=="boolean")return l;if(c>n)return"[Max Depth Exceeded]";if(typeof l=="function")return`[Function: ${l.name||"anonymous"}]`;if(l instanceof Element)return`[Element: ${l.tagName}${l.className?"."+l.className.split(" ").join("."):""}]`;if(l instanceof Event)return`[Event: ${l.type}]`;if(Array.isArray(l)){if(a.has(l))return"[Circular Array]";a.add(l);const d=l.slice(0,10).map(f=>i(f,c+1));return l.length>10&&d.push(`... ${l.length-10} more items`),d}if(typeof l=="object"){if(a.has(l))return"[Circular Reference]";a.add(l);const d={},f=Object.keys(l),g=f.slice(0,20);for(const m of g)try{d[m]=i(l[m],c+1)}catch{d[m]="[Error accessing property]"}return f.length>20&&(d["..."]=`${f.length-20} more properties`),d}return String(l)};return i(e,0)}};$(ps,"loadedScripts",new Set);let po=ps,Zl=null;function cm(o){return Zl||(Zl=new po(o)),Zl}function cC(o){Zl=o}const um=ut("TransitionManager");class uC{constructor(){$(this,"isPending",!1);$(this,"subscribers",new Set)}setPending(e){this.isPending!==e&&(um.log("setPending:",{from:this.isPending,to:e,stack:new Error().stack}),this.isPending=e,this.notifySubscribers())}getIsPending(){return this.isPending}subscribe(e){return this.subscribers.add(e),()=>{this.subscribers.delete(e)}}notifySubscribers(){um.log("notifySubscribers:",{isPending:this.isPending,subscriberCount:this.subscribers.size}),this.subscribers.forEach(e=>{e(this.isPending)})}clearSubscribers(){this.subscribers.clear()}}const Hr=new uC,ec=ut("TransitionContext"),dm=N.createContext({isTransitioning:!1}),Bd=({children:o})=>{const[e,n]=N.useState(()=>{const i=Hr.getIsPending();return ec.log("Initial state:",i),i});N.useEffect(()=>{ec.log("Subscribing to TransitionManager");const i=Hr.subscribe(l=>{ec.log("Received isPending update:",l),n(l)});return()=>{ec.log("Unsubscribing from TransitionManager"),i()}},[]);const a=N.useMemo(()=>({isTransitioning:e}),[e]);return dt.jsx(dm.Provider,{value:a,children:o})},fm=()=>N.useContext(dm),tc="__g7LocalInitTracking";function Pd(){const o=globalThis;return o[tc]||(o[tc]={hash:"",timestamp:void 0,consumed:void 0}),o[tc]}function dC(){const o=globalThis;o[tc]={hash:"",timestamp:void 0,consumed:void 0}}function fC(o){Pd().consumed=o}function hC(o){return Pd().consumed===o}function gC(o){return o.globalTrackedKey!==o.trackingKey?"apply":o.instanceHandledKey===o.trackingKey?"skip":"prune"}function pC(o,e){return typeof o=="number"&&typeof e=="number"?Math.max(o,e):e??o}function mC(o,e){if(e===void 0)return o;if(!(o!==null&&typeof o=="object"&&e!==null&&typeof e=="object"&&!hC(o)))return e;const{_forceLocalInit:a,...i}=o,{_forceLocalInit:l,...c}=e,d={...i,...c},f=pC(a,l);return f!==void 0&&(d._forceLocalInit=f),d}function yC(o){return o<768?"mobile":o<1024?"tablet":"desktop"}function hm(o){const e=yC(o);return{width:o,isMobile:e==="mobile",isTablet:e==="tablet",isDesktop:e==="desktop",matchedPreset:e}}const qd=N.createContext(hm(1024)),nc=({children:o,overrideWidth:e})=>{const[n,a]=N.useState(()=>hi.getWidth());N.useEffect(()=>{const c=hi.subscribe(d=>{a(d)});return()=>{c()}},[]);const i=e??n,l=N.useMemo(()=>hm(i),[i]);return dt.jsx(qd.Provider,{value:l,children:o})},Gd=()=>N.useContext(qd);window.G7Core?.createLogger?.("Core:State");function bC(o,e,n,a){const{syncOnControlledChange:i=!0,isEqual:l}=a??{},c=o!==void 0;N.useRef(c);const[d,f]=N.useState(o!==void 0?o:e),g=d,m=N.useRef(o),b=N.useRef(!1);N.useEffect(()=>{if(b.current){m.current=o;return}i&&c&&o!==m.current&&(l?!l(d,o):d!==o)&&f(o),m.current=o},[o,c,i,l,d]);const S=N.useCallback(v=>{const _=typeof v=="function"?v(g):v;(l?l(g,_):g===_)||(b.current=!0,f(_),n?.(_),requestAnimationFrame(()=>{b.current=!1}))},[g,c,n,l]);return[g,S]}function vC(o,e){return o===e?!0:o.length!==e.length?!1:o.every((n,a)=>n===e[a])}function gm(o,e){if(o===e)return!0;const n=Object.keys(o),a=Object.keys(e);return n.length!==a.length?!1:n.every(i=>o[i]===e[i])}const pm=N.createContext({}),SC=({children:o,value:e})=>dt.jsx(pm.Provider,{value:e,children:o}),wC=()=>N.useContext(pm),CC=(o,e)=>{if(!o)return;const n=e.split(".");let a=o;for(const i of n){if(a==null)return;a=a[i]}return a},EC=(o,e,n)=>{const a=e.split(".");if(a.length===1)return{...o,[a[0]]:n};const i={...o};let l=i;for(let c=0;c{os.warn("IsolatedStateContext not available: setState called outside IsolatedStateProvider")},getState:()=>{},mergeState:()=>{os.warn("IsolatedStateContext not available: mergeState called outside IsolatedStateProvider")},stateRef:{current:{}},scopeId:""},mm=N.createContext(_C);let AC=0;function xC(){return`isolated-${++AC}-${Date.now().toString(36)}`}function ym(o,e){if(e==null)return o;if(typeof e!="object"||Array.isArray(e))return e;const n={...o};for(const a of Object.keys(e)){const i=e[a],l=o?.[a];i!==null&&typeof i=="object"&&!Array.isArray(i)&&l!==null&&typeof l=="object"&&!Array.isArray(l)?n[a]=ym(l,i):n[a]=i}return n}function TC(o,e){return e?e.split(".").reduce((n,a)=>n?.[a],o):o}function RC(o,e,n){const a=e.split("."),i={...o};let l=i;for(let c=0;c{const i=N.useRef(e||xC()).current,[l,c]=N.useState(o),d=N.useRef(l);d.current=l;const f=N.useCallback(v=>TC(d.current,v),[]),g=N.useCallback((v,_)=>{c(A=>{const x=RC(A,v,_);return os.log(`[${i}] setState: ${v} =`,_),x})},[i]),m=N.useCallback((v,_="deep")=>{c(A=>{let x;return _==="replace"?x=v:_==="shallow"?x={...A,...v}:x=ym(A,v),os.log(`[${i}] mergeState (${_}):`,v),x})},[i]),b=N.useMemo(()=>({state:l,setState:g,getState:f,mergeState:m,stateRef:d,scopeId:i}),[l,g,f,m,i]),S=N.useMemo(()=>({state:d.current,setState:g,getState:f,mergeState:m}),[g,f,m]);return N.useEffect(()=>{const v=window.G7Core,_=window.__g7IsolatedStates||{};return window.__g7IsolatedStates||(window.__g7IsolatedStates=_),_[i]=S,v&&(v._isolatedStates||(v._isolatedStates={}),v._isolatedStates[i]=l),v?.devTools?.isEnabled?.()&&v.devTools.emit?.("isolated:created",{id:i,state:l}),os.log(`[${i}] IsolatedStateProvider mounted with initial state:`,l),()=>{delete _[i],v?._isolatedStates&&delete v._isolatedStates[i],v?.devTools?.isEnabled?.()&&v.devTools.emit?.("isolated:destroyed",{id:i}),os.log(`[${i}] IsolatedStateProvider unmounted`)}},[i,S]),N.useEffect(()=>{const v=window.G7Core,_=window.__g7IsolatedStates;_?.[i]&&(_[i].state=l),v?._isolatedStates&&(v._isolatedStates[i]=l),v?.devTools?.isEnabled?.()&&v.devTools.emit?.("isolated:updated",{id:i,state:l})},[l,i]),dt.jsx(mm.Provider,{value:b,children:n})},DC=()=>{const o=N.useContext(mm);return o.scopeId?o:null};var ka=Yg();function OC(){for(var o=arguments.length,e=new Array(o),n=0;na=>{e.forEach(i=>i(a))},e)}const rc=typeof window<"u"&&typeof window.document<"u"&&typeof window.document.createElement<"u";function ls(o){const e=Object.prototype.toString.call(o);return e==="[object Window]"||e==="[object global]"}function Vd(o){return"nodeType"in o}function Ln(o){var e,n;return o?ls(o)?o:Vd(o)&&(e=(n=o.ownerDocument)==null?void 0:n.defaultView)!=null?e:window:window}function Fd(o){const{Document:e}=Ln(o);return o instanceof e}function mo(o){return ls(o)?!1:o instanceof Ln(o).HTMLElement}function bm(o){return o instanceof Ln(o).SVGElement}function cs(o){return o?ls(o)?o.document:Vd(o)?Fd(o)?o:mo(o)||bm(o)?o.ownerDocument:document:document:document}const zr=rc?N.useLayoutEffect:N.useEffect;function Kd(o){const e=N.useRef(o);return zr(()=>{e.current=o}),N.useCallback(function(){for(var n=arguments.length,a=new Array(n),i=0;i{o.current=setInterval(a,i)},[]),n=N.useCallback(()=>{o.current!==null&&(clearInterval(o.current),o.current=null)},[]);return[e,n]}function yo(o,e){e===void 0&&(e=[o]);const n=N.useRef(o);return zr(()=>{n.current!==o&&(n.current=o)},e),n}function bo(o,e){const n=N.useRef();return N.useMemo(()=>{const a=o(n.current);return n.current=a,a},[...e])}function ac(o){const e=Kd(o),n=N.useRef(null),a=N.useCallback(i=>{i!==n.current&&e?.(i,n.current),n.current=i},[]);return[n,a]}function Wd(o){const e=N.useRef();return N.useEffect(()=>{e.current=o},[o]),e.current}let Yd={};function vo(o,e){return N.useMemo(()=>{if(e)return e;const n=Yd[o]==null?0:Yd[o]+1;return Yd[o]=n,o+"-"+n},[o,e])}function vm(o){return function(e){for(var n=arguments.length,a=new Array(n>1?n-1:0),i=1;i{const d=Object.entries(c);for(const[f,g]of d){const m=l[f];m!=null&&(l[f]=m+o*g)}return l},{...e})}}const us=vm(1),So=vm(-1);function MC(o){return"clientX"in o&&"clientY"in o}function Xd(o){if(!o)return!1;const{KeyboardEvent:e}=Ln(o.target);return e&&o instanceof e}function $C(o){if(!o)return!1;const{TouchEvent:e}=Ln(o.target);return e&&o instanceof e}function Jd(o){if($C(o)){if(o.touches&&o.touches.length){const{clientX:e,clientY:n}=o.touches[0];return{x:e,y:n}}else if(o.changedTouches&&o.changedTouches.length){const{clientX:e,clientY:n}=o.changedTouches[0];return{x:e,y:n}}}return MC(o)?{x:o.clientX,y:o.clientY}:null}const wo=Object.freeze({Translate:{toString(o){if(!o)return;const{x:e,y:n}=o;return"translate3d("+(e?Math.round(e):0)+"px, "+(n?Math.round(n):0)+"px, 0)"}},Scale:{toString(o){if(!o)return;const{scaleX:e,scaleY:n}=o;return"scaleX("+e+") scaleY("+n+")"}},Transform:{toString(o){if(o)return[wo.Translate.toString(o),wo.Scale.toString(o)].join(" ")}},Transition:{toString(o){let{property:e,duration:n,easing:a}=o;return e+" "+n+"ms "+a}}}),Sm="a,frame,iframe,input:not([type=hidden]):not(:disabled),select:not(:disabled),textarea:not(:disabled),button:not(:disabled),*[tabindex]";function NC(o){return o.matches(Sm)?o:o.querySelector(Sm)}const IC={display:"none"};function HC(o){let{id:e,value:n}=o;return Ye.createElement("div",{id:e,style:IC},n)}function zC(o){let{id:e,announcement:n,ariaLiveType:a="assertive"}=o;const i={position:"fixed",top:0,left:0,width:1,height:1,margin:-1,border:0,padding:0,overflow:"hidden",clip:"rect(0 0 0 0)",clipPath:"inset(100%)",whiteSpace:"nowrap"};return Ye.createElement("div",{id:e,style:i,role:"status","aria-live":a,"aria-atomic":!0},n)}function jC(){const[o,e]=N.useState("");return{announce:N.useCallback(a=>{a!=null&&e(a)},[]),announcement:o}}const wm=N.createContext(null);function UC(o){const e=N.useContext(wm);N.useEffect(()=>{if(!e)throw new Error("useDndMonitor must be used within a children of ");return e(o)},[o,e])}function BC(){const[o]=N.useState(()=>new Set),e=N.useCallback(a=>(o.add(a),()=>o.delete(a)),[o]);return[N.useCallback(a=>{let{type:i,event:l}=a;o.forEach(c=>{var d;return(d=c[i])==null?void 0:d.call(c,l)})},[o]),e]}const PC={draggable:` To pick up a draggable item, press the space bar. While dragging, use the arrow keys to move the item. Press space again to drop the item in its new position, or press escape to cancel. - `},iE={onDragStart(o){let{active:e}=o;return"Picked up draggable item "+e.id+"."},onDragOver(o){let{active:e,over:n}=o;return n?"Draggable item "+e.id+" was moved over droppable area "+n.id+".":"Draggable item "+e.id+" is no longer over a droppable area."},onDragEnd(o){let{active:e,over:n}=o;return n?"Draggable item "+e.id+" was dropped over droppable area "+n.id:"Draggable item "+e.id+" was dropped."},onDragCancel(o){let{active:e}=o;return"Dragging was cancelled. Draggable item "+e.id+" was dropped."}};function sE(o){let{announcements:e=iE,container:n,hiddenTextDescribedById:a,screenReaderInstructions:i=aE}=o;const{announce:l,announcement:c}=tE(),d=Co("DndLiveRegion"),[f,g]=H.useState(!1);if(H.useEffect(()=>{g(!0)},[]),nE(H.useMemo(()=>({onDragStart(y){let{active:S}=y;l(e.onDragStart({active:S}))},onDragMove(y){let{active:S,over:v}=y;e.onDragMove&&l(e.onDragMove({active:S,over:v}))},onDragOver(y){let{active:S,over:v}=y;l(e.onDragOver({active:S,over:v}))},onDragEnd(y){let{active:S,over:v}=y;l(e.onDragEnd({active:S,over:v}))},onDragCancel(y){let{active:S,over:v}=y;l(e.onDragCancel({active:S,over:v}))}}),[l,e])),!f)return null;const m=Xe.createElement(Xe.Fragment,null,Xe.createElement(ZC,{id:a,value:i.draggable}),Xe.createElement(eE,{id:d,announcement:c}));return n?ka.createPortal(m,n):m}var an;(function(o){o.DragStart="dragStart",o.DragMove="dragMove",o.DragEnd="dragEnd",o.DragCancel="dragCancel",o.DragOver="dragOver",o.RegisterDroppable="registerDroppable",o.SetDroppableDisabled="setDroppableDisabled",o.UnregisterDroppable="unregisterDroppable"})(an||(an={}));function lc(){}function Om(o,e){return H.useMemo(()=>({sensor:o,options:e??{}}),[o,e])}function oE(){for(var o=arguments.length,e=new Array(o),n=0;n[...e].filter(a=>a!=null),[...e])}const wr=Object.freeze({x:0,y:0});function Lm(o,e){return Math.sqrt(Math.pow(o.x-e.x,2)+Math.pow(o.y-e.y,2))}function Mm(o,e){let{data:{value:n}}=o,{data:{value:a}}=e;return n-a}function lE(o,e){let{data:{value:n}}=o,{data:{value:a}}=e;return a-n}function $m(o){let{left:e,top:n,height:a,width:i}=o;return[{x:e,y:n},{x:e+i,y:n},{x:e,y:n+a},{x:e+i,y:n+a}]}function Nm(o,e){if(!o||o.length===0)return null;const[n]=o;return n[e]}function Im(o,e,n){return e===void 0&&(e=o.left),n===void 0&&(n=o.top),{x:e+o.width*.5,y:n+o.height*.5}}const cE=o=>{let{collisionRect:e,droppableRects:n,droppableContainers:a}=o;const i=Im(e,e.left,e.top),l=[];for(const c of a){const{id:d}=c,f=n.get(d);if(f){const g=Lm(Im(f),i);l.push({id:d,data:{droppableContainer:c,value:g}})}}return l.sort(Mm)},uE=o=>{let{collisionRect:e,droppableRects:n,droppableContainers:a}=o;const i=$m(e),l=[];for(const c of a){const{id:d}=c,f=n.get(d);if(f){const g=$m(f),m=i.reduce((S,v,_)=>S+Lm(g[_],v),0),y=Number((m/4).toFixed(4));l.push({id:d,data:{droppableContainer:c,value:y}})}}return l.sort(Mm)};function dE(o,e){const n=Math.max(e.top,o.top),a=Math.max(e.left,o.left),i=Math.min(e.left+e.width,o.left+o.width),l=Math.min(e.top+e.height,o.top+o.height),c=i-a,d=l-n;if(a{let{collisionRect:e,droppableRects:n,droppableContainers:a}=o;const i=[];for(const l of a){const{id:c}=l,d=n.get(c);if(d){const f=dE(d,e);f>0&&i.push({id:c,data:{droppableContainer:l,value:f}})}}return i.sort(lE)};function hE(o,e,n){return{...o,scaleX:e&&n?e.width/n.width:1,scaleY:e&&n?e.height/n.height:1}}function Hm(o,e){return o&&e?{x:o.left-e.left,y:o.top-e.top}:wr}function gE(o){return function(n){for(var a=arguments.length,i=new Array(a>1?a-1:0),l=1;l({...c,top:c.top+o*d.y,bottom:c.bottom+o*d.y,left:c.left+o*d.x,right:c.right+o*d.x}),{...n})}}const pE=gE(1);function mE(o){if(o.startsWith("matrix3d(")){const e=o.slice(9,-1).split(/, /);return{x:+e[12],y:+e[13],scaleX:+e[0],scaleY:+e[5]}}else if(o.startsWith("matrix(")){const e=o.slice(7,-1).split(/, /);return{x:+e[4],y:+e[5],scaleX:+e[0],scaleY:+e[3]}}return null}function yE(o,e,n){const a=mE(e);if(!a)return o;const{scaleX:i,scaleY:l,x:c,y:d}=a,f=o.left-c-(1-i)*parseFloat(n),g=o.top-d-(1-l)*parseFloat(n.slice(n.indexOf(" ")+1)),m=i?o.width/i:o.width,y=l?o.height/l:o.height;return{width:m,height:y,top:g,right:f+m,bottom:g+y,left:f}}const bE={ignoreTransform:!1};function gs(o,e){e===void 0&&(e=bE);let n=o.getBoundingClientRect();if(e.ignoreTransform){const{transform:g,transformOrigin:m}=Nn(o).getComputedStyle(o);g&&(n=yE(n,g,m))}const{top:a,left:i,width:l,height:c,bottom:d,right:f}=n;return{top:a,left:i,width:l,height:c,bottom:d,right:f}}function jm(o){return gs(o,{ignoreTransform:!0})}function vE(o){const e=o.innerWidth,n=o.innerHeight;return{top:0,left:0,right:e,bottom:n,width:e,height:n}}function SE(o,e){return e===void 0&&(e=Nn(o).getComputedStyle(o)),e.position==="fixed"}function wE(o,e){e===void 0&&(e=Nn(o).getComputedStyle(o));const n=/(auto|scroll|overlay)/;return["overflow","overflowX","overflowY"].some(i=>{const l=e[i];return typeof l=="string"?n.test(l):!1})}function cc(o,e){const n=[];function a(i){if(e!=null&&n.length>=e||!i)return n;if(Xd(i)&&i.scrollingElement!=null&&!n.includes(i.scrollingElement))return n.push(i.scrollingElement),n;if(!vo(i)||Tm(i)||n.includes(i))return n;const l=Nn(o).getComputedStyle(i);return i!==o&&wE(i,l)&&n.push(i),SE(i,l)?n:a(i.parentNode)}return o?a(o):n}function zm(o){const[e]=cc(o,1);return e??null}function nf(o){return!sc||!o?null:ds(o)?o:Yd(o)?Xd(o)||o===fs(o).scrollingElement?window:vo(o)?o:null:null}function Um(o){return ds(o)?o.scrollX:o.scrollLeft}function Pm(o){return ds(o)?o.scrollY:o.scrollTop}function rf(o){return{x:Um(o),y:Pm(o)}}var gn;(function(o){o[o.Forward=1]="Forward",o[o.Backward=-1]="Backward"})(gn||(gn={}));function Bm(o){return!sc||!o?!1:o===document.scrollingElement}function qm(o){const e={x:0,y:0},n=Bm(o)?{height:window.innerHeight,width:window.innerWidth}:{height:o.clientHeight,width:o.clientWidth},a={x:o.scrollWidth-n.width,y:o.scrollHeight-n.height},i=o.scrollTop<=e.y,l=o.scrollLeft<=e.x,c=o.scrollTop>=a.y,d=o.scrollLeft>=a.x;return{isTop:i,isLeft:l,isBottom:c,isRight:d,maxScroll:a,minScroll:e}}const CE={x:.2,y:.2};function EE(o,e,n,a,i){let{top:l,left:c,right:d,bottom:f}=n;a===void 0&&(a=10),i===void 0&&(i=CE);const{isTop:g,isBottom:m,isLeft:y,isRight:S}=qm(o),v={x:0,y:0},_={x:0,y:0},A={height:e.height*i.y,width:e.width*i.x};return!g&&l<=e.top+A.height?(v.y=gn.Backward,_.y=a*Math.abs((e.top+A.height-l)/A.height)):!m&&f>=e.bottom-A.height&&(v.y=gn.Forward,_.y=a*Math.abs((e.bottom-A.height-f)/A.height)),!S&&d>=e.right-A.width?(v.x=gn.Forward,_.x=a*Math.abs((e.right-A.width-d)/A.width)):!y&&c<=e.left+A.width&&(v.x=gn.Backward,_.x=a*Math.abs((e.left+A.width-c)/A.width)),{direction:v,speed:_}}function _E(o){if(o===document.scrollingElement){const{innerWidth:l,innerHeight:c}=window;return{top:0,left:0,right:l,bottom:c,width:l,height:c}}const{top:e,left:n,right:a,bottom:i}=o.getBoundingClientRect();return{top:e,left:n,right:a,bottom:i,width:o.clientWidth,height:o.clientHeight}}function Gm(o){return o.reduce((e,n)=>hs(e,rf(n)),wr)}function AE(o){return o.reduce((e,n)=>e+Um(n),0)}function xE(o){return o.reduce((e,n)=>e+Pm(n),0)}function TE(o,e){if(e===void 0&&(e=gs),!o)return;const{top:n,left:a,bottom:i,right:l}=e(o);zm(o)&&(i<=0||l<=0||n>=window.innerHeight||a>=window.innerWidth)&&o.scrollIntoView({block:"center",inline:"center"})}const RE=[["x",["left","right"],AE],["y",["top","bottom"],xE]];class af{constructor(e,n){this.rect=void 0,this.width=void 0,this.height=void 0,this.top=void 0,this.bottom=void 0,this.right=void 0,this.left=void 0;const a=cc(n),i=Gm(a);this.rect={...e},this.width=e.width,this.height=e.height;for(const[l,c,d]of RE)for(const f of c)Object.defineProperty(this,f,{get:()=>{const g=d(a),m=i[l]-g;return this.rect[f]+m},enumerable:!0});Object.defineProperty(this,"rect",{enumerable:!1})}}class Ao{constructor(e){this.target=void 0,this.listeners=[],this.removeAll=()=>{this.listeners.forEach(n=>{var a;return(a=this.target)==null?void 0:a.removeEventListener(...n)})},this.target=e}add(e,n,a){var i;(i=this.target)==null||i.addEventListener(e,n,a),this.listeners.push([e,n,a])}}function kE(o){const{EventTarget:e}=Nn(o);return o instanceof e?o:fs(o)}function sf(o,e){const n=Math.abs(o.x),a=Math.abs(o.y);return typeof e=="number"?Math.sqrt(n**2+a**2)>e:"x"in e&&"y"in e?n>e.x&&a>e.y:"x"in e?n>e.x:"y"in e?a>e.y:!1}var ar;(function(o){o.Click="click",o.DragStart="dragstart",o.Keydown="keydown",o.ContextMenu="contextmenu",o.Resize="resize",o.SelectionChange="selectionchange",o.VisibilityChange="visibilitychange"})(ar||(ar={}));function Vm(o){o.preventDefault()}function DE(o){o.stopPropagation()}var ut;(function(o){o.Space="Space",o.Down="ArrowDown",o.Right="ArrowRight",o.Left="ArrowLeft",o.Up="ArrowUp",o.Esc="Escape",o.Enter="Enter",o.Tab="Tab"})(ut||(ut={}));const Fm={start:[ut.Space,ut.Enter],cancel:[ut.Esc],end:[ut.Space,ut.Enter,ut.Tab]},OE=(o,e)=>{let{currentCoordinates:n}=e;switch(o.code){case ut.Right:return{...n,x:n.x+25};case ut.Left:return{...n,x:n.x-25};case ut.Down:return{...n,y:n.y+25};case ut.Up:return{...n,y:n.y-25}}};class of{constructor(e){this.props=void 0,this.autoScrollEnabled=!1,this.referenceCoordinates=void 0,this.listeners=void 0,this.windowListeners=void 0,this.props=e;const{event:{target:n}}=e;this.props=e,this.listeners=new Ao(fs(n)),this.windowListeners=new Ao(Nn(n)),this.handleKeyDown=this.handleKeyDown.bind(this),this.handleCancel=this.handleCancel.bind(this),this.attach()}attach(){this.handleStart(),this.windowListeners.add(ar.Resize,this.handleCancel),this.windowListeners.add(ar.VisibilityChange,this.handleCancel),setTimeout(()=>this.listeners.add(ar.Keydown,this.handleKeyDown))}handleStart(){const{activeNode:e,onStart:n}=this.props,a=e.node.current;a&&TE(a),n(wr)}handleKeyDown(e){if(ef(e)){const{active:n,context:a,options:i}=this.props,{keyboardCodes:l=Fm,coordinateGetter:c=OE,scrollBehavior:d="smooth"}=i,{code:f}=e;if(l.end.includes(f)){this.handleEnd(e);return}if(l.cancel.includes(f)){this.handleCancel(e);return}const{collisionRect:g}=a.current,m=g?{x:g.left,y:g.top}:wr;this.referenceCoordinates||(this.referenceCoordinates=m);const y=c(e,{active:n,context:a.current,currentCoordinates:m});if(y){const S=Eo(y,m),v={x:0,y:0},{scrollableAncestors:_}=a.current;for(const A of _){const x=e.code,{isTop:O,isRight:M,isLeft:T,isBottom:D,maxScroll:z,minScroll:P}=qm(A),q=_E(A),W={x:Math.min(x===ut.Right?q.right-q.width/2:q.right,Math.max(x===ut.Right?q.left:q.left+q.width/2,y.x)),y:Math.min(x===ut.Down?q.bottom-q.height/2:q.bottom,Math.max(x===ut.Down?q.top:q.top+q.height/2,y.y))},he=x===ut.Right&&!M||x===ut.Left&&!T,Ee=x===ut.Down&&!D||x===ut.Up&&!O;if(he&&W.x!==y.x){const be=A.scrollLeft+S.x,Ue=x===ut.Right&&be<=z.x||x===ut.Left&&be>=P.x;if(Ue&&!S.y){A.scrollTo({left:be,behavior:d});return}Ue?v.x=A.scrollLeft-be:v.x=x===ut.Right?A.scrollLeft-z.x:A.scrollLeft-P.x,v.x&&A.scrollBy({left:-v.x,behavior:d});break}else if(Ee&&W.y!==y.y){const be=A.scrollTop+S.y,Ue=x===ut.Down&&be<=z.y||x===ut.Up&&be>=P.y;if(Ue&&!S.x){A.scrollTo({top:be,behavior:d});return}Ue?v.y=A.scrollTop-be:v.y=x===ut.Down?A.scrollTop-z.y:A.scrollTop-P.y,v.y&&A.scrollBy({top:-v.y,behavior:d});break}}this.handleMove(e,hs(Eo(y,this.referenceCoordinates),v))}}}handleMove(e,n){const{onMove:a}=this.props;e.preventDefault(),a(n)}handleEnd(e){const{onEnd:n}=this.props;e.preventDefault(),this.detach(),n()}handleCancel(e){const{onCancel:n}=this.props;e.preventDefault(),this.detach(),n()}detach(){this.listeners.removeAll(),this.windowListeners.removeAll()}}of.activators=[{eventName:"onKeyDown",handler:(o,e,n)=>{let{keyboardCodes:a=Fm,onActivation:i}=e,{active:l}=n;const{code:c}=o.nativeEvent;if(a.start.includes(c)){const d=l.activatorNode.current;return d&&o.target!==d?!1:(o.preventDefault(),i?.({event:o.nativeEvent}),!0)}return!1}}];function Km(o){return!!(o&&"distance"in o)}function Wm(o){return!!(o&&"delay"in o)}class lf{constructor(e,n,a){var i;a===void 0&&(a=kE(e.event.target)),this.props=void 0,this.events=void 0,this.autoScrollEnabled=!0,this.document=void 0,this.activated=!1,this.initialCoordinates=void 0,this.timeoutId=null,this.listeners=void 0,this.documentListeners=void 0,this.windowListeners=void 0,this.props=e,this.events=n;const{event:l}=e,{target:c}=l;this.props=e,this.events=n,this.document=fs(c),this.documentListeners=new Ao(this.document),this.listeners=new Ao(a),this.windowListeners=new Ao(Nn(c)),this.initialCoordinates=(i=tf(l))!=null?i:wr,this.handleStart=this.handleStart.bind(this),this.handleMove=this.handleMove.bind(this),this.handleEnd=this.handleEnd.bind(this),this.handleCancel=this.handleCancel.bind(this),this.handleKeydown=this.handleKeydown.bind(this),this.removeTextSelection=this.removeTextSelection.bind(this),this.attach()}attach(){const{events:e,props:{options:{activationConstraint:n,bypassActivationConstraint:a}}}=this;if(this.listeners.add(e.move.name,this.handleMove,{passive:!1}),this.listeners.add(e.end.name,this.handleEnd),e.cancel&&this.listeners.add(e.cancel.name,this.handleCancel),this.windowListeners.add(ar.Resize,this.handleCancel),this.windowListeners.add(ar.DragStart,Vm),this.windowListeners.add(ar.VisibilityChange,this.handleCancel),this.windowListeners.add(ar.ContextMenu,Vm),this.documentListeners.add(ar.Keydown,this.handleKeydown),n){if(a!=null&&a({event:this.props.event,activeNode:this.props.activeNode,options:this.props.options}))return this.handleStart();if(Wm(n)){this.timeoutId=setTimeout(this.handleStart,n.delay),this.handlePending(n);return}if(Km(n)){this.handlePending(n);return}}this.handleStart()}detach(){this.listeners.removeAll(),this.windowListeners.removeAll(),setTimeout(this.documentListeners.removeAll,50),this.timeoutId!==null&&(clearTimeout(this.timeoutId),this.timeoutId=null)}handlePending(e,n){const{active:a,onPending:i}=this.props;i(a,e,this.initialCoordinates,n)}handleStart(){const{initialCoordinates:e}=this,{onStart:n}=this.props;e&&(this.activated=!0,this.documentListeners.add(ar.Click,DE,{capture:!0}),this.removeTextSelection(),this.documentListeners.add(ar.SelectionChange,this.removeTextSelection),n(e))}handleMove(e){var n;const{activated:a,initialCoordinates:i,props:l}=this,{onMove:c,options:{activationConstraint:d}}=l;if(!i)return;const f=(n=tf(e))!=null?n:wr,g=Eo(i,f);if(!a&&d){if(Km(d)){if(d.tolerance!=null&&sf(g,d.tolerance))return this.handleCancel();if(sf(g,d.distance))return this.handleStart()}if(Wm(d)&&sf(g,d.tolerance))return this.handleCancel();this.handlePending(d,g);return}e.cancelable&&e.preventDefault(),c(f)}handleEnd(){const{onAbort:e,onEnd:n}=this.props;this.detach(),this.activated||e(this.props.active),n()}handleCancel(){const{onAbort:e,onCancel:n}=this.props;this.detach(),this.activated||e(this.props.active),n()}handleKeydown(e){e.code===ut.Esc&&this.handleCancel()}removeTextSelection(){var e;(e=this.document.getSelection())==null||e.removeAllRanges()}}const LE={cancel:{name:"pointercancel"},move:{name:"pointermove"},end:{name:"pointerup"}};class cf extends lf{constructor(e){const{event:n}=e,a=fs(n.target);super(e,LE,a)}}cf.activators=[{eventName:"onPointerDown",handler:(o,e)=>{let{nativeEvent:n}=o,{onActivation:a}=e;return!n.isPrimary||n.button!==0?!1:(a?.({event:n}),!0)}}];const ME={move:{name:"mousemove"},end:{name:"mouseup"}};var uf;(function(o){o[o.RightClick=2]="RightClick"})(uf||(uf={}));class $E extends lf{constructor(e){super(e,ME,fs(e.event.target))}}$E.activators=[{eventName:"onMouseDown",handler:(o,e)=>{let{nativeEvent:n}=o,{onActivation:a}=e;return n.button===uf.RightClick?!1:(a?.({event:n}),!0)}}];const df={cancel:{name:"touchcancel"},move:{name:"touchmove"},end:{name:"touchend"}};class NE extends lf{constructor(e){super(e,df)}static setup(){return window.addEventListener(df.move.name,e,{capture:!1,passive:!1}),function(){window.removeEventListener(df.move.name,e)};function e(){}}}NE.activators=[{eventName:"onTouchStart",handler:(o,e)=>{let{nativeEvent:n}=o,{onActivation:a}=e;const{touches:i}=n;return i.length>1?!1:(a?.({event:n}),!0)}}];var xo;(function(o){o[o.Pointer=0]="Pointer",o[o.DraggableRect=1]="DraggableRect"})(xo||(xo={}));var uc;(function(o){o[o.TreeOrder=0]="TreeOrder",o[o.ReversedTreeOrder=1]="ReversedTreeOrder"})(uc||(uc={}));function IE(o){let{acceleration:e,activator:n=xo.Pointer,canScroll:a,draggingRect:i,enabled:l,interval:c=5,order:d=uc.TreeOrder,pointerCoordinates:f,scrollableAncestors:g,scrollableAncestorRects:m,delta:y,threshold:S}=o;const v=jE({delta:y,disabled:!l}),[_,A]=WC(),x=H.useRef({x:0,y:0}),O=H.useRef({x:0,y:0}),M=H.useMemo(()=>{switch(n){case xo.Pointer:return f?{top:f.y,bottom:f.y,left:f.x,right:f.x}:null;case xo.DraggableRect:return i}},[n,i,f]),T=H.useRef(null),D=H.useCallback(()=>{const P=T.current;if(!P)return;const q=x.current.x*O.current.x,W=x.current.y*O.current.y;P.scrollBy(q,W)},[]),z=H.useMemo(()=>d===uc.TreeOrder?[...g].reverse():g,[d,g]);H.useEffect(()=>{if(!l||!g.length||!M){A();return}for(const P of z){if(a?.(P)===!1)continue;const q=g.indexOf(P),W=m[q];if(!W)continue;const{direction:he,speed:Ee}=EE(P,W,M,e,S);for(const be of["x","y"])v[be][he[be]]||(Ee[be]=0,he[be]=0);if(Ee.x>0||Ee.y>0){A(),T.current=P,_(D,c),x.current=Ee,O.current=he;return}}x.current={x:0,y:0},O.current={x:0,y:0},A()},[e,D,a,A,l,c,JSON.stringify(M),JSON.stringify(v),_,g,z,m,JSON.stringify(S)])}const HE={x:{[gn.Backward]:!1,[gn.Forward]:!1},y:{[gn.Backward]:!1,[gn.Forward]:!1}};function jE(o){let{delta:e,disabled:n}=o;const a=Qd(e);return wo(i=>{if(n||!a||!i)return HE;const l={x:Math.sign(e.x-a.x),y:Math.sign(e.y-a.y)};return{x:{[gn.Backward]:i.x[gn.Backward]||l.x===-1,[gn.Forward]:i.x[gn.Forward]||l.x===1},y:{[gn.Backward]:i.y[gn.Backward]||l.y===-1,[gn.Forward]:i.y[gn.Forward]||l.y===1}}},[n,e,a])}function zE(o,e){const n=e!=null?o.get(e):void 0,a=n?n.node.current:null;return wo(i=>{var l;return e==null?null:(l=a??i)!=null?l:null},[a,e])}function UE(o,e){return H.useMemo(()=>o.reduce((n,a)=>{const{sensor:i}=a,l=i.activators.map(c=>({eventName:c.eventName,handler:e(c.handler,a)}));return[...n,...l]},[]),[o,e])}var To;(function(o){o[o.Always=0]="Always",o[o.BeforeDragging=1]="BeforeDragging",o[o.WhileDragging=2]="WhileDragging"})(To||(To={}));var ff;(function(o){o.Optimized="optimized"})(ff||(ff={}));const Ym=new Map;function PE(o,e){let{dragging:n,dependencies:a,config:i}=e;const[l,c]=H.useState(null),{frequency:d,measure:f,strategy:g}=i,m=H.useRef(o),y=x(),S=So(y),v=H.useCallback(function(O){O===void 0&&(O=[]),!S.current&&c(M=>M===null?O:M.concat(O.filter(T=>!M.includes(T))))},[S]),_=H.useRef(null),A=wo(O=>{if(y&&!n)return Ym;if(!O||O===Ym||m.current!==o||l!=null){const M=new Map;for(let T of o){if(!T)continue;if(l&&l.length>0&&!l.includes(T.id)&&T.rect.current){M.set(T.id,T.rect.current);continue}const D=T.node.current,z=D?new af(f(D),D):null;T.rect.current=z,z&&M.set(T.id,z)}return M}return O},[o,l,n,y,f]);return H.useEffect(()=>{m.current=o},[o]),H.useEffect(()=>{y||v()},[n,y]),H.useEffect(()=>{l&&l.length>0&&c(null)},[JSON.stringify(l)]),H.useEffect(()=>{y||typeof d!="number"||_.current!==null||(_.current=setTimeout(()=>{v(),_.current=null},d))},[d,y,v,...a]),{droppableRects:A,measureDroppableContainers:v,measuringScheduled:l!=null};function x(){switch(g){case To.Always:return!1;case To.BeforeDragging:return n;default:return!n}}}function Xm(o,e){return wo(n=>o?n||(typeof e=="function"?e(o):o):null,[e,o])}function BE(o,e){return Xm(o,e)}function qE(o){let{callback:e,disabled:n}=o;const a=Jd(e),i=H.useMemo(()=>{if(n||typeof window>"u"||typeof window.MutationObserver>"u")return;const{MutationObserver:l}=window;return new l(a)},[a,n]);return H.useEffect(()=>()=>i?.disconnect(),[i]),i}function dc(o){let{callback:e,disabled:n}=o;const a=Jd(e),i=H.useMemo(()=>{if(n||typeof window>"u"||typeof window.ResizeObserver>"u")return;const{ResizeObserver:l}=window;return new l(a)},[n]);return H.useEffect(()=>()=>i?.disconnect(),[i]),i}function GE(o){return new af(gs(o),o)}function Jm(o,e,n){e===void 0&&(e=GE);const[a,i]=H.useState(null);function l(){i(f=>{if(!o)return null;if(o.isConnected===!1){var g;return(g=f??n)!=null?g:null}const m=e(o);return JSON.stringify(f)===JSON.stringify(m)?f:m})}const c=qE({callback(f){if(o)for(const g of f){const{type:m,target:y}=g;if(m==="childList"&&y instanceof HTMLElement&&y.contains(o)){l();break}}}}),d=dc({callback:l});return zr(()=>{l(),o?(d?.observe(o),c?.observe(document.body,{childList:!0,subtree:!0})):(d?.disconnect(),c?.disconnect())},[o]),a}function VE(o){const e=Xm(o);return Hm(o,e)}const Qm=[];function FE(o){const e=H.useRef(o),n=wo(a=>o?a&&a!==Qm&&o&&e.current&&o.parentNode===e.current.parentNode?a:cc(o):Qm,[o]);return H.useEffect(()=>{e.current=o},[o]),n}function KE(o){const[e,n]=H.useState(null),a=H.useRef(o),i=H.useCallback(l=>{const c=nf(l.target);c&&n(d=>d?(d.set(c,rf(c)),new Map(d)):null)},[]);return H.useEffect(()=>{const l=a.current;if(o!==l){c(l);const d=o.map(f=>{const g=nf(f);return g?(g.addEventListener("scroll",i,{passive:!0}),[g,rf(g)]):null}).filter(f=>f!=null);n(d.length?new Map(d):null),a.current=o}return()=>{c(o),c(l)};function c(d){d.forEach(f=>{const g=nf(f);g?.removeEventListener("scroll",i)})}},[i,o]),H.useMemo(()=>o.length?e?Array.from(e.values()).reduce((l,c)=>hs(l,c),wr):Gm(o):wr,[o,e])}function Zm(o,e){e===void 0&&(e=[]);const n=H.useRef(null);return H.useEffect(()=>{n.current=null},e),H.useEffect(()=>{const a=o!==wr;a&&!n.current&&(n.current=o),!a&&n.current&&(n.current=null)},[o]),n.current?Eo(o,n.current):wr}function WE(o){H.useEffect(()=>{if(!sc)return;const e=o.map(n=>{let{sensor:a}=n;return a.setup==null?void 0:a.setup()});return()=>{for(const n of e)n?.()}},o.map(e=>{let{sensor:n}=e;return n}))}function YE(o,e){return H.useMemo(()=>o.reduce((n,a)=>{let{eventName:i,handler:l}=a;return n[i]=c=>{l(c,e)},n},{}),[o,e])}function ey(o){return H.useMemo(()=>o?vE(o):null,[o])}const ty=[];function XE(o,e){e===void 0&&(e=gs);const[n]=o,a=ey(n?Nn(n):null),[i,l]=H.useState(ty);function c(){l(()=>o.length?o.map(f=>Bm(f)?a:new af(e(f),f)):ty)}const d=dc({callback:c});return zr(()=>{d?.disconnect(),c(),o.forEach(f=>d?.observe(f))},[o]),i}function JE(o){if(!o)return null;if(o.children.length>1)return o;const e=o.children[0];return vo(e)?e:o}function QE(o){let{measure:e}=o;const[n,a]=H.useState(null),i=H.useCallback(g=>{for(const{target:m}of g)if(vo(m)){a(y=>{const S=e(m);return y?{...y,width:S.width,height:S.height}:S});break}},[e]),l=dc({callback:i}),c=H.useCallback(g=>{const m=JE(g);l?.disconnect(),m&&l?.observe(m),a(m?e(m):null)},[e,l]),[d,f]=oc(c);return H.useMemo(()=>({nodeRef:d,rect:n,setRef:f}),[n,d,f])}const ZE=[{sensor:cf,options:{}},{sensor:of,options:{}}],e_={current:{}},fc={draggable:{measure:jm},droppable:{measure:jm,strategy:To.WhileDragging,frequency:ff.Optimized},dragOverlay:{measure:gs}};class Ro extends Map{get(e){var n;return e!=null&&(n=super.get(e))!=null?n:void 0}toArray(){return Array.from(this.values())}getEnabled(){return this.toArray().filter(e=>{let{disabled:n}=e;return!n})}getNodeFor(e){var n,a;return(n=(a=this.get(e))==null?void 0:a.node.current)!=null?n:void 0}}const t_={activatorEvent:null,active:null,activeNode:null,activeNodeRect:null,collisions:null,containerNodeRect:null,draggableNodes:new Map,droppableRects:new Map,droppableContainers:new Ro,over:null,dragOverlay:{nodeRef:{current:null},rect:null,setRef:lc},scrollableAncestors:[],scrollableAncestorRects:[],measuringConfiguration:fc,measureDroppableContainers:lc,windowRect:null,measuringScheduled:!1},n_={activatorEvent:null,activators:[],active:null,activeNodeRect:null,ariaDescribedById:{draggable:""},dispatch:lc,draggableNodes:new Map,over:null,measureDroppableContainers:lc},hc=H.createContext(n_),ny=H.createContext(t_);function r_(){return{draggable:{active:null,initialCoordinates:{x:0,y:0},nodes:new Map,translate:{x:0,y:0}},droppable:{containers:new Ro}}}function a_(o,e){switch(e.type){case an.DragStart:return{...o,draggable:{...o.draggable,initialCoordinates:e.initialCoordinates,active:e.active}};case an.DragMove:return o.draggable.active==null?o:{...o,draggable:{...o.draggable,translate:{x:e.coordinates.x-o.draggable.initialCoordinates.x,y:e.coordinates.y-o.draggable.initialCoordinates.y}}};case an.DragEnd:case an.DragCancel:return{...o,draggable:{...o.draggable,active:null,initialCoordinates:{x:0,y:0},translate:{x:0,y:0}}};case an.RegisterDroppable:{const{element:n}=e,{id:a}=n,i=new Ro(o.droppable.containers);return i.set(a,n),{...o,droppable:{...o.droppable,containers:i}}}case an.SetDroppableDisabled:{const{id:n,key:a,disabled:i}=e,l=o.droppable.containers.get(n);if(!l||a!==l.key)return o;const c=new Ro(o.droppable.containers);return c.set(n,{...l,disabled:i}),{...o,droppable:{...o.droppable,containers:c}}}case an.UnregisterDroppable:{const{id:n,key:a}=e,i=o.droppable.containers.get(n);if(!i||a!==i.key)return o;const l=new Ro(o.droppable.containers);return l.delete(n),{...o,droppable:{...o.droppable,containers:l}}}default:return o}}function i_(o){let{disabled:e}=o;const{active:n,activatorEvent:a,draggableNodes:i}=H.useContext(hc),l=Qd(a),c=Qd(n?.id);return H.useEffect(()=>{if(!e&&!a&&l&&c!=null){if(!ef(l)||document.activeElement===l.target)return;const d=i.get(c);if(!d)return;const{activatorNode:f,node:g}=d;if(!f.current&&!g.current)return;requestAnimationFrame(()=>{for(const m of[f.current,g.current]){if(!m)continue;const y=JC(m);if(y){y.focus();break}}})}},[a,e,i,c,l]),null}function s_(o,e){let{transform:n,...a}=e;return o!=null&&o.length?o.reduce((i,l)=>l({transform:i,...a}),n):n}function o_(o){return H.useMemo(()=>({draggable:{...fc.draggable,...o?.draggable},droppable:{...fc.droppable,...o?.droppable},dragOverlay:{...fc.dragOverlay,...o?.dragOverlay}}),[o?.draggable,o?.droppable,o?.dragOverlay])}function l_(o){let{activeNode:e,measure:n,initialRect:a,config:i=!0}=o;const l=H.useRef(!1),{x:c,y:d}=typeof i=="boolean"?{x:i,y:i}:i;zr(()=>{if(!c&&!d||!e){l.current=!1;return}if(l.current||!a)return;const g=e?.node.current;if(!g||g.isConnected===!1)return;const m=n(g),y=Hm(m,a);if(c||(y.x=0),d||(y.y=0),l.current=!0,Math.abs(y.x)>0||Math.abs(y.y)>0){const S=zm(g);S&&S.scrollBy({top:y.y,left:y.x})}},[e,c,d,a,n])}const ry=H.createContext({...wr,scaleX:1,scaleY:1});var Da;(function(o){o[o.Uninitialized=0]="Uninitialized",o[o.Initializing=1]="Initializing",o[o.Initialized=2]="Initialized"})(Da||(Da={}));const c_=H.memo(function(e){var n,a,i,l;let{id:c,accessibility:d,autoScroll:f=!0,children:g,sensors:m=ZE,collisionDetection:y=fE,measuring:S,modifiers:v,..._}=e;const A=H.useReducer(a_,void 0,r_),[x,O]=A,[M,T]=rE(),[D,z]=H.useState(Da.Uninitialized),P=D===Da.Initialized,{draggable:{active:q,nodes:W,translate:he},droppable:{containers:Ee}}=x,be=q!=null?W.get(q):null,Ue=H.useRef({initial:null,translated:null}),Fe=H.useMemo(()=>{var ge;return q!=null?{id:q,data:(ge=be?.data)!=null?ge:e_,rect:Ue}:null},[q,be]),Ge=H.useRef(null),[wt,J]=H.useState(null),[ue,Me]=H.useState(null),te=So(_,Object.values(_)),pe=Co("DndDescribedBy",c),N=H.useMemo(()=>Ee.getEnabled(),[Ee]),R=o_(S),{droppableRects:de,measureDroppableContainers:fe,measuringScheduled:xe}=PE(N,{dragging:P,dependencies:[he.x,he.y],config:R.droppable}),ee=zE(W,q),_e=H.useMemo(()=>ue?tf(ue):null,[ue]),Be=ve(),ke=BE(ee,R.draggable.measure);l_({activeNode:q!=null?W.get(q):null,config:Be.layoutShiftCompensation,initialRect:ke,measure:R.draggable.measure});const Ie=Jm(ee,R.draggable.measure,ke),Ct=Jm(ee?ee.parentElement:null),ot=H.useRef({activatorEvent:null,active:null,activeNode:ee,collisionRect:null,collisions:null,droppableRects:de,draggableNodes:W,draggingNode:null,draggingNodeRect:null,droppableContainers:Ee,over:null,scrollableAncestors:[],scrollAdjustedTranslate:null}),Ot=Ee.getNodeFor((n=ot.current.over)==null?void 0:n.id),vt=QE({measure:R.dragOverlay.measure}),_t=(a=vt.nodeRef.current)!=null?a:ee,Oe=P?(i=vt.rect)!=null?i:Ie:null,nt=!!(vt.nodeRef.current&&vt.rect),sn=VE(nt?null:Ie),Mt=ey(_t?Nn(_t):null),Vt=FE(P?Ot??ee:null),on=XE(Vt),ln=s_(v,{transform:{x:he.x-sn.x,y:he.y-sn.y,scaleX:1,scaleY:1},activatorEvent:ue,active:Fe,activeNodeRect:Ie,containerNodeRect:Ct,draggingNodeRect:Oe,over:ot.current.over,overlayNodeRect:vt.rect,scrollableAncestors:Vt,scrollableAncestorRects:on,windowRect:Mt}),Br=_e?hs(_e,he):null,Ft=KE(Vt),Ci=Zm(Ft),na=Zm(Ft,[Ie]),yn=hs(ln,Ci),On=Oe?pE(Oe,ln):null,rt=Fe&&On?y({active:Fe,collisionRect:On,droppableRects:de,droppableContainers:N,pointerCoordinates:Br}):null,Hn=Nm(rt,"id"),[bn,ra]=H.useState(null),sr=nt?ln:hs(ln,na),cn=hE(sr,(l=bn?.rect)!=null?l:null,Ie),Tn=H.useRef(null),un=H.useCallback((ge,we)=>{let{sensor:Ae,options:Ke}=we;if(Ge.current==null)return;const Te=W.get(Ge.current);if(!Te)return;const $e=ge.nativeEvent,Re=new Ae({active:Ge.current,activeNode:Te,event:$e,options:Ke,context:ot,onAbort(je){if(!W.get(je))return;const{onDragAbort:Se}=te.current,He={id:je};Se?.(He),M({type:"onDragAbort",event:He})},onPending(je,Ve,Se,He){if(!W.get(je))return;const{onDragPending:Tt}=te.current,It={id:je,constraint:Ve,initialCoordinates:Se,offset:He};Tt?.(It),M({type:"onDragPending",event:It})},onStart(je){const Ve=Ge.current;if(Ve==null)return;const Se=W.get(Ve);if(!Se)return;const{onDragStart:He}=te.current,tt={activatorEvent:$e,active:{id:Ve,data:Se.data,rect:Ue}};ka.unstable_batchedUpdates(()=>{He?.(tt),z(Da.Initializing),O({type:an.DragStart,initialCoordinates:je,active:Ve}),M({type:"onDragStart",event:tt}),J(Tn.current),Me($e)})},onMove(je){O({type:an.DragMove,coordinates:je})},onEnd:et(an.DragEnd),onCancel:et(an.DragCancel)});Tn.current=Re;function et(je){return async function(){const{active:Se,collisions:He,over:tt,scrollAdjustedTranslate:Tt}=ot.current;let It=null;if(Se&&Tt){const{cancelDrop:Ln}=te.current;It={activatorEvent:$e,active:Se,collisions:He,delta:Tt,over:tt},je===an.DragEnd&&typeof Ln=="function"&&await Promise.resolve(Ln(It))&&(je=an.DragCancel)}Ge.current=null,ka.unstable_batchedUpdates(()=>{O({type:je}),z(Da.Uninitialized),ra(null),J(null),Me(null),Tn.current=null;const Ln=je===an.DragEnd?"onDragEnd":"onDragCancel";if(It){const zt=te.current[Ln];zt?.(It),M({type:Ln,event:It})}})}}},[W]),V=H.useCallback((ge,we)=>(Ae,Ke)=>{const Te=Ae.nativeEvent,$e=W.get(Ke);if(Ge.current!==null||!$e||Te.dndKit||Te.defaultPrevented)return;const Re={active:$e};ge(Ae,we.options,Re)===!0&&(Te.dndKit={capturedBy:we.sensor},Ge.current=Ke,un(Ae,we))},[W,un]),se=UE(m,V);WE(m),zr(()=>{Ie&&D===Da.Initializing&&z(Da.Initialized)},[Ie,D]),H.useEffect(()=>{const{onDragMove:ge}=te.current,{active:we,activatorEvent:Ae,collisions:Ke,over:Te}=ot.current;if(!we||!Ae)return;const $e={active:we,activatorEvent:Ae,collisions:Ke,delta:{x:yn.x,y:yn.y},over:Te};ka.unstable_batchedUpdates(()=>{ge?.($e),M({type:"onDragMove",event:$e})})},[yn.x,yn.y]),H.useEffect(()=>{const{active:ge,activatorEvent:we,collisions:Ae,droppableContainers:Ke,scrollAdjustedTranslate:Te}=ot.current;if(!ge||Ge.current==null||!we||!Te)return;const{onDragOver:$e}=te.current,Re=Ke.get(Hn),et=Re&&Re.rect.current?{id:Re.id,rect:Re.rect.current,data:Re.data,disabled:Re.disabled}:null,je={active:ge,activatorEvent:we,collisions:Ae,delta:{x:Te.x,y:Te.y},over:et};ka.unstable_batchedUpdates(()=>{ra(et),$e?.(je),M({type:"onDragOver",event:je})})},[Hn]),zr(()=>{ot.current={activatorEvent:ue,active:Fe,activeNode:ee,collisionRect:On,collisions:rt,droppableRects:de,draggableNodes:W,draggingNode:_t,draggingNodeRect:Oe,droppableContainers:Ee,over:bn,scrollableAncestors:Vt,scrollAdjustedTranslate:yn},Ue.current={initial:Oe,translated:On}},[Fe,ee,rt,On,W,_t,Oe,de,Ee,bn,Vt,yn]),IE({...Be,delta:he,draggingRect:On,pointerCoordinates:Br,scrollableAncestors:Vt,scrollableAncestorRects:on});const ce=H.useMemo(()=>({active:Fe,activeNode:ee,activeNodeRect:Ie,activatorEvent:ue,collisions:rt,containerNodeRect:Ct,dragOverlay:vt,draggableNodes:W,droppableContainers:Ee,droppableRects:de,over:bn,measureDroppableContainers:fe,scrollableAncestors:Vt,scrollableAncestorRects:on,measuringConfiguration:R,measuringScheduled:xe,windowRect:Mt}),[Fe,ee,Ie,ue,rt,Ct,vt,W,Ee,de,bn,fe,Vt,on,R,xe,Mt]),Z=H.useMemo(()=>({activatorEvent:ue,activators:se,active:Fe,activeNodeRect:Ie,ariaDescribedById:{draggable:pe},dispatch:O,draggableNodes:W,over:bn,measureDroppableContainers:fe}),[ue,se,Fe,Ie,O,pe,W,bn,fe]);return Xe.createElement(Dm.Provider,{value:T},Xe.createElement(hc.Provider,{value:Z},Xe.createElement(ny.Provider,{value:ce},Xe.createElement(ry.Provider,{value:cn},g)),Xe.createElement(i_,{disabled:d?.restoreFocus===!1})),Xe.createElement(sE,{...d,hiddenTextDescribedById:pe}));function ve(){const ge=wt?.autoScrollEnabled===!1,we=typeof f=="object"?f.enabled===!1:f===!1,Ae=P&&!ge&&!we;return typeof f=="object"?{...f,enabled:Ae}:{enabled:Ae}}}),u_=H.createContext(null),ay="button",d_="Draggable";function f_(o){let{id:e,data:n,disabled:a=!1,attributes:i}=o;const l=Co(d_),{activators:c,activatorEvent:d,active:f,activeNodeRect:g,ariaDescribedById:m,draggableNodes:y,over:S}=H.useContext(hc),{role:v=ay,roleDescription:_="draggable",tabIndex:A=0}=i??{},x=f?.id===e,O=H.useContext(x?ry:u_),[M,T]=oc(),[D,z]=oc(),P=YE(c,e),q=So(n);zr(()=>(y.set(e,{id:e,key:l,node:M,activatorNode:D,data:q}),()=>{const he=y.get(e);he&&he.key===l&&y.delete(e)}),[y,e]);const W=H.useMemo(()=>({role:v,tabIndex:A,"aria-disabled":a,"aria-pressed":x&&v===ay?!0:void 0,"aria-roledescription":_,"aria-describedby":m.draggable}),[a,v,A,x,_,m.draggable]);return{active:f,activatorEvent:d,activeNodeRect:g,attributes:W,isDragging:x,listeners:a?void 0:P,node:M,over:S,setNodeRef:T,setActivatorNodeRef:z,transform:O}}function h_(){return H.useContext(ny)}const g_="Droppable",p_={timeout:25};function m_(o){let{data:e,disabled:n=!1,id:a,resizeObserverConfig:i}=o;const l=Co(g_),{active:c,dispatch:d,over:f,measureDroppableContainers:g}=H.useContext(hc),m=H.useRef({disabled:n}),y=H.useRef(!1),S=H.useRef(null),v=H.useRef(null),{disabled:_,updateMeasurementsFor:A,timeout:x}={...p_,...i},O=So(A??a),M=H.useCallback(()=>{if(!y.current){y.current=!0;return}v.current!=null&&clearTimeout(v.current),v.current=setTimeout(()=>{g(Array.isArray(O.current)?O.current:[O.current]),v.current=null},x)},[x]),T=dc({callback:M,disabled:_||!c}),D=H.useCallback((W,he)=>{T&&(he&&(T.unobserve(he),y.current=!1),W&&T.observe(W))},[T]),[z,P]=oc(D),q=So(e);return H.useEffect(()=>{!T||!z.current||(T.disconnect(),y.current=!1,T.observe(z.current))},[z,T]),H.useEffect(()=>(d({type:an.RegisterDroppable,element:{id:a,key:l,disabled:n,node:z,rect:S,data:q}}),()=>d({type:an.UnregisterDroppable,key:l,id:a})),[a]),H.useEffect(()=>{n!==m.current.disabled&&(d({type:an.SetDroppableDisabled,id:a,key:l,disabled:n}),m.current.disabled=n)},[a,l,n,d]),{active:c,rect:S,isOver:f?.id===a,node:z,over:f,setNodeRef:P}}function hf(o,e,n){const a=o.slice();return a.splice(n<0?a.length+n:n,0,a.splice(e,1)[0]),a}function y_(o,e){return o.reduce((n,a,i)=>{const l=e.get(a);return l&&(n[i]=l),n},Array(o.length))}function gc(o){return o!==null&&o>=0}function b_(o,e){if(o===e)return!0;if(o.length!==e.length)return!1;for(let n=0;n{var e;let{rects:n,activeNodeRect:a,activeIndex:i,overIndex:l,index:c}=o;const d=(e=n[i])!=null?e:a;if(!d)return null;const f=w_(n,c,i);if(c===i){const g=n[l];return g?{x:ii&&c<=l?{x:-d.width-f,y:0,...pc}:c=l?{x:d.width+f,y:0,...pc}:{x:0,y:0,...pc}};function w_(o,e,n){const a=o[e],i=o[e-1],l=o[e+1];return!a||!i&&!l?0:n{let{rects:e,activeIndex:n,overIndex:a,index:i}=o;const l=hf(e,a,n),c=e[i],d=l[i];return!d||!c?null:{x:d.left-c.left,y:d.top-c.top,scaleX:d.width/c.width,scaleY:d.height/c.height}},mc={scaleX:1,scaleY:1},C_=o=>{var e;let{activeIndex:n,activeNodeRect:a,index:i,rects:l,overIndex:c}=o;const d=(e=l[n])!=null?e:a;if(!d)return null;if(i===n){const g=l[c];return g?{x:0,y:nn&&i<=c?{x:0,y:-d.height-f,...mc}:i=c?{x:0,y:d.height+f,...mc}:{x:0,y:0,...mc}};function E_(o,e,n){const a=o[e],i=o[e-1],l=o[e+1];return a?na.map(P=>typeof P=="object"&&"id"in P?P.id:P),[a]),_=c!=null,A=c?v.indexOf(c.id):-1,x=g?v.indexOf(g.id):-1,O=H.useRef(v),M=!b_(v,O.current),T=x!==-1&&A===-1||M,D=v_(l);zr(()=>{M&&_&&m(v)},[M,v,_,m]),H.useEffect(()=>{O.current=v},[v]);const z=H.useMemo(()=>({activeIndex:A,containerId:y,disabled:D,disableTransforms:T,items:v,overIndex:x,useDragOverlay:S,sortedRects:y_(v,f),strategy:i}),[A,y,D.draggable,D.droppable,T,v,x,f,S,i]);return Xe.createElement(sy.Provider,{value:z},e)}const A_=o=>{let{id:e,items:n,activeIndex:a,overIndex:i}=o;return hf(n,a,i).indexOf(e)},x_=o=>{let{containerId:e,isSorting:n,wasDragging:a,index:i,items:l,newIndex:c,previousItems:d,previousContainerId:f,transition:g}=o;return!g||!a||d!==l&&i===c?!1:n?!0:c!==i&&e===f},T_={duration:200,easing:"ease"},oy="transform",R_=_o.Transition.toString({property:oy,duration:0,easing:"linear"}),k_={roleDescription:"sortable"};function D_(o){let{disabled:e,index:n,node:a,rect:i}=o;const[l,c]=H.useState(null),d=H.useRef(n);return zr(()=>{if(!e&&n!==d.current&&a.current){const f=i.current;if(f){const g=gs(a.current,{ignoreTransform:!0}),m={x:f.left-g.left,y:f.top-g.top,scaleX:f.width/g.width,scaleY:f.height/g.height};(m.x||m.y)&&c(m)}}n!==d.current&&(d.current=n)},[e,n,a,i]),H.useEffect(()=>{l&&c(null)},[l]),l}function O_(o){let{animateLayoutChanges:e=x_,attributes:n,disabled:a,data:i,getNewIndex:l=A_,id:c,strategy:d,resizeObserverConfig:f,transition:g=T_}=o;const{items:m,containerId:y,activeIndex:S,disabled:v,disableTransforms:_,sortedRects:A,overIndex:x,useDragOverlay:O,strategy:M}=H.useContext(sy),T=L_(a,v),D=m.indexOf(c),z=H.useMemo(()=>({sortable:{containerId:y,index:D,items:m},...i}),[y,i,D,m]),P=H.useMemo(()=>m.slice(m.indexOf(c)),[m,c]),{rect:q,node:W,isOver:he,setNodeRef:Ee}=m_({id:c,data:z,disabled:T.droppable,resizeObserverConfig:{updateMeasurementsFor:P,...f}}),{active:be,activatorEvent:Ue,activeNodeRect:Fe,attributes:Ge,setNodeRef:wt,listeners:J,isDragging:ue,over:Me,setActivatorNodeRef:te,transform:pe}=f_({id:c,data:z,attributes:{...k_,...n},disabled:T.draggable}),N=KC(Ee,wt),R=!!be,de=R&&!_&&gc(S)&&gc(x),fe=!O&&ue,xe=fe&&de?pe:null,_e=de?xe??(d??M)({rects:A,activeNodeRect:Fe,activeIndex:S,overIndex:x,index:D}):null,Be=gc(S)&&gc(x)?l({id:c,items:m,activeIndex:S,overIndex:x}):D,ke=be?.id,Ie=H.useRef({activeId:ke,items:m,newIndex:Be,containerId:y}),Ct=m!==Ie.current.items,ot=e({active:be,containerId:y,isDragging:ue,isSorting:R,id:c,index:D,items:m,newIndex:Ie.current.newIndex,previousItems:Ie.current.items,previousContainerId:Ie.current.containerId,transition:g,wasDragging:Ie.current.activeId!=null}),Ot=D_({disabled:!ot,index:D,node:W,rect:q});return H.useEffect(()=>{R&&Ie.current.newIndex!==Be&&(Ie.current.newIndex=Be),y!==Ie.current.containerId&&(Ie.current.containerId=y),m!==Ie.current.items&&(Ie.current.items=m)},[R,Be,y,m]),H.useEffect(()=>{if(ke===Ie.current.activeId)return;if(ke!=null&&Ie.current.activeId==null){Ie.current.activeId=ke;return}const _t=setTimeout(()=>{Ie.current.activeId=ke},50);return()=>clearTimeout(_t)},[ke]),{active:be,activeIndex:S,attributes:Ge,data:z,rect:q,index:D,newIndex:Be,items:m,isOver:he,isSorting:R,isDragging:ue,listeners:J,node:W,overIndex:x,over:Me,setNodeRef:N,setActivatorNodeRef:te,setDroppableNodeRef:Ee,setDraggableNodeRef:wt,transform:Ot??_e,transition:vt()};function vt(){if(Ot||Ct&&Ie.current.newIndex===D)return R_;if(!(fe&&!ef(Ue)||!g)&&(R||ot))return _o.Transition.toString({...g,property:oy})}}function L_(o,e){var n,a;return typeof o=="boolean"?{draggable:o,droppable:!1}:{draggable:(n=o?.draggable)!=null?n:e.draggable,droppable:(a=o?.droppable)!=null?a:e.droppable}}function yc(o){if(!o)return!1;const e=o.data.current;return!!(e&&"sortable"in e&&typeof e.sortable=="object"&&"containerId"in e.sortable&&"items"in e.sortable&&"index"in e.sortable)}const M_=[ut.Down,ut.Right,ut.Up,ut.Left],$_=(o,e)=>{let{context:{active:n,collisionRect:a,droppableRects:i,droppableContainers:l,over:c,scrollableAncestors:d}}=e;if(M_.includes(o.code)){if(o.preventDefault(),!n||!a)return;const f=[];l.getEnabled().forEach(y=>{if(!y||y!=null&&y.disabled)return;const S=i.get(y.id);if(S)switch(o.code){case ut.Down:a.topS.top&&f.push(y);break;case ut.Left:a.left>S.left&&f.push(y);break;case ut.Right:a.left1&&(m=g[1].id),m!=null){const y=l.get(n.id),S=l.get(m),v=S?i.get(S.id):null,_=S?.node.current;if(_&&v&&y&&S){const x=cc(_).some((P,q)=>d[q]!==P),O=ly(y,S),M=N_(y,S),T=x||!O?{x:0,y:0}:{x:M?a.width-v.width:0,y:M?a.height-v.height:0},D={x:v.left,y:v.top};return T.x&&T.y?D:Eo(D,T)}}}};function ly(o,e){return!yc(o)||!yc(e)?!1:o.data.current.sortable.containerId===e.data.current.sortable.containerId}function N_(o,e){return!yc(o)||!yc(e)||!ly(o,e)?!1:o.data.current.sortable.index{const d=H.useId(),f=H.useRef({inProgress:!1,lastSortTime:0,lastActiveId:null}),g=oE(Om(cf,{activationConstraint:{distance:5}}),Om(of,{coordinateGetter:$_})),m=H.useCallback(v=>{f.current={inProgress:!0,lastSortTime:Date.now(),lastActiveId:v.active.id},l?.({activeId:v.active.id})},[l]),y=H.useCallback(v=>{const{active:_,over:A}=v,x=Date.now();if(!A||_.id===A.id){f.current.inProgress=!1;return}const{lastSortTime:O,lastActiveId:M,inProgress:T}=f.current;if(M===_.id&&x-O<500&&!T)return;f.current={inProgress:!1,lastSortTime:x,lastActiveId:_.id};const D=o.findIndex(P=>String(P[e])===String(_.id)),z=o.findIndex(P=>String(P[e])===String(A.id));if(D!==-1&&z!==-1&&D!==z){const P=hf(o,D,z);i?.(P,{oldIndex:D,newIndex:z})}},[o,e,i]),S=o.map(v=>String(v[e]));return gt.jsx(c_,{id:d,sensors:g,collisionDetection:cE,onDragStart:m,onDragEnd:y,children:gt.jsx(__,{items:S,strategy:I_[n],children:a})},c!==void 0?`${d}-v${c}`:d)},cy=H.createContext(null),j_=({value:o,children:e})=>gt.jsx(cy.Provider,{value:o,children:e}),z_=()=>H.useContext(cy),U_=({id:o,handle:e,as:n="div",children:a})=>{const{attributes:i,listeners:l,setNodeRef:c,transform:d,transition:f,isDragging:g}=O_({id:o}),m={transform:_o.Transform.toString(d),transition:f,opacity:g?.5:1,position:"relative"};return gt.jsx(n,{ref:c,style:m,...i,...e?{}:l,"data-sortable-item":!0,"data-sortable-id":String(o),"data-dragging":g,children:gt.jsx(j_,{value:{listeners:l,handle:e,isDragging:g},children:a})})},st=ht("DynamicRenderer");function Zr(){try{return window.G7Core?.devTools}catch{return}}function pf(o,e,n,a){if(typeof o=="string"){if(/\$t:[a-zA-Z_][a-zA-Z0-9_.\-]*/.test(o))try{return e.resolveTranslations(o,n,a)}catch{return o}return o}if(Array.isArray(o))return o.map(i=>pf(i,e,n,a));if(o&&typeof o=="object"){const i={};for(const[l,c]of Object.entries(o))i[l]=pf(c,e,n,a);return i}return o}function P_(o){const e=Object.keys(o);return e.length>0&&e.every(n=>/^\d+$/.test(n))}const Cr=(o,e)=>{const n=["errors"],a={...o};for(const i of Object.keys(e)){const l=e[i],c=o[i];if(l===null){a[i]=null;continue}if(n.includes(i)){a[i]=l;continue}if(Array.isArray(l)){a[i]=l;continue}if(Array.isArray(c)&&l!==null&&typeof l=="object"&&!Array.isArray(l)&&P_(l)){const d=Object.keys(l).map(g=>parseInt(g,10));if((d.length>0?Math.max(...d):0)>=c.length+d.length+10)a[i]={...l};else{const g=[...c];for(const[m,y]of Object.entries(l)){const S=parseInt(m,10);S>=0&&S=g.length&&(g[S]=y)}a[i]=g}}else l!==null&&typeof l=="object"&&c!==null&&typeof c=="object"&&!Array.isArray(c)?a[i]=Cr(c,l):a[i]=l}return a},bc=(o,e)=>{let n=null;const a=()=>(n===null&&(n={...o}),n);for(const i of Object.keys(e)){if(!(i in o))continue;const l=e[i],c=o[i];if(l!==null&&typeof l=="object"&&!Array.isArray(l)&&c!==null&&typeof c=="object"&&!Array.isArray(c)){const d=bc(c,l);if(d===c)continue;Object.keys(d).length===0?delete a()[i]:a()[i]=d}else delete a()[i]}return n??o},B_=o=>{const e={},n=o.split(";").filter(a=>a.trim());for(const a of n){const i=a.indexOf(":");if(i===-1)continue;const l=a.substring(0,i).trim(),c=a.substring(i+1).trim();if(l&&c){const d=l.replace(/-([a-z])/g,(f,g)=>g.toUpperCase());e[d]=c}}return e};class q_ extends H.Component{constructor(e){super(e),this.state={hasError:!1,error:null}}static getDerivedStateFromError(e){return{hasError:!0,error:e}}componentDidCatch(e,n){st.error(`컴포넌트 렌더링 에러 (ID: ${this.props.componentId}, Name: ${this.props.componentName}):`,e,n)}render(){return this.state.hasError?this.props.fallback?this.props.fallback:gt.jsxs("div",{className:"p-4 my-2 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg text-red-800 dark:text-red-400",children:[gt.jsx("div",{className:"font-semibold mb-1",children:"컴포넌트 로드 실패"}),gt.jsxs("div",{className:"text-xs opacity-80",children:[this.props.componentName&&`[${this.props.componentName}] `,"데이터를 표시할 수 없습니다."]}),this.state.error&>.jsxs("details",{className:"mt-2",children:[gt.jsx("summary",{className:"cursor-pointer text-xs opacity-70 hover:opacity-100",children:"상세 정보"}),gt.jsx("div",{className:"mt-1 p-2 bg-red-100 dark:bg-red-900/30 rounded text-xs font-mono whitespace-pre-wrap break-all",children:this.state.error.message})]})]}):this.props.children}}const Ur=H.memo(({componentDef:o,dataContext:e,translationContext:n,registry:a,bindingEngine:i,translationEngine:l,actionDispatcher:c,parentComponentContext:d,parentFormContextProp:f,parentDataContext:g,isRootRenderer:m=!1,isInsideIteration:y=!1,isEditMode:S=!1,onComponentSelect:v,onComponentHover:_,onDragStart:A,onDragEnd:x,componentPath:O,layoutKey:M})=>{const[T,D]=H.useState({loadingActions:{}});H.useLayoutEffect(()=>{d||(i.startRenderCycle(),window.__g7SetLocalOverrideKeys||(window.__g7ForcedLocalFields=void 0),window.__g7PendingLocalState=null)});const z=H.useRef(null);H.useLayoutEffect(()=>{if(!d&&e._localInit&&typeof e._localInit=="object"){const{_forceLocalInit:V,_merge:se,...ce}=e._localInit,Z=`${JSON.stringify(ce)}:${V||"no-force"}`;if(z.current!==Z){z.current=Z;const ve=se||"shallow",we=window.G7Core?.state?.get?.()?._local||{},Ae=window.__g7PendingLocalState||{},Ke={...we,...Ae};ve==="replace"?window.__g7PendingLocalState={loadingActions:Ke.loadingActions||{},...ce,hasChanges:!1}:ve==="deep"?window.__g7PendingLocalState=Cr(Ke,{...ce,hasChanges:!1}):window.__g7PendingLocalState={...Ke,...ce,hasChanges:!1};const Te=window.__g7SetLocalOverrideKeys;Te&&typeof Te=="object"&&(ve==="replace"?window.__g7SetLocalOverrideKeys={...ce,hasChanges:!1}:window.__g7SetLocalOverrideKeys=Cr(Te,{...ce,hasChanges:!1}))}}}),H.useLayoutEffect(()=>{if(!d){const V=window.__g7SetLocalOverrideKeys;if(V){D(ve=>bc(ve,V));const se=V,ce=window.__g7ForcedLocalFields,Z=window.__g7LastSetLocalSnapshot;queueMicrotask(()=>{window.__g7SetLocalOverrideKeys===se&&(window.__g7SetLocalOverrideKeys=void 0),window.__g7ForcedLocalFields===ce&&(window.__g7ForcedLocalFields=void 0),window.__g7LastSetLocalSnapshot===Z&&(window.__g7LastSetLocalSnapshot=void 0)})}}},[e._local]);const P=H.useRef(M);H.useEffect(()=>{o._fromBase&&M&&P.current!==M&&(P.current=M,D({loadingActions:{}}))},[M,o._fromBase]);const q=H.useRef(""),W=H.useRef(!1),he=H.useRef(null),Ee=H.useRef(null),be=H.useRef({}),Ue=H.useRef({}),Fe=H.useRef(g),Ge=FC(),wt=CC(),J=wt?.getParentDataContext()??g;H.useEffect(()=>{if(e._localInit&&typeof e._localInit=="object"){const{_forceLocalInit:V,...se}=e._localInit,ce=JSON.stringify(se),Z=V!==void 0,ve=Fd(),ge=`${ce}:${V||"no-force"}`,we=DC({globalTrackedKey:ve.hash,instanceHandledKey:Ee.current,trackingKey:ge});if(we==="apply"){Ee.current=ge,ve.hash=ge,ve.timestamp=V,q.current=ce,Z&&(W.current=!0);const{_merge:Ke,...Te}=se,$e=Ke||"shallow";D(Re=>$e==="replace"?{loadingActions:Re.loadingActions||{},...Te,hasChanges:!1}:$e==="deep"?Cr(Re,{...Te,hasChanges:!1}):{...Re,...Te,hasChanges:!1}),e._globalSetState&&e._globalSetState(Re=>{const et=Re?._local||{};let je;return $e==="replace"?je={...Te,hasChanges:!1}:$e==="deep"?je=Cr(et,{...Te,hasChanges:!1}):je={...et,...Te,hasChanges:!1},{...Re,_local:je}}),i.invalidateCacheByKeys(["_local"]),st.log("_localInit applied (data changed):",Object.keys(se))}else if(we==="prune"){Ee.current=ge;const{_merge:Ke,...Te}=se;D(Re=>bc(Re,Te)),bc(T,Te)!==T&&(i.invalidateCacheByKeys(["_local"]),st.log("_localInit pruned (applied by another renderer):",Object.keys(Te)))}he.current=e._localInit,RC(e._localInit)}},[e._localInit,g,i]);const{isTransitioning:ue}=wm(),Me=HC(),te=f??Me,{width:pe}=Wd(),N=z_(),R=H.useMemo(()=>{let V=o;if(o.type==="iterator"&&o.data){const{data:Z,itemName:ve,indexName:ge,...we}=o;V={...we,iteration:{source:Z,item_var:ve||"item",index_var:ge}}}if(!V.responsive)return V;const se=hi.getMatchingKey(V.responsive,pe);if(!se)return V;const ce=V.responsive[se];return{...V,props:{...V.props,...ce.props},children:ce.children??V.children,text:ce.text??V.text,if:ce.if??V.if,iteration:ce.iteration??V.iteration,__responsiveChildrenKey:ce.children!==void 0?se:void 0}},[o,pe]),de=H.useCallback(V=>{const se=V?.__setStateId;if(D(ce=>{const Z=window.__g7PendingLocalState,ge=window.__g7SetLocalOverrideKeys||Z,we=ge?Cr(ce,ge):ce;if(typeof V=="function"){const Re=V(we);return Cr(we,Re)}const{__mergeMode:Ae,__setStateId:Ke,...Te}=V;let $e;if(Ae==="replace"?$e=Te:Ae==="shallow"?$e={...we,...Te}:$e=Cr(we,Te),Ae!==void 0){const Re=window.__g7ForcedLocalFields;Re&&(window.__g7ForcedLocalFields={...Re,...Te})}return $e}),se){const ce=Zr();if(ce?.isEnabled()){const Z=R.id||"unknown",ve=R.name||"Unknown";setTimeout(()=>{ce.trackComponentRender?.(Z,ve,0,["_local"],[]),ce.completeStateChange?.(se)},0)}}},[R.id,R.name]),fe=d?.state??T,xe=d?.setState??de,ee=H.useMemo(()=>{const V=e._localInit&&e._localInit!==he.current;let se;V&&e._local?se=e._local:se=e._local?Cr(e._local,fe):fe;const ce=window.__g7ForcedLocalFields;ce&&(se=Cr(se,ce));const Z={...e,_local:se},ve=e._computedDefinitions;if(ve&&Object.keys(ve).length>0){const ge=new Dn,we={...Z,...Ge?{_isolated:Ge.state}:{}},Ae={},Ke=Zr(),Te=Ke?.isEnabled()??!1;for(const[$e,Re]of Object.entries(ve)){const et=Te?Date.now():0,je=Te?e._computed?.[$e]:void 0;let Ve;try{if(typeof Re=="string")if(Re.startsWith("{{")&&Re.endsWith("}}")){const Se=Re.slice(2,-2).trim(),He=Vn(Se)?ge.evaluatePipeExpression(Se,we,{skipCache:!0}):ge.evaluateExpression(Se,we,{skipCache:!0});Ae[$e]=He}else Ae[$e]=Re;else if(Re&&typeof Re=="object"&&"$switch"in Re){const Se=ge.resolveSwitch(Re,we,{skipCache:!0});Ae[$e]=Se}}catch(Se){st.warn(`Failed to recalculate computed value: ${$e}`,Se),Ae[$e]=e._computed?.[$e],Ve=Se instanceof Error?Se.message:String(Se)}if(Te&&(Ve||je!==Ae[$e])){const He=Date.now()-et;Ke.trackComputedProperty?.($e,typeof Re=="string"?Re:JSON.stringify(Re),[{type:"_local",path:"_local",value:Z._local}],Ae[$e],He,R.id,Ve),je!==Ae[$e]&&Ke.trackComputedRecalc?.($e,"dependency-change",je,Ae[$e],He,{type:"_local",path:"_local"},R.id)}}Z._computed=Ae}if(Ge&&(Z._isolated=Ge.state),J&&(Z.$parent=J),o.extensionPointProps){const ge=i.resolveObject(o.extensionPointProps,Z,y?{skipCache:!0}:void 0);Z.extensionPointProps=S&&l?pf(ge,l,n,Z):ge}return o.extensionPointCallbacks&&(Z.extensionPointCallbacks=o.extensionPointCallbacks),S&&(Z.$templateId===void 0&&n?.templateId&&(Z.$templateId=n.templateId),Z.$locale===void 0&&n?.locale&&(Z.$locale=n.locale)),Z},[e,fe,Ge?.state,J,wt?.version,o.extensionPointProps,o.extensionPointCallbacks]);be.current=ee._local,Ue.current=ee._computed||{},Fe.current=J,H.useEffect(()=>{const V=Zr();if(!V?.isEnabled())return;const se=ee._local;se&&V.updateLocalState(se);const ce=ee._computed;ce&&V.updateComputedState(ce),J&&V.updateParentContext?.(J);const Z=R.id||`comp-${Date.now()}`,ve=R.name||"Unknown",ge={global:Object.keys(e._global||{}),local:Object.keys(se||{}),context:d?Object.keys(d.state||{}):[]},we={type:d?"componentContext":"dynamicState",hasComponentContext:!!d,parentId:d?"parent":void 0};V.trackComponentStateSource?.(Z,ve,ge,we),fe&&Object.keys(fe).length>0&&V.trackDynamicState?.(Z,fe);const Ae=!!(R.expandChildren||R.props?.expandChildren);V.trackContextFlow?.(Z,ve,!!d,Ae,!0)},[ee._local,e,R.id,R.name,d,fe,R.expandChildren,R.props?.expandChildren,J]);const _e=H.useMemo(()=>({state:{...ee._local},setState:xe,stateRef:be,computedRef:Ue,isolatedContext:Ge,parentDataContext:ee}),[ee._local,xe,T,Ge,ee]),Be=H.useMemo(()=>{const V=ns({if:R.if,condition:R.condition,conditions:R.conditions},ee,i,R.id);if(R.if||R.condition!==void 0||R.conditions){const ce=Zr();ce?.isEnabled()&&R.if&&ce.trackIfCondition(R.id||`if-${Date.now()}`,R.if,V)}return V},[R.if,R.conditions,ee,i,R.id]),ke=H.useMemo(()=>{if(!R.slot)return null;const V=R.slot;if(V.startsWith("{{")&&V.endsWith("}}")){const se=V.slice(2,-2).trim();try{const ce={componentId:R.id,componentName:R.name,propName:"slot"},Z=Vn(se)?i.evaluatePipeExpression(se,ee,void 0,ce):i.evaluateExpression(se,ee,ce);return st.log(`[Slot] 표현식 평가: "${se}" => "${Z}" (컴포넌트: ${R.id})`),typeof Z=="string"?Z:null}catch(ce){return st.warn(`slot 표현식 평가 실패 (컴포넌트: ${R.id}):`,ce),null}}return st.log(`[Slot] 정적 슬롯 ID: "${V}" (컴포넌트: ${R.id})`),V},[R.slot,R.id,ee,i]),Ie=H.useMemo(()=>!ke||!R.id?"":`${R.id}-${ke}-${JSON.stringify(R.slotOrder??0)}`,[ke,R.id,R.slotOrder]),Ct=H.useRef(null);H.useEffect(()=>{const V=window.__slotContextValue,se=V?.isEnabled??!1;if(R.slot&&st.log(`[Slot Registration] 컴포넌트: ${R.id}, isSlotEnabled: ${se}, shouldRender: ${Be}, resolvedSlotId: ${ke}`),!se||!Be){R.slot&&st.log(`[Slot Registration] 등록 스킵 - isSlotEnabled: ${se}, shouldRender: ${Be}`),Ct.current&&R.id&&V&&(V.unregisterFromSlot(Ct.current,R.id),Ct.current=null);return}if(!ke||!R.id){R.slot&&st.log(`[Slot Registration] 등록 스킵 - resolvedSlotId: ${ke}, id: ${R.id}`),Ct.current&&R.id&&V&&(V.unregisterFromSlot(Ct.current,R.id),Ct.current=null);return}Ct.current&&Ct.current!==ke&&V.unregisterFromSlot(Ct.current,R.id);const ce={...R,slot:void 0,slotOrder:void 0};return V.registerToSlot(ke,R.id,{componentDef:ce,dataContext:e,order:R.slotOrder??0,parentFormContext:f??null,getParentComponentContext:()=>_e,translationContext:n,registrationKey:Ie}),Ct.current=ke,()=>{const Z=window.__slotContextValue;ke&&R.id&&Z&&Z.unregisterFromSlot(ke,R.id)}},[Be,ke,R,e,f,_e,n,Ie]);const ot=H.useCallback(V=>{if(typeof V=="string"){if(jl(V))return bd(V);if(zl(V)){const se=[],ce=V.replace(new RegExp(`${ao}([^${fi}]*)${fi}`,"g"),(ve,ge)=>(se.push(ge),`${xa}${se.length-1}${xa}`));let Z=ce;return/\$t:[a-zA-Z0-9._-]+/.test(ce)&&(Z=l.resolveTranslations(ce,n,ee)),Z.replace(new RegExp(`${xa}(\\d+)${xa}`,"g"),(ve,ge)=>se[parseInt(ge)])}if(V.startsWith("$t:defer:"))return V;if(/\$t:[a-zA-Z0-9._-]+/.test(V)){const se=V.trim();return se.startsWith("{")&&se.endsWith("}")||se.startsWith("[")&&se.endsWith("]")?V:l.resolveTranslations(V,n,ee)}return V}if(Array.isArray(V))return V.map(se=>ot(se));if(V&&typeof V=="object"){const se={};for(const[ce,Z]of Object.entries(V))se[ce]=ot(Z);return se}return V},[l,n,ee]),Ot=H.useCallback(V=>ro(V),[]),vt=H.useCallback(V=>Aa(V),[]),_t=H.useMemo(()=>{let V={...R.props||{}};const se=["cellChildren","expandChildren","expandContext","render"],Z=a.getMetadata(R.name)?.skipBindingKeys||[],ve=[...new Set([...se,...Z])],ge=y?{skipCache:!0}:void 0,we={...ge??{},skipBindingKeys:ve},Ae=Se=>{if(ge)return ge;if(Se.startsWith("_computed")||Se.startsWith("$computed"))return{skipCache:!0}};for(const[Se,He]of Object.entries(V))if(!ve.includes(Se))if(typeof He=="string"){const tt=vt(He);if(tt!==null){let Tt=!1,It=tt;tt.startsWith(nr)&&(Tt=!0,It=tt.slice(nr.length));const Ln={componentId:R.id,componentName:R.name,propName:Se};let zt;if(Vn(It))try{zt=i.evaluatePipeExpression(It,ee,Ae(It),Ln)}catch(Ut){st.warn(`파이프 표현식 평가 실패 (컴포넌트: ${R.id}, prop: ${Se}):`,Ut),zt=void 0}else if(Ot(It))try{zt=i.evaluateExpression(It,ee,Ln)}catch(Ut){st.warn(`표현식 평가 실패 (컴포넌트: ${R.id}, prop: ${Se}):`,Ut),zt=void 0}else zt=i.resolve(It,ee,Ae(It),Ln);V[Se]=Tt&&zt!=null?ts(zt):zt}else{const Tt={componentId:R.id,componentName:R.name,propName:Se};V[Se]=i.resolveBindings(He,ee,ge,Tt)}}else Array.isArray(He)?V[Se]=He.map(tt=>typeof tt=="string"?i.resolveBindings(tt,ee,ge):typeof tt=="object"&&tt!==null?i.resolveObject(tt,ee,we):tt):He&&typeof He=="object"&&(V[Se]=i.resolveObject(He,ee,we));for(const[Se,He]of Object.entries(V))V[Se]=ot(He);if(typeof V.style=="string"&&(V.style=B_(V.style)),V=Cd(V,S?void 0:R.actions,ee,{componentContext:_e,actionDispatcher:c}),R.classMap){const Se=Cw(R.classMap,ee,i,{skipCache:!0});if(Se){const He=V.className||"";V.className=He?`${He} ${Se}`:Se}}R.style&&(V.style=R.style),R.actions&&R.actions.length>0&&R.type==="composite"&&Object.assign(V,fe),R.component_layout&&(V.__componentLayoutDefs=R.component_layout);const Ke=R.expandChildren||R.props?.expandChildren,Te=R.props?.cellChildren,$e=R.children&&R.children.length>0,Re=R.component_layout,et=R.slot,je=R.props?.modalId,Ve=R.type==="composite";return(Ke||Te||$e||Re||et||je||Ve)&&(V.__componentContext=_e),V},[R.props,R.actions,R.style,R.classMap,R.id,ee,n,i,l,_e,fe,ot,Ot,vt,y]),Oe=H.useRef(null),nt=H.useMemo(()=>Oe.current&&Em(Oe.current,_t)?Oe.current:(Oe.current=_t,_t),[_t]),sn=H.useMemo(()=>{const V=R.id;if(typeof V!="string"||!V.includes("{{"))return V;const se=i.resolveBindings(V,ee,{skipCache:!0});return typeof se=="string"?se:V},[R.id,ee,i]),Mt=H.useMemo(()=>{const V=R.dataKey,se=R.trackChanges,ce=R.debounce;if(V){const Z=V.startsWith("_global."),ve=Z?V.slice(8):V,ge=Z?e._global:ee._local;return{dataKey:ve,trackChanges:se??!1,debounce:ce,setState:Z?Ae=>{e._globalSetState?e._globalSetState(Ae):st.warn("_global.formData 사용 시 _globalSetState가 필요합니다.")}:xe,state:ge??{},_isGlobal:Z}}if(te.dataKey)return te},[R.dataKey,R.trackChanges,R.debounce,R.id,xe,ee._local,e._global,e._globalSetState,te]),Vt=H.useRef(new Set);H.useEffect(()=>{Vt.current.clear()},[e._global?._remountKeys]);const on=H.useCallback((V,se)=>{const ce=e._global?._remountKeys;return V&&ce?.[V]&&!Vt.current.has(V)?(Vt.current.add(V),`${V}-remount-${ce[V]}`):V||se},[e._global?._remountKeys]),ln=H.useMemo(()=>{if(R.text!==void 0){if(typeof R.text=="string"){const V=R.text;let se=V;const ce={componentId:R.id,componentName:R.name,propName:"text",skipCache:!0},Z=vt(V);if(Z!==null){let ve=!1,ge=Z;if(Z.startsWith(nr)&&(ve=!0,ge=Z.slice(nr.length)),Vn(ge))try{se=i.evaluatePipeExpression(ge,ee,{skipCache:!0},ce)}catch(we){st.warn(`text 파이프 표현식 평가 실패 (컴포넌트: ${R.id}):`,we),se=""}else if(Ot(ge))try{se=i.evaluateExpression(ge,ee,ce)}catch(we){st.warn(`text 표현식 평가 실패 (컴포넌트: ${R.id}):`,we),se=""}else se=i.resolve(ge,ee,{skipCache:!0},ce);ve&&se!=null&&(se=ts(se))}else V.includes("{{")&&(se=i.resolveBindings(V,ee,{skipCache:!0},ce));return se=ot(se),se??""}return R.text}return!R.children||R.children.length===0?null:R.children.map((V,se)=>{if(typeof V=="string")return V;const ce=R.__responsiveChildrenKey,Z=ce?`responsive.${ce}.children`:"children",ve=O?`${O}.${Z}.${se}`:`${se}`,ge=V._fromBase?V.id||`child-${se}`:on(V.id,`child-${se}`),we=!V._fromBase&&M?`${ge}_${M}`:ge;return gt.jsx(Ur,{componentDef:V,dataContext:ee,translationContext:n,registry:a,bindingEngine:i,translationEngine:l,actionDispatcher:c,parentComponentContext:_e,parentFormContextProp:Mt,isInsideIteration:y,isEditMode:S,onComponentSelect:v,onComponentHover:_,componentPath:ve,onDragStart:A,onDragEnd:x,layoutKey:M},we)})},[R.text,R.children,R.id,e,ee,n,a,i,l,c,_e,Mt,vt,Ot,ot,on,y,S,v,_,O,A,x,M]),Br=H.useMemo(()=>{if(!R.iteration)return null;try{const V=wd(R.iteration.source,ee,i);if(!Array.isArray(V))return st.warn(`iteration source가 배열이 아닙니다 (컴포넌트: ${R.id}):`,V),null;const se=Zr();return se?.isEnabled()&&se.trackIteration(R.id||`iteration-${Date.now()}`,R.iteration.source,R.iteration.item_var,R.iteration.index_var,V.length),V.map((ce,Z)=>{const ve={...ee,[R.iteration.item_var]:ce,[`${R.iteration.item_var}_index`]:Z};R.iteration.index_var&&(ve[R.iteration.index_var]=Z);const ge=Z===0||Z===V.length-1;se?.isEnabled()&&ge&&se.trackNestedContext?.({componentId:`${R.id||"unknown"}-iter-${Z}`,componentType:"iteration",parentContext:{available:Object.keys(ee),values:{}},ownContext:{added:[R.iteration.item_var,...R.iteration.index_var?[R.iteration.index_var]:[]],values:{[R.iteration.item_var]:ce,...R.iteration.index_var?{[R.iteration.index_var]:Z}:{}}},depth:1});const we={...R,iteration:void 0,responsive:void 0},Ae=O?`${O}.iteration.${Z}`:`iteration.${Z}`,Ke=`${on(R.id,"item")}-${Z}`;return gt.jsx(Ur,{componentDef:we,dataContext:ve,translationContext:n,registry:a,bindingEngine:i,translationEngine:l,actionDispatcher:c,parentComponentContext:_e,parentFormContextProp:Mt,isInsideIteration:!0,isEditMode:S,onComponentSelect:v,onComponentHover:_,componentPath:Ae,onDragStart:A,onDragEnd:x,layoutKey:M},Ke)})}catch(V){return st.error(`iteration 렌더링 실패 (컴포넌트: ${R.id}):`,V),null}},[R.iteration,R.children,R.id,e,ee,n,a,i,l,c,_e,Mt,on,S,v,_,O,A,x,M]),[Ft,Ci]=H.useState(0),na=H.useMemo(()=>{if(!R.sortable||!R.itemTemplate)return null;const{sortable:V,itemTemplate:se}=R;try{const ce=wd(V.source,ee,i);if(!Array.isArray(ce))return st.warn(`sortable source가 배열이 아닙니다 (컴포넌트: ${R.id}):`,ce),null;if(ce.length===0)return null;const Z=V.itemKey||"id",ve=V.strategy||"verticalList",ge=V.itemVar||"$item",we=V.indexVar||"$index",Ae=V.handle,Ke=V.wrapperElement,Te=(et,je)=>{const Ve=R.actions?.filter(Se=>Se.event==="onSortEnd"||Se.type==="onSortEnd");if(Ci(Se=>Se+1),Ve&&Ve.length>0&&c){const Se={...ee,$sortedItems:et,$sortEvent:je};Ve.forEach(He=>{try{const tt=c.createHandler(He,Se,_e),Tt=new Event("sortend");tt(Tt)}catch(tt){st.error(`onSortEnd action failed: ${He.handler}`,tt)}})}},$e=et=>{const je=R.actions?.filter(Ve=>Ve.event==="onSortStart"||Ve.type==="onSortStart");if(je&&je.length>0&&c){const Ve={...ee,$activeId:et.activeId};je.forEach(Se=>{try{const He=c.createHandler(Se,Ve,_e),tt=new Event("sortstart");He(tt)}catch(He){st.error(`onSortStart action failed: ${Se.handler}`,He)}})}},Re=ce.map((et,je)=>{const Ve=String(et[Z]),Se={...ee,[ge]:et,[we]:je},He=O?`${O}.sortable.${je}`:`sortable.${je}`,tt=`${on(R.id,"sortable-item")}-v${Ft}-${Ve}`;return gt.jsx(U_,{id:Ve,handle:Ae,...Ke?{as:Ke}:{},children:(Ke&&se.children?se.children:[se]).map((Tt,It)=>gt.jsx(Ur,{componentDef:Tt,dataContext:Se,translationContext:n,registry:a,bindingEngine:i,translationEngine:l,actionDispatcher:c,parentComponentContext:_e,parentFormContextProp:void 0,isInsideIteration:!0,isEditMode:S,onComponentSelect:v,onComponentHover:_,componentPath:He,onDragStart:A,onDragEnd:x,layoutKey:M},`${tt}-child-${It}`))},tt)});return gt.jsx(H_,{items:ce,itemKey:Z,strategy:ve,onSortEnd:Te,onSortStart:$e,sortVersion:Ft,children:Re})}catch(ce){return st.error(`sortable 렌더링 실패 (컴포넌트: ${R.id}):`,ce),null}},[R.sortable,R.itemTemplate,R.actions,R.id,e,ee,n,a,i,l,c,_e,on,S,v,_,O,A,x,Ft,M]),yn=H.useRef(!1);H.useEffect(()=>{if(yn.current)return;yn.current=!0;const V=Zr();if(V?.isEnabled()&&V.trackMount(o.id,{name:o.name,type:o.type||"component",props:nt}),o.lifecycle?.onMount&&o.lifecycle.onMount.length>0)for(const ce of o.lifecycle.onMount)try{const Z=c.createHandler(ce,ee,_e),ve=new Event("mount");Z(ve),st.log(`Lifecycle onMount executed: ${ce.handler} (Component: ${o.id})`)}catch(Z){st.error(`Lifecycle onMount failed: ${ce.handler} (Component: ${o.id})`,Z)}const se=[];if(o.onComponentEvent&&o.onComponentEvent.length>0){const ce=window.G7Core;if(ce?.componentEvent?.on)for(const Z of o.onComponentEvent){if(!Z.event){st.warn(`onComponentEvent: event name is required (Component: ${o.id})`);continue}const ve=async we=>{try{const Ae={...ee,_eventData:we},Te=await c.createHandler({handler:Z.handler,params:Z.params,onSuccess:Z.onSuccess,onError:Z.onError},Ae,_e)(new CustomEvent(Z.event,{detail:we}));return st.log(`onComponentEvent "${Z.event}" handled: ${Z.handler} (Component: ${o.id})`,{eventData:we,result:Te}),Te}catch(Ae){throw st.error(`onComponentEvent "${Z.event}" failed: ${Z.handler} (Component: ${o.id})`,Ae),Ae}},ge=ce.componentEvent.on(Z.event,ve);se.push(ge),st.log(`onComponentEvent: Subscribed to "${Z.event}" (Component: ${o.id})`)}else st.warn(`onComponentEvent: G7Core.componentEvent is not available (Component: ${o.id})`)}return()=>{const ce=Zr();ce?.isEnabled()&&ce.trackUnmount(o.id);for(const Z of se)try{Z()}catch(ve){st.error(`onComponentEvent: Failed to unsubscribe (Component: ${o.id})`,ve)}if(se.length>0&&st.log(`onComponentEvent: Unsubscribed ${se.length} event(s) (Component: ${o.id})`),o.lifecycle?.onUnmount&&o.lifecycle.onUnmount.length>0)for(const Z of o.lifecycle.onUnmount)try{const ve=c.createHandler(Z,ee,_e),ge=new Event("unmount");ve(ge),st.log(`Lifecycle onUnmount executed: ${Z.handler} (Component: ${o.id})`)}catch(ve){st.error(`Lifecycle onUnmount failed: ${Z.handler} (Component: ${o.id})`,ve)}}},[]);const On=H.useMemo(()=>{const V=nt.dataKey,se=nt.trackChanges,ce=nt.debounce;return V?{dataKey:V,trackChanges:se??!1,debounce:ce,setState:xe,state:fe}:null},[nt.dataKey,nt.trackChanges,nt.debounce,xe,fe]);H.useEffect(()=>{const V=R.dataKey||nt.dataKey;if(!V)return;const se=Zr();if(!se?.isEnabled())return;const ce=[],Z=ge=>{if(ge)for(const we of ge){const Ae=we?.props?.name;if(Ae&&ce.push({name:Ae,type:we.name||we.type||"unknown"}),we?.children&&Z(we.children),we?.slots)for(const Ke of Object.values(we.slots))Array.isArray(Ke)&&Z(Ke)}};if(o.children&&Z(o.children),o.slots)for(const ge of Object.values(o.slots))Array.isArray(ge)&&Z(ge);const ve=o.id||`form-${V}`;return se.trackForm(ve,V,ce),()=>{se.untrackForm(ve)}},[R,nt.dataKey,o.id,o.children]);const rt=H.useRef(null),Hn=H.useRef(void 0);H.useEffect(()=>{const V=nt?.name;if(!V||!te.dataKey||!te.setState||nt?.autoBinding===!1)return;const se=`${te.dataKey}.${V}`,ce=window.__g7AutoBindingPaths??new Map;return window.__g7AutoBindingPaths=ce,ce.set(se,(ce.get(se)??0)+1),()=>{const Z=ce.get(se)??0;Z<=1?ce.delete(se):ce.set(se,Z-1)}},[nt?.name,nt?.autoBinding,te.dataKey,te.setState]);const bn=H.useMemo(()=>{const{dataKey:V,trackChanges:se,debounce:ce,...Z}=nt,ve=Z.name;if(!ve||!te.dataKey||!te.setState)return Z;if(Z.autoBinding===!1){const{autoBinding:Ve,...Se}=Z;return Se}const ge=`${te.dataKey}.${ve}`,we=jC(te.state,ge),Ae=we!==void 0?we:Z.value,Te=a.getMetadata(o.name)?.bindingType,$e=typeof Ae=="boolean"&&(Te==="checked"||Te==="checkable"&&["checkbox","radio"].includes(Z?.type)),Re=te.debounce,et=Ve=>{const Se=te.state||{},He=zC(Se,ge,Ve);if(te.trackChanges&&(He.hasChanges=!0),window.__g7PendingLocalState=He,te.setState(He),te._isGlobal)return;const tt=window.G7Core,Tt={[ge]:Ve};te.trackChanges&&(Tt.hasChanges=!0),tt?.state?.setLocal?.(Tt,{render:!1})},je=(Ve,Se)=>{Re&&Re>0?(rt.current&&clearTimeout(rt.current),rt.current=setTimeout(()=>{et(Ve),rt.current=null,Se&&(Se(),window.__g7PendingLocalState=null)},Re)):(et(Ve),Se&&(Se(),window.__g7PendingLocalState=null))};if($e)return{...Z,checked:Ae,onChange:Se=>{const He=Se.target.checked;et(He),Z.onChange&&Z.onChange(Se)}};{const Ve=He=>{const tt=He?.target?.value!==void 0?He.target.value:He;Re&&Re>0&&(Hn.current=tt);const Tt=He?.target?{target:{value:tt,name:He.target.name,type:He.target.type,checked:He.target.checked},currentTarget:He.currentTarget?{value:tt,name:He.currentTarget.name}:void 0}:He;je(tt,()=>{Z.onChange&&Z.onChange(Tt)})};let Se;return Hn.current!==void 0?rt.current!==null?Se=Hn.current:String(Ae??"")===String(Hn.current)?(Hn.current=void 0,Se=Ae??""):Se=Hn.current:Se=Ae??"",{...Z,value:Se,onChange:Ve}}},[nt,te,te.state,te.debounce]),ra=H.useMemo(()=>{const V=Zr();if(V?.isEnabled()&&V.trackRender(o.name),!Be)return R.iteration?Br:null;if(R.sortable&&R.itemTemplate)return na;if(R.iteration)return Br;if(R.type==="extension_point"){const{layout:Te,columns:$e,gap:Re,className:et,...je}=R.props||{};let Ve=et||"";return Te==="grid"&&$e?Ve=`${Ve} grid grid-cols-${$e} gap-${Re||4}`.trim():Te==="flex"&&(Ve=`${Ve} flex flex-col gap-${Re||4}`.trim()),gt.jsx("div",{id:sn,className:Ve||void 0,...je,children:ln})}const se=a.getComponent(R.name);if(!se)return st.error(`컴포넌트를 찾을 수 없습니다: ${R.name} (ID: ${R.id})`,"componentDef:",JSON.stringify(R,null,2)),null;const ce=e._global?._remountKeys,Z={...bn,id:bn.id??sn,...ce&&Object.keys(ce).length>0&&{__remountTrigger:JSON.stringify(ce)}};if(N?.listeners&&N.handle&&bn["data-drag-handle"]!==void 0&&(Object.assign(Z,N.listeners),Z.style={...Z.style,cursor:N.isDragging?"grabbing":"grab"}),S){const Te=R.id||`auto_${R.name||R.type||"unknown"}_${Math.random().toString(36).substring(2,8)}`,$e=je=>{typeof je.preventDefault=="function"&&je.preventDefault(),v&&(je.stopPropagation(),v(Te,je))},Re={"data-editor-id":Te,"data-editor-name":R.name,"data-editor-type":R.type,onClick:$e};O&&(Re["data-editor-path"]=O);const et=R.__editorSlotName??R.slot;typeof et=="string"&&(Re["data-editor-slot"]=et),_&&(Re.onMouseMove=je=>{je.stopPropagation(),_(Te,je)},Re.onMouseLeave=je=>{_(null,je)}),Object.assign(Z,Re),Z.editorAttrs=Re}const ve=R.type==="basic",ge=R.type==="layout",Ae=ve||ge?(()=>{const Te={};for(const[$e,Re]of Object.entries(Z))$e.startsWith("__")||$e==="editorAttrs"&&ve||(Te[$e]=Re);return Te})():Z;let Ke=gt.jsx(se,{...Ae,children:ln});if(Mt&&(Ke=gt.jsx(IC,{value:Mt,children:Ke})),R.isolatedState){const Te=typeof R.isolatedState=="object"?R.isolatedState:{},$e=e?._isolatedInit,Re=$e?{...Te,...$e}:Te;Ke=gt.jsx(VC,{initialState:Re,scopeId:R.isolatedScopeId,children:Ke})}return Ke},[Be,R.iteration,R.sortable,R.itemTemplate,R.type,R.name,R.id,R.props,R.isolatedState,R.isolatedScopeId,a,bn,ln,Br,na,N,On,S,v,_,A,x,O,e._global?._remountKeys]),sr=H.useMemo(()=>{const V=R.blur_until_loaded;if(!V||e?._global?.__isPreview)return!1;if(typeof V=="string"){const Z=vt(V);if(Z!==null)try{const ve={componentId:R.id,componentName:R.name,propName:"blur_until_loaded"};return!!i.evaluateExpression(Z,ee,ve)}catch(ve){return st.warn(`blur_until_loaded 표현식 평가 실패 (컴포넌트: ${R.id}):`,ve),!1}return!1}if(typeof V=="object"&&V!==null){if(!V.enabled)return!1;if(V.data_sources)return(Array.isArray(V.data_sources)?V.data_sources:[V.data_sources]).some(we=>e[we]===void 0);if(ue)return!0;const Z=["route","query","_global","_local","_dataSourceErrors"];return Object.keys(e).filter(ge=>!Z.includes(ge)&&!ge.startsWith("_")).some(ge=>e[ge]===void 0)}if(ue)return!0;const se=["route","query","_global","_local","_dataSourceErrors"];return Object.keys(e).filter(Z=>!se.includes(Z)&&!Z.startsWith("_")).some(Z=>e[Z]===void 0)},[R.blur_until_loaded,R.id,ue,e,ee,i,vt]);if(sr){const V=R.blur_until_loaded;if(typeof V=="object"&&V?.data_sources){const ce=(Array.isArray(V.data_sources)?V.data_sources:[V.data_sources]).reduce((ve,ge)=>(ve[ge]=e[ge]===void 0?"UNDEFINED":"LOADED",ve),{}),Z=Object.keys(e).filter(ve=>!ve.startsWith("_"));st.log(`[BLUR APPLIED] Component: ${R.id}, data_sources status:`,ce,"Available keys:",Z)}else st.log(`[BLUR APPLIED] Component: ${R.id}, blur_until_loaded:`,V)}const cn=H.useMemo(()=>{if(!R.blur_until_loaded)return{wrapperClasses:"",innerClasses:""};const V=_t?.className||"";if(!V)return{wrapperClasses:"",innerClasses:""};const se=[/^(sm:|md:|lg:|xl:|2xl:)?(col-span-|row-span-|col-start-|col-end-|row-start-|row-end-)/,/^(sm:|md:|lg:|xl:|2xl:)?(self-|place-self-|order-)/,/^(sm:|md:|lg:|xl:|2xl:)?(flex-shrink|flex-grow|flex-\d|basis-)/],ce=V.split(/\s+/).filter(Boolean),Z=[],ve=[];for(const ge of ce)se.some(Ae=>Ae.test(ge))?Z.push(ge):ve.push(ge);return{wrapperClasses:Z.join(" "),innerClasses:ve.join(" ")}},[R.blur_until_loaded,_t?.className]),Tn=window.__slotContextValue;return ke&&Tn?.isEnabled?null:gt.jsx(q_,{componentId:o.id,componentName:o.name,children:R.blur_until_loaded?gt.jsx("div",{className:[cn.wrapperClasses,sr?"opacity-50 blur-sm transition-all duration-300 pointer-events-none":void 0].filter(Boolean).join(" ")||void 0,children:ra}):ra})});Ur.displayName="DynamicRenderer",typeof window<"u"&&(window.__DynamicRenderer=Ur);function G_(o){const e={};try{const a=new URL(o,typeof window<"u"?window.location.origin:"http://localhost").searchParams,i=new Set;for(const l of a.keys()){if(i.has(l))continue;i.add(l);const c=a.getAll(l);c.length>1||l.endsWith("[]")?e[l]=c:e[l]=c[0]}}catch{}return e}function V_(o){try{return new URL(o,typeof window<"u"?window.location.origin:"http://localhost").pathname}catch{return o.split("?")[0]}}const wi=class wi{constructor(){$(this,"config",{enabled:!1,maxHistorySize:100,logLevel:"info",serverEndpoint:"/_boost/g7-debug/dump-state",autoCapture:!0});$(this,"stateHistory",[]);$(this,"snapshotIdCounter",0);$(this,"stateWatchers",new Map);$(this,"currentLocalState",{});$(this,"currentComputedState",{});$(this,"currentParentContext");$(this,"actionHistory",[]);$(this,"actionWatchers",new Set);$(this,"cacheStats",{hits:0,misses:0,entries:0});$(this,"cacheDecisions",[]);$(this,"cacheDecisionIdCounter",0);$(this,"maxCacheDecisions",200);$(this,"currentRenderCycleId",null);$(this,"isInActionExecution",!1);$(this,"isInIteration",!1);$(this,"mountedComponents",new Map);$(this,"eventListeners",new Map);$(this,"mountWatchers",new Set);$(this,"unmountWatchers",new Set);$(this,"renderCounts",new Map);$(this,"bindingEvalCount",0);$(this,"profilingData",[]);$(this,"isProfiling",!1);$(this,"profilingStartTime",0);$(this,"activeRequests",new Map);$(this,"requestHistory",[]);$(this,"pendingDataSources",new Set);$(this,"requestWatchers",new Set);$(this,"wsConnections",new Map);$(this,"wsMessageHistory",[]);$(this,"wsMessageWatchers",new Set);$(this,"ifConditions",new Map);$(this,"iterations",new Map);$(this,"conditionWatchers",new Set);$(this,"forms",new Map);$(this,"formWatchers",new Set);$(this,"expressionHistory",[]);$(this,"expressionWatchers",new Set);$(this,"expressionIdCounter",0);$(this,"dataSources",new Map);$(this,"dataPathTransforms",[]);$(this,"maxDataPathTransforms",100);$(this,"nestedContexts",[]);$(this,"nestedContextIdCounter",0);$(this,"maxNestedContexts",100);$(this,"formBindingIssues",[]);$(this,"formBindingValidations",[]);$(this,"formBindingIssueIdCounter",0);$(this,"maxFormBindingIssues",100);$(this,"computedProperties",new Map);$(this,"computedRecalcLogs",[]);$(this,"computedRecalcIdCounter",0);$(this,"maxComputedRecalcLogs",100);$(this,"handlers",new Map);$(this,"componentEventSubscriptions",new Map);$(this,"componentEventEmitHistory",[]);$(this,"componentEventIdCounter",0);$(this,"maxComponentEventHistory",100);$(this,"stateRenderingLogs",[]);$(this,"currentStateChangeContext",null);$(this,"stateRenderingIdCounter",0);$(this,"componentRenderCounts",new Map);$(this,"stateToComponentMap",new Map);$(this,"maxStateRenderingLogs",100);$(this,"componentStateSources",new Map);$(this,"contextFlowNodes",new Map);$(this,"dynamicStates",new Map);$(this,"styleIssues",[]);$(this,"componentStyles",new Map);$(this,"authEvents",[]);$(this,"authHeaderHistory",[]);$(this,"authEventIdCounter",0);$(this,"maxAuthEventHistory",50);$(this,"logHistory",[]);$(this,"logIdCounter",0);$(this,"maxLogHistory",500);$(this,"currentLayout",null);$(this,"layoutHistory",[]);$(this,"layoutIdCounter",0);$(this,"layoutStats",{totalLoads:0,cacheHits:0,apiLoads:0});$(this,"maxLayoutHistory",50);$(this,"executionDetails",new Map);$(this,"stateChangeHistory",[]);$(this,"dataSourceChangeHistory",[]);$(this,"changeAlerts",[]);$(this,"changeExpectations",new Map);$(this,"executionIdCounter",0);$(this,"stateChangeIdCounter",0);$(this,"alertIdCounter",0);$(this,"maxExecutionDetails",100);$(this,"maxStateChangeHistory",200);$(this,"maxChangeAlerts",100);$(this,"sequenceExecutions",[]);$(this,"sequenceExecutionStack",[]);$(this,"sequenceIdCounter",0);$(this,"maxSequenceExecutions",50);$(this,"staleClosureWarnings",[]);$(this,"staleClosureIdCounter",0);$(this,"maxStaleClosureWarnings",100);$(this,"stateCaptureRegistry",new Map);$(this,"modalStates",new Map);$(this,"modalStateIssues",[]);$(this,"modalStateRelations",[]);$(this,"modalStateChangeLogs",[]);$(this,"modalStateIssueIdCounter",0);$(this,"modalStateChangeLogIdCounter",0);$(this,"maxModalStateIssues",100);$(this,"maxModalStateChangeLogs",200);$(this,"namedActionDefinitions",{});$(this,"namedActionRefLogs",[]);$(this,"namedActionRefIdCounter",0);$(this,"maxNamedActionRefLogs",200);$(this,"debounceActionHistory",[]);$(this,"dataSourceUpdateHistory",[]);$(this,"debounceActionIdCounter",0);$(this,"dataSourceUpdateIdCounter",0)}static getInstance(){return wi.instance||(wi.instance=new wi),wi.instance}initialize(){if(this.config.enabled=this.checkDebugMode(),!this.config.enabled){console.log("[G7DevTools] 비활성화됨 (환경설정 > 고급 설정 > 디버그 모드를 켜세요)");return}console.log("[G7DevTools] 활성화됨"),this.setupGlobalErrorHandler()}checkDebugMode(){try{if(window.G7Config?.debug===!0)return!0;const a=window.G7Core?.state?.get?.();return a?.settings?.advanced?.debug_mode===!0||a?._global?.settings?.advanced?.debug_mode===!0}catch{return!1}}isEnabled(){return this.config.enabled}setupGlobalErrorHandler(){window.addEventListener("error",e=>{this.config.enabled&&this.logAction({id:this.generateId(),type:"globalError",startTime:Date.now(),status:"error",error:{name:"Error",message:e.message,stack:e.error?.stack}})}),window.addEventListener("unhandledrejection",e=>{this.config.enabled&&this.logAction({id:this.generateId(),type:"unhandledRejection",startTime:Date.now(),status:"error",error:{name:"UnhandledRejection",message:String(e.reason)}})})}captureStateSnapshot(e){if(!this.config.enabled)return;const n={id:++this.snapshotIdCounter,timestamp:Date.now(),source:e.source,prev:this.sanitizeObject(e.prev),next:this.sanitizeObject(e.next),diff:e.diff};this.stateHistory.push(n),this.stateHistory.length>this.config.maxHistorySize&&this.stateHistory.shift(),this.notifyStateWatchers(n)}getState(){try{const e=window.G7Core,n=e?.state?.get?.()||{},a={};if(e?._isolatedStates&&typeof e._isolatedStates=="object")for(const[i,l]of Object.entries(e._isolatedStates))a[i]=this.sanitizeObject(l);return{_global:n._global||n,_local:this.currentLocalState,_computed:this.currentComputedState,_isolated:Object.keys(a).length>0?a:void 0,$parent:this.currentParentContext}}catch{return{_global:{},_local:this.currentLocalState,_computed:this.currentComputedState}}}updateLocalState(e){this.config.enabled&&(this.currentLocalState=this.sanitizeObject(e))}updateComputedState(e){this.config.enabled&&(this.currentComputedState=this.sanitizeObject(e))}updateParentContext(e){this.config.enabled&&(this.currentParentContext=e?this.sanitizeObject(e):void 0)}getStateHistory(){return[...this.stateHistory]}getDualStorageMismatch(){const e=this.currentLocalState||{};let n={};try{n=(window.G7Core?.state?.get?.()||{})._local||{}}catch{n={}}const a=this.findMismatchedLeafPaths(e,n);return{hasMismatch:a.length>0,mismatchedPaths:a,storageA:e,storageB:n}}findMismatchedLeafPaths(e,n,a=""){const i=[],l=new Set([...Object.keys(e||{}),...Object.keys(n||{})]);for(const c of l){const d=a?`${a}.${c}`:c,f=e?.[c],g=n?.[c],m=f&&typeof f=="object"&&!Array.isArray(f),y=g&&typeof g=="object"&&!Array.isArray(g);if(m&&y)i.push(...this.findMismatchedLeafPaths(f,g,d));else{const S=f===void 0?"__undefined__":JSON.stringify(f),v=g===void 0?"__undefined__":JSON.stringify(g);S!==v&&i.push(d)}}return i}watchState(e,n){return this.stateWatchers.has(e)||this.stateWatchers.set(e,new Set),this.stateWatchers.get(e).add(n),()=>{this.stateWatchers.get(e)?.delete(n)}}notifyStateWatchers(e){if(this.stateWatchers.get("*")?.forEach(n=>{try{n(e.next,e.prev,"*")}catch(a){console.error("[G7DevTools] State watcher error:",a)}}),e.diff)for(const n of e.diff.changed)this.stateWatchers.get(n.path)?.forEach(a=>{try{a(n.newValue,n.oldValue,n.path)}catch(i){console.error("[G7DevTools] State watcher error:",i)}})}diffSnapshots(e,n){const a=this.stateHistory.find(l=>l.id===e),i=this.stateHistory.find(l=>l.id===n);return!a||!i?null:this.calculateDiff(a.next,i.next)}calculateDiff(e,n,a=""){const i={added:[],removed:[],changed:[]},l=new Set(Object.keys(e)),c=new Set(Object.keys(n));for(const d of c){const f=a?`${a}.${d}`:d;l.has(d)?JSON.stringify(e[d])!==JSON.stringify(n[d])&&i.changed.push({path:f,oldValue:e[d],newValue:n[d]}):i.added.push(f)}for(const d of l){const f=a?`${a}.${d}`:d;c.has(d)||i.removed.push(f)}return i}logAction(e){if(!this.config.enabled)return;const n=this.actionHistory.findIndex(a=>a.id===e.id);n>=0?this.actionHistory[n]={...this.actionHistory[n],...e}:(this.actionHistory.push(e),this.actionHistory.length>this.config.maxHistorySize&&this.actionHistory.shift()),this.isProfiling&&this.profilingData.push({type:"action",action:e.type,timestamp:performance.now(),duration:e.duration}),this.notifyActionWatchers(e)}getActionHistory(){return[...this.actionHistory]}watchActions(e){return this.actionWatchers.add(e),()=>{this.actionWatchers.delete(e)}}notifyActionWatchers(e){this.actionWatchers.forEach(n=>{try{n(e)}catch(a){console.error("[G7DevTools] Action watcher error:",a)}})}getActionMetrics(){const e=this.actionHistory.filter(l=>l.status==="success"),n=this.actionHistory.filter(l=>l.status==="error"),a={};for(const l of this.actionHistory)a[l.type]=(a[l.type]||0)+1;const i=e.reduce((l,c)=>l+(c.duration||0),0);return{totalActions:this.actionHistory.length,successCount:e.length,errorCount:n.length,averageDuration:e.length>0?i/e.length:0,actionsByType:a}}recordCacheHit(){this.config.enabled&&this.cacheStats.hits++}recordCacheMiss(){this.config.enabled&&this.cacheStats.misses++}updateCacheEntries(e){this.config.enabled&&(this.cacheStats.entries=e)}getCacheStats(){const e=this.cacheStats.hits+this.cacheStats.misses;return{...this.cacheStats,hitRate:e>0?this.cacheStats.hits/e:0}}resetCacheStats(){this.cacheStats={hits:0,misses:0,entries:0}}trackCacheDecision(e){if(!this.config.enabled)return;const n={id:`cache-${++this.cacheDecisionIdCounter}`,timestamp:Date.now(),expression:e.expression,decision:e.decision,reason:e.reason,context:{isInIteration:this.isInIteration,isInAction:this.isInActionExecution,renderCycleId:this.currentRenderCycleId||void 0,componentId:e.componentId,skipCacheOption:e.skipCacheOption},cachedValue:e.cachedValue!==void 0?this.safeClone(e.cachedValue):void 0,freshValue:e.freshValue!==void 0?this.safeClone(e.freshValue):void 0,valueMatch:e.cachedValue!==void 0&&e.freshValue!==void 0?JSON.stringify(e.cachedValue)===JSON.stringify(e.freshValue):void 0,duration:e.duration};this.cacheDecisions.push(n),this.cacheDecisions.length>this.maxCacheDecisions&&this.cacheDecisions.shift(),e.decision==="cache_hit"?this.cacheStats.hits++:(e.decision==="cache_miss"||e.decision==="skip_cache")&&this.cacheStats.misses++}startRenderCycle(e){return this.currentRenderCycleId=`render-${Date.now()}-${e||"root"}`,this.currentRenderCycleId}endRenderCycle(){this.currentRenderCycleId=null}startActionContext(){this.isInActionExecution=!0}endActionContext(){this.isInActionExecution=!1}startIterationContext(){this.isInIteration=!0}endIterationContext(){this.isInIteration=!1}getCacheDecisionTrackingInfo(){const e=this.getCacheDecisionStats();return{decisions:[...this.cacheDecisions],stats:e,timestamp:Date.now()}}getCacheDecisionStats(){const e=this.cacheDecisions;let n=0,a=0,i=0,l=0;const c={},d={};for(const g of e)g.decision==="cache_hit"?n++:g.decision==="cache_miss"?a++:g.decision==="skip_cache"?i++:g.decision==="invalidate"&&l++,c[g.reason]=(c[g.reason]||0)+1,g.context.componentId&&(d[g.context.componentId]=(d[g.context.componentId]||0)+1);const f=n+a+i;return{totalDecisions:e.length,cacheHits:n,cacheMisses:a,skipCacheCount:i,invalidateCount:l,byReason:c,byComponent:d,avgHitRate:f>0?n/f:0}}getRecentCacheDecisions(e=20,n){let a=[...this.cacheDecisions];return n&&(a=a.filter(i=>i.decision===n)),a.slice(-e)}clearCacheDecisionData(){this.cacheDecisions=[],this.cacheDecisionIdCounter=0}summarizePropsForLifecycle(e){if(!e||typeof e!="object")return e;const n={},a=["children","ref","key"];for(const[i,l]of Object.entries(e))if(!a.includes(i))if(l==null)n[i]=l;else if(Array.isArray(l))n[i]=`[Array(${l.length})]`;else if(typeof l=="object"){const c=Object.keys(l);c.length<=3?n[i]=`{${c.join(", ")}}`:n[i]=`{${c.slice(0,3).join(", ")}, ... +${c.length-3}}`}else typeof l=="string"&&l.length>50?n[i]=l.substring(0,50)+"...":typeof l=="function"?n[i]="[Function]":n[i]=l;return n}trackMount(e,n){if(!this.config.enabled)return;const a=this.summarizePropsForLifecycle(n.props),i={...n,props:a,id:e,mountTime:Date.now()};this.mountedComponents.set(e,i),this.mountWatchers.forEach(l=>{try{l({componentId:e,componentName:n.name,timestamp:i.mountTime})}catch(c){console.error("[G7DevTools] Mount watcher error:",c)}})}trackUnmount(e){if(!this.config.enabled)return;const n=this.mountedComponents.get(e),a=this.eventListeners.get(e)||[];a.length>0&&console.warn(`[G7DevTools] 컴포넌트 ${e} 언마운트 시 ${a.length}개 리스너 미정리`),n&&this.unmountWatchers.forEach(i=>{try{i({componentId:e,componentName:n.name,timestamp:Date.now(),orphanedListeners:a.length})}catch(l){console.error("[G7DevTools] Unmount watcher error:",l)}}),this.mountedComponents.delete(e)}trackListener(e,n,a){if(!this.config.enabled)return;const i=this.eventListeners.get(e)||[];i.push({type:n,target:a,addedAt:Date.now()}),this.eventListeners.set(e,i)}removeListener(e,n){if(!this.config.enabled)return;const a=this.eventListeners.get(e)||[],i=a.findIndex(l=>l.type===n);i>=0&&a.splice(i,1)}getMountedComponents(){return Array.from(this.mountedComponents.values())}getOrphanedListeners(){const e=[];for(const[n,a]of this.eventListeners)this.mountedComponents.has(n)||e.push(...a.map(i=>({...i,componentId:n})));return e}watchMount(e){return this.mountWatchers.add(e),()=>{this.mountWatchers.delete(e)}}watchUnmount(e){return this.unmountWatchers.add(e),()=>{this.unmountWatchers.delete(e)}}trackRender(e){if(!this.config.enabled)return;const n=this.renderCounts.get(e)||0;this.renderCounts.set(e,n+1),this.isProfiling&&this.profilingData.push({type:"render",component:e,timestamp:performance.now()})}trackBindingEval(e){this.config.enabled&&(this.bindingEvalCount++,this.isProfiling&&e&&this.profilingData.push({type:"binding",expression:e,timestamp:performance.now()}))}getRenderCount(){return new Map(this.renderCounts)}getBindingEvalCount(){return this.bindingEvalCount}getMemoryWarnings(){const e=[];try{const i=this.getState(),l=JSON.stringify(i).length;l>1e6&&e.push({type:"large-state",message:`상태 크기가 ${(l/1024/1024).toFixed(2)}MB입니다`,suggestion:"불필요한 데이터 정리 필요",severity:l>5e6?"error":"warning"})}catch{}this.stateHistory.length>this.config.maxHistorySize*.8&&e.push({type:"large-history",message:`상태 이력이 ${this.stateHistory.length}개입니다`,suggestion:"maxHistory 설정 조정 필요",severity:"warning"});const n=this.getOrphanedListeners().length;n>0&&e.push({type:"orphaned-listeners",message:`${n}개의 정리되지 않은 이벤트 리스너`,suggestion:"useEffect cleanup 또는 removeEventListener 확인",severity:n>10?"error":"warning"});const a=Array.from(this.renderCounts.entries()).filter(([,i])=>i>50);return a.length>0&&e.push({type:"excessive-renders",message:`${a.length}개 컴포넌트가 50회 이상 렌더링`,suggestion:"React.memo, useMemo, useCallback 사용 고려",severity:"warning"}),e}startProfiling(){this.config.enabled&&(this.isProfiling=!0,this.profilingData=[],this.profilingStartTime=performance.now(),console.log("[G7DevTools] 프로파일링 시작"))}stopProfiling(){this.isProfiling=!1;const e=performance.now()-this.profilingStartTime,n=this.analyzeProfile(e);return console.log("[G7DevTools] 프로파일링 완료",n),n}analyzeProfile(e){const n=this.profilingData.filter(g=>g.type==="render"),a=this.profilingData.filter(g=>g.type==="binding"),i=this.profilingData.filter(g=>g.type==="action"),l={};for(const g of n)g.component&&(l[g.component]||(l[g.component]={count:0,durations:[]}),l[g.component].count++,g.duration&&l[g.component].durations.push(g.duration));const c=Object.entries(l).map(([g,m])=>({name:g,renderCount:m.count,avgDuration:m.durations.length>0?m.durations.reduce((y,S)=>y+S,0)/m.durations.length:0})).sort((g,m)=>m.renderCount-g.renderCount).slice(0,10),d={};for(const g of a)g.expression&&(d[g.expression]=(d[g.expression]||0)+1);const f=Object.entries(d).sort(([,g],[,m])=>m-g).slice(0,10).map(([g])=>g);return{duration:e,entries:this.profilingData,summary:{totalRenders:n.length,totalBindings:a.length,totalActions:i.length,slowestComponents:c,hotPaths:f}}}resetPerformanceStats(){this.renderCounts.clear(),this.bindingEvalCount=0,this.profilingData=[]}trackRequest(e,n,a){if(!this.config.enabled)return"";const i=this.generateId(),l=V_(e),c=G_(e);return this.activeRequests.set(i,{id:i,url:l,fullUrl:e,queryParams:c,method:n,startTime:Date.now(),status:"pending",requestBody:a?.requestBody?this.sanitizeObject(a.requestBody):void 0,dataSourceId:a?.dataSourceId}),this.isProfiling&&this.profilingData.push({type:"network",timestamp:performance.now()}),i}completeRequest(e,n,a){if(!this.config.enabled)return;const i=this.activeRequests.get(e);if(!i)return;const l={...i,status:n>=200&&n<300?"success":"error",statusCode:n,duration:Date.now()-i.startTime,endTime:Date.now(),response:this.sanitizeObject(a)};this.requestHistory.push(l),this.activeRequests.delete(e),this.requestHistory.length>this.config.maxHistorySize&&this.requestHistory.shift(),this.notifyRequestWatchers(l)}failRequest(e,n){if(!this.config.enabled)return;const a=this.activeRequests.get(e);if(!a)return;const i={...a,status:"error",error:n,duration:Date.now()-a.startTime,endTime:Date.now()};this.requestHistory.push(i),this.activeRequests.delete(e),this.notifyRequestWatchers(i)}trackDataSource(e){this.config.enabled&&this.pendingDataSources.add(e)}completeDataSource(e){this.config.enabled&&this.pendingDataSources.delete(e)}getActiveRequests(){return Array.from(this.activeRequests.values())}getRequestHistory(){return[...this.requestHistory]}getPendingDataSources(){return Array.from(this.pendingDataSources)}watchRequest(e){return this.requestWatchers.add(e),()=>{this.requestWatchers.delete(e)}}notifyRequestWatchers(e){this.requestWatchers.forEach(n=>{try{n(e)}catch(a){console.error("[G7DevTools] Request watcher error:",a)}})}trackWebSocketConnection(e,n,a){this.config.enabled&&this.wsConnections.set(e,{id:e,url:n,state:a,connectedAt:a==="open"?Date.now():void 0})}trackWebSocketMessage(e,n,a,i){if(!this.config.enabled)return;const l={id:this.generateId(),connectionId:e,direction:n,type:a,payload:this.sanitizeObject(i),timestamp:Date.now(),sequence:this.wsMessageHistory.length};this.wsMessageHistory.push(l),this.wsMessageHistory.length>this.config.maxHistorySize&&this.wsMessageHistory.shift(),this.notifyWebSocketWatchers(l)}getWebSocketConnections(){return Array.from(this.wsConnections.values())}getWebSocketMessageHistory(){return[...this.wsMessageHistory]}getWebSocketConnectionState(){const e=Array.from(this.wsConnections.values());return e.some(n=>n.state==="open")?"connected":e.some(n=>n.state==="connecting")?"reconnecting":"disconnected"}watchWebSocketMessage(e){return this.wsMessageWatchers.add(e),()=>{this.wsMessageWatchers.delete(e)}}notifyWebSocketWatchers(e){this.wsMessageWatchers.forEach(n=>{try{n(e)}catch(a){console.error("[G7DevTools] WebSocket watcher error:",a)}})}trackIfCondition(e,n,a){if(!this.config.enabled)return;const i=this.ifConditions.get(e),l=i?.evaluatedValue;this.ifConditions.set(e,{id:e,expression:n,evaluatedValue:a,evaluationCount:(i?.evaluationCount||0)+1,lastEvaluated:Date.now()}),i&&l!==a&&this.notifyConditionWatchers({id:e,expression:n,oldValue:l,newValue:a,timestamp:Date.now()})}trackIteration(e,n){this.config.enabled&&this.iterations.set(e,{id:e,...n,lastRendered:Date.now()})}getIfConditions(){return Array.from(this.ifConditions.values())}getIterations(){return Array.from(this.iterations.values())}getScopeChain(e){const n=[],a=this.getState();return n.push({name:"_global",value:a._global,source:"global"}),n.push({name:"_local",value:a._local,source:"local"}),n.push({name:"_computed",value:a._computed,source:"computed"}),n}watchConditionChange(e){return this.conditionWatchers.add(e),()=>{this.conditionWatchers.delete(e)}}notifyConditionWatchers(e){this.conditionWatchers.forEach(n=>{try{n(e)}catch(a){console.error("[G7DevTools] Condition watcher error:",a)}})}trackForm(e,n,a){this.config.enabled&&this.forms.set(e,{id:e,dataKey:n,inputs:a,trackedAt:Date.now()})}trackFormChange(e,n,a){if(!this.config.enabled)return;const i={formId:e,inputName:n,value:a,timestamp:Date.now()};this.notifyFormWatchers(i)}untrackForm(e){this.forms.delete(e)}getForms(){return Array.from(this.forms.values())}getFormState(e){const n=this.getState();return this.getNestedValue(n,e)||{}}getBindingPath(e,n){return`${e}.${n}`}watchFormChange(e){return this.formWatchers.add(e),()=>{this.formWatchers.delete(e)}}notifyFormWatchers(e){this.formWatchers.forEach(n=>{try{n(e)}catch(a){console.error("[G7DevTools] Form watcher error:",a)}})}trackFormBindingIssue(e,n,a){if(!this.config.enabled)return;const i={id:`form-issue-${++this.formBindingIssueIdCounter}`,timestamp:Date.now(),type:e,severity:n,formId:a.formId,formDataKey:a.formDataKey,inputInfo:a.inputInfo,contextInfo:a.contextInfo,description:a.description,suggestion:a.suggestion,docLink:a.docLink};this.formBindingIssues.push(i),this.formBindingIssues.length>this.maxFormBindingIssues&&this.formBindingIssues.shift()}validateFormBinding(e,n,a,i){const l=a.map(f=>{const g=[];let m=!0;return f.name||(g.push("Input에 name 속성이 없습니다"),m=!1,this.trackFormBindingIssue("missing-input-name","error",{formId:e,formDataKey:n,inputInfo:{name:f.name,type:f.type},description:"Input에 name 속성이 없어 자동 바인딩이 동작하지 않습니다",suggestion:"Input에 name 속성을 추가하세요",docLink:"troubleshooting-components-form.md"})),n||(g.push("Form에 dataKey가 없습니다"),m=!1),{inputName:f.name||"(unnamed)",inputType:f.type,bindingPath:n&&f.name?`${n}.${f.name}`:"-",isValid:m,currentValue:f.value,issues:g}});if(n||this.trackFormBindingIssue("missing-datakey","error",{formId:e,description:"Form에 dataKey가 설정되지 않아 자동 바인딩이 동작하지 않습니다",suggestion:"Form 컴포넌트에 dataKey props를 설정하세요",docLink:"troubleshooting-components-form.md"}),i.isContextBroken){const f=i.breakReason?.includes("sortable")?"sortable-context-break":i.breakReason?.includes("modal")?"modal-context-isolation":"context-not-propagated";this.trackFormBindingIssue(f,"warning",{formId:e,formDataKey:n,contextInfo:{hasParentFormContext:i.hasParentContext,parentFormContextProp:i.parentContextProp,isInsideSortable:i.breakReason?.includes("sortable")||!1,isInsideModal:i.breakReason?.includes("modal")||!1,depth:0},description:i.breakReason||"Form 컨텍스트가 제대로 전파되지 않았습니다",suggestion:"parentFormContextProp={undefined}를 확인하거나 중첩 구조를 검토하세요",docLink:"troubleshooting-components-form.md"})}const c={formId:e,dataKey:n,timestamp:Date.now(),contextPropagation:i,inputBindings:l,isValid:l.every(f=>f.isValid)&&!!n&&!i.isContextBroken,issueCount:l.filter(f=>!f.isValid).length+(n?0:1)+(i.isContextBroken?1:0)},d=this.formBindingValidations.findIndex(f=>f.formId===e);return d>=0?this.formBindingValidations[d]=c:this.formBindingValidations.push(c),c}getFormBindingValidationTrackingInfo(){const e=this.getFormBindingValidationStats();return{issues:[...this.formBindingIssues],validations:[...this.formBindingValidations],stats:e,timestamp:Date.now()}}getFormBindingValidationStats(){const e={"missing-datakey":0,"missing-input-name":0,"context-not-propagated":0,"sortable-context-break":0,"modal-context-isolation":0,"duplicate-input-name":0,"value-type-mismatch":0,"binding-path-invalid":0},n={info:0,warning:0,error:0};for(const i of this.formBindingIssues)e[i.type]++,n[i.severity]++;const a=Object.entries(e).filter(([,i])=>i>0).sort((i,l)=>l[1]-i[1]).slice(0,5).map(([i,l])=>({type:i,count:l,description:this.getIssueTypeDescription(i)}));return{totalFormsValidated:this.formBindingValidations.length,validForms:this.formBindingValidations.filter(i=>i.isValid).length,formsWithIssues:this.formBindingValidations.filter(i=>!i.isValid).length,totalIssues:this.formBindingIssues.length,byIssueType:e,bySeverity:n,topIssues:a}}getIssueTypeDescription(e){return{"missing-datakey":"Form에 dataKey 누락","missing-input-name":"Input에 name 속성 누락","context-not-propagated":"Form 컨텍스트 전파 실패","sortable-context-break":"Sortable 컨테이너에서 컨텍스트 단절","modal-context-isolation":"모달에서 부모 Form 컨텍스트 격리","duplicate-input-name":"동일 Form 내 중복 Input name","value-type-mismatch":"값 타입과 Input 타입 불일치","binding-path-invalid":"바인딩 경로 유효하지 않음"}[e]||e}clearFormBindingIssues(){this.formBindingIssues=[],this.formBindingValidations=[],this.formBindingIssueIdCounter=0}trackComputedProperty(e,n,a,i,l,c,d){if(!this.config.enabled)return;const f=`computed-${e}-${c||"global"}`,g={id:f,name:e,expression:n,componentId:c,dependencies:a,currentValue:this.safeClone(i),lastComputedAt:Date.now(),computationTime:l,error:d};this.computedProperties.set(f,g)}trackComputedRecalc(e,n,a,i,l,c,d){if(!this.config.enabled)return;const f=`computed-${e}-${d||"global"}`,g=JSON.stringify(a)!==JSON.stringify(i),m={id:`recalc-${++this.computedRecalcIdCounter}`,computedId:f,computedName:e,timestamp:Date.now(),trigger:n,triggeredBy:c,previousValue:this.safeClone(a),newValue:this.safeClone(i),valueChanged:g,computationTime:l,cascadeCount:0};this.computedRecalcLogs.push(m),this.computedRecalcLogs.length>this.maxComputedRecalcLogs&&this.computedRecalcLogs.shift();const y=this.computedProperties.get(f);y&&(y.currentValue=this.safeClone(i),y.lastComputedAt=Date.now(),y.computationTime=l)}analyzeComputedDependencyChain(e,n){const a=new Set,i=[];let l=!1,c,d=0;const f=(m,y)=>{if(d=Math.max(d,y),a.has(m))return l=!0,c=[...i,m],{name:m,type:"computed",children:[],depth:y};a.add(m),i.push(m);const S=this.computedProperties.get(`computed-${m}-${n||"global"}`),v=[];if(S)for(const _ of S.dependencies)_.type==="computed"?v.push(f(_.path,y+1)):v.push({name:_.path,type:_.type,children:[],depth:y+1});return i.pop(),a.delete(m),{name:m,type:"computed",children:v,depth:y}},g=f(e,0);return{root:e,tree:g,hasCycle:l,cyclePath:c,maxDepth:d}}getComputedDependencyTrackingInfo(){const e=Array.from(this.computedProperties.values()),n=this.getComputedDependencyStats(),a=[];for(const i of e){const l=this.analyzeComputedDependencyChain(i.name,i.componentId);a.push(l)}return{properties:e,recalcLogs:[...this.computedRecalcLogs],dependencyChains:a,stats:n,timestamp:Date.now()}}getComputedDependencyStats(){const e={"state-change":0,"datasource-update":0,"dependency-change":0,manual:0,initial:0},n={},a={};let i=0,l=0;for(const g of this.computedRecalcLogs)e[g.trigger]++,g.valueChanged||l++,n[g.computedName]=(n[g.computedName]||0)+1,a[g.computedName]||(a[g.computedName]=[]),a[g.computedName].push(g.computationTime),i+=g.computationTime;const c=Object.entries(n).sort((g,m)=>m[1]-g[1]).slice(0,5).map(([g,m])=>({name:g,count:m})),d=Object.entries(a).map(([g,m])=>({name:g,avgTime:m.reduce((y,S)=>y+S,0)/m.length})).sort((g,m)=>m.avgTime-g.avgTime).slice(0,5);let f=0;for(const g of this.computedProperties.values())this.analyzeComputedDependencyChain(g.name,g.componentId).hasCycle&&f++;return{totalComputed:this.computedProperties.size,totalRecalculations:this.computedRecalcLogs.length,unnecessaryRecalculations:l,avgComputationTime:this.computedRecalcLogs.length>0?i/this.computedRecalcLogs.length:0,topRecalculated:c,slowestComputed:d,cycleDetectionCount:f,byTrigger:e}}clearComputedTracking(){this.computedProperties.clear(),this.computedRecalcLogs=[],this.computedRecalcIdCounter=0}trackModalOpen(e){if(!this.config.enabled)return;const n={modalId:e.modalId,modalName:e.modalName,openedAt:Date.now(),closedAt:null,scopeType:e.scopeType||"isolated",parentModalId:e.parentModalId,componentId:e.componentId,initialState:e.initialState?{...e.initialState}:{},currentState:e.initialState?{...e.initialState}:{},stateChangeCount:0,isolatedStateKeys:e.isolatedStateKeys||[],sharedStateKeys:e.sharedStateKeys||[]};this.modalStates.set(e.modalId,n),this.validateModalDefinitionExists(e.modalId,e.modalName),e.parentModalId&&(this.modalStateRelations.find(i=>i.parentModalId===e.parentModalId&&i.childModalId===e.modalId)||this.modalStateRelations.push({parentModalId:e.parentModalId,childModalId:e.modalId,sharedKeys:e.sharedStateKeys||[],isolatedKeys:e.isolatedStateKeys||[],relationType:"parent-child"}))}trackModalClose(e,n){if(!this.config.enabled)return;const a=this.modalStates.get(e);a&&(a.closedAt=Date.now(),n&&(a.currentState={...n}),this.detectStateLeakage(a))}validateModalDefinitionExists(e,n){const a=this.currentLayout;if(!a?.layoutJson)return;const i=a.layoutJson.modals;if(!i){this.recordModalStateIssue({type:"missing-definition",modalId:e,modalName:n,severity:"error",description:`모달 "${e}"이(가) modalStack에 추가되었으나, 현재 레이아웃에 modals 섹션이 없습니다. 레이아웃 경로: ${a.layoutPath}`,affectedStateKeys:[],expectedValue:`modals 섹션에 id="${e}" 정의 존재`,actualValue:"modals 섹션 없음"});return}const l=Array.isArray(i)?i:Object.values(i);l.some(d=>d?.id===e||d?.props?.id===e)||this.recordModalStateIssue({type:"missing-definition",modalId:e,modalName:n,severity:"error",description:`모달 "${e}"이(가) modalStack에 추가되었으나, 렌더링된 레이아웃의 modals 섹션에 해당 ID의 정의가 없습니다. partial 로딩 실패, 레이아웃 병합 문제, extends 상속 누락 등을 확인하세요. 레이아웃 경로: ${a.layoutPath}`,affectedStateKeys:[],expectedValue:`modals 배열에 id="${e}" 항목 존재`,actualValue:`modals 배열에 ${l.length}개 모달 정의 (${l.map(d=>d?.id||"unknown").join(", ")})`})}trackModalStateChange(e){if(!this.config.enabled)return;const n=this.modalStates.get(e.modalId);if(!n)return;n.stateChangeCount++,n.currentState[e.stateKey]=e.newValue;const a=this.checkIsolationViolation(n,e.stateKey),i={id:`modal-change-${++this.modalStateChangeLogIdCounter}`,modalId:e.modalId,modalName:n.modalName,timestamp:Date.now(),stateKey:e.stateKey,previousValue:e.previousValue,newValue:e.newValue,changeSource:e.changeSource||"user-action",violatesIsolation:a};this.modalStateChangeLogs.push(i),this.modalStateChangeLogs.length>this.maxModalStateChangeLogs&&this.modalStateChangeLogs.shift(),a&&this.recordModalStateIssue({type:"isolation-violation",modalId:e.modalId,modalName:n.modalName,severity:"warning",description:`모달 '${n.modalName}'에서 격리된 상태 키 '${e.stateKey}'가 변경됨`,affectedStateKeys:[e.stateKey]})}detectStateLeakage(e){if(e.scopeType==="isolated")for(const n of e.isolatedStateKeys){const a=e.initialState[n],i=e.currentState[n];if(a!==i&&e.parentModalId){const l=this.modalStates.get(e.parentModalId);l&&l.currentState.hasOwnProperty(n)&&this.recordModalStateIssue({type:"state-leakage",modalId:e.modalId,modalName:e.modalName,severity:"error",description:`모달 닫힘 후 상태 '${n}'가 부모 모달로 유출될 수 있음`,affectedStateKeys:[n],leakedValue:i,expectedValue:a})}}}checkIsolationViolation(e,n){return e.scopeType==="isolated",!1}recordModalStateIssue(e){const n={id:`modal-issue-${++this.modalStateIssueIdCounter}`,type:e.type,modalId:e.modalId,modalName:e.modalName,timestamp:Date.now(),severity:e.severity,description:e.description,affectedStateKeys:e.affectedStateKeys,leakedValue:e.leakedValue,expectedValue:e.expectedValue,actualValue:e.actualValue,stackInfo:e.stackInfo};this.modalStateIssues.push(n),this.modalStateIssues.length>this.maxModalStateIssues&&this.modalStateIssues.shift()}getModalStateScopeTrackingInfo(){const e=Array.from(this.modalStates.values()),n=this.getModalStateScopeStats();return{modals:e,issues:[...this.modalStateIssues],relations:[...this.modalStateRelations],changeLogs:[...this.modalStateChangeLogs],stats:n,timestamp:Date.now()}}getModalStateScopeStats(){const e=Array.from(this.modalStates.values()),n={warning:0,error:0},a={"state-leakage":0,"isolation-violation":0,"parent-mutation":0,"orphaned-state":0,"scope-mismatch":0,"cleanup-failure":0,"missing-definition":0};for(const l of this.modalStateIssues)n[l.severity]++,a[l.type]++;const i={isolated:0,shared:0,inherited:0};for(const l of e)i[l.scopeType]++;return{totalModals:e.length,openModals:e.filter(l=>l.closedAt===null).length,nestedModals:e.filter(l=>l.parentModalId!==void 0).length,totalIssues:this.modalStateIssues.length,issuesBySeverity:n,issuesByType:a,byScope:i,leakageDetectionCount:a["state-leakage"],cleanupFailureCount:a["cleanup-failure"]}}clearModalStateScopeTracking(){this.modalStates.clear(),this.modalStateIssues=[],this.modalStateRelations=[],this.modalStateChangeLogs=[],this.modalStateIssueIdCounter=0,this.modalStateChangeLogIdCounter=0}setNamedActionDefinitions(e){this.config.enabled&&(this.namedActionDefinitions=e||{})}trackNamedActionRef(e){if(!this.config.enabled)return;const n={...e,id:`named_action_ref_${++this.namedActionRefIdCounter}`};this.namedActionRefLogs.push(n),this.namedActionRefLogs.length>this.maxNamedActionRefLogs&&(this.namedActionRefLogs=this.namedActionRefLogs.slice(-this.maxNamedActionRefLogs))}getNamedActionTrackingInfo(){const e={};for(const a of this.namedActionRefLogs)e[a.actionRefName]=(e[a.actionRefName]||0)+1;const n=Object.keys(this.namedActionDefinitions).filter(a=>!e[a]);return{definitions:this.namedActionDefinitions,refLogs:[...this.namedActionRefLogs],stats:{totalDefinitions:Object.keys(this.namedActionDefinitions).length,totalRefs:this.namedActionRefLogs.length,refCountByName:e,unusedDefinitions:n},timestamp:Date.now()}}clearNamedActionTracking(){this.namedActionDefinitions={},this.namedActionRefLogs=[],this.namedActionRefIdCounter=0}trackExpressionEval(e){if(!this.config.enabled)return;const n={...e,id:`expr-${++this.expressionIdCounter}`,timestamp:Date.now(),warning:this.detectExpressionWarning(e)};this.expressionHistory.push(n),this.expressionHistory.length>500&&this.expressionHistory.shift(),this.isProfiling&&this.profilingData.push({type:"binding",expression:e.expression,timestamp:performance.now(),duration:e.duration}),this.notifyExpressionWatchers(n)}detectExpressionWarning(e){const{expression:n,result:a,resultType:i,method:l,duration:c}=e;if(i==="undefined"&&!n.includes("??")&&!n.includes("||")&&/^[a-zA-Z_$][a-zA-Z0-9_$.]*$/.test(n.replace(/{{|}}/g,"").trim()))return{type:"undefined-result",message:"표현식 결과가 undefined입니다",suggestion:`데이터가 로드되었는지 확인하거나 fallback 값을 추가하세요: ${n} ?? ''`};if(i==="null")return{type:"null-result",message:"표현식 결과가 null입니다",suggestion:`nullish coalescing 연산자를 사용하세요: ${n} ?? ''`};if(l==="resolveBindings"&&i==="string"){const d=String(a);if(d.includes("[object Object]")||/^\[.*\]$/.test(d))return{type:"array-to-string",message:"배열/객체가 문자열로 변환되었습니다",suggestion:"evaluateExpression()을 사용하여 원본 타입을 유지하세요"}}if(l==="resolveBindings"&&i==="string"&&String(a).includes("[object Object]"))return{type:"object-to-string",message:"객체가 [object Object]로 변환되었습니다",suggestion:"evaluateExpression()을 사용하거나 특정 속성에 접근하세요"};if(i==="undefined"&&n.includes(".")&&!n.includes("?.")){const d=n.replace(/{{|}}/g,"").trim().split(".");if(d.length>=2)return{type:"missing-optional-chain",message:"Optional chaining(?.) 누락 가능성",suggestion:`안전한 접근을 위해 ?. 사용을 고려하세요: ${d.join("?.")}`}}if((n.includes("{{item.")||n.includes("{{index}}"))&&Array.from(this.iterations.values()).some(g=>g.itemVar!=="item"))return{type:"wrong-iteration-var",message:"'item'/'index' 대신 iteration에서 정의한 변수명을 사용해야 합니다",suggestion:"iteration의 item_var/index_var에 정의된 변수명을 확인하세요"};if(c&&c>10)return{type:"slow-evaluation",message:`표현식 평가에 ${c.toFixed(2)}ms 소요됨`,suggestion:"복잡한 표현식을 단순화하거나 computed 값으로 분리하세요"}}getExpressions(){return[...this.expressionHistory]}getExpressionWarnings(){return this.expressionHistory.filter(e=>e.warning!=null)}searchExpressions(e){const n=e.toLowerCase();return this.expressionHistory.filter(a=>a.expression.toLowerCase().includes(n)||a.componentName?.toLowerCase().includes(n)||a.propName?.toLowerCase().includes(n))}getExpressionStats(){const e=this.expressionHistory,n=new Set(e.map(f=>f.expression)),a=e.filter(f=>f.warning!=null),i=e.filter(f=>f.fromCache),l=e.filter(f=>f.duration!=null).map(f=>f.duration),c={};for(const f of e)c[f.resultType]=(c[f.resultType]||0)+1;const d={};for(const f of a)f.warning&&(d[f.warning.type]=(d[f.warning.type]||0)+1);return{totalEvaluations:e.length,uniqueExpressions:n.size,warningCount:a.length,cacheHitRate:e.length>0?i.length/e.length:0,averageDuration:l.length>0?l.reduce((f,g)=>f+g,0)/l.length:0,byType:c,byWarning:d}}clearExpressions(){this.expressionHistory=[],this.expressionIdCounter=0}watchExpressions(e){return this.expressionWatchers.add(e),()=>{this.expressionWatchers.delete(e)}}notifyExpressionWatchers(e){this.expressionWatchers.forEach(n=>{try{n(e)}catch(a){console.error("[G7DevTools] Expression watcher error:",a)}})}setLogLevel(e){this.config.logLevel=e}setMaxHistory(e){this.config.maxHistorySize=Math.max(10,Math.min(1e3,e))}setServerEndpoint(e){this.config.serverEndpoint=e}getConfig(){return{...this.config}}generateId(){return`${Date.now()}-${Math.random().toString(36).substr(2,9)}`}sanitizeObject(e,n=new WeakSet){if(e===null||typeof e!="object")return e;if(n.has(e))return"[Circular]";if(n.add(e),Array.isArray(e))return e.map(i=>this.sanitizeObject(i,n));const a={};for(const i of Object.keys(e))try{a[i]=this.sanitizeObject(e[i],n)}catch{a[i]="[Unable to serialize]"}return a}getNestedValue(e,n){if(!n)return e;const a=n.split(".");let i=e;for(const l of a){if(i==null)return;i=i[l]}return i}getLifecycleInfo(){return{mountedComponents:Array.from(this.mountedComponents.values()),orphanedListeners:this.getOrphanedListeners()}}getPerformanceInfo(){return{renderCounts:this.renderCounts,bindingEvalCount:this.bindingEvalCount,memoryWarnings:this.getMemoryWarnings()}}getNetworkInfo(){return{activeRequests:Array.from(this.activeRequests.values()),requestHistory:[...this.requestHistory],pendingDataSources:Array.from(this.pendingDataSources)}}getConditionalInfo(){return{ifConditions:Array.from(this.ifConditions.values()),iterations:Array.from(this.iterations.values())}}getFormInfo(){return Array.from(this.forms.values())}getWebSocketInfo(){return{connections:Array.from(this.wsConnections.values()),messageHistory:[...this.wsMessageHistory],connectionState:this.getWebSocketConnectionState()}}getStateAtTime(e){const n=[...this.stateHistory].reverse().find(a=>a.timestamp<=e);if(n)return{_global:n.next,_local:{}}}reset(){this.stateHistory=[],this.actionHistory=[],this.cacheStats={hits:0,misses:0,entries:0},this.cacheDecisions=[],this.cacheDecisionIdCounter=0,this.currentRenderCycleId=null,this.isInActionExecution=!1,this.isInIteration=!1,this.mountedComponents.clear(),this.eventListeners.clear(),this.renderCounts.clear(),this.bindingEvalCount=0,this.profilingData=[],this.activeRequests.clear(),this.requestHistory=[],this.pendingDataSources.clear(),this.wsConnections.clear(),this.wsMessageHistory=[],this.ifConditions.clear(),this.iterations.clear(),this.forms.clear(),this.expressionHistory=[],this.expressionIdCounter=0,this.dataSources.clear(),this.dataPathTransforms=[],this.nestedContexts=[],this.nestedContextIdCounter=0,this.formBindingIssues=[],this.formBindingValidations=[],this.formBindingIssueIdCounter=0,this.computedProperties.clear(),this.computedRecalcLogs=[],this.computedRecalcIdCounter=0,this.modalStates.clear(),this.modalStateIssues=[],this.modalStateRelations=[],this.modalStateChangeLogs=[],this.modalStateIssueIdCounter=0,this.modalStateChangeLogIdCounter=0,this.logHistory=[],this.logIdCounter=0}getExpressionInfo(){return{expressions:this.getExpressions(),stats:this.getExpressionStats()}}trackLog(e,n,a){if(!this.config.enabled)return;const i=a.map(c=>{if(typeof c=="string")return c;try{return JSON.stringify(c)}catch{return String(c)}}).join(" "),l={id:`log-${++this.logIdCounter}`,level:e,prefix:n,message:i,args:this.sanitizeObject(a),timestamp:Date.now(),stack:e==="error"?new Error().stack:void 0};this.logHistory.push(l),this.logHistory.length>this.maxLogHistory&&this.logHistory.shift()}trackAction(e){if(!this.config.enabled)return;const n={id:`debounce-action-${++this.debounceActionIdCounter}`,...e,timestamp:Date.now()};this.debounceActionHistory.push(n),this.debounceActionHistory.length>100&&this.debounceActionHistory.shift()}trackDataSourceUpdate(e){if(!this.config.enabled)return;const n={id:`ds-update-${++this.dataSourceUpdateIdCounter}`,...e};this.dataSourceUpdateHistory.push(n),this.dataSourceUpdateHistory.length>100&&this.dataSourceUpdateHistory.shift()}getDebounceActionHistory(e){let n=[...this.debounceActionHistory];return e?.handler&&(n=n.filter(a=>a.handler.includes(e.handler))),e?.status&&(n=n.filter(a=>a.status===e.status)),e?.limit&&(n=n.slice(-e.limit)),n}getDataSourceUpdateHistory(e){let n=[...this.dataSourceUpdateHistory];return e?.dataSourceId&&(n=n.filter(a=>a.dataSourceId===e.dataSourceId)),e?.updateType&&(n=n.filter(a=>a.updateType===e.updateType)),e?.limit&&(n=n.slice(-e.limit)),n}getLogs(e){let n=[...this.logHistory];if(e?.level){const a=Array.isArray(e.level)?e.level:[e.level];n=n.filter(i=>a.includes(i.level))}if(e?.prefix){const a=e.prefix.toLowerCase();n=n.filter(i=>i.prefix.toLowerCase().includes(a))}if(e?.search){const a=e.search.toLowerCase();n=n.filter(i=>i.message.toLowerCase().includes(a))}return e?.since&&(n=n.filter(a=>a.timestamp>=e.since)),e?.limit&&(n=n.slice(-e.limit)),n}getLogStats(){const n=Date.now()-6e4,a={log:0,warn:0,error:0,debug:0,info:0},i={};let l=0,c=0;for(const d of this.logHistory)a[d.level]++,i[d.prefix]=(i[d.prefix]||0)+1,d.timestamp>=n&&(d.level==="error"&&l++,d.level==="warn"&&c++);return{totalLogs:this.logHistory.length,byLevel:a,byPrefix:i,recentErrors:l,recentWarnings:c}}clearLogs(){this.logHistory=[],this.logIdCounter=0}getLogInfo(){return{entries:this.getLogs(),stats:this.getLogStats()}}trackDataSourceDefinition(e){this.config.enabled&&this.dataSources.set(e.id,{...e,status:"idle"})}trackDataSourceLoading(e){if(!this.config.enabled)return;const n=this.dataSources.get(e);n&&this.dataSources.set(e,{...n,status:"loading"})}trackDataSourceLoaded(e,n,a){if(!this.config.enabled)return;const i=this.dataSources.get(e);if(!i)return;let l,c,d=a;n&&(Array.isArray(n)?(l=n.length,c=n.length>0&&typeof n[0]=="object"?Object.keys(n[0]):void 0):typeof n=="object"&&(Array.isArray(n.data)?(d=d||"data",l=n.data.length,c=n.data.length>0&&typeof n.data[0]=="object"?Object.keys(n.data[0]):void 0):Array.isArray(n.items)?(d=d||"items",l=n.items.length,c=n.items.length>0&&typeof n.items[0]=="object"?Object.keys(n.items[0]):void 0):c=Object.keys(n))),this.dataSources.set(e,{...i,status:"loaded",dataPath:d,itemCount:l,keys:c,lastLoadedAt:Date.now(),error:void 0})}trackDataSourceError(e,n){if(!this.config.enabled)return;const a=this.dataSources.get(e);a&&this.dataSources.set(e,{...a,status:"error",error:n,lastLoadedAt:Date.now()})}untrackDataSource(e){this.dataSources.delete(e)}getDataSources(){return Array.from(this.dataSources.values())}getDataSource(e){return this.dataSources.get(e)}clearDataSources(){this.dataSources.clear()}trackDataPathTransform(e){if(!this.config.enabled)return;let n=this.dataPathTransforms.find(a=>a.dataSourceId===e.dataSourceId);n||(n={dataSourceId:e.dataSourceId,timestamp:Date.now(),transformSteps:[],warnings:[]},this.dataPathTransforms.push(n),this.dataPathTransforms.length>this.maxDataPathTransforms&&this.dataPathTransforms.shift()),n.transformSteps.push({step:e.step,inputPath:e.inputPath,inputValue:this.safeClone(e.inputValue),outputPath:e.outputPath,outputValue:this.safeClone(e.outputValue),config:e.config}),e.warning&&n.warnings.push(e.warning)}setDataPathFinalBinding(e,n){if(!this.config.enabled)return;const a=this.dataPathTransforms.find(i=>i.dataSourceId===e);a&&(a.finalBinding={expression:n.expression,resolvedPath:n.resolvedPath,value:this.safeClone(n.value)})}getDataPathTransformTrackingInfo(){const e=this.getDataPathTransformStats();return{transforms:[...this.dataPathTransforms],stats:e,timestamp:Date.now()}}getDataPathTransformStats(){const e=this.dataPathTransforms,n={};let a=0;const i={};for(const c of e){n[c.dataSourceId]=(n[c.dataSourceId]||0)+1,a+=c.warnings.length;for(const d of c.warnings)i[d]=(i[d]||0)+1}const l=Object.entries(i).map(([c,d])=>({warning:c,count:d})).sort((c,d)=>d.count-c.count).slice(0,10);return{totalTransforms:e.length,byDataSource:n,warningsCount:a,commonWarnings:l}}getDataPathTransformForDataSource(e){return this.dataPathTransforms.find(n=>n.dataSourceId===e)}clearDataPathTransforms(){this.dataPathTransforms=[]}trackNestedContext(e){if(!this.config.enabled)return"";const n=`nested-${++this.nestedContextIdCounter}`,a=[...new Set([...e.parentContext.available,...e.ownContext.added])],i={...e.parentContext.values,...e.ownContext.values},l={id:n,timestamp:Date.now(),componentId:e.componentId,componentType:e.componentType,parentContext:{available:e.parentContext.available,values:this.safeClone(e.parentContext.values)},ownContext:{added:e.ownContext.added,values:this.safeClone(e.ownContext.values)},mergedContext:{all:a,values:this.safeClone(i)},accessAttempts:[],depth:e.depth,parentId:e.parentId};return this.nestedContexts.push(l),this.nestedContexts.length>this.maxNestedContexts&&this.nestedContexts.shift(),n}trackNestedContextAccess(e,n){if(!this.config.enabled)return;const a=this.nestedContexts.find(i=>i.id===e);a&&a.accessAttempts.push({path:n.path,found:n.found,value:n.found?this.safeClone(n.value):void 0,error:n.error})}getNestedContextTrackingInfo(){const e=this.getNestedContextStats();return{contexts:[...this.nestedContexts],stats:e,timestamp:Date.now()}}getNestedContextStats(){const e=this.nestedContexts,n={expandChildren:0,cellChildren:0,iteration:0,modal:0,slot:0};let a=0,i=0;const l={};for(const d of e){n[d.componentType]++,d.depth>a&&(a=d.depth);for(const f of d.accessAttempts)f.found||(i++,l[f.path]=(l[f.path]||0)+1)}const c=Object.entries(l).map(([d,f])=>({path:d,count:f})).sort((d,f)=>f.count-d.count).slice(0,10);return{totalContexts:e.length,byType:n,maxDepth:a,failedAccessCount:i,commonFailedPaths:c}}getNestedContextForComponent(e){return this.nestedContexts.find(n=>n.componentId===e)}clearNestedContexts(){this.nestedContexts=[],this.nestedContextIdCounter=0}trackHandlerRegistration(e,n="custom",a,i){this.isEnabled&&this.handlers.set(e,{name:e,category:n,description:a,registeredAt:Date.now(),source:i})}trackHandlerUnregistration(e){this.isEnabled&&this.handlers.delete(e)}getHandlers(){return Array.from(this.handlers.values())}getHandler(e){return this.handlers.get(e)}getHandlersByCategory(e){return Array.from(this.handlers.values()).filter(n=>n.category===e)}clearHandlers(){this.handlers.clear()}trackEventSubscribe(e){if(!this.isEnabled)return;const n=this.componentEventSubscriptions.get(e),a=Date.now();n?(n.subscriberCount++,n.lastSubscribedAt=a):this.componentEventSubscriptions.set(e,{eventName:e,subscriberCount:1,firstSubscribedAt:a,lastSubscribedAt:a})}trackEventUnsubscribe(e){if(!this.isEnabled)return;const n=this.componentEventSubscriptions.get(e);n&&(n.subscriberCount--,n.subscriberCount<=0&&this.componentEventSubscriptions.delete(e))}trackEventEmit(e,n,a,i,l){if(!this.isEnabled)return;const c={id:`evt_${++this.componentEventIdCounter}`,eventName:e,data:this.sanitizeForLogging(n),timestamp:Date.now(),listenerCount:a,results:i?this.sanitizeForLogging(i):void 0,hasError:!!l,errorMessage:l?.message};this.componentEventEmitHistory.push(c),this.componentEventEmitHistory.length>this.maxComponentEventHistory&&(this.componentEventEmitHistory=this.componentEventEmitHistory.slice(-this.maxComponentEventHistory))}trackEventOff(e){this.isEnabled&&this.componentEventSubscriptions.delete(e)}trackEventClear(){this.isEnabled&&this.componentEventSubscriptions.clear()}getComponentEventInfo(){const e=Array.from(this.componentEventSubscriptions.values());return{subscriptions:e,emitHistory:[...this.componentEventEmitHistory],totalSubscribers:e.reduce((n,a)=>n+a.subscriberCount,0),totalEmits:this.componentEventEmitHistory.length}}getEventEmitHistory(){return[...this.componentEventEmitHistory]}getEventSubscriptions(){return Array.from(this.componentEventSubscriptions.values())}clearComponentEvents(){this.componentEventSubscriptions.clear(),this.componentEventEmitHistory=[],this.componentEventIdCounter=0}sanitizeForLogging(e){if(e==null||typeof e!="object")return e;try{return JSON.parse(JSON.stringify(e))}catch{return"[Circular or Non-serializable]"}}startStateChange(e,n,a,i){if(!this.config.enabled)return"";const l=`setState_${++this.stateRenderingIdCounter}`;this.currentStateChangeContext={setStateId:l,startTime:performance.now(),changedPath:e,trigger:i||{},renderedComponents:[]};const c={id:`sr_${this.stateRenderingIdCounter}`,setStateId:l,statePath:e,oldValue:this.sanitizeForLogging(n),newValue:this.sanitizeForLogging(a),timestamp:Date.now(),triggeredBy:i||{},renderedComponents:[],totalRenderDuration:0,affectedBindingsCount:0};return this.stateRenderingLogs.push(c),this.stateRenderingLogs.length>this.maxStateRenderingLogs&&this.stateRenderingLogs.shift(),l}trackComponentRender(e,n,a,i=[],l=[],c){if(!this.config.enabled)return;const d=this.componentRenderCounts.get(n)||0;this.componentRenderCounts.set(n,d+1);for(const f of i)this.stateToComponentMap.has(f)||this.stateToComponentMap.set(f,new Set),this.stateToComponentMap.get(f).add(n);if(this.currentStateChangeContext){const f={componentId:e,componentName:n,renderDuration:a,accessedStatePaths:i,evaluatedBindings:l,renderOrder:this.currentStateChangeContext.renderedComponents.length,parentId:c};this.currentStateChangeContext.renderedComponents.push(f)}this.trackRender(n)}completeStateChange(e){if(!this.config.enabled||!this.currentStateChangeContext||this.currentStateChangeContext.setStateId!==e)return;const n=this.currentStateChangeContext,i=performance.now()-n.startTime,l=this.stateRenderingLogs.findIndex(c=>c.setStateId===e);if(l>=0){const c=this.stateRenderingLogs[l];c.renderedComponents=n.renderedComponents,c.totalRenderDuration=i,c.affectedBindingsCount=n.renderedComponents.reduce((d,f)=>d+f.evaluatedBindings.length,0)}this.currentStateChangeContext=null}getCurrentStateChangeContext(){return this.currentStateChangeContext}getStateRenderingLogs(){return[...this.stateRenderingLogs]}getStateRenderingInfo(){const e=this.stateRenderingLogs,n=Object.fromEntries(this.componentRenderCounts),a={};for(const[i,l]of this.stateToComponentMap)a[i]=Array.from(l);return{logs:e,componentRenderCounts:n,stateToComponentMap:a,stats:this.calculateStateRenderingStats()}}calculateStateRenderingStats(){const e=this.stateRenderingLogs;if(e.length===0)return{totalStateChanges:0,totalRenders:0,avgRenderDuration:0,avgComponentsPerChange:0,topRenderedComponents:[],topInfluentialPaths:[]};const n=e.reduce((d,f)=>d+f.renderedComponents.length,0),a=e.reduce((d,f)=>d+f.totalRenderDuration,0)/e.length,i=n/e.length,l=Array.from(this.componentRenderCounts.entries()).sort((d,f)=>f[1]-d[1]).slice(0,5).map(([d,f])=>({name:d,count:f})),c=Array.from(this.stateToComponentMap.entries()).sort((d,f)=>f[1].size-d[1].size).slice(0,5).map(([d,f])=>({path:d,affectedComponents:f.size}));return{totalStateChanges:e.length,totalRenders:n,avgRenderDuration:a,avgComponentsPerChange:i,topRenderedComponents:l,topInfluentialPaths:c}}getComponentsAffectedByState(e){const n=this.stateToComponentMap.get(e);return n?Array.from(n):[]}getComponentRenderHistory(e){return this.stateRenderingLogs.filter(n=>n.renderedComponents.some(a=>a.componentName===e))}clearStateRenderingLogs(){this.stateRenderingLogs=[],this.componentRenderCounts.clear(),this.stateToComponentMap.clear(),this.currentStateChangeContext=null,this.stateRenderingIdCounter=0}trackComponentStateSource(e,n,a,i){this.config.enabled&&this.componentStateSources.set(e,{componentId:e,componentName:n,stateSource:a,stateProvider:i})}trackDynamicState(e,n){this.config.enabled&&this.dynamicStates.set(e,n)}trackContextFlow(e,n,a,i,l,c){if(!this.config.enabled)return;const d={component:n,componentId:e,contextReceived:a,passedToChildren:i,usedInRender:l};if(this.contextFlowNodes.set(e,d),c){const f=this.contextFlowNodes.get(c);f&&(f.children||(f.children=[]),f.children.push(d))}}getStateHierarchyInfo(e=!0){const n=this.buildStateHierarchyLayers(),a=this.detectStateConflicts(n),i=Array.from(this.componentStateSources.values());return{layers:e?n.map(c=>({...c,values:this.summarizeValues(c.values,2)})):n,conflicts:a,componentStateSources:i,timestamp:Date.now()}}summarizeValues(e,n,a=0){if(e==null)return e;if(typeof e!="object")return typeof e=="string"&&e.length>100?e.substring(0,100)+`... (${e.length}자)`:e;if(a>=n){if(Array.isArray(e))return`[Array(${e.length})]`;const c=Object.keys(e);return`{${c.slice(0,5).join(", ")}${c.length>5?`, ... +${c.length-5}`:""}}`}if(Array.isArray(e))return e.length<=3?e.map(c=>this.summarizeValues(c,n,a+1)):{__type:"array",__length:e.length,__preview:e.slice(0,3).map(c=>this.summarizeValues(c,n,a+1))};const i={},l=Object.keys(e);for(const c of l)i[c]=this.summarizeValues(e[c],n,a+1);return i}buildStateHierarchyLayers(){const e=[];try{const a=window.G7Core?.state?.get?.();if(!a)return e;a._local&&e.push({name:"Global _local",type:"global",path:"_local",values:a._local,priority:1}),a._global&&e.push({name:"Global _global",type:"global",path:"_global",values:a._global,priority:1});for(const[l,c]of this.dynamicStates)c&&Object.keys(c).length>0&&e.push({name:`DynamicState (${l})`,type:"dynamicState",componentId:l,values:c,priority:2});const i=this.computeEffectiveState(a._local||{});e.push({name:"Effective _local (merged)",type:"effective",values:i,priority:3})}catch(n){console.warn("[G7DevTools] 상태 계층 빌드 실패:",n)}return e}computeEffectiveState(e){const n={...e};for(const[,a]of this.dynamicStates)a._local&&Object.assign(n,a._local);return n}detectStateConflicts(e){const n=[],a=e.find(l=>l.type==="global"&&l.path==="_local");if(!a)return n;const i=e.filter(l=>l.type==="dynamicState");for(const l of i){const c=l.values._local||l.values;for(const[d,f]of Object.entries(c)){const g=a.values[d];if(JSON.stringify(g)!==JSON.stringify(f)){const m=[],y=[];for(const S of this.componentStateSources.values())S.stateSource.local.some(_=>_===`_local.${d}`||_===d)&&(S.stateProvider.type==="dynamicState"?m.push(S.componentName):S.stateProvider.type==="globalState"&&y.push(S.componentName));n.push({path:`_local.${d}`,globalValue:g,dynamicStateValue:f,effectiveValue:f,usedBy:m,notUsedBy:y,severity:y.length>0?"warning":"info",description:y.length>0?`${y.join(", ")}은(는) globalState._local을 읽어 dynamicState 값을 사용하지 못함`:"dynamicState 값이 globalState와 다르지만 모든 컴포넌트가 dynamicState를 사용"})}}}return n}getContextFlowInfo(){const e=[],n=new Set(this.contextFlowNodes.keys());for(const a of this.contextFlowNodes.values())if(a.children)for(const i of a.children)n.delete(i.componentId);for(const a of n){const i=this.contextFlowNodes.get(a);i&&e.push(i)}return{rootComponent:e.length>0?e[0].component:"Unknown",contextFlow:e,timestamp:Date.now()}}clearStateHierarchyData(){this.componentStateSources.clear(),this.contextFlowNodes.clear(),this.dynamicStates.clear()}trackComponentStyle(e,n,a,i){if(!this.config.enabled)return;const l=this.analyzeTailwindClasses(a),c={componentId:e,componentName:n,classes:a,computedStyles:i,tailwindAnalysis:l};this.componentStyles.set(e,c),this.detectStyleIssues(c)}analyzeTailwindClasses(e){const n=[],a=[],i=[],l=[],c=["sm:","md:","lg:","xl:","2xl:"],d=[/bg-\w+-\d+/,/text-\w+-\d+/,/border-\w+-\d+/];for(const f of e){l.push(f),f.startsWith("dark:")&&n.push(f);for(const g of c)if(f.startsWith(g)){a.push(f);break}for(const g of d)if(g.test(f)){i.push(f);break}}return{usedClasses:l,darkClasses:n,responsiveClasses:a,dynamicClasses:i}}detectStyleIssues(e){const{componentId:n,componentName:a,computedStyles:i,tailwindAnalysis:l}=e;(i.opacity==="0"||i.visibility==="hidden"||i.display==="none"||i.width==="0px"&&i.height==="0px")&&this.addStyleIssue({id:this.generateId(),type:"invisible-element",componentId:n,componentName:a,property:i.opacity==="0"?"opacity":i.visibility==="hidden"?"visibility":i.display==="none"?"display":"width/height",currentValue:i.opacity==="0"?"0":i.visibility==="hidden"?"hidden":i.display==="none"?"none":"0px",severity:"warning",description:"요소가 화면에 보이지 않습니다.",suggestion:"opacity, visibility, display, width/height 값을 확인하세요."});const c=l.usedClasses.filter(f=>f.startsWith("bg-")&&!f.startsWith("dark:")),d=l.darkClasses.filter(f=>f.includes("bg-"));c.length>0&&d.length===0&&this.addStyleIssue({id:this.generateId(),type:"dark-mode-missing",componentId:n,componentName:a,property:"background-color",currentValue:c.join(", "),expectedValue:"dark:bg-* 클래스 필요",severity:"info",description:"다크 모드 배경색 클래스가 누락되었습니다.",suggestion:`${c[0]}에 대응하는 dark: 클래스를 추가하세요.`}),l.dynamicClasses.length>0&&this.addStyleIssue({id:this.generateId(),type:"tailwind-purging",componentId:n,componentName:a,property:"class",currentValue:l.dynamicClasses.join(", "),severity:"info",description:"동적으로 생성된 Tailwind 클래스가 있습니다.",suggestion:"tailwind.config.js의 safelist에 추가하거나 전체 클래스명을 상수로 정의하세요."})}addStyleIssue(e){this.styleIssues.some(a=>a.componentId===e.componentId&&a.type===e.type&&a.property===e.property)||this.styleIssues.push(e)}getStyleValidationInfo(){const e=Array.from(this.componentStyles.values());return{issues:[...this.styleIssues],componentStyles:e,stats:{totalComponents:e.length,invisibleCount:this.styleIssues.filter(n=>n.type==="invisible-element").length,tailwindIssueCount:this.styleIssues.filter(n=>n.type==="tailwind-purging").length,darkModeIssueCount:this.styleIssues.filter(n=>n.type==="dark-mode-missing").length},timestamp:Date.now()}}clearStyleValidationData(){this.styleIssues=[],this.componentStyles.clear()}trackAuthEvent(e,n,a,i){if(!this.config.enabled)return;const l={id:`auth_${++this.authEventIdCounter}`,type:e,timestamp:Date.now(),success:n,error:a,details:i};this.authEvents.push(l),this.authEvents.length>this.maxAuthEventHistory&&(this.authEvents=this.authEvents.slice(-this.maxAuthEventHistory))}trackAuthHeader(e,n,a,i,l){if(!this.config.enabled)return;const c={url:e,hasAuthHeader:n,headerType:a,tokenValid:i??!1,responseStatus:l,timestamp:Date.now()};this.authHeaderHistory.push(c),this.authHeaderHistory.length>this.maxAuthEventHistory&&(this.authHeaderHistory=this.authHeaderHistory.slice(-this.maxAuthEventHistory)),l===401&&this.trackAuthEvent("api-unauthorized",!1,`401 Unauthorized: ${e}`,{url:e})}getAuthState(){try{const e=window.G7Core,n=e?.api;let a=e?.auth;!a&&e?.AuthManager?.getInstance&&(a=e.AuthManager.getInstance());const i=n?.getToken?.()||a?.getAccessToken?.()||localStorage.getItem("auth_token")||localStorage.getItem("access_token"),l=a?.getRefreshToken?.()||localStorage.getItem("refresh_token");let c="unknown",d=a?.getUser?.();if(d&&(c="AuthManager"),!d){const v=e?.state?.get?.();d=v?._global?.user||v?._global?.currentUser||v?.user||v?.currentUser,d&&(c="G7Core.state")}const f=a?.isAuthenticated?.()??!1;let g="memory";localStorage.getItem("auth_token")||localStorage.getItem("access_token")?g="localStorage":(sessionStorage.getItem("auth_token")||sessionStorage.getItem("access_token"))&&(g="sessionStorage");const m=!!i&&(!!d||f);let y;i&&typeof i=="string"&&i.length>20?y=`${i.substring(0,10)}...${i.substring(i.length-6)}`:i&&(y="***");const S=d?{id:d.id,email:d.email,name:d.name,roles:d.roles?.map(v=>typeof v=="string"?{name:v}:{id:v.id,name:v.name,guard_name:v.guard_name}),permissions:d.permissions?.map(v=>typeof v=="string"?{name:v}:{id:v.id,name:v.name,guard_name:v.guard_name}),avatar:d.avatar||d.profile_photo_url||d.profile_image,created_at:d.created_at,updated_at:d.updated_at,last_login_at:d.last_login_at,email_verified_at:d.email_verified_at,...d.phone&&{phone:d.phone},...d.nickname&&{nickname:d.nickname},...d.status&&{status:d.status},...d.locale&&{locale:d.locale}}:void 0;return{isAuthenticated:m,user:S,tokens:{hasAccessToken:!!i,hasRefreshToken:!!l,storage:g,accessTokenPreview:y},lastActivity:Date.now(),source:c}}catch{return{isAuthenticated:!1,tokens:{hasAccessToken:!1,hasRefreshToken:!1,storage:"memory"},source:"unknown"}}}getAuthDebugInfo(){const e=[...this.authEvents];return{state:this.getAuthState(),events:e,headerAnalysis:[...this.authHeaderHistory],stats:{loginAttempts:e.filter(n=>n.type==="login").length,successfulLogins:e.filter(n=>n.type==="login"&&n.success).length,failedLogins:e.filter(n=>n.type==="login"&&!n.success).length,tokenRefreshes:e.filter(n=>n.type==="token-refresh").length,unauthorizedResponses:e.filter(n=>n.type==="api-unauthorized").length},timestamp:Date.now()}}clearAuthData(){this.authEvents=[],this.authHeaderHistory=[],this.authEventIdCounter=0}trackLayoutLoad(e,n,a,i="api"){if(!this.config.enabled)return;const l=Date.now();this.currentLayout={layoutPath:e,templateId:n,layoutJson:this.sanitizeObject(a),loadedAt:l,version:a.version,layoutName:a.layout_name,source:i};const c={id:`layout-${++this.layoutIdCounter}`,layoutPath:e,templateId:n,loadedAt:l,source:i,version:a.version};this.layoutHistory.push(c),this.layoutHistory.length>this.maxLayoutHistory&&this.layoutHistory.shift(),this.layoutStats.totalLoads++,i==="cache"?this.layoutStats.cacheHits++:this.layoutStats.apiLoads++}getCurrentLayout(){return this.currentLayout}getLayoutHistory(){return[...this.layoutHistory]}getLayoutDebugInfo(){return{current:this.currentLayout,history:[...this.layoutHistory],stats:{...this.layoutStats}}}clearLayoutData(){this.currentLayout=null,this.layoutHistory=[],this.layoutIdCounter=0,this.layoutStats={totalLoads:0,cacheHits:0,apiLoads:0}}startHandlerExecution(e){if(!this.config.enabled)return"";const n=`exec_${++this.executionIdCounter}_${Date.now()}`,a={handlerName:e,executionId:n,startTime:Date.now(),stateChanges:[],dataSourceChanges:[],alerts:[]};if(this.executionDetails.set(n,a),this.executionDetails.size>this.maxExecutionDetails){const i=Array.from(this.executionDetails.keys());i.slice(0,i.length-this.maxExecutionDetails).forEach(c=>this.executionDetails.delete(c))}return n}endHandlerExecution(e,n,a,i){if(!this.config.enabled||!e)return;const l=this.executionDetails.get(e);if(!l)return;l.endTime=Date.now(),l.duration=l.endTime-l.startTime,l.exitReason=n,l.exitLocation=a,l.exitDescription=i;const c=this.changeExpectations.get(e);c&&(l.expectedChanges=c,this.checkExpectations(e,l,c),this.changeExpectations.delete(e)),n==="normal"&&l.stateChanges.length===0&&l.dataSourceChanges.length===0&&this.addChangeAlert(e,{type:"no-state-change",severity:"warning",message:`핸들러 "${l.handlerName}"이(가) 상태 변경 없이 완료됨`,description:"핸들러가 성공적으로 완료되었지만 상태나 데이터소스 변경이 없습니다. 의도한 동작인지 확인하세요.",handlerName:l.handlerName,suggestion:'핸들러 내부 조건문을 확인하거나 DevTools의 "변경감지" 탭에서 exitLocation을 확인하세요.',docLink:"troubleshooting-state.md"}),(n==="early-return-condition"||n==="early-return-validation")&&this.addChangeAlert(e,{type:"early-return-detected",severity:"info",message:`핸들러 "${l.handlerName}"이(가) early return으로 종료됨`,description:i||`종료 위치: ${a||"알 수 없음"}`,handlerName:l.handlerName,suggestion:n==="early-return-validation"?"검증 실패로 인한 early return입니다. 입력 데이터를 확인하세요.":"조건부 early return입니다. 조건문의 평가 결과를 확인하세요."})}recordExitReason(e,n,a,i){if(!this.config.enabled||!e)return;const l=this.executionDetails.get(e);l&&(l.exitReason=n,l.exitLocation=a,l.exitDescription=i)}recordStateChange(e,n,a,i,l){if(!this.config.enabled)return;const c={id:`sc_${++this.stateChangeIdCounter}`,path:n,changeType:a,oldValue:this.safeClone(i),newValue:this.safeClone(l),timestamp:Date.now(),comparison:this.compareValues(i,l),executionId:e};if(this.stateChangeHistory.push(c),e){const d=this.executionDetails.get(e);d&&d.stateChanges.push(c)}this.stateChangeHistory.length>this.maxStateChangeHistory&&this.stateChangeHistory.splice(0,this.stateChangeHistory.length-this.maxStateChangeHistory),c.comparison.isDeepEqual&&typeof i=="object"&&i!==null&&this.addChangeAlert(e||"",{type:"object-reference-same",severity:"warning",message:`상태 "${n}"가 변경되었으나 값이 동일함`,description:"객체 참조가 동일하거나 깊은 비교 결과 변경이 없습니다. 변경 감지가 실패할 수 있습니다.",handlerName:e&&this.executionDetails.get(e)?.handlerName||"unknown",statePath:n,suggestion:"불변성을 유지하여 새 객체를 생성하세요. 예: { ...oldObject, newField: value }",docLink:"troubleshooting-state.md#object-mutation"})}recordDataSourceChange(e,n,a,i,l){if(!this.config.enabled)return;const c={dataSourceId:n,changeType:a,timestamp:Date.now(),previousStatus:i,newStatus:l,executionId:e};if(this.dataSourceChangeHistory.push(c),e){const d=this.executionDetails.get(e);d&&d.dataSourceChanges.push(c)}this.dataSourceChangeHistory.length>this.maxStateChangeHistory&&this.dataSourceChangeHistory.splice(0,this.dataSourceChangeHistory.length-this.maxStateChangeHistory)}expectChange(e,n,a){if(!this.config.enabled||!e)return;const i=this.executionDetails.get(e);if(!i)return;const l={expectedStatePaths:n,expectedDataSources:a,setAt:Date.now(),source:i.handlerName};this.changeExpectations.set(e,l)}checkExpectations(e,n,a){const i=new Set(n.stateChanges.map(c=>c.path)),l=new Set(n.dataSourceChanges.map(c=>c.dataSourceId));for(const c of a.expectedStatePaths)i.has(c)||this.addChangeAlert(e,{type:"expected-not-fulfilled",severity:"error",message:`기대한 상태 변경 "${c}"가 발생하지 않음`,description:`핸들러 "${n.handlerName}"이(가) "${c}" 변경을 예고했지만 실제로 변경되지 않았습니다.`,handlerName:n.handlerName,statePath:c,suggestion:"핸들러 내부 로직을 확인하세요. early return이나 조건부 분기로 인해 변경이 생략되었을 수 있습니다.",docLink:"troubleshooting-state.md"});for(const c of a.expectedDataSources)l.has(c)||this.addChangeAlert(e,{type:"expected-not-fulfilled",severity:"error",message:`기대한 데이터소스 갱신 "${c}"가 발생하지 않음`,description:`핸들러 "${n.handlerName}"이(가) "${c}" 갱신을 예고했지만 실제로 갱신되지 않았습니다.`,handlerName:n.handlerName,dataSourceId:c,suggestion:"refetchDataSource 호출이 실행되었는지 확인하세요.",docLink:"data-sources.md"})}addChangeAlert(e,n){const a={...n,id:`alert_${++this.alertIdCounter}`,timestamp:Date.now()};if(this.changeAlerts.push(a),e){const i=this.executionDetails.get(e);i&&i.alerts.push(a)}this.changeAlerts.length>this.maxChangeAlerts&&this.changeAlerts.splice(0,this.changeAlerts.length-this.maxChangeAlerts)}compareValues(e,n){const a=this.getValueType(e),i=this.getValueType(n),l=a!==i;let c=!1,d;return!l&&(a==="object"||a==="array")?(c=this.deepEqual(e,n),!c&&a==="object"&&(d=this.getChangedKeys(e,n))):c=e===n,{typeChanged:l,oldType:a,newType:i,isDeepEqual:c,changedKeys:d}}getValueType(e){return e===null?"null":e===void 0?"undefined":Array.isArray(e)?"array":typeof e}deepEqual(e,n){if(e===n)return!0;if(typeof e!=typeof n)return!1;if(e===null||n===null||typeof e!="object")return e===n;if(Array.isArray(e)!==Array.isArray(n))return!1;const a=Object.keys(e),i=Object.keys(n);if(a.length!==i.length)return!1;for(const l of a)if(!i.includes(l)||!this.deepEqual(e[l],n[l]))return!1;return!0}getChangedKeys(e,n){const a=[],i=new Set([...Object.keys(e),...Object.keys(n)]);for(const l of i)this.deepEqual(e[l],n[l])||a.push(l);return a}getChangeDetectionInfo(){const e=Array.from(this.executionDetails.values()),n={totalExecutions:e.length,executionsWithStateChange:e.filter(a=>a.stateChanges.length>0).length,executionsWithoutStateChange:e.filter(a=>a.stateChanges.length===0&&a.exitReason==="normal").length,earlyReturnCount:e.filter(a=>a.exitReason==="early-return-condition"||a.exitReason==="early-return-validation").length,alertCount:this.changeAlerts.length,alertsByType:this.getAlertsByType()};return{executionDetails:e,stateChangeHistory:[...this.stateChangeHistory],dataSourceChangeHistory:[...this.dataSourceChangeHistory],alerts:[...this.changeAlerts],stats:n,timestamp:Date.now()}}getAlertsByType(){const e={"no-state-change":0,"no-datasource-change":0,"expected-not-fulfilled":0,"object-reference-same":0,"early-return-detected":0,"async-timing-issue":0};for(const n of this.changeAlerts)e[n.type]++;return e}clearChangeDetectionData(){this.executionDetails.clear(),this.stateChangeHistory=[],this.dataSourceChangeHistory=[],this.changeAlerts=[],this.changeExpectations.clear(),this.executionIdCounter=0,this.stateChangeIdCounter=0,this.alertIdCounter=0}getRecentAlerts(e=10,n){let a=[...this.changeAlerts];return n&&(a=a.filter(i=>i.severity===n)),a.slice(-e)}getHandlerExecutions(e,n=10){return Array.from(this.executionDetails.values()).filter(i=>i.handlerName===e).slice(-n)}findExecutionInStack(e){return this.sequenceExecutionStack.find(n=>n.sequenceId===e)}startSequenceExecution(e){if(!this.config.enabled)return"";const n=`seq_${++this.sequenceIdCounter}_${Date.now()}`,a={sequenceId:n,startTime:Date.now(),totalDuration:0,trigger:e,actions:[],status:"running"};return this.sequenceExecutionStack.push(a),n}captureSequenceActionBefore(e,n,a,i,l){if(!this.config.enabled)return;const c=this.findExecutionInStack(e);if(!c)return;const d={index:n,handler:a,params:this.safeClone(i),stateBeforeAction:{_global:this.safeClone(l._global),_local:this.safeClone(l._local),_isolated:l._isolated?this.safeClone(l._isolated):void 0},stateAfterAction:{_global:{},_local:{}},duration:0,status:"success"};c.actions.push(d)}captureSequenceActionAfter(e,n,a,i,l,c){if(!this.config.enabled)return;const d=this.findExecutionInStack(e);if(!d)return;const f=d.actions.find(m=>m.index===n);if(!f)return;f.stateAfterAction={_global:this.safeClone(a._global),_local:this.safeClone(a._local),_isolated:a._isolated?this.safeClone(a._isolated):void 0},f.duration=i,f.result=this.safeClone(l),f.stateDiff={global:this.computeStateDiff(f.stateBeforeAction._global,f.stateAfterAction._global),local:this.computeStateDiff(f.stateBeforeAction._local,f.stateAfterAction._local)},f.stateBeforeAction._isolated&&f.stateAfterAction._isolated&&(f.stateDiff.isolated=this.computeStateDiff(f.stateBeforeAction._isolated,f.stateAfterAction._isolated));const g=window.__g7PendingLocalState;g&&(f.pendingState=this.safeClone(g)),c&&(f.status="error",f.error={name:c.name,message:c.message,stack:c.stack})}endSequenceExecution(e,n){if(!this.config.enabled)return;const a=this.sequenceExecutionStack.findIndex(l=>l.sequenceId===e);if(a===-1)return;const i=this.sequenceExecutionStack[a];if(i.endTime=Date.now(),i.totalDuration=i.endTime-i.startTime,n){i.status="error",i.error={name:n.name,message:n.message,stack:n.stack};const l=i.actions.find(c=>c.status==="error");l&&(i.failedAtIndex=l.index)}else i.status="success";this.sequenceExecutions.push(i),this.sequenceExecutions.length>this.maxSequenceExecutions&&this.sequenceExecutions.shift(),this.sequenceExecutionStack.splice(a,1)}computeStateDiff(e,n){const a=[],i=[],l=[],c=new Set(Object.keys(e)),d=new Set(Object.keys(n));for(const f of d)c.has(f)||a.push(f);for(const f of c)d.has(f)||i.push(f);for(const f of c)if(d.has(f)){const g=e[f],m=n[f];JSON.stringify(g)!==JSON.stringify(m)&&l.push({path:f,oldValue:g,newValue:m})}return{added:a,removed:i,changed:l}}getSequenceTrackingInfo(){const e=this.getSequenceStats(),n=this.sequenceExecutionStack.length>0?this.sequenceExecutionStack[this.sequenceExecutionStack.length-1]:void 0;return{executions:[...this.sequenceExecutions],currentExecution:n,stats:e}}getSequenceStats(){const e=this.sequenceExecutions,n=e.length,a=e.filter(g=>g.status==="success").length,i=e.filter(g=>g.status==="error").length,l=e.reduce((g,m)=>g+m.actions.length,0),c=n>0?e.reduce((g,m)=>g+m.totalDuration,0)/n:0,d={};for(const g of e)for(const m of g.actions)d[m.handler]=(d[m.handler]||0)+1;const f=Object.entries(d).sort(([,g],[,m])=>m-g).slice(0,5).map(([g,m])=>({handler:g,count:m}));return{totalExecutions:n,successCount:a,errorCount:i,totalActions:l,avgDuration:c,topHandlers:f}}clearSequenceData(){this.sequenceExecutions=[],this.sequenceExecutionStack=[],this.sequenceIdCounter=0}getSequenceExecution(e){const n=this.findExecutionInStack(e);return n||this.sequenceExecutions.find(a=>a.sequenceId===e)}getRecentSequences(e=10){return this.sequenceExecutions.slice(-e)}registerStateCaptureForHandler(e,n,a){if(!this.config.enabled)return;const i=new Map,l=Date.now();for(const c of n)i.set(c,{value:this.safeClone(a[c]),capturedAt:l});this.stateCaptureRegistry.set(e,i)}detectStaleClosure(e,n,a,i,l){if(!this.config.enabled)return[];const c=this.stateCaptureRegistry.get(e);if(!c)return[];const d=Date.now(),f=[];for(const[g,m]of c){const y=this.getValueByPath(a,g),S=d-m.capturedAt;if(!this.isDeepEqual(m.value,y)&&S>0){const v=this.createStaleClosureWarning(i,n,g,m.value,m.capturedAt,y,d,S,l);f.push(v),this.addStaleClosureWarning(v)}}return this.stateCaptureRegistry.delete(e),f}trackStaleClosureWarning(e){if(!this.config.enabled)return;const n=Date.now(),a=this.createStaleClosureWarning(e.type,e.location,e.capturedPath,e.capturedValue,e.capturedAt,e.currentValue,n,n-e.capturedAt,e.actionId,e.stackTrace);this.addStaleClosureWarning(a)}createStaleClosureWarning(e,n,a,i,l,c,d,f,g,m){const y=this.getStaleClosureSeverity(e,f),{description:S,suggestion:v,docLink:_}=this.getStaleClosureMessages(e,a,f);return{id:`stale_${++this.staleClosureIdCounter}_${Date.now()}`,timestamp:Date.now(),type:e,location:n,capturedState:{path:a,capturedValue:this.safeClone(i),capturedAt:l},currentState:{path:a,currentValue:this.safeClone(c),retrievedAt:d},timeDiff:f,severity:y,description:S,suggestion:v,docLink:_,actionId:g,stackTrace:m}}getStaleClosureSeverity(e,n){if(n>5e3)return"error";if(n>1e3)return"warning";switch(e){case"async-state-capture":case"sequence-state-mismatch":return"warning";case"callback-state-capture":case"timeout-state-capture":return"info";case"event-handler-stale":return"warning";default:return"info"}}getStaleClosureMessages(e,n,a){const i=a<1e3?`${a}ms`:`${(a/1e3).toFixed(1)}s`;switch(e){case"async-state-capture":return{description:`await 후 ${i} 경과 시점에 '${n}' 상태가 변경됨. 캡처된 상태 대신 최신 상태를 사용해야 함`,suggestion:"await 이후에는 G7Core.state.get()으로 최신 상태를 다시 조회하거나, useRef + getter 패턴을 사용하세요",docLink:"troubleshooting-state-closure.md"};case"callback-state-capture":return{description:`콜백에서 ${i} 전에 캡처된 '${n}' 상태를 사용 중. 최신 상태와 다름`,suggestion:"콜백에서 상태를 참조할 때는 stateRef.current 패턴 또는 G7Core.state.get()를 사용하세요",docLink:"troubleshooting-state-closure.md"};case"timeout-state-capture":return{description:`setTimeout/setInterval 콜백에서 '${n}' 상태가 ${i} 전 값 사용 중`,suggestion:"타이머 콜백 내에서는 G7Core.state.get()으로 최신 상태를 조회하세요",docLink:"troubleshooting-state-closure.md"};case"sequence-state-mismatch":return{description:`sequence 내 '${n}' 상태가 이전 액션 결과와 불일치. context.state 대신 캡처된 값 사용 의심`,suggestion:"sequence 내에서는 context.state 또는 $prev를 사용하여 최신 상태를 참조하세요",docLink:"troubleshooting-state-setstate.md"};case"event-handler-stale":return{description:`이벤트 핸들러에서 ${i} 전에 바인딩된 '${n}' 상태 사용 중`,suggestion:"이벤트 핸들러에서 상태를 참조할 때는 useRef 패턴 또는 G7Core.state.get()를 사용하세요",docLink:"troubleshooting-state-closure.md"};default:return{description:`'${n}' 상태에서 stale closure 감지됨 (${i} 경과)`,suggestion:"상태 참조 시 최신 값을 사용하는지 확인하세요"}}}addStaleClosureWarning(e){this.staleClosureWarnings.push(e),this.staleClosureWarnings.length>this.maxStaleClosureWarnings&&this.staleClosureWarnings.shift()}getValueByPath(e,n){const a=n.split(".");let i=e;for(const l of a){if(i==null)return;i=i[l]}return i}isDeepEqual(e,n){if(e===n)return!0;if(e===null||n===null)return e===n;if(typeof e!=typeof n)return!1;if(typeof e!="object")return e===n;try{return JSON.stringify(e)===JSON.stringify(n)}catch{return!1}}getStaleClosureTrackingInfo(){const e=this.getStaleClosureStats();return{warnings:[...this.staleClosureWarnings],stats:e,timestamp:Date.now()}}getStaleClosureStats(){const e=this.staleClosureWarnings,n={"async-state-capture":0,"callback-state-capture":0,"timeout-state-capture":0,"sequence-state-mismatch":0,"event-handler-stale":0};for(const f of e)n[f.type]++;const a={info:0,warning:0,error:0};for(const f of e)a[f.severity]++;const i={};for(const f of e)i[f.location]=(i[f.location]||0)+1;const l=Object.entries(i).sort(([,f],[,g])=>g-f).slice(0,5).map(([f,g])=>({location:f,count:g})),c={};for(const f of e)c[f.capturedState.path]=(c[f.capturedState.path]||0)+1;const d=Object.entries(c).sort(([,f],[,g])=>g-f).slice(0,5).map(([f,g])=>({path:f,count:g}));return{totalWarnings:e.length,warningsByType:n,warningsBySeverity:a,topLocations:l,topAffectedPaths:d}}clearStaleClosureData(){this.staleClosureWarnings=[],this.staleClosureIdCounter=0,this.stateCaptureRegistry.clear()}getRecentStaleClosureWarnings(e=10,n){let a=[...this.staleClosureWarnings];return n&&(a=a.filter(i=>i.severity===n)),a.slice(-e)}safeClone(e){if(e==null||typeof e!="object")return e;try{return JSON.parse(JSON.stringify(e))}catch{if(Array.isArray(e))return e.map(a=>{try{return JSON.parse(JSON.stringify(a))}catch{return"[Unserializable]"}});const n={};for(const a of Object.keys(e))try{n[a]=JSON.parse(JSON.stringify(e[a]))}catch{n[a]="[Unserializable]"}return n}}};$(wi,"instance");let Oa=wi;const Kn=ht("LayoutLoader");class Er extends Error{constructor(n,a,i){super(n);$(this,"code");$(this,"details");this.code=a,this.details=i,this.name="LayoutLoaderError"}}class mf{constructor(e){$(this,"componentRegistry");$(this,"currentLayout",null);$(this,"layoutCache",new Map);$(this,"cacheVersion",0);this.componentRegistry=e}setCacheVersion(e){this.cacheVersion!==e&&(Kn.log("Cache version updated:",this.cacheVersion,"->",e),this.cacheVersion=e,this.layoutCache.clear())}getCacheVersion(){return this.cacheVersion}async loadLayout(e,n){const a=`${e}:${n}`;if(this.layoutCache.has(a)){Kn.log("Loading layout from cache:",n);const l=await this.layoutCache.get(a),c=JSON.parse(JSON.stringify(l));return this.currentLayout=c,Oa.getInstance().trackLayoutLoad(n,e,c,"cache"),c}const i=this.fetchLayout(e,n);return this.layoutCache.set(a,i),i.catch(()=>{this.layoutCache.get(a)===i&&this.layoutCache.delete(a)}),i}async fetchLayout(e,n,a=!1){try{const i=this.cacheVersion>0?this.cacheVersion:null;let l;if(n.startsWith("__preview__/")){const S=n.replace("__preview__/","");l=Nr(`/api/layouts/preview/${S}`,"json",i)}else l=Nr(`/api/layouts/${e}/${n}`,"json",i);Kn.log("Fetching layout from API:",l);const c=Hr(),d=a?null:c.getToken(),f={Accept:"application/json"};d&&(f.Authorization=`Bearer ${d}`);const g=await Il(l,{init:{headers:f},label:`layout: ${n}`});let m;try{m=await g.json()}catch{m=null}if(!g.ok){if(g.status===401&&!a&&d){Kn.log("Token invalid, removing and retrying without token"),c.removeToken();try{return await this.fetchLayout(e,n,!0)}catch(v){throw v instanceof Er&&v.details?.status===401?new Er(v.message,v.code,{...v.details,hadToken:!0}):v}}const S=m?.message||m?.error;throw new Er(`Failed to fetch layout: ${g.status} ${g.statusText}`,"FETCH_FAILED",{status:g.status,statusText:g.statusText,url:l,apiMessage:S})}const y=m.data||m;return this.validateLayoutData(y),this.currentLayout=y,Oa.getInstance().trackLayoutLoad(n,e,y,"api"),Kn.log("Layout fetched and cached successfully:",y.layout_name),y}catch(i){const l=`${e}:${n}`;throw this.layoutCache.delete(l),i instanceof Er?i:new Er("Failed to load layout","LOAD_FAILED",{originalError:i})}}prefetchLayout(e,n){return this.loadLayout(e,n)}validateLayoutData(e){if(!e.version)throw new Er("Layout data missing required field: version","VALIDATION_FAILED",{field:"version"});if(!e.layout_name)throw new Er("Layout data missing required field: layout_name","VALIDATION_FAILED",{field:"layout_name"});if(!Array.isArray(e.components))throw new Er('Layout data field "components" must be an array',"VALIDATION_FAILED",{field:"components",value:e.components});Kn.log("Layout data validation passed")}renderLayout(e,n){try{const a=n||this.currentLayout;if(!a)throw new Er("No layout data to render","NO_LAYOUT_DATA");Kn.log("Rendering layout:",a.layout_name),e.innerHTML="",this.renderComponents(e,a.components),Kn.log("Layout rendered successfully")}catch(a){Kn.error("Render error:",a),this.renderErrorState(e,a)}}renderComponents(e,n){for(const a of n){const i=this.renderComponent(a);i&&e.appendChild(i)}}renderComponent(e){try{if(!this.componentRegistry.getComponent(e.type))return Kn.warn(`Component not found: ${e.type}`),this.createPlaceholderElement(e.type);const a=document.createElement("div");return a.setAttribute("data-component",e.type),e.props&&Object.entries(e.props).forEach(([i,l])=>{a.setAttribute(`data-prop-${i}`,JSON.stringify(l))}),e.children&&e.children.length>0?this.renderComponents(a,e.children):e.text&&(a.textContent=e.text),a}catch(n){return Kn.error(`Error rendering component ${e.type}:`,n),this.createErrorElement(e.type,n)}}createPlaceholderElement(e){const n=document.createElement("div");return n.className="component-placeholder",n.setAttribute("data-component-type",e),n.innerHTML=` + `},qC={onDragStart(o){let{active:e}=o;return"Picked up draggable item "+e.id+"."},onDragOver(o){let{active:e,over:n}=o;return n?"Draggable item "+e.id+" was moved over droppable area "+n.id+".":"Draggable item "+e.id+" is no longer over a droppable area."},onDragEnd(o){let{active:e,over:n}=o;return n?"Draggable item "+e.id+" was dropped over droppable area "+n.id:"Draggable item "+e.id+" was dropped."},onDragCancel(o){let{active:e}=o;return"Dragging was cancelled. Draggable item "+e.id+" was dropped."}};function GC(o){let{announcements:e=qC,container:n,hiddenTextDescribedById:a,screenReaderInstructions:i=PC}=o;const{announce:l,announcement:c}=jC(),d=vo("DndLiveRegion"),[f,g]=N.useState(!1);if(N.useEffect(()=>{g(!0)},[]),UC(N.useMemo(()=>({onDragStart(b){let{active:S}=b;l(e.onDragStart({active:S}))},onDragMove(b){let{active:S,over:v}=b;e.onDragMove&&l(e.onDragMove({active:S,over:v}))},onDragOver(b){let{active:S,over:v}=b;l(e.onDragOver({active:S,over:v}))},onDragEnd(b){let{active:S,over:v}=b;l(e.onDragEnd({active:S,over:v}))},onDragCancel(b){let{active:S,over:v}=b;l(e.onDragCancel({active:S,over:v}))}}),[l,e])),!f)return null;const m=Ye.createElement(Ye.Fragment,null,Ye.createElement(HC,{id:a,value:i.draggable}),Ye.createElement(zC,{id:d,announcement:c}));return n?ka.createPortal(m,n):m}var tn;(function(o){o.DragStart="dragStart",o.DragMove="dragMove",o.DragEnd="dragEnd",o.DragCancel="dragCancel",o.DragOver="dragOver",o.RegisterDroppable="registerDroppable",o.SetDroppableDisabled="setDroppableDisabled",o.UnregisterDroppable="unregisterDroppable"})(tn||(tn={}));function ic(){}function Cm(o,e){return N.useMemo(()=>({sensor:o,options:e??{}}),[o,e])}function VC(){for(var o=arguments.length,e=new Array(o),n=0;n[...e].filter(a=>a!=null),[...e])}const Sr=Object.freeze({x:0,y:0});function Em(o,e){return Math.sqrt(Math.pow(o.x-e.x,2)+Math.pow(o.y-e.y,2))}function _m(o,e){let{data:{value:n}}=o,{data:{value:a}}=e;return n-a}function FC(o,e){let{data:{value:n}}=o,{data:{value:a}}=e;return a-n}function Am(o){let{left:e,top:n,height:a,width:i}=o;return[{x:e,y:n},{x:e+i,y:n},{x:e,y:n+a},{x:e+i,y:n+a}]}function xm(o,e){if(!o||o.length===0)return null;const[n]=o;return n[e]}function Tm(o,e,n){return e===void 0&&(e=o.left),n===void 0&&(n=o.top),{x:e+o.width*.5,y:n+o.height*.5}}const KC=o=>{let{collisionRect:e,droppableRects:n,droppableContainers:a}=o;const i=Tm(e,e.left,e.top),l=[];for(const c of a){const{id:d}=c,f=n.get(d);if(f){const g=Em(Tm(f),i);l.push({id:d,data:{droppableContainer:c,value:g}})}}return l.sort(_m)},WC=o=>{let{collisionRect:e,droppableRects:n,droppableContainers:a}=o;const i=Am(e),l=[];for(const c of a){const{id:d}=c,f=n.get(d);if(f){const g=Am(f),m=i.reduce((S,v,_)=>S+Em(g[_],v),0),b=Number((m/4).toFixed(4));l.push({id:d,data:{droppableContainer:c,value:b}})}}return l.sort(_m)};function YC(o,e){const n=Math.max(e.top,o.top),a=Math.max(e.left,o.left),i=Math.min(e.left+e.width,o.left+o.width),l=Math.min(e.top+e.height,o.top+o.height),c=i-a,d=l-n;if(a{let{collisionRect:e,droppableRects:n,droppableContainers:a}=o;const i=[];for(const l of a){const{id:c}=l,d=n.get(c);if(d){const f=YC(d,e);f>0&&i.push({id:c,data:{droppableContainer:l,value:f}})}}return i.sort(FC)};function JC(o,e,n){return{...o,scaleX:e&&n?e.width/n.width:1,scaleY:e&&n?e.height/n.height:1}}function Rm(o,e){return o&&e?{x:o.left-e.left,y:o.top-e.top}:Sr}function QC(o){return function(n){for(var a=arguments.length,i=new Array(a>1?a-1:0),l=1;l({...c,top:c.top+o*d.y,bottom:c.bottom+o*d.y,left:c.left+o*d.x,right:c.right+o*d.x}),{...n})}}const ZC=QC(1);function eE(o){if(o.startsWith("matrix3d(")){const e=o.slice(9,-1).split(/, /);return{x:+e[12],y:+e[13],scaleX:+e[0],scaleY:+e[5]}}else if(o.startsWith("matrix(")){const e=o.slice(7,-1).split(/, /);return{x:+e[4],y:+e[5],scaleX:+e[0],scaleY:+e[3]}}return null}function tE(o,e,n){const a=eE(e);if(!a)return o;const{scaleX:i,scaleY:l,x:c,y:d}=a,f=o.left-c-(1-i)*parseFloat(n),g=o.top-d-(1-l)*parseFloat(n.slice(n.indexOf(" ")+1)),m=i?o.width/i:o.width,b=l?o.height/l:o.height;return{width:m,height:b,top:g,right:f+m,bottom:g+b,left:f}}const nE={ignoreTransform:!1};function ds(o,e){e===void 0&&(e=nE);let n=o.getBoundingClientRect();if(e.ignoreTransform){const{transform:g,transformOrigin:m}=Ln(o).getComputedStyle(o);g&&(n=tE(n,g,m))}const{top:a,left:i,width:l,height:c,bottom:d,right:f}=n;return{top:a,left:i,width:l,height:c,bottom:d,right:f}}function km(o){return ds(o,{ignoreTransform:!0})}function rE(o){const e=o.innerWidth,n=o.innerHeight;return{top:0,left:0,right:e,bottom:n,width:e,height:n}}function aE(o,e){return e===void 0&&(e=Ln(o).getComputedStyle(o)),e.position==="fixed"}function iE(o,e){e===void 0&&(e=Ln(o).getComputedStyle(o));const n=/(auto|scroll|overlay)/;return["overflow","overflowX","overflowY"].some(i=>{const l=e[i];return typeof l=="string"?n.test(l):!1})}function sc(o,e){const n=[];function a(i){if(e!=null&&n.length>=e||!i)return n;if(Fd(i)&&i.scrollingElement!=null&&!n.includes(i.scrollingElement))return n.push(i.scrollingElement),n;if(!mo(i)||bm(i)||n.includes(i))return n;const l=Ln(o).getComputedStyle(i);return i!==o&&iE(i,l)&&n.push(i),aE(i,l)?n:a(i.parentNode)}return o?a(o):n}function Dm(o){const[e]=sc(o,1);return e??null}function Qd(o){return!rc||!o?null:ls(o)?o:Vd(o)?Fd(o)||o===cs(o).scrollingElement?window:mo(o)?o:null:null}function Om(o){return ls(o)?o.scrollX:o.scrollLeft}function Lm(o){return ls(o)?o.scrollY:o.scrollTop}function Zd(o){return{x:Om(o),y:Lm(o)}}var un;(function(o){o[o.Forward=1]="Forward",o[o.Backward=-1]="Backward"})(un||(un={}));function Mm(o){return!rc||!o?!1:o===document.scrollingElement}function $m(o){const e={x:0,y:0},n=Mm(o)?{height:window.innerHeight,width:window.innerWidth}:{height:o.clientHeight,width:o.clientWidth},a={x:o.scrollWidth-n.width,y:o.scrollHeight-n.height},i=o.scrollTop<=e.y,l=o.scrollLeft<=e.x,c=o.scrollTop>=a.y,d=o.scrollLeft>=a.x;return{isTop:i,isLeft:l,isBottom:c,isRight:d,maxScroll:a,minScroll:e}}const sE={x:.2,y:.2};function oE(o,e,n,a,i){let{top:l,left:c,right:d,bottom:f}=n;a===void 0&&(a=10),i===void 0&&(i=sE);const{isTop:g,isBottom:m,isLeft:b,isRight:S}=$m(o),v={x:0,y:0},_={x:0,y:0},A={height:e.height*i.y,width:e.width*i.x};return!g&&l<=e.top+A.height?(v.y=un.Backward,_.y=a*Math.abs((e.top+A.height-l)/A.height)):!m&&f>=e.bottom-A.height&&(v.y=un.Forward,_.y=a*Math.abs((e.bottom-A.height-f)/A.height)),!S&&d>=e.right-A.width?(v.x=un.Forward,_.x=a*Math.abs((e.right-A.width-d)/A.width)):!b&&c<=e.left+A.width&&(v.x=un.Backward,_.x=a*Math.abs((e.left+A.width-c)/A.width)),{direction:v,speed:_}}function lE(o){if(o===document.scrollingElement){const{innerWidth:l,innerHeight:c}=window;return{top:0,left:0,right:l,bottom:c,width:l,height:c}}const{top:e,left:n,right:a,bottom:i}=o.getBoundingClientRect();return{top:e,left:n,right:a,bottom:i,width:o.clientWidth,height:o.clientHeight}}function Nm(o){return o.reduce((e,n)=>us(e,Zd(n)),Sr)}function cE(o){return o.reduce((e,n)=>e+Om(n),0)}function uE(o){return o.reduce((e,n)=>e+Lm(n),0)}function dE(o,e){if(e===void 0&&(e=ds),!o)return;const{top:n,left:a,bottom:i,right:l}=e(o);Dm(o)&&(i<=0||l<=0||n>=window.innerHeight||a>=window.innerWidth)&&o.scrollIntoView({block:"center",inline:"center"})}const fE=[["x",["left","right"],cE],["y",["top","bottom"],uE]];class ef{constructor(e,n){this.rect=void 0,this.width=void 0,this.height=void 0,this.top=void 0,this.bottom=void 0,this.right=void 0,this.left=void 0;const a=sc(n),i=Nm(a);this.rect={...e},this.width=e.width,this.height=e.height;for(const[l,c,d]of fE)for(const f of c)Object.defineProperty(this,f,{get:()=>{const g=d(a),m=i[l]-g;return this.rect[f]+m},enumerable:!0});Object.defineProperty(this,"rect",{enumerable:!1})}}class Co{constructor(e){this.target=void 0,this.listeners=[],this.removeAll=()=>{this.listeners.forEach(n=>{var a;return(a=this.target)==null?void 0:a.removeEventListener(...n)})},this.target=e}add(e,n,a){var i;(i=this.target)==null||i.addEventListener(e,n,a),this.listeners.push([e,n,a])}}function hE(o){const{EventTarget:e}=Ln(o);return o instanceof e?o:cs(o)}function tf(o,e){const n=Math.abs(o.x),a=Math.abs(o.y);return typeof e=="number"?Math.sqrt(n**2+a**2)>e:"x"in e&&"y"in e?n>e.x&&a>e.y:"x"in e?n>e.x:"y"in e?a>e.y:!1}var rr;(function(o){o.Click="click",o.DragStart="dragstart",o.Keydown="keydown",o.ContextMenu="contextmenu",o.Resize="resize",o.SelectionChange="selectionchange",o.VisibilityChange="visibilitychange"})(rr||(rr={}));function Im(o){o.preventDefault()}function gE(o){o.stopPropagation()}var ot;(function(o){o.Space="Space",o.Down="ArrowDown",o.Right="ArrowRight",o.Left="ArrowLeft",o.Up="ArrowUp",o.Esc="Escape",o.Enter="Enter",o.Tab="Tab"})(ot||(ot={}));const Hm={start:[ot.Space,ot.Enter],cancel:[ot.Esc],end:[ot.Space,ot.Enter,ot.Tab]},pE=(o,e)=>{let{currentCoordinates:n}=e;switch(o.code){case ot.Right:return{...n,x:n.x+25};case ot.Left:return{...n,x:n.x-25};case ot.Down:return{...n,y:n.y+25};case ot.Up:return{...n,y:n.y-25}}};class nf{constructor(e){this.props=void 0,this.autoScrollEnabled=!1,this.referenceCoordinates=void 0,this.listeners=void 0,this.windowListeners=void 0,this.props=e;const{event:{target:n}}=e;this.props=e,this.listeners=new Co(cs(n)),this.windowListeners=new Co(Ln(n)),this.handleKeyDown=this.handleKeyDown.bind(this),this.handleCancel=this.handleCancel.bind(this),this.attach()}attach(){this.handleStart(),this.windowListeners.add(rr.Resize,this.handleCancel),this.windowListeners.add(rr.VisibilityChange,this.handleCancel),setTimeout(()=>this.listeners.add(rr.Keydown,this.handleKeyDown))}handleStart(){const{activeNode:e,onStart:n}=this.props,a=e.node.current;a&&dE(a),n(Sr)}handleKeyDown(e){if(Xd(e)){const{active:n,context:a,options:i}=this.props,{keyboardCodes:l=Hm,coordinateGetter:c=pE,scrollBehavior:d="smooth"}=i,{code:f}=e;if(l.end.includes(f)){this.handleEnd(e);return}if(l.cancel.includes(f)){this.handleCancel(e);return}const{collisionRect:g}=a.current,m=g?{x:g.left,y:g.top}:Sr;this.referenceCoordinates||(this.referenceCoordinates=m);const b=c(e,{active:n,context:a.current,currentCoordinates:m});if(b){const S=So(b,m),v={x:0,y:0},{scrollableAncestors:_}=a.current;for(const A of _){const x=e.code,{isTop:L,isRight:M,isLeft:R,isBottom:D,maxScroll:B,minScroll:q}=$m(A),P=lE(A),K={x:Math.min(x===ot.Right?P.right-P.width/2:P.right,Math.max(x===ot.Right?P.left:P.left+P.width/2,b.x)),y:Math.min(x===ot.Down?P.bottom-P.height/2:P.bottom,Math.max(x===ot.Down?P.top:P.top+P.height/2,b.y))},ge=x===ot.Right&&!M||x===ot.Left&&!R,Ee=x===ot.Down&&!D||x===ot.Up&&!L;if(ge&&K.x!==b.x){const Se=A.scrollLeft+S.x,Ue=x===ot.Right&&Se<=B.x||x===ot.Left&&Se>=q.x;if(Ue&&!S.y){A.scrollTo({left:Se,behavior:d});return}Ue?v.x=A.scrollLeft-Se:v.x=x===ot.Right?A.scrollLeft-B.x:A.scrollLeft-q.x,v.x&&A.scrollBy({left:-v.x,behavior:d});break}else if(Ee&&K.y!==b.y){const Se=A.scrollTop+S.y,Ue=x===ot.Down&&Se<=B.y||x===ot.Up&&Se>=q.y;if(Ue&&!S.x){A.scrollTo({top:Se,behavior:d});return}Ue?v.y=A.scrollTop-Se:v.y=x===ot.Down?A.scrollTop-B.y:A.scrollTop-q.y,v.y&&A.scrollBy({top:-v.y,behavior:d});break}}this.handleMove(e,us(So(b,this.referenceCoordinates),v))}}}handleMove(e,n){const{onMove:a}=this.props;e.preventDefault(),a(n)}handleEnd(e){const{onEnd:n}=this.props;e.preventDefault(),this.detach(),n()}handleCancel(e){const{onCancel:n}=this.props;e.preventDefault(),this.detach(),n()}detach(){this.listeners.removeAll(),this.windowListeners.removeAll()}}nf.activators=[{eventName:"onKeyDown",handler:(o,e,n)=>{let{keyboardCodes:a=Hm,onActivation:i}=e,{active:l}=n;const{code:c}=o.nativeEvent;if(a.start.includes(c)){const d=l.activatorNode.current;return d&&o.target!==d?!1:(o.preventDefault(),i?.({event:o.nativeEvent}),!0)}return!1}}];function zm(o){return!!(o&&"distance"in o)}function jm(o){return!!(o&&"delay"in o)}class rf{constructor(e,n,a){var i;a===void 0&&(a=hE(e.event.target)),this.props=void 0,this.events=void 0,this.autoScrollEnabled=!0,this.document=void 0,this.activated=!1,this.initialCoordinates=void 0,this.timeoutId=null,this.listeners=void 0,this.documentListeners=void 0,this.windowListeners=void 0,this.props=e,this.events=n;const{event:l}=e,{target:c}=l;this.props=e,this.events=n,this.document=cs(c),this.documentListeners=new Co(this.document),this.listeners=new Co(a),this.windowListeners=new Co(Ln(c)),this.initialCoordinates=(i=Jd(l))!=null?i:Sr,this.handleStart=this.handleStart.bind(this),this.handleMove=this.handleMove.bind(this),this.handleEnd=this.handleEnd.bind(this),this.handleCancel=this.handleCancel.bind(this),this.handleKeydown=this.handleKeydown.bind(this),this.removeTextSelection=this.removeTextSelection.bind(this),this.attach()}attach(){const{events:e,props:{options:{activationConstraint:n,bypassActivationConstraint:a}}}=this;if(this.listeners.add(e.move.name,this.handleMove,{passive:!1}),this.listeners.add(e.end.name,this.handleEnd),e.cancel&&this.listeners.add(e.cancel.name,this.handleCancel),this.windowListeners.add(rr.Resize,this.handleCancel),this.windowListeners.add(rr.DragStart,Im),this.windowListeners.add(rr.VisibilityChange,this.handleCancel),this.windowListeners.add(rr.ContextMenu,Im),this.documentListeners.add(rr.Keydown,this.handleKeydown),n){if(a!=null&&a({event:this.props.event,activeNode:this.props.activeNode,options:this.props.options}))return this.handleStart();if(jm(n)){this.timeoutId=setTimeout(this.handleStart,n.delay),this.handlePending(n);return}if(zm(n)){this.handlePending(n);return}}this.handleStart()}detach(){this.listeners.removeAll(),this.windowListeners.removeAll(),setTimeout(this.documentListeners.removeAll,50),this.timeoutId!==null&&(clearTimeout(this.timeoutId),this.timeoutId=null)}handlePending(e,n){const{active:a,onPending:i}=this.props;i(a,e,this.initialCoordinates,n)}handleStart(){const{initialCoordinates:e}=this,{onStart:n}=this.props;e&&(this.activated=!0,this.documentListeners.add(rr.Click,gE,{capture:!0}),this.removeTextSelection(),this.documentListeners.add(rr.SelectionChange,this.removeTextSelection),n(e))}handleMove(e){var n;const{activated:a,initialCoordinates:i,props:l}=this,{onMove:c,options:{activationConstraint:d}}=l;if(!i)return;const f=(n=Jd(e))!=null?n:Sr,g=So(i,f);if(!a&&d){if(zm(d)){if(d.tolerance!=null&&tf(g,d.tolerance))return this.handleCancel();if(tf(g,d.distance))return this.handleStart()}if(jm(d)&&tf(g,d.tolerance))return this.handleCancel();this.handlePending(d,g);return}e.cancelable&&e.preventDefault(),c(f)}handleEnd(){const{onAbort:e,onEnd:n}=this.props;this.detach(),this.activated||e(this.props.active),n()}handleCancel(){const{onAbort:e,onCancel:n}=this.props;this.detach(),this.activated||e(this.props.active),n()}handleKeydown(e){e.code===ot.Esc&&this.handleCancel()}removeTextSelection(){var e;(e=this.document.getSelection())==null||e.removeAllRanges()}}const mE={cancel:{name:"pointercancel"},move:{name:"pointermove"},end:{name:"pointerup"}};class af extends rf{constructor(e){const{event:n}=e,a=cs(n.target);super(e,mE,a)}}af.activators=[{eventName:"onPointerDown",handler:(o,e)=>{let{nativeEvent:n}=o,{onActivation:a}=e;return!n.isPrimary||n.button!==0?!1:(a?.({event:n}),!0)}}];const yE={move:{name:"mousemove"},end:{name:"mouseup"}};var sf;(function(o){o[o.RightClick=2]="RightClick"})(sf||(sf={}));class bE extends rf{constructor(e){super(e,yE,cs(e.event.target))}}bE.activators=[{eventName:"onMouseDown",handler:(o,e)=>{let{nativeEvent:n}=o,{onActivation:a}=e;return n.button===sf.RightClick?!1:(a?.({event:n}),!0)}}];const of={cancel:{name:"touchcancel"},move:{name:"touchmove"},end:{name:"touchend"}};class vE extends rf{constructor(e){super(e,of)}static setup(){return window.addEventListener(of.move.name,e,{capture:!1,passive:!1}),function(){window.removeEventListener(of.move.name,e)};function e(){}}}vE.activators=[{eventName:"onTouchStart",handler:(o,e)=>{let{nativeEvent:n}=o,{onActivation:a}=e;const{touches:i}=n;return i.length>1?!1:(a?.({event:n}),!0)}}];var Eo;(function(o){o[o.Pointer=0]="Pointer",o[o.DraggableRect=1]="DraggableRect"})(Eo||(Eo={}));var oc;(function(o){o[o.TreeOrder=0]="TreeOrder",o[o.ReversedTreeOrder=1]="ReversedTreeOrder"})(oc||(oc={}));function SE(o){let{acceleration:e,activator:n=Eo.Pointer,canScroll:a,draggingRect:i,enabled:l,interval:c=5,order:d=oc.TreeOrder,pointerCoordinates:f,scrollableAncestors:g,scrollableAncestorRects:m,delta:b,threshold:S}=o;const v=CE({delta:b,disabled:!l}),[_,A]=LC(),x=N.useRef({x:0,y:0}),L=N.useRef({x:0,y:0}),M=N.useMemo(()=>{switch(n){case Eo.Pointer:return f?{top:f.y,bottom:f.y,left:f.x,right:f.x}:null;case Eo.DraggableRect:return i}},[n,i,f]),R=N.useRef(null),D=N.useCallback(()=>{const q=R.current;if(!q)return;const P=x.current.x*L.current.x,K=x.current.y*L.current.y;q.scrollBy(P,K)},[]),B=N.useMemo(()=>d===oc.TreeOrder?[...g].reverse():g,[d,g]);N.useEffect(()=>{if(!l||!g.length||!M){A();return}for(const q of B){if(a?.(q)===!1)continue;const P=g.indexOf(q),K=m[P];if(!K)continue;const{direction:ge,speed:Ee}=oE(q,K,M,e,S);for(const Se of["x","y"])v[Se][ge[Se]]||(Ee[Se]=0,ge[Se]=0);if(Ee.x>0||Ee.y>0){A(),R.current=q,_(D,c),x.current=Ee,L.current=ge;return}}x.current={x:0,y:0},L.current={x:0,y:0},A()},[e,D,a,A,l,c,JSON.stringify(M),JSON.stringify(v),_,g,B,m,JSON.stringify(S)])}const wE={x:{[un.Backward]:!1,[un.Forward]:!1},y:{[un.Backward]:!1,[un.Forward]:!1}};function CE(o){let{delta:e,disabled:n}=o;const a=Wd(e);return bo(i=>{if(n||!a||!i)return wE;const l={x:Math.sign(e.x-a.x),y:Math.sign(e.y-a.y)};return{x:{[un.Backward]:i.x[un.Backward]||l.x===-1,[un.Forward]:i.x[un.Forward]||l.x===1},y:{[un.Backward]:i.y[un.Backward]||l.y===-1,[un.Forward]:i.y[un.Forward]||l.y===1}}},[n,e,a])}function EE(o,e){const n=e!=null?o.get(e):void 0,a=n?n.node.current:null;return bo(i=>{var l;return e==null?null:(l=a??i)!=null?l:null},[a,e])}function _E(o,e){return N.useMemo(()=>o.reduce((n,a)=>{const{sensor:i}=a,l=i.activators.map(c=>({eventName:c.eventName,handler:e(c.handler,a)}));return[...n,...l]},[]),[o,e])}var _o;(function(o){o[o.Always=0]="Always",o[o.BeforeDragging=1]="BeforeDragging",o[o.WhileDragging=2]="WhileDragging"})(_o||(_o={}));var lf;(function(o){o.Optimized="optimized"})(lf||(lf={}));const Um=new Map;function AE(o,e){let{dragging:n,dependencies:a,config:i}=e;const[l,c]=N.useState(null),{frequency:d,measure:f,strategy:g}=i,m=N.useRef(o),b=x(),S=yo(b),v=N.useCallback(function(L){L===void 0&&(L=[]),!S.current&&c(M=>M===null?L:M.concat(L.filter(R=>!M.includes(R))))},[S]),_=N.useRef(null),A=bo(L=>{if(b&&!n)return Um;if(!L||L===Um||m.current!==o||l!=null){const M=new Map;for(let R of o){if(!R)continue;if(l&&l.length>0&&!l.includes(R.id)&&R.rect.current){M.set(R.id,R.rect.current);continue}const D=R.node.current,B=D?new ef(f(D),D):null;R.rect.current=B,B&&M.set(R.id,B)}return M}return L},[o,l,n,b,f]);return N.useEffect(()=>{m.current=o},[o]),N.useEffect(()=>{b||v()},[n,b]),N.useEffect(()=>{l&&l.length>0&&c(null)},[JSON.stringify(l)]),N.useEffect(()=>{b||typeof d!="number"||_.current!==null||(_.current=setTimeout(()=>{v(),_.current=null},d))},[d,b,v,...a]),{droppableRects:A,measureDroppableContainers:v,measuringScheduled:l!=null};function x(){switch(g){case _o.Always:return!1;case _o.BeforeDragging:return n;default:return!n}}}function Bm(o,e){return bo(n=>o?n||(typeof e=="function"?e(o):o):null,[e,o])}function xE(o,e){return Bm(o,e)}function TE(o){let{callback:e,disabled:n}=o;const a=Kd(e),i=N.useMemo(()=>{if(n||typeof window>"u"||typeof window.MutationObserver>"u")return;const{MutationObserver:l}=window;return new l(a)},[a,n]);return N.useEffect(()=>()=>i?.disconnect(),[i]),i}function lc(o){let{callback:e,disabled:n}=o;const a=Kd(e),i=N.useMemo(()=>{if(n||typeof window>"u"||typeof window.ResizeObserver>"u")return;const{ResizeObserver:l}=window;return new l(a)},[n]);return N.useEffect(()=>()=>i?.disconnect(),[i]),i}function RE(o){return new ef(ds(o),o)}function Pm(o,e,n){e===void 0&&(e=RE);const[a,i]=N.useState(null);function l(){i(f=>{if(!o)return null;if(o.isConnected===!1){var g;return(g=f??n)!=null?g:null}const m=e(o);return JSON.stringify(f)===JSON.stringify(m)?f:m})}const c=TE({callback(f){if(o)for(const g of f){const{type:m,target:b}=g;if(m==="childList"&&b instanceof HTMLElement&&b.contains(o)){l();break}}}}),d=lc({callback:l});return zr(()=>{l(),o?(d?.observe(o),c?.observe(document.body,{childList:!0,subtree:!0})):(d?.disconnect(),c?.disconnect())},[o]),a}function kE(o){const e=Bm(o);return Rm(o,e)}const qm=[];function DE(o){const e=N.useRef(o),n=bo(a=>o?a&&a!==qm&&o&&e.current&&o.parentNode===e.current.parentNode?a:sc(o):qm,[o]);return N.useEffect(()=>{e.current=o},[o]),n}function OE(o){const[e,n]=N.useState(null),a=N.useRef(o),i=N.useCallback(l=>{const c=Qd(l.target);c&&n(d=>d?(d.set(c,Zd(c)),new Map(d)):null)},[]);return N.useEffect(()=>{const l=a.current;if(o!==l){c(l);const d=o.map(f=>{const g=Qd(f);return g?(g.addEventListener("scroll",i,{passive:!0}),[g,Zd(g)]):null}).filter(f=>f!=null);n(d.length?new Map(d):null),a.current=o}return()=>{c(o),c(l)};function c(d){d.forEach(f=>{const g=Qd(f);g?.removeEventListener("scroll",i)})}},[i,o]),N.useMemo(()=>o.length?e?Array.from(e.values()).reduce((l,c)=>us(l,c),Sr):Nm(o):Sr,[o,e])}function Gm(o,e){e===void 0&&(e=[]);const n=N.useRef(null);return N.useEffect(()=>{n.current=null},e),N.useEffect(()=>{const a=o!==Sr;a&&!n.current&&(n.current=o),!a&&n.current&&(n.current=null)},[o]),n.current?So(o,n.current):Sr}function LE(o){N.useEffect(()=>{if(!rc)return;const e=o.map(n=>{let{sensor:a}=n;return a.setup==null?void 0:a.setup()});return()=>{for(const n of e)n?.()}},o.map(e=>{let{sensor:n}=e;return n}))}function ME(o,e){return N.useMemo(()=>o.reduce((n,a)=>{let{eventName:i,handler:l}=a;return n[i]=c=>{l(c,e)},n},{}),[o,e])}function Vm(o){return N.useMemo(()=>o?rE(o):null,[o])}const Fm=[];function $E(o,e){e===void 0&&(e=ds);const[n]=o,a=Vm(n?Ln(n):null),[i,l]=N.useState(Fm);function c(){l(()=>o.length?o.map(f=>Mm(f)?a:new ef(e(f),f)):Fm)}const d=lc({callback:c});return zr(()=>{d?.disconnect(),c(),o.forEach(f=>d?.observe(f))},[o]),i}function NE(o){if(!o)return null;if(o.children.length>1)return o;const e=o.children[0];return mo(e)?e:o}function IE(o){let{measure:e}=o;const[n,a]=N.useState(null),i=N.useCallback(g=>{for(const{target:m}of g)if(mo(m)){a(b=>{const S=e(m);return b?{...b,width:S.width,height:S.height}:S});break}},[e]),l=lc({callback:i}),c=N.useCallback(g=>{const m=NE(g);l?.disconnect(),m&&l?.observe(m),a(m?e(m):null)},[e,l]),[d,f]=ac(c);return N.useMemo(()=>({nodeRef:d,rect:n,setRef:f}),[n,d,f])}const HE=[{sensor:af,options:{}},{sensor:nf,options:{}}],zE={current:{}},cc={draggable:{measure:km},droppable:{measure:km,strategy:_o.WhileDragging,frequency:lf.Optimized},dragOverlay:{measure:ds}};class Ao extends Map{get(e){var n;return e!=null&&(n=super.get(e))!=null?n:void 0}toArray(){return Array.from(this.values())}getEnabled(){return this.toArray().filter(e=>{let{disabled:n}=e;return!n})}getNodeFor(e){var n,a;return(n=(a=this.get(e))==null?void 0:a.node.current)!=null?n:void 0}}const jE={activatorEvent:null,active:null,activeNode:null,activeNodeRect:null,collisions:null,containerNodeRect:null,draggableNodes:new Map,droppableRects:new Map,droppableContainers:new Ao,over:null,dragOverlay:{nodeRef:{current:null},rect:null,setRef:ic},scrollableAncestors:[],scrollableAncestorRects:[],measuringConfiguration:cc,measureDroppableContainers:ic,windowRect:null,measuringScheduled:!1},UE={activatorEvent:null,activators:[],active:null,activeNodeRect:null,ariaDescribedById:{draggable:""},dispatch:ic,draggableNodes:new Map,over:null,measureDroppableContainers:ic},uc=N.createContext(UE),Km=N.createContext(jE);function BE(){return{draggable:{active:null,initialCoordinates:{x:0,y:0},nodes:new Map,translate:{x:0,y:0}},droppable:{containers:new Ao}}}function PE(o,e){switch(e.type){case tn.DragStart:return{...o,draggable:{...o.draggable,initialCoordinates:e.initialCoordinates,active:e.active}};case tn.DragMove:return o.draggable.active==null?o:{...o,draggable:{...o.draggable,translate:{x:e.coordinates.x-o.draggable.initialCoordinates.x,y:e.coordinates.y-o.draggable.initialCoordinates.y}}};case tn.DragEnd:case tn.DragCancel:return{...o,draggable:{...o.draggable,active:null,initialCoordinates:{x:0,y:0},translate:{x:0,y:0}}};case tn.RegisterDroppable:{const{element:n}=e,{id:a}=n,i=new Ao(o.droppable.containers);return i.set(a,n),{...o,droppable:{...o.droppable,containers:i}}}case tn.SetDroppableDisabled:{const{id:n,key:a,disabled:i}=e,l=o.droppable.containers.get(n);if(!l||a!==l.key)return o;const c=new Ao(o.droppable.containers);return c.set(n,{...l,disabled:i}),{...o,droppable:{...o.droppable,containers:c}}}case tn.UnregisterDroppable:{const{id:n,key:a}=e,i=o.droppable.containers.get(n);if(!i||a!==i.key)return o;const l=new Ao(o.droppable.containers);return l.delete(n),{...o,droppable:{...o.droppable,containers:l}}}default:return o}}function qE(o){let{disabled:e}=o;const{active:n,activatorEvent:a,draggableNodes:i}=N.useContext(uc),l=Wd(a),c=Wd(n?.id);return N.useEffect(()=>{if(!e&&!a&&l&&c!=null){if(!Xd(l)||document.activeElement===l.target)return;const d=i.get(c);if(!d)return;const{activatorNode:f,node:g}=d;if(!f.current&&!g.current)return;requestAnimationFrame(()=>{for(const m of[f.current,g.current]){if(!m)continue;const b=NC(m);if(b){b.focus();break}}})}},[a,e,i,c,l]),null}function GE(o,e){let{transform:n,...a}=e;return o!=null&&o.length?o.reduce((i,l)=>l({transform:i,...a}),n):n}function VE(o){return N.useMemo(()=>({draggable:{...cc.draggable,...o?.draggable},droppable:{...cc.droppable,...o?.droppable},dragOverlay:{...cc.dragOverlay,...o?.dragOverlay}}),[o?.draggable,o?.droppable,o?.dragOverlay])}function FE(o){let{activeNode:e,measure:n,initialRect:a,config:i=!0}=o;const l=N.useRef(!1),{x:c,y:d}=typeof i=="boolean"?{x:i,y:i}:i;zr(()=>{if(!c&&!d||!e){l.current=!1;return}if(l.current||!a)return;const g=e?.node.current;if(!g||g.isConnected===!1)return;const m=n(g),b=Rm(m,a);if(c||(b.x=0),d||(b.y=0),l.current=!0,Math.abs(b.x)>0||Math.abs(b.y)>0){const S=Dm(g);S&&S.scrollBy({top:b.y,left:b.x})}},[e,c,d,a,n])}const Wm=N.createContext({...Sr,scaleX:1,scaleY:1});var Da;(function(o){o[o.Uninitialized=0]="Uninitialized",o[o.Initializing=1]="Initializing",o[o.Initialized=2]="Initialized"})(Da||(Da={}));const KE=N.memo(function(e){var n,a,i,l;let{id:c,accessibility:d,autoScroll:f=!0,children:g,sensors:m=HE,collisionDetection:b=XC,measuring:S,modifiers:v,..._}=e;const A=N.useReducer(PE,void 0,BE),[x,L]=A,[M,R]=BC(),[D,B]=N.useState(Da.Uninitialized),q=D===Da.Initialized,{draggable:{active:P,nodes:K,translate:ge},droppable:{containers:Ee}}=x,Se=P!=null?K.get(P):null,Ue=N.useRef({initial:null,translated:null}),Ve=N.useMemo(()=>{var he;return P!=null?{id:P,data:(he=Se?.data)!=null?he:zE,rect:Ue}:null},[P,Se]),qe=N.useRef(null),[St,J]=N.useState(null),[fe,Le]=N.useState(null),Z=yo(_,Object.values(_)),pe=vo("DndDescribedBy",c),H=N.useMemo(()=>Ee.getEnabled(),[Ee]),T=VE(S),{droppableRects:ce,measureDroppableContainers:de,measuringScheduled:ye}=AE(H,{dragging:q,dependencies:[ge.x,ge.y],config:T.droppable}),ie=EE(K,P),xe=N.useMemo(()=>fe?Jd(fe):null,[fe]),_e=ve(),Te=xE(ie,T.draggable.measure);FE({activeNode:P!=null?K.get(P):null,config:_e.layoutShiftCompensation,initialRect:Te,measure:T.draggable.measure});const $e=Pm(ie,T.draggable.measure,Te),ht=Pm(ie?ie.parentElement:null),wt=N.useRef({activatorEvent:null,active:null,activeNode:ie,collisionRect:null,collisions:null,droppableRects:ce,draggableNodes:K,draggingNode:null,draggingNodeRect:null,droppableContainers:Ee,over:null,scrollableAncestors:[],scrollAdjustedTranslate:null}),kt=Ee.getNodeFor((n=wt.current.over)==null?void 0:n.id),ft=IE({measure:T.dragOverlay.measure}),nn=(a=ft.nodeRef.current)!=null?a:ie,Ft=q?(i=ft.rect)!=null?i:$e:null,Nt=!!(ft.nodeRef.current&&ft.rect),Br=kE(Nt?null:$e),fn=Vm(nn?Ln(nn):null),rn=DE(q?kt??ie:null),hn=$E(rn),Fn=GE(v,{transform:{x:ge.x-Br.x,y:ge.y-Br.y,scaleX:1,scaleY:1},activatorEvent:fe,active:Ve,activeNodeRect:$e,containerNodeRect:ht,draggingNodeRect:Ft,over:wt.current.over,overlayNodeRect:ft.rect,scrollableAncestors:rn,scrollableAncestorRects:hn,windowRect:fn}),Pr=xe?us(xe,ge):null,Pt=OE(rn),wi=Gm(Pt),na=Gm(Pt,[$e]),gn=us(Fn,wi),Rn=Ft?ZC(Ft,Fn):null,tt=Ve&&Rn?b({active:Ve,collisionRect:Rn,droppableRects:ce,droppableContainers:H,pointerCoordinates:Pr}):null,$n=xm(tt,"id"),[pn,ra]=N.useState(null),ir=Nt?Fn:us(Fn,na),an=JC(ir,(l=pn?.rect)!=null?l:null,$e),En=N.useRef(null),sn=N.useCallback((he,Ce)=>{let{sensor:Ae,options:Fe}=Ce;if(qe.current==null)return;const Re=K.get(qe.current);if(!Re)return;const Ne=he.nativeEvent,ke=new Ae({active:qe.current,activeNode:Re,event:Ne,options:Fe,context:wt,onAbort(ze){if(!K.get(ze))return;const{onDragAbort:we}=Z.current,He={id:ze};we?.(He),M({type:"onDragAbort",event:He})},onPending(ze,Ge,we,He){if(!K.get(ze))return;const{onDragPending:_t}=Z.current,Lt={id:ze,constraint:Ge,initialCoordinates:we,offset:He};_t?.(Lt),M({type:"onDragPending",event:Lt})},onStart(ze){const Ge=qe.current;if(Ge==null)return;const we=K.get(Ge);if(!we)return;const{onDragStart:He}=Z.current,et={activatorEvent:Ne,active:{id:Ge,data:we.data,rect:Ue}};ka.unstable_batchedUpdates(()=>{He?.(et),B(Da.Initializing),L({type:tn.DragStart,initialCoordinates:ze,active:Ge}),M({type:"onDragStart",event:et}),J(En.current),Le(Ne)})},onMove(ze){L({type:tn.DragMove,coordinates:ze})},onEnd:Ze(tn.DragEnd),onCancel:Ze(tn.DragCancel)});En.current=ke;function Ze(ze){return async function(){const{active:we,collisions:He,over:et,scrollAdjustedTranslate:_t}=wt.current;let Lt=null;if(we&&_t){const{cancelDrop:kn}=Z.current;Lt={activatorEvent:Ne,active:we,collisions:He,delta:_t,over:et},ze===tn.DragEnd&&typeof kn=="function"&&await Promise.resolve(kn(Lt))&&(ze=tn.DragCancel)}qe.current=null,ka.unstable_batchedUpdates(()=>{L({type:ze}),B(Da.Uninitialized),ra(null),J(null),Le(null),En.current=null;const kn=ze===tn.DragEnd?"onDragEnd":"onDragCancel";if(Lt){const It=Z.current[kn];It?.(Lt),M({type:kn,event:Lt})}})}}},[K]),V=N.useCallback((he,Ce)=>(Ae,Fe)=>{const Re=Ae.nativeEvent,Ne=K.get(Fe);if(qe.current!==null||!Ne||Re.dndKit||Re.defaultPrevented)return;const ke={active:Ne};he(Ae,Ce.options,ke)===!0&&(Re.dndKit={capturedBy:Ce.sensor},qe.current=Fe,sn(Ae,Ce))},[K,sn]),se=_E(m,V);LE(m),zr(()=>{$e&&D===Da.Initializing&&B(Da.Initialized)},[$e,D]),N.useEffect(()=>{const{onDragMove:he}=Z.current,{active:Ce,activatorEvent:Ae,collisions:Fe,over:Re}=wt.current;if(!Ce||!Ae)return;const Ne={active:Ce,activatorEvent:Ae,collisions:Fe,delta:{x:gn.x,y:gn.y},over:Re};ka.unstable_batchedUpdates(()=>{he?.(Ne),M({type:"onDragMove",event:Ne})})},[gn.x,gn.y]),N.useEffect(()=>{const{active:he,activatorEvent:Ce,collisions:Ae,droppableContainers:Fe,scrollAdjustedTranslate:Re}=wt.current;if(!he||qe.current==null||!Ce||!Re)return;const{onDragOver:Ne}=Z.current,ke=Fe.get($n),Ze=ke&&ke.rect.current?{id:ke.id,rect:ke.rect.current,data:ke.data,disabled:ke.disabled}:null,ze={active:he,activatorEvent:Ce,collisions:Ae,delta:{x:Re.x,y:Re.y},over:Ze};ka.unstable_batchedUpdates(()=>{ra(Ze),Ne?.(ze),M({type:"onDragOver",event:ze})})},[$n]),zr(()=>{wt.current={activatorEvent:fe,active:Ve,activeNode:ie,collisionRect:Rn,collisions:tt,droppableRects:ce,draggableNodes:K,draggingNode:nn,draggingNodeRect:Ft,droppableContainers:Ee,over:pn,scrollableAncestors:rn,scrollAdjustedTranslate:gn},Ue.current={initial:Ft,translated:Rn}},[Ve,ie,tt,Rn,K,nn,Ft,ce,Ee,pn,rn,gn]),SE({..._e,delta:ge,draggingRect:Rn,pointerCoordinates:Pr,scrollableAncestors:rn,scrollableAncestorRects:hn});const ue=N.useMemo(()=>({active:Ve,activeNode:ie,activeNodeRect:$e,activatorEvent:fe,collisions:tt,containerNodeRect:ht,dragOverlay:ft,draggableNodes:K,droppableContainers:Ee,droppableRects:ce,over:pn,measureDroppableContainers:de,scrollableAncestors:rn,scrollableAncestorRects:hn,measuringConfiguration:T,measuringScheduled:ye,windowRect:fn}),[Ve,ie,$e,fe,tt,ht,ft,K,Ee,ce,pn,de,rn,hn,T,ye,fn]),ee=N.useMemo(()=>({activatorEvent:fe,activators:se,active:Ve,activeNodeRect:$e,ariaDescribedById:{draggable:pe},dispatch:L,draggableNodes:K,over:pn,measureDroppableContainers:de}),[fe,se,Ve,$e,L,pe,K,pn,de]);return Ye.createElement(wm.Provider,{value:R},Ye.createElement(uc.Provider,{value:ee},Ye.createElement(Km.Provider,{value:ue},Ye.createElement(Wm.Provider,{value:an},g)),Ye.createElement(qE,{disabled:d?.restoreFocus===!1})),Ye.createElement(GC,{...d,hiddenTextDescribedById:pe}));function ve(){const he=St?.autoScrollEnabled===!1,Ce=typeof f=="object"?f.enabled===!1:f===!1,Ae=q&&!he&&!Ce;return typeof f=="object"?{...f,enabled:Ae}:{enabled:Ae}}}),WE=N.createContext(null),Ym="button",YE="Draggable";function XE(o){let{id:e,data:n,disabled:a=!1,attributes:i}=o;const l=vo(YE),{activators:c,activatorEvent:d,active:f,activeNodeRect:g,ariaDescribedById:m,draggableNodes:b,over:S}=N.useContext(uc),{role:v=Ym,roleDescription:_="draggable",tabIndex:A=0}=i??{},x=f?.id===e,L=N.useContext(x?Wm:WE),[M,R]=ac(),[D,B]=ac(),q=ME(c,e),P=yo(n);zr(()=>(b.set(e,{id:e,key:l,node:M,activatorNode:D,data:P}),()=>{const ge=b.get(e);ge&&ge.key===l&&b.delete(e)}),[b,e]);const K=N.useMemo(()=>({role:v,tabIndex:A,"aria-disabled":a,"aria-pressed":x&&v===Ym?!0:void 0,"aria-roledescription":_,"aria-describedby":m.draggable}),[a,v,A,x,_,m.draggable]);return{active:f,activatorEvent:d,activeNodeRect:g,attributes:K,isDragging:x,listeners:a?void 0:q,node:M,over:S,setNodeRef:R,setActivatorNodeRef:B,transform:L}}function JE(){return N.useContext(Km)}const QE="Droppable",ZE={timeout:25};function e_(o){let{data:e,disabled:n=!1,id:a,resizeObserverConfig:i}=o;const l=vo(QE),{active:c,dispatch:d,over:f,measureDroppableContainers:g}=N.useContext(uc),m=N.useRef({disabled:n}),b=N.useRef(!1),S=N.useRef(null),v=N.useRef(null),{disabled:_,updateMeasurementsFor:A,timeout:x}={...ZE,...i},L=yo(A??a),M=N.useCallback(()=>{if(!b.current){b.current=!0;return}v.current!=null&&clearTimeout(v.current),v.current=setTimeout(()=>{g(Array.isArray(L.current)?L.current:[L.current]),v.current=null},x)},[x]),R=lc({callback:M,disabled:_||!c}),D=N.useCallback((K,ge)=>{R&&(ge&&(R.unobserve(ge),b.current=!1),K&&R.observe(K))},[R]),[B,q]=ac(D),P=yo(e);return N.useEffect(()=>{!R||!B.current||(R.disconnect(),b.current=!1,R.observe(B.current))},[B,R]),N.useEffect(()=>(d({type:tn.RegisterDroppable,element:{id:a,key:l,disabled:n,node:B,rect:S,data:P}}),()=>d({type:tn.UnregisterDroppable,key:l,id:a})),[a]),N.useEffect(()=>{n!==m.current.disabled&&(d({type:tn.SetDroppableDisabled,id:a,key:l,disabled:n}),m.current.disabled=n)},[a,l,n,d]),{active:c,rect:S,isOver:f?.id===a,node:B,over:f,setNodeRef:q}}function cf(o,e,n){const a=o.slice();return a.splice(n<0?a.length+n:n,0,a.splice(e,1)[0]),a}function t_(o,e){return o.reduce((n,a,i)=>{const l=e.get(a);return l&&(n[i]=l),n},Array(o.length))}function dc(o){return o!==null&&o>=0}function n_(o,e){if(o===e)return!0;if(o.length!==e.length)return!1;for(let n=0;n{var e;let{rects:n,activeNodeRect:a,activeIndex:i,overIndex:l,index:c}=o;const d=(e=n[i])!=null?e:a;if(!d)return null;const f=i_(n,c,i);if(c===i){const g=n[l];return g?{x:ii&&c<=l?{x:-d.width-f,y:0,...fc}:c=l?{x:d.width+f,y:0,...fc}:{x:0,y:0,...fc}};function i_(o,e,n){const a=o[e],i=o[e-1],l=o[e+1];return!a||!i&&!l?0:n{let{rects:e,activeIndex:n,overIndex:a,index:i}=o;const l=cf(e,a,n),c=e[i],d=l[i];return!d||!c?null:{x:d.left-c.left,y:d.top-c.top,scaleX:d.width/c.width,scaleY:d.height/c.height}},hc={scaleX:1,scaleY:1},s_=o=>{var e;let{activeIndex:n,activeNodeRect:a,index:i,rects:l,overIndex:c}=o;const d=(e=l[n])!=null?e:a;if(!d)return null;if(i===n){const g=l[c];return g?{x:0,y:nn&&i<=c?{x:0,y:-d.height-f,...hc}:i=c?{x:0,y:d.height+f,...hc}:{x:0,y:0,...hc}};function o_(o,e,n){const a=o[e],i=o[e-1],l=o[e+1];return a?na.map(q=>typeof q=="object"&&"id"in q?q.id:q),[a]),_=c!=null,A=c?v.indexOf(c.id):-1,x=g?v.indexOf(g.id):-1,L=N.useRef(v),M=!n_(v,L.current),R=x!==-1&&A===-1||M,D=r_(l);zr(()=>{M&&_&&m(v)},[M,v,_,m]),N.useEffect(()=>{L.current=v},[v]);const B=N.useMemo(()=>({activeIndex:A,containerId:b,disabled:D,disableTransforms:R,items:v,overIndex:x,useDragOverlay:S,sortedRects:t_(v,f),strategy:i}),[A,b,D.draggable,D.droppable,R,v,x,f,S,i]);return Ye.createElement(Jm.Provider,{value:B},e)}const c_=o=>{let{id:e,items:n,activeIndex:a,overIndex:i}=o;return cf(n,a,i).indexOf(e)},u_=o=>{let{containerId:e,isSorting:n,wasDragging:a,index:i,items:l,newIndex:c,previousItems:d,previousContainerId:f,transition:g}=o;return!g||!a||d!==l&&i===c?!1:n?!0:c!==i&&e===f},d_={duration:200,easing:"ease"},Qm="transform",f_=wo.Transition.toString({property:Qm,duration:0,easing:"linear"}),h_={roleDescription:"sortable"};function g_(o){let{disabled:e,index:n,node:a,rect:i}=o;const[l,c]=N.useState(null),d=N.useRef(n);return zr(()=>{if(!e&&n!==d.current&&a.current){const f=i.current;if(f){const g=ds(a.current,{ignoreTransform:!0}),m={x:f.left-g.left,y:f.top-g.top,scaleX:f.width/g.width,scaleY:f.height/g.height};(m.x||m.y)&&c(m)}}n!==d.current&&(d.current=n)},[e,n,a,i]),N.useEffect(()=>{l&&c(null)},[l]),l}function p_(o){let{animateLayoutChanges:e=u_,attributes:n,disabled:a,data:i,getNewIndex:l=c_,id:c,strategy:d,resizeObserverConfig:f,transition:g=d_}=o;const{items:m,containerId:b,activeIndex:S,disabled:v,disableTransforms:_,sortedRects:A,overIndex:x,useDragOverlay:L,strategy:M}=N.useContext(Jm),R=m_(a,v),D=m.indexOf(c),B=N.useMemo(()=>({sortable:{containerId:b,index:D,items:m},...i}),[b,i,D,m]),q=N.useMemo(()=>m.slice(m.indexOf(c)),[m,c]),{rect:P,node:K,isOver:ge,setNodeRef:Ee}=e_({id:c,data:B,disabled:R.droppable,resizeObserverConfig:{updateMeasurementsFor:q,...f}}),{active:Se,activatorEvent:Ue,activeNodeRect:Ve,attributes:qe,setNodeRef:St,listeners:J,isDragging:fe,over:Le,setActivatorNodeRef:Z,transform:pe}=XE({id:c,data:B,attributes:{...h_,...n},disabled:R.draggable}),H=OC(Ee,St),T=!!Se,ce=T&&!_&&dc(S)&&dc(x),de=!L&&fe,ye=de&&ce?pe:null,xe=ce?ye??(d??M)({rects:A,activeNodeRect:Ve,activeIndex:S,overIndex:x,index:D}):null,_e=dc(S)&&dc(x)?l({id:c,items:m,activeIndex:S,overIndex:x}):D,Te=Se?.id,$e=N.useRef({activeId:Te,items:m,newIndex:_e,containerId:b}),ht=m!==$e.current.items,wt=e({active:Se,containerId:b,isDragging:fe,isSorting:T,id:c,index:D,items:m,newIndex:$e.current.newIndex,previousItems:$e.current.items,previousContainerId:$e.current.containerId,transition:g,wasDragging:$e.current.activeId!=null}),kt=g_({disabled:!wt,index:D,node:K,rect:P});return N.useEffect(()=>{T&&$e.current.newIndex!==_e&&($e.current.newIndex=_e),b!==$e.current.containerId&&($e.current.containerId=b),m!==$e.current.items&&($e.current.items=m)},[T,_e,b,m]),N.useEffect(()=>{if(Te===$e.current.activeId)return;if(Te!=null&&$e.current.activeId==null){$e.current.activeId=Te;return}const nn=setTimeout(()=>{$e.current.activeId=Te},50);return()=>clearTimeout(nn)},[Te]),{active:Se,activeIndex:S,attributes:qe,data:B,rect:P,index:D,newIndex:_e,items:m,isOver:ge,isSorting:T,isDragging:fe,listeners:J,node:K,overIndex:x,over:Le,setNodeRef:H,setActivatorNodeRef:Z,setDroppableNodeRef:Ee,setDraggableNodeRef:St,transform:kt??xe,transition:ft()};function ft(){if(kt||ht&&$e.current.newIndex===D)return f_;if(!(de&&!Xd(Ue)||!g)&&(T||wt))return wo.Transition.toString({...g,property:Qm})}}function m_(o,e){var n,a;return typeof o=="boolean"?{draggable:o,droppable:!1}:{draggable:(n=o?.draggable)!=null?n:e.draggable,droppable:(a=o?.droppable)!=null?a:e.droppable}}function gc(o){if(!o)return!1;const e=o.data.current;return!!(e&&"sortable"in e&&typeof e.sortable=="object"&&"containerId"in e.sortable&&"items"in e.sortable&&"index"in e.sortable)}const y_=[ot.Down,ot.Right,ot.Up,ot.Left],b_=(o,e)=>{let{context:{active:n,collisionRect:a,droppableRects:i,droppableContainers:l,over:c,scrollableAncestors:d}}=e;if(y_.includes(o.code)){if(o.preventDefault(),!n||!a)return;const f=[];l.getEnabled().forEach(b=>{if(!b||b!=null&&b.disabled)return;const S=i.get(b.id);if(S)switch(o.code){case ot.Down:a.topS.top&&f.push(b);break;case ot.Left:a.left>S.left&&f.push(b);break;case ot.Right:a.left1&&(m=g[1].id),m!=null){const b=l.get(n.id),S=l.get(m),v=S?i.get(S.id):null,_=S?.node.current;if(_&&v&&b&&S){const x=sc(_).some((q,P)=>d[P]!==q),L=Zm(b,S),M=v_(b,S),R=x||!L?{x:0,y:0}:{x:M?a.width-v.width:0,y:M?a.height-v.height:0},D={x:v.left,y:v.top};return R.x&&R.y?D:So(D,R)}}}};function Zm(o,e){return!gc(o)||!gc(e)?!1:o.data.current.sortable.containerId===e.data.current.sortable.containerId}function v_(o,e){return!gc(o)||!gc(e)||!Zm(o,e)?!1:o.data.current.sortable.index{const d=N.useId(),f=N.useRef({inProgress:!1,lastSortTime:0,lastActiveId:null}),g=VC(Cm(af,{activationConstraint:{distance:5}}),Cm(nf,{coordinateGetter:b_})),m=N.useCallback(v=>{f.current={inProgress:!0,lastSortTime:Date.now(),lastActiveId:v.active.id},l?.({activeId:v.active.id})},[l]),b=N.useCallback(v=>{const{active:_,over:A}=v,x=Date.now();if(!A||_.id===A.id){f.current.inProgress=!1;return}const{lastSortTime:L,lastActiveId:M,inProgress:R}=f.current;if(M===_.id&&x-L<500&&!R)return;f.current={inProgress:!1,lastSortTime:x,lastActiveId:_.id};const D=o.findIndex(q=>String(q[e])===String(_.id)),B=o.findIndex(q=>String(q[e])===String(A.id));if(D!==-1&&B!==-1&&D!==B){const q=cf(o,D,B);i?.(q,{oldIndex:D,newIndex:B})}},[o,e,i]),S=o.map(v=>String(v[e]));return dt.jsx(KE,{id:d,sensors:g,collisionDetection:KC,onDragStart:m,onDragEnd:b,children:dt.jsx(l_,{items:S,strategy:S_[n],children:a})},c!==void 0?`${d}-v${c}`:d)},ey=N.createContext(null),C_=({value:o,children:e})=>dt.jsx(ey.Provider,{value:o,children:e}),E_=()=>N.useContext(ey),__=({id:o,handle:e,as:n="div",children:a})=>{const{attributes:i,listeners:l,setNodeRef:c,transform:d,transition:f,isDragging:g}=p_({id:o}),m={transform:wo.Transform.toString(d),transition:f,opacity:g?.5:1,position:"relative"};return dt.jsx(n,{ref:c,style:m,...i,...e?{}:l,"data-sortable-item":!0,"data-sortable-id":String(o),"data-dragging":g,children:dt.jsx(C_,{value:{listeners:l,handle:e,isDragging:g},children:a})})},at=ut("DynamicRenderer");function Zr(){try{return window.G7Core?.devTools}catch{return}}function df(o,e,n,a){if(typeof o=="string"){if(/\$t:[a-zA-Z_][a-zA-Z0-9_.\-]*/.test(o))try{return e.resolveTranslations(o,n,a)}catch{return o}return o}if(Array.isArray(o))return o.map(i=>df(i,e,n,a));if(o&&typeof o=="object"){const i={};for(const[l,c]of Object.entries(o))i[l]=df(c,e,n,a);return i}return o}function A_(o){const e=Object.keys(o);return e.length>0&&e.every(n=>/^\d+$/.test(n))}const wr=(o,e)=>{const n=["errors"],a={...o};for(const i of Object.keys(e)){const l=e[i],c=o[i];if(l===null){a[i]=null;continue}if(n.includes(i)){a[i]=l;continue}if(Array.isArray(l)){a[i]=l;continue}if(Array.isArray(c)&&l!==null&&typeof l=="object"&&!Array.isArray(l)&&A_(l)){const d=Object.keys(l).map(g=>parseInt(g,10));if((d.length>0?Math.max(...d):0)>=c.length+d.length+10)a[i]={...l};else{const g=[...c];for(const[m,b]of Object.entries(l)){const S=parseInt(m,10);S>=0&&S=g.length&&(g[S]=b)}a[i]=g}}else l!==null&&typeof l=="object"&&c!==null&&typeof c=="object"&&!Array.isArray(c)?a[i]=wr(c,l):a[i]=l}return a},pc=(o,e)=>{let n=null;const a=()=>(n===null&&(n={...o}),n);for(const i of Object.keys(e)){if(!(i in o))continue;const l=e[i],c=o[i];if(l!==null&&typeof l=="object"&&!Array.isArray(l)&&c!==null&&typeof c=="object"&&!Array.isArray(c)){const d=pc(c,l);if(d===c)continue;Object.keys(d).length===0?delete a()[i]:a()[i]=d}else delete a()[i]}return n??o},x_=o=>{const e={},n=o.split(";").filter(a=>a.trim());for(const a of n){const i=a.indexOf(":");if(i===-1)continue;const l=a.substring(0,i).trim(),c=a.substring(i+1).trim();if(l&&c){const d=l.replace(/-([a-z])/g,(f,g)=>g.toUpperCase());e[d]=c}}return e};class T_ extends N.Component{constructor(e){super(e),this.state={hasError:!1,error:null}}static getDerivedStateFromError(e){return{hasError:!0,error:e}}componentDidCatch(e,n){at.error(`컴포넌트 렌더링 에러 (ID: ${this.props.componentId}, Name: ${this.props.componentName}):`,e,n)}render(){return this.state.hasError?this.props.fallback?this.props.fallback:dt.jsxs("div",{className:"p-4 my-2 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg text-red-800 dark:text-red-400",children:[dt.jsx("div",{className:"font-semibold mb-1",children:"컴포넌트 로드 실패"}),dt.jsxs("div",{className:"text-xs opacity-80",children:[this.props.componentName&&`[${this.props.componentName}] `,"데이터를 표시할 수 없습니다."]}),this.state.error&&dt.jsxs("details",{className:"mt-2",children:[dt.jsx("summary",{className:"cursor-pointer text-xs opacity-70 hover:opacity-100",children:"상세 정보"}),dt.jsx("div",{className:"mt-1 p-2 bg-red-100 dark:bg-red-900/30 rounded text-xs font-mono whitespace-pre-wrap break-all",children:this.state.error.message})]})]}):this.props.children}}const jr=N.memo(({componentDef:o,dataContext:e,translationContext:n,registry:a,bindingEngine:i,translationEngine:l,actionDispatcher:c,parentComponentContext:d,parentFormContextProp:f,parentDataContext:g,isRootRenderer:m=!1,isInsideIteration:b=!1,isEditMode:S=!1,onComponentSelect:v,onComponentHover:_,onDragStart:A,onDragEnd:x,componentPath:L,layoutKey:M})=>{const[R,D]=N.useState({loadingActions:{}});N.useLayoutEffect(()=>{d||(i.startRenderCycle(),window.__g7SetLocalOverrideKeys||(window.__g7ForcedLocalFields=void 0),window.__g7PendingLocalState=null)});const B=N.useRef(null);N.useLayoutEffect(()=>{if(!d&&e._localInit&&typeof e._localInit=="object"){const{_forceLocalInit:V,_merge:se,...ue}=e._localInit,ee=`${JSON.stringify(ue)}:${V||"no-force"}`;if(B.current!==ee){B.current=ee;const ve=se||"shallow",Ce=window.G7Core?.state?.get?.()?._local||{},Ae=window.__g7PendingLocalState||{},Fe={...Ce,...Ae};ve==="replace"?window.__g7PendingLocalState={loadingActions:Fe.loadingActions||{},...ue,hasChanges:!1}:ve==="deep"?window.__g7PendingLocalState=wr(Fe,{...ue,hasChanges:!1}):window.__g7PendingLocalState={...Fe,...ue,hasChanges:!1};const Re=window.__g7SetLocalOverrideKeys;Re&&typeof Re=="object"&&(ve==="replace"?window.__g7SetLocalOverrideKeys={...ue,hasChanges:!1}:window.__g7SetLocalOverrideKeys=wr(Re,{...ue,hasChanges:!1}))}}}),N.useLayoutEffect(()=>{if(!d){const V=window.__g7SetLocalOverrideKeys;if(V){D(ve=>pc(ve,V));const se=V,ue=window.__g7ForcedLocalFields,ee=window.__g7LastSetLocalSnapshot;queueMicrotask(()=>{window.__g7SetLocalOverrideKeys===se&&(window.__g7SetLocalOverrideKeys=void 0),window.__g7ForcedLocalFields===ue&&(window.__g7ForcedLocalFields=void 0),window.__g7LastSetLocalSnapshot===ee&&(window.__g7LastSetLocalSnapshot=void 0)})}}},[e._local]);const q=N.useRef(M);N.useEffect(()=>{o._fromBase&&M&&q.current!==M&&(q.current=M,D({loadingActions:{}}))},[M,o._fromBase]);const P=N.useRef(""),K=N.useRef(!1),ge=N.useRef(null),Ee=N.useRef(null),Se=N.useRef({}),Ue=N.useRef({}),Ve=N.useRef(g),qe=DC(),St=sC(),J=St?.getParentDataContext()??g;N.useEffect(()=>{if(e._localInit&&typeof e._localInit=="object"){const{_forceLocalInit:V,...se}=e._localInit,ue=JSON.stringify(se),ee=V!==void 0,ve=Pd(),he=`${ue}:${V||"no-force"}`,Ce=gC({globalTrackedKey:ve.hash,instanceHandledKey:Ee.current,trackingKey:he});if(Ce==="apply"){Ee.current=he,ve.hash=he,ve.timestamp=V,P.current=ue,ee&&(K.current=!0);const{_merge:Fe,...Re}=se,Ne=Fe||"shallow";D(ke=>Ne==="replace"?{loadingActions:ke.loadingActions||{},...Re,hasChanges:!1}:Ne==="deep"?wr(ke,{...Re,hasChanges:!1}):{...ke,...Re,hasChanges:!1}),e._globalSetState&&e._globalSetState(ke=>{const Ze=ke?._local||{};let ze;return Ne==="replace"?ze={...Re,hasChanges:!1}:Ne==="deep"?ze=wr(Ze,{...Re,hasChanges:!1}):ze={...Ze,...Re,hasChanges:!1},{...ke,_local:ze}}),i.invalidateCacheByKeys(["_local"]),at.log("_localInit applied (data changed):",Object.keys(se))}else if(Ce==="prune"){Ee.current=he;const{_merge:Fe,...Re}=se;D(ke=>pc(ke,Re)),pc(R,Re)!==R&&(i.invalidateCacheByKeys(["_local"]),at.log("_localInit pruned (applied by another renderer):",Object.keys(Re)))}ge.current=e._localInit,fC(e._localInit)}},[e._localInit,g,i]);const{isTransitioning:fe}=fm(),Le=wC(),Z=f??Le,{width:pe}=Gd(),H=E_(),T=N.useMemo(()=>{let V=o;if(o.type==="iterator"&&o.data){const{data:ee,itemName:ve,indexName:he,...Ce}=o;V={...Ce,iteration:{source:ee,item_var:ve||"item",index_var:he}}}if(!V.responsive)return V;const se=hi.getMatchingKey(V.responsive,pe);if(!se)return V;const ue=V.responsive[se];return{...V,props:{...V.props,...ue.props},children:ue.children??V.children,text:ue.text??V.text,if:ue.if??V.if,iteration:ue.iteration??V.iteration,__responsiveChildrenKey:ue.children!==void 0?se:void 0}},[o,pe]),ce=N.useCallback(V=>{const se=V?.__setStateId;if(D(ue=>{const ee=window.__g7PendingLocalState,he=window.__g7SetLocalOverrideKeys||ee,Ce=he?wr(ue,he):ue;if(typeof V=="function"){const ke=V(Ce);return wr(Ce,ke)}const{__mergeMode:Ae,__setStateId:Fe,...Re}=V;let Ne;if(Ae==="replace"?Ne=Re:Ae==="shallow"?Ne={...Ce,...Re}:Ne=wr(Ce,Re),Ae!==void 0){const ke=window.__g7ForcedLocalFields;ke&&(window.__g7ForcedLocalFields={...ke,...Re})}return Ne}),se){const ue=Zr();if(ue?.isEnabled()){const ee=T.id||"unknown",ve=T.name||"Unknown";setTimeout(()=>{ue.trackComponentRender?.(ee,ve,0,["_local"],[]),ue.completeStateChange?.(se)},0)}}},[T.id,T.name]),de=d?.state??R,ye=d?.setState??ce,ie=N.useMemo(()=>{const V=e._localInit&&e._localInit!==ge.current;let se;V&&e._local?se=e._local:se=e._local?wr(e._local,de):de;const ue=window.__g7ForcedLocalFields;ue&&(se=wr(se,ue));const ee={...e,_local:se},ve=e._computedDefinitions;if(ve&&Object.keys(ve).length>0){const he=new xn,Ce={...ee,...qe?{_isolated:qe.state}:{}},Ae={},Fe=Zr(),Re=Fe?.isEnabled()??!1;for(const[Ne,ke]of Object.entries(ve)){const Ze=Re?Date.now():0,ze=Re?e._computed?.[Ne]:void 0;let Ge;try{if(typeof ke=="string")if(ke.startsWith("{{")&&ke.endsWith("}}")){const we=ke.slice(2,-2).trim(),He=Pn(we)?he.evaluatePipeExpression(we,Ce,{skipCache:!0}):he.evaluateExpression(we,Ce,{skipCache:!0});Ae[Ne]=He}else Ae[Ne]=ke;else if(ke&&typeof ke=="object"&&"$switch"in ke){const we=he.resolveSwitch(ke,Ce,{skipCache:!0});Ae[Ne]=we}}catch(we){at.warn(`Failed to recalculate computed value: ${Ne}`,we),Ae[Ne]=e._computed?.[Ne],Ge=we instanceof Error?we.message:String(we)}if(Re&&(Ge||ze!==Ae[Ne])){const He=Date.now()-Ze;Fe.trackComputedProperty?.(Ne,typeof ke=="string"?ke:JSON.stringify(ke),[{type:"_local",path:"_local",value:ee._local}],Ae[Ne],He,T.id,Ge),ze!==Ae[Ne]&&Fe.trackComputedRecalc?.(Ne,"dependency-change",ze,Ae[Ne],He,{type:"_local",path:"_local"},T.id)}}ee._computed=Ae}if(qe&&(ee._isolated=qe.state),J&&(ee.$parent=J),o.extensionPointProps){const he=i.resolveObject(o.extensionPointProps,ee,b?{skipCache:!0}:void 0);ee.extensionPointProps=S&&l?df(he,l,n,ee):he}return o.extensionPointCallbacks&&(ee.extensionPointCallbacks=o.extensionPointCallbacks),S&&(ee.$templateId===void 0&&n?.templateId&&(ee.$templateId=n.templateId),ee.$locale===void 0&&n?.locale&&(ee.$locale=n.locale)),ee},[e,de,qe?.state,J,St?.version,o.extensionPointProps,o.extensionPointCallbacks]);Se.current=ie._local,Ue.current=ie._computed||{},Ve.current=J,N.useEffect(()=>{const V=Zr();if(!V?.isEnabled())return;const se=ie._local;se&&V.updateLocalState(se);const ue=ie._computed;ue&&V.updateComputedState(ue),J&&V.updateParentContext?.(J);const ee=T.id||`comp-${Date.now()}`,ve=T.name||"Unknown",he={global:Object.keys(e._global||{}),local:Object.keys(se||{}),context:d?Object.keys(d.state||{}):[]},Ce={type:d?"componentContext":"dynamicState",hasComponentContext:!!d,parentId:d?"parent":void 0};V.trackComponentStateSource?.(ee,ve,he,Ce),de&&Object.keys(de).length>0&&V.trackDynamicState?.(ee,de);const Ae=!!(T.expandChildren||T.props?.expandChildren);V.trackContextFlow?.(ee,ve,!!d,Ae,!0)},[ie._local,e,T.id,T.name,d,de,T.expandChildren,T.props?.expandChildren,J]);const xe=N.useMemo(()=>({state:{...ie._local},setState:ye,stateRef:Se,computedRef:Ue,isolatedContext:qe,parentDataContext:ie}),[ie._local,ye,R,qe,ie]),_e=N.useMemo(()=>{const V=ts({if:T.if,condition:T.condition,conditions:T.conditions},ie,i,T.id);if(T.if||T.condition!==void 0||T.conditions){const ue=Zr();ue?.isEnabled()&&T.if&&ue.trackIfCondition(T.id||`if-${Date.now()}`,T.if,V)}return V},[T.if,T.conditions,ie,i,T.id]),Te=N.useMemo(()=>{if(!T.slot)return null;const V=T.slot;if(V.startsWith("{{")&&V.endsWith("}}")){const se=V.slice(2,-2).trim();try{const ue={componentId:T.id,componentName:T.name,propName:"slot"},ee=Pn(se)?i.evaluatePipeExpression(se,ie,void 0,ue):i.evaluateExpression(se,ie,ue);return at.log(`[Slot] 표현식 평가: "${se}" => "${ee}" (컴포넌트: ${T.id})`),typeof ee=="string"?ee:null}catch(ue){return at.warn(`slot 표현식 평가 실패 (컴포넌트: ${T.id}):`,ue),null}}return at.log(`[Slot] 정적 슬롯 ID: "${V}" (컴포넌트: ${T.id})`),V},[T.slot,T.id,ie,i]),$e=N.useMemo(()=>!Te||!T.id?"":`${T.id}-${Te}-${JSON.stringify(T.slotOrder??0)}`,[Te,T.id,T.slotOrder]),ht=N.useRef(null);N.useEffect(()=>{const V=window.__slotContextValue,se=V?.isEnabled??!1;if(T.slot&&at.log(`[Slot Registration] 컴포넌트: ${T.id}, isSlotEnabled: ${se}, shouldRender: ${_e}, resolvedSlotId: ${Te}`),!se||!_e){T.slot&&at.log(`[Slot Registration] 등록 스킵 - isSlotEnabled: ${se}, shouldRender: ${_e}`),ht.current&&T.id&&V&&(V.unregisterFromSlot(ht.current,T.id),ht.current=null);return}if(!Te||!T.id){T.slot&&at.log(`[Slot Registration] 등록 스킵 - resolvedSlotId: ${Te}, id: ${T.id}`),ht.current&&T.id&&V&&(V.unregisterFromSlot(ht.current,T.id),ht.current=null);return}ht.current&&ht.current!==Te&&V.unregisterFromSlot(ht.current,T.id);const ue={...T,slot:void 0,slotOrder:void 0};return V.registerToSlot(Te,T.id,{componentDef:ue,dataContext:e,order:T.slotOrder??0,parentFormContext:f??null,getParentComponentContext:()=>xe,translationContext:n,registrationKey:$e}),ht.current=Te,()=>{const ee=window.__slotContextValue;Te&&T.id&&ee&&ee.unregisterFromSlot(Te,T.id)}},[_e,Te,T,e,f,xe,n,$e]);const wt=N.useCallback(V=>{if(typeof V=="string"){if($l(V))return gd(V);if(Nl(V)){const se=[],ue=V.replace(new RegExp(`${to}([^${fi}]*)${fi}`,"g"),(ve,he)=>(se.push(he),`${xa}${se.length-1}${xa}`));let ee=ue;return/\$t:[a-zA-Z0-9._-]+/.test(ue)&&(ee=l.resolveTranslations(ue,n,ie)),ee.replace(new RegExp(`${xa}(\\d+)${xa}`,"g"),(ve,he)=>se[parseInt(he)])}if(V.startsWith("$t:defer:"))return V;if(/\$t:[a-zA-Z0-9._-]+/.test(V)){const se=V.trim();return se.startsWith("{")&&se.endsWith("}")||se.startsWith("[")&&se.endsWith("]")?V:l.resolveTranslations(V,n,ie)}return V}if(Array.isArray(V))return V.map(se=>wt(se));if(V&&typeof V=="object"){const se={};for(const[ue,ee]of Object.entries(V))se[ue]=wt(ee);return se}return V},[l,n,ie]),kt=N.useCallback(V=>eo(V),[]),ft=N.useCallback(V=>Aa(V),[]),nn=N.useMemo(()=>{let V={...T.props||{}};const se=["cellChildren","expandChildren","expandContext","render"],ee=a.getMetadata(T.name)?.skipBindingKeys||[],ve=[...new Set([...se,...ee])],he=b?{skipCache:!0}:void 0,Ce={...he??{},skipBindingKeys:ve},Ae=we=>{if(he)return he;if(we.startsWith("_computed")||we.startsWith("$computed"))return{skipCache:!0}};for(const[we,He]of Object.entries(V))if(!ve.includes(we))if(typeof He=="string"){const et=ft(He);if(et!==null){let _t=!1,Lt=et;et.startsWith(er)&&(_t=!0,Lt=et.slice(er.length));const kn={componentId:T.id,componentName:T.name,propName:we};let It;if(Pn(Lt))try{It=i.evaluatePipeExpression(Lt,ie,Ae(Lt),kn)}catch(Ht){at.warn(`파이프 표현식 평가 실패 (컴포넌트: ${T.id}, prop: ${we}):`,Ht),It=void 0}else if(kt(Lt))try{It=i.evaluateExpression(Lt,ie,kn)}catch(Ht){at.warn(`표현식 평가 실패 (컴포넌트: ${T.id}, prop: ${we}):`,Ht),It=void 0}else It=i.resolve(Lt,ie,Ae(Lt),kn);V[we]=_t&&It!=null?es(It):It}else{const _t={componentId:T.id,componentName:T.name,propName:we};V[we]=i.resolveBindings(He,ie,he,_t)}}else Array.isArray(He)?V[we]=He.map(et=>typeof et=="string"?i.resolveBindings(et,ie,he):typeof et=="object"&&et!==null?i.resolveObject(et,ie,Ce):et):He&&typeof He=="object"&&(V[we]=i.resolveObject(He,ie,Ce));for(const[we,He]of Object.entries(V))V[we]=wt(He);if(typeof V.style=="string"&&(V.style=x_(V.style)),V=bd(V,S?void 0:T.actions,ie,{componentContext:xe,actionDispatcher:c}),T.classMap){const we=pw(T.classMap,ie,i,{skipCache:!0});if(we){const He=V.className||"";V.className=He?`${He} ${we}`:we}}T.style&&(V.style=T.style),T.actions&&T.actions.length>0&&T.type==="composite"&&Object.assign(V,de),T.component_layout&&(V.__componentLayoutDefs=T.component_layout);const Fe=T.expandChildren||T.props?.expandChildren,Re=T.props?.cellChildren,Ne=T.children&&T.children.length>0,ke=T.component_layout,Ze=T.slot,ze=T.props?.modalId,Ge=T.type==="composite";return(Fe||Re||Ne||ke||Ze||ze||Ge)&&(V.__componentContext=xe),V},[T.props,T.actions,T.style,T.classMap,T.id,ie,n,i,l,xe,de,wt,kt,ft,b]),Ft=N.useRef(null),Nt=N.useMemo(()=>Ft.current&&gm(Ft.current,nn)?Ft.current:(Ft.current=nn,nn),[nn]),Br=N.useMemo(()=>{const V=T.id;if(typeof V!="string"||!V.includes("{{"))return V;const se=i.resolveBindings(V,ie,{skipCache:!0});return typeof se=="string"?se:V},[T.id,ie,i]),fn=N.useMemo(()=>{const V=T.dataKey,se=T.trackChanges,ue=T.debounce;if(V){const ee=V.startsWith("_global."),ve=ee?V.slice(8):V,he=ee?e._global:ie._local;return{dataKey:ve,trackChanges:se??!1,debounce:ue,setState:ee?Ae=>{e._globalSetState?e._globalSetState(Ae):at.warn("_global.formData 사용 시 _globalSetState가 필요합니다.")}:ye,state:he??{},_isGlobal:ee}}if(Z.dataKey)return Z},[T.dataKey,T.trackChanges,T.debounce,T.id,ye,ie._local,e._global,e._globalSetState,Z]),rn=N.useRef(new Set);N.useEffect(()=>{rn.current.clear()},[e._global?._remountKeys]);const hn=N.useCallback((V,se)=>{const ue=e._global?._remountKeys;return V&&ue?.[V]&&!rn.current.has(V)?(rn.current.add(V),`${V}-remount-${ue[V]}`):V||se},[e._global?._remountKeys]),Fn=N.useMemo(()=>{if(T.text!==void 0){if(typeof T.text=="string"){const V=T.text;let se=V;const ue={componentId:T.id,componentName:T.name,propName:"text",skipCache:!0},ee=ft(V);if(ee!==null){let ve=!1,he=ee;if(ee.startsWith(er)&&(ve=!0,he=ee.slice(er.length)),Pn(he))try{se=i.evaluatePipeExpression(he,ie,{skipCache:!0},ue)}catch(Ce){at.warn(`text 파이프 표현식 평가 실패 (컴포넌트: ${T.id}):`,Ce),se=""}else if(kt(he))try{se=i.evaluateExpression(he,ie,ue)}catch(Ce){at.warn(`text 표현식 평가 실패 (컴포넌트: ${T.id}):`,Ce),se=""}else se=i.resolve(he,ie,{skipCache:!0},ue);ve&&se!=null&&(se=es(se))}else V.includes("{{")&&(se=i.resolveBindings(V,ie,{skipCache:!0},ue));return se=wt(se),se??""}return T.text}return!T.children||T.children.length===0?null:T.children.map((V,se)=>{if(typeof V=="string")return V;const ue=T.__responsiveChildrenKey,ee=ue?`responsive.${ue}.children`:"children",ve=L?`${L}.${ee}.${se}`:`${se}`,he=V._fromBase?V.id||`child-${se}`:hn(V.id,`child-${se}`),Ce=!V._fromBase&&M?`${he}_${M}`:he;return dt.jsx(jr,{componentDef:V,dataContext:ie,translationContext:n,registry:a,bindingEngine:i,translationEngine:l,actionDispatcher:c,parentComponentContext:xe,parentFormContextProp:fn,isInsideIteration:b,isEditMode:S,onComponentSelect:v,onComponentHover:_,componentPath:ve,onDragStart:A,onDragEnd:x,layoutKey:M},Ce)})},[T.text,T.children,T.id,e,ie,n,a,i,l,c,xe,fn,ft,kt,wt,hn,b,S,v,_,L,A,x,M]),Pr=N.useMemo(()=>{if(!T.iteration)return null;try{const V=yd(T.iteration.source,ie,i);if(!Array.isArray(V))return at.warn(`iteration source가 배열이 아닙니다 (컴포넌트: ${T.id}):`,V),null;const se=Zr();return se?.isEnabled()&&se.trackIteration(T.id||`iteration-${Date.now()}`,T.iteration.source,T.iteration.item_var,T.iteration.index_var,V.length),V.map((ue,ee)=>{const ve={...ie,[T.iteration.item_var]:ue,[`${T.iteration.item_var}_index`]:ee};T.iteration.index_var&&(ve[T.iteration.index_var]=ee);const he=ee===0||ee===V.length-1;se?.isEnabled()&&he&&se.trackNestedContext?.({componentId:`${T.id||"unknown"}-iter-${ee}`,componentType:"iteration",parentContext:{available:Object.keys(ie),values:{}},ownContext:{added:[T.iteration.item_var,...T.iteration.index_var?[T.iteration.index_var]:[]],values:{[T.iteration.item_var]:ue,...T.iteration.index_var?{[T.iteration.index_var]:ee}:{}}},depth:1});const Ce={...T,iteration:void 0,responsive:void 0},Ae=L?`${L}.iteration.${ee}`:`iteration.${ee}`,Fe=`${hn(T.id,"item")}-${ee}`;return dt.jsx(jr,{componentDef:Ce,dataContext:ve,translationContext:n,registry:a,bindingEngine:i,translationEngine:l,actionDispatcher:c,parentComponentContext:xe,parentFormContextProp:fn,isInsideIteration:!0,isEditMode:S,onComponentSelect:v,onComponentHover:_,componentPath:Ae,onDragStart:A,onDragEnd:x,layoutKey:M},Fe)})}catch(V){return at.error(`iteration 렌더링 실패 (컴포넌트: ${T.id}):`,V),null}},[T.iteration,T.children,T.id,e,ie,n,a,i,l,c,xe,fn,hn,S,v,_,L,A,x,M]),[Pt,wi]=N.useState(0),na=N.useMemo(()=>{if(!T.sortable||!T.itemTemplate)return null;const{sortable:V,itemTemplate:se}=T;try{const ue=yd(V.source,ie,i);if(!Array.isArray(ue))return at.warn(`sortable source가 배열이 아닙니다 (컴포넌트: ${T.id}):`,ue),null;if(ue.length===0)return null;const ee=V.itemKey||"id",ve=V.strategy||"verticalList",he=V.itemVar||"$item",Ce=V.indexVar||"$index",Ae=V.handle,Fe=V.wrapperElement,Re=(Ze,ze)=>{const Ge=T.actions?.filter(we=>we.event==="onSortEnd"||we.type==="onSortEnd");if(wi(we=>we+1),Ge&&Ge.length>0&&c){const we={...ie,$sortedItems:Ze,$sortEvent:ze};Ge.forEach(He=>{try{const et=c.createHandler(He,we,xe),_t=new Event("sortend");et(_t)}catch(et){at.error(`onSortEnd action failed: ${He.handler}`,et)}})}},Ne=Ze=>{const ze=T.actions?.filter(Ge=>Ge.event==="onSortStart"||Ge.type==="onSortStart");if(ze&&ze.length>0&&c){const Ge={...ie,$activeId:Ze.activeId};ze.forEach(we=>{try{const He=c.createHandler(we,Ge,xe),et=new Event("sortstart");He(et)}catch(He){at.error(`onSortStart action failed: ${we.handler}`,He)}})}},ke=ue.map((Ze,ze)=>{const Ge=String(Ze[ee]),we={...ie,[he]:Ze,[Ce]:ze},He=L?`${L}.sortable.${ze}`:`sortable.${ze}`,et=`${hn(T.id,"sortable-item")}-v${Pt}-${Ge}`;return dt.jsx(__,{id:Ge,handle:Ae,...Fe?{as:Fe}:{},children:(Fe&&se.children?se.children:[se]).map((_t,Lt)=>dt.jsx(jr,{componentDef:_t,dataContext:we,translationContext:n,registry:a,bindingEngine:i,translationEngine:l,actionDispatcher:c,parentComponentContext:xe,parentFormContextProp:void 0,isInsideIteration:!0,isEditMode:S,onComponentSelect:v,onComponentHover:_,componentPath:He,onDragStart:A,onDragEnd:x,layoutKey:M},`${et}-child-${Lt}`))},et)});return dt.jsx(w_,{items:ue,itemKey:ee,strategy:ve,onSortEnd:Re,onSortStart:Ne,sortVersion:Pt,children:ke})}catch(ue){return at.error(`sortable 렌더링 실패 (컴포넌트: ${T.id}):`,ue),null}},[T.sortable,T.itemTemplate,T.actions,T.id,e,ie,n,a,i,l,c,xe,hn,S,v,_,L,A,x,Pt,M]),gn=N.useRef(!1);N.useEffect(()=>{if(gn.current)return;gn.current=!0;const V=Zr();if(V?.isEnabled()&&V.trackMount(o.id,{name:o.name,type:o.type||"component",props:Nt}),o.lifecycle?.onMount&&o.lifecycle.onMount.length>0)for(const ue of o.lifecycle.onMount)try{const ee=c.createHandler(ue,ie,xe),ve=new Event("mount");ee(ve),at.log(`Lifecycle onMount executed: ${ue.handler} (Component: ${o.id})`)}catch(ee){at.error(`Lifecycle onMount failed: ${ue.handler} (Component: ${o.id})`,ee)}const se=[];if(o.onComponentEvent&&o.onComponentEvent.length>0){const ue=window.G7Core;if(ue?.componentEvent?.on)for(const ee of o.onComponentEvent){if(!ee.event){at.warn(`onComponentEvent: event name is required (Component: ${o.id})`);continue}const ve=async Ce=>{try{const Ae={...ie,_eventData:Ce},Re=await c.createHandler({handler:ee.handler,params:ee.params,onSuccess:ee.onSuccess,onError:ee.onError},Ae,xe)(new CustomEvent(ee.event,{detail:Ce}));return at.log(`onComponentEvent "${ee.event}" handled: ${ee.handler} (Component: ${o.id})`,{eventData:Ce,result:Re}),Re}catch(Ae){throw at.error(`onComponentEvent "${ee.event}" failed: ${ee.handler} (Component: ${o.id})`,Ae),Ae}},he=ue.componentEvent.on(ee.event,ve);se.push(he),at.log(`onComponentEvent: Subscribed to "${ee.event}" (Component: ${o.id})`)}else at.warn(`onComponentEvent: G7Core.componentEvent is not available (Component: ${o.id})`)}return()=>{const ue=Zr();ue?.isEnabled()&&ue.trackUnmount(o.id);for(const ee of se)try{ee()}catch(ve){at.error(`onComponentEvent: Failed to unsubscribe (Component: ${o.id})`,ve)}if(se.length>0&&at.log(`onComponentEvent: Unsubscribed ${se.length} event(s) (Component: ${o.id})`),o.lifecycle?.onUnmount&&o.lifecycle.onUnmount.length>0)for(const ee of o.lifecycle.onUnmount)try{const ve=c.createHandler(ee,ie,xe),he=new Event("unmount");ve(he),at.log(`Lifecycle onUnmount executed: ${ee.handler} (Component: ${o.id})`)}catch(ve){at.error(`Lifecycle onUnmount failed: ${ee.handler} (Component: ${o.id})`,ve)}}},[]);const Rn=N.useMemo(()=>{const V=Nt.dataKey,se=Nt.trackChanges,ue=Nt.debounce;return V?{dataKey:V,trackChanges:se??!1,debounce:ue,setState:ye,state:de}:null},[Nt.dataKey,Nt.trackChanges,Nt.debounce,ye,de]);N.useEffect(()=>{const V=T.dataKey||Nt.dataKey;if(!V)return;const se=Zr();if(!se?.isEnabled())return;const ue=[],ee=he=>{if(he)for(const Ce of he){const Ae=Ce?.props?.name;if(Ae&&ue.push({name:Ae,type:Ce.name||Ce.type||"unknown"}),Ce?.children&&ee(Ce.children),Ce?.slots)for(const Fe of Object.values(Ce.slots))Array.isArray(Fe)&&ee(Fe)}};if(o.children&&ee(o.children),o.slots)for(const he of Object.values(o.slots))Array.isArray(he)&&ee(he);const ve=o.id||`form-${V}`;return se.trackForm(ve,V,ue),()=>{se.untrackForm(ve)}},[T,Nt.dataKey,o.id,o.children]);const tt=N.useRef(null),$n=N.useRef(void 0);N.useEffect(()=>{const V=Nt?.name;if(!V||!Z.dataKey||!Z.setState||Nt?.autoBinding===!1)return;const se=`${Z.dataKey}.${V}`,ue=window.__g7AutoBindingPaths??new Map;return window.__g7AutoBindingPaths=ue,ue.set(se,(ue.get(se)??0)+1),()=>{const ee=ue.get(se)??0;ee<=1?ue.delete(se):ue.set(se,ee-1)}},[Nt?.name,Nt?.autoBinding,Z.dataKey,Z.setState]);const pn=N.useMemo(()=>{const{dataKey:V,trackChanges:se,debounce:ue,...ee}=Nt,ve=ee.name;if(!ve||!Z.dataKey||!Z.setState)return ee;if(ee.autoBinding===!1){const{autoBinding:Ge,...we}=ee;return we}const he=`${Z.dataKey}.${ve}`,Ce=CC(Z.state,he),Ae=Ce!==void 0?Ce:ee.value,Re=a.getMetadata(o.name)?.bindingType,Ne=typeof Ae=="boolean"&&(Re==="checked"||Re==="checkable"&&["checkbox","radio"].includes(ee?.type)),ke=Z.debounce,Ze=Ge=>{const we=Z.state||{},He=EC(we,he,Ge);if(Z.trackChanges&&(He.hasChanges=!0),window.__g7PendingLocalState=He,Z.setState(He),Z._isGlobal)return;const et=window.G7Core,_t={[he]:Ge};Z.trackChanges&&(_t.hasChanges=!0),et?.state?.setLocal?.(_t,{render:!1})},ze=(Ge,we)=>{ke&&ke>0?(tt.current&&clearTimeout(tt.current),tt.current=setTimeout(()=>{Ze(Ge),tt.current=null,we&&(we(),window.__g7PendingLocalState=null)},ke)):(Ze(Ge),we&&(we(),window.__g7PendingLocalState=null))};if(Ne)return{...ee,checked:Ae,onChange:we=>{const He=we.target.checked;Ze(He),ee.onChange&&ee.onChange(we)}};{const Ge=He=>{const et=He?.target?.value!==void 0?He.target.value:He;ke&&ke>0&&($n.current=et);const _t=He?.target?{target:{value:et,name:He.target.name,type:He.target.type,checked:He.target.checked},currentTarget:He.currentTarget?{value:et,name:He.currentTarget.name}:void 0}:He;ze(et,()=>{ee.onChange&&ee.onChange(_t)})};let we;return $n.current!==void 0?tt.current!==null?we=$n.current:String(Ae??"")===String($n.current)?($n.current=void 0,we=Ae??""):we=$n.current:we=Ae??"",{...ee,value:we,onChange:Ge}}},[Nt,Z,Z.state,Z.debounce]),ra=N.useMemo(()=>{const V=Zr();if(V?.isEnabled()&&V.trackRender(o.name),!_e)return T.iteration?Pr:null;if(T.sortable&&T.itemTemplate)return na;if(T.iteration)return Pr;if(T.type==="extension_point"){const{layout:Re,columns:Ne,gap:ke,className:Ze,...ze}=T.props||{};let Ge=Ze||"";return Re==="grid"&&Ne?Ge=`${Ge} grid grid-cols-${Ne} gap-${ke||4}`.trim():Re==="flex"&&(Ge=`${Ge} flex flex-col gap-${ke||4}`.trim()),dt.jsx("div",{id:Br,className:Ge||void 0,...ze,children:Fn})}const se=a.getComponent(T.name);if(!se)return at.error(`컴포넌트를 찾을 수 없습니다: ${T.name} (ID: ${T.id})`,"componentDef:",JSON.stringify(T,null,2)),null;const ue=e._global?._remountKeys,ee={...pn,id:pn.id??Br,...ue&&Object.keys(ue).length>0&&{__remountTrigger:JSON.stringify(ue)}};if(H?.listeners&&H.handle&&pn["data-drag-handle"]!==void 0&&(Object.assign(ee,H.listeners),ee.style={...ee.style,cursor:H.isDragging?"grabbing":"grab"}),S){const Re=T.id||`auto_${T.name||T.type||"unknown"}_${Math.random().toString(36).substring(2,8)}`,Ne=ze=>{typeof ze.preventDefault=="function"&&ze.preventDefault(),v&&(ze.stopPropagation(),v(Re,ze))},ke={"data-editor-id":Re,"data-editor-name":T.name,"data-editor-type":T.type,onClick:Ne};L&&(ke["data-editor-path"]=L);const Ze=T.__editorSlotName??T.slot;typeof Ze=="string"&&(ke["data-editor-slot"]=Ze),_&&(ke.onMouseMove=ze=>{ze.stopPropagation(),_(Re,ze)},ke.onMouseLeave=ze=>{_(null,ze)}),Object.assign(ee,ke),ee.editorAttrs=ke}const ve=T.type==="basic",he=T.type==="layout",Ae=ve||he?(()=>{const Re={};for(const[Ne,ke]of Object.entries(ee))Ne.startsWith("__")||Ne==="editorAttrs"&&ve||(Re[Ne]=ke);return Re})():ee;let Fe=dt.jsx(se,{...Ae,children:Fn});if(fn&&(Fe=dt.jsx(SC,{value:fn,children:Fe})),T.isolatedState){const Re=typeof T.isolatedState=="object"?T.isolatedState:{},Ne=e?._isolatedInit,ke=Ne?{...Re,...Ne}:Re;Fe=dt.jsx(kC,{initialState:ke,scopeId:T.isolatedScopeId,children:Fe})}return Fe},[_e,T.iteration,T.sortable,T.itemTemplate,T.type,T.name,T.id,T.props,T.isolatedState,T.isolatedScopeId,a,pn,Fn,Pr,na,H,Rn,S,v,_,A,x,L,e._global?._remountKeys]),ir=N.useMemo(()=>{const V=T.blur_until_loaded;if(!V||e?._global?.__isPreview)return!1;if(typeof V=="string"){const ee=ft(V);if(ee!==null)try{const ve={componentId:T.id,componentName:T.name,propName:"blur_until_loaded"};return!!i.evaluateExpression(ee,ie,ve)}catch(ve){return at.warn(`blur_until_loaded 표현식 평가 실패 (컴포넌트: ${T.id}):`,ve),!1}return!1}if(typeof V=="object"&&V!==null){if(!V.enabled)return!1;if(V.data_sources)return(Array.isArray(V.data_sources)?V.data_sources:[V.data_sources]).some(Ce=>e[Ce]===void 0);if(fe)return!0;const ee=["route","query","_global","_local","_dataSourceErrors"];return Object.keys(e).filter(he=>!ee.includes(he)&&!he.startsWith("_")).some(he=>e[he]===void 0)}if(fe)return!0;const se=["route","query","_global","_local","_dataSourceErrors"];return Object.keys(e).filter(ee=>!se.includes(ee)&&!ee.startsWith("_")).some(ee=>e[ee]===void 0)},[T.blur_until_loaded,T.id,fe,e,ie,i,ft]);if(ir){const V=T.blur_until_loaded;if(typeof V=="object"&&V?.data_sources){const ue=(Array.isArray(V.data_sources)?V.data_sources:[V.data_sources]).reduce((ve,he)=>(ve[he]=e[he]===void 0?"UNDEFINED":"LOADED",ve),{}),ee=Object.keys(e).filter(ve=>!ve.startsWith("_"));at.log(`[BLUR APPLIED] Component: ${T.id}, data_sources status:`,ue,"Available keys:",ee)}else at.log(`[BLUR APPLIED] Component: ${T.id}, blur_until_loaded:`,V)}const an=N.useMemo(()=>{if(!T.blur_until_loaded)return{wrapperClasses:"",innerClasses:""};const V=nn?.className||"";if(!V)return{wrapperClasses:"",innerClasses:""};const se=[/^(sm:|md:|lg:|xl:|2xl:)?(col-span-|row-span-|col-start-|col-end-|row-start-|row-end-)/,/^(sm:|md:|lg:|xl:|2xl:)?(self-|place-self-|order-)/,/^(sm:|md:|lg:|xl:|2xl:)?(flex-shrink|flex-grow|flex-\d|basis-)/],ue=V.split(/\s+/).filter(Boolean),ee=[],ve=[];for(const he of ue)se.some(Ae=>Ae.test(he))?ee.push(he):ve.push(he);return{wrapperClasses:ee.join(" "),innerClasses:ve.join(" ")}},[T.blur_until_loaded,nn?.className]),En=window.__slotContextValue;return Te&&En?.isEnabled?null:dt.jsx(T_,{componentId:o.id,componentName:o.name,children:T.blur_until_loaded?dt.jsx("div",{className:[an.wrapperClasses,ir?"opacity-50 blur-sm transition-all duration-300 pointer-events-none":void 0].filter(Boolean).join(" ")||void 0,children:ra}):ra})});jr.displayName="DynamicRenderer",typeof window<"u"&&(window.__DynamicRenderer=jr);function R_(o){const e={};try{const a=new URL(o,typeof window<"u"?window.location.origin:"http://localhost").searchParams,i=new Set;for(const l of a.keys()){if(i.has(l))continue;i.add(l);const c=a.getAll(l);c.length>1||l.endsWith("[]")?e[l]=c:e[l]=c[0]}}catch{}return e}function k_(o){try{return new URL(o,typeof window<"u"?window.location.origin:"http://localhost").pathname}catch{return o.split("?")[0]}}const Si=class Si{constructor(){$(this,"config",{enabled:!1,maxHistorySize:100,logLevel:"info",serverEndpoint:"/_boost/g7-debug/dump-state",autoCapture:!0});$(this,"stateHistory",[]);$(this,"snapshotIdCounter",0);$(this,"stateWatchers",new Map);$(this,"currentLocalState",{});$(this,"currentComputedState",{});$(this,"currentParentContext");$(this,"actionHistory",[]);$(this,"actionWatchers",new Set);$(this,"cacheStats",{hits:0,misses:0,entries:0});$(this,"cacheDecisions",[]);$(this,"cacheDecisionIdCounter",0);$(this,"maxCacheDecisions",200);$(this,"currentRenderCycleId",null);$(this,"isInActionExecution",!1);$(this,"isInIteration",!1);$(this,"mountedComponents",new Map);$(this,"eventListeners",new Map);$(this,"mountWatchers",new Set);$(this,"unmountWatchers",new Set);$(this,"renderCounts",new Map);$(this,"bindingEvalCount",0);$(this,"profilingData",[]);$(this,"isProfiling",!1);$(this,"profilingStartTime",0);$(this,"activeRequests",new Map);$(this,"requestHistory",[]);$(this,"pendingDataSources",new Set);$(this,"requestWatchers",new Set);$(this,"wsConnections",new Map);$(this,"wsMessageHistory",[]);$(this,"wsMessageWatchers",new Set);$(this,"ifConditions",new Map);$(this,"iterations",new Map);$(this,"conditionWatchers",new Set);$(this,"forms",new Map);$(this,"formWatchers",new Set);$(this,"expressionHistory",[]);$(this,"expressionWatchers",new Set);$(this,"expressionIdCounter",0);$(this,"dataSources",new Map);$(this,"dataPathTransforms",[]);$(this,"maxDataPathTransforms",100);$(this,"nestedContexts",[]);$(this,"nestedContextIdCounter",0);$(this,"maxNestedContexts",100);$(this,"formBindingIssues",[]);$(this,"formBindingValidations",[]);$(this,"formBindingIssueIdCounter",0);$(this,"maxFormBindingIssues",100);$(this,"computedProperties",new Map);$(this,"computedRecalcLogs",[]);$(this,"computedRecalcIdCounter",0);$(this,"maxComputedRecalcLogs",100);$(this,"handlers",new Map);$(this,"componentEventSubscriptions",new Map);$(this,"componentEventEmitHistory",[]);$(this,"componentEventIdCounter",0);$(this,"maxComponentEventHistory",100);$(this,"stateRenderingLogs",[]);$(this,"currentStateChangeContext",null);$(this,"stateRenderingIdCounter",0);$(this,"componentRenderCounts",new Map);$(this,"stateToComponentMap",new Map);$(this,"maxStateRenderingLogs",100);$(this,"componentStateSources",new Map);$(this,"contextFlowNodes",new Map);$(this,"dynamicStates",new Map);$(this,"styleIssues",[]);$(this,"componentStyles",new Map);$(this,"authEvents",[]);$(this,"authHeaderHistory",[]);$(this,"authEventIdCounter",0);$(this,"maxAuthEventHistory",50);$(this,"logHistory",[]);$(this,"logIdCounter",0);$(this,"maxLogHistory",500);$(this,"currentLayout",null);$(this,"layoutHistory",[]);$(this,"layoutIdCounter",0);$(this,"layoutStats",{totalLoads:0,cacheHits:0,apiLoads:0});$(this,"maxLayoutHistory",50);$(this,"executionDetails",new Map);$(this,"stateChangeHistory",[]);$(this,"dataSourceChangeHistory",[]);$(this,"changeAlerts",[]);$(this,"changeExpectations",new Map);$(this,"executionIdCounter",0);$(this,"stateChangeIdCounter",0);$(this,"alertIdCounter",0);$(this,"maxExecutionDetails",100);$(this,"maxStateChangeHistory",200);$(this,"maxChangeAlerts",100);$(this,"sequenceExecutions",[]);$(this,"sequenceExecutionStack",[]);$(this,"sequenceIdCounter",0);$(this,"maxSequenceExecutions",50);$(this,"staleClosureWarnings",[]);$(this,"staleClosureIdCounter",0);$(this,"maxStaleClosureWarnings",100);$(this,"stateCaptureRegistry",new Map);$(this,"modalStates",new Map);$(this,"modalStateIssues",[]);$(this,"modalStateRelations",[]);$(this,"modalStateChangeLogs",[]);$(this,"modalStateIssueIdCounter",0);$(this,"modalStateChangeLogIdCounter",0);$(this,"maxModalStateIssues",100);$(this,"maxModalStateChangeLogs",200);$(this,"namedActionDefinitions",{});$(this,"namedActionRefLogs",[]);$(this,"namedActionRefIdCounter",0);$(this,"maxNamedActionRefLogs",200);$(this,"debounceActionHistory",[]);$(this,"dataSourceUpdateHistory",[]);$(this,"debounceActionIdCounter",0);$(this,"dataSourceUpdateIdCounter",0)}static getInstance(){return Si.instance||(Si.instance=new Si),Si.instance}initialize(){if(this.config.enabled=this.checkDebugMode(),!this.config.enabled){console.log("[G7DevTools] 비활성화됨 (환경설정 > 고급 설정 > 디버그 모드를 켜세요)");return}console.log("[G7DevTools] 활성화됨"),this.setupGlobalErrorHandler()}checkDebugMode(){try{if(window.G7Config?.debug===!0)return!0;const a=window.G7Core?.state?.get?.();return a?.settings?.advanced?.debug_mode===!0||a?._global?.settings?.advanced?.debug_mode===!0}catch{return!1}}isEnabled(){return this.config.enabled}setupGlobalErrorHandler(){window.addEventListener("error",e=>{this.config.enabled&&this.logAction({id:this.generateId(),type:"globalError",startTime:Date.now(),status:"error",error:{name:"Error",message:e.message,stack:e.error?.stack}})}),window.addEventListener("unhandledrejection",e=>{this.config.enabled&&this.logAction({id:this.generateId(),type:"unhandledRejection",startTime:Date.now(),status:"error",error:{name:"UnhandledRejection",message:String(e.reason)}})})}captureStateSnapshot(e){if(!this.config.enabled)return;const n={id:++this.snapshotIdCounter,timestamp:Date.now(),source:e.source,prev:this.sanitizeObject(e.prev),next:this.sanitizeObject(e.next),diff:e.diff};this.stateHistory.push(n),this.stateHistory.length>this.config.maxHistorySize&&this.stateHistory.shift(),this.notifyStateWatchers(n)}getState(){try{const e=window.G7Core,n=e?.state?.get?.()||{},a={};if(e?._isolatedStates&&typeof e._isolatedStates=="object")for(const[i,l]of Object.entries(e._isolatedStates))a[i]=this.sanitizeObject(l);return{_global:n._global||n,_local:this.currentLocalState,_computed:this.currentComputedState,_isolated:Object.keys(a).length>0?a:void 0,$parent:this.currentParentContext}}catch{return{_global:{},_local:this.currentLocalState,_computed:this.currentComputedState}}}updateLocalState(e){this.config.enabled&&(this.currentLocalState=this.sanitizeObject(e))}updateComputedState(e){this.config.enabled&&(this.currentComputedState=this.sanitizeObject(e))}updateParentContext(e){this.config.enabled&&(this.currentParentContext=e?this.sanitizeObject(e):void 0)}getStateHistory(){return[...this.stateHistory]}getDualStorageMismatch(){const e=this.currentLocalState||{};let n={};try{n=(window.G7Core?.state?.get?.()||{})._local||{}}catch{n={}}const a=this.findMismatchedLeafPaths(e,n);return{hasMismatch:a.length>0,mismatchedPaths:a,storageA:e,storageB:n}}findMismatchedLeafPaths(e,n,a=""){const i=[],l=new Set([...Object.keys(e||{}),...Object.keys(n||{})]);for(const c of l){const d=a?`${a}.${c}`:c,f=e?.[c],g=n?.[c],m=f&&typeof f=="object"&&!Array.isArray(f),b=g&&typeof g=="object"&&!Array.isArray(g);if(m&&b)i.push(...this.findMismatchedLeafPaths(f,g,d));else{const S=f===void 0?"__undefined__":JSON.stringify(f),v=g===void 0?"__undefined__":JSON.stringify(g);S!==v&&i.push(d)}}return i}watchState(e,n){return this.stateWatchers.has(e)||this.stateWatchers.set(e,new Set),this.stateWatchers.get(e).add(n),()=>{this.stateWatchers.get(e)?.delete(n)}}notifyStateWatchers(e){if(this.stateWatchers.get("*")?.forEach(n=>{try{n(e.next,e.prev,"*")}catch(a){console.error("[G7DevTools] State watcher error:",a)}}),e.diff)for(const n of e.diff.changed)this.stateWatchers.get(n.path)?.forEach(a=>{try{a(n.newValue,n.oldValue,n.path)}catch(i){console.error("[G7DevTools] State watcher error:",i)}})}diffSnapshots(e,n){const a=this.stateHistory.find(l=>l.id===e),i=this.stateHistory.find(l=>l.id===n);return!a||!i?null:this.calculateDiff(a.next,i.next)}calculateDiff(e,n,a=""){const i={added:[],removed:[],changed:[]},l=new Set(Object.keys(e)),c=new Set(Object.keys(n));for(const d of c){const f=a?`${a}.${d}`:d;l.has(d)?JSON.stringify(e[d])!==JSON.stringify(n[d])&&i.changed.push({path:f,oldValue:e[d],newValue:n[d]}):i.added.push(f)}for(const d of l){const f=a?`${a}.${d}`:d;c.has(d)||i.removed.push(f)}return i}logAction(e){if(!this.config.enabled)return;const n=this.actionHistory.findIndex(a=>a.id===e.id);n>=0?this.actionHistory[n]={...this.actionHistory[n],...e}:(this.actionHistory.push(e),this.actionHistory.length>this.config.maxHistorySize&&this.actionHistory.shift()),this.isProfiling&&this.profilingData.push({type:"action",action:e.type,timestamp:performance.now(),duration:e.duration}),this.notifyActionWatchers(e)}getActionHistory(){return[...this.actionHistory]}watchActions(e){return this.actionWatchers.add(e),()=>{this.actionWatchers.delete(e)}}notifyActionWatchers(e){this.actionWatchers.forEach(n=>{try{n(e)}catch(a){console.error("[G7DevTools] Action watcher error:",a)}})}getActionMetrics(){const e=this.actionHistory.filter(l=>l.status==="success"),n=this.actionHistory.filter(l=>l.status==="error"),a={};for(const l of this.actionHistory)a[l.type]=(a[l.type]||0)+1;const i=e.reduce((l,c)=>l+(c.duration||0),0);return{totalActions:this.actionHistory.length,successCount:e.length,errorCount:n.length,averageDuration:e.length>0?i/e.length:0,actionsByType:a}}recordCacheHit(){this.config.enabled&&this.cacheStats.hits++}recordCacheMiss(){this.config.enabled&&this.cacheStats.misses++}updateCacheEntries(e){this.config.enabled&&(this.cacheStats.entries=e)}getCacheStats(){const e=this.cacheStats.hits+this.cacheStats.misses;return{...this.cacheStats,hitRate:e>0?this.cacheStats.hits/e:0}}resetCacheStats(){this.cacheStats={hits:0,misses:0,entries:0}}trackCacheDecision(e){if(!this.config.enabled)return;const n={id:`cache-${++this.cacheDecisionIdCounter}`,timestamp:Date.now(),expression:e.expression,decision:e.decision,reason:e.reason,context:{isInIteration:this.isInIteration,isInAction:this.isInActionExecution,renderCycleId:this.currentRenderCycleId||void 0,componentId:e.componentId,skipCacheOption:e.skipCacheOption},cachedValue:e.cachedValue!==void 0?this.safeClone(e.cachedValue):void 0,freshValue:e.freshValue!==void 0?this.safeClone(e.freshValue):void 0,valueMatch:e.cachedValue!==void 0&&e.freshValue!==void 0?JSON.stringify(e.cachedValue)===JSON.stringify(e.freshValue):void 0,duration:e.duration};this.cacheDecisions.push(n),this.cacheDecisions.length>this.maxCacheDecisions&&this.cacheDecisions.shift(),e.decision==="cache_hit"?this.cacheStats.hits++:(e.decision==="cache_miss"||e.decision==="skip_cache")&&this.cacheStats.misses++}startRenderCycle(e){return this.currentRenderCycleId=`render-${Date.now()}-${e||"root"}`,this.currentRenderCycleId}endRenderCycle(){this.currentRenderCycleId=null}startActionContext(){this.isInActionExecution=!0}endActionContext(){this.isInActionExecution=!1}startIterationContext(){this.isInIteration=!0}endIterationContext(){this.isInIteration=!1}getCacheDecisionTrackingInfo(){const e=this.getCacheDecisionStats();return{decisions:[...this.cacheDecisions],stats:e,timestamp:Date.now()}}getCacheDecisionStats(){const e=this.cacheDecisions;let n=0,a=0,i=0,l=0;const c={},d={};for(const g of e)g.decision==="cache_hit"?n++:g.decision==="cache_miss"?a++:g.decision==="skip_cache"?i++:g.decision==="invalidate"&&l++,c[g.reason]=(c[g.reason]||0)+1,g.context.componentId&&(d[g.context.componentId]=(d[g.context.componentId]||0)+1);const f=n+a+i;return{totalDecisions:e.length,cacheHits:n,cacheMisses:a,skipCacheCount:i,invalidateCount:l,byReason:c,byComponent:d,avgHitRate:f>0?n/f:0}}getRecentCacheDecisions(e=20,n){let a=[...this.cacheDecisions];return n&&(a=a.filter(i=>i.decision===n)),a.slice(-e)}clearCacheDecisionData(){this.cacheDecisions=[],this.cacheDecisionIdCounter=0}summarizePropsForLifecycle(e){if(!e||typeof e!="object")return e;const n={},a=["children","ref","key"];for(const[i,l]of Object.entries(e))if(!a.includes(i))if(l==null)n[i]=l;else if(Array.isArray(l))n[i]=`[Array(${l.length})]`;else if(typeof l=="object"){const c=Object.keys(l);c.length<=3?n[i]=`{${c.join(", ")}}`:n[i]=`{${c.slice(0,3).join(", ")}, ... +${c.length-3}}`}else typeof l=="string"&&l.length>50?n[i]=l.substring(0,50)+"...":typeof l=="function"?n[i]="[Function]":n[i]=l;return n}trackMount(e,n){if(!this.config.enabled)return;const a=this.summarizePropsForLifecycle(n.props),i={...n,props:a,id:e,mountTime:Date.now()};this.mountedComponents.set(e,i),this.mountWatchers.forEach(l=>{try{l({componentId:e,componentName:n.name,timestamp:i.mountTime})}catch(c){console.error("[G7DevTools] Mount watcher error:",c)}})}trackUnmount(e){if(!this.config.enabled)return;const n=this.mountedComponents.get(e),a=this.eventListeners.get(e)||[];a.length>0&&console.warn(`[G7DevTools] 컴포넌트 ${e} 언마운트 시 ${a.length}개 리스너 미정리`),n&&this.unmountWatchers.forEach(i=>{try{i({componentId:e,componentName:n.name,timestamp:Date.now(),orphanedListeners:a.length})}catch(l){console.error("[G7DevTools] Unmount watcher error:",l)}}),this.mountedComponents.delete(e)}trackListener(e,n,a){if(!this.config.enabled)return;const i=this.eventListeners.get(e)||[];i.push({type:n,target:a,addedAt:Date.now()}),this.eventListeners.set(e,i)}removeListener(e,n){if(!this.config.enabled)return;const a=this.eventListeners.get(e)||[],i=a.findIndex(l=>l.type===n);i>=0&&a.splice(i,1)}getMountedComponents(){return Array.from(this.mountedComponents.values())}getOrphanedListeners(){const e=[];for(const[n,a]of this.eventListeners)this.mountedComponents.has(n)||e.push(...a.map(i=>({...i,componentId:n})));return e}watchMount(e){return this.mountWatchers.add(e),()=>{this.mountWatchers.delete(e)}}watchUnmount(e){return this.unmountWatchers.add(e),()=>{this.unmountWatchers.delete(e)}}trackRender(e){if(!this.config.enabled)return;const n=this.renderCounts.get(e)||0;this.renderCounts.set(e,n+1),this.isProfiling&&this.profilingData.push({type:"render",component:e,timestamp:performance.now()})}trackBindingEval(e){this.config.enabled&&(this.bindingEvalCount++,this.isProfiling&&e&&this.profilingData.push({type:"binding",expression:e,timestamp:performance.now()}))}getRenderCount(){return new Map(this.renderCounts)}getBindingEvalCount(){return this.bindingEvalCount}getMemoryWarnings(){const e=[];try{const i=this.getState(),l=JSON.stringify(i).length;l>1e6&&e.push({type:"large-state",message:`상태 크기가 ${(l/1024/1024).toFixed(2)}MB입니다`,suggestion:"불필요한 데이터 정리 필요",severity:l>5e6?"error":"warning"})}catch{}this.stateHistory.length>this.config.maxHistorySize*.8&&e.push({type:"large-history",message:`상태 이력이 ${this.stateHistory.length}개입니다`,suggestion:"maxHistory 설정 조정 필요",severity:"warning"});const n=this.getOrphanedListeners().length;n>0&&e.push({type:"orphaned-listeners",message:`${n}개의 정리되지 않은 이벤트 리스너`,suggestion:"useEffect cleanup 또는 removeEventListener 확인",severity:n>10?"error":"warning"});const a=Array.from(this.renderCounts.entries()).filter(([,i])=>i>50);return a.length>0&&e.push({type:"excessive-renders",message:`${a.length}개 컴포넌트가 50회 이상 렌더링`,suggestion:"React.memo, useMemo, useCallback 사용 고려",severity:"warning"}),e}startProfiling(){this.config.enabled&&(this.isProfiling=!0,this.profilingData=[],this.profilingStartTime=performance.now(),console.log("[G7DevTools] 프로파일링 시작"))}stopProfiling(){this.isProfiling=!1;const e=performance.now()-this.profilingStartTime,n=this.analyzeProfile(e);return console.log("[G7DevTools] 프로파일링 완료",n),n}analyzeProfile(e){const n=this.profilingData.filter(g=>g.type==="render"),a=this.profilingData.filter(g=>g.type==="binding"),i=this.profilingData.filter(g=>g.type==="action"),l={};for(const g of n)g.component&&(l[g.component]||(l[g.component]={count:0,durations:[]}),l[g.component].count++,g.duration&&l[g.component].durations.push(g.duration));const c=Object.entries(l).map(([g,m])=>({name:g,renderCount:m.count,avgDuration:m.durations.length>0?m.durations.reduce((b,S)=>b+S,0)/m.durations.length:0})).sort((g,m)=>m.renderCount-g.renderCount).slice(0,10),d={};for(const g of a)g.expression&&(d[g.expression]=(d[g.expression]||0)+1);const f=Object.entries(d).sort(([,g],[,m])=>m-g).slice(0,10).map(([g])=>g);return{duration:e,entries:this.profilingData,summary:{totalRenders:n.length,totalBindings:a.length,totalActions:i.length,slowestComponents:c,hotPaths:f}}}resetPerformanceStats(){this.renderCounts.clear(),this.bindingEvalCount=0,this.profilingData=[]}trackRequest(e,n,a){if(!this.config.enabled)return"";const i=this.generateId(),l=k_(e),c=R_(e);return this.activeRequests.set(i,{id:i,url:l,fullUrl:e,queryParams:c,method:n,startTime:Date.now(),status:"pending",requestBody:a?.requestBody?this.sanitizeObject(a.requestBody):void 0,dataSourceId:a?.dataSourceId}),this.isProfiling&&this.profilingData.push({type:"network",timestamp:performance.now()}),i}completeRequest(e,n,a){if(!this.config.enabled)return;const i=this.activeRequests.get(e);if(!i)return;const l={...i,status:n>=200&&n<300?"success":"error",statusCode:n,duration:Date.now()-i.startTime,endTime:Date.now(),response:this.sanitizeObject(a)};this.requestHistory.push(l),this.activeRequests.delete(e),this.requestHistory.length>this.config.maxHistorySize&&this.requestHistory.shift(),this.notifyRequestWatchers(l)}failRequest(e,n){if(!this.config.enabled)return;const a=this.activeRequests.get(e);if(!a)return;const i={...a,status:"error",error:n,duration:Date.now()-a.startTime,endTime:Date.now()};this.requestHistory.push(i),this.activeRequests.delete(e),this.notifyRequestWatchers(i)}trackDataSource(e){this.config.enabled&&this.pendingDataSources.add(e)}completeDataSource(e){this.config.enabled&&this.pendingDataSources.delete(e)}getActiveRequests(){return Array.from(this.activeRequests.values())}getRequestHistory(){return[...this.requestHistory]}getPendingDataSources(){return Array.from(this.pendingDataSources)}watchRequest(e){return this.requestWatchers.add(e),()=>{this.requestWatchers.delete(e)}}notifyRequestWatchers(e){this.requestWatchers.forEach(n=>{try{n(e)}catch(a){console.error("[G7DevTools] Request watcher error:",a)}})}trackWebSocketConnection(e,n,a){this.config.enabled&&this.wsConnections.set(e,{id:e,url:n,state:a,connectedAt:a==="open"?Date.now():void 0})}trackWebSocketMessage(e,n,a,i){if(!this.config.enabled)return;const l={id:this.generateId(),connectionId:e,direction:n,type:a,payload:this.sanitizeObject(i),timestamp:Date.now(),sequence:this.wsMessageHistory.length};this.wsMessageHistory.push(l),this.wsMessageHistory.length>this.config.maxHistorySize&&this.wsMessageHistory.shift(),this.notifyWebSocketWatchers(l)}getWebSocketConnections(){return Array.from(this.wsConnections.values())}getWebSocketMessageHistory(){return[...this.wsMessageHistory]}getWebSocketConnectionState(){const e=Array.from(this.wsConnections.values());return e.some(n=>n.state==="open")?"connected":e.some(n=>n.state==="connecting")?"reconnecting":"disconnected"}watchWebSocketMessage(e){return this.wsMessageWatchers.add(e),()=>{this.wsMessageWatchers.delete(e)}}notifyWebSocketWatchers(e){this.wsMessageWatchers.forEach(n=>{try{n(e)}catch(a){console.error("[G7DevTools] WebSocket watcher error:",a)}})}trackIfCondition(e,n,a){if(!this.config.enabled)return;const i=this.ifConditions.get(e),l=i?.evaluatedValue;this.ifConditions.set(e,{id:e,expression:n,evaluatedValue:a,evaluationCount:(i?.evaluationCount||0)+1,lastEvaluated:Date.now()}),i&&l!==a&&this.notifyConditionWatchers({id:e,expression:n,oldValue:l,newValue:a,timestamp:Date.now()})}trackIteration(e,n){this.config.enabled&&this.iterations.set(e,{id:e,...n,lastRendered:Date.now()})}getIfConditions(){return Array.from(this.ifConditions.values())}getIterations(){return Array.from(this.iterations.values())}getScopeChain(e){const n=[],a=this.getState();return n.push({name:"_global",value:a._global,source:"global"}),n.push({name:"_local",value:a._local,source:"local"}),n.push({name:"_computed",value:a._computed,source:"computed"}),n}watchConditionChange(e){return this.conditionWatchers.add(e),()=>{this.conditionWatchers.delete(e)}}notifyConditionWatchers(e){this.conditionWatchers.forEach(n=>{try{n(e)}catch(a){console.error("[G7DevTools] Condition watcher error:",a)}})}trackForm(e,n,a){this.config.enabled&&this.forms.set(e,{id:e,dataKey:n,inputs:a,trackedAt:Date.now()})}trackFormChange(e,n,a){if(!this.config.enabled)return;const i={formId:e,inputName:n,value:a,timestamp:Date.now()};this.notifyFormWatchers(i)}untrackForm(e){this.forms.delete(e)}getForms(){return Array.from(this.forms.values())}getFormState(e){const n=this.getState();return this.getNestedValue(n,e)||{}}getBindingPath(e,n){return`${e}.${n}`}watchFormChange(e){return this.formWatchers.add(e),()=>{this.formWatchers.delete(e)}}notifyFormWatchers(e){this.formWatchers.forEach(n=>{try{n(e)}catch(a){console.error("[G7DevTools] Form watcher error:",a)}})}trackFormBindingIssue(e,n,a){if(!this.config.enabled)return;const i={id:`form-issue-${++this.formBindingIssueIdCounter}`,timestamp:Date.now(),type:e,severity:n,formId:a.formId,formDataKey:a.formDataKey,inputInfo:a.inputInfo,contextInfo:a.contextInfo,description:a.description,suggestion:a.suggestion,docLink:a.docLink};this.formBindingIssues.push(i),this.formBindingIssues.length>this.maxFormBindingIssues&&this.formBindingIssues.shift()}validateFormBinding(e,n,a,i){const l=a.map(f=>{const g=[];let m=!0;return f.name||(g.push("Input에 name 속성이 없습니다"),m=!1,this.trackFormBindingIssue("missing-input-name","error",{formId:e,formDataKey:n,inputInfo:{name:f.name,type:f.type},description:"Input에 name 속성이 없어 자동 바인딩이 동작하지 않습니다",suggestion:"Input에 name 속성을 추가하세요",docLink:"troubleshooting-components-form.md"})),n||(g.push("Form에 dataKey가 없습니다"),m=!1),{inputName:f.name||"(unnamed)",inputType:f.type,bindingPath:n&&f.name?`${n}.${f.name}`:"-",isValid:m,currentValue:f.value,issues:g}});if(n||this.trackFormBindingIssue("missing-datakey","error",{formId:e,description:"Form에 dataKey가 설정되지 않아 자동 바인딩이 동작하지 않습니다",suggestion:"Form 컴포넌트에 dataKey props를 설정하세요",docLink:"troubleshooting-components-form.md"}),i.isContextBroken){const f=i.breakReason?.includes("sortable")?"sortable-context-break":i.breakReason?.includes("modal")?"modal-context-isolation":"context-not-propagated";this.trackFormBindingIssue(f,"warning",{formId:e,formDataKey:n,contextInfo:{hasParentFormContext:i.hasParentContext,parentFormContextProp:i.parentContextProp,isInsideSortable:i.breakReason?.includes("sortable")||!1,isInsideModal:i.breakReason?.includes("modal")||!1,depth:0},description:i.breakReason||"Form 컨텍스트가 제대로 전파되지 않았습니다",suggestion:"parentFormContextProp={undefined}를 확인하거나 중첩 구조를 검토하세요",docLink:"troubleshooting-components-form.md"})}const c={formId:e,dataKey:n,timestamp:Date.now(),contextPropagation:i,inputBindings:l,isValid:l.every(f=>f.isValid)&&!!n&&!i.isContextBroken,issueCount:l.filter(f=>!f.isValid).length+(n?0:1)+(i.isContextBroken?1:0)},d=this.formBindingValidations.findIndex(f=>f.formId===e);return d>=0?this.formBindingValidations[d]=c:this.formBindingValidations.push(c),c}getFormBindingValidationTrackingInfo(){const e=this.getFormBindingValidationStats();return{issues:[...this.formBindingIssues],validations:[...this.formBindingValidations],stats:e,timestamp:Date.now()}}getFormBindingValidationStats(){const e={"missing-datakey":0,"missing-input-name":0,"context-not-propagated":0,"sortable-context-break":0,"modal-context-isolation":0,"duplicate-input-name":0,"value-type-mismatch":0,"binding-path-invalid":0},n={info:0,warning:0,error:0};for(const i of this.formBindingIssues)e[i.type]++,n[i.severity]++;const a=Object.entries(e).filter(([,i])=>i>0).sort((i,l)=>l[1]-i[1]).slice(0,5).map(([i,l])=>({type:i,count:l,description:this.getIssueTypeDescription(i)}));return{totalFormsValidated:this.formBindingValidations.length,validForms:this.formBindingValidations.filter(i=>i.isValid).length,formsWithIssues:this.formBindingValidations.filter(i=>!i.isValid).length,totalIssues:this.formBindingIssues.length,byIssueType:e,bySeverity:n,topIssues:a}}getIssueTypeDescription(e){return{"missing-datakey":"Form에 dataKey 누락","missing-input-name":"Input에 name 속성 누락","context-not-propagated":"Form 컨텍스트 전파 실패","sortable-context-break":"Sortable 컨테이너에서 컨텍스트 단절","modal-context-isolation":"모달에서 부모 Form 컨텍스트 격리","duplicate-input-name":"동일 Form 내 중복 Input name","value-type-mismatch":"값 타입과 Input 타입 불일치","binding-path-invalid":"바인딩 경로 유효하지 않음"}[e]||e}clearFormBindingIssues(){this.formBindingIssues=[],this.formBindingValidations=[],this.formBindingIssueIdCounter=0}trackComputedProperty(e,n,a,i,l,c,d){if(!this.config.enabled)return;const f=`computed-${e}-${c||"global"}`,g={id:f,name:e,expression:n,componentId:c,dependencies:a,currentValue:this.safeClone(i),lastComputedAt:Date.now(),computationTime:l,error:d};this.computedProperties.set(f,g)}trackComputedRecalc(e,n,a,i,l,c,d){if(!this.config.enabled)return;const f=`computed-${e}-${d||"global"}`,g=JSON.stringify(a)!==JSON.stringify(i),m={id:`recalc-${++this.computedRecalcIdCounter}`,computedId:f,computedName:e,timestamp:Date.now(),trigger:n,triggeredBy:c,previousValue:this.safeClone(a),newValue:this.safeClone(i),valueChanged:g,computationTime:l,cascadeCount:0};this.computedRecalcLogs.push(m),this.computedRecalcLogs.length>this.maxComputedRecalcLogs&&this.computedRecalcLogs.shift();const b=this.computedProperties.get(f);b&&(b.currentValue=this.safeClone(i),b.lastComputedAt=Date.now(),b.computationTime=l)}analyzeComputedDependencyChain(e,n){const a=new Set,i=[];let l=!1,c,d=0;const f=(m,b)=>{if(d=Math.max(d,b),a.has(m))return l=!0,c=[...i,m],{name:m,type:"computed",children:[],depth:b};a.add(m),i.push(m);const S=this.computedProperties.get(`computed-${m}-${n||"global"}`),v=[];if(S)for(const _ of S.dependencies)_.type==="computed"?v.push(f(_.path,b+1)):v.push({name:_.path,type:_.type,children:[],depth:b+1});return i.pop(),a.delete(m),{name:m,type:"computed",children:v,depth:b}},g=f(e,0);return{root:e,tree:g,hasCycle:l,cyclePath:c,maxDepth:d}}getComputedDependencyTrackingInfo(){const e=Array.from(this.computedProperties.values()),n=this.getComputedDependencyStats(),a=[];for(const i of e){const l=this.analyzeComputedDependencyChain(i.name,i.componentId);a.push(l)}return{properties:e,recalcLogs:[...this.computedRecalcLogs],dependencyChains:a,stats:n,timestamp:Date.now()}}getComputedDependencyStats(){const e={"state-change":0,"datasource-update":0,"dependency-change":0,manual:0,initial:0},n={},a={};let i=0,l=0;for(const g of this.computedRecalcLogs)e[g.trigger]++,g.valueChanged||l++,n[g.computedName]=(n[g.computedName]||0)+1,a[g.computedName]||(a[g.computedName]=[]),a[g.computedName].push(g.computationTime),i+=g.computationTime;const c=Object.entries(n).sort((g,m)=>m[1]-g[1]).slice(0,5).map(([g,m])=>({name:g,count:m})),d=Object.entries(a).map(([g,m])=>({name:g,avgTime:m.reduce((b,S)=>b+S,0)/m.length})).sort((g,m)=>m.avgTime-g.avgTime).slice(0,5);let f=0;for(const g of this.computedProperties.values())this.analyzeComputedDependencyChain(g.name,g.componentId).hasCycle&&f++;return{totalComputed:this.computedProperties.size,totalRecalculations:this.computedRecalcLogs.length,unnecessaryRecalculations:l,avgComputationTime:this.computedRecalcLogs.length>0?i/this.computedRecalcLogs.length:0,topRecalculated:c,slowestComputed:d,cycleDetectionCount:f,byTrigger:e}}clearComputedTracking(){this.computedProperties.clear(),this.computedRecalcLogs=[],this.computedRecalcIdCounter=0}trackModalOpen(e){if(!this.config.enabled)return;const n={modalId:e.modalId,modalName:e.modalName,openedAt:Date.now(),closedAt:null,scopeType:e.scopeType||"isolated",parentModalId:e.parentModalId,componentId:e.componentId,initialState:e.initialState?{...e.initialState}:{},currentState:e.initialState?{...e.initialState}:{},stateChangeCount:0,isolatedStateKeys:e.isolatedStateKeys||[],sharedStateKeys:e.sharedStateKeys||[]};this.modalStates.set(e.modalId,n),this.validateModalDefinitionExists(e.modalId,e.modalName),e.parentModalId&&(this.modalStateRelations.find(i=>i.parentModalId===e.parentModalId&&i.childModalId===e.modalId)||this.modalStateRelations.push({parentModalId:e.parentModalId,childModalId:e.modalId,sharedKeys:e.sharedStateKeys||[],isolatedKeys:e.isolatedStateKeys||[],relationType:"parent-child"}))}trackModalClose(e,n){if(!this.config.enabled)return;const a=this.modalStates.get(e);a&&(a.closedAt=Date.now(),n&&(a.currentState={...n}),this.detectStateLeakage(a))}validateModalDefinitionExists(e,n){const a=this.currentLayout;if(!a?.layoutJson)return;const i=a.layoutJson.modals;if(!i){this.recordModalStateIssue({type:"missing-definition",modalId:e,modalName:n,severity:"error",description:`모달 "${e}"이(가) modalStack에 추가되었으나, 현재 레이아웃에 modals 섹션이 없습니다. 레이아웃 경로: ${a.layoutPath}`,affectedStateKeys:[],expectedValue:`modals 섹션에 id="${e}" 정의 존재`,actualValue:"modals 섹션 없음"});return}const l=Array.isArray(i)?i:Object.values(i);l.some(d=>d?.id===e||d?.props?.id===e)||this.recordModalStateIssue({type:"missing-definition",modalId:e,modalName:n,severity:"error",description:`모달 "${e}"이(가) modalStack에 추가되었으나, 렌더링된 레이아웃의 modals 섹션에 해당 ID의 정의가 없습니다. partial 로딩 실패, 레이아웃 병합 문제, extends 상속 누락 등을 확인하세요. 레이아웃 경로: ${a.layoutPath}`,affectedStateKeys:[],expectedValue:`modals 배열에 id="${e}" 항목 존재`,actualValue:`modals 배열에 ${l.length}개 모달 정의 (${l.map(d=>d?.id||"unknown").join(", ")})`})}trackModalStateChange(e){if(!this.config.enabled)return;const n=this.modalStates.get(e.modalId);if(!n)return;n.stateChangeCount++,n.currentState[e.stateKey]=e.newValue;const a=this.checkIsolationViolation(n,e.stateKey),i={id:`modal-change-${++this.modalStateChangeLogIdCounter}`,modalId:e.modalId,modalName:n.modalName,timestamp:Date.now(),stateKey:e.stateKey,previousValue:e.previousValue,newValue:e.newValue,changeSource:e.changeSource||"user-action",violatesIsolation:a};this.modalStateChangeLogs.push(i),this.modalStateChangeLogs.length>this.maxModalStateChangeLogs&&this.modalStateChangeLogs.shift(),a&&this.recordModalStateIssue({type:"isolation-violation",modalId:e.modalId,modalName:n.modalName,severity:"warning",description:`모달 '${n.modalName}'에서 격리된 상태 키 '${e.stateKey}'가 변경됨`,affectedStateKeys:[e.stateKey]})}detectStateLeakage(e){if(e.scopeType==="isolated")for(const n of e.isolatedStateKeys){const a=e.initialState[n],i=e.currentState[n];if(a!==i&&e.parentModalId){const l=this.modalStates.get(e.parentModalId);l&&l.currentState.hasOwnProperty(n)&&this.recordModalStateIssue({type:"state-leakage",modalId:e.modalId,modalName:e.modalName,severity:"error",description:`모달 닫힘 후 상태 '${n}'가 부모 모달로 유출될 수 있음`,affectedStateKeys:[n],leakedValue:i,expectedValue:a})}}}checkIsolationViolation(e,n){return e.scopeType==="isolated",!1}recordModalStateIssue(e){const n={id:`modal-issue-${++this.modalStateIssueIdCounter}`,type:e.type,modalId:e.modalId,modalName:e.modalName,timestamp:Date.now(),severity:e.severity,description:e.description,affectedStateKeys:e.affectedStateKeys,leakedValue:e.leakedValue,expectedValue:e.expectedValue,actualValue:e.actualValue,stackInfo:e.stackInfo};this.modalStateIssues.push(n),this.modalStateIssues.length>this.maxModalStateIssues&&this.modalStateIssues.shift()}getModalStateScopeTrackingInfo(){const e=Array.from(this.modalStates.values()),n=this.getModalStateScopeStats();return{modals:e,issues:[...this.modalStateIssues],relations:[...this.modalStateRelations],changeLogs:[...this.modalStateChangeLogs],stats:n,timestamp:Date.now()}}getModalStateScopeStats(){const e=Array.from(this.modalStates.values()),n={warning:0,error:0},a={"state-leakage":0,"isolation-violation":0,"parent-mutation":0,"orphaned-state":0,"scope-mismatch":0,"cleanup-failure":0,"missing-definition":0};for(const l of this.modalStateIssues)n[l.severity]++,a[l.type]++;const i={isolated:0,shared:0,inherited:0};for(const l of e)i[l.scopeType]++;return{totalModals:e.length,openModals:e.filter(l=>l.closedAt===null).length,nestedModals:e.filter(l=>l.parentModalId!==void 0).length,totalIssues:this.modalStateIssues.length,issuesBySeverity:n,issuesByType:a,byScope:i,leakageDetectionCount:a["state-leakage"],cleanupFailureCount:a["cleanup-failure"]}}clearModalStateScopeTracking(){this.modalStates.clear(),this.modalStateIssues=[],this.modalStateRelations=[],this.modalStateChangeLogs=[],this.modalStateIssueIdCounter=0,this.modalStateChangeLogIdCounter=0}setNamedActionDefinitions(e){this.config.enabled&&(this.namedActionDefinitions=e||{})}trackNamedActionRef(e){if(!this.config.enabled)return;const n={...e,id:`named_action_ref_${++this.namedActionRefIdCounter}`};this.namedActionRefLogs.push(n),this.namedActionRefLogs.length>this.maxNamedActionRefLogs&&(this.namedActionRefLogs=this.namedActionRefLogs.slice(-this.maxNamedActionRefLogs))}getNamedActionTrackingInfo(){const e={};for(const a of this.namedActionRefLogs)e[a.actionRefName]=(e[a.actionRefName]||0)+1;const n=Object.keys(this.namedActionDefinitions).filter(a=>!e[a]);return{definitions:this.namedActionDefinitions,refLogs:[...this.namedActionRefLogs],stats:{totalDefinitions:Object.keys(this.namedActionDefinitions).length,totalRefs:this.namedActionRefLogs.length,refCountByName:e,unusedDefinitions:n},timestamp:Date.now()}}clearNamedActionTracking(){this.namedActionDefinitions={},this.namedActionRefLogs=[],this.namedActionRefIdCounter=0}trackExpressionEval(e){if(!this.config.enabled)return;const n={...e,id:`expr-${++this.expressionIdCounter}`,timestamp:Date.now(),warning:this.detectExpressionWarning(e)};this.expressionHistory.push(n),this.expressionHistory.length>500&&this.expressionHistory.shift(),this.isProfiling&&this.profilingData.push({type:"binding",expression:e.expression,timestamp:performance.now(),duration:e.duration}),this.notifyExpressionWatchers(n)}detectExpressionWarning(e){const{expression:n,result:a,resultType:i,method:l,duration:c}=e;if(i==="undefined"&&!n.includes("??")&&!n.includes("||")&&/^[a-zA-Z_$][a-zA-Z0-9_$.]*$/.test(n.replace(/{{|}}/g,"").trim()))return{type:"undefined-result",message:"표현식 결과가 undefined입니다",suggestion:`데이터가 로드되었는지 확인하거나 fallback 값을 추가하세요: ${n} ?? ''`};if(i==="null")return{type:"null-result",message:"표현식 결과가 null입니다",suggestion:`nullish coalescing 연산자를 사용하세요: ${n} ?? ''`};if(l==="resolveBindings"&&i==="string"){const d=String(a);if(d.includes("[object Object]")||/^\[.*\]$/.test(d))return{type:"array-to-string",message:"배열/객체가 문자열로 변환되었습니다",suggestion:"evaluateExpression()을 사용하여 원본 타입을 유지하세요"}}if(l==="resolveBindings"&&i==="string"&&String(a).includes("[object Object]"))return{type:"object-to-string",message:"객체가 [object Object]로 변환되었습니다",suggestion:"evaluateExpression()을 사용하거나 특정 속성에 접근하세요"};if(i==="undefined"&&n.includes(".")&&!n.includes("?.")){const d=n.replace(/{{|}}/g,"").trim().split(".");if(d.length>=2)return{type:"missing-optional-chain",message:"Optional chaining(?.) 누락 가능성",suggestion:`안전한 접근을 위해 ?. 사용을 고려하세요: ${d.join("?.")}`}}if((n.includes("{{item.")||n.includes("{{index}}"))&&Array.from(this.iterations.values()).some(g=>g.itemVar!=="item"))return{type:"wrong-iteration-var",message:"'item'/'index' 대신 iteration에서 정의한 변수명을 사용해야 합니다",suggestion:"iteration의 item_var/index_var에 정의된 변수명을 확인하세요"};if(c&&c>10)return{type:"slow-evaluation",message:`표현식 평가에 ${c.toFixed(2)}ms 소요됨`,suggestion:"복잡한 표현식을 단순화하거나 computed 값으로 분리하세요"}}getExpressions(){return[...this.expressionHistory]}getExpressionWarnings(){return this.expressionHistory.filter(e=>e.warning!=null)}searchExpressions(e){const n=e.toLowerCase();return this.expressionHistory.filter(a=>a.expression.toLowerCase().includes(n)||a.componentName?.toLowerCase().includes(n)||a.propName?.toLowerCase().includes(n))}getExpressionStats(){const e=this.expressionHistory,n=new Set(e.map(f=>f.expression)),a=e.filter(f=>f.warning!=null),i=e.filter(f=>f.fromCache),l=e.filter(f=>f.duration!=null).map(f=>f.duration),c={};for(const f of e)c[f.resultType]=(c[f.resultType]||0)+1;const d={};for(const f of a)f.warning&&(d[f.warning.type]=(d[f.warning.type]||0)+1);return{totalEvaluations:e.length,uniqueExpressions:n.size,warningCount:a.length,cacheHitRate:e.length>0?i.length/e.length:0,averageDuration:l.length>0?l.reduce((f,g)=>f+g,0)/l.length:0,byType:c,byWarning:d}}clearExpressions(){this.expressionHistory=[],this.expressionIdCounter=0}watchExpressions(e){return this.expressionWatchers.add(e),()=>{this.expressionWatchers.delete(e)}}notifyExpressionWatchers(e){this.expressionWatchers.forEach(n=>{try{n(e)}catch(a){console.error("[G7DevTools] Expression watcher error:",a)}})}setLogLevel(e){this.config.logLevel=e}setMaxHistory(e){this.config.maxHistorySize=Math.max(10,Math.min(1e3,e))}setServerEndpoint(e){this.config.serverEndpoint=e}getConfig(){return{...this.config}}generateId(){return`${Date.now()}-${Math.random().toString(36).substr(2,9)}`}sanitizeObject(e,n=new WeakSet){if(e===null||typeof e!="object")return e;if(n.has(e))return"[Circular]";if(n.add(e),Array.isArray(e))return e.map(i=>this.sanitizeObject(i,n));const a={};for(const i of Object.keys(e))try{a[i]=this.sanitizeObject(e[i],n)}catch{a[i]="[Unable to serialize]"}return a}getNestedValue(e,n){if(!n)return e;const a=n.split(".");let i=e;for(const l of a){if(i==null)return;i=i[l]}return i}getLifecycleInfo(){return{mountedComponents:Array.from(this.mountedComponents.values()),orphanedListeners:this.getOrphanedListeners()}}getPerformanceInfo(){return{renderCounts:this.renderCounts,bindingEvalCount:this.bindingEvalCount,memoryWarnings:this.getMemoryWarnings()}}getNetworkInfo(){return{activeRequests:Array.from(this.activeRequests.values()),requestHistory:[...this.requestHistory],pendingDataSources:Array.from(this.pendingDataSources)}}getConditionalInfo(){return{ifConditions:Array.from(this.ifConditions.values()),iterations:Array.from(this.iterations.values())}}getFormInfo(){return Array.from(this.forms.values())}getWebSocketInfo(){return{connections:Array.from(this.wsConnections.values()),messageHistory:[...this.wsMessageHistory],connectionState:this.getWebSocketConnectionState()}}getStateAtTime(e){const n=[...this.stateHistory].reverse().find(a=>a.timestamp<=e);if(n)return{_global:n.next,_local:{}}}reset(){this.stateHistory=[],this.actionHistory=[],this.cacheStats={hits:0,misses:0,entries:0},this.cacheDecisions=[],this.cacheDecisionIdCounter=0,this.currentRenderCycleId=null,this.isInActionExecution=!1,this.isInIteration=!1,this.mountedComponents.clear(),this.eventListeners.clear(),this.renderCounts.clear(),this.bindingEvalCount=0,this.profilingData=[],this.activeRequests.clear(),this.requestHistory=[],this.pendingDataSources.clear(),this.wsConnections.clear(),this.wsMessageHistory=[],this.ifConditions.clear(),this.iterations.clear(),this.forms.clear(),this.expressionHistory=[],this.expressionIdCounter=0,this.dataSources.clear(),this.dataPathTransforms=[],this.nestedContexts=[],this.nestedContextIdCounter=0,this.formBindingIssues=[],this.formBindingValidations=[],this.formBindingIssueIdCounter=0,this.computedProperties.clear(),this.computedRecalcLogs=[],this.computedRecalcIdCounter=0,this.modalStates.clear(),this.modalStateIssues=[],this.modalStateRelations=[],this.modalStateChangeLogs=[],this.modalStateIssueIdCounter=0,this.modalStateChangeLogIdCounter=0,this.logHistory=[],this.logIdCounter=0}getExpressionInfo(){return{expressions:this.getExpressions(),stats:this.getExpressionStats()}}trackLog(e,n,a){if(!this.config.enabled)return;const i=a.map(c=>{if(typeof c=="string")return c;try{return JSON.stringify(c)}catch{return String(c)}}).join(" "),l={id:`log-${++this.logIdCounter}`,level:e,prefix:n,message:i,args:this.sanitizeObject(a),timestamp:Date.now(),stack:e==="error"?new Error().stack:void 0};this.logHistory.push(l),this.logHistory.length>this.maxLogHistory&&this.logHistory.shift()}trackAction(e){if(!this.config.enabled)return;const n={id:`debounce-action-${++this.debounceActionIdCounter}`,...e,timestamp:Date.now()};this.debounceActionHistory.push(n),this.debounceActionHistory.length>100&&this.debounceActionHistory.shift()}trackDataSourceUpdate(e){if(!this.config.enabled)return;const n={id:`ds-update-${++this.dataSourceUpdateIdCounter}`,...e};this.dataSourceUpdateHistory.push(n),this.dataSourceUpdateHistory.length>100&&this.dataSourceUpdateHistory.shift()}getDebounceActionHistory(e){let n=[...this.debounceActionHistory];return e?.handler&&(n=n.filter(a=>a.handler.includes(e.handler))),e?.status&&(n=n.filter(a=>a.status===e.status)),e?.limit&&(n=n.slice(-e.limit)),n}getDataSourceUpdateHistory(e){let n=[...this.dataSourceUpdateHistory];return e?.dataSourceId&&(n=n.filter(a=>a.dataSourceId===e.dataSourceId)),e?.updateType&&(n=n.filter(a=>a.updateType===e.updateType)),e?.limit&&(n=n.slice(-e.limit)),n}getLogs(e){let n=[...this.logHistory];if(e?.level){const a=Array.isArray(e.level)?e.level:[e.level];n=n.filter(i=>a.includes(i.level))}if(e?.prefix){const a=e.prefix.toLowerCase();n=n.filter(i=>i.prefix.toLowerCase().includes(a))}if(e?.search){const a=e.search.toLowerCase();n=n.filter(i=>i.message.toLowerCase().includes(a))}return e?.since&&(n=n.filter(a=>a.timestamp>=e.since)),e?.limit&&(n=n.slice(-e.limit)),n}getLogStats(){const n=Date.now()-6e4,a={log:0,warn:0,error:0,debug:0,info:0},i={};let l=0,c=0;for(const d of this.logHistory)a[d.level]++,i[d.prefix]=(i[d.prefix]||0)+1,d.timestamp>=n&&(d.level==="error"&&l++,d.level==="warn"&&c++);return{totalLogs:this.logHistory.length,byLevel:a,byPrefix:i,recentErrors:l,recentWarnings:c}}clearLogs(){this.logHistory=[],this.logIdCounter=0}getLogInfo(){return{entries:this.getLogs(),stats:this.getLogStats()}}trackDataSourceDefinition(e){this.config.enabled&&this.dataSources.set(e.id,{...e,status:"idle"})}trackDataSourceLoading(e){if(!this.config.enabled)return;const n=this.dataSources.get(e);n&&this.dataSources.set(e,{...n,status:"loading"})}trackDataSourceLoaded(e,n,a){if(!this.config.enabled)return;const i=this.dataSources.get(e);if(!i)return;let l,c,d=a;n&&(Array.isArray(n)?(l=n.length,c=n.length>0&&typeof n[0]=="object"?Object.keys(n[0]):void 0):typeof n=="object"&&(Array.isArray(n.data)?(d=d||"data",l=n.data.length,c=n.data.length>0&&typeof n.data[0]=="object"?Object.keys(n.data[0]):void 0):Array.isArray(n.items)?(d=d||"items",l=n.items.length,c=n.items.length>0&&typeof n.items[0]=="object"?Object.keys(n.items[0]):void 0):c=Object.keys(n))),this.dataSources.set(e,{...i,status:"loaded",dataPath:d,itemCount:l,keys:c,lastLoadedAt:Date.now(),error:void 0})}trackDataSourceError(e,n){if(!this.config.enabled)return;const a=this.dataSources.get(e);a&&this.dataSources.set(e,{...a,status:"error",error:n,lastLoadedAt:Date.now()})}untrackDataSource(e){this.dataSources.delete(e)}getDataSources(){return Array.from(this.dataSources.values())}getDataSource(e){return this.dataSources.get(e)}clearDataSources(){this.dataSources.clear()}trackDataPathTransform(e){if(!this.config.enabled)return;let n=this.dataPathTransforms.find(a=>a.dataSourceId===e.dataSourceId);n||(n={dataSourceId:e.dataSourceId,timestamp:Date.now(),transformSteps:[],warnings:[]},this.dataPathTransforms.push(n),this.dataPathTransforms.length>this.maxDataPathTransforms&&this.dataPathTransforms.shift()),n.transformSteps.push({step:e.step,inputPath:e.inputPath,inputValue:this.safeClone(e.inputValue),outputPath:e.outputPath,outputValue:this.safeClone(e.outputValue),config:e.config}),e.warning&&n.warnings.push(e.warning)}setDataPathFinalBinding(e,n){if(!this.config.enabled)return;const a=this.dataPathTransforms.find(i=>i.dataSourceId===e);a&&(a.finalBinding={expression:n.expression,resolvedPath:n.resolvedPath,value:this.safeClone(n.value)})}getDataPathTransformTrackingInfo(){const e=this.getDataPathTransformStats();return{transforms:[...this.dataPathTransforms],stats:e,timestamp:Date.now()}}getDataPathTransformStats(){const e=this.dataPathTransforms,n={};let a=0;const i={};for(const c of e){n[c.dataSourceId]=(n[c.dataSourceId]||0)+1,a+=c.warnings.length;for(const d of c.warnings)i[d]=(i[d]||0)+1}const l=Object.entries(i).map(([c,d])=>({warning:c,count:d})).sort((c,d)=>d.count-c.count).slice(0,10);return{totalTransforms:e.length,byDataSource:n,warningsCount:a,commonWarnings:l}}getDataPathTransformForDataSource(e){return this.dataPathTransforms.find(n=>n.dataSourceId===e)}clearDataPathTransforms(){this.dataPathTransforms=[]}trackNestedContext(e){if(!this.config.enabled)return"";const n=`nested-${++this.nestedContextIdCounter}`,a=[...new Set([...e.parentContext.available,...e.ownContext.added])],i={...e.parentContext.values,...e.ownContext.values},l={id:n,timestamp:Date.now(),componentId:e.componentId,componentType:e.componentType,parentContext:{available:e.parentContext.available,values:this.safeClone(e.parentContext.values)},ownContext:{added:e.ownContext.added,values:this.safeClone(e.ownContext.values)},mergedContext:{all:a,values:this.safeClone(i)},accessAttempts:[],depth:e.depth,parentId:e.parentId};return this.nestedContexts.push(l),this.nestedContexts.length>this.maxNestedContexts&&this.nestedContexts.shift(),n}trackNestedContextAccess(e,n){if(!this.config.enabled)return;const a=this.nestedContexts.find(i=>i.id===e);a&&a.accessAttempts.push({path:n.path,found:n.found,value:n.found?this.safeClone(n.value):void 0,error:n.error})}getNestedContextTrackingInfo(){const e=this.getNestedContextStats();return{contexts:[...this.nestedContexts],stats:e,timestamp:Date.now()}}getNestedContextStats(){const e=this.nestedContexts,n={expandChildren:0,cellChildren:0,iteration:0,modal:0,slot:0};let a=0,i=0;const l={};for(const d of e){n[d.componentType]++,d.depth>a&&(a=d.depth);for(const f of d.accessAttempts)f.found||(i++,l[f.path]=(l[f.path]||0)+1)}const c=Object.entries(l).map(([d,f])=>({path:d,count:f})).sort((d,f)=>f.count-d.count).slice(0,10);return{totalContexts:e.length,byType:n,maxDepth:a,failedAccessCount:i,commonFailedPaths:c}}getNestedContextForComponent(e){return this.nestedContexts.find(n=>n.componentId===e)}clearNestedContexts(){this.nestedContexts=[],this.nestedContextIdCounter=0}trackHandlerRegistration(e,n="custom",a,i){this.isEnabled&&this.handlers.set(e,{name:e,category:n,description:a,registeredAt:Date.now(),source:i})}trackHandlerUnregistration(e){this.isEnabled&&this.handlers.delete(e)}getHandlers(){return Array.from(this.handlers.values())}getHandler(e){return this.handlers.get(e)}getHandlersByCategory(e){return Array.from(this.handlers.values()).filter(n=>n.category===e)}clearHandlers(){this.handlers.clear()}trackEventSubscribe(e){if(!this.isEnabled)return;const n=this.componentEventSubscriptions.get(e),a=Date.now();n?(n.subscriberCount++,n.lastSubscribedAt=a):this.componentEventSubscriptions.set(e,{eventName:e,subscriberCount:1,firstSubscribedAt:a,lastSubscribedAt:a})}trackEventUnsubscribe(e){if(!this.isEnabled)return;const n=this.componentEventSubscriptions.get(e);n&&(n.subscriberCount--,n.subscriberCount<=0&&this.componentEventSubscriptions.delete(e))}trackEventEmit(e,n,a,i,l){if(!this.isEnabled)return;const c={id:`evt_${++this.componentEventIdCounter}`,eventName:e,data:this.sanitizeForLogging(n),timestamp:Date.now(),listenerCount:a,results:i?this.sanitizeForLogging(i):void 0,hasError:!!l,errorMessage:l?.message};this.componentEventEmitHistory.push(c),this.componentEventEmitHistory.length>this.maxComponentEventHistory&&(this.componentEventEmitHistory=this.componentEventEmitHistory.slice(-this.maxComponentEventHistory))}trackEventOff(e){this.isEnabled&&this.componentEventSubscriptions.delete(e)}trackEventClear(){this.isEnabled&&this.componentEventSubscriptions.clear()}getComponentEventInfo(){const e=Array.from(this.componentEventSubscriptions.values());return{subscriptions:e,emitHistory:[...this.componentEventEmitHistory],totalSubscribers:e.reduce((n,a)=>n+a.subscriberCount,0),totalEmits:this.componentEventEmitHistory.length}}getEventEmitHistory(){return[...this.componentEventEmitHistory]}getEventSubscriptions(){return Array.from(this.componentEventSubscriptions.values())}clearComponentEvents(){this.componentEventSubscriptions.clear(),this.componentEventEmitHistory=[],this.componentEventIdCounter=0}sanitizeForLogging(e){if(e==null||typeof e!="object")return e;try{return JSON.parse(JSON.stringify(e))}catch{return"[Circular or Non-serializable]"}}startStateChange(e,n,a,i){if(!this.config.enabled)return"";const l=`setState_${++this.stateRenderingIdCounter}`;this.currentStateChangeContext={setStateId:l,startTime:performance.now(),changedPath:e,trigger:i||{},renderedComponents:[]};const c={id:`sr_${this.stateRenderingIdCounter}`,setStateId:l,statePath:e,oldValue:this.sanitizeForLogging(n),newValue:this.sanitizeForLogging(a),timestamp:Date.now(),triggeredBy:i||{},renderedComponents:[],totalRenderDuration:0,affectedBindingsCount:0};return this.stateRenderingLogs.push(c),this.stateRenderingLogs.length>this.maxStateRenderingLogs&&this.stateRenderingLogs.shift(),l}trackComponentRender(e,n,a,i=[],l=[],c){if(!this.config.enabled)return;const d=this.componentRenderCounts.get(n)||0;this.componentRenderCounts.set(n,d+1);for(const f of i)this.stateToComponentMap.has(f)||this.stateToComponentMap.set(f,new Set),this.stateToComponentMap.get(f).add(n);if(this.currentStateChangeContext){const f={componentId:e,componentName:n,renderDuration:a,accessedStatePaths:i,evaluatedBindings:l,renderOrder:this.currentStateChangeContext.renderedComponents.length,parentId:c};this.currentStateChangeContext.renderedComponents.push(f)}this.trackRender(n)}completeStateChange(e){if(!this.config.enabled||!this.currentStateChangeContext||this.currentStateChangeContext.setStateId!==e)return;const n=this.currentStateChangeContext,i=performance.now()-n.startTime,l=this.stateRenderingLogs.findIndex(c=>c.setStateId===e);if(l>=0){const c=this.stateRenderingLogs[l];c.renderedComponents=n.renderedComponents,c.totalRenderDuration=i,c.affectedBindingsCount=n.renderedComponents.reduce((d,f)=>d+f.evaluatedBindings.length,0)}this.currentStateChangeContext=null}getCurrentStateChangeContext(){return this.currentStateChangeContext}getStateRenderingLogs(){return[...this.stateRenderingLogs]}getStateRenderingInfo(){const e=this.stateRenderingLogs,n=Object.fromEntries(this.componentRenderCounts),a={};for(const[i,l]of this.stateToComponentMap)a[i]=Array.from(l);return{logs:e,componentRenderCounts:n,stateToComponentMap:a,stats:this.calculateStateRenderingStats()}}calculateStateRenderingStats(){const e=this.stateRenderingLogs;if(e.length===0)return{totalStateChanges:0,totalRenders:0,avgRenderDuration:0,avgComponentsPerChange:0,topRenderedComponents:[],topInfluentialPaths:[]};const n=e.reduce((d,f)=>d+f.renderedComponents.length,0),a=e.reduce((d,f)=>d+f.totalRenderDuration,0)/e.length,i=n/e.length,l=Array.from(this.componentRenderCounts.entries()).sort((d,f)=>f[1]-d[1]).slice(0,5).map(([d,f])=>({name:d,count:f})),c=Array.from(this.stateToComponentMap.entries()).sort((d,f)=>f[1].size-d[1].size).slice(0,5).map(([d,f])=>({path:d,affectedComponents:f.size}));return{totalStateChanges:e.length,totalRenders:n,avgRenderDuration:a,avgComponentsPerChange:i,topRenderedComponents:l,topInfluentialPaths:c}}getComponentsAffectedByState(e){const n=this.stateToComponentMap.get(e);return n?Array.from(n):[]}getComponentRenderHistory(e){return this.stateRenderingLogs.filter(n=>n.renderedComponents.some(a=>a.componentName===e))}clearStateRenderingLogs(){this.stateRenderingLogs=[],this.componentRenderCounts.clear(),this.stateToComponentMap.clear(),this.currentStateChangeContext=null,this.stateRenderingIdCounter=0}trackComponentStateSource(e,n,a,i){this.config.enabled&&this.componentStateSources.set(e,{componentId:e,componentName:n,stateSource:a,stateProvider:i})}trackDynamicState(e,n){this.config.enabled&&this.dynamicStates.set(e,n)}trackContextFlow(e,n,a,i,l,c){if(!this.config.enabled)return;const d={component:n,componentId:e,contextReceived:a,passedToChildren:i,usedInRender:l};if(this.contextFlowNodes.set(e,d),c){const f=this.contextFlowNodes.get(c);f&&(f.children||(f.children=[]),f.children.push(d))}}getStateHierarchyInfo(e=!0){const n=this.buildStateHierarchyLayers(),a=this.detectStateConflicts(n),i=Array.from(this.componentStateSources.values());return{layers:e?n.map(c=>({...c,values:this.summarizeValues(c.values,2)})):n,conflicts:a,componentStateSources:i,timestamp:Date.now()}}summarizeValues(e,n,a=0){if(e==null)return e;if(typeof e!="object")return typeof e=="string"&&e.length>100?e.substring(0,100)+`... (${e.length}자)`:e;if(a>=n){if(Array.isArray(e))return`[Array(${e.length})]`;const c=Object.keys(e);return`{${c.slice(0,5).join(", ")}${c.length>5?`, ... +${c.length-5}`:""}}`}if(Array.isArray(e))return e.length<=3?e.map(c=>this.summarizeValues(c,n,a+1)):{__type:"array",__length:e.length,__preview:e.slice(0,3).map(c=>this.summarizeValues(c,n,a+1))};const i={},l=Object.keys(e);for(const c of l)i[c]=this.summarizeValues(e[c],n,a+1);return i}buildStateHierarchyLayers(){const e=[];try{const a=window.G7Core?.state?.get?.();if(!a)return e;a._local&&e.push({name:"Global _local",type:"global",path:"_local",values:a._local,priority:1}),a._global&&e.push({name:"Global _global",type:"global",path:"_global",values:a._global,priority:1});for(const[l,c]of this.dynamicStates)c&&Object.keys(c).length>0&&e.push({name:`DynamicState (${l})`,type:"dynamicState",componentId:l,values:c,priority:2});const i=this.computeEffectiveState(a._local||{});e.push({name:"Effective _local (merged)",type:"effective",values:i,priority:3})}catch(n){console.warn("[G7DevTools] 상태 계층 빌드 실패:",n)}return e}computeEffectiveState(e){const n={...e};for(const[,a]of this.dynamicStates)a._local&&Object.assign(n,a._local);return n}detectStateConflicts(e){const n=[],a=e.find(l=>l.type==="global"&&l.path==="_local");if(!a)return n;const i=e.filter(l=>l.type==="dynamicState");for(const l of i){const c=l.values._local||l.values;for(const[d,f]of Object.entries(c)){const g=a.values[d];if(JSON.stringify(g)!==JSON.stringify(f)){const m=[],b=[];for(const S of this.componentStateSources.values())S.stateSource.local.some(_=>_===`_local.${d}`||_===d)&&(S.stateProvider.type==="dynamicState"?m.push(S.componentName):S.stateProvider.type==="globalState"&&b.push(S.componentName));n.push({path:`_local.${d}`,globalValue:g,dynamicStateValue:f,effectiveValue:f,usedBy:m,notUsedBy:b,severity:b.length>0?"warning":"info",description:b.length>0?`${b.join(", ")}은(는) globalState._local을 읽어 dynamicState 값을 사용하지 못함`:"dynamicState 값이 globalState와 다르지만 모든 컴포넌트가 dynamicState를 사용"})}}}return n}getContextFlowInfo(){const e=[],n=new Set(this.contextFlowNodes.keys());for(const a of this.contextFlowNodes.values())if(a.children)for(const i of a.children)n.delete(i.componentId);for(const a of n){const i=this.contextFlowNodes.get(a);i&&e.push(i)}return{rootComponent:e.length>0?e[0].component:"Unknown",contextFlow:e,timestamp:Date.now()}}clearStateHierarchyData(){this.componentStateSources.clear(),this.contextFlowNodes.clear(),this.dynamicStates.clear()}trackComponentStyle(e,n,a,i){if(!this.config.enabled)return;const l=this.analyzeTailwindClasses(a),c={componentId:e,componentName:n,classes:a,computedStyles:i,tailwindAnalysis:l};this.componentStyles.set(e,c),this.detectStyleIssues(c)}analyzeTailwindClasses(e){const n=[],a=[],i=[],l=[],c=["sm:","md:","lg:","xl:","2xl:"],d=[/bg-\w+-\d+/,/text-\w+-\d+/,/border-\w+-\d+/];for(const f of e){l.push(f),f.startsWith("dark:")&&n.push(f);for(const g of c)if(f.startsWith(g)){a.push(f);break}for(const g of d)if(g.test(f)){i.push(f);break}}return{usedClasses:l,darkClasses:n,responsiveClasses:a,dynamicClasses:i}}detectStyleIssues(e){const{componentId:n,componentName:a,computedStyles:i,tailwindAnalysis:l}=e;(i.opacity==="0"||i.visibility==="hidden"||i.display==="none"||i.width==="0px"&&i.height==="0px")&&this.addStyleIssue({id:this.generateId(),type:"invisible-element",componentId:n,componentName:a,property:i.opacity==="0"?"opacity":i.visibility==="hidden"?"visibility":i.display==="none"?"display":"width/height",currentValue:i.opacity==="0"?"0":i.visibility==="hidden"?"hidden":i.display==="none"?"none":"0px",severity:"warning",description:"요소가 화면에 보이지 않습니다.",suggestion:"opacity, visibility, display, width/height 값을 확인하세요."});const c=l.usedClasses.filter(f=>f.startsWith("bg-")&&!f.startsWith("dark:")),d=l.darkClasses.filter(f=>f.includes("bg-"));c.length>0&&d.length===0&&this.addStyleIssue({id:this.generateId(),type:"dark-mode-missing",componentId:n,componentName:a,property:"background-color",currentValue:c.join(", "),expectedValue:"dark:bg-* 클래스 필요",severity:"info",description:"다크 모드 배경색 클래스가 누락되었습니다.",suggestion:`${c[0]}에 대응하는 dark: 클래스를 추가하세요.`}),l.dynamicClasses.length>0&&this.addStyleIssue({id:this.generateId(),type:"tailwind-purging",componentId:n,componentName:a,property:"class",currentValue:l.dynamicClasses.join(", "),severity:"info",description:"동적으로 생성된 Tailwind 클래스가 있습니다.",suggestion:"tailwind.config.js의 safelist에 추가하거나 전체 클래스명을 상수로 정의하세요."})}addStyleIssue(e){this.styleIssues.some(a=>a.componentId===e.componentId&&a.type===e.type&&a.property===e.property)||this.styleIssues.push(e)}getStyleValidationInfo(){const e=Array.from(this.componentStyles.values());return{issues:[...this.styleIssues],componentStyles:e,stats:{totalComponents:e.length,invisibleCount:this.styleIssues.filter(n=>n.type==="invisible-element").length,tailwindIssueCount:this.styleIssues.filter(n=>n.type==="tailwind-purging").length,darkModeIssueCount:this.styleIssues.filter(n=>n.type==="dark-mode-missing").length},timestamp:Date.now()}}clearStyleValidationData(){this.styleIssues=[],this.componentStyles.clear()}trackAuthEvent(e,n,a,i){if(!this.config.enabled)return;const l={id:`auth_${++this.authEventIdCounter}`,type:e,timestamp:Date.now(),success:n,error:a,details:i};this.authEvents.push(l),this.authEvents.length>this.maxAuthEventHistory&&(this.authEvents=this.authEvents.slice(-this.maxAuthEventHistory))}trackAuthHeader(e,n,a,i,l){if(!this.config.enabled)return;const c={url:e,hasAuthHeader:n,headerType:a,tokenValid:i??!1,responseStatus:l,timestamp:Date.now()};this.authHeaderHistory.push(c),this.authHeaderHistory.length>this.maxAuthEventHistory&&(this.authHeaderHistory=this.authHeaderHistory.slice(-this.maxAuthEventHistory)),l===401&&this.trackAuthEvent("api-unauthorized",!1,`401 Unauthorized: ${e}`,{url:e})}getAuthState(){try{const e=window.G7Core,n=e?.api;let a=e?.auth;!a&&e?.AuthManager?.getInstance&&(a=e.AuthManager.getInstance());const i=n?.getToken?.()||a?.getAccessToken?.()||localStorage.getItem("auth_token")||localStorage.getItem("access_token"),l=a?.getRefreshToken?.()||localStorage.getItem("refresh_token");let c="unknown",d=a?.getUser?.();if(d&&(c="AuthManager"),!d){const v=e?.state?.get?.();d=v?._global?.user||v?._global?.currentUser||v?.user||v?.currentUser,d&&(c="G7Core.state")}const f=a?.isAuthenticated?.()??!1;let g="memory";localStorage.getItem("auth_token")||localStorage.getItem("access_token")?g="localStorage":(sessionStorage.getItem("auth_token")||sessionStorage.getItem("access_token"))&&(g="sessionStorage");const m=!!i&&(!!d||f);let b;i&&typeof i=="string"&&i.length>20?b=`${i.substring(0,10)}...${i.substring(i.length-6)}`:i&&(b="***");const S=d?{id:d.id,email:d.email,name:d.name,roles:d.roles?.map(v=>typeof v=="string"?{name:v}:{id:v.id,name:v.name,guard_name:v.guard_name}),permissions:d.permissions?.map(v=>typeof v=="string"?{name:v}:{id:v.id,name:v.name,guard_name:v.guard_name}),avatar:d.avatar||d.profile_photo_url||d.profile_image,created_at:d.created_at,updated_at:d.updated_at,last_login_at:d.last_login_at,email_verified_at:d.email_verified_at,...d.phone&&{phone:d.phone},...d.nickname&&{nickname:d.nickname},...d.status&&{status:d.status},...d.locale&&{locale:d.locale}}:void 0;return{isAuthenticated:m,user:S,tokens:{hasAccessToken:!!i,hasRefreshToken:!!l,storage:g,accessTokenPreview:b},lastActivity:Date.now(),source:c}}catch{return{isAuthenticated:!1,tokens:{hasAccessToken:!1,hasRefreshToken:!1,storage:"memory"},source:"unknown"}}}getAuthDebugInfo(){const e=[...this.authEvents];return{state:this.getAuthState(),events:e,headerAnalysis:[...this.authHeaderHistory],stats:{loginAttempts:e.filter(n=>n.type==="login").length,successfulLogins:e.filter(n=>n.type==="login"&&n.success).length,failedLogins:e.filter(n=>n.type==="login"&&!n.success).length,tokenRefreshes:e.filter(n=>n.type==="token-refresh").length,unauthorizedResponses:e.filter(n=>n.type==="api-unauthorized").length},timestamp:Date.now()}}clearAuthData(){this.authEvents=[],this.authHeaderHistory=[],this.authEventIdCounter=0}trackLayoutLoad(e,n,a,i="api"){if(!this.config.enabled)return;const l=Date.now();this.currentLayout={layoutPath:e,templateId:n,layoutJson:this.sanitizeObject(a),loadedAt:l,version:a.version,layoutName:a.layout_name,source:i};const c={id:`layout-${++this.layoutIdCounter}`,layoutPath:e,templateId:n,loadedAt:l,source:i,version:a.version};this.layoutHistory.push(c),this.layoutHistory.length>this.maxLayoutHistory&&this.layoutHistory.shift(),this.layoutStats.totalLoads++,i==="cache"?this.layoutStats.cacheHits++:this.layoutStats.apiLoads++}getCurrentLayout(){return this.currentLayout}getLayoutHistory(){return[...this.layoutHistory]}getLayoutDebugInfo(){return{current:this.currentLayout,history:[...this.layoutHistory],stats:{...this.layoutStats}}}clearLayoutData(){this.currentLayout=null,this.layoutHistory=[],this.layoutIdCounter=0,this.layoutStats={totalLoads:0,cacheHits:0,apiLoads:0}}startHandlerExecution(e){if(!this.config.enabled)return"";const n=`exec_${++this.executionIdCounter}_${Date.now()}`,a={handlerName:e,executionId:n,startTime:Date.now(),stateChanges:[],dataSourceChanges:[],alerts:[]};if(this.executionDetails.set(n,a),this.executionDetails.size>this.maxExecutionDetails){const i=Array.from(this.executionDetails.keys());i.slice(0,i.length-this.maxExecutionDetails).forEach(c=>this.executionDetails.delete(c))}return n}endHandlerExecution(e,n,a,i){if(!this.config.enabled||!e)return;const l=this.executionDetails.get(e);if(!l)return;l.endTime=Date.now(),l.duration=l.endTime-l.startTime,l.exitReason=n,l.exitLocation=a,l.exitDescription=i;const c=this.changeExpectations.get(e);c&&(l.expectedChanges=c,this.checkExpectations(e,l,c),this.changeExpectations.delete(e)),n==="normal"&&l.stateChanges.length===0&&l.dataSourceChanges.length===0&&this.addChangeAlert(e,{type:"no-state-change",severity:"warning",message:`핸들러 "${l.handlerName}"이(가) 상태 변경 없이 완료됨`,description:"핸들러가 성공적으로 완료되었지만 상태나 데이터소스 변경이 없습니다. 의도한 동작인지 확인하세요.",handlerName:l.handlerName,suggestion:'핸들러 내부 조건문을 확인하거나 DevTools의 "변경감지" 탭에서 exitLocation을 확인하세요.',docLink:"troubleshooting-state.md"}),(n==="early-return-condition"||n==="early-return-validation")&&this.addChangeAlert(e,{type:"early-return-detected",severity:"info",message:`핸들러 "${l.handlerName}"이(가) early return으로 종료됨`,description:i||`종료 위치: ${a||"알 수 없음"}`,handlerName:l.handlerName,suggestion:n==="early-return-validation"?"검증 실패로 인한 early return입니다. 입력 데이터를 확인하세요.":"조건부 early return입니다. 조건문의 평가 결과를 확인하세요."})}recordExitReason(e,n,a,i){if(!this.config.enabled||!e)return;const l=this.executionDetails.get(e);l&&(l.exitReason=n,l.exitLocation=a,l.exitDescription=i)}recordStateChange(e,n,a,i,l){if(!this.config.enabled)return;const c={id:`sc_${++this.stateChangeIdCounter}`,path:n,changeType:a,oldValue:this.safeClone(i),newValue:this.safeClone(l),timestamp:Date.now(),comparison:this.compareValues(i,l),executionId:e};if(this.stateChangeHistory.push(c),e){const d=this.executionDetails.get(e);d&&d.stateChanges.push(c)}this.stateChangeHistory.length>this.maxStateChangeHistory&&this.stateChangeHistory.splice(0,this.stateChangeHistory.length-this.maxStateChangeHistory),c.comparison.isDeepEqual&&typeof i=="object"&&i!==null&&this.addChangeAlert(e||"",{type:"object-reference-same",severity:"warning",message:`상태 "${n}"가 변경되었으나 값이 동일함`,description:"객체 참조가 동일하거나 깊은 비교 결과 변경이 없습니다. 변경 감지가 실패할 수 있습니다.",handlerName:e&&this.executionDetails.get(e)?.handlerName||"unknown",statePath:n,suggestion:"불변성을 유지하여 새 객체를 생성하세요. 예: { ...oldObject, newField: value }",docLink:"troubleshooting-state.md#object-mutation"})}recordDataSourceChange(e,n,a,i,l){if(!this.config.enabled)return;const c={dataSourceId:n,changeType:a,timestamp:Date.now(),previousStatus:i,newStatus:l,executionId:e};if(this.dataSourceChangeHistory.push(c),e){const d=this.executionDetails.get(e);d&&d.dataSourceChanges.push(c)}this.dataSourceChangeHistory.length>this.maxStateChangeHistory&&this.dataSourceChangeHistory.splice(0,this.dataSourceChangeHistory.length-this.maxStateChangeHistory)}expectChange(e,n,a){if(!this.config.enabled||!e)return;const i=this.executionDetails.get(e);if(!i)return;const l={expectedStatePaths:n,expectedDataSources:a,setAt:Date.now(),source:i.handlerName};this.changeExpectations.set(e,l)}checkExpectations(e,n,a){const i=new Set(n.stateChanges.map(c=>c.path)),l=new Set(n.dataSourceChanges.map(c=>c.dataSourceId));for(const c of a.expectedStatePaths)i.has(c)||this.addChangeAlert(e,{type:"expected-not-fulfilled",severity:"error",message:`기대한 상태 변경 "${c}"가 발생하지 않음`,description:`핸들러 "${n.handlerName}"이(가) "${c}" 변경을 예고했지만 실제로 변경되지 않았습니다.`,handlerName:n.handlerName,statePath:c,suggestion:"핸들러 내부 로직을 확인하세요. early return이나 조건부 분기로 인해 변경이 생략되었을 수 있습니다.",docLink:"troubleshooting-state.md"});for(const c of a.expectedDataSources)l.has(c)||this.addChangeAlert(e,{type:"expected-not-fulfilled",severity:"error",message:`기대한 데이터소스 갱신 "${c}"가 발생하지 않음`,description:`핸들러 "${n.handlerName}"이(가) "${c}" 갱신을 예고했지만 실제로 갱신되지 않았습니다.`,handlerName:n.handlerName,dataSourceId:c,suggestion:"refetchDataSource 호출이 실행되었는지 확인하세요.",docLink:"data-sources.md"})}addChangeAlert(e,n){const a={...n,id:`alert_${++this.alertIdCounter}`,timestamp:Date.now()};if(this.changeAlerts.push(a),e){const i=this.executionDetails.get(e);i&&i.alerts.push(a)}this.changeAlerts.length>this.maxChangeAlerts&&this.changeAlerts.splice(0,this.changeAlerts.length-this.maxChangeAlerts)}compareValues(e,n){const a=this.getValueType(e),i=this.getValueType(n),l=a!==i;let c=!1,d;return!l&&(a==="object"||a==="array")?(c=this.deepEqual(e,n),!c&&a==="object"&&(d=this.getChangedKeys(e,n))):c=e===n,{typeChanged:l,oldType:a,newType:i,isDeepEqual:c,changedKeys:d}}getValueType(e){return e===null?"null":e===void 0?"undefined":Array.isArray(e)?"array":typeof e}deepEqual(e,n){if(e===n)return!0;if(typeof e!=typeof n)return!1;if(e===null||n===null||typeof e!="object")return e===n;if(Array.isArray(e)!==Array.isArray(n))return!1;const a=Object.keys(e),i=Object.keys(n);if(a.length!==i.length)return!1;for(const l of a)if(!i.includes(l)||!this.deepEqual(e[l],n[l]))return!1;return!0}getChangedKeys(e,n){const a=[],i=new Set([...Object.keys(e),...Object.keys(n)]);for(const l of i)this.deepEqual(e[l],n[l])||a.push(l);return a}getChangeDetectionInfo(){const e=Array.from(this.executionDetails.values()),n={totalExecutions:e.length,executionsWithStateChange:e.filter(a=>a.stateChanges.length>0).length,executionsWithoutStateChange:e.filter(a=>a.stateChanges.length===0&&a.exitReason==="normal").length,earlyReturnCount:e.filter(a=>a.exitReason==="early-return-condition"||a.exitReason==="early-return-validation").length,alertCount:this.changeAlerts.length,alertsByType:this.getAlertsByType()};return{executionDetails:e,stateChangeHistory:[...this.stateChangeHistory],dataSourceChangeHistory:[...this.dataSourceChangeHistory],alerts:[...this.changeAlerts],stats:n,timestamp:Date.now()}}getAlertsByType(){const e={"no-state-change":0,"no-datasource-change":0,"expected-not-fulfilled":0,"object-reference-same":0,"early-return-detected":0,"async-timing-issue":0};for(const n of this.changeAlerts)e[n.type]++;return e}clearChangeDetectionData(){this.executionDetails.clear(),this.stateChangeHistory=[],this.dataSourceChangeHistory=[],this.changeAlerts=[],this.changeExpectations.clear(),this.executionIdCounter=0,this.stateChangeIdCounter=0,this.alertIdCounter=0}getRecentAlerts(e=10,n){let a=[...this.changeAlerts];return n&&(a=a.filter(i=>i.severity===n)),a.slice(-e)}getHandlerExecutions(e,n=10){return Array.from(this.executionDetails.values()).filter(i=>i.handlerName===e).slice(-n)}findExecutionInStack(e){return this.sequenceExecutionStack.find(n=>n.sequenceId===e)}startSequenceExecution(e){if(!this.config.enabled)return"";const n=`seq_${++this.sequenceIdCounter}_${Date.now()}`,a={sequenceId:n,startTime:Date.now(),totalDuration:0,trigger:e,actions:[],status:"running"};return this.sequenceExecutionStack.push(a),n}captureSequenceActionBefore(e,n,a,i,l){if(!this.config.enabled)return;const c=this.findExecutionInStack(e);if(!c)return;const d={index:n,handler:a,params:this.safeClone(i),stateBeforeAction:{_global:this.safeClone(l._global),_local:this.safeClone(l._local),_isolated:l._isolated?this.safeClone(l._isolated):void 0},stateAfterAction:{_global:{},_local:{}},duration:0,status:"success"};c.actions.push(d)}captureSequenceActionAfter(e,n,a,i,l,c){if(!this.config.enabled)return;const d=this.findExecutionInStack(e);if(!d)return;const f=d.actions.find(m=>m.index===n);if(!f)return;f.stateAfterAction={_global:this.safeClone(a._global),_local:this.safeClone(a._local),_isolated:a._isolated?this.safeClone(a._isolated):void 0},f.duration=i,f.result=this.safeClone(l),f.stateDiff={global:this.computeStateDiff(f.stateBeforeAction._global,f.stateAfterAction._global),local:this.computeStateDiff(f.stateBeforeAction._local,f.stateAfterAction._local)},f.stateBeforeAction._isolated&&f.stateAfterAction._isolated&&(f.stateDiff.isolated=this.computeStateDiff(f.stateBeforeAction._isolated,f.stateAfterAction._isolated));const g=window.__g7PendingLocalState;g&&(f.pendingState=this.safeClone(g)),c&&(f.status="error",f.error={name:c.name,message:c.message,stack:c.stack})}endSequenceExecution(e,n){if(!this.config.enabled)return;const a=this.sequenceExecutionStack.findIndex(l=>l.sequenceId===e);if(a===-1)return;const i=this.sequenceExecutionStack[a];if(i.endTime=Date.now(),i.totalDuration=i.endTime-i.startTime,n){i.status="error",i.error={name:n.name,message:n.message,stack:n.stack};const l=i.actions.find(c=>c.status==="error");l&&(i.failedAtIndex=l.index)}else i.status="success";this.sequenceExecutions.push(i),this.sequenceExecutions.length>this.maxSequenceExecutions&&this.sequenceExecutions.shift(),this.sequenceExecutionStack.splice(a,1)}computeStateDiff(e,n){const a=[],i=[],l=[],c=new Set(Object.keys(e)),d=new Set(Object.keys(n));for(const f of d)c.has(f)||a.push(f);for(const f of c)d.has(f)||i.push(f);for(const f of c)if(d.has(f)){const g=e[f],m=n[f];JSON.stringify(g)!==JSON.stringify(m)&&l.push({path:f,oldValue:g,newValue:m})}return{added:a,removed:i,changed:l}}getSequenceTrackingInfo(){const e=this.getSequenceStats(),n=this.sequenceExecutionStack.length>0?this.sequenceExecutionStack[this.sequenceExecutionStack.length-1]:void 0;return{executions:[...this.sequenceExecutions],currentExecution:n,stats:e}}getSequenceStats(){const e=this.sequenceExecutions,n=e.length,a=e.filter(g=>g.status==="success").length,i=e.filter(g=>g.status==="error").length,l=e.reduce((g,m)=>g+m.actions.length,0),c=n>0?e.reduce((g,m)=>g+m.totalDuration,0)/n:0,d={};for(const g of e)for(const m of g.actions)d[m.handler]=(d[m.handler]||0)+1;const f=Object.entries(d).sort(([,g],[,m])=>m-g).slice(0,5).map(([g,m])=>({handler:g,count:m}));return{totalExecutions:n,successCount:a,errorCount:i,totalActions:l,avgDuration:c,topHandlers:f}}clearSequenceData(){this.sequenceExecutions=[],this.sequenceExecutionStack=[],this.sequenceIdCounter=0}getSequenceExecution(e){const n=this.findExecutionInStack(e);return n||this.sequenceExecutions.find(a=>a.sequenceId===e)}getRecentSequences(e=10){return this.sequenceExecutions.slice(-e)}registerStateCaptureForHandler(e,n,a){if(!this.config.enabled)return;const i=new Map,l=Date.now();for(const c of n)i.set(c,{value:this.safeClone(a[c]),capturedAt:l});this.stateCaptureRegistry.set(e,i)}detectStaleClosure(e,n,a,i,l){if(!this.config.enabled)return[];const c=this.stateCaptureRegistry.get(e);if(!c)return[];const d=Date.now(),f=[];for(const[g,m]of c){const b=this.getValueByPath(a,g),S=d-m.capturedAt;if(!this.isDeepEqual(m.value,b)&&S>0){const v=this.createStaleClosureWarning(i,n,g,m.value,m.capturedAt,b,d,S,l);f.push(v),this.addStaleClosureWarning(v)}}return this.stateCaptureRegistry.delete(e),f}trackStaleClosureWarning(e){if(!this.config.enabled)return;const n=Date.now(),a=this.createStaleClosureWarning(e.type,e.location,e.capturedPath,e.capturedValue,e.capturedAt,e.currentValue,n,n-e.capturedAt,e.actionId,e.stackTrace);this.addStaleClosureWarning(a)}createStaleClosureWarning(e,n,a,i,l,c,d,f,g,m){const b=this.getStaleClosureSeverity(e,f),{description:S,suggestion:v,docLink:_}=this.getStaleClosureMessages(e,a,f);return{id:`stale_${++this.staleClosureIdCounter}_${Date.now()}`,timestamp:Date.now(),type:e,location:n,capturedState:{path:a,capturedValue:this.safeClone(i),capturedAt:l},currentState:{path:a,currentValue:this.safeClone(c),retrievedAt:d},timeDiff:f,severity:b,description:S,suggestion:v,docLink:_,actionId:g,stackTrace:m}}getStaleClosureSeverity(e,n){if(n>5e3)return"error";if(n>1e3)return"warning";switch(e){case"async-state-capture":case"sequence-state-mismatch":return"warning";case"callback-state-capture":case"timeout-state-capture":return"info";case"event-handler-stale":return"warning";default:return"info"}}getStaleClosureMessages(e,n,a){const i=a<1e3?`${a}ms`:`${(a/1e3).toFixed(1)}s`;switch(e){case"async-state-capture":return{description:`await 후 ${i} 경과 시점에 '${n}' 상태가 변경됨. 캡처된 상태 대신 최신 상태를 사용해야 함`,suggestion:"await 이후에는 G7Core.state.get()으로 최신 상태를 다시 조회하거나, useRef + getter 패턴을 사용하세요",docLink:"troubleshooting-state-closure.md"};case"callback-state-capture":return{description:`콜백에서 ${i} 전에 캡처된 '${n}' 상태를 사용 중. 최신 상태와 다름`,suggestion:"콜백에서 상태를 참조할 때는 stateRef.current 패턴 또는 G7Core.state.get()를 사용하세요",docLink:"troubleshooting-state-closure.md"};case"timeout-state-capture":return{description:`setTimeout/setInterval 콜백에서 '${n}' 상태가 ${i} 전 값 사용 중`,suggestion:"타이머 콜백 내에서는 G7Core.state.get()으로 최신 상태를 조회하세요",docLink:"troubleshooting-state-closure.md"};case"sequence-state-mismatch":return{description:`sequence 내 '${n}' 상태가 이전 액션 결과와 불일치. context.state 대신 캡처된 값 사용 의심`,suggestion:"sequence 내에서는 context.state 또는 $prev를 사용하여 최신 상태를 참조하세요",docLink:"troubleshooting-state-setstate.md"};case"event-handler-stale":return{description:`이벤트 핸들러에서 ${i} 전에 바인딩된 '${n}' 상태 사용 중`,suggestion:"이벤트 핸들러에서 상태를 참조할 때는 useRef 패턴 또는 G7Core.state.get()를 사용하세요",docLink:"troubleshooting-state-closure.md"};default:return{description:`'${n}' 상태에서 stale closure 감지됨 (${i} 경과)`,suggestion:"상태 참조 시 최신 값을 사용하는지 확인하세요"}}}addStaleClosureWarning(e){this.staleClosureWarnings.push(e),this.staleClosureWarnings.length>this.maxStaleClosureWarnings&&this.staleClosureWarnings.shift()}getValueByPath(e,n){const a=n.split(".");let i=e;for(const l of a){if(i==null)return;i=i[l]}return i}isDeepEqual(e,n){if(e===n)return!0;if(e===null||n===null)return e===n;if(typeof e!=typeof n)return!1;if(typeof e!="object")return e===n;try{return JSON.stringify(e)===JSON.stringify(n)}catch{return!1}}getStaleClosureTrackingInfo(){const e=this.getStaleClosureStats();return{warnings:[...this.staleClosureWarnings],stats:e,timestamp:Date.now()}}getStaleClosureStats(){const e=this.staleClosureWarnings,n={"async-state-capture":0,"callback-state-capture":0,"timeout-state-capture":0,"sequence-state-mismatch":0,"event-handler-stale":0};for(const f of e)n[f.type]++;const a={info:0,warning:0,error:0};for(const f of e)a[f.severity]++;const i={};for(const f of e)i[f.location]=(i[f.location]||0)+1;const l=Object.entries(i).sort(([,f],[,g])=>g-f).slice(0,5).map(([f,g])=>({location:f,count:g})),c={};for(const f of e)c[f.capturedState.path]=(c[f.capturedState.path]||0)+1;const d=Object.entries(c).sort(([,f],[,g])=>g-f).slice(0,5).map(([f,g])=>({path:f,count:g}));return{totalWarnings:e.length,warningsByType:n,warningsBySeverity:a,topLocations:l,topAffectedPaths:d}}clearStaleClosureData(){this.staleClosureWarnings=[],this.staleClosureIdCounter=0,this.stateCaptureRegistry.clear()}getRecentStaleClosureWarnings(e=10,n){let a=[...this.staleClosureWarnings];return n&&(a=a.filter(i=>i.severity===n)),a.slice(-e)}safeClone(e){if(e==null||typeof e!="object")return e;try{return JSON.parse(JSON.stringify(e))}catch{if(Array.isArray(e))return e.map(a=>{try{return JSON.parse(JSON.stringify(a))}catch{return"[Unserializable]"}});const n={};for(const a of Object.keys(e))try{n[a]=JSON.parse(JSON.stringify(e[a]))}catch{n[a]="[Unserializable]"}return n}}};$(Si,"instance");let Oa=Si;const Gn=ut("LayoutLoader");class Cr extends Error{constructor(n,a,i){super(n);$(this,"code");$(this,"details");this.code=a,this.details=i,this.name="LayoutLoaderError"}}class ff{constructor(e){$(this,"componentRegistry");$(this,"currentLayout",null);$(this,"layoutCache",new Map);$(this,"cacheVersion",0);this.componentRegistry=e}setCacheVersion(e){this.cacheVersion!==e&&(Gn.log("Cache version updated:",this.cacheVersion,"->",e),this.cacheVersion=e,this.layoutCache.clear())}getCacheVersion(){return this.cacheVersion}async loadLayout(e,n){const a=`${e}:${n}`;if(this.layoutCache.has(a)){Gn.log("Loading layout from cache:",n);const l=await this.layoutCache.get(a),c=JSON.parse(JSON.stringify(l));return this.currentLayout=c,Oa.getInstance().trackLayoutLoad(n,e,c,"cache"),c}const i=this.fetchLayout(e,n);return this.layoutCache.set(a,i),i.catch(()=>{this.layoutCache.get(a)===i&&this.layoutCache.delete(a)}),i}async fetchLayout(e,n,a=!1){try{const i=this.cacheVersion>0?this.cacheVersion:null;let l;if(n.startsWith("__preview__/")){const S=n.replace("__preview__/","");l=$r(`/api/layouts/preview/${S}`,"json",i)}else l=$r(`/api/layouts/${e}/${n}`,"json",i);Gn.log("Fetching layout from API:",l);const c=Ir(),d=a?null:c.getToken(),f={Accept:"application/json"};d&&(f.Authorization=`Bearer ${d}`);const g=await Ll(l,{init:{headers:f},label:`layout: ${n}`});let m;try{m=await g.json()}catch{m=null}if(!g.ok){if(g.status===401&&!a&&d){Gn.log("Token invalid, removing and retrying without token"),c.removeToken();try{return await this.fetchLayout(e,n,!0)}catch(v){throw v instanceof Cr&&v.details?.status===401?new Cr(v.message,v.code,{...v.details,hadToken:!0}):v}}const S=m?.message||m?.error;throw new Cr(`Failed to fetch layout: ${g.status} ${g.statusText}`,"FETCH_FAILED",{status:g.status,statusText:g.statusText,url:l,apiMessage:S})}const b=m.data||m;return this.validateLayoutData(b),this.currentLayout=b,Oa.getInstance().trackLayoutLoad(n,e,b,"api"),Gn.log("Layout fetched and cached successfully:",b.layout_name),b}catch(i){const l=`${e}:${n}`;throw this.layoutCache.delete(l),i instanceof Cr?i:new Cr("Failed to load layout","LOAD_FAILED",{originalError:i})}}prefetchLayout(e,n){return this.loadLayout(e,n)}validateLayoutData(e){if(!e.version)throw new Cr("Layout data missing required field: version","VALIDATION_FAILED",{field:"version"});if(!e.layout_name)throw new Cr("Layout data missing required field: layout_name","VALIDATION_FAILED",{field:"layout_name"});if(!Array.isArray(e.components))throw new Cr('Layout data field "components" must be an array',"VALIDATION_FAILED",{field:"components",value:e.components});Gn.log("Layout data validation passed")}renderLayout(e,n){try{const a=n||this.currentLayout;if(!a)throw new Cr("No layout data to render","NO_LAYOUT_DATA");Gn.log("Rendering layout:",a.layout_name),e.innerHTML="",this.renderComponents(e,a.components),Gn.log("Layout rendered successfully")}catch(a){Gn.error("Render error:",a),this.renderErrorState(e,a)}}renderComponents(e,n){for(const a of n){const i=this.renderComponent(a);i&&e.appendChild(i)}}renderComponent(e){try{if(!this.componentRegistry.getComponent(e.type))return Gn.warn(`Component not found: ${e.type}`),this.createPlaceholderElement(e.type);const a=document.createElement("div");return a.setAttribute("data-component",e.type),e.props&&Object.entries(e.props).forEach(([i,l])=>{a.setAttribute(`data-prop-${i}`,JSON.stringify(l))}),e.children&&e.children.length>0?this.renderComponents(a,e.children):e.text&&(a.textContent=e.text),a}catch(n){return Gn.error(`Error rendering component ${e.type}:`,n),this.createErrorElement(e.type,n)}}createPlaceholderElement(e){const n=document.createElement("div");return n.className="component-placeholder",n.setAttribute("data-component-type",e),n.innerHTML=`

Component not found: ${e}

@@ -60,7 +60,7 @@ Error generating stack: `+u.message+`

Component Error: ${e}

${n?.message||"Unknown error"}

- `,a}renderErrorState(e,n){const a=n instanceof Error?n.message:String(n),i=n instanceof Er?n.code:"UNKNOWN_ERROR";e.innerHTML=` + `,a}renderErrorState(e,n){const a=n instanceof Error?n.message:String(n),i=n instanceof Cr?n.code:"UNKNOWN_ERROR";e.innerHTML=`
페이지 새로고침
- `,Kn.error("Error state rendered:",i,a)}getCurrentLayout(){return this.currentLayout}clear(){this.currentLayout=null,this.layoutCache.clear(),Kn.log("Layout data and cache cleared")}}const F_=ht("TranslationContext"),yf=H.createContext(null),vc=({children:o,translationEngine:e,translationContext:n})=>{const a=H.useMemo(()=>(l,c)=>{if(c){const d="|"+Object.entries(c).map(([f,g])=>`${f}=${g}`).join("|");return e.translate(l,n,d)}return e.translate(l,n)},[e,n]),i=H.useMemo(()=>({translationEngine:e,translationContext:n,t:a}),[e,n,a]);return gt.jsx(yf.Provider,{value:i,children:o})},uy=()=>{const o=H.useContext(yf);return o||(F_.warn("TranslationProvider 외부에서 호출되었습니다. 키를 그대로 반환합니다."),{t:e=>e,translationEngine:null,translationContext:null})},La=ht("SlotContext"),K_={registerToSlot:()=>{La.warn("SlotContext not available: registerToSlot called outside SlotProvider")},unregisterFromSlot:()=>{La.warn("SlotContext not available: unregisterFromSlot called outside SlotProvider")},getSlotComponents:()=>[],subscribeToSlot:()=>()=>{},clearAllSlots:()=>{},isEnabled:!1},W_=H.createContext(K_),bf=({children:o})=>{const e=H.useRef(new Map),n=H.useRef(new Map),a=H.useCallback(m=>{const y=n.current.get(m);y&&y.forEach(S=>{try{S()}catch(v){La.error(`Slot subscriber error (slotId: ${m}):`,v)}})},[]),i=H.useCallback((m,y,S)=>{if(!m||!y){La.warn("registerToSlot: slotId and componentId are required");return}e.current.has(m)||e.current.set(m,new Map);const v=e.current.get(m),_=v.get(y);_&&_.registrationKey===S.registrationKey||(v.set(y,S),La.log(`Component registered to slot: ${y} -> ${m} (order: ${S.order})`),a(m))},[a]),l=H.useCallback((m,y)=>{const S=e.current.get(m);S&&S.has(y)&&(S.delete(y),La.log(`Component unregistered from slot: ${y} <- ${m}`),S.size===0&&e.current.delete(m),a(m))},[a]),c=H.useCallback(m=>{const y=e.current.get(m);return y?Array.from(y.values()).sort((S,v)=>S.order-v.order):[]},[]),d=H.useCallback((m,y)=>{n.current.has(m)||n.current.set(m,new Set);const S=n.current.get(m);return S.add(y),()=>{S.delete(y),S.size===0&&n.current.delete(m)}},[]),f=H.useCallback(()=>{e.current.forEach((m,y)=>{a(y)}),e.current.clear(),n.current.clear(),La.log("All slots cleared")},[a]),g=H.useMemo(()=>({registerToSlot:i,unregisterFromSlot:l,getSlotComponents:c,subscribeToSlot:d,clearAllSlots:f,isEnabled:!0}),[i,l,c,d,f]);return H.useLayoutEffect(()=>{typeof window<"u"&&(window.__slotContextValue=g,La.log("SlotContext exposed to window.__slotContextValue"))},[g]),gt.jsx(W_.Provider,{value:g,children:o})};class vf{constructor(){this.notificationCreatedEvent=".Illuminate\\Notifications\\Events\\BroadcastNotificationCreated"}listenForWhisper(e,n){return this.listen(".client-"+e,n)}notification(e){return this.listen(this.notificationCreatedEvent,e)}stopListeningForNotification(e){return this.stopListening(this.notificationCreatedEvent,e)}stopListeningForWhisper(e,n){return this.stopListening(".client-"+e,n)}}class dy{constructor(e){this.namespace=e}format(e){return[".","\\"].includes(e.charAt(0))?e.substring(1):(this.namespace&&(e=this.namespace+"."+e),e.replace(/\./g,"\\"))}setNamespace(e){this.namespace=e}}function Y_(o){try{return Reflect.construct(String,[],o),!0}catch{return!1}}class Sf extends vf{constructor(e,n,a){super(),this.name=n,this.pusher=e,this.options=a,this.eventFormatter=new dy(this.options.namespace),this.subscribe()}subscribe(){this.subscription=this.pusher.subscribe(this.name)}unsubscribe(){this.pusher.unsubscribe(this.name)}listen(e,n){return this.on(this.eventFormatter.format(e),n),this}listenToAll(e){return this.subscription.bind_global((n,a)=>{if(n.startsWith("pusher:"))return;let i=String(this.options.namespace??"").replace(/\./g,"\\"),l=n.startsWith(i)?n.substring(i.length+1):"."+n;e(l,a)}),this}stopListening(e,n){return n?this.subscription.unbind(this.eventFormatter.format(e),n):this.subscription.unbind(this.eventFormatter.format(e)),this}stopListeningToAll(e){return e?this.subscription.unbind_global(e):this.subscription.unbind_global(),this}subscribed(e){return this.on("pusher:subscription_succeeded",()=>{e()}),this}error(e){return this.on("pusher:subscription_error",n=>{e(n)}),this}on(e,n){return this.subscription.bind(e,n),this}}class fy extends Sf{whisper(e,n){return this.pusher.channels.channels[this.name].trigger(`client-${e}`,n),this}}class X_ extends Sf{whisper(e,n){return this.pusher.channels.channels[this.name].trigger(`client-${e}`,n),this}}class J_ extends fy{here(e){return this.on("pusher:subscription_succeeded",n=>{e(Object.keys(n.members).map(a=>n.members[a]))}),this}joining(e){return this.on("pusher:member_added",n=>{e(n.info)}),this}whisper(e,n){return this.pusher.channels.channels[this.name].trigger(`client-${e}`,n),this}leaving(e){return this.on("pusher:member_removed",n=>{e(n.info)}),this}}class hy extends vf{constructor(e,n,a){super(),this.events={},this.listeners={},this.name=n,this.socket=e,this.options=a,this.eventFormatter=new dy(this.options.namespace),this.subscribe()}subscribe(){this.socket.emit("subscribe",{channel:this.name,auth:this.options.auth||{}})}unsubscribe(){this.unbind(),this.socket.emit("unsubscribe",{channel:this.name,auth:this.options.auth||{}})}listen(e,n){return this.on(this.eventFormatter.format(e),n),this}stopListening(e,n){return this.unbindEvent(this.eventFormatter.format(e),n),this}subscribed(e){return this.on("connect",n=>{e(n)}),this}error(e){return this}on(e,n){return this.listeners[e]=this.listeners[e]||[],this.events[e]||(this.events[e]=(a,i)=>{this.name===a&&this.listeners[e]&&this.listeners[e].forEach(l=>l(i))},this.socket.on(e,this.events[e])),this.listeners[e].push(n),this}unbind(){Object.keys(this.events).forEach(e=>{this.unbindEvent(e)})}unbindEvent(e,n){this.listeners[e]=this.listeners[e]||[],n&&(this.listeners[e]=this.listeners[e].filter(a=>a!==n)),(!n||this.listeners[e].length===0)&&(this.events[e]&&(this.socket.removeListener(e,this.events[e]),delete this.events[e]),delete this.listeners[e])}}class gy extends hy{whisper(e,n){return this.socket.emit("client event",{channel:this.name,event:`client-${e}`,data:n}),this}}class Q_ extends gy{here(e){return this.on("presence:subscribed",n=>{e(n.map(a=>a.user_info))}),this}joining(e){return this.on("presence:joining",n=>e(n.user_info)),this}whisper(e,n){return this.socket.emit("client event",{channel:this.name,event:`client-${e}`,data:n}),this}leaving(e){return this.on("presence:leaving",n=>e(n.user_info)),this}}class Sc extends vf{subscribe(){}unsubscribe(){}listen(e,n){return this}listenToAll(e){return this}stopListening(e,n){return this}subscribed(e){return this}error(e){return this}on(e,n){return this}}class py extends Sc{whisper(e,n){return this}}class Z_ extends Sc{whisper(e,n){return this}}class eA extends py{here(e){return this}joining(e){return this}whisper(e,n){return this}leaving(e){return this}}const Tc=class Tc{constructor(e){this.setOptions(e),this.connect()}setOptions(e){this.options={...Tc._defaultOptions,...e,broadcaster:e.broadcaster};let n=this.csrfToken();n&&(this.options.auth.headers["X-CSRF-TOKEN"]=n,this.options.userAuthentication.headers["X-CSRF-TOKEN"]=n),n=this.options.bearerToken,n&&(this.options.auth.headers.Authorization="Bearer "+n,this.options.userAuthentication.headers.Authorization="Bearer "+n)}csrfToken(){return typeof window<"u"&&window.Laravel?.csrfToken?window.Laravel.csrfToken:this.options.csrfToken?this.options.csrfToken:typeof document<"u"&&typeof document.querySelector=="function"?document.querySelector('meta[name="csrf-token"]')?.getAttribute("content")??null:null}};Tc._defaultOptions={auth:{headers:{}},authEndpoint:"/broadcasting/auth",userAuthentication:{endpoint:"/broadcasting/user-auth",headers:{}},csrfToken:null,bearerToken:null,host:null,key:null,namespace:"App.Events"};let ko=Tc;class wc extends ko{constructor(){super(...arguments),this.channels={}}connect(){if(typeof this.options.client<"u")this.pusher=this.options.client;else if(this.options.Pusher)this.pusher=new this.options.Pusher(this.options.key,this.options);else if(typeof window<"u"&&typeof window.Pusher<"u")this.pusher=new window.Pusher(this.options.key,this.options);else throw new Error("Pusher client not found. Should be globally available or passed via options.client")}signin(){this.pusher.signin()}listen(e,n,a){return this.channel(e).listen(n,a)}channel(e){return this.channels[e]||(this.channels[e]=new Sf(this.pusher,e,this.options)),this.channels[e]}privateChannel(e){return this.channels["private-"+e]||(this.channels["private-"+e]=new fy(this.pusher,"private-"+e,this.options)),this.channels["private-"+e]}encryptedPrivateChannel(e){return this.channels["private-encrypted-"+e]||(this.channels["private-encrypted-"+e]=new X_(this.pusher,"private-encrypted-"+e,this.options)),this.channels["private-encrypted-"+e]}presenceChannel(e){return this.channels["presence-"+e]||(this.channels["presence-"+e]=new J_(this.pusher,"presence-"+e,this.options)),this.channels["presence-"+e]}leave(e){[e,"private-"+e,"private-encrypted-"+e,"presence-"+e].forEach(n=>{this.leaveChannel(n)})}leaveChannel(e){this.channels[e]&&(this.channels[e].unsubscribe(),delete this.channels[e])}socketId(){return this.pusher.connection.socket_id}connectionStatus(){const e=this.pusher.connection.state;switch(e){case"connected":case"connecting":return e;case"failed":case"unavailable":return"failed";default:return"disconnected"}}onConnectionChange(e){const n=()=>{e(this.connectionStatus())},a=["state_change","connected","disconnected"];return a.forEach(i=>{this.pusher.connection.bind(i,n)}),()=>{a.forEach(i=>{this.pusher.connection.unbind(i,n)})}}disconnect(){this.pusher.disconnect()}}class tA extends ko{constructor(){super(...arguments),this.channels={}}connect(){const e=this.getSocketIO();this.socket=e(this.options.host??void 0,this.options),this.socket.io.on("reconnect",()=>{Object.values(this.channels).forEach(n=>{n.subscribe()})})}getSocketIO(){if(typeof this.options.client<"u")return this.options.client;if(typeof window<"u"&&typeof window.io<"u")return window.io;throw new Error("Socket.io client not found. Should be globally available or passed via options.client")}listen(e,n,a){return this.channel(e).listen(n,a)}channel(e){return this.channels[e]||(this.channels[e]=new hy(this.socket,e,this.options)),this.channels[e]}privateChannel(e){return this.channels["private-"+e]||(this.channels["private-"+e]=new gy(this.socket,"private-"+e,this.options)),this.channels["private-"+e]}presenceChannel(e){return this.channels["presence-"+e]||(this.channels["presence-"+e]=new Q_(this.socket,"presence-"+e,this.options)),this.channels["presence-"+e]}leave(e){[e,"private-"+e,"presence-"+e].forEach(n=>{this.leaveChannel(n)})}leaveChannel(e){this.channels[e]&&(this.channels[e].unsubscribe(),delete this.channels[e])}socketId(){return this.socket.id}connectionStatus(){return this.socket.connected?"connected":this.socket.io._reconnecting?"reconnecting":this.socket.id!==void 0?"disconnected":"connecting"}onConnectionChange(e){const n=()=>{e(this.connectionStatus())},a=["connect","disconnect","connect_error","reconnect_attempt","reconnect","reconnect_error","reconnect_failed"];return a.forEach(i=>{this.socket.on(i,n)}),()=>{a.forEach(i=>{this.socket.off(i,n)})}}disconnect(){this.socket.disconnect()}}class my extends ko{constructor(){super(...arguments),this.channels={}}connect(){}listen(e,n,a){return new Sc}channel(e){return new Sc}privateChannel(e){return new py}encryptedPrivateChannel(e){return new Z_}presenceChannel(e){return new eA}leave(e){}leaveChannel(e){}socketId(){return"fake-socket-id"}connectionStatus(){return"connected"}onConnectionChange(e){return()=>{}}disconnect(){}}class nA{constructor(e){this.options=e,this.connect(),this.options.withoutInterceptors||this.registerInterceptors()}channel(e){return this.connector.channel(e)}connect(){if(this.options.broadcaster==="reverb")this.connector=new wc({...this.options,cluster:""});else if(this.options.broadcaster==="pusher")this.connector=new wc(this.options);else if(this.options.broadcaster==="ably")this.connector=new wc({...this.options,cluster:"",broadcaster:"pusher"});else if(this.options.broadcaster==="socket.io")this.connector=new tA(this.options);else if(this.options.broadcaster==="null")this.connector=new my(this.options);else if(typeof this.options.broadcaster=="function"&&Y_(this.options.broadcaster))this.connector=new this.options.broadcaster(this.options);else throw new Error(`Broadcaster ${typeof this.options.broadcaster} ${String(this.options.broadcaster)} is not supported.`)}disconnect(){this.connector.disconnect()}join(e){return this.connector.presenceChannel(e)}leave(e){this.connector.leave(e)}leaveChannel(e){this.connector.leaveChannel(e)}leaveAllChannels(){for(const e in this.connector.channels)this.leaveChannel(e)}listen(e,n,a){return this.connector.listen(e,n,a)}private(e){return this.connector.privateChannel(e)}encryptedPrivate(e){if(this.connectorSupportsEncryptedPrivateChannels(this.connector))return this.connector.encryptedPrivateChannel(e);throw new Error(`Broadcaster ${typeof this.options.broadcaster} ${String(this.options.broadcaster)} does not support encrypted private channels.`)}connectorSupportsEncryptedPrivateChannels(e){return e instanceof wc||e instanceof my}socketId(){return this.connector.socketId()}connectionStatus(){return this.connector.connectionStatus()}registerInterceptors(){typeof Vue<"u"&&Vue?.http&&this.registerVueRequestInterceptor(),typeof axios=="function"&&this.registerAxiosRequestInterceptor(),typeof jQuery=="function"&&this.registerjQueryAjaxSetup(),typeof Turbo=="object"&&this.registerTurboRequestInterceptor()}registerVueRequestInterceptor(){Vue.http.interceptors.push((e,n)=>{this.socketId()&&e.headers.set("X-Socket-ID",this.socketId()),n()})}registerAxiosRequestInterceptor(){axios.interceptors.request.use(e=>(this.socketId()&&(e.headers["X-Socket-Id"]=this.socketId()),e))}registerjQueryAjaxSetup(){typeof jQuery.ajax<"u"&&jQuery.ajaxPrefilter((e,n,a)=>{this.socketId()&&a.setRequestHeader("X-Socket-Id",this.socketId())})}registerTurboRequestInterceptor(){document.addEventListener("turbo:before-fetch-request",e=>{e.detail.fetchOptions.headers["X-Socket-Id"]=this.socketId()})}}var wf={exports:{}};var yy;function rA(){return yy||(yy=1,(function(o,e){(function(a,i){o.exports=i()})(self,()=>(()=>{var n={594(c,d){var f=this&&this.__extends||(function(){var M=function(T,D){return M=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(z,P){z.__proto__=P}||function(z,P){for(var q in P)P.hasOwnProperty(q)&&(z[q]=P[q])},M(T,D)};return function(T,D){M(T,D);function z(){this.constructor=T}T.prototype=D===null?Object.create(D):(z.prototype=D.prototype,new z)}})();Object.defineProperty(d,"__esModule",{value:!0});var g=256,m=(function(){function M(T){T===void 0&&(T="="),this._paddingCharacter=T}return M.prototype.encodedLength=function(T){return this._paddingCharacter?(T+2)/3*4|0:(T*8+5)/6|0},M.prototype.encode=function(T){for(var D="",z=0;z>>18&63),D+=this._encodeByte(P>>>12&63),D+=this._encodeByte(P>>>6&63),D+=this._encodeByte(P>>>0&63)}var q=T.length-z;if(q>0){var P=T[z]<<16|(q===2?T[z+1]<<8:0);D+=this._encodeByte(P>>>18&63),D+=this._encodeByte(P>>>12&63),q===2?D+=this._encodeByte(P>>>6&63):D+=this._paddingCharacter||"",D+=this._paddingCharacter||""}return D},M.prototype.maxDecodedLength=function(T){return this._paddingCharacter?T/4*3|0:(T*6+7)/8|0},M.prototype.decodedLength=function(T){return this.maxDecodedLength(T.length-this._getPaddingLength(T))},M.prototype.decode=function(T){if(T.length===0)return new Uint8Array(0);for(var D=this._getPaddingLength(T),z=T.length-D,P=new Uint8Array(this.maxDecodedLength(z)),q=0,W=0,he=0,Ee=0,be=0,Ue=0,Fe=0;W>>4,P[q++]=be<<4|Ue>>>2,P[q++]=Ue<<6|Fe,he|=Ee&g,he|=be&g,he|=Ue&g,he|=Fe&g;if(W>>4,he|=Ee&g,he|=be&g),W>>2,he|=Ue&g),W>>8&6,D+=51-T>>>8&-75,D+=61-T>>>8&-15,D+=62-T>>>8&3,String.fromCharCode(D)},M.prototype._decodeChar=function(T){var D=g;return D+=(42-T&T-44)>>>8&-g+T-43+62,D+=(46-T&T-48)>>>8&-g+T-47+63,D+=(47-T&T-58)>>>8&-g+T-48+52,D+=(64-T&T-91)>>>8&-g+T-65+0,D+=(96-T&T-123)>>>8&-g+T-97+26,D},M.prototype._getPaddingLength=function(T){var D=0;if(this._paddingCharacter){for(var z=T.length-1;z>=0&&T[z]===this._paddingCharacter;z--)D++;if(T.length<4||D>2)throw new Error("Base64Coder: incorrect padding")}return D},M})();d.Coder=m;var y=new m;function S(M){return y.encode(M)}d.encode=S;function v(M){return y.decode(M)}d.decode=v;var _=(function(M){f(T,M);function T(){return M!==null&&M.apply(this,arguments)||this}return T.prototype._encodeByte=function(D){var z=D;return z+=65,z+=25-D>>>8&6,z+=51-D>>>8&-75,z+=61-D>>>8&-13,z+=62-D>>>8&49,String.fromCharCode(z)},T.prototype._decodeChar=function(D){var z=g;return z+=(44-D&D-46)>>>8&-g+D-45+62,z+=(94-D&D-96)>>>8&-g+D-95+63,z+=(47-D&D-58)>>>8&-g+D-48+52,z+=(64-D&D-91)>>>8&-g+D-65+0,z+=(96-D&D-123)>>>8&-g+D-97+26,z},T})(m);d.URLSafeCoder=_;var A=new _;function x(M){return A.encode(M)}d.encodeURLSafe=x;function O(M){return A.decode(M)}d.decodeURLSafe=O,d.encodedLength=function(M){return y.encodedLength(M)},d.maxDecodedLength=function(M){return y.maxDecodedLength(M)},d.decodedLength=function(M){return y.decodedLength(M)}},978(c,d){var f="utf8: invalid source encoding";function g(m){for(var y=[],S=0;S=m.length)throw new Error(f);var A=m[++S];if((A&192)!==128)throw new Error(f);v=(v&31)<<6|A&63,_=128}else if(v<240){if(S>=m.length-1)throw new Error(f);var A=m[++S],x=m[++S];if((A&192)!==128||(x&192)!==128)throw new Error(f);v=(v&15)<<12|(A&63)<<6|x&63,_=2048}else if(v<248){if(S>=m.length-2)throw new Error(f);var A=m[++S],x=m[++S],O=m[++S];if((A&192)!==128||(x&192)!==128||(O&192)!==128)throw new Error(f);v=(v&15)<<18|(A&63)<<12|(x&63)<<6|O&63,_=65536}else throw new Error(f);if(v<_||v>=55296&&v<=57343)throw new Error(f);if(v>=65536){if(v>1114111)throw new Error(f);v-=65536,y.push(String.fromCharCode(55296|v>>10)),v=56320|v&1023}}y.push(String.fromCharCode(v))}return y.join("")}d.D4=g},721(c,d,f){c.exports=f(207).default},207(c,d,f){f.d(d,{default:()=>_s});class g{constructor(b,w){this.lastId=0,this.prefix=b,this.name=w}create(b){this.lastId++;var w=this.lastId,L=this.prefix+w,I=this.name+"["+w+"]",re=!1,me=function(){re||(b.apply(null,arguments),re=!0)};return this[w]=me,{number:w,id:L,name:I,callback:me}}remove(b){delete this[b.number]}}var m=new g("_pusher_script_","Pusher.ScriptReceivers"),y={VERSION:"8.6.0",PROTOCOL:7,wsPort:80,wssPort:443,wsPath:"",httpHost:"sockjs.pusher.com",httpPort:80,httpsPort:443,httpPath:"/pusher",stats_host:"stats.pusher.com",authEndpoint:"/pusher/auth",authTransport:"ajax",activityTimeout:12e4,pongTimeout:3e4,unavailableTimeout:1e4,userAuthentication:{endpoint:"/pusher/user-auth",transport:"ajax"},channelAuthorization:{endpoint:"/pusher/auth",transport:"ajax"},cdn_http:"http://js.pusher.com",cdn_https:"https://js.pusher.com",dependency_suffix:""};const S=y;class v{constructor(b){this.options=b,this.receivers=b.receivers||m,this.loading={}}load(b,w,L){var I=this;if(I.loading[b]&&I.loading[b].length>0)I.loading[b].push(L);else{I.loading[b]=[L];var re=ze.createScriptRequest(I.getPath(b,w)),me=I.receivers.create(function(Le){if(I.receivers.remove(me),I.loading[b]){var We=I.loading[b];delete I.loading[b];for(var dt=function(Pt){Pt||re.cleanup()},pt=0;pt>>6)+J(128|b&63):J(224|b>>>12&15)+J(128|b>>>6&63)+J(128|b&63)},te=function(E){return E.replace(/[^\x00-\x7F]/g,Me)},pe=function(E){var b=[0,2,1][E.length%3],w=E.charCodeAt(0)<<16|(E.length>1?E.charCodeAt(1):0)<<8|(E.length>2?E.charCodeAt(2):0),L=[ue.charAt(w>>>18),ue.charAt(w>>>12&63),b>=2?"=":ue.charAt(w>>>6&63),b>=1?"=":ue.charAt(w&63)];return L.join("")},N=typeof window<"u"&&window.btoa||function(E){return E.replace(/[\s\S]{1,3}/g,pe)};class R{constructor(b,w,L,I){this.clear=w,this.timer=b(()=>{this.timer&&(this.timer=I(this.timer))},L)}isRunning(){return this.timer!==null}ensureAborted(){this.timer&&(this.clear(this.timer),this.timer=null)}}const de=R;function fe(E){window.clearTimeout(E)}function xe(E){window.clearInterval(E)}class ee extends de{constructor(b,w){super(setTimeout,fe,b,function(L){return w(),null})}}class _e extends de{constructor(b,w){super(setInterval,xe,b,function(L){return w(),L})}}var Be={now(){return Date.now?Date.now():new Date().valueOf()},defer(E){return new ee(0,E)},method(E,...b){var w=Array.prototype.slice.call(arguments,1);return function(L){return L[E].apply(L,w.concat(arguments))}}};const ke=Be;function Ie(E,...b){for(var w=0;w{window.console&&window.console.log&&window.console.log(b)}}debug(...b){this.log(this.globalLog,b)}warn(...b){this.log(this.globalLogWarn,b)}error(...b){this.log(this.globalLogError,b)}globalLogWarn(b){window.console&&window.console.warn?window.console.warn(b):this.globalLog(b)}globalLogError(b){window.console&&window.console.error?window.console.error(b):this.globalLogWarn(b)}log(b,...w){var L=Ct.apply(this,arguments);_s.log?_s.log(L):_s.logToConsole&&b.bind(this)(L)}}const rt=new On;var Hn=function(E,b,w,L,I){(w.headers!==void 0||w.headersProvider!=null)&&rt.warn(`To send headers with the ${L.toString()} request, you must use AJAX, rather than JSONP.`);var re=E.nextAuthCallbackID.toString();E.nextAuthCallbackID++;var me=E.getDocument(),Le=me.createElement("script");E.auth_callbacks[re]=function(pt){I(null,pt)};var We="Pusher.auth_callbacks['"+re+"']";Le.src=w.endpoint+"?callback="+encodeURIComponent(We)+"&"+b;var dt=me.getElementsByTagName("head")[0]||me.documentElement;dt.insertBefore(Le,dt.firstChild)};const bn=Hn;class ra{constructor(b){this.src=b}send(b){var w=this,L="Error loading "+w.src;w.script=document.createElement("script"),w.script.id=b.id,w.script.src=w.src,w.script.type="text/javascript",w.script.charset="UTF-8",w.script.addEventListener?(w.script.onerror=function(){b.callback(L)},w.script.onload=function(){b.callback(null)}):w.script.onreadystatechange=function(){(w.script.readyState==="loaded"||w.script.readyState==="complete")&&b.callback(null)},w.script.async===void 0&&document.attachEvent&&/opera/i.test(navigator.userAgent)?(w.errorScript=document.createElement("script"),w.errorScript.id=b.id+"_error",w.errorScript.text=b.name+"('"+L+"');",w.script.async=w.errorScript.async=!1):w.script.async=!0;var I=document.getElementsByTagName("head")[0];I.insertBefore(w.script,I.firstChild),w.errorScript&&I.insertBefore(w.errorScript,w.script.nextSibling)}cleanup(){this.script&&(this.script.onload=this.script.onerror=null,this.script.onreadystatechange=null),this.script&&this.script.parentNode&&this.script.parentNode.removeChild(this.script),this.errorScript&&this.errorScript.parentNode&&this.errorScript.parentNode.removeChild(this.errorScript),this.script=null,this.errorScript=null}}class sr{constructor(b,w){this.url=b,this.data=w}send(b){if(!this.request){var w=Ci(this.data),L=this.url+"/"+b.number+"?"+w;this.request=ze.createScriptRequest(L),this.request.send(b)}}cleanup(){this.request&&this.request.cleanup()}}var cn=function(E,b){return function(w,L){var I="http"+(b?"s":"")+"://",re=I+(E.host||E.options.host)+E.options.path,me=ze.createJSONPRequest(re,w),Le=ze.ScriptReceivers.create(function(We,dt){m.remove(Le),me.cleanup(),dt&&dt.host&&(E.host=dt.host),L&&L(We,dt)});me.send(Le)}},Tn={name:"jsonp",getAgent:cn};const un=Tn;function V(E,b,w){var L=E+(b.useTLS?"s":""),I=b.useTLS?b.hostTLS:b.hostNonTLS;return L+"://"+I+w}function se(E,b){var w="/app/"+E,L="?protocol="+S.PROTOCOL+"&client=js&version="+S.VERSION+(b?"&"+b:"");return w+L}var ce={getInitial:function(E,b){var w=(b.httpPath||"")+se(E,"flash=false");return V("ws",b,w)}},Z={getInitial:function(E,b){var w=(b.httpPath||"/pusher")+se(E);return V("http",b,w)}},ve={getInitial:function(E,b){return V("http",b,b.httpPath||"/pusher")},getPath:function(E,b){return se(E)}};class ge{constructor(){this._callbacks={}}get(b){return this._callbacks[we(b)]}add(b,w,L){var I=we(b);this._callbacks[I]=this._callbacks[I]||[],this._callbacks[I].push({fn:w,context:L})}remove(b,w,L){if(!b&&!w&&!L){this._callbacks={};return}var I=b?[we(b)]:vt(this._callbacks);w||L?this.removeCallback(I,w,L):this.removeAllCallbacks(I)}removeCallback(b,w,L){Oe(b,function(I){this._callbacks[I]=Mt(this._callbacks[I]||[],function(re){return w&&w!==re.fn||L&&L!==re.context}),this._callbacks[I].length===0&&delete this._callbacks[I]},this)}removeAllCallbacks(b){Oe(b,function(w){delete this._callbacks[w]},this)}}function we(E){return"_"+E}class Ae{constructor(b){this.callbacks=new ge,this.global_callbacks=[],this.failThrough=b}bind(b,w,L){return this.callbacks.add(b,w,L),this}bind_global(b){return this.global_callbacks.push(b),this}unbind(b,w,L){return this.callbacks.remove(b,w,L),this}unbind_global(b){return b?(this.global_callbacks=Mt(this.global_callbacks||[],w=>w!==b),this):(this.global_callbacks=[],this)}unbind_all(){return this.unbind(),this.unbind_global(),this}emit(b,w,L){for(var I=0;I0)for(var I=0;I{this.onError(w),this.changeState("closed")}),!1}return this.bindListeners(),rt.debug("Connecting",{transport:this.name,url:b}),this.changeState("connecting"),!0}close(){return this.socket?(this.socket.close(),!0):!1}send(b){return this.state==="open"?(ke.defer(()=>{this.socket&&this.socket.send(b)}),!0):!1}ping(){this.state==="open"&&this.supportsPing()&&this.socket.ping()}onOpen(){this.hooks.beforeOpen&&this.hooks.beforeOpen(this.socket,this.hooks.urls.getPath(this.key,this.options)),this.changeState("open"),this.socket.onopen=void 0}onError(b){this.emit("error",{type:"WebSocketError",error:b}),this.timeline.error(this.buildTimelineMessage({error:b.toString()}))}onClose(b){b?this.changeState("closed",{code:b.code,reason:b.reason,wasClean:b.wasClean}):this.changeState("closed"),this.unbindListeners(),this.socket=void 0}onMessage(b){this.emit("message",b)}onActivity(){this.emit("activity")}bindListeners(){this.socket.onopen=()=>{this.onOpen()},this.socket.onerror=b=>{this.onError(b)},this.socket.onclose=b=>{this.onClose(b)},this.socket.onmessage=b=>{this.onMessage(b)},this.supportsPing()&&(this.socket.onactivity=()=>{this.onActivity()})}unbindListeners(){this.socket&&(this.socket.onopen=void 0,this.socket.onerror=void 0,this.socket.onclose=void 0,this.socket.onmessage=void 0,this.supportsPing()&&(this.socket.onactivity=void 0))}changeState(b,w){this.state=b,this.timeline.info(this.buildTimelineMessage({state:b,params:w})),this.emit(b,w)}buildTimelineMessage(b){return Ie({cid:this.id},b)}}class Te{constructor(b){this.hooks=b}isSupported(b){return this.hooks.isSupported(b)}createConnection(b,w,L,I){return new Ke(this.hooks,b,w,L,I)}}var $e=new Te({urls:ce,handlesActivityChecks:!1,supportsPing:!1,isInitialized:function(){return!!ze.getWebSocketAPI()},isSupported:function(){return!!ze.getWebSocketAPI()},getSocket:function(E){return ze.createWebSocket(E)}}),Re={urls:Z,handlesActivityChecks:!1,supportsPing:!0,isInitialized:function(){return!0}},et=Ie({getSocket:function(E){return ze.HTTPFactory.createStreamingSocket(E)}},Re),je=Ie({getSocket:function(E){return ze.HTTPFactory.createPollingSocket(E)}},Re),Ve={isSupported:function(){return ze.isXHRSupported()}},Se=new Te(Ie({},et,Ve)),He=new Te(Ie({},je,Ve)),tt={ws:$e,xhr_streaming:Se,xhr_polling:He};const Tt=tt;var It=new Te({file:"sockjs",urls:ve,handlesActivityChecks:!0,supportsPing:!1,isSupported:function(){return!0},isInitialized:function(){return window.SockJS!==void 0},getSocket:function(E,b){return new window.SockJS(E,null,{js_path:A.getPath("sockjs",{useTLS:b.useTLS}),ignore_null_origin:b.ignoreNullOrigin})},beforeOpen:function(E,b){E.send(JSON.stringify({path:b}))}}),Ln={isSupported:function(E){var b=ze.isXDRSupported(E.useTLS);return b}},zt=new Te(Ie({},et,Ln)),Ut=new Te(Ie({},je,Ln));Tt.xdr_streaming=zt,Tt.xdr_polling=Ut,Tt.sockjs=It;const Rn=Tt;class $a extends Ae{constructor(){super();var b=this;typeof window<"u"&&window.addEventListener!==void 0&&(window.addEventListener("online",function(){b.emit("online")},!1),window.addEventListener("offline",function(){b.emit("offline")},!1))}isOnline(){return window.navigator.onLine===void 0?!0:window.navigator.onLine}}var jo=new $a;class Rf{constructor(b,w,L){this.manager=b,this.transport=w,this.minPingDelay=L.minPingDelay,this.maxPingDelay=L.maxPingDelay,this.pingDelay=void 0}createConnection(b,w,L,I){I=Ie({},I,{activityTimeout:this.pingDelay});var re=this.transport.createConnection(b,w,L,I),me=null,Le=function(){re.unbind("open",Le),re.bind("closed",We),me=ke.now()},We=dt=>{if(re.unbind("closed",We),dt.code===1002||dt.code===1003)this.manager.reportDeath();else if(!dt.wasClean&&me){var pt=ke.now()-me;pt<2*this.maxPingDelay&&(this.manager.reportDeath(),this.pingDelay=Math.max(pt/2,this.minPingDelay))}};return re.bind("open",Le),re}isSupported(b){return this.manager.isAlive()&&this.transport.isSupported(b)}}const kc={decodeMessage:function(E){try{var b=JSON.parse(E.data),w=b.data;if(typeof w=="string")try{w=JSON.parse(b.data)}catch{}var L={event:b.event,channel:b.channel,data:w};return b.user_id&&(L.user_id=b.user_id),L}catch(I){throw{type:"MessageParseError",error:I,data:E.data}}},encodeMessage:function(E){return JSON.stringify(E)},processHandshake:function(E){var b=kc.decodeMessage(E);if(b.event==="pusher:connection_established"){if(!b.data.activity_timeout)throw"No activity timeout specified in handshake";return{action:"connected",id:b.data.socket_id,activityTimeout:b.data.activity_timeout*1e3}}else{if(b.event==="pusher:error")return{action:this.getCloseAction(b.data),error:this.getCloseError(b.data)};throw"Invalid handshake"}},getCloseAction:function(E){return E.code<4e3?E.code>=1002&&E.code<=1004?"backoff":null:E.code===4e3?"tls_only":E.code<4100?"refused":E.code<4200?"backoff":E.code<4300?"retry":"refused"},getCloseError:function(E){return E.code!==1e3&&E.code!==1001?{type:"PusherError",data:{code:E.code,message:E.reason||E.message}}:null}},qr=kc;class Ei extends Ae{constructor(b,w){super(),this.id=b,this.transport=w,this.activityTimeout=w.activityTimeout,this.bindListeners()}handlesActivityChecks(){return this.transport.handlesActivityChecks()}send(b){return this.transport.send(b)}send_event(b,w,L){var I={event:b,data:w};return L&&(I.channel=L),rt.debug("Event sent",I),this.send(qr.encodeMessage(I))}ping(){this.transport.supportsPing()?this.transport.ping():this.send_event("pusher:ping",{})}close(){this.transport.close()}bindListeners(){var b={message:L=>{var I;try{I=qr.decodeMessage(L)}catch(re){this.emit("error",{type:"MessageParseError",error:re,data:L.data})}if(I!==void 0){switch(rt.debug("Event recd",I),I.event){case"pusher:error":this.emit("error",{type:"PusherError",data:I.data});break;case"pusher:ping":this.emit("ping");break;case"pusher:pong":this.emit("pong");break}this.emit("message",I)}},activity:()=>{this.emit("activity")},error:L=>{this.emit("error",L)},closed:L=>{w(),L&&L.code&&this.handleCloseEvent(L),this.transport=null,this.emit("closed")}},w=()=>{Ot(b,(L,I)=>{this.transport.unbind(I,L)})};Ot(b,(L,I)=>{this.transport.bind(I,L)})}handleCloseEvent(b){var w=qr.getCloseAction(b),L=qr.getCloseError(b);L&&this.emit("error",L),w&&this.emit(w,{action:w,error:L})}}class zo{constructor(b,w){this.transport=b,this.callback=w,this.bindListeners()}close(){this.unbindListeners(),this.transport.close()}bindListeners(){this.onMessage=b=>{this.unbindListeners();var w;try{w=qr.processHandshake(b)}catch(L){this.finish("error",{error:L}),this.transport.close();return}w.action==="connected"?this.finish("connected",{connection:new Ei(w.id,this.transport),activityTimeout:w.activityTimeout}):(this.finish(w.action,{error:w.error}),this.transport.close())},this.onClosed=b=>{this.unbindListeners();var w=qr.getCloseAction(b)||"backoff",L=qr.getCloseError(b);this.finish(w,{error:L})},this.transport.bind("message",this.onMessage),this.transport.bind("closed",this.onClosed)}unbindListeners(){this.transport.unbind("message",this.onMessage),this.transport.unbind("closed",this.onClosed)}finish(b,w){this.callback(Ie({transport:this.transport,action:b},w))}}class Na{constructor(b,w){this.timeline=b,this.options=w||{}}send(b,w){this.timeline.isEmpty()||this.timeline.send(ze.TimelineTransport.getAgent(this,b),w)}}class Gr extends Ae{constructor(b,w){super(function(L,I){rt.debug("No callbacks on "+b+" for "+L)}),this.name=b,this.pusher=w,this.subscribed=!1,this.subscriptionPending=!1,this.subscriptionCancelled=!1}authorize(b,w){return w(null,{auth:""})}trigger(b,w){if(b.indexOf("client-")!==0)throw new D("Event '"+b+"' does not start with 'client-'");if(!this.subscribed){var L=M.buildLogSuffix("triggeringClientEvents");rt.warn(`Client event triggered before channel 'subscription_succeeded' event . ${L}`)}return this.pusher.send_event(b,w,this.name)}disconnect(){this.subscribed=!1,this.subscriptionPending=!1}handleEvent(b){var w=b.event,L=b.data;if(w==="pusher_internal:subscription_succeeded")this.handleSubscriptionSucceededEvent(b);else if(w==="pusher_internal:subscription_count")this.handleSubscriptionCountEvent(b);else if(w.indexOf("pusher_internal:")!==0){var I={};this.emit(w,L,I)}}handleSubscriptionSucceededEvent(b){this.subscriptionPending=!1,this.subscribed=!0,this.subscriptionCancelled?this.pusher.unsubscribe(this.name):this.emit("pusher:subscription_succeeded",b.data)}handleSubscriptionCountEvent(b){b.data.subscription_count&&(this.subscriptionCount=b.data.subscription_count),this.emit("pusher:subscription_count",b.data)}subscribe(){this.subscribed||(this.subscriptionPending=!0,this.subscriptionCancelled=!1,this.authorize(this.pusher.connection.socket_id,(b,w)=>{b?(this.subscriptionPending=!1,rt.error(b.toString()),this.emit("pusher:subscription_error",Object.assign({},{type:"AuthError",error:b.message},b instanceof Ue?{status:b.status}:{}))):this.pusher.send_event("pusher:subscribe",{auth:w.auth,channel_data:w.channel_data,channel:this.name})}))}unsubscribe(){this.subscribed=!1,this.pusher.send_event("pusher:unsubscribe",{channel:this.name})}cancelSubscription(){this.subscriptionCancelled=!0}reinstateSubscription(){this.subscriptionCancelled=!1}}class aa extends Gr{authorize(b,w){return this.pusher.config.channelAuthorizer({channelName:this.name,socketId:b},w)}}class Ia{constructor(){this.reset()}get(b){return Object.prototype.hasOwnProperty.call(this.members,b)?{id:b,info:this.members[b]}:null}each(b){Ot(this.members,(w,L)=>{b(this.get(L))})}setMyID(b){this.myID=b}onSubscription(b){this.members=b.presence.hash,this.count=b.presence.count,this.me=this.get(this.myID)}addMember(b){return this.get(b.user_id)===null&&this.count++,this.members[b.user_id]=b.user_info,this.get(b.user_id)}removeMember(b){var w=this.get(b.user_id);return w&&(delete this.members[b.user_id],this.count--),w}reset(){this.members={},this.count=0,this.myID=null,this.me=null}}var Zt=function(E,b,w,L){function I(re){return re instanceof w?re:new w(function(me){me(re)})}return new(w||(w=Promise))(function(re,me){function Le(pt){try{dt(L.next(pt))}catch(Pt){me(Pt)}}function We(pt){try{dt(L.throw(pt))}catch(Pt){me(Pt)}}function dt(pt){pt.done?re(pt.value):I(pt.value).then(Le,We)}dt((L=L.apply(E,b||[])).next())})};class Dc extends aa{constructor(b,w){super(b,w),this.members=new Ia}authorize(b,w){super.authorize(b,(L,I)=>Zt(this,void 0,void 0,function*(){if(!L)if(I=I,I.channel_data!=null){var re=JSON.parse(I.channel_data);this.members.setMyID(re.user_id)}else if(yield this.pusher.user.signinDonePromise,this.pusher.user.user_data!=null)this.members.setMyID(this.pusher.user.user_data.id);else{let me=M.buildLogSuffix("authorizationEndpoint");rt.error(`Invalid auth response for channel '${this.name}', expected 'channel_data' field. ${me}, or the user should be signed in.`),w("Invalid auth response");return}w(L,I)}))}handleEvent(b){var w=b.event;if(w.indexOf("pusher_internal:")===0)this.handleInternalEvent(b);else{var L=b.data,I={};b.user_id&&(I.user_id=b.user_id),this.emit(w,L,I)}}handleInternalEvent(b){var w=b.event,L=b.data;switch(w){case"pusher_internal:subscription_succeeded":this.handleSubscriptionSucceededEvent(b);break;case"pusher_internal:subscription_count":this.handleSubscriptionCountEvent(b);break;case"pusher_internal:member_added":var I=this.members.addMember(L);this.emit("pusher:member_added",I);break;case"pusher_internal:member_removed":var re=this.members.removeMember(L);re&&this.emit("pusher:member_removed",re);break}}handleSubscriptionSucceededEvent(b){this.subscriptionPending=!1,this.subscribed=!0,this.subscriptionCancelled?this.pusher.unsubscribe(this.name):(this.members.onSubscription(b.data),this.emit("pusher:subscription_succeeded",this.members))}disconnect(){this.members.reset(),super.disconnect()}}var Oc=f(978),Tr=f(594);class Ha extends aa{constructor(b,w,L){super(b,w),this.key=null,this.nacl=L}authorize(b,w){super.authorize(b,(L,I)=>{if(L){w(L,I);return}let re=I.shared_secret;if(!re){w(new Error(`No shared_secret key in auth payload for encrypted channel: ${this.name}`),null);return}this.key=(0,Tr.decode)(re),delete I.shared_secret,w(null,I)})}trigger(b,w){throw new he("Client events are not currently supported for encrypted channels")}handleEvent(b){var w=b.event,L=b.data;if(w.indexOf("pusher_internal:")===0||w.indexOf("pusher:")===0){super.handleEvent(b);return}this.handleEncryptedEvent(w,L)}handleEncryptedEvent(b,w){if(!this.key){rt.debug("Received encrypted event before key has been retrieved from the authEndpoint");return}if(!w.ciphertext||!w.nonce){rt.error("Unexpected format for encrypted event, expected object with `ciphertext` and `nonce` fields, got: "+w);return}let L=(0,Tr.decode)(w.ciphertext);if(L.length{if(me){rt.error(`Failed to make a request to the authEndpoint: ${Le}. Unable to fetch new key, so dropping encrypted event`);return}if(re=this.nacl.secretbox.open(L,I,this.key),re===null){rt.error("Failed to decrypt event with new key. Dropping encrypted event");return}this.emit(b,this.getDataToEmit(re))});return}this.emit(b,this.getDataToEmit(re))}getDataToEmit(b){let w=(0,Oc.D4)(b);try{return JSON.parse(w)}catch{return w}}}class kf extends Ae{constructor(b,w){super(),this.state="initialized",this.connection=null,this.key=b,this.options=w,this.timeline=this.options.timeline,this.usingTLS=this.options.useTLS,this.errorCallbacks=this.buildErrorCallbacks(),this.connectionCallbacks=this.buildConnectionCallbacks(this.errorCallbacks),this.handshakeCallbacks=this.buildHandshakeCallbacks(this.errorCallbacks);var L=ze.getNetwork();L.bind("online",()=>{this.timeline.info({netinfo:"online"}),(this.state==="connecting"||this.state==="unavailable")&&this.retryIn(0)}),L.bind("offline",()=>{this.timeline.info({netinfo:"offline"}),this.connection&&this.sendActivityCheck()}),this.updateStrategy()}switchCluster(b){this.key=b,this.updateStrategy(),this.retryIn(0)}connect(){if(!(this.connection||this.runner)){if(!this.strategy.isSupported()){this.updateState("failed");return}this.updateState("connecting"),this.startConnecting(),this.setUnavailableTimer()}}send(b){return this.connection?this.connection.send(b):!1}send_event(b,w,L){return this.connection?this.connection.send_event(b,w,L):!1}disconnect(){this.disconnectInternally(),this.updateState("disconnected")}isUsingTLS(){return this.usingTLS}startConnecting(){var b=(w,L)=>{w?this.runner=this.strategy.connect(0,b):L.action==="error"?(this.emit("error",{type:"HandshakeError",error:L.error}),this.timeline.error({handshakeError:L.error})):(this.abortConnecting(),this.handshakeCallbacks[L.action](L))};this.runner=this.strategy.connect(0,b)}abortConnecting(){this.runner&&(this.runner.abort(),this.runner=null)}disconnectInternally(){if(this.abortConnecting(),this.clearRetryTimer(),this.clearUnavailableTimer(),this.connection){var b=this.abandonConnection();b.close()}}updateStrategy(){this.strategy=this.options.getStrategy({key:this.key,timeline:this.timeline,useTLS:this.usingTLS})}retryIn(b){this.timeline.info({action:"retry",delay:b}),b>0&&this.emit("connecting_in",Math.round(b/1e3)),this.retryTimer=new ee(b||0,()=>{this.disconnectInternally(),this.connect()})}clearRetryTimer(){this.retryTimer&&(this.retryTimer.ensureAborted(),this.retryTimer=null)}setUnavailableTimer(){this.unavailableTimer=new ee(this.options.unavailableTimeout,()=>{this.updateState("unavailable")})}clearUnavailableTimer(){this.unavailableTimer&&this.unavailableTimer.ensureAborted()}sendActivityCheck(){this.stopActivityCheck(),this.connection.ping(),this.activityTimer=new ee(this.options.pongTimeout,()=>{this.timeline.error({pong_timed_out:this.options.pongTimeout}),this.retryIn(0)})}resetActivityCheck(){this.stopActivityCheck(),this.connection&&!this.connection.handlesActivityChecks()&&(this.activityTimer=new ee(this.activityTimeout,()=>{this.sendActivityCheck()}))}stopActivityCheck(){this.activityTimer&&this.activityTimer.ensureAborted()}buildConnectionCallbacks(b){return Ie({},b,{message:w=>{this.resetActivityCheck(),this.emit("message",w)},ping:()=>{this.send_event("pusher:pong",{})},activity:()=>{this.resetActivityCheck()},error:w=>{this.emit("error",w)},closed:()=>{this.abandonConnection(),this.shouldRetry()&&this.retryIn(1e3)}})}buildHandshakeCallbacks(b){return Ie({},b,{connected:w=>{this.activityTimeout=Math.min(this.options.activityTimeout,w.activityTimeout,w.connection.activityTimeout||1/0),this.clearUnavailableTimer(),this.setConnection(w.connection),this.socket_id=this.connection.id,this.updateState("connected",{socket_id:this.socket_id})}})}buildErrorCallbacks(){let b=w=>L=>{L.error&&this.emit("error",{type:"WebSocketError",error:L.error}),w(L)};return{tls_only:b(()=>{this.usingTLS=!0,this.updateStrategy(),this.retryIn(0)}),refused:b(()=>{this.disconnect()}),backoff:b(()=>{this.retryIn(1e3)}),retry:b(()=>{this.retryIn(0)})}}setConnection(b){this.connection=b;for(var w in this.connectionCallbacks)this.connection.bind(w,this.connectionCallbacks[w]);this.resetActivityCheck()}abandonConnection(){if(this.connection){this.stopActivityCheck();for(var b in this.connectionCallbacks)this.connection.unbind(b,this.connectionCallbacks[b]);var w=this.connection;return this.connection=null,w}}updateState(b,w){var L=this.state;if(this.state=b,L!==b){var I=b;I==="connected"&&(I+=" with new socket ID "+w.socket_id),rt.debug("State changed",L+" -> "+I),this.timeline.info({state:b,params:w}),this.emit("state_change",{previous:L,current:b}),this.emit(b,w)}}shouldRetry(){return this.state==="connecting"||this.state==="connected"}}class Lc{constructor(){this.channels={}}add(b,w){return this.channels[b]||(this.channels[b]=Mc(b,w)),this.channels[b]}all(){return _t(this.channels)}find(b){return this.channels[b]}remove(b){var w=this.channels[b];return delete this.channels[b],w}disconnect(){Ot(this.channels,function(b){b.disconnect()})}}function Mc(E,b){if(E.indexOf("private-encrypted-")===0){if(b.config.nacl)return jn.createEncryptedChannel(E,b,b.config.nacl);let w="Tried to subscribe to a private-encrypted- channel but no nacl implementation available",L=M.buildLogSuffix("encryptedChannelSupport");throw new he(`${w}. ${L}`)}else{if(E.indexOf("private-")===0)return jn.createPrivateChannel(E,b);if(E.indexOf("presence-")===0)return jn.createPresenceChannel(E,b);if(E.indexOf("#")===0)throw new z('Cannot create a channel with name "'+E+'".');return jn.createChannel(E,b)}}var Df={createChannels(){return new Lc},createConnectionManager(E,b){return new kf(E,b)},createChannel(E,b){return new Gr(E,b)},createPrivateChannel(E,b){return new aa(E,b)},createPresenceChannel(E,b){return new Dc(E,b)},createEncryptedChannel(E,b,w){return new Ha(E,b,w)},createTimelineSender(E,b){return new Na(E,b)},createHandshake(E,b){return new zo(E,b)},createAssistantToTheTransportManager(E,b,w){return new Rf(E,b,w)}};const jn=Df;class _i{constructor(b){this.options=b||{},this.livesLeft=this.options.lives||1/0}getAssistant(b){return jn.createAssistantToTheTransportManager(this,b,{minPingDelay:this.options.minPingDelay,maxPingDelay:this.options.maxPingDelay})}isAlive(){return this.livesLeft>0}reportDeath(){this.livesLeft-=1}}class vn{constructor(b,w){this.strategies=b,this.loop=!!w.loop,this.failFast=!!w.failFast,this.timeout=w.timeout,this.timeoutLimit=w.timeoutLimit}isSupported(){return ln(this.strategies,ke.method("isSupported"))}connect(b,w){var L=this.strategies,I=0,re=this.timeout,me=null,Le=(We,dt)=>{dt?w(null,dt):(I=I+1,this.loop&&(I=I%L.length),I0&&(re=new ee(L.timeout,function(){me.abort(),I(!0)})),me=b.connect(w,function(Le,We){Le&&re&&re.isRunning()&&!L.failFast||(re&&re.ensureAborted(),I(Le,We))}),{abort:function(){re&&re.ensureAborted(),me.abort()},forceMinPriority:function(Le){me.forceMinPriority(Le)}}}}class kn{constructor(b){this.strategies=b}isSupported(){return ln(this.strategies,ke.method("isSupported"))}connect(b,w){return $c(this.strategies,b,function(L,I){return function(re,me){if(I[L].error=re,re){Of(I)&&w(!0);return}Oe(I,function(Le){Le.forceMinPriority(me.transport.priority)}),w(null,me)}})}}function $c(E,b,w){var L=nt(E,function(I,re,me,Le){return I.connect(b,w(re,Le))});return{abort:function(){Oe(L,Uo)},forceMinPriority:function(I){Oe(L,function(re){re.forceMinPriority(I)})}}}function Of(E){return Br(E,function(b){return!!b.error})}function Uo(E){!E.error&&!E.aborted&&(E.abort(),E.aborted=!0)}class Nc{constructor(b,w,L){this.strategy=b,this.transports=w,this.ttl=L.ttl||18e5,this.usingTLS=L.useTLS,this.timeline=L.timeline}isSupported(){return this.strategy.isSupported()}connect(b,w){var L=this.usingTLS,I=Lf(L),re=I&&I.cacheSkipCount?I.cacheSkipCount:0,me=[this.strategy];if(I&&I.timestamp+this.ttl>=ke.now()){var Le=this.transports[I.transport];Le&&(["ws","wss"].includes(I.transport)||re>3?(this.timeline.info({cached:!0,transport:I.transport,latency:I.latency}),me.push(new vn([Le],{timeout:I.latency*2+1e3,failFast:!0}))):re++)}var We=ke.now(),dt=me.pop().connect(b,function pt(Pt,Or){Pt?(vs(L),me.length>0?(We=ke.now(),dt=me.pop().connect(b,pt)):w(Pt)):(zn(L,Or.transport.name,ke.now()-We,re),w(null,Or))});return{abort:function(){dt.abort()},forceMinPriority:function(pt){b=pt,dt&&dt.forceMinPriority(pt)}}}}function ja(E){return"pusherTransport"+(E?"TLS":"NonTLS")}function Lf(E){var b=ze.getLocalStorage();if(b)try{var w=b[ja(E)];if(w)return JSON.parse(w)}catch{vs(E)}return null}function zn(E,b,w,L){var I=ze.getLocalStorage();if(I)try{I[ja(E)]=yn({timestamp:ke.now(),transport:b,latency:w,cacheSkipCount:L})}catch{}}function vs(E){var b=ze.getLocalStorage();if(b)try{delete b[ja(E)]}catch{}}class Ai{constructor(b,{delay:w}){this.strategy=b,this.options={delay:w}}isSupported(){return this.strategy.isSupported()}connect(b,w){var L=this.strategy,I,re=new ee(this.options.delay,function(){I=L.connect(b,w)});return{abort:function(){re.ensureAborted(),I&&I.abort()},forceMinPriority:function(me){b=me,I&&I.forceMinPriority(me)}}}}class ia{constructor(b,w,L){this.test=b,this.trueBranch=w,this.falseBranch=L}isSupported(){var b=this.test()?this.trueBranch:this.falseBranch;return b.isSupported()}connect(b,w){var L=this.test()?this.trueBranch:this.falseBranch;return L.connect(b,w)}}class za{constructor(b){this.strategy=b}isSupported(){return this.strategy.isSupported()}connect(b,w){var L=this.strategy.connect(b,function(I,re){re&&L.abort(),w(I,re)});return L}}function Ua(E){return function(){return E.isSupported()}}var Ic=function(E,b,w){var L={};function I(el,Xc,Jc,Zf,eh){var Qc=w(E,el,Xc,Jc,Zf,eh);return L[el]=Qc,Qc}var re=Object.assign({},b,{hostNonTLS:E.wsHost+":"+E.wsPort,hostTLS:E.wsHost+":"+E.wssPort,httpPath:E.wsPath}),me=Object.assign({},re,{useTLS:!0}),Le=Object.assign({},b,{hostNonTLS:E.httpHost+":"+E.httpPort,hostTLS:E.httpHost+":"+E.httpsPort,httpPath:E.httpPath}),We={loop:!0,timeout:15e3,timeoutLimit:6e4},dt=new _i({minPingDelay:1e4,maxPingDelay:E.activityTimeout}),pt=new _i({lives:2,minPingDelay:1e4,maxPingDelay:E.activityTimeout}),Pt=I("ws","ws",3,re,dt),Or=I("wss","ws",3,me,dt),Xf=I("sockjs","sockjs",1,Le),Wc=I("xhr_streaming","xhr_streaming",1,Le,pt),Jf=I("xdr_streaming","xdr_streaming",1,Le,pt),Jo=I("xhr_polling","xhr_polling",1,Le),Yc=I("xdr_polling","xdr_polling",1,Le),qa=new vn([Pt],We),Oi=new vn([Or],We),Qf=new vn([Xf],We),Li=new vn([new ia(Ua(Wc),Wc,Jf)],We),Qo=new vn([new ia(Ua(Jo),Jo,Yc)],We),Zo=new vn([new ia(Ua(Li),new kn([Li,new Ai(Qo,{delay:4e3})]),Qo)],We),Mi=new ia(Ua(Zo),Zo,Qf),$i;return b.useTLS?$i=new kn([qa,new Ai(Mi,{delay:2e3})]):$i=new kn([qa,new Ai(Oi,{delay:2e3}),new Ai(Mi,{delay:5e3})]),new Nc(new za(new ia(Ua(Pt),$i,Mi)),L,{ttl:18e5,timeline:b.timeline,useTLS:b.useTLS})};const Pa=Ic;function Mf(){var E=this;E.timeline.info(E.buildTimelineMessage({transport:E.name+(E.options.useTLS?"s":"")})),E.hooks.isInitialized()?E.changeState("initialized"):E.hooks.file?(E.changeState("initializing"),A.load(E.hooks.file,{useTLS:E.options.useTLS},function(b,w){E.hooks.isInitialized()?(E.changeState("initialized"),w(!0)):(b&&E.onError(b),E.onClose(),w(!1))})):E.onClose()}var Hc={getRequest:function(E){var b=new window.XDomainRequest;return b.ontimeout=function(){E.emit("error",new P),E.close()},b.onerror=function(w){E.emit("error",w),E.close()},b.onprogress=function(){b.responseText&&b.responseText.length>0&&E.onChunk(200,b.responseText)},b.onload=function(){b.responseText&&b.responseText.length>0&&E.onChunk(200,b.responseText),E.emit("finished",200),E.close()},b},abortRequest:function(E){E.ontimeout=E.onerror=E.onprogress=E.onload=null,E.abort()}};const jc=Hc,Po=256*1024;class $f extends Ae{constructor(b,w,L){super(),this.hooks=b,this.method=w,this.url=L}start(b){this.position=0,this.xhr=this.hooks.getRequest(this),this.unloader=()=>{this.close()},ze.addUnloadListener(this.unloader),this.xhr.open(this.method,this.url,!0),this.xhr.setRequestHeader&&this.xhr.setRequestHeader("Content-Type","application/json"),this.xhr.send(b)}close(){this.unloader&&(ze.removeUnloadListener(this.unloader),this.unloader=null),this.xhr&&(this.hooks.abortRequest(this.xhr),this.xhr=null)}onChunk(b,w){for(;;){var L=this.advanceBuffer(w);if(L)this.emit("chunk",{status:b,data:L});else break}this.isBufferTooLong(w)&&this.emit("buffer_too_long")}advanceBuffer(b){var w=b.slice(this.position),L=w.indexOf(` -`);return L!==-1?(this.position+=L+1,w.slice(0,L)):null}isBufferTooLong(b){return this.position===b.length&&b.length>Po}}var Bo;(function(E){E[E.CONNECTING=0]="CONNECTING",E[E.OPEN=1]="OPEN",E[E.CLOSED=3]="CLOSED"})(Bo||(Bo={}));const or=Bo;var Rr=1;class qo{constructor(b,w){this.hooks=b,this.session=Ss(1e3)+"/"+Uc(8),this.location=Go(w),this.readyState=or.CONNECTING,this.openStream()}send(b){return this.sendRaw(JSON.stringify([b]))}ping(){this.hooks.sendHeartbeat(this)}close(b,w){this.onClose(b,w,!0)}sendRaw(b){if(this.readyState===or.OPEN)try{return ze.createSocketRequest("POST",sa(Ba(this.location,this.session))).start(b),!0}catch{return!1}else return!1}reconnect(){this.closeStream(),this.openStream()}onClose(b,w,L){this.closeStream(),this.readyState=or.CLOSED,this.onclose&&this.onclose({code:b,reason:w,wasClean:L})}onChunk(b){if(b.status===200){this.readyState===or.OPEN&&this.onActivity();var w,L=b.data.slice(0,1);switch(L){case"o":w=JSON.parse(b.data.slice(1)||"{}"),this.onOpen(w);break;case"a":w=JSON.parse(b.data.slice(1)||"[]");for(var I=0;I{this.onChunk(b)}),this.stream.bind("finished",b=>{this.hooks.onFinished(this,b)}),this.stream.bind("buffer_too_long",()=>{this.reconnect()});try{this.stream.start()}catch(b){ke.defer(()=>{this.onError(b),this.onClose(1006,"Could not start streaming",!1)})}}closeStream(){this.stream&&(this.stream.unbind_all(),this.stream.close(),this.stream=null)}}function Go(E){var b=/([^\?]*)\/*(\??.*)/.exec(E);return{base:b[1],queryString:b[2]}}function Ba(E,b){return E.base+"/"+b+"/xhr_send"}function sa(E){var b=E.indexOf("?")===-1?"?":"&";return E+b+"t="+ +new Date+"&n="+Rr++}function zc(E,b){var w=/(https?:\/\/)([^\/:]+)((\/|:)?.*)/.exec(E);return w[1]+b+w[3]}function Ss(E){return ze.randomInt(E)}function Uc(E){for(var b=[],w=0;w0&&E.onChunk(w.status,w.responseText);break;case 4:w.responseText&&w.responseText.length>0&&E.onChunk(w.status,w.responseText),E.emit("finished",w.status),E.close();break}},w},abortRequest:function(E){E.onreadystatechange=null,E.abort()}};const ws=Fo;var Pc={createStreamingSocket(E){return this.createSocket(Vo,E)},createPollingSocket(E){return this.createSocket(Vr,E)},createSocket(E,b){return new xi(E,b)},createXHR(E,b){return this.createRequest(ws,E,b)},createRequest(E,b,w){return new $f(E,b,w)}};const Ri=Pc;Ri.createXDR=function(E,b){return this.createRequest(jc,E,b)};var Bc={nextAuthCallbackID:1,auth_callbacks:{},ScriptReceivers:m,DependenciesReceivers:_,getDefaultStrategy:Pa,Transports:Rn,transportConnectionInitializer:Mf,HTTPFactory:Ri,TimelineTransport:un,getXHRAPI(){return window.XMLHttpRequest},getWebSocketAPI(){return window.WebSocket||window.MozWebSocket},setup(E){if(typeof window<"u"){window.Pusher=E;var b=()=>{this.onDocumentBody(E.ready)};window.JSON?b():A.load("json2",{},b)}},getDocument(){return document},getProtocol(){return this.getDocument().location.protocol},getAuthorizers(){return{ajax:Ge,jsonp:bn}},onDocumentBody(E){document.body?E():setTimeout(()=>{this.onDocumentBody(E)},0)},createJSONPRequest(E,b){return new sr(E,b)},createScriptRequest(E){return new ra(E)},getLocalStorage(){try{return window.localStorage}catch{return}},createXHR(){return this.getXHRAPI()?this.createXMLHttpRequest():this.createMicrosoftXHR()},createXMLHttpRequest(){var E=this.getXHRAPI();return new E},createMicrosoftXHR(){return new ActiveXObject("Microsoft.XMLHTTP")},getNetwork(){return jo},createWebSocket(E){var b=this.getWebSocketAPI();return new b(E)},createSocketRequest(E,b){if(this.isXHRSupported())return this.HTTPFactory.createXHR(E,b);if(this.isXDRSupported(b.indexOf("https:")===0))return this.HTTPFactory.createXDR(E,b);throw"Cross-origin HTTP requests are not supported"},isXHRSupported(){var E=this.getXHRAPI();return!!E&&new E().withCredentials!==void 0},isXDRSupported(E){var b=E?"https:":"http:",w=this.getProtocol();return!!window.XDomainRequest&&w===b},addUnloadListener(E){window.addEventListener!==void 0?window.addEventListener("pagehide",E,!1):window.attachEvent!==void 0&&window.attachEvent("onunload",E)},removeUnloadListener(E){window.addEventListener!==void 0?window.removeEventListener("pagehide",E,!1):window.detachEvent!==void 0&&window.detachEvent("onunload",E)},randomInt(E){const b=window.crypto||window.msCrypto,w=Math.floor(Math.pow(2,32)/E)*E;let L;do L=b.getRandomValues(new Uint32Array(1))[0];while(L>=w);return L%E}};const ze=Bc;var Dr;(function(E){E[E.ERROR=3]="ERROR",E[E.INFO=6]="INFO",E[E.DEBUG=7]="DEBUG"})(Dr||(Dr={}));const oa=Dr;class ki{constructor(b,w,L){this.key=b,this.session=w,this.events=[],this.options=L||{},this.sent=0,this.uniqueID=0}log(b,w){b<=this.options.level&&(this.events.push(Ie({},w,{timestamp:ke.now()})),this.options.limit&&this.events.length>this.options.limit&&this.events.shift())}error(b){this.log(oa.ERROR,b)}info(b){this.log(oa.INFO,b)}debug(b){this.log(oa.DEBUG,b)}isEmpty(){return this.events.length===0}send(b,w){var L=Ie({session:this.session,bundle:this.sent+1,key:this.key,lib:"js",version:this.options.version,cluster:this.options.cluster,features:this.options.features,timeline:this.events},this.options.params);return this.events=[],b(L,(I,re)=>{I||this.sent++,w&&w(I,re)}),!0}generateUniqueID(){return this.uniqueID++,this.uniqueID}}class Nf{constructor(b,w,L,I){this.name=b,this.priority=w,this.transport=L,this.options=I||{}}isSupported(){return this.transport.isSupported({useTLS:this.options.useTLS})}connect(b,w){if(this.isSupported()){if(this.priority{L||(pt(),re?re.close():I.close())},forceMinPriority:Pt=>{L||this.priority{var w="socket_id="+encodeURIComponent(E.socketId);for(var L in b.params)w+="&"+encodeURIComponent(L)+"="+encodeURIComponent(b.params[L]);if(b.paramsProvider!=null){let I=b.paramsProvider();for(var L in I)w+="&"+encodeURIComponent(L)+"="+encodeURIComponent(I[L])}return w},Hf=E=>{if(typeof ze.getAuthorizers()[E.transport]>"u")throw`'${E.transport}' is not a recognized auth transport`;return(b,w)=>{const L=If(b,E);ze.getAuthorizers()[E.transport](ze,L,E,T.UserAuthentication,w)}},Yo=(E,b)=>{var w="socket_id="+encodeURIComponent(E.socketId);w+="&channel_name="+encodeURIComponent(E.channelName);for(var L in b.params)w+="&"+encodeURIComponent(L)+"="+encodeURIComponent(b.params[L]);if(b.paramsProvider!=null){let I=b.paramsProvider();for(var L in I)w+="&"+encodeURIComponent(L)+"="+encodeURIComponent(I[L])}return w},jf=E=>{if(typeof ze.getAuthorizers()[E.transport]>"u")throw`'${E.transport}' is not a recognized auth transport`;return(b,w)=>{const L=Yo(b,E);ze.getAuthorizers()[E.transport](ze,L,E,T.ChannelAuthorization,w)}},zf=(E,b,w)=>{const L={authTransport:b.transport,authEndpoint:b.endpoint,auth:{params:b.params,headers:b.headers}};return(I,re)=>{const me=E.channel(I.channelName);w(me,L).authorize(I.socketId,re)}};function Gc(E,b){let w={activityTimeout:E.activityTimeout||S.activityTimeout,cluster:E.cluster,httpPath:E.httpPath||S.httpPath,httpPort:E.httpPort||S.httpPort,httpsPort:E.httpsPort||S.httpsPort,pongTimeout:E.pongTimeout||S.pongTimeout,statsHost:E.statsHost||S.stats_host,unavailableTimeout:E.unavailableTimeout||S.unavailableTimeout,wsPath:E.wsPath||S.wsPath,wsPort:E.wsPort||S.wsPort,wssPort:E.wssPort||S.wssPort,enableStats:qf(E),httpHost:Uf(E),useTLS:Bf(E),wsHost:Vc(E),userAuthenticator:Xo(E),channelAuthorizer:Vf(E,b)};return"disabledTransports"in E&&(w.disabledTransports=E.disabledTransports),"enabledTransports"in E&&(w.enabledTransports=E.enabledTransports),"ignoreNullOrigin"in E&&(w.ignoreNullOrigin=E.ignoreNullOrigin),"timelineParams"in E&&(w.timelineParams=E.timelineParams),"nacl"in E&&(w.nacl=E.nacl),w}function Uf(E){return E.httpHost?E.httpHost:E.cluster?`sockjs-${E.cluster}.pusher.com`:S.httpHost}function Vc(E){return E.wsHost?E.wsHost:Pf(E.cluster)}function Pf(E){return`ws-${E}.pusher.com`}function Bf(E){return ze.getProtocol()==="https:"?!0:E.forceTLS!==!1}function qf(E){return"enableStats"in E?E.enableStats:"disableStats"in E?!E.disableStats:!1}const Fc=E=>"customHandler"in E&&E.customHandler!=null;function Xo(E){const b=Object.assign(Object.assign({},S.userAuthentication),E.userAuthentication);return Fc(b)?b.customHandler:Hf(b)}function Gf(E,b){let w;return"channelAuthorization"in E?w=Object.assign(Object.assign({},S.channelAuthorization),E.channelAuthorization):(w={transport:E.authTransport||S.authTransport,endpoint:E.authEndpoint||S.authEndpoint},"auth"in E&&("params"in E.auth&&(w.params=E.auth.params),"headers"in E.auth&&(w.headers=E.auth.headers)),"authorizer"in E&&(w.customHandler=zf(b,w,E.authorizer))),w}function Vf(E,b){const w=Gf(E,b);return Fc(w)?w.customHandler:jf(w)}class Ff extends Ae{constructor(b){super(function(w,L){rt.debug(`No callbacks on watchlist events for ${w}`)}),this.pusher=b,this.bindWatchlistInternalEvent()}handleEvent(b){b.data.events.forEach(w=>{this.emit(w.name,w)})}bindWatchlistInternalEvent(){this.pusher.connection.bind("message",b=>{var w=b.event;w==="pusher_internal:watchlist_events"&&this.handleEvent(b)})}}function Kc(){let E,b;return{promise:new Promise((L,I)=>{E=L,b=I}),resolve:E,reject:b}}const Kf=Kc;class Wf extends Ae{constructor(b){super(function(w,L){rt.debug("No callbacks on user for "+w)}),this.signin_requested=!1,this.user_data=null,this.serverToUserChannel=null,this.signinDonePromise=null,this._signinDoneResolve=null,this._onAuthorize=(w,L)=>{if(w){rt.warn(`Error during signin: ${w}`),this.emit("pusher:signin_error",Object.assign({},{type:"AuthError",error:w.message},w instanceof Ue?{status:w.status}:{})),this._cleanup();return}this.pusher.send_event("pusher:signin",{auth:L.auth,user_data:L.user_data})},this.pusher=b,this.pusher.connection.bind("state_change",({previous:w,current:L})=>{w!=="connected"&&L==="connected"&&this._signin(),w==="connected"&&L!=="connected"&&(this._cleanup(),this._newSigninPromiseIfNeeded())}),this.watchlist=new Ff(b),this.pusher.connection.bind("message",w=>{var L=w.event;L==="pusher:signin_success"&&this._onSigninSuccess(w.data),this.serverToUserChannel&&this.serverToUserChannel.name===w.channel&&this.serverToUserChannel.handleEvent(w)})}signin(){this.signin_requested||(this.signin_requested=!0,this._signin())}_signin(){this.signin_requested&&(this._newSigninPromiseIfNeeded(),this.pusher.connection.state==="connected"&&this.pusher.config.userAuthenticator({socketId:this.pusher.connection.socket_id},this._onAuthorize))}_onSigninSuccess(b){try{this.user_data=JSON.parse(b.user_data)}catch{rt.error(`Failed parsing user data after signin: ${b.user_data}`),this._cleanup();return}if(typeof this.user_data.id!="string"||this.user_data.id===""){rt.error(`user_data doesn't contain an id. user_data: ${this.user_data}`),this._cleanup();return}this._signinDoneResolve(),this._subscribeChannels()}_subscribeChannels(){const b=w=>{w.subscriptionPending&&w.subscriptionCancelled?w.reinstateSubscription():!w.subscriptionPending&&this.pusher.connection.state==="connected"&&w.subscribe()};this.serverToUserChannel=new Gr(`#server-to-user-${this.user_data.id}`,this.pusher),this.serverToUserChannel.bind_global((w,L)=>{w.indexOf("pusher_internal:")===0||w.indexOf("pusher:")===0||this.emit(w,L)}),b(this.serverToUserChannel)}_cleanup(){this.user_data=null,this.serverToUserChannel&&(this.serverToUserChannel.unbind_all(),this.serverToUserChannel.disconnect(),this.serverToUserChannel=null),this.signin_requested&&this._signinDoneResolve()}_newSigninPromiseIfNeeded(){if(!this.signin_requested||this.signinDonePromise&&!this.signinDonePromise.done)return;const{promise:b,resolve:w}=Kf();b.done=!1;const L=()=>{b.done=!0};b.then(L).catch(L),this.signinDonePromise=b,this._signinDoneResolve=w}}class dn{static ready(){dn.isReady=!0;for(var b=0,w=dn.instances.length;bze.getDefaultStrategy(this.config,I,Di);this.connection=jn.createConnectionManager(this.key,{getStrategy:L,timeline:this.timeline,activityTimeout:this.config.activityTimeout,pongTimeout:this.config.pongTimeout,unavailableTimeout:this.config.unavailableTimeout,useTLS:!!this.config.useTLS}),this.connection.bind("connected",()=>{this.subscribeAll(),this.timelineSender&&this.timelineSender.send(this.connection.isUsingTLS())}),this.connection.bind("message",I=>{var re=I.event,me=re.indexOf("pusher_internal:")===0;if(I.channel){var Le=this.channel(I.channel);Le&&Le.handleEvent(I)}me||this.global_emitter.emit(I.event,I.data)}),this.connection.bind("connecting",()=>{this.channels.disconnect()}),this.connection.bind("disconnected",()=>{this.channels.disconnect()}),this.connection.bind("error",I=>{rt.warn(I)}),dn.instances.push(this),this.timeline.info({instances:dn.instances.length}),this.user=new Wf(this),dn.isReady&&this.connect()}switchCluster(b){const{appKey:w,cluster:L}=b;this.key=w,this.options=Object.assign(Object.assign({},this.options),{cluster:L}),this.config=Gc(this.options,this),this.connection.switchCluster(this.key)}channel(b){return this.channels.find(b)}allChannels(){return this.channels.all()}connect(){if(this.connection.connect(),this.timelineSender&&!this.timelineSenderTimer){var b=this.connection.isUsingTLS(),w=this.timelineSender;this.timelineSenderTimer=new _e(6e4,function(){w.send(b)})}}disconnect(){this.connection.disconnect(),this.timelineSenderTimer&&(this.timelineSenderTimer.ensureAborted(),this.timelineSenderTimer=null)}bind(b,w,L){return this.global_emitter.bind(b,w,L),this}unbind(b,w,L){return this.global_emitter.unbind(b,w,L),this}bind_global(b){return this.global_emitter.bind_global(b),this}unbind_global(b){return this.global_emitter.unbind_global(b),this}unbind_all(b){return this.global_emitter.unbind_all(),this}subscribeAll(){var b;for(b in this.channels.channels)this.channels.channels.hasOwnProperty(b)&&this.subscribe(b)}subscribe(b){var w=this.channels.add(b,this);return w.subscriptionPending&&w.subscriptionCancelled?w.reinstateSubscription():!w.subscriptionPending&&this.connection.state==="connected"&&w.subscribe(),w}unsubscribe(b){var w=this.channels.find(b);w&&w.subscriptionPending?w.cancelSubscription():(w=this.channels.remove(b),w&&w.subscribed&&w.unsubscribe())}send_event(b,w,L){return this.connection.send_event(b,w,L)}shouldUseTLS(){return this.config.useTLS}signin(){this.user.signin()}}dn.instances=[],dn.isReady=!1,dn.logToConsole=!1,dn.Runtime=ze,dn.ScriptReceivers=ze.ScriptReceivers,dn.DependenciesReceivers=ze.DependenciesReceivers,dn.auth_callbacks=ze.auth_callbacks;const _s=dn;function Yf(E){if(E==null)throw"You must pass your app key when you instantiate Pusher."}ze.setup(dn)}},a={};function i(c){var d=a[c];if(d!==void 0)return d.exports;var f=a[c]={exports:{}};return n[c].call(f.exports,f,f.exports,i),f.exports}i.d=(c,d)=>{for(var f in d)i.o(d,f)&&!i.o(c,f)&&Object.defineProperty(c,f,{enumerable:!0,get:d[f]})},i.o=(c,d)=>Object.prototype.hasOwnProperty.call(c,d);var l=i(721);return l})())})(wf)),wf.exports}var aA=rA();const by=Jr(aA),qt=ht("WebSocketManager");class iA{constructor(){$(this,"echo",null);$(this,"subscriptions",new Map);$(this,"initialized",!1);$(this,"config",null)}configure(e){if(this.initialized){qt.warn("[WebSocketManager] 이미 초기화되어 설정을 변경할 수 없습니다.");return}this.config=e,qt.log("[WebSocketManager] 설정 완료:",{appKey:e.appKey?"***":"(없음)",host:e.host,port:e.port,scheme:e.scheme})}initialize(){if(this.initialized)return;if(!this.config||!this.config.appKey){qt.warn("[WebSocketManager] WebSocket 설정이 없습니다. initTemplateApp에서 websocket 옵션을 전달해주세요.");return}const{appKey:e,host:n="localhost",port:a=80,scheme:i="https",authEndpoint:l="/api/broadcasting/auth"}=this.config,c=Number(a)||80,d=i==="https";qt.log("[WebSocketManager] 연결 설정:",{host:n,port:c,scheme:i,useTLS:d,authEndpoint:l}),window.Pusher=by;const f=localStorage.getItem("auth_token"),g={wsHost:n,wsPort:c,wssPort:c,forceTLS:d,disableStats:!0,enabledTransports:["ws","wss"],cluster:"mt1",authEndpoint:l,auth:{headers:{Authorization:f?`Bearer ${f}`:"",Accept:"application/json"}}};qt.log("[WebSocketManager] Pusher 옵션:",g);const m=new by(e,g);m.connection.bind("connecting",()=>{qt.log("[WebSocketManager] 연결 시도 중...")}),m.connection.bind("connected",()=>{qt.log("[WebSocketManager] 연결 성공! Socket ID:",m.connection.socket_id)}),m.connection.bind("failed",()=>{qt.error("[WebSocketManager] 연결 실패 - WebSocket을 사용할 수 없습니다.")}),m.connection.bind("error",y=>{qt.error("[WebSocketManager] 연결 오류:",y)}),m.connection.bind("state_change",y=>{qt.log(`[WebSocketManager] 상태 변경: ${y.previous} → ${y.current}`)}),m.connection.bind("unavailable",()=>{qt.error("[WebSocketManager] WebSocket 사용 불가 - 연결할 수 없습니다.")}),m.connection.bind("disconnected",()=>{qt.warn("[WebSocketManager] 연결이 끊어졌습니다.")}),qt.log("[WebSocketManager] 초기 연결 상태:",m.connection.state),this.echo=new nA({broadcaster:"reverb",client:m}),qt.log("[WebSocketManager] 연결 시작 시도..."),m.connect(),window.Echo=this.echo,this.initialized=!0,qt.log("[WebSocketManager] Echo 초기화 완료")}subscribe(e,n,a,i={}){if(this.initialize(),!this.echo)return qt.warn("[WebSocketManager] Echo가 초기화되지 않았습니다."),"";const{channelType:l="private"}=i,c=`${e}:${n}`;if(this.subscriptions.has(c))return qt.log(`[WebSocketManager] 이미 구독 중: ${c}`),c;let d;switch(l){case"public":d=this.echo.channel(e);break;case"presence":d=this.echo.join(e);break;default:d=this.echo.private(e)}return d.listen(`.${n}`,a),this.subscriptions.set(c,d),qt.log(`[WebSocketManager] 구독 완료: ${c} (${l})`),c}unsubscribe(e){const n=this.subscriptions.get(e);if(!n)return;const a=e.lastIndexOf(":"),i=a>=0?e.substring(a+1):"";if(i)try{n.stopListening(`.${i}`)}catch(l){qt.warn(`[WebSocketManager] stopListening 실패: ${e}`,l)}this.subscriptions.delete(e),qt.log(`[WebSocketManager] 구독 해제: ${e}`)}leaveChannel(e){if(!this.echo)return;this.echo.leave(e);const n=[];this.subscriptions.forEach((a,i)=>{i.startsWith(`${e}:`)&&n.push(i)}),n.forEach(a=>this.subscriptions.delete(a)),qt.log(`[WebSocketManager] 채널 구독 해제: ${e}`)}disconnect(){this.echo&&(this.echo.disconnect(),this.subscriptions.clear(),this.initialized=!1,qt.log("[WebSocketManager] 연결 종료"))}getEcho(){return this.echo}isInitialized(){return this.initialized}isConfigured(){return this.config!==null&&!!this.config.appKey}getSubscriptionCount(){return this.subscriptions.size}}const Cc=new iA,bt=ht("DataSourceManager"),vy=new Dn;function Ec(o,e){const n=Aa(o);if(n!==null)try{return Vn(n)?vy.evaluatePipeExpression(n,e,{skipCache:!0}):vy.evaluateExpression(n,e)}catch(a){return bt.error("Failed to evaluate param expression:",n,a),o}return rs(o,e,{skipCache:!0})}function Sy(o){const e={};for(const n of o.keys()){if(n in e)continue;const a=o.getAll(n);a.length>1?e[n]=a:a.length===1&&(n.endsWith("[]")?e[n]=a:e[n]=a[0])}return e}function sA(){try{return window.G7Core?.devTools}catch{return}}const Rc=class Rc{constructor(e={}){$(this,"options");$(this,"dataCache",new Map);$(this,"bindingEngine");$(this,"globalHeaders",[]);this.options=e,this.bindingEngine=new Dn}setGlobalHeaders(e){this.globalHeaders=e||[]}matchesPattern(e,n){if(n==="*")return!0;const a=n.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*");return new RegExp(`^${a}$`).test(e)}toFormData(e){const n=new FormData;for(const[a,i]of Object.entries(e))i instanceof File||i instanceof Blob?n.append(a,i):i!=null&&n.append(a,typeof i=="object"?JSON.stringify(i):String(i));return n}getMatchingGlobalHeaders(e,n){const a={};for(const i of this.globalHeaders)this.matchesPattern(e,i.pattern)&&Object.entries(i.headers).forEach(([l,c])=>{const d=Ec(c,n);d!=null&&d!==""&&(a[l]=String(d))});return a}filterByCondition(e,n={}){const a=new Set,i=[];for(const l of e){if(a.has(l.id))continue;if(!l.if&&!l.conditions){a.add(l.id),i.push(l);continue}ns({if:l.if,conditions:l.conditions},n,this.bindingEngine,`data_source:${l.id}`)&&(a.add(l.id),i.push(l))}return i}checkErrorCondition(e,n){if(!e.errorCondition?.if||e.errorCondition?.errorCode===void 0)return null;try{const a={response:n};if(ns({if:e.errorCondition.if},a,this.bindingEngine,`errorCondition:${e.id}`))return bt.log(`errorCondition matched for ${e.id}, triggering error ${e.errorCondition.errorCode}`),e.errorCondition.errorCode}catch(a){bt.error(`Failed to evaluate errorCondition for ${e.id}:`,a)}return null}async fetchDataSources(e,n={},a=new URLSearchParams,i,l){const c={},d=!!this.options.sampleProvider,f=e.filter(g=>(d||g.auto_fetch!==!1)&&g.type!=="websocket");return await Promise.all(f.map(async g=>{try{const m=await this.fetchDataSource(g,n,a,i,l);c[g.id]=m,this.dataCache.set(g.id,{data:m,timestamp:Date.now()});const y=this.checkErrorCondition(g,m);if(y!==null){const S={status:y,message:`Error condition matched for ${g.id}`,data:m},v=Sr(),_=v.resolve(y,{errorHandling:g.errorHandling,onError:g.onError});if(_.handler)try{await v.execute(_.handler,S)}catch(A){bt.error(`Error executing errorCondition handler for ${g.id}:`,A)}g.fallback!==void 0&&(c[g.id]=g.fallback,this.dataCache.set(g.id,{data:g.fallback,timestamp:Date.now()}),bt.log(`Using fallback data for ${g.id} (errorCondition)`));return}if(g.onSuccess)try{const S={data:m,sourceId:g.id};await this.executeOnSuccessHandler(g.onSuccess,S)}catch(S){bt.error(`Error executing onSuccess handler for ${g.id}:`,S)}}catch(m){bt.error(`Failed to fetch data source: ${g.id}`,m);const y=m?.response?.status||m?.status||500,S=m?.response?.data,v=S?.message||m.message||"Unknown error",_={status:y,message:v,errors:S?.errors,data:S,statusText:m?.response?.statusText},A=Sr(),x=A.resolve(y,{errorHandling:g.errorHandling,onError:g.onError});if(x.handler)try{await A.execute(x.handler,_)}catch(O){bt.error(`Error executing error handler for ${g.id}:`,O)}g.fallback!==void 0&&(c[g.id]=g.fallback,this.dataCache.set(g.id,{data:g.fallback,timestamp:Date.now()}),bt.log(`Using fallback data for ${g.id}`)),this.options.onError&&this.options.onError(m,g)}})),c}async fetchDataSourcesWithResults(e,n={},a=new URLSearchParams,i,l,c){if(e.some(m=>m.type==="websocket")){const m=e.filter(y=>y.type==="websocket").map(y=>y.id).join(", ");bt.warn(`fetchDataSourcesWithResults received WebSocket sources (caller should filter them out before calling): ${m}. WebSocket sources are event listeners, not data providers — they should not be passed to fetch.`)}const f=c?.ignoreAutoFetch?e.filter(m=>m.type!=="websocket"):e.filter(m=>m.auto_fetch!==!1&&m.type!=="websocket");return await Promise.all(f.map(async m=>{try{const y=await this.fetchDataSource(m,n,a,i,l);this.dataCache.set(m.id,{data:y,timestamp:Date.now()});const S=this.checkErrorCondition(m,y);if(S!==null){const v={status:S,message:`Error condition matched for ${m.id}`,data:y},_=Sr(),A=_.resolve(S,{errorHandling:m.errorHandling,onError:m.onError});let x=!1;if(A.handler)if(m.loading_strategy==="blocking")_.execute(A.handler,v).catch(O=>{bt.error(`Error executing errorCondition handler for ${m.id}:`,O)}),x=!0;else try{await _.execute(A.handler,v),x=!0}catch(O){bt.error(`Error executing errorCondition handler for ${m.id}:`,O)}return m.fallback!==void 0?(this.dataCache.set(m.id,{data:m.fallback,timestamp:Date.now()}),bt.log(`Using fallback data for ${m.id} (errorCondition)`),{id:m.id,state:"success",data:m.fallback}):{id:m.id,state:"error",error:new Error(`Error condition matched: ${S}`),errorCode:S,errorHandled:x}}if(m.onSuccess)try{const v={data:y,sourceId:m.id};await this.executeOnSuccessHandler(m.onSuccess,v)}catch(v){bt.error(`Error executing onSuccess handler for ${m.id}:`,v)}return{id:m.id,state:"success",data:y}}catch(y){bt.error(`Failed to fetch data source: ${m.id}`,y);const S=y?.response?.status||y?.status||500,v=y?.response?.data,_=v?.message||y.message||"Unknown error",A={status:S,message:_,errors:v?.errors,data:v,statusText:y?.response?.statusText},x=Sr(),O=x.resolve(S,{errorHandling:m.errorHandling,onError:m.onError});let M=!1;if(O.handler)if(m.loading_strategy==="blocking")x.execute(O.handler,A).catch(T=>{bt.error(`Error executing error handler for ${m.id}:`,T)}),M=!0;else try{await x.execute(O.handler,A),M=!0}catch(T){bt.error(`Error executing error handler for ${m.id}:`,T)}return m.fallback!==void 0?(this.dataCache.set(m.id,{data:m.fallback,timestamp:Date.now()}),bt.log(`Using fallback data for ${m.id}`),{id:m.id,state:"success",data:m.fallback}):(this.options.onError&&this.options.onError(y,m),{id:m.id,state:"error",error:y,errorCode:S,errorHandled:M})}}))}fetchDataSourcesInBackground(e,n={},a=new URLSearchParams,i){e.filter(c=>c.auto_fetch!==!1&&c.loading_strategy==="background"&&c.type!=="websocket").forEach(c=>{this.fetchDataSource(c,n,a).then(async d=>{this.dataCache.set(c.id,{data:d,timestamp:Date.now()});const f=this.checkErrorCondition(c,d);if(f!==null){const g={status:f,message:`Error condition matched for ${c.id}`,data:d},m=Sr(),y=m.resolve(f,{errorHandling:c.errorHandling,onError:c.onError});if(y.handler)try{await m.execute(y.handler,g)}catch(S){bt.error(`Error executing errorCondition handler for ${c.id}:`,S)}c.fallback!==void 0&&(this.dataCache.set(c.id,{data:c.fallback,timestamp:Date.now()}),bt.log(`Using fallback data for ${c.id} (errorCondition)`),i&&i(c.id,c.fallback));return}if(c.onSuccess)try{const g={data:d,sourceId:c.id};await this.executeOnSuccessHandler(c.onSuccess,g)}catch(g){bt.error(`Error executing onSuccess handler for ${c.id}:`,g)}i&&i(c.id,d)}).catch(async d=>{bt.error(`Background fetch failed: ${c.id}`,d);const f=d?.response?.status||d?.status||500,g=d?.response?.data,m=g?.message||d.message||"Unknown error",y={status:f,message:m,errors:g?.errors,data:g,statusText:d?.response?.statusText},S=Sr(),v=S.resolve(f,{errorHandling:c.errorHandling,onError:c.onError});if(v.handler)try{await S.execute(v.handler,y)}catch(_){bt.error(`Error executing error handler for ${c.id}:`,_)}if(c.fallback!==void 0){this.dataCache.set(c.id,{data:c.fallback,timestamp:Date.now()}),bt.log(`Using fallback data for ${c.id}`),i&&i(c.id,c.fallback);return}this.options.onError&&this.options.onError(d,c)})})}async fetchDataSource(e,n,a,i,l){switch(e.type){case"api":return this.fetchApiDataSource(e,n,a,i,l);case"static":return e.data;case"route_params":return n;case"query_params":return Sy(a);default:throw new Error(`Unknown data source type: ${e.type}`)}}async fetchApiDataSource(e,n,a,i,l){if(this.options.sampleProvider?.has(e.id))return await Promise.resolve(this.options.sampleProvider.resolve(e));if(!e.endpoint)throw new Error(`API data source ${e.id} has no endpoint`);const c={route:n,query:Sy(a)};i&&(c._global=i),l&&(c._local=l);let d=rs(e.endpoint,c,{skipCache:!0});const f=e.method||"GET",m=(e.contentType?typeof e.contentType=="string"&&e.contentType.includes("{{")?rs(e.contentType,c,{skipCache:!0}):e.contentType:"application/json")==="multipart/form-data";let y=m?{...e.params||{}}:JSON.parse(JSON.stringify(e.params||{}));Object.entries(y).forEach(([_,A])=>{typeof A=="string"&&(y[_]=Ec(A,c))}),Object.keys(y).forEach(_=>{y[_]===""&&delete y[_]});const S=sA();let v=null;if(S?.isEnabled()){S.trackDataSourceDefinition({id:e.id,type:e.type,endpoint:d,method:f,autoFetch:e.auto_fetch,initLocal:e.initLocal,initGlobal:e.initGlobal});let _=d;if(f==="GET"&&Object.keys(y).length>0){const A=new URLSearchParams;Object.entries(y).forEach(([O,M])=>{Array.isArray(M)?M.forEach(T=>A.append(O,String(T))):M!=null&&A.append(O,String(M))});const x=A.toString();_=x?`${d}?${x}`:d}v=S.trackRequest(_,f,{requestBody:f!=="GET"?y:void 0,dataSourceId:e.id}),S.trackDataSourceLoading(e.id)}try{let _;const A=e.auth_mode??(e.auth_required===!0?"required":"none"),x=br.getInstance().isAuthenticated();if(A==="required"&&!x){bt.log(`[DataSourceManager] Skipping ${e.id}: auth_mode is 'required' but no token available`),S?.isEnabled()&&(S.trackDataSourceError(e.id,"Skipped: auth_mode required but not authenticated"),v&&S.failRequest(v,"Skipped: not authenticated"));const M=new Error("Auth required but not authenticated");throw M.response={status:401,statusText:"Unauthorized",data:null},M.status=401,M._authSkipped=!0,M}const O=A==="required"||A==="optional"&&x;if(O){const M=Hr(),T=d.startsWith("/api/")?d.substring(4):d.startsWith("/api")?d.substring(4)||"/":d;this.options.onError&&bt.log(`[DataSourceManager] Normalized endpoint: ${d} -> ${T}`);const D=this.getMatchingGlobalHeaders(d,c),z={};e.headers&&Object.entries(e.headers).forEach(([he,Ee])=>{const be=Ec(Ee,c);be!=null&&be!==""&&(z[he]=String(be))});const P={...D,...z},q=Object.keys(P).length>0,W=m?this.toFormData(y):y;switch(f){case"GET":_=await M.get(T,{params:y,...q&&{headers:P}});break;case"POST":q?_=await M.post(T,W,{headers:P}):_=await M.post(T,W);break;case"PUT":q?_=await M.put(T,W,{headers:P}):_=await M.put(T,W);break;case"PATCH":q?_=await M.patch(T,W,{headers:P}):_=await M.patch(T,W);break;case"DELETE":_=await M.delete(T,{params:y,...q&&{headers:P}});break;default:throw new Error(`Unsupported HTTP method: ${f}`)}}else{let M=d;if(f==="GET"){const W=new URLSearchParams(y).toString();M=W?`${d}?${W}`:d}const T={...m?{}:{"Content-Type":"application/json"},Accept:"application/json"};if(typeof window<"u"){const W=localStorage.getItem("g7_locale");W&&(T["Accept-Language"]=W)}const D=this.getMatchingGlobalHeaders(d,c);Object.assign(T,D),e.headers&&Object.entries(e.headers).forEach(([W,he])=>{const Ee=Ec(he,c);Ee!=null&&Ee!==""&&(T[W]=String(Ee))});let z;f!=="GET"&&(m?(z=this.toFormData(y),delete T["Content-Type"]):z=JSON.stringify(y));const P=await fetch(M,{method:f,headers:T,body:z});let q;try{q=await P.json()}catch{q=null}if(v&&S?.isEnabled()&&(S.completeRequest(v,P.status,q),v=null),!P.ok){S?.isEnabled()&&S.trackDataSourceError(e.id,`HTTP ${P.status}: ${P.statusText}`);const W=new Error(`HTTP error! status: ${P.status}`);throw W.response={status:P.status,statusText:P.statusText,data:q},W.status=P.status,W}_=q,S?.isEnabled()&&S.trackDataSourceLoaded(e.id,_)}return O&&v&&S?.isEnabled()&&(S.completeRequest(v,200,_),S.trackDataSourceLoaded(e.id,_)),_}catch(_){if(v&&S?.isEnabled()){const A=_;A?.response?S.completeRequest(v,A.response.status,A.response.data):S.failRequest(v,_ instanceof Error?_.message:String(_)),S.trackDataSourceError(e.id,_ instanceof Error?_.message:String(_))}throw _}}getCachedData(e){const n=this.dataCache.get(e);if(!n)return;if(Date.now()-n.timestamp>Rc.DATA_CACHE_TTL){this.dataCache.delete(e);return}return n.data}clearCache(){this.dataCache.clear()}subscribeWebSockets(e,n,a={}){const i=[],l=e.filter(c=>c.type==="websocket");return l.length===0||l.forEach(c=>{if(!c.channel||!c.event){bt.warn(`WebSocket source ${c.id} missing channel or event`);return}let d,f;try{d=rs(c.channel,a,{skipCache:!0}),f=rs(c.event,a,{skipCache:!0})}catch(y){bt.error(`Failed to resolve WebSocket channel/event for source ${c.id}:`,y);return}const g=y=>!!(!y||y.trim()===""||y.includes("{{")||y.endsWith(".")||y.endsWith(":")||y.startsWith(".")||y.startsWith(":")||y.includes("..")||y.includes("::"));if(g(d)||g(f)){bt.warn(`WebSocket source ${c.id} has invalid channel/event after expression resolution, skipping. Original channel="${c.channel}", resolved="${d}". Original event="${c.event}", resolved="${f}". Available context keys: [${Object.keys(a).join(", ")}]`);return}const m=Cc.subscribe(d,f,y=>{const S=c.target_source||c.id;this.dataCache.set(S,{data:y,timestamp:Date.now()}),n(S,y)},{channelType:c.channel_type||"private"});m&&i.push(m)}),i}unsubscribeWebSockets(e){e.length!==0&&e.forEach(n=>{Cc.unsubscribe(n)})}async executeOnSuccessHandler(e,n){const a=this.options.actionDispatcher??bm();if(!a){bt.warn("ActionDispatcher not available, skipping onSuccess handler");return}const i=Array.isArray(e)?e:[e];for(const l of i)try{await a.dispatchAction({type:"click",...l},{data:{response:n}})}catch(c){throw bt.error("Failed to execute onSuccess handler:",c),c}}};$(Rc,"DATA_CACHE_TTL",3e5);let _r=Rc;const oA=new _r,bi=ht("ModalDataSourceWrapper");function lA(o,e){if(!o||!e)return o;const a=e.replace(/\?\./g,".").split(/\.(?![^\[]*\])/).flatMap(l=>{const c=l.match(/^([^\[]*)((?:\[\d+\])*)$/);if(c){const[,d,f]=c,g=[];d&&g.push(d);const m=f.match(/\[\d+\]/g);return m&&g.push(...m),g}return[l]}).filter(l=>l!=="");let i=o;for(const l of a){if(i==null)return;if(l.startsWith("[")&&l.endsWith("]")){const c=parseInt(l.slice(1,-1),10);i=i[c]}else i=i[l]}return i}const wy=({isOpen:o,modalId:e,dataSources:n,dataContext:a,globalStateUpdater:i,bindingEngine:l,debug:c=!1,children:d})=>{const[f,g]=H.useState(!1),[m,y]=H.useState(!1),S=H.useRef(!1),v=H.useRef(null);v.current||(v.current=new _r);const _=H.useCallback(async()=>{if(!(!n||n.length===0)&&v.current){g(!0);try{const A=a.route||{},x=new URLSearchParams(window.location.search),O=n.map(T=>{let D={...T};if(T.endpoint&&l){const z=l.resolveBindings(T.endpoint,a,{skipCache:!0});D.endpoint=z}if(T.params&&l){const z={};Object.entries(T.params).forEach(([P,q])=>{typeof q=="string"?z[P]=l.resolveBindings(q,a,{skipCache:!0}):z[P]=q}),D.params=z}return D});c&&bi.log(`[ModalDataSourceWrapper] Modal "${e}" fetching data sources:`,O.map(T=>({id:T.id,endpoint:T.endpoint})));const M=await v.current.fetchDataSourcesWithResults(O,A,x);i&&M.forEach(T=>{if(T.state==="success"&&T.data!==void 0){const D=O.find(z=>z.id===T.id);if(D?.initGlobal){const z=T.data?.data??T.data,P=Array.isArray(D.initGlobal)?D.initGlobal:[D.initGlobal];for(const q of P)if(typeof q=="string")i({[q]:z}),c&&bi.log(`[ModalDataSourceWrapper] Modal "${e}" initGlobal: ${T.id}.data -> _global.${q}`);else if(typeof q=="object"&&q.key){const{key:W,path:he}=q,Ee=he?lA(z,he):z;i({[W]:Ee}),c&&bi.log(`Modal "${e}" initGlobal: ${T.id}.data${he?"."+he:""} -> _global.${W}`)}}}else T.state==="error"&&bi.error(`Modal "${e}" data source error:`,T.id,T.error)}),c&&bi.log(`Modal "${e}" data sources fetched successfully`)}catch(A){bi.error(`Modal "${e}" data source fetch error:`,A)}finally{g(!1),y(!0)}}},[n,a,i,l,e,c]);return H.useEffect(()=>{if(!n||n.length===0)return;const A=window.__templateApp;return A?.registerModalDataSources&&A.registerModalDataSources(e,n),()=>{A?.unregisterModalDataSources&&A.unregisterModalDataSources(e)}},[e,n]),H.useEffect(()=>{!o&&S.current&&(y(!1),c&&bi.log(`[ModalDataSourceWrapper] Modal "${e}" closed, reset fetch state`)),o&&!S.current&&n&&n.length>0&&!m&&_(),S.current=o},[o,n,m,_,e,c]),gt.jsx(gt.Fragment,{children:d})};class _c extends Error{constructor(n,a,i={}){super(a);$(this,"code");$(this,"userMessageKey");$(this,"showStack");$(this,"icon");$(this,"recoverable");this.name="TemplateEngineError",this.code=n,this.userMessageKey=a,this.showStack=i.showStack??!0,this.icon=i.icon??"fa-circle-exclamation",this.recoverable=i.recoverable??!0,Error.captureStackTrace&&Error.captureStackTrace(this,this.constructor)}getUserMessage(n,a){if(!a||!n)return this.userMessageKey;try{return a.translate(this.userMessageKey,n)}catch{return this.userMessageKey}}}class cA extends _c{constructor(){super("TEMPLATE_NOT_FOUND","$t:core.errors.template_not_found",{showStack:!1,icon:"fa-paint-brush",recoverable:!1}),this.name="TemplateNotFoundError"}}function Cy(o){if(o instanceof _c)return o;if(o instanceof Error){const e=new _c("TEMPLATE_INIT_ERROR",o.message||"$t:core.errors.template_init_error",{showStack:!0,icon:"fa-circle-exclamation",recoverable:!0});return e.stack=o.stack,e}return new _c("TEMPLATE_INIT_ERROR","$t:core.errors.template_init_error",{showStack:!1,icon:"fa-circle-exclamation",recoverable:!0})}const uA=ht("ErrorDisplay");class Cf{static render(e,n){const a=document.getElementById(e);if(!a){uA.error(`Container #${e} not found`);return}const i=n.debug&&n.showStack&&n.stackTrace,l=window.matchMedia("(prefers-color-scheme: dark)").matches,c={container:` + `,Gn.error("Error state rendered:",i,a)}getCurrentLayout(){return this.currentLayout}clear(){this.currentLayout=null,this.layoutCache.clear(),Gn.log("Layout data and cache cleared")}}const D_=ut("TranslationContext"),hf=N.createContext(null),mc=({children:o,translationEngine:e,translationContext:n})=>{const a=N.useMemo(()=>(l,c)=>{if(c){const d="|"+Object.entries(c).map(([f,g])=>`${f}=${g}`).join("|");return e.translate(l,n,d)}return e.translate(l,n)},[e,n]),i=N.useMemo(()=>({translationEngine:e,translationContext:n,t:a}),[e,n,a]);return dt.jsx(hf.Provider,{value:i,children:o})},ty=()=>{const o=N.useContext(hf);return o||(D_.warn("TranslationProvider 외부에서 호출되었습니다. 키를 그대로 반환합니다."),{t:e=>e,translationEngine:null,translationContext:null})},La=ut("SlotContext"),O_={registerToSlot:()=>{La.warn("SlotContext not available: registerToSlot called outside SlotProvider")},unregisterFromSlot:()=>{La.warn("SlotContext not available: unregisterFromSlot called outside SlotProvider")},getSlotComponents:()=>[],subscribeToSlot:()=>()=>{},clearAllSlots:()=>{},isEnabled:!1},L_=N.createContext(O_),gf=({children:o})=>{const e=N.useRef(new Map),n=N.useRef(new Map),a=N.useCallback(m=>{const b=n.current.get(m);b&&b.forEach(S=>{try{S()}catch(v){La.error(`Slot subscriber error (slotId: ${m}):`,v)}})},[]),i=N.useCallback((m,b,S)=>{if(!m||!b){La.warn("registerToSlot: slotId and componentId are required");return}e.current.has(m)||e.current.set(m,new Map);const v=e.current.get(m),_=v.get(b);_&&_.registrationKey===S.registrationKey||(v.set(b,S),La.log(`Component registered to slot: ${b} -> ${m} (order: ${S.order})`),a(m))},[a]),l=N.useCallback((m,b)=>{const S=e.current.get(m);S&&S.has(b)&&(S.delete(b),La.log(`Component unregistered from slot: ${b} <- ${m}`),S.size===0&&e.current.delete(m),a(m))},[a]),c=N.useCallback(m=>{const b=e.current.get(m);return b?Array.from(b.values()).sort((S,v)=>S.order-v.order):[]},[]),d=N.useCallback((m,b)=>{n.current.has(m)||n.current.set(m,new Set);const S=n.current.get(m);return S.add(b),()=>{S.delete(b),S.size===0&&n.current.delete(m)}},[]),f=N.useCallback(()=>{e.current.forEach((m,b)=>{a(b)}),e.current.clear(),n.current.clear(),La.log("All slots cleared")},[a]),g=N.useMemo(()=>({registerToSlot:i,unregisterFromSlot:l,getSlotComponents:c,subscribeToSlot:d,clearAllSlots:f,isEnabled:!0}),[i,l,c,d,f]);return N.useLayoutEffect(()=>{typeof window<"u"&&(window.__slotContextValue=g,La.log("SlotContext exposed to window.__slotContextValue"))},[g]),dt.jsx(L_.Provider,{value:g,children:o})};class pf{constructor(){this.notificationCreatedEvent=".Illuminate\\Notifications\\Events\\BroadcastNotificationCreated"}listenForWhisper(e,n){return this.listen(".client-"+e,n)}notification(e){return this.listen(this.notificationCreatedEvent,e)}stopListeningForNotification(e){return this.stopListening(this.notificationCreatedEvent,e)}stopListeningForWhisper(e,n){return this.stopListening(".client-"+e,n)}}class ny{constructor(e){this.namespace=e}format(e){return[".","\\"].includes(e.charAt(0))?e.substring(1):(this.namespace&&(e=this.namespace+"."+e),e.replace(/\./g,"\\"))}setNamespace(e){this.namespace=e}}function M_(o){try{return Reflect.construct(String,[],o),!0}catch{return!1}}class mf extends pf{constructor(e,n,a){super(),this.name=n,this.pusher=e,this.options=a,this.eventFormatter=new ny(this.options.namespace),this.subscribe()}subscribe(){this.subscription=this.pusher.subscribe(this.name)}unsubscribe(){this.pusher.unsubscribe(this.name)}listen(e,n){return this.on(this.eventFormatter.format(e),n),this}listenToAll(e){return this.subscription.bind_global((n,a)=>{if(n.startsWith("pusher:"))return;let i=String(this.options.namespace??"").replace(/\./g,"\\"),l=n.startsWith(i)?n.substring(i.length+1):"."+n;e(l,a)}),this}stopListening(e,n){return n?this.subscription.unbind(this.eventFormatter.format(e),n):this.subscription.unbind(this.eventFormatter.format(e)),this}stopListeningToAll(e){return e?this.subscription.unbind_global(e):this.subscription.unbind_global(),this}subscribed(e){return this.on("pusher:subscription_succeeded",()=>{e()}),this}error(e){return this.on("pusher:subscription_error",n=>{e(n)}),this}on(e,n){return this.subscription.bind(e,n),this}}class ry extends mf{whisper(e,n){return this.pusher.channels.channels[this.name].trigger(`client-${e}`,n),this}}class $_ extends mf{whisper(e,n){return this.pusher.channels.channels[this.name].trigger(`client-${e}`,n),this}}class N_ extends ry{here(e){return this.on("pusher:subscription_succeeded",n=>{e(Object.keys(n.members).map(a=>n.members[a]))}),this}joining(e){return this.on("pusher:member_added",n=>{e(n.info)}),this}whisper(e,n){return this.pusher.channels.channels[this.name].trigger(`client-${e}`,n),this}leaving(e){return this.on("pusher:member_removed",n=>{e(n.info)}),this}}class ay extends pf{constructor(e,n,a){super(),this.events={},this.listeners={},this.name=n,this.socket=e,this.options=a,this.eventFormatter=new ny(this.options.namespace),this.subscribe()}subscribe(){this.socket.emit("subscribe",{channel:this.name,auth:this.options.auth||{}})}unsubscribe(){this.unbind(),this.socket.emit("unsubscribe",{channel:this.name,auth:this.options.auth||{}})}listen(e,n){return this.on(this.eventFormatter.format(e),n),this}stopListening(e,n){return this.unbindEvent(this.eventFormatter.format(e),n),this}subscribed(e){return this.on("connect",n=>{e(n)}),this}error(e){return this}on(e,n){return this.listeners[e]=this.listeners[e]||[],this.events[e]||(this.events[e]=(a,i)=>{this.name===a&&this.listeners[e]&&this.listeners[e].forEach(l=>l(i))},this.socket.on(e,this.events[e])),this.listeners[e].push(n),this}unbind(){Object.keys(this.events).forEach(e=>{this.unbindEvent(e)})}unbindEvent(e,n){this.listeners[e]=this.listeners[e]||[],n&&(this.listeners[e]=this.listeners[e].filter(a=>a!==n)),(!n||this.listeners[e].length===0)&&(this.events[e]&&(this.socket.removeListener(e,this.events[e]),delete this.events[e]),delete this.listeners[e])}}class iy extends ay{whisper(e,n){return this.socket.emit("client event",{channel:this.name,event:`client-${e}`,data:n}),this}}class I_ extends iy{here(e){return this.on("presence:subscribed",n=>{e(n.map(a=>a.user_info))}),this}joining(e){return this.on("presence:joining",n=>e(n.user_info)),this}whisper(e,n){return this.socket.emit("client event",{channel:this.name,event:`client-${e}`,data:n}),this}leaving(e){return this.on("presence:leaving",n=>e(n.user_info)),this}}class yc extends pf{subscribe(){}unsubscribe(){}listen(e,n){return this}listenToAll(e){return this}stopListening(e,n){return this}subscribed(e){return this}error(e){return this}on(e,n){return this}}class sy extends yc{whisper(e,n){return this}}class H_ extends yc{whisper(e,n){return this}}class z_ extends sy{here(e){return this}joining(e){return this}whisper(e,n){return this}leaving(e){return this}}const oy=class US{constructor(e){this.setOptions(e),this.connect()}setOptions(e){this.options={...US._defaultOptions,...e,broadcaster:e.broadcaster};let n=this.csrfToken();n&&(this.options.auth.headers["X-CSRF-TOKEN"]=n,this.options.userAuthentication.headers["X-CSRF-TOKEN"]=n),n=this.options.bearerToken,n&&(this.options.auth.headers.Authorization="Bearer "+n,this.options.userAuthentication.headers.Authorization="Bearer "+n)}csrfToken(){var e,n;return typeof window<"u"&&(e=window.Laravel)!=null&&e.csrfToken?window.Laravel.csrfToken:this.options.csrfToken?this.options.csrfToken:typeof document<"u"&&typeof document.querySelector=="function"?((n=document.querySelector('meta[name="csrf-token"]'))==null?void 0:n.getAttribute("content"))??null:null}};oy._defaultOptions={auth:{headers:{}},authEndpoint:"/broadcasting/auth",userAuthentication:{endpoint:"/broadcasting/user-auth",headers:{}},csrfToken:null,bearerToken:null,host:null,key:null,namespace:"App.Events"};let yf=oy;class bc extends yf{constructor(){super(...arguments),this.channels={}}connect(){if(typeof this.options.client<"u")this.pusher=this.options.client;else if(this.options.Pusher)this.pusher=new this.options.Pusher(this.options.key,this.options);else if(typeof window<"u"&&typeof window.Pusher<"u")this.pusher=new window.Pusher(this.options.key,this.options);else throw new Error("Pusher client not found. Should be globally available or passed via options.client")}signin(){this.pusher.signin()}listen(e,n,a){return this.channel(e).listen(n,a)}channel(e){return this.channels[e]||(this.channels[e]=new mf(this.pusher,e,this.options)),this.channels[e]}privateChannel(e){return this.channels["private-"+e]||(this.channels["private-"+e]=new ry(this.pusher,"private-"+e,this.options)),this.channels["private-"+e]}encryptedPrivateChannel(e){return this.channels["private-encrypted-"+e]||(this.channels["private-encrypted-"+e]=new $_(this.pusher,"private-encrypted-"+e,this.options)),this.channels["private-encrypted-"+e]}presenceChannel(e){return this.channels["presence-"+e]||(this.channels["presence-"+e]=new N_(this.pusher,"presence-"+e,this.options)),this.channels["presence-"+e]}leave(e){[e,"private-"+e,"private-encrypted-"+e,"presence-"+e].forEach(n=>{this.leaveChannel(n)})}leaveChannel(e){this.channels[e]&&(this.channels[e].unsubscribe(),delete this.channels[e])}socketId(){return this.pusher.connection.socket_id}connectionStatus(){const e=this.pusher.connection.state;switch(e){case"connected":case"connecting":return e;case"failed":case"unavailable":return"failed";default:return"disconnected"}}onConnectionChange(e){const n=()=>{e(this.connectionStatus())},a=["state_change","connected","disconnected"];return a.forEach(i=>{this.pusher.connection.bind(i,n)}),()=>{a.forEach(i=>{this.pusher.connection.unbind(i,n)})}}disconnect(){this.pusher.disconnect()}}class j_ extends yf{constructor(){super(...arguments),this.channels={}}connect(){let e=this.getSocketIO();this.socket=e(this.options.host??void 0,this.options),this.socket.io.on("reconnect",()=>{Object.values(this.channels).forEach(n=>{n.subscribe()})})}getSocketIO(){if(typeof this.options.client<"u")return this.options.client;if(typeof window<"u"&&typeof window.io<"u")return window.io;throw new Error("Socket.io client not found. Should be globally available or passed via options.client")}listen(e,n,a){return this.channel(e).listen(n,a)}channel(e){return this.channels[e]||(this.channels[e]=new ay(this.socket,e,this.options)),this.channels[e]}privateChannel(e){return this.channels["private-"+e]||(this.channels["private-"+e]=new iy(this.socket,"private-"+e,this.options)),this.channels["private-"+e]}presenceChannel(e){return this.channels["presence-"+e]||(this.channels["presence-"+e]=new I_(this.socket,"presence-"+e,this.options)),this.channels["presence-"+e]}leave(e){[e,"private-"+e,"presence-"+e].forEach(n=>{this.leaveChannel(n)})}leaveChannel(e){this.channels[e]&&(this.channels[e].unsubscribe(),delete this.channels[e])}socketId(){return this.socket.id}connectionStatus(){return this.socket.connected?"connected":this.socket.io._reconnecting?"reconnecting":this.socket.id!==void 0?"disconnected":"connecting"}onConnectionChange(e){const n=()=>{e(this.connectionStatus())},a=["connect","disconnect","connect_error","reconnect_attempt","reconnect","reconnect_error","reconnect_failed"];return a.forEach(i=>{this.socket.on(i,n)}),()=>{a.forEach(i=>{this.socket.off(i,n)})}}disconnect(){this.socket.disconnect()}}class ly extends yf{constructor(){super(...arguments),this.channels={}}connect(){}listen(e,n,a){return new yc}channel(e){return new yc}privateChannel(e){return new sy}encryptedPrivateChannel(e){return new H_}presenceChannel(e){return new z_}leave(e){}leaveChannel(e){}socketId(){return"fake-socket-id"}connectionStatus(){return"connected"}onConnectionChange(e){return()=>{}}disconnect(){}}class U_{constructor(e){this.options=e,this.connect(),this.options.withoutInterceptors||this.registerInterceptors()}channel(e){return this.connector.channel(e)}connect(){if(this.options.broadcaster==="reverb")this.connector=new bc({...this.options,cluster:""});else if(this.options.broadcaster==="pusher")this.connector=new bc(this.options);else if(this.options.broadcaster==="ably")this.connector=new bc({...this.options,cluster:"",broadcaster:"pusher"});else if(this.options.broadcaster==="socket.io")this.connector=new j_(this.options);else if(this.options.broadcaster==="null")this.connector=new ly(this.options);else if(typeof this.options.broadcaster=="function"&&M_(this.options.broadcaster))this.connector=new this.options.broadcaster(this.options);else throw new Error(`Broadcaster ${typeof this.options.broadcaster} ${String(this.options.broadcaster)} is not supported.`)}disconnect(){this.connector.disconnect()}join(e){return this.connector.presenceChannel(e)}leave(e){this.connector.leave(e)}leaveChannel(e){this.connector.leaveChannel(e)}leaveAllChannels(){for(const e in this.connector.channels)this.leaveChannel(e)}listen(e,n,a){return this.connector.listen(e,n,a)}private(e){return this.connector.privateChannel(e)}encryptedPrivate(e){if(this.connectorSupportsEncryptedPrivateChannels(this.connector))return this.connector.encryptedPrivateChannel(e);throw new Error(`Broadcaster ${typeof this.options.broadcaster} ${String(this.options.broadcaster)} does not support encrypted private channels.`)}connectorSupportsEncryptedPrivateChannels(e){return e instanceof bc||e instanceof ly}socketId(){return this.connector.socketId()}connectionStatus(){return this.connector.connectionStatus()}registerInterceptors(){typeof Vue<"u"&&Vue!=null&&Vue.http&&this.registerVueRequestInterceptor(),typeof axios=="function"&&this.registerAxiosRequestInterceptor(),typeof jQuery=="function"&&this.registerjQueryAjaxSetup(),typeof Turbo=="object"&&this.registerTurboRequestInterceptor()}registerVueRequestInterceptor(){Vue.http.interceptors.push((e,n)=>{this.socketId()&&e.headers.set("X-Socket-ID",this.socketId()),n()})}registerAxiosRequestInterceptor(){axios.interceptors.request.use(e=>(this.socketId()&&(e.headers["X-Socket-Id"]=this.socketId()),e))}registerjQueryAjaxSetup(){typeof jQuery.ajax<"u"&&jQuery.ajaxPrefilter((e,n,a)=>{this.socketId()&&a.setRequestHeader("X-Socket-Id",this.socketId())})}registerTurboRequestInterceptor(){document.addEventListener("turbo:before-fetch-request",e=>{e.detail.fetchOptions.headers["X-Socket-Id"]=this.socketId()})}}var bf={exports:{}};var cy;function B_(){return cy||(cy=1,(function(o,e){(function(a,i){o.exports=i()})(self,()=>(()=>{var n={594(c,d){var f=this&&this.__extends||(function(){var M=function(R,D){return M=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(B,q){B.__proto__=q}||function(B,q){for(var P in q)q.hasOwnProperty(P)&&(B[P]=q[P])},M(R,D)};return function(R,D){M(R,D);function B(){this.constructor=R}R.prototype=D===null?Object.create(D):(B.prototype=D.prototype,new B)}})();Object.defineProperty(d,"__esModule",{value:!0});var g=256,m=(function(){function M(R){R===void 0&&(R="="),this._paddingCharacter=R}return M.prototype.encodedLength=function(R){return this._paddingCharacter?(R+2)/3*4|0:(R*8+5)/6|0},M.prototype.encode=function(R){for(var D="",B=0;B>>18&63),D+=this._encodeByte(q>>>12&63),D+=this._encodeByte(q>>>6&63),D+=this._encodeByte(q>>>0&63)}var P=R.length-B;if(P>0){var q=R[B]<<16|(P===2?R[B+1]<<8:0);D+=this._encodeByte(q>>>18&63),D+=this._encodeByte(q>>>12&63),P===2?D+=this._encodeByte(q>>>6&63):D+=this._paddingCharacter||"",D+=this._paddingCharacter||""}return D},M.prototype.maxDecodedLength=function(R){return this._paddingCharacter?R/4*3|0:(R*6+7)/8|0},M.prototype.decodedLength=function(R){return this.maxDecodedLength(R.length-this._getPaddingLength(R))},M.prototype.decode=function(R){if(R.length===0)return new Uint8Array(0);for(var D=this._getPaddingLength(R),B=R.length-D,q=new Uint8Array(this.maxDecodedLength(B)),P=0,K=0,ge=0,Ee=0,Se=0,Ue=0,Ve=0;K>>4,q[P++]=Se<<4|Ue>>>2,q[P++]=Ue<<6|Ve,ge|=Ee&g,ge|=Se&g,ge|=Ue&g,ge|=Ve&g;if(K>>4,ge|=Ee&g,ge|=Se&g),K>>2,ge|=Ue&g),K>>8&6,D+=51-R>>>8&-75,D+=61-R>>>8&-15,D+=62-R>>>8&3,String.fromCharCode(D)},M.prototype._decodeChar=function(R){var D=g;return D+=(42-R&R-44)>>>8&-g+R-43+62,D+=(46-R&R-48)>>>8&-g+R-47+63,D+=(47-R&R-58)>>>8&-g+R-48+52,D+=(64-R&R-91)>>>8&-g+R-65+0,D+=(96-R&R-123)>>>8&-g+R-97+26,D},M.prototype._getPaddingLength=function(R){var D=0;if(this._paddingCharacter){for(var B=R.length-1;B>=0&&R[B]===this._paddingCharacter;B--)D++;if(R.length<4||D>2)throw new Error("Base64Coder: incorrect padding")}return D},M})();d.Coder=m;var b=new m;function S(M){return b.encode(M)}d.encode=S;function v(M){return b.decode(M)}d.decode=v;var _=(function(M){f(R,M);function R(){return M!==null&&M.apply(this,arguments)||this}return R.prototype._encodeByte=function(D){var B=D;return B+=65,B+=25-D>>>8&6,B+=51-D>>>8&-75,B+=61-D>>>8&-13,B+=62-D>>>8&49,String.fromCharCode(B)},R.prototype._decodeChar=function(D){var B=g;return B+=(44-D&D-46)>>>8&-g+D-45+62,B+=(94-D&D-96)>>>8&-g+D-95+63,B+=(47-D&D-58)>>>8&-g+D-48+52,B+=(64-D&D-91)>>>8&-g+D-65+0,B+=(96-D&D-123)>>>8&-g+D-97+26,B},R})(m);d.URLSafeCoder=_;var A=new _;function x(M){return A.encode(M)}d.encodeURLSafe=x;function L(M){return A.decode(M)}d.decodeURLSafe=L,d.encodedLength=function(M){return b.encodedLength(M)},d.maxDecodedLength=function(M){return b.maxDecodedLength(M)},d.decodedLength=function(M){return b.decodedLength(M)}},978(c,d){var f="utf8: invalid source encoding";function g(m){for(var b=[],S=0;S=m.length)throw new Error(f);var A=m[++S];if((A&192)!==128)throw new Error(f);v=(v&31)<<6|A&63,_=128}else if(v<240){if(S>=m.length-1)throw new Error(f);var A=m[++S],x=m[++S];if((A&192)!==128||(x&192)!==128)throw new Error(f);v=(v&15)<<12|(A&63)<<6|x&63,_=2048}else if(v<248){if(S>=m.length-2)throw new Error(f);var A=m[++S],x=m[++S],L=m[++S];if((A&192)!==128||(x&192)!==128||(L&192)!==128)throw new Error(f);v=(v&15)<<18|(A&63)<<12|(x&63)<<6|L&63,_=65536}else throw new Error(f);if(v<_||v>=55296&&v<=57343)throw new Error(f);if(v>=65536){if(v>1114111)throw new Error(f);v-=65536,b.push(String.fromCharCode(55296|v>>10)),v=56320|v&1023}}b.push(String.fromCharCode(v))}return b.join("")}d.D4=g},721(c,d,f){c.exports=f(207).default},207(c,d,f){f.d(d,{default:()=>ws});class g{constructor(y,C){this.lastId=0,this.prefix=y,this.name=C}create(y){this.lastId++;var C=this.lastId,O=this.prefix+C,I=this.name+"["+C+"]",ne=!1,me=function(){ne||(y.apply(null,arguments),ne=!0)};return this[C]=me,{number:C,id:O,name:I,callback:me}}remove(y){delete this[y.number]}}var m=new g("_pusher_script_","Pusher.ScriptReceivers"),b={VERSION:"8.5.0",PROTOCOL:7,wsPort:80,wssPort:443,wsPath:"",httpHost:"sockjs.pusher.com",httpPort:80,httpsPort:443,httpPath:"/pusher",stats_host:"stats.pusher.com",authEndpoint:"/pusher/auth",authTransport:"ajax",activityTimeout:12e4,pongTimeout:3e4,unavailableTimeout:1e4,userAuthentication:{endpoint:"/pusher/user-auth",transport:"ajax"},channelAuthorization:{endpoint:"/pusher/auth",transport:"ajax"},cdn_http:"http://js.pusher.com",cdn_https:"https://js.pusher.com",dependency_suffix:""};const S=b;class v{constructor(y){this.options=y,this.receivers=y.receivers||m,this.loading={}}load(y,C,O){var I=this;if(I.loading[y]&&I.loading[y].length>0)I.loading[y].push(O);else{I.loading[y]=[O];var ne=je.createScriptRequest(I.getPath(y,C)),me=I.receivers.create(function(Me){if(I.receivers.remove(me),I.loading[y]){var Ke=I.loading[y];delete I.loading[y];for(var lt=function(zt){zt||ne.cleanup()},gt=0;gt>>6)+J(128|y&63):J(224|y>>>12&15)+J(128|y>>>6&63)+J(128|y&63)},Z=function(E){return E.replace(/[^\x00-\x7F]/g,Le)},pe=function(E){var y=[0,2,1][E.length%3],C=E.charCodeAt(0)<<16|(E.length>1?E.charCodeAt(1):0)<<8|(E.length>2?E.charCodeAt(2):0),O=[fe.charAt(C>>>18),fe.charAt(C>>>12&63),y>=2?"=":fe.charAt(C>>>6&63),y>=1?"=":fe.charAt(C&63)];return O.join("")},H=window.btoa||function(E){return E.replace(/[\s\S]{1,3}/g,pe)};class T{constructor(y,C,O,I){this.clear=C,this.timer=y(()=>{this.timer&&(this.timer=I(this.timer))},O)}isRunning(){return this.timer!==null}ensureAborted(){this.timer&&(this.clear(this.timer),this.timer=null)}}const ce=T;function de(E){window.clearTimeout(E)}function ye(E){window.clearInterval(E)}class ie extends ce{constructor(y,C){super(setTimeout,de,y,function(O){return C(),null})}}class xe extends ce{constructor(y,C){super(setInterval,ye,y,function(O){return C(),O})}}var _e={now(){return Date.now?Date.now():new Date().valueOf()},defer(E){return new ie(0,E)},method(E,...y){var C=Array.prototype.slice.call(arguments,1);return function(O){return O[E].apply(O,C.concat(arguments))}}};const Te=_e;function $e(E,...y){for(var C=0;C{window.console&&window.console.log&&window.console.log(y)}}debug(...y){this.log(this.globalLog,y)}warn(...y){this.log(this.globalLogWarn,y)}error(...y){this.log(this.globalLogError,y)}globalLogWarn(y){window.console&&window.console.warn?window.console.warn(y):this.globalLog(y)}globalLogError(y){window.console&&window.console.error?window.console.error(y):this.globalLogWarn(y)}log(y,...C){var O=ht.apply(this,arguments);ws.log?ws.log(O):ws.logToConsole&&y.bind(this)(O)}}const tt=new Rn;var $n=function(E,y,C,O,I){(C.headers!==void 0||C.headersProvider!=null)&&tt.warn(`To send headers with the ${O.toString()} request, you must use AJAX, rather than JSONP.`);var ne=E.nextAuthCallbackID.toString();E.nextAuthCallbackID++;var me=E.getDocument(),Me=me.createElement("script");E.auth_callbacks[ne]=function(gt){I(null,gt)};var Ke="Pusher.auth_callbacks['"+ne+"']";Me.src=C.endpoint+"?callback="+encodeURIComponent(Ke)+"&"+y;var lt=me.getElementsByTagName("head")[0]||me.documentElement;lt.insertBefore(Me,lt.firstChild)};const pn=$n;class ra{constructor(y){this.src=y}send(y){var C=this,O="Error loading "+C.src;C.script=document.createElement("script"),C.script.id=y.id,C.script.src=C.src,C.script.type="text/javascript",C.script.charset="UTF-8",C.script.addEventListener?(C.script.onerror=function(){y.callback(O)},C.script.onload=function(){y.callback(null)}):C.script.onreadystatechange=function(){(C.script.readyState==="loaded"||C.script.readyState==="complete")&&y.callback(null)},C.script.async===void 0&&document.attachEvent&&/opera/i.test(navigator.userAgent)?(C.errorScript=document.createElement("script"),C.errorScript.id=y.id+"_error",C.errorScript.text=y.name+"('"+O+"');",C.script.async=C.errorScript.async=!1):C.script.async=!0;var I=document.getElementsByTagName("head")[0];I.insertBefore(C.script,I.firstChild),C.errorScript&&I.insertBefore(C.errorScript,C.script.nextSibling)}cleanup(){this.script&&(this.script.onload=this.script.onerror=null,this.script.onreadystatechange=null),this.script&&this.script.parentNode&&this.script.parentNode.removeChild(this.script),this.errorScript&&this.errorScript.parentNode&&this.errorScript.parentNode.removeChild(this.errorScript),this.script=null,this.errorScript=null}}class ir{constructor(y,C){this.url=y,this.data=C}send(y){if(!this.request){var C=wi(this.data),O=this.url+"/"+y.number+"?"+C;this.request=je.createScriptRequest(O),this.request.send(y)}}cleanup(){this.request&&this.request.cleanup()}}var an=function(E,y){return function(C,O){var I="http"+(y?"s":"")+"://",ne=I+(E.host||E.options.host)+E.options.path,me=je.createJSONPRequest(ne,C),Me=je.ScriptReceivers.create(function(Ke,lt){m.remove(Me),me.cleanup(),lt&<.host&&(E.host=lt.host),O&&O(Ke,lt)});me.send(Me)}},En={name:"jsonp",getAgent:an};const sn=En;function V(E,y,C){var O=E+(y.useTLS?"s":""),I=y.useTLS?y.hostTLS:y.hostNonTLS;return O+"://"+I+C}function se(E,y){var C="/app/"+E,O="?protocol="+S.PROTOCOL+"&client=js&version="+S.VERSION+(y?"&"+y:"");return C+O}var ue={getInitial:function(E,y){var C=(y.httpPath||"")+se(E,"flash=false");return V("ws",y,C)}},ee={getInitial:function(E,y){var C=(y.httpPath||"/pusher")+se(E);return V("http",y,C)}},ve={getInitial:function(E,y){return V("http",y,y.httpPath||"/pusher")},getPath:function(E,y){return se(E)}};class he{constructor(){this._callbacks={}}get(y){return this._callbacks[Ce(y)]}add(y,C,O){var I=Ce(y);this._callbacks[I]=this._callbacks[I]||[],this._callbacks[I].push({fn:C,context:O})}remove(y,C,O){if(!y&&!C&&!O){this._callbacks={};return}var I=y?[Ce(y)]:ft(this._callbacks);C||O?this.removeCallback(I,C,O):this.removeAllCallbacks(I)}removeCallback(y,C,O){Ft(y,function(I){this._callbacks[I]=fn(this._callbacks[I]||[],function(ne){return C&&C!==ne.fn||O&&O!==ne.context}),this._callbacks[I].length===0&&delete this._callbacks[I]},this)}removeAllCallbacks(y){Ft(y,function(C){delete this._callbacks[C]},this)}}function Ce(E){return"_"+E}class Ae{constructor(y){this.callbacks=new he,this.global_callbacks=[],this.failThrough=y}bind(y,C,O){return this.callbacks.add(y,C,O),this}bind_global(y){return this.global_callbacks.push(y),this}unbind(y,C,O){return this.callbacks.remove(y,C,O),this}unbind_global(y){return y?(this.global_callbacks=fn(this.global_callbacks||[],C=>C!==y),this):(this.global_callbacks=[],this)}unbind_all(){return this.unbind(),this.unbind_global(),this}emit(y,C,O){for(var I=0;I0)for(var I=0;I{this.onError(C),this.changeState("closed")}),!1}return this.bindListeners(),tt.debug("Connecting",{transport:this.name,url:y}),this.changeState("connecting"),!0}close(){return this.socket?(this.socket.close(),!0):!1}send(y){return this.state==="open"?(Te.defer(()=>{this.socket&&this.socket.send(y)}),!0):!1}ping(){this.state==="open"&&this.supportsPing()&&this.socket.ping()}onOpen(){this.hooks.beforeOpen&&this.hooks.beforeOpen(this.socket,this.hooks.urls.getPath(this.key,this.options)),this.changeState("open"),this.socket.onopen=void 0}onError(y){this.emit("error",{type:"WebSocketError",error:y}),this.timeline.error(this.buildTimelineMessage({error:y.toString()}))}onClose(y){y?this.changeState("closed",{code:y.code,reason:y.reason,wasClean:y.wasClean}):this.changeState("closed"),this.unbindListeners(),this.socket=void 0}onMessage(y){this.emit("message",y)}onActivity(){this.emit("activity")}bindListeners(){this.socket.onopen=()=>{this.onOpen()},this.socket.onerror=y=>{this.onError(y)},this.socket.onclose=y=>{this.onClose(y)},this.socket.onmessage=y=>{this.onMessage(y)},this.supportsPing()&&(this.socket.onactivity=()=>{this.onActivity()})}unbindListeners(){this.socket&&(this.socket.onopen=void 0,this.socket.onerror=void 0,this.socket.onclose=void 0,this.socket.onmessage=void 0,this.supportsPing()&&(this.socket.onactivity=void 0))}changeState(y,C){this.state=y,this.timeline.info(this.buildTimelineMessage({state:y,params:C})),this.emit(y,C)}buildTimelineMessage(y){return $e({cid:this.id},y)}}class Re{constructor(y){this.hooks=y}isSupported(y){return this.hooks.isSupported(y)}createConnection(y,C,O,I){return new Fe(this.hooks,y,C,O,I)}}var Ne=new Re({urls:ue,handlesActivityChecks:!1,supportsPing:!1,isInitialized:function(){return!!je.getWebSocketAPI()},isSupported:function(){return!!je.getWebSocketAPI()},getSocket:function(E){return je.createWebSocket(E)}}),ke={urls:ee,handlesActivityChecks:!1,supportsPing:!0,isInitialized:function(){return!0}},Ze=$e({getSocket:function(E){return je.HTTPFactory.createStreamingSocket(E)}},ke),ze=$e({getSocket:function(E){return je.HTTPFactory.createPollingSocket(E)}},ke),Ge={isSupported:function(){return je.isXHRSupported()}},we=new Re($e({},Ze,Ge)),He=new Re($e({},ze,Ge)),et={ws:Ne,xhr_streaming:we,xhr_polling:He};const _t=et;var Lt=new Re({file:"sockjs",urls:ve,handlesActivityChecks:!0,supportsPing:!1,isSupported:function(){return!0},isInitialized:function(){return window.SockJS!==void 0},getSocket:function(E,y){return new window.SockJS(E,null,{js_path:A.getPath("sockjs",{useTLS:y.useTLS}),ignore_null_origin:y.ignoreNullOrigin})},beforeOpen:function(E,y){E.send(JSON.stringify({path:y}))}}),kn={isSupported:function(E){var y=je.isXDRSupported(E.useTLS);return y}},It=new Re($e({},Ze,kn)),Ht=new Re($e({},ze,kn));_t.xdr_streaming=It,_t.xdr_polling=Ht,_t.sockjs=Lt;const _n=_t;class $a extends Ae{constructor(){super();var y=this;window.addEventListener!==void 0&&(window.addEventListener("online",function(){y.emit("online")},!1),window.addEventListener("offline",function(){y.emit("offline")},!1))}isOnline(){return window.navigator.onLine===void 0?!0:window.navigator.onLine}}var $o=new $a;class Af{constructor(y,C,O){this.manager=y,this.transport=C,this.minPingDelay=O.minPingDelay,this.maxPingDelay=O.maxPingDelay,this.pingDelay=void 0}createConnection(y,C,O,I){I=$e({},I,{activityTimeout:this.pingDelay});var ne=this.transport.createConnection(y,C,O,I),me=null,Me=function(){ne.unbind("open",Me),ne.bind("closed",Ke),me=Te.now()},Ke=lt=>{if(ne.unbind("closed",Ke),lt.code===1002||lt.code===1003)this.manager.reportDeath();else if(!lt.wasClean&&me){var gt=Te.now()-me;gt<2*this.maxPingDelay&&(this.manager.reportDeath(),this.pingDelay=Math.max(gt/2,this.minPingDelay))}};return ne.bind("open",Me),ne}isSupported(y){return this.manager.isAlive()&&this.transport.isSupported(y)}}const Ac={decodeMessage:function(E){try{var y=JSON.parse(E.data),C=y.data;if(typeof C=="string")try{C=JSON.parse(y.data)}catch{}var O={event:y.event,channel:y.channel,data:C};return y.user_id&&(O.user_id=y.user_id),O}catch(I){throw{type:"MessageParseError",error:I,data:E.data}}},encodeMessage:function(E){return JSON.stringify(E)},processHandshake:function(E){var y=Ac.decodeMessage(E);if(y.event==="pusher:connection_established"){if(!y.data.activity_timeout)throw"No activity timeout specified in handshake";return{action:"connected",id:y.data.socket_id,activityTimeout:y.data.activity_timeout*1e3}}else{if(y.event==="pusher:error")return{action:this.getCloseAction(y.data),error:this.getCloseError(y.data)};throw"Invalid handshake"}},getCloseAction:function(E){return E.code<4e3?E.code>=1002&&E.code<=1004?"backoff":null:E.code===4e3?"tls_only":E.code<4100?"refused":E.code<4200?"backoff":E.code<4300?"retry":"refused"},getCloseError:function(E){return E.code!==1e3&&E.code!==1001?{type:"PusherError",data:{code:E.code,message:E.reason||E.message}}:null}},qr=Ac;class Ci extends Ae{constructor(y,C){super(),this.id=y,this.transport=C,this.activityTimeout=C.activityTimeout,this.bindListeners()}handlesActivityChecks(){return this.transport.handlesActivityChecks()}send(y){return this.transport.send(y)}send_event(y,C,O){var I={event:y,data:C};return O&&(I.channel=O),tt.debug("Event sent",I),this.send(qr.encodeMessage(I))}ping(){this.transport.supportsPing()?this.transport.ping():this.send_event("pusher:ping",{})}close(){this.transport.close()}bindListeners(){var y={message:O=>{var I;try{I=qr.decodeMessage(O)}catch(ne){this.emit("error",{type:"MessageParseError",error:ne,data:O.data})}if(I!==void 0){switch(tt.debug("Event recd",I),I.event){case"pusher:error":this.emit("error",{type:"PusherError",data:I.data});break;case"pusher:ping":this.emit("ping");break;case"pusher:pong":this.emit("pong");break}this.emit("message",I)}},activity:()=>{this.emit("activity")},error:O=>{this.emit("error",O)},closed:O=>{C(),O&&O.code&&this.handleCloseEvent(O),this.transport=null,this.emit("closed")}},C=()=>{kt(y,(O,I)=>{this.transport.unbind(I,O)})};kt(y,(O,I)=>{this.transport.bind(I,O)})}handleCloseEvent(y){var C=qr.getCloseAction(y),O=qr.getCloseError(y);O&&this.emit("error",O),C&&this.emit(C,{action:C,error:O})}}class No{constructor(y,C){this.transport=y,this.callback=C,this.bindListeners()}close(){this.unbindListeners(),this.transport.close()}bindListeners(){this.onMessage=y=>{this.unbindListeners();var C;try{C=qr.processHandshake(y)}catch(O){this.finish("error",{error:O}),this.transport.close();return}C.action==="connected"?this.finish("connected",{connection:new Ci(C.id,this.transport),activityTimeout:C.activityTimeout}):(this.finish(C.action,{error:C.error}),this.transport.close())},this.onClosed=y=>{this.unbindListeners();var C=qr.getCloseAction(y)||"backoff",O=qr.getCloseError(y);this.finish(C,{error:O})},this.transport.bind("message",this.onMessage),this.transport.bind("closed",this.onClosed)}unbindListeners(){this.transport.unbind("message",this.onMessage),this.transport.unbind("closed",this.onClosed)}finish(y,C){this.callback($e({transport:this.transport,action:y},C))}}class Na{constructor(y,C){this.timeline=y,this.options=C||{}}send(y,C){this.timeline.isEmpty()||this.timeline.send(je.TimelineTransport.getAgent(this,y),C)}}class Gr extends Ae{constructor(y,C){super(function(O,I){tt.debug("No callbacks on "+y+" for "+O)}),this.name=y,this.pusher=C,this.subscribed=!1,this.subscriptionPending=!1,this.subscriptionCancelled=!1}authorize(y,C){return C(null,{auth:""})}trigger(y,C){if(y.indexOf("client-")!==0)throw new D("Event '"+y+"' does not start with 'client-'");if(!this.subscribed){var O=M.buildLogSuffix("triggeringClientEvents");tt.warn(`Client event triggered before channel 'subscription_succeeded' event . ${O}`)}return this.pusher.send_event(y,C,this.name)}disconnect(){this.subscribed=!1,this.subscriptionPending=!1}handleEvent(y){var C=y.event,O=y.data;if(C==="pusher_internal:subscription_succeeded")this.handleSubscriptionSucceededEvent(y);else if(C==="pusher_internal:subscription_count")this.handleSubscriptionCountEvent(y);else if(C.indexOf("pusher_internal:")!==0){var I={};this.emit(C,O,I)}}handleSubscriptionSucceededEvent(y){this.subscriptionPending=!1,this.subscribed=!0,this.subscriptionCancelled?this.pusher.unsubscribe(this.name):this.emit("pusher:subscription_succeeded",y.data)}handleSubscriptionCountEvent(y){y.data.subscription_count&&(this.subscriptionCount=y.data.subscription_count),this.emit("pusher:subscription_count",y.data)}subscribe(){this.subscribed||(this.subscriptionPending=!0,this.subscriptionCancelled=!1,this.authorize(this.pusher.connection.socket_id,(y,C)=>{y?(this.subscriptionPending=!1,tt.error(y.toString()),this.emit("pusher:subscription_error",Object.assign({},{type:"AuthError",error:y.message},y instanceof Ue?{status:y.status}:{}))):this.pusher.send_event("pusher:subscribe",{auth:C.auth,channel_data:C.channel_data,channel:this.name})}))}unsubscribe(){this.subscribed=!1,this.pusher.send_event("pusher:unsubscribe",{channel:this.name})}cancelSubscription(){this.subscriptionCancelled=!0}reinstateSubscription(){this.subscriptionCancelled=!1}}class aa extends Gr{authorize(y,C){return this.pusher.config.channelAuthorizer({channelName:this.name,socketId:y},C)}}class Ia{constructor(){this.reset()}get(y){return Object.prototype.hasOwnProperty.call(this.members,y)?{id:y,info:this.members[y]}:null}each(y){kt(this.members,(C,O)=>{y(this.get(O))})}setMyID(y){this.myID=y}onSubscription(y){this.members=y.presence.hash,this.count=y.presence.count,this.me=this.get(this.myID)}addMember(y){return this.get(y.user_id)===null&&this.count++,this.members[y.user_id]=y.user_info,this.get(y.user_id)}removeMember(y){var C=this.get(y.user_id);return C&&(delete this.members[y.user_id],this.count--),C}reset(){this.members={},this.count=0,this.myID=null,this.me=null}}var Xt=function(E,y,C,O){function I(ne){return ne instanceof C?ne:new C(function(me){me(ne)})}return new(C||(C=Promise))(function(ne,me){function Me(gt){try{lt(O.next(gt))}catch(zt){me(zt)}}function Ke(gt){try{lt(O.throw(gt))}catch(zt){me(zt)}}function lt(gt){gt.done?ne(gt.value):I(gt.value).then(Me,Ke)}lt((O=O.apply(E,y||[])).next())})};class xc extends aa{constructor(y,C){super(y,C),this.members=new Ia}authorize(y,C){super.authorize(y,(O,I)=>Xt(this,void 0,void 0,function*(){if(!O)if(I=I,I.channel_data!=null){var ne=JSON.parse(I.channel_data);this.members.setMyID(ne.user_id)}else if(yield this.pusher.user.signinDonePromise,this.pusher.user.user_data!=null)this.members.setMyID(this.pusher.user.user_data.id);else{let me=M.buildLogSuffix("authorizationEndpoint");tt.error(`Invalid auth response for channel '${this.name}', expected 'channel_data' field. ${me}, or the user should be signed in.`),C("Invalid auth response");return}C(O,I)}))}handleEvent(y){var C=y.event;if(C.indexOf("pusher_internal:")===0)this.handleInternalEvent(y);else{var O=y.data,I={};y.user_id&&(I.user_id=y.user_id),this.emit(C,O,I)}}handleInternalEvent(y){var C=y.event,O=y.data;switch(C){case"pusher_internal:subscription_succeeded":this.handleSubscriptionSucceededEvent(y);break;case"pusher_internal:subscription_count":this.handleSubscriptionCountEvent(y);break;case"pusher_internal:member_added":var I=this.members.addMember(O);this.emit("pusher:member_added",I);break;case"pusher_internal:member_removed":var ne=this.members.removeMember(O);ne&&this.emit("pusher:member_removed",ne);break}}handleSubscriptionSucceededEvent(y){this.subscriptionPending=!1,this.subscribed=!0,this.subscriptionCancelled?this.pusher.unsubscribe(this.name):(this.members.onSubscription(y.data),this.emit("pusher:subscription_succeeded",this.members))}disconnect(){this.members.reset(),super.disconnect()}}var Tc=f(978),xr=f(594);class Ha extends aa{constructor(y,C,O){super(y,C),this.key=null,this.nacl=O}authorize(y,C){super.authorize(y,(O,I)=>{if(O){C(O,I);return}let ne=I.shared_secret;if(!ne){C(new Error(`No shared_secret key in auth payload for encrypted channel: ${this.name}`),null);return}this.key=(0,xr.decode)(ne),delete I.shared_secret,C(null,I)})}trigger(y,C){throw new ge("Client events are not currently supported for encrypted channels")}handleEvent(y){var C=y.event,O=y.data;if(C.indexOf("pusher_internal:")===0||C.indexOf("pusher:")===0){super.handleEvent(y);return}this.handleEncryptedEvent(C,O)}handleEncryptedEvent(y,C){if(!this.key){tt.debug("Received encrypted event before key has been retrieved from the authEndpoint");return}if(!C.ciphertext||!C.nonce){tt.error("Unexpected format for encrypted event, expected object with `ciphertext` and `nonce` fields, got: "+C);return}let O=(0,xr.decode)(C.ciphertext);if(O.length{if(me){tt.error(`Failed to make a request to the authEndpoint: ${Me}. Unable to fetch new key, so dropping encrypted event`);return}if(ne=this.nacl.secretbox.open(O,I,this.key),ne===null){tt.error("Failed to decrypt event with new key. Dropping encrypted event");return}this.emit(y,this.getDataToEmit(ne))});return}this.emit(y,this.getDataToEmit(ne))}getDataToEmit(y){let C=(0,Tc.D4)(y);try{return JSON.parse(C)}catch{return C}}}class xf extends Ae{constructor(y,C){super(),this.state="initialized",this.connection=null,this.key=y,this.options=C,this.timeline=this.options.timeline,this.usingTLS=this.options.useTLS,this.errorCallbacks=this.buildErrorCallbacks(),this.connectionCallbacks=this.buildConnectionCallbacks(this.errorCallbacks),this.handshakeCallbacks=this.buildHandshakeCallbacks(this.errorCallbacks);var O=je.getNetwork();O.bind("online",()=>{this.timeline.info({netinfo:"online"}),(this.state==="connecting"||this.state==="unavailable")&&this.retryIn(0)}),O.bind("offline",()=>{this.timeline.info({netinfo:"offline"}),this.connection&&this.sendActivityCheck()}),this.updateStrategy()}switchCluster(y){this.key=y,this.updateStrategy(),this.retryIn(0)}connect(){if(!(this.connection||this.runner)){if(!this.strategy.isSupported()){this.updateState("failed");return}this.updateState("connecting"),this.startConnecting(),this.setUnavailableTimer()}}send(y){return this.connection?this.connection.send(y):!1}send_event(y,C,O){return this.connection?this.connection.send_event(y,C,O):!1}disconnect(){this.disconnectInternally(),this.updateState("disconnected")}isUsingTLS(){return this.usingTLS}startConnecting(){var y=(C,O)=>{C?this.runner=this.strategy.connect(0,y):O.action==="error"?(this.emit("error",{type:"HandshakeError",error:O.error}),this.timeline.error({handshakeError:O.error})):(this.abortConnecting(),this.handshakeCallbacks[O.action](O))};this.runner=this.strategy.connect(0,y)}abortConnecting(){this.runner&&(this.runner.abort(),this.runner=null)}disconnectInternally(){if(this.abortConnecting(),this.clearRetryTimer(),this.clearUnavailableTimer(),this.connection){var y=this.abandonConnection();y.close()}}updateStrategy(){this.strategy=this.options.getStrategy({key:this.key,timeline:this.timeline,useTLS:this.usingTLS})}retryIn(y){this.timeline.info({action:"retry",delay:y}),y>0&&this.emit("connecting_in",Math.round(y/1e3)),this.retryTimer=new ie(y||0,()=>{this.disconnectInternally(),this.connect()})}clearRetryTimer(){this.retryTimer&&(this.retryTimer.ensureAborted(),this.retryTimer=null)}setUnavailableTimer(){this.unavailableTimer=new ie(this.options.unavailableTimeout,()=>{this.updateState("unavailable")})}clearUnavailableTimer(){this.unavailableTimer&&this.unavailableTimer.ensureAborted()}sendActivityCheck(){this.stopActivityCheck(),this.connection.ping(),this.activityTimer=new ie(this.options.pongTimeout,()=>{this.timeline.error({pong_timed_out:this.options.pongTimeout}),this.retryIn(0)})}resetActivityCheck(){this.stopActivityCheck(),this.connection&&!this.connection.handlesActivityChecks()&&(this.activityTimer=new ie(this.activityTimeout,()=>{this.sendActivityCheck()}))}stopActivityCheck(){this.activityTimer&&this.activityTimer.ensureAborted()}buildConnectionCallbacks(y){return $e({},y,{message:C=>{this.resetActivityCheck(),this.emit("message",C)},ping:()=>{this.send_event("pusher:pong",{})},activity:()=>{this.resetActivityCheck()},error:C=>{this.emit("error",C)},closed:()=>{this.abandonConnection(),this.shouldRetry()&&this.retryIn(1e3)}})}buildHandshakeCallbacks(y){return $e({},y,{connected:C=>{this.activityTimeout=Math.min(this.options.activityTimeout,C.activityTimeout,C.connection.activityTimeout||1/0),this.clearUnavailableTimer(),this.setConnection(C.connection),this.socket_id=this.connection.id,this.updateState("connected",{socket_id:this.socket_id})}})}buildErrorCallbacks(){let y=C=>O=>{O.error&&this.emit("error",{type:"WebSocketError",error:O.error}),C(O)};return{tls_only:y(()=>{this.usingTLS=!0,this.updateStrategy(),this.retryIn(0)}),refused:y(()=>{this.disconnect()}),backoff:y(()=>{this.retryIn(1e3)}),retry:y(()=>{this.retryIn(0)})}}setConnection(y){this.connection=y;for(var C in this.connectionCallbacks)this.connection.bind(C,this.connectionCallbacks[C]);this.resetActivityCheck()}abandonConnection(){if(this.connection){this.stopActivityCheck();for(var y in this.connectionCallbacks)this.connection.unbind(y,this.connectionCallbacks[y]);var C=this.connection;return this.connection=null,C}}updateState(y,C){var O=this.state;if(this.state=y,O!==y){var I=y;I==="connected"&&(I+=" with new socket ID "+C.socket_id),tt.debug("State changed",O+" -> "+I),this.timeline.info({state:y,params:C}),this.emit("state_change",{previous:O,current:y}),this.emit(y,C)}}shouldRetry(){return this.state==="connecting"||this.state==="connected"}}class Rc{constructor(){this.channels={}}add(y,C){return this.channels[y]||(this.channels[y]=kc(y,C)),this.channels[y]}all(){return nn(this.channels)}find(y){return this.channels[y]}remove(y){var C=this.channels[y];return delete this.channels[y],C}disconnect(){kt(this.channels,function(y){y.disconnect()})}}function kc(E,y){if(E.indexOf("private-encrypted-")===0){if(y.config.nacl)return Nn.createEncryptedChannel(E,y,y.config.nacl);let C="Tried to subscribe to a private-encrypted- channel but no nacl implementation available",O=M.buildLogSuffix("encryptedChannelSupport");throw new ge(`${C}. ${O}`)}else{if(E.indexOf("private-")===0)return Nn.createPrivateChannel(E,y);if(E.indexOf("presence-")===0)return Nn.createPresenceChannel(E,y);if(E.indexOf("#")===0)throw new B('Cannot create a channel with name "'+E+'".');return Nn.createChannel(E,y)}}var Tf={createChannels(){return new Rc},createConnectionManager(E,y){return new xf(E,y)},createChannel(E,y){return new Gr(E,y)},createPrivateChannel(E,y){return new aa(E,y)},createPresenceChannel(E,y){return new xc(E,y)},createEncryptedChannel(E,y,C){return new Ha(E,y,C)},createTimelineSender(E,y){return new Na(E,y)},createHandshake(E,y){return new No(E,y)},createAssistantToTheTransportManager(E,y,C){return new Af(E,y,C)}};const Nn=Tf;class Ei{constructor(y){this.options=y||{},this.livesLeft=this.options.lives||1/0}getAssistant(y){return Nn.createAssistantToTheTransportManager(this,y,{minPingDelay:this.options.minPingDelay,maxPingDelay:this.options.maxPingDelay})}isAlive(){return this.livesLeft>0}reportDeath(){this.livesLeft-=1}}class mn{constructor(y,C){this.strategies=y,this.loop=!!C.loop,this.failFast=!!C.failFast,this.timeout=C.timeout,this.timeoutLimit=C.timeoutLimit}isSupported(){return Fn(this.strategies,Te.method("isSupported"))}connect(y,C){var O=this.strategies,I=0,ne=this.timeout,me=null,Me=(Ke,lt)=>{lt?C(null,lt):(I=I+1,this.loop&&(I=I%O.length),I0&&(ne=new ie(O.timeout,function(){me.abort(),I(!0)})),me=y.connect(C,function(Me,Ke){Me&&ne&&ne.isRunning()&&!O.failFast||(ne&&ne.ensureAborted(),I(Me,Ke))}),{abort:function(){ne&&ne.ensureAborted(),me.abort()},forceMinPriority:function(Me){me.forceMinPriority(Me)}}}}class An{constructor(y){this.strategies=y}isSupported(){return Fn(this.strategies,Te.method("isSupported"))}connect(y,C){return Dc(this.strategies,y,function(O,I){return function(ne,me){if(I[O].error=ne,ne){Rf(I)&&C(!0);return}Ft(I,function(Me){Me.forceMinPriority(me.transport.priority)}),C(null,me)}})}}function Dc(E,y,C){var O=Nt(E,function(I,ne,me,Me){return I.connect(y,C(ne,Me))});return{abort:function(){Ft(O,Io)},forceMinPriority:function(I){Ft(O,function(ne){ne.forceMinPriority(I)})}}}function Rf(E){return Pr(E,function(y){return!!y.error})}function Io(E){!E.error&&!E.aborted&&(E.abort(),E.aborted=!0)}class Oc{constructor(y,C,O){this.strategy=y,this.transports=C,this.ttl=O.ttl||18e5,this.usingTLS=O.useTLS,this.timeline=O.timeline}isSupported(){return this.strategy.isSupported()}connect(y,C){var O=this.usingTLS,I=kf(O),ne=I&&I.cacheSkipCount?I.cacheSkipCount:0,me=[this.strategy];if(I&&I.timestamp+this.ttl>=Te.now()){var Me=this.transports[I.transport];Me&&(["ws","wss"].includes(I.transport)||ne>3?(this.timeline.info({cached:!0,transport:I.transport,latency:I.latency}),me.push(new mn([Me],{timeout:I.latency*2+1e3,failFast:!0}))):ne++)}var Ke=Te.now(),lt=me.pop().connect(y,function gt(zt,Dr){zt?(ms(O),me.length>0?(Ke=Te.now(),lt=me.pop().connect(y,gt)):C(zt)):(In(O,Dr.transport.name,Te.now()-Ke,ne),C(null,Dr))});return{abort:function(){lt.abort()},forceMinPriority:function(gt){y=gt,lt&<.forceMinPriority(gt)}}}}function za(E){return"pusherTransport"+(E?"TLS":"NonTLS")}function kf(E){var y=je.getLocalStorage();if(y)try{var C=y[za(E)];if(C)return JSON.parse(C)}catch{ms(E)}return null}function In(E,y,C,O){var I=je.getLocalStorage();if(I)try{I[za(E)]=gn({timestamp:Te.now(),transport:y,latency:C,cacheSkipCount:O})}catch{}}function ms(E){var y=je.getLocalStorage();if(y)try{delete y[za(E)]}catch{}}class _i{constructor(y,{delay:C}){this.strategy=y,this.options={delay:C}}isSupported(){return this.strategy.isSupported()}connect(y,C){var O=this.strategy,I,ne=new ie(this.options.delay,function(){I=O.connect(y,C)});return{abort:function(){ne.ensureAborted(),I&&I.abort()},forceMinPriority:function(me){y=me,I&&I.forceMinPriority(me)}}}}class ia{constructor(y,C,O){this.test=y,this.trueBranch=C,this.falseBranch=O}isSupported(){var y=this.test()?this.trueBranch:this.falseBranch;return y.isSupported()}connect(y,C){var O=this.test()?this.trueBranch:this.falseBranch;return O.connect(y,C)}}class ja{constructor(y){this.strategy=y}isSupported(){return this.strategy.isSupported()}connect(y,C){var O=this.strategy.connect(y,function(I,ne){ne&&O.abort(),C(I,ne)});return O}}function Ua(E){return function(){return E.isSupported()}}var Lc=function(E,y,C){var O={};function I(Xo,Fc,Kc,Xf,Jf){var Wc=C(E,Xo,Fc,Kc,Xf,Jf);return O[Xo]=Wc,Wc}var ne=Object.assign({},y,{hostNonTLS:E.wsHost+":"+E.wsPort,hostTLS:E.wsHost+":"+E.wssPort,httpPath:E.wsPath}),me=Object.assign({},ne,{useTLS:!0}),Me=Object.assign({},y,{hostNonTLS:E.httpHost+":"+E.httpPort,hostTLS:E.httpHost+":"+E.httpsPort,httpPath:E.httpPath}),Ke={loop:!0,timeout:15e3,timeoutLimit:6e4},lt=new Ei({minPingDelay:1e4,maxPingDelay:E.activityTimeout}),gt=new Ei({lives:2,minPingDelay:1e4,maxPingDelay:E.activityTimeout}),zt=I("ws","ws",3,ne,lt),Dr=I("wss","ws",3,me,lt),Kf=I("sockjs","sockjs",1,Me),Gc=I("xhr_streaming","xhr_streaming",1,Me,gt),Wf=I("xdr_streaming","xdr_streaming",1,Me,gt),Ko=I("xhr_polling","xhr_polling",1,Me),Vc=I("xdr_polling","xdr_polling",1,Me),qa=new mn([zt],Ke),Di=new mn([Dr],Ke),Yf=new mn([Kf],Ke),Oi=new mn([new ia(Ua(Gc),Gc,Wf)],Ke),Wo=new mn([new ia(Ua(Ko),Ko,Vc)],Ke),Yo=new mn([new ia(Ua(Oi),new An([Oi,new _i(Wo,{delay:4e3})]),Wo)],Ke),Li=new ia(Ua(Yo),Yo,Yf),Mi;return y.useTLS?Mi=new An([qa,new _i(Li,{delay:2e3})]):Mi=new An([qa,new _i(Di,{delay:2e3}),new _i(Li,{delay:5e3})]),new Oc(new ja(new ia(Ua(zt),Mi,Li)),O,{ttl:18e5,timeline:y.timeline,useTLS:y.useTLS})};const Ba=Lc;function Df(){var E=this;E.timeline.info(E.buildTimelineMessage({transport:E.name+(E.options.useTLS?"s":"")})),E.hooks.isInitialized()?E.changeState("initialized"):E.hooks.file?(E.changeState("initializing"),A.load(E.hooks.file,{useTLS:E.options.useTLS},function(y,C){E.hooks.isInitialized()?(E.changeState("initialized"),C(!0)):(y&&E.onError(y),E.onClose(),C(!1))})):E.onClose()}var Mc={getRequest:function(E){var y=new window.XDomainRequest;return y.ontimeout=function(){E.emit("error",new q),E.close()},y.onerror=function(C){E.emit("error",C),E.close()},y.onprogress=function(){y.responseText&&y.responseText.length>0&&E.onChunk(200,y.responseText)},y.onload=function(){y.responseText&&y.responseText.length>0&&E.onChunk(200,y.responseText),E.emit("finished",200),E.close()},y},abortRequest:function(E){E.ontimeout=E.onerror=E.onprogress=E.onload=null,E.abort()}};const $c=Mc,Ho=256*1024;class Of extends Ae{constructor(y,C,O){super(),this.hooks=y,this.method=C,this.url=O}start(y){this.position=0,this.xhr=this.hooks.getRequest(this),this.unloader=()=>{this.close()},je.addUnloadListener(this.unloader),this.xhr.open(this.method,this.url,!0),this.xhr.setRequestHeader&&this.xhr.setRequestHeader("Content-Type","application/json"),this.xhr.send(y)}close(){this.unloader&&(je.removeUnloadListener(this.unloader),this.unloader=null),this.xhr&&(this.hooks.abortRequest(this.xhr),this.xhr=null)}onChunk(y,C){for(;;){var O=this.advanceBuffer(C);if(O)this.emit("chunk",{status:y,data:O});else break}this.isBufferTooLong(C)&&this.emit("buffer_too_long")}advanceBuffer(y){var C=y.slice(this.position),O=C.indexOf(` +`);return O!==-1?(this.position+=O+1,C.slice(0,O)):null}isBufferTooLong(y){return this.position===y.length&&y.length>Ho}}var zo;(function(E){E[E.CONNECTING=0]="CONNECTING",E[E.OPEN=1]="OPEN",E[E.CLOSED=3]="CLOSED"})(zo||(zo={}));const sr=zo;var Tr=1;class jo{constructor(y,C){this.hooks=y,this.session=ys(1e3)+"/"+Ic(8),this.location=Uo(C),this.readyState=sr.CONNECTING,this.openStream()}send(y){return this.sendRaw(JSON.stringify([y]))}ping(){this.hooks.sendHeartbeat(this)}close(y,C){this.onClose(y,C,!0)}sendRaw(y){if(this.readyState===sr.OPEN)try{return je.createSocketRequest("POST",sa(Pa(this.location,this.session))).start(y),!0}catch{return!1}else return!1}reconnect(){this.closeStream(),this.openStream()}onClose(y,C,O){this.closeStream(),this.readyState=sr.CLOSED,this.onclose&&this.onclose({code:y,reason:C,wasClean:O})}onChunk(y){if(y.status===200){this.readyState===sr.OPEN&&this.onActivity();var C,O=y.data.slice(0,1);switch(O){case"o":C=JSON.parse(y.data.slice(1)||"{}"),this.onOpen(C);break;case"a":C=JSON.parse(y.data.slice(1)||"[]");for(var I=0;I{this.onChunk(y)}),this.stream.bind("finished",y=>{this.hooks.onFinished(this,y)}),this.stream.bind("buffer_too_long",()=>{this.reconnect()});try{this.stream.start()}catch(y){Te.defer(()=>{this.onError(y),this.onClose(1006,"Could not start streaming",!1)})}}closeStream(){this.stream&&(this.stream.unbind_all(),this.stream.close(),this.stream=null)}}function Uo(E){var y=/([^\?]*)\/*(\??.*)/.exec(E);return{base:y[1],queryString:y[2]}}function Pa(E,y){return E.base+"/"+y+"/xhr_send"}function sa(E){var y=E.indexOf("?")===-1?"?":"&";return E+y+"t="+ +new Date+"&n="+Tr++}function Nc(E,y){var C=/(https?:\/\/)([^\/:]+)((\/|:)?.*)/.exec(E);return C[1]+y+C[3]}function ys(E){return je.randomInt(E)}function Ic(E){for(var y=[],C=0;C0&&E.onChunk(C.status,C.responseText);break;case 4:C.responseText&&C.responseText.length>0&&E.onChunk(C.status,C.responseText),E.emit("finished",C.status),E.close();break}},C},abortRequest:function(E){E.onreadystatechange=null,E.abort()}};const bs=Po;var Hc={createStreamingSocket(E){return this.createSocket(Bo,E)},createPollingSocket(E){return this.createSocket(Vr,E)},createSocket(E,y){return new Ai(E,y)},createXHR(E,y){return this.createRequest(bs,E,y)},createRequest(E,y,C){return new Of(E,y,C)}};const Ti=Hc;Ti.createXDR=function(E,y){return this.createRequest($c,E,y)};var zc={nextAuthCallbackID:1,auth_callbacks:{},ScriptReceivers:m,DependenciesReceivers:_,getDefaultStrategy:Ba,Transports:_n,transportConnectionInitializer:Df,HTTPFactory:Ti,TimelineTransport:sn,getXHRAPI(){return window.XMLHttpRequest},getWebSocketAPI(){return window.WebSocket||window.MozWebSocket},setup(E){window.Pusher=E;var y=()=>{this.onDocumentBody(E.ready)};window.JSON?y():A.load("json2",{},y)},getDocument(){return document},getProtocol(){return this.getDocument().location.protocol},getAuthorizers(){return{ajax:qe,jsonp:pn}},onDocumentBody(E){document.body?E():setTimeout(()=>{this.onDocumentBody(E)},0)},createJSONPRequest(E,y){return new ir(E,y)},createScriptRequest(E){return new ra(E)},getLocalStorage(){try{return window.localStorage}catch{return}},createXHR(){return this.getXHRAPI()?this.createXMLHttpRequest():this.createMicrosoftXHR()},createXMLHttpRequest(){var E=this.getXHRAPI();return new E},createMicrosoftXHR(){return new ActiveXObject("Microsoft.XMLHTTP")},getNetwork(){return $o},createWebSocket(E){var y=this.getWebSocketAPI();return new y(E)},createSocketRequest(E,y){if(this.isXHRSupported())return this.HTTPFactory.createXHR(E,y);if(this.isXDRSupported(y.indexOf("https:")===0))return this.HTTPFactory.createXDR(E,y);throw"Cross-origin HTTP requests are not supported"},isXHRSupported(){var E=this.getXHRAPI();return!!E&&new E().withCredentials!==void 0},isXDRSupported(E){var y=E?"https:":"http:",C=this.getProtocol();return!!window.XDomainRequest&&C===y},addUnloadListener(E){window.addEventListener!==void 0?window.addEventListener("unload",E,!1):window.attachEvent!==void 0&&window.attachEvent("onunload",E)},removeUnloadListener(E){window.addEventListener!==void 0?window.removeEventListener("unload",E,!1):window.detachEvent!==void 0&&window.detachEvent("onunload",E)},randomInt(E){return Math.floor(function(){return(window.crypto||window.msCrypto).getRandomValues(new Uint32Array(1))[0]/Math.pow(2,32)}()*E)}};const je=zc;var kr;(function(E){E[E.ERROR=3]="ERROR",E[E.INFO=6]="INFO",E[E.DEBUG=7]="DEBUG"})(kr||(kr={}));const oa=kr;class Ri{constructor(y,C,O){this.key=y,this.session=C,this.events=[],this.options=O||{},this.sent=0,this.uniqueID=0}log(y,C){y<=this.options.level&&(this.events.push($e({},C,{timestamp:Te.now()})),this.options.limit&&this.events.length>this.options.limit&&this.events.shift())}error(y){this.log(oa.ERROR,y)}info(y){this.log(oa.INFO,y)}debug(y){this.log(oa.DEBUG,y)}isEmpty(){return this.events.length===0}send(y,C){var O=$e({session:this.session,bundle:this.sent+1,key:this.key,lib:"js",version:this.options.version,cluster:this.options.cluster,features:this.options.features,timeline:this.events},this.options.params);return this.events=[],y(O,(I,ne)=>{I||this.sent++,C&&C(I,ne)}),!0}generateUniqueID(){return this.uniqueID++,this.uniqueID}}class Lf{constructor(y,C,O,I){this.name=y,this.priority=C,this.transport=O,this.options=I||{}}isSupported(){return this.transport.isSupported({useTLS:this.options.useTLS})}connect(y,C){if(this.isSupported()){if(this.priority{O||(gt(),ne?ne.close():I.close())},forceMinPriority:zt=>{O||this.priority{var C="socket_id="+encodeURIComponent(E.socketId);for(var O in y.params)C+="&"+encodeURIComponent(O)+"="+encodeURIComponent(y.params[O]);if(y.paramsProvider!=null){let I=y.paramsProvider();for(var O in I)C+="&"+encodeURIComponent(O)+"="+encodeURIComponent(I[O])}return C},$f=E=>{if(typeof je.getAuthorizers()[E.transport]>"u")throw`'${E.transport}' is not a recognized auth transport`;return(y,C)=>{const O=Mf(y,E);je.getAuthorizers()[E.transport](je,O,E,R.UserAuthentication,C)}},Vo=(E,y)=>{var C="socket_id="+encodeURIComponent(E.socketId);C+="&channel_name="+encodeURIComponent(E.channelName);for(var O in y.params)C+="&"+encodeURIComponent(O)+"="+encodeURIComponent(y.params[O]);if(y.paramsProvider!=null){let I=y.paramsProvider();for(var O in I)C+="&"+encodeURIComponent(O)+"="+encodeURIComponent(I[O])}return C},Nf=E=>{if(typeof je.getAuthorizers()[E.transport]>"u")throw`'${E.transport}' is not a recognized auth transport`;return(y,C)=>{const O=Vo(y,E);je.getAuthorizers()[E.transport](je,O,E,R.ChannelAuthorization,C)}},If=(E,y,C)=>{const O={authTransport:y.transport,authEndpoint:y.endpoint,auth:{params:y.params,headers:y.headers}};return(I,ne)=>{const me=E.channel(I.channelName);C(me,O).authorize(I.socketId,ne)}};function Uc(E,y){let C={activityTimeout:E.activityTimeout||S.activityTimeout,cluster:E.cluster,httpPath:E.httpPath||S.httpPath,httpPort:E.httpPort||S.httpPort,httpsPort:E.httpsPort||S.httpsPort,pongTimeout:E.pongTimeout||S.pongTimeout,statsHost:E.statsHost||S.stats_host,unavailableTimeout:E.unavailableTimeout||S.unavailableTimeout,wsPath:E.wsPath||S.wsPath,wsPort:E.wsPort||S.wsPort,wssPort:E.wssPort||S.wssPort,enableStats:Uf(E),httpHost:Hf(E),useTLS:jf(E),wsHost:Bc(E),userAuthenticator:Fo(E),channelAuthorizer:Pf(E,y)};return"disabledTransports"in E&&(C.disabledTransports=E.disabledTransports),"enabledTransports"in E&&(C.enabledTransports=E.enabledTransports),"ignoreNullOrigin"in E&&(C.ignoreNullOrigin=E.ignoreNullOrigin),"timelineParams"in E&&(C.timelineParams=E.timelineParams),"nacl"in E&&(C.nacl=E.nacl),C}function Hf(E){return E.httpHost?E.httpHost:E.cluster?`sockjs-${E.cluster}.pusher.com`:S.httpHost}function Bc(E){return E.wsHost?E.wsHost:zf(E.cluster)}function zf(E){return`ws-${E}.pusher.com`}function jf(E){return je.getProtocol()==="https:"?!0:E.forceTLS!==!1}function Uf(E){return"enableStats"in E?E.enableStats:"disableStats"in E?!E.disableStats:!1}const Pc=E=>"customHandler"in E&&E.customHandler!=null;function Fo(E){const y=Object.assign(Object.assign({},S.userAuthentication),E.userAuthentication);return Pc(y)?y.customHandler:$f(y)}function Bf(E,y){let C;if("channelAuthorization"in E)C=Object.assign(Object.assign({},S.channelAuthorization),E.channelAuthorization);else if(C={transport:E.authTransport||S.authTransport,endpoint:E.authEndpoint||S.authEndpoint},"auth"in E&&("params"in E.auth&&(C.params=E.auth.params),"headers"in E.auth&&(C.headers=E.auth.headers)),"authorizer"in E)return{customHandler:If(y,C,E.authorizer)};return C}function Pf(E,y){const C=Bf(E,y);return Pc(C)?C.customHandler:Nf(C)}class qf extends Ae{constructor(y){super(function(C,O){tt.debug(`No callbacks on watchlist events for ${C}`)}),this.pusher=y,this.bindWatchlistInternalEvent()}handleEvent(y){y.data.events.forEach(C=>{this.emit(C.name,C)})}bindWatchlistInternalEvent(){this.pusher.connection.bind("message",y=>{var C=y.event;C==="pusher_internal:watchlist_events"&&this.handleEvent(y)})}}function qc(){let E,y;return{promise:new Promise((O,I)=>{E=O,y=I}),resolve:E,reject:y}}const Gf=qc;class Vf extends Ae{constructor(y){super(function(C,O){tt.debug("No callbacks on user for "+C)}),this.signin_requested=!1,this.user_data=null,this.serverToUserChannel=null,this.signinDonePromise=null,this._signinDoneResolve=null,this._onAuthorize=(C,O)=>{if(C){tt.warn(`Error during signin: ${C}`),this._cleanup();return}this.pusher.send_event("pusher:signin",{auth:O.auth,user_data:O.user_data})},this.pusher=y,this.pusher.connection.bind("state_change",({previous:C,current:O})=>{C!=="connected"&&O==="connected"&&this._signin(),C==="connected"&&O!=="connected"&&(this._cleanup(),this._newSigninPromiseIfNeeded())}),this.watchlist=new qf(y),this.pusher.connection.bind("message",C=>{var O=C.event;O==="pusher:signin_success"&&this._onSigninSuccess(C.data),this.serverToUserChannel&&this.serverToUserChannel.name===C.channel&&this.serverToUserChannel.handleEvent(C)})}signin(){this.signin_requested||(this.signin_requested=!0,this._signin())}_signin(){this.signin_requested&&(this._newSigninPromiseIfNeeded(),this.pusher.connection.state==="connected"&&this.pusher.config.userAuthenticator({socketId:this.pusher.connection.socket_id},this._onAuthorize))}_onSigninSuccess(y){try{this.user_data=JSON.parse(y.user_data)}catch{tt.error(`Failed parsing user data after signin: ${y.user_data}`),this._cleanup();return}if(typeof this.user_data.id!="string"||this.user_data.id===""){tt.error(`user_data doesn't contain an id. user_data: ${this.user_data}`),this._cleanup();return}this._signinDoneResolve(),this._subscribeChannels()}_subscribeChannels(){const y=C=>{C.subscriptionPending&&C.subscriptionCancelled?C.reinstateSubscription():!C.subscriptionPending&&this.pusher.connection.state==="connected"&&C.subscribe()};this.serverToUserChannel=new Gr(`#server-to-user-${this.user_data.id}`,this.pusher),this.serverToUserChannel.bind_global((C,O)=>{C.indexOf("pusher_internal:")===0||C.indexOf("pusher:")===0||this.emit(C,O)}),y(this.serverToUserChannel)}_cleanup(){this.user_data=null,this.serverToUserChannel&&(this.serverToUserChannel.unbind_all(),this.serverToUserChannel.disconnect(),this.serverToUserChannel=null),this.signin_requested&&this._signinDoneResolve()}_newSigninPromiseIfNeeded(){if(!this.signin_requested||this.signinDonePromise&&!this.signinDonePromise.done)return;const{promise:y,resolve:C}=Gf();y.done=!1;const O=()=>{y.done=!0};y.then(O).catch(O),this.signinDonePromise=y,this._signinDoneResolve=C}}class on{static ready(){on.isReady=!0;for(var y=0,C=on.instances.length;yje.getDefaultStrategy(this.config,I,ki);this.connection=Nn.createConnectionManager(this.key,{getStrategy:O,timeline:this.timeline,activityTimeout:this.config.activityTimeout,pongTimeout:this.config.pongTimeout,unavailableTimeout:this.config.unavailableTimeout,useTLS:!!this.config.useTLS}),this.connection.bind("connected",()=>{this.subscribeAll(),this.timelineSender&&this.timelineSender.send(this.connection.isUsingTLS())}),this.connection.bind("message",I=>{var ne=I.event,me=ne.indexOf("pusher_internal:")===0;if(I.channel){var Me=this.channel(I.channel);Me&&Me.handleEvent(I)}me||this.global_emitter.emit(I.event,I.data)}),this.connection.bind("connecting",()=>{this.channels.disconnect()}),this.connection.bind("disconnected",()=>{this.channels.disconnect()}),this.connection.bind("error",I=>{tt.warn(I)}),on.instances.push(this),this.timeline.info({instances:on.instances.length}),this.user=new Vf(this),on.isReady&&this.connect()}switchCluster(y){const{appKey:C,cluster:O}=y;this.key=C,this.options=Object.assign(Object.assign({},this.options),{cluster:O}),this.config=Uc(this.options,this),this.connection.switchCluster(this.key)}channel(y){return this.channels.find(y)}allChannels(){return this.channels.all()}connect(){if(this.connection.connect(),this.timelineSender&&!this.timelineSenderTimer){var y=this.connection.isUsingTLS(),C=this.timelineSender;this.timelineSenderTimer=new xe(6e4,function(){C.send(y)})}}disconnect(){this.connection.disconnect(),this.timelineSenderTimer&&(this.timelineSenderTimer.ensureAborted(),this.timelineSenderTimer=null)}bind(y,C,O){return this.global_emitter.bind(y,C,O),this}unbind(y,C,O){return this.global_emitter.unbind(y,C,O),this}bind_global(y){return this.global_emitter.bind_global(y),this}unbind_global(y){return this.global_emitter.unbind_global(y),this}unbind_all(y){return this.global_emitter.unbind_all(),this}subscribeAll(){var y;for(y in this.channels.channels)this.channels.channels.hasOwnProperty(y)&&this.subscribe(y)}subscribe(y){var C=this.channels.add(y,this);return C.subscriptionPending&&C.subscriptionCancelled?C.reinstateSubscription():!C.subscriptionPending&&this.connection.state==="connected"&&C.subscribe(),C}unsubscribe(y){var C=this.channels.find(y);C&&C.subscriptionPending?C.cancelSubscription():(C=this.channels.remove(y),C&&C.subscribed&&C.unsubscribe())}send_event(y,C,O){return this.connection.send_event(y,C,O)}shouldUseTLS(){return this.config.useTLS}signin(){this.user.signin()}}on.instances=[],on.isReady=!1,on.logToConsole=!1,on.Runtime=je,on.ScriptReceivers=je.ScriptReceivers,on.DependenciesReceivers=je.DependenciesReceivers,on.auth_callbacks=je.auth_callbacks;const ws=on;function Ff(E){if(E==null)throw"You must pass your app key when you instantiate Pusher."}je.setup(on)}},a={};function i(c){var d=a[c];if(d!==void 0)return d.exports;var f=a[c]={exports:{}};return n[c].call(f.exports,f,f.exports,i),f.exports}i.d=(c,d)=>{for(var f in d)i.o(d,f)&&!i.o(c,f)&&Object.defineProperty(c,f,{enumerable:!0,get:d[f]})},i.o=(c,d)=>Object.prototype.hasOwnProperty.call(c,d);var l=i(721);return l})())})(bf)),bf.exports}var P_=B_();const uy=Jr(P_),Ut=ut("WebSocketManager");class q_{constructor(){$(this,"echo",null);$(this,"subscriptions",new Map);$(this,"initialized",!1);$(this,"config",null)}configure(e){if(this.initialized){Ut.warn("[WebSocketManager] 이미 초기화되어 설정을 변경할 수 없습니다.");return}this.config=e,Ut.log("[WebSocketManager] 설정 완료:",{appKey:e.appKey?"***":"(없음)",host:e.host,port:e.port,scheme:e.scheme})}initialize(){if(this.initialized)return;if(!this.config||!this.config.appKey){Ut.warn("[WebSocketManager] WebSocket 설정이 없습니다. initTemplateApp에서 websocket 옵션을 전달해주세요.");return}const{appKey:e,host:n="localhost",port:a=80,scheme:i="https",authEndpoint:l="/api/broadcasting/auth"}=this.config,c=Number(a)||80,d=i==="https";Ut.log("[WebSocketManager] 연결 설정:",{host:n,port:c,scheme:i,useTLS:d,authEndpoint:l}),window.Pusher=uy;const f=localStorage.getItem("auth_token"),g={wsHost:n,wsPort:c,wssPort:c,forceTLS:d,disableStats:!0,enabledTransports:["ws","wss"],cluster:"mt1",authEndpoint:l,auth:{headers:{Authorization:f?`Bearer ${f}`:"",Accept:"application/json"}}};Ut.log("[WebSocketManager] Pusher 옵션:",g);const m=new uy(e,g);m.connection.bind("connecting",()=>{Ut.log("[WebSocketManager] 연결 시도 중...")}),m.connection.bind("connected",()=>{Ut.log("[WebSocketManager] 연결 성공! Socket ID:",m.connection.socket_id)}),m.connection.bind("failed",()=>{Ut.error("[WebSocketManager] 연결 실패 - WebSocket을 사용할 수 없습니다.")}),m.connection.bind("error",b=>{Ut.error("[WebSocketManager] 연결 오류:",b)}),m.connection.bind("state_change",b=>{Ut.log(`[WebSocketManager] 상태 변경: ${b.previous} → ${b.current}`)}),m.connection.bind("unavailable",()=>{Ut.error("[WebSocketManager] WebSocket 사용 불가 - 연결할 수 없습니다.")}),m.connection.bind("disconnected",()=>{Ut.warn("[WebSocketManager] 연결이 끊어졌습니다.")}),Ut.log("[WebSocketManager] 초기 연결 상태:",m.connection.state),this.echo=new U_({broadcaster:"reverb",client:m}),Ut.log("[WebSocketManager] 연결 시작 시도..."),m.connect(),window.Echo=this.echo,this.initialized=!0,Ut.log("[WebSocketManager] Echo 초기화 완료")}subscribe(e,n,a,i={}){if(this.initialize(),!this.echo)return Ut.warn("[WebSocketManager] Echo가 초기화되지 않았습니다."),"";const{channelType:l="private"}=i,c=`${e}:${n}`;if(this.subscriptions.has(c))return Ut.log(`[WebSocketManager] 이미 구독 중: ${c}`),c;let d;switch(l){case"public":d=this.echo.channel(e);break;case"presence":d=this.echo.join(e);break;default:d=this.echo.private(e)}return d.listen(`.${n}`,a),this.subscriptions.set(c,d),Ut.log(`[WebSocketManager] 구독 완료: ${c} (${l})`),c}unsubscribe(e){const n=this.subscriptions.get(e);if(!n)return;const a=e.lastIndexOf(":"),i=a>=0?e.substring(a+1):"";if(i)try{n.stopListening(`.${i}`)}catch(l){Ut.warn(`[WebSocketManager] stopListening 실패: ${e}`,l)}this.subscriptions.delete(e),Ut.log(`[WebSocketManager] 구독 해제: ${e}`)}leaveChannel(e){if(!this.echo)return;this.echo.leave(e);const n=[];this.subscriptions.forEach((a,i)=>{i.startsWith(`${e}:`)&&n.push(i)}),n.forEach(a=>this.subscriptions.delete(a)),Ut.log(`[WebSocketManager] 채널 구독 해제: ${e}`)}disconnect(){this.echo&&(this.echo.disconnect(),this.subscriptions.clear(),this.initialized=!1,Ut.log("[WebSocketManager] 연결 종료"))}getEcho(){return this.echo}isInitialized(){return this.initialized}isConfigured(){return this.config!==null&&!!this.config.appKey}getSubscriptionCount(){return this.subscriptions.size}}const vc=new q_,yt=ut("DataSourceManager"),dy=new xn;function Sc(o,e){const n=Aa(o);if(n!==null)try{return Pn(n)?dy.evaluatePipeExpression(n,e,{skipCache:!0}):dy.evaluateExpression(n,e)}catch(a){return yt.error("Failed to evaluate param expression:",n,a),o}return ns(o,e,{skipCache:!0})}function fy(o){const e={};for(const n of o.keys()){if(n in e)continue;const a=o.getAll(n);a.length>1?e[n]=a:a.length===1&&(n.endsWith("[]")?e[n]=a:e[n]=a[0])}return e}function G_(){try{return window.G7Core?.devTools}catch{return}}const _c=class _c{constructor(e={}){$(this,"options");$(this,"dataCache",new Map);$(this,"bindingEngine");$(this,"globalHeaders",[]);this.options=e,this.bindingEngine=new xn}setGlobalHeaders(e){this.globalHeaders=e||[]}matchesPattern(e,n){if(n==="*")return!0;const a=n.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*");return new RegExp(`^${a}$`).test(e)}toFormData(e){const n=new FormData;for(const[a,i]of Object.entries(e))i instanceof File||i instanceof Blob?n.append(a,i):i!=null&&n.append(a,typeof i=="object"?JSON.stringify(i):String(i));return n}getMatchingGlobalHeaders(e,n){const a={};for(const i of this.globalHeaders)this.matchesPattern(e,i.pattern)&&Object.entries(i.headers).forEach(([l,c])=>{const d=Sc(c,n);d!=null&&d!==""&&(a[l]=String(d))});return a}filterByCondition(e,n={}){const a=new Set,i=[];for(const l of e){if(a.has(l.id))continue;if(!l.if&&!l.conditions){a.add(l.id),i.push(l);continue}ts({if:l.if,conditions:l.conditions},n,this.bindingEngine,`data_source:${l.id}`)&&(a.add(l.id),i.push(l))}return i}checkErrorCondition(e,n){if(!e.errorCondition?.if||e.errorCondition?.errorCode===void 0)return null;try{const a={response:n};if(ts({if:e.errorCondition.if},a,this.bindingEngine,`errorCondition:${e.id}`))return yt.log(`errorCondition matched for ${e.id}, triggering error ${e.errorCondition.errorCode}`),e.errorCondition.errorCode}catch(a){yt.error(`Failed to evaluate errorCondition for ${e.id}:`,a)}return null}async fetchDataSources(e,n={},a=new URLSearchParams,i,l){const c={},d=!!this.options.sampleProvider,f=e.filter(g=>(d||g.auto_fetch!==!1)&&g.type!=="websocket");return await Promise.all(f.map(async g=>{try{const m=await this.fetchDataSource(g,n,a,i,l);c[g.id]=m,this.dataCache.set(g.id,{data:m,timestamp:Date.now()});const b=this.checkErrorCondition(g,m);if(b!==null){const S={status:b,message:`Error condition matched for ${g.id}`,data:m},v=vr(),_=v.resolve(b,{errorHandling:g.errorHandling,onError:g.onError});if(_.handler)try{await v.execute(_.handler,S)}catch(A){yt.error(`Error executing errorCondition handler for ${g.id}:`,A)}g.fallback!==void 0&&(c[g.id]=g.fallback,this.dataCache.set(g.id,{data:g.fallback,timestamp:Date.now()}),yt.log(`Using fallback data for ${g.id} (errorCondition)`));return}if(g.onSuccess)try{const S={data:m,sourceId:g.id};await this.executeOnSuccessHandler(g.onSuccess,S)}catch(S){yt.error(`Error executing onSuccess handler for ${g.id}:`,S)}}catch(m){yt.error(`Failed to fetch data source: ${g.id}`,m);const b=m?.response?.status||m?.status||500,S=m?.response?.data,v=S?.message||m.message||"Unknown error",_={status:b,message:v,errors:S?.errors,data:S,statusText:m?.response?.statusText},A=vr(),x=A.resolve(b,{errorHandling:g.errorHandling,onError:g.onError});if(x.handler)try{await A.execute(x.handler,_)}catch(L){yt.error(`Error executing error handler for ${g.id}:`,L)}g.fallback!==void 0&&(c[g.id]=g.fallback,this.dataCache.set(g.id,{data:g.fallback,timestamp:Date.now()}),yt.log(`Using fallback data for ${g.id}`)),this.options.onError&&this.options.onError(m,g)}})),c}async fetchDataSourcesWithResults(e,n={},a=new URLSearchParams,i,l,c){if(e.some(m=>m.type==="websocket")){const m=e.filter(b=>b.type==="websocket").map(b=>b.id).join(", ");yt.warn(`fetchDataSourcesWithResults received WebSocket sources (caller should filter them out before calling): ${m}. WebSocket sources are event listeners, not data providers — they should not be passed to fetch.`)}const f=c?.ignoreAutoFetch?e.filter(m=>m.type!=="websocket"):e.filter(m=>m.auto_fetch!==!1&&m.type!=="websocket");return await Promise.all(f.map(async m=>{try{const b=await this.fetchDataSource(m,n,a,i,l);this.dataCache.set(m.id,{data:b,timestamp:Date.now()});const S=this.checkErrorCondition(m,b);if(S!==null){const v={status:S,message:`Error condition matched for ${m.id}`,data:b},_=vr(),A=_.resolve(S,{errorHandling:m.errorHandling,onError:m.onError});let x=!1;if(A.handler)if(m.loading_strategy==="blocking")_.execute(A.handler,v).catch(L=>{yt.error(`Error executing errorCondition handler for ${m.id}:`,L)}),x=!0;else try{await _.execute(A.handler,v),x=!0}catch(L){yt.error(`Error executing errorCondition handler for ${m.id}:`,L)}return m.fallback!==void 0?(this.dataCache.set(m.id,{data:m.fallback,timestamp:Date.now()}),yt.log(`Using fallback data for ${m.id} (errorCondition)`),{id:m.id,state:"success",data:m.fallback}):{id:m.id,state:"error",error:new Error(`Error condition matched: ${S}`),errorCode:S,errorHandled:x}}if(m.onSuccess)try{const v={data:b,sourceId:m.id};await this.executeOnSuccessHandler(m.onSuccess,v)}catch(v){yt.error(`Error executing onSuccess handler for ${m.id}:`,v)}return{id:m.id,state:"success",data:b}}catch(b){yt.error(`Failed to fetch data source: ${m.id}`,b);const S=b?.response?.status||b?.status||500,v=b?.response?.data,_=v?.message||b.message||"Unknown error",A={status:S,message:_,errors:v?.errors,data:v,statusText:b?.response?.statusText},x=vr(),L=x.resolve(S,{errorHandling:m.errorHandling,onError:m.onError});let M=!1;if(L.handler)if(m.loading_strategy==="blocking")x.execute(L.handler,A).catch(R=>{yt.error(`Error executing error handler for ${m.id}:`,R)}),M=!0;else try{await x.execute(L.handler,A),M=!0}catch(R){yt.error(`Error executing error handler for ${m.id}:`,R)}return m.fallback!==void 0?(this.dataCache.set(m.id,{data:m.fallback,timestamp:Date.now()}),yt.log(`Using fallback data for ${m.id}`),{id:m.id,state:"success",data:m.fallback}):(this.options.onError&&this.options.onError(b,m),{id:m.id,state:"error",error:b,errorCode:S,errorHandled:M})}}))}fetchDataSourcesInBackground(e,n={},a=new URLSearchParams,i){e.filter(c=>c.auto_fetch!==!1&&c.loading_strategy==="background"&&c.type!=="websocket").forEach(c=>{this.fetchDataSource(c,n,a).then(async d=>{this.dataCache.set(c.id,{data:d,timestamp:Date.now()});const f=this.checkErrorCondition(c,d);if(f!==null){const g={status:f,message:`Error condition matched for ${c.id}`,data:d},m=vr(),b=m.resolve(f,{errorHandling:c.errorHandling,onError:c.onError});if(b.handler)try{await m.execute(b.handler,g)}catch(S){yt.error(`Error executing errorCondition handler for ${c.id}:`,S)}c.fallback!==void 0&&(this.dataCache.set(c.id,{data:c.fallback,timestamp:Date.now()}),yt.log(`Using fallback data for ${c.id} (errorCondition)`),i&&i(c.id,c.fallback));return}if(c.onSuccess)try{const g={data:d,sourceId:c.id};await this.executeOnSuccessHandler(c.onSuccess,g)}catch(g){yt.error(`Error executing onSuccess handler for ${c.id}:`,g)}i&&i(c.id,d)}).catch(async d=>{yt.error(`Background fetch failed: ${c.id}`,d);const f=d?.response?.status||d?.status||500,g=d?.response?.data,m=g?.message||d.message||"Unknown error",b={status:f,message:m,errors:g?.errors,data:g,statusText:d?.response?.statusText},S=vr(),v=S.resolve(f,{errorHandling:c.errorHandling,onError:c.onError});if(v.handler)try{await S.execute(v.handler,b)}catch(_){yt.error(`Error executing error handler for ${c.id}:`,_)}if(c.fallback!==void 0){this.dataCache.set(c.id,{data:c.fallback,timestamp:Date.now()}),yt.log(`Using fallback data for ${c.id}`),i&&i(c.id,c.fallback);return}this.options.onError&&this.options.onError(d,c)})})}async fetchDataSource(e,n,a,i,l){switch(e.type){case"api":return this.fetchApiDataSource(e,n,a,i,l);case"static":return e.data;case"route_params":return n;case"query_params":return fy(a);default:throw new Error(`Unknown data source type: ${e.type}`)}}async fetchApiDataSource(e,n,a,i,l){if(this.options.sampleProvider?.has(e.id))return await Promise.resolve(this.options.sampleProvider.resolve(e));if(!e.endpoint)throw new Error(`API data source ${e.id} has no endpoint`);const c={route:n,query:fy(a)};i&&(c._global=i),l&&(c._local=l);let d=ns(e.endpoint,c,{skipCache:!0});const f=e.method||"GET",m=(e.contentType?typeof e.contentType=="string"&&e.contentType.includes("{{")?ns(e.contentType,c,{skipCache:!0}):e.contentType:"application/json")==="multipart/form-data";let b=m?{...e.params||{}}:JSON.parse(JSON.stringify(e.params||{}));Object.entries(b).forEach(([_,A])=>{typeof A=="string"&&(b[_]=Sc(A,c))}),Object.keys(b).forEach(_=>{b[_]===""&&delete b[_]});const S=G_();let v=null;if(S?.isEnabled()){S.trackDataSourceDefinition({id:e.id,type:e.type,endpoint:d,method:f,autoFetch:e.auto_fetch,initLocal:e.initLocal,initGlobal:e.initGlobal});let _=d;if(f==="GET"&&Object.keys(b).length>0){const A=new URLSearchParams;Object.entries(b).forEach(([L,M])=>{Array.isArray(M)?M.forEach(R=>A.append(L,String(R))):M!=null&&A.append(L,String(M))});const x=A.toString();_=x?`${d}?${x}`:d}v=S.trackRequest(_,f,{requestBody:f!=="GET"?b:void 0,dataSourceId:e.id}),S.trackDataSourceLoading(e.id)}try{let _;const A=e.auth_mode??(e.auth_required===!0?"required":"none"),x=yr.getInstance().isAuthenticated();if(A==="required"&&!x){yt.log(`[DataSourceManager] Skipping ${e.id}: auth_mode is 'required' but no token available`),S?.isEnabled()&&(S.trackDataSourceError(e.id,"Skipped: auth_mode required but not authenticated"),v&&S.failRequest(v,"Skipped: not authenticated"));const M=new Error("Auth required but not authenticated");throw M.response={status:401,statusText:"Unauthorized",data:null},M.status=401,M._authSkipped=!0,M}const L=A==="required"||A==="optional"&&x;if(L){const M=Ir(),R=d.startsWith("/api/")?d.substring(4):d.startsWith("/api")?d.substring(4)||"/":d;this.options.onError&&yt.log(`[DataSourceManager] Normalized endpoint: ${d} -> ${R}`);const D=this.getMatchingGlobalHeaders(d,c),B={};e.headers&&Object.entries(e.headers).forEach(([ge,Ee])=>{const Se=Sc(Ee,c);Se!=null&&Se!==""&&(B[ge]=String(Se))});const q={...D,...B},P=Object.keys(q).length>0,K=m?this.toFormData(b):b;switch(f){case"GET":_=await M.get(R,{params:b,...P&&{headers:q}});break;case"POST":P?_=await M.post(R,K,{headers:q}):_=await M.post(R,K);break;case"PUT":P?_=await M.put(R,K,{headers:q}):_=await M.put(R,K);break;case"PATCH":P?_=await M.patch(R,K,{headers:q}):_=await M.patch(R,K);break;case"DELETE":_=await M.delete(R,{params:b,...P&&{headers:q}});break;default:throw new Error(`Unsupported HTTP method: ${f}`)}}else{let M=d;if(f==="GET"){const K=new URLSearchParams(b).toString();M=K?`${d}?${K}`:d}const R={...m?{}:{"Content-Type":"application/json"},Accept:"application/json"};if(typeof window<"u"){const K=localStorage.getItem("g7_locale");K&&(R["Accept-Language"]=K)}const D=this.getMatchingGlobalHeaders(d,c);Object.assign(R,D),e.headers&&Object.entries(e.headers).forEach(([K,ge])=>{const Ee=Sc(ge,c);Ee!=null&&Ee!==""&&(R[K]=String(Ee))});let B;f!=="GET"&&(m?(B=this.toFormData(b),delete R["Content-Type"]):B=JSON.stringify(b));const q=await fetch(M,{method:f,headers:R,body:B});let P;try{P=await q.json()}catch{P=null}if(v&&S?.isEnabled()&&(S.completeRequest(v,q.status,P),v=null),!q.ok){S?.isEnabled()&&S.trackDataSourceError(e.id,`HTTP ${q.status}: ${q.statusText}`);const K=new Error(`HTTP error! status: ${q.status}`);throw K.response={status:q.status,statusText:q.statusText,data:P},K.status=q.status,K}_=P,S?.isEnabled()&&S.trackDataSourceLoaded(e.id,_)}return L&&v&&S?.isEnabled()&&(S.completeRequest(v,200,_),S.trackDataSourceLoaded(e.id,_)),_}catch(_){if(v&&S?.isEnabled()){const A=_;A?.response?S.completeRequest(v,A.response.status,A.response.data):S.failRequest(v,_ instanceof Error?_.message:String(_)),S.trackDataSourceError(e.id,_ instanceof Error?_.message:String(_))}throw _}}getCachedData(e){const n=this.dataCache.get(e);if(!n)return;if(Date.now()-n.timestamp>_c.DATA_CACHE_TTL){this.dataCache.delete(e);return}return n.data}clearCache(){this.dataCache.clear()}subscribeWebSockets(e,n,a={}){const i=[],l=e.filter(c=>c.type==="websocket");return l.length===0||l.forEach(c=>{if(!c.channel||!c.event){yt.warn(`WebSocket source ${c.id} missing channel or event`);return}let d,f;try{d=ns(c.channel,a,{skipCache:!0}),f=ns(c.event,a,{skipCache:!0})}catch(b){yt.error(`Failed to resolve WebSocket channel/event for source ${c.id}:`,b);return}const g=b=>!!(!b||b.trim()===""||b.includes("{{")||b.endsWith(".")||b.endsWith(":")||b.startsWith(".")||b.startsWith(":")||b.includes("..")||b.includes("::"));if(g(d)||g(f)){yt.warn(`WebSocket source ${c.id} has invalid channel/event after expression resolution, skipping. Original channel="${c.channel}", resolved="${d}". Original event="${c.event}", resolved="${f}". Available context keys: [${Object.keys(a).join(", ")}]`);return}const m=vc.subscribe(d,f,b=>{const S=c.target_source||c.id;this.dataCache.set(S,{data:b,timestamp:Date.now()}),n(S,b)},{channelType:c.channel_type||"private"});m&&i.push(m)}),i}unsubscribeWebSockets(e){e.length!==0&&e.forEach(n=>{vc.unsubscribe(n)})}async executeOnSuccessHandler(e,n){const a=this.options.actionDispatcher??cm();if(!a){yt.warn("ActionDispatcher not available, skipping onSuccess handler");return}const i=Array.isArray(e)?e:[e];for(const l of i)try{await a.dispatchAction({type:"click",...l},{data:{response:n}})}catch(c){throw yt.error("Failed to execute onSuccess handler:",c),c}}};$(_c,"DATA_CACHE_TTL",3e5);let Er=_c;const V_=new Er,yi=ut("ModalDataSourceWrapper");function F_(o,e){if(!o||!e)return o;const a=e.replace(/\?\./g,".").split(/\.(?![^\[]*\])/).flatMap(l=>{const c=l.match(/^([^\[]*)((?:\[\d+\])*)$/);if(c){const[,d,f]=c,g=[];d&&g.push(d);const m=f.match(/\[\d+\]/g);return m&&g.push(...m),g}return[l]}).filter(l=>l!=="");let i=o;for(const l of a){if(i==null)return;if(l.startsWith("[")&&l.endsWith("]")){const c=parseInt(l.slice(1,-1),10);i=i[c]}else i=i[l]}return i}const hy=({isOpen:o,modalId:e,dataSources:n,dataContext:a,globalStateUpdater:i,bindingEngine:l,debug:c=!1,children:d})=>{const[f,g]=N.useState(!1),[m,b]=N.useState(!1),S=N.useRef(!1),v=N.useRef(null);v.current||(v.current=new Er);const _=N.useCallback(async()=>{if(!(!n||n.length===0)&&v.current){g(!0);try{const A=a.route||{},x=new URLSearchParams(window.location.search),L=n.map(R=>{let D={...R};if(R.endpoint&&l){const B=l.resolveBindings(R.endpoint,a,{skipCache:!0});D.endpoint=B}if(R.params&&l){const B={};Object.entries(R.params).forEach(([q,P])=>{typeof P=="string"?B[q]=l.resolveBindings(P,a,{skipCache:!0}):B[q]=P}),D.params=B}return D});c&&yi.log(`[ModalDataSourceWrapper] Modal "${e}" fetching data sources:`,L.map(R=>({id:R.id,endpoint:R.endpoint})));const M=await v.current.fetchDataSourcesWithResults(L,A,x);i&&M.forEach(R=>{if(R.state==="success"&&R.data!==void 0){const D=L.find(B=>B.id===R.id);if(D?.initGlobal){const B=R.data?.data??R.data,q=Array.isArray(D.initGlobal)?D.initGlobal:[D.initGlobal];for(const P of q)if(typeof P=="string")i({[P]:B}),c&&yi.log(`[ModalDataSourceWrapper] Modal "${e}" initGlobal: ${R.id}.data -> _global.${P}`);else if(typeof P=="object"&&P.key){const{key:K,path:ge}=P,Ee=ge?F_(B,ge):B;i({[K]:Ee}),c&&yi.log(`Modal "${e}" initGlobal: ${R.id}.data${ge?"."+ge:""} -> _global.${K}`)}}}else R.state==="error"&&yi.error(`Modal "${e}" data source error:`,R.id,R.error)}),c&&yi.log(`Modal "${e}" data sources fetched successfully`)}catch(A){yi.error(`Modal "${e}" data source fetch error:`,A)}finally{g(!1),b(!0)}}},[n,a,i,l,e,c]);return N.useEffect(()=>{if(!n||n.length===0)return;const A=window.__templateApp;return A?.registerModalDataSources&&A.registerModalDataSources(e,n),()=>{A?.unregisterModalDataSources&&A.unregisterModalDataSources(e)}},[e,n]),N.useEffect(()=>{!o&&S.current&&(b(!1),c&&yi.log(`[ModalDataSourceWrapper] Modal "${e}" closed, reset fetch state`)),o&&!S.current&&n&&n.length>0&&!m&&_(),S.current=o},[o,n,m,_,e,c]),dt.jsx(dt.Fragment,{children:d})};class wc extends Error{constructor(n,a,i={}){super(a);$(this,"code");$(this,"userMessageKey");$(this,"showStack");$(this,"icon");$(this,"recoverable");this.name="TemplateEngineError",this.code=n,this.userMessageKey=a,this.showStack=i.showStack??!0,this.icon=i.icon??"fa-circle-exclamation",this.recoverable=i.recoverable??!0,Error.captureStackTrace&&Error.captureStackTrace(this,this.constructor)}getUserMessage(n,a){if(!a||!n)return this.userMessageKey;try{return a.translate(this.userMessageKey,n)}catch{return this.userMessageKey}}}class K_ extends wc{constructor(){super("TEMPLATE_NOT_FOUND","$t:core.errors.template_not_found",{showStack:!1,icon:"fa-paint-brush",recoverable:!1}),this.name="TemplateNotFoundError"}}function gy(o){if(o instanceof wc)return o;if(o instanceof Error){const e=new wc("TEMPLATE_INIT_ERROR",o.message||"$t:core.errors.template_init_error",{showStack:!0,icon:"fa-circle-exclamation",recoverable:!0});return e.stack=o.stack,e}return new wc("TEMPLATE_INIT_ERROR","$t:core.errors.template_init_error",{showStack:!1,icon:"fa-circle-exclamation",recoverable:!0})}const W_=ut("ErrorDisplay");class vf{static render(e,n){const a=document.getElementById(e);if(!a){W_.error(`Container #${e} not found`);return}const i=n.debug&&n.showStack&&n.stackTrace,l=window.matchMedia("(prefers-color-scheme: dark)").matches,c={container:` display: flex; align-items: center; justify-content: center; @@ -224,7 +224,7 @@ Error generating stack: `+u.message+` `:""} - `}static renderFromError(e,n,a,i=!1,l,c,d){let f,g;if(n.userMessageKey.startsWith("$t:"))if(c&&l)try{f=c.translate(n.userMessageKey,l)}catch{f=n.userMessageKey.replace("$t:","")}else f=n.userMessageKey.replace("$t:","");else f=n.userMessageKey;if(d){const m=d;m.details?.apiMessage?g=m.details.apiMessage:m.response?.data?.message&&(g=m.response.data.message)}this.render(e,{title:a,message:f,detailMessage:g,icon:n.icon,showStack:n.showStack,stackTrace:n.stack,showReloadButton:n.recoverable,debug:i})}static escapeHtml(e){if(!e)return"";const n=document.createElement("div");return n.textContent=e,n.innerHTML}}const ps=ht("Router");class dA{constructor(e){$(this,"routes",[]);$(this,"templateIdentifier");$(this,"eventHandlers",new Map);$(this,"pendingNavigation",null);$(this,"isNavigating",!1);$(this,"handlePopState",()=>{this.navigateToCurrentPath()});this.templateIdentifier=e,this.initPopstateListener()}initPopstateListener(){window.addEventListener("popstate",this.handlePopState)}on(e,n){this.eventHandlers.has(e)||this.eventHandlers.set(e,[]),this.eventHandlers.get(e).push(n)}async emit(e,...n){const a=this.eventHandlers.get(e);a&&await Promise.all(a.map(i=>i(...n)))}async loadRoutes(e){try{const n=e!==void 0&&e>0?e:null,a=await fetch(Nr(`/api/templates/${this.templateIdentifier}/routes`,"json",n));if(!a.ok)throw new Error(`Failed to load routes: ${a.statusText}`);const i=await a.json();if(!i.success)throw new Error("Failed to load routes from API");if(i.data&&Array.isArray(i.data.routes))this.routes=i.data.routes,ps.log(`Loaded ${this.routes.length} routes${e?` (v=${e})`:""}`);else throw new Error("Invalid routes data format")}catch(n){throw ps.error("Error loading routes:",n),n}}setRoutes(e){this.routes=e,ps.log(`Set ${this.routes.length} routes`)}match(e){const n=this.normalizePathname(e);for(const a of this.routes){const i=this.matchPattern(a.path,n);if(i!==null)return{route:a,params:i}}return null}normalizePathname(e){return e.length>1&&e.endsWith("/")?e.replace(/\/+$/,"")||"/":e}matchPattern(e,n){const a=[];let i=e.replace(/:([^/]+)/g,(f,g)=>(a.push(g),"([^/]+)"));i.startsWith("*/")&&(i="(?:/[^/]+)?"+i.slice(1)),i=i.replace(/\//g,"\\/");const l=new RegExp(`^${i}$`),c=n.match(l);if(!c)return null;const d={};return a.forEach((f,g)=>{d[f]=c[g+1]}),d}getRoutes(){return[...this.routes]}getAuthType(e,n){return e.auth_type?e.auth_type:n.startsWith("/admin")?"admin":"user"}async navigateToCurrentPath(){const e=window.location.pathname,n=window.location.search,a=this.match(e);if(!a){ps.warn(`No route matched for path: ${e}`),this.emit("routeNotFound",e);return}if(a.route.redirect){ps.log(`Redirecting from ${e} to ${a.route.redirect}`),this.navigate(a.route.redirect);return}if(a.route.auth_required){const c=this.getAuthType(a.route,e),d=br.getInstance();let f=d.isAuthenticated()&&d.getAuthType()===c;if(f||(f=await d.checkAuth(c)),!f){const g=d.getLoginRedirectUrl(c,e+n);ps.log(`Not authenticated, redirecting to: ${g}`),window.location.href=g;return}}const i=new URLSearchParams(n),l={};for(const c of i.keys()){if(c in l)continue;const d=i.getAll(c);d.length>1?l[c]=d:d.length===1&&(c.endsWith("[]")?l[c]=d:l[c]=d[0])}await this.emit("routeChange",{path:e,layout:a.route.layout,endpoint:a.route.endpoint,params:{...a.route.params||{},...a.params},query:l,auth_required:a.route.auth_required,auth_type:a.route.auth_type,meta:a.route.meta})}navigate(e){if(this.isNavigating){this.pendingNavigation=e;return}this.executeNavigation(e)}async executeNavigation(e){this.isNavigating=!0,this.pendingNavigation=null;try{window.history.pushState({},"",e),await this.navigateToCurrentPath()}finally{if(this.isNavigating=!1,this.pendingNavigation){const n=this.pendingNavigation;this.executeNavigation(n)}}}}const fA=/^\/admin\/layout-editor\/([^/?#]+)\/?$/;function Ey(o){const e=fA.exec(o);return e?{templateIdentifier:e[1]}:null}const Gt=ht("ErrorPageHandler");class hA{constructor(e){$(this,"templateId");$(this,"layoutLoader");$(this,"locale");$(this,"debug");$(this,"renderFunction");$(this,"dataSourceManager");$(this,"globalState");$(this,"errorConfig",null);$(this,"configLoaded",!1);this.templateId=e.templateId,this.layoutLoader=e.layoutLoader,this.locale=e.locale,this.debug=e.debug,this.renderFunction=e.renderFunction,this.dataSourceManager=e.dataSourceManager,this.globalState=e.globalState||{}}async loadConfig(){if(this.configLoaded)return this.errorConfig;try{const e=await fetch(Nr(`/api/templates/${this.templateId}/config`,"json"));if(!e.ok)return this.debug&&Gt.warn("Failed to load template config:",e.statusText),this.configLoaded=!0,null;const n=await e.json();if(!n.success||!n.data)return this.debug&&Gt.warn("Invalid template config response"),this.configLoaded=!0,null;const a=n.data;return!a.error_config||!a.error_config.layouts?(this.debug&&Gt.warn("No error_config found in template.json"),this.configLoaded=!0,null):(this.errorConfig=a.error_config,this.configLoaded=!0,this.debug&&Gt.log("Error config loaded:",this.errorConfig),this.errorConfig)}catch(e){return Gt.error("Failed to load error config:",e),this.configLoaded=!0,null}}async renderError(e,n="app"){try{const a=await this.loadConfig();if(!a)return this.debug&&Gt.warn("No error config available, cannot render error page"),!1;const i=a.layouts[e]||a.layouts[String(e)];if(!i)return this.debug&&Gt.warn(`No layout defined for error code: ${e}`),!1;this.debug&&Gt.log(`Loading error layout: ${i} for code: ${e}`);const l=await this.layoutLoader.loadLayout(this.templateId,i);if(!l)return this.debug&&Gt.error(`Failed to load error layout: ${i}`),!1;this.debug&&Gt.log("Error layout loaded:",l);let c={};const d=l.data_sources||[];if(d.length>0){this.debug&&Gt.log("Fetching data sources:",d.map(v=>v.id));const m=d.filter(v=>v.loading_strategy==="blocking"),y=d.filter(v=>!v.loading_strategy||v.loading_strategy==="progressive"),S=[...m,...y];if(S.length>0)try{c=await this.dataSourceManager.fetchDataSources(S,{},new URLSearchParams),this.debug&&Gt.log("Data sources fetched:",Object.keys(c))}catch(v){Gt.error("Failed to fetch data sources:",v)}}const f={};d.length>0&&(this.processInitOptions(d,c,f),this.debug&&Gt.log("initOptions processed:",{globalKeys:Object.keys(this.globalState),localKeys:Object.keys(f)}));const g={...c,errorCode:e,_global:{...this.globalState},_local:{...f}};return await this.renderFunction({containerId:n,layoutJson:l,dataContext:g,translationContext:{templateId:this.templateId,locale:this.locale}}),this.debug&&Gt.log(`Error page ${e} rendered successfully`),!0}catch(a){return Gt.error(`Failed to render error page ${e}:`,a),!1}}processInitOptions(e,n,a){for(const i of e){const l=n[i.id];if(!l)continue;const c=l?.data??l;if(i.initLocal){if(typeof i.initLocal=="string")a[i.initLocal]=c,Gt.log(`initLocal: ${i.id}.data -> _local.${i.initLocal}`);else if(typeof i.initLocal=="object"&&"key"in i.initLocal){const{key:d,path:f}=i.initLocal;a[d]=f?this.getValueByPath(c,f):c,Gt.log(`initLocal: ${i.id}.data${f?"."+f:""} -> _local.${d}`)}}if(i.initGlobal){const d=Array.isArray(i.initGlobal)?i.initGlobal:[i.initGlobal];for(const f of d)if(typeof f=="string")this.globalState[f]=c,Gt.log(`initGlobal: ${i.id}.data -> _global.${f}`);else if(typeof f=="object"&&f!==null&&"key"in f){const{key:g,path:m}=f;this.globalState[g]=m?this.getValueByPath(c,m):c,Gt.log(`initGlobal: ${i.id}.data${m?"."+m:""} -> _global.${g}`)}}}}getValueByPath(e,n){return n.split(".").reduce((a,i)=>a?.[i],e)}updateGlobalState(e){this.globalState={...this.globalState,...e}}updateLocale(e){this.locale=e,this.debug&&Gt.log("Locale updated:",e)}isConfigLoaded(){return this.configLoaded}async hasErrorLayout(e){const n=await this.loadConfig();return n?!!(n.layouts[e]||n.layouts[String(e)]):!1}clearConfigCache(){this.errorConfig=null,this.configLoaded=!1,this.debug&&Gt.log("Config cache cleared")}}const jt=ht("ModuleAssetLoader");class gA{constructor(){$(this,"loadedAssets",new Map);$(this,"loadingPromises",new Map);$(this,"failedJsAssets",new Set)}hasFailedJsAssets(){return this.failedJsAssets.size>0}getFailedJsAssets(){return[...this.failedJsAssets]}async loadActiveExtensionAssets(e){if(!e||e.length===0){jt.log("No module assets to load");return}const n=[...e].sort((d,f)=>d.priority-f.priority);jt.log("Loading module assets:",n.map(d=>d.identifier));const a=n.filter(d=>d.css).map(d=>this.loadCSS(d.identifier,d.css)),i=n.filter(d=>d.js).map(d=>this.loadJS(d.identifier,d.js)),l=await Promise.allSettled([...a,...i]),c=l.filter(d=>d.status==="rejected");if(c.length>0){jt.warn(`Some module assets failed to load (${c.length}/${l.length}); continuing with the rest`,this.getFailedJsAssets());return}jt.log("All module assets loaded successfully")}async loadBundle(e,n,a){const i=[];if(a&&i.push(this.loadBundleCss(e,a)),n&&i.push(this.loadBundleJs(e,n)),i.length===0){jt.log(`No bundle assets to load for: ${e}`);return}await Promise.all(i)}async loadBundleCss(e,n){const a=`ext-bundle-css-${e}`;if(document.getElementById(a)){jt.log(`Bundle CSS already loaded: ${e}`);return}return new Promise(i=>{const l=document.createElement("link");l.rel="stylesheet",l.href=n,l.id=a,l.onload=()=>{jt.log(`Bundle CSS loaded: ${e}`),this.registerLoadedAsset(`bundle-${e}`,{type:"css",element:l}),i()},l.onerror=()=>{jt.warn(`Failed to load bundle CSS: ${e} (${n})`),i()},document.head.appendChild(l)})}async loadBundleJs(e,n){const a=`ext-bundle-js-${e}`;if(document.getElementById(a)){jt.log(`Bundle JS already loaded: ${e}`);return}const i=this.loadingPromises.get(a);if(i)return jt.log(`Bundle JS already loading: ${e}`),i;const l=cp(n,{id:a},{label:`bundle JS: ${e}`}).then(()=>{jt.log(`Bundle JS loaded: ${e}`);const c=document.getElementById(a);c&&this.registerLoadedAsset(`bundle-${e}`,{type:"js",element:c}),this.loadingPromises.delete(a)}).catch(c=>{throw jt.warn(`Failed to load bundle JS: ${e} (${n})`,c),this.failedJsAssets.add(e),this.loadingPromises.delete(a),c});return this.loadingPromises.set(a,l),l}async loadCSS(e,n){const a=`module-css-${e}`;if(document.getElementById(a)){jt.log(`CSS already loaded: ${e}`);return}return new Promise((i,l)=>{const c=document.createElement("link");c.rel="stylesheet",c.href=n,c.id=a,c.onload=()=>{jt.log(`CSS loaded: ${e}`),this.registerLoadedAsset(e,{type:"css",element:c}),i()},c.onerror=()=>{jt.warn(`Failed to load CSS: ${e} (${n})`),i()},document.head.appendChild(c)})}async loadJS(e,n){const a=`module-js-${e}`;if(document.getElementById(a)){jt.log(`JS already loaded: ${e}`);return}const i=this.loadingPromises.get(e);if(i)return jt.log(`JS already loading: ${e}`),i;const l=cp(n,{id:a},{label:`JS: ${e}`}).then(()=>{jt.log(`JS loaded: ${e}`);const c=document.getElementById(a);c&&this.registerLoadedAsset(e,{type:"js",element:c}),this.loadingPromises.delete(e)}).catch(c=>{throw jt.warn(`Failed to load JS: ${e} (${n})`,c),this.failedJsAssets.add(e),this.loadingPromises.delete(e),c});return this.loadingPromises.set(e,l),l}registerLoadedAsset(e,n){const a=this.loadedAssets.get(e)||[];a.push(n),this.loadedAssets.set(e,a)}unloadExtensionAsset(e){const n=this.loadedAssets.get(e);if(!n||n.length===0){jt.log(`No assets to unload for: ${e}`);return}n.forEach(a=>{a.element.parentNode&&(a.element.parentNode.removeChild(a.element),jt.log(`${a.type.toUpperCase()} unloaded: ${e}`))}),this.loadedAssets.delete(e),jt.log(`All assets unloaded for: ${e}`)}unloadAllAssets(){Array.from(this.loadedAssets.keys()).forEach(n=>{this.unloadExtensionAsset(n)}),jt.log("All module assets unloaded")}isLoaded(e){return this.loadedAssets.has(e)}getLoadedModules(){return Array.from(this.loadedAssets.keys())}}let Ef=null;function _f(){return Ef||(Ef=new gA),Ef}function pA(){if(typeof window>"u")return[];const o=window.G7Config;if(!o?.moduleAssets)return[];const e=[];for(const[n,a]of Object.entries(o.moduleAssets)){const i=a;e.push({identifier:n,js:i.js?Ea(i.js):i.js,css:i.css?Ea(i.css):i.css,priority:i.priority,external:i.external})}return e}function mA(){if(typeof window>"u")return null;const o=window.G7Config;if(!o?.bundleUrls)return null;const e=o.bundleUrls;return{moduleJs:e.moduleJs?Ea(e.moduleJs):e.moduleJs,moduleCss:e.moduleCss?Ea(e.moduleCss):e.moduleCss,pluginJs:e.pluginJs?Ea(e.pluginJs):e.pluginJs,pluginCss:e.pluginCss?Ea(e.pluginCss):e.pluginCss}}function yA(){if(typeof window>"u")return[];const o=window.G7Config;if(!o?.pluginAssets)return[];const e=[];for(const[n,a]of Object.entries(o.pluginAssets)){const i=a;e.push({identifier:n,js:i.js?Ea(i.js):i.js,css:i.css?Ea(i.css):i.css,priority:i.priority,external:i.external})}return e}const Af=ht("SystemBannerManager");class Ac{static show(e){this.banners.set(e.id,e),this.render(),Af.log(`Banner shown: ${e.id}`)}static hide(e){this.banners.delete(e)&&(this.render(),Af.log(`Banner hidden: ${e}`))}static hideAll(){this.banners.clear(),this.render(),Af.log("All banners hidden")}static detectLocale(){try{const n=window.G7Core;if(n?.locale?.current)return n.locale.current()}catch{}return(navigator.language||"ko").split("-")[0]}static resolveMessage(e,n){return typeof e=="string"?e:e[n]||e.en||e.ko||Object.values(e)[0]||""}static render(){if(typeof document>"u")return;let e=document.getElementById(this.containerId);if(this.banners.size===0){e&&(e.remove(),this.adjustAppPadding(0));return}e||(e=document.createElement("div"),e.id=this.containerId,e.style.cssText="position:fixed;top:0;left:0;right:0;z-index:99999;",document.body.prepend(e));const n=Array.from(this.banners.values()).sort((i,l)=>(i.order??0)-(l.order??0)),a=this.detectLocale();e.innerHTML=n.map(i=>{const l=this.resolveMessage(i.message,a),c=i.background||"#f59e0b",d=i.color||"white";return`
${l}
`}).join(""),requestAnimationFrame(()=>{e&&this.adjustAppPadding(e.offsetHeight)})}static adjustAppPadding(e){const n=document.getElementById("app");n&&(n.style.paddingTop=e>0?`${e}px`:"")}}$(Ac,"banners",new Map),$(Ac,"containerId","g7-system-banners");const B=ht("TemplateApp");function _y(o){const e={};for(const n of o.keys()){if(n in e)continue;const a=o.getAll(n);a.length>1?e[n]=a:a.length===1&&(n.endsWith("[]")?e[n]=a:e[n]=a[0])}return e}const xr=class xr{constructor(e){$(this,"router",null);$(this,"layoutLoader",null);$(this,"errorPageHandler",null);$(this,"config");$(this,"globalState");$(this,"globalStateListeners",new Set);$(this,"currentRouteChangeId",0);$(this,"currentDataSources",[]);$(this,"currentRawDataSources",[]);$(this,"currentRouteParams",{});$(this,"currentQueryParams",new URLSearchParams);$(this,"currentFetchedData",{});$(this,"currentLayoutName","");$(this,"templateErrorHandling",null);$(this,"currentWebSocketSubscriptions",[]);$(this,"extensionCacheVersion",0);$(this,"currentGlobalHeaders",[]);$(this,"transitionOverlayEl",null);$(this,"skeletonOverlayRoot",null);$(this,"skeletonOverlayContainer",null);$(this,"_spinnerState",null);$(this,"modalDataSources",new Map);this.globalState={sidebarOpen:!1},this.loadG7Config(),this.migrateLocaleStorage();const n=this.loadLocaleFromStorage(),a=n||e.locale||"ko";this.config={...e,locale:a},!n&&e.locale&&this.saveLocaleToStorage(e.locale)}loadG7Config(){if(typeof window<"u"&&window.G7Config){const e=window.G7Config;e.settings&&(this.globalState.settings=e.settings,B.log("Loaded settings from G7Config:",Object.keys(e.settings)),e.settings.upload&&(this.globalState.uploadSettings=e.settings.upload)),e.plugins&&(this.globalState.plugins=e.plugins,B.log("Loaded plugin settings from G7Config:",Object.keys(e.plugins))),e.modules&&(this.globalState.modules=e.modules,B.log("Loaded module settings from G7Config:",Object.keys(e.modules))),e.appConfig&&(this.globalState.appConfig=e.appConfig,B.log("Loaded appConfig from G7Config:",Object.keys(e.appConfig)))}}resolveRouteExpressions(e){const n=new Dn,a={_global:this.globalState};return e.map(i=>{const l={...i};if(l.path&&l.path.includes("{{")){const c=l.path;l.path=n.resolveBindings(l.path,a),l.path=l.path.replace(/\/\/+/g,"/")||"/",l.path.includes("{{")&&(B.warn("Route expression resolution failed, using fallback:",c),l.path=c.replace(/\{\{[^}]+\}\}/g,"").replace(/\/\/+/g,"/")||"/")}if(l.redirect&&l.redirect.includes("{{")){const c=l.redirect;l.redirect=n.resolveBindings(l.redirect,a),l.redirect=l.redirect.replace(/\/\/+/g,"/")||"/",l.redirect.includes("{{")&&(B.warn("Route redirect expression resolution failed:",c),l.redirect=c.replace(/\{\{[^}]+\}\}/g,"").replace(/\/\/+/g,"/")||"/")}return l})}migrateLocaleStorage(){const e=["locale","g7_template_locale"];for(const n of e)try{const a=localStorage.getItem(n);a&&a!==localStorage.getItem(xr.LOCALE_STORAGE_KEY)&&(localStorage.setItem(xr.LOCALE_STORAGE_KEY,a),B.log(`Migrated locale from '${n}' to '${xr.LOCALE_STORAGE_KEY}'`)),localStorage.removeItem(n)}catch(a){B.warn(`Failed to migrate locale key '${n}':`,a)}}async init(){try{to.getInstance().setDebug(this.config.debug),B.log("Initializing with config:",this.config),nw();const e=mr.getInstance(),n=br.getInstance(),a=this.loadCacheVersionFromStorage()||0,[i,l,c,d,f]=await Promise.all([No({templateId:this.config.templateId,templateType:this.config.templateType,locale:this.config.locale,debug:this.config.debug,cacheVersion:a}),e.loadComponents(this.config.templateId,this.config.templateType),Il(Nr(`/api/templates/${this.config.templateId}/routes`,"json",a>0?a:null),{label:"routes.json"}).then(A=>{if(!A.ok)throw new Error(`Failed to load routes: ${A.statusText}`);return A.json()}).then(A=>{if(!A.success)throw new Error("Failed to load routes from API");return A.data}).catch(A=>{throw B.error("Error loading routes:",A),A}),n.preloadAuth(this.config.templateType==="admin"?"admin":"user"),fetch(Nr(`/api/templates/${this.config.templateId}/config`,"json")).then(A=>A.ok?A.json():null).then(A=>!A?.success||!A?.data?null:A.data).catch(A=>(B.warn("Error loading template config:",A),null))]);if(B.log("Template Engine initialized"),B.log("ComponentRegistry loaded"),B.log("Routes data loaded"),B.log("User info preloaded"),B.log("Template config loaded:",f),f?.cache_version!==void 0){const A=this.loadCacheVersionFromStorage();if(this.extensionCacheVersion=f.cache_version,this.saveCacheVersionToStorage(this.extensionCacheVersion),B.log("Extension cache version:",this.extensionCacheVersion),A!==null&&A!==this.extensionCacheVersion){B.log("Cache version changed, reloading routes...");const x=await Il(Nr(`/api/templates/${this.config.templateId}/routes`,"json",this.extensionCacheVersion),{label:"routes.json (reload)"}).then(O=>{if(!O.ok)throw new Error(`Failed to reload routes: ${O.statusText}`);return O.json()}).then(O=>{if(!O.success)throw new Error("Failed to reload routes from API");return O.data});Array.isArray(x.routes)&&(x.routes=this.resolveRouteExpressions(x.routes),Object.assign(c,x),B.log("Routes reloaded with new cache version"));try{const{TranslationEngine:O}=await Promise.resolve().then(()=>gp),M=O.getInstance();M.setCacheVersion(this.extensionCacheVersion);const T=this.config.locale||"ko",D="en";await M.loadTranslations(this.config.templateId,T,"/api",!0),T!==D&&await M.loadTranslations(this.config.templateId,D,"/api",!0),B.log("Translations reloaded with new cache version")}catch(O){B.error("Failed to reload translations:",O)}}}f?.errorHandling&&(this.templateErrorHandling=f.errorHandling,Sr().setTemplateConfig(this.templateErrorHandling),B.log("Template errorHandling registered:",this.templateErrorHandling)),n.on("logout",()=>{B.log("User logged out")}),n.on("authStateChange",A=>{B.log("Auth state changed:",A)}),B.log("AuthManager event handlers registered");const g=Hr(),m=this.config.templateType==="admin"?"admin":"user",y=n.getConfig(m);g.setOnUnauthorized(()=>{B.log("Unauthorized - redirecting to login page"),g.removeToken();const A=window.location.pathname+window.location.search,x=n.getLoginRedirectUrl(m,A,"session_expired");window.location.href=x}),B.log("ApiClient onUnauthorized callback registered"),this.layoutLoader=new mf(e),this.extensionCacheVersion>0&&this.layoutLoader.setCacheVersion(this.extensionCacheVersion),B.log("LayoutLoader initialized:",this.layoutLoader);const S=new _r({onUnauthorized:()=>{B.warn("Unauthorized request in error page")}});if(this.errorPageHandler=new hA({templateId:this.config.templateId,layoutLoader:this.layoutLoader,locale:this.config.locale,debug:this.config.debug,renderFunction:ms,dataSourceManager:S,globalState:this.globalState}),B.log("ErrorPageHandler initialized with DataSourceManager"),await this.handleServerError()){B.log("Server error handled, skipping normal initialization");return}if(this.router=new dA(this.config.templateId),Array.isArray(c.routes)){const A=this.resolveRouteExpressions(c.routes);this.router.setRoutes(A)}else throw new Error("Invalid routes data format");B.log("Router initialized:",this.router),B.log("Routes set:",this.router.getRoutes());const{getActionDispatcher:v}=await Promise.resolve().then(()=>Pr),_=v();if(_&&(_.setDefaultContext({navigate:A=>this.router?.navigate(A)}),_.setGlobalStateUpdater((A,x)=>this.setGlobalState(A,x)),B.log("Navigate function and setGlobalState injected to ActionDispatcher")),await this.loadExtensionAssets(),this.reinitializeTemplateHandlers(),this.router.on("routeChange",A=>this.handleRouteChange(A)),this.router.on("routeNotFound",A=>this.handleRouteNotFound(A)),typeof window<"u"&&Ey(window.location.pathname)){B.log("Layout editor mode detected — skipping router match"),await ms({containerId:"app",layoutJson:{components:[]},dataContext:{},translationContext:{templateId:this.config.templateId,locale:this.config.locale}}),B.log("Template App initialized in layout editor mode");return}this.router.navigateToCurrentPath(),B.log("Template App initialized successfully")}catch(e){B.error("Initialization failed:",e),this.showInitError(e)}}async loadExtensionAssets(){try{const e=_f(),n=mA();if(!n){await this.loadExtensionAssetsIndividually();return}await e.loadBundle("module",n.moduleJs,n.moduleCss),await e.loadBundle("plugin",n.pluginJs,n.pluginCss),B.log("Extension bundle assets loaded successfully")}catch(e){B.warn("Failed to load extension assets:",e)}}async loadExtensionAssetsIndividually(){const e=_f(),n=pA();n.length>0&&(B.log("Loading module assets (individual fallback):",n.map(i=>i.identifier)),await e.loadActiveExtensionAssets(n));const a=yA();a.length>0&&(B.log("Loading plugin assets (individual fallback):",a.map(i=>i.identifier)),await e.loadActiveExtensionAssets(a))}async handleRouteChange(e){const n=++this.currentRouteChangeId;window.__g7ForcedLocalFields=void 0,window.__g7ActionContext=void 0,window.__g7PendingLocalState=void 0,window.__g7LastSetLocalSnapshot=void 0,window.__g7SetLocalOverrideKeys=void 0,window.__g7SequenceLocalSync=void 0,window.__g7AutoBindingPaths=new Map;try{if(B.log("Route changed:",e,"requestId:",n),!this.layoutLoader)throw new Error("LayoutLoader is not initialized");if(!e.layout)throw new Error("Route layout is not defined");let a=e.layout;const i=a==="__preview__";i&&e.params?.token&&(a=`__preview__/${e.params.token}`);const l=await this.layoutLoader.loadLayout(this.config.templateId,a);if(n!==this.currentRouteChangeId){B.log("Route change cancelled (newer request exists):",n);return}B.log("Layout loaded:",l);const c=Sr();l.errorHandling?(c.setLayoutConfig(l.errorHandling),B.log("Layout errorHandling registered:",l.errorHandling)):c.clearLayoutConfig();const d=l.data_sources||[],f=e.query||{},g=new URLSearchParams;for(const[te,pe]of Object.entries(f))if(Array.isArray(pe))for(const N of pe)g.append(te,N);else g.set(te,pe);const m={route:e.params||{},query:f,_global:this.globalState};l.scripts&&Array.isArray(l.scripts)&&await this.loadLayoutScripts(l.scripts,m);const{DataSourceManager:y,getActionDispatcher:S}=await Promise.resolve().then(()=>Pr),v=new y;if(i){const te=S();te&&te.setPreviewMode(!0),this.setGlobalState({__isPreview:!0}),Ac.show({id:"preview-mode",message:{ko:"⚠ 미리보기 모드 — 페이지 이동이 비활성화됩니다",en:"⚠ Preview Mode — Navigation is disabled"},background:"linear-gradient(90deg, #f59e0b, #d97706)",color:"white"}),B.log("Preview mode activated")}else{const te=S();te?.isPreviewMode()&&(te.setPreviewMode(!1),Ac.hide("preview-mode"))}if(this.currentGlobalHeaders=l.globalHeaders||[],this.currentGlobalHeaders.length>0){v.setGlobalHeaders(this.currentGlobalHeaders);const te=S();te&&te.setGlobalHeaders(this.currentGlobalHeaders),B.log("globalHeaders set:",this.currentGlobalHeaders.map(pe=>pe.pattern))}if(l.named_actions&&Object.keys(l.named_actions).length>0){const te=S();te&&te.setNamedActions(l.named_actions)}const _=v.filterByCondition(d,m);d.length!==_.length&&B.log("Data sources filtered by condition:",{before:d.map(te=>te.id),after:_.map(te=>te.id)});const A=_.filter(te=>te.loading_strategy==="blocking"),x=_.filter(te=>(te.loading_strategy||"progressive")!=="blocking"&&te.type!=="websocket"),O=Array.isArray(l.transition_overlay?.wait_for)?l.transition_overlay.wait_for:[],M=O.length>0&&_.some(te=>O.includes(te.id)&&te.type!=="websocket"&&(te.loading_strategy||"progressive")!=="background");if((A.length>0||M)&&l.transition_overlay){const te=typeof l.transition_overlay=="boolean"?{enabled:l.transition_overlay,style:"opaque"}:l.transition_overlay;te.enabled&&te.style==="skeleton"&&te.skeleton?.component&&te.target?this.renderSkeletonOverlay(te.target,te.skeleton,l,te.fallback_target):te.enabled&&te.style==="spinner"&&te.target&&this.renderSpinnerOverlay(te.target,te.spinner,te.fallback_target)}let T={},D={},z={},P={};this.currentWebSocketSubscriptions.length>0&&(B.log("Unsubscribing previous WebSocket subscriptions:",this.currentWebSocketSubscriptions),v.unsubscribeWebSockets(this.currentWebSocketSubscriptions),this.currentWebSocketSubscriptions=[]),this.currentDataSources=_,this.currentRawDataSources=d,this.currentRouteParams=e.params||{},this.currentQueryParams=g,B.log(`handleRouteChange #${n} - queryObject:`,f),B.log(`handleRouteChange #${n} - queryParams:`,g.toString()),A.length>0&&(B.log("Fetching blocking data sources:",A.map(pe=>pe.id)),(await v.fetchDataSourcesWithResults(A,e.params||{},g)).forEach(pe=>{if(pe.state==="success"&&pe.data!==void 0)T[pe.id]=pe.data;else if(pe.state==="error"&&pe.error){const N=pe.error,R=N.response?.data?.message;D[pe.id]={message:R||pe.error.message,status:N.response?.status||N.status}}}),this.processInitOptions(A,T,z,P),this.currentFetchedData={...T},B.log("Blocking data loaded:",Object.keys(T)),Object.keys(D).length>0&&B.log("Data source errors:",D),Object.keys(z).length>0&&B.log("Local state init (blocking):",Object.keys(z)),Object.keys(P).length>0&&B.log("Isolated state init (blocking):",Object.keys(P)));const q=x.length>0;if(q&&(jr.setPending(!0),B.log("Transition started before rendering")),n!==this.currentRouteChangeId){B.log("Route change cancelled after blocking data (newer request exists):",n);return}const{getState:W}=await Promise.resolve().then(()=>Pr),he=W().currentDataContext||{},Ee=x.map(te=>te.id),be={};if(q){Ee.forEach(pe=>{he[pe]!==void 0?be[pe]=he[pe]:be[pe]=void 0});const te=Object.keys(be).filter(pe=>be[pe]!==void 0);te.length>0&&B.log("Preserving previous progressive data:",te),B.log("Progressive data sources initialized:",Ee)}const Ue=l.defines||{},Fe={...be,...e.params,...T,route:{...e.params||{},path:e.path},query:f,_global:{...this.globalState},_globalSetState:te=>this.setGlobalState(te),_dataSourceErrors:Object.keys(D).length>0?D:void 0,_localInit:Object.keys(z).length>0?z:void 0,_isolatedInit:Object.keys(P).length>0?P:void 0,_defines:Object.keys(Ue).length>0?Ue:void 0};if(l.computed&&Object.keys(l.computed).length>0){const te=this.calculateComputed(l.computed,Fe);Object.keys(te).length>0&&(Fe._computed=te,this.globalState._computed=te,B.log("Computed values calculated:",Object.keys(te))),Fe._computedDefinitions=l.computed}const Ge=l.layout_name||e.layout;this.currentLayoutName!==""&&this.currentLayoutName!==Ge&&(B.log("_local reset due to layout change:",{from:this.currentLayoutName,to:Ge}),this.globalState._local={},TC()),this.currentLayoutName=Ge;const J=l.initLocal||l.state;if(J&&Object.keys(J).length>0){this.globalState._local||(this.globalState._local={});for(const[te,pe]of Object.entries(J))this.globalState._local[te]===void 0&&(this.globalState._local[te]=JSON.parse(JSON.stringify(pe)));Fe._local={...this.globalState._local},B.log("initLocal applied to _local:",Object.keys(J))}if(l.initGlobal&&Object.keys(l.initGlobal).length>0){for(const[te,pe]of Object.entries(l.initGlobal))this.globalState[te]===void 0&&(this.globalState[te]=JSON.parse(JSON.stringify(pe)));B.log("initGlobal applied to _global:",Object.keys(l.initGlobal))}if(l.initIsolated&&Object.keys(l.initIsolated).length>0){for(const[te,pe]of Object.entries(l.initIsolated))P[te]===void 0&&(P[te]=JSON.parse(JSON.stringify(pe)));B.log("initIsolated applied:",Object.keys(l.initIsolated))}Fe._global={...this.globalState,layoutWarnings:l.warnings||[]};const ue=l.initActions||l.init_actions;if(ue&&ue.length>0){await this.executeInitActions(ue,Fe),B.log("initActions executed before render");const te=this.globalState;if(te._local){if(Object.keys(z).length>0){for(const[pe,N]of Object.entries(z))te._local[pe]!==void 0?te._local[pe]=this.deepMerge(te._local[pe],N):te._local[pe]=N;B.log("localInit merged into _local after initActions:",Object.keys(z))}Fe._local=te._local,B.log("_local merged into dataContext:",te._local)}if(Fe._global={...this.globalState,layoutWarnings:l.warnings||[]},B.log("_global merged into dataContext after initActions"),l.computed&&Object.keys(l.computed).length>0){const pe=this.calculateComputed(l.computed,Fe);Object.keys(pe).length>0&&(Fe._computed=pe,this.globalState._computed=pe,B.log("Computed values recalculated after init_actions:",Object.keys(pe)))}if(Object.keys(this.currentFetchedData).length>0){for(const[pe,N]of Object.entries(this.currentFetchedData))Fe[pe]=N;B.log("Fetched data sources merged into dataContext after initActions:",Object.keys(this.currentFetchedData))}}if(l.transition_overlay&&!this.skeletonOverlayContainer&&this.showTransitionOverlay(l.transition_overlay,l),await ms({containerId:"app",layoutJson:l,dataContext:Fe,translationContext:{templateId:this.config.templateId,locale:this.config.locale}}),B.log("Initial render complete with blocking data"),this.reattachSpinnerOverlay(),x.length>0){if(n!==this.currentRouteChangeId){B.log("Route change cancelled before progressive fetch (newer request exists):",n),jr.setPending(!1),this.hideTransitionOverlay();return}B.log("Fetching progressive/background data sources:",x.map(N=>N.id));const{updateTemplateData:te,getState:pe}=await Promise.resolve().then(()=>Pr);try{const N=x.map(async R=>{try{const fe=(await v.fetchDataSourcesWithResults([R],e.params||{},g,this.globalState))[0];if(!fe){B.log(`Data source ${R.id} skipped (no fetch result)`);return}if(n!==this.currentRouteChangeId){B.log(`Data source ${R.id} fetch cancelled (newer request exists)`);return}if(fe.state==="success"&&fe.data!==void 0){B.log(`Progressive data source loaded: ${R.id}`),this.currentFetchedData[R.id]=fe.data;const xe={};this.processInitOptions([R],{[R.id]:fe.data},xe);const ee=pe();if(ee.bindingEngine){const Be=[R.id];R.initGlobal&&Be.push("_global"),R.initLocal&&Be.push("_local"),ee.bindingEngine.invalidateCacheByKeys(Be)}const _e={[R.id]:fe.data};Object.keys(xe).length>0&&(_e._localInit=xe,Object.assign(z,xe)),R.initGlobal&&(_e._global={...this.globalState}),te(_e)}else if(fe.state==="error"&&fe.error){const xe=fe.error,ee=xe.response?.data?.message;D[R.id]={message:ee||fe.error.message,status:xe.response?.status||xe.status},B.log(`Progressive data source error: ${R.id}`,D[R.id]),te({[R.id]:null,_dataSourceErrors:{...D}})}}catch(de){B.error(`Failed to fetch data source: ${R.id}`,de)}});if(await Promise.all(N),n!==this.currentRouteChangeId){B.log("Route change cancelled after progressive fetch (newer request exists):",n);return}B.log("All progressive data sources loaded")}finally{jr.setPending(!1),this.hideTransitionOverlay()}}else this.hideTransitionOverlay();const Me=_.filter(te=>te.type==="websocket");if(Me.length>0){B.log("Subscribing WebSocket data sources:",Me.map(R=>R.id));const{updateTemplateData:te,getState:pe}=await Promise.resolve().then(()=>Pr),N={...this.currentFetchedData,...e.params,route:{...e.params||{},path:e.path},query:f,_global:{...this.globalState}};B.log("WebSocket binding context keys:",Object.keys(N)),this.currentWebSocketSubscriptions=v.subscribeWebSockets(_,(R,de)=>{B.log(`WebSocket data received for: ${R}`,de),this.currentFetchedData[R]=de;const fe=_.find(Be=>Be.id===R),xe=pe();if(xe.bindingEngine){const Be=[R];fe?.initGlobal&&Be.push("_global"),fe?.initLocal&&Be.push("_local"),xe.bindingEngine.invalidateCacheByKeys(Be)}te({[R]:de});const _e=_.find(Be=>Be.type==="websocket"&&(Be.target_source||Be.id)===R)?.onReceive;Array.isArray(_e)&&_e.length>0&&window.G7Core?.dispatch&&(async()=>{for(const ke of _e)try{const Ie=this.getActionDispatcher?.();Ie&&await Ie.dispatchAction(ke,{navigate:this.getRouter?.()?(Ct,ot)=>this.getRouter().navigate(Ct,ot):void 0,setState:Ct=>this.setGlobalState(Ct),state:this.globalState,data:{...this.globalState,$args:[de],$event:de},_isDispatchFallbackContext:!0})}catch(Ie){B.error(`WebSocket onReceive action failed for ${R}:`,Ie)}})()},N),B.log("WebSocket subscriptions established:",this.currentWebSocketSubscriptions)}B.log("Layout rendered successfully")}catch(a){B.error("Route change handling failed:",a),this.hideTransitionOverlay(),this.showRouteError(a)}}processInitOptions(e,n,a,i){e.some(c=>c.refetchOnMount===!0&&c.initLocal)&&(a._forceLocalInit=Date.now(),B.log("refetchOnMount detected, forcing local state init")),e.forEach(c=>{const d=n[c.id];if(!d)return;const f=d.data??d;if(c.initLocal){if(typeof c.initLocal=="string"){let g=f;if(c.initLocalDefaults&&typeof c.initLocalDefaults=="object"){const y=this.evaluateDefaults(c.initLocalDefaults,n);g={...y,...f},B.log(`initLocalDefaults applied for ${c.initLocal}:`,Object.keys(y))}const m=this.globalState._local?.[c.initLocal];m!==void 0&&typeof m=="object"&&typeof g=="object"?(a[c.initLocal]=this.deepMerge(m,g),B.log(`initLocal (merged): ${c.id}.data -> _local.${c.initLocal}`)):(a[c.initLocal]=g,B.log(`initLocal: ${c.id}.data -> _local.${c.initLocal}`))}else if(typeof c.initLocal=="object"&&c.initLocal.key){const{key:g,path:m}=c.initLocal;let y=m?this.extractValueByPathOrExpression(f,m,c.id):f;if(c.initLocalDefaults&&typeof c.initLocalDefaults=="object"){const v=this.evaluateDefaults(c.initLocalDefaults,n);y={...v,...y},B.log(`initLocalDefaults applied for ${g}:`,Object.keys(v))}const S=this.globalState._local?.[g];S!==void 0&&typeof S=="object"&&typeof y=="object"?(a[g]=this.deepMerge(S,y),B.log(`initLocal (merged): ${c.id}.data${m?"."+m:""} -> _local.${g}`)):(a[g]=y,B.log(`initLocal: ${c.id}.data${m?"."+m:""} -> _local.${g}`))}else if(typeof c.initLocal=="object"){const g=c.initLocal._merge||"deep",m=this.flattenNestedObjectToMappings(c.initLocal);for(const{targetPath:y,sourcePath:S}of m){const v=this.extractValueByPathOrExpression(f,S,c.id);if(y.includes(".")){const _=y.split(".")[0];a[_]===void 0&&this.globalState._local?.[_]!==void 0&&(a[_]=JSON.parse(JSON.stringify(this.globalState._local[_]))),this.setValueAtPath(a,y,v,g),B.log(`initLocal map (${g}): ${c.id} ${S} -> _local.${y}`)}else{const _=this.globalState._local?.[y];g==="replace"?(a[y]=v,B.log(`initLocal map (replace): ${c.id} ${S} -> _local.${y}`)):g==="shallow"?_!==void 0&&typeof _=="object"&&typeof v=="object"?(a[y]={..._,...v},B.log(`initLocal map (shallow): ${c.id} ${S} -> _local.${y}`)):(a[y]=v,B.log(`initLocal map: ${c.id} ${S} -> _local.${y}`)):_!==void 0&&typeof _=="object"&&typeof v=="object"?(a[y]=this.deepMerge(_,v),B.log(`initLocal map (deep): ${c.id} ${S} -> _local.${y}`)):(a[y]=v,B.log(`initLocal map: ${c.id} ${S} -> _local.${y}`))}}}}if(c.initGlobal)if(typeof c.initGlobal=="object"&&!Array.isArray(c.initGlobal)&&!("key"in c.initGlobal))for(const[m,y]of Object.entries(c.initGlobal)){if(typeof y!="string"){B.warn(`initGlobal map value must be string, got ${typeof y} for key ${m}`);continue}const S=this.extractValueByPathOrExpression(f,y,c.id),v=this.globalState[m];v!==void 0&&typeof v=="object"&&typeof S=="object"?(this.globalState[m]=this.deepMerge(v,S),B.log(`initGlobal map (merged): ${c.id} ${y} -> _global.${m}`)):(this.globalState[m]=S,B.log(`initGlobal map: ${c.id} ${y} -> _global.${m}`))}else{const m=Array.isArray(c.initGlobal)?c.initGlobal:[c.initGlobal];for(const y of m)if(typeof y=="string"){const S=this.globalState[y];S!==void 0&&typeof S=="object"&&typeof f=="object"?(this.globalState[y]=this.deepMerge(S,f),B.log(`initGlobal (merged): ${c.id}.data -> _global.${y}`)):(this.globalState[y]=f,B.log(`initGlobal: ${c.id}.data -> _global.${y}`))}else if(typeof y=="object"&&y.key){const{key:S,path:v}=y,_=v?this.extractValueByPathOrExpression(f,v,c.id):f,A=this.globalState[S];A!==void 0&&typeof A=="object"&&typeof _=="object"?(this.globalState[S]=this.deepMerge(A,_),B.log(`initGlobal (merged): ${c.id}.data${v?"."+v:""} -> _global.${S}`)):(this.globalState[S]=_,B.log(`initGlobal: ${c.id}.data${v?"."+v:""} -> _global.${S}`))}}if(i&&c.initIsolated){if(typeof c.initIsolated=="string"){const g=i[c.initIsolated];g!==void 0&&typeof g=="object"&&typeof f=="object"?(i[c.initIsolated]=this.deepMerge(g,f),B.log(`initIsolated (merged): ${c.id}.data -> _isolated.${c.initIsolated}`)):(i[c.initIsolated]=f,B.log(`initIsolated: ${c.id}.data -> _isolated.${c.initIsolated}`))}else if(typeof c.initIsolated=="object"&&c.initIsolated.key){const{key:g,path:m}=c.initIsolated,y=m?this.extractValueByPathOrExpression(f,m,c.id):f,S=i[g];S!==void 0&&typeof S=="object"&&typeof y=="object"?(i[g]=this.deepMerge(S,y),B.log(`initIsolated (merged): ${c.id}.data${m?"."+m:""} -> _isolated.${g}`)):(i[g]=y,B.log(`initIsolated: ${c.id}.data${m?"."+m:""} -> _isolated.${g}`))}else if(typeof c.initIsolated=="object")for(const[g,m]of Object.entries(c.initIsolated)){if(typeof m!="string"){B.warn(`initIsolated map value must be string, got ${typeof m} for key ${g}`);continue}const y=this.extractValueByPathOrExpression(f,m,c.id),S=i[g];S!==void 0&&typeof S=="object"&&typeof y=="object"?(i[g]=this.deepMerge(S,y),B.log(`initIsolated map (merged): ${c.id} ${m} -> _isolated.${g}`)):(i[g]=y,B.log(`initIsolated map: ${c.id} ${m} -> _isolated.${g}`))}}})}evaluateDefaults(e,n){const a={};for(const[i,l]of Object.entries(e))if(typeof l=="string"&&l.startsWith("{{")&&l.endsWith("}}")){const c=l.slice(2,-2).trim();a[i]=this.evaluateExpression(c,n)}else a[i]=l;return a}evaluateExpression(e,n){try{const a=e.split("??").map(i=>i.trim());for(const i of a){if(/^['"].*['"]$/.test(i))return i.slice(1,-1);if(i==="true")return!0;if(i==="false")return!1;if(/^-?\d+(\.\d+)?$/.test(i))return Number(i);const l=this.getNestedValue(n,i);if(l!=null)return l}return}catch(a){B.warn(`Expression evaluation failed: ${e}`,a);return}}getNestedValue(e,n){const a=n.replace(/\?\./g,".");let i=e;const l=a.split(/\.(?![^\[]*\])/).flatMap(c=>{const d=c.match(/^([^\[]*)((?:\[\d+\])*)$/);if(d){const[,f,g]=d,m=[];f&&m.push(f);const y=g.match(/\[\d+\]/g);return y&&m.push(...y),m}return[c]}).filter(c=>c!=="");for(const c of l){if(i==null)return;if(c.startsWith("[")&&c.endsWith("]")){const d=parseInt(c.slice(1,-1),10);i=i[d]}else i=i[c]}return i}extractValueByPathOrExpression(e,n,a){const i=Aa(n);if(i!==null){const l=new Dn,c={data:e,_global:this.globalState,_local:this.globalState._local||{}};try{const d=Vn(i)?l.evaluatePipeExpression(i,c,{skipCache:!0}):l.evaluateExpression(i,c);return B.log(`initLocal/initGlobal expression evaluated: ${n} -> `,d),d}catch(d){B.warn(`initLocal/initGlobal expression evaluation failed for ${a}:`,n,d);return}}return this.getNestedValue(e,n)}async loadLayoutScripts(e,n){const a=[];for(const i of e){if((i.if!==void 0||i.conditions!==void 0)&&!ns({if:i.if,conditions:i.conditions},n,this.bindingEngine,`script:${i.id}`)){B.log(`Script skipped (condition not met): ${i.id}`);continue}if(document.getElementById(i.id)){B.log(`Script already loaded: ${i.id}`);continue}const c=new Promise((d,f)=>{const g=document.createElement("script");g.src=i.src,g.id=i.id,g.async=i.async??!0,g.onload=()=>{B.log(`Script loaded successfully: ${i.id}`),d()},g.onerror=()=>{B.warn(`Failed to load script: ${i.id} (${i.src})`),d()},document.head.appendChild(g)});a.push(c)}a.length>0&&(await Promise.all(a),B.log(`All scripts loaded: ${e.filter(i=>!document.getElementById(i.id)||a.length>0).map(i=>i.id).join(", ")}`))}evaluateScriptCondition(e,n){try{if(e.startsWith("{{")&&e.endsWith("}}")){const a=e.slice(2,-2).trim();return new Function("ctx",` + `}static renderFromError(e,n,a,i=!1,l,c,d){let f,g;if(n.userMessageKey.startsWith("$t:"))if(c&&l)try{f=c.translate(n.userMessageKey,l)}catch{f=n.userMessageKey.replace("$t:","")}else f=n.userMessageKey.replace("$t:","");else f=n.userMessageKey;if(d){const m=d;m.details?.apiMessage?g=m.details.apiMessage:m.response?.data?.message&&(g=m.response.data.message)}this.render(e,{title:a,message:f,detailMessage:g,icon:n.icon,showStack:n.showStack,stackTrace:n.stack,showReloadButton:n.recoverable,debug:i})}static escapeHtml(e){if(!e)return"";const n=document.createElement("div");return n.textContent=e,n.innerHTML}}const fs=ut("Router");class Y_{constructor(e){$(this,"routes",[]);$(this,"templateIdentifier");$(this,"eventHandlers",new Map);$(this,"pendingNavigation",null);$(this,"isNavigating",!1);$(this,"handlePopState",()=>{this.navigateToCurrentPath()});this.templateIdentifier=e,this.initPopstateListener()}initPopstateListener(){window.addEventListener("popstate",this.handlePopState)}on(e,n){this.eventHandlers.has(e)||this.eventHandlers.set(e,[]),this.eventHandlers.get(e).push(n)}async emit(e,...n){const a=this.eventHandlers.get(e);a&&await Promise.all(a.map(i=>i(...n)))}async loadRoutes(e){try{const n=e!==void 0&&e>0?e:null,a=await fetch($r(`/api/templates/${this.templateIdentifier}/routes`,"json",n));if(!a.ok)throw new Error(`Failed to load routes: ${a.statusText}`);const i=await a.json();if(!i.success)throw new Error("Failed to load routes from API");if(i.data&&Array.isArray(i.data.routes))this.routes=i.data.routes,fs.log(`Loaded ${this.routes.length} routes${e?` (v=${e})`:""}`);else throw new Error("Invalid routes data format")}catch(n){throw fs.error("Error loading routes:",n),n}}setRoutes(e){this.routes=e,fs.log(`Set ${this.routes.length} routes`)}match(e){const n=this.normalizePathname(e);for(const a of this.routes){const i=this.matchPattern(a.path,n);if(i!==null)return{route:a,params:i}}return null}normalizePathname(e){return e.length>1&&e.endsWith("/")?e.replace(/\/+$/,"")||"/":e}matchPattern(e,n){const a=[];let i=e.replace(/:([^/]+)/g,(f,g)=>(a.push(g),"([^/]+)"));i.startsWith("*/")&&(i="(?:/[^/]+)?"+i.slice(1)),i=i.replace(/\//g,"\\/");const l=new RegExp(`^${i}$`),c=n.match(l);if(!c)return null;const d={};return a.forEach((f,g)=>{d[f]=c[g+1]}),d}getRoutes(){return[...this.routes]}getAuthType(e,n){return e.auth_type?e.auth_type:n.startsWith("/admin")?"admin":"user"}async navigateToCurrentPath(){const e=window.location.pathname,n=window.location.search,a=this.match(e);if(!a){fs.warn(`No route matched for path: ${e}`),this.emit("routeNotFound",e);return}if(a.route.redirect){fs.log(`Redirecting from ${e} to ${a.route.redirect}`),this.navigate(a.route.redirect);return}if(a.route.auth_required){const c=this.getAuthType(a.route,e),d=yr.getInstance();let f=d.isAuthenticated()&&d.getAuthType()===c;if(f||(f=await d.checkAuth(c)),!f){const g=d.getLoginRedirectUrl(c,e+n);fs.log(`Not authenticated, redirecting to: ${g}`),window.location.href=g;return}}const i=new URLSearchParams(n),l={};for(const c of i.keys()){if(c in l)continue;const d=i.getAll(c);d.length>1?l[c]=d:d.length===1&&(c.endsWith("[]")?l[c]=d:l[c]=d[0])}await this.emit("routeChange",{path:e,layout:a.route.layout,endpoint:a.route.endpoint,params:{...a.route.params||{},...a.params},query:l,auth_required:a.route.auth_required,auth_type:a.route.auth_type,meta:a.route.meta})}navigate(e){if(this.isNavigating){this.pendingNavigation=e;return}this.executeNavigation(e)}async executeNavigation(e){this.isNavigating=!0,this.pendingNavigation=null;try{window.history.pushState({},"",e),await this.navigateToCurrentPath()}finally{if(this.isNavigating=!1,this.pendingNavigation){const n=this.pendingNavigation;this.executeNavigation(n)}}}}const X_=/^\/admin\/layout-editor\/([^/?#]+)\/?$/;function py(o){const e=X_.exec(o);return e?{templateIdentifier:e[1]}:null}const Bt=ut("ErrorPageHandler");class J_{constructor(e){$(this,"templateId");$(this,"layoutLoader");$(this,"locale");$(this,"debug");$(this,"renderFunction");$(this,"dataSourceManager");$(this,"globalState");$(this,"errorConfig",null);$(this,"configLoaded",!1);this.templateId=e.templateId,this.layoutLoader=e.layoutLoader,this.locale=e.locale,this.debug=e.debug,this.renderFunction=e.renderFunction,this.dataSourceManager=e.dataSourceManager,this.globalState=e.globalState||{}}async loadConfig(){if(this.configLoaded)return this.errorConfig;try{const e=await fetch($r(`/api/templates/${this.templateId}/config`,"json"));if(!e.ok)return this.debug&&Bt.warn("Failed to load template config:",e.statusText),this.configLoaded=!0,null;const n=await e.json();if(!n.success||!n.data)return this.debug&&Bt.warn("Invalid template config response"),this.configLoaded=!0,null;const a=n.data;return!a.error_config||!a.error_config.layouts?(this.debug&&Bt.warn("No error_config found in template.json"),this.configLoaded=!0,null):(this.errorConfig=a.error_config,this.configLoaded=!0,this.debug&&Bt.log("Error config loaded:",this.errorConfig),this.errorConfig)}catch(e){return Bt.error("Failed to load error config:",e),this.configLoaded=!0,null}}async renderError(e,n="app"){try{const a=await this.loadConfig();if(!a)return this.debug&&Bt.warn("No error config available, cannot render error page"),!1;const i=a.layouts[e]||a.layouts[String(e)];if(!i)return this.debug&&Bt.warn(`No layout defined for error code: ${e}`),!1;this.debug&&Bt.log(`Loading error layout: ${i} for code: ${e}`);const l=await this.layoutLoader.loadLayout(this.templateId,i);if(!l)return this.debug&&Bt.error(`Failed to load error layout: ${i}`),!1;this.debug&&Bt.log("Error layout loaded:",l);let c={};const d=l.data_sources||[];if(d.length>0){this.debug&&Bt.log("Fetching data sources:",d.map(v=>v.id));const m=d.filter(v=>v.loading_strategy==="blocking"),b=d.filter(v=>!v.loading_strategy||v.loading_strategy==="progressive"),S=[...m,...b];if(S.length>0)try{c=await this.dataSourceManager.fetchDataSources(S,{},new URLSearchParams),this.debug&&Bt.log("Data sources fetched:",Object.keys(c))}catch(v){Bt.error("Failed to fetch data sources:",v)}}const f={};d.length>0&&(this.processInitOptions(d,c,f),this.debug&&Bt.log("initOptions processed:",{globalKeys:Object.keys(this.globalState),localKeys:Object.keys(f)}));const g={...c,errorCode:e,_global:{...this.globalState},_local:{...f}};return await this.renderFunction({containerId:n,layoutJson:l,dataContext:g,translationContext:{templateId:this.templateId,locale:this.locale}}),this.debug&&Bt.log(`Error page ${e} rendered successfully`),!0}catch(a){return Bt.error(`Failed to render error page ${e}:`,a),!1}}processInitOptions(e,n,a){for(const i of e){const l=n[i.id];if(!l)continue;const c=l?.data??l;if(i.initLocal){if(typeof i.initLocal=="string")a[i.initLocal]=c,Bt.log(`initLocal: ${i.id}.data -> _local.${i.initLocal}`);else if(typeof i.initLocal=="object"&&"key"in i.initLocal){const{key:d,path:f}=i.initLocal;a[d]=f?this.getValueByPath(c,f):c,Bt.log(`initLocal: ${i.id}.data${f?"."+f:""} -> _local.${d}`)}}if(i.initGlobal){const d=Array.isArray(i.initGlobal)?i.initGlobal:[i.initGlobal];for(const f of d)if(typeof f=="string")this.globalState[f]=c,Bt.log(`initGlobal: ${i.id}.data -> _global.${f}`);else if(typeof f=="object"&&f!==null&&"key"in f){const{key:g,path:m}=f;this.globalState[g]=m?this.getValueByPath(c,m):c,Bt.log(`initGlobal: ${i.id}.data${m?"."+m:""} -> _global.${g}`)}}}}getValueByPath(e,n){return n.split(".").reduce((a,i)=>a?.[i],e)}updateGlobalState(e){this.globalState={...this.globalState,...e}}updateLocale(e){this.locale=e,this.debug&&Bt.log("Locale updated:",e)}isConfigLoaded(){return this.configLoaded}async hasErrorLayout(e){const n=await this.loadConfig();return n?!!(n.layouts[e]||n.layouts[String(e)]):!1}clearConfigCache(){this.errorConfig=null,this.configLoaded=!1,this.debug&&Bt.log("Config cache cleared")}}const $t=ut("ModuleAssetLoader");class Q_{constructor(){$(this,"loadedAssets",new Map);$(this,"loadingPromises",new Map);$(this,"failedJsAssets",new Set)}hasFailedJsAssets(){return this.failedJsAssets.size>0}getFailedJsAssets(){return[...this.failedJsAssets]}async loadActiveExtensionAssets(e){if(!e||e.length===0){$t.log("No module assets to load");return}const n=[...e].sort((d,f)=>d.priority-f.priority);$t.log("Loading module assets:",n.map(d=>d.identifier));const a=n.filter(d=>d.css).map(d=>this.loadCSS(d.identifier,d.css)),i=n.filter(d=>d.js).map(d=>this.loadJS(d.identifier,d.js)),l=await Promise.allSettled([...a,...i]),c=l.filter(d=>d.status==="rejected");if(c.length>0){$t.warn(`Some module assets failed to load (${c.length}/${l.length}); continuing with the rest`,this.getFailedJsAssets());return}$t.log("All module assets loaded successfully")}async loadBundle(e,n,a){const i=[];if(a&&i.push(this.loadBundleCss(e,a)),n&&i.push(this.loadBundleJs(e,n)),i.length===0){$t.log(`No bundle assets to load for: ${e}`);return}await Promise.all(i)}async loadBundleCss(e,n){const a=`ext-bundle-css-${e}`;if(document.getElementById(a)){$t.log(`Bundle CSS already loaded: ${e}`);return}return new Promise(i=>{const l=document.createElement("link");l.rel="stylesheet",l.href=n,l.id=a,l.onload=()=>{$t.log(`Bundle CSS loaded: ${e}`),this.registerLoadedAsset(`bundle-${e}`,{type:"css",element:l}),i()},l.onerror=()=>{$t.warn(`Failed to load bundle CSS: ${e} (${n})`),i()},document.head.appendChild(l)})}async loadBundleJs(e,n){const a=`ext-bundle-js-${e}`;if(document.getElementById(a)){$t.log(`Bundle JS already loaded: ${e}`);return}const i=this.loadingPromises.get(a);if(i)return $t.log(`Bundle JS already loading: ${e}`),i;const l=sp(n,{id:a},{label:`bundle JS: ${e}`}).then(()=>{$t.log(`Bundle JS loaded: ${e}`);const c=document.getElementById(a);c&&this.registerLoadedAsset(`bundle-${e}`,{type:"js",element:c}),this.loadingPromises.delete(a)}).catch(c=>{throw $t.warn(`Failed to load bundle JS: ${e} (${n})`,c),this.failedJsAssets.add(e),this.loadingPromises.delete(a),c});return this.loadingPromises.set(a,l),l}async loadCSS(e,n){const a=`module-css-${e}`;if(document.getElementById(a)){$t.log(`CSS already loaded: ${e}`);return}return new Promise((i,l)=>{const c=document.createElement("link");c.rel="stylesheet",c.href=n,c.id=a,c.onload=()=>{$t.log(`CSS loaded: ${e}`),this.registerLoadedAsset(e,{type:"css",element:c}),i()},c.onerror=()=>{$t.warn(`Failed to load CSS: ${e} (${n})`),i()},document.head.appendChild(c)})}async loadJS(e,n){const a=`module-js-${e}`;if(document.getElementById(a)){$t.log(`JS already loaded: ${e}`);return}const i=this.loadingPromises.get(e);if(i)return $t.log(`JS already loading: ${e}`),i;const l=sp(n,{id:a},{label:`JS: ${e}`}).then(()=>{$t.log(`JS loaded: ${e}`);const c=document.getElementById(a);c&&this.registerLoadedAsset(e,{type:"js",element:c}),this.loadingPromises.delete(e)}).catch(c=>{throw $t.warn(`Failed to load JS: ${e} (${n})`,c),this.failedJsAssets.add(e),this.loadingPromises.delete(e),c});return this.loadingPromises.set(e,l),l}registerLoadedAsset(e,n){const a=this.loadedAssets.get(e)||[];a.push(n),this.loadedAssets.set(e,a)}unloadExtensionAsset(e){const n=this.loadedAssets.get(e);if(!n||n.length===0){$t.log(`No assets to unload for: ${e}`);return}n.forEach(a=>{a.element.parentNode&&(a.element.parentNode.removeChild(a.element),$t.log(`${a.type.toUpperCase()} unloaded: ${e}`))}),this.loadedAssets.delete(e),$t.log(`All assets unloaded for: ${e}`)}unloadAllAssets(){Array.from(this.loadedAssets.keys()).forEach(n=>{this.unloadExtensionAsset(n)}),$t.log("All module assets unloaded")}isLoaded(e){return this.loadedAssets.has(e)}getLoadedModules(){return Array.from(this.loadedAssets.keys())}}let Sf=null;function wf(){return Sf||(Sf=new Q_),Sf}function Z_(){if(typeof window>"u")return[];const o=window.G7Config;if(!o?.moduleAssets)return[];const e=[];for(const[n,a]of Object.entries(o.moduleAssets)){const i=a;e.push({identifier:n,js:i.js?Ea(i.js):i.js,css:i.css?Ea(i.css):i.css,priority:i.priority,external:i.external})}return e}function eA(){if(typeof window>"u")return null;const o=window.G7Config;if(!o?.bundleUrls)return null;const e=o.bundleUrls;return{moduleJs:e.moduleJs?Ea(e.moduleJs):e.moduleJs,moduleCss:e.moduleCss?Ea(e.moduleCss):e.moduleCss,pluginJs:e.pluginJs?Ea(e.pluginJs):e.pluginJs,pluginCss:e.pluginCss?Ea(e.pluginCss):e.pluginCss}}function tA(){if(typeof window>"u")return[];const o=window.G7Config;if(!o?.pluginAssets)return[];const e=[];for(const[n,a]of Object.entries(o.pluginAssets)){const i=a;e.push({identifier:n,js:i.js?Ea(i.js):i.js,css:i.css?Ea(i.css):i.css,priority:i.priority,external:i.external})}return e}const Cf=ut("SystemBannerManager");class Cc{static show(e){this.banners.set(e.id,e),this.render(),Cf.log(`Banner shown: ${e.id}`)}static hide(e){this.banners.delete(e)&&(this.render(),Cf.log(`Banner hidden: ${e}`))}static hideAll(){this.banners.clear(),this.render(),Cf.log("All banners hidden")}static detectLocale(){try{const n=window.G7Core;if(n?.locale?.current)return n.locale.current()}catch{}return(navigator.language||"ko").split("-")[0]}static resolveMessage(e,n){return typeof e=="string"?e:e[n]||e.en||e.ko||Object.values(e)[0]||""}static render(){if(typeof document>"u")return;let e=document.getElementById(this.containerId);if(this.banners.size===0){e&&(e.remove(),this.adjustAppPadding(0));return}e||(e=document.createElement("div"),e.id=this.containerId,e.style.cssText="position:fixed;top:0;left:0;right:0;z-index:99999;",document.body.prepend(e));const n=Array.from(this.banners.values()).sort((i,l)=>(i.order??0)-(l.order??0)),a=this.detectLocale();e.innerHTML=n.map(i=>{const l=this.resolveMessage(i.message,a),c=i.background||"#f59e0b",d=i.color||"white";return`
${l}
`}).join(""),requestAnimationFrame(()=>{e&&this.adjustAppPadding(e.offsetHeight)})}static adjustAppPadding(e){const n=document.getElementById("app");n&&(n.style.paddingTop=e>0?`${e}px`:"")}}$(Cc,"banners",new Map),$(Cc,"containerId","g7-system-banners");const U=ut("TemplateApp");function my(o){const e={};for(const n of o.keys()){if(n in e)continue;const a=o.getAll(n);a.length>1?e[n]=a:a.length===1&&(n.endsWith("[]")?e[n]=a:e[n]=a[0])}return e}const Ar=class Ar{constructor(e){$(this,"router",null);$(this,"layoutLoader",null);$(this,"errorPageHandler",null);$(this,"config");$(this,"globalState");$(this,"globalStateListeners",new Set);$(this,"currentRouteChangeId",0);$(this,"currentDataSources",[]);$(this,"currentRawDataSources",[]);$(this,"currentRouteParams",{});$(this,"currentQueryParams",new URLSearchParams);$(this,"currentFetchedData",{});$(this,"currentLayoutName","");$(this,"templateErrorHandling",null);$(this,"currentWebSocketSubscriptions",[]);$(this,"extensionCacheVersion",0);$(this,"currentGlobalHeaders",[]);$(this,"transitionOverlayEl",null);$(this,"skeletonOverlayRoot",null);$(this,"skeletonOverlayContainer",null);$(this,"_spinnerState",null);$(this,"modalDataSources",new Map);this.globalState={sidebarOpen:!1},this.loadG7Config(),this.migrateLocaleStorage();const n=this.loadLocaleFromStorage(),a=n||e.locale||"ko";this.config={...e,locale:a},!n&&e.locale&&this.saveLocaleToStorage(e.locale)}loadG7Config(){if(typeof window<"u"&&window.G7Config){const e=window.G7Config;e.settings&&(this.globalState.settings=e.settings,U.log("Loaded settings from G7Config:",Object.keys(e.settings)),e.settings.upload&&(this.globalState.uploadSettings=e.settings.upload)),e.plugins&&(this.globalState.plugins=e.plugins,U.log("Loaded plugin settings from G7Config:",Object.keys(e.plugins))),e.modules&&(this.globalState.modules=e.modules,U.log("Loaded module settings from G7Config:",Object.keys(e.modules))),e.appConfig&&(this.globalState.appConfig=e.appConfig,U.log("Loaded appConfig from G7Config:",Object.keys(e.appConfig)))}}resolveRouteExpressions(e){const n=new xn,a={_global:this.globalState};return e.map(i=>{const l={...i};if(l.path&&l.path.includes("{{")){const c=l.path;l.path=n.resolveBindings(l.path,a),l.path=l.path.replace(/\/\/+/g,"/")||"/",l.path.includes("{{")&&(U.warn("Route expression resolution failed, using fallback:",c),l.path=c.replace(/\{\{[^}]+\}\}/g,"").replace(/\/\/+/g,"/")||"/")}if(l.redirect&&l.redirect.includes("{{")){const c=l.redirect;l.redirect=n.resolveBindings(l.redirect,a),l.redirect=l.redirect.replace(/\/\/+/g,"/")||"/",l.redirect.includes("{{")&&(U.warn("Route redirect expression resolution failed:",c),l.redirect=c.replace(/\{\{[^}]+\}\}/g,"").replace(/\/\/+/g,"/")||"/")}return l})}migrateLocaleStorage(){const e=["locale","g7_template_locale"];for(const n of e)try{const a=localStorage.getItem(n);a&&a!==localStorage.getItem(Ar.LOCALE_STORAGE_KEY)&&(localStorage.setItem(Ar.LOCALE_STORAGE_KEY,a),U.log(`Migrated locale from '${n}' to '${Ar.LOCALE_STORAGE_KEY}'`)),localStorage.removeItem(n)}catch(a){U.warn(`Failed to migrate locale key '${n}':`,a)}}async init(){try{Qs.getInstance().setDebug(this.config.debug),U.log("Initializing with config:",this.config),YS();const e=pr.getInstance(),n=yr.getInstance(),a=this.loadCacheVersionFromStorage()||0,[i,l,c,d,f]=await Promise.all([Oo({templateId:this.config.templateId,templateType:this.config.templateType,locale:this.config.locale,debug:this.config.debug,cacheVersion:a}),e.loadComponents(this.config.templateId,this.config.templateType),Ll($r(`/api/templates/${this.config.templateId}/routes`,"json",a>0?a:null),{label:"routes.json"}).then(A=>{if(!A.ok)throw new Error(`Failed to load routes: ${A.statusText}`);return A.json()}).then(A=>{if(!A.success)throw new Error("Failed to load routes from API");return A.data}).catch(A=>{throw U.error("Error loading routes:",A),A}),n.preloadAuth(this.config.templateType==="admin"?"admin":"user"),fetch($r(`/api/templates/${this.config.templateId}/config`,"json")).then(A=>A.ok?A.json():null).then(A=>!A?.success||!A?.data?null:A.data).catch(A=>(U.warn("Error loading template config:",A),null))]);if(U.log("Template Engine initialized"),U.log("ComponentRegistry loaded"),U.log("Routes data loaded"),U.log("User info preloaded"),U.log("Template config loaded:",f),f?.cache_version!==void 0){const A=this.loadCacheVersionFromStorage();if(this.extensionCacheVersion=f.cache_version,this.saveCacheVersionToStorage(this.extensionCacheVersion),U.log("Extension cache version:",this.extensionCacheVersion),A!==null&&A!==this.extensionCacheVersion){U.log("Cache version changed, reloading routes...");const x=await Ll($r(`/api/templates/${this.config.templateId}/routes`,"json",this.extensionCacheVersion),{label:"routes.json (reload)"}).then(L=>{if(!L.ok)throw new Error(`Failed to reload routes: ${L.statusText}`);return L.json()}).then(L=>{if(!L.success)throw new Error("Failed to reload routes from API");return L.data});Array.isArray(x.routes)&&(x.routes=this.resolveRouteExpressions(x.routes),Object.assign(c,x),U.log("Routes reloaded with new cache version"));try{const{TranslationEngine:L}=await Promise.resolve().then(()=>dp),M=L.getInstance();M.setCacheVersion(this.extensionCacheVersion);const R=this.config.locale||"ko",D="en";await M.loadTranslations(this.config.templateId,R,"/api",!0),R!==D&&await M.loadTranslations(this.config.templateId,D,"/api",!0),U.log("Translations reloaded with new cache version")}catch(L){U.error("Failed to reload translations:",L)}}}f?.errorHandling&&(this.templateErrorHandling=f.errorHandling,vr().setTemplateConfig(this.templateErrorHandling),U.log("Template errorHandling registered:",this.templateErrorHandling)),n.on("logout",()=>{U.log("User logged out")}),n.on("authStateChange",A=>{U.log("Auth state changed:",A)}),U.log("AuthManager event handlers registered");const g=Ir(),m=this.config.templateType==="admin"?"admin":"user",b=n.getConfig(m);g.setOnUnauthorized(()=>{U.log("Unauthorized - redirecting to login page"),g.removeToken();const A=window.location.pathname+window.location.search,x=n.getLoginRedirectUrl(m,A,"session_expired");window.location.href=x}),U.log("ApiClient onUnauthorized callback registered"),this.layoutLoader=new ff(e),this.extensionCacheVersion>0&&this.layoutLoader.setCacheVersion(this.extensionCacheVersion),U.log("LayoutLoader initialized:",this.layoutLoader);const S=new Er({onUnauthorized:()=>{U.warn("Unauthorized request in error page")}});if(this.errorPageHandler=new J_({templateId:this.config.templateId,layoutLoader:this.layoutLoader,locale:this.config.locale,debug:this.config.debug,renderFunction:hs,dataSourceManager:S,globalState:this.globalState}),U.log("ErrorPageHandler initialized with DataSourceManager"),await this.handleServerError()){U.log("Server error handled, skipping normal initialization");return}if(this.router=new Y_(this.config.templateId),Array.isArray(c.routes)){const A=this.resolveRouteExpressions(c.routes);this.router.setRoutes(A)}else throw new Error("Invalid routes data format");U.log("Router initialized:",this.router),U.log("Routes set:",this.router.getRoutes());const{getActionDispatcher:v}=await Promise.resolve().then(()=>Ur),_=v();if(_&&(_.setDefaultContext({navigate:A=>this.router?.navigate(A)}),_.setGlobalStateUpdater((A,x)=>this.setGlobalState(A,x)),U.log("Navigate function and setGlobalState injected to ActionDispatcher")),await this.loadExtensionAssets(),this.reinitializeTemplateHandlers(),this.router.on("routeChange",A=>this.handleRouteChange(A)),this.router.on("routeNotFound",A=>this.handleRouteNotFound(A)),typeof window<"u"&&py(window.location.pathname)){U.log("Layout editor mode detected — skipping router match"),await hs({containerId:"app",layoutJson:{components:[]},dataContext:{},translationContext:{templateId:this.config.templateId,locale:this.config.locale}}),U.log("Template App initialized in layout editor mode");return}this.router.navigateToCurrentPath(),U.log("Template App initialized successfully")}catch(e){U.error("Initialization failed:",e),this.showInitError(e)}}async loadExtensionAssets(){try{const e=wf(),n=eA();if(!n){await this.loadExtensionAssetsIndividually();return}await e.loadBundle("module",n.moduleJs,n.moduleCss),await e.loadBundle("plugin",n.pluginJs,n.pluginCss),U.log("Extension bundle assets loaded successfully")}catch(e){U.warn("Failed to load extension assets:",e)}}async loadExtensionAssetsIndividually(){const e=wf(),n=Z_();n.length>0&&(U.log("Loading module assets (individual fallback):",n.map(i=>i.identifier)),await e.loadActiveExtensionAssets(n));const a=tA();a.length>0&&(U.log("Loading plugin assets (individual fallback):",a.map(i=>i.identifier)),await e.loadActiveExtensionAssets(a))}async handleRouteChange(e){const n=++this.currentRouteChangeId;window.__g7ForcedLocalFields=void 0,window.__g7ActionContext=void 0,window.__g7PendingLocalState=void 0,window.__g7LastSetLocalSnapshot=void 0,window.__g7SetLocalOverrideKeys=void 0,window.__g7SequenceLocalSync=void 0,window.__g7AutoBindingPaths=new Map;try{if(U.log("Route changed:",e,"requestId:",n),!this.layoutLoader)throw new Error("LayoutLoader is not initialized");if(!e.layout)throw new Error("Route layout is not defined");let a=e.layout;const i=a==="__preview__";i&&e.params?.token&&(a=`__preview__/${e.params.token}`);const l=await this.layoutLoader.loadLayout(this.config.templateId,a);if(n!==this.currentRouteChangeId){U.log("Route change cancelled (newer request exists):",n);return}U.log("Layout loaded:",l);const c=vr();l.errorHandling?(c.setLayoutConfig(l.errorHandling),U.log("Layout errorHandling registered:",l.errorHandling)):c.clearLayoutConfig();const d=l.data_sources||[],f=e.query||{},g=new URLSearchParams;for(const[Z,pe]of Object.entries(f))if(Array.isArray(pe))for(const H of pe)g.append(Z,H);else g.set(Z,pe);const m={route:e.params||{},query:f,_global:this.globalState};l.scripts&&Array.isArray(l.scripts)&&await this.loadLayoutScripts(l.scripts,m);const{DataSourceManager:b,getActionDispatcher:S}=await Promise.resolve().then(()=>Ur),v=new b;if(i){const Z=S();Z&&Z.setPreviewMode(!0),this.setGlobalState({__isPreview:!0}),Cc.show({id:"preview-mode",message:{ko:"⚠ 미리보기 모드 — 페이지 이동이 비활성화됩니다",en:"⚠ Preview Mode — Navigation is disabled"},background:"linear-gradient(90deg, #f59e0b, #d97706)",color:"white"}),U.log("Preview mode activated")}else{const Z=S();Z?.isPreviewMode()&&(Z.setPreviewMode(!1),Cc.hide("preview-mode"))}if(this.currentGlobalHeaders=l.globalHeaders||[],this.currentGlobalHeaders.length>0){v.setGlobalHeaders(this.currentGlobalHeaders);const Z=S();Z&&Z.setGlobalHeaders(this.currentGlobalHeaders),U.log("globalHeaders set:",this.currentGlobalHeaders.map(pe=>pe.pattern))}if(l.named_actions&&Object.keys(l.named_actions).length>0){const Z=S();Z&&Z.setNamedActions(l.named_actions)}const _=v.filterByCondition(d,m);d.length!==_.length&&U.log("Data sources filtered by condition:",{before:d.map(Z=>Z.id),after:_.map(Z=>Z.id)});const A=_.filter(Z=>Z.loading_strategy==="blocking"),x=_.filter(Z=>(Z.loading_strategy||"progressive")!=="blocking"&&Z.type!=="websocket"),L=Array.isArray(l.transition_overlay?.wait_for)?l.transition_overlay.wait_for:[],M=L.length>0&&_.some(Z=>L.includes(Z.id)&&Z.type!=="websocket"&&(Z.loading_strategy||"progressive")!=="background");if((A.length>0||M)&&l.transition_overlay){const Z=typeof l.transition_overlay=="boolean"?{enabled:l.transition_overlay,style:"opaque"}:l.transition_overlay;Z.enabled&&Z.style==="skeleton"&&Z.skeleton?.component&&Z.target?this.renderSkeletonOverlay(Z.target,Z.skeleton,l,Z.fallback_target):Z.enabled&&Z.style==="spinner"&&Z.target&&this.renderSpinnerOverlay(Z.target,Z.spinner,Z.fallback_target)}let R={},D={},B={},q={};this.currentWebSocketSubscriptions.length>0&&(U.log("Unsubscribing previous WebSocket subscriptions:",this.currentWebSocketSubscriptions),v.unsubscribeWebSockets(this.currentWebSocketSubscriptions),this.currentWebSocketSubscriptions=[]),this.currentDataSources=_,this.currentRawDataSources=d,this.currentRouteParams=e.params||{},this.currentQueryParams=g,U.log(`handleRouteChange #${n} - queryObject:`,f),U.log(`handleRouteChange #${n} - queryParams:`,g.toString()),A.length>0&&(U.log("Fetching blocking data sources:",A.map(pe=>pe.id)),(await v.fetchDataSourcesWithResults(A,e.params||{},g)).forEach(pe=>{if(pe.state==="success"&&pe.data!==void 0)R[pe.id]=pe.data;else if(pe.state==="error"&&pe.error){const H=pe.error,T=H.response?.data?.message;D[pe.id]={message:T||pe.error.message,status:H.response?.status||H.status}}}),this.processInitOptions(A,R,B,q),this.currentFetchedData={...R},U.log("Blocking data loaded:",Object.keys(R)),Object.keys(D).length>0&&U.log("Data source errors:",D),Object.keys(B).length>0&&U.log("Local state init (blocking):",Object.keys(B)),Object.keys(q).length>0&&U.log("Isolated state init (blocking):",Object.keys(q)));const P=x.length>0;if(P&&(Hr.setPending(!0),U.log("Transition started before rendering")),n!==this.currentRouteChangeId){U.log("Route change cancelled after blocking data (newer request exists):",n);return}const{getState:K}=await Promise.resolve().then(()=>Ur),ge=K().currentDataContext||{},Ee=x.map(Z=>Z.id),Se={};if(P){Ee.forEach(pe=>{ge[pe]!==void 0?Se[pe]=ge[pe]:Se[pe]=void 0});const Z=Object.keys(Se).filter(pe=>Se[pe]!==void 0);Z.length>0&&U.log("Preserving previous progressive data:",Z),U.log("Progressive data sources initialized:",Ee)}const Ue=l.defines||{},Ve={...Se,...e.params,...R,route:{...e.params||{},path:e.path},query:f,_global:{...this.globalState},_globalSetState:Z=>this.setGlobalState(Z),_dataSourceErrors:Object.keys(D).length>0?D:void 0,_localInit:Object.keys(B).length>0?B:void 0,_isolatedInit:Object.keys(q).length>0?q:void 0,_defines:Object.keys(Ue).length>0?Ue:void 0};if(l.computed&&Object.keys(l.computed).length>0){const Z=this.calculateComputed(l.computed,Ve);Object.keys(Z).length>0&&(Ve._computed=Z,this.globalState._computed=Z,U.log("Computed values calculated:",Object.keys(Z))),Ve._computedDefinitions=l.computed}const qe=l.layout_name||e.layout;this.currentLayoutName!==""&&this.currentLayoutName!==qe&&(U.log("_local reset due to layout change:",{from:this.currentLayoutName,to:qe}),this.globalState._local={},dC()),this.currentLayoutName=qe;const J=l.initLocal||l.state;if(J&&Object.keys(J).length>0){this.globalState._local||(this.globalState._local={});for(const[Z,pe]of Object.entries(J))this.globalState._local[Z]===void 0&&(this.globalState._local[Z]=JSON.parse(JSON.stringify(pe)));Ve._local={...this.globalState._local},U.log("initLocal applied to _local:",Object.keys(J))}if(l.initGlobal&&Object.keys(l.initGlobal).length>0){for(const[Z,pe]of Object.entries(l.initGlobal))this.globalState[Z]===void 0&&(this.globalState[Z]=JSON.parse(JSON.stringify(pe)));U.log("initGlobal applied to _global:",Object.keys(l.initGlobal))}if(l.initIsolated&&Object.keys(l.initIsolated).length>0){for(const[Z,pe]of Object.entries(l.initIsolated))q[Z]===void 0&&(q[Z]=JSON.parse(JSON.stringify(pe)));U.log("initIsolated applied:",Object.keys(l.initIsolated))}Ve._global={...this.globalState,layoutWarnings:l.warnings||[]};const fe=l.initActions||l.init_actions;if(fe&&fe.length>0){await this.executeInitActions(fe,Ve),U.log("initActions executed before render");const Z=this.globalState;if(Z._local){if(Object.keys(B).length>0){for(const[pe,H]of Object.entries(B))Z._local[pe]!==void 0?Z._local[pe]=this.deepMerge(Z._local[pe],H):Z._local[pe]=H;U.log("localInit merged into _local after initActions:",Object.keys(B))}Ve._local=Z._local,U.log("_local merged into dataContext:",Z._local)}if(Ve._global={...this.globalState,layoutWarnings:l.warnings||[]},U.log("_global merged into dataContext after initActions"),l.computed&&Object.keys(l.computed).length>0){const pe=this.calculateComputed(l.computed,Ve);Object.keys(pe).length>0&&(Ve._computed=pe,this.globalState._computed=pe,U.log("Computed values recalculated after init_actions:",Object.keys(pe)))}if(Object.keys(this.currentFetchedData).length>0){for(const[pe,H]of Object.entries(this.currentFetchedData))Ve[pe]=H;U.log("Fetched data sources merged into dataContext after initActions:",Object.keys(this.currentFetchedData))}}if(l.transition_overlay&&!this.skeletonOverlayContainer&&this.showTransitionOverlay(l.transition_overlay,l),await hs({containerId:"app",layoutJson:l,dataContext:Ve,translationContext:{templateId:this.config.templateId,locale:this.config.locale}}),U.log("Initial render complete with blocking data"),this.reattachSpinnerOverlay(),x.length>0){if(n!==this.currentRouteChangeId){U.log("Route change cancelled before progressive fetch (newer request exists):",n),Hr.setPending(!1),this.hideTransitionOverlay();return}U.log("Fetching progressive/background data sources:",x.map(H=>H.id));const{updateTemplateData:Z,getState:pe}=await Promise.resolve().then(()=>Ur);try{const H=x.map(async T=>{try{const de=(await v.fetchDataSourcesWithResults([T],e.params||{},g,this.globalState))[0];if(!de){U.log(`Data source ${T.id} skipped (no fetch result)`);return}if(n!==this.currentRouteChangeId){U.log(`Data source ${T.id} fetch cancelled (newer request exists)`);return}if(de.state==="success"&&de.data!==void 0){U.log(`Progressive data source loaded: ${T.id}`),this.currentFetchedData[T.id]=de.data;const ye={};this.processInitOptions([T],{[T.id]:de.data},ye);const ie=pe();if(ie.bindingEngine){const _e=[T.id];T.initGlobal&&_e.push("_global"),T.initLocal&&_e.push("_local"),ie.bindingEngine.invalidateCacheByKeys(_e)}const xe={[T.id]:de.data};Object.keys(ye).length>0&&(xe._localInit=ye,Object.assign(B,ye)),T.initGlobal&&(xe._global={...this.globalState}),Z(xe)}else if(de.state==="error"&&de.error){const ye=de.error,ie=ye.response?.data?.message;D[T.id]={message:ie||de.error.message,status:ye.response?.status||ye.status},U.log(`Progressive data source error: ${T.id}`,D[T.id]),Z({[T.id]:null,_dataSourceErrors:{...D}})}}catch(ce){U.error(`Failed to fetch data source: ${T.id}`,ce)}});if(await Promise.all(H),n!==this.currentRouteChangeId){U.log("Route change cancelled after progressive fetch (newer request exists):",n);return}U.log("All progressive data sources loaded")}finally{Hr.setPending(!1),this.hideTransitionOverlay()}}else this.hideTransitionOverlay();const Le=_.filter(Z=>Z.type==="websocket");if(Le.length>0){U.log("Subscribing WebSocket data sources:",Le.map(T=>T.id));const{updateTemplateData:Z,getState:pe}=await Promise.resolve().then(()=>Ur),H={...this.currentFetchedData,...e.params,route:{...e.params||{},path:e.path},query:f,_global:{...this.globalState}};U.log("WebSocket binding context keys:",Object.keys(H)),this.currentWebSocketSubscriptions=v.subscribeWebSockets(_,(T,ce)=>{U.log(`WebSocket data received for: ${T}`,ce),this.currentFetchedData[T]=ce;const de=_.find(_e=>_e.id===T),ye=pe();if(ye.bindingEngine){const _e=[T];de?.initGlobal&&_e.push("_global"),de?.initLocal&&_e.push("_local"),ye.bindingEngine.invalidateCacheByKeys(_e)}Z({[T]:ce});const xe=_.find(_e=>_e.type==="websocket"&&(_e.target_source||_e.id)===T)?.onReceive;Array.isArray(xe)&&xe.length>0&&window.G7Core?.dispatch&&(async()=>{for(const Te of xe)try{const $e=this.getActionDispatcher?.();$e&&await $e.dispatchAction(Te,{navigate:this.getRouter?.()?(ht,wt)=>this.getRouter().navigate(ht,wt):void 0,setState:ht=>this.setGlobalState(ht),state:this.globalState,data:{...this.globalState,$args:[ce],$event:ce},_isDispatchFallbackContext:!0})}catch($e){U.error(`WebSocket onReceive action failed for ${T}:`,$e)}})()},H),U.log("WebSocket subscriptions established:",this.currentWebSocketSubscriptions)}U.log("Layout rendered successfully")}catch(a){U.error("Route change handling failed:",a),this.hideTransitionOverlay(),this.showRouteError(a)}}processInitOptions(e,n,a,i){e.some(c=>c.refetchOnMount===!0&&c.initLocal)&&(a._forceLocalInit=Date.now(),U.log("refetchOnMount detected, forcing local state init")),e.forEach(c=>{const d=n[c.id];if(!d)return;const f=d.data??d;if(c.initLocal){if(typeof c.initLocal=="string"){let g=f;if(c.initLocalDefaults&&typeof c.initLocalDefaults=="object"){const b=this.evaluateDefaults(c.initLocalDefaults,n);g={...b,...f},U.log(`initLocalDefaults applied for ${c.initLocal}:`,Object.keys(b))}const m=this.globalState._local?.[c.initLocal];m!==void 0&&typeof m=="object"&&typeof g=="object"?(a[c.initLocal]=this.deepMerge(m,g),U.log(`initLocal (merged): ${c.id}.data -> _local.${c.initLocal}`)):(a[c.initLocal]=g,U.log(`initLocal: ${c.id}.data -> _local.${c.initLocal}`))}else if(typeof c.initLocal=="object"&&c.initLocal.key){const{key:g,path:m}=c.initLocal;let b=m?this.extractValueByPathOrExpression(f,m,c.id):f;if(c.initLocalDefaults&&typeof c.initLocalDefaults=="object"){const v=this.evaluateDefaults(c.initLocalDefaults,n);b={...v,...b},U.log(`initLocalDefaults applied for ${g}:`,Object.keys(v))}const S=this.globalState._local?.[g];S!==void 0&&typeof S=="object"&&typeof b=="object"?(a[g]=this.deepMerge(S,b),U.log(`initLocal (merged): ${c.id}.data${m?"."+m:""} -> _local.${g}`)):(a[g]=b,U.log(`initLocal: ${c.id}.data${m?"."+m:""} -> _local.${g}`))}else if(typeof c.initLocal=="object"){const g=c.initLocal._merge||"deep",m=this.flattenNestedObjectToMappings(c.initLocal);for(const{targetPath:b,sourcePath:S}of m){const v=this.extractValueByPathOrExpression(f,S,c.id);if(b.includes(".")){const _=b.split(".")[0];a[_]===void 0&&this.globalState._local?.[_]!==void 0&&(a[_]=JSON.parse(JSON.stringify(this.globalState._local[_]))),this.setValueAtPath(a,b,v,g),U.log(`initLocal map (${g}): ${c.id} ${S} -> _local.${b}`)}else{const _=this.globalState._local?.[b];g==="replace"?(a[b]=v,U.log(`initLocal map (replace): ${c.id} ${S} -> _local.${b}`)):g==="shallow"?_!==void 0&&typeof _=="object"&&typeof v=="object"?(a[b]={..._,...v},U.log(`initLocal map (shallow): ${c.id} ${S} -> _local.${b}`)):(a[b]=v,U.log(`initLocal map: ${c.id} ${S} -> _local.${b}`)):_!==void 0&&typeof _=="object"&&typeof v=="object"?(a[b]=this.deepMerge(_,v),U.log(`initLocal map (deep): ${c.id} ${S} -> _local.${b}`)):(a[b]=v,U.log(`initLocal map: ${c.id} ${S} -> _local.${b}`))}}}}if(c.initGlobal)if(typeof c.initGlobal=="object"&&!Array.isArray(c.initGlobal)&&!("key"in c.initGlobal))for(const[m,b]of Object.entries(c.initGlobal)){if(typeof b!="string"){U.warn(`initGlobal map value must be string, got ${typeof b} for key ${m}`);continue}const S=this.extractValueByPathOrExpression(f,b,c.id),v=this.globalState[m];v!==void 0&&typeof v=="object"&&typeof S=="object"?(this.globalState[m]=this.deepMerge(v,S),U.log(`initGlobal map (merged): ${c.id} ${b} -> _global.${m}`)):(this.globalState[m]=S,U.log(`initGlobal map: ${c.id} ${b} -> _global.${m}`))}else{const m=Array.isArray(c.initGlobal)?c.initGlobal:[c.initGlobal];for(const b of m)if(typeof b=="string"){const S=this.globalState[b];S!==void 0&&typeof S=="object"&&typeof f=="object"?(this.globalState[b]=this.deepMerge(S,f),U.log(`initGlobal (merged): ${c.id}.data -> _global.${b}`)):(this.globalState[b]=f,U.log(`initGlobal: ${c.id}.data -> _global.${b}`))}else if(typeof b=="object"&&b.key){const{key:S,path:v}=b,_=v?this.extractValueByPathOrExpression(f,v,c.id):f,A=this.globalState[S];A!==void 0&&typeof A=="object"&&typeof _=="object"?(this.globalState[S]=this.deepMerge(A,_),U.log(`initGlobal (merged): ${c.id}.data${v?"."+v:""} -> _global.${S}`)):(this.globalState[S]=_,U.log(`initGlobal: ${c.id}.data${v?"."+v:""} -> _global.${S}`))}}if(i&&c.initIsolated){if(typeof c.initIsolated=="string"){const g=i[c.initIsolated];g!==void 0&&typeof g=="object"&&typeof f=="object"?(i[c.initIsolated]=this.deepMerge(g,f),U.log(`initIsolated (merged): ${c.id}.data -> _isolated.${c.initIsolated}`)):(i[c.initIsolated]=f,U.log(`initIsolated: ${c.id}.data -> _isolated.${c.initIsolated}`))}else if(typeof c.initIsolated=="object"&&c.initIsolated.key){const{key:g,path:m}=c.initIsolated,b=m?this.extractValueByPathOrExpression(f,m,c.id):f,S=i[g];S!==void 0&&typeof S=="object"&&typeof b=="object"?(i[g]=this.deepMerge(S,b),U.log(`initIsolated (merged): ${c.id}.data${m?"."+m:""} -> _isolated.${g}`)):(i[g]=b,U.log(`initIsolated: ${c.id}.data${m?"."+m:""} -> _isolated.${g}`))}else if(typeof c.initIsolated=="object")for(const[g,m]of Object.entries(c.initIsolated)){if(typeof m!="string"){U.warn(`initIsolated map value must be string, got ${typeof m} for key ${g}`);continue}const b=this.extractValueByPathOrExpression(f,m,c.id),S=i[g];S!==void 0&&typeof S=="object"&&typeof b=="object"?(i[g]=this.deepMerge(S,b),U.log(`initIsolated map (merged): ${c.id} ${m} -> _isolated.${g}`)):(i[g]=b,U.log(`initIsolated map: ${c.id} ${m} -> _isolated.${g}`))}}})}evaluateDefaults(e,n){const a={};for(const[i,l]of Object.entries(e))if(typeof l=="string"&&l.startsWith("{{")&&l.endsWith("}}")){const c=l.slice(2,-2).trim();a[i]=this.evaluateExpression(c,n)}else a[i]=l;return a}evaluateExpression(e,n){try{const a=e.split("??").map(i=>i.trim());for(const i of a){if(/^['"].*['"]$/.test(i))return i.slice(1,-1);if(i==="true")return!0;if(i==="false")return!1;if(/^-?\d+(\.\d+)?$/.test(i))return Number(i);const l=this.getNestedValue(n,i);if(l!=null)return l}return}catch(a){U.warn(`Expression evaluation failed: ${e}`,a);return}}getNestedValue(e,n){const a=n.replace(/\?\./g,".");let i=e;const l=a.split(/\.(?![^\[]*\])/).flatMap(c=>{const d=c.match(/^([^\[]*)((?:\[\d+\])*)$/);if(d){const[,f,g]=d,m=[];f&&m.push(f);const b=g.match(/\[\d+\]/g);return b&&m.push(...b),m}return[c]}).filter(c=>c!=="");for(const c of l){if(i==null)return;if(c.startsWith("[")&&c.endsWith("]")){const d=parseInt(c.slice(1,-1),10);i=i[d]}else i=i[c]}return i}extractValueByPathOrExpression(e,n,a){const i=Aa(n);if(i!==null){const l=new xn,c={data:e,_global:this.globalState,_local:this.globalState._local||{}};try{const d=Pn(i)?l.evaluatePipeExpression(i,c,{skipCache:!0}):l.evaluateExpression(i,c);return U.log(`initLocal/initGlobal expression evaluated: ${n} -> `,d),d}catch(d){U.warn(`initLocal/initGlobal expression evaluation failed for ${a}:`,n,d);return}}return this.getNestedValue(e,n)}async loadLayoutScripts(e,n){const a=[];for(const i of e){if((i.if!==void 0||i.conditions!==void 0)&&!ts({if:i.if,conditions:i.conditions},n,this.bindingEngine,`script:${i.id}`)){U.log(`Script skipped (condition not met): ${i.id}`);continue}if(document.getElementById(i.id)){U.log(`Script already loaded: ${i.id}`);continue}const c=new Promise((d,f)=>{const g=document.createElement("script");g.src=i.src,g.id=i.id,g.async=i.async??!0,g.onload=()=>{U.log(`Script loaded successfully: ${i.id}`),d()},g.onerror=()=>{U.warn(`Failed to load script: ${i.id} (${i.src})`),d()},document.head.appendChild(g)});a.push(c)}a.length>0&&(await Promise.all(a),U.log(`All scripts loaded: ${e.filter(i=>!document.getElementById(i.id)||a.length>0).map(i=>i.id).join(", ")}`))}evaluateScriptCondition(e,n){try{if(e.startsWith("{{")&&e.endsWith("}}")){const a=e.slice(2,-2).trim();return new Function("ctx",` with(ctx) { try { return Boolean(${a}); @@ -232,7 +232,7 @@ Error generating stack: `+u.message+` return false; } } - `)(n)}return!!e}catch(a){return B.warn(`Failed to evaluate script condition: ${e}`,a),!1}}showInitError(e){if(fd()){B.warn("Init failed while document is unloading — skipping error screen",e);return}const n=Cy(e);Cf.renderFromError("app",n,"초기화 실패",this.config.debug,{templateId:this.config.templateId,locale:this.config.locale},void 0,e)}showTransitionOverlay(e,n){const a=typeof e=="boolean"?{enabled:e,style:"opaque",target:void 0,skeleton:void 0,spinner:void 0}:{enabled:e.enabled,style:e.style||"opaque",target:e.target,skeleton:e.skeleton,spinner:e.spinner,fallback_target:e.fallback_target};if(!a.enabled)return;if(this.hideTransitionOverlay(),a.style==="skeleton"&&a.skeleton?.component&&a.target&&n){this.renderSkeletonOverlay(a.target,a.skeleton,n,a.fallback_target);return}if(a.style==="spinner"&&a.target){this.renderSpinnerOverlay(a.target,a.spinner,a.fallback_target);return}const i=document.documentElement.classList.contains("dark");let l,c="";switch(a.style){case"blur":l=i?"rgba(17,24,39,0.3)":"rgba(255,255,255,0.3)",c="backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);";break;case"fade":l=i?"rgba(17,24,39,0.8)":"rgba(255,255,255,0.8)";break;case"skeleton":l=i?"rgb(17,24,39)":"rgb(249,250,251)";break;default:l=i?"rgb(17,24,39)":"rgb(249,250,251)";break}if(a.target){const d=`#${CSS.escape(a.target)}`,f=document.createElement("style");f.id="g7-transition-overlay",f.textContent=`${d}{position:relative;z-index:0;}${d}::after{content:'';position:absolute;inset:0;background:${l};${c}z-index:2147483647;pointer-events:none;}`,document.head.appendChild(f),this.transitionOverlayEl=f}else{const d=document.createElement("div");d.id="g7-transition-overlay",d.setAttribute("aria-hidden","true"),d.style.position="fixed",d.style.inset="0",d.style.zIndex="9999",d.style.pointerEvents="none",d.style.background=l;for(const f of c.split(";")){const g=f.indexOf(":");g!==-1&&d.style.setProperty(f.slice(0,g).trim(),f.slice(g+1).trim())}document.body.appendChild(d),this.transitionOverlayEl=d}}hideTransitionOverlay(){this.transitionOverlayEl&&(this.transitionOverlayEl.remove(),this.transitionOverlayEl=null),this.hideSkeletonOverlay()}renderSkeletonOverlay(e,n,a,i){const c=mr.getInstance().getComponent(n.component);if(!c){B.log(`Skeleton component "${n.component}" not found in registry, falling back to opaque overlay`),this.showTransitionOverlay({enabled:!0,style:"opaque",target:e});return}let d=document.getElementById(e),f="target";if(!d&&i&&(d=document.getElementById(i),f="fallback"),d||(d=document.getElementById("app"),f="fullpage"),!d){B.log(`Skeleton overlay: no target found (target="#${e}", fallback="${i||"none"}"), falling back to opaque overlay`),this.showTransitionOverlay({enabled:!0,style:"opaque",target:e});return}this.hideSkeletonOverlay();const m=document.documentElement.classList.contains("dark")?"rgb(17,24,39)":"rgb(249,250,251)",y=f==="fullpage"?"app":f==="fallback"?i:e,S=`#${CSS.escape(y)}`,v=document.createElement("style");v.id="g7-skeleton-overlay-style",v.textContent=`${S}{position:relative;z-index:0;}${S}::after{content:'';position:absolute;inset:0;background:${m};z-index:2147483646;pointer-events:none;}`,document.head.appendChild(v),this.transitionOverlayEl=v;const _=document.createElement("div");if(_.id="g7-skeleton-overlay",_.setAttribute("role","status"),_.setAttribute("aria-busy","true"),_.setAttribute("aria-label","Loading..."),f==="fullpage")_.style.cssText=["position:fixed","inset:0","z-index:20","overflow:hidden","pointer-events:none",`background:${m}`].join(";")+";";else{const T=d.getBoundingClientRect(),D=window.scrollX||document.documentElement.scrollLeft,z=window.scrollY||document.documentElement.scrollTop;_.style.cssText=["position:absolute",`top:${T.top+z}px`,`left:${T.left+D}px`,`width:${T.width}px`,`height:${T.height}px`,"z-index:20","overflow:hidden","pointer-events:none",`background:${m}`].join(";")+";"}document.body.appendChild(_),this.skeletonOverlayContainer=_;const A=a.components||[];let x;if(f==="fullpage")x=A;else{const T=f==="fallback"?i:e;x=this.findComponentChildrenById(A,T)}const O=od.createRoot(_);this.skeletonOverlayRoot=O,ka.flushSync(()=>{O.render(Xe.createElement(c,{components:x,options:{animation:n.animation||"pulse",iteration_count:n.iteration_count||5}}))});const M=f==="fullpage"?"fullpage (#app)":f==="fallback"?`fallback (#${i})`:`target (#${e})`;B.log(`Skeleton overlay rendered [${M}] with "${n.component}" (${x.length} components)`)}findComponentChildrenById(e,n){const a=i=>{for(const l of i){if(l.id===n)return l.children||[];if(l.children&&Array.isArray(l.children)){const c=a(l.children);if(c!==null)return c}}return null};return a(e)||e}renderSpinnerOverlay(e,n,a){let i=document.getElementById(e),l="target";if(!i&&a&&(i=document.getElementById(a),l="fallback"),i||(i=document.getElementById("app"),l="fullpage"),!i){B.log(`Spinner overlay: no target found (target="#${e}", fallback="${a||"none"}"), falling back to opaque overlay`),this.showTransitionOverlay({enabled:!0,style:"opaque",target:e});return}this.hideSkeletonOverlay();const c=l==="fullpage"?"app":l==="fallback"?a:e,d=`#${CSS.escape(c)}`,f=document.createElement("style");f.id="g7-skeleton-overlay-style",f.textContent=[`${d}{position:relative;}`,"@keyframes g7-spin{to{transform:rotate(360deg)}}"].join(""),document.head.appendChild(f),this.transitionOverlayEl=f;const g=n?.text||window.G7Core?.t?.("nav.loading")||"";this._spinnerState={target:e,fallbackTarget:a,spinnerConfig:n,resolvedText:g},this._mountSpinnerInTarget(i);const m=l==="fullpage"?"fullpage (#app)":l==="fallback"?`fallback (#${a})`:`target (#${e})`,y=n?.component||"default spinner";B.log(`Spinner overlay rendered [${m}] with "${y}"`)}_mountSpinnerInTarget(e){if(!this._spinnerState)return;const{spinnerConfig:n,resolvedText:a}=this._spinnerState;if(this.skeletonOverlayRoot){try{this.skeletonOverlayRoot.unmount()}catch{}this.skeletonOverlayRoot=null}if(this.skeletonOverlayContainer){try{this.skeletonOverlayContainer.remove()}catch{}this.skeletonOverlayContainer=null}const i=document.createElement("div");if(i.id="g7-skeleton-overlay",i.setAttribute("role","status"),i.setAttribute("aria-busy","true"),e.appendChild(i),this.skeletonOverlayContainer=i,n?.component){const c=mr.getInstance().getComponent(n.component);if(c){const d=od.createRoot(i);this.skeletonOverlayRoot=d,ka.flushSync(()=>{d.render(Xe.createElement(c,{options:{text:a}}))});return}B.log(`Spinner component "${n.component}" not found in registry, using default spinner`)}i.innerHTML='
'}reattachSpinnerOverlay(){if(!this._spinnerState)return;const{target:e,fallbackTarget:n}=this._spinnerState;let a=document.getElementById(e);!a&&n&&(a=document.getElementById(n)),a||(a=document.getElementById("app")),a&&(this._mountSpinnerInTarget(a),B.log("Spinner overlay reattached to new DOM target"))}hideSkeletonOverlay(){if(this.skeletonOverlayRoot){try{this.skeletonOverlayRoot.unmount()}catch{}this.skeletonOverlayRoot=null}if(this.skeletonOverlayContainer){try{this.skeletonOverlayContainer.parentNode&&this.skeletonOverlayContainer.remove()}catch{}this.skeletonOverlayContainer=null}const e=document.getElementById("g7-skeleton-overlay-style");e&&e.remove(),this._spinnerState=null}showRouteError(e){if(e instanceof Er&&e.details?.status===401){const a=this.config.templateId||"",i=window.location.pathname,l=a.includes("admin")||i.startsWith("/admin")?"admin":"user",c=i+window.location.search,d=!!Hr().getToken()||e.details?.hadToken===!0,f=br.getInstance().getLoginRedirectUrl(l,c,d?"session_expired":void 0);window.location.href=f;return}const n=Cy(e);Cf.renderFromError("app",n,"페이지 로딩 실패",this.config.debug,{templateId:this.config.templateId,locale:this.config.locale},void 0,e)}async handleServerError(){const e=window.G7Error;if(!e)return!1;B.warn("Server error detected:",e);try{return this.errorPageHandler&&(e.data&&(this.globalState.errorData=e.data),this.errorPageHandler.updateGlobalState(this.globalState),this.errorPageHandler.updateLocale(this.config.locale),await this.errorPageHandler.renderError(e.code,"app"))?(B.log(`${e.code} error page rendered successfully`),!0):(B.log(`Falling back to ErrorDisplay for ${e.code}`),this.showInitError(new Error(`Service unavailable (${e.code})`)),!0)}catch(n){return B.error(`Failed to render ${e.code} page:`,n),this.showInitError(new Error(`Service unavailable (${e.code})`)),!0}}async handleRouteNotFound(e){B.warn("Route not found:",e);try{if(this.errorPageHandler&&(this.errorPageHandler.updateGlobalState(this.globalState),this.errorPageHandler.updateLocale(this.config.locale),await this.errorPageHandler.renderError(404,"app"))){B.log("404 error page rendered successfully");return}B.log("Falling back to ErrorDisplay for 404"),this.showRouteError(new Error(`Page not found: ${e}`))}catch(n){B.error("Failed to render 404 page:",n),this.showRouteError(new Error(`Page not found: ${e}`))}}getRouter(){return this.router}getConfig(){return this.config}getLayoutLoader(){return this.layoutLoader}async reloadExtensionState(){B.log("reloadExtensionState: start");let e;try{const n=await fetch(Nr(`/api/templates/${this.config.templateId}/config`,"json",null,`_=${Date.now()}`));if(n.ok){const a=await n.json();a?.success&&a?.data?.cache_version!==void 0&&(e=a.data.cache_version)}}catch(n){B.warn("reloadExtensionState: failed to fetch config.json",n)}e!==void 0&&e!==this.extensionCacheVersion&&(B.log(`reloadExtensionState: cache version ${this.extensionCacheVersion} -> ${e}`),this.extensionCacheVersion=e,this.saveCacheVersionToStorage(e));try{this.router&&(await this.router.loadRoutes(this.extensionCacheVersion),B.log("reloadExtensionState: routes reloaded"))}catch(n){B.error("reloadExtensionState: routes reload failed",n)}try{this.layoutLoader&&(this.extensionCacheVersion>0&&this.layoutLoader.setCacheVersion(this.extensionCacheVersion),this.layoutLoader.clear(),B.log("reloadExtensionState: layout cache cleared"))}catch(n){B.error("reloadExtensionState: layout cache clear failed",n)}try{const{TranslationEngine:n}=await Promise.resolve().then(()=>gp),a=n.getInstance();this.extensionCacheVersion>0&&a.setCacheVersion(this.extensionCacheVersion);const i=this.config.locale||"ko",l="en";await a.loadTranslations(this.config.templateId,i,"/api",!0),i!==l&&await a.loadTranslations(this.config.templateId,l,"/api",!0),B.log("reloadExtensionState: translations reloaded")}catch(n){B.error("reloadExtensionState: translations reload failed",n)}try{const n=await fetch(`/api/locales/active?_=${Date.now()}`);if(n.ok){const i=(await n.json())?.data?.locales;if(Array.isArray(i)&&i.length>0){const l=this.globalState.appConfig??{};this.setGlobalState({appConfig:{...l,supportedLocales:i}}),B.log("reloadExtensionState: supportedLocales refreshed",i)}}}catch(n){B.warn("reloadExtensionState: supportedLocales refresh failed",n)}B.log("reloadExtensionState: done")}getActionDispatcher(){return ea().actionDispatcher}async changeLocale(e){try{if(B.log("Changing locale to:",e),this.config.locale===e){B.log("Locale is already",e);return}this.config.locale=e,this.saveLocaleToStorage(e),await this.saveLocaleToDatabase(e),this.layoutLoader&&(this.layoutLoader.clear(),B.log("Layout cache cleared")),this.errorPageHandler&&(this.errorPageHandler.updateLocale(e),B.log("ErrorPageHandler locale updated")),xc(),await No({templateId:this.config.templateId,templateType:this.config.templateType,locale:e,debug:this.config.debug,cacheVersion:this.extensionCacheVersion}),B.log("Template Engine re-initialized with new locale");const{getActionDispatcher:n}=await Promise.resolve().then(()=>Pr),a=n();a&&(a.setDefaultContext({navigate:i=>this.router?.navigate(i)}),a.setGlobalStateUpdater((i,l)=>this.setGlobalState(i,l)),B.log("Navigate function and setGlobalState re-injected to ActionDispatcher")),this.reinitializeModuleHandlers(),B.log("Module handlers re-initialized"),this.reinitializeTemplateHandlers(),B.log("Template handlers re-initialized"),this.reinitializePluginHandlers(),B.log("Plugin handlers re-initialized"),this.router&&this.router.navigateToCurrentPath(),B.log("Locale changed successfully to",e)}catch(n){throw B.error("Failed to change locale:",n),n}}loadLocaleFromStorage(){try{return localStorage.getItem(xr.LOCALE_STORAGE_KEY)}catch(e){return B.warn("Failed to load locale from storage:",e),null}}saveLocaleToStorage(e){try{localStorage.setItem(xr.LOCALE_STORAGE_KEY,e)}catch(n){B.warn("Failed to save locale to storage:",n)}}async saveLocaleToDatabase(e){const n=localStorage.getItem("auth_token");if(!n){B.log("No auth token, skipping DB locale save");return}let a,i;if(this.config.localeApi)a=this.config.localeApi.endpoint,i=this.config.localeApi.method;else{const l=this.config.templateType==="admin";a=l?"/api/admin/users/me/language":"/api/user/profile/update-language",i=l?"PATCH":"POST"}try{const l=this.getXsrfToken(),c=await fetch(a,{method:i,headers:{"Content-Type":"application/json",Accept:"application/json",...l&&{"X-XSRF-TOKEN":l},Authorization:`Bearer ${n}`},credentials:"include",body:JSON.stringify({language:e})});c.ok?B.log("Locale saved to DB:",e):B.warn("Failed to save locale to DB (UI will still change):",c.statusText)}catch(l){B.warn("Failed to call locale API (UI will still change):",l)}}getXsrfToken(){if(typeof document>"u")return null;const n=`; ${document.cookie}`.split("; XSRF-TOKEN=");return n.length===2?decodeURIComponent(n.pop()?.split(";").shift()||""):null}loadCacheVersionFromStorage(){try{const e=localStorage.getItem(xr.CACHE_VERSION_STORAGE_KEY);return e?parseInt(e,10):null}catch(e){return B.warn("Failed to load cache version from storage:",e),null}}saveCacheVersionToStorage(e){try{localStorage.setItem(xr.CACHE_VERSION_STORAGE_KEY,String(e))}catch(n){B.warn("Failed to save cache version to storage:",n)}}reinitializeModuleHandlers(){if(typeof window>"u")return;const e="__",n=window;Object.keys(n).forEach(a=>{if(a.startsWith(e)&&typeof n[a]?.initModule=="function")try{n[a].initModule(),B.log(`Module handler re-initialized: ${a}`)}catch(i){B.warn(`Failed to re-initialize module handlers for ${a}:`,i)}})}reinitializeTemplateHandlers(){if(typeof window>"u")return;const e=window.G7TemplateHandlers;if(!e){B.warn("Template handlers not found on window.G7TemplateHandlers");return}const n=this.getActionDispatcher();if(!n){B.warn("ActionDispatcher not available for template handler registration");return}Object.entries(e).forEach(([a,i])=>{n.registerHandler(a,i)}),B.log(`${Object.keys(e).length} template handler(s) re-registered:`,Object.keys(e))}reinitializePluginHandlers(){if(typeof window>"u")return;const e="__",n=window;Object.keys(n).forEach(a=>{if(a.startsWith(e)&&typeof n[a]?.initPlugin=="function")try{n[a].initPlugin(),B.log(`Plugin handler re-initialized: ${a}`)}catch(i){B.warn(`Failed to re-initialize plugin handlers for ${a}:`,i)}})}getLocale(){return this.config.locale}getErrorPageHandler(){return this.errorPageHandler}getGlobalState(){return{...this.globalState}}setGlobalState(e,n){const a={...this.globalState};typeof e=="function"?this.globalState=e(this.globalState):this.globalState={...this.globalState,...e},B.log("Global state updated:",this.globalState),window.G7Core?.devTools?.captureStateSnapshot?.({source:"setGlobalState",prev:a,next:this.globalState}),this.globalStateListeners.forEach(l=>{l(this.globalState)}),n?.render!==!1&&Promise.resolve().then(()=>Pr).then(({updateTemplateData:l,getState:c})=>{const d=c();d.reactRoot&&d.currentLayoutJson&&l({_global:{...this.globalState},_local:this.globalState._local||{}})})}onGlobalStateChange(e){this.globalStateListeners.add(e)}offGlobalStateChange(e){this.globalStateListeners.delete(e)}async refetchDataSource(e,n){let a=this.currentDataSources.find(i=>i.id===e);if(!a)for(const[,i]of this.modalDataSources){const l=i.find(c=>c.id===e);if(l){a=l;break}}if(!a){B.warn(`Data source not found: ${e}`);return}B.log(`Refetching data source: ${e}`,n?.sync?"(sync mode)":"",n?.globalStateOverride?"(with global override)":"",n?.localStateOverride?"(with local override)":""),jr.setPending(!0);try{const i=new _r;this.currentGlobalHeaders.length>0&&i.setGlobalHeaders(this.currentGlobalHeaders);const l=ea(),c=l.currentDataContext?._global||{},d=n?.globalStateOverride?{...c,...n.globalStateOverride}:c,f=l.currentDataContext?._local||{},g=n?.localStateOverride?{...f,...n.localStateOverride}:f,y=(await i.fetchDataSourcesWithResults([a],this.currentRouteParams,this.currentQueryParams,d,g,{ignoreAutoFetch:!0}))[0];if(y.state==="success"&&y.data!==void 0){this.currentFetchedData[e]=y.data;const S={};this.processInitOptions([a],{[e]:y.data},S);const v=ea();if(v.bindingEngine){const A=[e];a.initGlobal&&A.push("_global"),a.initLocal&&A.push("_local"),v.bindingEngine.invalidateCacheByKeys(A)}const _={[e]:y.data};return Object.keys(S).length>0&&(_._localInit=S),a.initGlobal&&(_._global={...this.globalState}),ys(_,n?.sync?{sync:!0}:void 0),B.log(`Data source refetched successfully: ${e}`),y.data}else if(y.state==="error"){B.error(`Failed to refetch data source: ${e}`,y.error);return}}catch(i){B.error(`Error refetching data source: ${e}`,i);return}finally{jr.setPending(!1)}}registerModalDataSources(e,n){this.modalDataSources.set(e,n),B.log(`Modal data sources registered: ${e} (${n.length} sources)`)}unregisterModalDataSources(e){this.modalDataSources.delete(e),B.log(`Modal data sources unregistered: ${e}`)}getDataSource(e){return this.currentFetchedData[e]}setDataSource(e,n,a){if(!e){B.warn("setDataSource: dataSourceId is required");return}const{merge:i=!1,sync:l=!1}=a||{};i&&this.currentFetchedData[e]?this.currentFetchedData[e]={...this.currentFetchedData[e],...n}:this.currentFetchedData[e]=n,Promise.resolve().then(()=>Pr).then(({updateTemplateData:c})=>{c({[e]:this.currentFetchedData[e]},l?{sync:!0}:void 0)}),B.log(`setDataSource: Updated ${e}`,i?"(merged)":"(replaced)")}updateDataSourceItem(e,n,a,i,l){const{idField:c="id",merge:d=!0,skipRender:f=!1}=l||{},g=this.currentFetchedData[e];if(!g)return B.warn(`updateDataSourceItem: DataSource '${e}' not found`),!1;const m=this.parseItemPath(n);let y=g;for(const _ of m)if(y=y?.[_],y===void 0)return B.warn(`updateDataSourceItem: Path '${n}' not found in dataSource`),!1;if(!Array.isArray(y))return B.warn(`updateDataSourceItem: Target at '${n}' is not an array`),!1;const S=y.findIndex(_=>String(_[c])===String(a));if(S===-1)return B.warn(`updateDataSourceItem: Item with ${c}='${a}' not found`),!1;d?y[S]=this.deepMerge(y[S],i):y[S]={...y[S],...i};const v=window.G7Core?.devTools;return v?.isEnabled?.()&&v.trackDataSourceUpdate?.({dataSourceId:e,updateType:"partial",itemPath:n,itemId:a,updates:i,timestamp:Date.now()}),f||Promise.resolve().then(()=>Pr).then(({updateTemplateData:_})=>{_({[e]:this.currentFetchedData[e]},{sync:!1})}),B.log(`updateDataSourceItem: Updated ${e}.${n}[${c}=${a}]`),!0}parseItemPath(e){const n=[],a=/([^\.\[\]]+)|\[(\d+)\]/g;let i;for(;(i=a.exec(e))!==null;)i[1]!==void 0?n.push(i[1]):i[2]!==void 0&&n.push(parseInt(i[2],10));return n}deepMerge(e,n){if(n==null)return e;if(typeof n!="object"||Array.isArray(n))return n;const a={...e};for(const i of Object.keys(n))typeof n[i]=="object"&&n[i]!==null&&!Array.isArray(n[i])?a[i]=this.deepMerge(a[i]||{},n[i]):a[i]=n[i];return a}setValueAtPath(e,n,a,i="deep"){const l=n.split(".");let c=e;for(let g=0;g0?l.filterByCondition(this.currentRawDataSources,d):this.currentDataSources;this.currentDataSources=f;const g=f.filter(A=>A.auto_fetch!==!1&&A.type!=="websocket");if(g.length===0){B.log("No auto_fetch data sources to refetch");return}B.log(`Refetching ${g.length} auto_fetch data sources`),jr.setPending(!0);const m=ea().currentLayoutJson,y=m?.transition_overlay,S=Array.isArray(y?.wait_for)?y.wait_for:[],v=g.some(A=>(A.loading_strategy||"progressive")==="blocking"),_=S.length>0&&g.some(A=>S.includes(A.id)&&A.type!=="websocket"&&(A.loading_strategy||"progressive")!=="background");if((v||_)&&y&&typeof y=="object"){const A=n?.transitionOverlayTarget||y.target;y.enabled&&y.style==="skeleton"&&y.skeleton?.component&&A?this.renderSkeletonOverlay(A,y.skeleton,m,y.fallback_target):y.enabled&&y.style==="spinner"&&A&&this.renderSpinnerOverlay(A,y.spinner,y.fallback_target)}try{const A=new _r;this.currentGlobalHeaders.length>0&&A.setGlobalHeaders(this.currentGlobalHeaders);const x=ea(),O=x.currentDataContext?._global||{},M=await A.fetchDataSourcesWithResults(g,this.currentRouteParams,this.currentQueryParams,O,void 0,{ignoreAutoFetch:!1}),T={},D={},z=new Map(g.map(P=>[P.id,P]));for(const P of M){const q=z.get(P.id);if(!q){B.warn(`Refetch result id not found in autoFetchDataSources: ${P.id}`);continue}const W=P.id;P.state==="success"&&P.data!==void 0?(this.currentFetchedData[W]=P.data,T[W]=P.data,this.processInitOptions([q],{[W]:P.data},D),B.log(`Data source refetched: ${W}`)):P.state==="error"&&B.error(`Failed to refetch data source: ${W}`,P.error)}if(x.bindingEngine){const P=Object.keys(T);P.push("query"),x.bindingEngine.invalidateCacheByKeys(P)}if(Object.keys(D).length>0&&(T._localInit=D),Object.keys(T).length>0){if(T.query=_y(this.currentQueryParams),x.currentLayoutJson?.computed&&Object.keys(x.currentLayoutJson.computed).length>0){const P={...x.currentDataContext,...T,query:T.query},q=this.calculateComputed(x.currentLayoutJson.computed,P);Object.keys(q).length>0&&(T._computed=q,this.globalState._computed=q,B.log("Computed values recalculated in updateQueryParams:",Object.keys(q)))}ys(T,{sync:!0}),B.log("Template data updated with new query params")}}catch(A){B.error("Error in updateQueryParams:",A)}finally{jr.setPending(!1),this.hideTransitionOverlay()}}calculateComputed(e,n){const a={},i=new Dn;for(const[l,c]of Object.entries(e))try{let d;if(this.isComputedSwitchDefinition(c))d=i.resolveSwitch(c,n,{skipCache:!0});else if(typeof c=="string")if(c.startsWith("{{")&&c.endsWith("}}")){const f=c.slice(2,-2).trim();d=this.evaluateComputedExpression(f,n)}else d=c;a[l]=d,B.log(`Computed ${l}:`,d)}catch(d){B.warn(`Failed to calculate computed value: ${l}`,d),a[l]=void 0}return a}isComputedSwitchDefinition(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)&&"$switch"in e&&"$cases"in e}evaluateComputedExpression(e,n){try{return new Function("ctx",` + `)(n)}return!!e}catch(a){return U.warn(`Failed to evaluate script condition: ${e}`,a),!1}}showInitError(e){if(ld()){U.warn("Init failed while document is unloading — skipping error screen",e);return}const n=gy(e);vf.renderFromError("app",n,"초기화 실패",this.config.debug,{templateId:this.config.templateId,locale:this.config.locale},void 0,e)}showTransitionOverlay(e,n){const a=typeof e=="boolean"?{enabled:e,style:"opaque",target:void 0,skeleton:void 0,spinner:void 0}:{enabled:e.enabled,style:e.style||"opaque",target:e.target,skeleton:e.skeleton,spinner:e.spinner,fallback_target:e.fallback_target};if(!a.enabled)return;if(this.hideTransitionOverlay(),a.style==="skeleton"&&a.skeleton?.component&&a.target&&n){this.renderSkeletonOverlay(a.target,a.skeleton,n,a.fallback_target);return}if(a.style==="spinner"&&a.target){this.renderSpinnerOverlay(a.target,a.spinner,a.fallback_target);return}const i=document.documentElement.classList.contains("dark");let l,c="";switch(a.style){case"blur":l=i?"rgba(17,24,39,0.3)":"rgba(255,255,255,0.3)",c="backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);";break;case"fade":l=i?"rgba(17,24,39,0.8)":"rgba(255,255,255,0.8)";break;case"skeleton":l=i?"rgb(17,24,39)":"rgb(249,250,251)";break;default:l=i?"rgb(17,24,39)":"rgb(249,250,251)";break}if(a.target){const d=`#${CSS.escape(a.target)}`,f=document.createElement("style");f.id="g7-transition-overlay",f.textContent=`${d}{position:relative;z-index:0;}${d}::after{content:'';position:absolute;inset:0;background:${l};${c}z-index:2147483647;pointer-events:none;}`,document.head.appendChild(f),this.transitionOverlayEl=f}else{const d=document.createElement("div");d.id="g7-transition-overlay",d.setAttribute("aria-hidden","true"),d.style.position="fixed",d.style.inset="0",d.style.zIndex="9999",d.style.pointerEvents="none",d.style.background=l;for(const f of c.split(";")){const g=f.indexOf(":");g!==-1&&d.style.setProperty(f.slice(0,g).trim(),f.slice(g+1).trim())}document.body.appendChild(d),this.transitionOverlayEl=d}}hideTransitionOverlay(){this.transitionOverlayEl&&(this.transitionOverlayEl.remove(),this.transitionOverlayEl=null),this.hideSkeletonOverlay()}renderSkeletonOverlay(e,n,a,i){const c=pr.getInstance().getComponent(n.component);if(!c){U.log(`Skeleton component "${n.component}" not found in registry, falling back to opaque overlay`),this.showTransitionOverlay({enabled:!0,style:"opaque",target:e});return}let d=document.getElementById(e),f="target";if(!d&&i&&(d=document.getElementById(i),f="fallback"),d||(d=document.getElementById("app"),f="fullpage"),!d){U.log(`Skeleton overlay: no target found (target="#${e}", fallback="${i||"none"}"), falling back to opaque overlay`),this.showTransitionOverlay({enabled:!0,style:"opaque",target:e});return}this.hideSkeletonOverlay();const m=document.documentElement.classList.contains("dark")?"rgb(17,24,39)":"rgb(249,250,251)",b=f==="fullpage"?"app":f==="fallback"?i:e,S=`#${CSS.escape(b)}`,v=document.createElement("style");v.id="g7-skeleton-overlay-style",v.textContent=`${S}{position:relative;z-index:0;}${S}::after{content:'';position:absolute;inset:0;background:${m};z-index:2147483646;pointer-events:none;}`,document.head.appendChild(v),this.transitionOverlayEl=v;const _=document.createElement("div");if(_.id="g7-skeleton-overlay",_.setAttribute("role","status"),_.setAttribute("aria-busy","true"),_.setAttribute("aria-label","Loading..."),f==="fullpage")_.style.cssText=["position:fixed","inset:0","z-index:20","overflow:hidden","pointer-events:none",`background:${m}`].join(";")+";";else{const R=d.getBoundingClientRect(),D=window.scrollX||document.documentElement.scrollLeft,B=window.scrollY||document.documentElement.scrollTop;_.style.cssText=["position:absolute",`top:${R.top+B}px`,`left:${R.left+D}px`,`width:${R.width}px`,`height:${R.height}px`,"z-index:20","overflow:hidden","pointer-events:none",`background:${m}`].join(";")+";"}document.body.appendChild(_),this.skeletonOverlayContainer=_;const A=a.components||[];let x;if(f==="fullpage")x=A;else{const R=f==="fallback"?i:e;x=this.findComponentChildrenById(A,R)}const L=rd.createRoot(_);this.skeletonOverlayRoot=L,ka.flushSync(()=>{L.render(Ye.createElement(c,{components:x,options:{animation:n.animation||"pulse",iteration_count:n.iteration_count||5}}))});const M=f==="fullpage"?"fullpage (#app)":f==="fallback"?`fallback (#${i})`:`target (#${e})`;U.log(`Skeleton overlay rendered [${M}] with "${n.component}" (${x.length} components)`)}findComponentChildrenById(e,n){const a=i=>{for(const l of i){if(l.id===n)return l.children||[];if(l.children&&Array.isArray(l.children)){const c=a(l.children);if(c!==null)return c}}return null};return a(e)||e}renderSpinnerOverlay(e,n,a){let i=document.getElementById(e),l="target";if(!i&&a&&(i=document.getElementById(a),l="fallback"),i||(i=document.getElementById("app"),l="fullpage"),!i){U.log(`Spinner overlay: no target found (target="#${e}", fallback="${a||"none"}"), falling back to opaque overlay`),this.showTransitionOverlay({enabled:!0,style:"opaque",target:e});return}this.hideSkeletonOverlay();const c=l==="fullpage"?"app":l==="fallback"?a:e,d=`#${CSS.escape(c)}`,f=document.createElement("style");f.id="g7-skeleton-overlay-style",f.textContent=[`${d}{position:relative;}`,"@keyframes g7-spin{to{transform:rotate(360deg)}}"].join(""),document.head.appendChild(f),this.transitionOverlayEl=f;const g=n?.text||window.G7Core?.t?.("nav.loading")||"";this._spinnerState={target:e,fallbackTarget:a,spinnerConfig:n,resolvedText:g},this._mountSpinnerInTarget(i);const m=l==="fullpage"?"fullpage (#app)":l==="fallback"?`fallback (#${a})`:`target (#${e})`,b=n?.component||"default spinner";U.log(`Spinner overlay rendered [${m}] with "${b}"`)}_mountSpinnerInTarget(e){if(!this._spinnerState)return;const{spinnerConfig:n,resolvedText:a}=this._spinnerState;if(this.skeletonOverlayRoot){try{this.skeletonOverlayRoot.unmount()}catch{}this.skeletonOverlayRoot=null}if(this.skeletonOverlayContainer){try{this.skeletonOverlayContainer.remove()}catch{}this.skeletonOverlayContainer=null}const i=document.createElement("div");if(i.id="g7-skeleton-overlay",i.setAttribute("role","status"),i.setAttribute("aria-busy","true"),e.appendChild(i),this.skeletonOverlayContainer=i,n?.component){const c=pr.getInstance().getComponent(n.component);if(c){const d=rd.createRoot(i);this.skeletonOverlayRoot=d,ka.flushSync(()=>{d.render(Ye.createElement(c,{options:{text:a}}))});return}U.log(`Spinner component "${n.component}" not found in registry, using default spinner`)}i.innerHTML='
'}reattachSpinnerOverlay(){if(!this._spinnerState)return;const{target:e,fallbackTarget:n}=this._spinnerState;let a=document.getElementById(e);!a&&n&&(a=document.getElementById(n)),a||(a=document.getElementById("app")),a&&(this._mountSpinnerInTarget(a),U.log("Spinner overlay reattached to new DOM target"))}hideSkeletonOverlay(){if(this.skeletonOverlayRoot){try{this.skeletonOverlayRoot.unmount()}catch{}this.skeletonOverlayRoot=null}if(this.skeletonOverlayContainer){try{this.skeletonOverlayContainer.parentNode&&this.skeletonOverlayContainer.remove()}catch{}this.skeletonOverlayContainer=null}const e=document.getElementById("g7-skeleton-overlay-style");e&&e.remove(),this._spinnerState=null}showRouteError(e){if(e instanceof Cr&&e.details?.status===401){const a=this.config.templateId||"",i=window.location.pathname,l=a.includes("admin")||i.startsWith("/admin")?"admin":"user",c=i+window.location.search,d=!!Ir().getToken()||e.details?.hadToken===!0,f=yr.getInstance().getLoginRedirectUrl(l,c,d?"session_expired":void 0);window.location.href=f;return}const n=gy(e);vf.renderFromError("app",n,"페이지 로딩 실패",this.config.debug,{templateId:this.config.templateId,locale:this.config.locale},void 0,e)}async handleServerError(){const e=window.G7Error;if(!e)return!1;U.warn("Server error detected:",e);try{return this.errorPageHandler&&(e.data&&(this.globalState.errorData=e.data),this.errorPageHandler.updateGlobalState(this.globalState),this.errorPageHandler.updateLocale(this.config.locale),await this.errorPageHandler.renderError(e.code,"app"))?(U.log(`${e.code} error page rendered successfully`),!0):(U.log(`Falling back to ErrorDisplay for ${e.code}`),this.showInitError(new Error(`Service unavailable (${e.code})`)),!0)}catch(n){return U.error(`Failed to render ${e.code} page:`,n),this.showInitError(new Error(`Service unavailable (${e.code})`)),!0}}async handleRouteNotFound(e){U.warn("Route not found:",e);try{if(this.errorPageHandler&&(this.errorPageHandler.updateGlobalState(this.globalState),this.errorPageHandler.updateLocale(this.config.locale),await this.errorPageHandler.renderError(404,"app"))){U.log("404 error page rendered successfully");return}U.log("Falling back to ErrorDisplay for 404"),this.showRouteError(new Error(`Page not found: ${e}`))}catch(n){U.error("Failed to render 404 page:",n),this.showRouteError(new Error(`Page not found: ${e}`))}}getRouter(){return this.router}getConfig(){return this.config}getLayoutLoader(){return this.layoutLoader}async reloadExtensionState(){U.log("reloadExtensionState: start");let e;try{const n=await fetch($r(`/api/templates/${this.config.templateId}/config`,"json",null,`_=${Date.now()}`));if(n.ok){const a=await n.json();a?.success&&a?.data?.cache_version!==void 0&&(e=a.data.cache_version)}}catch(n){U.warn("reloadExtensionState: failed to fetch config.json",n)}e!==void 0&&e!==this.extensionCacheVersion&&(U.log(`reloadExtensionState: cache version ${this.extensionCacheVersion} -> ${e}`),this.extensionCacheVersion=e,this.saveCacheVersionToStorage(e));try{this.router&&(await this.router.loadRoutes(this.extensionCacheVersion),U.log("reloadExtensionState: routes reloaded"))}catch(n){U.error("reloadExtensionState: routes reload failed",n)}try{this.layoutLoader&&(this.extensionCacheVersion>0&&this.layoutLoader.setCacheVersion(this.extensionCacheVersion),this.layoutLoader.clear(),U.log("reloadExtensionState: layout cache cleared"))}catch(n){U.error("reloadExtensionState: layout cache clear failed",n)}try{const{TranslationEngine:n}=await Promise.resolve().then(()=>dp),a=n.getInstance();this.extensionCacheVersion>0&&a.setCacheVersion(this.extensionCacheVersion);const i=this.config.locale||"ko",l="en";await a.loadTranslations(this.config.templateId,i,"/api",!0),i!==l&&await a.loadTranslations(this.config.templateId,l,"/api",!0),U.log("reloadExtensionState: translations reloaded")}catch(n){U.error("reloadExtensionState: translations reload failed",n)}try{const n=await fetch(`/api/locales/active?_=${Date.now()}`);if(n.ok){const i=(await n.json())?.data?.locales;if(Array.isArray(i)&&i.length>0){const l=this.globalState.appConfig??{};this.setGlobalState({appConfig:{...l,supportedLocales:i}}),U.log("reloadExtensionState: supportedLocales refreshed",i)}}}catch(n){U.warn("reloadExtensionState: supportedLocales refresh failed",n)}U.log("reloadExtensionState: done")}getActionDispatcher(){return ea().actionDispatcher}async changeLocale(e){try{if(U.log("Changing locale to:",e),this.config.locale===e){U.log("Locale is already",e);return}this.config.locale=e,this.saveLocaleToStorage(e),await this.saveLocaleToDatabase(e),this.layoutLoader&&(this.layoutLoader.clear(),U.log("Layout cache cleared")),this.errorPageHandler&&(this.errorPageHandler.updateLocale(e),U.log("ErrorPageHandler locale updated")),Ec(),await Oo({templateId:this.config.templateId,templateType:this.config.templateType,locale:e,debug:this.config.debug,cacheVersion:this.extensionCacheVersion}),U.log("Template Engine re-initialized with new locale");const{getActionDispatcher:n}=await Promise.resolve().then(()=>Ur),a=n();a&&(a.setDefaultContext({navigate:i=>this.router?.navigate(i)}),a.setGlobalStateUpdater((i,l)=>this.setGlobalState(i,l)),U.log("Navigate function and setGlobalState re-injected to ActionDispatcher")),this.reinitializeModuleHandlers(),U.log("Module handlers re-initialized"),this.reinitializeTemplateHandlers(),U.log("Template handlers re-initialized"),this.reinitializePluginHandlers(),U.log("Plugin handlers re-initialized"),this.router&&this.router.navigateToCurrentPath(),U.log("Locale changed successfully to",e)}catch(n){throw U.error("Failed to change locale:",n),n}}loadLocaleFromStorage(){try{return localStorage.getItem(Ar.LOCALE_STORAGE_KEY)}catch(e){return U.warn("Failed to load locale from storage:",e),null}}saveLocaleToStorage(e){try{localStorage.setItem(Ar.LOCALE_STORAGE_KEY,e)}catch(n){U.warn("Failed to save locale to storage:",n)}}async saveLocaleToDatabase(e){const n=localStorage.getItem("auth_token");if(!n){U.log("No auth token, skipping DB locale save");return}let a,i;if(this.config.localeApi)a=this.config.localeApi.endpoint,i=this.config.localeApi.method;else{const l=this.config.templateType==="admin";a=l?"/api/admin/users/me/language":"/api/user/profile/update-language",i=l?"PATCH":"POST"}try{const l=this.getXsrfToken(),c=await fetch(a,{method:i,headers:{"Content-Type":"application/json",Accept:"application/json",...l&&{"X-XSRF-TOKEN":l},Authorization:`Bearer ${n}`},credentials:"include",body:JSON.stringify({language:e})});c.ok?U.log("Locale saved to DB:",e):U.warn("Failed to save locale to DB (UI will still change):",c.statusText)}catch(l){U.warn("Failed to call locale API (UI will still change):",l)}}getXsrfToken(){if(typeof document>"u")return null;const n=`; ${document.cookie}`.split("; XSRF-TOKEN=");return n.length===2?decodeURIComponent(n.pop()?.split(";").shift()||""):null}loadCacheVersionFromStorage(){try{const e=localStorage.getItem(Ar.CACHE_VERSION_STORAGE_KEY);return e?parseInt(e,10):null}catch(e){return U.warn("Failed to load cache version from storage:",e),null}}saveCacheVersionToStorage(e){try{localStorage.setItem(Ar.CACHE_VERSION_STORAGE_KEY,String(e))}catch(n){U.warn("Failed to save cache version to storage:",n)}}reinitializeModuleHandlers(){if(typeof window>"u")return;const e="__",n=window;Object.keys(n).forEach(a=>{if(a.startsWith(e)&&typeof n[a]?.initModule=="function")try{n[a].initModule(),U.log(`Module handler re-initialized: ${a}`)}catch(i){U.warn(`Failed to re-initialize module handlers for ${a}:`,i)}})}reinitializeTemplateHandlers(){if(typeof window>"u")return;const e=window.G7TemplateHandlers;if(!e){U.warn("Template handlers not found on window.G7TemplateHandlers");return}const n=this.getActionDispatcher();if(!n){U.warn("ActionDispatcher not available for template handler registration");return}Object.entries(e).forEach(([a,i])=>{n.registerHandler(a,i)}),U.log(`${Object.keys(e).length} template handler(s) re-registered:`,Object.keys(e))}reinitializePluginHandlers(){if(typeof window>"u")return;const e="__",n=window;Object.keys(n).forEach(a=>{if(a.startsWith(e)&&typeof n[a]?.initPlugin=="function")try{n[a].initPlugin(),U.log(`Plugin handler re-initialized: ${a}`)}catch(i){U.warn(`Failed to re-initialize plugin handlers for ${a}:`,i)}})}getLocale(){return this.config.locale}getErrorPageHandler(){return this.errorPageHandler}getGlobalState(){return{...this.globalState}}setGlobalState(e,n){const a={...this.globalState};typeof e=="function"?this.globalState=e(this.globalState):this.globalState={...this.globalState,...e},U.log("Global state updated:",this.globalState),window.G7Core?.devTools?.captureStateSnapshot?.({source:"setGlobalState",prev:a,next:this.globalState}),this.globalStateListeners.forEach(l=>{l(this.globalState)}),n?.render!==!1&&Promise.resolve().then(()=>Ur).then(({updateTemplateData:l,getState:c})=>{const d=c();d.reactRoot&&d.currentLayoutJson&&l({_global:{...this.globalState},_local:this.globalState._local||{}})})}onGlobalStateChange(e){this.globalStateListeners.add(e)}offGlobalStateChange(e){this.globalStateListeners.delete(e)}async refetchDataSource(e,n){let a=this.currentDataSources.find(i=>i.id===e);if(!a)for(const[,i]of this.modalDataSources){const l=i.find(c=>c.id===e);if(l){a=l;break}}if(!a){U.warn(`Data source not found: ${e}`);return}U.log(`Refetching data source: ${e}`,n?.sync?"(sync mode)":"",n?.globalStateOverride?"(with global override)":"",n?.localStateOverride?"(with local override)":""),Hr.setPending(!0);try{const i=new Er;this.currentGlobalHeaders.length>0&&i.setGlobalHeaders(this.currentGlobalHeaders);const l=ea(),c=l.currentDataContext?._global||{},d=n?.globalStateOverride?{...c,...n.globalStateOverride}:c,f=l.currentDataContext?._local||{},g=n?.localStateOverride?{...f,...n.localStateOverride}:f,b=(await i.fetchDataSourcesWithResults([a],this.currentRouteParams,this.currentQueryParams,d,g,{ignoreAutoFetch:!0}))[0];if(b.state==="success"&&b.data!==void 0){this.currentFetchedData[e]=b.data;const S={};this.processInitOptions([a],{[e]:b.data},S);const v=ea();if(v.bindingEngine){const A=[e];a.initGlobal&&A.push("_global"),a.initLocal&&A.push("_local"),v.bindingEngine.invalidateCacheByKeys(A)}const _={[e]:b.data};return Object.keys(S).length>0&&(_._localInit=S),a.initGlobal&&(_._global={...this.globalState}),gs(_,n?.sync?{sync:!0}:void 0),U.log(`Data source refetched successfully: ${e}`),b.data}else if(b.state==="error"){U.error(`Failed to refetch data source: ${e}`,b.error);return}}catch(i){U.error(`Error refetching data source: ${e}`,i);return}finally{Hr.setPending(!1)}}registerModalDataSources(e,n){this.modalDataSources.set(e,n),U.log(`Modal data sources registered: ${e} (${n.length} sources)`)}unregisterModalDataSources(e){this.modalDataSources.delete(e),U.log(`Modal data sources unregistered: ${e}`)}getDataSource(e){return this.currentFetchedData[e]}setDataSource(e,n,a){if(!e){U.warn("setDataSource: dataSourceId is required");return}const{merge:i=!1,sync:l=!1}=a||{};i&&this.currentFetchedData[e]?this.currentFetchedData[e]={...this.currentFetchedData[e],...n}:this.currentFetchedData[e]=n,Promise.resolve().then(()=>Ur).then(({updateTemplateData:c})=>{c({[e]:this.currentFetchedData[e]},l?{sync:!0}:void 0)}),U.log(`setDataSource: Updated ${e}`,i?"(merged)":"(replaced)")}updateDataSourceItem(e,n,a,i,l){const{idField:c="id",merge:d=!0,skipRender:f=!1}=l||{},g=this.currentFetchedData[e];if(!g)return U.warn(`updateDataSourceItem: DataSource '${e}' not found`),!1;const m=this.parseItemPath(n);let b=g;for(const _ of m)if(b=b?.[_],b===void 0)return U.warn(`updateDataSourceItem: Path '${n}' not found in dataSource`),!1;if(!Array.isArray(b))return U.warn(`updateDataSourceItem: Target at '${n}' is not an array`),!1;const S=b.findIndex(_=>String(_[c])===String(a));if(S===-1)return U.warn(`updateDataSourceItem: Item with ${c}='${a}' not found`),!1;d?b[S]=this.deepMerge(b[S],i):b[S]={...b[S],...i};const v=window.G7Core?.devTools;return v?.isEnabled?.()&&v.trackDataSourceUpdate?.({dataSourceId:e,updateType:"partial",itemPath:n,itemId:a,updates:i,timestamp:Date.now()}),f||Promise.resolve().then(()=>Ur).then(({updateTemplateData:_})=>{_({[e]:this.currentFetchedData[e]},{sync:!1})}),U.log(`updateDataSourceItem: Updated ${e}.${n}[${c}=${a}]`),!0}parseItemPath(e){const n=[],a=/([^\.\[\]]+)|\[(\d+)\]/g;let i;for(;(i=a.exec(e))!==null;)i[1]!==void 0?n.push(i[1]):i[2]!==void 0&&n.push(parseInt(i[2],10));return n}deepMerge(e,n){if(n==null)return e;if(typeof n!="object"||Array.isArray(n))return n;const a={...e};for(const i of Object.keys(n))typeof n[i]=="object"&&n[i]!==null&&!Array.isArray(n[i])?a[i]=this.deepMerge(a[i]||{},n[i]):a[i]=n[i];return a}setValueAtPath(e,n,a,i="deep"){const l=n.split(".");let c=e;for(let g=0;g0?l.filterByCondition(this.currentRawDataSources,d):this.currentDataSources;this.currentDataSources=f;const g=f.filter(A=>A.auto_fetch!==!1&&A.type!=="websocket");if(g.length===0){U.log("No auto_fetch data sources to refetch");return}U.log(`Refetching ${g.length} auto_fetch data sources`),Hr.setPending(!0);const m=ea().currentLayoutJson,b=m?.transition_overlay,S=Array.isArray(b?.wait_for)?b.wait_for:[],v=g.some(A=>(A.loading_strategy||"progressive")==="blocking"),_=S.length>0&&g.some(A=>S.includes(A.id)&&A.type!=="websocket"&&(A.loading_strategy||"progressive")!=="background");if((v||_)&&b&&typeof b=="object"){const A=n?.transitionOverlayTarget||b.target;b.enabled&&b.style==="skeleton"&&b.skeleton?.component&&A?this.renderSkeletonOverlay(A,b.skeleton,m,b.fallback_target):b.enabled&&b.style==="spinner"&&A&&this.renderSpinnerOverlay(A,b.spinner,b.fallback_target)}try{const A=new Er;this.currentGlobalHeaders.length>0&&A.setGlobalHeaders(this.currentGlobalHeaders);const x=ea(),L=x.currentDataContext?._global||{},M=await A.fetchDataSourcesWithResults(g,this.currentRouteParams,this.currentQueryParams,L,void 0,{ignoreAutoFetch:!1}),R={},D={},B=new Map(g.map(q=>[q.id,q]));for(const q of M){const P=B.get(q.id);if(!P){U.warn(`Refetch result id not found in autoFetchDataSources: ${q.id}`);continue}const K=q.id;q.state==="success"&&q.data!==void 0?(this.currentFetchedData[K]=q.data,R[K]=q.data,this.processInitOptions([P],{[K]:q.data},D),U.log(`Data source refetched: ${K}`)):q.state==="error"&&U.error(`Failed to refetch data source: ${K}`,q.error)}if(x.bindingEngine){const q=Object.keys(R);q.push("query"),x.bindingEngine.invalidateCacheByKeys(q)}if(Object.keys(D).length>0&&(R._localInit=D),Object.keys(R).length>0){if(R.query=my(this.currentQueryParams),x.currentLayoutJson?.computed&&Object.keys(x.currentLayoutJson.computed).length>0){const q={...x.currentDataContext,...R,query:R.query},P=this.calculateComputed(x.currentLayoutJson.computed,q);Object.keys(P).length>0&&(R._computed=P,this.globalState._computed=P,U.log("Computed values recalculated in updateQueryParams:",Object.keys(P)))}gs(R,{sync:!0}),U.log("Template data updated with new query params")}}catch(A){U.error("Error in updateQueryParams:",A)}finally{Hr.setPending(!1),this.hideTransitionOverlay()}}calculateComputed(e,n){const a={},i=new xn;for(const[l,c]of Object.entries(e))try{let d;if(this.isComputedSwitchDefinition(c))d=i.resolveSwitch(c,n,{skipCache:!0});else if(typeof c=="string")if(c.startsWith("{{")&&c.endsWith("}}")){const f=c.slice(2,-2).trim();d=this.evaluateComputedExpression(f,n)}else d=c;a[l]=d,U.log(`Computed ${l}:`,d)}catch(d){U.warn(`Failed to calculate computed value: ${l}`,d),a[l]=void 0}return a}isComputedSwitchDefinition(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)&&"$switch"in e&&"$cases"in e}evaluateComputedExpression(e,n){try{return new Function("ctx",` with(ctx) { try { return ${e}; @@ -240,5 +240,5 @@ Error generating stack: `+u.message+` return undefined; } } - `)(n)}catch(a){B.warn(`Expression evaluation failed: ${e}`,a);return}}async executeInitActions(e,n={}){const{getActionDispatcher:a}=await Promise.resolve().then(()=>Pr),i=a();if(!i){B.warn("ActionDispatcher not available for init_actions");return}B.log("Executing init_actions:",e.map(d=>d.handler)),B.log("Init actions dataContext:",n),B.log("Init actions dataContext._global:",n._global);const l=e.map(d=>d.handler).filter(d=>d.includes("."));l.length>0&&await this.waitForHandlers(i,l);let c={...n};for(const d of e)try{const f={type:"click",handler:d.handler,target:d.target,params:d.params,resultTo:d.resultTo,onSuccess:d.onSuccess,onError:d.onError,if:d.if,conditions:d.conditions,auth_mode:d.auth_mode,auth_required:d.auth_required},g=i.createHandler(f,c),m=new Event("init");await g(m),B.log(`Init action executed: ${d.handler}`);const y=this.globalState;c={...c,_global:{...y},_local:y._local||c._local||{}},B.log(`Data context refreshed after ${d.handler}:`,{cartKey:c._global?.cartKey})}catch(f){B.error(`Failed to execute init action: ${d.handler}`,f)}}async waitForHandlers(e,n,a=5e3){const i=Date.now(),l=50,c=()=>n.every(f=>e.customHandlers?.has(f));if(c()){B.log("All module handlers already registered");return}const d=_f().getFailedJsAssets();if(d.length>0){const f=d.some(y=>y==="module"||y==="plugin"),g=n.filter(y=>!e.customHandlers?.has(y)),m=g.length>0&&g.every(y=>d.some(S=>y.startsWith(`${S}.`)));if(f||m){B.warn("Extension asset load failed — not waiting for handlers that will never register:",{pending:g,failedAssets:d});return}}return B.log("Waiting for module handlers:",n),new Promise(f=>{const g=()=>{if(c()){B.log("All module handlers now registered"),f();return}if(Date.now()-i>=a){const m=n.filter(y=>!e.customHandlers?.has(y));B.warn("Timeout waiting for handlers:",m),f();return}setTimeout(g,l)};g()})}};$(xr,"LOCALE_STORAGE_KEY","g7_locale"),$(xr,"CACHE_VERSION_STORAGE_KEY","g7_cache_version");let Do=xr;function xf(o){const e=new Do(o);return o.websocket&&(Cc.configure(o.websocket),B.log("WebSocket 설정 완료")),document.readyState==="loading"?document.addEventListener("DOMContentLoaded",()=>{e.init(),window.__templateApp=e}):(e.init(),window.__templateApp=e),e}typeof window<"u"&&(window.G7Core=window.G7Core||{},window.G7Core.initTemplateApp=xf,window.G7Core.dataSource={refetch:async(o,e)=>{const n=window.__templateApp;if(!n){B.warn("TemplateApp not initialized (G7Core.dataSource.refetch)");return}return n.refetchDataSource(o,e)},get:o=>{const e=window.__templateApp;if(!e){B.warn("TemplateApp not initialized (G7Core.dataSource.get)");return}return e.getDataSource(o)}});function bA(o={}){const{checked:e,value:n,name:a,type:i="checkbox"}=o,c=n!==void 0?n:i==="checkbox"||i==="radio"?e??!1:String(e??""),d={checked:e??!1,value:c,name:a??"",type:i};return{target:d,currentTarget:d,preventDefault:()=>{},stopPropagation:()=>{},nativeEvent:new Event("change"),bubbles:!0,cancelable:!0,defaultPrevented:!1,eventPhase:Event.AT_TARGET,isTrusted:!1,timeStamp:Date.now(),type:"change",isDefaultPrevented:()=>!1,isPropagationStopped:()=>!1,persist:()=>{}}}function vA(o={}){const{button:e=0,clientX:n=0,clientY:a=0}=o;return{button:e,clientX:n,clientY:a,preventDefault:()=>{},stopPropagation:()=>{},nativeEvent:new MouseEvent("click"),bubbles:!0,cancelable:!0,defaultPrevented:!1,eventPhase:Event.AT_TARGET,isTrusted:!1,timeStamp:Date.now(),type:"click",isDefaultPrevented:()=>!1,isPropagationStopped:()=>!1,persist:()=>{},target:document.createElement("div"),currentTarget:document.createElement("div")}}function SA(){return{preventDefault:()=>{},stopPropagation:()=>{},nativeEvent:new Event("submit"),bubbles:!0,cancelable:!0,defaultPrevented:!1,eventPhase:Event.AT_TARGET,isTrusted:!1,timeStamp:Date.now(),type:"submit",isDefaultPrevented:()=>!1,isPropagationStopped:()=>!1,persist:()=>{},target:document.createElement("form"),currentTarget:document.createElement("form")}}function wA(o,e="keydown"){return{key:o,code:o.length===1?`Key${o.toUpperCase()}`:o,preventDefault:()=>{},stopPropagation:()=>{},nativeEvent:new KeyboardEvent(e,{key:o}),bubbles:!0,cancelable:!0,defaultPrevented:!1,eventPhase:Event.AT_TARGET,isTrusted:!1,timeStamp:Date.now(),type:e,isDefaultPrevented:()=>!1,isPropagationStopped:()=>!1,persist:()=>{},target:document.createElement("div"),currentTarget:document.createElement("div"),altKey:!1,ctrlKey:!1,metaKey:!1,shiftKey:!1,repeat:!1}}const CA={justify:/^justify-(start|end|center|between|around|evenly)$/,items:/^items-(start|end|center|baseline|stretch)$/,content:/^content-(start|end|center|between|around|evenly|stretch)$/,self:/^self-(auto|start|end|center|stretch|baseline)$/,flex:/^flex-(row|row-reverse|col|col-reverse|wrap|wrap-reverse|nowrap|1|auto|initial|none)$/,grow:/^(grow|grow-0)$/,shrink:/^(shrink|shrink-0)$/,basis:/^basis-/,order:/^(order-|-)order-/,gap:/^gap(-x|-y)?-/,gridCols:/^grid-cols-/,gridRows:/^grid-rows-/,colSpan:/^col-(span-|start-|end-)/,rowSpan:/^row-(span-|start-|end-)/,display:/^(block|inline-block|inline|flex|inline-flex|table|inline-table|table-caption|table-cell|table-column|table-column-group|table-footer-group|table-header-group|table-row-group|table-row|flow-root|grid|inline-grid|contents|list-item|hidden)$/,position:/^(static|fixed|absolute|relative|sticky)$/,inset:/^(inset|top|right|bottom|left)-/,zIndex:/^z-/,width:/^w-/,minWidth:/^min-w-/,maxWidth:/^max-w-/,height:/^h-/,minHeight:/^min-h-/,maxHeight:/^max-h-/,padding:/^p[xytblr]?-/,margin:/^-?m[xytblr]?-/,space:/^space-(x|y)-/,fontSize:/^text-(xs|sm|base|lg|xl|2xl|3xl|4xl|5xl|6xl|7xl|8xl|9xl)$/,fontWeight:/^font-(thin|extralight|light|normal|medium|semibold|bold|extrabold|black)$/,fontStyle:/^(italic|not-italic)$/,fontFamily:/^font-(sans|serif|mono)/,textAlign:/^text-(left|center|right|justify|start|end)$/,textColor:/^text-(inherit|current|transparent|black|white|slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-/,textDecoration:/^(underline|overline|line-through|no-underline)$/,textTransform:/^(uppercase|lowercase|capitalize|normal-case)$/,lineHeight:/^leading-/,letterSpacing:/^tracking-/,textOverflow:/^(truncate|text-ellipsis|text-clip)$/,whitespace:/^whitespace-/,wordBreak:/^break-/,bgColor:/^bg-(inherit|current|transparent|black|white|slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-/,bgGradient:/^bg-gradient-/,bgSize:/^bg-(auto|cover|contain)$/,bgPosition:/^bg-(bottom|center|left|left-bottom|left-top|right|right-bottom|right-top|top)$/,bgRepeat:/^bg-(repeat|no-repeat|repeat-x|repeat-y|repeat-round|repeat-space)$/,bgAttachment:/^bg-(fixed|local|scroll)$/,bgClip:/^bg-clip-/,bgOrigin:/^bg-origin-/,borderWidth:/^border(-[xytblr])?(-0|-2|-4|-8)?$/,borderColor:/^border-(inherit|current|transparent|black|white|slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-/,borderStyle:/^border-(solid|dashed|dotted|double|hidden|none)$/,borderRadius:/^rounded(-[tblrse]{1,2})?(-none|-sm|-md|-lg|-xl|-2xl|-3xl|-full)?$/,ringWidth:/^ring(-0|-1|-2|-4|-8|-inset)?$/,ringColor:/^ring-(inherit|current|transparent|black|white|slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-/,ringOffset:/^ring-offset-/,shadow:/^shadow(-sm|-md|-lg|-xl|-2xl|-inner|-none)?$/,opacity:/^opacity-/,mixBlend:/^mix-blend-/,bgBlend:/^bg-blend-/,blur:/^blur(-none|-sm|-md|-lg|-xl|-2xl|-3xl)?$/,brightness:/^brightness-/,contrast:/^contrast-/,grayscale:/^grayscale(-0)?$/,hueRotate:/^-?hue-rotate-/,invert:/^invert(-0)?$/,saturate:/^saturate-/,sepia:/^sepia(-0)?$/,backdropBlur:/^backdrop-blur-/,backdropBrightness:/^backdrop-brightness-/,backdropContrast:/^backdrop-contrast-/,backdropGrayscale:/^backdrop-grayscale-/,backdropHueRotate:/^backdrop-hue-rotate-/,backdropInvert:/^backdrop-invert-/,backdropOpacity:/^backdrop-opacity-/,backdropSaturate:/^backdrop-saturate-/,backdropSepia:/^backdrop-sepia-/,scale:/^scale(-x|-y)?-/,rotate:/^-?rotate-/,translate:/^-?translate-[xy]-/,skew:/^-?skew-[xy]-/,transformOrigin:/^origin-/,transition:/^transition(-none|-all|-colors|-opacity|-shadow|-transform)?$/,duration:/^duration-/,ease:/^ease-(linear|in|out|in-out)$/,delay:/^delay-/,animate:/^animate-/,cursor:/^cursor-/,userSelect:/^select-/,pointerEvents:/^pointer-events-/,resize:/^resize(-none|-x|-y)?$/,scrollBehavior:/^scroll-(auto|smooth)$/,touchAction:/^touch-/,overflow:/^overflow(-x|-y)?-(auto|hidden|clip|visible|scroll)$/,overscroll:/^overscroll(-x|-y)?-(auto|contain|none)$/,visibility:/^(visible|invisible|collapse)$/,aspectRatio:/^aspect-/,columns:/^columns-/,breakAfter:/^break-after-/,breakBefore:/^break-before-/,breakInside:/^break-inside-/,boxDecorationBreak:/^box-decoration-/,boxSizing:/^box-(border|content)$/,float:/^float-(right|left|none)$/,clear:/^clear-(left|right|both|none)$/,isolation:/^(isolate|isolation-auto)$/,objectFit:/^object-(contain|cover|fill|none|scale-down)$/,objectPosition:/^object-/};function Ay(o){const e=o.replace(/^(dark:|hover:|focus:|active:|disabled:|group-hover:|sm:|md:|lg:|xl:|2xl:)+/,"");for(const[n,a]of Object.entries(CA))if(a.test(e))return n;return null}function xy(o){const e=o.match(/^((dark:|hover:|focus:|active:|disabled:|group-hover:|sm:|md:|lg:|xl:|2xl:)+)/);return e?e[1]:""}function EA(o,e){if(!e||e.trim()==="")return o;if(!o||o.trim()==="")return e;const n=o.split(/\s+/).filter(Boolean),a=e.split(/\s+/).filter(Boolean),i=new Map;for(const c of a){const d=Ay(c);if(d){const f=xy(c),g=`${d}:${f}`;i.set(g,c)}}return[...n.filter(c=>{const d=Ay(c);if(!d)return!0;const f=xy(c),g=`${d}:${f}`;return!i.has(g)}),...a].join(" ")}function _A(o){return Object.entries(o).filter(([,e])=>e).map(([e])=>e).join(" ")}function AA(...o){return o.filter(e=>typeof e=="string"&&e.length>0).join(" ")}const ye=ht("G7CoreGlobals");function xA(){window.React=Xe,window.ReactDOM={...ld,createPortal:ka.createPortal,unstable_batchedUpdates:o=>o()},window.ReactJSXRuntime=wC,ye.log("전역 객체 window.React, window.ReactDOM, window.ReactJSXRuntime에 노출됨")}function TA(o){const e=new Map,n=Oa.getInstance();o.componentEvent={on:(a,i)=>(e.has(a)||e.set(a,new Set),e.get(a).add(i),n.trackEventSubscribe(a),()=>{e.get(a)?.delete(i),n.trackEventUnsubscribe(a)}),emit:async(a,i)=>{const l=e.get(a),c=l?.size??0;if(!l||l.size===0)return n.trackEventEmit(a,i,0),[];let d,f=[];try{f=await Promise.all(Array.from(l).map(async g=>{try{return await g(i)}catch(m){throw ye.error(`componentEvent: Error in listener for "${a}":`,m),m}}))}catch(g){d=g instanceof Error?g:new Error(String(g))}if(n.trackEventEmit(a,i,c,f,d),d)throw d;return f},off:a=>{e.delete(a),n.trackEventOff(a)},clear:()=>{e.clear(),n.trackEventClear()}},ye.log("전역 객체 window.G7Core.componentEvent에 노출됨")}function RA(o){o.__runtime||(o.__runtime={DynamicRenderer:Ur,ComponentRegistry:mr,TranslationEngine:_a,DataSourceManager:_r,dataSourceManager:oA,DataBindingEngine:Dn,dataBindingEngine:Sd,ActionDispatcher:bo,TranslationReactContext:yf,TranslationProvider:vc,useTranslation:uy,ResponsiveContext:Kd,ResponsiveProvider:ic,useResponsive:Wd,responsiveManager:hi,BREAKPOINT_PRESETS:Ul,AuthManager:br,createLogger:ht,G7DevToolsCore:Oa},ye.log("전역 객체 window.G7Core.__runtime(코어 런타임 표면)에 노출됨"))}function kA(o){if(o.layoutEditor&&o.layoutEditor.__isStub!==!0||o.layoutEditor&&o.layoutEditor.__isStub===!0)return;const e=[],n=[];o.layoutEditor={__isStub:!0,__queue:e,__readyCallbacks:n,registerWidget:(a,i)=>e.push(["widget",a,i]),registerNodeEditor:(a,i)=>e.push(["nodeEditor",a,i]),registerCanvasOverlay:(a,i)=>e.push(["canvasOverlay",a,i]),onReady:a=>{typeof a=="function"&&n.push(a)}},ye.log("전역 객체 window.G7Core.layoutEditor 예약 접수함(stub) 노출됨")}function DA(o,e){o.useTranslation=uy,o.t=(n,a)=>{const i=e.getState();if(!i.translationEngine||!i.translationContext)return n;if(a){const l="|"+Object.entries(a).map(([c,d])=>`${c}=${d}`).join("|");return i.translationEngine.translate(n,i.translationContext,l)}return i.translationEngine.translate(n,i.translationContext)},ye.log("전역 객체 window.G7Core.useTranslation에 노출됨")}function OA(o,e){o.createChangeEvent=bA,o.createClickEvent=vA,o.createSubmitEvent=SA,o.createKeyboardEvent=wA,o.uuid=()=>typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,n=>{const a=Math.random()*16|0;return(n==="x"?a:a&3|8).toString(16)}),o.createLogger=ht,ye.log("전역 객체 window.G7Core.createLogger에 노출됨"),o.renderItemChildren=(n,a,i,l,c)=>{const d=e.getState(),m={_global:window.__templateApp?.getGlobalState?.()||{},_local:a._local||{},_computed:a._computed||{},...a},y=new Set,S=(_,A)=>{const x=m._global?._remountKeys;return _&&x?.[_]&&!y.has(_)?(y.add(_),`${_}-remount-${x[_]}`):_||A},v={translationContext:d.translationContext,translationEngine:d.translationEngine,bindingEngine:d.bindingEngine,actionDispatcher:d.actionDispatcher,getRemountKey:S,...c};return lo(n,m,i,l,v)},o.evaluateCondition=(n,a)=>{if(!n)return!0;const i=e.getState(),c=window.__templateApp?.getGlobalState?.()||{},d={_global:c,_local:c._local||{},_computed:c._computed||{},...a||{}},f=i.bindingEngine??Sd;return vp(n,d,f)},o.getComponentMap=()=>mr.getInstance().getComponentMap(),o.renderComponentLayout=(n,a,i)=>{if(!n||!Array.isArray(n)||n.length===0)return null;const l=mr.getInstance().getComponentMap(),c=e.getState(),f=window.__templateApp?.getGlobalState?.()||{},g={_global:f._global||{},_local:f._local||{},_computed:f._computed||{},...a},m=new Set,y=(S,v)=>{const _=g._global?._remountKeys;return S&&_?.[S]&&!m.has(S)?(m.add(S),`${S}-remount-${_[S]}`):S||v};return lo(n,g,l,i,{translationContext:c.translationContext,translationEngine:c.translationEngine,bindingEngine:c.bindingEngine,actionDispatcher:c.actionDispatcher,getRemountKey:y})},o.renderExpandContent=n=>{const{children:a,row:i,expandContext:l,componentContext:c,keyPrefix:d}=n;if(!a||!Array.isArray(a)||a.length===0)return null;const f=n.componentMap||mr.getInstance().getComponentMap(),g=e.getState(),m=o.state?.get?.()||{},y=c?.stateRef?.current??c?.state??{},S={...m._local||{},...y},v=c?.computedRef?.current??m._computed??{},_=g.bindingEngine??o.getDataBindingEngine?.(),A={...m,_local:S,_computed:v,row:i,item:i,$item:i};let x={};if(l&&typeof l=="object"&&_)try{for(const[D,z]of Object.entries(l))if(typeof z=="string"){const P=Aa(z);if(P!==null)try{x[D]=Vn(P)?_.evaluatePipeExpression(P,A,{skipCache:!0}):_.evaluateExpression(P,A,{skipCache:!0})}catch{x[D]=void 0}else x[D]=_.resolveBindings(z,A,{skipCache:!0})}else x[D]=z}catch(D){ye.warn("renderExpandContent: expandContext 평가 오류:",D)}const O={row:i,item:i,$item:i,_local:S,_global:m._global||{},_computed:m._computed||{},...x},M=new Set,T=(D,z)=>{const P=O._global?._remountKeys;return D&&P?.[D]&&!M.has(D)?(M.add(D),`${D}-remount-${P[D]}`):D||z};return lo(a,O,f,d,{translationContext:g.translationContext,translationEngine:g.translationEngine??void 0,bindingEngine:g.bindingEngine??void 0,actionDispatcher:g.actionDispatcher,componentContext:c,getRemountKey:T})},o.$get=function(a,i,l=void 0){if(a==null)return l;const c=Array.isArray(i)?i:[i];if(c.length===0)return a;let d=a;for(const f of c){if(d==null||f==null)return l;d=d[f]}return d??l},ye.log("전역 객체 window.G7Core.renderItemChildren에 노출됨"),ye.log("전역 객체 window.G7Core.renderComponentLayout에 노출됨"),ye.log("전역 객체 window.G7Core.renderExpandContent에 노출됨"),ye.log("전역 객체 window.G7Core.$get에 노출됨")}function LA(o){o.dispatch=async(e,n)=>{const a=window.__templateApp;if(!a)return ye.warn("G7Core.dispatch: TemplateApp이 초기화되지 않았습니다."),{success:!1,error:new Error("TemplateApp이 초기화되지 않았습니다.")};const i=a.getActionDispatcher?.();if(!i)return ye.warn("G7Core.dispatch: ActionDispatcher를 찾을 수 없습니다."),{success:!1,error:new Error("ActionDispatcher를 찾을 수 없습니다.")};const l=a.getRouter?.(),c=a.getGlobalState?.(),d=a.setGlobalState?.bind(a),g=n?.componentContext||window.__g7ActionContext,m=window.__g7PendingLocalState;let y=g?.state??c;m&&g&&(y=m,ye.log("[dispatch] Using __g7PendingLocalState for context.state:",m));let S=g?.data??c;m&&g?.data?._local&&(S={...g.data,_local:m},ye.log("[dispatch] Updated context.data._local with pendingLocalState"));const v={navigate:l?(_,A)=>l.navigate(_,A):void 0,setState:g?.setState??d,state:y,data:S,_isDispatchFallbackContext:!g?.setState};if(e.debounce){const _=e.debounceKey||`dispatch-${e.handler}`,A=typeof e.debounce=="number"?e.debounce:e.debounce.delay;return i.debouncedCall(_,A,()=>{const{debounce:x,debounceKey:O,...M}=e;i.dispatchAction(M,v)}),{success:!0,debounced:!0}}try{return await i.dispatchAction(e,v)}catch(_){return ye.error("G7Core.dispatch: 액션 실행 오류:",_),{success:!1,error:_ instanceof Error?_:new Error(String(_))}}},ye.log("전역 객체 window.G7Core.dispatch에 노출됨")}function Oo(o){const e={};for(const[n,a]of Object.entries(o))if(n.includes(".")){const i=n.split(".");let l=e;for(let c=0;c0&&e.every(n=>/^\d+$/.test(n))}function Ry(o,e){const n={...o};for(const a of Object.keys(e))a in n?n[a]!==null&&typeof n[a]=="object"&&!Array.isArray(n[a])&&e[a]!==null&&typeof e[a]=="object"&&!Array.isArray(e[a])&&(n[a]=Ry(n[a],e[a])):n[a]=e[a];return n}function ir(o,e){if(Array.isArray(o)&&!Array.isArray(e)&&MA(e)){const a=Object.keys(e).map(l=>parseInt(l,10));if(!((a.length>0?Math.max(...a):0)>=o.length+a.length+10)){const l=[...o];for(const[c,d]of Object.entries(e)){const f=parseInt(c,10);f>=0&&f=l.length&&(l[f]=d)}return l}}const n={...o};for(const[a,i]of Object.entries(e))i!==null&&typeof i=="object"&&!Array.isArray(i)?n[a]!==null&&typeof n[a]=="object"?n[a]=ir(n[a],i):n[a]={...i}:n[a]=i;return n}function $A(o){o.state={get:()=>window.__templateApp?.getGlobalState?.()||{},set:(e,n)=>{const a=window.__templateApp;if(a?.setGlobalState&&a?.getGlobalState){const i=n?.merge||"deep",l=Oo(e);let c;if(i==="replace")c=l;else if(i==="shallow")c={...a.getGlobalState(),...l};else{const d=a.getGlobalState();c=ir(d,l)}a.setGlobalState(c,{render:n?.render})}else ye.warn("G7Core.state.set: TemplateApp이 초기화되지 않았습니다.")},setLocal:(e,n)=>{const a=window.__templateApp;if(n?.render===!1&&!n?.selfManaged){const x=window.__g7AutoBindingPaths;x&&x.size>0&&Ty(Oo(e)).some(M=>x.has(M))&&(ye.log("[setLocal] render:false + 자동바인딩 경로 겹침 감지 → render:true 자동 승격 (engine-v1.43.0)"),n={...n,render:!0})}if(n?.debounce){const x=a?.getActionDispatcher?.();if(x){const O=n.debounceKey||`setLocal-${Object.keys(e).join(",")}`,{debounce:M,debounceKey:T,...D}=n;x.debouncedCall(O,n.debounce,()=>{o.state.setLocal(e,D)});return}}const i=n?.scope??"current",l=n?.merge||"deep",c=window.__g7ActionContext,d=Oo(e);if(i==="parent"||i==="root"){const x=window.__g7LayoutContextStack||[];if(x.length>0){const O=i==="parent"?x[x.length-1]:x[0];if(O?.setState){O.setState(M=>l==="replace"?d:l==="shallow"?{...M||{},...d}:ir(M||{},d)),ye.log(`[setLocal] scope=${i}: 타겟 컨텍스트에 상태 업데이트 (mergeMode=${l})`,d);return}}ye.warn(`[setLocal] scope=${i}: 레이아웃 컨텍스트 스택이 비어있습니다. current로 폴백합니다.`)}const f=window.__g7PendingLocalState,g=a?.getGlobalState?.()?._local||{},S=!((window.__g7LayoutContextStack||[]).length>0)&&c?.state?c.state:void 0,v=S?Ry(g,S):g,_=f||v;let A;if(l==="replace"?A=d:l==="shallow"?A={..._,...d}:A=ir(_,d),a?.setGlobalState&&(a.setGlobalState({_local:A},{render:n?.render}),ye.log(`[setLocal] globalLocal updated via setGlobalState (mergeMode=${l}, render=${n?.render??!0}):`,d)),window.__g7PendingLocalState=A,ye.log("[setLocal] __g7PendingLocalState updated:",A),window.__g7SequenceLocalSync=A,window.__g7LastSetLocalSnapshot=A,l==="replace")window.__g7ForcedLocalFields=d;else{const x=window.__g7ForcedLocalFields||{};window.__g7ForcedLocalFields=ir(x,d)}if(ye.log("[setLocal] __g7ForcedLocalFields updated:",d),l==="replace")window.__g7SetLocalOverrideKeys=d;else{const x=window.__g7SetLocalOverrideKeys||{};window.__g7SetLocalOverrideKeys=ir(x,d)}ye.log("[setLocal] __g7SetLocalOverrideKeys updated:",d),c?.setState&&c.setState(x=>l==="replace"?d:l==="shallow"?{...x||{},...d}:ir(x||{},d)),a?.setGlobalState||ye.warn("G7Core.state.setLocal: TemplateApp이 없습니다.")},getLocal:()=>{const a=(window.__templateApp?.getGlobalState?.()||{})._local||{},i=window.__g7PendingLocalState;if(i)return ir(a,i);const l=window.__g7LastSetLocalSnapshot;return l&&l!==a?ir(a,l):a},update:e=>{const n=window.__templateApp;if(n?.getGlobalState&&n?.setGlobalState){const a=n.getGlobalState(),i=e(a);n.setGlobalState(i)}else ye.warn("G7Core.state.update: TemplateApp이 초기화되지 않았습니다.")},subscribe:e=>{const n=window.__templateApp;return n?.onGlobalStateChange?n.onGlobalStateChange(e):(ye.warn("G7Core.state.subscribe: TemplateApp이 초기화되지 않았습니다."),()=>{})},getDataSource:e=>window.__templateApp?.getDataSource?.(e),getIsolated:e=>e?window.__g7IsolatedStates?.[e]?.state??null:window.__g7ActionContext?.isolatedContext?.state??null,setIsolated:(e,n,a)=>{let i,l,c;typeof e=="string"?(i=e,l=n||{},c=a):(l=e,c=n&&typeof n=="object"&&"merge"in n?n:a);const d=c?.merge||"deep",f=Oo(l);if(i){const y=window.__g7IsolatedStates?.[i];y?.mergeState?y.mergeState(f,d):ye.warn(`G7Core.state.setIsolated: scopeId '${i}'를 찾을 수 없습니다.`);return}const g=window.__g7ActionContext;g?.isolatedContext?.mergeState?g.isolatedContext.mergeState(f,d):ye.warn("G7Core.state.setIsolated: 액션 컨텍스트에 isolatedContext가 없습니다.")},getParent:()=>{const e=window.__g7LayoutContextStack||[];if(e.length===0)return ye.log("[getParent] 레이아웃 컨텍스트 스택이 비어있습니다."),null;const n=e[e.length-1];if(!n)return null;const a=n.dataContext||{};return{_local:n.state||a._local||{},_global:a._global||o.state.get()||{},setState:n.setState}},setParentLocal:(e,n,a)=>{const i=window.__g7LayoutContextStack||[];if(i.length===0){ye.warn("[setParentLocal] 레이아웃 컨텍스트 스택이 비어있습니다.");return}const l=i[i.length-1];if(!l?.setState){ye.warn("[setParentLocal] 부모 컨텍스트에 setState가 없습니다.");return}let c,d;typeof e=="string"?(c={[e]:n},d=a):(c=e,d=n&&typeof n=="object"&&"merge"in n?n:a);const f=d?.merge||"deep",g=Oo(c),m=l.state?._local!==void 0,y=m?l.state._local:l.state||{};let S;f==="replace"?S=g:f==="shallow"?S={...y,...g}:S=ir(y,g),window.__g7PendingLocalState=S;const v=window.__templateApp;if(v?.setGlobalState&&v.setGlobalState({_local:S}),l.setState(S),m?l.state._local=S:l.state=S,l.dataContext&&(l.dataContext._local=S),f==="replace")window.__g7ForcedLocalFields=g;else{const _=window.__g7ForcedLocalFields||{};window.__g7ForcedLocalFields=ir(_,g)}Gd()},setParentGlobal:(e,n)=>{let a;typeof e=="string"?a={[e]:n}:a=e,o.state.set(a),ye.log("[setParentGlobal] 전역 상태 업데이트:",a)}},window.__g7IsolatedStates||(window.__g7IsolatedStates={}),o.state.getGlobal=o.state.get,o.state.setGlobal=o.state.set,ye.log("전역 객체 window.G7Core.state에 노출됨")}function NA(o){o.dataSource={get:e=>window.__templateApp?.getDataSource?.(e),set:(e,n,a)=>{const i=window.__templateApp;i?.setDataSource?i.setDataSource(e,n,a):ye.warn("G7Core.dataSource.set: TemplateApp이 초기화되지 않았습니다.")},refetch:async(e,n)=>{const a=window.__templateApp;if(a?.refetchDataSource)return a.refetchDataSource(e,n);ye.warn("G7Core.dataSource.refetch: TemplateApp이 초기화되지 않았습니다.")},updateItem:(e,n,a,i,l)=>{const c=window.__templateApp;return c?.updateDataSourceItem?c.updateDataSourceItem(e,n,a,i,l):(ye.warn("G7Core.dataSource.updateItem: TemplateApp이 초기화되지 않았습니다."),!1)},updateData:(e,n,a,i="append")=>{const l=window.__templateApp;if(!l?.getDataSource||!l?.setDataSource)return ye.warn("G7Core.dataSource.updateData: TemplateApp이 초기화되지 않았습니다."),!1;const c=l.getDataSource(e);if(!c)return ye.warn(`G7Core.dataSource.updateData: 데이터 소스 '${e}'를 찾을 수 없습니다.`),!1;let d,f=c,g=null;if(n){const y=n.split(".");g=y.pop();for(const S of y)if(f&&typeof f=="object"&&S in f)f=f[S];else return ye.warn(`G7Core.dataSource.updateData: 경로 '${n}'를 찾을 수 없습니다.`),!1;d=f[g]}else d=c;if(!Array.isArray(d))return ye.warn("G7Core.dataSource.updateData: 대상 경로의 데이터가 배열이 아닙니다."),!1;if(!Array.isArray(a))return ye.warn("G7Core.dataSource.updateData: newData가 배열이 아닙니다."),!1;const m=i==="prepend"?[...a,...d]:[...d,...a];return n&&g?(f[g]=m,l.setDataSource(e,c,{merge:!1})):l.setDataSource(e,m,{merge:!1}),ye.log(`G7Core.dataSource.updateData: '${e}'에 ${a.length}개 항목 ${i==="prepend"?"앞에":"뒤에"} 추가됨`),!0}},ye.log("전역 객체 window.G7Core.dataSource에 노출됨")}function IA(o,e){o.locale={current:()=>window.__templateApp?.getLocale?.()||"ko",supported:()=>{const a=window.__templateApp?.globalState?.appConfig?.supportedLocales;return Array.isArray(a)&&a.length>0?a:e.getState().templateMetadata?.locales||["ko","en"]},change:async n=>{const a=window.__templateApp;a?.changeLocale?await a.changeLocale(n):ye.warn("G7Core.locale.change: TemplateApp이 초기화되지 않았습니다.")}},ye.log("전역 객체 window.G7Core.locale에 노출됨")}function HA(o){o.toast={show:(e,n)=>{o.dispatch({handler:"toast",params:{message:e,type:n?.type||"info",...n?.duration&&{duration:n.duration}}})},success:(e,n)=>{o.toast.show(e,{type:"success",duration:n})},error:(e,n)=>{o.toast.show(e,{type:"error",duration:n})},warning:(e,n)=>{o.toast.show(e,{type:"warning",duration:n})},info:(e,n)=>{o.toast.show(e,{type:"info",duration:n})}},ye.log("전역 객체 window.G7Core.toast에 노출됨")}function jA(o){o.modal={open:e=>{o.dispatch({handler:"openModal",target:e})},close:e=>{o.dispatch({handler:"closeModal",...e&&{target:e}})},closeAll:()=>{o.dispatch({handler:"closeAllModals"})},isOpen:e=>{const n=o.state.get();return n._global?.activeModal===e||n._global?.modalStack?.includes(e)||!1},getStack:()=>o.state.get()._global?.modalStack||[]},ye.log("전역 객체 window.G7Core.modal에 노출됨")}function zA(o){o.style={mergeClasses:EA,conditionalClass:_A,joinClasses:AA},ye.log("전역 객체 window.G7Core.style에 노출됨")}function UA(o,e){const{webSocketManager:n}=e;o.websocket={manager:n,subscribe:(a,i,l,c)=>n.subscribe(a,i,l,c),unsubscribe:a=>n.unsubscribe(a),leaveChannel:a=>n.leaveChannel(a),disconnect:()=>n.disconnect(),isInitialized:()=>n.isInitialized(),getSubscriptionCount:()=>n.getSubscriptionCount()},ye.log("전역 객체 window.G7Core.websocket에 노출됨")}function PA(o,e){const{transitionManager:n}=e;o.navigation={isPending:()=>n?.getIsPending?.()||!1,onComplete:a=>{if(!n)return a(),()=>{};let i=n.getIsPending();const l=n.subscribe(c=>{c?i=!0:i&&(a(),l())});return l}},ye.log("전역 객체 window.G7Core.navigation에 노출됨")}function BA(o){o.plugin={getSettings:e=>window.G7Config?.plugins?.[e],get:(e,n,a)=>window.G7Config?.plugins?.[e]?.[n]??a,getAll:()=>window.G7Config?.plugins??{}},ye.log("전역 객체 window.G7Core.plugin에 노출됨")}function qA(o){o.module={getSettings:e=>window.G7Config?.modules?.[e],get:(e,n,a)=>window.G7Config?.modules?.[e]?.[n]??a,getAll:()=>window.G7Config?.modules??{}},ye.log("전역 객체 window.G7Core.module에 노출됨")}function GA(o,e){const{transitionManager:n,responsiveManager:a}=e;o.AuthManager=br,o.api=Hr(),o.TransitionManager=n,o.useTransitionState=wm,o.ResponsiveManager=a,o.useResponsive=Wd,o.useControllableState=$C,o.shallowArrayEqual=NC,o.shallowObjectEqual=Em,o.updateQueryParams=async(i,l)=>{const c=window.__templateApp;if(c?.updateQueryParams)return c.updateQueryParams(i,l);ye.warn("G7Core.updateQueryParams: TemplateApp이 초기화되지 않았습니다.")},ye.log("전역 객체 window.G7Core.AuthManager에 노출됨"),ye.log("전역 객체 window.G7Core.api에 노출됨"),ye.log("전역 객체 window.G7Core.ResponsiveManager에 노출됨"),ye.log("전역 객체 window.G7Core.useResponsive에 노출됨"),ye.log("전역 객체 window.G7Core.useControllableState에 노출됨"),ye.log("전역 객체 window.G7Core.updateQueryParams에 노출됨")}function VA(o,e){o.getSlotContext=()=>window.__slotContextValue??null,o.getDynamicRenderer=()=>window.__DynamicRenderer??null,o.getComponentRegistry=()=>e.getComponentRegistry?.()??mr.getInstance(),o.getDataBindingEngine=()=>e.getDataBindingEngine?.()??e.getState().bindingEngine,o.getTranslationEngine=()=>e.getTranslationEngine?.()??e.getState().translationEngine,o.getActionDispatcher=()=>e.getActionDispatcher?.()??e.getState().actionDispatcher,ye.log("전역 객체 window.G7Core 슬롯 API에 노출됨 (getSlotContext, getDynamicRenderer 등)")}function FA(o){o.identity={setLauncher:Et.setLauncher.bind(Et),handle:Et.handle.bind(Et),isIdentityRequired:Et.isIdentityRequired.bind(Et),hasLauncher:Et.hasLauncher.bind(Et),markDomainNoticeShown:Et.markDomainNoticeShown.bind(Et),redirectExternally:Et.redirectExternally.bind(Et),createDeferred:Et.createDeferred.bind(Et),resolveDeferred:Et.resolveDeferred.bind(Et),reset:Et.reset.bind(Et),redirectStashKey:jd},ye.log("전역 객체 window.G7Core 본인인증(IDV) API에 노출됨 (identity)")}function KA(o){if(typeof window>"u")return;xA(),window.G7Core||(window.G7Core={});const e=window.G7Core;RA(e),TA(e),kA(e),GA(e,o),DA(e,o),OA(e,o),LA(e),$A(e),NA(e),IA(e,o),HA(e),jA(e),zA(e),UA(e,o),PA(e,o),BA(e),qA(e),VA(e,o),FA(e),ye.log("G7Core 전역 객체 초기화 완료")}function WA(){const o=window.G7Core;if(!o){ye.warn("G7Core가 초기화되지 않았습니다.");return}const e=()=>Oa.getInstance(),n={isEnabled:()=>{try{return e().isEnabled()}catch{return!1}},trackRender:a=>{try{e().isEnabled()&&e().trackRender(a)}catch{}},trackIteration:(a,i,l,c,d)=>{try{e().isEnabled()&&e().trackIteration(a,{source:i,itemVar:l,indexVar:c,sourceLength:d})}catch{}},trackIfCondition:(a,i,l,c)=>{try{e().isEnabled()&&e().trackIfCondition(a,i,l)}catch{}},trackMount:(a,i)=>{try{e().isEnabled()&&e().trackMount(a,{...i,id:a})}catch{}},trackUnmount:a=>{try{e().isEnabled()&&e().trackUnmount(a)}catch{}},trackExpressionEval:a=>{try{e().isEnabled()&&e().trackExpressionEval(a)}catch{}},trackBindingEval:a=>{try{e().isEnabled()&&e().trackBindingEval(a)}catch{}},recordCacheHit:()=>{try{e().isEnabled()&&e().recordCacheHit()}catch{}},recordCacheMiss:()=>{try{e().isEnabled()&&e().recordCacheMiss()}catch{}},trackHandlerRegistration:(a,i,l,c)=>{try{e().isEnabled()&&e().trackHandlerRegistration(a,i,l,c)}catch{}},trackHandlerUnregistration:a=>{try{e().isEnabled()&&e().trackHandlerUnregistration(a)}catch{}},logAction:a=>{try{e().isEnabled()&&e().logAction(a)}catch{}},trackRequest:(a,i)=>{try{return e().isEnabled()?e().trackRequest(a,i):""}catch{return""}},completeRequest:(a,i,l)=>{try{e().isEnabled()&&e().completeRequest(a,i,l)}catch{}},failRequest:(a,i)=>{try{e().isEnabled()&&e().failRequest(a,i)}catch{}},trackDataSourceDefinition:a=>{try{e().isEnabled()&&e().trackDataSourceDefinition(a)}catch{}},trackDataSourceLoading:a=>{try{e().isEnabled()&&e().trackDataSourceLoading(a)}catch{}},trackDataSourceLoaded:(a,i,l)=>{try{e().isEnabled()&&e().trackDataSourceLoaded(a,i,l)}catch{}},trackDataSourceError:(a,i)=>{try{e().isEnabled()&&e().trackDataSourceError(a,i)}catch{}},trackForm:(a,i,l)=>{try{e().isEnabled()&&e().trackForm(a,i,l)}catch{}},untrackForm:a=>{try{e().isEnabled()&&e().untrackForm(a)}catch{}},startStateChange:(a,i,l,c)=>{try{return e().isEnabled()?e().startStateChange(a,i,l,c):""}catch{return""}},completeStateChange:a=>{try{e().isEnabled()&&e().completeStateChange(a)}catch{}},trackComponentRender:(a,i,l,c,d,f)=>{try{e().isEnabled()&&e().trackComponentRender(a,i,l,c,d,f)}catch{}},updateLocalState:a=>{try{e().isEnabled()&&e().updateLocalState(a)}catch{}},updateComputedState:a=>{try{e().isEnabled()&&e().updateComputedState(a)}catch{}},updateParentContext:a=>{try{e().isEnabled()&&e().updateParentContext(a)}catch{}},trackComponentStateSource:(a,i,l,c)=>{try{e().isEnabled()&&e().trackComponentStateSource(a,i,l,c)}catch{}},trackDynamicState:(a,i)=>{try{e().isEnabled()&&e().trackDynamicState(a,i)}catch{}},trackContextFlow:(a,i,l,c,d,f)=>{try{e().isEnabled()&&e().trackContextFlow(a,i,l,c,d,f)}catch{}},trackComponentStyle:(a,i,l,c)=>{try{e().isEnabled()&&e().trackComponentStyle(a,i,l,c)}catch{}},trackAuthEvent:(a,i,l,c)=>{try{e().isEnabled()&&e().trackAuthEvent(a,i,l,c)}catch{}},trackAuthHeader:(a,i,l,c,d)=>{try{e().isEnabled()&&e().trackAuthHeader(a,i,l,c,d)}catch{}},trackLog:(a,i,l)=>{try{e().isEnabled()&&e().trackLog(a,i,l)}catch{}},trackAction:a=>{try{e().isEnabled()&&e().trackAction?.(a)}catch{}},trackDataSourceUpdate:a=>{try{e().isEnabled()&&e().trackDataSourceUpdate?.(a)}catch{}},startSequenceExecution:a=>{try{return e().isEnabled()?e().startSequenceExecution(a):""}catch{return""}},captureSequenceActionBefore:(a,i,l,c,d)=>{try{e().isEnabled()&&e().captureSequenceActionBefore(a,i,l,c,d)}catch{}},captureSequenceActionAfter:(a,i,l,c,d,f)=>{try{e().isEnabled()&&e().captureSequenceActionAfter(a,i,l,c,d,f)}catch{}},endSequenceExecution:(a,i)=>{try{e().isEnabled()&&e().endSequenceExecution(a,i)}catch{}},registerStateCaptureForHandler:(a,i,l)=>{try{e().isEnabled()&&e().registerStateCaptureForHandler(a,i,l)}catch{}},detectStaleClosure:(a,i,l,c,d)=>{try{return e().isEnabled()?e().detectStaleClosure(a,i,l,c,d):[]}catch{return[]}},trackStaleClosureWarning:a=>{try{e().isEnabled()&&e().trackStaleClosureWarning(a)}catch{}},trackModalOpen:a=>{try{e().isEnabled()&&e().trackModalOpen(a)}catch{}},trackModalClose:(a,i)=>{try{e().isEnabled()&&e().trackModalClose(a,i)}catch{}},trackModalStateChange:a=>{try{e().isEnabled()&&e().trackModalStateChange(a)}catch{}},trackNestedContext:a=>{try{return e().isEnabled()?e().trackNestedContext(a):""}catch{return""}},trackNestedContextAccess:(a,i)=>{try{e().isEnabled()&&e().trackNestedContextAccess(a,i)}catch{}},trackComputedProperty:(a,i,l,c,d,f,g)=>{try{e().isEnabled()&&e().trackComputedProperty(a,i,l,c,d,f,g)}catch{}},trackComputedRecalc:(a,i,l,c,d,f,g)=>{try{e().isEnabled()&&e().trackComputedRecalc(a,i,l,c,d,f,g)}catch{}},setNamedActionDefinitions:a=>{try{e().isEnabled()&&e().setNamedActionDefinitions(a)}catch{}},trackNamedActionRef:a=>{try{e().isEnabled()&&e().trackNamedActionRef(a)}catch{}}};o.devTools=n,ye.log("G7Core.devTools 인터페이스 초기화 완료")}let Lo=null;function YA(){const o=window.G7Core;return o?.__devtools?Promise.resolve(o.__devtools):Lo||(Lo=new Promise((e,n)=>{const a=window.G7Config?.coreDevToolsAsset||"/build/core/devtools.min.js",i="g7-devtools-bundle",l=()=>{const f=window.G7Core?.__devtools;f?e(f):n(new Error("DevTools 번들 로드됨 — 그러나 __devtools 미노출"))};window.G7Core=window.G7Core||{},window.G7Core.__onDevToolsReady=l;const c=document.getElementById(i);if(c){c.addEventListener("load",l,{once:!0}),c.addEventListener("error",()=>n(new Error(`DevTools 번들 로드 실패: ${a}`)),{once:!0});return}const d=document.createElement("script");d.id=i,d.src=a,d.async=!1,d.addEventListener("load",l,{once:!0}),d.addEventListener("error",()=>{Lo=null,n(new Error(`DevTools 번들 로드 실패: ${a}`))},{once:!0}),document.head.appendChild(d)}),Lo)}function XA(){const o=window.G7Core;if(!o){ye.warn("G7Core가 초기화되지 않았습니다.");return}const e=Oa.getInstance();if(e.initialize(),!e.isEnabled()){window.G7DevTools={isEnabled:()=>!1,enable:()=>{ye.log("환경설정 > 고급 설정 > 디버그 모드를 켜세요")}},ud(),ye.log("G7DevTools 비활성화됨 (디버그 모드 꺼짐)");return}JA(o,e)}async function JA(o,e){let n;try{n=await YA()}catch(g){ye.warn("DevTools 번들 로드 실패 — 최소 API 로 폴백:",g),window.G7DevTools={isEnabled:()=>!1,enable:()=>{}},ud();return}const{DiagnosticEngine:a,getServerConnector:i,getStyleTracker:l}=n;WA(),ud();try{l().enable(),ye.log("StyleTracker 활성화됨")}catch(g){ye.warn("StyleTracker 활성화 실패:",g)}const c=new a,d=i(),f={isEnabled:()=>e.isEnabled(),state:{get:()=>e.getState(),getHistory:()=>e.getStateHistory(),watch:(g,m)=>e.watchState(g,m)},actions:{getHistory:()=>e.getActionHistory(),watch:g=>e.watchActions(g),getMetrics:()=>e.getActionMetrics()},binding:{evaluate:g=>{const m=o.getDataBindingEngine?.();if(!m){ye.warn("DataBindingEngine이 없습니다.");return}return m.evaluateExpression(g,e.getState())},getCacheStats:()=>e.getCacheStats(),clearStats:()=>{e.resetCacheStats(),ye.log("캐시 통계 초기화됨")}},diagnose:{analyze:g=>c.analyze(g),suggestFix:g=>c.suggestFix(g),getCommonIssues:()=>c.getCommonIssues(),getRulesByCategory:g=>c.getRulesByCategory(g)},server:{dumpState:g=>d.dumpState(g),sendLog:g=>d.sendLog(g),sendError:(g,m)=>d.sendError(g,m),isConnected:()=>d.isConnected(),testConnection:()=>d.testConnection()},config:{isDebugMode:()=>e.isEnabled(),setLogLevel:g=>e.setLogLevel(g),setMaxHistory:g=>e.setMaxHistory(g)},lifecycle:{getMountedComponents:()=>e.getLifecycleInfo().mountedComponents,getOrphanedListeners:()=>e.getLifecycleInfo().orphanedListeners},performance:{getRenderCount:()=>e.getPerformanceInfo().renderCounts,getBindingEvalCount:()=>e.getPerformanceInfo().bindingEvalCount,getMemoryWarnings:()=>e.getPerformanceInfo().memoryWarnings,startProfiling:()=>e.startProfiling(),stopProfiling:()=>e.stopProfiling()},network:{getActiveRequests:()=>e.getNetworkInfo().activeRequests,getRequestHistory:()=>e.getNetworkInfo().requestHistory,getPendingDataSources:()=>e.getNetworkInfo().pendingDataSources},conditional:{getIfConditions:()=>e.getConditionalInfo().ifConditions,getIterations:()=>e.getConditionalInfo().iterations},form:{getForms:()=>e.getFormInfo()},websocket:{getInfo:()=>e.getWebSocketInfo()}};window.G7DevTools=f,o.devtools=f,ye.log("G7DevTools 전역 객체 초기화 완료 (window.G7DevTools)"),QA(n.DevToolsPanel)}function QA(o){if(document.getElementById("g7-devtools-root")){ye.log("DevToolsPanel 이미 렌더링됨");return}const e=document.createElement("div");e.id="g7-devtools-root",document.body.appendChild(e);try{ld.createRoot(e).render(Xe.createElement(o)),ye.log("DevToolsPanel UI 렌더링 완료")}catch(n){ye.error("DevToolsPanel UI 렌더링 실패:",n)}}const Ze=ht("TemplateEngine"),ie={templateId:null,locale:"ko",isInitialized:!1,reactRoot:null,containerId:null,currentLayoutJson:null,currentDataContext:{},translationContext:{templateId:"",locale:"ko"},registry:null,bindingEngine:null,translationEngine:null,actionDispatcher:null,templateMetadata:null};let Mo=!1;const $o=[];async function ZA(o){try{Ze.log("템플릿 메타데이터 로드 중...",o);const e=o.split("-").map(a=>a.split("_").map(i=>i.charAt(0).toUpperCase()+i.slice(1)).join("")).join(""),n=window[e];return n?.templateMetadata?(Ze.log("템플릿 메타데이터 로드 완료 (전역 변수)",n.templateMetadata),n.templateMetadata):(Ze.warn(`템플릿 번들에 메타데이터가 없습니다. 전역 변수: ${e}`),{identifier:o,locales:["ko","en"],name:{ko:o,en:o},description:{ko:"",en:""},version:"1.0.0",type:"admin"})}catch(e){return Ze.error("템플릿 메타데이터 로드 실패",e),{identifier:o,locales:["ko","en"],name:{ko:o,en:o},description:{ko:"",en:""},version:"1.0.0",type:"admin"}}}function ky(){const e=(typeof window<"u"?window.__templateApp:void 0)?.globalState?.appConfig?.supportedLocales,n=ie.templateMetadata?.locales;return{$locale:ie.locale,$locales:Array.isArray(e)&&e.length>0?e:n||["ko","en"],$templateLocales:n||["ko","en"],$templateId:ie.templateId}}async function No(o){try{if(Ze.log("템플릿 엔진 초기화 시작",o),ie.isInitialized)throw new Error("템플릿 엔진이 이미 초기화되었습니다. destroyTemplate()을 먼저 호출하세요.");if(!o.templateId)throw new cA;Mo=o.debug??!1,typeof window<"u"&&(window.G7Config||(window.G7Config={}),window.G7Config.debug=Mo),XA(),ie.templateId=o.templateId,ie.locale=o.locale||"ko",Ze.log("엔진 인스턴스 생성 중..."),ie.registry=mr.getInstance(),ie.bindingEngine=new Dn,ie.translationEngine=_a.getInstance(),o.cacheVersion!==void 0&&o.cacheVersion>0&&ie.translationEngine.setCacheVersion(o.cacheVersion),ie.translationContext={templateId:o.templateId,locale:ie.locale},ie.actionDispatcher=new bo({},ie.translationEngine,ie.translationContext),AC(ie.actionDispatcher),Ze.log("다국어 파일 로드 중...",o.templateId,ie.locale);const e="en",n=[ie.translationEngine.loadTranslations(o.templateId,ie.locale).then(()=>{Ze.log(`다국어 파일 로드 완료: ${ie.locale}`)}).catch(a=>{Ze.warn(`다국어 파일 로드 실패 (${ie.locale}):`,a instanceof Error?a.message:a)})];ie.locale!==e&&n.push(ie.translationEngine.loadTranslations(o.templateId,e).then(()=>{Ze.log(`폴백 다국어 파일 로드 완료: ${e}`)}).catch(a=>{Ze.warn(`폴백 다국어 파일 로드 실패 (${e}):`,a instanceof Error?a.message:a)})),await Promise.allSettled(n),Ze.log("템플릿 메타데이터 로드 중...",o.templateId),ie.templateMetadata=await ZA(o.templateId),ie.isInitialized=!0,Ze.log("템플릿 엔진 초기화 완료 (ComponentRegistry는 별도 로드)")}catch(e){throw Ze.error("템플릿 엔진 초기화 실패",e),ie.isInitialized=!1,ie.registry=null,ie.bindingEngine=null,ie.translationEngine=null,ie.actionDispatcher=null,e}}let Io=null;function Tf(){const o=window.G7Core;return o?.__LayoutEditorChrome?Promise.resolve(o.__LayoutEditorChrome):Io||(Io=new Promise((e,n)=>{const a=window.G7Config?.coreEditorAsset||"/build/core/layout-editor.min.js",i="g7-layout-editor-bundle",l=document.getElementById(i),c=()=>{const f=window.G7Core?.__LayoutEditorChrome;f?e(f):n(new Error("편집기 번들 로드됨 — 그러나 __LayoutEditorChrome 미노출"))};if(window.G7Core=window.G7Core||{},window.G7Core.__onChromeReady=c,l){l.addEventListener("load",c,{once:!0}),l.addEventListener("error",()=>n(new Error(`편집기 번들 로드 실패: ${a}`)),{once:!0});return}const d=document.createElement("script");d.id=i,d.src=a,d.async=!1,d.addEventListener("load",c,{once:!0}),d.addEventListener("error",()=>{Io=null,n(new Error(`편집기 번들 로드 실패: ${a}`))},{once:!0}),document.head.appendChild(d)}),Io)}async function ms(o){try{if(Ze.log("템플릿 렌더링 시작",o),!ie.isInitialized)throw new Error("템플릿 엔진이 초기화되지 않았습니다. initTemplateEngine()을 먼저 호출하세요.");if(!o.containerId)throw new Error("containerId는 필수입니다.");if(!o.layoutJson)throw new Error("layoutJson은 필수입니다.");const e=document.getElementById(o.containerId);if(!e)throw new Error(`컨테이너를 찾을 수 없습니다: #${o.containerId}`);const n=ky();if(ie.containerId=o.containerId,ie.currentLayoutJson=o.layoutJson,ie.currentDataContext={...n,...o.dataContext||{}},ie.translationContext=o.translationContext||{templateId:ie.templateId||"",locale:ie.locale},ie.reactRoot||(Ze.log("React Root 생성"),ie.reactRoot=ld.createRoot(e)),typeof window<"u"){const l=Ey(window.location.pathname);if(l){Ze.log("레이아웃 편집기 모드 진입",l);let c;try{c=await Tf()}catch(f){Ze.error("레이아웃 편집기 번들 로드 실패",f),Cf.render(o.containerId,{title:"레이아웃 편집기 로드 실패",message:f instanceof Error?f.message:"편집기 번들을 불러오지 못했습니다.",icon:"fas fa-triangle-exclamation",showStack:!1,showReloadButton:!0,debug:Mo});return}const d=Xe.createElement(c,{templateIdentifier:l.templateIdentifier,initialLocale:ie.locale});ie.reactRoot.render(Xe.createElement(vc,{translationEngine:ie.translationEngine,translationContext:ie.translationContext,children:Xe.createElement(Vd,{children:Xe.createElement(ic,{children:Xe.createElement(bf,{children:d})})})}));return}}const a=o.layoutJson.components||[];if(a.length===0){Ze.warn("렌더링할 컴포넌트가 없습니다.");return}const i=o.layoutJson.modals||[];if(Ze.log("modals 배열:",i),Ze.log("modals 개수:",i.length),Ze.log("DynamicRenderer로 렌더링 시작"),ie.reactRoot.render(Xe.createElement(vc,{translationEngine:ie.translationEngine,translationContext:ie.translationContext},Xe.createElement(Vd,null,Xe.createElement(ic,null,Xe.createElement(bf,null,[...a.map((l,c)=>{const d=ie.currentLayoutJson?.layout_name||"";return Xe.createElement(Ur,{key:d&&!l._fromBase?`${l.id}_${d}`:l.id,componentDef:l,dataContext:ie.currentDataContext,translationContext:ie.translationContext,registry:ie.registry,bindingEngine:ie.bindingEngine,translationEngine:ie.translationEngine,actionDispatcher:ie.actionDispatcher,isRootRenderer:c===0,layoutKey:d})}),Xe.createElement(mm,{key:"__modal_parent_context_provider"},i.map(l=>{const c=ie.currentDataContext._global?.modalStack||[],f=c.includes(l.id)||ie.currentDataContext._global?.activeModal===l.id,g=c.indexOf(l.id),m=g>=0?50+g:50,y=window.__g7LayoutContextStack||[],v=y[y.length-1]?.dataContext,_=Xe.createElement(Ur,{key:`modal_${l.id}_renderer`,componentDef:{...l,props:{...l.props,isOpen:f,style:{...l.props?.style,zIndex:m},onClose:ie.actionDispatcher?.createHandler({type:"click",handler:"closeModal"},ie.currentDataContext)}},dataContext:ie.currentDataContext,translationContext:ie.translationContext,registry:ie.registry,bindingEngine:ie.bindingEngine,translationEngine:ie.translationEngine,actionDispatcher:ie.actionDispatcher,parentDataContext:v});return l.data_sources&&l.data_sources.length>0?Xe.createElement(wy,{key:`modal_${l.id}`,isOpen:f,modalId:l.id,dataSources:l.data_sources,dataContext:ie.currentDataContext,globalStateUpdater:ie.actionDispatcher?.getGlobalStateUpdater(),bindingEngine:ie.bindingEngine,debug:Mo,children:_}):_}))]))))),Ze.log("템플릿 렌더링 완료"),$o.length>0){Ze.log(`대기 중인 데이터 업데이트 ${$o.length}건 적용`);const l=[...$o];$o.length=0;for(const{data:c,options:d}of l)ys(c,d)}}catch(e){throw Ze.error("템플릿 렌더링 실패",e),e}}function ys(o,e){try{if(Ze.log("템플릿 데이터 업데이트 시작",Object.keys(o)),!ie.isInitialized)throw new Error("템플릿 엔진이 초기화되지 않았습니다.");if(!ie.reactRoot||!ie.currentLayoutJson){Ze.log("React 미준비 - 데이터 업데이트 큐잉:",Object.keys(o)),$o.push({data:o,options:e});return}const n=ky(),a={...ie.currentDataContext._global||{},...o._global||{}},i=LC(ie.currentDataContext._localInit,o._localInit);ie.currentDataContext={...n,...ie.currentDataContext,...o,_global:a,_localInit:i},a._local!==void 0&&(ie.currentDataContext._local=a._local),a._computed!==void 0&&(ie.currentDataContext._computed=a._computed);const l=ie.currentLayoutJson.components||[],c=ie.currentLayoutJson.modals||[];Ze.log("updateTemplateData - modals 배열:",c),Ze.log("updateTemplateData - modals 개수:",c.length),Ze.log("updateTemplateData - activeModal:",ie.currentDataContext._global?.activeModal);const d=()=>{ie.reactRoot.render(Xe.createElement(vc,{translationEngine:ie.translationEngine,translationContext:ie.translationContext},Xe.createElement(Vd,null,Xe.createElement(ic,null,Xe.createElement(bf,null,[...l.map((f,g)=>{const m=ie.currentLayoutJson?.layout_name||"";return Xe.createElement(Ur,{key:m&&!f._fromBase?`${f.id}_${m}`:f.id,componentDef:f,dataContext:ie.currentDataContext,translationContext:ie.translationContext,registry:ie.registry,bindingEngine:ie.bindingEngine,translationEngine:ie.translationEngine,actionDispatcher:ie.actionDispatcher,isRootRenderer:g===0,layoutKey:m})}),Xe.createElement(mm,{key:"__modal_parent_context_provider_update"},c.map(f=>{const g=ie.currentDataContext._global?.modalStack||[],y=g.includes(f.id)||ie.currentDataContext._global?.activeModal===f.id,S=g.indexOf(f.id),v=S>=0?50+S:50,_=window.__g7LayoutContextStack||[],x=_[_.length-1]?.dataContext,O=Xe.createElement(Ur,{key:`modal_${f.id}_renderer`,componentDef:{...f,props:{...f.props,isOpen:y,style:{...f.props?.style,zIndex:v},onClose:ie.actionDispatcher?.createHandler({type:"click",handler:"closeModal"},ie.currentDataContext)}},dataContext:ie.currentDataContext,translationContext:ie.translationContext,registry:ie.registry,bindingEngine:ie.bindingEngine,translationEngine:ie.translationEngine,actionDispatcher:ie.actionDispatcher,parentDataContext:x});return f.data_sources&&f.data_sources.length>0?Xe.createElement(wy,{key:`modal_${f.id}`,isOpen:y,modalId:f.id,dataSources:f.data_sources,dataContext:ie.currentDataContext,globalStateUpdater:ie.actionDispatcher?.getGlobalStateUpdater(),bindingEngine:ie.bindingEngine,debug:Mo,children:O}):O}))])))))};e?.sync?(Ze.log("재렌더링 시작 (sync mode - 즉시 렌더링)"),d()):(Ze.log("재렌더링 시작 (with startTransition)"),H.startTransition(()=>{d()})),Ze.log("템플릿 데이터 업데이트 완료")}catch(n){throw Ze.error("템플릿 데이터 업데이트 실패",n),n}}function xc(){try{Ze.log("템플릿 정리 시작"),ie.reactRoot&&(Ze.log("React Root 언마운트"),ie.reactRoot.unmount(),ie.reactRoot=null),ie.templateId=null,ie.locale="ko",ie.isInitialized=!1,ie.containerId=null,ie.currentLayoutJson=null,ie.currentDataContext={},ie.translationContext={templateId:"",locale:"ko"},ie.registry=null,ie.bindingEngine=null,ie.translationEngine=null,ie.actionDispatcher=null,ie.templateMetadata=null,Ze.log("템플릿 정리 완료")}catch(o){throw Ze.error("템플릿 정리 실패",o),o}}function ea(){return Object.freeze({...ie})}function Dy(){return ie.actionDispatcher}const Ho={initTemplateEngine:No,renderTemplate:ms,updateTemplateData:ys,destroyTemplate:xc,getState:ea};typeof window<"u"&&(window.G7Core||(window.G7Core={}),window.G7Core.TemplateEngine=Ho,KA({getState:()=>({translationEngine:ie.translationEngine,translationContext:ie.translationContext,bindingEngine:ie.bindingEngine,actionDispatcher:ie.actionDispatcher,templateMetadata:ie.templateMetadata}),transitionManager:jr,responsiveManager:hi,webSocketManager:Cc}),Ze.log("전역 객체 window.G7Core.TemplateEngine에 노출됨"));const Pr=Object.freeze(Object.defineProperty({__proto__:null,DataSourceManager:_r,LayoutLoader:mf,TemplateApp:Do,TemplateEngine:Ho,default:Ho,destroyTemplate:xc,getActionDispatcher:Dy,getState:ea,initTemplateApp:xf,initTemplateEngine:No,loadLayoutEditorBundle:Tf,renderTemplate:ms,updateTemplateData:ys},Symbol.toStringTag,{value:"Module"}));Qt.DataSourceManager=_r,Qt.LayoutLoader=mf,Qt.TemplateApp=Do,Qt.TemplateEngine=Ho,Qt.default=Ho,Qt.destroyTemplate=xc,Qt.getActionDispatcher=Dy,Qt.getState=ea,Qt.initTemplateApp=xf,Qt.initTemplateEngine=No,Qt.loadLayoutEditorBundle=Tf,Qt.renderTemplate=ms,Qt.updateTemplateData=ys,Object.defineProperties(Qt,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})})(this.G7Core=this.G7Core||{}); + `)(n)}catch(a){U.warn(`Expression evaluation failed: ${e}`,a);return}}async executeInitActions(e,n={}){const{getActionDispatcher:a}=await Promise.resolve().then(()=>Ur),i=a();if(!i){U.warn("ActionDispatcher not available for init_actions");return}U.log("Executing init_actions:",e.map(d=>d.handler)),U.log("Init actions dataContext:",n),U.log("Init actions dataContext._global:",n._global);const l=e.map(d=>d.handler).filter(d=>d.includes("."));l.length>0&&await this.waitForHandlers(i,l);let c={...n};for(const d of e)try{const f={type:"click",handler:d.handler,target:d.target,params:d.params,resultTo:d.resultTo,onSuccess:d.onSuccess,onError:d.onError,if:d.if,conditions:d.conditions,auth_mode:d.auth_mode,auth_required:d.auth_required},g=i.createHandler(f,c),m=new Event("init");await g(m),U.log(`Init action executed: ${d.handler}`);const b=this.globalState;c={...c,_global:{...b},_local:b._local||c._local||{}},U.log(`Data context refreshed after ${d.handler}:`,{cartKey:c._global?.cartKey})}catch(f){U.error(`Failed to execute init action: ${d.handler}`,f)}}async waitForHandlers(e,n,a=5e3){const i=Date.now(),l=50,c=()=>n.every(f=>e.customHandlers?.has(f));if(c()){U.log("All module handlers already registered");return}const d=wf().getFailedJsAssets();if(d.length>0){const f=d.some(b=>b==="module"||b==="plugin"),g=n.filter(b=>!e.customHandlers?.has(b)),m=g.length>0&&g.every(b=>d.some(S=>b.startsWith(`${S}.`)));if(f||m){U.warn("Extension asset load failed — not waiting for handlers that will never register:",{pending:g,failedAssets:d});return}}return U.log("Waiting for module handlers:",n),new Promise(f=>{const g=()=>{if(c()){U.log("All module handlers now registered"),f();return}if(Date.now()-i>=a){const m=n.filter(b=>!e.customHandlers?.has(b));U.warn("Timeout waiting for handlers:",m),f();return}setTimeout(g,l)};g()})}};$(Ar,"LOCALE_STORAGE_KEY","g7_locale"),$(Ar,"CACHE_VERSION_STORAGE_KEY","g7_cache_version");let xo=Ar;function Ef(o){const e=new xo(o);return o.websocket&&(vc.configure(o.websocket),U.log("WebSocket 설정 완료")),document.readyState==="loading"?document.addEventListener("DOMContentLoaded",()=>{e.init(),window.__templateApp=e}):(e.init(),window.__templateApp=e),e}typeof window<"u"&&(window.G7Core=window.G7Core||{},window.G7Core.initTemplateApp=Ef,window.G7Core.dataSource={refetch:async(o,e)=>{const n=window.__templateApp;if(!n){U.warn("TemplateApp not initialized (G7Core.dataSource.refetch)");return}return n.refetchDataSource(o,e)},get:o=>{const e=window.__templateApp;if(!e){U.warn("TemplateApp not initialized (G7Core.dataSource.get)");return}return e.getDataSource(o)}});function nA(o={}){const{checked:e,value:n,name:a,type:i="checkbox"}=o,c=n!==void 0?n:i==="checkbox"||i==="radio"?e??!1:String(e??""),d={checked:e??!1,value:c,name:a??"",type:i};return{target:d,currentTarget:d,preventDefault:()=>{},stopPropagation:()=>{},nativeEvent:new Event("change"),bubbles:!0,cancelable:!0,defaultPrevented:!1,eventPhase:Event.AT_TARGET,isTrusted:!1,timeStamp:Date.now(),type:"change",isDefaultPrevented:()=>!1,isPropagationStopped:()=>!1,persist:()=>{}}}function rA(o={}){const{button:e=0,clientX:n=0,clientY:a=0}=o;return{button:e,clientX:n,clientY:a,preventDefault:()=>{},stopPropagation:()=>{},nativeEvent:new MouseEvent("click"),bubbles:!0,cancelable:!0,defaultPrevented:!1,eventPhase:Event.AT_TARGET,isTrusted:!1,timeStamp:Date.now(),type:"click",isDefaultPrevented:()=>!1,isPropagationStopped:()=>!1,persist:()=>{},target:document.createElement("div"),currentTarget:document.createElement("div")}}function aA(){return{preventDefault:()=>{},stopPropagation:()=>{},nativeEvent:new Event("submit"),bubbles:!0,cancelable:!0,defaultPrevented:!1,eventPhase:Event.AT_TARGET,isTrusted:!1,timeStamp:Date.now(),type:"submit",isDefaultPrevented:()=>!1,isPropagationStopped:()=>!1,persist:()=>{},target:document.createElement("form"),currentTarget:document.createElement("form")}}function iA(o,e="keydown"){return{key:o,code:o.length===1?`Key${o.toUpperCase()}`:o,preventDefault:()=>{},stopPropagation:()=>{},nativeEvent:new KeyboardEvent(e,{key:o}),bubbles:!0,cancelable:!0,defaultPrevented:!1,eventPhase:Event.AT_TARGET,isTrusted:!1,timeStamp:Date.now(),type:e,isDefaultPrevented:()=>!1,isPropagationStopped:()=>!1,persist:()=>{},target:document.createElement("div"),currentTarget:document.createElement("div"),altKey:!1,ctrlKey:!1,metaKey:!1,shiftKey:!1,repeat:!1}}const sA={justify:/^justify-(start|end|center|between|around|evenly)$/,items:/^items-(start|end|center|baseline|stretch)$/,content:/^content-(start|end|center|between|around|evenly|stretch)$/,self:/^self-(auto|start|end|center|stretch|baseline)$/,flex:/^flex-(row|row-reverse|col|col-reverse|wrap|wrap-reverse|nowrap|1|auto|initial|none)$/,grow:/^(grow|grow-0)$/,shrink:/^(shrink|shrink-0)$/,basis:/^basis-/,order:/^(order-|-)order-/,gap:/^gap(-x|-y)?-/,gridCols:/^grid-cols-/,gridRows:/^grid-rows-/,colSpan:/^col-(span-|start-|end-)/,rowSpan:/^row-(span-|start-|end-)/,display:/^(block|inline-block|inline|flex|inline-flex|table|inline-table|table-caption|table-cell|table-column|table-column-group|table-footer-group|table-header-group|table-row-group|table-row|flow-root|grid|inline-grid|contents|list-item|hidden)$/,position:/^(static|fixed|absolute|relative|sticky)$/,inset:/^(inset|top|right|bottom|left)-/,zIndex:/^z-/,width:/^w-/,minWidth:/^min-w-/,maxWidth:/^max-w-/,height:/^h-/,minHeight:/^min-h-/,maxHeight:/^max-h-/,padding:/^p[xytblr]?-/,margin:/^-?m[xytblr]?-/,space:/^space-(x|y)-/,fontSize:/^text-(xs|sm|base|lg|xl|2xl|3xl|4xl|5xl|6xl|7xl|8xl|9xl)$/,fontWeight:/^font-(thin|extralight|light|normal|medium|semibold|bold|extrabold|black)$/,fontStyle:/^(italic|not-italic)$/,fontFamily:/^font-(sans|serif|mono)/,textAlign:/^text-(left|center|right|justify|start|end)$/,textColor:/^text-(inherit|current|transparent|black|white|slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-/,textDecoration:/^(underline|overline|line-through|no-underline)$/,textTransform:/^(uppercase|lowercase|capitalize|normal-case)$/,lineHeight:/^leading-/,letterSpacing:/^tracking-/,textOverflow:/^(truncate|text-ellipsis|text-clip)$/,whitespace:/^whitespace-/,wordBreak:/^break-/,bgColor:/^bg-(inherit|current|transparent|black|white|slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-/,bgGradient:/^bg-gradient-/,bgSize:/^bg-(auto|cover|contain)$/,bgPosition:/^bg-(bottom|center|left|left-bottom|left-top|right|right-bottom|right-top|top)$/,bgRepeat:/^bg-(repeat|no-repeat|repeat-x|repeat-y|repeat-round|repeat-space)$/,bgAttachment:/^bg-(fixed|local|scroll)$/,bgClip:/^bg-clip-/,bgOrigin:/^bg-origin-/,borderWidth:/^border(-[xytblr])?(-0|-2|-4|-8)?$/,borderColor:/^border-(inherit|current|transparent|black|white|slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-/,borderStyle:/^border-(solid|dashed|dotted|double|hidden|none)$/,borderRadius:/^rounded(-[tblrse]{1,2})?(-none|-sm|-md|-lg|-xl|-2xl|-3xl|-full)?$/,ringWidth:/^ring(-0|-1|-2|-4|-8|-inset)?$/,ringColor:/^ring-(inherit|current|transparent|black|white|slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-/,ringOffset:/^ring-offset-/,shadow:/^shadow(-sm|-md|-lg|-xl|-2xl|-inner|-none)?$/,opacity:/^opacity-/,mixBlend:/^mix-blend-/,bgBlend:/^bg-blend-/,blur:/^blur(-none|-sm|-md|-lg|-xl|-2xl|-3xl)?$/,brightness:/^brightness-/,contrast:/^contrast-/,grayscale:/^grayscale(-0)?$/,hueRotate:/^-?hue-rotate-/,invert:/^invert(-0)?$/,saturate:/^saturate-/,sepia:/^sepia(-0)?$/,backdropBlur:/^backdrop-blur-/,backdropBrightness:/^backdrop-brightness-/,backdropContrast:/^backdrop-contrast-/,backdropGrayscale:/^backdrop-grayscale-/,backdropHueRotate:/^backdrop-hue-rotate-/,backdropInvert:/^backdrop-invert-/,backdropOpacity:/^backdrop-opacity-/,backdropSaturate:/^backdrop-saturate-/,backdropSepia:/^backdrop-sepia-/,scale:/^scale(-x|-y)?-/,rotate:/^-?rotate-/,translate:/^-?translate-[xy]-/,skew:/^-?skew-[xy]-/,transformOrigin:/^origin-/,transition:/^transition(-none|-all|-colors|-opacity|-shadow|-transform)?$/,duration:/^duration-/,ease:/^ease-(linear|in|out|in-out)$/,delay:/^delay-/,animate:/^animate-/,cursor:/^cursor-/,userSelect:/^select-/,pointerEvents:/^pointer-events-/,resize:/^resize(-none|-x|-y)?$/,scrollBehavior:/^scroll-(auto|smooth)$/,touchAction:/^touch-/,overflow:/^overflow(-x|-y)?-(auto|hidden|clip|visible|scroll)$/,overscroll:/^overscroll(-x|-y)?-(auto|contain|none)$/,visibility:/^(visible|invisible|collapse)$/,aspectRatio:/^aspect-/,columns:/^columns-/,breakAfter:/^break-after-/,breakBefore:/^break-before-/,breakInside:/^break-inside-/,boxDecorationBreak:/^box-decoration-/,boxSizing:/^box-(border|content)$/,float:/^float-(right|left|none)$/,clear:/^clear-(left|right|both|none)$/,isolation:/^(isolate|isolation-auto)$/,objectFit:/^object-(contain|cover|fill|none|scale-down)$/,objectPosition:/^object-/};function yy(o){const e=o.replace(/^(dark:|hover:|focus:|active:|disabled:|group-hover:|sm:|md:|lg:|xl:|2xl:)+/,"");for(const[n,a]of Object.entries(sA))if(a.test(e))return n;return null}function by(o){const e=o.match(/^((dark:|hover:|focus:|active:|disabled:|group-hover:|sm:|md:|lg:|xl:|2xl:)+)/);return e?e[1]:""}function oA(o,e){if(!e||e.trim()==="")return o;if(!o||o.trim()==="")return e;const n=o.split(/\s+/).filter(Boolean),a=e.split(/\s+/).filter(Boolean),i=new Map;for(const c of a){const d=yy(c);if(d){const f=by(c),g=`${d}:${f}`;i.set(g,c)}}return[...n.filter(c=>{const d=yy(c);if(!d)return!0;const f=by(c),g=`${d}:${f}`;return!i.has(g)}),...a].join(" ")}function lA(o){return Object.entries(o).filter(([,e])=>e).map(([e])=>e).join(" ")}function cA(...o){return o.filter(e=>typeof e=="string"&&e.length>0).join(" ")}const be=ut("G7CoreGlobals");function uA(){window.React=Ye,window.ReactDOM={...ad,createPortal:ka.createPortal,unstable_batchedUpdates:o=>o()},window.ReactJSXRuntime=iC,be.log("전역 객체 window.React, window.ReactDOM, window.ReactJSXRuntime에 노출됨")}function dA(o){const e=new Map,n=Oa.getInstance();o.componentEvent={on:(a,i)=>(e.has(a)||e.set(a,new Set),e.get(a).add(i),n.trackEventSubscribe(a),()=>{e.get(a)?.delete(i),n.trackEventUnsubscribe(a)}),emit:async(a,i)=>{const l=e.get(a),c=l?.size??0;if(!l||l.size===0)return n.trackEventEmit(a,i,0),[];let d,f=[];try{f=await Promise.all(Array.from(l).map(async g=>{try{return await g(i)}catch(m){throw be.error(`componentEvent: Error in listener for "${a}":`,m),m}}))}catch(g){d=g instanceof Error?g:new Error(String(g))}if(n.trackEventEmit(a,i,c,f,d),d)throw d;return f},off:a=>{e.delete(a),n.trackEventOff(a)},clear:()=>{e.clear(),n.trackEventClear()}},be.log("전역 객체 window.G7Core.componentEvent에 노출됨")}function fA(o){o.__runtime||(o.__runtime={DynamicRenderer:jr,ComponentRegistry:pr,TranslationEngine:_a,DataSourceManager:Er,dataSourceManager:V_,DataBindingEngine:xn,dataBindingEngine:md,ActionDispatcher:po,TranslationReactContext:hf,TranslationProvider:mc,useTranslation:ty,ResponsiveContext:qd,ResponsiveProvider:nc,useResponsive:Gd,responsiveManager:hi,BREAKPOINT_PRESETS:Il,AuthManager:yr,createLogger:ut,G7DevToolsCore:Oa},be.log("전역 객체 window.G7Core.__runtime(코어 런타임 표면)에 노출됨"))}function hA(o){if(o.layoutEditor&&o.layoutEditor.__isStub!==!0||o.layoutEditor&&o.layoutEditor.__isStub===!0)return;const e=[],n=[];o.layoutEditor={__isStub:!0,__queue:e,__readyCallbacks:n,registerWidget:(a,i)=>e.push(["widget",a,i]),registerNodeEditor:(a,i)=>e.push(["nodeEditor",a,i]),registerCanvasOverlay:(a,i)=>e.push(["canvasOverlay",a,i]),onReady:a=>{typeof a=="function"&&n.push(a)}},be.log("전역 객체 window.G7Core.layoutEditor 예약 접수함(stub) 노출됨")}function gA(o,e){o.useTranslation=ty,o.t=(n,a)=>{const i=e.getState();if(!i.translationEngine||!i.translationContext)return n;if(a){const l="|"+Object.entries(a).map(([c,d])=>`${c}=${d}`).join("|");return i.translationEngine.translate(n,i.translationContext,l)}return i.translationEngine.translate(n,i.translationContext)},be.log("전역 객체 window.G7Core.useTranslation에 노출됨")}function pA(o,e){o.createChangeEvent=nA,o.createClickEvent=rA,o.createSubmitEvent=aA,o.createKeyboardEvent=iA,o.uuid=()=>typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,n=>{const a=Math.random()*16|0;return(n==="x"?a:a&3|8).toString(16)}),o.createLogger=ut,be.log("전역 객체 window.G7Core.createLogger에 노출됨"),o.renderItemChildren=(n,a,i,l,c)=>{const d=e.getState(),m={_global:window.__templateApp?.getGlobalState?.()||{},_local:a._local||{},_computed:a._computed||{},...a},b=new Set,S=(_,A)=>{const x=m._global?._remountKeys;return _&&x?.[_]&&!b.has(_)?(b.add(_),`${_}-remount-${x[_]}`):_||A},v={translationContext:d.translationContext,translationEngine:d.translationEngine,bindingEngine:d.bindingEngine,actionDispatcher:d.actionDispatcher,getRemountKey:S,...c};return io(n,m,i,l,v)},o.evaluateCondition=(n,a)=>{if(!n)return!0;const i=e.getState(),c=window.__templateApp?.getGlobalState?.()||{},d={_global:c,_local:c._local||{},_computed:c._computed||{},...a||{}},f=i.bindingEngine??md;return mp(n,d,f)},o.getComponentMap=()=>pr.getInstance().getComponentMap(),o.renderComponentLayout=(n,a,i)=>{if(!n||!Array.isArray(n)||n.length===0)return null;const l=pr.getInstance().getComponentMap(),c=e.getState(),f=window.__templateApp?.getGlobalState?.()||{},g={_global:f._global||{},_local:f._local||{},_computed:f._computed||{},...a},m=new Set,b=(S,v)=>{const _=g._global?._remountKeys;return S&&_?.[S]&&!m.has(S)?(m.add(S),`${S}-remount-${_[S]}`):S||v};return io(n,g,l,i,{translationContext:c.translationContext,translationEngine:c.translationEngine,bindingEngine:c.bindingEngine,actionDispatcher:c.actionDispatcher,getRemountKey:b})},o.renderExpandContent=n=>{const{children:a,row:i,expandContext:l,componentContext:c,keyPrefix:d}=n;if(!a||!Array.isArray(a)||a.length===0)return null;const f=n.componentMap||pr.getInstance().getComponentMap(),g=e.getState(),m=o.state?.get?.()||{},b=c?.stateRef?.current??c?.state??{},S={...m._local||{},...b},v=c?.computedRef?.current??m._computed??{},_=g.bindingEngine??o.getDataBindingEngine?.(),A={...m,_local:S,_computed:v,row:i,item:i,$item:i};let x={};if(l&&typeof l=="object"&&_)try{for(const[D,B]of Object.entries(l))if(typeof B=="string"){const q=Aa(B);if(q!==null)try{x[D]=Pn(q)?_.evaluatePipeExpression(q,A,{skipCache:!0}):_.evaluateExpression(q,A,{skipCache:!0})}catch{x[D]=void 0}else x[D]=_.resolveBindings(B,A,{skipCache:!0})}else x[D]=B}catch(D){be.warn("renderExpandContent: expandContext 평가 오류:",D)}const L={row:i,item:i,$item:i,_local:S,_global:m._global||{},_computed:m._computed||{},...x},M=new Set,R=(D,B)=>{const q=L._global?._remountKeys;return D&&q?.[D]&&!M.has(D)?(M.add(D),`${D}-remount-${q[D]}`):D||B};return io(a,L,f,d,{translationContext:g.translationContext,translationEngine:g.translationEngine??void 0,bindingEngine:g.bindingEngine??void 0,actionDispatcher:g.actionDispatcher,componentContext:c,getRemountKey:R})},o.$get=function(a,i,l=void 0){if(a==null)return l;const c=Array.isArray(i)?i:[i];if(c.length===0)return a;let d=a;for(const f of c){if(d==null||f==null)return l;d=d[f]}return d??l},be.log("전역 객체 window.G7Core.renderItemChildren에 노출됨"),be.log("전역 객체 window.G7Core.renderComponentLayout에 노출됨"),be.log("전역 객체 window.G7Core.renderExpandContent에 노출됨"),be.log("전역 객체 window.G7Core.$get에 노출됨")}function mA(o){o.dispatch=async(e,n)=>{const a=window.__templateApp;if(!a)return be.warn("G7Core.dispatch: TemplateApp이 초기화되지 않았습니다."),{success:!1,error:new Error("TemplateApp이 초기화되지 않았습니다.")};const i=a.getActionDispatcher?.();if(!i)return be.warn("G7Core.dispatch: ActionDispatcher를 찾을 수 없습니다."),{success:!1,error:new Error("ActionDispatcher를 찾을 수 없습니다.")};const l=a.getRouter?.(),c=a.getGlobalState?.(),d=a.setGlobalState?.bind(a),g=n?.componentContext||window.__g7ActionContext,m=window.__g7PendingLocalState;let b=g?.state??c;m&&g&&(b=m,be.log("[dispatch] Using __g7PendingLocalState for context.state:",m));let S=g?.data??c;m&&g?.data?._local&&(S={...g.data,_local:m},be.log("[dispatch] Updated context.data._local with pendingLocalState"));const v={navigate:l?(_,A)=>l.navigate(_,A):void 0,setState:g?.setState??d,state:b,data:S,_isDispatchFallbackContext:!g?.setState};if(e.debounce){const _=e.debounceKey||`dispatch-${e.handler}`,A=typeof e.debounce=="number"?e.debounce:e.debounce.delay;return i.debouncedCall(_,A,()=>{const{debounce:x,debounceKey:L,...M}=e;i.dispatchAction(M,v)}),{success:!0,debounced:!0}}try{return await i.dispatchAction(e,v)}catch(_){return be.error("G7Core.dispatch: 액션 실행 오류:",_),{success:!1,error:_ instanceof Error?_:new Error(String(_))}}},be.log("전역 객체 window.G7Core.dispatch에 노출됨")}function To(o){const e={};for(const[n,a]of Object.entries(o))if(n.includes(".")){const i=n.split(".");let l=e;for(let c=0;c0&&e.every(n=>/^\d+$/.test(n))}function Sy(o,e){const n={...o};for(const a of Object.keys(e))a in n?n[a]!==null&&typeof n[a]=="object"&&!Array.isArray(n[a])&&e[a]!==null&&typeof e[a]=="object"&&!Array.isArray(e[a])&&(n[a]=Sy(n[a],e[a])):n[a]=e[a];return n}function ar(o,e){if(Array.isArray(o)&&!Array.isArray(e)&&yA(e)){const a=Object.keys(e).map(l=>parseInt(l,10));if(!((a.length>0?Math.max(...a):0)>=o.length+a.length+10)){const l=[...o];for(const[c,d]of Object.entries(e)){const f=parseInt(c,10);f>=0&&f=l.length&&(l[f]=d)}return l}}const n={...o};for(const[a,i]of Object.entries(e))i!==null&&typeof i=="object"&&!Array.isArray(i)?n[a]!==null&&typeof n[a]=="object"?n[a]=ar(n[a],i):n[a]={...i}:n[a]=i;return n}function bA(o){o.state={get:()=>window.__templateApp?.getGlobalState?.()||{},set:(e,n)=>{const a=window.__templateApp;if(a?.setGlobalState&&a?.getGlobalState){const i=n?.merge||"deep",l=To(e);let c;if(i==="replace")c=l;else if(i==="shallow")c={...a.getGlobalState(),...l};else{const d=a.getGlobalState();c=ar(d,l)}a.setGlobalState(c,{render:n?.render})}else be.warn("G7Core.state.set: TemplateApp이 초기화되지 않았습니다.")},setLocal:(e,n)=>{const a=window.__templateApp;if(n?.render===!1&&!n?.selfManaged){const x=window.__g7AutoBindingPaths;x&&x.size>0&&vy(To(e)).some(M=>x.has(M))&&(be.log("[setLocal] render:false + 자동바인딩 경로 겹침 감지 → render:true 자동 승격 (engine-v1.43.0)"),n={...n,render:!0})}if(n?.debounce){const x=a?.getActionDispatcher?.();if(x){const L=n.debounceKey||`setLocal-${Object.keys(e).join(",")}`,{debounce:M,debounceKey:R,...D}=n;x.debouncedCall(L,n.debounce,()=>{o.state.setLocal(e,D)});return}}const i=n?.scope??"current",l=n?.merge||"deep",c=window.__g7ActionContext,d=To(e);if(i==="parent"||i==="root"){const x=window.__g7LayoutContextStack||[];if(x.length>0){const L=i==="parent"?x[x.length-1]:x[0];if(L?.setState){L.setState(M=>l==="replace"?d:l==="shallow"?{...M||{},...d}:ar(M||{},d)),be.log(`[setLocal] scope=${i}: 타겟 컨텍스트에 상태 업데이트 (mergeMode=${l})`,d);return}}be.warn(`[setLocal] scope=${i}: 레이아웃 컨텍스트 스택이 비어있습니다. current로 폴백합니다.`)}const f=window.__g7PendingLocalState,g=a?.getGlobalState?.()?._local||{},S=!((window.__g7LayoutContextStack||[]).length>0)&&c?.state?c.state:void 0,v=S?Sy(g,S):g,_=f||v;let A;if(l==="replace"?A=d:l==="shallow"?A={..._,...d}:A=ar(_,d),a?.setGlobalState&&(a.setGlobalState({_local:A},{render:n?.render}),be.log(`[setLocal] globalLocal updated via setGlobalState (mergeMode=${l}, render=${n?.render??!0}):`,d)),window.__g7PendingLocalState=A,be.log("[setLocal] __g7PendingLocalState updated:",A),window.__g7SequenceLocalSync=A,window.__g7LastSetLocalSnapshot=A,l==="replace")window.__g7ForcedLocalFields=d;else{const x=window.__g7ForcedLocalFields||{};window.__g7ForcedLocalFields=ar(x,d)}if(be.log("[setLocal] __g7ForcedLocalFields updated:",d),l==="replace")window.__g7SetLocalOverrideKeys=d;else{const x=window.__g7SetLocalOverrideKeys||{};window.__g7SetLocalOverrideKeys=ar(x,d)}be.log("[setLocal] __g7SetLocalOverrideKeys updated:",d),c?.setState&&c.setState(x=>l==="replace"?d:l==="shallow"?{...x||{},...d}:ar(x||{},d)),a?.setGlobalState||be.warn("G7Core.state.setLocal: TemplateApp이 없습니다.")},getLocal:()=>{const a=(window.__templateApp?.getGlobalState?.()||{})._local||{},i=window.__g7PendingLocalState;if(i)return ar(a,i);const l=window.__g7LastSetLocalSnapshot;return l&&l!==a?ar(a,l):a},update:e=>{const n=window.__templateApp;if(n?.getGlobalState&&n?.setGlobalState){const a=n.getGlobalState(),i=e(a);n.setGlobalState(i)}else be.warn("G7Core.state.update: TemplateApp이 초기화되지 않았습니다.")},subscribe:e=>{const n=window.__templateApp;return n?.onGlobalStateChange?n.onGlobalStateChange(e):(be.warn("G7Core.state.subscribe: TemplateApp이 초기화되지 않았습니다."),()=>{})},getDataSource:e=>window.__templateApp?.getDataSource?.(e),getIsolated:e=>e?window.__g7IsolatedStates?.[e]?.state??null:window.__g7ActionContext?.isolatedContext?.state??null,setIsolated:(e,n,a)=>{let i,l,c;typeof e=="string"?(i=e,l=n||{},c=a):(l=e,c=n&&typeof n=="object"&&"merge"in n?n:a);const d=c?.merge||"deep",f=To(l);if(i){const b=window.__g7IsolatedStates?.[i];b?.mergeState?b.mergeState(f,d):be.warn(`G7Core.state.setIsolated: scopeId '${i}'를 찾을 수 없습니다.`);return}const g=window.__g7ActionContext;g?.isolatedContext?.mergeState?g.isolatedContext.mergeState(f,d):be.warn("G7Core.state.setIsolated: 액션 컨텍스트에 isolatedContext가 없습니다.")},getParent:()=>{const e=window.__g7LayoutContextStack||[];if(e.length===0)return be.log("[getParent] 레이아웃 컨텍스트 스택이 비어있습니다."),null;const n=e[e.length-1];if(!n)return null;const a=n.dataContext||{};return{_local:n.state||a._local||{},_global:a._global||o.state.get()||{},setState:n.setState}},setParentLocal:(e,n,a)=>{const i=window.__g7LayoutContextStack||[];if(i.length===0){be.warn("[setParentLocal] 레이아웃 컨텍스트 스택이 비어있습니다.");return}const l=i[i.length-1];if(!l?.setState){be.warn("[setParentLocal] 부모 컨텍스트에 setState가 없습니다.");return}let c,d;typeof e=="string"?(c={[e]:n},d=a):(c=e,d=n&&typeof n=="object"&&"merge"in n?n:a);const f=d?.merge||"deep",g=To(c),m=l.state?._local!==void 0,b=m?l.state._local:l.state||{};let S;f==="replace"?S=g:f==="shallow"?S={...b,...g}:S=ar(b,g),window.__g7PendingLocalState=S;const v=window.__templateApp;if(v?.setGlobalState&&v.setGlobalState({_local:S}),l.setState(S),m?l.state._local=S:l.state=S,l.dataContext&&(l.dataContext._local=S),f==="replace")window.__g7ForcedLocalFields=g;else{const _=window.__g7ForcedLocalFields||{};window.__g7ForcedLocalFields=ar(_,g)}Ud()},setParentGlobal:(e,n)=>{let a;typeof e=="string"?a={[e]:n}:a=e,o.state.set(a),be.log("[setParentGlobal] 전역 상태 업데이트:",a)}},window.__g7IsolatedStates||(window.__g7IsolatedStates={}),o.state.getGlobal=o.state.get,o.state.setGlobal=o.state.set,be.log("전역 객체 window.G7Core.state에 노출됨")}function vA(o){o.dataSource={get:e=>window.__templateApp?.getDataSource?.(e),set:(e,n,a)=>{const i=window.__templateApp;i?.setDataSource?i.setDataSource(e,n,a):be.warn("G7Core.dataSource.set: TemplateApp이 초기화되지 않았습니다.")},refetch:async(e,n)=>{const a=window.__templateApp;if(a?.refetchDataSource)return a.refetchDataSource(e,n);be.warn("G7Core.dataSource.refetch: TemplateApp이 초기화되지 않았습니다.")},updateItem:(e,n,a,i,l)=>{const c=window.__templateApp;return c?.updateDataSourceItem?c.updateDataSourceItem(e,n,a,i,l):(be.warn("G7Core.dataSource.updateItem: TemplateApp이 초기화되지 않았습니다."),!1)},updateData:(e,n,a,i="append")=>{const l=window.__templateApp;if(!l?.getDataSource||!l?.setDataSource)return be.warn("G7Core.dataSource.updateData: TemplateApp이 초기화되지 않았습니다."),!1;const c=l.getDataSource(e);if(!c)return be.warn(`G7Core.dataSource.updateData: 데이터 소스 '${e}'를 찾을 수 없습니다.`),!1;let d,f=c,g=null;if(n){const b=n.split(".");g=b.pop();for(const S of b)if(f&&typeof f=="object"&&S in f)f=f[S];else return be.warn(`G7Core.dataSource.updateData: 경로 '${n}'를 찾을 수 없습니다.`),!1;d=f[g]}else d=c;if(!Array.isArray(d))return be.warn("G7Core.dataSource.updateData: 대상 경로의 데이터가 배열이 아닙니다."),!1;if(!Array.isArray(a))return be.warn("G7Core.dataSource.updateData: newData가 배열이 아닙니다."),!1;const m=i==="prepend"?[...a,...d]:[...d,...a];return n&&g?(f[g]=m,l.setDataSource(e,c,{merge:!1})):l.setDataSource(e,m,{merge:!1}),be.log(`G7Core.dataSource.updateData: '${e}'에 ${a.length}개 항목 ${i==="prepend"?"앞에":"뒤에"} 추가됨`),!0}},be.log("전역 객체 window.G7Core.dataSource에 노출됨")}function SA(o,e){o.locale={current:()=>window.__templateApp?.getLocale?.()||"ko",supported:()=>{const a=window.__templateApp?.globalState?.appConfig?.supportedLocales;return Array.isArray(a)&&a.length>0?a:e.getState().templateMetadata?.locales||["ko","en"]},change:async n=>{const a=window.__templateApp;a?.changeLocale?await a.changeLocale(n):be.warn("G7Core.locale.change: TemplateApp이 초기화되지 않았습니다.")}},be.log("전역 객체 window.G7Core.locale에 노출됨")}function wA(o){o.toast={show:(e,n)=>{o.dispatch({handler:"toast",params:{message:e,type:n?.type||"info",...n?.duration&&{duration:n.duration}}})},success:(e,n)=>{o.toast.show(e,{type:"success",duration:n})},error:(e,n)=>{o.toast.show(e,{type:"error",duration:n})},warning:(e,n)=>{o.toast.show(e,{type:"warning",duration:n})},info:(e,n)=>{o.toast.show(e,{type:"info",duration:n})}},be.log("전역 객체 window.G7Core.toast에 노출됨")}function CA(o){o.modal={open:e=>{o.dispatch({handler:"openModal",target:e})},close:e=>{o.dispatch({handler:"closeModal",...e&&{target:e}})},closeAll:()=>{o.dispatch({handler:"closeAllModals"})},isOpen:e=>{const n=o.state.get();return n._global?.activeModal===e||n._global?.modalStack?.includes(e)||!1},getStack:()=>o.state.get()._global?.modalStack||[]},be.log("전역 객체 window.G7Core.modal에 노출됨")}function EA(o){o.style={mergeClasses:oA,conditionalClass:lA,joinClasses:cA},be.log("전역 객체 window.G7Core.style에 노출됨")}function _A(o,e){const{webSocketManager:n}=e;o.websocket={manager:n,subscribe:(a,i,l,c)=>n.subscribe(a,i,l,c),unsubscribe:a=>n.unsubscribe(a),leaveChannel:a=>n.leaveChannel(a),disconnect:()=>n.disconnect(),isInitialized:()=>n.isInitialized(),getSubscriptionCount:()=>n.getSubscriptionCount()},be.log("전역 객체 window.G7Core.websocket에 노출됨")}function AA(o,e){const{transitionManager:n}=e;o.navigation={isPending:()=>n?.getIsPending?.()||!1,onComplete:a=>{if(!n)return a(),()=>{};let i=n.getIsPending();const l=n.subscribe(c=>{c?i=!0:i&&(a(),l())});return l}},be.log("전역 객체 window.G7Core.navigation에 노출됨")}function xA(o){o.plugin={getSettings:e=>window.G7Config?.plugins?.[e],get:(e,n,a)=>window.G7Config?.plugins?.[e]?.[n]??a,getAll:()=>window.G7Config?.plugins??{}},be.log("전역 객체 window.G7Core.plugin에 노출됨")}function TA(o){o.module={getSettings:e=>window.G7Config?.modules?.[e],get:(e,n,a)=>window.G7Config?.modules?.[e]?.[n]??a,getAll:()=>window.G7Config?.modules??{}},be.log("전역 객체 window.G7Core.module에 노출됨")}function RA(o,e){const{transitionManager:n,responsiveManager:a}=e;o.AuthManager=yr,o.api=Ir(),o.TransitionManager=n,o.useTransitionState=fm,o.ResponsiveManager=a,o.useResponsive=Gd,o.useControllableState=bC,o.shallowArrayEqual=vC,o.shallowObjectEqual=gm,o.updateQueryParams=async(i,l)=>{const c=window.__templateApp;if(c?.updateQueryParams)return c.updateQueryParams(i,l);be.warn("G7Core.updateQueryParams: TemplateApp이 초기화되지 않았습니다.")},be.log("전역 객체 window.G7Core.AuthManager에 노출됨"),be.log("전역 객체 window.G7Core.api에 노출됨"),be.log("전역 객체 window.G7Core.ResponsiveManager에 노출됨"),be.log("전역 객체 window.G7Core.useResponsive에 노출됨"),be.log("전역 객체 window.G7Core.useControllableState에 노출됨"),be.log("전역 객체 window.G7Core.updateQueryParams에 노출됨")}function kA(o,e){o.getSlotContext=()=>window.__slotContextValue??null,o.getDynamicRenderer=()=>window.__DynamicRenderer??null,o.getComponentRegistry=()=>e.getComponentRegistry?.()??pr.getInstance(),o.getDataBindingEngine=()=>e.getDataBindingEngine?.()??e.getState().bindingEngine,o.getTranslationEngine=()=>e.getTranslationEngine?.()??e.getState().translationEngine,o.getActionDispatcher=()=>e.getActionDispatcher?.()??e.getState().actionDispatcher,be.log("전역 객체 window.G7Core 슬롯 API에 노출됨 (getSlotContext, getDynamicRenderer 등)")}function DA(o){o.identity={setLauncher:vt.setLauncher.bind(vt),handle:vt.handle.bind(vt),isIdentityRequired:vt.isIdentityRequired.bind(vt),hasLauncher:vt.hasLauncher.bind(vt),markDomainNoticeShown:vt.markDomainNoticeShown.bind(vt),redirectExternally:vt.redirectExternally.bind(vt),createDeferred:vt.createDeferred.bind(vt),resolveDeferred:vt.resolveDeferred.bind(vt),reset:vt.reset.bind(vt),redirectStashKey:$d},be.log("전역 객체 window.G7Core 본인인증(IDV) API에 노출됨 (identity)")}function OA(o){if(typeof window>"u")return;uA(),window.G7Core||(window.G7Core={});const e=window.G7Core;fA(e),dA(e),hA(e),RA(e,o),gA(e,o),pA(e,o),mA(e),bA(e),vA(e),SA(e,o),wA(e),CA(e),EA(e),_A(e,o),AA(e,o),xA(e),TA(e),kA(e,o),DA(e),be.log("G7Core 전역 객체 초기화 완료")}function LA(){const o=window.G7Core;if(!o){be.warn("G7Core가 초기화되지 않았습니다.");return}const e=()=>Oa.getInstance(),n={isEnabled:()=>{try{return e().isEnabled()}catch{return!1}},trackRender:a=>{try{e().isEnabled()&&e().trackRender(a)}catch{}},trackIteration:(a,i,l,c,d)=>{try{e().isEnabled()&&e().trackIteration(a,{source:i,itemVar:l,indexVar:c,sourceLength:d})}catch{}},trackIfCondition:(a,i,l,c)=>{try{e().isEnabled()&&e().trackIfCondition(a,i,l)}catch{}},trackMount:(a,i)=>{try{e().isEnabled()&&e().trackMount(a,{...i,id:a})}catch{}},trackUnmount:a=>{try{e().isEnabled()&&e().trackUnmount(a)}catch{}},trackExpressionEval:a=>{try{e().isEnabled()&&e().trackExpressionEval(a)}catch{}},trackBindingEval:a=>{try{e().isEnabled()&&e().trackBindingEval(a)}catch{}},recordCacheHit:()=>{try{e().isEnabled()&&e().recordCacheHit()}catch{}},recordCacheMiss:()=>{try{e().isEnabled()&&e().recordCacheMiss()}catch{}},trackHandlerRegistration:(a,i,l,c)=>{try{e().isEnabled()&&e().trackHandlerRegistration(a,i,l,c)}catch{}},trackHandlerUnregistration:a=>{try{e().isEnabled()&&e().trackHandlerUnregistration(a)}catch{}},logAction:a=>{try{e().isEnabled()&&e().logAction(a)}catch{}},trackRequest:(a,i)=>{try{return e().isEnabled()?e().trackRequest(a,i):""}catch{return""}},completeRequest:(a,i,l)=>{try{e().isEnabled()&&e().completeRequest(a,i,l)}catch{}},failRequest:(a,i)=>{try{e().isEnabled()&&e().failRequest(a,i)}catch{}},trackDataSourceDefinition:a=>{try{e().isEnabled()&&e().trackDataSourceDefinition(a)}catch{}},trackDataSourceLoading:a=>{try{e().isEnabled()&&e().trackDataSourceLoading(a)}catch{}},trackDataSourceLoaded:(a,i,l)=>{try{e().isEnabled()&&e().trackDataSourceLoaded(a,i,l)}catch{}},trackDataSourceError:(a,i)=>{try{e().isEnabled()&&e().trackDataSourceError(a,i)}catch{}},trackForm:(a,i,l)=>{try{e().isEnabled()&&e().trackForm(a,i,l)}catch{}},untrackForm:a=>{try{e().isEnabled()&&e().untrackForm(a)}catch{}},startStateChange:(a,i,l,c)=>{try{return e().isEnabled()?e().startStateChange(a,i,l,c):""}catch{return""}},completeStateChange:a=>{try{e().isEnabled()&&e().completeStateChange(a)}catch{}},trackComponentRender:(a,i,l,c,d,f)=>{try{e().isEnabled()&&e().trackComponentRender(a,i,l,c,d,f)}catch{}},updateLocalState:a=>{try{e().isEnabled()&&e().updateLocalState(a)}catch{}},updateComputedState:a=>{try{e().isEnabled()&&e().updateComputedState(a)}catch{}},updateParentContext:a=>{try{e().isEnabled()&&e().updateParentContext(a)}catch{}},trackComponentStateSource:(a,i,l,c)=>{try{e().isEnabled()&&e().trackComponentStateSource(a,i,l,c)}catch{}},trackDynamicState:(a,i)=>{try{e().isEnabled()&&e().trackDynamicState(a,i)}catch{}},trackContextFlow:(a,i,l,c,d,f)=>{try{e().isEnabled()&&e().trackContextFlow(a,i,l,c,d,f)}catch{}},trackComponentStyle:(a,i,l,c)=>{try{e().isEnabled()&&e().trackComponentStyle(a,i,l,c)}catch{}},trackAuthEvent:(a,i,l,c)=>{try{e().isEnabled()&&e().trackAuthEvent(a,i,l,c)}catch{}},trackAuthHeader:(a,i,l,c,d)=>{try{e().isEnabled()&&e().trackAuthHeader(a,i,l,c,d)}catch{}},trackLog:(a,i,l)=>{try{e().isEnabled()&&e().trackLog(a,i,l)}catch{}},trackAction:a=>{try{e().isEnabled()&&e().trackAction?.(a)}catch{}},trackDataSourceUpdate:a=>{try{e().isEnabled()&&e().trackDataSourceUpdate?.(a)}catch{}},startSequenceExecution:a=>{try{return e().isEnabled()?e().startSequenceExecution(a):""}catch{return""}},captureSequenceActionBefore:(a,i,l,c,d)=>{try{e().isEnabled()&&e().captureSequenceActionBefore(a,i,l,c,d)}catch{}},captureSequenceActionAfter:(a,i,l,c,d,f)=>{try{e().isEnabled()&&e().captureSequenceActionAfter(a,i,l,c,d,f)}catch{}},endSequenceExecution:(a,i)=>{try{e().isEnabled()&&e().endSequenceExecution(a,i)}catch{}},registerStateCaptureForHandler:(a,i,l)=>{try{e().isEnabled()&&e().registerStateCaptureForHandler(a,i,l)}catch{}},detectStaleClosure:(a,i,l,c,d)=>{try{return e().isEnabled()?e().detectStaleClosure(a,i,l,c,d):[]}catch{return[]}},trackStaleClosureWarning:a=>{try{e().isEnabled()&&e().trackStaleClosureWarning(a)}catch{}},trackModalOpen:a=>{try{e().isEnabled()&&e().trackModalOpen(a)}catch{}},trackModalClose:(a,i)=>{try{e().isEnabled()&&e().trackModalClose(a,i)}catch{}},trackModalStateChange:a=>{try{e().isEnabled()&&e().trackModalStateChange(a)}catch{}},trackNestedContext:a=>{try{return e().isEnabled()?e().trackNestedContext(a):""}catch{return""}},trackNestedContextAccess:(a,i)=>{try{e().isEnabled()&&e().trackNestedContextAccess(a,i)}catch{}},trackComputedProperty:(a,i,l,c,d,f,g)=>{try{e().isEnabled()&&e().trackComputedProperty(a,i,l,c,d,f,g)}catch{}},trackComputedRecalc:(a,i,l,c,d,f,g)=>{try{e().isEnabled()&&e().trackComputedRecalc(a,i,l,c,d,f,g)}catch{}},setNamedActionDefinitions:a=>{try{e().isEnabled()&&e().setNamedActionDefinitions(a)}catch{}},trackNamedActionRef:a=>{try{e().isEnabled()&&e().trackNamedActionRef(a)}catch{}}};o.devTools=n,be.log("G7Core.devTools 인터페이스 초기화 완료")}let Ro=null;function MA(){const o=window.G7Core;return o?.__devtools?Promise.resolve(o.__devtools):Ro||(Ro=new Promise((e,n)=>{const a=window.G7Config?.coreDevToolsAsset||"/build/core/devtools.min.js",i="g7-devtools-bundle",l=()=>{const f=window.G7Core?.__devtools;f?e(f):n(new Error("DevTools 번들 로드됨 — 그러나 __devtools 미노출"))};window.G7Core=window.G7Core||{},window.G7Core.__onDevToolsReady=l;const c=document.getElementById(i);if(c){c.addEventListener("load",l,{once:!0}),c.addEventListener("error",()=>n(new Error(`DevTools 번들 로드 실패: ${a}`)),{once:!0});return}const d=document.createElement("script");d.id=i,d.src=a,d.async=!1,d.addEventListener("load",l,{once:!0}),d.addEventListener("error",()=>{Ro=null,n(new Error(`DevTools 번들 로드 실패: ${a}`))},{once:!0}),document.head.appendChild(d)}),Ro)}function $A(){const o=window.G7Core;if(!o){be.warn("G7Core가 초기화되지 않았습니다.");return}const e=Oa.getInstance();if(e.initialize(),!e.isEnabled()){window.G7DevTools={isEnabled:()=>!1,enable:()=>{be.log("환경설정 > 고급 설정 > 디버그 모드를 켜세요")}},sd(),be.log("G7DevTools 비활성화됨 (디버그 모드 꺼짐)");return}NA(o,e)}async function NA(o,e){let n;try{n=await MA()}catch(g){be.warn("DevTools 번들 로드 실패 — 최소 API 로 폴백:",g),window.G7DevTools={isEnabled:()=>!1,enable:()=>{}},sd();return}const{DiagnosticEngine:a,getServerConnector:i,getStyleTracker:l}=n;LA(),sd();try{l().enable(),be.log("StyleTracker 활성화됨")}catch(g){be.warn("StyleTracker 활성화 실패:",g)}const c=new a,d=i(),f={isEnabled:()=>e.isEnabled(),state:{get:()=>e.getState(),getHistory:()=>e.getStateHistory(),watch:(g,m)=>e.watchState(g,m)},actions:{getHistory:()=>e.getActionHistory(),watch:g=>e.watchActions(g),getMetrics:()=>e.getActionMetrics()},binding:{evaluate:g=>{const m=o.getDataBindingEngine?.();if(!m){be.warn("DataBindingEngine이 없습니다.");return}return m.evaluateExpression(g,e.getState())},getCacheStats:()=>e.getCacheStats(),clearStats:()=>{e.resetCacheStats(),be.log("캐시 통계 초기화됨")}},diagnose:{analyze:g=>c.analyze(g),suggestFix:g=>c.suggestFix(g),getCommonIssues:()=>c.getCommonIssues(),getRulesByCategory:g=>c.getRulesByCategory(g)},server:{dumpState:g=>d.dumpState(g),sendLog:g=>d.sendLog(g),sendError:(g,m)=>d.sendError(g,m),isConnected:()=>d.isConnected(),testConnection:()=>d.testConnection()},config:{isDebugMode:()=>e.isEnabled(),setLogLevel:g=>e.setLogLevel(g),setMaxHistory:g=>e.setMaxHistory(g)},lifecycle:{getMountedComponents:()=>e.getLifecycleInfo().mountedComponents,getOrphanedListeners:()=>e.getLifecycleInfo().orphanedListeners},performance:{getRenderCount:()=>e.getPerformanceInfo().renderCounts,getBindingEvalCount:()=>e.getPerformanceInfo().bindingEvalCount,getMemoryWarnings:()=>e.getPerformanceInfo().memoryWarnings,startProfiling:()=>e.startProfiling(),stopProfiling:()=>e.stopProfiling()},network:{getActiveRequests:()=>e.getNetworkInfo().activeRequests,getRequestHistory:()=>e.getNetworkInfo().requestHistory,getPendingDataSources:()=>e.getNetworkInfo().pendingDataSources},conditional:{getIfConditions:()=>e.getConditionalInfo().ifConditions,getIterations:()=>e.getConditionalInfo().iterations},form:{getForms:()=>e.getFormInfo()},websocket:{getInfo:()=>e.getWebSocketInfo()}};window.G7DevTools=f,o.devtools=f,be.log("G7DevTools 전역 객체 초기화 완료 (window.G7DevTools)"),IA(n.DevToolsPanel)}function IA(o){if(document.getElementById("g7-devtools-root")){be.log("DevToolsPanel 이미 렌더링됨");return}const e=document.createElement("div");e.id="g7-devtools-root",document.body.appendChild(e);try{ad.createRoot(e).render(Ye.createElement(o)),be.log("DevToolsPanel UI 렌더링 완료")}catch(n){be.error("DevToolsPanel UI 렌더링 실패:",n)}}const Qe=ut("TemplateEngine"),ae={templateId:null,locale:"ko",isInitialized:!1,reactRoot:null,containerId:null,currentLayoutJson:null,currentDataContext:{},translationContext:{templateId:"",locale:"ko"},registry:null,bindingEngine:null,translationEngine:null,actionDispatcher:null,templateMetadata:null};let ko=!1;const Do=[];async function HA(o){try{Qe.log("템플릿 메타데이터 로드 중...",o);const e=o.split("-").map(a=>a.split("_").map(i=>i.charAt(0).toUpperCase()+i.slice(1)).join("")).join(""),n=window[e];return n?.templateMetadata?(Qe.log("템플릿 메타데이터 로드 완료 (전역 변수)",n.templateMetadata),n.templateMetadata):(Qe.warn(`템플릿 번들에 메타데이터가 없습니다. 전역 변수: ${e}`),{identifier:o,locales:["ko","en"],name:{ko:o,en:o},description:{ko:"",en:""},version:"1.0.0",type:"admin"})}catch(e){return Qe.error("템플릿 메타데이터 로드 실패",e),{identifier:o,locales:["ko","en"],name:{ko:o,en:o},description:{ko:"",en:""},version:"1.0.0",type:"admin"}}}function wy(){const e=(typeof window<"u"?window.__templateApp:void 0)?.globalState?.appConfig?.supportedLocales,n=ae.templateMetadata?.locales;return{$locale:ae.locale,$locales:Array.isArray(e)&&e.length>0?e:n||["ko","en"],$templateLocales:n||["ko","en"],$templateId:ae.templateId}}async function Oo(o){try{if(Qe.log("템플릿 엔진 초기화 시작",o),ae.isInitialized)throw new Error("템플릿 엔진이 이미 초기화되었습니다. destroyTemplate()을 먼저 호출하세요.");if(!o.templateId)throw new K_;ko=o.debug??!1,typeof window<"u"&&(window.G7Config||(window.G7Config={}),window.G7Config.debug=ko),$A(),ae.templateId=o.templateId,ae.locale=o.locale||"ko",Qe.log("엔진 인스턴스 생성 중..."),ae.registry=pr.getInstance(),ae.bindingEngine=new xn,ae.translationEngine=_a.getInstance(),o.cacheVersion!==void 0&&o.cacheVersion>0&&ae.translationEngine.setCacheVersion(o.cacheVersion),ae.translationContext={templateId:o.templateId,locale:ae.locale},ae.actionDispatcher=new po({},ae.translationEngine,ae.translationContext),cC(ae.actionDispatcher),Qe.log("다국어 파일 로드 중...",o.templateId,ae.locale);const e="en",n=[ae.translationEngine.loadTranslations(o.templateId,ae.locale).then(()=>{Qe.log(`다국어 파일 로드 완료: ${ae.locale}`)}).catch(a=>{Qe.warn(`다국어 파일 로드 실패 (${ae.locale}):`,a instanceof Error?a.message:a)})];ae.locale!==e&&n.push(ae.translationEngine.loadTranslations(o.templateId,e).then(()=>{Qe.log(`폴백 다국어 파일 로드 완료: ${e}`)}).catch(a=>{Qe.warn(`폴백 다국어 파일 로드 실패 (${e}):`,a instanceof Error?a.message:a)})),await Promise.allSettled(n),Qe.log("템플릿 메타데이터 로드 중...",o.templateId),ae.templateMetadata=await HA(o.templateId),ae.isInitialized=!0,Qe.log("템플릿 엔진 초기화 완료 (ComponentRegistry는 별도 로드)")}catch(e){throw Qe.error("템플릿 엔진 초기화 실패",e),ae.isInitialized=!1,ae.registry=null,ae.bindingEngine=null,ae.translationEngine=null,ae.actionDispatcher=null,e}}let Lo=null;function _f(){const o=window.G7Core;return o?.__LayoutEditorChrome?Promise.resolve(o.__LayoutEditorChrome):Lo||(Lo=new Promise((e,n)=>{const a=window.G7Config?.coreEditorAsset||"/build/core/layout-editor.min.js",i="g7-layout-editor-bundle",l=document.getElementById(i),c=()=>{const f=window.G7Core?.__LayoutEditorChrome;f?e(f):n(new Error("편집기 번들 로드됨 — 그러나 __LayoutEditorChrome 미노출"))};if(window.G7Core=window.G7Core||{},window.G7Core.__onChromeReady=c,l){l.addEventListener("load",c,{once:!0}),l.addEventListener("error",()=>n(new Error(`편집기 번들 로드 실패: ${a}`)),{once:!0});return}const d=document.createElement("script");d.id=i,d.src=a,d.async=!1,d.addEventListener("load",c,{once:!0}),d.addEventListener("error",()=>{Lo=null,n(new Error(`편집기 번들 로드 실패: ${a}`))},{once:!0}),document.head.appendChild(d)}),Lo)}async function hs(o){try{if(Qe.log("템플릿 렌더링 시작",o),!ae.isInitialized)throw new Error("템플릿 엔진이 초기화되지 않았습니다. initTemplateEngine()을 먼저 호출하세요.");if(!o.containerId)throw new Error("containerId는 필수입니다.");if(!o.layoutJson)throw new Error("layoutJson은 필수입니다.");const e=document.getElementById(o.containerId);if(!e)throw new Error(`컨테이너를 찾을 수 없습니다: #${o.containerId}`);const n=wy();if(ae.containerId=o.containerId,ae.currentLayoutJson=o.layoutJson,ae.currentDataContext={...n,...o.dataContext||{}},ae.translationContext=o.translationContext||{templateId:ae.templateId||"",locale:ae.locale},ae.reactRoot||(Qe.log("React Root 생성"),ae.reactRoot=ad.createRoot(e)),typeof window<"u"){const l=py(window.location.pathname);if(l){Qe.log("레이아웃 편집기 모드 진입",l);let c;try{c=await _f()}catch(f){Qe.error("레이아웃 편집기 번들 로드 실패",f),vf.render(o.containerId,{title:"레이아웃 편집기 로드 실패",message:f instanceof Error?f.message:"편집기 번들을 불러오지 못했습니다.",icon:"fas fa-triangle-exclamation",showStack:!1,showReloadButton:!0,debug:ko});return}const d=Ye.createElement(c,{templateIdentifier:l.templateIdentifier,initialLocale:ae.locale});ae.reactRoot.render(Ye.createElement(mc,{translationEngine:ae.translationEngine,translationContext:ae.translationContext,children:Ye.createElement(Bd,{children:Ye.createElement(nc,{children:Ye.createElement(gf,{children:d})})})}));return}}const a=o.layoutJson.components||[];if(a.length===0){Qe.warn("렌더링할 컴포넌트가 없습니다.");return}const i=o.layoutJson.modals||[];if(Qe.log("modals 배열:",i),Qe.log("modals 개수:",i.length),Qe.log("DynamicRenderer로 렌더링 시작"),ae.reactRoot.render(Ye.createElement(mc,{translationEngine:ae.translationEngine,translationContext:ae.translationContext},Ye.createElement(Bd,null,Ye.createElement(nc,null,Ye.createElement(gf,null,[...a.map((l,c)=>{const d=ae.currentLayoutJson?.layout_name||"";return Ye.createElement(jr,{key:d&&!l._fromBase?`${l.id}_${d}`:l.id,componentDef:l,dataContext:ae.currentDataContext,translationContext:ae.translationContext,registry:ae.registry,bindingEngine:ae.bindingEngine,translationEngine:ae.translationEngine,actionDispatcher:ae.actionDispatcher,isRootRenderer:c===0,layoutKey:d})}),Ye.createElement(om,{key:"__modal_parent_context_provider"},i.map(l=>{const c=ae.currentDataContext._global?.modalStack||[],f=c.includes(l.id)||ae.currentDataContext._global?.activeModal===l.id,g=c.indexOf(l.id),m=g>=0?50+g:50,b=window.__g7LayoutContextStack||[],v=b[b.length-1]?.dataContext,_=Ye.createElement(jr,{key:`modal_${l.id}_renderer`,componentDef:{...l,props:{...l.props,isOpen:f,style:{...l.props?.style,zIndex:m},onClose:ae.actionDispatcher?.createHandler({type:"click",handler:"closeModal"},ae.currentDataContext)}},dataContext:ae.currentDataContext,translationContext:ae.translationContext,registry:ae.registry,bindingEngine:ae.bindingEngine,translationEngine:ae.translationEngine,actionDispatcher:ae.actionDispatcher,parentDataContext:v});return l.data_sources&&l.data_sources.length>0?Ye.createElement(hy,{key:`modal_${l.id}`,isOpen:f,modalId:l.id,dataSources:l.data_sources,dataContext:ae.currentDataContext,globalStateUpdater:ae.actionDispatcher?.getGlobalStateUpdater(),bindingEngine:ae.bindingEngine,debug:ko,children:_}):_}))]))))),Qe.log("템플릿 렌더링 완료"),Do.length>0){Qe.log(`대기 중인 데이터 업데이트 ${Do.length}건 적용`);const l=[...Do];Do.length=0;for(const{data:c,options:d}of l)gs(c,d)}}catch(e){throw Qe.error("템플릿 렌더링 실패",e),e}}function gs(o,e){try{if(Qe.log("템플릿 데이터 업데이트 시작",Object.keys(o)),!ae.isInitialized)throw new Error("템플릿 엔진이 초기화되지 않았습니다.");if(!ae.reactRoot||!ae.currentLayoutJson){Qe.log("React 미준비 - 데이터 업데이트 큐잉:",Object.keys(o)),Do.push({data:o,options:e});return}const n=wy(),a={...ae.currentDataContext._global||{},...o._global||{}},i=mC(ae.currentDataContext._localInit,o._localInit);ae.currentDataContext={...n,...ae.currentDataContext,...o,_global:a,_localInit:i},a._local!==void 0&&(ae.currentDataContext._local=a._local),a._computed!==void 0&&(ae.currentDataContext._computed=a._computed);const l=ae.currentLayoutJson.components||[],c=ae.currentLayoutJson.modals||[];Qe.log("updateTemplateData - modals 배열:",c),Qe.log("updateTemplateData - modals 개수:",c.length),Qe.log("updateTemplateData - activeModal:",ae.currentDataContext._global?.activeModal);const d=()=>{ae.reactRoot.render(Ye.createElement(mc,{translationEngine:ae.translationEngine,translationContext:ae.translationContext},Ye.createElement(Bd,null,Ye.createElement(nc,null,Ye.createElement(gf,null,[...l.map((f,g)=>{const m=ae.currentLayoutJson?.layout_name||"";return Ye.createElement(jr,{key:m&&!f._fromBase?`${f.id}_${m}`:f.id,componentDef:f,dataContext:ae.currentDataContext,translationContext:ae.translationContext,registry:ae.registry,bindingEngine:ae.bindingEngine,translationEngine:ae.translationEngine,actionDispatcher:ae.actionDispatcher,isRootRenderer:g===0,layoutKey:m})}),Ye.createElement(om,{key:"__modal_parent_context_provider_update"},c.map(f=>{const g=ae.currentDataContext._global?.modalStack||[],b=g.includes(f.id)||ae.currentDataContext._global?.activeModal===f.id,S=g.indexOf(f.id),v=S>=0?50+S:50,_=window.__g7LayoutContextStack||[],x=_[_.length-1]?.dataContext,L=Ye.createElement(jr,{key:`modal_${f.id}_renderer`,componentDef:{...f,props:{...f.props,isOpen:b,style:{...f.props?.style,zIndex:v},onClose:ae.actionDispatcher?.createHandler({type:"click",handler:"closeModal"},ae.currentDataContext)}},dataContext:ae.currentDataContext,translationContext:ae.translationContext,registry:ae.registry,bindingEngine:ae.bindingEngine,translationEngine:ae.translationEngine,actionDispatcher:ae.actionDispatcher,parentDataContext:x});return f.data_sources&&f.data_sources.length>0?Ye.createElement(hy,{key:`modal_${f.id}`,isOpen:b,modalId:f.id,dataSources:f.data_sources,dataContext:ae.currentDataContext,globalStateUpdater:ae.actionDispatcher?.getGlobalStateUpdater(),bindingEngine:ae.bindingEngine,debug:ko,children:L}):L}))])))))};e?.sync?(Qe.log("재렌더링 시작 (sync mode - 즉시 렌더링)"),d()):(Qe.log("재렌더링 시작 (with startTransition)"),N.startTransition(()=>{d()})),Qe.log("템플릿 데이터 업데이트 완료")}catch(n){throw Qe.error("템플릿 데이터 업데이트 실패",n),n}}function Ec(){try{Qe.log("템플릿 정리 시작"),ae.reactRoot&&(Qe.log("React Root 언마운트"),ae.reactRoot.unmount(),ae.reactRoot=null),ae.templateId=null,ae.locale="ko",ae.isInitialized=!1,ae.containerId=null,ae.currentLayoutJson=null,ae.currentDataContext={},ae.translationContext={templateId:"",locale:"ko"},ae.registry=null,ae.bindingEngine=null,ae.translationEngine=null,ae.actionDispatcher=null,ae.templateMetadata=null,Qe.log("템플릿 정리 완료")}catch(o){throw Qe.error("템플릿 정리 실패",o),o}}function ea(){return Object.freeze({...ae})}function Cy(){return ae.actionDispatcher}const Mo={initTemplateEngine:Oo,renderTemplate:hs,updateTemplateData:gs,destroyTemplate:Ec,getState:ea};typeof window<"u"&&(window.G7Core||(window.G7Core={}),window.G7Core.TemplateEngine=Mo,OA({getState:()=>({translationEngine:ae.translationEngine,translationContext:ae.translationContext,bindingEngine:ae.bindingEngine,actionDispatcher:ae.actionDispatcher,templateMetadata:ae.templateMetadata}),transitionManager:Hr,responsiveManager:hi,webSocketManager:vc}),Qe.log("전역 객체 window.G7Core.TemplateEngine에 노출됨"));const Ur=Object.freeze(Object.defineProperty({__proto__:null,DataSourceManager:Er,LayoutLoader:ff,TemplateApp:xo,TemplateEngine:Mo,default:Mo,destroyTemplate:Ec,getActionDispatcher:Cy,getState:ea,initTemplateApp:Ef,initTemplateEngine:Oo,loadLayoutEditorBundle:_f,renderTemplate:hs,updateTemplateData:gs},Symbol.toStringTag,{value:"Module"}));Yt.DataSourceManager=Er,Yt.LayoutLoader=ff,Yt.TemplateApp=xo,Yt.TemplateEngine=Mo,Yt.default=Mo,Yt.destroyTemplate=Ec,Yt.getActionDispatcher=Cy,Yt.getState=ea,Yt.initTemplateApp=Ef,Yt.initTemplateEngine=Oo,Yt.loadLayoutEditorBundle=_f,Yt.renderTemplate=hs,Yt.updateTemplateData=gs,Object.defineProperties(Yt,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})})(this.G7Core=this.G7Core||{}); //# sourceMappingURL=template-engine.min.js.map diff --git a/resources/js/core/template-engine/CHANGELOG.md b/resources/js/core/template-engine/CHANGELOG.md index a9100017..df2fe9c9 100644 --- a/resources/js/core/template-engine/CHANGELOG.md +++ b/resources/js/core/template-engine/CHANGELOG.md @@ -5,6 +5,18 @@ > > 형식: [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/) +## [engine-v1.57.0] - 2026-08-06 + +### Added + +#### 버전 이력 목록 조회 상한 + '더 보기' + +- `useLayoutVersions.ts` — 버전 목록 조회에 `limit` 을 붙이고, 한 묶음(`VERSION_PAGE_SIZE` 100)이 가득 찼을 때만 '더 보기' 를 노출한다. 버전 행은 저장할 때마다 쌓이고 정리되지 않아, 상한이 없으면 오래 편집한 레이아웃 하나가 수백~수천 건을 한 응답에 담았다. +- 더 보기는 이어붙이기가 아니라 **상한을 한 묶음씩 넓혀 재조회**한다. 서버가 최신순 상한 조회만 지원하므로 append 하면 경계에서 중복·누락이 생긴다. +- 넓히기는 서버 상한(`VERSION_MAX_LIMIT` 500)에서 멈춘다. 클램프가 없으면 상한을 넘는 순간 목록 전체가 422 가 되어 **이미 보고 있던 이력까지 사라진다**. 상한에 도달하면 '더 보기' 를 감춘다. +- 두 상수는 서버 `LayoutVersionListRequest` 의 `DEFAULT_LIMIT`/`MAX_LIMIT` 과 같아야 하며, 그 일치는 `tests/Unit/LayoutVersionLimitParityTest.php` 가 검사한다. 한쪽만 바꾸면 테스트가 깨진다. +- `VersionHistoryModal.tsx` — '더 보기' 버튼(`g7le-version-history-load-more`) + 조회 중 비활성 처리. + ## [engine-v1.56.4] - 2026-08-04 ### Fixed diff --git a/resources/js/core/template-engine/layout-editor/__tests__/components/VersionHistoryModal.test.tsx b/resources/js/core/template-engine/layout-editor/__tests__/components/VersionHistoryModal.test.tsx index ff3a6378..e484cb2d 100644 --- a/resources/js/core/template-engine/layout-editor/__tests__/components/VersionHistoryModal.test.tsx +++ b/resources/js/core/template-engine/layout-editor/__tests__/components/VersionHistoryModal.test.tsx @@ -20,6 +20,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { render, screen, fireEvent, cleanup, waitFor } from '@testing-library/react'; import { VersionHistoryModal } from '../../components/VersionHistoryModal'; +import { VERSION_MAX_LIMIT } from '../../hooks/useLayoutVersions'; const t = (k: string, params?: Record) => params ? `${k}(${JSON.stringify(params)})` : k; @@ -131,7 +132,8 @@ describe('VersionHistoryModal — 목록/상태', () => { renderModal(); await screen.findByTestId('g7le-version-row-11'); const [url, init] = fetchFn.mock.calls[0]; - expect(url).toBe('/api/admin/templates/sirsoft-basic/layouts/auth/login/versions'); + // 조회 건수 상한(limit)이 붙지만 경로는 그대로 — layoutName 의 슬래시가 보존되어야 한다 + expect(url).toBe('/api/admin/templates/sirsoft-basic/layouts/auth/login/versions?limit=100'); expect((init?.headers as Record).Authorization).toBe('Bearer TESTTOKEN'); }); @@ -344,3 +346,97 @@ describe('VersionHistoryModal — 확장 타겟', () => { ).toBe(true); }); }); + +describe('VersionHistoryModal — 더 보기 (#518 / 공개 #76)', () => { + /** + * 버전 상한만큼 채워진 목록을 만든다. + * + * @param count 만들 건수 + * @param startId 시작 id + */ + function makeVersions(count: number, startId = 1000) { + return Array.from({ length: count }, (_, i) => ({ + id: startId + i, + layout_id: 1, + version: count - i, + changes_summary: { added_count: 1, removed_count: 0, char_diff: 5 }, + created_at: '2026-06-01T00:00:00+00:00', + created_by_name: null, + })); + } + + it('첫 조회에 기본 상한을 붙이고, 상한 미만이면 더 보기 버튼이 없다', async () => { + const fetchMock = mockFetch(() => listOk(sampleVersions)); + renderModal(); + await screen.findByTestId('g7le-version-row-11'); + + expect(String(fetchMock.mock.calls[0][0])).toContain('limit=100'); + expect(screen.queryByTestId('g7le-version-history-load-more')).toBeNull(); + }); + + it('응답이 상한을 채우면 더 보기 버튼이 나타난다', async () => { + mockFetch(() => listOk(makeVersions(100))); + renderModal(); + await screen.findByTestId('g7le-version-history-load-more'); + }); + + it('더 보기를 누르면 상한을 넓혀 다시 조회한다 (이어붙이기 아님)', async () => { + const fetchMock = mockFetch((url: string) => { + const limit = Number(new URL(url, 'https://example.test').searchParams.get('limit')); + + return listOk(makeVersions(Math.min(limit, 150))); + }); + + renderModal(); + const button = await screen.findByTestId('g7le-version-history-load-more'); + + fireEvent.click(button); + + // 상한을 한 묶음 넓혀 재조회 — 서버가 최신순 상한 조회만 지원하므로 append 하지 않는다 + await waitFor(() => { + expect(fetchMock.mock.calls.some((c) => String(c[0]).includes('limit=200'))).toBe(true); + }); + + // 150건(상한 미만)이 왔으므로 더 이상 남은 것이 없다고 판정 → 버튼 사라짐 + await waitFor(() => { + expect(screen.queryByTestId('g7le-version-history-load-more')).toBeNull(); + }); + }); + + it('서버 최대 상한을 넘겨 요청하지 않는다 (초과 시 422 로 목록이 통째로 비어 버린다)', async () => { + // 매번 요청한 만큼 꽉 채워 돌려준다 — "아직 남았다" 판정이 계속 참이라 + // 클램프가 없으면 더 보기를 누를 때마다 limit 이 무한히 커진다. + const fetchMock = mockFetch((url: string) => { + const limit = Number(new URL(url, 'https://example.test').searchParams.get('limit')); + + return listOk(makeVersions(limit)); + }); + + renderModal(); + + await screen.findByTestId('g7le-version-history-load-more'); + + // 상한(500)에 닿을 때까지 계속 누른다. 클램프가 있으면 버튼이 사라져 루프가 끝난다. + for (let attempt = 0; attempt < 10; attempt++) { + const button = screen.queryByTestId('g7le-version-history-load-more'); + if (!button) break; + + fireEvent.click(button); + // 조회 중에는 버튼이 disabled — 끝날 때까지 기다린 뒤 다음 클릭 + await waitFor(() => { + const pending = screen.queryByTestId('g7le-version-history-load-more'); + expect(pending === null || !(pending as HTMLButtonElement).disabled).toBe(true); + }); + } + + const requestedLimits = fetchMock.mock.calls + .map((c) => Number(new URL(String(c[0]), 'https://example.test').searchParams.get('limit'))) + .filter((n) => !Number.isNaN(n)); + + expect(Math.max(...requestedLimits)).toBe(VERSION_MAX_LIMIT); + expect(requestedLimits.every((n) => n <= VERSION_MAX_LIMIT)).toBe(true); + + // 상한에 도달하면 더 넓힐 수 없으므로 버튼은 사라진다 + expect(screen.queryByTestId('g7le-version-history-load-more')).toBeNull(); + }); +}); diff --git a/resources/js/core/template-engine/layout-editor/components/VersionHistoryModal.tsx b/resources/js/core/template-engine/layout-editor/components/VersionHistoryModal.tsx index e126f22e..7119e0b5 100644 --- a/resources/js/core/template-engine/layout-editor/components/VersionHistoryModal.tsx +++ b/resources/js/core/template-engine/layout-editor/components/VersionHistoryModal.tsx @@ -212,8 +212,18 @@ export function VersionHistoryModal({ onRestored, onClose, }: VersionHistoryModalProps): React.ReactElement { - const { versions, isLoading, error, restoringId, loadVersions, restore, loadVersionDetail } = - useLayoutVersions(templateIdentifier, target, onRestored); + const { + versions, + isLoading, + isLoadingMore, + error, + restoringId, + hasMore, + loadVersions, + loadMore, + restore, + loadVersionDetail, + } = useLayoutVersions(templateIdentifier, target, onRestored); const [actionError, setActionError] = useState(null); // diff 뷰 상태 — null 이면 목록 뷰. 값이 있으면 두 버전 content 의 Unified diff 표시. @@ -425,9 +435,42 @@ export function VersionHistoryModal({ ); })} + + {/* + 버전 행은 저장할 때마다 쌓이고 정리되지 않아 서버가 조회 건수를 제한한다. + 상한에 걸린 경우에만 버튼을 노출해, 오래된 버전도 계속 도달할 수 있게 한다. + */} + {hasMore && ( + + )} )} ); } + +/** '더 보기' 버튼 스타일 — 목록 하단 전체 폭 */ +const loadMoreButton: React.CSSProperties = { + width: '100%', + marginTop: 8, + padding: '8px 12px', + fontSize: 12, + color: '#475569', + background: '#f8fafc', + border: '1px solid #e2e8f0', + borderRadius: 6, + cursor: 'pointer', +}; diff --git a/resources/js/core/template-engine/layout-editor/hooks/useLayoutVersions.ts b/resources/js/core/template-engine/layout-editor/hooks/useLayoutVersions.ts index 3bfc51bc..46729809 100644 --- a/resources/js/core/template-engine/layout-editor/hooks/useLayoutVersions.ts +++ b/resources/js/core/template-engine/layout-editor/hooks/useLayoutVersions.ts @@ -100,6 +100,25 @@ export type VersionDetailResult = | { kind: 'not_found' } | { kind: 'network_error'; message: string }; +/** + * 버전 목록 한 묶음 크기 — 서버 기본 상한과 같다. + * + * 버전 행은 저장할 때마다 쌓이고 정리되지 않으므로 서버가 조회 건수를 제한한다. 화면은 + * 기본 한 묶음을 보여주고 '더 보기' 로 이전 것을 넓혀 간다. + */ +export const VERSION_PAGE_SIZE = 100; + +/** + * 한 번에 조회 가능한 최대 건수 — 서버 `LayoutVersionListRequest::MAX_LIMIT` 과 같아야 한다. + * + * '더 보기' 는 조회 상한을 한 묶음씩 넓히는 방식이라, 넓히기만 하고 멈추지 않으면 서버 상한을 + * 넘는 순간 목록 전체가 422 가 되어 이미 보고 있던 이력까지 사라진다. 이 값에 도달하면 더 + * 넓히지 않는다. + * + * 두 상수의 일치는 `tests/Unit/LayoutVersionLimitParityTest.php` 가 검사한다. + */ +export const VERSION_MAX_LIMIT = 500; + /** 복원 결과 — UI 분기용. */ export type RestoreResult = | { kind: 'success'; newVersion: number } @@ -115,8 +134,14 @@ export interface UseLayoutVersionsResult { error: string | null; /** 복원 진행 중인 버전 ID (null = 없음) */ restoringId: number | null; - /** 버전 목록 로드/재로드 */ + /** 버전 목록 로드/재로드 (표시 건수는 현재까지 펼친 만큼 유지) */ loadVersions: () => Promise; + /** 더 오래된 버전이 남아 있을 수 있는지 (마지막 응답이 요청 상한을 채웠는지) */ + hasMore: boolean; + /** 다음 묶음까지 펼쳐 다시 조회 */ + loadMore: () => Promise; + /** 추가 로드 진행 중 여부 */ + isLoadingMore: boolean; /** * 특정 버전 복원 — 성공 시 onRestored 콜백 호출(호출자가 reload 수행). * onRestored 에 복원으로 적재된 새 버전 번호가 전달된다. @@ -145,38 +170,81 @@ export function useLayoutVersions( ): UseLayoutVersionsResult { const [versions, setVersions] = useState([]); const [isLoading, setIsLoading] = useState(false); + const [isLoadingMore, setIsLoadingMore] = useState(false); const [error, setError] = useState(null); const [restoringId, setRestoringId] = useState(null); + /** 현재 요청 중인 표시 상한 — '더 보기' 를 누를 때마다 한 묶음씩 넓어진다 */ + const [limit, setLimit] = useState(VERSION_PAGE_SIZE); + const [hasMore, setHasMore] = useState(false); - const loadVersions = useCallback(async (): Promise => { - if (!target) { - setVersions([]); - setError(null); - return; - } - setIsLoading(true); - setError(null); - try { - const url = buildVersionsBaseUrl(templateIdentifier, target); - const response = await fetch(url, { - credentials: 'same-origin', - headers: buildAuthHeaders(), - }); - const body = await response.json().catch(() => null); - if (!response.ok) { - setError((body as { message?: string })?.message ?? `HTTP ${response.status}`); + /** + * 버전 목록을 지정한 상한으로 조회한다. + * + * 응답 건수가 요청 상한과 같으면 더 오래된 버전이 남아 있을 수 있다고 본다. 서버가 최신순 + * 상한 조회만 지원하므로(누적 페이지 토큰 없음), '더 보기' 는 상한을 넓혀 **다시 조회**한다 — + * 이어붙이기(append)를 하지 않으므로 중복/누락이 생길 여지가 없다. + */ + const fetchVersions = useCallback( + async (nextLimit: number, mode: 'reload' | 'more'): Promise => { + if (!target) { setVersions([]); + setError(null); + setHasMore(false); return; } - const list = (body as { data?: unknown })?.data; - setVersions(Array.isArray(list) ? (list as LayoutVersionSummary[]) : []); - } catch (err: unknown) { - setError(err instanceof Error ? err.message : 'network error'); - setVersions([]); - } finally { - setIsLoading(false); + if (mode === 'more') { + setIsLoadingMore(true); + } else { + setIsLoading(true); + } + setError(null); + try { + const url = `${buildVersionsBaseUrl(templateIdentifier, target)}?limit=${nextLimit}`; + const response = await fetch(url, { + credentials: 'same-origin', + headers: buildAuthHeaders(), + }); + const body = await response.json().catch(() => null); + if (!response.ok) { + setError((body as { message?: string })?.message ?? `HTTP ${response.status}`); + if (mode === 'reload') setVersions([]); + return; + } + const list = (body as { data?: unknown })?.data; + const rows = Array.isArray(list) ? (list as LayoutVersionSummary[]) : []; + setVersions(rows); + // 서버 상한에 도달했으면 더 넓힐 수 없으므로 '더 보기' 를 감춘다 — 남은 이력이 있어도 + // 그 이상은 한 응답으로 받을 수 없다. + setHasMore(rows.length >= nextLimit && nextLimit < VERSION_MAX_LIMIT); + setLimit(nextLimit); + } catch (err: unknown) { + setError(err instanceof Error ? err.message : 'network error'); + if (mode === 'reload') setVersions([]); + } finally { + if (mode === 'more') { + setIsLoadingMore(false); + } else { + setIsLoading(false); + } + } + }, + [templateIdentifier, target], + ); + + const loadVersions = useCallback(async (): Promise => { + await fetchVersions(limit, 'reload'); + }, [fetchVersions, limit]); + + const loadMore = useCallback(async (): Promise => { + // 서버 상한을 넘는 limit 은 422 라 목록이 통째로 비어 버린다. 상한에서 멈춘다. + const nextLimit = Math.min(limit + VERSION_PAGE_SIZE, VERSION_MAX_LIMIT); + if (nextLimit <= limit) { + setHasMore(false); + + return; } - }, [templateIdentifier, target]); + await fetchVersions(nextLimit, 'more'); + }, [fetchVersions, limit]); const restore = useCallback( async (versionId: number): Promise => { @@ -260,5 +328,16 @@ export function useLayoutVersions( [templateIdentifier, target], ); - return { versions, isLoading, error, restoringId, loadVersions, restore, loadVersionDetail }; + return { + versions, + isLoading, + isLoadingMore, + error, + restoringId, + hasMore, + loadVersions, + loadMore, + restore, + loadVersionDetail, + }; } diff --git a/templates/_bundled/sirsoft-admin_basic/CHANGELOG.md b/templates/_bundled/sirsoft-admin_basic/CHANGELOG.md index 8a056af1..e4cb77a2 100644 --- a/templates/_bundled/sirsoft-admin_basic/CHANGELOG.md +++ b/templates/_bundled/sirsoft-admin_basic/CHANGELOG.md @@ -8,6 +8,7 @@ ### Added +- 역할 관리 목록에 「권한 수」 열이 추가되었습니다. 목록에서 권한 목록 전체를 받아오지 않고도 역할별 권한 규모를 확인할 수 있습니다. (#76 @jordy-bitree 님께서 제보해주셨습니다.) - 회원 상세에 계정 잠금 상태 표시와 잠금 해제 버튼을 추가했습니다. 로그인 실패가 누적돼 잠긴 계정, 특히 무기한 잠긴 계정을 관리자가 이 화면에서 바로 풀 수 있습니다. (#81 @jiwonpapa 님께서 제보해주셨습니다.) - 환경설정 > 보안에 비밀번호 최소 길이·특수문자 필수 항목을 추가하고, 로그인 시도 제한 항목에 IP 단위 제한 계산 방식 안내를 덧붙였습니다. (#81 @jiwonpapa 님께서 제보해주셨습니다.) - 일반 설정에 에셋 파일 서빙 방식 항목을 추가했습니다. 정적 파일 최적화 규칙(nginx `location ~* \.(js|css|json|...)$` 블록 등)이 확장자 주소를 가로채 화면이 뜨지 않을 때 확장자 없는 주소로 전환할 수 있습니다. "자동 감지" 버튼을 누르면 판정 결과가 항목 아래에 인라인으로 표시되며, 확장자를 가로채는 환경이면 원인이 되는 서버 규칙까지 안내합니다. 평소에는 자동으로 선택되므로 화면이 정상이면 바꿀 필요가 없습니다. @@ -26,6 +27,7 @@ ### Changed +- 코어 최소 요구 버전을 7.0.6 로 상향했습니다. 역할 관리 목록의 「권한 수」 열과 메뉴 관리의 하위 메뉴 역할 표시가 7.0.6 에서 추가된 응답 항목을 사용하므로, 이전 버전에서는 값이 비어 보입니다. - SEO 설정의 "지금 생성" 버튼이 sitemap 생성을 예약하고 바로 응답합니다. 사이트 규모가 커도 화면이 멈추지 않으며, 생성 중에는 버튼이 비활성화되고 진행 상황이 화면에 표시됩니다. ### Fixed diff --git a/templates/_bundled/sirsoft-admin_basic/__tests__/layouts/admin-menu-list-child-roles.test.tsx b/templates/_bundled/sirsoft-admin_basic/__tests__/layouts/admin-menu-list-child-roles.test.tsx new file mode 100644 index 00000000..08eeb9da --- /dev/null +++ b/templates/_bundled/sirsoft-admin_basic/__tests__/layouts/admin-menu-list-child-roles.test.tsx @@ -0,0 +1,241 @@ +/** + * @file admin-menu-list-child-roles.test.tsx + * @description 메뉴 관리 — 하위 메뉴 선택 시 역할 보강 회귀 테스트 + * + * 회귀 배경 (#518 / 공개 #76): + * 목록 응답에서 `children[].roles` 를 프루닝한 뒤, 화면이 목록 행을 그대로 `_global.selectedMenu` + * 로 쓰면 하위 메뉴의 역할 배지가 "모든 역할" 로 표시되고, 편집 폼이 빈 역할 배열을 저장에 실어 + * 보내 그 메뉴의 역할 제한이 전부 해제된다. + * + * 이 테스트는 **실제 파셜 JSON** 을 읽어 onSelect 액션 정의를 그대로 실행한다. + * + * @scenario resource=menu,endpoint=list,observation=consumer_screen + * @scenario resource=menu,endpoint=detail,observation=consumer_screen + * @effects menu_selection_hydrates_roles_from_detail + */ + +import React from 'react'; +import fs from 'fs'; +import path from 'path'; +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import { createLayoutTest, screen, fireEvent, waitFor } from '@core/template-engine/__tests__/utils/layoutTestUtils'; +import { ComponentRegistry } from '@core/template-engine/ComponentRegistry'; + +const PARTIAL_PATH = path.resolve( + __dirname, + '../../layouts/partials/admin_menu_list/_panel_menu_list.json', +); + +/** 목록 응답 — 하위 메뉴에는 roles 키가 없다 (#76 프루닝) */ +const MENU_ROWS = [ + { + id: 10, + name: { ko: '부모 메뉴' }, + slug: 'parent-menu', + url: '/parent', + icon: 'fa-folder', + order: 1, + is_active: true, + parent_id: null, + roles: [{ id: 1, name: { ko: '관리자' }, permission_type: 'read' }], + children: [ + { + id: 11, + name: { ko: '자식 메뉴' }, + slug: 'child-menu', + url: '/parent/child', + icon: 'fa-file', + order: 1, + is_active: true, + parent_id: 10, + }, + ], + }, +]; + +const CHILD_DETAIL = { + id: 11, + name: { ko: '자식 메뉴' }, + slug: 'child-menu', + url: '/parent/child', + icon: 'fa-file', + order: 1, + is_active: true, + parent_id: 10, + roles: [ + { id: 3, name: { ko: '편집자' }, permission_type: 'read' }, + { id: 5, name: { ko: '검수자' }, permission_type: 'read' }, + ], +}; + +/** 트리를 평탄화해 각 행마다 onSelect 를 쏘는 버튼을 만든다 (실제 목록의 선택 가능 행과 동일). */ +const TestSortableMenuList: React.FC<{ + items?: any[]; + onSelect?: (row: any) => void; +}> = ({ items, onSelect }) => { + const flat: any[] = []; + const walk = (rows: any[]) => { + for (const row of rows ?? []) { + flat.push(row); + if (row.children?.length) { + walk(row.children); + } + } + }; + walk(items ?? []); + + return ( +
+ {flat.map((row) => ( + + ))} +
+ ); +}; + +const TestDiv: React.FC<{ className?: string; children?: React.ReactNode }> = ({ className, children }) => ( +
{children}
+); + +const TestButton: React.FC<{ children?: React.ReactNode; text?: string; onClick?: () => void }> = ({ + children, + text, + onClick, +}) => ; + +const TestSpan: React.FC<{ children?: React.ReactNode; text?: string }> = ({ children, text }) => ( + {children || text} +); + +const TestIcon: React.FC<{ name?: string; className?: string }> = ({ name, className }) => ( + +); + +function setupTestRegistry(): void { + const registry = ComponentRegistry.getInstance(); + + (registry as any).registry = { + Div: { component: TestDiv, metadata: { name: 'Div', type: 'basic' } }, + Button: { component: TestButton, metadata: { name: 'Button', type: 'basic' } }, + Span: { component: TestSpan, metadata: { name: 'Span', type: 'basic' } }, + H3: { component: TestSpan, metadata: { name: 'H3', type: 'basic' } }, + Icon: { component: TestIcon, metadata: { name: 'Icon', type: 'basic' } }, + SortableMenuList: { + component: TestSortableMenuList, + metadata: { name: 'SortableMenuList', type: 'composite' }, + }, + // 렌더 루트가 Fragment 다 — 빠뜨리면 아무것도 그려지지 않는다. + Fragment: { + component: ({ children }: { children?: React.ReactNode }) => <>{children}, + metadata: { name: 'Fragment', type: 'layout' }, + }, + }; +} + +/** 실제 파셜 JSON 을 읽어 렌더 가능한 레이아웃으로 감싼다. */ +function buildLayoutFromPartial(): any { + const partial = JSON.parse(fs.readFileSync(PARTIAL_PATH, 'utf-8')); + + // 파셜 노드의 메타 키(layout_name)와 템플릿 전용 lifecycle 핸들러(initMenuFromUrl)는 + // 단독 렌더에서 해석 대상이 아니므로 걷어낸다. 검증 대상인 actions 는 그대로 둔다. + delete partial.layout_name; + delete partial.lifecycle; + + return { + version: '1.0.0', + layout_name: 'test_admin_menu_list_child_roles', + data_sources: [ + { + id: 'menus', + type: 'api', + endpoint: '/api/admin/menus', + method: 'GET', + auth_required: true, + params: { with_children: true, hierarchical: true }, + fallback: { data: [] }, + }, + ], + state: { selectedMenuId: null, selectedMenu: null, panelMode: 'view' }, + // 파셜은 단일 컴포넌트 노드다 (components 배열이 아니다). + components: [partial], + }; +} + +describe('메뉴 관리 — 하위 메뉴 선택 시 역할 보강 (#76)', () => { + let testUtils: ReturnType; + + beforeEach(() => { + setupTestRegistry(); + }); + + afterEach(() => { + testUtils?.cleanup(); + }); + + it('실제 파셜의 onSelect 가 단건 조회를 호출한다', async () => { + testUtils = createLayoutTest(buildLayoutFromPartial()); + testUtils.mockApi('menus', { + response: { success: true, data: { data: MENU_ROWS, abilities: { can_update: true } } }, + }); + testUtils.mockApi('/api/admin/menus/11', { response: { success: true, data: CHILD_DETAIL } }); + + await testUtils.render(); + + // 존재를 먼저 확정한 뒤 동작을 단언한다. + const childButton = await screen.findByTestId('select-child-menu'); + expect(childButton).toBeInTheDocument(); + + fireEvent.click(childButton); + + await waitFor(() => { + const calls = (globalThis.fetch as any).mock.calls.map((call: any[]) => String(call[0])); + expect(calls.some((url: string) => url.includes('/api/admin/menus/11'))).toBe(true); + }); + }); + + it('상위 메뉴 선택도 같은 경로로 단건을 조회한다 (경로가 하위 전용이면 URL 직접 진입에서 다시 갈린다)', async () => { + testUtils = createLayoutTest(buildLayoutFromPartial()); + testUtils.mockApi('menus', { + response: { success: true, data: { data: MENU_ROWS, abilities: { can_update: true } } }, + }); + testUtils.mockApi('/api/admin/menus/10', { response: { success: true, data: MENU_ROWS[0] } }); + + await testUtils.render(); + + const parentButton = await screen.findByTestId('select-parent-menu'); + expect(parentButton).toBeInTheDocument(); + + fireEvent.click(parentButton); + + await waitFor(() => { + const calls = (globalThis.fetch as any).mock.calls.map((call: any[]) => String(call[0])); + expect(calls.some((url: string) => url.includes('/api/admin/menus/10'))).toBe(true); + }); + }); + + it('단건 조회 응답이 selectedMenu 로 반영되도록 onSuccess 가 배선되어 있다', () => { + const partial = JSON.parse(fs.readFileSync(PARTIAL_PATH, 'utf-8')); + const menuList = partial.children.find((child: any) => child.id === 'sortable_menu_list'); + const onSelect = menuList.actions.find((action: any) => action.event === 'onSelect'); + const detailCall = onSelect.actions.find( + (action: any) => action.handler === 'apiCall' && String(action.target).includes('/api/admin/menus/'), + ); + + // 존재를 먼저 확정한 뒤 배선을 단언한다. + expect(detailCall).toBeDefined(); + expect(detailCall.onSuccess).toBeDefined(); + + const write = detailCall.onSuccess.find((action: any) => action.handler === 'setState'); + expect(write).toBeDefined(); + expect(write.params.target).toBe('global'); + expect(write.params.selectedMenu).toBe('{{response.data}}'); + }); + + it('목록 응답의 하위 메뉴 행에는 roles 키가 없다 (프루닝 전제 고정)', () => { + const child = MENU_ROWS[0].children[0]; + + expect(child).not.toHaveProperty('roles'); + }); +}); diff --git a/templates/_bundled/sirsoft-admin_basic/__tests__/layouts/admin-role-list-permission-pruning.test.tsx b/templates/_bundled/sirsoft-admin_basic/__tests__/layouts/admin-role-list-permission-pruning.test.tsx new file mode 100644 index 00000000..e54449c9 --- /dev/null +++ b/templates/_bundled/sirsoft-admin_basic/__tests__/layouts/admin-role-list-permission-pruning.test.tsx @@ -0,0 +1,288 @@ +/** + * @file admin-role-list-permission-pruning.test.tsx + * @description 역할 관리 목록 — 권한 배열 프루닝 후 화면 계약 회귀 테스트 + * + * 회귀 배경 (#518 / 공개 #76): + * 역할 목록 응답이 행마다 `permissions` 트리와 `permission_ids`/`permission_values` 를 3중으로 + * 싣고, Repository 가 `with('permissions')` 로 무조건 로드해 행당 `Permission::whereIn()` 이 + * 두 번씩 나갔다. 목록 화면은 이 3필드를 전혀 그리지 않으므로 목록에서 뺐고, 대신 규모를 + * 보여줄 `permissions_count` 집계를 새로 노출했다. + * + * 이 테스트가 잠그는 것은 두 방향이다. + * + * 1. **소비** — 목록 레이아웃이 뺀 3필드를 어디에서도 참조하지 않는다. 참조가 하나라도 + * 남으면 그 자리가 조용히 빈다(예외도 콘솔 경고도 없다). + * 2. **공급** — 그 대신 들어온 `permissions_count` 를 화면이 실제로 그린다. 서버가 집계를 + * 넣어도 화면에 열이 없으면 운영자에게는 "정보가 사라진" 것과 같다. + * + * 한쪽만 보면 어느 방향으로 되돌아가도 통과한다 — 그래서 같은 파일에서 함께 고정한다. + * + * @scenario resource=role,endpoint=list,observation=consumer_screen + * @effects role_list_omits_permission_payload + * @effects role_list_renders_permission_count + */ + +import React from 'react'; +import fs from 'fs'; +import path from 'path'; +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import { createLayoutTest, screen } from '@core/template-engine/__tests__/utils/layoutTestUtils'; +import { ComponentRegistry } from '@core/template-engine/ComponentRegistry'; + +const LAYOUT_PATH = path.resolve(__dirname, '../../layouts/admin_role_list.json'); + +/** 목록에서 제거된 필드 — 행 객체에서 이 이름들을 읽으면 회귀다 */ +const PRUNED_ROW_FIELDS = ['permissions', 'permission_ids', 'permission_values']; + +/** + * 레이아웃 JSON 전체에서 `{{ }}` 바인딩 문자열을 모두 수집합니다. + * + * 컬럼 정의·조건·props·텍스트 어디에 있든 걸리도록 값 전체를 훑는다. 키 이름이 아니라 + * **표현식** 만 모으는 것이 핵심 — 레이아웃 최상위의 `permissions`(화면 접근 권한 선언)는 + * 행 필드와 무관하므로 이름만 보면 오탐한다. + * + * @param node 탐색할 JSON 노드 + * @param acc 수집 결과 누적 배열 + * @returns 수집된 바인딩 표현식 목록 + */ +function collectBindings(node: unknown, acc: string[] = []): string[] { + if (typeof node === 'string') { + const matches = node.match(/\{\{[^}]*\}\}/g); + + if (matches) { + acc.push(...matches); + } + + return acc; + } + + if (Array.isArray(node)) { + node.forEach((child) => collectBindings(child, acc)); + + return acc; + } + + if (node && typeof node === 'object') { + Object.values(node as Record).forEach((child) => + collectBindings(child, acc), + ); + } + + return acc; +} + +/** + * 데이터그리드 컬럼 정의를 전부 모읍니다 (중첩 파셜/슬롯 무관). + * + * @param node 탐색할 JSON 노드 + * @param acc 수집 결과 누적 배열 + * @returns `field` 키를 가진 컬럼 객체 목록 + */ +function collectColumns(node: unknown, acc: Record[] = []): Record[] { + if (Array.isArray(node)) { + node.forEach((child) => collectColumns(child, acc)); + + return acc; + } + + if (node && typeof node === 'object') { + const obj = node as Record; + + if (typeof obj.field === 'string' && 'header' in obj) { + acc.push(obj); + } + + Object.values(obj).forEach((child) => collectColumns(child, acc)); + } + + return acc; +} + +const TestDiv: React.FC<{ className?: string; children?: React.ReactNode }> = ({ + className, + children, +}) =>
{children}
; + +const TestSpan: React.FC<{ className?: string; children?: React.ReactNode; text?: string }> = ({ + className, + children, + text, +}) => ( + + {/* + `||` 로 폴백하면 숫자 0 이 빈칸으로 접혀, 권한 0건 역할이 "빈칸" 으로 보이는지 + "0" 으로 보이는지를 이 테스트가 구분하지 못하게 된다. 널 병합으로 받는다. + */} + {(children ?? text) as React.ReactNode} + +); + +const TestIcon: React.FC<{ name?: string; size?: string }> = ({ name, size }) => ( + +); + +const TestFragment: React.FC<{ children?: React.ReactNode }> = ({ children }) => <>{children}; + +/** + * 테스트용 컴포넌트 레지스트리를 구성합니다. + * + * `Fragment` 는 반드시 등록해야 한다 — 테스트 하니스가 슬롯 전체를 `Fragment` 루트로 감싸 + * 렌더하므로, 빠지면 예외 없이 화면 전체가 비어 모든 단언이 "요소를 찾을 수 없음" 으로 깨진다. + * + * @returns 구성된 ComponentRegistry 인스턴스 + */ +function setupTestRegistry(): ComponentRegistry { + const registry = ComponentRegistry.getInstance(); + + (registry as any).registry = { + Div: { component: TestDiv, metadata: { name: 'Div', type: 'basic' } }, + Span: { component: TestSpan, metadata: { name: 'Span', type: 'basic' } }, + Icon: { component: TestIcon, metadata: { name: 'Icon', type: 'basic' } }, + Fragment: { component: TestFragment, metadata: { name: 'Fragment', type: 'layout' } }, + }; + + return registry; +} + +describe('역할 관리 목록 — 권한 배열 프루닝 (#518 / 공개 #76)', () => { + let layout: Record; + + beforeEach(() => { + layout = JSON.parse(fs.readFileSync(LAYOUT_PATH, 'utf-8')); + }); + + describe('소비 — 목록에서 뺀 필드를 화면이 읽지 않는다', () => { + it('행 바인딩이 permissions / permission_ids / permission_values 를 참조하지 않는다', () => { + const bindings = collectBindings(layout); + + // 모집단 확인이 먼저다. 수집이 0건이면 아래 부재 단언은 아무것도 증명하지 못한 채 + // 통과한다 (레이아웃 구조가 바뀌어 수집기가 헛돌아도 초록이 된다). + expect(bindings.length, '레이아웃에서 바인딩 표현식이 수집되어야 한다').toBeGreaterThan( + 20, + ); + + const offenders = bindings.filter((binding) => + PRUNED_ROW_FIELDS.some((field) => + new RegExp(`\\brow\\.${field}\\b`).test(binding), + ), + ); + + expect( + offenders, + '목록 응답에서 제거된 권한 필드를 화면이 참조하면 그 자리가 조용히 빈다', + ).toEqual([]); + }); + + it('제거된 필드를 컬럼 field 로 쓰지 않는다', () => { + const columns = collectColumns(layout); + + expect(columns.length, '데이터그리드 컬럼이 수집되어야 한다').toBeGreaterThan(0); + + const offenders = columns + .map((column) => column.field) + .filter((field) => PRUNED_ROW_FIELDS.includes(field)); + + expect(offenders).toEqual([]); + }); + }); + + describe('공급 — 대체 지표가 화면에 남아 있다', () => { + it('permissions_count 열이 존재하고 집계 필드에 바인딩된다', () => { + const columns = collectColumns(layout); + const column = columns.find((candidate) => candidate.field === 'permissions_count'); + + expect(column, '권한 수 열이 있어야 한다 — 목록에서 권한 배열을 뺀 대체 지표다').toBeDefined(); + + const bindings = collectBindings(column); + + expect( + bindings.some((binding) => /\brow\.permissions_count\b/.test(binding)), + '권한 수 열이 서버 집계(permissions_count)를 읽어야 한다', + ).toBe(true); + }); + + it('권한 수 헤더가 다국어 키로 선언된다 (하드코딩 금지)', () => { + const columns = collectColumns(layout); + const column = columns.find((candidate) => candidate.field === 'permissions_count'); + + expect(column?.header).toBe('$t:admin.roles.columns.permissions_count'); + }); + }); + + describe('렌더 — 0건 역할이 빈칸이 아니라 0 으로 보인다', () => { + let registry: ComponentRegistry; + let testUtils: ReturnType; + + beforeEach(() => { + registry = setupTestRegistry(); + }); + + afterEach(() => { + testUtils?.cleanup(); + (registry as any).registry = {}; + }); + + /** + * 권한 수 셀만 떼어낸 최소 레이아웃을 만듭니다. + * + * 실제 레이아웃의 `cellChildren` 을 그대로 가져다 쓰되, 행 변수만 재바인딩한다. + * `row` 는 데이터그리드 반복 변수라 그리드 밖에서는 해석되지 않으므로 `_global.row` + * 로 옮긴다. 셀 정의를 손으로 옮겨 적으면 원본이 바뀌어도 이 테스트는 계속 통과하므로, + * 컨텍스트 접두사만 바꾸고 구조·표현식은 원본 그대로 둔다. + * + * @param row 렌더할 행 객체 + * @returns 렌더 가능한 레이아웃 정의 + */ + function createCellLayout(row: Record) { + const columns = collectColumns(JSON.parse(fs.readFileSync(LAYOUT_PATH, 'utf-8'))); + const column = columns.find((candidate) => candidate.field === 'permissions_count'); + + const rebased = JSON.parse( + JSON.stringify(column!.cellChildren).replace(/\{\{([^}]*)\}\}/g, (whole, expr) => + `{{${expr.replace(/\brow\./g, '_global.row.')}}}`, + ), + ); + + return { + version: '1.0.0', + layout_name: 'admin_role_list_permission_count_cell_test', + initGlobal: { row }, + slots: { + content: rebased, + }, + }; + } + + it('권한 0건 역할은 0 으로 렌더된다 (집계 0 과 미집계를 화면이 구분한다)', async () => { + testUtils = createLayoutTest( + createCellLayout({ id: 1, permissions_count: 0 }) as any, + { componentRegistry: registry }, + ); + + await testUtils.render(); + + expect(screen.getByTestId('permission-count-cell')).toHaveTextContent('0'); + }); + + it('권한이 있는 역할은 그 수가 렌더된다', async () => { + testUtils = createLayoutTest( + createCellLayout({ id: 2, permissions_count: 51 }) as any, + { componentRegistry: registry }, + ); + + await testUtils.render(); + + expect(screen.getByTestId('permission-count-cell')).toHaveTextContent('51'); + }); + + it('집계 키가 없어도 빈칸 대신 0 으로 떨어진다 (?? 0 폴백)', async () => { + testUtils = createLayoutTest(createCellLayout({ id: 3 }) as any, { + componentRegistry: registry, + }); + + await testUtils.render(); + + expect(screen.getByTestId('permission-count-cell')).toHaveTextContent('0'); + }); + }); +}); diff --git a/templates/_bundled/sirsoft-admin_basic/__tests__/layouts/admin-settings-definition-template-pruning.test.tsx b/templates/_bundled/sirsoft-admin_basic/__tests__/layouts/admin-settings-definition-template-pruning.test.tsx new file mode 100644 index 00000000..3119c9bd --- /dev/null +++ b/templates/_bundled/sirsoft-admin_basic/__tests__/layouts/admin-settings-definition-template-pruning.test.tsx @@ -0,0 +1,271 @@ +/** + * @file admin-settings-definition-template-pruning.test.tsx + * @description 알림/본인인증 정의 목록 — 템플릿 프루닝 후 화면 계약 회귀 테스트 + * + * 회귀 배경 (#518 / 공개 #76): + * 두 정의 목록이 행마다 채널×로케일 템플릿 본문을 전량 싣고 있었다. 계획서는 목록에서 + * `templates` 를 빼고 `withCount` 로 대체하라고 했으나, 소비처를 실측하니 두 화면 모두 템플릿 + * 본문을 실제로 렌더하고 있었다 — 통째로 빼면 기능이 사라진다. 그래서 방식을 바꿨다. + * + * - 알림 정의: 행은 그대로 두고 **각 행의 템플릿을 한 채널로 좁혀** 싣는다(`template_channel`). + * - 본인인증 정의: 화면이 `templates[0]` 만 읽으므로 **대표 1건**만 싣는다. + * + * 이 방식은 응답을 줄이는 대신 **새로운 방식의 회귀 가능성**을 만든다. + * + * 1. 서버가 좁혀 보낸 채널과 화면이 찾는 채널이 어긋나면 → 템플릿이 "없음" 으로 보인다. + * 예외도 콘솔 경고도 없다. 두 표현식이 각각 옳아도 **서로 다르면** 깨진다. + * 2. 채널을 가리지 않는 판정(예전의 `templates.some(t => !t.is_default)`)이 남아 있으면 → + * 한 채널만 실린 배열을 전수라고 오판해, 다른 채널만 수정한 정의를 놓친다. + * 3. 본인인증 쪽이 `templates[1]` 이상을 읽으면 → 대표 1건만 오는 목록에서 영영 비어 있다. + * + * 셋 다 응답만 보는 백엔드 테스트로는 잡히지 않는다 — 화면이 무엇을 찾는지가 조건이기 때문이다. + * + * 마킹은 아래 describe 블록마다 따로 붙인다 — 한 docblock 에서는 @scenario/@effects 가 + * 각각 첫 줄만 읽히므로, 여기에 두 리소스를 나열하면 뒤엣것이 조용히 누락된다. + */ + +import fs from 'fs'; +import path from 'path'; +import { describe, it, expect } from 'vitest'; + +const SETTINGS_LAYOUT = path.resolve(__dirname, '../../layouts/admin_settings.json'); +const NOTIFICATION_TAB = path.resolve( + __dirname, + '../../layouts/partials/admin_settings/_tab_notification_definitions.json', +); +const IDENTITY_TAB = path.resolve( + __dirname, + '../../layouts/partials/admin_settings/_tab_identity_messages.json', +); + +/** + * JSON 트리에서 `{{ }}` 바인딩 표현식을 모두 수집합니다. + * + * @param node 탐색할 JSON 노드 + * @param acc 수집 결과 누적 배열 + * @returns 수집된 바인딩 표현식 목록 + */ +function collectBindings(node: unknown, acc: string[] = []): string[] { + if (typeof node === 'string') { + const matches = node.match(/\{\{[\s\S]*?\}\}/g); + + if (matches) { + acc.push(...matches); + } + + return acc; + } + + if (Array.isArray(node)) { + node.forEach((child) => collectBindings(child, acc)); + + return acc; + } + + if (node && typeof node === 'object') { + Object.values(node as Record).forEach((child) => + collectBindings(child, acc), + ); + } + + return acc; +} + +/** + * 공백을 제거해 표현식을 비교 가능한 형태로 만듭니다. + * + * 채널 표현식은 데이터소스와 소비자 양쪽에 손으로 적혀 있어 들여쓰기·공백이 다를 수 있다. + * 의미가 같은지만 보면 되므로 공백을 지우고 비교한다. + * + * @param expression 정규화할 표현식 + * @returns 공백이 제거된 표현식 + */ +function normalize(expression: string): string { + return expression.replace(/\s+/g, ''); +} + +/** + * 레이아웃 파일을 읽어 파싱합니다. + * + * @param filePath 레이아웃 파일 절대 경로 + * @returns 파싱된 JSON 객체 + */ +function readLayout(filePath: string): Record { + return JSON.parse(fs.readFileSync(filePath, 'utf-8')); +} + +/** + * 표현식에서 `{{ }}` 와 겉을 감싼 괄호를 벗겨 알맹이만 남깁니다. + * + * 데이터소스는 `{{...}}` 로, 소비자 표현식은 `(...)` 로 감싸 쓰므로 표기가 다르다. + * 비교해야 하는 것은 표기가 아니라 채널을 고르는 식 자체다. + * + * @param expression 벗겨낼 표현식 + * @returns 공백·래핑이 제거된 표현식 + */ +function unwrap(expression: string): string { + let inner = normalize(expression).replace(/^\{\{/, '').replace(/\}\}$/, ''); + + while (inner.startsWith('(') && inner.endsWith(')')) { + inner = inner.slice(1, -1); + } + + return inner; +} + +/** + * 소비자 표현식에서 채널 선택식을 뽑아냅니다. + * + * `t.channel === (...)` 의 괄호 안이 화면이 "이 채널의 템플릿" 이라고 판단하는 기준이다. + * 상수로 손으로 적어두면 화면 쪽이 바뀌어도 테스트가 계속 통과하므로 실제 값에서 뽑는다. + * + * @param bindings 수집된 바인딩 표현식 목록 + * @returns 채널 선택식 (없으면 null) + */ +function extractConsumerChannel(bindings: string[]): string | null { + for (const binding of bindings) { + const match = normalize(binding).match(/t\.channel===(\([^)]*\)|[^)]+?)\)/); + + if (match) { + return unwrap(match[1]); + } + } + + return null; +} + +/** + * @scenario resource=notification_definition,endpoint=list,observation=consumer_screen + * @effects definition_list_channel_scope_matches_consumer, definition_list_cross_channel_judgment_uses_aggregate + */ +describe('알림 정의 목록 — 채널 스코프 (#518 / 공개 #76)', () => { + it('데이터소스가 요청하는 채널과 화면이 찾는 채널이 같다', () => { + const layout = readLayout(SETTINGS_LAYOUT); + + const source = (layout.data_sources ?? []).find((ds: any) => + String(ds.endpoint ?? '').includes('/admin/notification-definitions'), + ); + + expect(source, '알림 정의 데이터소스가 있어야 한다').toBeDefined(); + + const requested = source.params?.template_channel; + + expect( + requested, + '목록이 한 채널만 싣도록 template_channel 을 보내야 한다 (안 보내면 templates 키 자체가 없다)', + ).toBeDefined(); + + const consumerChannel = extractConsumerChannel(collectBindings(readLayout(NOTIFICATION_TAB))); + + // 추출이 실패해 양쪽이 나란히 빈 문자열이면 아래 동치 단언은 통과해 버린다. + // 뽑아낸 값이 실제 채널식인지부터 확정한다. + expect( + consumerChannel, + '화면이 템플릿을 고를 때 쓰는 채널 표현식을 찾아야 한다', + ).toContain('query.channel'); + + // 서버가 좁혀 보낸 채널과 화면이 찾는 채널이 다르면, 각 표현식이 따로따로는 옳아도 + // 템플릿이 "없음" 으로 보인다. 그래서 두 실제 값을 직접 대조한다. + expect(unwrap(String(requested))).toBe(consumerChannel); + }); + + it('채널 표현식이 URL 단일 출처다 (_local 을 섞지 않는다)', () => { + // 회귀 배경 (#518 3차 실측 결함 #1): + // 종전 표현식은 `_local.activeNotificationChannel ?? query.channel ?? 'mail'` 이었다. + // 데이터소스와 소비자에 **글자까지 같은** 식이 적혀 있었으므로 위의 동치 단언은 통과했지만, + // 두 출처는 갱신 시점이 다르다. 다른 설정 탭에 갔다 돌아오면 URL 의 channel 은 사라지고 + // (탭 전환은 목록 상태를 승계하지 않는다) `_local` 만 이전 채널로 남는다. 그 사이 목록은 + // 기본 채널로 다시 불려 오고, 화면은 남아 있는 이전 채널로 템플릿을 찾는다 — + // 행마다 「이 채널에 대한 템플릿이 없습니다」 가 뜨고 예외도 콘솔 경고도 남지 않는다. + // + // 두 표현식이 같은지가 아니라, **출처가 하나인지**가 조건이다. + const bindings = [ + ...collectBindings(readLayout(SETTINGS_LAYOUT)), + ...collectBindings(readLayout(NOTIFICATION_TAB)), + ]; + + const channelReads = bindings.filter((binding) => /query\.channel/.test(binding)); + + expect( + channelReads.length, + '채널을 읽는 표현식이 있어야 한다 (0건이면 이 단언은 공회전한다)', + ).toBeGreaterThan(0); + + const dualSourced = channelReads.filter((binding) => /_local\./.test(binding)); + + expect( + dualSourced, + 'URL 이 나르지 않는 값(_local)을 채널 출처로 섞으면 데이터와 화면이 서로 다른 채널을 본다', + ).toEqual([]); + }); + + it('템플릿을 고르는 모든 표현식이 채널로 좁혀져 있다', () => { + const bindings = collectBindings(readLayout(NOTIFICATION_TAB)); + + expect(bindings.length, '바인딩이 수집되어야 한다').toBeGreaterThan(20); + + const templateReads = bindings.filter((binding) => /\bdef\.templates\b/.test(binding)); + + // 모집단 확인 — 템플릿을 읽는 표현식이 0건이면 아래 단언은 공회전한다. + expect( + templateReads.length, + '화면이 템플릿을 실제로 읽고 있어야 한다 (읽지 않는다면 프루닝 방식 자체를 재검토해야 한다)', + ).toBeGreaterThan(0); + + const unscoped = templateReads.filter( + (binding) => !/t\.channel\s*===/.test(binding), + ); + + expect( + unscoped, + '채널로 좁히지 않고 templates 를 읽으면, 한 채널만 실린 배열을 전수로 오판한다', + ).toEqual([]); + }); + + it('채널을 가리지 않는 판정은 서버 집계를 쓴다', () => { + const bindings = collectBindings(readLayout(NOTIFICATION_TAB)); + + expect( + bindings.some((binding) => /\bdef\.has_customized_templates\b/.test(binding)), + '"수정된 템플릿이 있는가" 는 전 채널 기준이라 집계로만 판정할 수 있다', + ).toBe(true); + + // 예전 구현. 한 채널만 실린 배열에 쓰면 다른 채널만 수정한 정의를 놓친다. + const legacy = bindings.filter((binding) => + /def\.templates[\s\S]*\.some\s*\(/.test(binding), + ); + + expect(legacy, '전 채널 판정을 배열 순회로 되돌리면 안 된다').toEqual([]); + }); +}); + +/** + * @scenario resource=identity_message_definition,endpoint=list,observation=consumer_screen + * @effects identity_definition_list_uses_representative_template + */ +describe('본인인증 메시지 정의 목록 — 대표 템플릿 (#518 / 공개 #76)', () => { + it('화면이 대표 1건만 읽는다 (목록이 그 이상을 싣지 않는다)', () => { + const bindings = collectBindings(readLayout(IDENTITY_TAB)); + const templateReads = bindings.filter((binding) => /\bdef\.templates\b/.test(binding)); + + expect( + templateReads.length, + '본인인증 정의 화면이 템플릿을 읽고 있어야 한다', + ).toBeGreaterThan(0); + + // `templates[0]` 이외의 인덱스나 순회를 쓰면, 대표 1건만 오는 목록에서 영영 비어 있다. + const beyondFirst = templateReads.filter((binding) => + /def\.templates\s*\??\.?\[\s*(?!0\s*\])/.test(binding), + ); + + expect(beyondFirst, '대표 1건을 넘어서는 참조가 있으면 목록에서 값이 비어 있다').toEqual( + [], + ); + + const iterated = templateReads.filter((binding) => + /def\.templates[\s\S]*\.(map|forEach|filter|some|every|find)\s*\(/.test(binding), + ); + + expect(iterated, '목록에서 전량 순회는 성립하지 않는다 (대표 1건만 실린다)').toEqual([]); + }); +}); diff --git a/templates/_bundled/sirsoft-admin_basic/__tests__/layouts/admin-template-layout-edit-list-pruning.test.tsx b/templates/_bundled/sirsoft-admin_basic/__tests__/layouts/admin-template-layout-edit-list-pruning.test.tsx new file mode 100644 index 00000000..d69e9706 --- /dev/null +++ b/templates/_bundled/sirsoft-admin_basic/__tests__/layouts/admin-template-layout-edit-list-pruning.test.tsx @@ -0,0 +1,131 @@ +/** + * @file admin-template-layout-edit-list-pruning.test.tsx + * @description 레이아웃 파일트리·확장 라우트트리 — 목록 프루닝 후 화면 계약 회귀 테스트 + * + * 회귀 배경 (#518 / 공개 #76): + * 레이아웃 목록(`GET .../layouts`)과 확장 목록(`GET .../layout-extensions`)이 페이지네이션 + * 없이 전 행을 돌려주면서 행마다 본문(`content`)과 파싱된 블록(`components` 등)을 통째로 + * 싣고 있었다. 템플릿 하나에 레이아웃이 수십 개면 편집기 진입만으로 그 전부의 JSON 전문이 + * 전송된다. + * + * 목록은 파일트리·라우트트리가 실제로 그리는 필드만 싣고, 본문은 단건 조회가 공급하도록 + * 바꿨다. 이 화면은 두 목록의 **유일한 소비자**이므로, 화면이 무엇을 읽는지가 곧 목록이 + * 무엇을 실어야 하는지의 기준이다. + * + * 이 테스트가 고정하는 것은 두 방향이다. + * + * 1. 화면이 목록 응답에서 본문을 읽지 않는다 — 읽기 시작하면 프루닝이 곧 기능 삭제가 된다. + * 2. 본문이 필요한 지점은 단건 데이터소스를 거친다 — 대체 경로가 살아 있다는 증거. + * + * 응답만 보는 백엔드 테스트로는 1번이 잡히지 않는다. 목록에서 필드가 사라진 것은 확인해도, + * 그 필드를 화면이 쓰고 있었다는 사실은 화면 쪽을 봐야 드러난다 — 메뉴(M)가 정확히 그렇게 + * 새어 나갔다. + */ + +import fs from 'fs'; +import path from 'path'; +import { describe, it, expect } from 'vitest'; + +const LAYOUT = path.resolve(__dirname, '../../layouts/admin_template_layout_edit.json'); + +/** 목록 데이터소스 id — 이 id 를 통해 들어온 값에는 본문이 없다. */ +const LIST_SOURCES = { + layouts: 'layout_files', + extensions: 'layout_extensions', +} as const; + +/** + * 레이아웃 JSON 원문을 읽습니다. + * + * @returns 파일 원문 + */ +function readRaw(): string { + return fs.readFileSync(LAYOUT, 'utf-8'); +} + +/** + * 레이아웃 JSON 을 파싱해 데이터소스 목록을 반환합니다. + * + * @returns 데이터소스 배열 + */ +function readDataSources(): Array> { + const parsed = JSON.parse(readRaw()) as { data_sources?: Array> }; + + return parsed.data_sources ?? []; +} + +/** + * @scenario resource=layout,endpoint=list,observation=consumer_screen + * @effects list_omits_layout_content, detail_still_returns_full_payload + */ +describe('레이아웃 파일트리 — 목록 프루닝 (#518 / 공개 #76)', () => { + it('목록 데이터소스가 확장자 없는 목록 엔드포인트를 그대로 쓴다', () => { + const source = readDataSources().find((d) => d.id === LIST_SOURCES.layouts); + + expect(source, '파일트리 목록 데이터소스가 있어야 한다').toBeDefined(); + expect(source!.endpoint).toBe('/api/admin/templates/{{route.identifier}}/layouts'); + }); + + it('목록 응답에서 본문·파싱 블록을 읽지 않는다', () => { + const raw = readRaw(); + + // 목록으로 들어온 값은 `layoutFilesList` / `layout_files` 두 이름으로 참조된다. + // 어느 쪽이든 본문 키를 뒤에 붙이면 프루닝된 응답에서 undefined 가 된다. + for (const holder of ['layoutFilesList', 'layout_files']) { + for (const heavy of ['content', 'components', 'data_sources', 'metadata']) { + expect( + raw, + `${holder}.${heavy} — 목록 응답에 없는 필드다. 본문이 필요하면 단건 데이터소스를 쓴다`, + ).not.toContain(`${holder}?.${heavy}`); + expect(raw).not.toContain(`${holder}.${heavy}`); + } + } + }); + + it('본문은 단건 데이터소스가 공급한다 (대체 경로 보존)', () => { + const endpoints = readDataSources().map((d) => String(d.endpoint ?? '')); + + // 선택된 레이아웃 1건을 이름으로 조회하는 데이터소스 — 여기가 본문 공급처다. + const detail = endpoints.find( + (e) => e.includes('/layouts/') && e.includes('selectedLayoutName') && !e.includes('/versions'), + ); + + expect(detail, '목록에서 본문을 뺐으므로 단건 조회 경로가 반드시 있어야 한다').toBeDefined(); + }); +}); + +/** + * @scenario resource=layout_extension,endpoint=list,observation=consumer_screen + * @effects list_omits_extension_content, detail_still_returns_full_payload + */ +describe('확장 라우트트리 — 목록 프루닝 (#518 / 공개 #76)', () => { + it('확장 목록 데이터소스가 목록 엔드포인트를 그대로 쓴다', () => { + const source = readDataSources().find((d) => d.id === LIST_SOURCES.extensions); + + expect(source, '라우트트리 확장 목록 데이터소스가 있어야 한다').toBeDefined(); + expect(source!.endpoint).toBe('/api/admin/templates/{{route.identifier}}/layout-extensions'); + }); + + it('확장 목록 응답에서 본문을 읽지 않는다', () => { + const raw = readRaw(); + + expect( + raw, + 'layout_extensions 응답에는 content 가 없다 — 캔버스는 단건 조회로 본문을 받는다', + ).not.toContain('layout_extensions?.data?.content'); + expect(raw).not.toContain('layout_extensions.data.content'); + }); + + it('확장 본문은 단건 데이터소스가 공급한다 (대체 경로 보존)', () => { + const endpoints = readDataSources().map((d) => String(d.endpoint ?? '')); + + const detail = endpoints.find( + (e) => + e.includes('/layout-extensions/') && + e.includes('selectedExtensionId') && + !e.includes('/versions'), + ); + + expect(detail, '목록에서 본문을 뺐으므로 확장 단건 조회 경로가 반드시 있어야 한다').toBeDefined(); + }); +}); diff --git a/templates/_bundled/sirsoft-admin_basic/dist/css/components.css b/templates/_bundled/sirsoft-admin_basic/dist/css/components.css index 38d052a8..26d39150 100644 --- a/templates/_bundled/sirsoft-admin_basic/dist/css/components.css +++ b/templates/_bundled/sirsoft-admin_basic/dist/css/components.css @@ -1 +1 @@ -@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-pan-x:initial;--tw-pan-y:initial;--tw-pinch-zoom:initial;--tw-scroll-snap-strictness:proximity;--tw-space-y-reverse:0;--tw-space-x-reverse:0;--tw-divide-x-reverse:0;--tw-border-style:solid;--tw-divide-y-reverse:0;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-ease:initial;--tw-content:""}}}@layer theme{:root,:host{--font-sans:"Pretendard", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-red-50:oklch(97.1% .013 17.38);--color-red-100:oklch(93.6% .032 17.717);--color-red-200:oklch(88.5% .062 18.334);--color-red-300:oklch(80.8% .114 19.571);--color-red-400:oklch(70.4% .191 22.216);--color-red-500:oklch(63.7% .237 25.331);--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-red-800:oklch(44.4% .177 26.899);--color-red-900:oklch(39.6% .141 25.723);--color-red-950:oklch(25.8% .092 26.042);--color-orange-50:oklch(98% .016 73.684);--color-orange-100:oklch(95.4% .038 75.164);--color-orange-200:oklch(90.1% .076 70.697);--color-orange-300:oklch(83.7% .128 66.29);--color-orange-400:oklch(75% .183 55.934);--color-orange-500:oklch(70.5% .213 47.604);--color-orange-600:oklch(64.6% .222 41.116);--color-orange-700:oklch(55.3% .195 38.402);--color-orange-800:oklch(47% .157 37.304);--color-orange-900:oklch(40.8% .123 38.172);--color-orange-950:oklch(26.6% .079 36.259);--color-amber-50:oklch(98.7% .022 95.277);--color-amber-100:oklch(96.2% .059 95.617);--color-amber-200:oklch(92.4% .12 95.746);--color-amber-300:oklch(87.9% .169 91.605);--color-amber-400:oklch(82.8% .189 84.429);--color-amber-500:oklch(76.9% .188 70.08);--color-amber-600:oklch(66.6% .179 58.318);--color-amber-700:oklch(55.5% .163 48.998);--color-amber-800:oklch(47.3% .137 46.201);--color-amber-900:oklch(41.4% .112 45.904);--color-amber-950:oklch(27.9% .077 45.635);--color-yellow-50:oklch(98.7% .026 102.212);--color-yellow-100:oklch(97.3% .071 103.193);--color-yellow-200:oklch(94.5% .129 101.54);--color-yellow-300:oklch(90.5% .182 98.111);--color-yellow-400:oklch(85.2% .199 91.936);--color-yellow-500:oklch(79.5% .184 86.047);--color-yellow-600:oklch(68.1% .162 75.834);--color-yellow-700:oklch(55.4% .135 66.442);--color-yellow-800:oklch(47.6% .114 61.907);--color-yellow-900:oklch(42.1% .095 57.708);--color-yellow-950:oklch(28.6% .066 53.813);--color-lime-50:oklch(98.6% .031 120.757);--color-lime-100:oklch(96.7% .067 122.328);--color-lime-200:oklch(93.8% .127 124.321);--color-lime-300:oklch(89.7% .196 126.665);--color-lime-400:oklch(84.1% .238 128.85);--color-lime-500:oklch(76.8% .233 130.85);--color-lime-600:oklch(64.8% .2 131.684);--color-lime-700:oklch(53.2% .157 131.589);--color-lime-800:oklch(45.3% .124 130.933);--color-lime-900:oklch(40.5% .101 131.063);--color-lime-950:oklch(27.4% .072 132.109);--color-green-50:oklch(98.2% .018 155.826);--color-green-100:oklch(96.2% .044 156.743);--color-green-200:oklch(92.5% .084 155.995);--color-green-300:oklch(87.1% .15 154.449);--color-green-400:oklch(79.2% .209 151.711);--color-green-500:oklch(72.3% .219 149.579);--color-green-600:oklch(62.7% .194 149.214);--color-green-700:oklch(52.7% .154 150.069);--color-green-800:oklch(44.8% .119 151.328);--color-green-900:oklch(39.3% .095 152.535);--color-green-950:oklch(26.6% .065 152.934);--color-emerald-50:oklch(97.9% .021 166.113);--color-emerald-100:oklch(95% .052 163.051);--color-emerald-200:oklch(90.5% .093 164.15);--color-emerald-300:oklch(84.5% .143 164.978);--color-emerald-400:oklch(76.5% .177 163.223);--color-emerald-500:oklch(69.6% .17 162.48);--color-emerald-600:oklch(59.6% .145 163.225);--color-emerald-700:oklch(50.8% .118 165.612);--color-emerald-800:oklch(43.2% .095 166.913);--color-emerald-900:oklch(37.8% .077 168.94);--color-emerald-950:oklch(26.2% .051 172.552);--color-teal-50:oklch(98.4% .014 180.72);--color-teal-100:oklch(95.3% .051 180.801);--color-teal-200:oklch(91% .096 180.426);--color-teal-300:oklch(85.5% .138 181.071);--color-teal-400:oklch(77.7% .152 181.912);--color-teal-500:oklch(70.4% .14 182.503);--color-teal-600:oklch(60% .118 184.704);--color-teal-700:oklch(51.1% .096 186.391);--color-teal-800:oklch(43.7% .078 188.216);--color-teal-900:oklch(38.6% .063 188.416);--color-teal-950:oklch(27.7% .046 192.524);--color-cyan-50:oklch(98.4% .019 200.873);--color-cyan-100:oklch(95.6% .045 203.388);--color-cyan-200:oklch(91.7% .08 205.041);--color-cyan-300:oklch(86.5% .127 207.078);--color-cyan-400:oklch(78.9% .154 211.53);--color-cyan-500:oklch(71.5% .143 215.221);--color-cyan-600:oklch(60.9% .126 221.723);--color-cyan-700:oklch(52% .105 223.128);--color-cyan-800:oklch(45% .085 224.283);--color-cyan-900:oklch(39.8% .07 227.392);--color-cyan-950:oklch(30.2% .056 229.695);--color-sky-50:oklch(97.7% .013 236.62);--color-sky-100:oklch(95.1% .026 236.824);--color-sky-200:oklch(90.1% .058 230.902);--color-sky-300:oklch(82.8% .111 230.318);--color-sky-400:oklch(74.6% .16 232.661);--color-sky-500:oklch(68.5% .169 237.323);--color-sky-600:oklch(58.8% .158 241.966);--color-sky-700:oklch(50% .134 242.749);--color-sky-800:oklch(44.3% .11 240.79);--color-sky-900:oklch(39.1% .09 240.876);--color-sky-950:oklch(29.3% .066 243.157);--color-blue-50:oklch(97% .014 254.604);--color-blue-100:oklch(93.2% .032 255.585);--color-blue-200:oklch(88.2% .059 254.128);--color-blue-300:oklch(80.9% .105 251.813);--color-blue-400:oklch(70.7% .165 254.624);--color-blue-500:oklch(62.3% .214 259.815);--color-blue-600:oklch(54.6% .245 262.881);--color-blue-700:oklch(48.8% .243 264.376);--color-blue-800:oklch(42.4% .199 265.638);--color-blue-900:oklch(37.9% .146 265.522);--color-blue-950:oklch(28.2% .091 267.935);--color-indigo-50:oklch(96.2% .018 272.314);--color-indigo-100:oklch(93% .034 272.788);--color-indigo-200:oklch(87% .065 274.039);--color-indigo-300:oklch(78.5% .115 274.713);--color-indigo-400:oklch(67.3% .182 276.935);--color-indigo-500:oklch(58.5% .233 277.117);--color-indigo-600:oklch(51.1% .262 276.966);--color-indigo-700:oklch(45.7% .24 277.023);--color-indigo-800:oklch(39.8% .195 277.366);--color-indigo-900:oklch(35.9% .144 278.697);--color-indigo-950:oklch(25.7% .09 281.288);--color-violet-50:oklch(96.9% .016 293.756);--color-violet-100:oklch(94.3% .029 294.588);--color-violet-200:oklch(89.4% .057 293.283);--color-violet-300:oklch(81.1% .111 293.571);--color-violet-400:oklch(70.2% .183 293.541);--color-violet-500:oklch(60.6% .25 292.717);--color-violet-600:oklch(54.1% .281 293.009);--color-violet-700:oklch(49.1% .27 292.581);--color-violet-800:oklch(43.2% .232 292.759);--color-violet-900:oklch(38% .189 293.745);--color-violet-950:oklch(28.3% .141 291.089);--color-purple-50:oklch(97.7% .014 308.299);--color-purple-100:oklch(94.6% .033 307.174);--color-purple-200:oklch(90.2% .063 306.703);--color-purple-300:oklch(82.7% .119 306.383);--color-purple-400:oklch(71.4% .203 305.504);--color-purple-500:oklch(62.7% .265 303.9);--color-purple-600:oklch(55.8% .288 302.321);--color-purple-700:oklch(49.6% .265 301.924);--color-purple-800:oklch(43.8% .218 303.724);--color-purple-900:oklch(38.1% .176 304.987);--color-purple-950:oklch(29.1% .149 302.717);--color-fuchsia-50:oklch(97.7% .017 320.058);--color-fuchsia-100:oklch(95.2% .037 318.852);--color-fuchsia-200:oklch(90.3% .076 319.62);--color-fuchsia-300:oklch(83.3% .145 321.434);--color-fuchsia-400:oklch(74% .238 322.16);--color-fuchsia-500:oklch(66.7% .295 322.15);--color-fuchsia-600:oklch(59.1% .293 322.896);--color-fuchsia-700:oklch(51.8% .253 323.949);--color-fuchsia-800:oklch(45.2% .211 324.591);--color-fuchsia-900:oklch(40.1% .17 325.612);--color-fuchsia-950:oklch(29.3% .136 325.661);--color-pink-50:oklch(97.1% .014 343.198);--color-pink-100:oklch(94.8% .028 342.258);--color-pink-200:oklch(89.9% .061 343.231);--color-pink-300:oklch(82.3% .12 346.018);--color-pink-400:oklch(71.8% .202 349.761);--color-pink-500:oklch(65.6% .241 354.308);--color-pink-600:oklch(59.2% .249 .584);--color-pink-700:oklch(52.5% .223 3.958);--color-pink-800:oklch(45.9% .187 3.815);--color-pink-900:oklch(40.8% .153 2.432);--color-pink-950:oklch(28.4% .109 3.907);--color-rose-50:oklch(96.9% .015 12.422);--color-rose-100:oklch(94.1% .03 12.58);--color-rose-200:oklch(89.2% .058 10.001);--color-rose-300:oklch(81% .117 11.638);--color-rose-400:oklch(71.2% .194 13.428);--color-rose-500:oklch(64.5% .246 16.439);--color-rose-600:oklch(58.6% .253 17.585);--color-rose-700:oklch(51.4% .222 16.935);--color-rose-800:oklch(45.5% .188 13.697);--color-rose-900:oklch(41% .159 10.272);--color-rose-950:oklch(27.1% .105 12.094);--color-slate-50:oklch(98.4% .003 247.858);--color-slate-100:oklch(96.8% .007 247.896);--color-slate-200:oklch(92.9% .013 255.508);--color-slate-300:oklch(86.9% .022 252.894);--color-slate-400:oklch(70.4% .04 256.788);--color-slate-500:oklch(55.4% .046 257.417);--color-slate-600:oklch(44.6% .043 257.281);--color-slate-700:oklch(37.2% .044 257.287);--color-slate-800:oklch(27.9% .041 260.031);--color-slate-900:oklch(20.8% .042 265.755);--color-slate-950:oklch(12.9% .042 264.695);--color-gray-50:oklch(98.5% .002 247.839);--color-gray-100:oklch(96.7% .003 264.542);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-700:oklch(37.3% .034 259.733);--color-gray-800:oklch(27.8% .033 256.848);--color-gray-900:oklch(21% .034 264.665);--color-gray-950:oklch(13% .028 261.692);--color-zinc-50:oklch(98.5% 0 none);--color-zinc-100:oklch(96.7% .001 286.375);--color-zinc-200:oklch(92% .004 286.32);--color-zinc-300:oklch(87.1% .006 286.286);--color-zinc-400:oklch(70.5% .015 286.067);--color-zinc-500:oklch(55.2% .016 285.938);--color-zinc-600:oklch(44.2% .017 285.786);--color-zinc-700:oklch(37% .013 285.805);--color-zinc-800:oklch(27.4% .006 286.033);--color-zinc-900:oklch(21% .006 285.885);--color-zinc-950:oklch(14.1% .005 285.823);--color-neutral-50:oklch(98.5% 0 none);--color-neutral-100:oklch(97% 0 none);--color-neutral-200:oklch(92.2% 0 none);--color-neutral-300:oklch(87% 0 none);--color-neutral-400:oklch(70.8% 0 none);--color-neutral-500:oklch(55.6% 0 none);--color-neutral-600:oklch(43.9% 0 none);--color-neutral-700:oklch(37.1% 0 none);--color-neutral-800:oklch(26.9% 0 none);--color-neutral-900:oklch(20.5% 0 none);--color-neutral-950:oklch(14.5% 0 none);--color-stone-50:oklch(98.5% .001 106.423);--color-stone-100:oklch(97% .001 106.424);--color-stone-200:oklch(92.3% .003 48.717);--color-stone-300:oklch(86.9% .005 56.366);--color-stone-400:oklch(70.9% .01 56.259);--color-stone-500:oklch(55.3% .013 58.071);--color-stone-600:oklch(44.4% .011 73.639);--color-stone-700:oklch(37.4% .01 67.558);--color-stone-800:oklch(26.8% .007 34.298);--color-stone-900:oklch(21.6% .006 56.043);--color-stone-950:oklch(14.7% .004 49.25);--color-black:#000;--color-white:#fff;--spacing:.25rem;--breakpoint-sm:40rem;--breakpoint-md:48rem;--breakpoint-lg:64rem;--breakpoint-xl:80rem;--breakpoint-2xl:96rem;--container-3xs:16rem;--container-2xs:18rem;--container-xs:20rem;--container-sm:24rem;--container-md:28rem;--container-lg:32rem;--container-xl:36rem;--container-2xl:42rem;--container-3xl:48rem;--container-4xl:56rem;--container-5xl:64rem;--container-6xl:72rem;--container-7xl:80rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height: 1.5 ;--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--text-3xl:1.875rem;--text-3xl--line-height: 1.2 ;--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5 / 2.25);--text-5xl:3rem;--text-5xl--line-height:1;--text-6xl:3.75rem;--text-6xl--line-height:1;--text-7xl:4.5rem;--text-7xl--line-height:1;--text-8xl:6rem;--text-8xl--line-height:1;--text-9xl:8rem;--text-9xl--line-height:1;--font-weight-thin:100;--font-weight-extralight:200;--font-weight-light:300;--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--font-weight-extrabold:800;--font-weight-black:900;--tracking-tighter:-.05em;--tracking-tight:-.025em;--tracking-normal:0em;--tracking-wide:.025em;--tracking-wider:.05em;--tracking-widest:.1em;--leading-tight:1.25;--leading-snug:1.375;--leading-normal:1.5;--leading-relaxed:1.625;--leading-loose:2;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--radius-2xl:1rem;--radius-3xl:1.5rem;--drop-shadow-sm:0 1px 2px #00000026;--drop-shadow-md:0 3px 3px #0000001f;--drop-shadow-lg:0 4px 4px #00000026;--drop-shadow-xl:0 9px 7px #0000001a;--drop-shadow-2xl:0 25px 25px #00000026;--ease-in:cubic-bezier(.4, 0, 1, 1);--ease-out:cubic-bezier(0, 0, .2, 1);--ease-in-out:cubic-bezier(.4, 0, .2, 1);--animate-spin:spin 1s linear infinite;--animate-ping:ping 1s cubic-bezier(0, 0, .2, 1) infinite;--animate-pulse:pulse 2s cubic-bezier(.4, 0, .6, 1) infinite;--animate-bounce:bounce 1s infinite;--blur-sm:8px;--blur-md:12px;--blur-lg:16px;--blur-xl:24px;--blur-2xl:40px;--blur-3xl:64px;--aspect-video:16 / 9;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring:where(:not(iframe)){outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}body{color:var(--color-gray-900);font-size:.875rem;line-height:1.5}.dark :is(body){color:var(--color-gray-100)}}@layer components{.admin_layout_root{background-color:var(--color-gray-50);height:100vh;display:flex;overflow:hidden}.dark .admin_layout_root{background-color:var(--color-gray-900)}.mobile_overlay{z-index:40;background-color:#00000080;position:fixed;inset:0}.left_sidebar_area{z-index:50;width:calc(var(--spacing) * 64);background-color:var(--color-white);inset-block:0;flex-direction:column;display:flex;position:relative;left:0}.dark .left_sidebar_area{background-color:var(--color-gray-800)}.left_sidebar_area{transition:width .3s}.left_sidebar_area_collapsed{width:0;overflow:hidden}.left_sidebar_area_portable{z-index:50;width:calc(var(--spacing) * 64);background-color:var(--color-white);transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.3s;flex-direction:column;transition-duration:.3s;display:flex;position:fixed;left:0}.dark .left_sidebar_area_portable{background-color:var(--color-gray-800)}.left_sidebar_area_portable{top:4rem;bottom:0;transform:translate(-100%)}.left_sidebar_area_portable_open{transform:translate(0)}.sidebar_header{height:calc(var(--spacing) * 16);padding-inline:calc(var(--spacing) * 4);justify-content:space-between;align-items:center;display:flex}.sidebar_logo{height:calc(var(--spacing) * 10);align-items:center;gap:calc(var(--spacing) * 2);flex:1;min-width:0;display:flex}.logo_image{max-height:calc(var(--spacing) * 10);object-fit:contain;width:auto}.admin_sidebar_menu{padding-top:calc(var(--spacing) * 4);box-shadow:inset 3px 2px 2px var(--tw-shadow-color,#0000000a);flex:1;overflow-y:auto}.admin_sidebar_menu_portable{padding-top:calc(var(--spacing) * 4);flex:1;overflow-y:auto}.admin_sidebar_empty{padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 8);text-align:center;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-400)}.dark .admin_sidebar_empty{color:var(--color-gray-500)}.admin_sidebar_empty_icon{margin-bottom:calc(var(--spacing) * 2);font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));color:var(--color-gray-300)}.dark .admin_sidebar_empty_icon{color:var(--color-gray-600)}.sidebar_footer{border-top-style:var(--tw-border-style);border-top-width:1px;border-color:var(--color-gray-100);padding:calc(var(--spacing) * 3);flex-shrink:0}.dark .sidebar_footer{border-color:var(--color-gray-700)}.sidebar_view_site_link{height:calc(var(--spacing) * 9);align-items:center;gap:calc(var(--spacing) * 2);border-radius:var(--radius-lg);padding-inline:calc(var(--spacing) * 3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-slate-600);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));text-decoration-line:none;display:flex}@media(hover:hover){.sidebar_view_site_link:hover{background-color:var(--color-slate-100);color:var(--color-slate-900)}}.dark .sidebar_view_site_link{color:var(--color-slate-300)}@media(hover:hover){.dark .sidebar_view_site_link:hover{background-color:var(--color-slate-700);color:var(--color-white)}}.sidebar_view_site_icon{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));flex-shrink:0}.sidebar_view_site_label{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.right_content_wrapper{flex-direction:column;flex:1;min-width:0;display:flex}.right_content_area{flex:1;min-width:0;min-height:0;overflow:auto}.main_header{z-index:50;height:calc(var(--spacing) * 16);background-color:var(--color-white);padding-inline:calc(var(--spacing) * 8);flex-shrink:0;justify-content:space-between;align-items:center;display:flex;position:relative}.dark .main_header{background-color:var(--color-gray-800)}.main_header{box-shadow:0 1px 3px #00000014}.header_left{flex:1;align-items:center;display:flex}.header_right{align-items:center;gap:calc(var(--spacing) * 2);display:flex}.menu_toggle_btn{margin-left:calc(var(--spacing) * -2);border-radius:var(--radius-lg);padding:calc(var(--spacing) * 2);color:var(--color-slate-500);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}@media(hover:hover){.menu_toggle_btn:hover{background-color:var(--color-slate-100);color:var(--color-slate-900)}}.dark .menu_toggle_btn{color:var(--color-slate-400)}@media(hover:hover){.dark .menu_toggle_btn:hover{background-color:var(--color-slate-700);color:var(--color-white)}}.menu_toggle_btn_icon{height:calc(var(--spacing) * 5);width:calc(var(--spacing) * 5)}.user_avatar_section{cursor:pointer;align-items:center;gap:calc(var(--spacing) * 3);display:flex;position:relative}.user_avatar{height:calc(var(--spacing) * 9);width:calc(var(--spacing) * 9);background-color:var(--color-gray-200);border-radius:3.40282e38px;justify-content:center;align-items:center;display:flex;overflow:hidden}.dark .user_avatar{background-color:var(--color-gray-600)}.user_avatar_img{object-fit:cover;width:100%;height:100%}.user_info{display:none}@media(min-width:48rem){.user_info{display:block}}.mobile_header_portable{z-index:50;height:calc(var(--spacing) * 16);background-color:var(--color-white);padding-inline:calc(var(--spacing) * 4);justify-content:space-between;align-items:center;display:flex;position:relative}.dark .mobile_header_portable{background-color:var(--color-gray-800)}.mobile_header_portable{box-shadow:0 1px 3px #00000014}.mobile_header_left{align-items:center;gap:calc(var(--spacing) * 3);display:flex}.mobile_header_right{align-items:center;gap:calc(var(--spacing) * 2);flex-shrink:0;display:flex}.mobile_logo{align-items:center;gap:calc(var(--spacing) * 2);flex:1;min-width:0;display:flex}.mobile_logo_image{height:calc(var(--spacing) * 8);object-fit:contain;width:auto;max-width:100%}.main_content{min-height:fit-content}.layout_warnings{padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2)}.text-primary{color:var(--color-gray-900)}.dark .text-primary{color:var(--color-white)}.text-secondary{color:var(--color-gray-600)}.dark .text-secondary{color:var(--color-gray-400)}.text-muted,.dark .text-muted{color:var(--color-gray-500)}.text-tertiary{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-gray-500)}.dark .text-tertiary{color:var(--color-gray-400)}.text-label{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-600)}.dark .text-label{color:var(--color-gray-400)}.text-label-subtle{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-500)}.dark .text-label-subtle{color:var(--color-gray-400)}.text-heading{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-900)}.dark .text-heading{color:var(--color-white)}.section-heading{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-gray-900)}.dark .section-heading{color:var(--color-white)}.section-title{margin-bottom:var(--spacing);font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-gray-900)}.dark .section-title{color:var(--color-white)}.section-description{margin-bottom:calc(var(--spacing) * 6);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-500)}.dark .section-description{color:var(--color-gray-400)}.card-title{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-gray-900)}.dark .card-title{color:var(--color-white)}.card-title-aside{justify-content:space-between;align-items:center;gap:calc(var(--spacing) * 2);display:flex}.card-description{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-500)}.dark .card-description{color:var(--color-gray-400)}.admin-card>.card-title{margin-bottom:calc(var(--spacing) * 4)}.admin-card>.card-title:has(+.card-description){margin-bottom:0}.admin-card>.card-description{margin-bottom:calc(var(--spacing) * 4)}.card-header{align-items:center;gap:calc(var(--spacing) * 2);--tw-leading:1;margin-bottom:0;line-height:1;display:flex}.card-header>*{--tw-leading:1;line-height:1}.text-body{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-700)}.dark .text-body{color:var(--color-gray-300)}.icon-muted{color:var(--color-gray-400)}.dark .icon-muted{color:var(--color-gray-500)}.bg-success{background-color:var(--color-green-100)}.dark .bg-success{background-color:var(--color-green-900)}.bg-success-light{background-color:var(--color-green-50)}.dark .bg-success-light{background-color:#0d542b33}@supports (color:color-mix(in lab,red,red)){.dark .bg-success-light{background-color:color-mix(in oklab,var(--color-green-900) 20%,transparent)}}.bg-success-solid{background-color:var(--color-green-600)}.dark .bg-success-solid{background-color:var(--color-green-500)}.bg-error{background-color:var(--color-red-100)}.dark .bg-error{background-color:var(--color-red-900)}.bg-error-light{background-color:var(--color-red-50)}.dark .bg-error-light{background-color:#82181a33}@supports (color:color-mix(in lab,red,red)){.dark .bg-error-light{background-color:color-mix(in oklab,var(--color-red-900) 20%,transparent)}}.bg-error-solid{background-color:var(--color-red-600)}.dark .bg-error-solid{background-color:var(--color-red-500)}.bg-warning{background-color:var(--color-yellow-100)}.dark .bg-warning{background-color:var(--color-yellow-900)}.bg-warning-light{background-color:var(--color-yellow-50)}.dark .bg-warning-light{background-color:#733e0a33}@supports (color:color-mix(in lab,red,red)){.dark .bg-warning-light{background-color:color-mix(in oklab,var(--color-yellow-900) 20%,transparent)}}.bg-warning-solid{background-color:var(--color-yellow-600)}.dark .bg-warning-solid{background-color:var(--color-yellow-500)}.bg-info{background-color:var(--color-blue-100)}.dark .bg-info{background-color:var(--color-blue-900)}.bg-info-light{background-color:var(--color-blue-50)}.dark .bg-info-light{background-color:#1c398e33}@supports (color:color-mix(in lab,red,red)){.dark .bg-info-light{background-color:color-mix(in oklab,var(--color-blue-900) 20%,transparent)}}.bg-info-solid{background-color:var(--color-blue-600)}.dark .bg-info-solid{background-color:var(--color-blue-500)}.bg-ready{background-color:var(--color-cyan-100)}.dark .bg-ready{background-color:var(--color-cyan-900)}.bg-ready-light{background-color:var(--color-cyan-50)}.dark .bg-ready-light{background-color:#104e6433}@supports (color:color-mix(in lab,red,red)){.dark .bg-ready-light{background-color:color-mix(in oklab,var(--color-cyan-900) 20%,transparent)}}.bg-ready-solid{background-color:var(--color-cyan-600)}.dark .bg-ready-solid{background-color:var(--color-cyan-400)}.bg-progress{background-color:var(--color-indigo-100)}.dark .bg-progress{background-color:var(--color-indigo-900)}.bg-progress-light{background-color:var(--color-indigo-50)}.dark .bg-progress-light{background-color:#312c8533}@supports (color:color-mix(in lab,red,red)){.dark .bg-progress-light{background-color:color-mix(in oklab,var(--color-indigo-900) 20%,transparent)}}.bg-progress-solid{background-color:var(--color-indigo-600)}.dark .bg-progress-solid{background-color:var(--color-indigo-400)}.bg-complete{background-color:var(--color-pink-100)}.dark .bg-complete{background-color:var(--color-pink-900)}.bg-complete-light{background-color:var(--color-pink-50)}.dark .bg-complete-light{background-color:#86104333}@supports (color:color-mix(in lab,red,red)){.dark .bg-complete-light{background-color:color-mix(in oklab,var(--color-pink-900) 20%,transparent)}}.bg-complete-solid{background-color:var(--color-pink-600)}.dark .bg-complete-solid{background-color:var(--color-pink-400)}.bg-hold{background-color:var(--color-gray-100)}.dark .bg-hold{background-color:var(--color-gray-800)}.bg-hold-light{background-color:var(--color-gray-50)}.dark .bg-hold-light{background-color:#1e293980}@supports (color:color-mix(in lab,red,red)){.dark .bg-hold-light{background-color:color-mix(in oklab,var(--color-gray-800) 50%,transparent)}}.bg-hold-solid{background-color:var(--color-gray-500)}.dark .bg-hold-solid{background-color:var(--color-gray-400)}.text-success{color:var(--color-green-600)}.dark .text-success{color:var(--color-green-400)}.text-error{color:var(--color-red-600)}.dark .text-error{color:var(--color-red-400)}.text-warning{color:var(--color-yellow-600)}.dark .text-warning{color:var(--color-yellow-400)}.text-info{color:var(--color-blue-600)}.dark .text-info{color:var(--color-blue-400)}.text-ready{color:var(--color-cyan-600)}.dark .text-ready{color:var(--color-cyan-400)}.text-progress{color:var(--color-indigo-600)}.dark .text-progress{color:var(--color-indigo-400)}.text-complete{color:var(--color-pink-600)}.dark .text-complete{color:var(--color-pink-400)}.text-hold{color:var(--color-gray-500)}.dark .text-hold{color:var(--color-gray-400)}.text-success-light{color:var(--color-white)}.dark .text-success-light{color:var(--color-green-900)}.text-error-light{color:var(--color-white)}.dark .text-error-light{color:var(--color-red-900)}.text-warning-light{color:var(--color-white)}.dark .text-warning-light{color:var(--color-yellow-900)}.text-info-light{color:var(--color-white)}.dark .text-info-light{color:var(--color-blue-900)}.text-ready-light{color:var(--color-white)}.dark .text-ready-light{color:var(--color-cyan-900)}.text-progress-light{color:var(--color-white)}.dark .text-progress-light{color:var(--color-indigo-900)}.text-complete-light{color:var(--color-white)}.dark .text-complete-light{color:var(--color-pink-900)}.text-hold-light{color:var(--color-white)}.dark .text-hold-light{color:var(--color-gray-900)}.border-success{border-color:var(--color-green-200)}.dark .border-success{border-color:var(--color-green-800)}.border-error{border-color:var(--color-red-200)}.dark .border-error{border-color:var(--color-red-800)}.border-warning{border-color:var(--color-yellow-200)}.dark .border-warning{border-color:var(--color-yellow-800)}.border-info{border-color:var(--color-blue-200)}.dark .border-info{border-color:var(--color-blue-800)}.border-ready{border-color:var(--color-cyan-200)}.dark .border-ready{border-color:var(--color-cyan-800)}.border-progress{border-color:var(--color-indigo-200)}.dark .border-progress{border-color:var(--color-indigo-800)}.border-complete{border-color:var(--color-pink-200)}.dark .border-complete{border-color:var(--color-pink-800)}.border-hold{border-color:var(--color-gray-300)}.dark .border-hold{border-color:var(--color-gray-600)}.btn-success{background-color:var(--color-green-600);color:var(--color-white)}@media(hover:hover){.btn-success:hover{background-color:var(--color-green-700)}}.dark .btn-success{background-color:var(--color-green-500)}@media(hover:hover){.dark .btn-success:hover{background-color:var(--color-green-600)}}.btn-danger{background-color:var(--color-red-600);color:var(--color-white)}@media(hover:hover){.btn-danger:hover{background-color:var(--color-red-700)}}.dark .btn-danger{background-color:var(--color-red-500)}@media(hover:hover){.dark .btn-danger:hover{background-color:var(--color-red-600)}}.btn-warning{background-color:var(--color-yellow-600);color:var(--color-white)}@media(hover:hover){.btn-warning:hover{background-color:var(--color-yellow-700)}}.dark .btn-warning{background-color:var(--color-yellow-500)}@media(hover:hover){.dark .btn-warning:hover{background-color:var(--color-yellow-600)}}.btn-info{background-color:var(--color-blue-600);color:var(--color-white)}@media(hover:hover){.btn-info:hover{background-color:var(--color-blue-700)}}.dark .btn-info{background-color:var(--color-blue-500)}@media(hover:hover){.dark .btn-info:hover{background-color:var(--color-blue-600)}}.btn-neutral{background-color:var(--color-gray-600);color:var(--color-white)}@media(hover:hover){.btn-neutral:hover{background-color:var(--color-gray-700)}}.dark .btn-neutral{background-color:var(--color-gray-500)}@media(hover:hover){.dark .btn-neutral:hover{background-color:var(--color-gray-600)}}.btn-sm{padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 1.5)}.button{cursor:pointer;justify-content:center;align-items:center;display:inline-flex}.button:disabled{cursor:not-allowed}.btn{cursor:pointer;justify-content:center;align-items:center;gap:calc(var(--spacing) * 2);border-radius:var(--radius-lg);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;transition-duration:.2s;display:inline-flex}.btn:disabled{cursor:not-allowed;opacity:.5}.btn-primary{background-color:var(--color-gray-900);color:var(--color-white)}@media(hover:hover){.btn-primary:hover{background-color:var(--color-gray-800)}}.dark .btn-primary{background-color:var(--color-white);color:var(--color-gray-900)}@media(hover:hover){.dark .btn-primary:hover{background-color:var(--color-gray-100)}}.btn-secondary{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-300);background-color:var(--color-white);color:var(--color-gray-700)}@media(hover:hover){.btn-secondary:hover{background-color:var(--color-gray-50)}}.dark .btn-secondary{border-color:var(--color-gray-600);background-color:var(--color-gray-800);color:var(--color-gray-300)}@media(hover:hover){.dark .btn-secondary:hover{background-color:var(--color-gray-700)}}.btn-outline{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-300);color:var(--color-gray-700);background-color:#0000}@media(hover:hover){.btn-outline:hover{background-color:var(--color-gray-50)}}.dark .btn-outline{border-color:var(--color-gray-600);color:var(--color-gray-300)}@media(hover:hover){.dark .btn-outline:hover{background-color:var(--color-gray-700)}}.btn-ghost{color:var(--color-gray-600);background-color:#0000}@media(hover:hover){.btn-ghost:hover{background-color:var(--color-gray-100)}}.dark .btn-ghost{color:var(--color-gray-400)}@media(hover:hover){.dark .btn-ghost:hover{background-color:var(--color-gray-700)}}.btn-icon{border-radius:var(--radius-lg);padding:calc(var(--spacing) * 2);color:var(--color-gray-500);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}@media(hover:hover){.btn-icon:hover{background-color:var(--color-gray-100)}}.dark .btn-icon{color:var(--color-gray-400)}@media(hover:hover){.dark .btn-icon:hover{background-color:var(--color-gray-700)}}.btn-preset{background-color:var(--color-gray-100);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 1.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-700);border-radius:3.40282e38px}.dark .btn-preset{background-color:var(--color-gray-700);color:var(--color-gray-300)}.btn-date{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 1.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-600)}.dark .btn-date{border-color:var(--color-gray-700);background-color:var(--color-gray-800);color:var(--color-gray-400)}.btn-date.active{border-color:var(--color-gray-900);background-color:var(--color-gray-900);color:var(--color-white)}.dark .btn-date.active{border-color:var(--color-white);background-color:var(--color-white);color:var(--color-gray-900)}.btn-badge{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 1.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-600);border-radius:3.40282e38px}.dark .btn-badge{border-color:var(--color-gray-700);background-color:var(--color-gray-800);color:var(--color-gray-400)}.btn-badge.active{border-color:var(--color-gray-900);color:var(--color-gray-900)}.dark .btn-badge.active{border-color:var(--color-white);color:var(--color-blue-400)}.btn-group{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);display:inline-flex;overflow:hidden}.dark .btn-group{border-color:var(--color-gray-700)}.btn-group-item{border-right-style:var(--tw-border-style);border-right-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 1.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-600);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}@media(hover:hover){.btn-group-item:hover{background-color:var(--color-gray-50)}}.dark .btn-group-item{border-color:var(--color-gray-700);background-color:var(--color-gray-800);color:var(--color-gray-400)}@media(hover:hover){.dark .btn-group-item:hover{background-color:var(--color-gray-700)}}.btn-group-item:last-child{border-right-style:var(--tw-border-style);border-right-width:0}.btn-group-item.active{background-color:var(--color-gray-900);color:var(--color-white)}.dark .btn-group-item.active{background-color:var(--color-white);color:var(--color-gray-900)}.btn-badge-green{border-color:var(--color-green-500);background-color:var(--color-green-50);color:var(--color-green-600)}.dark .btn-badge-green{border-color:var(--color-green-400);background-color:#0d542b33}@supports (color:color-mix(in lab,red,red)){.dark .btn-badge-green{background-color:color-mix(in oklab,var(--color-green-900) 20%,transparent)}}.dark .btn-badge-green{color:var(--color-green-400)}.btn-badge-blue{border-color:var(--color-blue-500);background-color:var(--color-blue-50);color:var(--color-blue-600)}.dark .btn-badge-blue{border-color:var(--color-blue-400);background-color:#1c398e33}@supports (color:color-mix(in lab,red,red)){.dark .btn-badge-blue{background-color:color-mix(in oklab,var(--color-blue-900) 20%,transparent)}}.dark .btn-badge-blue{color:var(--color-blue-400)}.btn-badge-cyan{border-color:var(--color-cyan-500);background-color:var(--color-cyan-50);color:var(--color-cyan-600)}.dark .btn-badge-cyan{border-color:var(--color-cyan-400);background-color:#104e6433}@supports (color:color-mix(in lab,red,red)){.dark .btn-badge-cyan{background-color:color-mix(in oklab,var(--color-cyan-900) 20%,transparent)}}.dark .btn-badge-cyan{color:var(--color-cyan-400)}.btn-badge-indigo{border-color:var(--color-indigo-500);background-color:var(--color-indigo-50);color:var(--color-indigo-600)}.dark .btn-badge-indigo{border-color:var(--color-indigo-400);background-color:#312c8533}@supports (color:color-mix(in lab,red,red)){.dark .btn-badge-indigo{background-color:color-mix(in oklab,var(--color-indigo-900) 20%,transparent)}}.dark .btn-badge-indigo{color:var(--color-indigo-400)}.btn-badge-pink{border-color:var(--color-pink-500);background-color:var(--color-pink-50);color:var(--color-pink-600)}.dark .btn-badge-pink{border-color:var(--color-pink-400);background-color:#86104333}@supports (color:color-mix(in lab,red,red)){.dark .btn-badge-pink{background-color:color-mix(in oklab,var(--color-pink-900) 20%,transparent)}}.dark .btn-badge-pink{color:var(--color-pink-400)}.btn-badge-red{border-color:var(--color-red-500);background-color:var(--color-red-50);color:var(--color-red-600)}.dark .btn-badge-red{border-color:var(--color-red-400);background-color:#82181a33}@supports (color:color-mix(in lab,red,red)){.dark .btn-badge-red{background-color:color-mix(in oklab,var(--color-red-900) 20%,transparent)}}.dark .btn-badge-red{color:var(--color-red-400)}.btn-badge-gray{border-color:var(--color-gray-300);background-color:var(--color-gray-50);color:var(--color-gray-600)}.dark .btn-badge-gray{border-color:var(--color-gray-600);background-color:var(--color-gray-800);color:var(--color-gray-400)}.btn-badge-yellow{border-color:var(--color-yellow-500);background-color:var(--color-yellow-50);color:var(--color-yellow-600)}.dark .btn-badge-yellow{border-color:var(--color-yellow-400);background-color:#733e0a33}@supports (color:color-mix(in lab,red,red)){.dark .btn-badge-yellow{background-color:color-mix(in oklab,var(--color-yellow-900) 20%,transparent)}}.dark .btn-badge-yellow{color:var(--color-yellow-400)}.btn-badge-orange{border-color:var(--color-orange-500);background-color:var(--color-orange-50);color:var(--color-orange-600)}.dark .btn-badge-orange{border-color:var(--color-orange-400);background-color:#7e2a0c33}@supports (color:color-mix(in lab,red,red)){.dark .btn-badge-orange{background-color:color-mix(in oklab,var(--color-orange-900) 20%,transparent)}}.dark .btn-badge-orange{color:var(--color-orange-400)}.input{height:calc(var(--spacing) * 9);border-radius:var(--radius-lg);background-color:var(--color-gray-100);width:100%;padding-inline:calc(var(--spacing) * 3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900)}.input::placeholder{color:var(--color-gray-400)}.input:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--color-blue-500);--tw-outline-style:none;outline-style:none}.dark .input{background-color:var(--color-gray-700);color:var(--color-gray-100)}.dark .input::placeholder{color:var(--color-gray-500)}.input:disabled{cursor:not-allowed;background-color:var(--color-gray-100);color:var(--color-gray-400)}.dark .input:disabled{background-color:var(--color-gray-800);color:var(--color-gray-500)}.input-tag{border-radius:var(--radius-lg);background-color:var(--color-gray-100);width:100%;padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 1.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900)}.input-tag:focus-within{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--color-blue-500);--tw-outline-style:none;outline-style:none}.dark .input-tag{background-color:var(--color-gray-700);color:var(--color-gray-100)}.input-tag{min-height:2.25rem}.textarea{border-radius:var(--radius-lg);background-color:var(--color-gray-100);width:100%;padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900)}.textarea::placeholder{color:var(--color-gray-400)}.textarea:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--color-blue-500);--tw-outline-style:none;outline-style:none}.dark .textarea{background-color:var(--color-gray-700);color:var(--color-gray-100)}.dark .textarea::placeholder{color:var(--color-gray-500)}.textarea:disabled{cursor:not-allowed;background-color:var(--color-gray-200);color:var(--color-gray-400)}.dark .textarea:disabled{background-color:var(--color-gray-700);color:var(--color-gray-500)}.input-readonly{cursor:default;color:var(--color-gray-500)}.dark .input-readonly{color:var(--color-gray-400)}.input-w-xs{width:calc(var(--spacing) * 20)}.input-w-sm{width:calc(var(--spacing) * 32)}.input-w-md{width:calc(var(--spacing) * 48)}.input-w-lg{width:calc(var(--spacing) * 64)}.input-w-xl{width:calc(var(--spacing) * 80)}.input-w-auto{width:auto}.input-search-container{border-radius:var(--radius-lg);background-color:var(--color-gray-100);align-items:center;display:flex;overflow:hidden}.dark .input-search-container{background-color:var(--color-gray-700)}.input-search-container .input-search-icon{padding-left:calc(var(--spacing) * 3);color:var(--color-gray-400)}.dark :is(.input-search-container .input-search-icon){color:var(--color-gray-500)}.input-search-container input{padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900);background-color:#0000;flex:1}.input-search-container input::placeholder{color:var(--color-gray-400)}.input-search-container input:focus{--tw-outline-style:none;outline-style:none}.dark :is(.input-search-container input){color:var(--color-gray-100)}.dark :is(.input-search-container input)::placeholder{color:var(--color-gray-500)}.input-search-container .input-search-dropdown{cursor:pointer;border-left-style:var(--tw-border-style);border-left-width:1px;border-color:var(--color-gray-200);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-600)}@media(hover:hover){.input-search-container .input-search-dropdown:hover{background-color:var(--color-gray-50)}}.dark :is(.input-search-container .input-search-dropdown){border-color:var(--color-gray-700);color:var(--color-gray-400)}@media(hover:hover){.dark :is(.input-search-container .input-search-dropdown):hover{background-color:var(--color-gray-700)}}.select{height:calc(var(--spacing) * 9);cursor:pointer;appearance:none;border-radius:var(--radius-lg);background-color:var(--color-gray-100);width:100%;padding-inline:calc(var(--spacing) * 3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900)}.select:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--color-blue-500);--tw-outline-style:none;outline-style:none}.dark .select{background-color:var(--color-gray-700);color:var(--color-gray-100)}.select:disabled{cursor:not-allowed;background-color:var(--color-gray-200);color:var(--color-gray-400)}.dark .select:disabled{background-color:var(--color-gray-700);color:var(--color-gray-500)}.select{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");background-position:right .5rem center;background-repeat:no-repeat;background-size:1.5em 1.5em;padding-right:2.5rem}.dark .select{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%239ca3af' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e")}.select-composite{height:calc(var(--spacing) * 9);border-radius:var(--radius-lg);background-color:var(--color-gray-100);padding-inline:calc(var(--spacing) * 3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900)}.select-composite:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--color-blue-500);--tw-outline-style:none;outline-style:none}.dark .select-composite{background-color:var(--color-gray-700);color:var(--color-gray-100)}.select-composite:disabled{cursor:not-allowed;background-color:var(--color-gray-200);color:var(--color-gray-400)}.dark .select-composite:disabled{background-color:var(--color-gray-700);color:var(--color-gray-500)}.select-composite-sm{height:calc(var(--spacing) * 8);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.select-w-sm{width:calc(var(--spacing) * 32)}.select-w-md{width:calc(var(--spacing) * 48)}.select-w-lg{width:calc(var(--spacing) * 64)}.input-split{align-items:center;gap:calc(var(--spacing) * 2);display:flex}.input-split input{border-radius:var(--radius-lg);background-color:var(--color-gray-100);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2);text-align:center;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900)}.input-split input:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--color-blue-500);--tw-outline-style:none;outline-style:none}.dark :is(.input-split input){background-color:var(--color-gray-700);color:var(--color-gray-100)}.input-split .input-split-separator{color:var(--color-gray-400)}.dark :is(.input-split .input-split-separator){color:var(--color-gray-500)}.tag-input-container{align-items:center;gap:calc(var(--spacing) * 2);flex-wrap:wrap;display:flex}.tag{align-items:center;gap:var(--spacing);background-color:var(--color-gray-100);padding-inline:calc(var(--spacing) * 3);padding-block:var(--spacing);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-700);border-radius:3.40282e38px;display:inline-flex}.dark .tag{background-color:var(--color-gray-700);color:var(--color-gray-300)}.tag .tag-remove{cursor:pointer;color:var(--color-gray-400)}@media(hover:hover){.tag .tag-remove:hover{color:var(--color-gray-600)}}.dark :is(.tag .tag-remove){color:var(--color-gray-500)}@media(hover:hover){.dark :is(.tag .tag-remove):hover{color:var(--color-gray-300)}}:where(.form-group>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 1.5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 1.5) * calc(1 - var(--tw-space-y-reverse)))}.textarea-sm{min-height:5rem}.textarea-md{min-height:8rem}.textarea-lg{min-height:12rem}.input-error{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-red-500)}.input-error:focus{--tw-ring-color:var(--color-red-500)}.dark .input-error{border-color:var(--color-red-500)}.input-success{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-green-500)}.input-success:focus{--tw-ring-color:var(--color-green-500)}.dark .input-success{border-color:var(--color-green-500)}.form-field-error .input,.form-field-error input[type],.form-field-error select,.form-field-error textarea{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-red-500)}:is(.form-field-error .input,.form-field-error input[type],.form-field-error select,.form-field-error textarea):focus{--tw-ring-color:var(--color-red-500)}.dark :is(.form-field-error .input,.form-field-error input[type],.form-field-error select,.form-field-error textarea){border-color:var(--color-red-500)}.form-error{margin-top:var(--spacing);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-red-500);display:block}.dark .form-error{color:var(--color-red-400)}.form-success{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-green-600)}.dark .form-success{color:var(--color-green-400)}.checkbox{height:calc(var(--spacing) * 4);width:calc(var(--spacing) * 4);cursor:pointer;border-color:var(--color-gray-300);color:var(--color-gray-900);border-radius:.25rem}.checkbox:focus{--tw-ring-color:var(--color-gray-500)}.dark .checkbox{border-color:var(--color-gray-600);background-color:var(--color-gray-800);color:var(--color-blue-400)}.checkbox:checked{border-color:var(--color-gray-900);background-color:var(--color-gray-900)}.dark .checkbox:checked{border-color:var(--color-white);background-color:var(--color-white)}.radio{height:calc(var(--spacing) * 4);width:calc(var(--spacing) * 4);cursor:pointer;border-color:var(--color-gray-300);color:var(--color-blue-600);flex-shrink:0}.radio:focus{--tw-ring-color:var(--color-blue-500)}.dark .radio{border-color:var(--color-gray-600);background-color:var(--color-gray-800);color:var(--color-blue-400)}.dark .radio:focus{--tw-ring-color:var(--color-blue-400)}.radio:checked{border-color:var(--color-blue-600)}.dark .radio:checked{border-color:var(--color-blue-400)}.toggle{height:calc(var(--spacing) * 6);width:calc(var(--spacing) * 11);cursor:pointer;transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));border-radius:3.40282e38px;align-items:center;display:inline-flex;position:relative}.toggle-track{background-color:var(--color-gray-200);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));border-radius:3.40282e38px;position:absolute;inset:0}.dark .toggle-track{background-color:var(--color-gray-700)}.toggle.active .toggle-track{background-color:var(--color-blue-600)}.toggle-handle{left:calc(var(--spacing) * .5);height:calc(var(--spacing) * 5);width:calc(var(--spacing) * 5);background-color:var(--color-white);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));border-radius:3.40282e38px;position:absolute}.toggle.active .toggle-handle{--tw-translate-x:calc(var(--spacing) * 5);translate:var(--tw-translate-x) var(--tw-translate-y)}.badge{align-items:center;gap:var(--spacing);border-radius:var(--radius-md);padding-inline:calc(var(--spacing) * 2);padding-block:calc(var(--spacing) * .5);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);display:inline-flex}.badge-green{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-green-200);background-color:var(--color-green-100);color:var(--color-green-700)}.dark .badge-green{border-color:var(--color-green-800);background-color:#0d542b4d}@supports (color:color-mix(in lab,red,red)){.dark .badge-green{background-color:color-mix(in oklab,var(--color-green-900) 30%,transparent)}}.dark .badge-green{color:var(--color-green-400)}.badge-blue{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-blue-200);background-color:var(--color-blue-100);color:var(--color-blue-700)}.dark .badge-blue{border-color:var(--color-blue-800);background-color:#1c398e4d}@supports (color:color-mix(in lab,red,red)){.dark .badge-blue{background-color:color-mix(in oklab,var(--color-blue-900) 30%,transparent)}}.dark .badge-blue{color:var(--color-blue-400)}.badge-cyan{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-cyan-200);background-color:var(--color-cyan-100);color:var(--color-cyan-700)}.dark .badge-cyan{border-color:var(--color-cyan-800);background-color:#104e644d}@supports (color:color-mix(in lab,red,red)){.dark .badge-cyan{background-color:color-mix(in oklab,var(--color-cyan-900) 30%,transparent)}}.dark .badge-cyan{color:var(--color-cyan-400)}.badge-indigo{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-indigo-200);background-color:var(--color-indigo-100);color:var(--color-indigo-700)}.dark .badge-indigo{border-color:var(--color-indigo-800);background-color:#312c854d}@supports (color:color-mix(in lab,red,red)){.dark .badge-indigo{background-color:color-mix(in oklab,var(--color-indigo-900) 30%,transparent)}}.dark .badge-indigo{color:var(--color-indigo-400)}.badge-pink{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-pink-200);background-color:var(--color-pink-100);color:var(--color-pink-700)}.dark .badge-pink{border-color:var(--color-pink-800);background-color:#8610434d}@supports (color:color-mix(in lab,red,red)){.dark .badge-pink{background-color:color-mix(in oklab,var(--color-pink-900) 30%,transparent)}}.dark .badge-pink{color:var(--color-pink-400)}.badge-red{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-red-200);background-color:var(--color-red-100);color:var(--color-red-700)}.dark .badge-red{border-color:var(--color-red-800);background-color:#82181a4d}@supports (color:color-mix(in lab,red,red)){.dark .badge-red{background-color:color-mix(in oklab,var(--color-red-900) 30%,transparent)}}.dark .badge-red{color:var(--color-red-400)}.badge-gray{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-gray-100);color:var(--color-gray-600)}.dark .badge-gray{border-color:var(--color-gray-700);background-color:var(--color-gray-800);color:var(--color-gray-400)}.badge-yellow{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-yellow-200);background-color:var(--color-yellow-100);color:var(--color-yellow-700)}.dark .badge-yellow{border-color:var(--color-yellow-800);background-color:#733e0a4d}@supports (color:color-mix(in lab,red,red)){.dark .badge-yellow{background-color:color-mix(in oklab,var(--color-yellow-900) 30%,transparent)}}.dark .badge-yellow{color:var(--color-yellow-400)}.badge-status{align-items:center;gap:calc(var(--spacing) * 1.5);padding-inline:calc(var(--spacing) * 2.5);padding-block:var(--spacing);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);border-radius:3.40282e38px;display:inline-flex}.badge-status-success{background-color:var(--color-green-50);color:var(--color-green-600)}.dark .badge-status-success{background-color:#0d542b33}@supports (color:color-mix(in lab,red,red)){.dark .badge-status-success{background-color:color-mix(in oklab,var(--color-green-900) 20%,transparent)}}.dark .badge-status-success{color:var(--color-green-400)}.badge-status-processing{background-color:var(--color-blue-50);color:var(--color-blue-600)}.dark .badge-status-processing{background-color:#1c398e33}@supports (color:color-mix(in lab,red,red)){.dark .badge-status-processing{background-color:color-mix(in oklab,var(--color-blue-900) 20%,transparent)}}.dark .badge-status-processing{color:var(--color-blue-400)}.badge-status-error{background-color:var(--color-red-50);color:var(--color-red-600)}.dark .badge-status-error{background-color:#82181a33}@supports (color:color-mix(in lab,red,red)){.dark .badge-status-error{background-color:color-mix(in oklab,var(--color-red-900) 20%,transparent)}}.dark .badge-status-error{color:var(--color-red-400)}.badge-status-expired{background-color:var(--color-gray-50);color:var(--color-gray-500)}.dark .badge-status-expired{background-color:var(--color-gray-800);color:var(--color-gray-400)}.search-bar{gap:calc(var(--spacing) * 3);flex-direction:column;display:flex}@media(min-width:48rem){.search-bar{flex-flow:wrap;align-items:center}}.search-bar-select{height:calc(var(--spacing) * 9);border-radius:var(--radius-lg);background-color:var(--color-gray-100);width:100%;padding-inline:calc(var(--spacing) * 3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900)}.search-bar-select::placeholder{color:var(--color-gray-400)}.search-bar-select:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--color-blue-500);--tw-outline-style:none;outline-style:none}.dark .search-bar-select{background-color:var(--color-gray-700);color:var(--color-gray-100)}.dark .search-bar-select::placeholder{color:var(--color-gray-500)}@media(min-width:48rem){.search-bar-select{width:calc(var(--spacing) * 32);flex-shrink:0}}.search-bar-select-custom{width:100%}@media(min-width:48rem){.search-bar-select-custom{width:calc(var(--spacing) * 32);flex-shrink:0}}.search-bar-input{height:calc(var(--spacing) * 9);border-radius:var(--radius-lg);background-color:var(--color-gray-100);width:100%;padding-inline:calc(var(--spacing) * 3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900)}.search-bar-input::placeholder{color:var(--color-gray-400)}.search-bar-input:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--color-blue-500);--tw-outline-style:none;outline-style:none}.dark .search-bar-input{background-color:var(--color-gray-700);color:var(--color-gray-100)}.dark .search-bar-input::placeholder{color:var(--color-gray-500)}@media(min-width:48rem){.search-bar-input{min-width:calc(var(--spacing) * 64);flex:1}}.search-bar-reset{cursor:pointer;justify-content:center;align-items:center;gap:calc(var(--spacing) * 2);border-radius:var(--radius-lg);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-300);color:var(--color-gray-700);background-color:#0000;transition-duration:.2s;display:inline-flex}@media(hover:hover){.search-bar-reset:hover{background-color:var(--color-gray-50)}}.dark .search-bar-reset{border-color:var(--color-gray-600);color:var(--color-gray-300)}@media(hover:hover){.dark .search-bar-reset:hover{background-color:var(--color-gray-700)}}.search-bar-reset{gap:calc(var(--spacing) * 1.5);flex-shrink:0;width:100%}@media(min-width:48rem){.search-bar-reset{width:auto}}.filter-grid{gap:calc(var(--spacing) * 4);flex-direction:column;display:flex}.filter-row{gap:calc(var(--spacing) * 2);flex-direction:column;display:flex}@media(min-width:48rem){.filter-row{flex-direction:row;align-items:center}}.filter-label{margin-bottom:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-600);display:block}.dark .filter-label{color:var(--color-gray-400)}@media(min-width:48rem){.filter-label{width:calc(var(--spacing) * 14);flex-shrink:0;align-items:center;margin-bottom:0;display:flex}}.filter-buttons{align-items:center;gap:calc(var(--spacing) * 2);flex-wrap:wrap;display:flex}.dropdown{display:inline-block;position:relative}.dropdown-trigger{border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-300);background-color:var(--color-white);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}@media(hover:hover){.dropdown-trigger:hover{background-color:var(--color-gray-50)}}.dropdown-trigger:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--color-blue-500);--tw-outline-style:none;outline-style:none}.dropdown-trigger{color:var(--color-gray-900)}.dark .dropdown-trigger{border-color:var(--color-gray-600);background-color:var(--color-gray-800);color:var(--color-white)}@media(hover:hover){.dark .dropdown-trigger:hover{background-color:var(--color-gray-700)}}.dark .dropdown-trigger:focus{--tw-ring-color:var(--color-blue-400)}.dropdown-trigger-text{cursor:pointer;--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-900);align-items:center;display:inline-flex}@media(hover:hover){.dropdown-trigger-text:hover{color:var(--color-blue-600)}}.dark .dropdown-trigger-text{color:var(--color-white)}@media(hover:hover){.dark .dropdown-trigger-text:hover{color:var(--color-blue-400)}}.dropdown-menu{z-index:50;width:calc(var(--spacing) * 56);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);position:absolute}.dark .dropdown-menu{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}.dropdown-menu-bottom-left{margin-top:calc(var(--spacing) * 2);top:100%;left:0}.dropdown-menu-bottom-right{margin-top:calc(var(--spacing) * 2);top:100%;right:0}.dropdown-menu-top-left{margin-bottom:calc(var(--spacing) * 2);bottom:100%;left:0}.dropdown-menu-top-right{margin-bottom:calc(var(--spacing) * 2);bottom:100%;right:0}.dropdown-item{cursor:pointer;padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-700)}@media(hover:hover){.dropdown-item:hover{background-color:var(--color-gray-100)}}.dark .dropdown-item{color:var(--color-gray-300)}@media(hover:hover){.dark .dropdown-item:hover{background-color:var(--color-gray-700)}}.dropdown-item-icon{color:var(--color-gray-400)}.dark .dropdown-item-icon{color:var(--color-gray-500)}.dropdown-item-disabled{cursor:not-allowed;padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-400)}.dark .dropdown-item-disabled{color:var(--color-gray-500)}.dropdown-item-focused{background-color:var(--color-gray-100)}.dark .dropdown-item-focused{background-color:var(--color-gray-700)}.dropdown-divider{margin-block:var(--spacing);border-top-style:var(--tw-border-style);border-top-width:1px;border-color:var(--color-gray-100)}.dark .dropdown-divider{border-color:var(--color-gray-700)}.pagination{justify-content:center;align-items:center;gap:calc(var(--spacing) * 2);flex-wrap:wrap;display:flex}.pagination-btn{border-style:var(--tw-border-style);padding-inline:calc(var(--spacing) * 3);padding-block:var(--spacing);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));border-width:1px;border-color:var(--color-gray-300);background-color:var(--color-white);color:var(--color-gray-700);border-radius:.25rem}@media(hover:hover){.pagination-btn:hover{background-color:var(--color-gray-100)}}.pagination-btn:disabled{cursor:not-allowed;opacity:.5}.dark .pagination-btn{border-color:var(--color-gray-600);background-color:var(--color-gray-800);color:var(--color-gray-300)}@media(hover:hover){.dark .pagination-btn:hover{background-color:var(--color-gray-700)}}.pagination-btn-active{border-style:var(--tw-border-style);padding-inline:calc(var(--spacing) * 3);padding-block:var(--spacing);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));border-width:1px;border-color:var(--color-blue-500);background-color:var(--color-blue-500);color:var(--color-white);border-radius:.25rem}.dark .pagination-btn-active{border-color:var(--color-blue-600);background-color:var(--color-blue-600)}.pagination-ellipsis{padding-inline:calc(var(--spacing) * 3);padding-block:var(--spacing);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-500)}.dark .pagination-ellipsis{color:var(--color-gray-400)}.dialog-header{margin-bottom:calc(var(--spacing) * 4);justify-content:space-between;align-items:center;display:flex}.dialog-title{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-gray-900)}.dark .dialog-title{color:var(--color-white)}.dialog-close{color:var(--color-gray-400)}@media(hover:hover){.dialog-close:hover{color:var(--color-gray-600)}}.dialog-close:focus{--tw-outline-style:none;outline-style:none}.dark .dialog-close{color:var(--color-gray-500)}@media(hover:hover){.dark .dialog-close:hover{color:var(--color-gray-300)}}.dialog-body{margin-bottom:calc(var(--spacing) * 6);color:var(--color-gray-700)}.dark .dialog-body{color:var(--color-gray-300)}.dialog-footer{justify-content:flex-end;gap:calc(var(--spacing) * 3);display:flex}.dialog-btn-primary{border-radius:var(--radius-md);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.dialog-btn-primary:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-outline-style:none;outline-style:none}.dialog-btn-primary:disabled{cursor:not-allowed;opacity:.5}.dialog-btn-primary{background-color:var(--color-blue-600);color:var(--color-white)}@media(hover:hover){.dialog-btn-primary:hover{background-color:var(--color-blue-700)}}.dialog-btn-primary:focus{--tw-ring-color:var(--color-blue-500)}.dialog-btn-secondary{border-radius:var(--radius-md);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.dialog-btn-secondary:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-outline-style:none;outline-style:none}.dialog-btn-secondary:disabled{cursor:not-allowed;opacity:.5}.dialog-btn-secondary{background-color:var(--color-gray-200);color:var(--color-gray-800)}@media(hover:hover){.dialog-btn-secondary:hover{background-color:var(--color-gray-300)}}.dialog-btn-secondary:focus{--tw-ring-color:var(--color-gray-500)}.dark .dialog-btn-secondary{background-color:var(--color-gray-700);color:var(--color-gray-200)}@media(hover:hover){.dark .dialog-btn-secondary:hover{background-color:var(--color-gray-600)}}.dialog-btn-danger{border-radius:var(--radius-md);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.dialog-btn-danger:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-outline-style:none;outline-style:none}.dialog-btn-danger:disabled{cursor:not-allowed;opacity:.5}.dialog-btn-danger{background-color:var(--color-red-600);color:var(--color-white)}@media(hover:hover){.dialog-btn-danger:hover{background-color:var(--color-red-700)}}.dialog-btn-danger:focus{--tw-ring-color:var(--color-red-500)}.dialog-btn-ghost{border-radius:var(--radius-md);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.dialog-btn-ghost:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-outline-style:none;outline-style:none}.dialog-btn-ghost:disabled{cursor:not-allowed;opacity:.5}.dialog-btn-ghost{color:var(--color-gray-700);background-color:#0000}@media(hover:hover){.dialog-btn-ghost:hover{background-color:var(--color-gray-100)}}.dialog-btn-ghost:focus{--tw-ring-color:var(--color-gray-500)}.dark .dialog-btn-ghost{color:var(--color-gray-300)}@media(hover:hover){.dark .dialog-btn-ghost:hover{background-color:var(--color-gray-700)}}.alert-error{align-items:center;gap:calc(var(--spacing) * 3);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-red-200);background-color:var(--color-red-50);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-red-900);display:flex}.dark .alert-error{border-color:var(--color-red-800);background-color:#82181a33}@supports (color:color-mix(in lab,red,red)){.dark .alert-error{background-color:color-mix(in oklab,var(--color-red-900) 20%,transparent)}}.dark .alert-error{color:var(--color-red-100)}.alert-icon-info{margin-top:calc(var(--spacing) * .5);color:var(--color-blue-600);flex-shrink:0}.dark .alert-icon-info{color:var(--color-blue-400)}.alert-icon-success{margin-top:calc(var(--spacing) * .5);color:var(--color-green-600);flex-shrink:0}.dark .alert-icon-success{color:var(--color-green-400)}.alert-icon-warning{margin-top:calc(var(--spacing) * .5);color:var(--color-yellow-600);flex-shrink:0}.dark .alert-icon-warning{color:var(--color-yellow-400)}.alert-icon-error{margin-top:calc(var(--spacing) * .5);color:var(--color-red-600);flex-shrink:0}.dark .alert-icon-error{color:var(--color-red-400)}.alert-text-info{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-blue-800);flex:1}.dark .alert-text-info{color:var(--color-blue-200)}.alert-text-success{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-green-800);flex:1}.dark .alert-text-success{color:var(--color-green-200)}.alert-text-warning{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-yellow-800);flex:1}.dark .alert-text-warning{color:var(--color-yellow-200)}.alert-text-error{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-red-800);flex:1}.dark .alert-text-error{color:var(--color-red-200)}.alert-dismiss{cursor:pointer;border-style:var(--tw-border-style);background-color:#0000;border-width:0;flex-shrink:0;padding:0}@media(hover:hover){.alert-dismiss:hover{opacity:.7}}.table-container{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);overflow:hidden}.dark .table-container{border-color:var(--color-gray-700)}.table{width:100%}.table thead{background-color:var(--color-gray-50)}.dark :is(.table thead){background-color:var(--color-gray-800)}.table th{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-200);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3);text-align:left;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-600)}.dark :is(.table th){border-color:var(--color-gray-700);color:var(--color-gray-400)}.table th.sortable{cursor:pointer}@media(hover:hover){.table th.sortable:hover{background-color:var(--color-gray-100)}.dark :is(.table th.sortable):hover{background-color:var(--color-gray-700)}}.table tbody tr{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-100);background-color:var(--color-white)}.dark :is(.table tbody tr){border-color:var(--color-gray-800);background-color:var(--color-gray-900)}.table tbody tr:hover{background-color:var(--color-gray-50)}.dark :is(.table tbody tr:hover){background-color:var(--color-gray-800)}.table tbody tr.selected{background-color:var(--color-blue-50)}.dark :is(.table tbody tr.selected){background-color:#1c398e33}@supports (color:color-mix(in lab,red,red)){.dark :is(.table tbody tr.selected){background-color:color-mix(in oklab,var(--color-blue-900) 20%,transparent)}}.table td{padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900)}.dark :is(.table td){color:var(--color-gray-100)}.table-actions{border-top-style:var(--tw-border-style);border-top-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3);justify-content:space-between;align-items:center;display:flex}.dark .table-actions{border-color:var(--color-gray-700);background-color:var(--color-gray-900)}.log-table{width:100%}.log-table th{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-200);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3);text-align:left;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-500)}.dark :is(.log-table th){border-color:var(--color-gray-700);color:var(--color-gray-400)}.log-table td{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-100);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900)}.dark :is(.log-table td){border-color:var(--color-gray-800);color:var(--color-gray-100)}.log-table tbody tr:last-child td{border-bottom-style:var(--tw-border-style);border-bottom-width:0}.log-table-actor{border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-gray-100);padding-inline:calc(var(--spacing) * 2.5);padding-block:var(--spacing);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-700);align-items:center;display:inline-flex}.dark .log-table-actor{border-color:var(--color-gray-600);background-color:var(--color-gray-700);color:var(--color-gray-300)}.log-card{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);padding:calc(var(--spacing) * 4)}.dark .log-card{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}.log-card-header{margin-bottom:calc(var(--spacing) * 2);justify-content:space-between;align-items:center;gap:calc(var(--spacing) * 3);display:flex}.log-card-body{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900)}.dark .log-card-body{color:var(--color-gray-100)}.modal-overlay{z-index:50;background-color:#00000080;justify-content:center;align-items:center;display:flex;position:fixed;inset:0}@supports (color:color-mix(in lab,red,red)){.modal-overlay{background-color:color-mix(in oklab,var(--color-black) 50%,transparent)}}.modal-wrapper{justify-content:center;align-items:center;display:flex;position:fixed;inset:0}.modal-backdrop{background-color:#00000080;position:absolute;inset:0}@supports (color:color-mix(in lab,red,red)){.modal-backdrop{background-color:color-mix(in oklab,var(--color-black) 50%,transparent)}}.modal-container{border-radius:var(--radius-lg);background-color:var(--color-white);--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a), 0 8px 10px -6px var(--tw-shadow-color,#0000001a);max-width:calc(100vw - 2rem);max-height:90vh;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);position:relative;overflow:hidden}.dark .modal-container{background-color:var(--color-gray-800)}.modal-header-row{padding:calc(var(--spacing) * 4);justify-content:space-between;align-items:center;display:flex}.modal-title{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-gray-900)}.dark .modal-title{color:var(--color-white)}.modal-close-btn{color:var(--color-gray-400);margin-left:auto}@media(hover:hover){.modal-close-btn:hover{color:var(--color-gray-600)}}.modal-close-btn:focus{--tw-outline-style:none;outline-style:none}.dark .modal-close-btn{color:var(--color-gray-500)}@media(hover:hover){.dark .modal-close-btn:hover{color:var(--color-gray-300)}}.modal-body-content{max-height:calc(90vh - 140px);padding-inline:calc(var(--spacing) * 6);padding-block:calc(var(--spacing) * 4);overflow-y:auto}.modal{margin-inline:calc(var(--spacing) * 4);width:100%;max-width:var(--container-md);border-radius:var(--radius-xl);background-color:var(--color-white);padding:calc(var(--spacing) * 6);--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a), 0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.dark .modal{background-color:var(--color-gray-800)}.modal-lg{max-width:var(--container-lg)}.modal-xl{max-width:var(--container-xl)}.modal-header{margin-bottom:calc(var(--spacing) * 4);justify-content:space-between;align-items:flex-start;display:flex}.modal-close{cursor:pointer;border-radius:var(--radius-lg);padding:var(--spacing);color:var(--color-gray-400)}@media(hover:hover){.modal-close:hover{background-color:var(--color-gray-100);color:var(--color-gray-600)}}.dark .modal-close{color:var(--color-gray-500)}@media(hover:hover){.dark .modal-close:hover{background-color:var(--color-gray-700);color:var(--color-gray-300)}}.modal-body{padding-block:calc(var(--spacing) * 4)}.modal-footer{margin-top:calc(var(--spacing) * 6);justify-content:flex-end;gap:calc(var(--spacing) * 3);display:flex}.danger-confirm-icon{margin-inline:auto;margin-bottom:calc(var(--spacing) * 4);height:calc(var(--spacing) * 12);width:calc(var(--spacing) * 12);background-color:var(--color-red-100);border-radius:3.40282e38px;justify-content:center;align-items:center;display:flex}.dark .danger-confirm-icon{background-color:#82181a4d}@supports (color:color-mix(in lab,red,red)){.dark .danger-confirm-icon{background-color:color-mix(in oklab,var(--color-red-900) 30%,transparent)}}.danger-confirm-icon svg,.danger-confirm-icon .icon{height:calc(var(--spacing) * 6);width:calc(var(--spacing) * 6);color:var(--color-red-600)}.dark :is(.danger-confirm-icon svg,.danger-confirm-icon .icon){color:var(--color-red-400)}.danger-confirm-title{margin-bottom:calc(var(--spacing) * 2);text-align:center;font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-gray-900)}.dark .danger-confirm-title{color:var(--color-white)}.danger-confirm-message{margin-bottom:calc(var(--spacing) * 4);text-align:center;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-600)}.dark .danger-confirm-message{color:var(--color-gray-400)}.danger-confirm-warning{margin-bottom:calc(var(--spacing) * 4);border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-yellow-200);background-color:var(--color-yellow-50);padding:calc(var(--spacing) * 4)}.dark .danger-confirm-warning{border-color:var(--color-yellow-700);background-color:#733e0a33}@supports (color:color-mix(in lab,red,red)){.dark .danger-confirm-warning{background-color:color-mix(in oklab,var(--color-yellow-900) 20%,transparent)}}.danger-confirm-warning-title{margin-bottom:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-yellow-800)}.dark .danger-confirm-warning-title{color:var(--color-yellow-300)}.danger-confirm-warning-list{list-style-type:disc;list-style-position:inside}:where(.danger-confirm-warning-list>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(var(--spacing) * var(--tw-space-y-reverse));margin-block-end:calc(var(--spacing) * calc(1 - var(--tw-space-y-reverse)))}.danger-confirm-warning-list{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-yellow-700)}.dark .danger-confirm-warning-list{color:var(--color-yellow-400)}.danger-confirm-alert{text-align:center;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-red-600)}.dark .danger-confirm-alert{color:var(--color-red-400)}.danger-confirm-footer{margin-top:calc(var(--spacing) * 6);justify-content:center;gap:calc(var(--spacing) * 3);display:flex}.card{border-radius:var(--radius-xl);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);width:100%;padding:calc(var(--spacing) * 6)}.dark .card{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}.admin-card{margin-bottom:calc(var(--spacing) * 6);border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);padding:calc(var(--spacing) * 4)}.dark .admin-card{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}.admin-card{flex-direction:column;display:flex}.admin-card-clickable{margin-bottom:calc(var(--spacing) * 2);cursor:pointer;border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2.5)}@media(hover:hover){.admin-card-clickable:hover{border-color:var(--color-gray-300)}}.dark .admin-card-clickable{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}@media(hover:hover){.dark .admin-card-clickable:hover{border-color:var(--color-gray-600)}}.admin-card-selected{margin-bottom:calc(var(--spacing) * 2);cursor:pointer;border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-blue-500);background-color:var(--color-blue-50);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2.5)}.dark .admin-card-selected{border-color:var(--color-blue-500);background-color:#1c398e4d}@supports (color:color-mix(in lab,red,red)){.dark .admin-card-selected{background-color:color-mix(in oklab,var(--color-blue-900) 30%,transparent)}}.card-clickable{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a), 0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);transition-property:box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));overflow:hidden}@media(hover:hover){.card-clickable:hover{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}}.dark .card-clickable{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}.card-image{height:calc(var(--spacing) * 48);object-fit:cover;width:100%}.card-content{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));white-space:pre-line;color:var(--color-gray-600)}.dark .card-content{color:var(--color-gray-300)}.card-section-title{margin-inline:calc(var(--spacing) * -6);margin-bottom:calc(var(--spacing) * 6);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-200);padding-inline:calc(var(--spacing) * 6);padding-bottom:calc(var(--spacing) * 4);font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-gray-900)}.dark .card-section-title{border-color:var(--color-gray-700);color:var(--color-white)}.card-account{border-radius:var(--radius-lg);background-color:var(--color-gray-50);padding:calc(var(--spacing) * 4);position:relative}.dark .card-account{background-color:var(--color-gray-900)}.card-account-default{top:calc(var(--spacing) * 3);left:calc(var(--spacing) * 3);position:absolute}.card-account-delete{top:calc(var(--spacing) * 3);right:calc(var(--spacing) * 3);position:absolute}.admin-card-accordion{margin-bottom:calc(var(--spacing) * 6);border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);padding:calc(var(--spacing) * 4)}.dark .admin-card-accordion{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}.admin-card-accordion{flex-direction:column;display:flex}.admin-card-accordion>.card-header{cursor:pointer}.admin-card-accordion-icon{height:calc(var(--spacing) * 5);width:calc(var(--spacing) * 5);color:var(--color-gray-400);transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;margin-left:auto;transition-duration:.2s}.dark .admin-card-accordion-icon{color:var(--color-gray-500)}.admin-card-accordion>.row-stack{margin-top:calc(var(--spacing) * 6)}.admin-card-accordion-icon.open{rotate:180deg}.tabs{border-radius:var(--radius-lg);background-color:var(--color-gray-100);padding:var(--spacing);display:inline-flex}.dark .tabs{background-color:var(--color-gray-800)}.tab{cursor:pointer;border-radius:var(--radius-md);padding-inline:calc(var(--spacing) * 6);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-500);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}@media(hover:hover){.tab:hover{color:var(--color-gray-700)}}.dark .tab{color:var(--color-gray-400)}@media(hover:hover){.dark .tab:hover{color:var(--color-gray-300)}}.tab.active{background-color:var(--color-white);color:var(--color-gray-900);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.dark .tab.active{background-color:var(--color-gray-700);color:var(--color-white)}.toast{right:calc(var(--spacing) * 4);bottom:calc(var(--spacing) * 4);z-index:50;position:fixed}.toast-item{align-items:center;gap:calc(var(--spacing) * 3);border-radius:var(--radius-lg);background-color:var(--color-gray-900);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3);color:var(--color-white);--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);display:flex}.dark .toast-item{background-color:var(--color-white);color:var(--color-gray-900)}.toast-icon{flex-shrink:0}.drag-item{align-items:center;gap:calc(var(--spacing) * 4);border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);padding:calc(var(--spacing) * 4);display:flex}@media(hover:hover){.drag-item:hover{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}}.dark .drag-item{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}.drag-handle{cursor:grab;color:var(--color-gray-400)}.dark .drag-handle{color:var(--color-gray-500)}.drag-handle:active{cursor:grabbing}.table-mobile-cards{display:none}.excel-card{border-radius:var(--radius-xl);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);padding:calc(var(--spacing) * 4)}.dark .excel-card{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}.excel-card-header{margin-bottom:calc(var(--spacing) * 4);justify-content:space-between;align-items:flex-start;gap:calc(var(--spacing) * 3);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-100);padding-bottom:calc(var(--spacing) * 3);display:flex}.dark .excel-card-header{border-color:var(--color-gray-700)}.excel-card-title{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-gray-900)}.dark .excel-card-title{color:var(--color-white)}:where(.excel-card-body>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)))}.excel-card-row{padding-block:var(--spacing);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));justify-content:space-between;align-items:center;display:flex}.excel-card-label{color:var(--color-gray-500);flex-shrink:0}.dark .excel-card-label{color:var(--color-gray-400)}.excel-card-value{text-align:right;color:var(--color-gray-900)}.dark .excel-card-value{color:var(--color-white)}.excel-card-footer{margin-top:calc(var(--spacing) * 4);border-top-style:var(--tw-border-style);border-top-width:1px;border-color:var(--color-gray-100);padding-top:calc(var(--spacing) * 3);justify-content:space-between;align-items:center;display:flex}.dark .excel-card-footer{border-color:var(--color-gray-700)}.excel-card-creator{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-700)}.dark .excel-card-creator{color:var(--color-gray-300)}.excel-card-creator-label{margin-right:calc(var(--spacing) * 2);color:var(--color-gray-500)}.dark .excel-card-creator-label{color:var(--color-gray-400)}.select-custom{display:inline-block;position:relative}.select-trigger{height:calc(var(--spacing) * 9);border-radius:var(--radius-xl);background-color:var(--color-gray-100);width:100%;padding-inline:calc(var(--spacing) * 4);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-700);cursor:pointer;justify-content:space-between;align-items:center;gap:calc(var(--spacing) * 2);border-style:var(--tw-border-style);border-width:0;display:flex}.select-trigger:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--color-blue-500);--tw-outline-style:none;outline-style:none}.dark .select-trigger{background-color:var(--color-gray-700);color:var(--color-gray-200)}.select-trigger:disabled{cursor:not-allowed;opacity:.5}.select-trigger span{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.select-trigger-icon{height:calc(var(--spacing) * 4);width:calc(var(--spacing) * 4);color:var(--color-gray-400);transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;flex-shrink:0;transition-duration:.2s}.dark .select-trigger-icon{color:var(--color-gray-500)}.select-trigger-icon.open{rotate:180deg}.select-dropdown{z-index:50;margin-top:calc(var(--spacing) * 2);border-radius:var(--radius-2xl);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);width:100%;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);position:absolute;overflow:hidden}.dark .select-dropdown{border-color:var(--color-gray-600);background-color:var(--color-gray-800)}.select-dropdown.hidden{display:none}.select-dropdown-inner{max-height:calc(var(--spacing) * 60);padding-block:calc(var(--spacing) * 2);overflow:auto}.select-option{cursor:pointer;width:100%;padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3);text-align:left;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-700);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));align-items:center;display:flex}@media(hover:hover){.select-option:hover{background-color:var(--color-gray-100)}}.dark .select-option{color:var(--color-gray-200)}@media(hover:hover){.dark .select-option:hover{background-color:var(--color-gray-700)}}.select-option.selected{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-blue-600)}.dark .select-option.selected{color:var(--color-blue-400)}.select-option:disabled{cursor:not-allowed;opacity:.5}.select-option-check{height:calc(var(--spacing) * 5);width:calc(var(--spacing) * 5);color:var(--color-blue-600);flex-shrink:0;margin-left:auto}.dark .select-option-check{color:var(--color-blue-400)}.multi-select{display:inline-block;position:relative}.multi-select-trigger{align-items:center;gap:calc(var(--spacing) * 2);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-300);color:var(--color-gray-700);cursor:pointer;background-color:#0000;display:inline-flex}@media(hover:hover){.multi-select-trigger:hover{background-color:var(--color-gray-50)}}.dark .multi-select-trigger{border-color:var(--color-gray-600);color:var(--color-gray-300)}@media(hover:hover){.dark .multi-select-trigger:hover{background-color:var(--color-gray-700)}}.multi-select-count{color:var(--color-gray-500)}.dark .multi-select-count{color:var(--color-gray-400)}.multi-select-dropdown{z-index:50;margin-top:calc(var(--spacing) * 2);min-width:calc(var(--spacing) * 64);border-radius:var(--radius-xl);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);position:absolute}.dark .multi-select-dropdown{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}.multi-select-dropdown.hidden{display:none}.multi-select-header{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-100);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-900)}.dark .multi-select-header{border-color:var(--color-gray-700);color:var(--color-white)}.multi-select-hint{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal);color:var(--color-gray-500)}.dark .multi-select-hint{color:var(--color-gray-400)}.multi-select-items{max-height:calc(var(--spacing) * 80);padding-block:calc(var(--spacing) * 2);overflow:auto}.multi-select-item{cursor:pointer;align-items:center;gap:calc(var(--spacing) * 3);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-700);display:flex}@media(hover:hover){.multi-select-item:hover{background-color:var(--color-gray-50)}}.dark .multi-select-item{color:var(--color-gray-300)}@media(hover:hover){.dark .multi-select-item:hover{background-color:var(--color-gray-700)}}.multi-select-item.disabled{cursor:not-allowed;opacity:.5}.multi-select-item.disabled:hover,.dark .multi-select-item.disabled:hover{background-color:#0000}.select-ghost{display:inline-block;position:relative}.select-ghost-trigger{align-items:center;gap:var(--spacing);padding-inline:calc(var(--spacing) * 2);padding-block:calc(var(--spacing) * 1.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);cursor:pointer;border-radius:var(--radius-lg);color:var(--color-gray-700);background-color:#0000;display:inline-flex}@media(hover:hover){.select-ghost-trigger:hover{background-color:var(--color-gray-100)}}.dark .select-ghost-trigger{color:var(--color-gray-300)}@media(hover:hover){.dark .select-ghost-trigger:hover{background-color:var(--color-gray-700)}}.select-ghost-dropdown{z-index:50;margin-top:var(--spacing);min-width:calc(var(--spacing) * 32);border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);padding-block:var(--spacing);--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);position:absolute}.dark .select-ghost-dropdown{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}.select-ghost-dropdown.hidden{display:none}.select-ghost-option{cursor:pointer;width:100%;padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-700);justify-content:space-between;align-items:center;display:flex}@media(hover:hover){.select-ghost-option:hover{background-color:var(--color-gray-50)}}.dark .select-ghost-option{color:var(--color-gray-300)}@media(hover:hover){.dark .select-ghost-option:hover{background-color:var(--color-gray-700)}}.select-ghost-option.selected{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-blue-600)}.dark .select-ghost-option.selected{color:var(--color-blue-400)}.select-ghost-check{height:calc(var(--spacing) * 4);width:calc(var(--spacing) * 4);color:var(--color-blue-600)}.dark .select-ghost-check{color:var(--color-blue-400)}.searchable-dropdown{display:inline-block;position:relative}.searchable-dropdown-trigger{border-radius:var(--radius-xl);background-color:var(--color-gray-100);min-width:200px;padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-700);cursor:pointer;justify-content:space-between;align-items:center;gap:calc(var(--spacing) * 2);border-style:var(--tw-border-style);border-width:0;display:flex}.searchable-dropdown-trigger:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--color-blue-500);--tw-outline-style:none;outline-style:none}.dark .searchable-dropdown-trigger{background-color:var(--color-gray-700);color:var(--color-gray-200)}.searchable-dropdown-trigger:disabled{cursor:not-allowed;opacity:.5}.searchable-dropdown-trigger-icon{height:calc(var(--spacing) * 4);width:calc(var(--spacing) * 4);color:var(--color-gray-400);transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;flex-shrink:0;transition-duration:.2s}.dark .searchable-dropdown-trigger-icon{color:var(--color-gray-500)}.searchable-dropdown-trigger-icon.open{rotate:180deg}.searchable-dropdown-dropdown{z-index:50;margin-top:calc(var(--spacing) * 2);border-radius:var(--radius-2xl);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);width:100%;min-width:280px;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);position:absolute;overflow:hidden}.dark .searchable-dropdown-dropdown{border-color:var(--color-gray-600);background-color:var(--color-gray-800)}.searchable-dropdown-dropdown.hidden{display:none}.searchable-dropdown-search{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-200);padding:calc(var(--spacing) * 3)}.dark .searchable-dropdown-search{border-color:var(--color-gray-700)}.searchable-dropdown-search-input{border-radius:var(--radius-lg);background-color:var(--color-gray-50);width:100%;padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200)}.searchable-dropdown-search-input::placeholder{color:var(--color-gray-400)}.searchable-dropdown-search-input:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--color-blue-500);--tw-outline-style:none;border-color:#0000;outline-style:none}.dark .searchable-dropdown-search-input{border-color:var(--color-gray-600);background-color:var(--color-gray-700);color:var(--color-white)}.dark .searchable-dropdown-search-input::placeholder{color:var(--color-gray-500)}.searchable-dropdown-items{max-height:calc(var(--spacing) * 80);padding-block:calc(var(--spacing) * 2);overflow:auto}.searchable-dropdown-option{cursor:pointer;width:100%;padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3);text-align:left;align-items:flex-start;gap:calc(var(--spacing) * 3);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));display:flex}@media(hover:hover){.searchable-dropdown-option:hover{background-color:var(--color-gray-100)}.dark .searchable-dropdown-option:hover{background-color:var(--color-gray-700)}}.searchable-dropdown-option:disabled{cursor:not-allowed;opacity:.5}.searchable-dropdown-check-area{margin-top:calc(var(--spacing) * .5);height:calc(var(--spacing) * 5);width:calc(var(--spacing) * 5);flex-shrink:0}.searchable-dropdown-check{height:calc(var(--spacing) * 5);width:calc(var(--spacing) * 5);color:var(--color-blue-600)}.dark .searchable-dropdown-check{color:var(--color-blue-400)}.searchable-dropdown-option-content{flex:1;min-width:0}.searchable-dropdown-option-label{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-900)}.dark .searchable-dropdown-option-label{color:var(--color-blue-400)}.searchable-dropdown-option.selected .searchable-dropdown-option-label{color:var(--color-blue-600)}.dark :is(.searchable-dropdown-option.selected .searchable-dropdown-option-label){color:var(--color-blue-400)}.searchable-dropdown-option-description{margin-top:calc(var(--spacing) * .5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-500)}.dark .searchable-dropdown-option-description{color:var(--color-gray-400)}.searchable-dropdown-message{padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 6);text-align:center;color:var(--color-gray-500)}.dark .searchable-dropdown-message{color:var(--color-gray-400)}.stat-card{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);padding:calc(var(--spacing) * 6);--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a), 0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);transition-property:box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}@media(hover:hover){.stat-card:hover{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}}.dark .stat-card{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}.stats-icon{border-radius:var(--radius-lg);padding:calc(var(--spacing) * 3)}.stats-icon-blue{background-color:var(--color-blue-50);color:var(--color-blue-600)}.dark .stats-icon-blue{background-color:#1c398e33}@supports (color:color-mix(in lab,red,red)){.dark .stats-icon-blue{background-color:color-mix(in oklab,var(--color-blue-900) 20%,transparent)}}.dark .stats-icon-blue{color:var(--color-blue-400)}.stats-icon-green{background-color:var(--color-green-50);color:var(--color-green-600)}.dark .stats-icon-green{background-color:#0d542b33}@supports (color:color-mix(in lab,red,red)){.dark .stats-icon-green{background-color:color-mix(in oklab,var(--color-green-900) 20%,transparent)}}.dark .stats-icon-green{color:var(--color-green-400)}.stats-icon-purple{background-color:var(--color-purple-50);color:var(--color-purple-600)}.dark .stats-icon-purple{background-color:#59168b33}@supports (color:color-mix(in lab,red,red)){.dark .stats-icon-purple{background-color:color-mix(in oklab,var(--color-purple-900) 20%,transparent)}}.dark .stats-icon-purple{color:var(--color-purple-400)}.stats-icon-orange{background-color:var(--color-orange-50);color:var(--color-orange-600)}.dark .stats-icon-orange{background-color:#7e2a0c33}@supports (color:color-mix(in lab,red,red)){.dark .stats-icon-orange{background-color:color-mix(in oklab,var(--color-orange-900) 20%,transparent)}}.dark .stats-icon-orange{color:var(--color-orange-400)}.stats-change{align-items:center;gap:var(--spacing);border-radius:var(--radius-md);padding-inline:calc(var(--spacing) * 2);padding-block:var(--spacing);display:flex}.stats-trend-up{background-color:var(--color-green-50);color:var(--color-green-600)}.dark .stats-trend-up{background-color:#0d542b33}@supports (color:color-mix(in lab,red,red)){.dark .stats-trend-up{background-color:color-mix(in oklab,var(--color-green-900) 20%,transparent)}}.dark .stats-trend-up{color:var(--color-green-400)}.stats-trend-down{background-color:var(--color-red-50);color:var(--color-red-600)}.dark .stats-trend-down{background-color:#82181a33}@supports (color:color-mix(in lab,red,red)){.dark .stats-trend-down{background-color:color-mix(in oklab,var(--color-red-900) 20%,transparent)}}.dark .stats-trend-down{color:var(--color-red-400)}.stats-trend-neutral{background-color:var(--color-gray-50);color:var(--color-gray-600)}.dark .stats-trend-neutral{background-color:var(--color-gray-700);color:var(--color-gray-400)}.stats-value-lg{margin-bottom:var(--spacing);font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height));--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold);color:var(--color-gray-900)}.dark .stats-value-lg{color:var(--color-white)}.stats-label{margin-bottom:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-600)}.dark .stats-label{color:var(--color-gray-400)}.stats-sublabel{align-items:center;gap:var(--spacing);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-gray-500);display:flex}.dark .stats-sublabel{color:var(--color-gray-400)}:where(.tree>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(var(--spacing) * var(--tw-space-y-reverse));margin-block-end:calc(var(--spacing) * calc(1 - var(--tw-space-y-reverse)))}.tree-item{cursor:pointer;align-items:center;gap:calc(var(--spacing) * 2);border-radius:var(--radius-lg);padding:calc(var(--spacing) * 2);display:flex}@media(hover:hover){.tree-item:hover{background-color:var(--color-gray-50)}.dark .tree-item:hover{background-color:var(--color-gray-800)}}.tree-item.selected{background-color:var(--color-blue-50)}.dark .tree-item.selected{background-color:#1c398e33}@supports (color:color-mix(in lab,red,red)){.dark .tree-item.selected{background-color:color-mix(in oklab,var(--color-blue-900) 20%,transparent)}}.tree-item-content{align-items:center;gap:calc(var(--spacing) * 2);flex:1;min-width:0;display:flex}.tree-item-icon{height:calc(var(--spacing) * 4);width:calc(var(--spacing) * 4);color:var(--color-gray-400);flex-shrink:0}.dark .tree-item-icon{color:var(--color-gray-500)}.tree-item-label{text-overflow:ellipsis;white-space:nowrap;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-700);overflow:hidden}.dark .tree-item-label{color:var(--color-gray-300)}.tree-expand-btn{height:calc(var(--spacing) * 6);width:calc(var(--spacing) * 6);color:var(--color-gray-400);border-radius:.25rem;justify-content:center;align-items:center;display:flex}@media(hover:hover){.tree-expand-btn:hover{background-color:var(--color-gray-100)}}.dark .tree-expand-btn{color:var(--color-gray-500)}@media(hover:hover){.dark .tree-expand-btn:hover{background-color:var(--color-gray-700)}}.tree-expand-btn.hidden{visibility:hidden}.tree-children{margin-left:calc(var(--spacing) * 6);border-left-style:var(--tw-border-style);border-left-width:1px;border-color:var(--color-gray-200);padding-left:calc(var(--spacing) * 4)}.dark .tree-children{border-color:var(--color-gray-700)}.tree-level-1{border-left-style:var(--tw-border-style);border-left-width:0;margin-left:0;padding-left:0}.tree-level-2{margin-left:calc(var(--spacing) * 6);border-left-style:var(--tw-border-style);border-left-width:1px;border-color:var(--color-gray-200);padding-left:calc(var(--spacing) * 4)}.dark .tree-level-2{border-color:var(--color-gray-700)}.tree-level-3{margin-left:calc(var(--spacing) * 6);border-left-style:var(--tw-border-style);border-left-width:1px;border-color:var(--color-gray-200);padding-left:calc(var(--spacing) * 4)}.dark .tree-level-3{border-color:var(--color-gray-700)}.tree-level-4{margin-left:calc(var(--spacing) * 6);border-left-style:var(--tw-border-style);border-left-width:1px;border-color:var(--color-gray-200);padding-left:calc(var(--spacing) * 4)}.dark .tree-level-4{border-color:var(--color-gray-700)}.category-item{cursor:pointer;padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900);justify-content:space-between;align-items:center;display:flex}.dark .category-item{color:var(--color-white)}@media(hover:hover){.category-item:hover{background-color:var(--color-gray-50)}.dark .category-item:hover{background-color:var(--color-gray-700)}}.category-item[data-selected=true]{background-color:var(--color-blue-600);color:var(--color-white)}.category-item[data-selected=true]:hover{background-color:var(--color-blue-700)}.category-item-mobile{cursor:pointer;padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900)}.dark .category-item-mobile{color:var(--color-white)}.category-item-mobile[data-selected=true]{background-color:var(--color-blue-50)}.dark .category-item-mobile[data-selected=true]{background-color:#1c398e4d}@supports (color:color-mix(in lab,red,red)){.dark .category-item-mobile[data-selected=true]{background-color:color-mix(in oklab,var(--color-blue-900) 30%,transparent)}}.stats-card{height:calc(var(--spacing) * 36);border-radius:var(--radius-2xl);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-100);background-color:var(--color-white);padding:calc(var(--spacing) * 5);flex-direction:column;justify-content:space-between;display:flex}.dark .stats-card{border-color:var(--color-slate-700);background-color:var(--color-slate-800)}.dashboard-panel{border-radius:var(--radius-3xl);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-100);background-color:var(--color-white);padding:calc(var(--spacing) * 6);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.dark .dashboard-panel{border-color:var(--color-slate-700);background-color:var(--color-slate-800)}.stats-value{margin-bottom:calc(var(--spacing) * 2);--tw-leading:1;--tw-font-weight:var(--font-weight-bold);font-size:2rem;line-height:1;font-weight:var(--font-weight-bold);color:var(--color-slate-900)}.dark .stats-value{color:var(--color-white)}.stats-trend{align-items:center;gap:var(--spacing);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-emerald-500);display:flex}.status-active{padding-inline:calc(var(--spacing) * 2.5);padding-block:var(--spacing);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);background-color:var(--color-green-100);color:var(--color-green-700);border-radius:3.40282e38px;align-items:center;display:inline-flex}.dark .status-active{background-color:#0d542b4d}@supports (color:color-mix(in lab,red,red)){.dark .status-active{background-color:color-mix(in oklab,var(--color-green-900) 30%,transparent)}}.dark .status-active{color:var(--color-green-400)}.status-inactive{padding-inline:calc(var(--spacing) * 2.5);padding-block:var(--spacing);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);background-color:var(--color-gray-100);color:var(--color-gray-600);border-radius:3.40282e38px;align-items:center;display:inline-flex}.dark .status-inactive{background-color:var(--color-gray-700);color:var(--color-gray-400)}.status-pending{padding-inline:calc(var(--spacing) * 2.5);padding-block:var(--spacing);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);background-color:var(--color-yellow-100);color:var(--color-yellow-700);border-radius:3.40282e38px;align-items:center;display:inline-flex}.dark .status-pending{background-color:#733e0a4d}@supports (color:color-mix(in lab,red,red)){.dark .status-pending{background-color:color-mix(in oklab,var(--color-yellow-900) 30%,transparent)}}.dark .status-pending{color:var(--color-yellow-400)}.status-error{padding-inline:calc(var(--spacing) * 2.5);padding-block:var(--spacing);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);background-color:var(--color-red-100);color:var(--color-red-700);border-radius:3.40282e38px;align-items:center;display:inline-flex}.dark .status-error{background-color:#82181a4d}@supports (color:color-mix(in lab,red,red)){.dark .status-error{background-color:color-mix(in oklab,var(--color-red-900) 30%,transparent)}}.dark .status-error{color:var(--color-red-400)}.list-item{align-items:center;gap:calc(var(--spacing) * 3);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-100);padding-block:calc(var(--spacing) * 3);display:flex}.dark .list-item{border-color:var(--color-gray-700)}.list-item:last-child{border-bottom-style:var(--tw-border-style);border-bottom-width:0}.list-item-hover{margin-inline:calc(var(--spacing) * -3);align-items:center;gap:calc(var(--spacing) * 3);border-radius:var(--radius-lg);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 3);display:flex}@media(hover:hover){.list-item-hover:hover{background-color:var(--color-gray-50)}.dark .list-item-hover:hover{background-color:var(--color-gray-800)}}.empty-state{padding-block:calc(var(--spacing) * 12);text-align:center;flex-direction:column;justify-content:center;align-items:center;display:flex}.empty-state-icon{margin-bottom:calc(var(--spacing) * 4);height:calc(var(--spacing) * 16);width:calc(var(--spacing) * 16);color:var(--color-gray-300)}.dark .empty-state-icon{color:var(--color-gray-600)}.empty-state-title{margin-bottom:calc(var(--spacing) * 2);font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-900)}.dark .empty-state-title{color:var(--color-white)}.empty-state-description{margin-bottom:calc(var(--spacing) * 4);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-500)}.dark .empty-state-description{color:var(--color-gray-400)}.page-header{margin-bottom:calc(var(--spacing) * 6);justify-content:space-between;align-items:center;display:flex}.page-header-padded{margin-bottom:calc(var(--spacing) * 6);padding-inline:calc(var(--spacing) * 6);justify-content:space-between;align-items:center;display:flex}.page-header-fullbleed{margin-inline:calc(var(--spacing) * -6);margin-bottom:calc(var(--spacing) * 6);background-color:var(--color-gray-50);padding-inline:calc(var(--spacing) * 6);justify-content:space-between;align-items:center;display:flex}.dark .page-header-fullbleed{background-color:var(--color-gray-900)}.page-actions{align-items:center;gap:calc(var(--spacing) * 3);display:flex}.section-card{margin-top:calc(var(--spacing) * 6);scroll-margin-top:calc(var(--spacing) * 32);border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);padding:calc(var(--spacing) * 6)}.dark .section-card{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}.section-header{margin-bottom:calc(var(--spacing) * 6);font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-gray-900)}.dark .section-header{color:var(--color-white)}.info-row{margin-bottom:calc(var(--spacing) * 4);align-items:center;gap:calc(var(--spacing) * 2);display:flex}.action-buttons{margin-top:calc(var(--spacing) * 6);justify-content:flex-end;gap:calc(var(--spacing) * 3);display:flex}.form-label{margin-bottom:var(--spacing);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-900);display:block}.dark .form-label{color:var(--color-white)}.form-label-muted{margin-bottom:var(--spacing);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-500);display:block}.dark .form-label-muted{color:var(--color-gray-400)}.form-hint{margin-top:var(--spacing);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-gray-500)}.dark .form-hint{color:var(--color-gray-400)}.text-value{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900)}.dark .text-value{color:var(--color-white)}.text-link{cursor:pointer;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-blue-600)}@media(hover:hover){.text-link:hover{color:var(--color-blue-700)}}.dark .text-link{color:var(--color-blue-400)}@media(hover:hover){.dark .text-link:hover{color:var(--color-blue-300)}}.icon-xs{height:calc(var(--spacing) * 3);width:calc(var(--spacing) * 3)}.icon-sm{height:calc(var(--spacing) * 4);width:calc(var(--spacing) * 4)}.icon-md{height:calc(var(--spacing) * 5);width:calc(var(--spacing) * 5)}.icon-lg{height:calc(var(--spacing) * 6);width:calc(var(--spacing) * 6)}.icon-xl{height:calc(var(--spacing) * 8);width:calc(var(--spacing) * 8)}.icon-2xl{height:calc(var(--spacing) * 10);width:calc(var(--spacing) * 10)}.icon-action{cursor:pointer;color:var(--color-gray-400)}@media(hover:hover){.icon-action:hover{color:var(--color-gray-600)}}.dark .icon-action{color:var(--color-gray-500)}@media(hover:hover){.dark .icon-action:hover{color:var(--color-gray-300)}}.page-container{background-color:var(--color-gray-50);min-height:100vh;padding:calc(var(--spacing) * 6)}.dark .page-container{background-color:var(--color-gray-900)}.content-area{max-width:var(--container-7xl);margin-inline:auto}.content-panel{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);padding:calc(var(--spacing) * 6)}.dark .content-panel{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}.flex-row-center{align-items:center;display:flex}.flex-row-between{justify-content:space-between;align-items:center;display:flex}.flex-row-end{justify-content:flex-end;align-items:center;display:flex}.flex-col-start{flex-direction:column;align-items:flex-start;display:flex}.flex-between{justify-content:space-between;align-items:center;display:flex}.flex-center{align-items:center;display:flex}.flex-end{justify-content:flex-end;align-items:center;display:flex}.flex-wrap-center{flex-wrap:wrap;align-items:center;display:flex}.inline-flex-center{align-items:center;display:inline-flex}.row-stack{flex-direction:column;display:flex}:where(.row-stack>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px * var(--tw-divide-y-reverse));border-bottom-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)));border-color:var(--color-gray-200)}:where(.dark .row-stack>:not(:last-child)){border-color:var(--color-gray-700)}.row-stack>*{padding-block:calc(var(--spacing) * 2)}.row-stack>:first-child{padding-top:0}.row-stack>:last-child{padding-bottom:0}.stack-flush{flex-direction:column;gap:0;display:flex}.stack-tight{gap:calc(var(--spacing) * 2);flex-direction:column;display:flex}.card-stack{margin-top:calc(var(--spacing) * 3);gap:calc(var(--spacing) * 2);flex-direction:column;display:flex}.card-stack-item{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);padding:calc(var(--spacing) * 4)}.dark .card-stack-item{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}.card-stack-item{flex-direction:column;display:flex}.option-card{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-gray-50);overflow:hidden}.dark .option-card{border-color:var(--color-gray-700);background-color:#36415380}@supports (color:color-mix(in lab,red,red)){.dark .option-card{background-color:color-mix(in oklab,var(--color-gray-700) 50%,transparent)}}.option-card>*{padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3)}.option-card>*+*{border-top-style:var(--tw-border-style);border-top-width:1px;border-color:var(--color-gray-200)}.dark :is(.option-card>*+*){border-color:var(--color-gray-700)}.alert-info{align-items:center;gap:calc(var(--spacing) * 3);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-blue-200);background-color:var(--color-blue-50);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-blue-900);display:flex}.dark .alert-info{border-color:var(--color-blue-800);background-color:#1c398e33}@supports (color:color-mix(in lab,red,red)){.dark .alert-info{background-color:color-mix(in oklab,var(--color-blue-900) 20%,transparent)}}.dark .alert-info{color:var(--color-blue-100)}.alert-warning{align-items:center;gap:calc(var(--spacing) * 3);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-yellow-200);background-color:var(--color-yellow-50);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-yellow-900);display:flex}.dark .alert-warning{border-color:var(--color-yellow-800);background-color:#733e0a33}@supports (color:color-mix(in lab,red,red)){.dark .alert-warning{background-color:color-mix(in oklab,var(--color-yellow-900) 20%,transparent)}}.dark .alert-warning{color:var(--color-yellow-100)}.alert-danger{align-items:center;gap:calc(var(--spacing) * 3);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-red-200);background-color:var(--color-red-50);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-red-900);display:flex}.dark .alert-danger{border-color:var(--color-red-800);background-color:#82181a33}@supports (color:color-mix(in lab,red,red)){.dark .alert-danger{background-color:color-mix(in oklab,var(--color-red-900) 20%,transparent)}}.dark .alert-danger{color:var(--color-red-100)}.alert-success{align-items:center;gap:calc(var(--spacing) * 3);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-green-200);background-color:var(--color-green-50);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-green-900);display:flex}.dark .alert-success{border-color:var(--color-green-800);background-color:#0d542b33}@supports (color:color-mix(in lab,red,red)){.dark .alert-success{background-color:color-mix(in oklab,var(--color-green-900) 20%,transparent)}}.dark .alert-success{color:var(--color-green-100)}.alert-info:has(+.row-stack),.alert-warning:has(+.row-stack),.alert-danger:has(+.row-stack),.alert-success:has(+.row-stack),.alert-error:has(+.row-stack),.alert-info:has(+.toggle-group),.alert-warning:has(+.toggle-group),.alert-danger:has(+.toggle-group),.alert-success:has(+.toggle-group),.alert-error:has(+.toggle-group),.alert-info:has(+.grid-2col-responsive),.alert-warning:has(+.grid-2col-responsive),.alert-danger:has(+.grid-2col-responsive),.alert-success:has(+.grid-2col-responsive),.alert-error:has(+.grid-2col-responsive){margin-bottom:calc(var(--spacing) * 4)}.row-stack>.alert-info,.row-stack>.alert-warning,.row-stack>.alert-danger,.row-stack>.alert-success,.row-stack>.alert-error{padding-top:calc(var(--spacing) * 3);padding-bottom:calc(var(--spacing) * 3)}.row-stack>*+.alert-info,.row-stack>*+.alert-warning,.row-stack>*+.alert-danger,.row-stack>*+.alert-success,.row-stack>*+.alert-error{margin-top:calc(var(--spacing) * 3)}.sub-tab-nav{gap:var(--spacing);background-color:var(--color-white);padding-inline:calc(var(--spacing) * 6);display:flex}@media(min-width:40rem){.sub-tab-nav{padding-inline:calc(var(--spacing) * 8)}}@media(min-width:64rem){.sub-tab-nav{padding-inline:calc(var(--spacing) * 10)}}.dark .sub-tab-nav{background-color:var(--color-gray-800)}.section-header{margin-bottom:calc(var(--spacing) * 6)}.section-header>h3{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-gray-900)}.dark :is(.section-header>h3){color:var(--color-white)}.section-header>p{margin-top:var(--spacing);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-500)}.dark :is(.section-header>p){color:var(--color-gray-400)}.toggle-group{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);overflow:hidden}.dark .toggle-group{border-color:var(--color-gray-700)}.section-header-row{align-items:center;gap:calc(var(--spacing) * 2);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3);background-color:var(--color-gray-50);display:flex}.dark .section-header-row{background-color:#36415380}@supports (color:color-mix(in lab,red,red)){.dark .section-header-row{background-color:color-mix(in oklab,var(--color-gray-700) 50%,transparent)}}.section-header-row{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-200)}.dark .section-header-row{border-color:var(--color-gray-700)}.section-header-row{cursor:pointer;-webkit-user-select:none;user-select:none}.list-row-hover{background-color:var(--color-white)}.dark .list-row-hover{background-color:var(--color-gray-800)}@media(hover:hover){.list-row-hover:hover{background-color:var(--color-gray-50)}.dark .list-row-hover:hover{background-color:var(--color-gray-700)}}.list-row-hover{padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2.5);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.text-hint{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-gray-400)}.dark .text-hint{color:var(--color-gray-500)}.text-info-soft{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-blue-700)}.dark .text-info-soft{color:var(--color-blue-300)}.text-warning-soft{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-yellow-700)}.dark .text-warning-soft{color:var(--color-yellow-300)}.text-danger-soft{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-red-700)}.dark .text-danger-soft{color:var(--color-red-300)}.text-success-soft{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-green-700)}.dark .text-success-soft{color:var(--color-green-300)}.checkbox-aligned{height:calc(var(--spacing) * 4);width:calc(var(--spacing) * 4);cursor:pointer;border-color:var(--color-gray-300);color:var(--color-gray-900);border-radius:.25rem}.checkbox-aligned:focus{--tw-ring-color:var(--color-gray-500)}.dark .checkbox-aligned{border-color:var(--color-gray-600);background-color:var(--color-gray-800);color:var(--color-blue-400)}.checkbox-aligned{margin-top:calc(var(--spacing) * .5);flex-shrink:0}:where(.divider-list>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px * var(--tw-divide-y-reverse));border-bottom-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)));border-color:var(--color-gray-200)}:where(.dark .divider-list>:not(:last-child)){border-color:var(--color-gray-700)}.modal-footer-buttons{margin-top:calc(var(--spacing) * 6);justify-content:flex-end;gap:calc(var(--spacing) * 3);display:flex}.spinner-sm{height:calc(var(--spacing) * 4);width:calc(var(--spacing) * 4);animation:var(--animate-spin)}.text-required{margin-left:var(--spacing);color:var(--color-red-500)}.dark .text-required{color:var(--color-red-400)}.form-label-block{margin-bottom:var(--spacing);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-700);display:block}.dark .form-label-block{color:var(--color-gray-300)}.form-sub-label{margin-bottom:var(--spacing);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-gray-500);display:block}.dark .form-sub-label{color:var(--color-gray-400)}.text-field-hint{margin-top:calc(var(--spacing) * .5);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal);color:var(--color-gray-500)}.dark .text-field-hint{color:var(--color-gray-400)}.section-heading-md{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-gray-900)}.dark .section-heading-md{color:var(--color-white)}.grid-3col-responsive{align-items:flex-start;gap:calc(var(--spacing) * 4);grid-template-columns:repeat(1,minmax(0,1fr));display:grid}@media(min-width:64rem){.grid-3col-responsive{grid-template-columns:repeat(3,minmax(0,1fr))}}.text-error-soft{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-red-700)}.dark .text-error-soft{color:var(--color-red-300)}.text-error-strong{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-red-800)}.dark .text-error-strong{color:var(--color-red-200)}.row-content-indent{padding-left:calc(var(--spacing) * 6)}.flex-start{align-items:flex-start;display:flex}.inline-clickable{cursor:pointer;align-items:center;gap:calc(var(--spacing) * 2);display:inline-flex}.grid-2col{gap:calc(var(--spacing) * 4);grid-template-columns:repeat(2,minmax(0,1fr));display:grid}.empty-state-cell{padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 4);text-align:center}.form-error-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-red-500)}.dark .form-error-xs{color:var(--color-red-400)}.text-page-heading{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height));--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold);color:var(--color-gray-900)}.dark .text-page-heading{color:var(--color-white)}.text-table-header{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider);color:var(--color-gray-500);text-transform:uppercase}.dark .text-table-header{color:var(--color-gray-400)}.panel-header-row{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-200);padding-inline:calc(var(--spacing) * 6);padding-block:calc(var(--spacing) * 4)}.dark .panel-header-row{border-color:var(--color-gray-700)}.grid-2col-responsive{gap:calc(var(--spacing) * 4);grid-template-columns:repeat(1,minmax(0,1fr));display:grid}@media(min-width:48rem){.grid-2col-responsive{grid-template-columns:repeat(2,minmax(0,1fr))}}.panel-header{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-200);padding-block:calc(var(--spacing) * 3)}.dark .panel-header{border-color:var(--color-gray-700)}.gap-sm{gap:calc(var(--spacing) * 2)}.gap-md{gap:calc(var(--spacing) * 4)}.gap-lg{gap:calc(var(--spacing) * 6)}.mb-sm{margin-bottom:calc(var(--spacing) * 2)}.mb-md{margin-bottom:calc(var(--spacing) * 4)}.mb-lg{margin-bottom:calc(var(--spacing) * 6)}.surface{background-color:var(--color-white)}.dark .surface{background-color:var(--color-gray-800)}.surface-alt{background-color:var(--color-gray-50)}.dark .surface-alt{background-color:var(--color-gray-900)}.card-border{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200)}.dark .card-border{border-color:var(--color-gray-700)}.card-border-xl{border-radius:var(--radius-xl);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200)}.dark .card-border-xl{border-color:var(--color-gray-700)}.border-light{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-300)}.dark .border-light{border-color:var(--color-gray-600)}.disabled-state:disabled{cursor:not-allowed;opacity:.5}.focus-ring:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--color-blue-500);--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-outline-style:none;outline-style:none}.dark .focus-ring:focus{--tw-ring-offset-color:var(--color-gray-800)}.focus-ring-inset:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--color-blue-500);--tw-outline-style:none;--tw-ring-inset:inset;outline-style:none}@media(hover:hover){.hover-bg:hover{background-color:var(--color-gray-100)}.dark .hover-bg:hover{background-color:var(--color-gray-700)}.hover-bg-light:hover{background-color:var(--color-gray-50)}.dark .hover-bg-light:hover{background-color:var(--color-gray-800)}}.btn-base{border-radius:var(--radius-lg);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;justify-content:center;align-items:center;transition-duration:.2s;display:inline-flex}.btn-base:disabled{cursor:not-allowed;opacity:.5}.btn-size-sm{padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 1.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.btn-size-md{padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.btn-size-lg{padding-inline:calc(var(--spacing) * 6);padding-block:calc(var(--spacing) * 3);font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.badge-base{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);border-radius:3.40282e38px;align-items:center;display:inline-flex}.badge-size-sm{padding-inline:calc(var(--spacing) * 2);padding-block:calc(var(--spacing) * .5)}.badge-size-md{padding-inline:calc(var(--spacing) * 2.5);padding-block:var(--spacing)}.shadow-card{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-dropdown{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}@media(hover:hover){.shadow-hover:hover{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a), 0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}}.page-title{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-gray-900)}@media(min-width:40rem){.page-title{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}}.dark .page-title{color:var(--color-white)}.page-description{margin-top:var(--spacing);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-600)}.dark .page-description{color:var(--color-gray-400)}.page-action-primary{align-items:center;gap:calc(var(--spacing) * 2);border-radius:var(--radius-lg);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));background-color:var(--color-blue-600);color:var(--color-white);display:flex}@media(hover:hover){.page-action-primary:hover{background-color:var(--color-blue-700)}}.page-action-secondary{align-items:center;gap:calc(var(--spacing) * 2);border-radius:var(--radius-lg);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));background-color:var(--color-gray-200);color:var(--color-gray-900);display:flex}@media(hover:hover){.page-action-secondary:hover{background-color:var(--color-gray-300)}}.dark .page-action-secondary{background-color:var(--color-gray-700);color:var(--color-gray-200)}@media(hover:hover){.dark .page-action-secondary:hover{background-color:var(--color-gray-600)}}.page-action-danger{align-items:center;gap:calc(var(--spacing) * 2);border-radius:var(--radius-lg);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));background-color:var(--color-red-600);color:var(--color-white);display:flex}@media(hover:hover){.page-action-danger:hover{background-color:var(--color-red-700)}}.page-action-ghost{align-items:center;gap:calc(var(--spacing) * 2);border-radius:var(--radius-lg);padding:calc(var(--spacing) * 2);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));color:var(--color-gray-500);display:flex}@media(hover:hover){.page-action-ghost:hover{background-color:var(--color-gray-100);color:var(--color-gray-700)}}.dark .page-action-ghost{color:var(--color-gray-400)}@media(hover:hover){.dark .page-action-ghost:hover{background-color:var(--color-gray-800);color:var(--color-gray-200)}}.tab-container{margin-bottom:calc(var(--spacing) * 6);gap:calc(var(--spacing) * 6);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-200);display:flex}.dark .tab-container{border-color:var(--color-gray-700)}.tab-btn{align-items:center;gap:calc(var(--spacing) * 2);border-bottom-style:var(--tw-border-style);padding-inline:var(--spacing);padding-block:calc(var(--spacing) * 3);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));color:var(--color-gray-600);border-color:#0000;border-bottom-width:2px;margin-bottom:-1px;display:flex}@media(hover:hover){.tab-btn:hover{color:var(--color-gray-900)}}.dark .tab-btn{color:var(--color-gray-400)}@media(hover:hover){.dark .tab-btn:hover{color:var(--color-gray-200)}}.tab-btn-active{align-items:center;gap:calc(var(--spacing) * 2);border-bottom-style:var(--tw-border-style);padding-inline:var(--spacing);padding-block:calc(var(--spacing) * 3);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));border-bottom-width:2px;border-color:var(--color-blue-600);color:var(--color-blue-600);margin-bottom:-1px;display:flex}.dark .tab-btn-active{color:var(--color-blue-400)}.tab-badge{padding-inline:calc(var(--spacing) * 2);padding-block:calc(var(--spacing) * .5);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);background-color:var(--color-gray-100);color:var(--color-gray-600);border-radius:3.40282e38px}.dark .tab-badge{background-color:var(--color-gray-700);color:var(--color-gray-400)}.tab-badge-active{padding-inline:calc(var(--spacing) * 2);padding-block:calc(var(--spacing) * .5);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);background-color:var(--color-blue-100);color:var(--color-blue-600);border-radius:3.40282e38px}.dark .tab-badge-active{background-color:#1c398e33}@supports (color:color-mix(in lab,red,red)){.dark .tab-badge-active{background-color:color-mix(in oklab,var(--color-blue-900) 20%,transparent)}}.dark .tab-badge-active{color:var(--color-blue-400)}.sticky-tab-nav{z-index:40;margin-inline:calc(var(--spacing) * -6);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-200);background-color:var(--color-gray-50);padding-inline:calc(var(--spacing) * 6);position:sticky;top:0}.dark .sticky-tab-nav{border-color:var(--color-gray-700);background-color:var(--color-gray-900)}.sticky-tab-nav-responsive{z-index:40;margin-inline:calc(var(--spacing) * -4);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-200);background-color:var(--color-gray-50);padding-inline:calc(var(--spacing) * 4);position:sticky;top:0}@media(min-width:40rem){.sticky-tab-nav-responsive{margin-inline:calc(var(--spacing) * -6);padding-inline:calc(var(--spacing) * 6)}}@media(min-width:64rem){.sticky-tab-nav-responsive{margin-inline:calc(var(--spacing) * -8);padding-inline:calc(var(--spacing) * 8)}}.dark .sticky-tab-nav-responsive{border-color:var(--color-gray-700);background-color:var(--color-gray-900)}.sticky-section-header{z-index:40;border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-200);background-color:var(--color-gray-50);position:sticky;top:0}.dark .sticky-section-header{border-color:var(--color-gray-700);background-color:var(--color-gray-900)}.sticky-footer-buttons{z-index:10;margin-top:calc(var(--spacing) * 6);justify-content:flex-end;align-items:center;gap:calc(var(--spacing) * 3);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3);border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-gray-50);display:flex;position:sticky;bottom:0}.dark .sticky-footer-buttons{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}.sticky-footer-buttons-between{z-index:10;margin-top:calc(var(--spacing) * 6);justify-content:space-between;align-items:center;gap:calc(var(--spacing) * 3);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3);border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-gray-50);display:flex;position:sticky;bottom:0}.dark .sticky-footer-buttons-between{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}.footer-buttons-between{justify-content:space-between;align-items:center;gap:calc(var(--spacing) * 3);border-top-style:var(--tw-border-style);border-top-width:1px;border-color:var(--color-gray-200);background-color:var(--color-gray-50);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3);display:flex}.dark .footer-buttons-between{border-color:var(--color-gray-700);background-color:var(--color-gray-900)}.tab-content{padding-top:calc(var(--spacing) * 6)}.admin-page-content{background-color:var(--color-gray-50);min-height:100vh;padding:calc(var(--spacing) * 6)}.dark .admin-page-content{background-color:var(--color-gray-900)}.admin-page-content-compact{background-color:var(--color-gray-50);min-height:100vh;padding:calc(var(--spacing) * 4)}.dark .admin-page-content-compact{background-color:var(--color-gray-900)}.admin-page-content-responsive{background-color:var(--color-gray-50);min-height:100vh;padding:calc(var(--spacing) * 4)}@media(min-width:40rem){.admin-page-content-responsive{padding:calc(var(--spacing) * 6)}}@media(min-width:64rem){.admin-page-content-responsive{padding:calc(var(--spacing) * 8)}}.dark .admin-page-content-responsive{background-color:var(--color-gray-900)}.admin-page-content-responsive-fluid{background-color:var(--color-gray-50);padding:calc(var(--spacing) * 4)}@media(min-width:40rem){.admin-page-content-responsive-fluid{padding:calc(var(--spacing) * 6)}}@media(min-width:64rem){.admin-page-content-responsive-fluid{padding:calc(var(--spacing) * 8)}}.dark .admin-page-content-responsive-fluid{background-color:var(--color-gray-900)}.admin-page-content-fluid{background-color:var(--color-gray-50);padding:calc(var(--spacing) * 6)}.dark .admin-page-content-fluid{background-color:var(--color-gray-900)}.admin-page-content-fluid-compact{background-color:var(--color-gray-50);padding:calc(var(--spacing) * 4)}.dark .admin-page-content-fluid-compact{background-color:var(--color-gray-900)}.admin-page-content-viewport{background-color:var(--color-gray-50);height:calc(100vh - 64px);padding:calc(var(--spacing) * 6);overflow:hidden}.dark .admin-page-content-viewport{background-color:var(--color-gray-900)}.admin-page-content-viewport-compact{background-color:var(--color-gray-50);height:calc(100vh - 64px);padding:calc(var(--spacing) * 4);overflow:hidden}.dark .admin-page-content-viewport-compact{background-color:var(--color-gray-900)}.bulk-actions-bar{margin-bottom:calc(var(--spacing) * 4);align-items:center;gap:calc(var(--spacing) * 3);border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-gray-100);padding:calc(var(--spacing) * 3);display:flex}.dark .bulk-actions-bar{border-color:var(--color-gray-600);background-color:#36415380}@supports (color:color-mix(in lab,red,red)){.dark .bulk-actions-bar{background-color:color-mix(in oklab,var(--color-gray-700) 50%,transparent)}}:where(.admin-sidebar-menu-list>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * .5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * .5) * calc(1 - var(--tw-space-y-reverse)))}.admin-sidebar-menu-list{padding-inline:calc(var(--spacing) * 4);padding-bottom:calc(var(--spacing) * 4)}.admin-sidebar-item{border-radius:var(--radius-lg);width:100%;padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));justify-content:space-between;align-items:center;display:flex}.admin-sidebar-subitem{border-radius:var(--radius-lg);width:100%;padding-block:6px;padding-right:calc(var(--spacing) * 3);--tw-font-weight:var(--font-weight-medium);font-size:13px;font-weight:var(--font-weight-medium);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));justify-content:space-between;align-items:center;padding-left:30px;display:flex}.admin-sidebar-item-active{background-color:var(--color-slate-100);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-slate-900)}.dark .admin-sidebar-item-active{background-color:var(--color-slate-700);color:var(--color-white)}.admin-sidebar-item-active-parent{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-slate-900)}.dark .admin-sidebar-item-active-parent{color:var(--color-white)}.admin-sidebar-item-default{color:var(--color-slate-500)}@media(hover:hover){.admin-sidebar-item-default:hover{background-color:var(--color-slate-50);color:var(--color-slate-900)}}.dark .admin-sidebar-item-default{color:var(--color-slate-400)}@media(hover:hover){.dark .admin-sidebar-item-default:hover{background-color:var(--color-slate-800);color:var(--color-white)}}.admin-sidebar-item-disabled{cursor:default;color:var(--color-slate-400)}.dark .admin-sidebar-item-disabled{color:var(--color-slate-500)}.admin-sidebar-icon-box{flex-shrink:0;justify-content:center;align-items:center;width:18px;height:18px;display:flex}.admin-sidebar-chevron-trail{align-items:center;gap:var(--spacing);display:flex}.admin-sidebar-item-content{align-items:center;gap:calc(var(--spacing) * 2);display:flex}.admin-sidebar-module-icon{margin-right:calc(var(--spacing) * 2);height:calc(var(--spacing) * 3);width:calc(var(--spacing) * 3);color:var(--color-slate-400);opacity:.6}.dark .admin-sidebar-module-icon{color:var(--color-slate-500)}.admin-sidebar-tree-line-vertical{z-index:0;border-left-style:var(--tw-border-style);border-left-width:1px;border-color:var(--color-slate-300);position:absolute;top:0;bottom:0;left:12px}.dark .admin-sidebar-tree-line-vertical{border-color:var(--color-slate-600)}.admin-sidebar-tree-line-curve{z-index:10;border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-left-style:var(--tw-border-style);width:14px;height:50%;transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));border-left-width:1px;border-color:var(--color-slate-300);border-bottom-left-radius:10px;position:absolute;top:0;left:12px}@media(hover:hover){.admin-sidebar-tree-line-curve:is(:where(.group):hover *){border-color:var(--color-blue-400)}}.dark .admin-sidebar-tree-line-curve{border-color:var(--color-slate-600)}@media(hover:hover){.dark .admin-sidebar-tree-line-curve:is(:where(.group):hover *){border-color:var(--color-blue-500)}}.admin-sidebar-header{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-slate-200);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 4);justify-content:space-between;align-items:center;display:flex}.dark .admin-sidebar-header{border-color:var(--color-slate-700)}.admin-sidebar-collapse-btn{border-radius:var(--radius-md);padding:calc(var(--spacing) * 2);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}@media(hover:hover){.admin-sidebar-collapse-btn:hover{background-color:var(--color-slate-100)}.dark .admin-sidebar-collapse-btn:hover{background-color:var(--color-slate-700)}}.tab-nav-default{gap:calc(var(--spacing) * 2);display:flex}.tab-nav-underline{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-200);gap:0;display:flex}.dark .tab-nav-underline{border-color:var(--color-gray-700)}.tab-btn-base{align-items:center;gap:calc(var(--spacing) * 2);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));display:flex}.tab-btn-default{border-bottom-style:var(--tw-border-style);color:var(--color-gray-600);border-color:#0000;border-bottom-width:2px}@media(hover:hover){.tab-btn-default:hover{background-color:var(--color-gray-50);color:var(--color-gray-900)}}.dark .tab-btn-default{color:var(--color-gray-400)}@media(hover:hover){.dark .tab-btn-default:hover{background-color:var(--color-gray-700);color:var(--color-gray-200)}}.tab-btn-default-active{border-bottom-style:var(--tw-border-style);border-bottom-width:2px;border-color:var(--color-blue-600);background-color:var(--color-blue-50);color:var(--color-blue-600)}.dark .tab-btn-default-active{border-color:var(--color-blue-400);background-color:#1c398e33}@supports (color:color-mix(in lab,red,red)){.dark .tab-btn-default-active{background-color:color-mix(in oklab,var(--color-blue-900) 20%,transparent)}}.dark .tab-btn-default-active{color:var(--color-blue-400)}.tab-btn-underline{border-bottom-style:var(--tw-border-style);color:var(--color-gray-600);border-color:#0000;border-bottom-width:2px}@media(hover:hover){.tab-btn-underline:hover{border-color:var(--color-gray-300);color:var(--color-gray-900)}}.dark .tab-btn-underline{color:var(--color-gray-400)}@media(hover:hover){.dark .tab-btn-underline:hover{border-color:var(--color-gray-600);color:var(--color-gray-200)}}.tab-btn-underline-active{border-bottom-style:var(--tw-border-style);border-bottom-width:2px;border-color:var(--color-blue-600);color:var(--color-blue-600)}.dark .tab-btn-underline-active{border-color:var(--color-blue-400);color:var(--color-blue-400)}.tab-btn-pills{border-radius:var(--radius-lg);color:var(--color-gray-600)}@media(hover:hover){.tab-btn-pills:hover{background-color:var(--color-gray-100)}}.dark .tab-btn-pills{color:var(--color-gray-400)}@media(hover:hover){.dark .tab-btn-pills:hover{background-color:var(--color-gray-700)}}.tab-btn-pills-active{border-radius:var(--radius-lg);background-color:var(--color-blue-600);color:var(--color-white)}.dark .tab-btn-pills-active{background-color:var(--color-blue-500)}.tab-btn-disabled{cursor:not-allowed;color:var(--color-gray-400);opacity:.5}.dark .tab-btn-disabled{color:var(--color-gray-600)}.toggle-container{align-items:flex-start;column-gap:calc(var(--spacing) * 2);flex-wrap:wrap;width:auto;display:inline-flex}.toggle-switch-wrapper{flex-shrink:0;align-items:center;display:inline-flex;position:relative}.toggle-switch-track{background-color:var(--color-gray-200);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));border-radius:3.40282e38px;position:relative}.toggle-switch-track:is(:where(.peer):focus~*){--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--color-blue-300);--tw-outline-style:none;outline-style:none}.toggle-switch-track:is(:where(.peer):checked~*){background-color:var(--color-blue-600)}.dark .toggle-switch-track{background-color:var(--color-gray-700)}.dark .toggle-switch-track:is(:where(.peer):focus~*){--tw-ring-color:var(--color-blue-800)}.dark .toggle-switch-track:is(:where(.peer):checked~*){background-color:var(--color-blue-500)}.toggle-switch-track:after{top:calc(var(--spacing) * .5);left:calc(var(--spacing) * .5);--tw-content:"";border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-300);background-color:var(--color-white);content:var(--tw-content);transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));border-radius:3.40282e38px;position:absolute}.dark .toggle-switch-track:after{content:var(--tw-content);border-color:var(--color-gray-600)}.toggle-switch-track:is(:where(.peer):checked~*):after{--tw-translate-x:100%;translate:var(--tw-translate-x) var(--tw-translate-y);content:var(--tw-content);border-color:var(--color-white)}.toggle-label{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-900)}.dark .toggle-label{color:var(--color-white)}.toggle-description{margin-top:var(--spacing);width:100%;font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-gray-500)}.dark .toggle-description{color:var(--color-gray-400)}.admin-footer{margin-top:calc(var(--spacing) * 6);padding-inline:calc(var(--spacing) * 6);padding-block:calc(var(--spacing) * 4)}.admin-footer-row{justify-content:space-between;align-items:center;gap:calc(var(--spacing) * 4);flex-direction:column;display:flex}@media(min-width:48rem){.admin-footer-row{flex-direction:row}}.admin-footer-group{align-items:center;gap:calc(var(--spacing) * 4);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-600);display:flex}.dark .admin-footer-group{color:var(--color-gray-400)}.admin-footer-link-hover{cursor:pointer;transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}@media(hover:hover){.admin-footer-link-hover:hover{color:var(--color-blue-600)}.dark .admin-footer-link-hover:hover{color:var(--color-blue-400)}}.admin-footer-link{align-items:center;gap:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-600);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));display:flex}@media(hover:hover){.admin-footer-link:hover{color:var(--color-blue-600)}}.dark .admin-footer-link{color:var(--color-gray-400)}@media(hover:hover){.dark .admin-footer-link:hover{color:var(--color-blue-400)}}.responsive-grid{gap:calc(var(--spacing) * 4);grid-template-columns:repeat(1,minmax(0,1fr));display:grid}.grid-cols-1-lg-3{gap:calc(var(--spacing) * 6);grid-template-columns:repeat(1,minmax(0,1fr));display:grid}.col-span-1-lg,.col-span-2-lg{grid-column:span 1/span 1}.modal-description{margin-top:var(--spacing);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-500)}.dark .modal-description{color:var(--color-gray-400)}.toast-message{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}}@layer utilities{.pointer-events-auto{pointer-events:auto}.pointer-events-none{pointer-events:none}.collapse{visibility:collapse}.invisible{visibility:hidden}.visible{visibility:visible}.visible\!{visibility:visible!important}.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.sticky{position:sticky}.inset-0{inset:0}.inset-1{inset:var(--spacing)}.inset-2{inset:calc(var(--spacing) * 2)}.inset-3{inset:calc(var(--spacing) * 3)}.inset-4{inset:calc(var(--spacing) * 4)}.inset-5{inset:calc(var(--spacing) * 5)}.inset-6{inset:calc(var(--spacing) * 6)}.inset-8{inset:calc(var(--spacing) * 8)}.inset-auto{inset:auto}.inset-x-0{inset-inline:0}.inset-x-1{inset-inline:var(--spacing)}.inset-x-2{inset-inline:calc(var(--spacing) * 2)}.inset-x-3{inset-inline:calc(var(--spacing) * 3)}.inset-x-4{inset-inline:calc(var(--spacing) * 4)}.inset-x-auto{inset-inline:auto}.inset-y-0{inset-block:0}.inset-y-1{inset-block:var(--spacing)}.inset-y-2{inset-block:calc(var(--spacing) * 2)}.inset-y-3{inset-block:calc(var(--spacing) * 3)}.inset-y-4{inset-block:calc(var(--spacing) * 4)}.inset-y-auto{inset-block:auto}.-top-1{top:calc(var(--spacing) * -1)}.-top-2{top:calc(var(--spacing) * -2)}.-top-3{top:calc(var(--spacing) * -3)}.-top-4{top:calc(var(--spacing) * -4)}.top-0{top:0}.top-1{top:var(--spacing)}.top-1\.5{top:calc(var(--spacing) * 1.5)}.top-1\/2{top:50%}.top-1\/3{top:33.3333%}.top-1\/4{top:25%}.top-2{top:calc(var(--spacing) * 2)}.top-2\/3{top:66.6667%}.top-3{top:calc(var(--spacing) * 3)}.top-3\/4{top:75%}.top-4{top:calc(var(--spacing) * 4)}.top-5{top:calc(var(--spacing) * 5)}.top-6{top:calc(var(--spacing) * 6)}.top-8{top:calc(var(--spacing) * 8)}.top-10{top:calc(var(--spacing) * 10)}.top-12{top:calc(var(--spacing) * 12)}.top-16{top:calc(var(--spacing) * 16)}.top-auto{top:auto}.top-full{top:100%}.-right-1{right:calc(var(--spacing) * -1)}.-right-2{right:calc(var(--spacing) * -2)}.-right-3{right:calc(var(--spacing) * -3)}.-right-4{right:calc(var(--spacing) * -4)}.right-0{right:0}.right-1{right:var(--spacing)}.right-1\/2{right:50%}.right-2{right:calc(var(--spacing) * 2)}.right-3{right:calc(var(--spacing) * 3)}.right-4{right:calc(var(--spacing) * 4)}.right-5{right:calc(var(--spacing) * 5)}.right-6{right:calc(var(--spacing) * 6)}.right-8{right:calc(var(--spacing) * 8)}.right-10{right:calc(var(--spacing) * 10)}.right-12{right:calc(var(--spacing) * 12)}.right-16{right:calc(var(--spacing) * 16)}.right-auto{right:auto}.right-full{right:100%}.-bottom-1{bottom:calc(var(--spacing) * -1)}.-bottom-2{bottom:calc(var(--spacing) * -2)}.-bottom-3{bottom:calc(var(--spacing) * -3)}.-bottom-4{bottom:calc(var(--spacing) * -4)}.bottom-0{bottom:0}.bottom-1{bottom:var(--spacing)}.bottom-1\/2{bottom:50%}.bottom-2{bottom:calc(var(--spacing) * 2)}.bottom-3{bottom:calc(var(--spacing) * 3)}.bottom-4{bottom:calc(var(--spacing) * 4)}.bottom-5{bottom:calc(var(--spacing) * 5)}.bottom-6{bottom:calc(var(--spacing) * 6)}.bottom-8{bottom:calc(var(--spacing) * 8)}.bottom-10{bottom:calc(var(--spacing) * 10)}.bottom-12{bottom:calc(var(--spacing) * 12)}.bottom-16{bottom:calc(var(--spacing) * 16)}.bottom-auto{bottom:auto}.bottom-full{bottom:100%}.-left-1{left:calc(var(--spacing) * -1)}.-left-2{left:calc(var(--spacing) * -2)}.-left-3{left:calc(var(--spacing) * -3)}.-left-4{left:calc(var(--spacing) * -4)}.left-0{left:0}.left-1{left:var(--spacing)}.left-1\.5{left:calc(var(--spacing) * 1.5)}.left-1\/2{left:50%}.left-2{left:calc(var(--spacing) * 2)}.left-3{left:calc(var(--spacing) * 3)}.left-4{left:calc(var(--spacing) * 4)}.left-5{left:calc(var(--spacing) * 5)}.left-6{left:calc(var(--spacing) * 6)}.left-8{left:calc(var(--spacing) * 8)}.left-10{left:calc(var(--spacing) * 10)}.left-12{left:calc(var(--spacing) * 12)}.left-16{left:calc(var(--spacing) * 16)}.left-auto{left:auto}.left-full{left:100%}.isolate{isolation:isolate}.isolation-auto{isolation:auto}.z-0{z-index:0}.z-10{z-index:10}.z-20{z-index:20}.z-30{z-index:30}.z-40{z-index:40}.z-50{z-index:50}.z-\[999\]{z-index:999}.z-\[9999\]{z-index:9999}.z-auto{z-index:auto}.order-1{order:1}.order-2{order:2}.order-3{order:3}.order-4{order:4}.order-5{order:5}.order-6{order:6}.order-first{order:-9999}.order-last{order:9999}.order-none{order:0}.col-span-1{grid-column:span 1/span 1}.col-span-2{grid-column:span 2/span 2}.col-span-3{grid-column:span 3/span 3}.col-span-4{grid-column:span 4/span 4}.col-span-5{grid-column:span 5/span 5}.col-span-6{grid-column:span 6/span 6}.col-span-7{grid-column:span 7/span 7}.col-span-8{grid-column:span 8/span 8}.col-span-9{grid-column:span 9/span 9}.col-span-10{grid-column:span 10/span 10}.col-span-11{grid-column:span 11/span 11}.col-span-12{grid-column:span 12/span 12}.col-span-full{grid-column:1/-1}.col-start-1{grid-column-start:1}.col-start-2{grid-column-start:2}.col-start-3{grid-column-start:3}.col-start-4{grid-column-start:4}.col-start-5{grid-column-start:5}.col-start-6{grid-column-start:6}.col-start-7{grid-column-start:7}.col-start-8{grid-column-start:8}.col-start-9{grid-column-start:9}.col-start-10{grid-column-start:10}.col-start-11{grid-column-start:11}.col-start-12{grid-column-start:12}.col-start-13{grid-column-start:13}.col-end-1{grid-column-end:1}.col-end-2{grid-column-end:2}.col-end-3{grid-column-end:3}.col-end-4{grid-column-end:4}.col-end-5{grid-column-end:5}.col-end-6{grid-column-end:6}.col-end-7{grid-column-end:7}.col-end-8{grid-column-end:8}.col-end-9{grid-column-end:9}.col-end-10{grid-column-end:10}.col-end-11{grid-column-end:11}.col-end-12{grid-column-end:12}.col-end-13{grid-column-end:13}.row-span-1{grid-row:span 1/span 1}.row-span-2{grid-row:span 2/span 2}.row-span-3{grid-row:span 3/span 3}.row-span-4{grid-row:span 4/span 4}.row-span-5{grid-row:span 5/span 5}.row-span-6{grid-row:span 6/span 6}.row-span-full{grid-row:1/-1}.row-start-1{grid-row-start:1}.row-start-2{grid-row-start:2}.row-start-3{grid-row-start:3}.row-start-4{grid-row-start:4}.row-start-5{grid-row-start:5}.row-start-6{grid-row-start:6}.row-start-7{grid-row-start:7}.row-end-1{grid-row-end:1}.row-end-2{grid-row-end:2}.row-end-3{grid-row-end:3}.row-end-4{grid-row-end:4}.row-end-5{grid-row-end:5}.row-end-6{grid-row-end:6}.row-end-7{grid-row-end:7}.float-end{float:inline-end}.float-left{float:left}.float-none{float:none}.float-right{float:right}.float-start{float:inline-start}.clear-both{clear:both}.clear-end{clear:inline-end}.clear-left{clear:left}.clear-none{clear:none}.clear-right{clear:right}.clear-start{clear:inline-start}.container{width:100%}@media(min-width:40rem){.container{max-width:40rem}}@media(min-width:48rem){.container{max-width:48rem}}@media(min-width:64rem){.container{max-width:64rem}}@media(min-width:80rem){.container{max-width:80rem}}@media(min-width:96rem){.container{max-width:96rem}}.\!m-0{margin:0!important}.\!m-1{margin:var(--spacing)!important}.\!m-2{margin:calc(var(--spacing) * 2)!important}.\!m-3{margin:calc(var(--spacing) * 3)!important}.\!m-4{margin:calc(var(--spacing) * 4)!important}.\!m-5{margin:calc(var(--spacing) * 5)!important}.\!m-6{margin:calc(var(--spacing) * 6)!important}.\!m-8{margin:calc(var(--spacing) * 8)!important}.\!m-10{margin:calc(var(--spacing) * 10)!important}.\!m-12{margin:calc(var(--spacing) * 12)!important}.\!m-16{margin:calc(var(--spacing) * 16)!important}.\!m-auto{margin:auto!important}.-m-1{margin:calc(var(--spacing) * -1)}.-m-2{margin:calc(var(--spacing) * -2)}.-m-3{margin:calc(var(--spacing) * -3)}.-m-4{margin:calc(var(--spacing) * -4)}.-m-5{margin:calc(var(--spacing) * -5)}.-m-6{margin:calc(var(--spacing) * -6)}.-m-8{margin:calc(var(--spacing) * -8)}.m-0{margin:0}.m-1{margin:var(--spacing)}.m-2{margin:calc(var(--spacing) * 2)}.m-3{margin:calc(var(--spacing) * 3)}.m-4{margin:calc(var(--spacing) * 4)}.m-5{margin:calc(var(--spacing) * 5)}.m-6{margin:calc(var(--spacing) * 6)}.m-8{margin:calc(var(--spacing) * 8)}.m-10{margin:calc(var(--spacing) * 10)}.m-12{margin:calc(var(--spacing) * 12)}.m-16{margin:calc(var(--spacing) * 16)}.m-auto{margin:auto}.\!mx-0{margin-inline:0!important}.\!mx-1{margin-inline:var(--spacing)!important}.\!mx-2{margin-inline:calc(var(--spacing) * 2)!important}.\!mx-3{margin-inline:calc(var(--spacing) * 3)!important}.\!mx-4{margin-inline:calc(var(--spacing) * 4)!important}.\!mx-5{margin-inline:calc(var(--spacing) * 5)!important}.\!mx-6{margin-inline:calc(var(--spacing) * 6)!important}.\!mx-8{margin-inline:calc(var(--spacing) * 8)!important}.\!mx-10{margin-inline:calc(var(--spacing) * 10)!important}.\!mx-12{margin-inline:calc(var(--spacing) * 12)!important}.\!mx-16{margin-inline:calc(var(--spacing) * 16)!important}.\!mx-auto{margin-inline:auto!important}.-mx-1{margin-inline:calc(var(--spacing) * -1)}.-mx-2{margin-inline:calc(var(--spacing) * -2)}.-mx-3{margin-inline:calc(var(--spacing) * -3)}.-mx-4{margin-inline:calc(var(--spacing) * -4)}.-mx-5{margin-inline:calc(var(--spacing) * -5)}.-mx-6{margin-inline:calc(var(--spacing) * -6)}.-mx-8{margin-inline:calc(var(--spacing) * -8)}.mx-0{margin-inline:0}.mx-1{margin-inline:var(--spacing)}.mx-1\.5{margin-inline:calc(var(--spacing) * 1.5)}.mx-2{margin-inline:calc(var(--spacing) * 2)}.mx-3{margin-inline:calc(var(--spacing) * 3)}.mx-4{margin-inline:calc(var(--spacing) * 4)}.mx-5{margin-inline:calc(var(--spacing) * 5)}.mx-6{margin-inline:calc(var(--spacing) * 6)}.mx-8{margin-inline:calc(var(--spacing) * 8)}.mx-10{margin-inline:calc(var(--spacing) * 10)}.mx-12{margin-inline:calc(var(--spacing) * 12)}.mx-16{margin-inline:calc(var(--spacing) * 16)}.mx-auto{margin-inline:auto}.\!my-0{margin-block:0!important}.\!my-1{margin-block:var(--spacing)!important}.\!my-2{margin-block:calc(var(--spacing) * 2)!important}.\!my-3{margin-block:calc(var(--spacing) * 3)!important}.\!my-4{margin-block:calc(var(--spacing) * 4)!important}.\!my-5{margin-block:calc(var(--spacing) * 5)!important}.\!my-6{margin-block:calc(var(--spacing) * 6)!important}.\!my-8{margin-block:calc(var(--spacing) * 8)!important}.\!my-10{margin-block:calc(var(--spacing) * 10)!important}.\!my-12{margin-block:calc(var(--spacing) * 12)!important}.\!my-16{margin-block:calc(var(--spacing) * 16)!important}.\!my-auto{margin-block:auto!important}.-my-1{margin-block:calc(var(--spacing) * -1)}.-my-2{margin-block:calc(var(--spacing) * -2)}.-my-3{margin-block:calc(var(--spacing) * -3)}.-my-4{margin-block:calc(var(--spacing) * -4)}.-my-5{margin-block:calc(var(--spacing) * -5)}.-my-6{margin-block:calc(var(--spacing) * -6)}.-my-8{margin-block:calc(var(--spacing) * -8)}.my-0{margin-block:0}.my-1{margin-block:var(--spacing)}.my-2{margin-block:calc(var(--spacing) * 2)}.my-3{margin-block:calc(var(--spacing) * 3)}.my-4{margin-block:calc(var(--spacing) * 4)}.my-5{margin-block:calc(var(--spacing) * 5)}.my-6{margin-block:calc(var(--spacing) * 6)}.my-8{margin-block:calc(var(--spacing) * 8)}.my-10{margin-block:calc(var(--spacing) * 10)}.my-12{margin-block:calc(var(--spacing) * 12)}.my-16{margin-block:calc(var(--spacing) * 16)}.my-auto{margin-block:auto}.-mt-1{margin-top:calc(var(--spacing) * -1)}.-mt-2{margin-top:calc(var(--spacing) * -2)}.-mt-3{margin-top:calc(var(--spacing) * -3)}.-mt-4{margin-top:calc(var(--spacing) * -4)}.-mt-5{margin-top:calc(var(--spacing) * -5)}.-mt-6{margin-top:calc(var(--spacing) * -6)}.-mt-8{margin-top:calc(var(--spacing) * -8)}.mt-0{margin-top:0}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:var(--spacing)}.mt-1\.5{margin-top:calc(var(--spacing) * 1.5)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mt-4{margin-top:calc(var(--spacing) * 4)}.mt-5{margin-top:calc(var(--spacing) * 5)}.mt-6{margin-top:calc(var(--spacing) * 6)}.mt-8{margin-top:calc(var(--spacing) * 8)}.mt-10{margin-top:calc(var(--spacing) * 10)}.mt-12{margin-top:calc(var(--spacing) * 12)}.mt-16{margin-top:calc(var(--spacing) * 16)}.mt-auto{margin-top:auto}.-mr-1{margin-right:calc(var(--spacing) * -1)}.-mr-2{margin-right:calc(var(--spacing) * -2)}.-mr-3{margin-right:calc(var(--spacing) * -3)}.-mr-4{margin-right:calc(var(--spacing) * -4)}.-mr-5{margin-right:calc(var(--spacing) * -5)}.-mr-6{margin-right:calc(var(--spacing) * -6)}.-mr-8{margin-right:calc(var(--spacing) * -8)}.mr-0{margin-right:0}.mr-1{margin-right:var(--spacing)}.mr-2{margin-right:calc(var(--spacing) * 2)}.mr-3{margin-right:calc(var(--spacing) * 3)}.mr-4{margin-right:calc(var(--spacing) * 4)}.mr-5{margin-right:calc(var(--spacing) * 5)}.mr-6{margin-right:calc(var(--spacing) * 6)}.mr-8{margin-right:calc(var(--spacing) * 8)}.mr-10{margin-right:calc(var(--spacing) * 10)}.mr-12{margin-right:calc(var(--spacing) * 12)}.mr-16{margin-right:calc(var(--spacing) * 16)}.mr-auto{margin-right:auto}.-mb-1{margin-bottom:calc(var(--spacing) * -1)}.-mb-2{margin-bottom:calc(var(--spacing) * -2)}.-mb-3{margin-bottom:calc(var(--spacing) * -3)}.-mb-4{margin-bottom:calc(var(--spacing) * -4)}.-mb-5{margin-bottom:calc(var(--spacing) * -5)}.-mb-6{margin-bottom:calc(var(--spacing) * -6)}.-mb-8{margin-bottom:calc(var(--spacing) * -8)}.-mb-px{margin-bottom:-1px}.mb-0{margin-bottom:0}.mb-1{margin-bottom:var(--spacing)}.mb-1\.5{margin-bottom:calc(var(--spacing) * 1.5)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.mb-5{margin-bottom:calc(var(--spacing) * 5)}.mb-6{margin-bottom:calc(var(--spacing) * 6)}.mb-8{margin-bottom:calc(var(--spacing) * 8)}.mb-10{margin-bottom:calc(var(--spacing) * 10)}.mb-12{margin-bottom:calc(var(--spacing) * 12)}.mb-16{margin-bottom:calc(var(--spacing) * 16)}.mb-auto{margin-bottom:auto}.-ml-1{margin-left:calc(var(--spacing) * -1)}.-ml-2{margin-left:calc(var(--spacing) * -2)}.-ml-3{margin-left:calc(var(--spacing) * -3)}.-ml-4{margin-left:calc(var(--spacing) * -4)}.-ml-5{margin-left:calc(var(--spacing) * -5)}.-ml-6{margin-left:calc(var(--spacing) * -6)}.-ml-8{margin-left:calc(var(--spacing) * -8)}.ml-0{margin-left:0}.ml-0\.5{margin-left:calc(var(--spacing) * .5)}.ml-1{margin-left:var(--spacing)}.ml-2{margin-left:calc(var(--spacing) * 2)}.ml-3{margin-left:calc(var(--spacing) * 3)}.ml-4{margin-left:calc(var(--spacing) * 4)}.ml-5{margin-left:calc(var(--spacing) * 5)}.ml-6{margin-left:calc(var(--spacing) * 6)}.ml-8{margin-left:calc(var(--spacing) * 8)}.ml-10{margin-left:calc(var(--spacing) * 10)}.ml-12{margin-left:calc(var(--spacing) * 12)}.ml-16{margin-left:calc(var(--spacing) * 16)}.ml-auto{margin-left:auto}.box-border{box-sizing:border-box}.box-content{box-sizing:content-box}.line-clamp-1{-webkit-line-clamp:1;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.line-clamp-2{-webkit-line-clamp:2;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.line-clamp-3{-webkit-line-clamp:3;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.\!block{display:block!important}.\!flex{display:flex!important}.\!grid{display:grid!important}.\!hidden{display:none!important}.\!inline{display:inline!important}.\!inline-block{display:inline-block!important}.\!inline-flex{display:inline-flex!important}.block{display:block}.contents{display:contents}.flex{display:flex}.flow-root{display:flow-root}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.inline-grid{display:inline-grid}.inline-table{display:inline-table}.list-item{display:list-item}.table{display:table}.table-caption{display:table-caption}.table-cell{display:table-cell}.table-column{display:table-column}.table-column-group{display:table-column-group}.table-footer-group{display:table-footer-group}.table-header-group{display:table-header-group}.table-row{display:table-row}.table-row-group{display:table-row-group}.aspect-auto{aspect-ratio:auto}.aspect-square{aspect-ratio:1}.aspect-video{aspect-ratio:var(--aspect-video)}.\!h-0{height:0!important}.\!h-1{height:var(--spacing)!important}.\!h-2{height:calc(var(--spacing) * 2)!important}.\!h-3{height:calc(var(--spacing) * 3)!important}.\!h-4{height:calc(var(--spacing) * 4)!important}.\!h-5{height:calc(var(--spacing) * 5)!important}.\!h-6{height:calc(var(--spacing) * 6)!important}.\!h-8{height:calc(var(--spacing) * 8)!important}.\!h-10{height:calc(var(--spacing) * 10)!important}.\!h-12{height:calc(var(--spacing) * 12)!important}.\!h-16{height:calc(var(--spacing) * 16)!important}.\!h-20{height:calc(var(--spacing) * 20)!important}.\!h-24{height:calc(var(--spacing) * 24)!important}.\!h-32{height:calc(var(--spacing) * 32)!important}.\!h-40{height:calc(var(--spacing) * 40)!important}.\!h-48{height:calc(var(--spacing) * 48)!important}.\!h-56{height:calc(var(--spacing) * 56)!important}.\!h-64{height:calc(var(--spacing) * 64)!important}.\!h-72{height:calc(var(--spacing) * 72)!important}.\!h-80{height:calc(var(--spacing) * 80)!important}.\!h-96{height:calc(var(--spacing) * 96)!important}.\!h-auto{height:auto!important}.\!h-fit{height:fit-content!important}.\!h-full{height:100%!important}.\!h-max{height:max-content!important}.\!h-min{height:min-content!important}.\!h-px{height:1px!important}.\!h-screen{height:100vh!important}.h-0{height:0}.h-1{height:var(--spacing)}.h-1\/2{height:50%}.h-1\/3{height:33.3333%}.h-1\/4{height:25%}.h-1\/5{height:20%}.h-1\/6{height:16.6667%}.h-2{height:calc(var(--spacing) * 2)}.h-2\/3{height:66.6667%}.h-2\/4{height:50%}.h-2\/5{height:40%}.h-3{height:calc(var(--spacing) * 3)}.h-3\.5{height:calc(var(--spacing) * 3.5)}.h-3\/4{height:75%}.h-3\/5{height:60%}.h-4{height:calc(var(--spacing) * 4)}.h-4\/5{height:80%}.h-5{height:calc(var(--spacing) * 5)}.h-5\/6{height:83.3333%}.h-6{height:calc(var(--spacing) * 6)}.h-7{height:calc(var(--spacing) * 7)}.h-8{height:calc(var(--spacing) * 8)}.h-9{height:calc(var(--spacing) * 9)}.h-10{height:calc(var(--spacing) * 10)}.h-12{height:calc(var(--spacing) * 12)}.h-14{height:calc(var(--spacing) * 14)}.h-16{height:calc(var(--spacing) * 16)}.h-20{height:calc(var(--spacing) * 20)}.h-24{height:calc(var(--spacing) * 24)}.h-32{height:calc(var(--spacing) * 32)}.h-40{height:calc(var(--spacing) * 40)}.h-48{height:calc(var(--spacing) * 48)}.h-56{height:calc(var(--spacing) * 56)}.h-64{height:calc(var(--spacing) * 64)}.h-72{height:calc(var(--spacing) * 72)}.h-80{height:calc(var(--spacing) * 80)}.h-96{height:calc(var(--spacing) * 96)}.h-\[42px\]{height:42px}.h-auto{height:auto}.h-fit{height:fit-content}.h-full{height:100%}.h-max{height:max-content}.h-min{height:min-content}.h-px{height:1px}.h-screen{height:100vh}.max-h-0{max-height:0}.max-h-20{max-height:calc(var(--spacing) * 20)}.max-h-32{max-height:calc(var(--spacing) * 32)}.max-h-40{max-height:calc(var(--spacing) * 40)}.max-h-48{max-height:calc(var(--spacing) * 48)}.max-h-56{max-height:calc(var(--spacing) * 56)}.max-h-60{max-height:calc(var(--spacing) * 60)}.max-h-64{max-height:calc(var(--spacing) * 64)}.max-h-72{max-height:calc(var(--spacing) * 72)}.max-h-80{max-height:calc(var(--spacing) * 80)}.max-h-96{max-height:calc(var(--spacing) * 96)}.max-h-\[70vh\]{max-height:70vh}.max-h-\[400px\]{max-height:400px}.max-h-\[500px\]{max-height:500px}.max-h-\[calc\(90vh-140px\)\]{max-height:calc(90vh - 140px)}.max-h-\[calc\(100vh-320px\)\]{max-height:calc(100vh - 320px)}.max-h-fit{max-height:fit-content}.max-h-full{max-height:100%}.max-h-max{max-height:max-content}.max-h-min{max-height:min-content}.max-h-none{max-height:none}.max-h-screen{max-height:100vh}.min-h-0{min-height:0}.min-h-10{min-height:calc(var(--spacing) * 10)}.min-h-12{min-height:calc(var(--spacing) * 12)}.min-h-16{min-height:calc(var(--spacing) * 16)}.min-h-20{min-height:calc(var(--spacing) * 20)}.min-h-24{min-height:calc(var(--spacing) * 24)}.min-h-32{min-height:calc(var(--spacing) * 32)}.min-h-40{min-height:calc(var(--spacing) * 40)}.min-h-48{min-height:calc(var(--spacing) * 48)}.min-h-56{min-height:calc(var(--spacing) * 56)}.min-h-64{min-height:calc(var(--spacing) * 64)}.min-h-\[40px\]{min-height:40px}.min-h-\[42px\]{min-height:42px}.min-h-\[60px\]{min-height:60px}.min-h-\[60vh\]{min-height:60vh}.min-h-\[80px\]{min-height:80px}.min-h-\[200px\]{min-height:200px}.min-h-\[400px\]{min-height:400px}.min-h-fit{min-height:fit-content}.min-h-full{min-height:100%}.min-h-max{min-height:max-content}.min-h-min{min-height:min-content}.min-h-screen{min-height:100vh}.\!w-0{width:0!important}.\!w-1{width:var(--spacing)!important}.\!w-1\/2{width:50%!important}.\!w-1\/3{width:33.3333%!important}.\!w-1\/4{width:25%!important}.\!w-1\/5{width:20%!important}.\!w-1\/6{width:16.6667%!important}.\!w-2{width:calc(var(--spacing) * 2)!important}.\!w-2\/3{width:66.6667%!important}.\!w-2\/4{width:50%!important}.\!w-2\/5{width:40%!important}.\!w-3{width:calc(var(--spacing) * 3)!important}.\!w-3\/4{width:75%!important}.\!w-3\/5{width:60%!important}.\!w-4{width:calc(var(--spacing) * 4)!important}.\!w-4\/5{width:80%!important}.\!w-5{width:calc(var(--spacing) * 5)!important}.\!w-5\/6{width:83.3333%!important}.\!w-6{width:calc(var(--spacing) * 6)!important}.\!w-8{width:calc(var(--spacing) * 8)!important}.\!w-10{width:calc(var(--spacing) * 10)!important}.\!w-12{width:calc(var(--spacing) * 12)!important}.\!w-16{width:calc(var(--spacing) * 16)!important}.\!w-20{width:calc(var(--spacing) * 20)!important}.\!w-24{width:calc(var(--spacing) * 24)!important}.\!w-32{width:calc(var(--spacing) * 32)!important}.\!w-40{width:calc(var(--spacing) * 40)!important}.\!w-48{width:calc(var(--spacing) * 48)!important}.\!w-52{width:calc(var(--spacing) * 52)!important}.\!w-56{width:calc(var(--spacing) * 56)!important}.\!w-60{width:calc(var(--spacing) * 60)!important}.\!w-64{width:calc(var(--spacing) * 64)!important}.\!w-72{width:calc(var(--spacing) * 72)!important}.\!w-80{width:calc(var(--spacing) * 80)!important}.\!w-96{width:calc(var(--spacing) * 96)!important}.\!w-auto{width:auto!important}.\!w-fit{width:fit-content!important}.\!w-full{width:100%!important}.\!w-max{width:max-content!important}.\!w-min{width:min-content!important}.\!w-px{width:1px!important}.\!w-screen{width:100vw!important}.w-0{width:0}.w-1{width:var(--spacing)}.w-1\/2{width:50%}.w-1\/3{width:33.3333%}.w-1\/4{width:25%}.w-1\/5{width:20%}.w-1\/6{width:16.6667%}.w-2{width:calc(var(--spacing) * 2)}.w-2\/3{width:66.6667%}.w-2\/4{width:50%}.w-2\/5{width:40%}.w-3{width:calc(var(--spacing) * 3)}.w-3\.5{width:calc(var(--spacing) * 3.5)}.w-3\/4{width:75%}.w-3\/5{width:60%}.w-4{width:calc(var(--spacing) * 4)}.w-4\/5{width:80%}.w-5{width:calc(var(--spacing) * 5)}.w-5\/6{width:83.3333%}.w-6{width:calc(var(--spacing) * 6)}.w-8{width:calc(var(--spacing) * 8)}.w-9{width:calc(var(--spacing) * 9)}.w-10{width:calc(var(--spacing) * 10)}.w-11{width:calc(var(--spacing) * 11)}.w-12{width:calc(var(--spacing) * 12)}.w-14{width:calc(var(--spacing) * 14)}.w-16{width:calc(var(--spacing) * 16)}.w-20{width:calc(var(--spacing) * 20)}.w-24{width:calc(var(--spacing) * 24)}.w-28{width:calc(var(--spacing) * 28)}.w-32{width:calc(var(--spacing) * 32)}.w-36{width:calc(var(--spacing) * 36)}.w-40{width:calc(var(--spacing) * 40)}.w-48{width:calc(var(--spacing) * 48)}.w-52{width:calc(var(--spacing) * 52)}.w-56{width:calc(var(--spacing) * 56)}.w-60{width:calc(var(--spacing) * 60)}.w-64{width:calc(var(--spacing) * 64)}.w-72{width:calc(var(--spacing) * 72)}.w-80{width:calc(var(--spacing) * 80)}.w-96{width:calc(var(--spacing) * 96)}.w-auto{width:auto}.w-fit{width:fit-content}.w-full{width:100%}.w-max{width:max-content}.w-min{width:min-content}.w-px{width:1px}.w-screen{width:100vw}.\!max-w-0{max-width:0!important}.\!max-w-2xl{max-width:var(--container-2xl)!important}.\!max-w-3xl{max-width:var(--container-3xl)!important}.\!max-w-4xl{max-width:var(--container-4xl)!important}.\!max-w-full{max-width:100%!important}.\!max-w-lg{max-width:var(--container-lg)!important}.\!max-w-md{max-width:var(--container-md)!important}.\!max-w-none{max-width:none!important}.\!max-w-sm{max-width:var(--container-sm)!important}.\!max-w-xl{max-width:var(--container-xl)!important}.\!max-w-xs{max-width:var(--container-xs)!important}.max-w-0{max-width:0}.max-w-2xl{max-width:var(--container-2xl)}.max-w-3xl{max-width:var(--container-3xl)}.max-w-4xl{max-width:var(--container-4xl)}.max-w-5xl{max-width:var(--container-5xl)}.max-w-6xl{max-width:var(--container-6xl)}.max-w-7xl{max-width:var(--container-7xl)}.max-w-\[200px\]{max-width:200px}.max-w-\[calc\(100vw-1rem\)\]{max-width:calc(100vw - 1rem)}.max-w-\[calc\(100vw-2rem\)\]{max-width:calc(100vw - 2rem)}.max-w-fit{max-width:fit-content}.max-w-full{max-width:100%}.max-w-lg{max-width:var(--container-lg)}.max-w-max{max-width:max-content}.max-w-md{max-width:var(--container-md)}.max-w-min{max-width:min-content}.max-w-none{max-width:none}.max-w-prose{max-width:65ch}.max-w-screen-2xl{max-width:var(--breakpoint-2xl)}.max-w-screen-lg{max-width:var(--breakpoint-lg)}.max-w-screen-md{max-width:var(--breakpoint-md)}.max-w-screen-sm{max-width:var(--breakpoint-sm)}.max-w-screen-xl{max-width:var(--breakpoint-xl)}.max-w-sm{max-width:var(--container-sm)}.max-w-xl{max-width:var(--container-xl)}.max-w-xs{max-width:var(--container-xs)}.\!min-w-0{min-width:0!important}.\!min-w-4{min-width:calc(var(--spacing) * 4)!important}.\!min-w-8{min-width:calc(var(--spacing) * 8)!important}.\!min-w-12{min-width:calc(var(--spacing) * 12)!important}.\!min-w-16{min-width:calc(var(--spacing) * 16)!important}.\!min-w-20{min-width:calc(var(--spacing) * 20)!important}.\!min-w-24{min-width:calc(var(--spacing) * 24)!important}.\!min-w-32{min-width:calc(var(--spacing) * 32)!important}.\!min-w-40{min-width:calc(var(--spacing) * 40)!important}.\!min-w-48{min-width:calc(var(--spacing) * 48)!important}.\!min-w-56{min-width:calc(var(--spacing) * 56)!important}.\!min-w-64{min-width:calc(var(--spacing) * 64)!important}.\!min-w-fit{min-width:fit-content!important}.\!min-w-full{min-width:100%!important}.\!min-w-max{min-width:max-content!important}.\!min-w-min{min-width:min-content!important}.min-w-0{min-width:0}.min-w-\[20px\]{min-width:20px}.min-w-\[80px\]{min-width:80px}.min-w-\[120px\]{min-width:120px}.min-w-\[200px\]{min-width:200px}.min-w-\[280px\]{min-width:280px}.min-w-\[300px\]{min-width:300px}.min-w-fit{min-width:fit-content}.min-w-full{min-width:100%}.min-w-max{min-width:max-content}.min-w-min{min-width:min-content}.\!flex-1{flex:1!important}.\!flex-auto{flex:auto!important}.\!flex-initial{flex:0 auto!important}.\!flex-none{flex:none!important}.flex-1{flex:1}.flex-auto{flex:auto}.flex-initial{flex:0 auto}.flex-none{flex:none}.flex-shrink{flex-shrink:1}.flex-shrink-0{flex-shrink:0}.shrink{flex-shrink:1}.shrink-0{flex-shrink:0}.flex-grow,.grow{flex-grow:1}.grow-0{flex-grow:0}.basis-0{flex-basis:0}.basis-1{flex-basis:var(--spacing)}.basis-1\/2{flex-basis:50%}.basis-1\/3{flex-basis:33.3333%}.basis-1\/4{flex-basis:25%}.basis-2{flex-basis:calc(var(--spacing) * 2)}.basis-2\/3{flex-basis:66.6667%}.basis-3\/4{flex-basis:75%}.basis-auto{flex-basis:auto}.basis-full{flex-basis:100%}.table-auto{table-layout:auto}.table-fixed{table-layout:fixed}.caption-bottom{caption-side:bottom}.caption-top{caption-side:top}.border-collapse{border-collapse:collapse}.border-separate{border-collapse:separate}.border-spacing-0{--tw-border-spacing-x:0px;--tw-border-spacing-y:0px;border-spacing:var(--tw-border-spacing-x) var(--tw-border-spacing-y)}.border-spacing-1{--tw-border-spacing-x:var(--spacing);--tw-border-spacing-y:var(--spacing);border-spacing:var(--tw-border-spacing-x) var(--tw-border-spacing-y)}.border-spacing-2{--tw-border-spacing-x:calc(var(--spacing) * 2);--tw-border-spacing-y:calc(var(--spacing) * 2);border-spacing:var(--tw-border-spacing-x) var(--tw-border-spacing-y)}.border-spacing-3{--tw-border-spacing-x:calc(var(--spacing) * 3);--tw-border-spacing-y:calc(var(--spacing) * 3);border-spacing:var(--tw-border-spacing-x) var(--tw-border-spacing-y)}.border-spacing-4{--tw-border-spacing-x:calc(var(--spacing) * 4);--tw-border-spacing-y:calc(var(--spacing) * 4);border-spacing:var(--tw-border-spacing-x) var(--tw-border-spacing-y)}.border-spacing-5{--tw-border-spacing-x:calc(var(--spacing) * 5);--tw-border-spacing-y:calc(var(--spacing) * 5);border-spacing:var(--tw-border-spacing-x) var(--tw-border-spacing-y)}.border-spacing-6{--tw-border-spacing-x:calc(var(--spacing) * 6);--tw-border-spacing-y:calc(var(--spacing) * 6);border-spacing:var(--tw-border-spacing-x) var(--tw-border-spacing-y)}.border-spacing-8{--tw-border-spacing-x:calc(var(--spacing) * 8);--tw-border-spacing-y:calc(var(--spacing) * 8);border-spacing:var(--tw-border-spacing-x) var(--tw-border-spacing-y)}.origin-bottom{transform-origin:bottom}.origin-bottom-left{transform-origin:0 100%}.origin-bottom-right{transform-origin:100% 100%}.origin-center{transform-origin:50%}.origin-left{transform-origin:0}.origin-right{transform-origin:100%}.origin-top{transform-origin:top}.origin-top-left{transform-origin:0 0}.origin-top-right{transform-origin:100% 0}.-translate-x-1{--tw-translate-x:calc(var(--spacing) * -1);translate:var(--tw-translate-x) var(--tw-translate-y)}.-translate-x-1\/2{--tw-translate-x: -50% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.-translate-x-2{--tw-translate-x:calc(var(--spacing) * -2);translate:var(--tw-translate-x) var(--tw-translate-y)}.-translate-x-3{--tw-translate-x:calc(var(--spacing) * -3);translate:var(--tw-translate-x) var(--tw-translate-y)}.-translate-x-4{--tw-translate-x:calc(var(--spacing) * -4);translate:var(--tw-translate-x) var(--tw-translate-y)}.-translate-x-full{--tw-translate-x:-100%;translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-0{--tw-translate-x:0px;translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-1{--tw-translate-x:var(--spacing);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-1\/2{--tw-translate-x: 50% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-1\/3{--tw-translate-x:calc(1 / 3 * 100%);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-1\/4{--tw-translate-x: 25% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-2{--tw-translate-x:calc(var(--spacing) * 2);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-2\/3{--tw-translate-x:calc(2 / 3 * 100%);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-3{--tw-translate-x:calc(var(--spacing) * 3);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-3\/4{--tw-translate-x: 75% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-4{--tw-translate-x:calc(var(--spacing) * 4);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-5{--tw-translate-x:calc(var(--spacing) * 5);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-6{--tw-translate-x:calc(var(--spacing) * 6);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-8{--tw-translate-x:calc(var(--spacing) * 8);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-10{--tw-translate-x:calc(var(--spacing) * 10);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-12{--tw-translate-x:calc(var(--spacing) * 12);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-full{--tw-translate-x:100%;translate:var(--tw-translate-x) var(--tw-translate-y)}.-translate-y-1{--tw-translate-y:calc(var(--spacing) * -1);translate:var(--tw-translate-x) var(--tw-translate-y)}.-translate-y-1\/2{--tw-translate-y: -50% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.-translate-y-2{--tw-translate-y:calc(var(--spacing) * -2);translate:var(--tw-translate-x) var(--tw-translate-y)}.-translate-y-3{--tw-translate-y:calc(var(--spacing) * -3);translate:var(--tw-translate-x) var(--tw-translate-y)}.-translate-y-4{--tw-translate-y:calc(var(--spacing) * -4);translate:var(--tw-translate-x) var(--tw-translate-y)}.-translate-y-full{--tw-translate-y:-100%;translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-0{--tw-translate-y:0px;translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-1{--tw-translate-y:var(--spacing);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-1\/2{--tw-translate-y: 50% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-1\/3{--tw-translate-y:calc(1 / 3 * 100%);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-1\/4{--tw-translate-y: 25% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-2{--tw-translate-y:calc(var(--spacing) * 2);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-2\/3{--tw-translate-y:calc(2 / 3 * 100%);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-3{--tw-translate-y:calc(var(--spacing) * 3);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-3\/4{--tw-translate-y: 75% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-4{--tw-translate-y:calc(var(--spacing) * 4);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-5{--tw-translate-y:calc(var(--spacing) * 5);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-6{--tw-translate-y:calc(var(--spacing) * 6);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-8{--tw-translate-y:calc(var(--spacing) * 8);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-10{--tw-translate-y:calc(var(--spacing) * 10);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-12{--tw-translate-y:calc(var(--spacing) * 12);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-full{--tw-translate-y:100%;translate:var(--tw-translate-x) var(--tw-translate-y)}.scale-0{--tw-scale-x:0%;--tw-scale-y:0%;--tw-scale-z:0%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-50{--tw-scale-x:50%;--tw-scale-y:50%;--tw-scale-z:50%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-75{--tw-scale-x:75%;--tw-scale-y:75%;--tw-scale-z:75%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-90{--tw-scale-x:90%;--tw-scale-y:90%;--tw-scale-z:90%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-95{--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-100{--tw-scale-x:100%;--tw-scale-y:100%;--tw-scale-z:100%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-105{--tw-scale-x:105%;--tw-scale-y:105%;--tw-scale-z:105%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-110{--tw-scale-x:110%;--tw-scale-y:110%;--tw-scale-z:110%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-125{--tw-scale-x:125%;--tw-scale-y:125%;--tw-scale-z:125%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-150{--tw-scale-x:150%;--tw-scale-y:150%;--tw-scale-z:150%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-x-0{--tw-scale-x:0%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-x-50{--tw-scale-x:50%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-x-75{--tw-scale-x:75%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-x-90{--tw-scale-x:90%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-x-95{--tw-scale-x:95%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-x-100{--tw-scale-x:100%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-x-105{--tw-scale-x:105%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-x-110{--tw-scale-x:110%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-x-125{--tw-scale-x:125%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-x-150{--tw-scale-x:150%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-y-0{--tw-scale-y:0%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-y-50{--tw-scale-y:50%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-y-75{--tw-scale-y:75%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-y-90{--tw-scale-y:90%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-y-95{--tw-scale-y:95%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-y-100{--tw-scale-y:100%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-y-105{--tw-scale-y:105%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-y-110{--tw-scale-y:110%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-y-125{--tw-scale-y:125%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-y-150{--tw-scale-y:150%;scale:var(--tw-scale-x) var(--tw-scale-y)}.-rotate-1{rotate:-1deg}.-rotate-2{rotate:-2deg}.-rotate-3{rotate:-3deg}.-rotate-6{rotate:-6deg}.-rotate-12{rotate:-12deg}.-rotate-45{rotate:-45deg}.-rotate-90{rotate:-90deg}.-rotate-180{rotate:-180deg}.rotate-0{rotate:0deg}.rotate-1{rotate:1deg}.rotate-2{rotate:2deg}.rotate-3{rotate:3deg}.rotate-6{rotate:6deg}.rotate-12{rotate:12deg}.rotate-45{rotate:45deg}.rotate-90{rotate:90deg}.rotate-180{rotate:180deg}.-skew-x-1{--tw-skew-x:skewX( -1deg );transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.-skew-x-2{--tw-skew-x:skewX( -2deg );transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.-skew-x-3{--tw-skew-x:skewX( -3deg );transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.-skew-x-6{--tw-skew-x:skewX( -6deg );transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.-skew-x-12{--tw-skew-x:skewX( -12deg );transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.skew-x-0{--tw-skew-x:skewX(0deg);transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.skew-x-1{--tw-skew-x:skewX(1deg);transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.skew-x-2{--tw-skew-x:skewX(2deg);transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.skew-x-3{--tw-skew-x:skewX(3deg);transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.skew-x-6{--tw-skew-x:skewX(6deg);transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.skew-x-12{--tw-skew-x:skewX(12deg);transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.-skew-y-1{--tw-skew-y:skewY( -1deg );transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.-skew-y-2{--tw-skew-y:skewY( -2deg );transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.-skew-y-3{--tw-skew-y:skewY( -3deg );transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.-skew-y-6{--tw-skew-y:skewY( -6deg );transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.-skew-y-12{--tw-skew-y:skewY( -12deg );transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.skew-y-0{--tw-skew-y:skewY(0deg);transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.skew-y-1{--tw-skew-y:skewY(1deg);transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.skew-y-2{--tw-skew-y:skewY(2deg);transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.skew-y-3{--tw-skew-y:skewY(3deg);transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.skew-y-6{--tw-skew-y:skewY(6deg);transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.skew-y-12{--tw-skew-y:skewY(12deg);transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.transform,.transform-cpu{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.transform-gpu{transform:translateZ(0) var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.transform-none{transform:none}.animate-\[loading_1\.5s_ease-in-out_infinite\]{animation:1.5s ease-in-out infinite loading}.animate-bounce{animation:var(--animate-bounce)}.animate-none{animation:none}.animate-ping{animation:var(--animate-ping)}.animate-pulse{animation:var(--animate-pulse)}.animate-spin{animation:var(--animate-spin)}.cursor-alias{cursor:alias}.cursor-all-scroll{cursor:all-scroll}.cursor-auto{cursor:auto}.cursor-cell{cursor:cell}.cursor-col-resize{cursor:col-resize}.cursor-context-menu{cursor:context-menu}.cursor-copy{cursor:copy}.cursor-crosshair{cursor:crosshair}.cursor-default{cursor:default}.cursor-e-resize{cursor:e-resize}.cursor-ew-resize{cursor:ew-resize}.cursor-grab{cursor:grab}.cursor-grabbing{cursor:grabbing}.cursor-help{cursor:help}.cursor-move{cursor:move}.cursor-n-resize{cursor:n-resize}.cursor-ne-resize{cursor:ne-resize}.cursor-nesw-resize{cursor:nesw-resize}.cursor-no-drop{cursor:no-drop}.cursor-none{cursor:none}.cursor-not-allowed{cursor:not-allowed}.cursor-ns-resize{cursor:ns-resize}.cursor-nw-resize{cursor:nw-resize}.cursor-nwse-resize{cursor:nwse-resize}.cursor-pointer{cursor:pointer}.cursor-progress{cursor:progress}.cursor-row-resize{cursor:row-resize}.cursor-s-resize{cursor:s-resize}.cursor-se-resize{cursor:se-resize}.cursor-sw-resize{cursor:sw-resize}.cursor-text{cursor:text}.cursor-vertical-text{cursor:vertical-text}.cursor-w-resize{cursor:w-resize}.cursor-wait{cursor:wait}.cursor-zoom-in{cursor:zoom-in}.cursor-zoom-out{cursor:zoom-out}.touch-pan-left{--tw-pan-x:pan-left;touch-action:var(--tw-pan-x,) var(--tw-pan-y,) var(--tw-pinch-zoom,)}.touch-pan-right{--tw-pan-x:pan-right;touch-action:var(--tw-pan-x,) var(--tw-pan-y,) var(--tw-pinch-zoom,)}.touch-pan-x{--tw-pan-x:pan-x;touch-action:var(--tw-pan-x,) var(--tw-pan-y,) var(--tw-pinch-zoom,)}.touch-pan-down{--tw-pan-y:pan-down;touch-action:var(--tw-pan-x,) var(--tw-pan-y,) var(--tw-pinch-zoom,)}.touch-pan-up{--tw-pan-y:pan-up;touch-action:var(--tw-pan-x,) var(--tw-pan-y,) var(--tw-pinch-zoom,)}.touch-pan-y{--tw-pan-y:pan-y;touch-action:var(--tw-pan-x,) var(--tw-pan-y,) var(--tw-pinch-zoom,)}.touch-pinch-zoom{--tw-pinch-zoom:pinch-zoom;touch-action:var(--tw-pan-x,) var(--tw-pan-y,) var(--tw-pinch-zoom,)}.touch-auto{touch-action:auto}.touch-manipulation{touch-action:manipulation}.touch-none{touch-action:none}.resize{resize:both}.resize-none{resize:none}.resize-x{resize:horizontal}.resize-y{resize:vertical}.snap-both{scroll-snap-type:both var(--tw-scroll-snap-strictness)}.snap-none{scroll-snap-type:none}.snap-x{scroll-snap-type:x var(--tw-scroll-snap-strictness)}.snap-y{scroll-snap-type:y var(--tw-scroll-snap-strictness)}.snap-mandatory{--tw-scroll-snap-strictness:mandatory}.snap-proximity{--tw-scroll-snap-strictness:proximity}.snap-align-none{scroll-snap-align:none}.snap-center{scroll-snap-align:center}.snap-end{scroll-snap-align:end}.snap-start{scroll-snap-align:start}.snap-always{scroll-snap-stop:always}.snap-normal{scroll-snap-stop:normal}.scroll-mt-44{scroll-margin-top:calc(var(--spacing) * 44)}.list-inside{list-style-position:inside}.list-outside{list-style-position:outside}.list-decimal{list-style-type:decimal}.list-disc{list-style-type:disc}.list-none{list-style-type:none}.appearance-auto{appearance:auto}.appearance-none{appearance:none}.columns-1{columns:1}.columns-2{columns:2}.columns-2xl{columns:var(--container-2xl)}.columns-2xs{columns:var(--container-2xs)}.columns-3{columns:3}.columns-3xl{columns:var(--container-3xl)}.columns-3xs{columns:var(--container-3xs)}.columns-4{columns:4}.columns-4xl{columns:var(--container-4xl)}.columns-5{columns:5}.columns-5xl{columns:var(--container-5xl)}.columns-6{columns:6}.columns-6xl{columns:var(--container-6xl)}.columns-7{columns:7}.columns-7xl{columns:var(--container-7xl)}.columns-8{columns:8}.columns-9{columns:9}.columns-10{columns:10}.columns-11{columns:11}.columns-12{columns:12}.columns-auto{columns:auto}.columns-lg{columns:var(--container-lg)}.columns-md{columns:var(--container-md)}.columns-sm{columns:var(--container-sm)}.columns-xl{columns:var(--container-xl)}.columns-xs{columns:var(--container-xs)}.break-before-all{break-before:all}.break-before-auto{break-before:auto}.break-before-avoid{break-before:avoid}.break-before-avoid-page{break-before:avoid-page}.break-before-column{break-before:column}.break-before-left{break-before:left}.break-before-page{break-before:page}.break-before-right{break-before:right}.break-inside-auto{break-inside:auto}.break-inside-avoid{break-inside:avoid}.break-inside-avoid-column{break-inside:avoid-column}.break-inside-avoid-page{break-inside:avoid-page}.break-after-all{break-after:all}.break-after-auto{break-after:auto}.break-after-avoid{break-after:avoid}.break-after-avoid-page{break-after:avoid-page}.break-after-column{break-after:column}.break-after-left{break-after:left}.break-after-page{break-after:page}.break-after-right{break-after:right}.auto-cols-auto{grid-auto-columns:auto}.auto-cols-fr{grid-auto-columns:minmax(0,1fr)}.auto-cols-max{grid-auto-columns:max-content}.auto-cols-min{grid-auto-columns:min-content}.grid-flow-col{grid-auto-flow:column}.grid-flow-col-dense{grid-auto-flow:column dense}.grid-flow-dense{grid-auto-flow:dense}.grid-flow-row{grid-auto-flow:row}.grid-flow-row-dense{grid-auto-flow:dense}.auto-rows-auto{grid-auto-rows:auto}.auto-rows-fr{grid-auto-rows:minmax(0,1fr)}.auto-rows-max{grid-auto-rows:max-content}.auto-rows-min{grid-auto-rows:min-content}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr))}.grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.grid-cols-7{grid-template-columns:repeat(7,minmax(0,1fr))}.grid-cols-8{grid-template-columns:repeat(8,minmax(0,1fr))}.grid-cols-9{grid-template-columns:repeat(9,minmax(0,1fr))}.grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.grid-cols-11{grid-template-columns:repeat(11,minmax(0,1fr))}.grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.grid-rows-1{grid-template-rows:repeat(1,minmax(0,1fr))}.grid-rows-2{grid-template-rows:repeat(2,minmax(0,1fr))}.grid-rows-3{grid-template-rows:repeat(3,minmax(0,1fr))}.grid-rows-4{grid-template-rows:repeat(4,minmax(0,1fr))}.grid-rows-5{grid-template-rows:repeat(5,minmax(0,1fr))}.grid-rows-6{grid-template-rows:repeat(6,minmax(0,1fr))}.\!flex-col{flex-direction:column!important}.\!flex-row{flex-direction:row!important}.flex-col{flex-direction:column}.flex-col-reverse{flex-direction:column-reverse}.flex-row{flex-direction:row}.flex-row-reverse{flex-direction:row-reverse}.\!flex-nowrap{flex-wrap:nowrap!important}.\!flex-wrap{flex-wrap:wrap!important}.flex-nowrap{flex-wrap:nowrap}.flex-wrap{flex-wrap:wrap}.flex-wrap-reverse{flex-wrap:wrap-reverse}.place-content-around{place-content:space-around}.place-content-baseline{place-content:baseline start}.place-content-between{place-content:space-between}.place-content-center{place-content:center}.place-content-end{place-content:end}.place-content-evenly{place-content:space-evenly}.place-content-start{place-content:start}.place-content-stretch{place-content:stretch}.place-items-baseline{place-items:baseline}.place-items-center{place-items:center}.place-items-end{place-items:end}.place-items-start{place-items:start}.place-items-stretch{place-items:stretch stretch}.content-around{align-content:space-around}.content-between{align-content:space-between}.content-center{align-content:center}.content-end{align-content:flex-end}.content-evenly{align-content:space-evenly}.content-start{align-content:flex-start}.items-baseline{align-items:baseline}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.items-stretch{align-items:stretch}.justify-around{justify-content:space-around}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.justify-evenly{justify-content:space-evenly}.justify-start{justify-content:flex-start}.\!gap-0{gap:0!important}.\!gap-1{gap:var(--spacing)!important}.\!gap-2{gap:calc(var(--spacing) * 2)!important}.\!gap-3{gap:calc(var(--spacing) * 3)!important}.\!gap-4{gap:calc(var(--spacing) * 4)!important}.\!gap-5{gap:calc(var(--spacing) * 5)!important}.\!gap-6{gap:calc(var(--spacing) * 6)!important}.\!gap-8{gap:calc(var(--spacing) * 8)!important}.\!gap-10{gap:calc(var(--spacing) * 10)!important}.\!gap-12{gap:calc(var(--spacing) * 12)!important}.\!gap-16{gap:calc(var(--spacing) * 16)!important}.gap-0{gap:0}.gap-0\.5{gap:calc(var(--spacing) * .5)}.gap-1{gap:var(--spacing)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-2\.5{gap:calc(var(--spacing) * 2.5)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}.gap-5{gap:calc(var(--spacing) * 5)}.gap-6{gap:calc(var(--spacing) * 6)}.gap-8{gap:calc(var(--spacing) * 8)}.gap-10{gap:calc(var(--spacing) * 10)}.gap-12{gap:calc(var(--spacing) * 12)}.gap-16{gap:calc(var(--spacing) * 16)}:where(.-space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * -1) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * -1) * calc(1 - var(--tw-space-y-reverse)))}:where(.-space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * -2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * -2) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-0>:not(:last-child)){--tw-space-y-reverse:0;margin-block:0}:where(.space-y-0\.5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * .5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * .5) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(var(--spacing) * var(--tw-space-y-reverse));margin-block-end:calc(var(--spacing) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 5) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 6) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-8>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 8) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 8) * calc(1 - var(--tw-space-y-reverse)))}.gap-x-0{column-gap:0}.gap-x-1{column-gap:var(--spacing)}.gap-x-2{column-gap:calc(var(--spacing) * 2)}.gap-x-3{column-gap:calc(var(--spacing) * 3)}.gap-x-4{column-gap:calc(var(--spacing) * 4)}.gap-x-5{column-gap:calc(var(--spacing) * 5)}.gap-x-6{column-gap:calc(var(--spacing) * 6)}.gap-x-8{column-gap:calc(var(--spacing) * 8)}.gap-x-10{column-gap:calc(var(--spacing) * 10)}.gap-x-12{column-gap:calc(var(--spacing) * 12)}:where(.-space-x-1>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * -1) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * -1) * calc(1 - var(--tw-space-x-reverse)))}:where(.-space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * -2) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * -2) * calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-0>:not(:last-child)){--tw-space-x-reverse:0;margin-inline:0}:where(.space-x-1>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(var(--spacing) * var(--tw-space-x-reverse));margin-inline-end:calc(var(--spacing) * calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 2) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-3>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 3) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-4>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 4) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-5>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 5) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 5) * calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-6>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 6) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-8>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 8) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 8) * calc(1 - var(--tw-space-x-reverse)))}.gap-y-0{row-gap:0}.gap-y-1{row-gap:var(--spacing)}.gap-y-2{row-gap:calc(var(--spacing) * 2)}.gap-y-3{row-gap:calc(var(--spacing) * 3)}.gap-y-4{row-gap:calc(var(--spacing) * 4)}.gap-y-5{row-gap:calc(var(--spacing) * 5)}.gap-y-6{row-gap:calc(var(--spacing) * 6)}.gap-y-8{row-gap:calc(var(--spacing) * 8)}.gap-y-10{row-gap:calc(var(--spacing) * 10)}.gap-y-12{row-gap:calc(var(--spacing) * 12)}:where(.divide-x>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-style:var(--tw-border-style);border-inline-start-width:calc(1px * var(--tw-divide-x-reverse));border-inline-end-width:calc(1px * calc(1 - var(--tw-divide-x-reverse)))}:where(.divide-x-0>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-style:var(--tw-border-style);border-inline-start-width:calc(0px * var(--tw-divide-x-reverse));border-inline-end-width:calc(0px * calc(1 - var(--tw-divide-x-reverse)))}:where(.divide-x-2>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-style:var(--tw-border-style);border-inline-start-width:calc(2px * var(--tw-divide-x-reverse));border-inline-end-width:calc(2px * calc(1 - var(--tw-divide-x-reverse)))}:where(.divide-x-4>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-style:var(--tw-border-style);border-inline-start-width:calc(4px * var(--tw-divide-x-reverse));border-inline-end-width:calc(4px * calc(1 - var(--tw-divide-x-reverse)))}:where(.divide-x-8>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-style:var(--tw-border-style);border-inline-start-width:calc(8px * var(--tw-divide-x-reverse));border-inline-end-width:calc(8px * calc(1 - var(--tw-divide-x-reverse)))}:where(.divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px * var(--tw-divide-y-reverse));border-bottom-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-y-0>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(0px * var(--tw-divide-y-reverse));border-bottom-width:calc(0px * calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-y-2>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(2px * var(--tw-divide-y-reverse));border-bottom-width:calc(2px * calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-y-4>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(4px * var(--tw-divide-y-reverse));border-bottom-width:calc(4px * calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-y-8>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(8px * var(--tw-divide-y-reverse));border-bottom-width:calc(8px * calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-y-reverse>:not(:last-child)){--tw-divide-y-reverse:1}:where(.divide-dashed>:not(:last-child)){--tw-border-style:dashed;border-style:dashed}:where(.divide-dotted>:not(:last-child)){--tw-border-style:dotted;border-style:dotted}:where(.divide-double>:not(:last-child)){--tw-border-style:double;border-style:double}:where(.divide-none>:not(:last-child)){--tw-border-style:none;border-style:none}:where(.divide-solid>:not(:last-child)){--tw-border-style:solid;border-style:solid}:where(.divide-black>:not(:last-child)){border-color:var(--color-black)}:where(.divide-current>:not(:last-child)){border-color:currentColor}:where(.divide-gray-50>:not(:last-child)){border-color:var(--color-gray-50)}:where(.divide-gray-100>:not(:last-child)){border-color:var(--color-gray-100)}:where(.divide-gray-200>:not(:last-child)){border-color:var(--color-gray-200)}:where(.divide-gray-300>:not(:last-child)){border-color:var(--color-gray-300)}:where(.divide-gray-400>:not(:last-child)){border-color:var(--color-gray-400)}:where(.divide-gray-500>:not(:last-child)){border-color:var(--color-gray-500)}:where(.divide-gray-600>:not(:last-child)){border-color:var(--color-gray-600)}:where(.divide-gray-700>:not(:last-child)){border-color:var(--color-gray-700)}:where(.divide-gray-800>:not(:last-child)){border-color:var(--color-gray-800)}:where(.divide-gray-900>:not(:last-child)){border-color:var(--color-gray-900)}:where(.divide-inherit>:not(:last-child)){border-color:inherit}:where(.divide-purple-100>:not(:last-child)){border-color:var(--color-purple-100)}:where(.divide-slate-50>:not(:last-child)){border-color:var(--color-slate-50)}:where(.divide-slate-100>:not(:last-child)){border-color:var(--color-slate-100)}:where(.divide-slate-200>:not(:last-child)){border-color:var(--color-slate-200)}:where(.divide-slate-300>:not(:last-child)){border-color:var(--color-slate-300)}:where(.divide-slate-400>:not(:last-child)){border-color:var(--color-slate-400)}:where(.divide-slate-500>:not(:last-child)){border-color:var(--color-slate-500)}:where(.divide-slate-600>:not(:last-child)){border-color:var(--color-slate-600)}:where(.divide-slate-700>:not(:last-child)){border-color:var(--color-slate-700)}:where(.divide-slate-800>:not(:last-child)){border-color:var(--color-slate-800)}:where(.divide-slate-900>:not(:last-child)){border-color:var(--color-slate-900)}:where(.divide-transparent>:not(:last-child)){border-color:#0000}:where(.divide-white>:not(:last-child)){border-color:var(--color-white)}.place-self-auto{place-self:auto}.place-self-center{place-self:center}.place-self-end{place-self:end}.place-self-start{place-self:start}.place-self-stretch{place-self:stretch stretch}.self-auto{align-self:auto}.self-baseline{align-self:baseline}.self-center{align-self:center}.self-end{align-self:flex-end}.self-start{align-self:flex-start}.self-stretch{align-self:stretch}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-clip{overflow:clip}.overflow-hidden{overflow:hidden}.overflow-scroll{overflow:scroll}.overflow-visible{overflow:visible}.overflow-x-auto{overflow-x:auto}.overflow-x-clip{overflow-x:clip}.overflow-x-hidden{overflow-x:hidden}.overflow-x-scroll{overflow-x:scroll}.overflow-x-visible{overflow-x:visible}.overflow-y-auto{overflow-y:auto}.overflow-y-clip{overflow-y:clip}.overflow-y-hidden{overflow-y:hidden}.overflow-y-scroll{overflow-y:scroll}.overflow-y-visible{overflow-y:visible}.overscroll-auto{overscroll-behavior:auto}.overscroll-contain{overscroll-behavior:contain}.overscroll-none{overscroll-behavior:none}.overscroll-x-auto{overscroll-behavior-x:auto}.overscroll-x-contain{overscroll-behavior-x:contain}.overscroll-x-none{overscroll-behavior-x:none}.overscroll-y-auto{overscroll-behavior-y:auto}.overscroll-y-contain{overscroll-behavior-y:contain}.overscroll-y-none{overscroll-behavior-y:none}.scroll-auto{scroll-behavior:auto}.scroll-smooth{scroll-behavior:smooth}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:var(--radius-2xl)}.rounded-3xl{border-radius:var(--radius-3xl)}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-none{border-radius:0}.rounded-sm{border-radius:var(--radius-sm)}.rounded-xl{border-radius:var(--radius-xl)}.rounded-t{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.rounded-t-2xl{border-top-left-radius:var(--radius-2xl);border-top-right-radius:var(--radius-2xl)}.rounded-t-3xl{border-top-left-radius:var(--radius-3xl);border-top-right-radius:var(--radius-3xl)}.rounded-t-full{border-top-left-radius:3.40282e38px;border-top-right-radius:3.40282e38px}.rounded-t-lg{border-top-left-radius:var(--radius-lg);border-top-right-radius:var(--radius-lg)}.rounded-t-md{border-top-left-radius:var(--radius-md);border-top-right-radius:var(--radius-md)}.rounded-t-none{border-top-left-radius:0;border-top-right-radius:0}.rounded-t-sm{border-top-left-radius:var(--radius-sm);border-top-right-radius:var(--radius-sm)}.rounded-t-xl{border-top-left-radius:var(--radius-xl);border-top-right-radius:var(--radius-xl)}.rounded-l{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.rounded-l-2xl{border-top-left-radius:var(--radius-2xl);border-bottom-left-radius:var(--radius-2xl)}.rounded-l-3xl{border-top-left-radius:var(--radius-3xl);border-bottom-left-radius:var(--radius-3xl)}.rounded-l-full{border-top-left-radius:3.40282e38px;border-bottom-left-radius:3.40282e38px}.rounded-l-lg{border-top-left-radius:var(--radius-lg);border-bottom-left-radius:var(--radius-lg)}.rounded-l-md{border-top-left-radius:var(--radius-md);border-bottom-left-radius:var(--radius-md)}.rounded-l-none{border-top-left-radius:0;border-bottom-left-radius:0}.rounded-l-sm{border-top-left-radius:var(--radius-sm);border-bottom-left-radius:var(--radius-sm)}.rounded-l-xl{border-top-left-radius:var(--radius-xl);border-bottom-left-radius:var(--radius-xl)}.rounded-tl{border-top-left-radius:.25rem}.rounded-tl-2xl{border-top-left-radius:var(--radius-2xl)}.rounded-tl-3xl{border-top-left-radius:var(--radius-3xl)}.rounded-tl-full{border-top-left-radius:3.40282e38px}.rounded-tl-lg{border-top-left-radius:var(--radius-lg)}.rounded-tl-md{border-top-left-radius:var(--radius-md)}.rounded-tl-none{border-top-left-radius:0}.rounded-tl-sm{border-top-left-radius:var(--radius-sm)}.rounded-tl-xl{border-top-left-radius:var(--radius-xl)}.rounded-r{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.rounded-r-2xl{border-top-right-radius:var(--radius-2xl);border-bottom-right-radius:var(--radius-2xl)}.rounded-r-3xl{border-top-right-radius:var(--radius-3xl);border-bottom-right-radius:var(--radius-3xl)}.rounded-r-full{border-top-right-radius:3.40282e38px;border-bottom-right-radius:3.40282e38px}.rounded-r-lg{border-top-right-radius:var(--radius-lg);border-bottom-right-radius:var(--radius-lg)}.rounded-r-md{border-top-right-radius:var(--radius-md);border-bottom-right-radius:var(--radius-md)}.rounded-r-none{border-top-right-radius:0;border-bottom-right-radius:0}.rounded-r-sm{border-top-right-radius:var(--radius-sm);border-bottom-right-radius:var(--radius-sm)}.rounded-r-xl{border-top-right-radius:var(--radius-xl);border-bottom-right-radius:var(--radius-xl)}.rounded-tr{border-top-right-radius:.25rem}.rounded-tr-2xl{border-top-right-radius:var(--radius-2xl)}.rounded-tr-3xl{border-top-right-radius:var(--radius-3xl)}.rounded-tr-full{border-top-right-radius:3.40282e38px}.rounded-tr-lg{border-top-right-radius:var(--radius-lg)}.rounded-tr-md{border-top-right-radius:var(--radius-md)}.rounded-tr-none{border-top-right-radius:0}.rounded-tr-sm{border-top-right-radius:var(--radius-sm)}.rounded-tr-xl{border-top-right-radius:var(--radius-xl)}.rounded-b{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.rounded-b-2xl{border-bottom-right-radius:var(--radius-2xl);border-bottom-left-radius:var(--radius-2xl)}.rounded-b-3xl{border-bottom-right-radius:var(--radius-3xl);border-bottom-left-radius:var(--radius-3xl)}.rounded-b-full{border-bottom-right-radius:3.40282e38px;border-bottom-left-radius:3.40282e38px}.rounded-b-lg{border-bottom-right-radius:var(--radius-lg);border-bottom-left-radius:var(--radius-lg)}.rounded-b-md{border-bottom-right-radius:var(--radius-md);border-bottom-left-radius:var(--radius-md)}.rounded-b-none{border-bottom-right-radius:0;border-bottom-left-radius:0}.rounded-b-sm{border-bottom-right-radius:var(--radius-sm);border-bottom-left-radius:var(--radius-sm)}.rounded-b-xl{border-bottom-right-radius:var(--radius-xl);border-bottom-left-radius:var(--radius-xl)}.rounded-br{border-bottom-right-radius:.25rem}.rounded-br-2xl{border-bottom-right-radius:var(--radius-2xl)}.rounded-br-3xl{border-bottom-right-radius:var(--radius-3xl)}.rounded-br-full{border-bottom-right-radius:3.40282e38px}.rounded-br-lg{border-bottom-right-radius:var(--radius-lg)}.rounded-br-md{border-bottom-right-radius:var(--radius-md)}.rounded-br-none{border-bottom-right-radius:0}.rounded-br-sm{border-bottom-right-radius:var(--radius-sm)}.rounded-br-xl{border-bottom-right-radius:var(--radius-xl)}.rounded-bl{border-bottom-left-radius:.25rem}.rounded-bl-2xl{border-bottom-left-radius:var(--radius-2xl)}.rounded-bl-3xl{border-bottom-left-radius:var(--radius-3xl)}.rounded-bl-full{border-bottom-left-radius:3.40282e38px}.rounded-bl-lg{border-bottom-left-radius:var(--radius-lg)}.rounded-bl-md{border-bottom-left-radius:var(--radius-md)}.rounded-bl-none{border-bottom-left-radius:0}.rounded-bl-sm{border-bottom-left-radius:var(--radius-sm)}.rounded-bl-xl{border-bottom-left-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-0{border-style:var(--tw-border-style);border-width:0}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-3{border-style:var(--tw-border-style);border-width:3px}.border-4{border-style:var(--tw-border-style);border-width:4px}.border-8{border-style:var(--tw-border-style);border-width:8px}.border-x{border-inline-style:var(--tw-border-style);border-inline-width:1px}.border-x-0{border-inline-style:var(--tw-border-style);border-inline-width:0}.border-x-2{border-inline-style:var(--tw-border-style);border-inline-width:2px}.border-y{border-block-style:var(--tw-border-style);border-block-width:1px}.border-y-0{border-block-style:var(--tw-border-style);border-block-width:0}.border-y-2{border-block-style:var(--tw-border-style);border-block-width:2px}.\!border-t-0{border-top-style:var(--tw-border-style)!important;border-top-width:0!important}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-t-0{border-top-style:var(--tw-border-style);border-top-width:0}.border-t-2{border-top-style:var(--tw-border-style);border-top-width:2px}.border-t-4{border-top-style:var(--tw-border-style);border-top-width:4px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-r-0{border-right-style:var(--tw-border-style);border-right-width:0}.border-r-2{border-right-style:var(--tw-border-style);border-right-width:2px}.border-r-4{border-right-style:var(--tw-border-style);border-right-width:4px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-b-0{border-bottom-style:var(--tw-border-style);border-bottom-width:0}.border-b-2{border-bottom-style:var(--tw-border-style);border-bottom-width:2px}.border-b-4{border-bottom-style:var(--tw-border-style);border-bottom-width:4px}.border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.border-l-0{border-left-style:var(--tw-border-style);border-left-width:0}.border-l-2{border-left-style:var(--tw-border-style);border-left-width:2px}.border-l-4{border-left-style:var(--tw-border-style);border-left-width:4px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.border-dotted{--tw-border-style:dotted;border-style:dotted}.border-double{--tw-border-style:double;border-style:double}.border-hidden{--tw-border-style:hidden;border-style:hidden}.border-none{--tw-border-style:none;border-style:none}.border-solid{--tw-border-style:solid;border-style:solid}.border-amber-50{border-color:var(--color-amber-50)}.border-amber-100{border-color:var(--color-amber-100)}.border-amber-200{border-color:var(--color-amber-200)}.border-amber-300{border-color:var(--color-amber-300)}.border-amber-400{border-color:var(--color-amber-400)}.border-amber-500{border-color:var(--color-amber-500)}.border-amber-600{border-color:var(--color-amber-600)}.border-amber-700{border-color:var(--color-amber-700)}.border-amber-800{border-color:var(--color-amber-800)}.border-amber-900{border-color:var(--color-amber-900)}.border-amber-950{border-color:var(--color-amber-950)}.border-black{border-color:var(--color-black)}.border-blue-50{border-color:var(--color-blue-50)}.border-blue-100{border-color:var(--color-blue-100)}.border-blue-200{border-color:var(--color-blue-200)}.border-blue-300{border-color:var(--color-blue-300)}.border-blue-400{border-color:var(--color-blue-400)}.border-blue-500{border-color:var(--color-blue-500)}.border-blue-600{border-color:var(--color-blue-600)}.border-blue-700{border-color:var(--color-blue-700)}.border-blue-800{border-color:var(--color-blue-800)}.border-blue-900{border-color:var(--color-blue-900)}.border-blue-950{border-color:var(--color-blue-950)}.border-current{border-color:currentColor}.border-cyan-50{border-color:var(--color-cyan-50)}.border-cyan-100{border-color:var(--color-cyan-100)}.border-cyan-200{border-color:var(--color-cyan-200)}.border-cyan-300{border-color:var(--color-cyan-300)}.border-cyan-400{border-color:var(--color-cyan-400)}.border-cyan-500{border-color:var(--color-cyan-500)}.border-cyan-600{border-color:var(--color-cyan-600)}.border-cyan-700{border-color:var(--color-cyan-700)}.border-cyan-800{border-color:var(--color-cyan-800)}.border-cyan-900{border-color:var(--color-cyan-900)}.border-cyan-950{border-color:var(--color-cyan-950)}.border-emerald-50{border-color:var(--color-emerald-50)}.border-emerald-100{border-color:var(--color-emerald-100)}.border-emerald-200{border-color:var(--color-emerald-200)}.border-emerald-300{border-color:var(--color-emerald-300)}.border-emerald-400{border-color:var(--color-emerald-400)}.border-emerald-500{border-color:var(--color-emerald-500)}.border-emerald-600{border-color:var(--color-emerald-600)}.border-emerald-700{border-color:var(--color-emerald-700)}.border-emerald-800{border-color:var(--color-emerald-800)}.border-emerald-900{border-color:var(--color-emerald-900)}.border-emerald-950{border-color:var(--color-emerald-950)}.border-fuchsia-50{border-color:var(--color-fuchsia-50)}.border-fuchsia-100{border-color:var(--color-fuchsia-100)}.border-fuchsia-200{border-color:var(--color-fuchsia-200)}.border-fuchsia-300{border-color:var(--color-fuchsia-300)}.border-fuchsia-400{border-color:var(--color-fuchsia-400)}.border-fuchsia-500{border-color:var(--color-fuchsia-500)}.border-fuchsia-600{border-color:var(--color-fuchsia-600)}.border-fuchsia-700{border-color:var(--color-fuchsia-700)}.border-fuchsia-800{border-color:var(--color-fuchsia-800)}.border-fuchsia-900{border-color:var(--color-fuchsia-900)}.border-fuchsia-950{border-color:var(--color-fuchsia-950)}.border-gray-50{border-color:var(--color-gray-50)}.border-gray-100{border-color:var(--color-gray-100)}.border-gray-200{border-color:var(--color-gray-200)}.border-gray-300{border-color:var(--color-gray-300)}.border-gray-400{border-color:var(--color-gray-400)}.border-gray-500{border-color:var(--color-gray-500)}.border-gray-600{border-color:var(--color-gray-600)}.border-gray-700{border-color:var(--color-gray-700)}.border-gray-800{border-color:var(--color-gray-800)}.border-gray-900{border-color:var(--color-gray-900)}.border-gray-950{border-color:var(--color-gray-950)}.border-green-50{border-color:var(--color-green-50)}.border-green-100{border-color:var(--color-green-100)}.border-green-200{border-color:var(--color-green-200)}.border-green-300{border-color:var(--color-green-300)}.border-green-400{border-color:var(--color-green-400)}.border-green-500{border-color:var(--color-green-500)}.border-green-600{border-color:var(--color-green-600)}.border-green-700{border-color:var(--color-green-700)}.border-green-800{border-color:var(--color-green-800)}.border-green-900{border-color:var(--color-green-900)}.border-green-950{border-color:var(--color-green-950)}.border-indigo-50{border-color:var(--color-indigo-50)}.border-indigo-100{border-color:var(--color-indigo-100)}.border-indigo-200{border-color:var(--color-indigo-200)}.border-indigo-300{border-color:var(--color-indigo-300)}.border-indigo-400{border-color:var(--color-indigo-400)}.border-indigo-500{border-color:var(--color-indigo-500)}.border-indigo-600{border-color:var(--color-indigo-600)}.border-indigo-700{border-color:var(--color-indigo-700)}.border-indigo-800{border-color:var(--color-indigo-800)}.border-indigo-900{border-color:var(--color-indigo-900)}.border-indigo-950{border-color:var(--color-indigo-950)}.border-inherit{border-color:inherit}.border-lime-50{border-color:var(--color-lime-50)}.border-lime-100{border-color:var(--color-lime-100)}.border-lime-200{border-color:var(--color-lime-200)}.border-lime-300{border-color:var(--color-lime-300)}.border-lime-400{border-color:var(--color-lime-400)}.border-lime-500{border-color:var(--color-lime-500)}.border-lime-600{border-color:var(--color-lime-600)}.border-lime-700{border-color:var(--color-lime-700)}.border-lime-800{border-color:var(--color-lime-800)}.border-lime-900{border-color:var(--color-lime-900)}.border-lime-950{border-color:var(--color-lime-950)}.border-neutral-50{border-color:var(--color-neutral-50)}.border-neutral-100{border-color:var(--color-neutral-100)}.border-neutral-200{border-color:var(--color-neutral-200)}.border-neutral-300{border-color:var(--color-neutral-300)}.border-neutral-400{border-color:var(--color-neutral-400)}.border-neutral-500{border-color:var(--color-neutral-500)}.border-neutral-600{border-color:var(--color-neutral-600)}.border-neutral-700{border-color:var(--color-neutral-700)}.border-neutral-800{border-color:var(--color-neutral-800)}.border-neutral-900{border-color:var(--color-neutral-900)}.border-neutral-950{border-color:var(--color-neutral-950)}.border-orange-50{border-color:var(--color-orange-50)}.border-orange-100{border-color:var(--color-orange-100)}.border-orange-200{border-color:var(--color-orange-200)}.border-orange-300{border-color:var(--color-orange-300)}.border-orange-400{border-color:var(--color-orange-400)}.border-orange-500{border-color:var(--color-orange-500)}.border-orange-600{border-color:var(--color-orange-600)}.border-orange-700{border-color:var(--color-orange-700)}.border-orange-800{border-color:var(--color-orange-800)}.border-orange-900{border-color:var(--color-orange-900)}.border-orange-950{border-color:var(--color-orange-950)}.border-pink-50{border-color:var(--color-pink-50)}.border-pink-100{border-color:var(--color-pink-100)}.border-pink-200{border-color:var(--color-pink-200)}.border-pink-300{border-color:var(--color-pink-300)}.border-pink-400{border-color:var(--color-pink-400)}.border-pink-500{border-color:var(--color-pink-500)}.border-pink-600{border-color:var(--color-pink-600)}.border-pink-700{border-color:var(--color-pink-700)}.border-pink-800{border-color:var(--color-pink-800)}.border-pink-900{border-color:var(--color-pink-900)}.border-pink-950{border-color:var(--color-pink-950)}.border-purple-50{border-color:var(--color-purple-50)}.border-purple-100{border-color:var(--color-purple-100)}.border-purple-200{border-color:var(--color-purple-200)}.border-purple-300{border-color:var(--color-purple-300)}.border-purple-400{border-color:var(--color-purple-400)}.border-purple-500{border-color:var(--color-purple-500)}.border-purple-600{border-color:var(--color-purple-600)}.border-purple-700{border-color:var(--color-purple-700)}.border-purple-800{border-color:var(--color-purple-800)}.border-purple-900{border-color:var(--color-purple-900)}.border-purple-950{border-color:var(--color-purple-950)}.border-red-50{border-color:var(--color-red-50)}.border-red-100{border-color:var(--color-red-100)}.border-red-200{border-color:var(--color-red-200)}.border-red-300{border-color:var(--color-red-300)}.border-red-400{border-color:var(--color-red-400)}.border-red-500{border-color:var(--color-red-500)}.border-red-600{border-color:var(--color-red-600)}.border-red-700{border-color:var(--color-red-700)}.border-red-800{border-color:var(--color-red-800)}.border-red-900{border-color:var(--color-red-900)}.border-red-950{border-color:var(--color-red-950)}.border-rose-50{border-color:var(--color-rose-50)}.border-rose-100{border-color:var(--color-rose-100)}.border-rose-200{border-color:var(--color-rose-200)}.border-rose-300{border-color:var(--color-rose-300)}.border-rose-400{border-color:var(--color-rose-400)}.border-rose-500{border-color:var(--color-rose-500)}.border-rose-600{border-color:var(--color-rose-600)}.border-rose-700{border-color:var(--color-rose-700)}.border-rose-800{border-color:var(--color-rose-800)}.border-rose-900{border-color:var(--color-rose-900)}.border-rose-950{border-color:var(--color-rose-950)}.border-sky-50{border-color:var(--color-sky-50)}.border-sky-100{border-color:var(--color-sky-100)}.border-sky-200{border-color:var(--color-sky-200)}.border-sky-300{border-color:var(--color-sky-300)}.border-sky-400{border-color:var(--color-sky-400)}.border-sky-500{border-color:var(--color-sky-500)}.border-sky-600{border-color:var(--color-sky-600)}.border-sky-700{border-color:var(--color-sky-700)}.border-sky-800{border-color:var(--color-sky-800)}.border-sky-900{border-color:var(--color-sky-900)}.border-sky-950{border-color:var(--color-sky-950)}.border-slate-50{border-color:var(--color-slate-50)}.border-slate-100{border-color:var(--color-slate-100)}.border-slate-200{border-color:var(--color-slate-200)}.border-slate-300{border-color:var(--color-slate-300)}.border-slate-400{border-color:var(--color-slate-400)}.border-slate-500{border-color:var(--color-slate-500)}.border-slate-600{border-color:var(--color-slate-600)}.border-slate-700{border-color:var(--color-slate-700)}.border-slate-800{border-color:var(--color-slate-800)}.border-slate-900{border-color:var(--color-slate-900)}.border-slate-950{border-color:var(--color-slate-950)}.border-stone-50{border-color:var(--color-stone-50)}.border-stone-100{border-color:var(--color-stone-100)}.border-stone-200{border-color:var(--color-stone-200)}.border-stone-300{border-color:var(--color-stone-300)}.border-stone-400{border-color:var(--color-stone-400)}.border-stone-500{border-color:var(--color-stone-500)}.border-stone-600{border-color:var(--color-stone-600)}.border-stone-700{border-color:var(--color-stone-700)}.border-stone-800{border-color:var(--color-stone-800)}.border-stone-900{border-color:var(--color-stone-900)}.border-stone-950{border-color:var(--color-stone-950)}.border-teal-50{border-color:var(--color-teal-50)}.border-teal-100{border-color:var(--color-teal-100)}.border-teal-200{border-color:var(--color-teal-200)}.border-teal-300{border-color:var(--color-teal-300)}.border-teal-400{border-color:var(--color-teal-400)}.border-teal-500{border-color:var(--color-teal-500)}.border-teal-600{border-color:var(--color-teal-600)}.border-teal-700{border-color:var(--color-teal-700)}.border-teal-800{border-color:var(--color-teal-800)}.border-teal-900{border-color:var(--color-teal-900)}.border-teal-950{border-color:var(--color-teal-950)}.border-transparent{border-color:#0000}.border-violet-50{border-color:var(--color-violet-50)}.border-violet-100{border-color:var(--color-violet-100)}.border-violet-200{border-color:var(--color-violet-200)}.border-violet-300{border-color:var(--color-violet-300)}.border-violet-400{border-color:var(--color-violet-400)}.border-violet-500{border-color:var(--color-violet-500)}.border-violet-600{border-color:var(--color-violet-600)}.border-violet-700{border-color:var(--color-violet-700)}.border-violet-800{border-color:var(--color-violet-800)}.border-violet-900{border-color:var(--color-violet-900)}.border-violet-950{border-color:var(--color-violet-950)}.border-white{border-color:var(--color-white)}.border-white\/30{border-color:#ffffff4d}@supports (color:color-mix(in lab,red,red)){.border-white\/30{border-color:color-mix(in oklab,var(--color-white) 30%,transparent)}}.border-yellow-50{border-color:var(--color-yellow-50)}.border-yellow-100{border-color:var(--color-yellow-100)}.border-yellow-200{border-color:var(--color-yellow-200)}.border-yellow-300{border-color:var(--color-yellow-300)}.border-yellow-400{border-color:var(--color-yellow-400)}.border-yellow-500{border-color:var(--color-yellow-500)}.border-yellow-600{border-color:var(--color-yellow-600)}.border-yellow-700{border-color:var(--color-yellow-700)}.border-yellow-800{border-color:var(--color-yellow-800)}.border-yellow-900{border-color:var(--color-yellow-900)}.border-yellow-950{border-color:var(--color-yellow-950)}.border-zinc-50{border-color:var(--color-zinc-50)}.border-zinc-100{border-color:var(--color-zinc-100)}.border-zinc-200{border-color:var(--color-zinc-200)}.border-zinc-300{border-color:var(--color-zinc-300)}.border-zinc-400{border-color:var(--color-zinc-400)}.border-zinc-500{border-color:var(--color-zinc-500)}.border-zinc-600{border-color:var(--color-zinc-600)}.border-zinc-700{border-color:var(--color-zinc-700)}.border-zinc-800{border-color:var(--color-zinc-800)}.border-zinc-900{border-color:var(--color-zinc-900)}.border-zinc-950{border-color:var(--color-zinc-950)}.border-t-current{border-top-color:currentColor}.border-t-white{border-top-color:var(--color-white)}.bg-amber-50{background-color:var(--color-amber-50)}.bg-amber-100{background-color:var(--color-amber-100)}.bg-amber-200{background-color:var(--color-amber-200)}.bg-amber-300{background-color:var(--color-amber-300)}.bg-amber-400{background-color:var(--color-amber-400)}.bg-amber-500{background-color:var(--color-amber-500)}.bg-amber-600{background-color:var(--color-amber-600)}.bg-amber-700{background-color:var(--color-amber-700)}.bg-amber-800{background-color:var(--color-amber-800)}.bg-amber-900{background-color:var(--color-amber-900)}.bg-amber-950{background-color:var(--color-amber-950)}.bg-black{background-color:var(--color-black)}.bg-black\/0{background-color:#0000}@supports (color:color-mix(in lab,red,red)){.bg-black\/0{background-color:color-mix(in oklab,var(--color-black) 0%,transparent)}}.bg-black\/50{background-color:#00000080}@supports (color:color-mix(in lab,red,red)){.bg-black\/50{background-color:color-mix(in oklab,var(--color-black) 50%,transparent)}}.bg-blue-50{background-color:var(--color-blue-50)}.bg-blue-50\/70{background-color:#eff6ffb3}@supports (color:color-mix(in lab,red,red)){.bg-blue-50\/70{background-color:color-mix(in oklab,var(--color-blue-50) 70%,transparent)}}.bg-blue-100{background-color:var(--color-blue-100)}.bg-blue-200{background-color:var(--color-blue-200)}.bg-blue-300{background-color:var(--color-blue-300)}.bg-blue-400{background-color:var(--color-blue-400)}.bg-blue-500{background-color:var(--color-blue-500)}.bg-blue-600{background-color:var(--color-blue-600)}.bg-blue-700{background-color:var(--color-blue-700)}.bg-blue-800{background-color:var(--color-blue-800)}.bg-blue-900{background-color:var(--color-blue-900)}.bg-blue-950{background-color:var(--color-blue-950)}.bg-current{background-color:currentColor}.bg-cyan-50{background-color:var(--color-cyan-50)}.bg-cyan-100{background-color:var(--color-cyan-100)}.bg-cyan-200{background-color:var(--color-cyan-200)}.bg-cyan-300{background-color:var(--color-cyan-300)}.bg-cyan-400{background-color:var(--color-cyan-400)}.bg-cyan-500{background-color:var(--color-cyan-500)}.bg-cyan-600{background-color:var(--color-cyan-600)}.bg-cyan-700{background-color:var(--color-cyan-700)}.bg-cyan-800{background-color:var(--color-cyan-800)}.bg-cyan-900{background-color:var(--color-cyan-900)}.bg-cyan-950{background-color:var(--color-cyan-950)}.bg-emerald-50{background-color:var(--color-emerald-50)}.bg-emerald-100{background-color:var(--color-emerald-100)}.bg-emerald-200{background-color:var(--color-emerald-200)}.bg-emerald-300{background-color:var(--color-emerald-300)}.bg-emerald-400{background-color:var(--color-emerald-400)}.bg-emerald-500{background-color:var(--color-emerald-500)}.bg-emerald-600{background-color:var(--color-emerald-600)}.bg-emerald-700{background-color:var(--color-emerald-700)}.bg-emerald-800{background-color:var(--color-emerald-800)}.bg-emerald-900{background-color:var(--color-emerald-900)}.bg-emerald-950{background-color:var(--color-emerald-950)}.bg-fuchsia-50{background-color:var(--color-fuchsia-50)}.bg-fuchsia-100{background-color:var(--color-fuchsia-100)}.bg-fuchsia-200{background-color:var(--color-fuchsia-200)}.bg-fuchsia-300{background-color:var(--color-fuchsia-300)}.bg-fuchsia-400{background-color:var(--color-fuchsia-400)}.bg-fuchsia-500{background-color:var(--color-fuchsia-500)}.bg-fuchsia-600{background-color:var(--color-fuchsia-600)}.bg-fuchsia-700{background-color:var(--color-fuchsia-700)}.bg-fuchsia-800{background-color:var(--color-fuchsia-800)}.bg-fuchsia-900{background-color:var(--color-fuchsia-900)}.bg-fuchsia-950{background-color:var(--color-fuchsia-950)}.bg-gray-50{background-color:var(--color-gray-50)}.bg-gray-100{background-color:var(--color-gray-100)}.bg-gray-100\/60{background-color:#f3f4f699}@supports (color:color-mix(in lab,red,red)){.bg-gray-100\/60{background-color:color-mix(in oklab,var(--color-gray-100) 60%,transparent)}}.bg-gray-200{background-color:var(--color-gray-200)}.bg-gray-300{background-color:var(--color-gray-300)}.bg-gray-400{background-color:var(--color-gray-400)}.bg-gray-500{background-color:var(--color-gray-500)}.bg-gray-600{background-color:var(--color-gray-600)}.bg-gray-700{background-color:var(--color-gray-700)}.bg-gray-800{background-color:var(--color-gray-800)}.bg-gray-800\/50{background-color:#1e293980}@supports (color:color-mix(in lab,red,red)){.bg-gray-800\/50{background-color:color-mix(in oklab,var(--color-gray-800) 50%,transparent)}}.bg-gray-900{background-color:var(--color-gray-900)}.bg-gray-950{background-color:var(--color-gray-950)}.bg-green-50{background-color:var(--color-green-50)}.bg-green-100{background-color:var(--color-green-100)}.bg-green-200{background-color:var(--color-green-200)}.bg-green-300{background-color:var(--color-green-300)}.bg-green-400{background-color:var(--color-green-400)}.bg-green-500{background-color:var(--color-green-500)}.bg-green-600{background-color:var(--color-green-600)}.bg-green-700{background-color:var(--color-green-700)}.bg-green-800{background-color:var(--color-green-800)}.bg-green-900{background-color:var(--color-green-900)}.bg-green-950{background-color:var(--color-green-950)}.bg-indigo-50{background-color:var(--color-indigo-50)}.bg-indigo-100{background-color:var(--color-indigo-100)}.bg-indigo-200{background-color:var(--color-indigo-200)}.bg-indigo-300{background-color:var(--color-indigo-300)}.bg-indigo-400{background-color:var(--color-indigo-400)}.bg-indigo-500{background-color:var(--color-indigo-500)}.bg-indigo-600{background-color:var(--color-indigo-600)}.bg-indigo-700{background-color:var(--color-indigo-700)}.bg-indigo-800{background-color:var(--color-indigo-800)}.bg-indigo-900{background-color:var(--color-indigo-900)}.bg-indigo-950{background-color:var(--color-indigo-950)}.bg-inherit{background-color:inherit}.bg-lime-50{background-color:var(--color-lime-50)}.bg-lime-100{background-color:var(--color-lime-100)}.bg-lime-200{background-color:var(--color-lime-200)}.bg-lime-300{background-color:var(--color-lime-300)}.bg-lime-400{background-color:var(--color-lime-400)}.bg-lime-500{background-color:var(--color-lime-500)}.bg-lime-600{background-color:var(--color-lime-600)}.bg-lime-700{background-color:var(--color-lime-700)}.bg-lime-800{background-color:var(--color-lime-800)}.bg-lime-900{background-color:var(--color-lime-900)}.bg-lime-950{background-color:var(--color-lime-950)}.bg-neutral-50{background-color:var(--color-neutral-50)}.bg-neutral-100{background-color:var(--color-neutral-100)}.bg-neutral-200{background-color:var(--color-neutral-200)}.bg-neutral-300{background-color:var(--color-neutral-300)}.bg-neutral-400{background-color:var(--color-neutral-400)}.bg-neutral-500{background-color:var(--color-neutral-500)}.bg-neutral-600{background-color:var(--color-neutral-600)}.bg-neutral-700{background-color:var(--color-neutral-700)}.bg-neutral-800{background-color:var(--color-neutral-800)}.bg-neutral-900{background-color:var(--color-neutral-900)}.bg-neutral-950{background-color:var(--color-neutral-950)}.bg-orange-50{background-color:var(--color-orange-50)}.bg-orange-100{background-color:var(--color-orange-100)}.bg-orange-200{background-color:var(--color-orange-200)}.bg-orange-300{background-color:var(--color-orange-300)}.bg-orange-400{background-color:var(--color-orange-400)}.bg-orange-500{background-color:var(--color-orange-500)}.bg-orange-600{background-color:var(--color-orange-600)}.bg-orange-700{background-color:var(--color-orange-700)}.bg-orange-800{background-color:var(--color-orange-800)}.bg-orange-900{background-color:var(--color-orange-900)}.bg-orange-950{background-color:var(--color-orange-950)}.bg-pink-50{background-color:var(--color-pink-50)}.bg-pink-100{background-color:var(--color-pink-100)}.bg-pink-200{background-color:var(--color-pink-200)}.bg-pink-300{background-color:var(--color-pink-300)}.bg-pink-400{background-color:var(--color-pink-400)}.bg-pink-500{background-color:var(--color-pink-500)}.bg-pink-600{background-color:var(--color-pink-600)}.bg-pink-700{background-color:var(--color-pink-700)}.bg-pink-800{background-color:var(--color-pink-800)}.bg-pink-900{background-color:var(--color-pink-900)}.bg-pink-950{background-color:var(--color-pink-950)}.bg-purple-50{background-color:var(--color-purple-50)}.bg-purple-100{background-color:var(--color-purple-100)}.bg-purple-200{background-color:var(--color-purple-200)}.bg-purple-300{background-color:var(--color-purple-300)}.bg-purple-400{background-color:var(--color-purple-400)}.bg-purple-500{background-color:var(--color-purple-500)}.bg-purple-600{background-color:var(--color-purple-600)}.bg-purple-700{background-color:var(--color-purple-700)}.bg-purple-800{background-color:var(--color-purple-800)}.bg-purple-900{background-color:var(--color-purple-900)}.bg-purple-950{background-color:var(--color-purple-950)}.bg-red-50{background-color:var(--color-red-50)}.bg-red-100{background-color:var(--color-red-100)}.bg-red-200{background-color:var(--color-red-200)}.bg-red-300{background-color:var(--color-red-300)}.bg-red-400{background-color:var(--color-red-400)}.bg-red-500{background-color:var(--color-red-500)}.bg-red-500\/50{background-color:#fb2c3680}@supports (color:color-mix(in lab,red,red)){.bg-red-500\/50{background-color:color-mix(in oklab,var(--color-red-500) 50%,transparent)}}.bg-red-600{background-color:var(--color-red-600)}.bg-red-700{background-color:var(--color-red-700)}.bg-red-800{background-color:var(--color-red-800)}.bg-red-900{background-color:var(--color-red-900)}.bg-red-950{background-color:var(--color-red-950)}.bg-rose-50{background-color:var(--color-rose-50)}.bg-rose-100{background-color:var(--color-rose-100)}.bg-rose-200{background-color:var(--color-rose-200)}.bg-rose-300{background-color:var(--color-rose-300)}.bg-rose-400{background-color:var(--color-rose-400)}.bg-rose-500{background-color:var(--color-rose-500)}.bg-rose-600{background-color:var(--color-rose-600)}.bg-rose-700{background-color:var(--color-rose-700)}.bg-rose-800{background-color:var(--color-rose-800)}.bg-rose-900{background-color:var(--color-rose-900)}.bg-rose-950{background-color:var(--color-rose-950)}.bg-sky-50{background-color:var(--color-sky-50)}.bg-sky-100{background-color:var(--color-sky-100)}.bg-sky-200{background-color:var(--color-sky-200)}.bg-sky-300{background-color:var(--color-sky-300)}.bg-sky-400{background-color:var(--color-sky-400)}.bg-sky-500{background-color:var(--color-sky-500)}.bg-sky-600{background-color:var(--color-sky-600)}.bg-sky-700{background-color:var(--color-sky-700)}.bg-sky-800{background-color:var(--color-sky-800)}.bg-sky-900{background-color:var(--color-sky-900)}.bg-sky-950{background-color:var(--color-sky-950)}.bg-slate-50{background-color:var(--color-slate-50)}.bg-slate-100{background-color:var(--color-slate-100)}.bg-slate-200{background-color:var(--color-slate-200)}.bg-slate-300{background-color:var(--color-slate-300)}.bg-slate-400{background-color:var(--color-slate-400)}.bg-slate-500{background-color:var(--color-slate-500)}.bg-slate-600{background-color:var(--color-slate-600)}.bg-slate-700{background-color:var(--color-slate-700)}.bg-slate-800{background-color:var(--color-slate-800)}.bg-slate-900{background-color:var(--color-slate-900)}.bg-slate-950{background-color:var(--color-slate-950)}.bg-stone-50{background-color:var(--color-stone-50)}.bg-stone-100{background-color:var(--color-stone-100)}.bg-stone-200{background-color:var(--color-stone-200)}.bg-stone-300{background-color:var(--color-stone-300)}.bg-stone-400{background-color:var(--color-stone-400)}.bg-stone-500{background-color:var(--color-stone-500)}.bg-stone-600{background-color:var(--color-stone-600)}.bg-stone-700{background-color:var(--color-stone-700)}.bg-stone-800{background-color:var(--color-stone-800)}.bg-stone-900{background-color:var(--color-stone-900)}.bg-stone-950{background-color:var(--color-stone-950)}.bg-teal-50{background-color:var(--color-teal-50)}.bg-teal-100{background-color:var(--color-teal-100)}.bg-teal-200{background-color:var(--color-teal-200)}.bg-teal-300{background-color:var(--color-teal-300)}.bg-teal-400{background-color:var(--color-teal-400)}.bg-teal-500{background-color:var(--color-teal-500)}.bg-teal-600{background-color:var(--color-teal-600)}.bg-teal-700{background-color:var(--color-teal-700)}.bg-teal-800{background-color:var(--color-teal-800)}.bg-teal-900{background-color:var(--color-teal-900)}.bg-teal-950{background-color:var(--color-teal-950)}.bg-transparent{background-color:#0000}.bg-violet-50{background-color:var(--color-violet-50)}.bg-violet-100{background-color:var(--color-violet-100)}.bg-violet-200{background-color:var(--color-violet-200)}.bg-violet-300{background-color:var(--color-violet-300)}.bg-violet-400{background-color:var(--color-violet-400)}.bg-violet-500{background-color:var(--color-violet-500)}.bg-violet-600{background-color:var(--color-violet-600)}.bg-violet-700{background-color:var(--color-violet-700)}.bg-violet-800{background-color:var(--color-violet-800)}.bg-violet-900{background-color:var(--color-violet-900)}.bg-violet-950{background-color:var(--color-violet-950)}.bg-white{background-color:var(--color-white)}.bg-white\/90{background-color:#ffffffe6}@supports (color:color-mix(in lab,red,red)){.bg-white\/90{background-color:color-mix(in oklab,var(--color-white) 90%,transparent)}}.bg-yellow-50{background-color:var(--color-yellow-50)}.bg-yellow-100{background-color:var(--color-yellow-100)}.bg-yellow-200{background-color:var(--color-yellow-200)}.bg-yellow-300{background-color:var(--color-yellow-300)}.bg-yellow-400{background-color:var(--color-yellow-400)}.bg-yellow-500{background-color:var(--color-yellow-500)}.bg-yellow-600{background-color:var(--color-yellow-600)}.bg-yellow-700{background-color:var(--color-yellow-700)}.bg-yellow-800{background-color:var(--color-yellow-800)}.bg-yellow-900{background-color:var(--color-yellow-900)}.bg-yellow-950{background-color:var(--color-yellow-950)}.bg-zinc-50{background-color:var(--color-zinc-50)}.bg-zinc-100{background-color:var(--color-zinc-100)}.bg-zinc-200{background-color:var(--color-zinc-200)}.bg-zinc-300{background-color:var(--color-zinc-300)}.bg-zinc-400{background-color:var(--color-zinc-400)}.bg-zinc-500{background-color:var(--color-zinc-500)}.bg-zinc-600{background-color:var(--color-zinc-600)}.bg-zinc-700{background-color:var(--color-zinc-700)}.bg-zinc-800{background-color:var(--color-zinc-800)}.bg-zinc-900{background-color:var(--color-zinc-900)}.bg-zinc-950{background-color:var(--color-zinc-950)}.bg-gradient-to-b{--tw-gradient-position:to bottom in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.bg-gradient-to-bl{--tw-gradient-position:to bottom left in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.bg-gradient-to-br{--tw-gradient-position:to bottom right in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.bg-gradient-to-l{--tw-gradient-position:to left in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.bg-gradient-to-r{--tw-gradient-position:to right in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.bg-gradient-to-t{--tw-gradient-position:to top in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.bg-gradient-to-tl{--tw-gradient-position:to top left in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.bg-gradient-to-tr{--tw-gradient-position:to top right in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.bg-none{background-image:none}.from-black{--tw-gradient-from:var(--color-black);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-blue-50{--tw-gradient-from:var(--color-blue-50);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-blue-100{--tw-gradient-from:var(--color-blue-100);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-blue-200{--tw-gradient-from:var(--color-blue-200);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-blue-300{--tw-gradient-from:var(--color-blue-300);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-blue-400{--tw-gradient-from:var(--color-blue-400);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-blue-500{--tw-gradient-from:var(--color-blue-500);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-blue-600{--tw-gradient-from:var(--color-blue-600);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-blue-700{--tw-gradient-from:var(--color-blue-700);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-blue-800{--tw-gradient-from:var(--color-blue-800);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-blue-900{--tw-gradient-from:var(--color-blue-900);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-current{--tw-gradient-from:currentcolor;--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-gray-50{--tw-gradient-from:var(--color-gray-50);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-gray-100{--tw-gradient-from:var(--color-gray-100);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-gray-200{--tw-gradient-from:var(--color-gray-200);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-gray-300{--tw-gradient-from:var(--color-gray-300);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-gray-400{--tw-gradient-from:var(--color-gray-400);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-gray-500{--tw-gradient-from:var(--color-gray-500);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-gray-600{--tw-gradient-from:var(--color-gray-600);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-gray-700{--tw-gradient-from:var(--color-gray-700);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-gray-800{--tw-gradient-from:var(--color-gray-800);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-gray-900{--tw-gradient-from:var(--color-gray-900);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-green-50{--tw-gradient-from:var(--color-green-50);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-green-100{--tw-gradient-from:var(--color-green-100);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-green-200{--tw-gradient-from:var(--color-green-200);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-green-300{--tw-gradient-from:var(--color-green-300);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-green-400{--tw-gradient-from:var(--color-green-400);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-green-500{--tw-gradient-from:var(--color-green-500);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-green-600{--tw-gradient-from:var(--color-green-600);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-green-700{--tw-gradient-from:var(--color-green-700);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-green-800{--tw-gradient-from:var(--color-green-800);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-green-900{--tw-gradient-from:var(--color-green-900);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-indigo-50{--tw-gradient-from:var(--color-indigo-50);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-indigo-100{--tw-gradient-from:var(--color-indigo-100);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-indigo-200{--tw-gradient-from:var(--color-indigo-200);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-indigo-300{--tw-gradient-from:var(--color-indigo-300);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-indigo-400{--tw-gradient-from:var(--color-indigo-400);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-indigo-500{--tw-gradient-from:var(--color-indigo-500);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-indigo-600{--tw-gradient-from:var(--color-indigo-600);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-indigo-700{--tw-gradient-from:var(--color-indigo-700);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-indigo-800{--tw-gradient-from:var(--color-indigo-800);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-indigo-900{--tw-gradient-from:var(--color-indigo-900);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-inherit{--tw-gradient-from:inherit;--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-orange-50{--tw-gradient-from:var(--color-orange-50);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-orange-100{--tw-gradient-from:var(--color-orange-100);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-orange-200{--tw-gradient-from:var(--color-orange-200);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-orange-300{--tw-gradient-from:var(--color-orange-300);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-orange-400{--tw-gradient-from:var(--color-orange-400);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-orange-500{--tw-gradient-from:var(--color-orange-500);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-orange-600{--tw-gradient-from:var(--color-orange-600);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-orange-700{--tw-gradient-from:var(--color-orange-700);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-orange-800{--tw-gradient-from:var(--color-orange-800);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-orange-900{--tw-gradient-from:var(--color-orange-900);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-pink-50{--tw-gradient-from:var(--color-pink-50);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-pink-100{--tw-gradient-from:var(--color-pink-100);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-pink-200{--tw-gradient-from:var(--color-pink-200);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-pink-300{--tw-gradient-from:var(--color-pink-300);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-pink-400{--tw-gradient-from:var(--color-pink-400);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-pink-500{--tw-gradient-from:var(--color-pink-500);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-pink-600{--tw-gradient-from:var(--color-pink-600);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-pink-700{--tw-gradient-from:var(--color-pink-700);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-pink-800{--tw-gradient-from:var(--color-pink-800);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-pink-900{--tw-gradient-from:var(--color-pink-900);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-purple-50{--tw-gradient-from:var(--color-purple-50);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-purple-100{--tw-gradient-from:var(--color-purple-100);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-purple-200{--tw-gradient-from:var(--color-purple-200);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-purple-300{--tw-gradient-from:var(--color-purple-300);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-purple-400{--tw-gradient-from:var(--color-purple-400);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-purple-500{--tw-gradient-from:var(--color-purple-500);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-purple-600{--tw-gradient-from:var(--color-purple-600);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-purple-700{--tw-gradient-from:var(--color-purple-700);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-purple-800{--tw-gradient-from:var(--color-purple-800);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-purple-900{--tw-gradient-from:var(--color-purple-900);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-red-50{--tw-gradient-from:var(--color-red-50);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-red-100{--tw-gradient-from:var(--color-red-100);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-red-200{--tw-gradient-from:var(--color-red-200);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-red-300{--tw-gradient-from:var(--color-red-300);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-red-400{--tw-gradient-from:var(--color-red-400);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-red-500{--tw-gradient-from:var(--color-red-500);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-red-600{--tw-gradient-from:var(--color-red-600);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-red-700{--tw-gradient-from:var(--color-red-700);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-red-800{--tw-gradient-from:var(--color-red-800);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-red-900{--tw-gradient-from:var(--color-red-900);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-slate-50{--tw-gradient-from:var(--color-slate-50);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-slate-100{--tw-gradient-from:var(--color-slate-100);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-slate-200{--tw-gradient-from:var(--color-slate-200);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-slate-300{--tw-gradient-from:var(--color-slate-300);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-slate-400{--tw-gradient-from:var(--color-slate-400);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-slate-500{--tw-gradient-from:var(--color-slate-500);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-slate-600{--tw-gradient-from:var(--color-slate-600);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-slate-700{--tw-gradient-from:var(--color-slate-700);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-slate-800{--tw-gradient-from:var(--color-slate-800);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-slate-900{--tw-gradient-from:var(--color-slate-900);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-transparent{--tw-gradient-from:transparent;--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-white{--tw-gradient-from:var(--color-white);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-yellow-50{--tw-gradient-from:var(--color-yellow-50);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-yellow-100{--tw-gradient-from:var(--color-yellow-100);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-yellow-200{--tw-gradient-from:var(--color-yellow-200);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-yellow-300{--tw-gradient-from:var(--color-yellow-300);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-yellow-400{--tw-gradient-from:var(--color-yellow-400);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-yellow-500{--tw-gradient-from:var(--color-yellow-500);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-yellow-600{--tw-gradient-from:var(--color-yellow-600);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-yellow-700{--tw-gradient-from:var(--color-yellow-700);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-yellow-800{--tw-gradient-from:var(--color-yellow-800);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-yellow-900{--tw-gradient-from:var(--color-yellow-900);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.via-black{--tw-gradient-via:var(--color-black);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-blue-50{--tw-gradient-via:var(--color-blue-50);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-blue-100{--tw-gradient-via:var(--color-blue-100);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-blue-200{--tw-gradient-via:var(--color-blue-200);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-blue-300{--tw-gradient-via:var(--color-blue-300);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-blue-400{--tw-gradient-via:var(--color-blue-400);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-blue-500{--tw-gradient-via:var(--color-blue-500);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-blue-600{--tw-gradient-via:var(--color-blue-600);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-blue-700{--tw-gradient-via:var(--color-blue-700);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-blue-800{--tw-gradient-via:var(--color-blue-800);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-blue-900{--tw-gradient-via:var(--color-blue-900);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-current{--tw-gradient-via:currentcolor;--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-gray-50{--tw-gradient-via:var(--color-gray-50);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-gray-100{--tw-gradient-via:var(--color-gray-100);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-gray-200{--tw-gradient-via:var(--color-gray-200);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-gray-300{--tw-gradient-via:var(--color-gray-300);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-gray-400{--tw-gradient-via:var(--color-gray-400);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-gray-500{--tw-gradient-via:var(--color-gray-500);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-gray-600{--tw-gradient-via:var(--color-gray-600);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-gray-700{--tw-gradient-via:var(--color-gray-700);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-gray-800{--tw-gradient-via:var(--color-gray-800);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-gray-900{--tw-gradient-via:var(--color-gray-900);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-inherit{--tw-gradient-via:inherit;--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-slate-50{--tw-gradient-via:var(--color-slate-50);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-slate-100{--tw-gradient-via:var(--color-slate-100);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-slate-200{--tw-gradient-via:var(--color-slate-200);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-slate-300{--tw-gradient-via:var(--color-slate-300);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-slate-400{--tw-gradient-via:var(--color-slate-400);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-slate-500{--tw-gradient-via:var(--color-slate-500);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-slate-600{--tw-gradient-via:var(--color-slate-600);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-slate-700{--tw-gradient-via:var(--color-slate-700);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-slate-800{--tw-gradient-via:var(--color-slate-800);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-slate-900{--tw-gradient-via:var(--color-slate-900);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-transparent{--tw-gradient-via:transparent;--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-white{--tw-gradient-via:var(--color-white);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.to-black{--tw-gradient-to:var(--color-black);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-blue-50{--tw-gradient-to:var(--color-blue-50);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-blue-100{--tw-gradient-to:var(--color-blue-100);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-blue-200{--tw-gradient-to:var(--color-blue-200);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-blue-300{--tw-gradient-to:var(--color-blue-300);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-blue-400{--tw-gradient-to:var(--color-blue-400);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-blue-500{--tw-gradient-to:var(--color-blue-500);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-blue-600{--tw-gradient-to:var(--color-blue-600);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-blue-700{--tw-gradient-to:var(--color-blue-700);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-blue-800{--tw-gradient-to:var(--color-blue-800);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-blue-900{--tw-gradient-to:var(--color-blue-900);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-current{--tw-gradient-to:currentcolor;--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-gray-50{--tw-gradient-to:var(--color-gray-50);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-gray-100{--tw-gradient-to:var(--color-gray-100);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-gray-200{--tw-gradient-to:var(--color-gray-200);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-gray-300{--tw-gradient-to:var(--color-gray-300);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-gray-400{--tw-gradient-to:var(--color-gray-400);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-gray-500{--tw-gradient-to:var(--color-gray-500);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-gray-600{--tw-gradient-to:var(--color-gray-600);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-gray-700{--tw-gradient-to:var(--color-gray-700);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-gray-800{--tw-gradient-to:var(--color-gray-800);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-gray-900{--tw-gradient-to:var(--color-gray-900);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-indigo-50{--tw-gradient-to:var(--color-indigo-50);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-inherit{--tw-gradient-to:inherit;--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-purple-50{--tw-gradient-to:var(--color-purple-50);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-purple-100{--tw-gradient-to:var(--color-purple-100);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-purple-200{--tw-gradient-to:var(--color-purple-200);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-purple-300{--tw-gradient-to:var(--color-purple-300);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-purple-400{--tw-gradient-to:var(--color-purple-400);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-purple-500{--tw-gradient-to:var(--color-purple-500);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-purple-600{--tw-gradient-to:var(--color-purple-600);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-purple-700{--tw-gradient-to:var(--color-purple-700);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-purple-800{--tw-gradient-to:var(--color-purple-800);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-purple-900{--tw-gradient-to:var(--color-purple-900);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-red-50{--tw-gradient-to:var(--color-red-50);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-red-100{--tw-gradient-to:var(--color-red-100);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-red-200{--tw-gradient-to:var(--color-red-200);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-red-300{--tw-gradient-to:var(--color-red-300);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-red-400{--tw-gradient-to:var(--color-red-400);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-red-500{--tw-gradient-to:var(--color-red-500);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-red-600{--tw-gradient-to:var(--color-red-600);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-red-700{--tw-gradient-to:var(--color-red-700);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-red-800{--tw-gradient-to:var(--color-red-800);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-red-900{--tw-gradient-to:var(--color-red-900);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-slate-50{--tw-gradient-to:var(--color-slate-50);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-slate-100{--tw-gradient-to:var(--color-slate-100);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-slate-200{--tw-gradient-to:var(--color-slate-200);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-slate-300{--tw-gradient-to:var(--color-slate-300);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-slate-400{--tw-gradient-to:var(--color-slate-400);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-slate-500{--tw-gradient-to:var(--color-slate-500);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-slate-600{--tw-gradient-to:var(--color-slate-600);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-slate-700{--tw-gradient-to:var(--color-slate-700);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-slate-800{--tw-gradient-to:var(--color-slate-800);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-slate-900{--tw-gradient-to:var(--color-slate-900);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-transparent{--tw-gradient-to:transparent;--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-white{--tw-gradient-to:var(--color-white);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.object-contain{object-fit:contain}.object-cover{object-fit:cover}.object-fill{object-fit:fill}.object-none{object-fit:none}.object-scale-down{object-fit:scale-down}.object-bottom{object-position:bottom}.object-center{object-position:center}.object-left{object-position:left}.object-left-bottom{object-position:left bottom}.object-left-top{object-position:left top}.object-right{object-position:right}.object-right-bottom{object-position:right bottom}.object-right-top{object-position:right top}.object-top{object-position:top}.\!p-0{padding:0!important}.\!p-1{padding:var(--spacing)!important}.\!p-2{padding:calc(var(--spacing) * 2)!important}.\!p-3{padding:calc(var(--spacing) * 3)!important}.\!p-4{padding:calc(var(--spacing) * 4)!important}.\!p-5{padding:calc(var(--spacing) * 5)!important}.\!p-6{padding:calc(var(--spacing) * 6)!important}.\!p-8{padding:calc(var(--spacing) * 8)!important}.\!p-10{padding:calc(var(--spacing) * 10)!important}.\!p-12{padding:calc(var(--spacing) * 12)!important}.\!p-16{padding:calc(var(--spacing) * 16)!important}.p-0{padding:0}.p-0\.5{padding:calc(var(--spacing) * .5)}.p-1{padding:var(--spacing)}.p-1\.5{padding:calc(var(--spacing) * 1.5)}.p-2{padding:calc(var(--spacing) * 2)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.p-5{padding:calc(var(--spacing) * 5)}.p-6{padding:calc(var(--spacing) * 6)}.p-8{padding:calc(var(--spacing) * 8)}.p-10{padding:calc(var(--spacing) * 10)}.p-12{padding:calc(var(--spacing) * 12)}.p-16{padding:calc(var(--spacing) * 16)}.p-20{padding:calc(var(--spacing) * 20)}.p-24{padding:calc(var(--spacing) * 24)}.\!px-0{padding-inline:0!important}.\!px-1{padding-inline:var(--spacing)!important}.\!px-2{padding-inline:calc(var(--spacing) * 2)!important}.\!px-3{padding-inline:calc(var(--spacing) * 3)!important}.\!px-4{padding-inline:calc(var(--spacing) * 4)!important}.\!px-5{padding-inline:calc(var(--spacing) * 5)!important}.\!px-6{padding-inline:calc(var(--spacing) * 6)!important}.\!px-8{padding-inline:calc(var(--spacing) * 8)!important}.\!px-10{padding-inline:calc(var(--spacing) * 10)!important}.\!px-12{padding-inline:calc(var(--spacing) * 12)!important}.\!px-16{padding-inline:calc(var(--spacing) * 16)!important}.px-0{padding-inline:0}.px-0\.5{padding-inline:calc(var(--spacing) * .5)}.px-1{padding-inline:var(--spacing)}.px-1\.5{padding-inline:calc(var(--spacing) * 1.5)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-2\.5{padding-inline:calc(var(--spacing) * 2.5)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-5{padding-inline:calc(var(--spacing) * 5)}.px-6{padding-inline:calc(var(--spacing) * 6)}.px-8{padding-inline:calc(var(--spacing) * 8)}.px-10{padding-inline:calc(var(--spacing) * 10)}.px-12{padding-inline:calc(var(--spacing) * 12)}.px-16{padding-inline:calc(var(--spacing) * 16)}.\!py-0{padding-block:0!important}.\!py-1{padding-block:var(--spacing)!important}.\!py-2{padding-block:calc(var(--spacing) * 2)!important}.\!py-3{padding-block:calc(var(--spacing) * 3)!important}.\!py-4{padding-block:calc(var(--spacing) * 4)!important}.\!py-5{padding-block:calc(var(--spacing) * 5)!important}.\!py-6{padding-block:calc(var(--spacing) * 6)!important}.\!py-8{padding-block:calc(var(--spacing) * 8)!important}.\!py-10{padding-block:calc(var(--spacing) * 10)!important}.\!py-12{padding-block:calc(var(--spacing) * 12)!important}.\!py-16{padding-block:calc(var(--spacing) * 16)!important}.py-0{padding-block:0}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:var(--spacing)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-2\.5{padding-block:calc(var(--spacing) * 2.5)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-4{padding-block:calc(var(--spacing) * 4)}.py-5{padding-block:calc(var(--spacing) * 5)}.py-6{padding-block:calc(var(--spacing) * 6)}.py-8{padding-block:calc(var(--spacing) * 8)}.py-10{padding-block:calc(var(--spacing) * 10)}.py-12{padding-block:calc(var(--spacing) * 12)}.py-16{padding-block:calc(var(--spacing) * 16)}.pt-0{padding-top:0}.pt-0\.5{padding-top:calc(var(--spacing) * .5)}.pt-1{padding-top:var(--spacing)}.pt-2{padding-top:calc(var(--spacing) * 2)}.pt-3{padding-top:calc(var(--spacing) * 3)}.pt-4{padding-top:calc(var(--spacing) * 4)}.pt-5{padding-top:calc(var(--spacing) * 5)}.pt-6{padding-top:calc(var(--spacing) * 6)}.pt-8{padding-top:calc(var(--spacing) * 8)}.pt-10{padding-top:calc(var(--spacing) * 10)}.pt-12{padding-top:calc(var(--spacing) * 12)}.pt-16{padding-top:calc(var(--spacing) * 16)}.pr-0{padding-right:0}.pr-1{padding-right:var(--spacing)}.pr-2{padding-right:calc(var(--spacing) * 2)}.pr-3{padding-right:calc(var(--spacing) * 3)}.pr-4{padding-right:calc(var(--spacing) * 4)}.pr-5{padding-right:calc(var(--spacing) * 5)}.pr-6{padding-right:calc(var(--spacing) * 6)}.pr-8{padding-right:calc(var(--spacing) * 8)}.pr-10{padding-right:calc(var(--spacing) * 10)}.pr-12{padding-right:calc(var(--spacing) * 12)}.pr-16{padding-right:calc(var(--spacing) * 16)}.pb-0{padding-bottom:0}.pb-1{padding-bottom:var(--spacing)}.pb-2{padding-bottom:calc(var(--spacing) * 2)}.pb-3{padding-bottom:calc(var(--spacing) * 3)}.pb-4{padding-bottom:calc(var(--spacing) * 4)}.pb-5{padding-bottom:calc(var(--spacing) * 5)}.pb-6{padding-bottom:calc(var(--spacing) * 6)}.pb-8{padding-bottom:calc(var(--spacing) * 8)}.pb-10{padding-bottom:calc(var(--spacing) * 10)}.pb-12{padding-bottom:calc(var(--spacing) * 12)}.pb-16{padding-bottom:calc(var(--spacing) * 16)}.pb-24{padding-bottom:calc(var(--spacing) * 24)}.pl-0{padding-left:0}.pl-1{padding-left:var(--spacing)}.pl-2{padding-left:calc(var(--spacing) * 2)}.pl-3{padding-left:calc(var(--spacing) * 3)}.pl-4{padding-left:calc(var(--spacing) * 4)}.pl-5{padding-left:calc(var(--spacing) * 5)}.pl-6{padding-left:calc(var(--spacing) * 6)}.pl-7{padding-left:calc(var(--spacing) * 7)}.pl-8{padding-left:calc(var(--spacing) * 8)}.pl-10{padding-left:calc(var(--spacing) * 10)}.pl-12{padding-left:calc(var(--spacing) * 12)}.pl-16{padding-left:calc(var(--spacing) * 16)}.\!text-center{text-align:center!important}.\!text-justify{text-align:justify!important}.\!text-left{text-align:left!important}.\!text-right{text-align:right!important}.text-center{text-align:center}.text-end{text-align:end}.text-justify{text-align:justify}.text-left{text-align:left}.text-right{text-align:right}.text-start{text-align:start}.indent-0{text-indent:0}.indent-1{text-indent:var(--spacing)}.indent-2{text-indent:calc(var(--spacing) * 2)}.indent-4{text-indent:calc(var(--spacing) * 4)}.indent-8{text-indent:calc(var(--spacing) * 8)}.align-baseline{vertical-align:baseline}.align-bottom{vertical-align:bottom}.align-middle{vertical-align:middle}.align-sub{vertical-align:sub}.align-super{vertical-align:super}.align-text-bottom{vertical-align:text-bottom}.align-text-top{vertical-align:text-top}.align-top{vertical-align:top}.font-mono{font-family:var(--font-mono)}.font-sans{font-family:var(--font-sans)}.\!text-2xl{font-size:var(--text-2xl)!important;line-height:var(--tw-leading,var(--text-2xl--line-height))!important}.\!text-3xl{font-size:var(--text-3xl)!important;line-height:var(--tw-leading,var(--text-3xl--line-height))!important}.\!text-4xl{font-size:var(--text-4xl)!important;line-height:var(--tw-leading,var(--text-4xl--line-height))!important}.\!text-base{font-size:var(--text-base)!important;line-height:var(--tw-leading,var(--text-base--line-height))!important}.\!text-lg{font-size:var(--text-lg)!important;line-height:var(--tw-leading,var(--text-lg--line-height))!important}.\!text-sm{font-size:var(--text-sm)!important;line-height:var(--tw-leading,var(--text-sm--line-height))!important}.\!text-xl{font-size:var(--text-xl)!important;line-height:var(--tw-leading,var(--text-xl--line-height))!important}.\!text-xs{font-size:var(--text-xs)!important;line-height:var(--tw-leading,var(--text-xs--line-height))!important}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.text-5xl{font-size:var(--text-5xl);line-height:var(--tw-leading,var(--text-5xl--line-height))}.text-6xl{font-size:var(--text-6xl);line-height:var(--tw-leading,var(--text-6xl--line-height))}.text-7xl{font-size:var(--text-7xl);line-height:var(--tw-leading,var(--text-7xl--line-height))}.text-8xl{font-size:var(--text-8xl);line-height:var(--tw-leading,var(--text-8xl--line-height))}.text-9xl{font-size:var(--text-9xl);line-height:var(--tw-leading,var(--text-9xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[2rem\]{font-size:2rem}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[13px\]{font-size:13px}.leading-3{--tw-leading:calc(var(--spacing) * 3);line-height:calc(var(--spacing) * 3)}.leading-4{--tw-leading:calc(var(--spacing) * 4);line-height:calc(var(--spacing) * 4)}.leading-5{--tw-leading:calc(var(--spacing) * 5);line-height:calc(var(--spacing) * 5)}.leading-6{--tw-leading:calc(var(--spacing) * 6);line-height:calc(var(--spacing) * 6)}.leading-7{--tw-leading:calc(var(--spacing) * 7);line-height:calc(var(--spacing) * 7)}.leading-8{--tw-leading:calc(var(--spacing) * 8);line-height:calc(var(--spacing) * 8)}.leading-9{--tw-leading:calc(var(--spacing) * 9);line-height:calc(var(--spacing) * 9)}.leading-10{--tw-leading:calc(var(--spacing) * 10);line-height:calc(var(--spacing) * 10)}.leading-loose{--tw-leading:var(--leading-loose);line-height:var(--leading-loose)}.leading-none{--tw-leading:1;line-height:1}.leading-normal{--tw-leading:var(--leading-normal);line-height:var(--leading-normal)}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.leading-snug{--tw-leading:var(--leading-snug);line-height:var(--leading-snug)}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.\!font-bold{--tw-font-weight:var(--font-weight-bold)!important;font-weight:var(--font-weight-bold)!important}.\!font-medium{--tw-font-weight:var(--font-weight-medium)!important;font-weight:var(--font-weight-medium)!important}.\!font-normal{--tw-font-weight:var(--font-weight-normal)!important;font-weight:var(--font-weight-normal)!important}.\!font-semibold{--tw-font-weight:var(--font-weight-semibold)!important;font-weight:var(--font-weight-semibold)!important}.font-black{--tw-font-weight:var(--font-weight-black);font-weight:var(--font-weight-black)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-extrabold{--tw-font-weight:var(--font-weight-extrabold);font-weight:var(--font-weight-extrabold)}.font-extralight{--tw-font-weight:var(--font-weight-extralight);font-weight:var(--font-weight-extralight)}.font-light{--tw-font-weight:var(--font-weight-light);font-weight:var(--font-weight-light)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.font-thin{--tw-font-weight:var(--font-weight-thin);font-weight:var(--font-weight-thin)}.tracking-normal{--tw-tracking:var(--tracking-normal);letter-spacing:var(--tracking-normal)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.tracking-tighter{--tw-tracking:var(--tracking-tighter);letter-spacing:var(--tracking-tighter)}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.tracking-widest{--tw-tracking:var(--tracking-widest);letter-spacing:var(--tracking-widest)}.break-normal{overflow-wrap:normal;word-break:normal}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.break-keep{word-break:keep-all}.text-clip{text-overflow:clip}.text-ellipsis{text-overflow:ellipsis}.whitespace-break-spaces{white-space:break-spaces}.whitespace-normal{white-space:normal}.whitespace-nowrap{white-space:nowrap}.whitespace-pre{white-space:pre}.whitespace-pre-line{white-space:pre-line}.whitespace-pre-wrap{white-space:pre-wrap}.text-amber-50{color:var(--color-amber-50)}.text-amber-100{color:var(--color-amber-100)}.text-amber-200{color:var(--color-amber-200)}.text-amber-300{color:var(--color-amber-300)}.text-amber-400{color:var(--color-amber-400)}.text-amber-500{color:var(--color-amber-500)}.text-amber-600{color:var(--color-amber-600)}.text-amber-700{color:var(--color-amber-700)}.text-amber-800{color:var(--color-amber-800)}.text-amber-900{color:var(--color-amber-900)}.text-amber-950{color:var(--color-amber-950)}.text-black{color:var(--color-black)}.text-blue-50{color:var(--color-blue-50)}.text-blue-100{color:var(--color-blue-100)}.text-blue-200{color:var(--color-blue-200)}.text-blue-300{color:var(--color-blue-300)}.text-blue-400{color:var(--color-blue-400)}.text-blue-500{color:var(--color-blue-500)}.text-blue-600{color:var(--color-blue-600)}.text-blue-700{color:var(--color-blue-700)}.text-blue-800{color:var(--color-blue-800)}.text-blue-900{color:var(--color-blue-900)}.text-blue-950{color:var(--color-blue-950)}.text-current{color:currentColor}.text-cyan-50{color:var(--color-cyan-50)}.text-cyan-100{color:var(--color-cyan-100)}.text-cyan-200{color:var(--color-cyan-200)}.text-cyan-300{color:var(--color-cyan-300)}.text-cyan-400{color:var(--color-cyan-400)}.text-cyan-500{color:var(--color-cyan-500)}.text-cyan-600{color:var(--color-cyan-600)}.text-cyan-700{color:var(--color-cyan-700)}.text-cyan-800{color:var(--color-cyan-800)}.text-cyan-900{color:var(--color-cyan-900)}.text-cyan-950{color:var(--color-cyan-950)}.text-emerald-50{color:var(--color-emerald-50)}.text-emerald-100{color:var(--color-emerald-100)}.text-emerald-200{color:var(--color-emerald-200)}.text-emerald-300{color:var(--color-emerald-300)}.text-emerald-400{color:var(--color-emerald-400)}.text-emerald-500{color:var(--color-emerald-500)}.text-emerald-600{color:var(--color-emerald-600)}.text-emerald-700{color:var(--color-emerald-700)}.text-emerald-800{color:var(--color-emerald-800)}.text-emerald-900{color:var(--color-emerald-900)}.text-emerald-950{color:var(--color-emerald-950)}.text-fuchsia-50{color:var(--color-fuchsia-50)}.text-fuchsia-100{color:var(--color-fuchsia-100)}.text-fuchsia-200{color:var(--color-fuchsia-200)}.text-fuchsia-300{color:var(--color-fuchsia-300)}.text-fuchsia-400{color:var(--color-fuchsia-400)}.text-fuchsia-500{color:var(--color-fuchsia-500)}.text-fuchsia-600{color:var(--color-fuchsia-600)}.text-fuchsia-700{color:var(--color-fuchsia-700)}.text-fuchsia-800{color:var(--color-fuchsia-800)}.text-fuchsia-900{color:var(--color-fuchsia-900)}.text-fuchsia-950{color:var(--color-fuchsia-950)}.text-gray-50{color:var(--color-gray-50)}.text-gray-100{color:var(--color-gray-100)}.text-gray-200{color:var(--color-gray-200)}.text-gray-300{color:var(--color-gray-300)}.text-gray-400{color:var(--color-gray-400)}.text-gray-500{color:var(--color-gray-500)}.text-gray-600{color:var(--color-gray-600)}.text-gray-700{color:var(--color-gray-700)}.text-gray-800{color:var(--color-gray-800)}.text-gray-900{color:var(--color-gray-900)}.text-gray-950{color:var(--color-gray-950)}.text-green-50{color:var(--color-green-50)}.text-green-100{color:var(--color-green-100)}.text-green-200{color:var(--color-green-200)}.text-green-300{color:var(--color-green-300)}.text-green-400{color:var(--color-green-400)}.text-green-500{color:var(--color-green-500)}.text-green-600{color:var(--color-green-600)}.text-green-700{color:var(--color-green-700)}.text-green-800{color:var(--color-green-800)}.text-green-900{color:var(--color-green-900)}.text-green-950{color:var(--color-green-950)}.text-indigo-50{color:var(--color-indigo-50)}.text-indigo-100{color:var(--color-indigo-100)}.text-indigo-200{color:var(--color-indigo-200)}.text-indigo-300{color:var(--color-indigo-300)}.text-indigo-400{color:var(--color-indigo-400)}.text-indigo-500{color:var(--color-indigo-500)}.text-indigo-600{color:var(--color-indigo-600)}.text-indigo-700{color:var(--color-indigo-700)}.text-indigo-800{color:var(--color-indigo-800)}.text-indigo-900{color:var(--color-indigo-900)}.text-indigo-950{color:var(--color-indigo-950)}.text-inherit{color:inherit}.text-lime-50{color:var(--color-lime-50)}.text-lime-100{color:var(--color-lime-100)}.text-lime-200{color:var(--color-lime-200)}.text-lime-300{color:var(--color-lime-300)}.text-lime-400{color:var(--color-lime-400)}.text-lime-500{color:var(--color-lime-500)}.text-lime-600{color:var(--color-lime-600)}.text-lime-700{color:var(--color-lime-700)}.text-lime-800{color:var(--color-lime-800)}.text-lime-900{color:var(--color-lime-900)}.text-lime-950{color:var(--color-lime-950)}.text-neutral-50{color:var(--color-neutral-50)}.text-neutral-100{color:var(--color-neutral-100)}.text-neutral-200{color:var(--color-neutral-200)}.text-neutral-300{color:var(--color-neutral-300)}.text-neutral-400{color:var(--color-neutral-400)}.text-neutral-500{color:var(--color-neutral-500)}.text-neutral-600{color:var(--color-neutral-600)}.text-neutral-700{color:var(--color-neutral-700)}.text-neutral-800{color:var(--color-neutral-800)}.text-neutral-900{color:var(--color-neutral-900)}.text-neutral-950{color:var(--color-neutral-950)}.text-orange-50{color:var(--color-orange-50)}.text-orange-100{color:var(--color-orange-100)}.text-orange-200{color:var(--color-orange-200)}.text-orange-300{color:var(--color-orange-300)}.text-orange-400{color:var(--color-orange-400)}.text-orange-500{color:var(--color-orange-500)}.text-orange-600{color:var(--color-orange-600)}.text-orange-700{color:var(--color-orange-700)}.text-orange-800{color:var(--color-orange-800)}.text-orange-900{color:var(--color-orange-900)}.text-orange-950{color:var(--color-orange-950)}.text-pink-50{color:var(--color-pink-50)}.text-pink-100{color:var(--color-pink-100)}.text-pink-200{color:var(--color-pink-200)}.text-pink-300{color:var(--color-pink-300)}.text-pink-400{color:var(--color-pink-400)}.text-pink-500{color:var(--color-pink-500)}.text-pink-600{color:var(--color-pink-600)}.text-pink-700{color:var(--color-pink-700)}.text-pink-800{color:var(--color-pink-800)}.text-pink-900{color:var(--color-pink-900)}.text-pink-950{color:var(--color-pink-950)}.text-purple-50{color:var(--color-purple-50)}.text-purple-100{color:var(--color-purple-100)}.text-purple-200{color:var(--color-purple-200)}.text-purple-300{color:var(--color-purple-300)}.text-purple-400{color:var(--color-purple-400)}.text-purple-500{color:var(--color-purple-500)}.text-purple-600{color:var(--color-purple-600)}.text-purple-700{color:var(--color-purple-700)}.text-purple-800{color:var(--color-purple-800)}.text-purple-900{color:var(--color-purple-900)}.text-purple-950{color:var(--color-purple-950)}.text-red-50{color:var(--color-red-50)}.text-red-100{color:var(--color-red-100)}.text-red-200{color:var(--color-red-200)}.text-red-300{color:var(--color-red-300)}.text-red-400{color:var(--color-red-400)}.text-red-500{color:var(--color-red-500)}.text-red-600{color:var(--color-red-600)}.text-red-700{color:var(--color-red-700)}.text-red-800{color:var(--color-red-800)}.text-red-900{color:var(--color-red-900)}.text-red-950{color:var(--color-red-950)}.text-rose-50{color:var(--color-rose-50)}.text-rose-100{color:var(--color-rose-100)}.text-rose-200{color:var(--color-rose-200)}.text-rose-300{color:var(--color-rose-300)}.text-rose-400{color:var(--color-rose-400)}.text-rose-500{color:var(--color-rose-500)}.text-rose-600{color:var(--color-rose-600)}.text-rose-700{color:var(--color-rose-700)}.text-rose-800{color:var(--color-rose-800)}.text-rose-900{color:var(--color-rose-900)}.text-rose-950{color:var(--color-rose-950)}.text-sky-50{color:var(--color-sky-50)}.text-sky-100{color:var(--color-sky-100)}.text-sky-200{color:var(--color-sky-200)}.text-sky-300{color:var(--color-sky-300)}.text-sky-400{color:var(--color-sky-400)}.text-sky-500{color:var(--color-sky-500)}.text-sky-600{color:var(--color-sky-600)}.text-sky-700{color:var(--color-sky-700)}.text-sky-800{color:var(--color-sky-800)}.text-sky-900{color:var(--color-sky-900)}.text-sky-950{color:var(--color-sky-950)}.text-slate-50{color:var(--color-slate-50)}.text-slate-100{color:var(--color-slate-100)}.text-slate-200{color:var(--color-slate-200)}.text-slate-300{color:var(--color-slate-300)}.text-slate-400{color:var(--color-slate-400)}.text-slate-500{color:var(--color-slate-500)}.text-slate-600{color:var(--color-slate-600)}.text-slate-700{color:var(--color-slate-700)}.text-slate-800{color:var(--color-slate-800)}.text-slate-900{color:var(--color-slate-900)}.text-slate-950{color:var(--color-slate-950)}.text-stone-50{color:var(--color-stone-50)}.text-stone-100{color:var(--color-stone-100)}.text-stone-200{color:var(--color-stone-200)}.text-stone-300{color:var(--color-stone-300)}.text-stone-400{color:var(--color-stone-400)}.text-stone-500{color:var(--color-stone-500)}.text-stone-600{color:var(--color-stone-600)}.text-stone-700{color:var(--color-stone-700)}.text-stone-800{color:var(--color-stone-800)}.text-stone-900{color:var(--color-stone-900)}.text-stone-950{color:var(--color-stone-950)}.text-teal-50{color:var(--color-teal-50)}.text-teal-100{color:var(--color-teal-100)}.text-teal-200{color:var(--color-teal-200)}.text-teal-300{color:var(--color-teal-300)}.text-teal-400{color:var(--color-teal-400)}.text-teal-500{color:var(--color-teal-500)}.text-teal-600{color:var(--color-teal-600)}.text-teal-700{color:var(--color-teal-700)}.text-teal-800{color:var(--color-teal-800)}.text-teal-900{color:var(--color-teal-900)}.text-teal-950{color:var(--color-teal-950)}.text-transparent{color:#0000}.text-violet-50{color:var(--color-violet-50)}.text-violet-100{color:var(--color-violet-100)}.text-violet-200{color:var(--color-violet-200)}.text-violet-300{color:var(--color-violet-300)}.text-violet-400{color:var(--color-violet-400)}.text-violet-500{color:var(--color-violet-500)}.text-violet-600{color:var(--color-violet-600)}.text-violet-700{color:var(--color-violet-700)}.text-violet-800{color:var(--color-violet-800)}.text-violet-900{color:var(--color-violet-900)}.text-violet-950{color:var(--color-violet-950)}.text-white{color:var(--color-white)}.text-yellow-50{color:var(--color-yellow-50)}.text-yellow-100{color:var(--color-yellow-100)}.text-yellow-200{color:var(--color-yellow-200)}.text-yellow-300{color:var(--color-yellow-300)}.text-yellow-400{color:var(--color-yellow-400)}.text-yellow-500{color:var(--color-yellow-500)}.text-yellow-600{color:var(--color-yellow-600)}.text-yellow-700{color:var(--color-yellow-700)}.text-yellow-800{color:var(--color-yellow-800)}.text-yellow-900{color:var(--color-yellow-900)}.text-yellow-950{color:var(--color-yellow-950)}.text-zinc-50{color:var(--color-zinc-50)}.text-zinc-100{color:var(--color-zinc-100)}.text-zinc-200{color:var(--color-zinc-200)}.text-zinc-300{color:var(--color-zinc-300)}.text-zinc-400{color:var(--color-zinc-400)}.text-zinc-500{color:var(--color-zinc-500)}.text-zinc-600{color:var(--color-zinc-600)}.text-zinc-700{color:var(--color-zinc-700)}.text-zinc-800{color:var(--color-zinc-800)}.text-zinc-900{color:var(--color-zinc-900)}.text-zinc-950{color:var(--color-zinc-950)}.capitalize{text-transform:capitalize}.lowercase{text-transform:lowercase}.normal-case{text-transform:none}.uppercase{text-transform:uppercase}.italic{font-style:italic}.not-italic{font-style:normal}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}.line-through{text-decoration-line:line-through}.no-underline{text-decoration-line:none}.overline{text-decoration-line:overline}.underline{text-decoration-line:underline}.placeholder-gray-400::placeholder{color:var(--color-gray-400)}.accent-blue-500{accent-color:var(--color-blue-500)}.opacity-0{opacity:0}.opacity-5{opacity:.05}.opacity-10{opacity:.1}.opacity-15{opacity:.15}.opacity-20{opacity:.2}.opacity-25{opacity:.25}.opacity-30{opacity:.3}.opacity-35{opacity:.35}.opacity-40{opacity:.4}.opacity-45{opacity:.45}.opacity-50{opacity:.5}.opacity-55{opacity:.55}.opacity-60{opacity:.6}.opacity-65{opacity:.65}.opacity-70{opacity:.7}.opacity-75{opacity:.75}.opacity-80{opacity:.8}.opacity-85{opacity:.85}.opacity-90{opacity:.9}.opacity-95{opacity:.95}.opacity-100{opacity:1}.bg-blend-color{background-blend-mode:color}.bg-blend-color-burn{background-blend-mode:color-burn}.bg-blend-color-dodge{background-blend-mode:color-dodge}.bg-blend-darken{background-blend-mode:darken}.bg-blend-difference{background-blend-mode:difference}.bg-blend-exclusion{background-blend-mode:exclusion}.bg-blend-hard-light{background-blend-mode:hard-light}.bg-blend-hue{background-blend-mode:hue}.bg-blend-lighten{background-blend-mode:lighten}.bg-blend-luminosity{background-blend-mode:luminosity}.bg-blend-multiply{background-blend-mode:multiply}.bg-blend-normal{background-blend-mode:normal}.bg-blend-overlay{background-blend-mode:overlay}.bg-blend-saturation{background-blend-mode:saturation}.bg-blend-screen{background-blend-mode:screen}.bg-blend-soft-light{background-blend-mode:soft-light}.mix-blend-color{mix-blend-mode:color}.mix-blend-color-burn{mix-blend-mode:color-burn}.mix-blend-color-dodge{mix-blend-mode:color-dodge}.mix-blend-darken{mix-blend-mode:darken}.mix-blend-difference{mix-blend-mode:difference}.mix-blend-exclusion{mix-blend-mode:exclusion}.mix-blend-hard-light{mix-blend-mode:hard-light}.mix-blend-hue{mix-blend-mode:hue}.mix-blend-lighten{mix-blend-mode:lighten}.mix-blend-luminosity{mix-blend-mode:luminosity}.mix-blend-multiply{mix-blend-mode:multiply}.mix-blend-normal{mix-blend-mode:normal}.mix-blend-overlay{mix-blend-mode:overlay}.mix-blend-plus-darker{mix-blend-mode:plus-darker}.mix-blend-plus-lighter{mix-blend-mode:plus-lighter}.mix-blend-saturation{mix-blend-mode:saturation}.mix-blend-screen{mix-blend-mode:screen}.mix-blend-soft-light{mix-blend-mode:soft-light}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-2xl{--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-inner{--tw-shadow:inset 0 2px 4px 0 var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-md{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a), 0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-none{--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a), 0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-0{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-1{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-2{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-4{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-8{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(8px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-purple-200{--tw-shadow-color:oklch(90.2% .063 306.703)}@supports (color:color-mix(in lab,red,red)){.shadow-purple-200{--tw-shadow-color:color-mix(in oklab, var(--color-purple-200) var(--tw-shadow-alpha), transparent)}}.ring-black{--tw-ring-color:var(--color-black)}.ring-blue-50{--tw-ring-color:var(--color-blue-50)}.ring-blue-100{--tw-ring-color:var(--color-blue-100)}.ring-blue-200{--tw-ring-color:var(--color-blue-200)}.ring-blue-300{--tw-ring-color:var(--color-blue-300)}.ring-blue-400{--tw-ring-color:var(--color-blue-400)}.ring-blue-500{--tw-ring-color:var(--color-blue-500)}.ring-blue-600{--tw-ring-color:var(--color-blue-600)}.ring-blue-700{--tw-ring-color:var(--color-blue-700)}.ring-blue-800{--tw-ring-color:var(--color-blue-800)}.ring-blue-900{--tw-ring-color:var(--color-blue-900)}.ring-current{--tw-ring-color:currentcolor}.ring-gray-50{--tw-ring-color:var(--color-gray-50)}.ring-gray-100{--tw-ring-color:var(--color-gray-100)}.ring-gray-200{--tw-ring-color:var(--color-gray-200)}.ring-gray-300{--tw-ring-color:var(--color-gray-300)}.ring-gray-400{--tw-ring-color:var(--color-gray-400)}.ring-gray-500{--tw-ring-color:var(--color-gray-500)}.ring-gray-600{--tw-ring-color:var(--color-gray-600)}.ring-gray-700{--tw-ring-color:var(--color-gray-700)}.ring-gray-800{--tw-ring-color:var(--color-gray-800)}.ring-gray-900{--tw-ring-color:var(--color-gray-900)}.ring-inherit{--tw-ring-color:inherit}.ring-slate-50{--tw-ring-color:var(--color-slate-50)}.ring-slate-100{--tw-ring-color:var(--color-slate-100)}.ring-slate-200{--tw-ring-color:var(--color-slate-200)}.ring-slate-300{--tw-ring-color:var(--color-slate-300)}.ring-slate-400{--tw-ring-color:var(--color-slate-400)}.ring-slate-500{--tw-ring-color:var(--color-slate-500)}.ring-slate-600{--tw-ring-color:var(--color-slate-600)}.ring-slate-700{--tw-ring-color:var(--color-slate-700)}.ring-slate-800{--tw-ring-color:var(--color-slate-800)}.ring-slate-900{--tw-ring-color:var(--color-slate-900)}.ring-transparent{--tw-ring-color:transparent}.ring-white{--tw-ring-color:var(--color-white)}.ring-offset-0{--tw-ring-offset-width:0px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.ring-offset-1{--tw-ring-offset-width:1px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.ring-offset-2{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.ring-offset-4{--tw-ring-offset-width:4px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.ring-offset-8{--tw-ring-offset-width:8px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.ring-offset-black{--tw-ring-offset-color:var(--color-black)}.ring-offset-current{--tw-ring-offset-color:currentcolor}.ring-offset-inherit{--tw-ring-offset-color:inherit}.ring-offset-transparent{--tw-ring-offset-color:transparent}.ring-offset-white{--tw-ring-offset-color:var(--color-white)}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.outline-0{outline-style:var(--tw-outline-style);outline-width:0}.outline-1{outline-style:var(--tw-outline-style);outline-width:1px}.outline-2{outline-style:var(--tw-outline-style);outline-width:2px}.outline-4{outline-style:var(--tw-outline-style);outline-width:4px}.outline-8{outline-style:var(--tw-outline-style);outline-width:8px}.outline-offset-0{outline-offset:0px}.outline-offset-1{outline-offset:1px}.outline-offset-2{outline-offset:2px}.outline-offset-4{outline-offset:4px}.outline-offset-8{outline-offset:8px}.outline-black{outline-color:var(--color-black)}.outline-current{outline-color:currentColor}.outline-inherit{outline-color:inherit}.outline-transparent{outline-color:#0000}.outline-white{outline-color:var(--color-white)}.blur{--tw-blur:blur(8px);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.blur-2xl{--tw-blur:blur(var(--blur-2xl));filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.blur-3xl{--tw-blur:blur(var(--blur-3xl));filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.blur-lg{--tw-blur:blur(var(--blur-lg));filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.blur-md{--tw-blur:blur(var(--blur-md));filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.blur-none{--tw-blur: ;filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.blur-sm{--tw-blur:blur(var(--blur-sm));filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.blur-xl{--tw-blur:blur(var(--blur-xl));filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.brightness-0{--tw-brightness:brightness(0%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.brightness-50{--tw-brightness:brightness(50%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.brightness-75{--tw-brightness:brightness(75%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.brightness-90{--tw-brightness:brightness(90%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.brightness-95{--tw-brightness:brightness(95%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.brightness-100{--tw-brightness:brightness(100%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.brightness-105{--tw-brightness:brightness(105%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.brightness-110{--tw-brightness:brightness(110%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.brightness-125{--tw-brightness:brightness(125%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.brightness-150{--tw-brightness:brightness(150%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.brightness-200{--tw-brightness:brightness(200%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.contrast-0{--tw-contrast:contrast(0%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.contrast-50{--tw-contrast:contrast(50%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.contrast-75{--tw-contrast:contrast(75%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.contrast-100{--tw-contrast:contrast(100%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.contrast-125{--tw-contrast:contrast(125%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.contrast-150{--tw-contrast:contrast(150%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.contrast-200{--tw-contrast:contrast(200%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.drop-shadow{--tw-drop-shadow-size:drop-shadow(0 1px 2px var(--tw-drop-shadow-color,#0000001a)) drop-shadow(0 1px 1px var(--tw-drop-shadow-color,#0000000f));--tw-drop-shadow:drop-shadow(0 1px 2px #0000001a) drop-shadow(0 1px 1px #0000000f);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.drop-shadow-2xl{--tw-drop-shadow-size:drop-shadow(0 25px 25px var(--tw-drop-shadow-color,#00000026));--tw-drop-shadow:drop-shadow(var(--drop-shadow-2xl));filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.drop-shadow-lg{--tw-drop-shadow-size:drop-shadow(0 4px 4px var(--tw-drop-shadow-color,#00000026));--tw-drop-shadow:drop-shadow(var(--drop-shadow-lg));filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.drop-shadow-md{--tw-drop-shadow-size:drop-shadow(0 3px 3px var(--tw-drop-shadow-color,#0000001f));--tw-drop-shadow:drop-shadow(var(--drop-shadow-md));filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.drop-shadow-sm{--tw-drop-shadow-size:drop-shadow(0 1px 2px var(--tw-drop-shadow-color,#00000026));--tw-drop-shadow:drop-shadow(var(--drop-shadow-sm));filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.drop-shadow-xl{--tw-drop-shadow-size:drop-shadow(0 9px 7px var(--tw-drop-shadow-color,#0000001a));--tw-drop-shadow:drop-shadow(var(--drop-shadow-xl));filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.drop-shadow-none{--tw-drop-shadow: ;filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.grayscale{--tw-grayscale:grayscale(100%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.grayscale-0{--tw-grayscale:grayscale(0%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.-hue-rotate-15{--tw-hue-rotate:hue-rotate( -15deg );filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.-hue-rotate-30{--tw-hue-rotate:hue-rotate( -30deg );filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.-hue-rotate-60{--tw-hue-rotate:hue-rotate( -60deg );filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.-hue-rotate-90{--tw-hue-rotate:hue-rotate( -90deg );filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.-hue-rotate-180{--tw-hue-rotate:hue-rotate( -180deg );filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.hue-rotate-0{--tw-hue-rotate:hue-rotate(0deg);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.hue-rotate-15{--tw-hue-rotate:hue-rotate(15deg);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.hue-rotate-30{--tw-hue-rotate:hue-rotate(30deg);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.hue-rotate-60{--tw-hue-rotate:hue-rotate(60deg);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.hue-rotate-90{--tw-hue-rotate:hue-rotate(90deg);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.hue-rotate-180{--tw-hue-rotate:hue-rotate(180deg);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.invert{--tw-invert:invert(100%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.invert-0{--tw-invert:invert(0%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.saturate-0{--tw-saturate:saturate(0%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.saturate-50{--tw-saturate:saturate(50%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.saturate-100{--tw-saturate:saturate(100%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.saturate-150{--tw-saturate:saturate(150%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.saturate-200{--tw-saturate:saturate(200%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.sepia{--tw-sepia:sepia(100%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.sepia-0{--tw-sepia:sepia(0%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.backdrop-blur{--tw-backdrop-blur:blur(8px);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-blur-2xl{--tw-backdrop-blur:blur(var(--blur-2xl));-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-blur-3xl{--tw-backdrop-blur:blur(var(--blur-3xl));-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-blur-lg{--tw-backdrop-blur:blur(var(--blur-lg));-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-blur-md{--tw-backdrop-blur:blur(var(--blur-md));-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-blur-none{--tw-backdrop-blur: ;-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-blur-sm{--tw-backdrop-blur:blur(var(--blur-sm));-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-blur-xl{--tw-backdrop-blur:blur(var(--blur-xl));-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-brightness-0{--tw-backdrop-brightness:brightness(0%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-brightness-50{--tw-backdrop-brightness:brightness(50%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-brightness-75{--tw-backdrop-brightness:brightness(75%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-brightness-90{--tw-backdrop-brightness:brightness(90%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-brightness-95{--tw-backdrop-brightness:brightness(95%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-brightness-100{--tw-backdrop-brightness:brightness(100%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-brightness-105{--tw-backdrop-brightness:brightness(105%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-brightness-110{--tw-backdrop-brightness:brightness(110%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-brightness-125{--tw-backdrop-brightness:brightness(125%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-brightness-150{--tw-backdrop-brightness:brightness(150%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-brightness-200{--tw-backdrop-brightness:brightness(200%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-contrast-0{--tw-backdrop-contrast:contrast(0%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-contrast-50{--tw-backdrop-contrast:contrast(50%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-contrast-75{--tw-backdrop-contrast:contrast(75%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-contrast-100{--tw-backdrop-contrast:contrast(100%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-contrast-125{--tw-backdrop-contrast:contrast(125%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-contrast-150{--tw-backdrop-contrast:contrast(150%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-contrast-200{--tw-backdrop-contrast:contrast(200%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-grayscale{--tw-backdrop-grayscale:grayscale(100%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-grayscale-0{--tw-backdrop-grayscale:grayscale(0%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-hue-rotate-0{--tw-backdrop-hue-rotate:hue-rotate(0deg);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-hue-rotate-15{--tw-backdrop-hue-rotate:hue-rotate(15deg);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-hue-rotate-30{--tw-backdrop-hue-rotate:hue-rotate(30deg);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-hue-rotate-60{--tw-backdrop-hue-rotate:hue-rotate(60deg);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-hue-rotate-90{--tw-backdrop-hue-rotate:hue-rotate(90deg);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-hue-rotate-180{--tw-backdrop-hue-rotate:hue-rotate(180deg);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-invert{--tw-backdrop-invert:invert(100%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-invert-0{--tw-backdrop-invert:invert(0%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-opacity-0{--tw-backdrop-opacity:opacity(0%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-opacity-5{--tw-backdrop-opacity:opacity(5%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-opacity-10{--tw-backdrop-opacity:opacity(10%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-opacity-20{--tw-backdrop-opacity:opacity(20%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-opacity-25{--tw-backdrop-opacity:opacity(25%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-opacity-30{--tw-backdrop-opacity:opacity(30%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-opacity-40{--tw-backdrop-opacity:opacity(40%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-opacity-50{--tw-backdrop-opacity:opacity(50%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-opacity-60{--tw-backdrop-opacity:opacity(60%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-opacity-70{--tw-backdrop-opacity:opacity(70%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-opacity-75{--tw-backdrop-opacity:opacity(75%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-opacity-80{--tw-backdrop-opacity:opacity(80%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-opacity-90{--tw-backdrop-opacity:opacity(90%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-opacity-95{--tw-backdrop-opacity:opacity(95%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-opacity-100{--tw-backdrop-opacity:opacity(100%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-saturate-0{--tw-backdrop-saturate:saturate(0%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-saturate-50{--tw-backdrop-saturate:saturate(50%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-saturate-100{--tw-backdrop-saturate:saturate(100%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-saturate-150{--tw-backdrop-saturate:saturate(150%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-saturate-200{--tw-backdrop-saturate:saturate(200%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-sepia{--tw-backdrop-sepia:sepia(100%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-sepia-0{--tw-backdrop-sepia:sepia(0%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-shadow{transition-property:box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-none{transition-property:none}.delay-75{transition-delay:75ms}.delay-100{transition-delay:.1s}.delay-150{transition-delay:.15s}.delay-200{transition-delay:.2s}.delay-300{transition-delay:.3s}.delay-500{transition-delay:.5s}.delay-700{transition-delay:.7s}.delay-1000{transition-delay:1s}.duration-75{--tw-duration:75ms;transition-duration:75ms}.duration-100{--tw-duration:.1s;transition-duration:.1s}.duration-150{--tw-duration:.15s;transition-duration:.15s}.duration-200{--tw-duration:.2s;transition-duration:.2s}.duration-300{--tw-duration:.3s;transition-duration:.3s}.duration-500{--tw-duration:.5s;transition-duration:.5s}.duration-700{--tw-duration:.7s;transition-duration:.7s}.duration-1000{--tw-duration:1s;transition-duration:1s}.ease-in{--tw-ease:var(--ease-in);transition-timing-function:var(--ease-in)}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}.ease-linear{--tw-ease:linear;transition-timing-function:linear}.ease-out{--tw-ease:var(--ease-out);transition-timing-function:var(--ease-out)}.outline-dashed{--tw-outline-style:dashed;outline-style:dashed}.outline-dotted{--tw-outline-style:dotted;outline-style:dotted}.outline-double{--tw-outline-style:double;outline-style:double}.outline-none{--tw-outline-style:none;outline-style:none}.select-all{-webkit-user-select:all;user-select:all}.select-auto{-webkit-user-select:auto;user-select:auto}.select-none{-webkit-user-select:none;user-select:none}.select-text{-webkit-user-select:text;user-select:text}:where(.divide-x-reverse>:not(:last-child)){--tw-divide-x-reverse:1}.ring-inset{--tw-ring-inset:inset}@media(hover:hover){.group-hover\:visible:is(:where(.group):hover *){visibility:visible}.group-hover\:translate-x-1:is(:where(.group):hover *){--tw-translate-x:var(--spacing);translate:var(--tw-translate-x) var(--tw-translate-y)}.group-hover\:translate-y-1:is(:where(.group):hover *){--tw-translate-y:var(--spacing);translate:var(--tw-translate-x) var(--tw-translate-y)}.group-hover\:scale-100:is(:where(.group):hover *){--tw-scale-x:100%;--tw-scale-y:100%;--tw-scale-z:100%;scale:var(--tw-scale-x) var(--tw-scale-y)}.group-hover\:scale-105:is(:where(.group):hover *){--tw-scale-x:105%;--tw-scale-y:105%;--tw-scale-z:105%;scale:var(--tw-scale-x) var(--tw-scale-y)}.group-hover\:bg-black\/40:is(:where(.group):hover *){background-color:#0006}@supports (color:color-mix(in lab,red,red)){.group-hover\:bg-black\/40:is(:where(.group):hover *){background-color:color-mix(in oklab,var(--color-black) 40%,transparent)}}.group-hover\:bg-blue-100:is(:where(.group):hover *){background-color:var(--color-blue-100)}.group-hover\:bg-blue-500:is(:where(.group):hover *){background-color:var(--color-blue-500)}.group-hover\:bg-blue-600:is(:where(.group):hover *){background-color:var(--color-blue-600)}.group-hover\:bg-gray-100:is(:where(.group):hover *){background-color:var(--color-gray-100)}.group-hover\:bg-gray-200:is(:where(.group):hover *){background-color:var(--color-gray-200)}.group-hover\:bg-gray-700:is(:where(.group):hover *){background-color:var(--color-gray-700)}.group-hover\:bg-gray-800:is(:where(.group):hover *){background-color:var(--color-gray-800)}.group-hover\:text-black:is(:where(.group):hover *){color:var(--color-black)}.group-hover\:text-blue-500:is(:where(.group):hover *){color:var(--color-blue-500)}.group-hover\:text-blue-600:is(:where(.group):hover *){color:var(--color-blue-600)}.group-hover\:text-blue-700:is(:where(.group):hover *){color:var(--color-blue-700)}.group-hover\:text-gray-700:is(:where(.group):hover *){color:var(--color-gray-700)}.group-hover\:text-gray-800:is(:where(.group):hover *){color:var(--color-gray-800)}.group-hover\:text-gray-900:is(:where(.group):hover *){color:var(--color-gray-900)}.group-hover\:text-white:is(:where(.group):hover *){color:var(--color-white)}.group-hover\:opacity-100:is(:where(.group):hover *){opacity:1}}.group-focus\:border-blue-500:is(:where(.group):focus *){border-color:var(--color-blue-500)}.group-focus\:ring-2:is(:where(.group):focus *){--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.peer-checked\:border-blue-500:is(:where(.peer):checked~*){border-color:var(--color-blue-500)}.peer-checked\:bg-blue-500:is(:where(.peer):checked~*){background-color:var(--color-blue-500)}.peer-invalid\:border-red-500:is(:where(.peer):invalid~*){border-color:var(--color-red-500)}.peer-invalid\:text-red-500:is(:where(.peer):invalid~*){color:var(--color-red-500)}.peer-focus\:border-blue-500:is(:where(.peer):focus~*){border-color:var(--color-blue-500)}.peer-focus\:ring-2:is(:where(.peer):focus~*){--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.peer-disabled\:cursor-not-allowed:is(:where(.peer):disabled~*){cursor:not-allowed}.peer-disabled\:opacity-50:is(:where(.peer):disabled~*){opacity:.5}.first-letter\:text-2xl:first-letter{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.first-letter\:font-bold:first-letter{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.first-letter\:text-blue-600:first-letter{color:var(--color-blue-600)}.first-line\:font-semibold:first-line{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.first-line\:text-gray-700:first-line{color:var(--color-gray-700)}.marker\:text-blue-500 ::marker{color:var(--color-blue-500)}.marker\:text-blue-500::marker{color:var(--color-blue-500)}.marker\:text-blue-500 ::-webkit-details-marker{color:var(--color-blue-500)}.marker\:text-blue-500::-webkit-details-marker{color:var(--color-blue-500)}.marker\:text-gray-500 ::marker{color:var(--color-gray-500)}.marker\:text-gray-500::marker{color:var(--color-gray-500)}.marker\:text-gray-500 ::-webkit-details-marker{color:var(--color-gray-500)}.marker\:text-gray-500::-webkit-details-marker{color:var(--color-gray-500)}.selection\:bg-blue-200 ::selection{background-color:var(--color-blue-200)}.selection\:bg-blue-200::selection{background-color:var(--color-blue-200)}.selection\:text-blue-900 ::selection{color:var(--color-blue-900)}.selection\:text-blue-900::selection{color:var(--color-blue-900)}.file\:mr-4::file-selector-button{margin-right:calc(var(--spacing) * 4)}.file\:cursor-pointer::file-selector-button{cursor:pointer}.file\:rounded-full::file-selector-button{border-radius:3.40282e38px}.file\:border-0::file-selector-button{border-style:var(--tw-border-style);border-width:0}.file\:bg-blue-50::file-selector-button{background-color:var(--color-blue-50)}.file\:px-4::file-selector-button{padding-inline:calc(var(--spacing) * 4)}.file\:py-2::file-selector-button{padding-block:calc(var(--spacing) * 2)}.file\:text-sm::file-selector-button{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.file\:font-semibold::file-selector-button{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.file\:text-blue-700::file-selector-button{color:var(--color-blue-700)}.before\:absolute:before{content:var(--tw-content);position:absolute}.before\:inset-0:before{content:var(--tw-content);inset:0}.before\:block:before{content:var(--tw-content);display:block}.after\:absolute:after{content:var(--tw-content);position:absolute}.after\:inset-0:after{content:var(--tw-content);inset:0}.after\:clear-both:after{content:var(--tw-content);clear:both}.after\:block:after{content:var(--tw-content);display:block}.after\:h-4:after{content:var(--tw-content);height:calc(var(--spacing) * 4)}.after\:h-5:after{content:var(--tw-content);height:calc(var(--spacing) * 5)}.after\:h-6:after{content:var(--tw-content);height:calc(var(--spacing) * 6)}.after\:w-4:after{content:var(--tw-content);width:calc(var(--spacing) * 4)}.after\:w-5:after{content:var(--tw-content);width:calc(var(--spacing) * 5)}.after\:w-6:after{content:var(--tw-content);width:calc(var(--spacing) * 6)}.after\:content-\[\'\'\]:after{--tw-content:"";content:var(--tw-content)}.first\:mt-0:first-child{margin-top:0}.first\:rounded-t-lg:first-child{border-top-left-radius:var(--radius-lg);border-top-right-radius:var(--radius-lg)}.first\:rounded-l:first-child{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.first\:rounded-l-lg:first-child{border-top-left-radius:var(--radius-lg);border-bottom-left-radius:var(--radius-lg)}.first\:border-t-0:first-child{border-top-style:var(--tw-border-style);border-top-width:0}.first\:pt-0:first-child{padding-top:0}.last\:mb-0:last-child{margin-bottom:0}.last\:rounded-r:last-child{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.last\:rounded-r-lg:last-child{border-top-right-radius:var(--radius-lg);border-bottom-right-radius:var(--radius-lg)}.last\:rounded-b-lg:last-child{border-bottom-right-radius:var(--radius-lg);border-bottom-left-radius:var(--radius-lg)}.last\:border-r:last-child{border-right-style:var(--tw-border-style);border-right-width:1px}.last\:border-b-0:last-child{border-bottom-style:var(--tw-border-style);border-bottom-width:0}.last\:pb-0:last-child{padding-bottom:0}.odd\:bg-gray-50:nth-child(odd){background-color:var(--color-gray-50)}.odd\:bg-gray-100:nth-child(odd){background-color:var(--color-gray-100)}.odd\:bg-white:nth-child(odd){background-color:var(--color-white)}.even\:bg-gray-50:nth-child(2n){background-color:var(--color-gray-50)}.even\:bg-gray-100:nth-child(2n){background-color:var(--color-gray-100)}.even\:bg-white:nth-child(2n){background-color:var(--color-white)}.checked\:border-blue-500:checked{border-color:var(--color-blue-500)}.checked\:border-blue-600:checked{border-color:var(--color-blue-600)}.checked\:bg-blue-500:checked{background-color:var(--color-blue-500)}.checked\:bg-blue-600:checked{background-color:var(--color-blue-600)}.checked\:bg-green-500:checked{background-color:var(--color-green-500)}.checked\:text-white:checked{color:var(--color-white)}.indeterminate\:border-gray-400:indeterminate{border-color:var(--color-gray-400)}.indeterminate\:bg-gray-300:indeterminate{background-color:var(--color-gray-300)}.placeholder-shown\:border-gray-300:placeholder-shown{border-color:var(--color-gray-300)}.autofill\:bg-yellow-100:autofill{background-color:var(--color-yellow-100)}.autofill\:text-black:autofill{color:var(--color-black)}.valid\:border-green-500:valid{border-color:var(--color-green-500)}.valid\:text-green-600:valid{color:var(--color-green-600)}.valid\:ring-green-500:valid{--tw-ring-color:var(--color-green-500)}.invalid\:border-red-500:invalid{border-color:var(--color-red-500)}.invalid\:text-red-600:invalid{color:var(--color-red-600)}.invalid\:ring-red-500:invalid{--tw-ring-color:var(--color-red-500)}.read-only\:cursor-not-allowed:read-only{cursor:not-allowed}.read-only\:bg-gray-50:read-only{background-color:var(--color-gray-50)}.read-only\:bg-gray-100:read-only{background-color:var(--color-gray-100)}.empty\:hidden:empty{display:none}.focus-within\:border-blue-500:focus-within{border-color:var(--color-blue-500)}.focus-within\:border-gray-500:focus-within{border-color:var(--color-gray-500)}.focus-within\:ring-2:focus-within{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus-within\:ring-blue-500:focus-within{--tw-ring-color:var(--color-blue-500)}.focus-within\:ring-gray-500:focus-within{--tw-ring-color:var(--color-gray-500)}@media(hover:hover){.hover\:-translate-y-0\.5:hover{--tw-translate-y:calc(var(--spacing) * -.5);translate:var(--tw-translate-x) var(--tw-translate-y)}.hover\:scale-95:hover{--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x) var(--tw-scale-y)}.hover\:scale-100:hover{--tw-scale-x:100%;--tw-scale-y:100%;--tw-scale-z:100%;scale:var(--tw-scale-x) var(--tw-scale-y)}.hover\:scale-105:hover{--tw-scale-x:105%;--tw-scale-y:105%;--tw-scale-z:105%;scale:var(--tw-scale-x) var(--tw-scale-y)}.hover\:scale-110:hover{--tw-scale-x:110%;--tw-scale-y:110%;--tw-scale-z:110%;scale:var(--tw-scale-x) var(--tw-scale-y)}.hover\:border-blue-50:hover{border-color:var(--color-blue-50)}.hover\:border-blue-100:hover{border-color:var(--color-blue-100)}.hover\:border-blue-200:hover{border-color:var(--color-blue-200)}.hover\:border-blue-300:hover{border-color:var(--color-blue-300)}.hover\:border-blue-400:hover{border-color:var(--color-blue-400)}.hover\:border-blue-500:hover{border-color:var(--color-blue-500)}.hover\:border-blue-600:hover{border-color:var(--color-blue-600)}.hover\:border-blue-700:hover{border-color:var(--color-blue-700)}.hover\:border-blue-800:hover{border-color:var(--color-blue-800)}.hover\:border-blue-900:hover{border-color:var(--color-blue-900)}.hover\:border-gray-50:hover{border-color:var(--color-gray-50)}.hover\:border-gray-100:hover{border-color:var(--color-gray-100)}.hover\:border-gray-200:hover{border-color:var(--color-gray-200)}.hover\:border-gray-300:hover{border-color:var(--color-gray-300)}.hover\:border-gray-400:hover{border-color:var(--color-gray-400)}.hover\:border-gray-500:hover{border-color:var(--color-gray-500)}.hover\:border-gray-600:hover{border-color:var(--color-gray-600)}.hover\:border-gray-700:hover{border-color:var(--color-gray-700)}.hover\:border-gray-800:hover{border-color:var(--color-gray-800)}.hover\:border-gray-900:hover{border-color:var(--color-gray-900)}.hover\:bg-amber-50:hover{background-color:var(--color-amber-50)}.hover\:bg-amber-700:hover{background-color:var(--color-amber-700)}.hover\:bg-black:hover{background-color:var(--color-black)}.hover\:bg-black\/10:hover{background-color:#0000001a}@supports (color:color-mix(in lab,red,red)){.hover\:bg-black\/10:hover{background-color:color-mix(in oklab,var(--color-black) 10%,transparent)}}.hover\:bg-blue-50:hover{background-color:var(--color-blue-50)}.hover\:bg-blue-100:hover{background-color:var(--color-blue-100)}.hover\:bg-blue-200:hover{background-color:var(--color-blue-200)}.hover\:bg-blue-300:hover{background-color:var(--color-blue-300)}.hover\:bg-blue-400:hover{background-color:var(--color-blue-400)}.hover\:bg-blue-500:hover{background-color:var(--color-blue-500)}.hover\:bg-blue-600:hover{background-color:var(--color-blue-600)}.hover\:bg-blue-700:hover{background-color:var(--color-blue-700)}.hover\:bg-blue-800:hover{background-color:var(--color-blue-800)}.hover\:bg-blue-900:hover{background-color:var(--color-blue-900)}.hover\:bg-gray-50:hover{background-color:var(--color-gray-50)}.hover\:bg-gray-100:hover{background-color:var(--color-gray-100)}.hover\:bg-gray-200:hover{background-color:var(--color-gray-200)}.hover\:bg-gray-300:hover{background-color:var(--color-gray-300)}.hover\:bg-gray-400:hover{background-color:var(--color-gray-400)}.hover\:bg-gray-500:hover{background-color:var(--color-gray-500)}.hover\:bg-gray-600:hover{background-color:var(--color-gray-600)}.hover\:bg-gray-700:hover{background-color:var(--color-gray-700)}.hover\:bg-gray-800:hover{background-color:var(--color-gray-800)}.hover\:bg-gray-900:hover{background-color:var(--color-gray-900)}.hover\:bg-green-50:hover{background-color:var(--color-green-50)}.hover\:bg-green-100:hover{background-color:var(--color-green-100)}.hover\:bg-green-200:hover{background-color:var(--color-green-200)}.hover\:bg-green-300:hover{background-color:var(--color-green-300)}.hover\:bg-green-400:hover{background-color:var(--color-green-400)}.hover\:bg-green-500:hover{background-color:var(--color-green-500)}.hover\:bg-green-600:hover{background-color:var(--color-green-600)}.hover\:bg-green-700:hover{background-color:var(--color-green-700)}.hover\:bg-green-800:hover{background-color:var(--color-green-800)}.hover\:bg-green-900:hover{background-color:var(--color-green-900)}.hover\:bg-indigo-50:hover{background-color:var(--color-indigo-50)}.hover\:bg-indigo-100:hover{background-color:var(--color-indigo-100)}.hover\:bg-indigo-200:hover{background-color:var(--color-indigo-200)}.hover\:bg-indigo-300:hover{background-color:var(--color-indigo-300)}.hover\:bg-indigo-400:hover{background-color:var(--color-indigo-400)}.hover\:bg-indigo-500:hover{background-color:var(--color-indigo-500)}.hover\:bg-indigo-600:hover{background-color:var(--color-indigo-600)}.hover\:bg-indigo-700:hover{background-color:var(--color-indigo-700)}.hover\:bg-indigo-800:hover{background-color:var(--color-indigo-800)}.hover\:bg-indigo-900:hover{background-color:var(--color-indigo-900)}.hover\:bg-orange-50:hover{background-color:var(--color-orange-50)}.hover\:bg-orange-100:hover{background-color:var(--color-orange-100)}.hover\:bg-orange-200:hover{background-color:var(--color-orange-200)}.hover\:bg-orange-300:hover{background-color:var(--color-orange-300)}.hover\:bg-orange-400:hover{background-color:var(--color-orange-400)}.hover\:bg-orange-500:hover{background-color:var(--color-orange-500)}.hover\:bg-orange-600:hover{background-color:var(--color-orange-600)}.hover\:bg-orange-700:hover{background-color:var(--color-orange-700)}.hover\:bg-orange-800:hover{background-color:var(--color-orange-800)}.hover\:bg-orange-900:hover{background-color:var(--color-orange-900)}.hover\:bg-pink-50:hover{background-color:var(--color-pink-50)}.hover\:bg-pink-100:hover{background-color:var(--color-pink-100)}.hover\:bg-pink-200:hover{background-color:var(--color-pink-200)}.hover\:bg-pink-300:hover{background-color:var(--color-pink-300)}.hover\:bg-pink-400:hover{background-color:var(--color-pink-400)}.hover\:bg-pink-500:hover{background-color:var(--color-pink-500)}.hover\:bg-pink-600:hover{background-color:var(--color-pink-600)}.hover\:bg-pink-700:hover{background-color:var(--color-pink-700)}.hover\:bg-pink-800:hover{background-color:var(--color-pink-800)}.hover\:bg-pink-900:hover{background-color:var(--color-pink-900)}.hover\:bg-purple-50:hover{background-color:var(--color-purple-50)}.hover\:bg-purple-100:hover{background-color:var(--color-purple-100)}.hover\:bg-purple-200:hover{background-color:var(--color-purple-200)}.hover\:bg-purple-300:hover{background-color:var(--color-purple-300)}.hover\:bg-purple-400:hover{background-color:var(--color-purple-400)}.hover\:bg-purple-500:hover{background-color:var(--color-purple-500)}.hover\:bg-purple-600:hover{background-color:var(--color-purple-600)}.hover\:bg-purple-700:hover{background-color:var(--color-purple-700)}.hover\:bg-purple-800:hover{background-color:var(--color-purple-800)}.hover\:bg-purple-900:hover{background-color:var(--color-purple-900)}.hover\:bg-red-50:hover{background-color:var(--color-red-50)}.hover\:bg-red-100:hover{background-color:var(--color-red-100)}.hover\:bg-red-200:hover{background-color:var(--color-red-200)}.hover\:bg-red-300:hover{background-color:var(--color-red-300)}.hover\:bg-red-400:hover{background-color:var(--color-red-400)}.hover\:bg-red-500:hover{background-color:var(--color-red-500)}.hover\:bg-red-600:hover{background-color:var(--color-red-600)}.hover\:bg-red-700:hover{background-color:var(--color-red-700)}.hover\:bg-red-800:hover{background-color:var(--color-red-800)}.hover\:bg-red-900:hover{background-color:var(--color-red-900)}.hover\:bg-slate-50:hover{background-color:var(--color-slate-50)}.hover\:bg-slate-100:hover{background-color:var(--color-slate-100)}.hover\:bg-slate-200:hover{background-color:var(--color-slate-200)}.hover\:bg-slate-300:hover{background-color:var(--color-slate-300)}.hover\:bg-slate-400:hover{background-color:var(--color-slate-400)}.hover\:bg-slate-500:hover{background-color:var(--color-slate-500)}.hover\:bg-slate-600:hover{background-color:var(--color-slate-600)}.hover\:bg-slate-700:hover{background-color:var(--color-slate-700)}.hover\:bg-slate-800:hover{background-color:var(--color-slate-800)}.hover\:bg-slate-900:hover{background-color:var(--color-slate-900)}.hover\:bg-transparent:hover{background-color:#0000}.hover\:bg-white:hover{background-color:var(--color-white)}.hover\:bg-yellow-50:hover{background-color:var(--color-yellow-50)}.hover\:bg-yellow-100:hover{background-color:var(--color-yellow-100)}.hover\:bg-yellow-200:hover{background-color:var(--color-yellow-200)}.hover\:bg-yellow-300:hover{background-color:var(--color-yellow-300)}.hover\:bg-yellow-400:hover{background-color:var(--color-yellow-400)}.hover\:bg-yellow-500:hover{background-color:var(--color-yellow-500)}.hover\:bg-yellow-600:hover{background-color:var(--color-yellow-600)}.hover\:bg-yellow-700:hover{background-color:var(--color-yellow-700)}.hover\:bg-yellow-800:hover{background-color:var(--color-yellow-800)}.hover\:bg-yellow-900:hover{background-color:var(--color-yellow-900)}.hover\:text-black:hover{color:var(--color-black)}.hover\:text-blue-50:hover{color:var(--color-blue-50)}.hover\:text-blue-100:hover{color:var(--color-blue-100)}.hover\:text-blue-200:hover{color:var(--color-blue-200)}.hover\:text-blue-300:hover{color:var(--color-blue-300)}.hover\:text-blue-400:hover{color:var(--color-blue-400)}.hover\:text-blue-500:hover{color:var(--color-blue-500)}.hover\:text-blue-600:hover{color:var(--color-blue-600)}.hover\:text-blue-700:hover{color:var(--color-blue-700)}.hover\:text-blue-800:hover{color:var(--color-blue-800)}.hover\:text-blue-900:hover{color:var(--color-blue-900)}.hover\:text-gray-50:hover{color:var(--color-gray-50)}.hover\:text-gray-100:hover{color:var(--color-gray-100)}.hover\:text-gray-200:hover{color:var(--color-gray-200)}.hover\:text-gray-300:hover{color:var(--color-gray-300)}.hover\:text-gray-400:hover{color:var(--color-gray-400)}.hover\:text-gray-500:hover{color:var(--color-gray-500)}.hover\:text-gray-600:hover{color:var(--color-gray-600)}.hover\:text-gray-700:hover{color:var(--color-gray-700)}.hover\:text-gray-800:hover{color:var(--color-gray-800)}.hover\:text-gray-900:hover{color:var(--color-gray-900)}.hover\:text-green-50:hover{color:var(--color-green-50)}.hover\:text-green-100:hover{color:var(--color-green-100)}.hover\:text-green-200:hover{color:var(--color-green-200)}.hover\:text-green-300:hover{color:var(--color-green-300)}.hover\:text-green-400:hover{color:var(--color-green-400)}.hover\:text-green-500:hover{color:var(--color-green-500)}.hover\:text-green-600:hover{color:var(--color-green-600)}.hover\:text-green-700:hover{color:var(--color-green-700)}.hover\:text-green-800:hover{color:var(--color-green-800)}.hover\:text-green-900:hover{color:var(--color-green-900)}.hover\:text-purple-50:hover{color:var(--color-purple-50)}.hover\:text-purple-100:hover{color:var(--color-purple-100)}.hover\:text-purple-200:hover{color:var(--color-purple-200)}.hover\:text-purple-300:hover{color:var(--color-purple-300)}.hover\:text-purple-400:hover{color:var(--color-purple-400)}.hover\:text-purple-500:hover{color:var(--color-purple-500)}.hover\:text-purple-600:hover{color:var(--color-purple-600)}.hover\:text-purple-700:hover{color:var(--color-purple-700)}.hover\:text-purple-800:hover{color:var(--color-purple-800)}.hover\:text-purple-900:hover{color:var(--color-purple-900)}.hover\:text-red-50:hover{color:var(--color-red-50)}.hover\:text-red-100:hover{color:var(--color-red-100)}.hover\:text-red-200:hover{color:var(--color-red-200)}.hover\:text-red-300:hover{color:var(--color-red-300)}.hover\:text-red-400:hover{color:var(--color-red-400)}.hover\:text-red-500:hover{color:var(--color-red-500)}.hover\:text-red-600:hover{color:var(--color-red-600)}.hover\:text-red-700:hover{color:var(--color-red-700)}.hover\:text-red-800:hover{color:var(--color-red-800)}.hover\:text-red-900:hover{color:var(--color-red-900)}.hover\:text-slate-50:hover{color:var(--color-slate-50)}.hover\:text-slate-100:hover{color:var(--color-slate-100)}.hover\:text-slate-200:hover{color:var(--color-slate-200)}.hover\:text-slate-300:hover{color:var(--color-slate-300)}.hover\:text-slate-400:hover{color:var(--color-slate-400)}.hover\:text-slate-500:hover{color:var(--color-slate-500)}.hover\:text-slate-600:hover{color:var(--color-slate-600)}.hover\:text-slate-700:hover{color:var(--color-slate-700)}.hover\:text-slate-800:hover{color:var(--color-slate-800)}.hover\:text-slate-900:hover{color:var(--color-slate-900)}.hover\:text-white:hover{color:var(--color-white)}.hover\:line-through:hover{text-decoration-line:line-through}.hover\:no-underline:hover{text-decoration-line:none}.hover\:underline:hover{text-decoration-line:underline}.hover\:opacity-0:hover{opacity:0}.hover\:opacity-25:hover{opacity:.25}.hover\:opacity-50:hover{opacity:.5}.hover\:opacity-75:hover{opacity:.75}.hover\:opacity-100:hover{opacity:1}.hover\:shadow:hover{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hover\:shadow-2xl:hover{--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hover\:shadow-lg:hover{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hover\:shadow-md:hover{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a), 0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hover\:shadow-none:hover{--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hover\:shadow-sm:hover{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hover\:shadow-xl:hover{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a), 0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hover\:ring-0:hover{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hover\:ring-1:hover{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hover\:ring-2:hover{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hover\:ring-4:hover{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hover\:ring-8:hover{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(8px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hover\:ring-blue-500:hover{--tw-ring-color:var(--color-blue-500)}.hover\:ring-gray-500:hover{--tw-ring-color:var(--color-gray-500)}.hover\:ring-green-500:hover{--tw-ring-color:var(--color-green-500)}.hover\:ring-red-500:hover{--tw-ring-color:var(--color-red-500)}.file\:hover\:bg-blue-100::file-selector-button:hover{background-color:var(--color-blue-100)}}.focus\:border-blue-50:focus{border-color:var(--color-blue-50)}.focus\:border-blue-100:focus{border-color:var(--color-blue-100)}.focus\:border-blue-200:focus{border-color:var(--color-blue-200)}.focus\:border-blue-300:focus{border-color:var(--color-blue-300)}.focus\:border-blue-400:focus{border-color:var(--color-blue-400)}.focus\:border-blue-500:focus{border-color:var(--color-blue-500)}.focus\:border-blue-600:focus{border-color:var(--color-blue-600)}.focus\:border-blue-700:focus{border-color:var(--color-blue-700)}.focus\:border-blue-800:focus{border-color:var(--color-blue-800)}.focus\:border-blue-900:focus{border-color:var(--color-blue-900)}.focus\:border-gray-50:focus{border-color:var(--color-gray-50)}.focus\:border-gray-100:focus{border-color:var(--color-gray-100)}.focus\:border-gray-200:focus{border-color:var(--color-gray-200)}.focus\:border-gray-300:focus{border-color:var(--color-gray-300)}.focus\:border-gray-400:focus{border-color:var(--color-gray-400)}.focus\:border-gray-500:focus{border-color:var(--color-gray-500)}.focus\:border-gray-600:focus{border-color:var(--color-gray-600)}.focus\:border-gray-700:focus{border-color:var(--color-gray-700)}.focus\:border-gray-800:focus{border-color:var(--color-gray-800)}.focus\:border-gray-900:focus{border-color:var(--color-gray-900)}.focus\:border-green-500:focus{border-color:var(--color-green-500)}.focus\:border-indigo-500:focus{border-color:var(--color-indigo-500)}.focus\:border-purple-500:focus{border-color:var(--color-purple-500)}.focus\:border-red-500:focus{border-color:var(--color-red-500)}.focus\:border-transparent:focus{border-color:#0000}.focus\:border-yellow-500:focus{border-color:var(--color-yellow-500)}.focus\:bg-black:focus{background-color:var(--color-black)}.focus\:bg-blue-50:focus{background-color:var(--color-blue-50)}.focus\:bg-blue-100:focus{background-color:var(--color-blue-100)}.focus\:bg-blue-200:focus{background-color:var(--color-blue-200)}.focus\:bg-blue-300:focus{background-color:var(--color-blue-300)}.focus\:bg-blue-400:focus{background-color:var(--color-blue-400)}.focus\:bg-blue-500:focus{background-color:var(--color-blue-500)}.focus\:bg-blue-600:focus{background-color:var(--color-blue-600)}.focus\:bg-blue-700:focus{background-color:var(--color-blue-700)}.focus\:bg-blue-800:focus{background-color:var(--color-blue-800)}.focus\:bg-blue-900:focus{background-color:var(--color-blue-900)}.focus\:bg-gray-50:focus{background-color:var(--color-gray-50)}.focus\:bg-gray-100:focus{background-color:var(--color-gray-100)}.focus\:bg-gray-200:focus{background-color:var(--color-gray-200)}.focus\:bg-gray-300:focus{background-color:var(--color-gray-300)}.focus\:bg-gray-400:focus{background-color:var(--color-gray-400)}.focus\:bg-gray-500:focus{background-color:var(--color-gray-500)}.focus\:bg-gray-600:focus{background-color:var(--color-gray-600)}.focus\:bg-gray-700:focus{background-color:var(--color-gray-700)}.focus\:bg-gray-800:focus{background-color:var(--color-gray-800)}.focus\:bg-gray-900:focus{background-color:var(--color-gray-900)}.focus\:bg-transparent:focus{background-color:#0000}.focus\:bg-white:focus{background-color:var(--color-white)}.focus\:text-black:focus{color:var(--color-black)}.focus\:text-blue-50:focus{color:var(--color-blue-50)}.focus\:text-blue-100:focus{color:var(--color-blue-100)}.focus\:text-blue-200:focus{color:var(--color-blue-200)}.focus\:text-blue-300:focus{color:var(--color-blue-300)}.focus\:text-blue-400:focus{color:var(--color-blue-400)}.focus\:text-blue-500:focus{color:var(--color-blue-500)}.focus\:text-blue-600:focus{color:var(--color-blue-600)}.focus\:text-blue-700:focus{color:var(--color-blue-700)}.focus\:text-blue-800:focus{color:var(--color-blue-800)}.focus\:text-blue-900:focus{color:var(--color-blue-900)}.focus\:text-gray-50:focus{color:var(--color-gray-50)}.focus\:text-gray-100:focus{color:var(--color-gray-100)}.focus\:text-gray-200:focus{color:var(--color-gray-200)}.focus\:text-gray-300:focus{color:var(--color-gray-300)}.focus\:text-gray-400:focus{color:var(--color-gray-400)}.focus\:text-gray-500:focus{color:var(--color-gray-500)}.focus\:text-gray-600:focus{color:var(--color-gray-600)}.focus\:text-gray-700:focus{color:var(--color-gray-700)}.focus\:text-gray-800:focus{color:var(--color-gray-800)}.focus\:text-gray-900:focus{color:var(--color-gray-900)}.focus\:text-white:focus{color:var(--color-white)}.focus\:shadow:focus{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:shadow-lg:focus{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:shadow-md:focus{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a), 0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:shadow-none:focus{--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:shadow-sm:focus{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:shadow-xl:focus{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a), 0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-0:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-1:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-4:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-8:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(8px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-amber-500:focus{--tw-ring-color:var(--color-amber-500)}.focus\:ring-blue-50:focus{--tw-ring-color:var(--color-blue-50)}.focus\:ring-blue-100:focus{--tw-ring-color:var(--color-blue-100)}.focus\:ring-blue-200:focus{--tw-ring-color:var(--color-blue-200)}.focus\:ring-blue-300:focus{--tw-ring-color:var(--color-blue-300)}.focus\:ring-blue-400:focus{--tw-ring-color:var(--color-blue-400)}.focus\:ring-blue-500:focus{--tw-ring-color:var(--color-blue-500)}.focus\:ring-blue-600:focus{--tw-ring-color:var(--color-blue-600)}.focus\:ring-blue-700:focus{--tw-ring-color:var(--color-blue-700)}.focus\:ring-blue-800:focus{--tw-ring-color:var(--color-blue-800)}.focus\:ring-blue-900:focus{--tw-ring-color:var(--color-blue-900)}.focus\:ring-current:focus{--tw-ring-color:currentcolor}.focus\:ring-gray-50:focus{--tw-ring-color:var(--color-gray-50)}.focus\:ring-gray-100:focus{--tw-ring-color:var(--color-gray-100)}.focus\:ring-gray-200:focus{--tw-ring-color:var(--color-gray-200)}.focus\:ring-gray-300:focus{--tw-ring-color:var(--color-gray-300)}.focus\:ring-gray-400:focus{--tw-ring-color:var(--color-gray-400)}.focus\:ring-gray-500:focus{--tw-ring-color:var(--color-gray-500)}.focus\:ring-gray-600:focus{--tw-ring-color:var(--color-gray-600)}.focus\:ring-gray-700:focus{--tw-ring-color:var(--color-gray-700)}.focus\:ring-gray-800:focus{--tw-ring-color:var(--color-gray-800)}.focus\:ring-gray-900:focus{--tw-ring-color:var(--color-gray-900)}.focus\:ring-green-500:focus{--tw-ring-color:var(--color-green-500)}.focus\:ring-indigo-500:focus{--tw-ring-color:var(--color-indigo-500)}.focus\:ring-purple-500:focus{--tw-ring-color:var(--color-purple-500)}.focus\:ring-red-500:focus{--tw-ring-color:var(--color-red-500)}.focus\:ring-slate-400:focus{--tw-ring-color:var(--color-slate-400)}.focus\:ring-transparent:focus{--tw-ring-color:transparent}.focus\:ring-yellow-500:focus{--tw-ring-color:var(--color-yellow-500)}.focus\:ring-offset-0:focus{--tw-ring-offset-width:0px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.focus\:ring-offset-1:focus{--tw-ring-offset-width:1px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.focus\:ring-offset-2:focus{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.focus\:ring-offset-4:focus{--tw-ring-offset-width:4px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.focus\:ring-offset-gray-100:focus{--tw-ring-offset-color:var(--color-gray-100)}.focus\:ring-offset-gray-900:focus{--tw-ring-offset-color:var(--color-gray-900)}.focus\:ring-offset-white:focus{--tw-ring-offset-color:var(--color-white)}.focus\:outline:focus{outline-style:var(--tw-outline-style);outline-width:1px}.focus\:outline-0:focus{outline-style:var(--tw-outline-style);outline-width:0}.focus\:outline-1:focus{outline-style:var(--tw-outline-style);outline-width:1px}.focus\:outline-2:focus{outline-style:var(--tw-outline-style);outline-width:2px}.focus\:outline-4:focus{outline-style:var(--tw-outline-style);outline-width:4px}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.focus\:ring-inset:focus{--tw-ring-inset:inset}.focus-visible\:ring-2:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus-visible\:ring-blue-500:focus-visible{--tw-ring-color:var(--color-blue-500)}.focus-visible\:ring-gray-500:focus-visible{--tw-ring-color:var(--color-gray-500)}.focus-visible\:ring-offset-2:focus-visible{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.focus-visible\:outline:focus-visible{outline-style:var(--tw-outline-style);outline-width:1px}.focus-visible\:outline-2:focus-visible{outline-style:var(--tw-outline-style);outline-width:2px}.focus-visible\:outline-offset-2:focus-visible{outline-offset:2px}.active\:scale-95:active{--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x) var(--tw-scale-y)}.active\:scale-100:active{--tw-scale-x:100%;--tw-scale-y:100%;--tw-scale-z:100%;scale:var(--tw-scale-x) var(--tw-scale-y)}.active\:cursor-grabbing:active{cursor:grabbing}.active\:bg-blue-600:active{background-color:var(--color-blue-600)}.active\:bg-blue-700:active{background-color:var(--color-blue-700)}.active\:bg-blue-800:active{background-color:var(--color-blue-800)}.active\:bg-gray-100:active{background-color:var(--color-gray-100)}.active\:bg-gray-200:active{background-color:var(--color-gray-200)}.active\:bg-gray-300:active{background-color:var(--color-gray-300)}.active\:bg-gray-700:active{background-color:var(--color-gray-700)}.active\:bg-gray-800:active{background-color:var(--color-gray-800)}.active\:bg-green-600:active{background-color:var(--color-green-600)}.active\:bg-green-700:active{background-color:var(--color-green-700)}.active\:bg-red-600:active{background-color:var(--color-red-600)}.active\:bg-red-700:active{background-color:var(--color-red-700)}.active\:opacity-75:active{opacity:.75}.active\:opacity-100:active{opacity:1}.disabled\:cursor-default:disabled{cursor:default}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:border-gray-200:disabled{border-color:var(--color-gray-200)}.disabled\:border-gray-300:disabled{border-color:var(--color-gray-300)}.disabled\:bg-blue-400:disabled{background-color:var(--color-blue-400)}.disabled\:bg-gray-100:disabled{background-color:var(--color-gray-100)}.disabled\:bg-gray-200:disabled{background-color:var(--color-gray-200)}.disabled\:bg-gray-300:disabled{background-color:var(--color-gray-300)}.disabled\:bg-gray-400:disabled{background-color:var(--color-gray-400)}.disabled\:text-gray-300:disabled{color:var(--color-gray-300)}.disabled\:text-gray-400:disabled{color:var(--color-gray-400)}.disabled\:text-gray-500:disabled{color:var(--color-gray-500)}.disabled\:no-underline:disabled{text-decoration-line:none}.disabled\:opacity-25:disabled{opacity:.25}.disabled\:opacity-50:disabled{opacity:.5}.disabled\:opacity-75:disabled{opacity:.75}@media(hover:hover){.disabled\:hover\:bg-indigo-600:disabled:hover{background-color:var(--color-indigo-600)}.disabled\:hover\:bg-transparent:disabled:hover{background-color:#0000}}@media(min-width:40rem){.sm\:col-span-1{grid-column:span 1/span 1}.sm\:col-span-2{grid-column:span 2/span 2}.sm\:col-span-3{grid-column:span 3/span 3}.sm\:col-span-4{grid-column:span 4/span 4}.sm\:col-span-6{grid-column:span 6/span 6}.sm\:col-span-12{grid-column:span 12/span 12}.sm\:col-span-full{grid-column:1/-1}.sm\:m-0{margin:0}.sm\:m-1{margin:var(--spacing)}.sm\:m-2{margin:calc(var(--spacing) * 2)}.sm\:m-3{margin:calc(var(--spacing) * 3)}.sm\:m-4{margin:calc(var(--spacing) * 4)}.sm\:m-auto{margin:auto}.sm\:mx-auto{margin-inline:auto}.sm\:my-auto{margin-block:auto}.sm\:mt-6{margin-top:calc(var(--spacing) * 6)}.sm\:block{display:block}.sm\:flex{display:flex}.sm\:hidden{display:none}.sm\:inline{display:inline}.sm\:inline-block{display:inline-block}.sm\:inline-flex{display:inline-flex}.sm\:w-1\/2{width:50%}.sm\:w-1\/3{width:33.3333%}.sm\:w-1\/4{width:25%}.sm\:w-2\/3{width:66.6667%}.sm\:w-3\/4{width:75%}.sm\:w-40{width:calc(var(--spacing) * 40)}.sm\:w-auto{width:auto}.sm\:w-full{width:100%}.sm\:grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.sm\:grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.sm\:grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.sm\:flex-col{flex-direction:column}.sm\:flex-row{flex-direction:row}.sm\:items-center{align-items:center}.sm\:items-end{align-items:flex-end}.sm\:items-start{align-items:flex-start}.sm\:justify-between{justify-content:space-between}.sm\:justify-center{justify-content:center}.sm\:justify-end{justify-content:flex-end}.sm\:justify-start{justify-content:flex-start}.sm\:gap-0{gap:0}.sm\:gap-1{gap:var(--spacing)}.sm\:gap-2{gap:calc(var(--spacing) * 2)}.sm\:gap-3{gap:calc(var(--spacing) * 3)}.sm\:gap-4{gap:calc(var(--spacing) * 4)}.sm\:gap-5{gap:calc(var(--spacing) * 5)}.sm\:gap-6{gap:calc(var(--spacing) * 6)}.sm\:gap-8{gap:calc(var(--spacing) * 8)}:where(.sm\:space-y-0>:not(:last-child)){--tw-space-y-reverse:0;margin-block:0}:where(.sm\:space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(var(--spacing) * var(--tw-space-y-reverse));margin-block-end:calc(var(--spacing) * calc(1 - var(--tw-space-y-reverse)))}:where(.sm\:space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)))}:where(.sm\:space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse)))}:where(.sm\:space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse)))}:where(.sm\:space-x-0>:not(:last-child)){--tw-space-x-reverse:0;margin-inline:0}:where(.sm\:space-x-1>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(var(--spacing) * var(--tw-space-x-reverse));margin-inline-end:calc(var(--spacing) * calc(1 - var(--tw-space-x-reverse)))}:where(.sm\:space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 2) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-x-reverse)))}:where(.sm\:space-x-3>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 3) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-x-reverse)))}:where(.sm\:space-x-4>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 4) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-x-reverse)))}.sm\:p-0{padding:0}.sm\:p-1{padding:var(--spacing)}.sm\:p-2{padding:calc(var(--spacing) * 2)}.sm\:p-3{padding:calc(var(--spacing) * 3)}.sm\:p-4{padding:calc(var(--spacing) * 4)}.sm\:p-5{padding:calc(var(--spacing) * 5)}.sm\:p-6{padding:calc(var(--spacing) * 6)}.sm\:p-8{padding:calc(var(--spacing) * 8)}.sm\:px-0{padding-inline:0}.sm\:px-1{padding-inline:var(--spacing)}.sm\:px-2{padding-inline:calc(var(--spacing) * 2)}.sm\:px-3{padding-inline:calc(var(--spacing) * 3)}.sm\:px-4{padding-inline:calc(var(--spacing) * 4)}.sm\:px-5{padding-inline:calc(var(--spacing) * 5)}.sm\:px-6{padding-inline:calc(var(--spacing) * 6)}.sm\:px-8{padding-inline:calc(var(--spacing) * 8)}.sm\:py-0{padding-block:0}.sm\:py-1{padding-block:var(--spacing)}.sm\:py-2{padding-block:calc(var(--spacing) * 2)}.sm\:py-3{padding-block:calc(var(--spacing) * 3)}.sm\:py-4{padding-block:calc(var(--spacing) * 4)}.sm\:py-5{padding-block:calc(var(--spacing) * 5)}.sm\:py-6{padding-block:calc(var(--spacing) * 6)}.sm\:py-8{padding-block:calc(var(--spacing) * 8)}.sm\:text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.sm\:text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.sm\:text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.sm\:text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.sm\:text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.sm\:text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.sm\:text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}}@media(min-width:48rem){.md\:absolute{position:absolute}.md\:fixed{position:fixed}.md\:relative{position:relative}.md\:static{position:static}.md\:sticky{position:sticky}.md\:inset-0{inset:0}.md\:top-0{top:0}.md\:right-0{right:0}.md\:bottom-0{bottom:0}.md\:left-0{left:0}.md\:order-1{order:1}.md\:order-2{order:2}.md\:order-3{order:3}.md\:order-first{order:-9999}.md\:order-last{order:9999}.md\:order-none{order:0}.md\:col-span-1{grid-column:span 1/span 1}.md\:col-span-2{grid-column:span 2/span 2}.md\:col-span-3{grid-column:span 3/span 3}.md\:col-span-4{grid-column:span 4/span 4}.md\:col-span-5{grid-column:span 5/span 5}.md\:col-span-6{grid-column:span 6/span 6}.md\:col-span-12{grid-column:span 12/span 12}.md\:col-span-full{grid-column:1/-1}.md\:row-span-1{grid-row:span 1/span 1}.md\:row-span-2{grid-row:span 2/span 2}.md\:row-span-3{grid-row:span 3/span 3}.md\:row-span-4{grid-row:span 4/span 4}.md\:row-span-full{grid-row:1/-1}.md\:m-0{margin:0}.md\:m-1{margin:var(--spacing)}.md\:m-2{margin:calc(var(--spacing) * 2)}.md\:m-3{margin:calc(var(--spacing) * 3)}.md\:m-4{margin:calc(var(--spacing) * 4)}.md\:m-auto{margin:auto}.md\:mx-auto{margin-inline:auto}.md\:my-auto{margin-block:auto}.md\:mt-0{margin-top:0}.md\:mt-1{margin-top:var(--spacing)}.md\:mt-2{margin-top:calc(var(--spacing) * 2)}.md\:mt-3{margin-top:calc(var(--spacing) * 3)}.md\:mt-4{margin-top:calc(var(--spacing) * 4)}.md\:mt-6{margin-top:calc(var(--spacing) * 6)}.md\:mt-8{margin-top:calc(var(--spacing) * 8)}.md\:mr-0{margin-right:0}.md\:mr-1{margin-right:var(--spacing)}.md\:mr-2{margin-right:calc(var(--spacing) * 2)}.md\:mr-3{margin-right:calc(var(--spacing) * 3)}.md\:mr-4{margin-right:calc(var(--spacing) * 4)}.md\:mr-6{margin-right:calc(var(--spacing) * 6)}.md\:mr-8{margin-right:calc(var(--spacing) * 8)}.md\:mr-auto{margin-right:auto}.md\:mb-0{margin-bottom:0}.md\:mb-1{margin-bottom:var(--spacing)}.md\:mb-2{margin-bottom:calc(var(--spacing) * 2)}.md\:mb-3{margin-bottom:calc(var(--spacing) * 3)}.md\:mb-4{margin-bottom:calc(var(--spacing) * 4)}.md\:mb-6{margin-bottom:calc(var(--spacing) * 6)}.md\:mb-8{margin-bottom:calc(var(--spacing) * 8)}.md\:ml-0{margin-left:0}.md\:ml-1{margin-left:var(--spacing)}.md\:ml-2{margin-left:calc(var(--spacing) * 2)}.md\:ml-3{margin-left:calc(var(--spacing) * 3)}.md\:ml-4{margin-left:calc(var(--spacing) * 4)}.md\:ml-6{margin-left:calc(var(--spacing) * 6)}.md\:ml-8{margin-left:calc(var(--spacing) * 8)}.md\:ml-auto{margin-left:auto}.md\:block{display:block}.md\:flex{display:flex}.md\:hidden{display:none}.md\:inline{display:inline}.md\:inline-block{display:inline-block}.md\:inline-flex{display:inline-flex}.md\:h-20{height:calc(var(--spacing) * 20)}.md\:w-1\/2{width:50%}.md\:w-1\/3{width:33.3333%}.md\:w-1\/4{width:25%}.md\:w-1\/5{width:20%}.md\:w-2\/3{width:66.6667%}.md\:w-2\/5{width:40%}.md\:w-3\/4{width:75%}.md\:w-3\/5{width:60%}.md\:w-4\/5{width:80%}.md\:w-20{width:calc(var(--spacing) * 20)}.md\:w-40{width:calc(var(--spacing) * 40)}.md\:w-48{width:calc(var(--spacing) * 48)}.md\:w-64{width:calc(var(--spacing) * 64)}.md\:w-72{width:calc(var(--spacing) * 72)}.md\:w-80{width:calc(var(--spacing) * 80)}.md\:w-auto{width:auto}.md\:w-full{width:100%}.md\:max-w-2xl{max-width:var(--container-2xl)}.md\:max-w-3xl{max-width:var(--container-3xl)}.md\:max-w-4xl{max-width:var(--container-4xl)}.md\:max-w-5xl{max-width:var(--container-5xl)}.md\:max-w-6xl{max-width:var(--container-6xl)}.md\:max-w-7xl{max-width:var(--container-7xl)}.md\:max-w-full{max-width:100%}.md\:max-w-lg{max-width:var(--container-lg)}.md\:max-w-md{max-width:var(--container-md)}.md\:max-w-none{max-width:none}.md\:max-w-sm{max-width:var(--container-sm)}.md\:max-w-xl{max-width:var(--container-xl)}.md\:max-w-xs{max-width:var(--container-xs)}.md\:flex-1{flex:1}.md\:grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr))}.md\:grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.md\:grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.md\:grid-rows-1{grid-template-rows:repeat(1,minmax(0,1fr))}.md\:grid-rows-2{grid-template-rows:repeat(2,minmax(0,1fr))}.md\:grid-rows-3{grid-template-rows:repeat(3,minmax(0,1fr))}.md\:grid-rows-4{grid-template-rows:repeat(4,minmax(0,1fr))}.md\:grid-rows-5{grid-template-rows:repeat(5,minmax(0,1fr))}.md\:grid-rows-6{grid-template-rows:repeat(6,minmax(0,1fr))}.md\:flex-col{flex-direction:column}.md\:flex-row{flex-direction:row}.md\:items-baseline{align-items:baseline}.md\:items-center{align-items:center}.md\:items-end{align-items:flex-end}.md\:items-start{align-items:flex-start}.md\:items-stretch{align-items:stretch}.md\:justify-around{justify-content:space-around}.md\:justify-between{justify-content:space-between}.md\:justify-center{justify-content:center}.md\:justify-end{justify-content:flex-end}.md\:justify-start{justify-content:flex-start}.md\:gap-0{gap:0}.md\:gap-1{gap:var(--spacing)}.md\:gap-2{gap:calc(var(--spacing) * 2)}.md\:gap-3{gap:calc(var(--spacing) * 3)}.md\:gap-4{gap:calc(var(--spacing) * 4)}.md\:gap-5{gap:calc(var(--spacing) * 5)}.md\:gap-6{gap:calc(var(--spacing) * 6)}.md\:gap-8{gap:calc(var(--spacing) * 8)}.md\:gap-10{gap:calc(var(--spacing) * 10)}.md\:gap-12{gap:calc(var(--spacing) * 12)}:where(.md\:space-y-0>:not(:last-child)){--tw-space-y-reverse:0;margin-block:0}:where(.md\:space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(var(--spacing) * var(--tw-space-y-reverse));margin-block-end:calc(var(--spacing) * calc(1 - var(--tw-space-y-reverse)))}:where(.md\:space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)))}:where(.md\:space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse)))}:where(.md\:space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse)))}:where(.md\:space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 6) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-y-reverse)))}:where(.md\:space-y-8>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 8) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 8) * calc(1 - var(--tw-space-y-reverse)))}:where(.md\:space-x-0>:not(:last-child)){--tw-space-x-reverse:0;margin-inline:0}:where(.md\:space-x-1>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(var(--spacing) * var(--tw-space-x-reverse));margin-inline-end:calc(var(--spacing) * calc(1 - var(--tw-space-x-reverse)))}:where(.md\:space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 2) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-x-reverse)))}:where(.md\:space-x-3>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 3) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-x-reverse)))}:where(.md\:space-x-4>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 4) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-x-reverse)))}:where(.md\:space-x-6>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 6) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-x-reverse)))}:where(.md\:space-x-8>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 8) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 8) * calc(1 - var(--tw-space-x-reverse)))}.md\:rounded-2xl{border-radius:var(--radius-2xl)}.md\:p-0{padding:0}.md\:p-1{padding:var(--spacing)}.md\:p-2{padding:calc(var(--spacing) * 2)}.md\:p-3{padding:calc(var(--spacing) * 3)}.md\:p-4{padding:calc(var(--spacing) * 4)}.md\:p-5{padding:calc(var(--spacing) * 5)}.md\:p-6{padding:calc(var(--spacing) * 6)}.md\:p-8{padding:calc(var(--spacing) * 8)}.md\:p-10{padding:calc(var(--spacing) * 10)}.md\:p-12{padding:calc(var(--spacing) * 12)}.md\:px-0{padding-inline:0}.md\:px-1{padding-inline:var(--spacing)}.md\:px-2{padding-inline:calc(var(--spacing) * 2)}.md\:px-3{padding-inline:calc(var(--spacing) * 3)}.md\:px-4{padding-inline:calc(var(--spacing) * 4)}.md\:px-5{padding-inline:calc(var(--spacing) * 5)}.md\:px-6{padding-inline:calc(var(--spacing) * 6)}.md\:px-8{padding-inline:calc(var(--spacing) * 8)}.md\:px-10{padding-inline:calc(var(--spacing) * 10)}.md\:px-12{padding-inline:calc(var(--spacing) * 12)}.md\:py-0{padding-block:0}.md\:py-1{padding-block:var(--spacing)}.md\:py-2{padding-block:calc(var(--spacing) * 2)}.md\:py-3{padding-block:calc(var(--spacing) * 3)}.md\:py-4{padding-block:calc(var(--spacing) * 4)}.md\:py-5{padding-block:calc(var(--spacing) * 5)}.md\:py-6{padding-block:calc(var(--spacing) * 6)}.md\:py-8{padding-block:calc(var(--spacing) * 8)}.md\:py-10{padding-block:calc(var(--spacing) * 10)}.md\:py-12{padding-block:calc(var(--spacing) * 12)}.md\:text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.md\:text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.md\:text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.md\:text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.md\:text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.md\:text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.md\:text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.md\:text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}}@media(min-width:64rem){.lg\:absolute{position:absolute}.lg\:fixed{position:fixed}.lg\:relative{position:relative}.lg\:static{position:static}.lg\:sticky{position:sticky}.lg\:inset-0{inset:0}.lg\:top-0{top:0}.lg\:right-0{right:0}.lg\:bottom-0{bottom:0}.lg\:left-0{left:0}.lg\:order-1{order:1}.lg\:order-2{order:2}.lg\:order-3{order:3}.lg\:order-4{order:4}.lg\:order-first{order:-9999}.lg\:order-last{order:9999}.lg\:order-none{order:0}.lg\:col-span-1{grid-column:span 1/span 1}.lg\:col-span-2{grid-column:span 2/span 2}.lg\:col-span-3{grid-column:span 3/span 3}.lg\:col-span-4{grid-column:span 4/span 4}.lg\:col-span-5{grid-column:span 5/span 5}.lg\:col-span-6{grid-column:span 6/span 6}.lg\:col-span-7{grid-column:span 7/span 7}.lg\:col-span-8{grid-column:span 8/span 8}.lg\:col-span-12{grid-column:span 12/span 12}.lg\:col-span-full{grid-column:1/-1}.lg\:row-span-1{grid-row:span 1/span 1}.lg\:row-span-2{grid-row:span 2/span 2}.lg\:row-span-3{grid-row:span 3/span 3}.lg\:row-span-4{grid-row:span 4/span 4}.lg\:row-span-full{grid-row:1/-1}.lg\:m-0{margin:0}.lg\:m-1{margin:var(--spacing)}.lg\:m-2{margin:calc(var(--spacing) * 2)}.lg\:m-3{margin:calc(var(--spacing) * 3)}.lg\:m-4{margin:calc(var(--spacing) * 4)}.lg\:m-auto{margin:auto}.lg\:mx-auto{margin-inline:auto}.lg\:my-auto{margin-block:auto}.lg\:mt-0{margin-top:0}.lg\:mt-1{margin-top:var(--spacing)}.lg\:mt-2{margin-top:calc(var(--spacing) * 2)}.lg\:mt-3{margin-top:calc(var(--spacing) * 3)}.lg\:mt-4{margin-top:calc(var(--spacing) * 4)}.lg\:mt-6{margin-top:calc(var(--spacing) * 6)}.lg\:mt-8{margin-top:calc(var(--spacing) * 8)}.lg\:mt-10{margin-top:calc(var(--spacing) * 10)}.lg\:mt-12{margin-top:calc(var(--spacing) * 12)}.lg\:mr-0{margin-right:0}.lg\:mr-1{margin-right:var(--spacing)}.lg\:mr-2{margin-right:calc(var(--spacing) * 2)}.lg\:mr-3{margin-right:calc(var(--spacing) * 3)}.lg\:mr-4{margin-right:calc(var(--spacing) * 4)}.lg\:mr-6{margin-right:calc(var(--spacing) * 6)}.lg\:mr-8{margin-right:calc(var(--spacing) * 8)}.lg\:mr-auto{margin-right:auto}.lg\:mb-0{margin-bottom:0}.lg\:mb-1{margin-bottom:var(--spacing)}.lg\:mb-2{margin-bottom:calc(var(--spacing) * 2)}.lg\:mb-3{margin-bottom:calc(var(--spacing) * 3)}.lg\:mb-4{margin-bottom:calc(var(--spacing) * 4)}.lg\:mb-6{margin-bottom:calc(var(--spacing) * 6)}.lg\:mb-8{margin-bottom:calc(var(--spacing) * 8)}.lg\:mb-10{margin-bottom:calc(var(--spacing) * 10)}.lg\:mb-12{margin-bottom:calc(var(--spacing) * 12)}.lg\:ml-0{margin-left:0}.lg\:ml-1{margin-left:var(--spacing)}.lg\:ml-2{margin-left:calc(var(--spacing) * 2)}.lg\:ml-3{margin-left:calc(var(--spacing) * 3)}.lg\:ml-4{margin-left:calc(var(--spacing) * 4)}.lg\:ml-6{margin-left:calc(var(--spacing) * 6)}.lg\:ml-8{margin-left:calc(var(--spacing) * 8)}.lg\:ml-auto{margin-left:auto}.lg\:block{display:block}.lg\:flex{display:flex}.lg\:hidden{display:none}.lg\:inline{display:inline}.lg\:inline-block{display:inline-block}.lg\:inline-flex{display:inline-flex}.lg\:w-1\/2{width:50%}.lg\:w-1\/3{width:33.3333%}.lg\:w-1\/4{width:25%}.lg\:w-1\/5{width:20%}.lg\:w-1\/6{width:16.6667%}.lg\:w-2\/3{width:66.6667%}.lg\:w-2\/5{width:40%}.lg\:w-3\/4{width:75%}.lg\:w-3\/5{width:60%}.lg\:w-4\/5{width:80%}.lg\:w-5\/6{width:83.3333%}.lg\:w-auto{width:auto}.lg\:w-full{width:100%}.lg\:max-w-2xl{max-width:var(--container-2xl)}.lg\:max-w-3xl{max-width:var(--container-3xl)}.lg\:max-w-4xl{max-width:var(--container-4xl)}.lg\:max-w-5xl{max-width:var(--container-5xl)}.lg\:max-w-6xl{max-width:var(--container-6xl)}.lg\:max-w-7xl{max-width:var(--container-7xl)}.lg\:max-w-full{max-width:100%}.lg\:max-w-lg{max-width:var(--container-lg)}.lg\:max-w-md{max-width:var(--container-md)}.lg\:max-w-none{max-width:none}.lg\:max-w-sm{max-width:var(--container-sm)}.lg\:max-w-xl{max-width:var(--container-xl)}.lg\:max-w-xs{max-width:var(--container-xs)}.lg\:grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.lg\:grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr))}.lg\:grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.lg\:grid-cols-7{grid-template-columns:repeat(7,minmax(0,1fr))}.lg\:grid-cols-8{grid-template-columns:repeat(8,minmax(0,1fr))}.lg\:grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.lg\:grid-rows-1{grid-template-rows:repeat(1,minmax(0,1fr))}.lg\:grid-rows-2{grid-template-rows:repeat(2,minmax(0,1fr))}.lg\:grid-rows-3{grid-template-rows:repeat(3,minmax(0,1fr))}.lg\:grid-rows-4{grid-template-rows:repeat(4,minmax(0,1fr))}.lg\:grid-rows-5{grid-template-rows:repeat(5,minmax(0,1fr))}.lg\:grid-rows-6{grid-template-rows:repeat(6,minmax(0,1fr))}.lg\:flex-col{flex-direction:column}.lg\:flex-row{flex-direction:row}.lg\:items-baseline{align-items:baseline}.lg\:items-center{align-items:center}.lg\:items-end{align-items:flex-end}.lg\:items-start{align-items:flex-start}.lg\:items-stretch{align-items:stretch}.lg\:justify-around{justify-content:space-around}.lg\:justify-between{justify-content:space-between}.lg\:justify-center{justify-content:center}.lg\:justify-end{justify-content:flex-end}.lg\:justify-evenly{justify-content:space-evenly}.lg\:justify-start{justify-content:flex-start}.lg\:gap-0{gap:0}.lg\:gap-1{gap:var(--spacing)}.lg\:gap-2{gap:calc(var(--spacing) * 2)}.lg\:gap-3{gap:calc(var(--spacing) * 3)}.lg\:gap-4{gap:calc(var(--spacing) * 4)}.lg\:gap-5{gap:calc(var(--spacing) * 5)}.lg\:gap-6{gap:calc(var(--spacing) * 6)}.lg\:gap-8{gap:calc(var(--spacing) * 8)}.lg\:gap-10{gap:calc(var(--spacing) * 10)}.lg\:gap-12{gap:calc(var(--spacing) * 12)}.lg\:gap-16{gap:calc(var(--spacing) * 16)}:where(.lg\:space-y-0>:not(:last-child)){--tw-space-y-reverse:0;margin-block:0}:where(.lg\:space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(var(--spacing) * var(--tw-space-y-reverse));margin-block-end:calc(var(--spacing) * calc(1 - var(--tw-space-y-reverse)))}:where(.lg\:space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)))}:where(.lg\:space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse)))}:where(.lg\:space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse)))}:where(.lg\:space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 6) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-y-reverse)))}:where(.lg\:space-y-8>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 8) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 8) * calc(1 - var(--tw-space-y-reverse)))}:where(.lg\:space-y-10>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 10) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 10) * calc(1 - var(--tw-space-y-reverse)))}:where(.lg\:space-x-0>:not(:last-child)){--tw-space-x-reverse:0;margin-inline:0}:where(.lg\:space-x-1>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(var(--spacing) * var(--tw-space-x-reverse));margin-inline-end:calc(var(--spacing) * calc(1 - var(--tw-space-x-reverse)))}:where(.lg\:space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 2) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-x-reverse)))}:where(.lg\:space-x-3>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 3) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-x-reverse)))}:where(.lg\:space-x-4>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 4) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-x-reverse)))}:where(.lg\:space-x-6>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 6) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-x-reverse)))}:where(.lg\:space-x-8>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 8) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 8) * calc(1 - var(--tw-space-x-reverse)))}:where(.lg\:space-x-10>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 10) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 10) * calc(1 - var(--tw-space-x-reverse)))}.lg\:p-0{padding:0}.lg\:p-1{padding:var(--spacing)}.lg\:p-2{padding:calc(var(--spacing) * 2)}.lg\:p-3{padding:calc(var(--spacing) * 3)}.lg\:p-4{padding:calc(var(--spacing) * 4)}.lg\:p-5{padding:calc(var(--spacing) * 5)}.lg\:p-6{padding:calc(var(--spacing) * 6)}.lg\:p-8{padding:calc(var(--spacing) * 8)}.lg\:p-10{padding:calc(var(--spacing) * 10)}.lg\:p-12{padding:calc(var(--spacing) * 12)}.lg\:p-16{padding:calc(var(--spacing) * 16)}.lg\:px-0{padding-inline:0}.lg\:px-1{padding-inline:var(--spacing)}.lg\:px-2{padding-inline:calc(var(--spacing) * 2)}.lg\:px-3{padding-inline:calc(var(--spacing) * 3)}.lg\:px-4{padding-inline:calc(var(--spacing) * 4)}.lg\:px-5{padding-inline:calc(var(--spacing) * 5)}.lg\:px-6{padding-inline:calc(var(--spacing) * 6)}.lg\:px-8{padding-inline:calc(var(--spacing) * 8)}.lg\:px-10{padding-inline:calc(var(--spacing) * 10)}.lg\:px-12{padding-inline:calc(var(--spacing) * 12)}.lg\:px-16{padding-inline:calc(var(--spacing) * 16)}.lg\:py-0{padding-block:0}.lg\:py-1{padding-block:var(--spacing)}.lg\:py-2{padding-block:calc(var(--spacing) * 2)}.lg\:py-3{padding-block:calc(var(--spacing) * 3)}.lg\:py-4{padding-block:calc(var(--spacing) * 4)}.lg\:py-5{padding-block:calc(var(--spacing) * 5)}.lg\:py-6{padding-block:calc(var(--spacing) * 6)}.lg\:py-8{padding-block:calc(var(--spacing) * 8)}.lg\:py-10{padding-block:calc(var(--spacing) * 10)}.lg\:py-12{padding-block:calc(var(--spacing) * 12)}.lg\:py-16{padding-block:calc(var(--spacing) * 16)}.lg\:text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.lg\:text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.lg\:text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.lg\:text-5xl{font-size:var(--text-5xl);line-height:var(--tw-leading,var(--text-5xl--line-height))}.lg\:text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.lg\:text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.lg\:text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.lg\:text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.lg\:text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}}@media(min-width:80rem){.xl\:col-span-1{grid-column:span 1/span 1}.xl\:col-span-2{grid-column:span 2/span 2}.xl\:col-span-3{grid-column:span 3/span 3}.xl\:col-span-4{grid-column:span 4/span 4}.xl\:col-span-5{grid-column:span 5/span 5}.xl\:col-span-6{grid-column:span 6/span 6}.xl\:col-span-12{grid-column:span 12/span 12}.xl\:col-span-full{grid-column:1/-1}.xl\:row-span-1{grid-row:span 1/span 1}.xl\:row-span-2{grid-row:span 2/span 2}.xl\:row-span-3{grid-row:span 3/span 3}.xl\:row-span-full{grid-row:1/-1}.xl\:m-0{margin:0}.xl\:m-auto{margin:auto}.xl\:mx-auto{margin-inline:auto}.xl\:my-auto{margin-block:auto}.xl\:block{display:block}.xl\:flex{display:flex}.xl\:hidden{display:none}.xl\:inline{display:inline}.xl\:inline-block{display:inline-block}.xl\:inline-flex{display:inline-flex}.xl\:w-1\/2{width:50%}.xl\:w-1\/3{width:33.3333%}.xl\:w-1\/4{width:25%}.xl\:w-1\/5{width:20%}.xl\:w-2\/3{width:66.6667%}.xl\:w-2\/5{width:40%}.xl\:w-3\/4{width:75%}.xl\:w-3\/5{width:60%}.xl\:w-4\/5{width:80%}.xl\:w-auto{width:auto}.xl\:w-full{width:100%}.xl\:max-w-2xl{max-width:var(--container-2xl)}.xl\:max-w-3xl{max-width:var(--container-3xl)}.xl\:max-w-4xl{max-width:var(--container-4xl)}.xl\:max-w-5xl{max-width:var(--container-5xl)}.xl\:max-w-6xl{max-width:var(--container-6xl)}.xl\:max-w-7xl{max-width:var(--container-7xl)}.xl\:max-w-full{max-width:100%}.xl\:max-w-lg{max-width:var(--container-lg)}.xl\:max-w-md{max-width:var(--container-md)}.xl\:max-w-none{max-width:none}.xl\:max-w-sm{max-width:var(--container-sm)}.xl\:max-w-xl{max-width:var(--container-xl)}.xl\:max-w-xs{max-width:var(--container-xs)}.xl\:grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.xl\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.xl\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.xl\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.xl\:grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr))}.xl\:grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.xl\:grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.xl\:grid-rows-1{grid-template-rows:repeat(1,minmax(0,1fr))}.xl\:grid-rows-2{grid-template-rows:repeat(2,minmax(0,1fr))}.xl\:grid-rows-3{grid-template-rows:repeat(3,minmax(0,1fr))}.xl\:grid-rows-4{grid-template-rows:repeat(4,minmax(0,1fr))}.xl\:flex-col{flex-direction:column}.xl\:flex-row{flex-direction:row}.xl\:items-center{align-items:center}.xl\:items-end{align-items:flex-end}.xl\:items-start{align-items:flex-start}.xl\:justify-between{justify-content:space-between}.xl\:justify-center{justify-content:center}.xl\:justify-end{justify-content:flex-end}.xl\:justify-start{justify-content:flex-start}.xl\:gap-0{gap:0}.xl\:gap-1{gap:var(--spacing)}.xl\:gap-2{gap:calc(var(--spacing) * 2)}.xl\:gap-3{gap:calc(var(--spacing) * 3)}.xl\:gap-4{gap:calc(var(--spacing) * 4)}.xl\:gap-5{gap:calc(var(--spacing) * 5)}.xl\:gap-6{gap:calc(var(--spacing) * 6)}.xl\:gap-8{gap:calc(var(--spacing) * 8)}.xl\:gap-10{gap:calc(var(--spacing) * 10)}.xl\:gap-12{gap:calc(var(--spacing) * 12)}.xl\:gap-16{gap:calc(var(--spacing) * 16)}:where(.xl\:space-y-0>:not(:last-child)){--tw-space-y-reverse:0;margin-block:0}:where(.xl\:space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(var(--spacing) * var(--tw-space-y-reverse));margin-block-end:calc(var(--spacing) * calc(1 - var(--tw-space-y-reverse)))}:where(.xl\:space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)))}:where(.xl\:space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse)))}:where(.xl\:space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse)))}:where(.xl\:space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 6) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-y-reverse)))}:where(.xl\:space-y-8>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 8) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 8) * calc(1 - var(--tw-space-y-reverse)))}:where(.xl\:space-x-0>:not(:last-child)){--tw-space-x-reverse:0;margin-inline:0}:where(.xl\:space-x-1>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(var(--spacing) * var(--tw-space-x-reverse));margin-inline-end:calc(var(--spacing) * calc(1 - var(--tw-space-x-reverse)))}:where(.xl\:space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 2) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-x-reverse)))}:where(.xl\:space-x-3>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 3) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-x-reverse)))}:where(.xl\:space-x-4>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 4) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-x-reverse)))}:where(.xl\:space-x-6>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 6) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-x-reverse)))}:where(.xl\:space-x-8>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 8) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 8) * calc(1 - var(--tw-space-x-reverse)))}.xl\:p-0{padding:0}.xl\:p-1{padding:var(--spacing)}.xl\:p-2{padding:calc(var(--spacing) * 2)}.xl\:p-3{padding:calc(var(--spacing) * 3)}.xl\:p-4{padding:calc(var(--spacing) * 4)}.xl\:p-5{padding:calc(var(--spacing) * 5)}.xl\:p-6{padding:calc(var(--spacing) * 6)}.xl\:p-8{padding:calc(var(--spacing) * 8)}.xl\:p-10{padding:calc(var(--spacing) * 10)}.xl\:p-12{padding:calc(var(--spacing) * 12)}.xl\:p-16{padding:calc(var(--spacing) * 16)}.xl\:px-0{padding-inline:0}.xl\:px-1{padding-inline:var(--spacing)}.xl\:px-2{padding-inline:calc(var(--spacing) * 2)}.xl\:px-3{padding-inline:calc(var(--spacing) * 3)}.xl\:px-4{padding-inline:calc(var(--spacing) * 4)}.xl\:px-5{padding-inline:calc(var(--spacing) * 5)}.xl\:px-6{padding-inline:calc(var(--spacing) * 6)}.xl\:px-8{padding-inline:calc(var(--spacing) * 8)}.xl\:px-10{padding-inline:calc(var(--spacing) * 10)}.xl\:px-12{padding-inline:calc(var(--spacing) * 12)}.xl\:px-16{padding-inline:calc(var(--spacing) * 16)}.xl\:py-0{padding-block:0}.xl\:py-1{padding-block:var(--spacing)}.xl\:py-2{padding-block:calc(var(--spacing) * 2)}.xl\:py-3{padding-block:calc(var(--spacing) * 3)}.xl\:py-4{padding-block:calc(var(--spacing) * 4)}.xl\:py-5{padding-block:calc(var(--spacing) * 5)}.xl\:py-6{padding-block:calc(var(--spacing) * 6)}.xl\:py-8{padding-block:calc(var(--spacing) * 8)}.xl\:py-10{padding-block:calc(var(--spacing) * 10)}.xl\:py-12{padding-block:calc(var(--spacing) * 12)}.xl\:py-16{padding-block:calc(var(--spacing) * 16)}.xl\:text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.xl\:text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.xl\:text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.xl\:text-5xl{font-size:var(--text-5xl);line-height:var(--tw-leading,var(--text-5xl--line-height))}.xl\:text-6xl{font-size:var(--text-6xl);line-height:var(--tw-leading,var(--text-6xl--line-height))}.xl\:text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.xl\:text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.xl\:text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.xl\:text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.xl\:text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}}@media(min-width:96rem){.\32xl\:col-span-1{grid-column:span 1/span 1}.\32xl\:col-span-2{grid-column:span 2/span 2}.\32xl\:col-span-3{grid-column:span 3/span 3}.\32xl\:col-span-4{grid-column:span 4/span 4}.\32xl\:col-span-6{grid-column:span 6/span 6}.\32xl\:col-span-12{grid-column:span 12/span 12}.\32xl\:block{display:block}.\32xl\:flex{display:flex}.\32xl\:hidden{display:none}.\32xl\:w-1\/2{width:50%}.\32xl\:w-1\/3{width:33.3333%}.\32xl\:w-1\/4{width:25%}.\32xl\:w-2\/3{width:66.6667%}.\32xl\:w-3\/4{width:75%}.\32xl\:w-auto{width:auto}.\32xl\:w-full{width:100%}.\32xl\:max-w-2xl{max-width:var(--container-2xl)}.\32xl\:max-w-3xl{max-width:var(--container-3xl)}.\32xl\:max-w-4xl{max-width:var(--container-4xl)}.\32xl\:max-w-5xl{max-width:var(--container-5xl)}.\32xl\:max-w-6xl{max-width:var(--container-6xl)}.\32xl\:max-w-7xl{max-width:var(--container-7xl)}.\32xl\:max-w-full{max-width:100%}.\32xl\:max-w-lg{max-width:var(--container-lg)}.\32xl\:max-w-md{max-width:var(--container-md)}.\32xl\:max-w-sm{max-width:var(--container-sm)}.\32xl\:max-w-xl{max-width:var(--container-xl)}.\32xl\:max-w-xs{max-width:var(--container-xs)}.\32xl\:grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.\32xl\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.\32xl\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.\32xl\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.\32xl\:grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr))}.\32xl\:grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.\32xl\:grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.\32xl\:gap-0{gap:0}.\32xl\:gap-4{gap:calc(var(--spacing) * 4)}.\32xl\:gap-6{gap:calc(var(--spacing) * 6)}.\32xl\:gap-8{gap:calc(var(--spacing) * 8)}.\32xl\:gap-12{gap:calc(var(--spacing) * 12)}.\32xl\:gap-16{gap:calc(var(--spacing) * 16)}.\32xl\:p-0{padding:0}.\32xl\:p-4{padding:calc(var(--spacing) * 4)}.\32xl\:p-6{padding:calc(var(--spacing) * 6)}.\32xl\:p-8{padding:calc(var(--spacing) * 8)}.\32xl\:p-12{padding:calc(var(--spacing) * 12)}.\32xl\:p-16{padding:calc(var(--spacing) * 16)}.\32xl\:px-0{padding-inline:0}.\32xl\:px-4{padding-inline:calc(var(--spacing) * 4)}.\32xl\:px-6{padding-inline:calc(var(--spacing) * 6)}.\32xl\:px-8{padding-inline:calc(var(--spacing) * 8)}.\32xl\:px-12{padding-inline:calc(var(--spacing) * 12)}.\32xl\:px-16{padding-inline:calc(var(--spacing) * 16)}.\32xl\:py-0{padding-block:0}.\32xl\:py-4{padding-block:calc(var(--spacing) * 4)}.\32xl\:py-6{padding-block:calc(var(--spacing) * 6)}.\32xl\:py-8{padding-block:calc(var(--spacing) * 8)}.\32xl\:py-12{padding-block:calc(var(--spacing) * 12)}.\32xl\:py-16{padding-block:calc(var(--spacing) * 16)}.\32xl\:text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.\32xl\:text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.\32xl\:text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.\32xl\:text-5xl{font-size:var(--text-5xl);line-height:var(--tw-leading,var(--text-5xl--line-height))}.\32xl\:text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.\32xl\:text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.\32xl\:text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.\32xl\:text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.\32xl\:text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}}.dark .dark\:block{display:block}.dark .dark\:flex{display:flex}.dark .dark\:flex-1{flex:1}.dark .dark\:flex-auto{flex:auto}.dark .dark\:flex-none{flex:none}.dark .dark\:flex-col{flex-direction:column}.dark .dark\:flex-row{flex-direction:row}.dark .dark\:flex-wrap{flex-wrap:wrap}.dark .dark\:items-center{align-items:center}.dark .dark\:items-end{align-items:flex-end}.dark .dark\:items-start{align-items:flex-start}.dark .dark\:items-stretch{align-items:stretch}.dark .dark\:justify-around{justify-content:space-around}.dark .dark\:justify-between{justify-content:space-between}.dark .dark\:justify-center{justify-content:center}.dark .dark\:justify-end{justify-content:flex-end}.dark .dark\:justify-start{justify-content:flex-start}:where(.dark .dark\:divide-gray-50>:not(:last-child)){border-color:var(--color-gray-50)}:where(.dark .dark\:divide-gray-100>:not(:last-child)){border-color:var(--color-gray-100)}:where(.dark .dark\:divide-gray-200>:not(:last-child)){border-color:var(--color-gray-200)}:where(.dark .dark\:divide-gray-300>:not(:last-child)){border-color:var(--color-gray-300)}:where(.dark .dark\:divide-gray-400>:not(:last-child)){border-color:var(--color-gray-400)}:where(.dark .dark\:divide-gray-500>:not(:last-child)){border-color:var(--color-gray-500)}:where(.dark .dark\:divide-gray-600>:not(:last-child)){border-color:var(--color-gray-600)}:where(.dark .dark\:divide-gray-700>:not(:last-child)){border-color:var(--color-gray-700)}:where(.dark .dark\:divide-gray-800>:not(:last-child)){border-color:var(--color-gray-800)}:where(.dark .dark\:divide-gray-900>:not(:last-child)){border-color:var(--color-gray-900)}:where(.dark .dark\:divide-purple-800>:not(:last-child)){border-color:var(--color-purple-800)}:where(.dark .dark\:divide-slate-700>:not(:last-child)){border-color:var(--color-slate-700)}.dark .dark\:self-center{align-self:center}.dark .dark\:self-end{align-self:flex-end}.dark .dark\:self-start{align-self:flex-start}.dark .dark\:self-stretch{align-self:stretch}.dark .dark\:overflow-auto{overflow:auto}.dark .dark\:overflow-hidden{overflow:hidden}.dark .dark\:overflow-visible{overflow:visible}.dark .dark\:overflow-x-auto{overflow-x:auto}.dark .dark\:overflow-y-auto{overflow-y:auto}.dark .dark\:rounded{border-radius:.25rem}.dark .dark\:rounded-full{border-radius:3.40282e38px}.dark .dark\:rounded-lg{border-radius:var(--radius-lg)}.dark .dark\:rounded-none{border-radius:0}.dark .dark\:rounded-sm{border-radius:var(--radius-sm)}.dark .dark\:rounded-xl{border-radius:var(--radius-xl)}.dark .dark\:border{border-style:var(--tw-border-style);border-width:1px}.dark .dark\:border-0{border-style:var(--tw-border-style);border-width:0}.dark .dark\:border-dashed{--tw-border-style:dashed;border-style:dashed}.dark .dark\:border-dotted{--tw-border-style:dotted;border-style:dotted}.dark .dark\:border-solid{--tw-border-style:solid;border-style:solid}.dark .dark\:border-amber-50{border-color:var(--color-amber-50)}.dark .dark\:border-amber-100{border-color:var(--color-amber-100)}.dark .dark\:border-amber-200{border-color:var(--color-amber-200)}.dark .dark\:border-amber-300{border-color:var(--color-amber-300)}.dark .dark\:border-amber-400{border-color:var(--color-amber-400)}.dark .dark\:border-amber-500{border-color:var(--color-amber-500)}.dark .dark\:border-amber-600{border-color:var(--color-amber-600)}.dark .dark\:border-amber-700{border-color:var(--color-amber-700)}.dark .dark\:border-amber-800{border-color:var(--color-amber-800)}.dark .dark\:border-amber-900{border-color:var(--color-amber-900)}.dark .dark\:border-black{border-color:var(--color-black)}.dark .dark\:border-blue-50{border-color:var(--color-blue-50)}.dark .dark\:border-blue-100{border-color:var(--color-blue-100)}.dark .dark\:border-blue-200{border-color:var(--color-blue-200)}.dark .dark\:border-blue-300{border-color:var(--color-blue-300)}.dark .dark\:border-blue-400{border-color:var(--color-blue-400)}.dark .dark\:border-blue-500{border-color:var(--color-blue-500)}.dark .dark\:border-blue-600{border-color:var(--color-blue-600)}.dark .dark\:border-blue-700{border-color:var(--color-blue-700)}.dark .dark\:border-blue-800{border-color:var(--color-blue-800)}.dark .dark\:border-blue-900{border-color:var(--color-blue-900)}.dark .dark\:border-gray-50{border-color:var(--color-gray-50)}.dark .dark\:border-gray-100{border-color:var(--color-gray-100)}.dark .dark\:border-gray-200{border-color:var(--color-gray-200)}.dark .dark\:border-gray-300{border-color:var(--color-gray-300)}.dark .dark\:border-gray-400{border-color:var(--color-gray-400)}.dark .dark\:border-gray-500{border-color:var(--color-gray-500)}.dark .dark\:border-gray-600{border-color:var(--color-gray-600)}.dark .dark\:border-gray-700{border-color:var(--color-gray-700)}.dark .dark\:border-gray-800{border-color:var(--color-gray-800)}.dark .dark\:border-gray-900{border-color:var(--color-gray-900)}.dark .dark\:border-gray-950{border-color:var(--color-gray-950)}.dark .dark\:border-green-50{border-color:var(--color-green-50)}.dark .dark\:border-green-100{border-color:var(--color-green-100)}.dark .dark\:border-green-200{border-color:var(--color-green-200)}.dark .dark\:border-green-300{border-color:var(--color-green-300)}.dark .dark\:border-green-400{border-color:var(--color-green-400)}.dark .dark\:border-green-500{border-color:var(--color-green-500)}.dark .dark\:border-green-600{border-color:var(--color-green-600)}.dark .dark\:border-green-700{border-color:var(--color-green-700)}.dark .dark\:border-green-800{border-color:var(--color-green-800)}.dark .dark\:border-green-900{border-color:var(--color-green-900)}.dark .dark\:border-indigo-400{border-color:var(--color-indigo-400)}.dark .dark\:border-orange-50{border-color:var(--color-orange-50)}.dark .dark\:border-orange-100{border-color:var(--color-orange-100)}.dark .dark\:border-orange-200{border-color:var(--color-orange-200)}.dark .dark\:border-orange-300{border-color:var(--color-orange-300)}.dark .dark\:border-orange-400{border-color:var(--color-orange-400)}.dark .dark\:border-orange-500{border-color:var(--color-orange-500)}.dark .dark\:border-orange-600{border-color:var(--color-orange-600)}.dark .dark\:border-orange-700{border-color:var(--color-orange-700)}.dark .dark\:border-orange-800{border-color:var(--color-orange-800)}.dark .dark\:border-orange-900{border-color:var(--color-orange-900)}.dark .dark\:border-pink-50{border-color:var(--color-pink-50)}.dark .dark\:border-pink-100{border-color:var(--color-pink-100)}.dark .dark\:border-pink-200{border-color:var(--color-pink-200)}.dark .dark\:border-pink-300{border-color:var(--color-pink-300)}.dark .dark\:border-pink-400{border-color:var(--color-pink-400)}.dark .dark\:border-pink-500{border-color:var(--color-pink-500)}.dark .dark\:border-pink-600{border-color:var(--color-pink-600)}.dark .dark\:border-pink-700{border-color:var(--color-pink-700)}.dark .dark\:border-pink-800{border-color:var(--color-pink-800)}.dark .dark\:border-pink-900{border-color:var(--color-pink-900)}.dark .dark\:border-purple-50{border-color:var(--color-purple-50)}.dark .dark\:border-purple-100{border-color:var(--color-purple-100)}.dark .dark\:border-purple-200{border-color:var(--color-purple-200)}.dark .dark\:border-purple-300{border-color:var(--color-purple-300)}.dark .dark\:border-purple-400{border-color:var(--color-purple-400)}.dark .dark\:border-purple-500{border-color:var(--color-purple-500)}.dark .dark\:border-purple-600{border-color:var(--color-purple-600)}.dark .dark\:border-purple-700{border-color:var(--color-purple-700)}.dark .dark\:border-purple-800{border-color:var(--color-purple-800)}.dark .dark\:border-purple-900{border-color:var(--color-purple-900)}.dark .dark\:border-red-50{border-color:var(--color-red-50)}.dark .dark\:border-red-100{border-color:var(--color-red-100)}.dark .dark\:border-red-200{border-color:var(--color-red-200)}.dark .dark\:border-red-300{border-color:var(--color-red-300)}.dark .dark\:border-red-400{border-color:var(--color-red-400)}.dark .dark\:border-red-500{border-color:var(--color-red-500)}.dark .dark\:border-red-600{border-color:var(--color-red-600)}.dark .dark\:border-red-700{border-color:var(--color-red-700)}.dark .dark\:border-red-800{border-color:var(--color-red-800)}.dark .dark\:border-red-900{border-color:var(--color-red-900)}.dark .dark\:border-slate-50{border-color:var(--color-slate-50)}.dark .dark\:border-slate-100{border-color:var(--color-slate-100)}.dark .dark\:border-slate-200{border-color:var(--color-slate-200)}.dark .dark\:border-slate-300{border-color:var(--color-slate-300)}.dark .dark\:border-slate-400{border-color:var(--color-slate-400)}.dark .dark\:border-slate-500{border-color:var(--color-slate-500)}.dark .dark\:border-slate-600{border-color:var(--color-slate-600)}.dark .dark\:border-slate-700{border-color:var(--color-slate-700)}.dark .dark\:border-slate-800{border-color:var(--color-slate-800)}.dark .dark\:border-slate-900{border-color:var(--color-slate-900)}.dark .dark\:border-slate-950{border-color:var(--color-slate-950)}.dark .dark\:border-transparent{border-color:#0000}.dark .dark\:border-white{border-color:var(--color-white)}.dark .dark\:border-yellow-50{border-color:var(--color-yellow-50)}.dark .dark\:border-yellow-100{border-color:var(--color-yellow-100)}.dark .dark\:border-yellow-200{border-color:var(--color-yellow-200)}.dark .dark\:border-yellow-300{border-color:var(--color-yellow-300)}.dark .dark\:border-yellow-400{border-color:var(--color-yellow-400)}.dark .dark\:border-yellow-500{border-color:var(--color-yellow-500)}.dark .dark\:border-yellow-600{border-color:var(--color-yellow-600)}.dark .dark\:border-yellow-700{border-color:var(--color-yellow-700)}.dark .dark\:border-yellow-800{border-color:var(--color-yellow-800)}.dark .dark\:border-yellow-900{border-color:var(--color-yellow-900)}.dark .dark\:bg-amber-50{background-color:var(--color-amber-50)}.dark .dark\:bg-amber-100{background-color:var(--color-amber-100)}.dark .dark\:bg-amber-200{background-color:var(--color-amber-200)}.dark .dark\:bg-amber-300{background-color:var(--color-amber-300)}.dark .dark\:bg-amber-400{background-color:var(--color-amber-400)}.dark .dark\:bg-amber-500{background-color:var(--color-amber-500)}.dark .dark\:bg-amber-600{background-color:var(--color-amber-600)}.dark .dark\:bg-amber-700{background-color:var(--color-amber-700)}.dark .dark\:bg-amber-800{background-color:var(--color-amber-800)}.dark .dark\:bg-amber-900{background-color:var(--color-amber-900)}.dark .dark\:bg-amber-900\/20{background-color:#7b330633}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-amber-900\/20{background-color:color-mix(in oklab,var(--color-amber-900) 20%,transparent)}}.dark .dark\:bg-amber-900\/30{background-color:#7b33064d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-amber-900\/30{background-color:color-mix(in oklab,var(--color-amber-900) 30%,transparent)}}.dark .dark\:bg-amber-900\/40{background-color:#7b330666}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-amber-900\/40{background-color:color-mix(in oklab,var(--color-amber-900) 40%,transparent)}}.dark .dark\:bg-amber-950{background-color:var(--color-amber-950)}.dark .dark\:bg-black{background-color:var(--color-black)}.dark .dark\:bg-blue-50{background-color:var(--color-blue-50)}.dark .dark\:bg-blue-100{background-color:var(--color-blue-100)}.dark .dark\:bg-blue-200{background-color:var(--color-blue-200)}.dark .dark\:bg-blue-300{background-color:var(--color-blue-300)}.dark .dark\:bg-blue-400{background-color:var(--color-blue-400)}.dark .dark\:bg-blue-500{background-color:var(--color-blue-500)}.dark .dark\:bg-blue-600{background-color:var(--color-blue-600)}.dark .dark\:bg-blue-700{background-color:var(--color-blue-700)}.dark .dark\:bg-blue-800{background-color:var(--color-blue-800)}.dark .dark\:bg-blue-900{background-color:var(--color-blue-900)}.dark .dark\:bg-blue-900\/20{background-color:#1c398e33}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-blue-900\/20{background-color:color-mix(in oklab,var(--color-blue-900) 20%,transparent)}}.dark .dark\:bg-blue-900\/30{background-color:#1c398e4d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-blue-900\/30{background-color:color-mix(in oklab,var(--color-blue-900) 30%,transparent)}}.dark .dark\:bg-blue-900\/40{background-color:#1c398e66}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-blue-900\/40{background-color:color-mix(in oklab,var(--color-blue-900) 40%,transparent)}}.dark .dark\:bg-blue-900\/50{background-color:#1c398e80}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-blue-900\/50{background-color:color-mix(in oklab,var(--color-blue-900) 50%,transparent)}}.dark .dark\:bg-blue-950{background-color:var(--color-blue-950)}.dark .dark\:bg-cyan-50{background-color:var(--color-cyan-50)}.dark .dark\:bg-cyan-100{background-color:var(--color-cyan-100)}.dark .dark\:bg-cyan-200{background-color:var(--color-cyan-200)}.dark .dark\:bg-cyan-300{background-color:var(--color-cyan-300)}.dark .dark\:bg-cyan-400{background-color:var(--color-cyan-400)}.dark .dark\:bg-cyan-500{background-color:var(--color-cyan-500)}.dark .dark\:bg-cyan-600{background-color:var(--color-cyan-600)}.dark .dark\:bg-cyan-700{background-color:var(--color-cyan-700)}.dark .dark\:bg-cyan-800{background-color:var(--color-cyan-800)}.dark .dark\:bg-cyan-900{background-color:var(--color-cyan-900)}.dark .dark\:bg-cyan-900\/30{background-color:#104e644d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-cyan-900\/30{background-color:color-mix(in oklab,var(--color-cyan-900) 30%,transparent)}}.dark .dark\:bg-cyan-950{background-color:var(--color-cyan-950)}.dark .dark\:bg-emerald-50{background-color:var(--color-emerald-50)}.dark .dark\:bg-emerald-100{background-color:var(--color-emerald-100)}.dark .dark\:bg-emerald-200{background-color:var(--color-emerald-200)}.dark .dark\:bg-emerald-300{background-color:var(--color-emerald-300)}.dark .dark\:bg-emerald-400{background-color:var(--color-emerald-400)}.dark .dark\:bg-emerald-500{background-color:var(--color-emerald-500)}.dark .dark\:bg-emerald-600{background-color:var(--color-emerald-600)}.dark .dark\:bg-emerald-700{background-color:var(--color-emerald-700)}.dark .dark\:bg-emerald-800{background-color:var(--color-emerald-800)}.dark .dark\:bg-emerald-900{background-color:var(--color-emerald-900)}.dark .dark\:bg-emerald-950{background-color:var(--color-emerald-950)}.dark .dark\:bg-gray-50{background-color:var(--color-gray-50)}.dark .dark\:bg-gray-100{background-color:var(--color-gray-100)}.dark .dark\:bg-gray-200{background-color:var(--color-gray-200)}.dark .dark\:bg-gray-300{background-color:var(--color-gray-300)}.dark .dark\:bg-gray-400{background-color:var(--color-gray-400)}.dark .dark\:bg-gray-500{background-color:var(--color-gray-500)}.dark .dark\:bg-gray-600{background-color:var(--color-gray-600)}.dark .dark\:bg-gray-700{background-color:var(--color-gray-700)}.dark .dark\:bg-gray-700\/30{background-color:#3641534d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-gray-700\/30{background-color:color-mix(in oklab,var(--color-gray-700) 30%,transparent)}}.dark .dark\:bg-gray-700\/50{background-color:#36415380}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-gray-700\/50{background-color:color-mix(in oklab,var(--color-gray-700) 50%,transparent)}}.dark .dark\:bg-gray-800{background-color:var(--color-gray-800)}.dark .dark\:bg-gray-800\/50{background-color:#1e293980}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-gray-800\/50{background-color:color-mix(in oklab,var(--color-gray-800) 50%,transparent)}}.dark .dark\:bg-gray-800\/90{background-color:#1e2939e6}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-gray-800\/90{background-color:color-mix(in oklab,var(--color-gray-800) 90%,transparent)}}.dark .dark\:bg-gray-900{background-color:var(--color-gray-900)}.dark .dark\:bg-gray-900\/50{background-color:#10182880}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-gray-900\/50{background-color:color-mix(in oklab,var(--color-gray-900) 50%,transparent)}}.dark .dark\:bg-gray-950{background-color:var(--color-gray-950)}.dark .dark\:bg-green-50{background-color:var(--color-green-50)}.dark .dark\:bg-green-100{background-color:var(--color-green-100)}.dark .dark\:bg-green-200{background-color:var(--color-green-200)}.dark .dark\:bg-green-300{background-color:var(--color-green-300)}.dark .dark\:bg-green-400{background-color:var(--color-green-400)}.dark .dark\:bg-green-500{background-color:var(--color-green-500)}.dark .dark\:bg-green-600{background-color:var(--color-green-600)}.dark .dark\:bg-green-700{background-color:var(--color-green-700)}.dark .dark\:bg-green-800{background-color:var(--color-green-800)}.dark .dark\:bg-green-900{background-color:var(--color-green-900)}.dark .dark\:bg-green-900\/20{background-color:#0d542b33}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-green-900\/20{background-color:color-mix(in oklab,var(--color-green-900) 20%,transparent)}}.dark .dark\:bg-green-900\/30{background-color:#0d542b4d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-green-900\/30{background-color:color-mix(in oklab,var(--color-green-900) 30%,transparent)}}.dark .dark\:bg-green-950{background-color:var(--color-green-950)}.dark .dark\:bg-indigo-50{background-color:var(--color-indigo-50)}.dark .dark\:bg-indigo-100{background-color:var(--color-indigo-100)}.dark .dark\:bg-indigo-200{background-color:var(--color-indigo-200)}.dark .dark\:bg-indigo-300{background-color:var(--color-indigo-300)}.dark .dark\:bg-indigo-400{background-color:var(--color-indigo-400)}.dark .dark\:bg-indigo-500{background-color:var(--color-indigo-500)}.dark .dark\:bg-indigo-600{background-color:var(--color-indigo-600)}.dark .dark\:bg-indigo-700{background-color:var(--color-indigo-700)}.dark .dark\:bg-indigo-800{background-color:var(--color-indigo-800)}.dark .dark\:bg-indigo-900{background-color:var(--color-indigo-900)}.dark .dark\:bg-indigo-900\/30{background-color:#312c854d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-indigo-900\/30{background-color:color-mix(in oklab,var(--color-indigo-900) 30%,transparent)}}.dark .dark\:bg-indigo-950{background-color:var(--color-indigo-950)}.dark .dark\:bg-orange-50{background-color:var(--color-orange-50)}.dark .dark\:bg-orange-100{background-color:var(--color-orange-100)}.dark .dark\:bg-orange-200{background-color:var(--color-orange-200)}.dark .dark\:bg-orange-300{background-color:var(--color-orange-300)}.dark .dark\:bg-orange-400{background-color:var(--color-orange-400)}.dark .dark\:bg-orange-500{background-color:var(--color-orange-500)}.dark .dark\:bg-orange-600{background-color:var(--color-orange-600)}.dark .dark\:bg-orange-700{background-color:var(--color-orange-700)}.dark .dark\:bg-orange-800{background-color:var(--color-orange-800)}.dark .dark\:bg-orange-900{background-color:var(--color-orange-900)}.dark .dark\:bg-orange-900\/20{background-color:#7e2a0c33}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-orange-900\/20{background-color:color-mix(in oklab,var(--color-orange-900) 20%,transparent)}}.dark .dark\:bg-orange-900\/30{background-color:#7e2a0c4d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-orange-900\/30{background-color:color-mix(in oklab,var(--color-orange-900) 30%,transparent)}}.dark .dark\:bg-orange-950{background-color:var(--color-orange-950)}.dark .dark\:bg-pink-50{background-color:var(--color-pink-50)}.dark .dark\:bg-pink-100{background-color:var(--color-pink-100)}.dark .dark\:bg-pink-200{background-color:var(--color-pink-200)}.dark .dark\:bg-pink-300{background-color:var(--color-pink-300)}.dark .dark\:bg-pink-400{background-color:var(--color-pink-400)}.dark .dark\:bg-pink-500{background-color:var(--color-pink-500)}.dark .dark\:bg-pink-600{background-color:var(--color-pink-600)}.dark .dark\:bg-pink-700{background-color:var(--color-pink-700)}.dark .dark\:bg-pink-800{background-color:var(--color-pink-800)}.dark .dark\:bg-pink-900{background-color:var(--color-pink-900)}.dark .dark\:bg-pink-950{background-color:var(--color-pink-950)}.dark .dark\:bg-purple-50{background-color:var(--color-purple-50)}.dark .dark\:bg-purple-100{background-color:var(--color-purple-100)}.dark .dark\:bg-purple-200{background-color:var(--color-purple-200)}.dark .dark\:bg-purple-300{background-color:var(--color-purple-300)}.dark .dark\:bg-purple-400{background-color:var(--color-purple-400)}.dark .dark\:bg-purple-500{background-color:var(--color-purple-500)}.dark .dark\:bg-purple-600{background-color:var(--color-purple-600)}.dark .dark\:bg-purple-700{background-color:var(--color-purple-700)}.dark .dark\:bg-purple-800{background-color:var(--color-purple-800)}.dark .dark\:bg-purple-800\/50{background-color:#6e11b080}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-purple-800\/50{background-color:color-mix(in oklab,var(--color-purple-800) 50%,transparent)}}.dark .dark\:bg-purple-900{background-color:var(--color-purple-900)}.dark .dark\:bg-purple-900\/20{background-color:#59168b33}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-purple-900\/20{background-color:color-mix(in oklab,var(--color-purple-900) 20%,transparent)}}.dark .dark\:bg-purple-900\/30{background-color:#59168b4d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-purple-900\/30{background-color:color-mix(in oklab,var(--color-purple-900) 30%,transparent)}}.dark .dark\:bg-purple-900\/50{background-color:#59168b80}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-purple-900\/50{background-color:color-mix(in oklab,var(--color-purple-900) 50%,transparent)}}.dark .dark\:bg-purple-950{background-color:var(--color-purple-950)}.dark .dark\:bg-red-50{background-color:var(--color-red-50)}.dark .dark\:bg-red-100{background-color:var(--color-red-100)}.dark .dark\:bg-red-200{background-color:var(--color-red-200)}.dark .dark\:bg-red-300{background-color:var(--color-red-300)}.dark .dark\:bg-red-400{background-color:var(--color-red-400)}.dark .dark\:bg-red-500{background-color:var(--color-red-500)}.dark .dark\:bg-red-600{background-color:var(--color-red-600)}.dark .dark\:bg-red-700{background-color:var(--color-red-700)}.dark .dark\:bg-red-800{background-color:var(--color-red-800)}.dark .dark\:bg-red-900{background-color:var(--color-red-900)}.dark .dark\:bg-red-900\/20{background-color:#82181a33}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-red-900\/20{background-color:color-mix(in oklab,var(--color-red-900) 20%,transparent)}}.dark .dark\:bg-red-900\/30{background-color:#82181a4d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-red-900\/30{background-color:color-mix(in oklab,var(--color-red-900) 30%,transparent)}}.dark .dark\:bg-red-950{background-color:var(--color-red-950)}.dark .dark\:bg-rose-50{background-color:var(--color-rose-50)}.dark .dark\:bg-rose-100{background-color:var(--color-rose-100)}.dark .dark\:bg-rose-200{background-color:var(--color-rose-200)}.dark .dark\:bg-rose-300{background-color:var(--color-rose-300)}.dark .dark\:bg-rose-400{background-color:var(--color-rose-400)}.dark .dark\:bg-rose-500{background-color:var(--color-rose-500)}.dark .dark\:bg-rose-600{background-color:var(--color-rose-600)}.dark .dark\:bg-rose-700{background-color:var(--color-rose-700)}.dark .dark\:bg-rose-800{background-color:var(--color-rose-800)}.dark .dark\:bg-rose-900{background-color:var(--color-rose-900)}.dark .dark\:bg-rose-950{background-color:var(--color-rose-950)}.dark .dark\:bg-slate-50{background-color:var(--color-slate-50)}.dark .dark\:bg-slate-100{background-color:var(--color-slate-100)}.dark .dark\:bg-slate-200{background-color:var(--color-slate-200)}.dark .dark\:bg-slate-300{background-color:var(--color-slate-300)}.dark .dark\:bg-slate-400{background-color:var(--color-slate-400)}.dark .dark\:bg-slate-500{background-color:var(--color-slate-500)}.dark .dark\:bg-slate-600{background-color:var(--color-slate-600)}.dark .dark\:bg-slate-700{background-color:var(--color-slate-700)}.dark .dark\:bg-slate-700\/50{background-color:#31415880}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-slate-700\/50{background-color:color-mix(in oklab,var(--color-slate-700) 50%,transparent)}}.dark .dark\:bg-slate-800{background-color:var(--color-slate-800)}.dark .dark\:bg-slate-900{background-color:var(--color-slate-900)}.dark .dark\:bg-slate-950{background-color:var(--color-slate-950)}.dark .dark\:bg-teal-50{background-color:var(--color-teal-50)}.dark .dark\:bg-teal-100{background-color:var(--color-teal-100)}.dark .dark\:bg-teal-200{background-color:var(--color-teal-200)}.dark .dark\:bg-teal-300{background-color:var(--color-teal-300)}.dark .dark\:bg-teal-400{background-color:var(--color-teal-400)}.dark .dark\:bg-teal-500{background-color:var(--color-teal-500)}.dark .dark\:bg-teal-600{background-color:var(--color-teal-600)}.dark .dark\:bg-teal-700{background-color:var(--color-teal-700)}.dark .dark\:bg-teal-800{background-color:var(--color-teal-800)}.dark .dark\:bg-teal-900{background-color:var(--color-teal-900)}.dark .dark\:bg-teal-900\/30{background-color:#0b4f4a4d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-teal-900\/30{background-color:color-mix(in oklab,var(--color-teal-900) 30%,transparent)}}.dark .dark\:bg-teal-950{background-color:var(--color-teal-950)}.dark .dark\:bg-white{background-color:var(--color-white)}.dark .dark\:bg-yellow-50{background-color:var(--color-yellow-50)}.dark .dark\:bg-yellow-100{background-color:var(--color-yellow-100)}.dark .dark\:bg-yellow-200{background-color:var(--color-yellow-200)}.dark .dark\:bg-yellow-300{background-color:var(--color-yellow-300)}.dark .dark\:bg-yellow-400{background-color:var(--color-yellow-400)}.dark .dark\:bg-yellow-500{background-color:var(--color-yellow-500)}.dark .dark\:bg-yellow-600{background-color:var(--color-yellow-600)}.dark .dark\:bg-yellow-700{background-color:var(--color-yellow-700)}.dark .dark\:bg-yellow-800{background-color:var(--color-yellow-800)}.dark .dark\:bg-yellow-900{background-color:var(--color-yellow-900)}.dark .dark\:bg-yellow-900\/20{background-color:#733e0a33}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-yellow-900\/20{background-color:color-mix(in oklab,var(--color-yellow-900) 20%,transparent)}}.dark .dark\:bg-yellow-900\/30{background-color:#733e0a4d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-yellow-900\/30{background-color:color-mix(in oklab,var(--color-yellow-900) 30%,transparent)}}.dark .dark\:bg-yellow-900\/40{background-color:#733e0a66}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-yellow-900\/40{background-color:color-mix(in oklab,var(--color-yellow-900) 40%,transparent)}}.dark .dark\:bg-yellow-950{background-color:var(--color-yellow-950)}.dark .dark\:from-blue-900{--tw-gradient-from:var(--color-blue-900);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.dark .dark\:from-gray-50{--tw-gradient-from:var(--color-gray-50);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.dark .dark\:from-gray-100{--tw-gradient-from:var(--color-gray-100);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.dark .dark\:from-gray-200{--tw-gradient-from:var(--color-gray-200);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.dark .dark\:from-gray-300{--tw-gradient-from:var(--color-gray-300);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.dark .dark\:from-gray-400{--tw-gradient-from:var(--color-gray-400);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.dark .dark\:from-gray-500{--tw-gradient-from:var(--color-gray-500);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.dark .dark\:from-gray-600{--tw-gradient-from:var(--color-gray-600);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.dark .dark\:from-gray-700{--tw-gradient-from:var(--color-gray-700);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.dark .dark\:from-gray-800{--tw-gradient-from:var(--color-gray-800);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.dark .dark\:from-gray-900{--tw-gradient-from:var(--color-gray-900);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.dark .dark\:to-gray-50{--tw-gradient-to:var(--color-gray-50);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.dark .dark\:to-gray-100{--tw-gradient-to:var(--color-gray-100);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.dark .dark\:to-gray-200{--tw-gradient-to:var(--color-gray-200);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.dark .dark\:to-gray-300{--tw-gradient-to:var(--color-gray-300);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.dark .dark\:to-gray-400{--tw-gradient-to:var(--color-gray-400);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.dark .dark\:to-gray-500{--tw-gradient-to:var(--color-gray-500);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.dark .dark\:to-gray-600{--tw-gradient-to:var(--color-gray-600);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.dark .dark\:to-gray-700{--tw-gradient-to:var(--color-gray-700);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.dark .dark\:to-gray-800{--tw-gradient-to:var(--color-gray-800);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.dark .dark\:to-gray-900{--tw-gradient-to:var(--color-gray-900);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.dark .dark\:to-indigo-900{--tw-gradient-to:var(--color-indigo-900);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.dark .dark\:text-center{text-align:center}.dark .dark\:text-justify{text-align:justify}.dark .dark\:text-left{text-align:left}.dark .dark\:text-right{text-align:right}.dark .dark\:text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.dark .dark\:text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.dark .dark\:text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.dark .dark\:text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.dark .dark\:text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.dark .dark\:text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.dark .dark\:font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.dark .dark\:font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.dark .dark\:font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.dark .dark\:whitespace-normal{white-space:normal}.dark .dark\:whitespace-nowrap{white-space:nowrap}.dark .dark\:whitespace-pre-wrap{white-space:pre-wrap}.dark .dark\:text-amber-50{color:var(--color-amber-50)}.dark .dark\:text-amber-100{color:var(--color-amber-100)}.dark .dark\:text-amber-200{color:var(--color-amber-200)}.dark .dark\:text-amber-300{color:var(--color-amber-300)}.dark .dark\:text-amber-400{color:var(--color-amber-400)}.dark .dark\:text-amber-500{color:var(--color-amber-500)}.dark .dark\:text-amber-600{color:var(--color-amber-600)}.dark .dark\:text-amber-700{color:var(--color-amber-700)}.dark .dark\:text-amber-800{color:var(--color-amber-800)}.dark .dark\:text-amber-900{color:var(--color-amber-900)}.dark .dark\:text-black{color:var(--color-black)}.dark .dark\:text-blue-50{color:var(--color-blue-50)}.dark .dark\:text-blue-100{color:var(--color-blue-100)}.dark .dark\:text-blue-200{color:var(--color-blue-200)}.dark .dark\:text-blue-300{color:var(--color-blue-300)}.dark .dark\:text-blue-400{color:var(--color-blue-400)}.dark .dark\:text-blue-500{color:var(--color-blue-500)}.dark .dark\:text-blue-600{color:var(--color-blue-600)}.dark .dark\:text-blue-700{color:var(--color-blue-700)}.dark .dark\:text-blue-800{color:var(--color-blue-800)}.dark .dark\:text-blue-900{color:var(--color-blue-900)}.dark .dark\:text-cyan-50{color:var(--color-cyan-50)}.dark .dark\:text-cyan-100{color:var(--color-cyan-100)}.dark .dark\:text-cyan-200{color:var(--color-cyan-200)}.dark .dark\:text-cyan-300{color:var(--color-cyan-300)}.dark .dark\:text-cyan-400{color:var(--color-cyan-400)}.dark .dark\:text-cyan-500{color:var(--color-cyan-500)}.dark .dark\:text-cyan-600{color:var(--color-cyan-600)}.dark .dark\:text-cyan-700{color:var(--color-cyan-700)}.dark .dark\:text-cyan-800{color:var(--color-cyan-800)}.dark .dark\:text-cyan-900{color:var(--color-cyan-900)}.dark .dark\:text-emerald-50{color:var(--color-emerald-50)}.dark .dark\:text-emerald-100{color:var(--color-emerald-100)}.dark .dark\:text-emerald-200{color:var(--color-emerald-200)}.dark .dark\:text-emerald-300{color:var(--color-emerald-300)}.dark .dark\:text-emerald-400{color:var(--color-emerald-400)}.dark .dark\:text-emerald-500{color:var(--color-emerald-500)}.dark .dark\:text-emerald-600{color:var(--color-emerald-600)}.dark .dark\:text-emerald-700{color:var(--color-emerald-700)}.dark .dark\:text-emerald-800{color:var(--color-emerald-800)}.dark .dark\:text-emerald-900{color:var(--color-emerald-900)}.dark .dark\:text-gray-50{color:var(--color-gray-50)}.dark .dark\:text-gray-100{color:var(--color-gray-100)}.dark .dark\:text-gray-200{color:var(--color-gray-200)}.dark .dark\:text-gray-300{color:var(--color-gray-300)}.dark .dark\:text-gray-400{color:var(--color-gray-400)}.dark .dark\:text-gray-500{color:var(--color-gray-500)}.dark .dark\:text-gray-600{color:var(--color-gray-600)}.dark .dark\:text-gray-700{color:var(--color-gray-700)}.dark .dark\:text-gray-800{color:var(--color-gray-800)}.dark .dark\:text-gray-900{color:var(--color-gray-900)}.dark .dark\:text-green-50{color:var(--color-green-50)}.dark .dark\:text-green-100{color:var(--color-green-100)}.dark .dark\:text-green-200{color:var(--color-green-200)}.dark .dark\:text-green-300{color:var(--color-green-300)}.dark .dark\:text-green-400{color:var(--color-green-400)}.dark .dark\:text-green-500{color:var(--color-green-500)}.dark .dark\:text-green-600{color:var(--color-green-600)}.dark .dark\:text-green-700{color:var(--color-green-700)}.dark .dark\:text-green-800{color:var(--color-green-800)}.dark .dark\:text-green-900{color:var(--color-green-900)}.dark .dark\:text-indigo-50{color:var(--color-indigo-50)}.dark .dark\:text-indigo-100{color:var(--color-indigo-100)}.dark .dark\:text-indigo-200{color:var(--color-indigo-200)}.dark .dark\:text-indigo-300{color:var(--color-indigo-300)}.dark .dark\:text-indigo-400{color:var(--color-indigo-400)}.dark .dark\:text-indigo-500{color:var(--color-indigo-500)}.dark .dark\:text-indigo-600{color:var(--color-indigo-600)}.dark .dark\:text-indigo-700{color:var(--color-indigo-700)}.dark .dark\:text-indigo-800{color:var(--color-indigo-800)}.dark .dark\:text-indigo-900{color:var(--color-indigo-900)}.dark .dark\:text-orange-50{color:var(--color-orange-50)}.dark .dark\:text-orange-100{color:var(--color-orange-100)}.dark .dark\:text-orange-200{color:var(--color-orange-200)}.dark .dark\:text-orange-300{color:var(--color-orange-300)}.dark .dark\:text-orange-400{color:var(--color-orange-400)}.dark .dark\:text-orange-500{color:var(--color-orange-500)}.dark .dark\:text-orange-600{color:var(--color-orange-600)}.dark .dark\:text-orange-700{color:var(--color-orange-700)}.dark .dark\:text-orange-800{color:var(--color-orange-800)}.dark .dark\:text-orange-900{color:var(--color-orange-900)}.dark .dark\:text-pink-50{color:var(--color-pink-50)}.dark .dark\:text-pink-100{color:var(--color-pink-100)}.dark .dark\:text-pink-200{color:var(--color-pink-200)}.dark .dark\:text-pink-300{color:var(--color-pink-300)}.dark .dark\:text-pink-400{color:var(--color-pink-400)}.dark .dark\:text-pink-500{color:var(--color-pink-500)}.dark .dark\:text-pink-600{color:var(--color-pink-600)}.dark .dark\:text-pink-700{color:var(--color-pink-700)}.dark .dark\:text-pink-800{color:var(--color-pink-800)}.dark .dark\:text-pink-900{color:var(--color-pink-900)}.dark .dark\:text-purple-50{color:var(--color-purple-50)}.dark .dark\:text-purple-100{color:var(--color-purple-100)}.dark .dark\:text-purple-200{color:var(--color-purple-200)}.dark .dark\:text-purple-300{color:var(--color-purple-300)}.dark .dark\:text-purple-400{color:var(--color-purple-400)}.dark .dark\:text-purple-500{color:var(--color-purple-500)}.dark .dark\:text-purple-600{color:var(--color-purple-600)}.dark .dark\:text-purple-700{color:var(--color-purple-700)}.dark .dark\:text-purple-800{color:var(--color-purple-800)}.dark .dark\:text-purple-900{color:var(--color-purple-900)}.dark .dark\:text-red-50{color:var(--color-red-50)}.dark .dark\:text-red-100{color:var(--color-red-100)}.dark .dark\:text-red-200{color:var(--color-red-200)}.dark .dark\:text-red-300{color:var(--color-red-300)}.dark .dark\:text-red-400{color:var(--color-red-400)}.dark .dark\:text-red-500{color:var(--color-red-500)}.dark .dark\:text-red-600{color:var(--color-red-600)}.dark .dark\:text-red-700{color:var(--color-red-700)}.dark .dark\:text-red-800{color:var(--color-red-800)}.dark .dark\:text-red-900{color:var(--color-red-900)}.dark .dark\:text-slate-50{color:var(--color-slate-50)}.dark .dark\:text-slate-100{color:var(--color-slate-100)}.dark .dark\:text-slate-200{color:var(--color-slate-200)}.dark .dark\:text-slate-300{color:var(--color-slate-300)}.dark .dark\:text-slate-400{color:var(--color-slate-400)}.dark .dark\:text-slate-500{color:var(--color-slate-500)}.dark .dark\:text-slate-600{color:var(--color-slate-600)}.dark .dark\:text-slate-700{color:var(--color-slate-700)}.dark .dark\:text-slate-800{color:var(--color-slate-800)}.dark .dark\:text-slate-900{color:var(--color-slate-900)}.dark .dark\:text-teal-50{color:var(--color-teal-50)}.dark .dark\:text-teal-100{color:var(--color-teal-100)}.dark .dark\:text-teal-200{color:var(--color-teal-200)}.dark .dark\:text-teal-300{color:var(--color-teal-300)}.dark .dark\:text-teal-400{color:var(--color-teal-400)}.dark .dark\:text-teal-500{color:var(--color-teal-500)}.dark .dark\:text-teal-600{color:var(--color-teal-600)}.dark .dark\:text-teal-700{color:var(--color-teal-700)}.dark .dark\:text-teal-800{color:var(--color-teal-800)}.dark .dark\:text-teal-900{color:var(--color-teal-900)}.dark .dark\:text-white{color:var(--color-white)}.dark .dark\:text-yellow-50{color:var(--color-yellow-50)}.dark .dark\:text-yellow-100{color:var(--color-yellow-100)}.dark .dark\:text-yellow-200{color:var(--color-yellow-200)}.dark .dark\:text-yellow-300{color:var(--color-yellow-300)}.dark .dark\:text-yellow-400{color:var(--color-yellow-400)}.dark .dark\:text-yellow-500{color:var(--color-yellow-500)}.dark .dark\:text-yellow-600{color:var(--color-yellow-600)}.dark .dark\:text-yellow-700{color:var(--color-yellow-700)}.dark .dark\:text-yellow-800{color:var(--color-yellow-800)}.dark .dark\:text-yellow-900{color:var(--color-yellow-900)}.dark .dark\:italic{font-style:italic}.dark .dark\:underline{text-decoration-line:underline}.dark .dark\:placeholder-gray-400::placeholder{color:var(--color-gray-400)}.dark .dark\:placeholder-gray-500::placeholder{color:var(--color-gray-500)}.dark .dark\:placeholder-gray-600::placeholder{color:var(--color-gray-600)}.dark .dark\:opacity-0{opacity:0}.dark .dark\:opacity-25{opacity:.25}.dark .dark\:opacity-50{opacity:.5}.dark .dark\:opacity-75{opacity:.75}.dark .dark\:opacity-100{opacity:1}.dark .dark\:shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.dark .dark\:shadow-2xl{--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.dark .dark\:shadow-inner{--tw-shadow:inset 0 2px 4px 0 var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.dark .dark\:shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.dark .dark\:shadow-md{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a), 0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.dark .dark\:shadow-none{--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.dark .dark\:shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.dark .dark\:shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a), 0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.dark .dark\:shadow-purple-900\/50{--tw-shadow-color:#59168b80}@supports (color:color-mix(in lab,red,red)){.dark .dark\:shadow-purple-900\/50{--tw-shadow-color:color-mix(in oklab, color-mix(in oklab, var(--color-purple-900) 50%, transparent) var(--tw-shadow-alpha), transparent)}}.dark .dark\:ring-blue-50{--tw-ring-color:var(--color-blue-50)}.dark .dark\:ring-blue-100{--tw-ring-color:var(--color-blue-100)}.dark .dark\:ring-blue-200{--tw-ring-color:var(--color-blue-200)}.dark .dark\:ring-blue-300{--tw-ring-color:var(--color-blue-300)}.dark .dark\:ring-blue-400{--tw-ring-color:var(--color-blue-400)}.dark .dark\:ring-blue-500{--tw-ring-color:var(--color-blue-500)}.dark .dark\:ring-blue-600{--tw-ring-color:var(--color-blue-600)}.dark .dark\:ring-blue-700{--tw-ring-color:var(--color-blue-700)}.dark .dark\:ring-blue-800{--tw-ring-color:var(--color-blue-800)}.dark .dark\:ring-blue-900{--tw-ring-color:var(--color-blue-900)}.dark .dark\:ring-gray-50{--tw-ring-color:var(--color-gray-50)}.dark .dark\:ring-gray-100{--tw-ring-color:var(--color-gray-100)}.dark .dark\:ring-gray-200{--tw-ring-color:var(--color-gray-200)}.dark .dark\:ring-gray-300{--tw-ring-color:var(--color-gray-300)}.dark .dark\:ring-gray-400{--tw-ring-color:var(--color-gray-400)}.dark .dark\:ring-gray-500{--tw-ring-color:var(--color-gray-500)}.dark .dark\:ring-gray-600{--tw-ring-color:var(--color-gray-600)}.dark .dark\:ring-gray-700{--tw-ring-color:var(--color-gray-700)}.dark .dark\:ring-gray-800{--tw-ring-color:var(--color-gray-800)}.dark .dark\:ring-gray-900{--tw-ring-color:var(--color-gray-900)}.dark .dark\:ring-offset-gray-800{--tw-ring-offset-color:var(--color-gray-800)}.dark .dark\:ring-offset-gray-900{--tw-ring-offset-color:var(--color-gray-900)}@media(hover:hover){.dark .dark\:group-hover\:bg-gray-700:is(:where(.group):hover *){background-color:var(--color-gray-700)}.dark .dark\:group-hover\:bg-gray-800:is(:where(.group):hover *){background-color:var(--color-gray-800)}.dark .dark\:group-hover\:text-gray-100:is(:where(.group):hover *){color:var(--color-gray-100)}.dark .dark\:group-hover\:text-white:is(:where(.group):hover *){color:var(--color-white)}}.dark .dark\:selection\:bg-blue-800 ::selection{background-color:var(--color-blue-800)}.dark .dark\:selection\:bg-blue-800::selection{background-color:var(--color-blue-800)}.dark .dark\:selection\:text-blue-100 ::selection{color:var(--color-blue-100)}.dark .dark\:selection\:text-blue-100::selection{color:var(--color-blue-100)}.dark .dark\:odd\:bg-gray-800:nth-child(odd){background-color:var(--color-gray-800)}.dark .dark\:odd\:bg-gray-900:nth-child(odd){background-color:var(--color-gray-900)}.dark .dark\:even\:bg-gray-800:nth-child(2n){background-color:var(--color-gray-800)}.dark .dark\:even\:bg-gray-900:nth-child(2n){background-color:var(--color-gray-900)}@media(hover:hover){.dark .dark\:hover\:border-amber-50:hover{border-color:var(--color-amber-50)}.dark .dark\:hover\:border-amber-100:hover{border-color:var(--color-amber-100)}.dark .dark\:hover\:border-amber-200:hover{border-color:var(--color-amber-200)}.dark .dark\:hover\:border-amber-300:hover{border-color:var(--color-amber-300)}.dark .dark\:hover\:border-amber-400:hover{border-color:var(--color-amber-400)}.dark .dark\:hover\:border-amber-500:hover{border-color:var(--color-amber-500)}.dark .dark\:hover\:border-amber-600:hover{border-color:var(--color-amber-600)}.dark .dark\:hover\:border-amber-700:hover{border-color:var(--color-amber-700)}.dark .dark\:hover\:border-amber-800:hover{border-color:var(--color-amber-800)}.dark .dark\:hover\:border-amber-900:hover{border-color:var(--color-amber-900)}.dark .dark\:hover\:border-blue-50:hover{border-color:var(--color-blue-50)}.dark .dark\:hover\:border-blue-100:hover{border-color:var(--color-blue-100)}.dark .dark\:hover\:border-blue-200:hover{border-color:var(--color-blue-200)}.dark .dark\:hover\:border-blue-300:hover{border-color:var(--color-blue-300)}.dark .dark\:hover\:border-blue-400:hover{border-color:var(--color-blue-400)}.dark .dark\:hover\:border-blue-500:hover{border-color:var(--color-blue-500)}.dark .dark\:hover\:border-blue-600:hover{border-color:var(--color-blue-600)}.dark .dark\:hover\:border-blue-700:hover{border-color:var(--color-blue-700)}.dark .dark\:hover\:border-blue-800:hover{border-color:var(--color-blue-800)}.dark .dark\:hover\:border-blue-900:hover{border-color:var(--color-blue-900)}.dark .dark\:hover\:border-gray-50:hover{border-color:var(--color-gray-50)}.dark .dark\:hover\:border-gray-100:hover{border-color:var(--color-gray-100)}.dark .dark\:hover\:border-gray-200:hover{border-color:var(--color-gray-200)}.dark .dark\:hover\:border-gray-300:hover{border-color:var(--color-gray-300)}.dark .dark\:hover\:border-gray-400:hover{border-color:var(--color-gray-400)}.dark .dark\:hover\:border-gray-500:hover{border-color:var(--color-gray-500)}.dark .dark\:hover\:border-gray-600:hover{border-color:var(--color-gray-600)}.dark .dark\:hover\:border-gray-700:hover{border-color:var(--color-gray-700)}.dark .dark\:hover\:border-gray-800:hover{border-color:var(--color-gray-800)}.dark .dark\:hover\:border-gray-900:hover{border-color:var(--color-gray-900)}.dark .dark\:hover\:bg-amber-50:hover{background-color:var(--color-amber-50)}.dark .dark\:hover\:bg-amber-100:hover{background-color:var(--color-amber-100)}.dark .dark\:hover\:bg-amber-200:hover{background-color:var(--color-amber-200)}.dark .dark\:hover\:bg-amber-300:hover{background-color:var(--color-amber-300)}.dark .dark\:hover\:bg-amber-400:hover{background-color:var(--color-amber-400)}.dark .dark\:hover\:bg-amber-500:hover{background-color:var(--color-amber-500)}.dark .dark\:hover\:bg-amber-600:hover{background-color:var(--color-amber-600)}.dark .dark\:hover\:bg-amber-700:hover{background-color:var(--color-amber-700)}.dark .dark\:hover\:bg-amber-800:hover{background-color:var(--color-amber-800)}.dark .dark\:hover\:bg-amber-900:hover{background-color:var(--color-amber-900)}.dark .dark\:hover\:bg-blue-50:hover{background-color:var(--color-blue-50)}.dark .dark\:hover\:bg-blue-100:hover{background-color:var(--color-blue-100)}.dark .dark\:hover\:bg-blue-200:hover{background-color:var(--color-blue-200)}.dark .dark\:hover\:bg-blue-300:hover{background-color:var(--color-blue-300)}.dark .dark\:hover\:bg-blue-400:hover{background-color:var(--color-blue-400)}.dark .dark\:hover\:bg-blue-500:hover{background-color:var(--color-blue-500)}.dark .dark\:hover\:bg-blue-600:hover{background-color:var(--color-blue-600)}.dark .dark\:hover\:bg-blue-700:hover{background-color:var(--color-blue-700)}.dark .dark\:hover\:bg-blue-800:hover{background-color:var(--color-blue-800)}.dark .dark\:hover\:bg-blue-900:hover{background-color:var(--color-blue-900)}.dark .dark\:hover\:bg-blue-900\/30:hover{background-color:#1c398e4d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:hover\:bg-blue-900\/30:hover{background-color:color-mix(in oklab,var(--color-blue-900) 30%,transparent)}}.dark .dark\:hover\:bg-blue-900\/50:hover{background-color:#1c398e80}@supports (color:color-mix(in lab,red,red)){.dark .dark\:hover\:bg-blue-900\/50:hover{background-color:color-mix(in oklab,var(--color-blue-900) 50%,transparent)}}.dark .dark\:hover\:bg-gray-50:hover{background-color:var(--color-gray-50)}.dark .dark\:hover\:bg-gray-100:hover{background-color:var(--color-gray-100)}.dark .dark\:hover\:bg-gray-200:hover{background-color:var(--color-gray-200)}.dark .dark\:hover\:bg-gray-300:hover{background-color:var(--color-gray-300)}.dark .dark\:hover\:bg-gray-400:hover{background-color:var(--color-gray-400)}.dark .dark\:hover\:bg-gray-500:hover{background-color:var(--color-gray-500)}.dark .dark\:hover\:bg-gray-600:hover{background-color:var(--color-gray-600)}.dark .dark\:hover\:bg-gray-700:hover{background-color:var(--color-gray-700)}.dark .dark\:hover\:bg-gray-700\/30:hover{background-color:#3641534d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:hover\:bg-gray-700\/30:hover{background-color:color-mix(in oklab,var(--color-gray-700) 30%,transparent)}}.dark .dark\:hover\:bg-gray-700\/50:hover{background-color:#36415380}@supports (color:color-mix(in lab,red,red)){.dark .dark\:hover\:bg-gray-700\/50:hover{background-color:color-mix(in oklab,var(--color-gray-700) 50%,transparent)}}.dark .dark\:hover\:bg-gray-800:hover{background-color:var(--color-gray-800)}.dark .dark\:hover\:bg-gray-800\/50:hover{background-color:#1e293980}@supports (color:color-mix(in lab,red,red)){.dark .dark\:hover\:bg-gray-800\/50:hover{background-color:color-mix(in oklab,var(--color-gray-800) 50%,transparent)}}.dark .dark\:hover\:bg-gray-900:hover{background-color:var(--color-gray-900)}.dark .dark\:hover\:bg-green-50:hover{background-color:var(--color-green-50)}.dark .dark\:hover\:bg-green-100:hover{background-color:var(--color-green-100)}.dark .dark\:hover\:bg-green-200:hover{background-color:var(--color-green-200)}.dark .dark\:hover\:bg-green-300:hover{background-color:var(--color-green-300)}.dark .dark\:hover\:bg-green-400:hover{background-color:var(--color-green-400)}.dark .dark\:hover\:bg-green-500:hover{background-color:var(--color-green-500)}.dark .dark\:hover\:bg-green-600:hover{background-color:var(--color-green-600)}.dark .dark\:hover\:bg-green-700:hover{background-color:var(--color-green-700)}.dark .dark\:hover\:bg-green-800:hover{background-color:var(--color-green-800)}.dark .dark\:hover\:bg-green-900:hover{background-color:var(--color-green-900)}.dark .dark\:hover\:bg-green-900\/30:hover{background-color:#0d542b4d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:hover\:bg-green-900\/30:hover{background-color:color-mix(in oklab,var(--color-green-900) 30%,transparent)}}.dark .dark\:hover\:bg-indigo-50:hover{background-color:var(--color-indigo-50)}.dark .dark\:hover\:bg-indigo-100:hover{background-color:var(--color-indigo-100)}.dark .dark\:hover\:bg-indigo-200:hover{background-color:var(--color-indigo-200)}.dark .dark\:hover\:bg-indigo-300:hover{background-color:var(--color-indigo-300)}.dark .dark\:hover\:bg-indigo-400:hover{background-color:var(--color-indigo-400)}.dark .dark\:hover\:bg-indigo-500:hover{background-color:var(--color-indigo-500)}.dark .dark\:hover\:bg-indigo-600:hover{background-color:var(--color-indigo-600)}.dark .dark\:hover\:bg-indigo-700:hover{background-color:var(--color-indigo-700)}.dark .dark\:hover\:bg-indigo-800:hover{background-color:var(--color-indigo-800)}.dark .dark\:hover\:bg-indigo-900:hover{background-color:var(--color-indigo-900)}.dark .dark\:hover\:bg-indigo-900\/30:hover{background-color:#312c854d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:hover\:bg-indigo-900\/30:hover{background-color:color-mix(in oklab,var(--color-indigo-900) 30%,transparent)}}.dark .dark\:hover\:bg-orange-50:hover{background-color:var(--color-orange-50)}.dark .dark\:hover\:bg-orange-100:hover{background-color:var(--color-orange-100)}.dark .dark\:hover\:bg-orange-200:hover{background-color:var(--color-orange-200)}.dark .dark\:hover\:bg-orange-300:hover{background-color:var(--color-orange-300)}.dark .dark\:hover\:bg-orange-400:hover{background-color:var(--color-orange-400)}.dark .dark\:hover\:bg-orange-500:hover{background-color:var(--color-orange-500)}.dark .dark\:hover\:bg-orange-600:hover{background-color:var(--color-orange-600)}.dark .dark\:hover\:bg-orange-700:hover{background-color:var(--color-orange-700)}.dark .dark\:hover\:bg-orange-800:hover{background-color:var(--color-orange-800)}.dark .dark\:hover\:bg-orange-900:hover{background-color:var(--color-orange-900)}.dark .dark\:hover\:bg-orange-900\/30:hover{background-color:#7e2a0c4d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:hover\:bg-orange-900\/30:hover{background-color:color-mix(in oklab,var(--color-orange-900) 30%,transparent)}}.dark .dark\:hover\:bg-pink-50:hover{background-color:var(--color-pink-50)}.dark .dark\:hover\:bg-pink-100:hover{background-color:var(--color-pink-100)}.dark .dark\:hover\:bg-pink-200:hover{background-color:var(--color-pink-200)}.dark .dark\:hover\:bg-pink-300:hover{background-color:var(--color-pink-300)}.dark .dark\:hover\:bg-pink-400:hover{background-color:var(--color-pink-400)}.dark .dark\:hover\:bg-pink-500:hover{background-color:var(--color-pink-500)}.dark .dark\:hover\:bg-pink-600:hover{background-color:var(--color-pink-600)}.dark .dark\:hover\:bg-pink-700:hover{background-color:var(--color-pink-700)}.dark .dark\:hover\:bg-pink-800:hover{background-color:var(--color-pink-800)}.dark .dark\:hover\:bg-pink-900:hover{background-color:var(--color-pink-900)}.dark .dark\:hover\:bg-purple-50:hover{background-color:var(--color-purple-50)}.dark .dark\:hover\:bg-purple-100:hover{background-color:var(--color-purple-100)}.dark .dark\:hover\:bg-purple-200:hover{background-color:var(--color-purple-200)}.dark .dark\:hover\:bg-purple-300:hover{background-color:var(--color-purple-300)}.dark .dark\:hover\:bg-purple-400:hover{background-color:var(--color-purple-400)}.dark .dark\:hover\:bg-purple-500:hover{background-color:var(--color-purple-500)}.dark .dark\:hover\:bg-purple-600:hover{background-color:var(--color-purple-600)}.dark .dark\:hover\:bg-purple-700:hover{background-color:var(--color-purple-700)}.dark .dark\:hover\:bg-purple-800:hover{background-color:var(--color-purple-800)}.dark .dark\:hover\:bg-purple-900:hover{background-color:var(--color-purple-900)}.dark .dark\:hover\:bg-purple-900\/30:hover{background-color:#59168b4d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:hover\:bg-purple-900\/30:hover{background-color:color-mix(in oklab,var(--color-purple-900) 30%,transparent)}}.dark .dark\:hover\:bg-red-50:hover{background-color:var(--color-red-50)}.dark .dark\:hover\:bg-red-100:hover{background-color:var(--color-red-100)}.dark .dark\:hover\:bg-red-200:hover{background-color:var(--color-red-200)}.dark .dark\:hover\:bg-red-300:hover{background-color:var(--color-red-300)}.dark .dark\:hover\:bg-red-400:hover{background-color:var(--color-red-400)}.dark .dark\:hover\:bg-red-500:hover{background-color:var(--color-red-500)}.dark .dark\:hover\:bg-red-600:hover{background-color:var(--color-red-600)}.dark .dark\:hover\:bg-red-700:hover{background-color:var(--color-red-700)}.dark .dark\:hover\:bg-red-800:hover{background-color:var(--color-red-800)}.dark .dark\:hover\:bg-red-900:hover{background-color:var(--color-red-900)}.dark .dark\:hover\:bg-red-900\/20:hover{background-color:#82181a33}@supports (color:color-mix(in lab,red,red)){.dark .dark\:hover\:bg-red-900\/20:hover{background-color:color-mix(in oklab,var(--color-red-900) 20%,transparent)}}.dark .dark\:hover\:bg-red-900\/30:hover{background-color:#82181a4d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:hover\:bg-red-900\/30:hover{background-color:color-mix(in oklab,var(--color-red-900) 30%,transparent)}}.dark .dark\:hover\:bg-slate-50:hover{background-color:var(--color-slate-50)}.dark .dark\:hover\:bg-slate-100:hover{background-color:var(--color-slate-100)}.dark .dark\:hover\:bg-slate-200:hover{background-color:var(--color-slate-200)}.dark .dark\:hover\:bg-slate-300:hover{background-color:var(--color-slate-300)}.dark .dark\:hover\:bg-slate-400:hover{background-color:var(--color-slate-400)}.dark .dark\:hover\:bg-slate-500:hover{background-color:var(--color-slate-500)}.dark .dark\:hover\:bg-slate-600:hover{background-color:var(--color-slate-600)}.dark .dark\:hover\:bg-slate-700:hover{background-color:var(--color-slate-700)}.dark .dark\:hover\:bg-slate-800:hover{background-color:var(--color-slate-800)}.dark .dark\:hover\:bg-slate-900:hover{background-color:var(--color-slate-900)}.dark .dark\:hover\:bg-white\/10:hover{background-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.dark .dark\:hover\:bg-white\/10:hover{background-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.dark .dark\:hover\:bg-yellow-500:hover{background-color:var(--color-yellow-500)}.dark .dark\:hover\:bg-yellow-800:hover{background-color:var(--color-yellow-800)}.dark .dark\:hover\:bg-yellow-900\/30:hover{background-color:#733e0a4d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:hover\:bg-yellow-900\/30:hover{background-color:color-mix(in oklab,var(--color-yellow-900) 30%,transparent)}}.dark .dark\:hover\:text-black:hover{color:var(--color-black)}.dark .dark\:hover\:text-blue-50:hover{color:var(--color-blue-50)}.dark .dark\:hover\:text-blue-100:hover{color:var(--color-blue-100)}.dark .dark\:hover\:text-blue-200:hover{color:var(--color-blue-200)}.dark .dark\:hover\:text-blue-300:hover{color:var(--color-blue-300)}.dark .dark\:hover\:text-blue-400:hover{color:var(--color-blue-400)}.dark .dark\:hover\:text-blue-500:hover{color:var(--color-blue-500)}.dark .dark\:hover\:text-blue-600:hover{color:var(--color-blue-600)}.dark .dark\:hover\:text-blue-700:hover{color:var(--color-blue-700)}.dark .dark\:hover\:text-blue-800:hover{color:var(--color-blue-800)}.dark .dark\:hover\:text-blue-900:hover{color:var(--color-blue-900)}.dark .dark\:hover\:text-gray-50:hover{color:var(--color-gray-50)}.dark .dark\:hover\:text-gray-100:hover{color:var(--color-gray-100)}.dark .dark\:hover\:text-gray-200:hover{color:var(--color-gray-200)}.dark .dark\:hover\:text-gray-300:hover{color:var(--color-gray-300)}.dark .dark\:hover\:text-gray-400:hover{color:var(--color-gray-400)}.dark .dark\:hover\:text-gray-500:hover{color:var(--color-gray-500)}.dark .dark\:hover\:text-gray-600:hover{color:var(--color-gray-600)}.dark .dark\:hover\:text-gray-700:hover{color:var(--color-gray-700)}.dark .dark\:hover\:text-gray-800:hover{color:var(--color-gray-800)}.dark .dark\:hover\:text-gray-900:hover{color:var(--color-gray-900)}.dark .dark\:hover\:text-green-50:hover{color:var(--color-green-50)}.dark .dark\:hover\:text-green-100:hover{color:var(--color-green-100)}.dark .dark\:hover\:text-green-200:hover{color:var(--color-green-200)}.dark .dark\:hover\:text-green-300:hover{color:var(--color-green-300)}.dark .dark\:hover\:text-green-400:hover{color:var(--color-green-400)}.dark .dark\:hover\:text-green-500:hover{color:var(--color-green-500)}.dark .dark\:hover\:text-green-600:hover{color:var(--color-green-600)}.dark .dark\:hover\:text-green-700:hover{color:var(--color-green-700)}.dark .dark\:hover\:text-green-800:hover{color:var(--color-green-800)}.dark .dark\:hover\:text-green-900:hover{color:var(--color-green-900)}.dark .dark\:hover\:text-purple-200:hover{color:var(--color-purple-200)}.dark .dark\:hover\:text-purple-300:hover{color:var(--color-purple-300)}.dark .dark\:hover\:text-red-50:hover{color:var(--color-red-50)}.dark .dark\:hover\:text-red-100:hover{color:var(--color-red-100)}.dark .dark\:hover\:text-red-200:hover{color:var(--color-red-200)}.dark .dark\:hover\:text-red-300:hover{color:var(--color-red-300)}.dark .dark\:hover\:text-red-400:hover{color:var(--color-red-400)}.dark .dark\:hover\:text-red-500:hover{color:var(--color-red-500)}.dark .dark\:hover\:text-red-600:hover{color:var(--color-red-600)}.dark .dark\:hover\:text-red-700:hover{color:var(--color-red-700)}.dark .dark\:hover\:text-red-800:hover{color:var(--color-red-800)}.dark .dark\:hover\:text-red-900:hover{color:var(--color-red-900)}.dark .dark\:hover\:text-slate-800:hover{color:var(--color-slate-800)}.dark .dark\:hover\:text-white:hover{color:var(--color-white)}}.dark .dark\:focus\:border-blue-400:focus{border-color:var(--color-blue-400)}.dark .dark\:focus\:border-gray-500:focus{border-color:var(--color-gray-500)}.dark .dark\:focus\:border-gray-600:focus{border-color:var(--color-gray-600)}.dark .dark\:focus\:border-gray-700:focus{border-color:var(--color-gray-700)}.dark .dark\:focus\:bg-gray-700:focus{background-color:var(--color-gray-700)}.dark .dark\:focus\:bg-gray-800:focus{background-color:var(--color-gray-800)}.dark .dark\:focus\:bg-gray-900:focus{background-color:var(--color-gray-900)}.dark .dark\:focus\:text-gray-100:focus{color:var(--color-gray-100)}.dark .dark\:focus\:text-gray-200:focus{color:var(--color-gray-200)}.dark .dark\:focus\:text-gray-300:focus{color:var(--color-gray-300)}.dark .dark\:focus\:text-white:focus{color:var(--color-white)}.dark .dark\:focus\:ring-amber-400:focus{--tw-ring-color:var(--color-amber-400)}.dark .dark\:focus\:ring-amber-500:focus{--tw-ring-color:var(--color-amber-500)}.dark .dark\:focus\:ring-amber-600:focus{--tw-ring-color:var(--color-amber-600)}.dark .dark\:focus\:ring-blue-400:focus{--tw-ring-color:var(--color-blue-400)}.dark .dark\:focus\:ring-blue-500:focus{--tw-ring-color:var(--color-blue-500)}.dark .dark\:focus\:ring-blue-600:focus{--tw-ring-color:var(--color-blue-600)}.dark .dark\:focus\:ring-blue-700:focus{--tw-ring-color:var(--color-blue-700)}.dark .dark\:focus\:ring-gray-400:focus{--tw-ring-color:var(--color-gray-400)}.dark .dark\:focus\:ring-gray-500:focus{--tw-ring-color:var(--color-gray-500)}.dark .dark\:focus\:ring-gray-600:focus{--tw-ring-color:var(--color-gray-600)}.dark .dark\:focus\:ring-gray-700:focus{--tw-ring-color:var(--color-gray-700)}.dark .dark\:focus\:ring-indigo-400:focus{--tw-ring-color:var(--color-indigo-400)}.dark .dark\:focus\:ring-red-400:focus{--tw-ring-color:var(--color-red-400)}.dark .dark\:focus\:ring-red-500:focus{--tw-ring-color:var(--color-red-500)}.dark .dark\:focus\:ring-slate-500:focus{--tw-ring-color:var(--color-slate-500)}.dark .dark\:focus\:ring-offset-gray-800:focus{--tw-ring-offset-color:var(--color-gray-800)}.dark .dark\:focus\:ring-offset-gray-900:focus{--tw-ring-offset-color:var(--color-gray-900)}.dark .dark\:active\:bg-gray-600:active{background-color:var(--color-gray-600)}.dark .dark\:active\:bg-gray-700:active{background-color:var(--color-gray-700)}.dark .dark\:active\:bg-gray-800:active{background-color:var(--color-gray-800)}.dark .dark\:disabled\:bg-gray-700:disabled{background-color:var(--color-gray-700)}.dark .dark\:disabled\:bg-gray-800:disabled{background-color:var(--color-gray-800)}.dark .dark\:disabled\:bg-gray-900:disabled{background-color:var(--color-gray-900)}.dark .dark\:disabled\:text-gray-500:disabled{color:var(--color-gray-500)}.dark .dark\:disabled\:text-gray-600:disabled{color:var(--color-gray-600)}@media(hover:hover){.dark .dark\:disabled\:hover\:bg-indigo-500:disabled:hover{background-color:var(--color-indigo-500)}}.\[\&_table\]\:w-full table{width:100%}.\[\&_table\]\:border-collapse table{border-collapse:collapse}.\[\&_table\]\:border table{border-style:var(--tw-border-style);border-width:1px}.\[\&_table\]\:border-gray-300 table{border-color:var(--color-gray-300)}.dark .dark\:\[\&_table\]\:border-gray-600 table{border-color:var(--color-gray-600)}.\[\&_td\]\:border td{border-style:var(--tw-border-style);border-width:1px}.\[\&_td\]\:border-gray-300 td{border-color:var(--color-gray-300)}.\[\&_td\]\:px-4 td{padding-inline:calc(var(--spacing) * 4)}.\[\&_td\]\:py-2 td{padding-block:calc(var(--spacing) * 2)}.dark .dark\:\[\&_td\]\:border-gray-600 td{border-color:var(--color-gray-600)}.\[\&_th\]\:border th{border-style:var(--tw-border-style);border-width:1px}.\[\&_th\]\:border-gray-300 th{border-color:var(--color-gray-300)}.\[\&_th\]\:bg-gray-100 th{background-color:var(--color-gray-100)}.\[\&_th\]\:px-4 th{padding-inline:calc(var(--spacing) * 4)}.\[\&_th\]\:py-2 th{padding-block:calc(var(--spacing) * 2)}.\[\&_th\]\:text-left th{text-align:left}.dark .dark\:\[\&_th\]\:border-gray-600 th{border-color:var(--color-gray-600)}.dark .dark\:\[\&_th\]\:bg-gray-800 th{background-color:var(--color-gray-800)}.\[\&_tr\:hover\]\:bg-gray-50 tr:hover{background-color:var(--color-gray-50)}.dark .dark\:\[\&_tr\:hover\]\:bg-gray-700\/50 tr:hover{background-color:#36415380}@supports (color:color-mix(in lab,red,red)){.dark .dark\:\[\&_tr\:hover\]\:bg-gray-700\/50 tr:hover{background-color:color-mix(in oklab,var(--color-gray-700) 50%,transparent)}}}.dashboard-content{max-width:80rem;margin:0 auto}.dashboard-stats-grid{gap:var(--spacing-5);margin-bottom:var(--spacing-8);grid-template-columns:1fr;display:grid}@media(min-width:768px){.dashboard-stats-grid{grid-template-columns:repeat(2,1fr)}}@media(min-width:1024px){.dashboard-stats-grid{grid-template-columns:repeat(4,1fr)}}.dashboard-main-grid{gap:var(--spacing-6);grid-template-columns:1fr;display:grid}@media(min-width:1024px){.dashboard-main-grid{grid-template-columns:repeat(5,1fr)}}.dashboard-column-left{gap:var(--spacing-6);flex-direction:column;display:flex}@media(min-width:1024px){.dashboard-column-left{grid-column:span 3}}.dashboard-column-right{gap:var(--spacing-6);flex-direction:column;display:flex}@media(min-width:1024px){.dashboard-column-right{grid-column:span 2}}.progress-bar-container{background-color:var(--color-slate-100);border-radius:var(--radius-full);width:100%;height:.5rem;overflow:hidden}.dark .progress-bar-container{background-color:var(--color-slate-700)}.progress-bar-fill{border-radius:var(--radius-full);height:.5rem;transition:width var(--transition-normal) var(--ease-in-out)}.progress-bar-emerald{background-color:var(--color-emerald-500)}.progress-bar-blue{background-color:var(--color-blue-500)}.progress-bar-slate{background-color:var(--color-slate-600)}.dark .progress-bar-slate{background-color:var(--color-slate-500)}.module-list{gap:var(--spacing-3);flex-direction:column;display:flex}.module-item{padding:var(--spacing-2) 0;justify-content:space-between;align-items:center;display:flex}.module-info{flex-direction:column;display:flex}.module-name{font-size:var(--text-sm);font-weight:var(--font-medium);color:var(--color-slate-900)}.dark .module-name{color:#fff}.module-version{font-size:var(--text-xs);color:var(--color-slate-500)}.dark .module-version{color:var(--color-slate-400)}.module-badge-active{padding:var(--spacing-1) var(--spacing-2);font-size:var(--text-xs);font-weight:var(--font-medium);background-color:var(--color-green-100);color:var(--color-green-700);border-radius:var(--radius-full)}.dark .module-badge-active{background-color:var(--color-green-900);color:var(--color-green-400)}.module-badge-inactive{padding:var(--spacing-1) var(--spacing-2);font-size:var(--text-xs);font-weight:var(--font-medium);background-color:var(--color-slate-100);color:var(--color-slate-600);border-radius:var(--radius-full)}.dark .module-badge-inactive{background-color:var(--color-slate-700);color:var(--color-slate-400)}.activity-list{gap:var(--spacing-4);flex-direction:column;display:flex}.activity-item{align-items:flex-start;gap:var(--spacing-3);display:flex}.activity-dot{width:.5rem;height:.5rem;margin-top:var(--spacing-2);border-radius:var(--radius-full);background-color:var(--color-blue-500);flex-shrink:0}.activity-content{flex:1;min-width:0}.activity-title{font-size:var(--text-sm);font-weight:var(--font-medium);color:var(--color-slate-900)}.dark .activity-title{color:#fff}.activity-desc{font-size:var(--text-sm);color:var(--color-slate-500)}.dark .activity-desc{color:var(--color-slate-400)}.activity-time{font-size:var(--text-xs);color:var(--color-slate-400);white-space:nowrap}.dark .activity-time{color:var(--color-slate-500)}.alert-list{gap:var(--spacing-3);flex-direction:column;display:flex}.alert-item{align-items:flex-start;gap:var(--spacing-3);padding:var(--spacing-3);border-radius:var(--radius-lg);display:flex}.alert-bg-info{background-color:var(--color-blue-50)}.dark .alert-bg-info{background-color:var(--color-slate-800)}.alert-bg-warning{background-color:var(--color-yellow-50)}.dark .alert-bg-warning{background-color:var(--color-slate-800)}.alert-bg-error{background-color:var(--color-red-50)}.dark .alert-bg-error{background-color:var(--color-slate-800)}.alert-bg-success{background-color:var(--color-green-50)}.dark .alert-bg-success{background-color:var(--color-slate-800)}.alert-dot{width:.5rem;height:.5rem;margin-top:var(--spacing-2);border-radius:var(--radius-full);flex-shrink:0}.alert-dot-info{background-color:var(--color-blue-500)}.alert-dot-warning{background-color:var(--color-yellow-500)}.alert-dot-error{background-color:var(--color-red-500)}.alert-dot-success{background-color:var(--color-green-500)}.alert-content{flex:1}.alert-title{font-size:var(--text-sm);font-weight:var(--font-medium);color:var(--color-slate-900)}.dark .alert-title{color:#fff}.alert-time{font-size:var(--text-xs);color:var(--color-slate-500);margin-top:var(--spacing-1)}.dark .alert-time{color:var(--color-slate-400)}.text-subtitle{font-size:var(--text-sm);color:var(--color-slate-500)}.dark .text-subtitle{color:var(--color-slate-400)}.section-margin{margin-bottom:var(--spacing-6)}.flex-between{justify-content:space-between;align-items:center;display:flex}.flex-between-mb{margin-bottom:var(--spacing-2);justify-content:space-between;align-items:center;display:flex}.resource-label{font-size:var(--text-sm);font-weight:var(--font-medium);color:var(--color-slate-700)}.dark .resource-label{color:var(--color-slate-300)}.resource-value{font-size:var(--text-sm);font-weight:var(--font-medium);color:var(--color-slate-700)}.dark .resource-value{color:var(--color-slate-300)}.icon-stat{width:1rem;height:1rem}.icon-trend{width:.875rem;height:.875rem}@media(max-width:640px){.input-split{flex-wrap:wrap}.input-split input{flex:1;min-width:0}.page-header{flex-direction:column;align-items:flex-start;gap:1rem}.page-actions{justify-content:flex-start;width:100%}}@media(min-width:640px){.responsive-grid{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(min-width:1024px){.responsive-grid,.grid-cols-1-lg-3{grid-template-columns:repeat(3,minmax(0,1fr))}.col-span-1-lg{grid-column:span 1/span 1}.col-span-2-lg{grid-column:span 2/span 2}}@media(max-width:480px){.responsive-grid{grid-template-columns:1fr!important}}@media(max-width:768px){.table-mobile-cards{display:block}.table-mobile-cards>*+*{margin-top:1rem}.table-desktop{display:none!important}}@media(max-width:640px){.modal{max-width:calc(100% - 2rem);max-height:calc(100vh - 2rem);margin-left:1rem;margin-right:1rem;overflow-y:auto}.modal-overlay{align-items:flex-start;padding:2rem 1rem 1rem}.modal-footer{flex-direction:column;gap:.5rem}.modal-footer .btn,.card-account{width:100%}}.sidebar-scrollbar{scrollbar-width:thin;scrollbar-color:transparent transparent;transition:scrollbar-color .3s}.sidebar-scrollbar:hover{scrollbar-color:#d1d5db transparent}.dark .sidebar-scrollbar:hover{scrollbar-color:#4b5563 transparent}.sidebar-scrollbar::-webkit-scrollbar{width:6px}.sidebar-scrollbar::-webkit-scrollbar-track{background:0 0}.sidebar-scrollbar::-webkit-scrollbar-thumb{background:0 0;border-radius:3px;transition:background .3s}.sidebar-scrollbar:hover::-webkit-scrollbar-thumb{background:#d1d5db}.dark .sidebar-scrollbar:hover::-webkit-scrollbar-thumb{background:#4b5563}.sidebar-scrollbar::-webkit-scrollbar-thumb:hover{background:#9ca3af}.dark .sidebar-scrollbar::-webkit-scrollbar-thumb:hover{background:#6b7280}@property --tw-border-spacing-x{syntax:"";inherits:false;initial-value:0}@property --tw-border-spacing-y{syntax:"";inherits:false;initial-value:0}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-pan-x{syntax:"*";inherits:false}@property --tw-pan-y{syntax:"*";inherits:false}@property --tw-pinch-zoom{syntax:"*";inherits:false}@property --tw-scroll-snap-strictness{syntax:"*";inherits:false;initial-value:proximity}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-divide-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-divide-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-gradient-position{syntax:"*";inherits:false}@property --tw-gradient-from{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-via{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-to{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-stops{syntax:"*";inherits:false}@property --tw-gradient-via-stops{syntax:"*";inherits:false}@property --tw-gradient-from-position{syntax:"";inherits:false;initial-value:0%}@property --tw-gradient-via-position{syntax:"";inherits:false;initial-value:50%}@property --tw-gradient-to-position{syntax:"";inherits:false;initial-value:100%}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@property --tw-content{syntax:"*";inherits:false;initial-value:""}@keyframes spin{to{transform:rotate(360deg)}}@keyframes ping{75%,to{opacity:0;transform:scale(2)}}@keyframes pulse{50%{opacity:.5}}@keyframes bounce{0%,to{animation-timing-function:cubic-bezier(.8,0,1,1);transform:translateY(-25%)}50%{animation-timing-function:cubic-bezier(0,0,.2,1);transform:none}}.yarl__fullsize{width:100%;height:100%}.yarl__relative{position:relative}.yarl__portal{position:fixed;inset:0;opacity:0;overflow:hidden;z-index:var(--yarl__portal_zindex,9999);transition:opacity var(--yarl__fade_animation_duration,.25s) var(--yarl__fade_animation_timing_function,ease);pointer-events:auto}.yarl__portal_open{opacity:1}.yarl__container{position:absolute;inset:0;overflow:hidden;background-color:var(--yarl__container_background_color,var(--yarl__color_backdrop,#000));outline:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;touch-action:var(--yarl__controller_touch_action,none);overscroll-behavior:var(--yarl__controller_overscroll_behavior,contain)}.yarl__carousel{display:flex;flex:0 0 auto;height:100%;align-content:center;justify-content:center;align-items:stretch;width:calc(100% + (var(--yarl__carousel_slides_count) - 1)*(100% + var(--yarl__carousel_spacing_px, 0)*1px + var(--yarl__carousel_spacing_percent, 0)*1%));transform:translate(var(--yarl__swipe_offset,0),var(--yarl__pull_offset,0));opacity:var(--yarl__pull_opacity,1)}.yarl__carousel_with_slides{-moz-column-gap:calc(var(--yarl__carousel_spacing_px, 0)*1px + 100/(var(--yarl__carousel_slides_count)*100 + (var(--yarl__carousel_slides_count) - 1)*var(--yarl__carousel_spacing_percent, 0))*var(--yarl__carousel_spacing_percent, 0)*1%);column-gap:calc(var(--yarl__carousel_spacing_px, 0)*1px + 100/(var(--yarl__carousel_slides_count)*100 + (var(--yarl__carousel_slides_count) - 1)*var(--yarl__carousel_spacing_percent, 0))*var(--yarl__carousel_spacing_percent, 0)*1%)}.yarl__flex_center{display:flex;justify-content:center;align-content:center;align-items:center}.yarl__slide{flex:1;position:relative;overflow:hidden;padding:calc(var(--yarl__carousel_padding_px, 0)*1px + 100/(var(--yarl__carousel_slides_count)*100 + (var(--yarl__carousel_slides_count) - 1)*var(--yarl__carousel_spacing_percent, 0))*var(--yarl__carousel_padding_percent, 0)*1%)}[dir=rtl] .yarl__slide{--yarl__direction:-1}.yarl__slide_image{max-width:100%;max-height:100%;-o-object-fit:contain;object-fit:contain;touch-action:var(--yarl__controller_touch_action,none);-webkit-user-select:none;-moz-user-select:none;user-select:none;-webkit-touch-callout:none}.yarl__slide_image_cover{width:100%;height:100%;-o-object-fit:cover;object-fit:cover}.yarl__slide_image_loading{opacity:0}@media screen and (min-width:800px){.yarl__slide_wrapper:not(.yarl__slide_wrapper_interactive) .yarl__slide_image{-webkit-transform:translateZ(0);-webkit-backface-visibility:hidden;-webkit-transform-style:preserve-3d}}.yarl__slide_placeholder{position:absolute;top:50%;left:50%;transform:translate(-50%) translateY(-50%);line-height:0}.yarl__slide_loading{color:var(--yarl__slide_icon_loading_color,var(--yarl__color_button,hsla(0,0%,100%,.8)));animation:yarl__delayed_fadein 1s linear}.yarl__slide_loading line{animation:yarl__stroke_opacity 1s linear infinite}.yarl__slide_loading line:first-of-type{animation-delay:-1.875s}.yarl__slide_loading line:nth-of-type(2){animation-delay:-1.75s}.yarl__slide_loading line:nth-of-type(3){animation-delay:-1.625s}.yarl__slide_loading line:nth-of-type(4){animation-delay:-1.5s}.yarl__slide_loading line:nth-of-type(5){animation-delay:-1.375s}.yarl__slide_loading line:nth-of-type(6){animation-delay:-1.25s}.yarl__slide_loading line:nth-of-type(7){animation-delay:-1.125s}.yarl__slide_loading line:nth-of-type(8){animation-delay:-1s}.yarl__slide_error{width:var(--yarl__slide_icon_error_size,48px);height:var(--yarl__slide_icon_error_size,48px);color:var(--yarl__slide_icon_error_color,red)}@media(prefers-reduced-motion){.yarl__portal,.yarl__slide{transition:unset}.yarl__slide_loading,.yarl__slide_loading line{animation:unset}}.yarl__toolbar{position:absolute;inset:0 0 auto auto;display:flex;justify-content:flex-end;padding:var(--yarl__toolbar_padding,8px)}[dir=rtl] .yarl__toolbar{inset:0 auto auto 0}.yarl__icon{width:var(--yarl__icon_size,32px);height:var(--yarl__icon_size,32px)}.yarl__button{cursor:pointer;-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:var(--yarl__button_background_color,transparent);border:var(--yarl__button_border,0);margin:var(--yarl__button_margin,0);outline:none;line-height:0;padding:var(--yarl__button_padding,8px);color:var(--yarl__color_button,hsla(0,0%,100%,.8));filter:var(--yarl__button_filter,drop-shadow(2px 2px 2px rgba(0,0,0,.8)));-webkit-tap-highlight-color:transparent}.yarl__button:focus{color:var(--yarl__color_button_active,#fff)}.yarl__button:focus:not(:focus-visible){color:var(--yarl__color_button,hsla(0,0%,100%,.8))}.yarl__button:focus-visible{color:var(--yarl__color_button_active,#fff)}@media(hover:hover){.yarl__button:focus-visible:hover,.yarl__button:focus:hover,.yarl__button:hover{color:var(--yarl__color_button_active,#fff)}}.yarl__button:disabled{color:var(--yarl__color_button_disabled,hsla(0,0%,100%,.4));cursor:default}.yarl__navigation_next,.yarl__navigation_prev{position:absolute;top:50%;transform:translateY(-50%);padding:var(--yarl__navigation_button_padding,24px 16px)}.yarl__navigation_prev{left:0}[dir=rtl] .yarl__navigation_prev{left:unset;right:0;transform:translateY(-50%) rotate(180deg)}.yarl__navigation_next{right:0}[dir=rtl] .yarl__navigation_next{left:0;right:unset;transform:translateY(-50%) rotate(180deg)}.yarl__no_scroll{height:100%;overflow:hidden;overscroll-behavior:none}@keyframes yarl__delayed_fadein{0%{opacity:0}80%{opacity:0}to{opacity:1}}@keyframes yarl__stroke_opacity{0%{stroke-opacity:1}to{stroke-opacity:.125}}.yarl__counter{position:var(--yarl__counter_position,absolute);top:var(--yarl__counter_top,0);left:var(--yarl__counter_left,0);bottom:var(--yarl__counter_bottom,unset);right:var(--yarl__counter_right,unset);color:var(--yarl__counter_color,var(--yarl__color_button,hsla(0,0%,100%,.8)));filter:var(--yarl__counter_filter,drop-shadow(2px 2px 2px rgba(0,0,0,.8)));margin:var(--yarl__counter_margin,var(--yarl__toolbar_padding,8px));padding:var(--yarl__counter_padding,var(--yarl__button_padding,8px));line-height:var(--yarl__counter_line_height,var(--yarl__icon_size,32px));-webkit-user-select:var(--yarl__counter_user_select,none);-moz-user-select:var(--yarl__counter_user_select,none);user-select:var(--yarl__counter_user_select,none)}[dir=rtl] .yarl__counter{left:var(--yarl__counter_rtl_left,unset);right:var(--yarl__counter_rtl_right,0)}.yarl__thumbnails{display:flex;height:100%}.yarl__thumbnails_bottom,.yarl__thumbnails_end .yarl__thumbnails_track,.yarl__thumbnails_start .yarl__thumbnails_track,.yarl__thumbnails_top{flex-direction:column}.yarl__thumbnails_wrapper{flex:1;position:relative}.yarl__thumbnails_container{flex:0 0 auto;background-color:var(--yarl__thumbnails_container_background_color,var(--yarl__color_backdrop,#000));padding:var(--yarl__thumbnails_container_padding,16px);position:relative;overflow:hidden;-webkit-user-select:none;-moz-user-select:none;user-select:none;-webkit-touch-callout:none}.yarl__thumbnails_vignette{position:absolute;pointer-events:none;--yarl__thumbnails_vignette_size:12%}@media(min-width:1200px){.yarl__thumbnails_vignette{--yarl__thumbnails_vignette_size:8%}}@media(min-width:2000px){.yarl__thumbnails_vignette{--yarl__thumbnails_vignette_size:5%}}.yarl__thumbnails_bottom .yarl__thumbnails_vignette,.yarl__thumbnails_top .yarl__thumbnails_vignette{height:100%;left:0;right:0;background:linear-gradient(to right,var(--yarl__color_backdrop,#000) 0,transparent var(--yarl__thumbnails_vignette_size,12%) calc(100% - var(--yarl__thumbnails_vignette_size, 12%)),var(--yarl__color_backdrop,#000) 100%)}.yarl__thumbnails_end .yarl__thumbnails_vignette,.yarl__thumbnails_start .yarl__thumbnails_vignette{width:100%;top:0;bottom:0;background:linear-gradient(to bottom,var(--yarl__color_backdrop,#000) 0,transparent var(--yarl__thumbnails_vignette_size,12%) calc(100% - var(--yarl__thumbnails_vignette_size, 12%)),var(--yarl__color_backdrop,#000) 100%)}.yarl__thumbnails_track{outline:none;gap:var(--yarl__thumbnails_thumbnail_gap,16px)}.yarl__thumbnails_thumbnail{flex:0 0 auto;cursor:pointer;-webkit-appearance:none;-moz-appearance:none;appearance:none;background:var(--yarl__thumbnails_thumbnail_background,#000);border-width:var(--yarl__thumbnails_thumbnail_border,1px);border-style:var(--yarl__thumbnails_thumbnail_border_style,solid);border-color:var(--yarl__thumbnails_thumbnail_border_color,var(--yarl__color_button,hsla(0,0%,100%,.8)));border-radius:var(--yarl__thumbnails_thumbnail_border_radius,4px);-webkit-tap-highlight-color:transparent;position:relative;overflow:hidden;padding:var(--yarl__thumbnails_thumbnail_padding,4px);outline:none;width:var(--yarl__thumbnails_thumbnail_width,120px);height:var(--yarl__thumbnails_thumbnail_height,80px);box-sizing:content-box}.yarl__thumbnails_thumbnail_active{border-color:var(--yarl__thumbnails_thumbnail_active_border_color,var(--yarl__color_button_active,#fff))}.yarl__thumbnails_thumbnail_fadein{opacity:0;animation:yarl__thumbnails_thumbnail_fadein var(--yarl__thumbnails_thumbnail_fadein_duration,.5s) ease-in-out var(--yarl__thumbnails_thumbnail_fadein_delay,0s) forwards}.yarl__thumbnails_thumbnail_fadeout{animation:yarl__thumbnails_thumbnail_fadeout var(--yarl__thumbnails_thumbnail_fadeout_duration,.5s) ease-in-out var(--yarl__thumbnails_thumbnail_fadeout_delay,0s) forwards;cursor:unset}.yarl__thumbnails_thumbnail_placeholder{visibility:hidden;cursor:unset}.yarl__thumbnails_thumbnail:focus{box-shadow:var(--yarl__thumbnails_thumbnail_focus_box_shadow,#000 0 0 0 2px,var(--yarl__color_button,hsla(0,0%,100%,.8)) 0 0 0 4px)}.yarl__thumbnails_thumbnail:focus:not(:focus-visible){box-shadow:unset}.yarl__thumbnails_thumbnail:focus-visible{box-shadow:var(--yarl__thumbnails_thumbnail_focus_box_shadow,#000 0 0 0 2px,var(--yarl__color_button,hsla(0,0%,100%,.8)) 0 0 0 4px)}.yarl__thumbnails_thumbnail_icon{color:var(--yarl__thumbnails_thumbnail_icon_color,var(--yarl__color_button,hsla(0,0%,100%,.8)));filter:var(--yarl__thumbnails_thumbnail_icon_filter,drop-shadow(2px 2px 2px rgba(0,0,0,.8)));position:absolute;top:50%;left:50%;transform:translate(-50%) translateY(-50%);width:var(--yarl__thumbnails_thumbnail_icon_size,32px);height:var(--yarl__thumbnails_thumbnail_icon_size,32px)}@keyframes yarl__thumbnails_thumbnail_fadein{0%{opacity:0}to{opacity:1}}@keyframes yarl__thumbnails_thumbnail_fadeout{0%{opacity:1}to{opacity:0}} +@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-pan-x:initial;--tw-pan-y:initial;--tw-pinch-zoom:initial;--tw-scroll-snap-strictness:proximity;--tw-space-y-reverse:0;--tw-space-x-reverse:0;--tw-divide-x-reverse:0;--tw-border-style:solid;--tw-divide-y-reverse:0;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-ease:initial;--tw-content:""}}}@layer theme{:root,:host{--font-sans:"Pretendard", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-red-50:oklch(97.1% .013 17.38);--color-red-100:oklch(93.6% .032 17.717);--color-red-200:oklch(88.5% .062 18.334);--color-red-300:oklch(80.8% .114 19.571);--color-red-400:oklch(70.4% .191 22.216);--color-red-500:oklch(63.7% .237 25.331);--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-red-800:oklch(44.4% .177 26.899);--color-red-900:oklch(39.6% .141 25.723);--color-red-950:oklch(25.8% .092 26.042);--color-orange-50:oklch(98% .016 73.684);--color-orange-100:oklch(95.4% .038 75.164);--color-orange-200:oklch(90.1% .076 70.697);--color-orange-300:oklch(83.7% .128 66.29);--color-orange-400:oklch(75% .183 55.934);--color-orange-500:oklch(70.5% .213 47.604);--color-orange-600:oklch(64.6% .222 41.116);--color-orange-700:oklch(55.3% .195 38.402);--color-orange-800:oklch(47% .157 37.304);--color-orange-900:oklch(40.8% .123 38.172);--color-orange-950:oklch(26.6% .079 36.259);--color-amber-50:oklch(98.7% .022 95.277);--color-amber-100:oklch(96.2% .059 95.617);--color-amber-200:oklch(92.4% .12 95.746);--color-amber-300:oklch(87.9% .169 91.605);--color-amber-400:oklch(82.8% .189 84.429);--color-amber-500:oklch(76.9% .188 70.08);--color-amber-600:oklch(66.6% .179 58.318);--color-amber-700:oklch(55.5% .163 48.998);--color-amber-800:oklch(47.3% .137 46.201);--color-amber-900:oklch(41.4% .112 45.904);--color-amber-950:oklch(27.9% .077 45.635);--color-yellow-50:oklch(98.7% .026 102.212);--color-yellow-100:oklch(97.3% .071 103.193);--color-yellow-200:oklch(94.5% .129 101.54);--color-yellow-300:oklch(90.5% .182 98.111);--color-yellow-400:oklch(85.2% .199 91.936);--color-yellow-500:oklch(79.5% .184 86.047);--color-yellow-600:oklch(68.1% .162 75.834);--color-yellow-700:oklch(55.4% .135 66.442);--color-yellow-800:oklch(47.6% .114 61.907);--color-yellow-900:oklch(42.1% .095 57.708);--color-yellow-950:oklch(28.6% .066 53.813);--color-lime-50:oklch(98.6% .031 120.757);--color-lime-100:oklch(96.7% .067 122.328);--color-lime-200:oklch(93.8% .127 124.321);--color-lime-300:oklch(89.7% .196 126.665);--color-lime-400:oklch(84.1% .238 128.85);--color-lime-500:oklch(76.8% .233 130.85);--color-lime-600:oklch(64.8% .2 131.684);--color-lime-700:oklch(53.2% .157 131.589);--color-lime-800:oklch(45.3% .124 130.933);--color-lime-900:oklch(40.5% .101 131.063);--color-lime-950:oklch(27.4% .072 132.109);--color-green-50:oklch(98.2% .018 155.826);--color-green-100:oklch(96.2% .044 156.743);--color-green-200:oklch(92.5% .084 155.995);--color-green-300:oklch(87.1% .15 154.449);--color-green-400:oklch(79.2% .209 151.711);--color-green-500:oklch(72.3% .219 149.579);--color-green-600:oklch(62.7% .194 149.214);--color-green-700:oklch(52.7% .154 150.069);--color-green-800:oklch(44.8% .119 151.328);--color-green-900:oklch(39.3% .095 152.535);--color-green-950:oklch(26.6% .065 152.934);--color-emerald-50:oklch(97.9% .021 166.113);--color-emerald-100:oklch(95% .052 163.051);--color-emerald-200:oklch(90.5% .093 164.15);--color-emerald-300:oklch(84.5% .143 164.978);--color-emerald-400:oklch(76.5% .177 163.223);--color-emerald-500:oklch(69.6% .17 162.48);--color-emerald-600:oklch(59.6% .145 163.225);--color-emerald-700:oklch(50.8% .118 165.612);--color-emerald-800:oklch(43.2% .095 166.913);--color-emerald-900:oklch(37.8% .077 168.94);--color-emerald-950:oklch(26.2% .051 172.552);--color-teal-50:oklch(98.4% .014 180.72);--color-teal-100:oklch(95.3% .051 180.801);--color-teal-200:oklch(91% .096 180.426);--color-teal-300:oklch(85.5% .138 181.071);--color-teal-400:oklch(77.7% .152 181.912);--color-teal-500:oklch(70.4% .14 182.503);--color-teal-600:oklch(60% .118 184.704);--color-teal-700:oklch(51.1% .096 186.391);--color-teal-800:oklch(43.7% .078 188.216);--color-teal-900:oklch(38.6% .063 188.416);--color-teal-950:oklch(27.7% .046 192.524);--color-cyan-50:oklch(98.4% .019 200.873);--color-cyan-100:oklch(95.6% .045 203.388);--color-cyan-200:oklch(91.7% .08 205.041);--color-cyan-300:oklch(86.5% .127 207.078);--color-cyan-400:oklch(78.9% .154 211.53);--color-cyan-500:oklch(71.5% .143 215.221);--color-cyan-600:oklch(60.9% .126 221.723);--color-cyan-700:oklch(52% .105 223.128);--color-cyan-800:oklch(45% .085 224.283);--color-cyan-900:oklch(39.8% .07 227.392);--color-cyan-950:oklch(30.2% .056 229.695);--color-sky-50:oklch(97.7% .013 236.62);--color-sky-100:oklch(95.1% .026 236.824);--color-sky-200:oklch(90.1% .058 230.902);--color-sky-300:oklch(82.8% .111 230.318);--color-sky-400:oklch(74.6% .16 232.661);--color-sky-500:oklch(68.5% .169 237.323);--color-sky-600:oklch(58.8% .158 241.966);--color-sky-700:oklch(50% .134 242.749);--color-sky-800:oklch(44.3% .11 240.79);--color-sky-900:oklch(39.1% .09 240.876);--color-sky-950:oklch(29.3% .066 243.157);--color-blue-50:oklch(97% .014 254.604);--color-blue-100:oklch(93.2% .032 255.585);--color-blue-200:oklch(88.2% .059 254.128);--color-blue-300:oklch(80.9% .105 251.813);--color-blue-400:oklch(70.7% .165 254.624);--color-blue-500:oklch(62.3% .214 259.815);--color-blue-600:oklch(54.6% .245 262.881);--color-blue-700:oklch(48.8% .243 264.376);--color-blue-800:oklch(42.4% .199 265.638);--color-blue-900:oklch(37.9% .146 265.522);--color-blue-950:oklch(28.2% .091 267.935);--color-indigo-50:oklch(96.2% .018 272.314);--color-indigo-100:oklch(93% .034 272.788);--color-indigo-200:oklch(87% .065 274.039);--color-indigo-300:oklch(78.5% .115 274.713);--color-indigo-400:oklch(67.3% .182 276.935);--color-indigo-500:oklch(58.5% .233 277.117);--color-indigo-600:oklch(51.1% .262 276.966);--color-indigo-700:oklch(45.7% .24 277.023);--color-indigo-800:oklch(39.8% .195 277.366);--color-indigo-900:oklch(35.9% .144 278.697);--color-indigo-950:oklch(25.7% .09 281.288);--color-violet-50:oklch(96.9% .016 293.756);--color-violet-100:oklch(94.3% .029 294.588);--color-violet-200:oklch(89.4% .057 293.283);--color-violet-300:oklch(81.1% .111 293.571);--color-violet-400:oklch(70.2% .183 293.541);--color-violet-500:oklch(60.6% .25 292.717);--color-violet-600:oklch(54.1% .281 293.009);--color-violet-700:oklch(49.1% .27 292.581);--color-violet-800:oklch(43.2% .232 292.759);--color-violet-900:oklch(38% .189 293.745);--color-violet-950:oklch(28.3% .141 291.089);--color-purple-50:oklch(97.7% .014 308.299);--color-purple-100:oklch(94.6% .033 307.174);--color-purple-200:oklch(90.2% .063 306.703);--color-purple-300:oklch(82.7% .119 306.383);--color-purple-400:oklch(71.4% .203 305.504);--color-purple-500:oklch(62.7% .265 303.9);--color-purple-600:oklch(55.8% .288 302.321);--color-purple-700:oklch(49.6% .265 301.924);--color-purple-800:oklch(43.8% .218 303.724);--color-purple-900:oklch(38.1% .176 304.987);--color-purple-950:oklch(29.1% .149 302.717);--color-fuchsia-50:oklch(97.7% .017 320.058);--color-fuchsia-100:oklch(95.2% .037 318.852);--color-fuchsia-200:oklch(90.3% .076 319.62);--color-fuchsia-300:oklch(83.3% .145 321.434);--color-fuchsia-400:oklch(74% .238 322.16);--color-fuchsia-500:oklch(66.7% .295 322.15);--color-fuchsia-600:oklch(59.1% .293 322.896);--color-fuchsia-700:oklch(51.8% .253 323.949);--color-fuchsia-800:oklch(45.2% .211 324.591);--color-fuchsia-900:oklch(40.1% .17 325.612);--color-fuchsia-950:oklch(29.3% .136 325.661);--color-pink-50:oklch(97.1% .014 343.198);--color-pink-100:oklch(94.8% .028 342.258);--color-pink-200:oklch(89.9% .061 343.231);--color-pink-300:oklch(82.3% .12 346.018);--color-pink-400:oklch(71.8% .202 349.761);--color-pink-500:oklch(65.6% .241 354.308);--color-pink-600:oklch(59.2% .249 .584);--color-pink-700:oklch(52.5% .223 3.958);--color-pink-800:oklch(45.9% .187 3.815);--color-pink-900:oklch(40.8% .153 2.432);--color-pink-950:oklch(28.4% .109 3.907);--color-rose-50:oklch(96.9% .015 12.422);--color-rose-100:oklch(94.1% .03 12.58);--color-rose-200:oklch(89.2% .058 10.001);--color-rose-300:oklch(81% .117 11.638);--color-rose-400:oklch(71.2% .194 13.428);--color-rose-500:oklch(64.5% .246 16.439);--color-rose-600:oklch(58.6% .253 17.585);--color-rose-700:oklch(51.4% .222 16.935);--color-rose-800:oklch(45.5% .188 13.697);--color-rose-900:oklch(41% .159 10.272);--color-rose-950:oklch(27.1% .105 12.094);--color-slate-50:oklch(98.4% .003 247.858);--color-slate-100:oklch(96.8% .007 247.896);--color-slate-200:oklch(92.9% .013 255.508);--color-slate-300:oklch(86.9% .022 252.894);--color-slate-400:oklch(70.4% .04 256.788);--color-slate-500:oklch(55.4% .046 257.417);--color-slate-600:oklch(44.6% .043 257.281);--color-slate-700:oklch(37.2% .044 257.287);--color-slate-800:oklch(27.9% .041 260.031);--color-slate-900:oklch(20.8% .042 265.755);--color-slate-950:oklch(12.9% .042 264.695);--color-gray-50:oklch(98.5% .002 247.839);--color-gray-100:oklch(96.7% .003 264.542);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-700:oklch(37.3% .034 259.733);--color-gray-800:oklch(27.8% .033 256.848);--color-gray-900:oklch(21% .034 264.665);--color-gray-950:oklch(13% .028 261.692);--color-zinc-50:oklch(98.5% 0 0);--color-zinc-100:oklch(96.7% .001 286.375);--color-zinc-200:oklch(92% .004 286.32);--color-zinc-300:oklch(87.1% .006 286.286);--color-zinc-400:oklch(70.5% .015 286.067);--color-zinc-500:oklch(55.2% .016 285.938);--color-zinc-600:oklch(44.2% .017 285.786);--color-zinc-700:oklch(37% .013 285.805);--color-zinc-800:oklch(27.4% .006 286.033);--color-zinc-900:oklch(21% .006 285.885);--color-zinc-950:oklch(14.1% .005 285.823);--color-neutral-50:oklch(98.5% 0 0);--color-neutral-100:oklch(97% 0 0);--color-neutral-200:oklch(92.2% 0 0);--color-neutral-300:oklch(87% 0 0);--color-neutral-400:oklch(70.8% 0 0);--color-neutral-500:oklch(55.6% 0 0);--color-neutral-600:oklch(43.9% 0 0);--color-neutral-700:oklch(37.1% 0 0);--color-neutral-800:oklch(26.9% 0 0);--color-neutral-900:oklch(20.5% 0 0);--color-neutral-950:oklch(14.5% 0 0);--color-stone-50:oklch(98.5% .001 106.423);--color-stone-100:oklch(97% .001 106.424);--color-stone-200:oklch(92.3% .003 48.717);--color-stone-300:oklch(86.9% .005 56.366);--color-stone-400:oklch(70.9% .01 56.259);--color-stone-500:oklch(55.3% .013 58.071);--color-stone-600:oklch(44.4% .011 73.639);--color-stone-700:oklch(37.4% .01 67.558);--color-stone-800:oklch(26.8% .007 34.298);--color-stone-900:oklch(21.6% .006 56.043);--color-stone-950:oklch(14.7% .004 49.25);--color-black:#000;--color-white:#fff;--spacing:.25rem;--breakpoint-sm:40rem;--breakpoint-md:48rem;--breakpoint-lg:64rem;--breakpoint-xl:80rem;--breakpoint-2xl:96rem;--container-3xs:16rem;--container-2xs:18rem;--container-xs:20rem;--container-sm:24rem;--container-md:28rem;--container-lg:32rem;--container-xl:36rem;--container-2xl:42rem;--container-3xl:48rem;--container-4xl:56rem;--container-5xl:64rem;--container-6xl:72rem;--container-7xl:80rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height: 1.5 ;--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--text-3xl:1.875rem;--text-3xl--line-height: 1.2 ;--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5 / 2.25);--text-5xl:3rem;--text-5xl--line-height:1;--text-6xl:3.75rem;--text-6xl--line-height:1;--text-7xl:4.5rem;--text-7xl--line-height:1;--text-8xl:6rem;--text-8xl--line-height:1;--text-9xl:8rem;--text-9xl--line-height:1;--font-weight-thin:100;--font-weight-extralight:200;--font-weight-light:300;--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--font-weight-extrabold:800;--font-weight-black:900;--tracking-tighter:-.05em;--tracking-tight:-.025em;--tracking-normal:0em;--tracking-wide:.025em;--tracking-wider:.05em;--tracking-widest:.1em;--leading-tight:1.25;--leading-snug:1.375;--leading-normal:1.5;--leading-relaxed:1.625;--leading-loose:2;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--radius-2xl:1rem;--radius-3xl:1.5rem;--drop-shadow-sm:0 1px 2px #00000026;--drop-shadow-md:0 3px 3px #0000001f;--drop-shadow-lg:0 4px 4px #00000026;--drop-shadow-xl:0 9px 7px #0000001a;--drop-shadow-2xl:0 25px 25px #00000026;--ease-in:cubic-bezier(.4, 0, 1, 1);--ease-out:cubic-bezier(0, 0, .2, 1);--ease-in-out:cubic-bezier(.4, 0, .2, 1);--animate-spin:spin 1s linear infinite;--animate-ping:ping 1s cubic-bezier(0, 0, .2, 1) infinite;--animate-pulse:pulse 2s cubic-bezier(.4, 0, .6, 1) infinite;--animate-bounce:bounce 1s infinite;--blur-sm:8px;--blur-md:12px;--blur-lg:16px;--blur-xl:24px;--blur-2xl:40px;--blur-3xl:64px;--aspect-video:16 / 9;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}body{color:var(--color-gray-900);font-size:.875rem;line-height:1.5}.dark :is(body){color:var(--color-gray-100)}}@layer components{.admin_layout_root{background-color:var(--color-gray-50);height:100vh;display:flex;overflow:hidden}.dark .admin_layout_root{background-color:var(--color-gray-900)}.mobile_overlay{inset:calc(var(--spacing) * 0);z-index:40;background-color:#00000080;position:fixed}.left_sidebar_area{inset-block:calc(var(--spacing) * 0);left:calc(var(--spacing) * 0);z-index:50;width:calc(var(--spacing) * 64);background-color:var(--color-white);flex-direction:column;display:flex;position:relative}.dark .left_sidebar_area{background-color:var(--color-gray-800)}.left_sidebar_area{transition:width .3s}.left_sidebar_area_collapsed{width:calc(var(--spacing) * 0);overflow:hidden}.left_sidebar_area_portable{left:calc(var(--spacing) * 0);z-index:50;width:calc(var(--spacing) * 64);background-color:var(--color-white);transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.3s;flex-direction:column;transition-duration:.3s;display:flex;position:fixed}.dark .left_sidebar_area_portable{background-color:var(--color-gray-800)}.left_sidebar_area_portable{top:4rem;bottom:0;transform:translate(-100%)}.left_sidebar_area_portable_open{transform:translate(0)}.sidebar_header{height:calc(var(--spacing) * 16);padding-inline:calc(var(--spacing) * 4);justify-content:space-between;align-items:center;display:flex}.sidebar_logo{height:calc(var(--spacing) * 10);min-width:calc(var(--spacing) * 0);align-items:center;gap:calc(var(--spacing) * 2);flex:1;display:flex}.logo_image{max-height:calc(var(--spacing) * 10);object-fit:contain;width:auto}.admin_sidebar_menu{padding-top:calc(var(--spacing) * 4);box-shadow:inset 3px 2px 2px var(--tw-shadow-color,#0000000a);flex:1;overflow-y:auto}.admin_sidebar_menu_portable{padding-top:calc(var(--spacing) * 4);flex:1;overflow-y:auto}.admin_sidebar_empty{padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 8);text-align:center;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-400)}.dark .admin_sidebar_empty{color:var(--color-gray-500)}.admin_sidebar_empty_icon{margin-bottom:calc(var(--spacing) * 2);font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));color:var(--color-gray-300)}.dark .admin_sidebar_empty_icon{color:var(--color-gray-600)}.sidebar_footer{border-top-style:var(--tw-border-style);border-top-width:1px;border-color:var(--color-gray-100);padding:calc(var(--spacing) * 3);flex-shrink:0}.dark .sidebar_footer{border-color:var(--color-gray-700)}.sidebar_view_site_link{height:calc(var(--spacing) * 9);align-items:center;gap:calc(var(--spacing) * 2);border-radius:var(--radius-lg);padding-inline:calc(var(--spacing) * 3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-slate-600);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));text-decoration-line:none;display:flex}@media(hover:hover){.sidebar_view_site_link:hover{background-color:var(--color-slate-100);color:var(--color-slate-900)}}.dark .sidebar_view_site_link{color:var(--color-slate-300)}@media(hover:hover){.dark .sidebar_view_site_link:hover{background-color:var(--color-slate-700);color:var(--color-white)}}.sidebar_view_site_icon{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));flex-shrink:0}.sidebar_view_site_label{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.right_content_wrapper{min-width:calc(var(--spacing) * 0);flex-direction:column;flex:1;display:flex}.right_content_area{min-height:calc(var(--spacing) * 0);min-width:calc(var(--spacing) * 0);flex:1;overflow:auto}.main_header{z-index:50;height:calc(var(--spacing) * 16);background-color:var(--color-white);padding-inline:calc(var(--spacing) * 8);flex-shrink:0;justify-content:space-between;align-items:center;display:flex;position:relative}.dark .main_header{background-color:var(--color-gray-800)}.main_header{box-shadow:0 1px 3px #00000014}.header_left{flex:1;align-items:center;display:flex}.header_right{align-items:center;gap:calc(var(--spacing) * 2);display:flex}.menu_toggle_btn{margin-left:calc(var(--spacing) * -2);border-radius:var(--radius-lg);padding:calc(var(--spacing) * 2);color:var(--color-slate-500);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}@media(hover:hover){.menu_toggle_btn:hover{background-color:var(--color-slate-100);color:var(--color-slate-900)}}.dark .menu_toggle_btn{color:var(--color-slate-400)}@media(hover:hover){.dark .menu_toggle_btn:hover{background-color:var(--color-slate-700);color:var(--color-white)}}.menu_toggle_btn_icon{height:calc(var(--spacing) * 5);width:calc(var(--spacing) * 5)}.user_avatar_section{cursor:pointer;align-items:center;gap:calc(var(--spacing) * 3);display:flex;position:relative}.user_avatar{height:calc(var(--spacing) * 9);width:calc(var(--spacing) * 9);background-color:var(--color-gray-200);border-radius:3.40282e38px;justify-content:center;align-items:center;display:flex;overflow:hidden}.dark .user_avatar{background-color:var(--color-gray-600)}.user_avatar_img{object-fit:cover;width:100%;height:100%}.user_info{display:none}@media(min-width:48rem){.user_info{display:block}}.mobile_header_portable{z-index:50;height:calc(var(--spacing) * 16);background-color:var(--color-white);padding-inline:calc(var(--spacing) * 4);justify-content:space-between;align-items:center;display:flex;position:relative}.dark .mobile_header_portable{background-color:var(--color-gray-800)}.mobile_header_portable{box-shadow:0 1px 3px #00000014}.mobile_header_left{align-items:center;gap:calc(var(--spacing) * 3);display:flex}.mobile_header_right{align-items:center;gap:calc(var(--spacing) * 2);flex-shrink:0;display:flex}.mobile_logo{min-width:calc(var(--spacing) * 0);align-items:center;gap:calc(var(--spacing) * 2);flex:1;display:flex}.mobile_logo_image{height:calc(var(--spacing) * 8);object-fit:contain;width:auto;max-width:100%}.main_content{min-height:fit-content}.layout_warnings{padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2)}.text-primary{color:var(--color-gray-900)}.dark .text-primary{color:var(--color-white)}.text-secondary{color:var(--color-gray-600)}.dark .text-secondary{color:var(--color-gray-400)}.text-muted,.dark .text-muted{color:var(--color-gray-500)}.text-tertiary{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-gray-500)}.dark .text-tertiary{color:var(--color-gray-400)}.text-label{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-600)}.dark .text-label{color:var(--color-gray-400)}.text-label-subtle{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-500)}.dark .text-label-subtle{color:var(--color-gray-400)}.text-heading{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-900)}.dark .text-heading{color:var(--color-white)}.section-heading{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-gray-900)}.dark .section-heading{color:var(--color-white)}.section-title{margin-bottom:calc(var(--spacing) * 1);font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-gray-900)}.dark .section-title{color:var(--color-white)}.section-description{margin-bottom:calc(var(--spacing) * 6);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-500)}.dark .section-description{color:var(--color-gray-400)}.card-title{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-gray-900)}.dark .card-title{color:var(--color-white)}.card-title-aside{justify-content:space-between;align-items:center;gap:calc(var(--spacing) * 2);display:flex}.card-description{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-500)}.dark .card-description{color:var(--color-gray-400)}.admin-card>.card-title{margin-bottom:calc(var(--spacing) * 4)}.admin-card>.card-title:has(+.card-description){margin-bottom:calc(var(--spacing) * 0)}.admin-card>.card-description{margin-bottom:calc(var(--spacing) * 4)}.card-header{margin-bottom:calc(var(--spacing) * 0);align-items:center;gap:calc(var(--spacing) * 2);--tw-leading:1;line-height:1;display:flex}.card-header>*{--tw-leading:1;line-height:1}.text-body{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-700)}.dark .text-body{color:var(--color-gray-300)}.icon-muted{color:var(--color-gray-400)}.dark .icon-muted{color:var(--color-gray-500)}.bg-success{background-color:var(--color-green-100)}.dark .bg-success{background-color:var(--color-green-900)}.bg-success-light{background-color:var(--color-green-50)}.dark .bg-success-light{background-color:#0d542b33}@supports (color:color-mix(in lab,red,red)){.dark .bg-success-light{background-color:color-mix(in oklab,var(--color-green-900) 20%,transparent)}}.bg-success-solid{background-color:var(--color-green-600)}.dark .bg-success-solid{background-color:var(--color-green-500)}.bg-error{background-color:var(--color-red-100)}.dark .bg-error{background-color:var(--color-red-900)}.bg-error-light{background-color:var(--color-red-50)}.dark .bg-error-light{background-color:#82181a33}@supports (color:color-mix(in lab,red,red)){.dark .bg-error-light{background-color:color-mix(in oklab,var(--color-red-900) 20%,transparent)}}.bg-error-solid{background-color:var(--color-red-600)}.dark .bg-error-solid{background-color:var(--color-red-500)}.bg-warning{background-color:var(--color-yellow-100)}.dark .bg-warning{background-color:var(--color-yellow-900)}.bg-warning-light{background-color:var(--color-yellow-50)}.dark .bg-warning-light{background-color:#733e0a33}@supports (color:color-mix(in lab,red,red)){.dark .bg-warning-light{background-color:color-mix(in oklab,var(--color-yellow-900) 20%,transparent)}}.bg-warning-solid{background-color:var(--color-yellow-600)}.dark .bg-warning-solid{background-color:var(--color-yellow-500)}.bg-info{background-color:var(--color-blue-100)}.dark .bg-info{background-color:var(--color-blue-900)}.bg-info-light{background-color:var(--color-blue-50)}.dark .bg-info-light{background-color:#1c398e33}@supports (color:color-mix(in lab,red,red)){.dark .bg-info-light{background-color:color-mix(in oklab,var(--color-blue-900) 20%,transparent)}}.bg-info-solid{background-color:var(--color-blue-600)}.dark .bg-info-solid{background-color:var(--color-blue-500)}.bg-ready{background-color:var(--color-cyan-100)}.dark .bg-ready{background-color:var(--color-cyan-900)}.bg-ready-light{background-color:var(--color-cyan-50)}.dark .bg-ready-light{background-color:#104e6433}@supports (color:color-mix(in lab,red,red)){.dark .bg-ready-light{background-color:color-mix(in oklab,var(--color-cyan-900) 20%,transparent)}}.bg-ready-solid{background-color:var(--color-cyan-600)}.dark .bg-ready-solid{background-color:var(--color-cyan-400)}.bg-progress{background-color:var(--color-indigo-100)}.dark .bg-progress{background-color:var(--color-indigo-900)}.bg-progress-light{background-color:var(--color-indigo-50)}.dark .bg-progress-light{background-color:#312c8533}@supports (color:color-mix(in lab,red,red)){.dark .bg-progress-light{background-color:color-mix(in oklab,var(--color-indigo-900) 20%,transparent)}}.bg-progress-solid{background-color:var(--color-indigo-600)}.dark .bg-progress-solid{background-color:var(--color-indigo-400)}.bg-complete{background-color:var(--color-pink-100)}.dark .bg-complete{background-color:var(--color-pink-900)}.bg-complete-light{background-color:var(--color-pink-50)}.dark .bg-complete-light{background-color:#86104333}@supports (color:color-mix(in lab,red,red)){.dark .bg-complete-light{background-color:color-mix(in oklab,var(--color-pink-900) 20%,transparent)}}.bg-complete-solid{background-color:var(--color-pink-600)}.dark .bg-complete-solid{background-color:var(--color-pink-400)}.bg-hold{background-color:var(--color-gray-100)}.dark .bg-hold{background-color:var(--color-gray-800)}.bg-hold-light{background-color:var(--color-gray-50)}.dark .bg-hold-light{background-color:#1e293980}@supports (color:color-mix(in lab,red,red)){.dark .bg-hold-light{background-color:color-mix(in oklab,var(--color-gray-800) 50%,transparent)}}.bg-hold-solid{background-color:var(--color-gray-500)}.dark .bg-hold-solid{background-color:var(--color-gray-400)}.text-success{color:var(--color-green-600)}.dark .text-success{color:var(--color-green-400)}.text-error{color:var(--color-red-600)}.dark .text-error{color:var(--color-red-400)}.text-warning{color:var(--color-yellow-600)}.dark .text-warning{color:var(--color-yellow-400)}.text-info{color:var(--color-blue-600)}.dark .text-info{color:var(--color-blue-400)}.text-ready{color:var(--color-cyan-600)}.dark .text-ready{color:var(--color-cyan-400)}.text-progress{color:var(--color-indigo-600)}.dark .text-progress{color:var(--color-indigo-400)}.text-complete{color:var(--color-pink-600)}.dark .text-complete{color:var(--color-pink-400)}.text-hold{color:var(--color-gray-500)}.dark .text-hold{color:var(--color-gray-400)}.text-success-light{color:var(--color-white)}.dark .text-success-light{color:var(--color-green-900)}.text-error-light{color:var(--color-white)}.dark .text-error-light{color:var(--color-red-900)}.text-warning-light{color:var(--color-white)}.dark .text-warning-light{color:var(--color-yellow-900)}.text-info-light{color:var(--color-white)}.dark .text-info-light{color:var(--color-blue-900)}.text-ready-light{color:var(--color-white)}.dark .text-ready-light{color:var(--color-cyan-900)}.text-progress-light{color:var(--color-white)}.dark .text-progress-light{color:var(--color-indigo-900)}.text-complete-light{color:var(--color-white)}.dark .text-complete-light{color:var(--color-pink-900)}.text-hold-light{color:var(--color-white)}.dark .text-hold-light{color:var(--color-gray-900)}.border-success{border-color:var(--color-green-200)}.dark .border-success{border-color:var(--color-green-800)}.border-error{border-color:var(--color-red-200)}.dark .border-error{border-color:var(--color-red-800)}.border-warning{border-color:var(--color-yellow-200)}.dark .border-warning{border-color:var(--color-yellow-800)}.border-info{border-color:var(--color-blue-200)}.dark .border-info{border-color:var(--color-blue-800)}.border-ready{border-color:var(--color-cyan-200)}.dark .border-ready{border-color:var(--color-cyan-800)}.border-progress{border-color:var(--color-indigo-200)}.dark .border-progress{border-color:var(--color-indigo-800)}.border-complete{border-color:var(--color-pink-200)}.dark .border-complete{border-color:var(--color-pink-800)}.border-hold{border-color:var(--color-gray-300)}.dark .border-hold{border-color:var(--color-gray-600)}.btn-success{background-color:var(--color-green-600);color:var(--color-white)}@media(hover:hover){.btn-success:hover{background-color:var(--color-green-700)}}.dark .btn-success{background-color:var(--color-green-500)}@media(hover:hover){.dark .btn-success:hover{background-color:var(--color-green-600)}}.btn-danger{background-color:var(--color-red-600);color:var(--color-white)}@media(hover:hover){.btn-danger:hover{background-color:var(--color-red-700)}}.dark .btn-danger{background-color:var(--color-red-500)}@media(hover:hover){.dark .btn-danger:hover{background-color:var(--color-red-600)}}.btn-warning{background-color:var(--color-yellow-600);color:var(--color-white)}@media(hover:hover){.btn-warning:hover{background-color:var(--color-yellow-700)}}.dark .btn-warning{background-color:var(--color-yellow-500)}@media(hover:hover){.dark .btn-warning:hover{background-color:var(--color-yellow-600)}}.btn-info{background-color:var(--color-blue-600);color:var(--color-white)}@media(hover:hover){.btn-info:hover{background-color:var(--color-blue-700)}}.dark .btn-info{background-color:var(--color-blue-500)}@media(hover:hover){.dark .btn-info:hover{background-color:var(--color-blue-600)}}.btn-neutral{background-color:var(--color-gray-600);color:var(--color-white)}@media(hover:hover){.btn-neutral:hover{background-color:var(--color-gray-700)}}.dark .btn-neutral{background-color:var(--color-gray-500)}@media(hover:hover){.dark .btn-neutral:hover{background-color:var(--color-gray-600)}}.btn-sm{padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 1.5)}.button{cursor:pointer;justify-content:center;align-items:center;display:inline-flex}.button:disabled{cursor:not-allowed}.btn{cursor:pointer;justify-content:center;align-items:center;gap:calc(var(--spacing) * 2);border-radius:var(--radius-lg);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;transition-duration:.2s;display:inline-flex}.btn:disabled{cursor:not-allowed;opacity:.5}.btn-primary{background-color:var(--color-gray-900);color:var(--color-white)}@media(hover:hover){.btn-primary:hover{background-color:var(--color-gray-800)}}.dark .btn-primary{background-color:var(--color-white);color:var(--color-gray-900)}@media(hover:hover){.dark .btn-primary:hover{background-color:var(--color-gray-100)}}.btn-secondary{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-300);background-color:var(--color-white);color:var(--color-gray-700)}@media(hover:hover){.btn-secondary:hover{background-color:var(--color-gray-50)}}.dark .btn-secondary{border-color:var(--color-gray-600);background-color:var(--color-gray-800);color:var(--color-gray-300)}@media(hover:hover){.dark .btn-secondary:hover{background-color:var(--color-gray-700)}}.btn-outline{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-300);color:var(--color-gray-700);background-color:#0000}@media(hover:hover){.btn-outline:hover{background-color:var(--color-gray-50)}}.dark .btn-outline{border-color:var(--color-gray-600);color:var(--color-gray-300)}@media(hover:hover){.dark .btn-outline:hover{background-color:var(--color-gray-700)}}.btn-ghost{color:var(--color-gray-600);background-color:#0000}@media(hover:hover){.btn-ghost:hover{background-color:var(--color-gray-100)}}.dark .btn-ghost{color:var(--color-gray-400)}@media(hover:hover){.dark .btn-ghost:hover{background-color:var(--color-gray-700)}}.btn-icon{border-radius:var(--radius-lg);padding:calc(var(--spacing) * 2);color:var(--color-gray-500);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}@media(hover:hover){.btn-icon:hover{background-color:var(--color-gray-100)}}.dark .btn-icon{color:var(--color-gray-400)}@media(hover:hover){.dark .btn-icon:hover{background-color:var(--color-gray-700)}}.btn-preset{background-color:var(--color-gray-100);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 1.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-700);border-radius:3.40282e38px}.dark .btn-preset{background-color:var(--color-gray-700);color:var(--color-gray-300)}.btn-date{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 1.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-600)}.dark .btn-date{border-color:var(--color-gray-700);background-color:var(--color-gray-800);color:var(--color-gray-400)}.btn-date.active{border-color:var(--color-gray-900);background-color:var(--color-gray-900);color:var(--color-white)}.dark .btn-date.active{border-color:var(--color-white);background-color:var(--color-white);color:var(--color-gray-900)}.btn-badge{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 1.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-600);border-radius:3.40282e38px}.dark .btn-badge{border-color:var(--color-gray-700);background-color:var(--color-gray-800);color:var(--color-gray-400)}.btn-badge.active{border-color:var(--color-gray-900);color:var(--color-gray-900)}.dark .btn-badge.active{border-color:var(--color-white);color:var(--color-blue-400)}.btn-group{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);display:inline-flex;overflow:hidden}.dark .btn-group{border-color:var(--color-gray-700)}.btn-group-item{border-right-style:var(--tw-border-style);border-right-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 1.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-600);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}@media(hover:hover){.btn-group-item:hover{background-color:var(--color-gray-50)}}.dark .btn-group-item{border-color:var(--color-gray-700);background-color:var(--color-gray-800);color:var(--color-gray-400)}@media(hover:hover){.dark .btn-group-item:hover{background-color:var(--color-gray-700)}}.btn-group-item:last-child{border-right-style:var(--tw-border-style);border-right-width:0}.btn-group-item.active{background-color:var(--color-gray-900);color:var(--color-white)}.dark .btn-group-item.active{background-color:var(--color-white);color:var(--color-gray-900)}.btn-badge-green{border-color:var(--color-green-500);background-color:var(--color-green-50);color:var(--color-green-600)}.dark .btn-badge-green{border-color:var(--color-green-400);background-color:#0d542b33}@supports (color:color-mix(in lab,red,red)){.dark .btn-badge-green{background-color:color-mix(in oklab,var(--color-green-900) 20%,transparent)}}.dark .btn-badge-green{color:var(--color-green-400)}.btn-badge-blue{border-color:var(--color-blue-500);background-color:var(--color-blue-50);color:var(--color-blue-600)}.dark .btn-badge-blue{border-color:var(--color-blue-400);background-color:#1c398e33}@supports (color:color-mix(in lab,red,red)){.dark .btn-badge-blue{background-color:color-mix(in oklab,var(--color-blue-900) 20%,transparent)}}.dark .btn-badge-blue{color:var(--color-blue-400)}.btn-badge-cyan{border-color:var(--color-cyan-500);background-color:var(--color-cyan-50);color:var(--color-cyan-600)}.dark .btn-badge-cyan{border-color:var(--color-cyan-400);background-color:#104e6433}@supports (color:color-mix(in lab,red,red)){.dark .btn-badge-cyan{background-color:color-mix(in oklab,var(--color-cyan-900) 20%,transparent)}}.dark .btn-badge-cyan{color:var(--color-cyan-400)}.btn-badge-indigo{border-color:var(--color-indigo-500);background-color:var(--color-indigo-50);color:var(--color-indigo-600)}.dark .btn-badge-indigo{border-color:var(--color-indigo-400);background-color:#312c8533}@supports (color:color-mix(in lab,red,red)){.dark .btn-badge-indigo{background-color:color-mix(in oklab,var(--color-indigo-900) 20%,transparent)}}.dark .btn-badge-indigo{color:var(--color-indigo-400)}.btn-badge-pink{border-color:var(--color-pink-500);background-color:var(--color-pink-50);color:var(--color-pink-600)}.dark .btn-badge-pink{border-color:var(--color-pink-400);background-color:#86104333}@supports (color:color-mix(in lab,red,red)){.dark .btn-badge-pink{background-color:color-mix(in oklab,var(--color-pink-900) 20%,transparent)}}.dark .btn-badge-pink{color:var(--color-pink-400)}.btn-badge-red{border-color:var(--color-red-500);background-color:var(--color-red-50);color:var(--color-red-600)}.dark .btn-badge-red{border-color:var(--color-red-400);background-color:#82181a33}@supports (color:color-mix(in lab,red,red)){.dark .btn-badge-red{background-color:color-mix(in oklab,var(--color-red-900) 20%,transparent)}}.dark .btn-badge-red{color:var(--color-red-400)}.btn-badge-gray{border-color:var(--color-gray-300);background-color:var(--color-gray-50);color:var(--color-gray-600)}.dark .btn-badge-gray{border-color:var(--color-gray-600);background-color:var(--color-gray-800);color:var(--color-gray-400)}.btn-badge-yellow{border-color:var(--color-yellow-500);background-color:var(--color-yellow-50);color:var(--color-yellow-600)}.dark .btn-badge-yellow{border-color:var(--color-yellow-400);background-color:#733e0a33}@supports (color:color-mix(in lab,red,red)){.dark .btn-badge-yellow{background-color:color-mix(in oklab,var(--color-yellow-900) 20%,transparent)}}.dark .btn-badge-yellow{color:var(--color-yellow-400)}.btn-badge-orange{border-color:var(--color-orange-500);background-color:var(--color-orange-50);color:var(--color-orange-600)}.dark .btn-badge-orange{border-color:var(--color-orange-400);background-color:#7e2a0c33}@supports (color:color-mix(in lab,red,red)){.dark .btn-badge-orange{background-color:color-mix(in oklab,var(--color-orange-900) 20%,transparent)}}.dark .btn-badge-orange{color:var(--color-orange-400)}.input{height:calc(var(--spacing) * 9);border-radius:var(--radius-lg);background-color:var(--color-gray-100);width:100%;padding-inline:calc(var(--spacing) * 3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900)}.input::placeholder{color:var(--color-gray-400)}.input:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--color-blue-500);--tw-outline-style:none;outline-style:none}.dark .input{background-color:var(--color-gray-700);color:var(--color-gray-100)}.dark .input::placeholder{color:var(--color-gray-500)}.input:disabled{cursor:not-allowed;background-color:var(--color-gray-100);color:var(--color-gray-400)}.dark .input:disabled{background-color:var(--color-gray-800);color:var(--color-gray-500)}.input-tag{border-radius:var(--radius-lg);background-color:var(--color-gray-100);width:100%;padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 1.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900)}.input-tag:focus-within{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--color-blue-500);--tw-outline-style:none;outline-style:none}.dark .input-tag{background-color:var(--color-gray-700);color:var(--color-gray-100)}.input-tag{min-height:2.25rem}.textarea{border-radius:var(--radius-lg);background-color:var(--color-gray-100);width:100%;padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900)}.textarea::placeholder{color:var(--color-gray-400)}.textarea:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--color-blue-500);--tw-outline-style:none;outline-style:none}.dark .textarea{background-color:var(--color-gray-700);color:var(--color-gray-100)}.dark .textarea::placeholder{color:var(--color-gray-500)}.textarea:disabled{cursor:not-allowed;background-color:var(--color-gray-200);color:var(--color-gray-400)}.dark .textarea:disabled{background-color:var(--color-gray-700);color:var(--color-gray-500)}.input-readonly{cursor:default;color:var(--color-gray-500)}.dark .input-readonly{color:var(--color-gray-400)}.input-w-xs{width:calc(var(--spacing) * 20)}.input-w-sm{width:calc(var(--spacing) * 32)}.input-w-md{width:calc(var(--spacing) * 48)}.input-w-lg{width:calc(var(--spacing) * 64)}.input-w-xl{width:calc(var(--spacing) * 80)}.input-w-auto{width:auto}.input-search-container{border-radius:var(--radius-lg);background-color:var(--color-gray-100);align-items:center;display:flex;overflow:hidden}.dark .input-search-container{background-color:var(--color-gray-700)}.input-search-container .input-search-icon{padding-left:calc(var(--spacing) * 3);color:var(--color-gray-400)}.dark :is(.input-search-container .input-search-icon){color:var(--color-gray-500)}.input-search-container input{padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900);background-color:#0000;flex:1}.input-search-container input::placeholder{color:var(--color-gray-400)}.input-search-container input:focus{--tw-outline-style:none;outline-style:none}.dark :is(.input-search-container input){color:var(--color-gray-100)}.dark :is(.input-search-container input)::placeholder{color:var(--color-gray-500)}.input-search-container .input-search-dropdown{cursor:pointer;border-left-style:var(--tw-border-style);border-left-width:1px;border-color:var(--color-gray-200);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-600)}@media(hover:hover){.input-search-container .input-search-dropdown:hover{background-color:var(--color-gray-50)}}.dark :is(.input-search-container .input-search-dropdown){border-color:var(--color-gray-700);color:var(--color-gray-400)}@media(hover:hover){.dark :is(.input-search-container .input-search-dropdown):hover{background-color:var(--color-gray-700)}}.select{height:calc(var(--spacing) * 9);cursor:pointer;appearance:none;border-radius:var(--radius-lg);background-color:var(--color-gray-100);width:100%;padding-inline:calc(var(--spacing) * 3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900)}.select:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--color-blue-500);--tw-outline-style:none;outline-style:none}.dark .select{background-color:var(--color-gray-700);color:var(--color-gray-100)}.select:disabled{cursor:not-allowed;background-color:var(--color-gray-200);color:var(--color-gray-400)}.dark .select:disabled{background-color:var(--color-gray-700);color:var(--color-gray-500)}.select{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");background-position:right .5rem center;background-repeat:no-repeat;background-size:1.5em 1.5em;padding-right:2.5rem}.dark .select{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%239ca3af' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e")}.select-composite{height:calc(var(--spacing) * 9);border-radius:var(--radius-lg);background-color:var(--color-gray-100);padding-inline:calc(var(--spacing) * 3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900)}.select-composite:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--color-blue-500);--tw-outline-style:none;outline-style:none}.dark .select-composite{background-color:var(--color-gray-700);color:var(--color-gray-100)}.select-composite:disabled{cursor:not-allowed;background-color:var(--color-gray-200);color:var(--color-gray-400)}.dark .select-composite:disabled{background-color:var(--color-gray-700);color:var(--color-gray-500)}.select-composite-sm{height:calc(var(--spacing) * 8);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.select-w-sm{width:calc(var(--spacing) * 32)}.select-w-md{width:calc(var(--spacing) * 48)}.select-w-lg{width:calc(var(--spacing) * 64)}.input-split{align-items:center;gap:calc(var(--spacing) * 2);display:flex}.input-split input{border-radius:var(--radius-lg);background-color:var(--color-gray-100);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2);text-align:center;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900)}.input-split input:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--color-blue-500);--tw-outline-style:none;outline-style:none}.dark :is(.input-split input){background-color:var(--color-gray-700);color:var(--color-gray-100)}.input-split .input-split-separator{color:var(--color-gray-400)}.dark :is(.input-split .input-split-separator){color:var(--color-gray-500)}.tag-input-container{align-items:center;gap:calc(var(--spacing) * 2);flex-wrap:wrap;display:flex}.tag{align-items:center;gap:calc(var(--spacing) * 1);background-color:var(--color-gray-100);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 1);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-700);border-radius:3.40282e38px;display:inline-flex}.dark .tag{background-color:var(--color-gray-700);color:var(--color-gray-300)}.tag .tag-remove{cursor:pointer;color:var(--color-gray-400)}@media(hover:hover){.tag .tag-remove:hover{color:var(--color-gray-600)}}.dark :is(.tag .tag-remove){color:var(--color-gray-500)}@media(hover:hover){.dark :is(.tag .tag-remove):hover{color:var(--color-gray-300)}}:where(.form-group>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 1.5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 1.5) * calc(1 - var(--tw-space-y-reverse)))}.textarea-sm{min-height:5rem}.textarea-md{min-height:8rem}.textarea-lg{min-height:12rem}.input-error{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-red-500)}.input-error:focus{--tw-ring-color:var(--color-red-500)}.dark .input-error{border-color:var(--color-red-500)}.input-success{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-green-500)}.input-success:focus{--tw-ring-color:var(--color-green-500)}.dark .input-success{border-color:var(--color-green-500)}.form-field-error .input,.form-field-error input[type],.form-field-error select,.form-field-error textarea{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-red-500)}:is(.form-field-error .input,.form-field-error input[type],.form-field-error select,.form-field-error textarea):focus{--tw-ring-color:var(--color-red-500)}.dark :is(.form-field-error .input,.form-field-error input[type],.form-field-error select,.form-field-error textarea){border-color:var(--color-red-500)}.form-error{margin-top:calc(var(--spacing) * 1);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-red-500);display:block}.dark .form-error{color:var(--color-red-400)}.form-success{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-green-600)}.dark .form-success{color:var(--color-green-400)}.checkbox{height:calc(var(--spacing) * 4);width:calc(var(--spacing) * 4);cursor:pointer;border-color:var(--color-gray-300);color:var(--color-gray-900);border-radius:.25rem}.checkbox:focus{--tw-ring-color:var(--color-gray-500)}.dark .checkbox{border-color:var(--color-gray-600);background-color:var(--color-gray-800);color:var(--color-blue-400)}.checkbox:checked{border-color:var(--color-gray-900);background-color:var(--color-gray-900)}.dark .checkbox:checked{border-color:var(--color-white);background-color:var(--color-white)}.radio{height:calc(var(--spacing) * 4);width:calc(var(--spacing) * 4);cursor:pointer;border-color:var(--color-gray-300);color:var(--color-blue-600);flex-shrink:0}.radio:focus{--tw-ring-color:var(--color-blue-500)}.dark .radio{border-color:var(--color-gray-600);background-color:var(--color-gray-800);color:var(--color-blue-400)}.dark .radio:focus{--tw-ring-color:var(--color-blue-400)}.radio:checked{border-color:var(--color-blue-600)}.dark .radio:checked{border-color:var(--color-blue-400)}.toggle{height:calc(var(--spacing) * 6);width:calc(var(--spacing) * 11);cursor:pointer;transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));border-radius:3.40282e38px;align-items:center;display:inline-flex;position:relative}.toggle-track{inset:calc(var(--spacing) * 0);background-color:var(--color-gray-200);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));border-radius:3.40282e38px;position:absolute}.dark .toggle-track{background-color:var(--color-gray-700)}.toggle.active .toggle-track{background-color:var(--color-blue-600)}.toggle-handle{left:calc(var(--spacing) * .5);height:calc(var(--spacing) * 5);width:calc(var(--spacing) * 5);background-color:var(--color-white);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));border-radius:3.40282e38px;position:absolute}.toggle.active .toggle-handle{--tw-translate-x:calc(var(--spacing) * 5);translate:var(--tw-translate-x) var(--tw-translate-y)}.badge{align-items:center;gap:calc(var(--spacing) * 1);border-radius:var(--radius-md);padding-inline:calc(var(--spacing) * 2);padding-block:calc(var(--spacing) * .5);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);display:inline-flex}.badge-green{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-green-200);background-color:var(--color-green-100);color:var(--color-green-700)}.dark .badge-green{border-color:var(--color-green-800);background-color:#0d542b4d}@supports (color:color-mix(in lab,red,red)){.dark .badge-green{background-color:color-mix(in oklab,var(--color-green-900) 30%,transparent)}}.dark .badge-green{color:var(--color-green-400)}.badge-blue{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-blue-200);background-color:var(--color-blue-100);color:var(--color-blue-700)}.dark .badge-blue{border-color:var(--color-blue-800);background-color:#1c398e4d}@supports (color:color-mix(in lab,red,red)){.dark .badge-blue{background-color:color-mix(in oklab,var(--color-blue-900) 30%,transparent)}}.dark .badge-blue{color:var(--color-blue-400)}.badge-cyan{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-cyan-200);background-color:var(--color-cyan-100);color:var(--color-cyan-700)}.dark .badge-cyan{border-color:var(--color-cyan-800);background-color:#104e644d}@supports (color:color-mix(in lab,red,red)){.dark .badge-cyan{background-color:color-mix(in oklab,var(--color-cyan-900) 30%,transparent)}}.dark .badge-cyan{color:var(--color-cyan-400)}.badge-indigo{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-indigo-200);background-color:var(--color-indigo-100);color:var(--color-indigo-700)}.dark .badge-indigo{border-color:var(--color-indigo-800);background-color:#312c854d}@supports (color:color-mix(in lab,red,red)){.dark .badge-indigo{background-color:color-mix(in oklab,var(--color-indigo-900) 30%,transparent)}}.dark .badge-indigo{color:var(--color-indigo-400)}.badge-pink{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-pink-200);background-color:var(--color-pink-100);color:var(--color-pink-700)}.dark .badge-pink{border-color:var(--color-pink-800);background-color:#8610434d}@supports (color:color-mix(in lab,red,red)){.dark .badge-pink{background-color:color-mix(in oklab,var(--color-pink-900) 30%,transparent)}}.dark .badge-pink{color:var(--color-pink-400)}.badge-red{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-red-200);background-color:var(--color-red-100);color:var(--color-red-700)}.dark .badge-red{border-color:var(--color-red-800);background-color:#82181a4d}@supports (color:color-mix(in lab,red,red)){.dark .badge-red{background-color:color-mix(in oklab,var(--color-red-900) 30%,transparent)}}.dark .badge-red{color:var(--color-red-400)}.badge-gray{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-gray-100);color:var(--color-gray-600)}.dark .badge-gray{border-color:var(--color-gray-700);background-color:var(--color-gray-800);color:var(--color-gray-400)}.badge-yellow{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-yellow-200);background-color:var(--color-yellow-100);color:var(--color-yellow-700)}.dark .badge-yellow{border-color:var(--color-yellow-800);background-color:#733e0a4d}@supports (color:color-mix(in lab,red,red)){.dark .badge-yellow{background-color:color-mix(in oklab,var(--color-yellow-900) 30%,transparent)}}.dark .badge-yellow{color:var(--color-yellow-400)}.badge-status{align-items:center;gap:calc(var(--spacing) * 1.5);padding-inline:calc(var(--spacing) * 2.5);padding-block:calc(var(--spacing) * 1);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);border-radius:3.40282e38px;display:inline-flex}.badge-status-success{background-color:var(--color-green-50);color:var(--color-green-600)}.dark .badge-status-success{background-color:#0d542b33}@supports (color:color-mix(in lab,red,red)){.dark .badge-status-success{background-color:color-mix(in oklab,var(--color-green-900) 20%,transparent)}}.dark .badge-status-success{color:var(--color-green-400)}.badge-status-processing{background-color:var(--color-blue-50);color:var(--color-blue-600)}.dark .badge-status-processing{background-color:#1c398e33}@supports (color:color-mix(in lab,red,red)){.dark .badge-status-processing{background-color:color-mix(in oklab,var(--color-blue-900) 20%,transparent)}}.dark .badge-status-processing{color:var(--color-blue-400)}.badge-status-error{background-color:var(--color-red-50);color:var(--color-red-600)}.dark .badge-status-error{background-color:#82181a33}@supports (color:color-mix(in lab,red,red)){.dark .badge-status-error{background-color:color-mix(in oklab,var(--color-red-900) 20%,transparent)}}.dark .badge-status-error{color:var(--color-red-400)}.badge-status-expired{background-color:var(--color-gray-50);color:var(--color-gray-500)}.dark .badge-status-expired{background-color:var(--color-gray-800);color:var(--color-gray-400)}.search-bar{gap:calc(var(--spacing) * 3);flex-direction:column;display:flex}@media(min-width:48rem){.search-bar{flex-flow:wrap;align-items:center}}.search-bar-select{height:calc(var(--spacing) * 9);border-radius:var(--radius-lg);background-color:var(--color-gray-100);width:100%;padding-inline:calc(var(--spacing) * 3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900)}.search-bar-select::placeholder{color:var(--color-gray-400)}.search-bar-select:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--color-blue-500);--tw-outline-style:none;outline-style:none}.dark .search-bar-select{background-color:var(--color-gray-700);color:var(--color-gray-100)}.dark .search-bar-select::placeholder{color:var(--color-gray-500)}@media(min-width:48rem){.search-bar-select{width:calc(var(--spacing) * 32);flex-shrink:0}}.search-bar-select-custom{width:100%}@media(min-width:48rem){.search-bar-select-custom{width:calc(var(--spacing) * 32);flex-shrink:0}}.search-bar-input{height:calc(var(--spacing) * 9);border-radius:var(--radius-lg);background-color:var(--color-gray-100);width:100%;padding-inline:calc(var(--spacing) * 3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900)}.search-bar-input::placeholder{color:var(--color-gray-400)}.search-bar-input:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--color-blue-500);--tw-outline-style:none;outline-style:none}.dark .search-bar-input{background-color:var(--color-gray-700);color:var(--color-gray-100)}.dark .search-bar-input::placeholder{color:var(--color-gray-500)}@media(min-width:48rem){.search-bar-input{min-width:calc(var(--spacing) * 64);flex:1}}.search-bar-reset{cursor:pointer;justify-content:center;align-items:center;gap:calc(var(--spacing) * 2);border-radius:var(--radius-lg);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-300);color:var(--color-gray-700);background-color:#0000;transition-duration:.2s;display:inline-flex}@media(hover:hover){.search-bar-reset:hover{background-color:var(--color-gray-50)}}.dark .search-bar-reset{border-color:var(--color-gray-600);color:var(--color-gray-300)}@media(hover:hover){.dark .search-bar-reset:hover{background-color:var(--color-gray-700)}}.search-bar-reset{gap:calc(var(--spacing) * 1.5);flex-shrink:0;width:100%}@media(min-width:48rem){.search-bar-reset{width:auto}}.filter-grid{gap:calc(var(--spacing) * 4);flex-direction:column;display:flex}.filter-row{gap:calc(var(--spacing) * 2);flex-direction:column;display:flex}@media(min-width:48rem){.filter-row{flex-direction:row;align-items:center}}.filter-label{margin-bottom:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-600);display:block}.dark .filter-label{color:var(--color-gray-400)}@media(min-width:48rem){.filter-label{margin-bottom:calc(var(--spacing) * 0);width:calc(var(--spacing) * 14);flex-shrink:0;align-items:center;display:flex}}.filter-buttons{align-items:center;gap:calc(var(--spacing) * 2);flex-wrap:wrap;display:flex}.dropdown{display:inline-block;position:relative}.dropdown-trigger{border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-300);background-color:var(--color-white);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}@media(hover:hover){.dropdown-trigger:hover{background-color:var(--color-gray-50)}}.dropdown-trigger:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--color-blue-500);--tw-outline-style:none;outline-style:none}.dropdown-trigger{color:var(--color-gray-900)}.dark .dropdown-trigger{border-color:var(--color-gray-600);background-color:var(--color-gray-800);color:var(--color-white)}@media(hover:hover){.dark .dropdown-trigger:hover{background-color:var(--color-gray-700)}}.dark .dropdown-trigger:focus{--tw-ring-color:var(--color-blue-400)}.dropdown-trigger-text{cursor:pointer;--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-900);align-items:center;display:inline-flex}@media(hover:hover){.dropdown-trigger-text:hover{color:var(--color-blue-600)}}.dark .dropdown-trigger-text{color:var(--color-white)}@media(hover:hover){.dark .dropdown-trigger-text:hover{color:var(--color-blue-400)}}.dropdown-menu{z-index:50;width:calc(var(--spacing) * 56);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);position:absolute}.dark .dropdown-menu{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}.dropdown-menu-bottom-left{top:100%;left:calc(var(--spacing) * 0);margin-top:calc(var(--spacing) * 2)}.dropdown-menu-bottom-right{top:100%;right:calc(var(--spacing) * 0);margin-top:calc(var(--spacing) * 2)}.dropdown-menu-top-left{bottom:100%;left:calc(var(--spacing) * 0);margin-bottom:calc(var(--spacing) * 2)}.dropdown-menu-top-right{right:calc(var(--spacing) * 0);margin-bottom:calc(var(--spacing) * 2);bottom:100%}.dropdown-item{cursor:pointer;padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-700)}@media(hover:hover){.dropdown-item:hover{background-color:var(--color-gray-100)}}.dark .dropdown-item{color:var(--color-gray-300)}@media(hover:hover){.dark .dropdown-item:hover{background-color:var(--color-gray-700)}}.dropdown-item-icon{color:var(--color-gray-400)}.dark .dropdown-item-icon{color:var(--color-gray-500)}.dropdown-item-disabled{cursor:not-allowed;padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-400)}.dark .dropdown-item-disabled{color:var(--color-gray-500)}.dropdown-item-focused{background-color:var(--color-gray-100)}.dark .dropdown-item-focused{background-color:var(--color-gray-700)}.dropdown-divider{margin-block:calc(var(--spacing) * 1);border-top-style:var(--tw-border-style);border-top-width:1px;border-color:var(--color-gray-100)}.dark .dropdown-divider{border-color:var(--color-gray-700)}.pagination{justify-content:center;align-items:center;gap:calc(var(--spacing) * 2);flex-wrap:wrap;display:flex}.pagination-btn{border-style:var(--tw-border-style);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 1);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));border-width:1px;border-color:var(--color-gray-300);background-color:var(--color-white);color:var(--color-gray-700);border-radius:.25rem}@media(hover:hover){.pagination-btn:hover{background-color:var(--color-gray-100)}}.pagination-btn:disabled{cursor:not-allowed;opacity:.5}.dark .pagination-btn{border-color:var(--color-gray-600);background-color:var(--color-gray-800);color:var(--color-gray-300)}@media(hover:hover){.dark .pagination-btn:hover{background-color:var(--color-gray-700)}}.pagination-btn-active{border-style:var(--tw-border-style);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 1);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));border-width:1px;border-color:var(--color-blue-500);background-color:var(--color-blue-500);color:var(--color-white);border-radius:.25rem}.dark .pagination-btn-active{border-color:var(--color-blue-600);background-color:var(--color-blue-600)}.pagination-ellipsis{padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 1);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-500)}.dark .pagination-ellipsis{color:var(--color-gray-400)}.dialog-header{margin-bottom:calc(var(--spacing) * 4);justify-content:space-between;align-items:center;display:flex}.dialog-title{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-gray-900)}.dark .dialog-title{color:var(--color-white)}.dialog-close{color:var(--color-gray-400)}@media(hover:hover){.dialog-close:hover{color:var(--color-gray-600)}}.dialog-close:focus{--tw-outline-style:none;outline-style:none}.dark .dialog-close{color:var(--color-gray-500)}@media(hover:hover){.dark .dialog-close:hover{color:var(--color-gray-300)}}.dialog-body{margin-bottom:calc(var(--spacing) * 6);color:var(--color-gray-700)}.dark .dialog-body{color:var(--color-gray-300)}.dialog-footer{justify-content:flex-end;gap:calc(var(--spacing) * 3);display:flex}.dialog-btn-primary{border-radius:var(--radius-md);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.dialog-btn-primary:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-outline-style:none;outline-style:none}.dialog-btn-primary:disabled{cursor:not-allowed;opacity:.5}.dialog-btn-primary{background-color:var(--color-blue-600);color:var(--color-white)}@media(hover:hover){.dialog-btn-primary:hover{background-color:var(--color-blue-700)}}.dialog-btn-primary:focus{--tw-ring-color:var(--color-blue-500)}.dialog-btn-secondary{border-radius:var(--radius-md);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.dialog-btn-secondary:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-outline-style:none;outline-style:none}.dialog-btn-secondary:disabled{cursor:not-allowed;opacity:.5}.dialog-btn-secondary{background-color:var(--color-gray-200);color:var(--color-gray-800)}@media(hover:hover){.dialog-btn-secondary:hover{background-color:var(--color-gray-300)}}.dialog-btn-secondary:focus{--tw-ring-color:var(--color-gray-500)}.dark .dialog-btn-secondary{background-color:var(--color-gray-700);color:var(--color-gray-200)}@media(hover:hover){.dark .dialog-btn-secondary:hover{background-color:var(--color-gray-600)}}.dialog-btn-danger{border-radius:var(--radius-md);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.dialog-btn-danger:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-outline-style:none;outline-style:none}.dialog-btn-danger:disabled{cursor:not-allowed;opacity:.5}.dialog-btn-danger{background-color:var(--color-red-600);color:var(--color-white)}@media(hover:hover){.dialog-btn-danger:hover{background-color:var(--color-red-700)}}.dialog-btn-danger:focus{--tw-ring-color:var(--color-red-500)}.dialog-btn-ghost{border-radius:var(--radius-md);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.dialog-btn-ghost:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-outline-style:none;outline-style:none}.dialog-btn-ghost:disabled{cursor:not-allowed;opacity:.5}.dialog-btn-ghost{color:var(--color-gray-700);background-color:#0000}@media(hover:hover){.dialog-btn-ghost:hover{background-color:var(--color-gray-100)}}.dialog-btn-ghost:focus{--tw-ring-color:var(--color-gray-500)}.dark .dialog-btn-ghost{color:var(--color-gray-300)}@media(hover:hover){.dark .dialog-btn-ghost:hover{background-color:var(--color-gray-700)}}.alert-error{align-items:center;gap:calc(var(--spacing) * 3);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-red-200);background-color:var(--color-red-50);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-red-900);display:flex}.dark .alert-error{border-color:var(--color-red-800);background-color:#82181a33}@supports (color:color-mix(in lab,red,red)){.dark .alert-error{background-color:color-mix(in oklab,var(--color-red-900) 20%,transparent)}}.dark .alert-error{color:var(--color-red-100)}.alert-icon-info{margin-top:calc(var(--spacing) * .5);color:var(--color-blue-600);flex-shrink:0}.dark .alert-icon-info{color:var(--color-blue-400)}.alert-icon-success{margin-top:calc(var(--spacing) * .5);color:var(--color-green-600);flex-shrink:0}.dark .alert-icon-success{color:var(--color-green-400)}.alert-icon-warning{margin-top:calc(var(--spacing) * .5);color:var(--color-yellow-600);flex-shrink:0}.dark .alert-icon-warning{color:var(--color-yellow-400)}.alert-icon-error{margin-top:calc(var(--spacing) * .5);color:var(--color-red-600);flex-shrink:0}.dark .alert-icon-error{color:var(--color-red-400)}.alert-text-info{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-blue-800);flex:1}.dark .alert-text-info{color:var(--color-blue-200)}.alert-text-success{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-green-800);flex:1}.dark .alert-text-success{color:var(--color-green-200)}.alert-text-warning{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-yellow-800);flex:1}.dark .alert-text-warning{color:var(--color-yellow-200)}.alert-text-error{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-red-800);flex:1}.dark .alert-text-error{color:var(--color-red-200)}.alert-dismiss{cursor:pointer;border-style:var(--tw-border-style);padding:calc(var(--spacing) * 0);background-color:#0000;border-width:0;flex-shrink:0}@media(hover:hover){.alert-dismiss:hover{opacity:.7}}.table-container{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);overflow:hidden}.dark .table-container{border-color:var(--color-gray-700)}.table{width:100%}.table thead{background-color:var(--color-gray-50)}.dark :is(.table thead){background-color:var(--color-gray-800)}.table th{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-200);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3);text-align:left;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-600)}.dark :is(.table th){border-color:var(--color-gray-700);color:var(--color-gray-400)}.table th.sortable{cursor:pointer}@media(hover:hover){.table th.sortable:hover{background-color:var(--color-gray-100)}.dark :is(.table th.sortable):hover{background-color:var(--color-gray-700)}}.table tbody tr{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-100);background-color:var(--color-white)}.dark :is(.table tbody tr){border-color:var(--color-gray-800);background-color:var(--color-gray-900)}.table tbody tr:hover{background-color:var(--color-gray-50)}.dark :is(.table tbody tr:hover){background-color:var(--color-gray-800)}.table tbody tr.selected{background-color:var(--color-blue-50)}.dark :is(.table tbody tr.selected){background-color:#1c398e33}@supports (color:color-mix(in lab,red,red)){.dark :is(.table tbody tr.selected){background-color:color-mix(in oklab,var(--color-blue-900) 20%,transparent)}}.table td{padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900)}.dark :is(.table td){color:var(--color-gray-100)}.table-actions{border-top-style:var(--tw-border-style);border-top-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3);justify-content:space-between;align-items:center;display:flex}.dark .table-actions{border-color:var(--color-gray-700);background-color:var(--color-gray-900)}.log-table{width:100%}.log-table th{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-200);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3);text-align:left;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-500)}.dark :is(.log-table th){border-color:var(--color-gray-700);color:var(--color-gray-400)}.log-table td{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-100);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900)}.dark :is(.log-table td){border-color:var(--color-gray-800);color:var(--color-gray-100)}.log-table tbody tr:last-child td{border-bottom-style:var(--tw-border-style);border-bottom-width:0}.log-table-actor{border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-gray-100);padding-inline:calc(var(--spacing) * 2.5);padding-block:calc(var(--spacing) * 1);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-700);align-items:center;display:inline-flex}.dark .log-table-actor{border-color:var(--color-gray-600);background-color:var(--color-gray-700);color:var(--color-gray-300)}.log-card{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);padding:calc(var(--spacing) * 4)}.dark .log-card{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}.log-card-header{margin-bottom:calc(var(--spacing) * 2);justify-content:space-between;align-items:center;gap:calc(var(--spacing) * 3);display:flex}.log-card-body{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900)}.dark .log-card-body{color:var(--color-gray-100)}.modal-overlay{inset:calc(var(--spacing) * 0);z-index:50;background-color:#00000080;justify-content:center;align-items:center;display:flex;position:fixed}@supports (color:color-mix(in lab,red,red)){.modal-overlay{background-color:color-mix(in oklab,var(--color-black) 50%,transparent)}}.modal-wrapper{inset:calc(var(--spacing) * 0);justify-content:center;align-items:center;display:flex;position:fixed}.modal-backdrop{inset:calc(var(--spacing) * 0);background-color:#00000080;position:absolute}@supports (color:color-mix(in lab,red,red)){.modal-backdrop{background-color:color-mix(in oklab,var(--color-black) 50%,transparent)}}.modal-container{border-radius:var(--radius-lg);background-color:var(--color-white);--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a), 0 8px 10px -6px var(--tw-shadow-color,#0000001a);max-width:calc(100vw - 2rem);max-height:90vh;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);position:relative;overflow:hidden}.dark .modal-container{background-color:var(--color-gray-800)}.modal-header-row{padding:calc(var(--spacing) * 4);justify-content:space-between;align-items:center;display:flex}.modal-title{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-gray-900)}.dark .modal-title{color:var(--color-white)}.modal-close-btn{color:var(--color-gray-400);margin-left:auto}@media(hover:hover){.modal-close-btn:hover{color:var(--color-gray-600)}}.modal-close-btn:focus{--tw-outline-style:none;outline-style:none}.dark .modal-close-btn{color:var(--color-gray-500)}@media(hover:hover){.dark .modal-close-btn:hover{color:var(--color-gray-300)}}.modal-body-content{max-height:calc(90vh - 140px);padding-inline:calc(var(--spacing) * 6);padding-block:calc(var(--spacing) * 4);overflow-y:auto}.modal{margin-inline:calc(var(--spacing) * 4);width:100%;max-width:var(--container-md);border-radius:var(--radius-xl);background-color:var(--color-white);padding:calc(var(--spacing) * 6);--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a), 0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.dark .modal{background-color:var(--color-gray-800)}.modal-lg{max-width:var(--container-lg)}.modal-xl{max-width:var(--container-xl)}.modal-header{margin-bottom:calc(var(--spacing) * 4);justify-content:space-between;align-items:flex-start;display:flex}.modal-close{cursor:pointer;border-radius:var(--radius-lg);padding:calc(var(--spacing) * 1);color:var(--color-gray-400)}@media(hover:hover){.modal-close:hover{background-color:var(--color-gray-100);color:var(--color-gray-600)}}.dark .modal-close{color:var(--color-gray-500)}@media(hover:hover){.dark .modal-close:hover{background-color:var(--color-gray-700);color:var(--color-gray-300)}}.modal-body{padding-block:calc(var(--spacing) * 4)}.modal-footer{margin-top:calc(var(--spacing) * 6);justify-content:flex-end;gap:calc(var(--spacing) * 3);display:flex}.danger-confirm-icon{margin-inline:auto;margin-bottom:calc(var(--spacing) * 4);height:calc(var(--spacing) * 12);width:calc(var(--spacing) * 12);background-color:var(--color-red-100);border-radius:3.40282e38px;justify-content:center;align-items:center;display:flex}.dark .danger-confirm-icon{background-color:#82181a4d}@supports (color:color-mix(in lab,red,red)){.dark .danger-confirm-icon{background-color:color-mix(in oklab,var(--color-red-900) 30%,transparent)}}.danger-confirm-icon svg,.danger-confirm-icon .icon{height:calc(var(--spacing) * 6);width:calc(var(--spacing) * 6);color:var(--color-red-600)}.dark :is(.danger-confirm-icon svg,.danger-confirm-icon .icon){color:var(--color-red-400)}.danger-confirm-title{margin-bottom:calc(var(--spacing) * 2);text-align:center;font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-gray-900)}.dark .danger-confirm-title{color:var(--color-white)}.danger-confirm-message{margin-bottom:calc(var(--spacing) * 4);text-align:center;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-600)}.dark .danger-confirm-message{color:var(--color-gray-400)}.danger-confirm-warning{margin-bottom:calc(var(--spacing) * 4);border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-yellow-200);background-color:var(--color-yellow-50);padding:calc(var(--spacing) * 4)}.dark .danger-confirm-warning{border-color:var(--color-yellow-700);background-color:#733e0a33}@supports (color:color-mix(in lab,red,red)){.dark .danger-confirm-warning{background-color:color-mix(in oklab,var(--color-yellow-900) 20%,transparent)}}.danger-confirm-warning-title{margin-bottom:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-yellow-800)}.dark .danger-confirm-warning-title{color:var(--color-yellow-300)}.danger-confirm-warning-list{list-style-type:disc;list-style-position:inside}:where(.danger-confirm-warning-list>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 1) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 1) * calc(1 - var(--tw-space-y-reverse)))}.danger-confirm-warning-list{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-yellow-700)}.dark .danger-confirm-warning-list{color:var(--color-yellow-400)}.danger-confirm-alert{text-align:center;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-red-600)}.dark .danger-confirm-alert{color:var(--color-red-400)}.danger-confirm-footer{margin-top:calc(var(--spacing) * 6);justify-content:center;gap:calc(var(--spacing) * 3);display:flex}.card{border-radius:var(--radius-xl);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);width:100%;padding:calc(var(--spacing) * 6)}.dark .card{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}.admin-card{margin-bottom:calc(var(--spacing) * 6);border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);padding:calc(var(--spacing) * 4)}.dark .admin-card{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}.admin-card{flex-direction:column;display:flex}.admin-card-clickable{margin-bottom:calc(var(--spacing) * 2);cursor:pointer;border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2.5)}@media(hover:hover){.admin-card-clickable:hover{border-color:var(--color-gray-300)}}.dark .admin-card-clickable{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}@media(hover:hover){.dark .admin-card-clickable:hover{border-color:var(--color-gray-600)}}.admin-card-selected{margin-bottom:calc(var(--spacing) * 2);cursor:pointer;border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-blue-500);background-color:var(--color-blue-50);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2.5)}.dark .admin-card-selected{border-color:var(--color-blue-500);background-color:#1c398e4d}@supports (color:color-mix(in lab,red,red)){.dark .admin-card-selected{background-color:color-mix(in oklab,var(--color-blue-900) 30%,transparent)}}.card-clickable{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a), 0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);transition-property:box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));overflow:hidden}@media(hover:hover){.card-clickable:hover{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}}.dark .card-clickable{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}.card-image{height:calc(var(--spacing) * 48);object-fit:cover;width:100%}.card-content{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));white-space:pre-line;color:var(--color-gray-600)}.dark .card-content{color:var(--color-gray-300)}.card-section-title{margin-inline:calc(var(--spacing) * -6);margin-bottom:calc(var(--spacing) * 6);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-200);padding-inline:calc(var(--spacing) * 6);padding-bottom:calc(var(--spacing) * 4);font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-gray-900)}.dark .card-section-title{border-color:var(--color-gray-700);color:var(--color-white)}.card-account{border-radius:var(--radius-lg);background-color:var(--color-gray-50);padding:calc(var(--spacing) * 4);position:relative}.dark .card-account{background-color:var(--color-gray-900)}.card-account-default{top:calc(var(--spacing) * 3);left:calc(var(--spacing) * 3);position:absolute}.card-account-delete{top:calc(var(--spacing) * 3);right:calc(var(--spacing) * 3);position:absolute}.admin-card-accordion{margin-bottom:calc(var(--spacing) * 6);border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);padding:calc(var(--spacing) * 4)}.dark .admin-card-accordion{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}.admin-card-accordion{flex-direction:column;display:flex}.admin-card-accordion>.card-header{cursor:pointer}.admin-card-accordion-icon{height:calc(var(--spacing) * 5);width:calc(var(--spacing) * 5);color:var(--color-gray-400);transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;margin-left:auto;transition-duration:.2s}.dark .admin-card-accordion-icon{color:var(--color-gray-500)}.admin-card-accordion>.row-stack{margin-top:calc(var(--spacing) * 6)}.admin-card-accordion-icon.open{rotate:180deg}.tabs{border-radius:var(--radius-lg);background-color:var(--color-gray-100);padding:calc(var(--spacing) * 1);display:inline-flex}.dark .tabs{background-color:var(--color-gray-800)}.tab{cursor:pointer;border-radius:var(--radius-md);padding-inline:calc(var(--spacing) * 6);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-500);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}@media(hover:hover){.tab:hover{color:var(--color-gray-700)}}.dark .tab{color:var(--color-gray-400)}@media(hover:hover){.dark .tab:hover{color:var(--color-gray-300)}}.tab.active{background-color:var(--color-white);color:var(--color-gray-900);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.dark .tab.active{background-color:var(--color-gray-700);color:var(--color-white)}.toast{right:calc(var(--spacing) * 4);bottom:calc(var(--spacing) * 4);z-index:50;position:fixed}.toast-item{align-items:center;gap:calc(var(--spacing) * 3);border-radius:var(--radius-lg);background-color:var(--color-gray-900);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3);color:var(--color-white);--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);display:flex}.dark .toast-item{background-color:var(--color-white);color:var(--color-gray-900)}.toast-icon{flex-shrink:0}.drag-item{align-items:center;gap:calc(var(--spacing) * 4);border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);padding:calc(var(--spacing) * 4);display:flex}@media(hover:hover){.drag-item:hover{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}}.dark .drag-item{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}.drag-handle{cursor:grab;color:var(--color-gray-400)}.dark .drag-handle{color:var(--color-gray-500)}.drag-handle:active{cursor:grabbing}.table-mobile-cards{display:none}.excel-card{border-radius:var(--radius-xl);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);padding:calc(var(--spacing) * 4)}.dark .excel-card{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}.excel-card-header{margin-bottom:calc(var(--spacing) * 4);justify-content:space-between;align-items:flex-start;gap:calc(var(--spacing) * 3);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-100);padding-bottom:calc(var(--spacing) * 3);display:flex}.dark .excel-card-header{border-color:var(--color-gray-700)}.excel-card-title{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-gray-900)}.dark .excel-card-title{color:var(--color-white)}:where(.excel-card-body>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)))}.excel-card-row{padding-block:calc(var(--spacing) * 1);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));justify-content:space-between;align-items:center;display:flex}.excel-card-label{color:var(--color-gray-500);flex-shrink:0}.dark .excel-card-label{color:var(--color-gray-400)}.excel-card-value{text-align:right;color:var(--color-gray-900)}.dark .excel-card-value{color:var(--color-white)}.excel-card-footer{margin-top:calc(var(--spacing) * 4);border-top-style:var(--tw-border-style);border-top-width:1px;border-color:var(--color-gray-100);padding-top:calc(var(--spacing) * 3);justify-content:space-between;align-items:center;display:flex}.dark .excel-card-footer{border-color:var(--color-gray-700)}.excel-card-creator{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-700)}.dark .excel-card-creator{color:var(--color-gray-300)}.excel-card-creator-label{margin-right:calc(var(--spacing) * 2);color:var(--color-gray-500)}.dark .excel-card-creator-label{color:var(--color-gray-400)}.select-custom{display:inline-block;position:relative}.select-trigger{height:calc(var(--spacing) * 9);border-radius:var(--radius-xl);background-color:var(--color-gray-100);width:100%;padding-inline:calc(var(--spacing) * 4);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-700);cursor:pointer;justify-content:space-between;align-items:center;gap:calc(var(--spacing) * 2);border-style:var(--tw-border-style);border-width:0;display:flex}.select-trigger:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--color-blue-500);--tw-outline-style:none;outline-style:none}.dark .select-trigger{background-color:var(--color-gray-700);color:var(--color-gray-200)}.select-trigger:disabled{cursor:not-allowed;opacity:.5}.select-trigger span{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.select-trigger-icon{height:calc(var(--spacing) * 4);width:calc(var(--spacing) * 4);color:var(--color-gray-400);transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;flex-shrink:0;transition-duration:.2s}.dark .select-trigger-icon{color:var(--color-gray-500)}.select-trigger-icon.open{rotate:180deg}.select-dropdown{z-index:50;margin-top:calc(var(--spacing) * 2);border-radius:var(--radius-2xl);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);width:100%;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);position:absolute;overflow:hidden}.dark .select-dropdown{border-color:var(--color-gray-600);background-color:var(--color-gray-800)}.select-dropdown.hidden{display:none}.select-dropdown-inner{max-height:calc(var(--spacing) * 60);padding-block:calc(var(--spacing) * 2);overflow:auto}.select-option{cursor:pointer;width:100%;padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3);text-align:left;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-700);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));align-items:center;display:flex}@media(hover:hover){.select-option:hover{background-color:var(--color-gray-100)}}.dark .select-option{color:var(--color-gray-200)}@media(hover:hover){.dark .select-option:hover{background-color:var(--color-gray-700)}}.select-option.selected{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-blue-600)}.dark .select-option.selected{color:var(--color-blue-400)}.select-option:disabled{cursor:not-allowed;opacity:.5}.select-option-check{height:calc(var(--spacing) * 5);width:calc(var(--spacing) * 5);color:var(--color-blue-600);flex-shrink:0;margin-left:auto}.dark .select-option-check{color:var(--color-blue-400)}.multi-select{display:inline-block;position:relative}.multi-select-trigger{align-items:center;gap:calc(var(--spacing) * 2);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-300);color:var(--color-gray-700);cursor:pointer;background-color:#0000;display:inline-flex}@media(hover:hover){.multi-select-trigger:hover{background-color:var(--color-gray-50)}}.dark .multi-select-trigger{border-color:var(--color-gray-600);color:var(--color-gray-300)}@media(hover:hover){.dark .multi-select-trigger:hover{background-color:var(--color-gray-700)}}.multi-select-count{color:var(--color-gray-500)}.dark .multi-select-count{color:var(--color-gray-400)}.multi-select-dropdown{z-index:50;margin-top:calc(var(--spacing) * 2);min-width:calc(var(--spacing) * 64);border-radius:var(--radius-xl);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);position:absolute}.dark .multi-select-dropdown{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}.multi-select-dropdown.hidden{display:none}.multi-select-header{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-100);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-900)}.dark .multi-select-header{border-color:var(--color-gray-700);color:var(--color-white)}.multi-select-hint{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal);color:var(--color-gray-500)}.dark .multi-select-hint{color:var(--color-gray-400)}.multi-select-items{max-height:calc(var(--spacing) * 80);padding-block:calc(var(--spacing) * 2);overflow:auto}.multi-select-item{cursor:pointer;align-items:center;gap:calc(var(--spacing) * 3);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-700);display:flex}@media(hover:hover){.multi-select-item:hover{background-color:var(--color-gray-50)}}.dark .multi-select-item{color:var(--color-gray-300)}@media(hover:hover){.dark .multi-select-item:hover{background-color:var(--color-gray-700)}}.multi-select-item.disabled{cursor:not-allowed;opacity:.5}.multi-select-item.disabled:hover,.dark .multi-select-item.disabled:hover{background-color:#0000}.select-ghost{display:inline-block;position:relative}.select-ghost-trigger{align-items:center;gap:calc(var(--spacing) * 1);padding-inline:calc(var(--spacing) * 2);padding-block:calc(var(--spacing) * 1.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);cursor:pointer;border-radius:var(--radius-lg);color:var(--color-gray-700);background-color:#0000;display:inline-flex}@media(hover:hover){.select-ghost-trigger:hover{background-color:var(--color-gray-100)}}.dark .select-ghost-trigger{color:var(--color-gray-300)}@media(hover:hover){.dark .select-ghost-trigger:hover{background-color:var(--color-gray-700)}}.select-ghost-dropdown{z-index:50;margin-top:calc(var(--spacing) * 1);min-width:calc(var(--spacing) * 32);border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);padding-block:calc(var(--spacing) * 1);--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);position:absolute}.dark .select-ghost-dropdown{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}.select-ghost-dropdown.hidden{display:none}.select-ghost-option{cursor:pointer;width:100%;padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-700);justify-content:space-between;align-items:center;display:flex}@media(hover:hover){.select-ghost-option:hover{background-color:var(--color-gray-50)}}.dark .select-ghost-option{color:var(--color-gray-300)}@media(hover:hover){.dark .select-ghost-option:hover{background-color:var(--color-gray-700)}}.select-ghost-option.selected{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-blue-600)}.dark .select-ghost-option.selected{color:var(--color-blue-400)}.select-ghost-check{height:calc(var(--spacing) * 4);width:calc(var(--spacing) * 4);color:var(--color-blue-600)}.dark .select-ghost-check{color:var(--color-blue-400)}.searchable-dropdown{display:inline-block;position:relative}.searchable-dropdown-trigger{border-radius:var(--radius-xl);background-color:var(--color-gray-100);min-width:200px;padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-700);cursor:pointer;justify-content:space-between;align-items:center;gap:calc(var(--spacing) * 2);border-style:var(--tw-border-style);border-width:0;display:flex}.searchable-dropdown-trigger:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--color-blue-500);--tw-outline-style:none;outline-style:none}.dark .searchable-dropdown-trigger{background-color:var(--color-gray-700);color:var(--color-gray-200)}.searchable-dropdown-trigger:disabled{cursor:not-allowed;opacity:.5}.searchable-dropdown-trigger-icon{height:calc(var(--spacing) * 4);width:calc(var(--spacing) * 4);color:var(--color-gray-400);transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;flex-shrink:0;transition-duration:.2s}.dark .searchable-dropdown-trigger-icon{color:var(--color-gray-500)}.searchable-dropdown-trigger-icon.open{rotate:180deg}.searchable-dropdown-dropdown{z-index:50;margin-top:calc(var(--spacing) * 2);border-radius:var(--radius-2xl);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);width:100%;min-width:280px;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);position:absolute;overflow:hidden}.dark .searchable-dropdown-dropdown{border-color:var(--color-gray-600);background-color:var(--color-gray-800)}.searchable-dropdown-dropdown.hidden{display:none}.searchable-dropdown-search{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-200);padding:calc(var(--spacing) * 3)}.dark .searchable-dropdown-search{border-color:var(--color-gray-700)}.searchable-dropdown-search-input{border-radius:var(--radius-lg);background-color:var(--color-gray-50);width:100%;padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200)}.searchable-dropdown-search-input::placeholder{color:var(--color-gray-400)}.searchable-dropdown-search-input:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--color-blue-500);--tw-outline-style:none;border-color:#0000;outline-style:none}.dark .searchable-dropdown-search-input{border-color:var(--color-gray-600);background-color:var(--color-gray-700);color:var(--color-white)}.dark .searchable-dropdown-search-input::placeholder{color:var(--color-gray-500)}.searchable-dropdown-items{max-height:calc(var(--spacing) * 80);padding-block:calc(var(--spacing) * 2);overflow:auto}.searchable-dropdown-option{cursor:pointer;width:100%;padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3);text-align:left;align-items:flex-start;gap:calc(var(--spacing) * 3);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));display:flex}@media(hover:hover){.searchable-dropdown-option:hover{background-color:var(--color-gray-100)}.dark .searchable-dropdown-option:hover{background-color:var(--color-gray-700)}}.searchable-dropdown-option:disabled{cursor:not-allowed;opacity:.5}.searchable-dropdown-check-area{margin-top:calc(var(--spacing) * .5);height:calc(var(--spacing) * 5);width:calc(var(--spacing) * 5);flex-shrink:0}.searchable-dropdown-check{height:calc(var(--spacing) * 5);width:calc(var(--spacing) * 5);color:var(--color-blue-600)}.dark .searchable-dropdown-check{color:var(--color-blue-400)}.searchable-dropdown-option-content{min-width:calc(var(--spacing) * 0);flex:1}.searchable-dropdown-option-label{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-900)}.dark .searchable-dropdown-option-label{color:var(--color-blue-400)}.searchable-dropdown-option.selected .searchable-dropdown-option-label{color:var(--color-blue-600)}.dark :is(.searchable-dropdown-option.selected .searchable-dropdown-option-label){color:var(--color-blue-400)}.searchable-dropdown-option-description{margin-top:calc(var(--spacing) * .5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-500)}.dark .searchable-dropdown-option-description{color:var(--color-gray-400)}.searchable-dropdown-message{padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 6);text-align:center;color:var(--color-gray-500)}.dark .searchable-dropdown-message{color:var(--color-gray-400)}.stat-card{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);padding:calc(var(--spacing) * 6);--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a), 0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);transition-property:box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}@media(hover:hover){.stat-card:hover{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}}.dark .stat-card{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}.stats-icon{border-radius:var(--radius-lg);padding:calc(var(--spacing) * 3)}.stats-icon-blue{background-color:var(--color-blue-50);color:var(--color-blue-600)}.dark .stats-icon-blue{background-color:#1c398e33}@supports (color:color-mix(in lab,red,red)){.dark .stats-icon-blue{background-color:color-mix(in oklab,var(--color-blue-900) 20%,transparent)}}.dark .stats-icon-blue{color:var(--color-blue-400)}.stats-icon-green{background-color:var(--color-green-50);color:var(--color-green-600)}.dark .stats-icon-green{background-color:#0d542b33}@supports (color:color-mix(in lab,red,red)){.dark .stats-icon-green{background-color:color-mix(in oklab,var(--color-green-900) 20%,transparent)}}.dark .stats-icon-green{color:var(--color-green-400)}.stats-icon-purple{background-color:var(--color-purple-50);color:var(--color-purple-600)}.dark .stats-icon-purple{background-color:#59168b33}@supports (color:color-mix(in lab,red,red)){.dark .stats-icon-purple{background-color:color-mix(in oklab,var(--color-purple-900) 20%,transparent)}}.dark .stats-icon-purple{color:var(--color-purple-400)}.stats-icon-orange{background-color:var(--color-orange-50);color:var(--color-orange-600)}.dark .stats-icon-orange{background-color:#7e2a0c33}@supports (color:color-mix(in lab,red,red)){.dark .stats-icon-orange{background-color:color-mix(in oklab,var(--color-orange-900) 20%,transparent)}}.dark .stats-icon-orange{color:var(--color-orange-400)}.stats-change{align-items:center;gap:calc(var(--spacing) * 1);border-radius:var(--radius-md);padding-inline:calc(var(--spacing) * 2);padding-block:calc(var(--spacing) * 1);display:flex}.stats-trend-up{background-color:var(--color-green-50);color:var(--color-green-600)}.dark .stats-trend-up{background-color:#0d542b33}@supports (color:color-mix(in lab,red,red)){.dark .stats-trend-up{background-color:color-mix(in oklab,var(--color-green-900) 20%,transparent)}}.dark .stats-trend-up{color:var(--color-green-400)}.stats-trend-down{background-color:var(--color-red-50);color:var(--color-red-600)}.dark .stats-trend-down{background-color:#82181a33}@supports (color:color-mix(in lab,red,red)){.dark .stats-trend-down{background-color:color-mix(in oklab,var(--color-red-900) 20%,transparent)}}.dark .stats-trend-down{color:var(--color-red-400)}.stats-trend-neutral{background-color:var(--color-gray-50);color:var(--color-gray-600)}.dark .stats-trend-neutral{background-color:var(--color-gray-700);color:var(--color-gray-400)}.stats-value-lg{margin-bottom:calc(var(--spacing) * 1);font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height));--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold);color:var(--color-gray-900)}.dark .stats-value-lg{color:var(--color-white)}.stats-label{margin-bottom:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-600)}.dark .stats-label{color:var(--color-gray-400)}.stats-sublabel{align-items:center;gap:calc(var(--spacing) * 1);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-gray-500);display:flex}.dark .stats-sublabel{color:var(--color-gray-400)}:where(.tree>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 1) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 1) * calc(1 - var(--tw-space-y-reverse)))}.tree-item{cursor:pointer;align-items:center;gap:calc(var(--spacing) * 2);border-radius:var(--radius-lg);padding:calc(var(--spacing) * 2);display:flex}@media(hover:hover){.tree-item:hover{background-color:var(--color-gray-50)}.dark .tree-item:hover{background-color:var(--color-gray-800)}}.tree-item.selected{background-color:var(--color-blue-50)}.dark .tree-item.selected{background-color:#1c398e33}@supports (color:color-mix(in lab,red,red)){.dark .tree-item.selected{background-color:color-mix(in oklab,var(--color-blue-900) 20%,transparent)}}.tree-item-content{min-width:calc(var(--spacing) * 0);align-items:center;gap:calc(var(--spacing) * 2);flex:1;display:flex}.tree-item-icon{height:calc(var(--spacing) * 4);width:calc(var(--spacing) * 4);color:var(--color-gray-400);flex-shrink:0}.dark .tree-item-icon{color:var(--color-gray-500)}.tree-item-label{text-overflow:ellipsis;white-space:nowrap;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-700);overflow:hidden}.dark .tree-item-label{color:var(--color-gray-300)}.tree-expand-btn{height:calc(var(--spacing) * 6);width:calc(var(--spacing) * 6);color:var(--color-gray-400);border-radius:.25rem;justify-content:center;align-items:center;display:flex}@media(hover:hover){.tree-expand-btn:hover{background-color:var(--color-gray-100)}}.dark .tree-expand-btn{color:var(--color-gray-500)}@media(hover:hover){.dark .tree-expand-btn:hover{background-color:var(--color-gray-700)}}.tree-expand-btn.hidden{visibility:hidden}.tree-children{margin-left:calc(var(--spacing) * 6);border-left-style:var(--tw-border-style);border-left-width:1px;border-color:var(--color-gray-200);padding-left:calc(var(--spacing) * 4)}.dark .tree-children{border-color:var(--color-gray-700)}.tree-level-1{margin-left:calc(var(--spacing) * 0);border-left-style:var(--tw-border-style);padding-left:calc(var(--spacing) * 0);border-left-width:0}.tree-level-2{margin-left:calc(var(--spacing) * 6);border-left-style:var(--tw-border-style);border-left-width:1px;border-color:var(--color-gray-200);padding-left:calc(var(--spacing) * 4)}.dark .tree-level-2{border-color:var(--color-gray-700)}.tree-level-3{margin-left:calc(var(--spacing) * 6);border-left-style:var(--tw-border-style);border-left-width:1px;border-color:var(--color-gray-200);padding-left:calc(var(--spacing) * 4)}.dark .tree-level-3{border-color:var(--color-gray-700)}.tree-level-4{margin-left:calc(var(--spacing) * 6);border-left-style:var(--tw-border-style);border-left-width:1px;border-color:var(--color-gray-200);padding-left:calc(var(--spacing) * 4)}.dark .tree-level-4{border-color:var(--color-gray-700)}.category-item{cursor:pointer;padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900);justify-content:space-between;align-items:center;display:flex}.dark .category-item{color:var(--color-white)}@media(hover:hover){.category-item:hover{background-color:var(--color-gray-50)}.dark .category-item:hover{background-color:var(--color-gray-700)}}.category-item[data-selected=true]{background-color:var(--color-blue-600);color:var(--color-white)}.category-item[data-selected=true]:hover{background-color:var(--color-blue-700)}.category-item-mobile{cursor:pointer;padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900)}.dark .category-item-mobile{color:var(--color-white)}.category-item-mobile[data-selected=true]{background-color:var(--color-blue-50)}.dark .category-item-mobile[data-selected=true]{background-color:#1c398e4d}@supports (color:color-mix(in lab,red,red)){.dark .category-item-mobile[data-selected=true]{background-color:color-mix(in oklab,var(--color-blue-900) 30%,transparent)}}.stats-card{height:calc(var(--spacing) * 36);border-radius:var(--radius-2xl);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-100);background-color:var(--color-white);padding:calc(var(--spacing) * 5);flex-direction:column;justify-content:space-between;display:flex}.dark .stats-card{border-color:var(--color-slate-700);background-color:var(--color-slate-800)}.dashboard-panel{border-radius:var(--radius-3xl);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-100);background-color:var(--color-white);padding:calc(var(--spacing) * 6);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.dark .dashboard-panel{border-color:var(--color-slate-700);background-color:var(--color-slate-800)}.stats-value{margin-bottom:calc(var(--spacing) * 2);--tw-leading:1;--tw-font-weight:var(--font-weight-bold);font-size:2rem;line-height:1;font-weight:var(--font-weight-bold);color:var(--color-slate-900)}.dark .stats-value{color:var(--color-white)}.stats-trend{align-items:center;gap:calc(var(--spacing) * 1);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-emerald-500);display:flex}.status-active{padding-inline:calc(var(--spacing) * 2.5);padding-block:calc(var(--spacing) * 1);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);background-color:var(--color-green-100);color:var(--color-green-700);border-radius:3.40282e38px;align-items:center;display:inline-flex}.dark .status-active{background-color:#0d542b4d}@supports (color:color-mix(in lab,red,red)){.dark .status-active{background-color:color-mix(in oklab,var(--color-green-900) 30%,transparent)}}.dark .status-active{color:var(--color-green-400)}.status-inactive{padding-inline:calc(var(--spacing) * 2.5);padding-block:calc(var(--spacing) * 1);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);background-color:var(--color-gray-100);color:var(--color-gray-600);border-radius:3.40282e38px;align-items:center;display:inline-flex}.dark .status-inactive{background-color:var(--color-gray-700);color:var(--color-gray-400)}.status-pending{padding-inline:calc(var(--spacing) * 2.5);padding-block:calc(var(--spacing) * 1);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);background-color:var(--color-yellow-100);color:var(--color-yellow-700);border-radius:3.40282e38px;align-items:center;display:inline-flex}.dark .status-pending{background-color:#733e0a4d}@supports (color:color-mix(in lab,red,red)){.dark .status-pending{background-color:color-mix(in oklab,var(--color-yellow-900) 30%,transparent)}}.dark .status-pending{color:var(--color-yellow-400)}.status-error{padding-inline:calc(var(--spacing) * 2.5);padding-block:calc(var(--spacing) * 1);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);background-color:var(--color-red-100);color:var(--color-red-700);border-radius:3.40282e38px;align-items:center;display:inline-flex}.dark .status-error{background-color:#82181a4d}@supports (color:color-mix(in lab,red,red)){.dark .status-error{background-color:color-mix(in oklab,var(--color-red-900) 30%,transparent)}}.dark .status-error{color:var(--color-red-400)}.list-item{align-items:center;gap:calc(var(--spacing) * 3);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-100);padding-block:calc(var(--spacing) * 3);display:flex}.dark .list-item{border-color:var(--color-gray-700)}.list-item:last-child{border-bottom-style:var(--tw-border-style);border-bottom-width:0}.list-item-hover{margin-inline:calc(var(--spacing) * -3);align-items:center;gap:calc(var(--spacing) * 3);border-radius:var(--radius-lg);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 3);display:flex}@media(hover:hover){.list-item-hover:hover{background-color:var(--color-gray-50)}.dark .list-item-hover:hover{background-color:var(--color-gray-800)}}.empty-state{padding-block:calc(var(--spacing) * 12);text-align:center;flex-direction:column;justify-content:center;align-items:center;display:flex}.empty-state-icon{margin-bottom:calc(var(--spacing) * 4);height:calc(var(--spacing) * 16);width:calc(var(--spacing) * 16);color:var(--color-gray-300)}.dark .empty-state-icon{color:var(--color-gray-600)}.empty-state-title{margin-bottom:calc(var(--spacing) * 2);font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-900)}.dark .empty-state-title{color:var(--color-white)}.empty-state-description{margin-bottom:calc(var(--spacing) * 4);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-500)}.dark .empty-state-description{color:var(--color-gray-400)}.page-header{margin-bottom:calc(var(--spacing) * 6);justify-content:space-between;align-items:center;display:flex}.page-header-padded{margin-bottom:calc(var(--spacing) * 6);padding-inline:calc(var(--spacing) * 6);justify-content:space-between;align-items:center;display:flex}.page-header-fullbleed{margin-inline:calc(var(--spacing) * -6);margin-bottom:calc(var(--spacing) * 6);background-color:var(--color-gray-50);padding-inline:calc(var(--spacing) * 6);justify-content:space-between;align-items:center;display:flex}.dark .page-header-fullbleed{background-color:var(--color-gray-900)}.page-actions{align-items:center;gap:calc(var(--spacing) * 3);display:flex}.section-card{margin-top:calc(var(--spacing) * 6);scroll-margin-top:calc(var(--spacing) * 32);border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);padding:calc(var(--spacing) * 6)}.dark .section-card{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}.section-header{margin-bottom:calc(var(--spacing) * 6);font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-gray-900)}.dark .section-header{color:var(--color-white)}.info-row{margin-bottom:calc(var(--spacing) * 4);align-items:center;gap:calc(var(--spacing) * 2);display:flex}.action-buttons{margin-top:calc(var(--spacing) * 6);justify-content:flex-end;gap:calc(var(--spacing) * 3);display:flex}.form-label{margin-bottom:calc(var(--spacing) * 1);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-900);display:block}.dark .form-label{color:var(--color-white)}.form-label-muted{margin-bottom:calc(var(--spacing) * 1);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-500);display:block}.dark .form-label-muted{color:var(--color-gray-400)}.form-hint{margin-top:calc(var(--spacing) * 1);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-gray-500)}.dark .form-hint{color:var(--color-gray-400)}.text-value{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900)}.dark .text-value{color:var(--color-white)}.text-link{cursor:pointer;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-blue-600)}@media(hover:hover){.text-link:hover{color:var(--color-blue-700)}}.dark .text-link{color:var(--color-blue-400)}@media(hover:hover){.dark .text-link:hover{color:var(--color-blue-300)}}.icon-xs{height:calc(var(--spacing) * 3);width:calc(var(--spacing) * 3)}.icon-sm{height:calc(var(--spacing) * 4);width:calc(var(--spacing) * 4)}.icon-md{height:calc(var(--spacing) * 5);width:calc(var(--spacing) * 5)}.icon-lg{height:calc(var(--spacing) * 6);width:calc(var(--spacing) * 6)}.icon-xl{height:calc(var(--spacing) * 8);width:calc(var(--spacing) * 8)}.icon-2xl{height:calc(var(--spacing) * 10);width:calc(var(--spacing) * 10)}.icon-action{cursor:pointer;color:var(--color-gray-400)}@media(hover:hover){.icon-action:hover{color:var(--color-gray-600)}}.dark .icon-action{color:var(--color-gray-500)}@media(hover:hover){.dark .icon-action:hover{color:var(--color-gray-300)}}.page-container{background-color:var(--color-gray-50);min-height:100vh;padding:calc(var(--spacing) * 6)}.dark .page-container{background-color:var(--color-gray-900)}.content-area{max-width:var(--container-7xl);margin-inline:auto}.content-panel{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);padding:calc(var(--spacing) * 6)}.dark .content-panel{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}.flex-row-center{align-items:center;display:flex}.flex-row-between{justify-content:space-between;align-items:center;display:flex}.flex-row-end{justify-content:flex-end;align-items:center;display:flex}.flex-col-start{flex-direction:column;align-items:flex-start;display:flex}.flex-between{justify-content:space-between;align-items:center;display:flex}.flex-center{align-items:center;display:flex}.flex-end{justify-content:flex-end;align-items:center;display:flex}.flex-wrap-center{flex-wrap:wrap;align-items:center;display:flex}.inline-flex-center{align-items:center;display:inline-flex}.row-stack{flex-direction:column;display:flex}:where(.row-stack>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px * var(--tw-divide-y-reverse));border-bottom-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)));border-color:var(--color-gray-200)}:where(.dark .row-stack>:not(:last-child)){border-color:var(--color-gray-700)}.row-stack>*{padding-block:calc(var(--spacing) * 2)}.row-stack>:first-child{padding-top:calc(var(--spacing) * 0)}.row-stack>:last-child{padding-bottom:calc(var(--spacing) * 0)}.stack-flush{gap:calc(var(--spacing) * 0);flex-direction:column;display:flex}.stack-tight{gap:calc(var(--spacing) * 2);flex-direction:column;display:flex}.card-stack{margin-top:calc(var(--spacing) * 3);gap:calc(var(--spacing) * 2);flex-direction:column;display:flex}.card-stack-item{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);padding:calc(var(--spacing) * 4)}.dark .card-stack-item{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}.card-stack-item{flex-direction:column;display:flex}.option-card{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-gray-50);overflow:hidden}.dark .option-card{border-color:var(--color-gray-700);background-color:#36415380}@supports (color:color-mix(in lab,red,red)){.dark .option-card{background-color:color-mix(in oklab,var(--color-gray-700) 50%,transparent)}}.option-card>*{padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3)}.option-card>*+*{border-top-style:var(--tw-border-style);border-top-width:1px;border-color:var(--color-gray-200)}.dark :is(.option-card>*+*){border-color:var(--color-gray-700)}.alert-info{align-items:center;gap:calc(var(--spacing) * 3);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-blue-200);background-color:var(--color-blue-50);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-blue-900);display:flex}.dark .alert-info{border-color:var(--color-blue-800);background-color:#1c398e33}@supports (color:color-mix(in lab,red,red)){.dark .alert-info{background-color:color-mix(in oklab,var(--color-blue-900) 20%,transparent)}}.dark .alert-info{color:var(--color-blue-100)}.alert-warning{align-items:center;gap:calc(var(--spacing) * 3);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-yellow-200);background-color:var(--color-yellow-50);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-yellow-900);display:flex}.dark .alert-warning{border-color:var(--color-yellow-800);background-color:#733e0a33}@supports (color:color-mix(in lab,red,red)){.dark .alert-warning{background-color:color-mix(in oklab,var(--color-yellow-900) 20%,transparent)}}.dark .alert-warning{color:var(--color-yellow-100)}.alert-danger{align-items:center;gap:calc(var(--spacing) * 3);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-red-200);background-color:var(--color-red-50);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-red-900);display:flex}.dark .alert-danger{border-color:var(--color-red-800);background-color:#82181a33}@supports (color:color-mix(in lab,red,red)){.dark .alert-danger{background-color:color-mix(in oklab,var(--color-red-900) 20%,transparent)}}.dark .alert-danger{color:var(--color-red-100)}.alert-success{align-items:center;gap:calc(var(--spacing) * 3);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-green-200);background-color:var(--color-green-50);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-green-900);display:flex}.dark .alert-success{border-color:var(--color-green-800);background-color:#0d542b33}@supports (color:color-mix(in lab,red,red)){.dark .alert-success{background-color:color-mix(in oklab,var(--color-green-900) 20%,transparent)}}.dark .alert-success{color:var(--color-green-100)}.alert-info:has(+.row-stack),.alert-warning:has(+.row-stack),.alert-danger:has(+.row-stack),.alert-success:has(+.row-stack),.alert-error:has(+.row-stack),.alert-info:has(+.toggle-group),.alert-warning:has(+.toggle-group),.alert-danger:has(+.toggle-group),.alert-success:has(+.toggle-group),.alert-error:has(+.toggle-group),.alert-info:has(+.grid-2col-responsive),.alert-warning:has(+.grid-2col-responsive),.alert-danger:has(+.grid-2col-responsive),.alert-success:has(+.grid-2col-responsive),.alert-error:has(+.grid-2col-responsive){margin-bottom:calc(var(--spacing) * 4)}.row-stack>.alert-info,.row-stack>.alert-warning,.row-stack>.alert-danger,.row-stack>.alert-success,.row-stack>.alert-error{padding-top:calc(var(--spacing) * 3);padding-bottom:calc(var(--spacing) * 3)}.row-stack>*+.alert-info,.row-stack>*+.alert-warning,.row-stack>*+.alert-danger,.row-stack>*+.alert-success,.row-stack>*+.alert-error{margin-top:calc(var(--spacing) * 3)}.sub-tab-nav{gap:calc(var(--spacing) * 1);background-color:var(--color-white);padding-inline:calc(var(--spacing) * 6);display:flex}@media(min-width:40rem){.sub-tab-nav{padding-inline:calc(var(--spacing) * 8)}}@media(min-width:64rem){.sub-tab-nav{padding-inline:calc(var(--spacing) * 10)}}.dark .sub-tab-nav{background-color:var(--color-gray-800)}.section-header{margin-bottom:calc(var(--spacing) * 6)}.section-header>h3{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-gray-900)}.dark :is(.section-header>h3){color:var(--color-white)}.section-header>p{margin-top:calc(var(--spacing) * 1);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-500)}.dark :is(.section-header>p){color:var(--color-gray-400)}.toggle-group{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);overflow:hidden}.dark .toggle-group{border-color:var(--color-gray-700)}.section-header-row{align-items:center;gap:calc(var(--spacing) * 2);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3);background-color:var(--color-gray-50);display:flex}.dark .section-header-row{background-color:#36415380}@supports (color:color-mix(in lab,red,red)){.dark .section-header-row{background-color:color-mix(in oklab,var(--color-gray-700) 50%,transparent)}}.section-header-row{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-200)}.dark .section-header-row{border-color:var(--color-gray-700)}.section-header-row{cursor:pointer;-webkit-user-select:none;user-select:none}.list-row-hover{background-color:var(--color-white)}.dark .list-row-hover{background-color:var(--color-gray-800)}@media(hover:hover){.list-row-hover:hover{background-color:var(--color-gray-50)}.dark .list-row-hover:hover{background-color:var(--color-gray-700)}}.list-row-hover{padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2.5);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.text-hint{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-gray-400)}.dark .text-hint{color:var(--color-gray-500)}.text-info-soft{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-blue-700)}.dark .text-info-soft{color:var(--color-blue-300)}.text-warning-soft{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-yellow-700)}.dark .text-warning-soft{color:var(--color-yellow-300)}.text-danger-soft{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-red-700)}.dark .text-danger-soft{color:var(--color-red-300)}.text-success-soft{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-green-700)}.dark .text-success-soft{color:var(--color-green-300)}.checkbox-aligned{height:calc(var(--spacing) * 4);width:calc(var(--spacing) * 4);cursor:pointer;border-color:var(--color-gray-300);color:var(--color-gray-900);border-radius:.25rem}.checkbox-aligned:focus{--tw-ring-color:var(--color-gray-500)}.dark .checkbox-aligned{border-color:var(--color-gray-600);background-color:var(--color-gray-800);color:var(--color-blue-400)}.checkbox-aligned{margin-top:calc(var(--spacing) * .5);flex-shrink:0}:where(.divider-list>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px * var(--tw-divide-y-reverse));border-bottom-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)));border-color:var(--color-gray-200)}:where(.dark .divider-list>:not(:last-child)){border-color:var(--color-gray-700)}.modal-footer-buttons{margin-top:calc(var(--spacing) * 6);justify-content:flex-end;gap:calc(var(--spacing) * 3);display:flex}.spinner-sm{height:calc(var(--spacing) * 4);width:calc(var(--spacing) * 4);animation:var(--animate-spin)}.text-required{margin-left:calc(var(--spacing) * 1);color:var(--color-red-500)}.dark .text-required{color:var(--color-red-400)}.form-label-block{margin-bottom:calc(var(--spacing) * 1);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-700);display:block}.dark .form-label-block{color:var(--color-gray-300)}.form-sub-label{margin-bottom:calc(var(--spacing) * 1);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-gray-500);display:block}.dark .form-sub-label{color:var(--color-gray-400)}.text-field-hint{margin-top:calc(var(--spacing) * .5);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal);color:var(--color-gray-500)}.dark .text-field-hint{color:var(--color-gray-400)}.section-heading-md{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-gray-900)}.dark .section-heading-md{color:var(--color-white)}.grid-3col-responsive{align-items:flex-start;gap:calc(var(--spacing) * 4);grid-template-columns:repeat(1,minmax(0,1fr));display:grid}@media(min-width:64rem){.grid-3col-responsive{grid-template-columns:repeat(3,minmax(0,1fr))}}.text-error-soft{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-red-700)}.dark .text-error-soft{color:var(--color-red-300)}.text-error-strong{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-red-800)}.dark .text-error-strong{color:var(--color-red-200)}.row-content-indent{padding-left:calc(var(--spacing) * 6)}.flex-start{align-items:flex-start;display:flex}.inline-clickable{cursor:pointer;align-items:center;gap:calc(var(--spacing) * 2);display:inline-flex}.grid-2col{gap:calc(var(--spacing) * 4);grid-template-columns:repeat(2,minmax(0,1fr));display:grid}.empty-state-cell{padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 4);text-align:center}.form-error-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-red-500)}.dark .form-error-xs{color:var(--color-red-400)}.text-page-heading{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height));--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold);color:var(--color-gray-900)}.dark .text-page-heading{color:var(--color-white)}.text-table-header{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider);color:var(--color-gray-500);text-transform:uppercase}.dark .text-table-header{color:var(--color-gray-400)}.panel-header-row{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-200);padding-inline:calc(var(--spacing) * 6);padding-block:calc(var(--spacing) * 4)}.dark .panel-header-row{border-color:var(--color-gray-700)}.grid-2col-responsive{gap:calc(var(--spacing) * 4);grid-template-columns:repeat(1,minmax(0,1fr));display:grid}@media(min-width:48rem){.grid-2col-responsive{grid-template-columns:repeat(2,minmax(0,1fr))}}.panel-header{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-200);padding-block:calc(var(--spacing) * 3)}.dark .panel-header{border-color:var(--color-gray-700)}.gap-sm{gap:calc(var(--spacing) * 2)}.gap-md{gap:calc(var(--spacing) * 4)}.gap-lg{gap:calc(var(--spacing) * 6)}.mb-sm{margin-bottom:calc(var(--spacing) * 2)}.mb-md{margin-bottom:calc(var(--spacing) * 4)}.mb-lg{margin-bottom:calc(var(--spacing) * 6)}.surface{background-color:var(--color-white)}.dark .surface{background-color:var(--color-gray-800)}.surface-alt{background-color:var(--color-gray-50)}.dark .surface-alt{background-color:var(--color-gray-900)}.card-border{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200)}.dark .card-border{border-color:var(--color-gray-700)}.card-border-xl{border-radius:var(--radius-xl);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200)}.dark .card-border-xl{border-color:var(--color-gray-700)}.border-light{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-300)}.dark .border-light{border-color:var(--color-gray-600)}.disabled-state:disabled{cursor:not-allowed;opacity:.5}.focus-ring:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--color-blue-500);--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-outline-style:none;outline-style:none}.dark .focus-ring:focus{--tw-ring-offset-color:var(--color-gray-800)}.focus-ring-inset:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--color-blue-500);--tw-outline-style:none;--tw-ring-inset:inset;outline-style:none}@media(hover:hover){.hover-bg:hover{background-color:var(--color-gray-100)}.dark .hover-bg:hover{background-color:var(--color-gray-700)}.hover-bg-light:hover{background-color:var(--color-gray-50)}.dark .hover-bg-light:hover{background-color:var(--color-gray-800)}}.btn-base{border-radius:var(--radius-lg);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;justify-content:center;align-items:center;transition-duration:.2s;display:inline-flex}.btn-base:disabled{cursor:not-allowed;opacity:.5}.btn-size-sm{padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 1.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.btn-size-md{padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.btn-size-lg{padding-inline:calc(var(--spacing) * 6);padding-block:calc(var(--spacing) * 3);font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.badge-base{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);border-radius:3.40282e38px;align-items:center;display:inline-flex}.badge-size-sm{padding-inline:calc(var(--spacing) * 2);padding-block:calc(var(--spacing) * .5)}.badge-size-md{padding-inline:calc(var(--spacing) * 2.5);padding-block:calc(var(--spacing) * 1)}.shadow-card{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-dropdown{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}@media(hover:hover){.shadow-hover:hover{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a), 0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}}.page-title{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-gray-900)}@media(min-width:40rem){.page-title{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}}.dark .page-title{color:var(--color-white)}.page-description{margin-top:calc(var(--spacing) * 1);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-600)}.dark .page-description{color:var(--color-gray-400)}.page-action-primary{align-items:center;gap:calc(var(--spacing) * 2);border-radius:var(--radius-lg);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));background-color:var(--color-blue-600);color:var(--color-white);display:flex}@media(hover:hover){.page-action-primary:hover{background-color:var(--color-blue-700)}}.page-action-secondary{align-items:center;gap:calc(var(--spacing) * 2);border-radius:var(--radius-lg);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));background-color:var(--color-gray-200);color:var(--color-gray-900);display:flex}@media(hover:hover){.page-action-secondary:hover{background-color:var(--color-gray-300)}}.dark .page-action-secondary{background-color:var(--color-gray-700);color:var(--color-gray-200)}@media(hover:hover){.dark .page-action-secondary:hover{background-color:var(--color-gray-600)}}.page-action-danger{align-items:center;gap:calc(var(--spacing) * 2);border-radius:var(--radius-lg);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));background-color:var(--color-red-600);color:var(--color-white);display:flex}@media(hover:hover){.page-action-danger:hover{background-color:var(--color-red-700)}}.page-action-ghost{align-items:center;gap:calc(var(--spacing) * 2);border-radius:var(--radius-lg);padding:calc(var(--spacing) * 2);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));color:var(--color-gray-500);display:flex}@media(hover:hover){.page-action-ghost:hover{background-color:var(--color-gray-100);color:var(--color-gray-700)}}.dark .page-action-ghost{color:var(--color-gray-400)}@media(hover:hover){.dark .page-action-ghost:hover{background-color:var(--color-gray-800);color:var(--color-gray-200)}}.tab-container{margin-bottom:calc(var(--spacing) * 6);gap:calc(var(--spacing) * 6);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-200);display:flex}.dark .tab-container{border-color:var(--color-gray-700)}.tab-btn{align-items:center;gap:calc(var(--spacing) * 2);border-bottom-style:var(--tw-border-style);padding-inline:calc(var(--spacing) * 1);padding-block:calc(var(--spacing) * 3);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));color:var(--color-gray-600);border-color:#0000;border-bottom-width:2px;margin-bottom:-1px;display:flex}@media(hover:hover){.tab-btn:hover{color:var(--color-gray-900)}}.dark .tab-btn{color:var(--color-gray-400)}@media(hover:hover){.dark .tab-btn:hover{color:var(--color-gray-200)}}.tab-btn-active{align-items:center;gap:calc(var(--spacing) * 2);border-bottom-style:var(--tw-border-style);padding-inline:calc(var(--spacing) * 1);padding-block:calc(var(--spacing) * 3);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));border-bottom-width:2px;border-color:var(--color-blue-600);color:var(--color-blue-600);margin-bottom:-1px;display:flex}.dark .tab-btn-active{color:var(--color-blue-400)}.tab-badge{padding-inline:calc(var(--spacing) * 2);padding-block:calc(var(--spacing) * .5);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);background-color:var(--color-gray-100);color:var(--color-gray-600);border-radius:3.40282e38px}.dark .tab-badge{background-color:var(--color-gray-700);color:var(--color-gray-400)}.tab-badge-active{padding-inline:calc(var(--spacing) * 2);padding-block:calc(var(--spacing) * .5);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);background-color:var(--color-blue-100);color:var(--color-blue-600);border-radius:3.40282e38px}.dark .tab-badge-active{background-color:#1c398e33}@supports (color:color-mix(in lab,red,red)){.dark .tab-badge-active{background-color:color-mix(in oklab,var(--color-blue-900) 20%,transparent)}}.dark .tab-badge-active{color:var(--color-blue-400)}.sticky-tab-nav{top:calc(var(--spacing) * 0);z-index:40;margin-inline:calc(var(--spacing) * -6);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-200);background-color:var(--color-gray-50);padding-inline:calc(var(--spacing) * 6);position:sticky}.dark .sticky-tab-nav{border-color:var(--color-gray-700);background-color:var(--color-gray-900)}.sticky-tab-nav-responsive{top:calc(var(--spacing) * 0);z-index:40;margin-inline:calc(var(--spacing) * -4);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-200);background-color:var(--color-gray-50);padding-inline:calc(var(--spacing) * 4);position:sticky}@media(min-width:40rem){.sticky-tab-nav-responsive{margin-inline:calc(var(--spacing) * -6);padding-inline:calc(var(--spacing) * 6)}}@media(min-width:64rem){.sticky-tab-nav-responsive{margin-inline:calc(var(--spacing) * -8);padding-inline:calc(var(--spacing) * 8)}}.dark .sticky-tab-nav-responsive{border-color:var(--color-gray-700);background-color:var(--color-gray-900)}.sticky-section-header{top:calc(var(--spacing) * 0);z-index:40;border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-200);background-color:var(--color-gray-50);position:sticky}.dark .sticky-section-header{border-color:var(--color-gray-700);background-color:var(--color-gray-900)}.sticky-footer-buttons{bottom:calc(var(--spacing) * 0);z-index:10;margin-top:calc(var(--spacing) * 6);justify-content:flex-end;align-items:center;gap:calc(var(--spacing) * 3);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3);border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-gray-50);display:flex;position:sticky}.dark .sticky-footer-buttons{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}.sticky-footer-buttons-between{bottom:calc(var(--spacing) * 0);z-index:10;margin-top:calc(var(--spacing) * 6);justify-content:space-between;align-items:center;gap:calc(var(--spacing) * 3);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3);border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-gray-50);display:flex;position:sticky}.dark .sticky-footer-buttons-between{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}.footer-buttons-between{justify-content:space-between;align-items:center;gap:calc(var(--spacing) * 3);border-top-style:var(--tw-border-style);border-top-width:1px;border-color:var(--color-gray-200);background-color:var(--color-gray-50);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3);display:flex}.dark .footer-buttons-between{border-color:var(--color-gray-700);background-color:var(--color-gray-900)}.tab-content{padding-top:calc(var(--spacing) * 6)}.admin-page-content{background-color:var(--color-gray-50);min-height:100vh;padding:calc(var(--spacing) * 6)}.dark .admin-page-content{background-color:var(--color-gray-900)}.admin-page-content-compact{background-color:var(--color-gray-50);min-height:100vh;padding:calc(var(--spacing) * 4)}.dark .admin-page-content-compact{background-color:var(--color-gray-900)}.admin-page-content-responsive{background-color:var(--color-gray-50);min-height:100vh;padding:calc(var(--spacing) * 4)}@media(min-width:40rem){.admin-page-content-responsive{padding:calc(var(--spacing) * 6)}}@media(min-width:64rem){.admin-page-content-responsive{padding:calc(var(--spacing) * 8)}}.dark .admin-page-content-responsive{background-color:var(--color-gray-900)}.admin-page-content-responsive-fluid{background-color:var(--color-gray-50);padding:calc(var(--spacing) * 4)}@media(min-width:40rem){.admin-page-content-responsive-fluid{padding:calc(var(--spacing) * 6)}}@media(min-width:64rem){.admin-page-content-responsive-fluid{padding:calc(var(--spacing) * 8)}}.dark .admin-page-content-responsive-fluid{background-color:var(--color-gray-900)}.admin-page-content-fluid{background-color:var(--color-gray-50);padding:calc(var(--spacing) * 6)}.dark .admin-page-content-fluid{background-color:var(--color-gray-900)}.admin-page-content-fluid-compact{background-color:var(--color-gray-50);padding:calc(var(--spacing) * 4)}.dark .admin-page-content-fluid-compact{background-color:var(--color-gray-900)}.admin-page-content-viewport{background-color:var(--color-gray-50);height:calc(100vh - 64px);padding:calc(var(--spacing) * 6);overflow:hidden}.dark .admin-page-content-viewport{background-color:var(--color-gray-900)}.admin-page-content-viewport-compact{background-color:var(--color-gray-50);height:calc(100vh - 64px);padding:calc(var(--spacing) * 4);overflow:hidden}.dark .admin-page-content-viewport-compact{background-color:var(--color-gray-900)}.bulk-actions-bar{margin-bottom:calc(var(--spacing) * 4);align-items:center;gap:calc(var(--spacing) * 3);border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-gray-100);padding:calc(var(--spacing) * 3);display:flex}.dark .bulk-actions-bar{border-color:var(--color-gray-600);background-color:#36415380}@supports (color:color-mix(in lab,red,red)){.dark .bulk-actions-bar{background-color:color-mix(in oklab,var(--color-gray-700) 50%,transparent)}}:where(.admin-sidebar-menu-list>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * .5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * .5) * calc(1 - var(--tw-space-y-reverse)))}.admin-sidebar-menu-list{padding-inline:calc(var(--spacing) * 4);padding-bottom:calc(var(--spacing) * 4)}.admin-sidebar-item{border-radius:var(--radius-lg);width:100%;padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));justify-content:space-between;align-items:center;display:flex}.admin-sidebar-subitem{border-radius:var(--radius-lg);width:100%;padding-block:6px;padding-right:calc(var(--spacing) * 3);--tw-font-weight:var(--font-weight-medium);font-size:13px;font-weight:var(--font-weight-medium);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));justify-content:space-between;align-items:center;padding-left:30px;display:flex}.admin-sidebar-item-active{background-color:var(--color-slate-100);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-slate-900)}.dark .admin-sidebar-item-active{background-color:var(--color-slate-700);color:var(--color-white)}.admin-sidebar-item-active-parent{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-slate-900)}.dark .admin-sidebar-item-active-parent{color:var(--color-white)}.admin-sidebar-item-default{color:var(--color-slate-500)}@media(hover:hover){.admin-sidebar-item-default:hover{background-color:var(--color-slate-50);color:var(--color-slate-900)}}.dark .admin-sidebar-item-default{color:var(--color-slate-400)}@media(hover:hover){.dark .admin-sidebar-item-default:hover{background-color:var(--color-slate-800);color:var(--color-white)}}.admin-sidebar-item-disabled{cursor:default;color:var(--color-slate-400)}.dark .admin-sidebar-item-disabled{color:var(--color-slate-500)}.admin-sidebar-icon-box{flex-shrink:0;justify-content:center;align-items:center;width:18px;height:18px;display:flex}.admin-sidebar-chevron-trail{align-items:center;gap:calc(var(--spacing) * 1);display:flex}.admin-sidebar-item-content{align-items:center;gap:calc(var(--spacing) * 2);display:flex}.admin-sidebar-module-icon{margin-right:calc(var(--spacing) * 2);height:calc(var(--spacing) * 3);width:calc(var(--spacing) * 3);color:var(--color-slate-400);opacity:.6}.dark .admin-sidebar-module-icon{color:var(--color-slate-500)}.admin-sidebar-tree-line-vertical{top:calc(var(--spacing) * 0);bottom:calc(var(--spacing) * 0);z-index:0;border-left-style:var(--tw-border-style);border-left-width:1px;border-color:var(--color-slate-300);position:absolute;left:12px}.dark .admin-sidebar-tree-line-vertical{border-color:var(--color-slate-600)}.admin-sidebar-tree-line-curve{top:calc(var(--spacing) * 0);z-index:10;border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-left-style:var(--tw-border-style);width:14px;height:50%;transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));border-left-width:1px;border-color:var(--color-slate-300);border-bottom-left-radius:10px;position:absolute;left:12px}@media(hover:hover){.admin-sidebar-tree-line-curve:is(:where(.group):hover *){border-color:var(--color-blue-400)}}.dark .admin-sidebar-tree-line-curve{border-color:var(--color-slate-600)}@media(hover:hover){.dark .admin-sidebar-tree-line-curve:is(:where(.group):hover *){border-color:var(--color-blue-500)}}.admin-sidebar-header{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-slate-200);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 4);justify-content:space-between;align-items:center;display:flex}.dark .admin-sidebar-header{border-color:var(--color-slate-700)}.admin-sidebar-collapse-btn{border-radius:var(--radius-md);padding:calc(var(--spacing) * 2);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}@media(hover:hover){.admin-sidebar-collapse-btn:hover{background-color:var(--color-slate-100)}.dark .admin-sidebar-collapse-btn:hover{background-color:var(--color-slate-700)}}.tab-nav-default{gap:calc(var(--spacing) * 2);display:flex}.tab-nav-underline{gap:calc(var(--spacing) * 0);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-200);display:flex}.dark .tab-nav-underline{border-color:var(--color-gray-700)}.tab-btn-base{align-items:center;gap:calc(var(--spacing) * 2);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));display:flex}.tab-btn-default{border-bottom-style:var(--tw-border-style);color:var(--color-gray-600);border-color:#0000;border-bottom-width:2px}@media(hover:hover){.tab-btn-default:hover{background-color:var(--color-gray-50);color:var(--color-gray-900)}}.dark .tab-btn-default{color:var(--color-gray-400)}@media(hover:hover){.dark .tab-btn-default:hover{background-color:var(--color-gray-700);color:var(--color-gray-200)}}.tab-btn-default-active{border-bottom-style:var(--tw-border-style);border-bottom-width:2px;border-color:var(--color-blue-600);background-color:var(--color-blue-50);color:var(--color-blue-600)}.dark .tab-btn-default-active{border-color:var(--color-blue-400);background-color:#1c398e33}@supports (color:color-mix(in lab,red,red)){.dark .tab-btn-default-active{background-color:color-mix(in oklab,var(--color-blue-900) 20%,transparent)}}.dark .tab-btn-default-active{color:var(--color-blue-400)}.tab-btn-underline{border-bottom-style:var(--tw-border-style);color:var(--color-gray-600);border-color:#0000;border-bottom-width:2px}@media(hover:hover){.tab-btn-underline:hover{border-color:var(--color-gray-300);color:var(--color-gray-900)}}.dark .tab-btn-underline{color:var(--color-gray-400)}@media(hover:hover){.dark .tab-btn-underline:hover{border-color:var(--color-gray-600);color:var(--color-gray-200)}}.tab-btn-underline-active{border-bottom-style:var(--tw-border-style);border-bottom-width:2px;border-color:var(--color-blue-600);color:var(--color-blue-600)}.dark .tab-btn-underline-active{border-color:var(--color-blue-400);color:var(--color-blue-400)}.tab-btn-pills{border-radius:var(--radius-lg);color:var(--color-gray-600)}@media(hover:hover){.tab-btn-pills:hover{background-color:var(--color-gray-100)}}.dark .tab-btn-pills{color:var(--color-gray-400)}@media(hover:hover){.dark .tab-btn-pills:hover{background-color:var(--color-gray-700)}}.tab-btn-pills-active{border-radius:var(--radius-lg);background-color:var(--color-blue-600);color:var(--color-white)}.dark .tab-btn-pills-active{background-color:var(--color-blue-500)}.tab-btn-disabled{cursor:not-allowed;color:var(--color-gray-400);opacity:.5}.dark .tab-btn-disabled{color:var(--color-gray-600)}.toggle-container{align-items:flex-start;column-gap:calc(var(--spacing) * 2);flex-wrap:wrap;width:auto;display:inline-flex}.toggle-switch-wrapper{flex-shrink:0;align-items:center;display:inline-flex;position:relative}.toggle-switch-track{background-color:var(--color-gray-200);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));border-radius:3.40282e38px;position:relative}.toggle-switch-track:is(:where(.peer):focus~*){--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--color-blue-300);--tw-outline-style:none;outline-style:none}.toggle-switch-track:is(:where(.peer):checked~*){background-color:var(--color-blue-600)}.dark .toggle-switch-track{background-color:var(--color-gray-700)}.dark .toggle-switch-track:is(:where(.peer):focus~*){--tw-ring-color:var(--color-blue-800)}.dark .toggle-switch-track:is(:where(.peer):checked~*){background-color:var(--color-blue-500)}.toggle-switch-track:after{top:calc(var(--spacing) * .5);left:calc(var(--spacing) * .5);--tw-content:"";border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-300);background-color:var(--color-white);content:var(--tw-content);transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));border-radius:3.40282e38px;position:absolute}.dark .toggle-switch-track:after{content:var(--tw-content);border-color:var(--color-gray-600)}.toggle-switch-track:is(:where(.peer):checked~*):after{--tw-translate-x:100%;translate:var(--tw-translate-x) var(--tw-translate-y);content:var(--tw-content);border-color:var(--color-white)}.toggle-label{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-900)}.dark .toggle-label{color:var(--color-white)}.toggle-description{margin-top:calc(var(--spacing) * 1);width:100%;font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-gray-500)}.dark .toggle-description{color:var(--color-gray-400)}.admin-footer{margin-top:calc(var(--spacing) * 6);padding-inline:calc(var(--spacing) * 6);padding-block:calc(var(--spacing) * 4)}.admin-footer-row{justify-content:space-between;align-items:center;gap:calc(var(--spacing) * 4);flex-direction:column;display:flex}@media(min-width:48rem){.admin-footer-row{flex-direction:row}}.admin-footer-group{align-items:center;gap:calc(var(--spacing) * 4);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-600);display:flex}.dark .admin-footer-group{color:var(--color-gray-400)}.admin-footer-link-hover{cursor:pointer;transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}@media(hover:hover){.admin-footer-link-hover:hover{color:var(--color-blue-600)}.dark .admin-footer-link-hover:hover{color:var(--color-blue-400)}}.admin-footer-link{align-items:center;gap:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-600);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));display:flex}@media(hover:hover){.admin-footer-link:hover{color:var(--color-blue-600)}}.dark .admin-footer-link{color:var(--color-gray-400)}@media(hover:hover){.dark .admin-footer-link:hover{color:var(--color-blue-400)}}.responsive-grid{gap:calc(var(--spacing) * 4);grid-template-columns:repeat(1,minmax(0,1fr));display:grid}.grid-cols-1-lg-3{gap:calc(var(--spacing) * 6);grid-template-columns:repeat(1,minmax(0,1fr));display:grid}.col-span-1-lg,.col-span-2-lg{grid-column:span 1/span 1}.modal-description{margin-top:calc(var(--spacing) * 1);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-500)}.dark .modal-description{color:var(--color-gray-400)}.toast-message{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}}@layer utilities{.pointer-events-auto{pointer-events:auto}.pointer-events-none{pointer-events:none}.collapse{visibility:collapse}.invisible{visibility:hidden}.visible{visibility:visible}.visible\!{visibility:visible!important}.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.sticky{position:sticky}.inset-0{inset:calc(var(--spacing) * 0)}.inset-1{inset:calc(var(--spacing) * 1)}.inset-2{inset:calc(var(--spacing) * 2)}.inset-3{inset:calc(var(--spacing) * 3)}.inset-4{inset:calc(var(--spacing) * 4)}.inset-5{inset:calc(var(--spacing) * 5)}.inset-6{inset:calc(var(--spacing) * 6)}.inset-8{inset:calc(var(--spacing) * 8)}.inset-auto{inset:auto}.inset-x-0{inset-inline:calc(var(--spacing) * 0)}.inset-x-1{inset-inline:calc(var(--spacing) * 1)}.inset-x-2{inset-inline:calc(var(--spacing) * 2)}.inset-x-3{inset-inline:calc(var(--spacing) * 3)}.inset-x-4{inset-inline:calc(var(--spacing) * 4)}.inset-x-auto{inset-inline:auto}.inset-y-0{inset-block:calc(var(--spacing) * 0)}.inset-y-1{inset-block:calc(var(--spacing) * 1)}.inset-y-2{inset-block:calc(var(--spacing) * 2)}.inset-y-3{inset-block:calc(var(--spacing) * 3)}.inset-y-4{inset-block:calc(var(--spacing) * 4)}.inset-y-auto{inset-block:auto}.-top-1{top:calc(var(--spacing) * -1)}.-top-2{top:calc(var(--spacing) * -2)}.-top-3{top:calc(var(--spacing) * -3)}.-top-4{top:calc(var(--spacing) * -4)}.top-0{top:calc(var(--spacing) * 0)}.top-1{top:calc(var(--spacing) * 1)}.top-1\.5{top:calc(var(--spacing) * 1.5)}.top-1\/2{top:50%}.top-1\/3{top:33.3333%}.top-1\/4{top:25%}.top-2{top:calc(var(--spacing) * 2)}.top-2\/3{top:66.6667%}.top-3{top:calc(var(--spacing) * 3)}.top-3\/4{top:75%}.top-4{top:calc(var(--spacing) * 4)}.top-5{top:calc(var(--spacing) * 5)}.top-6{top:calc(var(--spacing) * 6)}.top-8{top:calc(var(--spacing) * 8)}.top-10{top:calc(var(--spacing) * 10)}.top-12{top:calc(var(--spacing) * 12)}.top-16{top:calc(var(--spacing) * 16)}.top-auto{top:auto}.top-full{top:100%}.-right-1{right:calc(var(--spacing) * -1)}.-right-2{right:calc(var(--spacing) * -2)}.-right-3{right:calc(var(--spacing) * -3)}.-right-4{right:calc(var(--spacing) * -4)}.right-0{right:calc(var(--spacing) * 0)}.right-1{right:calc(var(--spacing) * 1)}.right-1\/2{right:50%}.right-2{right:calc(var(--spacing) * 2)}.right-3{right:calc(var(--spacing) * 3)}.right-4{right:calc(var(--spacing) * 4)}.right-5{right:calc(var(--spacing) * 5)}.right-6{right:calc(var(--spacing) * 6)}.right-8{right:calc(var(--spacing) * 8)}.right-10{right:calc(var(--spacing) * 10)}.right-12{right:calc(var(--spacing) * 12)}.right-16{right:calc(var(--spacing) * 16)}.right-auto{right:auto}.right-full{right:100%}.-bottom-1{bottom:calc(var(--spacing) * -1)}.-bottom-2{bottom:calc(var(--spacing) * -2)}.-bottom-3{bottom:calc(var(--spacing) * -3)}.-bottom-4{bottom:calc(var(--spacing) * -4)}.bottom-0{bottom:calc(var(--spacing) * 0)}.bottom-1{bottom:calc(var(--spacing) * 1)}.bottom-1\/2{bottom:50%}.bottom-2{bottom:calc(var(--spacing) * 2)}.bottom-3{bottom:calc(var(--spacing) * 3)}.bottom-4{bottom:calc(var(--spacing) * 4)}.bottom-5{bottom:calc(var(--spacing) * 5)}.bottom-6{bottom:calc(var(--spacing) * 6)}.bottom-8{bottom:calc(var(--spacing) * 8)}.bottom-10{bottom:calc(var(--spacing) * 10)}.bottom-12{bottom:calc(var(--spacing) * 12)}.bottom-16{bottom:calc(var(--spacing) * 16)}.bottom-auto{bottom:auto}.bottom-full{bottom:100%}.-left-1{left:calc(var(--spacing) * -1)}.-left-2{left:calc(var(--spacing) * -2)}.-left-3{left:calc(var(--spacing) * -3)}.-left-4{left:calc(var(--spacing) * -4)}.left-0{left:calc(var(--spacing) * 0)}.left-1{left:calc(var(--spacing) * 1)}.left-1\.5{left:calc(var(--spacing) * 1.5)}.left-1\/2{left:50%}.left-2{left:calc(var(--spacing) * 2)}.left-3{left:calc(var(--spacing) * 3)}.left-4{left:calc(var(--spacing) * 4)}.left-5{left:calc(var(--spacing) * 5)}.left-6{left:calc(var(--spacing) * 6)}.left-8{left:calc(var(--spacing) * 8)}.left-10{left:calc(var(--spacing) * 10)}.left-12{left:calc(var(--spacing) * 12)}.left-16{left:calc(var(--spacing) * 16)}.left-auto{left:auto}.left-full{left:100%}.isolate{isolation:isolate}.isolation-auto{isolation:auto}.z-0{z-index:0}.z-10{z-index:10}.z-20{z-index:20}.z-30{z-index:30}.z-40{z-index:40}.z-50{z-index:50}.z-\[999\]{z-index:999}.z-\[9999\]{z-index:9999}.z-auto{z-index:auto}.order-1{order:1}.order-2{order:2}.order-3{order:3}.order-4{order:4}.order-5{order:5}.order-6{order:6}.order-first{order:-9999}.order-last{order:9999}.order-none{order:0}.col-span-1{grid-column:span 1/span 1}.col-span-2{grid-column:span 2/span 2}.col-span-3{grid-column:span 3/span 3}.col-span-4{grid-column:span 4/span 4}.col-span-5{grid-column:span 5/span 5}.col-span-6{grid-column:span 6/span 6}.col-span-7{grid-column:span 7/span 7}.col-span-8{grid-column:span 8/span 8}.col-span-9{grid-column:span 9/span 9}.col-span-10{grid-column:span 10/span 10}.col-span-11{grid-column:span 11/span 11}.col-span-12{grid-column:span 12/span 12}.col-span-full{grid-column:1/-1}.col-start-1{grid-column-start:1}.col-start-2{grid-column-start:2}.col-start-3{grid-column-start:3}.col-start-4{grid-column-start:4}.col-start-5{grid-column-start:5}.col-start-6{grid-column-start:6}.col-start-7{grid-column-start:7}.col-start-8{grid-column-start:8}.col-start-9{grid-column-start:9}.col-start-10{grid-column-start:10}.col-start-11{grid-column-start:11}.col-start-12{grid-column-start:12}.col-start-13{grid-column-start:13}.col-end-1{grid-column-end:1}.col-end-2{grid-column-end:2}.col-end-3{grid-column-end:3}.col-end-4{grid-column-end:4}.col-end-5{grid-column-end:5}.col-end-6{grid-column-end:6}.col-end-7{grid-column-end:7}.col-end-8{grid-column-end:8}.col-end-9{grid-column-end:9}.col-end-10{grid-column-end:10}.col-end-11{grid-column-end:11}.col-end-12{grid-column-end:12}.col-end-13{grid-column-end:13}.row-span-1{grid-row:span 1/span 1}.row-span-2{grid-row:span 2/span 2}.row-span-3{grid-row:span 3/span 3}.row-span-4{grid-row:span 4/span 4}.row-span-5{grid-row:span 5/span 5}.row-span-6{grid-row:span 6/span 6}.row-span-full{grid-row:1/-1}.row-start-1{grid-row-start:1}.row-start-2{grid-row-start:2}.row-start-3{grid-row-start:3}.row-start-4{grid-row-start:4}.row-start-5{grid-row-start:5}.row-start-6{grid-row-start:6}.row-start-7{grid-row-start:7}.row-end-1{grid-row-end:1}.row-end-2{grid-row-end:2}.row-end-3{grid-row-end:3}.row-end-4{grid-row-end:4}.row-end-5{grid-row-end:5}.row-end-6{grid-row-end:6}.row-end-7{grid-row-end:7}.float-end{float:inline-end}.float-left{float:left}.float-none{float:none}.float-right{float:right}.float-start{float:inline-start}.clear-both{clear:both}.clear-end{clear:inline-end}.clear-left{clear:left}.clear-none{clear:none}.clear-right{clear:right}.clear-start{clear:inline-start}.container{width:100%}@media(min-width:40rem){.container{max-width:40rem}}@media(min-width:48rem){.container{max-width:48rem}}@media(min-width:64rem){.container{max-width:64rem}}@media(min-width:80rem){.container{max-width:80rem}}@media(min-width:96rem){.container{max-width:96rem}}.\!m-0{margin:calc(var(--spacing) * 0)!important}.\!m-1{margin:calc(var(--spacing) * 1)!important}.\!m-2{margin:calc(var(--spacing) * 2)!important}.\!m-3{margin:calc(var(--spacing) * 3)!important}.\!m-4{margin:calc(var(--spacing) * 4)!important}.\!m-5{margin:calc(var(--spacing) * 5)!important}.\!m-6{margin:calc(var(--spacing) * 6)!important}.\!m-8{margin:calc(var(--spacing) * 8)!important}.\!m-10{margin:calc(var(--spacing) * 10)!important}.\!m-12{margin:calc(var(--spacing) * 12)!important}.\!m-16{margin:calc(var(--spacing) * 16)!important}.\!m-auto{margin:auto!important}.-m-1{margin:calc(var(--spacing) * -1)}.-m-2{margin:calc(var(--spacing) * -2)}.-m-3{margin:calc(var(--spacing) * -3)}.-m-4{margin:calc(var(--spacing) * -4)}.-m-5{margin:calc(var(--spacing) * -5)}.-m-6{margin:calc(var(--spacing) * -6)}.-m-8{margin:calc(var(--spacing) * -8)}.m-0{margin:calc(var(--spacing) * 0)}.m-1{margin:calc(var(--spacing) * 1)}.m-2{margin:calc(var(--spacing) * 2)}.m-3{margin:calc(var(--spacing) * 3)}.m-4{margin:calc(var(--spacing) * 4)}.m-5{margin:calc(var(--spacing) * 5)}.m-6{margin:calc(var(--spacing) * 6)}.m-8{margin:calc(var(--spacing) * 8)}.m-10{margin:calc(var(--spacing) * 10)}.m-12{margin:calc(var(--spacing) * 12)}.m-16{margin:calc(var(--spacing) * 16)}.m-auto{margin:auto}.\!mx-0{margin-inline:calc(var(--spacing) * 0)!important}.\!mx-1{margin-inline:calc(var(--spacing) * 1)!important}.\!mx-2{margin-inline:calc(var(--spacing) * 2)!important}.\!mx-3{margin-inline:calc(var(--spacing) * 3)!important}.\!mx-4{margin-inline:calc(var(--spacing) * 4)!important}.\!mx-5{margin-inline:calc(var(--spacing) * 5)!important}.\!mx-6{margin-inline:calc(var(--spacing) * 6)!important}.\!mx-8{margin-inline:calc(var(--spacing) * 8)!important}.\!mx-10{margin-inline:calc(var(--spacing) * 10)!important}.\!mx-12{margin-inline:calc(var(--spacing) * 12)!important}.\!mx-16{margin-inline:calc(var(--spacing) * 16)!important}.\!mx-auto{margin-inline:auto!important}.-mx-1{margin-inline:calc(var(--spacing) * -1)}.-mx-2{margin-inline:calc(var(--spacing) * -2)}.-mx-3{margin-inline:calc(var(--spacing) * -3)}.-mx-4{margin-inline:calc(var(--spacing) * -4)}.-mx-5{margin-inline:calc(var(--spacing) * -5)}.-mx-6{margin-inline:calc(var(--spacing) * -6)}.-mx-8{margin-inline:calc(var(--spacing) * -8)}.mx-0{margin-inline:calc(var(--spacing) * 0)}.mx-1{margin-inline:calc(var(--spacing) * 1)}.mx-1\.5{margin-inline:calc(var(--spacing) * 1.5)}.mx-2{margin-inline:calc(var(--spacing) * 2)}.mx-3{margin-inline:calc(var(--spacing) * 3)}.mx-4{margin-inline:calc(var(--spacing) * 4)}.mx-5{margin-inline:calc(var(--spacing) * 5)}.mx-6{margin-inline:calc(var(--spacing) * 6)}.mx-8{margin-inline:calc(var(--spacing) * 8)}.mx-10{margin-inline:calc(var(--spacing) * 10)}.mx-12{margin-inline:calc(var(--spacing) * 12)}.mx-16{margin-inline:calc(var(--spacing) * 16)}.mx-auto{margin-inline:auto}.\!my-0{margin-block:calc(var(--spacing) * 0)!important}.\!my-1{margin-block:calc(var(--spacing) * 1)!important}.\!my-2{margin-block:calc(var(--spacing) * 2)!important}.\!my-3{margin-block:calc(var(--spacing) * 3)!important}.\!my-4{margin-block:calc(var(--spacing) * 4)!important}.\!my-5{margin-block:calc(var(--spacing) * 5)!important}.\!my-6{margin-block:calc(var(--spacing) * 6)!important}.\!my-8{margin-block:calc(var(--spacing) * 8)!important}.\!my-10{margin-block:calc(var(--spacing) * 10)!important}.\!my-12{margin-block:calc(var(--spacing) * 12)!important}.\!my-16{margin-block:calc(var(--spacing) * 16)!important}.\!my-auto{margin-block:auto!important}.-my-1{margin-block:calc(var(--spacing) * -1)}.-my-2{margin-block:calc(var(--spacing) * -2)}.-my-3{margin-block:calc(var(--spacing) * -3)}.-my-4{margin-block:calc(var(--spacing) * -4)}.-my-5{margin-block:calc(var(--spacing) * -5)}.-my-6{margin-block:calc(var(--spacing) * -6)}.-my-8{margin-block:calc(var(--spacing) * -8)}.my-0{margin-block:calc(var(--spacing) * 0)}.my-1{margin-block:calc(var(--spacing) * 1)}.my-2{margin-block:calc(var(--spacing) * 2)}.my-3{margin-block:calc(var(--spacing) * 3)}.my-4{margin-block:calc(var(--spacing) * 4)}.my-5{margin-block:calc(var(--spacing) * 5)}.my-6{margin-block:calc(var(--spacing) * 6)}.my-8{margin-block:calc(var(--spacing) * 8)}.my-10{margin-block:calc(var(--spacing) * 10)}.my-12{margin-block:calc(var(--spacing) * 12)}.my-16{margin-block:calc(var(--spacing) * 16)}.my-auto{margin-block:auto}.-mt-1{margin-top:calc(var(--spacing) * -1)}.-mt-2{margin-top:calc(var(--spacing) * -2)}.-mt-3{margin-top:calc(var(--spacing) * -3)}.-mt-4{margin-top:calc(var(--spacing) * -4)}.-mt-5{margin-top:calc(var(--spacing) * -5)}.-mt-6{margin-top:calc(var(--spacing) * -6)}.-mt-8{margin-top:calc(var(--spacing) * -8)}.mt-0{margin-top:calc(var(--spacing) * 0)}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:calc(var(--spacing) * 1)}.mt-1\.5{margin-top:calc(var(--spacing) * 1.5)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mt-4{margin-top:calc(var(--spacing) * 4)}.mt-5{margin-top:calc(var(--spacing) * 5)}.mt-6{margin-top:calc(var(--spacing) * 6)}.mt-8{margin-top:calc(var(--spacing) * 8)}.mt-10{margin-top:calc(var(--spacing) * 10)}.mt-12{margin-top:calc(var(--spacing) * 12)}.mt-16{margin-top:calc(var(--spacing) * 16)}.mt-auto{margin-top:auto}.-mr-1{margin-right:calc(var(--spacing) * -1)}.-mr-2{margin-right:calc(var(--spacing) * -2)}.-mr-3{margin-right:calc(var(--spacing) * -3)}.-mr-4{margin-right:calc(var(--spacing) * -4)}.-mr-5{margin-right:calc(var(--spacing) * -5)}.-mr-6{margin-right:calc(var(--spacing) * -6)}.-mr-8{margin-right:calc(var(--spacing) * -8)}.mr-0{margin-right:calc(var(--spacing) * 0)}.mr-1{margin-right:calc(var(--spacing) * 1)}.mr-2{margin-right:calc(var(--spacing) * 2)}.mr-3{margin-right:calc(var(--spacing) * 3)}.mr-4{margin-right:calc(var(--spacing) * 4)}.mr-5{margin-right:calc(var(--spacing) * 5)}.mr-6{margin-right:calc(var(--spacing) * 6)}.mr-8{margin-right:calc(var(--spacing) * 8)}.mr-10{margin-right:calc(var(--spacing) * 10)}.mr-12{margin-right:calc(var(--spacing) * 12)}.mr-16{margin-right:calc(var(--spacing) * 16)}.mr-auto{margin-right:auto}.-mb-1{margin-bottom:calc(var(--spacing) * -1)}.-mb-2{margin-bottom:calc(var(--spacing) * -2)}.-mb-3{margin-bottom:calc(var(--spacing) * -3)}.-mb-4{margin-bottom:calc(var(--spacing) * -4)}.-mb-5{margin-bottom:calc(var(--spacing) * -5)}.-mb-6{margin-bottom:calc(var(--spacing) * -6)}.-mb-8{margin-bottom:calc(var(--spacing) * -8)}.-mb-px{margin-bottom:-1px}.mb-0{margin-bottom:calc(var(--spacing) * 0)}.mb-1{margin-bottom:calc(var(--spacing) * 1)}.mb-1\.5{margin-bottom:calc(var(--spacing) * 1.5)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.mb-5{margin-bottom:calc(var(--spacing) * 5)}.mb-6{margin-bottom:calc(var(--spacing) * 6)}.mb-8{margin-bottom:calc(var(--spacing) * 8)}.mb-10{margin-bottom:calc(var(--spacing) * 10)}.mb-12{margin-bottom:calc(var(--spacing) * 12)}.mb-16{margin-bottom:calc(var(--spacing) * 16)}.mb-auto{margin-bottom:auto}.-ml-1{margin-left:calc(var(--spacing) * -1)}.-ml-2{margin-left:calc(var(--spacing) * -2)}.-ml-3{margin-left:calc(var(--spacing) * -3)}.-ml-4{margin-left:calc(var(--spacing) * -4)}.-ml-5{margin-left:calc(var(--spacing) * -5)}.-ml-6{margin-left:calc(var(--spacing) * -6)}.-ml-8{margin-left:calc(var(--spacing) * -8)}.ml-0{margin-left:calc(var(--spacing) * 0)}.ml-0\.5{margin-left:calc(var(--spacing) * .5)}.ml-1{margin-left:calc(var(--spacing) * 1)}.ml-2{margin-left:calc(var(--spacing) * 2)}.ml-3{margin-left:calc(var(--spacing) * 3)}.ml-4{margin-left:calc(var(--spacing) * 4)}.ml-5{margin-left:calc(var(--spacing) * 5)}.ml-6{margin-left:calc(var(--spacing) * 6)}.ml-8{margin-left:calc(var(--spacing) * 8)}.ml-10{margin-left:calc(var(--spacing) * 10)}.ml-12{margin-left:calc(var(--spacing) * 12)}.ml-16{margin-left:calc(var(--spacing) * 16)}.ml-auto{margin-left:auto}.box-border{box-sizing:border-box}.box-content{box-sizing:content-box}.line-clamp-1{-webkit-line-clamp:1;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.line-clamp-2{-webkit-line-clamp:2;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.line-clamp-3{-webkit-line-clamp:3;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.\!block{display:block!important}.\!flex{display:flex!important}.\!grid{display:grid!important}.\!hidden{display:none!important}.\!inline{display:inline!important}.\!inline-block{display:inline-block!important}.\!inline-flex{display:inline-flex!important}.block{display:block}.contents{display:contents}.flex{display:flex}.flow-root{display:flow-root}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.inline-grid{display:inline-grid}.inline-table{display:inline-table}.list-item{display:list-item}.table{display:table}.table-caption{display:table-caption}.table-cell{display:table-cell}.table-column{display:table-column}.table-column-group{display:table-column-group}.table-footer-group{display:table-footer-group}.table-header-group{display:table-header-group}.table-row{display:table-row}.table-row-group{display:table-row-group}.aspect-auto{aspect-ratio:auto}.aspect-square{aspect-ratio:1}.aspect-video{aspect-ratio:var(--aspect-video)}.\!h-0{height:calc(var(--spacing) * 0)!important}.\!h-1{height:calc(var(--spacing) * 1)!important}.\!h-2{height:calc(var(--spacing) * 2)!important}.\!h-3{height:calc(var(--spacing) * 3)!important}.\!h-4{height:calc(var(--spacing) * 4)!important}.\!h-5{height:calc(var(--spacing) * 5)!important}.\!h-6{height:calc(var(--spacing) * 6)!important}.\!h-8{height:calc(var(--spacing) * 8)!important}.\!h-10{height:calc(var(--spacing) * 10)!important}.\!h-12{height:calc(var(--spacing) * 12)!important}.\!h-16{height:calc(var(--spacing) * 16)!important}.\!h-20{height:calc(var(--spacing) * 20)!important}.\!h-24{height:calc(var(--spacing) * 24)!important}.\!h-32{height:calc(var(--spacing) * 32)!important}.\!h-40{height:calc(var(--spacing) * 40)!important}.\!h-48{height:calc(var(--spacing) * 48)!important}.\!h-56{height:calc(var(--spacing) * 56)!important}.\!h-64{height:calc(var(--spacing) * 64)!important}.\!h-72{height:calc(var(--spacing) * 72)!important}.\!h-80{height:calc(var(--spacing) * 80)!important}.\!h-96{height:calc(var(--spacing) * 96)!important}.\!h-auto{height:auto!important}.\!h-fit{height:fit-content!important}.\!h-full{height:100%!important}.\!h-max{height:max-content!important}.\!h-min{height:min-content!important}.\!h-px{height:1px!important}.\!h-screen{height:100vh!important}.h-0{height:calc(var(--spacing) * 0)}.h-1{height:calc(var(--spacing) * 1)}.h-1\/2{height:50%}.h-1\/3{height:33.3333%}.h-1\/4{height:25%}.h-1\/5{height:20%}.h-1\/6{height:16.6667%}.h-2{height:calc(var(--spacing) * 2)}.h-2\/3{height:66.6667%}.h-2\/4{height:50%}.h-2\/5{height:40%}.h-3{height:calc(var(--spacing) * 3)}.h-3\.5{height:calc(var(--spacing) * 3.5)}.h-3\/4{height:75%}.h-3\/5{height:60%}.h-4{height:calc(var(--spacing) * 4)}.h-4\/5{height:80%}.h-5{height:calc(var(--spacing) * 5)}.h-5\/6{height:83.3333%}.h-6{height:calc(var(--spacing) * 6)}.h-7{height:calc(var(--spacing) * 7)}.h-8{height:calc(var(--spacing) * 8)}.h-9{height:calc(var(--spacing) * 9)}.h-10{height:calc(var(--spacing) * 10)}.h-12{height:calc(var(--spacing) * 12)}.h-14{height:calc(var(--spacing) * 14)}.h-16{height:calc(var(--spacing) * 16)}.h-20{height:calc(var(--spacing) * 20)}.h-24{height:calc(var(--spacing) * 24)}.h-32{height:calc(var(--spacing) * 32)}.h-40{height:calc(var(--spacing) * 40)}.h-48{height:calc(var(--spacing) * 48)}.h-56{height:calc(var(--spacing) * 56)}.h-64{height:calc(var(--spacing) * 64)}.h-72{height:calc(var(--spacing) * 72)}.h-80{height:calc(var(--spacing) * 80)}.h-96{height:calc(var(--spacing) * 96)}.h-\[42px\]{height:42px}.h-auto{height:auto}.h-fit{height:fit-content}.h-full{height:100%}.h-max{height:max-content}.h-min{height:min-content}.h-px{height:1px}.h-screen{height:100vh}.max-h-0{max-height:calc(var(--spacing) * 0)}.max-h-20{max-height:calc(var(--spacing) * 20)}.max-h-32{max-height:calc(var(--spacing) * 32)}.max-h-40{max-height:calc(var(--spacing) * 40)}.max-h-48{max-height:calc(var(--spacing) * 48)}.max-h-56{max-height:calc(var(--spacing) * 56)}.max-h-60{max-height:calc(var(--spacing) * 60)}.max-h-64{max-height:calc(var(--spacing) * 64)}.max-h-72{max-height:calc(var(--spacing) * 72)}.max-h-80{max-height:calc(var(--spacing) * 80)}.max-h-96{max-height:calc(var(--spacing) * 96)}.max-h-\[70vh\]{max-height:70vh}.max-h-\[400px\]{max-height:400px}.max-h-\[500px\]{max-height:500px}.max-h-\[calc\(90vh-140px\)\]{max-height:calc(90vh - 140px)}.max-h-\[calc\(100vh-320px\)\]{max-height:calc(100vh - 320px)}.max-h-fit{max-height:fit-content}.max-h-full{max-height:100%}.max-h-max{max-height:max-content}.max-h-min{max-height:min-content}.max-h-none{max-height:none}.max-h-screen{max-height:100vh}.min-h-0{min-height:calc(var(--spacing) * 0)}.min-h-10{min-height:calc(var(--spacing) * 10)}.min-h-12{min-height:calc(var(--spacing) * 12)}.min-h-16{min-height:calc(var(--spacing) * 16)}.min-h-20{min-height:calc(var(--spacing) * 20)}.min-h-24{min-height:calc(var(--spacing) * 24)}.min-h-32{min-height:calc(var(--spacing) * 32)}.min-h-40{min-height:calc(var(--spacing) * 40)}.min-h-48{min-height:calc(var(--spacing) * 48)}.min-h-56{min-height:calc(var(--spacing) * 56)}.min-h-64{min-height:calc(var(--spacing) * 64)}.min-h-\[40px\]{min-height:40px}.min-h-\[42px\]{min-height:42px}.min-h-\[60px\]{min-height:60px}.min-h-\[60vh\]{min-height:60vh}.min-h-\[80px\]{min-height:80px}.min-h-\[200px\]{min-height:200px}.min-h-\[400px\]{min-height:400px}.min-h-fit{min-height:fit-content}.min-h-full{min-height:100%}.min-h-max{min-height:max-content}.min-h-min{min-height:min-content}.min-h-screen{min-height:100vh}.\!w-0{width:calc(var(--spacing) * 0)!important}.\!w-1{width:calc(var(--spacing) * 1)!important}.\!w-1\/2{width:50%!important}.\!w-1\/3{width:33.3333%!important}.\!w-1\/4{width:25%!important}.\!w-1\/5{width:20%!important}.\!w-1\/6{width:16.6667%!important}.\!w-2{width:calc(var(--spacing) * 2)!important}.\!w-2\/3{width:66.6667%!important}.\!w-2\/4{width:50%!important}.\!w-2\/5{width:40%!important}.\!w-3{width:calc(var(--spacing) * 3)!important}.\!w-3\/4{width:75%!important}.\!w-3\/5{width:60%!important}.\!w-4{width:calc(var(--spacing) * 4)!important}.\!w-4\/5{width:80%!important}.\!w-5{width:calc(var(--spacing) * 5)!important}.\!w-5\/6{width:83.3333%!important}.\!w-6{width:calc(var(--spacing) * 6)!important}.\!w-8{width:calc(var(--spacing) * 8)!important}.\!w-10{width:calc(var(--spacing) * 10)!important}.\!w-12{width:calc(var(--spacing) * 12)!important}.\!w-16{width:calc(var(--spacing) * 16)!important}.\!w-20{width:calc(var(--spacing) * 20)!important}.\!w-24{width:calc(var(--spacing) * 24)!important}.\!w-32{width:calc(var(--spacing) * 32)!important}.\!w-40{width:calc(var(--spacing) * 40)!important}.\!w-48{width:calc(var(--spacing) * 48)!important}.\!w-52{width:calc(var(--spacing) * 52)!important}.\!w-56{width:calc(var(--spacing) * 56)!important}.\!w-60{width:calc(var(--spacing) * 60)!important}.\!w-64{width:calc(var(--spacing) * 64)!important}.\!w-72{width:calc(var(--spacing) * 72)!important}.\!w-80{width:calc(var(--spacing) * 80)!important}.\!w-96{width:calc(var(--spacing) * 96)!important}.\!w-auto{width:auto!important}.\!w-fit{width:fit-content!important}.\!w-full{width:100%!important}.\!w-max{width:max-content!important}.\!w-min{width:min-content!important}.\!w-px{width:1px!important}.\!w-screen{width:100vw!important}.w-0{width:calc(var(--spacing) * 0)}.w-1{width:calc(var(--spacing) * 1)}.w-1\/2{width:50%}.w-1\/3{width:33.3333%}.w-1\/4{width:25%}.w-1\/5{width:20%}.w-1\/6{width:16.6667%}.w-2{width:calc(var(--spacing) * 2)}.w-2\/3{width:66.6667%}.w-2\/4{width:50%}.w-2\/5{width:40%}.w-3{width:calc(var(--spacing) * 3)}.w-3\.5{width:calc(var(--spacing) * 3.5)}.w-3\/4{width:75%}.w-3\/5{width:60%}.w-4{width:calc(var(--spacing) * 4)}.w-4\/5{width:80%}.w-5{width:calc(var(--spacing) * 5)}.w-5\/6{width:83.3333%}.w-6{width:calc(var(--spacing) * 6)}.w-8{width:calc(var(--spacing) * 8)}.w-9{width:calc(var(--spacing) * 9)}.w-10{width:calc(var(--spacing) * 10)}.w-11{width:calc(var(--spacing) * 11)}.w-12{width:calc(var(--spacing) * 12)}.w-14{width:calc(var(--spacing) * 14)}.w-16{width:calc(var(--spacing) * 16)}.w-20{width:calc(var(--spacing) * 20)}.w-24{width:calc(var(--spacing) * 24)}.w-28{width:calc(var(--spacing) * 28)}.w-32{width:calc(var(--spacing) * 32)}.w-36{width:calc(var(--spacing) * 36)}.w-40{width:calc(var(--spacing) * 40)}.w-48{width:calc(var(--spacing) * 48)}.w-52{width:calc(var(--spacing) * 52)}.w-56{width:calc(var(--spacing) * 56)}.w-60{width:calc(var(--spacing) * 60)}.w-64{width:calc(var(--spacing) * 64)}.w-72{width:calc(var(--spacing) * 72)}.w-80{width:calc(var(--spacing) * 80)}.w-96{width:calc(var(--spacing) * 96)}.w-auto{width:auto}.w-fit{width:fit-content}.w-full{width:100%}.w-max{width:max-content}.w-min{width:min-content}.w-px{width:1px}.w-screen{width:100vw}.\!max-w-0{max-width:calc(var(--spacing) * 0)!important}.\!max-w-2xl{max-width:var(--container-2xl)!important}.\!max-w-3xl{max-width:var(--container-3xl)!important}.\!max-w-4xl{max-width:var(--container-4xl)!important}.\!max-w-full{max-width:100%!important}.\!max-w-lg{max-width:var(--container-lg)!important}.\!max-w-md{max-width:var(--container-md)!important}.\!max-w-none{max-width:none!important}.\!max-w-sm{max-width:var(--container-sm)!important}.\!max-w-xl{max-width:var(--container-xl)!important}.\!max-w-xs{max-width:var(--container-xs)!important}.max-w-0{max-width:calc(var(--spacing) * 0)}.max-w-2xl{max-width:var(--container-2xl)}.max-w-3xl{max-width:var(--container-3xl)}.max-w-4xl{max-width:var(--container-4xl)}.max-w-5xl{max-width:var(--container-5xl)}.max-w-6xl{max-width:var(--container-6xl)}.max-w-7xl{max-width:var(--container-7xl)}.max-w-\[200px\]{max-width:200px}.max-w-\[calc\(100vw-1rem\)\]{max-width:calc(100vw - 1rem)}.max-w-\[calc\(100vw-2rem\)\]{max-width:calc(100vw - 2rem)}.max-w-fit{max-width:fit-content}.max-w-full{max-width:100%}.max-w-lg{max-width:var(--container-lg)}.max-w-max{max-width:max-content}.max-w-md{max-width:var(--container-md)}.max-w-min{max-width:min-content}.max-w-none{max-width:none}.max-w-prose{max-width:65ch}.max-w-screen-2xl{max-width:var(--breakpoint-2xl)}.max-w-screen-lg{max-width:var(--breakpoint-lg)}.max-w-screen-md{max-width:var(--breakpoint-md)}.max-w-screen-sm{max-width:var(--breakpoint-sm)}.max-w-screen-xl{max-width:var(--breakpoint-xl)}.max-w-sm{max-width:var(--container-sm)}.max-w-xl{max-width:var(--container-xl)}.max-w-xs{max-width:var(--container-xs)}.\!min-w-0{min-width:calc(var(--spacing) * 0)!important}.\!min-w-4{min-width:calc(var(--spacing) * 4)!important}.\!min-w-8{min-width:calc(var(--spacing) * 8)!important}.\!min-w-12{min-width:calc(var(--spacing) * 12)!important}.\!min-w-16{min-width:calc(var(--spacing) * 16)!important}.\!min-w-20{min-width:calc(var(--spacing) * 20)!important}.\!min-w-24{min-width:calc(var(--spacing) * 24)!important}.\!min-w-32{min-width:calc(var(--spacing) * 32)!important}.\!min-w-40{min-width:calc(var(--spacing) * 40)!important}.\!min-w-48{min-width:calc(var(--spacing) * 48)!important}.\!min-w-56{min-width:calc(var(--spacing) * 56)!important}.\!min-w-64{min-width:calc(var(--spacing) * 64)!important}.\!min-w-fit{min-width:fit-content!important}.\!min-w-full{min-width:100%!important}.\!min-w-max{min-width:max-content!important}.\!min-w-min{min-width:min-content!important}.min-w-0{min-width:calc(var(--spacing) * 0)}.min-w-\[20px\]{min-width:20px}.min-w-\[80px\]{min-width:80px}.min-w-\[120px\]{min-width:120px}.min-w-\[200px\]{min-width:200px}.min-w-\[280px\]{min-width:280px}.min-w-\[300px\]{min-width:300px}.min-w-fit{min-width:fit-content}.min-w-full{min-width:100%}.min-w-max{min-width:max-content}.min-w-min{min-width:min-content}.\!flex-1{flex:1!important}.\!flex-auto{flex:auto!important}.\!flex-initial{flex:0 auto!important}.\!flex-none{flex:none!important}.flex-1{flex:1}.flex-auto{flex:auto}.flex-initial{flex:0 auto}.flex-none{flex:none}.flex-shrink{flex-shrink:1}.flex-shrink-0{flex-shrink:0}.shrink{flex-shrink:1}.shrink-0{flex-shrink:0}.flex-grow,.grow{flex-grow:1}.grow-0{flex-grow:0}.basis-0{flex-basis:calc(var(--spacing) * 0)}.basis-1{flex-basis:calc(var(--spacing) * 1)}.basis-1\/2{flex-basis:50%}.basis-1\/3{flex-basis:33.3333%}.basis-1\/4{flex-basis:25%}.basis-2{flex-basis:calc(var(--spacing) * 2)}.basis-2\/3{flex-basis:66.6667%}.basis-3\/4{flex-basis:75%}.basis-auto{flex-basis:auto}.basis-full{flex-basis:100%}.table-auto{table-layout:auto}.table-fixed{table-layout:fixed}.caption-bottom{caption-side:bottom}.caption-top{caption-side:top}.border-collapse{border-collapse:collapse}.border-separate{border-collapse:separate}.border-spacing-0{--tw-border-spacing-x:calc(var(--spacing) * 0);--tw-border-spacing-y:calc(var(--spacing) * 0);border-spacing:var(--tw-border-spacing-x) var(--tw-border-spacing-y)}.border-spacing-1{--tw-border-spacing-x:calc(var(--spacing) * 1);--tw-border-spacing-y:calc(var(--spacing) * 1);border-spacing:var(--tw-border-spacing-x) var(--tw-border-spacing-y)}.border-spacing-2{--tw-border-spacing-x:calc(var(--spacing) * 2);--tw-border-spacing-y:calc(var(--spacing) * 2);border-spacing:var(--tw-border-spacing-x) var(--tw-border-spacing-y)}.border-spacing-3{--tw-border-spacing-x:calc(var(--spacing) * 3);--tw-border-spacing-y:calc(var(--spacing) * 3);border-spacing:var(--tw-border-spacing-x) var(--tw-border-spacing-y)}.border-spacing-4{--tw-border-spacing-x:calc(var(--spacing) * 4);--tw-border-spacing-y:calc(var(--spacing) * 4);border-spacing:var(--tw-border-spacing-x) var(--tw-border-spacing-y)}.border-spacing-5{--tw-border-spacing-x:calc(var(--spacing) * 5);--tw-border-spacing-y:calc(var(--spacing) * 5);border-spacing:var(--tw-border-spacing-x) var(--tw-border-spacing-y)}.border-spacing-6{--tw-border-spacing-x:calc(var(--spacing) * 6);--tw-border-spacing-y:calc(var(--spacing) * 6);border-spacing:var(--tw-border-spacing-x) var(--tw-border-spacing-y)}.border-spacing-8{--tw-border-spacing-x:calc(var(--spacing) * 8);--tw-border-spacing-y:calc(var(--spacing) * 8);border-spacing:var(--tw-border-spacing-x) var(--tw-border-spacing-y)}.origin-bottom{transform-origin:bottom}.origin-bottom-left{transform-origin:0 100%}.origin-bottom-right{transform-origin:100% 100%}.origin-center{transform-origin:50%}.origin-left{transform-origin:0}.origin-right{transform-origin:100%}.origin-top{transform-origin:top}.origin-top-left{transform-origin:0 0}.origin-top-right{transform-origin:100% 0}.-translate-x-1{--tw-translate-x:calc(var(--spacing) * -1);translate:var(--tw-translate-x) var(--tw-translate-y)}.-translate-x-1\/2{--tw-translate-x: -50% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.-translate-x-2{--tw-translate-x:calc(var(--spacing) * -2);translate:var(--tw-translate-x) var(--tw-translate-y)}.-translate-x-3{--tw-translate-x:calc(var(--spacing) * -3);translate:var(--tw-translate-x) var(--tw-translate-y)}.-translate-x-4{--tw-translate-x:calc(var(--spacing) * -4);translate:var(--tw-translate-x) var(--tw-translate-y)}.-translate-x-full{--tw-translate-x:-100%;translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-0{--tw-translate-x:calc(var(--spacing) * 0);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-1{--tw-translate-x:calc(var(--spacing) * 1);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-1\/2{--tw-translate-x: 50% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-1\/3{--tw-translate-x:calc(1 / 3 * 100%);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-1\/4{--tw-translate-x: 25% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-2{--tw-translate-x:calc(var(--spacing) * 2);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-2\/3{--tw-translate-x:calc(2 / 3 * 100%);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-3{--tw-translate-x:calc(var(--spacing) * 3);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-3\/4{--tw-translate-x: 75% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-4{--tw-translate-x:calc(var(--spacing) * 4);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-5{--tw-translate-x:calc(var(--spacing) * 5);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-6{--tw-translate-x:calc(var(--spacing) * 6);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-8{--tw-translate-x:calc(var(--spacing) * 8);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-10{--tw-translate-x:calc(var(--spacing) * 10);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-12{--tw-translate-x:calc(var(--spacing) * 12);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-full{--tw-translate-x:100%;translate:var(--tw-translate-x) var(--tw-translate-y)}.-translate-y-1{--tw-translate-y:calc(var(--spacing) * -1);translate:var(--tw-translate-x) var(--tw-translate-y)}.-translate-y-1\/2{--tw-translate-y: -50% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.-translate-y-2{--tw-translate-y:calc(var(--spacing) * -2);translate:var(--tw-translate-x) var(--tw-translate-y)}.-translate-y-3{--tw-translate-y:calc(var(--spacing) * -3);translate:var(--tw-translate-x) var(--tw-translate-y)}.-translate-y-4{--tw-translate-y:calc(var(--spacing) * -4);translate:var(--tw-translate-x) var(--tw-translate-y)}.-translate-y-full{--tw-translate-y:-100%;translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-0{--tw-translate-y:calc(var(--spacing) * 0);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-1{--tw-translate-y:calc(var(--spacing) * 1);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-1\/2{--tw-translate-y: 50% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-1\/3{--tw-translate-y:calc(1 / 3 * 100%);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-1\/4{--tw-translate-y: 25% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-2{--tw-translate-y:calc(var(--spacing) * 2);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-2\/3{--tw-translate-y:calc(2 / 3 * 100%);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-3{--tw-translate-y:calc(var(--spacing) * 3);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-3\/4{--tw-translate-y: 75% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-4{--tw-translate-y:calc(var(--spacing) * 4);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-5{--tw-translate-y:calc(var(--spacing) * 5);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-6{--tw-translate-y:calc(var(--spacing) * 6);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-8{--tw-translate-y:calc(var(--spacing) * 8);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-10{--tw-translate-y:calc(var(--spacing) * 10);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-12{--tw-translate-y:calc(var(--spacing) * 12);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-full{--tw-translate-y:100%;translate:var(--tw-translate-x) var(--tw-translate-y)}.scale-0{--tw-scale-x:0%;--tw-scale-y:0%;--tw-scale-z:0%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-50{--tw-scale-x:50%;--tw-scale-y:50%;--tw-scale-z:50%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-75{--tw-scale-x:75%;--tw-scale-y:75%;--tw-scale-z:75%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-90{--tw-scale-x:90%;--tw-scale-y:90%;--tw-scale-z:90%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-95{--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-100{--tw-scale-x:100%;--tw-scale-y:100%;--tw-scale-z:100%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-105{--tw-scale-x:105%;--tw-scale-y:105%;--tw-scale-z:105%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-110{--tw-scale-x:110%;--tw-scale-y:110%;--tw-scale-z:110%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-125{--tw-scale-x:125%;--tw-scale-y:125%;--tw-scale-z:125%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-150{--tw-scale-x:150%;--tw-scale-y:150%;--tw-scale-z:150%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-x-0{--tw-scale-x:0%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-x-50{--tw-scale-x:50%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-x-75{--tw-scale-x:75%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-x-90{--tw-scale-x:90%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-x-95{--tw-scale-x:95%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-x-100{--tw-scale-x:100%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-x-105{--tw-scale-x:105%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-x-110{--tw-scale-x:110%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-x-125{--tw-scale-x:125%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-x-150{--tw-scale-x:150%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-y-0{--tw-scale-y:0%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-y-50{--tw-scale-y:50%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-y-75{--tw-scale-y:75%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-y-90{--tw-scale-y:90%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-y-95{--tw-scale-y:95%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-y-100{--tw-scale-y:100%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-y-105{--tw-scale-y:105%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-y-110{--tw-scale-y:110%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-y-125{--tw-scale-y:125%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-y-150{--tw-scale-y:150%;scale:var(--tw-scale-x) var(--tw-scale-y)}.-rotate-1{rotate:-1deg}.-rotate-2{rotate:-2deg}.-rotate-3{rotate:-3deg}.-rotate-6{rotate:-6deg}.-rotate-12{rotate:-12deg}.-rotate-45{rotate:-45deg}.-rotate-90{rotate:-90deg}.-rotate-180{rotate:-180deg}.rotate-0{rotate:0deg}.rotate-1{rotate:1deg}.rotate-2{rotate:2deg}.rotate-3{rotate:3deg}.rotate-6{rotate:6deg}.rotate-12{rotate:12deg}.rotate-45{rotate:45deg}.rotate-90{rotate:90deg}.rotate-180{rotate:180deg}.-skew-x-1{--tw-skew-x:skewX( -1deg );transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.-skew-x-2{--tw-skew-x:skewX( -2deg );transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.-skew-x-3{--tw-skew-x:skewX( -3deg );transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.-skew-x-6{--tw-skew-x:skewX( -6deg );transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.-skew-x-12{--tw-skew-x:skewX( -12deg );transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.skew-x-0{--tw-skew-x:skewX(0deg);transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.skew-x-1{--tw-skew-x:skewX(1deg);transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.skew-x-2{--tw-skew-x:skewX(2deg);transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.skew-x-3{--tw-skew-x:skewX(3deg);transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.skew-x-6{--tw-skew-x:skewX(6deg);transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.skew-x-12{--tw-skew-x:skewX(12deg);transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.-skew-y-1{--tw-skew-y:skewY( -1deg );transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.-skew-y-2{--tw-skew-y:skewY( -2deg );transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.-skew-y-3{--tw-skew-y:skewY( -3deg );transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.-skew-y-6{--tw-skew-y:skewY( -6deg );transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.-skew-y-12{--tw-skew-y:skewY( -12deg );transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.skew-y-0{--tw-skew-y:skewY(0deg);transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.skew-y-1{--tw-skew-y:skewY(1deg);transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.skew-y-2{--tw-skew-y:skewY(2deg);transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.skew-y-3{--tw-skew-y:skewY(3deg);transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.skew-y-6{--tw-skew-y:skewY(6deg);transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.skew-y-12{--tw-skew-y:skewY(12deg);transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.transform,.transform-cpu{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.transform-gpu{transform:translateZ(0) var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.transform-none{transform:none}.animate-\[loading_1\.5s_ease-in-out_infinite\]{animation:1.5s ease-in-out infinite loading}.animate-bounce{animation:var(--animate-bounce)}.animate-none{animation:none}.animate-ping{animation:var(--animate-ping)}.animate-pulse{animation:var(--animate-pulse)}.animate-spin{animation:var(--animate-spin)}.cursor-alias{cursor:alias}.cursor-all-scroll{cursor:all-scroll}.cursor-auto{cursor:auto}.cursor-cell{cursor:cell}.cursor-col-resize{cursor:col-resize}.cursor-context-menu{cursor:context-menu}.cursor-copy{cursor:copy}.cursor-crosshair{cursor:crosshair}.cursor-default{cursor:default}.cursor-e-resize{cursor:e-resize}.cursor-ew-resize{cursor:ew-resize}.cursor-grab{cursor:grab}.cursor-grabbing{cursor:grabbing}.cursor-help{cursor:help}.cursor-move{cursor:move}.cursor-n-resize{cursor:n-resize}.cursor-ne-resize{cursor:ne-resize}.cursor-nesw-resize{cursor:nesw-resize}.cursor-no-drop{cursor:no-drop}.cursor-none{cursor:none}.cursor-not-allowed{cursor:not-allowed}.cursor-ns-resize{cursor:ns-resize}.cursor-nw-resize{cursor:nw-resize}.cursor-nwse-resize{cursor:nwse-resize}.cursor-pointer{cursor:pointer}.cursor-progress{cursor:progress}.cursor-row-resize{cursor:row-resize}.cursor-s-resize{cursor:s-resize}.cursor-se-resize{cursor:se-resize}.cursor-sw-resize{cursor:sw-resize}.cursor-text{cursor:text}.cursor-vertical-text{cursor:vertical-text}.cursor-w-resize{cursor:w-resize}.cursor-wait{cursor:wait}.cursor-zoom-in{cursor:zoom-in}.cursor-zoom-out{cursor:zoom-out}.touch-pan-left{--tw-pan-x:pan-left;touch-action:var(--tw-pan-x,) var(--tw-pan-y,) var(--tw-pinch-zoom,)}.touch-pan-right{--tw-pan-x:pan-right;touch-action:var(--tw-pan-x,) var(--tw-pan-y,) var(--tw-pinch-zoom,)}.touch-pan-x{--tw-pan-x:pan-x;touch-action:var(--tw-pan-x,) var(--tw-pan-y,) var(--tw-pinch-zoom,)}.touch-pan-down{--tw-pan-y:pan-down;touch-action:var(--tw-pan-x,) var(--tw-pan-y,) var(--tw-pinch-zoom,)}.touch-pan-up{--tw-pan-y:pan-up;touch-action:var(--tw-pan-x,) var(--tw-pan-y,) var(--tw-pinch-zoom,)}.touch-pan-y{--tw-pan-y:pan-y;touch-action:var(--tw-pan-x,) var(--tw-pan-y,) var(--tw-pinch-zoom,)}.touch-pinch-zoom{--tw-pinch-zoom:pinch-zoom;touch-action:var(--tw-pan-x,) var(--tw-pan-y,) var(--tw-pinch-zoom,)}.touch-auto{touch-action:auto}.touch-manipulation{touch-action:manipulation}.touch-none{touch-action:none}.resize{resize:both}.resize-none{resize:none}.resize-x{resize:horizontal}.resize-y{resize:vertical}.snap-both{scroll-snap-type:both var(--tw-scroll-snap-strictness)}.snap-none{scroll-snap-type:none}.snap-x{scroll-snap-type:x var(--tw-scroll-snap-strictness)}.snap-y{scroll-snap-type:y var(--tw-scroll-snap-strictness)}.snap-mandatory{--tw-scroll-snap-strictness:mandatory}.snap-proximity{--tw-scroll-snap-strictness:proximity}.snap-align-none{scroll-snap-align:none}.snap-center{scroll-snap-align:center}.snap-end{scroll-snap-align:end}.snap-start{scroll-snap-align:start}.snap-always{scroll-snap-stop:always}.snap-normal{scroll-snap-stop:normal}.scroll-mt-44{scroll-margin-top:calc(var(--spacing) * 44)}.list-inside{list-style-position:inside}.list-outside{list-style-position:outside}.list-decimal{list-style-type:decimal}.list-disc{list-style-type:disc}.list-none{list-style-type:none}.appearance-auto{appearance:auto}.appearance-none{appearance:none}.columns-1{columns:1}.columns-2{columns:2}.columns-2xl{columns:var(--container-2xl)}.columns-2xs{columns:var(--container-2xs)}.columns-3{columns:3}.columns-3xl{columns:var(--container-3xl)}.columns-3xs{columns:var(--container-3xs)}.columns-4{columns:4}.columns-4xl{columns:var(--container-4xl)}.columns-5{columns:5}.columns-5xl{columns:var(--container-5xl)}.columns-6{columns:6}.columns-6xl{columns:var(--container-6xl)}.columns-7{columns:7}.columns-7xl{columns:var(--container-7xl)}.columns-8{columns:8}.columns-9{columns:9}.columns-10{columns:10}.columns-11{columns:11}.columns-12{columns:12}.columns-auto{columns:auto}.columns-lg{columns:var(--container-lg)}.columns-md{columns:var(--container-md)}.columns-sm{columns:var(--container-sm)}.columns-xl{columns:var(--container-xl)}.columns-xs{columns:var(--container-xs)}.break-before-all{break-before:all}.break-before-auto{break-before:auto}.break-before-avoid{break-before:avoid}.break-before-avoid-page{break-before:avoid-page}.break-before-column{break-before:column}.break-before-left{break-before:left}.break-before-page{break-before:page}.break-before-right{break-before:right}.break-inside-auto{break-inside:auto}.break-inside-avoid{break-inside:avoid}.break-inside-avoid-column{break-inside:avoid-column}.break-inside-avoid-page{break-inside:avoid-page}.break-after-all{break-after:all}.break-after-auto{break-after:auto}.break-after-avoid{break-after:avoid}.break-after-avoid-page{break-after:avoid-page}.break-after-column{break-after:column}.break-after-left{break-after:left}.break-after-page{break-after:page}.break-after-right{break-after:right}.auto-cols-auto{grid-auto-columns:auto}.auto-cols-fr{grid-auto-columns:minmax(0,1fr)}.auto-cols-max{grid-auto-columns:max-content}.auto-cols-min{grid-auto-columns:min-content}.grid-flow-col{grid-auto-flow:column}.grid-flow-col-dense{grid-auto-flow:column dense}.grid-flow-dense{grid-auto-flow:dense}.grid-flow-row{grid-auto-flow:row}.grid-flow-row-dense{grid-auto-flow:dense}.auto-rows-auto{grid-auto-rows:auto}.auto-rows-fr{grid-auto-rows:minmax(0,1fr)}.auto-rows-max{grid-auto-rows:max-content}.auto-rows-min{grid-auto-rows:min-content}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr))}.grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.grid-cols-7{grid-template-columns:repeat(7,minmax(0,1fr))}.grid-cols-8{grid-template-columns:repeat(8,minmax(0,1fr))}.grid-cols-9{grid-template-columns:repeat(9,minmax(0,1fr))}.grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.grid-cols-11{grid-template-columns:repeat(11,minmax(0,1fr))}.grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.grid-rows-1{grid-template-rows:repeat(1,minmax(0,1fr))}.grid-rows-2{grid-template-rows:repeat(2,minmax(0,1fr))}.grid-rows-3{grid-template-rows:repeat(3,minmax(0,1fr))}.grid-rows-4{grid-template-rows:repeat(4,minmax(0,1fr))}.grid-rows-5{grid-template-rows:repeat(5,minmax(0,1fr))}.grid-rows-6{grid-template-rows:repeat(6,minmax(0,1fr))}.\!flex-col{flex-direction:column!important}.\!flex-row{flex-direction:row!important}.flex-col{flex-direction:column}.flex-col-reverse{flex-direction:column-reverse}.flex-row{flex-direction:row}.flex-row-reverse{flex-direction:row-reverse}.\!flex-nowrap{flex-wrap:nowrap!important}.\!flex-wrap{flex-wrap:wrap!important}.flex-nowrap{flex-wrap:nowrap}.flex-wrap{flex-wrap:wrap}.flex-wrap-reverse{flex-wrap:wrap-reverse}.place-content-around{place-content:space-around}.place-content-baseline{place-content:baseline start}.place-content-between{place-content:space-between}.place-content-center{place-content:center}.place-content-end{place-content:end}.place-content-evenly{place-content:space-evenly}.place-content-start{place-content:start}.place-content-stretch{place-content:stretch}.place-items-baseline{place-items:baseline}.place-items-center{place-items:center}.place-items-end{place-items:end}.place-items-start{place-items:start}.place-items-stretch{place-items:stretch stretch}.content-around{align-content:space-around}.content-between{align-content:space-between}.content-center{align-content:center}.content-end{align-content:flex-end}.content-evenly{align-content:space-evenly}.content-start{align-content:flex-start}.items-baseline{align-items:baseline}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.items-stretch{align-items:stretch}.justify-around{justify-content:space-around}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.justify-evenly{justify-content:space-evenly}.justify-start{justify-content:flex-start}.\!gap-0{gap:calc(var(--spacing) * 0)!important}.\!gap-1{gap:calc(var(--spacing) * 1)!important}.\!gap-2{gap:calc(var(--spacing) * 2)!important}.\!gap-3{gap:calc(var(--spacing) * 3)!important}.\!gap-4{gap:calc(var(--spacing) * 4)!important}.\!gap-5{gap:calc(var(--spacing) * 5)!important}.\!gap-6{gap:calc(var(--spacing) * 6)!important}.\!gap-8{gap:calc(var(--spacing) * 8)!important}.\!gap-10{gap:calc(var(--spacing) * 10)!important}.\!gap-12{gap:calc(var(--spacing) * 12)!important}.\!gap-16{gap:calc(var(--spacing) * 16)!important}.gap-0{gap:calc(var(--spacing) * 0)}.gap-0\.5{gap:calc(var(--spacing) * .5)}.gap-1{gap:calc(var(--spacing) * 1)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-2\.5{gap:calc(var(--spacing) * 2.5)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}.gap-5{gap:calc(var(--spacing) * 5)}.gap-6{gap:calc(var(--spacing) * 6)}.gap-8{gap:calc(var(--spacing) * 8)}.gap-10{gap:calc(var(--spacing) * 10)}.gap-12{gap:calc(var(--spacing) * 12)}.gap-16{gap:calc(var(--spacing) * 16)}:where(.-space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * -1) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * -1) * calc(1 - var(--tw-space-y-reverse)))}:where(.-space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * -2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * -2) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-0>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 0) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 0) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-0\.5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * .5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * .5) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 1) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 1) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 5) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 6) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-8>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 8) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 8) * calc(1 - var(--tw-space-y-reverse)))}.gap-x-0{column-gap:calc(var(--spacing) * 0)}.gap-x-1{column-gap:calc(var(--spacing) * 1)}.gap-x-2{column-gap:calc(var(--spacing) * 2)}.gap-x-3{column-gap:calc(var(--spacing) * 3)}.gap-x-4{column-gap:calc(var(--spacing) * 4)}.gap-x-5{column-gap:calc(var(--spacing) * 5)}.gap-x-6{column-gap:calc(var(--spacing) * 6)}.gap-x-8{column-gap:calc(var(--spacing) * 8)}.gap-x-10{column-gap:calc(var(--spacing) * 10)}.gap-x-12{column-gap:calc(var(--spacing) * 12)}:where(.-space-x-1>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * -1) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * -1) * calc(1 - var(--tw-space-x-reverse)))}:where(.-space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * -2) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * -2) * calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-0>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 0) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 0) * calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-1>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 1) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 1) * calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 2) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-3>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 3) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-4>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 4) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-5>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 5) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 5) * calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-6>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 6) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-8>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 8) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 8) * calc(1 - var(--tw-space-x-reverse)))}.gap-y-0{row-gap:calc(var(--spacing) * 0)}.gap-y-1{row-gap:calc(var(--spacing) * 1)}.gap-y-2{row-gap:calc(var(--spacing) * 2)}.gap-y-3{row-gap:calc(var(--spacing) * 3)}.gap-y-4{row-gap:calc(var(--spacing) * 4)}.gap-y-5{row-gap:calc(var(--spacing) * 5)}.gap-y-6{row-gap:calc(var(--spacing) * 6)}.gap-y-8{row-gap:calc(var(--spacing) * 8)}.gap-y-10{row-gap:calc(var(--spacing) * 10)}.gap-y-12{row-gap:calc(var(--spacing) * 12)}:where(.divide-x>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-style:var(--tw-border-style);border-inline-start-width:calc(1px * var(--tw-divide-x-reverse));border-inline-end-width:calc(1px * calc(1 - var(--tw-divide-x-reverse)))}:where(.divide-x-0>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-style:var(--tw-border-style);border-inline-start-width:calc(0px * var(--tw-divide-x-reverse));border-inline-end-width:calc(0px * calc(1 - var(--tw-divide-x-reverse)))}:where(.divide-x-2>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-style:var(--tw-border-style);border-inline-start-width:calc(2px * var(--tw-divide-x-reverse));border-inline-end-width:calc(2px * calc(1 - var(--tw-divide-x-reverse)))}:where(.divide-x-4>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-style:var(--tw-border-style);border-inline-start-width:calc(4px * var(--tw-divide-x-reverse));border-inline-end-width:calc(4px * calc(1 - var(--tw-divide-x-reverse)))}:where(.divide-x-8>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-style:var(--tw-border-style);border-inline-start-width:calc(8px * var(--tw-divide-x-reverse));border-inline-end-width:calc(8px * calc(1 - var(--tw-divide-x-reverse)))}:where(.divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px * var(--tw-divide-y-reverse));border-bottom-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-y-0>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(0px * var(--tw-divide-y-reverse));border-bottom-width:calc(0px * calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-y-2>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(2px * var(--tw-divide-y-reverse));border-bottom-width:calc(2px * calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-y-4>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(4px * var(--tw-divide-y-reverse));border-bottom-width:calc(4px * calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-y-8>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(8px * var(--tw-divide-y-reverse));border-bottom-width:calc(8px * calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-y-reverse>:not(:last-child)){--tw-divide-y-reverse:1}:where(.divide-dashed>:not(:last-child)){--tw-border-style:dashed;border-style:dashed}:where(.divide-dotted>:not(:last-child)){--tw-border-style:dotted;border-style:dotted}:where(.divide-double>:not(:last-child)){--tw-border-style:double;border-style:double}:where(.divide-none>:not(:last-child)){--tw-border-style:none;border-style:none}:where(.divide-solid>:not(:last-child)){--tw-border-style:solid;border-style:solid}:where(.divide-black>:not(:last-child)){border-color:var(--color-black)}:where(.divide-current>:not(:last-child)){border-color:currentColor}:where(.divide-gray-50>:not(:last-child)){border-color:var(--color-gray-50)}:where(.divide-gray-100>:not(:last-child)){border-color:var(--color-gray-100)}:where(.divide-gray-200>:not(:last-child)){border-color:var(--color-gray-200)}:where(.divide-gray-300>:not(:last-child)){border-color:var(--color-gray-300)}:where(.divide-gray-400>:not(:last-child)){border-color:var(--color-gray-400)}:where(.divide-gray-500>:not(:last-child)){border-color:var(--color-gray-500)}:where(.divide-gray-600>:not(:last-child)){border-color:var(--color-gray-600)}:where(.divide-gray-700>:not(:last-child)){border-color:var(--color-gray-700)}:where(.divide-gray-800>:not(:last-child)){border-color:var(--color-gray-800)}:where(.divide-gray-900>:not(:last-child)){border-color:var(--color-gray-900)}:where(.divide-inherit>:not(:last-child)){border-color:inherit}:where(.divide-purple-100>:not(:last-child)){border-color:var(--color-purple-100)}:where(.divide-slate-50>:not(:last-child)){border-color:var(--color-slate-50)}:where(.divide-slate-100>:not(:last-child)){border-color:var(--color-slate-100)}:where(.divide-slate-200>:not(:last-child)){border-color:var(--color-slate-200)}:where(.divide-slate-300>:not(:last-child)){border-color:var(--color-slate-300)}:where(.divide-slate-400>:not(:last-child)){border-color:var(--color-slate-400)}:where(.divide-slate-500>:not(:last-child)){border-color:var(--color-slate-500)}:where(.divide-slate-600>:not(:last-child)){border-color:var(--color-slate-600)}:where(.divide-slate-700>:not(:last-child)){border-color:var(--color-slate-700)}:where(.divide-slate-800>:not(:last-child)){border-color:var(--color-slate-800)}:where(.divide-slate-900>:not(:last-child)){border-color:var(--color-slate-900)}:where(.divide-transparent>:not(:last-child)){border-color:#0000}:where(.divide-white>:not(:last-child)){border-color:var(--color-white)}.place-self-auto{place-self:auto}.place-self-center{place-self:center}.place-self-end{place-self:end}.place-self-start{place-self:start}.place-self-stretch{place-self:stretch stretch}.self-auto{align-self:auto}.self-baseline{align-self:baseline}.self-center{align-self:center}.self-end{align-self:flex-end}.self-start{align-self:flex-start}.self-stretch{align-self:stretch}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-clip{overflow:clip}.overflow-hidden{overflow:hidden}.overflow-scroll{overflow:scroll}.overflow-visible{overflow:visible}.overflow-x-auto{overflow-x:auto}.overflow-x-clip{overflow-x:clip}.overflow-x-hidden{overflow-x:hidden}.overflow-x-scroll{overflow-x:scroll}.overflow-x-visible{overflow-x:visible}.overflow-y-auto{overflow-y:auto}.overflow-y-clip{overflow-y:clip}.overflow-y-hidden{overflow-y:hidden}.overflow-y-scroll{overflow-y:scroll}.overflow-y-visible{overflow-y:visible}.overscroll-auto{overscroll-behavior:auto}.overscroll-contain{overscroll-behavior:contain}.overscroll-none{overscroll-behavior:none}.overscroll-x-auto{overscroll-behavior-x:auto}.overscroll-x-contain{overscroll-behavior-x:contain}.overscroll-x-none{overscroll-behavior-x:none}.overscroll-y-auto{overscroll-behavior-y:auto}.overscroll-y-contain{overscroll-behavior-y:contain}.overscroll-y-none{overscroll-behavior-y:none}.scroll-auto{scroll-behavior:auto}.scroll-smooth{scroll-behavior:smooth}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:var(--radius-2xl)}.rounded-3xl{border-radius:var(--radius-3xl)}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-none{border-radius:0}.rounded-sm{border-radius:var(--radius-sm)}.rounded-xl{border-radius:var(--radius-xl)}.rounded-t{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.rounded-t-2xl{border-top-left-radius:var(--radius-2xl);border-top-right-radius:var(--radius-2xl)}.rounded-t-3xl{border-top-left-radius:var(--radius-3xl);border-top-right-radius:var(--radius-3xl)}.rounded-t-full{border-top-left-radius:3.40282e38px;border-top-right-radius:3.40282e38px}.rounded-t-lg{border-top-left-radius:var(--radius-lg);border-top-right-radius:var(--radius-lg)}.rounded-t-md{border-top-left-radius:var(--radius-md);border-top-right-radius:var(--radius-md)}.rounded-t-none{border-top-left-radius:0;border-top-right-radius:0}.rounded-t-sm{border-top-left-radius:var(--radius-sm);border-top-right-radius:var(--radius-sm)}.rounded-t-xl{border-top-left-radius:var(--radius-xl);border-top-right-radius:var(--radius-xl)}.rounded-l{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.rounded-l-2xl{border-top-left-radius:var(--radius-2xl);border-bottom-left-radius:var(--radius-2xl)}.rounded-l-3xl{border-top-left-radius:var(--radius-3xl);border-bottom-left-radius:var(--radius-3xl)}.rounded-l-full{border-top-left-radius:3.40282e38px;border-bottom-left-radius:3.40282e38px}.rounded-l-lg{border-top-left-radius:var(--radius-lg);border-bottom-left-radius:var(--radius-lg)}.rounded-l-md{border-top-left-radius:var(--radius-md);border-bottom-left-radius:var(--radius-md)}.rounded-l-none{border-top-left-radius:0;border-bottom-left-radius:0}.rounded-l-sm{border-top-left-radius:var(--radius-sm);border-bottom-left-radius:var(--radius-sm)}.rounded-l-xl{border-top-left-radius:var(--radius-xl);border-bottom-left-radius:var(--radius-xl)}.rounded-tl{border-top-left-radius:.25rem}.rounded-tl-2xl{border-top-left-radius:var(--radius-2xl)}.rounded-tl-3xl{border-top-left-radius:var(--radius-3xl)}.rounded-tl-full{border-top-left-radius:3.40282e38px}.rounded-tl-lg{border-top-left-radius:var(--radius-lg)}.rounded-tl-md{border-top-left-radius:var(--radius-md)}.rounded-tl-none{border-top-left-radius:0}.rounded-tl-sm{border-top-left-radius:var(--radius-sm)}.rounded-tl-xl{border-top-left-radius:var(--radius-xl)}.rounded-r{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.rounded-r-2xl{border-top-right-radius:var(--radius-2xl);border-bottom-right-radius:var(--radius-2xl)}.rounded-r-3xl{border-top-right-radius:var(--radius-3xl);border-bottom-right-radius:var(--radius-3xl)}.rounded-r-full{border-top-right-radius:3.40282e38px;border-bottom-right-radius:3.40282e38px}.rounded-r-lg{border-top-right-radius:var(--radius-lg);border-bottom-right-radius:var(--radius-lg)}.rounded-r-md{border-top-right-radius:var(--radius-md);border-bottom-right-radius:var(--radius-md)}.rounded-r-none{border-top-right-radius:0;border-bottom-right-radius:0}.rounded-r-sm{border-top-right-radius:var(--radius-sm);border-bottom-right-radius:var(--radius-sm)}.rounded-r-xl{border-top-right-radius:var(--radius-xl);border-bottom-right-radius:var(--radius-xl)}.rounded-tr{border-top-right-radius:.25rem}.rounded-tr-2xl{border-top-right-radius:var(--radius-2xl)}.rounded-tr-3xl{border-top-right-radius:var(--radius-3xl)}.rounded-tr-full{border-top-right-radius:3.40282e38px}.rounded-tr-lg{border-top-right-radius:var(--radius-lg)}.rounded-tr-md{border-top-right-radius:var(--radius-md)}.rounded-tr-none{border-top-right-radius:0}.rounded-tr-sm{border-top-right-radius:var(--radius-sm)}.rounded-tr-xl{border-top-right-radius:var(--radius-xl)}.rounded-b{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.rounded-b-2xl{border-bottom-right-radius:var(--radius-2xl);border-bottom-left-radius:var(--radius-2xl)}.rounded-b-3xl{border-bottom-right-radius:var(--radius-3xl);border-bottom-left-radius:var(--radius-3xl)}.rounded-b-full{border-bottom-right-radius:3.40282e38px;border-bottom-left-radius:3.40282e38px}.rounded-b-lg{border-bottom-right-radius:var(--radius-lg);border-bottom-left-radius:var(--radius-lg)}.rounded-b-md{border-bottom-right-radius:var(--radius-md);border-bottom-left-radius:var(--radius-md)}.rounded-b-none{border-bottom-right-radius:0;border-bottom-left-radius:0}.rounded-b-sm{border-bottom-right-radius:var(--radius-sm);border-bottom-left-radius:var(--radius-sm)}.rounded-b-xl{border-bottom-right-radius:var(--radius-xl);border-bottom-left-radius:var(--radius-xl)}.rounded-br{border-bottom-right-radius:.25rem}.rounded-br-2xl{border-bottom-right-radius:var(--radius-2xl)}.rounded-br-3xl{border-bottom-right-radius:var(--radius-3xl)}.rounded-br-full{border-bottom-right-radius:3.40282e38px}.rounded-br-lg{border-bottom-right-radius:var(--radius-lg)}.rounded-br-md{border-bottom-right-radius:var(--radius-md)}.rounded-br-none{border-bottom-right-radius:0}.rounded-br-sm{border-bottom-right-radius:var(--radius-sm)}.rounded-br-xl{border-bottom-right-radius:var(--radius-xl)}.rounded-bl{border-bottom-left-radius:.25rem}.rounded-bl-2xl{border-bottom-left-radius:var(--radius-2xl)}.rounded-bl-3xl{border-bottom-left-radius:var(--radius-3xl)}.rounded-bl-full{border-bottom-left-radius:3.40282e38px}.rounded-bl-lg{border-bottom-left-radius:var(--radius-lg)}.rounded-bl-md{border-bottom-left-radius:var(--radius-md)}.rounded-bl-none{border-bottom-left-radius:0}.rounded-bl-sm{border-bottom-left-radius:var(--radius-sm)}.rounded-bl-xl{border-bottom-left-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-0{border-style:var(--tw-border-style);border-width:0}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-3{border-style:var(--tw-border-style);border-width:3px}.border-4{border-style:var(--tw-border-style);border-width:4px}.border-8{border-style:var(--tw-border-style);border-width:8px}.border-x{border-inline-style:var(--tw-border-style);border-inline-width:1px}.border-x-0{border-inline-style:var(--tw-border-style);border-inline-width:0}.border-x-2{border-inline-style:var(--tw-border-style);border-inline-width:2px}.border-y{border-block-style:var(--tw-border-style);border-block-width:1px}.border-y-0{border-block-style:var(--tw-border-style);border-block-width:0}.border-y-2{border-block-style:var(--tw-border-style);border-block-width:2px}.\!border-t-0{border-top-style:var(--tw-border-style)!important;border-top-width:0!important}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-t-0{border-top-style:var(--tw-border-style);border-top-width:0}.border-t-2{border-top-style:var(--tw-border-style);border-top-width:2px}.border-t-4{border-top-style:var(--tw-border-style);border-top-width:4px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-r-0{border-right-style:var(--tw-border-style);border-right-width:0}.border-r-2{border-right-style:var(--tw-border-style);border-right-width:2px}.border-r-4{border-right-style:var(--tw-border-style);border-right-width:4px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-b-0{border-bottom-style:var(--tw-border-style);border-bottom-width:0}.border-b-2{border-bottom-style:var(--tw-border-style);border-bottom-width:2px}.border-b-4{border-bottom-style:var(--tw-border-style);border-bottom-width:4px}.border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.border-l-0{border-left-style:var(--tw-border-style);border-left-width:0}.border-l-2{border-left-style:var(--tw-border-style);border-left-width:2px}.border-l-4{border-left-style:var(--tw-border-style);border-left-width:4px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.border-dotted{--tw-border-style:dotted;border-style:dotted}.border-double{--tw-border-style:double;border-style:double}.border-hidden{--tw-border-style:hidden;border-style:hidden}.border-none{--tw-border-style:none;border-style:none}.border-solid{--tw-border-style:solid;border-style:solid}.border-amber-50{border-color:var(--color-amber-50)}.border-amber-100{border-color:var(--color-amber-100)}.border-amber-200{border-color:var(--color-amber-200)}.border-amber-300{border-color:var(--color-amber-300)}.border-amber-400{border-color:var(--color-amber-400)}.border-amber-500{border-color:var(--color-amber-500)}.border-amber-600{border-color:var(--color-amber-600)}.border-amber-700{border-color:var(--color-amber-700)}.border-amber-800{border-color:var(--color-amber-800)}.border-amber-900{border-color:var(--color-amber-900)}.border-amber-950{border-color:var(--color-amber-950)}.border-black{border-color:var(--color-black)}.border-blue-50{border-color:var(--color-blue-50)}.border-blue-100{border-color:var(--color-blue-100)}.border-blue-200{border-color:var(--color-blue-200)}.border-blue-300{border-color:var(--color-blue-300)}.border-blue-400{border-color:var(--color-blue-400)}.border-blue-500{border-color:var(--color-blue-500)}.border-blue-600{border-color:var(--color-blue-600)}.border-blue-700{border-color:var(--color-blue-700)}.border-blue-800{border-color:var(--color-blue-800)}.border-blue-900{border-color:var(--color-blue-900)}.border-blue-950{border-color:var(--color-blue-950)}.border-current{border-color:currentColor}.border-cyan-50{border-color:var(--color-cyan-50)}.border-cyan-100{border-color:var(--color-cyan-100)}.border-cyan-200{border-color:var(--color-cyan-200)}.border-cyan-300{border-color:var(--color-cyan-300)}.border-cyan-400{border-color:var(--color-cyan-400)}.border-cyan-500{border-color:var(--color-cyan-500)}.border-cyan-600{border-color:var(--color-cyan-600)}.border-cyan-700{border-color:var(--color-cyan-700)}.border-cyan-800{border-color:var(--color-cyan-800)}.border-cyan-900{border-color:var(--color-cyan-900)}.border-cyan-950{border-color:var(--color-cyan-950)}.border-emerald-50{border-color:var(--color-emerald-50)}.border-emerald-100{border-color:var(--color-emerald-100)}.border-emerald-200{border-color:var(--color-emerald-200)}.border-emerald-300{border-color:var(--color-emerald-300)}.border-emerald-400{border-color:var(--color-emerald-400)}.border-emerald-500{border-color:var(--color-emerald-500)}.border-emerald-600{border-color:var(--color-emerald-600)}.border-emerald-700{border-color:var(--color-emerald-700)}.border-emerald-800{border-color:var(--color-emerald-800)}.border-emerald-900{border-color:var(--color-emerald-900)}.border-emerald-950{border-color:var(--color-emerald-950)}.border-fuchsia-50{border-color:var(--color-fuchsia-50)}.border-fuchsia-100{border-color:var(--color-fuchsia-100)}.border-fuchsia-200{border-color:var(--color-fuchsia-200)}.border-fuchsia-300{border-color:var(--color-fuchsia-300)}.border-fuchsia-400{border-color:var(--color-fuchsia-400)}.border-fuchsia-500{border-color:var(--color-fuchsia-500)}.border-fuchsia-600{border-color:var(--color-fuchsia-600)}.border-fuchsia-700{border-color:var(--color-fuchsia-700)}.border-fuchsia-800{border-color:var(--color-fuchsia-800)}.border-fuchsia-900{border-color:var(--color-fuchsia-900)}.border-fuchsia-950{border-color:var(--color-fuchsia-950)}.border-gray-50{border-color:var(--color-gray-50)}.border-gray-100{border-color:var(--color-gray-100)}.border-gray-200{border-color:var(--color-gray-200)}.border-gray-300{border-color:var(--color-gray-300)}.border-gray-400{border-color:var(--color-gray-400)}.border-gray-500{border-color:var(--color-gray-500)}.border-gray-600{border-color:var(--color-gray-600)}.border-gray-700{border-color:var(--color-gray-700)}.border-gray-800{border-color:var(--color-gray-800)}.border-gray-900{border-color:var(--color-gray-900)}.border-gray-950{border-color:var(--color-gray-950)}.border-green-50{border-color:var(--color-green-50)}.border-green-100{border-color:var(--color-green-100)}.border-green-200{border-color:var(--color-green-200)}.border-green-300{border-color:var(--color-green-300)}.border-green-400{border-color:var(--color-green-400)}.border-green-500{border-color:var(--color-green-500)}.border-green-600{border-color:var(--color-green-600)}.border-green-700{border-color:var(--color-green-700)}.border-green-800{border-color:var(--color-green-800)}.border-green-900{border-color:var(--color-green-900)}.border-green-950{border-color:var(--color-green-950)}.border-indigo-50{border-color:var(--color-indigo-50)}.border-indigo-100{border-color:var(--color-indigo-100)}.border-indigo-200{border-color:var(--color-indigo-200)}.border-indigo-300{border-color:var(--color-indigo-300)}.border-indigo-400{border-color:var(--color-indigo-400)}.border-indigo-500{border-color:var(--color-indigo-500)}.border-indigo-600{border-color:var(--color-indigo-600)}.border-indigo-700{border-color:var(--color-indigo-700)}.border-indigo-800{border-color:var(--color-indigo-800)}.border-indigo-900{border-color:var(--color-indigo-900)}.border-indigo-950{border-color:var(--color-indigo-950)}.border-inherit{border-color:inherit}.border-lime-50{border-color:var(--color-lime-50)}.border-lime-100{border-color:var(--color-lime-100)}.border-lime-200{border-color:var(--color-lime-200)}.border-lime-300{border-color:var(--color-lime-300)}.border-lime-400{border-color:var(--color-lime-400)}.border-lime-500{border-color:var(--color-lime-500)}.border-lime-600{border-color:var(--color-lime-600)}.border-lime-700{border-color:var(--color-lime-700)}.border-lime-800{border-color:var(--color-lime-800)}.border-lime-900{border-color:var(--color-lime-900)}.border-lime-950{border-color:var(--color-lime-950)}.border-neutral-50{border-color:var(--color-neutral-50)}.border-neutral-100{border-color:var(--color-neutral-100)}.border-neutral-200{border-color:var(--color-neutral-200)}.border-neutral-300{border-color:var(--color-neutral-300)}.border-neutral-400{border-color:var(--color-neutral-400)}.border-neutral-500{border-color:var(--color-neutral-500)}.border-neutral-600{border-color:var(--color-neutral-600)}.border-neutral-700{border-color:var(--color-neutral-700)}.border-neutral-800{border-color:var(--color-neutral-800)}.border-neutral-900{border-color:var(--color-neutral-900)}.border-neutral-950{border-color:var(--color-neutral-950)}.border-orange-50{border-color:var(--color-orange-50)}.border-orange-100{border-color:var(--color-orange-100)}.border-orange-200{border-color:var(--color-orange-200)}.border-orange-300{border-color:var(--color-orange-300)}.border-orange-400{border-color:var(--color-orange-400)}.border-orange-500{border-color:var(--color-orange-500)}.border-orange-600{border-color:var(--color-orange-600)}.border-orange-700{border-color:var(--color-orange-700)}.border-orange-800{border-color:var(--color-orange-800)}.border-orange-900{border-color:var(--color-orange-900)}.border-orange-950{border-color:var(--color-orange-950)}.border-pink-50{border-color:var(--color-pink-50)}.border-pink-100{border-color:var(--color-pink-100)}.border-pink-200{border-color:var(--color-pink-200)}.border-pink-300{border-color:var(--color-pink-300)}.border-pink-400{border-color:var(--color-pink-400)}.border-pink-500{border-color:var(--color-pink-500)}.border-pink-600{border-color:var(--color-pink-600)}.border-pink-700{border-color:var(--color-pink-700)}.border-pink-800{border-color:var(--color-pink-800)}.border-pink-900{border-color:var(--color-pink-900)}.border-pink-950{border-color:var(--color-pink-950)}.border-purple-50{border-color:var(--color-purple-50)}.border-purple-100{border-color:var(--color-purple-100)}.border-purple-200{border-color:var(--color-purple-200)}.border-purple-300{border-color:var(--color-purple-300)}.border-purple-400{border-color:var(--color-purple-400)}.border-purple-500{border-color:var(--color-purple-500)}.border-purple-600{border-color:var(--color-purple-600)}.border-purple-700{border-color:var(--color-purple-700)}.border-purple-800{border-color:var(--color-purple-800)}.border-purple-900{border-color:var(--color-purple-900)}.border-purple-950{border-color:var(--color-purple-950)}.border-red-50{border-color:var(--color-red-50)}.border-red-100{border-color:var(--color-red-100)}.border-red-200{border-color:var(--color-red-200)}.border-red-300{border-color:var(--color-red-300)}.border-red-400{border-color:var(--color-red-400)}.border-red-500{border-color:var(--color-red-500)}.border-red-600{border-color:var(--color-red-600)}.border-red-700{border-color:var(--color-red-700)}.border-red-800{border-color:var(--color-red-800)}.border-red-900{border-color:var(--color-red-900)}.border-red-950{border-color:var(--color-red-950)}.border-rose-50{border-color:var(--color-rose-50)}.border-rose-100{border-color:var(--color-rose-100)}.border-rose-200{border-color:var(--color-rose-200)}.border-rose-300{border-color:var(--color-rose-300)}.border-rose-400{border-color:var(--color-rose-400)}.border-rose-500{border-color:var(--color-rose-500)}.border-rose-600{border-color:var(--color-rose-600)}.border-rose-700{border-color:var(--color-rose-700)}.border-rose-800{border-color:var(--color-rose-800)}.border-rose-900{border-color:var(--color-rose-900)}.border-rose-950{border-color:var(--color-rose-950)}.border-sky-50{border-color:var(--color-sky-50)}.border-sky-100{border-color:var(--color-sky-100)}.border-sky-200{border-color:var(--color-sky-200)}.border-sky-300{border-color:var(--color-sky-300)}.border-sky-400{border-color:var(--color-sky-400)}.border-sky-500{border-color:var(--color-sky-500)}.border-sky-600{border-color:var(--color-sky-600)}.border-sky-700{border-color:var(--color-sky-700)}.border-sky-800{border-color:var(--color-sky-800)}.border-sky-900{border-color:var(--color-sky-900)}.border-sky-950{border-color:var(--color-sky-950)}.border-slate-50{border-color:var(--color-slate-50)}.border-slate-100{border-color:var(--color-slate-100)}.border-slate-200{border-color:var(--color-slate-200)}.border-slate-300{border-color:var(--color-slate-300)}.border-slate-400{border-color:var(--color-slate-400)}.border-slate-500{border-color:var(--color-slate-500)}.border-slate-600{border-color:var(--color-slate-600)}.border-slate-700{border-color:var(--color-slate-700)}.border-slate-800{border-color:var(--color-slate-800)}.border-slate-900{border-color:var(--color-slate-900)}.border-slate-950{border-color:var(--color-slate-950)}.border-stone-50{border-color:var(--color-stone-50)}.border-stone-100{border-color:var(--color-stone-100)}.border-stone-200{border-color:var(--color-stone-200)}.border-stone-300{border-color:var(--color-stone-300)}.border-stone-400{border-color:var(--color-stone-400)}.border-stone-500{border-color:var(--color-stone-500)}.border-stone-600{border-color:var(--color-stone-600)}.border-stone-700{border-color:var(--color-stone-700)}.border-stone-800{border-color:var(--color-stone-800)}.border-stone-900{border-color:var(--color-stone-900)}.border-stone-950{border-color:var(--color-stone-950)}.border-teal-50{border-color:var(--color-teal-50)}.border-teal-100{border-color:var(--color-teal-100)}.border-teal-200{border-color:var(--color-teal-200)}.border-teal-300{border-color:var(--color-teal-300)}.border-teal-400{border-color:var(--color-teal-400)}.border-teal-500{border-color:var(--color-teal-500)}.border-teal-600{border-color:var(--color-teal-600)}.border-teal-700{border-color:var(--color-teal-700)}.border-teal-800{border-color:var(--color-teal-800)}.border-teal-900{border-color:var(--color-teal-900)}.border-teal-950{border-color:var(--color-teal-950)}.border-transparent{border-color:#0000}.border-violet-50{border-color:var(--color-violet-50)}.border-violet-100{border-color:var(--color-violet-100)}.border-violet-200{border-color:var(--color-violet-200)}.border-violet-300{border-color:var(--color-violet-300)}.border-violet-400{border-color:var(--color-violet-400)}.border-violet-500{border-color:var(--color-violet-500)}.border-violet-600{border-color:var(--color-violet-600)}.border-violet-700{border-color:var(--color-violet-700)}.border-violet-800{border-color:var(--color-violet-800)}.border-violet-900{border-color:var(--color-violet-900)}.border-violet-950{border-color:var(--color-violet-950)}.border-white{border-color:var(--color-white)}.border-white\/30{border-color:#ffffff4d}@supports (color:color-mix(in lab,red,red)){.border-white\/30{border-color:color-mix(in oklab,var(--color-white) 30%,transparent)}}.border-yellow-50{border-color:var(--color-yellow-50)}.border-yellow-100{border-color:var(--color-yellow-100)}.border-yellow-200{border-color:var(--color-yellow-200)}.border-yellow-300{border-color:var(--color-yellow-300)}.border-yellow-400{border-color:var(--color-yellow-400)}.border-yellow-500{border-color:var(--color-yellow-500)}.border-yellow-600{border-color:var(--color-yellow-600)}.border-yellow-700{border-color:var(--color-yellow-700)}.border-yellow-800{border-color:var(--color-yellow-800)}.border-yellow-900{border-color:var(--color-yellow-900)}.border-yellow-950{border-color:var(--color-yellow-950)}.border-zinc-50{border-color:var(--color-zinc-50)}.border-zinc-100{border-color:var(--color-zinc-100)}.border-zinc-200{border-color:var(--color-zinc-200)}.border-zinc-300{border-color:var(--color-zinc-300)}.border-zinc-400{border-color:var(--color-zinc-400)}.border-zinc-500{border-color:var(--color-zinc-500)}.border-zinc-600{border-color:var(--color-zinc-600)}.border-zinc-700{border-color:var(--color-zinc-700)}.border-zinc-800{border-color:var(--color-zinc-800)}.border-zinc-900{border-color:var(--color-zinc-900)}.border-zinc-950{border-color:var(--color-zinc-950)}.border-t-current{border-top-color:currentColor}.border-t-white{border-top-color:var(--color-white)}.bg-amber-50{background-color:var(--color-amber-50)}.bg-amber-100{background-color:var(--color-amber-100)}.bg-amber-200{background-color:var(--color-amber-200)}.bg-amber-300{background-color:var(--color-amber-300)}.bg-amber-400{background-color:var(--color-amber-400)}.bg-amber-500{background-color:var(--color-amber-500)}.bg-amber-600{background-color:var(--color-amber-600)}.bg-amber-700{background-color:var(--color-amber-700)}.bg-amber-800{background-color:var(--color-amber-800)}.bg-amber-900{background-color:var(--color-amber-900)}.bg-amber-950{background-color:var(--color-amber-950)}.bg-black{background-color:var(--color-black)}.bg-black\/0{background-color:#0000}@supports (color:color-mix(in lab,red,red)){.bg-black\/0{background-color:color-mix(in oklab,var(--color-black) 0%,transparent)}}.bg-black\/50{background-color:#00000080}@supports (color:color-mix(in lab,red,red)){.bg-black\/50{background-color:color-mix(in oklab,var(--color-black) 50%,transparent)}}.bg-blue-50{background-color:var(--color-blue-50)}.bg-blue-50\/70{background-color:#eff6ffb3}@supports (color:color-mix(in lab,red,red)){.bg-blue-50\/70{background-color:color-mix(in oklab,var(--color-blue-50) 70%,transparent)}}.bg-blue-100{background-color:var(--color-blue-100)}.bg-blue-200{background-color:var(--color-blue-200)}.bg-blue-300{background-color:var(--color-blue-300)}.bg-blue-400{background-color:var(--color-blue-400)}.bg-blue-500{background-color:var(--color-blue-500)}.bg-blue-600{background-color:var(--color-blue-600)}.bg-blue-700{background-color:var(--color-blue-700)}.bg-blue-800{background-color:var(--color-blue-800)}.bg-blue-900{background-color:var(--color-blue-900)}.bg-blue-950{background-color:var(--color-blue-950)}.bg-current{background-color:currentColor}.bg-cyan-50{background-color:var(--color-cyan-50)}.bg-cyan-100{background-color:var(--color-cyan-100)}.bg-cyan-200{background-color:var(--color-cyan-200)}.bg-cyan-300{background-color:var(--color-cyan-300)}.bg-cyan-400{background-color:var(--color-cyan-400)}.bg-cyan-500{background-color:var(--color-cyan-500)}.bg-cyan-600{background-color:var(--color-cyan-600)}.bg-cyan-700{background-color:var(--color-cyan-700)}.bg-cyan-800{background-color:var(--color-cyan-800)}.bg-cyan-900{background-color:var(--color-cyan-900)}.bg-cyan-950{background-color:var(--color-cyan-950)}.bg-emerald-50{background-color:var(--color-emerald-50)}.bg-emerald-100{background-color:var(--color-emerald-100)}.bg-emerald-200{background-color:var(--color-emerald-200)}.bg-emerald-300{background-color:var(--color-emerald-300)}.bg-emerald-400{background-color:var(--color-emerald-400)}.bg-emerald-500{background-color:var(--color-emerald-500)}.bg-emerald-600{background-color:var(--color-emerald-600)}.bg-emerald-700{background-color:var(--color-emerald-700)}.bg-emerald-800{background-color:var(--color-emerald-800)}.bg-emerald-900{background-color:var(--color-emerald-900)}.bg-emerald-950{background-color:var(--color-emerald-950)}.bg-fuchsia-50{background-color:var(--color-fuchsia-50)}.bg-fuchsia-100{background-color:var(--color-fuchsia-100)}.bg-fuchsia-200{background-color:var(--color-fuchsia-200)}.bg-fuchsia-300{background-color:var(--color-fuchsia-300)}.bg-fuchsia-400{background-color:var(--color-fuchsia-400)}.bg-fuchsia-500{background-color:var(--color-fuchsia-500)}.bg-fuchsia-600{background-color:var(--color-fuchsia-600)}.bg-fuchsia-700{background-color:var(--color-fuchsia-700)}.bg-fuchsia-800{background-color:var(--color-fuchsia-800)}.bg-fuchsia-900{background-color:var(--color-fuchsia-900)}.bg-fuchsia-950{background-color:var(--color-fuchsia-950)}.bg-gray-50{background-color:var(--color-gray-50)}.bg-gray-100{background-color:var(--color-gray-100)}.bg-gray-100\/60{background-color:#f3f4f699}@supports (color:color-mix(in lab,red,red)){.bg-gray-100\/60{background-color:color-mix(in oklab,var(--color-gray-100) 60%,transparent)}}.bg-gray-200{background-color:var(--color-gray-200)}.bg-gray-300{background-color:var(--color-gray-300)}.bg-gray-400{background-color:var(--color-gray-400)}.bg-gray-500{background-color:var(--color-gray-500)}.bg-gray-600{background-color:var(--color-gray-600)}.bg-gray-700{background-color:var(--color-gray-700)}.bg-gray-800{background-color:var(--color-gray-800)}.bg-gray-800\/50{background-color:#1e293980}@supports (color:color-mix(in lab,red,red)){.bg-gray-800\/50{background-color:color-mix(in oklab,var(--color-gray-800) 50%,transparent)}}.bg-gray-900{background-color:var(--color-gray-900)}.bg-gray-950{background-color:var(--color-gray-950)}.bg-green-50{background-color:var(--color-green-50)}.bg-green-100{background-color:var(--color-green-100)}.bg-green-200{background-color:var(--color-green-200)}.bg-green-300{background-color:var(--color-green-300)}.bg-green-400{background-color:var(--color-green-400)}.bg-green-500{background-color:var(--color-green-500)}.bg-green-600{background-color:var(--color-green-600)}.bg-green-700{background-color:var(--color-green-700)}.bg-green-800{background-color:var(--color-green-800)}.bg-green-900{background-color:var(--color-green-900)}.bg-green-950{background-color:var(--color-green-950)}.bg-indigo-50{background-color:var(--color-indigo-50)}.bg-indigo-100{background-color:var(--color-indigo-100)}.bg-indigo-200{background-color:var(--color-indigo-200)}.bg-indigo-300{background-color:var(--color-indigo-300)}.bg-indigo-400{background-color:var(--color-indigo-400)}.bg-indigo-500{background-color:var(--color-indigo-500)}.bg-indigo-600{background-color:var(--color-indigo-600)}.bg-indigo-700{background-color:var(--color-indigo-700)}.bg-indigo-800{background-color:var(--color-indigo-800)}.bg-indigo-900{background-color:var(--color-indigo-900)}.bg-indigo-950{background-color:var(--color-indigo-950)}.bg-inherit{background-color:inherit}.bg-lime-50{background-color:var(--color-lime-50)}.bg-lime-100{background-color:var(--color-lime-100)}.bg-lime-200{background-color:var(--color-lime-200)}.bg-lime-300{background-color:var(--color-lime-300)}.bg-lime-400{background-color:var(--color-lime-400)}.bg-lime-500{background-color:var(--color-lime-500)}.bg-lime-600{background-color:var(--color-lime-600)}.bg-lime-700{background-color:var(--color-lime-700)}.bg-lime-800{background-color:var(--color-lime-800)}.bg-lime-900{background-color:var(--color-lime-900)}.bg-lime-950{background-color:var(--color-lime-950)}.bg-neutral-50{background-color:var(--color-neutral-50)}.bg-neutral-100{background-color:var(--color-neutral-100)}.bg-neutral-200{background-color:var(--color-neutral-200)}.bg-neutral-300{background-color:var(--color-neutral-300)}.bg-neutral-400{background-color:var(--color-neutral-400)}.bg-neutral-500{background-color:var(--color-neutral-500)}.bg-neutral-600{background-color:var(--color-neutral-600)}.bg-neutral-700{background-color:var(--color-neutral-700)}.bg-neutral-800{background-color:var(--color-neutral-800)}.bg-neutral-900{background-color:var(--color-neutral-900)}.bg-neutral-950{background-color:var(--color-neutral-950)}.bg-orange-50{background-color:var(--color-orange-50)}.bg-orange-100{background-color:var(--color-orange-100)}.bg-orange-200{background-color:var(--color-orange-200)}.bg-orange-300{background-color:var(--color-orange-300)}.bg-orange-400{background-color:var(--color-orange-400)}.bg-orange-500{background-color:var(--color-orange-500)}.bg-orange-600{background-color:var(--color-orange-600)}.bg-orange-700{background-color:var(--color-orange-700)}.bg-orange-800{background-color:var(--color-orange-800)}.bg-orange-900{background-color:var(--color-orange-900)}.bg-orange-950{background-color:var(--color-orange-950)}.bg-pink-50{background-color:var(--color-pink-50)}.bg-pink-100{background-color:var(--color-pink-100)}.bg-pink-200{background-color:var(--color-pink-200)}.bg-pink-300{background-color:var(--color-pink-300)}.bg-pink-400{background-color:var(--color-pink-400)}.bg-pink-500{background-color:var(--color-pink-500)}.bg-pink-600{background-color:var(--color-pink-600)}.bg-pink-700{background-color:var(--color-pink-700)}.bg-pink-800{background-color:var(--color-pink-800)}.bg-pink-900{background-color:var(--color-pink-900)}.bg-pink-950{background-color:var(--color-pink-950)}.bg-purple-50{background-color:var(--color-purple-50)}.bg-purple-100{background-color:var(--color-purple-100)}.bg-purple-200{background-color:var(--color-purple-200)}.bg-purple-300{background-color:var(--color-purple-300)}.bg-purple-400{background-color:var(--color-purple-400)}.bg-purple-500{background-color:var(--color-purple-500)}.bg-purple-600{background-color:var(--color-purple-600)}.bg-purple-700{background-color:var(--color-purple-700)}.bg-purple-800{background-color:var(--color-purple-800)}.bg-purple-900{background-color:var(--color-purple-900)}.bg-purple-950{background-color:var(--color-purple-950)}.bg-red-50{background-color:var(--color-red-50)}.bg-red-100{background-color:var(--color-red-100)}.bg-red-200{background-color:var(--color-red-200)}.bg-red-300{background-color:var(--color-red-300)}.bg-red-400{background-color:var(--color-red-400)}.bg-red-500{background-color:var(--color-red-500)}.bg-red-500\/50{background-color:#fb2c3680}@supports (color:color-mix(in lab,red,red)){.bg-red-500\/50{background-color:color-mix(in oklab,var(--color-red-500) 50%,transparent)}}.bg-red-600{background-color:var(--color-red-600)}.bg-red-700{background-color:var(--color-red-700)}.bg-red-800{background-color:var(--color-red-800)}.bg-red-900{background-color:var(--color-red-900)}.bg-red-950{background-color:var(--color-red-950)}.bg-rose-50{background-color:var(--color-rose-50)}.bg-rose-100{background-color:var(--color-rose-100)}.bg-rose-200{background-color:var(--color-rose-200)}.bg-rose-300{background-color:var(--color-rose-300)}.bg-rose-400{background-color:var(--color-rose-400)}.bg-rose-500{background-color:var(--color-rose-500)}.bg-rose-600{background-color:var(--color-rose-600)}.bg-rose-700{background-color:var(--color-rose-700)}.bg-rose-800{background-color:var(--color-rose-800)}.bg-rose-900{background-color:var(--color-rose-900)}.bg-rose-950{background-color:var(--color-rose-950)}.bg-sky-50{background-color:var(--color-sky-50)}.bg-sky-100{background-color:var(--color-sky-100)}.bg-sky-200{background-color:var(--color-sky-200)}.bg-sky-300{background-color:var(--color-sky-300)}.bg-sky-400{background-color:var(--color-sky-400)}.bg-sky-500{background-color:var(--color-sky-500)}.bg-sky-600{background-color:var(--color-sky-600)}.bg-sky-700{background-color:var(--color-sky-700)}.bg-sky-800{background-color:var(--color-sky-800)}.bg-sky-900{background-color:var(--color-sky-900)}.bg-sky-950{background-color:var(--color-sky-950)}.bg-slate-50{background-color:var(--color-slate-50)}.bg-slate-100{background-color:var(--color-slate-100)}.bg-slate-200{background-color:var(--color-slate-200)}.bg-slate-300{background-color:var(--color-slate-300)}.bg-slate-400{background-color:var(--color-slate-400)}.bg-slate-500{background-color:var(--color-slate-500)}.bg-slate-600{background-color:var(--color-slate-600)}.bg-slate-700{background-color:var(--color-slate-700)}.bg-slate-800{background-color:var(--color-slate-800)}.bg-slate-900{background-color:var(--color-slate-900)}.bg-slate-950{background-color:var(--color-slate-950)}.bg-stone-50{background-color:var(--color-stone-50)}.bg-stone-100{background-color:var(--color-stone-100)}.bg-stone-200{background-color:var(--color-stone-200)}.bg-stone-300{background-color:var(--color-stone-300)}.bg-stone-400{background-color:var(--color-stone-400)}.bg-stone-500{background-color:var(--color-stone-500)}.bg-stone-600{background-color:var(--color-stone-600)}.bg-stone-700{background-color:var(--color-stone-700)}.bg-stone-800{background-color:var(--color-stone-800)}.bg-stone-900{background-color:var(--color-stone-900)}.bg-stone-950{background-color:var(--color-stone-950)}.bg-teal-50{background-color:var(--color-teal-50)}.bg-teal-100{background-color:var(--color-teal-100)}.bg-teal-200{background-color:var(--color-teal-200)}.bg-teal-300{background-color:var(--color-teal-300)}.bg-teal-400{background-color:var(--color-teal-400)}.bg-teal-500{background-color:var(--color-teal-500)}.bg-teal-600{background-color:var(--color-teal-600)}.bg-teal-700{background-color:var(--color-teal-700)}.bg-teal-800{background-color:var(--color-teal-800)}.bg-teal-900{background-color:var(--color-teal-900)}.bg-teal-950{background-color:var(--color-teal-950)}.bg-transparent{background-color:#0000}.bg-violet-50{background-color:var(--color-violet-50)}.bg-violet-100{background-color:var(--color-violet-100)}.bg-violet-200{background-color:var(--color-violet-200)}.bg-violet-300{background-color:var(--color-violet-300)}.bg-violet-400{background-color:var(--color-violet-400)}.bg-violet-500{background-color:var(--color-violet-500)}.bg-violet-600{background-color:var(--color-violet-600)}.bg-violet-700{background-color:var(--color-violet-700)}.bg-violet-800{background-color:var(--color-violet-800)}.bg-violet-900{background-color:var(--color-violet-900)}.bg-violet-950{background-color:var(--color-violet-950)}.bg-white{background-color:var(--color-white)}.bg-white\/90{background-color:#ffffffe6}@supports (color:color-mix(in lab,red,red)){.bg-white\/90{background-color:color-mix(in oklab,var(--color-white) 90%,transparent)}}.bg-yellow-50{background-color:var(--color-yellow-50)}.bg-yellow-100{background-color:var(--color-yellow-100)}.bg-yellow-200{background-color:var(--color-yellow-200)}.bg-yellow-300{background-color:var(--color-yellow-300)}.bg-yellow-400{background-color:var(--color-yellow-400)}.bg-yellow-500{background-color:var(--color-yellow-500)}.bg-yellow-600{background-color:var(--color-yellow-600)}.bg-yellow-700{background-color:var(--color-yellow-700)}.bg-yellow-800{background-color:var(--color-yellow-800)}.bg-yellow-900{background-color:var(--color-yellow-900)}.bg-yellow-950{background-color:var(--color-yellow-950)}.bg-zinc-50{background-color:var(--color-zinc-50)}.bg-zinc-100{background-color:var(--color-zinc-100)}.bg-zinc-200{background-color:var(--color-zinc-200)}.bg-zinc-300{background-color:var(--color-zinc-300)}.bg-zinc-400{background-color:var(--color-zinc-400)}.bg-zinc-500{background-color:var(--color-zinc-500)}.bg-zinc-600{background-color:var(--color-zinc-600)}.bg-zinc-700{background-color:var(--color-zinc-700)}.bg-zinc-800{background-color:var(--color-zinc-800)}.bg-zinc-900{background-color:var(--color-zinc-900)}.bg-zinc-950{background-color:var(--color-zinc-950)}.bg-gradient-to-b{--tw-gradient-position:to bottom in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.bg-gradient-to-bl{--tw-gradient-position:to bottom left in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.bg-gradient-to-br{--tw-gradient-position:to bottom right in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.bg-gradient-to-l{--tw-gradient-position:to left in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.bg-gradient-to-r{--tw-gradient-position:to right in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.bg-gradient-to-t{--tw-gradient-position:to top in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.bg-gradient-to-tl{--tw-gradient-position:to top left in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.bg-gradient-to-tr{--tw-gradient-position:to top right in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.bg-none{background-image:none}.from-black{--tw-gradient-from:var(--color-black);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-blue-50{--tw-gradient-from:var(--color-blue-50);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-blue-100{--tw-gradient-from:var(--color-blue-100);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-blue-200{--tw-gradient-from:var(--color-blue-200);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-blue-300{--tw-gradient-from:var(--color-blue-300);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-blue-400{--tw-gradient-from:var(--color-blue-400);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-blue-500{--tw-gradient-from:var(--color-blue-500);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-blue-600{--tw-gradient-from:var(--color-blue-600);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-blue-700{--tw-gradient-from:var(--color-blue-700);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-blue-800{--tw-gradient-from:var(--color-blue-800);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-blue-900{--tw-gradient-from:var(--color-blue-900);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-current{--tw-gradient-from:currentcolor;--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-gray-50{--tw-gradient-from:var(--color-gray-50);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-gray-100{--tw-gradient-from:var(--color-gray-100);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-gray-200{--tw-gradient-from:var(--color-gray-200);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-gray-300{--tw-gradient-from:var(--color-gray-300);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-gray-400{--tw-gradient-from:var(--color-gray-400);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-gray-500{--tw-gradient-from:var(--color-gray-500);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-gray-600{--tw-gradient-from:var(--color-gray-600);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-gray-700{--tw-gradient-from:var(--color-gray-700);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-gray-800{--tw-gradient-from:var(--color-gray-800);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-gray-900{--tw-gradient-from:var(--color-gray-900);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-green-50{--tw-gradient-from:var(--color-green-50);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-green-100{--tw-gradient-from:var(--color-green-100);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-green-200{--tw-gradient-from:var(--color-green-200);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-green-300{--tw-gradient-from:var(--color-green-300);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-green-400{--tw-gradient-from:var(--color-green-400);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-green-500{--tw-gradient-from:var(--color-green-500);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-green-600{--tw-gradient-from:var(--color-green-600);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-green-700{--tw-gradient-from:var(--color-green-700);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-green-800{--tw-gradient-from:var(--color-green-800);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-green-900{--tw-gradient-from:var(--color-green-900);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-indigo-50{--tw-gradient-from:var(--color-indigo-50);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-indigo-100{--tw-gradient-from:var(--color-indigo-100);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-indigo-200{--tw-gradient-from:var(--color-indigo-200);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-indigo-300{--tw-gradient-from:var(--color-indigo-300);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-indigo-400{--tw-gradient-from:var(--color-indigo-400);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-indigo-500{--tw-gradient-from:var(--color-indigo-500);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-indigo-600{--tw-gradient-from:var(--color-indigo-600);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-indigo-700{--tw-gradient-from:var(--color-indigo-700);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-indigo-800{--tw-gradient-from:var(--color-indigo-800);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-indigo-900{--tw-gradient-from:var(--color-indigo-900);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-inherit{--tw-gradient-from:inherit;--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-orange-50{--tw-gradient-from:var(--color-orange-50);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-orange-100{--tw-gradient-from:var(--color-orange-100);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-orange-200{--tw-gradient-from:var(--color-orange-200);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-orange-300{--tw-gradient-from:var(--color-orange-300);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-orange-400{--tw-gradient-from:var(--color-orange-400);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-orange-500{--tw-gradient-from:var(--color-orange-500);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-orange-600{--tw-gradient-from:var(--color-orange-600);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-orange-700{--tw-gradient-from:var(--color-orange-700);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-orange-800{--tw-gradient-from:var(--color-orange-800);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-orange-900{--tw-gradient-from:var(--color-orange-900);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-pink-50{--tw-gradient-from:var(--color-pink-50);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-pink-100{--tw-gradient-from:var(--color-pink-100);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-pink-200{--tw-gradient-from:var(--color-pink-200);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-pink-300{--tw-gradient-from:var(--color-pink-300);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-pink-400{--tw-gradient-from:var(--color-pink-400);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-pink-500{--tw-gradient-from:var(--color-pink-500);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-pink-600{--tw-gradient-from:var(--color-pink-600);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-pink-700{--tw-gradient-from:var(--color-pink-700);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-pink-800{--tw-gradient-from:var(--color-pink-800);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-pink-900{--tw-gradient-from:var(--color-pink-900);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-purple-50{--tw-gradient-from:var(--color-purple-50);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-purple-100{--tw-gradient-from:var(--color-purple-100);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-purple-200{--tw-gradient-from:var(--color-purple-200);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-purple-300{--tw-gradient-from:var(--color-purple-300);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-purple-400{--tw-gradient-from:var(--color-purple-400);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-purple-500{--tw-gradient-from:var(--color-purple-500);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-purple-600{--tw-gradient-from:var(--color-purple-600);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-purple-700{--tw-gradient-from:var(--color-purple-700);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-purple-800{--tw-gradient-from:var(--color-purple-800);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-purple-900{--tw-gradient-from:var(--color-purple-900);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-red-50{--tw-gradient-from:var(--color-red-50);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-red-100{--tw-gradient-from:var(--color-red-100);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-red-200{--tw-gradient-from:var(--color-red-200);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-red-300{--tw-gradient-from:var(--color-red-300);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-red-400{--tw-gradient-from:var(--color-red-400);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-red-500{--tw-gradient-from:var(--color-red-500);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-red-600{--tw-gradient-from:var(--color-red-600);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-red-700{--tw-gradient-from:var(--color-red-700);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-red-800{--tw-gradient-from:var(--color-red-800);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-red-900{--tw-gradient-from:var(--color-red-900);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-slate-50{--tw-gradient-from:var(--color-slate-50);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-slate-100{--tw-gradient-from:var(--color-slate-100);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-slate-200{--tw-gradient-from:var(--color-slate-200);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-slate-300{--tw-gradient-from:var(--color-slate-300);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-slate-400{--tw-gradient-from:var(--color-slate-400);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-slate-500{--tw-gradient-from:var(--color-slate-500);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-slate-600{--tw-gradient-from:var(--color-slate-600);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-slate-700{--tw-gradient-from:var(--color-slate-700);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-slate-800{--tw-gradient-from:var(--color-slate-800);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-slate-900{--tw-gradient-from:var(--color-slate-900);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-transparent{--tw-gradient-from:transparent;--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-white{--tw-gradient-from:var(--color-white);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-yellow-50{--tw-gradient-from:var(--color-yellow-50);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-yellow-100{--tw-gradient-from:var(--color-yellow-100);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-yellow-200{--tw-gradient-from:var(--color-yellow-200);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-yellow-300{--tw-gradient-from:var(--color-yellow-300);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-yellow-400{--tw-gradient-from:var(--color-yellow-400);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-yellow-500{--tw-gradient-from:var(--color-yellow-500);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-yellow-600{--tw-gradient-from:var(--color-yellow-600);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-yellow-700{--tw-gradient-from:var(--color-yellow-700);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-yellow-800{--tw-gradient-from:var(--color-yellow-800);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-yellow-900{--tw-gradient-from:var(--color-yellow-900);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.via-black{--tw-gradient-via:var(--color-black);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-blue-50{--tw-gradient-via:var(--color-blue-50);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-blue-100{--tw-gradient-via:var(--color-blue-100);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-blue-200{--tw-gradient-via:var(--color-blue-200);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-blue-300{--tw-gradient-via:var(--color-blue-300);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-blue-400{--tw-gradient-via:var(--color-blue-400);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-blue-500{--tw-gradient-via:var(--color-blue-500);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-blue-600{--tw-gradient-via:var(--color-blue-600);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-blue-700{--tw-gradient-via:var(--color-blue-700);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-blue-800{--tw-gradient-via:var(--color-blue-800);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-blue-900{--tw-gradient-via:var(--color-blue-900);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-current{--tw-gradient-via:currentcolor;--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-gray-50{--tw-gradient-via:var(--color-gray-50);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-gray-100{--tw-gradient-via:var(--color-gray-100);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-gray-200{--tw-gradient-via:var(--color-gray-200);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-gray-300{--tw-gradient-via:var(--color-gray-300);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-gray-400{--tw-gradient-via:var(--color-gray-400);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-gray-500{--tw-gradient-via:var(--color-gray-500);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-gray-600{--tw-gradient-via:var(--color-gray-600);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-gray-700{--tw-gradient-via:var(--color-gray-700);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-gray-800{--tw-gradient-via:var(--color-gray-800);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-gray-900{--tw-gradient-via:var(--color-gray-900);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-inherit{--tw-gradient-via:inherit;--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-slate-50{--tw-gradient-via:var(--color-slate-50);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-slate-100{--tw-gradient-via:var(--color-slate-100);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-slate-200{--tw-gradient-via:var(--color-slate-200);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-slate-300{--tw-gradient-via:var(--color-slate-300);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-slate-400{--tw-gradient-via:var(--color-slate-400);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-slate-500{--tw-gradient-via:var(--color-slate-500);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-slate-600{--tw-gradient-via:var(--color-slate-600);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-slate-700{--tw-gradient-via:var(--color-slate-700);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-slate-800{--tw-gradient-via:var(--color-slate-800);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-slate-900{--tw-gradient-via:var(--color-slate-900);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-transparent{--tw-gradient-via:transparent;--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-white{--tw-gradient-via:var(--color-white);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.to-black{--tw-gradient-to:var(--color-black);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-blue-50{--tw-gradient-to:var(--color-blue-50);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-blue-100{--tw-gradient-to:var(--color-blue-100);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-blue-200{--tw-gradient-to:var(--color-blue-200);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-blue-300{--tw-gradient-to:var(--color-blue-300);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-blue-400{--tw-gradient-to:var(--color-blue-400);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-blue-500{--tw-gradient-to:var(--color-blue-500);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-blue-600{--tw-gradient-to:var(--color-blue-600);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-blue-700{--tw-gradient-to:var(--color-blue-700);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-blue-800{--tw-gradient-to:var(--color-blue-800);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-blue-900{--tw-gradient-to:var(--color-blue-900);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-current{--tw-gradient-to:currentcolor;--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-gray-50{--tw-gradient-to:var(--color-gray-50);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-gray-100{--tw-gradient-to:var(--color-gray-100);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-gray-200{--tw-gradient-to:var(--color-gray-200);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-gray-300{--tw-gradient-to:var(--color-gray-300);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-gray-400{--tw-gradient-to:var(--color-gray-400);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-gray-500{--tw-gradient-to:var(--color-gray-500);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-gray-600{--tw-gradient-to:var(--color-gray-600);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-gray-700{--tw-gradient-to:var(--color-gray-700);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-gray-800{--tw-gradient-to:var(--color-gray-800);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-gray-900{--tw-gradient-to:var(--color-gray-900);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-indigo-50{--tw-gradient-to:var(--color-indigo-50);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-inherit{--tw-gradient-to:inherit;--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-purple-50{--tw-gradient-to:var(--color-purple-50);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-purple-100{--tw-gradient-to:var(--color-purple-100);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-purple-200{--tw-gradient-to:var(--color-purple-200);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-purple-300{--tw-gradient-to:var(--color-purple-300);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-purple-400{--tw-gradient-to:var(--color-purple-400);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-purple-500{--tw-gradient-to:var(--color-purple-500);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-purple-600{--tw-gradient-to:var(--color-purple-600);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-purple-700{--tw-gradient-to:var(--color-purple-700);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-purple-800{--tw-gradient-to:var(--color-purple-800);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-purple-900{--tw-gradient-to:var(--color-purple-900);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-red-50{--tw-gradient-to:var(--color-red-50);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-red-100{--tw-gradient-to:var(--color-red-100);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-red-200{--tw-gradient-to:var(--color-red-200);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-red-300{--tw-gradient-to:var(--color-red-300);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-red-400{--tw-gradient-to:var(--color-red-400);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-red-500{--tw-gradient-to:var(--color-red-500);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-red-600{--tw-gradient-to:var(--color-red-600);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-red-700{--tw-gradient-to:var(--color-red-700);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-red-800{--tw-gradient-to:var(--color-red-800);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-red-900{--tw-gradient-to:var(--color-red-900);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-slate-50{--tw-gradient-to:var(--color-slate-50);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-slate-100{--tw-gradient-to:var(--color-slate-100);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-slate-200{--tw-gradient-to:var(--color-slate-200);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-slate-300{--tw-gradient-to:var(--color-slate-300);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-slate-400{--tw-gradient-to:var(--color-slate-400);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-slate-500{--tw-gradient-to:var(--color-slate-500);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-slate-600{--tw-gradient-to:var(--color-slate-600);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-slate-700{--tw-gradient-to:var(--color-slate-700);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-slate-800{--tw-gradient-to:var(--color-slate-800);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-slate-900{--tw-gradient-to:var(--color-slate-900);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-transparent{--tw-gradient-to:transparent;--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-white{--tw-gradient-to:var(--color-white);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.object-contain{object-fit:contain}.object-cover{object-fit:cover}.object-fill{object-fit:fill}.object-none{object-fit:none}.object-scale-down{object-fit:scale-down}.object-bottom{object-position:bottom}.object-center{object-position:center}.object-left{object-position:left}.object-left-bottom{object-position:left bottom}.object-left-top{object-position:left top}.object-right{object-position:right}.object-right-bottom{object-position:right bottom}.object-right-top{object-position:right top}.object-top{object-position:top}.\!p-0{padding:calc(var(--spacing) * 0)!important}.\!p-1{padding:calc(var(--spacing) * 1)!important}.\!p-2{padding:calc(var(--spacing) * 2)!important}.\!p-3{padding:calc(var(--spacing) * 3)!important}.\!p-4{padding:calc(var(--spacing) * 4)!important}.\!p-5{padding:calc(var(--spacing) * 5)!important}.\!p-6{padding:calc(var(--spacing) * 6)!important}.\!p-8{padding:calc(var(--spacing) * 8)!important}.\!p-10{padding:calc(var(--spacing) * 10)!important}.\!p-12{padding:calc(var(--spacing) * 12)!important}.\!p-16{padding:calc(var(--spacing) * 16)!important}.p-0{padding:calc(var(--spacing) * 0)}.p-0\.5{padding:calc(var(--spacing) * .5)}.p-1{padding:calc(var(--spacing) * 1)}.p-1\.5{padding:calc(var(--spacing) * 1.5)}.p-2{padding:calc(var(--spacing) * 2)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.p-5{padding:calc(var(--spacing) * 5)}.p-6{padding:calc(var(--spacing) * 6)}.p-8{padding:calc(var(--spacing) * 8)}.p-10{padding:calc(var(--spacing) * 10)}.p-12{padding:calc(var(--spacing) * 12)}.p-16{padding:calc(var(--spacing) * 16)}.p-20{padding:calc(var(--spacing) * 20)}.p-24{padding:calc(var(--spacing) * 24)}.\!px-0{padding-inline:calc(var(--spacing) * 0)!important}.\!px-1{padding-inline:calc(var(--spacing) * 1)!important}.\!px-2{padding-inline:calc(var(--spacing) * 2)!important}.\!px-3{padding-inline:calc(var(--spacing) * 3)!important}.\!px-4{padding-inline:calc(var(--spacing) * 4)!important}.\!px-5{padding-inline:calc(var(--spacing) * 5)!important}.\!px-6{padding-inline:calc(var(--spacing) * 6)!important}.\!px-8{padding-inline:calc(var(--spacing) * 8)!important}.\!px-10{padding-inline:calc(var(--spacing) * 10)!important}.\!px-12{padding-inline:calc(var(--spacing) * 12)!important}.\!px-16{padding-inline:calc(var(--spacing) * 16)!important}.px-0{padding-inline:calc(var(--spacing) * 0)}.px-0\.5{padding-inline:calc(var(--spacing) * .5)}.px-1{padding-inline:calc(var(--spacing) * 1)}.px-1\.5{padding-inline:calc(var(--spacing) * 1.5)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-2\.5{padding-inline:calc(var(--spacing) * 2.5)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-5{padding-inline:calc(var(--spacing) * 5)}.px-6{padding-inline:calc(var(--spacing) * 6)}.px-8{padding-inline:calc(var(--spacing) * 8)}.px-10{padding-inline:calc(var(--spacing) * 10)}.px-12{padding-inline:calc(var(--spacing) * 12)}.px-16{padding-inline:calc(var(--spacing) * 16)}.\!py-0{padding-block:calc(var(--spacing) * 0)!important}.\!py-1{padding-block:calc(var(--spacing) * 1)!important}.\!py-2{padding-block:calc(var(--spacing) * 2)!important}.\!py-3{padding-block:calc(var(--spacing) * 3)!important}.\!py-4{padding-block:calc(var(--spacing) * 4)!important}.\!py-5{padding-block:calc(var(--spacing) * 5)!important}.\!py-6{padding-block:calc(var(--spacing) * 6)!important}.\!py-8{padding-block:calc(var(--spacing) * 8)!important}.\!py-10{padding-block:calc(var(--spacing) * 10)!important}.\!py-12{padding-block:calc(var(--spacing) * 12)!important}.\!py-16{padding-block:calc(var(--spacing) * 16)!important}.py-0{padding-block:calc(var(--spacing) * 0)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:calc(var(--spacing) * 1)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-2\.5{padding-block:calc(var(--spacing) * 2.5)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-4{padding-block:calc(var(--spacing) * 4)}.py-5{padding-block:calc(var(--spacing) * 5)}.py-6{padding-block:calc(var(--spacing) * 6)}.py-8{padding-block:calc(var(--spacing) * 8)}.py-10{padding-block:calc(var(--spacing) * 10)}.py-12{padding-block:calc(var(--spacing) * 12)}.py-16{padding-block:calc(var(--spacing) * 16)}.pt-0{padding-top:calc(var(--spacing) * 0)}.pt-0\.5{padding-top:calc(var(--spacing) * .5)}.pt-1{padding-top:calc(var(--spacing) * 1)}.pt-2{padding-top:calc(var(--spacing) * 2)}.pt-3{padding-top:calc(var(--spacing) * 3)}.pt-4{padding-top:calc(var(--spacing) * 4)}.pt-5{padding-top:calc(var(--spacing) * 5)}.pt-6{padding-top:calc(var(--spacing) * 6)}.pt-8{padding-top:calc(var(--spacing) * 8)}.pt-10{padding-top:calc(var(--spacing) * 10)}.pt-12{padding-top:calc(var(--spacing) * 12)}.pt-16{padding-top:calc(var(--spacing) * 16)}.pr-0{padding-right:calc(var(--spacing) * 0)}.pr-1{padding-right:calc(var(--spacing) * 1)}.pr-2{padding-right:calc(var(--spacing) * 2)}.pr-3{padding-right:calc(var(--spacing) * 3)}.pr-4{padding-right:calc(var(--spacing) * 4)}.pr-5{padding-right:calc(var(--spacing) * 5)}.pr-6{padding-right:calc(var(--spacing) * 6)}.pr-8{padding-right:calc(var(--spacing) * 8)}.pr-10{padding-right:calc(var(--spacing) * 10)}.pr-12{padding-right:calc(var(--spacing) * 12)}.pr-16{padding-right:calc(var(--spacing) * 16)}.pb-0{padding-bottom:calc(var(--spacing) * 0)}.pb-1{padding-bottom:calc(var(--spacing) * 1)}.pb-2{padding-bottom:calc(var(--spacing) * 2)}.pb-3{padding-bottom:calc(var(--spacing) * 3)}.pb-4{padding-bottom:calc(var(--spacing) * 4)}.pb-5{padding-bottom:calc(var(--spacing) * 5)}.pb-6{padding-bottom:calc(var(--spacing) * 6)}.pb-8{padding-bottom:calc(var(--spacing) * 8)}.pb-10{padding-bottom:calc(var(--spacing) * 10)}.pb-12{padding-bottom:calc(var(--spacing) * 12)}.pb-16{padding-bottom:calc(var(--spacing) * 16)}.pb-24{padding-bottom:calc(var(--spacing) * 24)}.pl-0{padding-left:calc(var(--spacing) * 0)}.pl-1{padding-left:calc(var(--spacing) * 1)}.pl-2{padding-left:calc(var(--spacing) * 2)}.pl-3{padding-left:calc(var(--spacing) * 3)}.pl-4{padding-left:calc(var(--spacing) * 4)}.pl-5{padding-left:calc(var(--spacing) * 5)}.pl-6{padding-left:calc(var(--spacing) * 6)}.pl-7{padding-left:calc(var(--spacing) * 7)}.pl-8{padding-left:calc(var(--spacing) * 8)}.pl-10{padding-left:calc(var(--spacing) * 10)}.pl-12{padding-left:calc(var(--spacing) * 12)}.pl-16{padding-left:calc(var(--spacing) * 16)}.\!text-center{text-align:center!important}.\!text-justify{text-align:justify!important}.\!text-left{text-align:left!important}.\!text-right{text-align:right!important}.text-center{text-align:center}.text-end{text-align:end}.text-justify{text-align:justify}.text-left{text-align:left}.text-right{text-align:right}.text-start{text-align:start}.indent-0{text-indent:calc(var(--spacing) * 0)}.indent-1{text-indent:calc(var(--spacing) * 1)}.indent-2{text-indent:calc(var(--spacing) * 2)}.indent-4{text-indent:calc(var(--spacing) * 4)}.indent-8{text-indent:calc(var(--spacing) * 8)}.align-baseline{vertical-align:baseline}.align-bottom{vertical-align:bottom}.align-middle{vertical-align:middle}.align-sub{vertical-align:sub}.align-super{vertical-align:super}.align-text-bottom{vertical-align:text-bottom}.align-text-top{vertical-align:text-top}.align-top{vertical-align:top}.font-mono{font-family:var(--font-mono)}.font-sans{font-family:var(--font-sans)}.\!text-2xl{font-size:var(--text-2xl)!important;line-height:var(--tw-leading,var(--text-2xl--line-height))!important}.\!text-3xl{font-size:var(--text-3xl)!important;line-height:var(--tw-leading,var(--text-3xl--line-height))!important}.\!text-4xl{font-size:var(--text-4xl)!important;line-height:var(--tw-leading,var(--text-4xl--line-height))!important}.\!text-base{font-size:var(--text-base)!important;line-height:var(--tw-leading,var(--text-base--line-height))!important}.\!text-lg{font-size:var(--text-lg)!important;line-height:var(--tw-leading,var(--text-lg--line-height))!important}.\!text-sm{font-size:var(--text-sm)!important;line-height:var(--tw-leading,var(--text-sm--line-height))!important}.\!text-xl{font-size:var(--text-xl)!important;line-height:var(--tw-leading,var(--text-xl--line-height))!important}.\!text-xs{font-size:var(--text-xs)!important;line-height:var(--tw-leading,var(--text-xs--line-height))!important}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.text-5xl{font-size:var(--text-5xl);line-height:var(--tw-leading,var(--text-5xl--line-height))}.text-6xl{font-size:var(--text-6xl);line-height:var(--tw-leading,var(--text-6xl--line-height))}.text-7xl{font-size:var(--text-7xl);line-height:var(--tw-leading,var(--text-7xl--line-height))}.text-8xl{font-size:var(--text-8xl);line-height:var(--tw-leading,var(--text-8xl--line-height))}.text-9xl{font-size:var(--text-9xl);line-height:var(--tw-leading,var(--text-9xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[2rem\]{font-size:2rem}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[13px\]{font-size:13px}.leading-3{--tw-leading:calc(var(--spacing) * 3);line-height:calc(var(--spacing) * 3)}.leading-4{--tw-leading:calc(var(--spacing) * 4);line-height:calc(var(--spacing) * 4)}.leading-5{--tw-leading:calc(var(--spacing) * 5);line-height:calc(var(--spacing) * 5)}.leading-6{--tw-leading:calc(var(--spacing) * 6);line-height:calc(var(--spacing) * 6)}.leading-7{--tw-leading:calc(var(--spacing) * 7);line-height:calc(var(--spacing) * 7)}.leading-8{--tw-leading:calc(var(--spacing) * 8);line-height:calc(var(--spacing) * 8)}.leading-9{--tw-leading:calc(var(--spacing) * 9);line-height:calc(var(--spacing) * 9)}.leading-10{--tw-leading:calc(var(--spacing) * 10);line-height:calc(var(--spacing) * 10)}.leading-loose{--tw-leading:var(--leading-loose);line-height:var(--leading-loose)}.leading-none{--tw-leading:1;line-height:1}.leading-normal{--tw-leading:var(--leading-normal);line-height:var(--leading-normal)}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.leading-snug{--tw-leading:var(--leading-snug);line-height:var(--leading-snug)}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.\!font-bold{--tw-font-weight:var(--font-weight-bold)!important;font-weight:var(--font-weight-bold)!important}.\!font-medium{--tw-font-weight:var(--font-weight-medium)!important;font-weight:var(--font-weight-medium)!important}.\!font-normal{--tw-font-weight:var(--font-weight-normal)!important;font-weight:var(--font-weight-normal)!important}.\!font-semibold{--tw-font-weight:var(--font-weight-semibold)!important;font-weight:var(--font-weight-semibold)!important}.font-black{--tw-font-weight:var(--font-weight-black);font-weight:var(--font-weight-black)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-extrabold{--tw-font-weight:var(--font-weight-extrabold);font-weight:var(--font-weight-extrabold)}.font-extralight{--tw-font-weight:var(--font-weight-extralight);font-weight:var(--font-weight-extralight)}.font-light{--tw-font-weight:var(--font-weight-light);font-weight:var(--font-weight-light)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.font-thin{--tw-font-weight:var(--font-weight-thin);font-weight:var(--font-weight-thin)}.tracking-normal{--tw-tracking:var(--tracking-normal);letter-spacing:var(--tracking-normal)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.tracking-tighter{--tw-tracking:var(--tracking-tighter);letter-spacing:var(--tracking-tighter)}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.tracking-widest{--tw-tracking:var(--tracking-widest);letter-spacing:var(--tracking-widest)}.break-normal{overflow-wrap:normal;word-break:normal}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.break-keep{word-break:keep-all}.text-clip{text-overflow:clip}.text-ellipsis{text-overflow:ellipsis}.whitespace-break-spaces{white-space:break-spaces}.whitespace-normal{white-space:normal}.whitespace-nowrap{white-space:nowrap}.whitespace-pre{white-space:pre}.whitespace-pre-line{white-space:pre-line}.whitespace-pre-wrap{white-space:pre-wrap}.text-amber-50{color:var(--color-amber-50)}.text-amber-100{color:var(--color-amber-100)}.text-amber-200{color:var(--color-amber-200)}.text-amber-300{color:var(--color-amber-300)}.text-amber-400{color:var(--color-amber-400)}.text-amber-500{color:var(--color-amber-500)}.text-amber-600{color:var(--color-amber-600)}.text-amber-700{color:var(--color-amber-700)}.text-amber-800{color:var(--color-amber-800)}.text-amber-900{color:var(--color-amber-900)}.text-amber-950{color:var(--color-amber-950)}.text-black{color:var(--color-black)}.text-blue-50{color:var(--color-blue-50)}.text-blue-100{color:var(--color-blue-100)}.text-blue-200{color:var(--color-blue-200)}.text-blue-300{color:var(--color-blue-300)}.text-blue-400{color:var(--color-blue-400)}.text-blue-500{color:var(--color-blue-500)}.text-blue-600{color:var(--color-blue-600)}.text-blue-700{color:var(--color-blue-700)}.text-blue-800{color:var(--color-blue-800)}.text-blue-900{color:var(--color-blue-900)}.text-blue-950{color:var(--color-blue-950)}.text-current{color:currentColor}.text-cyan-50{color:var(--color-cyan-50)}.text-cyan-100{color:var(--color-cyan-100)}.text-cyan-200{color:var(--color-cyan-200)}.text-cyan-300{color:var(--color-cyan-300)}.text-cyan-400{color:var(--color-cyan-400)}.text-cyan-500{color:var(--color-cyan-500)}.text-cyan-600{color:var(--color-cyan-600)}.text-cyan-700{color:var(--color-cyan-700)}.text-cyan-800{color:var(--color-cyan-800)}.text-cyan-900{color:var(--color-cyan-900)}.text-cyan-950{color:var(--color-cyan-950)}.text-emerald-50{color:var(--color-emerald-50)}.text-emerald-100{color:var(--color-emerald-100)}.text-emerald-200{color:var(--color-emerald-200)}.text-emerald-300{color:var(--color-emerald-300)}.text-emerald-400{color:var(--color-emerald-400)}.text-emerald-500{color:var(--color-emerald-500)}.text-emerald-600{color:var(--color-emerald-600)}.text-emerald-700{color:var(--color-emerald-700)}.text-emerald-800{color:var(--color-emerald-800)}.text-emerald-900{color:var(--color-emerald-900)}.text-emerald-950{color:var(--color-emerald-950)}.text-fuchsia-50{color:var(--color-fuchsia-50)}.text-fuchsia-100{color:var(--color-fuchsia-100)}.text-fuchsia-200{color:var(--color-fuchsia-200)}.text-fuchsia-300{color:var(--color-fuchsia-300)}.text-fuchsia-400{color:var(--color-fuchsia-400)}.text-fuchsia-500{color:var(--color-fuchsia-500)}.text-fuchsia-600{color:var(--color-fuchsia-600)}.text-fuchsia-700{color:var(--color-fuchsia-700)}.text-fuchsia-800{color:var(--color-fuchsia-800)}.text-fuchsia-900{color:var(--color-fuchsia-900)}.text-fuchsia-950{color:var(--color-fuchsia-950)}.text-gray-50{color:var(--color-gray-50)}.text-gray-100{color:var(--color-gray-100)}.text-gray-200{color:var(--color-gray-200)}.text-gray-300{color:var(--color-gray-300)}.text-gray-400{color:var(--color-gray-400)}.text-gray-500{color:var(--color-gray-500)}.text-gray-600{color:var(--color-gray-600)}.text-gray-700{color:var(--color-gray-700)}.text-gray-800{color:var(--color-gray-800)}.text-gray-900{color:var(--color-gray-900)}.text-gray-950{color:var(--color-gray-950)}.text-green-50{color:var(--color-green-50)}.text-green-100{color:var(--color-green-100)}.text-green-200{color:var(--color-green-200)}.text-green-300{color:var(--color-green-300)}.text-green-400{color:var(--color-green-400)}.text-green-500{color:var(--color-green-500)}.text-green-600{color:var(--color-green-600)}.text-green-700{color:var(--color-green-700)}.text-green-800{color:var(--color-green-800)}.text-green-900{color:var(--color-green-900)}.text-green-950{color:var(--color-green-950)}.text-indigo-50{color:var(--color-indigo-50)}.text-indigo-100{color:var(--color-indigo-100)}.text-indigo-200{color:var(--color-indigo-200)}.text-indigo-300{color:var(--color-indigo-300)}.text-indigo-400{color:var(--color-indigo-400)}.text-indigo-500{color:var(--color-indigo-500)}.text-indigo-600{color:var(--color-indigo-600)}.text-indigo-700{color:var(--color-indigo-700)}.text-indigo-800{color:var(--color-indigo-800)}.text-indigo-900{color:var(--color-indigo-900)}.text-indigo-950{color:var(--color-indigo-950)}.text-inherit{color:inherit}.text-lime-50{color:var(--color-lime-50)}.text-lime-100{color:var(--color-lime-100)}.text-lime-200{color:var(--color-lime-200)}.text-lime-300{color:var(--color-lime-300)}.text-lime-400{color:var(--color-lime-400)}.text-lime-500{color:var(--color-lime-500)}.text-lime-600{color:var(--color-lime-600)}.text-lime-700{color:var(--color-lime-700)}.text-lime-800{color:var(--color-lime-800)}.text-lime-900{color:var(--color-lime-900)}.text-lime-950{color:var(--color-lime-950)}.text-neutral-50{color:var(--color-neutral-50)}.text-neutral-100{color:var(--color-neutral-100)}.text-neutral-200{color:var(--color-neutral-200)}.text-neutral-300{color:var(--color-neutral-300)}.text-neutral-400{color:var(--color-neutral-400)}.text-neutral-500{color:var(--color-neutral-500)}.text-neutral-600{color:var(--color-neutral-600)}.text-neutral-700{color:var(--color-neutral-700)}.text-neutral-800{color:var(--color-neutral-800)}.text-neutral-900{color:var(--color-neutral-900)}.text-neutral-950{color:var(--color-neutral-950)}.text-orange-50{color:var(--color-orange-50)}.text-orange-100{color:var(--color-orange-100)}.text-orange-200{color:var(--color-orange-200)}.text-orange-300{color:var(--color-orange-300)}.text-orange-400{color:var(--color-orange-400)}.text-orange-500{color:var(--color-orange-500)}.text-orange-600{color:var(--color-orange-600)}.text-orange-700{color:var(--color-orange-700)}.text-orange-800{color:var(--color-orange-800)}.text-orange-900{color:var(--color-orange-900)}.text-orange-950{color:var(--color-orange-950)}.text-pink-50{color:var(--color-pink-50)}.text-pink-100{color:var(--color-pink-100)}.text-pink-200{color:var(--color-pink-200)}.text-pink-300{color:var(--color-pink-300)}.text-pink-400{color:var(--color-pink-400)}.text-pink-500{color:var(--color-pink-500)}.text-pink-600{color:var(--color-pink-600)}.text-pink-700{color:var(--color-pink-700)}.text-pink-800{color:var(--color-pink-800)}.text-pink-900{color:var(--color-pink-900)}.text-pink-950{color:var(--color-pink-950)}.text-purple-50{color:var(--color-purple-50)}.text-purple-100{color:var(--color-purple-100)}.text-purple-200{color:var(--color-purple-200)}.text-purple-300{color:var(--color-purple-300)}.text-purple-400{color:var(--color-purple-400)}.text-purple-500{color:var(--color-purple-500)}.text-purple-600{color:var(--color-purple-600)}.text-purple-700{color:var(--color-purple-700)}.text-purple-800{color:var(--color-purple-800)}.text-purple-900{color:var(--color-purple-900)}.text-purple-950{color:var(--color-purple-950)}.text-red-50{color:var(--color-red-50)}.text-red-100{color:var(--color-red-100)}.text-red-200{color:var(--color-red-200)}.text-red-300{color:var(--color-red-300)}.text-red-400{color:var(--color-red-400)}.text-red-500{color:var(--color-red-500)}.text-red-600{color:var(--color-red-600)}.text-red-700{color:var(--color-red-700)}.text-red-800{color:var(--color-red-800)}.text-red-900{color:var(--color-red-900)}.text-red-950{color:var(--color-red-950)}.text-rose-50{color:var(--color-rose-50)}.text-rose-100{color:var(--color-rose-100)}.text-rose-200{color:var(--color-rose-200)}.text-rose-300{color:var(--color-rose-300)}.text-rose-400{color:var(--color-rose-400)}.text-rose-500{color:var(--color-rose-500)}.text-rose-600{color:var(--color-rose-600)}.text-rose-700{color:var(--color-rose-700)}.text-rose-800{color:var(--color-rose-800)}.text-rose-900{color:var(--color-rose-900)}.text-rose-950{color:var(--color-rose-950)}.text-sky-50{color:var(--color-sky-50)}.text-sky-100{color:var(--color-sky-100)}.text-sky-200{color:var(--color-sky-200)}.text-sky-300{color:var(--color-sky-300)}.text-sky-400{color:var(--color-sky-400)}.text-sky-500{color:var(--color-sky-500)}.text-sky-600{color:var(--color-sky-600)}.text-sky-700{color:var(--color-sky-700)}.text-sky-800{color:var(--color-sky-800)}.text-sky-900{color:var(--color-sky-900)}.text-sky-950{color:var(--color-sky-950)}.text-slate-50{color:var(--color-slate-50)}.text-slate-100{color:var(--color-slate-100)}.text-slate-200{color:var(--color-slate-200)}.text-slate-300{color:var(--color-slate-300)}.text-slate-400{color:var(--color-slate-400)}.text-slate-500{color:var(--color-slate-500)}.text-slate-600{color:var(--color-slate-600)}.text-slate-700{color:var(--color-slate-700)}.text-slate-800{color:var(--color-slate-800)}.text-slate-900{color:var(--color-slate-900)}.text-slate-950{color:var(--color-slate-950)}.text-stone-50{color:var(--color-stone-50)}.text-stone-100{color:var(--color-stone-100)}.text-stone-200{color:var(--color-stone-200)}.text-stone-300{color:var(--color-stone-300)}.text-stone-400{color:var(--color-stone-400)}.text-stone-500{color:var(--color-stone-500)}.text-stone-600{color:var(--color-stone-600)}.text-stone-700{color:var(--color-stone-700)}.text-stone-800{color:var(--color-stone-800)}.text-stone-900{color:var(--color-stone-900)}.text-stone-950{color:var(--color-stone-950)}.text-teal-50{color:var(--color-teal-50)}.text-teal-100{color:var(--color-teal-100)}.text-teal-200{color:var(--color-teal-200)}.text-teal-300{color:var(--color-teal-300)}.text-teal-400{color:var(--color-teal-400)}.text-teal-500{color:var(--color-teal-500)}.text-teal-600{color:var(--color-teal-600)}.text-teal-700{color:var(--color-teal-700)}.text-teal-800{color:var(--color-teal-800)}.text-teal-900{color:var(--color-teal-900)}.text-teal-950{color:var(--color-teal-950)}.text-transparent{color:#0000}.text-violet-50{color:var(--color-violet-50)}.text-violet-100{color:var(--color-violet-100)}.text-violet-200{color:var(--color-violet-200)}.text-violet-300{color:var(--color-violet-300)}.text-violet-400{color:var(--color-violet-400)}.text-violet-500{color:var(--color-violet-500)}.text-violet-600{color:var(--color-violet-600)}.text-violet-700{color:var(--color-violet-700)}.text-violet-800{color:var(--color-violet-800)}.text-violet-900{color:var(--color-violet-900)}.text-violet-950{color:var(--color-violet-950)}.text-white{color:var(--color-white)}.text-yellow-50{color:var(--color-yellow-50)}.text-yellow-100{color:var(--color-yellow-100)}.text-yellow-200{color:var(--color-yellow-200)}.text-yellow-300{color:var(--color-yellow-300)}.text-yellow-400{color:var(--color-yellow-400)}.text-yellow-500{color:var(--color-yellow-500)}.text-yellow-600{color:var(--color-yellow-600)}.text-yellow-700{color:var(--color-yellow-700)}.text-yellow-800{color:var(--color-yellow-800)}.text-yellow-900{color:var(--color-yellow-900)}.text-yellow-950{color:var(--color-yellow-950)}.text-zinc-50{color:var(--color-zinc-50)}.text-zinc-100{color:var(--color-zinc-100)}.text-zinc-200{color:var(--color-zinc-200)}.text-zinc-300{color:var(--color-zinc-300)}.text-zinc-400{color:var(--color-zinc-400)}.text-zinc-500{color:var(--color-zinc-500)}.text-zinc-600{color:var(--color-zinc-600)}.text-zinc-700{color:var(--color-zinc-700)}.text-zinc-800{color:var(--color-zinc-800)}.text-zinc-900{color:var(--color-zinc-900)}.text-zinc-950{color:var(--color-zinc-950)}.capitalize{text-transform:capitalize}.lowercase{text-transform:lowercase}.normal-case{text-transform:none}.uppercase{text-transform:uppercase}.italic{font-style:italic}.not-italic{font-style:normal}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}.line-through{text-decoration-line:line-through}.no-underline{text-decoration-line:none}.overline{text-decoration-line:overline}.underline{text-decoration-line:underline}.placeholder-gray-400::placeholder{color:var(--color-gray-400)}.accent-blue-500{accent-color:var(--color-blue-500)}.opacity-0{opacity:0}.opacity-5{opacity:.05}.opacity-10{opacity:.1}.opacity-15{opacity:.15}.opacity-20{opacity:.2}.opacity-25{opacity:.25}.opacity-30{opacity:.3}.opacity-35{opacity:.35}.opacity-40{opacity:.4}.opacity-45{opacity:.45}.opacity-50{opacity:.5}.opacity-55{opacity:.55}.opacity-60{opacity:.6}.opacity-65{opacity:.65}.opacity-70{opacity:.7}.opacity-75{opacity:.75}.opacity-80{opacity:.8}.opacity-85{opacity:.85}.opacity-90{opacity:.9}.opacity-95{opacity:.95}.opacity-100{opacity:1}.bg-blend-color{background-blend-mode:color}.bg-blend-color-burn{background-blend-mode:color-burn}.bg-blend-color-dodge{background-blend-mode:color-dodge}.bg-blend-darken{background-blend-mode:darken}.bg-blend-difference{background-blend-mode:difference}.bg-blend-exclusion{background-blend-mode:exclusion}.bg-blend-hard-light{background-blend-mode:hard-light}.bg-blend-hue{background-blend-mode:hue}.bg-blend-lighten{background-blend-mode:lighten}.bg-blend-luminosity{background-blend-mode:luminosity}.bg-blend-multiply{background-blend-mode:multiply}.bg-blend-normal{background-blend-mode:normal}.bg-blend-overlay{background-blend-mode:overlay}.bg-blend-saturation{background-blend-mode:saturation}.bg-blend-screen{background-blend-mode:screen}.bg-blend-soft-light{background-blend-mode:soft-light}.mix-blend-color{mix-blend-mode:color}.mix-blend-color-burn{mix-blend-mode:color-burn}.mix-blend-color-dodge{mix-blend-mode:color-dodge}.mix-blend-darken{mix-blend-mode:darken}.mix-blend-difference{mix-blend-mode:difference}.mix-blend-exclusion{mix-blend-mode:exclusion}.mix-blend-hard-light{mix-blend-mode:hard-light}.mix-blend-hue{mix-blend-mode:hue}.mix-blend-lighten{mix-blend-mode:lighten}.mix-blend-luminosity{mix-blend-mode:luminosity}.mix-blend-multiply{mix-blend-mode:multiply}.mix-blend-normal{mix-blend-mode:normal}.mix-blend-overlay{mix-blend-mode:overlay}.mix-blend-plus-darker{mix-blend-mode:plus-darker}.mix-blend-plus-lighter{mix-blend-mode:plus-lighter}.mix-blend-saturation{mix-blend-mode:saturation}.mix-blend-screen{mix-blend-mode:screen}.mix-blend-soft-light{mix-blend-mode:soft-light}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-2xl{--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-inner{--tw-shadow:inset 0 2px 4px 0 var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-md{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a), 0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-none{--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a), 0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-0{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-1{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-2{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-4{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-8{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(8px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-purple-200{--tw-shadow-color:oklch(90.2% .063 306.703)}@supports (color:color-mix(in lab,red,red)){.shadow-purple-200{--tw-shadow-color:color-mix(in oklab, var(--color-purple-200) var(--tw-shadow-alpha), transparent)}}.ring-black{--tw-ring-color:var(--color-black)}.ring-blue-50{--tw-ring-color:var(--color-blue-50)}.ring-blue-100{--tw-ring-color:var(--color-blue-100)}.ring-blue-200{--tw-ring-color:var(--color-blue-200)}.ring-blue-300{--tw-ring-color:var(--color-blue-300)}.ring-blue-400{--tw-ring-color:var(--color-blue-400)}.ring-blue-500{--tw-ring-color:var(--color-blue-500)}.ring-blue-600{--tw-ring-color:var(--color-blue-600)}.ring-blue-700{--tw-ring-color:var(--color-blue-700)}.ring-blue-800{--tw-ring-color:var(--color-blue-800)}.ring-blue-900{--tw-ring-color:var(--color-blue-900)}.ring-current{--tw-ring-color:currentcolor}.ring-gray-50{--tw-ring-color:var(--color-gray-50)}.ring-gray-100{--tw-ring-color:var(--color-gray-100)}.ring-gray-200{--tw-ring-color:var(--color-gray-200)}.ring-gray-300{--tw-ring-color:var(--color-gray-300)}.ring-gray-400{--tw-ring-color:var(--color-gray-400)}.ring-gray-500{--tw-ring-color:var(--color-gray-500)}.ring-gray-600{--tw-ring-color:var(--color-gray-600)}.ring-gray-700{--tw-ring-color:var(--color-gray-700)}.ring-gray-800{--tw-ring-color:var(--color-gray-800)}.ring-gray-900{--tw-ring-color:var(--color-gray-900)}.ring-inherit{--tw-ring-color:inherit}.ring-slate-50{--tw-ring-color:var(--color-slate-50)}.ring-slate-100{--tw-ring-color:var(--color-slate-100)}.ring-slate-200{--tw-ring-color:var(--color-slate-200)}.ring-slate-300{--tw-ring-color:var(--color-slate-300)}.ring-slate-400{--tw-ring-color:var(--color-slate-400)}.ring-slate-500{--tw-ring-color:var(--color-slate-500)}.ring-slate-600{--tw-ring-color:var(--color-slate-600)}.ring-slate-700{--tw-ring-color:var(--color-slate-700)}.ring-slate-800{--tw-ring-color:var(--color-slate-800)}.ring-slate-900{--tw-ring-color:var(--color-slate-900)}.ring-transparent{--tw-ring-color:transparent}.ring-white{--tw-ring-color:var(--color-white)}.ring-offset-0{--tw-ring-offset-width:0px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.ring-offset-1{--tw-ring-offset-width:1px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.ring-offset-2{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.ring-offset-4{--tw-ring-offset-width:4px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.ring-offset-8{--tw-ring-offset-width:8px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.ring-offset-black{--tw-ring-offset-color:var(--color-black)}.ring-offset-current{--tw-ring-offset-color:currentcolor}.ring-offset-inherit{--tw-ring-offset-color:inherit}.ring-offset-transparent{--tw-ring-offset-color:transparent}.ring-offset-white{--tw-ring-offset-color:var(--color-white)}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.outline-0{outline-style:var(--tw-outline-style);outline-width:0}.outline-1{outline-style:var(--tw-outline-style);outline-width:1px}.outline-2{outline-style:var(--tw-outline-style);outline-width:2px}.outline-4{outline-style:var(--tw-outline-style);outline-width:4px}.outline-8{outline-style:var(--tw-outline-style);outline-width:8px}.outline-offset-0{outline-offset:0px}.outline-offset-1{outline-offset:1px}.outline-offset-2{outline-offset:2px}.outline-offset-4{outline-offset:4px}.outline-offset-8{outline-offset:8px}.outline-black{outline-color:var(--color-black)}.outline-current{outline-color:currentColor}.outline-inherit{outline-color:inherit}.outline-transparent{outline-color:#0000}.outline-white{outline-color:var(--color-white)}.blur{--tw-blur:blur(8px);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.blur-2xl{--tw-blur:blur(var(--blur-2xl));filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.blur-3xl{--tw-blur:blur(var(--blur-3xl));filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.blur-lg{--tw-blur:blur(var(--blur-lg));filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.blur-md{--tw-blur:blur(var(--blur-md));filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.blur-none{--tw-blur: ;filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.blur-sm{--tw-blur:blur(var(--blur-sm));filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.blur-xl{--tw-blur:blur(var(--blur-xl));filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.brightness-0{--tw-brightness:brightness(0%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.brightness-50{--tw-brightness:brightness(50%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.brightness-75{--tw-brightness:brightness(75%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.brightness-90{--tw-brightness:brightness(90%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.brightness-95{--tw-brightness:brightness(95%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.brightness-100{--tw-brightness:brightness(100%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.brightness-105{--tw-brightness:brightness(105%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.brightness-110{--tw-brightness:brightness(110%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.brightness-125{--tw-brightness:brightness(125%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.brightness-150{--tw-brightness:brightness(150%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.brightness-200{--tw-brightness:brightness(200%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.contrast-0{--tw-contrast:contrast(0%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.contrast-50{--tw-contrast:contrast(50%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.contrast-75{--tw-contrast:contrast(75%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.contrast-100{--tw-contrast:contrast(100%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.contrast-125{--tw-contrast:contrast(125%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.contrast-150{--tw-contrast:contrast(150%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.contrast-200{--tw-contrast:contrast(200%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.drop-shadow{--tw-drop-shadow-size:drop-shadow(0 1px 2px var(--tw-drop-shadow-color,#0000001a)) drop-shadow(0 1px 1px var(--tw-drop-shadow-color,#0000000f));--tw-drop-shadow:drop-shadow(0 1px 2px #0000001a) drop-shadow(0 1px 1px #0000000f);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.drop-shadow-2xl{--tw-drop-shadow-size:drop-shadow(0 25px 25px var(--tw-drop-shadow-color,#00000026));--tw-drop-shadow:drop-shadow(var(--drop-shadow-2xl));filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.drop-shadow-lg{--tw-drop-shadow-size:drop-shadow(0 4px 4px var(--tw-drop-shadow-color,#00000026));--tw-drop-shadow:drop-shadow(var(--drop-shadow-lg));filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.drop-shadow-md{--tw-drop-shadow-size:drop-shadow(0 3px 3px var(--tw-drop-shadow-color,#0000001f));--tw-drop-shadow:drop-shadow(var(--drop-shadow-md));filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.drop-shadow-sm{--tw-drop-shadow-size:drop-shadow(0 1px 2px var(--tw-drop-shadow-color,#00000026));--tw-drop-shadow:drop-shadow(var(--drop-shadow-sm));filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.drop-shadow-xl{--tw-drop-shadow-size:drop-shadow(0 9px 7px var(--tw-drop-shadow-color,#0000001a));--tw-drop-shadow:drop-shadow(var(--drop-shadow-xl));filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.drop-shadow-none{--tw-drop-shadow: ;filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.grayscale{--tw-grayscale:grayscale(100%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.grayscale-0{--tw-grayscale:grayscale(0%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.-hue-rotate-15{--tw-hue-rotate:hue-rotate( -15deg );filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.-hue-rotate-30{--tw-hue-rotate:hue-rotate( -30deg );filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.-hue-rotate-60{--tw-hue-rotate:hue-rotate( -60deg );filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.-hue-rotate-90{--tw-hue-rotate:hue-rotate( -90deg );filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.-hue-rotate-180{--tw-hue-rotate:hue-rotate( -180deg );filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.hue-rotate-0{--tw-hue-rotate:hue-rotate(0deg);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.hue-rotate-15{--tw-hue-rotate:hue-rotate(15deg);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.hue-rotate-30{--tw-hue-rotate:hue-rotate(30deg);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.hue-rotate-60{--tw-hue-rotate:hue-rotate(60deg);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.hue-rotate-90{--tw-hue-rotate:hue-rotate(90deg);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.hue-rotate-180{--tw-hue-rotate:hue-rotate(180deg);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.invert{--tw-invert:invert(100%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.invert-0{--tw-invert:invert(0%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.saturate-0{--tw-saturate:saturate(0%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.saturate-50{--tw-saturate:saturate(50%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.saturate-100{--tw-saturate:saturate(100%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.saturate-150{--tw-saturate:saturate(150%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.saturate-200{--tw-saturate:saturate(200%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.sepia{--tw-sepia:sepia(100%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.sepia-0{--tw-sepia:sepia(0%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.backdrop-blur{--tw-backdrop-blur:blur(8px);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-blur-2xl{--tw-backdrop-blur:blur(var(--blur-2xl));-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-blur-3xl{--tw-backdrop-blur:blur(var(--blur-3xl));-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-blur-lg{--tw-backdrop-blur:blur(var(--blur-lg));-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-blur-md{--tw-backdrop-blur:blur(var(--blur-md));-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-blur-none{--tw-backdrop-blur: ;-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-blur-sm{--tw-backdrop-blur:blur(var(--blur-sm));-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-blur-xl{--tw-backdrop-blur:blur(var(--blur-xl));-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-brightness-0{--tw-backdrop-brightness:brightness(0%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-brightness-50{--tw-backdrop-brightness:brightness(50%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-brightness-75{--tw-backdrop-brightness:brightness(75%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-brightness-90{--tw-backdrop-brightness:brightness(90%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-brightness-95{--tw-backdrop-brightness:brightness(95%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-brightness-100{--tw-backdrop-brightness:brightness(100%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-brightness-105{--tw-backdrop-brightness:brightness(105%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-brightness-110{--tw-backdrop-brightness:brightness(110%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-brightness-125{--tw-backdrop-brightness:brightness(125%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-brightness-150{--tw-backdrop-brightness:brightness(150%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-brightness-200{--tw-backdrop-brightness:brightness(200%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-contrast-0{--tw-backdrop-contrast:contrast(0%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-contrast-50{--tw-backdrop-contrast:contrast(50%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-contrast-75{--tw-backdrop-contrast:contrast(75%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-contrast-100{--tw-backdrop-contrast:contrast(100%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-contrast-125{--tw-backdrop-contrast:contrast(125%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-contrast-150{--tw-backdrop-contrast:contrast(150%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-contrast-200{--tw-backdrop-contrast:contrast(200%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-grayscale{--tw-backdrop-grayscale:grayscale(100%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-grayscale-0{--tw-backdrop-grayscale:grayscale(0%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-hue-rotate-0{--tw-backdrop-hue-rotate:hue-rotate(0deg);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-hue-rotate-15{--tw-backdrop-hue-rotate:hue-rotate(15deg);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-hue-rotate-30{--tw-backdrop-hue-rotate:hue-rotate(30deg);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-hue-rotate-60{--tw-backdrop-hue-rotate:hue-rotate(60deg);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-hue-rotate-90{--tw-backdrop-hue-rotate:hue-rotate(90deg);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-hue-rotate-180{--tw-backdrop-hue-rotate:hue-rotate(180deg);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-invert{--tw-backdrop-invert:invert(100%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-invert-0{--tw-backdrop-invert:invert(0%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-opacity-0{--tw-backdrop-opacity:opacity(0%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-opacity-5{--tw-backdrop-opacity:opacity(5%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-opacity-10{--tw-backdrop-opacity:opacity(10%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-opacity-20{--tw-backdrop-opacity:opacity(20%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-opacity-25{--tw-backdrop-opacity:opacity(25%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-opacity-30{--tw-backdrop-opacity:opacity(30%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-opacity-40{--tw-backdrop-opacity:opacity(40%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-opacity-50{--tw-backdrop-opacity:opacity(50%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-opacity-60{--tw-backdrop-opacity:opacity(60%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-opacity-70{--tw-backdrop-opacity:opacity(70%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-opacity-75{--tw-backdrop-opacity:opacity(75%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-opacity-80{--tw-backdrop-opacity:opacity(80%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-opacity-90{--tw-backdrop-opacity:opacity(90%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-opacity-95{--tw-backdrop-opacity:opacity(95%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-opacity-100{--tw-backdrop-opacity:opacity(100%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-saturate-0{--tw-backdrop-saturate:saturate(0%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-saturate-50{--tw-backdrop-saturate:saturate(50%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-saturate-100{--tw-backdrop-saturate:saturate(100%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-saturate-150{--tw-backdrop-saturate:saturate(150%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-saturate-200{--tw-backdrop-saturate:saturate(200%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-sepia{--tw-backdrop-sepia:sepia(100%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-sepia-0{--tw-backdrop-sepia:sepia(0%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-shadow{transition-property:box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-none{transition-property:none}.delay-75{transition-delay:75ms}.delay-100{transition-delay:.1s}.delay-150{transition-delay:.15s}.delay-200{transition-delay:.2s}.delay-300{transition-delay:.3s}.delay-500{transition-delay:.5s}.delay-700{transition-delay:.7s}.delay-1000{transition-delay:1s}.duration-75{--tw-duration:75ms;transition-duration:75ms}.duration-100{--tw-duration:.1s;transition-duration:.1s}.duration-150{--tw-duration:.15s;transition-duration:.15s}.duration-200{--tw-duration:.2s;transition-duration:.2s}.duration-300{--tw-duration:.3s;transition-duration:.3s}.duration-500{--tw-duration:.5s;transition-duration:.5s}.duration-700{--tw-duration:.7s;transition-duration:.7s}.duration-1000{--tw-duration:1s;transition-duration:1s}.ease-in{--tw-ease:var(--ease-in);transition-timing-function:var(--ease-in)}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}.ease-linear{--tw-ease:linear;transition-timing-function:linear}.ease-out{--tw-ease:var(--ease-out);transition-timing-function:var(--ease-out)}.outline-dashed{--tw-outline-style:dashed;outline-style:dashed}.outline-dotted{--tw-outline-style:dotted;outline-style:dotted}.outline-double{--tw-outline-style:double;outline-style:double}.outline-none{--tw-outline-style:none;outline-style:none}.select-all{-webkit-user-select:all;user-select:all}.select-auto{-webkit-user-select:auto;user-select:auto}.select-none{-webkit-user-select:none;user-select:none}.select-text{-webkit-user-select:text;user-select:text}:where(.divide-x-reverse>:not(:last-child)){--tw-divide-x-reverse:1}.ring-inset{--tw-ring-inset:inset}@media(hover:hover){.group-hover\:visible:is(:where(.group):hover *){visibility:visible}.group-hover\:translate-x-1:is(:where(.group):hover *){--tw-translate-x:calc(var(--spacing) * 1);translate:var(--tw-translate-x) var(--tw-translate-y)}.group-hover\:translate-y-1:is(:where(.group):hover *){--tw-translate-y:calc(var(--spacing) * 1);translate:var(--tw-translate-x) var(--tw-translate-y)}.group-hover\:scale-100:is(:where(.group):hover *){--tw-scale-x:100%;--tw-scale-y:100%;--tw-scale-z:100%;scale:var(--tw-scale-x) var(--tw-scale-y)}.group-hover\:scale-105:is(:where(.group):hover *){--tw-scale-x:105%;--tw-scale-y:105%;--tw-scale-z:105%;scale:var(--tw-scale-x) var(--tw-scale-y)}.group-hover\:bg-black\/40:is(:where(.group):hover *){background-color:#0006}@supports (color:color-mix(in lab,red,red)){.group-hover\:bg-black\/40:is(:where(.group):hover *){background-color:color-mix(in oklab,var(--color-black) 40%,transparent)}}.group-hover\:bg-blue-100:is(:where(.group):hover *){background-color:var(--color-blue-100)}.group-hover\:bg-blue-500:is(:where(.group):hover *){background-color:var(--color-blue-500)}.group-hover\:bg-blue-600:is(:where(.group):hover *){background-color:var(--color-blue-600)}.group-hover\:bg-gray-100:is(:where(.group):hover *){background-color:var(--color-gray-100)}.group-hover\:bg-gray-200:is(:where(.group):hover *){background-color:var(--color-gray-200)}.group-hover\:bg-gray-700:is(:where(.group):hover *){background-color:var(--color-gray-700)}.group-hover\:bg-gray-800:is(:where(.group):hover *){background-color:var(--color-gray-800)}.group-hover\:text-black:is(:where(.group):hover *){color:var(--color-black)}.group-hover\:text-blue-500:is(:where(.group):hover *){color:var(--color-blue-500)}.group-hover\:text-blue-600:is(:where(.group):hover *){color:var(--color-blue-600)}.group-hover\:text-blue-700:is(:where(.group):hover *){color:var(--color-blue-700)}.group-hover\:text-gray-700:is(:where(.group):hover *){color:var(--color-gray-700)}.group-hover\:text-gray-800:is(:where(.group):hover *){color:var(--color-gray-800)}.group-hover\:text-gray-900:is(:where(.group):hover *){color:var(--color-gray-900)}.group-hover\:text-white:is(:where(.group):hover *){color:var(--color-white)}.group-hover\:opacity-100:is(:where(.group):hover *){opacity:1}}.group-focus\:border-blue-500:is(:where(.group):focus *){border-color:var(--color-blue-500)}.group-focus\:ring-2:is(:where(.group):focus *){--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.peer-checked\:border-blue-500:is(:where(.peer):checked~*){border-color:var(--color-blue-500)}.peer-checked\:bg-blue-500:is(:where(.peer):checked~*){background-color:var(--color-blue-500)}.peer-invalid\:border-red-500:is(:where(.peer):invalid~*){border-color:var(--color-red-500)}.peer-invalid\:text-red-500:is(:where(.peer):invalid~*){color:var(--color-red-500)}.peer-focus\:border-blue-500:is(:where(.peer):focus~*){border-color:var(--color-blue-500)}.peer-focus\:ring-2:is(:where(.peer):focus~*){--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.peer-disabled\:cursor-not-allowed:is(:where(.peer):disabled~*){cursor:not-allowed}.peer-disabled\:opacity-50:is(:where(.peer):disabled~*){opacity:.5}.first-letter\:text-2xl:first-letter{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.first-letter\:font-bold:first-letter{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.first-letter\:text-blue-600:first-letter{color:var(--color-blue-600)}.first-line\:font-semibold:first-line{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.first-line\:text-gray-700:first-line{color:var(--color-gray-700)}.marker\:text-blue-500 ::marker{color:var(--color-blue-500)}.marker\:text-blue-500::marker{color:var(--color-blue-500)}.marker\:text-blue-500 ::-webkit-details-marker{color:var(--color-blue-500)}.marker\:text-blue-500::-webkit-details-marker{color:var(--color-blue-500)}.marker\:text-gray-500 ::marker{color:var(--color-gray-500)}.marker\:text-gray-500::marker{color:var(--color-gray-500)}.marker\:text-gray-500 ::-webkit-details-marker{color:var(--color-gray-500)}.marker\:text-gray-500::-webkit-details-marker{color:var(--color-gray-500)}.selection\:bg-blue-200 ::selection{background-color:var(--color-blue-200)}.selection\:bg-blue-200::selection{background-color:var(--color-blue-200)}.selection\:text-blue-900 ::selection{color:var(--color-blue-900)}.selection\:text-blue-900::selection{color:var(--color-blue-900)}.file\:mr-4::file-selector-button{margin-right:calc(var(--spacing) * 4)}.file\:cursor-pointer::file-selector-button{cursor:pointer}.file\:rounded-full::file-selector-button{border-radius:3.40282e38px}.file\:border-0::file-selector-button{border-style:var(--tw-border-style);border-width:0}.file\:bg-blue-50::file-selector-button{background-color:var(--color-blue-50)}.file\:px-4::file-selector-button{padding-inline:calc(var(--spacing) * 4)}.file\:py-2::file-selector-button{padding-block:calc(var(--spacing) * 2)}.file\:text-sm::file-selector-button{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.file\:font-semibold::file-selector-button{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.file\:text-blue-700::file-selector-button{color:var(--color-blue-700)}.before\:absolute:before{content:var(--tw-content);position:absolute}.before\:inset-0:before{content:var(--tw-content);inset:calc(var(--spacing) * 0)}.before\:block:before{content:var(--tw-content);display:block}.after\:absolute:after{content:var(--tw-content);position:absolute}.after\:inset-0:after{content:var(--tw-content);inset:calc(var(--spacing) * 0)}.after\:clear-both:after{content:var(--tw-content);clear:both}.after\:block:after{content:var(--tw-content);display:block}.after\:h-4:after{content:var(--tw-content);height:calc(var(--spacing) * 4)}.after\:h-5:after{content:var(--tw-content);height:calc(var(--spacing) * 5)}.after\:h-6:after{content:var(--tw-content);height:calc(var(--spacing) * 6)}.after\:w-4:after{content:var(--tw-content);width:calc(var(--spacing) * 4)}.after\:w-5:after{content:var(--tw-content);width:calc(var(--spacing) * 5)}.after\:w-6:after{content:var(--tw-content);width:calc(var(--spacing) * 6)}.after\:content-\[\'\'\]:after{--tw-content:"";content:var(--tw-content)}.first\:mt-0:first-child{margin-top:calc(var(--spacing) * 0)}.first\:rounded-t-lg:first-child{border-top-left-radius:var(--radius-lg);border-top-right-radius:var(--radius-lg)}.first\:rounded-l:first-child{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.first\:rounded-l-lg:first-child{border-top-left-radius:var(--radius-lg);border-bottom-left-radius:var(--radius-lg)}.first\:border-t-0:first-child{border-top-style:var(--tw-border-style);border-top-width:0}.first\:pt-0:first-child{padding-top:calc(var(--spacing) * 0)}.last\:mb-0:last-child{margin-bottom:calc(var(--spacing) * 0)}.last\:rounded-r:last-child{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.last\:rounded-r-lg:last-child{border-top-right-radius:var(--radius-lg);border-bottom-right-radius:var(--radius-lg)}.last\:rounded-b-lg:last-child{border-bottom-right-radius:var(--radius-lg);border-bottom-left-radius:var(--radius-lg)}.last\:border-r:last-child{border-right-style:var(--tw-border-style);border-right-width:1px}.last\:border-b-0:last-child{border-bottom-style:var(--tw-border-style);border-bottom-width:0}.last\:pb-0:last-child{padding-bottom:calc(var(--spacing) * 0)}.odd\:bg-gray-50:nth-child(odd){background-color:var(--color-gray-50)}.odd\:bg-gray-100:nth-child(odd){background-color:var(--color-gray-100)}.odd\:bg-white:nth-child(odd){background-color:var(--color-white)}.even\:bg-gray-50:nth-child(2n){background-color:var(--color-gray-50)}.even\:bg-gray-100:nth-child(2n){background-color:var(--color-gray-100)}.even\:bg-white:nth-child(2n){background-color:var(--color-white)}.checked\:border-blue-500:checked{border-color:var(--color-blue-500)}.checked\:border-blue-600:checked{border-color:var(--color-blue-600)}.checked\:bg-blue-500:checked{background-color:var(--color-blue-500)}.checked\:bg-blue-600:checked{background-color:var(--color-blue-600)}.checked\:bg-green-500:checked{background-color:var(--color-green-500)}.checked\:text-white:checked{color:var(--color-white)}.indeterminate\:border-gray-400:indeterminate{border-color:var(--color-gray-400)}.indeterminate\:bg-gray-300:indeterminate{background-color:var(--color-gray-300)}.placeholder-shown\:border-gray-300:placeholder-shown{border-color:var(--color-gray-300)}.autofill\:bg-yellow-100:autofill{background-color:var(--color-yellow-100)}.autofill\:text-black:autofill{color:var(--color-black)}.valid\:border-green-500:valid{border-color:var(--color-green-500)}.valid\:text-green-600:valid{color:var(--color-green-600)}.valid\:ring-green-500:valid{--tw-ring-color:var(--color-green-500)}.invalid\:border-red-500:invalid{border-color:var(--color-red-500)}.invalid\:text-red-600:invalid{color:var(--color-red-600)}.invalid\:ring-red-500:invalid{--tw-ring-color:var(--color-red-500)}.read-only\:cursor-not-allowed:read-only{cursor:not-allowed}.read-only\:bg-gray-50:read-only{background-color:var(--color-gray-50)}.read-only\:bg-gray-100:read-only{background-color:var(--color-gray-100)}.empty\:hidden:empty{display:none}.focus-within\:border-blue-500:focus-within{border-color:var(--color-blue-500)}.focus-within\:border-gray-500:focus-within{border-color:var(--color-gray-500)}.focus-within\:ring-2:focus-within{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus-within\:ring-blue-500:focus-within{--tw-ring-color:var(--color-blue-500)}.focus-within\:ring-gray-500:focus-within{--tw-ring-color:var(--color-gray-500)}@media(hover:hover){.hover\:-translate-y-0\.5:hover{--tw-translate-y:calc(var(--spacing) * -.5);translate:var(--tw-translate-x) var(--tw-translate-y)}.hover\:scale-95:hover{--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x) var(--tw-scale-y)}.hover\:scale-100:hover{--tw-scale-x:100%;--tw-scale-y:100%;--tw-scale-z:100%;scale:var(--tw-scale-x) var(--tw-scale-y)}.hover\:scale-105:hover{--tw-scale-x:105%;--tw-scale-y:105%;--tw-scale-z:105%;scale:var(--tw-scale-x) var(--tw-scale-y)}.hover\:scale-110:hover{--tw-scale-x:110%;--tw-scale-y:110%;--tw-scale-z:110%;scale:var(--tw-scale-x) var(--tw-scale-y)}.hover\:border-blue-50:hover{border-color:var(--color-blue-50)}.hover\:border-blue-100:hover{border-color:var(--color-blue-100)}.hover\:border-blue-200:hover{border-color:var(--color-blue-200)}.hover\:border-blue-300:hover{border-color:var(--color-blue-300)}.hover\:border-blue-400:hover{border-color:var(--color-blue-400)}.hover\:border-blue-500:hover{border-color:var(--color-blue-500)}.hover\:border-blue-600:hover{border-color:var(--color-blue-600)}.hover\:border-blue-700:hover{border-color:var(--color-blue-700)}.hover\:border-blue-800:hover{border-color:var(--color-blue-800)}.hover\:border-blue-900:hover{border-color:var(--color-blue-900)}.hover\:border-gray-50:hover{border-color:var(--color-gray-50)}.hover\:border-gray-100:hover{border-color:var(--color-gray-100)}.hover\:border-gray-200:hover{border-color:var(--color-gray-200)}.hover\:border-gray-300:hover{border-color:var(--color-gray-300)}.hover\:border-gray-400:hover{border-color:var(--color-gray-400)}.hover\:border-gray-500:hover{border-color:var(--color-gray-500)}.hover\:border-gray-600:hover{border-color:var(--color-gray-600)}.hover\:border-gray-700:hover{border-color:var(--color-gray-700)}.hover\:border-gray-800:hover{border-color:var(--color-gray-800)}.hover\:border-gray-900:hover{border-color:var(--color-gray-900)}.hover\:bg-amber-50:hover{background-color:var(--color-amber-50)}.hover\:bg-amber-700:hover{background-color:var(--color-amber-700)}.hover\:bg-black:hover{background-color:var(--color-black)}.hover\:bg-black\/10:hover{background-color:#0000001a}@supports (color:color-mix(in lab,red,red)){.hover\:bg-black\/10:hover{background-color:color-mix(in oklab,var(--color-black) 10%,transparent)}}.hover\:bg-blue-50:hover{background-color:var(--color-blue-50)}.hover\:bg-blue-100:hover{background-color:var(--color-blue-100)}.hover\:bg-blue-200:hover{background-color:var(--color-blue-200)}.hover\:bg-blue-300:hover{background-color:var(--color-blue-300)}.hover\:bg-blue-400:hover{background-color:var(--color-blue-400)}.hover\:bg-blue-500:hover{background-color:var(--color-blue-500)}.hover\:bg-blue-600:hover{background-color:var(--color-blue-600)}.hover\:bg-blue-700:hover{background-color:var(--color-blue-700)}.hover\:bg-blue-800:hover{background-color:var(--color-blue-800)}.hover\:bg-blue-900:hover{background-color:var(--color-blue-900)}.hover\:bg-gray-50:hover{background-color:var(--color-gray-50)}.hover\:bg-gray-100:hover{background-color:var(--color-gray-100)}.hover\:bg-gray-200:hover{background-color:var(--color-gray-200)}.hover\:bg-gray-300:hover{background-color:var(--color-gray-300)}.hover\:bg-gray-400:hover{background-color:var(--color-gray-400)}.hover\:bg-gray-500:hover{background-color:var(--color-gray-500)}.hover\:bg-gray-600:hover{background-color:var(--color-gray-600)}.hover\:bg-gray-700:hover{background-color:var(--color-gray-700)}.hover\:bg-gray-800:hover{background-color:var(--color-gray-800)}.hover\:bg-gray-900:hover{background-color:var(--color-gray-900)}.hover\:bg-green-50:hover{background-color:var(--color-green-50)}.hover\:bg-green-100:hover{background-color:var(--color-green-100)}.hover\:bg-green-200:hover{background-color:var(--color-green-200)}.hover\:bg-green-300:hover{background-color:var(--color-green-300)}.hover\:bg-green-400:hover{background-color:var(--color-green-400)}.hover\:bg-green-500:hover{background-color:var(--color-green-500)}.hover\:bg-green-600:hover{background-color:var(--color-green-600)}.hover\:bg-green-700:hover{background-color:var(--color-green-700)}.hover\:bg-green-800:hover{background-color:var(--color-green-800)}.hover\:bg-green-900:hover{background-color:var(--color-green-900)}.hover\:bg-indigo-50:hover{background-color:var(--color-indigo-50)}.hover\:bg-indigo-100:hover{background-color:var(--color-indigo-100)}.hover\:bg-indigo-200:hover{background-color:var(--color-indigo-200)}.hover\:bg-indigo-300:hover{background-color:var(--color-indigo-300)}.hover\:bg-indigo-400:hover{background-color:var(--color-indigo-400)}.hover\:bg-indigo-500:hover{background-color:var(--color-indigo-500)}.hover\:bg-indigo-600:hover{background-color:var(--color-indigo-600)}.hover\:bg-indigo-700:hover{background-color:var(--color-indigo-700)}.hover\:bg-indigo-800:hover{background-color:var(--color-indigo-800)}.hover\:bg-indigo-900:hover{background-color:var(--color-indigo-900)}.hover\:bg-orange-50:hover{background-color:var(--color-orange-50)}.hover\:bg-orange-100:hover{background-color:var(--color-orange-100)}.hover\:bg-orange-200:hover{background-color:var(--color-orange-200)}.hover\:bg-orange-300:hover{background-color:var(--color-orange-300)}.hover\:bg-orange-400:hover{background-color:var(--color-orange-400)}.hover\:bg-orange-500:hover{background-color:var(--color-orange-500)}.hover\:bg-orange-600:hover{background-color:var(--color-orange-600)}.hover\:bg-orange-700:hover{background-color:var(--color-orange-700)}.hover\:bg-orange-800:hover{background-color:var(--color-orange-800)}.hover\:bg-orange-900:hover{background-color:var(--color-orange-900)}.hover\:bg-pink-50:hover{background-color:var(--color-pink-50)}.hover\:bg-pink-100:hover{background-color:var(--color-pink-100)}.hover\:bg-pink-200:hover{background-color:var(--color-pink-200)}.hover\:bg-pink-300:hover{background-color:var(--color-pink-300)}.hover\:bg-pink-400:hover{background-color:var(--color-pink-400)}.hover\:bg-pink-500:hover{background-color:var(--color-pink-500)}.hover\:bg-pink-600:hover{background-color:var(--color-pink-600)}.hover\:bg-pink-700:hover{background-color:var(--color-pink-700)}.hover\:bg-pink-800:hover{background-color:var(--color-pink-800)}.hover\:bg-pink-900:hover{background-color:var(--color-pink-900)}.hover\:bg-purple-50:hover{background-color:var(--color-purple-50)}.hover\:bg-purple-100:hover{background-color:var(--color-purple-100)}.hover\:bg-purple-200:hover{background-color:var(--color-purple-200)}.hover\:bg-purple-300:hover{background-color:var(--color-purple-300)}.hover\:bg-purple-400:hover{background-color:var(--color-purple-400)}.hover\:bg-purple-500:hover{background-color:var(--color-purple-500)}.hover\:bg-purple-600:hover{background-color:var(--color-purple-600)}.hover\:bg-purple-700:hover{background-color:var(--color-purple-700)}.hover\:bg-purple-800:hover{background-color:var(--color-purple-800)}.hover\:bg-purple-900:hover{background-color:var(--color-purple-900)}.hover\:bg-red-50:hover{background-color:var(--color-red-50)}.hover\:bg-red-100:hover{background-color:var(--color-red-100)}.hover\:bg-red-200:hover{background-color:var(--color-red-200)}.hover\:bg-red-300:hover{background-color:var(--color-red-300)}.hover\:bg-red-400:hover{background-color:var(--color-red-400)}.hover\:bg-red-500:hover{background-color:var(--color-red-500)}.hover\:bg-red-600:hover{background-color:var(--color-red-600)}.hover\:bg-red-700:hover{background-color:var(--color-red-700)}.hover\:bg-red-800:hover{background-color:var(--color-red-800)}.hover\:bg-red-900:hover{background-color:var(--color-red-900)}.hover\:bg-slate-50:hover{background-color:var(--color-slate-50)}.hover\:bg-slate-100:hover{background-color:var(--color-slate-100)}.hover\:bg-slate-200:hover{background-color:var(--color-slate-200)}.hover\:bg-slate-300:hover{background-color:var(--color-slate-300)}.hover\:bg-slate-400:hover{background-color:var(--color-slate-400)}.hover\:bg-slate-500:hover{background-color:var(--color-slate-500)}.hover\:bg-slate-600:hover{background-color:var(--color-slate-600)}.hover\:bg-slate-700:hover{background-color:var(--color-slate-700)}.hover\:bg-slate-800:hover{background-color:var(--color-slate-800)}.hover\:bg-slate-900:hover{background-color:var(--color-slate-900)}.hover\:bg-transparent:hover{background-color:#0000}.hover\:bg-white:hover{background-color:var(--color-white)}.hover\:bg-yellow-50:hover{background-color:var(--color-yellow-50)}.hover\:bg-yellow-100:hover{background-color:var(--color-yellow-100)}.hover\:bg-yellow-200:hover{background-color:var(--color-yellow-200)}.hover\:bg-yellow-300:hover{background-color:var(--color-yellow-300)}.hover\:bg-yellow-400:hover{background-color:var(--color-yellow-400)}.hover\:bg-yellow-500:hover{background-color:var(--color-yellow-500)}.hover\:bg-yellow-600:hover{background-color:var(--color-yellow-600)}.hover\:bg-yellow-700:hover{background-color:var(--color-yellow-700)}.hover\:bg-yellow-800:hover{background-color:var(--color-yellow-800)}.hover\:bg-yellow-900:hover{background-color:var(--color-yellow-900)}.hover\:text-black:hover{color:var(--color-black)}.hover\:text-blue-50:hover{color:var(--color-blue-50)}.hover\:text-blue-100:hover{color:var(--color-blue-100)}.hover\:text-blue-200:hover{color:var(--color-blue-200)}.hover\:text-blue-300:hover{color:var(--color-blue-300)}.hover\:text-blue-400:hover{color:var(--color-blue-400)}.hover\:text-blue-500:hover{color:var(--color-blue-500)}.hover\:text-blue-600:hover{color:var(--color-blue-600)}.hover\:text-blue-700:hover{color:var(--color-blue-700)}.hover\:text-blue-800:hover{color:var(--color-blue-800)}.hover\:text-blue-900:hover{color:var(--color-blue-900)}.hover\:text-gray-50:hover{color:var(--color-gray-50)}.hover\:text-gray-100:hover{color:var(--color-gray-100)}.hover\:text-gray-200:hover{color:var(--color-gray-200)}.hover\:text-gray-300:hover{color:var(--color-gray-300)}.hover\:text-gray-400:hover{color:var(--color-gray-400)}.hover\:text-gray-500:hover{color:var(--color-gray-500)}.hover\:text-gray-600:hover{color:var(--color-gray-600)}.hover\:text-gray-700:hover{color:var(--color-gray-700)}.hover\:text-gray-800:hover{color:var(--color-gray-800)}.hover\:text-gray-900:hover{color:var(--color-gray-900)}.hover\:text-green-50:hover{color:var(--color-green-50)}.hover\:text-green-100:hover{color:var(--color-green-100)}.hover\:text-green-200:hover{color:var(--color-green-200)}.hover\:text-green-300:hover{color:var(--color-green-300)}.hover\:text-green-400:hover{color:var(--color-green-400)}.hover\:text-green-500:hover{color:var(--color-green-500)}.hover\:text-green-600:hover{color:var(--color-green-600)}.hover\:text-green-700:hover{color:var(--color-green-700)}.hover\:text-green-800:hover{color:var(--color-green-800)}.hover\:text-green-900:hover{color:var(--color-green-900)}.hover\:text-purple-50:hover{color:var(--color-purple-50)}.hover\:text-purple-100:hover{color:var(--color-purple-100)}.hover\:text-purple-200:hover{color:var(--color-purple-200)}.hover\:text-purple-300:hover{color:var(--color-purple-300)}.hover\:text-purple-400:hover{color:var(--color-purple-400)}.hover\:text-purple-500:hover{color:var(--color-purple-500)}.hover\:text-purple-600:hover{color:var(--color-purple-600)}.hover\:text-purple-700:hover{color:var(--color-purple-700)}.hover\:text-purple-800:hover{color:var(--color-purple-800)}.hover\:text-purple-900:hover{color:var(--color-purple-900)}.hover\:text-red-50:hover{color:var(--color-red-50)}.hover\:text-red-100:hover{color:var(--color-red-100)}.hover\:text-red-200:hover{color:var(--color-red-200)}.hover\:text-red-300:hover{color:var(--color-red-300)}.hover\:text-red-400:hover{color:var(--color-red-400)}.hover\:text-red-500:hover{color:var(--color-red-500)}.hover\:text-red-600:hover{color:var(--color-red-600)}.hover\:text-red-700:hover{color:var(--color-red-700)}.hover\:text-red-800:hover{color:var(--color-red-800)}.hover\:text-red-900:hover{color:var(--color-red-900)}.hover\:text-slate-50:hover{color:var(--color-slate-50)}.hover\:text-slate-100:hover{color:var(--color-slate-100)}.hover\:text-slate-200:hover{color:var(--color-slate-200)}.hover\:text-slate-300:hover{color:var(--color-slate-300)}.hover\:text-slate-400:hover{color:var(--color-slate-400)}.hover\:text-slate-500:hover{color:var(--color-slate-500)}.hover\:text-slate-600:hover{color:var(--color-slate-600)}.hover\:text-slate-700:hover{color:var(--color-slate-700)}.hover\:text-slate-800:hover{color:var(--color-slate-800)}.hover\:text-slate-900:hover{color:var(--color-slate-900)}.hover\:text-white:hover{color:var(--color-white)}.hover\:line-through:hover{text-decoration-line:line-through}.hover\:no-underline:hover{text-decoration-line:none}.hover\:underline:hover{text-decoration-line:underline}.hover\:opacity-0:hover{opacity:0}.hover\:opacity-25:hover{opacity:.25}.hover\:opacity-50:hover{opacity:.5}.hover\:opacity-75:hover{opacity:.75}.hover\:opacity-100:hover{opacity:1}.hover\:shadow:hover{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hover\:shadow-2xl:hover{--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hover\:shadow-lg:hover{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hover\:shadow-md:hover{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a), 0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hover\:shadow-none:hover{--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hover\:shadow-sm:hover{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hover\:shadow-xl:hover{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a), 0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hover\:ring-0:hover{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hover\:ring-1:hover{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hover\:ring-2:hover{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hover\:ring-4:hover{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hover\:ring-8:hover{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(8px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hover\:ring-blue-500:hover{--tw-ring-color:var(--color-blue-500)}.hover\:ring-gray-500:hover{--tw-ring-color:var(--color-gray-500)}.hover\:ring-green-500:hover{--tw-ring-color:var(--color-green-500)}.hover\:ring-red-500:hover{--tw-ring-color:var(--color-red-500)}.file\:hover\:bg-blue-100::file-selector-button:hover{background-color:var(--color-blue-100)}}.focus\:border-blue-50:focus{border-color:var(--color-blue-50)}.focus\:border-blue-100:focus{border-color:var(--color-blue-100)}.focus\:border-blue-200:focus{border-color:var(--color-blue-200)}.focus\:border-blue-300:focus{border-color:var(--color-blue-300)}.focus\:border-blue-400:focus{border-color:var(--color-blue-400)}.focus\:border-blue-500:focus{border-color:var(--color-blue-500)}.focus\:border-blue-600:focus{border-color:var(--color-blue-600)}.focus\:border-blue-700:focus{border-color:var(--color-blue-700)}.focus\:border-blue-800:focus{border-color:var(--color-blue-800)}.focus\:border-blue-900:focus{border-color:var(--color-blue-900)}.focus\:border-gray-50:focus{border-color:var(--color-gray-50)}.focus\:border-gray-100:focus{border-color:var(--color-gray-100)}.focus\:border-gray-200:focus{border-color:var(--color-gray-200)}.focus\:border-gray-300:focus{border-color:var(--color-gray-300)}.focus\:border-gray-400:focus{border-color:var(--color-gray-400)}.focus\:border-gray-500:focus{border-color:var(--color-gray-500)}.focus\:border-gray-600:focus{border-color:var(--color-gray-600)}.focus\:border-gray-700:focus{border-color:var(--color-gray-700)}.focus\:border-gray-800:focus{border-color:var(--color-gray-800)}.focus\:border-gray-900:focus{border-color:var(--color-gray-900)}.focus\:border-green-500:focus{border-color:var(--color-green-500)}.focus\:border-indigo-500:focus{border-color:var(--color-indigo-500)}.focus\:border-purple-500:focus{border-color:var(--color-purple-500)}.focus\:border-red-500:focus{border-color:var(--color-red-500)}.focus\:border-transparent:focus{border-color:#0000}.focus\:border-yellow-500:focus{border-color:var(--color-yellow-500)}.focus\:bg-black:focus{background-color:var(--color-black)}.focus\:bg-blue-50:focus{background-color:var(--color-blue-50)}.focus\:bg-blue-100:focus{background-color:var(--color-blue-100)}.focus\:bg-blue-200:focus{background-color:var(--color-blue-200)}.focus\:bg-blue-300:focus{background-color:var(--color-blue-300)}.focus\:bg-blue-400:focus{background-color:var(--color-blue-400)}.focus\:bg-blue-500:focus{background-color:var(--color-blue-500)}.focus\:bg-blue-600:focus{background-color:var(--color-blue-600)}.focus\:bg-blue-700:focus{background-color:var(--color-blue-700)}.focus\:bg-blue-800:focus{background-color:var(--color-blue-800)}.focus\:bg-blue-900:focus{background-color:var(--color-blue-900)}.focus\:bg-gray-50:focus{background-color:var(--color-gray-50)}.focus\:bg-gray-100:focus{background-color:var(--color-gray-100)}.focus\:bg-gray-200:focus{background-color:var(--color-gray-200)}.focus\:bg-gray-300:focus{background-color:var(--color-gray-300)}.focus\:bg-gray-400:focus{background-color:var(--color-gray-400)}.focus\:bg-gray-500:focus{background-color:var(--color-gray-500)}.focus\:bg-gray-600:focus{background-color:var(--color-gray-600)}.focus\:bg-gray-700:focus{background-color:var(--color-gray-700)}.focus\:bg-gray-800:focus{background-color:var(--color-gray-800)}.focus\:bg-gray-900:focus{background-color:var(--color-gray-900)}.focus\:bg-transparent:focus{background-color:#0000}.focus\:bg-white:focus{background-color:var(--color-white)}.focus\:text-black:focus{color:var(--color-black)}.focus\:text-blue-50:focus{color:var(--color-blue-50)}.focus\:text-blue-100:focus{color:var(--color-blue-100)}.focus\:text-blue-200:focus{color:var(--color-blue-200)}.focus\:text-blue-300:focus{color:var(--color-blue-300)}.focus\:text-blue-400:focus{color:var(--color-blue-400)}.focus\:text-blue-500:focus{color:var(--color-blue-500)}.focus\:text-blue-600:focus{color:var(--color-blue-600)}.focus\:text-blue-700:focus{color:var(--color-blue-700)}.focus\:text-blue-800:focus{color:var(--color-blue-800)}.focus\:text-blue-900:focus{color:var(--color-blue-900)}.focus\:text-gray-50:focus{color:var(--color-gray-50)}.focus\:text-gray-100:focus{color:var(--color-gray-100)}.focus\:text-gray-200:focus{color:var(--color-gray-200)}.focus\:text-gray-300:focus{color:var(--color-gray-300)}.focus\:text-gray-400:focus{color:var(--color-gray-400)}.focus\:text-gray-500:focus{color:var(--color-gray-500)}.focus\:text-gray-600:focus{color:var(--color-gray-600)}.focus\:text-gray-700:focus{color:var(--color-gray-700)}.focus\:text-gray-800:focus{color:var(--color-gray-800)}.focus\:text-gray-900:focus{color:var(--color-gray-900)}.focus\:text-white:focus{color:var(--color-white)}.focus\:shadow:focus{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:shadow-lg:focus{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:shadow-md:focus{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a), 0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:shadow-none:focus{--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:shadow-sm:focus{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:shadow-xl:focus{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a), 0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-0:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-1:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-4:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-8:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(8px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-amber-500:focus{--tw-ring-color:var(--color-amber-500)}.focus\:ring-blue-50:focus{--tw-ring-color:var(--color-blue-50)}.focus\:ring-blue-100:focus{--tw-ring-color:var(--color-blue-100)}.focus\:ring-blue-200:focus{--tw-ring-color:var(--color-blue-200)}.focus\:ring-blue-300:focus{--tw-ring-color:var(--color-blue-300)}.focus\:ring-blue-400:focus{--tw-ring-color:var(--color-blue-400)}.focus\:ring-blue-500:focus{--tw-ring-color:var(--color-blue-500)}.focus\:ring-blue-600:focus{--tw-ring-color:var(--color-blue-600)}.focus\:ring-blue-700:focus{--tw-ring-color:var(--color-blue-700)}.focus\:ring-blue-800:focus{--tw-ring-color:var(--color-blue-800)}.focus\:ring-blue-900:focus{--tw-ring-color:var(--color-blue-900)}.focus\:ring-current:focus{--tw-ring-color:currentcolor}.focus\:ring-gray-50:focus{--tw-ring-color:var(--color-gray-50)}.focus\:ring-gray-100:focus{--tw-ring-color:var(--color-gray-100)}.focus\:ring-gray-200:focus{--tw-ring-color:var(--color-gray-200)}.focus\:ring-gray-300:focus{--tw-ring-color:var(--color-gray-300)}.focus\:ring-gray-400:focus{--tw-ring-color:var(--color-gray-400)}.focus\:ring-gray-500:focus{--tw-ring-color:var(--color-gray-500)}.focus\:ring-gray-600:focus{--tw-ring-color:var(--color-gray-600)}.focus\:ring-gray-700:focus{--tw-ring-color:var(--color-gray-700)}.focus\:ring-gray-800:focus{--tw-ring-color:var(--color-gray-800)}.focus\:ring-gray-900:focus{--tw-ring-color:var(--color-gray-900)}.focus\:ring-green-500:focus{--tw-ring-color:var(--color-green-500)}.focus\:ring-indigo-500:focus{--tw-ring-color:var(--color-indigo-500)}.focus\:ring-purple-500:focus{--tw-ring-color:var(--color-purple-500)}.focus\:ring-red-500:focus{--tw-ring-color:var(--color-red-500)}.focus\:ring-slate-400:focus{--tw-ring-color:var(--color-slate-400)}.focus\:ring-transparent:focus{--tw-ring-color:transparent}.focus\:ring-yellow-500:focus{--tw-ring-color:var(--color-yellow-500)}.focus\:ring-offset-0:focus{--tw-ring-offset-width:0px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.focus\:ring-offset-1:focus{--tw-ring-offset-width:1px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.focus\:ring-offset-2:focus{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.focus\:ring-offset-4:focus{--tw-ring-offset-width:4px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.focus\:ring-offset-gray-100:focus{--tw-ring-offset-color:var(--color-gray-100)}.focus\:ring-offset-gray-900:focus{--tw-ring-offset-color:var(--color-gray-900)}.focus\:ring-offset-white:focus{--tw-ring-offset-color:var(--color-white)}.focus\:outline:focus{outline-style:var(--tw-outline-style);outline-width:1px}.focus\:outline-0:focus{outline-style:var(--tw-outline-style);outline-width:0}.focus\:outline-1:focus{outline-style:var(--tw-outline-style);outline-width:1px}.focus\:outline-2:focus{outline-style:var(--tw-outline-style);outline-width:2px}.focus\:outline-4:focus{outline-style:var(--tw-outline-style);outline-width:4px}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.focus\:ring-inset:focus{--tw-ring-inset:inset}.focus-visible\:ring-2:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus-visible\:ring-blue-500:focus-visible{--tw-ring-color:var(--color-blue-500)}.focus-visible\:ring-gray-500:focus-visible{--tw-ring-color:var(--color-gray-500)}.focus-visible\:ring-offset-2:focus-visible{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.focus-visible\:outline:focus-visible{outline-style:var(--tw-outline-style);outline-width:1px}.focus-visible\:outline-2:focus-visible{outline-style:var(--tw-outline-style);outline-width:2px}.focus-visible\:outline-offset-2:focus-visible{outline-offset:2px}.active\:scale-95:active{--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x) var(--tw-scale-y)}.active\:scale-100:active{--tw-scale-x:100%;--tw-scale-y:100%;--tw-scale-z:100%;scale:var(--tw-scale-x) var(--tw-scale-y)}.active\:cursor-grabbing:active{cursor:grabbing}.active\:bg-blue-600:active{background-color:var(--color-blue-600)}.active\:bg-blue-700:active{background-color:var(--color-blue-700)}.active\:bg-blue-800:active{background-color:var(--color-blue-800)}.active\:bg-gray-100:active{background-color:var(--color-gray-100)}.active\:bg-gray-200:active{background-color:var(--color-gray-200)}.active\:bg-gray-300:active{background-color:var(--color-gray-300)}.active\:bg-gray-700:active{background-color:var(--color-gray-700)}.active\:bg-gray-800:active{background-color:var(--color-gray-800)}.active\:bg-green-600:active{background-color:var(--color-green-600)}.active\:bg-green-700:active{background-color:var(--color-green-700)}.active\:bg-red-600:active{background-color:var(--color-red-600)}.active\:bg-red-700:active{background-color:var(--color-red-700)}.active\:opacity-75:active{opacity:.75}.active\:opacity-100:active{opacity:1}.disabled\:cursor-default:disabled{cursor:default}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:border-gray-200:disabled{border-color:var(--color-gray-200)}.disabled\:border-gray-300:disabled{border-color:var(--color-gray-300)}.disabled\:bg-blue-400:disabled{background-color:var(--color-blue-400)}.disabled\:bg-gray-100:disabled{background-color:var(--color-gray-100)}.disabled\:bg-gray-200:disabled{background-color:var(--color-gray-200)}.disabled\:bg-gray-300:disabled{background-color:var(--color-gray-300)}.disabled\:bg-gray-400:disabled{background-color:var(--color-gray-400)}.disabled\:text-gray-300:disabled{color:var(--color-gray-300)}.disabled\:text-gray-400:disabled{color:var(--color-gray-400)}.disabled\:text-gray-500:disabled{color:var(--color-gray-500)}.disabled\:no-underline:disabled{text-decoration-line:none}.disabled\:opacity-25:disabled{opacity:.25}.disabled\:opacity-50:disabled{opacity:.5}.disabled\:opacity-75:disabled{opacity:.75}@media(hover:hover){.disabled\:hover\:bg-indigo-600:disabled:hover{background-color:var(--color-indigo-600)}.disabled\:hover\:bg-transparent:disabled:hover{background-color:#0000}}@media(min-width:40rem){.sm\:col-span-1{grid-column:span 1/span 1}.sm\:col-span-2{grid-column:span 2/span 2}.sm\:col-span-3{grid-column:span 3/span 3}.sm\:col-span-4{grid-column:span 4/span 4}.sm\:col-span-6{grid-column:span 6/span 6}.sm\:col-span-12{grid-column:span 12/span 12}.sm\:col-span-full{grid-column:1/-1}.sm\:m-0{margin:calc(var(--spacing) * 0)}.sm\:m-1{margin:calc(var(--spacing) * 1)}.sm\:m-2{margin:calc(var(--spacing) * 2)}.sm\:m-3{margin:calc(var(--spacing) * 3)}.sm\:m-4{margin:calc(var(--spacing) * 4)}.sm\:m-auto{margin:auto}.sm\:mx-auto{margin-inline:auto}.sm\:my-auto{margin-block:auto}.sm\:mt-6{margin-top:calc(var(--spacing) * 6)}.sm\:block{display:block}.sm\:flex{display:flex}.sm\:hidden{display:none}.sm\:inline{display:inline}.sm\:inline-block{display:inline-block}.sm\:inline-flex{display:inline-flex}.sm\:w-1\/2{width:50%}.sm\:w-1\/3{width:33.3333%}.sm\:w-1\/4{width:25%}.sm\:w-2\/3{width:66.6667%}.sm\:w-3\/4{width:75%}.sm\:w-40{width:calc(var(--spacing) * 40)}.sm\:w-auto{width:auto}.sm\:w-full{width:100%}.sm\:grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.sm\:grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.sm\:grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.sm\:flex-col{flex-direction:column}.sm\:flex-row{flex-direction:row}.sm\:items-center{align-items:center}.sm\:items-end{align-items:flex-end}.sm\:items-start{align-items:flex-start}.sm\:justify-between{justify-content:space-between}.sm\:justify-center{justify-content:center}.sm\:justify-end{justify-content:flex-end}.sm\:justify-start{justify-content:flex-start}.sm\:gap-0{gap:calc(var(--spacing) * 0)}.sm\:gap-1{gap:calc(var(--spacing) * 1)}.sm\:gap-2{gap:calc(var(--spacing) * 2)}.sm\:gap-3{gap:calc(var(--spacing) * 3)}.sm\:gap-4{gap:calc(var(--spacing) * 4)}.sm\:gap-5{gap:calc(var(--spacing) * 5)}.sm\:gap-6{gap:calc(var(--spacing) * 6)}.sm\:gap-8{gap:calc(var(--spacing) * 8)}:where(.sm\:space-y-0>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 0) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 0) * calc(1 - var(--tw-space-y-reverse)))}:where(.sm\:space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 1) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 1) * calc(1 - var(--tw-space-y-reverse)))}:where(.sm\:space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)))}:where(.sm\:space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse)))}:where(.sm\:space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse)))}:where(.sm\:space-x-0>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 0) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 0) * calc(1 - var(--tw-space-x-reverse)))}:where(.sm\:space-x-1>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 1) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 1) * calc(1 - var(--tw-space-x-reverse)))}:where(.sm\:space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 2) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-x-reverse)))}:where(.sm\:space-x-3>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 3) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-x-reverse)))}:where(.sm\:space-x-4>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 4) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-x-reverse)))}.sm\:p-0{padding:calc(var(--spacing) * 0)}.sm\:p-1{padding:calc(var(--spacing) * 1)}.sm\:p-2{padding:calc(var(--spacing) * 2)}.sm\:p-3{padding:calc(var(--spacing) * 3)}.sm\:p-4{padding:calc(var(--spacing) * 4)}.sm\:p-5{padding:calc(var(--spacing) * 5)}.sm\:p-6{padding:calc(var(--spacing) * 6)}.sm\:p-8{padding:calc(var(--spacing) * 8)}.sm\:px-0{padding-inline:calc(var(--spacing) * 0)}.sm\:px-1{padding-inline:calc(var(--spacing) * 1)}.sm\:px-2{padding-inline:calc(var(--spacing) * 2)}.sm\:px-3{padding-inline:calc(var(--spacing) * 3)}.sm\:px-4{padding-inline:calc(var(--spacing) * 4)}.sm\:px-5{padding-inline:calc(var(--spacing) * 5)}.sm\:px-6{padding-inline:calc(var(--spacing) * 6)}.sm\:px-8{padding-inline:calc(var(--spacing) * 8)}.sm\:py-0{padding-block:calc(var(--spacing) * 0)}.sm\:py-1{padding-block:calc(var(--spacing) * 1)}.sm\:py-2{padding-block:calc(var(--spacing) * 2)}.sm\:py-3{padding-block:calc(var(--spacing) * 3)}.sm\:py-4{padding-block:calc(var(--spacing) * 4)}.sm\:py-5{padding-block:calc(var(--spacing) * 5)}.sm\:py-6{padding-block:calc(var(--spacing) * 6)}.sm\:py-8{padding-block:calc(var(--spacing) * 8)}.sm\:text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.sm\:text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.sm\:text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.sm\:text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.sm\:text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.sm\:text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.sm\:text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}}@media(min-width:48rem){.md\:absolute{position:absolute}.md\:fixed{position:fixed}.md\:relative{position:relative}.md\:static{position:static}.md\:sticky{position:sticky}.md\:inset-0{inset:calc(var(--spacing) * 0)}.md\:top-0{top:calc(var(--spacing) * 0)}.md\:right-0{right:calc(var(--spacing) * 0)}.md\:bottom-0{bottom:calc(var(--spacing) * 0)}.md\:left-0{left:calc(var(--spacing) * 0)}.md\:order-1{order:1}.md\:order-2{order:2}.md\:order-3{order:3}.md\:order-first{order:-9999}.md\:order-last{order:9999}.md\:order-none{order:0}.md\:col-span-1{grid-column:span 1/span 1}.md\:col-span-2{grid-column:span 2/span 2}.md\:col-span-3{grid-column:span 3/span 3}.md\:col-span-4{grid-column:span 4/span 4}.md\:col-span-5{grid-column:span 5/span 5}.md\:col-span-6{grid-column:span 6/span 6}.md\:col-span-12{grid-column:span 12/span 12}.md\:col-span-full{grid-column:1/-1}.md\:row-span-1{grid-row:span 1/span 1}.md\:row-span-2{grid-row:span 2/span 2}.md\:row-span-3{grid-row:span 3/span 3}.md\:row-span-4{grid-row:span 4/span 4}.md\:row-span-full{grid-row:1/-1}.md\:m-0{margin:calc(var(--spacing) * 0)}.md\:m-1{margin:calc(var(--spacing) * 1)}.md\:m-2{margin:calc(var(--spacing) * 2)}.md\:m-3{margin:calc(var(--spacing) * 3)}.md\:m-4{margin:calc(var(--spacing) * 4)}.md\:m-auto{margin:auto}.md\:mx-auto{margin-inline:auto}.md\:my-auto{margin-block:auto}.md\:mt-0{margin-top:calc(var(--spacing) * 0)}.md\:mt-1{margin-top:calc(var(--spacing) * 1)}.md\:mt-2{margin-top:calc(var(--spacing) * 2)}.md\:mt-3{margin-top:calc(var(--spacing) * 3)}.md\:mt-4{margin-top:calc(var(--spacing) * 4)}.md\:mt-6{margin-top:calc(var(--spacing) * 6)}.md\:mt-8{margin-top:calc(var(--spacing) * 8)}.md\:mr-0{margin-right:calc(var(--spacing) * 0)}.md\:mr-1{margin-right:calc(var(--spacing) * 1)}.md\:mr-2{margin-right:calc(var(--spacing) * 2)}.md\:mr-3{margin-right:calc(var(--spacing) * 3)}.md\:mr-4{margin-right:calc(var(--spacing) * 4)}.md\:mr-6{margin-right:calc(var(--spacing) * 6)}.md\:mr-8{margin-right:calc(var(--spacing) * 8)}.md\:mr-auto{margin-right:auto}.md\:mb-0{margin-bottom:calc(var(--spacing) * 0)}.md\:mb-1{margin-bottom:calc(var(--spacing) * 1)}.md\:mb-2{margin-bottom:calc(var(--spacing) * 2)}.md\:mb-3{margin-bottom:calc(var(--spacing) * 3)}.md\:mb-4{margin-bottom:calc(var(--spacing) * 4)}.md\:mb-6{margin-bottom:calc(var(--spacing) * 6)}.md\:mb-8{margin-bottom:calc(var(--spacing) * 8)}.md\:ml-0{margin-left:calc(var(--spacing) * 0)}.md\:ml-1{margin-left:calc(var(--spacing) * 1)}.md\:ml-2{margin-left:calc(var(--spacing) * 2)}.md\:ml-3{margin-left:calc(var(--spacing) * 3)}.md\:ml-4{margin-left:calc(var(--spacing) * 4)}.md\:ml-6{margin-left:calc(var(--spacing) * 6)}.md\:ml-8{margin-left:calc(var(--spacing) * 8)}.md\:ml-auto{margin-left:auto}.md\:block{display:block}.md\:flex{display:flex}.md\:hidden{display:none}.md\:inline{display:inline}.md\:inline-block{display:inline-block}.md\:inline-flex{display:inline-flex}.md\:h-20{height:calc(var(--spacing) * 20)}.md\:w-1\/2{width:50%}.md\:w-1\/3{width:33.3333%}.md\:w-1\/4{width:25%}.md\:w-1\/5{width:20%}.md\:w-2\/3{width:66.6667%}.md\:w-2\/5{width:40%}.md\:w-3\/4{width:75%}.md\:w-3\/5{width:60%}.md\:w-4\/5{width:80%}.md\:w-20{width:calc(var(--spacing) * 20)}.md\:w-40{width:calc(var(--spacing) * 40)}.md\:w-48{width:calc(var(--spacing) * 48)}.md\:w-64{width:calc(var(--spacing) * 64)}.md\:w-72{width:calc(var(--spacing) * 72)}.md\:w-80{width:calc(var(--spacing) * 80)}.md\:w-auto{width:auto}.md\:w-full{width:100%}.md\:max-w-2xl{max-width:var(--container-2xl)}.md\:max-w-3xl{max-width:var(--container-3xl)}.md\:max-w-4xl{max-width:var(--container-4xl)}.md\:max-w-5xl{max-width:var(--container-5xl)}.md\:max-w-6xl{max-width:var(--container-6xl)}.md\:max-w-7xl{max-width:var(--container-7xl)}.md\:max-w-full{max-width:100%}.md\:max-w-lg{max-width:var(--container-lg)}.md\:max-w-md{max-width:var(--container-md)}.md\:max-w-none{max-width:none}.md\:max-w-sm{max-width:var(--container-sm)}.md\:max-w-xl{max-width:var(--container-xl)}.md\:max-w-xs{max-width:var(--container-xs)}.md\:flex-1{flex:1}.md\:grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr))}.md\:grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.md\:grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.md\:grid-rows-1{grid-template-rows:repeat(1,minmax(0,1fr))}.md\:grid-rows-2{grid-template-rows:repeat(2,minmax(0,1fr))}.md\:grid-rows-3{grid-template-rows:repeat(3,minmax(0,1fr))}.md\:grid-rows-4{grid-template-rows:repeat(4,minmax(0,1fr))}.md\:grid-rows-5{grid-template-rows:repeat(5,minmax(0,1fr))}.md\:grid-rows-6{grid-template-rows:repeat(6,minmax(0,1fr))}.md\:flex-col{flex-direction:column}.md\:flex-row{flex-direction:row}.md\:items-baseline{align-items:baseline}.md\:items-center{align-items:center}.md\:items-end{align-items:flex-end}.md\:items-start{align-items:flex-start}.md\:items-stretch{align-items:stretch}.md\:justify-around{justify-content:space-around}.md\:justify-between{justify-content:space-between}.md\:justify-center{justify-content:center}.md\:justify-end{justify-content:flex-end}.md\:justify-start{justify-content:flex-start}.md\:gap-0{gap:calc(var(--spacing) * 0)}.md\:gap-1{gap:calc(var(--spacing) * 1)}.md\:gap-2{gap:calc(var(--spacing) * 2)}.md\:gap-3{gap:calc(var(--spacing) * 3)}.md\:gap-4{gap:calc(var(--spacing) * 4)}.md\:gap-5{gap:calc(var(--spacing) * 5)}.md\:gap-6{gap:calc(var(--spacing) * 6)}.md\:gap-8{gap:calc(var(--spacing) * 8)}.md\:gap-10{gap:calc(var(--spacing) * 10)}.md\:gap-12{gap:calc(var(--spacing) * 12)}:where(.md\:space-y-0>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 0) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 0) * calc(1 - var(--tw-space-y-reverse)))}:where(.md\:space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 1) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 1) * calc(1 - var(--tw-space-y-reverse)))}:where(.md\:space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)))}:where(.md\:space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse)))}:where(.md\:space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse)))}:where(.md\:space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 6) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-y-reverse)))}:where(.md\:space-y-8>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 8) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 8) * calc(1 - var(--tw-space-y-reverse)))}:where(.md\:space-x-0>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 0) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 0) * calc(1 - var(--tw-space-x-reverse)))}:where(.md\:space-x-1>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 1) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 1) * calc(1 - var(--tw-space-x-reverse)))}:where(.md\:space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 2) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-x-reverse)))}:where(.md\:space-x-3>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 3) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-x-reverse)))}:where(.md\:space-x-4>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 4) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-x-reverse)))}:where(.md\:space-x-6>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 6) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-x-reverse)))}:where(.md\:space-x-8>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 8) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 8) * calc(1 - var(--tw-space-x-reverse)))}.md\:rounded-2xl{border-radius:var(--radius-2xl)}.md\:p-0{padding:calc(var(--spacing) * 0)}.md\:p-1{padding:calc(var(--spacing) * 1)}.md\:p-2{padding:calc(var(--spacing) * 2)}.md\:p-3{padding:calc(var(--spacing) * 3)}.md\:p-4{padding:calc(var(--spacing) * 4)}.md\:p-5{padding:calc(var(--spacing) * 5)}.md\:p-6{padding:calc(var(--spacing) * 6)}.md\:p-8{padding:calc(var(--spacing) * 8)}.md\:p-10{padding:calc(var(--spacing) * 10)}.md\:p-12{padding:calc(var(--spacing) * 12)}.md\:px-0{padding-inline:calc(var(--spacing) * 0)}.md\:px-1{padding-inline:calc(var(--spacing) * 1)}.md\:px-2{padding-inline:calc(var(--spacing) * 2)}.md\:px-3{padding-inline:calc(var(--spacing) * 3)}.md\:px-4{padding-inline:calc(var(--spacing) * 4)}.md\:px-5{padding-inline:calc(var(--spacing) * 5)}.md\:px-6{padding-inline:calc(var(--spacing) * 6)}.md\:px-8{padding-inline:calc(var(--spacing) * 8)}.md\:px-10{padding-inline:calc(var(--spacing) * 10)}.md\:px-12{padding-inline:calc(var(--spacing) * 12)}.md\:py-0{padding-block:calc(var(--spacing) * 0)}.md\:py-1{padding-block:calc(var(--spacing) * 1)}.md\:py-2{padding-block:calc(var(--spacing) * 2)}.md\:py-3{padding-block:calc(var(--spacing) * 3)}.md\:py-4{padding-block:calc(var(--spacing) * 4)}.md\:py-5{padding-block:calc(var(--spacing) * 5)}.md\:py-6{padding-block:calc(var(--spacing) * 6)}.md\:py-8{padding-block:calc(var(--spacing) * 8)}.md\:py-10{padding-block:calc(var(--spacing) * 10)}.md\:py-12{padding-block:calc(var(--spacing) * 12)}.md\:text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.md\:text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.md\:text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.md\:text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.md\:text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.md\:text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.md\:text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.md\:text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}}@media(min-width:64rem){.lg\:absolute{position:absolute}.lg\:fixed{position:fixed}.lg\:relative{position:relative}.lg\:static{position:static}.lg\:sticky{position:sticky}.lg\:inset-0{inset:calc(var(--spacing) * 0)}.lg\:top-0{top:calc(var(--spacing) * 0)}.lg\:right-0{right:calc(var(--spacing) * 0)}.lg\:bottom-0{bottom:calc(var(--spacing) * 0)}.lg\:left-0{left:calc(var(--spacing) * 0)}.lg\:order-1{order:1}.lg\:order-2{order:2}.lg\:order-3{order:3}.lg\:order-4{order:4}.lg\:order-first{order:-9999}.lg\:order-last{order:9999}.lg\:order-none{order:0}.lg\:col-span-1{grid-column:span 1/span 1}.lg\:col-span-2{grid-column:span 2/span 2}.lg\:col-span-3{grid-column:span 3/span 3}.lg\:col-span-4{grid-column:span 4/span 4}.lg\:col-span-5{grid-column:span 5/span 5}.lg\:col-span-6{grid-column:span 6/span 6}.lg\:col-span-7{grid-column:span 7/span 7}.lg\:col-span-8{grid-column:span 8/span 8}.lg\:col-span-12{grid-column:span 12/span 12}.lg\:col-span-full{grid-column:1/-1}.lg\:row-span-1{grid-row:span 1/span 1}.lg\:row-span-2{grid-row:span 2/span 2}.lg\:row-span-3{grid-row:span 3/span 3}.lg\:row-span-4{grid-row:span 4/span 4}.lg\:row-span-full{grid-row:1/-1}.lg\:m-0{margin:calc(var(--spacing) * 0)}.lg\:m-1{margin:calc(var(--spacing) * 1)}.lg\:m-2{margin:calc(var(--spacing) * 2)}.lg\:m-3{margin:calc(var(--spacing) * 3)}.lg\:m-4{margin:calc(var(--spacing) * 4)}.lg\:m-auto{margin:auto}.lg\:mx-auto{margin-inline:auto}.lg\:my-auto{margin-block:auto}.lg\:mt-0{margin-top:calc(var(--spacing) * 0)}.lg\:mt-1{margin-top:calc(var(--spacing) * 1)}.lg\:mt-2{margin-top:calc(var(--spacing) * 2)}.lg\:mt-3{margin-top:calc(var(--spacing) * 3)}.lg\:mt-4{margin-top:calc(var(--spacing) * 4)}.lg\:mt-6{margin-top:calc(var(--spacing) * 6)}.lg\:mt-8{margin-top:calc(var(--spacing) * 8)}.lg\:mt-10{margin-top:calc(var(--spacing) * 10)}.lg\:mt-12{margin-top:calc(var(--spacing) * 12)}.lg\:mr-0{margin-right:calc(var(--spacing) * 0)}.lg\:mr-1{margin-right:calc(var(--spacing) * 1)}.lg\:mr-2{margin-right:calc(var(--spacing) * 2)}.lg\:mr-3{margin-right:calc(var(--spacing) * 3)}.lg\:mr-4{margin-right:calc(var(--spacing) * 4)}.lg\:mr-6{margin-right:calc(var(--spacing) * 6)}.lg\:mr-8{margin-right:calc(var(--spacing) * 8)}.lg\:mr-auto{margin-right:auto}.lg\:mb-0{margin-bottom:calc(var(--spacing) * 0)}.lg\:mb-1{margin-bottom:calc(var(--spacing) * 1)}.lg\:mb-2{margin-bottom:calc(var(--spacing) * 2)}.lg\:mb-3{margin-bottom:calc(var(--spacing) * 3)}.lg\:mb-4{margin-bottom:calc(var(--spacing) * 4)}.lg\:mb-6{margin-bottom:calc(var(--spacing) * 6)}.lg\:mb-8{margin-bottom:calc(var(--spacing) * 8)}.lg\:mb-10{margin-bottom:calc(var(--spacing) * 10)}.lg\:mb-12{margin-bottom:calc(var(--spacing) * 12)}.lg\:ml-0{margin-left:calc(var(--spacing) * 0)}.lg\:ml-1{margin-left:calc(var(--spacing) * 1)}.lg\:ml-2{margin-left:calc(var(--spacing) * 2)}.lg\:ml-3{margin-left:calc(var(--spacing) * 3)}.lg\:ml-4{margin-left:calc(var(--spacing) * 4)}.lg\:ml-6{margin-left:calc(var(--spacing) * 6)}.lg\:ml-8{margin-left:calc(var(--spacing) * 8)}.lg\:ml-auto{margin-left:auto}.lg\:block{display:block}.lg\:flex{display:flex}.lg\:hidden{display:none}.lg\:inline{display:inline}.lg\:inline-block{display:inline-block}.lg\:inline-flex{display:inline-flex}.lg\:w-1\/2{width:50%}.lg\:w-1\/3{width:33.3333%}.lg\:w-1\/4{width:25%}.lg\:w-1\/5{width:20%}.lg\:w-1\/6{width:16.6667%}.lg\:w-2\/3{width:66.6667%}.lg\:w-2\/5{width:40%}.lg\:w-3\/4{width:75%}.lg\:w-3\/5{width:60%}.lg\:w-4\/5{width:80%}.lg\:w-5\/6{width:83.3333%}.lg\:w-auto{width:auto}.lg\:w-full{width:100%}.lg\:max-w-2xl{max-width:var(--container-2xl)}.lg\:max-w-3xl{max-width:var(--container-3xl)}.lg\:max-w-4xl{max-width:var(--container-4xl)}.lg\:max-w-5xl{max-width:var(--container-5xl)}.lg\:max-w-6xl{max-width:var(--container-6xl)}.lg\:max-w-7xl{max-width:var(--container-7xl)}.lg\:max-w-full{max-width:100%}.lg\:max-w-lg{max-width:var(--container-lg)}.lg\:max-w-md{max-width:var(--container-md)}.lg\:max-w-none{max-width:none}.lg\:max-w-sm{max-width:var(--container-sm)}.lg\:max-w-xl{max-width:var(--container-xl)}.lg\:max-w-xs{max-width:var(--container-xs)}.lg\:grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.lg\:grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr))}.lg\:grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.lg\:grid-cols-7{grid-template-columns:repeat(7,minmax(0,1fr))}.lg\:grid-cols-8{grid-template-columns:repeat(8,minmax(0,1fr))}.lg\:grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.lg\:grid-rows-1{grid-template-rows:repeat(1,minmax(0,1fr))}.lg\:grid-rows-2{grid-template-rows:repeat(2,minmax(0,1fr))}.lg\:grid-rows-3{grid-template-rows:repeat(3,minmax(0,1fr))}.lg\:grid-rows-4{grid-template-rows:repeat(4,minmax(0,1fr))}.lg\:grid-rows-5{grid-template-rows:repeat(5,minmax(0,1fr))}.lg\:grid-rows-6{grid-template-rows:repeat(6,minmax(0,1fr))}.lg\:flex-col{flex-direction:column}.lg\:flex-row{flex-direction:row}.lg\:items-baseline{align-items:baseline}.lg\:items-center{align-items:center}.lg\:items-end{align-items:flex-end}.lg\:items-start{align-items:flex-start}.lg\:items-stretch{align-items:stretch}.lg\:justify-around{justify-content:space-around}.lg\:justify-between{justify-content:space-between}.lg\:justify-center{justify-content:center}.lg\:justify-end{justify-content:flex-end}.lg\:justify-evenly{justify-content:space-evenly}.lg\:justify-start{justify-content:flex-start}.lg\:gap-0{gap:calc(var(--spacing) * 0)}.lg\:gap-1{gap:calc(var(--spacing) * 1)}.lg\:gap-2{gap:calc(var(--spacing) * 2)}.lg\:gap-3{gap:calc(var(--spacing) * 3)}.lg\:gap-4{gap:calc(var(--spacing) * 4)}.lg\:gap-5{gap:calc(var(--spacing) * 5)}.lg\:gap-6{gap:calc(var(--spacing) * 6)}.lg\:gap-8{gap:calc(var(--spacing) * 8)}.lg\:gap-10{gap:calc(var(--spacing) * 10)}.lg\:gap-12{gap:calc(var(--spacing) * 12)}.lg\:gap-16{gap:calc(var(--spacing) * 16)}:where(.lg\:space-y-0>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 0) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 0) * calc(1 - var(--tw-space-y-reverse)))}:where(.lg\:space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 1) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 1) * calc(1 - var(--tw-space-y-reverse)))}:where(.lg\:space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)))}:where(.lg\:space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse)))}:where(.lg\:space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse)))}:where(.lg\:space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 6) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-y-reverse)))}:where(.lg\:space-y-8>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 8) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 8) * calc(1 - var(--tw-space-y-reverse)))}:where(.lg\:space-y-10>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 10) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 10) * calc(1 - var(--tw-space-y-reverse)))}:where(.lg\:space-x-0>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 0) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 0) * calc(1 - var(--tw-space-x-reverse)))}:where(.lg\:space-x-1>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 1) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 1) * calc(1 - var(--tw-space-x-reverse)))}:where(.lg\:space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 2) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-x-reverse)))}:where(.lg\:space-x-3>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 3) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-x-reverse)))}:where(.lg\:space-x-4>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 4) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-x-reverse)))}:where(.lg\:space-x-6>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 6) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-x-reverse)))}:where(.lg\:space-x-8>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 8) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 8) * calc(1 - var(--tw-space-x-reverse)))}:where(.lg\:space-x-10>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 10) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 10) * calc(1 - var(--tw-space-x-reverse)))}.lg\:p-0{padding:calc(var(--spacing) * 0)}.lg\:p-1{padding:calc(var(--spacing) * 1)}.lg\:p-2{padding:calc(var(--spacing) * 2)}.lg\:p-3{padding:calc(var(--spacing) * 3)}.lg\:p-4{padding:calc(var(--spacing) * 4)}.lg\:p-5{padding:calc(var(--spacing) * 5)}.lg\:p-6{padding:calc(var(--spacing) * 6)}.lg\:p-8{padding:calc(var(--spacing) * 8)}.lg\:p-10{padding:calc(var(--spacing) * 10)}.lg\:p-12{padding:calc(var(--spacing) * 12)}.lg\:p-16{padding:calc(var(--spacing) * 16)}.lg\:px-0{padding-inline:calc(var(--spacing) * 0)}.lg\:px-1{padding-inline:calc(var(--spacing) * 1)}.lg\:px-2{padding-inline:calc(var(--spacing) * 2)}.lg\:px-3{padding-inline:calc(var(--spacing) * 3)}.lg\:px-4{padding-inline:calc(var(--spacing) * 4)}.lg\:px-5{padding-inline:calc(var(--spacing) * 5)}.lg\:px-6{padding-inline:calc(var(--spacing) * 6)}.lg\:px-8{padding-inline:calc(var(--spacing) * 8)}.lg\:px-10{padding-inline:calc(var(--spacing) * 10)}.lg\:px-12{padding-inline:calc(var(--spacing) * 12)}.lg\:px-16{padding-inline:calc(var(--spacing) * 16)}.lg\:py-0{padding-block:calc(var(--spacing) * 0)}.lg\:py-1{padding-block:calc(var(--spacing) * 1)}.lg\:py-2{padding-block:calc(var(--spacing) * 2)}.lg\:py-3{padding-block:calc(var(--spacing) * 3)}.lg\:py-4{padding-block:calc(var(--spacing) * 4)}.lg\:py-5{padding-block:calc(var(--spacing) * 5)}.lg\:py-6{padding-block:calc(var(--spacing) * 6)}.lg\:py-8{padding-block:calc(var(--spacing) * 8)}.lg\:py-10{padding-block:calc(var(--spacing) * 10)}.lg\:py-12{padding-block:calc(var(--spacing) * 12)}.lg\:py-16{padding-block:calc(var(--spacing) * 16)}.lg\:text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.lg\:text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.lg\:text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.lg\:text-5xl{font-size:var(--text-5xl);line-height:var(--tw-leading,var(--text-5xl--line-height))}.lg\:text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.lg\:text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.lg\:text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.lg\:text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.lg\:text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}}@media(min-width:80rem){.xl\:col-span-1{grid-column:span 1/span 1}.xl\:col-span-2{grid-column:span 2/span 2}.xl\:col-span-3{grid-column:span 3/span 3}.xl\:col-span-4{grid-column:span 4/span 4}.xl\:col-span-5{grid-column:span 5/span 5}.xl\:col-span-6{grid-column:span 6/span 6}.xl\:col-span-12{grid-column:span 12/span 12}.xl\:col-span-full{grid-column:1/-1}.xl\:row-span-1{grid-row:span 1/span 1}.xl\:row-span-2{grid-row:span 2/span 2}.xl\:row-span-3{grid-row:span 3/span 3}.xl\:row-span-full{grid-row:1/-1}.xl\:m-0{margin:calc(var(--spacing) * 0)}.xl\:m-auto{margin:auto}.xl\:mx-auto{margin-inline:auto}.xl\:my-auto{margin-block:auto}.xl\:block{display:block}.xl\:flex{display:flex}.xl\:hidden{display:none}.xl\:inline{display:inline}.xl\:inline-block{display:inline-block}.xl\:inline-flex{display:inline-flex}.xl\:w-1\/2{width:50%}.xl\:w-1\/3{width:33.3333%}.xl\:w-1\/4{width:25%}.xl\:w-1\/5{width:20%}.xl\:w-2\/3{width:66.6667%}.xl\:w-2\/5{width:40%}.xl\:w-3\/4{width:75%}.xl\:w-3\/5{width:60%}.xl\:w-4\/5{width:80%}.xl\:w-auto{width:auto}.xl\:w-full{width:100%}.xl\:max-w-2xl{max-width:var(--container-2xl)}.xl\:max-w-3xl{max-width:var(--container-3xl)}.xl\:max-w-4xl{max-width:var(--container-4xl)}.xl\:max-w-5xl{max-width:var(--container-5xl)}.xl\:max-w-6xl{max-width:var(--container-6xl)}.xl\:max-w-7xl{max-width:var(--container-7xl)}.xl\:max-w-full{max-width:100%}.xl\:max-w-lg{max-width:var(--container-lg)}.xl\:max-w-md{max-width:var(--container-md)}.xl\:max-w-none{max-width:none}.xl\:max-w-sm{max-width:var(--container-sm)}.xl\:max-w-xl{max-width:var(--container-xl)}.xl\:max-w-xs{max-width:var(--container-xs)}.xl\:grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.xl\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.xl\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.xl\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.xl\:grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr))}.xl\:grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.xl\:grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.xl\:grid-rows-1{grid-template-rows:repeat(1,minmax(0,1fr))}.xl\:grid-rows-2{grid-template-rows:repeat(2,minmax(0,1fr))}.xl\:grid-rows-3{grid-template-rows:repeat(3,minmax(0,1fr))}.xl\:grid-rows-4{grid-template-rows:repeat(4,minmax(0,1fr))}.xl\:flex-col{flex-direction:column}.xl\:flex-row{flex-direction:row}.xl\:items-center{align-items:center}.xl\:items-end{align-items:flex-end}.xl\:items-start{align-items:flex-start}.xl\:justify-between{justify-content:space-between}.xl\:justify-center{justify-content:center}.xl\:justify-end{justify-content:flex-end}.xl\:justify-start{justify-content:flex-start}.xl\:gap-0{gap:calc(var(--spacing) * 0)}.xl\:gap-1{gap:calc(var(--spacing) * 1)}.xl\:gap-2{gap:calc(var(--spacing) * 2)}.xl\:gap-3{gap:calc(var(--spacing) * 3)}.xl\:gap-4{gap:calc(var(--spacing) * 4)}.xl\:gap-5{gap:calc(var(--spacing) * 5)}.xl\:gap-6{gap:calc(var(--spacing) * 6)}.xl\:gap-8{gap:calc(var(--spacing) * 8)}.xl\:gap-10{gap:calc(var(--spacing) * 10)}.xl\:gap-12{gap:calc(var(--spacing) * 12)}.xl\:gap-16{gap:calc(var(--spacing) * 16)}:where(.xl\:space-y-0>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 0) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 0) * calc(1 - var(--tw-space-y-reverse)))}:where(.xl\:space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 1) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 1) * calc(1 - var(--tw-space-y-reverse)))}:where(.xl\:space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)))}:where(.xl\:space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse)))}:where(.xl\:space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse)))}:where(.xl\:space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 6) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-y-reverse)))}:where(.xl\:space-y-8>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 8) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 8) * calc(1 - var(--tw-space-y-reverse)))}:where(.xl\:space-x-0>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 0) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 0) * calc(1 - var(--tw-space-x-reverse)))}:where(.xl\:space-x-1>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 1) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 1) * calc(1 - var(--tw-space-x-reverse)))}:where(.xl\:space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 2) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-x-reverse)))}:where(.xl\:space-x-3>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 3) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-x-reverse)))}:where(.xl\:space-x-4>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 4) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-x-reverse)))}:where(.xl\:space-x-6>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 6) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-x-reverse)))}:where(.xl\:space-x-8>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 8) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 8) * calc(1 - var(--tw-space-x-reverse)))}.xl\:p-0{padding:calc(var(--spacing) * 0)}.xl\:p-1{padding:calc(var(--spacing) * 1)}.xl\:p-2{padding:calc(var(--spacing) * 2)}.xl\:p-3{padding:calc(var(--spacing) * 3)}.xl\:p-4{padding:calc(var(--spacing) * 4)}.xl\:p-5{padding:calc(var(--spacing) * 5)}.xl\:p-6{padding:calc(var(--spacing) * 6)}.xl\:p-8{padding:calc(var(--spacing) * 8)}.xl\:p-10{padding:calc(var(--spacing) * 10)}.xl\:p-12{padding:calc(var(--spacing) * 12)}.xl\:p-16{padding:calc(var(--spacing) * 16)}.xl\:px-0{padding-inline:calc(var(--spacing) * 0)}.xl\:px-1{padding-inline:calc(var(--spacing) * 1)}.xl\:px-2{padding-inline:calc(var(--spacing) * 2)}.xl\:px-3{padding-inline:calc(var(--spacing) * 3)}.xl\:px-4{padding-inline:calc(var(--spacing) * 4)}.xl\:px-5{padding-inline:calc(var(--spacing) * 5)}.xl\:px-6{padding-inline:calc(var(--spacing) * 6)}.xl\:px-8{padding-inline:calc(var(--spacing) * 8)}.xl\:px-10{padding-inline:calc(var(--spacing) * 10)}.xl\:px-12{padding-inline:calc(var(--spacing) * 12)}.xl\:px-16{padding-inline:calc(var(--spacing) * 16)}.xl\:py-0{padding-block:calc(var(--spacing) * 0)}.xl\:py-1{padding-block:calc(var(--spacing) * 1)}.xl\:py-2{padding-block:calc(var(--spacing) * 2)}.xl\:py-3{padding-block:calc(var(--spacing) * 3)}.xl\:py-4{padding-block:calc(var(--spacing) * 4)}.xl\:py-5{padding-block:calc(var(--spacing) * 5)}.xl\:py-6{padding-block:calc(var(--spacing) * 6)}.xl\:py-8{padding-block:calc(var(--spacing) * 8)}.xl\:py-10{padding-block:calc(var(--spacing) * 10)}.xl\:py-12{padding-block:calc(var(--spacing) * 12)}.xl\:py-16{padding-block:calc(var(--spacing) * 16)}.xl\:text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.xl\:text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.xl\:text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.xl\:text-5xl{font-size:var(--text-5xl);line-height:var(--tw-leading,var(--text-5xl--line-height))}.xl\:text-6xl{font-size:var(--text-6xl);line-height:var(--tw-leading,var(--text-6xl--line-height))}.xl\:text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.xl\:text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.xl\:text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.xl\:text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.xl\:text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}}@media(min-width:96rem){.\32xl\:col-span-1{grid-column:span 1/span 1}.\32xl\:col-span-2{grid-column:span 2/span 2}.\32xl\:col-span-3{grid-column:span 3/span 3}.\32xl\:col-span-4{grid-column:span 4/span 4}.\32xl\:col-span-6{grid-column:span 6/span 6}.\32xl\:col-span-12{grid-column:span 12/span 12}.\32xl\:block{display:block}.\32xl\:flex{display:flex}.\32xl\:hidden{display:none}.\32xl\:w-1\/2{width:50%}.\32xl\:w-1\/3{width:33.3333%}.\32xl\:w-1\/4{width:25%}.\32xl\:w-2\/3{width:66.6667%}.\32xl\:w-3\/4{width:75%}.\32xl\:w-auto{width:auto}.\32xl\:w-full{width:100%}.\32xl\:max-w-2xl{max-width:var(--container-2xl)}.\32xl\:max-w-3xl{max-width:var(--container-3xl)}.\32xl\:max-w-4xl{max-width:var(--container-4xl)}.\32xl\:max-w-5xl{max-width:var(--container-5xl)}.\32xl\:max-w-6xl{max-width:var(--container-6xl)}.\32xl\:max-w-7xl{max-width:var(--container-7xl)}.\32xl\:max-w-full{max-width:100%}.\32xl\:max-w-lg{max-width:var(--container-lg)}.\32xl\:max-w-md{max-width:var(--container-md)}.\32xl\:max-w-sm{max-width:var(--container-sm)}.\32xl\:max-w-xl{max-width:var(--container-xl)}.\32xl\:max-w-xs{max-width:var(--container-xs)}.\32xl\:grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.\32xl\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.\32xl\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.\32xl\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.\32xl\:grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr))}.\32xl\:grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.\32xl\:grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.\32xl\:gap-0{gap:calc(var(--spacing) * 0)}.\32xl\:gap-4{gap:calc(var(--spacing) * 4)}.\32xl\:gap-6{gap:calc(var(--spacing) * 6)}.\32xl\:gap-8{gap:calc(var(--spacing) * 8)}.\32xl\:gap-12{gap:calc(var(--spacing) * 12)}.\32xl\:gap-16{gap:calc(var(--spacing) * 16)}.\32xl\:p-0{padding:calc(var(--spacing) * 0)}.\32xl\:p-4{padding:calc(var(--spacing) * 4)}.\32xl\:p-6{padding:calc(var(--spacing) * 6)}.\32xl\:p-8{padding:calc(var(--spacing) * 8)}.\32xl\:p-12{padding:calc(var(--spacing) * 12)}.\32xl\:p-16{padding:calc(var(--spacing) * 16)}.\32xl\:px-0{padding-inline:calc(var(--spacing) * 0)}.\32xl\:px-4{padding-inline:calc(var(--spacing) * 4)}.\32xl\:px-6{padding-inline:calc(var(--spacing) * 6)}.\32xl\:px-8{padding-inline:calc(var(--spacing) * 8)}.\32xl\:px-12{padding-inline:calc(var(--spacing) * 12)}.\32xl\:px-16{padding-inline:calc(var(--spacing) * 16)}.\32xl\:py-0{padding-block:calc(var(--spacing) * 0)}.\32xl\:py-4{padding-block:calc(var(--spacing) * 4)}.\32xl\:py-6{padding-block:calc(var(--spacing) * 6)}.\32xl\:py-8{padding-block:calc(var(--spacing) * 8)}.\32xl\:py-12{padding-block:calc(var(--spacing) * 12)}.\32xl\:py-16{padding-block:calc(var(--spacing) * 16)}.\32xl\:text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.\32xl\:text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.\32xl\:text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.\32xl\:text-5xl{font-size:var(--text-5xl);line-height:var(--tw-leading,var(--text-5xl--line-height))}.\32xl\:text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.\32xl\:text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.\32xl\:text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.\32xl\:text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.\32xl\:text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}}.dark .dark\:block{display:block}.dark .dark\:flex{display:flex}.dark .dark\:flex-1{flex:1}.dark .dark\:flex-auto{flex:auto}.dark .dark\:flex-none{flex:none}.dark .dark\:flex-col{flex-direction:column}.dark .dark\:flex-row{flex-direction:row}.dark .dark\:flex-wrap{flex-wrap:wrap}.dark .dark\:items-center{align-items:center}.dark .dark\:items-end{align-items:flex-end}.dark .dark\:items-start{align-items:flex-start}.dark .dark\:items-stretch{align-items:stretch}.dark .dark\:justify-around{justify-content:space-around}.dark .dark\:justify-between{justify-content:space-between}.dark .dark\:justify-center{justify-content:center}.dark .dark\:justify-end{justify-content:flex-end}.dark .dark\:justify-start{justify-content:flex-start}:where(.dark .dark\:divide-gray-50>:not(:last-child)){border-color:var(--color-gray-50)}:where(.dark .dark\:divide-gray-100>:not(:last-child)){border-color:var(--color-gray-100)}:where(.dark .dark\:divide-gray-200>:not(:last-child)){border-color:var(--color-gray-200)}:where(.dark .dark\:divide-gray-300>:not(:last-child)){border-color:var(--color-gray-300)}:where(.dark .dark\:divide-gray-400>:not(:last-child)){border-color:var(--color-gray-400)}:where(.dark .dark\:divide-gray-500>:not(:last-child)){border-color:var(--color-gray-500)}:where(.dark .dark\:divide-gray-600>:not(:last-child)){border-color:var(--color-gray-600)}:where(.dark .dark\:divide-gray-700>:not(:last-child)){border-color:var(--color-gray-700)}:where(.dark .dark\:divide-gray-800>:not(:last-child)){border-color:var(--color-gray-800)}:where(.dark .dark\:divide-gray-900>:not(:last-child)){border-color:var(--color-gray-900)}:where(.dark .dark\:divide-purple-800>:not(:last-child)){border-color:var(--color-purple-800)}:where(.dark .dark\:divide-slate-700>:not(:last-child)){border-color:var(--color-slate-700)}.dark .dark\:self-center{align-self:center}.dark .dark\:self-end{align-self:flex-end}.dark .dark\:self-start{align-self:flex-start}.dark .dark\:self-stretch{align-self:stretch}.dark .dark\:overflow-auto{overflow:auto}.dark .dark\:overflow-hidden{overflow:hidden}.dark .dark\:overflow-visible{overflow:visible}.dark .dark\:overflow-x-auto{overflow-x:auto}.dark .dark\:overflow-y-auto{overflow-y:auto}.dark .dark\:rounded{border-radius:.25rem}.dark .dark\:rounded-full{border-radius:3.40282e38px}.dark .dark\:rounded-lg{border-radius:var(--radius-lg)}.dark .dark\:rounded-none{border-radius:0}.dark .dark\:rounded-sm{border-radius:var(--radius-sm)}.dark .dark\:rounded-xl{border-radius:var(--radius-xl)}.dark .dark\:border{border-style:var(--tw-border-style);border-width:1px}.dark .dark\:border-0{border-style:var(--tw-border-style);border-width:0}.dark .dark\:border-dashed{--tw-border-style:dashed;border-style:dashed}.dark .dark\:border-dotted{--tw-border-style:dotted;border-style:dotted}.dark .dark\:border-solid{--tw-border-style:solid;border-style:solid}.dark .dark\:border-amber-50{border-color:var(--color-amber-50)}.dark .dark\:border-amber-100{border-color:var(--color-amber-100)}.dark .dark\:border-amber-200{border-color:var(--color-amber-200)}.dark .dark\:border-amber-300{border-color:var(--color-amber-300)}.dark .dark\:border-amber-400{border-color:var(--color-amber-400)}.dark .dark\:border-amber-500{border-color:var(--color-amber-500)}.dark .dark\:border-amber-600{border-color:var(--color-amber-600)}.dark .dark\:border-amber-700{border-color:var(--color-amber-700)}.dark .dark\:border-amber-800{border-color:var(--color-amber-800)}.dark .dark\:border-amber-900{border-color:var(--color-amber-900)}.dark .dark\:border-black{border-color:var(--color-black)}.dark .dark\:border-blue-50{border-color:var(--color-blue-50)}.dark .dark\:border-blue-100{border-color:var(--color-blue-100)}.dark .dark\:border-blue-200{border-color:var(--color-blue-200)}.dark .dark\:border-blue-300{border-color:var(--color-blue-300)}.dark .dark\:border-blue-400{border-color:var(--color-blue-400)}.dark .dark\:border-blue-500{border-color:var(--color-blue-500)}.dark .dark\:border-blue-600{border-color:var(--color-blue-600)}.dark .dark\:border-blue-700{border-color:var(--color-blue-700)}.dark .dark\:border-blue-800{border-color:var(--color-blue-800)}.dark .dark\:border-blue-900{border-color:var(--color-blue-900)}.dark .dark\:border-gray-50{border-color:var(--color-gray-50)}.dark .dark\:border-gray-100{border-color:var(--color-gray-100)}.dark .dark\:border-gray-200{border-color:var(--color-gray-200)}.dark .dark\:border-gray-300{border-color:var(--color-gray-300)}.dark .dark\:border-gray-400{border-color:var(--color-gray-400)}.dark .dark\:border-gray-500{border-color:var(--color-gray-500)}.dark .dark\:border-gray-600{border-color:var(--color-gray-600)}.dark .dark\:border-gray-700{border-color:var(--color-gray-700)}.dark .dark\:border-gray-800{border-color:var(--color-gray-800)}.dark .dark\:border-gray-900{border-color:var(--color-gray-900)}.dark .dark\:border-gray-950{border-color:var(--color-gray-950)}.dark .dark\:border-green-50{border-color:var(--color-green-50)}.dark .dark\:border-green-100{border-color:var(--color-green-100)}.dark .dark\:border-green-200{border-color:var(--color-green-200)}.dark .dark\:border-green-300{border-color:var(--color-green-300)}.dark .dark\:border-green-400{border-color:var(--color-green-400)}.dark .dark\:border-green-500{border-color:var(--color-green-500)}.dark .dark\:border-green-600{border-color:var(--color-green-600)}.dark .dark\:border-green-700{border-color:var(--color-green-700)}.dark .dark\:border-green-800{border-color:var(--color-green-800)}.dark .dark\:border-green-900{border-color:var(--color-green-900)}.dark .dark\:border-indigo-400{border-color:var(--color-indigo-400)}.dark .dark\:border-orange-50{border-color:var(--color-orange-50)}.dark .dark\:border-orange-100{border-color:var(--color-orange-100)}.dark .dark\:border-orange-200{border-color:var(--color-orange-200)}.dark .dark\:border-orange-300{border-color:var(--color-orange-300)}.dark .dark\:border-orange-400{border-color:var(--color-orange-400)}.dark .dark\:border-orange-500{border-color:var(--color-orange-500)}.dark .dark\:border-orange-600{border-color:var(--color-orange-600)}.dark .dark\:border-orange-700{border-color:var(--color-orange-700)}.dark .dark\:border-orange-800{border-color:var(--color-orange-800)}.dark .dark\:border-orange-900{border-color:var(--color-orange-900)}.dark .dark\:border-pink-50{border-color:var(--color-pink-50)}.dark .dark\:border-pink-100{border-color:var(--color-pink-100)}.dark .dark\:border-pink-200{border-color:var(--color-pink-200)}.dark .dark\:border-pink-300{border-color:var(--color-pink-300)}.dark .dark\:border-pink-400{border-color:var(--color-pink-400)}.dark .dark\:border-pink-500{border-color:var(--color-pink-500)}.dark .dark\:border-pink-600{border-color:var(--color-pink-600)}.dark .dark\:border-pink-700{border-color:var(--color-pink-700)}.dark .dark\:border-pink-800{border-color:var(--color-pink-800)}.dark .dark\:border-pink-900{border-color:var(--color-pink-900)}.dark .dark\:border-purple-50{border-color:var(--color-purple-50)}.dark .dark\:border-purple-100{border-color:var(--color-purple-100)}.dark .dark\:border-purple-200{border-color:var(--color-purple-200)}.dark .dark\:border-purple-300{border-color:var(--color-purple-300)}.dark .dark\:border-purple-400{border-color:var(--color-purple-400)}.dark .dark\:border-purple-500{border-color:var(--color-purple-500)}.dark .dark\:border-purple-600{border-color:var(--color-purple-600)}.dark .dark\:border-purple-700{border-color:var(--color-purple-700)}.dark .dark\:border-purple-800{border-color:var(--color-purple-800)}.dark .dark\:border-purple-900{border-color:var(--color-purple-900)}.dark .dark\:border-red-50{border-color:var(--color-red-50)}.dark .dark\:border-red-100{border-color:var(--color-red-100)}.dark .dark\:border-red-200{border-color:var(--color-red-200)}.dark .dark\:border-red-300{border-color:var(--color-red-300)}.dark .dark\:border-red-400{border-color:var(--color-red-400)}.dark .dark\:border-red-500{border-color:var(--color-red-500)}.dark .dark\:border-red-600{border-color:var(--color-red-600)}.dark .dark\:border-red-700{border-color:var(--color-red-700)}.dark .dark\:border-red-800{border-color:var(--color-red-800)}.dark .dark\:border-red-900{border-color:var(--color-red-900)}.dark .dark\:border-slate-50{border-color:var(--color-slate-50)}.dark .dark\:border-slate-100{border-color:var(--color-slate-100)}.dark .dark\:border-slate-200{border-color:var(--color-slate-200)}.dark .dark\:border-slate-300{border-color:var(--color-slate-300)}.dark .dark\:border-slate-400{border-color:var(--color-slate-400)}.dark .dark\:border-slate-500{border-color:var(--color-slate-500)}.dark .dark\:border-slate-600{border-color:var(--color-slate-600)}.dark .dark\:border-slate-700{border-color:var(--color-slate-700)}.dark .dark\:border-slate-800{border-color:var(--color-slate-800)}.dark .dark\:border-slate-900{border-color:var(--color-slate-900)}.dark .dark\:border-slate-950{border-color:var(--color-slate-950)}.dark .dark\:border-transparent{border-color:#0000}.dark .dark\:border-white{border-color:var(--color-white)}.dark .dark\:border-yellow-50{border-color:var(--color-yellow-50)}.dark .dark\:border-yellow-100{border-color:var(--color-yellow-100)}.dark .dark\:border-yellow-200{border-color:var(--color-yellow-200)}.dark .dark\:border-yellow-300{border-color:var(--color-yellow-300)}.dark .dark\:border-yellow-400{border-color:var(--color-yellow-400)}.dark .dark\:border-yellow-500{border-color:var(--color-yellow-500)}.dark .dark\:border-yellow-600{border-color:var(--color-yellow-600)}.dark .dark\:border-yellow-700{border-color:var(--color-yellow-700)}.dark .dark\:border-yellow-800{border-color:var(--color-yellow-800)}.dark .dark\:border-yellow-900{border-color:var(--color-yellow-900)}.dark .dark\:bg-amber-50{background-color:var(--color-amber-50)}.dark .dark\:bg-amber-100{background-color:var(--color-amber-100)}.dark .dark\:bg-amber-200{background-color:var(--color-amber-200)}.dark .dark\:bg-amber-300{background-color:var(--color-amber-300)}.dark .dark\:bg-amber-400{background-color:var(--color-amber-400)}.dark .dark\:bg-amber-500{background-color:var(--color-amber-500)}.dark .dark\:bg-amber-600{background-color:var(--color-amber-600)}.dark .dark\:bg-amber-700{background-color:var(--color-amber-700)}.dark .dark\:bg-amber-800{background-color:var(--color-amber-800)}.dark .dark\:bg-amber-900{background-color:var(--color-amber-900)}.dark .dark\:bg-amber-900\/20{background-color:#7b330633}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-amber-900\/20{background-color:color-mix(in oklab,var(--color-amber-900) 20%,transparent)}}.dark .dark\:bg-amber-900\/30{background-color:#7b33064d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-amber-900\/30{background-color:color-mix(in oklab,var(--color-amber-900) 30%,transparent)}}.dark .dark\:bg-amber-900\/40{background-color:#7b330666}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-amber-900\/40{background-color:color-mix(in oklab,var(--color-amber-900) 40%,transparent)}}.dark .dark\:bg-amber-950{background-color:var(--color-amber-950)}.dark .dark\:bg-black{background-color:var(--color-black)}.dark .dark\:bg-blue-50{background-color:var(--color-blue-50)}.dark .dark\:bg-blue-100{background-color:var(--color-blue-100)}.dark .dark\:bg-blue-200{background-color:var(--color-blue-200)}.dark .dark\:bg-blue-300{background-color:var(--color-blue-300)}.dark .dark\:bg-blue-400{background-color:var(--color-blue-400)}.dark .dark\:bg-blue-500{background-color:var(--color-blue-500)}.dark .dark\:bg-blue-600{background-color:var(--color-blue-600)}.dark .dark\:bg-blue-700{background-color:var(--color-blue-700)}.dark .dark\:bg-blue-800{background-color:var(--color-blue-800)}.dark .dark\:bg-blue-900{background-color:var(--color-blue-900)}.dark .dark\:bg-blue-900\/20{background-color:#1c398e33}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-blue-900\/20{background-color:color-mix(in oklab,var(--color-blue-900) 20%,transparent)}}.dark .dark\:bg-blue-900\/30{background-color:#1c398e4d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-blue-900\/30{background-color:color-mix(in oklab,var(--color-blue-900) 30%,transparent)}}.dark .dark\:bg-blue-900\/40{background-color:#1c398e66}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-blue-900\/40{background-color:color-mix(in oklab,var(--color-blue-900) 40%,transparent)}}.dark .dark\:bg-blue-900\/50{background-color:#1c398e80}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-blue-900\/50{background-color:color-mix(in oklab,var(--color-blue-900) 50%,transparent)}}.dark .dark\:bg-blue-950{background-color:var(--color-blue-950)}.dark .dark\:bg-cyan-50{background-color:var(--color-cyan-50)}.dark .dark\:bg-cyan-100{background-color:var(--color-cyan-100)}.dark .dark\:bg-cyan-200{background-color:var(--color-cyan-200)}.dark .dark\:bg-cyan-300{background-color:var(--color-cyan-300)}.dark .dark\:bg-cyan-400{background-color:var(--color-cyan-400)}.dark .dark\:bg-cyan-500{background-color:var(--color-cyan-500)}.dark .dark\:bg-cyan-600{background-color:var(--color-cyan-600)}.dark .dark\:bg-cyan-700{background-color:var(--color-cyan-700)}.dark .dark\:bg-cyan-800{background-color:var(--color-cyan-800)}.dark .dark\:bg-cyan-900{background-color:var(--color-cyan-900)}.dark .dark\:bg-cyan-900\/30{background-color:#104e644d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-cyan-900\/30{background-color:color-mix(in oklab,var(--color-cyan-900) 30%,transparent)}}.dark .dark\:bg-cyan-950{background-color:var(--color-cyan-950)}.dark .dark\:bg-emerald-50{background-color:var(--color-emerald-50)}.dark .dark\:bg-emerald-100{background-color:var(--color-emerald-100)}.dark .dark\:bg-emerald-200{background-color:var(--color-emerald-200)}.dark .dark\:bg-emerald-300{background-color:var(--color-emerald-300)}.dark .dark\:bg-emerald-400{background-color:var(--color-emerald-400)}.dark .dark\:bg-emerald-500{background-color:var(--color-emerald-500)}.dark .dark\:bg-emerald-600{background-color:var(--color-emerald-600)}.dark .dark\:bg-emerald-700{background-color:var(--color-emerald-700)}.dark .dark\:bg-emerald-800{background-color:var(--color-emerald-800)}.dark .dark\:bg-emerald-900{background-color:var(--color-emerald-900)}.dark .dark\:bg-emerald-950{background-color:var(--color-emerald-950)}.dark .dark\:bg-gray-50{background-color:var(--color-gray-50)}.dark .dark\:bg-gray-100{background-color:var(--color-gray-100)}.dark .dark\:bg-gray-200{background-color:var(--color-gray-200)}.dark .dark\:bg-gray-300{background-color:var(--color-gray-300)}.dark .dark\:bg-gray-400{background-color:var(--color-gray-400)}.dark .dark\:bg-gray-500{background-color:var(--color-gray-500)}.dark .dark\:bg-gray-600{background-color:var(--color-gray-600)}.dark .dark\:bg-gray-700{background-color:var(--color-gray-700)}.dark .dark\:bg-gray-700\/30{background-color:#3641534d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-gray-700\/30{background-color:color-mix(in oklab,var(--color-gray-700) 30%,transparent)}}.dark .dark\:bg-gray-700\/50{background-color:#36415380}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-gray-700\/50{background-color:color-mix(in oklab,var(--color-gray-700) 50%,transparent)}}.dark .dark\:bg-gray-800{background-color:var(--color-gray-800)}.dark .dark\:bg-gray-800\/50{background-color:#1e293980}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-gray-800\/50{background-color:color-mix(in oklab,var(--color-gray-800) 50%,transparent)}}.dark .dark\:bg-gray-800\/90{background-color:#1e2939e6}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-gray-800\/90{background-color:color-mix(in oklab,var(--color-gray-800) 90%,transparent)}}.dark .dark\:bg-gray-900{background-color:var(--color-gray-900)}.dark .dark\:bg-gray-900\/50{background-color:#10182880}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-gray-900\/50{background-color:color-mix(in oklab,var(--color-gray-900) 50%,transparent)}}.dark .dark\:bg-gray-950{background-color:var(--color-gray-950)}.dark .dark\:bg-green-50{background-color:var(--color-green-50)}.dark .dark\:bg-green-100{background-color:var(--color-green-100)}.dark .dark\:bg-green-200{background-color:var(--color-green-200)}.dark .dark\:bg-green-300{background-color:var(--color-green-300)}.dark .dark\:bg-green-400{background-color:var(--color-green-400)}.dark .dark\:bg-green-500{background-color:var(--color-green-500)}.dark .dark\:bg-green-600{background-color:var(--color-green-600)}.dark .dark\:bg-green-700{background-color:var(--color-green-700)}.dark .dark\:bg-green-800{background-color:var(--color-green-800)}.dark .dark\:bg-green-900{background-color:var(--color-green-900)}.dark .dark\:bg-green-900\/20{background-color:#0d542b33}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-green-900\/20{background-color:color-mix(in oklab,var(--color-green-900) 20%,transparent)}}.dark .dark\:bg-green-900\/30{background-color:#0d542b4d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-green-900\/30{background-color:color-mix(in oklab,var(--color-green-900) 30%,transparent)}}.dark .dark\:bg-green-950{background-color:var(--color-green-950)}.dark .dark\:bg-indigo-50{background-color:var(--color-indigo-50)}.dark .dark\:bg-indigo-100{background-color:var(--color-indigo-100)}.dark .dark\:bg-indigo-200{background-color:var(--color-indigo-200)}.dark .dark\:bg-indigo-300{background-color:var(--color-indigo-300)}.dark .dark\:bg-indigo-400{background-color:var(--color-indigo-400)}.dark .dark\:bg-indigo-500{background-color:var(--color-indigo-500)}.dark .dark\:bg-indigo-600{background-color:var(--color-indigo-600)}.dark .dark\:bg-indigo-700{background-color:var(--color-indigo-700)}.dark .dark\:bg-indigo-800{background-color:var(--color-indigo-800)}.dark .dark\:bg-indigo-900{background-color:var(--color-indigo-900)}.dark .dark\:bg-indigo-900\/30{background-color:#312c854d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-indigo-900\/30{background-color:color-mix(in oklab,var(--color-indigo-900) 30%,transparent)}}.dark .dark\:bg-indigo-950{background-color:var(--color-indigo-950)}.dark .dark\:bg-orange-50{background-color:var(--color-orange-50)}.dark .dark\:bg-orange-100{background-color:var(--color-orange-100)}.dark .dark\:bg-orange-200{background-color:var(--color-orange-200)}.dark .dark\:bg-orange-300{background-color:var(--color-orange-300)}.dark .dark\:bg-orange-400{background-color:var(--color-orange-400)}.dark .dark\:bg-orange-500{background-color:var(--color-orange-500)}.dark .dark\:bg-orange-600{background-color:var(--color-orange-600)}.dark .dark\:bg-orange-700{background-color:var(--color-orange-700)}.dark .dark\:bg-orange-800{background-color:var(--color-orange-800)}.dark .dark\:bg-orange-900{background-color:var(--color-orange-900)}.dark .dark\:bg-orange-900\/20{background-color:#7e2a0c33}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-orange-900\/20{background-color:color-mix(in oklab,var(--color-orange-900) 20%,transparent)}}.dark .dark\:bg-orange-900\/30{background-color:#7e2a0c4d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-orange-900\/30{background-color:color-mix(in oklab,var(--color-orange-900) 30%,transparent)}}.dark .dark\:bg-orange-950{background-color:var(--color-orange-950)}.dark .dark\:bg-pink-50{background-color:var(--color-pink-50)}.dark .dark\:bg-pink-100{background-color:var(--color-pink-100)}.dark .dark\:bg-pink-200{background-color:var(--color-pink-200)}.dark .dark\:bg-pink-300{background-color:var(--color-pink-300)}.dark .dark\:bg-pink-400{background-color:var(--color-pink-400)}.dark .dark\:bg-pink-500{background-color:var(--color-pink-500)}.dark .dark\:bg-pink-600{background-color:var(--color-pink-600)}.dark .dark\:bg-pink-700{background-color:var(--color-pink-700)}.dark .dark\:bg-pink-800{background-color:var(--color-pink-800)}.dark .dark\:bg-pink-900{background-color:var(--color-pink-900)}.dark .dark\:bg-pink-950{background-color:var(--color-pink-950)}.dark .dark\:bg-purple-50{background-color:var(--color-purple-50)}.dark .dark\:bg-purple-100{background-color:var(--color-purple-100)}.dark .dark\:bg-purple-200{background-color:var(--color-purple-200)}.dark .dark\:bg-purple-300{background-color:var(--color-purple-300)}.dark .dark\:bg-purple-400{background-color:var(--color-purple-400)}.dark .dark\:bg-purple-500{background-color:var(--color-purple-500)}.dark .dark\:bg-purple-600{background-color:var(--color-purple-600)}.dark .dark\:bg-purple-700{background-color:var(--color-purple-700)}.dark .dark\:bg-purple-800{background-color:var(--color-purple-800)}.dark .dark\:bg-purple-800\/50{background-color:#6e11b080}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-purple-800\/50{background-color:color-mix(in oklab,var(--color-purple-800) 50%,transparent)}}.dark .dark\:bg-purple-900{background-color:var(--color-purple-900)}.dark .dark\:bg-purple-900\/20{background-color:#59168b33}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-purple-900\/20{background-color:color-mix(in oklab,var(--color-purple-900) 20%,transparent)}}.dark .dark\:bg-purple-900\/30{background-color:#59168b4d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-purple-900\/30{background-color:color-mix(in oklab,var(--color-purple-900) 30%,transparent)}}.dark .dark\:bg-purple-900\/50{background-color:#59168b80}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-purple-900\/50{background-color:color-mix(in oklab,var(--color-purple-900) 50%,transparent)}}.dark .dark\:bg-purple-950{background-color:var(--color-purple-950)}.dark .dark\:bg-red-50{background-color:var(--color-red-50)}.dark .dark\:bg-red-100{background-color:var(--color-red-100)}.dark .dark\:bg-red-200{background-color:var(--color-red-200)}.dark .dark\:bg-red-300{background-color:var(--color-red-300)}.dark .dark\:bg-red-400{background-color:var(--color-red-400)}.dark .dark\:bg-red-500{background-color:var(--color-red-500)}.dark .dark\:bg-red-600{background-color:var(--color-red-600)}.dark .dark\:bg-red-700{background-color:var(--color-red-700)}.dark .dark\:bg-red-800{background-color:var(--color-red-800)}.dark .dark\:bg-red-900{background-color:var(--color-red-900)}.dark .dark\:bg-red-900\/20{background-color:#82181a33}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-red-900\/20{background-color:color-mix(in oklab,var(--color-red-900) 20%,transparent)}}.dark .dark\:bg-red-900\/30{background-color:#82181a4d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-red-900\/30{background-color:color-mix(in oklab,var(--color-red-900) 30%,transparent)}}.dark .dark\:bg-red-950{background-color:var(--color-red-950)}.dark .dark\:bg-rose-50{background-color:var(--color-rose-50)}.dark .dark\:bg-rose-100{background-color:var(--color-rose-100)}.dark .dark\:bg-rose-200{background-color:var(--color-rose-200)}.dark .dark\:bg-rose-300{background-color:var(--color-rose-300)}.dark .dark\:bg-rose-400{background-color:var(--color-rose-400)}.dark .dark\:bg-rose-500{background-color:var(--color-rose-500)}.dark .dark\:bg-rose-600{background-color:var(--color-rose-600)}.dark .dark\:bg-rose-700{background-color:var(--color-rose-700)}.dark .dark\:bg-rose-800{background-color:var(--color-rose-800)}.dark .dark\:bg-rose-900{background-color:var(--color-rose-900)}.dark .dark\:bg-rose-950{background-color:var(--color-rose-950)}.dark .dark\:bg-slate-50{background-color:var(--color-slate-50)}.dark .dark\:bg-slate-100{background-color:var(--color-slate-100)}.dark .dark\:bg-slate-200{background-color:var(--color-slate-200)}.dark .dark\:bg-slate-300{background-color:var(--color-slate-300)}.dark .dark\:bg-slate-400{background-color:var(--color-slate-400)}.dark .dark\:bg-slate-500{background-color:var(--color-slate-500)}.dark .dark\:bg-slate-600{background-color:var(--color-slate-600)}.dark .dark\:bg-slate-700{background-color:var(--color-slate-700)}.dark .dark\:bg-slate-700\/50{background-color:#31415880}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-slate-700\/50{background-color:color-mix(in oklab,var(--color-slate-700) 50%,transparent)}}.dark .dark\:bg-slate-800{background-color:var(--color-slate-800)}.dark .dark\:bg-slate-900{background-color:var(--color-slate-900)}.dark .dark\:bg-slate-950{background-color:var(--color-slate-950)}.dark .dark\:bg-teal-50{background-color:var(--color-teal-50)}.dark .dark\:bg-teal-100{background-color:var(--color-teal-100)}.dark .dark\:bg-teal-200{background-color:var(--color-teal-200)}.dark .dark\:bg-teal-300{background-color:var(--color-teal-300)}.dark .dark\:bg-teal-400{background-color:var(--color-teal-400)}.dark .dark\:bg-teal-500{background-color:var(--color-teal-500)}.dark .dark\:bg-teal-600{background-color:var(--color-teal-600)}.dark .dark\:bg-teal-700{background-color:var(--color-teal-700)}.dark .dark\:bg-teal-800{background-color:var(--color-teal-800)}.dark .dark\:bg-teal-900{background-color:var(--color-teal-900)}.dark .dark\:bg-teal-900\/30{background-color:#0b4f4a4d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-teal-900\/30{background-color:color-mix(in oklab,var(--color-teal-900) 30%,transparent)}}.dark .dark\:bg-teal-950{background-color:var(--color-teal-950)}.dark .dark\:bg-white{background-color:var(--color-white)}.dark .dark\:bg-yellow-50{background-color:var(--color-yellow-50)}.dark .dark\:bg-yellow-100{background-color:var(--color-yellow-100)}.dark .dark\:bg-yellow-200{background-color:var(--color-yellow-200)}.dark .dark\:bg-yellow-300{background-color:var(--color-yellow-300)}.dark .dark\:bg-yellow-400{background-color:var(--color-yellow-400)}.dark .dark\:bg-yellow-500{background-color:var(--color-yellow-500)}.dark .dark\:bg-yellow-600{background-color:var(--color-yellow-600)}.dark .dark\:bg-yellow-700{background-color:var(--color-yellow-700)}.dark .dark\:bg-yellow-800{background-color:var(--color-yellow-800)}.dark .dark\:bg-yellow-900{background-color:var(--color-yellow-900)}.dark .dark\:bg-yellow-900\/20{background-color:#733e0a33}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-yellow-900\/20{background-color:color-mix(in oklab,var(--color-yellow-900) 20%,transparent)}}.dark .dark\:bg-yellow-900\/30{background-color:#733e0a4d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-yellow-900\/30{background-color:color-mix(in oklab,var(--color-yellow-900) 30%,transparent)}}.dark .dark\:bg-yellow-900\/40{background-color:#733e0a66}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-yellow-900\/40{background-color:color-mix(in oklab,var(--color-yellow-900) 40%,transparent)}}.dark .dark\:bg-yellow-950{background-color:var(--color-yellow-950)}.dark .dark\:from-blue-900{--tw-gradient-from:var(--color-blue-900);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.dark .dark\:from-gray-50{--tw-gradient-from:var(--color-gray-50);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.dark .dark\:from-gray-100{--tw-gradient-from:var(--color-gray-100);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.dark .dark\:from-gray-200{--tw-gradient-from:var(--color-gray-200);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.dark .dark\:from-gray-300{--tw-gradient-from:var(--color-gray-300);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.dark .dark\:from-gray-400{--tw-gradient-from:var(--color-gray-400);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.dark .dark\:from-gray-500{--tw-gradient-from:var(--color-gray-500);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.dark .dark\:from-gray-600{--tw-gradient-from:var(--color-gray-600);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.dark .dark\:from-gray-700{--tw-gradient-from:var(--color-gray-700);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.dark .dark\:from-gray-800{--tw-gradient-from:var(--color-gray-800);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.dark .dark\:from-gray-900{--tw-gradient-from:var(--color-gray-900);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.dark .dark\:to-gray-50{--tw-gradient-to:var(--color-gray-50);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.dark .dark\:to-gray-100{--tw-gradient-to:var(--color-gray-100);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.dark .dark\:to-gray-200{--tw-gradient-to:var(--color-gray-200);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.dark .dark\:to-gray-300{--tw-gradient-to:var(--color-gray-300);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.dark .dark\:to-gray-400{--tw-gradient-to:var(--color-gray-400);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.dark .dark\:to-gray-500{--tw-gradient-to:var(--color-gray-500);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.dark .dark\:to-gray-600{--tw-gradient-to:var(--color-gray-600);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.dark .dark\:to-gray-700{--tw-gradient-to:var(--color-gray-700);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.dark .dark\:to-gray-800{--tw-gradient-to:var(--color-gray-800);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.dark .dark\:to-gray-900{--tw-gradient-to:var(--color-gray-900);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.dark .dark\:to-indigo-900{--tw-gradient-to:var(--color-indigo-900);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.dark .dark\:text-center{text-align:center}.dark .dark\:text-justify{text-align:justify}.dark .dark\:text-left{text-align:left}.dark .dark\:text-right{text-align:right}.dark .dark\:text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.dark .dark\:text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.dark .dark\:text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.dark .dark\:text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.dark .dark\:text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.dark .dark\:text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.dark .dark\:font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.dark .dark\:font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.dark .dark\:font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.dark .dark\:whitespace-normal{white-space:normal}.dark .dark\:whitespace-nowrap{white-space:nowrap}.dark .dark\:whitespace-pre-wrap{white-space:pre-wrap}.dark .dark\:text-amber-50{color:var(--color-amber-50)}.dark .dark\:text-amber-100{color:var(--color-amber-100)}.dark .dark\:text-amber-200{color:var(--color-amber-200)}.dark .dark\:text-amber-300{color:var(--color-amber-300)}.dark .dark\:text-amber-400{color:var(--color-amber-400)}.dark .dark\:text-amber-500{color:var(--color-amber-500)}.dark .dark\:text-amber-600{color:var(--color-amber-600)}.dark .dark\:text-amber-700{color:var(--color-amber-700)}.dark .dark\:text-amber-800{color:var(--color-amber-800)}.dark .dark\:text-amber-900{color:var(--color-amber-900)}.dark .dark\:text-black{color:var(--color-black)}.dark .dark\:text-blue-50{color:var(--color-blue-50)}.dark .dark\:text-blue-100{color:var(--color-blue-100)}.dark .dark\:text-blue-200{color:var(--color-blue-200)}.dark .dark\:text-blue-300{color:var(--color-blue-300)}.dark .dark\:text-blue-400{color:var(--color-blue-400)}.dark .dark\:text-blue-500{color:var(--color-blue-500)}.dark .dark\:text-blue-600{color:var(--color-blue-600)}.dark .dark\:text-blue-700{color:var(--color-blue-700)}.dark .dark\:text-blue-800{color:var(--color-blue-800)}.dark .dark\:text-blue-900{color:var(--color-blue-900)}.dark .dark\:text-cyan-50{color:var(--color-cyan-50)}.dark .dark\:text-cyan-100{color:var(--color-cyan-100)}.dark .dark\:text-cyan-200{color:var(--color-cyan-200)}.dark .dark\:text-cyan-300{color:var(--color-cyan-300)}.dark .dark\:text-cyan-400{color:var(--color-cyan-400)}.dark .dark\:text-cyan-500{color:var(--color-cyan-500)}.dark .dark\:text-cyan-600{color:var(--color-cyan-600)}.dark .dark\:text-cyan-700{color:var(--color-cyan-700)}.dark .dark\:text-cyan-800{color:var(--color-cyan-800)}.dark .dark\:text-cyan-900{color:var(--color-cyan-900)}.dark .dark\:text-emerald-50{color:var(--color-emerald-50)}.dark .dark\:text-emerald-100{color:var(--color-emerald-100)}.dark .dark\:text-emerald-200{color:var(--color-emerald-200)}.dark .dark\:text-emerald-300{color:var(--color-emerald-300)}.dark .dark\:text-emerald-400{color:var(--color-emerald-400)}.dark .dark\:text-emerald-500{color:var(--color-emerald-500)}.dark .dark\:text-emerald-600{color:var(--color-emerald-600)}.dark .dark\:text-emerald-700{color:var(--color-emerald-700)}.dark .dark\:text-emerald-800{color:var(--color-emerald-800)}.dark .dark\:text-emerald-900{color:var(--color-emerald-900)}.dark .dark\:text-gray-50{color:var(--color-gray-50)}.dark .dark\:text-gray-100{color:var(--color-gray-100)}.dark .dark\:text-gray-200{color:var(--color-gray-200)}.dark .dark\:text-gray-300{color:var(--color-gray-300)}.dark .dark\:text-gray-400{color:var(--color-gray-400)}.dark .dark\:text-gray-500{color:var(--color-gray-500)}.dark .dark\:text-gray-600{color:var(--color-gray-600)}.dark .dark\:text-gray-700{color:var(--color-gray-700)}.dark .dark\:text-gray-800{color:var(--color-gray-800)}.dark .dark\:text-gray-900{color:var(--color-gray-900)}.dark .dark\:text-green-50{color:var(--color-green-50)}.dark .dark\:text-green-100{color:var(--color-green-100)}.dark .dark\:text-green-200{color:var(--color-green-200)}.dark .dark\:text-green-300{color:var(--color-green-300)}.dark .dark\:text-green-400{color:var(--color-green-400)}.dark .dark\:text-green-500{color:var(--color-green-500)}.dark .dark\:text-green-600{color:var(--color-green-600)}.dark .dark\:text-green-700{color:var(--color-green-700)}.dark .dark\:text-green-800{color:var(--color-green-800)}.dark .dark\:text-green-900{color:var(--color-green-900)}.dark .dark\:text-indigo-50{color:var(--color-indigo-50)}.dark .dark\:text-indigo-100{color:var(--color-indigo-100)}.dark .dark\:text-indigo-200{color:var(--color-indigo-200)}.dark .dark\:text-indigo-300{color:var(--color-indigo-300)}.dark .dark\:text-indigo-400{color:var(--color-indigo-400)}.dark .dark\:text-indigo-500{color:var(--color-indigo-500)}.dark .dark\:text-indigo-600{color:var(--color-indigo-600)}.dark .dark\:text-indigo-700{color:var(--color-indigo-700)}.dark .dark\:text-indigo-800{color:var(--color-indigo-800)}.dark .dark\:text-indigo-900{color:var(--color-indigo-900)}.dark .dark\:text-orange-50{color:var(--color-orange-50)}.dark .dark\:text-orange-100{color:var(--color-orange-100)}.dark .dark\:text-orange-200{color:var(--color-orange-200)}.dark .dark\:text-orange-300{color:var(--color-orange-300)}.dark .dark\:text-orange-400{color:var(--color-orange-400)}.dark .dark\:text-orange-500{color:var(--color-orange-500)}.dark .dark\:text-orange-600{color:var(--color-orange-600)}.dark .dark\:text-orange-700{color:var(--color-orange-700)}.dark .dark\:text-orange-800{color:var(--color-orange-800)}.dark .dark\:text-orange-900{color:var(--color-orange-900)}.dark .dark\:text-pink-50{color:var(--color-pink-50)}.dark .dark\:text-pink-100{color:var(--color-pink-100)}.dark .dark\:text-pink-200{color:var(--color-pink-200)}.dark .dark\:text-pink-300{color:var(--color-pink-300)}.dark .dark\:text-pink-400{color:var(--color-pink-400)}.dark .dark\:text-pink-500{color:var(--color-pink-500)}.dark .dark\:text-pink-600{color:var(--color-pink-600)}.dark .dark\:text-pink-700{color:var(--color-pink-700)}.dark .dark\:text-pink-800{color:var(--color-pink-800)}.dark .dark\:text-pink-900{color:var(--color-pink-900)}.dark .dark\:text-purple-50{color:var(--color-purple-50)}.dark .dark\:text-purple-100{color:var(--color-purple-100)}.dark .dark\:text-purple-200{color:var(--color-purple-200)}.dark .dark\:text-purple-300{color:var(--color-purple-300)}.dark .dark\:text-purple-400{color:var(--color-purple-400)}.dark .dark\:text-purple-500{color:var(--color-purple-500)}.dark .dark\:text-purple-600{color:var(--color-purple-600)}.dark .dark\:text-purple-700{color:var(--color-purple-700)}.dark .dark\:text-purple-800{color:var(--color-purple-800)}.dark .dark\:text-purple-900{color:var(--color-purple-900)}.dark .dark\:text-red-50{color:var(--color-red-50)}.dark .dark\:text-red-100{color:var(--color-red-100)}.dark .dark\:text-red-200{color:var(--color-red-200)}.dark .dark\:text-red-300{color:var(--color-red-300)}.dark .dark\:text-red-400{color:var(--color-red-400)}.dark .dark\:text-red-500{color:var(--color-red-500)}.dark .dark\:text-red-600{color:var(--color-red-600)}.dark .dark\:text-red-700{color:var(--color-red-700)}.dark .dark\:text-red-800{color:var(--color-red-800)}.dark .dark\:text-red-900{color:var(--color-red-900)}.dark .dark\:text-slate-50{color:var(--color-slate-50)}.dark .dark\:text-slate-100{color:var(--color-slate-100)}.dark .dark\:text-slate-200{color:var(--color-slate-200)}.dark .dark\:text-slate-300{color:var(--color-slate-300)}.dark .dark\:text-slate-400{color:var(--color-slate-400)}.dark .dark\:text-slate-500{color:var(--color-slate-500)}.dark .dark\:text-slate-600{color:var(--color-slate-600)}.dark .dark\:text-slate-700{color:var(--color-slate-700)}.dark .dark\:text-slate-800{color:var(--color-slate-800)}.dark .dark\:text-slate-900{color:var(--color-slate-900)}.dark .dark\:text-teal-50{color:var(--color-teal-50)}.dark .dark\:text-teal-100{color:var(--color-teal-100)}.dark .dark\:text-teal-200{color:var(--color-teal-200)}.dark .dark\:text-teal-300{color:var(--color-teal-300)}.dark .dark\:text-teal-400{color:var(--color-teal-400)}.dark .dark\:text-teal-500{color:var(--color-teal-500)}.dark .dark\:text-teal-600{color:var(--color-teal-600)}.dark .dark\:text-teal-700{color:var(--color-teal-700)}.dark .dark\:text-teal-800{color:var(--color-teal-800)}.dark .dark\:text-teal-900{color:var(--color-teal-900)}.dark .dark\:text-white{color:var(--color-white)}.dark .dark\:text-yellow-50{color:var(--color-yellow-50)}.dark .dark\:text-yellow-100{color:var(--color-yellow-100)}.dark .dark\:text-yellow-200{color:var(--color-yellow-200)}.dark .dark\:text-yellow-300{color:var(--color-yellow-300)}.dark .dark\:text-yellow-400{color:var(--color-yellow-400)}.dark .dark\:text-yellow-500{color:var(--color-yellow-500)}.dark .dark\:text-yellow-600{color:var(--color-yellow-600)}.dark .dark\:text-yellow-700{color:var(--color-yellow-700)}.dark .dark\:text-yellow-800{color:var(--color-yellow-800)}.dark .dark\:text-yellow-900{color:var(--color-yellow-900)}.dark .dark\:italic{font-style:italic}.dark .dark\:underline{text-decoration-line:underline}.dark .dark\:placeholder-gray-400::placeholder{color:var(--color-gray-400)}.dark .dark\:placeholder-gray-500::placeholder{color:var(--color-gray-500)}.dark .dark\:placeholder-gray-600::placeholder{color:var(--color-gray-600)}.dark .dark\:opacity-0{opacity:0}.dark .dark\:opacity-25{opacity:.25}.dark .dark\:opacity-50{opacity:.5}.dark .dark\:opacity-75{opacity:.75}.dark .dark\:opacity-100{opacity:1}.dark .dark\:shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.dark .dark\:shadow-2xl{--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.dark .dark\:shadow-inner{--tw-shadow:inset 0 2px 4px 0 var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.dark .dark\:shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.dark .dark\:shadow-md{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a), 0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.dark .dark\:shadow-none{--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.dark .dark\:shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.dark .dark\:shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a), 0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.dark .dark\:shadow-purple-900\/50{--tw-shadow-color:#59168b80}@supports (color:color-mix(in lab,red,red)){.dark .dark\:shadow-purple-900\/50{--tw-shadow-color:color-mix(in oklab, color-mix(in oklab, var(--color-purple-900) 50%, transparent) var(--tw-shadow-alpha), transparent)}}.dark .dark\:ring-blue-50{--tw-ring-color:var(--color-blue-50)}.dark .dark\:ring-blue-100{--tw-ring-color:var(--color-blue-100)}.dark .dark\:ring-blue-200{--tw-ring-color:var(--color-blue-200)}.dark .dark\:ring-blue-300{--tw-ring-color:var(--color-blue-300)}.dark .dark\:ring-blue-400{--tw-ring-color:var(--color-blue-400)}.dark .dark\:ring-blue-500{--tw-ring-color:var(--color-blue-500)}.dark .dark\:ring-blue-600{--tw-ring-color:var(--color-blue-600)}.dark .dark\:ring-blue-700{--tw-ring-color:var(--color-blue-700)}.dark .dark\:ring-blue-800{--tw-ring-color:var(--color-blue-800)}.dark .dark\:ring-blue-900{--tw-ring-color:var(--color-blue-900)}.dark .dark\:ring-gray-50{--tw-ring-color:var(--color-gray-50)}.dark .dark\:ring-gray-100{--tw-ring-color:var(--color-gray-100)}.dark .dark\:ring-gray-200{--tw-ring-color:var(--color-gray-200)}.dark .dark\:ring-gray-300{--tw-ring-color:var(--color-gray-300)}.dark .dark\:ring-gray-400{--tw-ring-color:var(--color-gray-400)}.dark .dark\:ring-gray-500{--tw-ring-color:var(--color-gray-500)}.dark .dark\:ring-gray-600{--tw-ring-color:var(--color-gray-600)}.dark .dark\:ring-gray-700{--tw-ring-color:var(--color-gray-700)}.dark .dark\:ring-gray-800{--tw-ring-color:var(--color-gray-800)}.dark .dark\:ring-gray-900{--tw-ring-color:var(--color-gray-900)}.dark .dark\:ring-offset-gray-800{--tw-ring-offset-color:var(--color-gray-800)}.dark .dark\:ring-offset-gray-900{--tw-ring-offset-color:var(--color-gray-900)}@media(hover:hover){.dark .dark\:group-hover\:bg-gray-700:is(:where(.group):hover *){background-color:var(--color-gray-700)}.dark .dark\:group-hover\:bg-gray-800:is(:where(.group):hover *){background-color:var(--color-gray-800)}.dark .dark\:group-hover\:text-gray-100:is(:where(.group):hover *){color:var(--color-gray-100)}.dark .dark\:group-hover\:text-white:is(:where(.group):hover *){color:var(--color-white)}}.dark .dark\:selection\:bg-blue-800 ::selection{background-color:var(--color-blue-800)}.dark .dark\:selection\:bg-blue-800::selection{background-color:var(--color-blue-800)}.dark .dark\:selection\:text-blue-100 ::selection{color:var(--color-blue-100)}.dark .dark\:selection\:text-blue-100::selection{color:var(--color-blue-100)}.dark .dark\:odd\:bg-gray-800:nth-child(odd){background-color:var(--color-gray-800)}.dark .dark\:odd\:bg-gray-900:nth-child(odd){background-color:var(--color-gray-900)}.dark .dark\:even\:bg-gray-800:nth-child(2n){background-color:var(--color-gray-800)}.dark .dark\:even\:bg-gray-900:nth-child(2n){background-color:var(--color-gray-900)}@media(hover:hover){.dark .dark\:hover\:border-amber-50:hover{border-color:var(--color-amber-50)}.dark .dark\:hover\:border-amber-100:hover{border-color:var(--color-amber-100)}.dark .dark\:hover\:border-amber-200:hover{border-color:var(--color-amber-200)}.dark .dark\:hover\:border-amber-300:hover{border-color:var(--color-amber-300)}.dark .dark\:hover\:border-amber-400:hover{border-color:var(--color-amber-400)}.dark .dark\:hover\:border-amber-500:hover{border-color:var(--color-amber-500)}.dark .dark\:hover\:border-amber-600:hover{border-color:var(--color-amber-600)}.dark .dark\:hover\:border-amber-700:hover{border-color:var(--color-amber-700)}.dark .dark\:hover\:border-amber-800:hover{border-color:var(--color-amber-800)}.dark .dark\:hover\:border-amber-900:hover{border-color:var(--color-amber-900)}.dark .dark\:hover\:border-blue-50:hover{border-color:var(--color-blue-50)}.dark .dark\:hover\:border-blue-100:hover{border-color:var(--color-blue-100)}.dark .dark\:hover\:border-blue-200:hover{border-color:var(--color-blue-200)}.dark .dark\:hover\:border-blue-300:hover{border-color:var(--color-blue-300)}.dark .dark\:hover\:border-blue-400:hover{border-color:var(--color-blue-400)}.dark .dark\:hover\:border-blue-500:hover{border-color:var(--color-blue-500)}.dark .dark\:hover\:border-blue-600:hover{border-color:var(--color-blue-600)}.dark .dark\:hover\:border-blue-700:hover{border-color:var(--color-blue-700)}.dark .dark\:hover\:border-blue-800:hover{border-color:var(--color-blue-800)}.dark .dark\:hover\:border-blue-900:hover{border-color:var(--color-blue-900)}.dark .dark\:hover\:border-gray-50:hover{border-color:var(--color-gray-50)}.dark .dark\:hover\:border-gray-100:hover{border-color:var(--color-gray-100)}.dark .dark\:hover\:border-gray-200:hover{border-color:var(--color-gray-200)}.dark .dark\:hover\:border-gray-300:hover{border-color:var(--color-gray-300)}.dark .dark\:hover\:border-gray-400:hover{border-color:var(--color-gray-400)}.dark .dark\:hover\:border-gray-500:hover{border-color:var(--color-gray-500)}.dark .dark\:hover\:border-gray-600:hover{border-color:var(--color-gray-600)}.dark .dark\:hover\:border-gray-700:hover{border-color:var(--color-gray-700)}.dark .dark\:hover\:border-gray-800:hover{border-color:var(--color-gray-800)}.dark .dark\:hover\:border-gray-900:hover{border-color:var(--color-gray-900)}.dark .dark\:hover\:bg-amber-50:hover{background-color:var(--color-amber-50)}.dark .dark\:hover\:bg-amber-100:hover{background-color:var(--color-amber-100)}.dark .dark\:hover\:bg-amber-200:hover{background-color:var(--color-amber-200)}.dark .dark\:hover\:bg-amber-300:hover{background-color:var(--color-amber-300)}.dark .dark\:hover\:bg-amber-400:hover{background-color:var(--color-amber-400)}.dark .dark\:hover\:bg-amber-500:hover{background-color:var(--color-amber-500)}.dark .dark\:hover\:bg-amber-600:hover{background-color:var(--color-amber-600)}.dark .dark\:hover\:bg-amber-700:hover{background-color:var(--color-amber-700)}.dark .dark\:hover\:bg-amber-800:hover{background-color:var(--color-amber-800)}.dark .dark\:hover\:bg-amber-900:hover{background-color:var(--color-amber-900)}.dark .dark\:hover\:bg-blue-50:hover{background-color:var(--color-blue-50)}.dark .dark\:hover\:bg-blue-100:hover{background-color:var(--color-blue-100)}.dark .dark\:hover\:bg-blue-200:hover{background-color:var(--color-blue-200)}.dark .dark\:hover\:bg-blue-300:hover{background-color:var(--color-blue-300)}.dark .dark\:hover\:bg-blue-400:hover{background-color:var(--color-blue-400)}.dark .dark\:hover\:bg-blue-500:hover{background-color:var(--color-blue-500)}.dark .dark\:hover\:bg-blue-600:hover{background-color:var(--color-blue-600)}.dark .dark\:hover\:bg-blue-700:hover{background-color:var(--color-blue-700)}.dark .dark\:hover\:bg-blue-800:hover{background-color:var(--color-blue-800)}.dark .dark\:hover\:bg-blue-900:hover{background-color:var(--color-blue-900)}.dark .dark\:hover\:bg-blue-900\/30:hover{background-color:#1c398e4d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:hover\:bg-blue-900\/30:hover{background-color:color-mix(in oklab,var(--color-blue-900) 30%,transparent)}}.dark .dark\:hover\:bg-blue-900\/50:hover{background-color:#1c398e80}@supports (color:color-mix(in lab,red,red)){.dark .dark\:hover\:bg-blue-900\/50:hover{background-color:color-mix(in oklab,var(--color-blue-900) 50%,transparent)}}.dark .dark\:hover\:bg-gray-50:hover{background-color:var(--color-gray-50)}.dark .dark\:hover\:bg-gray-100:hover{background-color:var(--color-gray-100)}.dark .dark\:hover\:bg-gray-200:hover{background-color:var(--color-gray-200)}.dark .dark\:hover\:bg-gray-300:hover{background-color:var(--color-gray-300)}.dark .dark\:hover\:bg-gray-400:hover{background-color:var(--color-gray-400)}.dark .dark\:hover\:bg-gray-500:hover{background-color:var(--color-gray-500)}.dark .dark\:hover\:bg-gray-600:hover{background-color:var(--color-gray-600)}.dark .dark\:hover\:bg-gray-700:hover{background-color:var(--color-gray-700)}.dark .dark\:hover\:bg-gray-700\/30:hover{background-color:#3641534d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:hover\:bg-gray-700\/30:hover{background-color:color-mix(in oklab,var(--color-gray-700) 30%,transparent)}}.dark .dark\:hover\:bg-gray-700\/50:hover{background-color:#36415380}@supports (color:color-mix(in lab,red,red)){.dark .dark\:hover\:bg-gray-700\/50:hover{background-color:color-mix(in oklab,var(--color-gray-700) 50%,transparent)}}.dark .dark\:hover\:bg-gray-800:hover{background-color:var(--color-gray-800)}.dark .dark\:hover\:bg-gray-800\/50:hover{background-color:#1e293980}@supports (color:color-mix(in lab,red,red)){.dark .dark\:hover\:bg-gray-800\/50:hover{background-color:color-mix(in oklab,var(--color-gray-800) 50%,transparent)}}.dark .dark\:hover\:bg-gray-900:hover{background-color:var(--color-gray-900)}.dark .dark\:hover\:bg-green-50:hover{background-color:var(--color-green-50)}.dark .dark\:hover\:bg-green-100:hover{background-color:var(--color-green-100)}.dark .dark\:hover\:bg-green-200:hover{background-color:var(--color-green-200)}.dark .dark\:hover\:bg-green-300:hover{background-color:var(--color-green-300)}.dark .dark\:hover\:bg-green-400:hover{background-color:var(--color-green-400)}.dark .dark\:hover\:bg-green-500:hover{background-color:var(--color-green-500)}.dark .dark\:hover\:bg-green-600:hover{background-color:var(--color-green-600)}.dark .dark\:hover\:bg-green-700:hover{background-color:var(--color-green-700)}.dark .dark\:hover\:bg-green-800:hover{background-color:var(--color-green-800)}.dark .dark\:hover\:bg-green-900:hover{background-color:var(--color-green-900)}.dark .dark\:hover\:bg-green-900\/30:hover{background-color:#0d542b4d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:hover\:bg-green-900\/30:hover{background-color:color-mix(in oklab,var(--color-green-900) 30%,transparent)}}.dark .dark\:hover\:bg-indigo-50:hover{background-color:var(--color-indigo-50)}.dark .dark\:hover\:bg-indigo-100:hover{background-color:var(--color-indigo-100)}.dark .dark\:hover\:bg-indigo-200:hover{background-color:var(--color-indigo-200)}.dark .dark\:hover\:bg-indigo-300:hover{background-color:var(--color-indigo-300)}.dark .dark\:hover\:bg-indigo-400:hover{background-color:var(--color-indigo-400)}.dark .dark\:hover\:bg-indigo-500:hover{background-color:var(--color-indigo-500)}.dark .dark\:hover\:bg-indigo-600:hover{background-color:var(--color-indigo-600)}.dark .dark\:hover\:bg-indigo-700:hover{background-color:var(--color-indigo-700)}.dark .dark\:hover\:bg-indigo-800:hover{background-color:var(--color-indigo-800)}.dark .dark\:hover\:bg-indigo-900:hover{background-color:var(--color-indigo-900)}.dark .dark\:hover\:bg-indigo-900\/30:hover{background-color:#312c854d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:hover\:bg-indigo-900\/30:hover{background-color:color-mix(in oklab,var(--color-indigo-900) 30%,transparent)}}.dark .dark\:hover\:bg-orange-50:hover{background-color:var(--color-orange-50)}.dark .dark\:hover\:bg-orange-100:hover{background-color:var(--color-orange-100)}.dark .dark\:hover\:bg-orange-200:hover{background-color:var(--color-orange-200)}.dark .dark\:hover\:bg-orange-300:hover{background-color:var(--color-orange-300)}.dark .dark\:hover\:bg-orange-400:hover{background-color:var(--color-orange-400)}.dark .dark\:hover\:bg-orange-500:hover{background-color:var(--color-orange-500)}.dark .dark\:hover\:bg-orange-600:hover{background-color:var(--color-orange-600)}.dark .dark\:hover\:bg-orange-700:hover{background-color:var(--color-orange-700)}.dark .dark\:hover\:bg-orange-800:hover{background-color:var(--color-orange-800)}.dark .dark\:hover\:bg-orange-900:hover{background-color:var(--color-orange-900)}.dark .dark\:hover\:bg-orange-900\/30:hover{background-color:#7e2a0c4d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:hover\:bg-orange-900\/30:hover{background-color:color-mix(in oklab,var(--color-orange-900) 30%,transparent)}}.dark .dark\:hover\:bg-pink-50:hover{background-color:var(--color-pink-50)}.dark .dark\:hover\:bg-pink-100:hover{background-color:var(--color-pink-100)}.dark .dark\:hover\:bg-pink-200:hover{background-color:var(--color-pink-200)}.dark .dark\:hover\:bg-pink-300:hover{background-color:var(--color-pink-300)}.dark .dark\:hover\:bg-pink-400:hover{background-color:var(--color-pink-400)}.dark .dark\:hover\:bg-pink-500:hover{background-color:var(--color-pink-500)}.dark .dark\:hover\:bg-pink-600:hover{background-color:var(--color-pink-600)}.dark .dark\:hover\:bg-pink-700:hover{background-color:var(--color-pink-700)}.dark .dark\:hover\:bg-pink-800:hover{background-color:var(--color-pink-800)}.dark .dark\:hover\:bg-pink-900:hover{background-color:var(--color-pink-900)}.dark .dark\:hover\:bg-purple-50:hover{background-color:var(--color-purple-50)}.dark .dark\:hover\:bg-purple-100:hover{background-color:var(--color-purple-100)}.dark .dark\:hover\:bg-purple-200:hover{background-color:var(--color-purple-200)}.dark .dark\:hover\:bg-purple-300:hover{background-color:var(--color-purple-300)}.dark .dark\:hover\:bg-purple-400:hover{background-color:var(--color-purple-400)}.dark .dark\:hover\:bg-purple-500:hover{background-color:var(--color-purple-500)}.dark .dark\:hover\:bg-purple-600:hover{background-color:var(--color-purple-600)}.dark .dark\:hover\:bg-purple-700:hover{background-color:var(--color-purple-700)}.dark .dark\:hover\:bg-purple-800:hover{background-color:var(--color-purple-800)}.dark .dark\:hover\:bg-purple-900:hover{background-color:var(--color-purple-900)}.dark .dark\:hover\:bg-purple-900\/30:hover{background-color:#59168b4d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:hover\:bg-purple-900\/30:hover{background-color:color-mix(in oklab,var(--color-purple-900) 30%,transparent)}}.dark .dark\:hover\:bg-red-50:hover{background-color:var(--color-red-50)}.dark .dark\:hover\:bg-red-100:hover{background-color:var(--color-red-100)}.dark .dark\:hover\:bg-red-200:hover{background-color:var(--color-red-200)}.dark .dark\:hover\:bg-red-300:hover{background-color:var(--color-red-300)}.dark .dark\:hover\:bg-red-400:hover{background-color:var(--color-red-400)}.dark .dark\:hover\:bg-red-500:hover{background-color:var(--color-red-500)}.dark .dark\:hover\:bg-red-600:hover{background-color:var(--color-red-600)}.dark .dark\:hover\:bg-red-700:hover{background-color:var(--color-red-700)}.dark .dark\:hover\:bg-red-800:hover{background-color:var(--color-red-800)}.dark .dark\:hover\:bg-red-900:hover{background-color:var(--color-red-900)}.dark .dark\:hover\:bg-red-900\/20:hover{background-color:#82181a33}@supports (color:color-mix(in lab,red,red)){.dark .dark\:hover\:bg-red-900\/20:hover{background-color:color-mix(in oklab,var(--color-red-900) 20%,transparent)}}.dark .dark\:hover\:bg-red-900\/30:hover{background-color:#82181a4d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:hover\:bg-red-900\/30:hover{background-color:color-mix(in oklab,var(--color-red-900) 30%,transparent)}}.dark .dark\:hover\:bg-slate-50:hover{background-color:var(--color-slate-50)}.dark .dark\:hover\:bg-slate-100:hover{background-color:var(--color-slate-100)}.dark .dark\:hover\:bg-slate-200:hover{background-color:var(--color-slate-200)}.dark .dark\:hover\:bg-slate-300:hover{background-color:var(--color-slate-300)}.dark .dark\:hover\:bg-slate-400:hover{background-color:var(--color-slate-400)}.dark .dark\:hover\:bg-slate-500:hover{background-color:var(--color-slate-500)}.dark .dark\:hover\:bg-slate-600:hover{background-color:var(--color-slate-600)}.dark .dark\:hover\:bg-slate-700:hover{background-color:var(--color-slate-700)}.dark .dark\:hover\:bg-slate-800:hover{background-color:var(--color-slate-800)}.dark .dark\:hover\:bg-slate-900:hover{background-color:var(--color-slate-900)}.dark .dark\:hover\:bg-white\/10:hover{background-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.dark .dark\:hover\:bg-white\/10:hover{background-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.dark .dark\:hover\:bg-yellow-500:hover{background-color:var(--color-yellow-500)}.dark .dark\:hover\:bg-yellow-800:hover{background-color:var(--color-yellow-800)}.dark .dark\:hover\:bg-yellow-900\/30:hover{background-color:#733e0a4d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:hover\:bg-yellow-900\/30:hover{background-color:color-mix(in oklab,var(--color-yellow-900) 30%,transparent)}}.dark .dark\:hover\:text-black:hover{color:var(--color-black)}.dark .dark\:hover\:text-blue-50:hover{color:var(--color-blue-50)}.dark .dark\:hover\:text-blue-100:hover{color:var(--color-blue-100)}.dark .dark\:hover\:text-blue-200:hover{color:var(--color-blue-200)}.dark .dark\:hover\:text-blue-300:hover{color:var(--color-blue-300)}.dark .dark\:hover\:text-blue-400:hover{color:var(--color-blue-400)}.dark .dark\:hover\:text-blue-500:hover{color:var(--color-blue-500)}.dark .dark\:hover\:text-blue-600:hover{color:var(--color-blue-600)}.dark .dark\:hover\:text-blue-700:hover{color:var(--color-blue-700)}.dark .dark\:hover\:text-blue-800:hover{color:var(--color-blue-800)}.dark .dark\:hover\:text-blue-900:hover{color:var(--color-blue-900)}.dark .dark\:hover\:text-gray-50:hover{color:var(--color-gray-50)}.dark .dark\:hover\:text-gray-100:hover{color:var(--color-gray-100)}.dark .dark\:hover\:text-gray-200:hover{color:var(--color-gray-200)}.dark .dark\:hover\:text-gray-300:hover{color:var(--color-gray-300)}.dark .dark\:hover\:text-gray-400:hover{color:var(--color-gray-400)}.dark .dark\:hover\:text-gray-500:hover{color:var(--color-gray-500)}.dark .dark\:hover\:text-gray-600:hover{color:var(--color-gray-600)}.dark .dark\:hover\:text-gray-700:hover{color:var(--color-gray-700)}.dark .dark\:hover\:text-gray-800:hover{color:var(--color-gray-800)}.dark .dark\:hover\:text-gray-900:hover{color:var(--color-gray-900)}.dark .dark\:hover\:text-green-50:hover{color:var(--color-green-50)}.dark .dark\:hover\:text-green-100:hover{color:var(--color-green-100)}.dark .dark\:hover\:text-green-200:hover{color:var(--color-green-200)}.dark .dark\:hover\:text-green-300:hover{color:var(--color-green-300)}.dark .dark\:hover\:text-green-400:hover{color:var(--color-green-400)}.dark .dark\:hover\:text-green-500:hover{color:var(--color-green-500)}.dark .dark\:hover\:text-green-600:hover{color:var(--color-green-600)}.dark .dark\:hover\:text-green-700:hover{color:var(--color-green-700)}.dark .dark\:hover\:text-green-800:hover{color:var(--color-green-800)}.dark .dark\:hover\:text-green-900:hover{color:var(--color-green-900)}.dark .dark\:hover\:text-purple-200:hover{color:var(--color-purple-200)}.dark .dark\:hover\:text-purple-300:hover{color:var(--color-purple-300)}.dark .dark\:hover\:text-red-50:hover{color:var(--color-red-50)}.dark .dark\:hover\:text-red-100:hover{color:var(--color-red-100)}.dark .dark\:hover\:text-red-200:hover{color:var(--color-red-200)}.dark .dark\:hover\:text-red-300:hover{color:var(--color-red-300)}.dark .dark\:hover\:text-red-400:hover{color:var(--color-red-400)}.dark .dark\:hover\:text-red-500:hover{color:var(--color-red-500)}.dark .dark\:hover\:text-red-600:hover{color:var(--color-red-600)}.dark .dark\:hover\:text-red-700:hover{color:var(--color-red-700)}.dark .dark\:hover\:text-red-800:hover{color:var(--color-red-800)}.dark .dark\:hover\:text-red-900:hover{color:var(--color-red-900)}.dark .dark\:hover\:text-slate-800:hover{color:var(--color-slate-800)}.dark .dark\:hover\:text-white:hover{color:var(--color-white)}}.dark .dark\:focus\:border-blue-400:focus{border-color:var(--color-blue-400)}.dark .dark\:focus\:border-gray-500:focus{border-color:var(--color-gray-500)}.dark .dark\:focus\:border-gray-600:focus{border-color:var(--color-gray-600)}.dark .dark\:focus\:border-gray-700:focus{border-color:var(--color-gray-700)}.dark .dark\:focus\:bg-gray-700:focus{background-color:var(--color-gray-700)}.dark .dark\:focus\:bg-gray-800:focus{background-color:var(--color-gray-800)}.dark .dark\:focus\:bg-gray-900:focus{background-color:var(--color-gray-900)}.dark .dark\:focus\:text-gray-100:focus{color:var(--color-gray-100)}.dark .dark\:focus\:text-gray-200:focus{color:var(--color-gray-200)}.dark .dark\:focus\:text-gray-300:focus{color:var(--color-gray-300)}.dark .dark\:focus\:text-white:focus{color:var(--color-white)}.dark .dark\:focus\:ring-amber-400:focus{--tw-ring-color:var(--color-amber-400)}.dark .dark\:focus\:ring-amber-500:focus{--tw-ring-color:var(--color-amber-500)}.dark .dark\:focus\:ring-amber-600:focus{--tw-ring-color:var(--color-amber-600)}.dark .dark\:focus\:ring-blue-400:focus{--tw-ring-color:var(--color-blue-400)}.dark .dark\:focus\:ring-blue-500:focus{--tw-ring-color:var(--color-blue-500)}.dark .dark\:focus\:ring-blue-600:focus{--tw-ring-color:var(--color-blue-600)}.dark .dark\:focus\:ring-blue-700:focus{--tw-ring-color:var(--color-blue-700)}.dark .dark\:focus\:ring-gray-400:focus{--tw-ring-color:var(--color-gray-400)}.dark .dark\:focus\:ring-gray-500:focus{--tw-ring-color:var(--color-gray-500)}.dark .dark\:focus\:ring-gray-600:focus{--tw-ring-color:var(--color-gray-600)}.dark .dark\:focus\:ring-gray-700:focus{--tw-ring-color:var(--color-gray-700)}.dark .dark\:focus\:ring-indigo-400:focus{--tw-ring-color:var(--color-indigo-400)}.dark .dark\:focus\:ring-red-400:focus{--tw-ring-color:var(--color-red-400)}.dark .dark\:focus\:ring-red-500:focus{--tw-ring-color:var(--color-red-500)}.dark .dark\:focus\:ring-slate-500:focus{--tw-ring-color:var(--color-slate-500)}.dark .dark\:focus\:ring-offset-gray-800:focus{--tw-ring-offset-color:var(--color-gray-800)}.dark .dark\:focus\:ring-offset-gray-900:focus{--tw-ring-offset-color:var(--color-gray-900)}.dark .dark\:active\:bg-gray-600:active{background-color:var(--color-gray-600)}.dark .dark\:active\:bg-gray-700:active{background-color:var(--color-gray-700)}.dark .dark\:active\:bg-gray-800:active{background-color:var(--color-gray-800)}.dark .dark\:disabled\:bg-gray-700:disabled{background-color:var(--color-gray-700)}.dark .dark\:disabled\:bg-gray-800:disabled{background-color:var(--color-gray-800)}.dark .dark\:disabled\:bg-gray-900:disabled{background-color:var(--color-gray-900)}.dark .dark\:disabled\:text-gray-500:disabled{color:var(--color-gray-500)}.dark .dark\:disabled\:text-gray-600:disabled{color:var(--color-gray-600)}@media(hover:hover){.dark .dark\:disabled\:hover\:bg-indigo-500:disabled:hover{background-color:var(--color-indigo-500)}}.\[\&_table\]\:w-full table{width:100%}.\[\&_table\]\:border-collapse table{border-collapse:collapse}.\[\&_table\]\:border table{border-style:var(--tw-border-style);border-width:1px}.\[\&_table\]\:border-gray-300 table{border-color:var(--color-gray-300)}.dark .dark\:\[\&_table\]\:border-gray-600 table{border-color:var(--color-gray-600)}.\[\&_td\]\:border td{border-style:var(--tw-border-style);border-width:1px}.\[\&_td\]\:border-gray-300 td{border-color:var(--color-gray-300)}.\[\&_td\]\:px-4 td{padding-inline:calc(var(--spacing) * 4)}.\[\&_td\]\:py-2 td{padding-block:calc(var(--spacing) * 2)}.dark .dark\:\[\&_td\]\:border-gray-600 td{border-color:var(--color-gray-600)}.\[\&_th\]\:border th{border-style:var(--tw-border-style);border-width:1px}.\[\&_th\]\:border-gray-300 th{border-color:var(--color-gray-300)}.\[\&_th\]\:bg-gray-100 th{background-color:var(--color-gray-100)}.\[\&_th\]\:px-4 th{padding-inline:calc(var(--spacing) * 4)}.\[\&_th\]\:py-2 th{padding-block:calc(var(--spacing) * 2)}.\[\&_th\]\:text-left th{text-align:left}.dark .dark\:\[\&_th\]\:border-gray-600 th{border-color:var(--color-gray-600)}.dark .dark\:\[\&_th\]\:bg-gray-800 th{background-color:var(--color-gray-800)}.\[\&_tr\:hover\]\:bg-gray-50 tr:hover{background-color:var(--color-gray-50)}.dark .dark\:\[\&_tr\:hover\]\:bg-gray-700\/50 tr:hover{background-color:#36415380}@supports (color:color-mix(in lab,red,red)){.dark .dark\:\[\&_tr\:hover\]\:bg-gray-700\/50 tr:hover{background-color:color-mix(in oklab,var(--color-gray-700) 50%,transparent)}}}.dashboard-content{max-width:80rem;margin:0 auto}.dashboard-stats-grid{gap:var(--spacing-5);margin-bottom:var(--spacing-8);grid-template-columns:1fr;display:grid}@media(min-width:768px){.dashboard-stats-grid{grid-template-columns:repeat(2,1fr)}}@media(min-width:1024px){.dashboard-stats-grid{grid-template-columns:repeat(4,1fr)}}.dashboard-main-grid{gap:var(--spacing-6);grid-template-columns:1fr;display:grid}@media(min-width:1024px){.dashboard-main-grid{grid-template-columns:repeat(5,1fr)}}.dashboard-column-left{gap:var(--spacing-6);flex-direction:column;display:flex}@media(min-width:1024px){.dashboard-column-left{grid-column:span 3}}.dashboard-column-right{gap:var(--spacing-6);flex-direction:column;display:flex}@media(min-width:1024px){.dashboard-column-right{grid-column:span 2}}.progress-bar-container{background-color:var(--color-slate-100);border-radius:var(--radius-full);width:100%;height:.5rem;overflow:hidden}.dark .progress-bar-container{background-color:var(--color-slate-700)}.progress-bar-fill{border-radius:var(--radius-full);height:.5rem;transition:width var(--transition-normal) var(--ease-in-out)}.progress-bar-emerald{background-color:var(--color-emerald-500)}.progress-bar-blue{background-color:var(--color-blue-500)}.progress-bar-slate{background-color:var(--color-slate-600)}.dark .progress-bar-slate{background-color:var(--color-slate-500)}.module-list{gap:var(--spacing-3);flex-direction:column;display:flex}.module-item{padding:var(--spacing-2) 0;justify-content:space-between;align-items:center;display:flex}.module-info{flex-direction:column;display:flex}.module-name{font-size:var(--text-sm);font-weight:var(--font-medium);color:var(--color-slate-900)}.dark .module-name{color:#fff}.module-version{font-size:var(--text-xs);color:var(--color-slate-500)}.dark .module-version{color:var(--color-slate-400)}.module-badge-active{padding:var(--spacing-1) var(--spacing-2);font-size:var(--text-xs);font-weight:var(--font-medium);background-color:var(--color-green-100);color:var(--color-green-700);border-radius:var(--radius-full)}.dark .module-badge-active{background-color:var(--color-green-900);color:var(--color-green-400)}.module-badge-inactive{padding:var(--spacing-1) var(--spacing-2);font-size:var(--text-xs);font-weight:var(--font-medium);background-color:var(--color-slate-100);color:var(--color-slate-600);border-radius:var(--radius-full)}.dark .module-badge-inactive{background-color:var(--color-slate-700);color:var(--color-slate-400)}.activity-list{gap:var(--spacing-4);flex-direction:column;display:flex}.activity-item{align-items:flex-start;gap:var(--spacing-3);display:flex}.activity-dot{width:.5rem;height:.5rem;margin-top:var(--spacing-2);border-radius:var(--radius-full);background-color:var(--color-blue-500);flex-shrink:0}.activity-content{flex:1;min-width:0}.activity-title{font-size:var(--text-sm);font-weight:var(--font-medium);color:var(--color-slate-900)}.dark .activity-title{color:#fff}.activity-desc{font-size:var(--text-sm);color:var(--color-slate-500)}.dark .activity-desc{color:var(--color-slate-400)}.activity-time{font-size:var(--text-xs);color:var(--color-slate-400);white-space:nowrap}.dark .activity-time{color:var(--color-slate-500)}.alert-list{gap:var(--spacing-3);flex-direction:column;display:flex}.alert-item{align-items:flex-start;gap:var(--spacing-3);padding:var(--spacing-3);border-radius:var(--radius-lg);display:flex}.alert-bg-info{background-color:var(--color-blue-50)}.dark .alert-bg-info{background-color:var(--color-slate-800)}.alert-bg-warning{background-color:var(--color-yellow-50)}.dark .alert-bg-warning{background-color:var(--color-slate-800)}.alert-bg-error{background-color:var(--color-red-50)}.dark .alert-bg-error{background-color:var(--color-slate-800)}.alert-bg-success{background-color:var(--color-green-50)}.dark .alert-bg-success{background-color:var(--color-slate-800)}.alert-dot{width:.5rem;height:.5rem;margin-top:var(--spacing-2);border-radius:var(--radius-full);flex-shrink:0}.alert-dot-info{background-color:var(--color-blue-500)}.alert-dot-warning{background-color:var(--color-yellow-500)}.alert-dot-error{background-color:var(--color-red-500)}.alert-dot-success{background-color:var(--color-green-500)}.alert-content{flex:1}.alert-title{font-size:var(--text-sm);font-weight:var(--font-medium);color:var(--color-slate-900)}.dark .alert-title{color:#fff}.alert-time{font-size:var(--text-xs);color:var(--color-slate-500);margin-top:var(--spacing-1)}.dark .alert-time{color:var(--color-slate-400)}.text-subtitle{font-size:var(--text-sm);color:var(--color-slate-500)}.dark .text-subtitle{color:var(--color-slate-400)}.section-margin{margin-bottom:var(--spacing-6)}.flex-between{justify-content:space-between;align-items:center;display:flex}.flex-between-mb{margin-bottom:var(--spacing-2);justify-content:space-between;align-items:center;display:flex}.resource-label{font-size:var(--text-sm);font-weight:var(--font-medium);color:var(--color-slate-700)}.dark .resource-label{color:var(--color-slate-300)}.resource-value{font-size:var(--text-sm);font-weight:var(--font-medium);color:var(--color-slate-700)}.dark .resource-value{color:var(--color-slate-300)}.icon-stat{width:1rem;height:1rem}.icon-trend{width:.875rem;height:.875rem}@media(max-width:640px){.input-split{flex-wrap:wrap}.input-split input{flex:1;min-width:0}.page-header{flex-direction:column;align-items:flex-start;gap:1rem}.page-actions{justify-content:flex-start;width:100%}}@media(min-width:640px){.responsive-grid{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(min-width:1024px){.responsive-grid,.grid-cols-1-lg-3{grid-template-columns:repeat(3,minmax(0,1fr))}.col-span-1-lg{grid-column:span 1/span 1}.col-span-2-lg{grid-column:span 2/span 2}}@media(max-width:480px){.responsive-grid{grid-template-columns:1fr!important}}@media(max-width:768px){.table-mobile-cards{display:block}.table-mobile-cards>*+*{margin-top:1rem}.table-desktop{display:none!important}}@media(max-width:640px){.modal{max-width:calc(100% - 2rem);max-height:calc(100vh - 2rem);margin-left:1rem;margin-right:1rem;overflow-y:auto}.modal-overlay{align-items:flex-start;padding:2rem 1rem 1rem}.modal-footer{flex-direction:column;gap:.5rem}.modal-footer .btn,.card-account{width:100%}}.sidebar-scrollbar{scrollbar-width:thin;scrollbar-color:transparent transparent;transition:scrollbar-color .3s}.sidebar-scrollbar:hover{scrollbar-color:#d1d5db transparent}.dark .sidebar-scrollbar:hover{scrollbar-color:#4b5563 transparent}.sidebar-scrollbar::-webkit-scrollbar{width:6px}.sidebar-scrollbar::-webkit-scrollbar-track{background:0 0}.sidebar-scrollbar::-webkit-scrollbar-thumb{background:0 0;border-radius:3px;transition:background .3s}.sidebar-scrollbar:hover::-webkit-scrollbar-thumb{background:#d1d5db}.dark .sidebar-scrollbar:hover::-webkit-scrollbar-thumb{background:#4b5563}.sidebar-scrollbar::-webkit-scrollbar-thumb:hover{background:#9ca3af}.dark .sidebar-scrollbar::-webkit-scrollbar-thumb:hover{background:#6b7280}@property --tw-border-spacing-x{syntax:"";inherits:false;initial-value:0}@property --tw-border-spacing-y{syntax:"";inherits:false;initial-value:0}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-pan-x{syntax:"*";inherits:false}@property --tw-pan-y{syntax:"*";inherits:false}@property --tw-pinch-zoom{syntax:"*";inherits:false}@property --tw-scroll-snap-strictness{syntax:"*";inherits:false;initial-value:proximity}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-divide-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-divide-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-gradient-position{syntax:"*";inherits:false}@property --tw-gradient-from{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-via{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-to{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-stops{syntax:"*";inherits:false}@property --tw-gradient-via-stops{syntax:"*";inherits:false}@property --tw-gradient-from-position{syntax:"";inherits:false;initial-value:0%}@property --tw-gradient-via-position{syntax:"";inherits:false;initial-value:50%}@property --tw-gradient-to-position{syntax:"";inherits:false;initial-value:100%}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@property --tw-content{syntax:"*";inherits:false;initial-value:""}@keyframes spin{to{transform:rotate(360deg)}}@keyframes ping{75%,to{opacity:0;transform:scale(2)}}@keyframes pulse{50%{opacity:.5}}@keyframes bounce{0%,to{animation-timing-function:cubic-bezier(.8,0,1,1);transform:translateY(-25%)}50%{animation-timing-function:cubic-bezier(0,0,.2,1);transform:none}}.yarl__fullsize{height:100%;width:100%}.yarl__relative{position:relative}.yarl__portal{inset:0;opacity:0;overflow:hidden;pointer-events:auto;position:fixed;transition:opacity var(--yarl__fade_animation_duration,.25s) var(--yarl__fade_animation_timing_function,ease);z-index:var(--yarl__portal_zindex,9999)}.yarl__portal_open{opacity:1}.yarl__container{background-color:var(--yarl__container_background_color,var(--yarl__color_backdrop,#000));inset:0;outline:none;overflow:hidden;overscroll-behavior:var(--yarl__controller_overscroll_behavior,contain);position:absolute;touch-action:var(--yarl__controller_touch_action,none);-webkit-user-select:none;-moz-user-select:none;user-select:none}.yarl__carousel{align-content:center;align-items:stretch;display:flex;flex:0 0 auto;height:100%;justify-content:center;opacity:var(--yarl__pull_opacity,1);transform:translate(var(--yarl__swipe_offset,0),var(--yarl__pull_offset,0));width:calc(100% + (var(--yarl__carousel_slides_count) - 1)*(100% + var(--yarl__carousel_spacing_px, 0)*1px + var(--yarl__carousel_spacing_percent, 0)*1%))}.yarl__carousel_with_slides{-moz-column-gap:calc(var(--yarl__carousel_spacing_px, 0)*1px + 100/(var(--yarl__carousel_slides_count)*100 + (var(--yarl__carousel_slides_count) - 1)*var(--yarl__carousel_spacing_percent, 0))*var(--yarl__carousel_spacing_percent, 0)*1%);column-gap:calc(var(--yarl__carousel_spacing_px, 0)*1px + 100/(var(--yarl__carousel_slides_count)*100 + (var(--yarl__carousel_slides_count) - 1)*var(--yarl__carousel_spacing_percent, 0))*var(--yarl__carousel_spacing_percent, 0)*1%)}.yarl__flex_center{align-content:center;align-items:center;display:flex;justify-content:center}.yarl__slide{flex:1;overflow:hidden;padding:calc(var(--yarl__carousel_padding_px, 0)*1px + 100/(var(--yarl__carousel_slides_count)*100 + (var(--yarl__carousel_slides_count) - 1)*var(--yarl__carousel_spacing_percent, 0))*var(--yarl__carousel_padding_percent, 0)*1%);position:relative}[dir=rtl] .yarl__slide{--yarl__direction:-1}.yarl__slide_image{max-height:100%;max-width:100%;-o-object-fit:contain;object-fit:contain;touch-action:var(--yarl__controller_touch_action,none);-webkit-user-select:none;-moz-user-select:none;user-select:none;-webkit-touch-callout:none}.yarl__slide_image_cover{height:100%;-o-object-fit:cover;object-fit:cover;width:100%}.yarl__slide_image_loading{opacity:0}@media screen and (min-width:800px){.yarl__slide_wrapper:not(.yarl__slide_wrapper_interactive) .yarl__slide_image{-webkit-backface-visibility:hidden;-webkit-transform:translateZ(0);-webkit-transform-style:preserve-3d}}.yarl__slide_placeholder{left:50%;line-height:0;position:absolute;top:50%;transform:translate(-50%) translateY(-50%)}.yarl__slide_loading{animation:yarl__delayed_fadein 1s linear;color:var(--yarl__slide_icon_loading_color,var(--yarl__color_button,hsla(0,0%,100%,.8)))}.yarl__slide_loading line{animation:yarl__stroke_opacity 1s linear infinite}.yarl__slide_loading line:first-of-type{animation-delay:-1.875s}.yarl__slide_loading line:nth-of-type(2){animation-delay:-1.75s}.yarl__slide_loading line:nth-of-type(3){animation-delay:-1.625s}.yarl__slide_loading line:nth-of-type(4){animation-delay:-1.5s}.yarl__slide_loading line:nth-of-type(5){animation-delay:-1.375s}.yarl__slide_loading line:nth-of-type(6){animation-delay:-1.25s}.yarl__slide_loading line:nth-of-type(7){animation-delay:-1.125s}.yarl__slide_loading line:nth-of-type(8){animation-delay:-1s}.yarl__slide_error{color:var(--yarl__slide_icon_error_color,red);height:var(--yarl__slide_icon_error_size,48px);width:var(--yarl__slide_icon_error_size,48px)}@media(prefers-reduced-motion){.yarl__portal,.yarl__slide{transition:unset}.yarl__slide_loading,.yarl__slide_loading line{animation:unset}}.yarl__toolbar{display:flex;justify-content:flex-end;inset:0 0 auto auto;padding:var(--yarl__toolbar_padding,8px);position:absolute}[dir=rtl] .yarl__toolbar{inset:0 auto auto 0}.yarl__icon{height:var(--yarl__icon_size,32px);width:var(--yarl__icon_size,32px)}.yarl__button{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:var(--yarl__button_background_color,transparent);border:var(--yarl__button_border,0);color:var(--yarl__color_button,hsla(0,0%,100%,.8));cursor:pointer;filter:var(--yarl__button_filter,drop-shadow(2px 2px 2px rgba(0,0,0,.8)));line-height:0;margin:var(--yarl__button_margin,0);outline:none;padding:var(--yarl__button_padding,8px);-webkit-tap-highlight-color:transparent}.yarl__button:focus{color:var(--yarl__color_button_active,#fff)}.yarl__button:focus:not(:focus-visible){color:var(--yarl__color_button,hsla(0,0%,100%,.8))}.yarl__button:focus-visible{color:var(--yarl__color_button_active,#fff)}@media(hover:hover){.yarl__button:focus-visible:hover,.yarl__button:focus:hover,.yarl__button:hover{color:var(--yarl__color_button_active,#fff)}}.yarl__button:disabled{color:var(--yarl__color_button_disabled,hsla(0,0%,100%,.4));cursor:default}.yarl__navigation_next,.yarl__navigation_prev{padding:var(--yarl__navigation_button_padding,24px 16px);position:absolute;top:50%;transform:translateY(-50%)}.yarl__navigation_prev{left:0}[dir=rtl] .yarl__navigation_prev{left:unset;right:0;transform:translateY(-50%) rotate(180deg)}.yarl__navigation_next{right:0}[dir=rtl] .yarl__navigation_next{left:0;right:unset;transform:translateY(-50%) rotate(180deg)}.yarl__no_scroll{height:100%;overflow:hidden;overscroll-behavior:none}@keyframes yarl__delayed_fadein{0%{opacity:0}80%{opacity:0}to{opacity:1}}@keyframes yarl__stroke_opacity{0%{stroke-opacity:1}to{stroke-opacity:.125}}.yarl__counter{bottom:var(--yarl__counter_bottom,unset);color:var(--yarl__counter_color,var(--yarl__color_button,hsla(0,0%,100%,.8)));filter:var(--yarl__counter_filter,drop-shadow(2px 2px 2px rgba(0,0,0,.8)));left:var(--yarl__counter_left,0);line-height:var(--yarl__counter_line_height,var(--yarl__icon_size,32px));margin:var(--yarl__counter_margin,var(--yarl__toolbar_padding,8px));padding:var(--yarl__counter_padding,var(--yarl__button_padding,8px));position:var(--yarl__counter_position,absolute);right:var(--yarl__counter_right,unset);top:var(--yarl__counter_top,0);-webkit-user-select:var(--yarl__counter_user_select,none);-moz-user-select:var(--yarl__counter_user_select,none);user-select:var(--yarl__counter_user_select,none)}[dir=rtl] .yarl__counter{left:var(--yarl__counter_rtl_left,unset);right:var(--yarl__counter_rtl_right,0)}.yarl__thumbnails{display:flex;height:100%}.yarl__thumbnails_bottom,.yarl__thumbnails_end .yarl__thumbnails_track,.yarl__thumbnails_start .yarl__thumbnails_track,.yarl__thumbnails_top{flex-direction:column}.yarl__thumbnails_wrapper{flex:1;position:relative}.yarl__thumbnails_container{background-color:var(--yarl__thumbnails_container_background_color,var(--yarl__color_backdrop,#000));flex:0 0 auto;overflow:hidden;padding:var(--yarl__thumbnails_container_padding,16px);position:relative;-webkit-user-select:none;-moz-user-select:none;user-select:none;-webkit-touch-callout:none}.yarl__thumbnails_vignette{pointer-events:none;position:absolute;--yarl__thumbnails_vignette_size:12%}@media(min-width:1200px){.yarl__thumbnails_vignette{--yarl__thumbnails_vignette_size:8%}}@media(min-width:2000px){.yarl__thumbnails_vignette{--yarl__thumbnails_vignette_size:5%}}.yarl__thumbnails_bottom .yarl__thumbnails_vignette,.yarl__thumbnails_top .yarl__thumbnails_vignette{background:linear-gradient(to right,var(--yarl__color_backdrop,#000) 0,transparent var(--yarl__thumbnails_vignette_size,12%) calc(100% - var(--yarl__thumbnails_vignette_size, 12%)),var(--yarl__color_backdrop,#000) 100%);height:100%;left:0;right:0}.yarl__thumbnails_end .yarl__thumbnails_vignette,.yarl__thumbnails_start .yarl__thumbnails_vignette{background:linear-gradient(to bottom,var(--yarl__color_backdrop,#000) 0,transparent var(--yarl__thumbnails_vignette_size,12%) calc(100% - var(--yarl__thumbnails_vignette_size, 12%)),var(--yarl__color_backdrop,#000) 100%);bottom:0;top:0;width:100%}.yarl__thumbnails_track{gap:var(--yarl__thumbnails_thumbnail_gap,16px);outline:none}.yarl__thumbnails_thumbnail{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:var(--yarl__thumbnails_thumbnail_background,#000);border-color:var(--yarl__thumbnails_thumbnail_border_color,var(--yarl__color_button,hsla(0,0%,100%,.8)));border-radius:var(--yarl__thumbnails_thumbnail_border_radius,4px);border-style:var(--yarl__thumbnails_thumbnail_border_style,solid);border-width:var(--yarl__thumbnails_thumbnail_border,1px);cursor:pointer;flex:0 0 auto;-webkit-tap-highlight-color:transparent;box-sizing:content-box;height:var(--yarl__thumbnails_thumbnail_height,80px);outline:none;overflow:hidden;padding:var(--yarl__thumbnails_thumbnail_padding,4px);position:relative;width:var(--yarl__thumbnails_thumbnail_width,120px)}.yarl__thumbnails_thumbnail_active{border-color:var(--yarl__thumbnails_thumbnail_active_border_color,var(--yarl__color_button_active,#fff))}.yarl__thumbnails_thumbnail_fadein{animation:yarl__thumbnails_thumbnail_fadein var(--yarl__thumbnails_thumbnail_fadein_duration,.5s) ease-in-out var(--yarl__thumbnails_thumbnail_fadein_delay,0s) forwards;opacity:0}.yarl__thumbnails_thumbnail_fadeout{animation:yarl__thumbnails_thumbnail_fadeout var(--yarl__thumbnails_thumbnail_fadeout_duration,.5s) ease-in-out var(--yarl__thumbnails_thumbnail_fadeout_delay,0s) forwards;cursor:unset}.yarl__thumbnails_thumbnail_placeholder{cursor:unset;visibility:hidden}.yarl__thumbnails_thumbnail:focus{box-shadow:var(--yarl__thumbnails_thumbnail_focus_box_shadow,#000 0 0 0 2px,var(--yarl__color_button,hsla(0,0%,100%,.8)) 0 0 0 4px)}.yarl__thumbnails_thumbnail:focus:not(:focus-visible){box-shadow:unset}.yarl__thumbnails_thumbnail:focus-visible{box-shadow:var(--yarl__thumbnails_thumbnail_focus_box_shadow,#000 0 0 0 2px,var(--yarl__color_button,hsla(0,0%,100%,.8)) 0 0 0 4px)}.yarl__thumbnails_thumbnail_icon{color:var(--yarl__thumbnails_thumbnail_icon_color,var(--yarl__color_button,hsla(0,0%,100%,.8)));filter:var(--yarl__thumbnails_thumbnail_icon_filter,drop-shadow(2px 2px 2px rgba(0,0,0,.8)));height:var(--yarl__thumbnails_thumbnail_icon_size,32px);left:50%;position:absolute;top:50%;transform:translate(-50%) translateY(-50%);width:var(--yarl__thumbnails_thumbnail_icon_size,32px)}@keyframes yarl__thumbnails_thumbnail_fadein{0%{opacity:0}to{opacity:1}}@keyframes yarl__thumbnails_thumbnail_fadeout{0%{opacity:1}to{opacity:0}} diff --git a/templates/_bundled/sirsoft-admin_basic/dist/js/components.iife.js b/templates/_bundled/sirsoft-admin_basic/dist/js/components.iife.js index b26e4b11..5c7ab7f0 100644 --- a/templates/_bundled/sirsoft-admin_basic/dist/js/components.iife.js +++ b/templates/_bundled/sirsoft-admin_basic/dist/js/components.iife.js @@ -1,20 +1,19 @@ -var SirsoftAdminBasic=(function(R,h,v,$n){"use strict";var sA=Object.defineProperty;var oA=(R,h,v)=>h in R?sA(R,h,{enumerable:!0,configurable:!0,writable:!0,value:v}):R[h]=v;var Pe=(R,h,v)=>oA(R,typeof h!="symbol"?h+"":h,v);var Va,wr;function fb(e){const t=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(e){for(const n in e)if(n!=="default"){const r=Object.getOwnPropertyDescriptor(e,n);Object.defineProperty(t,n,r.get?r:{enumerable:!0,get:()=>e[n]})}}return t.default=e,Object.freeze(t)}const F=fb(v),hb=()=>window.G7Core,ae=v.forwardRef(({children:e,variant:t="primary",size:n="md",className:r="",...s},o)=>{const i="button",a=hb()?.style?.mergeClasses?.(i,r)??`${i} ${r}`;return h.jsx("button",{ref:o,className:a,...s,children:e})});ae.displayName="Button";const Pd=v.forwardRef(({accept:e,maxSize:t,onChange:n,onError:r,buttonText:s="Browse",placeholder:o="No file selected",className:i="",disabled:a,...l},c)=>{const d=v.useRef(null),u=c||d,[f,g]=v.useState(""),m={},p={};Object.entries(l).forEach(([x,M])=>{x.startsWith("data-editor-")||x==="id"||x==="onMouseMove"||x==="onMouseLeave"?m[x]=M:p[x]=M});const y=v.useCallback(()=>{!a&&u.current&&u.current.click()},[a,u]),b=v.useCallback(x=>{const M=x.target.files?.[0]||null;if(!M){g(""),n?.({target:{value:null,name:""}});return}if(t&&M.size>t*1024*1024){const C=`File size exceeds ${t}MB limit`;r?.(C),u.current&&(u.current.value=""),g("");return}g(M.name),n?.({target:{value:M,name:M.name}})},[t,n,r,u]),_=v.useCallback(x=>{x.stopPropagation(),u.current&&(u.current.value=""),g(""),n?.({target:{value:null,name:""}})},[n,u]);return h.jsxs("div",{className:`flex items-center gap-2 ${i}`,...m,children:[h.jsx("input",{ref:u,type:"file",accept:e,onChange:b,disabled:a,className:"hidden",...p}),h.jsx("button",{type:"button",onClick:y,disabled:a,className:"px-4 py-2 bg-gray-100 dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md text-sm font-medium text-gray-700 dark:text-gray-200 hover:bg-gray-200 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed",children:s}),h.jsx("span",{className:"flex-1 text-sm text-gray-600 dark:text-gray-400 truncate",children:f||o}),f&&!a&&h.jsx("button",{type:"button",onClick:_,className:"p-1 text-gray-400 hover:text-gray-600 dark:hover:text-gray-200","aria-label":"Clear file",children:h.jsx("svg",{className:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:h.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M6 18L18 6M6 6l12 12"})})})]})});Pd.displayName="FileInput";const gb=e=>e==="checkbox"?"checkbox":e==="radio"?"radio":"input",pb=(e,t)=>{const n=gb(e);return t?t.split(/\s+/).filter(Boolean).includes(n)?t:`${n} ${t}`:n},St=v.forwardRef(({label:e,error:t,className:n="",onChange:r,onKeyPress:s,value:o,defaultValue:i,type:a="text",checked:l,defaultChecked:c,...d},u)=>{const f=a==="radio"||a==="checkbox",g=pb(a,n),m=v.useRef(!1),p=v.useRef(null);v.useImperativeHandle(u,()=>p.current);const[y,b]=v.useState(o??i??"");v.useEffect(()=>{if(!f&&!m.current&&o!==void 0){if(p.current&&document.activeElement===p.current)return;b(o)}},[o,f]);const _=v.useCallback(()=>{m.current=!0},[]),x=v.useCallback(E=>{if(m.current=!1,r){const D=E.target.value;b(D);const P=new Event("change",{bubbles:!0});Object.defineProperty(P,"target",{writable:!1,value:{value:D}}),Object.defineProperty(P,"currentTarget",{writable:!1,value:{value:D}}),r(P)}},[r]),M=v.useCallback(E=>{const D=E.target.value;b(D),!m.current&&r&&r(E)},[r]),C=v.useCallback(E=>{m.current||s&&s(E)},[s]),{loadingactions:k,formdata:N,...S}=d;if(f)return h.jsx("input",{ref:p,type:a,className:g,onChange:r,checked:l,defaultChecked:c,value:o,...S});const w=o!==void 0?{value:y}:{defaultValue:i};return h.jsx("input",{ref:p,type:a,className:g,onChange:M,onKeyPress:C,onCompositionStart:_,onCompositionEnd:x,...w,...S})});St.displayName="Input";const ir=({label:e,error:t,className:n="",onChange:r,value:s,defaultValue:o,...i})=>{const a=v.useRef(!1),l=v.useRef(null),[c,d]=v.useState(s??o??"");v.useEffect(()=>{!a.current&&s!==void 0&&d(s)},[s]);const u=v.useCallback(()=>{a.current=!0},[]),f=v.useCallback(M=>{if(a.current=!1,r){const C=M.target.value;d(C);const k=new Event("change",{bubbles:!0});Object.defineProperty(k,"target",{writable:!1,value:{value:C}}),Object.defineProperty(k,"currentTarget",{writable:!1,value:{value:C}}),r(k)}},[r]),g=v.useCallback(M=>{const C=M.target.value;d(C),!a.current&&r&&r(M)},[r]),{loadingactions:m,formdata:p,...y}=i,b=s!==void 0?{value:c}:{defaultValue:o},x=(n??"").split(/\s+/).filter(Boolean).includes("textarea")?n??"":["textarea",n].filter(Boolean).join(" ");return h.jsx("textarea",{ref:l,className:x,onChange:g,onCompositionStart:u,onCompositionEnd:f,...b,...y})},ln=({children:e,className:t="",...n})=>h.jsx("label",{className:t,...n,children:e}),I=v.forwardRef(({children:e,className:t="",...n},r)=>h.jsx("div",{ref:r,className:t,...n,children:e})),Q=({children:e,className:t="",...n})=>h.jsx("span",{className:t,...n,children:e}),vt=({children:e,className:t="",...n})=>h.jsx("p",{className:t,...n,children:e}),_n=({className:e="",alt:t="",...n})=>h.jsx("img",{className:e,alt:t,...n}),Ba=({children:e,className:t="",...n})=>h.jsx("h1",{className:t,...n,children:e}),ar=({children:e,className:t="",...n})=>h.jsx("h2",{className:t,...n,children:e}),Gr=({children:e,className:t="",...n})=>h.jsx("h3",{className:t,...n,children:e}),$d=({children:e,className:t="",...n})=>h.jsx("h4",{className:t,...n,children:e}),ja=({children:e,className:t="",...n})=>h.jsx("ul",{className:t,...n,children:e}),mb=({children:e,className:t="",...n})=>h.jsx("ol",{className:t,...n,children:e}),Wa=({children:e,className:t="",...n})=>h.jsx("li",{className:t,...n,children:e}),Yr=({children:e,className:t="",...n})=>h.jsx("a",{className:t,...n,children:e}),Ga=({children:e,className:t="",...n})=>h.jsx("form",{className:t,...n,children:e}),Zn=({children:e,className:t="",...n})=>h.jsx("svg",{className:t,...n,children:e}),bb=window.G7Core?.createLogger?.("Comp:Select")??{log:(...e)=>console.log("[Comp:Select]",...e),warn:(...e)=>console.warn("[Comp:Select]",...e),error:(...e)=>console.error("[Comp:Select]",...e)};function yb(e){const t=window.G7Core?.state?.get?.("_global.appConfig.localeNames")?.[e];return t||{ko:"한국어",en:"English",ja:"日本語",zh:"中文",es:"Español",fr:"Français",de:"Deutsch"}[e]||e}const Xr=({children:e,label:t,error:n,options:r,className:s="",value:o,onChange:i,disabled:a,searchable:l=!1,searchPlaceholder:c,editorAttrs:d,...u})=>{const[f,g]=v.useState(!1),[m,p]=v.useState(""),[y,b]=v.useState(null),_=v.useRef(null),x=v.useRef(null),M=v.useRef(null),C=v.useRef(null),k=v.useMemo(()=>r?Array.isArray(r)?r.length===0?[]:typeof r[0]=="string"?r.map(T=>({value:T,label:yb(T)})):r:(bb.warn("options prop is not an array:",r),null):null,[r]),N=v.useMemo(()=>k&&k.find(z=>String(z.value)===String(o))?.label||"",[k,o]),S=v.useMemo(()=>{if(!k)return null;if(!l||m.trim()==="")return k;const T=m.trim().toLowerCase();return k.filter(z=>z.label.toLowerCase().includes(T)||String(z.value).toLowerCase().includes(T))},[k,l,m]);v.useEffect(()=>{if(f&&l){p("");const T=setTimeout(()=>M.current?.focus(),0);return()=>clearTimeout(T)}f||p("")},[f,l]);const w=v.useCallback(()=>{if(!x.current)return;const T=x.current.getBoundingClientRect(),z=window.innerHeight-T.bottom,j=z<280&&T.top>z;b(j?{bottom:window.innerHeight-T.top+4,left:T.left,width:T.width}:{top:T.bottom+4,left:T.left,width:T.width})},[]);v.useEffect(()=>{if(!f){b(null);return}return w(),window.addEventListener("scroll",w,!0),window.addEventListener("resize",w),()=>{window.removeEventListener("scroll",w,!0),window.removeEventListener("resize",w)}},[f,w]),v.useEffect(()=>{const T=z=>{const H=z.target;_.current&&!_.current.contains(H)&&(!C.current||!C.current.contains(H))&&g(!1)};return f&&document.addEventListener("mousedown",T),()=>{document.removeEventListener("mousedown",T)}},[f]),v.useEffect(()=>{const T=z=>{z.key==="Escape"&&f&&(g(!1),x.current?.focus())};return document.addEventListener("keydown",T),()=>document.removeEventListener("keydown",T)},[f]);const E=()=>{a||g(!f)},D=T=>{i&&i({target:{value:T},preventDefault:()=>{},stopPropagation:()=>{},type:"change"}),g(!1),x.current?.focus()};if(!k){const z=(s??"").split(/\s+/).filter(Boolean).includes("select")?s??"":["select",s].filter(Boolean).join(" ");return h.jsx("select",{className:z,value:o,onChange:i,disabled:a,...d,...u,children:e})}const P=(s??"").split(/\s+/).filter(T=>T&&!T.includes(":")).join(" "),L=P.includes("bg-"),$=/text-(gray|red|blue|green|yellow|white|black|indigo|purple|pink|orange|amber|emerald|teal|cyan|slate)-\d+|text-white|text-black/.test(P),A=L?`${s}${$?"":" text-gray-700 dark:text-gray-200"}`:`w-full px-4 py-2.5 bg-gray-100 dark:bg-gray-700 border-0 rounded-xl text-sm text-gray-700 dark:text-gray-200 font-medium focus:ring-2 focus:ring-blue-500 focus:outline-none ${s}`,O={...d,...u};return h.jsxs(I,{ref:_,className:"relative",...O,children:[h.jsxs(ae,{ref:x,type:"button",onClick:E,disabled:a,className:`${A} flex items-center justify-between gap-2 text-left cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed`,"aria-haspopup":"listbox","aria-expanded":f,children:[h.jsx(Q,{className:"truncate",children:N||" "}),h.jsx(Zn,{className:`w-4 h-4 flex-shrink-0 transition-transform duration-200 ${f?"rotate-180":""}`,fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:h.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M19 9l-7 7-7-7"})})]}),f&&y&&$n.createPortal(h.jsxs(I,{ref:C,className:"fixed z-[9999] bg-white dark:bg-gray-800 rounded-2xl shadow-lg border border-gray-200 dark:border-gray-600 overflow-hidden",style:{top:y.top,bottom:y.bottom,left:y.left,width:y.width},role:"listbox",children:[l&&h.jsx(I,{className:"p-2 border-b border-gray-200 dark:border-gray-600",children:h.jsx(St,{ref:M,type:"text",value:m,onChange:T=>p(T.target.value),placeholder:c??"Search...",className:"w-full px-3 py-2 bg-gray-100 dark:bg-gray-700 border-0 rounded-lg text-sm text-gray-700 dark:text-gray-200 placeholder-gray-400 dark:placeholder-gray-500 focus:ring-2 focus:ring-blue-500 focus:outline-none",role:"searchbox","aria-label":c??"Search"})}),h.jsxs(I,{className:"py-2 max-h-60 overflow-auto",children:[S&&S.length===0&&h.jsx(I,{className:"px-4 py-3 text-sm text-gray-500 dark:text-gray-400 text-center",children:m?"No results":""}),(S??[]).map(T=>{const z=String(T.value)===String(o);return h.jsxs(ae,{type:"button",onClick:()=>!T.disabled&&D(T.value),disabled:T.disabled,className:`w-full px-4 py-3 text-left text-sm flex items-center justify-between hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors ${z?"text-blue-600 dark:text-blue-400 font-medium":"text-gray-700 dark:text-gray-200"} ${T.disabled?"opacity-50 cursor-not-allowed":"cursor-pointer"}`,role:"option","aria-selected":z,children:[h.jsx(Q,{children:T.label}),z&&h.jsx(Zn,{className:"w-5 h-5 text-blue-600 dark:text-blue-400",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:h.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M5 13l4 4L19 7"})})]},T.value)})]})]}),document.body)]})},vb=({children:e,className:t="",...n})=>h.jsx("option",{className:t,...n,children:e}),xb=({children:e,className:t="",...n})=>h.jsx("optgroup",{className:t,...n,children:e}),En=v.forwardRef(({label:e,className:t="",indeterminate:n=!1,...r},s)=>{const o=v.useRef(null),i=a=>{o.current=a,typeof s=="function"?s(a):s&&(s.current=a)};return v.useEffect(()=>{o.current&&(o.current.indeterminate=n)},[n]),h.jsx("input",{ref:i,type:"checkbox",className:t,...r})});En.displayName="Checkbox";const Ya=({children:e,className:t="",...n})=>h.jsx("table",{className:t,...n,children:e}),Xa=({children:e,className:t="",...n})=>h.jsx("thead",{className:t,...n,children:e}),Ka=({children:e,className:t="",...n})=>h.jsx("tbody",{className:t,...n,children:e}),qa=({children:e,className:t="",...n})=>h.jsx("tfoot",{className:t,...n,children:e}),lr=({children:e,className:t="",...n})=>h.jsx("tr",{className:t,...n,children:e}),Kr=({children:e,className:t="",...n})=>h.jsx("th",{className:t,...n,children:e}),en=({children:e,className:t="",...n})=>h.jsx("td",{className:t,...n,children:e}),Yo=({children:e,className:t="",...n})=>h.jsx("nav",{className:t,...n,children:e}),Fd=({children:e,className:t="",...n})=>h.jsx("section",{className:t,...n,children:e}),oe=({name:e,iconStyle:t="solid",size:n,color:r,spin:s=!1,pulse:o=!1,fixedWidth:i=!1,ariaLabel:a,className:l="",style:c,...d})=>{const u={solid:"fas",regular:"far",light:"fal",duotone:"fad",brands:"fab"},f={"fa-solid":"solid","fa-regular":"regular","fa-light":"light","fa-duotone":"duotone","fa-brands":"brands"},g=typeof e=="string"?e:String(e);let m=t,p=g;for(const[_,x]of Object.entries(f))if(g.startsWith(_+" ")){m=x,p=g.substring(_.length+1);break}p=p.replace(/^fa-/,"");const y=[u[m],`fa-${p}`,n&&`fa-${n}`,s&&"fa-spin",o&&"fa-pulse",i&&"fa-fw",r,l].filter(Boolean).join(" "),b=a||p.replace(/-/g," ");return h.jsx("i",{className:y,style:c,"aria-label":b,role:"img",...d})},wb={xs:"w-3 h-3",sm:"w-4 h-4",md:"w-5 h-5",lg:"w-6 h-6",xl:"w-8 h-8"},zd={xs:"xs",sm:"sm",md:void 0,lg:"lg",xl:"2x"},kb=({icon:e,size:t="md",className:n="",color:r,ariaLabel:s})=>{const o=e.indexOf(":"),i=e.startsWith("fa-")||e.startsWith("fas ")||e.startsWith("far ")||e.startsWith("fab ")||e.startsWith("fal ")||e.startsWith("fad ");let a,l;i?(a="fa",l=e):o>0?(a=e.substring(0,o),l=e.substring(o+1)):(a="fa",l=e);const c=wb[t],d=r||"";switch(a){case"fa":return h.jsx(oe,{name:l,size:zd[t],className:n,color:r,ariaLabel:s});case"svg":return h.jsx("img",{src:l,className:`${c} ${d} ${n}`.trim(),alt:s||"","aria-label":s,role:"img"});case"img":return h.jsx("img",{src:l,className:`${c} ${n}`.trim(),alt:s||"",role:"img"});default:return h.jsx(oe,{name:e,size:zd[t],className:n,color:r,ariaLabel:s})}},Cb=({children:e,className:t="",...n})=>h.jsx("code",{className:t,...n,children:e}),Sb=({children:e,className:t="",...n})=>h.jsx("pre",{className:t,...n,children:e}),_b=({children:e})=>h.jsx(h.Fragment,{children:e}),Eb=({className:e="",...t})=>h.jsx("hr",{className:e,...t}),bt=({className:e="",children:t,style:n,...r})=>h.jsx("i",{className:e,style:n,...r,children:t});var de=(e=>(e.User="fa-user",e.Users="fa-users",e.UserCircle="fa-user-circle",e.UserPlus="fa-user-plus",e.UserMinus="fa-user-minus",e.UserShield="fa-user-shield",e.Search="fa-search",e.Edit="fa-edit",e.Pencil="fa-pencil-alt",e.Trash="fa-trash",e.Save="fa-save",e.Plus="fa-plus",e.Minus="fa-minus",e.Check="fa-check",e.Times="fa-times",e.Download="fa-download",e.Upload="fa-upload",e.Copy="fa-copy",e.ExternalLink="fa-external-link-alt",e.Home="fa-home",e.CaretRight="fa-caret-right",e.CaretDown="fa-caret-down",e.ChevronLeft="fa-chevron-left",e.ChevronRight="fa-chevron-right",e.ChevronUp="fa-chevron-up",e.ChevronDown="fa-chevron-down",e.AngleLeft="fa-angle-left",e.AngleRight="fa-angle-right",e.AngleUp="fa-angle-up",e.AngleDown="fa-angle-down",e.ArrowUp="fa-arrow-up",e.ArrowDown="fa-arrow-down",e.ArrowLeft="fa-arrow-left",e.ArrowRight="fa-arrow-right",e.Bars="fa-bars",e.EllipsisVertical="fa-ellipsis-v",e.EllipsisHorizontal="fa-ellipsis-h",e.CheckCircle="fa-check-circle",e.TimesCircle="fa-times-circle",e.ExclamationCircle="fa-exclamation-circle",e.ExclamationTriangle="fa-exclamation-triangle",e.InfoCircle="fa-info-circle",e.QuestionCircle="fa-question-circle",e.XCircle="fa-times-circle",e.ShoppingCart="fa-shopping-cart",e.ShoppingBag="fa-shopping-bag",e.CreditCard="fa-credit-card",e.Tag="fa-tag",e.Tags="fa-tags",e.File="fa-file",e.FileAlt="fa-file-alt",e.Folder="fa-folder",e.FolderOpen="fa-folder-open",e.Envelope="fa-envelope",e.Phone="fa-phone",e.Comment="fa-comment",e.Comments="fa-comments",e.Cog="fa-cog",e.Cogs="fa-cogs",e.Settings="fa-cog",e.Wrench="fa-wrench",e.Sliders="fa-sliders-h",e.Image="fa-image",e.Images="fa-images",e.Video="fa-video",e.Camera="fa-camera",e.Heart="fa-heart",e.Star="fa-star",e.ThumbsUp="fa-thumbs-up",e.ThumbsDown="fa-thumbs-down",e.Share="fa-share",e.Clock="fa-clock",e.Calendar="fa-calendar",e.CalendarAlt="fa-calendar-alt",e.Lock="fa-lock",e.Unlock="fa-unlock",e.Eye="fa-eye",e.EyeSlash="fa-eye-slash",e.Bell="fa-bell",e.Flag="fa-flag",e.Map="fa-map",e.MapMarker="fa-map-marker-alt",e.Globe="fa-globe",e.Link="fa-link",e.Unlink="fa-unlink",e.Sun="fa-sun",e.Moon="fa-moon",e.ChartBar="fa-chart-bar",e.ChartLine="fa-chart-line",e.ChartPie="fa-chart-pie",e.ChartArea="fa-chart-area",e.Spinner="fa-spinner",e.CircleNotch="fa-circle-notch",e.CheckBadge="fa-badge-check",e.Cube="fa-cube",e.Cubes="fa-cubes",e.Plug="fa-plug",e.PuzzlePiece="fa-puzzle-piece",e.Palette="fa-palette",e))(de||{});const Mb=({title:e,content:t,imageUrl:n,imageAlt:r="",className:s="",onClick:o,style:i,id:a,editorAttrs:l})=>h.jsxs(I,{className:`card-clickable ${s}`,onClick:o,style:i,id:a,...l,children:[n&&h.jsx(I,{className:"w-full",children:h.jsx(_n,{src:n,alt:r,className:"card-image"})}),h.jsxs(I,{className:"p-4",children:[e&&h.jsx(ar,{className:"card-title",children:e}),t&&h.jsx(vt,{className:"card-content",children:t})]})]}),Ps=({items:e,triggerLabel:t="작업",triggerIconName:n=de.EllipsisVertical,position:r="right",className:s="",style:o,children:i,onItemClick:a,id:l,editorAttrs:c})=>{const[d,u]=v.useState(!1),[f,g]=v.useState({top:0,left:0}),m=v.useRef(null),p=v.useRef(null),y=v.useCallback(()=>{if(!m.current)return;const x=m.current.getBoundingClientRect(),M=224;let C=r==="right"?x.right-M:x.left;const k=x.bottom+8;C<8&&(C=8),C+M>window.innerWidth-8&&(C=window.innerWidth-M-8),g({top:k,left:C})},[r]);v.useEffect(()=>{d&&y()},[d,y]),v.useEffect(()=>{if(!d)return;const x=()=>{u(!1)};return window.addEventListener("scroll",x,!0),window.addEventListener("resize",x),()=>{window.removeEventListener("scroll",x,!0),window.removeEventListener("resize",x)}},[d]),v.useEffect(()=>{const x=M=>{const C=M.target,k=m.current?.contains(C),N=p.current?.contains(C);!k&&!N&&u(!1)};return d&&document.addEventListener("mousedown",x),()=>{document.removeEventListener("mousedown",x)}},[d]);const b=x=>{x.disabled||(x.onClick&&x.onClick(),a&&a(x.id,x),u(!1))},_=d?$n.createPortal(h.jsx("div",{ref:p,className:"fixed z-[9999] w-56 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg overflow-hidden",style:{top:f.top,left:f.left},children:e.filter(x=>x.if!==!1).map(x=>{if(x.divider)return h.jsx("div",{className:"border-t border-gray-200 dark:border-gray-600 my-1"},x.id);const M=x.variant==="danger"?"text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/20":"text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700",C=x.disabled?"opacity-50 cursor-not-allowed":"cursor-pointer";return h.jsxs("div",{onClick:()=>b(x),className:`flex items-center gap-3 px-4 py-3 transition-colors ${M} ${C}`,children:[x.iconName&&h.jsx(oe,{name:x.iconName,className:"w-4 h-4"}),h.jsx("span",{className:"text-sm font-medium",children:x.label})]},x.id)})}),document.body):null;return h.jsxs(I,{className:`relative inline-block ${s}`,style:o,id:l,...c,children:[i?h.jsx(I,{ref:m,onClick:()=>u(!d),className:"cursor-pointer",children:i}):h.jsxs(ae,{ref:m,onClick:()=>u(!d),className:"flex items-center gap-2 px-2 py-1 bg-transparent hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors",children:[t&&h.jsx(Q,{className:"text-sm font-medium text-gray-700 dark:text-gray-300",children:t}),n&&h.jsx(oe,{name:n,className:"w-4 h-4 text-gray-500 dark:text-gray-400"})]}),_]})},qr=(e,t)=>window.G7Core?.t?.(e,t)??e,Xo=({currentPage:e,totalPages:t,onPageChange:n,maxVisiblePages:r=5,showFirstLast:s=!0,className:o="",style:i,prevText:a,nextText:l,id:c,editorAttrs:d})=>{const u=v.useMemo(()=>{const p=[];if(t<=r+2)for(let y=1;y<=t;y++)p.push(y);else{const y=Math.floor(r/2);let b=Math.max(2,e-y),_=Math.min(t-1,e+y);e<=y+1?_=r:e>=t-y&&(b=t-r+1),p.push(1),b>2&&p.push("...");for(let x=b;x<=_;x++)p.push(x);_1&&p.push(t)}return p},[e,t,r]),f=p=>{p<1||p>t||p===e||n(p)},g=()=>{e>1&&n(e-1)},m=()=>{ef(1),disabled:e===1,className:"pagination-btn","aria-label":qr("common.first_page"),children:"«"}),h.jsx(ae,{onClick:g,disabled:e===1,className:"pagination-btn","aria-label":qr("common.prev_page"),children:a||h.jsx(h.Fragment,{children:"‹"})}),h.jsx(I,{className:"pagination",children:u.map((p,y)=>{if(p==="...")return h.jsx(Q,{className:"pagination-ellipsis",children:"..."},`ellipsis-${y}`);const b=p,_=b===e;return h.jsx(ae,{onClick:()=>f(b),className:_?"pagination-btn-active":"pagination-btn","aria-label":qr("common.page_n",{n:b}),"aria-current":_?"page":void 0,children:b},b)})}),h.jsx(ae,{onClick:m,disabled:e===t,className:"pagination-btn","aria-label":qr("common.next_page"),children:l||h.jsx(h.Fragment,{children:"›"})}),s&&h.jsx(ae,{onClick:()=>f(t),disabled:e===t,className:"pagination-btn","aria-label":qr("common.last_page"),children:"»"})]})},Vd=(e,t)=>window.G7Core?.t?.(e,t)??e,Nb=()=>window.G7Core?.locale?.supported?.()??["ko","en"],Tb=()=>window.G7Core?.locale?.current?.()??"ko",Hd=e=>{const t=Vd(`common.language_${e}`);return t===`common.language_${e}`?{ko:"한국어",en:"English",ja:"日本語",zh:"中文"}[e]||e.toUpperCase():t},Ob=()=>Nb().map(t=>({code:t,name:Hd(t),nativeName:Hd(t)})),Za=({value:e={},onChange:t,inputType:n="text",layout:r="inline",availableLocales:s,defaultLocale:o,placeholder:i="",required:a=!1,disabled:l=!1,maxLength:c,rows:d=4,className:u="",name:f="multilingual_input",showCodeOnMobile:g=!1,error:m,id:p,editorAttrs:y})=>{const b=!!m,_=v.useMemo(()=>s??Ob(),[s]),x=v.useMemo(()=>o??Tb(),[o]),M=v.useRef(e),[C,k]=v.useState(e),N=v.useRef(JSON.stringify(e));v.useEffect(()=>{const K=JSON.stringify(e);K!==N.current&&(N.current=K,M.current=e,k(e))},[e]);const S=v.useCallback((K,q)=>{const ne={target:{name:f,value:K}};q&&(ne._changedKeys=q),t?.(ne)},[t,f]),[w,E]=v.useState(()=>{if(r==="tabs"||r==="compact"){const ne=_.map(ue=>ue.code);return[x,...ne.filter(ue=>ue!==x)]}const K=Object.keys(e).filter(ne=>e[ne]!==void 0);if(K.length===0)return[x];const q=[];return K.includes(x)&&q.push(x),K.forEach(ne=>{ne!==x&&q.push(ne)}),q}),[D,P]=v.useState(x),L=v.useMemo(()=>_.filter(K=>!w.includes(K.code)),[_,w]),[$,A]=v.useState(!1);v.useEffect(()=>{if(r==="tabs"||r==="compact"){const ue=_.map(le=>le.code),J=[x,...ue.filter(le=>le!==x)],ce=w.join(","),me=J.join(",");ce!==me&&E(J);return}const K=Object.keys(e).filter(ue=>e[ue]!==void 0);if(K.length===0){E([x]);return}const q=[];K.includes(x)&&q.push(x),K.forEach(ue=>{ue!==x&&q.push(ue)});const ne=w.join(","),ee=q.join(",");ne!==ee&&E(q)},[e,x,r,_]);const O=v.useCallback((K,q)=>{const ne={...M.current,[K]:q};M.current=ne,k(ne),S(ne,[K])},[S]),T=v.useCallback(K=>{E(ne=>[...ne,K]),P(K),A(!1);const q={...M.current,[K]:""};M.current=q,k(q),S(q)},[S]),z=v.useCallback(K=>{if(K===x)return;E(ne=>ne.filter(ee=>ee!==K)),D===K&&P(x);const q={...M.current};delete q[K],M.current=q,k(q),S(q)},[D,x,S]),H=v.useCallback(K=>{const q=_.find(ne=>ne.code===K);return q?.nativeName||q?.name||K.toUpperCase()},[_]),j=v.useCallback(K=>!!C[K]?.trim(),[C]),G=v.useCallback(()=>L.length===0?null:h.jsxs(I,{className:"relative",children:[h.jsxs(ae,{type:"button",className:"inline-flex items-center gap-1 px-3 py-1 text-xs font-medium text-blue-600 dark:text-blue-400 bg-blue-50 dark:bg-blue-900/30 hover:bg-blue-100 dark:hover:bg-blue-900/50 rounded-full border border-dashed border-blue-300 dark:border-blue-600 transition-colors",onClick:()=>A(!$),disabled:l,children:[h.jsx(oe,{name:"plus",className:"w-3 h-3"}),h.jsx(Q,{children:Vd("common.add_language")})]}),$&&h.jsx(I,{className:"absolute top-full left-0 mt-1 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg z-10 min-w-[120px]",children:L.map(K=>h.jsxs(ae,{type:"button",className:"w-full flex items-center gap-2 px-3 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 first:rounded-t-lg last:rounded-b-lg transition-colors",onClick:()=>T(K.code),children:[h.jsx(oe,{name:"globe",className:"w-3.5 h-3.5 text-gray-400 dark:text-gray-500"}),h.jsx(Q,{children:K.nativeName||K.name})]},K.code))})]}),[L,$,l,T]),U=v.useCallback((K,q=!1)=>{const ne=b?"input input-error":"input",ee=b?"textarea input-error":"textarea",ue=K===x,J=`${i}${i?" (":""}${H(K)}${i?")":""}`;return h.jsxs(I,{className:"space-y-2",children:[h.jsxs(I,{className:"flex items-center gap-2",children:[h.jsxs(Q,{className:` +var SirsoftAdminBasic=(function(R,h,v,Tn){"use strict";var U5=Object.defineProperty;var B5=(R,h,v)=>h in R?U5(R,h,{enumerable:!0,configurable:!0,writable:!0,value:v}):R[h]=v;var $e=(R,h,v)=>B5(R,typeof h!="symbol"?h+"":h,v);var Aa,fr;function eb(t){const e=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(t){for(const n in t)if(n!=="default"){const r=Object.getOwnPropertyDescriptor(t,n);Object.defineProperty(e,n,r.get?r:{enumerable:!0,get:()=>t[n]})}}return e.default=t,Object.freeze(e)}const F=eb(v),tb=()=>window.G7Core,oe=v.forwardRef(({children:t,variant:e="primary",size:n="md",className:r="",...s},o)=>{const i="button",a=tb()?.style?.mergeClasses?.(i,r)??`${i} ${r}`;return h.jsx("button",{ref:o,className:a,...s,children:t})});oe.displayName="Button";const Ed=v.forwardRef(({accept:t,maxSize:e,onChange:n,onError:r,buttonText:s="Browse",placeholder:o="No file selected",className:i="",disabled:a,...l},c)=>{const d=v.useRef(null),u=c||d,[f,g]=v.useState(""),m={},p={};Object.entries(l).forEach(([x,E])=>{x.startsWith("data-editor-")||x==="id"||x==="onMouseMove"||x==="onMouseLeave"?m[x]=E:p[x]=E});const y=v.useCallback(()=>{!a&&u.current&&u.current.click()},[a,u]),b=v.useCallback(x=>{const E=x.target.files?.[0]||null;if(!E){g(""),n?.({target:{value:null,name:""}});return}if(e&&E.size>e*1024*1024){const C=`File size exceeds ${e}MB limit`;r?.(C),u.current&&(u.current.value=""),g("");return}g(E.name),n?.({target:{value:E,name:E.name}})},[e,n,r,u]),_=v.useCallback(x=>{x.stopPropagation(),u.current&&(u.current.value=""),g(""),n?.({target:{value:null,name:""}})},[n,u]);return h.jsxs("div",{className:`flex items-center gap-2 ${i}`,...m,children:[h.jsx("input",{ref:u,type:"file",accept:t,onChange:b,disabled:a,className:"hidden",...p}),h.jsx("button",{type:"button",onClick:y,disabled:a,className:"px-4 py-2 bg-gray-100 dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md text-sm font-medium text-gray-700 dark:text-gray-200 hover:bg-gray-200 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed",children:s}),h.jsx("span",{className:"flex-1 text-sm text-gray-600 dark:text-gray-400 truncate",children:f||o}),f&&!a&&h.jsx("button",{type:"button",onClick:_,className:"p-1 text-gray-400 hover:text-gray-600 dark:hover:text-gray-200","aria-label":"Clear file",children:h.jsx("svg",{className:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:h.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M6 18L18 6M6 6l12 12"})})})]})});Ed.displayName="FileInput";const nb=t=>t==="checkbox"?"checkbox":t==="radio"?"radio":"input",rb=(t,e)=>{const n=nb(t);return e?e.split(/\s+/).filter(Boolean).includes(n)?e:`${n} ${e}`:n},kt=v.forwardRef(({label:t,error:e,className:n="",onChange:r,onKeyPress:s,value:o,defaultValue:i,type:a="text",checked:l,defaultChecked:c,...d},u)=>{const f=a==="radio"||a==="checkbox",g=rb(a,n),m=v.useRef(!1),p=v.useRef(null);v.useImperativeHandle(u,()=>p.current);const[y,b]=v.useState(o??i??"");v.useEffect(()=>{if(!f&&!m.current&&o!==void 0){if(p.current&&document.activeElement===p.current)return;b(o)}},[o,f]);const _=v.useCallback(()=>{m.current=!0},[]),x=v.useCallback(M=>{if(m.current=!1,r){const T=M.target.value;b(T);const P=new Event("change",{bubbles:!0});Object.defineProperty(P,"target",{writable:!1,value:{value:T}}),Object.defineProperty(P,"currentTarget",{writable:!1,value:{value:T}}),r(P)}},[r]),E=v.useCallback(M=>{const T=M.target.value;b(T),!m.current&&r&&r(M)},[r]),C=v.useCallback(M=>{m.current||s&&s(M)},[s]),{loadingactions:k,formdata:N,...S}=d;if(f)return h.jsx("input",{ref:p,type:a,className:g,onChange:r,checked:l,defaultChecked:c,value:o,...S});const w=o!==void 0?{value:y}:{defaultValue:i};return h.jsx("input",{ref:p,type:a,className:g,onChange:E,onKeyPress:C,onCompositionStart:_,onCompositionEnd:x,...w,...S})});kt.displayName="Input";const Qn=({label:t,error:e,className:n="",onChange:r,value:s,defaultValue:o,...i})=>{const a=v.useRef(!1),l=v.useRef(null),[c,d]=v.useState(s??o??"");v.useEffect(()=>{!a.current&&s!==void 0&&d(s)},[s]);const u=v.useCallback(()=>{a.current=!0},[]),f=v.useCallback(E=>{if(a.current=!1,r){const C=E.target.value;d(C);const k=new Event("change",{bubbles:!0});Object.defineProperty(k,"target",{writable:!1,value:{value:C}}),Object.defineProperty(k,"currentTarget",{writable:!1,value:{value:C}}),r(k)}},[r]),g=v.useCallback(E=>{const C=E.target.value;d(C),!a.current&&r&&r(E)},[r]),{loadingactions:m,formdata:p,...y}=i,b=s!==void 0?{value:c}:{defaultValue:o},x=(n??"").split(/\s+/).filter(Boolean).includes("textarea")?n??"":["textarea",n].filter(Boolean).join(" ");return h.jsx("textarea",{ref:l,className:x,onChange:g,onCompositionStart:u,onCompositionEnd:f,...b,...y})},sn=({children:t,className:e="",...n})=>h.jsx("label",{className:e,...n,children:t}),I=v.forwardRef(({children:t,className:e="",...n},r)=>h.jsx("div",{ref:r,className:e,...n,children:t})),Q=({children:t,className:e="",...n})=>h.jsx("span",{className:e,...n,children:t}),yt=({children:t,className:e="",...n})=>h.jsx("p",{className:e,...n,children:t}),yn=({className:t="",alt:e="",...n})=>h.jsx("img",{className:t,alt:e,...n}),Da=({children:t,className:e="",...n})=>h.jsx("h1",{className:e,...n,children:t}),Jn=({children:t,className:e="",...n})=>h.jsx("h2",{className:e,...n,children:t}),Ir=({children:t,className:e="",...n})=>h.jsx("h3",{className:e,...n,children:t}),Md=({children:t,className:e="",...n})=>h.jsx("h4",{className:e,...n,children:t}),Ia=({children:t,className:e="",...n})=>h.jsx("ul",{className:e,...n,children:t}),sb=({children:t,className:e="",...n})=>h.jsx("ol",{className:e,...n,children:t}),La=({children:t,className:e="",...n})=>h.jsx("li",{className:e,...n,children:t}),Lr=({children:t,className:e="",...n})=>h.jsx("a",{className:e,...n,children:t}),Pa=({children:t,className:e="",...n})=>h.jsx("form",{className:e,...n,children:t}),Un=({children:t,className:e="",...n})=>h.jsx("svg",{className:e,...n,children:t}),ob=window.G7Core?.createLogger?.("Comp:Select")??{log:(...t)=>console.log("[Comp:Select]",...t),warn:(...t)=>console.warn("[Comp:Select]",...t),error:(...t)=>console.error("[Comp:Select]",...t)};function ib(t){const e=window.G7Core?.state?.get?.("_global.appConfig.localeNames")?.[t];return e||{ko:"한국어",en:"English",ja:"日本語",zh:"中文",es:"Español",fr:"Français",de:"Deutsch"}[t]||t}const Pr=({children:t,label:e,error:n,options:r,className:s="",value:o,onChange:i,disabled:a,searchable:l=!1,searchPlaceholder:c,editorAttrs:d,...u})=>{const[f,g]=v.useState(!1),[m,p]=v.useState(""),[y,b]=v.useState(null),_=v.useRef(null),x=v.useRef(null),E=v.useRef(null),C=v.useRef(null),k=v.useMemo(()=>r?Array.isArray(r)?r.length===0?[]:typeof r[0]=="string"?r.map(O=>({value:O,label:ib(O)})):r:(ob.warn("options prop is not an array:",r),null):null,[r]),N=v.useMemo(()=>k&&k.find(z=>String(z.value)===String(o))?.label||"",[k,o]),S=v.useMemo(()=>{if(!k)return null;if(!l||m.trim()==="")return k;const O=m.trim().toLowerCase();return k.filter(z=>z.label.toLowerCase().includes(O)||String(z.value).toLowerCase().includes(O))},[k,l,m]);v.useEffect(()=>{if(f&&l){p("");const O=setTimeout(()=>E.current?.focus(),0);return()=>clearTimeout(O)}f||p("")},[f,l]);const w=v.useCallback(()=>{if(!x.current)return;const O=x.current.getBoundingClientRect(),z=window.innerHeight-O.bottom,j=z<280&&O.top>z;b(j?{bottom:window.innerHeight-O.top+4,left:O.left,width:O.width}:{top:O.bottom+4,left:O.left,width:O.width})},[]);v.useEffect(()=>{if(!f){b(null);return}return w(),window.addEventListener("scroll",w,!0),window.addEventListener("resize",w),()=>{window.removeEventListener("scroll",w,!0),window.removeEventListener("resize",w)}},[f,w]),v.useEffect(()=>{const O=z=>{const H=z.target;_.current&&!_.current.contains(H)&&(!C.current||!C.current.contains(H))&&g(!1)};return f&&document.addEventListener("mousedown",O),()=>{document.removeEventListener("mousedown",O)}},[f]),v.useEffect(()=>{const O=z=>{z.key==="Escape"&&f&&(g(!1),x.current?.focus())};return document.addEventListener("keydown",O),()=>document.removeEventListener("keydown",O)},[f]);const M=()=>{a||g(!f)},T=O=>{i&&i({target:{value:O},preventDefault:()=>{},stopPropagation:()=>{},type:"change"}),g(!1),x.current?.focus()};if(!k){const z=(s??"").split(/\s+/).filter(Boolean).includes("select")?s??"":["select",s].filter(Boolean).join(" ");return h.jsx("select",{className:z,value:o,onChange:i,disabled:a,...d,...u,children:t})}const P=(s??"").split(/\s+/).filter(O=>O&&!O.includes(":")).join(" "),L=P.includes("bg-"),$=/text-(gray|red|blue|green|yellow|white|black|indigo|purple|pink|orange|amber|emerald|teal|cyan|slate)-\d+|text-white|text-black/.test(P),A=L?`${s}${$?"":" text-gray-700 dark:text-gray-200"}`:`w-full px-4 py-2.5 bg-gray-100 dark:bg-gray-700 border-0 rounded-xl text-sm text-gray-700 dark:text-gray-200 font-medium focus:ring-2 focus:ring-blue-500 focus:outline-none ${s}`,D={...d,...u};return h.jsxs(I,{ref:_,className:"relative",...D,children:[h.jsxs(oe,{ref:x,type:"button",onClick:M,disabled:a,className:`${A} flex items-center justify-between gap-2 text-left cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed`,"aria-haspopup":"listbox","aria-expanded":f,children:[h.jsx(Q,{className:"truncate",children:N||" "}),h.jsx(Un,{className:`w-4 h-4 flex-shrink-0 transition-transform duration-200 ${f?"rotate-180":""}`,fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:h.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M19 9l-7 7-7-7"})})]}),f&&y&&Tn.createPortal(h.jsxs(I,{ref:C,className:"fixed z-[9999] bg-white dark:bg-gray-800 rounded-2xl shadow-lg border border-gray-200 dark:border-gray-600 overflow-hidden",style:{top:y.top,bottom:y.bottom,left:y.left,width:y.width},role:"listbox",children:[l&&h.jsx(I,{className:"p-2 border-b border-gray-200 dark:border-gray-600",children:h.jsx(kt,{ref:E,type:"text",value:m,onChange:O=>p(O.target.value),placeholder:c??"Search...",className:"w-full px-3 py-2 bg-gray-100 dark:bg-gray-700 border-0 rounded-lg text-sm text-gray-700 dark:text-gray-200 placeholder-gray-400 dark:placeholder-gray-500 focus:ring-2 focus:ring-blue-500 focus:outline-none",role:"searchbox","aria-label":c??"Search"})}),h.jsxs(I,{className:"py-2 max-h-60 overflow-auto",children:[S&&S.length===0&&h.jsx(I,{className:"px-4 py-3 text-sm text-gray-500 dark:text-gray-400 text-center",children:m?"No results":""}),(S??[]).map(O=>{const z=String(O.value)===String(o);return h.jsxs(oe,{type:"button",onClick:()=>!O.disabled&&T(O.value),disabled:O.disabled,className:`w-full px-4 py-3 text-left text-sm flex items-center justify-between hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors ${z?"text-blue-600 dark:text-blue-400 font-medium":"text-gray-700 dark:text-gray-200"} ${O.disabled?"opacity-50 cursor-not-allowed":"cursor-pointer"}`,role:"option","aria-selected":z,children:[h.jsx(Q,{children:O.label}),z&&h.jsx(Un,{className:"w-5 h-5 text-blue-600 dark:text-blue-400",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:h.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M5 13l4 4L19 7"})})]},O.value)})]})]}),document.body)]})},ab=({children:t,className:e="",...n})=>h.jsx("option",{className:e,...n,children:t}),lb=({children:t,className:e="",...n})=>h.jsx("optgroup",{className:e,...n,children:t}),vn=v.forwardRef(({label:t,className:e="",indeterminate:n=!1,...r},s)=>{const o=v.useRef(null),i=a=>{o.current=a,typeof s=="function"?s(a):s&&(s.current=a)};return v.useEffect(()=>{o.current&&(o.current.indeterminate=n)},[n]),h.jsx("input",{ref:i,type:"checkbox",className:e,...r})});vn.displayName="Checkbox";const $a=({children:t,className:e="",...n})=>h.jsx("table",{className:e,...n,children:t}),Fa=({children:t,className:e="",...n})=>h.jsx("thead",{className:e,...n,children:t}),za=({children:t,className:e="",...n})=>h.jsx("tbody",{className:e,...n,children:t}),Va=({children:t,className:e="",...n})=>h.jsx("tfoot",{className:e,...n,children:t}),Rn=({children:t,className:e="",...n})=>h.jsx("tr",{className:e,...n,children:t}),$r=({children:t,className:e="",...n})=>h.jsx("th",{className:e,...n,children:t}),Zt=({children:t,className:e="",...n})=>h.jsx("td",{className:e,...n,children:t}),Po=({children:t,className:e="",...n})=>h.jsx("nav",{className:e,...n,children:t}),Nd=({children:t,className:e="",...n})=>h.jsx("section",{className:e,...n,children:t}),re=({name:t,iconStyle:e="solid",size:n,color:r,spin:s=!1,pulse:o=!1,fixedWidth:i=!1,ariaLabel:a,className:l="",style:c,...d})=>{const u={solid:"fas",regular:"far",light:"fal",duotone:"fad",brands:"fab"},f={"fa-solid":"solid","fa-regular":"regular","fa-light":"light","fa-duotone":"duotone","fa-brands":"brands"},g=typeof t=="string"?t:String(t);let m=e,p=g;for(const[_,x]of Object.entries(f))if(g.startsWith(_+" ")){m=x,p=g.substring(_.length+1);break}p=p.replace(/^fa-/,"");const y=[u[m],`fa-${p}`,n&&`fa-${n}`,s&&"fa-spin",o&&"fa-pulse",i&&"fa-fw",r,l].filter(Boolean).join(" "),b=a||p.replace(/-/g," ");return h.jsx("i",{className:y,style:c,"aria-label":b,role:"img",...d})},cb={xs:"w-3 h-3",sm:"w-4 h-4",md:"w-5 h-5",lg:"w-6 h-6",xl:"w-8 h-8"},Od={xs:"xs",sm:"sm",md:void 0,lg:"lg",xl:"2x"},db=({icon:t,size:e="md",className:n="",color:r,ariaLabel:s})=>{const o=t.indexOf(":"),i=t.startsWith("fa-")||t.startsWith("fas ")||t.startsWith("far ")||t.startsWith("fab ")||t.startsWith("fal ")||t.startsWith("fad ");let a,l;i?(a="fa",l=t):o>0?(a=t.substring(0,o),l=t.substring(o+1)):(a="fa",l=t);const c=cb[e],d=r||"";switch(a){case"fa":return h.jsx(re,{name:l,size:Od[e],className:n,color:r,ariaLabel:s});case"svg":return h.jsx("img",{src:l,className:`${c} ${d} ${n}`.trim(),alt:s||"","aria-label":s,role:"img"});case"img":return h.jsx("img",{src:l,className:`${c} ${n}`.trim(),alt:s||"",role:"img"});default:return h.jsx(re,{name:t,size:Od[e],className:n,color:r,ariaLabel:s})}},ub=({children:t,className:e="",...n})=>h.jsx("code",{className:e,...n,children:t}),fb=({children:t,className:e="",...n})=>h.jsx("pre",{className:e,...n,children:t}),hb=({children:t})=>h.jsx(h.Fragment,{children:t}),gb=({className:t="",...e})=>h.jsx("hr",{className:t,...e}),mt=({className:t="",children:e,style:n,...r})=>h.jsx("i",{className:t,style:n,...r,children:e});var de=(t=>(t.User="fa-user",t.Users="fa-users",t.UserCircle="fa-user-circle",t.UserPlus="fa-user-plus",t.UserMinus="fa-user-minus",t.UserShield="fa-user-shield",t.Search="fa-search",t.Edit="fa-edit",t.Pencil="fa-pencil-alt",t.Trash="fa-trash",t.Save="fa-save",t.Plus="fa-plus",t.Minus="fa-minus",t.Check="fa-check",t.Times="fa-times",t.Download="fa-download",t.Upload="fa-upload",t.Copy="fa-copy",t.ExternalLink="fa-external-link-alt",t.Home="fa-home",t.CaretRight="fa-caret-right",t.CaretDown="fa-caret-down",t.ChevronLeft="fa-chevron-left",t.ChevronRight="fa-chevron-right",t.ChevronUp="fa-chevron-up",t.ChevronDown="fa-chevron-down",t.AngleLeft="fa-angle-left",t.AngleRight="fa-angle-right",t.AngleUp="fa-angle-up",t.AngleDown="fa-angle-down",t.ArrowUp="fa-arrow-up",t.ArrowDown="fa-arrow-down",t.ArrowLeft="fa-arrow-left",t.ArrowRight="fa-arrow-right",t.Bars="fa-bars",t.EllipsisVertical="fa-ellipsis-v",t.EllipsisHorizontal="fa-ellipsis-h",t.CheckCircle="fa-check-circle",t.TimesCircle="fa-times-circle",t.ExclamationCircle="fa-exclamation-circle",t.ExclamationTriangle="fa-exclamation-triangle",t.InfoCircle="fa-info-circle",t.QuestionCircle="fa-question-circle",t.XCircle="fa-times-circle",t.ShoppingCart="fa-shopping-cart",t.ShoppingBag="fa-shopping-bag",t.CreditCard="fa-credit-card",t.Tag="fa-tag",t.Tags="fa-tags",t.File="fa-file",t.FileAlt="fa-file-alt",t.Folder="fa-folder",t.FolderOpen="fa-folder-open",t.Envelope="fa-envelope",t.Phone="fa-phone",t.Comment="fa-comment",t.Comments="fa-comments",t.Cog="fa-cog",t.Cogs="fa-cogs",t.Settings="fa-cog",t.Wrench="fa-wrench",t.Sliders="fa-sliders-h",t.Image="fa-image",t.Images="fa-images",t.Video="fa-video",t.Camera="fa-camera",t.Heart="fa-heart",t.Star="fa-star",t.ThumbsUp="fa-thumbs-up",t.ThumbsDown="fa-thumbs-down",t.Share="fa-share",t.Clock="fa-clock",t.Calendar="fa-calendar",t.CalendarAlt="fa-calendar-alt",t.Lock="fa-lock",t.Unlock="fa-unlock",t.Eye="fa-eye",t.EyeSlash="fa-eye-slash",t.Bell="fa-bell",t.Flag="fa-flag",t.Map="fa-map",t.MapMarker="fa-map-marker-alt",t.Globe="fa-globe",t.Link="fa-link",t.Unlink="fa-unlink",t.Sun="fa-sun",t.Moon="fa-moon",t.ChartBar="fa-chart-bar",t.ChartLine="fa-chart-line",t.ChartPie="fa-chart-pie",t.ChartArea="fa-chart-area",t.Spinner="fa-spinner",t.CircleNotch="fa-circle-notch",t.CheckBadge="fa-badge-check",t.Cube="fa-cube",t.Cubes="fa-cubes",t.Plug="fa-plug",t.PuzzlePiece="fa-puzzle-piece",t.Palette="fa-palette",t))(de||{});const pb=({title:t,content:e,imageUrl:n,imageAlt:r="",className:s="",onClick:o,style:i,id:a,editorAttrs:l})=>h.jsxs(I,{className:`card-clickable ${s}`,onClick:o,style:i,id:a,...l,children:[n&&h.jsx(I,{className:"w-full",children:h.jsx(yn,{src:n,alt:r,className:"card-image"})}),h.jsxs(I,{className:"p-4",children:[t&&h.jsx(Jn,{className:"card-title",children:t}),e&&h.jsx(yt,{className:"card-content",children:e})]})]}),xs=({items:t,triggerLabel:e="작업",triggerIconName:n=de.EllipsisVertical,position:r="right",className:s="",style:o,children:i,onItemClick:a,id:l,editorAttrs:c})=>{const[d,u]=v.useState(!1),[f,g]=v.useState({top:0,left:0}),m=v.useRef(null),p=v.useRef(null),y=v.useCallback(()=>{if(!m.current)return;const x=m.current.getBoundingClientRect(),E=224;let C=r==="right"?x.right-E:x.left;const k=x.bottom+8;C<8&&(C=8),C+E>window.innerWidth-8&&(C=window.innerWidth-E-8),g({top:k,left:C})},[r]);v.useEffect(()=>{d&&y()},[d,y]),v.useEffect(()=>{if(!d)return;const x=()=>{u(!1)};return window.addEventListener("scroll",x,!0),window.addEventListener("resize",x),()=>{window.removeEventListener("scroll",x,!0),window.removeEventListener("resize",x)}},[d]),v.useEffect(()=>{const x=E=>{const C=E.target,k=m.current?.contains(C),N=p.current?.contains(C);!k&&!N&&u(!1)};return d&&document.addEventListener("mousedown",x),()=>{document.removeEventListener("mousedown",x)}},[d]);const b=x=>{x.disabled||(x.onClick&&x.onClick(),a&&a(x.id,x),u(!1))},_=d?Tn.createPortal(h.jsx("div",{ref:p,className:"fixed z-[9999] w-56 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg overflow-hidden",style:{top:f.top,left:f.left},children:t.filter(x=>x.if!==!1).map(x=>{if(x.divider)return h.jsx("div",{className:"border-t border-gray-200 dark:border-gray-600 my-1"},x.id);const E=x.variant==="danger"?"text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/20":"text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700",C=x.disabled?"opacity-50 cursor-not-allowed":"cursor-pointer";return h.jsxs("div",{onClick:()=>b(x),className:`flex items-center gap-3 px-4 py-3 transition-colors ${E} ${C}`,children:[x.iconName&&h.jsx(re,{name:x.iconName,className:"w-4 h-4"}),h.jsx("span",{className:"text-sm font-medium",children:x.label})]},x.id)})}),document.body):null;return h.jsxs(I,{className:`relative inline-block ${s}`,style:o,id:l,...c,children:[i?h.jsx(I,{ref:m,onClick:()=>u(!d),className:"cursor-pointer",children:i}):h.jsxs(oe,{ref:m,onClick:()=>u(!d),className:"flex items-center gap-2 px-2 py-1 bg-transparent hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors",children:[e&&h.jsx(Q,{className:"text-sm font-medium text-gray-700 dark:text-gray-300",children:e}),n&&h.jsx(re,{name:n,className:"w-4 h-4 text-gray-500 dark:text-gray-400"})]}),_]})},Fr=(t,e)=>window.G7Core?.t?.(t,e)??t,$o=({currentPage:t,totalPages:e,onPageChange:n,maxVisiblePages:r=5,showFirstLast:s=!0,className:o="",style:i,prevText:a,nextText:l,id:c,editorAttrs:d})=>{const u=v.useMemo(()=>{const p=[];if(e<=r+2)for(let y=1;y<=e;y++)p.push(y);else{const y=Math.floor(r/2);let b=Math.max(2,t-y),_=Math.min(e-1,t+y);t<=y+1?_=r:t>=e-y&&(b=e-r+1),p.push(1),b>2&&p.push("...");for(let x=b;x<=_;x++)p.push(x);_1&&p.push(e)}return p},[t,e,r]),f=p=>{p<1||p>e||p===t||n(p)},g=()=>{t>1&&n(t-1)},m=()=>{tf(1),disabled:t===1,className:"pagination-btn","aria-label":Fr("common.first_page"),children:"«"}),h.jsx(oe,{onClick:g,disabled:t===1,className:"pagination-btn","aria-label":Fr("common.prev_page"),children:a||h.jsx(h.Fragment,{children:"‹"})}),h.jsx(I,{className:"pagination",children:u.map((p,y)=>{if(p==="...")return h.jsx(Q,{className:"pagination-ellipsis",children:"..."},`ellipsis-${y}`);const b=p,_=b===t;return h.jsx(oe,{onClick:()=>f(b),className:_?"pagination-btn-active":"pagination-btn","aria-label":Fr("common.page_n",{n:b}),"aria-current":_?"page":void 0,children:b},b)})}),h.jsx(oe,{onClick:m,disabled:t===e,className:"pagination-btn","aria-label":Fr("common.next_page"),children:l||h.jsx(h.Fragment,{children:"›"})}),s&&h.jsx(oe,{onClick:()=>f(e),disabled:t===e,className:"pagination-btn","aria-label":Fr("common.last_page"),children:"»"})]})},Td=(t,e)=>window.G7Core?.t?.(t,e)??t,mb=()=>window.G7Core?.locale?.supported?.()??["ko","en"],bb=()=>window.G7Core?.locale?.current?.()??"ko",Ad=t=>{const e=Td(`common.language_${t}`);return e===`common.language_${t}`?{ko:"한국어",en:"English",ja:"日本語",zh:"中文"}[t]||t.toUpperCase():e},yb=()=>mb().map(e=>({code:e,name:Ad(e),nativeName:Ad(e)})),Ha=({value:t={},onChange:e,inputType:n="text",layout:r="inline",availableLocales:s,defaultLocale:o,placeholder:i="",required:a=!1,disabled:l=!1,maxLength:c,rows:d=4,className:u="",name:f="multilingual_input",showCodeOnMobile:g=!1,error:m,id:p,editorAttrs:y})=>{const b=!!m,_=v.useMemo(()=>s??yb(),[s]),x=v.useMemo(()=>o??bb(),[o]),E=v.useRef(t),[C,k]=v.useState(t),N=v.useRef(JSON.stringify(t));v.useEffect(()=>{const K=JSON.stringify(t);K!==N.current&&(N.current=K,E.current=t,k(t))},[t]);const S=v.useCallback((K,Z)=>{const ee={target:{name:f,value:K}};Z&&(ee._changedKeys=Z),e?.(ee)},[e,f]),[w,M]=v.useState(()=>{if(r==="tabs"||r==="compact"){const ee=_.map(he=>he.code);return[x,...ee.filter(he=>he!==x)]}const K=Object.keys(t).filter(ee=>t[ee]!==void 0);if(K.length===0)return[x];const Z=[];return K.includes(x)&&Z.push(x),K.forEach(ee=>{ee!==x&&Z.push(ee)}),Z}),[T,P]=v.useState(x),L=v.useMemo(()=>_.filter(K=>!w.includes(K.code)),[_,w]),[$,A]=v.useState(!1);v.useEffect(()=>{if(r==="tabs"||r==="compact"){const he=_.map(ae=>ae.code),J=[x,...he.filter(ae=>ae!==x)],le=w.join(","),ue=J.join(",");le!==ue&&M(J);return}const K=Object.keys(t).filter(he=>t[he]!==void 0);if(K.length===0){M([x]);return}const Z=[];K.includes(x)&&Z.push(x),K.forEach(he=>{he!==x&&Z.push(he)});const ee=w.join(","),ne=Z.join(",");ee!==ne&&M(Z)},[t,x,r,_]);const D=v.useCallback((K,Z)=>{const ee={...E.current,[K]:Z};E.current=ee,k(ee),S(ee,[K])},[S]),O=v.useCallback(K=>{M(ee=>[...ee,K]),P(K),A(!1);const Z={...E.current,[K]:""};E.current=Z,k(Z),S(Z)},[S]),z=v.useCallback(K=>{if(K===x)return;M(ee=>ee.filter(ne=>ne!==K)),T===K&&P(x);const Z={...E.current};delete Z[K],E.current=Z,k(Z),S(Z)},[T,x,S]),H=v.useCallback(K=>{const Z=_.find(ee=>ee.code===K);return Z?.nativeName||Z?.name||K.toUpperCase()},[_]),j=v.useCallback(K=>!!C[K]?.trim(),[C]),W=v.useCallback(()=>L.length===0?null:h.jsxs(I,{className:"relative",children:[h.jsxs(oe,{type:"button",className:"inline-flex items-center gap-1 px-3 py-1 text-xs font-medium text-blue-600 dark:text-blue-400 bg-blue-50 dark:bg-blue-900/30 hover:bg-blue-100 dark:hover:bg-blue-900/50 rounded-full border border-dashed border-blue-300 dark:border-blue-600 transition-colors",onClick:()=>A(!$),disabled:l,children:[h.jsx(re,{name:"plus",className:"w-3 h-3"}),h.jsx(Q,{children:Td("common.add_language")})]}),$&&h.jsx(I,{className:"absolute top-full left-0 mt-1 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg z-10 min-w-[120px]",children:L.map(K=>h.jsxs(oe,{type:"button",className:"w-full flex items-center gap-2 px-3 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 first:rounded-t-lg last:rounded-b-lg transition-colors",onClick:()=>O(K.code),children:[h.jsx(re,{name:"globe",className:"w-3.5 h-3.5 text-gray-400 dark:text-gray-500"}),h.jsx(Q,{children:K.nativeName||K.name})]},K.code))})]}),[L,$,l,O]),U=v.useCallback((K,Z=!1)=>{const ee=b?"input input-error":"input",ne=b?"textarea input-error":"textarea",he=K===x,J=`${i}${i?" (":""}${H(K)}${i?")":""}`;return h.jsxs(I,{className:"space-y-2",children:[h.jsxs(I,{className:"flex items-center gap-2",children:[h.jsxs(Q,{className:` inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-xs font-medium - ${ue?"bg-blue-100 text-blue-700 dark:bg-blue-900/50 dark:text-blue-300":"bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-300"} - `,children:[h.jsx(oe,{name:"globe",className:"w-3 h-3"}),H(K),ue&&h.jsx(Q,{className:"text-red-500 dark:text-red-400",children:"*"}),!ue&&h.jsx(ae,{type:"button",className:"ml-0.5 p-0.5 rounded-full hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors",onClick:()=>z(K),disabled:l,children:h.jsx(oe,{name:"xmark",className:"w-3 h-3"})})]}),ue&&q&&G()]}),n==="textarea"?h.jsx(ir,{name:`${f}[${K}]`,value:C[K]||"",onChange:ce=>O(K,ce.target.value),placeholder:J,required:a&&ue,disabled:l,maxLength:c,rows:d,className:`${ee} resize-vertical`}):h.jsx(St,{type:"text",name:`${f}[${K}]`,value:C[K]||"",onChange:ce=>O(K,ce.target.value),placeholder:J,required:a&&ue,disabled:l,maxLength:c,className:ne})]},K)},[f,C,O,i,a,l,c,d,n,H,x,z,G,b]),W=v.useCallback(K=>{const q=b?"input input-error":"input",ne=b?"textarea input-error":"textarea",ee=`${i}${i?" (":""}${H(K)}${i?")":""}`;return h.jsx(I,{children:n==="textarea"?h.jsx(ir,{name:`${f}[${K}]`,value:C[K]||"",onChange:ue=>O(K,ue.target.value),placeholder:ee,required:a&&K===x,disabled:l,maxLength:c,rows:d,className:`${ne} resize-vertical`}):h.jsx(St,{type:"text",name:`${f}[${K}]`,value:C[K]||"",onChange:ue=>O(K,ue.target.value),placeholder:ee,required:a&&K===x,disabled:l,maxLength:c,className:q})},K)},[f,C,O,i,a,l,c,d,n,x,H,b]),Y=()=>h.jsx(I,{className:u,children:h.jsx(I,{className:"space-y-4",children:w.map((K,q)=>U(K,q===0))})}),V=()=>h.jsxs(I,{className:u,children:[h.jsxs(I,{className:"flex flex-wrap items-center gap-2 mb-3",children:[w.map(K=>h.jsxs(ae,{type:"button",className:` + ${he?"bg-blue-100 text-blue-700 dark:bg-blue-900/50 dark:text-blue-300":"bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-300"} + `,children:[h.jsx(re,{name:"globe",className:"w-3 h-3"}),H(K),he&&h.jsx(Q,{className:"text-red-500 dark:text-red-400",children:"*"}),!he&&h.jsx(oe,{type:"button",className:"ml-0.5 p-0.5 rounded-full hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors",onClick:()=>z(K),disabled:l,children:h.jsx(re,{name:"xmark",className:"w-3 h-3"})})]}),he&&Z&&W()]}),n==="textarea"?h.jsx(Qn,{name:`${f}[${K}]`,value:C[K]||"",onChange:le=>D(K,le.target.value),placeholder:J,required:a&&he,disabled:l,maxLength:c,rows:d,className:`${ne} resize-vertical`}):h.jsx(kt,{type:"text",name:`${f}[${K}]`,value:C[K]||"",onChange:le=>D(K,le.target.value),placeholder:J,required:a&&he,disabled:l,maxLength:c,className:ee})]},K)},[f,C,D,i,a,l,c,d,n,H,x,z,W,b]),B=v.useCallback(K=>{const Z=b?"input input-error":"input",ee=b?"textarea input-error":"textarea",ne=`${i}${i?" (":""}${H(K)}${i?")":""}`;return h.jsx(I,{children:n==="textarea"?h.jsx(Qn,{name:`${f}[${K}]`,value:C[K]||"",onChange:he=>D(K,he.target.value),placeholder:ne,required:a&&K===x,disabled:l,maxLength:c,rows:d,className:`${ee} resize-vertical`}):h.jsx(kt,{type:"text",name:`${f}[${K}]`,value:C[K]||"",onChange:he=>D(K,he.target.value),placeholder:ne,required:a&&K===x,disabled:l,maxLength:c,className:Z})},K)},[f,C,D,i,a,l,c,d,n,x,H,b]),G=()=>h.jsx(I,{className:u,children:h.jsx(I,{className:"space-y-4",children:w.map((K,Z)=>U(K,Z===0))})}),V=()=>h.jsxs(I,{className:u,children:[h.jsxs(I,{className:"flex flex-wrap items-center gap-2 mb-3",children:[w.map(K=>h.jsxs(oe,{type:"button",className:` inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-semibold transition-all - ${D===K?K===x?"bg-blue-500 text-white dark:bg-blue-600 dark:text-white shadow-md scale-105":"bg-gray-500 text-white dark:bg-gray-600 dark:text-white shadow-md scale-105":K===x?"bg-blue-50 text-blue-600 dark:bg-blue-900/30 dark:text-blue-400 hover:bg-blue-100 dark:hover:bg-blue-900/50":"bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600"} - `,onClick:()=>P(K),children:[h.jsx(oe,{name:"globe",className:"w-3 h-3"}),g?h.jsxs(h.Fragment,{children:[h.jsx(Q,{className:"hidden md:inline",children:H(K)}),h.jsx(Q,{className:"inline md:hidden",children:K.toUpperCase()})]}):H(K),K===x&&h.jsx(Q,{className:"text-red-500 dark:text-red-400",children:"*"}),j(K)&&D!==K&&h.jsx(oe,{name:"check",className:"w-3 h-3 text-green-500 dark:text-green-400"}),K!==x&&h.jsx(ae,{type:"button",className:"ml-0.5 p-0.5 rounded-full hover:bg-gray-300 dark:hover:bg-gray-500 transition-colors",onClick:q=>{q.stopPropagation(),z(K)},disabled:l,children:h.jsx(oe,{name:"xmark",className:"w-3 h-3"})})]},K)),G()]}),w.map(K=>h.jsx(I,{className:D===K?"block":"hidden",children:W(K)},K))]}),X=()=>{const K=b?"input input-error":"input",q=b?"textarea input-error":"textarea";return h.jsx(I,{className:u,children:h.jsxs(I,{className:"flex items-center gap-1",children:[h.jsx(I,{className:"flex items-center shrink-0",children:w.map(ne=>h.jsxs(ae,{type:"button",className:` + ${T===K?K===x?"bg-blue-500 text-white dark:bg-blue-600 dark:text-white shadow-md scale-105":"bg-gray-500 text-white dark:bg-gray-600 dark:text-white shadow-md scale-105":K===x?"bg-blue-50 text-blue-600 dark:bg-blue-900/30 dark:text-blue-400 hover:bg-blue-100 dark:hover:bg-blue-900/50":"bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600"} + `,onClick:()=>P(K),children:[h.jsx(re,{name:"globe",className:"w-3 h-3"}),g?h.jsxs(h.Fragment,{children:[h.jsx(Q,{className:"hidden md:inline",children:H(K)}),h.jsx(Q,{className:"inline md:hidden",children:K.toUpperCase()})]}):H(K),K===x&&h.jsx(Q,{className:"text-red-500 dark:text-red-400",children:"*"}),j(K)&&T!==K&&h.jsx(re,{name:"check",className:"w-3 h-3 text-green-500 dark:text-green-400"}),K!==x&&h.jsx(oe,{type:"button",className:"ml-0.5 p-0.5 rounded-full hover:bg-gray-300 dark:hover:bg-gray-500 transition-colors",onClick:Z=>{Z.stopPropagation(),z(K)},disabled:l,children:h.jsx(re,{name:"xmark",className:"w-3 h-3"})})]},K)),W()]}),w.map(K=>h.jsx(I,{className:T===K?"block":"hidden",children:B(K)},K))]}),X=()=>{const K=b?"input input-error":"input",Z=b?"textarea input-error":"textarea";return h.jsx(I,{className:u,children:h.jsxs(I,{className:"flex items-center gap-1",children:[h.jsx(I,{className:"flex items-center shrink-0",children:w.map(ee=>h.jsxs(oe,{type:"button",className:` px-2 py-1.5 text-xs font-medium border-y border-l first:rounded-l last:rounded-r last:border-r transition-colors - ${D===ne?"bg-blue-500 text-white border-blue-500 dark:bg-blue-600 dark:border-blue-600":"bg-white text-gray-600 border-gray-200 hover:bg-gray-50 dark:bg-gray-800 dark:text-gray-300 dark:border-gray-600 dark:hover:bg-gray-700"} - `,onClick:()=>P(ne),title:H(ne),children:[ne.toUpperCase(),ne===x&&h.jsx(Q,{className:"text-red-400 ml-0.5",children:"*"})]},ne))}),h.jsx(I,{className:"flex-1 min-w-0",children:n==="textarea"?h.jsx(ir,{name:`${f}[${D}]`,value:C[D]||"",onChange:ne=>O(D,ne.target.value),placeholder:i,required:a&&D===x,disabled:l,maxLength:c,rows:d,className:q}):h.jsx(St,{type:"text",name:`${f}[${D}]`,value:C[D]||"",onChange:ne=>O(D,ne.target.value),placeholder:i,required:a&&D===x,disabled:l,maxLength:c,className:K})})]})})};return h.jsxs(I,{className:l?"opacity-50 cursor-not-allowed":void 0,id:p,...y,children:[r==="compact"?X():r==="tabs"?V():Y(),b&&m&&h.jsx(Q,{className:"text-xs text-red-500 dark:text-red-400 mt-1 block",children:m})]})};Za.displayName="MultilingualInput";const Ab=()=>window.G7Core,Qa=({checked:e,value:t,onChange:n,disabled:r=!1,label:s,description:o,size:i="md",className:a="",name:l,id:c,editorAttrs:d})=>{const[u,f]=v.useState(()=>e??t??!1);v.useEffect(()=>{f(e??t??!1)},[e,t]);const g=_=>{const x=_.target.checked;f(x),n&&!r&&n(_)},p={sm:{track:"w-9 h-5",thumb:"after:h-4 after:w-4"},md:{track:"w-11 h-6",thumb:"after:h-5 after:w-5"},lg:{track:"w-14 h-7",thumb:"after:h-6 after:w-6"}}[i],y=()=>{if(!r){const _=!u;if(f(_),n){const x=Ab();x?.createChangeEvent&&n(x.createChangeEvent({checked:_,name:l}))}}},b=_=>{r||_.key!==" "&&_.key!=="Enter"&&_.key!=="Spacebar"||(_.preventDefault(),y())};return h.jsxs("div",{className:`toggle-container ${a}`,id:c,...d,children:[h.jsxs("div",{className:`toggle-switch-wrapper ${r?"cursor-not-allowed opacity-50":"cursor-pointer"}`,role:"switch","aria-checked":u,"aria-disabled":r||void 0,"aria-label":s??l,tabIndex:r?-1:0,onKeyDown:b,onClick:_=>{_.preventDefault(),_.stopPropagation(),y()},children:[h.jsx("input",{type:"checkbox",id:l,name:l,checked:u,onChange:g,disabled:r,className:"sr-only peer",tabIndex:-1}),h.jsx("div",{className:`toggle-switch-track ${p.track} ${p.thumb}`})]}),s&&h.jsx("span",{className:`toggle-label ${r?"opacity-50 cursor-not-allowed":"cursor-pointer"}`,onClick:y,children:s}),o&&h.jsx("p",{className:"toggle-description",children:o})]})},Ko=({status:e,label:t,showIcon:n=!0,iconName:r,className:s="",style:o,id:i,editorAttrs:a})=>{const l={success:"bg-green-50 dark:bg-green-900/20 text-green-700 dark:text-green-400 border-green-200 dark:border-green-800",warning:"bg-yellow-50 dark:bg-yellow-900/20 text-yellow-700 dark:text-yellow-400 border-yellow-200 dark:border-yellow-800",error:"bg-red-50 dark:bg-red-900/20 text-red-700 dark:text-red-400 border-red-200 dark:border-red-800",info:"bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-400 border-blue-200 dark:border-blue-800",pending:"bg-gray-50 dark:bg-gray-700 text-gray-700 dark:text-gray-300 border-gray-200 dark:border-gray-600",default:"bg-gray-50 dark:bg-gray-700 text-gray-600 dark:text-gray-400 border-gray-200 dark:border-gray-600"},c={success:de.CheckCircle,warning:de.ExclamationTriangle,error:de.XCircle,info:de.InfoCircle,pending:de.Clock,default:de.QuestionCircle},d={success:"성공",warning:"경고",error:"오류",info:"정보",pending:"대기 중",default:"기본"},u=r||c[e],f=t||d[e],g=l[e];return h.jsxs(Q,{className:`inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-xs font-medium border ${g} ${s}`,style:o,id:i,...a,children:[n&&h.jsx(oe,{name:u,className:"w-4 h-4"}),h.jsx(Q,{children:f})]})},Ud={blue:"bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-300",green:"bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300",red:"bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-300",gray:"bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300",yellow:"bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-300",purple:"bg-purple-100 text-purple-800 dark:bg-purple-900/30 dark:text-purple-300",orange:"bg-orange-100 text-orange-800 dark:bg-orange-900/30 dark:text-orange-300",teal:"bg-teal-100 text-teal-800 dark:bg-teal-900/30 dark:text-teal-300",cyan:"bg-cyan-100 text-cyan-800 dark:bg-cyan-900/30 dark:text-cyan-300"},Db={success:"green",warning:"yellow",danger:"red",error:"red",info:"blue",primary:"blue",secondary:"gray",text:"gray",default:"gray"},Bd={sm:"px-1.5 py-0.5 text-[10px]",md:"px-2 py-0.5 text-xs",lg:"px-2.5 py-1 text-sm"},jd=({color:e,variant:t,text:n,size:r="md",className:s="",style:o,children:i,id:a,editorAttrs:l})=>{const c=e||(t?Db[t]:void 0)||"gray",d=Ud[c]||Ud.gray,u=Bd[r]||Bd.md;return h.jsxs(Q,{className:`inline-flex items-center rounded-full font-medium ${u} ${d} ${s}`,style:o,id:a,...l,children:[n,i]})};function Wd(e,t){(t==null||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n2?r-2:0),o=2;o1?n-1:0),s=1;s"u"?null:_t(BigInt.prototype.toString),Jd=typeof Symbol>"u"?null:_t(Symbol.prototype.toString),xt=_t(Object.prototype.hasOwnProperty),Fs=_t(Object.prototype.toString),zt=_t(RegExp.prototype.test),Rr=Yb(TypeError);function _t(e){return function(t){t instanceof RegExp&&(t.lastIndex=0);for(var n=arguments.length,r=new Array(n>1?n-1:0),s=1;s2&&arguments[2]!==void 0?arguments[2]:$s;if(Yd&&Yd(e,null),!Qt(t))return e;let r=t.length;for(;r--;){let s=t[r];if(typeof s=="string"){const o=n(s);o!==s&&(zb(t)||(t[r]=o),s=o)}e[s]=!0}return e}function Xb(e){for(let t=0;t/g),ey=hn(/\${[\w\W]*/g),ty=hn(/^data-[\-\w.\u00B7-\uFFFF]+$/),ny=hn(/^aria-[\-\w]+$/),ru=hn(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),ry=hn(/^(?:\w+script|data):/i),sy=hn(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g),oy=hn(/^html$/i),iy=hn(/^[a-z][.\w]*(-[.\w]+)+$/i),zn={element:1,attribute:2,text:3,cdataSection:4,entityReference:5,entityNode:6,progressingInstruction:7,comment:8,document:9,documentType:10,documentFragment:11,notation:12},ay=function(){return typeof window>"u"?null:window},ly=function(t,n){if(typeof t!="object"||typeof t.createPolicy!="function")return null;let r=null;const s="data-tt-policy-suffix";n&&n.hasAttribute(s)&&(r=n.getAttribute(s));const o="dompurify"+(r?"#"+r:"");try{return t.createPolicy(o,{createHTML(i){return i},createScriptURL(i){return i}})}catch{return console.warn("TrustedTypes policy "+o+" could not be created."),null}},su=function(){return{afterSanitizeAttributes:[],afterSanitizeElements:[],afterSanitizeShadowDOM:[],beforeSanitizeAttributes:[],beforeSanitizeElements:[],beforeSanitizeShadowDOM:[],uponSanitizeAttribute:[],uponSanitizeElement:[],uponSanitizeShadowNode:[]}};function ou(){let e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:ay();const t=se=>ou(se);if(t.version="3.4.8",t.removed=[],!e||!e.document||e.document.nodeType!==zn.document||!e.Element)return t.isSupported=!1,t;let n=e.document;const r=n,s=r.currentScript;e.DocumentFragment;const o=e.HTMLTemplateElement,i=e.Node,a=e.Element,l=e.NodeFilter,c=e.NamedNodeMap;c===void 0&&(e.NamedNodeMap||e.MozNamedAttrMap),e.HTMLFormElement;const d=e.DOMParser,u=e.trustedTypes,f=a.prototype,g=Fn(f,"cloneNode"),m=Fn(f,"remove"),p=Fn(f,"nextSibling"),y=Fn(f,"childNodes"),b=Fn(f,"parentNode"),_=Fn(f,"shadowRoot"),x=Fn(f,"attributes"),M=i&&i.prototype?Fn(i.prototype,"nodeType"):null,C=i&&i.prototype?Fn(i.prototype,"nodeName"):null;if(typeof o=="function"){const se=n.createElement("template");se.content&&se.content.ownerDocument&&(n=se.content.ownerDocument)}let k,N="",S=0;const w=function(B){if(S>0)throw Rr('The configured TRUSTED_TYPES_POLICY.createHTML must not call DOMPurify.sanitize, as that causes infinite recursion. Do not pass a policy whose createHTML wraps DOMPurify as TRUSTED_TYPES_POLICY; see the "DOMPurify and Trusted Types" section of the README.');S++;try{return k.createHTML(B)}finally{S--}},E=n,D=E.implementation,P=E.createNodeIterator,L=E.createDocumentFragment,$=E.getElementsByTagName,A=r.importNode;let O=su();t.isSupported=typeof Gd=="function"&&typeof b=="function"&&D&&D.createHTMLDocument!==void 0;const T=Jb,z=Rb,H=ey,j=ty,G=ny,U=ry,W=sy,Y=iy;let V=ru,X=null;const K=Ve({},[...Rd,...tl,...nl,...rl,...eu]);let q=null;const ne=Ve({},[...tu,...sl,...nu,...qo]);let ee=Object.seal(Zr(null,{tagNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},allowCustomizedBuiltInElements:{writable:!0,configurable:!1,enumerable:!0,value:!1}})),ue=null,J=null;const ce=Object.seal(Zr(null,{tagCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeCheck:{writable:!0,configurable:!1,enumerable:!0,value:null}}));let me=!0,le=!0,ve=!1,ge=!0,pe=!1,ke=!0,$e=!1,ie=!1,Me=!1,Ae=!1,Ie=!1,Xe=!1,gt=!0,pt=!1;const Be="user-content-";let et=!0,je=!1,re={},fe=null;const ye=Ve({},["annotation-xml","audio","colgroup","desc","foreignobject","head","iframe","math","mi","mn","mo","ms","mtext","noembed","noframes","noscript","plaintext","script","style","svg","template","thead","title","video","xmp"]);let we=null;const xe=Ve({},["audio","video","img","source","image","track"]);let He=null;const _e=Ve({},["alt","class","for","id","label","name","pattern","placeholder","role","summary","title","value","style","xmlns"]),Le="http://www.w3.org/1998/Math/MathML",Ce="http://www.w3.org/2000/svg",We="http://www.w3.org/1999/xhtml";let Ze=We,Fe=!1,Ne=null;const ze=Ve({},[Le,Ce,We],el);let Ke=Ve({},["mi","mo","mn","ms","mtext"]),tt=Ve({},["annotation-xml"]);const at=Ve({},["title","style","font","a","script"]);let ct=null;const wn=["application/xhtml+xml","text/html"],yt="text/html";let Ue=null,Ct=null;const Ut=n.createElement("form"),st=function(B){return B instanceof RegExp||B instanceof Function},kn=function(){let B=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{};if(Ct&&Ct===B)return;(!B||typeof B!="object")&&(B={}),B=Bt(B),ct=wn.indexOf(B.PARSER_MEDIA_TYPE)===-1?yt:B.PARSER_MEDIA_TYPE,Ue=ct==="application/xhtml+xml"?el:$s,X=xt(B,"ALLOWED_TAGS")&&Qt(B.ALLOWED_TAGS)?Ve({},B.ALLOWED_TAGS,Ue):K,q=xt(B,"ALLOWED_ATTR")&&Qt(B.ALLOWED_ATTR)?Ve({},B.ALLOWED_ATTR,Ue):ne,Ne=xt(B,"ALLOWED_NAMESPACES")&&Qt(B.ALLOWED_NAMESPACES)?Ve({},B.ALLOWED_NAMESPACES,el):ze,He=xt(B,"ADD_URI_SAFE_ATTR")&&Qt(B.ADD_URI_SAFE_ATTR)?Ve(Bt(_e),B.ADD_URI_SAFE_ATTR,Ue):_e,we=xt(B,"ADD_DATA_URI_TAGS")&&Qt(B.ADD_DATA_URI_TAGS)?Ve(Bt(xe),B.ADD_DATA_URI_TAGS,Ue):xe,fe=xt(B,"FORBID_CONTENTS")&&Qt(B.FORBID_CONTENTS)?Ve({},B.FORBID_CONTENTS,Ue):ye,ue=xt(B,"FORBID_TAGS")&&Qt(B.FORBID_TAGS)?Ve({},B.FORBID_TAGS,Ue):Bt({}),J=xt(B,"FORBID_ATTR")&&Qt(B.FORBID_ATTR)?Ve({},B.FORBID_ATTR,Ue):Bt({}),re=xt(B,"USE_PROFILES")?B.USE_PROFILES&&typeof B.USE_PROFILES=="object"?Bt(B.USE_PROFILES):B.USE_PROFILES:!1,me=B.ALLOW_ARIA_ATTR!==!1,le=B.ALLOW_DATA_ATTR!==!1,ve=B.ALLOW_UNKNOWN_PROTOCOLS||!1,ge=B.ALLOW_SELF_CLOSE_IN_ATTR!==!1,pe=B.SAFE_FOR_TEMPLATES||!1,ke=B.SAFE_FOR_XML!==!1,$e=B.WHOLE_DOCUMENT||!1,Ae=B.RETURN_DOM||!1,Ie=B.RETURN_DOM_FRAGMENT||!1,Xe=B.RETURN_TRUSTED_TYPE||!1,Me=B.FORCE_BODY||!1,gt=B.SANITIZE_DOM!==!1,pt=B.SANITIZE_NAMED_PROPS||!1,et=B.KEEP_CONTENT!==!1,je=B.IN_PLACE||!1,V=qb(B.ALLOWED_URI_REGEXP)?B.ALLOWED_URI_REGEXP:ru,Ze=typeof B.NAMESPACE=="string"?B.NAMESPACE:We,Ke=xt(B,"MATHML_TEXT_INTEGRATION_POINTS")&&B.MATHML_TEXT_INTEGRATION_POINTS&&typeof B.MATHML_TEXT_INTEGRATION_POINTS=="object"?Bt(B.MATHML_TEXT_INTEGRATION_POINTS):Ve({},["mi","mo","mn","ms","mtext"]),tt=xt(B,"HTML_INTEGRATION_POINTS")&&B.HTML_INTEGRATION_POINTS&&typeof B.HTML_INTEGRATION_POINTS=="object"?Bt(B.HTML_INTEGRATION_POINTS):Ve({},["annotation-xml"]);const te=xt(B,"CUSTOM_ELEMENT_HANDLING")&&B.CUSTOM_ELEMENT_HANDLING&&typeof B.CUSTOM_ELEMENT_HANDLING=="object"?Bt(B.CUSTOM_ELEMENT_HANDLING):Zr(null);if(ee=Zr(null),xt(te,"tagNameCheck")&&st(te.tagNameCheck)&&(ee.tagNameCheck=te.tagNameCheck),xt(te,"attributeNameCheck")&&st(te.attributeNameCheck)&&(ee.attributeNameCheck=te.attributeNameCheck),xt(te,"allowCustomizedBuiltInElements")&&typeof te.allowCustomizedBuiltInElements=="boolean"&&(ee.allowCustomizedBuiltInElements=te.allowCustomizedBuiltInElements),pe&&(le=!1),Ie&&(Ae=!0),re&&(X=Ve({},eu),q=Zr(null),re.html===!0&&(Ve(X,Rd),Ve(q,tu)),re.svg===!0&&(Ve(X,tl),Ve(q,sl),Ve(q,qo)),re.svgFilters===!0&&(Ve(X,nl),Ve(q,sl),Ve(q,qo)),re.mathMl===!0&&(Ve(X,rl),Ve(q,nu),Ve(q,qo))),ce.tagCheck=null,ce.attributeCheck=null,xt(B,"ADD_TAGS")&&(typeof B.ADD_TAGS=="function"?ce.tagCheck=B.ADD_TAGS:Qt(B.ADD_TAGS)&&(X===K&&(X=Bt(X)),Ve(X,B.ADD_TAGS,Ue))),xt(B,"ADD_ATTR")&&(typeof B.ADD_ATTR=="function"?ce.attributeCheck=B.ADD_ATTR:Qt(B.ADD_ATTR)&&(q===ne&&(q=Bt(q)),Ve(q,B.ADD_ATTR,Ue))),xt(B,"ADD_URI_SAFE_ATTR")&&Qt(B.ADD_URI_SAFE_ATTR)&&Ve(He,B.ADD_URI_SAFE_ATTR,Ue),xt(B,"FORBID_CONTENTS")&&Qt(B.FORBID_CONTENTS)&&(fe===ye&&(fe=Bt(fe)),Ve(fe,B.FORBID_CONTENTS,Ue)),xt(B,"ADD_FORBID_CONTENTS")&&Qt(B.ADD_FORBID_CONTENTS)&&(fe===ye&&(fe=Bt(fe)),Ve(fe,B.ADD_FORBID_CONTENTS,Ue)),et&&(X["#text"]=!0),$e&&Ve(X,["html","head","body"]),X.table&&(Ve(X,["tbody"]),delete ue.tbody),B.TRUSTED_TYPES_POLICY){if(typeof B.TRUSTED_TYPES_POLICY.createHTML!="function")throw Rr('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.');if(typeof B.TRUSTED_TYPES_POLICY.createScriptURL!="function")throw Rr('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.');const he=k;k=B.TRUSTED_TYPES_POLICY;try{N=w("")}catch(ot){throw k=he,ot}}else k===void 0&&B.TRUSTED_TYPES_POLICY!==null&&(k=ly(u,s)),k&&typeof N=="string"&&(N=w(""));(O.uponSanitizeElement.length>0||O.uponSanitizeAttribute.length>0)&&X===K&&(X=Bt(X)),O.uponSanitizeAttribute.length>0&&q===ne&&(q=Bt(q)),Zt&&Zt(B),Ct=B},qt=Ve({},[...tl,...nl,...Zb]),Ln=Ve({},[...rl,...Qb]),jr=function(B){let te=b(B);(!te||!te.tagName)&&(te={namespaceURI:Ze,tagName:"template"});const he=$s(B.tagName),ot=$s(te.tagName);return Ne[B.namespaceURI]?B.namespaceURI===Ce?te.namespaceURI===We?he==="svg":te.namespaceURI===Le?he==="svg"&&(ot==="annotation-xml"||Ke[ot]):!!qt[he]:B.namespaceURI===Le?te.namespaceURI===We?he==="math":te.namespaceURI===Ce?he==="math"&&tt[ot]:!!Ln[he]:B.namespaceURI===We?te.namespaceURI===Ce&&!tt[ot]||te.namespaceURI===Le&&!Ke[ot]?!1:!Ln[he]&&(at[he]||!qt[he]):!!(ct==="application/xhtml+xml"&&Ne[B.namespaceURI]):!1},fn=function(B){Qr(t.removed,{element:B});try{b(B).removeChild(B)}catch{m(B)}},qn=function(B,te){try{Qr(t.removed,{attribute:te.getAttributeNode(B),from:te})}catch{Qr(t.removed,{attribute:null,from:te})}if(te.removeAttribute(B),B==="is")if(Ae||Ie)try{fn(te)}catch{}else try{te.setAttribute(B,"")}catch{}},Wo=function(B){let te=null,he=null;if(Me)B=""+B;else{const ft=qd(B,/^[\r\n\t ]+/);he=ft&&ft[0]}ct==="application/xhtml+xml"&&Ze===We&&(B=''+B+"");const ot=k?w(B):B;if(Ze===We)try{te=new d().parseFromString(ot,ct)}catch{}if(!te||!te.documentElement){te=D.createDocument(Ze,"template",null);try{te.documentElement.innerHTML=Fe?N:ot}catch{}}const Ge=te.body||te.documentElement;return B&&he&&Ge.insertBefore(n.createTextNode(he),Ge.childNodes[0]||null),Ze===We?$.call(te,$e?"html":"body")[0]:$e?te.documentElement:Ge},Go=function(B){return P.call(B.ownerDocument||B,B,l.SHOW_ELEMENT|l.SHOW_COMMENT|l.SHOW_TEXT|l.SHOW_PROCESSING_INSTRUCTION|l.SHOW_CDATA_SECTION,null)},Ls=function(B){var te,he;B.normalize();const ot=P.call(B.ownerDocument||B,B,l.SHOW_TEXT|l.SHOW_COMMENT|l.SHOW_CDATA_SECTION|l.SHOW_PROCESSING_INSTRUCTION,null);let Ge=ot.nextNode();for(;Ge;){let At=Ge.data;kr([T,z,H],Sn=>{At=Jr(At,Sn," ")}),Ge.data=At,Ge=ot.nextNode()}const ft=(te=(he=B.querySelectorAll)===null||he===void 0?void 0:he.call(B,"template"))!==null&&te!==void 0?te:[];kr(Array.from(ft),At=>{or(At.content)&&Ls(At.content)})},sr=function(B){const te=C?C(B):null;return typeof te!="string"||Ue(te)!=="form"?!1:typeof B.nodeName!="string"||typeof B.textContent!="string"||typeof B.removeChild!="function"||B.attributes!==x(B)||typeof B.removeAttribute!="function"||typeof B.setAttribute!="function"||typeof B.namespaceURI!="string"||typeof B.insertBefore!="function"||typeof B.hasChildNodes!="function"||B.nodeType!==M(B)||B.childNodes!==y(B)},or=function(B){if(!M||typeof B!="object"||B===null)return!1;try{return M(B)===zn.documentFragment}catch{return!1}},Wr=function(B){if(!M||typeof B!="object"||B===null)return!1;try{return typeof M(B)=="number"}catch{return!1}};function Cn(se,B,te){kr(se,he=>{he.call(t,B,te,Ct)})}const Ha=function(B){let te=null;if(Cn(O.beforeSanitizeElements,B,null),sr(B))return fn(B),!0;const he=Ue(C?C(B):B.nodeName);if(Cn(O.uponSanitizeElement,B,{tagName:he,allowedTags:X}),ke&&B.hasChildNodes()&&!Wr(B.firstElementChild)&&zt(/<[/\w!]/g,B.innerHTML)&&zt(/<[/\w!]/g,B.textContent)||ke&&B.namespaceURI===We&&he==="style"&&Wr(B.firstElementChild)||B.nodeType===zn.progressingInstruction||ke&&B.nodeType===zn.comment&&zt(/<[/\w]/g,B.data))return fn(B),!0;if(ue[he]||!(ce.tagCheck instanceof Function&&ce.tagCheck(he))&&!X[he]){if(!ue[he]&&Z(he)&&(ee.tagNameCheck instanceof RegExp&&zt(ee.tagNameCheck,he)||ee.tagNameCheck instanceof Function&&ee.tagNameCheck(he)))return!1;if(et&&!fe[he]){const Ge=b(B),ft=y(B);if(ft&&Ge){const At=ft.length;for(let Sn=At-1;Sn>=0;--Sn){const Pn=g(ft[Sn],!0);Ge.insertBefore(Pn,p(B))}}}return fn(B),!0}return(M?M(B):B.nodeType)===zn.element&&!jr(B)||(he==="noscript"||he==="noembed"||he==="noframes")&&zt(/<\/no(script|embed|frames)/i,B.innerHTML)?(fn(B),!0):(pe&&B.nodeType===zn.text&&(te=B.textContent,kr([T,z,H],Ge=>{te=Jr(te,Ge," ")}),B.textContent!==te&&(Qr(t.removed,{element:B.cloneNode()}),B.textContent=te)),Cn(O.afterSanitizeElements,B,null),!1)},Ua=function(B,te,he){if(J[te]||gt&&(te==="id"||te==="name")&&(he in n||he in Ut))return!1;const ot=q[te]||ce.attributeCheck instanceof Function&&ce.attributeCheck(te,B);if(!(le&&!J[te]&&zt(j,te))){if(!(me&&zt(G,te))){if(!ot||J[te]){if(!(Z(B)&&(ee.tagNameCheck instanceof RegExp&&zt(ee.tagNameCheck,B)||ee.tagNameCheck instanceof Function&&ee.tagNameCheck(B))&&(ee.attributeNameCheck instanceof RegExp&&zt(ee.attributeNameCheck,te)||ee.attributeNameCheck instanceof Function&&ee.attributeNameCheck(te,B))||te==="is"&&ee.allowCustomizedBuiltInElements&&(ee.tagNameCheck instanceof RegExp&&zt(ee.tagNameCheck,he)||ee.tagNameCheck instanceof Function&&ee.tagNameCheck(he))))return!1}else if(!He[te]){if(!zt(V,Jr(he,W,""))){if(!((te==="src"||te==="xlink:href"||te==="href")&&B!=="script"&&Zd(he,"data:")===0&&we[B])){if(!(ve&&!zt(U,Jr(he,W,"")))){if(he)return!1}}}}}}return!0},Id=Ve({},["annotation-xml","color-profile","font-face","font-face-format","font-face-name","font-face-src","font-face-uri","missing-glyph"]),Z=function(B){return!Id[$s(B)]&&zt(Y,B)},be=function(B){Cn(O.beforeSanitizeAttributes,B,null);const te=B.attributes;if(!te||sr(B))return;const he={attrName:"",attrValue:"",keepAttr:!0,allowedAttributes:q,forceKeepAttr:void 0};let ot=te.length;for(;ot--;){const Ge=te[ot],ft=Ge.name,At=Ge.namespaceURI,Sn=Ge.value,Pn=Ue(ft),Ld=Sn;let Ft=ft==="value"?Ld:jb(Ld);if(he.attrName=Pn,he.attrValue=Ft,he.keepAttr=!0,he.forceKeepAttr=void 0,Cn(O.uponSanitizeAttribute,B,he),Ft=he.attrValue,pt&&(Pn==="id"||Pn==="name")&&Zd(Ft,Be)!==0&&(qn(ft,B),Ft=Be+Ft),ke&&zt(/((--!?|])>)|<\/(style|script|title|xmp|textarea|noscript|iframe|noembed|noframes)/i,Ft)){qn(ft,B);continue}if(Pn==="attributename"&&qd(Ft,"href")){qn(ft,B);continue}if(he.forceKeepAttr)continue;if(!he.keepAttr){qn(ft,B);continue}if(!ge&&zt(/\/>/i,Ft)){qn(ft,B);continue}pe&&kr([T,z,H],ub=>{Ft=Jr(Ft,ub," ")});const db=Ue(B.nodeName);if(!Ua(db,Pn,Ft)){qn(ft,B);continue}if(k&&typeof u=="object"&&typeof u.getAttributeType=="function"&&!At)switch(u.getAttributeType(db,Pn)){case"TrustedHTML":{Ft=w(Ft);break}case"TrustedScriptURL":{Ft=k.createScriptURL(Ft);break}}if(Ft!==Ld)try{At?B.setAttributeNS(At,ft,Ft):B.setAttribute(ft,Ft),sr(B)?fn(B):Kd(t.removed)}catch{qn(ft,B)}}Cn(O.afterSanitizeAttributes,B,null)},De=function(B){let te=null;const he=Go(B);for(Cn(O.beforeSanitizeShadowDOM,B,null);te=he.nextNode();)if(Cn(O.uponSanitizeShadowNode,te,null),Ha(te),be(te),or(te.content)&&De(te.content),(M?M(te):te.nodeType)===zn.element){const Ge=_?_(te):te.shadowRoot;or(Ge)&&(Qe(Ge),De(Ge))}Cn(O.afterSanitizeShadowDOM,B,null)},Qe=function(B){const te=M?M(B):B.nodeType;if(te===zn.element){const Ge=_?_(B):B.shadowRoot;or(Ge)&&(Qe(Ge),De(Ge))}const he=y?y(B):B.childNodes;if(!he)return;const ot=[];kr(he,Ge=>{Qr(ot,Ge)});for(const Ge of ot)Qe(Ge);if(te===zn.element){const Ge=C?C(B):null;if(typeof Ge=="string"&&Ue(Ge)==="template"){const ft=B.content;or(ft)&&Qe(ft)}}};return t.sanitize=function(se){let B=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{},te=null,he=null,ot=null,Ge=null;if(Fe=!se,Fe&&(se=""),typeof se!="string"&&!Wr(se)&&(se=Kb(se),typeof se!="string"))throw Rr("dirty is not a string, aborting");if(!t.isSupported)return se;if(ie||kn(B),t.removed=[],typeof se=="string"&&(je=!1),je){const Sn=C?C(se):se.nodeName;if(typeof Sn=="string"){const Pn=Ue(Sn);if(!X[Pn]||ue[Pn])throw Rr("root node is forbidden and cannot be sanitized in-place")}if(sr(se))throw Rr("root node is clobbered and cannot be sanitized in-place");Qe(se)}else if(Wr(se))te=Wo(""),he=te.ownerDocument.importNode(se,!0),he.nodeType===zn.element&&he.nodeName==="BODY"||he.nodeName==="HTML"?te=he:te.appendChild(he),Qe(he);else{if(!Ae&&!pe&&!$e&&se.indexOf("<")===-1)return k&&Xe?w(se):se;if(te=Wo(se),!te)return Ae?null:Xe?N:""}te&&Me&&fn(te.firstChild);const ft=Go(je?se:te);for(;ot=ft.nextNode();)Ha(ot),be(ot),or(ot.content)&&De(ot.content);if(je)return pe&&Ls(se),se;if(Ae){if(pe&&Ls(te),Ie)for(Ge=L.call(te.ownerDocument);te.firstChild;)Ge.appendChild(te.firstChild);else Ge=te;return(q.shadowroot||q.shadowrootmode)&&(Ge=A.call(r,Ge,!0)),Ge}let At=$e?te.outerHTML:te.innerHTML;return $e&&X["!doctype"]&&te.ownerDocument&&te.ownerDocument.doctype&&te.ownerDocument.doctype.name&&zt(oy,te.ownerDocument.doctype.name)&&(At=" -`+At),pe&&kr([T,z,H],Sn=>{At=Jr(At,Sn," ")}),k&&Xe?w(At):At},t.setConfig=function(){let se=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{};kn(se),ie=!0},t.clearConfig=function(){Ct=null,ie=!1},t.isValidAttribute=function(se,B,te){Ct||kn({});const he=Ue(se),ot=Ue(B);return Ua(he,ot,te)},t.addHook=function(se,B){typeof B=="function"&&Qr(O[se],B)},t.removeHook=function(se,B){if(B!==void 0){const te=Ub(O[se],B);return te===-1?void 0:Bb(O[se],te,1)[0]}return Kd(O[se])},t.removeHooks=function(se){O[se]=[]},t.removeAllHooks=function(){O=su()},t}var cy=ou();const ol=({content:e,text:t,isHtml:n=!0,className:r="",purifyConfig:s,id:o,editorAttrs:i})=>{const a=t??e??"";if(!a||a.trim()==="")return null;if(!n){const u=` + ${T===ee?"bg-blue-500 text-white border-blue-500 dark:bg-blue-600 dark:border-blue-600":"bg-white text-gray-600 border-gray-200 hover:bg-gray-50 dark:bg-gray-800 dark:text-gray-300 dark:border-gray-600 dark:hover:bg-gray-700"} + `,onClick:()=>P(ee),title:H(ee),children:[ee.toUpperCase(),ee===x&&h.jsx(Q,{className:"text-red-400 ml-0.5",children:"*"})]},ee))}),h.jsx(I,{className:"flex-1 min-w-0",children:n==="textarea"?h.jsx(Qn,{name:`${f}[${T}]`,value:C[T]||"",onChange:ee=>D(T,ee.target.value),placeholder:i,required:a&&T===x,disabled:l,maxLength:c,rows:d,className:Z}):h.jsx(kt,{type:"text",name:`${f}[${T}]`,value:C[T]||"",onChange:ee=>D(T,ee.target.value),placeholder:i,required:a&&T===x,disabled:l,maxLength:c,className:K})})]})})};return h.jsxs(I,{className:l?"opacity-50 cursor-not-allowed":void 0,id:p,...y,children:[r==="compact"?X():r==="tabs"?V():G(),b&&m&&h.jsx(Q,{className:"text-xs text-red-500 dark:text-red-400 mt-1 block",children:m})]})};Ha.displayName="MultilingualInput";const vb=()=>window.G7Core,Ua=({checked:t,value:e,onChange:n,disabled:r=!1,label:s,description:o,size:i="md",className:a="",name:l,id:c,editorAttrs:d})=>{const[u,f]=v.useState(()=>t??e??!1);v.useEffect(()=>{f(t??e??!1)},[t,e]);const g=_=>{const x=_.target.checked;f(x),n&&!r&&n(_)},p={sm:{track:"w-9 h-5",thumb:"after:h-4 after:w-4"},md:{track:"w-11 h-6",thumb:"after:h-5 after:w-5"},lg:{track:"w-14 h-7",thumb:"after:h-6 after:w-6"}}[i],y=()=>{if(!r){const _=!u;if(f(_),n){const x=vb();x?.createChangeEvent&&n(x.createChangeEvent({checked:_,name:l}))}}},b=_=>{r||_.key!==" "&&_.key!=="Enter"&&_.key!=="Spacebar"||(_.preventDefault(),y())};return h.jsxs("div",{className:`toggle-container ${a}`,id:c,...d,children:[h.jsxs("div",{className:`toggle-switch-wrapper ${r?"cursor-not-allowed opacity-50":"cursor-pointer"}`,role:"switch","aria-checked":u,"aria-disabled":r||void 0,"aria-label":s??l,tabIndex:r?-1:0,onKeyDown:b,onClick:_=>{_.preventDefault(),_.stopPropagation(),y()},children:[h.jsx("input",{type:"checkbox",id:l,name:l,checked:u,onChange:g,disabled:r,className:"sr-only peer",tabIndex:-1}),h.jsx("div",{className:`toggle-switch-track ${p.track} ${p.thumb}`})]}),s&&h.jsx("span",{className:`toggle-label ${r?"opacity-50 cursor-not-allowed":"cursor-pointer"}`,onClick:y,children:s}),o&&h.jsx("p",{className:"toggle-description",children:o})]})},Fo=({status:t,label:e,showIcon:n=!0,iconName:r,className:s="",style:o,id:i,editorAttrs:a})=>{const l={success:"bg-green-50 dark:bg-green-900/20 text-green-700 dark:text-green-400 border-green-200 dark:border-green-800",warning:"bg-yellow-50 dark:bg-yellow-900/20 text-yellow-700 dark:text-yellow-400 border-yellow-200 dark:border-yellow-800",error:"bg-red-50 dark:bg-red-900/20 text-red-700 dark:text-red-400 border-red-200 dark:border-red-800",info:"bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-400 border-blue-200 dark:border-blue-800",pending:"bg-gray-50 dark:bg-gray-700 text-gray-700 dark:text-gray-300 border-gray-200 dark:border-gray-600",default:"bg-gray-50 dark:bg-gray-700 text-gray-600 dark:text-gray-400 border-gray-200 dark:border-gray-600"},c={success:de.CheckCircle,warning:de.ExclamationTriangle,error:de.XCircle,info:de.InfoCircle,pending:de.Clock,default:de.QuestionCircle},d={success:"성공",warning:"경고",error:"오류",info:"정보",pending:"대기 중",default:"기본"},u=r||c[t],f=e||d[t],g=l[t];return h.jsxs(Q,{className:`inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-xs font-medium border ${g} ${s}`,style:o,id:i,...a,children:[n&&h.jsx(re,{name:u,className:"w-4 h-4"}),h.jsx(Q,{children:f})]})},Dd={blue:"bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-300",green:"bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300",red:"bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-300",gray:"bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300",yellow:"bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-300",purple:"bg-purple-100 text-purple-800 dark:bg-purple-900/30 dark:text-purple-300",orange:"bg-orange-100 text-orange-800 dark:bg-orange-900/30 dark:text-orange-300",teal:"bg-teal-100 text-teal-800 dark:bg-teal-900/30 dark:text-teal-300",cyan:"bg-cyan-100 text-cyan-800 dark:bg-cyan-900/30 dark:text-cyan-300"},xb={success:"green",warning:"yellow",danger:"red",error:"red",info:"blue",primary:"blue",secondary:"gray",text:"gray",default:"gray"},Id={sm:"px-1.5 py-0.5 text-[10px]",md:"px-2 py-0.5 text-xs",lg:"px-2.5 py-1 text-sm"},Ld=({color:t,variant:e,text:n,size:r="md",className:s="",style:o,children:i,id:a,editorAttrs:l})=>{const c=t||(e?xb[e]:void 0)||"gray",d=Dd[c]||Dd.gray,u=Id[r]||Id.md;return h.jsxs(Q,{className:`inline-flex items-center rounded-full font-medium ${u} ${d} ${s}`,style:o,id:a,...l,children:[n,i]})};const{entries:Pd,setPrototypeOf:$d,isFrozen:wb,getPrototypeOf:kb,getOwnPropertyDescriptor:Cb}=Object;let{freeze:Gt,seal:cn,create:Fd}=Object,{apply:Ba,construct:ja}=typeof Reflect<"u"&&Reflect;Gt||(Gt=function(e){return e}),cn||(cn=function(e){return e}),Ba||(Ba=function(e,n){for(var r=arguments.length,s=new Array(r>2?r-2:0),o=2;o1?n-1:0),s=1;s1?n-1:0),s=1;s2&&arguments[2]!==void 0?arguments[2]:Vo;$d&&$d(t,null);let r=e.length;for(;r--;){let s=e[r];if(typeof s=="string"){const o=n(s);o!==s&&(wb(e)||(e[r]=o),s=o)}t[s]=!0}return t}function Ob(t){for(let e=0;e/gm),Lb=cn(/\$\{[\w\W]*/gm),Pb=cn(/^data-[\-\w.\u00B7-\uFFFF]+$/),$b=cn(/^aria-[\-\w]+$/),jd=cn(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),Fb=cn(/^(?:\w+script|data):/i),zb=cn(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g),Wd=cn(/^html$/i),Vb=cn(/^[a-z][.\w]*(-[.\w]+)+$/i);var Gd=Object.freeze({__proto__:null,ARIA_ATTR:$b,ATTR_WHITESPACE:zb,CUSTOM_ELEMENT:Vb,DATA_ATTR:Pb,DOCTYPE_NAME:Wd,ERB_EXPR:Ib,IS_ALLOWED_URI:jd,IS_SCRIPT_OR_DATA:Fb,MUSTACHE_EXPR:Db,TMPLIT_EXPR:Lb});const _s={element:1,text:3,progressingInstruction:7,comment:8,document:9},Hb=function(){return typeof window>"u"?null:window},Ub=function(e,n){if(typeof e!="object"||typeof e.createPolicy!="function")return null;let r=null;const s="data-tt-policy-suffix";n&&n.hasAttribute(s)&&(r=n.getAttribute(s));const o="dompurify"+(r?"#"+r:"");try{return e.createPolicy(o,{createHTML(i){return i},createScriptURL(i){return i}})}catch{return console.warn("TrustedTypes policy "+o+" could not be created."),null}},Yd=function(){return{afterSanitizeAttributes:[],afterSanitizeElements:[],afterSanitizeShadowDOM:[],beforeSanitizeAttributes:[],beforeSanitizeElements:[],beforeSanitizeShadowDOM:[],uponSanitizeAttribute:[],uponSanitizeElement:[],uponSanitizeShadowNode:[]}};function Kd(){let t=arguments.length>0&&arguments[0]!==void 0?arguments[0]:Hb();const e=ke=>Kd(ke);if(e.version="3.2.7",e.removed=[],!t||!t.document||t.document.nodeType!==_s.document||!t.Element)return e.isSupported=!1,e;let{document:n}=t;const r=n,s=r.currentScript,{DocumentFragment:o,HTMLTemplateElement:i,Node:a,Element:l,NodeFilter:c,NamedNodeMap:d=t.NamedNodeMap||t.MozNamedAttrMap,HTMLFormElement:u,DOMParser:f,trustedTypes:g}=t,m=l.prototype,p=Ss(m,"cloneNode"),y=Ss(m,"remove"),b=Ss(m,"nextSibling"),_=Ss(m,"childNodes"),x=Ss(m,"parentNode");if(typeof i=="function"){const ke=n.createElement("template");ke.content&&ke.content.ownerDocument&&(n=ke.content.ownerDocument)}let E,C="";const{implementation:k,createNodeIterator:N,createDocumentFragment:S,getElementsByTagName:w}=n,{importNode:M}=r;let T=Yd();e.isSupported=typeof Pd=="function"&&typeof x=="function"&&k&&k.createHTMLDocument!==void 0;const{MUSTACHE_EXPR:P,ERB_EXPR:L,TMPLIT_EXPR:$,DATA_ATTR:A,ARIA_ATTR:D,IS_SCRIPT_OR_DATA:O,ATTR_WHITESPACE:z,CUSTOM_ELEMENT:H}=Gd;let{IS_ALLOWED_URI:j}=Gd,W=null;const U=Ge({},[...Vd,...Ya,...Ka,...Xa,...Hd]);let B=null;const G=Ge({},[...Ud,...qa,...Bd,...Ho]);let V=Object.seal(Fd(null,{tagNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},allowCustomizedBuiltInElements:{writable:!0,configurable:!1,enumerable:!0,value:!1}})),X=null,K=null,Z=!0,ee=!0,ne=!1,he=!0,J=!1,le=!0,ue=!1,ae=!1,ve=!1,fe=!1,ye=!1,we=!1,He=!0,se=!1;const Oe="user-content-";let Le=!0,Ie=!1,Be={},st=null;const ut=Ge({},["annotation-xml","audio","colgroup","desc","foreignobject","head","iframe","math","mi","mn","mo","ms","mtext","noembed","noframes","noscript","plaintext","script","style","svg","template","thead","title","video","xmp"]);let je=null;const nt=Ge({},["audio","video","img","source","image","track"]);let Ye=null;const te=Ge({},["alt","class","for","id","label","name","pattern","placeholder","role","summary","title","value","style","xmlns"]),ge="http://www.w3.org/1998/Math/MathML",me="http://www.w3.org/2000/svg",pe="http://www.w3.org/1999/xhtml";let be=pe,Ue=!1,Ce=null;const ze=Ge({},[ge,me,pe],Wa);let Se=Ge({},["mi","mo","mn","ms","mtext"]),Re=Ge({},["annotation-xml"]);const ot=Ge({},["title","style","font","a","script"]);let Pe=null;const Te=["application/xhtml+xml","text/html"],Ve="text/html";let De=null,qe=null;const it=n.createElement("form"),pt=function(Y){return Y instanceof RegExp||Y instanceof Function},rn=function(){let Y=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{};if(!(qe&&qe===Y)){if((!Y||typeof Y!="object")&&(Y={}),Y=Bn(Y),Pe=Te.indexOf(Y.PARSER_MEDIA_TYPE)===-1?Ve:Y.PARSER_MEDIA_TYPE,De=Pe==="application/xhtml+xml"?Wa:Vo,W=xn(Y,"ALLOWED_TAGS")?Ge({},Y.ALLOWED_TAGS,De):U,B=xn(Y,"ALLOWED_ATTR")?Ge({},Y.ALLOWED_ATTR,De):G,Ce=xn(Y,"ALLOWED_NAMESPACES")?Ge({},Y.ALLOWED_NAMESPACES,Wa):ze,Ye=xn(Y,"ADD_URI_SAFE_ATTR")?Ge(Bn(te),Y.ADD_URI_SAFE_ATTR,De):te,je=xn(Y,"ADD_DATA_URI_TAGS")?Ge(Bn(nt),Y.ADD_DATA_URI_TAGS,De):nt,st=xn(Y,"FORBID_CONTENTS")?Ge({},Y.FORBID_CONTENTS,De):ut,X=xn(Y,"FORBID_TAGS")?Ge({},Y.FORBID_TAGS,De):Bn({}),K=xn(Y,"FORBID_ATTR")?Ge({},Y.FORBID_ATTR,De):Bn({}),Be=xn(Y,"USE_PROFILES")?Y.USE_PROFILES:!1,Z=Y.ALLOW_ARIA_ATTR!==!1,ee=Y.ALLOW_DATA_ATTR!==!1,ne=Y.ALLOW_UNKNOWN_PROTOCOLS||!1,he=Y.ALLOW_SELF_CLOSE_IN_ATTR!==!1,J=Y.SAFE_FOR_TEMPLATES||!1,le=Y.SAFE_FOR_XML!==!1,ue=Y.WHOLE_DOCUMENT||!1,fe=Y.RETURN_DOM||!1,ye=Y.RETURN_DOM_FRAGMENT||!1,we=Y.RETURN_TRUSTED_TYPE||!1,ve=Y.FORCE_BODY||!1,He=Y.SANITIZE_DOM!==!1,se=Y.SANITIZE_NAMED_PROPS||!1,Le=Y.KEEP_CONTENT!==!1,Ie=Y.IN_PLACE||!1,j=Y.ALLOWED_URI_REGEXP||jd,be=Y.NAMESPACE||pe,Se=Y.MATHML_TEXT_INTEGRATION_POINTS||Se,Re=Y.HTML_INTEGRATION_POINTS||Re,V=Y.CUSTOM_ELEMENT_HANDLING||{},Y.CUSTOM_ELEMENT_HANDLING&&pt(Y.CUSTOM_ELEMENT_HANDLING.tagNameCheck)&&(V.tagNameCheck=Y.CUSTOM_ELEMENT_HANDLING.tagNameCheck),Y.CUSTOM_ELEMENT_HANDLING&&pt(Y.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)&&(V.attributeNameCheck=Y.CUSTOM_ELEMENT_HANDLING.attributeNameCheck),Y.CUSTOM_ELEMENT_HANDLING&&typeof Y.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements=="boolean"&&(V.allowCustomizedBuiltInElements=Y.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements),J&&(ee=!1),ye&&(fe=!0),Be&&(W=Ge({},Hd),B=[],Be.html===!0&&(Ge(W,Vd),Ge(B,Ud)),Be.svg===!0&&(Ge(W,Ya),Ge(B,qa),Ge(B,Ho)),Be.svgFilters===!0&&(Ge(W,Ka),Ge(B,qa),Ge(B,Ho)),Be.mathMl===!0&&(Ge(W,Xa),Ge(B,Bd),Ge(B,Ho))),Y.ADD_TAGS&&(W===U&&(W=Bn(W)),Ge(W,Y.ADD_TAGS,De)),Y.ADD_ATTR&&(B===G&&(B=Bn(B)),Ge(B,Y.ADD_ATTR,De)),Y.ADD_URI_SAFE_ATTR&&Ge(Ye,Y.ADD_URI_SAFE_ATTR,De),Y.FORBID_CONTENTS&&(st===ut&&(st=Bn(st)),Ge(st,Y.FORBID_CONTENTS,De)),Le&&(W["#text"]=!0),ue&&Ge(W,["html","head","body"]),W.table&&(Ge(W,["tbody"]),delete X.tbody),Y.TRUSTED_TYPES_POLICY){if(typeof Y.TRUSTED_TYPES_POLICY.createHTML!="function")throw Cs('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.');if(typeof Y.TRUSTED_TYPES_POLICY.createScriptURL!="function")throw Cs('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.');E=Y.TRUSTED_TYPES_POLICY,C=E.createHTML("")}else E===void 0&&(E=Ub(g,s)),E!==null&&typeof C=="string"&&(C=E.createHTML(""));Gt&&Gt(Y),qe=Y}},bt=Ge({},[...Ya,...Ka,...Tb]),wt=Ge({},[...Xa,...Ab]),jt=function(Y){let ie=x(Y);(!ie||!ie.tagName)&&(ie={namespaceURI:be,tagName:"template"});const xe=Vo(Y.tagName),ht=Vo(ie.tagName);return Ce[Y.namespaceURI]?Y.namespaceURI===me?ie.namespaceURI===pe?xe==="svg":ie.namespaceURI===ge?xe==="svg"&&(ht==="annotation-xml"||Se[ht]):!!bt[xe]:Y.namespaceURI===ge?ie.namespaceURI===pe?xe==="math":ie.namespaceURI===me?xe==="math"&&Re[ht]:!!wt[xe]:Y.namespaceURI===pe?ie.namespaceURI===me&&!Re[ht]||ie.namespaceURI===ge&&!Se[ht]?!1:!wt[xe]&&(ot[xe]||!bt[xe]):!!(Pe==="application/xhtml+xml"&&Ce[Y.namespaceURI]):!1},ft=function(Y){ws(e.removed,{element:Y});try{x(Y).removeChild(Y)}catch{y(Y)}},Ke=function(Y,ie){try{ws(e.removed,{attribute:ie.getAttributeNode(Y),from:ie})}catch{ws(e.removed,{attribute:null,from:ie})}if(ie.removeAttribute(Y),Y==="is")if(fe||ye)try{ft(ie)}catch{}else try{ie.setAttribute(Y,"")}catch{}},Nn=function(Y){let ie=null,xe=null;if(ve)Y=""+Y;else{const q=Ga(Y,/^[\r\n\t ]+/);xe=q&&q[0]}Pe==="application/xhtml+xml"&&be===pe&&(Y=''+Y+"");const ht=E?E.createHTML(Y):Y;if(be===pe)try{ie=new f().parseFromString(ht,Pe)}catch{}if(!ie||!ie.documentElement){ie=k.createDocument(be,"template",null);try{ie.documentElement.innerHTML=Ue?C:ht}catch{}}const Lt=ie.body||ie.documentElement;return Y&&xe&&Lt.insertBefore(n.createTextNode(xe),Lt.childNodes[0]||null),be===pe?w.call(ie,ue?"html":"body")[0]:ue?ie.documentElement:Lt},Wt=function(Y){return N.call(Y.ownerDocument||Y,Y,c.SHOW_ELEMENT|c.SHOW_COMMENT|c.SHOW_TEXT|c.SHOW_PROCESSING_INSTRUCTION|c.SHOW_CDATA_SECTION,null)},bn=function(Y){return Y instanceof u&&(typeof Y.nodeName!="string"||typeof Y.textContent!="string"||typeof Y.removeChild!="function"||!(Y.attributes instanceof d)||typeof Y.removeAttribute!="function"||typeof Y.setAttribute!="function"||typeof Y.namespaceURI!="string"||typeof Y.insertBefore!="function"||typeof Y.hasChildNodes!="function")},hr=function(Y){return typeof a=="function"&&Y instanceof a};function On(ke,Y,ie){zo(ke,xe=>{xe.call(e,Y,ie,qe)})}const Ao=function(Y){let ie=null;if(On(T.beforeSanitizeElements,Y,null),bn(Y))return ft(Y),!0;const xe=De(Y.nodeName);if(On(T.uponSanitizeElement,Y,{tagName:xe,allowedTags:W}),le&&Y.hasChildNodes()&&!hr(Y.firstElementChild)&&Yt(/<[/\w!]/g,Y.innerHTML)&&Yt(/<[/\w!]/g,Y.textContent)||Y.nodeType===_s.progressingInstruction||le&&Y.nodeType===_s.comment&&Yt(/<[/\w]/g,Y.data))return ft(Y),!0;if(!W[xe]||X[xe]){if(!X[xe]&&Io(xe)&&(V.tagNameCheck instanceof RegExp&&Yt(V.tagNameCheck,xe)||V.tagNameCheck instanceof Function&&V.tagNameCheck(xe)))return!1;if(Le&&!st[xe]){const ht=x(Y)||Y.parentNode,Lt=_(Y)||Y.childNodes;if(Lt&&ht){const q=Lt.length;for(let ce=q-1;ce>=0;--ce){const Ee=p(Lt[ce],!0);Ee.__removalCount=(Y.__removalCount||0)+1,ht.insertBefore(Ee,b(Y))}}}return ft(Y),!0}return Y instanceof l&&!jt(Y)||(xe==="noscript"||xe==="noembed"||xe==="noframes")&&Yt(/<\/no(script|embed|frames)/i,Y.innerHTML)?(ft(Y),!0):(J&&Y.nodeType===_s.text&&(ie=Y.textContent,zo([P,L,$],ht=>{ie=ks(ie,ht," ")}),Y.textContent!==ie&&(ws(e.removed,{element:Y.cloneNode()}),Y.textContent=ie)),On(T.afterSanitizeElements,Y,null),!1)},Do=function(Y,ie,xe){if(He&&(ie==="id"||ie==="name")&&(xe in n||xe in it))return!1;if(!(ee&&!K[ie]&&Yt(A,ie))){if(!(Z&&Yt(D,ie))){if(!B[ie]||K[ie]){if(!(Io(Y)&&(V.tagNameCheck instanceof RegExp&&Yt(V.tagNameCheck,Y)||V.tagNameCheck instanceof Function&&V.tagNameCheck(Y))&&(V.attributeNameCheck instanceof RegExp&&Yt(V.attributeNameCheck,ie)||V.attributeNameCheck instanceof Function&&V.attributeNameCheck(ie,Y))||ie==="is"&&V.allowCustomizedBuiltInElements&&(V.tagNameCheck instanceof RegExp&&Yt(V.tagNameCheck,xe)||V.tagNameCheck instanceof Function&&V.tagNameCheck(xe))))return!1}else if(!Ye[ie]){if(!Yt(j,ks(xe,z,""))){if(!((ie==="src"||ie==="xlink:href"||ie==="href")&&Y!=="script"&&Eb(xe,"data:")===0&&je[Y])){if(!(ne&&!Yt(O,ks(xe,z,"")))){if(xe)return!1}}}}}}return!0},Io=function(Y){return Y!=="annotation-xml"&&Ga(Y,H)},Lo=function(Y){On(T.beforeSanitizeAttributes,Y,null);const{attributes:ie}=Y;if(!ie||bn(Y))return;const xe={attrName:"",attrValue:"",keepAttr:!0,allowedAttributes:B,forceKeepAttr:void 0};let ht=ie.length;for(;ht--;){const Lt=ie[ht],{name:q,namespaceURI:ce,value:Ee}=Lt,Ze=De(q),Fe=Ee;let Nt=q==="value"?Fe:Mb(Fe);if(xe.attrName=Ze,xe.attrValue=Nt,xe.keepAttr=!0,xe.forceKeepAttr=void 0,On(T.uponSanitizeAttribute,Y,xe),Nt=xe.attrValue,se&&(Ze==="id"||Ze==="name")&&(Ke(q,Y),Nt=Oe+Nt),le&&Yt(/((--!?|])>)|<\/(style|title|textarea)/i,Nt)){Ke(q,Y);continue}if(Ze==="attributename"&&Ga(Nt,"href")){Ke(q,Y);continue}if(xe.forceKeepAttr)continue;if(!xe.keepAttr){Ke(q,Y);continue}if(!he&&Yt(/\/>/i,Nt)){Ke(q,Y);continue}J&&zo([P,L,$],Rm=>{Nt=ks(Nt,Rm," ")});const Jm=De(Y.nodeName);if(!Do(Jm,Ze,Nt)){Ke(q,Y);continue}if(E&&typeof g=="object"&&typeof g.getAttributeType=="function"&&!ce)switch(g.getAttributeType(Jm,Ze)){case"TrustedHTML":{Nt=E.createHTML(Nt);break}case"TrustedScriptURL":{Nt=E.createScriptURL(Nt);break}}if(Nt!==Fe)try{ce?Y.setAttributeNS(ce,q,Nt):Y.setAttribute(q,Nt),bn(Y)?ft(Y):zd(e.removed)}catch{Ke(q,Y)}}On(T.afterSanitizeAttributes,Y,null)},vs=function ke(Y){let ie=null;const xe=Wt(Y);for(On(T.beforeSanitizeShadowDOM,Y,null);ie=xe.nextNode();)On(T.uponSanitizeShadowNode,ie,null),Ao(ie),Lo(ie),ie.content instanceof o&&ke(ie.content);On(T.afterSanitizeShadowDOM,Y,null)};return e.sanitize=function(ke){let Y=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{},ie=null,xe=null,ht=null,Lt=null;if(Ue=!ke,Ue&&(ke=""),typeof ke!="string"&&!hr(ke))if(typeof ke.toString=="function"){if(ke=ke.toString(),typeof ke!="string")throw Cs("dirty is not a string, aborting")}else throw Cs("toString is not a function");if(!e.isSupported)return ke;if(ae||rn(Y),e.removed=[],typeof ke=="string"&&(Ie=!1),Ie){if(ke.nodeName){const Ee=De(ke.nodeName);if(!W[Ee]||X[Ee])throw Cs("root node is forbidden and cannot be sanitized in-place")}}else if(ke instanceof a)ie=Nn(""),xe=ie.ownerDocument.importNode(ke,!0),xe.nodeType===_s.element&&xe.nodeName==="BODY"||xe.nodeName==="HTML"?ie=xe:ie.appendChild(xe);else{if(!fe&&!J&&!ue&&ke.indexOf("<")===-1)return E&&we?E.createHTML(ke):ke;if(ie=Nn(ke),!ie)return fe?null:we?C:""}ie&&ve&&ft(ie.firstChild);const q=Wt(Ie?ke:ie);for(;ht=q.nextNode();)Ao(ht),Lo(ht),ht.content instanceof o&&vs(ht.content);if(Ie)return ke;if(fe){if(ye)for(Lt=S.call(ie.ownerDocument);ie.firstChild;)Lt.appendChild(ie.firstChild);else Lt=ie;return(B.shadowroot||B.shadowrootmode)&&(Lt=M.call(r,Lt,!0)),Lt}let ce=ue?ie.outerHTML:ie.innerHTML;return ue&&W["!doctype"]&&ie.ownerDocument&&ie.ownerDocument.doctype&&ie.ownerDocument.doctype.name&&Yt(Wd,ie.ownerDocument.doctype.name)&&(ce=" +`+ce),J&&zo([P,L,$],Ee=>{ce=ks(ce,Ee," ")}),E&&we?E.createHTML(ce):ce},e.setConfig=function(){let ke=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{};rn(ke),ae=!0},e.clearConfig=function(){qe=null,ae=!1},e.isValidAttribute=function(ke,Y,ie){qe||rn({});const xe=De(ke),ht=De(Y);return Do(xe,ht,ie)},e.addHook=function(ke,Y){typeof Y=="function"&&ws(T[ke],Y)},e.removeHook=function(ke,Y){if(Y!==void 0){const ie=Sb(T[ke],Y);return ie===-1?void 0:_b(T[ke],ie,1)[0]}return zd(T[ke])},e.removeHooks=function(ke){T[ke]=[]},e.removeAllHooks=function(){T=Yd()},e}var Bb=Kd();const Za=({content:t,text:e,isHtml:n=!0,className:r="",purifyConfig:s,id:o,editorAttrs:i})=>{const a=e??t??"";if(!a||a.trim()==="")return null;if(!n){const u=` whitespace-pre-wrap text-gray-900 dark:text-gray-100 font-sans ${r} - `.trim().replace(/\s+/g," ");return h.jsx(I,{className:u,id:o,...i,children:a})}const l={FORBID_TAGS:["script","noscript","iframe","frame","frameset","object","embed","applet","portal","form","input","textarea","select","button","style","link","meta","base","body","head","html","title","svg","math","audio","video","source","track","canvas","details","dialog","plaintext","xmp","listing","marquee","noframes","noembed","template","slot"],FORBID_ATTR:["onerror","onload","onclick","onmouseover","onfocus","onblur","ontoggle","oncanplay","onloadstart","formaction","xlink:href","action"],ALLOW_DATA_ATTR:!0,ADD_ATTR:["target"]},c=v.useMemo(()=>{const u=s?{...l,...s,FORBID_TAGS:[...l.FORBID_TAGS,...s.FORBID_TAGS||[]],FORBID_ATTR:[...l.FORBID_ATTR,...s.FORBID_ATTR||[]]}:l;return cy.sanitize(a,u).replace(/]*?)href=["']([^"']+)["']([^>]*?)>/gi,(g,m,p,y)=>(p.startsWith("http://")||p.startsWith("https://"))&&!g.includes("rel=")?``:g)},[a,s]),d=` + `.trim().replace(/\s+/g," ");return h.jsx(I,{className:u,id:o,...i,children:a})}const l={FORBID_TAGS:["script","noscript","iframe","frame","frameset","object","embed","applet","portal","form","input","textarea","select","button","style","link","meta","base","body","head","html","title","svg","math","audio","video","source","track","canvas","details","dialog","plaintext","xmp","listing","marquee","noframes","noembed","template","slot"],FORBID_ATTR:["onerror","onload","onclick","onmouseover","onfocus","onblur","ontoggle","oncanplay","onloadstart","formaction","xlink:href","action"],ALLOW_DATA_ATTR:!0,ADD_ATTR:["target"]},c=v.useMemo(()=>{const u=s?{...l,...s,FORBID_TAGS:[...l.FORBID_TAGS,...s.FORBID_TAGS||[]],FORBID_ATTR:[...l.FORBID_ATTR,...s.FORBID_ATTR||[]]}:l;return Bb.sanitize(a,u).replace(/]*?)href=["']([^"']+)["']([^>]*?)>/gi,(g,m,p,y)=>(p.startsWith("http://")||p.startsWith("https://"))&&!g.includes("rel=")?``:g)},[a,s]),d=` prose dark:prose-invert max-w-none prose-p:my-2 prose-headings:font-bold prose-headings:mt-6 prose-headings:mb-4 @@ -36,67 +35,67 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho [&_tr:hover]:bg-gray-50 dark:[&_tr:hover]:bg-gray-700/50 after:content-[''] after:block after:clear-both ${r} - `.trim().replace(/\s+/g," ");return h.jsx(I,{className:d,dangerouslySetInnerHTML:{__html:c},id:o,...i})},Zo=window.G7Core?.createLogger?.("Comp:DataGrid")??{log:(...e)=>console.log("[Comp:DataGrid]",...e),warn:(...e)=>console.warn("[Comp:DataGrid]",...e),error:(...e)=>console.error("[Comp:DataGrid]",...e)},zs=(e,t)=>window.G7Core?.t?.(e,t)??e,dy={Div:I,Span:Q,Button:ae,Icon:oe,Checkbox:En,P:vt,A:Yr,Label:ln,Input:St,H1:Ba,H2:ar,H3:Gr,H4:$d,Img:_n,Select:Xr,Textarea:ir,Table:Ya,Thead:Xa,Tbody:Ka,Tfoot:qa,Tr:lr,Th:Kr,Td:en,MultilingualInput:Za,Toggle:Qa,StatusBadge:Ko,Badge:jd,HtmlContent:ol,ActionMenu:Ps},il=()=>{const e=window.G7Core?.getComponentMap?.();return e&&Object.keys(e).length>0?e:dy},al=e=>{if(e==null)return"";if(typeof e=="string"||typeof e=="number"||typeof e=="boolean")return String(e);if(Array.isArray(e))return e.map(t=>al(t)).join(", ");if(typeof e=="object"){if(v.isValidElement(e))return e;if(Object.keys(e).length===0)return"";try{return JSON.stringify(e)}catch{return Zo.warn("객체를 문자열로 변환할 수 없습니다:",e),"[Object]"}}return String(e)},iu=(e,t)=>{if(!e)return!0;const n=window.G7Core;return typeof n?.evaluateCondition=="function"?n.evaluateCondition(e,{row:t,item:t,$item:t}):(Zo.warn("G7Core.evaluateCondition 미노출 — subRow 조건을 평가할 수 없습니다:",e),!0)},es=(e,t,n,r="",s)=>{const o=window.G7Core;if(o?.renderItemChildren){const i={row:t,value:n,$value:n};return o.renderItemChildren(e,i,il(),r,s?{componentContext:s}:void 0)}return Zo.warn("G7Core.renderItemChildren을 사용할 수 없습니다."),null},au=(e,t,n)=>e?!e.includes("{{")&&!e.includes("$t:")?e:es([{type:"basic",name:"Span",text:e}],{},null,t,n):"",ll=(e,t,n="",r)=>{const s=window.G7Core;if(s?.renderItemChildren){const o={row:t,$value:t};return s.renderItemChildren(e,o,il(),n,r?{componentContext:r}:void 0)}return Zo.warn("G7Core.renderItemChildren을 사용할 수 없습니다."),null},uy=({id:e,columns:t,data:n,sortable:r=!0,pagination:s=!1,pageSize:o=10,onRowClick:i,className:a="",style:l,sortField:c,sortDirection:d,onSortChange:u,selectable:f=!1,selectedIds:g=[],onSelectionChange:m,idField:p="id",showColumnSelector:y=!1,visibleColumns:b,onColumnVisibilityChange:_,responsiveBreakpoint:x=768,cardRenderer:M,rowActions:C,onRowAction:k,alwaysShowPagination:N=!1,serverSidePagination:S=!1,serverCurrentPage:w,serverTotalPages:E,onPageChange:D,prevText:P,nextText:L,columnSelectorText:$,emptyMessage:A,actionsColumnHeader:O,requiredText:T,selectedCountText:z,showFirstLast:H=!0,expandable:j=!1,expandedRowIds:G,onExpandChange:U,expandChildren:W,expandedRowRender:Y,expandColumnWidth:V="48px",expandIconExpanded:X=de.ChevronDown,expandIconCollapsed:K=de.ChevronRight,expandConditionField:q,dynamicColumns:ne,dynamicColumnsInsertAfter:ee,expandContext:ue,__componentContext:J,subRowChildren:ce,subRowCondition:me,subRowClassName:le="px-6 py-2 bg-gray-50 dark:bg-gray-900",subRowRender:ve,footerCells:ge,footerClassName:pe="bg-gray-50 dark:bg-gray-800/50 font-medium",footerCardChildren:ke,editorAttrs:$e})=>{const ie=P||void 0,Me=L||void 0,Ae=$??zs("common.column_selector"),Ie=A??zs("common.no_data"),Xe=O??zs("common.actions"),gt=T??zs("common.required"),pt=z??zs("common.selected_count"),Be=window.G7Core,et=Be?.useControllableState,je=Be?.shallowArrayEqual,[re,fe]=et?et(c,null):[c??null,()=>{}],[ye,we]=et?et(d,"asc"):[d??"asc",()=>{}],[xe,He]=v.useState(1),[_e,Le]=et?et(G,[],U,{isEqual:je}):[G??[],()=>{}],[Ce,We]=v.useState(!1),Ze=v.useRef(null),Fe=Be?.useResponsive,Ne=Fe?.(),ze=Ne?Ne.width{if(!ne||!Array.isArray(ne)||ne.length===0)return t;if(ee){const Z=t.findIndex(be=>be.field===ee);if(Z!==-1)return[...t.slice(0,Z+1),...ne,...t.slice(Z+1)]}return[...t,...ne]},[t,ne,ee]),[tt,at]=v.useState(()=>b&&b.length>0?b:t.filter(Z=>!Z.hidden).map(Z=>Z.field));v.useEffect(()=>{b&&b.length>0&&at(b)},[b]);const ct=v.useRef("");v.useEffect(()=>{const Z=t.map(be=>be.field).join(",");ct.current&&Z!==ct.current&&(!b||b.length===0)&&at(t.filter(be=>!be.hidden).map(be=>be.field)),ct.current=Z},[t,b]);const wn=v.useRef([]),yt=v.useRef(null);v.useEffect(()=>{if(ne&&ne.length>0){const Z=ne.map(Qe=>Qe.field);if(yt.current===null&&(yt.current=b!==void 0&&b.length>0,yt.current)){wn.current=Z;return}const be=wn.current,De=Z.filter(Qe=>!be.includes(Qe));if(De.length>0){const Qe=De.filter(se=>!tt.includes(se));if(Qe.length>0){const se=[...tt,...Qe];at(se),_?.(se)}}wn.current=Z}},[ne]),v.useEffect(()=>{S||He(1)},[n,S]),v.useEffect(()=>{const Z=be=>{Ze.current&&!Ze.current.contains(be.target)&&We(!1)};return Ce&&document.addEventListener("mousedown",Z),()=>document.removeEventListener("mousedown",Z)},[Ce]);const Ue=v.useMemo(()=>!y&&(!b||b.length===0)?Ke.filter(Z=>!Z.hidden):Ke.filter(Z=>tt.includes(Z.field)),[Ke,tt,y,b]),Ct=r&&!(S&&!u),Ut=v.useMemo(()=>!re||!r||!n?n||[]:u||S?n:[...n].sort((Z,be)=>{const De=Z[re],Qe=be[re];if(De===Qe)return 0;const se=De>Qe?1:-1;return ye==="asc"?se:-se}),[n,re,ye,r,u,S]),st=v.useMemo(()=>{if(S||!s)return Ut;const Z=(xe-1)*o,be=Z+o;return Ut.slice(Z,be)},[Ut,xe,o,s,S]),kn=S?w||1:xe,qt=S?E||1:Math.ceil((Ut?.length||0)/o),Ln=v.useCallback(Z=>{S&&D?D(Z):He(Z)},[S,D]),jr=Z=>{if(!Ct||t.find(Qe=>Qe.field===Z)?.sortable===!1)return;const De=re===Z&&ye==="asc"?"desc":"asc";fe(Z),we(De),u?.(Z,De)},fn=v.useCallback(()=>{if(!m||!st)return;const Z=st.map(De=>De[p]);if(Z.every(De=>g.includes(De)))m(g.filter(De=>!Z.includes(De)));else{const De=[...new Set([...g,...Z])];m(De)}},[st,g,m,p]),qn=v.useCallback(Z=>{m&&(g.includes(Z)?m(g.filter(be=>be!==Z)):m([...g,Z]))},[g,m]),Wo=v.useCallback(Z=>{if(Ke.find(Qe=>Qe.field===Z)?.required)return;const De=tt.includes(Z)?tt.filter(Qe=>Qe!==Z):[...tt,Z];at(De),_?.(De)},[Ke,tt,_]),Go=v.useCallback((Z,be)=>{k?.(Z,be)},[k]),Ls=v.useCallback(Z=>C?C.map(be=>{let De=be.disabled??!1;if(be.disabledField&&!De){const Qe=be.disabledField.split(".");let se=Z;for(const B of Qe)se=se?.[B];se||(De=!0)}return{...be,disabled:De,onClick:()=>Go(be.id,Z)}}):[],[C,Go]),sr=v.useCallback(Z=>{if(!q)return!0;const be=Z[q];return Array.isArray(be)?be.length>0:!!be},[q]),or=v.useCallback(Z=>{const be=_e.includes(Z)?_e.filter(De=>De!==Z):[..._e,Z];et?Le(be):U&&U(be)},[_e,U,et,Le]),Wr=v.useCallback(Z=>_e.includes(Z),[_e]),Cn=v.useCallback(Z=>Y?Y(Z):W&&W.length>0&&Be?.renderExpandContent?Be.renderExpandContent({children:W,row:Z,expandContext:ue,componentContext:J,componentMap:il(),keyPrefix:`expand-${Z[p]}`}):null,[Y,W,p,ue,J,J?.state]),Ha=v.useMemo(()=>!st||st.length===0?!1:st.every(Z=>g.includes(Z[p])),[st,g,p]),Ua=v.useMemo(()=>{if(!st||st.length===0)return!1;const Z=st.filter(be=>g.includes(be[p])).length;return Z>0&&Z{const De=Z[p],Qe=Wr(De);return h.jsxs(I,{className:"bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg p-4 mb-4",children:[h.jsxs(I,{className:"flex items-start justify-between",children:[j&&sr(Z)&&h.jsx(ae,{onClick:()=>or(De),className:"p-1.5 rounded-md hover:bg-gray-100 dark:hover:bg-gray-600 transition-colors mr-2","aria-label":Qe?"Collapse card":"Expand card",children:h.jsx(oe,{name:Qe?X:K,className:"w-4 h-4 text-gray-500 dark:text-gray-400"})}),f&&h.jsx(En,{checked:g.includes(Z[p]),onChange:()=>qn(Z[p]),className:"w-4 h-4 text-blue-600 border-gray-300 dark:border-gray-600 rounded mr-3 mt-1"}),h.jsx(I,{className:"flex-1",children:h.jsx(I,{className:"grid grid-cols-2 gap-x-4 gap-y-2",children:be.map(se=>h.jsxs(I,{className:"min-w-0",children:[h.jsx(Q,{className:"text-xs font-medium text-gray-500 dark:text-gray-400 uppercase",children:se.header}),h.jsx(I,{className:"text-sm text-gray-900 dark:text-gray-200 mt-0.5 break-words",children:se.cellChildren&&se.cellChildren.length>0?es(se.cellChildren,Z,Z[se.field],`card-${Z[p]}-${se.field}`,J):se.render?se.render(Z[se.field],Z):al(Z[se.field])})]},se.field))})}),C&&C.length>0&&h.jsx(I,{className:"ml-2",children:h.jsx(Ps,{items:Ls(Z),triggerLabel:"",triggerIconName:de.EllipsisHorizontal})})]}),j&&Qe&&sr(Z)&&h.jsx(I,{className:"mt-4 pt-4 border-t border-gray-200 dark:border-gray-700",children:Cn(Z)}),(ce&&ce.length>0||ve)&&iu(me,Z)&&h.jsx(I,{className:le?`mt-3 ${le}`:"mt-3 pt-3 border-t border-gray-100 dark:border-gray-700",children:ve?ve(Z):ll(ce,Z,`card-subrow-${De}`,J)})]})};return ze?h.jsxs(I,{id:e,className:`w-full ${a}`,style:l,...$e,children:[y&&h.jsx(I,{className:"mb-4 flex justify-end",children:h.jsxs(I,{ref:Ze,className:"relative",children:[h.jsxs(ae,{onClick:()=>We(!Ce),className:"flex items-center gap-2 px-4 py-2 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700",children:[h.jsx(oe,{name:de.Cog,className:"w-4 h-4 text-gray-500 dark:text-gray-400"}),h.jsx(Q,{className:"text-sm text-gray-700 dark:text-gray-300",children:Ae})]}),Ce&&h.jsx(I,{className:"absolute right-0 z-50 mt-2 w-64 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg",children:h.jsx(I,{className:"p-2 max-h-64 overflow-y-auto",children:Ke.map(Z=>h.jsxs(I,{className:`flex items-center gap-2 px-3 py-2 rounded hover:bg-gray-50 dark:hover:bg-gray-700 ${Z.required?"opacity-50 cursor-not-allowed":"cursor-pointer"}`,onClick:()=>!Z.required&&Wo(Z.field),children:[h.jsx(En,{checked:tt.includes(Z.field),disabled:Z.required,onChange:()=>{},className:"w-4 h-4 text-blue-600 border-gray-300 dark:border-gray-600 rounded"}),h.jsxs(Q,{className:"text-sm text-gray-700 dark:text-gray-300",children:[Z.header,Z.required&&h.jsxs(Q,{className:"text-xs text-gray-500 dark:text-gray-400 ml-1",children:["(",gt,")"]})]})]},Z.field))})})]})}),f&&g.length>0&&pt&&h.jsx(I,{className:"mb-4 text-sm text-gray-600 dark:text-gray-400",children:pt.replace("{count}",String(g.length))}),h.jsx(I,{id:e?`${e}__body`:void 0,children:st&&st.length>0?st.map((Z,be)=>h.jsx(I,{children:M?M(Z,Ue):Id(Z,Ue)},Z[p]||be)):h.jsx(I,{className:"text-center py-8 text-gray-500 dark:text-gray-400",children:Ie})}),ge&&ge.length>0&&st&&st.length>0&&h.jsx(I,{className:`rounded-lg p-4 mt-2 ${pe}`,children:ke&&ke.length>0?es(ke,{},null,"footer-card",J):h.jsx(I,{className:"space-y-1",children:ge.filter(Z=>Z.text||Z.children&&Z.children.length>0).map((Z,be)=>{const De=Ue.find(Qe=>Qe.field===Z.field);return h.jsxs(I,{className:"flex justify-between text-sm",children:[h.jsx(Q,{className:"text-gray-500 dark:text-gray-400",children:De?.header||""}),h.jsx(Q,{className:Z.className||"text-gray-900 dark:text-gray-100",children:Z.children&&Z.children.length>0?es(Z.children,{},null,`footer-mobile-${be}`,J):au(Z.text,`footer-mobile-text-${be}`,J)})]},`footer-mobile-${be}`)})})}),s&&(N||qt>1)&&h.jsx(I,{className:"flex justify-center mt-4",children:h.jsx(Xo,{currentPage:kn,totalPages:qt,onPageChange:Ln,showFirstLast:H,maxVisiblePages:5,prevText:ie,nextText:Me})})]}):h.jsxs(I,{id:e,className:`w-full ${a}`,style:l,...$e,children:[y&&h.jsx(I,{className:"mb-4 flex justify-end",children:h.jsxs(I,{ref:Ze,className:"relative",children:[h.jsxs(ae,{onClick:()=>We(!Ce),className:"flex items-center gap-2 px-4 py-2 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700",children:[h.jsx(oe,{name:de.Cog,className:"w-4 h-4 text-gray-500 dark:text-gray-400"}),h.jsx(Q,{className:"text-sm text-gray-700 dark:text-gray-300",children:Ae})]}),Ce&&h.jsx(I,{className:"absolute right-0 z-50 mt-2 w-64 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg",children:h.jsx(I,{className:"p-2 max-h-64 overflow-y-auto",children:Ke.map(Z=>h.jsxs(I,{className:`flex items-center gap-2 px-3 py-2 rounded hover:bg-gray-50 dark:hover:bg-gray-700 ${Z.required?"opacity-50 cursor-not-allowed":"cursor-pointer"}`,onClick:()=>!Z.required&&Wo(Z.field),children:[h.jsx(En,{checked:tt.includes(Z.field),disabled:Z.required,onChange:()=>{},className:"w-4 h-4 text-blue-600 border-gray-300 dark:border-gray-600 rounded"}),h.jsxs(Q,{className:"text-sm text-gray-700 dark:text-gray-300",children:[Z.header,Z.required&&h.jsxs(Q,{className:"text-xs text-gray-500 dark:text-gray-400 ml-1",children:["(",gt,")"]})]})]},Z.field))})})]})}),f&&g.length>0&&pt&&h.jsx(I,{className:"mb-4 text-sm text-gray-600 dark:text-gray-400",children:pt.replace("{count}",String(g.length))}),h.jsx(I,{id:e?`${e}__body`:void 0,className:"overflow-x-auto border border-gray-200 dark:border-gray-700 rounded-lg",children:h.jsxs(Ya,{className:"w-full",children:[h.jsx(Xa,{className:"bg-gray-50 dark:bg-gray-700",children:h.jsxs(lr,{children:[j&&h.jsx(Kr,{className:"px-2 py-3",style:{width:V}}),f&&h.jsx(Kr,{className:"px-4 py-3 w-10",children:h.jsx(En,{checked:Ha,ref:Z=>{Z&&(Z.indeterminate=Ua)},onChange:fn,className:"w-4 h-4 text-blue-600 border-gray-300 dark:border-gray-600 rounded"})}),Ue.map(Z=>h.jsxs(Kr,{className:`px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider ${Ct&&Z.sortable!==!1?"cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-600":""}`,style:{width:Z.width,minWidth:Z.width},onClick:Ct?()=>jr(Z.field):void 0,children:[Z.header,re===Z.field&&h.jsx(Q,{className:"ml-1",children:ye==="asc"?"↑":"↓"})]},Z.field)),C&&C.length>0&&h.jsx(Kr,{className:"px-6 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider w-24",children:Xe})]})}),h.jsx(Ka,{className:`bg-white dark:bg-gray-800 ${le?"":"divide-y divide-gray-200 dark:divide-gray-700"}`,children:st&&st.length>0?st.map((Z,be)=>{const De=Z[p]||be,Qe=Wr(De);return h.jsxs(v.Fragment,{children:[h.jsxs(lr,{className:`${i?"cursor-pointer":""} hover:bg-gray-50 dark:hover:bg-gray-700 ${g.includes(Z[p])?"bg-blue-50 dark:bg-blue-900/20":""} ${le&&be>0?"border-t border-gray-200 dark:border-gray-700":""}`,onClick:()=>i?.(Z),children:[j&&h.jsx(en,{className:"px-2 py-4 text-center",onClick:se=>se.stopPropagation(),children:sr(Z)?h.jsx(ae,{onClick:()=>or(De),className:"p-1.5 rounded-md hover:bg-gray-100 dark:hover:bg-gray-600 transition-colors","aria-label":Qe?"Collapse row":"Expand row",children:h.jsx(oe,{name:Qe?X:K,className:"w-4 h-4 text-gray-500 dark:text-gray-400"})}):null}),f&&h.jsx(en,{className:"px-4 py-4",onClick:se=>se.stopPropagation(),children:h.jsx(En,{checked:g.includes(Z[p]),onChange:()=>qn(Z[p]),className:"w-4 h-4 text-blue-600 border-gray-300 dark:border-gray-600 rounded"})}),Ue.map(se=>h.jsx(en,{className:"px-6 py-4 align-top whitespace-nowrap text-sm text-gray-900 dark:text-gray-200",style:{width:se.width,minWidth:se.width},children:se.cellChildren&&se.cellChildren.length>0?es(se.cellChildren,Z,Z[se.field],`${Z[p]}-${se.field}`,J):se.render?se.render(Z[se.field],Z):al(Z[se.field])},se.field)),C&&C.length>0&&h.jsx(en,{className:"px-6 py-4 text-right",onClick:se=>se.stopPropagation(),children:h.jsx(Ps,{items:Ls(Z),triggerLabel:"",triggerIconName:de.EllipsisHorizontal,position:"right"})})]}),j&&Qe&&sr(Z)&&h.jsx(lr,{className:"bg-gray-50 dark:bg-gray-900",children:h.jsx(en,{colSpan:Ue.length+1+(f?1:0)+(C&&C.length>0?1:0),className:"px-6 py-4",children:Cn(Z)})}),(ce&&ce.length>0||ve)&&iu(me,Z)&&h.jsx(lr,{className:le?"!border-t-0":"",children:h.jsx(en,{colSpan:Ue.length+(j?1:0)+(f?1:0)+(C&&C.length>0?1:0),className:le?"p-2":le||"px-6 py-2 bg-gray-50 dark:bg-gray-900",children:le?h.jsx(I,{className:le,children:ve?ve(Z):ll(ce,Z,`subrow-${De}`,J)}):ve?ve(Z):ll(ce,Z,`subrow-${De}`,J)})})]},De)}):h.jsx(lr,{children:h.jsx(en,{colSpan:Ue.length+(j?1:0)+(f?1:0)+(C&&C.length>0?1:0),className:"px-6 py-8 text-center text-gray-500 dark:text-gray-400",children:Ie})})}),ge&&ge.length>0&&st&&st.length>0&&h.jsx(qa,{children:h.jsxs(lr,{className:pe,children:[j&&h.jsx(en,{className:"px-2 py-4"}),f&&h.jsx(en,{className:"px-4 py-4"}),(()=>{const Z=new Map(ge.filter(be=>be.field).map(be=>[be.field,be]));return Ue.map(be=>{const De=Z.get(be.field);return De?h.jsx(en,{className:`px-6 py-4 text-sm text-gray-900 dark:text-gray-200 ${De.className||""}`,colSpan:De.colSpan,style:{width:be.width,minWidth:be.width},children:De.children&&De.children.length>0?es(De.children,{},null,`footer-${be.field}`,J):au(De.text,`footer-text-${be.field}`,J)},`footer-${be.field}`):h.jsx(en,{className:"px-6 py-4",style:{width:be.width,minWidth:be.width}},`footer-${be.field}`)})})(),C&&C.length>0&&h.jsx(en,{className:"px-6 py-4"})]})})]})}),s&&(N||qt>1)&&h.jsx(I,{className:"flex justify-center mt-4",children:h.jsx(Xo,{currentPage:kn,totalPages:qt,onPageChange:Ln,showFirstLast:H,maxVisiblePages:5,prevText:ie,nextText:Me})})]})},fy=window.G7Core?.createLogger?.("Comp:CardGrid")??{log:(...e)=>console.log("[Comp:CardGrid]",...e),warn:(...e)=>console.warn("[Comp:CardGrid]",...e),error:(...e)=>console.error("[Comp:CardGrid]",...e)},hy=(e,t)=>window.G7Core?.t?.(e,t)??e,gy=()=>window.G7Core?.getComponentMap?.()||{},py=(e,t)=>{if(!e)return!0;const n=window.G7Core;return typeof n?.evaluateCondition=="function"?n.evaluateCondition(e,{row:t,item:t,$item:t}):(fy.warn("G7Core.evaluateCondition 미노출 — 조건을 평가할 수 없습니다:",e),!0)},lu=(e,t,n,r,s)=>{const o=window.G7Core;if(e.name==="ActionMenu"){if(r&&r.length>0&&s)return{...e,props:{...e.props,items:r.map(i=>({...i,onClick:()=>s(i.id,t)}))}};if(e.actions&&Array.isArray(e.actions)){const i=e.props?.items||[],a=new Map(e.actions.map(c=>[c.id,c])),l=i.map(c=>{if(typeof c.onClick=="string"){const d=c.onClick,u=a.get(d);if(u&&o?.ActionDispatcher)return{...c,onClick:()=>{const f={...n};o.ActionDispatcher.dispatch(u,f)}}}return c});return{...e,props:{...e.props,items:l}}}}return e.children&&Array.isArray(e.children)?{...e,children:e.children.map(i=>lu(i,t,n,r,s))}:e},my=(e,t,n,r="",s,o)=>{const i=window.G7Core;if(i?.renderItemChildren){const a=e.filter(d=>{const u=d.condition||d.if;return u?py(u,t):!0}),l={row:t,$value:t,index:n},c=a.map(d=>lu(d,t,l,s,o));try{const d=gy();return i.renderItemChildren(c,l,d,r)}catch{return h.jsxs(I,{className:"text-red-600 dark:text-red-400 p-4",children:[h.jsx(vt,{children:"컴포넌트 로드 실패"}),h.jsx(vt,{className:"text-sm",children:"[CardGrid] 데이터를 표시할 수 없습니다."})]})}}return null},cl=(e,t)=>{if(e.name==="Div"&&e.children&&e.children.length>0){const n=e.children.map((r,s)=>cl(r,`${t}-child-${s}`)).filter(Boolean);return h.jsx(I,{className:e.props?.className||"",children:n},t)}return h.jsx(I,{className:"h-4 bg-gray-200 dark:bg-gray-700 rounded w-full animate-pulse"},t)},cu=(e,t="skeleton")=>!e||e.length===0?null:e.map((n,r)=>{const s=`${t}-${n.id||n.name}-${r}`;return n.condition||n.if?null:(n.iteration,cl(n,s))}).filter(Boolean),by=({data:e,cardColumns:t=[],gridColumns:n=3,gap:r=4,responsiveColumns:s,cardClassName:o="",idField:i="id",pagination:a=!0,pageSize:l=12,serverSidePagination:c=!1,serverCurrentPage:d,serverTotalPages:u,alwaysShowPagination:f=!1,onPageChange:g,paginationInfoText:m,rowActions:p,onRowAction:y,className:b="",style:_,emptyMessage:x,prevText:M,nextText:C,showSkeleton:k=!0,skeletonCount:N,skeletonCellChildren:S,id:w,editorAttrs:E})=>{const D=x??hy("common.no_data"),P=t[0]?.cellChildren||[],[L,$]=v.useState(1);window.G7Core?.useResponsive?.();const O=c?d??1:L,T=c?u??1:Math.ceil((e?.length??0)/l),z=Y=>{c?g?.(Y):$(Y)},H=v.useCallback((Y,V)=>{y?.(Y,V)},[y]),j=v.useMemo(()=>{if(!e)return[];if(!a||c)return e;const Y=(L-1)*l,V=Y+l;return e.slice(Y,V)},[e,a,c,L,l]),G=v.useMemo(()=>{const Y=[],V=s?.sm??1;Y.push(`grid-cols-${V}`),s?.sm&&Y.push(`sm:grid-cols-${s.sm}`);const X=s?.md??Math.min(n,2);Y.push(`md:grid-cols-${X}`);const K=s?.lg??n;return Y.push(`lg:grid-cols-${K}`),s?.xl&&Y.push(`xl:grid-cols-${s.xl}`),Y.join(" ")},[n,s]),U=v.useMemo(()=>k?S&&S.length>0?cu(S,"custom-skeleton"):P&&P.length>0?cu(P,"auto-skeleton"):null:null,[k,S,P]);if(!e){if(!k)return null;const Y=N??n;return h.jsx(I,{className:`grid ${G} gap-${r} ${b}`,style:_,id:w,...E,children:Array.from({length:Y}).map((V,X)=>h.jsx(I,{className:"bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg p-6 space-y-4",children:U},`skeleton-${X}`))})}if(j.length===0)return h.jsx(I,{className:b,style:_,id:w,...E,children:h.jsx(I,{className:"bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg p-12",children:h.jsx(vt,{className:"text-center text-gray-600 dark:text-gray-400",children:D})})});const W=a&&(f||T>1);return h.jsxs(I,{className:b,style:_,id:w,...E,children:[h.jsx(I,{className:`grid ${G} gap-${r}`,children:j.map((Y,V)=>h.jsx(I,{className:o,children:my(P,Y,V,`card-${V}`,p,H)},Y[i]||V))}),W&&h.jsxs(I,{className:"mt-6 space-y-4 flex flex-col items-center",children:[h.jsx(Xo,{currentPage:O,totalPages:T,onPageChange:z,showFirstLast:!1,maxVisiblePages:5,prevText:M,nextText:C}),m&&h.jsx(I,{className:"text-sm text-gray-600 dark:text-gray-400 text-center",children:m})]})]})},yy=(e,t)=>window.G7Core?.t?.(e,t)??e,Qo=({isOpen:e,onClose:t,title:n,titlePrefix:r,titleSuffix:s,closeOnBackdropClick:o=!0,closeOnOverlayClick:i,showCloseButton:a=!0,closeOnEscape:l=!0,children:c,width:d="500px",className:u="",style:f,__dataContext:g,__translationContext:m})=>{const p=i!==void 0?i:o,y=v.useRef(null),b=v.useRef(null);if(v.useEffect(()=>{const M=C=>{C.key==="Escape"&&e&&l&&t()};if(e&&l)return document.addEventListener("keydown",M),()=>document.removeEventListener("keydown",M)},[e,t,l]),v.useEffect(()=>{if(e&&y.current){b.current=document.activeElement;const M=y.current.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'),C=M[0],k=M[M.length-1];C?.focus();const N=S=>{S.key==="Tab"&&(S.shiftKey?document.activeElement===C&&(S.preventDefault(),k?.focus()):document.activeElement===k&&(S.preventDefault(),C?.focus()))};return document.addEventListener("keydown",N),()=>{document.removeEventListener("keydown",N),b.current?.focus()}}},[e]),v.useEffect(()=>{if(e)return document.body.style.overflow="hidden",()=>{document.body.style.overflow=""}},[e]),!e)return null;const _=f?.zIndex??50,x=(M,C)=>{if(!M||M.length===0)return null;const k=window.G7Core;if(!k)return null;const N=k.getDynamicRenderer?.();return N?M.map((S,w)=>h.jsx(N,{componentDef:S,dataContext:g||{},translationContext:m,registry:k.getComponentRegistry(),bindingEngine:k.getDataBindingEngine(),translationEngine:k.getTranslationEngine(),actionDispatcher:k.getActionDispatcher(),isRootRenderer:!1},`${C}-${w}`)):null};return h.jsxs(I,{className:"modal-wrapper",style:{zIndex:_},onClick:p?t:void 0,children:[h.jsx(I,{className:"modal-backdrop","aria-hidden":"true"}),h.jsxs(I,{ref:y,className:`modal-container ${u}`,style:{width:d,...f?{...f,zIndex:void 0}:{}},onClick:M=>M.stopPropagation(),role:"dialog","aria-modal":"true","aria-labelledby":n?"modal-title":void 0,children:[(n||r||s||a)&&h.jsxs(I,{className:"modal-header-row",children:[h.jsxs(I,{className:"flex-center gap-sm",children:[x(r,"title-prefix"),n&&h.jsx(I,{id:"modal-title",className:"modal-title",children:n}),x(s,"title-suffix")]}),a&&h.jsx(ae,{onClick:t,className:"modal-close-btn","aria-label":yy("common.close_modal"),children:h.jsx("svg",{className:"icon-lg",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:h.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M6 18L18 6M6 6l12 12"})})})]}),h.jsx(I,{className:"modal-body-content",children:c})]})]})},vy=({label:e,items:t,onItemClick:n,position:r="bottom-left",variant:s="default",className:o="",style:i,id:a,editorAttrs:l})=>{const[c,d]=v.useState(!1),[u,f]=v.useState(-1),g=v.useRef(null),m=v.useRef(null),[p,y]=v.useState(r);v.useEffect(()=>{const x=M=>{g.current&&!g.current.contains(M.target)&&(d(!1),f(-1))};if(c)return document.addEventListener("mousedown",x),()=>document.removeEventListener("mousedown",x)},[c]),v.useEffect(()=>{const x=M=>{if(!c)return;const C=t.map((k,N)=>k.disabled?-1:N).filter(k=>k!==-1);switch(M.key){case"ArrowDown":M.preventDefault(),f(k=>{const S=(C.indexOf(k)+1)%C.length;return C[S]});break;case"ArrowUp":M.preventDefault(),f(k=>{const N=C.indexOf(k),S=N<=0?C.length-1:N-1;return C[S]});break;case"Enter":M.preventDefault(),u>=0&&!t[u].disabled&&b(t[u]);break;case"Escape":M.preventDefault(),d(!1),f(-1);break}};if(c)return document.addEventListener("keydown",x),()=>document.removeEventListener("keydown",x)},[c,u,t]),v.useEffect(()=>{if(c&&m.current&&g.current){const x=m.current.getBoundingClientRect(),M=g.current.getBoundingClientRect(),C=window.innerWidth,k=window.innerHeight,N=x.width||224,S=x.height||200;let w=r;r.includes("left")?M.left+N>C&&(w=w.replace("left","right")):M.right-N<0&&(w=w.replace("right","left")),r.includes("top")?M.top-S<0&&(w=w.replace("top","bottom")):M.bottom+S>k&&(w=w.replace("bottom","top")),y(w)}},[c,r]);const b=x=>{x.disabled||(x.onClick?.(),n?.(x.value,x),d(!1),f(-1))},_={"bottom-left":"dropdown-menu-bottom-left","bottom-right":"dropdown-menu-bottom-right","top-left":"dropdown-menu-top-left","top-right":"dropdown-menu-top-right"};return h.jsxs(I,{ref:g,className:`dropdown ${o}`,style:i,id:a,...l,children:[h.jsxs(ae,{onClick:()=>d(!c),className:s==="text"?"dropdown-trigger-text":"dropdown-trigger","aria-haspopup":"true","aria-expanded":c,children:[e,s!=="text"&&h.jsx("svg",{className:`inline-block ml-2 w-4 h-4 transition-transform ${c?"rotate-180":""}`,fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:h.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M19 9l-7 7-7-7"})})]}),c&&h.jsx(I,{ref:m,className:`dropdown-menu ${_[p]}`,role:"menu","aria-orientation":"vertical",children:h.jsx(I,{className:"py-1",children:t.map((x,M)=>h.jsx(I,{className:`${x.disabled?"dropdown-item-disabled":"dropdown-item"} ${u===M?"dropdown-item-focused":""}`,onClick:()=>b(x),onMouseEnter:()=>!x.disabled&&f(M),role:"menuitem","aria-disabled":x.disabled,tabIndex:x.disabled?-1:0,children:x.label},x.value))})})]})},xy=({imageUrl:e,imageAlt:t="",title:n,subtitle:r,description:s,price:o,originalPrice:i,discountRate:a,currency:l="₩",buttonText:c="자세히 보기",onButtonClick:d,className:u="",style:f,id:g,editorAttrs:m})=>{const p=a&&a>0,y=(o??0).toLocaleString(),b=i?.toLocaleString();return h.jsxs(I,{className:`rounded-lg bg-white dark:bg-gray-800 shadow-md hover:shadow-xl transition-shadow border border-gray-200 dark:border-gray-700 overflow-hidden ${u}`,style:f,id:g,...m,children:[e&&h.jsxs(I,{className:"relative w-full",children:[h.jsx(_n,{src:e,alt:t||n,className:"w-full h-64 object-cover"}),p&&h.jsx(I,{className:"absolute top-2 right-2 bg-red-500 text-white px-2 py-1 rounded-md",children:h.jsxs(Q,{className:"text-sm font-bold",children:["-",a,"%"]})})]}),h.jsxs(I,{className:"p-4",children:[r&&h.jsx(Gr,{className:"text-xs font-medium text-blue-600 dark:text-blue-400 uppercase tracking-wider mb-2",children:r}),h.jsx(ar,{className:"text-xl font-bold text-gray-900 dark:text-white mb-2 line-clamp-2",children:n}),s&&h.jsx(vt,{className:"text-sm text-gray-600 dark:text-gray-400 mb-4 line-clamp-3",children:s}),h.jsxs(I,{className:"flex items-center gap-2 mb-4",children:[h.jsxs(Q,{className:"text-2xl font-bold text-gray-900 dark:text-white",children:[l,y]}),i&&h.jsxs(Q,{className:"text-sm text-gray-400 dark:text-gray-500 line-through",children:[l,b]})]}),c&&h.jsx(ae,{onClick:d,className:"w-full bg-blue-600 hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600 text-white font-medium py-2 px-4 rounded-lg transition-colors",children:c})]})]})},wy=({value:e,label:t,change:n,changeLabel:r="전월 대비",iconName:s,trend:o="neutral",className:i="",style:a,id:l,editorAttrs:c})=>{const d=o==="up",u=o==="down",f=n!==void 0?Math.abs(n):0,g=d?"stats-trend-up":u?"stats-trend-down":"stats-trend-neutral",m=d?de.ArrowUp:u?de.ArrowDown:void 0;return h.jsxs(I,{className:`stat-card ${i}`,style:a,id:l,...c,children:[h.jsxs(I,{className:"flex-between mb-md",children:[s&&h.jsx(I,{className:"stats-icon stats-icon-blue",children:h.jsx(oe,{name:s,className:"icon-lg"})}),n!==void 0&&h.jsxs(I,{className:`stats-change ${g}`,children:[m&&h.jsx(oe,{name:m,className:"icon-sm"}),h.jsxs(Q,{className:"text-sm font-semibold",children:[f,"%"]})]})]}),h.jsx(Gr,{className:"stats-value-lg",children:typeof e=="number"?e.toLocaleString():e}),h.jsx(vt,{className:"stats-label",children:t}),n!==void 0&&r&&h.jsx(I,{className:"stats-sublabel",children:h.jsx(Q,{children:r})})]})},du=()=>window.G7Core,uu=(e,t)=>du()?.t?.(e,t)??e,ky=({name:e="search",placeholder:t,value:n,onChange:r,onSubmit:s,showButton:o=!1,suggestions:i=[],onSuggestionClick:a,showSuggestions:l=!1,className:c="",style:d,id:u,editorAttrs:f})=>{const g=t??uu("common.search_placeholder"),[m,p]=v.useState(""),[y,b]=v.useState(!1),_=v.useRef(null),x=v.useRef(""),M=n!==void 0?n:m,C=l&&y&&i.length>0&&M.length>0,k="w-64 pl-10 pr-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white text-sm",N=du()?.style?.mergeClasses?.(k,c)??`${k} ${c}`;v.useEffect(()=>{n!==void 0&&(x.current=n)},[n]);const S=D=>{const P=D.target.value;p(P),r?.(D),x.current!==""&&P===""&&!D.nativeEvent.inputType&&setTimeout(()=>{const L=_.current?.querySelector("form");L&&L.requestSubmit()},0),x.current=P},w=D=>{p(D.text);const P={target:{value:D.text,name:e}};r?.(P),a?.(D),b(!1)},E=D=>{D.preventDefault(),s?.(D)};return h.jsxs(I,{ref:_,className:"relative",style:d,id:u,...f,children:[h.jsx(Ga,{onSubmit:E,className:"relative",children:h.jsxs(I,{className:`relative flex items-center ${o?"gap-2":""}`,children:[h.jsxs(I,{className:"relative flex-1",children:[h.jsx(I,{className:"absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none",children:h.jsx(oe,{name:de.Search,className:"w-5 h-5 text-gray-400 dark:text-gray-500"})}),h.jsx(St,{type:"search",name:e,value:M,onChange:S,onFocus:()=>b(!0),onBlur:()=>setTimeout(()=>b(!1),200),placeholder:g,className:N})]}),o&&h.jsx(ae,{type:"submit",className:"px-4 py-2 bg-gray-800 dark:bg-gray-600 text-white rounded-lg hover:bg-gray-900 dark:hover:bg-gray-500 transition-colors text-sm font-medium cursor-pointer h-[42px]",children:uu("common.search")})]})}),C&&h.jsx(I,{className:"absolute z-10 w-full mt-2 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg max-h-64 overflow-y-auto",children:i.map(D=>h.jsx(I,{className:"px-4 py-3 hover:bg-gray-50 dark:hover:bg-gray-700 cursor-pointer border-b border-gray-100 dark:border-gray-700 last:border-b-0 transition-colors",onClick:()=>w(D),children:h.jsxs(I,{className:"flex items-center gap-2",children:[h.jsx(oe,{name:de.Search,className:"w-4 h-4 text-gray-400 dark:text-gray-500"}),h.jsx(Q,{className:"text-sm text-gray-700 dark:text-gray-300",children:D.text})]})},D.id))})]})},dl=(e,t)=>window.G7Core?.t?.(e,t)??e,Cy=({title:e,filters:t,onChange:n,onReset:r,showResetButton:s=!0,className:o="",style:i,id:a,editorAttrs:l})=>{const c=e??dl("common.filter"),d=(f,g)=>{n?.(f,g)},u=(f,g,m)=>{const p=t.find(b=>b.id===f);if(!p)return;const y=Array.isArray(p.value)?p.value:[];if(typeof g=="string"){const b=y.filter(x=>typeof x=="string"),_=m?[...b,g]:b.filter(x=>x!==g);n?.(f,_)}else{const b=y.filter(x=>typeof x=="number"),_=m?[...b,g]:b.filter(x=>x!==g);n?.(f,_)}};return h.jsxs(I,{className:`rounded-lg bg-white dark:bg-gray-800 shadow-md border border-gray-200 dark:border-gray-700 p-4 ${o}`,style:i,id:a,...l,children:[h.jsxs(I,{className:"flex items-center justify-between mb-4 pb-3 border-b border-gray-200 dark:border-gray-700",children:[h.jsx(Q,{className:"text-lg font-semibold text-gray-900 dark:text-white",children:c}),s&&r&&h.jsx(ae,{onClick:r,className:"text-sm text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300 font-medium",children:dl("common.reset")})]}),h.jsx(I,{className:"space-y-4",children:t.map(f=>h.jsxs(I,{className:"space-y-2",children:[h.jsx(ln,{className:"block text-sm font-medium text-gray-700 dark:text-gray-300",children:f.label}),f.type==="select"&&h.jsxs(Xr,{value:f.value,onChange:g=>d(f.id,g.target.value),className:"w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400 focus:border-blue-500 dark:focus:border-blue-400 bg-white dark:bg-gray-700 text-gray-900 dark:text-white",children:[h.jsx("option",{value:"",children:dl("common.select_placeholder")}),f.options.map(g=>h.jsx("option",{value:g.value,children:g.label},g.id))]}),f.type==="checkbox"&&h.jsx(I,{className:"space-y-2",children:f.options.map(g=>{const m=Array.isArray(f.value)?f.value.some(p=>p===g.value):!1;return h.jsxs(ln,{className:"flex items-center gap-2 cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-700 p-2 rounded-md transition-colors",children:[h.jsx(En,{checked:m,onChange:p=>u(f.id,g.value,p.target.checked),className:"w-4 h-4 text-blue-600 border-gray-300 dark:border-gray-600 rounded focus:ring-blue-500"}),h.jsx(Q,{className:"text-sm text-gray-700 dark:text-gray-300",children:g.label})]},g.id)})})]},f.id))})]})},Sy=({tabs:e,activeTabId:t,onTabChange:n,variant:r="default",className:s="",style:o,mobileBreakpoint:i=768,id:a,editorAttrs:l})=>{const d=window.G7Core?.useResponsive,u=d?.(),f=u?u.width{!k.disabled&&k.id!==t&&n?.(k.id)},p=(k,N)=>{const S=k.key==="ArrowRight"?1:k.key==="ArrowLeft"?-1:0;let w=null;if(S!==0)for(let E=1;E<=e.length;E+=1){const D=(N+S*E+e.length*E)%e.length;if(!e[D]?.disabled){w=D;break}}else if(k.key==="Home")w=e.findIndex(E=>!E.disabled);else if(k.key==="End"){for(let E=e.length-1;E>=0;E-=1)if(!e[E]?.disabled){w=E;break}}w===null||w<0||(k.preventDefault(),g.current[w]?.focus(),m(e[w]))},y=k=>{const N=k.target.value,S=Number(N),w=typeof N=="string"&&N!==""&&!Number.isNaN(S)?S:N,E=e.find(D=>String(D.id)===String(w));E&&m(E)};if(f)return h.jsx(I,{className:s,style:o,id:a,...l,children:h.jsx(Xr,{value:t!==void 0?String(t):"",onChange:y,options:e.map(k=>({value:String(k.id),label:k.badge!==void 0?`${k.label} (${k.badge})`:k.label,disabled:k.disabled})),className:"w-full"})});const b=k=>{const N=k.id===t;if(k.disabled)return"tab-btn-base tab-btn-disabled";switch(r){case"pills":return N?"tab-btn-base tab-btn-pills-active":"tab-btn-base tab-btn-pills";case"underline":return N?"tab-btn-base tab-btn-underline-active":"tab-btn-base tab-btn-underline";default:return N?"tab-btn-base tab-btn-default-active":"tab-btn-base tab-btn-default"}},_=r==="underline"?"tab-nav-underline":"tab-nav-default",x=l?.["data-editor-path"],M=e.findIndex(k=>k.id===t),C=M>=0?M:e.findIndex(k=>!k.disabled);return h.jsx(Yo,{className:`${_} ${s}`,style:o,role:"tablist","aria-orientation":"horizontal",id:a,...l,children:e.map((k,N)=>h.jsxs(ae,{ref:S=>{g.current[N]=S},type:"button",role:"tab","aria-selected":k.id===t,tabIndex:N===C?0:-1,onClick:()=>m(k),onKeyDown:S=>p(S,N),disabled:k.disabled,className:b(k),...x?{"data-editor-item-path":`${x}.props.tabs.${N}`}:{},children:[k.iconName&&h.jsx(oe,{name:k.iconName,className:"w-4 h-4"}),h.jsx(Q,{children:k.label}),k.badge!==void 0&&h.jsx(I,{className:"flex items-center justify-center min-w-[20px] h-5 px-1.5 bg-red-500 dark:bg-red-600 text-white dark:text-white text-xs font-bold rounded-full",children:h.jsx(Q,{children:k.badge})})]},k.id))})},ul=window.G7Core?.createLogger?.("Handler:ScrollToSection")??{log:(...e)=>console.log("[Handler:ScrollToSection]",...e),warn:(...e)=>console.warn("[Handler:ScrollToSection]",...e),error:(...e)=>console.error("[Handler:ScrollToSection]",...e)};async function fu(e,t){const n=e.params||{},{targetId:r,offset:s=120,delay:o=100,scrollContainerId:i}=n;if(!r){ul.warn("targetId is required");return}const a=await _y(r,o);if(!a){ul.warn(`Element not found: ${r}`);return}const l=Ey(a,i),c=My(a,l,s);Ny(l,c)}async function _y(e,t){await new Promise(s=>setTimeout(s,t));const n=10,r=50;for(let s=0;ssetTimeout(i,50)),o;await new Promise(i=>setTimeout(i,r))}return null}function Ey(e,t){if(t){const o=document.getElementById(t);if(o&&hu(o))return o;ul.warn(`Container '${t}' ${o?"has no scroll":"not found"}, trying auto-detect`)}let n=e.parentElement,r=0;const s=10;for(;n&&n!==document.body&&re.clientHeight}function My(e,t,n){if(t===window||!("getBoundingClientRect"in t))return e.getBoundingClientRect().top+window.pageYOffset-n;{const r=t.getBoundingClientRect(),s=e.getBoundingClientRect();return t.scrollTop+(s.top-r.top)-n}}function Ny(e,t){e===window||!("scrollTo"in e)?window.scrollTo({top:t,behavior:"smooth"}):e.scrollTo({top:t,behavior:"smooth"})}window.G7Core?.createLogger?.("Comp:TabNavigation");const Ty=({tabs:e,activeTabId:t,className:n="bg-white dark:bg-gray-800 flex gap-2 border-b border-gray-200 dark:border-gray-700 px-6",style:r,activeClassName:s="tab-btn-base tab-btn-default-active",inactiveClassName:o="tab-btn-base tab-btn-default",sectionIdPrefix:i="tab_content_",scrollOffset:a=120,scrollDelay:l=100,onTabChange:c,enableScrollSpy:d=!1,scrollSpyOffset:u=80,scrollContainerId:f,id:g,editorAttrs:m})=>{const[p,y]=v.useState(t??(e.length>0?e[0].id:"")),_=window.G7Core?.useResponsive,x=_?.(),M=x?x.width<768:typeof window<"u"&&window.innerWidth<768,C=v.useRef(!1),k=v.useRef(null),N=v.useRef([]),S=L=>{if(d&&(C.current=!0,k.current&&clearTimeout(k.current)),L.onClick){if(L.onClick(L.id),!d)return}else if(c){if(c(L.id),!d)return}else y(L.id);const $=L.scrollOffset??a,A=L.scrollDelay??l;fu({params:{targetId:`${i}${L.id}`,offset:$,delay:A,scrollContainerId:f}}),d&&(k.current=setTimeout(()=>{C.current=!1},A+500))},w=(L,$)=>{const A=L.key==="ArrowRight"?1:L.key==="ArrowLeft"?-1:0;let O=null;if(A!==0)for(let T=1;T<=e.length;T+=1){const z=($+A*T+e.length*T)%e.length;if(!e[z]?.disabled){O=z;break}}else if(L.key==="Home")O=e.findIndex(T=>!T.disabled);else if(L.key==="End"){for(let T=e.length-1;T>=0;T-=1)if(!e[T]?.disabled){O=T;break}}O===null||O<0||(L.preventDefault(),N.current[O]?.focus(),S(e[O]))};v.useEffect(()=>{if(!d)return;const L=f?document.getElementById(f):null,$={root:L??null,rootMargin:`-${u}px 0px -30% 0px`,threshold:[0,.1,.2,.3,.4,.5,.6,.7,.8,.9,1]},A=new Map,O=()=>{const j=L,G=j?j.scrollTop:window.pageYOffset||document.documentElement.scrollTop,U=j?j.scrollHeight:document.documentElement.scrollHeight,W=j?j.clientHeight:document.documentElement.clientHeight;return G+W>=U-10},T=j=>{if(C.current)return;j.forEach(X=>{const K=X.target.id;X.isIntersecting?A.set(K,X.intersectionRatio):A.delete(K)});let G=0,U="";A.forEach((X,K)=>{X>G&&(G=X,U=K)});const W=e[e.length-1],Y=`${i}${W?.id}`,V=A.has(Y);if(O()&&V&&W){y(W.id);return}if(U){const X=U.replace(i,""),K=Number(X),q=isNaN(K)?X:K;e.some(ee=>ee.id===q)&&y(q)}},z=()=>{},H=new IntersectionObserver(T,$);return e.forEach(j=>{const G=document.getElementById(`${i}${j.id}`);G&&H.observe(G)}),window.addEventListener("scroll",z,{passive:!0}),()=>{H.disconnect(),window.removeEventListener("scroll",z),k.current&&clearTimeout(k.current)}},[d,e,i,u,f]);const E=L=>{const $=L.target.value,A=Number($),O=isNaN(A)?$:A,T=e.find(z=>z.id===O);T&&S(T)};if(M)return h.jsx(I,{className:"bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 px-4 py-3",id:g,...m,children:h.jsx(Xr,{value:String(p),onChange:E,options:e.map(L=>({value:String(L.id),label:L.label,disabled:L.disabled})),className:"w-full"})});const D=e.findIndex(L=>L.id===p),P=D>=0?D:e.findIndex(L=>!L.disabled);return h.jsx(I,{className:n,style:r,role:"tablist","aria-orientation":"horizontal",id:g,...m,children:e.map((L,$)=>h.jsxs(ae,{ref:A=>{N.current[$]=A},type:"button",role:"tab","aria-selected":p===L.id,tabIndex:$===P?0:-1,className:p===L.id?s:o,disabled:L.disabled,onClick:()=>S(L),onKeyDown:A=>w(A,$),children:[L.iconName&&h.jsx(oe,{name:L.iconName,size:"sm"}),h.jsx(Q,{children:L.label})]},L.id))})},Oy=()=>window.G7Core,Ay=({iconName:e,label:t,onClick:n,variant:r="primary",size:s="md",disabled:o=!1,className:i="",style:a,id:l,editorAttrs:c})=>{const d={sm:t?"px-3 py-1.5 text-sm":"w-8 h-8",md:t?"px-4 py-2 text-base":"w-10 h-10",lg:t?"px-5 py-2.5 text-lg":"w-12 h-12"},u={sm:"w-4 h-4",md:"w-5 h-5",lg:"w-6 h-6"},f={primary:"bg-blue-600 hover:bg-blue-700 text-white border-transparent",secondary:"bg-gray-100 dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-gray-600 text-gray-700 dark:text-gray-300 border-gray-300 dark:border-gray-600",danger:"bg-red-600 hover:bg-red-700 text-white border-transparent",ghost:"bg-transparent hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300 border-transparent",outline:"bg-transparent hover:bg-gray-50 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300 border-gray-300 dark:border-gray-600"},g="inline-flex items-center justify-center gap-2 font-medium rounded-lg border transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 dark:focus:ring-blue-400",m=o?"opacity-50 cursor-not-allowed":"",p=`${g} ${d[s]} ${f[r]} ${m}`,y=Oy()?.style?.mergeClasses?.(p,i)??`${p} ${i}`;return h.jsxs(ae,{onClick:n,disabled:o,className:y,style:a,id:l,...c,children:[h.jsx(oe,{name:e,className:u[s]}),t]})},Dy=(e,t)=>window.G7Core?.t?.(e,t)??e,gu=({isOpen:e,onClose:t,title:n,content:r,actions:s=[],width:o="500px",closeOnOverlay:i=!0,showCloseButton:a=!0,className:l="",style:c})=>{const d=()=>{i&&t()},u={primary:"dialog-btn-primary",secondary:"dialog-btn-secondary",danger:"dialog-btn-danger",ghost:"dialog-btn-ghost"};return h.jsxs(Qo,{isOpen:e,onClose:d,width:o,className:l,style:c,children:[h.jsxs(I,{className:"dialog-header",children:[h.jsx(ar,{className:"dialog-title",children:n}),a&&h.jsx(ae,{onClick:t,className:"dialog-close","aria-label":Dy("common.close_dialog"),children:h.jsx(oe,{name:de.Times,className:"icon-md"})})]}),h.jsx(I,{className:"dialog-body",children:r}),s.length>0&&h.jsx(I,{className:"dialog-footer",children:s.map((f,g)=>h.jsxs(ae,{onClick:f.onClick,disabled:f.disabled,className:u[f.variant||"primary"],children:[f.iconName&&h.jsx(oe,{name:f.iconName,className:"icon-sm mr-2 inline-block"}),f.label]},g))})]})},fl=(e,t)=>window.G7Core?.t?.(e,t)??e,pu=({isOpen:e,onClose:t,title:n,message:r,confirmText:s,cancelText:o,onConfirm:i,onCancel:a,confirmButtonVariant:l="primary",cancelButtonVariant:c="secondary",width:d="500px",closeOnOverlay:u=!0,isLoading:f=!1,loadingText:g})=>{const m=s??fl("common.confirm"),p=o??fl("common.cancel"),y=g??fl("common.loading"),b=()=>{i()},_=()=>{f||(a?.(),t())},x=()=>{f||t()},M={primary:"bg-blue-600 text-white hover:bg-blue-700",secondary:"bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200 hover:bg-gray-300 dark:hover:bg-gray-600",danger:"bg-red-600 text-white hover:bg-red-700",ghost:"bg-transparent text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700"};return h.jsxs(Qo,{isOpen:e,onClose:x,title:n,width:d,children:[h.jsx(I,{className:"py-2",children:typeof r=="string"?h.jsx(vt,{className:"text-gray-700 dark:text-gray-300",children:r}):r}),h.jsxs(I,{className:"flex justify-end gap-3 mt-6",children:[h.jsx(ae,{onClick:_,disabled:f,className:"px-4 py-2 bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 disabled:opacity-50 disabled:cursor-not-allowed",children:h.jsx(Q,{children:p})}),h.jsxs(ae,{onClick:b,disabled:f,className:`flex items-center gap-2 px-4 py-2 rounded-lg disabled:opacity-50 disabled:cursor-not-allowed ${M[l]}`,children:[f&&h.jsx(oe,{name:de.Spinner,className:"w-4 h-4 animate-spin"}),h.jsx(Q,{children:f?y:m})]})]})]})},Iy=(e,t)=>window.G7Core?.t?.(e,t)??e,Ly=({isOpen:e,onClose:t,title:n,message:r,confirmText:s,onConfirm:o,confirmButtonVariant:i="primary",width:a="500px",closeOnOverlay:l=!0})=>{const u=[{label:s??Iy("common.confirm"),onClick:()=>{o?.(),t()},variant:i}],f=typeof r=="string"?h.jsx(vt,{className:"text-gray-700 dark:text-gray-300",children:r}):r;return h.jsx(gu,{isOpen:e,onClose:t,title:n,content:f,actions:u,width:a,closeOnOverlay:l})},Py=(e,t)=>window.G7Core?.t?.(e,t)??e,$y={success:de.CheckCircle,error:de.XCircle,warning:de.ExclamationTriangle,info:de.InfoCircle},mu={success:{bg:"bg-green-50 dark:bg-green-900/20 border-green-200 dark:border-green-800",text:"text-green-800 dark:text-green-300"},error:{bg:"bg-red-50 dark:bg-red-900/20 border-red-200 dark:border-red-800",text:"text-red-800 dark:text-red-300"},warning:{bg:"bg-yellow-50 dark:bg-yellow-900/20 border-yellow-200 dark:border-yellow-800",text:"text-yellow-800 dark:text-yellow-300"},info:{bg:"bg-blue-50 dark:bg-blue-900/20 border-blue-200 dark:border-blue-800",text:"text-blue-800 dark:text-blue-300"}},Fy={"top-left":"top-4 left-4","top-center":"top-4 left-1/2 -translate-x-1/2","top-right":"top-4 right-4","bottom-left":"bottom-4 left-4","bottom-center":"bottom-4 left-1/2 -translate-x-1/2","bottom-right":"bottom-4 right-4"},zy=({toasts:e,position:t="bottom-center",duration:n=3e3,onRemove:r})=>{const[s,o]=v.useState(new Map),i=v.useCallback(a=>{o(l=>{const c=new Map(l);return c.set(a,!1),c}),setTimeout(()=>{if(r)r(a);else{const l=window.G7Core;l?.state?.update&&l.state.update(c=>({toasts:(Array.isArray(c.toasts)?c.toasts:[]).filter(u=>u.id!==a)}))}},300)},[r]);return v.useEffect(()=>{!e||e.length===0||e.forEach(a=>{if(s.has(a.id))return;o(c=>{const d=new Map(c);return d.set(a.id,!0),d});const l=a.duration||n;l>0&&setTimeout(()=>{i(a.id)},l)})},[e,n,i,s]),!e||e.length===0?null:h.jsx(I,{className:`fixed z-[9999] flex flex-col gap-2 ${Fy[t]}`,"aria-live":"polite","aria-atomic":"true",children:e.map(a=>h.jsx(Vy,{toast:a,position:t,isVisible:s.get(a.id)??!0,onClose:()=>i(a.id)},a.id))})},Vy=({toast:e,position:t,isVisible:n,onClose:r})=>{const s=mu[e.type]||mu.info,o=e.icon?e.icon:$y[e.type],i=t.startsWith("bottom")?"translate-y-2":"-translate-y-2";return h.jsxs(I,{className:` + `.trim().replace(/\s+/g," ");return h.jsx(I,{className:d,dangerouslySetInnerHTML:{__html:c},id:o,...i})},Uo=window.G7Core?.createLogger?.("Comp:DataGrid")??{log:(...t)=>console.log("[Comp:DataGrid]",...t),warn:(...t)=>console.warn("[Comp:DataGrid]",...t),error:(...t)=>console.error("[Comp:DataGrid]",...t)},Es=(t,e)=>window.G7Core?.t?.(t,e)??t,jb={Div:I,Span:Q,Button:oe,Icon:re,Checkbox:vn,P:yt,A:Lr,Label:sn,Input:kt,H1:Da,H2:Jn,H3:Ir,H4:Md,Img:yn,Select:Pr,Textarea:Qn,Table:$a,Thead:Fa,Tbody:za,Tfoot:Va,Tr:Rn,Th:$r,Td:Zt,MultilingualInput:Ha,Toggle:Ua,StatusBadge:Fo,Badge:Ld,HtmlContent:Za,ActionMenu:xs},Qa=()=>{const t=window.G7Core?.getComponentMap?.();return t&&Object.keys(t).length>0?t:jb},Ja=t=>{if(t==null)return"";if(typeof t=="string"||typeof t=="number"||typeof t=="boolean")return String(t);if(Array.isArray(t))return t.map(e=>Ja(e)).join(", ");if(typeof t=="object"){if(v.isValidElement(t))return t;if(Object.keys(t).length===0)return"";try{return JSON.stringify(t)}catch{return Uo.warn("객체를 문자열로 변환할 수 없습니다:",t),"[Object]"}}return String(t)},Xd=(t,e)=>{if(!t)return!0;const n=window.G7Core;return typeof n?.evaluateCondition=="function"?n.evaluateCondition(t,{row:e,item:e,$item:e}):(Uo.warn("G7Core.evaluateCondition 미노출 — subRow 조건을 평가할 수 없습니다:",t),!0)},zr=(t,e,n,r="",s)=>{const o=window.G7Core;if(o?.renderItemChildren){const i={row:e,value:n,$value:n};return o.renderItemChildren(t,i,Qa(),r,s?{componentContext:s}:void 0)}return Uo.warn("G7Core.renderItemChildren을 사용할 수 없습니다."),null},qd=(t,e,n)=>t?!t.includes("{{")&&!t.includes("$t:")?t:zr([{type:"basic",name:"Span",text:t}],{},null,e,n):"",Ra=(t,e,n="",r)=>{const s=window.G7Core;if(s?.renderItemChildren){const o={row:e,$value:e};return s.renderItemChildren(t,o,Qa(),n,r?{componentContext:r}:void 0)}return Uo.warn("G7Core.renderItemChildren을 사용할 수 없습니다."),null},Wb=({id:t,columns:e,data:n,sortable:r=!0,pagination:s=!1,pageSize:o=10,onRowClick:i,className:a="",style:l,sortField:c,sortDirection:d,onSortChange:u,selectable:f=!1,selectedIds:g=[],onSelectionChange:m,idField:p="id",showColumnSelector:y=!1,visibleColumns:b,onColumnVisibilityChange:_,responsiveBreakpoint:x=768,cardRenderer:E,rowActions:C,onRowAction:k,alwaysShowPagination:N=!1,serverSidePagination:S=!1,serverCurrentPage:w,serverTotalPages:M,onPageChange:T,prevText:P,nextText:L,columnSelectorText:$,emptyMessage:A,actionsColumnHeader:D,requiredText:O,selectedCountText:z,showFirstLast:H=!0,expandable:j=!1,expandedRowIds:W,onExpandChange:U,expandChildren:B,expandedRowRender:G,expandColumnWidth:V="48px",expandIconExpanded:X=de.ChevronDown,expandIconCollapsed:K=de.ChevronRight,expandConditionField:Z,dynamicColumns:ee,dynamicColumnsInsertAfter:ne,expandContext:he,__componentContext:J,subRowChildren:le,subRowCondition:ue,subRowClassName:ae="px-6 py-2 bg-gray-50 dark:bg-gray-900",subRowRender:ve,footerCells:fe,footerClassName:ye="bg-gray-50 dark:bg-gray-800/50 font-medium",footerCardChildren:we,editorAttrs:He})=>{const se=P||void 0,Oe=L||void 0,Le=$??Es("common.column_selector"),Ie=A??Es("common.no_data"),Be=D??Es("common.actions"),st=O??Es("common.required"),ut=z??Es("common.selected_count"),je=window.G7Core,nt=je?.useControllableState,Ye=je?.shallowArrayEqual,[te,ge]=nt?nt(c,null):[c??null,()=>{}],[me,pe]=nt?nt(d,"asc"):[d??"asc",()=>{}],[be,Ue]=v.useState(1),[Ce,ze]=nt?nt(W,[],U,{isEqual:Ye}):[W??[],()=>{}],[Se,Re]=v.useState(!1),ot=v.useRef(null),Pe=je?.useResponsive,Te=Pe?.(),Ve=Te?Te.width{if(!ee||!Array.isArray(ee)||ee.length===0)return e;if(ne){const q=e.findIndex(ce=>ce.field===ne);if(q!==-1)return[...e.slice(0,q+1),...ee,...e.slice(q+1)]}return[...e,...ee]},[e,ee,ne]),[qe,it]=v.useState(()=>b&&b.length>0?b:e.filter(q=>!q.hidden).map(q=>q.field));v.useEffect(()=>{b&&b.length>0&&it(b)},[b]);const pt=v.useRef("");v.useEffect(()=>{const q=e.map(ce=>ce.field).join(",");pt.current&&q!==pt.current&&(!b||b.length===0)&&it(e.filter(ce=>!ce.hidden).map(ce=>ce.field)),pt.current=q},[e,b]);const rn=v.useRef([]),bt=v.useRef(null);v.useEffect(()=>{if(ee&&ee.length>0){const q=ee.map(Ze=>Ze.field);if(bt.current===null&&(bt.current=b!==void 0&&b.length>0,bt.current)){rn.current=q;return}const ce=rn.current,Ee=q.filter(Ze=>!ce.includes(Ze));if(Ee.length>0){const Ze=Ee.filter(Fe=>!qe.includes(Fe));if(Ze.length>0){const Fe=[...qe,...Ze];it(Fe),_?.(Fe)}}rn.current=q}},[ee]),v.useEffect(()=>{S||Ue(1)},[n,S]),v.useEffect(()=>{const q=ce=>{ot.current&&!ot.current.contains(ce.target)&&Re(!1)};return Se&&document.addEventListener("mousedown",q),()=>document.removeEventListener("mousedown",q)},[Se]);const wt=v.useMemo(()=>!y&&(!b||b.length===0)?De.filter(q=>!q.hidden):De.filter(q=>qe.includes(q.field)),[De,qe,y,b]),jt=r&&!(S&&!u),ft=v.useMemo(()=>!te||!r||!n?n||[]:u||S?n:[...n].sort((q,ce)=>{const Ee=q[te],Ze=ce[te];if(Ee===Ze)return 0;const Fe=Ee>Ze?1:-1;return me==="asc"?Fe:-Fe}),[n,te,me,r,u,S]),Ke=v.useMemo(()=>{if(S||!s)return ft;const q=(be-1)*o,ce=q+o;return ft.slice(q,ce)},[ft,be,o,s,S]),Nn=S?w||1:be,Wt=S?M||1:Math.ceil((ft?.length||0)/o),bn=v.useCallback(q=>{S&&T?T(q):Ue(q)},[S,T]),hr=q=>{if(!jt||e.find(Ze=>Ze.field===q)?.sortable===!1)return;const Ee=te===q&&me==="asc"?"desc":"asc";ge(q),pe(Ee),u?.(q,Ee)},On=v.useCallback(()=>{if(!m||!Ke)return;const q=Ke.map(Ee=>Ee[p]);if(q.every(Ee=>g.includes(Ee)))m(g.filter(Ee=>!q.includes(Ee)));else{const Ee=[...new Set([...g,...q])];m(Ee)}},[Ke,g,m,p]),Ao=v.useCallback(q=>{m&&(g.includes(q)?m(g.filter(ce=>ce!==q)):m([...g,q]))},[g,m]),Do=v.useCallback(q=>{if(De.find(Ze=>Ze.field===q)?.required)return;const Ee=qe.includes(q)?qe.filter(Ze=>Ze!==q):[...qe,q];it(Ee),_?.(Ee)},[De,qe,_]),Io=v.useCallback((q,ce)=>{k?.(q,ce)},[k]),Lo=v.useCallback(q=>C?C.map(ce=>{let Ee=ce.disabled??!1;if(ce.disabledField&&!Ee){const Ze=ce.disabledField.split(".");let Fe=q;for(const Nt of Ze)Fe=Fe?.[Nt];Fe||(Ee=!0)}return{...ce,disabled:Ee,onClick:()=>Io(ce.id,q)}}):[],[C,Io]),vs=v.useCallback(q=>{if(!Z)return!0;const ce=q[Z];return Array.isArray(ce)?ce.length>0:!!ce},[Z]),ke=v.useCallback(q=>{const ce=Ce.includes(q)?Ce.filter(Ee=>Ee!==q):[...Ce,q];nt?ze(ce):U&&U(ce)},[Ce,U,nt,ze]),Y=v.useCallback(q=>Ce.includes(q),[Ce]),ie=v.useCallback(q=>G?G(q):B&&B.length>0&&je?.renderExpandContent?je.renderExpandContent({children:B,row:q,expandContext:he,componentContext:J,componentMap:Qa(),keyPrefix:`expand-${q[p]}`}):null,[G,B,p,he,J,J?.state]),xe=v.useMemo(()=>!Ke||Ke.length===0?!1:Ke.every(q=>g.includes(q[p])),[Ke,g,p]),ht=v.useMemo(()=>{if(!Ke||Ke.length===0)return!1;const q=Ke.filter(ce=>g.includes(ce[p])).length;return q>0&&q{const Ee=q[p],Ze=Y(Ee);return h.jsxs(I,{className:"bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg p-4 mb-4",children:[h.jsxs(I,{className:"flex items-start justify-between",children:[j&&vs(q)&&h.jsx(oe,{onClick:()=>ke(Ee),className:"p-1.5 rounded-md hover:bg-gray-100 dark:hover:bg-gray-600 transition-colors mr-2","aria-label":Ze?"Collapse card":"Expand card",children:h.jsx(re,{name:Ze?X:K,className:"w-4 h-4 text-gray-500 dark:text-gray-400"})}),f&&h.jsx(vn,{checked:g.includes(q[p]),onChange:()=>Ao(q[p]),className:"w-4 h-4 text-blue-600 border-gray-300 dark:border-gray-600 rounded mr-3 mt-1"}),h.jsx(I,{className:"flex-1",children:h.jsx(I,{className:"grid grid-cols-2 gap-x-4 gap-y-2",children:ce.map(Fe=>h.jsxs(I,{className:"min-w-0",children:[h.jsx(Q,{className:"text-xs font-medium text-gray-500 dark:text-gray-400 uppercase",children:Fe.header}),h.jsx(I,{className:"text-sm text-gray-900 dark:text-gray-200 mt-0.5 break-words",children:Fe.cellChildren&&Fe.cellChildren.length>0?zr(Fe.cellChildren,q,q[Fe.field],`card-${q[p]}-${Fe.field}`,J):Fe.render?Fe.render(q[Fe.field],q):Ja(q[Fe.field])})]},Fe.field))})}),C&&C.length>0&&h.jsx(I,{className:"ml-2",children:h.jsx(xs,{items:Lo(q),triggerLabel:"",triggerIconName:de.EllipsisHorizontal})})]}),j&&Ze&&vs(q)&&h.jsx(I,{className:"mt-4 pt-4 border-t border-gray-200 dark:border-gray-700",children:ie(q)}),(le&&le.length>0||ve)&&Xd(ue,q)&&h.jsx(I,{className:ae?`mt-3 ${ae}`:"mt-3 pt-3 border-t border-gray-100 dark:border-gray-700",children:ve?ve(q):Ra(le,q,`card-subrow-${Ee}`,J)})]})};return Ve?h.jsxs(I,{id:t,className:`w-full ${a}`,style:l,...He,children:[y&&h.jsx(I,{className:"mb-4 flex justify-end",children:h.jsxs(I,{ref:ot,className:"relative",children:[h.jsxs(oe,{onClick:()=>Re(!Se),className:"flex items-center gap-2 px-4 py-2 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700",children:[h.jsx(re,{name:de.Cog,className:"w-4 h-4 text-gray-500 dark:text-gray-400"}),h.jsx(Q,{className:"text-sm text-gray-700 dark:text-gray-300",children:Le})]}),Se&&h.jsx(I,{className:"absolute right-0 z-50 mt-2 w-64 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg",children:h.jsx(I,{className:"p-2 max-h-64 overflow-y-auto",children:De.map(q=>h.jsxs(I,{className:`flex items-center gap-2 px-3 py-2 rounded hover:bg-gray-50 dark:hover:bg-gray-700 ${q.required?"opacity-50 cursor-not-allowed":"cursor-pointer"}`,onClick:()=>!q.required&&Do(q.field),children:[h.jsx(vn,{checked:qe.includes(q.field),disabled:q.required,onChange:()=>{},className:"w-4 h-4 text-blue-600 border-gray-300 dark:border-gray-600 rounded"}),h.jsxs(Q,{className:"text-sm text-gray-700 dark:text-gray-300",children:[q.header,q.required&&h.jsxs(Q,{className:"text-xs text-gray-500 dark:text-gray-400 ml-1",children:["(",st,")"]})]})]},q.field))})})]})}),f&&g.length>0&&ut&&h.jsx(I,{className:"mb-4 text-sm text-gray-600 dark:text-gray-400",children:ut.replace("{count}",String(g.length))}),h.jsx(I,{id:t?`${t}__body`:void 0,children:Ke&&Ke.length>0?Ke.map((q,ce)=>h.jsx(I,{children:E?E(q,wt):Lt(q,wt)},q[p]||ce)):h.jsx(I,{className:"text-center py-8 text-gray-500 dark:text-gray-400",children:Ie})}),fe&&fe.length>0&&Ke&&Ke.length>0&&h.jsx(I,{className:`rounded-lg p-4 mt-2 ${ye}`,children:we&&we.length>0?zr(we,{},null,"footer-card",J):h.jsx(I,{className:"space-y-1",children:fe.filter(q=>q.text||q.children&&q.children.length>0).map((q,ce)=>{const Ee=wt.find(Ze=>Ze.field===q.field);return h.jsxs(I,{className:"flex justify-between text-sm",children:[h.jsx(Q,{className:"text-gray-500 dark:text-gray-400",children:Ee?.header||""}),h.jsx(Q,{className:q.className||"text-gray-900 dark:text-gray-100",children:q.children&&q.children.length>0?zr(q.children,{},null,`footer-mobile-${ce}`,J):qd(q.text,`footer-mobile-text-${ce}`,J)})]},`footer-mobile-${ce}`)})})}),s&&(N||Wt>1)&&h.jsx(I,{className:"flex justify-center mt-4",children:h.jsx($o,{currentPage:Nn,totalPages:Wt,onPageChange:bn,showFirstLast:H,maxVisiblePages:5,prevText:se,nextText:Oe})})]}):h.jsxs(I,{id:t,className:`w-full ${a}`,style:l,...He,children:[y&&h.jsx(I,{className:"mb-4 flex justify-end",children:h.jsxs(I,{ref:ot,className:"relative",children:[h.jsxs(oe,{onClick:()=>Re(!Se),className:"flex items-center gap-2 px-4 py-2 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700",children:[h.jsx(re,{name:de.Cog,className:"w-4 h-4 text-gray-500 dark:text-gray-400"}),h.jsx(Q,{className:"text-sm text-gray-700 dark:text-gray-300",children:Le})]}),Se&&h.jsx(I,{className:"absolute right-0 z-50 mt-2 w-64 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg",children:h.jsx(I,{className:"p-2 max-h-64 overflow-y-auto",children:De.map(q=>h.jsxs(I,{className:`flex items-center gap-2 px-3 py-2 rounded hover:bg-gray-50 dark:hover:bg-gray-700 ${q.required?"opacity-50 cursor-not-allowed":"cursor-pointer"}`,onClick:()=>!q.required&&Do(q.field),children:[h.jsx(vn,{checked:qe.includes(q.field),disabled:q.required,onChange:()=>{},className:"w-4 h-4 text-blue-600 border-gray-300 dark:border-gray-600 rounded"}),h.jsxs(Q,{className:"text-sm text-gray-700 dark:text-gray-300",children:[q.header,q.required&&h.jsxs(Q,{className:"text-xs text-gray-500 dark:text-gray-400 ml-1",children:["(",st,")"]})]})]},q.field))})})]})}),f&&g.length>0&&ut&&h.jsx(I,{className:"mb-4 text-sm text-gray-600 dark:text-gray-400",children:ut.replace("{count}",String(g.length))}),h.jsx(I,{id:t?`${t}__body`:void 0,className:"overflow-x-auto border border-gray-200 dark:border-gray-700 rounded-lg",children:h.jsxs($a,{className:"w-full",children:[h.jsx(Fa,{className:"bg-gray-50 dark:bg-gray-700",children:h.jsxs(Rn,{children:[j&&h.jsx($r,{className:"px-2 py-3",style:{width:V}}),f&&h.jsx($r,{className:"px-4 py-3 w-10",children:h.jsx(vn,{checked:xe,ref:q=>{q&&(q.indeterminate=ht)},onChange:On,className:"w-4 h-4 text-blue-600 border-gray-300 dark:border-gray-600 rounded"})}),wt.map(q=>h.jsxs($r,{className:`px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider ${jt&&q.sortable!==!1?"cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-600":""}`,style:{width:q.width,minWidth:q.width},onClick:jt?()=>hr(q.field):void 0,children:[q.header,te===q.field&&h.jsx(Q,{className:"ml-1",children:me==="asc"?"↑":"↓"})]},q.field)),C&&C.length>0&&h.jsx($r,{className:"px-6 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider w-24",children:Be})]})}),h.jsx(za,{className:`bg-white dark:bg-gray-800 ${ae?"":"divide-y divide-gray-200 dark:divide-gray-700"}`,children:Ke&&Ke.length>0?Ke.map((q,ce)=>{const Ee=q[p]||ce,Ze=Y(Ee);return h.jsxs(v.Fragment,{children:[h.jsxs(Rn,{className:`${i?"cursor-pointer":""} hover:bg-gray-50 dark:hover:bg-gray-700 ${g.includes(q[p])?"bg-blue-50 dark:bg-blue-900/20":""} ${ae&&ce>0?"border-t border-gray-200 dark:border-gray-700":""}`,onClick:()=>i?.(q),children:[j&&h.jsx(Zt,{className:"px-2 py-4 text-center",onClick:Fe=>Fe.stopPropagation(),children:vs(q)?h.jsx(oe,{onClick:()=>ke(Ee),className:"p-1.5 rounded-md hover:bg-gray-100 dark:hover:bg-gray-600 transition-colors","aria-label":Ze?"Collapse row":"Expand row",children:h.jsx(re,{name:Ze?X:K,className:"w-4 h-4 text-gray-500 dark:text-gray-400"})}):null}),f&&h.jsx(Zt,{className:"px-4 py-4",onClick:Fe=>Fe.stopPropagation(),children:h.jsx(vn,{checked:g.includes(q[p]),onChange:()=>Ao(q[p]),className:"w-4 h-4 text-blue-600 border-gray-300 dark:border-gray-600 rounded"})}),wt.map(Fe=>h.jsx(Zt,{className:"px-6 py-4 align-top whitespace-nowrap text-sm text-gray-900 dark:text-gray-200",style:{width:Fe.width,minWidth:Fe.width},children:Fe.cellChildren&&Fe.cellChildren.length>0?zr(Fe.cellChildren,q,q[Fe.field],`${q[p]}-${Fe.field}`,J):Fe.render?Fe.render(q[Fe.field],q):Ja(q[Fe.field])},Fe.field)),C&&C.length>0&&h.jsx(Zt,{className:"px-6 py-4 text-right",onClick:Fe=>Fe.stopPropagation(),children:h.jsx(xs,{items:Lo(q),triggerLabel:"",triggerIconName:de.EllipsisHorizontal,position:"right"})})]}),j&&Ze&&vs(q)&&h.jsx(Rn,{className:"bg-gray-50 dark:bg-gray-900",children:h.jsx(Zt,{colSpan:wt.length+1+(f?1:0)+(C&&C.length>0?1:0),className:"px-6 py-4",children:ie(q)})}),(le&&le.length>0||ve)&&Xd(ue,q)&&h.jsx(Rn,{className:ae?"!border-t-0":"",children:h.jsx(Zt,{colSpan:wt.length+(j?1:0)+(f?1:0)+(C&&C.length>0?1:0),className:ae?"p-2":ae||"px-6 py-2 bg-gray-50 dark:bg-gray-900",children:ae?h.jsx(I,{className:ae,children:ve?ve(q):Ra(le,q,`subrow-${Ee}`,J)}):ve?ve(q):Ra(le,q,`subrow-${Ee}`,J)})})]},Ee)}):h.jsx(Rn,{children:h.jsx(Zt,{colSpan:wt.length+(j?1:0)+(f?1:0)+(C&&C.length>0?1:0),className:"px-6 py-8 text-center text-gray-500 dark:text-gray-400",children:Ie})})}),fe&&fe.length>0&&Ke&&Ke.length>0&&h.jsx(Va,{children:h.jsxs(Rn,{className:ye,children:[j&&h.jsx(Zt,{className:"px-2 py-4"}),f&&h.jsx(Zt,{className:"px-4 py-4"}),(()=>{const q=new Map(fe.filter(ce=>ce.field).map(ce=>[ce.field,ce]));return wt.map(ce=>{const Ee=q.get(ce.field);return Ee?h.jsx(Zt,{className:`px-6 py-4 text-sm text-gray-900 dark:text-gray-200 ${Ee.className||""}`,colSpan:Ee.colSpan,style:{width:ce.width,minWidth:ce.width},children:Ee.children&&Ee.children.length>0?zr(Ee.children,{},null,`footer-${ce.field}`,J):qd(Ee.text,`footer-text-${ce.field}`,J)},`footer-${ce.field}`):h.jsx(Zt,{className:"px-6 py-4",style:{width:ce.width,minWidth:ce.width}},`footer-${ce.field}`)})})(),C&&C.length>0&&h.jsx(Zt,{className:"px-6 py-4"})]})})]})}),s&&(N||Wt>1)&&h.jsx(I,{className:"flex justify-center mt-4",children:h.jsx($o,{currentPage:Nn,totalPages:Wt,onPageChange:bn,showFirstLast:H,maxVisiblePages:5,prevText:se,nextText:Oe})})]})},Gb=window.G7Core?.createLogger?.("Comp:CardGrid")??{log:(...t)=>console.log("[Comp:CardGrid]",...t),warn:(...t)=>console.warn("[Comp:CardGrid]",...t),error:(...t)=>console.error("[Comp:CardGrid]",...t)},Yb=(t,e)=>window.G7Core?.t?.(t,e)??t,Kb=()=>window.G7Core?.getComponentMap?.()||{},Xb=(t,e)=>{if(!t)return!0;const n=window.G7Core;return typeof n?.evaluateCondition=="function"?n.evaluateCondition(t,{row:e,item:e,$item:e}):(Gb.warn("G7Core.evaluateCondition 미노출 — 조건을 평가할 수 없습니다:",t),!0)},Zd=(t,e,n,r,s)=>{const o=window.G7Core;if(t.name==="ActionMenu"){if(r&&r.length>0&&s)return{...t,props:{...t.props,items:r.map(i=>({...i,onClick:()=>s(i.id,e)}))}};if(t.actions&&Array.isArray(t.actions)){const i=t.props?.items||[],a=new Map(t.actions.map(c=>[c.id,c])),l=i.map(c=>{if(typeof c.onClick=="string"){const d=c.onClick,u=a.get(d);if(u&&o?.ActionDispatcher)return{...c,onClick:()=>{const f={...n};o.ActionDispatcher.dispatch(u,f)}}}return c});return{...t,props:{...t.props,items:l}}}}return t.children&&Array.isArray(t.children)?{...t,children:t.children.map(i=>Zd(i,e,n,r,s))}:t},qb=(t,e,n,r="",s,o)=>{const i=window.G7Core;if(i?.renderItemChildren){const a=t.filter(d=>{const u=d.condition||d.if;return u?Xb(u,e):!0}),l={row:e,$value:e,index:n},c=a.map(d=>Zd(d,e,l,s,o));try{const d=Kb();return i.renderItemChildren(c,l,d,r)}catch{return h.jsxs(I,{className:"text-red-600 dark:text-red-400 p-4",children:[h.jsx(yt,{children:"컴포넌트 로드 실패"}),h.jsx(yt,{className:"text-sm",children:"[CardGrid] 데이터를 표시할 수 없습니다."})]})}}return null},el=(t,e)=>{if(t.name==="Div"&&t.children&&t.children.length>0){const n=t.children.map((r,s)=>el(r,`${e}-child-${s}`)).filter(Boolean);return h.jsx(I,{className:t.props?.className||"",children:n},e)}return h.jsx(I,{className:"h-4 bg-gray-200 dark:bg-gray-700 rounded w-full animate-pulse"},e)},Qd=(t,e="skeleton")=>!t||t.length===0?null:t.map((n,r)=>{const s=`${e}-${n.id||n.name}-${r}`;return n.condition||n.if?null:(n.iteration,el(n,s))}).filter(Boolean),Zb=({data:t,cardColumns:e=[],gridColumns:n=3,gap:r=4,responsiveColumns:s,cardClassName:o="",idField:i="id",pagination:a=!0,pageSize:l=12,serverSidePagination:c=!1,serverCurrentPage:d,serverTotalPages:u,alwaysShowPagination:f=!1,onPageChange:g,paginationInfoText:m,rowActions:p,onRowAction:y,className:b="",style:_,emptyMessage:x,prevText:E,nextText:C,showSkeleton:k=!0,skeletonCount:N,skeletonCellChildren:S,id:w,editorAttrs:M})=>{const T=x??Yb("common.no_data"),P=e[0]?.cellChildren||[],[L,$]=v.useState(1);window.G7Core?.useResponsive?.();const D=c?d??1:L,O=c?u??1:Math.ceil((t?.length??0)/l),z=G=>{c?g?.(G):$(G)},H=v.useCallback((G,V)=>{y?.(G,V)},[y]),j=v.useMemo(()=>{if(!t)return[];if(!a||c)return t;const G=(L-1)*l,V=G+l;return t.slice(G,V)},[t,a,c,L,l]),W=v.useMemo(()=>{const G=[],V=s?.sm??1;G.push(`grid-cols-${V}`),s?.sm&&G.push(`sm:grid-cols-${s.sm}`);const X=s?.md??Math.min(n,2);G.push(`md:grid-cols-${X}`);const K=s?.lg??n;return G.push(`lg:grid-cols-${K}`),s?.xl&&G.push(`xl:grid-cols-${s.xl}`),G.join(" ")},[n,s]),U=v.useMemo(()=>k?S&&S.length>0?Qd(S,"custom-skeleton"):P&&P.length>0?Qd(P,"auto-skeleton"):null:null,[k,S,P]);if(!t){if(!k)return null;const G=N??n;return h.jsx(I,{className:`grid ${W} gap-${r} ${b}`,style:_,id:w,...M,children:Array.from({length:G}).map((V,X)=>h.jsx(I,{className:"bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg p-6 space-y-4",children:U},`skeleton-${X}`))})}if(j.length===0)return h.jsx(I,{className:b,style:_,id:w,...M,children:h.jsx(I,{className:"bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg p-12",children:h.jsx(yt,{className:"text-center text-gray-600 dark:text-gray-400",children:T})})});const B=a&&(f||O>1);return h.jsxs(I,{className:b,style:_,id:w,...M,children:[h.jsx(I,{className:`grid ${W} gap-${r}`,children:j.map((G,V)=>h.jsx(I,{className:o,children:qb(P,G,V,`card-${V}`,p,H)},G[i]||V))}),B&&h.jsxs(I,{className:"mt-6 space-y-4 flex flex-col items-center",children:[h.jsx($o,{currentPage:D,totalPages:O,onPageChange:z,showFirstLast:!1,maxVisiblePages:5,prevText:E,nextText:C}),m&&h.jsx(I,{className:"text-sm text-gray-600 dark:text-gray-400 text-center",children:m})]})]})},Qb=(t,e)=>window.G7Core?.t?.(t,e)??t,Bo=({isOpen:t,onClose:e,title:n,titlePrefix:r,titleSuffix:s,closeOnBackdropClick:o=!0,closeOnOverlayClick:i,showCloseButton:a=!0,closeOnEscape:l=!0,children:c,width:d="500px",className:u="",style:f,__dataContext:g,__translationContext:m})=>{const p=i!==void 0?i:o,y=v.useRef(null),b=v.useRef(null);if(v.useEffect(()=>{const E=C=>{C.key==="Escape"&&t&&l&&e()};if(t&&l)return document.addEventListener("keydown",E),()=>document.removeEventListener("keydown",E)},[t,e,l]),v.useEffect(()=>{if(t&&y.current){b.current=document.activeElement;const E=y.current.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'),C=E[0],k=E[E.length-1];C?.focus();const N=S=>{S.key==="Tab"&&(S.shiftKey?document.activeElement===C&&(S.preventDefault(),k?.focus()):document.activeElement===k&&(S.preventDefault(),C?.focus()))};return document.addEventListener("keydown",N),()=>{document.removeEventListener("keydown",N),b.current?.focus()}}},[t]),v.useEffect(()=>{if(t)return document.body.style.overflow="hidden",()=>{document.body.style.overflow=""}},[t]),!t)return null;const _=f?.zIndex??50,x=(E,C)=>{if(!E||E.length===0)return null;const k=window.G7Core;if(!k)return null;const N=k.getDynamicRenderer?.();return N?E.map((S,w)=>h.jsx(N,{componentDef:S,dataContext:g||{},translationContext:m,registry:k.getComponentRegistry(),bindingEngine:k.getDataBindingEngine(),translationEngine:k.getTranslationEngine(),actionDispatcher:k.getActionDispatcher(),isRootRenderer:!1},`${C}-${w}`)):null};return h.jsxs(I,{className:"modal-wrapper",style:{zIndex:_},onClick:p?e:void 0,children:[h.jsx(I,{className:"modal-backdrop","aria-hidden":"true"}),h.jsxs(I,{ref:y,className:`modal-container ${u}`,style:{width:d,...f?{...f,zIndex:void 0}:{}},onClick:E=>E.stopPropagation(),role:"dialog","aria-modal":"true","aria-labelledby":n?"modal-title":void 0,children:[(n||r||s||a)&&h.jsxs(I,{className:"modal-header-row",children:[h.jsxs(I,{className:"flex-center gap-sm",children:[x(r,"title-prefix"),n&&h.jsx(I,{id:"modal-title",className:"modal-title",children:n}),x(s,"title-suffix")]}),a&&h.jsx(oe,{onClick:e,className:"modal-close-btn","aria-label":Qb("common.close_modal"),children:h.jsx("svg",{className:"icon-lg",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:h.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M6 18L18 6M6 6l12 12"})})})]}),h.jsx(I,{className:"modal-body-content",children:c})]})]})},Jb=({label:t,items:e,onItemClick:n,position:r="bottom-left",variant:s="default",className:o="",style:i,id:a,editorAttrs:l})=>{const[c,d]=v.useState(!1),[u,f]=v.useState(-1),g=v.useRef(null),m=v.useRef(null),[p,y]=v.useState(r);v.useEffect(()=>{const x=E=>{g.current&&!g.current.contains(E.target)&&(d(!1),f(-1))};if(c)return document.addEventListener("mousedown",x),()=>document.removeEventListener("mousedown",x)},[c]),v.useEffect(()=>{const x=E=>{if(!c)return;const C=e.map((k,N)=>k.disabled?-1:N).filter(k=>k!==-1);switch(E.key){case"ArrowDown":E.preventDefault(),f(k=>{const S=(C.indexOf(k)+1)%C.length;return C[S]});break;case"ArrowUp":E.preventDefault(),f(k=>{const N=C.indexOf(k),S=N<=0?C.length-1:N-1;return C[S]});break;case"Enter":E.preventDefault(),u>=0&&!e[u].disabled&&b(e[u]);break;case"Escape":E.preventDefault(),d(!1),f(-1);break}};if(c)return document.addEventListener("keydown",x),()=>document.removeEventListener("keydown",x)},[c,u,e]),v.useEffect(()=>{if(c&&m.current&&g.current){const x=m.current.getBoundingClientRect(),E=g.current.getBoundingClientRect(),C=window.innerWidth,k=window.innerHeight,N=x.width||224,S=x.height||200;let w=r;r.includes("left")?E.left+N>C&&(w=w.replace("left","right")):E.right-N<0&&(w=w.replace("right","left")),r.includes("top")?E.top-S<0&&(w=w.replace("top","bottom")):E.bottom+S>k&&(w=w.replace("bottom","top")),y(w)}},[c,r]);const b=x=>{x.disabled||(x.onClick?.(),n?.(x.value,x),d(!1),f(-1))},_={"bottom-left":"dropdown-menu-bottom-left","bottom-right":"dropdown-menu-bottom-right","top-left":"dropdown-menu-top-left","top-right":"dropdown-menu-top-right"};return h.jsxs(I,{ref:g,className:`dropdown ${o}`,style:i,id:a,...l,children:[h.jsxs(oe,{onClick:()=>d(!c),className:s==="text"?"dropdown-trigger-text":"dropdown-trigger","aria-haspopup":"true","aria-expanded":c,children:[t,s!=="text"&&h.jsx("svg",{className:`inline-block ml-2 w-4 h-4 transition-transform ${c?"rotate-180":""}`,fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:h.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M19 9l-7 7-7-7"})})]}),c&&h.jsx(I,{ref:m,className:`dropdown-menu ${_[p]}`,role:"menu","aria-orientation":"vertical",children:h.jsx(I,{className:"py-1",children:e.map((x,E)=>h.jsx(I,{className:`${x.disabled?"dropdown-item-disabled":"dropdown-item"} ${u===E?"dropdown-item-focused":""}`,onClick:()=>b(x),onMouseEnter:()=>!x.disabled&&f(E),role:"menuitem","aria-disabled":x.disabled,tabIndex:x.disabled?-1:0,children:x.label},x.value))})})]})},Rb=({imageUrl:t,imageAlt:e="",title:n,subtitle:r,description:s,price:o,originalPrice:i,discountRate:a,currency:l="₩",buttonText:c="자세히 보기",onButtonClick:d,className:u="",style:f,id:g,editorAttrs:m})=>{const p=a&&a>0,y=(o??0).toLocaleString(),b=i?.toLocaleString();return h.jsxs(I,{className:`rounded-lg bg-white dark:bg-gray-800 shadow-md hover:shadow-xl transition-shadow border border-gray-200 dark:border-gray-700 overflow-hidden ${u}`,style:f,id:g,...m,children:[t&&h.jsxs(I,{className:"relative w-full",children:[h.jsx(yn,{src:t,alt:e||n,className:"w-full h-64 object-cover"}),p&&h.jsx(I,{className:"absolute top-2 right-2 bg-red-500 text-white px-2 py-1 rounded-md",children:h.jsxs(Q,{className:"text-sm font-bold",children:["-",a,"%"]})})]}),h.jsxs(I,{className:"p-4",children:[r&&h.jsx(Ir,{className:"text-xs font-medium text-blue-600 dark:text-blue-400 uppercase tracking-wider mb-2",children:r}),h.jsx(Jn,{className:"text-xl font-bold text-gray-900 dark:text-white mb-2 line-clamp-2",children:n}),s&&h.jsx(yt,{className:"text-sm text-gray-600 dark:text-gray-400 mb-4 line-clamp-3",children:s}),h.jsxs(I,{className:"flex items-center gap-2 mb-4",children:[h.jsxs(Q,{className:"text-2xl font-bold text-gray-900 dark:text-white",children:[l,y]}),i&&h.jsxs(Q,{className:"text-sm text-gray-400 dark:text-gray-500 line-through",children:[l,b]})]}),c&&h.jsx(oe,{onClick:d,className:"w-full bg-blue-600 hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600 text-white font-medium py-2 px-4 rounded-lg transition-colors",children:c})]})]})},ey=({value:t,label:e,change:n,changeLabel:r="전월 대비",iconName:s,trend:o="neutral",className:i="",style:a,id:l,editorAttrs:c})=>{const d=o==="up",u=o==="down",f=n!==void 0?Math.abs(n):0,g=d?"stats-trend-up":u?"stats-trend-down":"stats-trend-neutral",m=d?de.ArrowUp:u?de.ArrowDown:void 0;return h.jsxs(I,{className:`stat-card ${i}`,style:a,id:l,...c,children:[h.jsxs(I,{className:"flex-between mb-md",children:[s&&h.jsx(I,{className:"stats-icon stats-icon-blue",children:h.jsx(re,{name:s,className:"icon-lg"})}),n!==void 0&&h.jsxs(I,{className:`stats-change ${g}`,children:[m&&h.jsx(re,{name:m,className:"icon-sm"}),h.jsxs(Q,{className:"text-sm font-semibold",children:[f,"%"]})]})]}),h.jsx(Ir,{className:"stats-value-lg",children:typeof t=="number"?t.toLocaleString():t}),h.jsx(yt,{className:"stats-label",children:e}),n!==void 0&&r&&h.jsx(I,{className:"stats-sublabel",children:h.jsx(Q,{children:r})})]})},Jd=()=>window.G7Core,Rd=(t,e)=>Jd()?.t?.(t,e)??t,ty=({name:t="search",placeholder:e,value:n,onChange:r,onSubmit:s,showButton:o=!1,suggestions:i=[],onSuggestionClick:a,showSuggestions:l=!1,className:c="",style:d,id:u,editorAttrs:f})=>{const g=e??Rd("common.search_placeholder"),[m,p]=v.useState(""),[y,b]=v.useState(!1),_=v.useRef(null),x=v.useRef(""),E=n!==void 0?n:m,C=l&&y&&i.length>0&&E.length>0,k="w-64 pl-10 pr-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white text-sm",N=Jd()?.style?.mergeClasses?.(k,c)??`${k} ${c}`;v.useEffect(()=>{n!==void 0&&(x.current=n)},[n]);const S=T=>{const P=T.target.value;p(P),r?.(T),x.current!==""&&P===""&&!T.nativeEvent.inputType&&setTimeout(()=>{const L=_.current?.querySelector("form");L&&L.requestSubmit()},0),x.current=P},w=T=>{p(T.text);const P={target:{value:T.text,name:t}};r?.(P),a?.(T),b(!1)},M=T=>{T.preventDefault(),s?.(T)};return h.jsxs(I,{ref:_,className:"relative",style:d,id:u,...f,children:[h.jsx(Pa,{onSubmit:M,className:"relative",children:h.jsxs(I,{className:`relative flex items-center ${o?"gap-2":""}`,children:[h.jsxs(I,{className:"relative flex-1",children:[h.jsx(I,{className:"absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none",children:h.jsx(re,{name:de.Search,className:"w-5 h-5 text-gray-400 dark:text-gray-500"})}),h.jsx(kt,{type:"search",name:t,value:E,onChange:S,onFocus:()=>b(!0),onBlur:()=>setTimeout(()=>b(!1),200),placeholder:g,className:N})]}),o&&h.jsx(oe,{type:"submit",className:"px-4 py-2 bg-gray-800 dark:bg-gray-600 text-white rounded-lg hover:bg-gray-900 dark:hover:bg-gray-500 transition-colors text-sm font-medium cursor-pointer h-[42px]",children:Rd("common.search")})]})}),C&&h.jsx(I,{className:"absolute z-10 w-full mt-2 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg max-h-64 overflow-y-auto",children:i.map(T=>h.jsx(I,{className:"px-4 py-3 hover:bg-gray-50 dark:hover:bg-gray-700 cursor-pointer border-b border-gray-100 dark:border-gray-700 last:border-b-0 transition-colors",onClick:()=>w(T),children:h.jsxs(I,{className:"flex items-center gap-2",children:[h.jsx(re,{name:de.Search,className:"w-4 h-4 text-gray-400 dark:text-gray-500"}),h.jsx(Q,{className:"text-sm text-gray-700 dark:text-gray-300",children:T.text})]})},T.id))})]})},tl=(t,e)=>window.G7Core?.t?.(t,e)??t,ny=({title:t,filters:e,onChange:n,onReset:r,showResetButton:s=!0,className:o="",style:i,id:a,editorAttrs:l})=>{const c=t??tl("common.filter"),d=(f,g)=>{n?.(f,g)},u=(f,g,m)=>{const p=e.find(b=>b.id===f);if(!p)return;const y=Array.isArray(p.value)?p.value:[];if(typeof g=="string"){const b=y.filter(x=>typeof x=="string"),_=m?[...b,g]:b.filter(x=>x!==g);n?.(f,_)}else{const b=y.filter(x=>typeof x=="number"),_=m?[...b,g]:b.filter(x=>x!==g);n?.(f,_)}};return h.jsxs(I,{className:`rounded-lg bg-white dark:bg-gray-800 shadow-md border border-gray-200 dark:border-gray-700 p-4 ${o}`,style:i,id:a,...l,children:[h.jsxs(I,{className:"flex items-center justify-between mb-4 pb-3 border-b border-gray-200 dark:border-gray-700",children:[h.jsx(Q,{className:"text-lg font-semibold text-gray-900 dark:text-white",children:c}),s&&r&&h.jsx(oe,{onClick:r,className:"text-sm text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300 font-medium",children:tl("common.reset")})]}),h.jsx(I,{className:"space-y-4",children:e.map(f=>h.jsxs(I,{className:"space-y-2",children:[h.jsx(sn,{className:"block text-sm font-medium text-gray-700 dark:text-gray-300",children:f.label}),f.type==="select"&&h.jsxs(Pr,{value:f.value,onChange:g=>d(f.id,g.target.value),className:"w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400 focus:border-blue-500 dark:focus:border-blue-400 bg-white dark:bg-gray-700 text-gray-900 dark:text-white",children:[h.jsx("option",{value:"",children:tl("common.select_placeholder")}),f.options.map(g=>h.jsx("option",{value:g.value,children:g.label},g.id))]}),f.type==="checkbox"&&h.jsx(I,{className:"space-y-2",children:f.options.map(g=>{const m=Array.isArray(f.value)?f.value.some(p=>p===g.value):!1;return h.jsxs(sn,{className:"flex items-center gap-2 cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-700 p-2 rounded-md transition-colors",children:[h.jsx(vn,{checked:m,onChange:p=>u(f.id,g.value,p.target.checked),className:"w-4 h-4 text-blue-600 border-gray-300 dark:border-gray-600 rounded focus:ring-blue-500"}),h.jsx(Q,{className:"text-sm text-gray-700 dark:text-gray-300",children:g.label})]},g.id)})})]},f.id))})]})},ry=({tabs:t,activeTabId:e,onTabChange:n,variant:r="default",className:s="",style:o,mobileBreakpoint:i=768,id:a,editorAttrs:l})=>{const d=window.G7Core?.useResponsive,u=d?.(),f=u?u.width{!k.disabled&&k.id!==e&&n?.(k.id)},p=(k,N)=>{const S=k.key==="ArrowRight"?1:k.key==="ArrowLeft"?-1:0;let w=null;if(S!==0)for(let M=1;M<=t.length;M+=1){const T=(N+S*M+t.length*M)%t.length;if(!t[T]?.disabled){w=T;break}}else if(k.key==="Home")w=t.findIndex(M=>!M.disabled);else if(k.key==="End"){for(let M=t.length-1;M>=0;M-=1)if(!t[M]?.disabled){w=M;break}}w===null||w<0||(k.preventDefault(),g.current[w]?.focus(),m(t[w]))},y=k=>{const N=k.target.value,S=Number(N),w=typeof N=="string"&&N!==""&&!Number.isNaN(S)?S:N,M=t.find(T=>String(T.id)===String(w));M&&m(M)};if(f)return h.jsx(I,{className:s,style:o,id:a,...l,children:h.jsx(Pr,{value:e!==void 0?String(e):"",onChange:y,options:t.map(k=>({value:String(k.id),label:k.badge!==void 0?`${k.label} (${k.badge})`:k.label,disabled:k.disabled})),className:"w-full"})});const b=k=>{const N=k.id===e;if(k.disabled)return"tab-btn-base tab-btn-disabled";switch(r){case"pills":return N?"tab-btn-base tab-btn-pills-active":"tab-btn-base tab-btn-pills";case"underline":return N?"tab-btn-base tab-btn-underline-active":"tab-btn-base tab-btn-underline";default:return N?"tab-btn-base tab-btn-default-active":"tab-btn-base tab-btn-default"}},_=r==="underline"?"tab-nav-underline":"tab-nav-default",x=l?.["data-editor-path"],E=t.findIndex(k=>k.id===e),C=E>=0?E:t.findIndex(k=>!k.disabled);return h.jsx(Po,{className:`${_} ${s}`,style:o,role:"tablist","aria-orientation":"horizontal",id:a,...l,children:t.map((k,N)=>h.jsxs(oe,{ref:S=>{g.current[N]=S},type:"button",role:"tab","aria-selected":k.id===e,tabIndex:N===C?0:-1,onClick:()=>m(k),onKeyDown:S=>p(S,N),disabled:k.disabled,className:b(k),...x?{"data-editor-item-path":`${x}.props.tabs.${N}`}:{},children:[k.iconName&&h.jsx(re,{name:k.iconName,className:"w-4 h-4"}),h.jsx(Q,{children:k.label}),k.badge!==void 0&&h.jsx(I,{className:"flex items-center justify-center min-w-[20px] h-5 px-1.5 bg-red-500 dark:bg-red-600 text-white dark:text-white text-xs font-bold rounded-full",children:h.jsx(Q,{children:k.badge})})]},k.id))})},nl=window.G7Core?.createLogger?.("Handler:ScrollToSection")??{log:(...t)=>console.log("[Handler:ScrollToSection]",...t),warn:(...t)=>console.warn("[Handler:ScrollToSection]",...t),error:(...t)=>console.error("[Handler:ScrollToSection]",...t)};async function eu(t,e){const n=t.params||{},{targetId:r,offset:s=120,delay:o=100,scrollContainerId:i}=n;if(!r){nl.warn("targetId is required");return}const a=await sy(r,o);if(!a){nl.warn(`Element not found: ${r}`);return}const l=oy(a,i),c=iy(a,l,s);ay(l,c)}async function sy(t,e){await new Promise(s=>setTimeout(s,e));const n=10,r=50;for(let s=0;ssetTimeout(i,50)),o;await new Promise(i=>setTimeout(i,r))}return null}function oy(t,e){if(e){const o=document.getElementById(e);if(o&&tu(o))return o;nl.warn(`Container '${e}' ${o?"has no scroll":"not found"}, trying auto-detect`)}let n=t.parentElement,r=0;const s=10;for(;n&&n!==document.body&&rt.clientHeight}function iy(t,e,n){if(e===window||!("getBoundingClientRect"in e))return t.getBoundingClientRect().top+window.pageYOffset-n;{const r=e.getBoundingClientRect(),s=t.getBoundingClientRect();return e.scrollTop+(s.top-r.top)-n}}function ay(t,e){t===window||!("scrollTo"in t)?window.scrollTo({top:e,behavior:"smooth"}):t.scrollTo({top:e,behavior:"smooth"})}window.G7Core?.createLogger?.("Comp:TabNavigation");const ly=({tabs:t,activeTabId:e,className:n="bg-white dark:bg-gray-800 flex gap-2 border-b border-gray-200 dark:border-gray-700 px-6",style:r,activeClassName:s="tab-btn-base tab-btn-default-active",inactiveClassName:o="tab-btn-base tab-btn-default",sectionIdPrefix:i="tab_content_",scrollOffset:a=120,scrollDelay:l=100,onTabChange:c,enableScrollSpy:d=!1,scrollSpyOffset:u=80,scrollContainerId:f,id:g,editorAttrs:m})=>{const[p,y]=v.useState(e??(t.length>0?t[0].id:"")),_=window.G7Core?.useResponsive,x=_?.(),E=x?x.width<768:typeof window<"u"&&window.innerWidth<768,C=v.useRef(!1),k=v.useRef(null),N=v.useRef([]),S=L=>{if(d&&(C.current=!0,k.current&&clearTimeout(k.current)),L.onClick){if(L.onClick(L.id),!d)return}else if(c){if(c(L.id),!d)return}else y(L.id);const $=L.scrollOffset??a,A=L.scrollDelay??l;eu({params:{targetId:`${i}${L.id}`,offset:$,delay:A,scrollContainerId:f}}),d&&(k.current=setTimeout(()=>{C.current=!1},A+500))},w=(L,$)=>{const A=L.key==="ArrowRight"?1:L.key==="ArrowLeft"?-1:0;let D=null;if(A!==0)for(let O=1;O<=t.length;O+=1){const z=($+A*O+t.length*O)%t.length;if(!t[z]?.disabled){D=z;break}}else if(L.key==="Home")D=t.findIndex(O=>!O.disabled);else if(L.key==="End"){for(let O=t.length-1;O>=0;O-=1)if(!t[O]?.disabled){D=O;break}}D===null||D<0||(L.preventDefault(),N.current[D]?.focus(),S(t[D]))};v.useEffect(()=>{if(!d)return;const L=f?document.getElementById(f):null,$={root:L??null,rootMargin:`-${u}px 0px -30% 0px`,threshold:[0,.1,.2,.3,.4,.5,.6,.7,.8,.9,1]},A=new Map,D=()=>{const j=L,W=j?j.scrollTop:window.pageYOffset||document.documentElement.scrollTop,U=j?j.scrollHeight:document.documentElement.scrollHeight,B=j?j.clientHeight:document.documentElement.clientHeight;return W+B>=U-10},O=j=>{if(C.current)return;j.forEach(X=>{const K=X.target.id;X.isIntersecting?A.set(K,X.intersectionRatio):A.delete(K)});let W=0,U="";A.forEach((X,K)=>{X>W&&(W=X,U=K)});const B=t[t.length-1],G=`${i}${B?.id}`,V=A.has(G);if(D()&&V&&B){y(B.id);return}if(U){const X=U.replace(i,""),K=Number(X),Z=isNaN(K)?X:K;t.some(ne=>ne.id===Z)&&y(Z)}},z=()=>{},H=new IntersectionObserver(O,$);return t.forEach(j=>{const W=document.getElementById(`${i}${j.id}`);W&&H.observe(W)}),window.addEventListener("scroll",z,{passive:!0}),()=>{H.disconnect(),window.removeEventListener("scroll",z),k.current&&clearTimeout(k.current)}},[d,t,i,u,f]);const M=L=>{const $=L.target.value,A=Number($),D=isNaN(A)?$:A,O=t.find(z=>z.id===D);O&&S(O)};if(E)return h.jsx(I,{className:"bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 px-4 py-3",id:g,...m,children:h.jsx(Pr,{value:String(p),onChange:M,options:t.map(L=>({value:String(L.id),label:L.label,disabled:L.disabled})),className:"w-full"})});const T=t.findIndex(L=>L.id===p),P=T>=0?T:t.findIndex(L=>!L.disabled);return h.jsx(I,{className:n,style:r,role:"tablist","aria-orientation":"horizontal",id:g,...m,children:t.map((L,$)=>h.jsxs(oe,{ref:A=>{N.current[$]=A},type:"button",role:"tab","aria-selected":p===L.id,tabIndex:$===P?0:-1,className:p===L.id?s:o,disabled:L.disabled,onClick:()=>S(L),onKeyDown:A=>w(A,$),children:[L.iconName&&h.jsx(re,{name:L.iconName,size:"sm"}),h.jsx(Q,{children:L.label})]},L.id))})},cy=()=>window.G7Core,dy=({iconName:t,label:e,onClick:n,variant:r="primary",size:s="md",disabled:o=!1,className:i="",style:a,id:l,editorAttrs:c})=>{const d={sm:e?"px-3 py-1.5 text-sm":"w-8 h-8",md:e?"px-4 py-2 text-base":"w-10 h-10",lg:e?"px-5 py-2.5 text-lg":"w-12 h-12"},u={sm:"w-4 h-4",md:"w-5 h-5",lg:"w-6 h-6"},f={primary:"bg-blue-600 hover:bg-blue-700 text-white border-transparent",secondary:"bg-gray-100 dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-gray-600 text-gray-700 dark:text-gray-300 border-gray-300 dark:border-gray-600",danger:"bg-red-600 hover:bg-red-700 text-white border-transparent",ghost:"bg-transparent hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300 border-transparent",outline:"bg-transparent hover:bg-gray-50 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300 border-gray-300 dark:border-gray-600"},g="inline-flex items-center justify-center gap-2 font-medium rounded-lg border transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 dark:focus:ring-blue-400",m=o?"opacity-50 cursor-not-allowed":"",p=`${g} ${d[s]} ${f[r]} ${m}`,y=cy()?.style?.mergeClasses?.(p,i)??`${p} ${i}`;return h.jsxs(oe,{onClick:n,disabled:o,className:y,style:a,id:l,...c,children:[h.jsx(re,{name:t,className:u[s]}),e]})},uy=(t,e)=>window.G7Core?.t?.(t,e)??t,nu=({isOpen:t,onClose:e,title:n,content:r,actions:s=[],width:o="500px",closeOnOverlay:i=!0,showCloseButton:a=!0,className:l="",style:c})=>{const d=()=>{i&&e()},u={primary:"dialog-btn-primary",secondary:"dialog-btn-secondary",danger:"dialog-btn-danger",ghost:"dialog-btn-ghost"};return h.jsxs(Bo,{isOpen:t,onClose:d,width:o,className:l,style:c,children:[h.jsxs(I,{className:"dialog-header",children:[h.jsx(Jn,{className:"dialog-title",children:n}),a&&h.jsx(oe,{onClick:e,className:"dialog-close","aria-label":uy("common.close_dialog"),children:h.jsx(re,{name:de.Times,className:"icon-md"})})]}),h.jsx(I,{className:"dialog-body",children:r}),s.length>0&&h.jsx(I,{className:"dialog-footer",children:s.map((f,g)=>h.jsxs(oe,{onClick:f.onClick,disabled:f.disabled,className:u[f.variant||"primary"],children:[f.iconName&&h.jsx(re,{name:f.iconName,className:"icon-sm mr-2 inline-block"}),f.label]},g))})]})},rl=(t,e)=>window.G7Core?.t?.(t,e)??t,ru=({isOpen:t,onClose:e,title:n,message:r,confirmText:s,cancelText:o,onConfirm:i,onCancel:a,confirmButtonVariant:l="primary",cancelButtonVariant:c="secondary",width:d="500px",closeOnOverlay:u=!0,isLoading:f=!1,loadingText:g})=>{const m=s??rl("common.confirm"),p=o??rl("common.cancel"),y=g??rl("common.loading"),b=()=>{i()},_=()=>{f||(a?.(),e())},x=()=>{f||e()},E={primary:"bg-blue-600 text-white hover:bg-blue-700",secondary:"bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200 hover:bg-gray-300 dark:hover:bg-gray-600",danger:"bg-red-600 text-white hover:bg-red-700",ghost:"bg-transparent text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700"};return h.jsxs(Bo,{isOpen:t,onClose:x,title:n,width:d,children:[h.jsx(I,{className:"py-2",children:typeof r=="string"?h.jsx(yt,{className:"text-gray-700 dark:text-gray-300",children:r}):r}),h.jsxs(I,{className:"flex justify-end gap-3 mt-6",children:[h.jsx(oe,{onClick:_,disabled:f,className:"px-4 py-2 bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 disabled:opacity-50 disabled:cursor-not-allowed",children:h.jsx(Q,{children:p})}),h.jsxs(oe,{onClick:b,disabled:f,className:`flex items-center gap-2 px-4 py-2 rounded-lg disabled:opacity-50 disabled:cursor-not-allowed ${E[l]}`,children:[f&&h.jsx(re,{name:de.Spinner,className:"w-4 h-4 animate-spin"}),h.jsx(Q,{children:f?y:m})]})]})]})},fy=(t,e)=>window.G7Core?.t?.(t,e)??t,hy=({isOpen:t,onClose:e,title:n,message:r,confirmText:s,onConfirm:o,confirmButtonVariant:i="primary",width:a="500px",closeOnOverlay:l=!0})=>{const u=[{label:s??fy("common.confirm"),onClick:()=>{o?.(),e()},variant:i}],f=typeof r=="string"?h.jsx(yt,{className:"text-gray-700 dark:text-gray-300",children:r}):r;return h.jsx(nu,{isOpen:t,onClose:e,title:n,content:f,actions:u,width:a,closeOnOverlay:l})},gy=(t,e)=>window.G7Core?.t?.(t,e)??t,py={success:de.CheckCircle,error:de.XCircle,warning:de.ExclamationTriangle,info:de.InfoCircle},su={success:{bg:"bg-green-50 dark:bg-green-900/20 border-green-200 dark:border-green-800",text:"text-green-800 dark:text-green-300"},error:{bg:"bg-red-50 dark:bg-red-900/20 border-red-200 dark:border-red-800",text:"text-red-800 dark:text-red-300"},warning:{bg:"bg-yellow-50 dark:bg-yellow-900/20 border-yellow-200 dark:border-yellow-800",text:"text-yellow-800 dark:text-yellow-300"},info:{bg:"bg-blue-50 dark:bg-blue-900/20 border-blue-200 dark:border-blue-800",text:"text-blue-800 dark:text-blue-300"}},my={"top-left":"top-4 left-4","top-center":"top-4 left-1/2 -translate-x-1/2","top-right":"top-4 right-4","bottom-left":"bottom-4 left-4","bottom-center":"bottom-4 left-1/2 -translate-x-1/2","bottom-right":"bottom-4 right-4"},by=({toasts:t,position:e="bottom-center",duration:n=3e3,onRemove:r})=>{const[s,o]=v.useState(new Map),i=v.useCallback(a=>{o(l=>{const c=new Map(l);return c.set(a,!1),c}),setTimeout(()=>{if(r)r(a);else{const l=window.G7Core;l?.state?.update&&l.state.update(c=>({toasts:(Array.isArray(c.toasts)?c.toasts:[]).filter(u=>u.id!==a)}))}},300)},[r]);return v.useEffect(()=>{!t||t.length===0||t.forEach(a=>{if(s.has(a.id))return;o(c=>{const d=new Map(c);return d.set(a.id,!0),d});const l=a.duration||n;l>0&&setTimeout(()=>{i(a.id)},l)})},[t,n,i,s]),!t||t.length===0?null:h.jsx(I,{className:`fixed z-[9999] flex flex-col gap-2 ${my[e]}`,"aria-live":"polite","aria-atomic":"true",children:t.map(a=>h.jsx(yy,{toast:a,position:e,isVisible:s.get(a.id)??!0,onClose:()=>i(a.id)},a.id))})},yy=({toast:t,position:e,isVisible:n,onClose:r})=>{const s=su[t.type]||su.info,o=t.icon?t.icon:py[t.type],i=e.startsWith("bottom")?"translate-y-2":"-translate-y-2";return h.jsxs(I,{className:` flex items-center gap-3 p-4 rounded-lg border shadow-lg min-w-[300px] max-w-md transition-all duration-300 ${s.bg} ${n?"opacity-100 translate-y-0":`opacity-0 ${i}`} - `,role:"alert",children:[h.jsx(oe,{name:o,className:`w-5 h-5 flex-shrink-0 ${s.text}`}),h.jsx(Q,{className:`flex-1 text-sm font-medium ${s.text}`,children:e.message}),h.jsx(ae,{onClick:r,className:`flex-shrink-0 p-1 rounded hover:bg-black/10 dark:hover:bg-white/10 ${s.text} focus:outline-none`,"aria-label":Py("common.close_notification"),children:h.jsx(oe,{name:de.Times,className:"w-4 h-4"})})]})},bu=({items:e,separator:t=h.jsx(oe,{name:de.ChevronRight,className:"w-4 h-4 text-gray-400 dark:text-gray-500"}),showHome:n=!1,homeHref:r="/",maxItems:s,className:o="",id:i,editorAttrs:a})=>{let c=n?[{label:"Home",href:r},...e]:e,d=c,u=!1;if(s&&c.length>s){u=!0;const f=c.slice(0,1),g=c.slice(-(s-2));d=[...f,...g]}return h.jsx(Yo,{className:`flex items-center space-x-2 text-sm ${o}`,"aria-label":"Breadcrumb",id:i,...a,children:h.jsx(I,{className:"flex items-center space-x-2",children:d.map((f,g)=>{const m=g===d.length-1,p=g===0;return h.jsxs(v.Fragment,{children:[u&&p&&h.jsxs(h.Fragment,{children:[h.jsx(yu,{item:f,isLast:!1}),h.jsx(Q,{className:"text-gray-400 dark:text-gray-500","aria-hidden":"true",children:t}),h.jsx(Q,{className:"text-gray-500 dark:text-gray-400",children:"..."}),d.length>1&&h.jsx(Q,{className:"text-gray-400 dark:text-gray-500","aria-hidden":"true",children:t})]}),(!u||!p)&&h.jsxs(h.Fragment,{children:[h.jsx(yu,{item:f,isLast:m}),!m&&h.jsx(Q,{className:"text-gray-400 dark:text-gray-500","aria-hidden":"true",children:t})]})]},g)})})})},yu=({item:e,isLast:t})=>t||!e.href&&!e.onClick?h.jsx(Q,{className:"font-medium text-gray-900 dark:text-white","aria-current":t?"page":void 0,children:e.label}):e.onClick?h.jsx(Yr,{href:"#",onClick:n=>{n.preventDefault(),e.onClick?.()},className:"text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200 transition-colors",children:e.label}):h.jsx(Yr,{href:e.href,className:"text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200 transition-colors",children:e.label}),Hy=({title:e,description:t,iconName:n,illustrationSrc:r,illustrationAlt:s="Empty state illustration",actions:o=[],className:i="",id:a,editorAttrs:l})=>{const c={primary:"bg-blue-600 text-white hover:bg-blue-700 focus:ring-blue-500",secondary:"bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200 hover:bg-gray-300 dark:hover:bg-gray-600 focus:ring-gray-500",danger:"bg-red-600 text-white hover:bg-red-700 focus:ring-red-500",ghost:"bg-transparent text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 focus:ring-gray-500"};return h.jsxs(I,{className:`flex flex-col items-center justify-center p-8 text-center ${i}`,role:"status","aria-live":"polite",id:a,...l,children:[r?h.jsx(_n,{src:r,alt:s,className:"w-48 h-48 mb-6 object-contain"}):n?h.jsx(I,{className:"mb-6 p-4 rounded-full bg-gray-100 dark:bg-gray-700",children:h.jsx(oe,{name:n,className:"w-12 h-12 text-gray-400 dark:text-gray-500"})}):null,h.jsx(ar,{className:"text-xl font-semibold text-gray-900 dark:text-white mb-2",children:e}),t&&h.jsx(vt,{className:"text-gray-600 dark:text-gray-400 mb-6 max-w-md",children:t}),o.length>0&&h.jsx(I,{className:"flex flex-wrap gap-3 justify-center",children:o.map((d,u)=>h.jsxs(ae,{onClick:d.onClick,className:` + `,role:"alert",children:[h.jsx(re,{name:o,className:`w-5 h-5 flex-shrink-0 ${s.text}`}),h.jsx(Q,{className:`flex-1 text-sm font-medium ${s.text}`,children:t.message}),h.jsx(oe,{onClick:r,className:`flex-shrink-0 p-1 rounded hover:bg-black/10 dark:hover:bg-white/10 ${s.text} focus:outline-none`,"aria-label":gy("common.close_notification"),children:h.jsx(re,{name:de.Times,className:"w-4 h-4"})})]})},ou=({items:t,separator:e=h.jsx(re,{name:de.ChevronRight,className:"w-4 h-4 text-gray-400 dark:text-gray-500"}),showHome:n=!1,homeHref:r="/",maxItems:s,className:o="",id:i,editorAttrs:a})=>{let c=n?[{label:"Home",href:r},...t]:t,d=c,u=!1;if(s&&c.length>s){u=!0;const f=c.slice(0,1),g=c.slice(-(s-2));d=[...f,...g]}return h.jsx(Po,{className:`flex items-center space-x-2 text-sm ${o}`,"aria-label":"Breadcrumb",id:i,...a,children:h.jsx(I,{className:"flex items-center space-x-2",children:d.map((f,g)=>{const m=g===d.length-1,p=g===0;return h.jsxs(v.Fragment,{children:[u&&p&&h.jsxs(h.Fragment,{children:[h.jsx(iu,{item:f,isLast:!1}),h.jsx(Q,{className:"text-gray-400 dark:text-gray-500","aria-hidden":"true",children:e}),h.jsx(Q,{className:"text-gray-500 dark:text-gray-400",children:"..."}),d.length>1&&h.jsx(Q,{className:"text-gray-400 dark:text-gray-500","aria-hidden":"true",children:e})]}),(!u||!p)&&h.jsxs(h.Fragment,{children:[h.jsx(iu,{item:f,isLast:m}),!m&&h.jsx(Q,{className:"text-gray-400 dark:text-gray-500","aria-hidden":"true",children:e})]})]},g)})})})},iu=({item:t,isLast:e})=>e||!t.href&&!t.onClick?h.jsx(Q,{className:"font-medium text-gray-900 dark:text-white","aria-current":e?"page":void 0,children:t.label}):t.onClick?h.jsx(Lr,{href:"#",onClick:n=>{n.preventDefault(),t.onClick?.()},className:"text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200 transition-colors",children:t.label}):h.jsx(Lr,{href:t.href,className:"text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200 transition-colors",children:t.label}),vy=({title:t,description:e,iconName:n,illustrationSrc:r,illustrationAlt:s="Empty state illustration",actions:o=[],className:i="",id:a,editorAttrs:l})=>{const c={primary:"bg-blue-600 text-white hover:bg-blue-700 focus:ring-blue-500",secondary:"bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200 hover:bg-gray-300 dark:hover:bg-gray-600 focus:ring-gray-500",danger:"bg-red-600 text-white hover:bg-red-700 focus:ring-red-500",ghost:"bg-transparent text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 focus:ring-gray-500"};return h.jsxs(I,{className:`flex flex-col items-center justify-center p-8 text-center ${i}`,role:"status","aria-live":"polite",id:a,...l,children:[r?h.jsx(yn,{src:r,alt:s,className:"w-48 h-48 mb-6 object-contain"}):n?h.jsx(I,{className:"mb-6 p-4 rounded-full bg-gray-100 dark:bg-gray-700",children:h.jsx(re,{name:n,className:"w-12 h-12 text-gray-400 dark:text-gray-500"})}):null,h.jsx(Jn,{className:"text-xl font-semibold text-gray-900 dark:text-white mb-2",children:t}),e&&h.jsx(yt,{className:"text-gray-600 dark:text-gray-400 mb-6 max-w-md",children:e}),o.length>0&&h.jsx(I,{className:"flex flex-wrap gap-3 justify-center",children:o.map((d,u)=>h.jsxs(oe,{onClick:d.onClick,className:` px-4 py-2 rounded-md font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 ${c[d.variant||"primary"]} - `,children:[d.iconName&&h.jsx(oe,{name:d.iconName,className:"w-4 h-4 mr-2 inline-block"}),d.label]},u))})]})},Uy=(e,t)=>window.G7Core?.t?.(e,t)??e,By=({id:e,size:t="md",color:n="text-blue-600",fullscreen:r=!1,text:s,className:o="",editorAttrs:i})=>{const a={sm:"w-4 h-4 border-2",md:"w-8 h-8 border-2",lg:"w-12 h-12 border-3",xl:"w-16 h-16 border-4"},l={sm:"text-xs",md:"text-sm",lg:"text-base",xl:"text-lg"},c=h.jsxs(I,{className:`flex flex-col items-center justify-center gap-3 ${o}`,children:[h.jsx(I,{className:` - ${a[t]} + `,children:[d.iconName&&h.jsx(re,{name:d.iconName,className:"w-4 h-4 mr-2 inline-block"}),d.label]},u))})]})},xy=(t,e)=>window.G7Core?.t?.(t,e)??t,wy=({id:t,size:e="md",color:n="text-blue-600",fullscreen:r=!1,text:s,className:o="",editorAttrs:i})=>{const a={sm:"w-4 h-4 border-2",md:"w-8 h-8 border-2",lg:"w-12 h-12 border-3",xl:"w-16 h-16 border-4"},l={sm:"text-xs",md:"text-sm",lg:"text-base",xl:"text-lg"},c=h.jsxs(I,{className:`flex flex-col items-center justify-center gap-3 ${o}`,children:[h.jsx(I,{className:` + ${a[e]} border-gray-200 dark:border-gray-700 border-t-current rounded-full animate-spin ${n} - `,role:"status","aria-label":Uy("common.loading")}),s&&h.jsx(Q,{className:`${l[t]} ${n} font-medium`,children:s})]});return r?h.jsx(I,{id:e,className:"fixed inset-0 z-50 flex items-center justify-center bg-white dark:bg-gray-900 bg-opacity-90 dark:bg-opacity-90","aria-live":"polite","aria-busy":"true",...i,children:c}):h.jsx(I,{id:e,className:"flex items-center justify-center p-4","aria-live":"polite","aria-busy":"true",...i,children:c})},ts=()=>window.G7Core,hl=(e,t)=>window.G7Core?.t?.(e,t)??e,jy=({logo:e,logoAlt:t="Logo",menu:n=[],collapsed:r=!1,onToggleCollapse:s,className:o="",currentLocale:i,id:a,editorAttrs:l})=>{const[c,d]=v.useState(new Set),[u,f]=v.useState(i||ts()?.locale?.current?.()||"ko"),[g,m]=v.useState(window.location.pathname),p=v.useMemo(()=>{const w=[],E=D=>{for(const P of D)P.url&&w.push(P.url),P.children&&E(P.children)};return E(n),w},[n]),y=v.useCallback(w=>{let E=null;for(const D of p)(w===D||w.startsWith(D+"/"))&&(!E||D.length>E.length)&&(E=D);return E},[p]);v.useEffect(()=>{const w=ts();return w?.state?.subscribe?w.state.subscribe(D=>{const P=D?._global?.locale||w?.locale?.current?.()||"ko";f(P);const L=D?._global?.currentPath||window.location.pathname;m(L)}):void 0},[]),v.useEffect(()=>{const w=()=>{m(window.location.pathname)};return window.addEventListener("popstate",w),()=>window.removeEventListener("popstate",w)},[]),v.useEffect(()=>{i&&f(i)},[i]);const b=(w,E)=>!w||typeof E!="string"?!1:E===w?!0:E.startsWith(w+"/")?y(E)===w:!1,_=(w,E)=>{const D=new Set,P=L=>{for(const $ of L)if($.children&&$.children.length>0){if(P($.children)||b($.url,E))return D.add($.id),!0}else if(b($.url,E))return!0;return!1};return P(w),D};v.useEffect(()=>{if(n.length>0&&g){const w=_(n,g);w.size>0&&d(E=>new Set([...E,...w]))}},[g,n]);const x=w=>b(w,g),M=w=>w.children?w.children.some(E=>x(E.url)||M(E)):!1,C=w=>{d(E=>{const D=new Set(E);return D.has(w)?D.delete(w):D.add(w),D})},k=w=>typeof w=="string"?w:w[u]||w.en||Object.values(w)[0]||"",N=(w,E)=>{ts()?.dispatch?(w.preventDefault(),ts().navigation?.onComplete(()=>{ts().dispatch({handler:"setState",params:{target:"global",sidebarOpen:!1}})}),ts().dispatch({handler:"navigate",params:{path:E}})):window.location.href=E},S=(w,E=0,D=!1)=>{const P=w.children&&w.children.length>0,L=c.has(w.id),$=k(w.name),A=E>0,O=x(w.url),T=M(w),z=A?"admin-sidebar-subitem":"admin-sidebar-item",H="admin-sidebar-item-active",j="admin-sidebar-item-active-parent",G="admin-sidebar-item-default";return h.jsxs(I,{className:A?"relative group":"",children:[A&&h.jsxs(h.Fragment,{children:[!D&&h.jsx(I,{className:"admin-sidebar-tree-line-vertical"}),h.jsx(I,{className:"admin-sidebar-tree-line-curve"})]}),P&&w.url?h.jsxs(I,{className:` + `,role:"status","aria-label":xy("common.loading")}),s&&h.jsx(Q,{className:`${l[e]} ${n} font-medium`,children:s})]});return r?h.jsx(I,{id:t,className:"fixed inset-0 z-50 flex items-center justify-center bg-white dark:bg-gray-900 bg-opacity-90 dark:bg-opacity-90","aria-live":"polite","aria-busy":"true",...i,children:c}):h.jsx(I,{id:t,className:"flex items-center justify-center p-4","aria-live":"polite","aria-busy":"true",...i,children:c})},Vr=()=>window.G7Core,sl=(t,e)=>window.G7Core?.t?.(t,e)??t,ky=({logo:t,logoAlt:e="Logo",menu:n=[],collapsed:r=!1,onToggleCollapse:s,className:o="",currentLocale:i,id:a,editorAttrs:l})=>{const[c,d]=v.useState(new Set),[u,f]=v.useState(i||Vr()?.locale?.current?.()||"ko"),[g,m]=v.useState(window.location.pathname),p=v.useMemo(()=>{const w=[],M=T=>{for(const P of T)P.url&&w.push(P.url),P.children&&M(P.children)};return M(n),w},[n]),y=v.useCallback(w=>{let M=null;for(const T of p)(w===T||w.startsWith(T+"/"))&&(!M||T.length>M.length)&&(M=T);return M},[p]);v.useEffect(()=>{const w=Vr();return w?.state?.subscribe?w.state.subscribe(T=>{const P=T?._global?.locale||w?.locale?.current?.()||"ko";f(P);const L=T?._global?.currentPath||window.location.pathname;m(L)}):void 0},[]),v.useEffect(()=>{const w=()=>{m(window.location.pathname)};return window.addEventListener("popstate",w),()=>window.removeEventListener("popstate",w)},[]),v.useEffect(()=>{i&&f(i)},[i]);const b=(w,M)=>!w||typeof M!="string"?!1:M===w?!0:M.startsWith(w+"/")?y(M)===w:!1,_=(w,M)=>{const T=new Set,P=L=>{for(const $ of L)if($.children&&$.children.length>0){if(P($.children)||b($.url,M))return T.add($.id),!0}else if(b($.url,M))return!0;return!1};return P(w),T};v.useEffect(()=>{if(n.length>0&&g){const w=_(n,g);w.size>0&&d(M=>new Set([...M,...w]))}},[g,n]);const x=w=>b(w,g),E=w=>w.children?w.children.some(M=>x(M.url)||E(M)):!1,C=w=>{d(M=>{const T=new Set(M);return T.has(w)?T.delete(w):T.add(w),T})},k=w=>typeof w=="string"?w:w[u]||w.en||Object.values(w)[0]||"",N=(w,M)=>{Vr()?.dispatch?(w.preventDefault(),Vr().navigation?.onComplete(()=>{Vr().dispatch({handler:"setState",params:{target:"global",sidebarOpen:!1}})}),Vr().dispatch({handler:"navigate",params:{path:M}})):window.location.href=M},S=(w,M=0,T=!1)=>{const P=w.children&&w.children.length>0,L=c.has(w.id),$=k(w.name),A=M>0,D=x(w.url),O=E(w),z=A?"admin-sidebar-subitem":"admin-sidebar-item",H="admin-sidebar-item-active",j="admin-sidebar-item-active-parent",W="admin-sidebar-item-default";return h.jsxs(I,{className:A?"relative group":"",children:[A&&h.jsxs(h.Fragment,{children:[!T&&h.jsx(I,{className:"admin-sidebar-tree-line-vertical"}),h.jsx(I,{className:"admin-sidebar-tree-line-curve"})]}),P&&w.url?h.jsxs(I,{className:` ${z} - ${O?H:T?j:G} + ${D?H:O?j:W} ${r?"justify-center":""} - `,children:[h.jsxs(ae,{onClick:W=>N(W,w.url),className:"flex items-center justify-start flex-1 min-w-0",children:[w.icon&&h.jsx(bt,{className:`${w.icon} ${r?"":"mr-3"} w-5 h-5 flex items-center justify-center`}),!r&&h.jsx(Q,{className:"truncate",children:$})]}),!r&&h.jsxs(I,{className:"admin-sidebar-chevron-trail",children:[w.module_id&&h.jsx(bt,{className:"fas fa-cube admin-sidebar-module-icon",title:"모듈 메뉴"}),h.jsx(ae,{onClick:()=>C(w.id),className:"p-1 rounded hover:bg-gray-200 dark:hover:bg-gray-600","aria-label":hl(L?"common.collapse":"common.expand"),children:h.jsx(bt,{className:`fas fa-chevron-down w-4 h-4 transition-transform ${L?"rotate-180":""}`})})]})]}):P?h.jsxs(ae,{onClick:()=>C(w.id),className:` + `,children:[h.jsxs(oe,{onClick:B=>N(B,w.url),className:"flex items-center justify-start flex-1 min-w-0",children:[w.icon&&h.jsx(mt,{className:`${w.icon} ${r?"":"mr-3"} w-5 h-5 flex items-center justify-center`}),!r&&h.jsx(Q,{className:"truncate",children:$})]}),!r&&h.jsxs(I,{className:"admin-sidebar-chevron-trail",children:[w.module_id&&h.jsx(mt,{className:"fas fa-cube admin-sidebar-module-icon",title:"모듈 메뉴"}),h.jsx(oe,{onClick:()=>C(w.id),className:"p-1 rounded hover:bg-gray-200 dark:hover:bg-gray-600","aria-label":sl(L?"common.collapse":"common.expand"),children:h.jsx(mt,{className:`fas fa-chevron-down w-4 h-4 transition-transform ${L?"rotate-180":""}`})})]})]}):P?h.jsxs(oe,{onClick:()=>C(w.id),className:` ${z} - ${T?j:G} + ${O?j:W} ${r?"justify-center":""} - `,children:[h.jsxs(I,{className:"admin-sidebar-item-content",children:[w.icon&&h.jsx(I,{className:"admin-sidebar-icon-box",children:h.jsx(bt,{className:w.icon})}),!r&&h.jsx(Q,{children:$})]}),!r&&h.jsxs(I,{className:"admin-sidebar-chevron-trail",children:[w.module_id&&h.jsx(bt,{className:"fas fa-cube admin-sidebar-module-icon",title:"모듈 메뉴"}),h.jsx(bt,{className:`fas fa-chevron-right w-4 h-4 text-slate-400 transition-transform duration-200 ${L?"rotate-90":""}`})]})]}):w.url?h.jsxs(ae,{onClick:W=>N(W,w.url),className:` + `,children:[h.jsxs(I,{className:"admin-sidebar-item-content",children:[w.icon&&h.jsx(I,{className:"admin-sidebar-icon-box",children:h.jsx(mt,{className:w.icon})}),!r&&h.jsx(Q,{children:$})]}),!r&&h.jsxs(I,{className:"admin-sidebar-chevron-trail",children:[w.module_id&&h.jsx(mt,{className:"fas fa-cube admin-sidebar-module-icon",title:"모듈 메뉴"}),h.jsx(mt,{className:`fas fa-chevron-right w-4 h-4 text-slate-400 transition-transform duration-200 ${L?"rotate-90":""}`})]})]}):w.url?h.jsxs(oe,{onClick:B=>N(B,w.url),className:` ${z} - ${O?H:G} + ${D?H:W} ${r?"justify-center":""} text-left - `,children:[h.jsxs(I,{className:"admin-sidebar-item-content",children:[w.icon&&h.jsx(I,{className:"admin-sidebar-icon-box",children:h.jsx(bt,{className:w.icon})}),!r&&h.jsx(Q,{children:$})]}),!r&&w.module_id&&h.jsx(bt,{className:"fas fa-cube admin-sidebar-module-icon",title:"모듈 메뉴"})]}):h.jsxs(ae,{className:` + `,children:[h.jsxs(I,{className:"admin-sidebar-item-content",children:[w.icon&&h.jsx(I,{className:"admin-sidebar-icon-box",children:h.jsx(mt,{className:w.icon})}),!r&&h.jsx(Q,{children:$})]}),!r&&w.module_id&&h.jsx(mt,{className:"fas fa-cube admin-sidebar-module-icon",title:"모듈 메뉴"})]}):h.jsxs(oe,{className:` ${z} admin-sidebar-item-disabled ${r?"justify-center":""} text-left - `,children:[h.jsxs(I,{className:"admin-sidebar-item-content",children:[w.icon&&h.jsx(I,{className:"admin-sidebar-icon-box",children:h.jsx(bt,{className:w.icon})}),!r&&h.jsx(Q,{children:$})]}),!r&&w.module_id&&h.jsx(bt,{className:"fas fa-cube admin-sidebar-module-icon",title:"모듈 메뉴"})]}),P&&!r&&L&&h.jsx(I,{className:"relative ml-2",children:w.children.map((W,Y)=>S(W,E+1,Y===w.children.length-1))})]},w.id)};return h.jsxs(I,{className:o,id:a,...l,children:[(e||s)&&h.jsxs(I,{className:"admin-sidebar-header",children:[e&&!r&&h.jsx(_n,{src:e,alt:t,className:"h-8"}),s&&h.jsx(ae,{onClick:s,className:"admin-sidebar-collapse-btn","aria-label":hl(r?"common.expand_sidebar":"common.collapse_sidebar"),children:h.jsx(bt,{className:`${r?"fas fa-chevron-right":"fas fa-chevron-left"} w-5 h-5 text-slate-600 dark:text-slate-400`})})]}),h.jsx(I,{className:"admin-sidebar-menu-list",children:n.map((w,E)=>S(w,0,E===n.length-1))})]})},Vs=(e,t)=>window.G7Core?.t?.(e,t)??e,Wy=({user:e={name:"",email:""},notifications:t=[],onNotificationClick:n,onProfileClick:r,onLogoutClick:s,className:o=""})=>{const[i,a]=v.useState(!1),[l,c]=v.useState(!1),d=v.useRef(null),u=v.useRef(null),f=t.filter(g=>!g.read).length;return v.useEffect(()=>{const g=m=>{d.current&&!d.current.contains(m.target)&&a(!1),u.current&&!u.current.contains(m.target)&&c(!1)};return document.addEventListener("mousedown",g),()=>document.removeEventListener("mousedown",g)},[]),h.jsxs(I,{className:` + `,children:[h.jsxs(I,{className:"admin-sidebar-item-content",children:[w.icon&&h.jsx(I,{className:"admin-sidebar-icon-box",children:h.jsx(mt,{className:w.icon})}),!r&&h.jsx(Q,{children:$})]}),!r&&w.module_id&&h.jsx(mt,{className:"fas fa-cube admin-sidebar-module-icon",title:"모듈 메뉴"})]}),P&&!r&&L&&h.jsx(I,{className:"relative ml-2",children:w.children.map((B,G)=>S(B,M+1,G===w.children.length-1))})]},w.id)};return h.jsxs(I,{className:o,id:a,...l,children:[(t||s)&&h.jsxs(I,{className:"admin-sidebar-header",children:[t&&!r&&h.jsx(yn,{src:t,alt:e,className:"h-8"}),s&&h.jsx(oe,{onClick:s,className:"admin-sidebar-collapse-btn","aria-label":sl(r?"common.expand_sidebar":"common.collapse_sidebar"),children:h.jsx(mt,{className:`${r?"fas fa-chevron-right":"fas fa-chevron-left"} w-5 h-5 text-slate-600 dark:text-slate-400`})})]}),h.jsx(I,{className:"admin-sidebar-menu-list",children:n.map((w,M)=>S(w,0,M===n.length-1))})]})},Ms=(t,e)=>window.G7Core?.t?.(t,e)??t,Cy=({user:t={name:"",email:""},notifications:e=[],onNotificationClick:n,onProfileClick:r,onLogoutClick:s,className:o=""})=>{const[i,a]=v.useState(!1),[l,c]=v.useState(!1),d=v.useRef(null),u=v.useRef(null),f=e.filter(g=>!g.read).length;return v.useEffect(()=>{const g=m=>{d.current&&!d.current.contains(m.target)&&a(!1),u.current&&!u.current.contains(m.target)&&c(!1)};return document.addEventListener("mousedown",g),()=>document.removeEventListener("mousedown",g)},[]),h.jsxs(I,{className:` bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 px-6 py-4 flex items-center justify-end gap-4 ${o} - `,children:[h.jsxs(I,{ref:d,className:"relative",children:[h.jsxs(ae,{onClick:()=>a(!i),className:"relative p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-full transition-colors","aria-label":Vs("common.notifications"),children:[h.jsx(oe,{name:de.Bell,className:"w-5 h-5 text-gray-600 dark:text-gray-400"}),f>0&&h.jsx(Q,{className:"absolute top-0 right-0 flex items-center justify-center w-5 h-5 text-xs font-bold text-white bg-red-500 rounded-full",children:f>9?"9+":f})]}),i&&h.jsxs(I,{className:"absolute right-0 mt-2 w-80 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg z-50",children:[h.jsx(I,{className:"px-4 py-3 border-b border-gray-200 dark:border-gray-700",children:h.jsx(Q,{className:"font-semibold text-gray-900 dark:text-white",children:Vs("common.notifications")})}),h.jsx(I,{className:"max-h-96 overflow-y-auto",children:t.length===0?h.jsx(I,{className:"px-4 py-8 text-center text-gray-500 dark:text-gray-400",children:h.jsx(Q,{children:Vs("common.no_notifications")})}):t.map(g=>h.jsx(ae,{onClick:()=>{g.onClick?.(),n?.(g.id),a(!1)},className:` + `,children:[h.jsxs(I,{ref:d,className:"relative",children:[h.jsxs(oe,{onClick:()=>a(!i),className:"relative p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-full transition-colors","aria-label":Ms("common.notifications"),children:[h.jsx(re,{name:de.Bell,className:"w-5 h-5 text-gray-600 dark:text-gray-400"}),f>0&&h.jsx(Q,{className:"absolute top-0 right-0 flex items-center justify-center w-5 h-5 text-xs font-bold text-white bg-red-500 rounded-full",children:f>9?"9+":f})]}),i&&h.jsxs(I,{className:"absolute right-0 mt-2 w-80 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg z-50",children:[h.jsx(I,{className:"px-4 py-3 border-b border-gray-200 dark:border-gray-700",children:h.jsx(Q,{className:"font-semibold text-gray-900 dark:text-white",children:Ms("common.notifications")})}),h.jsx(I,{className:"max-h-96 overflow-y-auto",children:e.length===0?h.jsx(I,{className:"px-4 py-8 text-center text-gray-500 dark:text-gray-400",children:h.jsx(Q,{children:Ms("common.no_notifications")})}):e.map(g=>h.jsx(oe,{onClick:()=>{g.onClick?.(),n?.(g.id),a(!1)},className:` w-full px-4 py-3 text-left hover:bg-gray-50 dark:hover:bg-gray-700 border-b border-gray-100 dark:border-gray-700 transition-colors ${g.read?"":"bg-blue-50 dark:bg-blue-900/20"} - `,children:h.jsxs(I,{className:"flex items-start gap-3",children:[g.iconName&&h.jsx(oe,{name:g.iconName,className:"w-5 h-5 text-gray-600 dark:text-gray-400 mt-0.5"}),h.jsxs(I,{className:"flex-1",children:[h.jsx(I,{className:"font-semibold text-gray-900 dark:text-white text-sm",children:g.title}),h.jsx(I,{className:"text-gray-600 dark:text-gray-400 text-sm mt-1",children:g.message}),h.jsx(I,{className:"text-gray-400 dark:text-gray-500 text-xs mt-1",children:g.time})]}),!g.read&&h.jsx(Q,{className:"w-2 h-2 bg-blue-500 rounded-full mt-1.5"})]})},g.id))})]})]}),h.jsxs(I,{ref:u,className:"relative",children:[h.jsxs(ae,{onClick:()=>c(!l),className:"flex items-center gap-3 px-3 py-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors",children:[e.avatar?h.jsx(_n,{src:e.avatar,alt:e.name,className:"w-8 h-8 rounded-full object-cover"}):h.jsx(I,{className:"w-8 h-8 rounded-full bg-gray-300 dark:bg-gray-600 flex items-center justify-center",children:h.jsx(oe,{name:de.User,className:"w-5 h-5 text-gray-600 dark:text-gray-400"})}),h.jsxs(I,{className:"hidden md:block text-left",children:[h.jsx(I,{className:"font-semibold text-gray-900 dark:text-white text-sm",children:e.name}),e.role&&h.jsx(I,{className:"text-gray-500 dark:text-gray-400 text-xs",children:e.role})]}),h.jsx(oe,{name:de.ChevronDown,className:"w-4 h-4 text-gray-600 dark:text-gray-400"})]}),l&&h.jsxs(I,{className:"absolute right-0 mt-2 w-56 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg z-50",children:[h.jsxs(I,{className:"px-4 py-3 border-b border-gray-200 dark:border-gray-700",children:[h.jsx(I,{className:"font-semibold text-gray-900 dark:text-white",children:e.name}),h.jsx(I,{className:"text-gray-500 dark:text-gray-400 text-sm",children:e.email})]}),h.jsxs(I,{className:"py-2",children:[h.jsxs(ae,{onClick:()=>{r?.(),c(!1)},className:"w-full px-4 py-2 text-left hover:bg-gray-50 dark:hover:bg-gray-700 flex items-center gap-3 transition-colors text-gray-900 dark:text-white",children:[h.jsx(oe,{name:de.User,className:"w-5 h-5 text-gray-600 dark:text-gray-400"}),h.jsx(Q,{children:Vs("common.profile_settings")})]}),h.jsxs(ae,{onClick:()=>{s?.(),c(!1)},className:"w-full px-4 py-2 text-left hover:bg-gray-50 dark:hover:bg-gray-700 flex items-center gap-3 text-red-600 dark:text-red-400 transition-colors",children:[h.jsx(oe,{name:de.ArrowRight,className:"w-5 h-5"}),h.jsx(Q,{children:Vs("common.logout")})]})]})]})]})]})},Gy=({notifications:e=[],unreadCount:t=0,hasMore:n=!1,loading:r=!1,titleText:s="Notifications",emptyText:o="No notifications",markAllReadText:i="Mark all as read",deleteAllText:a="Delete all",unreadOnlyText:l="Unread only",unreadOnly:c=!1,onNotificationClick:d,onClose:u,onLoadMore:f,onMarkAllRead:g,onDeleteAll:m,onDelete:p,onUnreadOnlyToggle:y,dropdownAlign:b="right",className:_="",id:x,editorAttrs:M})=>{const[C,k]=v.useState(!1),N=v.useRef(null),S=v.useRef(null),w=v.useRef(null),E=v.useRef(null),D=v.useRef(new Set),P=v.useRef(f),L=v.useRef(n),$=v.useRef(r),A=v.useRef(e.length),O=v.useRef(-1);v.useEffect(()=>{P.current=f,L.current=n,$.current=r,A.current=e.length}),v.useEffect(()=>{if(!E.current||!w.current||!C)return;O.current=-1;const j=new IntersectionObserver(([G])=>{G.isIntersecting&&($.current||!L.current||O.current!==A.current&&(O.current=A.current,P.current?.()))},{root:w.current,threshold:.1});return j.observe(E.current),()=>j.disconnect()},[C]),v.useEffect(()=>{if(!w.current||!C)return;const j=new IntersectionObserver(U=>{U.forEach(W=>{const Y=W.target.getAttribute("data-notification-id"),V=W.target.getAttribute("data-unread")==="true";Y&&V&&W.isIntersecting&&D.current.add(Y)})},{root:w.current,threshold:.5});return w.current.querySelectorAll("[data-notification-id]").forEach(U=>j.observe(U)),()=>j.disconnect()},[C,e]);const T=t>0?t:e.filter(j=>!j.read).length,z=v.useCallback(()=>{if(C){if(T>0){const j=Array.from(D.current).filter(G=>{const U=e.find(W=>String(W.id)===String(G));return U&&!U.read});j.length>0&&u?.(j)}D.current.clear(),k(!1)}},[C,u,T,e]);v.useEffect(()=>{if(!C)return;const j=G=>{N.current&&!N.current.contains(G.target)&&z()};return document.addEventListener("mousedown",j),()=>document.removeEventListener("mousedown",j)},[C,z]);const H=v.useCallback(()=>{C?z():k(!0)},[C,z]);return v.useLayoutEffect(()=>{const j=S.current;if(!C||!j)return;j.style.transform="";const G=j.getBoundingClientRect(),U=typeof window<"u"?window.innerWidth:0;if(G.width===0&&G.height===0)return;const W=8;let Y=0;G.right>U-W?Y=-(G.right-(U-W)):G.left0&&h.jsx(Q,{className:"absolute top-0 right-0 flex items-center justify-center w-5 h-5 text-xs font-bold text-white bg-red-500 dark:bg-red-600 rounded-full",children:T>99?"99+":T})]}),C&&h.jsxs(I,{ref:S,className:`absolute ${b==="left"?"left-0":"right-0"} mt-2 w-80 max-w-[calc(100vw-1rem)] bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg z-50`,children:[h.jsxs(I,{className:"px-4 py-3 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between gap-2",children:[h.jsxs(I,{className:"flex items-center gap-3 min-w-0",children:[h.jsx(Q,{className:"font-semibold text-gray-900 dark:text-white",children:s}),y&&h.jsxs(I,{className:"flex items-center gap-1.5 cursor-pointer select-none",onClick:()=>y(!c),children:[h.jsx(St,{type:"checkbox",checked:c,onChange:j=>{j.stopPropagation(),y(j.target.checked)},onClick:j=>j.stopPropagation(),className:"w-3.5 h-3.5 rounded border-gray-300 dark:border-gray-600 text-blue-600 dark:text-blue-400 focus:ring-blue-500 dark:focus:ring-blue-400 cursor-pointer"}),h.jsx(Q,{className:"text-xs text-gray-600 dark:text-gray-400",children:l})]})]}),h.jsxs(I,{className:"flex items-center gap-3 flex-shrink-0",children:[g&&T>0&&h.jsx(ae,{onClick:()=>{g(),D.current.clear()},className:"text-xs text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300 hover:underline cursor-pointer",children:i}),m&&e.length>0&&h.jsx(ae,{onClick:()=>{m()},className:"text-xs text-red-600 dark:text-red-400 hover:text-red-800 dark:hover:text-red-300 hover:underline cursor-pointer",children:a})]})]}),h.jsx(I,{ref:w,className:"max-h-96 overflow-y-auto",children:e.length===0&&!r?h.jsx(I,{className:"px-4 py-8 text-center text-gray-500 dark:text-gray-400",children:h.jsx(Q,{children:o})}):h.jsxs(h.Fragment,{children:[e.map(j=>{const G=!j.read;return h.jsxs(I,{"data-notification-id":j.id,"data-unread":G?"true":"false",onClick:()=>{j.onClick?.(),d?.(j),k(!1)},className:`group flex items-start gap-3 px-4 py-3 text-left border-b border-gray-100 dark:border-gray-700 transition-colors cursor-pointer ${G?"bg-blue-50/70 dark:bg-blue-900/20 hover:bg-blue-50 dark:hover:bg-blue-900/30":"bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700/50 opacity-70"}`,children:[j.iconName&&h.jsx(oe,{name:j.iconName,className:`w-5 h-5 mt-0.5 flex-shrink-0 ${G?"text-blue-600 dark:text-blue-400":"text-gray-400 dark:text-gray-500"}`}),h.jsxs(I,{className:"flex-1 min-w-0",children:[h.jsx(I,{className:`text-sm truncate ${G?"font-semibold text-gray-900 dark:text-white":"font-normal text-gray-600 dark:text-gray-400"}`,children:j.title}),h.jsx(I,{className:`text-sm mt-1 line-clamp-2 ${G?"text-gray-700 dark:text-gray-300":"text-gray-500 dark:text-gray-500"}`,children:j.message}),h.jsx(I,{className:"text-gray-400 dark:text-gray-500 text-xs mt-1 font-mono",children:j.time})]}),h.jsxs(I,{className:"flex flex-col items-center gap-2 flex-shrink-0",children:[G&&h.jsx(Q,{className:"w-2 h-2 bg-blue-500 dark:bg-blue-400 rounded-full mt-1.5"}),p&&h.jsx(ae,{onClick:U=>{U.stopPropagation(),p(j)},className:"p-1 text-gray-400 dark:text-gray-500 hover:text-red-600 dark:hover:text-red-400 rounded hover:bg-red-50 dark:hover:bg-red-900/20 cursor-pointer opacity-0 group-hover:opacity-100 transition-opacity","aria-label":"Delete notification",children:h.jsx(oe,{name:de.Trash,className:"w-4 h-4"})})]})]},j.id)}),n&&h.jsx(I,{ref:E,className:"px-4 py-3 text-center",children:r?h.jsx(Q,{className:"text-sm text-gray-400 dark:text-gray-500",children:"..."}):h.jsx(Q,{className:"text-sm text-gray-400 dark:text-gray-500"})}),r&&!n&&e.length>0&&h.jsx(I,{className:"px-4 py-2 text-center",children:h.jsx(Q,{className:"text-sm text-gray-400 dark:text-gray-500",children:"..."})})]})})]})]})},vu=window.G7Core?.createLogger?.("Comp:UserProfile")??{log:(...e)=>console.log("[Comp:UserProfile]",...e),warn:(...e)=>console.warn("[Comp:UserProfile]",...e),error:(...e)=>console.error("[Comp:UserProfile]",...e)},Yy=({user:e={name:"",email:""},profileText:t="Profile Settings",logoutText:n="Logout",onProfileClick:r,onLogoutClick:s,className:o="",logoutEndpoint:i="/api/admin/auth/logout",redirectPath:a="/admin/login",showChevron:l=!0,dropdownDirection:c="up",dropdownOnly:d=!1,id:u,editorAttrs:f})=>{const[g,m]=v.useState(!1),[p,y]=v.useState(!1),b=v.useRef(null);v.useEffect(()=>{const C=k=>{b.current&&!b.current.contains(k.target)&&m(!1)};return document.addEventListener("mousedown",C),()=>document.removeEventListener("mousedown",C)},[]);const _=()=>{const k=`; ${document.cookie}`.split("; XSRF-TOKEN=");return k.length===2?decodeURIComponent(k.pop()?.split(";").shift()||""):null},x=()=>localStorage.getItem("auth_token"),M=async()=>{if(!p)try{if(y(!0),m(!1),s){s();return}const C=_(),k=x(),N=await fetch(i,{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json",...C&&{"X-XSRF-TOKEN":C},...k&&{Authorization:`Bearer ${k}`}},credentials:"include"});N.ok?(localStorage.removeItem("auth_token"),window.location.href=a):(vu.error("로그아웃 실패:",N.statusText),localStorage.removeItem("auth_token"),window.location.href=a)}catch(C){vu.error("로그아웃 오류:",C),localStorage.removeItem("auth_token"),window.location.href=a}finally{y(!1)}};return h.jsxs(I,{ref:b,className:d?`absolute inset-0 ${o}`:`relative ${o}`,id:u,...f,children:[h.jsx(ae,{onClick:()=>m(!g),className:d?"w-full h-full opacity-0 cursor-pointer":"flex items-center gap-2 px-3 py-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors w-full",children:!d&&h.jsxs(h.Fragment,{children:[e.avatar?h.jsx(_n,{src:e.avatar,alt:e.name,className:"w-8 h-8 rounded-full object-cover"}):h.jsx(I,{className:"w-8 h-8 rounded-full bg-gray-300 dark:bg-gray-600 flex items-center justify-center",children:h.jsx(oe,{name:de.User,className:"w-5 h-5 text-gray-600 dark:text-gray-400"})}),h.jsxs(I,{className:"flex-1 text-left",children:[h.jsx(I,{className:"font-semibold text-gray-900 dark:text-white text-sm",children:e.name}),e.role&&h.jsx(I,{className:"text-gray-500 dark:text-gray-400 text-xs",children:e.role})]}),l&&h.jsx(oe,{name:de.ChevronDown,className:"w-4 h-4 text-gray-600 dark:text-gray-400"})]})}),g&&h.jsxs(I,{className:`absolute ${c==="down"?"top-full mt-2":"bottom-full mb-2"} ${d?"right-0 w-72":"left-0 w-full"} bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg z-50`,children:[h.jsxs(I,{className:"px-4 py-3 border-b border-gray-200 dark:border-gray-700 cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors",onClick:()=>{m(!1),window.G7Core?.dispatch({handler:"navigate",params:{path:`/admin/users/${e.uuid}`}})},children:[h.jsx(I,{className:"font-semibold text-gray-900 dark:text-white",children:e.name}),h.jsx(I,{className:"text-gray-500 dark:text-gray-400 text-sm",children:e.email})]}),h.jsxs(I,{className:"py-2",children:[h.jsxs(ae,{onClick:()=>{m(!1),r?r():window.G7Core?.dispatch({handler:"navigate",params:{path:`/admin/users/${e.uuid}/edit`}})},className:"w-full px-4 py-2 text-left hover:bg-gray-50 dark:hover:bg-gray-700 flex items-center justify-start gap-3 transition-colors",children:[h.jsx(oe,{name:de.User,className:"w-5 h-5 text-gray-600 dark:text-gray-400"}),h.jsx(Q,{className:"text-gray-900 dark:text-white",children:t})]}),h.jsxs(ae,{onClick:M,disabled:p,className:"w-full px-4 py-2 text-left hover:bg-gray-50 dark:hover:bg-gray-700 flex items-center justify-start gap-3 text-red-600 dark:text-red-400 transition-colors disabled:opacity-50 disabled:cursor-not-allowed",children:[h.jsx(oe,{name:de.ArrowRight,className:"w-5 h-5"}),h.jsx(Q,{children:p?"Logging out...":n})]})]})]})]})},Jo=window.G7Core?.createLogger?.("Comp:LanguageSelector")??{log:(...e)=>console.log("[Comp:LanguageSelector]",...e),warn:(...e)=>console.warn("[Comp:LanguageSelector]",...e),error:(...e)=>console.error("[Comp:LanguageSelector]",...e)},xu=e=>{const t=window.G7Core?.state?.get?.("_global.appConfig.localeNames")?.[e];return t||{ko:"한국어",en:"English",ja:"日本語",zh:"中文",es:"Español",fr:"Français",de:"Deutsch"}[e]||e},Xy=({availableLocales:e=["ko","en"],languageText:t="Language",apiEndpoint:n="/api/admin/users/me/language",onLanguageChange:r,className:s="",inline:o=!1,showCode:i=!1,id:a,editorAttrs:l})=>{const[c,d]=v.useState(!1),[u,f]=v.useState(!1),[g,m]=v.useState(()=>localStorage.getItem("g7_locale")||"ko"),p=v.useRef(null);v.useEffect(()=>{if(o)return;const x=M=>{p.current&&!p.current.contains(M.target)&&d(!1)};return document.addEventListener("mousedown",x),()=>document.removeEventListener("mousedown",x)},[o]);const y=()=>{const M=`; ${document.cookie}`.split("; XSRF-TOKEN=");return M.length===2?decodeURIComponent(M.pop()?.split(";").shift()||""):null},b=()=>localStorage.getItem("auth_token"),_=async x=>{if(!(u||x===g))try{f(!0);const M=y(),C=b();try{const k=await fetch(n,{method:"PATCH",headers:{"Content-Type":"application/json",Accept:"application/json",...M&&{"X-XSRF-TOKEN":M},...C&&{Authorization:`Bearer ${C}`}},credentials:"include",body:JSON.stringify({language:x})});k.ok||Jo.warn("언어 설정 저장 실패 (UI는 변경됨):",k.statusText)}catch(k){Jo.warn("언어 설정 API 호출 실패 (UI는 변경됨):",k)}m(x),d(!1),r?.(x),window.__templateApp?await window.__templateApp.changeLocale(x):(Jo.warn("TemplateApp not found, reloading page"),window.location.reload())}catch(M){Jo.error("언어 변경 오류:",M)}finally{f(!1)}};return o?h.jsxs(I,{className:`${s}`,id:a,...l,children:[h.jsxs(ae,{onClick:()=>d(!c),disabled:u,className:"w-full px-4 py-2 text-left hover:bg-gray-50 dark:hover:bg-gray-700 flex items-center gap-3 transition-colors",children:[h.jsx(oe,{name:de.Globe,className:"w-5 h-5 text-gray-600 dark:text-gray-400"}),h.jsx(Q,{className:"text-gray-900 dark:text-white flex-1",children:t}),h.jsx(Q,{className:"px-2 py-0.5 text-xs font-medium bg-blue-100 dark:bg-blue-900 text-blue-700 dark:text-blue-300 rounded",children:g.toUpperCase()}),h.jsx(oe,{name:de.ChevronDown,className:`w-4 h-4 text-gray-400 dark:text-gray-500 transition-transform ${c?"rotate-180":""}`})]}),c&&h.jsx(I,{className:"px-4 py-2 flex flex-wrap gap-2",children:e.map(x=>h.jsx(ae,{onClick:()=>_(x),disabled:u,className:` + `,children:h.jsxs(I,{className:"flex items-start gap-3",children:[g.iconName&&h.jsx(re,{name:g.iconName,className:"w-5 h-5 text-gray-600 dark:text-gray-400 mt-0.5"}),h.jsxs(I,{className:"flex-1",children:[h.jsx(I,{className:"font-semibold text-gray-900 dark:text-white text-sm",children:g.title}),h.jsx(I,{className:"text-gray-600 dark:text-gray-400 text-sm mt-1",children:g.message}),h.jsx(I,{className:"text-gray-400 dark:text-gray-500 text-xs mt-1",children:g.time})]}),!g.read&&h.jsx(Q,{className:"w-2 h-2 bg-blue-500 rounded-full mt-1.5"})]})},g.id))})]})]}),h.jsxs(I,{ref:u,className:"relative",children:[h.jsxs(oe,{onClick:()=>c(!l),className:"flex items-center gap-3 px-3 py-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors",children:[t.avatar?h.jsx(yn,{src:t.avatar,alt:t.name,className:"w-8 h-8 rounded-full object-cover"}):h.jsx(I,{className:"w-8 h-8 rounded-full bg-gray-300 dark:bg-gray-600 flex items-center justify-center",children:h.jsx(re,{name:de.User,className:"w-5 h-5 text-gray-600 dark:text-gray-400"})}),h.jsxs(I,{className:"hidden md:block text-left",children:[h.jsx(I,{className:"font-semibold text-gray-900 dark:text-white text-sm",children:t.name}),t.role&&h.jsx(I,{className:"text-gray-500 dark:text-gray-400 text-xs",children:t.role})]}),h.jsx(re,{name:de.ChevronDown,className:"w-4 h-4 text-gray-600 dark:text-gray-400"})]}),l&&h.jsxs(I,{className:"absolute right-0 mt-2 w-56 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg z-50",children:[h.jsxs(I,{className:"px-4 py-3 border-b border-gray-200 dark:border-gray-700",children:[h.jsx(I,{className:"font-semibold text-gray-900 dark:text-white",children:t.name}),h.jsx(I,{className:"text-gray-500 dark:text-gray-400 text-sm",children:t.email})]}),h.jsxs(I,{className:"py-2",children:[h.jsxs(oe,{onClick:()=>{r?.(),c(!1)},className:"w-full px-4 py-2 text-left hover:bg-gray-50 dark:hover:bg-gray-700 flex items-center gap-3 transition-colors text-gray-900 dark:text-white",children:[h.jsx(re,{name:de.User,className:"w-5 h-5 text-gray-600 dark:text-gray-400"}),h.jsx(Q,{children:Ms("common.profile_settings")})]}),h.jsxs(oe,{onClick:()=>{s?.(),c(!1)},className:"w-full px-4 py-2 text-left hover:bg-gray-50 dark:hover:bg-gray-700 flex items-center gap-3 text-red-600 dark:text-red-400 transition-colors",children:[h.jsx(re,{name:de.ArrowRight,className:"w-5 h-5"}),h.jsx(Q,{children:Ms("common.logout")})]})]})]})]})]})},Sy=({notifications:t=[],unreadCount:e=0,hasMore:n=!1,loading:r=!1,titleText:s="Notifications",emptyText:o="No notifications",markAllReadText:i="Mark all as read",deleteAllText:a="Delete all",unreadOnlyText:l="Unread only",unreadOnly:c=!1,onNotificationClick:d,onClose:u,onLoadMore:f,onMarkAllRead:g,onDeleteAll:m,onDelete:p,onUnreadOnlyToggle:y,dropdownAlign:b="right",className:_="",id:x,editorAttrs:E})=>{const[C,k]=v.useState(!1),N=v.useRef(null),S=v.useRef(null),w=v.useRef(null),M=v.useRef(null),T=v.useRef(new Set),P=v.useRef(f),L=v.useRef(n),$=v.useRef(r),A=v.useRef(t.length),D=v.useRef(-1);v.useEffect(()=>{P.current=f,L.current=n,$.current=r,A.current=t.length}),v.useEffect(()=>{if(!M.current||!w.current||!C)return;D.current=-1;const j=new IntersectionObserver(([W])=>{W.isIntersecting&&($.current||!L.current||D.current!==A.current&&(D.current=A.current,P.current?.()))},{root:w.current,threshold:.1});return j.observe(M.current),()=>j.disconnect()},[C]),v.useEffect(()=>{if(!w.current||!C)return;const j=new IntersectionObserver(U=>{U.forEach(B=>{const G=B.target.getAttribute("data-notification-id"),V=B.target.getAttribute("data-unread")==="true";G&&V&&B.isIntersecting&&T.current.add(G)})},{root:w.current,threshold:.5});return w.current.querySelectorAll("[data-notification-id]").forEach(U=>j.observe(U)),()=>j.disconnect()},[C,t]);const O=e>0?e:t.filter(j=>!j.read).length,z=v.useCallback(()=>{if(C){if(O>0){const j=Array.from(T.current).filter(W=>{const U=t.find(B=>String(B.id)===String(W));return U&&!U.read});j.length>0&&u?.(j)}T.current.clear(),k(!1)}},[C,u,O,t]);v.useEffect(()=>{if(!C)return;const j=W=>{N.current&&!N.current.contains(W.target)&&z()};return document.addEventListener("mousedown",j),()=>document.removeEventListener("mousedown",j)},[C,z]);const H=v.useCallback(()=>{C?z():k(!0)},[C,z]);return v.useLayoutEffect(()=>{const j=S.current;if(!C||!j)return;j.style.transform="";const W=j.getBoundingClientRect(),U=typeof window<"u"?window.innerWidth:0;if(W.width===0&&W.height===0)return;const B=8;let G=0;W.right>U-B?G=-(W.right-(U-B)):W.left0&&h.jsx(Q,{className:"absolute top-0 right-0 flex items-center justify-center w-5 h-5 text-xs font-bold text-white bg-red-500 dark:bg-red-600 rounded-full",children:O>99?"99+":O})]}),C&&h.jsxs(I,{ref:S,className:`absolute ${b==="left"?"left-0":"right-0"} mt-2 w-80 max-w-[calc(100vw-1rem)] bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg z-50`,children:[h.jsxs(I,{className:"px-4 py-3 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between gap-2",children:[h.jsxs(I,{className:"flex items-center gap-3 min-w-0",children:[h.jsx(Q,{className:"font-semibold text-gray-900 dark:text-white",children:s}),y&&h.jsxs(I,{className:"flex items-center gap-1.5 cursor-pointer select-none",onClick:()=>y(!c),children:[h.jsx(kt,{type:"checkbox",checked:c,onChange:j=>{j.stopPropagation(),y(j.target.checked)},onClick:j=>j.stopPropagation(),className:"w-3.5 h-3.5 rounded border-gray-300 dark:border-gray-600 text-blue-600 dark:text-blue-400 focus:ring-blue-500 dark:focus:ring-blue-400 cursor-pointer"}),h.jsx(Q,{className:"text-xs text-gray-600 dark:text-gray-400",children:l})]})]}),h.jsxs(I,{className:"flex items-center gap-3 flex-shrink-0",children:[g&&O>0&&h.jsx(oe,{onClick:()=>{g(),T.current.clear()},className:"text-xs text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300 hover:underline cursor-pointer",children:i}),m&&t.length>0&&h.jsx(oe,{onClick:()=>{m()},className:"text-xs text-red-600 dark:text-red-400 hover:text-red-800 dark:hover:text-red-300 hover:underline cursor-pointer",children:a})]})]}),h.jsx(I,{ref:w,className:"max-h-96 overflow-y-auto",children:t.length===0&&!r?h.jsx(I,{className:"px-4 py-8 text-center text-gray-500 dark:text-gray-400",children:h.jsx(Q,{children:o})}):h.jsxs(h.Fragment,{children:[t.map(j=>{const W=!j.read;return h.jsxs(I,{"data-notification-id":j.id,"data-unread":W?"true":"false",onClick:()=>{j.onClick?.(),d?.(j),k(!1)},className:`group flex items-start gap-3 px-4 py-3 text-left border-b border-gray-100 dark:border-gray-700 transition-colors cursor-pointer ${W?"bg-blue-50/70 dark:bg-blue-900/20 hover:bg-blue-50 dark:hover:bg-blue-900/30":"bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700/50 opacity-70"}`,children:[j.iconName&&h.jsx(re,{name:j.iconName,className:`w-5 h-5 mt-0.5 flex-shrink-0 ${W?"text-blue-600 dark:text-blue-400":"text-gray-400 dark:text-gray-500"}`}),h.jsxs(I,{className:"flex-1 min-w-0",children:[h.jsx(I,{className:`text-sm truncate ${W?"font-semibold text-gray-900 dark:text-white":"font-normal text-gray-600 dark:text-gray-400"}`,children:j.title}),h.jsx(I,{className:`text-sm mt-1 line-clamp-2 ${W?"text-gray-700 dark:text-gray-300":"text-gray-500 dark:text-gray-500"}`,children:j.message}),h.jsx(I,{className:"text-gray-400 dark:text-gray-500 text-xs mt-1 font-mono",children:j.time})]}),h.jsxs(I,{className:"flex flex-col items-center gap-2 flex-shrink-0",children:[W&&h.jsx(Q,{className:"w-2 h-2 bg-blue-500 dark:bg-blue-400 rounded-full mt-1.5"}),p&&h.jsx(oe,{onClick:U=>{U.stopPropagation(),p(j)},className:"p-1 text-gray-400 dark:text-gray-500 hover:text-red-600 dark:hover:text-red-400 rounded hover:bg-red-50 dark:hover:bg-red-900/20 cursor-pointer opacity-0 group-hover:opacity-100 transition-opacity","aria-label":"Delete notification",children:h.jsx(re,{name:de.Trash,className:"w-4 h-4"})})]})]},j.id)}),n&&h.jsx(I,{ref:M,className:"px-4 py-3 text-center",children:r?h.jsx(Q,{className:"text-sm text-gray-400 dark:text-gray-500",children:"..."}):h.jsx(Q,{className:"text-sm text-gray-400 dark:text-gray-500"})}),r&&!n&&t.length>0&&h.jsx(I,{className:"px-4 py-2 text-center",children:h.jsx(Q,{className:"text-sm text-gray-400 dark:text-gray-500",children:"..."})})]})})]})]})},au=window.G7Core?.createLogger?.("Comp:UserProfile")??{log:(...t)=>console.log("[Comp:UserProfile]",...t),warn:(...t)=>console.warn("[Comp:UserProfile]",...t),error:(...t)=>console.error("[Comp:UserProfile]",...t)},_y=({user:t={name:"",email:""},profileText:e="Profile Settings",logoutText:n="Logout",onProfileClick:r,onLogoutClick:s,className:o="",logoutEndpoint:i="/api/admin/auth/logout",redirectPath:a="/admin/login",showChevron:l=!0,dropdownDirection:c="up",dropdownOnly:d=!1,id:u,editorAttrs:f})=>{const[g,m]=v.useState(!1),[p,y]=v.useState(!1),b=v.useRef(null);v.useEffect(()=>{const C=k=>{b.current&&!b.current.contains(k.target)&&m(!1)};return document.addEventListener("mousedown",C),()=>document.removeEventListener("mousedown",C)},[]);const _=()=>{const k=`; ${document.cookie}`.split("; XSRF-TOKEN=");return k.length===2?decodeURIComponent(k.pop()?.split(";").shift()||""):null},x=()=>localStorage.getItem("auth_token"),E=async()=>{if(!p)try{if(y(!0),m(!1),s){s();return}const C=_(),k=x(),N=await fetch(i,{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json",...C&&{"X-XSRF-TOKEN":C},...k&&{Authorization:`Bearer ${k}`}},credentials:"include"});N.ok?(localStorage.removeItem("auth_token"),window.location.href=a):(au.error("로그아웃 실패:",N.statusText),localStorage.removeItem("auth_token"),window.location.href=a)}catch(C){au.error("로그아웃 오류:",C),localStorage.removeItem("auth_token"),window.location.href=a}finally{y(!1)}};return h.jsxs(I,{ref:b,className:d?`absolute inset-0 ${o}`:`relative ${o}`,id:u,...f,children:[h.jsx(oe,{onClick:()=>m(!g),className:d?"w-full h-full opacity-0 cursor-pointer":"flex items-center gap-2 px-3 py-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors w-full",children:!d&&h.jsxs(h.Fragment,{children:[t.avatar?h.jsx(yn,{src:t.avatar,alt:t.name,className:"w-8 h-8 rounded-full object-cover"}):h.jsx(I,{className:"w-8 h-8 rounded-full bg-gray-300 dark:bg-gray-600 flex items-center justify-center",children:h.jsx(re,{name:de.User,className:"w-5 h-5 text-gray-600 dark:text-gray-400"})}),h.jsxs(I,{className:"flex-1 text-left",children:[h.jsx(I,{className:"font-semibold text-gray-900 dark:text-white text-sm",children:t.name}),t.role&&h.jsx(I,{className:"text-gray-500 dark:text-gray-400 text-xs",children:t.role})]}),l&&h.jsx(re,{name:de.ChevronDown,className:"w-4 h-4 text-gray-600 dark:text-gray-400"})]})}),g&&h.jsxs(I,{className:`absolute ${c==="down"?"top-full mt-2":"bottom-full mb-2"} ${d?"right-0 w-72":"left-0 w-full"} bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg z-50`,children:[h.jsxs(I,{className:"px-4 py-3 border-b border-gray-200 dark:border-gray-700 cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors",onClick:()=>{m(!1),window.G7Core?.dispatch({handler:"navigate",params:{path:`/admin/users/${t.uuid}`}})},children:[h.jsx(I,{className:"font-semibold text-gray-900 dark:text-white",children:t.name}),h.jsx(I,{className:"text-gray-500 dark:text-gray-400 text-sm",children:t.email})]}),h.jsxs(I,{className:"py-2",children:[h.jsxs(oe,{onClick:()=>{m(!1),r?r():window.G7Core?.dispatch({handler:"navigate",params:{path:`/admin/users/${t.uuid}/edit`}})},className:"w-full px-4 py-2 text-left hover:bg-gray-50 dark:hover:bg-gray-700 flex items-center justify-start gap-3 transition-colors",children:[h.jsx(re,{name:de.User,className:"w-5 h-5 text-gray-600 dark:text-gray-400"}),h.jsx(Q,{className:"text-gray-900 dark:text-white",children:e})]}),h.jsxs(oe,{onClick:E,disabled:p,className:"w-full px-4 py-2 text-left hover:bg-gray-50 dark:hover:bg-gray-700 flex items-center justify-start gap-3 text-red-600 dark:text-red-400 transition-colors disabled:opacity-50 disabled:cursor-not-allowed",children:[h.jsx(re,{name:de.ArrowRight,className:"w-5 h-5"}),h.jsx(Q,{children:p?"Logging out...":n})]})]})]})]})},jo=window.G7Core?.createLogger?.("Comp:LanguageSelector")??{log:(...t)=>console.log("[Comp:LanguageSelector]",...t),warn:(...t)=>console.warn("[Comp:LanguageSelector]",...t),error:(...t)=>console.error("[Comp:LanguageSelector]",...t)},lu=t=>{const e=window.G7Core?.state?.get?.("_global.appConfig.localeNames")?.[t];return e||{ko:"한국어",en:"English",ja:"日本語",zh:"中文",es:"Español",fr:"Français",de:"Deutsch"}[t]||t},Ey=({availableLocales:t=["ko","en"],languageText:e="Language",apiEndpoint:n="/api/admin/users/me/language",onLanguageChange:r,className:s="",inline:o=!1,showCode:i=!1,id:a,editorAttrs:l})=>{const[c,d]=v.useState(!1),[u,f]=v.useState(!1),[g,m]=v.useState(()=>localStorage.getItem("g7_locale")||"ko"),p=v.useRef(null);v.useEffect(()=>{if(o)return;const x=E=>{p.current&&!p.current.contains(E.target)&&d(!1)};return document.addEventListener("mousedown",x),()=>document.removeEventListener("mousedown",x)},[o]);const y=()=>{const E=`; ${document.cookie}`.split("; XSRF-TOKEN=");return E.length===2?decodeURIComponent(E.pop()?.split(";").shift()||""):null},b=()=>localStorage.getItem("auth_token"),_=async x=>{if(!(u||x===g))try{f(!0);const E=y(),C=b();try{const k=await fetch(n,{method:"PATCH",headers:{"Content-Type":"application/json",Accept:"application/json",...E&&{"X-XSRF-TOKEN":E},...C&&{Authorization:`Bearer ${C}`}},credentials:"include",body:JSON.stringify({language:x})});k.ok||jo.warn("언어 설정 저장 실패 (UI는 변경됨):",k.statusText)}catch(k){jo.warn("언어 설정 API 호출 실패 (UI는 변경됨):",k)}m(x),d(!1),r?.(x),window.__templateApp?await window.__templateApp.changeLocale(x):(jo.warn("TemplateApp not found, reloading page"),window.location.reload())}catch(E){jo.error("언어 변경 오류:",E)}finally{f(!1)}};return o?h.jsxs(I,{className:`${s}`,id:a,...l,children:[h.jsxs(oe,{onClick:()=>d(!c),disabled:u,className:"w-full px-4 py-2 text-left hover:bg-gray-50 dark:hover:bg-gray-700 flex items-center gap-3 transition-colors",children:[h.jsx(re,{name:de.Globe,className:"w-5 h-5 text-gray-600 dark:text-gray-400"}),h.jsx(Q,{className:"text-gray-900 dark:text-white flex-1",children:e}),h.jsx(Q,{className:"px-2 py-0.5 text-xs font-medium bg-blue-100 dark:bg-blue-900 text-blue-700 dark:text-blue-300 rounded",children:g.toUpperCase()}),h.jsx(re,{name:de.ChevronDown,className:`w-4 h-4 text-gray-400 dark:text-gray-500 transition-transform ${c?"rotate-180":""}`})]}),c&&h.jsx(I,{className:"px-4 py-2 flex flex-wrap gap-2",children:t.map(x=>h.jsx(oe,{onClick:()=>_(x),disabled:u,className:` px-3 py-1.5 text-sm font-medium rounded-md transition-colors ${g===x?"bg-blue-600 text-white":"bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600"} ${u?"opacity-50 cursor-not-allowed":""} - `,children:xu(x)},x))})]}):h.jsxs(I,{ref:p,className:`relative ${s}`,id:a,...l,children:[h.jsxs(ae,{onClick:()=>d(!c),className:i?"flex items-center gap-1.5 px-2.5 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors cursor-pointer":"p-2 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg transition-colors","aria-haspopup":"listbox","aria-expanded":c,"aria-label":t,children:[h.jsx(oe,{name:de.Globe,className:i?"w-4 h-4":"w-5 h-5 text-gray-600 dark:text-gray-400"}),i&&h.jsx(Q,{className:"font-medium uppercase",children:g}),i&&h.jsx(oe,{name:de.ChevronDown,className:"w-3 h-3"})]}),c&&h.jsx(I,{className:"absolute right-0 mt-2 w-48 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg z-50",children:h.jsx(I,{className:"py-2",children:e.map(x=>h.jsxs(ae,{onClick:()=>_(x),disabled:u,className:` + `,children:lu(x)},x))})]}):h.jsxs(I,{ref:p,className:`relative ${s}`,id:a,...l,children:[h.jsxs(oe,{onClick:()=>d(!c),className:i?"flex items-center gap-1.5 px-2.5 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors cursor-pointer":"p-2 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg transition-colors","aria-haspopup":"listbox","aria-expanded":c,"aria-label":e,children:[h.jsx(re,{name:de.Globe,className:i?"w-4 h-4":"w-5 h-5 text-gray-600 dark:text-gray-400"}),i&&h.jsx(Q,{className:"font-medium uppercase",children:g}),i&&h.jsx(re,{name:de.ChevronDown,className:"w-3 h-3"})]}),c&&h.jsx(I,{className:"absolute right-0 mt-2 w-48 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg z-50",children:h.jsx(I,{className:"py-2",children:t.map(x=>h.jsxs(oe,{onClick:()=>_(x),disabled:u,className:` w-full px-4 py-2 text-left hover:bg-gray-50 dark:hover:bg-gray-700 flex items-center gap-3 transition-colors ${g===x?"bg-gray-50 dark:bg-gray-700":""} ${u?"opacity-50 cursor-not-allowed":""} - `,children:[h.jsx(Q,{className:"text-gray-900 dark:text-white flex-1",children:xu(x)}),g===x&&h.jsx(oe,{name:de.Check,className:"w-4 h-4 text-blue-600 dark:text-blue-400"})]},x))})})]})},Ky=({copyright:e="© 2026 G7",version:t,quickLinks:n=[],className:r="",changelogModalId:s,copyrightModalId:o,id:i,editorAttrs:a})=>{const l=v.useCallback(()=>{s&&window.G7Core?.dispatch?.({handler:"sequence",actions:[{handler:"apiCall",auth_required:!0,target:"/api/admin/changelog",params:{method:"GET"},onSuccess:[{handler:"setState",params:{target:"global",coreChangelogContent:"{{response.data.content}}"}},{handler:"openModal",target:s}],onError:[{handler:"openModal",target:s}]}]})},[s]),c=v.useCallback(()=>{o&&window.G7Core?.dispatch?.({handler:"sequence",actions:[{handler:"apiCall",auth_required:!0,target:"/api/admin/license",params:{method:"GET"},onSuccess:[{handler:"setState",params:{target:"global",coreLicenseContent:"{{response.data.content}}"}},{handler:"openModal",target:o}],onError:[{handler:"openModal",target:o}]}]})},[o]);return h.jsx(I,{className:`admin-footer ${r}`,id:i,...a,children:h.jsxs(I,{className:"admin-footer-row",children:[h.jsxs(I,{className:"admin-footer-group",children:[h.jsx(Q,{className:o?"admin-footer-link-hover":"",onClick:o?c:void 0,children:e}),t&&h.jsxs(h.Fragment,{children:[h.jsx(Q,{className:"hidden md:inline",children:"•"}),h.jsxs(Q,{className:`flex-center gap-2 ${s?"admin-footer-link-hover":""}`,onClick:s?l:void 0,children:[h.jsx(oe,{name:de.Tag,className:"w-4 h-4"}),h.jsxs(Q,{children:["v",t]})]})]})]}),n.length>0&&h.jsx(I,{className:"flex-center gap-4",children:n.map(d=>h.jsxs(Yr,{href:d.url,className:"admin-footer-link",children:[d.iconName&&h.jsx(oe,{name:d.iconName,className:"w-4 h-4"}),h.jsx(Q,{children:d.label})]},d.id))})]})})},qy=({title:e,description:t,breadcrumbItems:n,tabs:r,onTabChange:s,actions:o,className:i="",children:a,id:l,editorAttrs:c})=>{const d=(u="secondary")=>({primary:"page-action-primary",secondary:"page-action-secondary",danger:"page-action-danger",ghost:"page-action-ghost"})[u];return h.jsxs(I,{className:i,id:l,...c,children:[n&&n.length>0&&h.jsx(I,{className:"mb-md",children:h.jsx(bu,{items:n})}),h.jsxs(I,{className:"flex-between mb-lg",children:[h.jsxs(I,{children:[h.jsx(Ba,{className:"page-title",children:e}),t&&h.jsx(vt,{className:"page-description",children:t})]}),a?h.jsx(I,{className:"flex-center page-actions",children:a}):o&&o.length>0?h.jsx(I,{className:"page-actions",children:o.map(u=>h.jsxs(ae,{onClick:u.onClick,disabled:u.disabled,className:d(u.variant),children:[u.iconName&&h.jsx(oe,{name:u.iconName,className:"icon-md"}),u.variant!=="ghost"&&u.label&&h.jsx(I,{children:u.label})]},u.id))}):null]}),r&&r.length>0&&h.jsx(I,{className:"tab-container",children:r.map(u=>h.jsxs(ae,{onClick:()=>s?.(u.value),className:u.active?"tab-btn-active":"tab-btn",children:[h.jsx(I,{children:u.label}),u.badge!==void 0&&h.jsx(I,{className:u.active?"tab-badge-active":"tab-badge",children:u.badge})]},u.id))})]})},Zy=()=>window.G7Core,Qy=({title:e,description:t,className:n="",style:r,children:s,id:o,editorAttrs:i})=>{const a="flex items-center justify-between",l=Zy()?.style?.mergeClasses?.(a,n)??`${a} ${n}`;return h.jsxs(I,{className:l,style:r,id:o,...i,children:[h.jsxs(I,{children:[e&&h.jsx(ar,{className:"text-base font-semibold text-gray-900 dark:text-white",children:e}),t&&h.jsx(vt,{className:"mt-1 text-sm text-gray-500 dark:text-gray-400",children:t})]}),s&&h.jsx(I,{className:"flex items-center gap-2",children:s})]})},cr=(e,t)=>window.G7Core?.t?.(e,t)??e,Jy=({image:e,imageAlt:t,vendor:n,name:r,version:s,status:o,updateAvailable:i=!1,latestVersion:a,dependencies:l=[],showLayoutEditButton:c=!1,actions:d=[],onLayoutEditClick:u,className:f="",id:g,editorAttrs:m})=>{const p=b=>({active:"success",inactive:"default",pending:"pending",error:"error"})[b],y=b=>({active:cr("common.status_active"),inactive:cr("common.status_inactive"),pending:cr("common.status_pending"),error:cr("common.status_error")})[b];return h.jsxs(I,{className:` + `,children:[h.jsx(Q,{className:"text-gray-900 dark:text-white flex-1",children:lu(x)}),g===x&&h.jsx(re,{name:de.Check,className:"w-4 h-4 text-blue-600 dark:text-blue-400"})]},x))})})]})},My=({copyright:t="© 2026 G7",version:e,quickLinks:n=[],className:r="",changelogModalId:s,copyrightModalId:o,id:i,editorAttrs:a})=>{const l=v.useCallback(()=>{s&&window.G7Core?.dispatch?.({handler:"sequence",actions:[{handler:"apiCall",auth_required:!0,target:"/api/admin/changelog",params:{method:"GET"},onSuccess:[{handler:"setState",params:{target:"global",coreChangelogContent:"{{response.data.content}}"}},{handler:"openModal",target:s}],onError:[{handler:"openModal",target:s}]}]})},[s]),c=v.useCallback(()=>{o&&window.G7Core?.dispatch?.({handler:"sequence",actions:[{handler:"apiCall",auth_required:!0,target:"/api/admin/license",params:{method:"GET"},onSuccess:[{handler:"setState",params:{target:"global",coreLicenseContent:"{{response.data.content}}"}},{handler:"openModal",target:o}],onError:[{handler:"openModal",target:o}]}]})},[o]);return h.jsx(I,{className:`admin-footer ${r}`,id:i,...a,children:h.jsxs(I,{className:"admin-footer-row",children:[h.jsxs(I,{className:"admin-footer-group",children:[h.jsx(Q,{className:o?"admin-footer-link-hover":"",onClick:o?c:void 0,children:t}),e&&h.jsxs(h.Fragment,{children:[h.jsx(Q,{className:"hidden md:inline",children:"•"}),h.jsxs(Q,{className:`flex-center gap-2 ${s?"admin-footer-link-hover":""}`,onClick:s?l:void 0,children:[h.jsx(re,{name:de.Tag,className:"w-4 h-4"}),h.jsxs(Q,{children:["v",e]})]})]})]}),n.length>0&&h.jsx(I,{className:"flex-center gap-4",children:n.map(d=>h.jsxs(Lr,{href:d.url,className:"admin-footer-link",children:[d.iconName&&h.jsx(re,{name:d.iconName,className:"w-4 h-4"}),h.jsx(Q,{children:d.label})]},d.id))})]})})},Ny=({title:t,description:e,breadcrumbItems:n,tabs:r,onTabChange:s,actions:o,className:i="",children:a,id:l,editorAttrs:c})=>{const d=(u="secondary")=>({primary:"page-action-primary",secondary:"page-action-secondary",danger:"page-action-danger",ghost:"page-action-ghost"})[u];return h.jsxs(I,{className:i,id:l,...c,children:[n&&n.length>0&&h.jsx(I,{className:"mb-md",children:h.jsx(ou,{items:n})}),h.jsxs(I,{className:"flex-between mb-lg",children:[h.jsxs(I,{children:[h.jsx(Da,{className:"page-title",children:t}),e&&h.jsx(yt,{className:"page-description",children:e})]}),a?h.jsx(I,{className:"flex-center page-actions",children:a}):o&&o.length>0?h.jsx(I,{className:"page-actions",children:o.map(u=>h.jsxs(oe,{onClick:u.onClick,disabled:u.disabled,className:d(u.variant),children:[u.iconName&&h.jsx(re,{name:u.iconName,className:"icon-md"}),u.variant!=="ghost"&&u.label&&h.jsx(I,{children:u.label})]},u.id))}):null]}),r&&r.length>0&&h.jsx(I,{className:"tab-container",children:r.map(u=>h.jsxs(oe,{onClick:()=>s?.(u.value),className:u.active?"tab-btn-active":"tab-btn",children:[h.jsx(I,{children:u.label}),u.badge!==void 0&&h.jsx(I,{className:u.active?"tab-badge-active":"tab-badge",children:u.badge})]},u.id))})]})},Oy=()=>window.G7Core,Ty=({title:t,description:e,className:n="",style:r,children:s,id:o,editorAttrs:i})=>{const a="flex items-center justify-between",l=Oy()?.style?.mergeClasses?.(a,n)??`${a} ${n}`;return h.jsxs(I,{className:l,style:r,id:o,...i,children:[h.jsxs(I,{children:[t&&h.jsx(Jn,{className:"text-base font-semibold text-gray-900 dark:text-white",children:t}),e&&h.jsx(yt,{className:"mt-1 text-sm text-gray-500 dark:text-gray-400",children:e})]}),s&&h.jsx(I,{className:"flex items-center gap-2",children:s})]})},er=(t,e)=>window.G7Core?.t?.(t,e)??t,Ay=({image:t,imageAlt:e,vendor:n,name:r,version:s,status:o,updateAvailable:i=!1,latestVersion:a,dependencies:l=[],showLayoutEditButton:c=!1,actions:d=[],onLayoutEditClick:u,className:f="",id:g,editorAttrs:m})=>{const p=b=>({active:"success",inactive:"default",pending:"pending",error:"error"})[b],y=b=>({active:er("common.status_active"),inactive:er("common.status_inactive"),pending:er("common.status_pending"),error:er("common.status_error")})[b];return h.jsxs(I,{className:` bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden hover:shadow-lg transition-shadow ${f} - `,id:g,...m,children:[e&&h.jsxs(I,{className:"relative",children:[h.jsx(_n,{src:e,alt:t||cr("common.template_preview",{vendor:n,name:r}),className:"w-full h-48 object-cover"}),h.jsx(I,{className:"absolute top-3 right-3",children:h.jsx(Ko,{status:p(o),label:y(o),showIcon:!0})}),i&&h.jsx(I,{className:"absolute top-3 left-3",children:h.jsxs(Q,{className:"flex items-center gap-1 px-2 py-1 bg-yellow-50 dark:bg-yellow-900/20 text-yellow-700 dark:text-yellow-400 border border-yellow-200 dark:border-yellow-800 rounded-md text-xs font-semibold",children:[h.jsx(oe,{name:de.ExclamationTriangle,className:"w-3 h-3"}),h.jsx(Q,{children:cr("common.update_available")})]})})]}),h.jsxs(I,{className:"p-4",children:[h.jsxs(I,{className:"flex items-start justify-between mb-3",children:[h.jsxs(I,{className:"flex-1",children:[h.jsxs(Gr,{className:"text-lg font-bold text-gray-900 dark:text-white",children:[n,"/",r]}),h.jsxs(I,{className:"flex items-center gap-2 mt-1 text-sm text-gray-600 dark:text-gray-400",children:[h.jsxs(Q,{children:["v",s]}),i&&a&&h.jsxs(h.Fragment,{children:[h.jsx(Q,{children:"→"}),h.jsxs(Q,{className:"text-yellow-600 dark:text-yellow-400 font-semibold",children:["v",a]})]})]}),!e&&h.jsx(I,{className:"mt-2",children:h.jsx(Ko,{status:p(o),label:y(o),showIcon:!0})})]}),d.length>0&&h.jsx(Ps,{items:d,triggerIconName:de.EllipsisVertical,position:"left"})]}),l.length>0&&h.jsxs(I,{className:"mb-3",children:[h.jsxs(vt,{className:"text-xs text-gray-500 dark:text-gray-400 mb-1",children:[cr("common.dependencies"),":"]}),h.jsx(I,{className:"flex flex-wrap gap-1",children:l.map((b,_)=>h.jsx(Q,{className:"px-2 py-0.5 bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 text-xs rounded",children:b},_))})]}),c&&u&&h.jsxs(ae,{onClick:u,className:"w-full px-4 py-2 bg-blue-600 dark:bg-blue-500 text-white rounded-md hover:bg-blue-700 dark:hover:bg-blue-600 transition-colors flex items-center justify-center gap-2 font-medium",children:[h.jsx(oe,{name:de.Edit,className:"w-4 h-4"}),h.jsx(Q,{children:cr("common.edit_layout")})]})]})]})},wu=({type:e,message:t,dismissible:n=!1,onDismiss:r,className:s="",id:o,editorAttrs:i})=>{const a={info:{containerClass:"alert-info",iconClass:"alert-icon-info",textClass:"alert-text-info",icon:de.InfoCircle},success:{containerClass:"alert-success",iconClass:"alert-icon-success",textClass:"alert-text-success",icon:de.CheckCircle},warning:{containerClass:"alert-warning",iconClass:"alert-icon-warning",textClass:"alert-text-warning",icon:de.ExclamationTriangle},error:{containerClass:"alert-error",iconClass:"alert-icon-error",textClass:"alert-text-error",icon:de.TimesCircle}},l=a[e]??a.info;return h.jsxs(I,{className:`${l.containerClass} ${s}`,role:"alert","aria-live":"polite",id:o,...i,children:[h.jsx(oe,{name:l.icon,className:l.iconClass,ariaLabel:`${e} icon`}),h.jsx(vt,{className:l.textClass,children:t}),n&&h.jsx(ae,{onClick:r,className:`alert-dismiss ${l.textClass}`,"aria-label":"Close alert",children:h.jsx(oe,{name:de.Times,className:l.iconClass,ariaLabel:"Close"})})]})},Ry=({layoutName:e,onBack:t,onPreview:n,onSave:r,isSaving:s=!1,className:o=""})=>{const i=` + `,id:g,...m,children:[t&&h.jsxs(I,{className:"relative",children:[h.jsx(yn,{src:t,alt:e||er("common.template_preview",{vendor:n,name:r}),className:"w-full h-48 object-cover"}),h.jsx(I,{className:"absolute top-3 right-3",children:h.jsx(Fo,{status:p(o),label:y(o),showIcon:!0})}),i&&h.jsx(I,{className:"absolute top-3 left-3",children:h.jsxs(Q,{className:"flex items-center gap-1 px-2 py-1 bg-yellow-50 dark:bg-yellow-900/20 text-yellow-700 dark:text-yellow-400 border border-yellow-200 dark:border-yellow-800 rounded-md text-xs font-semibold",children:[h.jsx(re,{name:de.ExclamationTriangle,className:"w-3 h-3"}),h.jsx(Q,{children:er("common.update_available")})]})})]}),h.jsxs(I,{className:"p-4",children:[h.jsxs(I,{className:"flex items-start justify-between mb-3",children:[h.jsxs(I,{className:"flex-1",children:[h.jsxs(Ir,{className:"text-lg font-bold text-gray-900 dark:text-white",children:[n,"/",r]}),h.jsxs(I,{className:"flex items-center gap-2 mt-1 text-sm text-gray-600 dark:text-gray-400",children:[h.jsxs(Q,{children:["v",s]}),i&&a&&h.jsxs(h.Fragment,{children:[h.jsx(Q,{children:"→"}),h.jsxs(Q,{className:"text-yellow-600 dark:text-yellow-400 font-semibold",children:["v",a]})]})]}),!t&&h.jsx(I,{className:"mt-2",children:h.jsx(Fo,{status:p(o),label:y(o),showIcon:!0})})]}),d.length>0&&h.jsx(xs,{items:d,triggerIconName:de.EllipsisVertical,position:"left"})]}),l.length>0&&h.jsxs(I,{className:"mb-3",children:[h.jsxs(yt,{className:"text-xs text-gray-500 dark:text-gray-400 mb-1",children:[er("common.dependencies"),":"]}),h.jsx(I,{className:"flex flex-wrap gap-1",children:l.map((b,_)=>h.jsx(Q,{className:"px-2 py-0.5 bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 text-xs rounded",children:b},_))})]}),c&&u&&h.jsxs(oe,{onClick:u,className:"w-full px-4 py-2 bg-blue-600 dark:bg-blue-500 text-white rounded-md hover:bg-blue-700 dark:hover:bg-blue-600 transition-colors flex items-center justify-center gap-2 font-medium",children:[h.jsx(re,{name:de.Edit,className:"w-4 h-4"}),h.jsx(Q,{children:er("common.edit_layout")})]})]})]})},cu=({type:t,message:e,dismissible:n=!1,onDismiss:r,className:s="",id:o,editorAttrs:i})=>{const a={info:{containerClass:"alert-info",iconClass:"alert-icon-info",textClass:"alert-text-info",icon:de.InfoCircle},success:{containerClass:"alert-success",iconClass:"alert-icon-success",textClass:"alert-text-success",icon:de.CheckCircle},warning:{containerClass:"alert-warning",iconClass:"alert-icon-warning",textClass:"alert-text-warning",icon:de.ExclamationTriangle},error:{containerClass:"alert-error",iconClass:"alert-icon-error",textClass:"alert-text-error",icon:de.TimesCircle}},l=a[t]??a.info;return h.jsxs(I,{className:`${l.containerClass} ${s}`,role:"alert","aria-live":"polite",id:o,...i,children:[h.jsx(re,{name:l.icon,className:l.iconClass,ariaLabel:`${t} icon`}),h.jsx(yt,{className:l.textClass,children:e}),n&&h.jsx(oe,{onClick:r,className:`alert-dismiss ${l.textClass}`,"aria-label":"Close alert",children:h.jsx(re,{name:de.Times,className:l.iconClass,ariaLabel:"Close"})})]})},Dy=({layoutName:t,onBack:e,onPreview:n,onSave:r,isSaving:s=!1,className:o=""})=>{const i=` flex items-center justify-between gap-4 p-4 bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 ${o} - `.trim().replace(/\s+/g," ");return h.jsxs(I,{className:i,children:[h.jsxs(I,{className:"flex items-center gap-3",children:[h.jsx(ae,{onClick:t,className:"p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors bg-transparent border-0 cursor-pointer","aria-label":"뒤로가기",children:h.jsx(oe,{name:de.ArrowLeft,className:"text-gray-700 dark:text-gray-300",ariaLabel:"뒤로가기"})}),h.jsx(ar,{className:"text-xl font-semibold text-gray-900 dark:text-gray-100",children:e})]}),h.jsxs(I,{className:"flex items-center gap-2",children:[h.jsx(ae,{onClick:n,className:"px-4 py-2 bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600 rounded-lg transition-colors border-0 cursor-pointer","aria-label":"미리보기",children:h.jsxs(I,{className:"flex items-center gap-2",children:[h.jsx(oe,{name:de.Eye,className:"text-gray-700 dark:text-gray-300",ariaLabel:"미리보기 아이콘"}),h.jsx("span",{children:"미리보기"})]})}),h.jsx(ae,{onClick:r,disabled:s,className:` + `.trim().replace(/\s+/g," ");return h.jsxs(I,{className:i,children:[h.jsxs(I,{className:"flex items-center gap-3",children:[h.jsx(oe,{onClick:e,className:"p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors bg-transparent border-0 cursor-pointer","aria-label":"뒤로가기",children:h.jsx(re,{name:de.ArrowLeft,className:"text-gray-700 dark:text-gray-300",ariaLabel:"뒤로가기"})}),h.jsx(Jn,{className:"text-xl font-semibold text-gray-900 dark:text-gray-100",children:t})]}),h.jsxs(I,{className:"flex items-center gap-2",children:[h.jsx(oe,{onClick:n,className:"px-4 py-2 bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600 rounded-lg transition-colors border-0 cursor-pointer","aria-label":"미리보기",children:h.jsxs(I,{className:"flex items-center gap-2",children:[h.jsx(re,{name:de.Eye,className:"text-gray-700 dark:text-gray-300",ariaLabel:"미리보기 아이콘"}),h.jsx("span",{children:"미리보기"})]})}),h.jsx(oe,{onClick:r,disabled:s,className:` px-4 py-2 rounded-lg transition-colors border-0 cursor-pointer ${s?"bg-blue-400 dark:bg-blue-600 cursor-not-allowed":"bg-blue-600 dark:bg-blue-500 hover:bg-blue-700 dark:hover:bg-blue-600"} text-white - `.trim().replace(/\s+/g," "),"aria-label":s?"저장 중...":"저장",children:h.jsxs(I,{className:"flex items-center gap-2",children:[h.jsx(oe,{name:s?de.Spinner:de.Save,className:`text-white ${s?"animate-spin":""}`,ariaLabel:s?"저장 중 아이콘":"저장 아이콘"}),h.jsx("span",{children:s?"저장 중...":"저장"})]})})]})]})},ev=({files:e,selectedId:t,onSelect:n,className:r=""})=>{const s=a=>{try{const l=new Date(a);if(isNaN(l.getTime()))return a;const c=l.getFullYear(),d=String(l.getMonth()+1).padStart(2,"0"),u=String(l.getDate()).padStart(2,"0"),f=String(l.getHours()).padStart(2,"0"),g=String(l.getMinutes()).padStart(2,"0");return`${c}-${d}-${u} ${f}:${g}`}catch{return a}},o=a=>{n(a)},i=` + `.trim().replace(/\s+/g," "),"aria-label":s?"저장 중...":"저장",children:h.jsxs(I,{className:"flex items-center gap-2",children:[h.jsx(re,{name:s?de.Spinner:de.Save,className:`text-white ${s?"animate-spin":""}`,ariaLabel:s?"저장 중 아이콘":"저장 아이콘"}),h.jsx("span",{children:s?"저장 중...":"저장"})]})})]})]})},Iy=({files:t,selectedId:e,onSelect:n,className:r=""})=>{const s=a=>{try{const l=new Date(a);if(isNaN(l.getTime()))return a;const c=l.getFullYear(),d=String(l.getMonth()+1).padStart(2,"0"),u=String(l.getDate()).padStart(2,"0"),f=String(l.getHours()).padStart(2,"0"),g=String(l.getMinutes()).padStart(2,"0");return`${c}-${d}-${u} ${f}:${g}`}catch{return a}},o=a=>{n(a)},i=` w-full ${r} - `.trim().replace(/\s+/g," ");return h.jsx(I,{className:i,children:e.length===0?h.jsx(I,{className:"p-4 text-center text-gray-500 dark:text-gray-400",children:"파일이 없습니다."}):h.jsx(ja,{className:"divide-y divide-gray-200 dark:divide-gray-700",role:"listbox","aria-label":"레이아웃 파일 목록",children:e.map(a=>{const l=a.id===t,c=` + `.trim().replace(/\s+/g," ");return h.jsx(I,{className:i,children:t.length===0?h.jsx(I,{className:"p-4 text-center text-gray-500 dark:text-gray-400",children:"파일이 없습니다."}):h.jsx(Ia,{className:"divide-y divide-gray-200 dark:divide-gray-700",role:"listbox","aria-label":"레이아웃 파일 목록",children:t.map(a=>{const l=a.id===e,c=` flex items-center justify-between p-4 cursor-pointer transition-colors duration-150 ${l?"bg-blue-100 dark:bg-blue-900 border-l-4 border-blue-500":"hover:bg-gray-100 dark:hover:bg-gray-800"} @@ -106,8 +105,8 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho `.trim().replace(/\s+/g," "),u=` text-sm ${l?"text-blue-700 dark:text-blue-300":"text-gray-500 dark:text-gray-400"} - `.trim().replace(/\s+/g," ");return h.jsx(Wa,{className:c,onClick:()=>o(a.id),role:"option","aria-selected":l,tabIndex:0,onKeyDown:f=>{(f.key==="Enter"||f.key===" ")&&(f.preventDefault(),o(a.id))},children:h.jsxs(I,{className:"flex flex-col gap-1",children:[h.jsx(Q,{className:d,children:a.name}),h.jsx(Q,{className:u,children:s(a.updated_at)})]})},a.id)})})})};function ku(e,t){(t==null||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n=e.length?e.apply(this,s):function(){for(var i=arguments.length,a=new Array(i),l=0;l1&&arguments[1]!==void 0?arguments[1]:{};ei.initial(e),ei.handler(t);var n={current:e},r=Hs(Sv)(n,t),s=Hs(Cv)(n),o=Hs(ei.changes)(e),i=Hs(kv)(n);function a(){var c=arguments.length>0&&arguments[0]!==void 0?arguments[0]:function(d){return d};return ei.selector(c),c(n.current)}function l(c){fv(r,s,o,i)(c)}return[a,l]}function kv(e,t){return Us(t)?t(e.current):t}function Cv(e,t){return e.current=Eu(Eu({},e.current),t),t}function Sv(e,t,n){return Us(t)?t(e.current):Object.keys(n).forEach(function(r){var s;return(s=t[r])===null||s===void 0?void 0:s.call(t,e.current[r])}),n}var _v={create:wv},Ev={paths:{vs:"https://cdn.jsdelivr.net/npm/monaco-editor@0.55.1/min/vs"}};function Mv(e){return function t(){for(var n=this,r=arguments.length,s=new Array(r),o=0;o=e.length?e.apply(this,s):function(){for(var i=arguments.length,a=new Array(i),l=0;lo(a.id),role:"option","aria-selected":l,tabIndex:0,onKeyDown:f=>{(f.key==="Enter"||f.key===" ")&&(f.preventDefault(),o(a.id))},children:h.jsxs(I,{className:"flex flex-col gap-1",children:[h.jsx(Q,{className:d,children:a.name}),h.jsx(Q,{className:u,children:s(a.updated_at)})]})},a.id)})})})};function du(t,e){(e==null||e>t.length)&&(e=t.length);for(var n=0,r=Array(e);n=t.length?t.apply(this,s):function(){for(var i=arguments.length,a=new Array(i),l=0;l1&&arguments[1]!==void 0?arguments[1]:{};Go.initial(t),Go.handler(e);var n={current:t},r=Ns(rv)(n,e),s=Ns(nv)(n),o=Ns(Go.changes)(t),i=Ns(tv)(n);function a(){var c=arguments.length>0&&arguments[0]!==void 0?arguments[0]:function(d){return d};return Go.selector(c),c(n.current)}function l(c){Gy(r,s,o,i)(c)}return[a,l]}function tv(t,e){return Os(e)?e(t.current):e}function nv(t,e){return t.current=gu(gu({},t.current),e),e}function rv(t,e,n){return Os(e)?e(t.current):Object.keys(n).forEach(function(r){var s;return(s=e[r])===null||s===void 0?void 0:s.call(e,t.current[r])}),n}var sv={create:ev},ov={paths:{vs:"https://cdn.jsdelivr.net/npm/monaco-editor@0.55.1/min/vs"}};function iv(t){return function e(){for(var n=this,r=arguments.length,s=new Array(r),o=0;o=t.length?t.apply(this,s):function(){for(var i=arguments.length,a=new Array(i),l=0;l{r.current=!1}:e,t)}var cn=Rv;function js(){}function ns(e,t,n,r){return ex(e,r)||tx(e,t,n,r)}function ex(e,t){return e.editor.getModel(Pu(e,t))}function tx(e,t,n,r){return e.editor.createModel(t,n,r?Pu(e,r):void 0)}function Pu(e,t){return e.Uri.parse(t)}function nx({original:e,modified:t,language:n,originalLanguage:r,modifiedLanguage:s,originalModelPath:o,modifiedModelPath:i,keepCurrentOriginalModel:a=!1,keepCurrentModifiedModel:l=!1,theme:c="light",loading:d="Loading...",options:u={},height:f="100%",width:g="100%",className:m,wrapperProps:p={},beforeMount:y=js,onMount:b=js}){let[_,x]=v.useState(!1),[M,C]=v.useState(!0),k=v.useRef(null),N=v.useRef(null),S=v.useRef(null),w=v.useRef(b),E=v.useRef(y),D=v.useRef(!1);Lu(()=>{let A=Du.init();return A.then(O=>(N.current=O)&&C(!1)).catch(O=>O?.type!=="cancelation"&&console.error("Monaco initialization: error:",O)),()=>k.current?$():A.cancel()}),cn(()=>{if(k.current&&N.current){let A=k.current.getOriginalEditor(),O=ns(N.current,e||"",r||n||"text",o||"");O!==A.getModel()&&A.setModel(O)}},[o],_),cn(()=>{if(k.current&&N.current){let A=k.current.getModifiedEditor(),O=ns(N.current,t||"",s||n||"text",i||"");O!==A.getModel()&&A.setModel(O)}},[i],_),cn(()=>{let A=k.current.getModifiedEditor();A.getOption(N.current.editor.EditorOption.readOnly)?A.setValue(t||""):t!==A.getValue()&&(A.executeEdits("",[{range:A.getModel().getFullModelRange(),text:t||"",forceMoveMarkers:!0}]),A.pushUndoStop())},[t],_),cn(()=>{k.current?.getModel()?.original.setValue(e||"")},[e],_),cn(()=>{let{original:A,modified:O}=k.current.getModel();N.current.editor.setModelLanguage(A,r||n||"text"),N.current.editor.setModelLanguage(O,s||n||"text")},[n,r,s],_),cn(()=>{N.current?.editor.setTheme(c)},[c],_),cn(()=>{k.current?.updateOptions(u)},[u],_);let P=v.useCallback(()=>{if(!N.current)return;E.current(N.current);let A=ns(N.current,e||"",r||n||"text",o||""),O=ns(N.current,t||"",s||n||"text",i||"");k.current?.setModel({original:A,modified:O})},[n,t,s,e,r,o,i]),L=v.useCallback(()=>{!D.current&&S.current&&(k.current=N.current.editor.createDiffEditor(S.current,{automaticLayout:!0,...u}),P(),N.current?.editor.setTheme(c),x(!0),D.current=!0)},[u,c,P]);v.useEffect(()=>{_&&w.current(k.current,N.current)},[_]),v.useEffect(()=>{!M&&!_&&L()},[M,_,L]);function $(){let A=k.current?.getModel();a||A?.original?.dispose(),l||A?.modified?.dispose(),k.current?.dispose()}return v.createElement(Iu,{width:g,height:f,isEditorReady:_,loading:d,_ref:S,className:m,wrapperProps:p})}var rx=nx;v.memo(rx);function sx(e){let t=v.useRef();return v.useEffect(()=>{t.current=e},[e]),t.current}var ox=sx,ni=new Map;function ix({defaultValue:e,defaultLanguage:t,defaultPath:n,value:r,language:s,path:o,theme:i="light",line:a,loading:l="Loading...",options:c={},overrideServices:d={},saveViewState:u=!0,keepCurrentModel:f=!1,width:g="100%",height:m="100%",className:p,wrapperProps:y={},beforeMount:b=js,onMount:_=js,onChange:x,onValidate:M=js}){let[C,k]=v.useState(!1),[N,S]=v.useState(!0),w=v.useRef(null),E=v.useRef(null),D=v.useRef(null),P=v.useRef(_),L=v.useRef(b),$=v.useRef(),A=v.useRef(r),O=ox(o),T=v.useRef(!1),z=v.useRef(!1);Lu(()=>{let G=Du.init();return G.then(U=>(w.current=U)&&S(!1)).catch(U=>U?.type!=="cancelation"&&console.error("Monaco initialization: error:",U)),()=>E.current?j():G.cancel()}),cn(()=>{let G=ns(w.current,e||r||"",t||s||"",o||n||"");G!==E.current?.getModel()&&(u&&ni.set(O,E.current?.saveViewState()),E.current?.setModel(G),u&&E.current?.restoreViewState(ni.get(o)))},[o],C),cn(()=>{E.current?.updateOptions(c)},[c],C),cn(()=>{!E.current||r===void 0||(E.current.getOption(w.current.editor.EditorOption.readOnly)?E.current.setValue(r):r!==E.current.getValue()&&(z.current=!0,E.current.executeEdits("",[{range:E.current.getModel().getFullModelRange(),text:r,forceMoveMarkers:!0}]),E.current.pushUndoStop(),z.current=!1))},[r],C),cn(()=>{let G=E.current?.getModel();G&&s&&w.current?.editor.setModelLanguage(G,s)},[s],C),cn(()=>{a!==void 0&&E.current?.revealLine(a)},[a],C),cn(()=>{w.current?.editor.setTheme(i)},[i],C);let H=v.useCallback(()=>{if(!(!D.current||!w.current)&&!T.current){L.current(w.current);let G=o||n,U=ns(w.current,r||e||"",t||s||"",G||"");E.current=w.current?.editor.create(D.current,{model:U,automaticLayout:!0,...c},d),u&&E.current.restoreViewState(ni.get(G)),w.current.editor.setTheme(i),a!==void 0&&E.current.revealLine(a),k(!0),T.current=!0}},[e,t,n,r,s,o,c,d,u,i,a]);v.useEffect(()=>{C&&P.current(E.current,w.current)},[C]),v.useEffect(()=>{!N&&!C&&H()},[N,C,H]),A.current=r,v.useEffect(()=>{C&&x&&($.current?.dispose(),$.current=E.current?.onDidChangeModelContent(G=>{z.current||x(E.current.getValue(),G)}))},[C,x]),v.useEffect(()=>{if(C){let G=w.current.editor.onDidChangeMarkers(U=>{let W=E.current.getModel()?.uri;if(W&&U.find(Y=>Y.path===W.path)){let Y=w.current.editor.getModelMarkers({resource:W});M?.(Y)}});return()=>{G?.dispose()}}return()=>{}},[C,M]);function j(){$.current?.dispose(),f?u&&ni.set(o,E.current.saveViewState()):E.current.getModel()?.dispose(),E.current.dispose()}return v.createElement(Iu,{width:g,height:m,isEditorReady:C,loading:l,_ref:D,className:p,wrapperProps:y})}var ax=ix,lx=v.memo(ax),cx=lx;const dx=({value:e,onChange:t,language:n="json",height:r="100%",readOnly:s=!1,theme:o="vs-dark"})=>{const i=v.useRef(null),a=(c,d)=>{i.current=c,n==="json"&&d.languages.json.jsonDefaults.setDiagnosticsOptions({validate:!0,schemaValidation:"error",allowComments:!1}),c.updateOptions({readOnly:s,minimap:{enabled:!1},scrollBeyondLastLine:!1,fontSize:14,lineNumbers:"on",renderWhitespace:"selection",automaticLayout:!0})},l=c=>{t&&c!==void 0&&t({target:{value:c}})};return v.useEffect(()=>{i.current&&i.current.updateOptions({readOnly:s})},[s]),h.jsx("div",{className:"border border-gray-300 dark:border-gray-700 rounded-lg overflow-hidden",children:h.jsx(cx,{height:r,language:n,value:e,theme:o,onChange:l,onMount:a,options:{readOnly:s,minimap:{enabled:!1},scrollBeyondLastLine:!1,fontSize:14,lineNumbers:"on",renderWhitespace:"selection",automaticLayout:!0}})})},ux=e=>{const t=new Date(e);return new Intl.DateTimeFormat("ko-KR",{year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit"}).format(t)},$u=v.memo(({version:e,isSelected:t,onSelect:n})=>{const r=v.useCallback(()=>{n?.(e.id)},[n,e.id]);return h.jsx(Wa,{role:"listitem","aria-selected":t,className:` + `},mu=iv(dv)(pu),uv={config:lv},fv=function(){for(var e=arguments.length,n=new Array(e),r=0;r{r.current=!1}:t,e)}var on=Dv;function As(){}function Hr(t,e,n,r){return Iv(t,r)||Lv(t,e,n,r)}function Iv(t,e){return t.editor.getModel(Cu(t,e))}function Lv(t,e,n,r){return t.editor.createModel(e,n,r?Cu(t,r):void 0)}function Cu(t,e){return t.Uri.parse(e)}function Pv({original:t,modified:e,language:n,originalLanguage:r,modifiedLanguage:s,originalModelPath:o,modifiedModelPath:i,keepCurrentOriginalModel:a=!1,keepCurrentModifiedModel:l=!1,theme:c="light",loading:d="Loading...",options:u={},height:f="100%",width:g="100%",className:m,wrapperProps:p={},beforeMount:y=As,onMount:b=As}){let[_,x]=v.useState(!1),[E,C]=v.useState(!0),k=v.useRef(null),N=v.useRef(null),S=v.useRef(null),w=v.useRef(b),M=v.useRef(y),T=v.useRef(!1);ku(()=>{let A=xu.init();return A.then(D=>(N.current=D)&&C(!1)).catch(D=>D?.type!=="cancelation"&&console.error("Monaco initialization: error:",D)),()=>k.current?$():A.cancel()}),on(()=>{if(k.current&&N.current){let A=k.current.getOriginalEditor(),D=Hr(N.current,t||"",r||n||"text",o||"");D!==A.getModel()&&A.setModel(D)}},[o],_),on(()=>{if(k.current&&N.current){let A=k.current.getModifiedEditor(),D=Hr(N.current,e||"",s||n||"text",i||"");D!==A.getModel()&&A.setModel(D)}},[i],_),on(()=>{let A=k.current.getModifiedEditor();A.getOption(N.current.editor.EditorOption.readOnly)?A.setValue(e||""):e!==A.getValue()&&(A.executeEdits("",[{range:A.getModel().getFullModelRange(),text:e||"",forceMoveMarkers:!0}]),A.pushUndoStop())},[e],_),on(()=>{k.current?.getModel()?.original.setValue(t||"")},[t],_),on(()=>{let{original:A,modified:D}=k.current.getModel();N.current.editor.setModelLanguage(A,r||n||"text"),N.current.editor.setModelLanguage(D,s||n||"text")},[n,r,s],_),on(()=>{N.current?.editor.setTheme(c)},[c],_),on(()=>{k.current?.updateOptions(u)},[u],_);let P=v.useCallback(()=>{if(!N.current)return;M.current(N.current);let A=Hr(N.current,t||"",r||n||"text",o||""),D=Hr(N.current,e||"",s||n||"text",i||"");k.current?.setModel({original:A,modified:D})},[n,e,s,t,r,o,i]),L=v.useCallback(()=>{!T.current&&S.current&&(k.current=N.current.editor.createDiffEditor(S.current,{automaticLayout:!0,...u}),P(),N.current?.editor.setTheme(c),x(!0),T.current=!0)},[u,c,P]);v.useEffect(()=>{_&&w.current(k.current,N.current)},[_]),v.useEffect(()=>{!E&&!_&&L()},[E,_,L]);function $(){let A=k.current?.getModel();a||A?.original?.dispose(),l||A?.modified?.dispose(),k.current?.dispose()}return v.createElement(wu,{width:g,height:f,isEditorReady:_,loading:d,_ref:S,className:m,wrapperProps:p})}var $v=Pv;v.memo($v);function Fv(t){let e=v.useRef();return v.useEffect(()=>{e.current=t},[t]),e.current}var zv=Fv,Ko=new Map;function Vv({defaultValue:t,defaultLanguage:e,defaultPath:n,value:r,language:s,path:o,theme:i="light",line:a,loading:l="Loading...",options:c={},overrideServices:d={},saveViewState:u=!0,keepCurrentModel:f=!1,width:g="100%",height:m="100%",className:p,wrapperProps:y={},beforeMount:b=As,onMount:_=As,onChange:x,onValidate:E=As}){let[C,k]=v.useState(!1),[N,S]=v.useState(!0),w=v.useRef(null),M=v.useRef(null),T=v.useRef(null),P=v.useRef(_),L=v.useRef(b),$=v.useRef(),A=v.useRef(r),D=zv(o),O=v.useRef(!1),z=v.useRef(!1);ku(()=>{let W=xu.init();return W.then(U=>(w.current=U)&&S(!1)).catch(U=>U?.type!=="cancelation"&&console.error("Monaco initialization: error:",U)),()=>M.current?j():W.cancel()}),on(()=>{let W=Hr(w.current,t||r||"",e||s||"",o||n||"");W!==M.current?.getModel()&&(u&&Ko.set(D,M.current?.saveViewState()),M.current?.setModel(W),u&&M.current?.restoreViewState(Ko.get(o)))},[o],C),on(()=>{M.current?.updateOptions(c)},[c],C),on(()=>{!M.current||r===void 0||(M.current.getOption(w.current.editor.EditorOption.readOnly)?M.current.setValue(r):r!==M.current.getValue()&&(z.current=!0,M.current.executeEdits("",[{range:M.current.getModel().getFullModelRange(),text:r,forceMoveMarkers:!0}]),M.current.pushUndoStop(),z.current=!1))},[r],C),on(()=>{let W=M.current?.getModel();W&&s&&w.current?.editor.setModelLanguage(W,s)},[s],C),on(()=>{a!==void 0&&M.current?.revealLine(a)},[a],C),on(()=>{w.current?.editor.setTheme(i)},[i],C);let H=v.useCallback(()=>{if(!(!T.current||!w.current)&&!O.current){L.current(w.current);let W=o||n,U=Hr(w.current,r||t||"",e||s||"",W||"");M.current=w.current?.editor.create(T.current,{model:U,automaticLayout:!0,...c},d),u&&M.current.restoreViewState(Ko.get(W)),w.current.editor.setTheme(i),a!==void 0&&M.current.revealLine(a),k(!0),O.current=!0}},[t,e,n,r,s,o,c,d,u,i,a]);v.useEffect(()=>{C&&P.current(M.current,w.current)},[C]),v.useEffect(()=>{!N&&!C&&H()},[N,C,H]),A.current=r,v.useEffect(()=>{C&&x&&($.current?.dispose(),$.current=M.current?.onDidChangeModelContent(W=>{z.current||x(M.current.getValue(),W)}))},[C,x]),v.useEffect(()=>{if(C){let W=w.current.editor.onDidChangeMarkers(U=>{let B=M.current.getModel()?.uri;if(B&&U.find(G=>G.path===B.path)){let G=w.current.editor.getModelMarkers({resource:B});E?.(G)}});return()=>{W?.dispose()}}return()=>{}},[C,E]);function j(){$.current?.dispose(),f?u&&Ko.set(o,M.current.saveViewState()):M.current.getModel()?.dispose(),M.current.dispose()}return v.createElement(wu,{width:g,height:m,isEditorReady:C,loading:l,_ref:T,className:p,wrapperProps:y})}var Hv=Vv,Uv=v.memo(Hv),Bv=Uv;const jv=({value:t,onChange:e,language:n="json",height:r="100%",readOnly:s=!1,theme:o="vs-dark"})=>{const i=v.useRef(null),a=(c,d)=>{i.current=c,n==="json"&&d.languages.json.jsonDefaults.setDiagnosticsOptions({validate:!0,schemaValidation:"error",allowComments:!1}),c.updateOptions({readOnly:s,minimap:{enabled:!1},scrollBeyondLastLine:!1,fontSize:14,lineNumbers:"on",renderWhitespace:"selection",automaticLayout:!0})},l=c=>{e&&c!==void 0&&e({target:{value:c}})};return v.useEffect(()=>{i.current&&i.current.updateOptions({readOnly:s})},[s]),h.jsx("div",{className:"border border-gray-300 dark:border-gray-700 rounded-lg overflow-hidden",children:h.jsx(Bv,{height:r,language:n,value:t,theme:o,onChange:l,onMount:a,options:{readOnly:s,minimap:{enabled:!1},scrollBeyondLastLine:!1,fontSize:14,lineNumbers:"on",renderWhitespace:"selection",automaticLayout:!0}})})},Wv=t=>{const e=new Date(t);return new Intl.DateTimeFormat("ko-KR",{year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit"}).format(e)},Su=v.memo(({version:t,isSelected:e,onSelect:n})=>{const r=v.useCallback(()=>{n?.(t.id)},[n,t.id]);return h.jsx(La,{role:"listitem","aria-selected":e,className:` p-3 rounded-lg border cursor-pointer transition-colors - ${t?"border-blue-500 bg-blue-50 dark:bg-blue-900/20":"border-gray-300 hover:border-gray-400 dark:border-gray-600 dark:hover:border-gray-500"} - `,onClick:r,children:h.jsxs(I,{className:"flex justify-between items-start",children:[h.jsxs(I,{className:"flex-1",children:[h.jsxs(Q,{className:"font-semibold text-gray-900 dark:text-gray-100",children:["버전 ",e.version]}),h.jsx(Q,{className:"ml-2 text-sm text-gray-500 dark:text-gray-400",children:ux(e.created_at)})]}),h.jsxs(I,{className:"flex gap-3 text-sm",children:[h.jsxs(Q,{className:"text-green-600 dark:text-green-400",children:["+",e.changes_summary.added]}),h.jsxs(Q,{className:"text-red-600 dark:text-red-400",children:["-",e.changes_summary.removed]})]})]})})});$u.displayName="VersionListItem";const Fu=({versions:e,selectedId:t,onSelect:n})=>e.length===0?h.jsx(I,{className:"p-4 text-center text-gray-500 dark:text-gray-400",children:"버전이 없습니다."}):h.jsx(ja,{role:"list",className:"space-y-2",children:e.map(r=>h.jsx($u,{version:r,isSelected:t===r.id,onSelect:n},r.id))}),fx=({layoutId:e,versions:t,onRestore:n})=>{const[r,s]=v.useState(),o=l=>{s(l)},i=()=>{r!==void 0&&n(r)},a=r===void 0;return h.jsxs(I,{className:"space-y-4",role:"region","aria-label":"레이아웃 히스토리 패널",children:[h.jsx(I,{className:"border border-gray-300 dark:border-gray-600 rounded-lg p-4",children:h.jsx(Fu,{versions:t,selectedId:r,onSelect:o})}),h.jsx(I,{className:"flex justify-end",children:h.jsx(ae,{variant:"primary",disabled:a,onClick:i,"aria-label":"선택한 버전으로 복원",children:"복원"})})]})},hx=({emailLabel:e="Email",passwordLabel:t="Password",submitButtonText:n="Sign In",processingText:r="Processing...",emailPlaceholder:s="Email",passwordPlaceholder:o="Password",forgotPasswordText:i="Forgot your password?",forgotPasswordUrl:a="/admin/password/reset",emailRequiredError:l="Please enter your email.",emailInvalidError:c="Please enter a valid email address.",passwordRequiredError:d="Please enter your password.",onSubmit:u,loadingActions:f={},isLoading:g=!1,disabled:m=!1,apiError:p="",className:y="",style:b})=>{const[_,x]=v.useState(""),[M,C]=v.useState(""),[k,N]=v.useState({}),S=p,w=Object.values(f).some($=>$===!0),E=g||w,D=m||E,P=()=>{const $={},A=/^[^\s@]+@[^\s@]+\.[^\s@]+$/;return!_||_.trim()===""?$.email=l:A.test(_)||($.email=c),(!M||M.trim()==="")&&($.password=d),N($),Object.keys($).length===0},L=async $=>{if(!P()){$.preventDefault();return}N({}),u?await u($):$.preventDefault()};return h.jsx(I,{className:`w-full ${y}`,style:b,children:h.jsxs(Ga,{onSubmit:L,className:"space-y-6",noValidate:!0,children:[S&&h.jsx(I,{className:"p-4 rounded-lg bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800",children:h.jsx(vt,{className:"text-sm text-red-600 dark:text-red-400",children:S})}),h.jsxs(I,{children:[h.jsx(ln,{htmlFor:"email",className:"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2",children:e}),h.jsx(St,{id:"email",name:"email",type:"email",value:_,onChange:$=>x($.target.value),placeholder:s,disabled:D,className:` + ${e?"border-blue-500 bg-blue-50 dark:bg-blue-900/20":"border-gray-300 hover:border-gray-400 dark:border-gray-600 dark:hover:border-gray-500"} + `,onClick:r,children:h.jsxs(I,{className:"flex justify-between items-start",children:[h.jsxs(I,{className:"flex-1",children:[h.jsxs(Q,{className:"font-semibold text-gray-900 dark:text-gray-100",children:["버전 ",t.version]}),h.jsx(Q,{className:"ml-2 text-sm text-gray-500 dark:text-gray-400",children:Wv(t.created_at)})]}),h.jsxs(I,{className:"flex gap-3 text-sm",children:[h.jsxs(Q,{className:"text-green-600 dark:text-green-400",children:["+",t.changes_summary.added]}),h.jsxs(Q,{className:"text-red-600 dark:text-red-400",children:["-",t.changes_summary.removed]})]})]})})});Su.displayName="VersionListItem";const _u=({versions:t,selectedId:e,onSelect:n})=>t.length===0?h.jsx(I,{className:"p-4 text-center text-gray-500 dark:text-gray-400",children:"버전이 없습니다."}):h.jsx(Ia,{role:"list",className:"space-y-2",children:t.map(r=>h.jsx(Su,{version:r,isSelected:e===r.id,onSelect:n},r.id))}),Gv=({layoutId:t,versions:e,onRestore:n})=>{const[r,s]=v.useState(),o=l=>{s(l)},i=()=>{r!==void 0&&n(r)},a=r===void 0;return h.jsxs(I,{className:"space-y-4",role:"region","aria-label":"레이아웃 히스토리 패널",children:[h.jsx(I,{className:"border border-gray-300 dark:border-gray-600 rounded-lg p-4",children:h.jsx(_u,{versions:e,selectedId:r,onSelect:o})}),h.jsx(I,{className:"flex justify-end",children:h.jsx(oe,{variant:"primary",disabled:a,onClick:i,"aria-label":"선택한 버전으로 복원",children:"복원"})})]})},Yv=({emailLabel:t="Email",passwordLabel:e="Password",submitButtonText:n="Sign In",processingText:r="Processing...",emailPlaceholder:s="Email",passwordPlaceholder:o="Password",forgotPasswordText:i="Forgot your password?",forgotPasswordUrl:a="/admin/password/reset",emailRequiredError:l="Please enter your email.",emailInvalidError:c="Please enter a valid email address.",passwordRequiredError:d="Please enter your password.",onSubmit:u,loadingActions:f={},isLoading:g=!1,disabled:m=!1,apiError:p="",className:y="",style:b})=>{const[_,x]=v.useState(""),[E,C]=v.useState(""),[k,N]=v.useState({}),S=p,w=Object.values(f).some($=>$===!0),M=g||w,T=m||M,P=()=>{const $={},A=/^[^\s@]+@[^\s@]+\.[^\s@]+$/;return!_||_.trim()===""?$.email=l:A.test(_)||($.email=c),(!E||E.trim()==="")&&($.password=d),N($),Object.keys($).length===0},L=async $=>{if(!P()){$.preventDefault();return}N({}),u?await u($):$.preventDefault()};return h.jsx(I,{className:`w-full ${y}`,style:b,children:h.jsxs(Pa,{onSubmit:L,className:"space-y-6",noValidate:!0,children:[S&&h.jsx(I,{className:"p-4 rounded-lg bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800",children:h.jsx(yt,{className:"text-sm text-red-600 dark:text-red-400",children:S})}),h.jsxs(I,{children:[h.jsx(sn,{htmlFor:"email",className:"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2",children:t}),h.jsx(kt,{id:"email",name:"email",type:"email",value:_,onChange:$=>x($.target.value),placeholder:s,disabled:T,className:` w-full px-4 py-3 rounded-lg border ${k.email?"border-red-500 focus:border-red-500 focus:ring-red-500":"border-gray-300 dark:border-gray-600 focus:border-blue-500 focus:ring-blue-500"} bg-white dark:bg-gray-800 @@ -128,7 +127,7 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho focus:outline-none focus:ring-2 disabled:opacity-50 disabled:cursor-not-allowed transition-colors duration-200 - `}),k.email&&h.jsx(vt,{className:"mt-2 text-sm text-red-600 dark:text-red-400",children:k.email})]}),h.jsxs(I,{children:[h.jsx(ln,{htmlFor:"password",className:"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2",children:t}),h.jsx(St,{id:"password",name:"password",type:"password",value:M,onChange:$=>C($.target.value),placeholder:o,disabled:D,className:` + `}),k.email&&h.jsx(yt,{className:"mt-2 text-sm text-red-600 dark:text-red-400",children:k.email})]}),h.jsxs(I,{children:[h.jsx(sn,{htmlFor:"password",className:"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2",children:e}),h.jsx(kt,{id:"password",name:"password",type:"password",value:E,onChange:$=>C($.target.value),placeholder:o,disabled:T,className:` w-full px-4 py-3 rounded-lg border ${k.password?"border-red-500 focus:border-red-500 focus:ring-red-500":"border-gray-300 dark:border-gray-600 focus:border-blue-500 focus:ring-blue-500"} bg-white dark:bg-gray-800 @@ -137,7 +136,7 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho focus:outline-none focus:ring-2 disabled:opacity-50 disabled:cursor-not-allowed transition-colors duration-200 - `}),k.password&&h.jsx(vt,{className:"mt-2 text-sm text-red-600 dark:text-red-400",children:k.password})]}),a&&h.jsx(I,{className:"flex items-center justify-end",children:h.jsx(Yr,{href:a,className:"text-sm text-blue-600 hover:text-blue-500 dark:text-blue-400 dark:hover:text-blue-300 transition-colors duration-200",children:i})}),h.jsx(ae,{type:"submit",disabled:D,className:` + `}),k.password&&h.jsx(yt,{className:"mt-2 text-sm text-red-600 dark:text-red-400",children:k.password})]}),a&&h.jsx(I,{className:"flex items-center justify-end",children:h.jsx(Lr,{href:a,className:"text-sm text-blue-600 hover:text-blue-500 dark:text-blue-400 dark:hover:text-blue-300 transition-colors duration-200",children:i})}),h.jsx(oe,{type:"submit",disabled:T,className:` w-full px-4 py-3 rounded-lg font-medium bg-blue-600 hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600 @@ -147,16 +146,16 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho disabled:opacity-50 disabled:cursor-not-allowed transition-colors duration-200 flex items-center justify-center - `,children:E?h.jsxs(h.Fragment,{children:[h.jsxs(Zn,{className:"animate-spin -ml-1 mr-3 h-5 w-5 text-white",xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",children:[h.jsx("circle",{className:"opacity-25",cx:"12",cy:"12",r:"10",stroke:"currentColor",strokeWidth:"4"}),h.jsx("path",{className:"opacity-75",fill:"currentColor",d:"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"})]}),r]}):n})]})})},zu=e=>e==="auto"?window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light":e,bl=e=>{const t=zu(e);document.documentElement.setAttribute("data-theme",t),t==="dark"?document.documentElement.classList.add("dark"):document.documentElement.classList.remove("dark")},gx=()=>{const e=localStorage.getItem("g7_color_scheme");return e&&["auto","light","dark"].includes(e)?e:"auto"},px=({onThemeChange:e,className:t="",autoText:n="System",lightText:r="Light",darkText:s="Dark",id:o,editorAttrs:i})=>{const a=gx(),[l,c]=v.useState(a),[d,u]=v.useState(!1),f=v.useRef(null);v.useEffect(()=>{bl(l)},[]),v.useEffect(()=>{if(l!=="auto")return;const p=window.matchMedia("(prefers-color-scheme: dark)"),y=()=>{bl("auto")};return p.addEventListener("change",y),()=>p.removeEventListener("change",y)},[l]),v.useEffect(()=>{const p=y=>{f.current&&!f.current.contains(y.target)&&u(!1)};return document.addEventListener("mousedown",p),()=>document.removeEventListener("mousedown",p)},[]);const g=p=>{c(p),localStorage.setItem("g7_color_scheme",p),bl(p),u(!1),e?.(p)},m=()=>zu(l)==="dark"?de.Moon:de.Sun;return h.jsxs(I,{ref:f,className:`relative ${t}`,id:o,...i,children:[h.jsx(ae,{onClick:()=>u(!d),className:"w-9 h-9 flex items-center justify-center hover:bg-gray-100 dark:hover:bg-gray-800 rounded-full transition-colors","aria-label":"Toggle theme",children:h.jsx(oe,{name:m(),className:"text-sm text-slate-500 dark:text-slate-400"})}),d&&h.jsx(I,{className:"absolute right-0 mt-2 w-48 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg z-50",children:h.jsxs(I,{className:"py-2",children:[h.jsxs(ae,{onClick:()=>g("auto"),className:` + `,children:M?h.jsxs(h.Fragment,{children:[h.jsxs(Un,{className:"animate-spin -ml-1 mr-3 h-5 w-5 text-white",xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",children:[h.jsx("circle",{className:"opacity-25",cx:"12",cy:"12",r:"10",stroke:"currentColor",strokeWidth:"4"}),h.jsx("path",{className:"opacity-75",fill:"currentColor",d:"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"})]}),r]}):n})]})})},Eu=t=>t==="auto"?window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light":t,ll=t=>{const e=Eu(t);document.documentElement.setAttribute("data-theme",e),e==="dark"?document.documentElement.classList.add("dark"):document.documentElement.classList.remove("dark")},Kv=()=>{const t=localStorage.getItem("g7_color_scheme");return t&&["auto","light","dark"].includes(t)?t:"auto"},Xv=({onThemeChange:t,className:e="",autoText:n="System",lightText:r="Light",darkText:s="Dark",id:o,editorAttrs:i})=>{const a=Kv(),[l,c]=v.useState(a),[d,u]=v.useState(!1),f=v.useRef(null);v.useEffect(()=>{ll(l)},[]),v.useEffect(()=>{if(l!=="auto")return;const p=window.matchMedia("(prefers-color-scheme: dark)"),y=()=>{ll("auto")};return p.addEventListener("change",y),()=>p.removeEventListener("change",y)},[l]),v.useEffect(()=>{const p=y=>{f.current&&!f.current.contains(y.target)&&u(!1)};return document.addEventListener("mousedown",p),()=>document.removeEventListener("mousedown",p)},[]);const g=p=>{c(p),localStorage.setItem("g7_color_scheme",p),ll(p),u(!1),t?.(p)},m=()=>Eu(l)==="dark"?de.Moon:de.Sun;return h.jsxs(I,{ref:f,className:`relative ${e}`,id:o,...i,children:[h.jsx(oe,{onClick:()=>u(!d),className:"w-9 h-9 flex items-center justify-center hover:bg-gray-100 dark:hover:bg-gray-800 rounded-full transition-colors","aria-label":"Toggle theme",children:h.jsx(re,{name:m(),className:"text-sm text-slate-500 dark:text-slate-400"})}),d&&h.jsx(I,{className:"absolute right-0 mt-2 w-48 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg z-50",children:h.jsxs(I,{className:"py-2",children:[h.jsxs(oe,{onClick:()=>g("auto"),className:` w-full px-4 py-2 text-left hover:bg-gray-50 dark:hover:bg-gray-700 flex items-center gap-3 transition-colors ${l==="auto"?"bg-gray-50 dark:bg-gray-700":""} - `,children:[h.jsx(oe,{name:de.Settings,className:"w-5 h-5 text-gray-600 dark:text-gray-400"}),h.jsx(Q,{className:"flex-1 text-left text-gray-900 dark:text-white",children:n}),l==="auto"&&h.jsx(oe,{name:de.Check,className:"w-4 h-4 text-blue-600 dark:text-blue-400 ml-auto"})]}),h.jsxs(ae,{onClick:()=>g("light"),className:` + `,children:[h.jsx(re,{name:de.Settings,className:"w-5 h-5 text-gray-600 dark:text-gray-400"}),h.jsx(Q,{className:"flex-1 text-left text-gray-900 dark:text-white",children:n}),l==="auto"&&h.jsx(re,{name:de.Check,className:"w-4 h-4 text-blue-600 dark:text-blue-400 ml-auto"})]}),h.jsxs(oe,{onClick:()=>g("light"),className:` w-full px-4 py-2 text-left hover:bg-gray-50 dark:hover:bg-gray-700 flex items-center gap-3 transition-colors ${l==="light"?"bg-gray-50 dark:bg-gray-700":""} - `,children:[h.jsx(oe,{name:de.Sun,className:"w-5 h-5 text-gray-600 dark:text-gray-400"}),h.jsx(Q,{className:"flex-1 text-left text-gray-900 dark:text-white",children:r}),l==="light"&&h.jsx(oe,{name:de.Check,className:"w-4 h-4 text-blue-600 dark:text-blue-400 ml-auto"})]}),h.jsxs(ae,{onClick:()=>g("dark"),className:` + `,children:[h.jsx(re,{name:de.Sun,className:"w-5 h-5 text-gray-600 dark:text-gray-400"}),h.jsx(Q,{className:"flex-1 text-left text-gray-900 dark:text-white",children:r}),l==="light"&&h.jsx(re,{name:de.Check,className:"w-4 h-4 text-blue-600 dark:text-blue-400 ml-auto"})]}),h.jsxs(oe,{onClick:()=>g("dark"),className:` w-full px-4 py-2 text-left hover:bg-gray-50 dark:hover:bg-gray-700 flex items-center gap-3 transition-colors ${l==="dark"?"bg-gray-50 dark:bg-gray-700":""} - `,children:[h.jsx(oe,{name:de.Moon,className:"w-5 h-5 text-gray-600 dark:text-gray-400"}),h.jsx(Q,{className:"flex-1 text-left text-gray-900 dark:text-white",children:s}),l==="dark"&&h.jsx(oe,{name:de.Check,className:"w-4 h-4 text-blue-600 dark:text-blue-400 ml-auto"})]})]})})]})},Vu=window.G7Core?.createLogger?.("Comp:ColumnSelector")??{log:(...e)=>console.log("[Comp:ColumnSelector]",...e),warn:(...e)=>console.warn("[Comp:ColumnSelector]",...e),error:(...e)=>console.error("[Comp:ColumnSelector]",...e)},Hu=(e,t)=>window.G7Core?.t?.(e,t)??e,mx=()=>window.G7Core,bx=({id:e,columns:t,visibleColumns:n,onColumnVisibilityChange:r,triggerLabel:s,triggerIconName:o=de.Sliders,position:i="right",className:a="",style:l,requiredText:c,menuTitle:d,menuDescription:u,defaultColumns:f,triggerClassName:g,labelClassName:m,dynamicColumns:p,dynamicColumnsInsertAfter:y,editorAttrs:b})=>{const _=s??Hu("common.column_selector"),x=c??Hu("common.required"),M=v.useMemo(()=>{if(!p||!Array.isArray(p)||p.length===0)return t;if(y){const V=t.findIndex(X=>X.field===y);if(V!==-1)return[...t.slice(0,V+1),...p,...t.slice(V+1)]}return[...t,...p]},[t,p,y]),[C,k]=v.useState(!1),N=v.useRef(null),S=v.useRef(null),[w,E]=v.useState(!1),[D,P]=v.useState(i),L=v.useMemo(()=>{try{const V=window.G7Core?.AuthManager?.getInstance();return V?V.getUser()?.uuid:void 0}catch{return}},[]),$=e?`g7_columns_${e}${L!==void 0?`_${L}`:""}`:null,A=()=>{if(!$)return null;try{const V=localStorage.getItem($);if(V){const X=JSON.parse(V);if(Array.isArray(X)&&X.length>0)return X}}catch(V){Vu.warn("Failed to load from localStorage:",V)}return null},O=V=>{if($)try{localStorage.setItem($,JSON.stringify(V))}catch(X){Vu.warn("Failed to save to localStorage:",X)}},T=()=>{const V=A();return V||(n&&n.length>0?n:f&&f.length>0?f:M.filter(X=>X.visible!==!1).map(X=>X.field))},[z,H]=v.useState(T);v.useEffect(()=>{w||(r&&z.length>0&&r(z),E(!0))},[w,r,z]),v.useEffect(()=>{w&&n&&n.length>0&&(A()||H(n))},[n,w]);const j=v.useRef([]),G=v.useRef(null);v.useEffect(()=>{if(w&&p&&p.length>0){const V=p.map(q=>q.field);if(G.current===null){const q=A();if(G.current=q!==null,q!==null){j.current=V;return}}const X=j.current,K=V.filter(q=>!X.includes(q));if(K.length>0){const q=n&&n.length>0?n:z,ne=K.filter(ee=>!q.includes(ee));if(ne.length>0){const ee=[...q,...ne];H(ee),O(ee),r?.(ee)}}j.current=V}},[p,w]);const U=n&&n.length>0?n:z;v.useEffect(()=>{const V=X=>{N.current&&!N.current.contains(X.target)&&k(!1)};return C&&document.addEventListener("mousedown",V),()=>{document.removeEventListener("mousedown",V)}},[C]),v.useEffect(()=>{if(C&&S.current&&N.current){const V=S.current.getBoundingClientRect(),X=N.current.getBoundingClientRect(),K=window.innerWidth,q=V.width||256;i==="right"?X.right-q<0?P("left"):P("right"):X.left+q>K?P("right"):P("left")}},[C,i]);const W=V=>{if(M.find(q=>q.field===V)?.required)return;const K=U.includes(V)?U.filter(q=>q!==V):[...U,V];H(K),O(K),r?.(K)},Y=D==="left"?"left-0":"right-0";return h.jsxs(I,{ref:N,className:`relative inline-block ${a}`,style:l,id:e,...b,children:[h.jsxs(ae,{onClick:()=>k(!C),className:mx()?.style?.mergeClasses?.("flex items-center gap-2 px-3 py-2 bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors",g)??`flex items-center gap-2 px-3 py-2 bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors ${g??""}`,children:[h.jsx(oe,{name:o,className:"w-5 h-5"}),_&&h.jsx(Q,{className:`${m||"hidden sm:inline"} text-sm`,children:_})]}),C&&h.jsxs(I,{ref:S,className:`absolute ${Y} z-50 mt-2 w-64 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg`,children:[(d||u)&&h.jsxs(I,{className:"px-4 pt-3 pb-2 border-b border-gray-200 dark:border-gray-700",children:[d&&h.jsx(Q,{className:"block text-sm font-medium text-gray-900 dark:text-white",children:d}),u&&h.jsx(Q,{className:"block text-xs text-gray-500 dark:text-gray-400 mt-1",children:u})]}),h.jsx(I,{className:"p-2 max-h-64 overflow-y-auto",children:M.map(V=>{const X=U.includes(V.field),K=V.required===!0;return h.jsxs(I,{onClick:()=>!K&&W(V.field),className:`flex items-center gap-2 px-3 py-2 rounded hover:bg-gray-50 dark:hover:bg-gray-700 ${K?"cursor-default":"cursor-pointer"}`,children:[h.jsx(En,{checked:X,disabled:K,readOnly:!0,className:"h-4 w-4 text-blue-600 rounded border-gray-300 dark:border-gray-600 pointer-events-none"}),h.jsx(Q,{className:"text-sm text-gray-700 dark:text-gray-300",children:V.header}),K&&x&&h.jsx(Q,{className:"ml-auto text-xs text-gray-400 dark:text-gray-500",children:x})]},V.field)})})]})]})},yx=window.G7Core?.createLogger?.("Comp:PageTransition")??{log:(...e)=>console.log("[Comp:PageTransition]",...e),warn:(...e)=>console.warn("[Comp:PageTransition]",...e),error:(...e)=>console.error("[Comp:PageTransition]",...e)},vx=({className:e="",style:t})=>{const[n,r]=v.useState(!1);return v.useEffect(()=>{const s=window.G7Core?.TransitionManager;if(!s){yx.warn("TransitionManager를 찾을 수 없습니다.");return}return r(s.getIsPending()),s.subscribe(i=>{r(i)})},[]),n?h.jsxs(I,{className:`fixed top-0 left-0 right-0 z-[999] ${e}`,style:t,role:"progressbar","aria-label":"페이지 로딩 중",children:[h.jsx(I,{className:"h-1 bg-gradient-to-r from-blue-500 via-blue-600 to-blue-500 animate-pulse",children:h.jsx(I,{className:"h-full bg-blue-700 animate-[loading_1.5s_ease-in-out_infinite]",style:{width:"30%",animation:"loading 1.5s ease-in-out infinite"}})}),h.jsx("style",{children:` + `,children:[h.jsx(re,{name:de.Moon,className:"w-5 h-5 text-gray-600 dark:text-gray-400"}),h.jsx(Q,{className:"flex-1 text-left text-gray-900 dark:text-white",children:s}),l==="dark"&&h.jsx(re,{name:de.Check,className:"w-4 h-4 text-blue-600 dark:text-blue-400 ml-auto"})]})]})})]})},Mu=window.G7Core?.createLogger?.("Comp:ColumnSelector")??{log:(...t)=>console.log("[Comp:ColumnSelector]",...t),warn:(...t)=>console.warn("[Comp:ColumnSelector]",...t),error:(...t)=>console.error("[Comp:ColumnSelector]",...t)},Nu=(t,e)=>window.G7Core?.t?.(t,e)??t,qv=()=>window.G7Core,Zv=({id:t,columns:e,visibleColumns:n,onColumnVisibilityChange:r,triggerLabel:s,triggerIconName:o=de.Sliders,position:i="right",className:a="",style:l,requiredText:c,menuTitle:d,menuDescription:u,defaultColumns:f,triggerClassName:g,labelClassName:m,dynamicColumns:p,dynamicColumnsInsertAfter:y,editorAttrs:b})=>{const _=s??Nu("common.column_selector"),x=c??Nu("common.required"),E=v.useMemo(()=>{if(!p||!Array.isArray(p)||p.length===0)return e;if(y){const V=e.findIndex(X=>X.field===y);if(V!==-1)return[...e.slice(0,V+1),...p,...e.slice(V+1)]}return[...e,...p]},[e,p,y]),[C,k]=v.useState(!1),N=v.useRef(null),S=v.useRef(null),[w,M]=v.useState(!1),[T,P]=v.useState(i),L=v.useMemo(()=>{try{const V=window.G7Core?.AuthManager?.getInstance();return V?V.getUser()?.uuid:void 0}catch{return}},[]),$=t?`g7_columns_${t}${L!==void 0?`_${L}`:""}`:null,A=()=>{if(!$)return null;try{const V=localStorage.getItem($);if(V){const X=JSON.parse(V);if(Array.isArray(X)&&X.length>0)return X}}catch(V){Mu.warn("Failed to load from localStorage:",V)}return null},D=V=>{if($)try{localStorage.setItem($,JSON.stringify(V))}catch(X){Mu.warn("Failed to save to localStorage:",X)}},O=()=>{const V=A();return V||(n&&n.length>0?n:f&&f.length>0?f:E.filter(X=>X.visible!==!1).map(X=>X.field))},[z,H]=v.useState(O);v.useEffect(()=>{w||(r&&z.length>0&&r(z),M(!0))},[w,r,z]),v.useEffect(()=>{w&&n&&n.length>0&&(A()||H(n))},[n,w]);const j=v.useRef([]),W=v.useRef(null);v.useEffect(()=>{if(w&&p&&p.length>0){const V=p.map(Z=>Z.field);if(W.current===null){const Z=A();if(W.current=Z!==null,Z!==null){j.current=V;return}}const X=j.current,K=V.filter(Z=>!X.includes(Z));if(K.length>0){const Z=n&&n.length>0?n:z,ee=K.filter(ne=>!Z.includes(ne));if(ee.length>0){const ne=[...Z,...ee];H(ne),D(ne),r?.(ne)}}j.current=V}},[p,w]);const U=n&&n.length>0?n:z;v.useEffect(()=>{const V=X=>{N.current&&!N.current.contains(X.target)&&k(!1)};return C&&document.addEventListener("mousedown",V),()=>{document.removeEventListener("mousedown",V)}},[C]),v.useEffect(()=>{if(C&&S.current&&N.current){const V=S.current.getBoundingClientRect(),X=N.current.getBoundingClientRect(),K=window.innerWidth,Z=V.width||256;i==="right"?X.right-Z<0?P("left"):P("right"):X.left+Z>K?P("right"):P("left")}},[C,i]);const B=V=>{if(E.find(Z=>Z.field===V)?.required)return;const K=U.includes(V)?U.filter(Z=>Z!==V):[...U,V];H(K),D(K),r?.(K)},G=T==="left"?"left-0":"right-0";return h.jsxs(I,{ref:N,className:`relative inline-block ${a}`,style:l,id:t,...b,children:[h.jsxs(oe,{onClick:()=>k(!C),className:qv()?.style?.mergeClasses?.("flex items-center gap-2 px-3 py-2 bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors",g)??`flex items-center gap-2 px-3 py-2 bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors ${g??""}`,children:[h.jsx(re,{name:o,className:"w-5 h-5"}),_&&h.jsx(Q,{className:`${m||"hidden sm:inline"} text-sm`,children:_})]}),C&&h.jsxs(I,{ref:S,className:`absolute ${G} z-50 mt-2 w-64 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg`,children:[(d||u)&&h.jsxs(I,{className:"px-4 pt-3 pb-2 border-b border-gray-200 dark:border-gray-700",children:[d&&h.jsx(Q,{className:"block text-sm font-medium text-gray-900 dark:text-white",children:d}),u&&h.jsx(Q,{className:"block text-xs text-gray-500 dark:text-gray-400 mt-1",children:u})]}),h.jsx(I,{className:"p-2 max-h-64 overflow-y-auto",children:E.map(V=>{const X=U.includes(V.field),K=V.required===!0;return h.jsxs(I,{onClick:()=>!K&&B(V.field),className:`flex items-center gap-2 px-3 py-2 rounded hover:bg-gray-50 dark:hover:bg-gray-700 ${K?"cursor-default":"cursor-pointer"}`,children:[h.jsx(vn,{checked:X,disabled:K,readOnly:!0,className:"h-4 w-4 text-blue-600 rounded border-gray-300 dark:border-gray-600 pointer-events-none"}),h.jsx(Q,{className:"text-sm text-gray-700 dark:text-gray-300",children:V.header}),K&&x&&h.jsx(Q,{className:"ml-auto text-xs text-gray-400 dark:text-gray-500",children:x})]},V.field)})})]})]})},Qv=window.G7Core?.createLogger?.("Comp:PageTransition")??{log:(...t)=>console.log("[Comp:PageTransition]",...t),warn:(...t)=>console.warn("[Comp:PageTransition]",...t),error:(...t)=>console.error("[Comp:PageTransition]",...t)},Jv=({className:t="",style:e})=>{const[n,r]=v.useState(!1);return v.useEffect(()=>{const s=window.G7Core?.TransitionManager;if(!s){Qv.warn("TransitionManager를 찾을 수 없습니다.");return}return r(s.getIsPending()),s.subscribe(i=>{r(i)})},[]),n?h.jsxs(I,{className:`fixed top-0 left-0 right-0 z-[999] ${t}`,style:e,role:"progressbar","aria-label":"페이지 로딩 중",children:[h.jsx(I,{className:"h-1 bg-gradient-to-r from-blue-500 via-blue-600 to-blue-500 animate-pulse",children:h.jsx(I,{className:"h-full bg-blue-700 animate-[loading_1.5s_ease-in-out_infinite]",style:{width:"30%",animation:"loading 1.5s ease-in-out infinite"}})}),h.jsx("style",{children:` @keyframes loading { 0% { transform: translateX(-100%); @@ -168,12 +167,12 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho transform: translateX(-100%); } } - `})]}):null},xx=(e,t)=>window.G7Core?.t?.(e,t)??e,wx=({options:e})=>{const t=document.documentElement.classList.contains("dark"),n=t?"rgb(17,24,39)":"rgb(249,250,251)",r=t?"#6b7280":"#9ca3af",s=t?"#9ca3af":"#6b7280";return h.jsxs("div",{style:{position:"absolute",inset:0,zIndex:2147483647,overflow:"hidden",background:n,display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"flex-start",paddingTop:"15%",gap:"12px"},children:[h.jsx("div",{style:{width:"32px",height:"32px",border:`3px solid ${r}`,borderTopColor:"transparent",borderRadius:"50%",animation:"g7-spin 0.8s linear infinite"}}),h.jsx("span",{style:{color:s,fontSize:"14px"},children:e?.text||xx("nav.loading")})]})};function Cr(e){"@babel/helpers - typeof";return Cr=typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?function(t){return typeof t}:function(t){return t&&typeof Symbol=="function"&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},Cr(e)}function kx(e,t){if(Cr(e)!="object"||!e)return e;var n=e[Symbol.toPrimitive];if(n!==void 0){var r=n.call(e,t);if(Cr(r)!="object")return r;throw new TypeError("@@toPrimitive must return a primitive value.")}return(t==="string"?String:Number)(e)}function Uu(e){var t=kx(e,"string");return Cr(t)=="symbol"?t:t+""}function Ws(e,t,n){return(t=Uu(t))in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function Bu(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(s){return Object.getOwnPropertyDescriptor(e,s).enumerable})),n.push.apply(n,r)}return n}function Ee(e){for(var t=1;te.length)&&(t=e.length);for(var n=0,r=Array(t);n0?Vt(os,--tn):0,ss--,Et===10&&(ss=1,ai--),Et}function dn(){return Et=tn2||Ks(Et)>3?"":" "}function Zx(e,t){for(;--t&&dn()&&!(Et<48||Et>102||Et>57&&Et<65||Et>70&&Et<97););return Xs(e,ci()+(t<6&&Un()==32&&dn()==32))}function Sl(e){for(;dn();)switch(Et){case e:return tn;case 34:case 39:e!==34&&e!==39&&Sl(Et);break;case 40:e===41&&Sl(e);break;case 92:dn();break}return tn}function Qx(e,t){for(;dn()&&e+Et!==57;)if(e+Et===84&&Un()===47)break;return"/*"+Xs(t,tn-1)+"*"+oi(e===47?e:dn())}function Jx(e){for(;!Ks(Un());)dn();return Xs(e,tn)}function Rx(e){return Ju(ui("",null,null,null,[""],e=Qu(e),0,[0],e))}function ui(e,t,n,r,s,o,i,a,l){for(var c=0,d=0,u=i,f=0,g=0,m=0,p=1,y=1,b=1,_=0,x="",M=s,C=o,k=r,N=x;y;)switch(m=_,_=dn()){case 40:if(m!=108&&Vt(N,u-1)==58){kl(N+=rt(di(_),"&","&\f"),"&\f")!=-1&&(b=-1);break}case 34:case 39:case 91:N+=di(_);break;case 9:case 10:case 13:case 32:N+=qx(m);break;case 92:N+=Zx(ci()-1,7);continue;case 47:switch(Un()){case 42:case 47:ii(ew(Qx(dn(),ci()),t,n),l);break;default:N+="/"}break;case 123*p:a[c++]=Hn(N)*b;case 125*p:case 59:case 0:switch(_){case 0:case 125:y=0;case 59+d:b==-1&&(N=rt(N,/\f/g,"")),g>0&&Hn(N)-u&&ii(g>32?ef(N+";",r,n,u-1):ef(rt(N," ","")+";",r,n,u-2),l);break;case 59:N+=";";default:if(ii(k=Ru(N,t,n,c,d,s,a,x,M=[],C=[],u),o),_===123)if(d===0)ui(N,t,k,k,M,o,u,a,C);else switch(f===99&&Vt(N,3)===110?100:f){case 100:case 108:case 109:case 115:ui(e,k,k,r&&ii(Ru(e,k,k,0,0,s,a,x,s,M=[],u),C),s,C,u,a,r?M:C);break;default:ui(N,k,k,k,[""],C,0,a,C)}}c=d=g=0,p=b=1,x=N="",u=i;break;case 58:u=1+Hn(N),g=m;default:if(p<1){if(_==123)--p;else if(_==125&&p++==0&&Kx()==125)continue}switch(N+=oi(_),_*p){case 38:b=d>0?1:(N+="\f",-1);break;case 44:a[c++]=(Hn(N)-1)*b,b=1;break;case 64:Un()===45&&(N+=di(dn())),f=Un(),d=u=Hn(x=N+=Jx(ci())),_++;break;case 45:m===45&&Hn(N)==2&&(p=0)}}return o}function Ru(e,t,n,r,s,o,i,a,l,c,d){for(var u=s-1,f=s===0?o:[""],g=Cl(f),m=0,p=0,y=0;m0?f[b]+" "+_:rt(_,/&\f/g,f[b])))&&(l[y++]=x);return li(e,t,n,s===0?xl:a,l,c,d)}function ew(e,t,n){return li(e,t,n,Xu,oi(Xx()),Gs(e,2,-2),0)}function ef(e,t,n,r){return li(e,t,n,wl,Gs(e,0,r),Gs(e,r+1,-1),r)}function is(e,t){for(var n="",r=Cl(e),s=0;s6)switch(Vt(e,t+1)){case 109:if(Vt(e,t+4)!==45)break;case 102:return rt(e,/(.+:)(.+)-([^]+)/,"$1"+nt+"$2-$3$1"+si+(Vt(e,t+3)==108?"$3":"$2-$3"))+e;case 115:return~kl(e,"stretch")?nf(rt(e,"stretch","fill-available"),t)+e:e}break;case 4949:if(Vt(e,t+1)!==115)break;case 6444:switch(Vt(e,Hn(e)-3-(~kl(e,"!important")&&10))){case 107:return rt(e,":",":"+nt)+e;case 101:return rt(e,/(.+:)([^;!]+)(;|!.+)?/,"$1"+nt+(Vt(e,14)===45?"inline-":"")+"box$3$1"+nt+"$2$3$1"+jt+"$2box$3")+e}break;case 5936:switch(Vt(e,t+11)){case 114:return nt+e+jt+rt(e,/[svh]\w+-[tblr]{2}/,"tb")+e;case 108:return nt+e+jt+rt(e,/[svh]\w+-[tblr]{2}/,"tb-rl")+e;case 45:return nt+e+jt+rt(e,/[svh]\w+-[tblr]{2}/,"lr")+e}return nt+e+jt+e+e}return e}var dw=function(t,n,r,s){if(t.length>-1&&!t.return)switch(t.type){case wl:t.return=nf(t.value,t.length);break;case Ku:return is([Ys(t,{value:rt(t.value,"@","@"+nt)})],s);case xl:if(t.length)return Yx(t.props,function(o){switch(Gx(o,/(::plac\w+|:read-\w+)/)){case":read-only":case":read-write":return is([Ys(t,{props:[rt(o,/:(read-\w+)/,":"+si+"$1")]})],s);case"::placeholder":return is([Ys(t,{props:[rt(o,/:(plac\w+)/,":"+nt+"input-$1")]}),Ys(t,{props:[rt(o,/:(plac\w+)/,":"+si+"$1")]}),Ys(t,{props:[rt(o,/:(plac\w+)/,jt+"input-$1")]})],s)}return""})}},uw=[dw],fw=function(t){var n=t.key;if(n==="css"){var r=document.querySelectorAll("style[data-emotion]:not([data-s])");Array.prototype.forEach.call(r,function(p){var y=p.getAttribute("data-emotion");y.indexOf(" ")!==-1&&(document.head.appendChild(p),p.setAttribute("data-s",""))})}var s=t.stylisPlugins||uw,o={},i,a=[];i=t.container||document.head,Array.prototype.forEach.call(document.querySelectorAll('style[data-emotion^="'+n+' "]'),function(p){for(var y=p.getAttribute("data-emotion").split(" "),b=1;b=4;++r,s-=4)n=e.charCodeAt(r)&255|(e.charCodeAt(++r)&255)<<8|(e.charCodeAt(++r)&255)<<16|(e.charCodeAt(++r)&255)<<24,n=(n&65535)*1540483477+((n>>>16)*59797<<16),n^=n>>>24,t=(n&65535)*1540483477+((n>>>16)*59797<<16)^(t&65535)*1540483477+((t>>>16)*59797<<16);switch(s){case 3:t^=(e.charCodeAt(r+2)&255)<<16;case 2:t^=(e.charCodeAt(r+1)&255)<<8;case 1:t^=e.charCodeAt(r)&255,t=(t&65535)*1540483477+((t>>>16)*59797<<16)}return t^=t>>>13,t=(t&65535)*1540483477+((t>>>16)*59797<<16),((t^t>>>15)>>>0).toString(36)}var xw={animationIterationCount:1,aspectRatio:1,borderImageOutset:1,borderImageSlice:1,borderImageWidth:1,boxFlex:1,boxFlexGroup:1,boxOrdinalGroup:1,columnCount:1,columns:1,flex:1,flexGrow:1,flexPositive:1,flexShrink:1,flexNegative:1,flexOrder:1,gridRow:1,gridRowEnd:1,gridRowSpan:1,gridRowStart:1,gridColumn:1,gridColumnEnd:1,gridColumnSpan:1,gridColumnStart:1,msGridRow:1,msGridRowSpan:1,msGridColumn:1,msGridColumnSpan:1,fontWeight:1,lineHeight:1,opacity:1,order:1,orphans:1,scale:1,tabSize:1,widows:1,zIndex:1,zoom:1,WebkitLineClamp:1,fillOpacity:1,floodOpacity:1,stopOpacity:1,strokeDasharray:1,strokeDashoffset:1,strokeMiterlimit:1,strokeOpacity:1,strokeWidth:1},ww=/[A-Z]|^ms/g,kw=/_EMO_([^_]+?)_([^]*?)_EMO_/g,lf=function(t){return t.charCodeAt(1)===45},cf=function(t){return t!=null&&typeof t!="boolean"},Ml=sw(function(e){return lf(e)?e:e.replace(ww,"-$&").toLowerCase()}),df=function(t,n){switch(t){case"animation":case"animationName":if(typeof n=="string")return n.replace(kw,function(r,s,o){return Bn={name:s,styles:o,next:Bn},s})}return xw[t]!==1&&!lf(t)&&typeof n=="number"&&n!==0?n+"px":n};function qs(e,t,n){if(n==null)return"";var r=n;if(r.__emotion_styles!==void 0)return r;switch(typeof n){case"boolean":return"";case"object":{var s=n;if(s.anim===1)return Bn={name:s.name,styles:s.styles,next:Bn},s.name;var o=n;if(o.styles!==void 0){var i=o.next;if(i!==void 0)for(;i!==void 0;)Bn={name:i.name,styles:i.styles,next:Bn},i=i.next;var a=o.styles+";";return a}return Cw(e,t,n)}case"function":{if(e!==void 0){var l=Bn,c=n(e);return Bn=l,qs(e,t,c)}break}}var d=n;return d}function Cw(e,t,n){var r="";if(Array.isArray(n))for(var s=0;s({x:e,y:e});function Fw(e){const{x:t,y:n,width:r,height:s}=e;return{width:r,height:s,top:n,left:t,right:t+r,bottom:n+s,x:t,y:n}}function gi(){return typeof window<"u"}function gf(e){return mf(e)?(e.nodeName||"").toLowerCase():"#document"}function Sr(e){var t;return(e==null||(t=e.ownerDocument)==null?void 0:t.defaultView)||window}function pf(e){var t;return(t=(mf(e)?e.ownerDocument:e.document)||window.document)==null?void 0:t.documentElement}function mf(e){return gi()?e instanceof Node||e instanceof Sr(e).Node:!1}function zw(e){return gi()?e instanceof Element||e instanceof Sr(e).Element:!1}function Dl(e){return gi()?e instanceof HTMLElement||e instanceof Sr(e).HTMLElement:!1}function bf(e){return!gi()||typeof ShadowRoot>"u"?!1:e instanceof ShadowRoot||e instanceof Sr(e).ShadowRoot}function yf(e){const{overflow:t,overflowX:n,overflowY:r,display:s}=vf(e);return/auto|scroll|overlay|hidden|clip/.test(t+r+n)&&s!=="inline"&&s!=="contents"}function Vw(e){return/^(html|body|#document)$/.test(gf(e))}function vf(e){return Sr(e).getComputedStyle(e)}function Hw(e){if(gf(e)==="html")return e;const t=e.assignedSlot||e.parentNode||bf(e)&&e.host||pf(e);return bf(t)?t.host:t}function xf(e){const t=Hw(e);return Vw(t)?(e.ownerDocument||e).body:Dl(t)&&yf(t)?t:xf(t)}function pi(e,t,n){var r;t===void 0&&(t=[]),n===void 0&&(n=!0);const s=xf(e),o=s===((r=e.ownerDocument)==null?void 0:r.body),i=Sr(s);if(o){const a=Uw(i);return t.concat(i,i.visualViewport||[],yf(s)?s:[],a&&n?pi(a):[])}else return t.concat(s,pi(s,[],n))}function Uw(e){return e.parent&&Object.getPrototypeOf(e.parent)?e.frameElement:null}function Bw(e){const t=vf(e);let n=parseFloat(t.width)||0,r=parseFloat(t.height)||0;const s=Dl(e),o=s?e.offsetWidth:n,i=s?e.offsetHeight:r,a=fi(n)!==o||fi(r)!==i;return a&&(n=o,r=i),{width:n,height:r,$:a}}function Il(e){return zw(e)?e:e.contextElement}function jw(e){const t=Il(e);if(!Dl(t))return Al(1);const n=t.getBoundingClientRect(),{width:r,height:s,$:o}=Bw(t);let i=(o?fi(n.width):n.width)/r,a=(o?fi(n.height):n.height)/s;return(!i||!Number.isFinite(i))&&(i=1),(!a||!Number.isFinite(a))&&(a=1),{x:i,y:a}}function wf(e,t,n,r){t===void 0&&(t=!1);const s=e.getBoundingClientRect();Il(e);let o=Al(1);t&&(o=jw(e));const i=Al(0);let a=(s.left+i.x)/o.x,l=(s.top+i.y)/o.y,c=s.width/o.x,d=s.height/o.y;return Fw({width:c,height:d,x:a,y:l})}function kf(e,t){return e.x===t.x&&e.y===t.y&&e.width===t.width&&e.height===t.height}function Ww(e,t,n){let r=null,s;const o=pf(e);function i(){var d;clearTimeout(s),(d=r)==null||d.disconnect(),r=null}function a(d,u){d===void 0&&(d=!1),u===void 0&&(u=1),i();const f=e.getBoundingClientRect(),{left:g,top:m,width:p,height:y}=f;if(d||t(),!p||!y)return;const b=hi(m),_=hi(o.clientWidth-(g+p)),x=hi(o.clientHeight-(m+y)),M=hi(g),k={rootMargin:-b+"px "+-_+"px "+-x+"px "+-M+"px",threshold:$w(0,Pw(1,u))||1};let N=!0;function S(w){const E=w[0].intersectionRatio;if(!kf(f,e.getBoundingClientRect()))return a();if(E!==u){if(!N)return a();E?a(!1,E):s=setTimeout(()=>{a(!1,1e-7)},1e3)}N=!1}try{r=new IntersectionObserver(S,{...k,root:o.ownerDocument})}catch{r=new IntersectionObserver(S,k)}r.observe(e)}const l=Sr(e),c=()=>a(n);return l.addEventListener("resize",c),a(!0),()=>{l.removeEventListener("resize",c),i()}}function Gw(e,t,n,r){r===void 0&&(r={});const{ancestorScroll:s=!0,ancestorResize:o=!0,elementResize:i=typeof ResizeObserver=="function",layoutShift:a=typeof IntersectionObserver=="function",animationFrame:l=!1}=r,c=Il(e),d=s||o?[...c?pi(c):[],...t?pi(t):[]]:[];d.forEach(b=>{s&&b.addEventListener("scroll",n),o&&b.addEventListener("resize",n)});const u=c&&a?Ww(c,n,o):null;let f=-1,g=null;i&&(g=new ResizeObserver(b=>{let[_]=b;_&&_.target===c&&g&&t&&(g.unobserve(t),cancelAnimationFrame(f),f=requestAnimationFrame(()=>{var x;(x=g)==null||x.observe(t)})),n()}),c&&!l&&g.observe(c),t&&g.observe(t));let m,p=l?wf(e):null;l&&y();function y(){const b=wf(e);p&&!kf(p,b)&&n(),p=b,m=requestAnimationFrame(y)}return n(),()=>{var b;d.forEach(_=>{s&&_.removeEventListener("scroll",n),o&&_.removeEventListener("resize",n)}),u?.(),(b=g)==null||b.disconnect(),g=null,l&&cancelAnimationFrame(m)}}var Ll=v.useLayoutEffect,Yw=["className","clearValue","cx","getStyles","getClassNames","getValue","hasValue","isMulti","isRtl","options","selectOption","selectProps","setValue","theme"],mi=function(){};function Xw(e,t){return t?t[0]==="-"?e+t:e+"__"+t:e}function Kw(e,t){for(var n=arguments.length,r=new Array(n>2?n-2:0),s=2;s-1}function qw(e){return yi(e)?window.innerHeight:e.clientHeight}function Sf(e){return yi(e)?window.pageYOffset:e.scrollTop}function vi(e,t){if(yi(e)){window.scrollTo(0,t);return}e.scrollTop=t}function Zw(e){var t=getComputedStyle(e),n=t.position==="absolute",r=/(auto|scroll)/;if(t.position==="fixed")return document.documentElement;for(var s=e;s=s.parentElement;)if(t=getComputedStyle(s),!(n&&t.position==="static")&&r.test(t.overflow+t.overflowY+t.overflowX))return s;return document.documentElement}function Qw(e,t,n,r){return n*((e=e/r-1)*e*e+1)+t}function xi(e,t){var n=arguments.length>2&&arguments[2]!==void 0?arguments[2]:200,r=arguments.length>3&&arguments[3]!==void 0?arguments[3]:mi,s=Sf(e),o=t-s,i=10,a=0;function l(){a+=i;var c=Qw(a,s,o,n);vi(e,c),an.bottom?vi(e,Math.min(t.offsetTop+t.clientHeight-e.offsetHeight+s,e.scrollHeight)):r.top-s1?n-1:0),s=1;s=m)return{placement:"bottom",maxHeight:t};if(w>=m&&!i)return o&&xi(l,E,P),{placement:"bottom",maxHeight:t};if(!i&&w>=r||i&&N>=r){o&&xi(l,E,P);var L=i?N-M:w-M;return{placement:"bottom",maxHeight:L}}if(s==="auto"||i){var $=t,A=i?k:S;return A>=r&&($=Math.min(A-M-a,t)),{placement:"top",maxHeight:$}}if(s==="bottom")return o&&vi(l,E),{placement:"bottom",maxHeight:t};break;case"top":if(k>=m)return{placement:"top",maxHeight:t};if(S>=m&&!i)return o&&xi(l,D,P),{placement:"top",maxHeight:t};if(!i&&S>=r||i&&k>=r){var O=t;return(!i&&S>=r||i&&k>=r)&&(O=i?k-C:S-C),o&&xi(l,D,P),{placement:"top",maxHeight:O}}return{placement:"bottom",maxHeight:t};default:throw new Error('Invalid placement provided "'.concat(s,'".'))}return c}function l1(e){var t={bottom:"top",top:"bottom"};return e?t[e]:"bottom"}var Nf=function(t){return t==="auto"?"bottom":t},c1=function(t,n){var r,s=t.placement,o=t.theme,i=o.borderRadius,a=o.spacing,l=o.colors;return Ee((r={label:"menu"},Ws(r,l1(s),"100%"),Ws(r,"position","absolute"),Ws(r,"width","100%"),Ws(r,"zIndex",1),r),n?{}:{backgroundColor:l.neutral0,borderRadius:i,boxShadow:"0 0 0 1px hsla(0, 0%, 0%, 0.1), 0 4px 11px hsla(0, 0%, 0%, 0.1)",marginBottom:a.menuGutter,marginTop:a.menuGutter})},Tf=v.createContext(null),d1=function(t){var n=t.children,r=t.minMenuHeight,s=t.maxMenuHeight,o=t.menuPlacement,i=t.menuPosition,a=t.menuShouldScrollIntoView,l=t.theme,c=v.useContext(Tf)||{},d=c.setPortalPlacement,u=v.useRef(null),f=v.useState(s),g=Qn(f,2),m=g[0],p=g[1],y=v.useState(null),b=Qn(y,2),_=b[0],x=b[1],M=l.spacing.controlHeight;return Ll(function(){var C=u.current;if(C){var k=i==="fixed",N=a&&!k,S=a1({maxHeight:s,menuEl:C,minHeight:r,placement:o,shouldScroll:N,isFixedPosition:k,controlHeight:M});p(S.maxHeight),x(S.placement),d?.(S.placement)}},[s,o,i,a,r,d,M]),n({ref:u,placerProps:Ee(Ee({},t),{},{placement:_||Nf(o),maxHeight:m})})},u1=function(t){var n=t.children,r=t.innerRef,s=t.innerProps;return Se("div",Oe({},wt(t,"menu",{menu:!0}),{ref:r},s),n)},f1=u1,h1=function(t,n){var r=t.maxHeight,s=t.theme.spacing.baseUnit;return Ee({maxHeight:r,overflowY:"auto",position:"relative",WebkitOverflowScrolling:"touch"},n?{}:{paddingBottom:s,paddingTop:s})},g1=function(t){var n=t.children,r=t.innerProps,s=t.innerRef,o=t.isMulti;return Se("div",Oe({},wt(t,"menuList",{"menu-list":!0,"menu-list--is-multi":o}),{ref:s},r),n)},Of=function(t,n){var r=t.theme,s=r.spacing.baseUnit,o=r.colors;return Ee({textAlign:"center"},n?{}:{color:o.neutral40,padding:"".concat(s*2,"px ").concat(s*3,"px")})},p1=Of,m1=Of,b1=function(t){var n=t.children,r=n===void 0?"No options":n,s=t.innerProps,o=Vn(t,o1);return Se("div",Oe({},wt(Ee(Ee({},o),{},{children:r,innerProps:s}),"noOptionsMessage",{"menu-notice":!0,"menu-notice--no-options":!0}),s),r)},y1=function(t){var n=t.children,r=n===void 0?"Loading...":n,s=t.innerProps,o=Vn(t,i1);return Se("div",Oe({},wt(Ee(Ee({},o),{},{children:r,innerProps:s}),"loadingMessage",{"menu-notice":!0,"menu-notice--loading":!0}),s),r)},v1=function(t){var n=t.rect,r=t.offset,s=t.position;return{left:n.left,position:s,top:r,width:n.width,zIndex:1}},x1=function(t){var n=t.appendTo,r=t.children,s=t.controlElement,o=t.innerProps,i=t.menuPlacement,a=t.menuPosition,l=v.useRef(null),c=v.useRef(null),d=v.useState(Nf(i)),u=Qn(d,2),f=u[0],g=u[1],m=v.useMemo(function(){return{setPortalPlacement:g}},[]),p=v.useState(null),y=Qn(p,2),b=y[0],_=y[1],x=v.useCallback(function(){if(s){var N=Jw(s),S=a==="fixed"?0:window.pageYOffset,w=N[f]+S;(w!==b?.offset||N.left!==b?.rect.left||N.width!==b?.rect.width)&&_({offset:w,rect:N})}},[s,a,f,b?.offset,b?.rect.left,b?.rect.width]);Ll(function(){x()},[x]);var M=v.useCallback(function(){typeof c.current=="function"&&(c.current(),c.current=null),s&&l.current&&(c.current=Gw(s,l.current,x,{elementResize:"ResizeObserver"in window}))},[s,x]);Ll(function(){M()},[M]);var C=v.useCallback(function(N){l.current=N,M()},[M]);if(!n&&a!=="fixed"||!b)return null;var k=Se("div",Oe({ref:C},wt(Ee(Ee({},t),{},{offset:b.offset,position:a,rect:b.rect}),"menuPortal",{"menu-portal":!0}),o),r);return Se(Tf.Provider,{value:m},n?$n.createPortal(k,n):k)},w1=function(t){var n=t.isDisabled,r=t.isRtl;return{label:"container",direction:r?"rtl":void 0,pointerEvents:n?"none":void 0,position:"relative"}},k1=function(t){var n=t.children,r=t.innerProps,s=t.isDisabled,o=t.isRtl;return Se("div",Oe({},wt(t,"container",{"--is-disabled":s,"--is-rtl":o}),r),n)},C1=function(t,n){var r=t.theme.spacing,s=t.isMulti,o=t.hasValue,i=t.selectProps.controlShouldRenderValue;return Ee({alignItems:"center",display:s&&o&&i?"flex":"grid",flex:1,flexWrap:"wrap",WebkitOverflowScrolling:"touch",position:"relative",overflow:"hidden"},n?{}:{padding:"".concat(r.baseUnit/2,"px ").concat(r.baseUnit*2,"px")})},S1=function(t){var n=t.children,r=t.innerProps,s=t.isMulti,o=t.hasValue;return Se("div",Oe({},wt(t,"valueContainer",{"value-container":!0,"value-container--is-multi":s,"value-container--has-value":o}),r),n)},_1=function(){return{alignItems:"center",alignSelf:"stretch",display:"flex",flexShrink:0}},E1=function(t){var n=t.children,r=t.innerProps;return Se("div",Oe({},wt(t,"indicatorsContainer",{indicators:!0}),r),n)},Af,M1=["size"],N1=["innerProps","isRtl","size"],T1={name:"8mmkcg",styles:"display:inline-block;fill:currentColor;line-height:1;stroke:currentColor;stroke-width:0"},Df=function(t){var n=t.size,r=Vn(t,M1);return Se("svg",Oe({height:n,width:n,viewBox:"0 0 20 20","aria-hidden":"true",focusable:"false",css:T1},r))},Pl=function(t){return Se(Df,Oe({size:20},t),Se("path",{d:"M14.348 14.849c-0.469 0.469-1.229 0.469-1.697 0l-2.651-3.030-2.651 3.029c-0.469 0.469-1.229 0.469-1.697 0-0.469-0.469-0.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-0.469-0.469-0.469-1.228 0-1.697s1.228-0.469 1.697 0l2.652 3.031 2.651-3.031c0.469-0.469 1.228-0.469 1.697 0s0.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c0.469 0.469 0.469 1.229 0 1.698z"}))},If=function(t){return Se(Df,Oe({size:20},t),Se("path",{d:"M4.516 7.548c0.436-0.446 1.043-0.481 1.576 0l3.908 3.747 3.908-3.747c0.533-0.481 1.141-0.446 1.574 0 0.436 0.445 0.408 1.197 0 1.615-0.406 0.418-4.695 4.502-4.695 4.502-0.217 0.223-0.502 0.335-0.787 0.335s-0.57-0.112-0.789-0.335c0 0-4.287-4.084-4.695-4.502s-0.436-1.17 0-1.615z"}))},Lf=function(t,n){var r=t.isFocused,s=t.theme,o=s.spacing.baseUnit,i=s.colors;return Ee({label:"indicatorContainer",display:"flex",transition:"color 150ms"},n?{}:{color:r?i.neutral60:i.neutral20,padding:o*2,":hover":{color:r?i.neutral80:i.neutral40}})},O1=Lf,A1=function(t){var n=t.children,r=t.innerProps;return Se("div",Oe({},wt(t,"dropdownIndicator",{indicator:!0,"dropdown-indicator":!0}),r),n||Se(If,null))},D1=Lf,I1=function(t){var n=t.children,r=t.innerProps;return Se("div",Oe({},wt(t,"clearIndicator",{indicator:!0,"clear-indicator":!0}),r),n||Se(Pl,null))},L1=function(t,n){var r=t.isDisabled,s=t.theme,o=s.spacing.baseUnit,i=s.colors;return Ee({label:"indicatorSeparator",alignSelf:"stretch",width:1},n?{}:{backgroundColor:r?i.neutral10:i.neutral20,marginBottom:o*2,marginTop:o*2})},P1=function(t){var n=t.innerProps;return Se("span",Oe({},n,wt(t,"indicatorSeparator",{"indicator-separator":!0})))},$1=Iw(Af||(Af=Lw([` + `})]}):null},Rv=(t,e)=>window.G7Core?.t?.(t,e)??t,ex=({options:t})=>{const e=document.documentElement.classList.contains("dark"),n=e?"rgb(17,24,39)":"rgb(249,250,251)",r=e?"#6b7280":"#9ca3af",s=e?"#9ca3af":"#6b7280";return h.jsxs("div",{style:{position:"absolute",inset:0,zIndex:2147483647,overflow:"hidden",background:n,display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"flex-start",paddingTop:"15%",gap:"12px"},children:[h.jsx("div",{style:{width:"32px",height:"32px",border:`3px solid ${r}`,borderTopColor:"transparent",borderRadius:"50%",animation:"g7-spin 0.8s linear infinite"}}),h.jsx("span",{style:{color:s,fontSize:"14px"},children:t?.text||Rv("nav.loading")})]})};function gr(t){"@babel/helpers - typeof";return gr=typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?function(e){return typeof e}:function(e){return e&&typeof Symbol=="function"&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},gr(t)}function tx(t,e){if(gr(t)!="object"||!t)return t;var n=t[Symbol.toPrimitive];if(n!==void 0){var r=n.call(t,e);if(gr(r)!="object")return r;throw new TypeError("@@toPrimitive must return a primitive value.")}return(e==="string"?String:Number)(t)}function Ou(t){var e=tx(t,"string");return gr(e)=="symbol"?e:e+""}function Ds(t,e,n){return(e=Ou(e))in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t}function Tu(t,e){var n=Object.keys(t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(t);e&&(r=r.filter(function(s){return Object.getOwnPropertyDescriptor(t,s).enumerable})),n.push.apply(n,r)}return n}function Me(t){for(var e=1;et.length)&&(e=t.length);for(var n=0,r=Array(e);n0?Pt(jr,--Qt):0,Br--,Ct===10&&(Br=1,Jo--),Ct}function an(){return Ct=Qt2||$s(Ct)>3?"":" "}function Ox(t,e){for(;--e&&an()&&!(Ct<48||Ct>102||Ct>57&&Ct<65||Ct>70&&Ct<97););return Ps(t,ei()+(e<6&&In()==32&&an()==32))}function pl(t){for(;an();)switch(Ct){case t:return Qt;case 34:case 39:t!==34&&t!==39&&pl(Ct);break;case 40:t===41&&pl(t);break;case 92:an();break}return Qt}function Tx(t,e){for(;an()&&t+Ct!==57;)if(t+Ct===84&&In()===47)break;return"/*"+Ps(e,Qt-1)+"*"+Zo(t===47?t:an())}function Ax(t){for(;!$s(In());)an();return Ps(t,Qt)}function Dx(t){return Hu(ni("",null,null,null,[""],t=Vu(t),0,[0],t))}function ni(t,e,n,r,s,o,i,a,l){for(var c=0,d=0,u=i,f=0,g=0,m=0,p=1,y=1,b=1,_=0,x="",E=s,C=o,k=r,N=x;y;)switch(m=_,_=an()){case 40:if(m!=108&&Pt(N,u-1)==58){hl(N+=tt(ti(_),"&","&\f"),"&\f")!=-1&&(b=-1);break}case 34:case 39:case 91:N+=ti(_);break;case 9:case 10:case 13:case 32:N+=Nx(m);break;case 92:N+=Ox(ei()-1,7);continue;case 47:switch(In()){case 42:case 47:Qo(Ix(Tx(an(),ei()),e,n),l);break;default:N+="/"}break;case 123*p:a[c++]=Dn(N)*b;case 125*p:case 59:case 0:switch(_){case 0:case 125:y=0;case 59+d:b==-1&&(N=tt(N,/\f/g,"")),g>0&&Dn(N)-u&&Qo(g>32?Bu(N+";",r,n,u-1):Bu(tt(N," ","")+";",r,n,u-2),l);break;case 59:N+=";";default:if(Qo(k=Uu(N,e,n,c,d,s,a,x,E=[],C=[],u),o),_===123)if(d===0)ni(N,e,k,k,E,o,u,a,C);else switch(f===99&&Pt(N,3)===110?100:f){case 100:case 108:case 109:case 115:ni(t,k,k,r&&Qo(Uu(t,k,k,0,0,s,a,x,s,E=[],u),C),s,C,u,a,r?E:C);break;default:ni(N,k,k,k,[""],C,0,a,C)}}c=d=g=0,p=b=1,x=N="",u=i;break;case 58:u=1+Dn(N),g=m;default:if(p<1){if(_==123)--p;else if(_==125&&p++==0&&Mx()==125)continue}switch(N+=Zo(_),_*p){case 38:b=d>0?1:(N+="\f",-1);break;case 44:a[c++]=(Dn(N)-1)*b,b=1;break;case 64:In()===45&&(N+=ti(an())),f=In(),d=u=Dn(x=N+=Ax(ei())),_++;break;case 45:m===45&&Dn(N)==2&&(p=0)}}return o}function Uu(t,e,n,r,s,o,i,a,l,c,d){for(var u=s-1,f=s===0?o:[""],g=gl(f),m=0,p=0,y=0;m0?f[b]+" "+_:tt(_,/&\f/g,f[b])))&&(l[y++]=x);return Ro(t,e,n,s===0?ul:a,l,c,d)}function Ix(t,e,n){return Ro(t,e,n,Pu,Zo(Ex()),Is(t,2,-2),0)}function Bu(t,e,n,r){return Ro(t,e,n,fl,Is(t,0,r),Is(t,r+1,-1),r)}function Wr(t,e){for(var n="",r=gl(t),s=0;s6)switch(Pt(t,e+1)){case 109:if(Pt(t,e+4)!==45)break;case 102:return tt(t,/(.+:)(.+)-([^]+)/,"$1"+et+"$2-$3$1"+qo+(Pt(t,e+3)==108?"$3":"$2-$3"))+t;case 115:return~hl(t,"stretch")?Wu(tt(t,"stretch","fill-available"),e)+t:t}break;case 4949:if(Pt(t,e+1)!==115)break;case 6444:switch(Pt(t,Dn(t)-3-(~hl(t,"!important")&&10))){case 107:return tt(t,":",":"+et)+t;case 101:return tt(t,/(.+:)([^;!]+)(;|!.+)?/,"$1"+et+(Pt(t,14)===45?"inline-":"")+"box$3$1"+et+"$2$3$1"+Ft+"$2box$3")+t}break;case 5936:switch(Pt(t,e+11)){case 114:return et+t+Ft+tt(t,/[svh]\w+-[tblr]{2}/,"tb")+t;case 108:return et+t+Ft+tt(t,/[svh]\w+-[tblr]{2}/,"tb-rl")+t;case 45:return et+t+Ft+tt(t,/[svh]\w+-[tblr]{2}/,"lr")+t}return et+t+Ft+t+t}return t}var jx=function(e,n,r,s){if(e.length>-1&&!e.return)switch(e.type){case fl:e.return=Wu(e.value,e.length);break;case $u:return Wr([Ls(e,{value:tt(e.value,"@","@"+et)})],s);case ul:if(e.length)return _x(e.props,function(o){switch(Sx(o,/(::plac\w+|:read-\w+)/)){case":read-only":case":read-write":return Wr([Ls(e,{props:[tt(o,/:(read-\w+)/,":"+qo+"$1")]})],s);case"::placeholder":return Wr([Ls(e,{props:[tt(o,/:(plac\w+)/,":"+et+"input-$1")]}),Ls(e,{props:[tt(o,/:(plac\w+)/,":"+qo+"$1")]}),Ls(e,{props:[tt(o,/:(plac\w+)/,Ft+"input-$1")]})],s)}return""})}},Wx=[jx],Gx=function(e){var n=e.key;if(n==="css"){var r=document.querySelectorAll("style[data-emotion]:not([data-s])");Array.prototype.forEach.call(r,function(p){var y=p.getAttribute("data-emotion");y.indexOf(" ")!==-1&&(document.head.appendChild(p),p.setAttribute("data-s",""))})}var s=e.stylisPlugins||Wx,o={},i,a=[];i=e.container||document.head,Array.prototype.forEach.call(document.querySelectorAll('style[data-emotion^="'+n+' "]'),function(p){for(var y=p.getAttribute("data-emotion").split(" "),b=1;b=4;++r,s-=4)n=t.charCodeAt(r)&255|(t.charCodeAt(++r)&255)<<8|(t.charCodeAt(++r)&255)<<16|(t.charCodeAt(++r)&255)<<24,n=(n&65535)*1540483477+((n>>>16)*59797<<16),n^=n>>>24,e=(n&65535)*1540483477+((n>>>16)*59797<<16)^(e&65535)*1540483477+((e>>>16)*59797<<16);switch(s){case 3:e^=(t.charCodeAt(r+2)&255)<<16;case 2:e^=(t.charCodeAt(r+1)&255)<<8;case 1:e^=t.charCodeAt(r)&255,e=(e&65535)*1540483477+((e>>>16)*59797<<16)}return e^=e>>>13,e=(e&65535)*1540483477+((e>>>16)*59797<<16),((e^e>>>15)>>>0).toString(36)}var Rx={animationIterationCount:1,aspectRatio:1,borderImageOutset:1,borderImageSlice:1,borderImageWidth:1,boxFlex:1,boxFlexGroup:1,boxOrdinalGroup:1,columnCount:1,columns:1,flex:1,flexGrow:1,flexPositive:1,flexShrink:1,flexNegative:1,flexOrder:1,gridRow:1,gridRowEnd:1,gridRowSpan:1,gridRowStart:1,gridColumn:1,gridColumnEnd:1,gridColumnSpan:1,gridColumnStart:1,msGridRow:1,msGridRowSpan:1,msGridColumn:1,msGridColumnSpan:1,fontWeight:1,lineHeight:1,opacity:1,order:1,orphans:1,scale:1,tabSize:1,widows:1,zIndex:1,zoom:1,WebkitLineClamp:1,fillOpacity:1,floodOpacity:1,stopOpacity:1,strokeDasharray:1,strokeDashoffset:1,strokeMiterlimit:1,strokeOpacity:1,strokeWidth:1},ew=/[A-Z]|^ms/g,tw=/_EMO_([^_]+?)_([^]*?)_EMO_/g,qu=function(e){return e.charCodeAt(1)===45},Zu=function(e){return e!=null&&typeof e!="boolean"},yl=Fx(function(t){return qu(t)?t:t.replace(ew,"-$&").toLowerCase()}),Qu=function(e,n){switch(e){case"animation":case"animationName":if(typeof n=="string")return n.replace(tw,function(r,s,o){return Ln={name:s,styles:o,next:Ln},s})}return Rx[e]!==1&&!qu(e)&&typeof n=="number"&&n!==0?n+"px":n};function Fs(t,e,n){if(n==null)return"";var r=n;if(r.__emotion_styles!==void 0)return r;switch(typeof n){case"boolean":return"";case"object":{var s=n;if(s.anim===1)return Ln={name:s.name,styles:s.styles,next:Ln},s.name;var o=n;if(o.styles!==void 0){var i=o.next;if(i!==void 0)for(;i!==void 0;)Ln={name:i.name,styles:i.styles,next:Ln},i=i.next;var a=o.styles+";";return a}return nw(t,e,n)}case"function":{if(t!==void 0){var l=Ln,c=n(t);return Ln=l,Fs(t,e,c)}break}}var d=n;return d}function nw(t,e,n){var r="";if(Array.isArray(n))for(var s=0;s({x:t,y:t});function mw(t){const{x:e,y:n,width:r,height:s}=t;return{width:r,height:s,top:n,left:e,right:e+r,bottom:n+s,x:e,y:n}}function ii(){return typeof window<"u"}function tf(t){return rf(t)?(t.nodeName||"").toLowerCase():"#document"}function Wn(t){var e;return(t==null||(e=t.ownerDocument)==null?void 0:e.defaultView)||window}function nf(t){var e;return(e=(rf(t)?t.ownerDocument:t.document)||window.document)==null?void 0:e.documentElement}function rf(t){return ii()?t instanceof Node||t instanceof Wn(t).Node:!1}function bw(t){return ii()?t instanceof Element||t instanceof Wn(t).Element:!1}function kl(t){return ii()?t instanceof HTMLElement||t instanceof Wn(t).HTMLElement:!1}function sf(t){return!ii()||typeof ShadowRoot>"u"?!1:t instanceof ShadowRoot||t instanceof Wn(t).ShadowRoot}function of(t){const{overflow:e,overflowX:n,overflowY:r,display:s}=Sl(t);return/auto|scroll|overlay|hidden|clip/.test(e+r+n)&&s!=="inline"&&s!=="contents"}let Cl;function yw(){return Cl==null&&(Cl=typeof CSS<"u"&&CSS.supports&&CSS.supports("-webkit-backdrop-filter","none")),Cl}function vw(t){return/^(html|body|#document)$/.test(tf(t))}function Sl(t){return Wn(t).getComputedStyle(t)}function xw(t){if(tf(t)==="html")return t;const e=t.assignedSlot||t.parentNode||sf(t)&&t.host||nf(t);return sf(e)?e.host:e}function af(t){const e=xw(t);return vw(e)?t.ownerDocument?t.ownerDocument.body:t.body:kl(e)&&of(e)?e:af(e)}function ai(t,e,n){var r;e===void 0&&(e=[]),n===void 0&&(n=!0);const s=af(t),o=s===((r=t.ownerDocument)==null?void 0:r.body),i=Wn(s);if(o){const a=_l(i);return e.concat(i,i.visualViewport||[],of(s)?s:[],a&&n?ai(a):[])}else return e.concat(s,ai(s,[],n))}function _l(t){return t.parent&&Object.getPrototypeOf(t.parent)?t.frameElement:null}function ww(t){const e=Sl(t);let n=parseFloat(e.width)||0,r=parseFloat(e.height)||0;const s=kl(t),o=s?t.offsetWidth:n,i=s?t.offsetHeight:r,a=ri(n)!==o||ri(r)!==i;return a&&(n=o,r=i),{width:n,height:r,$:a}}function El(t){return bw(t)?t:t.contextElement}function lf(t){const e=El(t);if(!kl(e))return oi(1);const n=e.getBoundingClientRect(),{width:r,height:s,$:o}=ww(e);let i=(o?ri(n.width):n.width)/r,a=(o?ri(n.height):n.height)/s;return(!i||!Number.isFinite(i))&&(i=1),(!a||!Number.isFinite(a))&&(a=1),{x:i,y:a}}const kw=oi(0);function Cw(t){const e=Wn(t);return!yw()||!e.visualViewport?kw:{x:e.visualViewport.offsetLeft,y:e.visualViewport.offsetTop}}function Sw(t,e,n){return!1}function cf(t,e,n,r){e===void 0&&(e=!1);const s=t.getBoundingClientRect(),o=El(t);let i=oi(1);e&&(i=lf(t));const a=Sw()?Cw(o):oi(0);let l=(s.left+a.x)/i.x,c=(s.top+a.y)/i.y,d=s.width/i.x,u=s.height/i.y;if(o){const f=Wn(o),g=r;let m=f,p=_l(m);for(;p&&r&&g!==m;){const y=lf(p),b=p.getBoundingClientRect(),_=Sl(p),x=b.left+(p.clientLeft+parseFloat(_.paddingLeft))*y.x,E=b.top+(p.clientTop+parseFloat(_.paddingTop))*y.y;l*=y.x,c*=y.y,d*=y.x,u*=y.y,l+=x,c+=E,m=Wn(p),p=_l(m)}}return mw({width:d,height:u,x:l,y:c})}function df(t,e){return t.x===e.x&&t.y===e.y&&t.width===e.width&&t.height===e.height}function _w(t,e){let n=null,r;const s=nf(t);function o(){var a;clearTimeout(r),(a=n)==null||a.disconnect(),n=null}function i(a,l){a===void 0&&(a=!1),l===void 0&&(l=1),o();const c=t.getBoundingClientRect(),{left:d,top:u,width:f,height:g}=c;if(a||e(),!f||!g)return;const m=si(u),p=si(s.clientWidth-(d+f)),y=si(s.clientHeight-(u+g)),b=si(d),x={rootMargin:-m+"px "+-p+"px "+-y+"px "+-b+"px",threshold:pw(0,gw(1,l))||1};let E=!0;function C(k){const N=k[0].intersectionRatio;if(N!==l){if(!E)return i();N?i(!1,N):r=setTimeout(()=>{i(!1,1e-7)},1e3)}N===1&&!df(c,t.getBoundingClientRect())&&i(),E=!1}try{n=new IntersectionObserver(C,{...x,root:s.ownerDocument})}catch{n=new IntersectionObserver(C,x)}n.observe(t)}return i(!0),o}function Ew(t,e,n,r){r===void 0&&(r={});const{ancestorScroll:s=!0,ancestorResize:o=!0,elementResize:i=typeof ResizeObserver=="function",layoutShift:a=typeof IntersectionObserver=="function",animationFrame:l=!1}=r,c=El(t),d=s||o?[...c?ai(c):[],...e?ai(e):[]]:[];d.forEach(b=>{s&&b.addEventListener("scroll",n,{passive:!0}),o&&b.addEventListener("resize",n)});const u=c&&a?_w(c,n):null;let f=-1,g=null;i&&(g=new ResizeObserver(b=>{let[_]=b;_&&_.target===c&&g&&e&&(g.unobserve(e),cancelAnimationFrame(f),f=requestAnimationFrame(()=>{var x;(x=g)==null||x.observe(e)})),n()}),c&&!l&&g.observe(c),e&&g.observe(e));let m,p=l?cf(t):null;l&&y();function y(){const b=cf(t);p&&!df(p,b)&&n(),p=b,m=requestAnimationFrame(y)}return n(),()=>{var b;d.forEach(_=>{s&&_.removeEventListener("scroll",n),o&&_.removeEventListener("resize",n)}),u?.(),(b=g)==null||b.disconnect(),g=null,l&&cancelAnimationFrame(m)}}var Ml=v.useLayoutEffect,Mw=["className","clearValue","cx","getStyles","getClassNames","getValue","hasValue","isMulti","isRtl","options","selectOption","selectProps","setValue","theme"],li=function(){};function Nw(t,e){return e?e[0]==="-"?t+e:t+"__"+e:t}function Ow(t,e){for(var n=arguments.length,r=new Array(n>2?n-2:0),s=2;s-1}function Tw(t){return di(t)?window.innerHeight:t.clientHeight}function ff(t){return di(t)?window.pageYOffset:t.scrollTop}function ui(t,e){if(di(t)){window.scrollTo(0,e);return}t.scrollTop=e}function Aw(t){var e=getComputedStyle(t),n=e.position==="absolute",r=/(auto|scroll)/;if(e.position==="fixed")return document.documentElement;for(var s=t;s=s.parentElement;)if(e=getComputedStyle(s),!(n&&e.position==="static")&&r.test(e.overflow+e.overflowY+e.overflowX))return s;return document.documentElement}function Dw(t,e,n,r){return n*((t=t/r-1)*t*t+1)+e}function fi(t,e){var n=arguments.length>2&&arguments[2]!==void 0?arguments[2]:200,r=arguments.length>3&&arguments[3]!==void 0?arguments[3]:li,s=ff(t),o=e-s,i=10,a=0;function l(){a+=i;var c=Dw(a,s,o,n);ui(t,c),an.bottom?ui(t,Math.min(e.offsetTop+e.clientHeight-t.offsetHeight+s,t.scrollHeight)):r.top-s1?n-1:0),s=1;s=m)return{placement:"bottom",maxHeight:e};if(w>=m&&!i)return o&&fi(l,M,P),{placement:"bottom",maxHeight:e};if(!i&&w>=r||i&&N>=r){o&&fi(l,M,P);var L=i?N-E:w-E;return{placement:"bottom",maxHeight:L}}if(s==="auto"||i){var $=e,A=i?k:S;return A>=r&&($=Math.min(A-E-a,e)),{placement:"top",maxHeight:$}}if(s==="bottom")return o&&ui(l,M),{placement:"bottom",maxHeight:e};break;case"top":if(k>=m)return{placement:"top",maxHeight:e};if(S>=m&&!i)return o&&fi(l,T,P),{placement:"top",maxHeight:e};if(!i&&S>=r||i&&k>=r){var D=e;return(!i&&S>=r||i&&k>=r)&&(D=i?k-C:S-C),o&&fi(l,T,P),{placement:"top",maxHeight:D}}return{placement:"bottom",maxHeight:e};default:throw new Error('Invalid placement provided "'.concat(s,'".'))}return c}function jw(t){var e={bottom:"top",top:"bottom"};return t?e[t]:"bottom"}var mf=function(e){return e==="auto"?"bottom":e},Ww=function(e,n){var r,s=e.placement,o=e.theme,i=o.borderRadius,a=o.spacing,l=o.colors;return Me((r={label:"menu"},Ds(r,jw(s),"100%"),Ds(r,"position","absolute"),Ds(r,"width","100%"),Ds(r,"zIndex",1),r),n?{}:{backgroundColor:l.neutral0,borderRadius:i,boxShadow:"0 0 0 1px hsla(0, 0%, 0%, 0.1), 0 4px 11px hsla(0, 0%, 0%, 0.1)",marginBottom:a.menuGutter,marginTop:a.menuGutter})},bf=v.createContext(null),Gw=function(e){var n=e.children,r=e.minMenuHeight,s=e.maxMenuHeight,o=e.menuPlacement,i=e.menuPosition,a=e.menuShouldScrollIntoView,l=e.theme,c=v.useContext(bf)||{},d=c.setPortalPlacement,u=v.useRef(null),f=v.useState(s),g=jn(f,2),m=g[0],p=g[1],y=v.useState(null),b=jn(y,2),_=b[0],x=b[1],E=l.spacing.controlHeight;return Ml(function(){var C=u.current;if(C){var k=i==="fixed",N=a&&!k,S=Bw({maxHeight:s,menuEl:C,minHeight:r,placement:o,shouldScroll:N,isFixedPosition:k,controlHeight:E});p(S.maxHeight),x(S.placement),d?.(S.placement)}},[s,o,i,a,r,d,E]),n({ref:u,placerProps:Me(Me({},e),{},{placement:_||mf(o),maxHeight:m})})},Yw=function(e){var n=e.children,r=e.innerRef,s=e.innerProps;return _e("div",Ae({},vt(e,"menu",{menu:!0}),{ref:r},s),n)},Kw=Yw,Xw=function(e,n){var r=e.maxHeight,s=e.theme.spacing.baseUnit;return Me({maxHeight:r,overflowY:"auto",position:"relative",WebkitOverflowScrolling:"touch"},n?{}:{paddingBottom:s,paddingTop:s})},qw=function(e){var n=e.children,r=e.innerProps,s=e.innerRef,o=e.isMulti;return _e("div",Ae({},vt(e,"menuList",{"menu-list":!0,"menu-list--is-multi":o}),{ref:s},r),n)},yf=function(e,n){var r=e.theme,s=r.spacing.baseUnit,o=r.colors;return Me({textAlign:"center"},n?{}:{color:o.neutral40,padding:"".concat(s*2,"px ").concat(s*3,"px")})},Zw=yf,Qw=yf,Jw=function(e){var n=e.children,r=n===void 0?"No options":n,s=e.innerProps,o=An(e,Hw);return _e("div",Ae({},vt(Me(Me({},o),{},{children:r,innerProps:s}),"noOptionsMessage",{"menu-notice":!0,"menu-notice--no-options":!0}),s),r)},Rw=function(e){var n=e.children,r=n===void 0?"Loading...":n,s=e.innerProps,o=An(e,Uw);return _e("div",Ae({},vt(Me(Me({},o),{},{children:r,innerProps:s}),"loadingMessage",{"menu-notice":!0,"menu-notice--loading":!0}),s),r)},e1=function(e){var n=e.rect,r=e.offset,s=e.position;return{left:n.left,position:s,top:r,width:n.width,zIndex:1}},t1=function(e){var n=e.appendTo,r=e.children,s=e.controlElement,o=e.innerProps,i=e.menuPlacement,a=e.menuPosition,l=v.useRef(null),c=v.useRef(null),d=v.useState(mf(i)),u=jn(d,2),f=u[0],g=u[1],m=v.useMemo(function(){return{setPortalPlacement:g}},[]),p=v.useState(null),y=jn(p,2),b=y[0],_=y[1],x=v.useCallback(function(){if(s){var N=Iw(s),S=a==="fixed"?0:window.pageYOffset,w=N[f]+S;(w!==b?.offset||N.left!==b?.rect.left||N.width!==b?.rect.width)&&_({offset:w,rect:N})}},[s,a,f,b?.offset,b?.rect.left,b?.rect.width]);Ml(function(){x()},[x]);var E=v.useCallback(function(){typeof c.current=="function"&&(c.current(),c.current=null),s&&l.current&&(c.current=Ew(s,l.current,x,{elementResize:"ResizeObserver"in window}))},[s,x]);Ml(function(){E()},[E]);var C=v.useCallback(function(N){l.current=N,E()},[E]);if(!n&&a!=="fixed"||!b)return null;var k=_e("div",Ae({ref:C},vt(Me(Me({},e),{},{offset:b.offset,position:a,rect:b.rect}),"menuPortal",{"menu-portal":!0}),o),r);return _e(bf.Provider,{value:m},n?Tn.createPortal(k,n):k)},n1=function(e){var n=e.isDisabled,r=e.isRtl;return{label:"container",direction:r?"rtl":void 0,pointerEvents:n?"none":void 0,position:"relative"}},r1=function(e){var n=e.children,r=e.innerProps,s=e.isDisabled,o=e.isRtl;return _e("div",Ae({},vt(e,"container",{"--is-disabled":s,"--is-rtl":o}),r),n)},s1=function(e,n){var r=e.theme.spacing,s=e.isMulti,o=e.hasValue,i=e.selectProps.controlShouldRenderValue;return Me({alignItems:"center",display:s&&o&&i?"flex":"grid",flex:1,flexWrap:"wrap",WebkitOverflowScrolling:"touch",position:"relative",overflow:"hidden"},n?{}:{padding:"".concat(r.baseUnit/2,"px ").concat(r.baseUnit*2,"px")})},o1=function(e){var n=e.children,r=e.innerProps,s=e.isMulti,o=e.hasValue;return _e("div",Ae({},vt(e,"valueContainer",{"value-container":!0,"value-container--is-multi":s,"value-container--has-value":o}),r),n)},i1=function(){return{alignItems:"center",alignSelf:"stretch",display:"flex",flexShrink:0}},a1=function(e){var n=e.children,r=e.innerProps;return _e("div",Ae({},vt(e,"indicatorsContainer",{indicators:!0}),r),n)},vf,l1=["size"],c1=["innerProps","isRtl","size"],d1={name:"8mmkcg",styles:"display:inline-block;fill:currentColor;line-height:1;stroke:currentColor;stroke-width:0"},xf=function(e){var n=e.size,r=An(e,l1);return _e("svg",Ae({height:n,width:n,viewBox:"0 0 20 20","aria-hidden":"true",focusable:"false",css:d1},r))},Nl=function(e){return _e(xf,Ae({size:20},e),_e("path",{d:"M14.348 14.849c-0.469 0.469-1.229 0.469-1.697 0l-2.651-3.030-2.651 3.029c-0.469 0.469-1.229 0.469-1.697 0-0.469-0.469-0.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-0.469-0.469-0.469-1.228 0-1.697s1.228-0.469 1.697 0l2.652 3.031 2.651-3.031c0.469-0.469 1.228-0.469 1.697 0s0.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c0.469 0.469 0.469 1.229 0 1.698z"}))},wf=function(e){return _e(xf,Ae({size:20},e),_e("path",{d:"M4.516 7.548c0.436-0.446 1.043-0.481 1.576 0l3.908 3.747 3.908-3.747c0.533-0.481 1.141-0.446 1.574 0 0.436 0.445 0.408 1.197 0 1.615-0.406 0.418-4.695 4.502-4.695 4.502-0.217 0.223-0.502 0.335-0.787 0.335s-0.57-0.112-0.789-0.335c0 0-4.287-4.084-4.695-4.502s-0.436-1.17 0-1.615z"}))},kf=function(e,n){var r=e.isFocused,s=e.theme,o=s.spacing.baseUnit,i=s.colors;return Me({label:"indicatorContainer",display:"flex",transition:"color 150ms"},n?{}:{color:r?i.neutral60:i.neutral20,padding:o*2,":hover":{color:r?i.neutral80:i.neutral40}})},u1=kf,f1=function(e){var n=e.children,r=e.innerProps;return _e("div",Ae({},vt(e,"dropdownIndicator",{indicator:!0,"dropdown-indicator":!0}),r),n||_e(wf,null))},h1=kf,g1=function(e){var n=e.children,r=e.innerProps;return _e("div",Ae({},vt(e,"clearIndicator",{indicator:!0,"clear-indicator":!0}),r),n||_e(Nl,null))},p1=function(e,n){var r=e.isDisabled,s=e.theme,o=s.spacing.baseUnit,i=s.colors;return Me({label:"indicatorSeparator",alignSelf:"stretch",width:1},n?{}:{backgroundColor:r?i.neutral10:i.neutral20,marginBottom:o*2,marginTop:o*2})},m1=function(e){var n=e.innerProps;return _e("span",Ae({},n,vt(e,"indicatorSeparator",{"indicator-separator":!0})))},b1=fw(vf||(vf=hw([` 0%, 80%, 100% { opacity: 0; } 40% { opacity: 1; } -`]))),F1=function(t,n){var r=t.isFocused,s=t.size,o=t.theme,i=o.colors,a=o.spacing.baseUnit;return Ee({label:"loadingIndicator",display:"flex",transition:"color 150ms",alignSelf:"center",fontSize:s,lineHeight:1,marginRight:s,textAlign:"center",verticalAlign:"middle"},n?{}:{color:r?i.neutral60:i.neutral20,padding:a*2})},$l=function(t){var n=t.delay,r=t.offset;return Se("span",{css:Ol({animation:"".concat($1," 1s ease-in-out ").concat(n,"ms infinite;"),backgroundColor:"currentColor",borderRadius:"1em",display:"inline-block",marginLeft:r?"1em":void 0,height:"1em",verticalAlign:"top",width:"1em"},"","")})},z1=function(t){var n=t.innerProps,r=t.isRtl,s=t.size,o=s===void 0?4:s,i=Vn(t,N1);return Se("div",Oe({},wt(Ee(Ee({},i),{},{innerProps:n,isRtl:r,size:o}),"loadingIndicator",{indicator:!0,"loading-indicator":!0}),n),Se($l,{delay:0,offset:r}),Se($l,{delay:160,offset:!0}),Se($l,{delay:320,offset:!r}))},V1=function(t,n){var r=t.isDisabled,s=t.isFocused,o=t.theme,i=o.colors,a=o.borderRadius,l=o.spacing;return Ee({label:"control",alignItems:"center",cursor:"default",display:"flex",flexWrap:"wrap",justifyContent:"space-between",minHeight:l.controlHeight,outline:"0 !important",position:"relative",transition:"all 100ms"},n?{}:{backgroundColor:r?i.neutral5:i.neutral0,borderColor:r?i.neutral10:s?i.primary:i.neutral20,borderRadius:a,borderStyle:"solid",borderWidth:1,boxShadow:s?"0 0 0 1px ".concat(i.primary):void 0,"&:hover":{borderColor:s?i.primary:i.neutral30}})},H1=function(t){var n=t.children,r=t.isDisabled,s=t.isFocused,o=t.innerRef,i=t.innerProps,a=t.menuIsOpen;return Se("div",Oe({ref:o},wt(t,"control",{control:!0,"control--is-disabled":r,"control--is-focused":s,"control--menu-is-open":a}),i,{"aria-disabled":r||void 0}),n)},U1=H1,B1=["data"],j1=function(t,n){var r=t.theme.spacing;return n?{}:{paddingBottom:r.baseUnit*2,paddingTop:r.baseUnit*2}},W1=function(t){var n=t.children,r=t.cx,s=t.getStyles,o=t.getClassNames,i=t.Heading,a=t.headingProps,l=t.innerProps,c=t.label,d=t.theme,u=t.selectProps;return Se("div",Oe({},wt(t,"group",{group:!0}),l),Se(i,Oe({},a,{selectProps:u,theme:d,getStyles:s,getClassNames:o,cx:r}),c),Se("div",null,n))},G1=function(t,n){var r=t.theme,s=r.colors,o=r.spacing;return Ee({label:"group",cursor:"default",display:"block"},n?{}:{color:s.neutral40,fontSize:"75%",fontWeight:500,marginBottom:"0.25em",paddingLeft:o.baseUnit*3,paddingRight:o.baseUnit*3,textTransform:"uppercase"})},Y1=function(t){var n=Cf(t);n.data;var r=Vn(n,B1);return Se("div",Oe({},wt(t,"groupHeading",{"group-heading":!0}),r))},X1=W1,K1=["innerRef","isDisabled","isHidden","inputClassName"],q1=function(t,n){var r=t.isDisabled,s=t.value,o=t.theme,i=o.spacing,a=o.colors;return Ee(Ee({visibility:r?"hidden":"visible",transform:s?"translateZ(0)":""},Z1),n?{}:{margin:i.baseUnit/2,paddingBottom:i.baseUnit/2,paddingTop:i.baseUnit/2,color:a.neutral80})},Pf={gridArea:"1 / 2",font:"inherit",minWidth:"2px",border:0,margin:0,outline:0,padding:0},Z1={flex:"1 1 auto",display:"inline-grid",gridArea:"1 / 1 / 2 / 3",gridTemplateColumns:"0 min-content","&:after":Ee({content:'attr(data-value) " "',visibility:"hidden",whiteSpace:"pre"},Pf)},Q1=function(t){return Ee({label:"input",color:"inherit",background:0,opacity:t?0:1,width:"100%"},Pf)},J1=function(t){var n=t.cx,r=t.value,s=Cf(t),o=s.innerRef,i=s.isDisabled,a=s.isHidden,l=s.inputClassName,c=Vn(s,K1);return Se("div",Oe({},wt(t,"input",{"input-container":!0}),{"data-value":r||""}),Se("input",Oe({className:n({input:!0},l),ref:o,style:Q1(a),disabled:i},c)))},R1=J1,ek=function(t,n){var r=t.theme,s=r.spacing,o=r.borderRadius,i=r.colors;return Ee({label:"multiValue",display:"flex",minWidth:0},n?{}:{backgroundColor:i.neutral10,borderRadius:o/2,margin:s.baseUnit/2})},tk=function(t,n){var r=t.theme,s=r.borderRadius,o=r.colors,i=t.cropWithEllipsis;return Ee({overflow:"hidden",textOverflow:i||i===void 0?"ellipsis":void 0,whiteSpace:"nowrap"},n?{}:{borderRadius:s/2,color:o.neutral80,fontSize:"85%",padding:3,paddingLeft:6})},nk=function(t,n){var r=t.theme,s=r.spacing,o=r.borderRadius,i=r.colors,a=t.isFocused;return Ee({alignItems:"center",display:"flex"},n?{}:{borderRadius:o/2,backgroundColor:a?i.dangerLight:void 0,paddingLeft:s.baseUnit,paddingRight:s.baseUnit,":hover":{backgroundColor:i.dangerLight,color:i.danger}})},$f=function(t){var n=t.children,r=t.innerProps;return Se("div",r,n)},rk=$f,sk=$f;function ok(e){var t=e.children,n=e.innerProps;return Se("div",Oe({role:"button"},n),t||Se(Pl,{size:14}))}var ik=function(t){var n=t.children,r=t.components,s=t.data,o=t.innerProps,i=t.isDisabled,a=t.removeProps,l=t.selectProps,c=r.Container,d=r.Label,u=r.Remove;return Se(c,{data:s,innerProps:Ee(Ee({},wt(t,"multiValue",{"multi-value":!0,"multi-value--is-disabled":i})),o),selectProps:l},Se(d,{data:s,innerProps:Ee({},wt(t,"multiValueLabel",{"multi-value__label":!0})),selectProps:l},n),Se(u,{data:s,innerProps:Ee(Ee({},wt(t,"multiValueRemove",{"multi-value__remove":!0})),{},{"aria-label":"Remove ".concat(n||"option")},a),selectProps:l}))},ak=ik,lk=function(t,n){var r=t.isDisabled,s=t.isFocused,o=t.isSelected,i=t.theme,a=i.spacing,l=i.colors;return Ee({label:"option",cursor:"default",display:"block",fontSize:"inherit",width:"100%",userSelect:"none",WebkitTapHighlightColor:"rgba(0, 0, 0, 0)"},n?{}:{backgroundColor:o?l.primary:s?l.primary25:"transparent",color:r?l.neutral20:o?l.neutral0:"inherit",padding:"".concat(a.baseUnit*2,"px ").concat(a.baseUnit*3,"px"),":active":{backgroundColor:r?void 0:o?l.primary:l.primary50}})},ck=function(t){var n=t.children,r=t.isDisabled,s=t.isFocused,o=t.isSelected,i=t.innerRef,a=t.innerProps;return Se("div",Oe({},wt(t,"option",{option:!0,"option--is-disabled":r,"option--is-focused":s,"option--is-selected":o}),{ref:i,"aria-disabled":r},a),n)},dk=ck,uk=function(t,n){var r=t.theme,s=r.spacing,o=r.colors;return Ee({label:"placeholder",gridArea:"1 / 1 / 2 / 3"},n?{}:{color:o.neutral50,marginLeft:s.baseUnit/2,marginRight:s.baseUnit/2})},fk=function(t){var n=t.children,r=t.innerProps;return Se("div",Oe({},wt(t,"placeholder",{placeholder:!0}),r),n)},hk=fk,gk=function(t,n){var r=t.isDisabled,s=t.theme,o=s.spacing,i=s.colors;return Ee({label:"singleValue",gridArea:"1 / 1 / 2 / 3",maxWidth:"100%",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},n?{}:{color:r?i.neutral40:i.neutral80,marginLeft:o.baseUnit/2,marginRight:o.baseUnit/2})},pk=function(t){var n=t.children,r=t.isDisabled,s=t.innerProps;return Se("div",Oe({},wt(t,"singleValue",{"single-value":!0,"single-value--is-disabled":r}),s),n)},mk=pk,ki={ClearIndicator:I1,Control:U1,DropdownIndicator:A1,DownChevron:If,CrossIcon:Pl,Group:X1,GroupHeading:Y1,IndicatorsContainer:E1,IndicatorSeparator:P1,Input:R1,LoadingIndicator:z1,Menu:f1,MenuList:g1,MenuPortal:x1,LoadingMessage:y1,NoOptionsMessage:b1,MultiValue:ak,MultiValueContainer:rk,MultiValueLabel:sk,MultiValueRemove:ok,Option:dk,Placeholder:hk,SelectContainer:k1,SingleValue:mk,ValueContainer:S1},bk=function(t){return Ee(Ee({},ki),t.components)},Ff=Number.isNaN||function(t){return typeof t=="number"&&t!==t};function yk(e,t){return!!(e===t||Ff(e)&&Ff(t))}function vk(e,t){if(e.length!==t.length)return!1;for(var n=0;n1?"s":""," ").concat(o.join(","),", selected.");case"select-option":return i?"option ".concat(s," is disabled. Select another option."):"option ".concat(s,", selected.");default:return""}},onFocus:function(t){var n=t.context,r=t.focused,s=t.options,o=t.label,i=o===void 0?"":o,a=t.selectValue,l=t.isDisabled,c=t.isSelected,d=t.isAppleDevice,u=function(p,y){return p&&p.length?"".concat(p.indexOf(y)+1," of ").concat(p.length):""};if(n==="value"&&a)return"value ".concat(i," focused, ").concat(u(a,r),".");if(n==="menu"&&d){var f=l?" disabled":"",g="".concat(c?" selected":"").concat(f);return"".concat(i).concat(g,", ").concat(u(s,r),".")}return""},onFilter:function(t){var n=t.inputValue,r=t.resultsMessage;return"".concat(r).concat(n?" for search term "+n:"",".")}},Sk=function(t){var n=t.ariaSelection,r=t.focusedOption,s=t.focusedValue,o=t.focusableOptions,i=t.isFocused,a=t.selectValue,l=t.selectProps,c=t.id,d=t.isAppleDevice,u=l.ariaLiveMessages,f=l.getOptionLabel,g=l.inputValue,m=l.isMulti,p=l.isOptionDisabled,y=l.isSearchable,b=l.menuIsOpen,_=l.options,x=l.screenReaderStatus,M=l.tabSelectsValue,C=l.isLoading,k=l["aria-label"],N=l["aria-live"],S=v.useMemo(function(){return Ee(Ee({},Ck),u||{})},[u]),w=v.useMemo(function(){var A="";if(n&&S.onChange){var O=n.option,T=n.options,z=n.removedValue,H=n.removedValues,j=n.value,G=function(q){return Array.isArray(q)?null:q},U=z||O||G(j),W=U?f(U):"",Y=T||H||void 0,V=Y?Y.map(f):[],X=Ee({isDisabled:U&&p(U,a),label:W,labels:V},n);A=S.onChange(X)}return A},[n,S,p,a,f]),E=v.useMemo(function(){var A="",O=r||s,T=!!(r&&a&&a.includes(r));if(O&&S.onFocus){var z={focused:O,label:f(O),isDisabled:p(O,a),isSelected:T,options:o,context:O===r?"menu":"value",selectValue:a,isAppleDevice:d};A=S.onFocus(z)}return A},[r,s,f,p,S,o,a,d]),D=v.useMemo(function(){var A="";if(b&&_.length&&!C&&S.onFilter){var O=x({count:o.length});A=S.onFilter({inputValue:g,resultsMessage:O})}return A},[o,g,b,S,_,x,C]),P=n?.action==="initial-input-focus",L=v.useMemo(function(){var A="";if(S.guidance){var O=s?"value":b?"menu":"input";A=S.guidance({"aria-label":k,context:O,isDisabled:r&&p(r,a),isMulti:m,isSearchable:y,tabSelectsValue:M,isInitialFocus:P})}return A},[k,r,s,m,p,y,b,S,a,M,P]),$=Se(v.Fragment,null,Se("span",{id:"aria-selection"},w),Se("span",{id:"aria-focused"},E),Se("span",{id:"aria-results"},D),Se("span",{id:"aria-guidance"},L));return Se(v.Fragment,null,Se(zf,{id:c},P&&$),Se(zf,{"aria-live":N,"aria-atomic":"false","aria-relevant":"additions text",role:"log"},i&&!P&&$))},_k=Sk,Fl=[{base:"A",letters:"AⒶAÀÁÂẦẤẪẨÃĀĂẰẮẴẲȦǠÄǞẢÅǺǍȀȂẠẬẶḀĄȺⱯ"},{base:"AA",letters:"Ꜳ"},{base:"AE",letters:"ÆǼǢ"},{base:"AO",letters:"Ꜵ"},{base:"AU",letters:"Ꜷ"},{base:"AV",letters:"ꜸꜺ"},{base:"AY",letters:"Ꜽ"},{base:"B",letters:"BⒷBḂḄḆɃƂƁ"},{base:"C",letters:"CⒸCĆĈĊČÇḈƇȻꜾ"},{base:"D",letters:"DⒹDḊĎḌḐḒḎĐƋƊƉꝹ"},{base:"DZ",letters:"DZDŽ"},{base:"Dz",letters:"DzDž"},{base:"E",letters:"EⒺEÈÉÊỀẾỄỂẼĒḔḖĔĖËẺĚȄȆẸỆȨḜĘḘḚƐƎ"},{base:"F",letters:"FⒻFḞƑꝻ"},{base:"G",letters:"GⒼGǴĜḠĞĠǦĢǤƓꞠꝽꝾ"},{base:"H",letters:"HⒽHĤḢḦȞḤḨḪĦⱧⱵꞍ"},{base:"I",letters:"IⒾIÌÍÎĨĪĬİÏḮỈǏȈȊỊĮḬƗ"},{base:"J",letters:"JⒿJĴɈ"},{base:"K",letters:"KⓀKḰǨḲĶḴƘⱩꝀꝂꝄꞢ"},{base:"L",letters:"LⓁLĿĹĽḶḸĻḼḺŁȽⱢⱠꝈꝆꞀ"},{base:"LJ",letters:"LJ"},{base:"Lj",letters:"Lj"},{base:"M",letters:"MⓂMḾṀṂⱮƜ"},{base:"N",letters:"NⓃNǸŃÑṄŇṆŅṊṈȠƝꞐꞤ"},{base:"NJ",letters:"NJ"},{base:"Nj",letters:"Nj"},{base:"O",letters:"OⓄOÒÓÔỒỐỖỔÕṌȬṎŌṐṒŎȮȰÖȪỎŐǑȌȎƠỜỚỠỞỢỌỘǪǬØǾƆƟꝊꝌ"},{base:"OI",letters:"Ƣ"},{base:"OO",letters:"Ꝏ"},{base:"OU",letters:"Ȣ"},{base:"P",letters:"PⓅPṔṖƤⱣꝐꝒꝔ"},{base:"Q",letters:"QⓆQꝖꝘɊ"},{base:"R",letters:"RⓇRŔṘŘȐȒṚṜŖṞɌⱤꝚꞦꞂ"},{base:"S",letters:"SⓈSẞŚṤŜṠŠṦṢṨȘŞⱾꞨꞄ"},{base:"T",letters:"TⓉTṪŤṬȚŢṰṮŦƬƮȾꞆ"},{base:"TZ",letters:"Ꜩ"},{base:"U",letters:"UⓊUÙÚÛŨṸŪṺŬÜǛǗǕǙỦŮŰǓȔȖƯỪỨỮỬỰỤṲŲṶṴɄ"},{base:"V",letters:"VⓋVṼṾƲꝞɅ"},{base:"VY",letters:"Ꝡ"},{base:"W",letters:"WⓌWẀẂŴẆẄẈⱲ"},{base:"X",letters:"XⓍXẊẌ"},{base:"Y",letters:"YⓎYỲÝŶỸȲẎŸỶỴƳɎỾ"},{base:"Z",letters:"ZⓏZŹẐŻŽẒẔƵȤⱿⱫꝢ"},{base:"a",letters:"aⓐaẚàáâầấẫẩãāăằắẵẳȧǡäǟảåǻǎȁȃạậặḁąⱥɐ"},{base:"aa",letters:"ꜳ"},{base:"ae",letters:"æǽǣ"},{base:"ao",letters:"ꜵ"},{base:"au",letters:"ꜷ"},{base:"av",letters:"ꜹꜻ"},{base:"ay",letters:"ꜽ"},{base:"b",letters:"bⓑbḃḅḇƀƃɓ"},{base:"c",letters:"cⓒcćĉċčçḉƈȼꜿↄ"},{base:"d",letters:"dⓓdḋďḍḑḓḏđƌɖɗꝺ"},{base:"dz",letters:"dzdž"},{base:"e",letters:"eⓔeèéêềếễểẽēḕḗĕėëẻěȅȇẹệȩḝęḙḛɇɛǝ"},{base:"f",letters:"fⓕfḟƒꝼ"},{base:"g",letters:"gⓖgǵĝḡğġǧģǥɠꞡᵹꝿ"},{base:"h",letters:"hⓗhĥḣḧȟḥḩḫẖħⱨⱶɥ"},{base:"hv",letters:"ƕ"},{base:"i",letters:"iⓘiìíîĩīĭïḯỉǐȉȋịįḭɨı"},{base:"j",letters:"jⓙjĵǰɉ"},{base:"k",letters:"kⓚkḱǩḳķḵƙⱪꝁꝃꝅꞣ"},{base:"l",letters:"lⓛlŀĺľḷḹļḽḻſłƚɫⱡꝉꞁꝇ"},{base:"lj",letters:"lj"},{base:"m",letters:"mⓜmḿṁṃɱɯ"},{base:"n",letters:"nⓝnǹńñṅňṇņṋṉƞɲʼnꞑꞥ"},{base:"nj",letters:"nj"},{base:"o",letters:"oⓞoòóôồốỗổõṍȭṏōṑṓŏȯȱöȫỏőǒȍȏơờớỡởợọộǫǭøǿɔꝋꝍɵ"},{base:"oi",letters:"ƣ"},{base:"ou",letters:"ȣ"},{base:"oo",letters:"ꝏ"},{base:"p",letters:"pⓟpṕṗƥᵽꝑꝓꝕ"},{base:"q",letters:"qⓠqɋꝗꝙ"},{base:"r",letters:"rⓡrŕṙřȑȓṛṝŗṟɍɽꝛꞧꞃ"},{base:"s",letters:"sⓢsßśṥŝṡšṧṣṩșşȿꞩꞅẛ"},{base:"t",letters:"tⓣtṫẗťṭțţṱṯŧƭʈⱦꞇ"},{base:"tz",letters:"ꜩ"},{base:"u",letters:"uⓤuùúûũṹūṻŭüǜǘǖǚủůűǔȕȗưừứữửựụṳųṷṵʉ"},{base:"v",letters:"vⓥvṽṿʋꝟʌ"},{base:"vy",letters:"ꝡ"},{base:"w",letters:"wⓦwẁẃŵẇẅẘẉⱳ"},{base:"x",letters:"xⓧxẋẍ"},{base:"y",letters:"yⓨyỳýŷỹȳẏÿỷẙỵƴɏỿ"},{base:"z",letters:"zⓩzźẑżžẓẕƶȥɀⱬꝣ"}],Ek=new RegExp("["+Fl.map(function(e){return e.letters}).join("")+"]","g"),Vf={},zl=0;zl-1}},Ok=["innerRef"];function Ak(e){var t=e.innerRef,n=Vn(e,Ok),r=s1(n,"onExited","in","enter","exit","appear");return Se("input",Oe({ref:t},r,{css:Ol({label:"dummyInput",background:0,border:0,caretColor:"transparent",fontSize:"inherit",gridArea:"1 / 1 / 2 / 3",outline:0,padding:0,width:1,color:"transparent",left:-100,opacity:0,position:"relative",transform:"scale(.01)"},"","")}))}var Dk=function(t){t.cancelable&&t.preventDefault(),t.stopPropagation()};function Ik(e){var t=e.isEnabled,n=e.onBottomArrive,r=e.onBottomLeave,s=e.onTopArrive,o=e.onTopLeave,i=v.useRef(!1),a=v.useRef(!1),l=v.useRef(0),c=v.useRef(null),d=v.useCallback(function(y,b){if(c.current!==null){var _=c.current,x=_.scrollTop,M=_.scrollHeight,C=_.clientHeight,k=c.current,N=b>0,S=M-C-x,w=!1;S>b&&i.current&&(r&&r(y),i.current=!1),N&&a.current&&(o&&o(y),a.current=!1),N&&b>S?(n&&!i.current&&n(y),k.scrollTop=M,w=!0,i.current=!0):!N&&-b>x&&(s&&!a.current&&s(y),k.scrollTop=0,w=!0,a.current=!0),w&&Dk(y)}},[n,r,s,o]),u=v.useCallback(function(y){d(y,y.deltaY)},[d]),f=v.useCallback(function(y){l.current=y.changedTouches[0].clientY},[]),g=v.useCallback(function(y){var b=l.current-y.changedTouches[0].clientY;d(y,b)},[d]),m=v.useCallback(function(y){if(y){var b=t1?{passive:!1}:!1;y.addEventListener("wheel",u,b),y.addEventListener("touchstart",f,b),y.addEventListener("touchmove",g,b)}},[g,f,u]),p=v.useCallback(function(y){y&&(y.removeEventListener("wheel",u,!1),y.removeEventListener("touchstart",f,!1),y.removeEventListener("touchmove",g,!1))},[g,f,u]);return v.useEffect(function(){if(t){var y=c.current;return m(y),function(){p(y)}}},[t,m,p]),function(y){c.current=y}}var Bf=["boxSizing","height","overflow","paddingRight","position"],jf={boxSizing:"border-box",overflow:"hidden",position:"relative",height:"100%"};function Wf(e){e.cancelable&&e.preventDefault()}function Gf(e){e.stopPropagation()}function Yf(){var e=this.scrollTop,t=this.scrollHeight,n=e+this.offsetHeight;e===0?this.scrollTop=1:n===t&&(this.scrollTop=e-1)}function Xf(){return"ontouchstart"in window||navigator.maxTouchPoints}var Kf=!!(typeof window<"u"&&window.document&&window.document.createElement),Qs=0,as={capture:!1,passive:!1};function Lk(e){var t=e.isEnabled,n=e.accountForScrollbars,r=n===void 0?!0:n,s=v.useRef({}),o=v.useRef(null),i=v.useCallback(function(l){if(Kf){var c=document.body,d=c&&c.style;if(r&&Bf.forEach(function(m){var p=d&&d[m];s.current[m]=p}),r&&Qs<1){var u=parseInt(s.current.paddingRight,10)||0,f=document.body?document.body.clientWidth:0,g=window.innerWidth-f+u||0;Object.keys(jf).forEach(function(m){var p=jf[m];d&&(d[m]=p)}),d&&(d.paddingRight="".concat(g,"px"))}c&&Xf()&&(c.addEventListener("touchmove",Wf,as),l&&(l.addEventListener("touchstart",Yf,as),l.addEventListener("touchmove",Gf,as))),Qs+=1}},[r]),a=v.useCallback(function(l){if(Kf){var c=document.body,d=c&&c.style;Qs=Math.max(Qs-1,0),r&&Qs<1&&Bf.forEach(function(u){var f=s.current[u];d&&(d[u]=f)}),c&&Xf()&&(c.removeEventListener("touchmove",Wf,as),l&&(l.removeEventListener("touchstart",Yf,as),l.removeEventListener("touchmove",Gf,as)))}},[r]);return v.useEffect(function(){if(t){var l=o.current;return i(l),function(){a(l)}}},[t,i,a]),function(l){o.current=l}}var Pk=function(t){var n=t.target;return n.ownerDocument.activeElement&&n.ownerDocument.activeElement.blur()},$k={name:"1kfdb0e",styles:"position:fixed;left:0;bottom:0;right:0;top:0"};function Fk(e){var t=e.children,n=e.lockEnabled,r=e.captureEnabled,s=r===void 0?!0:r,o=e.onBottomArrive,i=e.onBottomLeave,a=e.onTopArrive,l=e.onTopLeave,c=Ik({isEnabled:s,onBottomArrive:o,onBottomLeave:i,onTopArrive:a,onTopLeave:l}),d=Lk({isEnabled:n}),u=function(g){c(g),d(g)};return Se(v.Fragment,null,n&&Se("div",{onClick:Pk,css:$k}),t(u))}var zk={name:"1a0ro4n-requiredInput",styles:"label:requiredInput;opacity:0;pointer-events:none;position:absolute;bottom:0;left:0;right:0;width:100%"},Vk=function(t){var n=t.name,r=t.onFocus;return Se("input",{required:!0,name:n,tabIndex:-1,"aria-hidden":"true",onFocus:r,css:zk,value:"",onChange:function(){}})},Hk=Vk;function Ul(e){var t;return typeof window<"u"&&window.navigator!=null?e.test(((t=window.navigator.userAgentData)===null||t===void 0?void 0:t.platform)||window.navigator.platform):!1}function Uk(){return Ul(/^iPhone/i)}function qf(){return Ul(/^Mac/i)}function Bk(){return Ul(/^iPad/i)||qf()&&navigator.maxTouchPoints>1}function jk(){return Uk()||Bk()}function Wk(){return qf()||jk()}var Gk=function(t){return t.label},Zf=function(t){return t.label},Qf=function(t){return t.value},Yk=function(t){return!!t.isDisabled},Xk={clearIndicator:D1,container:w1,control:V1,dropdownIndicator:O1,group:j1,groupHeading:G1,indicatorsContainer:_1,indicatorSeparator:L1,input:q1,loadingIndicator:F1,loadingMessage:m1,menu:c1,menuList:h1,menuPortal:v1,multiValue:ek,multiValueLabel:tk,multiValueRemove:nk,noOptionsMessage:p1,option:lk,placeholder:uk,singleValue:gk,valueContainer:C1},Kk={primary:"#2684FF",primary75:"#4C9AFF",primary50:"#B2D4FF",primary25:"#DEEBFF",danger:"#DE350B",dangerLight:"#FFBDAD",neutral0:"hsl(0, 0%, 100%)",neutral5:"hsl(0, 0%, 95%)",neutral10:"hsl(0, 0%, 90%)",neutral20:"hsl(0, 0%, 80%)",neutral30:"hsl(0, 0%, 70%)",neutral40:"hsl(0, 0%, 60%)",neutral50:"hsl(0, 0%, 50%)",neutral60:"hsl(0, 0%, 40%)",neutral70:"hsl(0, 0%, 30%)",neutral80:"hsl(0, 0%, 20%)",neutral90:"hsl(0, 0%, 10%)"},qk=4,Jf=4,Zk=38,Qk=Jf*2,Jk={baseUnit:Jf,controlHeight:Zk,menuGutter:Qk},Bl={borderRadius:qk,colors:Kk,spacing:Jk},Rk={"aria-live":"polite",backspaceRemovesValue:!0,blurInputOnSelect:Ef(),captureMenuScroll:!Ef(),classNames:{},closeMenuOnSelect:!0,closeMenuOnScroll:!1,components:{},controlShouldRenderValue:!0,escapeClearsValue:!1,filterOption:Tk(),formatGroupLabel:Gk,getOptionLabel:Zf,getOptionValue:Qf,isDisabled:!1,isLoading:!1,isMulti:!1,isRtl:!1,isSearchable:!0,isOptionDisabled:Yk,loadingMessage:function(){return"Loading..."},maxMenuHeight:300,minMenuHeight:140,menuIsOpen:!1,menuPlacement:"bottom",menuPosition:"absolute",menuShouldBlockScroll:!1,menuShouldScrollIntoView:!Rw(),noOptionsMessage:function(){return"No options"},openMenuOnFocus:!1,openMenuOnClick:!0,options:[],pageSize:5,placeholder:"Select...",screenReaderStatus:function(t){var n=t.count;return"".concat(n," result").concat(n!==1?"s":""," available")},styles:{},tabIndex:0,tabSelectsValue:!0,unstyled:!1};function Rf(e,t,n,r){var s=sh(e,t,n),o=oh(e,t,n),i=rh(e,t),a=Si(e,t);return{type:"option",data:t,isDisabled:s,isSelected:o,label:i,value:a,index:r}}function Ci(e,t){return e.options.map(function(n,r){if("options"in n){var s=n.options.map(function(i,a){return Rf(e,i,t,a)}).filter(function(i){return nh(e,i)});return s.length>0?{type:"group",data:n,options:s,index:r}:void 0}var o=Rf(e,n,t,r);return nh(e,o)?o:void 0}).filter(n1)}function eh(e){return e.reduce(function(t,n){return n.type==="group"?t.push.apply(t,rs(n.options.map(function(r){return r.data}))):t.push(n.data),t},[])}function th(e,t){return e.reduce(function(n,r){return r.type==="group"?n.push.apply(n,rs(r.options.map(function(s){return{data:s.data,id:"".concat(t,"-").concat(r.index,"-").concat(s.index)}}))):n.push({data:r.data,id:"".concat(t,"-").concat(r.index)}),n},[])}function eC(e,t){return eh(Ci(e,t))}function nh(e,t){var n=e.inputValue,r=n===void 0?"":n,s=t.data,o=t.isSelected,i=t.label,a=t.value;return(!ah(e)||!o)&&ih(e,{label:i,value:a,data:s},r)}function tC(e,t){var n=e.focusedValue,r=e.selectValue,s=r.indexOf(n);if(s>-1){var o=t.indexOf(n);if(o>-1)return n;if(s-1?n:t[0]}var jl=function(t,n){var r,s=(r=t.find(function(o){return o.data===n}))===null||r===void 0?void 0:r.id;return s||null},rh=function(t,n){return t.getOptionLabel(n)},Si=function(t,n){return t.getOptionValue(n)};function sh(e,t,n){return typeof e.isOptionDisabled=="function"?e.isOptionDisabled(t,n):!1}function oh(e,t,n){if(n.indexOf(t)>-1)return!0;if(typeof e.isOptionSelected=="function")return e.isOptionSelected(t,n);var r=Si(e,t);return n.some(function(s){return Si(e,s)===r})}function ih(e,t,n){return e.filterOption?e.filterOption(t,n):!0}var ah=function(t){var n=t.hideSelectedOptions,r=t.isMulti;return n===void 0?r:n},rC=1,Wl=(function(e){Ox(n,e);var t=Ix(n);function n(r){var s;if(Nx(this,n),s=t.call(this,r),s.state={ariaSelection:null,focusedOption:null,focusedOptionId:null,focusableOptionsWithIds:[],focusedValue:null,inputIsHidden:!1,isFocused:!1,selectValue:[],clearFocusValueOnUpdate:!1,prevWasFocused:!1,inputIsHiddenAfterUpdate:void 0,prevProps:void 0,instancePrefix:"",isAppleDevice:!1},s.blockOptionHover=!1,s.isComposing=!1,s.commonProps=void 0,s.initialTouchX=0,s.initialTouchY=0,s.openAfterFocus=!1,s.scrollToFocusedOptionOnUpdate=!1,s.userIsDragging=void 0,s.controlRef=null,s.getControlRef=function(l){s.controlRef=l},s.focusedOptionRef=null,s.getFocusedOptionRef=function(l){s.focusedOptionRef=l},s.menuListRef=null,s.getMenuListRef=function(l){s.menuListRef=l},s.inputRef=null,s.getInputRef=function(l){s.inputRef=l},s.focus=s.focusInput,s.blur=s.blurInput,s.onChange=function(l,c){var d=s.props,u=d.onChange,f=d.name;c.name=f,s.ariaOnChange(l,c),u(l,c)},s.setValue=function(l,c,d){var u=s.props,f=u.closeMenuOnSelect,g=u.isMulti,m=u.inputValue;s.onInputChange("",{action:"set-value",prevInputValue:m}),f&&(s.setState({inputIsHiddenAfterUpdate:!g}),s.onMenuClose()),s.setState({clearFocusValueOnUpdate:!0}),s.onChange(l,{action:c,option:d})},s.selectOption=function(l){var c=s.props,d=c.blurInputOnSelect,u=c.isMulti,f=c.name,g=s.state.selectValue,m=u&&s.isOptionSelected(l,g),p=s.isOptionDisabled(l,g);if(m){var y=s.getOptionValue(l);s.setValue(g.filter(function(b){return s.getOptionValue(b)!==y}),"deselect-option",l)}else if(!p)u?s.setValue([].concat(rs(g),[l]),"select-option",l):s.setValue(l,"select-option");else{s.ariaOnChange(l,{action:"select-option",option:l,name:f});return}d&&s.blurInput()},s.removeValue=function(l){var c=s.props.isMulti,d=s.state.selectValue,u=s.getOptionValue(l),f=d.filter(function(m){return s.getOptionValue(m)!==u}),g=Zs(c,f,f[0]||null);s.onChange(g,{action:"remove-value",removedValue:l}),s.focusInput()},s.clearValue=function(){var l=s.state.selectValue;s.onChange(Zs(s.props.isMulti,[],null),{action:"clear",removedValues:l})},s.popValue=function(){var l=s.props.isMulti,c=s.state.selectValue,d=c[c.length-1],u=c.slice(0,c.length-1),f=Zs(l,u,u[0]||null);d&&s.onChange(f,{action:"pop-value",removedValue:d})},s.getFocusedOptionId=function(l){return jl(s.state.focusableOptionsWithIds,l)},s.getFocusableOptionsWithIds=function(){return th(Ci(s.props,s.state.selectValue),s.getElementId("option"))},s.getValue=function(){return s.state.selectValue},s.cx=function(){for(var l=arguments.length,c=new Array(l),d=0;dg||f>g}},s.onTouchEnd=function(l){s.userIsDragging||(s.controlRef&&!s.controlRef.contains(l.target)&&s.menuListRef&&!s.menuListRef.contains(l.target)&&s.blurInput(),s.initialTouchX=0,s.initialTouchY=0)},s.onControlTouchEnd=function(l){s.userIsDragging||s.onControlMouseDown(l)},s.onClearIndicatorTouchEnd=function(l){s.userIsDragging||s.onClearIndicatorMouseDown(l)},s.onDropdownIndicatorTouchEnd=function(l){s.userIsDragging||s.onDropdownIndicatorMouseDown(l)},s.handleInputChange=function(l){var c=s.props.inputValue,d=l.currentTarget.value;s.setState({inputIsHiddenAfterUpdate:!1}),s.onInputChange(d,{action:"input-change",prevInputValue:c}),s.props.menuIsOpen||s.onMenuOpen()},s.onInputFocus=function(l){s.props.onFocus&&s.props.onFocus(l),s.setState({inputIsHiddenAfterUpdate:!1,isFocused:!0}),(s.openAfterFocus||s.props.openMenuOnFocus)&&s.openMenu("first"),s.openAfterFocus=!1},s.onInputBlur=function(l){var c=s.props.inputValue;if(s.menuListRef&&s.menuListRef.contains(document.activeElement)){s.inputRef.focus();return}s.props.onBlur&&s.props.onBlur(l),s.onInputChange("",{action:"input-blur",prevInputValue:c}),s.onMenuClose(),s.setState({focusedValue:null,isFocused:!1})},s.onOptionHover=function(l){if(!(s.blockOptionHover||s.state.focusedOption===l)){var c=s.getFocusableOptions(),d=c.indexOf(l);s.setState({focusedOption:l,focusedOptionId:d>-1?s.getFocusedOptionId(l):null})}},s.shouldHideSelectedOptions=function(){return ah(s.props)},s.onValueInputFocus=function(l){l.preventDefault(),l.stopPropagation(),s.focus()},s.onKeyDown=function(l){var c=s.props,d=c.isMulti,u=c.backspaceRemovesValue,f=c.escapeClearsValue,g=c.inputValue,m=c.isClearable,p=c.isDisabled,y=c.menuIsOpen,b=c.onKeyDown,_=c.tabSelectsValue,x=c.openMenuOnFocus,M=s.state,C=M.focusedOption,k=M.focusedValue,N=M.selectValue;if(!p&&!(typeof b=="function"&&(b(l),l.defaultPrevented))){switch(s.blockOptionHover=!0,l.key){case"ArrowLeft":if(!d||g)return;s.focusValue("previous");break;case"ArrowRight":if(!d||g)return;s.focusValue("next");break;case"Delete":case"Backspace":if(g)return;if(k)s.removeValue(k);else{if(!u)return;d?s.popValue():m&&s.clearValue()}break;case"Tab":if(s.isComposing||l.shiftKey||!y||!_||!C||x&&s.isOptionSelected(C,N))return;s.selectOption(C);break;case"Enter":if(l.keyCode===229)break;if(y){if(!C||s.isComposing)return;s.selectOption(C);break}return;case"Escape":y?(s.setState({inputIsHiddenAfterUpdate:!1}),s.onInputChange("",{action:"menu-close",prevInputValue:g}),s.onMenuClose()):m&&f&&s.clearValue();break;case" ":if(g)return;if(!y){s.openMenu("first");break}if(!C)return;s.selectOption(C);break;case"ArrowUp":y?s.focusOption("up"):s.openMenu("last");break;case"ArrowDown":y?s.focusOption("down"):s.openMenu("first");break;case"PageUp":if(!y)return;s.focusOption("pageup");break;case"PageDown":if(!y)return;s.focusOption("pagedown");break;case"Home":if(!y)return;s.focusOption("first");break;case"End":if(!y)return;s.focusOption("last");break;default:return}l.preventDefault()}},s.state.instancePrefix="react-select-"+(s.props.instanceId||++rC),s.state.selectValue=bi(r.value),r.menuIsOpen&&s.state.selectValue.length){var o=s.getFocusableOptionsWithIds(),i=s.buildFocusableOptions(),a=i.indexOf(s.state.selectValue[0]);s.state.focusableOptionsWithIds=o,s.state.focusedOption=i[a],s.state.focusedOptionId=jl(o,i[a])}return s}return Tx(n,[{key:"componentDidMount",value:function(){this.startListeningComposition(),this.startListeningToTouch(),this.props.closeMenuOnScroll&&document&&document.addEventListener&&document.addEventListener("scroll",this.onScroll,!0),this.props.autoFocus&&this.focusInput(),this.props.menuIsOpen&&this.state.focusedOption&&this.menuListRef&&this.focusedOptionRef&&_f(this.menuListRef,this.focusedOptionRef),Wk()&&this.setState({isAppleDevice:!0})}},{key:"componentDidUpdate",value:function(s){var o=this.props,i=o.isDisabled,a=o.menuIsOpen,l=this.state.isFocused;(l&&!i&&s.isDisabled||l&&a&&!s.menuIsOpen)&&this.focusInput(),l&&i&&!s.isDisabled?this.setState({isFocused:!1},this.onMenuClose):!l&&!i&&s.isDisabled&&this.inputRef===document.activeElement&&this.setState({isFocused:!0}),this.menuListRef&&this.focusedOptionRef&&this.scrollToFocusedOptionOnUpdate&&(_f(this.menuListRef,this.focusedOptionRef),this.scrollToFocusedOptionOnUpdate=!1)}},{key:"componentWillUnmount",value:function(){this.stopListeningComposition(),this.stopListeningToTouch(),document.removeEventListener("scroll",this.onScroll,!0)}},{key:"onMenuOpen",value:function(){this.props.onMenuOpen()}},{key:"onMenuClose",value:function(){this.onInputChange("",{action:"menu-close",prevInputValue:this.props.inputValue}),this.props.onMenuClose()}},{key:"onInputChange",value:function(s,o){this.props.onInputChange(s,o)}},{key:"focusInput",value:function(){this.inputRef&&this.inputRef.focus()}},{key:"blurInput",value:function(){this.inputRef&&this.inputRef.blur()}},{key:"openMenu",value:function(s){var o=this,i=this.state,a=i.selectValue,l=i.isFocused,c=this.buildFocusableOptions(),d=s==="first"?0:c.length-1;if(!this.props.isMulti){var u=c.indexOf(a[0]);u>-1&&(d=u)}this.scrollToFocusedOptionOnUpdate=!(l&&this.menuListRef),this.setState({inputIsHiddenAfterUpdate:!1,focusedValue:null,focusedOption:c[d],focusedOptionId:this.getFocusedOptionId(c[d])},function(){return o.onMenuOpen()})}},{key:"focusValue",value:function(s){var o=this.state,i=o.selectValue,a=o.focusedValue;if(this.props.isMulti){this.setState({focusedOption:null});var l=i.indexOf(a);a||(l=-1);var c=i.length-1,d=-1;if(i.length){switch(s){case"previous":l===0?d=0:l===-1?d=c:d=l-1;break;case"next":l>-1&&l0&&arguments[0]!==void 0?arguments[0]:"first",o=this.props.pageSize,i=this.state.focusedOption,a=this.getFocusableOptions();if(a.length){var l=0,c=a.indexOf(i);i||(c=-1),s==="up"?l=c>0?c-1:a.length-1:s==="down"?l=(c+1)%a.length:s==="pageup"?(l=c-o,l<0&&(l=0)):s==="pagedown"?(l=c+o,l>a.length-1&&(l=a.length-1)):s==="last"&&(l=a.length-1),this.scrollToFocusedOptionOnUpdate=!0,this.setState({focusedOption:a[l],focusedValue:null,focusedOptionId:this.getFocusedOptionId(a[l])})}}},{key:"getTheme",value:(function(){return this.props.theme?typeof this.props.theme=="function"?this.props.theme(Bl):Ee(Ee({},Bl),this.props.theme):Bl})},{key:"getCommonProps",value:function(){var s=this.clearValue,o=this.cx,i=this.getStyles,a=this.getClassNames,l=this.getValue,c=this.selectOption,d=this.setValue,u=this.props,f=u.isMulti,g=u.isRtl,m=u.options,p=this.hasValue();return{clearValue:s,cx:o,getStyles:i,getClassNames:a,getValue:l,hasValue:p,isMulti:f,isRtl:g,options:m,selectOption:c,selectProps:u,setValue:d,theme:this.getTheme()}}},{key:"hasValue",value:function(){var s=this.state.selectValue;return s.length>0}},{key:"hasOptions",value:function(){return!!this.getFocusableOptions().length}},{key:"isClearable",value:function(){var s=this.props,o=s.isClearable,i=s.isMulti;return o===void 0?i:o}},{key:"isOptionDisabled",value:function(s,o){return sh(this.props,s,o)}},{key:"isOptionSelected",value:function(s,o){return oh(this.props,s,o)}},{key:"filterOption",value:function(s,o){return ih(this.props,s,o)}},{key:"formatOptionLabel",value:function(s,o){if(typeof this.props.formatOptionLabel=="function"){var i=this.props.inputValue,a=this.state.selectValue;return this.props.formatOptionLabel(s,{context:o,inputValue:i,selectValue:a})}else return this.getOptionLabel(s)}},{key:"formatGroupLabel",value:function(s){return this.props.formatGroupLabel(s)}},{key:"startListeningComposition",value:(function(){document&&document.addEventListener&&(document.addEventListener("compositionstart",this.onCompositionStart,!1),document.addEventListener("compositionend",this.onCompositionEnd,!1))})},{key:"stopListeningComposition",value:function(){document&&document.removeEventListener&&(document.removeEventListener("compositionstart",this.onCompositionStart),document.removeEventListener("compositionend",this.onCompositionEnd))}},{key:"startListeningToTouch",value:(function(){document&&document.addEventListener&&(document.addEventListener("touchstart",this.onTouchStart,!1),document.addEventListener("touchmove",this.onTouchMove,!1),document.addEventListener("touchend",this.onTouchEnd,!1))})},{key:"stopListeningToTouch",value:function(){document&&document.removeEventListener&&(document.removeEventListener("touchstart",this.onTouchStart),document.removeEventListener("touchmove",this.onTouchMove),document.removeEventListener("touchend",this.onTouchEnd))}},{key:"renderInput",value:(function(){var s=this.props,o=s.isDisabled,i=s.isSearchable,a=s.inputId,l=s.inputValue,c=s.tabIndex,d=s.form,u=s.menuIsOpen,f=s.required,g=this.getComponents(),m=g.Input,p=this.state,y=p.inputIsHidden,b=p.ariaSelection,_=this.commonProps,x=a||this.getElementId("input"),M=Ee(Ee(Ee({"aria-autocomplete":"list","aria-expanded":u,"aria-haspopup":!0,"aria-errormessage":this.props["aria-errormessage"],"aria-invalid":this.props["aria-invalid"],"aria-label":this.props["aria-label"],"aria-labelledby":this.props["aria-labelledby"],"aria-required":f,role:"combobox","aria-activedescendant":this.state.isAppleDevice?void 0:this.state.focusedOptionId||""},u&&{"aria-controls":this.getElementId("listbox")}),!i&&{"aria-readonly":!0}),this.hasValue()?b?.action==="initial-input-focus"&&{"aria-describedby":this.getElementId("live-region")}:{"aria-describedby":this.getElementId("placeholder")});return i?F.createElement(m,Oe({},_,{autoCapitalize:"none",autoComplete:"off",autoCorrect:"off",id:x,innerRef:this.getInputRef,isDisabled:o,isHidden:y,onBlur:this.onInputBlur,onChange:this.handleInputChange,onFocus:this.onInputFocus,spellCheck:"false",tabIndex:c,form:d,type:"text",value:l},M)):F.createElement(Ak,Oe({id:x,innerRef:this.getInputRef,onBlur:this.onInputBlur,onChange:mi,onFocus:this.onInputFocus,disabled:o,tabIndex:c,inputMode:"none",form:d,value:""},M))})},{key:"renderPlaceholderOrValue",value:function(){var s=this,o=this.getComponents(),i=o.MultiValue,a=o.MultiValueContainer,l=o.MultiValueLabel,c=o.MultiValueRemove,d=o.SingleValue,u=o.Placeholder,f=this.commonProps,g=this.props,m=g.controlShouldRenderValue,p=g.isDisabled,y=g.isMulti,b=g.inputValue,_=g.placeholder,x=this.state,M=x.selectValue,C=x.focusedValue,k=x.isFocused;if(!this.hasValue()||!m)return b?null:F.createElement(u,Oe({},f,{key:"placeholder",isDisabled:p,isFocused:k,innerProps:{id:this.getElementId("placeholder")}}),_);if(y)return M.map(function(S,w){var E=S===C,D="".concat(s.getOptionLabel(S),"-").concat(s.getOptionValue(S));return F.createElement(i,Oe({},f,{components:{Container:a,Label:l,Remove:c},isFocused:E,isDisabled:p,key:D,index:w,removeProps:{onClick:function(){return s.removeValue(S)},onTouchEnd:function(){return s.removeValue(S)},onMouseDown:function(L){L.preventDefault()}},data:S}),s.formatOptionLabel(S,"value"))});if(b)return null;var N=M[0];return F.createElement(d,Oe({},f,{data:N,isDisabled:p}),this.formatOptionLabel(N,"value"))}},{key:"renderClearIndicator",value:function(){var s=this.getComponents(),o=s.ClearIndicator,i=this.commonProps,a=this.props,l=a.isDisabled,c=a.isLoading,d=this.state.isFocused;if(!this.isClearable()||!o||l||!this.hasValue()||c)return null;var u={onMouseDown:this.onClearIndicatorMouseDown,onTouchEnd:this.onClearIndicatorTouchEnd,"aria-hidden":"true"};return F.createElement(o,Oe({},i,{innerProps:u,isFocused:d}))}},{key:"renderLoadingIndicator",value:function(){var s=this.getComponents(),o=s.LoadingIndicator,i=this.commonProps,a=this.props,l=a.isDisabled,c=a.isLoading,d=this.state.isFocused;if(!o||!c)return null;var u={"aria-hidden":"true"};return F.createElement(o,Oe({},i,{innerProps:u,isDisabled:l,isFocused:d}))}},{key:"renderIndicatorSeparator",value:function(){var s=this.getComponents(),o=s.DropdownIndicator,i=s.IndicatorSeparator;if(!o||!i)return null;var a=this.commonProps,l=this.props.isDisabled,c=this.state.isFocused;return F.createElement(i,Oe({},a,{isDisabled:l,isFocused:c}))}},{key:"renderDropdownIndicator",value:function(){var s=this.getComponents(),o=s.DropdownIndicator;if(!o)return null;var i=this.commonProps,a=this.props.isDisabled,l=this.state.isFocused,c={onMouseDown:this.onDropdownIndicatorMouseDown,onTouchEnd:this.onDropdownIndicatorTouchEnd,"aria-hidden":"true"};return F.createElement(o,Oe({},i,{innerProps:c,isDisabled:a,isFocused:l}))}},{key:"renderMenu",value:function(){var s=this,o=this.getComponents(),i=o.Group,a=o.GroupHeading,l=o.Menu,c=o.MenuList,d=o.MenuPortal,u=o.LoadingMessage,f=o.NoOptionsMessage,g=o.Option,m=this.commonProps,p=this.state.focusedOption,y=this.props,b=y.captureMenuScroll,_=y.inputValue,x=y.isLoading,M=y.loadingMessage,C=y.minMenuHeight,k=y.maxMenuHeight,N=y.menuIsOpen,S=y.menuPlacement,w=y.menuPosition,E=y.menuPortalTarget,D=y.menuShouldBlockScroll,P=y.menuShouldScrollIntoView,L=y.noOptionsMessage,$=y.onMenuScrollToTop,A=y.onMenuScrollToBottom;if(!N)return null;var O=function(W,Y){var V=W.type,X=W.data,K=W.isDisabled,q=W.isSelected,ne=W.label,ee=W.value,ue=p===X,J=K?void 0:function(){return s.onOptionHover(X)},ce=K?void 0:function(){return s.selectOption(X)},me="".concat(s.getElementId("option"),"-").concat(Y),le={id:me,onClick:ce,onMouseMove:J,onMouseOver:J,tabIndex:-1,role:"option","aria-selected":s.state.isAppleDevice?void 0:q};return F.createElement(g,Oe({},m,{innerProps:le,data:X,isDisabled:K,isSelected:q,key:me,label:ne,type:V,value:ee,isFocused:ue,innerRef:ue?s.getFocusedOptionRef:void 0}),s.formatOptionLabel(W.data,"menu"))},T;if(this.hasOptions())T=this.getCategorizedOptions().map(function(U){if(U.type==="group"){var W=U.data,Y=U.options,V=U.index,X="".concat(s.getElementId("group"),"-").concat(V),K="".concat(X,"-heading");return F.createElement(i,Oe({},m,{key:X,data:W,options:Y,Heading:a,headingProps:{id:K,data:U.data},label:s.formatGroupLabel(U.data)}),U.options.map(function(q){return O(q,"".concat(V,"-").concat(q.index))}))}else if(U.type==="option")return O(U,"".concat(U.index))});else if(x){var z=M({inputValue:_});if(z===null)return null;T=F.createElement(u,m,z)}else{var H=L({inputValue:_});if(H===null)return null;T=F.createElement(f,m,H)}var j={minMenuHeight:C,maxMenuHeight:k,menuPlacement:S,menuPosition:w,menuShouldScrollIntoView:P},G=F.createElement(d1,Oe({},m,j),function(U){var W=U.ref,Y=U.placerProps,V=Y.placement,X=Y.maxHeight;return F.createElement(l,Oe({},m,j,{innerRef:W,innerProps:{onMouseDown:s.onMenuMouseDown,onMouseMove:s.onMenuMouseMove},isLoading:x,placement:V}),F.createElement(Fk,{captureEnabled:b,onTopArrive:$,onBottomArrive:A,lockEnabled:D},function(K){return F.createElement(c,Oe({},m,{innerRef:function(ne){s.getMenuListRef(ne),K(ne)},innerProps:{role:"listbox","aria-multiselectable":m.isMulti,id:s.getElementId("listbox")},isLoading:x,maxHeight:X,focusedOption:p}),T)}))});return E||w==="fixed"?F.createElement(d,Oe({},m,{appendTo:E,controlElement:this.controlRef,menuPlacement:S,menuPosition:w}),G):G}},{key:"renderFormField",value:function(){var s=this,o=this.props,i=o.delimiter,a=o.isDisabled,l=o.isMulti,c=o.name,d=o.required,u=this.state.selectValue;if(d&&!this.hasValue()&&!a)return F.createElement(Hk,{name:c,onFocus:this.onValueInputFocus});if(!(!c||a))if(l)if(i){var f=u.map(function(p){return s.getOptionValue(p)}).join(i);return F.createElement("input",{name:c,type:"hidden",value:f})}else{var g=u.length>0?u.map(function(p,y){return F.createElement("input",{key:"i-".concat(y),name:c,type:"hidden",value:s.getOptionValue(p)})}):F.createElement("input",{name:c,type:"hidden",value:""});return F.createElement("div",null,g)}else{var m=u[0]?this.getOptionValue(u[0]):"";return F.createElement("input",{name:c,type:"hidden",value:m})}}},{key:"renderLiveRegion",value:function(){var s=this.commonProps,o=this.state,i=o.ariaSelection,a=o.focusedOption,l=o.focusedValue,c=o.isFocused,d=o.selectValue,u=this.getFocusableOptions();return F.createElement(_k,Oe({},s,{id:this.getElementId("live-region"),ariaSelection:i,focusedOption:a,focusedValue:l,isFocused:c,selectValue:d,focusableOptions:u,isAppleDevice:this.state.isAppleDevice}))}},{key:"render",value:function(){var s=this.getComponents(),o=s.Control,i=s.IndicatorsContainer,a=s.SelectContainer,l=s.ValueContainer,c=this.props,d=c.className,u=c.id,f=c.isDisabled,g=c.menuIsOpen,m=this.state.isFocused,p=this.commonProps=this.getCommonProps();return F.createElement(a,Oe({},p,{className:d,innerProps:{id:u,onKeyDown:this.onKeyDown},isDisabled:f,isFocused:m}),this.renderLiveRegion(),F.createElement(o,Oe({},p,{innerRef:this.getControlRef,innerProps:{onMouseDown:this.onControlMouseDown,onTouchEnd:this.onControlTouchEnd},isDisabled:f,isFocused:m,menuIsOpen:g}),F.createElement(l,Oe({},p,{isDisabled:f}),this.renderPlaceholderOrValue(),this.renderInput()),F.createElement(i,Oe({},p,{isDisabled:f}),this.renderClearIndicator(),this.renderLoadingIndicator(),this.renderIndicatorSeparator(),this.renderDropdownIndicator())),this.renderMenu(),this.renderFormField())}}],[{key:"getDerivedStateFromProps",value:function(s,o){var i=o.prevProps,a=o.clearFocusValueOnUpdate,l=o.inputIsHiddenAfterUpdate,c=o.ariaSelection,d=o.isFocused,u=o.prevWasFocused,f=o.instancePrefix,g=s.options,m=s.value,p=s.menuIsOpen,y=s.inputValue,b=s.isMulti,_=bi(m),x={};if(i&&(m!==i.value||g!==i.options||p!==i.menuIsOpen||y!==i.inputValue)){var M=p?eC(s,_):[],C=p?th(Ci(s,_),"".concat(f,"-option")):[],k=a?tC(o,_):null,N=nC(o,M),S=jl(C,N);x={selectValue:_,focusedOption:N,focusedOptionId:S,focusableOptionsWithIds:C,focusedValue:k,clearFocusValueOnUpdate:!1}}var w=l!=null&&s!==i?{inputIsHidden:l,inputIsHiddenAfterUpdate:void 0}:{},E=c,D=d&&u;return d&&!D&&(E={value:Zs(b,_,_[0]||null),options:_,action:"initial-input-focus"},D=!u),c?.action==="initial-input-focus"&&(E=null),Ee(Ee(Ee({},x),w),{},{prevProps:s,ariaSelection:E,prevWasFocused:D})}}]),n})(v.Component);Wl.defaultProps=Rk;var sC=v.forwardRef(function(e,t){var n=Wu(e);return F.createElement(Wl,Oe({ref:t},n))}),lh=sC,oC=["allowCreateWhileLoading","createOptionPosition","formatCreateLabel","isValidNewOption","getNewOptionData","onCreateOption","options","onChange"],ch=function(){var t=arguments.length>0&&arguments[0]!==void 0?arguments[0]:"",n=arguments.length>1?arguments[1]:void 0,r=arguments.length>2?arguments[2]:void 0,s=String(t).toLowerCase(),o=String(r.getOptionValue(n)).toLowerCase(),i=String(r.getOptionLabel(n)).toLowerCase();return o===s||i===s},Gl={formatCreateLabel:function(t){return'Create "'.concat(t,'"')},isValidNewOption:function(t,n,r,s){return!(!t||n.some(function(o){return ch(t,o,s)})||r.some(function(o){return ch(t,o,s)}))},getNewOptionData:function(t,n){return{label:n,value:t,__isNew__:!0}}};function iC(e){var t=e.allowCreateWhileLoading,n=t===void 0?!1:t,r=e.createOptionPosition,s=r===void 0?"last":r,o=e.formatCreateLabel,i=o===void 0?Gl.formatCreateLabel:o,a=e.isValidNewOption,l=a===void 0?Gl.isValidNewOption:a,c=e.getNewOptionData,d=c===void 0?Gl.getNewOptionData:c,u=e.onCreateOption,f=e.options,g=f===void 0?[]:f,m=e.onChange,p=Vn(e,oC),y=p.getOptionValue,b=y===void 0?Qf:y,_=p.getOptionLabel,x=_===void 0?Zf:_,M=p.inputValue,C=p.isLoading,k=p.isMulti,N=p.value,S=p.name,w=v.useMemo(function(){return l(M,bi(N),g,{getOptionValue:b,getOptionLabel:x})?d(M,i(M)):void 0},[i,d,x,b,M,l,g,N]),E=v.useMemo(function(){return(n||!C)&&w?s==="first"?[w].concat(rs(g)):[].concat(rs(g),[w]):g},[n,s,C,w,g]),D=v.useCallback(function(P,L){if(L.action!=="select-option")return m(P,L);var $=Array.isArray(P)?P:[P];if($[$.length-1]===w){if(u)u(M);else{var A=d(M,M),O={action:"create-option",name:S,option:A};m(Zs(k,[].concat(rs(bi(N)),[A]),A),O)}return}m(P,L)},[d,M,k,S,w,u,m,N]);return Ee(Ee({},p),{},{options:E,onChange:D})}var aC=v.forwardRef(function(e,t){var n=Wu(e),r=iC(n);return F.createElement(Wl,Oe({ref:t},r))}),dh=aC;const Ht={control:"input-tag",valueContainer:"gap-1 flex flex-wrap",multiValue:{blue:"bg-blue-100 dark:bg-blue-900/20 rounded px-1.5 py-0.5 flex items-center gap-1",green:"bg-green-100 dark:bg-green-900/20 rounded px-1.5 py-0.5 flex items-center gap-1",amber:"bg-amber-100 dark:bg-amber-900/20 rounded px-1.5 py-0.5 flex items-center gap-1",red:"bg-red-100 dark:bg-red-900/20 rounded px-1.5 py-0.5 flex items-center gap-1",purple:"bg-purple-100 dark:bg-purple-900/20 rounded px-1.5 py-0.5 flex items-center gap-1",gray:"bg-gray-100 dark:bg-gray-700/50 rounded px-1.5 py-0.5 flex items-center gap-1"},multiValueLabel:{blue:"text-blue-700 dark:text-blue-300 text-xs",green:"text-green-700 dark:text-green-300 text-xs font-semibold",amber:"text-amber-700 dark:text-amber-300 text-xs font-semibold",red:"text-red-700 dark:text-red-300 text-xs font-semibold",purple:"text-purple-700 dark:text-purple-300 text-xs font-semibold",gray:"text-gray-700 dark:text-gray-300 text-xs"},multiValueRemove:{blue:"text-blue-700 dark:text-blue-300 hover:bg-red-100 dark:hover:bg-red-900/30 hover:text-red-600 dark:hover:text-red-400 rounded px-1",green:"text-green-700 dark:text-green-300 hover:bg-red-100 dark:hover:bg-red-900/30 hover:text-red-600 dark:hover:text-red-400 rounded px-1",amber:"text-amber-700 dark:text-amber-300 hover:bg-red-100 dark:hover:bg-red-900/30 hover:text-red-600 dark:hover:text-red-400 rounded px-1",red:"text-red-700 dark:text-red-300 hover:bg-red-100 dark:hover:bg-red-900/30 hover:text-red-600 dark:hover:text-red-400 rounded px-1",purple:"text-purple-700 dark:text-purple-300 hover:bg-red-100 dark:hover:bg-red-900/30 hover:text-red-600 dark:hover:text-red-400 rounded px-1",gray:"text-gray-700 dark:text-gray-300 hover:bg-red-100 dark:hover:bg-red-900/30 hover:text-red-600 dark:hover:text-red-400 rounded px-1"},input:"text-gray-900 dark:text-white text-sm",placeholder:"text-gray-400 dark:text-gray-500 text-sm",menu:"mt-2 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg",menuList:"py-1",option:{base:"px-3 py-2 cursor-pointer text-gray-900 dark:text-white text-[13px]",selected:"bg-blue-100 dark:bg-blue-900/20",focused:"bg-gray-100 dark:bg-gray-700",default:"bg-white dark:bg-gray-800"},noOptionsMessage:"px-3 py-2 text-gray-500 dark:text-gray-400 text-[13px]",checkbox:{base:"w-4 h-4 rounded border flex items-center justify-center text-white text-[10px] shrink-0",selected:"bg-blue-500 border-blue-500",unselected:"border-gray-400 dark:border-gray-500"}},uh=e=>{const{data:t,isSelected:n}=e;return h.jsx(ki.Option,{...e,children:h.jsxs("div",{className:"flex items-center gap-2",children:[h.jsx("span",{className:`${Ht.checkbox.base} ${n?Ht.checkbox.selected:Ht.checkbox.unselected}`,children:n?"✓":""}),h.jsxs("div",{className:"flex-1 min-w-0",children:[h.jsxs("div",{className:"flex items-center gap-1",children:[h.jsx("span",{className:"text-[13px]",children:t.label}),t.count!==void 0&&t.count>0&&h.jsxs("span",{className:"text-gray-500 dark:text-gray-400 text-[10px]",children:["(",t.count,")"]})]}),t.description&&h.jsx("p",{className:"text-xs text-gray-500 dark:text-gray-400 truncate",children:t.description})]})]})})},fh=e=>h.jsx(ki.GroupHeading,{...e,children:h.jsx("div",{className:"px-3 py-1.5 text-xs font-medium text-gray-500 dark:text-gray-400 bg-gray-50 dark:bg-gray-800/50 border-b border-gray-200 dark:border-gray-700",children:e.data.label})}),lC=e=>{const{data:t}=e;return h.jsxs(ki.MultiValueLabel,{...e,children:[h.jsx("span",{children:t.label}),t.count!==void 0&&t.count>0&&h.jsxs("span",{className:"text-xs opacity-60 ml-1",children:["(",t.count,")"]})]})},cC=({value:e,options:t=[],onChange:n,creatable:r=!1,placeholder:s="검색 또는 입력...",noOptionsMessage:o="검색 결과가 없습니다",formatCreateLabel:i,createLabelSuffix:a="추가",maxItems:l,disabled:c=!1,onBeforeRemove:d,onCreateOption:u,className:f="",tagVariants:g,defaultVariant:m="blue",name:p,isMulti:y=!0,isClearable:b=!0,isSearchable:_=!0,delimiters:x=[","],splitOnPaste:M=!0,onInputChange:C,id:k,editorAttrs:N})=>{const S=c===!0||c==="true",w=v.useCallback(J=>J==null?[]:Array.isArray(J)?J:[J],[]),[E,D]=v.useState(()=>w(e)),[P,L]=v.useState(""),$=v.useRef(void 0);v.useEffect(()=>{const J=(me,le)=>!me&&!le?!0:!me||!le||me.length!==le.length?!1:me.every((ve,ge)=>ve===le[ge]),ce=w(e);J(ce,$.current)||(D(ce),$.current=ce)},[e,w]);const A=v.useMemo(()=>i?typeof i=="string"?J=>`${i} "${J}"`:i:J=>h.jsxs("span",{className:"text-[13px]",children:[h.jsx("strong",{children:J})," ",a]}),[i,a]),O=v.useMemo(()=>!E||!Array.isArray(E)?[]:E.map(J=>t.find(me=>me.value===J)||{value:J,label:String(J)}).filter(J=>J!==void 0),[E,t]),T=v.useMemo(()=>{const J=new Set(t.map(ke=>ke.value)),ce=E?E.filter(ke=>!J.has(ke)).map(ke=>({value:ke,label:String(ke)})):[],me=[...t,...ce];if(!me.some(ke=>ke.group))return me;const ve=new Map,ge=[];me.forEach(ke=>{if(ke.group){const $e=ve.get(ke.group)||[];$e.push(ke),ve.set(ke.group,$e)}else ge.push(ke)});const pe=[];return ve.forEach((ke,$e)=>{pe.push({label:$e,options:ke})}),ge.length>0&&pe.push({label:"",options:ge}),pe},[t,E]),z=v.useCallback(J=>({target:{value:J},currentTarget:{value:J},preventDefault:()=>{},stopPropagation:()=>{}}),[]),H=v.useCallback(J=>{if(!x?.length)return[J.trim()].filter(Boolean);const ce=x.map(le=>le.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")),me=new RegExp(`[${ce.join("")}]`);return J.split(me).map(le=>le.trim()).filter(Boolean)},[x]),j=v.useCallback((J,ce)=>{if(ce.action!=="input-change"){L(J);return}if(C&&C(z(J)),!x?.some(ge=>J.includes(ge))||!r||!y){L(J);return}const ve=H(J).filter(ge=>ge&&!E.includes(ge));if(ve.length>0){const ge=l?l-E.length:1/0,pe=ve.slice(0,ge);if(pe.length>0){const ke=[...E,...pe];D(ke),n(z(ke))}}L("")},[x,r,y,H,E,l,n,z,C]),G=v.useCallback(J=>{if(!M||!r||!y)return;const ce=J.clipboardData.getData("text"),me=H(ce);if(me.length<=1)return;J.preventDefault();const le=me.filter(ve=>ve&&!E.includes(ve));if(le.length>0){const ve=l?l-E.length:1/0,ge=le.slice(0,ve);if(ge.length>0){const pe=[...E,...ge];D(pe),n(z(pe))}}},[M,r,y,H,E,l,n,z]),U=v.useCallback((J,ce)=>{if((ce.action==="remove-value"||ce.action==="pop-value")&&ce.removedValue&&d){const le=d(ce.removedValue);if(le!==!0){const ve=typeof le=="string"?le:`"${ce.removedValue.label}"은(는) 제거할 수 없습니다.`;alert(ve);return}}if(l&&J.length>l)return;const me=J.map(le=>le.value);D(me),n(z(me))},[n,d,l,z]),W=v.useCallback((J,ce)=>{if(ce.action==="clear"&&E.length>0&&d){const ve=t.find(pe=>pe.value===E[0])||{value:E[0],label:String(E[0])},ge=d(ve);if(ge!==!0){const pe=typeof ge=="string"?ge:`"${ve.label}"은(는) 제거할 수 없습니다.`;alert(pe);return}}const me=J?J.value:null,le=J?[J.value]:[];D(le),n(z(me))},[n,d,E,t,z]),Y=v.useCallback(J=>{u&&u(J);const ce=[...E||[],J];D(ce),n(z(ce))},[n,u,E,z]),V=v.useCallback(J=>!J||!J.trim()?!1:!E?.includes(J.trim()),[E]),X=v.useCallback(J=>{const ce=P.trim();if(J.key==="Tab"&&r&&y&&ce){if(E.includes(ce))J.preventDefault(),L("");else if(!l||E.lengthg&&g[J]?g[J]:m,[g,m]),q={control:()=>`${Ht.control} ${f} ${S?"opacity-60 cursor-not-allowed":""}`,valueContainer:()=>Ht.valueContainer,multiValue:J=>{const ce=K(J.data.value);return Ht.multiValue[ce]},multiValueLabel:J=>{const ce=K(J.data.value);return Ht.multiValueLabel[ce]},multiValueRemove:J=>{const ce=K(J.data.value);return`${Ht.multiValueRemove[ce]} ${S?"pointer-events-none":""}`},singleValue:()=>"text-gray-900 dark:text-white text-sm",input:()=>Ht.input,placeholder:()=>Ht.placeholder,menu:()=>Ht.menu,menuList:()=>Ht.menuList,menuPortal:()=>"z-[9999]",option:J=>`${Ht.option.base} ${J.isSelected?Ht.option.selected:J.isFocused?Ht.option.focused:Ht.option.default}`,noOptionsMessage:()=>Ht.noOptionsMessage,clearIndicator:()=>"text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 cursor-pointer px-1",dropdownIndicator:()=>"text-gray-400 dark:text-gray-500 px-1"},ne={options:T,onKeyDown:X,placeholder:s,noOptionsMessage:()=>o,isDisabled:S,isSearchable:_,menuIsOpen:void 0,openMenuOnClick:!0,openMenuOnFocus:!0,menuPortalTarget:typeof document<"u"?document.body:null,menuPosition:"fixed",classNamePrefix:"tag-input",unstyled:!0,classNames:q,...r&&y||C?{inputValue:P,onInputChange:j}:{}},ee=y?E.join(","):E[0]??"";if(!y){const J={...ne,isMulti:!1,value:O[0]||null,onChange:W,isClearable:b,closeMenuOnSelect:!0,components:{Option:uh,GroupHeading:fh,IndicatorSeparator:()=>null,...b?{}:{ClearIndicator:()=>null}}};return r?h.jsxs(I,{id:k,...N,children:[p&&h.jsx("input",{type:"hidden",name:p,value:ee}),h.jsx(dh,{...J,onCreateOption:Y,formatCreateLabel:A,isValidNewOption:V,createOptionPosition:"first"})]}):h.jsxs(I,{id:k,...N,children:[p&&h.jsx("input",{type:"hidden",name:p,value:ee}),h.jsx(lh,{...J})]})}const ue={...ne,isMulti:!0,value:O,onChange:U,closeMenuOnSelect:!1,hideSelectedOptions:!1,backspaceRemovesValue:!1,isClearable:!1,components:{Option:uh,GroupHeading:fh,MultiValueLabel:lC,ClearIndicator:()=>null,DropdownIndicator:()=>null,IndicatorSeparator:()=>null}};return r?h.jsxs(I,{id:k,...N,onPaste:G,children:[p&&h.jsx("input",{type:"hidden",name:p,value:ee}),h.jsx(dh,{...ue,onCreateOption:Y,formatCreateLabel:A,isValidNewOption:V,createOptionPosition:"first"})]}):h.jsxs(I,{id:k,...N,children:[p&&h.jsx("input",{type:"hidden",name:p,value:ee}),h.jsx(lh,{...ue})]})},ls=(e,t)=>window.G7Core?.t?.(e,t)??e,dC=()=>window.G7Core?.locale?.supported?.()??["ko","en"],uC=()=>window.G7Core?.locale?.current?.()??"ko",Yl=e=>{const t=ls(`common.language_${e}`);return t===`common.language_${e}`?{ko:"한국어",en:"English",ja:"日本語",zh:"中文"}[e]||e.toUpperCase():t},fC=({value:e=[],onChange:t,placeholder:n="입력 후 Enter",disabled:r=!1,creatable:s=!0,delimiters:o=[","],className:i="",name:a,error:l,maxItems:c,modalId:d,statePath:u,__componentContext:f,id:g,editorAttrs:m})=>{const p=v.useMemo(()=>dC(),[]),y=v.useMemo(()=>uC(),[]),[b,_]=v.useState(()=>e||[]),[x,M]=v.useState(""),[C,k]=v.useState(!1),[N,S]=v.useState(null),[w,E]=v.useState({}),D=v.useRef(null),P=v.useRef(void 0);v.useEffect(()=>{const V=(K,q)=>!K&&!q?!0:!K||!q||K.length!==q.length?!1:JSON.stringify(K)===JSON.stringify(q),X=e||[];V(X,P.current)||(_(X),P.current=X)},[e]);const L=v.useCallback(V=>({target:{name:a,value:V},currentTarget:{name:a,value:V},preventDefault:()=>{},stopPropagation:()=>{}}),[a]),$=v.useCallback(V=>{const X=[];for(const K of p){const q=V[K];q?.trim()&&X.push(`${K.toUpperCase()}: ${q}`)}return X.length>0?X.join(" / "):""},[p]),A=v.useCallback(V=>{const X=b.filter((K,q)=>q!==V);_(X),t?.(L(X))},[b,t,L]),O=v.useCallback((V,X,K)=>{const q=window.G7Core;if(!q?.dispatch)return;const ne=p.map(ee=>({code:ee,label:Yl(ee)}));q.dispatch({handler:"setState",params:{target:"global",multilingualTagEdit:{fieldName:a,editingIndex:V,values:X,supportedLocales:ne,defaultLocale:y,statePath:u,currentTags:K}}}),q.dispatch({handler:"openModal",target:d},{componentContext:f})},[a,p,y,d,u,f]),T=v.useCallback(V=>{r||(d?O(V,{...b[V]},b):(S(V),E({...b[V]}),k(!0)))},[r,b,d,O]),z=v.useCallback((V="")=>{const X={};p.forEach(K=>{X[K]=K===y?V:""}),d?(O(null,X,b),M("")):(S(null),E(X),k(!0),M(""))},[p,y,d,O,b]),H=v.useCallback(()=>{if(!Object.values(w).some(K=>K.trim()!=="")){k(!1);return}let X;N!==null?X=b.map((K,q)=>q===N?w:K):X=[...b,w],_(X),t?.(L(X)),k(!1),E({}),S(null)},[w,N,b,t,L]),j=v.useCallback(()=>{k(!1),E({}),S(null)},[]),G=v.useCallback(V=>{const X=V.target.value;if(o.some(q=>X.includes(q))&&s){const q=o.map(ue=>ue.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")),ne=new RegExp(`[${q.join("")}]`),ee=X.split(ne).map(ue=>ue.trim()).filter(Boolean);if(ee.length>0){z(ee[0]);return}}M(X)},[o,s,z]),U=v.useCallback(V=>{if(V.key==="Enter"&&x.trim()&&s){if(V.preventDefault(),c&&b.length>=c)return;z(x.trim())}},[x,s,c,b.length,z]),W=v.useCallback((V,X)=>{E(K=>({...K,[V]:X}))},[]),Y=!!l;return h.jsxs(I,{className:i,id:g,...m,children:[h.jsxs(I,{className:`flex flex-wrap gap-1.5 p-2 border rounded-lg bg-white dark:bg-gray-700 min-h-[42px] ${Y?"border-red-500 dark:border-red-400":"border-gray-300 dark:border-gray-600 focus-within:ring-2 focus-within:ring-blue-500 focus-within:border-blue-500"} ${r?"opacity-60 bg-gray-100 dark:bg-gray-800 cursor-not-allowed":""}`,children:[b.map((V,X)=>h.jsxs(I,{className:"flex items-center gap-1 bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 rounded px-2 py-0.5 text-sm",children:[h.jsx(Q,{className:`cursor-pointer hover:underline ${r?"cursor-not-allowed":""}`,onClick:()=>T(X),children:$(V)}),!r&&h.jsx(ae,{type:"button",className:"p-0 h-4 w-4 min-w-0 border-0 bg-transparent text-blue-600 dark:text-blue-400 hover:text-red-600 dark:hover:text-red-400 hover:bg-transparent",onClick:K=>{K.stopPropagation(),A(X)},children:h.jsx(oe,{name:"times",className:"text-xs"})})]},X)),s&&!r&&(!c||b.lengthh.jsxs(I,{className:"space-y-1",children:[h.jsxs(I,{className:"flex items-center gap-2",children:[h.jsx(Q,{className:"text-sm font-medium text-gray-700 dark:text-gray-300 w-16",children:Yl(V)}),h.jsxs(Q,{className:"text-xs text-gray-400 dark:text-gray-500",children:["(",V.toUpperCase(),")"]})]}),h.jsx(St,{type:"text",value:w[V]??"",onChange:X=>W(V,X.target.value),placeholder:`${Yl(V)} 값 입력`,className:"w-full",autoFocus:V===y})]},V)),h.jsxs(I,{className:"flex justify-end gap-2 pt-4 border-t border-gray-200 dark:border-gray-700",children:[h.jsx(ae,{type:"button",variant:"secondary",onClick:j,children:ls("common.cancel")}),h.jsx(ae,{type:"button",variant:"primary",onClick:H,children:ls(N!==null?"common.save":"common.add")})]})]})}),a&&h.jsx("input",{type:"hidden",name:a,value:JSON.stringify(b)})]})},hh=(e,t)=>window.G7Core?.t?.(e,t)??e,gh=({type:e,identifier:t,name:n,installedModules:r=[],installedPlugins:s=[],className:o="",style:i,id:a,editorAttrs:l})=>{const c={module:de.Cubes,plugin:de.PuzzlePiece},d={module:hh("common.module"),plugin:hh("common.plugin")},u=v.useMemo(()=>{if(n)return n;if(t){const p=(e==="module"?r:s)?.find(y=>y.identifier===t);if(p)return p.name}},[n,t,e,r,s]),f=c[e],g=u?`${u} ${d[e]}`:d[e];return h.jsxs(Q,{className:`inline-flex items-center gap-1.5 px-2 py-1 rounded text-xs font-medium text-gray-400 dark:text-gray-500 ${o}`,style:i,id:a,...l,children:[h.jsx(oe,{name:f,className:"w-3.5 h-3.5"}),h.jsx(Q,{children:g})]})},hC=({options:e=[],value:t,onChange:n,multiple:r=!1,searchPlaceholder:s="Search...",onSearch:o,noResultsText:i="No results found",initialText:a="Type to search",triggerLabel:l="Select",triggerIcon:c,disabled:d=!1,className:u="",id:f,editorAttrs:g})=>{const[m,p]=v.useState(!1),[y,b]=v.useState(""),_=v.useRef(null),x=v.useRef(null),M=v.useRef(`searchable-dropdown-input-${Math.random().toString(36).slice(2,9)}`).current,C=v.useMemo(()=>t==null?[]:Array.isArray(t)?t:[t],[t]),k=v.useMemo(()=>{if(!y.trim())return e;const A=y.toLowerCase();return e.filter(O=>O.label.toLowerCase().includes(A)||O.description?.toLowerCase().includes(A))},[e,y]),N=v.useMemo(()=>{if(C.length===0)return l;if(r){const O=C.length;return O===1?e.find(z=>String(z.value)===String(C[0]))?.label||l:`${O} selected`}return e.find(O=>String(O.value)===String(C[0]))?.label||l},[C,e,r,l]);v.useEffect(()=>{const A=O=>{_.current&&!_.current.contains(O.target)&&p(!1)};return m&&document.addEventListener("mousedown",A),()=>{document.removeEventListener("mousedown",A)}},[m]),v.useEffect(()=>{const A=O=>{O.key==="Escape"&&m&&(p(!1),x.current?.focus())};return document.addEventListener("keydown",A),()=>document.removeEventListener("keydown",A)},[m]),v.useEffect(()=>{if(!m){b("");return}const A=setTimeout(()=>{document.getElementById(M)?.focus()},50);return()=>{clearTimeout(A)}},[m,M]);const S=v.useCallback(()=>{d||p(A=>!A)},[d]),w=v.useCallback(A=>{const O=A.target.value;b(O),o?.(O)},[o]),E=v.useCallback(A=>{if(r){const T=C.some(z=>String(z)===String(A))?C.filter(z=>String(z)!==String(A)):[...C,A];n?.(T)}else n?.(A),p(!1),x.current?.focus()},[r,C,n]),D=A=>C.some(O=>String(O)===String(A)),P=y.length===0&&e.length===0,L=y.length>0&&k.length===0,$=k.length>0;return h.jsxs(I,{ref:_,className:`relative inline-block ${u}`,id:f,...g,children:[h.jsxs(ae,{ref:x,type:"button",onClick:S,disabled:d,className:` +`]))),y1=function(e,n){var r=e.isFocused,s=e.size,o=e.theme,i=o.colors,a=o.spacing.baseUnit;return Me({label:"loadingIndicator",display:"flex",transition:"color 150ms",alignSelf:"center",fontSize:s,lineHeight:1,marginRight:s,textAlign:"center",verticalAlign:"middle"},n?{}:{color:r?i.neutral60:i.neutral20,padding:a*2})},Ol=function(e){var n=e.delay,r=e.offset;return _e("span",{css:wl({animation:"".concat(b1," 1s ease-in-out ").concat(n,"ms infinite;"),backgroundColor:"currentColor",borderRadius:"1em",display:"inline-block",marginLeft:r?"1em":void 0,height:"1em",verticalAlign:"top",width:"1em"},"","")})},v1=function(e){var n=e.innerProps,r=e.isRtl,s=e.size,o=s===void 0?4:s,i=An(e,c1);return _e("div",Ae({},vt(Me(Me({},i),{},{innerProps:n,isRtl:r,size:o}),"loadingIndicator",{indicator:!0,"loading-indicator":!0}),n),_e(Ol,{delay:0,offset:r}),_e(Ol,{delay:160,offset:!0}),_e(Ol,{delay:320,offset:!r}))},x1=function(e,n){var r=e.isDisabled,s=e.isFocused,o=e.theme,i=o.colors,a=o.borderRadius,l=o.spacing;return Me({label:"control",alignItems:"center",cursor:"default",display:"flex",flexWrap:"wrap",justifyContent:"space-between",minHeight:l.controlHeight,outline:"0 !important",position:"relative",transition:"all 100ms"},n?{}:{backgroundColor:r?i.neutral5:i.neutral0,borderColor:r?i.neutral10:s?i.primary:i.neutral20,borderRadius:a,borderStyle:"solid",borderWidth:1,boxShadow:s?"0 0 0 1px ".concat(i.primary):void 0,"&:hover":{borderColor:s?i.primary:i.neutral30}})},w1=function(e){var n=e.children,r=e.isDisabled,s=e.isFocused,o=e.innerRef,i=e.innerProps,a=e.menuIsOpen;return _e("div",Ae({ref:o},vt(e,"control",{control:!0,"control--is-disabled":r,"control--is-focused":s,"control--menu-is-open":a}),i,{"aria-disabled":r||void 0}),n)},k1=w1,C1=["data"],S1=function(e,n){var r=e.theme.spacing;return n?{}:{paddingBottom:r.baseUnit*2,paddingTop:r.baseUnit*2}},_1=function(e){var n=e.children,r=e.cx,s=e.getStyles,o=e.getClassNames,i=e.Heading,a=e.headingProps,l=e.innerProps,c=e.label,d=e.theme,u=e.selectProps;return _e("div",Ae({},vt(e,"group",{group:!0}),l),_e(i,Ae({},a,{selectProps:u,theme:d,getStyles:s,getClassNames:o,cx:r}),c),_e("div",null,n))},E1=function(e,n){var r=e.theme,s=r.colors,o=r.spacing;return Me({label:"group",cursor:"default",display:"block"},n?{}:{color:s.neutral40,fontSize:"75%",fontWeight:500,marginBottom:"0.25em",paddingLeft:o.baseUnit*3,paddingRight:o.baseUnit*3,textTransform:"uppercase"})},M1=function(e){var n=uf(e);n.data;var r=An(n,C1);return _e("div",Ae({},vt(e,"groupHeading",{"group-heading":!0}),r))},N1=_1,O1=["innerRef","isDisabled","isHidden","inputClassName"],T1=function(e,n){var r=e.isDisabled,s=e.value,o=e.theme,i=o.spacing,a=o.colors;return Me(Me({visibility:r?"hidden":"visible",transform:s?"translateZ(0)":""},A1),n?{}:{margin:i.baseUnit/2,paddingBottom:i.baseUnit/2,paddingTop:i.baseUnit/2,color:a.neutral80})},Cf={gridArea:"1 / 2",font:"inherit",minWidth:"2px",border:0,margin:0,outline:0,padding:0},A1={flex:"1 1 auto",display:"inline-grid",gridArea:"1 / 1 / 2 / 3",gridTemplateColumns:"0 min-content","&:after":Me({content:'attr(data-value) " "',visibility:"hidden",whiteSpace:"pre"},Cf)},D1=function(e){return Me({label:"input",color:"inherit",background:0,opacity:e?0:1,width:"100%"},Cf)},I1=function(e){var n=e.cx,r=e.value,s=uf(e),o=s.innerRef,i=s.isDisabled,a=s.isHidden,l=s.inputClassName,c=An(s,O1);return _e("div",Ae({},vt(e,"input",{"input-container":!0}),{"data-value":r||""}),_e("input",Ae({className:n({input:!0},l),ref:o,style:D1(a),disabled:i},c)))},L1=I1,P1=function(e,n){var r=e.theme,s=r.spacing,o=r.borderRadius,i=r.colors;return Me({label:"multiValue",display:"flex",minWidth:0},n?{}:{backgroundColor:i.neutral10,borderRadius:o/2,margin:s.baseUnit/2})},$1=function(e,n){var r=e.theme,s=r.borderRadius,o=r.colors,i=e.cropWithEllipsis;return Me({overflow:"hidden",textOverflow:i||i===void 0?"ellipsis":void 0,whiteSpace:"nowrap"},n?{}:{borderRadius:s/2,color:o.neutral80,fontSize:"85%",padding:3,paddingLeft:6})},F1=function(e,n){var r=e.theme,s=r.spacing,o=r.borderRadius,i=r.colors,a=e.isFocused;return Me({alignItems:"center",display:"flex"},n?{}:{borderRadius:o/2,backgroundColor:a?i.dangerLight:void 0,paddingLeft:s.baseUnit,paddingRight:s.baseUnit,":hover":{backgroundColor:i.dangerLight,color:i.danger}})},Sf=function(e){var n=e.children,r=e.innerProps;return _e("div",r,n)},z1=Sf,V1=Sf;function H1(t){var e=t.children,n=t.innerProps;return _e("div",Ae({role:"button"},n),e||_e(Nl,{size:14}))}var U1=function(e){var n=e.children,r=e.components,s=e.data,o=e.innerProps,i=e.isDisabled,a=e.removeProps,l=e.selectProps,c=r.Container,d=r.Label,u=r.Remove;return _e(c,{data:s,innerProps:Me(Me({},vt(e,"multiValue",{"multi-value":!0,"multi-value--is-disabled":i})),o),selectProps:l},_e(d,{data:s,innerProps:Me({},vt(e,"multiValueLabel",{"multi-value__label":!0})),selectProps:l},n),_e(u,{data:s,innerProps:Me(Me({},vt(e,"multiValueRemove",{"multi-value__remove":!0})),{},{"aria-label":"Remove ".concat(n||"option")},a),selectProps:l}))},B1=U1,j1=function(e,n){var r=e.isDisabled,s=e.isFocused,o=e.isSelected,i=e.theme,a=i.spacing,l=i.colors;return Me({label:"option",cursor:"default",display:"block",fontSize:"inherit",width:"100%",userSelect:"none",WebkitTapHighlightColor:"rgba(0, 0, 0, 0)"},n?{}:{backgroundColor:o?l.primary:s?l.primary25:"transparent",color:r?l.neutral20:o?l.neutral0:"inherit",padding:"".concat(a.baseUnit*2,"px ").concat(a.baseUnit*3,"px"),":active":{backgroundColor:r?void 0:o?l.primary:l.primary50}})},W1=function(e){var n=e.children,r=e.isDisabled,s=e.isFocused,o=e.isSelected,i=e.innerRef,a=e.innerProps;return _e("div",Ae({},vt(e,"option",{option:!0,"option--is-disabled":r,"option--is-focused":s,"option--is-selected":o}),{ref:i,"aria-disabled":r},a),n)},G1=W1,Y1=function(e,n){var r=e.theme,s=r.spacing,o=r.colors;return Me({label:"placeholder",gridArea:"1 / 1 / 2 / 3"},n?{}:{color:o.neutral50,marginLeft:s.baseUnit/2,marginRight:s.baseUnit/2})},K1=function(e){var n=e.children,r=e.innerProps;return _e("div",Ae({},vt(e,"placeholder",{placeholder:!0}),r),n)},X1=K1,q1=function(e,n){var r=e.isDisabled,s=e.theme,o=s.spacing,i=s.colors;return Me({label:"singleValue",gridArea:"1 / 1 / 2 / 3",maxWidth:"100%",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},n?{}:{color:r?i.neutral40:i.neutral80,marginLeft:o.baseUnit/2,marginRight:o.baseUnit/2})},Z1=function(e){var n=e.children,r=e.isDisabled,s=e.innerProps;return _e("div",Ae({},vt(e,"singleValue",{"single-value":!0,"single-value--is-disabled":r}),s),n)},Q1=Z1,gi={ClearIndicator:g1,Control:k1,DropdownIndicator:f1,DownChevron:wf,CrossIcon:Nl,Group:N1,GroupHeading:M1,IndicatorsContainer:a1,IndicatorSeparator:m1,Input:L1,LoadingIndicator:v1,Menu:Kw,MenuList:qw,MenuPortal:t1,LoadingMessage:Rw,NoOptionsMessage:Jw,MultiValue:B1,MultiValueContainer:z1,MultiValueLabel:V1,MultiValueRemove:H1,Option:G1,Placeholder:X1,SelectContainer:r1,SingleValue:Q1,ValueContainer:o1},J1=function(e){return Me(Me({},gi),e.components)},_f=Number.isNaN||function(e){return typeof e=="number"&&e!==e};function R1(t,e){return!!(t===e||_f(t)&&_f(e))}function ek(t,e){if(t.length!==e.length)return!1;for(var n=0;n1?"s":""," ").concat(o.join(","),", selected.");case"select-option":return i?"option ".concat(s," is disabled. Select another option."):"option ".concat(s,", selected.");default:return""}},onFocus:function(e){var n=e.context,r=e.focused,s=e.options,o=e.label,i=o===void 0?"":o,a=e.selectValue,l=e.isDisabled,c=e.isSelected,d=e.isAppleDevice,u=function(p,y){return p&&p.length?"".concat(p.indexOf(y)+1," of ").concat(p.length):""};if(n==="value"&&a)return"value ".concat(i," focused, ").concat(u(a,r),".");if(n==="menu"&&d){var f=l?" disabled":"",g="".concat(c?" selected":"").concat(f);return"".concat(i).concat(g,", ").concat(u(s,r),".")}return""},onFilter:function(e){var n=e.inputValue,r=e.resultsMessage;return"".concat(r).concat(n?" for search term "+n:"",".")}},ok=function(e){var n=e.ariaSelection,r=e.focusedOption,s=e.focusedValue,o=e.focusableOptions,i=e.isFocused,a=e.selectValue,l=e.selectProps,c=e.id,d=e.isAppleDevice,u=l.ariaLiveMessages,f=l.getOptionLabel,g=l.inputValue,m=l.isMulti,p=l.isOptionDisabled,y=l.isSearchable,b=l.menuIsOpen,_=l.options,x=l.screenReaderStatus,E=l.tabSelectsValue,C=l.isLoading,k=l["aria-label"],N=l["aria-live"],S=v.useMemo(function(){return Me(Me({},sk),u||{})},[u]),w=v.useMemo(function(){var A="";if(n&&S.onChange){var D=n.option,O=n.options,z=n.removedValue,H=n.removedValues,j=n.value,W=function(Z){return Array.isArray(Z)?null:Z},U=z||D||W(j),B=U?f(U):"",G=O||H||void 0,V=G?G.map(f):[],X=Me({isDisabled:U&&p(U,a),label:B,labels:V},n);A=S.onChange(X)}return A},[n,S,p,a,f]),M=v.useMemo(function(){var A="",D=r||s,O=!!(r&&a&&a.includes(r));if(D&&S.onFocus){var z={focused:D,label:f(D),isDisabled:p(D,a),isSelected:O,options:o,context:D===r?"menu":"value",selectValue:a,isAppleDevice:d};A=S.onFocus(z)}return A},[r,s,f,p,S,o,a,d]),T=v.useMemo(function(){var A="";if(b&&_.length&&!C&&S.onFilter){var D=x({count:o.length});A=S.onFilter({inputValue:g,resultsMessage:D})}return A},[o,g,b,S,_,x,C]),P=n?.action==="initial-input-focus",L=v.useMemo(function(){var A="";if(S.guidance){var D=s?"value":b?"menu":"input";A=S.guidance({"aria-label":k,context:D,isDisabled:r&&p(r,a),isMulti:m,isSearchable:y,tabSelectsValue:E,isInitialFocus:P})}return A},[k,r,s,m,p,y,b,S,a,E,P]),$=_e(v.Fragment,null,_e("span",{id:"aria-selection"},w),_e("span",{id:"aria-focused"},M),_e("span",{id:"aria-results"},T),_e("span",{id:"aria-guidance"},L));return _e(v.Fragment,null,_e(Ef,{id:c},P&&$),_e(Ef,{"aria-live":N,"aria-atomic":"false","aria-relevant":"additions text",role:"log"},i&&!P&&$))},ik=ok,Tl=[{base:"A",letters:"AⒶAÀÁÂẦẤẪẨÃĀĂẰẮẴẲȦǠÄǞẢÅǺǍȀȂẠẬẶḀĄȺⱯ"},{base:"AA",letters:"Ꜳ"},{base:"AE",letters:"ÆǼǢ"},{base:"AO",letters:"Ꜵ"},{base:"AU",letters:"Ꜷ"},{base:"AV",letters:"ꜸꜺ"},{base:"AY",letters:"Ꜽ"},{base:"B",letters:"BⒷBḂḄḆɃƂƁ"},{base:"C",letters:"CⒸCĆĈĊČÇḈƇȻꜾ"},{base:"D",letters:"DⒹDḊĎḌḐḒḎĐƋƊƉꝹ"},{base:"DZ",letters:"DZDŽ"},{base:"Dz",letters:"DzDž"},{base:"E",letters:"EⒺEÈÉÊỀẾỄỂẼĒḔḖĔĖËẺĚȄȆẸỆȨḜĘḘḚƐƎ"},{base:"F",letters:"FⒻFḞƑꝻ"},{base:"G",letters:"GⒼGǴĜḠĞĠǦĢǤƓꞠꝽꝾ"},{base:"H",letters:"HⒽHĤḢḦȞḤḨḪĦⱧⱵꞍ"},{base:"I",letters:"IⒾIÌÍÎĨĪĬİÏḮỈǏȈȊỊĮḬƗ"},{base:"J",letters:"JⒿJĴɈ"},{base:"K",letters:"KⓀKḰǨḲĶḴƘⱩꝀꝂꝄꞢ"},{base:"L",letters:"LⓁLĿĹĽḶḸĻḼḺŁȽⱢⱠꝈꝆꞀ"},{base:"LJ",letters:"LJ"},{base:"Lj",letters:"Lj"},{base:"M",letters:"MⓂMḾṀṂⱮƜ"},{base:"N",letters:"NⓃNǸŃÑṄŇṆŅṊṈȠƝꞐꞤ"},{base:"NJ",letters:"NJ"},{base:"Nj",letters:"Nj"},{base:"O",letters:"OⓄOÒÓÔỒỐỖỔÕṌȬṎŌṐṒŎȮȰÖȪỎŐǑȌȎƠỜỚỠỞỢỌỘǪǬØǾƆƟꝊꝌ"},{base:"OI",letters:"Ƣ"},{base:"OO",letters:"Ꝏ"},{base:"OU",letters:"Ȣ"},{base:"P",letters:"PⓅPṔṖƤⱣꝐꝒꝔ"},{base:"Q",letters:"QⓆQꝖꝘɊ"},{base:"R",letters:"RⓇRŔṘŘȐȒṚṜŖṞɌⱤꝚꞦꞂ"},{base:"S",letters:"SⓈSẞŚṤŜṠŠṦṢṨȘŞⱾꞨꞄ"},{base:"T",letters:"TⓉTṪŤṬȚŢṰṮŦƬƮȾꞆ"},{base:"TZ",letters:"Ꜩ"},{base:"U",letters:"UⓊUÙÚÛŨṸŪṺŬÜǛǗǕǙỦŮŰǓȔȖƯỪỨỮỬỰỤṲŲṶṴɄ"},{base:"V",letters:"VⓋVṼṾƲꝞɅ"},{base:"VY",letters:"Ꝡ"},{base:"W",letters:"WⓌWẀẂŴẆẄẈⱲ"},{base:"X",letters:"XⓍXẊẌ"},{base:"Y",letters:"YⓎYỲÝŶỸȲẎŸỶỴƳɎỾ"},{base:"Z",letters:"ZⓏZŹẐŻŽẒẔƵȤⱿⱫꝢ"},{base:"a",letters:"aⓐaẚàáâầấẫẩãāăằắẵẳȧǡäǟảåǻǎȁȃạậặḁąⱥɐ"},{base:"aa",letters:"ꜳ"},{base:"ae",letters:"æǽǣ"},{base:"ao",letters:"ꜵ"},{base:"au",letters:"ꜷ"},{base:"av",letters:"ꜹꜻ"},{base:"ay",letters:"ꜽ"},{base:"b",letters:"bⓑbḃḅḇƀƃɓ"},{base:"c",letters:"cⓒcćĉċčçḉƈȼꜿↄ"},{base:"d",letters:"dⓓdḋďḍḑḓḏđƌɖɗꝺ"},{base:"dz",letters:"dzdž"},{base:"e",letters:"eⓔeèéêềếễểẽēḕḗĕėëẻěȅȇẹệȩḝęḙḛɇɛǝ"},{base:"f",letters:"fⓕfḟƒꝼ"},{base:"g",letters:"gⓖgǵĝḡğġǧģǥɠꞡᵹꝿ"},{base:"h",letters:"hⓗhĥḣḧȟḥḩḫẖħⱨⱶɥ"},{base:"hv",letters:"ƕ"},{base:"i",letters:"iⓘiìíîĩīĭïḯỉǐȉȋịįḭɨı"},{base:"j",letters:"jⓙjĵǰɉ"},{base:"k",letters:"kⓚkḱǩḳķḵƙⱪꝁꝃꝅꞣ"},{base:"l",letters:"lⓛlŀĺľḷḹļḽḻſłƚɫⱡꝉꞁꝇ"},{base:"lj",letters:"lj"},{base:"m",letters:"mⓜmḿṁṃɱɯ"},{base:"n",letters:"nⓝnǹńñṅňṇņṋṉƞɲʼnꞑꞥ"},{base:"nj",letters:"nj"},{base:"o",letters:"oⓞoòóôồốỗổõṍȭṏōṑṓŏȯȱöȫỏőǒȍȏơờớỡởợọộǫǭøǿɔꝋꝍɵ"},{base:"oi",letters:"ƣ"},{base:"ou",letters:"ȣ"},{base:"oo",letters:"ꝏ"},{base:"p",letters:"pⓟpṕṗƥᵽꝑꝓꝕ"},{base:"q",letters:"qⓠqɋꝗꝙ"},{base:"r",letters:"rⓡrŕṙřȑȓṛṝŗṟɍɽꝛꞧꞃ"},{base:"s",letters:"sⓢsßśṥŝṡšṧṣṩșşȿꞩꞅẛ"},{base:"t",letters:"tⓣtṫẗťṭțţṱṯŧƭʈⱦꞇ"},{base:"tz",letters:"ꜩ"},{base:"u",letters:"uⓤuùúûũṹūṻŭüǜǘǖǚủůűǔȕȗưừứữửựụṳųṷṵʉ"},{base:"v",letters:"vⓥvṽṿʋꝟʌ"},{base:"vy",letters:"ꝡ"},{base:"w",letters:"wⓦwẁẃŵẇẅẘẉⱳ"},{base:"x",letters:"xⓧxẋẍ"},{base:"y",letters:"yⓨyỳýŷỹȳẏÿỷẙỵƴɏỿ"},{base:"z",letters:"zⓩzźẑżžẓẕƶȥɀⱬꝣ"}],ak=new RegExp("["+Tl.map(function(t){return t.letters}).join("")+"]","g"),Mf={},Al=0;Al-1}},uk=["innerRef"];function fk(t){var e=t.innerRef,n=An(t,uk),r=Vw(n,"onExited","in","enter","exit","appear");return _e("input",Ae({ref:e},r,{css:wl({label:"dummyInput",background:0,border:0,caretColor:"transparent",fontSize:"inherit",gridArea:"1 / 1 / 2 / 3",outline:0,padding:0,width:1,color:"transparent",left:-100,opacity:0,position:"relative",transform:"scale(.01)"},"","")}))}var hk=function(e){e.cancelable&&e.preventDefault(),e.stopPropagation()};function gk(t){var e=t.isEnabled,n=t.onBottomArrive,r=t.onBottomLeave,s=t.onTopArrive,o=t.onTopLeave,i=v.useRef(!1),a=v.useRef(!1),l=v.useRef(0),c=v.useRef(null),d=v.useCallback(function(y,b){if(c.current!==null){var _=c.current,x=_.scrollTop,E=_.scrollHeight,C=_.clientHeight,k=c.current,N=b>0,S=E-C-x,w=!1;S>b&&i.current&&(r&&r(y),i.current=!1),N&&a.current&&(o&&o(y),a.current=!1),N&&b>S?(n&&!i.current&&n(y),k.scrollTop=E,w=!0,i.current=!0):!N&&-b>x&&(s&&!a.current&&s(y),k.scrollTop=0,w=!0,a.current=!0),w&&hk(y)}},[n,r,s,o]),u=v.useCallback(function(y){d(y,y.deltaY)},[d]),f=v.useCallback(function(y){l.current=y.changedTouches[0].clientY},[]),g=v.useCallback(function(y){var b=l.current-y.changedTouches[0].clientY;d(y,b)},[d]),m=v.useCallback(function(y){if(y){var b=$w?{passive:!1}:!1;y.addEventListener("wheel",u,b),y.addEventListener("touchstart",f,b),y.addEventListener("touchmove",g,b)}},[g,f,u]),p=v.useCallback(function(y){y&&(y.removeEventListener("wheel",u,!1),y.removeEventListener("touchstart",f,!1),y.removeEventListener("touchmove",g,!1))},[g,f,u]);return v.useEffect(function(){if(e){var y=c.current;return m(y),function(){p(y)}}},[e,m,p]),function(y){c.current=y}}var Tf=["boxSizing","height","overflow","paddingRight","position"],Af={boxSizing:"border-box",overflow:"hidden",position:"relative",height:"100%"};function Df(t){t.cancelable&&t.preventDefault()}function If(t){t.stopPropagation()}function Lf(){var t=this.scrollTop,e=this.scrollHeight,n=t+this.offsetHeight;t===0?this.scrollTop=1:n===e&&(this.scrollTop=t-1)}function Pf(){return"ontouchstart"in window||navigator.maxTouchPoints}var $f=!!(typeof window<"u"&&window.document&&window.document.createElement),Vs=0,Gr={capture:!1,passive:!1};function pk(t){var e=t.isEnabled,n=t.accountForScrollbars,r=n===void 0?!0:n,s=v.useRef({}),o=v.useRef(null),i=v.useCallback(function(l){if($f){var c=document.body,d=c&&c.style;if(r&&Tf.forEach(function(m){var p=d&&d[m];s.current[m]=p}),r&&Vs<1){var u=parseInt(s.current.paddingRight,10)||0,f=document.body?document.body.clientWidth:0,g=window.innerWidth-f+u||0;Object.keys(Af).forEach(function(m){var p=Af[m];d&&(d[m]=p)}),d&&(d.paddingRight="".concat(g,"px"))}c&&Pf()&&(c.addEventListener("touchmove",Df,Gr),l&&(l.addEventListener("touchstart",Lf,Gr),l.addEventListener("touchmove",If,Gr))),Vs+=1}},[r]),a=v.useCallback(function(l){if($f){var c=document.body,d=c&&c.style;Vs=Math.max(Vs-1,0),r&&Vs<1&&Tf.forEach(function(u){var f=s.current[u];d&&(d[u]=f)}),c&&Pf()&&(c.removeEventListener("touchmove",Df,Gr),l&&(l.removeEventListener("touchstart",Lf,Gr),l.removeEventListener("touchmove",If,Gr)))}},[r]);return v.useEffect(function(){if(e){var l=o.current;return i(l),function(){a(l)}}},[e,i,a]),function(l){o.current=l}}var mk=function(e){var n=e.target;return n.ownerDocument.activeElement&&n.ownerDocument.activeElement.blur()},bk={name:"1kfdb0e",styles:"position:fixed;left:0;bottom:0;right:0;top:0"};function yk(t){var e=t.children,n=t.lockEnabled,r=t.captureEnabled,s=r===void 0?!0:r,o=t.onBottomArrive,i=t.onBottomLeave,a=t.onTopArrive,l=t.onTopLeave,c=gk({isEnabled:s,onBottomArrive:o,onBottomLeave:i,onTopArrive:a,onTopLeave:l}),d=pk({isEnabled:n}),u=function(g){c(g),d(g)};return _e(v.Fragment,null,n&&_e("div",{onClick:mk,css:bk}),e(u))}var vk={name:"1a0ro4n-requiredInput",styles:"label:requiredInput;opacity:0;pointer-events:none;position:absolute;bottom:0;left:0;right:0;width:100%"},xk=function(e){var n=e.name,r=e.onFocus;return _e("input",{required:!0,name:n,tabIndex:-1,"aria-hidden":"true",onFocus:r,css:vk,value:"",onChange:function(){}})},wk=xk;function Ll(t){var e;return typeof window<"u"&&window.navigator!=null?t.test(((e=window.navigator.userAgentData)===null||e===void 0?void 0:e.platform)||window.navigator.platform):!1}function kk(){return Ll(/^iPhone/i)}function Ff(){return Ll(/^Mac/i)}function Ck(){return Ll(/^iPad/i)||Ff()&&navigator.maxTouchPoints>1}function Sk(){return kk()||Ck()}function _k(){return Ff()||Sk()}var Ek=function(e){return e.label},zf=function(e){return e.label},Vf=function(e){return e.value},Mk=function(e){return!!e.isDisabled},Nk={clearIndicator:h1,container:n1,control:x1,dropdownIndicator:u1,group:S1,groupHeading:E1,indicatorsContainer:i1,indicatorSeparator:p1,input:T1,loadingIndicator:y1,loadingMessage:Qw,menu:Ww,menuList:Xw,menuPortal:e1,multiValue:P1,multiValueLabel:$1,multiValueRemove:F1,noOptionsMessage:Zw,option:j1,placeholder:Y1,singleValue:q1,valueContainer:s1},Ok={primary:"#2684FF",primary75:"#4C9AFF",primary50:"#B2D4FF",primary25:"#DEEBFF",danger:"#DE350B",dangerLight:"#FFBDAD",neutral0:"hsl(0, 0%, 100%)",neutral5:"hsl(0, 0%, 95%)",neutral10:"hsl(0, 0%, 90%)",neutral20:"hsl(0, 0%, 80%)",neutral30:"hsl(0, 0%, 70%)",neutral40:"hsl(0, 0%, 60%)",neutral50:"hsl(0, 0%, 50%)",neutral60:"hsl(0, 0%, 40%)",neutral70:"hsl(0, 0%, 30%)",neutral80:"hsl(0, 0%, 20%)",neutral90:"hsl(0, 0%, 10%)"},Tk=4,Hf=4,Ak=38,Dk=Hf*2,Ik={baseUnit:Hf,controlHeight:Ak,menuGutter:Dk},Pl={borderRadius:Tk,colors:Ok,spacing:Ik},Lk={"aria-live":"polite",backspaceRemovesValue:!0,blurInputOnSelect:gf(),captureMenuScroll:!gf(),classNames:{},closeMenuOnSelect:!0,closeMenuOnScroll:!1,components:{},controlShouldRenderValue:!0,escapeClearsValue:!1,filterOption:dk(),formatGroupLabel:Ek,getOptionLabel:zf,getOptionValue:Vf,isDisabled:!1,isLoading:!1,isMulti:!1,isRtl:!1,isSearchable:!0,isOptionDisabled:Mk,loadingMessage:function(){return"Loading..."},maxMenuHeight:300,minMenuHeight:140,menuIsOpen:!1,menuPlacement:"bottom",menuPosition:"absolute",menuShouldBlockScroll:!1,menuShouldScrollIntoView:!Lw(),noOptionsMessage:function(){return"No options"},openMenuOnFocus:!1,openMenuOnClick:!0,options:[],pageSize:5,placeholder:"Select...",screenReaderStatus:function(e){var n=e.count;return"".concat(n," result").concat(n!==1?"s":""," available")},styles:{},tabIndex:0,tabSelectsValue:!0,unstyled:!1};function Uf(t,e,n,r){var s=Yf(t,e,n),o=Kf(t,e,n),i=Gf(t,e),a=mi(t,e);return{type:"option",data:e,isDisabled:s,isSelected:o,label:i,value:a,index:r}}function pi(t,e){return t.options.map(function(n,r){if("options"in n){var s=n.options.map(function(i,a){return Uf(t,i,e,a)}).filter(function(i){return Wf(t,i)});return s.length>0?{type:"group",data:n,options:s,index:r}:void 0}var o=Uf(t,n,e,r);return Wf(t,o)?o:void 0}).filter(Fw)}function Bf(t){return t.reduce(function(e,n){return n.type==="group"?e.push.apply(e,Ur(n.options.map(function(r){return r.data}))):e.push(n.data),e},[])}function jf(t,e){return t.reduce(function(n,r){return r.type==="group"?n.push.apply(n,Ur(r.options.map(function(s){return{data:s.data,id:"".concat(e,"-").concat(r.index,"-").concat(s.index)}}))):n.push({data:r.data,id:"".concat(e,"-").concat(r.index)}),n},[])}function Pk(t,e){return Bf(pi(t,e))}function Wf(t,e){var n=t.inputValue,r=n===void 0?"":n,s=e.data,o=e.isSelected,i=e.label,a=e.value;return(!qf(t)||!o)&&Xf(t,{label:i,value:a,data:s},r)}function $k(t,e){var n=t.focusedValue,r=t.selectValue,s=r.indexOf(n);if(s>-1){var o=e.indexOf(n);if(o>-1)return n;if(s-1?n:e[0]}var $l=function(e,n){var r,s=(r=e.find(function(o){return o.data===n}))===null||r===void 0?void 0:r.id;return s||null},Gf=function(e,n){return e.getOptionLabel(n)},mi=function(e,n){return e.getOptionValue(n)};function Yf(t,e,n){return typeof t.isOptionDisabled=="function"?t.isOptionDisabled(e,n):!1}function Kf(t,e,n){if(n.indexOf(e)>-1)return!0;if(typeof t.isOptionSelected=="function")return t.isOptionSelected(e,n);var r=mi(t,e);return n.some(function(s){return mi(t,s)===r})}function Xf(t,e,n){return t.filterOption?t.filterOption(e,n):!0}var qf=function(e){var n=e.hideSelectedOptions,r=e.isMulti;return n===void 0?r:n},zk=1,Fl=(function(t){cx(n,t);var e=fx(n);function n(r){var s;if(ax(this,n),s=e.call(this,r),s.state={ariaSelection:null,focusedOption:null,focusedOptionId:null,focusableOptionsWithIds:[],focusedValue:null,inputIsHidden:!1,isFocused:!1,selectValue:[],clearFocusValueOnUpdate:!1,prevWasFocused:!1,inputIsHiddenAfterUpdate:void 0,prevProps:void 0,instancePrefix:"",isAppleDevice:!1},s.blockOptionHover=!1,s.isComposing=!1,s.commonProps=void 0,s.initialTouchX=0,s.initialTouchY=0,s.openAfterFocus=!1,s.scrollToFocusedOptionOnUpdate=!1,s.userIsDragging=void 0,s.controlRef=null,s.getControlRef=function(l){s.controlRef=l},s.focusedOptionRef=null,s.getFocusedOptionRef=function(l){s.focusedOptionRef=l},s.menuListRef=null,s.getMenuListRef=function(l){s.menuListRef=l},s.inputRef=null,s.getInputRef=function(l){s.inputRef=l},s.focus=s.focusInput,s.blur=s.blurInput,s.onChange=function(l,c){var d=s.props,u=d.onChange,f=d.name;c.name=f,s.ariaOnChange(l,c),u(l,c)},s.setValue=function(l,c,d){var u=s.props,f=u.closeMenuOnSelect,g=u.isMulti,m=u.inputValue;s.onInputChange("",{action:"set-value",prevInputValue:m}),f&&(s.setState({inputIsHiddenAfterUpdate:!g}),s.onMenuClose()),s.setState({clearFocusValueOnUpdate:!0}),s.onChange(l,{action:c,option:d})},s.selectOption=function(l){var c=s.props,d=c.blurInputOnSelect,u=c.isMulti,f=c.name,g=s.state.selectValue,m=u&&s.isOptionSelected(l,g),p=s.isOptionDisabled(l,g);if(m){var y=s.getOptionValue(l);s.setValue(g.filter(function(b){return s.getOptionValue(b)!==y}),"deselect-option",l)}else if(!p)u?s.setValue([].concat(Ur(g),[l]),"select-option",l):s.setValue(l,"select-option");else{s.ariaOnChange(l,{action:"select-option",option:l,name:f});return}d&&s.blurInput()},s.removeValue=function(l){var c=s.props.isMulti,d=s.state.selectValue,u=s.getOptionValue(l),f=d.filter(function(m){return s.getOptionValue(m)!==u}),g=zs(c,f,f[0]||null);s.onChange(g,{action:"remove-value",removedValue:l}),s.focusInput()},s.clearValue=function(){var l=s.state.selectValue;s.onChange(zs(s.props.isMulti,[],null),{action:"clear",removedValues:l})},s.popValue=function(){var l=s.props.isMulti,c=s.state.selectValue,d=c[c.length-1],u=c.slice(0,c.length-1),f=zs(l,u,u[0]||null);d&&s.onChange(f,{action:"pop-value",removedValue:d})},s.getFocusedOptionId=function(l){return $l(s.state.focusableOptionsWithIds,l)},s.getFocusableOptionsWithIds=function(){return jf(pi(s.props,s.state.selectValue),s.getElementId("option"))},s.getValue=function(){return s.state.selectValue},s.cx=function(){for(var l=arguments.length,c=new Array(l),d=0;dg||f>g}},s.onTouchEnd=function(l){s.userIsDragging||(s.controlRef&&!s.controlRef.contains(l.target)&&s.menuListRef&&!s.menuListRef.contains(l.target)&&s.blurInput(),s.initialTouchX=0,s.initialTouchY=0)},s.onControlTouchEnd=function(l){s.userIsDragging||s.onControlMouseDown(l)},s.onClearIndicatorTouchEnd=function(l){s.userIsDragging||s.onClearIndicatorMouseDown(l)},s.onDropdownIndicatorTouchEnd=function(l){s.userIsDragging||s.onDropdownIndicatorMouseDown(l)},s.handleInputChange=function(l){var c=s.props.inputValue,d=l.currentTarget.value;s.setState({inputIsHiddenAfterUpdate:!1}),s.onInputChange(d,{action:"input-change",prevInputValue:c}),s.props.menuIsOpen||s.onMenuOpen()},s.onInputFocus=function(l){s.props.onFocus&&s.props.onFocus(l),s.setState({inputIsHiddenAfterUpdate:!1,isFocused:!0}),(s.openAfterFocus||s.props.openMenuOnFocus)&&s.openMenu("first"),s.openAfterFocus=!1},s.onInputBlur=function(l){var c=s.props.inputValue;if(s.menuListRef&&s.menuListRef.contains(document.activeElement)){s.inputRef.focus();return}s.props.onBlur&&s.props.onBlur(l),s.onInputChange("",{action:"input-blur",prevInputValue:c}),s.onMenuClose(),s.setState({focusedValue:null,isFocused:!1})},s.onOptionHover=function(l){if(!(s.blockOptionHover||s.state.focusedOption===l)){var c=s.getFocusableOptions(),d=c.indexOf(l);s.setState({focusedOption:l,focusedOptionId:d>-1?s.getFocusedOptionId(l):null})}},s.shouldHideSelectedOptions=function(){return qf(s.props)},s.onValueInputFocus=function(l){l.preventDefault(),l.stopPropagation(),s.focus()},s.onKeyDown=function(l){var c=s.props,d=c.isMulti,u=c.backspaceRemovesValue,f=c.escapeClearsValue,g=c.inputValue,m=c.isClearable,p=c.isDisabled,y=c.menuIsOpen,b=c.onKeyDown,_=c.tabSelectsValue,x=c.openMenuOnFocus,E=s.state,C=E.focusedOption,k=E.focusedValue,N=E.selectValue;if(!p&&!(typeof b=="function"&&(b(l),l.defaultPrevented))){switch(s.blockOptionHover=!0,l.key){case"ArrowLeft":if(!d||g)return;s.focusValue("previous");break;case"ArrowRight":if(!d||g)return;s.focusValue("next");break;case"Delete":case"Backspace":if(g)return;if(k)s.removeValue(k);else{if(!u)return;d?s.popValue():m&&s.clearValue()}break;case"Tab":if(s.isComposing||l.shiftKey||!y||!_||!C||x&&s.isOptionSelected(C,N))return;s.selectOption(C);break;case"Enter":if(l.keyCode===229)break;if(y){if(!C||s.isComposing)return;s.selectOption(C);break}return;case"Escape":y?(s.setState({inputIsHiddenAfterUpdate:!1}),s.onInputChange("",{action:"menu-close",prevInputValue:g}),s.onMenuClose()):m&&f&&s.clearValue();break;case" ":if(g)return;if(!y){s.openMenu("first");break}if(!C)return;s.selectOption(C);break;case"ArrowUp":y?s.focusOption("up"):s.openMenu("last");break;case"ArrowDown":y?s.focusOption("down"):s.openMenu("first");break;case"PageUp":if(!y)return;s.focusOption("pageup");break;case"PageDown":if(!y)return;s.focusOption("pagedown");break;case"Home":if(!y)return;s.focusOption("first");break;case"End":if(!y)return;s.focusOption("last");break;default:return}l.preventDefault()}},s.state.instancePrefix="react-select-"+(s.props.instanceId||++zk),s.state.selectValue=ci(r.value),r.menuIsOpen&&s.state.selectValue.length){var o=s.getFocusableOptionsWithIds(),i=s.buildFocusableOptions(),a=i.indexOf(s.state.selectValue[0]);s.state.focusableOptionsWithIds=o,s.state.focusedOption=i[a],s.state.focusedOptionId=$l(o,i[a])}return s}return lx(n,[{key:"componentDidMount",value:function(){this.startListeningComposition(),this.startListeningToTouch(),this.props.closeMenuOnScroll&&document&&document.addEventListener&&document.addEventListener("scroll",this.onScroll,!0),this.props.autoFocus&&this.focusInput(),this.props.menuIsOpen&&this.state.focusedOption&&this.menuListRef&&this.focusedOptionRef&&hf(this.menuListRef,this.focusedOptionRef),_k()&&this.setState({isAppleDevice:!0})}},{key:"componentDidUpdate",value:function(s){var o=this.props,i=o.isDisabled,a=o.menuIsOpen,l=this.state.isFocused;(l&&!i&&s.isDisabled||l&&a&&!s.menuIsOpen)&&this.focusInput(),l&&i&&!s.isDisabled?this.setState({isFocused:!1},this.onMenuClose):!l&&!i&&s.isDisabled&&this.inputRef===document.activeElement&&this.setState({isFocused:!0}),this.menuListRef&&this.focusedOptionRef&&this.scrollToFocusedOptionOnUpdate&&(hf(this.menuListRef,this.focusedOptionRef),this.scrollToFocusedOptionOnUpdate=!1)}},{key:"componentWillUnmount",value:function(){this.stopListeningComposition(),this.stopListeningToTouch(),document.removeEventListener("scroll",this.onScroll,!0)}},{key:"onMenuOpen",value:function(){this.props.onMenuOpen()}},{key:"onMenuClose",value:function(){this.onInputChange("",{action:"menu-close",prevInputValue:this.props.inputValue}),this.props.onMenuClose()}},{key:"onInputChange",value:function(s,o){this.props.onInputChange(s,o)}},{key:"focusInput",value:function(){this.inputRef&&this.inputRef.focus()}},{key:"blurInput",value:function(){this.inputRef&&this.inputRef.blur()}},{key:"openMenu",value:function(s){var o=this,i=this.state,a=i.selectValue,l=i.isFocused,c=this.buildFocusableOptions(),d=s==="first"?0:c.length-1;if(!this.props.isMulti){var u=c.indexOf(a[0]);u>-1&&(d=u)}this.scrollToFocusedOptionOnUpdate=!(l&&this.menuListRef),this.setState({inputIsHiddenAfterUpdate:!1,focusedValue:null,focusedOption:c[d],focusedOptionId:this.getFocusedOptionId(c[d])},function(){return o.onMenuOpen()})}},{key:"focusValue",value:function(s){var o=this.state,i=o.selectValue,a=o.focusedValue;if(this.props.isMulti){this.setState({focusedOption:null});var l=i.indexOf(a);a||(l=-1);var c=i.length-1,d=-1;if(i.length){switch(s){case"previous":l===0?d=0:l===-1?d=c:d=l-1;break;case"next":l>-1&&l0&&arguments[0]!==void 0?arguments[0]:"first",o=this.props.pageSize,i=this.state.focusedOption,a=this.getFocusableOptions();if(a.length){var l=0,c=a.indexOf(i);i||(c=-1),s==="up"?l=c>0?c-1:a.length-1:s==="down"?l=(c+1)%a.length:s==="pageup"?(l=c-o,l<0&&(l=0)):s==="pagedown"?(l=c+o,l>a.length-1&&(l=a.length-1)):s==="last"&&(l=a.length-1),this.scrollToFocusedOptionOnUpdate=!0,this.setState({focusedOption:a[l],focusedValue:null,focusedOptionId:this.getFocusedOptionId(a[l])})}}},{key:"getTheme",value:(function(){return this.props.theme?typeof this.props.theme=="function"?this.props.theme(Pl):Me(Me({},Pl),this.props.theme):Pl})},{key:"getCommonProps",value:function(){var s=this.clearValue,o=this.cx,i=this.getStyles,a=this.getClassNames,l=this.getValue,c=this.selectOption,d=this.setValue,u=this.props,f=u.isMulti,g=u.isRtl,m=u.options,p=this.hasValue();return{clearValue:s,cx:o,getStyles:i,getClassNames:a,getValue:l,hasValue:p,isMulti:f,isRtl:g,options:m,selectOption:c,selectProps:u,setValue:d,theme:this.getTheme()}}},{key:"hasValue",value:function(){var s=this.state.selectValue;return s.length>0}},{key:"hasOptions",value:function(){return!!this.getFocusableOptions().length}},{key:"isClearable",value:function(){var s=this.props,o=s.isClearable,i=s.isMulti;return o===void 0?i:o}},{key:"isOptionDisabled",value:function(s,o){return Yf(this.props,s,o)}},{key:"isOptionSelected",value:function(s,o){return Kf(this.props,s,o)}},{key:"filterOption",value:function(s,o){return Xf(this.props,s,o)}},{key:"formatOptionLabel",value:function(s,o){if(typeof this.props.formatOptionLabel=="function"){var i=this.props.inputValue,a=this.state.selectValue;return this.props.formatOptionLabel(s,{context:o,inputValue:i,selectValue:a})}else return this.getOptionLabel(s)}},{key:"formatGroupLabel",value:function(s){return this.props.formatGroupLabel(s)}},{key:"startListeningComposition",value:(function(){document&&document.addEventListener&&(document.addEventListener("compositionstart",this.onCompositionStart,!1),document.addEventListener("compositionend",this.onCompositionEnd,!1))})},{key:"stopListeningComposition",value:function(){document&&document.removeEventListener&&(document.removeEventListener("compositionstart",this.onCompositionStart),document.removeEventListener("compositionend",this.onCompositionEnd))}},{key:"startListeningToTouch",value:(function(){document&&document.addEventListener&&(document.addEventListener("touchstart",this.onTouchStart,!1),document.addEventListener("touchmove",this.onTouchMove,!1),document.addEventListener("touchend",this.onTouchEnd,!1))})},{key:"stopListeningToTouch",value:function(){document&&document.removeEventListener&&(document.removeEventListener("touchstart",this.onTouchStart),document.removeEventListener("touchmove",this.onTouchMove),document.removeEventListener("touchend",this.onTouchEnd))}},{key:"renderInput",value:(function(){var s=this.props,o=s.isDisabled,i=s.isSearchable,a=s.inputId,l=s.inputValue,c=s.tabIndex,d=s.form,u=s.menuIsOpen,f=s.required,g=this.getComponents(),m=g.Input,p=this.state,y=p.inputIsHidden,b=p.ariaSelection,_=this.commonProps,x=a||this.getElementId("input"),E=Me(Me(Me({"aria-autocomplete":"list","aria-expanded":u,"aria-haspopup":!0,"aria-errormessage":this.props["aria-errormessage"],"aria-invalid":this.props["aria-invalid"],"aria-label":this.props["aria-label"],"aria-labelledby":this.props["aria-labelledby"],"aria-required":f,role:"combobox","aria-activedescendant":this.state.isAppleDevice?void 0:this.state.focusedOptionId||""},u&&{"aria-controls":this.getElementId("listbox")}),!i&&{"aria-readonly":!0}),this.hasValue()?b?.action==="initial-input-focus"&&{"aria-describedby":this.getElementId("live-region")}:{"aria-describedby":this.getElementId("placeholder")});return i?F.createElement(m,Ae({},_,{autoCapitalize:"none",autoComplete:"off",autoCorrect:"off",id:x,innerRef:this.getInputRef,isDisabled:o,isHidden:y,onBlur:this.onInputBlur,onChange:this.handleInputChange,onFocus:this.onInputFocus,spellCheck:"false",tabIndex:c,form:d,type:"text",value:l},E)):F.createElement(fk,Ae({id:x,innerRef:this.getInputRef,onBlur:this.onInputBlur,onChange:li,onFocus:this.onInputFocus,disabled:o,tabIndex:c,inputMode:"none",form:d,value:""},E))})},{key:"renderPlaceholderOrValue",value:function(){var s=this,o=this.getComponents(),i=o.MultiValue,a=o.MultiValueContainer,l=o.MultiValueLabel,c=o.MultiValueRemove,d=o.SingleValue,u=o.Placeholder,f=this.commonProps,g=this.props,m=g.controlShouldRenderValue,p=g.isDisabled,y=g.isMulti,b=g.inputValue,_=g.placeholder,x=this.state,E=x.selectValue,C=x.focusedValue,k=x.isFocused;if(!this.hasValue()||!m)return b?null:F.createElement(u,Ae({},f,{key:"placeholder",isDisabled:p,isFocused:k,innerProps:{id:this.getElementId("placeholder")}}),_);if(y)return E.map(function(S,w){var M=S===C,T="".concat(s.getOptionLabel(S),"-").concat(s.getOptionValue(S));return F.createElement(i,Ae({},f,{components:{Container:a,Label:l,Remove:c},isFocused:M,isDisabled:p,key:T,index:w,removeProps:{onClick:function(){return s.removeValue(S)},onTouchEnd:function(){return s.removeValue(S)},onMouseDown:function(L){L.preventDefault()}},data:S}),s.formatOptionLabel(S,"value"))});if(b)return null;var N=E[0];return F.createElement(d,Ae({},f,{data:N,isDisabled:p}),this.formatOptionLabel(N,"value"))}},{key:"renderClearIndicator",value:function(){var s=this.getComponents(),o=s.ClearIndicator,i=this.commonProps,a=this.props,l=a.isDisabled,c=a.isLoading,d=this.state.isFocused;if(!this.isClearable()||!o||l||!this.hasValue()||c)return null;var u={onMouseDown:this.onClearIndicatorMouseDown,onTouchEnd:this.onClearIndicatorTouchEnd,"aria-hidden":"true"};return F.createElement(o,Ae({},i,{innerProps:u,isFocused:d}))}},{key:"renderLoadingIndicator",value:function(){var s=this.getComponents(),o=s.LoadingIndicator,i=this.commonProps,a=this.props,l=a.isDisabled,c=a.isLoading,d=this.state.isFocused;if(!o||!c)return null;var u={"aria-hidden":"true"};return F.createElement(o,Ae({},i,{innerProps:u,isDisabled:l,isFocused:d}))}},{key:"renderIndicatorSeparator",value:function(){var s=this.getComponents(),o=s.DropdownIndicator,i=s.IndicatorSeparator;if(!o||!i)return null;var a=this.commonProps,l=this.props.isDisabled,c=this.state.isFocused;return F.createElement(i,Ae({},a,{isDisabled:l,isFocused:c}))}},{key:"renderDropdownIndicator",value:function(){var s=this.getComponents(),o=s.DropdownIndicator;if(!o)return null;var i=this.commonProps,a=this.props.isDisabled,l=this.state.isFocused,c={onMouseDown:this.onDropdownIndicatorMouseDown,onTouchEnd:this.onDropdownIndicatorTouchEnd,"aria-hidden":"true"};return F.createElement(o,Ae({},i,{innerProps:c,isDisabled:a,isFocused:l}))}},{key:"renderMenu",value:function(){var s=this,o=this.getComponents(),i=o.Group,a=o.GroupHeading,l=o.Menu,c=o.MenuList,d=o.MenuPortal,u=o.LoadingMessage,f=o.NoOptionsMessage,g=o.Option,m=this.commonProps,p=this.state.focusedOption,y=this.props,b=y.captureMenuScroll,_=y.inputValue,x=y.isLoading,E=y.loadingMessage,C=y.minMenuHeight,k=y.maxMenuHeight,N=y.menuIsOpen,S=y.menuPlacement,w=y.menuPosition,M=y.menuPortalTarget,T=y.menuShouldBlockScroll,P=y.menuShouldScrollIntoView,L=y.noOptionsMessage,$=y.onMenuScrollToTop,A=y.onMenuScrollToBottom;if(!N)return null;var D=function(B,G){var V=B.type,X=B.data,K=B.isDisabled,Z=B.isSelected,ee=B.label,ne=B.value,he=p===X,J=K?void 0:function(){return s.onOptionHover(X)},le=K?void 0:function(){return s.selectOption(X)},ue="".concat(s.getElementId("option"),"-").concat(G),ae={id:ue,onClick:le,onMouseMove:J,onMouseOver:J,tabIndex:-1,role:"option","aria-selected":s.state.isAppleDevice?void 0:Z};return F.createElement(g,Ae({},m,{innerProps:ae,data:X,isDisabled:K,isSelected:Z,key:ue,label:ee,type:V,value:ne,isFocused:he,innerRef:he?s.getFocusedOptionRef:void 0}),s.formatOptionLabel(B.data,"menu"))},O;if(this.hasOptions())O=this.getCategorizedOptions().map(function(U){if(U.type==="group"){var B=U.data,G=U.options,V=U.index,X="".concat(s.getElementId("group"),"-").concat(V),K="".concat(X,"-heading");return F.createElement(i,Ae({},m,{key:X,data:B,options:G,Heading:a,headingProps:{id:K,data:U.data},label:s.formatGroupLabel(U.data)}),U.options.map(function(Z){return D(Z,"".concat(V,"-").concat(Z.index))}))}else if(U.type==="option")return D(U,"".concat(U.index))});else if(x){var z=E({inputValue:_});if(z===null)return null;O=F.createElement(u,m,z)}else{var H=L({inputValue:_});if(H===null)return null;O=F.createElement(f,m,H)}var j={minMenuHeight:C,maxMenuHeight:k,menuPlacement:S,menuPosition:w,menuShouldScrollIntoView:P},W=F.createElement(Gw,Ae({},m,j),function(U){var B=U.ref,G=U.placerProps,V=G.placement,X=G.maxHeight;return F.createElement(l,Ae({},m,j,{innerRef:B,innerProps:{onMouseDown:s.onMenuMouseDown,onMouseMove:s.onMenuMouseMove},isLoading:x,placement:V}),F.createElement(yk,{captureEnabled:b,onTopArrive:$,onBottomArrive:A,lockEnabled:T},function(K){return F.createElement(c,Ae({},m,{innerRef:function(ee){s.getMenuListRef(ee),K(ee)},innerProps:{role:"listbox","aria-multiselectable":m.isMulti,id:s.getElementId("listbox")},isLoading:x,maxHeight:X,focusedOption:p}),O)}))});return M||w==="fixed"?F.createElement(d,Ae({},m,{appendTo:M,controlElement:this.controlRef,menuPlacement:S,menuPosition:w}),W):W}},{key:"renderFormField",value:function(){var s=this,o=this.props,i=o.delimiter,a=o.isDisabled,l=o.isMulti,c=o.name,d=o.required,u=this.state.selectValue;if(d&&!this.hasValue()&&!a)return F.createElement(wk,{name:c,onFocus:this.onValueInputFocus});if(!(!c||a))if(l)if(i){var f=u.map(function(p){return s.getOptionValue(p)}).join(i);return F.createElement("input",{name:c,type:"hidden",value:f})}else{var g=u.length>0?u.map(function(p,y){return F.createElement("input",{key:"i-".concat(y),name:c,type:"hidden",value:s.getOptionValue(p)})}):F.createElement("input",{name:c,type:"hidden",value:""});return F.createElement("div",null,g)}else{var m=u[0]?this.getOptionValue(u[0]):"";return F.createElement("input",{name:c,type:"hidden",value:m})}}},{key:"renderLiveRegion",value:function(){var s=this.commonProps,o=this.state,i=o.ariaSelection,a=o.focusedOption,l=o.focusedValue,c=o.isFocused,d=o.selectValue,u=this.getFocusableOptions();return F.createElement(ik,Ae({},s,{id:this.getElementId("live-region"),ariaSelection:i,focusedOption:a,focusedValue:l,isFocused:c,selectValue:d,focusableOptions:u,isAppleDevice:this.state.isAppleDevice}))}},{key:"render",value:function(){var s=this.getComponents(),o=s.Control,i=s.IndicatorsContainer,a=s.SelectContainer,l=s.ValueContainer,c=this.props,d=c.className,u=c.id,f=c.isDisabled,g=c.menuIsOpen,m=this.state.isFocused,p=this.commonProps=this.getCommonProps();return F.createElement(a,Ae({},p,{className:d,innerProps:{id:u,onKeyDown:this.onKeyDown},isDisabled:f,isFocused:m}),this.renderLiveRegion(),F.createElement(o,Ae({},p,{innerRef:this.getControlRef,innerProps:{onMouseDown:this.onControlMouseDown,onTouchEnd:this.onControlTouchEnd},isDisabled:f,isFocused:m,menuIsOpen:g}),F.createElement(l,Ae({},p,{isDisabled:f}),this.renderPlaceholderOrValue(),this.renderInput()),F.createElement(i,Ae({},p,{isDisabled:f}),this.renderClearIndicator(),this.renderLoadingIndicator(),this.renderIndicatorSeparator(),this.renderDropdownIndicator())),this.renderMenu(),this.renderFormField())}}],[{key:"getDerivedStateFromProps",value:function(s,o){var i=o.prevProps,a=o.clearFocusValueOnUpdate,l=o.inputIsHiddenAfterUpdate,c=o.ariaSelection,d=o.isFocused,u=o.prevWasFocused,f=o.instancePrefix,g=s.options,m=s.value,p=s.menuIsOpen,y=s.inputValue,b=s.isMulti,_=ci(m),x={};if(i&&(m!==i.value||g!==i.options||p!==i.menuIsOpen||y!==i.inputValue)){var E=p?Pk(s,_):[],C=p?jf(pi(s,_),"".concat(f,"-option")):[],k=a?$k(o,_):null,N=Fk(o,E),S=$l(C,N);x={selectValue:_,focusedOption:N,focusedOptionId:S,focusableOptionsWithIds:C,focusedValue:k,clearFocusValueOnUpdate:!1}}var w=l!=null&&s!==i?{inputIsHidden:l,inputIsHiddenAfterUpdate:void 0}:{},M=c,T=d&&u;return d&&!T&&(M={value:zs(b,_,_[0]||null),options:_,action:"initial-input-focus"},T=!u),c?.action==="initial-input-focus"&&(M=null),Me(Me(Me({},x),w),{},{prevProps:s,ariaSelection:M,prevWasFocused:T})}}]),n})(v.Component);Fl.defaultProps=Lk;var Vk=v.forwardRef(function(t,e){var n=Du(t);return F.createElement(Fl,Ae({ref:e},n))}),Zf=Vk,Hk=["allowCreateWhileLoading","createOptionPosition","formatCreateLabel","isValidNewOption","getNewOptionData","onCreateOption","options","onChange"],Qf=function(){var e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:"",n=arguments.length>1?arguments[1]:void 0,r=arguments.length>2?arguments[2]:void 0,s=String(e).toLowerCase(),o=String(r.getOptionValue(n)).toLowerCase(),i=String(r.getOptionLabel(n)).toLowerCase();return o===s||i===s},zl={formatCreateLabel:function(e){return'Create "'.concat(e,'"')},isValidNewOption:function(e,n,r,s){return!(!e||n.some(function(o){return Qf(e,o,s)})||r.some(function(o){return Qf(e,o,s)}))},getNewOptionData:function(e,n){return{label:n,value:e,__isNew__:!0}}};function Uk(t){var e=t.allowCreateWhileLoading,n=e===void 0?!1:e,r=t.createOptionPosition,s=r===void 0?"last":r,o=t.formatCreateLabel,i=o===void 0?zl.formatCreateLabel:o,a=t.isValidNewOption,l=a===void 0?zl.isValidNewOption:a,c=t.getNewOptionData,d=c===void 0?zl.getNewOptionData:c,u=t.onCreateOption,f=t.options,g=f===void 0?[]:f,m=t.onChange,p=An(t,Hk),y=p.getOptionValue,b=y===void 0?Vf:y,_=p.getOptionLabel,x=_===void 0?zf:_,E=p.inputValue,C=p.isLoading,k=p.isMulti,N=p.value,S=p.name,w=v.useMemo(function(){return l(E,ci(N),g,{getOptionValue:b,getOptionLabel:x})?d(E,i(E)):void 0},[i,d,x,b,E,l,g,N]),M=v.useMemo(function(){return(n||!C)&&w?s==="first"?[w].concat(Ur(g)):[].concat(Ur(g),[w]):g},[n,s,C,w,g]),T=v.useCallback(function(P,L){if(L.action!=="select-option")return m(P,L);var $=Array.isArray(P)?P:[P];if($[$.length-1]===w){if(u)u(E);else{var A=d(E,E),D={action:"create-option",name:S,option:A};m(zs(k,[].concat(Ur(ci(N)),[A]),A),D)}return}m(P,L)},[d,E,k,S,w,u,m,N]);return Me(Me({},p),{},{options:M,onChange:T})}var Bk=v.forwardRef(function(t,e){var n=Du(t),r=Uk(n);return F.createElement(Fl,Ae({ref:e},r))}),Jf=Bk;const $t={control:"input-tag",valueContainer:"gap-1 flex flex-wrap",multiValue:{blue:"bg-blue-100 dark:bg-blue-900/20 rounded px-1.5 py-0.5 flex items-center gap-1",green:"bg-green-100 dark:bg-green-900/20 rounded px-1.5 py-0.5 flex items-center gap-1",amber:"bg-amber-100 dark:bg-amber-900/20 rounded px-1.5 py-0.5 flex items-center gap-1",red:"bg-red-100 dark:bg-red-900/20 rounded px-1.5 py-0.5 flex items-center gap-1",purple:"bg-purple-100 dark:bg-purple-900/20 rounded px-1.5 py-0.5 flex items-center gap-1",gray:"bg-gray-100 dark:bg-gray-700/50 rounded px-1.5 py-0.5 flex items-center gap-1"},multiValueLabel:{blue:"text-blue-700 dark:text-blue-300 text-xs",green:"text-green-700 dark:text-green-300 text-xs font-semibold",amber:"text-amber-700 dark:text-amber-300 text-xs font-semibold",red:"text-red-700 dark:text-red-300 text-xs font-semibold",purple:"text-purple-700 dark:text-purple-300 text-xs font-semibold",gray:"text-gray-700 dark:text-gray-300 text-xs"},multiValueRemove:{blue:"text-blue-700 dark:text-blue-300 hover:bg-red-100 dark:hover:bg-red-900/30 hover:text-red-600 dark:hover:text-red-400 rounded px-1",green:"text-green-700 dark:text-green-300 hover:bg-red-100 dark:hover:bg-red-900/30 hover:text-red-600 dark:hover:text-red-400 rounded px-1",amber:"text-amber-700 dark:text-amber-300 hover:bg-red-100 dark:hover:bg-red-900/30 hover:text-red-600 dark:hover:text-red-400 rounded px-1",red:"text-red-700 dark:text-red-300 hover:bg-red-100 dark:hover:bg-red-900/30 hover:text-red-600 dark:hover:text-red-400 rounded px-1",purple:"text-purple-700 dark:text-purple-300 hover:bg-red-100 dark:hover:bg-red-900/30 hover:text-red-600 dark:hover:text-red-400 rounded px-1",gray:"text-gray-700 dark:text-gray-300 hover:bg-red-100 dark:hover:bg-red-900/30 hover:text-red-600 dark:hover:text-red-400 rounded px-1"},input:"text-gray-900 dark:text-white text-sm",placeholder:"text-gray-400 dark:text-gray-500 text-sm",menu:"mt-2 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg",menuList:"py-1",option:{base:"px-3 py-2 cursor-pointer text-gray-900 dark:text-white text-[13px]",selected:"bg-blue-100 dark:bg-blue-900/20",focused:"bg-gray-100 dark:bg-gray-700",default:"bg-white dark:bg-gray-800"},noOptionsMessage:"px-3 py-2 text-gray-500 dark:text-gray-400 text-[13px]",checkbox:{base:"w-4 h-4 rounded border flex items-center justify-center text-white text-[10px] shrink-0",selected:"bg-blue-500 border-blue-500",unselected:"border-gray-400 dark:border-gray-500"}},Rf=t=>{const{data:e,isSelected:n}=t;return h.jsx(gi.Option,{...t,children:h.jsxs("div",{className:"flex items-center gap-2",children:[h.jsx("span",{className:`${$t.checkbox.base} ${n?$t.checkbox.selected:$t.checkbox.unselected}`,children:n?"✓":""}),h.jsxs("div",{className:"flex-1 min-w-0",children:[h.jsxs("div",{className:"flex items-center gap-1",children:[h.jsx("span",{className:"text-[13px]",children:e.label}),e.count!==void 0&&e.count>0&&h.jsxs("span",{className:"text-gray-500 dark:text-gray-400 text-[10px]",children:["(",e.count,")"]})]}),e.description&&h.jsx("p",{className:"text-xs text-gray-500 dark:text-gray-400 truncate",children:e.description})]})]})})},eh=t=>h.jsx(gi.GroupHeading,{...t,children:h.jsx("div",{className:"px-3 py-1.5 text-xs font-medium text-gray-500 dark:text-gray-400 bg-gray-50 dark:bg-gray-800/50 border-b border-gray-200 dark:border-gray-700",children:t.data.label})}),jk=t=>{const{data:e}=t;return h.jsxs(gi.MultiValueLabel,{...t,children:[h.jsx("span",{children:e.label}),e.count!==void 0&&e.count>0&&h.jsxs("span",{className:"text-xs opacity-60 ml-1",children:["(",e.count,")"]})]})},Wk=({value:t,options:e=[],onChange:n,creatable:r=!1,placeholder:s="검색 또는 입력...",noOptionsMessage:o="검색 결과가 없습니다",formatCreateLabel:i,createLabelSuffix:a="추가",maxItems:l,disabled:c=!1,onBeforeRemove:d,onCreateOption:u,className:f="",tagVariants:g,defaultVariant:m="blue",name:p,isMulti:y=!0,isClearable:b=!0,isSearchable:_=!0,delimiters:x=[","],splitOnPaste:E=!0,onInputChange:C,id:k,editorAttrs:N})=>{const S=c===!0||c==="true",w=v.useCallback(J=>J==null?[]:Array.isArray(J)?J:[J],[]),[M,T]=v.useState(()=>w(t)),[P,L]=v.useState(""),$=v.useRef(void 0);v.useEffect(()=>{const J=(ue,ae)=>!ue&&!ae?!0:!ue||!ae||ue.length!==ae.length?!1:ue.every((ve,fe)=>ve===ae[fe]),le=w(t);J(le,$.current)||(T(le),$.current=le)},[t,w]);const A=v.useMemo(()=>i?typeof i=="string"?J=>`${i} "${J}"`:i:J=>h.jsxs("span",{className:"text-[13px]",children:[h.jsx("strong",{children:J})," ",a]}),[i,a]),D=v.useMemo(()=>!M||!Array.isArray(M)?[]:M.map(J=>e.find(ue=>ue.value===J)||{value:J,label:String(J)}).filter(J=>J!==void 0),[M,e]),O=v.useMemo(()=>{const J=new Set(e.map(we=>we.value)),le=M?M.filter(we=>!J.has(we)).map(we=>({value:we,label:String(we)})):[],ue=[...e,...le];if(!ue.some(we=>we.group))return ue;const ve=new Map,fe=[];ue.forEach(we=>{if(we.group){const He=ve.get(we.group)||[];He.push(we),ve.set(we.group,He)}else fe.push(we)});const ye=[];return ve.forEach((we,He)=>{ye.push({label:He,options:we})}),fe.length>0&&ye.push({label:"",options:fe}),ye},[e,M]),z=v.useCallback(J=>({target:{value:J},currentTarget:{value:J},preventDefault:()=>{},stopPropagation:()=>{}}),[]),H=v.useCallback(J=>{if(!x?.length)return[J.trim()].filter(Boolean);const le=x.map(ae=>ae.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")),ue=new RegExp(`[${le.join("")}]`);return J.split(ue).map(ae=>ae.trim()).filter(Boolean)},[x]),j=v.useCallback((J,le)=>{if(le.action!=="input-change"){L(J);return}if(C&&C(z(J)),!x?.some(fe=>J.includes(fe))||!r||!y){L(J);return}const ve=H(J).filter(fe=>fe&&!M.includes(fe));if(ve.length>0){const fe=l?l-M.length:1/0,ye=ve.slice(0,fe);if(ye.length>0){const we=[...M,...ye];T(we),n(z(we))}}L("")},[x,r,y,H,M,l,n,z,C]),W=v.useCallback(J=>{if(!E||!r||!y)return;const le=J.clipboardData.getData("text"),ue=H(le);if(ue.length<=1)return;J.preventDefault();const ae=ue.filter(ve=>ve&&!M.includes(ve));if(ae.length>0){const ve=l?l-M.length:1/0,fe=ae.slice(0,ve);if(fe.length>0){const ye=[...M,...fe];T(ye),n(z(ye))}}},[E,r,y,H,M,l,n,z]),U=v.useCallback((J,le)=>{if((le.action==="remove-value"||le.action==="pop-value")&&le.removedValue&&d){const ae=d(le.removedValue);if(ae!==!0){const ve=typeof ae=="string"?ae:`"${le.removedValue.label}"은(는) 제거할 수 없습니다.`;alert(ve);return}}if(l&&J.length>l)return;const ue=J.map(ae=>ae.value);T(ue),n(z(ue))},[n,d,l,z]),B=v.useCallback((J,le)=>{if(le.action==="clear"&&M.length>0&&d){const ve=e.find(ye=>ye.value===M[0])||{value:M[0],label:String(M[0])},fe=d(ve);if(fe!==!0){const ye=typeof fe=="string"?fe:`"${ve.label}"은(는) 제거할 수 없습니다.`;alert(ye);return}}const ue=J?J.value:null,ae=J?[J.value]:[];T(ae),n(z(ue))},[n,d,M,e,z]),G=v.useCallback(J=>{u&&u(J);const le=[...M||[],J];T(le),n(z(le))},[n,u,M,z]),V=v.useCallback(J=>!J||!J.trim()?!1:!M?.includes(J.trim()),[M]),X=v.useCallback(J=>{const le=P.trim();if(J.key==="Tab"&&r&&y&&le){if(M.includes(le))J.preventDefault(),L("");else if(!l||M.lengthg&&g[J]?g[J]:m,[g,m]),Z={control:()=>`${$t.control} ${f} ${S?"opacity-60 cursor-not-allowed":""}`,valueContainer:()=>$t.valueContainer,multiValue:J=>{const le=K(J.data.value);return $t.multiValue[le]},multiValueLabel:J=>{const le=K(J.data.value);return $t.multiValueLabel[le]},multiValueRemove:J=>{const le=K(J.data.value);return`${$t.multiValueRemove[le]} ${S?"pointer-events-none":""}`},singleValue:()=>"text-gray-900 dark:text-white text-sm",input:()=>$t.input,placeholder:()=>$t.placeholder,menu:()=>$t.menu,menuList:()=>$t.menuList,menuPortal:()=>"z-[9999]",option:J=>`${$t.option.base} ${J.isSelected?$t.option.selected:J.isFocused?$t.option.focused:$t.option.default}`,noOptionsMessage:()=>$t.noOptionsMessage,clearIndicator:()=>"text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 cursor-pointer px-1",dropdownIndicator:()=>"text-gray-400 dark:text-gray-500 px-1"},ee={options:O,onKeyDown:X,placeholder:s,noOptionsMessage:()=>o,isDisabled:S,isSearchable:_,menuIsOpen:void 0,openMenuOnClick:!0,openMenuOnFocus:!0,menuPortalTarget:typeof document<"u"?document.body:null,menuPosition:"fixed",classNamePrefix:"tag-input",unstyled:!0,classNames:Z,...r&&y||C?{inputValue:P,onInputChange:j}:{}},ne=y?M.join(","):M[0]??"";if(!y){const J={...ee,isMulti:!1,value:D[0]||null,onChange:B,isClearable:b,closeMenuOnSelect:!0,components:{Option:Rf,GroupHeading:eh,IndicatorSeparator:()=>null,...b?{}:{ClearIndicator:()=>null}}};return r?h.jsxs(I,{id:k,...N,children:[p&&h.jsx("input",{type:"hidden",name:p,value:ne}),h.jsx(Jf,{...J,onCreateOption:G,formatCreateLabel:A,isValidNewOption:V,createOptionPosition:"first"})]}):h.jsxs(I,{id:k,...N,children:[p&&h.jsx("input",{type:"hidden",name:p,value:ne}),h.jsx(Zf,{...J})]})}const he={...ee,isMulti:!0,value:D,onChange:U,closeMenuOnSelect:!1,hideSelectedOptions:!1,backspaceRemovesValue:!1,isClearable:!1,components:{Option:Rf,GroupHeading:eh,MultiValueLabel:jk,ClearIndicator:()=>null,DropdownIndicator:()=>null,IndicatorSeparator:()=>null}};return r?h.jsxs(I,{id:k,...N,onPaste:W,children:[p&&h.jsx("input",{type:"hidden",name:p,value:ne}),h.jsx(Jf,{...he,onCreateOption:G,formatCreateLabel:A,isValidNewOption:V,createOptionPosition:"first"})]}):h.jsxs(I,{id:k,...N,children:[p&&h.jsx("input",{type:"hidden",name:p,value:ne}),h.jsx(Zf,{...he})]})},Yr=(t,e)=>window.G7Core?.t?.(t,e)??t,Gk=()=>window.G7Core?.locale?.supported?.()??["ko","en"],Yk=()=>window.G7Core?.locale?.current?.()??"ko",Vl=t=>{const e=Yr(`common.language_${t}`);return e===`common.language_${t}`?{ko:"한국어",en:"English",ja:"日本語",zh:"中文"}[t]||t.toUpperCase():e},Kk=({value:t=[],onChange:e,placeholder:n="입력 후 Enter",disabled:r=!1,creatable:s=!0,delimiters:o=[","],className:i="",name:a,error:l,maxItems:c,modalId:d,statePath:u,__componentContext:f,id:g,editorAttrs:m})=>{const p=v.useMemo(()=>Gk(),[]),y=v.useMemo(()=>Yk(),[]),[b,_]=v.useState(()=>t||[]),[x,E]=v.useState(""),[C,k]=v.useState(!1),[N,S]=v.useState(null),[w,M]=v.useState({}),T=v.useRef(null),P=v.useRef(void 0);v.useEffect(()=>{const V=(K,Z)=>!K&&!Z?!0:!K||!Z||K.length!==Z.length?!1:JSON.stringify(K)===JSON.stringify(Z),X=t||[];V(X,P.current)||(_(X),P.current=X)},[t]);const L=v.useCallback(V=>({target:{name:a,value:V},currentTarget:{name:a,value:V},preventDefault:()=>{},stopPropagation:()=>{}}),[a]),$=v.useCallback(V=>{const X=[];for(const K of p){const Z=V[K];Z?.trim()&&X.push(`${K.toUpperCase()}: ${Z}`)}return X.length>0?X.join(" / "):""},[p]),A=v.useCallback(V=>{const X=b.filter((K,Z)=>Z!==V);_(X),e?.(L(X))},[b,e,L]),D=v.useCallback((V,X,K)=>{const Z=window.G7Core;if(!Z?.dispatch)return;const ee=p.map(ne=>({code:ne,label:Vl(ne)}));Z.dispatch({handler:"setState",params:{target:"global",multilingualTagEdit:{fieldName:a,editingIndex:V,values:X,supportedLocales:ee,defaultLocale:y,statePath:u,currentTags:K}}}),Z.dispatch({handler:"openModal",target:d},{componentContext:f})},[a,p,y,d,u,f]),O=v.useCallback(V=>{r||(d?D(V,{...b[V]},b):(S(V),M({...b[V]}),k(!0)))},[r,b,d,D]),z=v.useCallback((V="")=>{const X={};p.forEach(K=>{X[K]=K===y?V:""}),d?(D(null,X,b),E("")):(S(null),M(X),k(!0),E(""))},[p,y,d,D,b]),H=v.useCallback(()=>{if(!Object.values(w).some(K=>K.trim()!=="")){k(!1);return}let X;N!==null?X=b.map((K,Z)=>Z===N?w:K):X=[...b,w],_(X),e?.(L(X)),k(!1),M({}),S(null)},[w,N,b,e,L]),j=v.useCallback(()=>{k(!1),M({}),S(null)},[]),W=v.useCallback(V=>{const X=V.target.value;if(o.some(Z=>X.includes(Z))&&s){const Z=o.map(he=>he.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")),ee=new RegExp(`[${Z.join("")}]`),ne=X.split(ee).map(he=>he.trim()).filter(Boolean);if(ne.length>0){z(ne[0]);return}}E(X)},[o,s,z]),U=v.useCallback(V=>{if(V.key==="Enter"&&x.trim()&&s){if(V.preventDefault(),c&&b.length>=c)return;z(x.trim())}},[x,s,c,b.length,z]),B=v.useCallback((V,X)=>{M(K=>({...K,[V]:X}))},[]),G=!!l;return h.jsxs(I,{className:i,id:g,...m,children:[h.jsxs(I,{className:`flex flex-wrap gap-1.5 p-2 border rounded-lg bg-white dark:bg-gray-700 min-h-[42px] ${G?"border-red-500 dark:border-red-400":"border-gray-300 dark:border-gray-600 focus-within:ring-2 focus-within:ring-blue-500 focus-within:border-blue-500"} ${r?"opacity-60 bg-gray-100 dark:bg-gray-800 cursor-not-allowed":""}`,children:[b.map((V,X)=>h.jsxs(I,{className:"flex items-center gap-1 bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 rounded px-2 py-0.5 text-sm",children:[h.jsx(Q,{className:`cursor-pointer hover:underline ${r?"cursor-not-allowed":""}`,onClick:()=>O(X),children:$(V)}),!r&&h.jsx(oe,{type:"button",className:"p-0 h-4 w-4 min-w-0 border-0 bg-transparent text-blue-600 dark:text-blue-400 hover:text-red-600 dark:hover:text-red-400 hover:bg-transparent",onClick:K=>{K.stopPropagation(),A(X)},children:h.jsx(re,{name:"times",className:"text-xs"})})]},X)),s&&!r&&(!c||b.lengthh.jsxs(I,{className:"space-y-1",children:[h.jsxs(I,{className:"flex items-center gap-2",children:[h.jsx(Q,{className:"text-sm font-medium text-gray-700 dark:text-gray-300 w-16",children:Vl(V)}),h.jsxs(Q,{className:"text-xs text-gray-400 dark:text-gray-500",children:["(",V.toUpperCase(),")"]})]}),h.jsx(kt,{type:"text",value:w[V]??"",onChange:X=>B(V,X.target.value),placeholder:`${Vl(V)} 값 입력`,className:"w-full",autoFocus:V===y})]},V)),h.jsxs(I,{className:"flex justify-end gap-2 pt-4 border-t border-gray-200 dark:border-gray-700",children:[h.jsx(oe,{type:"button",variant:"secondary",onClick:j,children:Yr("common.cancel")}),h.jsx(oe,{type:"button",variant:"primary",onClick:H,children:Yr(N!==null?"common.save":"common.add")})]})]})}),a&&h.jsx("input",{type:"hidden",name:a,value:JSON.stringify(b)})]})},th=(t,e)=>window.G7Core?.t?.(t,e)??t,nh=({type:t,identifier:e,name:n,installedModules:r=[],installedPlugins:s=[],className:o="",style:i,id:a,editorAttrs:l})=>{const c={module:de.Cubes,plugin:de.PuzzlePiece},d={module:th("common.module"),plugin:th("common.plugin")},u=v.useMemo(()=>{if(n)return n;if(e){const p=(t==="module"?r:s)?.find(y=>y.identifier===e);if(p)return p.name}},[n,e,t,r,s]),f=c[t],g=u?`${u} ${d[t]}`:d[t];return h.jsxs(Q,{className:`inline-flex items-center gap-1.5 px-2 py-1 rounded text-xs font-medium text-gray-400 dark:text-gray-500 ${o}`,style:i,id:a,...l,children:[h.jsx(re,{name:f,className:"w-3.5 h-3.5"}),h.jsx(Q,{children:g})]})},Xk=({options:t=[],value:e,onChange:n,multiple:r=!1,searchPlaceholder:s="Search...",onSearch:o,noResultsText:i="No results found",initialText:a="Type to search",triggerLabel:l="Select",triggerIcon:c,disabled:d=!1,className:u="",id:f,editorAttrs:g})=>{const[m,p]=v.useState(!1),[y,b]=v.useState(""),_=v.useRef(null),x=v.useRef(null),E=v.useRef(`searchable-dropdown-input-${Math.random().toString(36).slice(2,9)}`).current,C=v.useMemo(()=>e==null?[]:Array.isArray(e)?e:[e],[e]),k=v.useMemo(()=>{if(!y.trim())return t;const A=y.toLowerCase();return t.filter(D=>D.label.toLowerCase().includes(A)||D.description?.toLowerCase().includes(A))},[t,y]),N=v.useMemo(()=>{if(C.length===0)return l;if(r){const D=C.length;return D===1?t.find(z=>String(z.value)===String(C[0]))?.label||l:`${D} selected`}return t.find(D=>String(D.value)===String(C[0]))?.label||l},[C,t,r,l]);v.useEffect(()=>{const A=D=>{_.current&&!_.current.contains(D.target)&&p(!1)};return m&&document.addEventListener("mousedown",A),()=>{document.removeEventListener("mousedown",A)}},[m]),v.useEffect(()=>{const A=D=>{D.key==="Escape"&&m&&(p(!1),x.current?.focus())};return document.addEventListener("keydown",A),()=>document.removeEventListener("keydown",A)},[m]),v.useEffect(()=>{if(!m){b("");return}const A=setTimeout(()=>{document.getElementById(E)?.focus()},50);return()=>{clearTimeout(A)}},[m,E]);const S=v.useCallback(()=>{d||p(A=>!A)},[d]),w=v.useCallback(A=>{const D=A.target.value;b(D),o?.(D)},[o]),M=v.useCallback(A=>{if(r){const O=C.some(z=>String(z)===String(A))?C.filter(z=>String(z)!==String(A)):[...C,A];n?.(O)}else n?.(A),p(!1),x.current?.focus()},[r,C,n]),T=A=>C.some(D=>String(D)===String(A)),P=y.length===0&&t.length===0,L=y.length>0&&k.length===0,$=k.length>0;return h.jsxs(I,{ref:_,className:`relative inline-block ${u}`,id:f,...g,children:[h.jsxs(oe,{ref:x,type:"button",onClick:S,disabled:d,className:` px-4 py-2.5 bg-gray-100 dark:bg-gray-700 border-0 rounded-xl text-gray-700 dark:text-gray-200 font-medium @@ -182,27 +181,27 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho text-left cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed min-w-[200px] - `,"aria-haspopup":"listbox","aria-expanded":m,children:[h.jsxs(I,{className:"flex items-center gap-2",children:[c,h.jsx(Q,{className:"truncate",children:N})]}),h.jsx(Zn,{className:`w-4 h-4 flex-shrink-0 transition-transform duration-200 ${m?"rotate-180":""}`,fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:h.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M19 9l-7 7-7-7"})})]}),m&&h.jsxs(I,{className:"absolute z-50 w-full mt-2 bg-white dark:bg-gray-800 rounded-2xl shadow-lg border border-gray-200 dark:border-gray-600 overflow-hidden min-w-[280px]",role:"listbox",children:[h.jsx(I,{className:"p-3 border-b border-gray-200 dark:border-gray-700",children:h.jsx("input",{id:M,type:"text",value:y,onChange:w,placeholder:s,className:"w-full px-3 py-2 bg-gray-50 dark:bg-gray-700 border border-gray-200 dark:border-gray-600 rounded-lg text-gray-900 dark:text-white placeholder-gray-400 dark:placeholder-gray-500 focus:ring-2 focus:ring-blue-500 focus:border-transparent focus:outline-none"})}),h.jsxs(I,{className:"max-h-80 overflow-auto",children:[P&&h.jsx(I,{className:"px-4 py-6 text-center text-gray-500 dark:text-gray-400",children:a}),L&&h.jsx(I,{className:"px-4 py-6 text-center text-gray-500 dark:text-gray-400",children:i}),$&&h.jsx(I,{className:"py-2",children:k.map(A=>{const O=D(A.value);return h.jsxs(ae,{type:"button",onClick:()=>!A.disabled&&E(A.value),disabled:A.disabled,className:` + `,"aria-haspopup":"listbox","aria-expanded":m,children:[h.jsxs(I,{className:"flex items-center gap-2",children:[c,h.jsx(Q,{className:"truncate",children:N})]}),h.jsx(Un,{className:`w-4 h-4 flex-shrink-0 transition-transform duration-200 ${m?"rotate-180":""}`,fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:h.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M19 9l-7 7-7-7"})})]}),m&&h.jsxs(I,{className:"absolute z-50 w-full mt-2 bg-white dark:bg-gray-800 rounded-2xl shadow-lg border border-gray-200 dark:border-gray-600 overflow-hidden min-w-[280px]",role:"listbox",children:[h.jsx(I,{className:"p-3 border-b border-gray-200 dark:border-gray-700",children:h.jsx("input",{id:E,type:"text",value:y,onChange:w,placeholder:s,className:"w-full px-3 py-2 bg-gray-50 dark:bg-gray-700 border border-gray-200 dark:border-gray-600 rounded-lg text-gray-900 dark:text-white placeholder-gray-400 dark:placeholder-gray-500 focus:ring-2 focus:ring-blue-500 focus:border-transparent focus:outline-none"})}),h.jsxs(I,{className:"max-h-80 overflow-auto",children:[P&&h.jsx(I,{className:"px-4 py-6 text-center text-gray-500 dark:text-gray-400",children:a}),L&&h.jsx(I,{className:"px-4 py-6 text-center text-gray-500 dark:text-gray-400",children:i}),$&&h.jsx(I,{className:"py-2",children:k.map(A=>{const D=T(A.value);return h.jsxs(oe,{type:"button",onClick:()=>!A.disabled&&M(A.value),disabled:A.disabled,className:` w-full px-4 py-3 text-left flex items-start gap-3 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors ${A.disabled?"opacity-50 cursor-not-allowed":"cursor-pointer"} - `,role:"option","aria-selected":O,children:[h.jsx(I,{className:"w-5 h-5 flex-shrink-0 mt-0.5",children:O&&h.jsx(Zn,{className:"w-5 h-5 text-blue-600 dark:text-blue-400",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:h.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M5 13l4 4L19 7"})})}),h.jsxs(I,{className:"flex-1 min-w-0",children:[h.jsx(I,{className:`font-medium ${O?"text-blue-600 dark:text-blue-400":"text-gray-900 dark:text-white"}`,children:A.label}),A.description&&h.jsx(I,{className:"text-sm text-gray-500 dark:text-gray-400 mt-0.5",children:A.description})]})]},A.value)})})]})]})]})},Js={container:{base:"tag-select",disabled:"opacity-50 cursor-not-allowed"},tag:"tag",tagRemove:"tag-remove",placeholder:"tag-select-placeholder"},gC=({options:e=[],value:t=[],onChange:n,placeholder:r="No items selected",disabled:s=!1,className:o="",id:i,editorAttrs:a})=>{const l=v.useCallback(f=>e.find(m=>String(m.value)===String(f))?.label||String(f),[e]),c=v.useCallback(f=>{if(s)return;const g=t.filter(m=>String(m)!==String(f));n?.(g)},[t,n,s]),d=v.useMemo(()=>t.map(f=>({value:f,label:l(f)})),[t,l]),u=`${Js.container.base} ${s?Js.container.disabled:""} ${o}`.trim();return h.jsx(I,{className:u,id:i,...a,children:d.length===0?h.jsx(Q,{className:Js.placeholder,children:r}):d.map(f=>h.jsxs(Q,{className:Js.tag,children:[f.label,!s&&h.jsx(Q,{className:Js.tagRemove,onClick:()=>c(f.value),role:"button","aria-label":`Remove ${f.label}`,children:h.jsx(Zn,{className:"w-3 h-3",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:h.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M6 18L18 6M6 6l12 12"})})})]},f.value))})};function pC(){for(var e=arguments.length,t=new Array(e),n=0;nr=>{t.forEach(s=>s(r))},t)}const _i=typeof window<"u"&&typeof window.document<"u"&&typeof window.document.createElement<"u";function cs(e){const t=Object.prototype.toString.call(e);return t==="[object Window]"||t==="[object global]"}function Xl(e){return"nodeType"in e}function Jt(e){var t,n;return e?cs(e)?e:Xl(e)&&(t=(n=e.ownerDocument)==null?void 0:n.defaultView)!=null?t:window:window}function Kl(e){const{Document:t}=Jt(e);return e instanceof t}function Rs(e){return cs(e)?!1:e instanceof Jt(e).HTMLElement}function ph(e){return e instanceof Jt(e).SVGElement}function ds(e){return e?cs(e)?e.document:Xl(e)?Kl(e)?e:Rs(e)||ph(e)?e.ownerDocument:document:document:document}const Mn=_i?v.useLayoutEffect:v.useEffect;function Ei(e){const t=v.useRef(e);return Mn(()=>{t.current=e}),v.useCallback(function(){for(var n=arguments.length,r=new Array(n),s=0;s{e.current=setInterval(r,s)},[]),n=v.useCallback(()=>{e.current!==null&&(clearInterval(e.current),e.current=null)},[]);return[t,n]}function eo(e,t){t===void 0&&(t=[e]);const n=v.useRef(e);return Mn(()=>{n.current!==e&&(n.current=e)},t),n}function to(e,t){const n=v.useRef();return v.useMemo(()=>{const r=e(n.current);return n.current=r,r},[...t])}function Mi(e){const t=Ei(e),n=v.useRef(null),r=v.useCallback(s=>{s!==n.current&&t?.(s,n.current),n.current=s},[]);return[n,r]}function Ni(e){const t=v.useRef();return v.useEffect(()=>{t.current=e},[e]),t.current}let ql={};function no(e,t){return v.useMemo(()=>{if(t)return t;const n=ql[e]==null?0:ql[e]+1;return ql[e]=n,e+"-"+n},[e,t])}function mh(e){return function(t){for(var n=arguments.length,r=new Array(n>1?n-1:0),s=1;s{const a=Object.entries(i);for(const[l,c]of a){const d=o[l];d!=null&&(o[l]=d+e*c)}return o},{...t})}}const us=mh(1),ro=mh(-1);function bC(e){return"clientX"in e&&"clientY"in e}function Ti(e){if(!e)return!1;const{KeyboardEvent:t}=Jt(e.target);return t&&e instanceof t}function yC(e){if(!e)return!1;const{TouchEvent:t}=Jt(e.target);return t&&e instanceof t}function Oi(e){if(yC(e)){if(e.touches&&e.touches.length){const{clientX:t,clientY:n}=e.touches[0];return{x:t,y:n}}else if(e.changedTouches&&e.changedTouches.length){const{clientX:t,clientY:n}=e.changedTouches[0];return{x:t,y:n}}}return bC(e)?{x:e.clientX,y:e.clientY}:null}const jn=Object.freeze({Translate:{toString(e){if(!e)return;const{x:t,y:n}=e;return"translate3d("+(t?Math.round(t):0)+"px, "+(n?Math.round(n):0)+"px, 0)"}},Scale:{toString(e){if(!e)return;const{scaleX:t,scaleY:n}=e;return"scaleX("+t+") scaleY("+n+")"}},Transform:{toString(e){if(e)return[jn.Translate.toString(e),jn.Scale.toString(e)].join(" ")}},Transition:{toString(e){let{property:t,duration:n,easing:r}=e;return t+" "+n+"ms "+r}}}),bh="a,frame,iframe,input:not([type=hidden]):not(:disabled),select:not(:disabled),textarea:not(:disabled),button:not(:disabled),*[tabindex]";function vC(e){return e.matches(bh)?e:e.querySelector(bh)}const xC={display:"none"};function wC(e){let{id:t,value:n}=e;return v.createElement("div",{id:t,style:xC},n)}function kC(e){let{id:t,announcement:n,ariaLiveType:r="assertive"}=e;const s={position:"fixed",top:0,left:0,width:1,height:1,margin:-1,border:0,padding:0,overflow:"hidden",clip:"rect(0 0 0 0)",clipPath:"inset(100%)",whiteSpace:"nowrap"};return v.createElement("div",{id:t,style:s,role:"status","aria-live":r,"aria-atomic":!0},n)}function CC(){const[e,t]=v.useState("");return{announce:v.useCallback(r=>{r!=null&&t(r)},[]),announcement:e}}const yh=v.createContext(null);function SC(e){const t=v.useContext(yh);v.useEffect(()=>{if(!t)throw new Error("useDndMonitor must be used within a children of ");return t(e)},[e,t])}function _C(){const[e]=v.useState(()=>new Set),t=v.useCallback(r=>(e.add(r),()=>e.delete(r)),[e]);return[v.useCallback(r=>{let{type:s,event:o}=r;e.forEach(i=>{var a;return(a=i[s])==null?void 0:a.call(i,o)})},[e]),t]}const EC={draggable:` + `,role:"option","aria-selected":D,children:[h.jsx(I,{className:"w-5 h-5 flex-shrink-0 mt-0.5",children:D&&h.jsx(Un,{className:"w-5 h-5 text-blue-600 dark:text-blue-400",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:h.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M5 13l4 4L19 7"})})}),h.jsxs(I,{className:"flex-1 min-w-0",children:[h.jsx(I,{className:`font-medium ${D?"text-blue-600 dark:text-blue-400":"text-gray-900 dark:text-white"}`,children:A.label}),A.description&&h.jsx(I,{className:"text-sm text-gray-500 dark:text-gray-400 mt-0.5",children:A.description})]})]},A.value)})})]})]})]})},Hs={container:{base:"tag-select",disabled:"opacity-50 cursor-not-allowed"},tag:"tag",tagRemove:"tag-remove",placeholder:"tag-select-placeholder"},qk=({options:t=[],value:e=[],onChange:n,placeholder:r="No items selected",disabled:s=!1,className:o="",id:i,editorAttrs:a})=>{const l=v.useCallback(f=>t.find(m=>String(m.value)===String(f))?.label||String(f),[t]),c=v.useCallback(f=>{if(s)return;const g=e.filter(m=>String(m)!==String(f));n?.(g)},[e,n,s]),d=v.useMemo(()=>e.map(f=>({value:f,label:l(f)})),[e,l]),u=`${Hs.container.base} ${s?Hs.container.disabled:""} ${o}`.trim();return h.jsx(I,{className:u,id:i,...a,children:d.length===0?h.jsx(Q,{className:Hs.placeholder,children:r}):d.map(f=>h.jsxs(Q,{className:Hs.tag,children:[f.label,!s&&h.jsx(Q,{className:Hs.tagRemove,onClick:()=>c(f.value),role:"button","aria-label":`Remove ${f.label}`,children:h.jsx(Un,{className:"w-3 h-3",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:h.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M6 18L18 6M6 6l12 12"})})})]},f.value))})};function Zk(){for(var t=arguments.length,e=new Array(t),n=0;nr=>{e.forEach(s=>s(r))},e)}const bi=typeof window<"u"&&typeof window.document<"u"&&typeof window.document.createElement<"u";function Kr(t){const e=Object.prototype.toString.call(t);return e==="[object Window]"||e==="[object global]"}function Hl(t){return"nodeType"in t}function Xt(t){var e,n;return t?Kr(t)?t:Hl(t)&&(e=(n=t.ownerDocument)==null?void 0:n.defaultView)!=null?e:window:window}function Ul(t){const{Document:e}=Xt(t);return t instanceof e}function Us(t){return Kr(t)?!1:t instanceof Xt(t).HTMLElement}function rh(t){return t instanceof Xt(t).SVGElement}function Xr(t){return t?Kr(t)?t.document:Hl(t)?Ul(t)?t:Us(t)||rh(t)?t.ownerDocument:document:document:document}const wn=bi?v.useLayoutEffect:v.useEffect;function yi(t){const e=v.useRef(t);return wn(()=>{e.current=t}),v.useCallback(function(){for(var n=arguments.length,r=new Array(n),s=0;s{t.current=setInterval(r,s)},[]),n=v.useCallback(()=>{t.current!==null&&(clearInterval(t.current),t.current=null)},[]);return[e,n]}function Bs(t,e){e===void 0&&(e=[t]);const n=v.useRef(t);return wn(()=>{n.current!==t&&(n.current=t)},e),n}function js(t,e){const n=v.useRef();return v.useMemo(()=>{const r=t(n.current);return n.current=r,r},[...e])}function vi(t){const e=yi(t),n=v.useRef(null),r=v.useCallback(s=>{s!==n.current&&e?.(s,n.current),n.current=s},[]);return[n,r]}function xi(t){const e=v.useRef();return v.useEffect(()=>{e.current=t},[t]),e.current}let Bl={};function Ws(t,e){return v.useMemo(()=>{if(e)return e;const n=Bl[t]==null?0:Bl[t]+1;return Bl[t]=n,t+"-"+n},[t,e])}function sh(t){return function(e){for(var n=arguments.length,r=new Array(n>1?n-1:0),s=1;s{const a=Object.entries(i);for(const[l,c]of a){const d=o[l];d!=null&&(o[l]=d+t*c)}return o},{...e})}}const qr=sh(1),Gs=sh(-1);function Jk(t){return"clientX"in t&&"clientY"in t}function wi(t){if(!t)return!1;const{KeyboardEvent:e}=Xt(t.target);return e&&t instanceof e}function Rk(t){if(!t)return!1;const{TouchEvent:e}=Xt(t.target);return e&&t instanceof e}function ki(t){if(Rk(t)){if(t.touches&&t.touches.length){const{clientX:e,clientY:n}=t.touches[0];return{x:e,y:n}}else if(t.changedTouches&&t.changedTouches.length){const{clientX:e,clientY:n}=t.changedTouches[0];return{x:e,y:n}}}return Jk(t)?{x:t.clientX,y:t.clientY}:null}const Pn=Object.freeze({Translate:{toString(t){if(!t)return;const{x:e,y:n}=t;return"translate3d("+(e?Math.round(e):0)+"px, "+(n?Math.round(n):0)+"px, 0)"}},Scale:{toString(t){if(!t)return;const{scaleX:e,scaleY:n}=t;return"scaleX("+e+") scaleY("+n+")"}},Transform:{toString(t){if(t)return[Pn.Translate.toString(t),Pn.Scale.toString(t)].join(" ")}},Transition:{toString(t){let{property:e,duration:n,easing:r}=t;return e+" "+n+"ms "+r}}}),oh="a,frame,iframe,input:not([type=hidden]):not(:disabled),select:not(:disabled),textarea:not(:disabled),button:not(:disabled),*[tabindex]";function eC(t){return t.matches(oh)?t:t.querySelector(oh)}const tC={display:"none"};function nC(t){let{id:e,value:n}=t;return v.createElement("div",{id:e,style:tC},n)}function rC(t){let{id:e,announcement:n,ariaLiveType:r="assertive"}=t;const s={position:"fixed",top:0,left:0,width:1,height:1,margin:-1,border:0,padding:0,overflow:"hidden",clip:"rect(0 0 0 0)",clipPath:"inset(100%)",whiteSpace:"nowrap"};return v.createElement("div",{id:e,style:s,role:"status","aria-live":r,"aria-atomic":!0},n)}function sC(){const[t,e]=v.useState("");return{announce:v.useCallback(r=>{r!=null&&e(r)},[]),announcement:t}}const ih=v.createContext(null);function oC(t){const e=v.useContext(ih);v.useEffect(()=>{if(!e)throw new Error("useDndMonitor must be used within a children of ");return e(t)},[t,e])}function iC(){const[t]=v.useState(()=>new Set),e=v.useCallback(r=>(t.add(r),()=>t.delete(r)),[t]);return[v.useCallback(r=>{let{type:s,event:o}=r;t.forEach(i=>{var a;return(a=i[s])==null?void 0:a.call(i,o)})},[t]),e]}const aC={draggable:` To pick up a draggable item, press the space bar. While dragging, use the arrow keys to move the item. Press space again to drop the item in its new position, or press escape to cancel. - `},MC={onDragStart(e){let{active:t}=e;return"Picked up draggable item "+t.id+"."},onDragOver(e){let{active:t,over:n}=e;return n?"Draggable item "+t.id+" was moved over droppable area "+n.id+".":"Draggable item "+t.id+" is no longer over a droppable area."},onDragEnd(e){let{active:t,over:n}=e;return n?"Draggable item "+t.id+" was dropped over droppable area "+n.id:"Draggable item "+t.id+" was dropped."},onDragCancel(e){let{active:t}=e;return"Dragging was cancelled. Draggable item "+t.id+" was dropped."}};function NC(e){let{announcements:t=MC,container:n,hiddenTextDescribedById:r,screenReaderInstructions:s=EC}=e;const{announce:o,announcement:i}=CC(),a=no("DndLiveRegion"),[l,c]=v.useState(!1);if(v.useEffect(()=>{c(!0)},[]),SC(v.useMemo(()=>({onDragStart(u){let{active:f}=u;o(t.onDragStart({active:f}))},onDragMove(u){let{active:f,over:g}=u;t.onDragMove&&o(t.onDragMove({active:f,over:g}))},onDragOver(u){let{active:f,over:g}=u;o(t.onDragOver({active:f,over:g}))},onDragEnd(u){let{active:f,over:g}=u;o(t.onDragEnd({active:f,over:g}))},onDragCancel(u){let{active:f,over:g}=u;o(t.onDragCancel({active:f,over:g}))}}),[o,t])),!l)return null;const d=v.createElement(v.Fragment,null,v.createElement(wC,{id:r,value:s.draggable}),v.createElement(kC,{id:a,announcement:i}));return n?$n.createPortal(d,n):d}var Nt;(function(e){e.DragStart="dragStart",e.DragMove="dragMove",e.DragEnd="dragEnd",e.DragCancel="dragCancel",e.DragOver="dragOver",e.RegisterDroppable="registerDroppable",e.SetDroppableDisabled="setDroppableDisabled",e.UnregisterDroppable="unregisterDroppable"})(Nt||(Nt={}));function Ai(){}function fs(e,t){return v.useMemo(()=>({sensor:e,options:t??{}}),[e,t])}function Zl(){for(var e=arguments.length,t=new Array(e),n=0;n[...t].filter(r=>r!=null),[...t])}const Nn=Object.freeze({x:0,y:0});function vh(e,t){return Math.sqrt(Math.pow(e.x-t.x,2)+Math.pow(e.y-t.y,2))}function TC(e,t){const n=Oi(e);if(!n)return"0 0";const r={x:(n.x-t.left)/t.width*100,y:(n.y-t.top)/t.height*100};return r.x+"% "+r.y+"%"}function xh(e,t){let{data:{value:n}}=e,{data:{value:r}}=t;return n-r}function OC(e,t){let{data:{value:n}}=e,{data:{value:r}}=t;return r-n}function wh(e){let{left:t,top:n,height:r,width:s}=e;return[{x:t,y:n},{x:t+s,y:n},{x:t,y:n+r},{x:t+s,y:n+r}]}function kh(e,t){if(!e||e.length===0)return null;const[n]=e;return n[t]}function Ch(e,t,n){return t===void 0&&(t=e.left),n===void 0&&(n=e.top),{x:t+e.width*.5,y:n+e.height*.5}}const Ql=e=>{let{collisionRect:t,droppableRects:n,droppableContainers:r}=e;const s=Ch(t,t.left,t.top),o=[];for(const i of r){const{id:a}=i,l=n.get(a);if(l){const c=vh(Ch(l),s);o.push({id:a,data:{droppableContainer:i,value:c}})}}return o.sort(xh)},AC=e=>{let{collisionRect:t,droppableRects:n,droppableContainers:r}=e;const s=wh(t),o=[];for(const i of r){const{id:a}=i,l=n.get(a);if(l){const c=wh(l),d=s.reduce((f,g,m)=>f+vh(c[m],g),0),u=Number((d/4).toFixed(4));o.push({id:a,data:{droppableContainer:i,value:u}})}}return o.sort(xh)};function DC(e,t){const n=Math.max(t.top,e.top),r=Math.max(t.left,e.left),s=Math.min(t.left+t.width,e.left+e.width),o=Math.min(t.top+t.height,e.top+e.height),i=s-r,a=o-n;if(r{let{collisionRect:t,droppableRects:n,droppableContainers:r}=e;const s=[];for(const o of r){const{id:i}=o,a=n.get(i);if(a){const l=DC(a,t);l>0&&s.push({id:i,data:{droppableContainer:o,value:l}})}}return s.sort(OC)};function LC(e,t,n){return{...e,scaleX:t&&n?t.width/n.width:1,scaleY:t&&n?t.height/n.height:1}}function Sh(e,t){return e&&t?{x:e.left-t.left,y:e.top-t.top}:Nn}function PC(e){return function(n){for(var r=arguments.length,s=new Array(r>1?r-1:0),o=1;o({...i,top:i.top+e*a.y,bottom:i.bottom+e*a.y,left:i.left+e*a.x,right:i.right+e*a.x}),{...n})}}const $C=PC(1);function _h(e){if(e.startsWith("matrix3d(")){const t=e.slice(9,-1).split(/, /);return{x:+t[12],y:+t[13],scaleX:+t[0],scaleY:+t[5]}}else if(e.startsWith("matrix(")){const t=e.slice(7,-1).split(/, /);return{x:+t[4],y:+t[5],scaleX:+t[0],scaleY:+t[3]}}return null}function FC(e,t,n){const r=_h(t);if(!r)return e;const{scaleX:s,scaleY:o,x:i,y:a}=r,l=e.left-i-(1-s)*parseFloat(n),c=e.top-a-(1-o)*parseFloat(n.slice(n.indexOf(" ")+1)),d=s?e.width/s:e.width,u=o?e.height/o:e.height;return{width:d,height:u,top:c,right:l+d,bottom:c+u,left:l}}const zC={ignoreTransform:!1};function hs(e,t){t===void 0&&(t=zC);let n=e.getBoundingClientRect();if(t.ignoreTransform){const{transform:c,transformOrigin:d}=Jt(e).getComputedStyle(e);c&&(n=FC(n,c,d))}const{top:r,left:s,width:o,height:i,bottom:a,right:l}=n;return{top:r,left:s,width:o,height:i,bottom:a,right:l}}function Eh(e){return hs(e,{ignoreTransform:!0})}function VC(e){const t=e.innerWidth,n=e.innerHeight;return{top:0,left:0,right:t,bottom:n,width:t,height:n}}function HC(e,t){return t===void 0&&(t=Jt(e).getComputedStyle(e)),t.position==="fixed"}function UC(e,t){t===void 0&&(t=Jt(e).getComputedStyle(e));const n=/(auto|scroll|overlay)/;return["overflow","overflowX","overflowY"].some(s=>{const o=t[s];return typeof o=="string"?n.test(o):!1})}function Di(e,t){const n=[];function r(s){if(t!=null&&n.length>=t||!s)return n;if(Kl(s)&&s.scrollingElement!=null&&!n.includes(s.scrollingElement))return n.push(s.scrollingElement),n;if(!Rs(s)||ph(s)||n.includes(s))return n;const o=Jt(e).getComputedStyle(s);return s!==e&&UC(s,o)&&n.push(s),HC(s,o)?n:r(s.parentNode)}return e?r(e):n}function Mh(e){const[t]=Di(e,1);return t??null}function Jl(e){return!_i||!e?null:cs(e)?e:Xl(e)?Kl(e)||e===ds(e).scrollingElement?window:Rs(e)?e:null:null}function Nh(e){return cs(e)?e.scrollX:e.scrollLeft}function Th(e){return cs(e)?e.scrollY:e.scrollTop}function Rl(e){return{x:Nh(e),y:Th(e)}}var Dt;(function(e){e[e.Forward=1]="Forward",e[e.Backward=-1]="Backward"})(Dt||(Dt={}));function Oh(e){return!_i||!e?!1:e===document.scrollingElement}function Ah(e){const t={x:0,y:0},n=Oh(e)?{height:window.innerHeight,width:window.innerWidth}:{height:e.clientHeight,width:e.clientWidth},r={x:e.scrollWidth-n.width,y:e.scrollHeight-n.height},s=e.scrollTop<=t.y,o=e.scrollLeft<=t.x,i=e.scrollTop>=r.y,a=e.scrollLeft>=r.x;return{isTop:s,isLeft:o,isBottom:i,isRight:a,maxScroll:r,minScroll:t}}const BC={x:.2,y:.2};function jC(e,t,n,r,s){let{top:o,left:i,right:a,bottom:l}=n;r===void 0&&(r=10),s===void 0&&(s=BC);const{isTop:c,isBottom:d,isLeft:u,isRight:f}=Ah(e),g={x:0,y:0},m={x:0,y:0},p={height:t.height*s.y,width:t.width*s.x};return!c&&o<=t.top+p.height?(g.y=Dt.Backward,m.y=r*Math.abs((t.top+p.height-o)/p.height)):!d&&l>=t.bottom-p.height&&(g.y=Dt.Forward,m.y=r*Math.abs((t.bottom-p.height-l)/p.height)),!f&&a>=t.right-p.width?(g.x=Dt.Forward,m.x=r*Math.abs((t.right-p.width-a)/p.width)):!u&&i<=t.left+p.width&&(g.x=Dt.Backward,m.x=r*Math.abs((t.left+p.width-i)/p.width)),{direction:g,speed:m}}function WC(e){if(e===document.scrollingElement){const{innerWidth:o,innerHeight:i}=window;return{top:0,left:0,right:o,bottom:i,width:o,height:i}}const{top:t,left:n,right:r,bottom:s}=e.getBoundingClientRect();return{top:t,left:n,right:r,bottom:s,width:e.clientWidth,height:e.clientHeight}}function Dh(e){return e.reduce((t,n)=>us(t,Rl(n)),Nn)}function GC(e){return e.reduce((t,n)=>t+Nh(n),0)}function YC(e){return e.reduce((t,n)=>t+Th(n),0)}function Ih(e,t){if(t===void 0&&(t=hs),!e)return;const{top:n,left:r,bottom:s,right:o}=t(e);Mh(e)&&(s<=0||o<=0||n>=window.innerHeight||r>=window.innerWidth)&&e.scrollIntoView({block:"center",inline:"center"})}const XC=[["x",["left","right"],GC],["y",["top","bottom"],YC]];class ec{constructor(t,n){this.rect=void 0,this.width=void 0,this.height=void 0,this.top=void 0,this.bottom=void 0,this.right=void 0,this.left=void 0;const r=Di(n),s=Dh(r);this.rect={...t},this.width=t.width,this.height=t.height;for(const[o,i,a]of XC)for(const l of i)Object.defineProperty(this,l,{get:()=>{const c=a(r),d=s[o]-c;return this.rect[l]+d},enumerable:!0});Object.defineProperty(this,"rect",{enumerable:!1})}}class so{constructor(t){this.target=void 0,this.listeners=[],this.removeAll=()=>{this.listeners.forEach(n=>{var r;return(r=this.target)==null?void 0:r.removeEventListener(...n)})},this.target=t}add(t,n,r){var s;(s=this.target)==null||s.addEventListener(t,n,r),this.listeners.push([t,n,r])}}function KC(e){const{EventTarget:t}=Jt(e);return e instanceof t?e:ds(e)}function tc(e,t){const n=Math.abs(e.x),r=Math.abs(e.y);return typeof t=="number"?Math.sqrt(n**2+r**2)>t:"x"in t&&"y"in t?n>t.x&&r>t.y:"x"in t?n>t.x:"y"in t?r>t.y:!1}var gn;(function(e){e.Click="click",e.DragStart="dragstart",e.Keydown="keydown",e.ContextMenu="contextmenu",e.Resize="resize",e.SelectionChange="selectionchange",e.VisibilityChange="visibilitychange"})(gn||(gn={}));function Lh(e){e.preventDefault()}function qC(e){e.stopPropagation()}var qe;(function(e){e.Space="Space",e.Down="ArrowDown",e.Right="ArrowRight",e.Left="ArrowLeft",e.Up="ArrowUp",e.Esc="Escape",e.Enter="Enter",e.Tab="Tab"})(qe||(qe={}));const Ph={start:[qe.Space,qe.Enter],cancel:[qe.Esc],end:[qe.Space,qe.Enter,qe.Tab]},ZC=(e,t)=>{let{currentCoordinates:n}=t;switch(e.code){case qe.Right:return{...n,x:n.x+25};case qe.Left:return{...n,x:n.x-25};case qe.Down:return{...n,y:n.y+25};case qe.Up:return{...n,y:n.y-25}}};class oo{constructor(t){this.props=void 0,this.autoScrollEnabled=!1,this.referenceCoordinates=void 0,this.listeners=void 0,this.windowListeners=void 0,this.props=t;const{event:{target:n}}=t;this.props=t,this.listeners=new so(ds(n)),this.windowListeners=new so(Jt(n)),this.handleKeyDown=this.handleKeyDown.bind(this),this.handleCancel=this.handleCancel.bind(this),this.attach()}attach(){this.handleStart(),this.windowListeners.add(gn.Resize,this.handleCancel),this.windowListeners.add(gn.VisibilityChange,this.handleCancel),setTimeout(()=>this.listeners.add(gn.Keydown,this.handleKeyDown))}handleStart(){const{activeNode:t,onStart:n}=this.props,r=t.node.current;r&&Ih(r),n(Nn)}handleKeyDown(t){if(Ti(t)){const{active:n,context:r,options:s}=this.props,{keyboardCodes:o=Ph,coordinateGetter:i=ZC,scrollBehavior:a="smooth"}=s,{code:l}=t;if(o.end.includes(l)){this.handleEnd(t);return}if(o.cancel.includes(l)){this.handleCancel(t);return}const{collisionRect:c}=r.current,d=c?{x:c.left,y:c.top}:Nn;this.referenceCoordinates||(this.referenceCoordinates=d);const u=i(t,{active:n,context:r.current,currentCoordinates:d});if(u){const f=ro(u,d),g={x:0,y:0},{scrollableAncestors:m}=r.current;for(const p of m){const y=t.code,{isTop:b,isRight:_,isLeft:x,isBottom:M,maxScroll:C,minScroll:k}=Ah(p),N=WC(p),S={x:Math.min(y===qe.Right?N.right-N.width/2:N.right,Math.max(y===qe.Right?N.left:N.left+N.width/2,u.x)),y:Math.min(y===qe.Down?N.bottom-N.height/2:N.bottom,Math.max(y===qe.Down?N.top:N.top+N.height/2,u.y))},w=y===qe.Right&&!_||y===qe.Left&&!x,E=y===qe.Down&&!M||y===qe.Up&&!b;if(w&&S.x!==u.x){const D=p.scrollLeft+f.x,P=y===qe.Right&&D<=C.x||y===qe.Left&&D>=k.x;if(P&&!f.y){p.scrollTo({left:D,behavior:a});return}P?g.x=p.scrollLeft-D:g.x=y===qe.Right?p.scrollLeft-C.x:p.scrollLeft-k.x,g.x&&p.scrollBy({left:-g.x,behavior:a});break}else if(E&&S.y!==u.y){const D=p.scrollTop+f.y,P=y===qe.Down&&D<=C.y||y===qe.Up&&D>=k.y;if(P&&!f.x){p.scrollTo({top:D,behavior:a});return}P?g.y=p.scrollTop-D:g.y=y===qe.Down?p.scrollTop-C.y:p.scrollTop-k.y,g.y&&p.scrollBy({top:-g.y,behavior:a});break}}this.handleMove(t,us(ro(u,this.referenceCoordinates),g))}}}handleMove(t,n){const{onMove:r}=this.props;t.preventDefault(),r(n)}handleEnd(t){const{onEnd:n}=this.props;t.preventDefault(),this.detach(),n()}handleCancel(t){const{onCancel:n}=this.props;t.preventDefault(),this.detach(),n()}detach(){this.listeners.removeAll(),this.windowListeners.removeAll()}}oo.activators=[{eventName:"onKeyDown",handler:(e,t,n)=>{let{keyboardCodes:r=Ph,onActivation:s}=t,{active:o}=n;const{code:i}=e.nativeEvent;if(r.start.includes(i)){const a=o.activatorNode.current;return a&&e.target!==a?!1:(e.preventDefault(),s?.({event:e.nativeEvent}),!0)}return!1}}];function $h(e){return!!(e&&"distance"in e)}function Fh(e){return!!(e&&"delay"in e)}class nc{constructor(t,n,r){var s;r===void 0&&(r=KC(t.event.target)),this.props=void 0,this.events=void 0,this.autoScrollEnabled=!0,this.document=void 0,this.activated=!1,this.initialCoordinates=void 0,this.timeoutId=null,this.listeners=void 0,this.documentListeners=void 0,this.windowListeners=void 0,this.props=t,this.events=n;const{event:o}=t,{target:i}=o;this.props=t,this.events=n,this.document=ds(i),this.documentListeners=new so(this.document),this.listeners=new so(r),this.windowListeners=new so(Jt(i)),this.initialCoordinates=(s=Oi(o))!=null?s:Nn,this.handleStart=this.handleStart.bind(this),this.handleMove=this.handleMove.bind(this),this.handleEnd=this.handleEnd.bind(this),this.handleCancel=this.handleCancel.bind(this),this.handleKeydown=this.handleKeydown.bind(this),this.removeTextSelection=this.removeTextSelection.bind(this),this.attach()}attach(){const{events:t,props:{options:{activationConstraint:n,bypassActivationConstraint:r}}}=this;if(this.listeners.add(t.move.name,this.handleMove,{passive:!1}),this.listeners.add(t.end.name,this.handleEnd),t.cancel&&this.listeners.add(t.cancel.name,this.handleCancel),this.windowListeners.add(gn.Resize,this.handleCancel),this.windowListeners.add(gn.DragStart,Lh),this.windowListeners.add(gn.VisibilityChange,this.handleCancel),this.windowListeners.add(gn.ContextMenu,Lh),this.documentListeners.add(gn.Keydown,this.handleKeydown),n){if(r!=null&&r({event:this.props.event,activeNode:this.props.activeNode,options:this.props.options}))return this.handleStart();if(Fh(n)){this.timeoutId=setTimeout(this.handleStart,n.delay),this.handlePending(n);return}if($h(n)){this.handlePending(n);return}}this.handleStart()}detach(){this.listeners.removeAll(),this.windowListeners.removeAll(),setTimeout(this.documentListeners.removeAll,50),this.timeoutId!==null&&(clearTimeout(this.timeoutId),this.timeoutId=null)}handlePending(t,n){const{active:r,onPending:s}=this.props;s(r,t,this.initialCoordinates,n)}handleStart(){const{initialCoordinates:t}=this,{onStart:n}=this.props;t&&(this.activated=!0,this.documentListeners.add(gn.Click,qC,{capture:!0}),this.removeTextSelection(),this.documentListeners.add(gn.SelectionChange,this.removeTextSelection),n(t))}handleMove(t){var n;const{activated:r,initialCoordinates:s,props:o}=this,{onMove:i,options:{activationConstraint:a}}=o;if(!s)return;const l=(n=Oi(t))!=null?n:Nn,c=ro(s,l);if(!r&&a){if($h(a)){if(a.tolerance!=null&&tc(c,a.tolerance))return this.handleCancel();if(tc(c,a.distance))return this.handleStart()}if(Fh(a)&&tc(c,a.tolerance))return this.handleCancel();this.handlePending(a,c);return}t.cancelable&&t.preventDefault(),i(l)}handleEnd(){const{onAbort:t,onEnd:n}=this.props;this.detach(),this.activated||t(this.props.active),n()}handleCancel(){const{onAbort:t,onCancel:n}=this.props;this.detach(),this.activated||t(this.props.active),n()}handleKeydown(t){t.code===qe.Esc&&this.handleCancel()}removeTextSelection(){var t;(t=this.document.getSelection())==null||t.removeAllRanges()}}const QC={cancel:{name:"pointercancel"},move:{name:"pointermove"},end:{name:"pointerup"}};class io extends nc{constructor(t){const{event:n}=t,r=ds(n.target);super(t,QC,r)}}io.activators=[{eventName:"onPointerDown",handler:(e,t)=>{let{nativeEvent:n}=e,{onActivation:r}=t;return!n.isPrimary||n.button!==0?!1:(r?.({event:n}),!0)}}];const JC={move:{name:"mousemove"},end:{name:"mouseup"}};var rc;(function(e){e[e.RightClick=2]="RightClick"})(rc||(rc={}));class RC extends nc{constructor(t){super(t,JC,ds(t.event.target))}}RC.activators=[{eventName:"onMouseDown",handler:(e,t)=>{let{nativeEvent:n}=e,{onActivation:r}=t;return n.button===rc.RightClick?!1:(r?.({event:n}),!0)}}];const sc={cancel:{name:"touchcancel"},move:{name:"touchmove"},end:{name:"touchend"}};class eS extends nc{constructor(t){super(t,sc)}static setup(){return window.addEventListener(sc.move.name,t,{capture:!1,passive:!1}),function(){window.removeEventListener(sc.move.name,t)};function t(){}}}eS.activators=[{eventName:"onTouchStart",handler:(e,t)=>{let{nativeEvent:n}=e,{onActivation:r}=t;const{touches:s}=n;return s.length>1?!1:(r?.({event:n}),!0)}}];var ao;(function(e){e[e.Pointer=0]="Pointer",e[e.DraggableRect=1]="DraggableRect"})(ao||(ao={}));var Ii;(function(e){e[e.TreeOrder=0]="TreeOrder",e[e.ReversedTreeOrder=1]="ReversedTreeOrder"})(Ii||(Ii={}));function tS(e){let{acceleration:t,activator:n=ao.Pointer,canScroll:r,draggingRect:s,enabled:o,interval:i=5,order:a=Ii.TreeOrder,pointerCoordinates:l,scrollableAncestors:c,scrollableAncestorRects:d,delta:u,threshold:f}=e;const g=rS({delta:u,disabled:!o}),[m,p]=mC(),y=v.useRef({x:0,y:0}),b=v.useRef({x:0,y:0}),_=v.useMemo(()=>{switch(n){case ao.Pointer:return l?{top:l.y,bottom:l.y,left:l.x,right:l.x}:null;case ao.DraggableRect:return s}},[n,s,l]),x=v.useRef(null),M=v.useCallback(()=>{const k=x.current;if(!k)return;const N=y.current.x*b.current.x,S=y.current.y*b.current.y;k.scrollBy(N,S)},[]),C=v.useMemo(()=>a===Ii.TreeOrder?[...c].reverse():c,[a,c]);v.useEffect(()=>{if(!o||!c.length||!_){p();return}for(const k of C){if(r?.(k)===!1)continue;const N=c.indexOf(k),S=d[N];if(!S)continue;const{direction:w,speed:E}=jC(k,S,_,t,f);for(const D of["x","y"])g[D][w[D]]||(E[D]=0,w[D]=0);if(E.x>0||E.y>0){p(),x.current=k,m(M,i),y.current=E,b.current=w;return}}y.current={x:0,y:0},b.current={x:0,y:0},p()},[t,M,r,p,o,i,JSON.stringify(_),JSON.stringify(g),m,c,C,d,JSON.stringify(f)])}const nS={x:{[Dt.Backward]:!1,[Dt.Forward]:!1},y:{[Dt.Backward]:!1,[Dt.Forward]:!1}};function rS(e){let{delta:t,disabled:n}=e;const r=Ni(t);return to(s=>{if(n||!r||!s)return nS;const o={x:Math.sign(t.x-r.x),y:Math.sign(t.y-r.y)};return{x:{[Dt.Backward]:s.x[Dt.Backward]||o.x===-1,[Dt.Forward]:s.x[Dt.Forward]||o.x===1},y:{[Dt.Backward]:s.y[Dt.Backward]||o.y===-1,[Dt.Forward]:s.y[Dt.Forward]||o.y===1}}},[n,t,r])}function sS(e,t){const n=t!=null?e.get(t):void 0,r=n?n.node.current:null;return to(s=>{var o;return t==null?null:(o=r??s)!=null?o:null},[r,t])}function oS(e,t){return v.useMemo(()=>e.reduce((n,r)=>{const{sensor:s}=r,o=s.activators.map(i=>({eventName:i.eventName,handler:t(i.handler,r)}));return[...n,...o]},[]),[e,t])}var lo;(function(e){e[e.Always=0]="Always",e[e.BeforeDragging=1]="BeforeDragging",e[e.WhileDragging=2]="WhileDragging"})(lo||(lo={}));var oc;(function(e){e.Optimized="optimized"})(oc||(oc={}));const zh=new Map;function iS(e,t){let{dragging:n,dependencies:r,config:s}=t;const[o,i]=v.useState(null),{frequency:a,measure:l,strategy:c}=s,d=v.useRef(e),u=y(),f=eo(u),g=v.useCallback(function(b){b===void 0&&(b=[]),!f.current&&i(_=>_===null?b:_.concat(b.filter(x=>!_.includes(x))))},[f]),m=v.useRef(null),p=to(b=>{if(u&&!n)return zh;if(!b||b===zh||d.current!==e||o!=null){const _=new Map;for(let x of e){if(!x)continue;if(o&&o.length>0&&!o.includes(x.id)&&x.rect.current){_.set(x.id,x.rect.current);continue}const M=x.node.current,C=M?new ec(l(M),M):null;x.rect.current=C,C&&_.set(x.id,C)}return _}return b},[e,o,n,u,l]);return v.useEffect(()=>{d.current=e},[e]),v.useEffect(()=>{u||g()},[n,u]),v.useEffect(()=>{o&&o.length>0&&i(null)},[JSON.stringify(o)]),v.useEffect(()=>{u||typeof a!="number"||m.current!==null||(m.current=setTimeout(()=>{g(),m.current=null},a))},[a,u,g,...r]),{droppableRects:p,measureDroppableContainers:g,measuringScheduled:o!=null};function y(){switch(c){case lo.Always:return!1;case lo.BeforeDragging:return n;default:return!n}}}function ic(e,t){return to(n=>e?n||(typeof t=="function"?t(e):e):null,[t,e])}function aS(e,t){return ic(e,t)}function lS(e){let{callback:t,disabled:n}=e;const r=Ei(t),s=v.useMemo(()=>{if(n||typeof window>"u"||typeof window.MutationObserver>"u")return;const{MutationObserver:o}=window;return new o(r)},[r,n]);return v.useEffect(()=>()=>s?.disconnect(),[s]),s}function Li(e){let{callback:t,disabled:n}=e;const r=Ei(t),s=v.useMemo(()=>{if(n||typeof window>"u"||typeof window.ResizeObserver>"u")return;const{ResizeObserver:o}=window;return new o(r)},[n]);return v.useEffect(()=>()=>s?.disconnect(),[s]),s}function cS(e){return new ec(hs(e),e)}function Vh(e,t,n){t===void 0&&(t=cS);const[r,s]=v.useState(null);function o(){s(l=>{if(!e)return null;if(e.isConnected===!1){var c;return(c=l??n)!=null?c:null}const d=t(e);return JSON.stringify(l)===JSON.stringify(d)?l:d})}const i=lS({callback(l){if(e)for(const c of l){const{type:d,target:u}=c;if(d==="childList"&&u instanceof HTMLElement&&u.contains(e)){o();break}}}}),a=Li({callback:o});return Mn(()=>{o(),e?(a?.observe(e),i?.observe(document.body,{childList:!0,subtree:!0})):(a?.disconnect(),i?.disconnect())},[e]),r}function dS(e){const t=ic(e);return Sh(e,t)}const Hh=[];function uS(e){const t=v.useRef(e),n=to(r=>e?r&&r!==Hh&&e&&t.current&&e.parentNode===t.current.parentNode?r:Di(e):Hh,[e]);return v.useEffect(()=>{t.current=e},[e]),n}function fS(e){const[t,n]=v.useState(null),r=v.useRef(e),s=v.useCallback(o=>{const i=Jl(o.target);i&&n(a=>a?(a.set(i,Rl(i)),new Map(a)):null)},[]);return v.useEffect(()=>{const o=r.current;if(e!==o){i(o);const a=e.map(l=>{const c=Jl(l);return c?(c.addEventListener("scroll",s,{passive:!0}),[c,Rl(c)]):null}).filter(l=>l!=null);n(a.length?new Map(a):null),r.current=e}return()=>{i(e),i(o)};function i(a){a.forEach(l=>{const c=Jl(l);c?.removeEventListener("scroll",s)})}},[s,e]),v.useMemo(()=>e.length?t?Array.from(t.values()).reduce((o,i)=>us(o,i),Nn):Dh(e):Nn,[e,t])}function Uh(e,t){t===void 0&&(t=[]);const n=v.useRef(null);return v.useEffect(()=>{n.current=null},t),v.useEffect(()=>{const r=e!==Nn;r&&!n.current&&(n.current=e),!r&&n.current&&(n.current=null)},[e]),n.current?ro(e,n.current):Nn}function hS(e){v.useEffect(()=>{if(!_i)return;const t=e.map(n=>{let{sensor:r}=n;return r.setup==null?void 0:r.setup()});return()=>{for(const n of t)n?.()}},e.map(t=>{let{sensor:n}=t;return n}))}function gS(e,t){return v.useMemo(()=>e.reduce((n,r)=>{let{eventName:s,handler:o}=r;return n[s]=i=>{o(i,t)},n},{}),[e,t])}function Bh(e){return v.useMemo(()=>e?VC(e):null,[e])}const jh=[];function pS(e,t){t===void 0&&(t=hs);const[n]=e,r=Bh(n?Jt(n):null),[s,o]=v.useState(jh);function i(){o(()=>e.length?e.map(l=>Oh(l)?r:new ec(t(l),l)):jh)}const a=Li({callback:i});return Mn(()=>{a?.disconnect(),i(),e.forEach(l=>a?.observe(l))},[e]),s}function Wh(e){if(!e)return null;if(e.children.length>1)return e;const t=e.children[0];return Rs(t)?t:e}function mS(e){let{measure:t}=e;const[n,r]=v.useState(null),s=v.useCallback(c=>{for(const{target:d}of c)if(Rs(d)){r(u=>{const f=t(d);return u?{...u,width:f.width,height:f.height}:f});break}},[t]),o=Li({callback:s}),i=v.useCallback(c=>{const d=Wh(c);o?.disconnect(),d&&o?.observe(d),r(d?t(d):null)},[t,o]),[a,l]=Mi(i);return v.useMemo(()=>({nodeRef:a,rect:n,setRef:l}),[n,a,l])}const bS=[{sensor:io,options:{}},{sensor:oo,options:{}}],yS={current:{}},Pi={draggable:{measure:Eh},droppable:{measure:Eh,strategy:lo.WhileDragging,frequency:oc.Optimized},dragOverlay:{measure:hs}};class co extends Map{get(t){var n;return t!=null&&(n=super.get(t))!=null?n:void 0}toArray(){return Array.from(this.values())}getEnabled(){return this.toArray().filter(t=>{let{disabled:n}=t;return!n})}getNodeFor(t){var n,r;return(n=(r=this.get(t))==null?void 0:r.node.current)!=null?n:void 0}}const vS={activatorEvent:null,active:null,activeNode:null,activeNodeRect:null,collisions:null,containerNodeRect:null,draggableNodes:new Map,droppableRects:new Map,droppableContainers:new co,over:null,dragOverlay:{nodeRef:{current:null},rect:null,setRef:Ai},scrollableAncestors:[],scrollableAncestorRects:[],measuringConfiguration:Pi,measureDroppableContainers:Ai,windowRect:null,measuringScheduled:!1},Gh={activatorEvent:null,activators:[],active:null,activeNodeRect:null,ariaDescribedById:{draggable:""},dispatch:Ai,draggableNodes:new Map,over:null,measureDroppableContainers:Ai},uo=v.createContext(Gh),Yh=v.createContext(vS);function xS(){return{draggable:{active:null,initialCoordinates:{x:0,y:0},nodes:new Map,translate:{x:0,y:0}},droppable:{containers:new co}}}function wS(e,t){switch(t.type){case Nt.DragStart:return{...e,draggable:{...e.draggable,initialCoordinates:t.initialCoordinates,active:t.active}};case Nt.DragMove:return e.draggable.active==null?e:{...e,draggable:{...e.draggable,translate:{x:t.coordinates.x-e.draggable.initialCoordinates.x,y:t.coordinates.y-e.draggable.initialCoordinates.y}}};case Nt.DragEnd:case Nt.DragCancel:return{...e,draggable:{...e.draggable,active:null,initialCoordinates:{x:0,y:0},translate:{x:0,y:0}}};case Nt.RegisterDroppable:{const{element:n}=t,{id:r}=n,s=new co(e.droppable.containers);return s.set(r,n),{...e,droppable:{...e.droppable,containers:s}}}case Nt.SetDroppableDisabled:{const{id:n,key:r,disabled:s}=t,o=e.droppable.containers.get(n);if(!o||r!==o.key)return e;const i=new co(e.droppable.containers);return i.set(n,{...o,disabled:s}),{...e,droppable:{...e.droppable,containers:i}}}case Nt.UnregisterDroppable:{const{id:n,key:r}=t,s=e.droppable.containers.get(n);if(!s||r!==s.key)return e;const o=new co(e.droppable.containers);return o.delete(n),{...e,droppable:{...e.droppable,containers:o}}}default:return e}}function kS(e){let{disabled:t}=e;const{active:n,activatorEvent:r,draggableNodes:s}=v.useContext(uo),o=Ni(r),i=Ni(n?.id);return v.useEffect(()=>{if(!t&&!r&&o&&i!=null){if(!Ti(o)||document.activeElement===o.target)return;const a=s.get(i);if(!a)return;const{activatorNode:l,node:c}=a;if(!l.current&&!c.current)return;requestAnimationFrame(()=>{for(const d of[l.current,c.current]){if(!d)continue;const u=vC(d);if(u){u.focus();break}}})}},[r,t,s,i,o]),null}function Xh(e,t){let{transform:n,...r}=t;return e!=null&&e.length?e.reduce((s,o)=>o({transform:s,...r}),n):n}function CS(e){return v.useMemo(()=>({draggable:{...Pi.draggable,...e?.draggable},droppable:{...Pi.droppable,...e?.droppable},dragOverlay:{...Pi.dragOverlay,...e?.dragOverlay}}),[e?.draggable,e?.droppable,e?.dragOverlay])}function SS(e){let{activeNode:t,measure:n,initialRect:r,config:s=!0}=e;const o=v.useRef(!1),{x:i,y:a}=typeof s=="boolean"?{x:s,y:s}:s;Mn(()=>{if(!i&&!a||!t){o.current=!1;return}if(o.current||!r)return;const c=t?.node.current;if(!c||c.isConnected===!1)return;const d=n(c),u=Sh(d,r);if(i||(u.x=0),a||(u.y=0),o.current=!0,Math.abs(u.x)>0||Math.abs(u.y)>0){const f=Mh(c);f&&f.scrollBy({top:u.y,left:u.x})}},[t,i,a,r,n])}const $i=v.createContext({...Nn,scaleX:1,scaleY:1});var ur;(function(e){e[e.Uninitialized=0]="Uninitialized",e[e.Initializing=1]="Initializing",e[e.Initialized=2]="Initialized"})(ur||(ur={}));const ac=v.memo(function(t){var n,r,s,o;let{id:i,accessibility:a,autoScroll:l=!0,children:c,sensors:d=bS,collisionDetection:u=IC,measuring:f,modifiers:g,...m}=t;const p=v.useReducer(wS,void 0,xS),[y,b]=p,[_,x]=_C(),[M,C]=v.useState(ur.Uninitialized),k=M===ur.Initialized,{draggable:{active:N,nodes:S,translate:w},droppable:{containers:E}}=y,D=N!=null?S.get(N):null,P=v.useRef({initial:null,translated:null}),L=v.useMemo(()=>{var Fe;return N!=null?{id:N,data:(Fe=D?.data)!=null?Fe:yS,rect:P}:null},[N,D]),$=v.useRef(null),[A,O]=v.useState(null),[T,z]=v.useState(null),H=eo(m,Object.values(m)),j=no("DndDescribedBy",i),G=v.useMemo(()=>E.getEnabled(),[E]),U=CS(f),{droppableRects:W,measureDroppableContainers:Y,measuringScheduled:V}=iS(G,{dragging:k,dependencies:[w.x,w.y],config:U.droppable}),X=sS(S,N),K=v.useMemo(()=>T?Oi(T):null,[T]),q=Ze(),ne=aS(X,U.draggable.measure);SS({activeNode:N!=null?S.get(N):null,config:q.layoutShiftCompensation,initialRect:ne,measure:U.draggable.measure});const ee=Vh(X,U.draggable.measure,ne),ue=Vh(X?X.parentElement:null),J=v.useRef({activatorEvent:null,active:null,activeNode:X,collisionRect:null,collisions:null,droppableRects:W,draggableNodes:S,draggingNode:null,draggingNodeRect:null,droppableContainers:E,over:null,scrollableAncestors:[],scrollAdjustedTranslate:null}),ce=E.getNodeFor((n=J.current.over)==null?void 0:n.id),me=mS({measure:U.dragOverlay.measure}),le=(r=me.nodeRef.current)!=null?r:X,ve=k?(s=me.rect)!=null?s:ee:null,ge=!!(me.nodeRef.current&&me.rect),pe=dS(ge?null:ee),ke=Bh(le?Jt(le):null),$e=uS(k?ce??X:null),ie=pS($e),Me=Xh(g,{transform:{x:w.x-pe.x,y:w.y-pe.y,scaleX:1,scaleY:1},activatorEvent:T,active:L,activeNodeRect:ee,containerNodeRect:ue,draggingNodeRect:ve,over:J.current.over,overlayNodeRect:me.rect,scrollableAncestors:$e,scrollableAncestorRects:ie,windowRect:ke}),Ae=K?us(K,w):null,Ie=fS($e),Xe=Uh(Ie),gt=Uh(Ie,[ee]),pt=us(Me,Xe),Be=ve?$C(ve,Me):null,et=L&&Be?u({active:L,collisionRect:Be,droppableRects:W,droppableContainers:G,pointerCoordinates:Ae}):null,je=kh(et,"id"),[re,fe]=v.useState(null),ye=ge?Me:us(Me,gt),we=LC(ye,(o=re?.rect)!=null?o:null,ee),xe=v.useRef(null),He=v.useCallback((Fe,Ne)=>{let{sensor:ze,options:Ke}=Ne;if($.current==null)return;const tt=S.get($.current);if(!tt)return;const at=Fe.nativeEvent,ct=new ze({active:$.current,activeNode:tt,event:at,options:Ke,context:J,onAbort(yt){if(!S.get(yt))return;const{onDragAbort:Ct}=H.current,Ut={id:yt};Ct?.(Ut),_({type:"onDragAbort",event:Ut})},onPending(yt,Ue,Ct,Ut){if(!S.get(yt))return;const{onDragPending:kn}=H.current,qt={id:yt,constraint:Ue,initialCoordinates:Ct,offset:Ut};kn?.(qt),_({type:"onDragPending",event:qt})},onStart(yt){const Ue=$.current;if(Ue==null)return;const Ct=S.get(Ue);if(!Ct)return;const{onDragStart:Ut}=H.current,st={activatorEvent:at,active:{id:Ue,data:Ct.data,rect:P}};$n.unstable_batchedUpdates(()=>{Ut?.(st),C(ur.Initializing),b({type:Nt.DragStart,initialCoordinates:yt,active:Ue}),_({type:"onDragStart",event:st}),O(xe.current),z(at)})},onMove(yt){b({type:Nt.DragMove,coordinates:yt})},onEnd:wn(Nt.DragEnd),onCancel:wn(Nt.DragCancel)});xe.current=ct;function wn(yt){return async function(){const{active:Ct,collisions:Ut,over:st,scrollAdjustedTranslate:kn}=J.current;let qt=null;if(Ct&&kn){const{cancelDrop:Ln}=H.current;qt={activatorEvent:at,active:Ct,collisions:Ut,delta:kn,over:st},yt===Nt.DragEnd&&typeof Ln=="function"&&await Promise.resolve(Ln(qt))&&(yt=Nt.DragCancel)}$.current=null,$n.unstable_batchedUpdates(()=>{b({type:yt}),C(ur.Uninitialized),fe(null),O(null),z(null),xe.current=null;const Ln=yt===Nt.DragEnd?"onDragEnd":"onDragCancel";if(qt){const jr=H.current[Ln];jr?.(qt),_({type:Ln,event:qt})}})}}},[S]),_e=v.useCallback((Fe,Ne)=>(ze,Ke)=>{const tt=ze.nativeEvent,at=S.get(Ke);if($.current!==null||!at||tt.dndKit||tt.defaultPrevented)return;const ct={active:at};Fe(ze,Ne.options,ct)===!0&&(tt.dndKit={capturedBy:Ne.sensor},$.current=Ke,He(ze,Ne))},[S,He]),Le=oS(d,_e);hS(d),Mn(()=>{ee&&M===ur.Initializing&&C(ur.Initialized)},[ee,M]),v.useEffect(()=>{const{onDragMove:Fe}=H.current,{active:Ne,activatorEvent:ze,collisions:Ke,over:tt}=J.current;if(!Ne||!ze)return;const at={active:Ne,activatorEvent:ze,collisions:Ke,delta:{x:pt.x,y:pt.y},over:tt};$n.unstable_batchedUpdates(()=>{Fe?.(at),_({type:"onDragMove",event:at})})},[pt.x,pt.y]),v.useEffect(()=>{const{active:Fe,activatorEvent:Ne,collisions:ze,droppableContainers:Ke,scrollAdjustedTranslate:tt}=J.current;if(!Fe||$.current==null||!Ne||!tt)return;const{onDragOver:at}=H.current,ct=Ke.get(je),wn=ct&&ct.rect.current?{id:ct.id,rect:ct.rect.current,data:ct.data,disabled:ct.disabled}:null,yt={active:Fe,activatorEvent:Ne,collisions:ze,delta:{x:tt.x,y:tt.y},over:wn};$n.unstable_batchedUpdates(()=>{fe(wn),at?.(yt),_({type:"onDragOver",event:yt})})},[je]),Mn(()=>{J.current={activatorEvent:T,active:L,activeNode:X,collisionRect:Be,collisions:et,droppableRects:W,draggableNodes:S,draggingNode:le,draggingNodeRect:ve,droppableContainers:E,over:re,scrollableAncestors:$e,scrollAdjustedTranslate:pt},P.current={initial:ve,translated:Be}},[L,X,et,Be,S,le,ve,W,E,re,$e,pt]),tS({...q,delta:w,draggingRect:Be,pointerCoordinates:Ae,scrollableAncestors:$e,scrollableAncestorRects:ie});const Ce=v.useMemo(()=>({active:L,activeNode:X,activeNodeRect:ee,activatorEvent:T,collisions:et,containerNodeRect:ue,dragOverlay:me,draggableNodes:S,droppableContainers:E,droppableRects:W,over:re,measureDroppableContainers:Y,scrollableAncestors:$e,scrollableAncestorRects:ie,measuringConfiguration:U,measuringScheduled:V,windowRect:ke}),[L,X,ee,T,et,ue,me,S,E,W,re,Y,$e,ie,U,V,ke]),We=v.useMemo(()=>({activatorEvent:T,activators:Le,active:L,activeNodeRect:ee,ariaDescribedById:{draggable:j},dispatch:b,draggableNodes:S,over:re,measureDroppableContainers:Y}),[T,Le,L,ee,b,j,S,re,Y]);return v.createElement(yh.Provider,{value:x},v.createElement(uo.Provider,{value:We},v.createElement(Yh.Provider,{value:Ce},v.createElement($i.Provider,{value:we},c)),v.createElement(kS,{disabled:a?.restoreFocus===!1})),v.createElement(NC,{...a,hiddenTextDescribedById:j}));function Ze(){const Fe=A?.autoScrollEnabled===!1,Ne=typeof l=="object"?l.enabled===!1:l===!1,ze=k&&!Fe&&!Ne;return typeof l=="object"?{...l,enabled:ze}:{enabled:ze}}}),_S=v.createContext(null),Kh="button",ES="Draggable";function MS(e){let{id:t,data:n,disabled:r=!1,attributes:s}=e;const o=no(ES),{activators:i,activatorEvent:a,active:l,activeNodeRect:c,ariaDescribedById:d,draggableNodes:u,over:f}=v.useContext(uo),{role:g=Kh,roleDescription:m="draggable",tabIndex:p=0}=s??{},y=l?.id===t,b=v.useContext(y?$i:_S),[_,x]=Mi(),[M,C]=Mi(),k=gS(i,t),N=eo(n);Mn(()=>(u.set(t,{id:t,key:o,node:_,activatorNode:M,data:N}),()=>{const w=u.get(t);w&&w.key===o&&u.delete(t)}),[u,t]);const S=v.useMemo(()=>({role:g,tabIndex:p,"aria-disabled":r,"aria-pressed":y&&g===Kh?!0:void 0,"aria-roledescription":m,"aria-describedby":d.draggable}),[r,g,p,y,m,d.draggable]);return{active:l,activatorEvent:a,activeNodeRect:c,attributes:S,isDragging:y,listeners:r?void 0:k,node:_,over:f,setNodeRef:x,setActivatorNodeRef:C,transform:b}}function qh(){return v.useContext(Yh)}const NS="Droppable",TS={timeout:25};function OS(e){let{data:t,disabled:n=!1,id:r,resizeObserverConfig:s}=e;const o=no(NS),{active:i,dispatch:a,over:l,measureDroppableContainers:c}=v.useContext(uo),d=v.useRef({disabled:n}),u=v.useRef(!1),f=v.useRef(null),g=v.useRef(null),{disabled:m,updateMeasurementsFor:p,timeout:y}={...TS,...s},b=eo(p??r),_=v.useCallback(()=>{if(!u.current){u.current=!0;return}g.current!=null&&clearTimeout(g.current),g.current=setTimeout(()=>{c(Array.isArray(b.current)?b.current:[b.current]),g.current=null},y)},[y]),x=Li({callback:_,disabled:m||!i}),M=v.useCallback((S,w)=>{x&&(w&&(x.unobserve(w),u.current=!1),S&&x.observe(S))},[x]),[C,k]=Mi(M),N=eo(t);return v.useEffect(()=>{!x||!C.current||(x.disconnect(),u.current=!1,x.observe(C.current))},[C,x]),v.useEffect(()=>(a({type:Nt.RegisterDroppable,element:{id:r,key:o,disabled:n,node:C,rect:f,data:N}}),()=>a({type:Nt.UnregisterDroppable,key:o,id:r})),[r]),v.useEffect(()=>{n!==d.current.disabled&&(a({type:Nt.SetDroppableDisabled,id:r,key:o,disabled:n}),d.current.disabled=n)},[r,o,n,a]),{active:i,rect:f,isOver:l?.id===r,node:C,over:l,setNodeRef:k}}function AS(e){let{animation:t,children:n}=e;const[r,s]=v.useState(null),[o,i]=v.useState(null),a=Ni(n);return!n&&!r&&a&&s(a),Mn(()=>{if(!o)return;const l=r?.key,c=r?.props.id;if(l==null||c==null){s(null);return}Promise.resolve(t(c,o)).then(()=>{s(null)})},[t,r,o]),v.createElement(v.Fragment,null,n,r?v.cloneElement(r,{ref:i}):null)}const DS={x:0,y:0,scaleX:1,scaleY:1};function IS(e){let{children:t}=e;return v.createElement(uo.Provider,{value:Gh},v.createElement($i.Provider,{value:DS},t))}const LS={position:"fixed",touchAction:"none"},PS=e=>Ti(e)?"transform 250ms ease":void 0,$S=v.forwardRef((e,t)=>{let{as:n,activatorEvent:r,adjustScale:s,children:o,className:i,rect:a,style:l,transform:c,transition:d=PS}=e;if(!a)return null;const u=s?c:{...c,scaleX:1,scaleY:1},f={...LS,width:a.width,height:a.height,top:a.top,left:a.left,transform:jn.Transform.toString(u),transformOrigin:s&&r?TC(r,a):void 0,transition:typeof d=="function"?d(r):d,...l};return v.createElement(n,{className:i,style:f,ref:t},o)}),FS={duration:250,easing:"ease",keyframes:e=>{let{transform:{initial:t,final:n}}=e;return[{transform:jn.Transform.toString(t)},{transform:jn.Transform.toString(n)}]},sideEffects:(e=>t=>{let{active:n,dragOverlay:r}=t;const s={},{styles:o,className:i}=e;if(o!=null&&o.active)for(const[a,l]of Object.entries(o.active))l!==void 0&&(s[a]=n.node.style.getPropertyValue(a),n.node.style.setProperty(a,l));if(o!=null&&o.dragOverlay)for(const[a,l]of Object.entries(o.dragOverlay))l!==void 0&&r.node.style.setProperty(a,l);return i!=null&&i.active&&n.node.classList.add(i.active),i!=null&&i.dragOverlay&&r.node.classList.add(i.dragOverlay),function(){for(const[l,c]of Object.entries(s))n.node.style.setProperty(l,c);i!=null&&i.active&&n.node.classList.remove(i.active)}})({styles:{active:{opacity:"0"}}})};function zS(e){let{config:t,draggableNodes:n,droppableContainers:r,measuringConfiguration:s}=e;return Ei((o,i)=>{if(t===null)return;const a=n.get(o);if(!a)return;const l=a.node.current;if(!l)return;const c=Wh(i);if(!c)return;const{transform:d}=Jt(i).getComputedStyle(i),u=_h(d);if(!u)return;const f=typeof t=="function"?t:VS(t);return Ih(l,s.draggable.measure),f({active:{id:o,data:a.data,node:l,rect:s.draggable.measure(l)},draggableNodes:n,dragOverlay:{node:i,rect:s.dragOverlay.measure(c)},droppableContainers:r,measuringConfiguration:s,transform:u})})}function VS(e){const{duration:t,easing:n,sideEffects:r,keyframes:s}={...FS,...e};return o=>{let{active:i,dragOverlay:a,transform:l,...c}=o;if(!t)return;const d={x:a.rect.left-i.rect.left,y:a.rect.top-i.rect.top},u={scaleX:l.scaleX!==1?i.rect.width*l.scaleX/a.rect.width:1,scaleY:l.scaleY!==1?i.rect.height*l.scaleY/a.rect.height:1},f={x:l.x-d.x,y:l.y-d.y,...u},g=s({...c,active:i,dragOverlay:a,transform:{initial:l,final:f}}),[m]=g,p=g[g.length-1];if(JSON.stringify(m)===JSON.stringify(p))return;const y=r?.({active:i,dragOverlay:a,...c}),b=a.node.animate(g,{duration:t,easing:n,fill:"forwards"});return new Promise(_=>{b.onfinish=()=>{y?.(),_()}})}}let Zh=0;function HS(e){return v.useMemo(()=>{if(e!=null)return Zh++,Zh},[e])}const US=v.memo(e=>{let{adjustScale:t=!1,children:n,dropAnimation:r,style:s,transition:o,modifiers:i,wrapperElement:a="div",className:l,zIndex:c=999}=e;const{activatorEvent:d,active:u,activeNodeRect:f,containerNodeRect:g,draggableNodes:m,droppableContainers:p,dragOverlay:y,over:b,measuringConfiguration:_,scrollableAncestors:x,scrollableAncestorRects:M,windowRect:C}=qh(),k=v.useContext($i),N=HS(u?.id),S=Xh(i,{activatorEvent:d,active:u,activeNodeRect:f,containerNodeRect:g,draggingNodeRect:y.rect,over:b,overlayNodeRect:y.rect,scrollableAncestors:x,scrollableAncestorRects:M,transform:k,windowRect:C}),w=ic(f),E=zS({config:r,draggableNodes:m,droppableContainers:p,measuringConfiguration:_}),D=w?y.setRef:void 0;return v.createElement(IS,null,v.createElement(AS,{animation:E},u&&N?v.createElement($S,{key:N,id:u.id,ref:D,as:a,activatorEvent:d,adjustScale:t,className:l,transition:o,rect:w,style:{zIndex:c,...s},transform:S},n):null))});function gs(e,t,n){const r=e.slice();return r.splice(n<0?r.length+n:n,0,r.splice(t,1)[0]),r}function BS(e,t){return e.reduce((n,r,s)=>{const o=t.get(r);return o&&(n[s]=o),n},Array(e.length))}function Fi(e){return e!==null&&e>=0}function jS(e,t){if(e===t)return!0;if(e.length!==t.length)return!1;for(let n=0;n{let{rects:t,activeIndex:n,overIndex:r,index:s}=e;const o=gs(t,r,n),i=t[s],a=o[s];return!a||!i?null:{x:a.left-i.left,y:a.top-i.top,scaleX:a.width/i.width,scaleY:a.height/i.height}},Vi={scaleX:1,scaleY:1},lc=e=>{var t;let{activeIndex:n,activeNodeRect:r,index:s,rects:o,overIndex:i}=e;const a=(t=o[n])!=null?t:r;if(!a)return null;if(s===n){const c=o[i];return c?{x:0,y:nn&&s<=i?{x:0,y:-a.height-l,...Vi}:s=i?{x:0,y:a.height+l,...Vi}:{x:0,y:0,...Vi}};function GS(e,t,n){const r=e[t],s=e[t-1],o=e[t+1];return r?nr.map(k=>typeof k=="object"&&"id"in k?k.id:k),[r]),m=i!=null,p=i?g.indexOf(i.id):-1,y=c?g.indexOf(c.id):-1,b=v.useRef(g),_=!jS(g,b.current),x=y!==-1&&p===-1||_,M=WS(o);Mn(()=>{_&&m&&d(g)},[_,g,m,d]),v.useEffect(()=>{b.current=g},[g]);const C=v.useMemo(()=>({activeIndex:p,containerId:u,disabled:M,disableTransforms:x,items:g,overIndex:y,useDragOverlay:f,sortedRects:BS(g,l),strategy:s}),[p,u,M.draggable,M.droppable,x,g,y,l,f,s]);return v.createElement(Jh.Provider,{value:C},t)}const Rh=e=>{let{id:t,items:n,activeIndex:r,overIndex:s}=e;return gs(n,r,s).indexOf(t)},eg=e=>{let{containerId:t,isSorting:n,wasDragging:r,index:s,items:o,newIndex:i,previousItems:a,previousContainerId:l,transition:c}=e;return!c||!r||a!==o&&s===i?!1:n?!0:i!==s&&t===l},YS={duration:200,easing:"ease"},tg="transform",XS=jn.Transition.toString({property:tg,duration:0,easing:"linear"}),KS={roleDescription:"sortable"};function qS(e){let{disabled:t,index:n,node:r,rect:s}=e;const[o,i]=v.useState(null),a=v.useRef(n);return Mn(()=>{if(!t&&n!==a.current&&r.current){const l=s.current;if(l){const c=hs(r.current,{ignoreTransform:!0}),d={x:l.left-c.left,y:l.top-c.top,scaleX:l.width/c.width,scaleY:l.height/c.height};(d.x||d.y)&&i(d)}}n!==a.current&&(a.current=n)},[t,n,r,s]),v.useEffect(()=>{o&&i(null)},[o]),o}function Ui(e){let{animateLayoutChanges:t=eg,attributes:n,disabled:r,data:s,getNewIndex:o=Rh,id:i,strategy:a,resizeObserverConfig:l,transition:c=YS}=e;const{items:d,containerId:u,activeIndex:f,disabled:g,disableTransforms:m,sortedRects:p,overIndex:y,useDragOverlay:b,strategy:_}=v.useContext(Jh),x=ZS(r,g),M=d.indexOf(i),C=v.useMemo(()=>({sortable:{containerId:u,index:M,items:d},...s}),[u,s,M,d]),k=v.useMemo(()=>d.slice(d.indexOf(i)),[d,i]),{rect:N,node:S,isOver:w,setNodeRef:E}=OS({id:i,data:C,disabled:x.droppable,resizeObserverConfig:{updateMeasurementsFor:k,...l}}),{active:D,activatorEvent:P,activeNodeRect:L,attributes:$,setNodeRef:A,listeners:O,isDragging:T,over:z,setActivatorNodeRef:H,transform:j}=MS({id:i,data:C,attributes:{...KS,...n},disabled:x.draggable}),G=pC(E,A),U=!!D,W=U&&!m&&Fi(f)&&Fi(y),Y=!b&&T,V=Y&&W?j:null,K=W?V??(a??_)({rects:p,activeNodeRect:L,activeIndex:f,overIndex:y,index:M}):null,q=Fi(f)&&Fi(y)?o({id:i,items:d,activeIndex:f,overIndex:y}):M,ne=D?.id,ee=v.useRef({activeId:ne,items:d,newIndex:q,containerId:u}),ue=d!==ee.current.items,J=t({active:D,containerId:u,isDragging:T,isSorting:U,id:i,index:M,items:d,newIndex:ee.current.newIndex,previousItems:ee.current.items,previousContainerId:ee.current.containerId,transition:c,wasDragging:ee.current.activeId!=null}),ce=qS({disabled:!J,index:M,node:S,rect:N});return v.useEffect(()=>{U&&ee.current.newIndex!==q&&(ee.current.newIndex=q),u!==ee.current.containerId&&(ee.current.containerId=u),d!==ee.current.items&&(ee.current.items=d)},[U,q,u,d]),v.useEffect(()=>{if(ne===ee.current.activeId)return;if(ne!=null&&ee.current.activeId==null){ee.current.activeId=ne;return}const le=setTimeout(()=>{ee.current.activeId=ne},50);return()=>clearTimeout(le)},[ne]),{active:D,activeIndex:f,attributes:$,data:C,rect:N,index:M,newIndex:q,items:d,isOver:w,isSorting:U,isDragging:T,listeners:O,node:S,overIndex:y,over:z,setNodeRef:G,setActivatorNodeRef:H,setDroppableNodeRef:E,setDraggableNodeRef:A,transform:ce??K,transition:me()};function me(){if(ce||ue&&ee.current.newIndex===M)return XS;if(!(Y&&!Ti(P)||!c)&&(U||J))return jn.Transition.toString({...c,property:tg})}}function ZS(e,t){var n,r;return typeof e=="boolean"?{draggable:e,droppable:!1}:{draggable:(n=e?.draggable)!=null?n:t.draggable,droppable:(r=e?.droppable)!=null?r:t.droppable}}function fo(e){if(!e)return!1;const t=e.data.current;return!!(t&&"sortable"in t&&typeof t.sortable=="object"&&"containerId"in t.sortable&&"items"in t.sortable&&"index"in t.sortable)}const QS=[qe.Down,qe.Right,qe.Up,qe.Left],Bi=(e,t)=>{let{context:{active:n,collisionRect:r,droppableRects:s,droppableContainers:o,over:i,scrollableAncestors:a}}=t;if(QS.includes(e.code)){if(e.preventDefault(),!n||!r)return;const l=[];o.getEnabled().forEach(u=>{if(!u||u!=null&&u.disabled)return;const f=s.get(u.id);if(f)switch(e.code){case qe.Down:r.topf.top&&l.push(u);break;case qe.Left:r.left>f.left&&l.push(u);break;case qe.Right:r.left1&&(d=c[1].id),d!=null){const u=o.get(n.id),f=o.get(d),g=f?s.get(f.id):null,m=f?.node.current;if(m&&g&&u&&f){const y=Di(m).some((k,N)=>a[N]!==k),b=ng(u,f),_=JS(u,f),x=y||!b?{x:0,y:0}:{x:_?r.width-g.width:0,y:_?r.height-g.height:0},M={x:g.left,y:g.top};return x.x&&x.y?M:ro(M,x)}}}};function ng(e,t){return!fo(e)||!fo(t)?!1:e.data.current.sortable.containerId===t.data.current.sortable.containerId}function JS(e,t){return!fo(e)||!fo(t)||!ng(e,t)?!1:e.data.current.sortable.indexwindow.G7Core,rg=({item:e,isSelected:t=!1,isExpanded:n=!1,level:r=0,onClick:s,onToggle:o,onExpandToggle:i,className:a="",selectedClassName:l="bg-blue-50 dark:bg-blue-900/30 border-blue-500",defaultClassName:c="bg-white dark:bg-gray-800 border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600",dragHandleClassName:d="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300",iconContainerClassName:u="bg-gray-100 dark:bg-gray-700",contentClassName:f="",enableDrag:g=!0,toggleDisabled:m=!1})=>{const{attributes:p,listeners:y,setNodeRef:b,transform:_,transition:x,isDragging:M}=Ui({id:e.id}),C={transform:jn.Transform.toString(_),transition:x,opacity:M?.5:1},k=r*24+12,S=`flex items-center gap-2 cursor-pointer transition-colors ${t?l:c}`,w=e_()?.style?.mergeClasses?.(S,a)??`${S} ${a}`.trim();return h.jsxs(I,{ref:b,style:C,className:w,onClick:s,children:[g&&h.jsx(I,{...p,...y,className:`flex-shrink-0 cursor-grab active:cursor-grabbing p-1 ${d}`,style:{paddingLeft:`${k}px`},children:h.jsx(oe,{name:"grip-vertical",className:"w-4 h-4"})}),!g&&r>0&&h.jsx(I,{style:{paddingLeft:`${k}px`}}),g&&h.jsx(h.Fragment,{children:e.hasChildren?h.jsx(ae,{type:"button",onClick:E=>{E.stopPropagation(),i?.()},className:`flex-shrink-0 p-1 ${d}`,children:h.jsx(oe,{name:n?"chevron-down":"chevron-right",className:"w-4 h-4"})}):h.jsx(I,{className:"flex-shrink-0 w-6"})}),h.jsx(I,{className:`flex-shrink-0 w-8 h-8 flex items-center justify-center rounded ${u}`,children:h.jsx(oe,{name:e.icon||"file",className:"w-4 h-4 text-gray-600 dark:text-gray-400"})}),h.jsxs(I,{className:`flex-1 min-w-0 ${f}`,children:[h.jsxs(I,{className:"flex items-center gap-1",children:[h.jsx(Q,{className:"text-sm font-medium text-gray-900 dark:text-white truncate",children:e.name}),e.isModuleMenu&&h.jsx(oe,{name:"cube",className:"w-2 h-2 text-gray-300 dark:text-gray-500 ml-2",title:e.moduleName})]}),h.jsx(Q,{className:"text-xs text-gray-500 dark:text-gray-400 truncate block",children:e.url})]}),h.jsx(I,{className:"flex-shrink-0",onClick:E=>E.stopPropagation(),children:h.jsx(Qa,{checked:e.isActive,onChange:()=>o?.(new MouseEvent("click")),size:"sm",disabled:m})})]})},t_=()=>window.G7Core;function sg(e,t,n=0,r=null){const s=[];for(const o of e)s.push({item:o,depth:n,parentId:r}),o.children?.length>0&&t.has(o.id)&&s.push(...sg(o.children,t,n+1,o.id));return s}function cc(e,t){for(const n of e){if(n.id===t)return n;if(n.children){const r=cc(n.children,t);if(r)return r}}return null}function og(e){const t=[];if(e.children)for(const n of e.children)t.push(n.id),t.push(...og(n));return t}function ig(e,t){return e.filter(n=>n.id!==t).map(n=>n.children?.length>0?{...n,children:ig(n.children,t)}:n)}function ag(e,t,n,r){if(n===null){const s=[...e];return s.splice(r,0,t),s}return e.map(s=>{if(s.id===n){const o=[...s.children||[]];return o.splice(r,0,t),{...s,children:o}}return s.children?.length>0?{...s,children:ag(s.children,t,n,r)}:s})}function lg(e,t,n,r){if(t===null){const s=e.findIndex(i=>i.id===n),o=e.findIndex(i=>i.id===r);return s===-1||o===-1?e:gs(e,s,o)}return e.map(s=>{if(s.id===t&&s.children){const o=s.children.findIndex(a=>a.id===n),i=s.children.findIndex(a=>a.id===r);return o===-1||i===-1?s:{...s,children:gs(s.children,o,i)}}return s.children?.length>0?{...s,children:lg(s.children,t,n,r)}:s})}function cg(e){const t=e.map((s,o)=>({id:s.id,order:o+1})),n={},r=s=>{s.forEach(o=>{o.children?.length>0&&(n[o.id]=o.children.map((i,a)=>({id:i.id,order:a+1})),r(o.children))})};return r(e),{parent_menus:t,child_menus:n}}const n_=({items:e=[],selectedId:t,onSelect:n,onOrderChange:r,onToggleStatus:s,title:o,className:i="",listClassName:a="",childrenContainerClassName:l="bg-gray-50 dark:bg-gray-900/50",itemClassName:c="py-2.5 px-3 mx-4 my-2 rounded-lg border",itemSelectedClassName:d,itemDefaultClassName:u,titleClassName:f="px-4 py-3 border-b border-gray-200 dark:border-gray-700",enableDrag:g=!0,toggleDisabled:m=!1,id:p,editorAttrs:y})=>{const[b,_]=v.useState(new Set),[x,M]=v.useState(null),[C,k]=v.useState(e),N=v.useRef(!1),S=v.useRef(""),w=v.useRef(""),E=v.useCallback(W=>JSON.stringify(W),[]),D=v.useCallback(W=>W.map(Y=>{const V=Y.children?.map(X=>X.id).join(",")||"";return`${Y.id}:${V}`}).join("|"),[]);v.useEffect(()=>{const W=D(e),Y=E(e);if(N.current){const V=D(C);(W===V||W!==S.current)&&(N.current=!1,S.current=W,w.current=Y,(W!==V||Y!==E(C))&&k(e));return}Y!==w.current&&(S.current=W,w.current=Y,k(e))},[e,D,E,C]);const P=Zl(fs(io,{activationConstraint:{distance:8}}),fs(oo,{coordinateGetter:Bi})),L=v.useCallback(W=>{if(typeof W=="string")return W;const V=t_()?.locale?.current?.()||"ko";return W[V]||W.en||Object.values(W)[0]||""},[]),$=v.useCallback(W=>{_(Y=>{const V=new Set(Y);return V.has(W)?V.delete(W):V.add(W),V})},[]),A=v.useCallback(W=>{n?.(W)},[n]),O=v.useCallback((W,Y,V)=>{V.stopPropagation(),s?.(W,Y)},[s]),T=W=>{M(W.active.id)},z=W=>{const{active:Y,over:V}=W;if(M(null),!V||Y.id===V.id)return;const X=j.find(q=>q.item.id===Number(Y.id)),K=j.find(q=>q.item.id===Number(V.id));if(!(!X||!K))if(X.parentId===K.parentId){const q=lg(C,X.parentId,Number(Y.id),Number(V.id));k(q),N.current=!0,r?.(cg(q))}else{const q=X.item;if(og(q).includes(Number(V.id)))return;let ee=ig(C,q.id);const ue=j.findIndex(pe=>pe.item.id===Number(Y.id)),J=j.findIndex(pe=>pe.item.id===Number(V.id)),ce=ue>J,me=K.parentId;let le;if(me===null)le=ee.findIndex(pe=>pe.id===Number(V.id)),le===-1?le=ee.length:ce||(le+=1);else{const ke=cc(ee,me)?.children||[];le=ke.findIndex($e=>$e.id===Number(V.id)),le===-1?le=ke.length:ce||(le+=1)}const ve={...q,parent_id:me,children:q.children||[]};ee=ag(ee,ve,me,le),k(ee),N.current=!0;const ge=cg(ee);ge.moved_items=[{id:q.id,new_parent_id:me}],r?.(ge)}},H=W=>({id:W.id,name:L(W.name),slug:W.slug,url:W.url,icon:W.icon,isActive:W.is_active,isModuleMenu:!!W.module_id,moduleName:W.module?L(W.module.name):void 0,hasChildren:W.children&&W.children.length>0}),j=v.useMemo(()=>sg(C,b),[C,b]),G=v.useMemo(()=>j.map(W=>W.item.id),[j]),U=x?cc(C,Number(x)):null;return h.jsxs(I,{className:i,id:p,...y,children:[o&&h.jsx(I,{className:f,children:h.jsx(Gr,{className:"text-sm font-medium text-gray-900 dark:text-white",children:o})}),h.jsxs(ac,{sensors:P,collisionDetection:Ql,onDragStart:T,onDragEnd:z,children:[h.jsx(Hi,{items:G,strategy:lc,children:h.jsx(I,{className:a,children:j.map(W=>h.jsx(I,{className:W.depth>0?l:"",children:h.jsx(rg,{item:H(W.item),isSelected:t===W.item.id,isExpanded:b.has(W.item.id),level:W.depth,onClick:()=>A(W.item),onToggle:Y=>O(W.item.id,W.item.is_active,Y),onExpandToggle:()=>$(W.item.id),className:c,selectedClassName:d,defaultClassName:u,enableDrag:g,toggleDisabled:m})},W.item.id))})}),h.jsx(US,{children:U?h.jsx(I,{className:"bg-white dark:bg-gray-800 shadow-lg rounded-lg border border-blue-500 p-3",children:h.jsx(I,{className:"flex items-center gap-2",children:h.jsx(I,{className:"text-sm font-medium text-gray-900 dark:text-white",children:L(U.name)})})}):null})]})]})},r_=[{value:"LayoutDashboard",label:"LayoutDashboard",faIcon:"tachometer-alt"},{value:"Settings",label:"Settings",faIcon:"cog"},{value:"Menu",label:"Menu",faIcon:"bars"},{value:"Users",label:"Users",faIcon:"users"},{value:"Package",label:"Package",faIcon:"cube"},{value:"Puzzle",label:"Puzzle",faIcon:"puzzle-piece"},{value:"FileText",label:"FileText",faIcon:"file-alt"},{value:"ShoppingCart",label:"ShoppingCart",faIcon:"shopping-cart"},{value:"MessageSquare",label:"MessageSquare",faIcon:"comment"},{value:"Home",label:"Home",faIcon:"home"},{value:"User",label:"User",faIcon:"user"},{value:"Search",label:"Search",faIcon:"search"},{value:"Bell",label:"Bell",faIcon:"bell"},{value:"Mail",label:"Mail",faIcon:"envelope"},{value:"Calendar",label:"Calendar",faIcon:"calendar"},{value:"Chart",label:"Chart",faIcon:"chart-bar"},{value:"Database",label:"Database",faIcon:"database"},{value:"Lock",label:"Lock",faIcon:"lock"},{value:"Globe",label:"Globe",faIcon:"globe"},{value:"Image",label:"Image",faIcon:"image"}],s_=({value:e,onChange:t,options:n=r_,placeholder:r,searchPlaceholder:s,noResultsText:o,className:i="",disabled:a=!1,name:l,id:c,editorAttrs:d})=>{const[u,f]=v.useState(!1),[g,m]=v.useState(""),p=v.useRef(null),y=n.find(x=>{if(!e)return!1;if(e.includes("fa-")){const M=e.replace(/^(fas|far|fab|fal|fad)\s+fa-/,"");return x.faIcon===M||x.faIcon===e}return x.value===e||x.faIcon===e}),b=n.filter(x=>x.label.toLowerCase().includes(g.toLowerCase()));v.useEffect(()=>{const x=M=>{p.current&&!p.current.contains(M.target)&&(f(!1),m(""))};if(u)return document.addEventListener("mousedown",x),()=>document.removeEventListener("mousedown",x)},[u]);const _=x=>{const M=`fas fa-${x.faIcon}`;t?.(M),f(!1),m("")};return h.jsxs(I,{ref:p,className:`relative ${i}`,id:c,...d,children:[h.jsxs(ae,{type:"button",onClick:()=>!a&&f(!u),disabled:a,className:` + `},lC={onDragStart(t){let{active:e}=t;return"Picked up draggable item "+e.id+"."},onDragOver(t){let{active:e,over:n}=t;return n?"Draggable item "+e.id+" was moved over droppable area "+n.id+".":"Draggable item "+e.id+" is no longer over a droppable area."},onDragEnd(t){let{active:e,over:n}=t;return n?"Draggable item "+e.id+" was dropped over droppable area "+n.id:"Draggable item "+e.id+" was dropped."},onDragCancel(t){let{active:e}=t;return"Dragging was cancelled. Draggable item "+e.id+" was dropped."}};function cC(t){let{announcements:e=lC,container:n,hiddenTextDescribedById:r,screenReaderInstructions:s=aC}=t;const{announce:o,announcement:i}=sC(),a=Ws("DndLiveRegion"),[l,c]=v.useState(!1);if(v.useEffect(()=>{c(!0)},[]),oC(v.useMemo(()=>({onDragStart(u){let{active:f}=u;o(e.onDragStart({active:f}))},onDragMove(u){let{active:f,over:g}=u;e.onDragMove&&o(e.onDragMove({active:f,over:g}))},onDragOver(u){let{active:f,over:g}=u;o(e.onDragOver({active:f,over:g}))},onDragEnd(u){let{active:f,over:g}=u;o(e.onDragEnd({active:f,over:g}))},onDragCancel(u){let{active:f,over:g}=u;o(e.onDragCancel({active:f,over:g}))}}),[o,e])),!l)return null;const d=v.createElement(v.Fragment,null,v.createElement(nC,{id:r,value:s.draggable}),v.createElement(rC,{id:a,announcement:i}));return n?Tn.createPortal(d,n):d}var _t;(function(t){t.DragStart="dragStart",t.DragMove="dragMove",t.DragEnd="dragEnd",t.DragCancel="dragCancel",t.DragOver="dragOver",t.RegisterDroppable="registerDroppable",t.SetDroppableDisabled="setDroppableDisabled",t.UnregisterDroppable="unregisterDroppable"})(_t||(_t={}));function Ci(){}function Zr(t,e){return v.useMemo(()=>({sensor:t,options:e??{}}),[t,e])}function jl(){for(var t=arguments.length,e=new Array(t),n=0;n[...e].filter(r=>r!=null),[...e])}const kn=Object.freeze({x:0,y:0});function ah(t,e){return Math.sqrt(Math.pow(t.x-e.x,2)+Math.pow(t.y-e.y,2))}function dC(t,e){const n=ki(t);if(!n)return"0 0";const r={x:(n.x-e.left)/e.width*100,y:(n.y-e.top)/e.height*100};return r.x+"% "+r.y+"%"}function lh(t,e){let{data:{value:n}}=t,{data:{value:r}}=e;return n-r}function uC(t,e){let{data:{value:n}}=t,{data:{value:r}}=e;return r-n}function ch(t){let{left:e,top:n,height:r,width:s}=t;return[{x:e,y:n},{x:e+s,y:n},{x:e,y:n+r},{x:e+s,y:n+r}]}function dh(t,e){if(!t||t.length===0)return null;const[n]=t;return n[e]}function uh(t,e,n){return e===void 0&&(e=t.left),n===void 0&&(n=t.top),{x:e+t.width*.5,y:n+t.height*.5}}const Wl=t=>{let{collisionRect:e,droppableRects:n,droppableContainers:r}=t;const s=uh(e,e.left,e.top),o=[];for(const i of r){const{id:a}=i,l=n.get(a);if(l){const c=ah(uh(l),s);o.push({id:a,data:{droppableContainer:i,value:c}})}}return o.sort(lh)},fC=t=>{let{collisionRect:e,droppableRects:n,droppableContainers:r}=t;const s=ch(e),o=[];for(const i of r){const{id:a}=i,l=n.get(a);if(l){const c=ch(l),d=s.reduce((f,g,m)=>f+ah(c[m],g),0),u=Number((d/4).toFixed(4));o.push({id:a,data:{droppableContainer:i,value:u}})}}return o.sort(lh)};function hC(t,e){const n=Math.max(e.top,t.top),r=Math.max(e.left,t.left),s=Math.min(e.left+e.width,t.left+t.width),o=Math.min(e.top+e.height,t.top+t.height),i=s-r,a=o-n;if(r{let{collisionRect:e,droppableRects:n,droppableContainers:r}=t;const s=[];for(const o of r){const{id:i}=o,a=n.get(i);if(a){const l=hC(a,e);l>0&&s.push({id:i,data:{droppableContainer:o,value:l}})}}return s.sort(uC)};function pC(t,e,n){return{...t,scaleX:e&&n?e.width/n.width:1,scaleY:e&&n?e.height/n.height:1}}function fh(t,e){return t&&e?{x:t.left-e.left,y:t.top-e.top}:kn}function mC(t){return function(n){for(var r=arguments.length,s=new Array(r>1?r-1:0),o=1;o({...i,top:i.top+t*a.y,bottom:i.bottom+t*a.y,left:i.left+t*a.x,right:i.right+t*a.x}),{...n})}}const bC=mC(1);function hh(t){if(t.startsWith("matrix3d(")){const e=t.slice(9,-1).split(/, /);return{x:+e[12],y:+e[13],scaleX:+e[0],scaleY:+e[5]}}else if(t.startsWith("matrix(")){const e=t.slice(7,-1).split(/, /);return{x:+e[4],y:+e[5],scaleX:+e[0],scaleY:+e[3]}}return null}function yC(t,e,n){const r=hh(e);if(!r)return t;const{scaleX:s,scaleY:o,x:i,y:a}=r,l=t.left-i-(1-s)*parseFloat(n),c=t.top-a-(1-o)*parseFloat(n.slice(n.indexOf(" ")+1)),d=s?t.width/s:t.width,u=o?t.height/o:t.height;return{width:d,height:u,top:c,right:l+d,bottom:c+u,left:l}}const vC={ignoreTransform:!1};function Qr(t,e){e===void 0&&(e=vC);let n=t.getBoundingClientRect();if(e.ignoreTransform){const{transform:c,transformOrigin:d}=Xt(t).getComputedStyle(t);c&&(n=yC(n,c,d))}const{top:r,left:s,width:o,height:i,bottom:a,right:l}=n;return{top:r,left:s,width:o,height:i,bottom:a,right:l}}function gh(t){return Qr(t,{ignoreTransform:!0})}function xC(t){const e=t.innerWidth,n=t.innerHeight;return{top:0,left:0,right:e,bottom:n,width:e,height:n}}function wC(t,e){return e===void 0&&(e=Xt(t).getComputedStyle(t)),e.position==="fixed"}function kC(t,e){e===void 0&&(e=Xt(t).getComputedStyle(t));const n=/(auto|scroll|overlay)/;return["overflow","overflowX","overflowY"].some(s=>{const o=e[s];return typeof o=="string"?n.test(o):!1})}function Si(t,e){const n=[];function r(s){if(e!=null&&n.length>=e||!s)return n;if(Ul(s)&&s.scrollingElement!=null&&!n.includes(s.scrollingElement))return n.push(s.scrollingElement),n;if(!Us(s)||rh(s)||n.includes(s))return n;const o=Xt(t).getComputedStyle(s);return s!==t&&kC(s,o)&&n.push(s),wC(s,o)?n:r(s.parentNode)}return t?r(t):n}function ph(t){const[e]=Si(t,1);return e??null}function Gl(t){return!bi||!t?null:Kr(t)?t:Hl(t)?Ul(t)||t===Xr(t).scrollingElement?window:Us(t)?t:null:null}function mh(t){return Kr(t)?t.scrollX:t.scrollLeft}function bh(t){return Kr(t)?t.scrollY:t.scrollTop}function Yl(t){return{x:mh(t),y:bh(t)}}var Ot;(function(t){t[t.Forward=1]="Forward",t[t.Backward=-1]="Backward"})(Ot||(Ot={}));function yh(t){return!bi||!t?!1:t===document.scrollingElement}function vh(t){const e={x:0,y:0},n=yh(t)?{height:window.innerHeight,width:window.innerWidth}:{height:t.clientHeight,width:t.clientWidth},r={x:t.scrollWidth-n.width,y:t.scrollHeight-n.height},s=t.scrollTop<=e.y,o=t.scrollLeft<=e.x,i=t.scrollTop>=r.y,a=t.scrollLeft>=r.x;return{isTop:s,isLeft:o,isBottom:i,isRight:a,maxScroll:r,minScroll:e}}const CC={x:.2,y:.2};function SC(t,e,n,r,s){let{top:o,left:i,right:a,bottom:l}=n;r===void 0&&(r=10),s===void 0&&(s=CC);const{isTop:c,isBottom:d,isLeft:u,isRight:f}=vh(t),g={x:0,y:0},m={x:0,y:0},p={height:e.height*s.y,width:e.width*s.x};return!c&&o<=e.top+p.height?(g.y=Ot.Backward,m.y=r*Math.abs((e.top+p.height-o)/p.height)):!d&&l>=e.bottom-p.height&&(g.y=Ot.Forward,m.y=r*Math.abs((e.bottom-p.height-l)/p.height)),!f&&a>=e.right-p.width?(g.x=Ot.Forward,m.x=r*Math.abs((e.right-p.width-a)/p.width)):!u&&i<=e.left+p.width&&(g.x=Ot.Backward,m.x=r*Math.abs((e.left+p.width-i)/p.width)),{direction:g,speed:m}}function _C(t){if(t===document.scrollingElement){const{innerWidth:o,innerHeight:i}=window;return{top:0,left:0,right:o,bottom:i,width:o,height:i}}const{top:e,left:n,right:r,bottom:s}=t.getBoundingClientRect();return{top:e,left:n,right:r,bottom:s,width:t.clientWidth,height:t.clientHeight}}function xh(t){return t.reduce((e,n)=>qr(e,Yl(n)),kn)}function EC(t){return t.reduce((e,n)=>e+mh(n),0)}function MC(t){return t.reduce((e,n)=>e+bh(n),0)}function wh(t,e){if(e===void 0&&(e=Qr),!t)return;const{top:n,left:r,bottom:s,right:o}=e(t);ph(t)&&(s<=0||o<=0||n>=window.innerHeight||r>=window.innerWidth)&&t.scrollIntoView({block:"center",inline:"center"})}const NC=[["x",["left","right"],EC],["y",["top","bottom"],MC]];class Kl{constructor(e,n){this.rect=void 0,this.width=void 0,this.height=void 0,this.top=void 0,this.bottom=void 0,this.right=void 0,this.left=void 0;const r=Si(n),s=xh(r);this.rect={...e},this.width=e.width,this.height=e.height;for(const[o,i,a]of NC)for(const l of i)Object.defineProperty(this,l,{get:()=>{const c=a(r),d=s[o]-c;return this.rect[l]+d},enumerable:!0});Object.defineProperty(this,"rect",{enumerable:!1})}}class Ys{constructor(e){this.target=void 0,this.listeners=[],this.removeAll=()=>{this.listeners.forEach(n=>{var r;return(r=this.target)==null?void 0:r.removeEventListener(...n)})},this.target=e}add(e,n,r){var s;(s=this.target)==null||s.addEventListener(e,n,r),this.listeners.push([e,n,r])}}function OC(t){const{EventTarget:e}=Xt(t);return t instanceof e?t:Xr(t)}function Xl(t,e){const n=Math.abs(t.x),r=Math.abs(t.y);return typeof e=="number"?Math.sqrt(n**2+r**2)>e:"x"in e&&"y"in e?n>e.x&&r>e.y:"x"in e?n>e.x:"y"in e?r>e.y:!1}var dn;(function(t){t.Click="click",t.DragStart="dragstart",t.Keydown="keydown",t.ContextMenu="contextmenu",t.Resize="resize",t.SelectionChange="selectionchange",t.VisibilityChange="visibilitychange"})(dn||(dn={}));function kh(t){t.preventDefault()}function TC(t){t.stopPropagation()}var Xe;(function(t){t.Space="Space",t.Down="ArrowDown",t.Right="ArrowRight",t.Left="ArrowLeft",t.Up="ArrowUp",t.Esc="Escape",t.Enter="Enter",t.Tab="Tab"})(Xe||(Xe={}));const Ch={start:[Xe.Space,Xe.Enter],cancel:[Xe.Esc],end:[Xe.Space,Xe.Enter,Xe.Tab]},AC=(t,e)=>{let{currentCoordinates:n}=e;switch(t.code){case Xe.Right:return{...n,x:n.x+25};case Xe.Left:return{...n,x:n.x-25};case Xe.Down:return{...n,y:n.y+25};case Xe.Up:return{...n,y:n.y-25}}};class Ks{constructor(e){this.props=void 0,this.autoScrollEnabled=!1,this.referenceCoordinates=void 0,this.listeners=void 0,this.windowListeners=void 0,this.props=e;const{event:{target:n}}=e;this.props=e,this.listeners=new Ys(Xr(n)),this.windowListeners=new Ys(Xt(n)),this.handleKeyDown=this.handleKeyDown.bind(this),this.handleCancel=this.handleCancel.bind(this),this.attach()}attach(){this.handleStart(),this.windowListeners.add(dn.Resize,this.handleCancel),this.windowListeners.add(dn.VisibilityChange,this.handleCancel),setTimeout(()=>this.listeners.add(dn.Keydown,this.handleKeyDown))}handleStart(){const{activeNode:e,onStart:n}=this.props,r=e.node.current;r&&wh(r),n(kn)}handleKeyDown(e){if(wi(e)){const{active:n,context:r,options:s}=this.props,{keyboardCodes:o=Ch,coordinateGetter:i=AC,scrollBehavior:a="smooth"}=s,{code:l}=e;if(o.end.includes(l)){this.handleEnd(e);return}if(o.cancel.includes(l)){this.handleCancel(e);return}const{collisionRect:c}=r.current,d=c?{x:c.left,y:c.top}:kn;this.referenceCoordinates||(this.referenceCoordinates=d);const u=i(e,{active:n,context:r.current,currentCoordinates:d});if(u){const f=Gs(u,d),g={x:0,y:0},{scrollableAncestors:m}=r.current;for(const p of m){const y=e.code,{isTop:b,isRight:_,isLeft:x,isBottom:E,maxScroll:C,minScroll:k}=vh(p),N=_C(p),S={x:Math.min(y===Xe.Right?N.right-N.width/2:N.right,Math.max(y===Xe.Right?N.left:N.left+N.width/2,u.x)),y:Math.min(y===Xe.Down?N.bottom-N.height/2:N.bottom,Math.max(y===Xe.Down?N.top:N.top+N.height/2,u.y))},w=y===Xe.Right&&!_||y===Xe.Left&&!x,M=y===Xe.Down&&!E||y===Xe.Up&&!b;if(w&&S.x!==u.x){const T=p.scrollLeft+f.x,P=y===Xe.Right&&T<=C.x||y===Xe.Left&&T>=k.x;if(P&&!f.y){p.scrollTo({left:T,behavior:a});return}P?g.x=p.scrollLeft-T:g.x=y===Xe.Right?p.scrollLeft-C.x:p.scrollLeft-k.x,g.x&&p.scrollBy({left:-g.x,behavior:a});break}else if(M&&S.y!==u.y){const T=p.scrollTop+f.y,P=y===Xe.Down&&T<=C.y||y===Xe.Up&&T>=k.y;if(P&&!f.x){p.scrollTo({top:T,behavior:a});return}P?g.y=p.scrollTop-T:g.y=y===Xe.Down?p.scrollTop-C.y:p.scrollTop-k.y,g.y&&p.scrollBy({top:-g.y,behavior:a});break}}this.handleMove(e,qr(Gs(u,this.referenceCoordinates),g))}}}handleMove(e,n){const{onMove:r}=this.props;e.preventDefault(),r(n)}handleEnd(e){const{onEnd:n}=this.props;e.preventDefault(),this.detach(),n()}handleCancel(e){const{onCancel:n}=this.props;e.preventDefault(),this.detach(),n()}detach(){this.listeners.removeAll(),this.windowListeners.removeAll()}}Ks.activators=[{eventName:"onKeyDown",handler:(t,e,n)=>{let{keyboardCodes:r=Ch,onActivation:s}=e,{active:o}=n;const{code:i}=t.nativeEvent;if(r.start.includes(i)){const a=o.activatorNode.current;return a&&t.target!==a?!1:(t.preventDefault(),s?.({event:t.nativeEvent}),!0)}return!1}}];function Sh(t){return!!(t&&"distance"in t)}function _h(t){return!!(t&&"delay"in t)}class ql{constructor(e,n,r){var s;r===void 0&&(r=OC(e.event.target)),this.props=void 0,this.events=void 0,this.autoScrollEnabled=!0,this.document=void 0,this.activated=!1,this.initialCoordinates=void 0,this.timeoutId=null,this.listeners=void 0,this.documentListeners=void 0,this.windowListeners=void 0,this.props=e,this.events=n;const{event:o}=e,{target:i}=o;this.props=e,this.events=n,this.document=Xr(i),this.documentListeners=new Ys(this.document),this.listeners=new Ys(r),this.windowListeners=new Ys(Xt(i)),this.initialCoordinates=(s=ki(o))!=null?s:kn,this.handleStart=this.handleStart.bind(this),this.handleMove=this.handleMove.bind(this),this.handleEnd=this.handleEnd.bind(this),this.handleCancel=this.handleCancel.bind(this),this.handleKeydown=this.handleKeydown.bind(this),this.removeTextSelection=this.removeTextSelection.bind(this),this.attach()}attach(){const{events:e,props:{options:{activationConstraint:n,bypassActivationConstraint:r}}}=this;if(this.listeners.add(e.move.name,this.handleMove,{passive:!1}),this.listeners.add(e.end.name,this.handleEnd),e.cancel&&this.listeners.add(e.cancel.name,this.handleCancel),this.windowListeners.add(dn.Resize,this.handleCancel),this.windowListeners.add(dn.DragStart,kh),this.windowListeners.add(dn.VisibilityChange,this.handleCancel),this.windowListeners.add(dn.ContextMenu,kh),this.documentListeners.add(dn.Keydown,this.handleKeydown),n){if(r!=null&&r({event:this.props.event,activeNode:this.props.activeNode,options:this.props.options}))return this.handleStart();if(_h(n)){this.timeoutId=setTimeout(this.handleStart,n.delay),this.handlePending(n);return}if(Sh(n)){this.handlePending(n);return}}this.handleStart()}detach(){this.listeners.removeAll(),this.windowListeners.removeAll(),setTimeout(this.documentListeners.removeAll,50),this.timeoutId!==null&&(clearTimeout(this.timeoutId),this.timeoutId=null)}handlePending(e,n){const{active:r,onPending:s}=this.props;s(r,e,this.initialCoordinates,n)}handleStart(){const{initialCoordinates:e}=this,{onStart:n}=this.props;e&&(this.activated=!0,this.documentListeners.add(dn.Click,TC,{capture:!0}),this.removeTextSelection(),this.documentListeners.add(dn.SelectionChange,this.removeTextSelection),n(e))}handleMove(e){var n;const{activated:r,initialCoordinates:s,props:o}=this,{onMove:i,options:{activationConstraint:a}}=o;if(!s)return;const l=(n=ki(e))!=null?n:kn,c=Gs(s,l);if(!r&&a){if(Sh(a)){if(a.tolerance!=null&&Xl(c,a.tolerance))return this.handleCancel();if(Xl(c,a.distance))return this.handleStart()}if(_h(a)&&Xl(c,a.tolerance))return this.handleCancel();this.handlePending(a,c);return}e.cancelable&&e.preventDefault(),i(l)}handleEnd(){const{onAbort:e,onEnd:n}=this.props;this.detach(),this.activated||e(this.props.active),n()}handleCancel(){const{onAbort:e,onCancel:n}=this.props;this.detach(),this.activated||e(this.props.active),n()}handleKeydown(e){e.code===Xe.Esc&&this.handleCancel()}removeTextSelection(){var e;(e=this.document.getSelection())==null||e.removeAllRanges()}}const DC={cancel:{name:"pointercancel"},move:{name:"pointermove"},end:{name:"pointerup"}};class Xs extends ql{constructor(e){const{event:n}=e,r=Xr(n.target);super(e,DC,r)}}Xs.activators=[{eventName:"onPointerDown",handler:(t,e)=>{let{nativeEvent:n}=t,{onActivation:r}=e;return!n.isPrimary||n.button!==0?!1:(r?.({event:n}),!0)}}];const IC={move:{name:"mousemove"},end:{name:"mouseup"}};var Zl;(function(t){t[t.RightClick=2]="RightClick"})(Zl||(Zl={}));class LC extends ql{constructor(e){super(e,IC,Xr(e.event.target))}}LC.activators=[{eventName:"onMouseDown",handler:(t,e)=>{let{nativeEvent:n}=t,{onActivation:r}=e;return n.button===Zl.RightClick?!1:(r?.({event:n}),!0)}}];const Ql={cancel:{name:"touchcancel"},move:{name:"touchmove"},end:{name:"touchend"}};class PC extends ql{constructor(e){super(e,Ql)}static setup(){return window.addEventListener(Ql.move.name,e,{capture:!1,passive:!1}),function(){window.removeEventListener(Ql.move.name,e)};function e(){}}}PC.activators=[{eventName:"onTouchStart",handler:(t,e)=>{let{nativeEvent:n}=t,{onActivation:r}=e;const{touches:s}=n;return s.length>1?!1:(r?.({event:n}),!0)}}];var qs;(function(t){t[t.Pointer=0]="Pointer",t[t.DraggableRect=1]="DraggableRect"})(qs||(qs={}));var _i;(function(t){t[t.TreeOrder=0]="TreeOrder",t[t.ReversedTreeOrder=1]="ReversedTreeOrder"})(_i||(_i={}));function $C(t){let{acceleration:e,activator:n=qs.Pointer,canScroll:r,draggingRect:s,enabled:o,interval:i=5,order:a=_i.TreeOrder,pointerCoordinates:l,scrollableAncestors:c,scrollableAncestorRects:d,delta:u,threshold:f}=t;const g=zC({delta:u,disabled:!o}),[m,p]=Qk(),y=v.useRef({x:0,y:0}),b=v.useRef({x:0,y:0}),_=v.useMemo(()=>{switch(n){case qs.Pointer:return l?{top:l.y,bottom:l.y,left:l.x,right:l.x}:null;case qs.DraggableRect:return s}},[n,s,l]),x=v.useRef(null),E=v.useCallback(()=>{const k=x.current;if(!k)return;const N=y.current.x*b.current.x,S=y.current.y*b.current.y;k.scrollBy(N,S)},[]),C=v.useMemo(()=>a===_i.TreeOrder?[...c].reverse():c,[a,c]);v.useEffect(()=>{if(!o||!c.length||!_){p();return}for(const k of C){if(r?.(k)===!1)continue;const N=c.indexOf(k),S=d[N];if(!S)continue;const{direction:w,speed:M}=SC(k,S,_,e,f);for(const T of["x","y"])g[T][w[T]]||(M[T]=0,w[T]=0);if(M.x>0||M.y>0){p(),x.current=k,m(E,i),y.current=M,b.current=w;return}}y.current={x:0,y:0},b.current={x:0,y:0},p()},[e,E,r,p,o,i,JSON.stringify(_),JSON.stringify(g),m,c,C,d,JSON.stringify(f)])}const FC={x:{[Ot.Backward]:!1,[Ot.Forward]:!1},y:{[Ot.Backward]:!1,[Ot.Forward]:!1}};function zC(t){let{delta:e,disabled:n}=t;const r=xi(e);return js(s=>{if(n||!r||!s)return FC;const o={x:Math.sign(e.x-r.x),y:Math.sign(e.y-r.y)};return{x:{[Ot.Backward]:s.x[Ot.Backward]||o.x===-1,[Ot.Forward]:s.x[Ot.Forward]||o.x===1},y:{[Ot.Backward]:s.y[Ot.Backward]||o.y===-1,[Ot.Forward]:s.y[Ot.Forward]||o.y===1}}},[n,e,r])}function VC(t,e){const n=e!=null?t.get(e):void 0,r=n?n.node.current:null;return js(s=>{var o;return e==null?null:(o=r??s)!=null?o:null},[r,e])}function HC(t,e){return v.useMemo(()=>t.reduce((n,r)=>{const{sensor:s}=r,o=s.activators.map(i=>({eventName:i.eventName,handler:e(i.handler,r)}));return[...n,...o]},[]),[t,e])}var Zs;(function(t){t[t.Always=0]="Always",t[t.BeforeDragging=1]="BeforeDragging",t[t.WhileDragging=2]="WhileDragging"})(Zs||(Zs={}));var Jl;(function(t){t.Optimized="optimized"})(Jl||(Jl={}));const Eh=new Map;function UC(t,e){let{dragging:n,dependencies:r,config:s}=e;const[o,i]=v.useState(null),{frequency:a,measure:l,strategy:c}=s,d=v.useRef(t),u=y(),f=Bs(u),g=v.useCallback(function(b){b===void 0&&(b=[]),!f.current&&i(_=>_===null?b:_.concat(b.filter(x=>!_.includes(x))))},[f]),m=v.useRef(null),p=js(b=>{if(u&&!n)return Eh;if(!b||b===Eh||d.current!==t||o!=null){const _=new Map;for(let x of t){if(!x)continue;if(o&&o.length>0&&!o.includes(x.id)&&x.rect.current){_.set(x.id,x.rect.current);continue}const E=x.node.current,C=E?new Kl(l(E),E):null;x.rect.current=C,C&&_.set(x.id,C)}return _}return b},[t,o,n,u,l]);return v.useEffect(()=>{d.current=t},[t]),v.useEffect(()=>{u||g()},[n,u]),v.useEffect(()=>{o&&o.length>0&&i(null)},[JSON.stringify(o)]),v.useEffect(()=>{u||typeof a!="number"||m.current!==null||(m.current=setTimeout(()=>{g(),m.current=null},a))},[a,u,g,...r]),{droppableRects:p,measureDroppableContainers:g,measuringScheduled:o!=null};function y(){switch(c){case Zs.Always:return!1;case Zs.BeforeDragging:return n;default:return!n}}}function Rl(t,e){return js(n=>t?n||(typeof e=="function"?e(t):t):null,[e,t])}function BC(t,e){return Rl(t,e)}function jC(t){let{callback:e,disabled:n}=t;const r=yi(e),s=v.useMemo(()=>{if(n||typeof window>"u"||typeof window.MutationObserver>"u")return;const{MutationObserver:o}=window;return new o(r)},[r,n]);return v.useEffect(()=>()=>s?.disconnect(),[s]),s}function Ei(t){let{callback:e,disabled:n}=t;const r=yi(e),s=v.useMemo(()=>{if(n||typeof window>"u"||typeof window.ResizeObserver>"u")return;const{ResizeObserver:o}=window;return new o(r)},[n]);return v.useEffect(()=>()=>s?.disconnect(),[s]),s}function WC(t){return new Kl(Qr(t),t)}function Mh(t,e,n){e===void 0&&(e=WC);const[r,s]=v.useState(null);function o(){s(l=>{if(!t)return null;if(t.isConnected===!1){var c;return(c=l??n)!=null?c:null}const d=e(t);return JSON.stringify(l)===JSON.stringify(d)?l:d})}const i=jC({callback(l){if(t)for(const c of l){const{type:d,target:u}=c;if(d==="childList"&&u instanceof HTMLElement&&u.contains(t)){o();break}}}}),a=Ei({callback:o});return wn(()=>{o(),t?(a?.observe(t),i?.observe(document.body,{childList:!0,subtree:!0})):(a?.disconnect(),i?.disconnect())},[t]),r}function GC(t){const e=Rl(t);return fh(t,e)}const Nh=[];function YC(t){const e=v.useRef(t),n=js(r=>t?r&&r!==Nh&&t&&e.current&&t.parentNode===e.current.parentNode?r:Si(t):Nh,[t]);return v.useEffect(()=>{e.current=t},[t]),n}function KC(t){const[e,n]=v.useState(null),r=v.useRef(t),s=v.useCallback(o=>{const i=Gl(o.target);i&&n(a=>a?(a.set(i,Yl(i)),new Map(a)):null)},[]);return v.useEffect(()=>{const o=r.current;if(t!==o){i(o);const a=t.map(l=>{const c=Gl(l);return c?(c.addEventListener("scroll",s,{passive:!0}),[c,Yl(c)]):null}).filter(l=>l!=null);n(a.length?new Map(a):null),r.current=t}return()=>{i(t),i(o)};function i(a){a.forEach(l=>{const c=Gl(l);c?.removeEventListener("scroll",s)})}},[s,t]),v.useMemo(()=>t.length?e?Array.from(e.values()).reduce((o,i)=>qr(o,i),kn):xh(t):kn,[t,e])}function Oh(t,e){e===void 0&&(e=[]);const n=v.useRef(null);return v.useEffect(()=>{n.current=null},e),v.useEffect(()=>{const r=t!==kn;r&&!n.current&&(n.current=t),!r&&n.current&&(n.current=null)},[t]),n.current?Gs(t,n.current):kn}function XC(t){v.useEffect(()=>{if(!bi)return;const e=t.map(n=>{let{sensor:r}=n;return r.setup==null?void 0:r.setup()});return()=>{for(const n of e)n?.()}},t.map(e=>{let{sensor:n}=e;return n}))}function qC(t,e){return v.useMemo(()=>t.reduce((n,r)=>{let{eventName:s,handler:o}=r;return n[s]=i=>{o(i,e)},n},{}),[t,e])}function Th(t){return v.useMemo(()=>t?xC(t):null,[t])}const Ah=[];function ZC(t,e){e===void 0&&(e=Qr);const[n]=t,r=Th(n?Xt(n):null),[s,o]=v.useState(Ah);function i(){o(()=>t.length?t.map(l=>yh(l)?r:new Kl(e(l),l)):Ah)}const a=Ei({callback:i});return wn(()=>{a?.disconnect(),i(),t.forEach(l=>a?.observe(l))},[t]),s}function Dh(t){if(!t)return null;if(t.children.length>1)return t;const e=t.children[0];return Us(e)?e:t}function QC(t){let{measure:e}=t;const[n,r]=v.useState(null),s=v.useCallback(c=>{for(const{target:d}of c)if(Us(d)){r(u=>{const f=e(d);return u?{...u,width:f.width,height:f.height}:f});break}},[e]),o=Ei({callback:s}),i=v.useCallback(c=>{const d=Dh(c);o?.disconnect(),d&&o?.observe(d),r(d?e(d):null)},[e,o]),[a,l]=vi(i);return v.useMemo(()=>({nodeRef:a,rect:n,setRef:l}),[n,a,l])}const JC=[{sensor:Xs,options:{}},{sensor:Ks,options:{}}],RC={current:{}},Mi={draggable:{measure:gh},droppable:{measure:gh,strategy:Zs.WhileDragging,frequency:Jl.Optimized},dragOverlay:{measure:Qr}};class Qs extends Map{get(e){var n;return e!=null&&(n=super.get(e))!=null?n:void 0}toArray(){return Array.from(this.values())}getEnabled(){return this.toArray().filter(e=>{let{disabled:n}=e;return!n})}getNodeFor(e){var n,r;return(n=(r=this.get(e))==null?void 0:r.node.current)!=null?n:void 0}}const eS={activatorEvent:null,active:null,activeNode:null,activeNodeRect:null,collisions:null,containerNodeRect:null,draggableNodes:new Map,droppableRects:new Map,droppableContainers:new Qs,over:null,dragOverlay:{nodeRef:{current:null},rect:null,setRef:Ci},scrollableAncestors:[],scrollableAncestorRects:[],measuringConfiguration:Mi,measureDroppableContainers:Ci,windowRect:null,measuringScheduled:!1},Ih={activatorEvent:null,activators:[],active:null,activeNodeRect:null,ariaDescribedById:{draggable:""},dispatch:Ci,draggableNodes:new Map,over:null,measureDroppableContainers:Ci},Js=v.createContext(Ih),Lh=v.createContext(eS);function tS(){return{draggable:{active:null,initialCoordinates:{x:0,y:0},nodes:new Map,translate:{x:0,y:0}},droppable:{containers:new Qs}}}function nS(t,e){switch(e.type){case _t.DragStart:return{...t,draggable:{...t.draggable,initialCoordinates:e.initialCoordinates,active:e.active}};case _t.DragMove:return t.draggable.active==null?t:{...t,draggable:{...t.draggable,translate:{x:e.coordinates.x-t.draggable.initialCoordinates.x,y:e.coordinates.y-t.draggable.initialCoordinates.y}}};case _t.DragEnd:case _t.DragCancel:return{...t,draggable:{...t.draggable,active:null,initialCoordinates:{x:0,y:0},translate:{x:0,y:0}}};case _t.RegisterDroppable:{const{element:n}=e,{id:r}=n,s=new Qs(t.droppable.containers);return s.set(r,n),{...t,droppable:{...t.droppable,containers:s}}}case _t.SetDroppableDisabled:{const{id:n,key:r,disabled:s}=e,o=t.droppable.containers.get(n);if(!o||r!==o.key)return t;const i=new Qs(t.droppable.containers);return i.set(n,{...o,disabled:s}),{...t,droppable:{...t.droppable,containers:i}}}case _t.UnregisterDroppable:{const{id:n,key:r}=e,s=t.droppable.containers.get(n);if(!s||r!==s.key)return t;const o=new Qs(t.droppable.containers);return o.delete(n),{...t,droppable:{...t.droppable,containers:o}}}default:return t}}function rS(t){let{disabled:e}=t;const{active:n,activatorEvent:r,draggableNodes:s}=v.useContext(Js),o=xi(r),i=xi(n?.id);return v.useEffect(()=>{if(!e&&!r&&o&&i!=null){if(!wi(o)||document.activeElement===o.target)return;const a=s.get(i);if(!a)return;const{activatorNode:l,node:c}=a;if(!l.current&&!c.current)return;requestAnimationFrame(()=>{for(const d of[l.current,c.current]){if(!d)continue;const u=eC(d);if(u){u.focus();break}}})}},[r,e,s,i,o]),null}function Ph(t,e){let{transform:n,...r}=e;return t!=null&&t.length?t.reduce((s,o)=>o({transform:s,...r}),n):n}function sS(t){return v.useMemo(()=>({draggable:{...Mi.draggable,...t?.draggable},droppable:{...Mi.droppable,...t?.droppable},dragOverlay:{...Mi.dragOverlay,...t?.dragOverlay}}),[t?.draggable,t?.droppable,t?.dragOverlay])}function oS(t){let{activeNode:e,measure:n,initialRect:r,config:s=!0}=t;const o=v.useRef(!1),{x:i,y:a}=typeof s=="boolean"?{x:s,y:s}:s;wn(()=>{if(!i&&!a||!e){o.current=!1;return}if(o.current||!r)return;const c=e?.node.current;if(!c||c.isConnected===!1)return;const d=n(c),u=fh(d,r);if(i||(u.x=0),a||(u.y=0),o.current=!0,Math.abs(u.x)>0||Math.abs(u.y)>0){const f=ph(c);f&&f.scrollBy({top:u.y,left:u.x})}},[e,i,a,r,n])}const Ni=v.createContext({...kn,scaleX:1,scaleY:1});var nr;(function(t){t[t.Uninitialized=0]="Uninitialized",t[t.Initializing=1]="Initializing",t[t.Initialized=2]="Initialized"})(nr||(nr={}));const ec=v.memo(function(e){var n,r,s,o;let{id:i,accessibility:a,autoScroll:l=!0,children:c,sensors:d=JC,collisionDetection:u=gC,measuring:f,modifiers:g,...m}=e;const p=v.useReducer(nS,void 0,tS),[y,b]=p,[_,x]=iC(),[E,C]=v.useState(nr.Uninitialized),k=E===nr.Initialized,{draggable:{active:N,nodes:S,translate:w},droppable:{containers:M}}=y,T=N!=null?S.get(N):null,P=v.useRef({initial:null,translated:null}),L=v.useMemo(()=>{var Pe;return N!=null?{id:N,data:(Pe=T?.data)!=null?Pe:RC,rect:P}:null},[N,T]),$=v.useRef(null),[A,D]=v.useState(null),[O,z]=v.useState(null),H=Bs(m,Object.values(m)),j=Ws("DndDescribedBy",i),W=v.useMemo(()=>M.getEnabled(),[M]),U=sS(f),{droppableRects:B,measureDroppableContainers:G,measuringScheduled:V}=UC(W,{dragging:k,dependencies:[w.x,w.y],config:U.droppable}),X=VC(S,N),K=v.useMemo(()=>O?ki(O):null,[O]),Z=ot(),ee=BC(X,U.draggable.measure);oS({activeNode:N!=null?S.get(N):null,config:Z.layoutShiftCompensation,initialRect:ee,measure:U.draggable.measure});const ne=Mh(X,U.draggable.measure,ee),he=Mh(X?X.parentElement:null),J=v.useRef({activatorEvent:null,active:null,activeNode:X,collisionRect:null,collisions:null,droppableRects:B,draggableNodes:S,draggingNode:null,draggingNodeRect:null,droppableContainers:M,over:null,scrollableAncestors:[],scrollAdjustedTranslate:null}),le=M.getNodeFor((n=J.current.over)==null?void 0:n.id),ue=QC({measure:U.dragOverlay.measure}),ae=(r=ue.nodeRef.current)!=null?r:X,ve=k?(s=ue.rect)!=null?s:ne:null,fe=!!(ue.nodeRef.current&&ue.rect),ye=GC(fe?null:ne),we=Th(ae?Xt(ae):null),He=YC(k?le??X:null),se=ZC(He),Oe=Ph(g,{transform:{x:w.x-ye.x,y:w.y-ye.y,scaleX:1,scaleY:1},activatorEvent:O,active:L,activeNodeRect:ne,containerNodeRect:he,draggingNodeRect:ve,over:J.current.over,overlayNodeRect:ue.rect,scrollableAncestors:He,scrollableAncestorRects:se,windowRect:we}),Le=K?qr(K,w):null,Ie=KC(He),Be=Oh(Ie),st=Oh(Ie,[ne]),ut=qr(Oe,Be),je=ve?bC(ve,Oe):null,nt=L&&je?u({active:L,collisionRect:je,droppableRects:B,droppableContainers:W,pointerCoordinates:Le}):null,Ye=dh(nt,"id"),[te,ge]=v.useState(null),me=fe?Oe:qr(Oe,st),pe=pC(me,(o=te?.rect)!=null?o:null,ne),be=v.useRef(null),Ue=v.useCallback((Pe,Te)=>{let{sensor:Ve,options:De}=Te;if($.current==null)return;const qe=S.get($.current);if(!qe)return;const it=Pe.nativeEvent,pt=new Ve({active:$.current,activeNode:qe,event:it,options:De,context:J,onAbort(bt){if(!S.get(bt))return;const{onDragAbort:jt}=H.current,ft={id:bt};jt?.(ft),_({type:"onDragAbort",event:ft})},onPending(bt,wt,jt,ft){if(!S.get(bt))return;const{onDragPending:Nn}=H.current,Wt={id:bt,constraint:wt,initialCoordinates:jt,offset:ft};Nn?.(Wt),_({type:"onDragPending",event:Wt})},onStart(bt){const wt=$.current;if(wt==null)return;const jt=S.get(wt);if(!jt)return;const{onDragStart:ft}=H.current,Ke={activatorEvent:it,active:{id:wt,data:jt.data,rect:P}};Tn.unstable_batchedUpdates(()=>{ft?.(Ke),C(nr.Initializing),b({type:_t.DragStart,initialCoordinates:bt,active:wt}),_({type:"onDragStart",event:Ke}),D(be.current),z(it)})},onMove(bt){b({type:_t.DragMove,coordinates:bt})},onEnd:rn(_t.DragEnd),onCancel:rn(_t.DragCancel)});be.current=pt;function rn(bt){return async function(){const{active:jt,collisions:ft,over:Ke,scrollAdjustedTranslate:Nn}=J.current;let Wt=null;if(jt&&Nn){const{cancelDrop:bn}=H.current;Wt={activatorEvent:it,active:jt,collisions:ft,delta:Nn,over:Ke},bt===_t.DragEnd&&typeof bn=="function"&&await Promise.resolve(bn(Wt))&&(bt=_t.DragCancel)}$.current=null,Tn.unstable_batchedUpdates(()=>{b({type:bt}),C(nr.Uninitialized),ge(null),D(null),z(null),be.current=null;const bn=bt===_t.DragEnd?"onDragEnd":"onDragCancel";if(Wt){const hr=H.current[bn];hr?.(Wt),_({type:bn,event:Wt})}})}}},[S]),Ce=v.useCallback((Pe,Te)=>(Ve,De)=>{const qe=Ve.nativeEvent,it=S.get(De);if($.current!==null||!it||qe.dndKit||qe.defaultPrevented)return;const pt={active:it};Pe(Ve,Te.options,pt)===!0&&(qe.dndKit={capturedBy:Te.sensor},$.current=De,Ue(Ve,Te))},[S,Ue]),ze=HC(d,Ce);XC(d),wn(()=>{ne&&E===nr.Initializing&&C(nr.Initialized)},[ne,E]),v.useEffect(()=>{const{onDragMove:Pe}=H.current,{active:Te,activatorEvent:Ve,collisions:De,over:qe}=J.current;if(!Te||!Ve)return;const it={active:Te,activatorEvent:Ve,collisions:De,delta:{x:ut.x,y:ut.y},over:qe};Tn.unstable_batchedUpdates(()=>{Pe?.(it),_({type:"onDragMove",event:it})})},[ut.x,ut.y]),v.useEffect(()=>{const{active:Pe,activatorEvent:Te,collisions:Ve,droppableContainers:De,scrollAdjustedTranslate:qe}=J.current;if(!Pe||$.current==null||!Te||!qe)return;const{onDragOver:it}=H.current,pt=De.get(Ye),rn=pt&&pt.rect.current?{id:pt.id,rect:pt.rect.current,data:pt.data,disabled:pt.disabled}:null,bt={active:Pe,activatorEvent:Te,collisions:Ve,delta:{x:qe.x,y:qe.y},over:rn};Tn.unstable_batchedUpdates(()=>{ge(rn),it?.(bt),_({type:"onDragOver",event:bt})})},[Ye]),wn(()=>{J.current={activatorEvent:O,active:L,activeNode:X,collisionRect:je,collisions:nt,droppableRects:B,draggableNodes:S,draggingNode:ae,draggingNodeRect:ve,droppableContainers:M,over:te,scrollableAncestors:He,scrollAdjustedTranslate:ut},P.current={initial:ve,translated:je}},[L,X,nt,je,S,ae,ve,B,M,te,He,ut]),$C({...Z,delta:w,draggingRect:je,pointerCoordinates:Le,scrollableAncestors:He,scrollableAncestorRects:se});const Se=v.useMemo(()=>({active:L,activeNode:X,activeNodeRect:ne,activatorEvent:O,collisions:nt,containerNodeRect:he,dragOverlay:ue,draggableNodes:S,droppableContainers:M,droppableRects:B,over:te,measureDroppableContainers:G,scrollableAncestors:He,scrollableAncestorRects:se,measuringConfiguration:U,measuringScheduled:V,windowRect:we}),[L,X,ne,O,nt,he,ue,S,M,B,te,G,He,se,U,V,we]),Re=v.useMemo(()=>({activatorEvent:O,activators:ze,active:L,activeNodeRect:ne,ariaDescribedById:{draggable:j},dispatch:b,draggableNodes:S,over:te,measureDroppableContainers:G}),[O,ze,L,ne,b,j,S,te,G]);return v.createElement(ih.Provider,{value:x},v.createElement(Js.Provider,{value:Re},v.createElement(Lh.Provider,{value:Se},v.createElement(Ni.Provider,{value:pe},c)),v.createElement(rS,{disabled:a?.restoreFocus===!1})),v.createElement(cC,{...a,hiddenTextDescribedById:j}));function ot(){const Pe=A?.autoScrollEnabled===!1,Te=typeof l=="object"?l.enabled===!1:l===!1,Ve=k&&!Pe&&!Te;return typeof l=="object"?{...l,enabled:Ve}:{enabled:Ve}}}),iS=v.createContext(null),$h="button",aS="Draggable";function lS(t){let{id:e,data:n,disabled:r=!1,attributes:s}=t;const o=Ws(aS),{activators:i,activatorEvent:a,active:l,activeNodeRect:c,ariaDescribedById:d,draggableNodes:u,over:f}=v.useContext(Js),{role:g=$h,roleDescription:m="draggable",tabIndex:p=0}=s??{},y=l?.id===e,b=v.useContext(y?Ni:iS),[_,x]=vi(),[E,C]=vi(),k=qC(i,e),N=Bs(n);wn(()=>(u.set(e,{id:e,key:o,node:_,activatorNode:E,data:N}),()=>{const w=u.get(e);w&&w.key===o&&u.delete(e)}),[u,e]);const S=v.useMemo(()=>({role:g,tabIndex:p,"aria-disabled":r,"aria-pressed":y&&g===$h?!0:void 0,"aria-roledescription":m,"aria-describedby":d.draggable}),[r,g,p,y,m,d.draggable]);return{active:l,activatorEvent:a,activeNodeRect:c,attributes:S,isDragging:y,listeners:r?void 0:k,node:_,over:f,setNodeRef:x,setActivatorNodeRef:C,transform:b}}function Fh(){return v.useContext(Lh)}const cS="Droppable",dS={timeout:25};function uS(t){let{data:e,disabled:n=!1,id:r,resizeObserverConfig:s}=t;const o=Ws(cS),{active:i,dispatch:a,over:l,measureDroppableContainers:c}=v.useContext(Js),d=v.useRef({disabled:n}),u=v.useRef(!1),f=v.useRef(null),g=v.useRef(null),{disabled:m,updateMeasurementsFor:p,timeout:y}={...dS,...s},b=Bs(p??r),_=v.useCallback(()=>{if(!u.current){u.current=!0;return}g.current!=null&&clearTimeout(g.current),g.current=setTimeout(()=>{c(Array.isArray(b.current)?b.current:[b.current]),g.current=null},y)},[y]),x=Ei({callback:_,disabled:m||!i}),E=v.useCallback((S,w)=>{x&&(w&&(x.unobserve(w),u.current=!1),S&&x.observe(S))},[x]),[C,k]=vi(E),N=Bs(e);return v.useEffect(()=>{!x||!C.current||(x.disconnect(),u.current=!1,x.observe(C.current))},[C,x]),v.useEffect(()=>(a({type:_t.RegisterDroppable,element:{id:r,key:o,disabled:n,node:C,rect:f,data:N}}),()=>a({type:_t.UnregisterDroppable,key:o,id:r})),[r]),v.useEffect(()=>{n!==d.current.disabled&&(a({type:_t.SetDroppableDisabled,id:r,key:o,disabled:n}),d.current.disabled=n)},[r,o,n,a]),{active:i,rect:f,isOver:l?.id===r,node:C,over:l,setNodeRef:k}}function fS(t){let{animation:e,children:n}=t;const[r,s]=v.useState(null),[o,i]=v.useState(null),a=xi(n);return!n&&!r&&a&&s(a),wn(()=>{if(!o)return;const l=r?.key,c=r?.props.id;if(l==null||c==null){s(null);return}Promise.resolve(e(c,o)).then(()=>{s(null)})},[e,r,o]),v.createElement(v.Fragment,null,n,r?v.cloneElement(r,{ref:i}):null)}const hS={x:0,y:0,scaleX:1,scaleY:1};function gS(t){let{children:e}=t;return v.createElement(Js.Provider,{value:Ih},v.createElement(Ni.Provider,{value:hS},e))}const pS={position:"fixed",touchAction:"none"},mS=t=>wi(t)?"transform 250ms ease":void 0,bS=v.forwardRef((t,e)=>{let{as:n,activatorEvent:r,adjustScale:s,children:o,className:i,rect:a,style:l,transform:c,transition:d=mS}=t;if(!a)return null;const u=s?c:{...c,scaleX:1,scaleY:1},f={...pS,width:a.width,height:a.height,top:a.top,left:a.left,transform:Pn.Transform.toString(u),transformOrigin:s&&r?dC(r,a):void 0,transition:typeof d=="function"?d(r):d,...l};return v.createElement(n,{className:i,style:f,ref:e},o)}),yS={duration:250,easing:"ease",keyframes:t=>{let{transform:{initial:e,final:n}}=t;return[{transform:Pn.Transform.toString(e)},{transform:Pn.Transform.toString(n)}]},sideEffects:(t=>e=>{let{active:n,dragOverlay:r}=e;const s={},{styles:o,className:i}=t;if(o!=null&&o.active)for(const[a,l]of Object.entries(o.active))l!==void 0&&(s[a]=n.node.style.getPropertyValue(a),n.node.style.setProperty(a,l));if(o!=null&&o.dragOverlay)for(const[a,l]of Object.entries(o.dragOverlay))l!==void 0&&r.node.style.setProperty(a,l);return i!=null&&i.active&&n.node.classList.add(i.active),i!=null&&i.dragOverlay&&r.node.classList.add(i.dragOverlay),function(){for(const[l,c]of Object.entries(s))n.node.style.setProperty(l,c);i!=null&&i.active&&n.node.classList.remove(i.active)}})({styles:{active:{opacity:"0"}}})};function vS(t){let{config:e,draggableNodes:n,droppableContainers:r,measuringConfiguration:s}=t;return yi((o,i)=>{if(e===null)return;const a=n.get(o);if(!a)return;const l=a.node.current;if(!l)return;const c=Dh(i);if(!c)return;const{transform:d}=Xt(i).getComputedStyle(i),u=hh(d);if(!u)return;const f=typeof e=="function"?e:xS(e);return wh(l,s.draggable.measure),f({active:{id:o,data:a.data,node:l,rect:s.draggable.measure(l)},draggableNodes:n,dragOverlay:{node:i,rect:s.dragOverlay.measure(c)},droppableContainers:r,measuringConfiguration:s,transform:u})})}function xS(t){const{duration:e,easing:n,sideEffects:r,keyframes:s}={...yS,...t};return o=>{let{active:i,dragOverlay:a,transform:l,...c}=o;if(!e)return;const d={x:a.rect.left-i.rect.left,y:a.rect.top-i.rect.top},u={scaleX:l.scaleX!==1?i.rect.width*l.scaleX/a.rect.width:1,scaleY:l.scaleY!==1?i.rect.height*l.scaleY/a.rect.height:1},f={x:l.x-d.x,y:l.y-d.y,...u},g=s({...c,active:i,dragOverlay:a,transform:{initial:l,final:f}}),[m]=g,p=g[g.length-1];if(JSON.stringify(m)===JSON.stringify(p))return;const y=r?.({active:i,dragOverlay:a,...c}),b=a.node.animate(g,{duration:e,easing:n,fill:"forwards"});return new Promise(_=>{b.onfinish=()=>{y?.(),_()}})}}let zh=0;function wS(t){return v.useMemo(()=>{if(t!=null)return zh++,zh},[t])}const kS=v.memo(t=>{let{adjustScale:e=!1,children:n,dropAnimation:r,style:s,transition:o,modifiers:i,wrapperElement:a="div",className:l,zIndex:c=999}=t;const{activatorEvent:d,active:u,activeNodeRect:f,containerNodeRect:g,draggableNodes:m,droppableContainers:p,dragOverlay:y,over:b,measuringConfiguration:_,scrollableAncestors:x,scrollableAncestorRects:E,windowRect:C}=Fh(),k=v.useContext(Ni),N=wS(u?.id),S=Ph(i,{activatorEvent:d,active:u,activeNodeRect:f,containerNodeRect:g,draggingNodeRect:y.rect,over:b,overlayNodeRect:y.rect,scrollableAncestors:x,scrollableAncestorRects:E,transform:k,windowRect:C}),w=Rl(f),M=vS({config:r,draggableNodes:m,droppableContainers:p,measuringConfiguration:_}),T=w?y.setRef:void 0;return v.createElement(gS,null,v.createElement(fS,{animation:M},u&&N?v.createElement(bS,{key:N,id:u.id,ref:T,as:a,activatorEvent:d,adjustScale:e,className:l,transition:o,rect:w,style:{zIndex:c,...s},transform:S},n):null))});function Jr(t,e,n){const r=t.slice();return r.splice(n<0?r.length+n:n,0,r.splice(e,1)[0]),r}function CS(t,e){return t.reduce((n,r,s)=>{const o=e.get(r);return o&&(n[s]=o),n},Array(t.length))}function Oi(t){return t!==null&&t>=0}function SS(t,e){if(t===e)return!0;if(t.length!==e.length)return!1;for(let n=0;n{let{rects:e,activeIndex:n,overIndex:r,index:s}=t;const o=Jr(e,r,n),i=e[s],a=o[s];return!a||!i?null:{x:a.left-i.left,y:a.top-i.top,scaleX:a.width/i.width,scaleY:a.height/i.height}},Ai={scaleX:1,scaleY:1},tc=t=>{var e;let{activeIndex:n,activeNodeRect:r,index:s,rects:o,overIndex:i}=t;const a=(e=o[n])!=null?e:r;if(!a)return null;if(s===n){const c=o[i];return c?{x:0,y:nn&&s<=i?{x:0,y:-a.height-l,...Ai}:s=i?{x:0,y:a.height+l,...Ai}:{x:0,y:0,...Ai}};function ES(t,e,n){const r=t[e],s=t[e-1],o=t[e+1];return r?nr.map(k=>typeof k=="object"&&"id"in k?k.id:k),[r]),m=i!=null,p=i?g.indexOf(i.id):-1,y=c?g.indexOf(c.id):-1,b=v.useRef(g),_=!SS(g,b.current),x=y!==-1&&p===-1||_,E=_S(o);wn(()=>{_&&m&&d(g)},[_,g,m,d]),v.useEffect(()=>{b.current=g},[g]);const C=v.useMemo(()=>({activeIndex:p,containerId:u,disabled:E,disableTransforms:x,items:g,overIndex:y,useDragOverlay:f,sortedRects:CS(g,l),strategy:s}),[p,u,E.draggable,E.droppable,x,g,y,l,f,s]);return v.createElement(Hh.Provider,{value:C},e)}const Uh=t=>{let{id:e,items:n,activeIndex:r,overIndex:s}=t;return Jr(n,r,s).indexOf(e)},Bh=t=>{let{containerId:e,isSorting:n,wasDragging:r,index:s,items:o,newIndex:i,previousItems:a,previousContainerId:l,transition:c}=t;return!c||!r||a!==o&&s===i?!1:n?!0:i!==s&&e===l},MS={duration:200,easing:"ease"},jh="transform",NS=Pn.Transition.toString({property:jh,duration:0,easing:"linear"}),OS={roleDescription:"sortable"};function TS(t){let{disabled:e,index:n,node:r,rect:s}=t;const[o,i]=v.useState(null),a=v.useRef(n);return wn(()=>{if(!e&&n!==a.current&&r.current){const l=s.current;if(l){const c=Qr(r.current,{ignoreTransform:!0}),d={x:l.left-c.left,y:l.top-c.top,scaleX:l.width/c.width,scaleY:l.height/c.height};(d.x||d.y)&&i(d)}}n!==a.current&&(a.current=n)},[e,n,r,s]),v.useEffect(()=>{o&&i(null)},[o]),o}function Ii(t){let{animateLayoutChanges:e=Bh,attributes:n,disabled:r,data:s,getNewIndex:o=Uh,id:i,strategy:a,resizeObserverConfig:l,transition:c=MS}=t;const{items:d,containerId:u,activeIndex:f,disabled:g,disableTransforms:m,sortedRects:p,overIndex:y,useDragOverlay:b,strategy:_}=v.useContext(Hh),x=AS(r,g),E=d.indexOf(i),C=v.useMemo(()=>({sortable:{containerId:u,index:E,items:d},...s}),[u,s,E,d]),k=v.useMemo(()=>d.slice(d.indexOf(i)),[d,i]),{rect:N,node:S,isOver:w,setNodeRef:M}=uS({id:i,data:C,disabled:x.droppable,resizeObserverConfig:{updateMeasurementsFor:k,...l}}),{active:T,activatorEvent:P,activeNodeRect:L,attributes:$,setNodeRef:A,listeners:D,isDragging:O,over:z,setActivatorNodeRef:H,transform:j}=lS({id:i,data:C,attributes:{...OS,...n},disabled:x.draggable}),W=Zk(M,A),U=!!T,B=U&&!m&&Oi(f)&&Oi(y),G=!b&&O,V=G&&B?j:null,K=B?V??(a??_)({rects:p,activeNodeRect:L,activeIndex:f,overIndex:y,index:E}):null,Z=Oi(f)&&Oi(y)?o({id:i,items:d,activeIndex:f,overIndex:y}):E,ee=T?.id,ne=v.useRef({activeId:ee,items:d,newIndex:Z,containerId:u}),he=d!==ne.current.items,J=e({active:T,containerId:u,isDragging:O,isSorting:U,id:i,index:E,items:d,newIndex:ne.current.newIndex,previousItems:ne.current.items,previousContainerId:ne.current.containerId,transition:c,wasDragging:ne.current.activeId!=null}),le=TS({disabled:!J,index:E,node:S,rect:N});return v.useEffect(()=>{U&&ne.current.newIndex!==Z&&(ne.current.newIndex=Z),u!==ne.current.containerId&&(ne.current.containerId=u),d!==ne.current.items&&(ne.current.items=d)},[U,Z,u,d]),v.useEffect(()=>{if(ee===ne.current.activeId)return;if(ee!=null&&ne.current.activeId==null){ne.current.activeId=ee;return}const ae=setTimeout(()=>{ne.current.activeId=ee},50);return()=>clearTimeout(ae)},[ee]),{active:T,activeIndex:f,attributes:$,data:C,rect:N,index:E,newIndex:Z,items:d,isOver:w,isSorting:U,isDragging:O,listeners:D,node:S,overIndex:y,over:z,setNodeRef:W,setActivatorNodeRef:H,setDroppableNodeRef:M,setDraggableNodeRef:A,transform:le??K,transition:ue()};function ue(){if(le||he&&ne.current.newIndex===E)return NS;if(!(G&&!wi(P)||!c)&&(U||J))return Pn.Transition.toString({...c,property:jh})}}function AS(t,e){var n,r;return typeof t=="boolean"?{draggable:t,droppable:!1}:{draggable:(n=t?.draggable)!=null?n:e.draggable,droppable:(r=t?.droppable)!=null?r:e.droppable}}function Rs(t){if(!t)return!1;const e=t.data.current;return!!(e&&"sortable"in e&&typeof e.sortable=="object"&&"containerId"in e.sortable&&"items"in e.sortable&&"index"in e.sortable)}const DS=[Xe.Down,Xe.Right,Xe.Up,Xe.Left],Li=(t,e)=>{let{context:{active:n,collisionRect:r,droppableRects:s,droppableContainers:o,over:i,scrollableAncestors:a}}=e;if(DS.includes(t.code)){if(t.preventDefault(),!n||!r)return;const l=[];o.getEnabled().forEach(u=>{if(!u||u!=null&&u.disabled)return;const f=s.get(u.id);if(f)switch(t.code){case Xe.Down:r.topf.top&&l.push(u);break;case Xe.Left:r.left>f.left&&l.push(u);break;case Xe.Right:r.left1&&(d=c[1].id),d!=null){const u=o.get(n.id),f=o.get(d),g=f?s.get(f.id):null,m=f?.node.current;if(m&&g&&u&&f){const y=Si(m).some((k,N)=>a[N]!==k),b=Wh(u,f),_=IS(u,f),x=y||!b?{x:0,y:0}:{x:_?r.width-g.width:0,y:_?r.height-g.height:0},E={x:g.left,y:g.top};return x.x&&x.y?E:Gs(E,x)}}}};function Wh(t,e){return!Rs(t)||!Rs(e)?!1:t.data.current.sortable.containerId===e.data.current.sortable.containerId}function IS(t,e){return!Rs(t)||!Rs(e)||!Wh(t,e)?!1:t.data.current.sortable.indexwindow.G7Core,Gh=({item:t,isSelected:e=!1,isExpanded:n=!1,level:r=0,onClick:s,onToggle:o,onExpandToggle:i,className:a="",selectedClassName:l="bg-blue-50 dark:bg-blue-900/30 border-blue-500",defaultClassName:c="bg-white dark:bg-gray-800 border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600",dragHandleClassName:d="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300",iconContainerClassName:u="bg-gray-100 dark:bg-gray-700",contentClassName:f="",enableDrag:g=!0,toggleDisabled:m=!1})=>{const{attributes:p,listeners:y,setNodeRef:b,transform:_,transition:x,isDragging:E}=Ii({id:t.id}),C={transform:Pn.Transform.toString(_),transition:x,opacity:E?.5:1},k=r*24+12,S=`flex items-center gap-2 cursor-pointer transition-colors ${e?l:c}`,w=PS()?.style?.mergeClasses?.(S,a)??`${S} ${a}`.trim();return h.jsxs(I,{ref:b,style:C,className:w,onClick:s,children:[g&&h.jsx(I,{...p,...y,className:`flex-shrink-0 cursor-grab active:cursor-grabbing p-1 ${d}`,style:{paddingLeft:`${k}px`},children:h.jsx(re,{name:"grip-vertical",className:"w-4 h-4"})}),!g&&r>0&&h.jsx(I,{style:{paddingLeft:`${k}px`}}),g&&h.jsx(h.Fragment,{children:t.hasChildren?h.jsx(oe,{type:"button",onClick:M=>{M.stopPropagation(),i?.()},className:`flex-shrink-0 p-1 ${d}`,children:h.jsx(re,{name:n?"chevron-down":"chevron-right",className:"w-4 h-4"})}):h.jsx(I,{className:"flex-shrink-0 w-6"})}),h.jsx(I,{className:`flex-shrink-0 w-8 h-8 flex items-center justify-center rounded ${u}`,children:h.jsx(re,{name:t.icon||"file",className:"w-4 h-4 text-gray-600 dark:text-gray-400"})}),h.jsxs(I,{className:`flex-1 min-w-0 ${f}`,children:[h.jsxs(I,{className:"flex items-center gap-1",children:[h.jsx(Q,{className:"text-sm font-medium text-gray-900 dark:text-white truncate",children:t.name}),t.isModuleMenu&&h.jsx(re,{name:"cube",className:"w-2 h-2 text-gray-300 dark:text-gray-500 ml-2",title:t.moduleName})]}),h.jsx(Q,{className:"text-xs text-gray-500 dark:text-gray-400 truncate block",children:t.url})]}),h.jsx(I,{className:"flex-shrink-0",onClick:M=>M.stopPropagation(),children:h.jsx(Ua,{checked:t.isActive,onChange:()=>o?.(new MouseEvent("click")),size:"sm",disabled:m})})]})},$S=()=>window.G7Core;function Yh(t,e,n=0,r=null){const s=[];for(const o of t)s.push({item:o,depth:n,parentId:r}),o.children?.length>0&&e.has(o.id)&&s.push(...Yh(o.children,e,n+1,o.id));return s}function nc(t,e){for(const n of t){if(n.id===e)return n;if(n.children){const r=nc(n.children,e);if(r)return r}}return null}function Kh(t){const e=[];if(t.children)for(const n of t.children)e.push(n.id),e.push(...Kh(n));return e}function Xh(t,e){return t.filter(n=>n.id!==e).map(n=>n.children?.length>0?{...n,children:Xh(n.children,e)}:n)}function qh(t,e,n,r){if(n===null){const s=[...t];return s.splice(r,0,e),s}return t.map(s=>{if(s.id===n){const o=[...s.children||[]];return o.splice(r,0,e),{...s,children:o}}return s.children?.length>0?{...s,children:qh(s.children,e,n,r)}:s})}function Zh(t,e,n,r){if(e===null){const s=t.findIndex(i=>i.id===n),o=t.findIndex(i=>i.id===r);return s===-1||o===-1?t:Jr(t,s,o)}return t.map(s=>{if(s.id===e&&s.children){const o=s.children.findIndex(a=>a.id===n),i=s.children.findIndex(a=>a.id===r);return o===-1||i===-1?s:{...s,children:Jr(s.children,o,i)}}return s.children?.length>0?{...s,children:Zh(s.children,e,n,r)}:s})}function Qh(t){const e=t.map((s,o)=>({id:s.id,order:o+1})),n={},r=s=>{s.forEach(o=>{o.children?.length>0&&(n[o.id]=o.children.map((i,a)=>({id:i.id,order:a+1})),r(o.children))})};return r(t),{parent_menus:e,child_menus:n}}const FS=({items:t=[],selectedId:e,onSelect:n,onOrderChange:r,onToggleStatus:s,title:o,className:i="",listClassName:a="",childrenContainerClassName:l="bg-gray-50 dark:bg-gray-900/50",itemClassName:c="py-2.5 px-3 mx-4 my-2 rounded-lg border",itemSelectedClassName:d,itemDefaultClassName:u,titleClassName:f="px-4 py-3 border-b border-gray-200 dark:border-gray-700",enableDrag:g=!0,toggleDisabled:m=!1,id:p,editorAttrs:y})=>{const[b,_]=v.useState(new Set),[x,E]=v.useState(null),[C,k]=v.useState(t),N=v.useRef(!1),S=v.useRef(""),w=v.useRef(""),M=v.useCallback(B=>JSON.stringify(B),[]),T=v.useCallback(B=>B.map(G=>{const V=G.children?.map(X=>X.id).join(",")||"";return`${G.id}:${V}`}).join("|"),[]);v.useEffect(()=>{const B=T(t),G=M(t);if(N.current){const V=T(C);(B===V||B!==S.current)&&(N.current=!1,S.current=B,w.current=G,(B!==V||G!==M(C))&&k(t));return}G!==w.current&&(S.current=B,w.current=G,k(t))},[t,T,M,C]);const P=jl(Zr(Xs,{activationConstraint:{distance:8}}),Zr(Ks,{coordinateGetter:Li})),L=v.useCallback(B=>{if(typeof B=="string")return B;const V=$S()?.locale?.current?.()||"ko";return B[V]||B.en||Object.values(B)[0]||""},[]),$=v.useCallback(B=>{_(G=>{const V=new Set(G);return V.has(B)?V.delete(B):V.add(B),V})},[]),A=v.useCallback(B=>{n?.(B)},[n]),D=v.useCallback((B,G,V)=>{V.stopPropagation(),s?.(B,G)},[s]),O=B=>{E(B.active.id)},z=B=>{const{active:G,over:V}=B;if(E(null),!V||G.id===V.id)return;const X=j.find(Z=>Z.item.id===Number(G.id)),K=j.find(Z=>Z.item.id===Number(V.id));if(!(!X||!K))if(X.parentId===K.parentId){const Z=Zh(C,X.parentId,Number(G.id),Number(V.id));k(Z),N.current=!0,r?.(Qh(Z))}else{const Z=X.item;if(Kh(Z).includes(Number(V.id)))return;let ne=Xh(C,Z.id);const he=j.findIndex(ye=>ye.item.id===Number(G.id)),J=j.findIndex(ye=>ye.item.id===Number(V.id)),le=he>J,ue=K.parentId;let ae;if(ue===null)ae=ne.findIndex(ye=>ye.id===Number(V.id)),ae===-1?ae=ne.length:le||(ae+=1);else{const we=nc(ne,ue)?.children||[];ae=we.findIndex(He=>He.id===Number(V.id)),ae===-1?ae=we.length:le||(ae+=1)}const ve={...Z,parent_id:ue,children:Z.children||[]};ne=qh(ne,ve,ue,ae),k(ne),N.current=!0;const fe=Qh(ne);fe.moved_items=[{id:Z.id,new_parent_id:ue}],r?.(fe)}},H=B=>({id:B.id,name:L(B.name),slug:B.slug,url:B.url,icon:B.icon,isActive:B.is_active,isModuleMenu:!!B.module_id,moduleName:B.module?L(B.module.name):void 0,hasChildren:B.children&&B.children.length>0}),j=v.useMemo(()=>Yh(C,b),[C,b]),W=v.useMemo(()=>j.map(B=>B.item.id),[j]),U=x?nc(C,Number(x)):null;return h.jsxs(I,{className:i,id:p,...y,children:[o&&h.jsx(I,{className:f,children:h.jsx(Ir,{className:"text-sm font-medium text-gray-900 dark:text-white",children:o})}),h.jsxs(ec,{sensors:P,collisionDetection:Wl,onDragStart:O,onDragEnd:z,children:[h.jsx(Di,{items:W,strategy:tc,children:h.jsx(I,{className:a,children:j.map(B=>h.jsx(I,{className:B.depth>0?l:"",children:h.jsx(Gh,{item:H(B.item),isSelected:e===B.item.id,isExpanded:b.has(B.item.id),level:B.depth,onClick:()=>A(B.item),onToggle:G=>D(B.item.id,B.item.is_active,G),onExpandToggle:()=>$(B.item.id),className:c,selectedClassName:d,defaultClassName:u,enableDrag:g,toggleDisabled:m})},B.item.id))})}),h.jsx(kS,{children:U?h.jsx(I,{className:"bg-white dark:bg-gray-800 shadow-lg rounded-lg border border-blue-500 p-3",children:h.jsx(I,{className:"flex items-center gap-2",children:h.jsx(I,{className:"text-sm font-medium text-gray-900 dark:text-white",children:L(U.name)})})}):null})]})]})},zS=[{value:"LayoutDashboard",label:"LayoutDashboard",faIcon:"tachometer-alt"},{value:"Settings",label:"Settings",faIcon:"cog"},{value:"Menu",label:"Menu",faIcon:"bars"},{value:"Users",label:"Users",faIcon:"users"},{value:"Package",label:"Package",faIcon:"cube"},{value:"Puzzle",label:"Puzzle",faIcon:"puzzle-piece"},{value:"FileText",label:"FileText",faIcon:"file-alt"},{value:"ShoppingCart",label:"ShoppingCart",faIcon:"shopping-cart"},{value:"MessageSquare",label:"MessageSquare",faIcon:"comment"},{value:"Home",label:"Home",faIcon:"home"},{value:"User",label:"User",faIcon:"user"},{value:"Search",label:"Search",faIcon:"search"},{value:"Bell",label:"Bell",faIcon:"bell"},{value:"Mail",label:"Mail",faIcon:"envelope"},{value:"Calendar",label:"Calendar",faIcon:"calendar"},{value:"Chart",label:"Chart",faIcon:"chart-bar"},{value:"Database",label:"Database",faIcon:"database"},{value:"Lock",label:"Lock",faIcon:"lock"},{value:"Globe",label:"Globe",faIcon:"globe"},{value:"Image",label:"Image",faIcon:"image"}],VS=({value:t,onChange:e,options:n=zS,placeholder:r,searchPlaceholder:s,noResultsText:o,className:i="",disabled:a=!1,name:l,id:c,editorAttrs:d})=>{const[u,f]=v.useState(!1),[g,m]=v.useState(""),p=v.useRef(null),y=n.find(x=>{if(!t)return!1;if(t.includes("fa-")){const E=t.replace(/^(fas|far|fab|fal|fad)\s+fa-/,"");return x.faIcon===E||x.faIcon===t}return x.value===t||x.faIcon===t}),b=n.filter(x=>x.label.toLowerCase().includes(g.toLowerCase()));v.useEffect(()=>{const x=E=>{p.current&&!p.current.contains(E.target)&&(f(!1),m(""))};if(u)return document.addEventListener("mousedown",x),()=>document.removeEventListener("mousedown",x)},[u]);const _=x=>{const E=`fas fa-${x.faIcon}`;e?.(E),f(!1),m("")};return h.jsxs(I,{ref:p,className:`relative ${i}`,id:c,...d,children:[h.jsxs(oe,{type:"button",onClick:()=>!a&&f(!u),disabled:a,className:` w-full flex items-center justify-between px-3 py-2 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg text-left text-sm ${a?"opacity-50 cursor-not-allowed":"hover:border-gray-400 dark:hover:border-gray-500"} focus:outline-none focus:ring-2 focus:ring-blue-500 - `,children:[h.jsx(I,{className:"flex items-center gap-2",children:y?h.jsxs(h.Fragment,{children:[h.jsx(oe,{name:y.faIcon,className:"w-4 h-4 text-gray-600 dark:text-gray-400"}),h.jsx(Q,{className:"text-gray-900 dark:text-white",children:y.label})]}):h.jsx(Q,{className:"text-gray-500 dark:text-gray-400",children:r})}),h.jsx(oe,{name:"chevron-down",className:`w-4 h-4 text-gray-400 dark:text-gray-500 transition-transform ${u?"rotate-180":""}`})]}),l&&h.jsx("input",{type:"hidden",name:l,value:e||""}),u&&h.jsxs(I,{className:"absolute z-50 w-full mt-1 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg max-h-72 overflow-hidden",children:[h.jsx(I,{className:"p-2 border-b border-gray-200 dark:border-gray-700",children:h.jsx(St,{type:"text",value:g,onChange:x=>m(x.target.value),placeholder:s,className:"w-full px-3 py-1.5 text-sm border border-gray-300 dark:border-gray-600 rounded bg-gray-50 dark:bg-gray-700 text-gray-900 dark:text-white",autoFocus:!0})}),h.jsx(I,{className:"max-h-56 overflow-y-auto",children:b.length>0?b.map(x=>h.jsxs(ae,{type:"button",onClick:()=>_(x),className:` + `,children:[h.jsx(I,{className:"flex items-center gap-2",children:y?h.jsxs(h.Fragment,{children:[h.jsx(re,{name:y.faIcon,className:"w-4 h-4 text-gray-600 dark:text-gray-400"}),h.jsx(Q,{className:"text-gray-900 dark:text-white",children:y.label})]}):h.jsx(Q,{className:"text-gray-500 dark:text-gray-400",children:r})}),h.jsx(re,{name:"chevron-down",className:`w-4 h-4 text-gray-400 dark:text-gray-500 transition-transform ${u?"rotate-180":""}`})]}),l&&h.jsx("input",{type:"hidden",name:l,value:t||""}),u&&h.jsxs(I,{className:"absolute z-50 w-full mt-1 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg max-h-72 overflow-hidden",children:[h.jsx(I,{className:"p-2 border-b border-gray-200 dark:border-gray-700",children:h.jsx(kt,{type:"text",value:g,onChange:x=>m(x.target.value),placeholder:s,className:"w-full px-3 py-1.5 text-sm border border-gray-300 dark:border-gray-600 rounded bg-gray-50 dark:bg-gray-700 text-gray-900 dark:text-white",autoFocus:!0})}),h.jsx(I,{className:"max-h-56 overflow-y-auto",children:b.length>0?b.map(x=>h.jsxs(oe,{type:"button",onClick:()=>_(x),className:` w-full flex items-center gap-3 px-3 py-2 text-left text-sm ${y?.faIcon===x.faIcon?"bg-blue-50 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300":"text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700"} - `,children:[h.jsx(oe,{name:x.faIcon,className:"w-4 h-4"}),h.jsx(Q,{children:x.label}),y?.faIcon===x.faIcon&&h.jsx(oe,{name:"check",className:"w-4 h-4 ml-auto text-blue-600"})]},x.value)):h.jsx(I,{className:"px-3 py-4 text-sm text-gray-500 dark:text-gray-400 text-center",children:o})})]})]})},dc=({defaultOpen:e=!1,isOpen:t,onToggle:n,className:r="",style:s,children:o,disabled:i=!1,id:a,editorAttrs:l})=>{const[c,d]=v.useState(e),u=t!==void 0,f=u?t:c,g=v.useRef(null),[m,p]=v.useState(e?void 0:0),y=()=>{if(i)return;const _=!f;u||d(_),n?.(_)};v.useEffect(()=>{if(g.current)if(f){p(g.current.scrollHeight);const _=setTimeout(()=>{p(void 0)},300);return()=>clearTimeout(_)}else p(g.current.scrollHeight),requestAnimationFrame(()=>{p(0)})},[f]);const b={};if(v.Children.forEach(o,_=>{if(v.isValidElement(_)){const x=_.props?.slot;x==="trigger"?b.trigger=_:x==="content"&&(b.content=_)}}),!b.trigger&&!b.content){const _=v.Children.toArray(o);_.length>=2?(b.trigger=_[0],b.content=_.slice(1)):_.length===1&&(b.content=_[0])}return h.jsxs(I,{className:`border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden ${r}`,style:s,id:a,...l,children:[h.jsxs(ae,{type:"button",onClick:y,disabled:i,className:`w-full flex items-center justify-between p-3 text-left bg-gray-50 dark:bg-gray-800 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors ${i?"opacity-50 cursor-not-allowed":""} ${f?"border-b border-gray-200 dark:border-gray-700":""}`,"aria-expanded":f,children:[h.jsx(I,{className:"flex-1",children:b.trigger}),!b.trigger&&h.jsx("svg",{className:`w-5 h-5 text-gray-500 dark:text-gray-400 transition-transform duration-200 ${f?"rotate-180":""}`,fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:h.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M19 9l-7 7-7-7"})})]}),h.jsx(I,{ref:g,className:"overflow-hidden transition-all duration-300 ease-in-out",style:{height:m!==void 0?`${m}px`:"auto"},children:h.jsx(I,{className:"bg-white dark:bg-gray-800",children:b.content})})]})},ho=window.G7Core,dg=ho?.useControllableState,ug=(e,t)=>window.G7Core?.t?.(e,t)??e,o_={"":"common.scope_type_all_desc",self:"common.scope_type_self_desc",role:"common.scope_type_role_desc"},i_=({options:e,value:t,onChange:n,disabled:r=!1})=>{const[s,o]=v.useState(!1),[i,a]=v.useState({}),l=v.useRef(null),c=e.find(u=>u.value===t)??e[0];v.useEffect(()=>{if(!s)return;const u=g=>{l.current&&!l.current.contains(g.target)&&o(!1)},f=()=>{if(!l.current)return;const g=l.current.getBoundingClientRect();a({position:"fixed",top:g.bottom+4,right:window.innerWidth-g.right,zIndex:9999})};return document.addEventListener("mousedown",u),window.addEventListener("scroll",f,!0),window.addEventListener("resize",f),()=>{document.removeEventListener("mousedown",u),window.removeEventListener("scroll",f,!0),window.removeEventListener("resize",f)}},[s]);const d=v.useCallback(()=>{if(r||!l.current)return;const u=l.current.getBoundingClientRect();a({position:"fixed",top:u.bottom+4,right:window.innerWidth-u.right,zIndex:9999}),o(f=>!f)},[r]);return h.jsxs(I,{ref:l,className:"relative flex-shrink-0",children:[h.jsxs(I,{role:"button",tabIndex:0,onClick:u=>{u.stopPropagation(),u.preventDefault(),d()},onMouseDown:u=>u.stopPropagation(),onKeyDown:u=>{(u.key==="Enter"||u.key===" ")&&(u.stopPropagation(),u.preventDefault(),d()),u.key==="Escape"&&o(!1)},className:`flex items-center gap-1 text-xs px-2 py-1 rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-700 dark:text-gray-300 cursor-pointer select-none ${r?"opacity-50 cursor-not-allowed":"hover:border-blue-400 dark:hover:border-blue-500"}`,children:[h.jsx(Q,{children:c?.label??""}),h.jsx(oe,{name:de.ChevronDown,className:"w-3 h-3 text-gray-400 dark:text-gray-500"})]}),s&&h.jsx(I,{style:i,className:"w-56 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-lg shadow-lg py-1",children:e.map(u=>{const f=u.value??"",g=o_[f],m=g?ug(g):"",p=u.value===t;return h.jsxs(I,{role:"option","aria-selected":p,onClick:y=>{y.stopPropagation(),y.preventDefault(),n(u.value),o(!1)},onMouseDown:y=>y.stopPropagation(),className:`px-3 py-2 cursor-pointer transition-colors ${p?"bg-blue-50 dark:bg-blue-900/30":"hover:bg-gray-50 dark:hover:bg-gray-700/50"}`,children:[h.jsxs(I,{className:"flex items-center justify-between",children:[h.jsx(Q,{className:`text-xs font-medium ${p?"text-blue-700 dark:text-blue-300":"text-gray-900 dark:text-white"}`,children:u.label}),p&&h.jsx(oe,{name:de.Check,className:"w-3.5 h-3.5 text-blue-600 dark:text-blue-400"})]}),m&&h.jsx(Q,{className:"block text-xs text-gray-500 dark:text-gray-400 mt-0.5",children:m})]},f||"__null__")})})]})},a_=(e,t)=>{if(e.length!==t.length)return!1;const n=[...e].sort((s,o)=>s.id-o.id),r=[...t].sort((s,o)=>s.id-o.id);return n.every((s,o)=>s.id===r[o].id&&s.scope_type===r[o].scope_type)},l_=({data:e=[],value:t=[],onChange:n,scopeOptions:r=[],className:s="",disabled:o=!1,desktopColumns:i=2,installedModules:a=[],installedPlugins:l=[],id:c,editorAttrs:d})=>{const u=v.useCallback(E=>{if(n){const D=ho?.createChangeEvent?ho.createChangeEvent({value:E}):{target:{value:E}};n(D)}},[n]),[f,g]=dg?dg(t,[],u,{isEqual:a_}):(()=>{const[E,D]=v.useState(t),P=v.useRef(n);P.current=n;const L=v.useRef(!1);v.useEffect(()=>{if(L.current){L.current=!1;return}D(t)},[t]);const $=v.useCallback(A=>{L.current=!0,D(O=>{const T=typeof A=="function"?A(O):A;if(P.current){const z=ho?.createChangeEvent?ho.createChangeEvent({value:T}):{target:{value:T}};P.current(z)}return T})},[]);return[E,$]})(),m=v.useMemo(()=>new Set(f.map(E=>E.id)),[f]),p=v.useCallback(E=>E.is_assignable?[E.id]:E.children.flatMap(D=>p(D)),[]),y=v.useCallback(E=>{const D=p(E);return D.length>0&&D.every(P=>m.has(P))},[m,p]),b=v.useCallback(E=>{const D=p(E),P=D.filter(L=>m.has(L)).length;return P>0&&Pp(E).filter(P=>m.has(P)).length,[m,p]),x=v.useCallback((E,D)=>{o||g(P=>D?[...P,{id:E,scope_type:null}]:P.filter(L=>L.id!==E))},[o,g]),M=v.useCallback((E,D)=>{if(o)return;const P=p(E),L=new Set(P);g($=>{if(D){const A=new Set($.map(T=>T.id)),O=P.filter(T=>!A.has(T)).map(T=>({id:T,scope_type:null}));return[...$,...O]}else return $.filter(A=>!L.has(A.id))})},[o,p,g]),C=v.useCallback((E,D)=>{o||g(P=>P.map(L=>L.id===E?{...L,scope_type:D}:L))},[o,g]),k=E=>{const D=m.has(E.id),P=!!E.resource_route_key,L=D&&P&&r.length>0,$=f.find(A=>A.id===E.id)?.scope_type??null;return h.jsxs(ln,{className:`flex items-start gap-3 p-3 rounded-lg border cursor-pointer transition-colors ${D?"border-blue-200 bg-blue-50 dark:border-blue-800 dark:bg-blue-900/20":"border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-700/50"} ${o?"opacity-50 cursor-not-allowed":""}`,children:[h.jsx(St,{type:"checkbox",checked:D,onChange:A=>{A.stopPropagation(),x(E.id,A.target.checked)},disabled:o,className:"w-4 h-4 mt-0.5 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600"}),h.jsxs(I,{className:"flex-1 min-w-0",children:[h.jsxs(I,{className:"flex items-center justify-between gap-2",children:[h.jsx(Q,{className:"text-sm font-medium text-gray-900 dark:text-white",children:E.name}),L&&h.jsx(i_,{options:r,value:$,onChange:A=>C(E.id,A),disabled:o})]}),E.description&&h.jsx(Q,{className:"block text-xs text-gray-500 dark:text-gray-400 mt-0.5",children:E.description})]})]},E.id)},N=(E,D)=>{const P=y(E),L=b(E),$=_(E),A=E.children.filter(z=>z.is_assignable);if(A.length===0&&E.children.length===0)return null;const O=E.children.some(z=>!z.is_assignable),T=i===1?"grid-cols-1":i===3?"grid-cols-1 md:grid-cols-3":"grid-cols-1 md:grid-cols-2";return h.jsxs(dc,{defaultOpen:!1,className:"border-0 rounded-none",children:[h.jsxs(I,{slot:"trigger",className:"flex items-center gap-3 w-full py-2",children:[h.jsx("input",{type:"checkbox",checked:P,ref:z=>{z&&(z.indeterminate=L)},onChange:z=>{z.stopPropagation(),M(E,z.target.checked)},onClick:z=>z.stopPropagation(),onMouseDown:z=>z.stopPropagation(),disabled:o,className:"w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600"}),h.jsx(oe,{name:de.Folder,className:"w-4 h-4 text-gray-400 dark:text-gray-500"}),h.jsx(Q,{className:"text-sm font-medium text-gray-700 dark:text-gray-300",children:E.name}),h.jsxs(Q,{className:"inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-300 ml-1",children:[$,"/",E.leaf_count]}),h.jsx(oe,{name:de.ChevronDown,className:"w-4 h-4 text-gray-400 dark:text-gray-500 ml-auto"})]}),h.jsx(I,{slot:"content",className:`pl-8 grid ${T} gap-2 py-2`,children:O?E.children.map(z=>z.is_assignable?k(z):N(z)):A.map(z=>k(z))})]},E.id)},S=E=>{if(E.extension_type==="core")return"core";if(E.extension_type==="module")return"module";if(E.extension_type==="plugin")return"plugin";const D=E.extension_identifier??E.module??"";return D==="core"||!D?"core":l?.some(L=>L.identifier===D)?"plugin":"module"},w=E=>{const D=y(E),P=b(E),L=_(E),$=S(E);return h.jsxs(dc,{defaultOpen:!1,className:"mb-2",children:[h.jsxs(I,{slot:"trigger",className:"flex items-center gap-3 w-full py-2",children:[h.jsx("input",{type:"checkbox",checked:D,ref:A=>{A&&(A.indeterminate=P)},onChange:A=>{A.stopPropagation(),M(E,A.target.checked)},onClick:A=>A.stopPropagation(),onMouseDown:A=>A.stopPropagation(),disabled:o,className:"w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600"}),h.jsx(oe,{name:$==="core"?de.Cog:de.Cube,className:"w-5 h-5 text-gray-400 dark:text-gray-500"}),h.jsx(Q,{className:"font-medium text-gray-900 dark:text-white",children:E.name}),$!=="core"&&h.jsx(gh,{type:$,identifier:E.extension_identifier??E.module,installedModules:a,installedPlugins:l}),h.jsxs(Q,{className:"inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-300 ml-2",children:[L,"/",E.leaf_count]}),h.jsx(oe,{name:de.ChevronDown,className:"w-4 h-4 text-gray-400 dark:text-gray-500 ml-auto"})]}),h.jsx(I,{slot:"content",className:"pl-8 space-y-2 pb-2",children:E.children.map(A=>A.is_assignable?k(A):N(A))})]},E.id)};return e.length===0?h.jsx(I,{className:`text-sm text-gray-400 dark:text-gray-500 py-4 text-center ${s}`,id:c,...d,children:ug("common.no_data")??"데이터가 없습니다."}):h.jsx(I,{className:`space-y-2 ${s}`,id:c,...d,children:e.map(E=>w(E))})};function c_({text:e,keyword:t}){if(!t||!e)return h.jsx(h.Fragment,{children:e});const n=t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),r=e.split(new RegExp(`(${n})`,"gi"));return h.jsx(h.Fragment,{children:r.map((s,o)=>s.toLowerCase()===t.toLowerCase()?h.jsx(Q,{className:"bg-yellow-200 dark:bg-yellow-700 rounded px-0.5",children:s},o):s)})}const d_=({data:e=[],expandedIds:t=[],selectedIds:n=[],searchKeyword:r="",showProductCount:s=!1,selectable:o=!1,className:i="",onToggle:a,onSelectionChange:l,id:c,editorAttrs:d})=>{const u=v.useCallback(y=>{if(!a)return;const b=t.includes(y)?t.filter(_=>_!==y):[...t,y];a(b)},[t,a]),f=v.useCallback((y,b)=>{if(!l)return;const _=b?[...n,y]:n.filter(x=>x!==y);l(_)},[n,l]),g=v.useMemo(()=>{if(!r)return null;const y=new Set,b=_=>{for(const x of _)(x.localized_name||x.name).toLowerCase().includes(r.toLowerCase())&&y.add(x.id),x.children&&b(x.children)};return b(e),y},[e,r]),m=v.useCallback(y=>!g||g.has(y.id)?!0:(y.children??[]).some(b=>m(b)),[g]),p=(y,b=0)=>{const _=(y.children??[]).length>0,x=t.includes(y.id),M=n.includes(y.id),C=y.localized_name||y.name;return r&&!m(y)?null:h.jsxs(I,{"data-testid":`category-node-${y.id}`,children:[h.jsxs(I,{className:`flex items-center gap-2 py-1.5 px-2 rounded cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors ${b>0?"ml-"+Math.min(b*5,20):""}`,style:b>0?{marginLeft:`${b*20}px`}:void 0,children:[_?h.jsx(I,{className:"flex-shrink-0 w-5 h-5 flex items-center justify-center text-gray-400 dark:text-gray-500",onClick:()=>u(y.id),"data-testid":`toggle-${y.id}`,children:h.jsx(oe,{name:x?de.CaretDown:de.CaretRight,className:"text-xs"})}):h.jsx(I,{className:"flex-shrink-0 w-5 h-5"}),o&&h.jsx(En,{checked:M,onChange:k=>{k.stopPropagation(),f(y.id,k.target.checked)},className:"flex-shrink-0","data-testid":`checkbox-${y.id}`}),h.jsx(oe,{name:de.Folder,className:`flex-shrink-0 text-sm ${_?"text-yellow-500 dark:text-yellow-400":"text-gray-400 dark:text-gray-500"}`}),h.jsx(Q,{className:"text-sm text-gray-700 dark:text-gray-300 select-none",onClick:()=>_&&u(y.id),children:h.jsx(c_,{text:C,keyword:r})}),s&&y.products_count!==void 0&&h.jsx(Q,{className:"inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-gray-100 text-gray-500 dark:bg-gray-700 dark:text-gray-400 ml-1",children:y.products_count})]}),_&&x&&h.jsx(I,{"data-testid":`children-${y.id}`,children:(y.children??[]).map(k=>p(k,b+1))})]},y.id)};return e.length===0?h.jsx(I,{className:`text-sm text-gray-400 dark:text-gray-500 py-4 text-center ${i}`,id:c,...d,children:"데이터가 없습니다."}):h.jsx(I,{className:`border border-gray-200 dark:border-gray-700 rounded-lg p-2 max-h-80 overflow-y-auto ${i}`,id:c,...d,children:e.map(y=>p(y,0))})},fg="carousel",go="controller",u_="navigation",f_="no-scroll",uc="portal",h_="root",hg="toolbar",g_="counter",po="fullscreen",gg="slideshow",mo="thumbnails",pg="zoom",ps="loading",fc="playing",bo="error",yo="complete",p_="placeholder",vo=e=>`active-slide-${e}`,m_=vo(ps),b_=vo(fc),y_=vo(bo),v_=vo(yo),mg="fullsize",ms="flex_center",x_="no_scroll",bg="no_scroll_padding",hc="slide",yg="slide_wrapper",w_="slide_wrapper_interactive",fr="prev",hr="next",gc="swipe",bs="close",vg="onPointerDown",xg="onPointerMove",wg="onPointerUp",kg="onPointerLeave",Cg="onPointerCancel",pc="onKeyDown",k_="onKeyUp",mc="onWheel",C_="Escape",S_="ArrowLeft",__="ArrowRight",E_="button",ji="icon",Sg="contain",_g="cover",Eg="Unknown action type",Mg="yarl__";function Wt(...e){return e.filter(Boolean).join(" ")}function Te(e){return`${Mg}${e}`}function ht(e){return`--${Mg}${e}`}function _r(e,t){return`${e}${t?`_${t}`:""}`}function Er(e){return t=>_r(e,t)}function Mr(e,t){var n;return(n=e?.[t])!==null&&n!==void 0?n:t}function Ng(e,t,n){return Mr(e,"{index} of {total}").replace(/\{index}/g,`${xc(n,t.length)+1}`).replace(/\{total}/g,`${t.length}`)}function ys(...e){return()=>{e.forEach(t=>{t()})}}function un(e,t,n){return()=>{const r=F.useContext(n);if(!r)throw new Error(`${e} must be used within a ${t}.Provider`);return r}}function bc(){return typeof window<"u"}function yc(e,t=0){const n=10**t;return Math.round((e+Number.EPSILON)*n)/n}function gr(e){return e.type===void 0||e.type==="image"}function vc(e,t){return e.imageFit===_g||e.imageFit!==Sg&&t===_g}function Wi(e){return typeof e=="string"?Number.parseInt(e,10):e}function Gi(e){if(typeof e=="number")return{pixel:e};if(typeof e=="string"){const t=Wi(e);return e.endsWith("%")?{percent:t}:{pixel:t}}return{pixel:0}}function M_(e,t){const n=Gi(t),r=n.percent!==void 0?e.width/100*n.percent:n.pixel;return{width:Math.max(e.width-2*r,0),height:Math.max(e.height-2*r,0)}}function N_(){return(bc()?window?.devicePixelRatio:void 0)||1}function xc(e,t){return t>0?(e%t+t)%t:0}function wc(e){return e.length>0}function kc(e,t){return e[xc(t,e.length)]}function Cc(e,t){return wc(e)?kc(e,t):void 0}function Tg(e){return gr(e)?e.src:void 0}function Yi(e,t,n){if(!n)return e;const{buttons:r,...s}=e,o=r.findIndex(a=>a===t),i=F.isValidElement(n)?F.cloneElement(n,{key:t},null):n;if(o>=0){const a=[...r];return a.splice(o,1,i),{buttons:a,...s}}return{buttons:[i,...r],...s}}function Og(e,t,n=0){return Math.min(e.preload,Math.max(e.finite?t.length-1:Math.floor(t.length/2),n))}const T_=Number(F.version.split(".")[0])>=19;function O_(e){return{inert:T_?e:e?"":void 0}}function A_(e){e.scrollTop}const Sc={open:!1,close:()=>{},index:0,slides:[],render:{},plugins:[],toolbar:{buttons:[bs]},labels:{},animation:{fade:250,swipe:500,easing:{fade:"ease",swipe:"ease-out",navigation:"ease-in-out"}},carousel:{finite:!1,preload:2,padding:"16px",spacing:"30%",imageFit:Sg,imageProps:{}},controller:{ref:null,focus:!0,aria:!1,touchAction:"none",closeOnPullUp:!1,closeOnPullDown:!1,closeOnBackdropClick:!1,closeOnEscape:!0,preventDefaultWheelX:!0,preventDefaultWheelY:!1,disableSwipeNavigation:!1},portal:{},noScroll:{disabled:!1},on:{},styles:{},className:""};function pn(e,t){return{name:e,component:t}}function It(e,t){return{module:e,children:t}}function Ag(e,t,n){return e.module.name===t?n(e):e.children?[It(e.module,e.children.flatMap(r=>{var s;return(s=Ag(r,t,n))!==null&&s!==void 0?s:[]}))]:[e]}function vs(e,t,n){return e.flatMap(r=>{var s;return(s=Ag(r,t,n))!==null&&s!==void 0?s:[]})}function D_(e,t=[],n=[]){let r=e;const s=g=>{const m=[...r];for(;m.length>0;){const p=m.pop();if(p?.module.name===g)return!0;p?.children&&m.push(...p.children)}return!1},o=(g,m)=>{if(g===""){r=[It(m,r)];return}r=vs(r,g,p=>[It(m,[p])])},i=(g,m)=>{r=vs(r,g,p=>[It(p.module,[It(m,p.children)])])},a=(g,m,p)=>{r=vs(r,g,y=>{var b;return[It(y.module,[...p?[It(m)]:[],...(b=y.children)!==null&&b!==void 0?b:[],...p?[]:[It(m)]])]})},l=(g,m,p)=>{r=vs(r,g,y=>[...p?[It(m)]:[],y,...p?[]:[It(m)]])},c=g=>{i(go,g)},d=(g,m)=>{r=vs(r,g,p=>[It(m,p.children)])},u=g=>{r=vs(r,g,m=>m.children)},f=g=>{n.push(g)};return t.forEach(g=>{g({contains:s,addParent:o,append:i,addChild:a,addSibling:l,addModule:c,replace:d,remove:u,augment:f})}),{config:r,augmentation:g=>n.reduce((m,p)=>p(m),g)}}const Dg=F.createContext(null),_c=un("useA11yContext","A11yContext",Dg);function I_({children:e}){const[t,n]=F.useState(!1),[r,s]=F.useState(!1),o=F.useMemo(()=>({focusWithin:t,trackFocusWithin:(a,l)=>{const c=d=>u=>{var f;u.currentTarget.contains(u.relatedTarget)||n(d),(f=d?a:l)===null||f===void 0||f(u)};return{onFocus:c(!0),onBlur:c(!1)}},autoPlaying:r,setAutoPlaying:s}),[t,r]);return F.createElement(Dg.Provider,{value:o},e)}const Ig=F.createContext(null),xs=un("useDocument","DocumentContext",Ig);function L_({nodeRef:e,children:t}){const n=F.useMemo(()=>{const r=o=>{var i;return((i=o||e.current)===null||i===void 0?void 0:i.ownerDocument)||document};return{getOwnerDocument:r,getOwnerWindow:o=>{var i;return((i=r(o))===null||i===void 0?void 0:i.defaultView)||window}}},[e]);return F.createElement(Ig.Provider,{value:n},t)}const Lg=F.createContext(null),ws=un("useEvents","EventsContext",Lg);function P_({children:e}){const[t]=F.useState({});F.useEffect(()=>()=>{Object.keys(t).forEach(r=>delete t[r])},[t]);const n=F.useMemo(()=>{const r=(i,a)=>{var l;(l=t[i])===null||l===void 0||l.splice(0,t[i].length,...t[i].filter(c=>c!==a))};return{publish:(...[i,a])=>{var l;(l=t[i])===null||l===void 0||l.forEach(c=>c(a))},subscribe:(i,a)=>(t[i]||(t[i]=[]),t[i].push(a),()=>r(i,a)),unsubscribe:r}},[t]);return F.createElement(Lg.Provider,{value:n},e)}const Pg=F.createContext(null),Lt=un("useLightboxProps","LightboxPropsContext",Pg);function $g({children:e,...t}){return F.createElement(Pg.Provider,{value:t},e)}const Fg=F.createContext(null),mn=un("useLightboxState","LightboxStateContext",Fg),zg=F.createContext(null),$_=un("useLightboxDispatch","LightboxDispatchContext",zg);function F_(e,t){switch(t.type){case"swipe":{const{slides:n}=e,r=t?.increment||0,s=e.globalIndex+r,o=xc(s,n.length),i=Cc(n,o),a=r||t.duration!==void 0?{increment:r,duration:t.duration,easing:t.easing}:void 0;return{slides:n,currentIndex:o,globalIndex:s,currentSlide:i,animation:a}}case"update":return t.slides!==e.slides||t.index!==e.currentIndex?{slides:t.slides,currentIndex:t.index,globalIndex:t.index,currentSlide:Cc(t.slides,t.index)}:e;default:throw new Error(Eg)}}function z_({slides:e,index:t,children:n}){const[r,s]=F.useReducer(F_,{slides:e,currentIndex:t,globalIndex:t,currentSlide:Cc(e,t)}),[o,i]=F.useState(e),[a,l]=F.useState(t);(e!==o||t!==a)&&(i(e),l(t),s({type:"update",slides:e,index:t}));const c=F.useMemo(()=>({...r,state:r,dispatch:s}),[r,s]);return F.createElement(zg.Provider,{value:s},F.createElement(Fg.Provider,{value:c},n))}const Vg=F.createContext(null),V_=un("useRTLContext","RTLContext",Vg);function H_({isRTL:e,children:t}){const n=F.useMemo(()=>({isRTL:e}),[e]);return F.createElement(Vg.Provider,{value:n},t)}const Hg=F.createContext(null),xo=un("useTimeouts","TimeoutsContext",Hg);function U_({children:e}){const[t]=F.useState([]);F.useEffect(()=>()=>{t.forEach(r=>window.clearTimeout(r)),t.splice(0,t.length)},[t]);const n=F.useMemo(()=>{const r=i=>{t.splice(0,t.length,...t.filter(a=>a!==i))};return{setTimeout:(i,a)=>{const l=window.setTimeout(()=>{r(l),i()},a);return t.push(l),l},clearTimeout:i=>{i!==void 0&&(r(i),window.clearTimeout(i))}}},[t]);return F.createElement(Hg.Provider,{value:n},e)}const ks=F.forwardRef(function({label:t,className:n,icon:r,renderIcon:s,onClick:o,style:i,...a},l){const{styles:c,labels:d}=Lt(),u=Mr(d,t);return F.createElement("button",{ref:l,type:"button",title:u,"aria-label":u,className:Wt(Te(E_),n),onClick:o,style:{...i,...c.button},...a},s?s():F.createElement(r,{className:Te(ji),style:c.icon}))});function Ug(e,t){const n=r=>F.createElement("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",width:"24",height:"24","aria-hidden":"true",focusable:"false",...r},t);return n.displayName=e,n}function nn(e,t){return Ug(e,F.createElement("g",{fill:"currentColor"},F.createElement("path",{d:"M0 0h24v24H0z",fill:"none"}),t))}function B_(e,t){return Ug(e,F.createElement(F.Fragment,null,F.createElement("defs",null,F.createElement("mask",{id:"strike"},F.createElement("path",{d:"M0 0h24v24H0z",fill:"white"}),F.createElement("path",{d:"M0 0L24 24",stroke:"black",strokeWidth:4}))),F.createElement("path",{d:"M0.70707 2.121320L21.878680 23.292883",stroke:"currentColor",strokeWidth:2}),F.createElement("g",{fill:"currentColor",mask:"url(#strike)"},F.createElement("path",{d:"M0 0h24v24H0z",fill:"none"}),t)))}const j_=nn("Close",F.createElement("path",{d:"M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"})),W_=nn("Previous",F.createElement("path",{d:"M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"})),G_=nn("Next",F.createElement("path",{d:"M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"})),Y_=nn("Loading",F.createElement(F.Fragment,null,Array.from({length:8}).map((e,t,n)=>F.createElement("line",{key:t,x1:"12",y1:"6.5",x2:"12",y2:"1.8",strokeLinecap:"round",strokeWidth:"2.6",stroke:"currentColor",strokeOpacity:1/n.length*(t+1),transform:`rotate(${360/n.length*t}, 12, 12)`})))),X_=nn("Error",F.createElement("path",{d:"M21.9,21.9l-8.49-8.49l0,0L3.59,3.59l0,0L2.1,2.1L0.69,3.51L3,5.83V19c0,1.1,0.9,2,2,2h13.17l2.31,2.31L21.9,21.9z M5,18 l3.5-4.5l2.5,3.01L12.17,15l3,3H5z M21,18.17L5.83,3H19c1.1,0,2,0.9,2,2V18.17z"})),Tn=bc()?F.useLayoutEffect:F.useEffect;function Ec(){const[e,t]=F.useState(!1);return F.useEffect(()=>{var n,r;const s=(n=window.matchMedia)===null||n===void 0?void 0:n.call(window,"(prefers-reduced-motion: reduce)");t(s?.matches);const o=i=>t(i.matches);return(r=s?.addEventListener)===null||r===void 0||r.call(s,"change",o),()=>{var i;return(i=s?.removeEventListener)===null||i===void 0?void 0:i.call(s,"change",o)}},[]),e}function K_(e){let t=0,n=0,r=0;const o=window.getComputedStyle(e).transform.match(/matrix.*\((.+)\)/);if(o){const i=o[1].split(",").map(Wi);i.length===6?(t=i[4],n=i[5]):i.length===16&&(t=i[12],n=i[13],r=i[14])}return{x:t,y:n,z:r}}function Mc(e,t){const n=F.useRef(void 0),r=F.useRef(void 0),s=Ec();return Tn(()=>{var o,i,a;if(e.current&&n.current!==void 0&&!s){const{keyframes:l,duration:c,easing:d,onfinish:u}=t(n.current,e.current.getBoundingClientRect(),K_(e.current))||{};if(l&&c){(o=r.current)===null||o===void 0||o.cancel(),r.current=void 0;try{r.current=(a=(i=e.current).animate)===null||a===void 0?void 0:a.call(i,l,{duration:c,easing:d})}catch(f){console.error(f)}r.current&&(r.current.onfinish=()=>{r.current=void 0,u?.()})}}n.current=void 0}),{prepareAnimation:o=>{n.current=o},isAnimationPlaying:()=>{var o;return((o=r.current)===null||o===void 0?void 0:o.playState)==="running"}}}function Bg(){const e=F.useRef(null),t=F.useRef(void 0),[n,r]=F.useState();return{setContainerRef:F.useCallback(o=>{e.current=o,t.current&&(t.current.disconnect(),t.current=void 0);const i=()=>{if(o){const a=window.getComputedStyle(o),l=c=>parseFloat(c)||0;r({width:Math.round(o.clientWidth-l(a.paddingLeft)-l(a.paddingRight)),height:Math.round(o.clientHeight-l(a.paddingTop)-l(a.paddingBottom))})}else r(void 0)};i(),o&&typeof ResizeObserver<"u"&&(t.current=new ResizeObserver(i),t.current.observe(o))},[]),containerRef:e,containerRect:n}}function Xi(){const e=F.useRef(void 0),{setTimeout:t,clearTimeout:n}=xo();return F.useCallback((r,s)=>{n(e.current),e.current=t(r,s>0?s:0)},[t,n])}function Ye(e){const t=F.useRef(e);return Tn(()=>{t.current=e}),F.useCallback((...n)=>{var r;return(r=t.current)===null||r===void 0?void 0:r.call(t,...n)},[])}function jg(e,t){typeof e=="function"?e(t):e&&(e.current=t)}function Nc(e,t){return F.useMemo(()=>e==null&&t==null?null:n=>{jg(e,n),jg(t,n)},[e,t])}function Wg(e,t=!1){const n=F.useRef(!1);Tn(()=>{t&&n.current&&(n.current=!1,e())},[t,e]);const r=F.useCallback(()=>{n.current=!0},[]),s=F.useCallback(()=>{n.current=!1},[]);return{onFocus:r,onBlur:s}}function Ki(){return V_().isRTL}function Gg(){const[e]=F.useState({}),t=F.useCallback((s,o)=>{var i;(i=e[s])===null||i===void 0||i.forEach(a=>{o.isPropagationStopped()||a(o)})},[e]),n=F.useMemo(()=>({onPointerDown:s=>t(vg,s),onPointerMove:s=>t(xg,s),onPointerUp:s=>t(wg,s),onPointerLeave:s=>t(kg,s),onPointerCancel:s=>t(Cg,s),onKeyDown:s=>t(pc,s),onKeyUp:s=>t(k_,s),onWheel:s=>t(mc,s)}),[t]),r=F.useCallback((s,o)=>(e[s]||(e[s]=[]),e[s].unshift(o),()=>{const i=e[s];i&&i.splice(0,i.length,...i.filter(a=>a!==o))}),[e]);return{registerSensors:n,subscribeSensors:r}}function Yg(e,t){const n=F.useRef(0),r=Xi(),s=Ye((...o)=>{n.current=Date.now(),e(o)});return F.useCallback((...o)=>{r(()=>{s(o)},t-(Date.now()-n.current))},[t,s,r])}const Tc=Er("slide"),Oc=Er("slide_image");function Nr({slide:e,offset:t,render:n,rect:r,imageFit:s,imageProps:o,onClick:i,onLoad:a,onError:l,style:c}){var d,u,f,g,m,p,y,b;const[_,x]=F.useState(ps),{publish:M}=ws(),{setTimeout:C}=xo(),k=F.useRef(null);F.useEffect(()=>{t===0&&M(vo(_))},[t,_,M]);const N=Ye(U=>{("decode"in U?U.decode():Promise.resolve()).catch(()=>{}).then(()=>{U.parentNode&&(x(yo),C(()=>{a?.(U)},0))})}),S=F.useCallback(U=>{k.current=U,U?.complete&&N(U)},[N]),w=F.useCallback(U=>{N(U.currentTarget)},[N]),E=Ye(()=>{x(bo),l?.()}),D=vc(e,s),P=(U,W)=>Number.isFinite(U)?U:W,L=P(Math.max(...((u=(d=e.srcSet)===null||d===void 0?void 0:d.map(U=>U.width))!==null&&u!==void 0?u:[]).concat(e.width?[e.width]:[]).filter(Boolean)),((f=k.current)===null||f===void 0?void 0:f.naturalWidth)||0),$=P(Math.max(...((m=(g=e.srcSet)===null||g===void 0?void 0:g.map(U=>U.height))!==null&&m!==void 0?m:[]).concat(e.height?[e.height]:[]).filter(Boolean)),((p=k.current)===null||p===void 0?void 0:p.naturalHeight)||0),A=L&&$?{maxWidth:`min(${L}px, 100%)`,maxHeight:`min(${$}px, 100%)`}:{maxWidth:"100%",maxHeight:"100%"},O=(y=e.srcSet)===null||y===void 0?void 0:y.slice().sort((U,W)=>U.width-W.width).map(U=>`${U.src} ${U.width}w`).join(", "),T=()=>r&&!D&&e.width&&e.height?r.height/e.height*e.width:Number.MAX_VALUE,z=O&&r&&bc()?`${Math.round(Math.min(T(),r.width))}px`:void 0,{style:H,className:j,...G}=(typeof o=="function"?o(e):o)||{};return F.createElement(F.Fragment,null,F.createElement("img",{ref:S,onLoad:w,onError:E,onClick:i,draggable:!1,className:Wt(Te(Oc()),D&&Te(Oc("cover")),_!==yo&&Te(Oc("loading")),j),style:{...A,...c,...H},...G,alt:(b=e.alt)!==null&&b!==void 0?b:"",sizes:z,srcSet:O,src:e.src}),_!==yo&&F.createElement("div",{className:Te(Tc(p_))},_===ps&&(n?.iconLoading?n.iconLoading():F.createElement(Y_,{className:Wt(Te(ji),Te(Tc(ps)))})),_===bo&&(n?.iconError?n.iconError():F.createElement(X_,{className:Wt(Te(ji),Te(Tc(bo)))}))))}const q_=F.forwardRef(function({className:t,children:n,onFocus:r,onBlur:s,...o},i){const a=F.useRef(null),[l,c]=F.useState(!1),{trackFocusWithin:d}=_c(),u=Ye(()=>{if(a.current){const f=window.getComputedStyle(a.current).direction==="rtl";f!==l&&c(f)}});return F.useEffect(u),F.createElement(L_,{nodeRef:a},F.createElement(H_,{isRTL:l},F.createElement("div",{ref:Nc(i,a),className:Wt(Te("root"),t),...d(r,s),...o},n)))});var Gt;(function(e){e[e.NONE=0]="NONE",e[e.SWIPE=1]="SWIPE",e[e.PULL=2]="PULL",e[e.ANIMATION=3]="ANIMATION"})(Gt||(Gt={}));function Xg(e,t,n,r,s){F.useEffect(()=>s?()=>{}:ys(e(vg,t),e(xg,n),e(wg,r),e(kg,r),e(Cg,r)),[e,t,n,r,s])}var On;(function(e){e[e.NONE=0]="NONE",e[e.SWIPE=1]="SWIPE",e[e.PULL=2]="PULL"})(On||(On={}));const Ac=30;function Z_({disableSwipeNavigation:e,closeOnBackdropClick:t},n,r,s,o,i,a,l,c,d,u,f,g,m,p,y){const b=F.useRef(0),_=F.useRef([]),x=F.useRef(void 0),M=F.useRef(0),C=F.useRef(On.NONE),k=F.useCallback(L=>{x.current===L.pointerId&&(x.current=void 0,C.current=On.NONE);const $=_.current;$.splice(0,$.length,...$.filter(A=>A.pointerId!==L.pointerId))},[]),N=F.useCallback(L=>{k(L),L.persist(),_.current.push(L)},[k]),S=F.useCallback(L=>_.current.find(({pointerId:$})=>L.pointerId===$),[]),w=Ye(L=>{N(L)}),E=(L,$)=>u&&L>$||d&&L<-$,D=Ye(L=>{const $=S(L);if($)if(x.current===L.pointerId){const A=Date.now()-M.current,O=b.current;C.current===On.SWIPE?Math.abs(O)>.3*s||Math.abs(O)>5&&A{const $=S(L);if($){const A=x.current===L.pointerId;if(L.buttons===0){A&&b.current!==0?D(L):k($);return}const O=L.clientX-$.clientX,T=L.clientY-$.clientY;if(x.current===void 0){const z=H=>{N(L),x.current=L.pointerId,M.current=Date.now(),C.current=H};Math.abs(O)>Math.abs(T)&&Math.abs(O)>Ac&&r(O)?e||(z(On.SWIPE),i()):Math.abs(T)>Math.abs(O)&&E(T,Ac)&&(z(On.PULL),f())}else A&&(C.current===On.SWIPE?(b.current=O,a(O)):C.current===On.PULL&&(b.current=T,g(T)))}});Xg(n,w,P,D)}function Q_({preventDefaultWheelX:e,preventDefaultWheelY:t}){const n=F.useRef(null),r=Ye(s=>{const o=Math.abs(s.deltaX)>Math.abs(s.deltaY);(o&&e||!o&&t||s.ctrlKey)&&s.preventDefault()});return F.useCallback(s=>{var o;s?s.addEventListener("wheel",r,{passive:!1}):(o=n.current)===null||o===void 0||o.removeEventListener("wheel",r),n.current=s},[r])}function J_(e,t,n,r,s,o,i,a,l){const c=F.useRef(0),d=F.useRef(0),u=F.useRef(void 0),f=F.useRef(void 0),g=F.useRef(0),m=F.useRef(void 0),p=F.useRef(0),{setTimeout:y,clearTimeout:b}=xo(),_=F.useCallback(()=>{u.current&&(b(u.current),u.current=void 0)},[b]),x=F.useCallback(()=>{f.current&&(b(f.current),f.current=void 0)},[b]),M=Ye(()=>{e!==Gt.SWIPE&&(c.current=0,p.current=0,_(),x())});F.useEffect(M,[e,M]);const C=Ye(N=>{f.current=void 0,c.current===N&&l(c.current)}),k=Ye(N=>{if(N.ctrlKey||Math.abs(N.deltaY)>Math.abs(N.deltaX))return;const S=w=>{g.current=w,b(m.current),m.current=w>0?y(()=>{g.current=0,m.current=void 0},300):void 0};if(e===Gt.NONE){if(Math.abs(N.deltaX)<=1.2*Math.abs(g.current)){S(N.deltaX);return}if(!n(-N.deltaX))return;if(d.current+=N.deltaX,_(),Math.abs(d.current)>30)d.current=0,S(0),p.current=Date.now(),o();else{const w=d.current;u.current=y(()=>{u.current=void 0,w===d.current&&(d.current=0)},s)}}else if(e===Gt.SWIPE){let w=c.current-N.deltaX;if(w=Math.min(Math.abs(w),r)*Math.sign(w),c.current=w,i(w),x(),Math.abs(w)>.2*r){S(N.deltaX),a(w,Date.now()-p.current);return}f.current=y(()=>C(w),2*s)}else S(N.deltaX)});F.useEffect(()=>t(mc,k),[t,k])}const Kg=Er("container"),qg=F.createContext(null),An=un("useController","ControllerContext",qg);function R_({children:e,...t}){var n;const{carousel:r,animation:s,controller:o,on:i,styles:a,render:l}=t,{closeOnPullUp:c,closeOnPullDown:d,preventDefaultWheelX:u,preventDefaultWheelY:f}=o,[g,m]=F.useState(),p=mn(),y=$_(),[b,_]=F.useState(Gt.NONE),x=F.useRef(0),M=F.useRef(0),C=F.useRef(1),{registerSensors:k,subscribeSensors:N}=Gg(),{subscribe:S,publish:w}=ws(),E=Xi(),D=Xi(),P=Xi(),{containerRef:L,setContainerRef:$,containerRect:A}=Bg(),O=Nc(Q_({preventDefaultWheelX:u,preventDefaultWheelY:f}),$),T=F.useRef(null),z=Nc(T,void 0),{getOwnerDocument:H}=xs(),j=Ki(),G=ie=>(j?-1:1)*(typeof ie=="number"?ie:1),U=Ye(()=>{var ie;return(ie=L.current)===null||ie===void 0?void 0:ie.focus()}),W=Ye(()=>t),Y=Ye(()=>p),V=F.useCallback(ie=>w(fr,ie),[w]),X=F.useCallback(ie=>w(hr,ie),[w]),K=F.useCallback(()=>w(bs),[w]),q=ie=>!(r.finite&&(G(ie)>0&&p.currentIndex===0||G(ie)<0&&p.currentIndex===p.slides.length-1)),ne=ie=>{var Me;x.current=ie,(Me=L.current)===null||Me===void 0||Me.style.setProperty(ht("swipe_offset"),`${Math.round(ie)}px`)},ee=ie=>{var Me,Ae;M.current=ie,C.current=(()=>{const gt=d&&ie>0?ie:c&&ie<0?-ie:0;return Math.min(Math.max(yc(1-gt/60*(1-.5),2),.5),1)})(),(Me=L.current)===null||Me===void 0||Me.style.setProperty(ht("pull_offset"),`${Math.round(ie)}px`),(Ae=L.current)===null||Ae===void 0||Ae.style.setProperty(ht("pull_opacity"),`${C.current}`)},{prepareAnimation:ue}=Mc(T,(ie,Me,Ae)=>{if(T.current&&A)return{keyframes:[{transform:`translate(0, ${ie.rect.y-Me.y+Ae.y}px)`,opacity:ie.opacity},{transform:"translate(0, 0)",opacity:1}],duration:ie.duration,easing:s.easing.fade}}),J=(ie,Me)=>{if(c||d){ee(ie);let Ae=0;T.current&&(Ae=s.fade*(Me?2:1),ue({rect:T.current.getBoundingClientRect(),opacity:C.current,duration:Ae})),P(()=>{ee(0),_(Gt.NONE)},Ae),_(Gt.ANIMATION),Me||K()}},{prepareAnimation:ce,isAnimationPlaying:me}=Mc(T,(ie,Me,Ae)=>{var Ie;if(T.current&&A&&(!((Ie=p.animation)===null||Ie===void 0)&&Ie.duration)){const Xe=Gi(r.spacing),gt=(Xe.percent?Xe.percent*A.width/100:Xe.pixel)||0;return{keyframes:[{transform:`translate(${G(p.globalIndex-ie.index)*(A.width+gt)+ie.rect.x-Me.x+Ae.x}px, 0)`},{transform:"translate(0, 0)"}],duration:p.animation.duration,easing:p.animation.easing}}}),le=Ye(ie=>{var Me,Ae;const Ie=ie.offset||0,Xe=Ie?s.swipe:(Me=s.navigation)!==null&&Me!==void 0?Me:s.swipe,gt=!Ie&&!me()?s.easing.navigation:s.easing.swipe;let{direction:pt}=ie;const Be=(Ae=ie.count)!==null&&Ae!==void 0?Ae:1;let et=Gt.ANIMATION,je=Xe*Be;if(!pt){const fe=A?.width,ye=ie.duration||0,we=fe?Xe/fe*Math.abs(Ie):Xe;Be!==0?(ye0?fr:hr):je=Xe/2}let re=0;pt===fr?q(G(1))?re=-Be:(et=Gt.NONE,je=Xe):pt===hr&&(q(G(-1))?re=Be:(et=Gt.NONE,je=Xe)),je=Math.round(je),D(()=>{ne(0),_(Gt.NONE)},je),T.current&&ce({rect:T.current.getBoundingClientRect(),index:p.globalIndex}),_(et),w(gc,{type:"swipe",increment:re,duration:je,easing:gt})});F.useEffect(()=>{var ie,Me;!((ie=p.animation)===null||ie===void 0)&&ie.increment&&(!((Me=p.animation)===null||Me===void 0)&&Me.duration)&&E(()=>y({type:"swipe",increment:0}),p.animation.duration)},[p.animation,y,E]);const ve=[N,q,A?.width||0,s.swipe,()=>_(Gt.SWIPE),ie=>ne(ie),(ie,Me)=>le({offset:ie,duration:Me,count:1}),ie=>le({offset:ie,count:0})],ge=[()=>{d&&_(Gt.PULL)},ie=>ee(ie),ie=>J(ie),ie=>J(ie,!0)];Z_(o,...ve,c,d,...ge,K),J_(b,...ve);const pe=Ye(()=>{o.focus&&H().querySelector(`.${Te(uc)} .${Te(Kg())}`)&&U()});F.useEffect(pe,[pe]);const ke=Ye(()=>{var ie;(ie=i.view)===null||ie===void 0||ie.call(i,{index:p.currentIndex})});F.useEffect(ke,[p.globalIndex,ke]),F.useEffect(()=>ys(S(fr,ie=>le({direction:fr,...ie})),S(hr,ie=>le({direction:hr,...ie})),S(gc,ie=>y(ie))),[S,le,y]);const $e=F.useMemo(()=>({prev:V,next:X,close:K,focus:U,slideRect:A?M_(A,r.padding):{width:0,height:0},containerRect:A||{width:0,height:0},subscribeSensors:N,containerRef:L,setCarouselRef:z,toolbarWidth:g,setToolbarWidth:m}),[V,X,K,U,N,A,L,z,g,m,r.padding]);return F.useImperativeHandle(o.ref,()=>({prev:V,next:X,close:K,focus:U,getLightboxProps:W,getLightboxState:Y}),[V,X,K,U,W,Y]),F.createElement("div",{ref:O,className:Wt(Te(Kg()),Te(ms)),style:{...b===Gt.SWIPE?{[ht("swipe_offset")]:`${Math.round(x.current)}px`}:null,...b===Gt.PULL?{[ht("pull_offset")]:`${Math.round(M.current)}px`,[ht("pull_opacity")]:`${C.current}`}:null,...o.touchAction!=="none"?{[ht("controller_touch_action")]:o.touchAction}:null,...a.container},tabIndex:-1,...k},A&&F.createElement(qg.Provider,{value:$e},e,(n=l.controls)===null||n===void 0?void 0:n.call(l)))}const e2=pn(go,R_);function Tr(e){return _r(fg,e)}function Zg(e){return _r(hc,e)}function t2({slide:e,offset:t}){const n=F.useRef(null),{currentIndex:r,slides:s}=mn(),{slideRect:o,focus:i}=An(),{render:a,carousel:{imageFit:l,imageProps:c},on:{click:d},styles:{slide:u},labels:f}=Lt(),{getOwnerDocument:g}=xs(),m=t!==0;F.useEffect(()=>{var y;m&&(!((y=n.current)===null||y===void 0)&&y.contains(g().activeElement))&&i()},[m,i,g]);const p=()=>{var y,b,_,x;let M=(y=a.slide)===null||y===void 0?void 0:y.call(a,{slide:e,offset:t,rect:o});return!M&&gr(e)&&(M=F.createElement(Nr,{slide:e,offset:t,render:a,rect:o,imageFit:l,imageProps:c,onClick:m?void 0:()=>d?.({index:r})})),M?F.createElement(F.Fragment,null,(b=a.slideHeader)===null||b===void 0?void 0:b.call(a,{slide:e}),((_=a.slideContainer)!==null&&_!==void 0?_:(({children:C})=>C))({slide:e,children:M}),(x=a.slideFooter)===null||x===void 0?void 0:x.call(a,{slide:e})):null};return F.createElement("div",{ref:n,className:Wt(Te(Zg()),!m&&Te(Zg("current")),Te(ms)),...O_(m),style:u,role:"group","aria-roledescription":Mr(f,"Slide"),"aria-label":Ng(f,s,r+t)},p())}function n2(){const e=Lt().styles.slide;return F.createElement("div",{className:Te(hc),style:e})}function r2({carousel:e,labels:t}){const{slides:n,currentIndex:r,globalIndex:s}=mn(),{setCarouselRef:o}=An(),{autoPlaying:i,focusWithin:a}=_c(),l=Gi(e.spacing),c=Gi(e.padding),d=Og(e,n,1),u=[];if(wc(n))for(let f=r-d;f<=r+d;f+=1){const g=kc(n,f),m=s-r+f,p=e.finite&&(f<0||f>n.length-1);u.push(p?{key:m}:{key:[`${m}`,Tg(g)].filter(Boolean).join("|"),offset:f-r,slide:g})}return F.createElement("div",{ref:o,className:Wt(Te(Tr()),u.length>0&&Te(Tr("with_slides"))),style:{[`${ht(Tr("slides_count"))}`]:u.length,[`${ht(Tr("spacing_px"))}`]:l.pixel||0,[`${ht(Tr("spacing_percent"))}`]:l.percent||0,[`${ht(Tr("padding_px"))}`]:c.pixel||0,[`${ht(Tr("padding_percent"))}`]:c.percent||0},role:"region","aria-live":i&&!a?"off":"polite","aria-roledescription":Mr(t,"Carousel"),"aria-label":Mr(t,"Photo gallery")},u.map(({key:f,slide:g,offset:m})=>g?F.createElement(t2,{key:f,slide:g,offset:m}):F.createElement(n2,{key:f})))}const s2=pn(fg,r2);function Qg(){const{carousel:e}=Lt(),{slides:t,currentIndex:n}=mn(),r=t.length===0||e.finite&&n===0,s=t.length===0||e.finite&&n===t.length-1;return{prevDisabled:r,nextDisabled:s}}function Jg(e){var t;const n=Ki(),{publish:r}=ws(),{animation:s,controller:o}=Lt(),{prevDisabled:i,nextDisabled:a}=Qg(),l=((t=s.navigation)!==null&&t!==void 0?t:s.swipe)/2,c=Yg(()=>r(fr),l),d=Yg(()=>r(hr),l),u=Ye(f=>{switch(f.key){case C_:if(!o.closeOnEscape)return;r(bs);break;case S_:(n?a:i)||(n?d:c)();break;case __:(n?i:a)||(n?c:d)();break;default:return}f.stopPropagation()});F.useEffect(()=>e(pc,u),[e,u])}function Rg({label:e,icon:t,renderIcon:n,action:r,onClick:s,disabled:o,style:i}){return F.createElement(ks,{label:e,icon:t,renderIcon:n,className:Te(`navigation_${r}`),disabled:o,onClick:s,style:i,...Wg(An().focus,o)})}function o2({render:{buttonPrev:e,buttonNext:t,iconPrev:n,iconNext:r},styles:s}){const{prev:o,next:i,subscribeSensors:a}=An(),{prevDisabled:l,nextDisabled:c}=Qg();return Jg(a),F.createElement(F.Fragment,null,e?e():F.createElement(Rg,{label:"Previous",action:fr,icon:W_,renderIcon:n,style:s.navigationPrev,disabled:l,onClick:o}),t?t():F.createElement(Rg,{label:"Next",action:hr,icon:G_,renderIcon:r,style:s.navigationNext,disabled:c,onClick:i}))}const i2=pn(u_,o2),ep=Te(x_),a2=Te(bg);function l2(e){return"style"in e}function tp(e,t,n){const r=window.getComputedStyle(e),s=n?"padding-left":"padding-right",o=n?r.paddingLeft:r.paddingRight,i=e.style.getPropertyValue(s);return e.style.setProperty(s,`${(Wi(o)||0)+t}px`),()=>{i?e.style.setProperty(s,i):e.style.removeProperty(s)}}function c2({noScroll:{disabled:e},children:t}){const n=Ki(),{getOwnerDocument:r,getOwnerWindow:s}=xs();return F.useEffect(()=>{if(e)return()=>{};const o=[],i=s(),{body:a,documentElement:l}=r(),c=Math.round(i.innerWidth-l.clientWidth);if(c>0){o.push(tp(a,c,n));const d=a.getElementsByTagName("*");for(let u=0;u{a.classList.remove(ep),o.forEach(d=>d())}},[n,e,r,s]),F.createElement(F.Fragment,null,t)}const d2=pn(f_,c2);function np(e){return _r(uc,e)}function rp(e,t,n){const r=e.getAttribute(t);return e.setAttribute(t,n),()=>{r?e.setAttribute(t,r):e.removeAttribute(t)}}function u2({portal:{root:e,container:{className:t,style:n,...r}={}},animation:s,styles:o,className:i,on:a,close:l,labels:c,children:d}){const[u,f]=F.useState(!1),[g,m]=F.useState(!1),p=F.useRef([]),y=F.useRef(null),{setTimeout:b}=xo(),{subscribe:_}=ws(),M=Ec()?0:s.fade;F.useEffect(()=>(f(!0),()=>{f(!1),m(!1)}),[]);const C=Ye(()=>{p.current.forEach(w=>w()),p.current=[]}),k=Ye(()=>{var w;m(!1),C(),(w=a.exiting)===null||w===void 0||w.call(a),b(()=>{var E;(E=a.exited)===null||E===void 0||E.call(a),l()},M)});F.useEffect(()=>_(bs,k),[_,k]);const N=Ye(w=>{var E,D,P;A_(w),m(!0),(E=a.entering)===null||E===void 0||E.call(a);const L=(P=(D=w.parentNode)===null||D===void 0?void 0:D.children)!==null&&P!==void 0?P:[];for(let $=0;${var $,A;(A=($=y.current)===null||$===void 0?void 0:$.focus)===null||A===void 0||A.call($)}),b(()=>{var $;($=a.entered)===null||$===void 0||$.call(a)},M)}),S=F.useCallback(w=>{w?N(w):C()},[N,C]);return u?$n.createPortal(F.createElement(q_,{ref:S,className:Wt(i,t,Te(np()),Te(bg),g&&Te(np("open"))),"aria-modal":!0,role:"dialog","aria-label":Mr(c,"Lightbox"),style:{...s.fade!==Sc.animation.fade?{[ht("fade_animation_duration")]:`${M}ms`}:null,...s.easing.fade!==Sc.animation.easing.fade?{[ht("fade_animation_timing_function")]:s.easing.fade}:null,...o.root,...n},onFocus:w=>{y.current||(y.current=w.relatedTarget)},...r},d),(typeof e=="function"?e():e)||document.body):null}const f2=pn(uc,u2);function h2({children:e}){return F.createElement(F.Fragment,null,e)}const g2=pn(h_,h2);function p2(e){return _r(hg,e)}function m2({toolbar:{buttons:e},render:{buttonClose:t,iconClose:n},styles:r}){const{close:s,setToolbarWidth:o}=An(),{setContainerRef:i,containerRect:a}=Bg();Tn(()=>{o(a?.width)},[o,a?.width]);const l=()=>t?t():F.createElement(ks,{key:bs,label:"Close",icon:j_,renderIcon:n,onClick:s});return F.createElement("div",{ref:i,style:r.toolbar,className:Te(p2())},e?.map(c=>c===bs?l():c))}const b2=pn(hg,m2);function sp(e,t){var n;return F.createElement(e.module.component,{key:e.module.name,...t},(n=e.children)===null||n===void 0?void 0:n.map(r=>sp(r,t)))}function y2(e,t={}){const{easing:n,...r}=e,{easing:s,...o}=t;return{easing:{...n,...s},...r,...o}}function v2({carousel:e,animation:t,render:n,toolbar:r,controller:s,noScroll:o,on:i,plugins:a,slides:l,index:c,...d}){const{animation:u,carousel:f,render:g,toolbar:m,controller:p,noScroll:y,on:b,slides:_,index:x,plugins:M,...C}=Sc,{config:k,augmentation:N}=D_([It(f2,[It(d2,[It(e2,[It(s2),It(b2),It(i2)])])])],a||M),S=N({animation:y2(u,t),carousel:{...f,...e},render:{...g,...n},toolbar:{...m,...r},controller:{...p,...s},noScroll:{...y,...o},on:{...b,...i},...C,...d});return S.open?F.createElement($g,{...S},F.createElement(z_,{slides:l||_,index:Wi(c||x)},F.createElement(U_,null,F.createElement(P_,null,F.createElement(I_,null,sp(It(g2,k),S)))))):null}const op={minZoom:1,maxZoomPixelRatio:1,zoomInMultiplier:2,doubleTapDelay:300,doubleClickDelay:500,doubleClickMaxStops:2,keyboardMoveDistance:50,wheelZoomDistanceFactor:100,pinchZoomDistanceFactor:100,pinchZoomV4:!1,scrollToZoom:!1,maxZoom:8};function x2(e){return Math.min(Math.max(e,Number.EPSILON),1)}function ip(e){const{minZoom:t,...n}={...op,...e};return{minZoom:x2(t),...n}}function w2(e,t,n,r){const s=F.useRef(void 0),o=F.useRef(void 0),{zoom:i}=Lt().animation,a=Ec(),l=Ye(()=>{var c,d,u;if((c=s.current)===null||c===void 0||c.cancel(),s.current=void 0,o.current&&r?.current){try{s.current=(u=(d=r.current).animate)===null||u===void 0?void 0:u.call(d,[{transform:o.current},{transform:`scale(${e}) translateX(${t}px) translateY(${n}px)`}],{duration:a?0:i??500,easing:s.current?"ease-out":"ease-in-out"})}catch(f){console.error(f)}o.current=void 0,s.current&&(s.current.onfinish=()=>{s.current=void 0})}});return Tn(l,[e,t,n,l]),F.useCallback(()=>{o.current=r?.current?window.getComputedStyle(r.current).transform:void 0},[r])}function k2(e,t){const{on:n}=Lt(),r=Ye(()=>{var s;t||(s=n.zoom)===null||s===void 0||s.call(n,{zoom:e})});F.useEffect(r,[e,r])}function qi(){const{zoom:e}=Lt();return ip(e)}function C2(e,t){var n;const r=typeof e=="function"?(n=e(t))!==null&&n!==void 0?n:op.maxZoom:e;return Math.max(r,1)}function S2(e,t){var n,r;let s={width:0,height:0},o={width:0,height:0};const{currentSlide:i}=mn(),{imageFit:a}=Lt().carousel,{maxZoomPixelRatio:l,maxZoom:c}=qi();if(e&&i){const u={...i,...t};if(gr(u)){const f=vc(u,a),g=Math.max(...(((n=u.srcSet)===null||n===void 0?void 0:n.map(p=>p.width))||[]).concat(u.width?[u.width]:[])),m=Math.max(...(((r=u.srcSet)===null||r===void 0?void 0:r.map(p=>p.height))||[]).concat(u.height?[u.height]:[]));g>0&&m>0&&e.width>0&&e.height>0&&(o=f?{width:Math.round(Math.min(g,e.width/e.height*m)),height:Math.round(Math.min(m,e.height/e.width*g))}:{width:g,height:m},o={width:o.width*l,height:o.height*l},s=f?{width:Math.min(e.width,o.width,g),height:Math.min(e.height,o.height,m)}:{width:Math.round(Math.min(e.width,e.height/m*g,g)),height:Math.round(Math.min(e.height,e.width/g*m,m))})}else e.width>0&&e.height>0&&(t&&t.width>0&&t.height>0?s={width:Math.min(e.width,t.width),height:Math.min(e.height,t.height)}:s={width:e.width,height:e.height})}const d=i&&s.width?gr(i)?Math.max(yc(o.width/s.width,5),1):C2(c,i):1;return{imageRect:s,maxZoom:d}}function ap(e,t){return Math.hypot(e.clientX-t.clientX,e.clientY-t.clientY)}function lp(e,t,n=100,r=2){return e*Math.min(1+Math.abs(t/n),r)**Math.sign(t)}function _2(e,t,n,r,s,o,i,a,l){const c=F.useRef([]),d=F.useRef(0),u=F.useRef(void 0),{globalIndex:f}=mn(),{getOwnerWindow:g}=xs(),{containerRef:m,subscribeSensors:p}=An(),{keyboardMoveDistance:y,zoomInMultiplier:b,wheelZoomDistanceFactor:_,scrollToZoom:x,doubleTapDelay:M,doubleClickDelay:C,doubleClickMaxStops:k,pinchZoomDistanceFactor:N,pinchZoomV4:S}=qi(),w=F.useCallback(z=>{if(m.current){const{pageX:H,pageY:j}=z,{scrollX:G,scrollY:U}=g(),{left:W,top:Y,width:V,height:X}=m.current.getBoundingClientRect();return[H-W-G-V/2,j-Y-U-X/2]}return[]},[m,g]),E=Ye(z=>{const{key:H,metaKey:j,ctrlKey:G}=z,U=j||G,W=()=>{z.preventDefault(),z.stopPropagation()};if(e>1){const Y=(V,X)=>{W(),a(V,X)};H==="ArrowDown"?Y(0,y):H==="ArrowUp"?Y(0,-y):H==="ArrowLeft"?Y(-y,0):H==="ArrowRight"&&Y(y,0)}H==="+"||U&&H==="="?(W(),s()):H==="-"||U&&H==="_"?(W(),o()):U&&H==="0"&&(W(),i(1))}),D=Ye(z=>{if((z.ctrlKey||x)&&Math.abs(z.deltaY)>Math.abs(z.deltaX)){z.stopPropagation(),i(lp(e,-z.deltaY,_),!0,...w(z));return}e>1&&(z.stopPropagation(),x||a(z.deltaX,z.deltaY))}),P=F.useCallback(z=>{const H=c.current;H.splice(0,H.length,...H.filter(j=>j.pointerId!==z.pointerId))},[]),L=F.useCallback(z=>{P(z),z.persist(),c.current.push(z)},[P]),$=Ye(z=>{var H;const j=c.current;if(z.pointerType==="mouse"&&z.buttons>1||!(!((H=l?.current)===null||H===void 0)&&H.contains(z.target)))return;e>1&&z.stopPropagation();const{timeStamp:G}=z;if(j.length===0&&G-d.current<(z.pointerType==="touch"?M:C)){d.current=0;const U=e>=1?e!==n?e*Math.max(n**(1/k),b):1:e!==t?e/Math.max(t**(-1/k),b):1;i(U,!1,...w(z))}else d.current=G;if(L(z),j.length===2){const U=ap(j[0],j[1]);u.current={previousDistance:U,initialDistance:Math.max(U,1),initialZoom:e}}}),A=Ye(z=>{const H=c.current,j=H.find(G=>G.pointerId===z.pointerId);if(H.length===2&&u.current){z.stopPropagation(),L(z);const G=ap(H[0],H[1]),U=S?u.current.initialZoom/u.current.initialDistance*G:lp(e,G-u.current.previousDistance,N);i(U,!0,...H.map(W=>w(W)).reduce((W,Y)=>Y.map((V,X)=>W[X]+V/2))),u.current.previousDistance=G;return}e>1&&(z.stopPropagation(),j&&(H.length===1&&a((j.clientX-z.clientX)/e,(j.clientY-z.clientY)/e),L(z)))}),O=F.useCallback(z=>{const H=c.current;H.length===2&&H.find(j=>j.pointerId===z.pointerId)&&(u.current=void 0),P(z)},[P]),T=F.useCallback(()=>{const z=c.current;z.splice(0,z.length),d.current=0,u.current=void 0},[]);Xg(p,$,A,O,r),F.useEffect(T,[f,T]),F.useEffect(()=>r?()=>{}:ys(T,p(pc,E),p(mc,D)),[r,p,T,E,D])}function E2(e,t,n){const[r,s]=F.useState(1),[o,i]=F.useState(0),[a,l]=F.useState(0),c=w2(r,o,a,n),{currentSlide:d,globalIndex:u}=mn(),{containerRect:f,slideRect:g}=An(),{minZoom:m,zoomInMultiplier:p}=qi(),y=d&&gr(d)?d.src:void 0,b=!n?.current;Tn(()=>{s(1),i(0),l(0)},[u,y]);const _=F.useCallback((N,S,w)=>{const E=w||r,D=o-(N||0),P=a-(S||0),L=(e.width*E-g.width)/2/E,$=(e.height*E-g.height)/2/E;i(Math.min(Math.abs(D),Math.max(L,0))*Math.sign(D)),l(Math.min(Math.abs(P),Math.max($,0))*Math.sign(P))},[r,o,a,g,e.width,e.height]),x=F.useCallback((N,S,w,E)=>{const D=yc(N+.01m?N:m:t,5);S||c(),_(w?w*(1/r-1/D):0,E?E*(1/r-1/D):0,D),s(D)},[r,m,t,_,c]),M=Ye(()=>{r>1&&(r>t&&x(t,!0),_())});Tn(M,[f.width,f.height,M]);const C=F.useCallback(()=>{const N=r*p;x(r<1&&N>1?1:N)},[r,p,x]),k=F.useCallback(()=>{const N=r/p;x(r>1&&N<1?1:N)},[r,p,x]);return{zoom:r,offsetX:o,offsetY:a,disabled:b,changeOffsets:_,changeZoom:x,zoomIn:C,zoomOut:k}}const cp=F.createContext(null),Dc=un("useZoom","ZoomControllerContext",cp);function M2({children:e}){const[t,n]=F.useState(),{slideRect:r}=An(),{ref:s,minZoom:o}=qi(),{imageRect:i,maxZoom:a}=S2(r,t?.imageDimensions),{zoom:l,offsetX:c,offsetY:d,disabled:u,changeZoom:f,changeOffsets:g,zoomIn:m,zoomOut:p}=E2(i,a,t?.zoomWrapperRef);k2(l,u),_2(l,o,a,u,m,p,f,g,t?.zoomWrapperRef);const y=F.useMemo(()=>({zoom:l,minZoom:o,maxZoom:a,offsetX:c,offsetY:d,disabled:u,zoomIn:m,zoomOut:p,changeZoom:f}),[l,o,a,c,d,u,m,p,f]);F.useImperativeHandle(s,()=>y,[y]);const b=F.useMemo(()=>({...y,setZoomWrapper:n}),[y,n]);return F.createElement(cp.Provider,{value:b},e)}const N2=nn("ZoomIn",F.createElement(F.Fragment,null,F.createElement("path",{d:"M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"}),F.createElement("path",{d:"M12 10h-2v2H9v-2H7V9h2V7h1v2h2v1z"}))),T2=nn("ZoomOut",F.createElement("path",{d:"M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14zM7 9h5v1H7z"})),dp=F.forwardRef(function({zoomIn:t,onLoseFocus:n},r){const s=F.useRef(!1),o=F.useRef(!1),{zoom:i,minZoom:a,maxZoom:l,zoomIn:c,zoomOut:d,disabled:u}=Dc(),{render:f}=Lt(),g=u||(t?i>=l:i<=a);return F.useEffect(()=>{g&&s.current&&o.current&&n(),g||(s.current=!0)},[g,n]),F.createElement(ks,{ref:r,disabled:g,label:t?"Zoom in":"Zoom out",icon:t?N2:T2,renderIcon:t?f.iconZoomIn:f.iconZoomOut,onClick:t?c:d,onFocus:()=>{o.current=!0},onBlur:()=>{o.current=!1}})});function O2(){const e=F.useRef(null),t=F.useRef(null),{focus:n}=An(),r=F.useCallback(i=>{var a,l;!((a=i.current)===null||a===void 0)&&a.disabled?n():(l=i.current)===null||l===void 0||l.focus()},[n]),s=F.useCallback(()=>r(e),[r]),o=F.useCallback(()=>r(t),[r]);return F.createElement(F.Fragment,null,F.createElement(dp,{zoomIn:!0,ref:e,onLoseFocus:o}),F.createElement(dp,{ref:t,onLoseFocus:s}))}function A2(){const{render:e}=Lt(),t=Dc();return e.buttonZoom?F.createElement(F.Fragment,null,e.buttonZoom(t)):F.createElement(O2,null)}function D2(e){var t;return(((t=e.srcSet)===null||t===void 0?void 0:t.length)||0)>0}function I2({current:e,preload:t},{type:n,source:r}){switch(n){case"fetch":return e?{current:e,preload:r}:{current:r};case"done":return r===t?{current:r}:{current:e,preload:t};default:throw new Error(Eg)}}function L2(e){var t,n;const[{current:r,preload:s},o]=F.useReducer(I2,{}),{slide:i,rect:a,imageFit:l,render:c,interactive:d}=e,u=i.srcSet.sort((C,k)=>C.width-k.width),f=(t=i.width)!==null&&t!==void 0?t:u[u.length-1].width,g=(n=i.height)!==null&&n!==void 0?n:u[u.length-1].height,m=vc(i,l),p=Math.max(...u.map(C=>C.width)),y=Math.min((m?Math.max:Math.min)(a.width,f*(a.height/g)),p),b=N_(),_=Ye(()=>{var C;const k=(C=u.find(N=>N.width>=y*b))!==null&&C!==void 0?C:u[u.length-1];(!r||u.findIndex(N=>N.src===r)N===k))&&o({type:"fetch",source:k.src})});Tn(_,[a.width,a.height,b,_]);const x=Ye(C=>o({type:"done",source:C})),M={WebkitTransform:d?"initial":"translateZ(0)"};return m||Object.assign(M,a.width/a.heightx(s),render:{...c,iconLoading:()=>null,iconError:()=>null}}),r&&F.createElement(Nr,{key:"current",...e,slide:{...i,src:r,srcSet:void 0},style:M}))}function P2({render:e,slide:t,offset:n,rect:r}){var s;const[o,i]=F.useState(),a=F.useRef(null),l=gr(t),{zoom:c,maxZoom:d,offsetX:u,offsetY:f,setZoomWrapper:g}=Dc(),m=c>1,{carousel:p,on:y}=Lt(),{currentIndex:b}=mn();Tn(()=>{if(n!==0||l||!a.current)return()=>{};const x=()=>{const C=a.current;if(!C)return;let k=0,N=0;for(const S of C.children)S instanceof HTMLElement&&(k=Math.max(k,S.offsetWidth),N=Math.max(N,S.offsetHeight));i(S=>S&&S.width===k&&S.height===N?S:{width:k,height:N})};if(x(),typeof ResizeObserver>"u")return()=>{};const M=new ResizeObserver(x);for(const C of a.current.children)M.observe(C);return()=>M.disconnect()},[n,l,r]),Tn(()=>n===0?(g({zoomWrapperRef:a,imageDimensions:o}),()=>g(void 0)):()=>{},[n,o,g]);let _=(s=e.slide)===null||s===void 0?void 0:s.call(e,{slide:t,offset:n,rect:r,zoom:c,maxZoom:d});if(!_&&l){const x={slide:t,offset:n,rect:r,render:e,imageFit:p.imageFit,imageProps:p.imageProps,onClick:n===0?()=>{var M;return(M=y.click)===null||M===void 0?void 0:M.call(y,{index:b})}:void 0};_=D2(t)?F.createElement(L2,{...x,slide:t,interactive:m,rect:n===0?{width:r.width*c,height:r.height*c}:r}):F.createElement(Nr,{onLoad:M=>i({width:M.naturalWidth,height:M.naturalHeight}),...x})}return _?F.createElement("div",{ref:a,className:Wt(Te(mg),Te(ms),Te(yg),m&&Te(w_)),style:n===0?{transform:`scale(${c}) translateX(${u}px) translateY(${f}px)`}:void 0},_):null}const $2=({augment:e,addModule:t})=>{e(({zoom:n,toolbar:r,render:s,controller:o,...i})=>{const a=ip(n);return{zoom:a,toolbar:Yi(r,pg,F.createElement(A2,null)),render:{...s,slide:l=>{var c,d;return gr(l.slide)||l.slide.type!=null&&(!((c=a.supports)===null||c===void 0)&&c.includes(l.slide.type))?F.createElement(P2,{render:s,...l}):(d=s.slide)===null||d===void 0?void 0:d.call(s,l)}},controller:{...o,preventDefaultWheelY:a.scrollToZoom},...i}}),t(pn(pg,M2))},F2={separator:"/",container:{}},up=e=>({...F2,...e});function z2({counter:e}){const{slides:t,currentIndex:n}=mn(),{separator:r,container:{className:s,...o},className:i,...a}=up(e);return t.length===0?null:F.createElement("div",{className:Wt(Te("counter"),s||i),...a,...o,"aria-hidden":!0},n+1," ",r," ",t.length)}function V2({augment:e,addChild:t}){e(({counter:n,...r})=>({counter:up(n),...r})),t(go,pn(g_,z2))}const H2={autoplay:!1,delay:3e3,ref:null},fp=e=>({...H2,...e}),hp=F.createContext(null),U2=un("useSlideshow","SlideshowContext",hp);function B2({slideshow:e,carousel:{finite:t},on:n,children:r}){const{autoplay:s,delay:o,ref:i}=fp(e),a=F.useRef(s),[l,c]=F.useState(s),d=F.useRef(void 0),u=F.useRef(void 0),{slides:f,currentIndex:g}=mn(),{setTimeout:m,clearTimeout:p}=xo(),{subscribe:y}=ws(),{next:b}=An(),{setAutoPlaying:_}=_c();F.useEffect(()=>_(l),[l,_]);const x=f.length===0||t&&g===f.length-1,M=F.useCallback(()=>{!l&&!x&&c(!0)},[l,x]),C=F.useCallback(()=>{l&&c(!1)},[l]),k=F.useCallback(()=>{p(d.current),d.current=void 0},[p]),N=Ye(()=>{k(),!(!l||x||u.current===ps||u.current===fc)&&(d.current=m(()=>{l&&(u.current=void 0,b())},o))});F.useEffect(N,[g,l,N]),F.useEffect(()=>{l&&x&&c(!1)},[g,l,x]);const S=Ye(()=>{var D;return(D=n.slideshowStart)===null||D===void 0?void 0:D.call(n)}),w=Ye(()=>{var D;return(D=n.slideshowStop)===null||D===void 0?void 0:D.call(n)});F.useEffect(()=>{l?S():a.current&&w(),a.current=l},[l,S,w]),F.useEffect(()=>ys(k,y(m_,()=>{u.current=ps,k()}),y(b_,()=>{u.current=fc,k()}),y(y_,()=>{u.current=bo,N()}),y(v_,()=>{u.current=yo,N()})),[y,k,N]);const E=F.useMemo(()=>({playing:l,disabled:x,play:M,pause:C}),[l,x,M,C]);return F.useImperativeHandle(i,()=>E,[E]),F.createElement(hp.Provider,{value:E},r)}const j2=nn("Play",F.createElement("path",{d:"M8 5v14l11-7z"})),W2=nn("Pause",F.createElement("path",{d:"M6 19h4V5H6v14zm8-14v14h4V5h-4z"}));function G2(){const{playing:e,disabled:t,play:n,pause:r}=U2(),{render:s}=Lt(),o=Wg(An().focus,t);return s.buttonSlideshow?F.createElement(F.Fragment,null,s.buttonSlideshow({playing:e,disabled:t,play:n,pause:r})):F.createElement(ks,{label:e?"Pause":"Play",icon:e?W2:j2,renderIcon:e?s.iconSlideshowPause:s.iconSlideshowPlay,onClick:e?r:n,disabled:t,...o})}function Y2({augment:e,addModule:t}){e(({slideshow:n,toolbar:r,...s})=>({toolbar:Yi(r,gg,F.createElement(G2,null)),slideshow:fp(n),...s})),t(pn(gg,B2))}const X2={auto:!1,ref:null},gp=e=>({...X2,...e}),pp=F.createContext(null),K2=un("useFullscreen","FullscreenContext",pp);function q2({fullscreen:e,on:t,children:n}){const{auto:r,ref:s}=gp(e),o=F.useRef(null),[i,a]=F.useState(),[l,c]=F.useState(!1),d=F.useRef(!1),{getOwnerDocument:u}=xs();Tn(()=>{var x,M,C,k;const N=u();a(!((k=(C=(M=(x=N.fullscreenEnabled)!==null&&x!==void 0?x:N.webkitFullscreenEnabled)!==null&&M!==void 0?M:N.mozFullScreenEnabled)!==null&&C!==void 0?C:N.msFullscreenEnabled)!==null&&k!==void 0&&k))},[u]);const f=F.useCallback(()=>{var x;const M=u(),C=M.fullscreenElement||M.webkitFullscreenElement||M.mozFullScreenElement||M.msFullscreenElement;return((x=C?.shadowRoot)===null||x===void 0?void 0:x.fullscreenElement)||C},[u]),g=F.useCallback(()=>{const x=o.current;try{x.requestFullscreen?x.requestFullscreen().catch(()=>{}):x.webkitRequestFullscreen?x.webkitRequestFullscreen():x.mozRequestFullScreen?x.mozRequestFullScreen():x.msRequestFullscreen&&x.msRequestFullscreen()}catch{}},[]),m=F.useCallback(()=>{if(!f())return;const x=u();try{x.exitFullscreen?x.exitFullscreen().catch(()=>{}):x.webkitExitFullscreen?x.webkitExitFullscreen():x.mozCancelFullScreen?x.mozCancelFullScreen():x.msExitFullscreen&&x.msExitFullscreen()}catch{}},[f,u]);F.useEffect(()=>{const x=u(),M=()=>{c(f()===o.current)};return ys(...["fullscreenchange","webkitfullscreenchange","mozfullscreenchange","MSFullscreenChange"].map(C=>(x.addEventListener(C,M),()=>x.removeEventListener(C,M))))},[f,u]);const p=Ye(()=>{var x;return(x=t.enterFullscreen)===null||x===void 0?void 0:x.call(t)}),y=Ye(()=>{var x;return(x=t.exitFullscreen)===null||x===void 0?void 0:x.call(t)});F.useEffect(()=>{l&&(d.current=!0),d.current&&(l?p:y)()},[l,p,y]);const b=Ye(()=>{var x;return(x=r?g:null)===null||x===void 0||x(),m});F.useEffect(b,[b]);const _=F.useMemo(()=>({fullscreen:l,disabled:i,enter:g,exit:m}),[l,i,g,m]);return F.useImperativeHandle(s,()=>_,[_]),F.createElement("div",{ref:o,className:Wt(Te(po),Te(mg))},F.createElement(pp.Provider,{value:_},n))}const Z2=nn("EnterFullscreen",F.createElement("path",{d:"M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z"})),Q2=nn("ExitFullscreen",F.createElement("path",{d:"M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z"}));function J2(){var e;const{fullscreen:t,disabled:n,enter:r,exit:s}=K2(),{render:o}=Lt();return n?null:o.buttonFullscreen?F.createElement(F.Fragment,null,(e=o.buttonFullscreen)===null||e===void 0?void 0:e.call(o,{fullscreen:t,disabled:n,enter:r,exit:s})):F.createElement(ks,{disabled:n,label:t?"Exit Fullscreen":"Enter Fullscreen",icon:t?Q2:Z2,renderIcon:t?o.iconExitFullscreen:o.iconEnterFullscreen,onClick:t?s:r})}function R2({augment:e,contains:t,addParent:n}){e(({fullscreen:r,toolbar:s,...o})=>({toolbar:Yi(s,po,F.createElement(J2,null)),fullscreen:gp(r),...o})),n(t(mo)?mo:go,pn(po,q2))}const Or={ref:null,position:"bottom",width:120,height:80,border:1,borderRadius:4,padding:4,gap:16,imageFit:"contain",vignette:!0,hidden:!1,showToggle:!1},Ic=e=>({...Or,...e});function mp(){const{thumbnails:e}=Lt();return Ic(e)}const Ar=e=>_r(mo,e),Pt=e=>Ar(_r("thumbnail",e)),eE=nn("VideoThumbnail",F.createElement("path",{d:"M10 16.5l6-4.5-6-4.5v9zM12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"})),tE=nn("UnknownThumbnail",F.createElement("path",{d:"M23 18V6c0-1.1-.9-2-2-2H3c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2zM8.5 12.5l2.5 3.01L14.5 11l4.5 6H5l3.5-4.5z"}));function nE({slide:e,render:t,rect:n,imageFit:r}){var s;const o=(s=t.thumbnail)===null||s===void 0?void 0:s.call(t,{slide:e,render:t,rect:n,imageFit:r});if(o)return o;const i={render:t,rect:n,imageFit:r};if(e.thumbnail)return F.createElement(Nr,{slide:{src:e.thumbnail},...i});if(gr(e))return F.createElement(Nr,{slide:e,...i});const a=Te(Pt(ji));return e.type==="video"?F.createElement(F.Fragment,null,e.poster&&F.createElement(Nr,{slide:{src:e.poster},...i}),F.createElement(eE,{className:a})):F.createElement(tE,{className:a})}const rE=Er("active"),Lc=Er("fadein"),Pc=Er("fadeout"),sE=Er("placeholder"),bp="delay",yp="duration";function oE({slide:e,index:t,onClick:n,fadeIn:r,fadeOut:s,placeholder:o,onLoseFocus:i}){const a=F.useRef(null),{render:l,styles:c,labels:d}=Lt(),{slides:u,globalIndex:f}=mn(),{getOwnerDocument:g}=xs(),{width:m,height:p,imageFit:y}=mp(),b={width:m,height:p},_=t===f,x=Ye(i);return F.useEffect(()=>{s&&g().activeElement===a.current&&x()},[s,x,g]),F.createElement("button",{ref:a,type:"button",className:Wt(Te(ms),Te(Pt()),_&&Te(Pt(rE())),r&&Te(Pt(Lc())),s&&Te(Pt(Pc())),o&&Te(Pt(sE()))),style:{...r?{[ht(Pt(Lc(yp)))]:`${r.duration}ms`,[ht(Pt(Lc(bp)))]:`${r.delay}ms`}:null,...s?{[ht(Pt(Pc(yp)))]:`${s.duration}ms`,[ht(Pt(Pc(bp)))]:`${s.delay}ms`}:null,...c.thumbnail},onClick:n,"aria-current":_?!0:void 0,"aria-label":Ng(d,u,t)},e&&nE({slide:e,render:l,rect:b,imageFit:y}))}function vp(e){return["top","bottom"].includes(e)}function xp(e,t){return t+2*(e.border+e.padding)+e.gap}function iE(e){const{thumbnail:t,poster:n}=e||{thumbnail:"placeholder"};return typeof t=="string"&&t||typeof n=="string"&&n||e&&Tg(e)||void 0}function wp({visible:e,containerRef:t}){const n=F.useRef(null),r=Ki(),{publish:s,subscribe:o}=ws(),{carousel:i,styles:a,labels:l}=Lt(),{slides:c,globalIndex:d,animation:u}=mn(),{registerSensors:f,subscribeSensors:g}=Gg();Jg(g);const m=mp(),{position:p,width:y,height:b,border:_,borderStyle:x,borderColor:M,borderRadius:C,padding:k,gap:N,vignette:S}=m,w=u?.duration!==void 0&&u?.increment||0,E=u?.duration||0,{prepareAnimation:D}=Mc(n,O=>({keyframes:vp(p)?[{transform:`translateX(${(r?-1:1)*xp(m,y)*w+O}px)`},{transform:"translateX(0)"}]:[{transform:`translateY(${xp(m,b)*w+O}px)`},{transform:"translateY(0)"}],duration:E,easing:u?.easing})),P=Ye(()=>{let O=0;if(t.current&&n.current){const T=t.current.getBoundingClientRect(),z=n.current.getBoundingClientRect();O=vp(p)?z.left-T.left-(T.width-z.width)/2:z.top-T.top-(T.height-z.height)/2}D(O)});F.useEffect(()=>ys(o(gc,P)),[o,P]);const L=Og(i,c),$=[];if(wc(c))for(let O=d-L-Math.abs(w);O<=d+L+Math.abs(w);O+=1){const z=i.finite&&(O<0||O>c.length-1)||w<0&&O0&&O>d+L?null:kc(c,O),H=[`${O}`,iE(z)].filter(Boolean).join("|");$.push({key:H,index:O,slide:z})}const A=O=>()=>{O>d?s(hr,{count:O-d}):O{const H=E/Math.abs(w||1),j=w>0&&T>d+L-w&&T<=d+L||w<0&&T=d-L?{duration:H,delay:((w>0?T-(d+L-w):d-L-w-T)-1)*H}:void 0,G=w>0&&Td+L?{duration:H,delay:(w>0?w-(d-L-T):-w-(T-(d+L)))*H}:void 0;return F.createElement(oE,{key:O,index:T,slide:z,fadeIn:j,fadeOut:G,placeholder:!z,onClick:A(T),onLoseFocus:()=>{var U;return(U=n.current)===null||U===void 0?void 0:U.focus()}})})),S&&F.createElement("div",{className:Te(Ar("vignette"))}))}const kp=F.createContext(null),aE=un("useThumbnails","ThumbnailsContext",kp);function lE({children:e,...t}){const{ref:n,position:r,hidden:s}=Ic(t.thumbnails),[o,i]=F.useState(!s),a=F.useRef(null),l=F.useMemo(()=>({visible:o,show:()=>i(!0),hide:()=>i(!1)}),[o]);return F.useImperativeHandle(n,()=>l,[l]),F.createElement($g,{...t},F.createElement(kp.Provider,{value:l},F.createElement("div",{ref:a,className:Wt(Te(Ar()),Te(Ar(`${r}`)))},["start","top"].includes(r)&&F.createElement(wp,{containerRef:a,visible:o}),F.createElement("div",{className:Te(Ar("wrapper"))},e),["end","bottom"].includes(r)&&F.createElement(wp,{containerRef:a,visible:o}))))}const Cp=()=>F.createElement(F.Fragment,null,F.createElement("path",{strokeWidth:2,stroke:"currentColor",strokeLinejoin:"round",fill:"none",d:"M3 5l18 0l0 14l-18 0l0-14z"}),F.createElement("path",{d:"M5 14h4v3h-4zM10 14h4v3h-4zM15 14h4v3h-4z"})),cE=nn("ThumbnailsVisible",Cp()),dE=B_("ThumbnailsHidden",Cp());function uE(){const{visible:e,show:t,hide:n}=aE(),{render:r}=Lt();return r.buttonThumbnails?F.createElement(F.Fragment,null,r.buttonThumbnails({visible:e,show:t,hide:n})):F.createElement(ks,{label:e?"Hide thumbnails":"Show thumbnails",icon:e?cE:dE,renderIcon:e?r.iconThumbnailsVisible:r.iconThumbnailsHidden,onClick:e?n:t})}function fE({augment:e,contains:t,append:n,addParent:r}){e(({thumbnails:o,toolbar:i,...a})=>{const l=Ic(o);return{toolbar:Yi(i,mo,l.showToggle?F.createElement(uE,null):null),thumbnails:l,...a}});const s=pn(mo,lE);t(po)?n(po,s):r(go,s)}const hE=window.G7Core?.createLogger?.("Comp:ImageGallery")??{log:(...e)=>console.log("[Comp:ImageGallery]",...e),warn:(...e)=>console.warn("[Comp:ImageGallery]",...e),error:(...e)=>console.error("[Comp:ImageGallery]",...e)},$c=window.G7Core,Zi=(e,t)=>$c?.t?.(e,t)??e,gE=async(e,t)=>{try{const n=await $c.api.get(e,{responseType:"blob"});if(n){const r=URL.createObjectURL(n),s=document.createElement("a");s.href=r,s.download=t,document.body.appendChild(s),s.click(),document.body.removeChild(s),URL.revokeObjectURL(r)}}catch(n){hE.error("Failed to download file:",n),$c?.toast?.error?.(Zi("common.download_failed"))}},pE=(e,t)=>{const n=document.createElement("a");n.href=e,n.download=t,n.target="_blank",document.body.appendChild(n),n.click(),document.body.removeChild(n)},Fc=async e=>{const t=e.downloadUrl||e.src,n=e.filename||e.title||"image";e.downloadRequiresAuth?await gE(t,n):pE(t,n)},mE=({image:e,index:t,onDownload:n})=>{const[r,s]=v.useState(!1),o=async i=>{if(i.stopPropagation(),n){n(e,t);return}s(!0);try{await Fc(e)}finally{s(!1)}};return h.jsx(ae,{type:"button",onClick:o,disabled:r,className:"yarl__button flex items-center justify-center","aria-label":Zi("common.download"),title:Zi("common.download"),children:r?h.jsx(bt,{className:"fa-solid fa-spinner fa-spin text-white"}):h.jsx(bt,{className:"fa-solid fa-download text-white"})})},Qi=({images:e,isOpen:t,onClose:n,startIndex:r=0,enableZoom:s=!0,enableSlideshow:o=!1,enableFullscreen:i=!0,showCounter:a=!0,showDownload:l=!0,showThumbnails:c=!0,onDownload:d,editorAttrs:u,id:f})=>{const[g,m]=v.useState(r),p=v.useRef(r),y=e.map(x=>({src:x.src,title:x.title,description:x.description})),b=[];s&&b.push($2),o&&b.push(Y2),i&&b.push(R2),a&&b.push(V2),c&&b.push(fE);const _=e[g];if(v.useEffect(()=>{p.current=g},[g]),u){const x=(e??[]).slice(0,4);return h.jsx(I,{id:f,className:"grid grid-cols-2 gap-2 p-3 rounded-lg border border-dashed border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800",...u,children:x.length>0?x.map((M,C)=>h.jsx(_n,{src:M.thumbnail||M.src,alt:M.title||"",className:"w-full aspect-square object-cover rounded bg-gray-200 dark:bg-gray-700"},C)):h.jsxs(I,{className:"col-span-2 flex items-center justify-center py-6 text-sm text-gray-400 dark:text-gray-500",children:[h.jsx(bt,{className:"fa-regular fa-images mr-2"}),Zi("editor.component.image_gallery")]})})}return h.jsx(v2,{open:t,close:n,slides:y,index:g,plugins:b,on:{view:({index:x})=>{x!==p.current&&m(x)}},zoom:{maxZoomPixelRatio:3,zoomInMultiplier:2,doubleTapDelay:300,doubleClickDelay:300,doubleClickMaxStops:2,keyboardMoveDistance:50,wheelZoomDistanceFactor:100,pinchZoomDistanceFactor:100,scrollToZoom:!0},carousel:{finite:!0,preload:2,padding:"16px",spacing:"30%"},animation:{fade:250,swipe:500,easing:{fade:"ease",swipe:"ease-out",navigation:"ease-in-out"}},controller:{closeOnBackdropClick:!0,closeOnPullDown:!0,closeOnPullUp:!0},thumbnails:{position:"bottom",width:120,height:80,border:2,borderRadius:4,padding:4,gap:16,showToggle:!1,vignette:!0},toolbar:{buttons:[l&&_&&h.jsx(mE,{image:_,index:g,onDownload:d},"download"),"close"].filter(Boolean)},styles:{container:{backgroundColor:"rgba(0, 0, 0, 0.9)"}}})},Sp=()=>{const[e,t]=v.useState(!1),[n,r]=v.useState([]),[s,o]=v.useState(0),i=v.useCallback((l,c=0)=>{r(l),o(c),t(!0)},[]),a=v.useCallback(()=>{t(!1)},[]);return{isOpen:e,openGallery:i,closeGallery:a,galleryProps:{images:n,isOpen:e,onClose:a,startIndex:s}}};Qi.displayName="ImageGallery";const bE=Object.freeze(Object.defineProperty({__proto__:null,ImageGallery:Qi,executeImageDownload:Fc,useImageGallery:Sp},Symbol.toStringTag,{value:"Module"})),yE=v.createContext(null),vE=({value:e,children:t})=>h.jsx(yE.Provider,{value:e,children:t});function xE(e,t){return t.forEach((function(n){n&&typeof n!="string"&&!Array.isArray(n)&&Object.keys(n).forEach((function(r){if(r!=="default"&&!(r in e)){var s=Object.getOwnPropertyDescriptor(n,r);Object.defineProperty(e,r,s.get?s:{enumerable:!0,get:function(){return n[r]}})}}))})),Object.freeze(e)}function _p(e,t){return new Promise((function(n,r){let s;return wE(e).then((function(o){try{return s=o,n(new Blob([t.slice(0,2),s,t.slice(2)],{type:"image/jpeg"}))}catch(i){return r(i)}}),r)}))}const wE=e=>new Promise(((t,n)=>{const r=new FileReader;r.addEventListener("load",(({target:{result:s}})=>{const o=new DataView(s);let i=0;if(o.getUint16(i)!==65496)return n("not a valid JPEG");for(i+=2;;){const a=o.getUint16(i);if(a===65498)break;const l=o.getUint16(i+2);if(a===65505&&o.getUint32(i+4)===1165519206){const c=i+10;let d;switch(o.getUint16(c)){case 18761:d=!0;break;case 19789:d=!1;break;default:return n("TIFF header contains invalid endian")}if(o.getUint16(c+2,d)!==42)return n("TIFF header contains invalid version");const u=o.getUint32(c+4,d),f=c+u+2+12*o.getUint16(c+u,d);for(let g=c+u+2;g>>24&255,a[i+1]=l>>>16&255,a[i+2]=l>>>8&255,a[i+3]=l>>>0&255,new Uint8Array(a.buffer,0,i+4)},r.deflateRaw=function(s,o){o==null&&(o={level:6});var i=new Uint8Array(50+Math.floor(1.1*s.length)),a=r.F.deflateRaw(s,i,a,o.level);return new Uint8Array(i.buffer,0,a)},r.encode=function(s,o){o==null&&(o=!1);var i=0,a=r.bin.writeUint,l=r.bin.writeUshort,c={};for(var d in s){var u=!r._noNeed(d)&&!o,f=s[d],g=r.crc.crc(f,0,f.length);c[d]={cpr:u,usize:f.length,crc:g,file:u?r.deflateRaw(f):f}}for(var d in c)i+=c[d].file.length+30+46+2*r.bin.sizeUTF8(d);i+=22;var m=new Uint8Array(i),p=0,y=[];for(var d in c){var b=c[d];y.push(p),p=r._writeHeader(m,p,d,b,0)}var _=0,x=p;for(var d in c)b=c[d],y.push(p),p=r._writeHeader(m,p,d,b,1,y[_++]);var M=p-x;return a(m,p,101010256),p+=4,l(m,p+=4,_),l(m,p+=2,_),a(m,p+=2,M),a(m,p+=4,x),p+=4,p+=2,m.buffer},r._noNeed=function(s){var o=s.split(".").pop().toLowerCase();return"png,jpg,jpeg,zip".indexOf(o)!=-1},r._writeHeader=function(s,o,i,a,l,c){var d=r.bin.writeUint,u=r.bin.writeUshort,f=a.file;return d(s,o,l==0?67324752:33639248),o+=4,l==1&&(o+=2),u(s,o,20),u(s,o+=2,0),u(s,o+=2,a.cpr?8:0),d(s,o+=2,0),d(s,o+=4,a.crc),d(s,o+=4,f.length),d(s,o+=4,a.usize),u(s,o+=4,r.bin.sizeUTF8(i)),u(s,o+=2,0),o+=2,l==1&&(o+=2,o+=2,d(s,o+=6,c),o+=4),o+=r.bin.writeUTF8(s,o,i),l==0&&(s.set(f,o),o+=f.length),o},r.crc={table:(function(){for(var s=new Uint32Array(256),o=0;o<256;o++){for(var i=o,a=0;a<8;a++)1&i?i=3988292384^i>>>1:i>>>=1;s[o]=i}return s})(),update:function(s,o,i,a){for(var l=0;l>>8;return s},crc:function(s,o,i){return 4294967295^r.crc.update(4294967295,s,o,i)}},r.adler=function(s,o,i){for(var a=1,l=0,c=o,d=o+i;c>8&255},readUint:function(s,o){return 16777216*s[o+3]+(s[o+2]<<16|s[o+1]<<8|s[o])},writeUint:function(s,o,i){s[o]=255&i,s[o+1]=i>>8&255,s[o+2]=i>>16&255,s[o+3]=i>>24&255},readASCII:function(s,o,i){for(var a="",l=0;l>6,s[o+l+1]=128|d>>0&63,l+=2;else if((4294901760&d)==0)s[o+l]=224|d>>12,s[o+l+1]=128|d>>6&63,s[o+l+2]=128|d>>0&63,l+=3;else{if((4292870144&d)!=0)throw"e";s[o+l]=240|d>>18,s[o+l+1]=128|d>>12&63,s[o+l+2]=128|d>>6&63,s[o+l+3]=128|d>>0&63,l+=4}}return l},sizeUTF8:function(s){for(var o=s.length,i=0,a=0;a>>3}var y=c.lits,b=c.strt,_=c.prev,x=0,M=0,C=0,k=0,N=0,S=0;for(p>2&&(b[S=r.F._hash(s,0)]=0),f=0;f14e3||M>26697)&&p-f>100&&(m>>16,P=65535&E;if(E!=0){P=65535&E;var L=d(D=E>>>16,c.of0);c.lhst[257+L]++;var $=d(P,c.df0);c.dhst[$]++,k+=c.exb[L]+c.dxb[$],y[x]=D<<23|f-m,y[x+1]=P<<16|L<<8|$,x+=2,m=f+D}else c.lhst[s[f]]++;M++}}for(C==f&&s.length!=0||(m>>3},r.F._bestMatch=function(s,o,i,a,l,c){var d=32767&o,u=i[d],f=d-u+32768&32767;if(u==d||a!=r.F._hash(s,o-f))return 0;for(var g=0,m=0,p=Math.min(32767,o);f<=p&&--c!=0&&u!=d;){if(g==0||s[o+g]==s[o+g-f]){var y=r.F._howLong(s,o,f);if(y>g){if(m=f,(g=y)>=l)break;f+2b&&(b=M,u=x)}}}f+=(d=u)-(u=i[d])+32768&32767}return g<<16|m},r.F._howLong=function(s,o,i){if(s[o]!=s[o-i]||s[o+1]!=s[o+1-i]||s[o+2]!=s[o+2-i])return 0;var a=o,l=Math.min(s.length,o+258);for(o+=3;o>>23,G=T+(8388607&H);T>16,Y=U>>8&255,V=255&U;S(u,f=r.F._writeLit(257+Y,$,u,f),j-k.of0[Y]),f+=k.exb[Y],N(u,f=r.F._writeLit(V,A,u,f),W-k.df0[V]),f+=k.dxb[V],T+=j}}f=r.F._writeLit(256,$,u,f)}return f},r.F._copyExact=function(s,o,i,a,l){var c=l>>>3;return a[c]=i,a[c+1]=i>>>8,a[c+2]=255-a[c],a[c+3]=255-a[c+1],c+=4,a.set(new Uint8Array(s.buffer,o,i),c),l+(i+4<<3)},r.F.getTrees=function(){for(var s=r.F.U,o=r.F._hufTree(s.lhst,s.ltree,15),i=r.F._hufTree(s.dhst,s.dtree,15),a=[],l=r.F._lenCodes(s.ltree,a),c=[],d=r.F._lenCodes(s.dtree,c),u=0;u4&&s.itree[1+(s.ordr[g-1]<<1)]==0;)g--;return[o,i,f,l,d,g,a,c]},r.F.getSecond=function(s){for(var o=[],i=0;i>1)+",");return o},r.F.contSize=function(s,o){for(var i=0,a=0;a15&&(r.F._putsE(i,a,d,u),a+=u)}return a},r.F._lenCodes=function(s,o){for(var i=s.length;i!=2&&s[i-1]==0;)i-=2;for(var a=0;a>>1,138))<11?o.push(17,g-3):o.push(18,g-11),a+=2*g-2}else if(l==u&&c==l&&d==l){for(f=a+5;f+2>>1,6);o.push(16,g-3),a+=2*g-2}else o.push(l,0)}return i>>>1},r.F._hufTree=function(s,o,i){var a=[],l=s.length,c=o.length,d=0;for(d=0;di&&(r.F.restrictDepth(f,i,x),x=i),d=0;do;a++){var d=s[a].d;s[a].d=o,c+=l-(1<>>=i-o;c>0;)(d=s[a].d)=0;a--)s[a].d==o&&c<0&&(s[a].d--,c++);c!=0&&console.log("debt left")},r.F._goodIndex=function(s,o){var i=0;return o[16|i]<=s&&(i|=16),o[8|i]<=s&&(i|=8),o[4|i]<=s&&(i|=4),o[2|i]<=s&&(i|=2),o[1|i]<=s&&(i|=1),i},r.F._writeLit=function(s,o,i,a){return r.F._putsF(i,a,o[s<<1]),a+o[1+(s<<1)]},r.F.inflate=function(s,o){var i=Uint8Array;if(s[0]==3&&s[1]==0)return o||new i(0);var a=r.F,l=a._bitsF,c=a._bitsE,d=a._decodeTiny,u=a.makeCodes,f=a.codes2map,g=a._get17,m=a.U,p=o==null;p&&(o=new i(s.length>>>2<<3));for(var y,b,_=0,x=0,M=0,C=0,k=0,N=0,S=0,w=0,E=0;_==0;)if(_=l(s,E,1),x=l(s,E+1,2),E+=3,x!=0){if(p&&(o=r.F._check(o,w+131072)),x==1&&(y=m.flmap,b=m.fdmap,N=511,S=31),x==2){M=c(s,E,5)+257,C=c(s,E+5,5)+1,k=c(s,E+10,4)+4,E+=14;for(var D=0;D<38;D+=2)m.itree[D]=0,m.itree[D+1]=0;var P=1;for(D=0;DP&&(P=L)}E+=3*k,u(m.itree,P),f(m.itree,P,m.imap),y=m.lmap,b=m.dmap,E=d(m.imap,(1<>>4;if(!(T>>>8))o[w++]=T;else{if(T==256)break;var z=w+T-254;if(T>264){var H=m.ldef[T-257];z=w+(H>>>3)+c(s,E,7&H),E+=7&H}var j=b[g(s,E)&S];E+=15&j;var G=j>>>4,U=m.ddef[G],W=(U>>>4)+l(s,E,15&U);for(E+=15&U,p&&(o=r.F._check(o,w+131072));w>>3),V=s[Y-4]|s[Y-3]<<8;p&&(o=r.F._check(o,w+V)),o.set(new i(s.buffer,s.byteOffset+Y,V),w),E=Y+V<<3,w+=V}return o.length==w?o:o.slice(0,w)},r.F._check=function(s,o){var i=s.length;if(o<=i)return s;var a=new Uint8Array(Math.max(i<<1,o));return a.set(s,0),a},r.F._decodeTiny=function(s,o,i,a,l,c){for(var d=r.F._bitsE,u=r.F._get17,f=0;f>>4;if(m<=15)c[f]=m,f++;else{var p=0,y=0;m==16?(y=3+d(a,l,2),l+=2,p=c[f-1]):m==17?(y=3+d(a,l,3),l+=3):m==18&&(y=11+d(a,l,7),l+=7);for(var b=f+y;f>>1;cl&&(l=u),c++}for(;c>1,u=s[c+1],f=d<<4|u,g=o-u,m=s[c]<>>15-o]=f,m++},r.F.revCodes=function(s,o){for(var i=r.F.U.rev15,a=15-o,l=0;l>>a}},r.F._putsE=function(s,o,i){i<<=7&o;var a=o>>>3;s[a]|=i,s[a+1]|=i>>>8},r.F._putsF=function(s,o,i){i<<=7&o;var a=o>>>3;s[a]|=i,s[a+1]|=i>>>8,s[a+2]|=i>>>16},r.F._bitsE=function(s,o,i){return(s[o>>>3]|s[1+(o>>>3)]<<8)>>>(7&o)&(1<>>3]|s[1+(o>>>3)]<<8|s[2+(o>>>3)]<<16)>>>(7&o)&(1<>>3]|s[1+(o>>>3)]<<8|s[2+(o>>>3)]<<16)>>>(7&o)},r.F._get25=function(s,o){return(s[o>>>3]|s[1+(o>>>3)]<<8|s[2+(o>>>3)]<<16|s[3+(o>>>3)]<<24)>>>(7&o)},r.F.U=(t=Uint16Array,n=Uint32Array,{next_code:new t(16),bl_count:new t(16),ordr:[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],of0:[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,999,999,999],exb:[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0,0],ldef:new t(32),df0:[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,65535,65535],dxb:[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13,0,0],ddef:new n(32),flmap:new t(512),fltree:[],fdmap:new t(32),fdtree:[],lmap:new t(32768),ltree:[],ttree:[],dmap:new t(32768),dtree:[],imap:new t(512),itree:[],rev15:new t(32768),lhst:new n(286),dhst:new n(30),ihst:new n(19),lits:new n(15e3),strt:new t(65536),prev:new t(32768)}),(function(){for(var s=r.F.U,o=0;o<32768;o++){var i=o;i=(4278255360&(i=(4042322160&(i=(3435973836&(i=(2863311530&i)>>>1|(1431655765&i)<<1))>>>2|(858993459&i)<<2))>>>4|(252645135&i)<<4))>>>8|(16711935&i)<<8,s.rev15[o]=(i>>>16|i<<16)>>>17}function a(l,c,d){for(;c--!=0;)l.push(0,d)}for(o=0;o<32;o++)s.ldef[o]=s.of0[o]<<3|s.exb[o],s.ddef[o]=s.df0[o]<<4|s.dxb[o];a(s.fltree,144,8),a(s.fltree,112,9),a(s.fltree,24,7),a(s.fltree,8,8),r.F.makeCodes(s.fltree,9),r.F.codes2map(s.fltree,9,s.flmap),r.F.revCodes(s.fltree,9),a(s.fdtree,32,5),r.F.makeCodes(s.fdtree,5),r.F.codes2map(s.fdtree,5,s.fdmap),r.F.revCodes(s.fdtree,5),a(s.itree,19,0),a(s.ltree,286,0),a(s.dtree,30,0),a(s.ttree,320,0)})()})();var CE=xE({__proto__:null,default:Ji},[Ji]);const Dn=(function(){var e={nextZero(d,u){for(;d[u]!=0;)u++;return u},readUshort:(d,u)=>d[u]<<8|d[u+1],writeUshort(d,u,f){d[u]=f>>8&255,d[u+1]=255&f},readUint:(d,u)=>16777216*d[u]+(d[u+1]<<16|d[u+2]<<8|d[u+3]),writeUint(d,u,f){d[u]=f>>24&255,d[u+1]=f>>16&255,d[u+2]=f>>8&255,d[u+3]=255&f},readASCII(d,u,f){let g="";for(let m=0;md.length<2?`0${d}`:d,readUTF8(d,u,f){let g,m="";for(let p=0;p>3)]>>7-((7&k)<<0)&1);b[w]=H[L],b[w+1]=H[L+1],b[w+2]=H[L+2],b[w+3]=$>2)]>>6-((3&k)<<1)&3),b[w]=H[L],b[w+1]=H[L+1],b[w+2]=H[L+2],b[w+3]=$>1)]>>4-((1&k)<<2)&15),b[w]=H[L],b[w+1]=H[L+1],b[w+2]=H[L+2],b[w+3]=$>>3)]>>>7-(7&T)&1))==255*S?0:255;_[j+T]=z<<24|A<<16|A<<8|A}else if(M==2)for(T=0;T>>2)]>>>6-((3&T)<<1)&3))==85*S?0:255,_[j+T]=z<<24|A<<16|A<<8|A;else if(M==4)for(T=0;T>>1)]>>>4-((1&T)<<2)&15))==17*S?0:255,_[j+T]=z<<24|A<<16|A<<8|A;else if(M==8)for(T=0;T>3,S=Math.ceil(M*k/8),w=new Uint8Array(C*S);let E=0;const D=[0,0,4,0,2,0,1],P=[0,4,0,2,0,1,0],L=[8,8,8,4,4,2,2],$=[8,8,4,4,2,2,1];let A=0;for(;A<7;){const T=L[A],z=$[A];let H=0,j=0,G=D[A];for(;G>3])>>7-(7&K)&1,w[V*S+(X>>3)]|=O<<7-((7&X)<<0)),k==2&&(O=(O=_[K>>3])>>6-(7&K)&3,w[V*S+(X>>2)]|=O<<6-((3&X)<<1)),k==4&&(O=(O=_[K>>3])>>4-(7&K)&15,w[V*S+(X>>1)]|=O<<4-((1&X)<<2)),k>=8){const q=V*S+X*N;for(let ne=0;ne>3)+ne]}K+=k,X+=z}Y++,V+=T}H*j!=0&&(E+=j*(1+W)),A+=1}return w})(u,d)),u}function r(d,u){return s(new Uint8Array(d.buffer,2,d.length-6),u)}var s=(function(){const d={H:{}};return d.H.N=function(u,f){const g=Uint8Array;let m,p,y=0,b=0,_=0,x=0,M=0,C=0,k=0,N=0,S=0;if(u[0]==3&&u[1]==0)return f||new g(0);const w=d.H,E=w.b,D=w.e,P=w.R,L=w.n,$=w.A,A=w.Z,O=w.m,T=f==null;for(T&&(f=new g(u.length>>>2<<5));y==0;)if(y=E(u,S,1),b=E(u,S+1,2),S+=3,b!=0){if(T&&(f=d.H.W(f,N+(1<<17))),b==1&&(m=O.J,p=O.h,C=511,k=31),b==2){_=D(u,S,5)+257,x=D(u,S+5,5)+1,M=D(u,S+10,4)+4,S+=14;let H=1;for(var z=0;z<38;z+=2)O.Q[z]=0,O.Q[z+1]=0;for(z=0;zH&&(H=U)}S+=3*M,L(O.Q,H),$(O.Q,H,O.u),m=O.w,p=O.d,S=P(O.u,(1<>>4;if(!(j>>>8))f[N++]=j;else{if(j==256)break;{let G=N+j-254;if(j>264){const X=O.q[j-257];G=N+(X>>>3)+D(u,S,7&X),S+=7&X}const U=p[A(u,S)&k];S+=15&U;const W=U>>>4,Y=O.c[W],V=(Y>>>4)+E(u,S,15&Y);for(S+=15&Y;N>>3),j=u[H-4]|u[H-3]<<8;T&&(f=d.H.W(f,N+j)),f.set(new g(u.buffer,u.byteOffset+H,j),N),S=H+j<<3,N+=j}return f.length==N?f:f.slice(0,N)},d.H.W=function(u,f){const g=u.length;if(f<=g)return u;const m=new Uint8Array(g<<1);return m.set(u,0),m},d.H.R=function(u,f,g,m,p,y){const b=d.H.e,_=d.H.Z;let x=0;for(;x>>4;if(C<=15)y[x]=C,x++;else{let k=0,N=0;C==16?(N=3+b(m,p,2),p+=2,k=y[x-1]):C==17?(N=3+b(m,p,3),p+=3):C==18&&(N=11+b(m,p,7),p+=7);const S=x+N;for(;x>>1;for(;yp&&(p=_),y++}for(;y>1,_=u[y+1],x=b<<4|_,M=f-_;let C=u[y]<>>15-f]=x,C++}},d.H.l=function(u,f){const g=d.H.m.r,m=15-f;for(let p=0;p>>m}},d.H.M=function(u,f,g){g<<=7&f;const m=f>>>3;u[m]|=g,u[m+1]|=g>>>8},d.H.I=function(u,f,g){g<<=7&f;const m=f>>>3;u[m]|=g,u[m+1]|=g>>>8,u[m+2]|=g>>>16},d.H.e=function(u,f,g){return(u[f>>>3]|u[1+(f>>>3)]<<8)>>>(7&f)&(1<>>3]|u[1+(f>>>3)]<<8|u[2+(f>>>3)]<<16)>>>(7&f)&(1<>>3]|u[1+(f>>>3)]<<8|u[2+(f>>>3)]<<16)>>>(7&f)},d.H.i=function(u,f){return(u[f>>>3]|u[1+(f>>>3)]<<8|u[2+(f>>>3)]<<16|u[3+(f>>>3)]<<24)>>>(7&f)},d.H.m=(function(){const u=Uint16Array,f=Uint32Array;return{K:new u(16),j:new u(16),X:[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],S:[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,999,999,999],T:[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0,0],q:new u(32),p:[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,65535,65535],z:[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13,0,0],c:new f(32),J:new u(512),_:[],h:new u(32),$:[],w:new u(32768),C:[],v:[],d:new u(32768),D:[],u:new u(512),Q:[],r:new u(32768),s:new f(286),Y:new f(30),a:new f(19),t:new f(15e3),k:new u(65536),g:new u(32768)}})(),(function(){const u=d.H.m;for(var f=0;f<32768;f++){let m=f;m=(2863311530&m)>>>1|(1431655765&m)<<1,m=(3435973836&m)>>>2|(858993459&m)<<2,m=(4042322160&m)>>>4|(252645135&m)<<4,m=(4278255360&m)>>>8|(16711935&m)<<8,u.r[f]=(m>>>16|m<<16)>>>17}function g(m,p,y){for(;p--!=0;)m.push(0,y)}for(f=0;f<32;f++)u.q[f]=u.S[f]<<3|u.T[f],u.c[f]=u.p[f]<<4|u.z[f];g(u._,144,8),g(u._,112,9),g(u._,24,7),g(u._,8,8),d.H.n(u._,9),d.H.A(u._,9,u.J),d.H.l(u._,9),g(u.$,32,5),d.H.n(u.$,5),d.H.A(u.$,5,u.h),d.H.l(u.$,5),g(u.Q,19,0),g(u.C,286,0),g(u.D,30,0),g(u.v,320,0)})(),d.H.N})();function o(d){return[1,null,3,1,2,null,4][d.ctype]*d.depth}function i(d,u,f,g,m){let p=o(u);const y=Math.ceil(g*p/8);let b,_;p=Math.ceil(p/8);let x=d[f],M=0;if(x>1&&(d[f]=[0,0,1][x-2]),x==3)for(M=p;M>>1)&255;for(let C=0;C>>1);for(;M>>1)}else{for(;M=0&&b>=0?(C=A*u+O<<2,k=(b+A)*m+y+O<<2):(C=(-b+A)*u-y+O<<2,k=A*m+O<<2),_==0)g[k]=d[C],g[k+1]=d[C+1],g[k+2]=d[C+2],g[k+3]=d[C+3];else if(_==1){var N=d[C+3]*.00392156862745098,S=d[C]*N,w=d[C+1]*N,E=d[C+2]*N,D=g[k+3]*(1/255),P=g[k]*D,L=g[k+1]*D,$=g[k+2]*D;const T=1-N,z=N+D*T,H=z==0?0:1/z;g[k+3]=255*z,g[k+0]=(S+P*T)*H,g[k+1]=(w+L*T)*H,g[k+2]=(E+$*T)*H}else if(_==2)N=d[C+3],S=d[C],w=d[C+1],E=d[C+2],D=g[k+3],P=g[k],L=g[k+1],$=g[k+2],N==D&&S==P&&w==L&&E==$?(g[k]=0,g[k+1]=0,g[k+2]=0,g[k+3]=0):(g[k]=S,g[k+1]=w,g[k+2]=E,g[k+3]=N);else if(_==3){if(N=d[C+3],S=d[C],w=d[C+1],E=d[C+2],D=g[k+3],P=g[k],L=g[k+1],$=g[k+2],N==D&&S==P&&w==L&&E==$)continue;if(N<220&&D>20)return!1}return!0}return{decode:function(u){const f=new Uint8Array(u);let g=8;const m=e,p=m.readUshort,y=m.readUint,b={tabs:{},frames:[]},_=new Uint8Array(f.length);let x,M=0,C=0;const k=[137,80,78,71,13,10,26,10];for(var N=0;N<8;N++)if(f[N]!=k[N])throw"The input is not a PNG file!";for(;g>>1:E>>>=1;S[w]=E}return S})(),update(S,w,E,D){for(let P=0;P>>8;return S},crc:(S,w,E)=>4294967295^r.update(4294967295,S,w,E)};function s(S,w,E,D){w[E]+=S[0]*D>>4,w[E+1]+=S[1]*D>>4,w[E+2]+=S[2]*D>>4,w[E+3]+=S[3]*D>>4}function o(S){return Math.max(0,Math.min(255,S))}function i(S,w){const E=S[0]-w[0],D=S[1]-w[1],P=S[2]-w[2],L=S[3]-w[3];return E*E+D*D+P*P+L*L}function a(S,w,E,D,P,L,$){$==null&&($=1);const A=D.length,O=[];for(var T=0;T>>0&255,V>>>8&255,V>>>16&255,V>>>24&255])}for(T=0;T>2]=z,G[T>>2]=D[z]}}function l(S,w,E,D,P){P==null&&(P={});const{crc:L}=r,$=t.writeUint,A=t.writeUshort,O=t.writeASCII;let T=8;const z=S.frames.length>1;let H,j=!1,G=33+(z?20:0);if(P.sRGB!=null&&(G+=13),P.pHYs!=null&&(G+=21),P.iCCP!=null&&(H=pako.deflate(P.iCCP),G+=21+H.length+4),S.ctype==3){for(var U=S.plte.length,W=0;W>>24!=255&&(j=!0);G+=8+3*U+4+(j?8+1*U+4:0)}for(var Y=0;Y>>8&255,ce=ee>>>16&255;V[T+ne+0]=ue,V[T+ne+1]=J,V[T+ne+2]=ce}if(T+=3*U,$(V,T,L(V,T-3*U-4,3*U+4)),T+=4,j){for($(V,T,U),T+=4,O(V,T,"tRNS"),T+=4,W=0;W>>24&255;T+=U,$(V,T,L(V,T-U-4,U+4)),T+=4}}let K=0;for(Y=0;Yze&&(ze=ct),atKe&&(Ke=at));ze==-1&&(Fe=Ne=ze=Ke=0),ie&&((1&Fe)==1&&Fe--,(1&Ne)==1&&Ne--);const tt=(ze-Fe+1)*(Ke-Ne+1);tt>2,$e>>2);ne.push(ee);const ie=new Uint8Array(pe.abuf,ke,$e);z&&a(ue.img,ue.rect.width,ue.rect.height,q,ie,ee),ue.img.set(ie),ke+=$e}}else for(U=0;UJ&&ge==ve[Y-J])ee[Y]=ee[Y-J];else{let pe=K[ge];if(pe==null&&(K[ge]=pe=q.length,q.push(ge),q.length>=300))break;ee[Y]=pe}}}const ce=q.length;for(ce<=256&&T==0&&(j=ce<=2?1:ce<=4?2:ce<=16?4:8,j=Math.max(j,O)),U=0;U>1)]|=$e[Me+le]<<4-4*(1&le);else if(j==2)for(le=0;le>2)]|=$e[Me+le]<<6-2*(3&le);else if(j==1)for(le=0;le>3)]|=$e[Me+le]<<7-1*(7&le)}ge=me,H=3,ke=1}else if(V==0&&X.length==1){me=new Uint8Array(J*ve*3);const $e=J*ve;for(Y=0;Y<$e;Y++){const ie=3*Y,Me=4*Y;me[ie]=ge[Me],me[ie+1]=ge[Me+1],me[ie+2]=ge[Me+2]}ge=me,H=2,ke=3,pe=3*J}ue.img=ge,ue.bpl=pe,ue.bpp=ke}return{ctype:H,depth:j,plte:q,frames:X}}function u(S,w,E,D,P,L,$){const A=Uint8Array,O=Uint32Array,T=new A(S[P-1]),z=new O(S[P-1]),H=P+1Y&&(Y=ne),eeV&&(V=ee))}Y==-1&&(U=W=Y=V=0),$&&((1&U)==1&&U--,(1&W)==1&&W--),L={x:U,y:W,width:Y-U+1,height:V-W+1};const X=D[P];X.rect=L,X.blend=1,X.img=new Uint8Array(L.width*L.height*4),D[P-1].dispose==0?(e(T,w,E,X.img,L.width,L.height,-L.x,-L.y,0),f(j,w,E,X.img,L)):e(j,w,E,X.img,L.width,L.height,-L.x,-L.y,0)}function f(S,w,E,D,P){e(S,w,E,D,P.width,P.height,-P.x,-P.y,2)}function g(S,w,E,D,P,L,$){const A=[];let O,T=[0,1,2,3,4];L!=-1?T=[L]:(w*D>5e5||E==1)&&(T=[0]),$&&(O={level:0});const z=CE;for(var H=0;H>1)+256&255;if(L==4)for(O=P;O>1)&255;for(O=P;O>1)&255}if(L==4){for(O=0;O>2);let z;if(E.length<2e7)for(var H=0;H>2]=z.ind,P[H>>2]=z.est.rgba;else for(H=0;H>2]=z.ind,P[H>>2]=z.est.rgba}return{abuf:D.buffer,inds:T,plte:A}}function y(S,w,E){E==null&&(E=1e-4);const D=new Uint32Array(S.buffer),P={i0:0,i1:S.length,bst:null,est:null,tdst:0,left:null,right:null};P.bst=C(S,P.i0,P.i1),P.est=k(P.bst);const L=[P];for(;L.lengthA&&(A=L[$].est.L,O=$);if(A=z||T.i1<=z){T.est.L=0;continue}const H={i0:T.i0,i1:z,bst:null,est:null,tdst:0,left:null,right:null};H.bst=C(S,H.i0,H.i1),H.est=k(H.bst);const j={i0:z,i1:T.i1,bst:null,est:null,tdst:0,left:null,right:null};for(j.bst={R:[],m:[],N:T.bst.N-H.bst.N},$=0;$<16;$++)j.bst.R[$]=T.bst.R[$]-H.bst.R[$];for($=0;$<4;$++)j.bst.m[$]=T.bst.m[$]-H.bst.m[$];j.est=k(j.bst),T.left=H,T.right=j,L[O]=H,L.push(j)}for(L.sort(((A,O)=>O.bst.N-A.bst.N)),$=0;$0&&($=S.right,A=S.left);const O=b($,w,E,D,P);if(O.tdst<=L*L)return O;const T=b(A,w,E,D,P);return T.tdstL;)D-=4;if(E>=D)break;const $=w[E>>2];w[E>>2]=w[D>>2],w[D>>2]=$,E+=4,D-=4}for(;M(S,E,P)>L;)E-=4;return E+4}function M(S,w,E){return S[w]*E[0]+S[w+1]*E[1]+S[w+2]*E[2]+S[w+3]*E[3]}function C(S,w,E){const D=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],P=[0,0,0,0],L=E-w>>2;for(let $=w;$>>0}}var N={multVec:(S,w)=>[S[0]*w[0]+S[1]*w[1]+S[2]*w[2]+S[3]*w[3],S[4]*w[0]+S[5]*w[1]+S[6]*w[2]+S[7]*w[3],S[8]*w[0]+S[9]*w[1]+S[10]*w[2]+S[11]*w[3],S[12]*w[0]+S[13]*w[1]+S[14]*w[2]+S[15]*w[3]],dot:(S,w)=>S[0]*w[0]+S[1]*w[1]+S[2]*w[2]+S[3]*w[3],sml:(S,w)=>[S*w[0],S*w[1],S*w[2],S*w[3]]};Dn.encode=function(w,E,D,P,L,$,A){P==null&&(P=0),A==null&&(A=!1);const O=d(w,E,D,P,[!1,!1,!1,0,A,!1]);return c(O,-1),l(O,E,D,L,$)},Dn.encodeLL=function(w,E,D,P,L,$,A,O){const T={ctype:0+(P==1?0:2)+(L==0?0:4),depth:$,frames:[]},z=(P+L)*$,H=z*E;for(let j=0;j>>0),C(1),C(32),k(3),k(l),k(2835),k(2835),N(8),k(16711680),k(65280),k(255),k(4278190080),k(1466527264),(function S(){for(;_0;){for(y=122+_*a,g=0;g>>24,u.setUint32(y+g,m<<8|p),g+=4;_++}M{t(new Blob([n],{type:"image/bmp"}))}))},_dly:9};var rn={CHROME:"CHROME",FIREFOX:"FIREFOX",DESKTOP_SAFARI:"DESKTOP_SAFARI",IE:"IE",IOS:"IOS",ETC:"ETC"},SE={[rn.CHROME]:16384,[rn.FIREFOX]:11180,[rn.DESKTOP_SAFARI]:16384,[rn.IE]:8192,[rn.IOS]:4096,[rn.ETC]:8192};const zc=typeof window<"u",Mp=typeof WorkerGlobalScope<"u"&&self instanceof WorkerGlobalScope,Ri=zc&&window.cordova&&window.cordova.require&&window.cordova.require("cordova/modulemapper"),_E=(zc||Mp)&&(Ri&&Ri.getOriginalSymbol(window,"File")||typeof File<"u"&&File),Np=(zc||Mp)&&(Ri&&Ri.getOriginalSymbol(window,"FileReader")||typeof FileReader<"u"&&FileReader);function Vc(e,t,n=Date.now()){return new Promise((r=>{const s=e.split(","),o=s[0].match(/:(.*?);/)[1],i=globalThis.atob(s[1]);let a=i.length;const l=new Uint8Array(a);for(;a--;)l[a]=i.charCodeAt(a);const c=new Blob([l],{type:o});c.name=t,c.lastModified=n,r(c)}))}function Tp(e){return new Promise(((t,n)=>{const r=new Np;r.onload=()=>t(r.result),r.onerror=s=>n(s),r.readAsDataURL(e)}))}function Op(e){return new Promise(((t,n)=>{const r=new Image;r.onload=()=>t(r),r.onerror=s=>n(s),r.src=e}))}function Dr(){if(Dr.cachedResult!==void 0)return Dr.cachedResult;let e=rn.ETC;const{userAgent:t}=navigator;return/Chrom(e|ium)/i.test(t)?e=rn.CHROME:/iP(ad|od|hone)/i.test(t)&&/WebKit/i.test(t)?e=rn.IOS:/Safari/i.test(t)?e=rn.DESKTOP_SAFARI:/Firefox/i.test(t)?e=rn.FIREFOX:(/MSIE/i.test(t)||document.documentMode)&&(e=rn.IE),Dr.cachedResult=e,Dr.cachedResult}function Ap(e,t){const n=Dr(),r=SE[n];let s=e,o=t,i=s*o;const a=s>o?o/s:s/o;for(;i>r*r;){const l=(r+s)/2,c=(r+o)/2;lEp.toBlob(e,d))).then(function(d){try{return a=d,a.name=n,a.lastModified=r,c.call(this)}catch(u){return i(u)}}.bind(this),i);{let d=function(){return c.call(this)};if(typeof OffscreenCanvas=="function"&&e instanceof OffscreenCanvas)return e.convertToBlob({type:t,quality:s}).then(function(u){try{return a=u,a.name=n,a.lastModified=r,d.call(this)}catch(f){return i(f)}}.bind(this),i);{let u;return u=e.toDataURL(t,s),Vc(u,n,r).then(function(f){try{return a=f,d.call(this)}catch(g){return i(g)}}.bind(this),i)}}}function l(){return o(a)}}))}function Wn(e){e.width=0,e.height=0}function Cs(){return new Promise((function(e,t){let n,r,s,o;return Cs.cachedResult!==void 0?e(Cs.cachedResult):Vc("data:image/jpeg;base64,/9j/4QAiRXhpZgAATU0AKgAAAAgAAQESAAMAAAABAAYAAAAAAAD/2wCEAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAf/AABEIAAEAAgMBEQACEQEDEQH/xABKAAEAAAAAAAAAAAAAAAAAAAALEAEAAAAAAAAAAAAAAAAAAAAAAQEAAAAAAAAAAAAAAAAAAAAAEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwA/8H//2Q==","test.jpg",Date.now()).then((function(i){try{return n=i,na(n).then((function(a){try{return r=a[1],ra(r,n.type,n.name,n.lastModified).then((function(l){try{return s=l,Wn(r),na(s).then((function(c){try{return o=c[0],Cs.cachedResult=o.width===1&&o.height===2,e(Cs.cachedResult)}catch(d){return t(d)}}),t)}catch(c){return t(c)}}),t)}catch(l){return t(l)}}),t)}catch(a){return t(a)}}),t)}))}function Ip(e){return new Promise(((t,n)=>{const r=new Np;r.onload=s=>{const o=new DataView(s.target.result);if(o.getUint16(0,!1)!=65496)return t(-2);const i=o.byteLength;let a=2;for(;an(s),r.readAsArrayBuffer(e)}))}function Lp(e,t){const{width:n}=e,{height:r}=e,{maxWidthOrHeight:s}=t;let o,i=e;return isFinite(s)&&(n>s||r>s)&&([i,o]=ea(n,r),n>r?(i.width=s,i.height=r/n*s):(i.width=n/r*s,i.height=s),o.drawImage(e,0,0,i.width,i.height),Wn(e)),i}function Pp(e,t){const{width:n}=e,{height:r}=e,[s,o]=ea(n,r);switch(t>4&&t<9?(s.width=r,s.height=n):(s.width=n,s.height=r),t){case 2:o.transform(-1,0,0,1,n,0);break;case 3:o.transform(-1,0,0,-1,n,r);break;case 4:o.transform(1,0,0,-1,0,r);break;case 5:o.transform(0,1,1,0,0,0);break;case 6:o.transform(0,1,-1,0,r,0);break;case 7:o.transform(0,-1,-1,0,r,n);break;case 8:o.transform(0,-1,1,0,0,n)}return o.drawImage(e,0,0,n,r),Wn(e),s}function $p(e,t,n=0){return new Promise((function(r,s){let o,i,a,l,c,d,u,f,g,m,p,y,b,_,x,M,C,k,N,S;function w(D=5){if(t.signal&&t.signal.aborted)throw t.signal.reason;o+=D,t.onProgress(Math.min(o,100))}function E(D){if(t.signal&&t.signal.aborted)throw t.signal.reason;o=Math.min(Math.max(D,o),100),t.onProgress(o)}return o=n,i=t.maxIteration||10,a=1024*t.maxSizeMB*1024,w(),na(e,t).then(function(D){try{return[,l]=D,w(),c=Lp(l,t),w(),new Promise((function(P,L){var $;if(!($=t.exifOrientation))return Ip(e).then(function(O){try{return $=O,A.call(this)}catch(T){return L(T)}}.bind(this),L);function A(){return P($)}return A.call(this)})).then(function(P){try{return d=P,w(),Cs().then(function(L){try{return u=L?c:Pp(c,d),w(),f=t.initialQuality||1,g=t.fileType||e.type,ra(u,g,e.name,e.lastModified,f).then(function($){try{{let O=function(){if(i--&&(x>a||x>b)){let z,H;return z=S?.95*N.width:N.width,H=S?.95*N.height:N.height,[C,k]=ea(z,H),k.drawImage(N,0,0,z,H),f*=g==="image/png"?.85:.95,ra(C,g,e.name,e.lastModified,f).then((function(j){try{return M=j,Wn(N),N=C,x=M.size,E(Math.min(99,Math.floor((_-x)/(_-a)*100))),O}catch(G){return s(G)}}),s)}return[1]},T=function(){return Wn(N),Wn(C),Wn(c),Wn(u),Wn(l),E(100),r(M)};if(m=$,w(),p=m.size>a,y=m.size>e.size,!p&&!y)return E(100),r(m);var A;return b=e.size,_=m.size,x=_,N=u,S=!t.alwaysKeepResolution&&p,(A=function(z){for(;z;){if(z.then)return void z.then(A,s);try{if(z.pop){if(z.length)return z.pop()?T.call(this):z;z=O}else z=z.call(this)}catch(H){return s(H)}}}.bind(this))(O)}}catch(O){return s(O)}}.bind(this),s)}catch($){return s($)}}.bind(this),s)}catch(L){return s(L)}}.bind(this),s)}catch(P){return s(P)}}.bind(this),s)}))}const EE=` + `,children:[h.jsx(re,{name:x.faIcon,className:"w-4 h-4"}),h.jsx(Q,{children:x.label}),y?.faIcon===x.faIcon&&h.jsx(re,{name:"check",className:"w-4 h-4 ml-auto text-blue-600"})]},x.value)):h.jsx(I,{className:"px-3 py-4 text-sm text-gray-500 dark:text-gray-400 text-center",children:o})})]})]})},rc=({defaultOpen:t=!1,isOpen:e,onToggle:n,className:r="",style:s,children:o,disabled:i=!1,id:a,editorAttrs:l})=>{const[c,d]=v.useState(t),u=e!==void 0,f=u?e:c,g=v.useRef(null),[m,p]=v.useState(t?void 0:0),y=()=>{if(i)return;const _=!f;u||d(_),n?.(_)};v.useEffect(()=>{if(g.current)if(f){p(g.current.scrollHeight);const _=setTimeout(()=>{p(void 0)},300);return()=>clearTimeout(_)}else p(g.current.scrollHeight),requestAnimationFrame(()=>{p(0)})},[f]);const b={};if(v.Children.forEach(o,_=>{if(v.isValidElement(_)){const x=_.props?.slot;x==="trigger"?b.trigger=_:x==="content"&&(b.content=_)}}),!b.trigger&&!b.content){const _=v.Children.toArray(o);_.length>=2?(b.trigger=_[0],b.content=_.slice(1)):_.length===1&&(b.content=_[0])}return h.jsxs(I,{className:`border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden ${r}`,style:s,id:a,...l,children:[h.jsxs(oe,{type:"button",onClick:y,disabled:i,className:`w-full flex items-center justify-between p-3 text-left bg-gray-50 dark:bg-gray-800 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors ${i?"opacity-50 cursor-not-allowed":""} ${f?"border-b border-gray-200 dark:border-gray-700":""}`,"aria-expanded":f,children:[h.jsx(I,{className:"flex-1",children:b.trigger}),!b.trigger&&h.jsx("svg",{className:`w-5 h-5 text-gray-500 dark:text-gray-400 transition-transform duration-200 ${f?"rotate-180":""}`,fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:h.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M19 9l-7 7-7-7"})})]}),h.jsx(I,{ref:g,className:"overflow-hidden transition-all duration-300 ease-in-out",style:{height:m!==void 0?`${m}px`:"auto"},children:h.jsx(I,{className:"bg-white dark:bg-gray-800",children:b.content})})]})},eo=window.G7Core,Jh=eo?.useControllableState,Rh=(t,e)=>window.G7Core?.t?.(t,e)??t,HS={"":"common.scope_type_all_desc",self:"common.scope_type_self_desc",role:"common.scope_type_role_desc"},US=({options:t,value:e,onChange:n,disabled:r=!1})=>{const[s,o]=v.useState(!1),[i,a]=v.useState({}),l=v.useRef(null),c=t.find(u=>u.value===e)??t[0];v.useEffect(()=>{if(!s)return;const u=g=>{l.current&&!l.current.contains(g.target)&&o(!1)},f=()=>{if(!l.current)return;const g=l.current.getBoundingClientRect();a({position:"fixed",top:g.bottom+4,right:window.innerWidth-g.right,zIndex:9999})};return document.addEventListener("mousedown",u),window.addEventListener("scroll",f,!0),window.addEventListener("resize",f),()=>{document.removeEventListener("mousedown",u),window.removeEventListener("scroll",f,!0),window.removeEventListener("resize",f)}},[s]);const d=v.useCallback(()=>{if(r||!l.current)return;const u=l.current.getBoundingClientRect();a({position:"fixed",top:u.bottom+4,right:window.innerWidth-u.right,zIndex:9999}),o(f=>!f)},[r]);return h.jsxs(I,{ref:l,className:"relative flex-shrink-0",children:[h.jsxs(I,{role:"button",tabIndex:0,onClick:u=>{u.stopPropagation(),u.preventDefault(),d()},onMouseDown:u=>u.stopPropagation(),onKeyDown:u=>{(u.key==="Enter"||u.key===" ")&&(u.stopPropagation(),u.preventDefault(),d()),u.key==="Escape"&&o(!1)},className:`flex items-center gap-1 text-xs px-2 py-1 rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-700 dark:text-gray-300 cursor-pointer select-none ${r?"opacity-50 cursor-not-allowed":"hover:border-blue-400 dark:hover:border-blue-500"}`,children:[h.jsx(Q,{children:c?.label??""}),h.jsx(re,{name:de.ChevronDown,className:"w-3 h-3 text-gray-400 dark:text-gray-500"})]}),s&&h.jsx(I,{style:i,className:"w-56 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-lg shadow-lg py-1",children:t.map(u=>{const f=u.value??"",g=HS[f],m=g?Rh(g):"",p=u.value===e;return h.jsxs(I,{role:"option","aria-selected":p,onClick:y=>{y.stopPropagation(),y.preventDefault(),n(u.value),o(!1)},onMouseDown:y=>y.stopPropagation(),className:`px-3 py-2 cursor-pointer transition-colors ${p?"bg-blue-50 dark:bg-blue-900/30":"hover:bg-gray-50 dark:hover:bg-gray-700/50"}`,children:[h.jsxs(I,{className:"flex items-center justify-between",children:[h.jsx(Q,{className:`text-xs font-medium ${p?"text-blue-700 dark:text-blue-300":"text-gray-900 dark:text-white"}`,children:u.label}),p&&h.jsx(re,{name:de.Check,className:"w-3.5 h-3.5 text-blue-600 dark:text-blue-400"})]}),m&&h.jsx(Q,{className:"block text-xs text-gray-500 dark:text-gray-400 mt-0.5",children:m})]},f||"__null__")})})]})},BS=(t,e)=>{if(t.length!==e.length)return!1;const n=[...t].sort((s,o)=>s.id-o.id),r=[...e].sort((s,o)=>s.id-o.id);return n.every((s,o)=>s.id===r[o].id&&s.scope_type===r[o].scope_type)},jS=({data:t=[],value:e=[],onChange:n,scopeOptions:r=[],className:s="",disabled:o=!1,desktopColumns:i=2,installedModules:a=[],installedPlugins:l=[],id:c,editorAttrs:d})=>{const u=v.useCallback(M=>{if(n){const T=eo?.createChangeEvent?eo.createChangeEvent({value:M}):{target:{value:M}};n(T)}},[n]),[f,g]=Jh?Jh(e,[],u,{isEqual:BS}):(()=>{const[M,T]=v.useState(e),P=v.useRef(n);P.current=n;const L=v.useRef(!1);v.useEffect(()=>{if(L.current){L.current=!1;return}T(e)},[e]);const $=v.useCallback(A=>{L.current=!0,T(D=>{const O=typeof A=="function"?A(D):A;if(P.current){const z=eo?.createChangeEvent?eo.createChangeEvent({value:O}):{target:{value:O}};P.current(z)}return O})},[]);return[M,$]})(),m=v.useMemo(()=>new Set(f.map(M=>M.id)),[f]),p=v.useCallback(M=>M.is_assignable?[M.id]:M.children.flatMap(T=>p(T)),[]),y=v.useCallback(M=>{const T=p(M);return T.length>0&&T.every(P=>m.has(P))},[m,p]),b=v.useCallback(M=>{const T=p(M),P=T.filter(L=>m.has(L)).length;return P>0&&Pp(M).filter(P=>m.has(P)).length,[m,p]),x=v.useCallback((M,T)=>{o||g(P=>T?[...P,{id:M,scope_type:null}]:P.filter(L=>L.id!==M))},[o,g]),E=v.useCallback((M,T)=>{if(o)return;const P=p(M),L=new Set(P);g($=>{if(T){const A=new Set($.map(O=>O.id)),D=P.filter(O=>!A.has(O)).map(O=>({id:O,scope_type:null}));return[...$,...D]}else return $.filter(A=>!L.has(A.id))})},[o,p,g]),C=v.useCallback((M,T)=>{o||g(P=>P.map(L=>L.id===M?{...L,scope_type:T}:L))},[o,g]),k=M=>{const T=m.has(M.id),P=!!M.resource_route_key,L=T&&P&&r.length>0,$=f.find(A=>A.id===M.id)?.scope_type??null;return h.jsxs(sn,{className:`flex items-start gap-3 p-3 rounded-lg border cursor-pointer transition-colors ${T?"border-blue-200 bg-blue-50 dark:border-blue-800 dark:bg-blue-900/20":"border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-700/50"} ${o?"opacity-50 cursor-not-allowed":""}`,children:[h.jsx(kt,{type:"checkbox",checked:T,onChange:A=>{A.stopPropagation(),x(M.id,A.target.checked)},disabled:o,className:"w-4 h-4 mt-0.5 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600"}),h.jsxs(I,{className:"flex-1 min-w-0",children:[h.jsxs(I,{className:"flex items-center justify-between gap-2",children:[h.jsx(Q,{className:"text-sm font-medium text-gray-900 dark:text-white",children:M.name}),L&&h.jsx(US,{options:r,value:$,onChange:A=>C(M.id,A),disabled:o})]}),M.description&&h.jsx(Q,{className:"block text-xs text-gray-500 dark:text-gray-400 mt-0.5",children:M.description})]})]},M.id)},N=(M,T)=>{const P=y(M),L=b(M),$=_(M),A=M.children.filter(z=>z.is_assignable);if(A.length===0&&M.children.length===0)return null;const D=M.children.some(z=>!z.is_assignable),O=i===1?"grid-cols-1":i===3?"grid-cols-1 md:grid-cols-3":"grid-cols-1 md:grid-cols-2";return h.jsxs(rc,{defaultOpen:!1,className:"border-0 rounded-none",children:[h.jsxs(I,{slot:"trigger",className:"flex items-center gap-3 w-full py-2",children:[h.jsx("input",{type:"checkbox",checked:P,ref:z=>{z&&(z.indeterminate=L)},onChange:z=>{z.stopPropagation(),E(M,z.target.checked)},onClick:z=>z.stopPropagation(),onMouseDown:z=>z.stopPropagation(),disabled:o,className:"w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600"}),h.jsx(re,{name:de.Folder,className:"w-4 h-4 text-gray-400 dark:text-gray-500"}),h.jsx(Q,{className:"text-sm font-medium text-gray-700 dark:text-gray-300",children:M.name}),h.jsxs(Q,{className:"inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-300 ml-1",children:[$,"/",M.leaf_count]}),h.jsx(re,{name:de.ChevronDown,className:"w-4 h-4 text-gray-400 dark:text-gray-500 ml-auto"})]}),h.jsx(I,{slot:"content",className:`pl-8 grid ${O} gap-2 py-2`,children:D?M.children.map(z=>z.is_assignable?k(z):N(z)):A.map(z=>k(z))})]},M.id)},S=M=>{if(M.extension_type==="core")return"core";if(M.extension_type==="module")return"module";if(M.extension_type==="plugin")return"plugin";const T=M.extension_identifier??M.module??"";return T==="core"||!T?"core":l?.some(L=>L.identifier===T)?"plugin":"module"},w=M=>{const T=y(M),P=b(M),L=_(M),$=S(M);return h.jsxs(rc,{defaultOpen:!1,className:"mb-2",children:[h.jsxs(I,{slot:"trigger",className:"flex items-center gap-3 w-full py-2",children:[h.jsx("input",{type:"checkbox",checked:T,ref:A=>{A&&(A.indeterminate=P)},onChange:A=>{A.stopPropagation(),E(M,A.target.checked)},onClick:A=>A.stopPropagation(),onMouseDown:A=>A.stopPropagation(),disabled:o,className:"w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600"}),h.jsx(re,{name:$==="core"?de.Cog:de.Cube,className:"w-5 h-5 text-gray-400 dark:text-gray-500"}),h.jsx(Q,{className:"font-medium text-gray-900 dark:text-white",children:M.name}),$!=="core"&&h.jsx(nh,{type:$,identifier:M.extension_identifier??M.module,installedModules:a,installedPlugins:l}),h.jsxs(Q,{className:"inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-300 ml-2",children:[L,"/",M.leaf_count]}),h.jsx(re,{name:de.ChevronDown,className:"w-4 h-4 text-gray-400 dark:text-gray-500 ml-auto"})]}),h.jsx(I,{slot:"content",className:"pl-8 space-y-2 pb-2",children:M.children.map(A=>A.is_assignable?k(A):N(A))})]},M.id)};return t.length===0?h.jsx(I,{className:`text-sm text-gray-400 dark:text-gray-500 py-4 text-center ${s}`,id:c,...d,children:Rh("common.no_data")??"데이터가 없습니다."}):h.jsx(I,{className:`space-y-2 ${s}`,id:c,...d,children:t.map(M=>w(M))})};function WS({text:t,keyword:e}){if(!e||!t)return h.jsx(h.Fragment,{children:t});const n=e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),r=t.split(new RegExp(`(${n})`,"gi"));return h.jsx(h.Fragment,{children:r.map((s,o)=>s.toLowerCase()===e.toLowerCase()?h.jsx(Q,{className:"bg-yellow-200 dark:bg-yellow-700 rounded px-0.5",children:s},o):s)})}const GS=({data:t=[],expandedIds:e=[],selectedIds:n=[],searchKeyword:r="",showProductCount:s=!1,selectable:o=!1,className:i="",onToggle:a,onSelectionChange:l,id:c,editorAttrs:d})=>{const u=v.useCallback(y=>{if(!a)return;const b=e.includes(y)?e.filter(_=>_!==y):[...e,y];a(b)},[e,a]),f=v.useCallback((y,b)=>{if(!l)return;const _=b?[...n,y]:n.filter(x=>x!==y);l(_)},[n,l]),g=v.useMemo(()=>{if(!r)return null;const y=new Set,b=_=>{for(const x of _)(x.localized_name||x.name).toLowerCase().includes(r.toLowerCase())&&y.add(x.id),x.children&&b(x.children)};return b(t),y},[t,r]),m=v.useCallback(y=>!g||g.has(y.id)?!0:(y.children??[]).some(b=>m(b)),[g]),p=(y,b=0)=>{const _=(y.children??[]).length>0,x=e.includes(y.id),E=n.includes(y.id),C=y.localized_name||y.name;return r&&!m(y)?null:h.jsxs(I,{"data-testid":`category-node-${y.id}`,children:[h.jsxs(I,{className:`flex items-center gap-2 py-1.5 px-2 rounded cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors ${b>0?"ml-"+Math.min(b*5,20):""}`,style:b>0?{marginLeft:`${b*20}px`}:void 0,children:[_?h.jsx(I,{className:"flex-shrink-0 w-5 h-5 flex items-center justify-center text-gray-400 dark:text-gray-500",onClick:()=>u(y.id),"data-testid":`toggle-${y.id}`,children:h.jsx(re,{name:x?de.CaretDown:de.CaretRight,className:"text-xs"})}):h.jsx(I,{className:"flex-shrink-0 w-5 h-5"}),o&&h.jsx(vn,{checked:E,onChange:k=>{k.stopPropagation(),f(y.id,k.target.checked)},className:"flex-shrink-0","data-testid":`checkbox-${y.id}`}),h.jsx(re,{name:de.Folder,className:`flex-shrink-0 text-sm ${_?"text-yellow-500 dark:text-yellow-400":"text-gray-400 dark:text-gray-500"}`}),h.jsx(Q,{className:"text-sm text-gray-700 dark:text-gray-300 select-none",onClick:()=>_&&u(y.id),children:h.jsx(WS,{text:C,keyword:r})}),s&&y.products_count!==void 0&&h.jsx(Q,{className:"inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-gray-100 text-gray-500 dark:bg-gray-700 dark:text-gray-400 ml-1",children:y.products_count})]}),_&&x&&h.jsx(I,{"data-testid":`children-${y.id}`,children:(y.children??[]).map(k=>p(k,b+1))})]},y.id)};return t.length===0?h.jsx(I,{className:`text-sm text-gray-400 dark:text-gray-500 py-4 text-center ${i}`,id:c,...d,children:"데이터가 없습니다."}):h.jsx(I,{className:`border border-gray-200 dark:border-gray-700 rounded-lg p-2 max-h-80 overflow-y-auto ${i}`,id:c,...d,children:t.map(y=>p(y,0))})},eg="carousel",to="controller",YS="navigation",KS="no-scroll",sc="portal",XS="root",tg="toolbar",qS="counter",no="fullscreen",ng="slideshow",ro="thumbnails",rg="zoom",Rr="loading",oc="playing",so="error",oo="complete",ZS="placeholder",io=t=>`active-slide-${t}`,QS=io(Rr),JS=io(oc),RS=io(so),e_=io(oo),sg="fullsize",es="flex_center",t_="no_scroll",og="no_scroll_padding",ic="slide",ig="slide_wrapper",n_="slide_wrapper_interactive",rr="prev",sr="next",ac="swipe",ts="close",ag="onPointerDown",lg="onPointerMove",cg="onPointerUp",dg="onPointerLeave",ug="onPointerCancel",lc="onKeyDown",r_="onKeyUp",cc="onWheel",s_="Escape",o_="ArrowLeft",i_="ArrowRight",a_="button",Pi="icon",fg="contain",hg="cover",gg="Unknown action type",pg="yarl__";function zt(...t){return t.filter(Boolean).join(" ")}function Ne(t){return`${pg}${t}`}function dt(t){return`--${pg}${t}`}function pr(t,e){return`${t}${e?`_${e}`:""}`}function mr(t){return e=>pr(t,e)}function br(t,e){var n;return(n=t?.[e])!==null&&n!==void 0?n:e}function mg(t,e,n){return br(t,"{index} of {total}").replace(/\{index}/g,`${hc(n,e.length)+1}`).replace(/\{total}/g,`${e.length}`)}function ns(...t){return()=>{t.forEach(e=>{e()})}}function ln(t,e,n){return()=>{const r=F.useContext(n);if(!r)throw new Error(`${t} must be used within a ${e}.Provider`);return r}}function dc(){return typeof window<"u"}function uc(t,e=0){const n=10**e;return Math.round((t+Number.EPSILON)*n)/n}function or(t){return t.type===void 0||t.type==="image"}function fc(t,e){return t.imageFit===hg||t.imageFit!==fg&&e===hg}function $i(t){return typeof t=="string"?Number.parseInt(t,10):t}function Fi(t){if(typeof t=="number")return{pixel:t};if(typeof t=="string"){const e=$i(t);return t.endsWith("%")?{percent:e}:{pixel:e}}return{pixel:0}}function l_(t,e){const n=Fi(e),r=n.percent!==void 0?t.width/100*n.percent:n.pixel;return{width:Math.max(t.width-2*r,0),height:Math.max(t.height-2*r,0)}}function c_(){return(dc()?window?.devicePixelRatio:void 0)||1}function hc(t,e){return e>0?(t%e+e)%e:0}function gc(t){return t.length>0}function pc(t,e){return t[hc(e,t.length)]}function mc(t,e){return gc(t)?pc(t,e):void 0}function bg(t){return or(t)?t.src:void 0}function zi(t,e,n){if(!n)return t;const{buttons:r,...s}=t,o=r.findIndex(a=>a===e),i=F.isValidElement(n)?F.cloneElement(n,{key:e},null):n;if(o>=0){const a=[...r];return a.splice(o,1,i),{buttons:a,...s}}return{buttons:[i,...r],...s}}function yg(t,e,n=0){return Math.min(t.preload,Math.max(t.finite?e.length-1:Math.floor(e.length/2),n))}const d_=Number(F.version.split(".")[0])>=19;function u_(t){return{inert:d_?t:t?"":void 0}}function f_(t){t.scrollTop}const bc={open:!1,close:()=>{},index:0,slides:[],render:{},plugins:[],toolbar:{buttons:[ts]},labels:{},animation:{fade:250,swipe:500,easing:{fade:"ease",swipe:"ease-out",navigation:"ease-in-out"}},carousel:{finite:!1,preload:2,padding:"16px",spacing:"30%",imageFit:fg,imageProps:{}},controller:{ref:null,focus:!0,aria:!1,touchAction:"none",closeOnPullUp:!1,closeOnPullDown:!1,closeOnBackdropClick:!1,closeOnEscape:!0,preventDefaultWheelX:!0,preventDefaultWheelY:!1,disableSwipeNavigation:!1},portal:{},noScroll:{disabled:!1},on:{},styles:{},className:""};function un(t,e){return{name:t,component:e}}function Tt(t,e){return{module:t,children:e}}function vg(t,e,n){return t.module.name===e?n(t):t.children?[Tt(t.module,t.children.flatMap(r=>{var s;return(s=vg(r,e,n))!==null&&s!==void 0?s:[]}))]:[t]}function rs(t,e,n){return t.flatMap(r=>{var s;return(s=vg(r,e,n))!==null&&s!==void 0?s:[]})}function h_(t,e=[],n=[]){let r=t;const s=g=>{const m=[...r];for(;m.length>0;){const p=m.pop();if(p?.module.name===g)return!0;p?.children&&m.push(...p.children)}return!1},o=(g,m)=>{if(g===""){r=[Tt(m,r)];return}r=rs(r,g,p=>[Tt(m,[p])])},i=(g,m)=>{r=rs(r,g,p=>[Tt(p.module,[Tt(m,p.children)])])},a=(g,m,p)=>{r=rs(r,g,y=>{var b;return[Tt(y.module,[...p?[Tt(m)]:[],...(b=y.children)!==null&&b!==void 0?b:[],...p?[]:[Tt(m)]])]})},l=(g,m,p)=>{r=rs(r,g,y=>[...p?[Tt(m)]:[],y,...p?[]:[Tt(m)]])},c=g=>{i(to,g)},d=(g,m)=>{r=rs(r,g,p=>[Tt(m,p.children)])},u=g=>{r=rs(r,g,m=>m.children)},f=g=>{n.push(g)};return e.forEach(g=>{g({contains:s,addParent:o,append:i,addChild:a,addSibling:l,addModule:c,replace:d,remove:u,augment:f})}),{config:r,augmentation:g=>n.reduce((m,p)=>p(m),g)}}const xg=F.createContext(null),yc=ln("useA11yContext","A11yContext",xg);function g_({children:t}){const[e,n]=F.useState(!1),[r,s]=F.useState(!1),o=F.useMemo(()=>({focusWithin:e,trackFocusWithin:(a,l)=>{const c=d=>u=>{var f;u.currentTarget.contains(u.relatedTarget)||n(d),(f=d?a:l)===null||f===void 0||f(u)};return{onFocus:c(!0),onBlur:c(!1)}},autoPlaying:r,setAutoPlaying:s}),[e,r]);return F.createElement(xg.Provider,{value:o},t)}const wg=F.createContext(null),ss=ln("useDocument","DocumentContext",wg);function p_({nodeRef:t,children:e}){const n=F.useMemo(()=>{const r=o=>{var i;return((i=o||t.current)===null||i===void 0?void 0:i.ownerDocument)||document};return{getOwnerDocument:r,getOwnerWindow:o=>{var i;return((i=r(o))===null||i===void 0?void 0:i.defaultView)||window}}},[t]);return F.createElement(wg.Provider,{value:n},e)}const kg=F.createContext(null),os=ln("useEvents","EventsContext",kg);function m_({children:t}){const[e]=F.useState({});F.useEffect(()=>()=>{Object.keys(e).forEach(r=>delete e[r])},[e]);const n=F.useMemo(()=>{const r=(i,a)=>{var l;(l=e[i])===null||l===void 0||l.splice(0,e[i].length,...e[i].filter(c=>c!==a))};return{publish:(...[i,a])=>{var l;(l=e[i])===null||l===void 0||l.forEach(c=>c(a))},subscribe:(i,a)=>(e[i]||(e[i]=[]),e[i].push(a),()=>r(i,a)),unsubscribe:r}},[e]);return F.createElement(kg.Provider,{value:n},t)}const Cg=F.createContext(null),At=ln("useLightboxProps","LightboxPropsContext",Cg);function Sg({children:t,...e}){return F.createElement(Cg.Provider,{value:e},t)}const _g=F.createContext(null),fn=ln("useLightboxState","LightboxStateContext",_g),Eg=F.createContext(null),b_=ln("useLightboxDispatch","LightboxDispatchContext",Eg);function y_(t,e){switch(e.type){case"swipe":{const{slides:n}=t,r=e?.increment||0,s=t.globalIndex+r,o=hc(s,n.length),i=mc(n,o),a=r||e.duration!==void 0?{increment:r,duration:e.duration,easing:e.easing}:void 0;return{slides:n,currentIndex:o,globalIndex:s,currentSlide:i,animation:a}}case"update":return e.slides!==t.slides||e.index!==t.currentIndex?{slides:e.slides,currentIndex:e.index,globalIndex:e.index,currentSlide:mc(e.slides,e.index)}:t;default:throw new Error(gg)}}function v_({slides:t,index:e,children:n}){const[r,s]=F.useReducer(y_,{slides:t,currentIndex:e,globalIndex:e,currentSlide:mc(t,e)}),[o,i]=F.useState(t),[a,l]=F.useState(e);(t!==o||e!==a)&&(i(t),l(e),s({type:"update",slides:t,index:e}));const c=F.useMemo(()=>({...r,state:r,dispatch:s}),[r,s]);return F.createElement(Eg.Provider,{value:s},F.createElement(_g.Provider,{value:c},n))}const Mg=F.createContext(null),x_=ln("useRTLContext","RTLContext",Mg);function w_({isRTL:t,children:e}){const n=F.useMemo(()=>({isRTL:t}),[t]);return F.createElement(Mg.Provider,{value:n},e)}const Ng=F.createContext(null),ao=ln("useTimeouts","TimeoutsContext",Ng);function k_({children:t}){const[e]=F.useState([]);F.useEffect(()=>()=>{e.forEach(r=>window.clearTimeout(r)),e.splice(0,e.length)},[e]);const n=F.useMemo(()=>{const r=i=>{e.splice(0,e.length,...e.filter(a=>a!==i))};return{setTimeout:(i,a)=>{const l=window.setTimeout(()=>{r(l),i()},a);return e.push(l),l},clearTimeout:i=>{i!==void 0&&(r(i),window.clearTimeout(i))}}},[e]);return F.createElement(Ng.Provider,{value:n},t)}const is=F.forwardRef(function({label:e,className:n,icon:r,renderIcon:s,onClick:o,style:i,...a},l){const{styles:c,labels:d}=At(),u=br(d,e);return F.createElement("button",{ref:l,type:"button",title:u,"aria-label":u,className:zt(Ne(a_),n),onClick:o,style:{...i,...c.button},...a},s?s():F.createElement(r,{className:Ne(Pi),style:c.icon}))});function Og(t,e){const n=r=>F.createElement("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",width:"24",height:"24","aria-hidden":"true",focusable:"false",...r},e);return n.displayName=t,n}function Jt(t,e){return Og(t,F.createElement("g",{fill:"currentColor"},F.createElement("path",{d:"M0 0h24v24H0z",fill:"none"}),e))}function C_(t,e){return Og(t,F.createElement(F.Fragment,null,F.createElement("defs",null,F.createElement("mask",{id:"strike"},F.createElement("path",{d:"M0 0h24v24H0z",fill:"white"}),F.createElement("path",{d:"M0 0L24 24",stroke:"black",strokeWidth:4}))),F.createElement("path",{d:"M0.70707 2.121320L21.878680 23.292883",stroke:"currentColor",strokeWidth:2}),F.createElement("g",{fill:"currentColor",mask:"url(#strike)"},F.createElement("path",{d:"M0 0h24v24H0z",fill:"none"}),e)))}const S_=Jt("Close",F.createElement("path",{d:"M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"})),__=Jt("Previous",F.createElement("path",{d:"M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"})),E_=Jt("Next",F.createElement("path",{d:"M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"})),M_=Jt("Loading",F.createElement(F.Fragment,null,Array.from({length:8}).map((t,e,n)=>F.createElement("line",{key:e,x1:"12",y1:"6.5",x2:"12",y2:"1.8",strokeLinecap:"round",strokeWidth:"2.6",stroke:"currentColor",strokeOpacity:1/n.length*(e+1),transform:`rotate(${360/n.length*e}, 12, 12)`})))),N_=Jt("Error",F.createElement("path",{d:"M21.9,21.9l-8.49-8.49l0,0L3.59,3.59l0,0L2.1,2.1L0.69,3.51L3,5.83V19c0,1.1,0.9,2,2,2h13.17l2.31,2.31L21.9,21.9z M5,18 l3.5-4.5l2.5,3.01L12.17,15l3,3H5z M21,18.17L5.83,3H19c1.1,0,2,0.9,2,2V18.17z"})),Cn=dc()?F.useLayoutEffect:F.useEffect;function vc(){const[t,e]=F.useState(!1);return F.useEffect(()=>{var n,r;const s=(n=window.matchMedia)===null||n===void 0?void 0:n.call(window,"(prefers-reduced-motion: reduce)");e(s?.matches);const o=i=>e(i.matches);return(r=s?.addEventListener)===null||r===void 0||r.call(s,"change",o),()=>{var i;return(i=s?.removeEventListener)===null||i===void 0?void 0:i.call(s,"change",o)}},[]),t}function O_(t){let e=0,n=0,r=0;const o=window.getComputedStyle(t).transform.match(/matrix.*\((.+)\)/);if(o){const i=o[1].split(",").map($i);i.length===6?(e=i[4],n=i[5]):i.length===16&&(e=i[12],n=i[13],r=i[14])}return{x:e,y:n,z:r}}function xc(t,e){const n=F.useRef(void 0),r=F.useRef(void 0),s=vc();return Cn(()=>{var o,i,a;if(t.current&&n.current!==void 0&&!s){const{keyframes:l,duration:c,easing:d,onfinish:u}=e(n.current,t.current.getBoundingClientRect(),O_(t.current))||{};if(l&&c){(o=r.current)===null||o===void 0||o.cancel(),r.current=void 0;try{r.current=(a=(i=t.current).animate)===null||a===void 0?void 0:a.call(i,l,{duration:c,easing:d})}catch(f){console.error(f)}r.current&&(r.current.onfinish=()=>{r.current=void 0,u?.()})}}n.current=void 0}),{prepareAnimation:o=>{n.current=o},isAnimationPlaying:()=>{var o;return((o=r.current)===null||o===void 0?void 0:o.playState)==="running"}}}function Tg(){const t=F.useRef(null),e=F.useRef(void 0),[n,r]=F.useState();return{setContainerRef:F.useCallback(o=>{t.current=o,e.current&&(e.current.disconnect(),e.current=void 0);const i=()=>{if(o){const a=window.getComputedStyle(o),l=c=>parseFloat(c)||0;r({width:Math.round(o.clientWidth-l(a.paddingLeft)-l(a.paddingRight)),height:Math.round(o.clientHeight-l(a.paddingTop)-l(a.paddingBottom))})}else r(void 0)};i(),o&&typeof ResizeObserver<"u"&&(e.current=new ResizeObserver(i),e.current.observe(o))},[]),containerRef:t,containerRect:n}}function Vi(){const t=F.useRef(void 0),{setTimeout:e,clearTimeout:n}=ao();return F.useCallback((r,s)=>{n(t.current),t.current=e(r,s>0?s:0)},[e,n])}function We(t){const e=F.useRef(t);return Cn(()=>{e.current=t}),F.useCallback((...n)=>{var r;return(r=e.current)===null||r===void 0?void 0:r.call(e,...n)},[])}function Ag(t,e){typeof t=="function"?t(e):t&&(t.current=e)}function wc(t,e){return F.useMemo(()=>t==null&&e==null?null:n=>{Ag(t,n),Ag(e,n)},[t,e])}function Dg(t,e=!1){const n=F.useRef(!1);Cn(()=>{e&&n.current&&(n.current=!1,t())},[e,t]);const r=F.useCallback(()=>{n.current=!0},[]),s=F.useCallback(()=>{n.current=!1},[]);return{onFocus:r,onBlur:s}}function Hi(){return x_().isRTL}function Ig(){const[t]=F.useState({}),e=F.useCallback((s,o)=>{var i;(i=t[s])===null||i===void 0||i.forEach(a=>{o.isPropagationStopped()||a(o)})},[t]),n=F.useMemo(()=>({onPointerDown:s=>e(ag,s),onPointerMove:s=>e(lg,s),onPointerUp:s=>e(cg,s),onPointerLeave:s=>e(dg,s),onPointerCancel:s=>e(ug,s),onKeyDown:s=>e(lc,s),onKeyUp:s=>e(r_,s),onWheel:s=>e(cc,s)}),[e]),r=F.useCallback((s,o)=>(t[s]||(t[s]=[]),t[s].unshift(o),()=>{const i=t[s];i&&i.splice(0,i.length,...i.filter(a=>a!==o))}),[t]);return{registerSensors:n,subscribeSensors:r}}function Lg(t,e){const n=F.useRef(0),r=Vi(),s=We((...o)=>{n.current=Date.now(),t(o)});return F.useCallback((...o)=>{r(()=>{s(o)},e-(Date.now()-n.current))},[e,s,r])}const kc=mr("slide"),Cc=mr("slide_image");function yr({slide:t,offset:e,render:n,rect:r,imageFit:s,imageProps:o,onClick:i,onLoad:a,onError:l,style:c}){var d,u,f,g,m,p,y,b;const[_,x]=F.useState(Rr),{publish:E}=os(),{setTimeout:C}=ao(),k=F.useRef(null);F.useEffect(()=>{e===0&&E(io(_))},[e,_,E]);const N=We(U=>{("decode"in U?U.decode():Promise.resolve()).catch(()=>{}).then(()=>{U.parentNode&&(x(oo),C(()=>{a?.(U)},0))})}),S=F.useCallback(U=>{k.current=U,U?.complete&&N(U)},[N]),w=F.useCallback(U=>{N(U.currentTarget)},[N]),M=We(()=>{x(so),l?.()}),T=fc(t,s),P=(U,B)=>Number.isFinite(U)?U:B,L=P(Math.max(...((u=(d=t.srcSet)===null||d===void 0?void 0:d.map(U=>U.width))!==null&&u!==void 0?u:[]).concat(t.width?[t.width]:[]).filter(Boolean)),((f=k.current)===null||f===void 0?void 0:f.naturalWidth)||0),$=P(Math.max(...((m=(g=t.srcSet)===null||g===void 0?void 0:g.map(U=>U.height))!==null&&m!==void 0?m:[]).concat(t.height?[t.height]:[]).filter(Boolean)),((p=k.current)===null||p===void 0?void 0:p.naturalHeight)||0),A=L&&$?{maxWidth:`min(${L}px, 100%)`,maxHeight:`min(${$}px, 100%)`}:{maxWidth:"100%",maxHeight:"100%"},D=(y=t.srcSet)===null||y===void 0?void 0:y.slice().sort((U,B)=>U.width-B.width).map(U=>`${U.src} ${U.width}w`).join(", "),O=()=>r&&!T&&t.width&&t.height?r.height/t.height*t.width:Number.MAX_VALUE,z=D&&r&&dc()?`${Math.round(Math.min(O(),r.width))}px`:void 0,{style:H,className:j,...W}=(typeof o=="function"?o(t):o)||{};return F.createElement(F.Fragment,null,F.createElement("img",{ref:S,onLoad:w,onError:M,onClick:i,draggable:!1,className:zt(Ne(Cc()),T&&Ne(Cc("cover")),_!==oo&&Ne(Cc("loading")),j),style:{...A,...c,...H},...W,alt:(b=t.alt)!==null&&b!==void 0?b:"",sizes:z,srcSet:D,src:t.src}),_!==oo&&F.createElement("div",{className:Ne(kc(ZS))},_===Rr&&(n?.iconLoading?n.iconLoading():F.createElement(M_,{className:zt(Ne(Pi),Ne(kc(Rr)))})),_===so&&(n?.iconError?n.iconError():F.createElement(N_,{className:zt(Ne(Pi),Ne(kc(so)))}))))}const T_=F.forwardRef(function({className:e,children:n,onFocus:r,onBlur:s,...o},i){const a=F.useRef(null),[l,c]=F.useState(!1),{trackFocusWithin:d}=yc(),u=We(()=>{if(a.current){const f=window.getComputedStyle(a.current).direction==="rtl";f!==l&&c(f)}});return F.useEffect(u),F.createElement(p_,{nodeRef:a},F.createElement(w_,{isRTL:l},F.createElement("div",{ref:wc(i,a),className:zt(Ne("root"),e),...d(r,s),...o},n)))});var Vt;(function(t){t[t.NONE=0]="NONE",t[t.SWIPE=1]="SWIPE",t[t.PULL=2]="PULL",t[t.ANIMATION=3]="ANIMATION"})(Vt||(Vt={}));function Pg(t,e,n,r,s){F.useEffect(()=>s?()=>{}:ns(t(ag,e),t(lg,n),t(cg,r),t(dg,r),t(ug,r)),[t,e,n,r,s])}var Sn;(function(t){t[t.NONE=0]="NONE",t[t.SWIPE=1]="SWIPE",t[t.PULL=2]="PULL"})(Sn||(Sn={}));const Sc=30;function A_({disableSwipeNavigation:t,closeOnBackdropClick:e},n,r,s,o,i,a,l,c,d,u,f,g,m,p,y){const b=F.useRef(0),_=F.useRef([]),x=F.useRef(void 0),E=F.useRef(0),C=F.useRef(Sn.NONE),k=F.useCallback(L=>{x.current===L.pointerId&&(x.current=void 0,C.current=Sn.NONE);const $=_.current;$.splice(0,$.length,...$.filter(A=>A.pointerId!==L.pointerId))},[]),N=F.useCallback(L=>{k(L),L.persist(),_.current.push(L)},[k]),S=F.useCallback(L=>_.current.find(({pointerId:$})=>L.pointerId===$),[]),w=We(L=>{N(L)}),M=(L,$)=>u&&L>$||d&&L<-$,T=We(L=>{const $=S(L);if($)if(x.current===L.pointerId){const A=Date.now()-E.current,D=b.current;C.current===Sn.SWIPE?Math.abs(D)>.3*s||Math.abs(D)>5&&A{const $=S(L);if($){const A=x.current===L.pointerId;if(L.buttons===0){A&&b.current!==0?T(L):k($);return}const D=L.clientX-$.clientX,O=L.clientY-$.clientY;if(x.current===void 0){const z=H=>{N(L),x.current=L.pointerId,E.current=Date.now(),C.current=H};Math.abs(D)>Math.abs(O)&&Math.abs(D)>Sc&&r(D)?t||(z(Sn.SWIPE),i()):Math.abs(O)>Math.abs(D)&&M(O,Sc)&&(z(Sn.PULL),f())}else A&&(C.current===Sn.SWIPE?(b.current=D,a(D)):C.current===Sn.PULL&&(b.current=O,g(O)))}});Pg(n,w,P,T)}function D_({preventDefaultWheelX:t,preventDefaultWheelY:e}){const n=F.useRef(null),r=We(s=>{const o=Math.abs(s.deltaX)>Math.abs(s.deltaY);(o&&t||!o&&e||s.ctrlKey)&&s.preventDefault()});return F.useCallback(s=>{var o;s?s.addEventListener("wheel",r,{passive:!1}):(o=n.current)===null||o===void 0||o.removeEventListener("wheel",r),n.current=s},[r])}function I_(t,e,n,r,s,o,i,a,l){const c=F.useRef(0),d=F.useRef(0),u=F.useRef(void 0),f=F.useRef(void 0),g=F.useRef(0),m=F.useRef(void 0),p=F.useRef(0),{setTimeout:y,clearTimeout:b}=ao(),_=F.useCallback(()=>{u.current&&(b(u.current),u.current=void 0)},[b]),x=F.useCallback(()=>{f.current&&(b(f.current),f.current=void 0)},[b]),E=We(()=>{t!==Vt.SWIPE&&(c.current=0,p.current=0,_(),x())});F.useEffect(E,[t,E]);const C=We(N=>{f.current=void 0,c.current===N&&l(c.current)}),k=We(N=>{if(N.ctrlKey||Math.abs(N.deltaY)>Math.abs(N.deltaX))return;const S=w=>{g.current=w,b(m.current),m.current=w>0?y(()=>{g.current=0,m.current=void 0},300):void 0};if(t===Vt.NONE){if(Math.abs(N.deltaX)<=1.2*Math.abs(g.current)){S(N.deltaX);return}if(!n(-N.deltaX))return;if(d.current+=N.deltaX,_(),Math.abs(d.current)>30)d.current=0,S(0),p.current=Date.now(),o();else{const w=d.current;u.current=y(()=>{u.current=void 0,w===d.current&&(d.current=0)},s)}}else if(t===Vt.SWIPE){let w=c.current-N.deltaX;if(w=Math.min(Math.abs(w),r)*Math.sign(w),c.current=w,i(w),x(),Math.abs(w)>.2*r){S(N.deltaX),a(w,Date.now()-p.current);return}f.current=y(()=>C(w),2*s)}else S(N.deltaX)});F.useEffect(()=>e(cc,k),[e,k])}const $g=mr("container"),Fg=F.createContext(null),_n=ln("useController","ControllerContext",Fg);function L_({children:t,...e}){var n;const{carousel:r,animation:s,controller:o,on:i,styles:a,render:l}=e,{closeOnPullUp:c,closeOnPullDown:d,preventDefaultWheelX:u,preventDefaultWheelY:f}=o,[g,m]=F.useState(),p=fn(),y=b_(),[b,_]=F.useState(Vt.NONE),x=F.useRef(0),E=F.useRef(0),C=F.useRef(1),{registerSensors:k,subscribeSensors:N}=Ig(),{subscribe:S,publish:w}=os(),M=Vi(),T=Vi(),P=Vi(),{containerRef:L,setContainerRef:$,containerRect:A}=Tg(),D=wc(D_({preventDefaultWheelX:u,preventDefaultWheelY:f}),$),O=F.useRef(null),z=wc(O,void 0),{getOwnerDocument:H}=ss(),j=Hi(),W=se=>(j?-1:1)*(typeof se=="number"?se:1),U=We(()=>{var se;return(se=L.current)===null||se===void 0?void 0:se.focus()}),B=We(()=>e),G=We(()=>p),V=F.useCallback(se=>w(rr,se),[w]),X=F.useCallback(se=>w(sr,se),[w]),K=F.useCallback(()=>w(ts),[w]),Z=se=>!(r.finite&&(W(se)>0&&p.currentIndex===0||W(se)<0&&p.currentIndex===p.slides.length-1)),ee=se=>{var Oe;x.current=se,(Oe=L.current)===null||Oe===void 0||Oe.style.setProperty(dt("swipe_offset"),`${Math.round(se)}px`)},ne=se=>{var Oe,Le;E.current=se,C.current=(()=>{const st=d&&se>0?se:c&&se<0?-se:0;return Math.min(Math.max(uc(1-st/60*(1-.5),2),.5),1)})(),(Oe=L.current)===null||Oe===void 0||Oe.style.setProperty(dt("pull_offset"),`${Math.round(se)}px`),(Le=L.current)===null||Le===void 0||Le.style.setProperty(dt("pull_opacity"),`${C.current}`)},{prepareAnimation:he}=xc(O,(se,Oe,Le)=>{if(O.current&&A)return{keyframes:[{transform:`translate(0, ${se.rect.y-Oe.y+Le.y}px)`,opacity:se.opacity},{transform:"translate(0, 0)",opacity:1}],duration:se.duration,easing:s.easing.fade}}),J=(se,Oe)=>{if(c||d){ne(se);let Le=0;O.current&&(Le=s.fade*(Oe?2:1),he({rect:O.current.getBoundingClientRect(),opacity:C.current,duration:Le})),P(()=>{ne(0),_(Vt.NONE)},Le),_(Vt.ANIMATION),Oe||K()}},{prepareAnimation:le,isAnimationPlaying:ue}=xc(O,(se,Oe,Le)=>{var Ie;if(O.current&&A&&(!((Ie=p.animation)===null||Ie===void 0)&&Ie.duration)){const Be=Fi(r.spacing),st=(Be.percent?Be.percent*A.width/100:Be.pixel)||0;return{keyframes:[{transform:`translate(${W(p.globalIndex-se.index)*(A.width+st)+se.rect.x-Oe.x+Le.x}px, 0)`},{transform:"translate(0, 0)"}],duration:p.animation.duration,easing:p.animation.easing}}}),ae=We(se=>{var Oe,Le;const Ie=se.offset||0,Be=Ie?s.swipe:(Oe=s.navigation)!==null&&Oe!==void 0?Oe:s.swipe,st=!Ie&&!ue()?s.easing.navigation:s.easing.swipe;let{direction:ut}=se;const je=(Le=se.count)!==null&&Le!==void 0?Le:1;let nt=Vt.ANIMATION,Ye=Be*je;if(!ut){const ge=A?.width,me=se.duration||0,pe=ge?Be/ge*Math.abs(Ie):Be;je!==0?(me0?rr:sr):Ye=Be/2}let te=0;ut===rr?Z(W(1))?te=-je:(nt=Vt.NONE,Ye=Be):ut===sr&&(Z(W(-1))?te=je:(nt=Vt.NONE,Ye=Be)),Ye=Math.round(Ye),T(()=>{ee(0),_(Vt.NONE)},Ye),O.current&&le({rect:O.current.getBoundingClientRect(),index:p.globalIndex}),_(nt),w(ac,{type:"swipe",increment:te,duration:Ye,easing:st})});F.useEffect(()=>{var se,Oe;!((se=p.animation)===null||se===void 0)&&se.increment&&(!((Oe=p.animation)===null||Oe===void 0)&&Oe.duration)&&M(()=>y({type:"swipe",increment:0}),p.animation.duration)},[p.animation,y,M]);const ve=[N,Z,A?.width||0,s.swipe,()=>_(Vt.SWIPE),se=>ee(se),(se,Oe)=>ae({offset:se,duration:Oe,count:1}),se=>ae({offset:se,count:0})],fe=[()=>{d&&_(Vt.PULL)},se=>ne(se),se=>J(se),se=>J(se,!0)];A_(o,...ve,c,d,...fe,K),I_(b,...ve);const ye=We(()=>{o.focus&&H().querySelector(`.${Ne(sc)} .${Ne($g())}`)&&U()});F.useEffect(ye,[ye]);const we=We(()=>{var se;(se=i.view)===null||se===void 0||se.call(i,{index:p.currentIndex})});F.useEffect(we,[p.globalIndex,we]),F.useEffect(()=>ns(S(rr,se=>ae({direction:rr,...se})),S(sr,se=>ae({direction:sr,...se})),S(ac,se=>y(se))),[S,ae,y]);const He=F.useMemo(()=>({prev:V,next:X,close:K,focus:U,slideRect:A?l_(A,r.padding):{width:0,height:0},containerRect:A||{width:0,height:0},subscribeSensors:N,containerRef:L,setCarouselRef:z,toolbarWidth:g,setToolbarWidth:m}),[V,X,K,U,N,A,L,z,g,m,r.padding]);return F.useImperativeHandle(o.ref,()=>({prev:V,next:X,close:K,focus:U,getLightboxProps:B,getLightboxState:G}),[V,X,K,U,B,G]),F.createElement("div",{ref:D,className:zt(Ne($g()),Ne(es)),style:{...b===Vt.SWIPE?{[dt("swipe_offset")]:`${Math.round(x.current)}px`}:null,...b===Vt.PULL?{[dt("pull_offset")]:`${Math.round(E.current)}px`,[dt("pull_opacity")]:`${C.current}`}:null,...o.touchAction!=="none"?{[dt("controller_touch_action")]:o.touchAction}:null,...a.container},tabIndex:-1,...k},A&&F.createElement(Fg.Provider,{value:He},t,(n=l.controls)===null||n===void 0?void 0:n.call(l)))}const P_=un(to,L_);function vr(t){return pr(eg,t)}function zg(t){return pr(ic,t)}function $_({slide:t,offset:e}){const n=F.useRef(null),{currentIndex:r,slides:s}=fn(),{slideRect:o,focus:i}=_n(),{render:a,carousel:{imageFit:l,imageProps:c},on:{click:d},styles:{slide:u},labels:f}=At(),{getOwnerDocument:g}=ss(),m=e!==0;F.useEffect(()=>{var y;m&&(!((y=n.current)===null||y===void 0)&&y.contains(g().activeElement))&&i()},[m,i,g]);const p=()=>{var y,b,_,x;let E=(y=a.slide)===null||y===void 0?void 0:y.call(a,{slide:t,offset:e,rect:o});return!E&&or(t)&&(E=F.createElement(yr,{slide:t,offset:e,render:a,rect:o,imageFit:l,imageProps:c,onClick:m?void 0:()=>d?.({index:r})})),E?F.createElement(F.Fragment,null,(b=a.slideHeader)===null||b===void 0?void 0:b.call(a,{slide:t}),((_=a.slideContainer)!==null&&_!==void 0?_:(({children:C})=>C))({slide:t,children:E}),(x=a.slideFooter)===null||x===void 0?void 0:x.call(a,{slide:t})):null};return F.createElement("div",{ref:n,className:zt(Ne(zg()),!m&&Ne(zg("current")),Ne(es)),...u_(m),style:u,role:"group","aria-roledescription":br(f,"Slide"),"aria-label":mg(f,s,r+e)},p())}function F_(){const t=At().styles.slide;return F.createElement("div",{className:Ne(ic),style:t})}function z_({carousel:t,labels:e}){const{slides:n,currentIndex:r,globalIndex:s}=fn(),{setCarouselRef:o}=_n(),{autoPlaying:i,focusWithin:a}=yc(),l=Fi(t.spacing),c=Fi(t.padding),d=yg(t,n,1),u=[];if(gc(n))for(let f=r-d;f<=r+d;f+=1){const g=pc(n,f),m=s-r+f,p=t.finite&&(f<0||f>n.length-1);u.push(p?{key:m}:{key:[`${m}`,bg(g)].filter(Boolean).join("|"),offset:f-r,slide:g})}return F.createElement("div",{ref:o,className:zt(Ne(vr()),u.length>0&&Ne(vr("with_slides"))),style:{[`${dt(vr("slides_count"))}`]:u.length,[`${dt(vr("spacing_px"))}`]:l.pixel||0,[`${dt(vr("spacing_percent"))}`]:l.percent||0,[`${dt(vr("padding_px"))}`]:c.pixel||0,[`${dt(vr("padding_percent"))}`]:c.percent||0},role:"region","aria-live":i&&!a?"off":"polite","aria-roledescription":br(e,"Carousel"),"aria-label":br(e,"Photo gallery")},u.map(({key:f,slide:g,offset:m})=>g?F.createElement($_,{key:f,slide:g,offset:m}):F.createElement(F_,{key:f})))}const V_=un(eg,z_);function Vg(){const{carousel:t}=At(),{slides:e,currentIndex:n}=fn(),r=e.length===0||t.finite&&n===0,s=e.length===0||t.finite&&n===e.length-1;return{prevDisabled:r,nextDisabled:s}}function Hg(t){var e;const n=Hi(),{publish:r}=os(),{animation:s,controller:o}=At(),{prevDisabled:i,nextDisabled:a}=Vg(),l=((e=s.navigation)!==null&&e!==void 0?e:s.swipe)/2,c=Lg(()=>r(rr),l),d=Lg(()=>r(sr),l),u=We(f=>{switch(f.key){case s_:if(!o.closeOnEscape)return;r(ts);break;case o_:(n?a:i)||(n?d:c)();break;case i_:(n?i:a)||(n?c:d)();break;default:return}f.stopPropagation()});F.useEffect(()=>t(lc,u),[t,u])}function Ug({label:t,icon:e,renderIcon:n,action:r,onClick:s,disabled:o,style:i}){return F.createElement(is,{label:t,icon:e,renderIcon:n,className:Ne(`navigation_${r}`),disabled:o,onClick:s,style:i,...Dg(_n().focus,o)})}function H_({render:{buttonPrev:t,buttonNext:e,iconPrev:n,iconNext:r},styles:s}){const{prev:o,next:i,subscribeSensors:a}=_n(),{prevDisabled:l,nextDisabled:c}=Vg();return Hg(a),F.createElement(F.Fragment,null,t?t():F.createElement(Ug,{label:"Previous",action:rr,icon:__,renderIcon:n,style:s.navigationPrev,disabled:l,onClick:o}),e?e():F.createElement(Ug,{label:"Next",action:sr,icon:E_,renderIcon:r,style:s.navigationNext,disabled:c,onClick:i}))}const U_=un(YS,H_),Bg=Ne(t_),B_=Ne(og);function j_(t){return"style"in t}function jg(t,e,n){const r=window.getComputedStyle(t),s=n?"padding-left":"padding-right",o=n?r.paddingLeft:r.paddingRight,i=t.style.getPropertyValue(s);return t.style.setProperty(s,`${($i(o)||0)+e}px`),()=>{i?t.style.setProperty(s,i):t.style.removeProperty(s)}}function W_({noScroll:{disabled:t},children:e}){const n=Hi(),{getOwnerDocument:r,getOwnerWindow:s}=ss();return F.useEffect(()=>{if(t)return()=>{};const o=[],i=s(),{body:a,documentElement:l}=r(),c=Math.round(i.innerWidth-l.clientWidth);if(c>0){o.push(jg(a,c,n));const d=a.getElementsByTagName("*");for(let u=0;u{a.classList.remove(Bg),o.forEach(d=>d())}},[n,t,r,s]),F.createElement(F.Fragment,null,e)}const G_=un(KS,W_);function Wg(t){return pr(sc,t)}function Gg(t,e,n){const r=t.getAttribute(e);return t.setAttribute(e,n),()=>{r?t.setAttribute(e,r):t.removeAttribute(e)}}function Y_({portal:{root:t,container:{className:e,style:n,...r}={}},animation:s,styles:o,className:i,on:a,close:l,labels:c,children:d}){const[u,f]=F.useState(!1),[g,m]=F.useState(!1),p=F.useRef([]),y=F.useRef(null),{setTimeout:b}=ao(),{subscribe:_}=os(),E=vc()?0:s.fade;F.useEffect(()=>(f(!0),()=>{f(!1),m(!1)}),[]);const C=We(()=>{p.current.forEach(w=>w()),p.current=[]}),k=We(()=>{var w;m(!1),C(),(w=a.exiting)===null||w===void 0||w.call(a),b(()=>{var M;(M=a.exited)===null||M===void 0||M.call(a),l()},E)});F.useEffect(()=>_(ts,k),[_,k]);const N=We(w=>{var M,T,P;f_(w),m(!0),(M=a.entering)===null||M===void 0||M.call(a);const L=(P=(T=w.parentNode)===null||T===void 0?void 0:T.children)!==null&&P!==void 0?P:[];for(let $=0;${var $,A;(A=($=y.current)===null||$===void 0?void 0:$.focus)===null||A===void 0||A.call($)}),b(()=>{var $;($=a.entered)===null||$===void 0||$.call(a)},E)}),S=F.useCallback(w=>{w?N(w):C()},[N,C]);return u?Tn.createPortal(F.createElement(T_,{ref:S,className:zt(i,e,Ne(Wg()),Ne(og),g&&Ne(Wg("open"))),"aria-modal":!0,role:"dialog","aria-label":br(c,"Lightbox"),style:{...s.fade!==bc.animation.fade?{[dt("fade_animation_duration")]:`${E}ms`}:null,...s.easing.fade!==bc.animation.easing.fade?{[dt("fade_animation_timing_function")]:s.easing.fade}:null,...o.root,...n},onFocus:w=>{y.current||(y.current=w.relatedTarget)},...r},d),(typeof t=="function"?t():t)||document.body):null}const K_=un(sc,Y_);function X_({children:t}){return F.createElement(F.Fragment,null,t)}const q_=un(XS,X_);function Z_(t){return pr(tg,t)}function Q_({toolbar:{buttons:t},render:{buttonClose:e,iconClose:n},styles:r}){const{close:s,setToolbarWidth:o}=_n(),{setContainerRef:i,containerRect:a}=Tg();Cn(()=>{o(a?.width)},[o,a?.width]);const l=()=>e?e():F.createElement(is,{key:ts,label:"Close",icon:S_,renderIcon:n,onClick:s});return F.createElement("div",{ref:i,style:r.toolbar,className:Ne(Z_())},t?.map(c=>c===ts?l():c))}const J_=un(tg,Q_);function Yg(t,e){var n;return F.createElement(t.module.component,{key:t.module.name,...e},(n=t.children)===null||n===void 0?void 0:n.map(r=>Yg(r,e)))}function R_(t,e={}){const{easing:n,...r}=t,{easing:s,...o}=e;return{easing:{...n,...s},...r,...o}}function e2({carousel:t,animation:e,render:n,toolbar:r,controller:s,noScroll:o,on:i,plugins:a,slides:l,index:c,...d}){const{animation:u,carousel:f,render:g,toolbar:m,controller:p,noScroll:y,on:b,slides:_,index:x,plugins:E,...C}=bc,{config:k,augmentation:N}=h_([Tt(K_,[Tt(G_,[Tt(P_,[Tt(V_),Tt(J_),Tt(U_)])])])],a||E),S=N({animation:R_(u,e),carousel:{...f,...t},render:{...g,...n},toolbar:{...m,...r},controller:{...p,...s},noScroll:{...y,...o},on:{...b,...i},...C,...d});return S.open?F.createElement(Sg,{...S},F.createElement(v_,{slides:l||_,index:$i(c||x)},F.createElement(k_,null,F.createElement(m_,null,F.createElement(g_,null,Yg(Tt(q_,k),S)))))):null}const Kg={minZoom:1,maxZoomPixelRatio:1,zoomInMultiplier:2,doubleTapDelay:300,doubleClickDelay:500,doubleClickMaxStops:2,keyboardMoveDistance:50,wheelZoomDistanceFactor:100,pinchZoomDistanceFactor:100,pinchZoomV4:!1,scrollToZoom:!1,maxZoom:8};function t2(t){return Math.min(Math.max(t,Number.EPSILON),1)}function Xg(t){const{minZoom:e,...n}={...Kg,...t};return{minZoom:t2(e),...n}}function n2(t,e,n,r){const s=F.useRef(void 0),o=F.useRef(void 0),{zoom:i}=At().animation,a=vc(),l=We(()=>{var c,d,u;if((c=s.current)===null||c===void 0||c.cancel(),s.current=void 0,o.current&&r?.current){try{s.current=(u=(d=r.current).animate)===null||u===void 0?void 0:u.call(d,[{transform:o.current},{transform:`scale(${t}) translateX(${e}px) translateY(${n}px)`}],{duration:a?0:i??500,easing:s.current?"ease-out":"ease-in-out"})}catch(f){console.error(f)}o.current=void 0,s.current&&(s.current.onfinish=()=>{s.current=void 0})}});return Cn(l,[t,e,n,l]),F.useCallback(()=>{o.current=r?.current?window.getComputedStyle(r.current).transform:void 0},[r])}function r2(t,e){const{on:n}=At(),r=We(()=>{var s;e||(s=n.zoom)===null||s===void 0||s.call(n,{zoom:t})});F.useEffect(r,[t,r])}function Ui(){const{zoom:t}=At();return Xg(t)}function s2(t,e){var n;const r=typeof t=="function"?(n=t(e))!==null&&n!==void 0?n:Kg.maxZoom:t;return Math.max(r,1)}function o2(t,e){var n,r;let s={width:0,height:0},o={width:0,height:0};const{currentSlide:i}=fn(),{imageFit:a}=At().carousel,{maxZoomPixelRatio:l,maxZoom:c}=Ui();if(t&&i){const u={...i,...e};if(or(u)){const f=fc(u,a),g=Math.max(...(((n=u.srcSet)===null||n===void 0?void 0:n.map(p=>p.width))||[]).concat(u.width?[u.width]:[])),m=Math.max(...(((r=u.srcSet)===null||r===void 0?void 0:r.map(p=>p.height))||[]).concat(u.height?[u.height]:[]));g>0&&m>0&&t.width>0&&t.height>0&&(o=f?{width:Math.round(Math.min(g,t.width/t.height*m)),height:Math.round(Math.min(m,t.height/t.width*g))}:{width:g,height:m},o={width:o.width*l,height:o.height*l},s=f?{width:Math.min(t.width,o.width,g),height:Math.min(t.height,o.height,m)}:{width:Math.round(Math.min(t.width,t.height/m*g,g)),height:Math.round(Math.min(t.height,t.width/g*m,m))})}else t.width>0&&t.height>0&&(e&&e.width>0&&e.height>0?s={width:Math.min(t.width,e.width),height:Math.min(t.height,e.height)}:s={width:t.width,height:t.height})}const d=i&&s.width?or(i)?Math.max(uc(o.width/s.width,5),1):s2(c,i):1;return{imageRect:s,maxZoom:d}}function qg(t,e){return Math.hypot(t.clientX-e.clientX,t.clientY-e.clientY)}function Zg(t,e,n=100,r=2){return t*Math.min(1+Math.abs(e/n),r)**Math.sign(e)}function i2(t,e,n,r,s,o,i,a,l){const c=F.useRef([]),d=F.useRef(0),u=F.useRef(void 0),{globalIndex:f}=fn(),{getOwnerWindow:g}=ss(),{containerRef:m,subscribeSensors:p}=_n(),{keyboardMoveDistance:y,zoomInMultiplier:b,wheelZoomDistanceFactor:_,scrollToZoom:x,doubleTapDelay:E,doubleClickDelay:C,doubleClickMaxStops:k,pinchZoomDistanceFactor:N,pinchZoomV4:S}=Ui(),w=F.useCallback(z=>{if(m.current){const{pageX:H,pageY:j}=z,{scrollX:W,scrollY:U}=g(),{left:B,top:G,width:V,height:X}=m.current.getBoundingClientRect();return[H-B-W-V/2,j-G-U-X/2]}return[]},[m,g]),M=We(z=>{const{key:H,metaKey:j,ctrlKey:W}=z,U=j||W,B=()=>{z.preventDefault(),z.stopPropagation()};if(t>1){const G=(V,X)=>{B(),a(V,X)};H==="ArrowDown"?G(0,y):H==="ArrowUp"?G(0,-y):H==="ArrowLeft"?G(-y,0):H==="ArrowRight"&&G(y,0)}H==="+"||U&&H==="="?(B(),s()):H==="-"||U&&H==="_"?(B(),o()):U&&H==="0"&&(B(),i(1))}),T=We(z=>{if((z.ctrlKey||x)&&Math.abs(z.deltaY)>Math.abs(z.deltaX)){z.stopPropagation(),i(Zg(t,-z.deltaY,_),!0,...w(z));return}t>1&&(z.stopPropagation(),x||a(z.deltaX,z.deltaY))}),P=F.useCallback(z=>{const H=c.current;H.splice(0,H.length,...H.filter(j=>j.pointerId!==z.pointerId))},[]),L=F.useCallback(z=>{P(z),z.persist(),c.current.push(z)},[P]),$=We(z=>{var H;const j=c.current;if(z.pointerType==="mouse"&&z.buttons>1||!(!((H=l?.current)===null||H===void 0)&&H.contains(z.target)))return;t>1&&z.stopPropagation();const{timeStamp:W}=z;if(j.length===0&&W-d.current<(z.pointerType==="touch"?E:C)){d.current=0;const U=t>=1?t!==n?t*Math.max(n**(1/k),b):1:t!==e?t/Math.max(e**(-1/k),b):1;i(U,!1,...w(z))}else d.current=W;if(L(z),j.length===2){const U=qg(j[0],j[1]);u.current={previousDistance:U,initialDistance:Math.max(U,1),initialZoom:t}}}),A=We(z=>{const H=c.current,j=H.find(W=>W.pointerId===z.pointerId);if(H.length===2&&u.current){z.stopPropagation(),L(z);const W=qg(H[0],H[1]),U=S?u.current.initialZoom/u.current.initialDistance*W:Zg(t,W-u.current.previousDistance,N);i(U,!0,...H.map(B=>w(B)).reduce((B,G)=>G.map((V,X)=>B[X]+V/2))),u.current.previousDistance=W;return}t>1&&(z.stopPropagation(),j&&(H.length===1&&a((j.clientX-z.clientX)/t,(j.clientY-z.clientY)/t),L(z)))}),D=F.useCallback(z=>{const H=c.current;H.length===2&&H.find(j=>j.pointerId===z.pointerId)&&(u.current=void 0),P(z)},[P]),O=F.useCallback(()=>{const z=c.current;z.splice(0,z.length),d.current=0,u.current=void 0},[]);Pg(p,$,A,D,r),F.useEffect(O,[f,O]),F.useEffect(()=>r?()=>{}:ns(O,p(lc,M),p(cc,T)),[r,p,O,M,T])}function a2(t,e,n){const[r,s]=F.useState(1),[o,i]=F.useState(0),[a,l]=F.useState(0),c=n2(r,o,a,n),{currentSlide:d,globalIndex:u}=fn(),{containerRect:f,slideRect:g}=_n(),{minZoom:m,zoomInMultiplier:p}=Ui(),y=d&&or(d)?d.src:void 0,b=!n?.current;Cn(()=>{s(1),i(0),l(0)},[u,y]);const _=F.useCallback((N,S,w)=>{const M=w||r,T=o-(N||0),P=a-(S||0),L=(t.width*M-g.width)/2/M,$=(t.height*M-g.height)/2/M;i(Math.min(Math.abs(T),Math.max(L,0))*Math.sign(T)),l(Math.min(Math.abs(P),Math.max($,0))*Math.sign(P))},[r,o,a,g,t.width,t.height]),x=F.useCallback((N,S,w,M)=>{const T=uc(N+.01m?N:m:e,5);S||c(),_(w?w*(1/r-1/T):0,M?M*(1/r-1/T):0,T),s(T)},[r,m,e,_,c]),E=We(()=>{r>1&&(r>e&&x(e,!0),_())});Cn(E,[f.width,f.height,E]);const C=F.useCallback(()=>{const N=r*p;x(r<1&&N>1?1:N)},[r,p,x]),k=F.useCallback(()=>{const N=r/p;x(r>1&&N<1?1:N)},[r,p,x]);return{zoom:r,offsetX:o,offsetY:a,disabled:b,changeOffsets:_,changeZoom:x,zoomIn:C,zoomOut:k}}const Qg=F.createContext(null),_c=ln("useZoom","ZoomControllerContext",Qg);function l2({children:t}){const[e,n]=F.useState(),{slideRect:r}=_n(),{ref:s,minZoom:o}=Ui(),{imageRect:i,maxZoom:a}=o2(r,e?.imageDimensions),{zoom:l,offsetX:c,offsetY:d,disabled:u,changeZoom:f,changeOffsets:g,zoomIn:m,zoomOut:p}=a2(i,a,e?.zoomWrapperRef);r2(l,u),i2(l,o,a,u,m,p,f,g,e?.zoomWrapperRef);const y=F.useMemo(()=>({zoom:l,minZoom:o,maxZoom:a,offsetX:c,offsetY:d,disabled:u,zoomIn:m,zoomOut:p,changeZoom:f}),[l,o,a,c,d,u,m,p,f]);F.useImperativeHandle(s,()=>y,[y]);const b=F.useMemo(()=>({...y,setZoomWrapper:n}),[y,n]);return F.createElement(Qg.Provider,{value:b},t)}const c2=Jt("ZoomIn",F.createElement(F.Fragment,null,F.createElement("path",{d:"M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"}),F.createElement("path",{d:"M12 10h-2v2H9v-2H7V9h2V7h1v2h2v1z"}))),d2=Jt("ZoomOut",F.createElement("path",{d:"M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14zM7 9h5v1H7z"})),Jg=F.forwardRef(function({zoomIn:e,onLoseFocus:n},r){const s=F.useRef(!1),o=F.useRef(!1),{zoom:i,minZoom:a,maxZoom:l,zoomIn:c,zoomOut:d,disabled:u}=_c(),{render:f}=At(),g=u||(e?i>=l:i<=a);return F.useEffect(()=>{g&&s.current&&o.current&&n(),g||(s.current=!0)},[g,n]),F.createElement(is,{ref:r,disabled:g,label:e?"Zoom in":"Zoom out",icon:e?c2:d2,renderIcon:e?f.iconZoomIn:f.iconZoomOut,onClick:e?c:d,onFocus:()=>{o.current=!0},onBlur:()=>{o.current=!1}})});function u2(){const t=F.useRef(null),e=F.useRef(null),{focus:n}=_n(),r=F.useCallback(i=>{var a,l;!((a=i.current)===null||a===void 0)&&a.disabled?n():(l=i.current)===null||l===void 0||l.focus()},[n]),s=F.useCallback(()=>r(t),[r]),o=F.useCallback(()=>r(e),[r]);return F.createElement(F.Fragment,null,F.createElement(Jg,{zoomIn:!0,ref:t,onLoseFocus:o}),F.createElement(Jg,{ref:e,onLoseFocus:s}))}function f2(){const{render:t}=At(),e=_c();return t.buttonZoom?F.createElement(F.Fragment,null,t.buttonZoom(e)):F.createElement(u2,null)}function h2(t){var e;return(((e=t.srcSet)===null||e===void 0?void 0:e.length)||0)>0}function g2({current:t,preload:e},{type:n,source:r}){switch(n){case"fetch":return t?{current:t,preload:r}:{current:r};case"done":return r===e?{current:r}:{current:t,preload:e};default:throw new Error(gg)}}function p2(t){var e,n;const[{current:r,preload:s},o]=F.useReducer(g2,{}),{slide:i,rect:a,imageFit:l,render:c,interactive:d}=t,u=i.srcSet.sort((C,k)=>C.width-k.width),f=(e=i.width)!==null&&e!==void 0?e:u[u.length-1].width,g=(n=i.height)!==null&&n!==void 0?n:u[u.length-1].height,m=fc(i,l),p=Math.max(...u.map(C=>C.width)),y=Math.min((m?Math.max:Math.min)(a.width,f*(a.height/g)),p),b=c_(),_=We(()=>{var C;const k=(C=u.find(N=>N.width>=y*b))!==null&&C!==void 0?C:u[u.length-1];(!r||u.findIndex(N=>N.src===r)N===k))&&o({type:"fetch",source:k.src})});Cn(_,[a.width,a.height,b,_]);const x=We(C=>o({type:"done",source:C})),E={WebkitTransform:d?"initial":"translateZ(0)"};return m||Object.assign(E,a.width/a.heightx(s),render:{...c,iconLoading:()=>null,iconError:()=>null}}),r&&F.createElement(yr,{key:"current",...t,slide:{...i,src:r,srcSet:void 0},style:E}))}function m2({render:t,slide:e,offset:n,rect:r}){var s;const[o,i]=F.useState(),a=F.useRef(null),l=or(e),{zoom:c,maxZoom:d,offsetX:u,offsetY:f,setZoomWrapper:g}=_c(),m=c>1,{carousel:p,on:y}=At(),{currentIndex:b}=fn();Cn(()=>{if(n!==0||l||!a.current)return()=>{};const x=()=>{const C=a.current;if(!C)return;let k=0,N=0;for(const S of C.children)S instanceof HTMLElement&&(k=Math.max(k,S.offsetWidth),N=Math.max(N,S.offsetHeight));i(S=>S&&S.width===k&&S.height===N?S:{width:k,height:N})};if(x(),typeof ResizeObserver>"u")return()=>{};const E=new ResizeObserver(x);for(const C of a.current.children)E.observe(C);return()=>E.disconnect()},[n,l,r]),Cn(()=>n===0?(g({zoomWrapperRef:a,imageDimensions:o}),()=>g(void 0)):()=>{},[n,o,g]);let _=(s=t.slide)===null||s===void 0?void 0:s.call(t,{slide:e,offset:n,rect:r,zoom:c,maxZoom:d});if(!_&&l){const x={slide:e,offset:n,rect:r,render:t,imageFit:p.imageFit,imageProps:p.imageProps,onClick:n===0?()=>{var E;return(E=y.click)===null||E===void 0?void 0:E.call(y,{index:b})}:void 0};_=h2(e)?F.createElement(p2,{...x,slide:e,interactive:m,rect:n===0?{width:r.width*c,height:r.height*c}:r}):F.createElement(yr,{onLoad:E=>i({width:E.naturalWidth,height:E.naturalHeight}),...x})}return _?F.createElement("div",{ref:a,className:zt(Ne(sg),Ne(es),Ne(ig),m&&Ne(n_)),style:n===0?{transform:`scale(${c}) translateX(${u}px) translateY(${f}px)`}:void 0},_):null}const b2=({augment:t,addModule:e})=>{t(({zoom:n,toolbar:r,render:s,controller:o,...i})=>{const a=Xg(n);return{zoom:a,toolbar:zi(r,rg,F.createElement(f2,null)),render:{...s,slide:l=>{var c,d;return or(l.slide)||l.slide.type!=null&&(!((c=a.supports)===null||c===void 0)&&c.includes(l.slide.type))?F.createElement(m2,{render:s,...l}):(d=s.slide)===null||d===void 0?void 0:d.call(s,l)}},controller:{...o,preventDefaultWheelY:a.scrollToZoom},...i}}),e(un(rg,l2))},y2={separator:"/",container:{}},Rg=t=>({...y2,...t});function v2({counter:t}){const{slides:e,currentIndex:n}=fn(),{separator:r,container:{className:s,...o},className:i,...a}=Rg(t);return e.length===0?null:F.createElement("div",{className:zt(Ne("counter"),s||i),...a,...o,"aria-hidden":!0},n+1," ",r," ",e.length)}function x2({augment:t,addChild:e}){t(({counter:n,...r})=>({counter:Rg(n),...r})),e(to,un(qS,v2))}const w2={autoplay:!1,delay:3e3,ref:null},ep=t=>({...w2,...t}),tp=F.createContext(null),k2=ln("useSlideshow","SlideshowContext",tp);function C2({slideshow:t,carousel:{finite:e},on:n,children:r}){const{autoplay:s,delay:o,ref:i}=ep(t),a=F.useRef(s),[l,c]=F.useState(s),d=F.useRef(void 0),u=F.useRef(void 0),{slides:f,currentIndex:g}=fn(),{setTimeout:m,clearTimeout:p}=ao(),{subscribe:y}=os(),{next:b}=_n(),{setAutoPlaying:_}=yc();F.useEffect(()=>_(l),[l,_]);const x=f.length===0||e&&g===f.length-1,E=F.useCallback(()=>{!l&&!x&&c(!0)},[l,x]),C=F.useCallback(()=>{l&&c(!1)},[l]),k=F.useCallback(()=>{p(d.current),d.current=void 0},[p]),N=We(()=>{k(),!(!l||x||u.current===Rr||u.current===oc)&&(d.current=m(()=>{l&&(u.current=void 0,b())},o))});F.useEffect(N,[g,l,N]),F.useEffect(()=>{l&&x&&c(!1)},[g,l,x]);const S=We(()=>{var T;return(T=n.slideshowStart)===null||T===void 0?void 0:T.call(n)}),w=We(()=>{var T;return(T=n.slideshowStop)===null||T===void 0?void 0:T.call(n)});F.useEffect(()=>{l?S():a.current&&w(),a.current=l},[l,S,w]),F.useEffect(()=>ns(k,y(QS,()=>{u.current=Rr,k()}),y(JS,()=>{u.current=oc,k()}),y(RS,()=>{u.current=so,N()}),y(e_,()=>{u.current=oo,N()})),[y,k,N]);const M=F.useMemo(()=>({playing:l,disabled:x,play:E,pause:C}),[l,x,E,C]);return F.useImperativeHandle(i,()=>M,[M]),F.createElement(tp.Provider,{value:M},r)}const S2=Jt("Play",F.createElement("path",{d:"M8 5v14l11-7z"})),_2=Jt("Pause",F.createElement("path",{d:"M6 19h4V5H6v14zm8-14v14h4V5h-4z"}));function E2(){const{playing:t,disabled:e,play:n,pause:r}=k2(),{render:s}=At(),o=Dg(_n().focus,e);return s.buttonSlideshow?F.createElement(F.Fragment,null,s.buttonSlideshow({playing:t,disabled:e,play:n,pause:r})):F.createElement(is,{label:t?"Pause":"Play",icon:t?_2:S2,renderIcon:t?s.iconSlideshowPause:s.iconSlideshowPlay,onClick:t?r:n,disabled:e,...o})}function M2({augment:t,addModule:e}){t(({slideshow:n,toolbar:r,...s})=>({toolbar:zi(r,ng,F.createElement(E2,null)),slideshow:ep(n),...s})),e(un(ng,C2))}const N2={auto:!1,ref:null},np=t=>({...N2,...t}),rp=F.createContext(null),O2=ln("useFullscreen","FullscreenContext",rp);function T2({fullscreen:t,on:e,children:n}){const{auto:r,ref:s}=np(t),o=F.useRef(null),[i,a]=F.useState(),[l,c]=F.useState(!1),d=F.useRef(!1),{getOwnerDocument:u}=ss();Cn(()=>{var x,E,C,k;const N=u();a(!((k=(C=(E=(x=N.fullscreenEnabled)!==null&&x!==void 0?x:N.webkitFullscreenEnabled)!==null&&E!==void 0?E:N.mozFullScreenEnabled)!==null&&C!==void 0?C:N.msFullscreenEnabled)!==null&&k!==void 0&&k))},[u]);const f=F.useCallback(()=>{var x;const E=u(),C=E.fullscreenElement||E.webkitFullscreenElement||E.mozFullScreenElement||E.msFullscreenElement;return((x=C?.shadowRoot)===null||x===void 0?void 0:x.fullscreenElement)||C},[u]),g=F.useCallback(()=>{const x=o.current;try{x.requestFullscreen?x.requestFullscreen().catch(()=>{}):x.webkitRequestFullscreen?x.webkitRequestFullscreen():x.mozRequestFullScreen?x.mozRequestFullScreen():x.msRequestFullscreen&&x.msRequestFullscreen()}catch{}},[]),m=F.useCallback(()=>{if(!f())return;const x=u();try{x.exitFullscreen?x.exitFullscreen().catch(()=>{}):x.webkitExitFullscreen?x.webkitExitFullscreen():x.mozCancelFullScreen?x.mozCancelFullScreen():x.msExitFullscreen&&x.msExitFullscreen()}catch{}},[f,u]);F.useEffect(()=>{const x=u(),E=()=>{c(f()===o.current)};return ns(...["fullscreenchange","webkitfullscreenchange","mozfullscreenchange","MSFullscreenChange"].map(C=>(x.addEventListener(C,E),()=>x.removeEventListener(C,E))))},[f,u]);const p=We(()=>{var x;return(x=e.enterFullscreen)===null||x===void 0?void 0:x.call(e)}),y=We(()=>{var x;return(x=e.exitFullscreen)===null||x===void 0?void 0:x.call(e)});F.useEffect(()=>{l&&(d.current=!0),d.current&&(l?p:y)()},[l,p,y]);const b=We(()=>{var x;return(x=r?g:null)===null||x===void 0||x(),m});F.useEffect(b,[b]);const _=F.useMemo(()=>({fullscreen:l,disabled:i,enter:g,exit:m}),[l,i,g,m]);return F.useImperativeHandle(s,()=>_,[_]),F.createElement("div",{ref:o,className:zt(Ne(no),Ne(sg))},F.createElement(rp.Provider,{value:_},n))}const A2=Jt("EnterFullscreen",F.createElement("path",{d:"M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z"})),D2=Jt("ExitFullscreen",F.createElement("path",{d:"M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z"}));function I2(){var t;const{fullscreen:e,disabled:n,enter:r,exit:s}=O2(),{render:o}=At();return n?null:o.buttonFullscreen?F.createElement(F.Fragment,null,(t=o.buttonFullscreen)===null||t===void 0?void 0:t.call(o,{fullscreen:e,disabled:n,enter:r,exit:s})):F.createElement(is,{disabled:n,label:e?"Exit Fullscreen":"Enter Fullscreen",icon:e?D2:A2,renderIcon:e?o.iconExitFullscreen:o.iconEnterFullscreen,onClick:e?s:r})}function L2({augment:t,contains:e,addParent:n}){t(({fullscreen:r,toolbar:s,...o})=>({toolbar:zi(s,no,F.createElement(I2,null)),fullscreen:np(r),...o})),n(e(ro)?ro:to,un(no,T2))}const xr={ref:null,position:"bottom",width:120,height:80,border:1,borderRadius:4,padding:4,gap:16,imageFit:"contain",vignette:!0,hidden:!1,showToggle:!1},Ec=t=>({...xr,...t});function sp(){const{thumbnails:t}=At();return Ec(t)}const wr=t=>pr(ro,t),Dt=t=>wr(pr("thumbnail",t)),P2=Jt("VideoThumbnail",F.createElement("path",{d:"M10 16.5l6-4.5-6-4.5v9zM12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"})),$2=Jt("UnknownThumbnail",F.createElement("path",{d:"M23 18V6c0-1.1-.9-2-2-2H3c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2zM8.5 12.5l2.5 3.01L14.5 11l4.5 6H5l3.5-4.5z"}));function F2({slide:t,render:e,rect:n,imageFit:r}){var s;const o=(s=e.thumbnail)===null||s===void 0?void 0:s.call(e,{slide:t,render:e,rect:n,imageFit:r});if(o)return o;const i={render:e,rect:n,imageFit:r};if(t.thumbnail)return F.createElement(yr,{slide:{src:t.thumbnail},...i});if(or(t))return F.createElement(yr,{slide:t,...i});const a=Ne(Dt(Pi));return t.type==="video"?F.createElement(F.Fragment,null,t.poster&&F.createElement(yr,{slide:{src:t.poster},...i}),F.createElement(P2,{className:a})):F.createElement($2,{className:a})}const z2=mr("active"),Mc=mr("fadein"),Nc=mr("fadeout"),V2=mr("placeholder"),op="delay",ip="duration";function H2({slide:t,index:e,onClick:n,fadeIn:r,fadeOut:s,placeholder:o,onLoseFocus:i}){const a=F.useRef(null),{render:l,styles:c,labels:d}=At(),{slides:u,globalIndex:f}=fn(),{getOwnerDocument:g}=ss(),{width:m,height:p,imageFit:y}=sp(),b={width:m,height:p},_=e===f,x=We(i);return F.useEffect(()=>{s&&g().activeElement===a.current&&x()},[s,x,g]),F.createElement("button",{ref:a,type:"button",className:zt(Ne(es),Ne(Dt()),_&&Ne(Dt(z2())),r&&Ne(Dt(Mc())),s&&Ne(Dt(Nc())),o&&Ne(Dt(V2()))),style:{...r?{[dt(Dt(Mc(ip)))]:`${r.duration}ms`,[dt(Dt(Mc(op)))]:`${r.delay}ms`}:null,...s?{[dt(Dt(Nc(ip)))]:`${s.duration}ms`,[dt(Dt(Nc(op)))]:`${s.delay}ms`}:null,...c.thumbnail},onClick:n,"aria-current":_?!0:void 0,"aria-label":mg(d,u,e)},t&&F2({slide:t,render:l,rect:b,imageFit:y}))}function ap(t){return["top","bottom"].includes(t)}function lp(t,e){return e+2*(t.border+t.padding)+t.gap}function U2(t){const{thumbnail:e,poster:n}=t||{thumbnail:"placeholder"};return typeof e=="string"&&e||typeof n=="string"&&n||t&&bg(t)||void 0}function cp({visible:t,containerRef:e}){const n=F.useRef(null),r=Hi(),{publish:s,subscribe:o}=os(),{carousel:i,styles:a,labels:l}=At(),{slides:c,globalIndex:d,animation:u}=fn(),{registerSensors:f,subscribeSensors:g}=Ig();Hg(g);const m=sp(),{position:p,width:y,height:b,border:_,borderStyle:x,borderColor:E,borderRadius:C,padding:k,gap:N,vignette:S}=m,w=u?.duration!==void 0&&u?.increment||0,M=u?.duration||0,{prepareAnimation:T}=xc(n,D=>({keyframes:ap(p)?[{transform:`translateX(${(r?-1:1)*lp(m,y)*w+D}px)`},{transform:"translateX(0)"}]:[{transform:`translateY(${lp(m,b)*w+D}px)`},{transform:"translateY(0)"}],duration:M,easing:u?.easing})),P=We(()=>{let D=0;if(e.current&&n.current){const O=e.current.getBoundingClientRect(),z=n.current.getBoundingClientRect();D=ap(p)?z.left-O.left-(O.width-z.width)/2:z.top-O.top-(O.height-z.height)/2}T(D)});F.useEffect(()=>ns(o(ac,P)),[o,P]);const L=yg(i,c),$=[];if(gc(c))for(let D=d-L-Math.abs(w);D<=d+L+Math.abs(w);D+=1){const z=i.finite&&(D<0||D>c.length-1)||w<0&&D0&&D>d+L?null:pc(c,D),H=[`${D}`,U2(z)].filter(Boolean).join("|");$.push({key:H,index:D,slide:z})}const A=D=>()=>{D>d?s(sr,{count:D-d}):D{const H=M/Math.abs(w||1),j=w>0&&O>d+L-w&&O<=d+L||w<0&&O=d-L?{duration:H,delay:((w>0?O-(d+L-w):d-L-w-O)-1)*H}:void 0,W=w>0&&Od+L?{duration:H,delay:(w>0?w-(d-L-O):-w-(O-(d+L)))*H}:void 0;return F.createElement(H2,{key:D,index:O,slide:z,fadeIn:j,fadeOut:W,placeholder:!z,onClick:A(O),onLoseFocus:()=>{var U;return(U=n.current)===null||U===void 0?void 0:U.focus()}})})),S&&F.createElement("div",{className:Ne(wr("vignette"))}))}const dp=F.createContext(null),B2=ln("useThumbnails","ThumbnailsContext",dp);function j2({children:t,...e}){const{ref:n,position:r,hidden:s}=Ec(e.thumbnails),[o,i]=F.useState(!s),a=F.useRef(null),l=F.useMemo(()=>({visible:o,show:()=>i(!0),hide:()=>i(!1)}),[o]);return F.useImperativeHandle(n,()=>l,[l]),F.createElement(Sg,{...e},F.createElement(dp.Provider,{value:l},F.createElement("div",{ref:a,className:zt(Ne(wr()),Ne(wr(`${r}`)))},["start","top"].includes(r)&&F.createElement(cp,{containerRef:a,visible:o}),F.createElement("div",{className:Ne(wr("wrapper"))},t),["end","bottom"].includes(r)&&F.createElement(cp,{containerRef:a,visible:o}))))}const up=()=>F.createElement(F.Fragment,null,F.createElement("path",{strokeWidth:2,stroke:"currentColor",strokeLinejoin:"round",fill:"none",d:"M3 5l18 0l0 14l-18 0l0-14z"}),F.createElement("path",{d:"M5 14h4v3h-4zM10 14h4v3h-4zM15 14h4v3h-4z"})),W2=Jt("ThumbnailsVisible",up()),G2=C_("ThumbnailsHidden",up());function Y2(){const{visible:t,show:e,hide:n}=B2(),{render:r}=At();return r.buttonThumbnails?F.createElement(F.Fragment,null,r.buttonThumbnails({visible:t,show:e,hide:n})):F.createElement(is,{label:t?"Hide thumbnails":"Show thumbnails",icon:t?W2:G2,renderIcon:t?r.iconThumbnailsVisible:r.iconThumbnailsHidden,onClick:t?n:e})}function K2({augment:t,contains:e,append:n,addParent:r}){t(({thumbnails:o,toolbar:i,...a})=>{const l=Ec(o);return{toolbar:zi(i,ro,l.showToggle?F.createElement(Y2,null):null),thumbnails:l,...a}});const s=un(ro,j2);e(no)?n(no,s):r(to,s)}const X2=window.G7Core?.createLogger?.("Comp:ImageGallery")??{log:(...t)=>console.log("[Comp:ImageGallery]",...t),warn:(...t)=>console.warn("[Comp:ImageGallery]",...t),error:(...t)=>console.error("[Comp:ImageGallery]",...t)},Oc=window.G7Core,Bi=(t,e)=>Oc?.t?.(t,e)??t,q2=async(t,e)=>{try{const n=await Oc.api.get(t,{responseType:"blob"});if(n){const r=URL.createObjectURL(n),s=document.createElement("a");s.href=r,s.download=e,document.body.appendChild(s),s.click(),document.body.removeChild(s),URL.revokeObjectURL(r)}}catch(n){X2.error("Failed to download file:",n),Oc?.toast?.error?.(Bi("common.download_failed"))}},Z2=(t,e)=>{const n=document.createElement("a");n.href=t,n.download=e,n.target="_blank",document.body.appendChild(n),n.click(),document.body.removeChild(n)},Tc=async t=>{const e=t.downloadUrl||t.src,n=t.filename||t.title||"image";t.downloadRequiresAuth?await q2(e,n):Z2(e,n)},Q2=({image:t,index:e,onDownload:n})=>{const[r,s]=v.useState(!1),o=async i=>{if(i.stopPropagation(),n){n(t,e);return}s(!0);try{await Tc(t)}finally{s(!1)}};return h.jsx(oe,{type:"button",onClick:o,disabled:r,className:"yarl__button flex items-center justify-center","aria-label":Bi("common.download"),title:Bi("common.download"),children:r?h.jsx(mt,{className:"fa-solid fa-spinner fa-spin text-white"}):h.jsx(mt,{className:"fa-solid fa-download text-white"})})},ji=({images:t,isOpen:e,onClose:n,startIndex:r=0,enableZoom:s=!0,enableSlideshow:o=!1,enableFullscreen:i=!0,showCounter:a=!0,showDownload:l=!0,showThumbnails:c=!0,onDownload:d,editorAttrs:u,id:f})=>{const[g,m]=v.useState(r),p=v.useRef(r),y=t.map(x=>({src:x.src,title:x.title,description:x.description})),b=[];s&&b.push(b2),o&&b.push(M2),i&&b.push(L2),a&&b.push(x2),c&&b.push(K2);const _=t[g];if(v.useEffect(()=>{p.current=g},[g]),u){const x=(t??[]).slice(0,4);return h.jsx(I,{id:f,className:"grid grid-cols-2 gap-2 p-3 rounded-lg border border-dashed border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800",...u,children:x.length>0?x.map((E,C)=>h.jsx(yn,{src:E.thumbnail||E.src,alt:E.title||"",className:"w-full aspect-square object-cover rounded bg-gray-200 dark:bg-gray-700"},C)):h.jsxs(I,{className:"col-span-2 flex items-center justify-center py-6 text-sm text-gray-400 dark:text-gray-500",children:[h.jsx(mt,{className:"fa-regular fa-images mr-2"}),Bi("editor.component.image_gallery")]})})}return h.jsx(e2,{open:e,close:n,slides:y,index:g,plugins:b,on:{view:({index:x})=>{x!==p.current&&m(x)}},zoom:{maxZoomPixelRatio:3,zoomInMultiplier:2,doubleTapDelay:300,doubleClickDelay:300,doubleClickMaxStops:2,keyboardMoveDistance:50,wheelZoomDistanceFactor:100,pinchZoomDistanceFactor:100,scrollToZoom:!0},carousel:{finite:!0,preload:2,padding:"16px",spacing:"30%"},animation:{fade:250,swipe:500,easing:{fade:"ease",swipe:"ease-out",navigation:"ease-in-out"}},controller:{closeOnBackdropClick:!0,closeOnPullDown:!0,closeOnPullUp:!0},thumbnails:{position:"bottom",width:120,height:80,border:2,borderRadius:4,padding:4,gap:16,showToggle:!1,vignette:!0},toolbar:{buttons:[l&&_&&h.jsx(Q2,{image:_,index:g,onDownload:d},"download"),"close"].filter(Boolean)},styles:{container:{backgroundColor:"rgba(0, 0, 0, 0.9)"}}})},fp=()=>{const[t,e]=v.useState(!1),[n,r]=v.useState([]),[s,o]=v.useState(0),i=v.useCallback((l,c=0)=>{r(l),o(c),e(!0)},[]),a=v.useCallback(()=>{e(!1)},[]);return{isOpen:t,openGallery:i,closeGallery:a,galleryProps:{images:n,isOpen:t,onClose:a,startIndex:s}}};ji.displayName="ImageGallery";const J2=Object.freeze(Object.defineProperty({__proto__:null,ImageGallery:ji,executeImageDownload:Tc,useImageGallery:fp},Symbol.toStringTag,{value:"Module"})),R2=v.createContext(null),eE=({value:t,children:e})=>h.jsx(R2.Provider,{value:t,children:e});function tE(t,e){return e.forEach((function(n){n&&typeof n!="string"&&!Array.isArray(n)&&Object.keys(n).forEach((function(r){if(r!=="default"&&!(r in t)){var s=Object.getOwnPropertyDescriptor(n,r);Object.defineProperty(t,r,s.get?s:{enumerable:!0,get:function(){return n[r]}})}}))})),Object.freeze(t)}function hp(t,e){return new Promise((function(n,r){let s;return nE(t).then((function(o){try{return s=o,n(new Blob([e.slice(0,2),s,e.slice(2)],{type:"image/jpeg"}))}catch(i){return r(i)}}),r)}))}const nE=t=>new Promise(((e,n)=>{const r=new FileReader;r.addEventListener("load",(({target:{result:s}})=>{const o=new DataView(s);let i=0;if(o.getUint16(i)!==65496)return n("not a valid JPEG");for(i+=2;;){const a=o.getUint16(i);if(a===65498)break;const l=o.getUint16(i+2);if(a===65505&&o.getUint32(i+4)===1165519206){const c=i+10;let d;switch(o.getUint16(c)){case 18761:d=!0;break;case 19789:d=!1;break;default:return n("TIFF header contains invalid endian")}if(o.getUint16(c+2,d)!==42)return n("TIFF header contains invalid version");const u=o.getUint32(c+4,d),f=c+u+2+12*o.getUint16(c+u,d);for(let g=c+u+2;g>>24&255,a[i+1]=l>>>16&255,a[i+2]=l>>>8&255,a[i+3]=l>>>0&255,new Uint8Array(a.buffer,0,i+4)},r.deflateRaw=function(s,o){o==null&&(o={level:6});var i=new Uint8Array(50+Math.floor(1.1*s.length)),a=r.F.deflateRaw(s,i,a,o.level);return new Uint8Array(i.buffer,0,a)},r.encode=function(s,o){o==null&&(o=!1);var i=0,a=r.bin.writeUint,l=r.bin.writeUshort,c={};for(var d in s){var u=!r._noNeed(d)&&!o,f=s[d],g=r.crc.crc(f,0,f.length);c[d]={cpr:u,usize:f.length,crc:g,file:u?r.deflateRaw(f):f}}for(var d in c)i+=c[d].file.length+30+46+2*r.bin.sizeUTF8(d);i+=22;var m=new Uint8Array(i),p=0,y=[];for(var d in c){var b=c[d];y.push(p),p=r._writeHeader(m,p,d,b,0)}var _=0,x=p;for(var d in c)b=c[d],y.push(p),p=r._writeHeader(m,p,d,b,1,y[_++]);var E=p-x;return a(m,p,101010256),p+=4,l(m,p+=4,_),l(m,p+=2,_),a(m,p+=2,E),a(m,p+=4,x),p+=4,p+=2,m.buffer},r._noNeed=function(s){var o=s.split(".").pop().toLowerCase();return"png,jpg,jpeg,zip".indexOf(o)!=-1},r._writeHeader=function(s,o,i,a,l,c){var d=r.bin.writeUint,u=r.bin.writeUshort,f=a.file;return d(s,o,l==0?67324752:33639248),o+=4,l==1&&(o+=2),u(s,o,20),u(s,o+=2,0),u(s,o+=2,a.cpr?8:0),d(s,o+=2,0),d(s,o+=4,a.crc),d(s,o+=4,f.length),d(s,o+=4,a.usize),u(s,o+=4,r.bin.sizeUTF8(i)),u(s,o+=2,0),o+=2,l==1&&(o+=2,o+=2,d(s,o+=6,c),o+=4),o+=r.bin.writeUTF8(s,o,i),l==0&&(s.set(f,o),o+=f.length),o},r.crc={table:(function(){for(var s=new Uint32Array(256),o=0;o<256;o++){for(var i=o,a=0;a<8;a++)1&i?i=3988292384^i>>>1:i>>>=1;s[o]=i}return s})(),update:function(s,o,i,a){for(var l=0;l>>8;return s},crc:function(s,o,i){return 4294967295^r.crc.update(4294967295,s,o,i)}},r.adler=function(s,o,i){for(var a=1,l=0,c=o,d=o+i;c>8&255},readUint:function(s,o){return 16777216*s[o+3]+(s[o+2]<<16|s[o+1]<<8|s[o])},writeUint:function(s,o,i){s[o]=255&i,s[o+1]=i>>8&255,s[o+2]=i>>16&255,s[o+3]=i>>24&255},readASCII:function(s,o,i){for(var a="",l=0;l>6,s[o+l+1]=128|d>>0&63,l+=2;else if((4294901760&d)==0)s[o+l]=224|d>>12,s[o+l+1]=128|d>>6&63,s[o+l+2]=128|d>>0&63,l+=3;else{if((4292870144&d)!=0)throw"e";s[o+l]=240|d>>18,s[o+l+1]=128|d>>12&63,s[o+l+2]=128|d>>6&63,s[o+l+3]=128|d>>0&63,l+=4}}return l},sizeUTF8:function(s){for(var o=s.length,i=0,a=0;a>>3}var y=c.lits,b=c.strt,_=c.prev,x=0,E=0,C=0,k=0,N=0,S=0;for(p>2&&(b[S=r.F._hash(s,0)]=0),f=0;f14e3||E>26697)&&p-f>100&&(m>>16,P=65535&M;if(M!=0){P=65535&M;var L=d(T=M>>>16,c.of0);c.lhst[257+L]++;var $=d(P,c.df0);c.dhst[$]++,k+=c.exb[L]+c.dxb[$],y[x]=T<<23|f-m,y[x+1]=P<<16|L<<8|$,x+=2,m=f+T}else c.lhst[s[f]]++;E++}}for(C==f&&s.length!=0||(m>>3},r.F._bestMatch=function(s,o,i,a,l,c){var d=32767&o,u=i[d],f=d-u+32768&32767;if(u==d||a!=r.F._hash(s,o-f))return 0;for(var g=0,m=0,p=Math.min(32767,o);f<=p&&--c!=0&&u!=d;){if(g==0||s[o+g]==s[o+g-f]){var y=r.F._howLong(s,o,f);if(y>g){if(m=f,(g=y)>=l)break;f+2b&&(b=E,u=x)}}}f+=(d=u)-(u=i[d])+32768&32767}return g<<16|m},r.F._howLong=function(s,o,i){if(s[o]!=s[o-i]||s[o+1]!=s[o+1-i]||s[o+2]!=s[o+2-i])return 0;var a=o,l=Math.min(s.length,o+258);for(o+=3;o>>23,W=O+(8388607&H);O>16,G=U>>8&255,V=255&U;S(u,f=r.F._writeLit(257+G,$,u,f),j-k.of0[G]),f+=k.exb[G],N(u,f=r.F._writeLit(V,A,u,f),B-k.df0[V]),f+=k.dxb[V],O+=j}}f=r.F._writeLit(256,$,u,f)}return f},r.F._copyExact=function(s,o,i,a,l){var c=l>>>3;return a[c]=i,a[c+1]=i>>>8,a[c+2]=255-a[c],a[c+3]=255-a[c+1],c+=4,a.set(new Uint8Array(s.buffer,o,i),c),l+(i+4<<3)},r.F.getTrees=function(){for(var s=r.F.U,o=r.F._hufTree(s.lhst,s.ltree,15),i=r.F._hufTree(s.dhst,s.dtree,15),a=[],l=r.F._lenCodes(s.ltree,a),c=[],d=r.F._lenCodes(s.dtree,c),u=0;u4&&s.itree[1+(s.ordr[g-1]<<1)]==0;)g--;return[o,i,f,l,d,g,a,c]},r.F.getSecond=function(s){for(var o=[],i=0;i>1)+",");return o},r.F.contSize=function(s,o){for(var i=0,a=0;a15&&(r.F._putsE(i,a,d,u),a+=u)}return a},r.F._lenCodes=function(s,o){for(var i=s.length;i!=2&&s[i-1]==0;)i-=2;for(var a=0;a>>1,138))<11?o.push(17,g-3):o.push(18,g-11),a+=2*g-2}else if(l==u&&c==l&&d==l){for(f=a+5;f+2>>1,6);o.push(16,g-3),a+=2*g-2}else o.push(l,0)}return i>>>1},r.F._hufTree=function(s,o,i){var a=[],l=s.length,c=o.length,d=0;for(d=0;di&&(r.F.restrictDepth(f,i,x),x=i),d=0;do;a++){var d=s[a].d;s[a].d=o,c+=l-(1<>>=i-o;c>0;)(d=s[a].d)=0;a--)s[a].d==o&&c<0&&(s[a].d--,c++);c!=0&&console.log("debt left")},r.F._goodIndex=function(s,o){var i=0;return o[16|i]<=s&&(i|=16),o[8|i]<=s&&(i|=8),o[4|i]<=s&&(i|=4),o[2|i]<=s&&(i|=2),o[1|i]<=s&&(i|=1),i},r.F._writeLit=function(s,o,i,a){return r.F._putsF(i,a,o[s<<1]),a+o[1+(s<<1)]},r.F.inflate=function(s,o){var i=Uint8Array;if(s[0]==3&&s[1]==0)return o||new i(0);var a=r.F,l=a._bitsF,c=a._bitsE,d=a._decodeTiny,u=a.makeCodes,f=a.codes2map,g=a._get17,m=a.U,p=o==null;p&&(o=new i(s.length>>>2<<3));for(var y,b,_=0,x=0,E=0,C=0,k=0,N=0,S=0,w=0,M=0;_==0;)if(_=l(s,M,1),x=l(s,M+1,2),M+=3,x!=0){if(p&&(o=r.F._check(o,w+131072)),x==1&&(y=m.flmap,b=m.fdmap,N=511,S=31),x==2){E=c(s,M,5)+257,C=c(s,M+5,5)+1,k=c(s,M+10,4)+4,M+=14;for(var T=0;T<38;T+=2)m.itree[T]=0,m.itree[T+1]=0;var P=1;for(T=0;TP&&(P=L)}M+=3*k,u(m.itree,P),f(m.itree,P,m.imap),y=m.lmap,b=m.dmap,M=d(m.imap,(1<>>4;if(!(O>>>8))o[w++]=O;else{if(O==256)break;var z=w+O-254;if(O>264){var H=m.ldef[O-257];z=w+(H>>>3)+c(s,M,7&H),M+=7&H}var j=b[g(s,M)&S];M+=15&j;var W=j>>>4,U=m.ddef[W],B=(U>>>4)+l(s,M,15&U);for(M+=15&U,p&&(o=r.F._check(o,w+131072));w>>3),V=s[G-4]|s[G-3]<<8;p&&(o=r.F._check(o,w+V)),o.set(new i(s.buffer,s.byteOffset+G,V),w),M=G+V<<3,w+=V}return o.length==w?o:o.slice(0,w)},r.F._check=function(s,o){var i=s.length;if(o<=i)return s;var a=new Uint8Array(Math.max(i<<1,o));return a.set(s,0),a},r.F._decodeTiny=function(s,o,i,a,l,c){for(var d=r.F._bitsE,u=r.F._get17,f=0;f>>4;if(m<=15)c[f]=m,f++;else{var p=0,y=0;m==16?(y=3+d(a,l,2),l+=2,p=c[f-1]):m==17?(y=3+d(a,l,3),l+=3):m==18&&(y=11+d(a,l,7),l+=7);for(var b=f+y;f>>1;cl&&(l=u),c++}for(;c>1,u=s[c+1],f=d<<4|u,g=o-u,m=s[c]<>>15-o]=f,m++},r.F.revCodes=function(s,o){for(var i=r.F.U.rev15,a=15-o,l=0;l>>a}},r.F._putsE=function(s,o,i){i<<=7&o;var a=o>>>3;s[a]|=i,s[a+1]|=i>>>8},r.F._putsF=function(s,o,i){i<<=7&o;var a=o>>>3;s[a]|=i,s[a+1]|=i>>>8,s[a+2]|=i>>>16},r.F._bitsE=function(s,o,i){return(s[o>>>3]|s[1+(o>>>3)]<<8)>>>(7&o)&(1<>>3]|s[1+(o>>>3)]<<8|s[2+(o>>>3)]<<16)>>>(7&o)&(1<>>3]|s[1+(o>>>3)]<<8|s[2+(o>>>3)]<<16)>>>(7&o)},r.F._get25=function(s,o){return(s[o>>>3]|s[1+(o>>>3)]<<8|s[2+(o>>>3)]<<16|s[3+(o>>>3)]<<24)>>>(7&o)},r.F.U=(e=Uint16Array,n=Uint32Array,{next_code:new e(16),bl_count:new e(16),ordr:[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],of0:[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,999,999,999],exb:[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0,0],ldef:new e(32),df0:[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,65535,65535],dxb:[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13,0,0],ddef:new n(32),flmap:new e(512),fltree:[],fdmap:new e(32),fdtree:[],lmap:new e(32768),ltree:[],ttree:[],dmap:new e(32768),dtree:[],imap:new e(512),itree:[],rev15:new e(32768),lhst:new n(286),dhst:new n(30),ihst:new n(19),lits:new n(15e3),strt:new e(65536),prev:new e(32768)}),(function(){for(var s=r.F.U,o=0;o<32768;o++){var i=o;i=(4278255360&(i=(4042322160&(i=(3435973836&(i=(2863311530&i)>>>1|(1431655765&i)<<1))>>>2|(858993459&i)<<2))>>>4|(252645135&i)<<4))>>>8|(16711935&i)<<8,s.rev15[o]=(i>>>16|i<<16)>>>17}function a(l,c,d){for(;c--!=0;)l.push(0,d)}for(o=0;o<32;o++)s.ldef[o]=s.of0[o]<<3|s.exb[o],s.ddef[o]=s.df0[o]<<4|s.dxb[o];a(s.fltree,144,8),a(s.fltree,112,9),a(s.fltree,24,7),a(s.fltree,8,8),r.F.makeCodes(s.fltree,9),r.F.codes2map(s.fltree,9,s.flmap),r.F.revCodes(s.fltree,9),a(s.fdtree,32,5),r.F.makeCodes(s.fdtree,5),r.F.codes2map(s.fdtree,5,s.fdmap),r.F.revCodes(s.fdtree,5),a(s.itree,19,0),a(s.ltree,286,0),a(s.dtree,30,0),a(s.ttree,320,0)})()})();var sE=tE({__proto__:null,default:Wi},[Wi]);const En=(function(){var t={nextZero(d,u){for(;d[u]!=0;)u++;return u},readUshort:(d,u)=>d[u]<<8|d[u+1],writeUshort(d,u,f){d[u]=f>>8&255,d[u+1]=255&f},readUint:(d,u)=>16777216*d[u]+(d[u+1]<<16|d[u+2]<<8|d[u+3]),writeUint(d,u,f){d[u]=f>>24&255,d[u+1]=f>>16&255,d[u+2]=f>>8&255,d[u+3]=255&f},readASCII(d,u,f){let g="";for(let m=0;md.length<2?`0${d}`:d,readUTF8(d,u,f){let g,m="";for(let p=0;p>3)]>>7-((7&k)<<0)&1);b[w]=H[L],b[w+1]=H[L+1],b[w+2]=H[L+2],b[w+3]=$>2)]>>6-((3&k)<<1)&3),b[w]=H[L],b[w+1]=H[L+1],b[w+2]=H[L+2],b[w+3]=$>1)]>>4-((1&k)<<2)&15),b[w]=H[L],b[w+1]=H[L+1],b[w+2]=H[L+2],b[w+3]=$>>3)]>>>7-(7&O)&1))==255*S?0:255;_[j+O]=z<<24|A<<16|A<<8|A}else if(E==2)for(O=0;O>>2)]>>>6-((3&O)<<1)&3))==85*S?0:255,_[j+O]=z<<24|A<<16|A<<8|A;else if(E==4)for(O=0;O>>1)]>>>4-((1&O)<<2)&15))==17*S?0:255,_[j+O]=z<<24|A<<16|A<<8|A;else if(E==8)for(O=0;O>3,S=Math.ceil(E*k/8),w=new Uint8Array(C*S);let M=0;const T=[0,0,4,0,2,0,1],P=[0,4,0,2,0,1,0],L=[8,8,8,4,4,2,2],$=[8,8,4,4,2,2,1];let A=0;for(;A<7;){const O=L[A],z=$[A];let H=0,j=0,W=T[A];for(;W>3])>>7-(7&K)&1,w[V*S+(X>>3)]|=D<<7-((7&X)<<0)),k==2&&(D=(D=_[K>>3])>>6-(7&K)&3,w[V*S+(X>>2)]|=D<<6-((3&X)<<1)),k==4&&(D=(D=_[K>>3])>>4-(7&K)&15,w[V*S+(X>>1)]|=D<<4-((1&X)<<2)),k>=8){const Z=V*S+X*N;for(let ee=0;ee>3)+ee]}K+=k,X+=z}G++,V+=O}H*j!=0&&(M+=j*(1+B)),A+=1}return w})(u,d)),u}function r(d,u){return s(new Uint8Array(d.buffer,2,d.length-6),u)}var s=(function(){const d={H:{}};return d.H.N=function(u,f){const g=Uint8Array;let m,p,y=0,b=0,_=0,x=0,E=0,C=0,k=0,N=0,S=0;if(u[0]==3&&u[1]==0)return f||new g(0);const w=d.H,M=w.b,T=w.e,P=w.R,L=w.n,$=w.A,A=w.Z,D=w.m,O=f==null;for(O&&(f=new g(u.length>>>2<<5));y==0;)if(y=M(u,S,1),b=M(u,S+1,2),S+=3,b!=0){if(O&&(f=d.H.W(f,N+(1<<17))),b==1&&(m=D.J,p=D.h,C=511,k=31),b==2){_=T(u,S,5)+257,x=T(u,S+5,5)+1,E=T(u,S+10,4)+4,S+=14;let H=1;for(var z=0;z<38;z+=2)D.Q[z]=0,D.Q[z+1]=0;for(z=0;zH&&(H=U)}S+=3*E,L(D.Q,H),$(D.Q,H,D.u),m=D.w,p=D.d,S=P(D.u,(1<>>4;if(!(j>>>8))f[N++]=j;else{if(j==256)break;{let W=N+j-254;if(j>264){const X=D.q[j-257];W=N+(X>>>3)+T(u,S,7&X),S+=7&X}const U=p[A(u,S)&k];S+=15&U;const B=U>>>4,G=D.c[B],V=(G>>>4)+M(u,S,15&G);for(S+=15&G;N>>3),j=u[H-4]|u[H-3]<<8;O&&(f=d.H.W(f,N+j)),f.set(new g(u.buffer,u.byteOffset+H,j),N),S=H+j<<3,N+=j}return f.length==N?f:f.slice(0,N)},d.H.W=function(u,f){const g=u.length;if(f<=g)return u;const m=new Uint8Array(g<<1);return m.set(u,0),m},d.H.R=function(u,f,g,m,p,y){const b=d.H.e,_=d.H.Z;let x=0;for(;x>>4;if(C<=15)y[x]=C,x++;else{let k=0,N=0;C==16?(N=3+b(m,p,2),p+=2,k=y[x-1]):C==17?(N=3+b(m,p,3),p+=3):C==18&&(N=11+b(m,p,7),p+=7);const S=x+N;for(;x>>1;for(;yp&&(p=_),y++}for(;y>1,_=u[y+1],x=b<<4|_,E=f-_;let C=u[y]<>>15-f]=x,C++}},d.H.l=function(u,f){const g=d.H.m.r,m=15-f;for(let p=0;p>>m}},d.H.M=function(u,f,g){g<<=7&f;const m=f>>>3;u[m]|=g,u[m+1]|=g>>>8},d.H.I=function(u,f,g){g<<=7&f;const m=f>>>3;u[m]|=g,u[m+1]|=g>>>8,u[m+2]|=g>>>16},d.H.e=function(u,f,g){return(u[f>>>3]|u[1+(f>>>3)]<<8)>>>(7&f)&(1<>>3]|u[1+(f>>>3)]<<8|u[2+(f>>>3)]<<16)>>>(7&f)&(1<>>3]|u[1+(f>>>3)]<<8|u[2+(f>>>3)]<<16)>>>(7&f)},d.H.i=function(u,f){return(u[f>>>3]|u[1+(f>>>3)]<<8|u[2+(f>>>3)]<<16|u[3+(f>>>3)]<<24)>>>(7&f)},d.H.m=(function(){const u=Uint16Array,f=Uint32Array;return{K:new u(16),j:new u(16),X:[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],S:[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,999,999,999],T:[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0,0],q:new u(32),p:[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,65535,65535],z:[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13,0,0],c:new f(32),J:new u(512),_:[],h:new u(32),$:[],w:new u(32768),C:[],v:[],d:new u(32768),D:[],u:new u(512),Q:[],r:new u(32768),s:new f(286),Y:new f(30),a:new f(19),t:new f(15e3),k:new u(65536),g:new u(32768)}})(),(function(){const u=d.H.m;for(var f=0;f<32768;f++){let m=f;m=(2863311530&m)>>>1|(1431655765&m)<<1,m=(3435973836&m)>>>2|(858993459&m)<<2,m=(4042322160&m)>>>4|(252645135&m)<<4,m=(4278255360&m)>>>8|(16711935&m)<<8,u.r[f]=(m>>>16|m<<16)>>>17}function g(m,p,y){for(;p--!=0;)m.push(0,y)}for(f=0;f<32;f++)u.q[f]=u.S[f]<<3|u.T[f],u.c[f]=u.p[f]<<4|u.z[f];g(u._,144,8),g(u._,112,9),g(u._,24,7),g(u._,8,8),d.H.n(u._,9),d.H.A(u._,9,u.J),d.H.l(u._,9),g(u.$,32,5),d.H.n(u.$,5),d.H.A(u.$,5,u.h),d.H.l(u.$,5),g(u.Q,19,0),g(u.C,286,0),g(u.D,30,0),g(u.v,320,0)})(),d.H.N})();function o(d){return[1,null,3,1,2,null,4][d.ctype]*d.depth}function i(d,u,f,g,m){let p=o(u);const y=Math.ceil(g*p/8);let b,_;p=Math.ceil(p/8);let x=d[f],E=0;if(x>1&&(d[f]=[0,0,1][x-2]),x==3)for(E=p;E>>1)&255;for(let C=0;C>>1);for(;E>>1)}else{for(;E=0&&b>=0?(C=A*u+D<<2,k=(b+A)*m+y+D<<2):(C=(-b+A)*u-y+D<<2,k=A*m+D<<2),_==0)g[k]=d[C],g[k+1]=d[C+1],g[k+2]=d[C+2],g[k+3]=d[C+3];else if(_==1){var N=d[C+3]*.00392156862745098,S=d[C]*N,w=d[C+1]*N,M=d[C+2]*N,T=g[k+3]*(1/255),P=g[k]*T,L=g[k+1]*T,$=g[k+2]*T;const O=1-N,z=N+T*O,H=z==0?0:1/z;g[k+3]=255*z,g[k+0]=(S+P*O)*H,g[k+1]=(w+L*O)*H,g[k+2]=(M+$*O)*H}else if(_==2)N=d[C+3],S=d[C],w=d[C+1],M=d[C+2],T=g[k+3],P=g[k],L=g[k+1],$=g[k+2],N==T&&S==P&&w==L&&M==$?(g[k]=0,g[k+1]=0,g[k+2]=0,g[k+3]=0):(g[k]=S,g[k+1]=w,g[k+2]=M,g[k+3]=N);else if(_==3){if(N=d[C+3],S=d[C],w=d[C+1],M=d[C+2],T=g[k+3],P=g[k],L=g[k+1],$=g[k+2],N==T&&S==P&&w==L&&M==$)continue;if(N<220&&T>20)return!1}return!0}return{decode:function(u){const f=new Uint8Array(u);let g=8;const m=t,p=m.readUshort,y=m.readUint,b={tabs:{},frames:[]},_=new Uint8Array(f.length);let x,E=0,C=0;const k=[137,80,78,71,13,10,26,10];for(var N=0;N<8;N++)if(f[N]!=k[N])throw"The input is not a PNG file!";for(;g>>1:M>>>=1;S[w]=M}return S})(),update(S,w,M,T){for(let P=0;P>>8;return S},crc:(S,w,M)=>4294967295^r.update(4294967295,S,w,M)};function s(S,w,M,T){w[M]+=S[0]*T>>4,w[M+1]+=S[1]*T>>4,w[M+2]+=S[2]*T>>4,w[M+3]+=S[3]*T>>4}function o(S){return Math.max(0,Math.min(255,S))}function i(S,w){const M=S[0]-w[0],T=S[1]-w[1],P=S[2]-w[2],L=S[3]-w[3];return M*M+T*T+P*P+L*L}function a(S,w,M,T,P,L,$){$==null&&($=1);const A=T.length,D=[];for(var O=0;O>>0&255,V>>>8&255,V>>>16&255,V>>>24&255])}for(O=0;O>2]=z,W[O>>2]=T[z]}}function l(S,w,M,T,P){P==null&&(P={});const{crc:L}=r,$=e.writeUint,A=e.writeUshort,D=e.writeASCII;let O=8;const z=S.frames.length>1;let H,j=!1,W=33+(z?20:0);if(P.sRGB!=null&&(W+=13),P.pHYs!=null&&(W+=21),P.iCCP!=null&&(H=pako.deflate(P.iCCP),W+=21+H.length+4),S.ctype==3){for(var U=S.plte.length,B=0;B>>24!=255&&(j=!0);W+=8+3*U+4+(j?8+1*U+4:0)}for(var G=0;G>>8&255,le=ne>>>16&255;V[O+ee+0]=he,V[O+ee+1]=J,V[O+ee+2]=le}if(O+=3*U,$(V,O,L(V,O-3*U-4,3*U+4)),O+=4,j){for($(V,O,U),O+=4,D(V,O,"tRNS"),O+=4,B=0;B>>24&255;O+=U,$(V,O,L(V,O-U-4,U+4)),O+=4}}let K=0;for(G=0;GVe&&(Ve=pt),itDe&&(De=it));Ve==-1&&(Pe=Te=Ve=De=0),se&&((1&Pe)==1&&Pe--,(1&Te)==1&&Te--);const qe=(Ve-Pe+1)*(De-Te+1);qe>2,He>>2);ee.push(ne);const se=new Uint8Array(ye.abuf,we,He);z&&a(he.img,he.rect.width,he.rect.height,Z,se,ne),he.img.set(se),we+=He}}else for(U=0;UJ&&fe==ve[G-J])ne[G]=ne[G-J];else{let ye=K[fe];if(ye==null&&(K[fe]=ye=Z.length,Z.push(fe),Z.length>=300))break;ne[G]=ye}}}const le=Z.length;for(le<=256&&O==0&&(j=le<=2?1:le<=4?2:le<=16?4:8,j=Math.max(j,D)),U=0;U>1)]|=He[Oe+ae]<<4-4*(1&ae);else if(j==2)for(ae=0;ae>2)]|=He[Oe+ae]<<6-2*(3&ae);else if(j==1)for(ae=0;ae>3)]|=He[Oe+ae]<<7-1*(7&ae)}fe=ue,H=3,we=1}else if(V==0&&X.length==1){ue=new Uint8Array(J*ve*3);const He=J*ve;for(G=0;GG&&(G=ee),neV&&(V=ne))}G==-1&&(U=B=G=V=0),$&&((1&U)==1&&U--,(1&B)==1&&B--),L={x:U,y:B,width:G-U+1,height:V-B+1};const X=T[P];X.rect=L,X.blend=1,X.img=new Uint8Array(L.width*L.height*4),T[P-1].dispose==0?(t(O,w,M,X.img,L.width,L.height,-L.x,-L.y,0),f(j,w,M,X.img,L)):t(j,w,M,X.img,L.width,L.height,-L.x,-L.y,0)}function f(S,w,M,T,P){t(S,w,M,T,P.width,P.height,-P.x,-P.y,2)}function g(S,w,M,T,P,L,$){const A=[];let D,O=[0,1,2,3,4];L!=-1?O=[L]:(w*T>5e5||M==1)&&(O=[0]),$&&(D={level:0});const z=sE;for(var H=0;H>1)+256&255;if(L==4)for(D=P;D>1)&255;for(D=P;D>1)&255}if(L==4){for(D=0;D>2);let z;if(M.length<2e7)for(var H=0;H>2]=z.ind,P[H>>2]=z.est.rgba;else for(H=0;H>2]=z.ind,P[H>>2]=z.est.rgba}return{abuf:T.buffer,inds:O,plte:A}}function y(S,w,M){M==null&&(M=1e-4);const T=new Uint32Array(S.buffer),P={i0:0,i1:S.length,bst:null,est:null,tdst:0,left:null,right:null};P.bst=C(S,P.i0,P.i1),P.est=k(P.bst);const L=[P];for(;L.lengthA&&(A=L[$].est.L,D=$);if(A=z||O.i1<=z){O.est.L=0;continue}const H={i0:O.i0,i1:z,bst:null,est:null,tdst:0,left:null,right:null};H.bst=C(S,H.i0,H.i1),H.est=k(H.bst);const j={i0:z,i1:O.i1,bst:null,est:null,tdst:0,left:null,right:null};for(j.bst={R:[],m:[],N:O.bst.N-H.bst.N},$=0;$<16;$++)j.bst.R[$]=O.bst.R[$]-H.bst.R[$];for($=0;$<4;$++)j.bst.m[$]=O.bst.m[$]-H.bst.m[$];j.est=k(j.bst),O.left=H,O.right=j,L[D]=H,L.push(j)}for(L.sort(((A,D)=>D.bst.N-A.bst.N)),$=0;$0&&($=S.right,A=S.left);const D=b($,w,M,T,P);if(D.tdst<=L*L)return D;const O=b(A,w,M,T,P);return O.tdstL;)T-=4;if(M>=T)break;const $=w[M>>2];w[M>>2]=w[T>>2],w[T>>2]=$,M+=4,T-=4}for(;E(S,M,P)>L;)M-=4;return M+4}function E(S,w,M){return S[w]*M[0]+S[w+1]*M[1]+S[w+2]*M[2]+S[w+3]*M[3]}function C(S,w,M){const T=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],P=[0,0,0,0],L=M-w>>2;for(let $=w;$>>0}}var N={multVec:(S,w)=>[S[0]*w[0]+S[1]*w[1]+S[2]*w[2]+S[3]*w[3],S[4]*w[0]+S[5]*w[1]+S[6]*w[2]+S[7]*w[3],S[8]*w[0]+S[9]*w[1]+S[10]*w[2]+S[11]*w[3],S[12]*w[0]+S[13]*w[1]+S[14]*w[2]+S[15]*w[3]],dot:(S,w)=>S[0]*w[0]+S[1]*w[1]+S[2]*w[2]+S[3]*w[3],sml:(S,w)=>[S*w[0],S*w[1],S*w[2],S*w[3]]};En.encode=function(w,M,T,P,L,$,A){P==null&&(P=0),A==null&&(A=!1);const D=d(w,M,T,P,[!1,!1,!1,0,A,!1]);return c(D,-1),l(D,M,T,L,$)},En.encodeLL=function(w,M,T,P,L,$,A,D){const O={ctype:0+(P==1?0:2)+(L==0?0:4),depth:$,frames:[]},z=(P+L)*$,H=z*M;for(let j=0;j>>0),C(1),C(32),k(3),k(l),k(2835),k(2835),N(8),k(16711680),k(65280),k(255),k(4278190080),k(1466527264),(function S(){for(;_0;){for(y=122+_*a,g=0;g>>24,u.setUint32(y+g,m<<8|p),g+=4;_++}E{e(new Blob([n],{type:"image/bmp"}))}))},_dly:9};var Rt={CHROME:"CHROME",FIREFOX:"FIREFOX",DESKTOP_SAFARI:"DESKTOP_SAFARI",IE:"IE",IOS:"IOS",ETC:"ETC"},oE={[Rt.CHROME]:16384,[Rt.FIREFOX]:11180,[Rt.DESKTOP_SAFARI]:16384,[Rt.IE]:8192,[Rt.IOS]:4096,[Rt.ETC]:8192};const Ac=typeof window<"u",pp=typeof WorkerGlobalScope<"u"&&self instanceof WorkerGlobalScope,Gi=Ac&&window.cordova&&window.cordova.require&&window.cordova.require("cordova/modulemapper"),iE=(Ac||pp)&&(Gi&&Gi.getOriginalSymbol(window,"File")||typeof File<"u"&&File),mp=(Ac||pp)&&(Gi&&Gi.getOriginalSymbol(window,"FileReader")||typeof FileReader<"u"&&FileReader);function Dc(t,e,n=Date.now()){return new Promise((r=>{const s=t.split(","),o=s[0].match(/:(.*?);/)[1],i=globalThis.atob(s[1]);let a=i.length;const l=new Uint8Array(a);for(;a--;)l[a]=i.charCodeAt(a);const c=new Blob([l],{type:o});c.name=e,c.lastModified=n,r(c)}))}function bp(t){return new Promise(((e,n)=>{const r=new mp;r.onload=()=>e(r.result),r.onerror=s=>n(s),r.readAsDataURL(t)}))}function yp(t){return new Promise(((e,n)=>{const r=new Image;r.onload=()=>e(r),r.onerror=s=>n(s),r.src=t}))}function kr(){if(kr.cachedResult!==void 0)return kr.cachedResult;let t=Rt.ETC;const{userAgent:e}=navigator;return/Chrom(e|ium)/i.test(e)?t=Rt.CHROME:/iP(ad|od|hone)/i.test(e)&&/WebKit/i.test(e)?t=Rt.IOS:/Safari/i.test(e)?t=Rt.DESKTOP_SAFARI:/Firefox/i.test(e)?t=Rt.FIREFOX:(/MSIE/i.test(e)||document.documentMode)&&(t=Rt.IE),kr.cachedResult=t,kr.cachedResult}function vp(t,e){const n=kr(),r=oE[n];let s=t,o=e,i=s*o;const a=s>o?o/s:s/o;for(;i>r*r;){const l=(r+s)/2,c=(r+o)/2;lgp.toBlob(t,d))).then(function(d){try{return a=d,a.name=n,a.lastModified=r,c.call(this)}catch(u){return i(u)}}.bind(this),i);{let d=function(){return c.call(this)};if(typeof OffscreenCanvas=="function"&&t instanceof OffscreenCanvas)return t.convertToBlob({type:e,quality:s}).then(function(u){try{return a=u,a.name=n,a.lastModified=r,d.call(this)}catch(f){return i(f)}}.bind(this),i);{let u;return u=t.toDataURL(e,s),Dc(u,n,r).then(function(f){try{return a=f,d.call(this)}catch(g){return i(g)}}.bind(this),i)}}}function l(){return o(a)}}))}function $n(t){t.width=0,t.height=0}function as(){return new Promise((function(t,e){let n,r,s,o;return as.cachedResult!==void 0?t(as.cachedResult):Dc("data:image/jpeg;base64,/9j/4QAiRXhpZgAATU0AKgAAAAgAAQESAAMAAAABAAYAAAAAAAD/2wCEAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAf/AABEIAAEAAgMBEQACEQEDEQH/xABKAAEAAAAAAAAAAAAAAAAAAAALEAEAAAAAAAAAAAAAAAAAAAAAAQEAAAAAAAAAAAAAAAAAAAAAEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwA/8H//2Q==","test.jpg",Date.now()).then((function(i){try{return n=i,Xi(n).then((function(a){try{return r=a[1],qi(r,n.type,n.name,n.lastModified).then((function(l){try{return s=l,$n(r),Xi(s).then((function(c){try{return o=c[0],as.cachedResult=o.width===1&&o.height===2,t(as.cachedResult)}catch(d){return e(d)}}),e)}catch(c){return e(c)}}),e)}catch(l){return e(l)}}),e)}catch(a){return e(a)}}),e)}))}function wp(t){return new Promise(((e,n)=>{const r=new mp;r.onload=s=>{const o=new DataView(s.target.result);if(o.getUint16(0,!1)!=65496)return e(-2);const i=o.byteLength;let a=2;for(;an(s),r.readAsArrayBuffer(t)}))}function kp(t,e){const{width:n}=t,{height:r}=t,{maxWidthOrHeight:s}=e;let o,i=t;return isFinite(s)&&(n>s||r>s)&&([i,o]=Yi(n,r),n>r?(i.width=s,i.height=r/n*s):(i.width=n/r*s,i.height=s),o.drawImage(t,0,0,i.width,i.height),$n(t)),i}function Cp(t,e){const{width:n}=t,{height:r}=t,[s,o]=Yi(n,r);switch(e>4&&e<9?(s.width=r,s.height=n):(s.width=n,s.height=r),e){case 2:o.transform(-1,0,0,1,n,0);break;case 3:o.transform(-1,0,0,-1,n,r);break;case 4:o.transform(1,0,0,-1,0,r);break;case 5:o.transform(0,1,1,0,0,0);break;case 6:o.transform(0,1,-1,0,r,0);break;case 7:o.transform(0,-1,-1,0,r,n);break;case 8:o.transform(0,-1,1,0,0,n)}return o.drawImage(t,0,0,n,r),$n(t),s}function Sp(t,e,n=0){return new Promise((function(r,s){let o,i,a,l,c,d,u,f,g,m,p,y,b,_,x,E,C,k,N,S;function w(T=5){if(e.signal&&e.signal.aborted)throw e.signal.reason;o+=T,e.onProgress(Math.min(o,100))}function M(T){if(e.signal&&e.signal.aborted)throw e.signal.reason;o=Math.min(Math.max(T,o),100),e.onProgress(o)}return o=n,i=e.maxIteration||10,a=1024*e.maxSizeMB*1024,w(),Xi(t,e).then(function(T){try{return[,l]=T,w(),c=kp(l,e),w(),new Promise((function(P,L){var $;if(!($=e.exifOrientation))return wp(t).then(function(D){try{return $=D,A.call(this)}catch(O){return L(O)}}.bind(this),L);function A(){return P($)}return A.call(this)})).then(function(P){try{return d=P,w(),as().then(function(L){try{return u=L?c:Cp(c,d),w(),f=e.initialQuality||1,g=e.fileType||t.type,qi(u,g,t.name,t.lastModified,f).then(function($){try{{let D=function(){if(i--&&(x>a||x>b)){let z,H;return z=S?.95*N.width:N.width,H=S?.95*N.height:N.height,[C,k]=Yi(z,H),k.drawImage(N,0,0,z,H),f*=g==="image/png"?.85:.95,qi(C,g,t.name,t.lastModified,f).then((function(j){try{return E=j,$n(N),N=C,x=E.size,M(Math.min(99,Math.floor((_-x)/(_-a)*100))),D}catch(W){return s(W)}}),s)}return[1]},O=function(){return $n(N),$n(C),$n(c),$n(u),$n(l),M(100),r(E)};if(m=$,w(),p=m.size>a,y=m.size>t.size,!p&&!y)return M(100),r(m);var A;return b=t.size,_=m.size,x=_,N=u,S=!e.alwaysKeepResolution&&p,(A=function(z){for(;z;){if(z.then)return void z.then(A,s);try{if(z.pop){if(z.length)return z.pop()?O.call(this):z;z=D}else z=z.call(this)}catch(H){return s(H)}}}.bind(this))(D)}}catch(D){return s(D)}}.bind(this),s)}catch($){return s($)}}.bind(this),s)}catch(L){return s(L)}}.bind(this),s)}catch(P){return s(P)}}.bind(this),s)}))}const aE=` let scriptImported = false self.addEventListener('message', async (e) => { const { file, id, imageCompressionLibUrl, options } = e.data @@ -221,11 +220,11 @@ self.addEventListener('message', async (e) => { self.postMessage({ error: e.message + '\\n' + e.stack, id }) } }) -`;let Hc;function ME(e,t){return new Promise(((n,r)=>{Hc||(Hc=(function(i){const a=[];return a.push(i),URL.createObjectURL(new Blob(a))})(EE));const s=new Worker(Hc);s.addEventListener("message",(function(i){if(t.signal&&t.signal.aborted)s.terminate();else if(i.data.progress===void 0){if(i.data.error)return r(new Error(i.data.error)),void s.terminate();n(i.data.file),s.terminate()}else t.onProgress(i.data.progress)})),s.addEventListener("error",r),t.signal&&t.signal.addEventListener("abort",(()=>{r(t.signal.reason),s.terminate()})),s.postMessage({file:e,imageCompressionLibUrl:t.libURL,options:{...t,onProgress:void 0,signal:void 0}})}))}function Yt(e,t){return new Promise((function(n,r){let s,o,i,a,l,c;if(s={...t},i=0,{onProgress:a}=s,s.maxSizeMB=s.maxSizeMB||Number.POSITIVE_INFINITY,l=typeof s.useWebWorker!="boolean"||s.useWebWorker,delete s.useWebWorker,s.onProgress=g=>{i=g,typeof a=="function"&&a(i)},!(e instanceof Blob||e instanceof _E))return r(new Error("The file given is not an instance of Blob or File"));if(!/^image/.test(e.type))return r(new Error("The file given is not an image"));if(c=typeof WorkerGlobalScope<"u"&&self instanceof WorkerGlobalScope,!l||typeof Worker!="function"||c)return $p(e,s).then(function(g){try{return o=g,f.call(this)}catch(m){return r(m)}}.bind(this),r);var d=function(){try{return f.call(this)}catch(g){return r(g)}}.bind(this),u=function(g){try{return $p(e,s).then((function(m){try{return o=m,d()}catch(p){return r(p)}}),r)}catch(m){return r(m)}};try{return s.libURL=s.libURL||"https://cdn.jsdelivr.net/npm/browser-image-compression@2.0.2/dist/browser-image-compression.js",ME(e,s).then((function(g){try{return o=g,d()}catch{return u()}}),u)}catch{u()}function f(){try{o.name=e.name,o.lastModified=e.lastModified}catch{}try{s.preserveExif&&e.type==="image/jpeg"&&(!s.fileType||s.fileType&&s.fileType===e.type)&&(o=_p(e,o))}catch{}return n(o)}}))}Yt.getDataUrlFromFile=Tp,Yt.getFilefromDataUrl=Vc,Yt.loadImage=Op,Yt.drawImageInCanvas=Dp,Yt.drawFileInCanvas=na,Yt.canvasToFile=ra,Yt.getExifOrientation=Ip,Yt.handleMaxWidthOrHeight=Lp,Yt.followExifOrientation=Pp,Yt.cleanupCanvasMemory=Wn,Yt.isAutoOrientationInBrowser=Cs,Yt.approximateBelowMaximumCanvasSizeOfBrowser=Ap,Yt.copyExifWithoutOrientation=_p,Yt.getBrowserName=Dr,Yt.version="2.0.2";const NE=window.G7Core,Uc=e=>{const t=["B","KB","MB","GB"];let n=0,r=e;for(;r>=1024&&n{if(e&&typeof e=="object"){const t=e;if(t.response?.data?.message)return t.response.data.message;if(t.response?.data?.errors){const n=Object.values(t.response.data.errors)[0];if(Array.isArray(n)&&n[0])return n[0]}if(t.message)return t.message}return e instanceof Error?e.message:"업로드 실패"},OE=e=>e?e.startsWith("image/")?"fa-file-image":e.startsWith("video/")?"fa-file-video":e.startsWith("audio/")?"fa-file-audio":e==="application/pdf"?"fa-file-pdf":e.includes("word")||e.includes("document")?"fa-file-word":e.includes("excel")||e.includes("spreadsheet")?"fa-file-excel":e.includes("zip")||e.includes("compressed")?"fa-file-zipper":"fa-file":"fa-file",$t=(e,t)=>NE?.t?.(e,t)??e,Fp=window.G7Core?.createLogger?.("Comp:FileUploader")??{log:(...e)=>console.log("[Comp:FileUploader]",...e),warn:(...e)=>console.warn("[Comp:FileUploader]",...e),error:(...e)=>console.error("[Comp:FileUploader]",...e)},Ir=window.G7Core;function AE(e){const{attachmentableType:t,attachmentableId:n,collection:r,maxFiles:s,maxSize:o,maxConcurrentUploads:i,accept:a,imageCompression:l,roleIds:c,autoUpload:d,initialFiles:u,endpoints:f,onFilesChange:g,onUploadComplete:m,onUploadError:p,onRemove:y,onReorder:b,uploadTriggerEvent:_,confirmBeforeRemove:x,uploadParams:M}=e,[C,k]=v.useState(u),[N,S]=v.useState([]),[w,E]=v.useState(!1),D=v.useRef(null),P=v.useRef(0),L=v.useRef(new Set),$=v.useRef(new Map),A=v.useRef(null),[O,T]=v.useState(!1),[z,H]=v.useState(null),[j,G]=v.useState(!1),U=v.useRef(!1),[W,Y]=v.useState([]),[V,X]=v.useState(!1),[K,q]=v.useState(0),ne=v.useRef(0),[ee,ue]=v.useState(new Map),J=v.useRef(new Map),ce=v.useRef(g);ce.current=g,v.useEffect(()=>{ce.current?.(N)},[N]),v.useEffect(()=>{if(U.current)return;const re=u.filter(we=>!L.current.has(we.id)&&(!we.hash||!L.current.has(we.hash))),fe=new Set(re.map(we=>we.id)),ye=Array.from($.current.values()).filter(we=>!fe.has(we.id)&&!L.current.has(we.id));k([...re,...ye])},[u]);const me=v.useCallback(async re=>{const fe={maxSizeMB:l?.maxSizeMB??1,maxWidthOrHeight:l?.maxWidthOrHeight??1920,useWebWorker:!0};return await Yt(re,fe)},[l]),le=v.useCallback(async re=>{const fe=C.length+N.length,ye=Math.max(0,s-fe),we=Array.from(re).slice(0,ye);if(re.length>ye){const xe=Array.from(re).slice(ye),He=$t("attachment.upload_limit_exceeded",{max:s});p?.(He,xe[0]??re[0])}for(const xe of we){if(xe.size>o*1024*1024){p?.(`[${xe.name}] 파일 크기(${Uc(xe.size)})가 최대 허용 크기(${o}MB)를 초과합니다.`,xe);continue}if(a){const _e="."+xe.name.split(".").pop()?.toLowerCase();if(!a.split(",").map(Ce=>Ce.trim().toLowerCase()).includes(_e)){p?.(`[${xe.name}] 허용되지 않는 파일 형식입니다. (허용: ${a})`,xe);continue}}const He={id:`pending-${Date.now()}-${Math.random().toString(36).substr(2,9)}`,file:xe,original_filename:xe.name,mime_type:xe.type,size:xe.size,size_formatted:Uc(xe.size),status:"pending",progress:0};if(xe.type.startsWith("image/")&&(He.preview=URL.createObjectURL(xe)),S(_e=>[..._e,He]),xe.type.startsWith("image/")&&l){S(_e=>_e.map(Le=>Le.id===He.id?{...Le,status:"compressing"}:Le));try{const _e=await me(xe);S(Le=>Le.map(Ce=>Ce.id===He.id?{...Ce,compressedBlob:_e,size:_e.size,size_formatted:Uc(_e.size),status:"pending",progress:0}:Ce))}catch{S(_e=>_e.map(Le=>Le.id===He.id?{...Le,status:"error",error:"이미지 압축 실패"}:Le))}}}},[C.length,N.length,s,o,a,l,me,p]),ve=v.useCallback(async re=>{const fe=new FormData,ye=re.compressedBlob?new File([re.compressedBlob],re.original_filename,{type:re.mime_type}):re.file;fe.append("file",ye),t&&fe.append("attachmentable_type",t),n&&fe.append("attachmentable_id",String(n)),fe.append("collection",r),c.forEach(we=>{fe.append("role_ids[]",String(we))}),M&&Object.entries(M).forEach(([we,xe])=>{fe.append(we,xe)});try{const we=await Ir.api.post(f.upload,fe,{headers:{"Content-Type":"multipart/form-data"},onUploadProgress:He=>{const _e=Math.round(He.loaded*100/(He.total??1));S(Le=>Le.map(Ce=>Ce.id===re.id?{...Ce,progress:_e}:Ce))}});return we?.data?.data??we?.data}catch(we){const xe=TE(we);throw new Error(xe)}},[t,n,r,c,f.upload]),ge=v.useCallback(async()=>{const re=N.filter(_e=>_e.status==="pending"||_e.status==="error");if(re.length===0)return A.current=C,[];const fe=[],ye=new Map,we=[...re],xe=async()=>{for(;we.length>0&&P.currentLe.map(Ce=>Ce.id===_e.id?{...Ce,status:"uploading",progress:0}:Ce));try{const Le=await ve(_e);if(Le)fe.push(Le),ye.set(String(_e.id),String(Le.hash)),$.current.set(Le.id,Le),S(Ce=>Ce.filter(We=>We.id!==_e.id)),k(Ce=>[...Ce,Le]),Y(Ce=>{if(Ce.length===0)return Ce;const We=String(_e.id);return Ce.some(Fe=>String(Fe)===We)?Ce.map(Fe=>String(Fe)===We?String(Le.hash):Fe):Ce}),_e.preview&&URL.revokeObjectURL(_e.preview);else{const Ce=$t("attachment.upload_response_invalid");S(We=>We.map(Ze=>Ze.id===_e.id?{...Ze,status:"error",error:Ce}:Ze)),p?.(`[${_e.original_filename}] ${Ce}`,_e.file)}}catch(Le){const Ce=Le instanceof Error?Le.message:"업로드 실패";S(We=>We.map(Ze=>Ze.id===_e.id?{...Ze,status:"error",error:Ce}:Ze)),p?.(`[${_e.original_filename}] ${Ce}`,_e.file)}finally{P.current--}}},He=Array(Math.min(i,re.length)).fill(null).map(()=>xe());if(await Promise.all(He),fe.length>0){m?.(fe);const _e=[...C,...fe];if(W.length>0){const Le=W.map(Ze=>ye.get(String(Ze))??Ze),Ce=new Map(Le.map((Ze,Fe)=>[String(Ze),Fe])),We=[..._e].sort((Ze,Fe)=>{const Ne=Ce.get(String(Ze.hash))??_e.length,ze=Ce.get(String(Fe.hash))??_e.length;return Ne-ze}).map((Ze,Fe)=>({...Ze,sort_order:Fe,order:Fe+1}));A.current=We,f.reorder&&b&&b(We)}else A.current=_e.map((Le,Ce)=>({...Le,sort_order:Ce,order:Ce+1}))}else A.current=C;return fe},[N,i,ve,m,p,W,C,b,f.reorder]);v.useEffect(()=>{if(!_)return;const re=Ir.componentEvent.on(_,async()=>{const fe=await ge(),ye=A.current??[...C,...fe];return{uploadedAttachments:fe,existingFiles:C,allFiles:ye}});return()=>{re()}},[_,ge,C]),v.useEffect(()=>{d&&N.some(re=>re.status==="pending")&&ge()},[d,N,ge]);const pe=v.useCallback(async re=>{G(!0);try{if(re.id&&f.delete){const ye=f.delete.replace(":id",String(re.id));await Ir.api.delete(ye)}const fe=re.hash||re.id;re.id&&(L.current.add(re.id),$.current.delete(re.id)),re.hash&&L.current.add(re.hash),k(ye=>ye.filter(we=>(we.hash||we.id)!==fe)),y?.(re.hash||re.id),T(!1),H(null)}catch{Fp.error("첨부파일 삭제 실패")}finally{G(!1)}},[y,f.delete]),ke=v.useCallback(async re=>{"hash"in re?x?(H(re),T(!0)):await pe(re):(re.preview&&URL.revokeObjectURL(re.preview),S(fe=>fe.filter(ye=>ye.id!==re.id)))},[x,pe]),$e=v.useCallback(re=>{S(fe=>fe.map(ye=>ye.id===re.id?{...ye,status:"pending",error:void 0}:ye))},[]),ie=v.useCallback(async re=>{const{arrayMove:fe}=await Promise.resolve().then(()=>RS),{active:ye,over:we}=re;if(!we||ye.id===we.id)return;const xe=[...C,...N];let He;if(W.length===0)He=xe;else{const Ne=new Map(W.map((ze,Ke)=>[String(ze),Ke]));He=[...xe].sort((ze,Ke)=>{const tt="hash"in ze?String(ze.hash):String(ze.id),at="hash"in Ke?String(Ke.hash):String(Ke.id);return(Ne.get(tt)??xe.length)-(Ne.get(at)??xe.length)})}const _e=He.findIndex(Ne=>"hash"in Ne?Ne.hash===ye.id:Ne.id===ye.id),Le=He.findIndex(Ne=>"hash"in Ne?Ne.hash===we.id:Ne.id===we.id),Ce=fe(He,_e,Le);Y(Ce.map(Ne=>"hash"in Ne?Ne.hash:Ne.id));const We=Ce.filter(Ne=>"hash"in Ne),Ze=Ce.filter(Ne=>!("hash"in Ne));U.current=!0,k(We),S(Ze);const Fe=We.map((Ne,ze)=>({...Ne,sort_order:ze,order:ze+1}));if(We.length>0&&f.reorder){const Ne=We.map((ze,Ke)=>({id:ze.id,order:Ke+1}));try{const ze=await Ir.api.patch(f.reorder,{order:Ne});Ir.toast.success(ze.message)}catch(ze){const Ke=ze instanceof Error?ze.message:$t("attachment.reorder_failed");Ir.toast.error(Ke)}}b?.(Fe)},[C,N,W,f.reorder,b]),Me=v.useMemo(()=>[...C,...N].filter(re=>"is_image"in re?re.is_image:re.mime_type?.startsWith("image/")??!1),[C,N]),Ae=v.useCallback(re=>{const fe=Me.findIndex(ye=>"hash"in re&&"hash"in ye?ye.hash===re.hash:"id"in re&&"id"in ye&&!("hash"in re)&&!("hash"in ye)?ye.id===re.id:!1);fe!==-1&&(ne.current+=1,q(fe),X(!0))},[Me]);v.useEffect(()=>{let re=!1;return(async()=>{const ye=C.filter(xe=>xe.is_image),we=new Set(ye.map(xe=>xe.id));for(const[xe,He]of J.current)we.has(xe)||(URL.revokeObjectURL(He),J.current.delete(xe));for(const xe of ye){if(re)break;if(!J.current.has(xe.id))try{const He=await Ir.api.get(xe.download_url,{responseType:"blob"});if(!re&&He){const _e=URL.createObjectURL(He);J.current.set(xe.id,_e),ue(new Map(J.current))}}catch(He){Fp.error("Failed to load authenticated image:",He)}}})(),()=>{re=!0}},[C]),v.useEffect(()=>()=>{J.current.forEach(re=>URL.revokeObjectURL(re)),J.current.clear()},[]);const Ie=v.useCallback(async re=>{const{executeImageDownload:fe}=await Promise.resolve().then(()=>bE);await fe({src:re.download_url,downloadUrl:re.download_url,filename:re.original_filename,downloadRequiresAuth:!0})},[]),Xe=v.useMemo(()=>{const re=[...C,...N];if(W.length===0)return re;const fe=new Map(W.map((ye,we)=>[String(ye),we]));return[...re].sort((ye,we)=>{const xe="hash"in ye?String(ye.hash):String(ye.id),He="hash"in we?String(we.hash):String(we.id),_e=fe.get(xe)??re.length,Le=fe.get(He)??re.length;return _e-Le})},[C,N,W]),gt=Xe.length,pt=gt>0,Be=gtS([]),[]),je=v.useCallback(()=>N,[N]);return{existingFiles:C,pendingFiles:N,isDragOver:w,setIsDragOver:E,isDeleting:j,totalCount:gt,hasFiles:pt,canAddMore:Be,allItems:Xe,imageFiles:Me,handleFiles:le,handleRemove:ke,handleRetry:$e,handleUploadAll:ge,handleOpenGallery:Ae,handleDownload:Ie,handleDragEnd:ie,inputRef:D,galleryOpen:V,setGalleryOpen:X,galleryStartIndex:K,galleryKeyRef:ne,confirmDialogOpen:O,setConfirmDialogOpen:T,itemToDelete:z,executeRemoveAttachment:pe,authenticatedImageUrls:ee,clear:et,getPendingFiles:je}}const DE=({isDragOver:e,canAddMore:t,onFiles:n,setIsDragOver:r,inputRef:s,accept:o,maxFiles:i,maxSize:a,disabled:l,children:c})=>{const d=t&&!l;return h.jsxs(I,{className:` +`;let Ic;function lE(t,e){return new Promise(((n,r)=>{Ic||(Ic=(function(i){const a=[];return a.push(i),URL.createObjectURL(new Blob(a))})(aE));const s=new Worker(Ic);s.addEventListener("message",(function(i){if(e.signal&&e.signal.aborted)s.terminate();else if(i.data.progress===void 0){if(i.data.error)return r(new Error(i.data.error)),void s.terminate();n(i.data.file),s.terminate()}else e.onProgress(i.data.progress)})),s.addEventListener("error",r),e.signal&&e.signal.addEventListener("abort",(()=>{r(e.signal.reason),s.terminate()})),s.postMessage({file:t,imageCompressionLibUrl:e.libURL,options:{...e,onProgress:void 0,signal:void 0}})}))}function Ht(t,e){return new Promise((function(n,r){let s,o,i,a,l,c;if(s={...e},i=0,{onProgress:a}=s,s.maxSizeMB=s.maxSizeMB||Number.POSITIVE_INFINITY,l=typeof s.useWebWorker!="boolean"||s.useWebWorker,delete s.useWebWorker,s.onProgress=g=>{i=g,typeof a=="function"&&a(i)},!(t instanceof Blob||t instanceof iE))return r(new Error("The file given is not an instance of Blob or File"));if(!/^image/.test(t.type))return r(new Error("The file given is not an image"));if(c=typeof WorkerGlobalScope<"u"&&self instanceof WorkerGlobalScope,!l||typeof Worker!="function"||c)return Sp(t,s).then(function(g){try{return o=g,f.call(this)}catch(m){return r(m)}}.bind(this),r);var d=function(){try{return f.call(this)}catch(g){return r(g)}}.bind(this),u=function(g){try{return Sp(t,s).then((function(m){try{return o=m,d()}catch(p){return r(p)}}),r)}catch(m){return r(m)}};try{return s.libURL=s.libURL||"https://cdn.jsdelivr.net/npm/browser-image-compression@2.0.2/dist/browser-image-compression.js",lE(t,s).then((function(g){try{return o=g,d()}catch{return u()}}),u)}catch{u()}function f(){try{o.name=t.name,o.lastModified=t.lastModified}catch{}try{s.preserveExif&&t.type==="image/jpeg"&&(!s.fileType||s.fileType&&s.fileType===t.type)&&(o=hp(t,o))}catch{}return n(o)}}))}Ht.getDataUrlFromFile=bp,Ht.getFilefromDataUrl=Dc,Ht.loadImage=yp,Ht.drawImageInCanvas=xp,Ht.drawFileInCanvas=Xi,Ht.canvasToFile=qi,Ht.getExifOrientation=wp,Ht.handleMaxWidthOrHeight=kp,Ht.followExifOrientation=Cp,Ht.cleanupCanvasMemory=$n,Ht.isAutoOrientationInBrowser=as,Ht.approximateBelowMaximumCanvasSizeOfBrowser=vp,Ht.copyExifWithoutOrientation=hp,Ht.getBrowserName=kr,Ht.version="2.0.2";const cE=window.G7Core,Lc=t=>{const e=["B","KB","MB","GB"];let n=0,r=t;for(;r>=1024&&n{if(t&&typeof t=="object"){const e=t;if(e.response?.data?.message)return e.response.data.message;if(e.response?.data?.errors){const n=Object.values(e.response.data.errors)[0];if(Array.isArray(n)&&n[0])return n[0]}if(e.message)return e.message}return t instanceof Error?t.message:"업로드 실패"},uE=t=>t?t.startsWith("image/")?"fa-file-image":t.startsWith("video/")?"fa-file-video":t.startsWith("audio/")?"fa-file-audio":t==="application/pdf"?"fa-file-pdf":t.includes("word")||t.includes("document")?"fa-file-word":t.includes("excel")||t.includes("spreadsheet")?"fa-file-excel":t.includes("zip")||t.includes("compressed")?"fa-file-zipper":"fa-file":"fa-file",It=(t,e)=>cE?.t?.(t,e)??t,_p=window.G7Core?.createLogger?.("Comp:FileUploader")??{log:(...t)=>console.log("[Comp:FileUploader]",...t),warn:(...t)=>console.warn("[Comp:FileUploader]",...t),error:(...t)=>console.error("[Comp:FileUploader]",...t)},Cr=window.G7Core;function fE(t){const{attachmentableType:e,attachmentableId:n,collection:r,maxFiles:s,maxSize:o,maxConcurrentUploads:i,accept:a,imageCompression:l,roleIds:c,autoUpload:d,initialFiles:u,endpoints:f,onFilesChange:g,onUploadComplete:m,onUploadError:p,onRemove:y,onReorder:b,uploadTriggerEvent:_,confirmBeforeRemove:x,uploadParams:E}=t,[C,k]=v.useState(u),[N,S]=v.useState([]),[w,M]=v.useState(!1),T=v.useRef(null),P=v.useRef(0),L=v.useRef(new Set),$=v.useRef(new Map),A=v.useRef(null),[D,O]=v.useState(!1),[z,H]=v.useState(null),[j,W]=v.useState(!1),U=v.useRef(!1),[B,G]=v.useState([]),[V,X]=v.useState(!1),[K,Z]=v.useState(0),ee=v.useRef(0),[ne,he]=v.useState(new Map),J=v.useRef(new Map),le=v.useRef(g);le.current=g,v.useEffect(()=>{le.current?.(N)},[N]),v.useEffect(()=>{if(U.current)return;const te=u.filter(pe=>!L.current.has(pe.id)&&(!pe.hash||!L.current.has(pe.hash))),ge=new Set(te.map(pe=>pe.id)),me=Array.from($.current.values()).filter(pe=>!ge.has(pe.id)&&!L.current.has(pe.id));k([...te,...me])},[u]);const ue=v.useCallback(async te=>{const ge={maxSizeMB:l?.maxSizeMB??1,maxWidthOrHeight:l?.maxWidthOrHeight??1920,useWebWorker:!0};return await Ht(te,ge)},[l]),ae=v.useCallback(async te=>{const ge=C.length+N.length,me=Math.max(0,s-ge),pe=Array.from(te).slice(0,me);if(te.length>me){const be=Array.from(te).slice(me),Ue=It("attachment.upload_limit_exceeded",{max:s});p?.(Ue,be[0]??te[0])}for(const be of pe){if(be.size>o*1024*1024){p?.(`[${be.name}] 파일 크기(${Lc(be.size)})가 최대 허용 크기(${o}MB)를 초과합니다.`,be);continue}if(a){const Ce="."+be.name.split(".").pop()?.toLowerCase();if(!a.split(",").map(Se=>Se.trim().toLowerCase()).includes(Ce)){p?.(`[${be.name}] 허용되지 않는 파일 형식입니다. (허용: ${a})`,be);continue}}const Ue={id:`pending-${Date.now()}-${Math.random().toString(36).substr(2,9)}`,file:be,original_filename:be.name,mime_type:be.type,size:be.size,size_formatted:Lc(be.size),status:"pending",progress:0};if(be.type.startsWith("image/")&&(Ue.preview=URL.createObjectURL(be)),S(Ce=>[...Ce,Ue]),be.type.startsWith("image/")&&l){S(Ce=>Ce.map(ze=>ze.id===Ue.id?{...ze,status:"compressing"}:ze));try{const Ce=await ue(be);S(ze=>ze.map(Se=>Se.id===Ue.id?{...Se,compressedBlob:Ce,size:Ce.size,size_formatted:Lc(Ce.size),status:"pending",progress:0}:Se))}catch{S(Ce=>Ce.map(ze=>ze.id===Ue.id?{...ze,status:"error",error:"이미지 압축 실패"}:ze))}}}},[C.length,N.length,s,o,a,l,ue,p]),ve=v.useCallback(async te=>{const ge=new FormData,me=te.compressedBlob?new File([te.compressedBlob],te.original_filename,{type:te.mime_type}):te.file;ge.append("file",me),e&&ge.append("attachmentable_type",e),n&&ge.append("attachmentable_id",String(n)),ge.append("collection",r),c.forEach(pe=>{ge.append("role_ids[]",String(pe))}),E&&Object.entries(E).forEach(([pe,be])=>{ge.append(pe,be)});try{const pe=await Cr.api.post(f.upload,ge,{headers:{"Content-Type":"multipart/form-data"},onUploadProgress:Ue=>{const Ce=Math.round(Ue.loaded*100/(Ue.total??1));S(ze=>ze.map(Se=>Se.id===te.id?{...Se,progress:Ce}:Se))}});return pe?.data?.data??pe?.data}catch(pe){const be=dE(pe);throw new Error(be)}},[e,n,r,c,f.upload]),fe=v.useCallback(async()=>{const te=N.filter(Ce=>Ce.status==="pending"||Ce.status==="error");if(te.length===0)return A.current=C,[];const ge=[],me=new Map,pe=[...te],be=async()=>{for(;pe.length>0&&P.currentze.map(Se=>Se.id===Ce.id?{...Se,status:"uploading",progress:0}:Se));try{const ze=await ve(Ce);if(ze)ge.push(ze),me.set(String(Ce.id),String(ze.hash)),$.current.set(ze.id,ze),S(Se=>Se.filter(Re=>Re.id!==Ce.id)),k(Se=>[...Se,ze]),G(Se=>{if(Se.length===0)return Se;const Re=String(Ce.id);return Se.some(Pe=>String(Pe)===Re)?Se.map(Pe=>String(Pe)===Re?String(ze.hash):Pe):Se}),Ce.preview&&URL.revokeObjectURL(Ce.preview);else{const Se=It("attachment.upload_response_invalid");S(Re=>Re.map(ot=>ot.id===Ce.id?{...ot,status:"error",error:Se}:ot)),p?.(`[${Ce.original_filename}] ${Se}`,Ce.file)}}catch(ze){const Se=ze instanceof Error?ze.message:"업로드 실패";S(Re=>Re.map(ot=>ot.id===Ce.id?{...ot,status:"error",error:Se}:ot)),p?.(`[${Ce.original_filename}] ${Se}`,Ce.file)}finally{P.current--}}},Ue=Array(Math.min(i,te.length)).fill(null).map(()=>be());if(await Promise.all(Ue),ge.length>0){m?.(ge);const Ce=[...C,...ge];if(B.length>0){const ze=B.map(ot=>me.get(String(ot))??ot),Se=new Map(ze.map((ot,Pe)=>[String(ot),Pe])),Re=[...Ce].sort((ot,Pe)=>{const Te=Se.get(String(ot.hash))??Ce.length,Ve=Se.get(String(Pe.hash))??Ce.length;return Te-Ve}).map((ot,Pe)=>({...ot,sort_order:Pe,order:Pe+1}));A.current=Re,f.reorder&&b&&b(Re)}else A.current=Ce.map((ze,Se)=>({...ze,sort_order:Se,order:Se+1}))}else A.current=C;return ge},[N,i,ve,m,p,B,C,b,f.reorder]);v.useEffect(()=>{if(!_)return;const te=Cr.componentEvent.on(_,async()=>{const ge=await fe(),me=A.current??[...C,...ge];return{uploadedAttachments:ge,existingFiles:C,allFiles:me}});return()=>{te()}},[_,fe,C]),v.useEffect(()=>{d&&N.some(te=>te.status==="pending")&&fe()},[d,N,fe]);const ye=v.useCallback(async te=>{W(!0);try{if(te.id&&f.delete){const me=f.delete.replace(":id",String(te.id));await Cr.api.delete(me)}const ge=te.hash||te.id;te.id&&(L.current.add(te.id),$.current.delete(te.id)),te.hash&&L.current.add(te.hash),k(me=>me.filter(pe=>(pe.hash||pe.id)!==ge)),y?.(te.hash||te.id),O(!1),H(null)}catch{_p.error("첨부파일 삭제 실패")}finally{W(!1)}},[y,f.delete]),we=v.useCallback(async te=>{"hash"in te?x?(H(te),O(!0)):await ye(te):(te.preview&&URL.revokeObjectURL(te.preview),S(ge=>ge.filter(me=>me.id!==te.id)))},[x,ye]),He=v.useCallback(te=>{S(ge=>ge.map(me=>me.id===te.id?{...me,status:"pending",error:void 0}:me))},[]),se=v.useCallback(async te=>{const{arrayMove:ge}=await Promise.resolve().then(()=>LS),{active:me,over:pe}=te;if(!pe||me.id===pe.id)return;const be=[...C,...N];let Ue;if(B.length===0)Ue=be;else{const Te=new Map(B.map((Ve,De)=>[String(Ve),De]));Ue=[...be].sort((Ve,De)=>{const qe="hash"in Ve?String(Ve.hash):String(Ve.id),it="hash"in De?String(De.hash):String(De.id);return(Te.get(qe)??be.length)-(Te.get(it)??be.length)})}const Ce=Ue.findIndex(Te=>"hash"in Te?Te.hash===me.id:Te.id===me.id),ze=Ue.findIndex(Te=>"hash"in Te?Te.hash===pe.id:Te.id===pe.id),Se=ge(Ue,Ce,ze);G(Se.map(Te=>"hash"in Te?Te.hash:Te.id));const Re=Se.filter(Te=>"hash"in Te),ot=Se.filter(Te=>!("hash"in Te));U.current=!0,k(Re),S(ot);const Pe=Re.map((Te,Ve)=>({...Te,sort_order:Ve,order:Ve+1}));if(Re.length>0&&f.reorder){const Te=Re.map((Ve,De)=>({id:Ve.id,order:De+1}));try{const Ve=await Cr.api.patch(f.reorder,{order:Te});Cr.toast.success(Ve.message)}catch(Ve){const De=Ve instanceof Error?Ve.message:It("attachment.reorder_failed");Cr.toast.error(De)}}b?.(Pe)},[C,N,B,f.reorder,b]),Oe=v.useMemo(()=>[...C,...N].filter(te=>"is_image"in te?te.is_image:te.mime_type?.startsWith("image/")??!1),[C,N]),Le=v.useCallback(te=>{const ge=Oe.findIndex(me=>"hash"in te&&"hash"in me?me.hash===te.hash:"id"in te&&"id"in me&&!("hash"in te)&&!("hash"in me)?me.id===te.id:!1);ge!==-1&&(ee.current+=1,Z(ge),X(!0))},[Oe]);v.useEffect(()=>{let te=!1;return(async()=>{const me=C.filter(be=>be.is_image),pe=new Set(me.map(be=>be.id));for(const[be,Ue]of J.current)pe.has(be)||(URL.revokeObjectURL(Ue),J.current.delete(be));for(const be of me){if(te)break;if(!J.current.has(be.id))try{const Ue=await Cr.api.get(be.download_url,{responseType:"blob"});if(!te&&Ue){const Ce=URL.createObjectURL(Ue);J.current.set(be.id,Ce),he(new Map(J.current))}}catch(Ue){_p.error("Failed to load authenticated image:",Ue)}}})(),()=>{te=!0}},[C]),v.useEffect(()=>()=>{J.current.forEach(te=>URL.revokeObjectURL(te)),J.current.clear()},[]);const Ie=v.useCallback(async te=>{const{executeImageDownload:ge}=await Promise.resolve().then(()=>J2);await ge({src:te.download_url,downloadUrl:te.download_url,filename:te.original_filename,downloadRequiresAuth:!0})},[]),Be=v.useMemo(()=>{const te=[...C,...N];if(B.length===0)return te;const ge=new Map(B.map((me,pe)=>[String(me),pe]));return[...te].sort((me,pe)=>{const be="hash"in me?String(me.hash):String(me.id),Ue="hash"in pe?String(pe.hash):String(pe.id),Ce=ge.get(be)??te.length,ze=ge.get(Ue)??te.length;return Ce-ze})},[C,N,B]),st=Be.length,ut=st>0,je=stS([]),[]),Ye=v.useCallback(()=>N,[N]);return{existingFiles:C,pendingFiles:N,isDragOver:w,setIsDragOver:M,isDeleting:j,totalCount:st,hasFiles:ut,canAddMore:je,allItems:Be,imageFiles:Oe,handleFiles:ae,handleRemove:we,handleRetry:He,handleUploadAll:fe,handleOpenGallery:Le,handleDownload:Ie,handleDragEnd:se,inputRef:T,galleryOpen:V,setGalleryOpen:X,galleryStartIndex:K,galleryKeyRef:ee,confirmDialogOpen:D,setConfirmDialogOpen:O,itemToDelete:z,executeRemoveAttachment:ye,authenticatedImageUrls:ne,clear:nt,getPendingFiles:Ye}}const hE=({isDragOver:t,canAddMore:e,onFiles:n,setIsDragOver:r,inputRef:s,accept:o,maxFiles:i,maxSize:a,disabled:l,children:c})=>{const d=e&&!l;return h.jsxs(I,{className:` relative border-2 border-dashed rounded-lg transition-colors ${l?"opacity-60 cursor-not-allowed":""} - ${e&&!l?"border-blue-500 bg-blue-50 dark:bg-blue-900/20":"border-gray-300 dark:border-gray-600"} - `,onDragOver:u=>{u.preventDefault(),d&&r(!0)},onDragLeave:()=>r(!1),onDrop:u=>{u.preventDefault(),r(!1),d&&n(u.dataTransfer.files)},children:[h.jsx("input",{ref:s,type:"file",multiple:!0,accept:o,onChange:u=>u.target.files&&!l&&n(u.target.files),className:"hidden",disabled:l}),!c&&h.jsxs(I,{className:`p-8 text-center transition-colors ${l?"cursor-not-allowed":"cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800/50"}`,onClick:()=>!l&&s.current?.click(),children:[h.jsx(bt,{className:"fa-solid fa-cloud-arrow-up text-4xl text-gray-400 dark:text-gray-500 mb-4"}),h.jsx(vt,{className:"text-sm text-gray-600 dark:text-gray-400",children:$t("attachment.drop_or_click")}),h.jsxs(vt,{className:"text-xs text-gray-500 dark:text-gray-500 mt-2",children:[$t("attachment.upload_limit",{maxFiles:i,maxSize:a}),o&&` (${o})`]})]}),c]})},IE=window.G7Core,LE=window.G7Core?.createLogger?.("Comp:SortableThumbnail")??{log:(...e)=>console.log("[Comp:SortableThumbnail]",...e),warn:(...e)=>console.warn("[Comp:SortableThumbnail]",...e),error:(...e)=>console.error("[Comp:SortableThumbnail]",...e)},PE=({file:e,onRemove:t,onRetry:n,onImageClick:r,onDownload:s,isPrimary:o,onPrimaryClick:i,disabled:a})=>{const{attributes:l,listeners:c,setNodeRef:d,transform:u,transition:f,isDragging:g}=Ui({id:"hash"in e?e.hash:e.id}),m={transform:jn.Transform.toString(u),transition:f,opacity:g?.5:1},p="status"in e&&e.status!==void 0,y=p?e.status:"done",b=y==="uploading",_=y==="compressing",x=y==="error",M=p?e.progress:100,C=p?e.preview:void 0,k=p?e.mime_type?.startsWith("image/")??!1:e.is_image,[N,S]=v.useState(void 0),w=!p&&k?e.download_url:void 0;v.useEffect(()=>{if(!w){S(void 0);return}let D=!0,P;return(async()=>{try{const $=await IE.api.get(w,{responseType:"blob"});D&&$&&(P=URL.createObjectURL($),S(P))}catch($){LE.error("Failed to load authenticated image:",$)}})(),()=>{D=!1,P&&URL.revokeObjectURL(P)}},[w]);const E=N;return h.jsxs(I,{ref:d,style:m,className:"relative group bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden",children:[h.jsxs(I,{className:`aspect-square w-full flex items-center justify-center bg-gray-50 dark:bg-gray-900 relative ${k||!p?"cursor-pointer":""}`,onClick:()=>{b||_||(k&&r?r():!p&&s&&s())},children:[C||E?h.jsx(_n,{src:C||E,alt:e.original_filename,className:"w-full h-full object-cover"}):h.jsx(bt,{className:`fa-solid ${OE(e.mime_type)} text-4xl text-gray-400 dark:text-gray-500`}),(b||_)&&h.jsxs(I,{className:"absolute inset-0 bg-black/50 flex flex-col items-center justify-center",children:[h.jsx(I,{className:"w-3/4 h-2 bg-gray-700 rounded-full overflow-hidden",children:h.jsx(I,{className:"h-full bg-blue-500 transition-all",style:{width:`${M}%`}})}),h.jsxs(Q,{className:"text-white text-xs mt-1",children:[M,"%"]})]}),x&&h.jsx(I,{className:"absolute inset-0 bg-red-500/50 flex items-center justify-center",children:h.jsx(bt,{className:"fa-solid fa-exclamation-triangle text-2xl text-white"})}),i&&h.jsx(ae,{type:"button",onClick:D=>{D.stopPropagation(),i()},className:`absolute top-1.5 left-1.5 z-10 p-1 rounded-full transition-colors ${o?"bg-yellow-500 text-white":"bg-gray-800/50 text-gray-300 hover:bg-yellow-500 hover:text-white opacity-0 group-hover:opacity-100"}`,title:$t(o?"attachment.primary_image":"attachment.set_as_primary"),children:h.jsx(bt,{className:"fa-solid fa-star text-xs"})}),h.jsxs(I,{className:`absolute inset-0 bg-black/0 group-hover:bg-black/40 transition-colors flex items-start justify-end p-1 gap-1 opacity-0 group-hover:opacity-100 ${a?"hidden":""}`,children:[h.jsx(ae,{type:"button",...l,...c,className:"p-1.5 bg-white/90 dark:bg-gray-800/90 rounded text-gray-600 dark:text-gray-300 hover:bg-white cursor-grab active:cursor-grabbing",title:$t("attachment.drag_to_reorder"),onClick:D=>D.stopPropagation(),children:h.jsx(bt,{className:"fa-solid fa-grip-vertical text-sm"})}),!p&&s&&h.jsx(ae,{type:"button",onClick:D=>{D.stopPropagation(),s()},className:"p-1.5 bg-white/90 dark:bg-gray-800/90 rounded text-blue-500 hover:bg-white",title:$t("common.download"),children:h.jsx(bt,{className:"fa-solid fa-download text-sm"})}),x&&n&&h.jsx(ae,{type:"button",onClick:D=>{D.stopPropagation(),n()},className:"p-1.5 bg-white/90 dark:bg-gray-800/90 rounded text-orange-500 hover:bg-white",title:$t("common.retry"),children:h.jsx(bt,{className:"fa-solid fa-arrow-rotate-right text-sm"})}),h.jsx(ae,{type:"button",onClick:D=>{D.stopPropagation(),t()},disabled:b||_,className:"p-1.5 bg-white/90 dark:bg-gray-800/90 rounded text-red-500 hover:bg-white disabled:opacity-50",title:$t("common.delete"),children:h.jsx(bt,{className:"fa-solid fa-times text-sm"})})]})]}),h.jsxs(I,{className:`p-2 ${!p&&s?"cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-700":""}`,onClick:()=>{!p&&s&&s()},children:[h.jsx(vt,{className:"text-xs font-medium text-gray-900 dark:text-white truncate",title:e.original_filename,children:e.original_filename}),h.jsx(vt,{className:"text-xs text-gray-500 dark:text-gray-400",children:e.size_formatted})]})]})},$E=({allItems:e,canAddMore:t,totalCount:n,maxFiles:r,maxSize:s,accept:o,onRemove:i,onRetry:a,onImageClick:l,onDownload:c,onDragEnd:d,onAddClick:u,enablePrimarySelection:f,primaryFileId:g,onPrimaryChange:m,disabled:p})=>{const y=Zl(fs(io,{activationConstraint:{distance:8}}),fs(oo,{coordinateGetter:Bi}));return h.jsxs(I,{className:"p-3",children:[h.jsx(ac,{sensors:y,collisionDetection:Ql,onDragEnd:d,children:h.jsx(Hi,{items:e.map(b=>"hash"in b?b.hash:b.id),strategy:zi,children:h.jsxs(I,{className:"grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-3",children:[e.map(b=>{const _="hash"in b,x=_?b.is_image:b.mime_type?.startsWith("image/")??!1;return h.jsx(PE,{file:b,onRemove:()=>i(b),onRetry:"status"in b&&b.status==="error"?()=>a(b):void 0,onImageClick:x?()=>l(b):void 0,onDownload:_?()=>c(b):void 0,isPrimary:f&&_&&(b.hash||b.id)===g,onPrimaryClick:f&&_?()=>m?.(b.hash||b.id):void 0,disabled:p},_?b.hash:b.id)}),t&&!p&&h.jsxs(I,{className:"aspect-square flex flex-col items-center justify-center border-2 border-dashed border-gray-300 dark:border-gray-600 rounded-lg cursor-pointer hover:border-gray-400 dark:hover:border-gray-500 hover:bg-gray-50 dark:hover:bg-gray-800/50 transition-colors",onClick:u,children:[h.jsx(bt,{className:"fa-solid fa-plus text-2xl text-gray-400 dark:text-gray-500 mb-1"}),h.jsx(Q,{className:"text-xs text-gray-500 dark:text-gray-400",children:$t("common.add")})]})]})})}),h.jsxs(I,{className:"mt-3 pt-3 border-t border-gray-200 dark:border-gray-700 flex justify-between items-center text-xs text-gray-500 dark:text-gray-400",children:[h.jsx(Q,{children:$t("attachment.attached_count",{count:n,max:r})}),h.jsxs(Q,{children:[$t("attachment.max_size",{size:s}),o&&` (${o})`]})]})]})},zp=v.forwardRef(({uploadTriggerEvent:e,attachmentableType:t,attachmentableId:n,collection:r="default",maxFiles:s=10,maxSize:o=10,maxConcurrentUploads:i=3,accept:a,imageCompression:l,roleIds:c=[],autoUpload:d=!0,onFilesChange:u,onUploadComplete:f,onUploadError:g,onRemove:m,onReorder:p,initialFiles:y=[],disabled:b=!1,className:_="",confirmBeforeRemove:x=!1,confirmRemoveTitle:M,confirmRemoveMessage:C,apiEndpoints:k,uploadParams:N,enablePrimarySelection:S=!1,primaryFileId:w,onPrimaryChange:E,id:D,editorAttrs:P},L)=>{const $={upload:k?.upload??"/api/admin/attachments",delete:k?.delete??"/api/admin/attachments/:id",reorder:k?.reorder??"/api/admin/attachments/reorder"},A=AE({attachmentableType:t,attachmentableId:n,collection:r,maxFiles:s,maxSize:o,maxConcurrentUploads:i,accept:a,imageCompression:l,roleIds:c,autoUpload:d,initialFiles:y,endpoints:$,onFilesChange:u,onUploadComplete:f,onUploadError:g,onRemove:m,onReorder:p,uploadTriggerEvent:e,confirmBeforeRemove:x,uploadParams:N});v.useImperativeHandle(L,()=>({uploadAll:A.handleUploadAll,clear:A.clear,getPendingFiles:A.getPendingFiles}));const O=v.useMemo(()=>A.imageFiles.map(z=>{const H=!("hash"in z),j=z,G=z;return H?{src:j.preview||"",filename:j.original_filename,title:j.original_filename,downloadRequiresAuth:!1}:{src:A.authenticatedImageUrls.get(G.id)||G.download_url,downloadUrl:G.download_url,filename:G.original_filename,title:G.original_filename,downloadRequiresAuth:!0}}),[A.imageFiles,A.authenticatedImageUrls]),T=v.useMemo(()=>({existingFiles:A.existingFiles,pendingFiles:A.pendingFiles,isDragOver:A.isDragOver,isDeleting:A.isDeleting,maxFiles:s,maxSize:o,accept:a,endpoints:$,confirmBeforeRemove:x,totalCount:A.totalCount,hasFiles:A.hasFiles,canAddMore:A.canAddMore,allItems:A.allItems,imageFiles:A.imageFiles,handleFiles:A.handleFiles,handleRemove:A.handleRemove,handleRetry:A.handleRetry,handleUploadAll:A.handleUploadAll,handleOpenGallery:A.handleOpenGallery,handleDownload:A.handleDownload,handleDragEnd:A.handleDragEnd,setIsDragOver:A.setIsDragOver,inputRef:A.inputRef,galleryOpen:A.galleryOpen,setGalleryOpen:A.setGalleryOpen,galleryStartIndex:A.galleryStartIndex,galleryKeyRef:A.galleryKeyRef,confirmDialogOpen:A.confirmDialogOpen,setConfirmDialogOpen:A.setConfirmDialogOpen,itemToDelete:A.itemToDelete,executeRemoveAttachment:A.executeRemoveAttachment,authenticatedImageUrls:A.authenticatedImageUrls}),[A,s,o,a,$,x]);return h.jsx(vE,{value:T,children:h.jsxs(I,{id:D,className:_,...P,children:[h.jsx(DE,{isDragOver:A.isDragOver,canAddMore:A.canAddMore,onFiles:A.handleFiles,setIsDragOver:A.setIsDragOver,inputRef:A.inputRef,accept:a,maxFiles:s,maxSize:o,disabled:b,children:A.hasFiles&&h.jsx($E,{allItems:A.allItems,canAddMore:A.canAddMore,totalCount:A.totalCount,maxFiles:s,maxSize:o,accept:a,onRemove:A.handleRemove,onRetry:A.handleRetry,onImageClick:A.handleOpenGallery,onDownload:A.handleDownload,onDragEnd:A.handleDragEnd,onAddClick:()=>A.inputRef.current?.click(),enablePrimarySelection:S,primaryFileId:w,onPrimaryChange:E,disabled:b})}),x&&h.jsx(pu,{isOpen:A.confirmDialogOpen,onClose:()=>{A.isDeleting||A.setConfirmDialogOpen(!1)},title:M??$t("attachment.delete_confirm_title"),message:C??$t("attachment.delete_confirm_message"),confirmText:$t("common.delete"),confirmButtonVariant:"danger",onConfirm:()=>{A.itemToDelete&&A.executeRemoveAttachment(A.itemToDelete)},isLoading:A.isDeleting,loadingText:$t("attachment.deleting")}),O.length>0&&A.galleryOpen&&h.jsx(Qi,{images:O,isOpen:!0,onClose:()=>A.setGalleryOpen(!1),startIndex:A.galleryStartIndex,showDownload:!0},`gallery-${A.galleryKeyRef.current}`)]})})});zp.displayName="FileUploader";const FE=({name:e,value:t,options:n,onChange:r,disabled:s=!1,inline:o=!1,className:i="",size:a="md",label:l,error:c,id:d,editorAttrs:u})=>{const[f,g]=v.useState(()=>t??"");v.useEffect(()=>{g(t??"")},[t]);const m=_=>{const x=_.target.value;g(x),r&&!s&&r(_)},y={sm:{radio:"w-3.5 h-3.5",label:"text-xs",gap:"gap-1.5"},md:{radio:"w-4 h-4",label:"text-sm",gap:"gap-2"},lg:{radio:"w-5 h-5",label:"text-base",gap:"gap-2.5"}}[a],b=o?"flex flex-wrap items-center gap-4":"flex flex-col gap-2";return h.jsxs(I,{className:i,id:d,...u,children:[l&&h.jsx(Q,{className:`block mb-2 font-medium text-gray-700 dark:text-gray-300 ${y.label}`,children:l}),h.jsx(I,{className:b,children:n.map(_=>{const x=String(_.value)===String(f),M=s||_.disabled;return h.jsxs(ln,{className:`inline-flex items-center ${y.gap} ${M?"cursor-not-allowed opacity-50":"cursor-pointer"}`,children:[h.jsx("input",{type:"radio",name:e,value:_.value,checked:x,onChange:m,disabled:M,className:` + ${t&&!l?"border-blue-500 bg-blue-50 dark:bg-blue-900/20":"border-gray-300 dark:border-gray-600"} + `,onDragOver:u=>{u.preventDefault(),d&&r(!0)},onDragLeave:()=>r(!1),onDrop:u=>{u.preventDefault(),r(!1),d&&n(u.dataTransfer.files)},children:[h.jsx("input",{ref:s,type:"file",multiple:!0,accept:o,onChange:u=>u.target.files&&!l&&n(u.target.files),className:"hidden",disabled:l}),!c&&h.jsxs(I,{className:`p-8 text-center transition-colors ${l?"cursor-not-allowed":"cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800/50"}`,onClick:()=>!l&&s.current?.click(),children:[h.jsx(mt,{className:"fa-solid fa-cloud-arrow-up text-4xl text-gray-400 dark:text-gray-500 mb-4"}),h.jsx(yt,{className:"text-sm text-gray-600 dark:text-gray-400",children:It("attachment.drop_or_click")}),h.jsxs(yt,{className:"text-xs text-gray-500 dark:text-gray-500 mt-2",children:[It("attachment.upload_limit",{maxFiles:i,maxSize:a}),o&&` (${o})`]})]}),c]})},gE=window.G7Core,pE=window.G7Core?.createLogger?.("Comp:SortableThumbnail")??{log:(...t)=>console.log("[Comp:SortableThumbnail]",...t),warn:(...t)=>console.warn("[Comp:SortableThumbnail]",...t),error:(...t)=>console.error("[Comp:SortableThumbnail]",...t)},mE=({file:t,onRemove:e,onRetry:n,onImageClick:r,onDownload:s,isPrimary:o,onPrimaryClick:i,disabled:a})=>{const{attributes:l,listeners:c,setNodeRef:d,transform:u,transition:f,isDragging:g}=Ii({id:"hash"in t?t.hash:t.id}),m={transform:Pn.Transform.toString(u),transition:f,opacity:g?.5:1},p="status"in t&&t.status!==void 0,y=p?t.status:"done",b=y==="uploading",_=y==="compressing",x=y==="error",E=p?t.progress:100,C=p?t.preview:void 0,k=p?t.mime_type?.startsWith("image/")??!1:t.is_image,[N,S]=v.useState(void 0),w=!p&&k?t.download_url:void 0;v.useEffect(()=>{if(!w){S(void 0);return}let T=!0,P;return(async()=>{try{const $=await gE.api.get(w,{responseType:"blob"});T&&$&&(P=URL.createObjectURL($),S(P))}catch($){pE.error("Failed to load authenticated image:",$)}})(),()=>{T=!1,P&&URL.revokeObjectURL(P)}},[w]);const M=N;return h.jsxs(I,{ref:d,style:m,className:"relative group bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden",children:[h.jsxs(I,{className:`aspect-square w-full flex items-center justify-center bg-gray-50 dark:bg-gray-900 relative ${k||!p?"cursor-pointer":""}`,onClick:()=>{b||_||(k&&r?r():!p&&s&&s())},children:[C||M?h.jsx(yn,{src:C||M,alt:t.original_filename,className:"w-full h-full object-cover"}):h.jsx(mt,{className:`fa-solid ${uE(t.mime_type)} text-4xl text-gray-400 dark:text-gray-500`}),(b||_)&&h.jsxs(I,{className:"absolute inset-0 bg-black/50 flex flex-col items-center justify-center",children:[h.jsx(I,{className:"w-3/4 h-2 bg-gray-700 rounded-full overflow-hidden",children:h.jsx(I,{className:"h-full bg-blue-500 transition-all",style:{width:`${E}%`}})}),h.jsxs(Q,{className:"text-white text-xs mt-1",children:[E,"%"]})]}),x&&h.jsx(I,{className:"absolute inset-0 bg-red-500/50 flex items-center justify-center",children:h.jsx(mt,{className:"fa-solid fa-exclamation-triangle text-2xl text-white"})}),i&&h.jsx(oe,{type:"button",onClick:T=>{T.stopPropagation(),i()},className:`absolute top-1.5 left-1.5 z-10 p-1 rounded-full transition-colors ${o?"bg-yellow-500 text-white":"bg-gray-800/50 text-gray-300 hover:bg-yellow-500 hover:text-white opacity-0 group-hover:opacity-100"}`,title:It(o?"attachment.primary_image":"attachment.set_as_primary"),children:h.jsx(mt,{className:"fa-solid fa-star text-xs"})}),h.jsxs(I,{className:`absolute inset-0 bg-black/0 group-hover:bg-black/40 transition-colors flex items-start justify-end p-1 gap-1 opacity-0 group-hover:opacity-100 ${a?"hidden":""}`,children:[h.jsx(oe,{type:"button",...l,...c,className:"p-1.5 bg-white/90 dark:bg-gray-800/90 rounded text-gray-600 dark:text-gray-300 hover:bg-white cursor-grab active:cursor-grabbing",title:It("attachment.drag_to_reorder"),onClick:T=>T.stopPropagation(),children:h.jsx(mt,{className:"fa-solid fa-grip-vertical text-sm"})}),!p&&s&&h.jsx(oe,{type:"button",onClick:T=>{T.stopPropagation(),s()},className:"p-1.5 bg-white/90 dark:bg-gray-800/90 rounded text-blue-500 hover:bg-white",title:It("common.download"),children:h.jsx(mt,{className:"fa-solid fa-download text-sm"})}),x&&n&&h.jsx(oe,{type:"button",onClick:T=>{T.stopPropagation(),n()},className:"p-1.5 bg-white/90 dark:bg-gray-800/90 rounded text-orange-500 hover:bg-white",title:It("common.retry"),children:h.jsx(mt,{className:"fa-solid fa-arrow-rotate-right text-sm"})}),h.jsx(oe,{type:"button",onClick:T=>{T.stopPropagation(),e()},disabled:b||_,className:"p-1.5 bg-white/90 dark:bg-gray-800/90 rounded text-red-500 hover:bg-white disabled:opacity-50",title:It("common.delete"),children:h.jsx(mt,{className:"fa-solid fa-times text-sm"})})]})]}),h.jsxs(I,{className:`p-2 ${!p&&s?"cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-700":""}`,onClick:()=>{!p&&s&&s()},children:[h.jsx(yt,{className:"text-xs font-medium text-gray-900 dark:text-white truncate",title:t.original_filename,children:t.original_filename}),h.jsx(yt,{className:"text-xs text-gray-500 dark:text-gray-400",children:t.size_formatted})]})]})},bE=({allItems:t,canAddMore:e,totalCount:n,maxFiles:r,maxSize:s,accept:o,onRemove:i,onRetry:a,onImageClick:l,onDownload:c,onDragEnd:d,onAddClick:u,enablePrimarySelection:f,primaryFileId:g,onPrimaryChange:m,disabled:p})=>{const y=jl(Zr(Xs,{activationConstraint:{distance:8}}),Zr(Ks,{coordinateGetter:Li}));return h.jsxs(I,{className:"p-3",children:[h.jsx(ec,{sensors:y,collisionDetection:Wl,onDragEnd:d,children:h.jsx(Di,{items:t.map(b=>"hash"in b?b.hash:b.id),strategy:Ti,children:h.jsxs(I,{className:"grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-3",children:[t.map(b=>{const _="hash"in b,x=_?b.is_image:b.mime_type?.startsWith("image/")??!1;return h.jsx(mE,{file:b,onRemove:()=>i(b),onRetry:"status"in b&&b.status==="error"?()=>a(b):void 0,onImageClick:x?()=>l(b):void 0,onDownload:_?()=>c(b):void 0,isPrimary:f&&_&&(b.hash||b.id)===g,onPrimaryClick:f&&_?()=>m?.(b.hash||b.id):void 0,disabled:p},_?b.hash:b.id)}),e&&!p&&h.jsxs(I,{className:"aspect-square flex flex-col items-center justify-center border-2 border-dashed border-gray-300 dark:border-gray-600 rounded-lg cursor-pointer hover:border-gray-400 dark:hover:border-gray-500 hover:bg-gray-50 dark:hover:bg-gray-800/50 transition-colors",onClick:u,children:[h.jsx(mt,{className:"fa-solid fa-plus text-2xl text-gray-400 dark:text-gray-500 mb-1"}),h.jsx(Q,{className:"text-xs text-gray-500 dark:text-gray-400",children:It("common.add")})]})]})})}),h.jsxs(I,{className:"mt-3 pt-3 border-t border-gray-200 dark:border-gray-700 flex justify-between items-center text-xs text-gray-500 dark:text-gray-400",children:[h.jsx(Q,{children:It("attachment.attached_count",{count:n,max:r})}),h.jsxs(Q,{children:[It("attachment.max_size",{size:s}),o&&` (${o})`]})]})]})},Ep=v.forwardRef(({uploadTriggerEvent:t,attachmentableType:e,attachmentableId:n,collection:r="default",maxFiles:s=10,maxSize:o=10,maxConcurrentUploads:i=3,accept:a,imageCompression:l,roleIds:c=[],autoUpload:d=!0,onFilesChange:u,onUploadComplete:f,onUploadError:g,onRemove:m,onReorder:p,initialFiles:y=[],disabled:b=!1,className:_="",confirmBeforeRemove:x=!1,confirmRemoveTitle:E,confirmRemoveMessage:C,apiEndpoints:k,uploadParams:N,enablePrimarySelection:S=!1,primaryFileId:w,onPrimaryChange:M,id:T,editorAttrs:P},L)=>{const $={upload:k?.upload??"/api/admin/attachments",delete:k?.delete??"/api/admin/attachments/:id",reorder:k?.reorder??"/api/admin/attachments/reorder"},A=fE({attachmentableType:e,attachmentableId:n,collection:r,maxFiles:s,maxSize:o,maxConcurrentUploads:i,accept:a,imageCompression:l,roleIds:c,autoUpload:d,initialFiles:y,endpoints:$,onFilesChange:u,onUploadComplete:f,onUploadError:g,onRemove:m,onReorder:p,uploadTriggerEvent:t,confirmBeforeRemove:x,uploadParams:N});v.useImperativeHandle(L,()=>({uploadAll:A.handleUploadAll,clear:A.clear,getPendingFiles:A.getPendingFiles}));const D=v.useMemo(()=>A.imageFiles.map(z=>{const H=!("hash"in z),j=z,W=z;return H?{src:j.preview||"",filename:j.original_filename,title:j.original_filename,downloadRequiresAuth:!1}:{src:A.authenticatedImageUrls.get(W.id)||W.download_url,downloadUrl:W.download_url,filename:W.original_filename,title:W.original_filename,downloadRequiresAuth:!0}}),[A.imageFiles,A.authenticatedImageUrls]),O=v.useMemo(()=>({existingFiles:A.existingFiles,pendingFiles:A.pendingFiles,isDragOver:A.isDragOver,isDeleting:A.isDeleting,maxFiles:s,maxSize:o,accept:a,endpoints:$,confirmBeforeRemove:x,totalCount:A.totalCount,hasFiles:A.hasFiles,canAddMore:A.canAddMore,allItems:A.allItems,imageFiles:A.imageFiles,handleFiles:A.handleFiles,handleRemove:A.handleRemove,handleRetry:A.handleRetry,handleUploadAll:A.handleUploadAll,handleOpenGallery:A.handleOpenGallery,handleDownload:A.handleDownload,handleDragEnd:A.handleDragEnd,setIsDragOver:A.setIsDragOver,inputRef:A.inputRef,galleryOpen:A.galleryOpen,setGalleryOpen:A.setGalleryOpen,galleryStartIndex:A.galleryStartIndex,galleryKeyRef:A.galleryKeyRef,confirmDialogOpen:A.confirmDialogOpen,setConfirmDialogOpen:A.setConfirmDialogOpen,itemToDelete:A.itemToDelete,executeRemoveAttachment:A.executeRemoveAttachment,authenticatedImageUrls:A.authenticatedImageUrls}),[A,s,o,a,$,x]);return h.jsx(eE,{value:O,children:h.jsxs(I,{id:T,className:_,...P,children:[h.jsx(hE,{isDragOver:A.isDragOver,canAddMore:A.canAddMore,onFiles:A.handleFiles,setIsDragOver:A.setIsDragOver,inputRef:A.inputRef,accept:a,maxFiles:s,maxSize:o,disabled:b,children:A.hasFiles&&h.jsx(bE,{allItems:A.allItems,canAddMore:A.canAddMore,totalCount:A.totalCount,maxFiles:s,maxSize:o,accept:a,onRemove:A.handleRemove,onRetry:A.handleRetry,onImageClick:A.handleOpenGallery,onDownload:A.handleDownload,onDragEnd:A.handleDragEnd,onAddClick:()=>A.inputRef.current?.click(),enablePrimarySelection:S,primaryFileId:w,onPrimaryChange:M,disabled:b})}),x&&h.jsx(ru,{isOpen:A.confirmDialogOpen,onClose:()=>{A.isDeleting||A.setConfirmDialogOpen(!1)},title:E??It("attachment.delete_confirm_title"),message:C??It("attachment.delete_confirm_message"),confirmText:It("common.delete"),confirmButtonVariant:"danger",onConfirm:()=>{A.itemToDelete&&A.executeRemoveAttachment(A.itemToDelete)},isLoading:A.isDeleting,loadingText:It("attachment.deleting")}),D.length>0&&A.galleryOpen&&h.jsx(ji,{images:D,isOpen:!0,onClose:()=>A.setGalleryOpen(!1),startIndex:A.galleryStartIndex,showDownload:!0},`gallery-${A.galleryKeyRef.current}`)]})})});Ep.displayName="FileUploader";const yE=({name:t,value:e,options:n,onChange:r,disabled:s=!1,inline:o=!1,className:i="",size:a="md",label:l,error:c,id:d,editorAttrs:u})=>{const[f,g]=v.useState(()=>e??"");v.useEffect(()=>{g(e??"")},[e]);const m=_=>{const x=_.target.value;g(x),r&&!s&&r(_)},y={sm:{radio:"w-3.5 h-3.5",label:"text-xs",gap:"gap-1.5"},md:{radio:"w-4 h-4",label:"text-sm",gap:"gap-2"},lg:{radio:"w-5 h-5",label:"text-base",gap:"gap-2.5"}}[a],b=o?"flex flex-wrap items-center gap-4":"flex flex-col gap-2";return h.jsxs(I,{className:i,id:d,...u,children:[l&&h.jsx(Q,{className:`block mb-2 font-medium text-gray-700 dark:text-gray-300 ${y.label}`,children:l}),h.jsx(I,{className:b,children:n.map(_=>{const x=String(_.value)===String(f),E=s||_.disabled;return h.jsxs(sn,{className:`inline-flex items-center ${y.gap} ${E?"cursor-not-allowed opacity-50":"cursor-pointer"}`,children:[h.jsx("input",{type:"radio",name:t,value:_.value,checked:x,onChange:m,disabled:E,className:` ${y.radio} text-blue-600 bg-gray-100 dark:bg-gray-700 @@ -233,15 +232,15 @@ self.addEventListener('message', async (e) => { focus:ring-blue-500 dark:focus:ring-blue-600 focus:ring-2 dark:ring-offset-gray-800 - ${M?"cursor-not-allowed":"cursor-pointer"} + ${E?"cursor-not-allowed":"cursor-pointer"} `}),h.jsx(Q,{className:` ${y.label} font-medium text-gray-900 dark:text-gray-300 - `,children:_.label})]},_.value)})}),c&&h.jsx(Q,{className:"mt-1 text-xs text-red-600 dark:text-red-400",children:c})]})},Ss=()=>window.G7Core,sa=(e,t)=>Ss()?.t?.(e,t)??e,zE=()=>Ss()?.locale?.supported?.()??["ko","en"],VE=()=>Ss()?.locale?.current?.()??"ko",Vp=e=>{const t=sa(`common.language_${e}`);return t===`common.language_${e}`?{ko:"한국어",en:"English",ja:"日本語",zh:"中文"}[e]||e.toUpperCase():t},HE=({content:e="",value:t,onChange:n,multilingual:r=!1,isHtml:s=!1,onIsHtmlChange:o,rows:i=15,placeholder:a="",label:l,showHtmlModeToggle:c=!0,contentClassName:d="",purifyConfig:u,className:f="",name:g="content",htmlFieldName:m="content_mode",readOnly:p=!1,id:y,editorAttrs:b})=>{const _=v.useMemo(()=>zE(),[]),x=v.useMemo(()=>VE(),[]),[M,C]=v.useState(x),[k,N]=v.useState(s),[S,w]=v.useState(e),[E,D]=v.useState(t??{}),[P,L]=v.useState(!1);v.useEffect(()=>{N(s)},[s]),v.useEffect(()=>{r||w(e)},[e,r]),v.useEffect(()=>{r&&t&&D(t)},[t,r]);const $=v.useCallback(G=>{const U=G.target.value;if(w(U),n){const W=Ss()?.createChangeEvent?.({value:U,name:g,type:"textarea"})??{target:{name:g,value:U}};n(W)}},[n,g]),A=v.useCallback((G,U)=>{const W={...E,[G]:U};if(D(W),n){const Y=Ss()?.createChangeEvent?.({value:W,name:g,type:"multilingual"})??{target:{name:g,value:W}};n(Y)}},[n,g,E]),O=v.useCallback(G=>{const U=G.target.checked;if(N(U),U||L(!1),o){const W=Ss()?.createChangeEvent?.({checked:U,name:m,type:"checkbox"})??{target:{name:m,checked:U}};o(W)}},[o,m]),T=v.useCallback(()=>{L(G=>!G)},[]),z=r?E[M]??"":S,H=v.useCallback(G=>!!E[G]?.trim(),[E]),j=()=>h.jsx(I,{className:"flex flex-wrap items-center gap-2 mb-3",children:_.map(G=>{const U=G===x,W=M===G;return h.jsxs(ae,{type:"button",className:` + `,children:_.label})]},_.value)})}),c&&h.jsx(Q,{className:"mt-1 text-xs text-red-600 dark:text-red-400",children:c})]})},ls=()=>window.G7Core,Zi=(t,e)=>ls()?.t?.(t,e)??t,vE=()=>ls()?.locale?.supported?.()??["ko","en"],xE=()=>ls()?.locale?.current?.()??"ko",Mp=t=>{const e=Zi(`common.language_${t}`);return e===`common.language_${t}`?{ko:"한국어",en:"English",ja:"日本語",zh:"中文"}[t]||t.toUpperCase():e},wE=({content:t="",value:e,onChange:n,multilingual:r=!1,isHtml:s=!1,onIsHtmlChange:o,rows:i=15,placeholder:a="",label:l,showHtmlModeToggle:c=!0,contentClassName:d="",purifyConfig:u,className:f="",name:g="content",htmlFieldName:m="content_mode",readOnly:p=!1,id:y,editorAttrs:b})=>{const _=v.useMemo(()=>vE(),[]),x=v.useMemo(()=>xE(),[]),[E,C]=v.useState(x),[k,N]=v.useState(s),[S,w]=v.useState(t),[M,T]=v.useState(e??{}),[P,L]=v.useState(!1);v.useEffect(()=>{N(s)},[s]),v.useEffect(()=>{r||w(t)},[t,r]),v.useEffect(()=>{r&&e&&T(e)},[e,r]);const $=v.useCallback(W=>{const U=W.target.value;if(w(U),n){const B=ls()?.createChangeEvent?.({value:U,name:g,type:"textarea"})??{target:{name:g,value:U}};n(B)}},[n,g]),A=v.useCallback((W,U)=>{const B={...M,[W]:U};if(T(B),n){const G=ls()?.createChangeEvent?.({value:B,name:g,type:"multilingual"})??{target:{name:g,value:B}};n(G)}},[n,g,M]),D=v.useCallback(W=>{const U=W.target.checked;if(N(U),U||L(!1),o){const B=ls()?.createChangeEvent?.({checked:U,name:m,type:"checkbox"})??{target:{name:m,checked:U}};o(B)}},[o,m]),O=v.useCallback(()=>{L(W=>!W)},[]),z=r?M[E]??"":S,H=v.useCallback(W=>!!M[W]?.trim(),[M]),j=()=>h.jsx(I,{className:"flex flex-wrap items-center gap-2 mb-3",children:_.map(W=>{const U=W===x,B=E===W;return h.jsxs(oe,{type:"button",className:` inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-semibold transition-all - ${W?U?"bg-blue-500 text-white dark:bg-blue-600 dark:text-white shadow-md scale-105":"bg-gray-500 text-white dark:bg-gray-600 dark:text-white shadow-md scale-105":U?"bg-blue-50 text-blue-600 dark:bg-blue-900/30 dark:text-blue-400 hover:bg-blue-100 dark:hover:bg-blue-900/50":"bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600"} - `,onClick:()=>C(G),children:[h.jsx(oe,{name:"globe",className:"w-3 h-3"}),h.jsx(Q,{children:Vp(G)}),U&&h.jsx(Q,{className:"text-red-500",children:"*"}),H(G)&&!W&&h.jsx(oe,{name:"check",className:"w-3 h-3 text-green-500 dark:text-green-400"})]},G)})});return h.jsxs(I,{className:`space-y-2 ${f}`,id:y,...b,children:[h.jsxs(I,{className:"flex items-center justify-between",children:[l&&h.jsx(ln,{className:"block text-sm font-medium text-gray-500 dark:text-gray-400",children:l}),h.jsxs(I,{className:"flex items-center gap-3",children:[k&&!p&&h.jsx(ae,{type:"button",onClick:T,className:`px-3 py-1.5 text-xs font-bold rounded-lg focus:outline-none focus:ring-2 ${P?"text-gray-700 dark:text-gray-200 bg-gray-200 dark:bg-gray-600 border border-gray-300 dark:border-gray-500 hover:bg-gray-300 dark:hover:bg-gray-500 focus:ring-gray-400 dark:focus:ring-gray-500":"text-blue-600 dark:text-blue-400 bg-white dark:bg-gray-700 border border-blue-300 dark:border-blue-600 hover:bg-blue-50 dark:hover:bg-gray-600 focus:ring-blue-500 dark:focus:ring-blue-600"}`,children:sa(P?"common.preview_off":"common.preview")}),c&&h.jsxs(ln,{className:"flex items-center gap-2 p-2 cursor-pointer",children:[h.jsx(St,{type:"checkbox",name:m,checked:k,onChange:O,disabled:p,className:"w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600"}),h.jsx(I,{className:"text-sm font-medium text-gray-700 dark:text-gray-300",children:sa("common.html_mode")})]})]})]}),r&&j(),!P&&(r?_.map(G=>h.jsx(I,{className:M===G?"block":"hidden",children:h.jsx(ir,{name:`${g}[${G}]`,value:E[G]??"",onChange:U=>A(G,U.target.value),placeholder:`${a} (${Vp(G)})`,rows:i,readOnly:p,className:`block w-full rounded-lg border px-3 py-2 text-sm placeholder-gray-400 dark:placeholder-gray-500 focus:outline-none focus:ring-1 disabled:opacity-50 disabled:cursor-not-allowed ${k?"font-mono bg-white dark:bg-gray-800 border-blue-300 dark:border-blue-600 text-gray-800 dark:text-gray-200 focus:border-blue-500 focus:ring-blue-500":"bg-white dark:bg-gray-800 border-gray-300 dark:border-gray-600 text-gray-900 dark:text-white focus:border-blue-500 focus:ring-blue-500"}`})},G)):h.jsx(ir,{name:g,value:S,onChange:$,placeholder:a,rows:i,readOnly:p,className:`block w-full rounded-lg border px-3 py-2 text-sm placeholder-gray-400 dark:placeholder-gray-500 focus:outline-none focus:ring-1 disabled:opacity-50 disabled:cursor-not-allowed ${k?"font-mono bg-white dark:bg-gray-800 border-blue-300 dark:border-blue-600 text-gray-800 dark:text-gray-200 focus:border-blue-500 focus:ring-blue-500":"bg-white dark:bg-gray-800 border-gray-300 dark:border-gray-600 text-gray-900 dark:text-white focus:border-blue-500 focus:ring-blue-500"}`})),P&&h.jsx(I,{className:"p-4 bg-gray-50 dark:bg-gray-900 rounded-lg border border-gray-200 dark:border-gray-800 min-h-[400px]",children:h.jsx(ol,{content:z,isHtml:!0,className:d||"prose dark:prose-invert max-w-none text-gray-900 dark:text-gray-100",purifyConfig:u})})]})},Hp="g7_dismissed_warnings";function Up(){try{const e=sessionStorage.getItem(Hp);return e?JSON.parse(e):[]}catch{return[]}}function UE(e){try{const t=Up();t.includes(e)||(t.push(e),sessionStorage.setItem(Hp,JSON.stringify(t)))}catch{}}function BE(e){switch(e){case"error":return"error";case"warning":return"warning";default:return"info"}}const jE=({warnings:e,className:t=""})=>{const[n,r]=v.useState([]);v.useEffect(()=>{r(Up())},[]);const s=v.useCallback(i=>{UE(i),r(a=>[...a,i])},[]);if(!e||e.length===0)return null;const o=e.filter(i=>!n.includes(i.id));return o.length===0?null:h.jsx(I,{className:`space-y-2 ${t}`.trim(),children:o.map(i=>h.jsx(wu,{type:BE(i.level),message:i.message,dismissible:!0,onDismiss:()=>s(i.id)},i.id))})},Bp=window.G7Core?.createLogger?.("Comp:FilterVisibility")??{log:(...e)=>console.log("[Comp:FilterVisibility]",...e),warn:(...e)=>console.warn("[Comp:FilterVisibility]",...e),error:(...e)=>console.error("[Comp:FilterVisibility]",...e)},WE=({id:e,visibleFilters:t,defaultFilters:n=[],onFilterVisibilityChange:r})=>{const[s,o]=v.useState(!1),i=v.useRef(!0),a=v.useMemo(()=>{try{const f=window.G7Core?.AuthManager?.getInstance();return f?f.getUser()?.uuid:void 0}catch{return}},[]),l=v.useMemo(()=>`g7_filters_${e}${a!==void 0?`_${a}`:""}`,[e,a]),c=v.useCallback(()=>{try{const f=localStorage.getItem(l);if(f){const g=JSON.parse(f);if(Array.isArray(g))return g}}catch(f){Bp.warn("Failed to load from localStorage:",f)}return null},[l]),d=v.useCallback(f=>{try{localStorage.setItem(l,JSON.stringify(f))}catch(g){Bp.warn("Failed to save to localStorage:",g)}},[l]),u=v.useCallback(()=>{const f=c();return f||(t&&t.length>0?t:n)},[c,t,n]);return v.useEffect(()=>{if(!s){const f=u();r&&f.length>0&&r(f),o(!0)}},[s,r,u]),v.useEffect(()=>{if(s&&t&&t.length>0){if(i.current){i.current=!1;return}d(t)}},[s,t,d]),null},GE=window.G7Core?.createLogger?.("Comp:SlotContainer")??{log:(...e)=>console.log("[Comp:SlotContainer]",...e),warn:(...e)=>console.warn("[Comp:SlotContainer]",...e),error:(...e)=>console.error("[Comp:SlotContainer]",...e)};function YE(e,t){return t&&e&&typeof e.id=="string"&&e.id.length>0?{...e,id:`${e.id}__${t}`}:e}const XE=({slotId:e,className:t="",emptyContent:n,style:r,id:s,children:o})=>{const[i,a]=v.useState([]),[,l]=v.useState({}),c=v.useCallback(()=>{const m=window.__slotContextValue;if(m)return m;const p=window.G7Core;return p&&p.getSlotContext?.()||null},[]),d=v.useCallback(()=>{const m=c();m&&m.isEnabled&&a(m.getSlotComponents(e))},[e,c]);v.useEffect(()=>{const m=c();return!m||!m.isEnabled?void 0:(d(),m.subscribeToSlot(e,()=>{d(),l({})}))},[e,c,d]);const u=v.useCallback(()=>{const m=window.G7Core;return m?m.getDynamicRenderer?.():null},[]);if(i.length===0)return n?h.jsx(I,{className:t,style:r,id:s,children:n}):o?h.jsx(I,{className:t,style:r,id:s,children:o}):null;const f=u(),g=window.G7Core;return!f||!g?(GE.error("G7Core or DynamicRenderer not available"),null):h.jsxs(I,{className:t,style:r,id:s,children:[i.map(m=>{const{componentDef:p,dataContext:y,parentFormContext:b,getParentComponentContext:_,translationContext:x}=m,M=YE(p,s);return h.jsx(f,{componentDef:M,dataContext:y,translationContext:x,registry:g.getComponentRegistry(),bindingEngine:g.getDataBindingEngine(),translationEngine:g.getTranslationEngine(),actionDispatcher:g.getActionDispatcher(),parentComponentContext:_?.(),parentFormContextProp:b,isRootRenderer:!1},M.id||`slot-${e}-${m.order}`)}),o]})},jp=(e,t)=>window.G7Core?.t?.(e,t)??e,Wp=(e,t,n)=>window.G7Core?.renderComponentLayout?.(e,t,n)??null,KE=({options:e=[],value:t,onChange:n,placeholder:r,disabled:s=!1,className:o="",maxHeight:i="320px",children:a,selectedChildren:l,__componentLayoutDefs:c,id:d,editorAttrs:u})=>{const[f,g]=v.useState(!1),m=v.useRef(null),p=v.useRef(null),y=v.useMemo(()=>t==null?null:e.find(k=>String(k.value)===String(t))||null,[e,t]);v.useEffect(()=>{const k=N=>{m.current&&!m.current.contains(N.target)&&g(!1)};return f&&document.addEventListener("mousedown",k),()=>{document.removeEventListener("mousedown",k)}},[f]),v.useEffect(()=>{const k=N=>{N.key==="Escape"&&f&&(g(!1),p.current?.focus())};return document.addEventListener("keydown",k),()=>document.removeEventListener("keydown",k)},[f]);const b=v.useCallback(()=>{s||g(k=>!k)},[s]),_=v.useCallback(k=>{n&&n({target:{value:k},preventDefault:()=>{},stopPropagation:()=>{},type:"change"}),g(!1),p.current?.focus()},[n]),x=r??jp("common.select"),M=()=>{if(!y)return h.jsx(Q,{className:"text-gray-400 dark:text-gray-500",children:x});if(c?.selected){const k=Wp(c.selected,{item:y,isSelected:!0},"selected");if(k)return k}return l||h.jsx(Q,{className:"truncate",children:y.label})},C=(k,N)=>h.jsxs(I,{className:"flex items-center justify-between w-full",children:[h.jsx(Q,{className:N?"text-blue-600 dark:text-blue-400 font-medium":"text-gray-900 dark:text-white",children:k.label}),N&&h.jsx(Zn,{className:"w-5 h-5 text-blue-600 dark:text-blue-400",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:h.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M5 13l4 4L19 7"})})]});return h.jsxs(I,{ref:m,className:`relative ${o}`,id:d,...u,children:[h.jsxs(ae,{ref:p,type:"button",onClick:b,disabled:s,className:` + ${B?U?"bg-blue-500 text-white dark:bg-blue-600 dark:text-white shadow-md scale-105":"bg-gray-500 text-white dark:bg-gray-600 dark:text-white shadow-md scale-105":U?"bg-blue-50 text-blue-600 dark:bg-blue-900/30 dark:text-blue-400 hover:bg-blue-100 dark:hover:bg-blue-900/50":"bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600"} + `,onClick:()=>C(W),children:[h.jsx(re,{name:"globe",className:"w-3 h-3"}),h.jsx(Q,{children:Mp(W)}),U&&h.jsx(Q,{className:"text-red-500",children:"*"}),H(W)&&!B&&h.jsx(re,{name:"check",className:"w-3 h-3 text-green-500 dark:text-green-400"})]},W)})});return h.jsxs(I,{className:`space-y-2 ${f}`,id:y,...b,children:[h.jsxs(I,{className:"flex items-center justify-between",children:[l&&h.jsx(sn,{className:"block text-sm font-medium text-gray-500 dark:text-gray-400",children:l}),h.jsxs(I,{className:"flex items-center gap-3",children:[k&&!p&&h.jsx(oe,{type:"button",onClick:O,className:`px-3 py-1.5 text-xs font-bold rounded-lg focus:outline-none focus:ring-2 ${P?"text-gray-700 dark:text-gray-200 bg-gray-200 dark:bg-gray-600 border border-gray-300 dark:border-gray-500 hover:bg-gray-300 dark:hover:bg-gray-500 focus:ring-gray-400 dark:focus:ring-gray-500":"text-blue-600 dark:text-blue-400 bg-white dark:bg-gray-700 border border-blue-300 dark:border-blue-600 hover:bg-blue-50 dark:hover:bg-gray-600 focus:ring-blue-500 dark:focus:ring-blue-600"}`,children:Zi(P?"common.preview_off":"common.preview")}),c&&h.jsxs(sn,{className:"flex items-center gap-2 p-2 cursor-pointer",children:[h.jsx(kt,{type:"checkbox",name:m,checked:k,onChange:D,disabled:p,className:"w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600"}),h.jsx(I,{className:"text-sm font-medium text-gray-700 dark:text-gray-300",children:Zi("common.html_mode")})]})]})]}),r&&j(),!P&&(r?_.map(W=>h.jsx(I,{className:E===W?"block":"hidden",children:h.jsx(Qn,{name:`${g}[${W}]`,value:M[W]??"",onChange:U=>A(W,U.target.value),placeholder:`${a} (${Mp(W)})`,rows:i,readOnly:p,className:`block w-full rounded-lg border px-3 py-2 text-sm placeholder-gray-400 dark:placeholder-gray-500 focus:outline-none focus:ring-1 disabled:opacity-50 disabled:cursor-not-allowed ${k?"font-mono bg-white dark:bg-gray-800 border-blue-300 dark:border-blue-600 text-gray-800 dark:text-gray-200 focus:border-blue-500 focus:ring-blue-500":"bg-white dark:bg-gray-800 border-gray-300 dark:border-gray-600 text-gray-900 dark:text-white focus:border-blue-500 focus:ring-blue-500"}`})},W)):h.jsx(Qn,{name:g,value:S,onChange:$,placeholder:a,rows:i,readOnly:p,className:`block w-full rounded-lg border px-3 py-2 text-sm placeholder-gray-400 dark:placeholder-gray-500 focus:outline-none focus:ring-1 disabled:opacity-50 disabled:cursor-not-allowed ${k?"font-mono bg-white dark:bg-gray-800 border-blue-300 dark:border-blue-600 text-gray-800 dark:text-gray-200 focus:border-blue-500 focus:ring-blue-500":"bg-white dark:bg-gray-800 border-gray-300 dark:border-gray-600 text-gray-900 dark:text-white focus:border-blue-500 focus:ring-blue-500"}`})),P&&h.jsx(I,{className:"p-4 bg-gray-50 dark:bg-gray-900 rounded-lg border border-gray-200 dark:border-gray-800 min-h-[400px]",children:h.jsx(Za,{content:z,isHtml:!0,className:d||"prose dark:prose-invert max-w-none text-gray-900 dark:text-gray-100",purifyConfig:u})})]})},Np="g7_dismissed_warnings";function Op(){try{const t=sessionStorage.getItem(Np);return t?JSON.parse(t):[]}catch{return[]}}function kE(t){try{const e=Op();e.includes(t)||(e.push(t),sessionStorage.setItem(Np,JSON.stringify(e)))}catch{}}function CE(t){switch(t){case"error":return"error";case"warning":return"warning";default:return"info"}}const SE=({warnings:t,className:e=""})=>{const[n,r]=v.useState([]);v.useEffect(()=>{r(Op())},[]);const s=v.useCallback(i=>{kE(i),r(a=>[...a,i])},[]);if(!t||t.length===0)return null;const o=t.filter(i=>!n.includes(i.id));return o.length===0?null:h.jsx(I,{className:`space-y-2 ${e}`.trim(),children:o.map(i=>h.jsx(cu,{type:CE(i.level),message:i.message,dismissible:!0,onDismiss:()=>s(i.id)},i.id))})},Tp=window.G7Core?.createLogger?.("Comp:FilterVisibility")??{log:(...t)=>console.log("[Comp:FilterVisibility]",...t),warn:(...t)=>console.warn("[Comp:FilterVisibility]",...t),error:(...t)=>console.error("[Comp:FilterVisibility]",...t)},_E=({id:t,visibleFilters:e,defaultFilters:n=[],onFilterVisibilityChange:r})=>{const[s,o]=v.useState(!1),i=v.useRef(!0),a=v.useMemo(()=>{try{const f=window.G7Core?.AuthManager?.getInstance();return f?f.getUser()?.uuid:void 0}catch{return}},[]),l=v.useMemo(()=>`g7_filters_${t}${a!==void 0?`_${a}`:""}`,[t,a]),c=v.useCallback(()=>{try{const f=localStorage.getItem(l);if(f){const g=JSON.parse(f);if(Array.isArray(g))return g}}catch(f){Tp.warn("Failed to load from localStorage:",f)}return null},[l]),d=v.useCallback(f=>{try{localStorage.setItem(l,JSON.stringify(f))}catch(g){Tp.warn("Failed to save to localStorage:",g)}},[l]),u=v.useCallback(()=>{const f=c();return f||(e&&e.length>0?e:n)},[c,e,n]);return v.useEffect(()=>{if(!s){const f=u();r&&f.length>0&&r(f),o(!0)}},[s,r,u]),v.useEffect(()=>{if(s&&e&&e.length>0){if(i.current){i.current=!1;return}d(e)}},[s,e,d]),null},EE=window.G7Core?.createLogger?.("Comp:SlotContainer")??{log:(...t)=>console.log("[Comp:SlotContainer]",...t),warn:(...t)=>console.warn("[Comp:SlotContainer]",...t),error:(...t)=>console.error("[Comp:SlotContainer]",...t)};function ME(t,e){return e&&t&&typeof t.id=="string"&&t.id.length>0?{...t,id:`${t.id}__${e}`}:t}const NE=({slotId:t,className:e="",emptyContent:n,style:r,id:s,children:o})=>{const[i,a]=v.useState([]),[,l]=v.useState({}),c=v.useCallback(()=>{const m=window.__slotContextValue;if(m)return m;const p=window.G7Core;return p&&p.getSlotContext?.()||null},[]),d=v.useCallback(()=>{const m=c();m&&m.isEnabled&&a(m.getSlotComponents(t))},[t,c]);v.useEffect(()=>{const m=c();return!m||!m.isEnabled?void 0:(d(),m.subscribeToSlot(t,()=>{d(),l({})}))},[t,c,d]);const u=v.useCallback(()=>{const m=window.G7Core;return m?m.getDynamicRenderer?.():null},[]);if(i.length===0)return n?h.jsx(I,{className:e,style:r,id:s,children:n}):o?h.jsx(I,{className:e,style:r,id:s,children:o}):null;const f=u(),g=window.G7Core;return!f||!g?(EE.error("G7Core or DynamicRenderer not available"),null):h.jsxs(I,{className:e,style:r,id:s,children:[i.map(m=>{const{componentDef:p,dataContext:y,parentFormContext:b,getParentComponentContext:_,translationContext:x}=m,E=ME(p,s);return h.jsx(f,{componentDef:E,dataContext:y,translationContext:x,registry:g.getComponentRegistry(),bindingEngine:g.getDataBindingEngine(),translationEngine:g.getTranslationEngine(),actionDispatcher:g.getActionDispatcher(),parentComponentContext:_?.(),parentFormContextProp:b,isRootRenderer:!1},E.id||`slot-${t}-${m.order}`)}),o]})},Ap=(t,e)=>window.G7Core?.t?.(t,e)??t,Dp=(t,e,n)=>window.G7Core?.renderComponentLayout?.(t,e,n)??null,OE=({options:t=[],value:e,onChange:n,placeholder:r,disabled:s=!1,className:o="",maxHeight:i="320px",children:a,selectedChildren:l,__componentLayoutDefs:c,id:d,editorAttrs:u})=>{const[f,g]=v.useState(!1),m=v.useRef(null),p=v.useRef(null),y=v.useMemo(()=>e==null?null:t.find(k=>String(k.value)===String(e))||null,[t,e]);v.useEffect(()=>{const k=N=>{m.current&&!m.current.contains(N.target)&&g(!1)};return f&&document.addEventListener("mousedown",k),()=>{document.removeEventListener("mousedown",k)}},[f]),v.useEffect(()=>{const k=N=>{N.key==="Escape"&&f&&(g(!1),p.current?.focus())};return document.addEventListener("keydown",k),()=>document.removeEventListener("keydown",k)},[f]);const b=v.useCallback(()=>{s||g(k=>!k)},[s]),_=v.useCallback(k=>{n&&n({target:{value:k},preventDefault:()=>{},stopPropagation:()=>{},type:"change"}),g(!1),p.current?.focus()},[n]),x=r??Ap("common.select"),E=()=>{if(!y)return h.jsx(Q,{className:"text-gray-400 dark:text-gray-500",children:x});if(c?.selected){const k=Dp(c.selected,{item:y,isSelected:!0},"selected");if(k)return k}return l||h.jsx(Q,{className:"truncate",children:y.label})},C=(k,N)=>h.jsxs(I,{className:"flex items-center justify-between w-full",children:[h.jsx(Q,{className:N?"text-blue-600 dark:text-blue-400 font-medium":"text-gray-900 dark:text-white",children:k.label}),N&&h.jsx(Un,{className:"w-5 h-5 text-blue-600 dark:text-blue-400",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:h.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M5 13l4 4L19 7"})})]});return h.jsxs(I,{ref:m,className:`relative ${o}`,id:d,...u,children:[h.jsxs(oe,{ref:p,type:"button",onClick:b,disabled:s,className:` w-full px-4 py-2.5 bg-gray-100 dark:bg-gray-700 border-0 rounded-xl @@ -250,13 +249,13 @@ self.addEventListener('message', async (e) => { flex items-center justify-between gap-2 text-left cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed - `,"aria-haspopup":"listbox","aria-expanded":f,children:[h.jsx(I,{className:"flex-1 min-w-0",children:M()}),h.jsx(Zn,{className:`w-4 h-4 flex-shrink-0 transition-transform duration-200 ${f?"rotate-180":""}`,fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:h.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M19 9l-7 7-7-7"})})]}),f&&h.jsx(I,{className:"absolute z-50 w-full mt-2 bg-white dark:bg-gray-800 rounded-2xl shadow-lg border border-gray-200 dark:border-gray-600 overflow-hidden",role:"listbox",children:h.jsx(I,{className:"overflow-auto",style:{maxHeight:i},children:e.length===0?h.jsx(I,{className:"px-4 py-6 text-center text-gray-500 dark:text-gray-400",children:jp("common.no_options")}):e.map((k,N)=>{const S=String(k.value)===String(t);return h.jsx(ae,{type:"button",onClick:()=>!k.disabled&&_(k.value),disabled:k.disabled,className:` + `,"aria-haspopup":"listbox","aria-expanded":f,children:[h.jsx(I,{className:"flex-1 min-w-0",children:E()}),h.jsx(Un,{className:`w-4 h-4 flex-shrink-0 transition-transform duration-200 ${f?"rotate-180":""}`,fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:h.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M19 9l-7 7-7-7"})})]}),f&&h.jsx(I,{className:"absolute z-50 w-full mt-2 bg-white dark:bg-gray-800 rounded-2xl shadow-lg border border-gray-200 dark:border-gray-600 overflow-hidden",role:"listbox",children:h.jsx(I,{className:"overflow-auto",style:{maxHeight:i},children:t.length===0?h.jsx(I,{className:"px-4 py-6 text-center text-gray-500 dark:text-gray-400",children:Ap("common.no_options")}):t.map((k,N)=>{const S=String(k.value)===String(e);return h.jsx(oe,{type:"button",onClick:()=>!k.disabled&&_(k.value),disabled:k.disabled,className:` w-full px-4 py-3 text-left text-sm hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors ${S?"bg-blue-50 dark:bg-blue-900/20":""} ${k.disabled?"opacity-50 cursor-not-allowed":"cursor-pointer"} - `,role:"option","aria-selected":S,"data-item-value":k.value,"data-item-index":N,"data-item-selected":S,children:c?.item?Wp(c.item,{item:k,index:N,isSelected:S},`item-${N}`):a||C(k,S)},k.value)})})})]})};function qE(e){const t=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(e);return t?{r:parseInt(t[1],16),g:parseInt(t[2],16),b:parseInt(t[3],16)}:null}const ZE=({value:e,checked:t=!1,icon:n,label:r,variant:s="blue",customColor:o,className:i="",style:a,onChange:l,id:c,editorAttrs:d})=>{const[u,f]=v.useState(!1),g="inline-flex items-center gap-2 px-3 py-1.5 rounded-lg text-sm font-medium border cursor-pointer transition-all";if(o){const b=qE(o),_=document.documentElement.classList.contains("dark");let x;return t?x={backgroundColor:o,color:"#ffffff",borderColor:o,opacity:u?.85:1}:x=b?{backgroundColor:`rgba(${b.r}, ${b.g}, ${b.b}, ${u?_?.25:.12:_?.15:.06})`,color:o,borderColor:`rgba(${b.r}, ${b.g}, ${b.b}, ${_?.3:.2})`}:{},h.jsxs(ln,{className:`${g} ${i}`,style:{...x,...a},onMouseEnter:()=>f(!0),onMouseLeave:()=>f(!1),id:c,...d,children:[h.jsx(St,{type:"checkbox",value:e,checked:t,onChange:M=>{l?.(M)},className:"hidden"}),n&&h.jsx(oe,{name:n,className:"w-4 h-4"}),h.jsx(I,{children:r})]})}const y=t?{green:"bg-green-600 dark:bg-green-600 text-white dark:text-white border-green-700 dark:border-green-500 hover:bg-green-700 dark:hover:bg-green-500",orange:"bg-orange-600 dark:bg-orange-600 text-white dark:text-white border-orange-700 dark:border-orange-500 hover:bg-orange-700 dark:hover:bg-orange-500",yellow:"bg-yellow-600 dark:bg-yellow-600 text-white dark:text-white border-yellow-700 dark:border-yellow-500 hover:bg-yellow-700 dark:hover:bg-yellow-500",gray:"bg-gray-600 dark:bg-gray-500 text-white dark:text-white border-gray-700 dark:border-gray-400 hover:bg-gray-700 dark:hover:bg-gray-400",blue:"bg-blue-600 dark:bg-blue-600 text-white dark:text-white border-blue-700 dark:border-blue-500 hover:bg-blue-700 dark:hover:bg-blue-500"}[s]:{green:"bg-green-50 dark:bg-green-900/20 text-green-600 dark:text-green-400 border-green-200 dark:border-green-800 hover:bg-green-100 dark:hover:bg-green-900/30",orange:"bg-orange-50 dark:bg-orange-900/20 text-orange-600 dark:text-orange-400 border-orange-200 dark:border-orange-800 hover:bg-orange-100 dark:hover:bg-orange-900/30",yellow:"bg-yellow-50 dark:bg-yellow-900/20 text-yellow-600 dark:text-yellow-400 border-yellow-200 dark:border-yellow-800 hover:bg-yellow-100 dark:hover:bg-yellow-900/30",gray:"bg-gray-50 dark:bg-gray-700 text-gray-600 dark:text-gray-300 border-gray-200 dark:border-gray-600 hover:bg-gray-100 dark:hover:bg-gray-600",blue:"bg-blue-50 dark:bg-blue-900/20 text-blue-600 dark:text-blue-400 border-blue-200 dark:border-blue-800 hover:bg-blue-100 dark:hover:bg-blue-900/30"}[s];return h.jsxs(ln,{className:`${g} ${y} ${i}`,style:a,id:c,...d,children:[h.jsx(St,{type:"checkbox",value:e,checked:t,onChange:b=>{l?.(b)},className:"hidden"}),n&&h.jsx(oe,{name:n,className:"w-4 h-4"}),h.jsx(I,{children:r})]})},QE=({label:e,icon:t,iconPosition:n="left",position:r="right",className:s="",style:o,children:i})=>{const[a,l]=v.useState(!1),[c,d]=v.useState({top:0,left:0}),u=v.useRef(null),f=v.useRef(null),g=v.useCallback(()=>{if(!u.current)return;const p=u.current.getBoundingClientRect(),y=224;let b=r==="right"?p.right-y:p.left;const _=p.bottom+8;b<8&&(b=8),b+y>window.innerWidth-8&&(b=window.innerWidth-y-8),d({top:_,left:b})},[r]);v.useEffect(()=>{a&&g()},[a,g]),v.useEffect(()=>{if(!a)return;const p=()=>{l(!1)};return window.addEventListener("scroll",p,!0),window.addEventListener("resize",p),()=>{window.removeEventListener("scroll",p,!0),window.removeEventListener("resize",p)}},[a]),v.useEffect(()=>{const p=y=>{const b=y.target,_=u.current?.contains(b),x=f.current?.contains(b);!_&&!x&&l(!1)};return a&&document.addEventListener("mousedown",p),()=>{document.removeEventListener("mousedown",p)}},[a]);const m=a?$n.createPortal(h.jsx("div",{ref:f,className:"fixed z-[9999] w-56 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg overflow-hidden",style:{top:c.top,left:c.left},onClick:()=>l(!1),children:i}),document.body):null;return h.jsxs(I,{className:`relative inline-block ${s}`,style:o,children:[h.jsxs(ae,{ref:u,onClick:()=>l(!a),className:"flex items-center gap-2",children:[n==="left"&&t&&h.jsx(oe,{name:t,className:"w-4 h-4"}),e&&h.jsx(Q,{className:"text-sm font-medium",children:e}),n==="right"&&t&&h.jsx(oe,{name:t,className:"w-4 h-4"})]}),m]})},JE=({label:e,icon:t,variant:n="default",disabled:r=!1,divider:s=!1,className:o="",style:i,onClick:a})=>{if(s)return h.jsx(I,{className:"h-px bg-gray-200 dark:bg-gray-700 my-1",style:i});const l=n==="danger"?"text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/20":"text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700",c=r?"opacity-50 cursor-not-allowed":"cursor-pointer",d=()=>{!r&&a&&a()};return h.jsxs(I,{onClick:d,className:`flex items-center gap-3 px-4 py-3 transition-colors ${l} ${c} ${o}`,style:i,children:[t&&h.jsx(oe,{name:t,className:"w-4 h-4"}),e&&h.jsx(Q,{className:"text-sm font-medium",children:e})]})},RE=()=>window.G7Core,Bc=v.createContext(null),Gp=()=>{const e=v.useContext(Bc);if(!e){const t=RE(),n=t?.locale?.current?.()||"ko",r=t?.locale?.supported?.()||["ko","en"];return{activeLocale:n,getActiveLocale:()=>t?.locale?.current?.()||"ko",supportedLocales:r,isInsideMultilingualTabPanel:!1}}return{...e,isInsideMultilingualTabPanel:!0}},eM=()=>window.G7Core,tM=({value:e,onChange:t,placeholder:n,readOnly:r,disabled:s,hasError:o})=>{const{activeLocale:i,getActiveLocale:a}=Gp(),l=typeof e=="string"?{[i]:e}:e||{},c=v.useCallback(u=>{const f=a();t({...l,[f]:u.target.value})},[l,a,t]),d=o?"border-red-500 dark:border-red-500 focus:ring-red-500 focus:border-red-500":"border-gray-300 dark:border-gray-600 focus:ring-blue-500 focus:border-blue-500";return h.jsx(St,{type:"text",value:l[i]||"",onChange:c,placeholder:n,readOnly:r,disabled:s,className:`w-full px-3 py-2 text-sm border ${d} rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2`})},nM=(e,t)=>{if(!t)return!1;const n=t.startsWith("!"),r=n?t.slice(1):t,s=!!e[r];return n?!s:s},rM=({column:e,item:t,index:n,onValueChange:r,readOnly:s,error:o})=>{const i=t[e.key],a=s||e.readOnly||nM(t,e.readOnlyCondition)||e.disabled,l=!!o,c=l?"border-red-500 dark:border-red-500 focus:ring-red-500 focus:border-red-500":"border-gray-300 dark:border-gray-600 focus:ring-blue-500 focus:border-blue-500",d=v.useCallback(f=>{r(e.key,f)},[e.key,r]),u=f=>h.jsxs(I,{className:"w-full",children:[f,l&&h.jsx(Q,{className:"text-xs text-red-500 dark:text-red-400 mt-1 block",children:o})]});switch(e.type){case"input":return u(h.jsx(St,{type:"text",value:i||"",onChange:f=>d(f.target.value),placeholder:e.placeholder,readOnly:a,disabled:a,className:`w-full px-3 py-2 text-sm border ${c} rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 ${e.className||""}`}));case"number":return u(h.jsx(St,{type:"number",value:i??"",onChange:f=>d(f.target.value?Number(f.target.value):null),placeholder:e.placeholder,min:e.min,max:e.max,step:e.step,readOnly:a,disabled:a,className:`w-full px-3 py-2 text-sm border ${c} rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 ${e.className||""}`}));case"select":return u(h.jsxs(Xr,{value:i??"",onChange:f=>d(f.target.value),disabled:a,className:`w-full px-3 py-2 text-sm border ${c} rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 ${e.className||""}`,children:[h.jsx("option",{value:"",children:"선택하세요"}),e.options?.map(f=>h.jsx("option",{value:f.value,disabled:f.disabled,children:f.label},f.value))]}));case"textarea":return u(h.jsx(ir,{value:i||"",onChange:f=>d(f.target.value),placeholder:e.placeholder,readOnly:a,disabled:a,className:`w-full px-3 py-2 text-sm border ${c} rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 resize-none ${e.className||""}`,rows:2}));case"multilingual":return u(h.jsx(tM,{value:i,onChange:f=>d(f),placeholder:e.placeholder,readOnly:a,disabled:a,hasError:l}));case"custom":return e.render?h.jsx(h.Fragment,{children:e.render(t,n,d)}):h.jsx(Q,{className:"text-gray-500 dark:text-gray-400",children:"-"});default:return h.jsx(Q,{className:"text-gray-900 dark:text-white text-sm",children:String(i??"")})}},sM=(e,t,n)=>{if(!e)return;if(n==="multilingual"){const o=eM()?.locale?.current?.()||"ko",i=`${t}.${o}`;return e[i]?.[0]}return e[t]?.[0]},oM=({item:e,index:t,id:n,columns:r,enableDrag:s=!0,showIndex:o=!0,canRemove:i=!0,rowActions:a,readOnly:l=!1,onValueChange:c,onRemove:d,className:u="",level:f=0,errors:g})=>{const{attributes:m,listeners:p,setNodeRef:y,transform:b,transition:_,isDragging:x}=Ui({id:n,disabled:!s||l}),M={transform:jn.Transform.toString(b),transition:_,opacity:x?.5:1},C=v.useCallback((S,w)=>{c?.(S,w)},[c]),k=v.useCallback(S=>{S.onClick?.(e,t)},[e,t]),N=f>0?{paddingLeft:`${f*24}px`}:{};return h.jsxs(I,{ref:y,style:M,className:`flex items-center gap-2 py-2 px-2 border-b border-gray-100 dark:border-gray-700 last:border-b-0 hover:bg-gray-50 dark:hover:bg-gray-800/50 transition-colors ${x?"bg-blue-50 dark:bg-blue-900/20 shadow-lg rounded-lg z-10":""} ${u}`,children:[s&&!l&&h.jsx(I,{...m,...p,className:"flex-shrink-0 w-6 h-6 flex items-center justify-center cursor-grab active:cursor-grabbing text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-400",style:N,children:h.jsx(oe,{name:"grip-vertical",className:"w-4 h-4"})}),o&&h.jsx(I,{className:"flex-shrink-0 w-8 text-center text-sm text-gray-500 dark:text-gray-400 font-medium",children:t+1}),r.map(S=>{const w=sM(g,S.key,S.type);return h.jsx(I,{className:`flex-1 min-w-0 ${S.width||""}`,style:S.width?{flex:"none",width:S.width}:{},children:h.jsx(rM,{column:S,item:e,index:t,onValueChange:C,readOnly:l,error:w})},S.key)}),a&&a.length>0&&!l&&h.jsx(I,{className:"flex-shrink-0 flex items-center gap-1",children:a.map(S=>{const w=typeof S.disabled=="function"?S.disabled(e,t):S.disabled;return h.jsx(ae,{type:"button",onClick:()=>k(S),disabled:w,className:`p-1.5 rounded transition-colors ${S.danger?"text-red-500 hover:bg-red-50 dark:hover:bg-red-900/20 hover:text-red-600":"text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-gray-600 dark:hover:text-gray-300"} disabled:opacity-50 disabled:cursor-not-allowed`,title:S.label,children:S.icon?h.jsx(oe,{name:S.icon,className:"w-4 h-4"}):h.jsx(Q,{className:"text-xs",children:S.label})},S.key)})}),i&&!a&&!l&&h.jsx(I,{className:"flex-shrink-0",children:h.jsx(ae,{type:"button",onClick:d,className:"p-1.5 text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-gray-600 dark:hover:text-gray-300 rounded transition-colors",title:"삭제",children:h.jsx(oe,{name:"minus",className:"w-4 h-4"})})})]})},Yp=()=>window.G7Core,jc=()=>`item_${Date.now()}_${Math.random().toString(36).substring(2,9)}`,iM=e=>{const t={_id:jc()};return e.forEach(n=>{if(n.type==="multilingual"){const s=Yp()?.locale?.all?.()||["ko","en"],o={};s.forEach(i=>{o[i]=""}),t[n.key]=o}else n.type==="number"?t[n.key]=null:(n.type,t[n.key]="")}),t},aM=(e,t)=>{if(!e)return{};const n=`fields.${t}.`,r={};return Object.keys(e).forEach(s=>{if(s.startsWith(n)){const o=s.substring(n.length);r[o]=e[s]}}),r},lM=({items:e=[],columns:t,onChange:n,onAddItem:r,onRemoveItem:s,onReorder:o,addLabel:i="항목 추가",enableDrag:a=!0,showIndex:l=!0,minItems:c=0,maxItems:d,rowActions:u,emptyMessage:f="항목이 없습니다.",className:g="",headerClassName:m="",rowClassName:p="",name:y,readOnly:b=!1,createDefaultItem:_,childrenKey:x,itemIdKey:M="_id",errors:C,id:k,editorAttrs:N})=>{const S=v.useRef(null),w=v.useRef([]),E=v.useRef(null),D=v.useMemo(()=>{let G=!1;for(const U of e)if(U[M]==null){G=!0;break}return G?e.map(U=>U[M]!=null?U:{...U,[M]:jc()}):e},[e,M]);w.current=D,v.useEffect(()=>{E.current=null},[D]);const P=v.useCallback(G=>String(G[M]??""),[M]);v.useEffect(()=>{if(y){const G=Yp();G?.form&&(S.current=G.form)}},[y]);const L=Zl(fs(io,{activationConstraint:{distance:8}}),fs(oo,{coordinateGetter:Bi})),$=v.useMemo(()=>D.map(G=>P(G)),[D,P]),A=v.useCallback(G=>{const{active:U,over:W}=G;if(W&&U.id!==W.id){const Y=E.current??w.current,V=Y.map(q=>String(q[M]??"")),X=V.indexOf(String(U.id)),K=V.indexOf(String(W.id));if(X!==-1&&K!==-1){const q=gs([...Y],X,K);E.current=q,n?.(q),o?.(q)}}},[n,o,M]),O=v.useCallback((G,U,W)=>{const V=[...E.current??w.current];V[G]={...V[G],[U]:W},E.current=V,n?.(V)},[n]),T=v.useCallback(()=>{const G=E.current??w.current;if(d&&G.length>=d)return;if(r){r();return}const U=_?_():iM(t);U[M]||(U[M]=jc());const W=[...G,U];E.current=W,n?.(W)},[d,r,_,t,M,n]),z=v.useCallback(G=>{const U=E.current??w.current;if(U.length<=c)return;const W=U[G];if(s){s(G,W);return}const Y=U.filter((V,X)=>X!==G);E.current=Y,n?.(Y)},[c,s,n]),H=D.length>c,j=!d||D.lengthh.jsxs(I,{className:`flex-1 min-w-0 text-xs font-medium text-gray-500 dark:text-gray-400 uppercase ${G.width||""}`,style:G.width?{flex:"none",width:G.width}:{},children:[G.label,G.required&&h.jsx(Q,{className:"text-red-500 dark:text-red-400 ml-0.5",children:"*"})]},G.key)),(u||!b&&H)&&h.jsx(I,{className:"flex-shrink-0 w-8"})]}),D.length>0?h.jsx(ac,{sensors:L,collisionDetection:Ql,onDragEnd:A,children:h.jsx(Hi,{items:$,strategy:lc,children:h.jsx(I,{className:"border-x border-gray-200 dark:border-gray-700",children:D.map((G,U)=>{const W=P(G),Y=aM(C,U);return h.jsx(oM,{id:W,item:G,index:U,columns:t,enableDrag:a,showIndex:l,canRemove:H,rowActions:u,readOnly:b,onValueChange:(V,X)=>O(U,V,X),onRemove:()=>z(U),className:p,errors:Y},W)})})})}):h.jsx(I,{className:"border-x border-gray-200 dark:border-gray-700 py-8 text-center text-gray-500 dark:text-gray-400 text-sm",children:f}),!b&&j&&h.jsx(I,{className:"relative border border-gray-200 dark:border-gray-700 border-t-0 rounded-b-lg",children:h.jsx(I,{className:"flex items-center justify-center py-2",children:h.jsxs(ae,{type:"button",onClick:T,className:"flex items-center gap-1.5 px-4 py-1.5 text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors",children:[h.jsx(oe,{name:"plus",className:"w-4 h-4"}),h.jsx(Q,{children:i})]})})}),(b||!j)&&D.length>0&&h.jsx(I,{className:"border-x border-b border-gray-200 dark:border-gray-700 rounded-b-lg h-1"})]})},Xp=()=>window.G7Core,cM=()=>Xp()?.locale?.supported?.()??["ko","en"],dM=()=>Xp()?.locale?.current?.()??"ko",uM={ko:"한국어",en:"English",ja:"日本語",zh:"中文"},fM=({children:e,className:t="",style:n,variant:r="default",defaultLocale:s,onLocaleChange:o,locales:i})=>{const a=v.useMemo(()=>i&&i.length>0?i:cM(),[i]),[l,c]=v.useState(s||dM());v.useEffect(()=>{if(a.length>0&&!a.includes(l)){const p=a[0];c(p),o?.(p)}},[a,l,o]);const d=v.useRef(l);d.current=l;const u=v.useCallback(()=>d.current,[]),f=v.useCallback(p=>{c(p),o?.(p)},[o]),g=v.useMemo(()=>({activeLocale:l,getActiveLocale:u,supportedLocales:a}),[l,u,a]),m=p=>{const y=p===l,b="px-4 py-2 text-sm font-medium transition-all";return r==="pills"?y?`${b} bg-blue-600 text-white rounded-lg`:`${b} text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg`:r==="underline"?y?`${b} text-blue-600 dark:text-blue-400 border-b-2 border-blue-600 dark:border-blue-400`:`${b} text-gray-600 dark:text-gray-400 border-b-2 border-transparent hover:border-gray-300 dark:hover:border-gray-500`:y?`${b} text-blue-600 dark:text-blue-400 bg-blue-50 dark:bg-blue-900/20 border-b-2 border-blue-600`:`${b} text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-700 border-b-2 border-transparent`};return h.jsxs(I,{className:t,style:n,children:[h.jsx(Yo,{className:"flex gap-1 border-b border-gray-200 dark:border-gray-700 mb-4",children:a.map(p=>h.jsx(ae,{type:"button",onClick:()=>f(p),className:m(p),children:h.jsx(Q,{children:uM[p]||p})},p))}),h.jsx(Bc.Provider,{value:g,children:e})]})},hM=({label:e,required:t=!1,error:n,helperText:r,className:s="",labelClassName:o="",children:i,horizontal:a=!1,labelWidth:l="w-1/3",id:c,editorAttrs:d})=>{const u=a?`flex items-start gap-4 ${s}`:`space-y-1 ${s}`,f=a?`${l} flex-shrink-0 pt-2`:"",g=a?"flex-1":"";return h.jsxs(I,{className:u,id:c,...d,children:[e&&h.jsx(I,{className:f,children:h.jsxs(ln,{className:`block text-sm font-medium text-gray-700 dark:text-gray-300 ${o}`,children:[e,t&&h.jsx("span",{className:"ml-1 text-red-500 dark:text-red-400",children:"*"})]})}),h.jsxs(I,{className:`${g} ${n?"form-field-error":""}`.trim(),children:[i,r&&!n&&h.jsx(I,{className:"mt-1 text-xs text-gray-500 dark:text-gray-400",children:r}),n&&h.jsx(I,{className:"mt-1 text-xs text-red-600 dark:text-red-400",children:n})]})]})};function wo(e){return e+.5|0}const pr=(e,t,n)=>Math.max(Math.min(e,n),t);function ko(e){return pr(wo(e*2.55),0,255)}function mr(e){return pr(wo(e*255),0,255)}function Jn(e){return pr(wo(e/2.55)/100,0,1)}function Kp(e){return pr(wo(e*100),0,100)}const bn={0:0,1:1,2:2,3:3,4:4,5:5,6:6,7:7,8:8,9:9,A:10,B:11,C:12,D:13,E:14,F:15,a:10,b:11,c:12,d:13,e:14,f:15},Wc=[..."0123456789ABCDEF"],gM=e=>Wc[e&15],pM=e=>Wc[(e&240)>>4]+Wc[e&15],oa=e=>(e&240)>>4===(e&15),mM=e=>oa(e.r)&&oa(e.g)&&oa(e.b)&&oa(e.a);function bM(e){var t=e.length,n;return e[0]==="#"&&(t===4||t===5?n={r:255&bn[e[1]]*17,g:255&bn[e[2]]*17,b:255&bn[e[3]]*17,a:t===5?bn[e[4]]*17:255}:(t===7||t===9)&&(n={r:bn[e[1]]<<4|bn[e[2]],g:bn[e[3]]<<4|bn[e[4]],b:bn[e[5]]<<4|bn[e[6]],a:t===9?bn[e[7]]<<4|bn[e[8]]:255})),n}const yM=(e,t)=>e<255?t(e):"";function vM(e){var t=mM(e)?gM:pM;return e?"#"+t(e.r)+t(e.g)+t(e.b)+yM(e.a,t):void 0}const xM=/^(hsla?|hwb|hsv)\(\s*([-+.e\d]+)(?:deg)?[\s,]+([-+.e\d]+)%[\s,]+([-+.e\d]+)%(?:[\s,]+([-+.e\d]+)(%)?)?\s*\)$/;function qp(e,t,n){const r=t*Math.min(n,1-n),s=(o,i=(o+e/30)%12)=>n-r*Math.max(Math.min(i-3,9-i,1),-1);return[s(0),s(8),s(4)]}function wM(e,t,n){const r=(s,o=(s+e/60)%6)=>n-n*t*Math.max(Math.min(o,4-o,1),0);return[r(5),r(3),r(1)]}function kM(e,t,n){const r=qp(e,1,.5);let s;for(t+n>1&&(s=1/(t+n),t*=s,n*=s),s=0;s<3;s++)r[s]*=1-t-n,r[s]+=t;return r}function CM(e,t,n,r,s){return e===s?(t-n)/r+(t.5?d/(2-o-i):d/(o+i),l=CM(n,r,s,d,o),l=l*60+.5),[l|0,c||0,a]}function Yc(e,t,n,r){return(Array.isArray(t)?e(t[0],t[1],t[2]):e(t,n,r)).map(mr)}function Xc(e,t,n){return Yc(qp,e,t,n)}function SM(e,t,n){return Yc(kM,e,t,n)}function _M(e,t,n){return Yc(wM,e,t,n)}function Zp(e){return(e%360+360)%360}function EM(e){const t=xM.exec(e);let n=255,r;if(!t)return;t[5]!==r&&(n=t[6]?ko(+t[5]):mr(+t[5]));const s=Zp(+t[2]),o=+t[3]/100,i=+t[4]/100;return t[1]==="hwb"?r=SM(s,o,i):t[1]==="hsv"?r=_M(s,o,i):r=Xc(s,o,i),{r:r[0],g:r[1],b:r[2],a:n}}function MM(e,t){var n=Gc(e);n[0]=Zp(n[0]+t),n=Xc(n),e.r=n[0],e.g=n[1],e.b=n[2]}function NM(e){if(!e)return;const t=Gc(e),n=t[0],r=Kp(t[1]),s=Kp(t[2]);return e.a<255?`hsla(${n}, ${r}%, ${s}%, ${Jn(e.a)})`:`hsl(${n}, ${r}%, ${s}%)`}const Qp={x:"dark",Z:"light",Y:"re",X:"blu",W:"gr",V:"medium",U:"slate",A:"ee",T:"ol",S:"or",B:"ra",C:"lateg",D:"ights",R:"in",Q:"turquois",E:"hi",P:"ro",O:"al",N:"le",M:"de",L:"yello",F:"en",K:"ch",G:"arks",H:"ea",I:"ightg",J:"wh"},Jp={OiceXe:"f0f8ff",antiquewEte:"faebd7",aqua:"ffff",aquamarRe:"7fffd4",azuY:"f0ffff",beige:"f5f5dc",bisque:"ffe4c4",black:"0",blanKedOmond:"ffebcd",Xe:"ff",XeviTet:"8a2be2",bPwn:"a52a2a",burlywood:"deb887",caMtXe:"5f9ea0",KartYuse:"7fff00",KocTate:"d2691e",cSO:"ff7f50",cSnflowerXe:"6495ed",cSnsilk:"fff8dc",crimson:"dc143c",cyan:"ffff",xXe:"8b",xcyan:"8b8b",xgTMnPd:"b8860b",xWay:"a9a9a9",xgYF:"6400",xgYy:"a9a9a9",xkhaki:"bdb76b",xmagFta:"8b008b",xTivegYF:"556b2f",xSange:"ff8c00",xScEd:"9932cc",xYd:"8b0000",xsOmon:"e9967a",xsHgYF:"8fbc8f",xUXe:"483d8b",xUWay:"2f4f4f",xUgYy:"2f4f4f",xQe:"ced1",xviTet:"9400d3",dAppRk:"ff1493",dApskyXe:"bfff",dimWay:"696969",dimgYy:"696969",dodgerXe:"1e90ff",fiYbrick:"b22222",flSOwEte:"fffaf0",foYstWAn:"228b22",fuKsia:"ff00ff",gaRsbSo:"dcdcdc",ghostwEte:"f8f8ff",gTd:"ffd700",gTMnPd:"daa520",Way:"808080",gYF:"8000",gYFLw:"adff2f",gYy:"808080",honeyMw:"f0fff0",hotpRk:"ff69b4",RdianYd:"cd5c5c",Rdigo:"4b0082",ivSy:"fffff0",khaki:"f0e68c",lavFMr:"e6e6fa",lavFMrXsh:"fff0f5",lawngYF:"7cfc00",NmoncEffon:"fffacd",ZXe:"add8e6",ZcSO:"f08080",Zcyan:"e0ffff",ZgTMnPdLw:"fafad2",ZWay:"d3d3d3",ZgYF:"90ee90",ZgYy:"d3d3d3",ZpRk:"ffb6c1",ZsOmon:"ffa07a",ZsHgYF:"20b2aa",ZskyXe:"87cefa",ZUWay:"778899",ZUgYy:"778899",ZstAlXe:"b0c4de",ZLw:"ffffe0",lime:"ff00",limegYF:"32cd32",lRF:"faf0e6",magFta:"ff00ff",maPon:"800000",VaquamarRe:"66cdaa",VXe:"cd",VScEd:"ba55d3",VpurpN:"9370db",VsHgYF:"3cb371",VUXe:"7b68ee",VsprRggYF:"fa9a",VQe:"48d1cc",VviTetYd:"c71585",midnightXe:"191970",mRtcYam:"f5fffa",mistyPse:"ffe4e1",moccasR:"ffe4b5",navajowEte:"ffdead",navy:"80",Tdlace:"fdf5e6",Tive:"808000",TivedBb:"6b8e23",Sange:"ffa500",SangeYd:"ff4500",ScEd:"da70d6",pOegTMnPd:"eee8aa",pOegYF:"98fb98",pOeQe:"afeeee",pOeviTetYd:"db7093",papayawEp:"ffefd5",pHKpuff:"ffdab9",peru:"cd853f",pRk:"ffc0cb",plum:"dda0dd",powMrXe:"b0e0e6",purpN:"800080",YbeccapurpN:"663399",Yd:"ff0000",Psybrown:"bc8f8f",PyOXe:"4169e1",saddNbPwn:"8b4513",sOmon:"fa8072",sandybPwn:"f4a460",sHgYF:"2e8b57",sHshell:"fff5ee",siFna:"a0522d",silver:"c0c0c0",skyXe:"87ceeb",UXe:"6a5acd",UWay:"708090",UgYy:"708090",snow:"fffafa",sprRggYF:"ff7f",stAlXe:"4682b4",tan:"d2b48c",teO:"8080",tEstN:"d8bfd8",tomato:"ff6347",Qe:"40e0d0",viTet:"ee82ee",JHt:"f5deb3",wEte:"ffffff",wEtesmoke:"f5f5f5",Lw:"ffff00",LwgYF:"9acd32"};function TM(){const e={},t=Object.keys(Jp),n=Object.keys(Qp);let r,s,o,i,a;for(r=0;r>16&255,o>>8&255,o&255]}return e}let ia;function OM(e){ia||(ia=TM(),ia.transparent=[0,0,0,0]);const t=ia[e.toLowerCase()];return t&&{r:t[0],g:t[1],b:t[2],a:t.length===4?t[3]:255}}const AM=/^rgba?\(\s*([-+.\d]+)(%)?[\s,]+([-+.e\d]+)(%)?[\s,]+([-+.e\d]+)(%)?(?:[\s,/]+([-+.e\d]+)(%)?)?\s*\)$/;function DM(e){const t=AM.exec(e);let n=255,r,s,o;if(t){if(t[7]!==r){const i=+t[7];n=t[8]?ko(i):pr(i*255,0,255)}return r=+t[1],s=+t[3],o=+t[5],r=255&(t[2]?ko(r):pr(r,0,255)),s=255&(t[4]?ko(s):pr(s,0,255)),o=255&(t[6]?ko(o):pr(o,0,255)),{r,g:s,b:o,a:n}}}function IM(e){return e&&(e.a<255?`rgba(${e.r}, ${e.g}, ${e.b}, ${Jn(e.a)})`:`rgb(${e.r}, ${e.g}, ${e.b})`)}const Kc=e=>e<=.0031308?e*12.92:Math.pow(e,1/2.4)*1.055-.055,_s=e=>e<=.04045?e/12.92:Math.pow((e+.055)/1.055,2.4);function LM(e,t,n){const r=_s(Jn(e.r)),s=_s(Jn(e.g)),o=_s(Jn(e.b));return{r:mr(Kc(r+n*(_s(Jn(t.r))-r))),g:mr(Kc(s+n*(_s(Jn(t.g))-s))),b:mr(Kc(o+n*(_s(Jn(t.b))-o))),a:e.a+n*(t.a-e.a)}}function aa(e,t,n){if(e){let r=Gc(e);r[t]=Math.max(0,Math.min(r[t]+r[t]*n,t===0?360:1)),r=Xc(r),e.r=r[0],e.g=r[1],e.b=r[2]}}function Rp(e,t){return e&&Object.assign(t||{},e)}function e0(e){var t={r:0,g:0,b:0,a:255};return Array.isArray(e)?e.length>=3&&(t={r:e[0],g:e[1],b:e[2],a:255},e.length>3&&(t.a=mr(e[3]))):(t=Rp(e,{r:0,g:0,b:0,a:1}),t.a=mr(t.a)),t}function PM(e){return e.charAt(0)==="r"?DM(e):EM(e)}class Co{constructor(t){if(t instanceof Co)return t;const n=typeof t;let r;n==="object"?r=e0(t):n==="string"&&(r=bM(t)||OM(t)||PM(t)),this._rgb=r,this._valid=!!r}get valid(){return this._valid}get rgb(){var t=Rp(this._rgb);return t&&(t.a=Jn(t.a)),t}set rgb(t){this._rgb=e0(t)}rgbString(){return this._valid?IM(this._rgb):void 0}hexString(){return this._valid?vM(this._rgb):void 0}hslString(){return this._valid?NM(this._rgb):void 0}mix(t,n){if(t){const r=this.rgb,s=t.rgb;let o;const i=n===o?.5:n,a=2*i-1,l=r.a-s.a,c=((a*l===-1?a:(a+l)/(1+a*l))+1)/2;o=1-c,r.r=255&c*r.r+o*s.r+.5,r.g=255&c*r.g+o*s.g+.5,r.b=255&c*r.b+o*s.b+.5,r.a=i*r.a+(1-i)*s.a,this.rgb=r}return this}interpolate(t,n){return t&&(this._rgb=LM(this._rgb,t._rgb,n)),this}clone(){return new Co(this.rgb)}alpha(t){return this._rgb.a=mr(t),this}clearer(t){const n=this._rgb;return n.a*=1-t,this}greyscale(){const t=this._rgb,n=wo(t.r*.3+t.g*.59+t.b*.11);return t.r=t.g=t.b=n,this}opaquer(t){const n=this._rgb;return n.a*=1+t,this}negate(){const t=this._rgb;return t.r=255-t.r,t.g=255-t.g,t.b=255-t.b,this}lighten(t){return aa(this._rgb,2,t),this}darken(t){return aa(this._rgb,2,-t),this}saturate(t){return aa(this._rgb,1,t),this}desaturate(t){return aa(this._rgb,1,-t),this}rotate(t){return MM(this._rgb,t),this}}function Rn(){}const $M=(()=>{let e=0;return()=>e++})();function dt(e){return e==null}function Tt(e){if(Array.isArray&&Array.isArray(e))return!0;const t=Object.prototype.toString.call(e);return t.slice(0,7)==="[object"&&t.slice(-6)==="Array]"}function Je(e){return e!==null&&Object.prototype.toString.call(e)==="[object Object]"}function yn(e){return(typeof e=="number"||e instanceof Number)&&isFinite(+e)}function Gn(e,t){return yn(e)?e:t}function Re(e,t){return typeof e>"u"?t:e}const FM=(e,t)=>typeof e=="string"&&e.endsWith("%")?parseFloat(e)/100:+e/t,t0=(e,t)=>typeof e=="string"&&e.endsWith("%")?parseFloat(e)/100*t:+e;function mt(e,t,n){if(e&&typeof e.call=="function")return e.apply(n,t)}function lt(e,t,n,r){let s,o,i;if(Tt(e))for(o=e.length,s=0;se,x:e=>e.x,y:e=>e.y};function HM(e){const t=e.split("."),n=[];let r="";for(const s of t)r+=s,r.endsWith("\\")?r=r.slice(0,-1)+".":(n.push(r),r="");return n}function UM(e){const t=HM(e);return n=>{for(const r of t){if(r==="")break;n=n&&n[r]}return n}}function Lr(e,t){return(r0[t]||(r0[t]=UM(t)))(e)}function qc(e){return e.charAt(0).toUpperCase()+e.slice(1)}const Eo=e=>typeof e<"u",br=e=>typeof e=="function",s0=(e,t)=>{if(e.size!==t.size)return!1;for(const n of e)if(!t.has(n))return!1;return!0};function BM(e){return e.type==="mouseup"||e.type==="click"||e.type==="contextmenu"}const ut=Math.PI,Mt=2*ut,da=Number.POSITIVE_INFINITY,jM=ut/180,Ot=ut/2,Pr=ut/4,o0=ut*2/3,i0=Math.log10,yr=Math.sign;function ua(e,t,n){return Math.abs(e-t)s-o).pop(),t}function GM(e){return typeof e=="symbol"||typeof e=="object"&&e!==null&&!(Symbol.toPrimitive in e||"toString"in e||"valueOf"in e)}function fa(e){return!GM(e)&&!isNaN(parseFloat(e))&&isFinite(e)}function YM(e,t){const n=Math.round(e);return n-t<=e&&n+t>=e}function XM(e,t,n){let r,s,o;for(r=0,s=e.length;rl&&c=Math.min(t,n)-r&&e<=Math.max(t,n)+r}function Zc(e,t,n){n=n||(i=>e[i]1;)o=s+r>>1,n(o)?s=o:r=o;return{lo:s,hi:r}}const Qc=(e,t,n,r)=>Zc(e,n,r?s=>{const o=e[s][t];return oe[s][t]Zc(e,n,r=>e[r][t]>=n);function JM(e,t,n){let r=0,s=e.length;for(;rr&&e[s-1]>n;)s--;return r>0||s{const r="_onData"+qc(n),s=e[n];Object.defineProperty(e,n,{configurable:!0,enumerable:!1,value(...o){const i=s.apply(this,o);return e._chartjs.listeners.forEach(a=>{typeof a[r]=="function"&&a[r](...o)}),i}})})}function u0(e,t){const n=e._chartjs;if(!n)return;const r=n.listeners,s=r.indexOf(t);s!==-1&&r.splice(s,1),!(r.length>0)&&(d0.forEach(o=>{delete e[o]}),delete e._chartjs)}function f0(e){const t=new Set(e);return t.size===e.length?e:Array.from(t)}const h0=(function(){return typeof window>"u"?function(e){return e()}:window.requestAnimationFrame})();function g0(e,t){let n=[],r=!1;return function(...s){n=s,r||(r=!0,h0.call(window,()=>{r=!1,e.apply(t,n)}))}}function eN(e,t){let n;return function(...r){return t?(clearTimeout(n),n=setTimeout(e,t,r)):e.apply(this,r),t}}const Jc=e=>e==="start"?"left":e==="end"?"right":"center",Xt=(e,t,n)=>e==="start"?t:e==="end"?n:(t+n)/2,tN=(e,t,n,r)=>e===(r?"left":"right")?n:e==="center"?(t+n)/2:t,ga=e=>e===0||e===1,p0=(e,t,n)=>-(Math.pow(2,10*(e-=1))*Math.sin((e-t)*Mt/n)),m0=(e,t,n)=>Math.pow(2,-10*e)*Math.sin((e-t)*Mt/n)+1,Mo={linear:e=>e,easeInQuad:e=>e*e,easeOutQuad:e=>-e*(e-2),easeInOutQuad:e=>(e/=.5)<1?.5*e*e:-.5*(--e*(e-2)-1),easeInCubic:e=>e*e*e,easeOutCubic:e=>(e-=1)*e*e+1,easeInOutCubic:e=>(e/=.5)<1?.5*e*e*e:.5*((e-=2)*e*e+2),easeInQuart:e=>e*e*e*e,easeOutQuart:e=>-((e-=1)*e*e*e-1),easeInOutQuart:e=>(e/=.5)<1?.5*e*e*e*e:-.5*((e-=2)*e*e*e-2),easeInQuint:e=>e*e*e*e*e,easeOutQuint:e=>(e-=1)*e*e*e*e+1,easeInOutQuint:e=>(e/=.5)<1?.5*e*e*e*e*e:.5*((e-=2)*e*e*e*e+2),easeInSine:e=>-Math.cos(e*Ot)+1,easeOutSine:e=>Math.sin(e*Ot),easeInOutSine:e=>-.5*(Math.cos(ut*e)-1),easeInExpo:e=>e===0?0:Math.pow(2,10*(e-1)),easeOutExpo:e=>e===1?1:-Math.pow(2,-10*e)+1,easeInOutExpo:e=>ga(e)?e:e<.5?.5*Math.pow(2,10*(e*2-1)):.5*(-Math.pow(2,-10*(e*2-1))+2),easeInCirc:e=>e>=1?e:-(Math.sqrt(1-e*e)-1),easeOutCirc:e=>Math.sqrt(1-(e-=1)*e),easeInOutCirc:e=>(e/=.5)<1?-.5*(Math.sqrt(1-e*e)-1):.5*(Math.sqrt(1-(e-=2)*e)+1),easeInElastic:e=>ga(e)?e:p0(e,.075,.3),easeOutElastic:e=>ga(e)?e:m0(e,.075,.3),easeInOutElastic(e){return ga(e)?e:e<.5?.5*p0(e*2,.1125,.45):.5+.5*m0(e*2-1,.1125,.45)},easeInBack(e){return e*e*((1.70158+1)*e-1.70158)},easeOutBack(e){return(e-=1)*e*((1.70158+1)*e+1.70158)+1},easeInOutBack(e){let t=1.70158;return(e/=.5)<1?.5*(e*e*(((t*=1.525)+1)*e-t)):.5*((e-=2)*e*(((t*=1.525)+1)*e+t)+2)},easeInBounce:e=>1-Mo.easeOutBounce(1-e),easeOutBounce(e){return e<1/2.75?7.5625*e*e:e<2/2.75?7.5625*(e-=1.5/2.75)*e+.75:e<2.5/2.75?7.5625*(e-=2.25/2.75)*e+.9375:7.5625*(e-=2.625/2.75)*e+.984375},easeInOutBounce:e=>e<.5?Mo.easeInBounce(e*2)*.5:Mo.easeOutBounce(e*2-1)*.5+.5};function b0(e){if(e&&typeof e=="object"){const t=e.toString();return t==="[object CanvasPattern]"||t==="[object CanvasGradient]"}return!1}function y0(e){return b0(e)?e:new Co(e)}function Rc(e){return b0(e)?e:new Co(e).saturate(.5).darken(.1).hexString()}const nN=["x","y","borderWidth","radius","tension"],rN=["color","borderColor","backgroundColor"];function sN(e){e.set("animation",{delay:void 0,duration:1e3,easing:"easeOutQuart",fn:void 0,from:void 0,loop:void 0,to:void 0,type:void 0}),e.describe("animation",{_fallback:!1,_indexable:!1,_scriptable:t=>t!=="onProgress"&&t!=="onComplete"&&t!=="fn"}),e.set("animations",{colors:{type:"color",properties:rN},numbers:{type:"number",properties:nN}}),e.describe("animations",{_fallback:"animation"}),e.set("transitions",{active:{animation:{duration:400}},resize:{animation:{duration:0}},show:{animations:{colors:{from:"transparent"},visible:{type:"boolean",duration:0}}},hide:{animations:{colors:{to:"transparent"},visible:{type:"boolean",easing:"linear",fn:t=>t|0}}}})}function oN(e){e.set("layout",{autoPadding:!0,padding:{top:0,right:0,bottom:0,left:0}})}const v0=new Map;function iN(e,t){t=t||{};const n=e+JSON.stringify(t);let r=v0.get(n);return r||(r=new Intl.NumberFormat(e,t),v0.set(n,r)),r}function ed(e,t,n){return iN(t,n).format(e)}const aN={values(e){return Tt(e)?e:""+e},numeric(e,t,n){if(e===0)return"0";const r=this.chart.options.locale;let s,o=e;if(n.length>1){const c=Math.max(Math.abs(n[0].value),Math.abs(n[n.length-1].value));(c<1e-4||c>1e15)&&(s="scientific"),o=lN(e,n)}const i=i0(Math.abs(o)),a=isNaN(i)?1:Math.max(Math.min(-1*Math.floor(i),20),0),l={notation:s,minimumFractionDigits:a,maximumFractionDigits:a};return Object.assign(l,this.options.ticks.format),ed(e,r,l)}};function lN(e,t){let n=t.length>3?t[2].value-t[1].value:t[1].value-t[0].value;return Math.abs(n)>=1&&e!==Math.floor(e)&&(n=e-Math.floor(e)),n}var x0={formatters:aN};function cN(e){e.set("scale",{display:!0,offset:!1,reverse:!1,beginAtZero:!1,bounds:"ticks",clip:!0,grace:0,grid:{display:!0,lineWidth:1,drawOnChartArea:!0,drawTicks:!0,tickLength:8,tickWidth:(t,n)=>n.lineWidth,tickColor:(t,n)=>n.color,offset:!1},border:{display:!0,dash:[],dashOffset:0,width:1},title:{display:!1,text:"",padding:{top:4,bottom:4}},ticks:{minRotation:0,maxRotation:50,mirror:!1,textStrokeWidth:0,textStrokeColor:"",padding:3,display:!0,autoSkip:!0,autoSkipPadding:3,labelOffset:0,callback:x0.formatters.values,minor:{},major:{},align:"center",crossAlign:"near",showLabelBackdrop:!1,backdropColor:"rgba(255, 255, 255, 0.75)",backdropPadding:2}}),e.route("scale.ticks","color","","color"),e.route("scale.grid","color","","borderColor"),e.route("scale.border","color","","borderColor"),e.route("scale.title","color","","color"),e.describe("scale",{_fallback:!1,_scriptable:t=>!t.startsWith("before")&&!t.startsWith("after")&&t!=="callback"&&t!=="parser",_indexable:t=>t!=="borderDash"&&t!=="tickBorderDash"&&t!=="dash"}),e.describe("scales",{_fallback:"scale"}),e.describe("scale.ticks",{_scriptable:t=>t!=="backdropPadding"&&t!=="callback",_indexable:t=>t!=="backdropPadding"})}const Fr=Object.create(null),td=Object.create(null);function No(e,t){if(!t)return e;const n=t.split(".");for(let r=0,s=n.length;rr.chart.platform.getDevicePixelRatio(),this.elements={},this.events=["mousemove","mouseout","click","touchstart","touchmove"],this.font={family:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",size:12,style:"normal",lineHeight:1.2,weight:null},this.hover={},this.hoverBackgroundColor=(r,s)=>Rc(s.backgroundColor),this.hoverBorderColor=(r,s)=>Rc(s.borderColor),this.hoverColor=(r,s)=>Rc(s.color),this.indexAxis="x",this.interaction={mode:"nearest",intersect:!0,includeInvisible:!1},this.maintainAspectRatio=!0,this.onHover=null,this.onClick=null,this.parsing=!0,this.plugins={},this.responsive=!0,this.scale=void 0,this.scales={},this.showLine=!0,this.drawActiveElementsOnTop=!0,this.describe(t),this.apply(n)}set(t,n){return nd(this,t,n)}get(t){return No(this,t)}describe(t,n){return nd(td,t,n)}override(t,n){return nd(Fr,t,n)}route(t,n,r,s){const o=No(this,t),i=No(this,r),a="_"+n;Object.defineProperties(o,{[a]:{value:o[n],writable:!0},[n]:{enumerable:!0,get(){const l=this[a],c=i[s];return Je(l)?Object.assign({},c,l):Re(l,c)},set(l){this[a]=l}}})}apply(t){t.forEach(n=>n(this))}}var kt=new dN({_scriptable:e=>!e.startsWith("on"),_indexable:e=>e!=="events",hover:{_fallback:"interaction"},interaction:{_scriptable:!1,_indexable:!1}},[sN,oN,cN]);function uN(e){return!e||dt(e.size)||dt(e.family)?null:(e.style?e.style+" ":"")+(e.weight?e.weight+" ":"")+e.size+"px "+e.family}function w0(e,t,n,r,s){let o=t[s];return o||(o=t[s]=e.measureText(s).width,n.push(s)),o>r&&(r=o),r}function zr(e,t,n){const r=e.currentDevicePixelRatio,s=n!==0?Math.max(n/2,.5):0;return Math.round((t-s)*r)/r+s}function k0(e,t){!t&&!e||(t=t||e.getContext("2d"),t.save(),t.resetTransform(),t.clearRect(0,0,e.width,e.height),t.restore())}function C0(e,t,n,r){S0(e,t,n,r,null)}function S0(e,t,n,r,s){let o,i,a,l,c,d,u,f;const g=t.pointStyle,m=t.rotation,p=t.radius;let y=(m||0)*jM;if(g&&typeof g=="object"&&(o=g.toString(),o==="[object HTMLImageElement]"||o==="[object HTMLCanvasElement]")){e.save(),e.translate(n,r),e.rotate(y),e.drawImage(g,-g.width/2,-g.height/2,g.width,g.height),e.restore();return}if(!(isNaN(p)||p<=0)){switch(e.beginPath(),g){default:s?e.ellipse(n,r,s/2,p,0,0,Mt):e.arc(n,r,p,0,Mt),e.closePath();break;case"triangle":d=s?s/2:p,e.moveTo(n+Math.sin(y)*d,r-Math.cos(y)*p),y+=o0,e.lineTo(n+Math.sin(y)*d,r-Math.cos(y)*p),y+=o0,e.lineTo(n+Math.sin(y)*d,r-Math.cos(y)*p),e.closePath();break;case"rectRounded":c=p*.516,l=p-c,i=Math.cos(y+Pr)*l,u=Math.cos(y+Pr)*(s?s/2-c:l),a=Math.sin(y+Pr)*l,f=Math.sin(y+Pr)*(s?s/2-c:l),e.arc(n-u,r-a,c,y-ut,y-Ot),e.arc(n+f,r-i,c,y-Ot,y),e.arc(n+u,r+a,c,y,y+Ot),e.arc(n-f,r+i,c,y+Ot,y+ut),e.closePath();break;case"rect":if(!m){l=Math.SQRT1_2*p,d=s?s/2:l,e.rect(n-d,r-l,2*d,2*l);break}y+=Pr;case"rectRot":u=Math.cos(y)*(s?s/2:p),i=Math.cos(y)*p,a=Math.sin(y)*p,f=Math.sin(y)*(s?s/2:p),e.moveTo(n-u,r-a),e.lineTo(n+f,r-i),e.lineTo(n+u,r+a),e.lineTo(n-f,r+i),e.closePath();break;case"crossRot":y+=Pr;case"cross":u=Math.cos(y)*(s?s/2:p),i=Math.cos(y)*p,a=Math.sin(y)*p,f=Math.sin(y)*(s?s/2:p),e.moveTo(n-u,r-a),e.lineTo(n+u,r+a),e.moveTo(n+f,r-i),e.lineTo(n-f,r+i);break;case"star":u=Math.cos(y)*(s?s/2:p),i=Math.cos(y)*p,a=Math.sin(y)*p,f=Math.sin(y)*(s?s/2:p),e.moveTo(n-u,r-a),e.lineTo(n+u,r+a),e.moveTo(n+f,r-i),e.lineTo(n-f,r+i),y+=Pr,u=Math.cos(y)*(s?s/2:p),i=Math.cos(y)*p,a=Math.sin(y)*p,f=Math.sin(y)*(s?s/2:p),e.moveTo(n-u,r-a),e.lineTo(n+u,r+a),e.moveTo(n+f,r-i),e.lineTo(n-f,r+i);break;case"line":i=s?s/2:Math.cos(y)*p,a=Math.sin(y)*p,e.moveTo(n-i,r-a),e.lineTo(n+i,r+a);break;case"dash":e.moveTo(n,r),e.lineTo(n+Math.cos(y)*(s?s/2:p),r+Math.sin(y)*p);break;case!1:e.closePath();break}e.fill(),t.borderWidth>0&&e.stroke()}}function _0(e,t,n){return n=n||.5,!t||e&&e.x>t.left-n&&e.xt.top-n&&e.y0&&o.strokeColor!=="";let l,c;for(e.save(),e.font=s.string,fN(e,o),l=0;l+e||0;function od(e,t){const n={},r=Je(t),s=r?Object.keys(t):t,o=Je(e)?r?i=>Re(e[i],e[t[i]]):i=>e[i]:()=>e;for(const i of s)n[i]=yN(o(i));return n}function E0(e){return od(e,{top:"y",right:"x",bottom:"y",left:"x"})}function Es(e){return od(e,["topLeft","topRight","bottomLeft","bottomRight"])}function vn(e){const t=E0(e);return t.width=t.left+t.right,t.height=t.top+t.bottom,t}function Kt(e,t){e=e||{},t=t||kt.font;let n=Re(e.size,t.size);typeof n=="string"&&(n=parseInt(n,10));let r=Re(e.style,t.style);r&&!(""+r).match(mN)&&(console.warn('Invalid font style specified: "'+r+'"'),r=void 0);const s={family:Re(e.family,t.family),lineHeight:bN(Re(e.lineHeight,t.lineHeight),n),size:n,style:r,weight:Re(e.weight,t.weight),string:""};return s.string=uN(s),s}function ma(e,t,n,r){let s,o,i;for(s=0,o=e.length;sn&&a===0?0:a+l;return{min:i(r,-Math.abs(o)),max:i(s,o)}}function Ms(e,t){return Object.assign(Object.create(e),t)}function id(e,t=[""],n,r,s=()=>e[0]){const o=n||e;typeof r>"u"&&(r=A0("_fallback",e));const i={[Symbol.toStringTag]:"Object",_cacheable:!0,_scopes:e,_rootScopes:o,_fallback:r,_getTarget:s,override:a=>id([a,...e],t,o,r)};return new Proxy(i,{deleteProperty(a,l){return delete a[l],delete a._keys,delete e[0][l],!0},get(a,l){return N0(a,l,()=>MN(l,t,e,a))},getOwnPropertyDescriptor(a,l){return Reflect.getOwnPropertyDescriptor(a._scopes[0],l)},getPrototypeOf(){return Reflect.getPrototypeOf(e[0])},has(a,l){return D0(a).includes(l)},ownKeys(a){return D0(a)},set(a,l,c){const d=a._storage||(a._storage=s());return a[l]=d[l]=c,delete a._keys,!0}})}function Ns(e,t,n,r){const s={_cacheable:!1,_proxy:e,_context:t,_subProxy:n,_stack:new Set,_descriptors:M0(e,r),setContext:o=>Ns(e,o,n,r),override:o=>Ns(e.override(o),t,n,r)};return new Proxy(s,{deleteProperty(o,i){return delete o[i],delete e[i],!0},get(o,i,a){return N0(o,i,()=>wN(o,i,a))},getOwnPropertyDescriptor(o,i){return o._descriptors.allKeys?Reflect.has(e,i)?{enumerable:!0,configurable:!0}:void 0:Reflect.getOwnPropertyDescriptor(e,i)},getPrototypeOf(){return Reflect.getPrototypeOf(e)},has(o,i){return Reflect.has(e,i)},ownKeys(){return Reflect.ownKeys(e)},set(o,i,a){return e[i]=a,delete o[i],!0}})}function M0(e,t={scriptable:!0,indexable:!0}){const{_scriptable:n=t.scriptable,_indexable:r=t.indexable,_allKeys:s=t.allKeys}=e;return{allKeys:s,scriptable:n,indexable:r,isScriptable:br(n)?n:()=>n,isIndexable:br(r)?r:()=>r}}const xN=(e,t)=>e?e+qc(t):t,ad=(e,t)=>Je(t)&&e!=="adapters"&&(Object.getPrototypeOf(t)===null||t.constructor===Object);function N0(e,t,n){if(Object.prototype.hasOwnProperty.call(e,t)||t==="constructor")return e[t];const r=n();return e[t]=r,r}function wN(e,t,n){const{_proxy:r,_context:s,_subProxy:o,_descriptors:i}=e;let a=r[t];return br(a)&&i.isScriptable(t)&&(a=kN(t,a,e,n)),Tt(a)&&a.length&&(a=CN(t,a,e,i.isIndexable)),ad(t,a)&&(a=Ns(a,s,o&&o[t],i)),a}function kN(e,t,n,r){const{_proxy:s,_context:o,_subProxy:i,_stack:a}=n;if(a.has(e))throw new Error("Recursion detected: "+Array.from(a).join("->")+"->"+e);a.add(e);let l=t(o,i||r);return a.delete(e),ad(e,l)&&(l=ld(s._scopes,s,e,l)),l}function CN(e,t,n,r){const{_proxy:s,_context:o,_subProxy:i,_descriptors:a}=n;if(typeof o.index<"u"&&r(e))return t[o.index%t.length];if(Je(t[0])){const l=t,c=s._scopes.filter(d=>d!==l);t=[];for(const d of l){const u=ld(c,s,e,d);t.push(Ns(u,o,i&&i[e],a))}}return t}function T0(e,t,n){return br(e)?e(t,n):e}const SN=(e,t)=>e===!0?t:typeof e=="string"?Lr(t,e):void 0;function _N(e,t,n,r,s){for(const o of t){const i=SN(n,o);if(i){e.add(i);const a=T0(i._fallback,n,s);if(typeof a<"u"&&a!==n&&a!==r)return a}else if(i===!1&&typeof r<"u"&&n!==r)return null}return!1}function ld(e,t,n,r){const s=t._rootScopes,o=T0(t._fallback,n,r),i=[...e,...s],a=new Set;a.add(r);let l=O0(a,i,n,o||n,r);return l===null||typeof o<"u"&&o!==n&&(l=O0(a,i,o,l,r),l===null)?!1:id(Array.from(a),[""],s,o,()=>EN(t,n,r))}function O0(e,t,n,r,s){for(;n;)n=_N(e,t,n,r,s);return n}function EN(e,t,n){const r=e._getTarget();t in r||(r[t]={});const s=r[t];return Tt(s)&&Je(n)?n:s||{}}function MN(e,t,n,r){let s;for(const o of t)if(s=A0(xN(o,e),n),typeof s<"u")return ad(e,s)?ld(n,r,e,s):s}function A0(e,t){for(const n of t){if(!n)continue;const r=n[e];if(typeof r<"u")return r}}function D0(e){let t=e._keys;return t||(t=e._keys=NN(e._scopes)),t}function NN(e){const t=new Set;for(const n of e)for(const r of Object.keys(n).filter(s=>!s.startsWith("_")))t.add(r);return Array.from(t)}function cd(){return typeof window<"u"&&typeof document<"u"}function dd(e){let t=e.parentNode;return t&&t.toString()==="[object ShadowRoot]"&&(t=t.host),t}function ba(e,t,n){let r;return typeof e=="string"?(r=parseInt(e,10),e.indexOf("%")!==-1&&(r=r/100*t.parentNode[n])):r=e,r}const ya=e=>e.ownerDocument.defaultView.getComputedStyle(e,null);function TN(e,t){return ya(e).getPropertyValue(t)}const ON=["top","right","bottom","left"];function Vr(e,t,n){const r={};n=n?"-"+n:"";for(let s=0;s<4;s++){const o=ON[s];r[o]=parseFloat(e[t+"-"+o+n])||0}return r.width=r.left+r.right,r.height=r.top+r.bottom,r}const AN=(e,t,n)=>(e>0||t>0)&&(!n||!n.shadowRoot);function DN(e,t){const n=e.touches,r=n&&n.length?n[0]:e,{offsetX:s,offsetY:o}=r;let i=!1,a,l;if(AN(s,o,e.target))a=s,l=o;else{const c=t.getBoundingClientRect();a=r.clientX-c.left,l=r.clientY-c.top,i=!0}return{x:a,y:l,box:i}}function Hr(e,t){if("native"in e)return e;const{canvas:n,currentDevicePixelRatio:r}=t,s=ya(n),o=s.boxSizing==="border-box",i=Vr(s,"padding"),a=Vr(s,"border","width"),{x:l,y:c,box:d}=DN(e,n),u=i.left+(d&&a.left),f=i.top+(d&&a.top);let{width:g,height:m}=t;return o&&(g-=i.width+a.width,m-=i.height+a.height),{x:Math.round((l-u)/g*n.width/r),y:Math.round((c-f)/m*n.height/r)}}function IN(e,t,n){let r,s;if(t===void 0||n===void 0){const o=e&&dd(e);if(!o)t=e.clientWidth,n=e.clientHeight;else{const i=o.getBoundingClientRect(),a=ya(o),l=Vr(a,"border","width"),c=Vr(a,"padding");t=i.width-c.width-l.width,n=i.height-c.height-l.height,r=ba(a.maxWidth,o,"clientWidth"),s=ba(a.maxHeight,o,"clientHeight")}}return{width:t,height:n,maxWidth:r||da,maxHeight:s||da}}const vr=e=>Math.round(e*10)/10;function LN(e,t,n,r){const s=ya(e),o=Vr(s,"margin"),i=ba(s.maxWidth,e,"clientWidth")||da,a=ba(s.maxHeight,e,"clientHeight")||da,l=IN(e,t,n);let{width:c,height:d}=l;if(s.boxSizing==="content-box"){const f=Vr(s,"border","width"),g=Vr(s,"padding");c-=g.width+f.width,d-=g.height+f.height}return c=Math.max(0,c-o.width),d=Math.max(0,r?c/r:d-o.height),c=vr(Math.min(c,i,l.maxWidth)),d=vr(Math.min(d,a,l.maxHeight)),c&&!d&&(d=vr(c/2)),(t!==void 0||n!==void 0)&&r&&l.height&&d>l.height&&(d=l.height,c=vr(Math.floor(d*r))),{width:c,height:d}}function I0(e,t,n){const r=t||1,s=vr(e.height*r),o=vr(e.width*r);e.height=vr(e.height),e.width=vr(e.width);const i=e.canvas;return i.style&&(n||!i.style.height&&!i.style.width)&&(i.style.height=`${e.height}px`,i.style.width=`${e.width}px`),e.currentDevicePixelRatio!==r||i.height!==s||i.width!==o?(e.currentDevicePixelRatio=r,i.height=s,i.width=o,e.ctx.setTransform(r,0,0,r,0,0),!0):!1}const PN=(function(){let e=!1;try{const t={get passive(){return e=!0,!1}};cd()&&(window.addEventListener("test",null,t),window.removeEventListener("test",null,t))}catch{}return e})();function L0(e,t){const n=TN(e,t),r=n&&n.match(/^(\d+)(\.\d+)?px$/);return r?+r[1]:void 0}const $N=function(e,t){return{x(n){return e+e+t-n},setWidth(n){t=n},textAlign(n){return n==="center"?n:n==="right"?"left":"right"},xPlus(n,r){return n-r},leftForLtr(n,r){return n-r}}},FN=function(){return{x(e){return e},setWidth(e){},textAlign(e){return e},xPlus(e,t){return e+t},leftForLtr(e,t){return e}}};function Ts(e,t,n){return e?$N(t,n):FN()}function P0(e,t){let n,r;(t==="ltr"||t==="rtl")&&(n=e.canvas.style,r=[n.getPropertyValue("direction"),n.getPropertyPriority("direction")],n.setProperty("direction",t,"important"),e.prevTextDirection=r)}function $0(e,t){t!==void 0&&(delete e.prevTextDirection,e.canvas.style.setProperty("direction",t[0],t[1]))}function va(e,t,n){return e.options.clip?e[n]:t[n]}function zN(e,t){const{xScale:n,yScale:r}=e;return n&&r?{left:va(n,t,"left"),right:va(n,t,"right"),top:va(r,t,"top"),bottom:va(r,t,"bottom")}:t}function VN(e,t){const n=t._clip;if(n.disabled)return!1;const r=zN(t,e.chartArea);return{left:n.left===!1?0:r.left-(n.left===!0?0:n.left),right:n.right===!1?e.width:r.right+(n.right===!0?0:n.right),top:n.top===!1?0:r.top-(n.top===!0?0:n.top),bottom:n.bottom===!1?e.height:r.bottom+(n.bottom===!0?0:n.bottom)}}class HN{constructor(){this._request=null,this._charts=new Map,this._running=!1,this._lastDate=void 0}_notify(t,n,r,s){const o=n.listeners[s],i=n.duration;o.forEach(a=>a({chart:t,initial:n.initial,numSteps:i,currentStep:Math.min(r-n.start,i)}))}_refresh(){this._request||(this._running=!0,this._request=h0.call(window,()=>{this._update(),this._request=null,this._running&&this._refresh()}))}_update(t=Date.now()){let n=0;this._charts.forEach((r,s)=>{if(!r.running||!r.items.length)return;const o=r.items;let i=o.length-1,a=!1,l;for(;i>=0;--i)l=o[i],l._active?(l._total>r.duration&&(r.duration=l._total),l.tick(t),a=!0):(o[i]=o[o.length-1],o.pop());a&&(s.draw(),this._notify(s,r,t,"progress")),o.length||(r.running=!1,this._notify(s,r,t,"complete"),r.initial=!1),n+=o.length}),this._lastDate=t,n===0&&(this._running=!1)}_getAnims(t){const n=this._charts;let r=n.get(t);return r||(r={running:!1,initial:!0,items:[],listeners:{complete:[],progress:[]}},n.set(t,r)),r}listen(t,n,r){this._getAnims(t).listeners[n].push(r)}add(t,n){!n||!n.length||this._getAnims(t).items.push(...n)}has(t){return this._getAnims(t).items.length>0}start(t){const n=this._charts.get(t);n&&(n.running=!0,n.start=Date.now(),n.duration=n.items.reduce((r,s)=>Math.max(r,s._duration),0),this._refresh())}running(t){if(!this._running)return!1;const n=this._charts.get(t);return!(!n||!n.running||!n.items.length)}stop(t){const n=this._charts.get(t);if(!n||!n.items.length)return;const r=n.items;let s=r.length-1;for(;s>=0;--s)r[s].cancel();n.items=[],this._notify(t,n,Date.now(),"complete")}remove(t){return this._charts.delete(t)}}var tr=new HN;const F0="transparent",UN={boolean(e,t,n){return n>.5?t:e},color(e,t,n){const r=y0(e||F0),s=r.valid&&y0(t||F0);return s&&s.valid?s.mix(r,n).hexString():t},number(e,t,n){return e+(t-e)*n}};class BN{constructor(t,n,r,s){const o=n[r];s=ma([t.to,s,o,t.from]);const i=ma([t.from,o,s]);this._active=!0,this._fn=t.fn||UN[t.type||typeof i],this._easing=Mo[t.easing]||Mo.linear,this._start=Math.floor(Date.now()+(t.delay||0)),this._duration=this._total=Math.floor(t.duration),this._loop=!!t.loop,this._target=n,this._prop=r,this._from=i,this._to=s,this._promises=void 0}active(){return this._active}update(t,n,r){if(this._active){this._notify(!1);const s=this._target[this._prop],o=r-this._start,i=this._duration-o;this._start=r,this._duration=Math.floor(Math.max(i,t.duration)),this._total+=o,this._loop=!!t.loop,this._to=ma([t.to,n,s,t.from]),this._from=ma([t.from,s,n])}}cancel(){this._active&&(this.tick(Date.now()),this._active=!1,this._notify(!1))}tick(t){const n=t-this._start,r=this._duration,s=this._prop,o=this._from,i=this._loop,a=this._to;let l;if(this._active=o!==a&&(i||n1?2-l:l,l=this._easing(Math.min(1,Math.max(0,l))),this._target[s]=this._fn(o,a,l)}wait(){const t=this._promises||(this._promises=[]);return new Promise((n,r)=>{t.push({res:n,rej:r})})}_notify(t){const n=t?"res":"rej",r=this._promises||[];for(let s=0;s{const o=t[s];if(!Je(o))return;const i={};for(const a of n)i[a]=o[a];(Tt(o.properties)&&o.properties||[s]).forEach(a=>{(a===s||!r.has(a))&&r.set(a,i)})})}_animateOptions(t,n){const r=n.options,s=WN(t,r);if(!s)return[];const o=this._createAnimations(s,r);return r.$shared&&jN(t.options.$animations,r).then(()=>{t.options=r},()=>{}),o}_createAnimations(t,n){const r=this._properties,s=[],o=t.$animations||(t.$animations={}),i=Object.keys(n),a=Date.now();let l;for(l=i.length-1;l>=0;--l){const c=i[l];if(c.charAt(0)==="$")continue;if(c==="options"){s.push(...this._animateOptions(t,n));continue}const d=n[c];let u=o[c];const f=r.get(c);if(u)if(f&&u.active()){u.update(f,d,a);continue}else u.cancel();if(!f||!f.duration){t[c]=d;continue}o[c]=u=new BN(f,t,c,d),s.push(u)}return s}update(t,n){if(this._properties.size===0){Object.assign(t,n);return}const r=this._createAnimations(t,n);if(r.length)return tr.add(this._chart,r),!0}}function jN(e,t){const n=[],r=Object.keys(t);for(let s=0;s0||!n&&o<0)return s.index}return null}function j0(e,t){const{chart:n,_cachedMeta:r}=e,s=n._stacks||(n._stacks={}),{iScale:o,vScale:i,index:a}=r,l=o.axis,c=i.axis,d=KN(o,i,r),u=t.length;let f;for(let g=0;gn[r].axis===t).shift()}function QN(e,t){return Ms(e,{active:!1,dataset:void 0,datasetIndex:t,index:t,mode:"default",type:"dataset"})}function JN(e,t,n){return Ms(e,{active:!1,dataIndex:t,parsed:void 0,raw:void 0,element:n,index:t,mode:"default",type:"data"})}function Oo(e,t){const n=e.controller.index,r=e.vScale&&e.vScale.axis;if(r){t=t||e._parsed;for(const s of t){const o=s._stacks;if(!o||o[r]===void 0||o[r][n]===void 0)return;delete o[r][n],o[r]._visualValues!==void 0&&o[r]._visualValues[n]!==void 0&&delete o[r]._visualValues[n]}}}const hd=e=>e==="reset"||e==="none",W0=(e,t)=>t?e:Object.assign({},e),RN=(e,t,n)=>e&&!t.hidden&&t._stacked&&{keys:H0(n,!0),values:null};class Os{constructor(t,n){this.chart=t,this._ctx=t.ctx,this.index=n,this._cachedDataOpts={},this._cachedMeta=this.getMeta(),this._type=this._cachedMeta.type,this.options=void 0,this._parsing=!1,this._data=void 0,this._objectData=void 0,this._sharedOptions=void 0,this._drawStart=void 0,this._drawCount=void 0,this.enableOptionSharing=!1,this.supportsDecimation=!1,this.$context=void 0,this._syncList=[],this.datasetElementType=new.target.datasetElementType,this.dataElementType=new.target.dataElementType,this.initialize()}initialize(){const t=this._cachedMeta;this.configure(),this.linkScales(),t._stacked=ud(t.vScale,t),this.addElements(),this.options.fill&&!this.chart.isPluginEnabled("filler")&&console.warn("Tried to use the 'fill' option without the 'Filler' plugin enabled. Please import and register the 'Filler' plugin and make sure it is not disabled in the options")}updateIndex(t){this.index!==t&&Oo(this._cachedMeta),this.index=t}linkScales(){const t=this.chart,n=this._cachedMeta,r=this.getDataset(),s=(u,f,g,m)=>u==="x"?f:u==="r"?m:g,o=n.xAxisID=Re(r.xAxisID,fd(t,"x")),i=n.yAxisID=Re(r.yAxisID,fd(t,"y")),a=n.rAxisID=Re(r.rAxisID,fd(t,"r")),l=n.indexAxis,c=n.iAxisID=s(l,o,i,a),d=n.vAxisID=s(l,i,o,a);n.xScale=this.getScaleForId(o),n.yScale=this.getScaleForId(i),n.rScale=this.getScaleForId(a),n.iScale=this.getScaleForId(c),n.vScale=this.getScaleForId(d)}getDataset(){return this.chart.data.datasets[this.index]}getMeta(){return this.chart.getDatasetMeta(this.index)}getScaleForId(t){return this.chart.scales[t]}_getOtherScale(t){const n=this._cachedMeta;return t===n.iScale?n.vScale:n.iScale}reset(){this._update("reset")}_destroy(){const t=this._cachedMeta;this._data&&u0(this._data,this),t._stacked&&Oo(t)}_dataCheck(){const t=this.getDataset(),n=t.data||(t.data=[]),r=this._data;if(Je(n)){const s=this._cachedMeta;this._data=XN(n,s)}else if(r!==n){if(r){u0(r,this);const s=this._cachedMeta;Oo(s),s._parsed=[]}n&&Object.isExtensible(n)&&RM(n,this),this._syncList=[],this._data=n}}addElements(){const t=this._cachedMeta;this._dataCheck(),this.datasetElementType&&(t.dataset=new this.datasetElementType)}buildOrUpdateElements(t){const n=this._cachedMeta,r=this.getDataset();let s=!1;this._dataCheck();const o=n._stacked;n._stacked=ud(n.vScale,n),n.stack!==r.stack&&(s=!0,Oo(n),n.stack=r.stack),this._resyncElements(t),(s||o!==n._stacked)&&(j0(this,n._parsed),n._stacked=ud(n.vScale,n))}configure(){const t=this.chart.config,n=t.datasetScopeKeys(this._type),r=t.getOptionScopes(this.getDataset(),n,!0);this.options=t.createResolver(r,this.getContext()),this._parsing=this.options.parsing,this._cachedDataOpts={}}parse(t,n){const{_cachedMeta:r,_data:s}=this,{iScale:o,_stacked:i}=r,a=o.axis;let l=t===0&&n===s.length?!0:r._sorted,c=t>0&&r._parsed[t-1],d,u,f;if(this._parsing===!1)r._parsed=s,r._sorted=!0,f=s;else{Tt(s[t])?f=this.parseArrayData(r,s,t,n):Je(s[t])?f=this.parseObjectData(r,s,t,n):f=this.parsePrimitiveData(r,s,t,n);const g=()=>u[a]===null||c&&u[a]p||u=0;--f)if(!m()){this.updateRangeFromParsed(c,t,g,l);break}}return c}getAllParsedValues(t){const n=this._cachedMeta._parsed,r=[];let s,o,i;for(s=0,o=n.length;s=0&&tthis.getContext(r,s,n),p=c.resolveNamedOptions(f,g,m,u);return p.$shared&&(p.$shared=l,o[i]=Object.freeze(W0(p,l))),p}_resolveAnimations(t,n,r){const s=this.chart,o=this._cachedDataOpts,i=`animation-${n}`,a=o[i];if(a)return a;let l;if(s.options.animation!==!1){const d=this.chart.config,u=d.datasetAnimationScopeKeys(this._type,n),f=d.getOptionScopes(this.getDataset(),u);l=d.createResolver(f,this.getContext(t,r,n))}const c=new z0(s,l&&l.animations);return l&&l._cacheable&&(o[i]=Object.freeze(c)),c}getSharedOptions(t){if(t.$shared)return this._sharedOptions||(this._sharedOptions=Object.assign({},t))}includeOptions(t,n){return!n||hd(t)||this.chart._animationsDisabled}_getSharedOptions(t,n){const r=this.resolveDataElementOptions(t,n),s=this._sharedOptions,o=this.getSharedOptions(r),i=this.includeOptions(n,o)||o!==s;return this.updateSharedOptions(o,n,r),{sharedOptions:o,includeOptions:i}}updateElement(t,n,r,s){hd(s)?Object.assign(t,r):this._resolveAnimations(n,s).update(t,r)}updateSharedOptions(t,n,r){t&&!hd(n)&&this._resolveAnimations(void 0,n).update(t,r)}_setStyle(t,n,r,s){t.active=s;const o=this.getStyle(n,s);this._resolveAnimations(n,r,s).update(t,{options:!s&&this.getSharedOptions(o)||o})}removeHoverStyle(t,n,r){this._setStyle(t,r,"active",!1)}setHoverStyle(t,n,r){this._setStyle(t,r,"active",!0)}_removeDatasetHoverStyle(){const t=this._cachedMeta.dataset;t&&this._setStyle(t,void 0,"active",!1)}_setDatasetHoverStyle(){const t=this._cachedMeta.dataset;t&&this._setStyle(t,void 0,"active",!0)}_resyncElements(t){const n=this._data,r=this._cachedMeta.data;for(const[a,l,c]of this._syncList)this[a](l,c);this._syncList=[];const s=r.length,o=n.length,i=Math.min(o,s);i&&this.parse(0,i),o>s?this._insertElements(s,o-s,t):o{for(c.length+=n,a=c.length-1;a>=i;a--)c[a]=c[a-n]};for(l(o),a=t;as-o))}return e._cache.$bar}function tT(e){const t=e.iScale,n=eT(t,e.type);let r=t._length,s,o,i,a;const l=()=>{i===32767||i===-32768||(Eo(a)&&(r=Math.min(r,Math.abs(i-a)||r)),a=i)};for(s=0,o=n.length;s0?s[e-1]:null,a=eMath.abs(a)&&(l=a,c=i),t[n.axis]=c,t._custom={barStart:l,barEnd:c,start:s,end:o,min:i,max:a}}function G0(e,t,n,r){return Tt(e)?sT(e,t,n,r):t[n.axis]=n.parse(e,r),t}function Y0(e,t,n,r){const s=e.iScale,o=e.vScale,i=s.getLabels(),a=s===o,l=[];let c,d,u,f;for(c=n,d=n+r;c=n?1:-1)}function iT(e){let t,n,r,s,o;return e.horizontal?(t=e.base>e.x,n="left",r="right"):(t=e.based.controller.options.grouped),o=r.options.stacked,i=[],a=this._cachedMeta.controller.getParsed(n),l=a&&a[r.axis],c=d=>{const u=d._parsed.find(g=>g[r.axis]===l),f=u&&u[d.vScale.axis];if(dt(f)||isNaN(f))return!0};for(const d of s)if(!(n!==void 0&&c(d))&&((o===!1||i.indexOf(d.stack)===-1||o===void 0&&d.stack===void 0)&&i.push(d.stack),d.index===t))break;return i.length||i.push(void 0),i}_getStackCount(t){return this._getStacks(void 0,t).length}_getAxisCount(){return this._getAxis().length}getFirstScaleIdForIndexAxis(){const t=this.chart.scales,n=this.chart.options.indexAxis;return Object.keys(t).filter(r=>t[r].axis===n).shift()}_getAxis(){const t={},n=this.getFirstScaleIdForIndexAxis();for(const r of this.chart.data.datasets)t[Re(this.chart.options.indexAxis==="x"?r.xAxisID:r.yAxisID,n)]=!0;return Object.keys(t)}_getStackIndex(t,n,r){const s=this._getStacks(t,r),o=n!==void 0?s.indexOf(n):-1;return o===-1?s.length-1:o}_getRuler(){const t=this.options,n=this._cachedMeta,r=n.iScale,s=[];let o,i;for(o=0,i=n.data.length;oha(x,a,l,!0)?1:Math.max(M,M*n,C,C*n),m=(x,M,C)=>ha(x,a,l,!0)?-1:Math.min(M,M*n,C,C*n),p=g(0,c,u),y=g(Ot,d,f),b=m(ut,c,u),_=m(ut+Ot,d,f);r=(p-b)/2,s=(y-_)/2,o=-(p+b)/2,i=-(y+_)/2}return{ratioX:r,ratioY:s,offsetX:o,offsetY:i}}class Ao extends Os{constructor(t,n){super(t,n),this.enableOptionSharing=!0,this.innerRadius=void 0,this.outerRadius=void 0,this.offsetX=void 0,this.offsetY=void 0}linkScales(){}parse(t,n){const r=this.getDataset().data,s=this._cachedMeta;if(this._parsing===!1)s._parsed=r;else{let o=l=>+r[l];if(Je(r[t])){const{key:l="value"}=this._parsing;o=c=>+Lr(r[c],l)}let i,a;for(i=t,a=t+n;i0&&!isNaN(t)?Mt*(Math.abs(t)/n):0}getLabelAndValue(t){const n=this._cachedMeta,r=this.chart,s=r.data.labels||[],o=ed(n._parsed[t],r.options.locale);return{label:s[t]||"",value:o}}getMaxBorderWidth(t){let n=0;const r=this.chart;let s,o,i,a,l;if(!t){for(s=0,o=r.data.datasets.length;st!=="spacing",_indexable:t=>t!=="spacing"&&!t.startsWith("borderDash")&&!t.startsWith("hoverBorderDash")}),Pe(Ao,"overrides",{aspectRatio:1,plugins:{legend:{labels:{generateLabels(t){const n=t.data,{labels:{pointStyle:r,textAlign:s,color:o,useBorderRadius:i,borderRadius:a}}=t.legend.options;return n.labels.length&&n.datasets.length?n.labels.map((l,c)=>{const u=t.getDatasetMeta(0).controller.getStyle(c);return{text:l,fillStyle:u.backgroundColor,fontColor:o,hidden:!t.getDataVisibility(c),lineDash:u.borderDash,lineDashOffset:u.borderDashOffset,lineJoin:u.borderJoinStyle,lineWidth:u.borderWidth,strokeStyle:u.borderColor,textAlign:s,pointStyle:r,borderRadius:i&&(a||u.borderRadius),index:c}}):[]}},onClick(t,n,r){r.chart.toggleDataVisibility(n.index),r.chart.update()}}}});function Ur(){throw new Error("This method is not implemented: Check that a complete date adapter is provided.")}class pd{constructor(t){Pe(this,"options");this.options=t||{}}static override(t){Object.assign(pd.prototype,t)}init(){}formats(){return Ur()}parse(){return Ur()}format(){return Ur()}add(){return Ur()}diff(){return Ur()}startOf(){return Ur()}endOf(){return Ur()}}var uT={_date:pd};function fT(e,t,n,r){const{controller:s,data:o,_sorted:i}=e,a=s._cachedMeta.iScale,l=e.dataset&&e.dataset.options?e.dataset.options.spanGaps:null;if(a&&t===a.axis&&t!=="r"&&i&&o.length){const c=a._reversePixels?QM:Qc;if(r){if(s._sharedOptions){const d=o[0],u=typeof d.getRange=="function"&&d.getRange(t);if(u){const f=c(o,t,n-u),g=c(o,t,n+u);return{lo:f.lo,hi:g.hi}}}}else{const d=c(o,t,n);if(l){const{vScale:u}=s._cachedMeta,{_parsed:f}=e,g=f.slice(0,d.lo+1).reverse().findIndex(p=>!dt(p[u.axis]));d.lo-=Math.max(0,g);const m=f.slice(d.hi).findIndex(p=>!dt(p[u.axis]));d.hi+=Math.max(0,m)}return d}}return{lo:0,hi:o.length-1}}function wa(e,t,n,r,s){const o=e.getSortedVisibleDatasetMetas(),i=n[t];for(let a=0,l=o.length;a{l[i]&&l[i](t[n],s)&&(o.push({element:l,datasetIndex:c,index:d}),a=a||l.inRange(t.x,t.y,s))}),r&&!a?[]:o}var mT={modes:{index(e,t,n,r){const s=Hr(t,e),o=n.axis||"x",i=n.includeInvisible||!1,a=n.intersect?md(e,s,o,r,i):bd(e,s,o,!1,r,i),l=[];return a.length?(e.getSortedVisibleDatasetMetas().forEach(c=>{const d=a[0].index,u=c.data[d];u&&!u.skip&&l.push({element:u,datasetIndex:c.index,index:d})}),l):[]},dataset(e,t,n,r){const s=Hr(t,e),o=n.axis||"xy",i=n.includeInvisible||!1;let a=n.intersect?md(e,s,o,r,i):bd(e,s,o,!1,r,i);if(a.length>0){const l=a[0].datasetIndex,c=e.getDatasetMeta(l).data;a=[];for(let d=0;dn.pos===t)}function Q0(e,t){return e.filter(n=>Z0.indexOf(n.pos)===-1&&n.box.axis===t)}function Io(e,t){return e.sort((n,r)=>{const s=t?r:n,o=t?n:r;return s.weight===o.weight?s.index-o.index:s.weight-o.weight})}function bT(e){const t=[];let n,r,s,o,i,a;for(n=0,r=(e||[]).length;nc.box.fullSize),!0),r=Io(Do(t,"left"),!0),s=Io(Do(t,"right")),o=Io(Do(t,"top"),!0),i=Io(Do(t,"bottom")),a=Q0(t,"x"),l=Q0(t,"y");return{fullSize:n,leftAndTop:r.concat(o),rightAndBottom:s.concat(l).concat(i).concat(a),chartArea:Do(t,"chartArea"),vertical:r.concat(s).concat(l),horizontal:o.concat(i).concat(a)}}function J0(e,t,n,r){return Math.max(e[n],t[n])+Math.max(e[r],t[r])}function R0(e,t){e.top=Math.max(e.top,t.top),e.left=Math.max(e.left,t.left),e.bottom=Math.max(e.bottom,t.bottom),e.right=Math.max(e.right,t.right)}function wT(e,t,n,r){const{pos:s,box:o}=n,i=e.maxPadding;if(!Je(s)){n.size&&(e[s]-=n.size);const u=r[n.stack]||{size:0,count:1};u.size=Math.max(u.size,n.horizontal?o.height:o.width),n.size=u.size/u.count,e[s]+=n.size}o.getPadding&&R0(i,o.getPadding());const a=Math.max(0,t.outerWidth-J0(i,e,"left","right")),l=Math.max(0,t.outerHeight-J0(i,e,"top","bottom")),c=a!==e.w,d=l!==e.h;return e.w=a,e.h=l,n.horizontal?{same:c,other:d}:{same:d,other:c}}function kT(e){const t=e.maxPadding;function n(r){const s=Math.max(t[r]-e[r],0);return e[r]+=s,s}e.y+=n("top"),e.x+=n("left"),n("right"),n("bottom")}function CT(e,t){const n=t.maxPadding;function r(s){const o={left:0,top:0,right:0,bottom:0};return s.forEach(i=>{o[i]=Math.max(t[i],n[i])}),o}return r(e?["left","right"]:["top","bottom"])}function Lo(e,t,n,r){const s=[];let o,i,a,l,c,d;for(o=0,i=e.length,c=0;o{typeof p.beforeLayout=="function"&&p.beforeLayout()});const d=l.reduce((p,y)=>y.box.options&&y.box.options.display===!1?p:p+1,0)||1,u=Object.freeze({outerWidth:t,outerHeight:n,padding:s,availableWidth:o,availableHeight:i,vBoxMaxWidth:o/2/d,hBoxMaxHeight:i/2}),f=Object.assign({},s);R0(f,vn(r));const g=Object.assign({maxPadding:f,w:o,h:i,x:s.left,y:s.top},s),m=vT(l.concat(c),u);Lo(a.fullSize,g,u,m),Lo(l,g,u,m),Lo(c,g,u,m)&&Lo(l,g,u,m),kT(g),em(a.leftAndTop,g,u,m),g.x+=g.w,g.y+=g.h,em(a.rightAndBottom,g,u,m),e.chartArea={left:g.left,top:g.top,right:g.left+g.w,bottom:g.top+g.h,height:g.h,width:g.w},lt(a.chartArea,p=>{const y=p.box;Object.assign(y,e.chartArea),y.update(g.w,g.h,{left:0,top:0,right:0,bottom:0})})}};class tm{acquireContext(t,n){}releaseContext(t){return!1}addEventListener(t,n,r){}removeEventListener(t,n,r){}getDevicePixelRatio(){return 1}getMaximumSize(t,n,r,s){return n=Math.max(0,n||t.width),r=r||t.height,{width:n,height:Math.max(0,s?Math.floor(n/s):r)}}isAttached(t){return!0}updateConfig(t){}}class ST extends tm{acquireContext(t){return t&&t.getContext&&t.getContext("2d")||null}updateConfig(t){t.options.animation=!1}}const Ca="$chartjs",_T={touchstart:"mousedown",touchmove:"mousemove",touchend:"mouseup",pointerenter:"mouseenter",pointerdown:"mousedown",pointermove:"mousemove",pointerup:"mouseup",pointerleave:"mouseout",pointerout:"mouseout"},nm=e=>e===null||e==="";function ET(e,t){const n=e.style,r=e.getAttribute("height"),s=e.getAttribute("width");if(e[Ca]={initial:{height:r,width:s,style:{display:n.display,height:n.height,width:n.width}}},n.display=n.display||"block",n.boxSizing=n.boxSizing||"border-box",nm(s)){const o=L0(e,"width");o!==void 0&&(e.width=o)}if(nm(r))if(e.style.height==="")e.height=e.width/(t||2);else{const o=L0(e,"height");o!==void 0&&(e.height=o)}return e}const rm=PN?{passive:!0}:!1;function MT(e,t,n){e&&e.addEventListener(t,n,rm)}function NT(e,t,n){e&&e.canvas&&e.canvas.removeEventListener(t,n,rm)}function TT(e,t){const n=_T[e.type]||e.type,{x:r,y:s}=Hr(e,t);return{type:n,chart:t,native:e,x:r!==void 0?r:null,y:s!==void 0?s:null}}function Sa(e,t){for(const n of e)if(n===t||n.contains(t))return!0}function OT(e,t,n){const r=e.canvas,s=new MutationObserver(o=>{let i=!1;for(const a of o)i=i||Sa(a.addedNodes,r),i=i&&!Sa(a.removedNodes,r);i&&n()});return s.observe(document,{childList:!0,subtree:!0}),s}function AT(e,t,n){const r=e.canvas,s=new MutationObserver(o=>{let i=!1;for(const a of o)i=i||Sa(a.removedNodes,r),i=i&&!Sa(a.addedNodes,r);i&&n()});return s.observe(document,{childList:!0,subtree:!0}),s}const Po=new Map;let sm=0;function om(){const e=window.devicePixelRatio;e!==sm&&(sm=e,Po.forEach((t,n)=>{n.currentDevicePixelRatio!==e&&t()}))}function DT(e,t){Po.size||window.addEventListener("resize",om),Po.set(e,t)}function IT(e){Po.delete(e),Po.size||window.removeEventListener("resize",om)}function LT(e,t,n){const r=e.canvas,s=r&&dd(r);if(!s)return;const o=g0((a,l)=>{const c=s.clientWidth;n(a,l),c{const l=a[0],c=l.contentRect.width,d=l.contentRect.height;c===0&&d===0||o(c,d)});return i.observe(s),DT(e,o),i}function yd(e,t,n){n&&n.disconnect(),t==="resize"&&IT(e)}function PT(e,t,n){const r=e.canvas,s=g0(o=>{e.ctx!==null&&n(TT(o,e))},e);return MT(r,t,s),s}class $T extends tm{acquireContext(t,n){const r=t&&t.getContext&&t.getContext("2d");return r&&r.canvas===t?(ET(t,n),r):null}releaseContext(t){const n=t.canvas;if(!n[Ca])return!1;const r=n[Ca].initial;["height","width"].forEach(o=>{const i=r[o];dt(i)?n.removeAttribute(o):n.setAttribute(o,i)});const s=r.style||{};return Object.keys(s).forEach(o=>{n.style[o]=s[o]}),n.width=n.width,delete n[Ca],!0}addEventListener(t,n,r){this.removeEventListener(t,n);const s=t.$proxies||(t.$proxies={}),i={attach:OT,detach:AT,resize:LT}[n]||PT;s[n]=i(t,n,r)}removeEventListener(t,n){const r=t.$proxies||(t.$proxies={}),s=r[n];if(!s)return;({attach:yd,detach:yd,resize:yd}[n]||NT)(t,n,s),r[n]=void 0}getDevicePixelRatio(){return window.devicePixelRatio}getMaximumSize(t,n,r,s){return LN(t,n,r,s)}isAttached(t){const n=t&&dd(t);return!!(n&&n.isConnected)}}function FT(e){return!cd()||typeof OffscreenCanvas<"u"&&e instanceof OffscreenCanvas?ST:$T}let Br=(Va=class{constructor(){Pe(this,"x");Pe(this,"y");Pe(this,"active",!1);Pe(this,"options");Pe(this,"$animations")}tooltipPosition(t){const{x:n,y:r}=this.getProps(["x","y"],t);return{x:n,y:r}}hasValue(){return fa(this.x)&&fa(this.y)}getProps(t,n){const r=this.$animations;if(!n||!r)return this;const s={};return t.forEach(o=>{s[o]=r[o]&&r[o].active()?r[o]._to:this[o]}),s}},Pe(Va,"defaults",{}),Pe(Va,"defaultRoutes"),Va);function zT(e,t){const n=e.options.ticks,r=VT(e),s=Math.min(n.maxTicksLimit||r,r),o=n.major.enabled?UT(t):[],i=o.length,a=o[0],l=o[i-1],c=[];if(i>s)return BT(t,c,o,i/s),c;const d=HT(o,t,s);if(i>0){let u,f;const g=i>1?Math.round((l-a)/(i-1)):null;for(_a(t,c,d,dt(g)?0:a-g,a),u=0,f=i-1;us)return l}return Math.max(s,1)}function UT(e){const t=[];let n,r;for(n=0,r=e.length;ne==="left"?"right":e==="right"?"left":e,im=(e,t,n)=>t==="top"||t==="left"?e[t]+n:e[t]-n,am=(e,t)=>Math.min(t||e,e);function lm(e,t){const n=[],r=e.length/t,s=e.length;let o=0;for(;oi+a)))return l}function YT(e,t){lt(e,n=>{const r=n.gc,s=r.length/2;let o;if(s>t){for(o=0;or?r:n,r=s&&n>r?n:r,{min:Gn(n,Gn(r,n)),max:Gn(r,Gn(n,r))}}getPadding(){return{left:this.paddingLeft||0,top:this.paddingTop||0,right:this.paddingRight||0,bottom:this.paddingBottom||0}}getTicks(){return this.ticks}getLabels(){const t=this.chart.data;return this.options.labels||(this.isHorizontal()?t.xLabels:t.yLabels)||t.labels||[]}getLabelItems(t=this.chart.chartArea){return this._labelItems||(this._labelItems=this._computeLabelItems(t))}beforeLayout(){this._cache={},this._dataLimitsCached=!1}beforeUpdate(){mt(this.options.beforeUpdate,[this])}update(t,n,r){const{beginAtZero:s,grace:o,ticks:i}=this.options,a=i.sampleSize;this.beforeUpdate(),this.maxWidth=t,this.maxHeight=n,this._margins=r=Object.assign({left:0,right:0,top:0,bottom:0},r),this.ticks=null,this._labelSizes=null,this._gridLineItems=null,this._labelItems=null,this.beforeSetDimensions(),this.setDimensions(),this.afterSetDimensions(),this._maxLength=this.isHorizontal()?this.width+r.left+r.right:this.height+r.top+r.bottom,this._dataLimitsCached||(this.beforeDataLimits(),this.determineDataLimits(),this.afterDataLimits(),this._range=vN(this,o,s),this._dataLimitsCached=!0),this.beforeBuildTicks(),this.ticks=this.buildTicks()||[],this.afterBuildTicks();const l=a=o||r<=1||!this.isHorizontal()){this.labelRotation=s;return}const d=this._getLabelSizes(),u=d.widest.width,f=d.highest.height,g=sn(this.chart.width-u,0,this.maxWidth);a=t.offset?this.maxWidth/r:g/(r-1),u+6>a&&(a=g/(r-(t.offset?.5:1)),l=this.maxHeight-$o(t.grid)-n.padding-cm(t.title,this.chart.options.font),c=Math.sqrt(u*u+f*f),i=KM(Math.min(Math.asin(sn((d.highest.height+6)/a,-1,1)),Math.asin(sn(l/c,-1,1))-Math.asin(sn(f/c,-1,1)))),i=Math.max(s,Math.min(o,i))),this.labelRotation=i}afterCalculateLabelRotation(){mt(this.options.afterCalculateLabelRotation,[this])}afterAutoSkip(){}beforeFit(){mt(this.options.beforeFit,[this])}fit(){const t={width:0,height:0},{chart:n,options:{ticks:r,title:s,grid:o}}=this,i=this._isVisible(),a=this.isHorizontal();if(i){const l=cm(s,n.options.font);if(a?(t.width=this.maxWidth,t.height=$o(o)+l):(t.height=this.maxHeight,t.width=$o(o)+l),r.display&&this.ticks.length){const{first:c,last:d,widest:u,highest:f}=this._getLabelSizes(),g=r.padding*2,m=er(this.labelRotation),p=Math.cos(m),y=Math.sin(m);if(a){const b=r.mirror?0:y*u.width+p*f.height;t.height=Math.min(this.maxHeight,t.height+b+g)}else{const b=r.mirror?0:p*u.width+y*f.height;t.width=Math.min(this.maxWidth,t.width+b+g)}this._calculatePadding(c,d,y,p)}}this._handleMargins(),a?(this.width=this._length=n.width-this._margins.left-this._margins.right,this.height=t.height):(this.width=t.width,this.height=this._length=n.height-this._margins.top-this._margins.bottom)}_calculatePadding(t,n,r,s){const{ticks:{align:o,padding:i},position:a}=this.options,l=this.labelRotation!==0,c=a!=="top"&&this.axis==="x";if(this.isHorizontal()){const d=this.getPixelForTick(0)-this.left,u=this.right-this.getPixelForTick(this.ticks.length-1);let f=0,g=0;l?c?(f=s*t.width,g=r*n.height):(f=r*t.height,g=s*n.width):o==="start"?g=n.width:o==="end"?f=t.width:o!=="inner"&&(f=t.width/2,g=n.width/2),this.paddingLeft=Math.max((f-d+i)*this.width/(this.width-d),0),this.paddingRight=Math.max((g-u+i)*this.width/(this.width-u),0)}else{let d=n.height/2,u=t.height/2;o==="start"?(d=0,u=t.height):o==="end"&&(d=n.height,u=0),this.paddingTop=d+i,this.paddingBottom=u+i}}_handleMargins(){this._margins&&(this._margins.left=Math.max(this.paddingLeft,this._margins.left),this._margins.top=Math.max(this.paddingTop,this._margins.top),this._margins.right=Math.max(this.paddingRight,this._margins.right),this._margins.bottom=Math.max(this.paddingBottom,this._margins.bottom))}afterFit(){mt(this.options.afterFit,[this])}isHorizontal(){const{axis:t,position:n}=this.options;return n==="top"||n==="bottom"||t==="x"}isFullSize(){return this.options.fullSize}_convertTicksToLabels(t){this.beforeTickToLabelConversion(),this.generateTickLabels(t);let n,r;for(n=0,r=t.length;n({width:i[w]||0,height:a[w]||0});return{first:S(0),last:S(n-1),widest:S(k),highest:S(N),widths:i,heights:a}}getLabelForValue(t){return t}getPixelForValue(t,n){return NaN}getValueForPixel(t){}getPixelForTick(t){const n=this.ticks;return t<0||t>n.length-1?null:this.getPixelForValue(n[t].value)}getPixelForDecimal(t){this._reversePixels&&(t=1-t);const n=this._startPixel+t*this._length;return ZM(this._alignToPixels?zr(this.chart,n,0):n)}getDecimalForPixel(t){const n=(t-this._startPixel)/this._length;return this._reversePixels?1-n:n}getBasePixel(){return this.getPixelForValue(this.getBaseValue())}getBaseValue(){const{min:t,max:n}=this;return t<0&&n<0?n:t>0&&n>0?t:0}getContext(t){const n=this.ticks||[];if(t>=0&&ta*s?a/r:l/s:l*s0}_computeGridLineItems(t){const n=this.axis,r=this.chart,s=this.options,{grid:o,position:i,border:a}=s,l=o.offset,c=this.isHorizontal(),u=this.ticks.length+(l?1:0),f=$o(o),g=[],m=a.setContext(this.getContext()),p=m.display?m.width:0,y=p/2,b=function(O){return zr(r,O,p)};let _,x,M,C,k,N,S,w,E,D,P,L;if(i==="top")_=b(this.bottom),N=this.bottom-f,w=_-y,D=b(t.top)+y,L=t.bottom;else if(i==="bottom")_=b(this.top),D=t.top,L=b(t.bottom)-y,N=_+y,w=this.top+f;else if(i==="left")_=b(this.right),k=this.right-f,S=_-y,E=b(t.left)+y,P=t.right;else if(i==="right")_=b(this.left),E=t.left,P=b(t.right)-y,k=_+y,S=this.left+f;else if(n==="x"){if(i==="center")_=b((t.top+t.bottom)/2+.5);else if(Je(i)){const O=Object.keys(i)[0],T=i[O];_=b(this.chart.scales[O].getPixelForValue(T))}D=t.top,L=t.bottom,N=_+y,w=N+f}else if(n==="y"){if(i==="center")_=b((t.left+t.right)/2);else if(Je(i)){const O=Object.keys(i)[0],T=i[O];_=b(this.chart.scales[O].getPixelForValue(T))}k=_-y,S=k-f,E=t.left,P=t.right}const $=Re(s.ticks.maxTicksLimit,u),A=Math.max(1,Math.ceil(u/$));for(x=0;x0&&(X-=Y/2);break}G={left:X,top:V,width:Y+U.width,height:W+U.height,color:A.backdropColor}}y.push({label:M,font:w,textOffset:P,options:{rotation:p,color:T,strokeColor:z,strokeWidth:H,textAlign:j,textBaseline:L,translation:[C,k],backdrop:G}})}return y}_getXAxisLabelAlignment(){const{position:t,ticks:n}=this.options;if(-er(this.labelRotation))return t==="top"?"left":"right";let s="center";return n.align==="start"?s="left":n.align==="end"?s="right":n.align==="inner"&&(s="inner"),s}_getYAxisLabelAlignment(t){const{position:n,ticks:{crossAlign:r,mirror:s,padding:o}}=this.options,i=this._getLabelSizes(),a=t+o,l=i.widest.width;let c,d;return n==="left"?s?(d=this.right+o,r==="near"?c="left":r==="center"?(c="center",d+=l/2):(c="right",d+=l)):(d=this.right-a,r==="near"?c="right":r==="center"?(c="center",d-=l/2):(c="left",d=this.left)):n==="right"?s?(d=this.left+o,r==="near"?c="right":r==="center"?(c="center",d-=l/2):(c="left",d-=l)):(d=this.left+a,r==="near"?c="left":r==="center"?(c="center",d+=l/2):(c="right",d=this.right)):c="right",{textAlign:c,x:d}}_computeLabelArea(){if(this.options.ticks.mirror)return;const t=this.chart,n=this.options.position;if(n==="left"||n==="right")return{top:0,left:this.left,bottom:t.height,right:this.right};if(n==="top"||n==="bottom")return{top:this.top,left:0,bottom:this.bottom,right:t.width}}drawBackground(){const{ctx:t,options:{backgroundColor:n},left:r,top:s,width:o,height:i}=this;n&&(t.save(),t.fillStyle=n,t.fillRect(r,s,o,i),t.restore())}getLineWidthForValue(t){const n=this.options.grid;if(!this._isVisible()||!n.display)return 0;const s=this.ticks.findIndex(o=>o.value===t);return s>=0?n.setContext(this.getContext(s)).lineWidth:0}drawGrid(t){const n=this.options.grid,r=this.ctx,s=this._gridLineItems||(this._gridLineItems=this._computeGridLineItems(t));let o,i;const a=(l,c,d)=>{!d.width||!d.color||(r.save(),r.lineWidth=d.width,r.strokeStyle=d.color,r.setLineDash(d.borderDash||[]),r.lineDashOffset=d.borderDashOffset,r.beginPath(),r.moveTo(l.x,l.y),r.lineTo(c.x,c.y),r.stroke(),r.restore())};if(n.display)for(o=0,i=s.length;o{this.draw(o)}}]:[{z:r,draw:o=>{this.drawBackground(),this.drawGrid(o),this.drawTitle()}},{z:s,draw:()=>{this.drawBorder()}},{z:n,draw:o=>{this.drawLabels(o)}}]}getMatchingVisibleMetas(t){const n=this.chart.getSortedVisibleDatasetMetas(),r=this.axis+"AxisID",s=[];let o,i;for(o=0,i=n.length;o{const r=n.split("."),s=r.pop(),o=[e].concat(r).join("."),i=t[n].split("."),a=i.pop(),l=i.join(".");kt.route(o,s,l,a)})}function RT(e){return"id"in e&&"defaults"in e}class eO{constructor(){this.controllers=new Ea(Os,"datasets",!0),this.elements=new Ea(Br,"elements"),this.plugins=new Ea(Object,"plugins"),this.scales=new Ea(As,"scales"),this._typedRegistries=[this.controllers,this.scales,this.elements]}add(...t){this._each("register",t)}remove(...t){this._each("unregister",t)}addControllers(...t){this._each("register",t,this.controllers)}addElements(...t){this._each("register",t,this.elements)}addPlugins(...t){this._each("register",t,this.plugins)}addScales(...t){this._each("register",t,this.scales)}getController(t){return this._get(t,this.controllers,"controller")}getElement(t){return this._get(t,this.elements,"element")}getPlugin(t){return this._get(t,this.plugins,"plugin")}getScale(t){return this._get(t,this.scales,"scale")}removeControllers(...t){this._each("unregister",t,this.controllers)}removeElements(...t){this._each("unregister",t,this.elements)}removePlugins(...t){this._each("unregister",t,this.plugins)}removeScales(...t){this._each("unregister",t,this.scales)}_each(t,n,r){[...n].forEach(s=>{const o=r||this._getRegistryForType(s);r||o.isForType(s)||o===this.plugins&&s.id?this._exec(t,o,s):lt(s,i=>{const a=r||this._getRegistryForType(i);this._exec(t,a,i)})})}_exec(t,n,r){const s=qc(t);mt(r["before"+s],[],r),n[t](r),mt(r["after"+s],[],r)}_getRegistryForType(t){for(let n=0;no.filter(a=>!i.some(l=>a.plugin.id===l.plugin.id));this._notify(s(n,r),t,"stop"),this._notify(s(r,n),t,"start")}}function nO(e){const t={},n=[],r=Object.keys(Xn.plugins.items);for(let o=0;o1&&dm(e[0].toLowerCase());if(r)return r}throw new Error(`Cannot determine type of '${e}' axis. Please provide 'axis' or 'position' option.`)}function um(e,t,n){if(n[t+"AxisID"]===e)return{axis:t}}function cO(e,t){if(t.data&&t.data.datasets){const n=t.data.datasets.filter(r=>r.xAxisID===e||r.yAxisID===e);if(n.length)return um(e,"x",n[0])||um(e,"y",n[0])}return{}}function dO(e,t){const n=Fr[e.type]||{scales:{}},r=t.scales||{},s=vd(e.type,t),o=Object.create(null);return Object.keys(r).forEach(i=>{const a=r[i];if(!Je(a))return console.error(`Invalid scale configuration for scale: ${i}`);if(a._proxy)return console.warn(`Ignoring resolver passed as options for scale: ${i}`);const l=xd(i,a,cO(i,e),kt.scales[a.type]),c=aO(l,s),d=n.scales||{};o[i]=_o(Object.create(null),[{axis:l},a,d[l],d[c]])}),e.data.datasets.forEach(i=>{const a=i.type||e.type,l=i.indexAxis||vd(a,t),d=(Fr[a]||{}).scales||{};Object.keys(d).forEach(u=>{const f=iO(u,l),g=i[f+"AxisID"]||f;o[g]=o[g]||Object.create(null),_o(o[g],[{axis:f},r[g],d[u]])})}),Object.keys(o).forEach(i=>{const a=o[i];_o(a,[kt.scales[a.type],kt.scale])}),o}function fm(e){const t=e.options||(e.options={});t.plugins=Re(t.plugins,{}),t.scales=dO(e,t)}function hm(e){return e=e||{},e.datasets=e.datasets||[],e.labels=e.labels||[],e}function uO(e){return e=e||{},e.data=hm(e.data),fm(e),e}const gm=new Map,pm=new Set;function Ma(e,t){let n=gm.get(e);return n||(n=t(),gm.set(e,n),pm.add(n)),n}const Fo=(e,t,n)=>{const r=Lr(t,n);r!==void 0&&e.add(r)};class fO{constructor(t){this._config=uO(t),this._scopeCache=new Map,this._resolverCache=new Map}get platform(){return this._config.platform}get type(){return this._config.type}set type(t){this._config.type=t}get data(){return this._config.data}set data(t){this._config.data=hm(t)}get options(){return this._config.options}set options(t){this._config.options=t}get plugins(){return this._config.plugins}update(){const t=this._config;this.clearCache(),fm(t)}clearCache(){this._scopeCache.clear(),this._resolverCache.clear()}datasetScopeKeys(t){return Ma(t,()=>[[`datasets.${t}`,""]])}datasetAnimationScopeKeys(t,n){return Ma(`${t}.transition.${n}`,()=>[[`datasets.${t}.transitions.${n}`,`transitions.${n}`],[`datasets.${t}`,""]])}datasetElementScopeKeys(t,n){return Ma(`${t}-${n}`,()=>[[`datasets.${t}.elements.${n}`,`datasets.${t}`,`elements.${n}`,""]])}pluginScopeKeys(t){const n=t.id,r=this.type;return Ma(`${r}-plugin-${n}`,()=>[[`plugins.${n}`,...t.additionalOptionScopes||[]]])}_cachedScopes(t,n){const r=this._scopeCache;let s=r.get(t);return(!s||n)&&(s=new Map,r.set(t,s)),s}getOptionScopes(t,n,r){const{options:s,type:o}=this,i=this._cachedScopes(t,r),a=i.get(n);if(a)return a;const l=new Set;n.forEach(d=>{t&&(l.add(t),d.forEach(u=>Fo(l,t,u))),d.forEach(u=>Fo(l,s,u)),d.forEach(u=>Fo(l,Fr[o]||{},u)),d.forEach(u=>Fo(l,kt,u)),d.forEach(u=>Fo(l,td,u))});const c=Array.from(l);return c.length===0&&c.push(Object.create(null)),pm.has(n)&&i.set(n,c),c}chartOptionScopes(){const{options:t,type:n}=this;return[t,Fr[n]||{},kt.datasets[n]||{},{type:n},kt,td]}resolveNamedOptions(t,n,r,s=[""]){const o={$shared:!0},{resolver:i,subPrefixes:a}=mm(this._resolverCache,t,s);let l=i;if(gO(i,n)){o.$shared=!1,r=br(r)?r():r;const c=this.createResolver(t,r,a);l=Ns(i,r,c)}for(const c of n)o[c]=l[c];return o}createResolver(t,n,r=[""],s){const{resolver:o}=mm(this._resolverCache,t,r);return Je(n)?Ns(o,n,void 0,s):o}}function mm(e,t,n){let r=e.get(t);r||(r=new Map,e.set(t,r));const s=n.join();let o=r.get(s);return o||(o={resolver:id(t,n),subPrefixes:n.filter(a=>!a.toLowerCase().includes("hover"))},r.set(s,o)),o}const hO=e=>Je(e)&&Object.getOwnPropertyNames(e).some(t=>br(e[t]));function gO(e,t){const{isScriptable:n,isIndexable:r}=M0(e);for(const s of t){const o=n(s),i=r(s),a=(i||o)&&e[s];if(o&&(br(a)||hO(a))||i&&Tt(a))return!0}return!1}var pO="4.5.1";const mO=["top","bottom","left","right","chartArea"];function bm(e,t){return e==="top"||e==="bottom"||mO.indexOf(e)===-1&&t==="x"}function ym(e,t){return function(n,r){return n[e]===r[e]?n[t]-r[t]:n[e]-r[e]}}function vm(e){const t=e.chart,n=t.options.animation;t.notifyPlugins("afterRender"),mt(n&&n.onComplete,[e],t)}function bO(e){const t=e.chart,n=t.options.animation;mt(n&&n.onProgress,[e],t)}function xm(e){return cd()&&typeof e=="string"?e=document.getElementById(e):e&&e.length&&(e=e[0]),e&&e.canvas&&(e=e.canvas),e}const Na={},wm=e=>{const t=xm(e);return Object.values(Na).filter(n=>n.canvas===t).pop()};function yO(e,t,n){const r=Object.keys(e);for(const s of r){const o=+s;if(o>=t){const i=e[s];delete e[s],(n>0||o>t)&&(e[o+n]=i)}}}function vO(e,t,n,r){return!n||e.type==="mouseout"?null:r?t:e}let zo=(wr=class{static register(...t){Xn.add(...t),km()}static unregister(...t){Xn.remove(...t),km()}constructor(t,n){const r=this.config=new fO(n),s=xm(t),o=wm(s);if(o)throw new Error("Canvas is already in use. Chart with ID '"+o.id+"' must be destroyed before the canvas with ID '"+o.canvas.id+"' can be reused.");const i=r.createResolver(r.chartOptionScopes(),this.getContext());this.platform=new(r.platform||FT(s)),this.platform.updateConfig(r);const a=this.platform.acquireContext(s,i.aspectRatio),l=a&&a.canvas,c=l&&l.height,d=l&&l.width;if(this.id=$M(),this.ctx=a,this.canvas=l,this.width=d,this.height=c,this._options=i,this._aspectRatio=this.aspectRatio,this._layers=[],this._metasets=[],this._stacks=void 0,this.boxes=[],this.currentDevicePixelRatio=void 0,this.chartArea=void 0,this._active=[],this._lastEvent=void 0,this._listeners={},this._responsiveListeners=void 0,this._sortedMetasets=[],this.scales={},this._plugins=new tO,this.$proxies={},this._hiddenIndices={},this.attached=!1,this._animationsDisabled=void 0,this.$context=void 0,this._doResize=eN(u=>this.update(u),i.resizeDelay||0),this._dataChanges=[],Na[this.id]=this,!a||!l){console.error("Failed to create chart: can't acquire context from the given item");return}tr.listen(this,"complete",vm),tr.listen(this,"progress",bO),this._initialize(),this.attached&&this.update()}get aspectRatio(){const{options:{aspectRatio:t,maintainAspectRatio:n},width:r,height:s,_aspectRatio:o}=this;return dt(t)?n&&o?o:s?r/s:null:t}get data(){return this.config.data}set data(t){this.config.data=t}get options(){return this._options}set options(t){this.config.options=t}get registry(){return Xn}_initialize(){return this.notifyPlugins("beforeInit"),this.options.responsive?this.resize():I0(this,this.options.devicePixelRatio),this.bindEvents(),this.notifyPlugins("afterInit"),this}clear(){return k0(this.canvas,this.ctx),this}stop(){return tr.stop(this),this}resize(t,n){tr.running(this)?this._resizeBeforeDraw={width:t,height:n}:this._resize(t,n)}_resize(t,n){const r=this.options,s=this.canvas,o=r.maintainAspectRatio&&this.aspectRatio,i=this.platform.getMaximumSize(s,t,n,o),a=r.devicePixelRatio||this.platform.getDevicePixelRatio(),l=this.width?"resize":"attach";this.width=i.width,this.height=i.height,this._aspectRatio=this.aspectRatio,I0(this,a,!0)&&(this.notifyPlugins("resize",{size:i}),mt(r.onResize,[this,i],this),this.attached&&this._doResize(l)&&this.render())}ensureScalesHaveIDs(){const n=this.options.scales||{};lt(n,(r,s)=>{r.id=s})}buildOrUpdateScales(){const t=this.options,n=t.scales,r=this.scales,s=Object.keys(r).reduce((i,a)=>(i[a]=!1,i),{});let o=[];n&&(o=o.concat(Object.keys(n).map(i=>{const a=n[i],l=xd(i,a),c=l==="r",d=l==="x";return{options:a,dposition:c?"chartArea":d?"bottom":"left",dtype:c?"radialLinear":d?"category":"linear"}}))),lt(o,i=>{const a=i.options,l=a.id,c=xd(l,a),d=Re(a.type,i.dtype);(a.position===void 0||bm(a.position,c)!==bm(i.dposition))&&(a.position=i.dposition),s[l]=!0;let u=null;if(l in r&&r[l].type===d)u=r[l];else{const f=Xn.getScale(d);u=new f({id:l,type:d,ctx:this.ctx,chart:this}),r[u.id]=u}u.init(a,t)}),lt(s,(i,a)=>{i||delete r[a]}),lt(r,i=>{xn.configure(this,i,i.options),xn.addBox(this,i)})}_updateMetasets(){const t=this._metasets,n=this.data.datasets.length,r=t.length;if(t.sort((s,o)=>s.index-o.index),r>n){for(let s=n;sn.length&&delete this._stacks,t.forEach((r,s)=>{n.filter(o=>o===r._dataset).length===0&&this._destroyDatasetMeta(s)})}buildOrUpdateControllers(){const t=[],n=this.data.datasets;let r,s;for(this._removeUnreferencedMetasets(),r=0,s=n.length;r{this.getDatasetMeta(n).controller.reset()},this)}reset(){this._resetElements(),this.notifyPlugins("reset")}update(t){const n=this.config;n.update();const r=this._options=n.createResolver(n.chartOptionScopes(),this.getContext()),s=this._animationsDisabled=!r.animation;if(this._updateScales(),this._checkEventBindings(),this._updateHiddenIndices(),this._plugins.invalidate(),this.notifyPlugins("beforeUpdate",{mode:t,cancelable:!0})===!1)return;const o=this.buildOrUpdateControllers();this.notifyPlugins("beforeElementsUpdate");let i=0;for(let c=0,d=this.data.datasets.length;c{c.reset()}),this._updateDatasets(t),this.notifyPlugins("afterUpdate",{mode:t}),this._layers.sort(ym("z","_idx"));const{_active:a,_lastEvent:l}=this;l?this._eventHandler(l,!0):a.length&&this._updateHoverStyles(a,a,!0),this.render()}_updateScales(){lt(this.scales,t=>{xn.removeBox(this,t)}),this.ensureScalesHaveIDs(),this.buildOrUpdateScales()}_checkEventBindings(){const t=this.options,n=new Set(Object.keys(this._listeners)),r=new Set(t.events);(!s0(n,r)||!!this._responsiveListeners!==t.responsive)&&(this.unbindEvents(),this.bindEvents())}_updateHiddenIndices(){const{_hiddenIndices:t}=this,n=this._getUniformDataChanges()||[];for(const{method:r,start:s,count:o}of n){const i=r==="_removeElements"?-o:o;yO(t,s,i)}}_getUniformDataChanges(){const t=this._dataChanges;if(!t||!t.length)return;this._dataChanges=[];const n=this.data.datasets.length,r=o=>new Set(t.filter(i=>i[0]===o).map((i,a)=>a+","+i.splice(1).join(","))),s=r(0);for(let o=1;oo.split(",")).map(o=>({method:o[1],start:+o[2],count:+o[3]}))}_updateLayout(t){if(this.notifyPlugins("beforeLayout",{cancelable:!0})===!1)return;xn.update(this,this.width,this.height,t);const n=this.chartArea,r=n.width<=0||n.height<=0;this._layers=[],lt(this.boxes,s=>{r&&s.position==="chartArea"||(s.configure&&s.configure(),this._layers.push(...s._layers()))},this),this._layers.forEach((s,o)=>{s._idx=o}),this.notifyPlugins("afterLayout")}_updateDatasets(t){if(this.notifyPlugins("beforeDatasetsUpdate",{mode:t,cancelable:!0})!==!1){for(let n=0,r=this.data.datasets.length;n=0;--n)this._drawDataset(t[n]);this.notifyPlugins("afterDatasetsDraw")}_drawDataset(t){const n=this.ctx,r={meta:t,index:t.index,cancelable:!0},s=VN(this,t);this.notifyPlugins("beforeDatasetDraw",r)!==!1&&(s&&rd(n,s),t.controller.draw(),s&&sd(n),r.cancelable=!1,this.notifyPlugins("afterDatasetDraw",r))}isPointInArea(t){return _0(t,this.chartArea,this._minPadding)}getElementsAtEventForMode(t,n,r,s){const o=mT.modes[n];return typeof o=="function"?o(this,t,r,s):[]}getDatasetMeta(t){const n=this.data.datasets[t],r=this._metasets;let s=r.filter(o=>o&&o._dataset===n).pop();return s||(s={type:null,data:[],dataset:null,controller:null,hidden:null,xAxisID:null,yAxisID:null,order:n&&n.order||0,index:t,_dataset:n,_parsed:[],_sorted:!1},r.push(s)),s}getContext(){return this.$context||(this.$context=Ms(null,{chart:this,type:"chart"}))}getVisibleDatasetCount(){return this.getSortedVisibleDatasetMetas().length}isDatasetVisible(t){const n=this.data.datasets[t];if(!n)return!1;const r=this.getDatasetMeta(t);return typeof r.hidden=="boolean"?!r.hidden:!n.hidden}setDatasetVisibility(t,n){const r=this.getDatasetMeta(t);r.hidden=!n}toggleDataVisibility(t){this._hiddenIndices[t]=!this._hiddenIndices[t]}getDataVisibility(t){return!this._hiddenIndices[t]}_updateVisibility(t,n,r){const s=r?"show":"hide",o=this.getDatasetMeta(t),i=o.controller._resolveAnimations(void 0,s);Eo(n)?(o.data[n].hidden=!r,this.update()):(this.setDatasetVisibility(t,r),i.update(o,{visible:r}),this.update(a=>a.datasetIndex===t?s:void 0))}hide(t,n){this._updateVisibility(t,n,!1)}show(t,n){this._updateVisibility(t,n,!0)}_destroyDatasetMeta(t){const n=this._metasets[t];n&&n.controller&&n.controller._destroy(),delete this._metasets[t]}_stop(){let t,n;for(this.stop(),tr.remove(this),t=0,n=this.data.datasets.length;t{n.addEventListener(this,o,i),t[o]=i},s=(o,i,a)=>{o.offsetX=i,o.offsetY=a,this._eventHandler(o)};lt(this.options.events,o=>r(o,s))}bindResponsiveEvents(){this._responsiveListeners||(this._responsiveListeners={});const t=this._responsiveListeners,n=this.platform,r=(l,c)=>{n.addEventListener(this,l,c),t[l]=c},s=(l,c)=>{t[l]&&(n.removeEventListener(this,l,c),delete t[l])},o=(l,c)=>{this.canvas&&this.resize(l,c)};let i;const a=()=>{s("attach",a),this.attached=!0,this.resize(),r("resize",o),r("detach",i)};i=()=>{this.attached=!1,s("resize",o),this._stop(),this._resize(0,0),r("attach",a)},n.isAttached(this.canvas)?a():i()}unbindEvents(){lt(this._listeners,(t,n)=>{this.platform.removeEventListener(this,n,t)}),this._listeners={},lt(this._responsiveListeners,(t,n)=>{this.platform.removeEventListener(this,n,t)}),this._responsiveListeners=void 0}updateHoverStyle(t,n,r){const s=r?"set":"remove";let o,i,a,l;for(n==="dataset"&&(o=this.getDatasetMeta(t[0].datasetIndex),o.controller["_"+s+"DatasetHoverStyle"]()),a=0,l=t.length;a{const a=this.getDatasetMeta(o);if(!a)throw new Error("No dataset found at index "+o);return{datasetIndex:o,element:a.data[i],index:i}});!la(r,n)&&(this._active=r,this._lastEvent=null,this._updateHoverStyles(r,n))}notifyPlugins(t,n,r){return this._plugins.notify(this,t,n,r)}isPluginEnabled(t){return this._plugins._cache.filter(n=>n.plugin.id===t).length===1}_updateHoverStyles(t,n,r){const s=this.options.hover,o=(l,c)=>l.filter(d=>!c.some(u=>d.datasetIndex===u.datasetIndex&&d.index===u.index)),i=o(n,t),a=r?t:o(t,n);i.length&&this.updateHoverStyle(i,s.mode,!1),a.length&&s.mode&&this.updateHoverStyle(a,s.mode,!0)}_eventHandler(t,n){const r={event:t,replay:n,cancelable:!0,inChartArea:this.isPointInArea(t)},s=i=>(i.options.events||this.options.events).includes(t.native.type);if(this.notifyPlugins("beforeEvent",r,s)===!1)return;const o=this._handleEvent(t,n,r.inChartArea);return r.cancelable=!1,this.notifyPlugins("afterEvent",r,s),(o||r.changed)&&this.render(),this}_handleEvent(t,n,r){const{_active:s=[],options:o}=this,i=n,a=this._getActiveElements(t,s,r,i),l=BM(t),c=vO(t,this._lastEvent,r,l);r&&(this._lastEvent=null,mt(o.onHover,[t,a,this],this),l&&mt(o.onClick,[t,a,this],this));const d=!la(a,s);return(d||n)&&(this._active=a,this._updateHoverStyles(a,s,n)),this._lastEvent=c,d}_getActiveElements(t,n,r,s){if(t.type==="mouseout")return[];if(!r)return n;const o=this.options.hover;return this.getElementsAtEventForMode(t,o.mode,o,s)}},Pe(wr,"defaults",kt),Pe(wr,"instances",Na),Pe(wr,"overrides",Fr),Pe(wr,"registry",Xn),Pe(wr,"version",pO),Pe(wr,"getChart",wm),wr);function km(){return lt(zo.instances,e=>e._plugins.invalidate())}function xO(e,t,n){const{startAngle:r,x:s,y:o,outerRadius:i,innerRadius:a,options:l}=t,{borderWidth:c,borderJoinStyle:d}=l,u=Math.min(c/i,Yn(r-n));if(e.beginPath(),e.arc(s,o,i-c/2,r+u/2,n-u/2),a>0){const f=Math.min(c/a,Yn(r-n));e.arc(s,o,a+c/2,n-f/2,r+f/2,!0)}else{const f=Math.min(c/2,i*Yn(r-n));if(d==="round")e.arc(s,o,f,n-ut/2,r+ut/2,!0);else if(d==="bevel"){const g=2*f*f,m=-g*Math.cos(n+ut/2)+s,p=-g*Math.sin(n+ut/2)+o,y=g*Math.cos(r+ut/2)+s,b=g*Math.sin(r+ut/2)+o;e.lineTo(m,p),e.lineTo(y,b)}}e.closePath(),e.moveTo(0,0),e.rect(0,0,e.canvas.width,e.canvas.height),e.clip("evenodd")}function wO(e,t,n){const{startAngle:r,pixelMargin:s,x:o,y:i,outerRadius:a,innerRadius:l}=t;let c=s/a;e.beginPath(),e.arc(o,i,a,r-c,n+c),l>s?(c=s/l,e.arc(o,i,l,n+c,r-c,!0)):e.arc(o,i,s,n+Ot,r-Ot),e.closePath(),e.clip()}function kO(e){return od(e,["outerStart","outerEnd","innerStart","innerEnd"])}function CO(e,t,n,r){const s=kO(e.options.borderRadius),o=(n-t)/2,i=Math.min(o,r*t/2),a=l=>{const c=(n-Math.min(o,l))*r/2;return sn(l,0,Math.min(o,c))};return{outerStart:a(s.outerStart),outerEnd:a(s.outerEnd),innerStart:sn(s.innerStart,0,i),innerEnd:sn(s.innerEnd,0,i)}}function Ds(e,t,n,r){return{x:n+e*Math.cos(t),y:r+e*Math.sin(t)}}function Ta(e,t,n,r,s,o){const{x:i,y:a,startAngle:l,pixelMargin:c,innerRadius:d}=t,u=Math.max(t.outerRadius+r+n-c,0),f=d>0?d+r+n+c:0;let g=0;const m=s-l;if(r){const A=d>0?d-r:0,O=u>0?u-r:0,T=(A+O)/2,z=T!==0?m*T/(T+r):m;g=(m-z)/2}const p=Math.max(.001,m*u-n/ut)/u,y=(m-p)/2,b=l+y+g,_=s-y-g,{outerStart:x,outerEnd:M,innerStart:C,innerEnd:k}=CO(t,f,u,_-b),N=u-x,S=u-M,w=b+x/N,E=_-M/S,D=f+C,P=f+k,L=b+C/D,$=_-k/P;if(e.beginPath(),o){const A=(w+E)/2;if(e.arc(i,a,u,w,A),e.arc(i,a,u,A,E),M>0){const H=Ds(S,E,i,a);e.arc(H.x,H.y,M,E,_+Ot)}const O=Ds(P,_,i,a);if(e.lineTo(O.x,O.y),k>0){const H=Ds(P,$,i,a);e.arc(H.x,H.y,k,_+Ot,$+Math.PI)}const T=(_-k/f+(b+C/f))/2;if(e.arc(i,a,f,_-k/f,T,!0),e.arc(i,a,f,T,b+C/f,!0),C>0){const H=Ds(D,L,i,a);e.arc(H.x,H.y,C,L+Math.PI,b-Ot)}const z=Ds(N,b,i,a);if(e.lineTo(z.x,z.y),x>0){const H=Ds(N,w,i,a);e.arc(H.x,H.y,x,b-Ot,w)}}else{e.moveTo(i,a);const A=Math.cos(w)*u+i,O=Math.sin(w)*u+a;e.lineTo(A,O);const T=Math.cos(E)*u+i,z=Math.sin(E)*u+a;e.lineTo(T,z)}e.closePath()}function SO(e,t,n,r,s){const{fullCircles:o,startAngle:i,circumference:a}=t;let l=t.endAngle;if(o){Ta(e,t,n,r,l,s);for(let c=0;c=ut&&g===0&&d!=="miter"&&xO(e,t,p),o||(Ta(e,t,n,r,p,s),e.stroke())}class Vo extends Br{constructor(n){super();Pe(this,"circumference");Pe(this,"endAngle");Pe(this,"fullCircles");Pe(this,"innerRadius");Pe(this,"outerRadius");Pe(this,"pixelMargin");Pe(this,"startAngle");this.options=void 0,this.circumference=void 0,this.startAngle=void 0,this.endAngle=void 0,this.innerRadius=void 0,this.outerRadius=void 0,this.pixelMargin=0,this.fullCircles=0,n&&Object.assign(this,n)}inRange(n,r,s){const o=this.getProps(["x","y"],s),{angle:i,distance:a}=c0(o,{x:n,y:r}),{startAngle:l,endAngle:c,innerRadius:d,outerRadius:u,circumference:f}=this.getProps(["startAngle","endAngle","innerRadius","outerRadius","circumference"],s),g=(this.options.spacing+this.options.borderWidth)/2,m=Re(f,c-l),p=ha(i,l,c)&&l!==c,y=m>=Mt||p,b=$r(a,d+g,u+g);return y&&b}getCenterPoint(n){const{x:r,y:s,startAngle:o,endAngle:i,innerRadius:a,outerRadius:l}=this.getProps(["x","y","startAngle","endAngle","innerRadius","outerRadius"],n),{offset:c,spacing:d}=this.options,u=(o+i)/2,f=(a+l+d+c)/2;return{x:r+Math.cos(u)*f,y:s+Math.sin(u)*f}}tooltipPosition(n){return this.getCenterPoint(n)}draw(n){const{options:r,circumference:s}=this,o=(r.offset||0)/4,i=(r.spacing||0)/2,a=r.circular;if(this.pixelMargin=r.borderAlign==="inner"?.33:0,this.fullCircles=s>Mt?Math.floor(s/Mt):0,s===0||this.innerRadius<0||this.outerRadius<0)return;n.save();const l=(this.startAngle+this.endAngle)/2;n.translate(Math.cos(l)*o,Math.sin(l)*o);const c=1-Math.sin(Math.min(ut,s||0)),d=o*c;n.fillStyle=r.backgroundColor,n.strokeStyle=r.borderColor,SO(n,this,d,i,a),_O(n,this,d,i,a),n.restore()}}Pe(Vo,"id","arc"),Pe(Vo,"defaults",{borderAlign:"center",borderColor:"#fff",borderDash:[],borderDashOffset:0,borderJoinStyle:void 0,borderRadius:0,borderWidth:2,offset:0,spacing:0,angle:void 0,circular:!0,selfJoin:!1}),Pe(Vo,"defaultRoutes",{backgroundColor:"backgroundColor"}),Pe(Vo,"descriptors",{_scriptable:!0,_indexable:n=>n!=="borderDash"});function Cm(e,t){const{x:n,y:r,base:s,width:o,height:i}=e.getProps(["x","y","base","width","height"],t);let a,l,c,d,u;return e.horizontal?(u=i/2,a=Math.min(n,s),l=Math.max(n,s),c=r-u,d=r+u):(u=o/2,a=n-u,l=n+u,c=Math.min(r,s),d=Math.max(r,s)),{left:a,top:c,right:l,bottom:d}}function xr(e,t,n,r){return e?0:sn(t,n,r)}function EO(e,t,n){const r=e.options.borderWidth,s=e.borderSkipped,o=E0(r);return{t:xr(s.top,o.top,0,n),r:xr(s.right,o.right,0,t),b:xr(s.bottom,o.bottom,0,n),l:xr(s.left,o.left,0,t)}}function MO(e,t,n){const{enableBorderRadius:r}=e.getProps(["enableBorderRadius"]),s=e.options.borderRadius,o=Es(s),i=Math.min(t,n),a=e.borderSkipped,l=r||Je(s);return{topLeft:xr(!l||a.top||a.left,o.topLeft,0,i),topRight:xr(!l||a.top||a.right,o.topRight,0,i),bottomLeft:xr(!l||a.bottom||a.left,o.bottomLeft,0,i),bottomRight:xr(!l||a.bottom||a.right,o.bottomRight,0,i)}}function NO(e){const t=Cm(e),n=t.right-t.left,r=t.bottom-t.top,s=EO(e,n/2,r/2),o=MO(e,n/2,r/2);return{outer:{x:t.left,y:t.top,w:n,h:r,radius:o},inner:{x:t.left+s.l,y:t.top+s.t,w:n-s.l-s.r,h:r-s.t-s.b,radius:{topLeft:Math.max(0,o.topLeft-Math.max(s.t,s.l)),topRight:Math.max(0,o.topRight-Math.max(s.t,s.r)),bottomLeft:Math.max(0,o.bottomLeft-Math.max(s.b,s.l)),bottomRight:Math.max(0,o.bottomRight-Math.max(s.b,s.r))}}}}function wd(e,t,n,r){const s=t===null,o=n===null,a=e&&!(s&&o)&&Cm(e,r);return a&&(s||$r(t,a.left,a.right))&&(o||$r(n,a.top,a.bottom))}function TO(e){return e.topLeft||e.topRight||e.bottomLeft||e.bottomRight}function OO(e,t){e.rect(t.x,t.y,t.w,t.h)}function kd(e,t,n={}){const r=e.x!==n.x?-t:0,s=e.y!==n.y?-t:0,o=(e.x+e.w!==n.x+n.w?t:0)-r,i=(e.y+e.h!==n.y+n.h?t:0)-s;return{x:e.x+r,y:e.y+s,w:e.w+o,h:e.h+i,radius:e.radius}}class Oa extends Br{constructor(t){super(),this.options=void 0,this.horizontal=void 0,this.base=void 0,this.width=void 0,this.height=void 0,this.inflateAmount=void 0,t&&Object.assign(this,t)}draw(t){const{inflateAmount:n,options:{borderColor:r,backgroundColor:s}}=this,{inner:o,outer:i}=NO(this),a=TO(i.radius)?pa:OO;t.save(),(i.w!==o.w||i.h!==o.h)&&(t.beginPath(),a(t,kd(i,n,o)),t.clip(),a(t,kd(o,-n,i)),t.fillStyle=r,t.fill("evenodd")),t.beginPath(),a(t,kd(o,n)),t.fillStyle=s,t.fill(),t.restore()}inRange(t,n,r){return wd(this,t,n,r)}inXRange(t,n){return wd(this,t,null,n)}inYRange(t,n){return wd(this,null,t,n)}getCenterPoint(t){const{x:n,y:r,base:s,horizontal:o}=this.getProps(["x","y","base","horizontal"],t);return{x:o?(n+s)/2:n,y:o?r:(r+s)/2}}getRange(t){return t==="x"?this.width/2:this.height/2}}Pe(Oa,"id","bar"),Pe(Oa,"defaults",{borderSkipped:"start",borderWidth:0,borderRadius:0,inflateAmount:"auto",pointStyle:void 0}),Pe(Oa,"defaultRoutes",{backgroundColor:"backgroundColor",borderColor:"borderColor"});const Sm=(e,t)=>{let{boxHeight:n=t,boxWidth:r=t}=e;return e.usePointStyle&&(n=Math.min(n,t),r=e.pointStyleWidth||Math.min(r,t)),{boxWidth:r,boxHeight:n,itemHeight:Math.max(t,n)}},AO=(e,t)=>e!==null&&t!==null&&e.datasetIndex===t.datasetIndex&&e.index===t.index;class _m extends Br{constructor(t){super(),this._added=!1,this.legendHitBoxes=[],this._hoveredItem=null,this.doughnutMode=!1,this.chart=t.chart,this.options=t.options,this.ctx=t.ctx,this.legendItems=void 0,this.columnSizes=void 0,this.lineWidths=void 0,this.maxHeight=void 0,this.maxWidth=void 0,this.top=void 0,this.bottom=void 0,this.left=void 0,this.right=void 0,this.height=void 0,this.width=void 0,this._margins=void 0,this.position=void 0,this.weight=void 0,this.fullSize=void 0}update(t,n,r){this.maxWidth=t,this.maxHeight=n,this._margins=r,this.setDimensions(),this.buildLabels(),this.fit()}setDimensions(){this.isHorizontal()?(this.width=this.maxWidth,this.left=this._margins.left,this.right=this.width):(this.height=this.maxHeight,this.top=this._margins.top,this.bottom=this.height)}buildLabels(){const t=this.options.labels||{};let n=mt(t.generateLabels,[this.chart],this)||[];t.filter&&(n=n.filter(r=>t.filter(r,this.chart.data))),t.sort&&(n=n.sort((r,s)=>t.sort(r,s,this.chart.data))),this.options.reverse&&n.reverse(),this.legendItems=n}fit(){const{options:t,ctx:n}=this;if(!t.display){this.width=this.height=0;return}const r=t.labels,s=Kt(r.font),o=s.size,i=this._computeTitleHeight(),{boxWidth:a,itemHeight:l}=Sm(r,o);let c,d;n.font=s.string,this.isHorizontal()?(c=this.maxWidth,d=this._fitRows(i,o,a,l)+10):(d=this.maxHeight,c=this._fitCols(i,s,a,l)+10),this.width=Math.min(c,t.maxWidth||this.maxWidth),this.height=Math.min(d,t.maxHeight||this.maxHeight)}_fitRows(t,n,r,s){const{ctx:o,maxWidth:i,options:{labels:{padding:a}}}=this,l=this.legendHitBoxes=[],c=this.lineWidths=[0],d=s+a;let u=t;o.textAlign="left",o.textBaseline="middle";let f=-1,g=-d;return this.legendItems.forEach((m,p)=>{const y=r+n/2+o.measureText(m.text).width;(p===0||c[c.length-1]+y+2*a>i)&&(u+=d,c[c.length-(p>0?0:1)]=0,g+=d,f++),l[p]={left:0,top:g,row:f,width:y,height:s},c[c.length-1]+=y+a}),u}_fitCols(t,n,r,s){const{ctx:o,maxHeight:i,options:{labels:{padding:a}}}=this,l=this.legendHitBoxes=[],c=this.columnSizes=[],d=i-t;let u=a,f=0,g=0,m=0,p=0;return this.legendItems.forEach((y,b)=>{const{itemWidth:_,itemHeight:x}=DO(r,n,o,y,s);b>0&&g+x+2*a>d&&(u+=f+a,c.push({width:f,height:g}),m+=f+a,p++,f=g=0),l[b]={left:m,top:g,col:p,width:_,height:x},f=Math.max(f,_),g+=x+a}),u+=f,c.push({width:f,height:g}),u}adjustHitBoxes(){if(!this.options.display)return;const t=this._computeTitleHeight(),{legendHitBoxes:n,options:{align:r,labels:{padding:s},rtl:o}}=this,i=Ts(o,this.left,this.width);if(this.isHorizontal()){let a=0,l=Xt(r,this.left+s,this.right-this.lineWidths[a]);for(const c of n)a!==c.row&&(a=c.row,l=Xt(r,this.left+s,this.right-this.lineWidths[a])),c.top+=this.top+t+s,c.left=i.leftForLtr(i.x(l),c.width),l+=c.width+s}else{let a=0,l=Xt(r,this.top+t+s,this.bottom-this.columnSizes[a].height);for(const c of n)c.col!==a&&(a=c.col,l=Xt(r,this.top+t+s,this.bottom-this.columnSizes[a].height)),c.top=l,c.left+=this.left+s,c.left=i.leftForLtr(i.x(c.left),c.width),l+=c.height+s}}isHorizontal(){return this.options.position==="top"||this.options.position==="bottom"}draw(){if(this.options.display){const t=this.ctx;rd(t,this),this._draw(),sd(t)}}_draw(){const{options:t,columnSizes:n,lineWidths:r,ctx:s}=this,{align:o,labels:i}=t,a=kt.color,l=Ts(t.rtl,this.left,this.width),c=Kt(i.font),{padding:d}=i,u=c.size,f=u/2;let g;this.drawTitle(),s.textAlign=l.textAlign("left"),s.textBaseline="middle",s.lineWidth=.5,s.font=c.string;const{boxWidth:m,boxHeight:p,itemHeight:y}=Sm(i,u),b=function(k,N,S){if(isNaN(m)||m<=0||isNaN(p)||p<0)return;s.save();const w=Re(S.lineWidth,1);if(s.fillStyle=Re(S.fillStyle,a),s.lineCap=Re(S.lineCap,"butt"),s.lineDashOffset=Re(S.lineDashOffset,0),s.lineJoin=Re(S.lineJoin,"miter"),s.lineWidth=w,s.strokeStyle=Re(S.strokeStyle,a),s.setLineDash(Re(S.lineDash,[])),i.usePointStyle){const E={radius:p*Math.SQRT2/2,pointStyle:S.pointStyle,rotation:S.rotation,borderWidth:w},D=l.xPlus(k,m/2),P=N+f;S0(s,E,D,P,i.pointStyleWidth&&m)}else{const E=N+Math.max((u-p)/2,0),D=l.leftForLtr(k,m),P=Es(S.borderRadius);s.beginPath(),Object.values(P).some(L=>L!==0)?pa(s,{x:D,y:E,w:m,h:p,radius:P}):s.rect(D,E,m,p),s.fill(),w!==0&&s.stroke()}s.restore()},_=function(k,N,S){To(s,S.text,k,N+y/2,c,{strikethrough:S.hidden,textAlign:l.textAlign(S.textAlign)})},x=this.isHorizontal(),M=this._computeTitleHeight();x?g={x:Xt(o,this.left+d,this.right-r[0]),y:this.top+d+M,line:0}:g={x:this.left+d,y:Xt(o,this.top+M+d,this.bottom-n[0].height),line:0},P0(this.ctx,t.textDirection);const C=y+d;this.legendItems.forEach((k,N)=>{s.strokeStyle=k.fontColor,s.fillStyle=k.fontColor;const S=s.measureText(k.text).width,w=l.textAlign(k.textAlign||(k.textAlign=i.textAlign)),E=m+f+S;let D=g.x,P=g.y;l.setWidth(this.width),x?N>0&&D+E+d>this.right&&(P=g.y+=C,g.line++,D=g.x=Xt(o,this.left+d,this.right-r[g.line])):N>0&&P+C>this.bottom&&(D=g.x=D+n[g.line].width+d,g.line++,P=g.y=Xt(o,this.top+M+d,this.bottom-n[g.line].height));const L=l.x(D);if(b(L,P,k),D=tN(w,D+m+f,x?D+E:this.right,t.rtl),_(l.x(D),P,k),x)g.x+=E+d;else if(typeof k.text!="string"){const $=c.lineHeight;g.y+=Em(k,$)+d}else g.y+=C}),$0(this.ctx,t.textDirection)}drawTitle(){const t=this.options,n=t.title,r=Kt(n.font),s=vn(n.padding);if(!n.display)return;const o=Ts(t.rtl,this.left,this.width),i=this.ctx,a=n.position,l=r.size/2,c=s.top+l;let d,u=this.left,f=this.width;if(this.isHorizontal())f=Math.max(...this.lineWidths),d=this.top+c,u=Xt(t.align,u,this.right-f);else{const m=this.columnSizes.reduce((p,y)=>Math.max(p,y.height),0);d=c+Xt(t.align,this.top,this.bottom-m-t.labels.padding-this._computeTitleHeight())}const g=Xt(a,u,u+f);i.textAlign=o.textAlign(Jc(a)),i.textBaseline="middle",i.strokeStyle=n.color,i.fillStyle=n.color,i.font=r.string,To(i,n.text,g,d,r)}_computeTitleHeight(){const t=this.options.title,n=Kt(t.font),r=vn(t.padding);return t.display?n.lineHeight+r.height:0}_getLegendItemAt(t,n){let r,s,o;if($r(t,this.left,this.right)&&$r(n,this.top,this.bottom)){for(o=this.legendHitBoxes,r=0;ro.length>i.length?o:i)),t+n.size/2+r.measureText(s).width}function LO(e,t,n){let r=e;return typeof t.text!="string"&&(r=Em(t,n)),r}function Em(e,t){const n=e.text?e.text.length:0;return t*n}function PO(e,t){return!!((e==="mousemove"||e==="mouseout")&&(t.onHover||t.onLeave)||t.onClick&&(e==="click"||e==="mouseup"))}var Mm={id:"legend",_element:_m,start(e,t,n){const r=e.legend=new _m({ctx:e.ctx,options:n,chart:e});xn.configure(e,r,n),xn.addBox(e,r)},stop(e){xn.removeBox(e,e.legend),delete e.legend},beforeUpdate(e,t,n){const r=e.legend;xn.configure(e,r,n),r.options=n},afterUpdate(e){const t=e.legend;t.buildLabels(),t.adjustHitBoxes()},afterEvent(e,t){t.replay||e.legend.handleEvent(t.event)},defaults:{display:!0,position:"top",align:"center",fullSize:!0,reverse:!1,weight:1e3,onClick(e,t,n){const r=t.datasetIndex,s=n.chart;s.isDatasetVisible(r)?(s.hide(r),t.hidden=!0):(s.show(r),t.hidden=!1)},onHover:null,onLeave:null,labels:{color:e=>e.chart.options.color,boxWidth:40,padding:10,generateLabels(e){const t=e.data.datasets,{labels:{usePointStyle:n,pointStyle:r,textAlign:s,color:o,useBorderRadius:i,borderRadius:a}}=e.legend.options;return e._getSortedDatasetMetas().map(l=>{const c=l.controller.getStyle(n?0:void 0),d=vn(c.borderWidth);return{text:t[l.index].label,fillStyle:c.backgroundColor,fontColor:o,hidden:!l.visible,lineCap:c.borderCapStyle,lineDash:c.borderDash,lineDashOffset:c.borderDashOffset,lineJoin:c.borderJoinStyle,lineWidth:(d.width+d.height)/4,strokeStyle:c.borderColor,pointStyle:r||c.pointStyle,rotation:c.rotation,textAlign:s||c.textAlign,borderRadius:i&&(a||c.borderRadius),datasetIndex:l.index}},this)}},title:{color:e=>e.chart.options.color,display:!1,position:"center",text:""}},descriptors:{_scriptable:e=>!e.startsWith("on"),labels:{_scriptable:e=>!["generateLabels","filter","sort"].includes(e)}}};class Nm extends Br{constructor(t){super(),this.chart=t.chart,this.options=t.options,this.ctx=t.ctx,this._padding=void 0,this.top=void 0,this.bottom=void 0,this.left=void 0,this.right=void 0,this.width=void 0,this.height=void 0,this.position=void 0,this.weight=void 0,this.fullSize=void 0}update(t,n){const r=this.options;if(this.left=0,this.top=0,!r.display){this.width=this.height=this.right=this.bottom=0;return}this.width=this.right=t,this.height=this.bottom=n;const s=Tt(r.text)?r.text.length:1;this._padding=vn(r.padding);const o=s*Kt(r.font).lineHeight+this._padding.height;this.isHorizontal()?this.height=o:this.width=o}isHorizontal(){const t=this.options.position;return t==="top"||t==="bottom"}_drawArgs(t){const{top:n,left:r,bottom:s,right:o,options:i}=this,a=i.align;let l=0,c,d,u;return this.isHorizontal()?(d=Xt(a,r,o),u=n+t,c=o-r):(i.position==="left"?(d=r+t,u=Xt(a,s,n),l=ut*-.5):(d=o-t,u=Xt(a,n,s),l=ut*.5),c=s-n),{titleX:d,titleY:u,maxWidth:c,rotation:l}}draw(){const t=this.ctx,n=this.options;if(!n.display)return;const r=Kt(n.font),o=r.lineHeight/2+this._padding.top,{titleX:i,titleY:a,maxWidth:l,rotation:c}=this._drawArgs(o);To(t,n.text,0,0,r,{color:n.color,maxWidth:l,rotation:c,textAlign:Jc(n.align),textBaseline:"middle",translation:[i,a]})}}function $O(e,t){const n=new Nm({ctx:e.ctx,options:t,chart:e});xn.configure(e,n,t),xn.addBox(e,n),e.titleBlock=n}var FO={id:"title",_element:Nm,start(e,t,n){$O(e,n)},stop(e){const t=e.titleBlock;xn.removeBox(e,t),delete e.titleBlock},beforeUpdate(e,t,n){const r=e.titleBlock;xn.configure(e,r,n),r.options=n},defaults:{align:"center",display:!1,font:{weight:"bold"},fullSize:!0,padding:10,position:"top",text:"",weight:2e3},defaultRoutes:{color:"color"},descriptors:{_scriptable:!0,_indexable:!1}};const Ho={average(e){if(!e.length)return!1;let t,n,r=new Set,s=0,o=0;for(t=0,n=e.length;ta+l)/r.size,y:s/o}},nearest(e,t){if(!e.length)return!1;let n=t.x,r=t.y,s=Number.POSITIVE_INFINITY,o,i,a;for(o=0,i=e.length;o-1?e.split(` -`):e}function zO(e,t){const{element:n,datasetIndex:r,index:s}=t,o=e.getDatasetMeta(r).controller,{label:i,value:a}=o.getLabelAndValue(s);return{chart:e,label:i,parsed:o.getParsed(s),raw:e.data.datasets[r].data[s],formattedValue:a,dataset:o.getDataset(),dataIndex:s,datasetIndex:r,element:n}}function Tm(e,t){const n=e.chart.ctx,{body:r,footer:s,title:o}=e,{boxWidth:i,boxHeight:a}=t,l=Kt(t.bodyFont),c=Kt(t.titleFont),d=Kt(t.footerFont),u=o.length,f=s.length,g=r.length,m=vn(t.padding);let p=m.height,y=0,b=r.reduce((M,C)=>M+C.before.length+C.lines.length+C.after.length,0);if(b+=e.beforeBody.length+e.afterBody.length,u&&(p+=u*c.lineHeight+(u-1)*t.titleSpacing+t.titleMarginBottom),b){const M=t.displayColors?Math.max(a,l.lineHeight):l.lineHeight;p+=g*M+(b-g)*l.lineHeight+(b-1)*t.bodySpacing}f&&(p+=t.footerMarginTop+f*d.lineHeight+(f-1)*t.footerSpacing);let _=0;const x=function(M){y=Math.max(y,n.measureText(M).width+_)};return n.save(),n.font=c.string,lt(e.title,x),n.font=l.string,lt(e.beforeBody.concat(e.afterBody),x),_=t.displayColors?i+2+t.boxPadding:0,lt(r,M=>{lt(M.before,x),lt(M.lines,x),lt(M.after,x)}),_=0,n.font=d.string,lt(e.footer,x),n.restore(),y+=m.width,{width:y,height:p}}function VO(e,t){const{y:n,height:r}=t;return ne.height-r/2?"bottom":"center"}function HO(e,t,n,r){const{x:s,width:o}=r,i=n.caretSize+n.caretPadding;if(e==="left"&&s+o+i>t.width||e==="right"&&s-o-i<0)return!0}function UO(e,t,n,r){const{x:s,width:o}=n,{width:i,chartArea:{left:a,right:l}}=e;let c="center";return r==="center"?c=s<=(a+l)/2?"left":"right":s<=o/2?c="left":s>=i-o/2&&(c="right"),HO(c,e,t,n)&&(c="center"),c}function Om(e,t,n){const r=n.yAlign||t.yAlign||VO(e,n);return{xAlign:n.xAlign||t.xAlign||UO(e,t,n,r),yAlign:r}}function BO(e,t){let{x:n,width:r}=e;return t==="right"?n-=r:t==="center"&&(n-=r/2),n}function jO(e,t,n){let{y:r,height:s}=e;return t==="top"?r+=n:t==="bottom"?r-=s+n:r-=s/2,r}function Am(e,t,n,r){const{caretSize:s,caretPadding:o,cornerRadius:i}=e,{xAlign:a,yAlign:l}=n,c=s+o,{topLeft:d,topRight:u,bottomLeft:f,bottomRight:g}=Es(i);let m=BO(t,a);const p=jO(t,l,c);return l==="center"?a==="left"?m+=c:a==="right"&&(m-=c):a==="left"?m-=Math.max(d,f)+s:a==="right"&&(m+=Math.max(u,g)+s),{x:sn(m,0,r.width-t.width),y:sn(p,0,r.height-t.height)}}function Aa(e,t,n){const r=vn(n.padding);return t==="center"?e.x+e.width/2:t==="right"?e.x+e.width-r.right:e.x+r.left}function Dm(e){return Kn([],nr(e))}function WO(e,t,n){return Ms(e,{tooltip:t,tooltipItems:n,type:"tooltip"})}function Im(e,t){const n=t&&t.dataset&&t.dataset.tooltip&&t.dataset.tooltip.callbacks;return n?e.override(n):e}const Lm={beforeTitle:Rn,title(e){if(e.length>0){const t=e[0],n=t.chart.data.labels,r=n?n.length:0;if(this&&this.options&&this.options.mode==="dataset")return t.dataset.label||"";if(t.label)return t.label;if(r>0&&t.dataIndex"u"?Lm[t].call(n,r):s}class Cd extends Br{constructor(t){super(),this.opacity=0,this._active=[],this._eventPosition=void 0,this._size=void 0,this._cachedAnimations=void 0,this._tooltipItems=[],this.$animations=void 0,this.$context=void 0,this.chart=t.chart,this.options=t.options,this.dataPoints=void 0,this.title=void 0,this.beforeBody=void 0,this.body=void 0,this.afterBody=void 0,this.footer=void 0,this.xAlign=void 0,this.yAlign=void 0,this.x=void 0,this.y=void 0,this.height=void 0,this.width=void 0,this.caretX=void 0,this.caretY=void 0,this.labelColors=void 0,this.labelPointStyles=void 0,this.labelTextColors=void 0}initialize(t){this.options=t,this._cachedAnimations=void 0,this.$context=void 0}_resolveAnimations(){const t=this._cachedAnimations;if(t)return t;const n=this.chart,r=this.options.setContext(this.getContext()),s=r.enabled&&n.options.animation&&r.animations,o=new z0(this.chart,s);return s._cacheable&&(this._cachedAnimations=Object.freeze(o)),o}getContext(){return this.$context||(this.$context=WO(this.chart.getContext(),this,this._tooltipItems))}getTitle(t,n){const{callbacks:r}=n,s=on(r,"beforeTitle",this,t),o=on(r,"title",this,t),i=on(r,"afterTitle",this,t);let a=[];return a=Kn(a,nr(s)),a=Kn(a,nr(o)),a=Kn(a,nr(i)),a}getBeforeBody(t,n){return Dm(on(n.callbacks,"beforeBody",this,t))}getBody(t,n){const{callbacks:r}=n,s=[];return lt(t,o=>{const i={before:[],lines:[],after:[]},a=Im(r,o);Kn(i.before,nr(on(a,"beforeLabel",this,o))),Kn(i.lines,on(a,"label",this,o)),Kn(i.after,nr(on(a,"afterLabel",this,o))),s.push(i)}),s}getAfterBody(t,n){return Dm(on(n.callbacks,"afterBody",this,t))}getFooter(t,n){const{callbacks:r}=n,s=on(r,"beforeFooter",this,t),o=on(r,"footer",this,t),i=on(r,"afterFooter",this,t);let a=[];return a=Kn(a,nr(s)),a=Kn(a,nr(o)),a=Kn(a,nr(i)),a}_createItems(t){const n=this._active,r=this.chart.data,s=[],o=[],i=[];let a=[],l,c;for(l=0,c=n.length;lt.filter(d,u,f,r))),t.itemSort&&(a=a.sort((d,u)=>t.itemSort(d,u,r))),lt(a,d=>{const u=Im(t.callbacks,d);s.push(on(u,"labelColor",this,d)),o.push(on(u,"labelPointStyle",this,d)),i.push(on(u,"labelTextColor",this,d))}),this.labelColors=s,this.labelPointStyles=o,this.labelTextColors=i,this.dataPoints=a,a}update(t,n){const r=this.options.setContext(this.getContext()),s=this._active;let o,i=[];if(!s.length)this.opacity!==0&&(o={opacity:0});else{const a=Ho[r.position].call(this,s,this._eventPosition);i=this._createItems(r),this.title=this.getTitle(i,r),this.beforeBody=this.getBeforeBody(i,r),this.body=this.getBody(i,r),this.afterBody=this.getAfterBody(i,r),this.footer=this.getFooter(i,r);const l=this._size=Tm(this,r),c=Object.assign({},a,l),d=Om(this.chart,r,c),u=Am(r,c,d,this.chart);this.xAlign=d.xAlign,this.yAlign=d.yAlign,o={opacity:1,x:u.x,y:u.y,width:l.width,height:l.height,caretX:a.x,caretY:a.y}}this._tooltipItems=i,this.$context=void 0,o&&this._resolveAnimations().update(this,o),t&&r.external&&r.external.call(this,{chart:this.chart,tooltip:this,replay:n})}drawCaret(t,n,r,s){const o=this.getCaretPosition(t,r,s);n.lineTo(o.x1,o.y1),n.lineTo(o.x2,o.y2),n.lineTo(o.x3,o.y3)}getCaretPosition(t,n,r){const{xAlign:s,yAlign:o}=this,{caretSize:i,cornerRadius:a}=r,{topLeft:l,topRight:c,bottomLeft:d,bottomRight:u}=Es(a),{x:f,y:g}=t,{width:m,height:p}=n;let y,b,_,x,M,C;return o==="center"?(M=g+p/2,s==="left"?(y=f,b=y-i,x=M+i,C=M-i):(y=f+m,b=y+i,x=M-i,C=M+i),_=y):(s==="left"?b=f+Math.max(l,d)+i:s==="right"?b=f+m-Math.max(c,u)-i:b=this.caretX,o==="top"?(x=g,M=x-i,y=b-i,_=b+i):(x=g+p,M=x+i,y=b+i,_=b-i),C=x),{x1:y,x2:b,x3:_,y1:x,y2:M,y3:C}}drawTitle(t,n,r){const s=this.title,o=s.length;let i,a,l;if(o){const c=Ts(r.rtl,this.x,this.width);for(t.x=Aa(this,r.titleAlign,r),n.textAlign=c.textAlign(r.titleAlign),n.textBaseline="middle",i=Kt(r.titleFont),a=r.titleSpacing,n.fillStyle=r.titleColor,n.font=i.string,l=0;l_!==0)?(t.beginPath(),t.fillStyle=o.multiKeyBackground,pa(t,{x:p,y:m,w:c,h:l,radius:b}),t.fill(),t.stroke(),t.fillStyle=i.backgroundColor,t.beginPath(),pa(t,{x:y,y:m+1,w:c-2,h:l-2,radius:b}),t.fill()):(t.fillStyle=o.multiKeyBackground,t.fillRect(p,m,c,l),t.strokeRect(p,m,c,l),t.fillStyle=i.backgroundColor,t.fillRect(y,m+1,c-2,l-2))}t.fillStyle=this.labelTextColors[r]}drawBody(t,n,r){const{body:s}=this,{bodySpacing:o,bodyAlign:i,displayColors:a,boxHeight:l,boxWidth:c,boxPadding:d}=r,u=Kt(r.bodyFont);let f=u.lineHeight,g=0;const m=Ts(r.rtl,this.x,this.width),p=function(S){n.fillText(S,m.x(t.x+g),t.y+f/2),t.y+=f+o},y=m.textAlign(i);let b,_,x,M,C,k,N;for(n.textAlign=i,n.textBaseline="middle",n.font=u.string,t.x=Aa(this,y,r),n.fillStyle=r.bodyColor,lt(this.beforeBody,p),g=a&&y!=="right"?i==="center"?c/2+d:c+2+d:0,M=0,k=s.length;M0&&n.stroke()}_updateAnimationTarget(t){const n=this.chart,r=this.$animations,s=r&&r.x,o=r&&r.y;if(s||o){const i=Ho[t.position].call(this,this._active,this._eventPosition);if(!i)return;const a=this._size=Tm(this,t),l=Object.assign({},i,this._size),c=Om(n,t,l),d=Am(t,l,c,n);(s._to!==d.x||o._to!==d.y)&&(this.xAlign=c.xAlign,this.yAlign=c.yAlign,this.width=a.width,this.height=a.height,this.caretX=i.x,this.caretY=i.y,this._resolveAnimations().update(this,d))}}_willRender(){return!!this.opacity}draw(t){const n=this.options.setContext(this.getContext());let r=this.opacity;if(!r)return;this._updateAnimationTarget(n);const s={width:this.width,height:this.height},o={x:this.x,y:this.y};r=Math.abs(r)<.001?0:r;const i=vn(n.padding),a=this.title.length||this.beforeBody.length||this.body.length||this.afterBody.length||this.footer.length;n.enabled&&a&&(t.save(),t.globalAlpha=r,this.drawBackground(o,t,s,n),P0(t,n.textDirection),o.y+=i.top,this.drawTitle(o,t,n),this.drawBody(o,t,n),this.drawFooter(o,t,n),$0(t,n.textDirection),t.restore())}getActiveElements(){return this._active||[]}setActiveElements(t,n){const r=this._active,s=t.map(({datasetIndex:a,index:l})=>{const c=this.chart.getDatasetMeta(a);if(!c)throw new Error("Cannot find a dataset at index "+a);return{datasetIndex:a,element:c.data[l],index:l}}),o=!la(r,s),i=this._positionChanged(s,n);(o||i)&&(this._active=s,this._eventPosition=n,this._ignoreReplayEvents=!0,this.update(!0))}handleEvent(t,n,r=!0){if(n&&this._ignoreReplayEvents)return!1;this._ignoreReplayEvents=!1;const s=this.options,o=this._active||[],i=this._getActiveElements(t,o,n,r),a=this._positionChanged(i,t),l=n||!la(i,o)||a;return l&&(this._active=i,(s.enabled||s.external)&&(this._eventPosition={x:t.x,y:t.y},this.update(!0,n))),l}_getActiveElements(t,n,r,s){const o=this.options;if(t.type==="mouseout")return[];if(!s)return n.filter(a=>this.chart.data.datasets[a.datasetIndex]&&this.chart.getDatasetMeta(a.datasetIndex).controller.getParsed(a.index)!==void 0);const i=this.chart.getElementsAtEventForMode(t,o.mode,o,r);return o.reverse&&i.reverse(),i}_positionChanged(t,n){const{caretX:r,caretY:s,options:o}=this,i=Ho[o.position].call(this,t,n);return i!==!1&&(r!==i.x||s!==i.y)}}Pe(Cd,"positioners",Ho);var Pm={id:"tooltip",_element:Cd,positioners:Ho,afterInit(e,t,n){n&&(e.tooltip=new Cd({chart:e,options:n}))},beforeUpdate(e,t,n){e.tooltip&&e.tooltip.initialize(n)},reset(e,t,n){e.tooltip&&e.tooltip.initialize(n)},afterDraw(e){const t=e.tooltip;if(t&&t._willRender()){const n={tooltip:t};if(e.notifyPlugins("beforeTooltipDraw",{...n,cancelable:!0})===!1)return;t.draw(e.ctx),e.notifyPlugins("afterTooltipDraw",n)}},afterEvent(e,t){if(e.tooltip){const n=t.replay;e.tooltip.handleEvent(t.event,n,t.inChartArea)&&(t.changed=!0)}},defaults:{enabled:!0,external:null,position:"average",backgroundColor:"rgba(0,0,0,0.8)",titleColor:"#fff",titleFont:{weight:"bold"},titleSpacing:2,titleMarginBottom:6,titleAlign:"left",bodyColor:"#fff",bodySpacing:2,bodyFont:{},bodyAlign:"left",footerColor:"#fff",footerSpacing:2,footerMarginTop:6,footerFont:{weight:"bold"},footerAlign:"left",padding:6,caretPadding:2,caretSize:5,cornerRadius:6,boxHeight:(e,t)=>t.bodyFont.size,boxWidth:(e,t)=>t.bodyFont.size,multiKeyBackground:"#fff",displayColors:!0,boxPadding:0,borderColor:"rgba(0,0,0,0)",borderWidth:0,animation:{duration:400,easing:"easeOutQuart"},animations:{numbers:{type:"number",properties:["x","y","width","height","caretX","caretY"]},opacity:{easing:"linear",duration:200}},callbacks:Lm},defaultRoutes:{bodyFont:"font",footerFont:"font",titleFont:"font"},descriptors:{_scriptable:e=>e!=="filter"&&e!=="itemSort"&&e!=="external",_indexable:!1,callbacks:{_scriptable:!1,_indexable:!1},animation:{_fallback:!1},animations:{_fallback:"animation"}},additionalOptionScopes:["interaction"]};const GO=(e,t,n,r)=>(typeof t=="string"?(n=e.push(t)-1,r.unshift({index:n,label:t})):isNaN(t)&&(n=null),n);function YO(e,t,n,r){const s=e.indexOf(t);if(s===-1)return GO(e,t,n,r);const o=e.lastIndexOf(t);return s!==o?n:s}const XO=(e,t)=>e===null?null:sn(Math.round(e),0,t);function $m(e){const t=this.getLabels();return e>=0&&en.length-1?null:this.getPixelForValue(n[t].value)}getValueForPixel(t){return Math.round(this._startValue+this.getDecimalForPixel(t)*this._valueRange)}getBasePixel(){return this.bottom}}Pe(Sd,"id","category"),Pe(Sd,"defaults",{ticks:{callback:$m}});function KO(e,t){const n=[],{bounds:s,step:o,min:i,max:a,precision:l,count:c,maxTicks:d,maxDigits:u,includeBounds:f}=e,g=o||1,m=d-1,{min:p,max:y}=t,b=!dt(i),_=!dt(a),x=!dt(c),M=(y-p)/(u+1);let C=a0((y-p)/m/g)*g,k,N,S,w;if(C<1e-14&&!b&&!_)return[{value:p},{value:y}];w=Math.ceil(y/C)-Math.floor(p/C),w>m&&(C=a0(w*C/m/g)*g),dt(l)||(k=Math.pow(10,l),C=Math.ceil(C*k)/k),s==="ticks"?(N=Math.floor(p/C)*C,S=Math.ceil(y/C)*C):(N=p,S=y),b&&_&&o&&YM((a-i)/o,C/1e3)?(w=Math.round(Math.min((a-i)/C,d)),C=(a-i)/w,N=i,S=a):x?(N=b?i:N,S=_?a:S,w=c-1,C=(S-N)/w):(w=(S-N)/C,ua(w,Math.round(w),C/1e3)?w=Math.round(w):w=Math.ceil(w));const E=Math.max(l0(C),l0(N));k=Math.pow(10,dt(l)?E:l),N=Math.round(N*k)/k,S=Math.round(S*k)/k;let D=0;for(b&&(f&&N!==i?(n.push({value:i}),Na)break;n.push({value:P})}return _&&f&&S!==a?n.length&&ua(n[n.length-1].value,a,Fm(a,M,e))?n[n.length-1].value=a:n.push({value:a}):(!_||S===a)&&n.push({value:S}),n}function Fm(e,t,{horizontal:n,minRotation:r}){const s=er(r),o=(n?Math.sin(s):Math.cos(s))||.001,i=.75*t*(""+e).length;return Math.min(t/o,i)}class qO extends As{constructor(t){super(t),this.start=void 0,this.end=void 0,this._startValue=void 0,this._endValue=void 0,this._valueRange=0}parse(t,n){return dt(t)||(typeof t=="number"||t instanceof Number)&&!isFinite(+t)?null:+t}handleTickRangeOptions(){const{beginAtZero:t}=this.options,{minDefined:n,maxDefined:r}=this.getUserBounds();let{min:s,max:o}=this;const i=l=>s=n?s:l,a=l=>o=r?o:l;if(t){const l=yr(s),c=yr(o);l<0&&c<0?a(0):l>0&&c>0&&i(0)}if(s===o){let l=o===0?1:Math.abs(o*.05);a(o+l),t||i(s-l)}this.min=s,this.max=o}getTickLimit(){const t=this.options.ticks;let{maxTicksLimit:n,stepSize:r}=t,s;return r?(s=Math.ceil(this.max/r)-Math.floor(this.min/r)+1,s>1e3&&(console.warn(`scales.${this.id}.ticks.stepSize: ${r} would result generating up to ${s} ticks. Limiting to 1000.`),s=1e3)):(s=this.computeTickLimit(),n=n||11),n&&(s=Math.min(n,s)),s}computeTickLimit(){return Number.POSITIVE_INFINITY}buildTicks(){const t=this.options,n=t.ticks;let r=this.getTickLimit();r=Math.max(2,r);const s={maxTicks:r,bounds:t.bounds,min:t.min,max:t.max,precision:n.precision,step:n.stepSize,count:n.count,maxDigits:this._maxDigits(),horizontal:this.isHorizontal(),minRotation:n.minRotation||0,includeBounds:n.includeBounds!==!1},o=this._range||this,i=KO(s,o);return t.bounds==="ticks"&&XM(i,this,"value"),t.reverse?(i.reverse(),this.start=this.max,this.end=this.min):(this.start=this.min,this.end=this.max),i}configure(){const t=this.ticks;let n=this.min,r=this.max;if(super.configure(),this.options.offset&&t.length){const s=(r-n)/Math.max(t.length-1,1)/2;n-=s,r+=s}this._startValue=n,this._endValue=r,this._valueRange=r-n}getLabelForValue(t){return ed(t,this.chart.options.locale,this.options.ticks.format)}}class _d extends qO{determineDataLimits(){const{min:t,max:n}=this.getMinMax(!0);this.min=yn(t)?t:0,this.max=yn(n)?n:1,this.handleTickRangeOptions()}computeTickLimit(){const t=this.isHorizontal(),n=t?this.width:this.height,r=er(this.options.ticks.minRotation),s=(t?Math.sin(r):Math.cos(r))||.001,o=this._resolveTickFontOptions(0);return Math.ceil(n/Math.min(40,o.lineHeight/s))}getPixelForValue(t){return t===null?NaN:this.getPixelForDecimal((t-this._startValue)/this._valueRange)}getValueForPixel(t){return this._startValue+this.getDecimalForPixel(t)*this._valueRange}}Pe(_d,"id","linear"),Pe(_d,"defaults",{ticks:{callback:x0.formatters.numeric}});const Da={millisecond:{common:!0,size:1,steps:1e3},second:{common:!0,size:1e3,steps:60},minute:{common:!0,size:6e4,steps:60},hour:{common:!0,size:36e5,steps:24},day:{common:!0,size:864e5,steps:30},week:{common:!1,size:6048e5,steps:4},month:{common:!0,size:2628e6,steps:12},quarter:{common:!1,size:7884e6,steps:4},year:{common:!0,size:3154e7}},an=Object.keys(Da);function zm(e,t){return e-t}function Vm(e,t){if(dt(t))return null;const n=e._adapter,{parser:r,round:s,isoWeekday:o}=e._parseOpts;let i=t;return typeof r=="function"&&(i=r(i)),yn(i)||(i=typeof r=="string"?n.parse(i,r):n.parse(i)),i===null?null:(s&&(i=s==="week"&&(fa(o)||o===!0)?n.startOf(i,"isoWeek",o):n.startOf(i,s)),+i)}function Hm(e,t,n,r){const s=an.length;for(let o=an.indexOf(e);o=an.indexOf(n);o--){const i=an[o];if(Da[i].common&&e._adapter.diff(s,r,i)>=t-1)return i}return an[n?an.indexOf(n):0]}function QO(e){for(let t=an.indexOf(e)+1,n=an.length;t=t?n[r]:n[s];e[o]=!0}}function JO(e,t,n,r){const s=e._adapter,o=+s.startOf(t[0].value,r),i=t[t.length-1].value;let a,l;for(a=o;a<=i;a=+s.add(a,1,r))l=n[a],l>=0&&(t[l].major=!0);return t}function Bm(e,t,n){const r=[],s={},o=t.length;let i,a;for(i=0;i+t.value))}initOffsets(t=[]){let n=0,r=0,s,o;this.options.offset&&t.length&&(s=this.getDecimalForValue(t[0]),t.length===1?n=1-s:n=(this.getDecimalForValue(t[1])-s)/2,o=this.getDecimalForValue(t[t.length-1]),t.length===1?r=o:r=(o-this.getDecimalForValue(t[t.length-2]))/2);const i=t.length<3?.5:.25;n=sn(n,0,i),r=sn(r,0,i),this._offsets={start:n,end:r,factor:1/(n+1+r)}}_generate(){const t=this._adapter,n=this.min,r=this.max,s=this.options,o=s.time,i=o.unit||Hm(o.minUnit,n,r,this._getLabelCapacity(n)),a=Re(s.ticks.stepSize,1),l=i==="week"?o.isoWeekday:!1,c=fa(l)||l===!0,d={};let u=n,f,g;if(c&&(u=+t.startOf(u,"isoWeek",l)),u=+t.startOf(u,c?"day":i),t.diff(r,n,i)>1e5*a)throw new Error(n+" and "+r+" are too far apart with stepSize of "+a+" "+i);const m=s.ticks.source==="data"&&this.getDataTimestamps();for(f=u,g=0;f+p)}getLabelForValue(t){const n=this._adapter,r=this.options.time;return r.tooltipFormat?n.format(t,r.tooltipFormat):n.format(t,r.displayFormats.datetime)}format(t,n){const s=this.options.time.displayFormats,o=this._unit,i=n||s[o];return this._adapter.format(t,i)}_tickFormatFunction(t,n,r,s){const o=this.options,i=o.ticks.callback;if(i)return mt(i,[t,n,r],this);const a=o.time.displayFormats,l=this._unit,c=this._majorUnit,d=l&&a[l],u=c&&a[c],f=r[n],g=c&&u&&f&&f.major;return this._adapter.format(t,s||(g?u:d))}generateTickLabels(t){let n,r,s;for(n=0,r=t.length;n0?a:1}getDataTimestamps(){let t=this._cache.data||[],n,r;if(t.length)return t;const s=this.getMatchingVisibleMetas();if(this._normalized&&s.length)return this._cache.data=s[0].controller.getAllParsedValues(this);for(n=0,r=s.length;n=e[r].pos&&t<=e[s].pos&&({lo:r,hi:s}=Qc(e,"pos",t)),{pos:o,time:a}=e[r],{pos:i,time:l}=e[s]):(t>=e[r].time&&t<=e[s].time&&({lo:r,hi:s}=Qc(e,"time",t)),{time:o,pos:a}=e[r],{time:i,pos:l}=e[s]);const c=i-o;return c?a+(l-a)*(t-o)/c:a}class jm extends Ia{constructor(t){super(t),this._table=[],this._minPos=void 0,this._tableRange=void 0}initOffsets(){const t=this._getTimestampsForTable(),n=this._table=this.buildLookupTable(t);this._minPos=La(n,this.min),this._tableRange=La(n,this.max)-this._minPos,super.initOffsets(t)}buildLookupTable(t){const{min:n,max:r}=this,s=[],o=[];let i,a,l,c,d;for(i=0,a=t.length;i=n&&c<=r&&s.push(c);if(s.length<2)return[{time:n,pos:0},{time:r,pos:1}];for(i=0,a=s.length;is-o)}_getTimestampsForTable(){let t=this._cache.all||[];if(t.length)return t;const n=this.getDataTimestamps(),r=this.getLabelTimestamps();return n.length&&r.length?t=this.normalize(n.concat(r)):t=n.length?n:r,t=this._cache.all=t,t}getDecimalForValue(t){return(La(this._table,t)-this._minPos)/this._tableRange}getValueForPixel(t){const n=this._offsets,r=this.getDecimalForPixel(t)/n.factor-n.end;return La(this._table,r*this._tableRange+this._minPos,!0)}}Pe(jm,"id","timeseries"),Pe(jm,"defaults",Ia.defaults);const Wm="label";function Gm(e,t){typeof e=="function"?e(t):e&&(e.current=t)}function RO(e,t){const n=e.options;n&&t&&Object.assign(n,t)}function Ym(e,t){e.labels=t}function Xm(e,t,n=Wm){const r=[];e.datasets=t.map(s=>{const o=e.datasets.find(i=>i[n]===s[n]);return!o||!s.data||r.includes(o)?{...s}:(r.push(o),Object.assign(o,s),o)})}function e5(e,t=Wm){const n={labels:[],datasets:[]};return Ym(n,e.labels),Xm(n,e.datasets,t),n}function t5(e,t){const{height:n=150,width:r=300,redraw:s=!1,datasetIdKey:o,type:i,data:a,options:l,plugins:c=[],fallbackContent:d,updateMode:u,...f}=e,g=v.useRef(null),m=v.useRef(null),p=()=>{g.current&&(m.current=new zo(g.current,{type:i,data:e5(a,o),options:l&&{...l},plugins:c}),Gm(t,m.current))},y=()=>{Gm(t,null),m.current&&(m.current.destroy(),m.current=null)};return v.useEffect(()=>{!s&&m.current&&l&&RO(m.current,l)},[s,l]),v.useEffect(()=>{!s&&m.current&&Ym(m.current.config.data,a.labels)},[s,a.labels]),v.useEffect(()=>{!s&&m.current&&a.datasets&&Xm(m.current.config.data,a.datasets,o)},[s,a.datasets]),v.useEffect(()=>{m.current&&(s?(y(),setTimeout(p)):m.current.update(u))},[s,l,a.labels,a.datasets,u]),v.useEffect(()=>{m.current&&(y(),setTimeout(p))},[i]),v.useEffect(()=>(p(),()=>y()),[]),h.jsx("canvas",{ref:g,role:"img",height:n,width:r,...f,children:d})}const n5=v.forwardRef(t5);function Km(e,t){return zo.register(t),v.forwardRef((n,r)=>h.jsx(n5,{...n,ref:r,type:e}))}const r5=Km("bar",xa),s5=Km("doughnut",Ao);zo.register(Sd,_d,Oa,FO,Pm,Mm);const o5=({labels:e,datasets:t,height:n=200,showLegend:r=!1,showGrid:s=!1,showYGrid:o=!1,showYAxis:i=!1,className:a="",id:l,editorAttrs:c})=>{const d=v.useMemo(()=>({labels:e??[],datasets:(t??[]).map((m,p)=>({label:m.label,data:m.data,backgroundColor:m.backgroundColor||(p===0?"#7C3AED":"#DDD6FE"),borderRadius:m.borderRadius??4,barThickness:16,maxBarThickness:20,yAxisID:m.yAxisID??"y"}))}),[e,t]),u=v.useMemo(()=>(t??[]).some(m=>m.yAxisID==="y1"),[t]),f=v.useMemo(()=>({responsive:!0,maintainAspectRatio:!1,animation:{duration:750,easing:"easeOutQuart"},animations:{x:{duration:0},y:{from:m=>{if(m.type==="data")return m.chart.scales.y.getPixelForValue(0)}}},transitions:{active:{animation:{duration:200}},resize:{animation:{duration:0}}},plugins:{legend:{display:r,position:"top"},tooltip:{backgroundColor:"#1e293b",titleColor:"#fff",bodyColor:"#fff",padding:12,cornerRadius:8}},scales:{x:{grid:{display:s},border:{display:!1},ticks:{color:"#94a3b8",font:{size:11}}},y:{type:"linear",position:"left",grid:{display:o,color:"#e2e8f0"},border:{display:!1},ticks:{display:i,color:"#94a3b8",font:{size:11},padding:8},beginAtZero:!0,grace:"5%"},...u?{y1:{type:"linear",position:"right",grid:{display:!1},border:{display:!1},ticks:{display:i,color:"#94a3b8",font:{size:11},padding:8},beginAtZero:!0,grace:"5%"}}:{}}}),[r,s,o,i,u]),g=typeof n=="number"?n:/^\d+$/.test(String(n))?`${n}px`:n;return h.jsx("div",{className:`bar-chart-container ${a}`,style:{height:g},id:l,...c,children:h.jsx(r5,{data:d,options:f})})},i5=v.memo(o5,(e,t)=>JSON.stringify(e.labels)===JSON.stringify(t.labels)&&JSON.stringify(e.datasets)===JSON.stringify(t.datasets)&&e.height===t.height&&e.showLegend===t.showLegend&&e.showGrid===t.showGrid&&e.showYGrid===t.showYGrid&&e.showYAxis===t.showYAxis&&e.className===t.className&&e.id===t.id&&e.editorAttrs===t.editorAttrs);zo.register(Vo,Pm,Mm);const a5=({data:e,centerLabel:t,centerValue:n,size:r=200,cutout:s="70%",showLegend:o=!1,className:i="",id:a,editorAttrs:l})=>{const c=v.useMemo(()=>({labels:(e??[]).map(f=>f.name),datasets:[{data:(e??[]).map(f=>f.value),backgroundColor:(e??[]).map(f=>f.color),borderWidth:0,hoverOffset:4}]}),[e]),d=v.useMemo(()=>({responsive:!0,maintainAspectRatio:!1,cutout:s,plugins:{legend:{display:o},tooltip:{backgroundColor:"#1e293b",titleColor:"#fff",bodyColor:"#fff",padding:12,cornerRadius:8,callbacks:{label:f=>` $${f.parsed.toLocaleString()}`}}}}),[s,o]),u=typeof r=="number"?r:/^\d+$/.test(String(r))?`${r}px`:r;return h.jsx("div",{className:`donut-chart-wrapper ${i}`,id:a,...l,children:h.jsxs("div",{className:"relative",style:{width:u,height:u},children:[h.jsx(s5,{data:c,options:d}),(t||n)&&h.jsxs("div",{className:"absolute inset-0 flex flex-col items-center justify-center pointer-events-none",children:[t&&h.jsx("span",{className:"text-xs text-slate-500 dark:text-slate-400",children:t}),n&&h.jsx("span",{className:"text-lg font-bold text-slate-900 dark:text-white",children:n})]})]})})},l5=({id:e,className:t,style:n,children:r,editorAttrs:s})=>h.jsx("div",{id:e,className:t,style:n,...s,children:r}),c5=({children:e,cols:t=1,responsive:n,gap:r,rowGap:s,colGap:o,autoRows:i,autoCols:a,flow:l="row",className:c="",style:d,onClick:u,id:f,editorAttrs:g})=>{const m=["grid"];if(t&&m.push(`grid-cols-${t}`),n&&(n.sm&&m.push(`sm:grid-cols-${n.sm}`),n.md&&m.push(`md:grid-cols-${n.md}`),n.lg&&m.push(`lg:grid-cols-${n.lg}`),n.xl&&m.push(`xl:grid-cols-${n.xl}`),n["2xl"]&&m.push(`2xl:grid-cols-${n["2xl"]}`)),r!==void 0&&r>0&&m.push(`gap-${r}`),s!==void 0&&s>0&&m.push(`gap-y-${s}`),o!==void 0&&o>0&&m.push(`gap-x-${o}`),i){const y={auto:"auto-rows-auto",min:"auto-rows-min",max:"auto-rows-max",fr:"auto-rows-fr"};m.push(y[i])}if(a){const y={auto:"auto-cols-auto",min:"auto-cols-min",max:"auto-cols-max",fr:"auto-cols-fr"};m.push(y[a])}const p={row:"grid-flow-row",col:"grid-flow-col",dense:"grid-flow-dense","row-dense":"grid-flow-row-dense","col-dense":"grid-flow-col-dense"};return m.push(p[l]),c&&m.push(c),h.jsx(I,{className:m.join(" "),style:d,onClick:u,id:f,...g,children:e})},d5=({children:e,direction:t="row",justify:n="start",align:r="stretch",wrap:s="nowrap",gap:o=0,grow:i=!1,shrink:a=!0,className:l="",style:c,onClick:d,id:u,editorAttrs:f})=>{const g=["flex"],m={row:"flex-row","row-reverse":"flex-row-reverse",col:"flex-col","col-reverse":"flex-col-reverse"};g.push(m[t]);const p={wrap:"flex-wrap",nowrap:"flex-nowrap","wrap-reverse":"flex-wrap-reverse"};g.push(p[s]);const y={start:"justify-start",end:"justify-end",center:"justify-center",between:"justify-between",around:"justify-around",evenly:"justify-evenly"};g.push(y[n]);const b={start:"items-start",end:"items-end",center:"items-center",baseline:"items-baseline",stretch:"items-stretch"};return g.push(b[r]),o>0&&g.push(`gap-${o}`),i===!0?g.push("flex-grow"):typeof i=="number"&&i>0&&g.push(`flex-grow-${i}`),a===!1?g.push("shrink-0"):a===!0?g.push("flex-shrink"):typeof a=="number"&&a>0&&g.push(`flex-shrink-${a}`),l&&g.push(l),h.jsx(I,{className:g.join(" "),style:c,onClick:d,id:u,...f,children:e})},u5=({title:e,subtitle:t,children:n,padding:r="none",background:s="none",maxWidth:o,centered:i=!1,border:a=!1,shadow:l="none",rounded:c=!1,className:d="",style:u,onClick:f,id:g,editorAttrs:m})=>{const p=[],y={none:"",sm:"p-2",md:"p-4",lg:"p-6",xl:"p-8"};y[r]&&p.push(y[r]);const b={none:"",white:"bg-white dark:bg-gray-700",gray:"bg-gray-50 dark:bg-gray-800",primary:"bg-blue-50 dark:bg-blue-900/20",secondary:"bg-gray-50 dark:bg-gray-800"};if(s!=="none"&&p.push(b[s]),o){const x={sm:"max-w-sm",md:"max-w-md",lg:"max-w-lg",xl:"max-w-xl","2xl":"max-w-2xl","4xl":"max-w-4xl","6xl":"max-w-6xl","7xl":"max-w-7xl",full:"max-w-full"};p.push(x[o])}i&&p.push("mx-auto"),a&&p.push("border","border-gray-200","dark:border-gray-700");const _={none:"",sm:"shadow-sm",md:"shadow-md",lg:"shadow-lg",xl:"shadow-xl"};return l!=="none"&&p.push(_[l]),c&&p.push("rounded-lg"),d&&p.push(d),h.jsxs(Fd,{className:p.join(" "),style:u,onClick:f,id:g,...m,children:[(e||t)&&h.jsxs("div",{className:"mb-4",children:[e&&h.jsx("h2",{className:"text-2xl font-bold text-gray-900 dark:text-white",children:e}),t&&h.jsx("p",{className:"mt-1 text-sm text-gray-600 dark:text-gray-400",children:t})]}),n]})},f5=({leftWidth:e="250px",rightWidth:t="300px",leftSlot:n,centerSlot:r,rightSlot:s,className:o="",style:i,id:a,editorAttrs:l})=>{const c=`flex flex-row w-full h-full ${o}`.trim(),d={width:e,flexShrink:0},u={flex:1,minWidth:0},f={width:t,flexShrink:0};return h.jsxs(I,{className:c,style:i,id:a,...l,children:[h.jsx(I,{className:"flex flex-col",style:d,children:n}),h.jsx(I,{className:"flex flex-col",style:u,children:r}),h.jsx(I,{className:"flex flex-col",style:f,children:s})]})},h5={language:"json",theme:"vs-dark",options:{automaticLayout:!0,minimap:{enabled:!0},scrollBeyondLastLine:!1,fontSize:14,tabSize:2,formatOnPaste:!0,formatOnType:!0,wordWrap:"on",folding:!0,lineNumbers:"on",renderWhitespace:"selection"}},qm=[{uri:`${window.location.origin}/schemas/layout.json`,fileMatch:["*layout*.json"],schema:{type:"object",properties:{version:{type:"string"},endpoint:{type:"string"},components:{type:"array"}},required:["version","endpoint","components"]}}],g5={"vs/nls":{availableLanguages:{"*":"ko"}}};function p5(e){e.languages.json.jsonDefaults.setDiagnosticsOptions({validate:!0,schemas:qm,enableSchemaRequest:!0,schemaValidation:"error",schemaRequest:"error"})}const m5={identifier:"sirsoft-admin_basic",vendor:"sirsoft",name:{ko:"Admin Basic",en:"Admin Basic"},version:"1.0.4",license:"MIT",description:{ko:"그누보드7 기본 관리자 템플릿",en:"Gnuboard7 default admin template"},type:"admin",locales:["ko","en"],author:{name:"sirsoft",email:"contact@sirsoft.com",url:"https://sirsoft.com"},release_date:"2026-05-15",g7_version:">=7.0.6",dependencies:{modules:{},plugins:{}},assets:{css:["assets/css/main.css","assets/css/ui-system.css"],js:["dist/components.js"],fonts:["assets/fonts/"],images:["assets/images/"]},components:{basic:["a","button","checkbox","div","form","h1","h2","h3","hr","icon","img","input","label","li","nav","p","section","select","span","svg","table","tbody","td","textarea","th","thead","tr","ul"],composite:["action-menu","admin-footer","admin-header","admin-sidebar","alert-dialog","breadcrumb","card","confirm-dialog","data-grid","dialog","dropdown","empty-state","filter-group","icon-button","loading-spinner","modal","page-header","pagination","product-card","search-bar","stat-card","status-badge","tab-navigation","template-card","toast"],layout:["container","flex","grid","section"]},preview:{thumbnail:"preview/thumbnail.png",screenshots:["preview/screenshot1.png","preview/screenshot2.png"]},error_config:{layouts:{401:"errors/401",403:"errors/403",404:"errors/404",500:"errors/500",503:"errors/503",maintenance:"errors/maintenance"}},github_url:"https://github.com/gnuboard/g7-template-sirsoft-admin_basic",github_changelog_url:"https://github.com/gnuboard/g7-template-sirsoft-admin_basic/blob/main/CHANGELOG.md",externals:[{id:"fontawesome",type:"style",url:"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css",preconnect:"https://cdnjs.cloudflare.com"},{id:"pretendard",type:"webfont",url:"https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css",preconnect:"https://cdn.jsdelivr.net",crossorigin:"anonymous"},{id:"flag-icons",type:"style",url:"https://cdn.jsdelivr.net/npm/flag-icons@7.2.3/css/flag-icons.min.css"}]},rr=window.G7Core?.createLogger?.("Handler:SetTheme")??{log:(...e)=>console.log("[Handler:SetTheme]",...e),warn:(...e)=>console.warn("[Handler:SetTheme]",...e),error:(...e)=>console.error("[Handler:SetTheme]",...e)},Ed=["auto","light","dark"],Zm="g7_color_scheme",b5=e=>e==="auto"?window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light":e,Pa=e=>{const t=b5(e);document.documentElement.setAttribute("data-theme",t),t==="dark"?document.documentElement.classList.add("dark"):document.documentElement.classList.remove("dark")};function y5(){try{const e=localStorage.getItem(Zm),t=e&&Ed.includes(e)?e:"auto";Pa(t);try{window.G7Core?.state?.setGlobal?.({theme:t})}catch(n){rr.warn("Failed to set global theme state:",n)}rr.log("Initial theme applied:",t)}catch(e){rr.warn("Failed to load initial theme:",e),Pa("auto")}}async function v5(e,t){const n=e?.target;if(n&&typeof n=="string"&&Ed.includes(n)){Pa(n);try{window.G7Core?.state?.setGlobal?.({theme:n})}catch(r){rr.warn("Failed to set global theme state:",r)}return}y5()}async function x5(e,t){let n;if(e?.target&&typeof e.target=="string"&&!e.target.includes("{{")&&(n=e.target),!n||typeof n!="string"){rr.warn("Invalid theme:",n);return}if(!Ed.includes(n)){rr.warn("Unsupported theme:",n);return}const r=n;try{localStorage.setItem(Zm,r)}catch(s){rr.error("Failed to save theme to localStorage:",s);return}Pa(r);try{window.G7Core?.state?.setGlobal?.({theme:r})}catch(s){rr.warn("Failed to set global theme state:",s)}rr.log("Theme changed to:",r)}const Uo=window.G7Core?.createLogger?.("Handler:InitMenu")??{log:(...e)=>console.log("[Handler:InitMenu]",...e),warn:(...e)=>console.warn("[Handler:InitMenu]",...e),error:(...e)=>console.error("[Handler:InitMenu]",...e)};function Qm(e,t){for(const n of e){if(n.slug===t)return n;if(n.children&&n.children.length>0){const r=Qm(n.children,t);if(r)return r}}return null}function Jm(e){return new URLSearchParams(window.location.search).get(e)}function w5(e){return window.G7Core?.state?.getDataSource?.(e)}async function k5(e,t=30,n=100){for(let r=0;r0)return o;await new Promise(i=>setTimeout(i,n))}return null}async function C5(e,t){const n=window.G7Core,r=Jm("menu"),s=Jm("mode");if(!r){Uo.log("[initMenuFromUrl] No menu parameter in URL");return}if(!n?.state?.set){Uo.warn("[initMenuFromUrl] G7Core.state.set not available");return}const o=await k5("menus");if(!o){Uo.warn("[initMenuFromUrl] menus data not available after waiting");return}const i=Qm(o,r);if(!i){Uo.warn("[initMenuFromUrl] Menu not found with slug:",r);return}const a=s==="edit"?"edit":"view";n.state.set({selectedMenuId:i.id,selectedMenu:i,panelMode:a}),a==="edit"&&n.state.set({formData:{name:i.name,slug:i.slug,url:i.url,icon:i.icon,parent_id:i.parent_id,module_id:i.module_id,is_active:i.is_active}}),Uo.log("[initMenuFromUrl] Menu initialized from URL:",{slug:r,mode:a,menuId:i.id})}const Rt=window.G7Core?.createLogger?.("Handler:FilterVisibility")??{log:(...e)=>console.log("[Handler:FilterVisibility]",...e),warn:(...e)=>console.warn("[Handler:FilterVisibility]",...e),error:(...e)=>console.error("[Handler:FilterVisibility]",...e)},$a="g7_filter_visibility_";async function S5(e,t){const{storageKey:n,defaultFilters:r=[],stateKey:s="visibleFilters"}=e?.params||{};if(!n){Rt.warn("[initFilterVisibility] storageKey is required");return}const o=`${$a}${n}`;try{const i=localStorage.getItem(o);let a;if(i){const l=JSON.parse(i);Array.isArray(l)?(a=l,Rt.log(`[initFilterVisibility] Loaded from localStorage: ${a.join(", ")}`)):(a=r,Rt.warn("[initFilterVisibility] Invalid saved data format, using defaults"))}else a=r,Rt.log(`[initFilterVisibility] No saved data, using defaults: ${r.join(", ")}`);t?.setState&&t.setState({[s]:a})}catch(i){Rt.error("[initFilterVisibility] Failed to load filter visibility:",i),t?.setState&&t.setState({[s]:r})}}async function _5(e,t){const{storageKey:n,filters:r}=e?.params||{};if(!n){Rt.warn("[saveFilterVisibility] storageKey is required");return}if(!Array.isArray(r)){Rt.warn("[saveFilterVisibility] filters must be an array");return}const s=`${$a}${n}`;try{localStorage.setItem(s,JSON.stringify(r)),Rt.log(`[saveFilterVisibility] Saved to localStorage: ${r.join(", ")}`)}catch(o){Rt.error("[saveFilterVisibility] Failed to save filter visibility:",o)}}async function E5(e,t){const{storageKey:n,filterId:r,stateKey:s="visibleFilters"}=e?.params||{};if(!n||!r){Rt.warn("[toggleFilterVisibility] storageKey and filterId are required");return}const o=t?.state?.[s]||[];let i;o.includes(r)?i=o.filter(l=>l!==r):i=[...o,r],t?.setState&&t.setState({[s]:i});const a=`${$a}${n}`;try{localStorage.setItem(a,JSON.stringify(i)),Rt.log(`[toggleFilterVisibility] Toggled ${r}: ${i.join(", ")}`)}catch(l){Rt.error("[toggleFilterVisibility] Failed to save:",l)}}async function M5(e,t){const{storageKey:n,defaultFilters:r=[],stateKey:s="visibleFilters"}=e?.params||{};if(!n){Rt.warn("[resetFilterVisibility] storageKey is required");return}const o=`${$a}${n}`;try{localStorage.setItem(o,JSON.stringify(r)),t?.setState&&t.setState({[s]:r}),Rt.log(`[resetFilterVisibility] Reset to defaults: ${r.join(", ")}`)}catch(i){Rt.error("[resetFilterVisibility] Failed to reset:",i)}}const Is=window.G7Core?.createLogger?.("Handler:MultilingualTag")??{log:(...e)=>console.log("[Handler:MultilingualTag]",...e),warn:(...e)=>console.warn("[Handler:MultilingualTag]",...e),error:(...e)=>console.error("[Handler:MultilingualTag]",...e)};async function N5(e,t){const n=window.G7Core;if(!n){Is.error("G7Core not available");return}const r=n.state?.getGlobal?.()?.multilingualTagEdit;if(!r){Is.warn("No multilingualTagEdit state found"),n.modal?.close?.();return}const{fieldName:s,editingIndex:o,values:i,statePath:a,currentTags:l}=r;if(!Object.values(i||{}).some(g=>typeof g=="string"&&g.trim()!=="")){Is.warn("No values to save"),n.modal?.close?.(),Md(n);return}const d=Array.isArray(l)?l:[];let u;o==null?u=[...d,i]:u=d.map((g,m)=>m===o?i:g);const f=a||(s?.startsWith("form.")?s:`form.${s}`);n.state?.setParentLocal?.({[f]:u}),n.modal?.close?.(),Md(n)}async function T5(e,t){const n=window.G7Core;if(!n){Is.error("G7Core not available");return}n.modal?.close?.(),Md(n)}async function O5(e,t){const n=window.G7Core;if(!n){Is.error("G7Core not available");return}const r=e?.params?.locale;if(!r){Is.warn("No locale specified");return}const s=t?.event;let o="";s?.target?.value!==void 0?o=s.target.value:s?.value!==void 0&&(o=s.value);const a=(n.state?.getGlobal?.()||{}).multilingualTagEdit||{},l={...a.values||{},[r]:o};n.dispatch?.({handler:"setState",params:{target:"global",multilingualTagEdit:{...a,values:l}}})}function Md(e){e.dispatch?.({handler:"setState",params:{target:"global",multilingualTagEdit:null}})}function Rm(e){const t=e.getFullYear(),n=String(e.getMonth()+1).padStart(2,"0"),r=String(e.getDate()).padStart(2,"0"),s=String(e.getHours()).padStart(2,"0"),o=String(e.getMinutes()).padStart(2,"0"),i=String(e.getSeconds()).padStart(2,"0");return`${t}-${n}-${r}T${s}:${o}:${i}`}function A5(e){const t=new Date,n=new Date(t.getFullYear(),t.getMonth(),t.getDate(),23,59,59),r=Rm(n),s=new Date(t.getFullYear(),t.getMonth(),t.getDate(),0,0,0);switch(e){case"today":break;case"week":s.setDate(s.getDate()-6);break;case"month":s.setMonth(s.getMonth()-1);break;case"3months":s.setMonth(s.getMonth()-3);break;case"6months":s.setMonth(s.getMonth()-6);break;case"1year":s.setFullYear(s.getFullYear()-1);break}return{startDate:Rm(s),endDate:r}}function D5(e){const{preset:t}=e.params||{preset:"today"},{startDate:n,endDate:r}=A5(t);return{startDate:n,endDate:r,preset:t}}const Bo=window.G7Core?.createLogger?.("Handler:Sidebar")??{log:(...e)=>console.log("[Handler:Sidebar]",...e),warn:(...e)=>console.warn("[Handler:Sidebar]",...e),error:(...e)=>console.error("[Handler:Sidebar]",...e)},eb="g7_admin_sidebar_collapsed";function tb(){try{return localStorage.getItem(eb)==="1"}catch{return!1}}function I5(e){try{localStorage.setItem(eb,e?"1":"0")}catch(t){Bo.error("Failed to save sidebar state to localStorage:",t)}try{window.G7Core?.state?.setGlobal?.({sidebarCollapsed:e})}catch(t){Bo.warn("Failed to set global sidebar state:",t)}}function nb(){const e=tb();try{window.G7Core?.state?.setGlobal?.({sidebarCollapsed:e})}catch(t){Bo.warn("Failed to restore sidebar state:",t)}Bo.log("Initial sidebar collapsed state restored:",e)}async function L5(e,t){nb()}async function P5(e,t){const r=!(()=>{try{return!!window.G7Core?.state?.getGlobal?.()?.sidebarCollapsed}catch{return tb()}})();I5(r),Bo.log("Sidebar collapsed toggled to:",r)}const Nd=window.G7Core?.createLogger?.("Handler:DownloadAttachment")??{log:(...e)=>console.log("[Handler:DownloadAttachment]",...e),warn:(...e)=>console.warn("[Handler:DownloadAttachment]",...e),error:(...e)=>console.error("[Handler:DownloadAttachment]",...e)};async function $5(e,t){const n=window.G7Core,{url:r,filename:s}=e?.params||{};if(!r){Nd.warn("다운로드 URL 이 없어 요청을 건너뜁니다.");return}if(!n?.api?.get){Nd.error("G7Core.api.get 이 초기화되지 않아 다운로드를 실행할 수 없습니다.");return}try{const o=await n.api.get(r,{responseType:"blob"});if(o){const i=URL.createObjectURL(o),a=document.createElement("a");a.href=i,a.download=s||"",document.body.appendChild(a),a.click(),document.body.removeChild(a),URL.revokeObjectURL(i)}}catch(o){Nd.error("첨부파일 다운로드 실패",o),n?.toast?.error?.(n?.t?.("common.download_failed")??"Download failed")}}const Fa=window.G7Core?.createLogger?.("Handler:DetectAssetUrlMode")??{log:(...e)=>console.log("[Handler:DetectAssetUrlMode]",...e),warn:(...e)=>console.warn("[Handler:DetectAssetUrlMode]",...e),error:(...e)=>console.error("[Handler:DetectAssetUrlMode]",...e)},F5="G7_ASSET_PROBE_OK";async function rb(e){try{const t=await fetch(e,{cache:"no-store",credentials:"omit"});if(!t.ok)return!1;const n=t.headers.get("content-type")??"";return/javascript|ecmascript/i.test(n)?(await t.text()).includes(F5):!1}catch{return!1}}async function sb(){const[e,t]=await Promise.all([rb("/api/system/asset-probe.js"),rb("/api/system/asset-probe")]);return e&&t?"extension":!e&&t?"extensionless":"unavailable"}function z5(){return window.G7Config?.settings?.general?.asset_url_mode??"extension"}async function V5(e){try{const t=await sb();if(t==="unavailable")return;const n=z5();if(t===n)return;window.G7Core?.state?.setGlobal?.({assetUrlModeDrift:{detected:t,stored:n}})}catch(t){Fa.warn("자산 URL 방식 대조 실패 (대시보드 동작에는 영향 없음):",t)}}async function H5(e,t){const n=window.G7Core,r=t?.setState??n?.state?.setLocal,s=o=>{typeof r=="function"?r({asset_url_mode_detect_status:o}):Fa.warn("setState 를 찾지 못해 감지 상태를 폼에 반영하지 못했습니다")};try{s("detecting");const o=await sb();if(o==="unavailable"){s("unavailable");return}typeof r=="function"?r({"general.asset_url_mode":o,asset_url_mode_detect_status:o}):Fa.warn("setState 를 찾지 못해 감지 결과를 폼에 반영하지 못했습니다")}catch(o){Fa.error("자산 URL 방식 감지 실패:",o),s("unavailable")}}const za={detectAssetUrlMode:H5,checkAssetUrlModeDrift:V5,setTheme:x5,initTheme:v5,scrollToSection:fu,initMenuFromUrl:C5,initFilterVisibility:S5,saveFilterVisibility:_5,toggleFilterVisibility:E5,resetFilterVisibility:M5,saveMultilingualTag:N5,cancelMultilingualTag:T5,updateMultilingualTagValue:O5,setDateRange:D5,toggleSidebar:P5,initSidebar:L5,downloadAttachment:$5},jo=window.G7Core?.createLogger?.("Template:sirsoft-admin_basic:IdentityLauncher")??{log:(...e)=>console.log("[Template:sirsoft-admin_basic:IdentityLauncher]",...e),warn:(...e)=>console.warn("[Template:sirsoft-admin_basic:IdentityLauncher]",...e),error:(...e)=>console.error("[Template:sirsoft-admin_basic:IdentityLauncher]",...e)};function U5(){const t=window.G7Core?.api?.getToken?.()??null;return t?{Authorization:`Bearer ${t}`}:{}}function B5(){try{const e=typeof window<"u"?window.localStorage?.getItem("g7_locale"):null;return e?{"Accept-Language":e}:{}}catch{return{}}}async function j5(e,t=null){const n={purpose:e.purpose};e.provider_id&&(n.provider_id=e.provider_id),t&&(n.target=t);const r=e.challenge_start_url||"/api/identity/challenges",s=await fetch(r,{method:"POST",credentials:"include",headers:{"Content-Type":"application/json",Accept:"application/json",...B5(),...U5()},body:JSON.stringify(n)}),o=await s.json().catch(()=>null);if(!s.ok||!o?.success){const a=o?.message??`Challenge 시작 실패 (HTTP ${s.status})`;throw new Error(a)}const i=o.data??o;return{id:i.id,expires_at:i.expires_at,render_hint:i.render_hint??e.render_hint??"text_code",public_payload:i.public_payload,redirect_url:i.redirect_url,max_attempts:typeof i.max_attempts=="number"?i.max_attempts:void 0}}async function W5(e){const t=window.G7Core,n=t?.identity;if(!t?.dispatch||!t?.state||!n)return jo.error("G7Core 또는 G7Core.identity 가 초기화되지 않아 launcher 를 실행할 수 없습니다."),{status:"failed",failureCode:"G7_NOT_READY"};if(e.render_hint==="external_redirect"||e.redirect_url)return n.redirectExternally(e);const r=e.target&&(e.target.email||e.target.phone)?e.target:null;let s;try{s=await j5(e,r)}catch(m){jo.error("Challenge 시작 실패:",m);try{await t.dispatch({handler:"toast",params:{type:"error",message:m instanceof Error?m.message:"본인인증 시작에 실패했습니다."}})}catch{}return{status:"failed",failureCode:"CHALLENGE_START_FAILED",reason:m instanceof Error?m.message:String(m)}}if(s.redirect_url)return n.redirectExternally({...e,render_hint:"external_redirect",redirect_url:s.redirect_url});const o=s.expires_at?new Date(s.expires_at).getTime():0,i=o>0?Math.max(0,Math.floor((o-Date.now())/1e3)):0,a=typeof s.max_attempts=="number"?s.max_attempts:0;t.state.set({identityChallenge:{policy_key:e.policy_key,purpose:e.purpose,provider_id:e.provider_id??null,render_hint:s.render_hint,challenge_id:s.id,expires_at:s.expires_at,public_payload:s.public_payload??{},target:r,code:"",error:null,attempts:0,maxAttempts:a,remainingSeconds:i,resendCooldown:0}});let l=o,c=0;const d=window.setInterval(()=>{const m=l>0?Math.max(0,Math.floor((l-Date.now())/1e3)):0;c=Math.max(0,c-1),t.state.set({identityChallenge:{remainingSeconds:m,resendCooldown:c}})},1e3);let u=null;try{u=t.state.subscribe?.(m=>{const p=m?.identityChallenge?.expires_at;if(typeof p=="string"){const b=new Date(p).getTime();Number.isFinite(b)&&b!==l&&(l=b)}const y=m?.identityChallenge?.resendCooldown;typeof y=="number"&&y>c&&(c=y)})??null}catch{}const f=n.createDeferred();try{await t.dispatch({handler:"openModal",target:"identity-challenge-modal"})}catch(m){return jo.error("openModal 실패 — 풀페이지로 폴백:",m),window.clearInterval(d),u?.(),n.redirectExternally({...e,render_hint:"external_redirect",redirect_url:`/admin/identity/challenge?challenge_id=${encodeURIComponent(s.id)}&return=${encodeURIComponent(window.location.href)}`})}const g=await f;return window.clearInterval(d),u?.(),g}function G5(){const e=window.G7Core?.identity;if(!e?.setLauncher){jo.warn("G7Core.identity 가 아직 초기화되지 않아 launcher 등록을 건너뜁니다. 코어 부트스트랩 순서를 확인하세요.");return}e.setLauncher(W5),jo.log("IDV launcher registered (sirsoft-admin_basic)")}function Y5(e){const t=e.icons;return Array.isArray(t)?t.filter(n=>!!n&&typeof n=="object"&&typeof n.value=="string"):[]}function X5(e,t){const n=e.label;return typeof n=="string"?n.startsWith("$t:")?t(n.slice(3)):n:e.value}function K5(e,t,n){if(n==="")return!0;const r=n.toLowerCase();return e.value.toLowerCase().includes(r)||t.toLowerCase().includes(r)?!0:(e.keywords??[]).some(s=>s.toLowerCase().includes(r))}function q5({entry:e}){const t=e.preview;return t?.html?h.jsx("span",{className:"g7le-icon-preview",dangerouslySetInnerHTML:{__html:t.html},style:Od}):t?.className?h.jsx("span",{className:`g7le-icon-preview ${t.className}`,style:Od}):h.jsx("i",{className:`fas fa-${e.value}`,style:Od,"aria-hidden":"true"})}function Z5({control:e,value:t,onChange:n,t:r}){const s=Y5(e),o=t==null?"":String(t),i=typeof e.iconColumns=="number"?e.iconColumns:6,a=e.iconSearchPlaceholder,l=typeof a=="string"&&a.startsWith("$t:")?r(a.slice(3)):r("layout_editor.icon_picker.search_placeholder"),[c,d]=v.useState("");if(s.length===0)return h.jsx("div",{className:"g7le-widget g7le-widget--icon-picker","data-testid":"g7le-widget-icon-picker",style:ob,children:h.jsx("input",{type:"text",className:"g7le-widget g7le-widget--icon-free","data-testid":"g7le-widget-icon-picker-free",value:o,placeholder:r("layout_editor.icon_picker.free_input_hint"),onChange:g=>n(g.target.value===""?void 0:g.target.value),style:ib})});const u=g=>X5(g,r),f=s.filter(g=>K5(g,u(g),c));return h.jsxs("div",{className:"g7le-widget g7le-widget--icon-picker","data-testid":"g7le-widget-icon-picker",style:ob,children:[h.jsx("input",{type:"text",className:"g7le-widget g7le-widget--icon-search","data-testid":"g7le-widget-icon-picker-search",value:c,placeholder:l,onChange:g=>d(g.target.value),style:ib}),h.jsxs("div",{className:"g7le-icon-grid","data-testid":"g7le-widget-icon-picker-grid",style:{...Q5,gridTemplateColumns:`repeat(${i}, minmax(0, 1fr))`},children:[h.jsx("button",{type:"button","data-testid":"g7le-icon-cell-none",title:r("layout_editor.icon_picker.none"),onClick:()=>n(void 0),"aria-pressed":o==="",style:o===""?ab:Td,children:h.jsx("span",{style:J5,children:"—"})}),f.map(g=>{const m=g.value===o;return h.jsx("button",{type:"button","data-testid":`g7le-icon-cell-${g.value}`,title:u(g),"aria-pressed":m,onClick:()=>n(g.value),style:m?ab:Td,children:h.jsx(q5,{entry:g})},g.value)})]}),f.length===0&&h.jsx("div",{"data-testid":"g7le-widget-icon-picker-empty",style:R5,children:r("layout_editor.icon_picker.no_results")})]})}const ob={display:"flex",flexDirection:"column",gap:6,width:"100%"},ib={padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,width:"100%"},Q5={display:"grid",gap:4,maxHeight:220,overflowY:"auto",padding:2},Td={display:"flex",alignItems:"center",justifyContent:"center",minHeight:34,padding:4,border:"1px solid #e2e8f0",borderRadius:6,background:"#fff",cursor:"pointer"},ab={...Td,border:"2px solid #2563eb",background:"#eff6ff"},Od={display:"inline-flex",alignItems:"center",justifyContent:"center",fontSize:16,lineHeight:1},J5={fontSize:10,color:"#475569",fontFamily:"monospace",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap",maxWidth:"100%"},R5={fontSize:11,color:"#94a3b8",fontStyle:"italic",padding:"4px 2px"},lb=130,In=20;function eA(e){const t=e?.arrayProp;return typeof t=="string"&&t?t:"tabs"}function tA(e){let t=0;for(const n of e){const r=typeof n.id=="number"?n.id:Number(n.id);Number.isFinite(r)&&r>t&&(t=r)}return t+1}function nA({node:e,params:t,cellBoxes:n,t:r,onPatchNode:s}){const o=eA(t),i=v.useMemo(()=>{const g=(e.props??{})[o];return typeof g=="string"||!Array.isArray(g)?null:g},[e,o]),a=v.useMemo(()=>{const g=new Map,m=`props.${o}.`;for(const p of n??[]){if(!p.path.startsWith(m))continue;const y=Number(p.path.slice(m.length));Number.isInteger(y)&&g.set(y,{top:p.top,left:p.left,width:p.width,height:p.height})}return g},[n,o]),l=v.useCallback(g=>{const m=e.props??{};s({...e,props:{...m,[o]:g}})},[e,o,s]),c=v.useCallback(g=>{if(!i)return;const p={id:tA(i),label:r("editor.tabnav_inplace.new_tab")},y=i.slice();y.splice(g,0,p),l(y)},[i,l,r]),d=v.useCallback(g=>{if(!i)return;const m=i.slice();m.splice(g,1),l(m)},[i,l]),u=v.useCallback((g,m)=>{if(!i)return;const p=g+m;if(p<0||p>=i.length)return;const y=i.slice(),[b]=y.splice(g,1);y.splice(p,0,b),l(y)},[i,l]);if(!i||a.size===0)return null;const f={position:"absolute",width:In,height:In,display:"flex",alignItems:"center",justifyContent:"center",fontSize:12,lineHeight:1,border:"1px solid rgba(0,0,0,0.15)",borderRadius:4,background:"#ffffff",color:"#1f2937",cursor:"pointer",boxShadow:"0 1px 2px rgba(0,0,0,0.18)",pointerEvents:"auto",padding:0,zIndex:lb};return h.jsx("div",{className:"g7le-tabnav-inplace","data-testid":"g7le-tabnav-inplace",style:{position:"absolute",inset:0,pointerEvents:"none",zIndex:lb},children:i.map((g,m)=>{const p=a.get(m);if(!p)return null;const y=p.left+p.width;return h.jsxs("div",{"data-testid":`g7le-tabnav-aff-${m}`,children:[h.jsx("button",{type:"button","data-testid":`g7le-tabnav-remove-${m}`,title:r("editor.tabnav_inplace.remove"),"aria-label":r("editor.tabnav_inplace.remove"),style:{...f,left:y-In/2,top:p.top-In/2,color:"#b91c1c"},onClick:b=>{b.preventDefault(),b.stopPropagation(),d(m)},children:"✕"}),m>0&&h.jsx("button",{type:"button","data-testid":`g7le-tabnav-move-left-${m}`,title:r("editor.tabnav_inplace.move_left"),"aria-label":r("editor.tabnav_inplace.move_left"),style:{...f,left:p.left,top:p.top+p.height-In/2},onClick:b=>{b.preventDefault(),b.stopPropagation(),u(m,-1)},children:"◀"}),m{b.preventDefault(),b.stopPropagation(),u(m,1)},children:"▶"}),m===0&&h.jsx("button",{type:"button","data-testid":"g7le-tabnav-add-0",title:r("editor.tabnav_inplace.add"),"aria-label":r("editor.tabnav_inplace.add"),style:{...f,left:p.left-In,top:p.top+p.height/2-In/2,color:"#15803d"},onClick:b=>{b.preventDefault(),b.stopPropagation(),c(0)},children:"+"}),h.jsx("button",{type:"button","data-testid":`g7le-tabnav-add-${m+1}`,title:r("editor.tabnav_inplace.add"),"aria-label":r("editor.tabnav_inplace.add"),style:{...f,left:y-In/2,top:p.top+p.height/2-In/2,color:"#15803d"},onClick:b=>{b.preventDefault(),b.stopPropagation(),c(m+1)},children:"+"})]},`tab-aff-${g.id}-${m}`)})})}const Ad=window.G7Core?.createLogger?.("Template:sirsoft-admin_basic:EditorWidgets")??{log:(...e)=>console.log("[Template:sirsoft-admin_basic:EditorWidgets]",...e),warn:(...e)=>console.warn("[Template:sirsoft-admin_basic:EditorWidgets]",...e)};function rA(){if(typeof window>"u")return;const e=window.G7Core?.layoutEditor;if(!e?.registerWidget){Ad.warn("G7Core.layoutEditor 미노출 — icon-picker 위젯 등록 보류");return}e.registerWidget("icon-picker",Z5),Ad.log("편집기 위젯 'icon-picker' 등록됨(Font Awesome 검색 그리드)"),e.registerCanvasOverlay&&(e.registerCanvasOverlay("tabnav",nA),Ad.log("편집기 캔버스 오버레이 'tabnav' 등록됨(TabNavigation 탭 인플레이스)"))}const Dd=window.G7Core?.createLogger?.("Template:sirsoft-admin_basic")??{log:(...e)=>console.log("[Template:sirsoft-admin_basic]",...e),warn:(...e)=>console.warn("[Template:sirsoft-admin_basic]",...e),error:(...e)=>console.error("[Template:sirsoft-admin_basic]",...e)};typeof window<"u"&&(window.G7TemplateHandlers=za);function cb(){if(typeof window<"u"){let e=0;const t=50,n=()=>{const r=window.G7Core?.getActionDispatcher?.();r?(Object.entries(za).forEach(([s,o])=>{r.registerHandler(s,o)}),Dd.log(`${Object.keys(za).length} custom handler(s) registered:`,Object.keys(za)),G5(),nb()):(e++,e<=t?(Dd.warn(`ActionDispatcher not found, retrying... (${e}/${t})`),setTimeout(n,100)):Dd.error("Failed to register handlers: ActionDispatcher not available after maximum retries"))};document.readyState==="complete"?n():window.addEventListener("load",n)}}return rA(),cb(),R.A=Yr,R.Accordion=dc,R.ActionMenu=Ps,R.AdminFooter=Ky,R.AdminHeader=Wy,R.AdminSidebar=jy,R.Alert=wu,R.AlertDialog=Ly,R.Badge=jd,R.BarChart=i5,R.Breadcrumb=bu,R.Button=ae,R.Card=Mb,R.CardGrid=by,R.CategoryTree=d_,R.Checkbox=En,R.ChipCheckbox=ZE,R.Code=Cb,R.CodeEditor=dx,R.ColumnSelector=bx,R.ConfirmDialog=pu,R.Container=l5,R.DataGrid=uy,R.Dialog=gu,R.Div=I,R.DonutChart=a5,R.Dropdown=vy,R.DropdownButton=QE,R.DropdownMenuItem=JE,R.DynamicFieldList=lM,R.EmptyState=Hy,R.ExtensionBadge=gh,R.FileInput=Pd,R.FileUploader=zp,R.FilterGroup=Cy,R.FilterVisibilitySelector=WE,R.Flex=d5,R.Form=Ga,R.FormField=hM,R.Fragment=_b,R.Grid=c5,R.H1=Ba,R.H2=ar,R.H3=Gr,R.H4=$d,R.Hr=Eb,R.HtmlContent=ol,R.HtmlEditor=HE,R.I=bt,R.Icon=oe,R.IconButton=Ay,R.IconName=de,R.IconSelect=s_,R.ImageGallery=Qi,R.Img=_n,R.Input=St,R.Label=ln,R.LanguageSelector=Xy,R.LayoutEditorHeader=Ry,R.LayoutFileList=ev,R.LayoutHistoryPanel=fx,R.LayoutWarnings=jE,R.Li=Wa,R.LoadingSpinner=By,R.LoginForm=hx,R.Modal=Qo,R.MultilingualInput=Za,R.MultilingualLocaleContext=Bc,R.MultilingualTabPanel=fM,R.MultilingualTagInput=fC,R.Nav=Yo,R.NotificationCenter=Gy,R.Ol=mb,R.Optgroup=xb,R.Option=vb,R.P=vt,R.PageHeader=qy,R.PageLoading=wx,R.PageTransitionIndicator=vx,R.Pagination=Xo,R.PermissionTree=l_,R.Pre=Sb,R.ProductCard=xy,R.RadioGroup=FE,R.RichSelect=KE,R.SearchBar=ky,R.SearchableDropdown=hC,R.Section=Fd,R.SectionHeader=Qy,R.SectionLayout=u5,R.Select=Xr,R.SlotContainer=XE,R.SortableMenuItem=rg,R.SortableMenuList=n_,R.Span=Q,R.StatCard=wy,R.StatusBadge=Ko,R.Svg=Zn,R.TabNavigation=Sy,R.TabNavigationScroll=Ty,R.Table=Ya,R.TagInput=cC,R.TagSelect=gC,R.Tbody=Ka,R.Td=en,R.TemplateCard=Jy,R.Textarea=ir,R.Tfoot=qa,R.Th=Kr,R.Thead=Xa,R.ThemeToggle=px,R.ThreeColumnLayout=f5,R.Toast=zy,R.Toggle=Qa,R.Tr=lr,R.Ul=ja,R.UnifiedIcon=kb,R.UserProfile=Yy,R.VersionList=Fu,R.configureJSONLanguageService=p5,R.defaultJSONSchemas=qm,R.executeImageDownload=Fc,R.initTemplate=cb,R.jsonEditorDefaults=h5,R.monacoInitOptions=g5,R.templateMetadata=m5,R.useCurrentMultilingualLocale=Gp,R.useImageGallery=Sp,Object.defineProperty(R,Symbol.toStringTag,{value:"Module"}),R})({},ReactJSXRuntime,React,ReactDOM); + `,role:"option","aria-selected":S,"data-item-value":k.value,"data-item-index":N,"data-item-selected":S,children:c?.item?Dp(c.item,{item:k,index:N,isSelected:S},`item-${N}`):a||C(k,S)},k.value)})})})]})};function TE(t){const e=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(t);return e?{r:parseInt(e[1],16),g:parseInt(e[2],16),b:parseInt(e[3],16)}:null}const AE=({value:t,checked:e=!1,icon:n,label:r,variant:s="blue",customColor:o,className:i="",style:a,onChange:l,id:c,editorAttrs:d})=>{const[u,f]=v.useState(!1),g="inline-flex items-center gap-2 px-3 py-1.5 rounded-lg text-sm font-medium border cursor-pointer transition-all";if(o){const b=TE(o),_=document.documentElement.classList.contains("dark");let x;return e?x={backgroundColor:o,color:"#ffffff",borderColor:o,opacity:u?.85:1}:x=b?{backgroundColor:`rgba(${b.r}, ${b.g}, ${b.b}, ${u?_?.25:.12:_?.15:.06})`,color:o,borderColor:`rgba(${b.r}, ${b.g}, ${b.b}, ${_?.3:.2})`}:{},h.jsxs(sn,{className:`${g} ${i}`,style:{...x,...a},onMouseEnter:()=>f(!0),onMouseLeave:()=>f(!1),id:c,...d,children:[h.jsx(kt,{type:"checkbox",value:t,checked:e,onChange:E=>{l?.(E)},className:"hidden"}),n&&h.jsx(re,{name:n,className:"w-4 h-4"}),h.jsx(I,{children:r})]})}const y=e?{green:"bg-green-600 dark:bg-green-600 text-white dark:text-white border-green-700 dark:border-green-500 hover:bg-green-700 dark:hover:bg-green-500",orange:"bg-orange-600 dark:bg-orange-600 text-white dark:text-white border-orange-700 dark:border-orange-500 hover:bg-orange-700 dark:hover:bg-orange-500",yellow:"bg-yellow-600 dark:bg-yellow-600 text-white dark:text-white border-yellow-700 dark:border-yellow-500 hover:bg-yellow-700 dark:hover:bg-yellow-500",gray:"bg-gray-600 dark:bg-gray-500 text-white dark:text-white border-gray-700 dark:border-gray-400 hover:bg-gray-700 dark:hover:bg-gray-400",blue:"bg-blue-600 dark:bg-blue-600 text-white dark:text-white border-blue-700 dark:border-blue-500 hover:bg-blue-700 dark:hover:bg-blue-500"}[s]:{green:"bg-green-50 dark:bg-green-900/20 text-green-600 dark:text-green-400 border-green-200 dark:border-green-800 hover:bg-green-100 dark:hover:bg-green-900/30",orange:"bg-orange-50 dark:bg-orange-900/20 text-orange-600 dark:text-orange-400 border-orange-200 dark:border-orange-800 hover:bg-orange-100 dark:hover:bg-orange-900/30",yellow:"bg-yellow-50 dark:bg-yellow-900/20 text-yellow-600 dark:text-yellow-400 border-yellow-200 dark:border-yellow-800 hover:bg-yellow-100 dark:hover:bg-yellow-900/30",gray:"bg-gray-50 dark:bg-gray-700 text-gray-600 dark:text-gray-300 border-gray-200 dark:border-gray-600 hover:bg-gray-100 dark:hover:bg-gray-600",blue:"bg-blue-50 dark:bg-blue-900/20 text-blue-600 dark:text-blue-400 border-blue-200 dark:border-blue-800 hover:bg-blue-100 dark:hover:bg-blue-900/30"}[s];return h.jsxs(sn,{className:`${g} ${y} ${i}`,style:a,id:c,...d,children:[h.jsx(kt,{type:"checkbox",value:t,checked:e,onChange:b=>{l?.(b)},className:"hidden"}),n&&h.jsx(re,{name:n,className:"w-4 h-4"}),h.jsx(I,{children:r})]})},DE=({label:t,icon:e,iconPosition:n="left",position:r="right",className:s="",style:o,children:i})=>{const[a,l]=v.useState(!1),[c,d]=v.useState({top:0,left:0}),u=v.useRef(null),f=v.useRef(null),g=v.useCallback(()=>{if(!u.current)return;const p=u.current.getBoundingClientRect(),y=224;let b=r==="right"?p.right-y:p.left;const _=p.bottom+8;b<8&&(b=8),b+y>window.innerWidth-8&&(b=window.innerWidth-y-8),d({top:_,left:b})},[r]);v.useEffect(()=>{a&&g()},[a,g]),v.useEffect(()=>{if(!a)return;const p=()=>{l(!1)};return window.addEventListener("scroll",p,!0),window.addEventListener("resize",p),()=>{window.removeEventListener("scroll",p,!0),window.removeEventListener("resize",p)}},[a]),v.useEffect(()=>{const p=y=>{const b=y.target,_=u.current?.contains(b),x=f.current?.contains(b);!_&&!x&&l(!1)};return a&&document.addEventListener("mousedown",p),()=>{document.removeEventListener("mousedown",p)}},[a]);const m=a?Tn.createPortal(h.jsx("div",{ref:f,className:"fixed z-[9999] w-56 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg overflow-hidden",style:{top:c.top,left:c.left},onClick:()=>l(!1),children:i}),document.body):null;return h.jsxs(I,{className:`relative inline-block ${s}`,style:o,children:[h.jsxs(oe,{ref:u,onClick:()=>l(!a),className:"flex items-center gap-2",children:[n==="left"&&e&&h.jsx(re,{name:e,className:"w-4 h-4"}),t&&h.jsx(Q,{className:"text-sm font-medium",children:t}),n==="right"&&e&&h.jsx(re,{name:e,className:"w-4 h-4"})]}),m]})},IE=({label:t,icon:e,variant:n="default",disabled:r=!1,divider:s=!1,className:o="",style:i,onClick:a})=>{if(s)return h.jsx(I,{className:"h-px bg-gray-200 dark:bg-gray-700 my-1",style:i});const l=n==="danger"?"text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/20":"text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700",c=r?"opacity-50 cursor-not-allowed":"cursor-pointer",d=()=>{!r&&a&&a()};return h.jsxs(I,{onClick:d,className:`flex items-center gap-3 px-4 py-3 transition-colors ${l} ${c} ${o}`,style:i,children:[e&&h.jsx(re,{name:e,className:"w-4 h-4"}),t&&h.jsx(Q,{className:"text-sm font-medium",children:t})]})},LE=()=>window.G7Core,Pc=v.createContext(null),Ip=()=>{const t=v.useContext(Pc);if(!t){const e=LE(),n=e?.locale?.current?.()||"ko",r=e?.locale?.supported?.()||["ko","en"];return{activeLocale:n,getActiveLocale:()=>e?.locale?.current?.()||"ko",supportedLocales:r,isInsideMultilingualTabPanel:!1}}return{...t,isInsideMultilingualTabPanel:!0}},PE=()=>window.G7Core,$E=({value:t,onChange:e,placeholder:n,readOnly:r,disabled:s,hasError:o})=>{const{activeLocale:i,getActiveLocale:a}=Ip(),l=typeof t=="string"?{[i]:t}:t||{},c=v.useCallback(u=>{const f=a();e({...l,[f]:u.target.value})},[l,a,e]),d=o?"border-red-500 dark:border-red-500 focus:ring-red-500 focus:border-red-500":"border-gray-300 dark:border-gray-600 focus:ring-blue-500 focus:border-blue-500";return h.jsx(kt,{type:"text",value:l[i]||"",onChange:c,placeholder:n,readOnly:r,disabled:s,className:`w-full px-3 py-2 text-sm border ${d} rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2`})},FE=(t,e)=>{if(!e)return!1;const n=e.startsWith("!"),r=n?e.slice(1):e,s=!!t[r];return n?!s:s},zE=({column:t,item:e,index:n,onValueChange:r,readOnly:s,error:o})=>{const i=e[t.key],a=s||t.readOnly||FE(e,t.readOnlyCondition)||t.disabled,l=!!o,c=l?"border-red-500 dark:border-red-500 focus:ring-red-500 focus:border-red-500":"border-gray-300 dark:border-gray-600 focus:ring-blue-500 focus:border-blue-500",d=v.useCallback(f=>{r(t.key,f)},[t.key,r]),u=f=>h.jsxs(I,{className:"w-full",children:[f,l&&h.jsx(Q,{className:"text-xs text-red-500 dark:text-red-400 mt-1 block",children:o})]});switch(t.type){case"input":return u(h.jsx(kt,{type:"text",value:i||"",onChange:f=>d(f.target.value),placeholder:t.placeholder,readOnly:a,disabled:a,className:`w-full px-3 py-2 text-sm border ${c} rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 ${t.className||""}`}));case"number":return u(h.jsx(kt,{type:"number",value:i??"",onChange:f=>d(f.target.value?Number(f.target.value):null),placeholder:t.placeholder,min:t.min,max:t.max,step:t.step,readOnly:a,disabled:a,className:`w-full px-3 py-2 text-sm border ${c} rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 ${t.className||""}`}));case"select":return u(h.jsxs(Pr,{value:i??"",onChange:f=>d(f.target.value),disabled:a,className:`w-full px-3 py-2 text-sm border ${c} rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 ${t.className||""}`,children:[h.jsx("option",{value:"",children:"선택하세요"}),t.options?.map(f=>h.jsx("option",{value:f.value,disabled:f.disabled,children:f.label},f.value))]}));case"textarea":return u(h.jsx(Qn,{value:i||"",onChange:f=>d(f.target.value),placeholder:t.placeholder,readOnly:a,disabled:a,className:`w-full px-3 py-2 text-sm border ${c} rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 resize-none ${t.className||""}`,rows:2}));case"multilingual":return u(h.jsx($E,{value:i,onChange:f=>d(f),placeholder:t.placeholder,readOnly:a,disabled:a,hasError:l}));case"custom":return t.render?h.jsx(h.Fragment,{children:t.render(e,n,d)}):h.jsx(Q,{className:"text-gray-500 dark:text-gray-400",children:"-"});default:return h.jsx(Q,{className:"text-gray-900 dark:text-white text-sm",children:String(i??"")})}},VE=(t,e,n)=>{if(!t)return;if(n==="multilingual"){const o=PE()?.locale?.current?.()||"ko",i=`${e}.${o}`;return t[i]?.[0]}return t[e]?.[0]},HE=({item:t,index:e,id:n,columns:r,enableDrag:s=!0,showIndex:o=!0,canRemove:i=!0,rowActions:a,readOnly:l=!1,onValueChange:c,onRemove:d,className:u="",level:f=0,errors:g})=>{const{attributes:m,listeners:p,setNodeRef:y,transform:b,transition:_,isDragging:x}=Ii({id:n,disabled:!s||l}),E={transform:Pn.Transform.toString(b),transition:_,opacity:x?.5:1},C=v.useCallback((S,w)=>{c?.(S,w)},[c]),k=v.useCallback(S=>{S.onClick?.(t,e)},[t,e]),N=f>0?{paddingLeft:`${f*24}px`}:{};return h.jsxs(I,{ref:y,style:E,className:`flex items-center gap-2 py-2 px-2 border-b border-gray-100 dark:border-gray-700 last:border-b-0 hover:bg-gray-50 dark:hover:bg-gray-800/50 transition-colors ${x?"bg-blue-50 dark:bg-blue-900/20 shadow-lg rounded-lg z-10":""} ${u}`,children:[s&&!l&&h.jsx(I,{...m,...p,className:"flex-shrink-0 w-6 h-6 flex items-center justify-center cursor-grab active:cursor-grabbing text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-400",style:N,children:h.jsx(re,{name:"grip-vertical",className:"w-4 h-4"})}),o&&h.jsx(I,{className:"flex-shrink-0 w-8 text-center text-sm text-gray-500 dark:text-gray-400 font-medium",children:e+1}),r.map(S=>{const w=VE(g,S.key,S.type);return h.jsx(I,{className:`flex-1 min-w-0 ${S.width||""}`,style:S.width?{flex:"none",width:S.width}:{},children:h.jsx(zE,{column:S,item:t,index:e,onValueChange:C,readOnly:l,error:w})},S.key)}),a&&a.length>0&&!l&&h.jsx(I,{className:"flex-shrink-0 flex items-center gap-1",children:a.map(S=>{const w=typeof S.disabled=="function"?S.disabled(t,e):S.disabled;return h.jsx(oe,{type:"button",onClick:()=>k(S),disabled:w,className:`p-1.5 rounded transition-colors ${S.danger?"text-red-500 hover:bg-red-50 dark:hover:bg-red-900/20 hover:text-red-600":"text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-gray-600 dark:hover:text-gray-300"} disabled:opacity-50 disabled:cursor-not-allowed`,title:S.label,children:S.icon?h.jsx(re,{name:S.icon,className:"w-4 h-4"}):h.jsx(Q,{className:"text-xs",children:S.label})},S.key)})}),i&&!a&&!l&&h.jsx(I,{className:"flex-shrink-0",children:h.jsx(oe,{type:"button",onClick:d,className:"p-1.5 text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-gray-600 dark:hover:text-gray-300 rounded transition-colors",title:"삭제",children:h.jsx(re,{name:"minus",className:"w-4 h-4"})})})]})},Lp=()=>window.G7Core,$c=()=>`item_${Date.now()}_${Math.random().toString(36).substring(2,9)}`,UE=t=>{const e={_id:$c()};return t.forEach(n=>{if(n.type==="multilingual"){const s=Lp()?.locale?.all?.()||["ko","en"],o={};s.forEach(i=>{o[i]=""}),e[n.key]=o}else n.type==="number"?e[n.key]=null:(n.type,e[n.key]="")}),e},BE=(t,e)=>{if(!t)return{};const n=`fields.${e}.`,r={};return Object.keys(t).forEach(s=>{if(s.startsWith(n)){const o=s.substring(n.length);r[o]=t[s]}}),r},jE=({items:t=[],columns:e,onChange:n,onAddItem:r,onRemoveItem:s,onReorder:o,addLabel:i="항목 추가",enableDrag:a=!0,showIndex:l=!0,minItems:c=0,maxItems:d,rowActions:u,emptyMessage:f="항목이 없습니다.",className:g="",headerClassName:m="",rowClassName:p="",name:y,readOnly:b=!1,createDefaultItem:_,childrenKey:x,itemIdKey:E="_id",errors:C,id:k,editorAttrs:N})=>{const S=v.useRef(null),w=v.useRef([]),M=v.useRef(null),T=v.useMemo(()=>{let W=!1;for(const U of t)if(U[E]==null){W=!0;break}return W?t.map(U=>U[E]!=null?U:{...U,[E]:$c()}):t},[t,E]);w.current=T,v.useEffect(()=>{M.current=null},[T]);const P=v.useCallback(W=>String(W[E]??""),[E]);v.useEffect(()=>{if(y){const W=Lp();W?.form&&(S.current=W.form)}},[y]);const L=jl(Zr(Xs,{activationConstraint:{distance:8}}),Zr(Ks,{coordinateGetter:Li})),$=v.useMemo(()=>T.map(W=>P(W)),[T,P]),A=v.useCallback(W=>{const{active:U,over:B}=W;if(B&&U.id!==B.id){const G=M.current??w.current,V=G.map(Z=>String(Z[E]??"")),X=V.indexOf(String(U.id)),K=V.indexOf(String(B.id));if(X!==-1&&K!==-1){const Z=Jr([...G],X,K);M.current=Z,n?.(Z),o?.(Z)}}},[n,o,E]),D=v.useCallback((W,U,B)=>{const V=[...M.current??w.current];V[W]={...V[W],[U]:B},M.current=V,n?.(V)},[n]),O=v.useCallback(()=>{const W=M.current??w.current;if(d&&W.length>=d)return;if(r){r();return}const U=_?_():UE(e);U[E]||(U[E]=$c());const B=[...W,U];M.current=B,n?.(B)},[d,r,_,e,E,n]),z=v.useCallback(W=>{const U=M.current??w.current;if(U.length<=c)return;const B=U[W];if(s){s(W,B);return}const G=U.filter((V,X)=>X!==W);M.current=G,n?.(G)},[c,s,n]),H=T.length>c,j=!d||T.lengthh.jsxs(I,{className:`flex-1 min-w-0 text-xs font-medium text-gray-500 dark:text-gray-400 uppercase ${W.width||""}`,style:W.width?{flex:"none",width:W.width}:{},children:[W.label,W.required&&h.jsx(Q,{className:"text-red-500 dark:text-red-400 ml-0.5",children:"*"})]},W.key)),(u||!b&&H)&&h.jsx(I,{className:"flex-shrink-0 w-8"})]}),T.length>0?h.jsx(ec,{sensors:L,collisionDetection:Wl,onDragEnd:A,children:h.jsx(Di,{items:$,strategy:tc,children:h.jsx(I,{className:"border-x border-gray-200 dark:border-gray-700",children:T.map((W,U)=>{const B=P(W),G=BE(C,U);return h.jsx(HE,{id:B,item:W,index:U,columns:e,enableDrag:a,showIndex:l,canRemove:H,rowActions:u,readOnly:b,onValueChange:(V,X)=>D(U,V,X),onRemove:()=>z(U),className:p,errors:G},B)})})})}):h.jsx(I,{className:"border-x border-gray-200 dark:border-gray-700 py-8 text-center text-gray-500 dark:text-gray-400 text-sm",children:f}),!b&&j&&h.jsx(I,{className:"relative border border-gray-200 dark:border-gray-700 border-t-0 rounded-b-lg",children:h.jsx(I,{className:"flex items-center justify-center py-2",children:h.jsxs(oe,{type:"button",onClick:O,className:"flex items-center gap-1.5 px-4 py-1.5 text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors",children:[h.jsx(re,{name:"plus",className:"w-4 h-4"}),h.jsx(Q,{children:i})]})})}),(b||!j)&&T.length>0&&h.jsx(I,{className:"border-x border-b border-gray-200 dark:border-gray-700 rounded-b-lg h-1"})]})},Pp=()=>window.G7Core,WE=()=>Pp()?.locale?.supported?.()??["ko","en"],GE=()=>Pp()?.locale?.current?.()??"ko",YE={ko:"한국어",en:"English",ja:"日本語",zh:"中文"},KE=({children:t,className:e="",style:n,variant:r="default",defaultLocale:s,onLocaleChange:o,locales:i})=>{const a=v.useMemo(()=>i&&i.length>0?i:WE(),[i]),[l,c]=v.useState(s||GE());v.useEffect(()=>{if(a.length>0&&!a.includes(l)){const p=a[0];c(p),o?.(p)}},[a,l,o]);const d=v.useRef(l);d.current=l;const u=v.useCallback(()=>d.current,[]),f=v.useCallback(p=>{c(p),o?.(p)},[o]),g=v.useMemo(()=>({activeLocale:l,getActiveLocale:u,supportedLocales:a}),[l,u,a]),m=p=>{const y=p===l,b="px-4 py-2 text-sm font-medium transition-all";return r==="pills"?y?`${b} bg-blue-600 text-white rounded-lg`:`${b} text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg`:r==="underline"?y?`${b} text-blue-600 dark:text-blue-400 border-b-2 border-blue-600 dark:border-blue-400`:`${b} text-gray-600 dark:text-gray-400 border-b-2 border-transparent hover:border-gray-300 dark:hover:border-gray-500`:y?`${b} text-blue-600 dark:text-blue-400 bg-blue-50 dark:bg-blue-900/20 border-b-2 border-blue-600`:`${b} text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-700 border-b-2 border-transparent`};return h.jsxs(I,{className:e,style:n,children:[h.jsx(Po,{className:"flex gap-1 border-b border-gray-200 dark:border-gray-700 mb-4",children:a.map(p=>h.jsx(oe,{type:"button",onClick:()=>f(p),className:m(p),children:h.jsx(Q,{children:YE[p]||p})},p))}),h.jsx(Pc.Provider,{value:g,children:t})]})},XE=({label:t,required:e=!1,error:n,helperText:r,className:s="",labelClassName:o="",children:i,horizontal:a=!1,labelWidth:l="w-1/3",id:c,editorAttrs:d})=>{const u=a?`flex items-start gap-4 ${s}`:`space-y-1 ${s}`,f=a?`${l} flex-shrink-0 pt-2`:"",g=a?"flex-1":"";return h.jsxs(I,{className:u,id:c,...d,children:[t&&h.jsx(I,{className:f,children:h.jsxs(sn,{className:`block text-sm font-medium text-gray-700 dark:text-gray-300 ${o}`,children:[t,e&&h.jsx("span",{className:"ml-1 text-red-500 dark:text-red-400",children:"*"})]})}),h.jsxs(I,{className:`${g} ${n?"form-field-error":""}`.trim(),children:[i,r&&!n&&h.jsx(I,{className:"mt-1 text-xs text-gray-500 dark:text-gray-400",children:r}),n&&h.jsx(I,{className:"mt-1 text-xs text-red-600 dark:text-red-400",children:n})]})]})};function lo(t){return t+.5|0}const ir=(t,e,n)=>Math.max(Math.min(t,n),e);function co(t){return ir(lo(t*2.55),0,255)}function ar(t){return ir(lo(t*255),0,255)}function Gn(t){return ir(lo(t/2.55)/100,0,1)}function $p(t){return ir(lo(t*100),0,100)}const hn={0:0,1:1,2:2,3:3,4:4,5:5,6:6,7:7,8:8,9:9,A:10,B:11,C:12,D:13,E:14,F:15,a:10,b:11,c:12,d:13,e:14,f:15},Fc=[..."0123456789ABCDEF"],qE=t=>Fc[t&15],ZE=t=>Fc[(t&240)>>4]+Fc[t&15],Qi=t=>(t&240)>>4===(t&15),QE=t=>Qi(t.r)&&Qi(t.g)&&Qi(t.b)&&Qi(t.a);function JE(t){var e=t.length,n;return t[0]==="#"&&(e===4||e===5?n={r:255&hn[t[1]]*17,g:255&hn[t[2]]*17,b:255&hn[t[3]]*17,a:e===5?hn[t[4]]*17:255}:(e===7||e===9)&&(n={r:hn[t[1]]<<4|hn[t[2]],g:hn[t[3]]<<4|hn[t[4]],b:hn[t[5]]<<4|hn[t[6]],a:e===9?hn[t[7]]<<4|hn[t[8]]:255})),n}const RE=(t,e)=>t<255?e(t):"";function eM(t){var e=QE(t)?qE:ZE;return t?"#"+e(t.r)+e(t.g)+e(t.b)+RE(t.a,e):void 0}const tM=/^(hsla?|hwb|hsv)\(\s*([-+.e\d]+)(?:deg)?[\s,]+([-+.e\d]+)%[\s,]+([-+.e\d]+)%(?:[\s,]+([-+.e\d]+)(%)?)?\s*\)$/;function Fp(t,e,n){const r=e*Math.min(n,1-n),s=(o,i=(o+t/30)%12)=>n-r*Math.max(Math.min(i-3,9-i,1),-1);return[s(0),s(8),s(4)]}function nM(t,e,n){const r=(s,o=(s+t/60)%6)=>n-n*e*Math.max(Math.min(o,4-o,1),0);return[r(5),r(3),r(1)]}function rM(t,e,n){const r=Fp(t,1,.5);let s;for(e+n>1&&(s=1/(e+n),e*=s,n*=s),s=0;s<3;s++)r[s]*=1-e-n,r[s]+=e;return r}function sM(t,e,n,r,s){return t===s?(e-n)/r+(e.5?d/(2-o-i):d/(o+i),l=sM(n,r,s,d,o),l=l*60+.5),[l|0,c||0,a]}function Vc(t,e,n,r){return(Array.isArray(e)?t(e[0],e[1],e[2]):t(e,n,r)).map(ar)}function Hc(t,e,n){return Vc(Fp,t,e,n)}function oM(t,e,n){return Vc(rM,t,e,n)}function iM(t,e,n){return Vc(nM,t,e,n)}function zp(t){return(t%360+360)%360}function aM(t){const e=tM.exec(t);let n=255,r;if(!e)return;e[5]!==r&&(n=e[6]?co(+e[5]):ar(+e[5]));const s=zp(+e[2]),o=+e[3]/100,i=+e[4]/100;return e[1]==="hwb"?r=oM(s,o,i):e[1]==="hsv"?r=iM(s,o,i):r=Hc(s,o,i),{r:r[0],g:r[1],b:r[2],a:n}}function lM(t,e){var n=zc(t);n[0]=zp(n[0]+e),n=Hc(n),t.r=n[0],t.g=n[1],t.b=n[2]}function cM(t){if(!t)return;const e=zc(t),n=e[0],r=$p(e[1]),s=$p(e[2]);return t.a<255?`hsla(${n}, ${r}%, ${s}%, ${Gn(t.a)})`:`hsl(${n}, ${r}%, ${s}%)`}const Vp={x:"dark",Z:"light",Y:"re",X:"blu",W:"gr",V:"medium",U:"slate",A:"ee",T:"ol",S:"or",B:"ra",C:"lateg",D:"ights",R:"in",Q:"turquois",E:"hi",P:"ro",O:"al",N:"le",M:"de",L:"yello",F:"en",K:"ch",G:"arks",H:"ea",I:"ightg",J:"wh"},Hp={OiceXe:"f0f8ff",antiquewEte:"faebd7",aqua:"ffff",aquamarRe:"7fffd4",azuY:"f0ffff",beige:"f5f5dc",bisque:"ffe4c4",black:"0",blanKedOmond:"ffebcd",Xe:"ff",XeviTet:"8a2be2",bPwn:"a52a2a",burlywood:"deb887",caMtXe:"5f9ea0",KartYuse:"7fff00",KocTate:"d2691e",cSO:"ff7f50",cSnflowerXe:"6495ed",cSnsilk:"fff8dc",crimson:"dc143c",cyan:"ffff",xXe:"8b",xcyan:"8b8b",xgTMnPd:"b8860b",xWay:"a9a9a9",xgYF:"6400",xgYy:"a9a9a9",xkhaki:"bdb76b",xmagFta:"8b008b",xTivegYF:"556b2f",xSange:"ff8c00",xScEd:"9932cc",xYd:"8b0000",xsOmon:"e9967a",xsHgYF:"8fbc8f",xUXe:"483d8b",xUWay:"2f4f4f",xUgYy:"2f4f4f",xQe:"ced1",xviTet:"9400d3",dAppRk:"ff1493",dApskyXe:"bfff",dimWay:"696969",dimgYy:"696969",dodgerXe:"1e90ff",fiYbrick:"b22222",flSOwEte:"fffaf0",foYstWAn:"228b22",fuKsia:"ff00ff",gaRsbSo:"dcdcdc",ghostwEte:"f8f8ff",gTd:"ffd700",gTMnPd:"daa520",Way:"808080",gYF:"8000",gYFLw:"adff2f",gYy:"808080",honeyMw:"f0fff0",hotpRk:"ff69b4",RdianYd:"cd5c5c",Rdigo:"4b0082",ivSy:"fffff0",khaki:"f0e68c",lavFMr:"e6e6fa",lavFMrXsh:"fff0f5",lawngYF:"7cfc00",NmoncEffon:"fffacd",ZXe:"add8e6",ZcSO:"f08080",Zcyan:"e0ffff",ZgTMnPdLw:"fafad2",ZWay:"d3d3d3",ZgYF:"90ee90",ZgYy:"d3d3d3",ZpRk:"ffb6c1",ZsOmon:"ffa07a",ZsHgYF:"20b2aa",ZskyXe:"87cefa",ZUWay:"778899",ZUgYy:"778899",ZstAlXe:"b0c4de",ZLw:"ffffe0",lime:"ff00",limegYF:"32cd32",lRF:"faf0e6",magFta:"ff00ff",maPon:"800000",VaquamarRe:"66cdaa",VXe:"cd",VScEd:"ba55d3",VpurpN:"9370db",VsHgYF:"3cb371",VUXe:"7b68ee",VsprRggYF:"fa9a",VQe:"48d1cc",VviTetYd:"c71585",midnightXe:"191970",mRtcYam:"f5fffa",mistyPse:"ffe4e1",moccasR:"ffe4b5",navajowEte:"ffdead",navy:"80",Tdlace:"fdf5e6",Tive:"808000",TivedBb:"6b8e23",Sange:"ffa500",SangeYd:"ff4500",ScEd:"da70d6",pOegTMnPd:"eee8aa",pOegYF:"98fb98",pOeQe:"afeeee",pOeviTetYd:"db7093",papayawEp:"ffefd5",pHKpuff:"ffdab9",peru:"cd853f",pRk:"ffc0cb",plum:"dda0dd",powMrXe:"b0e0e6",purpN:"800080",YbeccapurpN:"663399",Yd:"ff0000",Psybrown:"bc8f8f",PyOXe:"4169e1",saddNbPwn:"8b4513",sOmon:"fa8072",sandybPwn:"f4a460",sHgYF:"2e8b57",sHshell:"fff5ee",siFna:"a0522d",silver:"c0c0c0",skyXe:"87ceeb",UXe:"6a5acd",UWay:"708090",UgYy:"708090",snow:"fffafa",sprRggYF:"ff7f",stAlXe:"4682b4",tan:"d2b48c",teO:"8080",tEstN:"d8bfd8",tomato:"ff6347",Qe:"40e0d0",viTet:"ee82ee",JHt:"f5deb3",wEte:"ffffff",wEtesmoke:"f5f5f5",Lw:"ffff00",LwgYF:"9acd32"};function dM(){const t={},e=Object.keys(Hp),n=Object.keys(Vp);let r,s,o,i,a;for(r=0;r>16&255,o>>8&255,o&255]}return t}let Ji;function uM(t){Ji||(Ji=dM(),Ji.transparent=[0,0,0,0]);const e=Ji[t.toLowerCase()];return e&&{r:e[0],g:e[1],b:e[2],a:e.length===4?e[3]:255}}const fM=/^rgba?\(\s*([-+.\d]+)(%)?[\s,]+([-+.e\d]+)(%)?[\s,]+([-+.e\d]+)(%)?(?:[\s,/]+([-+.e\d]+)(%)?)?\s*\)$/;function hM(t){const e=fM.exec(t);let n=255,r,s,o;if(e){if(e[7]!==r){const i=+e[7];n=e[8]?co(i):ir(i*255,0,255)}return r=+e[1],s=+e[3],o=+e[5],r=255&(e[2]?co(r):ir(r,0,255)),s=255&(e[4]?co(s):ir(s,0,255)),o=255&(e[6]?co(o):ir(o,0,255)),{r,g:s,b:o,a:n}}}function gM(t){return t&&(t.a<255?`rgba(${t.r}, ${t.g}, ${t.b}, ${Gn(t.a)})`:`rgb(${t.r}, ${t.g}, ${t.b})`)}const Uc=t=>t<=.0031308?t*12.92:Math.pow(t,1/2.4)*1.055-.055,cs=t=>t<=.04045?t/12.92:Math.pow((t+.055)/1.055,2.4);function pM(t,e,n){const r=cs(Gn(t.r)),s=cs(Gn(t.g)),o=cs(Gn(t.b));return{r:ar(Uc(r+n*(cs(Gn(e.r))-r))),g:ar(Uc(s+n*(cs(Gn(e.g))-s))),b:ar(Uc(o+n*(cs(Gn(e.b))-o))),a:t.a+n*(e.a-t.a)}}function Ri(t,e,n){if(t){let r=zc(t);r[e]=Math.max(0,Math.min(r[e]+r[e]*n,e===0?360:1)),r=Hc(r),t.r=r[0],t.g=r[1],t.b=r[2]}}function Up(t,e){return t&&Object.assign(e||{},t)}function Bp(t){var e={r:0,g:0,b:0,a:255};return Array.isArray(t)?t.length>=3&&(e={r:t[0],g:t[1],b:t[2],a:255},t.length>3&&(e.a=ar(t[3]))):(e=Up(t,{r:0,g:0,b:0,a:1}),e.a=ar(e.a)),e}function mM(t){return t.charAt(0)==="r"?hM(t):aM(t)}class uo{constructor(e){if(e instanceof uo)return e;const n=typeof e;let r;n==="object"?r=Bp(e):n==="string"&&(r=JE(e)||uM(e)||mM(e)),this._rgb=r,this._valid=!!r}get valid(){return this._valid}get rgb(){var e=Up(this._rgb);return e&&(e.a=Gn(e.a)),e}set rgb(e){this._rgb=Bp(e)}rgbString(){return this._valid?gM(this._rgb):void 0}hexString(){return this._valid?eM(this._rgb):void 0}hslString(){return this._valid?cM(this._rgb):void 0}mix(e,n){if(e){const r=this.rgb,s=e.rgb;let o;const i=n===o?.5:n,a=2*i-1,l=r.a-s.a,c=((a*l===-1?a:(a+l)/(1+a*l))+1)/2;o=1-c,r.r=255&c*r.r+o*s.r+.5,r.g=255&c*r.g+o*s.g+.5,r.b=255&c*r.b+o*s.b+.5,r.a=i*r.a+(1-i)*s.a,this.rgb=r}return this}interpolate(e,n){return e&&(this._rgb=pM(this._rgb,e._rgb,n)),this}clone(){return new uo(this.rgb)}alpha(e){return this._rgb.a=ar(e),this}clearer(e){const n=this._rgb;return n.a*=1-e,this}greyscale(){const e=this._rgb,n=lo(e.r*.3+e.g*.59+e.b*.11);return e.r=e.g=e.b=n,this}opaquer(e){const n=this._rgb;return n.a*=1+e,this}negate(){const e=this._rgb;return e.r=255-e.r,e.g=255-e.g,e.b=255-e.b,this}lighten(e){return Ri(this._rgb,2,e),this}darken(e){return Ri(this._rgb,2,-e),this}saturate(e){return Ri(this._rgb,1,e),this}desaturate(e){return Ri(this._rgb,1,-e),this}rotate(e){return lM(this._rgb,e),this}}function Yn(){}const bM=(()=>{let t=0;return()=>t++})();function lt(t){return t==null}function Et(t){if(Array.isArray&&Array.isArray(t))return!0;const e=Object.prototype.toString.call(t);return e.slice(0,7)==="[object"&&e.slice(-6)==="Array]"}function Qe(t){return t!==null&&Object.prototype.toString.call(t)==="[object Object]"}function gn(t){return(typeof t=="number"||t instanceof Number)&&isFinite(+t)}function Fn(t,e){return gn(t)?t:e}function Je(t,e){return typeof t>"u"?e:t}const yM=(t,e)=>typeof t=="string"&&t.endsWith("%")?parseFloat(t)/100:+t/e,jp=(t,e)=>typeof t=="string"&&t.endsWith("%")?parseFloat(t)/100*e:+t;function gt(t,e,n){if(t&&typeof t.call=="function")return t.apply(n,e)}function at(t,e,n,r){let s,o,i;if(Et(t))for(o=t.length,s=0;st,x:t=>t.x,y:t=>t.y};function wM(t){const e=t.split("."),n=[];let r="";for(const s of e)r+=s,r.endsWith("\\")?r=r.slice(0,-1)+".":(n.push(r),r="");return n}function kM(t){const e=wM(t);return n=>{for(const r of e){if(r==="")break;n=n&&n[r]}return n}}function Sr(t,e){return(Gp[e]||(Gp[e]=kM(e)))(t)}function Bc(t){return t.charAt(0).toUpperCase()+t.slice(1)}const go=t=>typeof t<"u",lr=t=>typeof t=="function",Yp=(t,e)=>{if(t.size!==e.size)return!1;for(const n of t)if(!e.has(n))return!1;return!0};function CM(t){return t.type==="mouseup"||t.type==="click"||t.type==="contextmenu"}const ct=Math.PI,St=2*ct,na=Number.POSITIVE_INFINITY,SM=ct/180,Mt=ct/2,_r=ct/4,Kp=ct*2/3,Xp=Math.log10,cr=Math.sign;function ra(t,e,n){return Math.abs(t-e)s-o).pop(),e}function EM(t){return typeof t=="symbol"||typeof t=="object"&&t!==null&&!(Symbol.toPrimitive in t||"toString"in t||"valueOf"in t)}function sa(t){return!EM(t)&&!isNaN(parseFloat(t))&&isFinite(t)}function MM(t,e){const n=Math.round(t);return n-e<=t&&n+e>=t}function NM(t,e,n){let r,s,o;for(r=0,s=t.length;rl&&c=Math.min(e,n)-r&&t<=Math.max(e,n)+r}function jc(t,e,n){n=n||(i=>t[i]1;)o=s+r>>1,n(o)?s=o:r=o;return{lo:s,hi:r}}const Wc=(t,e,n,r)=>jc(t,n,r?s=>{const o=t[s][e];return ot[s][e]jc(t,n,r=>t[r][e]>=n);function IM(t,e,n){let r=0,s=t.length;for(;rr&&t[s-1]>n;)s--;return r>0||s{const r="_onData"+Bc(n),s=t[n];Object.defineProperty(t,n,{configurable:!0,enumerable:!1,value(...o){const i=s.apply(this,o);return t._chartjs.listeners.forEach(a=>{typeof a[r]=="function"&&a[r](...o)}),i}})})}function Rp(t,e){const n=t._chartjs;if(!n)return;const r=n.listeners,s=r.indexOf(e);s!==-1&&r.splice(s,1),!(r.length>0)&&(Jp.forEach(o=>{delete t[o]}),delete t._chartjs)}function e0(t){const e=new Set(t);return e.size===t.length?t:Array.from(e)}const t0=(function(){return typeof window>"u"?function(t){return t()}:window.requestAnimationFrame})();function n0(t,e){let n=[],r=!1;return function(...s){n=s,r||(r=!0,t0.call(window,()=>{r=!1,t.apply(e,n)}))}}function PM(t,e){let n;return function(...r){return e?(clearTimeout(n),n=setTimeout(t,e,r)):t.apply(this,r),e}}const Gc=t=>t==="start"?"left":t==="end"?"right":"center",Ut=(t,e,n)=>t==="start"?e:t==="end"?n:(e+n)/2,$M=(t,e,n,r)=>t===(r?"left":"right")?n:t==="center"?(e+n)/2:e,ia=t=>t===0||t===1,r0=(t,e,n)=>-(Math.pow(2,10*(t-=1))*Math.sin((t-e)*St/n)),s0=(t,e,n)=>Math.pow(2,-10*t)*Math.sin((t-e)*St/n)+1,po={linear:t=>t,easeInQuad:t=>t*t,easeOutQuad:t=>-t*(t-2),easeInOutQuad:t=>(t/=.5)<1?.5*t*t:-.5*(--t*(t-2)-1),easeInCubic:t=>t*t*t,easeOutCubic:t=>(t-=1)*t*t+1,easeInOutCubic:t=>(t/=.5)<1?.5*t*t*t:.5*((t-=2)*t*t+2),easeInQuart:t=>t*t*t*t,easeOutQuart:t=>-((t-=1)*t*t*t-1),easeInOutQuart:t=>(t/=.5)<1?.5*t*t*t*t:-.5*((t-=2)*t*t*t-2),easeInQuint:t=>t*t*t*t*t,easeOutQuint:t=>(t-=1)*t*t*t*t+1,easeInOutQuint:t=>(t/=.5)<1?.5*t*t*t*t*t:.5*((t-=2)*t*t*t*t+2),easeInSine:t=>-Math.cos(t*Mt)+1,easeOutSine:t=>Math.sin(t*Mt),easeInOutSine:t=>-.5*(Math.cos(ct*t)-1),easeInExpo:t=>t===0?0:Math.pow(2,10*(t-1)),easeOutExpo:t=>t===1?1:-Math.pow(2,-10*t)+1,easeInOutExpo:t=>ia(t)?t:t<.5?.5*Math.pow(2,10*(t*2-1)):.5*(-Math.pow(2,-10*(t*2-1))+2),easeInCirc:t=>t>=1?t:-(Math.sqrt(1-t*t)-1),easeOutCirc:t=>Math.sqrt(1-(t-=1)*t),easeInOutCirc:t=>(t/=.5)<1?-.5*(Math.sqrt(1-t*t)-1):.5*(Math.sqrt(1-(t-=2)*t)+1),easeInElastic:t=>ia(t)?t:r0(t,.075,.3),easeOutElastic:t=>ia(t)?t:s0(t,.075,.3),easeInOutElastic(t){return ia(t)?t:t<.5?.5*r0(t*2,.1125,.45):.5+.5*s0(t*2-1,.1125,.45)},easeInBack(t){return t*t*((1.70158+1)*t-1.70158)},easeOutBack(t){return(t-=1)*t*((1.70158+1)*t+1.70158)+1},easeInOutBack(t){let e=1.70158;return(t/=.5)<1?.5*(t*t*(((e*=1.525)+1)*t-e)):.5*((t-=2)*t*(((e*=1.525)+1)*t+e)+2)},easeInBounce:t=>1-po.easeOutBounce(1-t),easeOutBounce(t){return t<1/2.75?7.5625*t*t:t<2/2.75?7.5625*(t-=1.5/2.75)*t+.75:t<2.5/2.75?7.5625*(t-=2.25/2.75)*t+.9375:7.5625*(t-=2.625/2.75)*t+.984375},easeInOutBounce:t=>t<.5?po.easeInBounce(t*2)*.5:po.easeOutBounce(t*2-1)*.5+.5};function o0(t){if(t&&typeof t=="object"){const e=t.toString();return e==="[object CanvasPattern]"||e==="[object CanvasGradient]"}return!1}function i0(t){return o0(t)?t:new uo(t)}function Yc(t){return o0(t)?t:new uo(t).saturate(.5).darken(.1).hexString()}const FM=["x","y","borderWidth","radius","tension"],zM=["color","borderColor","backgroundColor"];function VM(t){t.set("animation",{delay:void 0,duration:1e3,easing:"easeOutQuart",fn:void 0,from:void 0,loop:void 0,to:void 0,type:void 0}),t.describe("animation",{_fallback:!1,_indexable:!1,_scriptable:e=>e!=="onProgress"&&e!=="onComplete"&&e!=="fn"}),t.set("animations",{colors:{type:"color",properties:zM},numbers:{type:"number",properties:FM}}),t.describe("animations",{_fallback:"animation"}),t.set("transitions",{active:{animation:{duration:400}},resize:{animation:{duration:0}},show:{animations:{colors:{from:"transparent"},visible:{type:"boolean",duration:0}}},hide:{animations:{colors:{to:"transparent"},visible:{type:"boolean",easing:"linear",fn:e=>e|0}}}})}function HM(t){t.set("layout",{autoPadding:!0,padding:{top:0,right:0,bottom:0,left:0}})}const a0=new Map;function UM(t,e){e=e||{};const n=t+JSON.stringify(e);let r=a0.get(n);return r||(r=new Intl.NumberFormat(t,e),a0.set(n,r)),r}function Kc(t,e,n){return UM(e,n).format(t)}const BM={values(t){return Et(t)?t:""+t},numeric(t,e,n){if(t===0)return"0";const r=this.chart.options.locale;let s,o=t;if(n.length>1){const c=Math.max(Math.abs(n[0].value),Math.abs(n[n.length-1].value));(c<1e-4||c>1e15)&&(s="scientific"),o=jM(t,n)}const i=Xp(Math.abs(o)),a=isNaN(i)?1:Math.max(Math.min(-1*Math.floor(i),20),0),l={notation:s,minimumFractionDigits:a,maximumFractionDigits:a};return Object.assign(l,this.options.ticks.format),Kc(t,r,l)}};function jM(t,e){let n=e.length>3?e[2].value-e[1].value:e[1].value-e[0].value;return Math.abs(n)>=1&&t!==Math.floor(t)&&(n=t-Math.floor(t)),n}var l0={formatters:BM};function WM(t){t.set("scale",{display:!0,offset:!1,reverse:!1,beginAtZero:!1,bounds:"ticks",clip:!0,grace:0,grid:{display:!0,lineWidth:1,drawOnChartArea:!0,drawTicks:!0,tickLength:8,tickWidth:(e,n)=>n.lineWidth,tickColor:(e,n)=>n.color,offset:!1},border:{display:!0,dash:[],dashOffset:0,width:1},title:{display:!1,text:"",padding:{top:4,bottom:4}},ticks:{minRotation:0,maxRotation:50,mirror:!1,textStrokeWidth:0,textStrokeColor:"",padding:3,display:!0,autoSkip:!0,autoSkipPadding:3,labelOffset:0,callback:l0.formatters.values,minor:{},major:{},align:"center",crossAlign:"near",showLabelBackdrop:!1,backdropColor:"rgba(255, 255, 255, 0.75)",backdropPadding:2}}),t.route("scale.ticks","color","","color"),t.route("scale.grid","color","","borderColor"),t.route("scale.border","color","","borderColor"),t.route("scale.title","color","","color"),t.describe("scale",{_fallback:!1,_scriptable:e=>!e.startsWith("before")&&!e.startsWith("after")&&e!=="callback"&&e!=="parser",_indexable:e=>e!=="borderDash"&&e!=="tickBorderDash"&&e!=="dash"}),t.describe("scales",{_fallback:"scale"}),t.describe("scale.ticks",{_scriptable:e=>e!=="backdropPadding"&&e!=="callback",_indexable:e=>e!=="backdropPadding"})}const Mr=Object.create(null),Xc=Object.create(null);function mo(t,e){if(!e)return t;const n=e.split(".");for(let r=0,s=n.length;rr.chart.platform.getDevicePixelRatio(),this.elements={},this.events=["mousemove","mouseout","click","touchstart","touchmove"],this.font={family:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",size:12,style:"normal",lineHeight:1.2,weight:null},this.hover={},this.hoverBackgroundColor=(r,s)=>Yc(s.backgroundColor),this.hoverBorderColor=(r,s)=>Yc(s.borderColor),this.hoverColor=(r,s)=>Yc(s.color),this.indexAxis="x",this.interaction={mode:"nearest",intersect:!0,includeInvisible:!1},this.maintainAspectRatio=!0,this.onHover=null,this.onClick=null,this.parsing=!0,this.plugins={},this.responsive=!0,this.scale=void 0,this.scales={},this.showLine=!0,this.drawActiveElementsOnTop=!0,this.describe(e),this.apply(n)}set(e,n){return qc(this,e,n)}get(e){return mo(this,e)}describe(e,n){return qc(Xc,e,n)}override(e,n){return qc(Mr,e,n)}route(e,n,r,s){const o=mo(this,e),i=mo(this,r),a="_"+n;Object.defineProperties(o,{[a]:{value:o[n],writable:!0},[n]:{enumerable:!0,get(){const l=this[a],c=i[s];return Qe(l)?Object.assign({},c,l):Je(l,c)},set(l){this[a]=l}}})}apply(e){e.forEach(n=>n(this))}}var xt=new GM({_scriptable:t=>!t.startsWith("on"),_indexable:t=>t!=="events",hover:{_fallback:"interaction"},interaction:{_scriptable:!1,_indexable:!1}},[VM,HM,WM]);function YM(t){return!t||lt(t.size)||lt(t.family)?null:(t.style?t.style+" ":"")+(t.weight?t.weight+" ":"")+t.size+"px "+t.family}function c0(t,e,n,r,s){let o=e[s];return o||(o=e[s]=t.measureText(s).width,n.push(s)),o>r&&(r=o),r}function Nr(t,e,n){const r=t.currentDevicePixelRatio,s=n!==0?Math.max(n/2,.5):0;return Math.round((e-s)*r)/r+s}function d0(t,e){!e&&!t||(e=e||t.getContext("2d"),e.save(),e.resetTransform(),e.clearRect(0,0,t.width,t.height),e.restore())}function u0(t,e,n,r){f0(t,e,n,r,null)}function f0(t,e,n,r,s){let o,i,a,l,c,d,u,f;const g=e.pointStyle,m=e.rotation,p=e.radius;let y=(m||0)*SM;if(g&&typeof g=="object"&&(o=g.toString(),o==="[object HTMLImageElement]"||o==="[object HTMLCanvasElement]")){t.save(),t.translate(n,r),t.rotate(y),t.drawImage(g,-g.width/2,-g.height/2,g.width,g.height),t.restore();return}if(!(isNaN(p)||p<=0)){switch(t.beginPath(),g){default:s?t.ellipse(n,r,s/2,p,0,0,St):t.arc(n,r,p,0,St),t.closePath();break;case"triangle":d=s?s/2:p,t.moveTo(n+Math.sin(y)*d,r-Math.cos(y)*p),y+=Kp,t.lineTo(n+Math.sin(y)*d,r-Math.cos(y)*p),y+=Kp,t.lineTo(n+Math.sin(y)*d,r-Math.cos(y)*p),t.closePath();break;case"rectRounded":c=p*.516,l=p-c,i=Math.cos(y+_r)*l,u=Math.cos(y+_r)*(s?s/2-c:l),a=Math.sin(y+_r)*l,f=Math.sin(y+_r)*(s?s/2-c:l),t.arc(n-u,r-a,c,y-ct,y-Mt),t.arc(n+f,r-i,c,y-Mt,y),t.arc(n+u,r+a,c,y,y+Mt),t.arc(n-f,r+i,c,y+Mt,y+ct),t.closePath();break;case"rect":if(!m){l=Math.SQRT1_2*p,d=s?s/2:l,t.rect(n-d,r-l,2*d,2*l);break}y+=_r;case"rectRot":u=Math.cos(y)*(s?s/2:p),i=Math.cos(y)*p,a=Math.sin(y)*p,f=Math.sin(y)*(s?s/2:p),t.moveTo(n-u,r-a),t.lineTo(n+f,r-i),t.lineTo(n+u,r+a),t.lineTo(n-f,r+i),t.closePath();break;case"crossRot":y+=_r;case"cross":u=Math.cos(y)*(s?s/2:p),i=Math.cos(y)*p,a=Math.sin(y)*p,f=Math.sin(y)*(s?s/2:p),t.moveTo(n-u,r-a),t.lineTo(n+u,r+a),t.moveTo(n+f,r-i),t.lineTo(n-f,r+i);break;case"star":u=Math.cos(y)*(s?s/2:p),i=Math.cos(y)*p,a=Math.sin(y)*p,f=Math.sin(y)*(s?s/2:p),t.moveTo(n-u,r-a),t.lineTo(n+u,r+a),t.moveTo(n+f,r-i),t.lineTo(n-f,r+i),y+=_r,u=Math.cos(y)*(s?s/2:p),i=Math.cos(y)*p,a=Math.sin(y)*p,f=Math.sin(y)*(s?s/2:p),t.moveTo(n-u,r-a),t.lineTo(n+u,r+a),t.moveTo(n+f,r-i),t.lineTo(n-f,r+i);break;case"line":i=s?s/2:Math.cos(y)*p,a=Math.sin(y)*p,t.moveTo(n-i,r-a),t.lineTo(n+i,r+a);break;case"dash":t.moveTo(n,r),t.lineTo(n+Math.cos(y)*(s?s/2:p),r+Math.sin(y)*p);break;case!1:t.closePath();break}t.fill(),e.borderWidth>0&&t.stroke()}}function h0(t,e,n){return n=n||.5,!e||t&&t.x>e.left-n&&t.xe.top-n&&t.y0&&o.strokeColor!=="";let l,c;for(t.save(),t.font=s.string,KM(t,o),l=0;l+t||0;function Jc(t,e){const n={},r=Qe(e),s=r?Object.keys(e):e,o=Qe(t)?r?i=>Je(t[i],t[e[i]]):i=>t[i]:()=>t;for(const i of s)n[i]=RM(o(i));return n}function g0(t){return Jc(t,{top:"y",right:"x",bottom:"y",left:"x"})}function ds(t){return Jc(t,["topLeft","topRight","bottomLeft","bottomRight"])}function pn(t){const e=g0(t);return e.width=e.left+e.right,e.height=e.top+e.bottom,e}function Bt(t,e){t=t||{},e=e||xt.font;let n=Je(t.size,e.size);typeof n=="string"&&(n=parseInt(n,10));let r=Je(t.style,e.style);r&&!(""+r).match(QM)&&(console.warn('Invalid font style specified: "'+r+'"'),r=void 0);const s={family:Je(t.family,e.family),lineHeight:JM(Je(t.lineHeight,e.lineHeight),n),size:n,style:r,weight:Je(t.weight,e.weight),string:""};return s.string=YM(s),s}function la(t,e,n,r){let s,o,i;for(s=0,o=t.length;sn&&a===0?0:a+l;return{min:i(r,-Math.abs(o)),max:i(s,o)}}function us(t,e){return Object.assign(Object.create(t),e)}function Rc(t,e=[""],n,r,s=()=>t[0]){const o=n||t;typeof r>"u"&&(r=v0("_fallback",t));const i={[Symbol.toStringTag]:"Object",_cacheable:!0,_scopes:t,_rootScopes:o,_fallback:r,_getTarget:s,override:a=>Rc([a,...t],e,o,r)};return new Proxy(i,{deleteProperty(a,l){return delete a[l],delete a._keys,delete t[0][l],!0},get(a,l){return m0(a,l,()=>lN(l,e,t,a))},getOwnPropertyDescriptor(a,l){return Reflect.getOwnPropertyDescriptor(a._scopes[0],l)},getPrototypeOf(){return Reflect.getPrototypeOf(t[0])},has(a,l){return x0(a).includes(l)},ownKeys(a){return x0(a)},set(a,l,c){const d=a._storage||(a._storage=s());return a[l]=d[l]=c,delete a._keys,!0}})}function fs(t,e,n,r){const s={_cacheable:!1,_proxy:t,_context:e,_subProxy:n,_stack:new Set,_descriptors:p0(t,r),setContext:o=>fs(t,o,n,r),override:o=>fs(t.override(o),e,n,r)};return new Proxy(s,{deleteProperty(o,i){return delete o[i],delete t[i],!0},get(o,i,a){return m0(o,i,()=>nN(o,i,a))},getOwnPropertyDescriptor(o,i){return o._descriptors.allKeys?Reflect.has(t,i)?{enumerable:!0,configurable:!0}:void 0:Reflect.getOwnPropertyDescriptor(t,i)},getPrototypeOf(){return Reflect.getPrototypeOf(t)},has(o,i){return Reflect.has(t,i)},ownKeys(){return Reflect.ownKeys(t)},set(o,i,a){return t[i]=a,delete o[i],!0}})}function p0(t,e={scriptable:!0,indexable:!0}){const{_scriptable:n=e.scriptable,_indexable:r=e.indexable,_allKeys:s=e.allKeys}=t;return{allKeys:s,scriptable:n,indexable:r,isScriptable:lr(n)?n:()=>n,isIndexable:lr(r)?r:()=>r}}const tN=(t,e)=>t?t+Bc(e):e,ed=(t,e)=>Qe(e)&&t!=="adapters"&&(Object.getPrototypeOf(e)===null||e.constructor===Object);function m0(t,e,n){if(Object.prototype.hasOwnProperty.call(t,e)||e==="constructor")return t[e];const r=n();return t[e]=r,r}function nN(t,e,n){const{_proxy:r,_context:s,_subProxy:o,_descriptors:i}=t;let a=r[e];return lr(a)&&i.isScriptable(e)&&(a=rN(e,a,t,n)),Et(a)&&a.length&&(a=sN(e,a,t,i.isIndexable)),ed(e,a)&&(a=fs(a,s,o&&o[e],i)),a}function rN(t,e,n,r){const{_proxy:s,_context:o,_subProxy:i,_stack:a}=n;if(a.has(t))throw new Error("Recursion detected: "+Array.from(a).join("->")+"->"+t);a.add(t);let l=e(o,i||r);return a.delete(t),ed(t,l)&&(l=td(s._scopes,s,t,l)),l}function sN(t,e,n,r){const{_proxy:s,_context:o,_subProxy:i,_descriptors:a}=n;if(typeof o.index<"u"&&r(t))return e[o.index%e.length];if(Qe(e[0])){const l=e,c=s._scopes.filter(d=>d!==l);e=[];for(const d of l){const u=td(c,s,t,d);e.push(fs(u,o,i&&i[t],a))}}return e}function b0(t,e,n){return lr(t)?t(e,n):t}const oN=(t,e)=>t===!0?e:typeof t=="string"?Sr(e,t):void 0;function iN(t,e,n,r,s){for(const o of e){const i=oN(n,o);if(i){t.add(i);const a=b0(i._fallback,n,s);if(typeof a<"u"&&a!==n&&a!==r)return a}else if(i===!1&&typeof r<"u"&&n!==r)return null}return!1}function td(t,e,n,r){const s=e._rootScopes,o=b0(e._fallback,n,r),i=[...t,...s],a=new Set;a.add(r);let l=y0(a,i,n,o||n,r);return l===null||typeof o<"u"&&o!==n&&(l=y0(a,i,o,l,r),l===null)?!1:Rc(Array.from(a),[""],s,o,()=>aN(e,n,r))}function y0(t,e,n,r,s){for(;n;)n=iN(t,e,n,r,s);return n}function aN(t,e,n){const r=t._getTarget();e in r||(r[e]={});const s=r[e];return Et(s)&&Qe(n)?n:s||{}}function lN(t,e,n,r){let s;for(const o of e)if(s=v0(tN(o,t),n),typeof s<"u")return ed(t,s)?td(n,r,t,s):s}function v0(t,e){for(const n of e){if(!n)continue;const r=n[t];if(typeof r<"u")return r}}function x0(t){let e=t._keys;return e||(e=t._keys=cN(t._scopes)),e}function cN(t){const e=new Set;for(const n of t)for(const r of Object.keys(n).filter(s=>!s.startsWith("_")))e.add(r);return Array.from(e)}function nd(){return typeof window<"u"&&typeof document<"u"}function rd(t){let e=t.parentNode;return e&&e.toString()==="[object ShadowRoot]"&&(e=e.host),e}function ca(t,e,n){let r;return typeof t=="string"?(r=parseInt(t,10),t.indexOf("%")!==-1&&(r=r/100*e.parentNode[n])):r=t,r}const da=t=>t.ownerDocument.defaultView.getComputedStyle(t,null);function dN(t,e){return da(t).getPropertyValue(e)}const uN=["top","right","bottom","left"];function Or(t,e,n){const r={};n=n?"-"+n:"";for(let s=0;s<4;s++){const o=uN[s];r[o]=parseFloat(t[e+"-"+o+n])||0}return r.width=r.left+r.right,r.height=r.top+r.bottom,r}const fN=(t,e,n)=>(t>0||e>0)&&(!n||!n.shadowRoot);function hN(t,e){const n=t.touches,r=n&&n.length?n[0]:t,{offsetX:s,offsetY:o}=r;let i=!1,a,l;if(fN(s,o,t.target))a=s,l=o;else{const c=e.getBoundingClientRect();a=r.clientX-c.left,l=r.clientY-c.top,i=!0}return{x:a,y:l,box:i}}function Tr(t,e){if("native"in t)return t;const{canvas:n,currentDevicePixelRatio:r}=e,s=da(n),o=s.boxSizing==="border-box",i=Or(s,"padding"),a=Or(s,"border","width"),{x:l,y:c,box:d}=hN(t,n),u=i.left+(d&&a.left),f=i.top+(d&&a.top);let{width:g,height:m}=e;return o&&(g-=i.width+a.width,m-=i.height+a.height),{x:Math.round((l-u)/g*n.width/r),y:Math.round((c-f)/m*n.height/r)}}function gN(t,e,n){let r,s;if(e===void 0||n===void 0){const o=t&&rd(t);if(!o)e=t.clientWidth,n=t.clientHeight;else{const i=o.getBoundingClientRect(),a=da(o),l=Or(a,"border","width"),c=Or(a,"padding");e=i.width-c.width-l.width,n=i.height-c.height-l.height,r=ca(a.maxWidth,o,"clientWidth"),s=ca(a.maxHeight,o,"clientHeight")}}return{width:e,height:n,maxWidth:r||na,maxHeight:s||na}}const dr=t=>Math.round(t*10)/10;function pN(t,e,n,r){const s=da(t),o=Or(s,"margin"),i=ca(s.maxWidth,t,"clientWidth")||na,a=ca(s.maxHeight,t,"clientHeight")||na,l=gN(t,e,n);let{width:c,height:d}=l;if(s.boxSizing==="content-box"){const f=Or(s,"border","width"),g=Or(s,"padding");c-=g.width+f.width,d-=g.height+f.height}return c=Math.max(0,c-o.width),d=Math.max(0,r?c/r:d-o.height),c=dr(Math.min(c,i,l.maxWidth)),d=dr(Math.min(d,a,l.maxHeight)),c&&!d&&(d=dr(c/2)),(e!==void 0||n!==void 0)&&r&&l.height&&d>l.height&&(d=l.height,c=dr(Math.floor(d*r))),{width:c,height:d}}function w0(t,e,n){const r=e||1,s=dr(t.height*r),o=dr(t.width*r);t.height=dr(t.height),t.width=dr(t.width);const i=t.canvas;return i.style&&(n||!i.style.height&&!i.style.width)&&(i.style.height=`${t.height}px`,i.style.width=`${t.width}px`),t.currentDevicePixelRatio!==r||i.height!==s||i.width!==o?(t.currentDevicePixelRatio=r,i.height=s,i.width=o,t.ctx.setTransform(r,0,0,r,0,0),!0):!1}const mN=(function(){let t=!1;try{const e={get passive(){return t=!0,!1}};nd()&&(window.addEventListener("test",null,e),window.removeEventListener("test",null,e))}catch{}return t})();function k0(t,e){const n=dN(t,e),r=n&&n.match(/^(\d+)(\.\d+)?px$/);return r?+r[1]:void 0}const bN=function(t,e){return{x(n){return t+t+e-n},setWidth(n){e=n},textAlign(n){return n==="center"?n:n==="right"?"left":"right"},xPlus(n,r){return n-r},leftForLtr(n,r){return n-r}}},yN=function(){return{x(t){return t},setWidth(t){},textAlign(t){return t},xPlus(t,e){return t+e},leftForLtr(t,e){return t}}};function hs(t,e,n){return t?bN(e,n):yN()}function C0(t,e){let n,r;(e==="ltr"||e==="rtl")&&(n=t.canvas.style,r=[n.getPropertyValue("direction"),n.getPropertyPriority("direction")],n.setProperty("direction",e,"important"),t.prevTextDirection=r)}function S0(t,e){e!==void 0&&(delete t.prevTextDirection,t.canvas.style.setProperty("direction",e[0],e[1]))}function ua(t,e,n){return t.options.clip?t[n]:e[n]}function vN(t,e){const{xScale:n,yScale:r}=t;return n&&r?{left:ua(n,e,"left"),right:ua(n,e,"right"),top:ua(r,e,"top"),bottom:ua(r,e,"bottom")}:e}function xN(t,e){const n=e._clip;if(n.disabled)return!1;const r=vN(e,t.chartArea);return{left:n.left===!1?0:r.left-(n.left===!0?0:n.left),right:n.right===!1?t.width:r.right+(n.right===!0?0:n.right),top:n.top===!1?0:r.top-(n.top===!0?0:n.top),bottom:n.bottom===!1?t.height:r.bottom+(n.bottom===!0?0:n.bottom)}}class wN{constructor(){this._request=null,this._charts=new Map,this._running=!1,this._lastDate=void 0}_notify(e,n,r,s){const o=n.listeners[s],i=n.duration;o.forEach(a=>a({chart:e,initial:n.initial,numSteps:i,currentStep:Math.min(r-n.start,i)}))}_refresh(){this._request||(this._running=!0,this._request=t0.call(window,()=>{this._update(),this._request=null,this._running&&this._refresh()}))}_update(e=Date.now()){let n=0;this._charts.forEach((r,s)=>{if(!r.running||!r.items.length)return;const o=r.items;let i=o.length-1,a=!1,l;for(;i>=0;--i)l=o[i],l._active?(l._total>r.duration&&(r.duration=l._total),l.tick(e),a=!0):(o[i]=o[o.length-1],o.pop());a&&(s.draw(),this._notify(s,r,e,"progress")),o.length||(r.running=!1,this._notify(s,r,e,"complete"),r.initial=!1),n+=o.length}),this._lastDate=e,n===0&&(this._running=!1)}_getAnims(e){const n=this._charts;let r=n.get(e);return r||(r={running:!1,initial:!0,items:[],listeners:{complete:[],progress:[]}},n.set(e,r)),r}listen(e,n,r){this._getAnims(e).listeners[n].push(r)}add(e,n){!n||!n.length||this._getAnims(e).items.push(...n)}has(e){return this._getAnims(e).items.length>0}start(e){const n=this._charts.get(e);n&&(n.running=!0,n.start=Date.now(),n.duration=n.items.reduce((r,s)=>Math.max(r,s._duration),0),this._refresh())}running(e){if(!this._running)return!1;const n=this._charts.get(e);return!(!n||!n.running||!n.items.length)}stop(e){const n=this._charts.get(e);if(!n||!n.items.length)return;const r=n.items;let s=r.length-1;for(;s>=0;--s)r[s].cancel();n.items=[],this._notify(e,n,Date.now(),"complete")}remove(e){return this._charts.delete(e)}}var Xn=new wN;const _0="transparent",kN={boolean(t,e,n){return n>.5?e:t},color(t,e,n){const r=i0(t||_0),s=r.valid&&i0(e||_0);return s&&s.valid?s.mix(r,n).hexString():e},number(t,e,n){return t+(e-t)*n}};class CN{constructor(e,n,r,s){const o=n[r];s=la([e.to,s,o,e.from]);const i=la([e.from,o,s]);this._active=!0,this._fn=e.fn||kN[e.type||typeof i],this._easing=po[e.easing]||po.linear,this._start=Math.floor(Date.now()+(e.delay||0)),this._duration=this._total=Math.floor(e.duration),this._loop=!!e.loop,this._target=n,this._prop=r,this._from=i,this._to=s,this._promises=void 0}active(){return this._active}update(e,n,r){if(this._active){this._notify(!1);const s=this._target[this._prop],o=r-this._start,i=this._duration-o;this._start=r,this._duration=Math.floor(Math.max(i,e.duration)),this._total+=o,this._loop=!!e.loop,this._to=la([e.to,n,s,e.from]),this._from=la([e.from,s,n])}}cancel(){this._active&&(this.tick(Date.now()),this._active=!1,this._notify(!1))}tick(e){const n=e-this._start,r=this._duration,s=this._prop,o=this._from,i=this._loop,a=this._to;let l;if(this._active=o!==a&&(i||n1?2-l:l,l=this._easing(Math.min(1,Math.max(0,l))),this._target[s]=this._fn(o,a,l)}wait(){const e=this._promises||(this._promises=[]);return new Promise((n,r)=>{e.push({res:n,rej:r})})}_notify(e){const n=e?"res":"rej",r=this._promises||[];for(let s=0;s{const o=e[s];if(!Qe(o))return;const i={};for(const a of n)i[a]=o[a];(Et(o.properties)&&o.properties||[s]).forEach(a=>{(a===s||!r.has(a))&&r.set(a,i)})})}_animateOptions(e,n){const r=n.options,s=_N(e,r);if(!s)return[];const o=this._createAnimations(s,r);return r.$shared&&SN(e.options.$animations,r).then(()=>{e.options=r},()=>{}),o}_createAnimations(e,n){const r=this._properties,s=[],o=e.$animations||(e.$animations={}),i=Object.keys(n),a=Date.now();let l;for(l=i.length-1;l>=0;--l){const c=i[l];if(c.charAt(0)==="$")continue;if(c==="options"){s.push(...this._animateOptions(e,n));continue}const d=n[c];let u=o[c];const f=r.get(c);if(u)if(f&&u.active()){u.update(f,d,a);continue}else u.cancel();if(!f||!f.duration){e[c]=d;continue}o[c]=u=new CN(f,e,c,d),s.push(u)}return s}update(e,n){if(this._properties.size===0){Object.assign(e,n);return}const r=this._createAnimations(e,n);if(r.length)return Xn.add(this._chart,r),!0}}function SN(t,e){const n=[],r=Object.keys(e);for(let s=0;s0||!n&&o<0)return s.index}return null}function A0(t,e){const{chart:n,_cachedMeta:r}=t,s=n._stacks||(n._stacks={}),{iScale:o,vScale:i,index:a}=r,l=o.axis,c=i.axis,d=ON(o,i,r),u=e.length;let f;for(let g=0;gn[r].axis===e).shift()}function DN(t,e){return us(t,{active:!1,dataset:void 0,datasetIndex:e,index:e,mode:"default",type:"dataset"})}function IN(t,e,n){return us(t,{active:!1,dataIndex:e,parsed:void 0,raw:void 0,element:n,index:e,mode:"default",type:"data"})}function yo(t,e){const n=t.controller.index,r=t.vScale&&t.vScale.axis;if(r){e=e||t._parsed;for(const s of e){const o=s._stacks;if(!o||o[r]===void 0||o[r][n]===void 0)return;delete o[r][n],o[r]._visualValues!==void 0&&o[r]._visualValues[n]!==void 0&&delete o[r]._visualValues[n]}}}const id=t=>t==="reset"||t==="none",D0=(t,e)=>e?t:Object.assign({},t),LN=(t,e,n)=>t&&!e.hidden&&e._stacked&&{keys:N0(n,!0),values:null};class gs{constructor(e,n){this.chart=e,this._ctx=e.ctx,this.index=n,this._cachedDataOpts={},this._cachedMeta=this.getMeta(),this._type=this._cachedMeta.type,this.options=void 0,this._parsing=!1,this._data=void 0,this._objectData=void 0,this._sharedOptions=void 0,this._drawStart=void 0,this._drawCount=void 0,this.enableOptionSharing=!1,this.supportsDecimation=!1,this.$context=void 0,this._syncList=[],this.datasetElementType=new.target.datasetElementType,this.dataElementType=new.target.dataElementType,this.initialize()}initialize(){const e=this._cachedMeta;this.configure(),this.linkScales(),e._stacked=sd(e.vScale,e),this.addElements(),this.options.fill&&!this.chart.isPluginEnabled("filler")&&console.warn("Tried to use the 'fill' option without the 'Filler' plugin enabled. Please import and register the 'Filler' plugin and make sure it is not disabled in the options")}updateIndex(e){this.index!==e&&yo(this._cachedMeta),this.index=e}linkScales(){const e=this.chart,n=this._cachedMeta,r=this.getDataset(),s=(u,f,g,m)=>u==="x"?f:u==="r"?m:g,o=n.xAxisID=Je(r.xAxisID,od(e,"x")),i=n.yAxisID=Je(r.yAxisID,od(e,"y")),a=n.rAxisID=Je(r.rAxisID,od(e,"r")),l=n.indexAxis,c=n.iAxisID=s(l,o,i,a),d=n.vAxisID=s(l,i,o,a);n.xScale=this.getScaleForId(o),n.yScale=this.getScaleForId(i),n.rScale=this.getScaleForId(a),n.iScale=this.getScaleForId(c),n.vScale=this.getScaleForId(d)}getDataset(){return this.chart.data.datasets[this.index]}getMeta(){return this.chart.getDatasetMeta(this.index)}getScaleForId(e){return this.chart.scales[e]}_getOtherScale(e){const n=this._cachedMeta;return e===n.iScale?n.vScale:n.iScale}reset(){this._update("reset")}_destroy(){const e=this._cachedMeta;this._data&&Rp(this._data,this),e._stacked&&yo(e)}_dataCheck(){const e=this.getDataset(),n=e.data||(e.data=[]),r=this._data;if(Qe(n)){const s=this._cachedMeta;this._data=NN(n,s)}else if(r!==n){if(r){Rp(r,this);const s=this._cachedMeta;yo(s),s._parsed=[]}n&&Object.isExtensible(n)&&LM(n,this),this._syncList=[],this._data=n}}addElements(){const e=this._cachedMeta;this._dataCheck(),this.datasetElementType&&(e.dataset=new this.datasetElementType)}buildOrUpdateElements(e){const n=this._cachedMeta,r=this.getDataset();let s=!1;this._dataCheck();const o=n._stacked;n._stacked=sd(n.vScale,n),n.stack!==r.stack&&(s=!0,yo(n),n.stack=r.stack),this._resyncElements(e),(s||o!==n._stacked)&&(A0(this,n._parsed),n._stacked=sd(n.vScale,n))}configure(){const e=this.chart.config,n=e.datasetScopeKeys(this._type),r=e.getOptionScopes(this.getDataset(),n,!0);this.options=e.createResolver(r,this.getContext()),this._parsing=this.options.parsing,this._cachedDataOpts={}}parse(e,n){const{_cachedMeta:r,_data:s}=this,{iScale:o,_stacked:i}=r,a=o.axis;let l=e===0&&n===s.length?!0:r._sorted,c=e>0&&r._parsed[e-1],d,u,f;if(this._parsing===!1)r._parsed=s,r._sorted=!0,f=s;else{Et(s[e])?f=this.parseArrayData(r,s,e,n):Qe(s[e])?f=this.parseObjectData(r,s,e,n):f=this.parsePrimitiveData(r,s,e,n);const g=()=>u[a]===null||c&&u[a]p||u=0;--f)if(!m()){this.updateRangeFromParsed(c,e,g,l);break}}return c}getAllParsedValues(e){const n=this._cachedMeta._parsed,r=[];let s,o,i;for(s=0,o=n.length;s=0&&ethis.getContext(r,s,n),p=c.resolveNamedOptions(f,g,m,u);return p.$shared&&(p.$shared=l,o[i]=Object.freeze(D0(p,l))),p}_resolveAnimations(e,n,r){const s=this.chart,o=this._cachedDataOpts,i=`animation-${n}`,a=o[i];if(a)return a;let l;if(s.options.animation!==!1){const d=this.chart.config,u=d.datasetAnimationScopeKeys(this._type,n),f=d.getOptionScopes(this.getDataset(),u);l=d.createResolver(f,this.getContext(e,r,n))}const c=new E0(s,l&&l.animations);return l&&l._cacheable&&(o[i]=Object.freeze(c)),c}getSharedOptions(e){if(e.$shared)return this._sharedOptions||(this._sharedOptions=Object.assign({},e))}includeOptions(e,n){return!n||id(e)||this.chart._animationsDisabled}_getSharedOptions(e,n){const r=this.resolveDataElementOptions(e,n),s=this._sharedOptions,o=this.getSharedOptions(r),i=this.includeOptions(n,o)||o!==s;return this.updateSharedOptions(o,n,r),{sharedOptions:o,includeOptions:i}}updateElement(e,n,r,s){id(s)?Object.assign(e,r):this._resolveAnimations(n,s).update(e,r)}updateSharedOptions(e,n,r){e&&!id(n)&&this._resolveAnimations(void 0,n).update(e,r)}_setStyle(e,n,r,s){e.active=s;const o=this.getStyle(n,s);this._resolveAnimations(n,r,s).update(e,{options:!s&&this.getSharedOptions(o)||o})}removeHoverStyle(e,n,r){this._setStyle(e,r,"active",!1)}setHoverStyle(e,n,r){this._setStyle(e,r,"active",!0)}_removeDatasetHoverStyle(){const e=this._cachedMeta.dataset;e&&this._setStyle(e,void 0,"active",!1)}_setDatasetHoverStyle(){const e=this._cachedMeta.dataset;e&&this._setStyle(e,void 0,"active",!0)}_resyncElements(e){const n=this._data,r=this._cachedMeta.data;for(const[a,l,c]of this._syncList)this[a](l,c);this._syncList=[];const s=r.length,o=n.length,i=Math.min(o,s);i&&this.parse(0,i),o>s?this._insertElements(s,o-s,e):o{for(c.length+=n,a=c.length-1;a>=i;a--)c[a]=c[a-n]};for(l(o),a=e;as-o))}return t._cache.$bar}function $N(t){const e=t.iScale,n=PN(e,t.type);let r=e._length,s,o,i,a;const l=()=>{i===32767||i===-32768||(go(a)&&(r=Math.min(r,Math.abs(i-a)||r)),a=i)};for(s=0,o=n.length;s0?s[t-1]:null,a=tMath.abs(a)&&(l=a,c=i),e[n.axis]=c,e._custom={barStart:l,barEnd:c,start:s,end:o,min:i,max:a}}function I0(t,e,n,r){return Et(t)?VN(t,e,n,r):e[n.axis]=n.parse(t,r),e}function L0(t,e,n,r){const s=t.iScale,o=t.vScale,i=s.getLabels(),a=s===o,l=[];let c,d,u,f;for(c=n,d=n+r;c=n?1:-1)}function UN(t){let e,n,r,s,o;return t.horizontal?(e=t.base>t.x,n="left",r="right"):(e=t.based.controller.options.grouped),o=r.options.stacked,i=[],a=this._cachedMeta.controller.getParsed(n),l=a&&a[r.axis],c=d=>{const u=d._parsed.find(g=>g[r.axis]===l),f=u&&u[d.vScale.axis];if(lt(f)||isNaN(f))return!0};for(const d of s)if(!(n!==void 0&&c(d))&&((o===!1||i.indexOf(d.stack)===-1||o===void 0&&d.stack===void 0)&&i.push(d.stack),d.index===e))break;return i.length||i.push(void 0),i}_getStackCount(e){return this._getStacks(void 0,e).length}_getAxisCount(){return this._getAxis().length}getFirstScaleIdForIndexAxis(){const e=this.chart.scales,n=this.chart.options.indexAxis;return Object.keys(e).filter(r=>e[r].axis===n).shift()}_getAxis(){const e={},n=this.getFirstScaleIdForIndexAxis();for(const r of this.chart.data.datasets)e[Je(this.chart.options.indexAxis==="x"?r.xAxisID:r.yAxisID,n)]=!0;return Object.keys(e)}_getStackIndex(e,n,r){const s=this._getStacks(e,r),o=n!==void 0?s.indexOf(n):-1;return o===-1?s.length-1:o}_getRuler(){const e=this.options,n=this._cachedMeta,r=n.iScale,s=[];let o,i;for(o=0,i=n.data.length;ooa(x,a,l,!0)?1:Math.max(E,E*n,C,C*n),m=(x,E,C)=>oa(x,a,l,!0)?-1:Math.min(E,E*n,C,C*n),p=g(0,c,u),y=g(Mt,d,f),b=m(ct,c,u),_=m(ct+Mt,d,f);r=(p-b)/2,s=(y-_)/2,o=-(p+b)/2,i=-(y+_)/2}return{ratioX:r,ratioY:s,offsetX:o,offsetY:i}}class vo extends gs{constructor(e,n){super(e,n),this.enableOptionSharing=!0,this.innerRadius=void 0,this.outerRadius=void 0,this.offsetX=void 0,this.offsetY=void 0}linkScales(){}parse(e,n){const r=this.getDataset().data,s=this._cachedMeta;if(this._parsing===!1)s._parsed=r;else{let o=l=>+r[l];if(Qe(r[e])){const{key:l="value"}=this._parsing;o=c=>+Sr(r[c],l)}let i,a;for(i=e,a=e+n;i0&&!isNaN(e)?St*(Math.abs(e)/n):0}getLabelAndValue(e){const n=this._cachedMeta,r=this.chart,s=r.data.labels||[],o=Kc(n._parsed[e],r.options.locale);return{label:s[e]||"",value:o}}getMaxBorderWidth(e){let n=0;const r=this.chart;let s,o,i,a,l;if(!e){for(s=0,o=r.data.datasets.length;se!=="spacing",_indexable:e=>e!=="spacing"&&!e.startsWith("borderDash")&&!e.startsWith("hoverBorderDash")}),$e(vo,"overrides",{aspectRatio:1,plugins:{legend:{labels:{generateLabels(e){const n=e.data,{labels:{pointStyle:r,textAlign:s,color:o,useBorderRadius:i,borderRadius:a}}=e.legend.options;return n.labels.length&&n.datasets.length?n.labels.map((l,c)=>{const u=e.getDatasetMeta(0).controller.getStyle(c);return{text:l,fillStyle:u.backgroundColor,fontColor:o,hidden:!e.getDataVisibility(c),lineDash:u.borderDash,lineDashOffset:u.borderDashOffset,lineJoin:u.borderJoinStyle,lineWidth:u.borderWidth,strokeStyle:u.borderColor,textAlign:s,pointStyle:r,borderRadius:i&&(a||u.borderRadius),index:c}}):[]}},onClick(e,n,r){r.chart.toggleDataVisibility(n.index),r.chart.update()}}}});function Ar(){throw new Error("This method is not implemented: Check that a complete date adapter is provided.")}class ld{constructor(e){$e(this,"options");this.options=e||{}}static override(e){Object.assign(ld.prototype,e)}init(){}formats(){return Ar()}parse(){return Ar()}format(){return Ar()}add(){return Ar()}diff(){return Ar()}startOf(){return Ar()}endOf(){return Ar()}}var YN={_date:ld};function KN(t,e,n,r){const{controller:s,data:o,_sorted:i}=t,a=s._cachedMeta.iScale,l=t.dataset&&t.dataset.options?t.dataset.options.spanGaps:null;if(a&&e===a.axis&&e!=="r"&&i&&o.length){const c=a._reversePixels?DM:Wc;if(r){if(s._sharedOptions){const d=o[0],u=typeof d.getRange=="function"&&d.getRange(e);if(u){const f=c(o,e,n-u),g=c(o,e,n+u);return{lo:f.lo,hi:g.hi}}}}else{const d=c(o,e,n);if(l){const{vScale:u}=s._cachedMeta,{_parsed:f}=t,g=f.slice(0,d.lo+1).reverse().findIndex(p=>!lt(p[u.axis]));d.lo-=Math.max(0,g);const m=f.slice(d.hi).findIndex(p=>!lt(p[u.axis]));d.hi+=Math.max(0,m)}return d}}return{lo:0,hi:o.length-1}}function ha(t,e,n,r,s){const o=t.getSortedVisibleDatasetMetas(),i=n[e];for(let a=0,l=o.length;a{l[i]&&l[i](e[n],s)&&(o.push({element:l,datasetIndex:c,index:d}),a=a||l.inRange(e.x,e.y,s))}),r&&!a?[]:o}var QN={modes:{index(t,e,n,r){const s=Tr(e,t),o=n.axis||"x",i=n.includeInvisible||!1,a=n.intersect?cd(t,s,o,r,i):dd(t,s,o,!1,r,i),l=[];return a.length?(t.getSortedVisibleDatasetMetas().forEach(c=>{const d=a[0].index,u=c.data[d];u&&!u.skip&&l.push({element:u,datasetIndex:c.index,index:d})}),l):[]},dataset(t,e,n,r){const s=Tr(e,t),o=n.axis||"xy",i=n.includeInvisible||!1;let a=n.intersect?cd(t,s,o,r,i):dd(t,s,o,!1,r,i);if(a.length>0){const l=a[0].datasetIndex,c=t.getDatasetMeta(l).data;a=[];for(let d=0;dn.pos===e)}function V0(t,e){return t.filter(n=>z0.indexOf(n.pos)===-1&&n.box.axis===e)}function wo(t,e){return t.sort((n,r)=>{const s=e?r:n,o=e?n:r;return s.weight===o.weight?s.index-o.index:s.weight-o.weight})}function JN(t){const e=[];let n,r,s,o,i,a;for(n=0,r=(t||[]).length;nc.box.fullSize),!0),r=wo(xo(e,"left"),!0),s=wo(xo(e,"right")),o=wo(xo(e,"top"),!0),i=wo(xo(e,"bottom")),a=V0(e,"x"),l=V0(e,"y");return{fullSize:n,leftAndTop:r.concat(o),rightAndBottom:s.concat(l).concat(i).concat(a),chartArea:xo(e,"chartArea"),vertical:r.concat(s).concat(l),horizontal:o.concat(i).concat(a)}}function H0(t,e,n,r){return Math.max(t[n],e[n])+Math.max(t[r],e[r])}function U0(t,e){t.top=Math.max(t.top,e.top),t.left=Math.max(t.left,e.left),t.bottom=Math.max(t.bottom,e.bottom),t.right=Math.max(t.right,e.right)}function nO(t,e,n,r){const{pos:s,box:o}=n,i=t.maxPadding;if(!Qe(s)){n.size&&(t[s]-=n.size);const u=r[n.stack]||{size:0,count:1};u.size=Math.max(u.size,n.horizontal?o.height:o.width),n.size=u.size/u.count,t[s]+=n.size}o.getPadding&&U0(i,o.getPadding());const a=Math.max(0,e.outerWidth-H0(i,t,"left","right")),l=Math.max(0,e.outerHeight-H0(i,t,"top","bottom")),c=a!==t.w,d=l!==t.h;return t.w=a,t.h=l,n.horizontal?{same:c,other:d}:{same:d,other:c}}function rO(t){const e=t.maxPadding;function n(r){const s=Math.max(e[r]-t[r],0);return t[r]+=s,s}t.y+=n("top"),t.x+=n("left"),n("right"),n("bottom")}function sO(t,e){const n=e.maxPadding;function r(s){const o={left:0,top:0,right:0,bottom:0};return s.forEach(i=>{o[i]=Math.max(e[i],n[i])}),o}return r(t?["left","right"]:["top","bottom"])}function ko(t,e,n,r){const s=[];let o,i,a,l,c,d;for(o=0,i=t.length,c=0;o{typeof p.beforeLayout=="function"&&p.beforeLayout()});const d=l.reduce((p,y)=>y.box.options&&y.box.options.display===!1?p:p+1,0)||1,u=Object.freeze({outerWidth:e,outerHeight:n,padding:s,availableWidth:o,availableHeight:i,vBoxMaxWidth:o/2/d,hBoxMaxHeight:i/2}),f=Object.assign({},s);U0(f,pn(r));const g=Object.assign({maxPadding:f,w:o,h:i,x:s.left,y:s.top},s),m=eO(l.concat(c),u);ko(a.fullSize,g,u,m),ko(l,g,u,m),ko(c,g,u,m)&&ko(l,g,u,m),rO(g),B0(a.leftAndTop,g,u,m),g.x+=g.w,g.y+=g.h,B0(a.rightAndBottom,g,u,m),t.chartArea={left:g.left,top:g.top,right:g.left+g.w,bottom:g.top+g.h,height:g.h,width:g.w},at(a.chartArea,p=>{const y=p.box;Object.assign(y,t.chartArea),y.update(g.w,g.h,{left:0,top:0,right:0,bottom:0})})}};class j0{acquireContext(e,n){}releaseContext(e){return!1}addEventListener(e,n,r){}removeEventListener(e,n,r){}getDevicePixelRatio(){return 1}getMaximumSize(e,n,r,s){return n=Math.max(0,n||e.width),r=r||e.height,{width:n,height:Math.max(0,s?Math.floor(n/s):r)}}isAttached(e){return!0}updateConfig(e){}}class oO extends j0{acquireContext(e){return e&&e.getContext&&e.getContext("2d")||null}updateConfig(e){e.options.animation=!1}}const pa="$chartjs",iO={touchstart:"mousedown",touchmove:"mousemove",touchend:"mouseup",pointerenter:"mouseenter",pointerdown:"mousedown",pointermove:"mousemove",pointerup:"mouseup",pointerleave:"mouseout",pointerout:"mouseout"},W0=t=>t===null||t==="";function aO(t,e){const n=t.style,r=t.getAttribute("height"),s=t.getAttribute("width");if(t[pa]={initial:{height:r,width:s,style:{display:n.display,height:n.height,width:n.width}}},n.display=n.display||"block",n.boxSizing=n.boxSizing||"border-box",W0(s)){const o=k0(t,"width");o!==void 0&&(t.width=o)}if(W0(r))if(t.style.height==="")t.height=t.width/(e||2);else{const o=k0(t,"height");o!==void 0&&(t.height=o)}return t}const G0=mN?{passive:!0}:!1;function lO(t,e,n){t&&t.addEventListener(e,n,G0)}function cO(t,e,n){t&&t.canvas&&t.canvas.removeEventListener(e,n,G0)}function dO(t,e){const n=iO[t.type]||t.type,{x:r,y:s}=Tr(t,e);return{type:n,chart:e,native:t,x:r!==void 0?r:null,y:s!==void 0?s:null}}function ma(t,e){for(const n of t)if(n===e||n.contains(e))return!0}function uO(t,e,n){const r=t.canvas,s=new MutationObserver(o=>{let i=!1;for(const a of o)i=i||ma(a.addedNodes,r),i=i&&!ma(a.removedNodes,r);i&&n()});return s.observe(document,{childList:!0,subtree:!0}),s}function fO(t,e,n){const r=t.canvas,s=new MutationObserver(o=>{let i=!1;for(const a of o)i=i||ma(a.removedNodes,r),i=i&&!ma(a.addedNodes,r);i&&n()});return s.observe(document,{childList:!0,subtree:!0}),s}const Co=new Map;let Y0=0;function K0(){const t=window.devicePixelRatio;t!==Y0&&(Y0=t,Co.forEach((e,n)=>{n.currentDevicePixelRatio!==t&&e()}))}function hO(t,e){Co.size||window.addEventListener("resize",K0),Co.set(t,e)}function gO(t){Co.delete(t),Co.size||window.removeEventListener("resize",K0)}function pO(t,e,n){const r=t.canvas,s=r&&rd(r);if(!s)return;const o=n0((a,l)=>{const c=s.clientWidth;n(a,l),c{const l=a[0],c=l.contentRect.width,d=l.contentRect.height;c===0&&d===0||o(c,d)});return i.observe(s),hO(t,o),i}function ud(t,e,n){n&&n.disconnect(),e==="resize"&&gO(t)}function mO(t,e,n){const r=t.canvas,s=n0(o=>{t.ctx!==null&&n(dO(o,t))},t);return lO(r,e,s),s}class bO extends j0{acquireContext(e,n){const r=e&&e.getContext&&e.getContext("2d");return r&&r.canvas===e?(aO(e,n),r):null}releaseContext(e){const n=e.canvas;if(!n[pa])return!1;const r=n[pa].initial;["height","width"].forEach(o=>{const i=r[o];lt(i)?n.removeAttribute(o):n.setAttribute(o,i)});const s=r.style||{};return Object.keys(s).forEach(o=>{n.style[o]=s[o]}),n.width=n.width,delete n[pa],!0}addEventListener(e,n,r){this.removeEventListener(e,n);const s=e.$proxies||(e.$proxies={}),i={attach:uO,detach:fO,resize:pO}[n]||mO;s[n]=i(e,n,r)}removeEventListener(e,n){const r=e.$proxies||(e.$proxies={}),s=r[n];if(!s)return;({attach:ud,detach:ud,resize:ud}[n]||cO)(e,n,s),r[n]=void 0}getDevicePixelRatio(){return window.devicePixelRatio}getMaximumSize(e,n,r,s){return pN(e,n,r,s)}isAttached(e){const n=e&&rd(e);return!!(n&&n.isConnected)}}function yO(t){return!nd()||typeof OffscreenCanvas<"u"&&t instanceof OffscreenCanvas?oO:bO}let Dr=(Aa=class{constructor(){$e(this,"x");$e(this,"y");$e(this,"active",!1);$e(this,"options");$e(this,"$animations")}tooltipPosition(e){const{x:n,y:r}=this.getProps(["x","y"],e);return{x:n,y:r}}hasValue(){return sa(this.x)&&sa(this.y)}getProps(e,n){const r=this.$animations;if(!n||!r)return this;const s={};return e.forEach(o=>{s[o]=r[o]&&r[o].active()?r[o]._to:this[o]}),s}},$e(Aa,"defaults",{}),$e(Aa,"defaultRoutes"),Aa);function vO(t,e){const n=t.options.ticks,r=xO(t),s=Math.min(n.maxTicksLimit||r,r),o=n.major.enabled?kO(e):[],i=o.length,a=o[0],l=o[i-1],c=[];if(i>s)return CO(e,c,o,i/s),c;const d=wO(o,e,s);if(i>0){let u,f;const g=i>1?Math.round((l-a)/(i-1)):null;for(ba(e,c,d,lt(g)?0:a-g,a),u=0,f=i-1;us)return l}return Math.max(s,1)}function kO(t){const e=[];let n,r;for(n=0,r=t.length;nt==="left"?"right":t==="right"?"left":t,X0=(t,e,n)=>e==="top"||e==="left"?t[e]+n:t[e]-n,q0=(t,e)=>Math.min(e||t,t);function Z0(t,e){const n=[],r=t.length/e,s=t.length;let o=0;for(;oi+a)))return l}function MO(t,e){at(t,n=>{const r=n.gc,s=r.length/2;let o;if(s>e){for(o=0;or?r:n,r=s&&n>r?n:r,{min:Fn(n,Fn(r,n)),max:Fn(r,Fn(n,r))}}getPadding(){return{left:this.paddingLeft||0,top:this.paddingTop||0,right:this.paddingRight||0,bottom:this.paddingBottom||0}}getTicks(){return this.ticks}getLabels(){const e=this.chart.data;return this.options.labels||(this.isHorizontal()?e.xLabels:e.yLabels)||e.labels||[]}getLabelItems(e=this.chart.chartArea){return this._labelItems||(this._labelItems=this._computeLabelItems(e))}beforeLayout(){this._cache={},this._dataLimitsCached=!1}beforeUpdate(){gt(this.options.beforeUpdate,[this])}update(e,n,r){const{beginAtZero:s,grace:o,ticks:i}=this.options,a=i.sampleSize;this.beforeUpdate(),this.maxWidth=e,this.maxHeight=n,this._margins=r=Object.assign({left:0,right:0,top:0,bottom:0},r),this.ticks=null,this._labelSizes=null,this._gridLineItems=null,this._labelItems=null,this.beforeSetDimensions(),this.setDimensions(),this.afterSetDimensions(),this._maxLength=this.isHorizontal()?this.width+r.left+r.right:this.height+r.top+r.bottom,this._dataLimitsCached||(this.beforeDataLimits(),this.determineDataLimits(),this.afterDataLimits(),this._range=eN(this,o,s),this._dataLimitsCached=!0),this.beforeBuildTicks(),this.ticks=this.buildTicks()||[],this.afterBuildTicks();const l=a=o||r<=1||!this.isHorizontal()){this.labelRotation=s;return}const d=this._getLabelSizes(),u=d.widest.width,f=d.highest.height,g=en(this.chart.width-u,0,this.maxWidth);a=e.offset?this.maxWidth/r:g/(r-1),u+6>a&&(a=g/(r-(e.offset?.5:1)),l=this.maxHeight-So(e.grid)-n.padding-Q0(e.title,this.chart.options.font),c=Math.sqrt(u*u+f*f),i=OM(Math.min(Math.asin(en((d.highest.height+6)/a,-1,1)),Math.asin(en(l/c,-1,1))-Math.asin(en(f/c,-1,1)))),i=Math.max(s,Math.min(o,i))),this.labelRotation=i}afterCalculateLabelRotation(){gt(this.options.afterCalculateLabelRotation,[this])}afterAutoSkip(){}beforeFit(){gt(this.options.beforeFit,[this])}fit(){const e={width:0,height:0},{chart:n,options:{ticks:r,title:s,grid:o}}=this,i=this._isVisible(),a=this.isHorizontal();if(i){const l=Q0(s,n.options.font);if(a?(e.width=this.maxWidth,e.height=So(o)+l):(e.height=this.maxHeight,e.width=So(o)+l),r.display&&this.ticks.length){const{first:c,last:d,widest:u,highest:f}=this._getLabelSizes(),g=r.padding*2,m=Kn(this.labelRotation),p=Math.cos(m),y=Math.sin(m);if(a){const b=r.mirror?0:y*u.width+p*f.height;e.height=Math.min(this.maxHeight,e.height+b+g)}else{const b=r.mirror?0:p*u.width+y*f.height;e.width=Math.min(this.maxWidth,e.width+b+g)}this._calculatePadding(c,d,y,p)}}this._handleMargins(),a?(this.width=this._length=n.width-this._margins.left-this._margins.right,this.height=e.height):(this.width=e.width,this.height=this._length=n.height-this._margins.top-this._margins.bottom)}_calculatePadding(e,n,r,s){const{ticks:{align:o,padding:i},position:a}=this.options,l=this.labelRotation!==0,c=a!=="top"&&this.axis==="x";if(this.isHorizontal()){const d=this.getPixelForTick(0)-this.left,u=this.right-this.getPixelForTick(this.ticks.length-1);let f=0,g=0;l?c?(f=s*e.width,g=r*n.height):(f=r*e.height,g=s*n.width):o==="start"?g=n.width:o==="end"?f=e.width:o!=="inner"&&(f=e.width/2,g=n.width/2),this.paddingLeft=Math.max((f-d+i)*this.width/(this.width-d),0),this.paddingRight=Math.max((g-u+i)*this.width/(this.width-u),0)}else{let d=n.height/2,u=e.height/2;o==="start"?(d=0,u=e.height):o==="end"&&(d=n.height,u=0),this.paddingTop=d+i,this.paddingBottom=u+i}}_handleMargins(){this._margins&&(this._margins.left=Math.max(this.paddingLeft,this._margins.left),this._margins.top=Math.max(this.paddingTop,this._margins.top),this._margins.right=Math.max(this.paddingRight,this._margins.right),this._margins.bottom=Math.max(this.paddingBottom,this._margins.bottom))}afterFit(){gt(this.options.afterFit,[this])}isHorizontal(){const{axis:e,position:n}=this.options;return n==="top"||n==="bottom"||e==="x"}isFullSize(){return this.options.fullSize}_convertTicksToLabels(e){this.beforeTickToLabelConversion(),this.generateTickLabels(e);let n,r;for(n=0,r=e.length;n({width:i[w]||0,height:a[w]||0});return{first:S(0),last:S(n-1),widest:S(k),highest:S(N),widths:i,heights:a}}getLabelForValue(e){return e}getPixelForValue(e,n){return NaN}getValueForPixel(e){}getPixelForTick(e){const n=this.ticks;return e<0||e>n.length-1?null:this.getPixelForValue(n[e].value)}getPixelForDecimal(e){this._reversePixels&&(e=1-e);const n=this._startPixel+e*this._length;return AM(this._alignToPixels?Nr(this.chart,n,0):n)}getDecimalForPixel(e){const n=(e-this._startPixel)/this._length;return this._reversePixels?1-n:n}getBasePixel(){return this.getPixelForValue(this.getBaseValue())}getBaseValue(){const{min:e,max:n}=this;return e<0&&n<0?n:e>0&&n>0?e:0}getContext(e){const n=this.ticks||[];if(e>=0&&ea*s?a/r:l/s:l*s0}_computeGridLineItems(e){const n=this.axis,r=this.chart,s=this.options,{grid:o,position:i,border:a}=s,l=o.offset,c=this.isHorizontal(),u=this.ticks.length+(l?1:0),f=So(o),g=[],m=a.setContext(this.getContext()),p=m.display?m.width:0,y=p/2,b=function(D){return Nr(r,D,p)};let _,x,E,C,k,N,S,w,M,T,P,L;if(i==="top")_=b(this.bottom),N=this.bottom-f,w=_-y,T=b(e.top)+y,L=e.bottom;else if(i==="bottom")_=b(this.top),T=e.top,L=b(e.bottom)-y,N=_+y,w=this.top+f;else if(i==="left")_=b(this.right),k=this.right-f,S=_-y,M=b(e.left)+y,P=e.right;else if(i==="right")_=b(this.left),M=e.left,P=b(e.right)-y,k=_+y,S=this.left+f;else if(n==="x"){if(i==="center")_=b((e.top+e.bottom)/2+.5);else if(Qe(i)){const D=Object.keys(i)[0],O=i[D];_=b(this.chart.scales[D].getPixelForValue(O))}T=e.top,L=e.bottom,N=_+y,w=N+f}else if(n==="y"){if(i==="center")_=b((e.left+e.right)/2);else if(Qe(i)){const D=Object.keys(i)[0],O=i[D];_=b(this.chart.scales[D].getPixelForValue(O))}k=_-y,S=k-f,M=e.left,P=e.right}const $=Je(s.ticks.maxTicksLimit,u),A=Math.max(1,Math.ceil(u/$));for(x=0;x0&&(X-=G/2);break}W={left:X,top:V,width:G+U.width,height:B+U.height,color:A.backdropColor}}y.push({label:E,font:w,textOffset:P,options:{rotation:p,color:O,strokeColor:z,strokeWidth:H,textAlign:j,textBaseline:L,translation:[C,k],backdrop:W}})}return y}_getXAxisLabelAlignment(){const{position:e,ticks:n}=this.options;if(-Kn(this.labelRotation))return e==="top"?"left":"right";let s="center";return n.align==="start"?s="left":n.align==="end"?s="right":n.align==="inner"&&(s="inner"),s}_getYAxisLabelAlignment(e){const{position:n,ticks:{crossAlign:r,mirror:s,padding:o}}=this.options,i=this._getLabelSizes(),a=e+o,l=i.widest.width;let c,d;return n==="left"?s?(d=this.right+o,r==="near"?c="left":r==="center"?(c="center",d+=l/2):(c="right",d+=l)):(d=this.right-a,r==="near"?c="right":r==="center"?(c="center",d-=l/2):(c="left",d=this.left)):n==="right"?s?(d=this.left+o,r==="near"?c="right":r==="center"?(c="center",d-=l/2):(c="left",d-=l)):(d=this.left+a,r==="near"?c="left":r==="center"?(c="center",d+=l/2):(c="right",d=this.right)):c="right",{textAlign:c,x:d}}_computeLabelArea(){if(this.options.ticks.mirror)return;const e=this.chart,n=this.options.position;if(n==="left"||n==="right")return{top:0,left:this.left,bottom:e.height,right:this.right};if(n==="top"||n==="bottom")return{top:this.top,left:0,bottom:this.bottom,right:e.width}}drawBackground(){const{ctx:e,options:{backgroundColor:n},left:r,top:s,width:o,height:i}=this;n&&(e.save(),e.fillStyle=n,e.fillRect(r,s,o,i),e.restore())}getLineWidthForValue(e){const n=this.options.grid;if(!this._isVisible()||!n.display)return 0;const s=this.ticks.findIndex(o=>o.value===e);return s>=0?n.setContext(this.getContext(s)).lineWidth:0}drawGrid(e){const n=this.options.grid,r=this.ctx,s=this._gridLineItems||(this._gridLineItems=this._computeGridLineItems(e));let o,i;const a=(l,c,d)=>{!d.width||!d.color||(r.save(),r.lineWidth=d.width,r.strokeStyle=d.color,r.setLineDash(d.borderDash||[]),r.lineDashOffset=d.borderDashOffset,r.beginPath(),r.moveTo(l.x,l.y),r.lineTo(c.x,c.y),r.stroke(),r.restore())};if(n.display)for(o=0,i=s.length;o{this.draw(o)}}]:[{z:r,draw:o=>{this.drawBackground(),this.drawGrid(o),this.drawTitle()}},{z:s,draw:()=>{this.drawBorder()}},{z:n,draw:o=>{this.drawLabels(o)}}]}getMatchingVisibleMetas(e){const n=this.chart.getSortedVisibleDatasetMetas(),r=this.axis+"AxisID",s=[];let o,i;for(o=0,i=n.length;o{const r=n.split("."),s=r.pop(),o=[t].concat(r).join("."),i=e[n].split("."),a=i.pop(),l=i.join(".");xt.route(o,s,l,a)})}function LO(t){return"id"in t&&"defaults"in t}class PO{constructor(){this.controllers=new ya(gs,"datasets",!0),this.elements=new ya(Dr,"elements"),this.plugins=new ya(Object,"plugins"),this.scales=new ya(ps,"scales"),this._typedRegistries=[this.controllers,this.scales,this.elements]}add(...e){this._each("register",e)}remove(...e){this._each("unregister",e)}addControllers(...e){this._each("register",e,this.controllers)}addElements(...e){this._each("register",e,this.elements)}addPlugins(...e){this._each("register",e,this.plugins)}addScales(...e){this._each("register",e,this.scales)}getController(e){return this._get(e,this.controllers,"controller")}getElement(e){return this._get(e,this.elements,"element")}getPlugin(e){return this._get(e,this.plugins,"plugin")}getScale(e){return this._get(e,this.scales,"scale")}removeControllers(...e){this._each("unregister",e,this.controllers)}removeElements(...e){this._each("unregister",e,this.elements)}removePlugins(...e){this._each("unregister",e,this.plugins)}removeScales(...e){this._each("unregister",e,this.scales)}_each(e,n,r){[...n].forEach(s=>{const o=r||this._getRegistryForType(s);r||o.isForType(s)||o===this.plugins&&s.id?this._exec(e,o,s):at(s,i=>{const a=r||this._getRegistryForType(i);this._exec(e,a,i)})})}_exec(e,n,r){const s=Bc(e);gt(r["before"+s],[],r),n[e](r),gt(r["after"+s],[],r)}_getRegistryForType(e){for(let n=0;no.filter(a=>!i.some(l=>a.plugin.id===l.plugin.id));this._notify(s(n,r),e,"stop"),this._notify(s(r,n),e,"start")}}function FO(t){const e={},n=[],r=Object.keys(Vn.plugins.items);for(let o=0;o1&&J0(t[0].toLowerCase());if(r)return r}throw new Error(`Cannot determine type of '${t}' axis. Please provide 'axis' or 'position' option.`)}function R0(t,e,n){if(n[e+"AxisID"]===t)return{axis:e}}function WO(t,e){if(e.data&&e.data.datasets){const n=e.data.datasets.filter(r=>r.xAxisID===t||r.yAxisID===t);if(n.length)return R0(t,"x",n[0])||R0(t,"y",n[0])}return{}}function GO(t,e){const n=Mr[t.type]||{scales:{}},r=e.scales||{},s=fd(t.type,e),o=Object.create(null);return Object.keys(r).forEach(i=>{const a=r[i];if(!Qe(a))return console.error(`Invalid scale configuration for scale: ${i}`);if(a._proxy)return console.warn(`Ignoring resolver passed as options for scale: ${i}`);const l=hd(i,a,WO(i,t),xt.scales[a.type]),c=BO(l,s),d=n.scales||{};o[i]=ho(Object.create(null),[{axis:l},a,d[l],d[c]])}),t.data.datasets.forEach(i=>{const a=i.type||t.type,l=i.indexAxis||fd(a,e),d=(Mr[a]||{}).scales||{};Object.keys(d).forEach(u=>{const f=UO(u,l),g=i[f+"AxisID"]||f;o[g]=o[g]||Object.create(null),ho(o[g],[{axis:f},r[g],d[u]])})}),Object.keys(o).forEach(i=>{const a=o[i];ho(a,[xt.scales[a.type],xt.scale])}),o}function em(t){const e=t.options||(t.options={});e.plugins=Je(e.plugins,{}),e.scales=GO(t,e)}function tm(t){return t=t||{},t.datasets=t.datasets||[],t.labels=t.labels||[],t}function YO(t){return t=t||{},t.data=tm(t.data),em(t),t}const nm=new Map,rm=new Set;function va(t,e){let n=nm.get(t);return n||(n=e(),nm.set(t,n),rm.add(n)),n}const _o=(t,e,n)=>{const r=Sr(e,n);r!==void 0&&t.add(r)};class KO{constructor(e){this._config=YO(e),this._scopeCache=new Map,this._resolverCache=new Map}get platform(){return this._config.platform}get type(){return this._config.type}set type(e){this._config.type=e}get data(){return this._config.data}set data(e){this._config.data=tm(e)}get options(){return this._config.options}set options(e){this._config.options=e}get plugins(){return this._config.plugins}update(){const e=this._config;this.clearCache(),em(e)}clearCache(){this._scopeCache.clear(),this._resolverCache.clear()}datasetScopeKeys(e){return va(e,()=>[[`datasets.${e}`,""]])}datasetAnimationScopeKeys(e,n){return va(`${e}.transition.${n}`,()=>[[`datasets.${e}.transitions.${n}`,`transitions.${n}`],[`datasets.${e}`,""]])}datasetElementScopeKeys(e,n){return va(`${e}-${n}`,()=>[[`datasets.${e}.elements.${n}`,`datasets.${e}`,`elements.${n}`,""]])}pluginScopeKeys(e){const n=e.id,r=this.type;return va(`${r}-plugin-${n}`,()=>[[`plugins.${n}`,...e.additionalOptionScopes||[]]])}_cachedScopes(e,n){const r=this._scopeCache;let s=r.get(e);return(!s||n)&&(s=new Map,r.set(e,s)),s}getOptionScopes(e,n,r){const{options:s,type:o}=this,i=this._cachedScopes(e,r),a=i.get(n);if(a)return a;const l=new Set;n.forEach(d=>{e&&(l.add(e),d.forEach(u=>_o(l,e,u))),d.forEach(u=>_o(l,s,u)),d.forEach(u=>_o(l,Mr[o]||{},u)),d.forEach(u=>_o(l,xt,u)),d.forEach(u=>_o(l,Xc,u))});const c=Array.from(l);return c.length===0&&c.push(Object.create(null)),rm.has(n)&&i.set(n,c),c}chartOptionScopes(){const{options:e,type:n}=this;return[e,Mr[n]||{},xt.datasets[n]||{},{type:n},xt,Xc]}resolveNamedOptions(e,n,r,s=[""]){const o={$shared:!0},{resolver:i,subPrefixes:a}=sm(this._resolverCache,e,s);let l=i;if(qO(i,n)){o.$shared=!1,r=lr(r)?r():r;const c=this.createResolver(e,r,a);l=fs(i,r,c)}for(const c of n)o[c]=l[c];return o}createResolver(e,n,r=[""],s){const{resolver:o}=sm(this._resolverCache,e,r);return Qe(n)?fs(o,n,void 0,s):o}}function sm(t,e,n){let r=t.get(e);r||(r=new Map,t.set(e,r));const s=n.join();let o=r.get(s);return o||(o={resolver:Rc(e,n),subPrefixes:n.filter(a=>!a.toLowerCase().includes("hover"))},r.set(s,o)),o}const XO=t=>Qe(t)&&Object.getOwnPropertyNames(t).some(e=>lr(t[e]));function qO(t,e){const{isScriptable:n,isIndexable:r}=p0(t);for(const s of e){const o=n(s),i=r(s),a=(i||o)&&t[s];if(o&&(lr(a)||XO(a))||i&&Et(a))return!0}return!1}var ZO="4.5.1";const QO=["top","bottom","left","right","chartArea"];function om(t,e){return t==="top"||t==="bottom"||QO.indexOf(t)===-1&&e==="x"}function im(t,e){return function(n,r){return n[t]===r[t]?n[e]-r[e]:n[t]-r[t]}}function am(t){const e=t.chart,n=e.options.animation;e.notifyPlugins("afterRender"),gt(n&&n.onComplete,[t],e)}function JO(t){const e=t.chart,n=e.options.animation;gt(n&&n.onProgress,[t],e)}function lm(t){return nd()&&typeof t=="string"?t=document.getElementById(t):t&&t.length&&(t=t[0]),t&&t.canvas&&(t=t.canvas),t}const xa={},cm=t=>{const e=lm(t);return Object.values(xa).filter(n=>n.canvas===e).pop()};function RO(t,e,n){const r=Object.keys(t);for(const s of r){const o=+s;if(o>=e){const i=t[s];delete t[s],(n>0||o>e)&&(t[o+n]=i)}}}function eT(t,e,n,r){return!n||t.type==="mouseout"?null:r?e:t}let Eo=(fr=class{static register(...e){Vn.add(...e),dm()}static unregister(...e){Vn.remove(...e),dm()}constructor(e,n){const r=this.config=new KO(n),s=lm(e),o=cm(s);if(o)throw new Error("Canvas is already in use. Chart with ID '"+o.id+"' must be destroyed before the canvas with ID '"+o.canvas.id+"' can be reused.");const i=r.createResolver(r.chartOptionScopes(),this.getContext());this.platform=new(r.platform||yO(s)),this.platform.updateConfig(r);const a=this.platform.acquireContext(s,i.aspectRatio),l=a&&a.canvas,c=l&&l.height,d=l&&l.width;if(this.id=bM(),this.ctx=a,this.canvas=l,this.width=d,this.height=c,this._options=i,this._aspectRatio=this.aspectRatio,this._layers=[],this._metasets=[],this._stacks=void 0,this.boxes=[],this.currentDevicePixelRatio=void 0,this.chartArea=void 0,this._active=[],this._lastEvent=void 0,this._listeners={},this._responsiveListeners=void 0,this._sortedMetasets=[],this.scales={},this._plugins=new $O,this.$proxies={},this._hiddenIndices={},this.attached=!1,this._animationsDisabled=void 0,this.$context=void 0,this._doResize=PM(u=>this.update(u),i.resizeDelay||0),this._dataChanges=[],xa[this.id]=this,!a||!l){console.error("Failed to create chart: can't acquire context from the given item");return}Xn.listen(this,"complete",am),Xn.listen(this,"progress",JO),this._initialize(),this.attached&&this.update()}get aspectRatio(){const{options:{aspectRatio:e,maintainAspectRatio:n},width:r,height:s,_aspectRatio:o}=this;return lt(e)?n&&o?o:s?r/s:null:e}get data(){return this.config.data}set data(e){this.config.data=e}get options(){return this._options}set options(e){this.config.options=e}get registry(){return Vn}_initialize(){return this.notifyPlugins("beforeInit"),this.options.responsive?this.resize():w0(this,this.options.devicePixelRatio),this.bindEvents(),this.notifyPlugins("afterInit"),this}clear(){return d0(this.canvas,this.ctx),this}stop(){return Xn.stop(this),this}resize(e,n){Xn.running(this)?this._resizeBeforeDraw={width:e,height:n}:this._resize(e,n)}_resize(e,n){const r=this.options,s=this.canvas,o=r.maintainAspectRatio&&this.aspectRatio,i=this.platform.getMaximumSize(s,e,n,o),a=r.devicePixelRatio||this.platform.getDevicePixelRatio(),l=this.width?"resize":"attach";this.width=i.width,this.height=i.height,this._aspectRatio=this.aspectRatio,w0(this,a,!0)&&(this.notifyPlugins("resize",{size:i}),gt(r.onResize,[this,i],this),this.attached&&this._doResize(l)&&this.render())}ensureScalesHaveIDs(){const n=this.options.scales||{};at(n,(r,s)=>{r.id=s})}buildOrUpdateScales(){const e=this.options,n=e.scales,r=this.scales,s=Object.keys(r).reduce((i,a)=>(i[a]=!1,i),{});let o=[];n&&(o=o.concat(Object.keys(n).map(i=>{const a=n[i],l=hd(i,a),c=l==="r",d=l==="x";return{options:a,dposition:c?"chartArea":d?"bottom":"left",dtype:c?"radialLinear":d?"category":"linear"}}))),at(o,i=>{const a=i.options,l=a.id,c=hd(l,a),d=Je(a.type,i.dtype);(a.position===void 0||om(a.position,c)!==om(i.dposition))&&(a.position=i.dposition),s[l]=!0;let u=null;if(l in r&&r[l].type===d)u=r[l];else{const f=Vn.getScale(d);u=new f({id:l,type:d,ctx:this.ctx,chart:this}),r[u.id]=u}u.init(a,e)}),at(s,(i,a)=>{i||delete r[a]}),at(r,i=>{mn.configure(this,i,i.options),mn.addBox(this,i)})}_updateMetasets(){const e=this._metasets,n=this.data.datasets.length,r=e.length;if(e.sort((s,o)=>s.index-o.index),r>n){for(let s=n;sn.length&&delete this._stacks,e.forEach((r,s)=>{n.filter(o=>o===r._dataset).length===0&&this._destroyDatasetMeta(s)})}buildOrUpdateControllers(){const e=[],n=this.data.datasets;let r,s;for(this._removeUnreferencedMetasets(),r=0,s=n.length;r{this.getDatasetMeta(n).controller.reset()},this)}reset(){this._resetElements(),this.notifyPlugins("reset")}update(e){const n=this.config;n.update();const r=this._options=n.createResolver(n.chartOptionScopes(),this.getContext()),s=this._animationsDisabled=!r.animation;if(this._updateScales(),this._checkEventBindings(),this._updateHiddenIndices(),this._plugins.invalidate(),this.notifyPlugins("beforeUpdate",{mode:e,cancelable:!0})===!1)return;const o=this.buildOrUpdateControllers();this.notifyPlugins("beforeElementsUpdate");let i=0;for(let c=0,d=this.data.datasets.length;c{c.reset()}),this._updateDatasets(e),this.notifyPlugins("afterUpdate",{mode:e}),this._layers.sort(im("z","_idx"));const{_active:a,_lastEvent:l}=this;l?this._eventHandler(l,!0):a.length&&this._updateHoverStyles(a,a,!0),this.render()}_updateScales(){at(this.scales,e=>{mn.removeBox(this,e)}),this.ensureScalesHaveIDs(),this.buildOrUpdateScales()}_checkEventBindings(){const e=this.options,n=new Set(Object.keys(this._listeners)),r=new Set(e.events);(!Yp(n,r)||!!this._responsiveListeners!==e.responsive)&&(this.unbindEvents(),this.bindEvents())}_updateHiddenIndices(){const{_hiddenIndices:e}=this,n=this._getUniformDataChanges()||[];for(const{method:r,start:s,count:o}of n){const i=r==="_removeElements"?-o:o;RO(e,s,i)}}_getUniformDataChanges(){const e=this._dataChanges;if(!e||!e.length)return;this._dataChanges=[];const n=this.data.datasets.length,r=o=>new Set(e.filter(i=>i[0]===o).map((i,a)=>a+","+i.splice(1).join(","))),s=r(0);for(let o=1;oo.split(",")).map(o=>({method:o[1],start:+o[2],count:+o[3]}))}_updateLayout(e){if(this.notifyPlugins("beforeLayout",{cancelable:!0})===!1)return;mn.update(this,this.width,this.height,e);const n=this.chartArea,r=n.width<=0||n.height<=0;this._layers=[],at(this.boxes,s=>{r&&s.position==="chartArea"||(s.configure&&s.configure(),this._layers.push(...s._layers()))},this),this._layers.forEach((s,o)=>{s._idx=o}),this.notifyPlugins("afterLayout")}_updateDatasets(e){if(this.notifyPlugins("beforeDatasetsUpdate",{mode:e,cancelable:!0})!==!1){for(let n=0,r=this.data.datasets.length;n=0;--n)this._drawDataset(e[n]);this.notifyPlugins("afterDatasetsDraw")}_drawDataset(e){const n=this.ctx,r={meta:e,index:e.index,cancelable:!0},s=xN(this,e);this.notifyPlugins("beforeDatasetDraw",r)!==!1&&(s&&Zc(n,s),e.controller.draw(),s&&Qc(n),r.cancelable=!1,this.notifyPlugins("afterDatasetDraw",r))}isPointInArea(e){return h0(e,this.chartArea,this._minPadding)}getElementsAtEventForMode(e,n,r,s){const o=QN.modes[n];return typeof o=="function"?o(this,e,r,s):[]}getDatasetMeta(e){const n=this.data.datasets[e],r=this._metasets;let s=r.filter(o=>o&&o._dataset===n).pop();return s||(s={type:null,data:[],dataset:null,controller:null,hidden:null,xAxisID:null,yAxisID:null,order:n&&n.order||0,index:e,_dataset:n,_parsed:[],_sorted:!1},r.push(s)),s}getContext(){return this.$context||(this.$context=us(null,{chart:this,type:"chart"}))}getVisibleDatasetCount(){return this.getSortedVisibleDatasetMetas().length}isDatasetVisible(e){const n=this.data.datasets[e];if(!n)return!1;const r=this.getDatasetMeta(e);return typeof r.hidden=="boolean"?!r.hidden:!n.hidden}setDatasetVisibility(e,n){const r=this.getDatasetMeta(e);r.hidden=!n}toggleDataVisibility(e){this._hiddenIndices[e]=!this._hiddenIndices[e]}getDataVisibility(e){return!this._hiddenIndices[e]}_updateVisibility(e,n,r){const s=r?"show":"hide",o=this.getDatasetMeta(e),i=o.controller._resolveAnimations(void 0,s);go(n)?(o.data[n].hidden=!r,this.update()):(this.setDatasetVisibility(e,r),i.update(o,{visible:r}),this.update(a=>a.datasetIndex===e?s:void 0))}hide(e,n){this._updateVisibility(e,n,!1)}show(e,n){this._updateVisibility(e,n,!0)}_destroyDatasetMeta(e){const n=this._metasets[e];n&&n.controller&&n.controller._destroy(),delete this._metasets[e]}_stop(){let e,n;for(this.stop(),Xn.remove(this),e=0,n=this.data.datasets.length;e{n.addEventListener(this,o,i),e[o]=i},s=(o,i,a)=>{o.offsetX=i,o.offsetY=a,this._eventHandler(o)};at(this.options.events,o=>r(o,s))}bindResponsiveEvents(){this._responsiveListeners||(this._responsiveListeners={});const e=this._responsiveListeners,n=this.platform,r=(l,c)=>{n.addEventListener(this,l,c),e[l]=c},s=(l,c)=>{e[l]&&(n.removeEventListener(this,l,c),delete e[l])},o=(l,c)=>{this.canvas&&this.resize(l,c)};let i;const a=()=>{s("attach",a),this.attached=!0,this.resize(),r("resize",o),r("detach",i)};i=()=>{this.attached=!1,s("resize",o),this._stop(),this._resize(0,0),r("attach",a)},n.isAttached(this.canvas)?a():i()}unbindEvents(){at(this._listeners,(e,n)=>{this.platform.removeEventListener(this,n,e)}),this._listeners={},at(this._responsiveListeners,(e,n)=>{this.platform.removeEventListener(this,n,e)}),this._responsiveListeners=void 0}updateHoverStyle(e,n,r){const s=r?"set":"remove";let o,i,a,l;for(n==="dataset"&&(o=this.getDatasetMeta(e[0].datasetIndex),o.controller["_"+s+"DatasetHoverStyle"]()),a=0,l=e.length;a{const a=this.getDatasetMeta(o);if(!a)throw new Error("No dataset found at index "+o);return{datasetIndex:o,element:a.data[i],index:i}});!ea(r,n)&&(this._active=r,this._lastEvent=null,this._updateHoverStyles(r,n))}notifyPlugins(e,n,r){return this._plugins.notify(this,e,n,r)}isPluginEnabled(e){return this._plugins._cache.filter(n=>n.plugin.id===e).length===1}_updateHoverStyles(e,n,r){const s=this.options.hover,o=(l,c)=>l.filter(d=>!c.some(u=>d.datasetIndex===u.datasetIndex&&d.index===u.index)),i=o(n,e),a=r?e:o(e,n);i.length&&this.updateHoverStyle(i,s.mode,!1),a.length&&s.mode&&this.updateHoverStyle(a,s.mode,!0)}_eventHandler(e,n){const r={event:e,replay:n,cancelable:!0,inChartArea:this.isPointInArea(e)},s=i=>(i.options.events||this.options.events).includes(e.native.type);if(this.notifyPlugins("beforeEvent",r,s)===!1)return;const o=this._handleEvent(e,n,r.inChartArea);return r.cancelable=!1,this.notifyPlugins("afterEvent",r,s),(o||r.changed)&&this.render(),this}_handleEvent(e,n,r){const{_active:s=[],options:o}=this,i=n,a=this._getActiveElements(e,s,r,i),l=CM(e),c=eT(e,this._lastEvent,r,l);r&&(this._lastEvent=null,gt(o.onHover,[e,a,this],this),l&>(o.onClick,[e,a,this],this));const d=!ea(a,s);return(d||n)&&(this._active=a,this._updateHoverStyles(a,s,n)),this._lastEvent=c,d}_getActiveElements(e,n,r,s){if(e.type==="mouseout")return[];if(!r)return n;const o=this.options.hover;return this.getElementsAtEventForMode(e,o.mode,o,s)}},$e(fr,"defaults",xt),$e(fr,"instances",xa),$e(fr,"overrides",Mr),$e(fr,"registry",Vn),$e(fr,"version",ZO),$e(fr,"getChart",cm),fr);function dm(){return at(Eo.instances,t=>t._plugins.invalidate())}function tT(t,e,n){const{startAngle:r,x:s,y:o,outerRadius:i,innerRadius:a,options:l}=e,{borderWidth:c,borderJoinStyle:d}=l,u=Math.min(c/i,zn(r-n));if(t.beginPath(),t.arc(s,o,i-c/2,r+u/2,n-u/2),a>0){const f=Math.min(c/a,zn(r-n));t.arc(s,o,a+c/2,n-f/2,r+f/2,!0)}else{const f=Math.min(c/2,i*zn(r-n));if(d==="round")t.arc(s,o,f,n-ct/2,r+ct/2,!0);else if(d==="bevel"){const g=2*f*f,m=-g*Math.cos(n+ct/2)+s,p=-g*Math.sin(n+ct/2)+o,y=g*Math.cos(r+ct/2)+s,b=g*Math.sin(r+ct/2)+o;t.lineTo(m,p),t.lineTo(y,b)}}t.closePath(),t.moveTo(0,0),t.rect(0,0,t.canvas.width,t.canvas.height),t.clip("evenodd")}function nT(t,e,n){const{startAngle:r,pixelMargin:s,x:o,y:i,outerRadius:a,innerRadius:l}=e;let c=s/a;t.beginPath(),t.arc(o,i,a,r-c,n+c),l>s?(c=s/l,t.arc(o,i,l,n+c,r-c,!0)):t.arc(o,i,s,n+Mt,r-Mt),t.closePath(),t.clip()}function rT(t){return Jc(t,["outerStart","outerEnd","innerStart","innerEnd"])}function sT(t,e,n,r){const s=rT(t.options.borderRadius),o=(n-e)/2,i=Math.min(o,r*e/2),a=l=>{const c=(n-Math.min(o,l))*r/2;return en(l,0,Math.min(o,c))};return{outerStart:a(s.outerStart),outerEnd:a(s.outerEnd),innerStart:en(s.innerStart,0,i),innerEnd:en(s.innerEnd,0,i)}}function ms(t,e,n,r){return{x:n+t*Math.cos(e),y:r+t*Math.sin(e)}}function wa(t,e,n,r,s,o){const{x:i,y:a,startAngle:l,pixelMargin:c,innerRadius:d}=e,u=Math.max(e.outerRadius+r+n-c,0),f=d>0?d+r+n+c:0;let g=0;const m=s-l;if(r){const A=d>0?d-r:0,D=u>0?u-r:0,O=(A+D)/2,z=O!==0?m*O/(O+r):m;g=(m-z)/2}const p=Math.max(.001,m*u-n/ct)/u,y=(m-p)/2,b=l+y+g,_=s-y-g,{outerStart:x,outerEnd:E,innerStart:C,innerEnd:k}=sT(e,f,u,_-b),N=u-x,S=u-E,w=b+x/N,M=_-E/S,T=f+C,P=f+k,L=b+C/T,$=_-k/P;if(t.beginPath(),o){const A=(w+M)/2;if(t.arc(i,a,u,w,A),t.arc(i,a,u,A,M),E>0){const H=ms(S,M,i,a);t.arc(H.x,H.y,E,M,_+Mt)}const D=ms(P,_,i,a);if(t.lineTo(D.x,D.y),k>0){const H=ms(P,$,i,a);t.arc(H.x,H.y,k,_+Mt,$+Math.PI)}const O=(_-k/f+(b+C/f))/2;if(t.arc(i,a,f,_-k/f,O,!0),t.arc(i,a,f,O,b+C/f,!0),C>0){const H=ms(T,L,i,a);t.arc(H.x,H.y,C,L+Math.PI,b-Mt)}const z=ms(N,b,i,a);if(t.lineTo(z.x,z.y),x>0){const H=ms(N,w,i,a);t.arc(H.x,H.y,x,b-Mt,w)}}else{t.moveTo(i,a);const A=Math.cos(w)*u+i,D=Math.sin(w)*u+a;t.lineTo(A,D);const O=Math.cos(M)*u+i,z=Math.sin(M)*u+a;t.lineTo(O,z)}t.closePath()}function oT(t,e,n,r,s){const{fullCircles:o,startAngle:i,circumference:a}=e;let l=e.endAngle;if(o){wa(t,e,n,r,l,s);for(let c=0;c=ct&&g===0&&d!=="miter"&&tT(t,e,p),o||(wa(t,e,n,r,p,s),t.stroke())}class Mo extends Dr{constructor(n){super();$e(this,"circumference");$e(this,"endAngle");$e(this,"fullCircles");$e(this,"innerRadius");$e(this,"outerRadius");$e(this,"pixelMargin");$e(this,"startAngle");this.options=void 0,this.circumference=void 0,this.startAngle=void 0,this.endAngle=void 0,this.innerRadius=void 0,this.outerRadius=void 0,this.pixelMargin=0,this.fullCircles=0,n&&Object.assign(this,n)}inRange(n,r,s){const o=this.getProps(["x","y"],s),{angle:i,distance:a}=Qp(o,{x:n,y:r}),{startAngle:l,endAngle:c,innerRadius:d,outerRadius:u,circumference:f}=this.getProps(["startAngle","endAngle","innerRadius","outerRadius","circumference"],s),g=(this.options.spacing+this.options.borderWidth)/2,m=Je(f,c-l),p=oa(i,l,c)&&l!==c,y=m>=St||p,b=Er(a,d+g,u+g);return y&&b}getCenterPoint(n){const{x:r,y:s,startAngle:o,endAngle:i,innerRadius:a,outerRadius:l}=this.getProps(["x","y","startAngle","endAngle","innerRadius","outerRadius"],n),{offset:c,spacing:d}=this.options,u=(o+i)/2,f=(a+l+d+c)/2;return{x:r+Math.cos(u)*f,y:s+Math.sin(u)*f}}tooltipPosition(n){return this.getCenterPoint(n)}draw(n){const{options:r,circumference:s}=this,o=(r.offset||0)/4,i=(r.spacing||0)/2,a=r.circular;if(this.pixelMargin=r.borderAlign==="inner"?.33:0,this.fullCircles=s>St?Math.floor(s/St):0,s===0||this.innerRadius<0||this.outerRadius<0)return;n.save();const l=(this.startAngle+this.endAngle)/2;n.translate(Math.cos(l)*o,Math.sin(l)*o);const c=1-Math.sin(Math.min(ct,s||0)),d=o*c;n.fillStyle=r.backgroundColor,n.strokeStyle=r.borderColor,oT(n,this,d,i,a),iT(n,this,d,i,a),n.restore()}}$e(Mo,"id","arc"),$e(Mo,"defaults",{borderAlign:"center",borderColor:"#fff",borderDash:[],borderDashOffset:0,borderJoinStyle:void 0,borderRadius:0,borderWidth:2,offset:0,spacing:0,angle:void 0,circular:!0,selfJoin:!1}),$e(Mo,"defaultRoutes",{backgroundColor:"backgroundColor"}),$e(Mo,"descriptors",{_scriptable:!0,_indexable:n=>n!=="borderDash"});function um(t,e){const{x:n,y:r,base:s,width:o,height:i}=t.getProps(["x","y","base","width","height"],e);let a,l,c,d,u;return t.horizontal?(u=i/2,a=Math.min(n,s),l=Math.max(n,s),c=r-u,d=r+u):(u=o/2,a=n-u,l=n+u,c=Math.min(r,s),d=Math.max(r,s)),{left:a,top:c,right:l,bottom:d}}function ur(t,e,n,r){return t?0:en(e,n,r)}function aT(t,e,n){const r=t.options.borderWidth,s=t.borderSkipped,o=g0(r);return{t:ur(s.top,o.top,0,n),r:ur(s.right,o.right,0,e),b:ur(s.bottom,o.bottom,0,n),l:ur(s.left,o.left,0,e)}}function lT(t,e,n){const{enableBorderRadius:r}=t.getProps(["enableBorderRadius"]),s=t.options.borderRadius,o=ds(s),i=Math.min(e,n),a=t.borderSkipped,l=r||Qe(s);return{topLeft:ur(!l||a.top||a.left,o.topLeft,0,i),topRight:ur(!l||a.top||a.right,o.topRight,0,i),bottomLeft:ur(!l||a.bottom||a.left,o.bottomLeft,0,i),bottomRight:ur(!l||a.bottom||a.right,o.bottomRight,0,i)}}function cT(t){const e=um(t),n=e.right-e.left,r=e.bottom-e.top,s=aT(t,n/2,r/2),o=lT(t,n/2,r/2);return{outer:{x:e.left,y:e.top,w:n,h:r,radius:o},inner:{x:e.left+s.l,y:e.top+s.t,w:n-s.l-s.r,h:r-s.t-s.b,radius:{topLeft:Math.max(0,o.topLeft-Math.max(s.t,s.l)),topRight:Math.max(0,o.topRight-Math.max(s.t,s.r)),bottomLeft:Math.max(0,o.bottomLeft-Math.max(s.b,s.l)),bottomRight:Math.max(0,o.bottomRight-Math.max(s.b,s.r))}}}}function gd(t,e,n,r){const s=e===null,o=n===null,a=t&&!(s&&o)&&um(t,r);return a&&(s||Er(e,a.left,a.right))&&(o||Er(n,a.top,a.bottom))}function dT(t){return t.topLeft||t.topRight||t.bottomLeft||t.bottomRight}function uT(t,e){t.rect(e.x,e.y,e.w,e.h)}function pd(t,e,n={}){const r=t.x!==n.x?-e:0,s=t.y!==n.y?-e:0,o=(t.x+t.w!==n.x+n.w?e:0)-r,i=(t.y+t.h!==n.y+n.h?e:0)-s;return{x:t.x+r,y:t.y+s,w:t.w+o,h:t.h+i,radius:t.radius}}class ka extends Dr{constructor(e){super(),this.options=void 0,this.horizontal=void 0,this.base=void 0,this.width=void 0,this.height=void 0,this.inflateAmount=void 0,e&&Object.assign(this,e)}draw(e){const{inflateAmount:n,options:{borderColor:r,backgroundColor:s}}=this,{inner:o,outer:i}=cT(this),a=dT(i.radius)?aa:uT;e.save(),(i.w!==o.w||i.h!==o.h)&&(e.beginPath(),a(e,pd(i,n,o)),e.clip(),a(e,pd(o,-n,i)),e.fillStyle=r,e.fill("evenodd")),e.beginPath(),a(e,pd(o,n)),e.fillStyle=s,e.fill(),e.restore()}inRange(e,n,r){return gd(this,e,n,r)}inXRange(e,n){return gd(this,e,null,n)}inYRange(e,n){return gd(this,null,e,n)}getCenterPoint(e){const{x:n,y:r,base:s,horizontal:o}=this.getProps(["x","y","base","horizontal"],e);return{x:o?(n+s)/2:n,y:o?r:(r+s)/2}}getRange(e){return e==="x"?this.width/2:this.height/2}}$e(ka,"id","bar"),$e(ka,"defaults",{borderSkipped:"start",borderWidth:0,borderRadius:0,inflateAmount:"auto",pointStyle:void 0}),$e(ka,"defaultRoutes",{backgroundColor:"backgroundColor",borderColor:"borderColor"});const fm=(t,e)=>{let{boxHeight:n=e,boxWidth:r=e}=t;return t.usePointStyle&&(n=Math.min(n,e),r=t.pointStyleWidth||Math.min(r,e)),{boxWidth:r,boxHeight:n,itemHeight:Math.max(e,n)}},fT=(t,e)=>t!==null&&e!==null&&t.datasetIndex===e.datasetIndex&&t.index===e.index;class hm extends Dr{constructor(e){super(),this._added=!1,this.legendHitBoxes=[],this._hoveredItem=null,this.doughnutMode=!1,this.chart=e.chart,this.options=e.options,this.ctx=e.ctx,this.legendItems=void 0,this.columnSizes=void 0,this.lineWidths=void 0,this.maxHeight=void 0,this.maxWidth=void 0,this.top=void 0,this.bottom=void 0,this.left=void 0,this.right=void 0,this.height=void 0,this.width=void 0,this._margins=void 0,this.position=void 0,this.weight=void 0,this.fullSize=void 0}update(e,n,r){this.maxWidth=e,this.maxHeight=n,this._margins=r,this.setDimensions(),this.buildLabels(),this.fit()}setDimensions(){this.isHorizontal()?(this.width=this.maxWidth,this.left=this._margins.left,this.right=this.width):(this.height=this.maxHeight,this.top=this._margins.top,this.bottom=this.height)}buildLabels(){const e=this.options.labels||{};let n=gt(e.generateLabels,[this.chart],this)||[];e.filter&&(n=n.filter(r=>e.filter(r,this.chart.data))),e.sort&&(n=n.sort((r,s)=>e.sort(r,s,this.chart.data))),this.options.reverse&&n.reverse(),this.legendItems=n}fit(){const{options:e,ctx:n}=this;if(!e.display){this.width=this.height=0;return}const r=e.labels,s=Bt(r.font),o=s.size,i=this._computeTitleHeight(),{boxWidth:a,itemHeight:l}=fm(r,o);let c,d;n.font=s.string,this.isHorizontal()?(c=this.maxWidth,d=this._fitRows(i,o,a,l)+10):(d=this.maxHeight,c=this._fitCols(i,s,a,l)+10),this.width=Math.min(c,e.maxWidth||this.maxWidth),this.height=Math.min(d,e.maxHeight||this.maxHeight)}_fitRows(e,n,r,s){const{ctx:o,maxWidth:i,options:{labels:{padding:a}}}=this,l=this.legendHitBoxes=[],c=this.lineWidths=[0],d=s+a;let u=e;o.textAlign="left",o.textBaseline="middle";let f=-1,g=-d;return this.legendItems.forEach((m,p)=>{const y=r+n/2+o.measureText(m.text).width;(p===0||c[c.length-1]+y+2*a>i)&&(u+=d,c[c.length-(p>0?0:1)]=0,g+=d,f++),l[p]={left:0,top:g,row:f,width:y,height:s},c[c.length-1]+=y+a}),u}_fitCols(e,n,r,s){const{ctx:o,maxHeight:i,options:{labels:{padding:a}}}=this,l=this.legendHitBoxes=[],c=this.columnSizes=[],d=i-e;let u=a,f=0,g=0,m=0,p=0;return this.legendItems.forEach((y,b)=>{const{itemWidth:_,itemHeight:x}=hT(r,n,o,y,s);b>0&&g+x+2*a>d&&(u+=f+a,c.push({width:f,height:g}),m+=f+a,p++,f=g=0),l[b]={left:m,top:g,col:p,width:_,height:x},f=Math.max(f,_),g+=x+a}),u+=f,c.push({width:f,height:g}),u}adjustHitBoxes(){if(!this.options.display)return;const e=this._computeTitleHeight(),{legendHitBoxes:n,options:{align:r,labels:{padding:s},rtl:o}}=this,i=hs(o,this.left,this.width);if(this.isHorizontal()){let a=0,l=Ut(r,this.left+s,this.right-this.lineWidths[a]);for(const c of n)a!==c.row&&(a=c.row,l=Ut(r,this.left+s,this.right-this.lineWidths[a])),c.top+=this.top+e+s,c.left=i.leftForLtr(i.x(l),c.width),l+=c.width+s}else{let a=0,l=Ut(r,this.top+e+s,this.bottom-this.columnSizes[a].height);for(const c of n)c.col!==a&&(a=c.col,l=Ut(r,this.top+e+s,this.bottom-this.columnSizes[a].height)),c.top=l,c.left+=this.left+s,c.left=i.leftForLtr(i.x(c.left),c.width),l+=c.height+s}}isHorizontal(){return this.options.position==="top"||this.options.position==="bottom"}draw(){if(this.options.display){const e=this.ctx;Zc(e,this),this._draw(),Qc(e)}}_draw(){const{options:e,columnSizes:n,lineWidths:r,ctx:s}=this,{align:o,labels:i}=e,a=xt.color,l=hs(e.rtl,this.left,this.width),c=Bt(i.font),{padding:d}=i,u=c.size,f=u/2;let g;this.drawTitle(),s.textAlign=l.textAlign("left"),s.textBaseline="middle",s.lineWidth=.5,s.font=c.string;const{boxWidth:m,boxHeight:p,itemHeight:y}=fm(i,u),b=function(k,N,S){if(isNaN(m)||m<=0||isNaN(p)||p<0)return;s.save();const w=Je(S.lineWidth,1);if(s.fillStyle=Je(S.fillStyle,a),s.lineCap=Je(S.lineCap,"butt"),s.lineDashOffset=Je(S.lineDashOffset,0),s.lineJoin=Je(S.lineJoin,"miter"),s.lineWidth=w,s.strokeStyle=Je(S.strokeStyle,a),s.setLineDash(Je(S.lineDash,[])),i.usePointStyle){const M={radius:p*Math.SQRT2/2,pointStyle:S.pointStyle,rotation:S.rotation,borderWidth:w},T=l.xPlus(k,m/2),P=N+f;f0(s,M,T,P,i.pointStyleWidth&&m)}else{const M=N+Math.max((u-p)/2,0),T=l.leftForLtr(k,m),P=ds(S.borderRadius);s.beginPath(),Object.values(P).some(L=>L!==0)?aa(s,{x:T,y:M,w:m,h:p,radius:P}):s.rect(T,M,m,p),s.fill(),w!==0&&s.stroke()}s.restore()},_=function(k,N,S){bo(s,S.text,k,N+y/2,c,{strikethrough:S.hidden,textAlign:l.textAlign(S.textAlign)})},x=this.isHorizontal(),E=this._computeTitleHeight();x?g={x:Ut(o,this.left+d,this.right-r[0]),y:this.top+d+E,line:0}:g={x:this.left+d,y:Ut(o,this.top+E+d,this.bottom-n[0].height),line:0},C0(this.ctx,e.textDirection);const C=y+d;this.legendItems.forEach((k,N)=>{s.strokeStyle=k.fontColor,s.fillStyle=k.fontColor;const S=s.measureText(k.text).width,w=l.textAlign(k.textAlign||(k.textAlign=i.textAlign)),M=m+f+S;let T=g.x,P=g.y;l.setWidth(this.width),x?N>0&&T+M+d>this.right&&(P=g.y+=C,g.line++,T=g.x=Ut(o,this.left+d,this.right-r[g.line])):N>0&&P+C>this.bottom&&(T=g.x=T+n[g.line].width+d,g.line++,P=g.y=Ut(o,this.top+E+d,this.bottom-n[g.line].height));const L=l.x(T);if(b(L,P,k),T=$M(w,T+m+f,x?T+M:this.right,e.rtl),_(l.x(T),P,k),x)g.x+=M+d;else if(typeof k.text!="string"){const $=c.lineHeight;g.y+=gm(k,$)+d}else g.y+=C}),S0(this.ctx,e.textDirection)}drawTitle(){const e=this.options,n=e.title,r=Bt(n.font),s=pn(n.padding);if(!n.display)return;const o=hs(e.rtl,this.left,this.width),i=this.ctx,a=n.position,l=r.size/2,c=s.top+l;let d,u=this.left,f=this.width;if(this.isHorizontal())f=Math.max(...this.lineWidths),d=this.top+c,u=Ut(e.align,u,this.right-f);else{const m=this.columnSizes.reduce((p,y)=>Math.max(p,y.height),0);d=c+Ut(e.align,this.top,this.bottom-m-e.labels.padding-this._computeTitleHeight())}const g=Ut(a,u,u+f);i.textAlign=o.textAlign(Gc(a)),i.textBaseline="middle",i.strokeStyle=n.color,i.fillStyle=n.color,i.font=r.string,bo(i,n.text,g,d,r)}_computeTitleHeight(){const e=this.options.title,n=Bt(e.font),r=pn(e.padding);return e.display?n.lineHeight+r.height:0}_getLegendItemAt(e,n){let r,s,o;if(Er(e,this.left,this.right)&&Er(n,this.top,this.bottom)){for(o=this.legendHitBoxes,r=0;ro.length>i.length?o:i)),e+n.size/2+r.measureText(s).width}function pT(t,e,n){let r=t;return typeof e.text!="string"&&(r=gm(e,n)),r}function gm(t,e){const n=t.text?t.text.length:0;return e*n}function mT(t,e){return!!((t==="mousemove"||t==="mouseout")&&(e.onHover||e.onLeave)||e.onClick&&(t==="click"||t==="mouseup"))}var pm={id:"legend",_element:hm,start(t,e,n){const r=t.legend=new hm({ctx:t.ctx,options:n,chart:t});mn.configure(t,r,n),mn.addBox(t,r)},stop(t){mn.removeBox(t,t.legend),delete t.legend},beforeUpdate(t,e,n){const r=t.legend;mn.configure(t,r,n),r.options=n},afterUpdate(t){const e=t.legend;e.buildLabels(),e.adjustHitBoxes()},afterEvent(t,e){e.replay||t.legend.handleEvent(e.event)},defaults:{display:!0,position:"top",align:"center",fullSize:!0,reverse:!1,weight:1e3,onClick(t,e,n){const r=e.datasetIndex,s=n.chart;s.isDatasetVisible(r)?(s.hide(r),e.hidden=!0):(s.show(r),e.hidden=!1)},onHover:null,onLeave:null,labels:{color:t=>t.chart.options.color,boxWidth:40,padding:10,generateLabels(t){const e=t.data.datasets,{labels:{usePointStyle:n,pointStyle:r,textAlign:s,color:o,useBorderRadius:i,borderRadius:a}}=t.legend.options;return t._getSortedDatasetMetas().map(l=>{const c=l.controller.getStyle(n?0:void 0),d=pn(c.borderWidth);return{text:e[l.index].label,fillStyle:c.backgroundColor,fontColor:o,hidden:!l.visible,lineCap:c.borderCapStyle,lineDash:c.borderDash,lineDashOffset:c.borderDashOffset,lineJoin:c.borderJoinStyle,lineWidth:(d.width+d.height)/4,strokeStyle:c.borderColor,pointStyle:r||c.pointStyle,rotation:c.rotation,textAlign:s||c.textAlign,borderRadius:i&&(a||c.borderRadius),datasetIndex:l.index}},this)}},title:{color:t=>t.chart.options.color,display:!1,position:"center",text:""}},descriptors:{_scriptable:t=>!t.startsWith("on"),labels:{_scriptable:t=>!["generateLabels","filter","sort"].includes(t)}}};class mm extends Dr{constructor(e){super(),this.chart=e.chart,this.options=e.options,this.ctx=e.ctx,this._padding=void 0,this.top=void 0,this.bottom=void 0,this.left=void 0,this.right=void 0,this.width=void 0,this.height=void 0,this.position=void 0,this.weight=void 0,this.fullSize=void 0}update(e,n){const r=this.options;if(this.left=0,this.top=0,!r.display){this.width=this.height=this.right=this.bottom=0;return}this.width=this.right=e,this.height=this.bottom=n;const s=Et(r.text)?r.text.length:1;this._padding=pn(r.padding);const o=s*Bt(r.font).lineHeight+this._padding.height;this.isHorizontal()?this.height=o:this.width=o}isHorizontal(){const e=this.options.position;return e==="top"||e==="bottom"}_drawArgs(e){const{top:n,left:r,bottom:s,right:o,options:i}=this,a=i.align;let l=0,c,d,u;return this.isHorizontal()?(d=Ut(a,r,o),u=n+e,c=o-r):(i.position==="left"?(d=r+e,u=Ut(a,s,n),l=ct*-.5):(d=o-e,u=Ut(a,n,s),l=ct*.5),c=s-n),{titleX:d,titleY:u,maxWidth:c,rotation:l}}draw(){const e=this.ctx,n=this.options;if(!n.display)return;const r=Bt(n.font),o=r.lineHeight/2+this._padding.top,{titleX:i,titleY:a,maxWidth:l,rotation:c}=this._drawArgs(o);bo(e,n.text,0,0,r,{color:n.color,maxWidth:l,rotation:c,textAlign:Gc(n.align),textBaseline:"middle",translation:[i,a]})}}function bT(t,e){const n=new mm({ctx:t.ctx,options:e,chart:t});mn.configure(t,n,e),mn.addBox(t,n),t.titleBlock=n}var yT={id:"title",_element:mm,start(t,e,n){bT(t,n)},stop(t){const e=t.titleBlock;mn.removeBox(t,e),delete t.titleBlock},beforeUpdate(t,e,n){const r=t.titleBlock;mn.configure(t,r,n),r.options=n},defaults:{align:"center",display:!1,font:{weight:"bold"},fullSize:!0,padding:10,position:"top",text:"",weight:2e3},defaultRoutes:{color:"color"},descriptors:{_scriptable:!0,_indexable:!1}};const No={average(t){if(!t.length)return!1;let e,n,r=new Set,s=0,o=0;for(e=0,n=t.length;ea+l)/r.size,y:s/o}},nearest(t,e){if(!t.length)return!1;let n=e.x,r=e.y,s=Number.POSITIVE_INFINITY,o,i,a;for(o=0,i=t.length;o-1?t.split(` +`):t}function vT(t,e){const{element:n,datasetIndex:r,index:s}=e,o=t.getDatasetMeta(r).controller,{label:i,value:a}=o.getLabelAndValue(s);return{chart:t,label:i,parsed:o.getParsed(s),raw:t.data.datasets[r].data[s],formattedValue:a,dataset:o.getDataset(),dataIndex:s,datasetIndex:r,element:n}}function bm(t,e){const n=t.chart.ctx,{body:r,footer:s,title:o}=t,{boxWidth:i,boxHeight:a}=e,l=Bt(e.bodyFont),c=Bt(e.titleFont),d=Bt(e.footerFont),u=o.length,f=s.length,g=r.length,m=pn(e.padding);let p=m.height,y=0,b=r.reduce((E,C)=>E+C.before.length+C.lines.length+C.after.length,0);if(b+=t.beforeBody.length+t.afterBody.length,u&&(p+=u*c.lineHeight+(u-1)*e.titleSpacing+e.titleMarginBottom),b){const E=e.displayColors?Math.max(a,l.lineHeight):l.lineHeight;p+=g*E+(b-g)*l.lineHeight+(b-1)*e.bodySpacing}f&&(p+=e.footerMarginTop+f*d.lineHeight+(f-1)*e.footerSpacing);let _=0;const x=function(E){y=Math.max(y,n.measureText(E).width+_)};return n.save(),n.font=c.string,at(t.title,x),n.font=l.string,at(t.beforeBody.concat(t.afterBody),x),_=e.displayColors?i+2+e.boxPadding:0,at(r,E=>{at(E.before,x),at(E.lines,x),at(E.after,x)}),_=0,n.font=d.string,at(t.footer,x),n.restore(),y+=m.width,{width:y,height:p}}function xT(t,e){const{y:n,height:r}=e;return nt.height-r/2?"bottom":"center"}function wT(t,e,n,r){const{x:s,width:o}=r,i=n.caretSize+n.caretPadding;if(t==="left"&&s+o+i>e.width||t==="right"&&s-o-i<0)return!0}function kT(t,e,n,r){const{x:s,width:o}=n,{width:i,chartArea:{left:a,right:l}}=t;let c="center";return r==="center"?c=s<=(a+l)/2?"left":"right":s<=o/2?c="left":s>=i-o/2&&(c="right"),wT(c,t,e,n)&&(c="center"),c}function ym(t,e,n){const r=n.yAlign||e.yAlign||xT(t,n);return{xAlign:n.xAlign||e.xAlign||kT(t,e,n,r),yAlign:r}}function CT(t,e){let{x:n,width:r}=t;return e==="right"?n-=r:e==="center"&&(n-=r/2),n}function ST(t,e,n){let{y:r,height:s}=t;return e==="top"?r+=n:e==="bottom"?r-=s+n:r-=s/2,r}function vm(t,e,n,r){const{caretSize:s,caretPadding:o,cornerRadius:i}=t,{xAlign:a,yAlign:l}=n,c=s+o,{topLeft:d,topRight:u,bottomLeft:f,bottomRight:g}=ds(i);let m=CT(e,a);const p=ST(e,l,c);return l==="center"?a==="left"?m+=c:a==="right"&&(m-=c):a==="left"?m-=Math.max(d,f)+s:a==="right"&&(m+=Math.max(u,g)+s),{x:en(m,0,r.width-e.width),y:en(p,0,r.height-e.height)}}function Ca(t,e,n){const r=pn(n.padding);return e==="center"?t.x+t.width/2:e==="right"?t.x+t.width-r.right:t.x+r.left}function xm(t){return Hn([],qn(t))}function _T(t,e,n){return us(t,{tooltip:e,tooltipItems:n,type:"tooltip"})}function wm(t,e){const n=e&&e.dataset&&e.dataset.tooltip&&e.dataset.tooltip.callbacks;return n?t.override(n):t}const km={beforeTitle:Yn,title(t){if(t.length>0){const e=t[0],n=e.chart.data.labels,r=n?n.length:0;if(this&&this.options&&this.options.mode==="dataset")return e.dataset.label||"";if(e.label)return e.label;if(r>0&&e.dataIndex"u"?km[e].call(n,r):s}class md extends Dr{constructor(e){super(),this.opacity=0,this._active=[],this._eventPosition=void 0,this._size=void 0,this._cachedAnimations=void 0,this._tooltipItems=[],this.$animations=void 0,this.$context=void 0,this.chart=e.chart,this.options=e.options,this.dataPoints=void 0,this.title=void 0,this.beforeBody=void 0,this.body=void 0,this.afterBody=void 0,this.footer=void 0,this.xAlign=void 0,this.yAlign=void 0,this.x=void 0,this.y=void 0,this.height=void 0,this.width=void 0,this.caretX=void 0,this.caretY=void 0,this.labelColors=void 0,this.labelPointStyles=void 0,this.labelTextColors=void 0}initialize(e){this.options=e,this._cachedAnimations=void 0,this.$context=void 0}_resolveAnimations(){const e=this._cachedAnimations;if(e)return e;const n=this.chart,r=this.options.setContext(this.getContext()),s=r.enabled&&n.options.animation&&r.animations,o=new E0(this.chart,s);return s._cacheable&&(this._cachedAnimations=Object.freeze(o)),o}getContext(){return this.$context||(this.$context=_T(this.chart.getContext(),this,this._tooltipItems))}getTitle(e,n){const{callbacks:r}=n,s=tn(r,"beforeTitle",this,e),o=tn(r,"title",this,e),i=tn(r,"afterTitle",this,e);let a=[];return a=Hn(a,qn(s)),a=Hn(a,qn(o)),a=Hn(a,qn(i)),a}getBeforeBody(e,n){return xm(tn(n.callbacks,"beforeBody",this,e))}getBody(e,n){const{callbacks:r}=n,s=[];return at(e,o=>{const i={before:[],lines:[],after:[]},a=wm(r,o);Hn(i.before,qn(tn(a,"beforeLabel",this,o))),Hn(i.lines,tn(a,"label",this,o)),Hn(i.after,qn(tn(a,"afterLabel",this,o))),s.push(i)}),s}getAfterBody(e,n){return xm(tn(n.callbacks,"afterBody",this,e))}getFooter(e,n){const{callbacks:r}=n,s=tn(r,"beforeFooter",this,e),o=tn(r,"footer",this,e),i=tn(r,"afterFooter",this,e);let a=[];return a=Hn(a,qn(s)),a=Hn(a,qn(o)),a=Hn(a,qn(i)),a}_createItems(e){const n=this._active,r=this.chart.data,s=[],o=[],i=[];let a=[],l,c;for(l=0,c=n.length;le.filter(d,u,f,r))),e.itemSort&&(a=a.sort((d,u)=>e.itemSort(d,u,r))),at(a,d=>{const u=wm(e.callbacks,d);s.push(tn(u,"labelColor",this,d)),o.push(tn(u,"labelPointStyle",this,d)),i.push(tn(u,"labelTextColor",this,d))}),this.labelColors=s,this.labelPointStyles=o,this.labelTextColors=i,this.dataPoints=a,a}update(e,n){const r=this.options.setContext(this.getContext()),s=this._active;let o,i=[];if(!s.length)this.opacity!==0&&(o={opacity:0});else{const a=No[r.position].call(this,s,this._eventPosition);i=this._createItems(r),this.title=this.getTitle(i,r),this.beforeBody=this.getBeforeBody(i,r),this.body=this.getBody(i,r),this.afterBody=this.getAfterBody(i,r),this.footer=this.getFooter(i,r);const l=this._size=bm(this,r),c=Object.assign({},a,l),d=ym(this.chart,r,c),u=vm(r,c,d,this.chart);this.xAlign=d.xAlign,this.yAlign=d.yAlign,o={opacity:1,x:u.x,y:u.y,width:l.width,height:l.height,caretX:a.x,caretY:a.y}}this._tooltipItems=i,this.$context=void 0,o&&this._resolveAnimations().update(this,o),e&&r.external&&r.external.call(this,{chart:this.chart,tooltip:this,replay:n})}drawCaret(e,n,r,s){const o=this.getCaretPosition(e,r,s);n.lineTo(o.x1,o.y1),n.lineTo(o.x2,o.y2),n.lineTo(o.x3,o.y3)}getCaretPosition(e,n,r){const{xAlign:s,yAlign:o}=this,{caretSize:i,cornerRadius:a}=r,{topLeft:l,topRight:c,bottomLeft:d,bottomRight:u}=ds(a),{x:f,y:g}=e,{width:m,height:p}=n;let y,b,_,x,E,C;return o==="center"?(E=g+p/2,s==="left"?(y=f,b=y-i,x=E+i,C=E-i):(y=f+m,b=y+i,x=E-i,C=E+i),_=y):(s==="left"?b=f+Math.max(l,d)+i:s==="right"?b=f+m-Math.max(c,u)-i:b=this.caretX,o==="top"?(x=g,E=x-i,y=b-i,_=b+i):(x=g+p,E=x+i,y=b+i,_=b-i),C=x),{x1:y,x2:b,x3:_,y1:x,y2:E,y3:C}}drawTitle(e,n,r){const s=this.title,o=s.length;let i,a,l;if(o){const c=hs(r.rtl,this.x,this.width);for(e.x=Ca(this,r.titleAlign,r),n.textAlign=c.textAlign(r.titleAlign),n.textBaseline="middle",i=Bt(r.titleFont),a=r.titleSpacing,n.fillStyle=r.titleColor,n.font=i.string,l=0;l_!==0)?(e.beginPath(),e.fillStyle=o.multiKeyBackground,aa(e,{x:p,y:m,w:c,h:l,radius:b}),e.fill(),e.stroke(),e.fillStyle=i.backgroundColor,e.beginPath(),aa(e,{x:y,y:m+1,w:c-2,h:l-2,radius:b}),e.fill()):(e.fillStyle=o.multiKeyBackground,e.fillRect(p,m,c,l),e.strokeRect(p,m,c,l),e.fillStyle=i.backgroundColor,e.fillRect(y,m+1,c-2,l-2))}e.fillStyle=this.labelTextColors[r]}drawBody(e,n,r){const{body:s}=this,{bodySpacing:o,bodyAlign:i,displayColors:a,boxHeight:l,boxWidth:c,boxPadding:d}=r,u=Bt(r.bodyFont);let f=u.lineHeight,g=0;const m=hs(r.rtl,this.x,this.width),p=function(S){n.fillText(S,m.x(e.x+g),e.y+f/2),e.y+=f+o},y=m.textAlign(i);let b,_,x,E,C,k,N;for(n.textAlign=i,n.textBaseline="middle",n.font=u.string,e.x=Ca(this,y,r),n.fillStyle=r.bodyColor,at(this.beforeBody,p),g=a&&y!=="right"?i==="center"?c/2+d:c+2+d:0,E=0,k=s.length;E0&&n.stroke()}_updateAnimationTarget(e){const n=this.chart,r=this.$animations,s=r&&r.x,o=r&&r.y;if(s||o){const i=No[e.position].call(this,this._active,this._eventPosition);if(!i)return;const a=this._size=bm(this,e),l=Object.assign({},i,this._size),c=ym(n,e,l),d=vm(e,l,c,n);(s._to!==d.x||o._to!==d.y)&&(this.xAlign=c.xAlign,this.yAlign=c.yAlign,this.width=a.width,this.height=a.height,this.caretX=i.x,this.caretY=i.y,this._resolveAnimations().update(this,d))}}_willRender(){return!!this.opacity}draw(e){const n=this.options.setContext(this.getContext());let r=this.opacity;if(!r)return;this._updateAnimationTarget(n);const s={width:this.width,height:this.height},o={x:this.x,y:this.y};r=Math.abs(r)<.001?0:r;const i=pn(n.padding),a=this.title.length||this.beforeBody.length||this.body.length||this.afterBody.length||this.footer.length;n.enabled&&a&&(e.save(),e.globalAlpha=r,this.drawBackground(o,e,s,n),C0(e,n.textDirection),o.y+=i.top,this.drawTitle(o,e,n),this.drawBody(o,e,n),this.drawFooter(o,e,n),S0(e,n.textDirection),e.restore())}getActiveElements(){return this._active||[]}setActiveElements(e,n){const r=this._active,s=e.map(({datasetIndex:a,index:l})=>{const c=this.chart.getDatasetMeta(a);if(!c)throw new Error("Cannot find a dataset at index "+a);return{datasetIndex:a,element:c.data[l],index:l}}),o=!ea(r,s),i=this._positionChanged(s,n);(o||i)&&(this._active=s,this._eventPosition=n,this._ignoreReplayEvents=!0,this.update(!0))}handleEvent(e,n,r=!0){if(n&&this._ignoreReplayEvents)return!1;this._ignoreReplayEvents=!1;const s=this.options,o=this._active||[],i=this._getActiveElements(e,o,n,r),a=this._positionChanged(i,e),l=n||!ea(i,o)||a;return l&&(this._active=i,(s.enabled||s.external)&&(this._eventPosition={x:e.x,y:e.y},this.update(!0,n))),l}_getActiveElements(e,n,r,s){const o=this.options;if(e.type==="mouseout")return[];if(!s)return n.filter(a=>this.chart.data.datasets[a.datasetIndex]&&this.chart.getDatasetMeta(a.datasetIndex).controller.getParsed(a.index)!==void 0);const i=this.chart.getElementsAtEventForMode(e,o.mode,o,r);return o.reverse&&i.reverse(),i}_positionChanged(e,n){const{caretX:r,caretY:s,options:o}=this,i=No[o.position].call(this,e,n);return i!==!1&&(r!==i.x||s!==i.y)}}$e(md,"positioners",No);var Cm={id:"tooltip",_element:md,positioners:No,afterInit(t,e,n){n&&(t.tooltip=new md({chart:t,options:n}))},beforeUpdate(t,e,n){t.tooltip&&t.tooltip.initialize(n)},reset(t,e,n){t.tooltip&&t.tooltip.initialize(n)},afterDraw(t){const e=t.tooltip;if(e&&e._willRender()){const n={tooltip:e};if(t.notifyPlugins("beforeTooltipDraw",{...n,cancelable:!0})===!1)return;e.draw(t.ctx),t.notifyPlugins("afterTooltipDraw",n)}},afterEvent(t,e){if(t.tooltip){const n=e.replay;t.tooltip.handleEvent(e.event,n,e.inChartArea)&&(e.changed=!0)}},defaults:{enabled:!0,external:null,position:"average",backgroundColor:"rgba(0,0,0,0.8)",titleColor:"#fff",titleFont:{weight:"bold"},titleSpacing:2,titleMarginBottom:6,titleAlign:"left",bodyColor:"#fff",bodySpacing:2,bodyFont:{},bodyAlign:"left",footerColor:"#fff",footerSpacing:2,footerMarginTop:6,footerFont:{weight:"bold"},footerAlign:"left",padding:6,caretPadding:2,caretSize:5,cornerRadius:6,boxHeight:(t,e)=>e.bodyFont.size,boxWidth:(t,e)=>e.bodyFont.size,multiKeyBackground:"#fff",displayColors:!0,boxPadding:0,borderColor:"rgba(0,0,0,0)",borderWidth:0,animation:{duration:400,easing:"easeOutQuart"},animations:{numbers:{type:"number",properties:["x","y","width","height","caretX","caretY"]},opacity:{easing:"linear",duration:200}},callbacks:km},defaultRoutes:{bodyFont:"font",footerFont:"font",titleFont:"font"},descriptors:{_scriptable:t=>t!=="filter"&&t!=="itemSort"&&t!=="external",_indexable:!1,callbacks:{_scriptable:!1,_indexable:!1},animation:{_fallback:!1},animations:{_fallback:"animation"}},additionalOptionScopes:["interaction"]};const ET=(t,e,n,r)=>(typeof e=="string"?(n=t.push(e)-1,r.unshift({index:n,label:e})):isNaN(e)&&(n=null),n);function MT(t,e,n,r){const s=t.indexOf(e);if(s===-1)return ET(t,e,n,r);const o=t.lastIndexOf(e);return s!==o?n:s}const NT=(t,e)=>t===null?null:en(Math.round(t),0,e);function Sm(t){const e=this.getLabels();return t>=0&&tn.length-1?null:this.getPixelForValue(n[e].value)}getValueForPixel(e){return Math.round(this._startValue+this.getDecimalForPixel(e)*this._valueRange)}getBasePixel(){return this.bottom}}$e(bd,"id","category"),$e(bd,"defaults",{ticks:{callback:Sm}});function OT(t,e){const n=[],{bounds:s,step:o,min:i,max:a,precision:l,count:c,maxTicks:d,maxDigits:u,includeBounds:f}=t,g=o||1,m=d-1,{min:p,max:y}=e,b=!lt(i),_=!lt(a),x=!lt(c),E=(y-p)/(u+1);let C=qp((y-p)/m/g)*g,k,N,S,w;if(C<1e-14&&!b&&!_)return[{value:p},{value:y}];w=Math.ceil(y/C)-Math.floor(p/C),w>m&&(C=qp(w*C/m/g)*g),lt(l)||(k=Math.pow(10,l),C=Math.ceil(C*k)/k),s==="ticks"?(N=Math.floor(p/C)*C,S=Math.ceil(y/C)*C):(N=p,S=y),b&&_&&o&&MM((a-i)/o,C/1e3)?(w=Math.round(Math.min((a-i)/C,d)),C=(a-i)/w,N=i,S=a):x?(N=b?i:N,S=_?a:S,w=c-1,C=(S-N)/w):(w=(S-N)/C,ra(w,Math.round(w),C/1e3)?w=Math.round(w):w=Math.ceil(w));const M=Math.max(Zp(C),Zp(N));k=Math.pow(10,lt(l)?M:l),N=Math.round(N*k)/k,S=Math.round(S*k)/k;let T=0;for(b&&(f&&N!==i?(n.push({value:i}),Na)break;n.push({value:P})}return _&&f&&S!==a?n.length&&ra(n[n.length-1].value,a,_m(a,E,t))?n[n.length-1].value=a:n.push({value:a}):(!_||S===a)&&n.push({value:S}),n}function _m(t,e,{horizontal:n,minRotation:r}){const s=Kn(r),o=(n?Math.sin(s):Math.cos(s))||.001,i=.75*e*(""+t).length;return Math.min(e/o,i)}class TT extends ps{constructor(e){super(e),this.start=void 0,this.end=void 0,this._startValue=void 0,this._endValue=void 0,this._valueRange=0}parse(e,n){return lt(e)||(typeof e=="number"||e instanceof Number)&&!isFinite(+e)?null:+e}handleTickRangeOptions(){const{beginAtZero:e}=this.options,{minDefined:n,maxDefined:r}=this.getUserBounds();let{min:s,max:o}=this;const i=l=>s=n?s:l,a=l=>o=r?o:l;if(e){const l=cr(s),c=cr(o);l<0&&c<0?a(0):l>0&&c>0&&i(0)}if(s===o){let l=o===0?1:Math.abs(o*.05);a(o+l),e||i(s-l)}this.min=s,this.max=o}getTickLimit(){const e=this.options.ticks;let{maxTicksLimit:n,stepSize:r}=e,s;return r?(s=Math.ceil(this.max/r)-Math.floor(this.min/r)+1,s>1e3&&(console.warn(`scales.${this.id}.ticks.stepSize: ${r} would result generating up to ${s} ticks. Limiting to 1000.`),s=1e3)):(s=this.computeTickLimit(),n=n||11),n&&(s=Math.min(n,s)),s}computeTickLimit(){return Number.POSITIVE_INFINITY}buildTicks(){const e=this.options,n=e.ticks;let r=this.getTickLimit();r=Math.max(2,r);const s={maxTicks:r,bounds:e.bounds,min:e.min,max:e.max,precision:n.precision,step:n.stepSize,count:n.count,maxDigits:this._maxDigits(),horizontal:this.isHorizontal(),minRotation:n.minRotation||0,includeBounds:n.includeBounds!==!1},o=this._range||this,i=OT(s,o);return e.bounds==="ticks"&&NM(i,this,"value"),e.reverse?(i.reverse(),this.start=this.max,this.end=this.min):(this.start=this.min,this.end=this.max),i}configure(){const e=this.ticks;let n=this.min,r=this.max;if(super.configure(),this.options.offset&&e.length){const s=(r-n)/Math.max(e.length-1,1)/2;n-=s,r+=s}this._startValue=n,this._endValue=r,this._valueRange=r-n}getLabelForValue(e){return Kc(e,this.chart.options.locale,this.options.ticks.format)}}class yd extends TT{determineDataLimits(){const{min:e,max:n}=this.getMinMax(!0);this.min=gn(e)?e:0,this.max=gn(n)?n:1,this.handleTickRangeOptions()}computeTickLimit(){const e=this.isHorizontal(),n=e?this.width:this.height,r=Kn(this.options.ticks.minRotation),s=(e?Math.sin(r):Math.cos(r))||.001,o=this._resolveTickFontOptions(0);return Math.ceil(n/Math.min(40,o.lineHeight/s))}getPixelForValue(e){return e===null?NaN:this.getPixelForDecimal((e-this._startValue)/this._valueRange)}getValueForPixel(e){return this._startValue+this.getDecimalForPixel(e)*this._valueRange}}$e(yd,"id","linear"),$e(yd,"defaults",{ticks:{callback:l0.formatters.numeric}});const Sa={millisecond:{common:!0,size:1,steps:1e3},second:{common:!0,size:1e3,steps:60},minute:{common:!0,size:6e4,steps:60},hour:{common:!0,size:36e5,steps:24},day:{common:!0,size:864e5,steps:30},week:{common:!1,size:6048e5,steps:4},month:{common:!0,size:2628e6,steps:12},quarter:{common:!1,size:7884e6,steps:4},year:{common:!0,size:3154e7}},nn=Object.keys(Sa);function Em(t,e){return t-e}function Mm(t,e){if(lt(e))return null;const n=t._adapter,{parser:r,round:s,isoWeekday:o}=t._parseOpts;let i=e;return typeof r=="function"&&(i=r(i)),gn(i)||(i=typeof r=="string"?n.parse(i,r):n.parse(i)),i===null?null:(s&&(i=s==="week"&&(sa(o)||o===!0)?n.startOf(i,"isoWeek",o):n.startOf(i,s)),+i)}function Nm(t,e,n,r){const s=nn.length;for(let o=nn.indexOf(t);o=nn.indexOf(n);o--){const i=nn[o];if(Sa[i].common&&t._adapter.diff(s,r,i)>=e-1)return i}return nn[n?nn.indexOf(n):0]}function DT(t){for(let e=nn.indexOf(t)+1,n=nn.length;e=e?n[r]:n[s];t[o]=!0}}function IT(t,e,n,r){const s=t._adapter,o=+s.startOf(e[0].value,r),i=e[e.length-1].value;let a,l;for(a=o;a<=i;a=+s.add(a,1,r))l=n[a],l>=0&&(e[l].major=!0);return e}function Tm(t,e,n){const r=[],s={},o=e.length;let i,a;for(i=0;i+e.value))}initOffsets(e=[]){let n=0,r=0,s,o;this.options.offset&&e.length&&(s=this.getDecimalForValue(e[0]),e.length===1?n=1-s:n=(this.getDecimalForValue(e[1])-s)/2,o=this.getDecimalForValue(e[e.length-1]),e.length===1?r=o:r=(o-this.getDecimalForValue(e[e.length-2]))/2);const i=e.length<3?.5:.25;n=en(n,0,i),r=en(r,0,i),this._offsets={start:n,end:r,factor:1/(n+1+r)}}_generate(){const e=this._adapter,n=this.min,r=this.max,s=this.options,o=s.time,i=o.unit||Nm(o.minUnit,n,r,this._getLabelCapacity(n)),a=Je(s.ticks.stepSize,1),l=i==="week"?o.isoWeekday:!1,c=sa(l)||l===!0,d={};let u=n,f,g;if(c&&(u=+e.startOf(u,"isoWeek",l)),u=+e.startOf(u,c?"day":i),e.diff(r,n,i)>1e5*a)throw new Error(n+" and "+r+" are too far apart with stepSize of "+a+" "+i);const m=s.ticks.source==="data"&&this.getDataTimestamps();for(f=u,g=0;f+p)}getLabelForValue(e){const n=this._adapter,r=this.options.time;return r.tooltipFormat?n.format(e,r.tooltipFormat):n.format(e,r.displayFormats.datetime)}format(e,n){const s=this.options.time.displayFormats,o=this._unit,i=n||s[o];return this._adapter.format(e,i)}_tickFormatFunction(e,n,r,s){const o=this.options,i=o.ticks.callback;if(i)return gt(i,[e,n,r],this);const a=o.time.displayFormats,l=this._unit,c=this._majorUnit,d=l&&a[l],u=c&&a[c],f=r[n],g=c&&u&&f&&f.major;return this._adapter.format(e,s||(g?u:d))}generateTickLabels(e){let n,r,s;for(n=0,r=e.length;n0?a:1}getDataTimestamps(){let e=this._cache.data||[],n,r;if(e.length)return e;const s=this.getMatchingVisibleMetas();if(this._normalized&&s.length)return this._cache.data=s[0].controller.getAllParsedValues(this);for(n=0,r=s.length;n=t[r].pos&&e<=t[s].pos&&({lo:r,hi:s}=Wc(t,"pos",e)),{pos:o,time:a}=t[r],{pos:i,time:l}=t[s]):(e>=t[r].time&&e<=t[s].time&&({lo:r,hi:s}=Wc(t,"time",e)),{time:o,pos:a}=t[r],{time:i,pos:l}=t[s]);const c=i-o;return c?a+(l-a)*(e-o)/c:a}class Am extends _a{constructor(e){super(e),this._table=[],this._minPos=void 0,this._tableRange=void 0}initOffsets(){const e=this._getTimestampsForTable(),n=this._table=this.buildLookupTable(e);this._minPos=Ea(n,this.min),this._tableRange=Ea(n,this.max)-this._minPos,super.initOffsets(e)}buildLookupTable(e){const{min:n,max:r}=this,s=[],o=[];let i,a,l,c,d;for(i=0,a=e.length;i=n&&c<=r&&s.push(c);if(s.length<2)return[{time:n,pos:0},{time:r,pos:1}];for(i=0,a=s.length;is-o)}_getTimestampsForTable(){let e=this._cache.all||[];if(e.length)return e;const n=this.getDataTimestamps(),r=this.getLabelTimestamps();return n.length&&r.length?e=this.normalize(n.concat(r)):e=n.length?n:r,e=this._cache.all=e,e}getDecimalForValue(e){return(Ea(this._table,e)-this._minPos)/this._tableRange}getValueForPixel(e){const n=this._offsets,r=this.getDecimalForPixel(e)/n.factor-n.end;return Ea(this._table,r*this._tableRange+this._minPos,!0)}}$e(Am,"id","timeseries"),$e(Am,"defaults",_a.defaults);const Dm="label";function Im(t,e){typeof t=="function"?t(e):t&&(t.current=e)}function LT(t,e){const n=t.options;n&&e&&Object.assign(n,e)}function Lm(t,e){t.labels=e}function Pm(t,e,n=Dm){const r=[];t.datasets=e.map(s=>{const o=t.datasets.find(i=>i[n]===s[n]);return!o||!s.data||r.includes(o)?{...s}:(r.push(o),Object.assign(o,s),o)})}function PT(t,e=Dm){const n={labels:[],datasets:[]};return Lm(n,t.labels),Pm(n,t.datasets,e),n}function $T(t,e){const{height:n=150,width:r=300,redraw:s=!1,datasetIdKey:o,type:i,data:a,options:l,plugins:c=[],fallbackContent:d,updateMode:u,...f}=t,g=v.useRef(null),m=v.useRef(null),p=()=>{g.current&&(m.current=new Eo(g.current,{type:i,data:PT(a,o),options:l&&{...l},plugins:c}),Im(e,m.current))},y=()=>{Im(e,null),m.current&&(m.current.destroy(),m.current=null)};return v.useEffect(()=>{!s&&m.current&&l&<(m.current,l)},[s,l]),v.useEffect(()=>{!s&&m.current&&Lm(m.current.config.data,a.labels)},[s,a.labels]),v.useEffect(()=>{!s&&m.current&&a.datasets&&Pm(m.current.config.data,a.datasets,o)},[s,a.datasets]),v.useEffect(()=>{m.current&&(s?(y(),setTimeout(p)):m.current.update(u))},[s,l,a.labels,a.datasets,u]),v.useEffect(()=>{m.current&&(y(),setTimeout(p))},[i]),v.useEffect(()=>(p(),()=>y()),[]),h.jsx("canvas",{ref:g,role:"img",height:n,width:r,...f,children:d})}const FT=v.forwardRef($T);function $m(t,e){return Eo.register(e),v.forwardRef((n,r)=>h.jsx(FT,{...n,ref:r,type:t}))}const zT=$m("bar",fa),VT=$m("doughnut",vo);Eo.register(bd,yd,ka,yT,Cm,pm);const HT=({labels:t,datasets:e,height:n=200,showLegend:r=!1,showGrid:s=!1,showYGrid:o=!1,showYAxis:i=!1,className:a="",id:l,editorAttrs:c})=>{const d=v.useMemo(()=>({labels:t??[],datasets:(e??[]).map((m,p)=>({label:m.label,data:m.data,backgroundColor:m.backgroundColor||(p===0?"#7C3AED":"#DDD6FE"),borderRadius:m.borderRadius??4,barThickness:16,maxBarThickness:20,yAxisID:m.yAxisID??"y"}))}),[t,e]),u=v.useMemo(()=>(e??[]).some(m=>m.yAxisID==="y1"),[e]),f=v.useMemo(()=>({responsive:!0,maintainAspectRatio:!1,animation:{duration:750,easing:"easeOutQuart"},animations:{x:{duration:0},y:{from:m=>{if(m.type==="data")return m.chart.scales.y.getPixelForValue(0)}}},transitions:{active:{animation:{duration:200}},resize:{animation:{duration:0}}},plugins:{legend:{display:r,position:"top"},tooltip:{backgroundColor:"#1e293b",titleColor:"#fff",bodyColor:"#fff",padding:12,cornerRadius:8}},scales:{x:{grid:{display:s},border:{display:!1},ticks:{color:"#94a3b8",font:{size:11}}},y:{type:"linear",position:"left",grid:{display:o,color:"#e2e8f0"},border:{display:!1},ticks:{display:i,color:"#94a3b8",font:{size:11},padding:8},beginAtZero:!0,grace:"5%"},...u?{y1:{type:"linear",position:"right",grid:{display:!1},border:{display:!1},ticks:{display:i,color:"#94a3b8",font:{size:11},padding:8},beginAtZero:!0,grace:"5%"}}:{}}}),[r,s,o,i,u]),g=typeof n=="number"?n:/^\d+$/.test(String(n))?`${n}px`:n;return h.jsx("div",{className:`bar-chart-container ${a}`,style:{height:g},id:l,...c,children:h.jsx(zT,{data:d,options:f})})},UT=v.memo(HT,(t,e)=>JSON.stringify(t.labels)===JSON.stringify(e.labels)&&JSON.stringify(t.datasets)===JSON.stringify(e.datasets)&&t.height===e.height&&t.showLegend===e.showLegend&&t.showGrid===e.showGrid&&t.showYGrid===e.showYGrid&&t.showYAxis===e.showYAxis&&t.className===e.className&&t.id===e.id&&t.editorAttrs===e.editorAttrs);Eo.register(Mo,Cm,pm);const BT=({data:t,centerLabel:e,centerValue:n,size:r=200,cutout:s="70%",showLegend:o=!1,className:i="",id:a,editorAttrs:l})=>{const c=v.useMemo(()=>({labels:(t??[]).map(f=>f.name),datasets:[{data:(t??[]).map(f=>f.value),backgroundColor:(t??[]).map(f=>f.color),borderWidth:0,hoverOffset:4}]}),[t]),d=v.useMemo(()=>({responsive:!0,maintainAspectRatio:!1,cutout:s,plugins:{legend:{display:o},tooltip:{backgroundColor:"#1e293b",titleColor:"#fff",bodyColor:"#fff",padding:12,cornerRadius:8,callbacks:{label:f=>` $${f.parsed.toLocaleString()}`}}}}),[s,o]),u=typeof r=="number"?r:/^\d+$/.test(String(r))?`${r}px`:r;return h.jsx("div",{className:`donut-chart-wrapper ${i}`,id:a,...l,children:h.jsxs("div",{className:"relative",style:{width:u,height:u},children:[h.jsx(VT,{data:c,options:d}),(e||n)&&h.jsxs("div",{className:"absolute inset-0 flex flex-col items-center justify-center pointer-events-none",children:[e&&h.jsx("span",{className:"text-xs text-slate-500 dark:text-slate-400",children:e}),n&&h.jsx("span",{className:"text-lg font-bold text-slate-900 dark:text-white",children:n})]})]})})},jT=({id:t,className:e,style:n,children:r,editorAttrs:s})=>h.jsx("div",{id:t,className:e,style:n,...s,children:r}),WT=({children:t,cols:e=1,responsive:n,gap:r,rowGap:s,colGap:o,autoRows:i,autoCols:a,flow:l="row",className:c="",style:d,onClick:u,id:f,editorAttrs:g})=>{const m=["grid"];if(e&&m.push(`grid-cols-${e}`),n&&(n.sm&&m.push(`sm:grid-cols-${n.sm}`),n.md&&m.push(`md:grid-cols-${n.md}`),n.lg&&m.push(`lg:grid-cols-${n.lg}`),n.xl&&m.push(`xl:grid-cols-${n.xl}`),n["2xl"]&&m.push(`2xl:grid-cols-${n["2xl"]}`)),r!==void 0&&r>0&&m.push(`gap-${r}`),s!==void 0&&s>0&&m.push(`gap-y-${s}`),o!==void 0&&o>0&&m.push(`gap-x-${o}`),i){const y={auto:"auto-rows-auto",min:"auto-rows-min",max:"auto-rows-max",fr:"auto-rows-fr"};m.push(y[i])}if(a){const y={auto:"auto-cols-auto",min:"auto-cols-min",max:"auto-cols-max",fr:"auto-cols-fr"};m.push(y[a])}const p={row:"grid-flow-row",col:"grid-flow-col",dense:"grid-flow-dense","row-dense":"grid-flow-row-dense","col-dense":"grid-flow-col-dense"};return m.push(p[l]),c&&m.push(c),h.jsx(I,{className:m.join(" "),style:d,onClick:u,id:f,...g,children:t})},GT=({children:t,direction:e="row",justify:n="start",align:r="stretch",wrap:s="nowrap",gap:o=0,grow:i=!1,shrink:a=!0,className:l="",style:c,onClick:d,id:u,editorAttrs:f})=>{const g=["flex"],m={row:"flex-row","row-reverse":"flex-row-reverse",col:"flex-col","col-reverse":"flex-col-reverse"};g.push(m[e]);const p={wrap:"flex-wrap",nowrap:"flex-nowrap","wrap-reverse":"flex-wrap-reverse"};g.push(p[s]);const y={start:"justify-start",end:"justify-end",center:"justify-center",between:"justify-between",around:"justify-around",evenly:"justify-evenly"};g.push(y[n]);const b={start:"items-start",end:"items-end",center:"items-center",baseline:"items-baseline",stretch:"items-stretch"};return g.push(b[r]),o>0&&g.push(`gap-${o}`),i===!0?g.push("flex-grow"):typeof i=="number"&&i>0&&g.push(`flex-grow-${i}`),a===!1?g.push("shrink-0"):a===!0?g.push("flex-shrink"):typeof a=="number"&&a>0&&g.push(`flex-shrink-${a}`),l&&g.push(l),h.jsx(I,{className:g.join(" "),style:c,onClick:d,id:u,...f,children:t})},YT=({title:t,subtitle:e,children:n,padding:r="none",background:s="none",maxWidth:o,centered:i=!1,border:a=!1,shadow:l="none",rounded:c=!1,className:d="",style:u,onClick:f,id:g,editorAttrs:m})=>{const p=[],y={none:"",sm:"p-2",md:"p-4",lg:"p-6",xl:"p-8"};y[r]&&p.push(y[r]);const b={none:"",white:"bg-white dark:bg-gray-700",gray:"bg-gray-50 dark:bg-gray-800",primary:"bg-blue-50 dark:bg-blue-900/20",secondary:"bg-gray-50 dark:bg-gray-800"};if(s!=="none"&&p.push(b[s]),o){const x={sm:"max-w-sm",md:"max-w-md",lg:"max-w-lg",xl:"max-w-xl","2xl":"max-w-2xl","4xl":"max-w-4xl","6xl":"max-w-6xl","7xl":"max-w-7xl",full:"max-w-full"};p.push(x[o])}i&&p.push("mx-auto"),a&&p.push("border","border-gray-200","dark:border-gray-700");const _={none:"",sm:"shadow-sm",md:"shadow-md",lg:"shadow-lg",xl:"shadow-xl"};return l!=="none"&&p.push(_[l]),c&&p.push("rounded-lg"),d&&p.push(d),h.jsxs(Nd,{className:p.join(" "),style:u,onClick:f,id:g,...m,children:[(t||e)&&h.jsxs("div",{className:"mb-4",children:[t&&h.jsx("h2",{className:"text-2xl font-bold text-gray-900 dark:text-white",children:t}),e&&h.jsx("p",{className:"mt-1 text-sm text-gray-600 dark:text-gray-400",children:e})]}),n]})},KT=({leftWidth:t="250px",rightWidth:e="300px",leftSlot:n,centerSlot:r,rightSlot:s,className:o="",style:i,id:a,editorAttrs:l})=>{const c=`flex flex-row w-full h-full ${o}`.trim(),d={width:t,flexShrink:0},u={flex:1,minWidth:0},f={width:e,flexShrink:0};return h.jsxs(I,{className:c,style:i,id:a,...l,children:[h.jsx(I,{className:"flex flex-col",style:d,children:n}),h.jsx(I,{className:"flex flex-col",style:u,children:r}),h.jsx(I,{className:"flex flex-col",style:f,children:s})]})},XT={language:"json",theme:"vs-dark",options:{automaticLayout:!0,minimap:{enabled:!0},scrollBeyondLastLine:!1,fontSize:14,tabSize:2,formatOnPaste:!0,formatOnType:!0,wordWrap:"on",folding:!0,lineNumbers:"on",renderWhitespace:"selection"}},Fm=[{uri:`${window.location.origin}/schemas/layout.json`,fileMatch:["*layout*.json"],schema:{type:"object",properties:{version:{type:"string"},endpoint:{type:"string"},components:{type:"array"}},required:["version","endpoint","components"]}}],qT={"vs/nls":{availableLanguages:{"*":"ko"}}};function ZT(t){t.languages.json.jsonDefaults.setDiagnosticsOptions({validate:!0,schemas:Fm,enableSchemaRequest:!0,schemaValidation:"error",schemaRequest:"error"})}const QT={identifier:"sirsoft-admin_basic",vendor:"sirsoft",name:{ko:"Admin Basic",en:"Admin Basic"},version:"1.0.4",license:"MIT",description:{ko:"그누보드7 기본 관리자 템플릿",en:"Gnuboard7 default admin template"},type:"admin",locales:["ko","en"],author:{name:"sirsoft",email:"contact@sirsoft.com",url:"https://sirsoft.com"},release_date:"2026-05-15",g7_version:">=7.0.6",dependencies:{modules:{},plugins:{}},assets:{css:["assets/css/main.css","assets/css/ui-system.css"],js:["dist/components.js"],fonts:["assets/fonts/"],images:["assets/images/"]},components:{basic:["a","button","checkbox","div","form","h1","h2","h3","hr","icon","img","input","label","li","nav","p","section","select","span","svg","table","tbody","td","textarea","th","thead","tr","ul"],composite:["action-menu","admin-footer","admin-header","admin-sidebar","alert-dialog","breadcrumb","card","confirm-dialog","data-grid","dialog","dropdown","empty-state","filter-group","icon-button","loading-spinner","modal","page-header","pagination","product-card","search-bar","stat-card","status-badge","tab-navigation","template-card","toast"],layout:["container","flex","grid","section"]},preview:{thumbnail:"preview/thumbnail.png",screenshots:["preview/screenshot1.png","preview/screenshot2.png"]},error_config:{layouts:{401:"errors/401",403:"errors/403",404:"errors/404",500:"errors/500",503:"errors/503",maintenance:"errors/maintenance"}},github_url:"https://github.com/gnuboard/g7-template-sirsoft-admin_basic",github_changelog_url:"https://github.com/gnuboard/g7-template-sirsoft-admin_basic/blob/main/CHANGELOG.md",externals:[{id:"fontawesome",type:"style",url:"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css",preconnect:"https://cdnjs.cloudflare.com"},{id:"pretendard",type:"webfont",url:"https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css",preconnect:"https://cdn.jsdelivr.net",crossorigin:"anonymous"},{id:"flag-icons",type:"style",url:"https://cdn.jsdelivr.net/npm/flag-icons@7.2.3/css/flag-icons.min.css"}]},Zn=window.G7Core?.createLogger?.("Handler:SetTheme")??{log:(...t)=>console.log("[Handler:SetTheme]",...t),warn:(...t)=>console.warn("[Handler:SetTheme]",...t),error:(...t)=>console.error("[Handler:SetTheme]",...t)},vd=["auto","light","dark"],zm="g7_color_scheme",JT=t=>t==="auto"?window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light":t,Ma=t=>{const e=JT(t);document.documentElement.setAttribute("data-theme",e),e==="dark"?document.documentElement.classList.add("dark"):document.documentElement.classList.remove("dark")};function RT(){try{const t=localStorage.getItem(zm),e=t&&vd.includes(t)?t:"auto";Ma(e);try{window.G7Core?.state?.setGlobal?.({theme:e})}catch(n){Zn.warn("Failed to set global theme state:",n)}Zn.log("Initial theme applied:",e)}catch(t){Zn.warn("Failed to load initial theme:",t),Ma("auto")}}async function e5(t,e){const n=t?.target;if(n&&typeof n=="string"&&vd.includes(n)){Ma(n);try{window.G7Core?.state?.setGlobal?.({theme:n})}catch(r){Zn.warn("Failed to set global theme state:",r)}return}RT()}async function t5(t,e){let n;if(t?.target&&typeof t.target=="string"&&!t.target.includes("{{")&&(n=t.target),!n||typeof n!="string"){Zn.warn("Invalid theme:",n);return}if(!vd.includes(n)){Zn.warn("Unsupported theme:",n);return}const r=n;try{localStorage.setItem(zm,r)}catch(s){Zn.error("Failed to save theme to localStorage:",s);return}Ma(r);try{window.G7Core?.state?.setGlobal?.({theme:r})}catch(s){Zn.warn("Failed to set global theme state:",s)}Zn.log("Theme changed to:",r)}const bs=window.G7Core?.createLogger?.("Handler:InitMenu")??{log:(...t)=>console.log("[Handler:InitMenu]",...t),warn:(...t)=>console.warn("[Handler:InitMenu]",...t),error:(...t)=>console.error("[Handler:InitMenu]",...t)};function Vm(t,e){for(const n of t){if(n.slug===e)return n;if(n.children&&n.children.length>0){const r=Vm(n.children,e);if(r)return r}}return null}function Hm(t){return new URLSearchParams(window.location.search).get(t)}function n5(t){return window.G7Core?.state?.getDataSource?.(t)}function r5(t){const e=[t,t?.data,t?.data?.data];for(const n of e)if(Array.isArray(n)&&n.length>0)return n;return null}async function s5(t,e=30,n=100){for(let r=0;rsetTimeout(o,n))}return null}async function o5(t){const e=window.G7Core;try{const r=(await e?.api?.get?.(`/api/admin/menus/${t.id}`))?.data;if(r&&typeof r=="object"&&r.id===t.id)return r}catch(n){bs.warn("[initMenuFromUrl] Failed to hydrate menu detail:",n)}return t}async function i5(t,e){const n=window.G7Core,r=Hm("menu"),s=Hm("mode");if(!r){bs.log("[initMenuFromUrl] No menu parameter in URL");return}if(!n?.state?.set){bs.warn("[initMenuFromUrl] G7Core.state.set not available");return}const o=await s5("menus");if(!o){bs.warn("[initMenuFromUrl] menus data not available after waiting");return}const i=Vm(o,r);if(!i){bs.warn("[initMenuFromUrl] Menu not found with slug:",r);return}const a=await o5(i),l=s==="edit"?"edit":"view";n.state.set({selectedMenuId:a.id,selectedMenu:a,panelMode:l}),l==="edit"&&n.state.set({formData:{name:a.name,slug:a.slug,url:a.url,icon:a.icon,parent_id:a.parent_id,extension_type:a.extension_type,extension_identifier:a.extension_identifier,is_active:a.is_active,roles:(a.roles??[]).map(c=>c.id)}}),bs.log("[initMenuFromUrl] Menu initialized from URL:",{slug:r,mode:l,menuId:i.id})}const qt=window.G7Core?.createLogger?.("Handler:FilterVisibility")??{log:(...t)=>console.log("[Handler:FilterVisibility]",...t),warn:(...t)=>console.warn("[Handler:FilterVisibility]",...t),error:(...t)=>console.error("[Handler:FilterVisibility]",...t)},Na="g7_filter_visibility_";async function a5(t,e){const{storageKey:n,defaultFilters:r=[],stateKey:s="visibleFilters"}=t?.params||{};if(!n){qt.warn("[initFilterVisibility] storageKey is required");return}const o=`${Na}${n}`;try{const i=localStorage.getItem(o);let a;if(i){const l=JSON.parse(i);Array.isArray(l)?(a=l,qt.log(`[initFilterVisibility] Loaded from localStorage: ${a.join(", ")}`)):(a=r,qt.warn("[initFilterVisibility] Invalid saved data format, using defaults"))}else a=r,qt.log(`[initFilterVisibility] No saved data, using defaults: ${r.join(", ")}`);e?.setState&&e.setState({[s]:a})}catch(i){qt.error("[initFilterVisibility] Failed to load filter visibility:",i),e?.setState&&e.setState({[s]:r})}}async function l5(t,e){const{storageKey:n,filters:r}=t?.params||{};if(!n){qt.warn("[saveFilterVisibility] storageKey is required");return}if(!Array.isArray(r)){qt.warn("[saveFilterVisibility] filters must be an array");return}const s=`${Na}${n}`;try{localStorage.setItem(s,JSON.stringify(r)),qt.log(`[saveFilterVisibility] Saved to localStorage: ${r.join(", ")}`)}catch(o){qt.error("[saveFilterVisibility] Failed to save filter visibility:",o)}}async function c5(t,e){const{storageKey:n,filterId:r,stateKey:s="visibleFilters"}=t?.params||{};if(!n||!r){qt.warn("[toggleFilterVisibility] storageKey and filterId are required");return}const o=e?.state?.[s]||[];let i;o.includes(r)?i=o.filter(l=>l!==r):i=[...o,r],e?.setState&&e.setState({[s]:i});const a=`${Na}${n}`;try{localStorage.setItem(a,JSON.stringify(i)),qt.log(`[toggleFilterVisibility] Toggled ${r}: ${i.join(", ")}`)}catch(l){qt.error("[toggleFilterVisibility] Failed to save:",l)}}async function d5(t,e){const{storageKey:n,defaultFilters:r=[],stateKey:s="visibleFilters"}=t?.params||{};if(!n){qt.warn("[resetFilterVisibility] storageKey is required");return}const o=`${Na}${n}`;try{localStorage.setItem(o,JSON.stringify(r)),e?.setState&&e.setState({[s]:r}),qt.log(`[resetFilterVisibility] Reset to defaults: ${r.join(", ")}`)}catch(i){qt.error("[resetFilterVisibility] Failed to reset:",i)}}const ys=window.G7Core?.createLogger?.("Handler:MultilingualTag")??{log:(...t)=>console.log("[Handler:MultilingualTag]",...t),warn:(...t)=>console.warn("[Handler:MultilingualTag]",...t),error:(...t)=>console.error("[Handler:MultilingualTag]",...t)};async function u5(t,e){const n=window.G7Core;if(!n){ys.error("G7Core not available");return}const r=n.state?.getGlobal?.()?.multilingualTagEdit;if(!r){ys.warn("No multilingualTagEdit state found"),n.modal?.close?.();return}const{fieldName:s,editingIndex:o,values:i,statePath:a,currentTags:l}=r;if(!Object.values(i||{}).some(g=>typeof g=="string"&&g.trim()!=="")){ys.warn("No values to save"),n.modal?.close?.(),xd(n);return}const d=Array.isArray(l)?l:[];let u;o==null?u=[...d,i]:u=d.map((g,m)=>m===o?i:g);const f=a||(s?.startsWith("form.")?s:`form.${s}`);n.state?.setParentLocal?.({[f]:u}),n.modal?.close?.(),xd(n)}async function f5(t,e){const n=window.G7Core;if(!n){ys.error("G7Core not available");return}n.modal?.close?.(),xd(n)}async function h5(t,e){const n=window.G7Core;if(!n){ys.error("G7Core not available");return}const r=t?.params?.locale;if(!r){ys.warn("No locale specified");return}const s=e?.event;let o="";s?.target?.value!==void 0?o=s.target.value:s?.value!==void 0&&(o=s.value);const a=(n.state?.getGlobal?.()||{}).multilingualTagEdit||{},l={...a.values||{},[r]:o};n.dispatch?.({handler:"setState",params:{target:"global",multilingualTagEdit:{...a,values:l}}})}function xd(t){t.dispatch?.({handler:"setState",params:{target:"global",multilingualTagEdit:null}})}function Um(t){const e=t.getFullYear(),n=String(t.getMonth()+1).padStart(2,"0"),r=String(t.getDate()).padStart(2,"0"),s=String(t.getHours()).padStart(2,"0"),o=String(t.getMinutes()).padStart(2,"0"),i=String(t.getSeconds()).padStart(2,"0");return`${e}-${n}-${r}T${s}:${o}:${i}`}function g5(t){const e=new Date,n=new Date(e.getFullYear(),e.getMonth(),e.getDate(),23,59,59),r=Um(n),s=new Date(e.getFullYear(),e.getMonth(),e.getDate(),0,0,0);switch(t){case"today":break;case"week":s.setDate(s.getDate()-6);break;case"month":s.setMonth(s.getMonth()-1);break;case"3months":s.setMonth(s.getMonth()-3);break;case"6months":s.setMonth(s.getMonth()-6);break;case"1year":s.setFullYear(s.getFullYear()-1);break}return{startDate:Um(s),endDate:r}}function p5(t){const{preset:e}=t.params||{preset:"today"},{startDate:n,endDate:r}=g5(e);return{startDate:n,endDate:r,preset:e}}const Oo=window.G7Core?.createLogger?.("Handler:Sidebar")??{log:(...t)=>console.log("[Handler:Sidebar]",...t),warn:(...t)=>console.warn("[Handler:Sidebar]",...t),error:(...t)=>console.error("[Handler:Sidebar]",...t)},Bm="g7_admin_sidebar_collapsed";function jm(){try{return localStorage.getItem(Bm)==="1"}catch{return!1}}function m5(t){try{localStorage.setItem(Bm,t?"1":"0")}catch(e){Oo.error("Failed to save sidebar state to localStorage:",e)}try{window.G7Core?.state?.setGlobal?.({sidebarCollapsed:t})}catch(e){Oo.warn("Failed to set global sidebar state:",e)}}function Wm(){const t=jm();try{window.G7Core?.state?.setGlobal?.({sidebarCollapsed:t})}catch(e){Oo.warn("Failed to restore sidebar state:",e)}Oo.log("Initial sidebar collapsed state restored:",t)}async function b5(t,e){Wm()}async function y5(t,e){const r=!(()=>{try{return!!window.G7Core?.state?.getGlobal?.()?.sidebarCollapsed}catch{return jm()}})();m5(r),Oo.log("Sidebar collapsed toggled to:",r)}const wd=window.G7Core?.createLogger?.("Handler:DownloadAttachment")??{log:(...t)=>console.log("[Handler:DownloadAttachment]",...t),warn:(...t)=>console.warn("[Handler:DownloadAttachment]",...t),error:(...t)=>console.error("[Handler:DownloadAttachment]",...t)};async function v5(t,e){const n=window.G7Core,{url:r,filename:s}=t?.params||{};if(!r){wd.warn("다운로드 URL 이 없어 요청을 건너뜁니다.");return}if(!n?.api?.get){wd.error("G7Core.api.get 이 초기화되지 않아 다운로드를 실행할 수 없습니다.");return}try{const o=await n.api.get(r,{responseType:"blob"});if(o){const i=URL.createObjectURL(o),a=document.createElement("a");a.href=i,a.download=s||"",document.body.appendChild(a),a.click(),document.body.removeChild(a),URL.revokeObjectURL(i)}}catch(o){wd.error("첨부파일 다운로드 실패",o),n?.toast?.error?.(n?.t?.("common.download_failed")??"Download failed")}}const Oa=window.G7Core?.createLogger?.("Handler:DetectAssetUrlMode")??{log:(...t)=>console.log("[Handler:DetectAssetUrlMode]",...t),warn:(...t)=>console.warn("[Handler:DetectAssetUrlMode]",...t),error:(...t)=>console.error("[Handler:DetectAssetUrlMode]",...t)},x5="G7_ASSET_PROBE_OK";async function Gm(t){try{const e=await fetch(t,{cache:"no-store",credentials:"omit"});if(!e.ok)return!1;const n=e.headers.get("content-type")??"";return/javascript|ecmascript/i.test(n)?(await e.text()).includes(x5):!1}catch{return!1}}async function Ym(){const[t,e]=await Promise.all([Gm("/api/system/asset-probe.js"),Gm("/api/system/asset-probe")]);return t&&e?"extension":!t&&e?"extensionless":"unavailable"}function w5(){return window.G7Config?.settings?.general?.asset_url_mode??"extension"}async function k5(t){try{const e=await Ym();if(e==="unavailable")return;const n=w5();if(e===n)return;window.G7Core?.state?.setGlobal?.({assetUrlModeDrift:{detected:e,stored:n}})}catch(e){Oa.warn("자산 URL 방식 대조 실패 (대시보드 동작에는 영향 없음):",e)}}async function C5(t,e){const n=window.G7Core,r=e?.setState??n?.state?.setLocal,s=o=>{typeof r=="function"?r({asset_url_mode_detect_status:o}):Oa.warn("setState 를 찾지 못해 감지 상태를 폼에 반영하지 못했습니다")};try{s("detecting");const o=await Ym();if(o==="unavailable"){s("unavailable");return}typeof r=="function"?r({"general.asset_url_mode":o,asset_url_mode_detect_status:o}):Oa.warn("setState 를 찾지 못해 감지 결과를 폼에 반영하지 못했습니다")}catch(o){Oa.error("자산 URL 방식 감지 실패:",o),s("unavailable")}}const Ta={detectAssetUrlMode:C5,checkAssetUrlModeDrift:k5,setTheme:t5,initTheme:e5,scrollToSection:eu,initMenuFromUrl:i5,initFilterVisibility:a5,saveFilterVisibility:l5,toggleFilterVisibility:c5,resetFilterVisibility:d5,saveMultilingualTag:u5,cancelMultilingualTag:f5,updateMultilingualTagValue:h5,setDateRange:p5,toggleSidebar:y5,initSidebar:b5,downloadAttachment:v5},To=window.G7Core?.createLogger?.("Template:sirsoft-admin_basic:IdentityLauncher")??{log:(...t)=>console.log("[Template:sirsoft-admin_basic:IdentityLauncher]",...t),warn:(...t)=>console.warn("[Template:sirsoft-admin_basic:IdentityLauncher]",...t),error:(...t)=>console.error("[Template:sirsoft-admin_basic:IdentityLauncher]",...t)};function S5(){const e=window.G7Core?.api?.getToken?.()??null;return e?{Authorization:`Bearer ${e}`}:{}}function _5(){try{const t=typeof window<"u"?window.localStorage?.getItem("g7_locale"):null;return t?{"Accept-Language":t}:{}}catch{return{}}}async function E5(t,e=null){const n={purpose:t.purpose};t.provider_id&&(n.provider_id=t.provider_id),e&&(n.target=e);const r=t.challenge_start_url||"/api/identity/challenges",s=await fetch(r,{method:"POST",credentials:"include",headers:{"Content-Type":"application/json",Accept:"application/json",..._5(),...S5()},body:JSON.stringify(n)}),o=await s.json().catch(()=>null);if(!s.ok||!o?.success){const a=o?.message??`Challenge 시작 실패 (HTTP ${s.status})`;throw new Error(a)}const i=o.data??o;return{id:i.id,expires_at:i.expires_at,render_hint:i.render_hint??t.render_hint??"text_code",public_payload:i.public_payload,redirect_url:i.redirect_url,max_attempts:typeof i.max_attempts=="number"?i.max_attempts:void 0}}async function M5(t){const e=window.G7Core,n=e?.identity;if(!e?.dispatch||!e?.state||!n)return To.error("G7Core 또는 G7Core.identity 가 초기화되지 않아 launcher 를 실행할 수 없습니다."),{status:"failed",failureCode:"G7_NOT_READY"};if(t.render_hint==="external_redirect"||t.redirect_url)return n.redirectExternally(t);const r=t.target&&(t.target.email||t.target.phone)?t.target:null;let s;try{s=await E5(t,r)}catch(m){To.error("Challenge 시작 실패:",m);try{await e.dispatch({handler:"toast",params:{type:"error",message:m instanceof Error?m.message:"본인인증 시작에 실패했습니다."}})}catch{}return{status:"failed",failureCode:"CHALLENGE_START_FAILED",reason:m instanceof Error?m.message:String(m)}}if(s.redirect_url)return n.redirectExternally({...t,render_hint:"external_redirect",redirect_url:s.redirect_url});const o=s.expires_at?new Date(s.expires_at).getTime():0,i=o>0?Math.max(0,Math.floor((o-Date.now())/1e3)):0,a=typeof s.max_attempts=="number"?s.max_attempts:0;e.state.set({identityChallenge:{policy_key:t.policy_key,purpose:t.purpose,provider_id:t.provider_id??null,render_hint:s.render_hint,challenge_id:s.id,expires_at:s.expires_at,public_payload:s.public_payload??{},target:r,code:"",error:null,attempts:0,maxAttempts:a,remainingSeconds:i,resendCooldown:0}});let l=o,c=0;const d=window.setInterval(()=>{const m=l>0?Math.max(0,Math.floor((l-Date.now())/1e3)):0;c=Math.max(0,c-1),e.state.set({identityChallenge:{remainingSeconds:m,resendCooldown:c}})},1e3);let u=null;try{u=e.state.subscribe?.(m=>{const p=m?.identityChallenge?.expires_at;if(typeof p=="string"){const b=new Date(p).getTime();Number.isFinite(b)&&b!==l&&(l=b)}const y=m?.identityChallenge?.resendCooldown;typeof y=="number"&&y>c&&(c=y)})??null}catch{}const f=n.createDeferred();try{await e.dispatch({handler:"openModal",target:"identity-challenge-modal"})}catch(m){return To.error("openModal 실패 — 풀페이지로 폴백:",m),window.clearInterval(d),u?.(),n.redirectExternally({...t,render_hint:"external_redirect",redirect_url:`/admin/identity/challenge?challenge_id=${encodeURIComponent(s.id)}&return=${encodeURIComponent(window.location.href)}`})}const g=await f;return window.clearInterval(d),u?.(),g}function N5(){const t=window.G7Core?.identity;if(!t?.setLauncher){To.warn("G7Core.identity 가 아직 초기화되지 않아 launcher 등록을 건너뜁니다. 코어 부트스트랩 순서를 확인하세요.");return}t.setLauncher(M5),To.log("IDV launcher registered (sirsoft-admin_basic)")}function O5(t){const e=t.icons;return Array.isArray(e)?e.filter(n=>!!n&&typeof n=="object"&&typeof n.value=="string"):[]}function T5(t,e){const n=t.label;return typeof n=="string"?n.startsWith("$t:")?e(n.slice(3)):n:t.value}function A5(t,e,n){if(n==="")return!0;const r=n.toLowerCase();return t.value.toLowerCase().includes(r)||e.toLowerCase().includes(r)?!0:(t.keywords??[]).some(s=>s.toLowerCase().includes(r))}function D5({entry:t}){const e=t.preview;return e?.html?h.jsx("span",{className:"g7le-icon-preview",dangerouslySetInnerHTML:{__html:e.html},style:Cd}):e?.className?h.jsx("span",{className:`g7le-icon-preview ${e.className}`,style:Cd}):h.jsx("i",{className:`fas fa-${t.value}`,style:Cd,"aria-hidden":"true"})}function I5({control:t,value:e,onChange:n,t:r}){const s=O5(t),o=e==null?"":String(e),i=typeof t.iconColumns=="number"?t.iconColumns:6,a=t.iconSearchPlaceholder,l=typeof a=="string"&&a.startsWith("$t:")?r(a.slice(3)):r("layout_editor.icon_picker.search_placeholder"),[c,d]=v.useState("");if(s.length===0)return h.jsx("div",{className:"g7le-widget g7le-widget--icon-picker","data-testid":"g7le-widget-icon-picker",style:Km,children:h.jsx("input",{type:"text",className:"g7le-widget g7le-widget--icon-free","data-testid":"g7le-widget-icon-picker-free",value:o,placeholder:r("layout_editor.icon_picker.free_input_hint"),onChange:g=>n(g.target.value===""?void 0:g.target.value),style:Xm})});const u=g=>T5(g,r),f=s.filter(g=>A5(g,u(g),c));return h.jsxs("div",{className:"g7le-widget g7le-widget--icon-picker","data-testid":"g7le-widget-icon-picker",style:Km,children:[h.jsx("input",{type:"text",className:"g7le-widget g7le-widget--icon-search","data-testid":"g7le-widget-icon-picker-search",value:c,placeholder:l,onChange:g=>d(g.target.value),style:Xm}),h.jsxs("div",{className:"g7le-icon-grid","data-testid":"g7le-widget-icon-picker-grid",style:{...L5,gridTemplateColumns:`repeat(${i}, minmax(0, 1fr))`},children:[h.jsx("button",{type:"button","data-testid":"g7le-icon-cell-none",title:r("layout_editor.icon_picker.none"),onClick:()=>n(void 0),"aria-pressed":o==="",style:o===""?qm:kd,children:h.jsx("span",{style:P5,children:"—"})}),f.map(g=>{const m=g.value===o;return h.jsx("button",{type:"button","data-testid":`g7le-icon-cell-${g.value}`,title:u(g),"aria-pressed":m,onClick:()=>n(g.value),style:m?qm:kd,children:h.jsx(D5,{entry:g})},g.value)})]}),f.length===0&&h.jsx("div",{"data-testid":"g7le-widget-icon-picker-empty",style:$5,children:r("layout_editor.icon_picker.no_results")})]})}const Km={display:"flex",flexDirection:"column",gap:6,width:"100%"},Xm={padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,width:"100%"},L5={display:"grid",gap:4,maxHeight:220,overflowY:"auto",padding:2},kd={display:"flex",alignItems:"center",justifyContent:"center",minHeight:34,padding:4,border:"1px solid #e2e8f0",borderRadius:6,background:"#fff",cursor:"pointer"},qm={...kd,border:"2px solid #2563eb",background:"#eff6ff"},Cd={display:"inline-flex",alignItems:"center",justifyContent:"center",fontSize:16,lineHeight:1},P5={fontSize:10,color:"#475569",fontFamily:"monospace",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap",maxWidth:"100%"},$5={fontSize:11,color:"#94a3b8",fontStyle:"italic",padding:"4px 2px"},Zm=130,Mn=20;function F5(t){const e=t?.arrayProp;return typeof e=="string"&&e?e:"tabs"}function z5(t){let e=0;for(const n of t){const r=typeof n.id=="number"?n.id:Number(n.id);Number.isFinite(r)&&r>e&&(e=r)}return e+1}function V5({node:t,params:e,cellBoxes:n,t:r,onPatchNode:s}){const o=F5(e),i=v.useMemo(()=>{const g=(t.props??{})[o];return typeof g=="string"||!Array.isArray(g)?null:g},[t,o]),a=v.useMemo(()=>{const g=new Map,m=`props.${o}.`;for(const p of n??[]){if(!p.path.startsWith(m))continue;const y=Number(p.path.slice(m.length));Number.isInteger(y)&&g.set(y,{top:p.top,left:p.left,width:p.width,height:p.height})}return g},[n,o]),l=v.useCallback(g=>{const m=t.props??{};s({...t,props:{...m,[o]:g}})},[t,o,s]),c=v.useCallback(g=>{if(!i)return;const p={id:z5(i),label:r("editor.tabnav_inplace.new_tab")},y=i.slice();y.splice(g,0,p),l(y)},[i,l,r]),d=v.useCallback(g=>{if(!i)return;const m=i.slice();m.splice(g,1),l(m)},[i,l]),u=v.useCallback((g,m)=>{if(!i)return;const p=g+m;if(p<0||p>=i.length)return;const y=i.slice(),[b]=y.splice(g,1);y.splice(p,0,b),l(y)},[i,l]);if(!i||a.size===0)return null;const f={position:"absolute",width:Mn,height:Mn,display:"flex",alignItems:"center",justifyContent:"center",fontSize:12,lineHeight:1,border:"1px solid rgba(0,0,0,0.15)",borderRadius:4,background:"#ffffff",color:"#1f2937",cursor:"pointer",boxShadow:"0 1px 2px rgba(0,0,0,0.18)",pointerEvents:"auto",padding:0,zIndex:Zm};return h.jsx("div",{className:"g7le-tabnav-inplace","data-testid":"g7le-tabnav-inplace",style:{position:"absolute",inset:0,pointerEvents:"none",zIndex:Zm},children:i.map((g,m)=>{const p=a.get(m);if(!p)return null;const y=p.left+p.width;return h.jsxs("div",{"data-testid":`g7le-tabnav-aff-${m}`,children:[h.jsx("button",{type:"button","data-testid":`g7le-tabnav-remove-${m}`,title:r("editor.tabnav_inplace.remove"),"aria-label":r("editor.tabnav_inplace.remove"),style:{...f,left:y-Mn/2,top:p.top-Mn/2,color:"#b91c1c"},onClick:b=>{b.preventDefault(),b.stopPropagation(),d(m)},children:"✕"}),m>0&&h.jsx("button",{type:"button","data-testid":`g7le-tabnav-move-left-${m}`,title:r("editor.tabnav_inplace.move_left"),"aria-label":r("editor.tabnav_inplace.move_left"),style:{...f,left:p.left,top:p.top+p.height-Mn/2},onClick:b=>{b.preventDefault(),b.stopPropagation(),u(m,-1)},children:"◀"}),m{b.preventDefault(),b.stopPropagation(),u(m,1)},children:"▶"}),m===0&&h.jsx("button",{type:"button","data-testid":"g7le-tabnav-add-0",title:r("editor.tabnav_inplace.add"),"aria-label":r("editor.tabnav_inplace.add"),style:{...f,left:p.left-Mn,top:p.top+p.height/2-Mn/2,color:"#15803d"},onClick:b=>{b.preventDefault(),b.stopPropagation(),c(0)},children:"+"}),h.jsx("button",{type:"button","data-testid":`g7le-tabnav-add-${m+1}`,title:r("editor.tabnav_inplace.add"),"aria-label":r("editor.tabnav_inplace.add"),style:{...f,left:y-Mn/2,top:p.top+p.height/2-Mn/2,color:"#15803d"},onClick:b=>{b.preventDefault(),b.stopPropagation(),c(m+1)},children:"+"})]},`tab-aff-${g.id}-${m}`)})})}const Sd=window.G7Core?.createLogger?.("Template:sirsoft-admin_basic:EditorWidgets")??{log:(...t)=>console.log("[Template:sirsoft-admin_basic:EditorWidgets]",...t),warn:(...t)=>console.warn("[Template:sirsoft-admin_basic:EditorWidgets]",...t)};function H5(){if(typeof window>"u")return;const t=window.G7Core?.layoutEditor;if(!t?.registerWidget){Sd.warn("G7Core.layoutEditor 미노출 — icon-picker 위젯 등록 보류");return}t.registerWidget("icon-picker",I5),Sd.log("편집기 위젯 'icon-picker' 등록됨(Font Awesome 검색 그리드)"),t.registerCanvasOverlay&&(t.registerCanvasOverlay("tabnav",V5),Sd.log("편집기 캔버스 오버레이 'tabnav' 등록됨(TabNavigation 탭 인플레이스)"))}const _d=window.G7Core?.createLogger?.("Template:sirsoft-admin_basic")??{log:(...t)=>console.log("[Template:sirsoft-admin_basic]",...t),warn:(...t)=>console.warn("[Template:sirsoft-admin_basic]",...t),error:(...t)=>console.error("[Template:sirsoft-admin_basic]",...t)};typeof window<"u"&&(window.G7TemplateHandlers=Ta);function Qm(){if(typeof window<"u"){let t=0;const e=50,n=()=>{const r=window.G7Core?.getActionDispatcher?.();r?(Object.entries(Ta).forEach(([s,o])=>{r.registerHandler(s,o)}),_d.log(`${Object.keys(Ta).length} custom handler(s) registered:`,Object.keys(Ta)),N5(),Wm()):(t++,t<=e?(_d.warn(`ActionDispatcher not found, retrying... (${t}/${e})`),setTimeout(n,100)):_d.error("Failed to register handlers: ActionDispatcher not available after maximum retries"))};document.readyState==="complete"?n():window.addEventListener("load",n)}}return H5(),Qm(),R.A=Lr,R.Accordion=rc,R.ActionMenu=xs,R.AdminFooter=My,R.AdminHeader=Cy,R.AdminSidebar=ky,R.Alert=cu,R.AlertDialog=hy,R.Badge=Ld,R.BarChart=UT,R.Breadcrumb=ou,R.Button=oe,R.Card=pb,R.CardGrid=Zb,R.CategoryTree=GS,R.Checkbox=vn,R.ChipCheckbox=AE,R.Code=ub,R.CodeEditor=jv,R.ColumnSelector=Zv,R.ConfirmDialog=ru,R.Container=jT,R.DataGrid=Wb,R.Dialog=nu,R.Div=I,R.DonutChart=BT,R.Dropdown=Jb,R.DropdownButton=DE,R.DropdownMenuItem=IE,R.DynamicFieldList=jE,R.EmptyState=vy,R.ExtensionBadge=nh,R.FileInput=Ed,R.FileUploader=Ep,R.FilterGroup=ny,R.FilterVisibilitySelector=_E,R.Flex=GT,R.Form=Pa,R.FormField=XE,R.Fragment=hb,R.Grid=WT,R.H1=Da,R.H2=Jn,R.H3=Ir,R.H4=Md,R.Hr=gb,R.HtmlContent=Za,R.HtmlEditor=wE,R.I=mt,R.Icon=re,R.IconButton=dy,R.IconName=de,R.IconSelect=VS,R.ImageGallery=ji,R.Img=yn,R.Input=kt,R.Label=sn,R.LanguageSelector=Ey,R.LayoutEditorHeader=Dy,R.LayoutFileList=Iy,R.LayoutHistoryPanel=Gv,R.LayoutWarnings=SE,R.Li=La,R.LoadingSpinner=wy,R.LoginForm=Yv,R.Modal=Bo,R.MultilingualInput=Ha,R.MultilingualLocaleContext=Pc,R.MultilingualTabPanel=KE,R.MultilingualTagInput=Kk,R.Nav=Po,R.NotificationCenter=Sy,R.Ol=sb,R.Optgroup=lb,R.Option=ab,R.P=yt,R.PageHeader=Ny,R.PageLoading=ex,R.PageTransitionIndicator=Jv,R.Pagination=$o,R.PermissionTree=jS,R.Pre=fb,R.ProductCard=Rb,R.RadioGroup=yE,R.RichSelect=OE,R.SearchBar=ty,R.SearchableDropdown=Xk,R.Section=Nd,R.SectionHeader=Ty,R.SectionLayout=YT,R.Select=Pr,R.SlotContainer=NE,R.SortableMenuItem=Gh,R.SortableMenuList=FS,R.Span=Q,R.StatCard=ey,R.StatusBadge=Fo,R.Svg=Un,R.TabNavigation=ry,R.TabNavigationScroll=ly,R.Table=$a,R.TagInput=Wk,R.TagSelect=qk,R.Tbody=za,R.Td=Zt,R.TemplateCard=Ay,R.Textarea=Qn,R.Tfoot=Va,R.Th=$r,R.Thead=Fa,R.ThemeToggle=Xv,R.ThreeColumnLayout=KT,R.Toast=by,R.Toggle=Ua,R.Tr=Rn,R.Ul=Ia,R.UnifiedIcon=db,R.UserProfile=_y,R.VersionList=_u,R.configureJSONLanguageService=ZT,R.defaultJSONSchemas=Fm,R.executeImageDownload=Tc,R.initTemplate=Qm,R.jsonEditorDefaults=XT,R.monacoInitOptions=qT,R.templateMetadata=QT,R.useCurrentMultilingualLocale=Ip,R.useImageGallery=fp,Object.defineProperty(R,Symbol.toStringTag,{value:"Module"}),R})({},ReactJSXRuntime,React,ReactDOM); //# sourceMappingURL=components.iife.js.map diff --git a/templates/_bundled/sirsoft-admin_basic/lang/partial/en/admin.json b/templates/_bundled/sirsoft-admin_basic/lang/partial/en/admin.json index 1728e307..46ee8cf4 100644 --- a/templates/_bundled/sirsoft-admin_basic/lang/partial/en/admin.json +++ b/templates/_bundled/sirsoft-admin_basic/lang/partial/en/admin.json @@ -1050,6 +1050,7 @@ "name": "Role Name", "description": "Description", "users_count": "Users", + "permissions_count": "Permissions", "status": "Status", "created_at": "Created At" }, diff --git a/templates/_bundled/sirsoft-admin_basic/lang/partial/ko/admin.json b/templates/_bundled/sirsoft-admin_basic/lang/partial/ko/admin.json index 39279b2d..cb36a768 100644 --- a/templates/_bundled/sirsoft-admin_basic/lang/partial/ko/admin.json +++ b/templates/_bundled/sirsoft-admin_basic/lang/partial/ko/admin.json @@ -1054,6 +1054,7 @@ "name": "역할명", "description": "설명", "users_count": "사용자 수", + "permissions_count": "권한 수", "status": "상태", "created_at": "생성일" }, diff --git a/templates/_bundled/sirsoft-admin_basic/layouts/admin_role_list.json b/templates/_bundled/sirsoft-admin_basic/layouts/admin_role_list.json index 8d7c03a1..20e3cc22 100644 --- a/templates/_bundled/sirsoft-admin_basic/layouts/admin_role_list.json +++ b/templates/_bundled/sirsoft-admin_basic/layouts/admin_role_list.json @@ -354,6 +354,35 @@ } ] }, + { + "field": "permissions_count", + "header": "$t:admin.roles.columns.permissions_count", + "sortable": false, + "cellChildren": [ + { + "type": "basic", + "name": "Div", + "props": { + "className": "flex items-center gap-1 text-gray-600 dark:text-gray-400" + }, + "children": [ + { + "type": "basic", + "name": "Icon", + "props": { + "name": "key", + "size": "sm" + } + }, + { + "type": "basic", + "name": "Span", + "text": "{{row.permissions_count ?? 0}}" + } + ] + } + ] + }, { "field": "is_active", "header": "$t:admin.roles.columns.status", diff --git a/templates/_bundled/sirsoft-admin_basic/layouts/admin_settings.json b/templates/_bundled/sirsoft-admin_basic/layouts/admin_settings.json index f8b7896e..df2553c3 100644 --- a/templates/_bundled/sirsoft-admin_basic/layouts/admin_settings.json +++ b/templates/_bundled/sirsoft-admin_basic/layouts/admin_settings.json @@ -155,7 +155,8 @@ "per_page": "{{query.per_page || 20}}", "search": "{{query.search}}", "search_type": "{{query.search_type}}", - "extension_identifier": "core" + "extension_identifier": "core", + "template_channel": "{{query.channel ?? 'mail'}}" }, "fallback": { "data": { @@ -877,7 +878,6 @@ ], "initLocal": { "driverTestResults": [], - "activeNotificationChannel": null, "editingDefinition": null, "editingTemplate": null, "editLang": "ko", diff --git a/templates/_bundled/sirsoft-admin_basic/layouts/partials/admin_menu_list/_panel_menu_list.json b/templates/_bundled/sirsoft-admin_basic/layouts/partials/admin_menu_list/_panel_menu_list.json index 0f1e38f3..5c55d2ec 100644 --- a/templates/_bundled/sirsoft-admin_basic/layouts/partials/admin_menu_list/_panel_menu_list.json +++ b/templates/_bundled/sirsoft-admin_basic/layouts/partials/admin_menu_list/_panel_menu_list.json @@ -63,6 +63,24 @@ "panelMode": "view" } }, + { + "comment": "목록 응답에는 하위 메뉴의 역할이 실려 있지 않다(#76 목록 하위 컬렉션 프루닝). 선택 즉시 단건을 조회해 역할 배지와 편집 폼 초기값이 실제 역할 제한을 반영하도록 보강한다 — 이 조회가 없으면 하위 메뉴 저장 시 역할이 전부 해제된다.", + "handler": "apiCall", + "auth_required": true, + "target": "/api/admin/menus/{{$args[0].id}}", + "params": { + "method": "GET" + }, + "onSuccess": [ + { + "handler": "setState", + "params": { + "target": "global", + "selectedMenu": "{{response.data}}" + } + } + ] + }, { "comment": "선택 상태만 주소에 기록한다 — 왼쪽 목록에 걸어둔 조건은 그대로 둔다 (#75).", "handler": "replaceUrl", diff --git a/templates/_bundled/sirsoft-admin_basic/layouts/partials/admin_settings/_tab_notification_definitions.json b/templates/_bundled/sirsoft-admin_basic/layouts/partials/admin_settings/_tab_notification_definitions.json index 22449485..d41eb9e1 100644 --- a/templates/_bundled/sirsoft-admin_basic/layouts/partials/admin_settings/_tab_notification_definitions.json +++ b/templates/_bundled/sirsoft-admin_basic/layouts/partials/admin_settings/_tab_notification_definitions.json @@ -145,7 +145,7 @@ }, "props": { "type": "button", - "className": "{{(_local.activeNotificationChannel ?? query.channel ?? (availableChannels?.data?.channels ?? [])[0]?.id ?? 'mail') === ch.id ? 'border-b-2 border-indigo-500 dark:border-indigo-400 text-indigo-600 dark:text-indigo-400' : 'text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300'}} px-4 py-2.5 text-sm font-medium whitespace-nowrap flex-center gap-2" + "className": "{{(query.channel ?? 'mail') === ch.id ? 'border-b-2 border-indigo-500 dark:border-indigo-400 text-indigo-600 dark:text-indigo-400' : 'text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300'}} px-4 py-2.5 text-sm font-medium whitespace-nowrap flex-center gap-2" }, "children": [ { @@ -168,13 +168,6 @@ "handler": "sequence", "params": { "actions": [ - { - "handler": "setState", - "params": { - "target": "local", - "activeNotificationChannel": "{{ch.id}}" - } - }, { "handler": "navigate", "params": { @@ -402,16 +395,16 @@ { "type": "basic", "name": "P", - "if": "{{(def.templates ?? []).find(t => t.channel === (_local.activeNotificationChannel ?? query.channel ?? 'mail'))}}", + "if": "{{(def.templates ?? []).find(t => t.channel === (query.channel ?? 'mail'))}}", "props": { "className": "text-label-subtle truncate" }, - "text": "{{(def.templates ?? []).find(t => t.channel === (_local.activeNotificationChannel ?? query.channel ?? 'mail'))?.subject?.[$locale] ?? ''}}" + "text": "{{(def.templates ?? []).find(t => t.channel === (query.channel ?? 'mail'))?.subject?.[$locale] ?? ''}}" }, { "type": "basic", "name": "P", - "if": "{{!(def.templates ?? []).find(t => t.channel === (_local.activeNotificationChannel ?? query.channel ?? 'mail'))}}", + "if": "{{!(def.templates ?? []).find(t => t.channel === (query.channel ?? 'mail'))}}", "props": { "className": "text-sm text-gray-400 dark:text-gray-500 italic" }, @@ -421,7 +414,7 @@ "id": "recipients_badge_{{defIdx}}", "type": "basic", "name": "Div", - "if": "{{((def.templates ?? []).find(t => t.channel === (_local.activeNotificationChannel ?? query.channel ?? 'mail'))?.recipients ?? []).length > 0}}", + "if": "{{((def.templates ?? []).find(t => t.channel === (query.channel ?? 'mail'))?.recipients ?? []).length > 0}}", "props": { "className": "flex-center flex-wrap gap-1.5 mt-2" }, @@ -438,7 +431,7 @@ "type": "basic", "name": "Span", "iteration": { - "source": "{{(def.templates ?? []).find(t => t.channel === (_local.activeNotificationChannel ?? query.channel ?? 'mail'))?.recipients ?? []}}", + "source": "{{(def.templates ?? []).find(t => t.channel === (query.channel ?? 'mail'))?.recipients ?? []}}", "item_var": "rcpt" }, "props": { @@ -507,7 +500,7 @@ "name": "notification_definition_row_footer", "props": { "definition": "{{def}}", - "activeChannel": "{{_local.activeNotificationChannel ?? query.channel ?? 'mail'}}" + "activeChannel": "{{query.channel ?? 'mail'}}" } } ] @@ -522,9 +515,9 @@ { "type": "composite", "name": "Toggle", - "if": "{{(def.templates ?? []).find(t => t.channel === (_local.activeNotificationChannel ?? query.channel ?? 'mail'))}}", + "if": "{{(def.templates ?? []).find(t => t.channel === (query.channel ?? 'mail'))}}", "props": { - "checked": "{{(def.templates ?? []).find(t => t.channel === (_local.activeNotificationChannel ?? query.channel ?? 'mail'))?.is_active ?? false}}", + "checked": "{{(def.templates ?? []).find(t => t.channel === (query.channel ?? 'mail'))?.is_active ?? false}}", "disabled": "{{_computed.isReadOnly}}" }, "actions": [ @@ -532,7 +525,7 @@ "type": "change", "handler": "apiCall", "auth_required": true, - "target": "{{'/api/admin/notification-templates/' + (def.templates ?? []).find(t => t.channel === (_local.activeNotificationChannel ?? query.channel ?? 'mail'))?.id + '/toggle-active'}}", + "target": "{{'/api/admin/notification-templates/' + (def.templates ?? []).find(t => t.channel === (query.channel ?? 'mail'))?.id + '/toggle-active'}}", "params": { "method": "PATCH" }, @@ -583,9 +576,9 @@ "params": { "target": "global", "notification_template_form_modal": { - "template": "{{(def.templates ?? []).find(t => t.channel === (_local.activeNotificationChannel ?? query.channel ?? 'mail'))}}", + "template": "{{(def.templates ?? []).find(t => t.channel === (query.channel ?? 'mail'))}}", "definition": "{{def}}", - "recipients": "{{JSON.parse(JSON.stringify((def.templates ?? []).find(t => t.channel === (_local.activeNotificationChannel ?? query.channel ?? 'mail'))?.recipients ?? []))}}", + "recipients": "{{JSON.parse(JSON.stringify((def.templates ?? []).find(t => t.channel === (query.channel ?? 'mail'))?.recipients ?? []))}}", "editLang": "{{$locale}}", "errors": null, "isSaving": false, @@ -606,7 +599,7 @@ { "type": "basic", "name": "Button", - "if": "{{!def.is_default || (def.templates ?? []).some(t => t.is_default === false)}}", + "if": "{{!def.is_default || def.has_customized_templates === true}}", "props": { "type": "button", "disabled": "{{_computed.isReadOnly}}", @@ -658,7 +651,7 @@ "type": "composite", "name": "HtmlContent", "props": { - "content": "{{(def.templates ?? []).find(t => t.channel === (_local.activeNotificationChannel ?? query.channel ?? 'mail'))?.body?.[$locale] ?? ''}}", + "content": "{{(def.templates ?? []).find(t => t.channel === (query.channel ?? 'mail'))?.body?.[$locale] ?? ''}}", "isHtml": true, "className": "prose dark:prose-invert prose-sm max-w-none bg-gray-50 dark:bg-gray-900/50 rounded-lg p-3 max-h-48 overflow-y-auto" } diff --git a/templates/_bundled/sirsoft-admin_basic/src/handlers/__tests__/initMenuFromUrlHandler.test.ts b/templates/_bundled/sirsoft-admin_basic/src/handlers/__tests__/initMenuFromUrlHandler.test.ts new file mode 100644 index 00000000..5a46ef22 --- /dev/null +++ b/templates/_bundled/sirsoft-admin_basic/src/handlers/__tests__/initMenuFromUrlHandler.test.ts @@ -0,0 +1,161 @@ +/** + * initMenuFromUrlHandler 테스트 + * + * 회귀 배경 (#518 / 공개 #76): + * 목록 응답에서 하위 메뉴의 역할(`children[].roles`)을 프루닝한 뒤, 화면이 목록 행을 그대로 + * 편집 폼 초기값으로 쓰면 저장 시 빈 역할 배열이 전송되어 해당 메뉴의 역할 제한이 전부 + * 해제된다. 이 핸들러는 선택된 메뉴를 단건 조회로 보강해 그 경로를 막는다. + * + * @scenario resource=menu,endpoint=detail,observation=consumer_screen + * @effects menu_edit_form_seeds_roles_from_detail + */ + +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { initMenuFromUrlHandler } from '../initMenuFromUrlHandler'; + +const PARENT_MENU = { + id: 10, + name: { ko: '부모 메뉴' }, + slug: 'parent-menu', + url: '/parent', + icon: 'fa-folder', + order: 1, + is_active: true, + parent_id: null, + roles: [{ id: 1, name: { ko: '관리자' }, permission_type: 'read' }], + children: [ + { + id: 11, + name: { ko: '자식 메뉴' }, + slug: 'child-menu', + url: '/parent/child', + icon: 'fa-file', + order: 1, + is_active: true, + parent_id: 10, + // 목록 응답에는 하위 메뉴의 roles 키 자체가 없다 (#76 프루닝) + }, + ], +}; + +/** 단건 조회가 돌려주는 자식 메뉴 — 목록에는 없던 역할이 실려 있다. */ +const CHILD_DETAIL = { + id: 11, + name: { ko: '자식 메뉴' }, + slug: 'child-menu', + url: '/parent/child', + icon: 'fa-file', + order: 1, + is_active: true, + parent_id: 10, + extension_type: null, + extension_identifier: null, + roles: [ + { id: 3, name: { ko: '편집자' }, permission_type: 'read' }, + { id: 5, name: { ko: '검수자' }, permission_type: 'read' }, + ], +}; + +function setUrl(search: string): void { + Object.defineProperty(window, 'location', { + value: { search, pathname: '/admin/menus', href: `https://example.test/admin/menus${search}` }, + writable: true, + }); +} + +describe('initMenuFromUrlHandler', () => { + let stateSet: ReturnType; + let apiGet: ReturnType; + + beforeEach(() => { + stateSet = vi.fn(); + apiGet = vi.fn(async (url: string) => { + if (url === '/api/admin/menus/11') { + return { success: true, data: CHILD_DETAIL }; + } + if (url === '/api/admin/menus/10') { + return { success: true, data: PARENT_MENU }; + } + throw new Error(`unexpected url: ${url}`); + }); + + // 실측 형태 — 목록 데이터소스는 응답 envelope 를 그대로 담는다. + // (`G7Core.state.getDataSource('menus')` → `{success, message, data: {data: [...], abilities}}`) + // 종전 모킹은 `{data: [메뉴]}` 라는 존재하지 않는 형태였고, 그래서 이 핸들러가 실제 + // 화면에서 한 번도 동작하지 않는데도 테스트는 통과했다. + (window as any).G7Core = { + state: { + set: stateSet, + getDataSource: (id: string) => + id === 'menus' + ? { success: true, message: 'ok', data: { data: [PARENT_MENU], abilities: {} } } + : undefined, + }, + api: { get: apiGet }, + }; + }); + + afterEach(() => { + delete (window as any).G7Core; + vi.restoreAllMocks(); + }); + + it('menu 파라미터가 없으면 아무 상태도 건드리지 않는다', async () => { + setUrl(''); + + await initMenuFromUrlHandler({}); + + expect(stateSet).not.toHaveBeenCalled(); + expect(apiGet).not.toHaveBeenCalled(); + }); + + it('하위 메뉴 선택 시 단건을 조회해 selectedMenu 를 보강한다', async () => { + setUrl('?menu=child-menu&mode=view'); + + await initMenuFromUrlHandler({}); + + expect(apiGet).toHaveBeenCalledWith('/api/admin/menus/11'); + + const selection = stateSet.mock.calls.map((call) => call[0]).find((arg) => 'selectedMenu' in arg); + expect(selection).toBeDefined(); + expect(selection.selectedMenu.roles).toHaveLength(2); + expect(selection.selectedMenuId).toBe(11); + expect(selection.panelMode).toBe('view'); + }); + + it('edit 모드에서 formData.roles 가 단건 조회의 역할 ID 로 채워진다 (회귀: 저장 시 역할 전체 해제)', async () => { + setUrl('?menu=child-menu&mode=edit'); + + await initMenuFromUrlHandler({}); + + const formCall = stateSet.mock.calls.map((call) => call[0]).find((arg) => 'formData' in arg); + expect(formCall).toBeDefined(); + + // 존재를 먼저 확정한 뒤 값을 단언한다 (부재 단언이 렌더 전에 통과하는 함정 회피) + expect(formCall.formData).toHaveProperty('roles'); + expect(formCall.formData.roles).toEqual([3, 5]); + expect(formCall.formData.slug).toBe('child-menu'); + }); + + it('단건 조회가 실패해도 목록 행으로 선택 상태는 유지한다', async () => { + apiGet.mockRejectedValueOnce(new Error('network down')); + setUrl('?menu=child-menu&mode=view'); + + await initMenuFromUrlHandler({}); + + const selection = stateSet.mock.calls.map((call) => call[0]).find((arg) => 'selectedMenu' in arg); + expect(selection).toBeDefined(); + expect(selection.selectedMenuId).toBe(11); + }); + + it('상위 메뉴도 동일하게 단건 조회로 보강한다', async () => { + setUrl('?menu=parent-menu&mode=edit'); + + await initMenuFromUrlHandler({}); + + expect(apiGet).toHaveBeenCalledWith('/api/admin/menus/10'); + + const formCall = stateSet.mock.calls.map((call) => call[0]).find((arg) => 'formData' in arg); + expect(formCall.formData.roles).toEqual([1]); + }); +}); diff --git a/templates/_bundled/sirsoft-admin_basic/src/handlers/initMenuFromUrlHandler.ts b/templates/_bundled/sirsoft-admin_basic/src/handlers/initMenuFromUrlHandler.ts index 88d60af8..b55d7987 100644 --- a/templates/_bundled/sirsoft-admin_basic/src/handlers/initMenuFromUrlHandler.ts +++ b/templates/_bundled/sirsoft-admin_basic/src/handlers/initMenuFromUrlHandler.ts @@ -74,6 +74,29 @@ function getDataSource(dataSourceId: string): any { return g7Core?.state?.getDataSource?.(dataSourceId); } +/** + * 데이터 소스 값에서 행 배열을 꺼냅니다. + * + * 목록 데이터소스는 응답 envelope 를 그대로 담는다 — + * `{ success, message, data: { data: [...], abilities } }`. 예전에는 `dataSource.data` 가 + * 배열이라고 가정했는데 그 형태는 실제로 존재하지 않아, 이 핸들러가 화면에서 한 번도 + * 동작하지 않았다(대기만 하다 조용히 종료). 중첩 깊이가 다른 형태도 함께 받아들인다. + * + * @param dataSource 데이터 소스 값 + * @returns 행 배열 (찾지 못하면 null) + */ +function extractRows(dataSource: any): any[] | null { + const candidates = [dataSource, dataSource?.data, dataSource?.data?.data]; + + for (const candidate of candidates) { + if (Array.isArray(candidate) && candidate.length > 0) { + return candidate; + } + } + + return null; +} + /** * 데이터 소스가 로드될 때까지 대기합니다. * @@ -88,16 +111,42 @@ async function waitForDataSource( interval: number = 100 ): Promise { for (let i = 0; i < maxAttempts; i++) { - const dataSource = getDataSource(dataSourceId); - const data = dataSource?.data; - if (Array.isArray(data) && data.length > 0) { - return data; + const rows = extractRows(getDataSource(dataSourceId)); + if (rows) { + return rows; } await new Promise((resolve) => setTimeout(resolve, interval)); } return null; } +/** + * 메뉴 단건을 조회해 목록 응답에 없는 필드(역할 등)까지 채운 메뉴를 반환합니다. + * + * 목록 응답은 하위 메뉴의 역할을 싣지 않는다(#76 목록 하위 컬렉션 프루닝). 목록 행을 + * 그대로 편집 폼 초기값으로 쓰면 하위 메뉴 저장 시 역할 제한이 전부 해제되므로, + * 선택 시점에 단건을 조회해 보강한다. 조회에 실패하면 목록 행을 그대로 돌려준다. + * + * @param menu 목록에서 찾은 메뉴 + * @returns 단건 조회로 보강된 메뉴 (실패 시 입력 그대로) + */ +async function hydrateMenuDetail(menu: MenuItem): Promise { + const g7Core = (window as any).G7Core; + + try { + const response = await g7Core?.api?.get?.(`/api/admin/menus/${menu.id}`); + const detail = response?.data; + + if (detail && typeof detail === 'object' && detail.id === menu.id) { + return detail as MenuItem; + } + } catch (error) { + logger.warn('[initMenuFromUrl] Failed to hydrate menu detail:', error); + } + + return menu; +} + /** * initMenuFromUrl 핸들러 * @@ -144,13 +193,16 @@ export async function initMenuFromUrlHandler( return; } - // 5. 상태 업데이트 + // 5. 단건 조회로 보강 (목록 응답에 없는 역할 등) + const hydratedMenu = await hydrateMenuDetail(foundMenu); + + // 6. 상태 업데이트 const panelMode = mode === 'edit' ? 'edit' : 'view'; // 전역 상태 업데이트 (G7Core.state.set 사용) g7Core.state.set({ - selectedMenuId: foundMenu.id, - selectedMenu: foundMenu, + selectedMenuId: hydratedMenu.id, + selectedMenu: hydratedMenu, panelMode: panelMode, }); @@ -158,13 +210,16 @@ export async function initMenuFromUrlHandler( if (panelMode === 'edit') { g7Core.state.set({ formData: { - name: foundMenu.name, - slug: foundMenu.slug, - url: foundMenu.url, - icon: foundMenu.icon, - parent_id: foundMenu.parent_id, - module_id: foundMenu.module_id, - is_active: foundMenu.is_active, + name: hydratedMenu.name, + slug: hydratedMenu.slug, + url: hydratedMenu.url, + icon: hydratedMenu.icon, + parent_id: hydratedMenu.parent_id, + extension_type: hydratedMenu.extension_type, + extension_identifier: hydratedMenu.extension_identifier, + is_active: hydratedMenu.is_active, + // 역할을 빠뜨리면 저장 시 폼이 빈 배열을 실어 보내 역할 제한이 전부 해제된다. + roles: (hydratedMenu.roles ?? []).map((role: { id: number }) => role.id), }, }); } diff --git a/templates/_bundled/sirsoft-basic/CHANGELOG.md b/templates/_bundled/sirsoft-basic/CHANGELOG.md index 65617c4c..a9c228ea 100644 --- a/templates/_bundled/sirsoft-basic/CHANGELOG.md +++ b/templates/_bundled/sirsoft-basic/CHANGELOG.md @@ -10,6 +10,10 @@ - 회원가입 화면에 휴대폰번호·전화번호 입력란을 추가했습니다. 두 항목 모두 선택 사항입니다. +### Changed + +- 마이페이지 주문내역이 주문상품 전체를 명시적으로 요청하도록 바꿨습니다. 주문내역 조회의 기본 응답이 대표 상품 1건만 담도록 가벼워졌기 때문이며, 화면에 보이는 주문상품 목록은 종전과 동일합니다. (#76 @jordy-bitree 님께서 제보해주셨습니다.) + ### Fixed #### 검색·봇 화면 diff --git a/templates/_bundled/sirsoft-basic/layouts/mypage/orders.json b/templates/_bundled/sirsoft-basic/layouts/mypage/orders.json index 5e3913df..94ec161a 100644 --- a/templates/_bundled/sirsoft-basic/layouts/mypage/orders.json +++ b/templates/_bundled/sirsoft-basic/layouts/mypage/orders.json @@ -26,9 +26,11 @@ "auth_required": true, "loading_strategy": "progressive", "blur_until_loaded": true, + "comment": "with_items=1 — 이 화면은 주문마다 아이템을 전부 나열한다(partials/mypage/orders/_list.json 의 order.items 순회). 서버 기본은 대표 1건 + 개수이므로 전량을 명시적으로 요청한다 (#76). 주석은 params 밖에 둔다 — params 의 키는 전부 쿼리스트링으로 전송된다.", "params": { "page": "{{query.page ?? 1}}", - "status": "{{query.status}}" + "status": "{{query.status}}", + "with_items": 1 } } ], diff --git a/tests/Feature/Api/Admin/CoreListPayloadPruningTest.php b/tests/Feature/Api/Admin/CoreListPayloadPruningTest.php new file mode 100644 index 00000000..654dbc35 --- /dev/null +++ b/tests/Feature/Api/Admin/CoreListPayloadPruningTest.php @@ -0,0 +1,365 @@ +adminUser = $this->createAdminUser([ + 'core.permissions.read', + 'core.permissions.create', + 'core.permissions.update', + 'core.menus.read', + ]); + $this->token = $this->adminUser->createToken('test-token')->plainTextToken; + } + + /** + * 필요한 권한을 가진 관리자를 만듭니다. + * + * @param array $permissions 부여할 권한 식별자 + * @return User 생성된 사용자 + */ + private function createAdminUser(array $permissions = []): User + { + $user = User::factory()->create(); + + $permissionIds = []; + foreach ($permissions as $identifier) { + $permission = Permission::firstOrCreate( + ['identifier' => $identifier], + [ + 'name' => json_encode(['ko' => $identifier, 'en' => $identifier]), + 'description' => json_encode(['ko' => $identifier, 'en' => $identifier]), + 'extension_type' => ExtensionOwnerType::Core, + 'extension_identifier' => 'core', + 'type' => 'admin', + ] + ); + $permissionIds[] = $permission->id; + } + + $testRole = Role::create([ + 'identifier' => 'admin_test_'.uniqid(), + 'name' => json_encode(['ko' => '테스트 관리자', 'en' => 'Test Administrator']), + 'description' => json_encode(['ko' => '테스트 관리자', 'en' => 'Test Administrator']), + 'is_active' => true, + ]); + + $adminRole = Role::firstOrCreate( + ['identifier' => 'admin'], + [ + 'name' => json_encode(['ko' => '관리자', 'en' => 'Administrator']), + 'description' => json_encode(['ko' => '시스템 관리자', 'en' => 'System Administrator']), + 'extension_type' => ExtensionOwnerType::Core, + 'extension_identifier' => 'core', + 'type' => 'admin', + 'is_active' => true, + ] + ); + + $testRole->permissions()->sync($permissionIds); + $user->roles()->attach($adminRole->id, ['assigned_at' => now(), 'assigned_by' => null]); + $user->roles()->attach($testRole->id, ['assigned_at' => now(), 'assigned_by' => null]); + + return $user->fresh(); + } + + /** 인증 헤더가 붙은 요청 헬퍼 */ + private function authRequest(): static + { + return $this->withHeaders([ + 'Authorization' => 'Bearer '.$this->token, + 'Accept' => 'application/json', + ]); + } + + // ==================== 역할 목록 (F) ==================== + + /** + * 역할 목록에는 권한 집합이 실리지 않는다. + * + * 목록 화면이 쓰는 행 필드는 이름·설명·사용자 수·활성 여부·확장 정보뿐이다. 권한을 로드하면 + * Resource 가 같은 집합을 `permissions`(계층 트리)·`permission_ids`·`permission_values` + * 세 형태로 중복 직렬화한다. 권한 편집은 단건 조회가 공급한다. + * + * @scenario resource=role,endpoint=list,observation=response_payload + * + * @effects list_omits_role_permissions + */ + public function test_role_list_omits_permission_payload(): void + { + $role = Role::create([ + 'identifier' => 'list_prune_role', + 'name' => json_encode(['ko' => '목록 프루닝', 'en' => 'List Pruning']), + 'description' => json_encode(['ko' => '설명', 'en' => 'Description']), + 'is_active' => true, + ]); + $role->permissions()->sync(Permission::query()->limit(2)->pluck('id')->all()); + + $response = $this->authRequest()->getJson('/api/admin/roles'); + + $response->assertStatus(200); + + $rows = $response->json('data.data') ?? $response->json('data'); + + $this->assertNotEmpty($rows); + + foreach ($rows as $row) { + foreach (['permissions', 'permission_ids', 'permission_values'] as $heavy) { + $this->assertArrayNotHasKey($heavy, $row, "역할 목록에 {$heavy} 가 실리면 안 된다"); + } + + // 뺀 값의 대체 경로: 권한 트리를 싣지 않아도 규모는 집계로 보인다. + $this->assertArrayHasKey('permissions_count', $row, '권한 개수 집계가 목록에 실려야 한다'); + $this->assertIsInt($row['permissions_count']); + } + + $target = collect($rows)->firstWhere('identifier', 'list_prune_role'); + + $this->assertNotNull($target, '생성한 역할이 목록에 있어야 한다'); + $this->assertSame( + $role->permissions()->count(), + $target['permissions_count'], + '집계 값이 실제 할당된 권한 수와 일치해야 한다', + ); + } + + /** + * 권한이 0건인 역할도 집계가 0 으로 실려야 한다. + * + * COUNT 는 0건에서 0 을 돌려주므로 값 검사(`!== null`)로는 "집계 안 함" 과 구분되지 않는다. + * Resource 는 별칭 존재 여부로 판정하므로 0 이 그대로 실린다. + * + * @scenario resource=role,endpoint=list,observation=response_payload + * + * @effects list_omits_role_permissions + */ + public function test_role_list_permission_count_is_zero_not_missing_when_role_has_none(): void + { + Role::create([ + 'identifier' => 'list_prune_role_empty', + 'name' => json_encode(['ko' => '권한 없음', 'en' => 'No Permission']), + 'description' => json_encode(['ko' => '설명', 'en' => 'Description']), + 'is_active' => true, + ]); + + $response = $this->authRequest()->getJson('/api/admin/roles'); + + $response->assertStatus(200); + + $rows = $response->json('data.data') ?? $response->json('data'); + $target = collect($rows)->firstWhere('identifier', 'list_prune_role_empty'); + + $this->assertNotNull($target, '생성한 역할이 목록에 있어야 한다'); + $this->assertArrayHasKey('permissions_count', $target); + $this->assertSame(0, $target['permissions_count']); + } + + /** + * 역할 목록이 쓰는 표시 필드는 그대로 남는다 (기능 축소 아님). + */ + public function test_role_list_keeps_display_fields(): void + { + $response = $this->authRequest()->getJson('/api/admin/roles'); + + $response->assertStatus(200); + + $row = ($response->json('data.data') ?? $response->json('data'))[0]; + + foreach (['id', 'name', 'description', 'is_active', 'users_count'] as $field) { + $this->assertArrayHasKey($field, $row, "목록 화면이 쓰는 {$field} 가 사라졌다"); + } + } + + /** + * 목록에서 뺀 권한 집합은 단건 조회가 여전히 공급한다. + * + * @scenario resource=role,endpoint=detail,observation=response_payload + * + * @effects detail_still_returns_full_payload + */ + public function test_role_detail_still_provides_permissions(): void + { + $role = Role::create([ + 'identifier' => 'detail_keeps_perms', + 'name' => json_encode(['ko' => '상세 유지', 'en' => 'Detail Keeps']), + 'description' => json_encode(['ko' => '설명', 'en' => 'Description']), + 'is_active' => true, + ]); + $role->permissions()->sync(Permission::query()->limit(2)->pluck('id')->all()); + + $response = $this->authRequest()->getJson("/api/admin/roles/{$role->id}"); + + $response->assertStatus(200); + $this->assertArrayHasKey('permission_ids', $response->json('data')); + $this->assertArrayHasKey('permissions', $response->json('data')); + } + + /** + * 역할 수가 늘어도 권한 테이블 조회가 늘지 않는다 (행당 재조회 부재). + * + * 종전에는 행마다 계층 트리를 만들며 상위/카테고리 권한을 다시 조회했다. + * + * @effects query_count_does_not_scale_with_row_count + */ + public function test_role_list_query_count_does_not_grow_with_rows(): void + { + for ($i = 0; $i < 3; $i++) { + $role = Role::create([ + 'identifier' => 'perf_role_'.$i, + 'name' => json_encode(['ko' => '역할'.$i, 'en' => 'Role '.$i]), + 'description' => json_encode(['ko' => '설명', 'en' => 'Desc']), + 'is_active' => true, + ]); + $role->permissions()->sync(Permission::query()->limit(2)->pluck('id')->all()); + } + + // 워밍 (권한/설정 캐시) + $this->authRequest()->getJson('/api/admin/roles?per_page=100')->assertOk(); + + $queries = []; + DB::listen(function ($query) use (&$queries) { + $queries[] = $query->sql; + }); + + $this->authRequest()->getJson('/api/admin/roles?per_page=100')->assertOk(); + $baseline = count(array_filter($queries, fn (string $sql) => $this->isRolePermissionEagerLoad($sql))); + + for ($i = 3; $i < 12; $i++) { + $role = Role::create([ + 'identifier' => 'perf_role_'.$i, + 'name' => json_encode(['ko' => '역할'.$i, 'en' => 'Role '.$i]), + 'description' => json_encode(['ko' => '설명', 'en' => 'Desc']), + 'is_active' => true, + ]); + $role->permissions()->sync(Permission::query()->limit(2)->pluck('id')->all()); + } + + $queries = []; + $this->authRequest()->getJson('/api/admin/roles?per_page=100')->assertOk(); + $grown = count(array_filter($queries, fn (string $sql) => $this->isRolePermissionEagerLoad($sql))); + + $this->assertSame( + $baseline, + $grown, + "역할 수에 비례해 권한 조회가 늘었다 (기준: {$baseline}, 증가 후: {$grown})" + ); + } + + // ==================== 메뉴 목록 (M) ==================== + + /** + * 메뉴 목록의 하위 메뉴에는 역할 배지가 실리지 않는다. + * + * 상위 메뉴마다 하위 전체의 역할 피벗까지 직렬화되던 것을 걷어낸다. 대신 목록은 `roles` 키 + * 자체를 생략하고, 그 값은 단건 조회가 공급한다 (기능 축소가 아님을 같은 테스트에서 확인). + * + * @scenario resource=menu,endpoint=list,observation=response_payload + * + * @effects list_omits_menu_child_roles, menu_detail_provides_child_roles + */ + public function test_menu_list_child_omits_roles_while_parent_keeps_them(): void + { + // 목록은 사용자가 접근 가능한 메뉴만 돌려주므로(accessibleBy), 테스트 사용자가 실제로 + // 가진 역할에 연결한다. + $role = Role::where('identifier', 'admin')->firstOrFail(); + + $parent = Menu::create([ + 'name' => ['ko' => '부모 메뉴', 'en' => 'Parent Menu'], + 'slug' => 'parent-menu-prune', + 'url' => '/parent', + 'order' => 1, + 'is_active' => true, + ]); + $child = Menu::create([ + 'name' => ['ko' => '자식 메뉴', 'en' => 'Child Menu'], + 'slug' => 'child-menu-prune', + 'url' => '/parent/child', + 'parent_id' => $parent->id, + 'order' => 1, + 'is_active' => true, + ]); + + $parent->roles()->attach($role->id, ['permission_type' => 'read']); + $child->roles()->attach($role->id, ['permission_type' => 'read']); + + $response = $this->authRequest()->getJson('/api/admin/menus?with_children=true'); + + $response->assertStatus(200); + + $rows = $response->json('data.data') ?? $response->json('data'); + $parentRow = collect($rows)->firstWhere('slug', 'parent-menu-prune'); + + $this->assertNotNull($parentRow, '부모 메뉴가 목록에 있어야 한다'); + + // 상위 메뉴의 역할 배지는 유지 (화면이 쓴다) + $this->assertNotEmpty($parentRow['roles'] ?? [], '상위 메뉴의 역할은 유지되어야 한다'); + + // 하위 메뉴는 표시 정보만 + $childRow = collect($parentRow['children'] ?? [])->firstWhere('slug', 'child-menu-prune'); + $this->assertNotNull($childRow, '하위 메뉴는 계속 내려와야 한다 (개수 표시에 필요)'); + $this->assertArrayNotHasKey( + 'roles', + $childRow, + '하위 메뉴의 역할은 목록에서 빠져야 한다 — 빈 배열로 내려주면 "역할 제한 없음" 이라는 ' + .'사실이 아닌 단언이 되어 화면이 그대로 저장에 실어 보낸다', + ); + + // 프루닝한 값은 대체 경로가 있어야 한다 — 단건 조회가 역할을 돌려준다. + $detail = $this->authRequest()->getJson("/api/admin/menus/{$child->id}"); + $detail->assertStatus(200); + + $detailRoles = $detail->json('data.roles'); + $this->assertIsArray($detailRoles, '단건 조회는 역할을 돌려줘야 한다 (지연 로드 경로)'); + $this->assertSame( + [$role->id], + collect($detailRoles)->pluck('id')->all(), + '단건 조회의 역할이 실제 부여된 역할과 일치해야 한다', + ); + } + + /** + * 역할→권한 **관계 eager load** 쿼리인지 판정합니다. + * + * 권한 체크(`hasPermission`)도 같은 피벗 테이블을 참조하지만 그쪽은 `exists(...)` 서브쿼리이며, + * 행마다 실행되는 그 N+1 은 별도 이슈(#519)가 소유한다. 이 테스트는 목록 쿼리가 권한 집합을 + * 다시 끌어오지 않는지만 본다. + * + * @param string $sql 실행된 SQL + * @return bool 관계 eager load 여부 + */ + private function isRolePermissionEagerLoad(string $sql): bool + { + return str_contains($sql, 'role_permissions') && ! str_contains($sql, 'exists('); + } +} diff --git a/tests/Feature/Api/Admin/Identity/AdminIdentityMessageDefinitionListPayloadTest.php b/tests/Feature/Api/Admin/Identity/AdminIdentityMessageDefinitionListPayloadTest.php new file mode 100644 index 00000000..4e77070a --- /dev/null +++ b/tests/Feature/Api/Admin/Identity/AdminIdentityMessageDefinitionListPayloadTest.php @@ -0,0 +1,319 @@ +seed(RolePermissionSeeder::class); + + $this->admin = User::factory()->create(['is_super' => true]); + $adminRole = Role::where('identifier', 'admin')->first(); + if ($adminRole) { + $this->admin->roles()->attach($adminRole->id, [ + 'assigned_at' => now(), + 'assigned_by' => null, + ]); + } + $this->admin = $this->admin->fresh(); + $this->token = $this->admin->createToken('test-token')->plainTextToken; + } + + /** + * 인증 헤더가 붙은 요청 빌더를 반환합니다. + * + * @return static + */ + private function authRequest(): static + { + return $this->withHeaders([ + 'Authorization' => 'Bearer '.$this->token, + 'Accept' => 'application/json', + ]); + } + + /** + * 채널 여러 개를 가진 메시지 정의를 만듭니다. + * + * @param string $key 정의 식별 키 (scope_value) + * @param array $channels 만들 채널 목록 (첫 번째가 대표) + * @return IdentityMessageDefinition 생성된 정의 + */ + private function makeDefinitionWithChannels(string $key, array $channels): IdentityMessageDefinition + { + $definition = IdentityMessageDefinition::create([ + 'provider_id' => 'test-provider', + 'scope_type' => 'purpose', + 'scope_value' => $key, + 'name' => ['ko' => '정의 '.$key], + 'description' => ['ko' => '설명'], + 'channels' => $channels, + 'variables' => [], + 'extension_type' => 'core', + 'extension_identifier' => 'core', + 'is_active' => true, + 'is_default' => true, + ]); + + foreach ($channels as $channel) { + IdentityMessageTemplate::create([ + 'definition_id' => $definition->id, + 'channel' => $channel, + 'subject' => ['ko' => "제목-{$key}-{$channel}"], + 'body' => ['ko' => str_repeat("본문-{$channel} ", 50)], + 'is_active' => true, + 'is_default' => true, + ]); + } + + return $definition; + } + + /** + * 목록은 정의마다 대표 템플릿 1건만 싣는다. + * + * @return void + * + * @scenario resource=identity_message_definition,endpoint=list,observation=response_payload + * @effects identity_definition_list_carries_representative_template_only + */ + public function test_definition_list_carries_only_the_representative_template(): void + { + $this->makeDefinitionWithChannels('first', ['mail', 'sms', 'push']); + $this->makeDefinitionWithChannels('second', ['mail', 'sms']); + + $response = $this->authRequest()->getJson('/api/admin/identity/messages/definitions'); + $response->assertStatus(200); + + $rows = $response->json('data.data'); + $this->assertCount(2, $rows); + + foreach ($rows as $row) { + $this->assertArrayHasKey('templates', $row, '화면 계약은 templates 배열이다 — 키 자체는 유지해야 한다'); + $this->assertCount( + 1, + $row['templates'], + '목록에 채널별 템플릿이 전부 실리면 한 페이지를 여는 것만으로 모든 채널 본문이 전송된다' + ); + } + } + + /** + * 대표 1건만 싣는 대신, 템플릿이 더 있다는 사실은 집계로 알린다. + * + * @return void + * + * @scenario resource=identity_message_definition,endpoint=list,observation=response_payload + * @effects identity_definition_list_reports_total_template_count + */ + public function test_definition_list_reports_total_template_count(): void + { + $this->makeDefinitionWithChannels('first', ['mail', 'sms', 'push']); + + $row = $this->authRequest() + ->getJson('/api/admin/identity/messages/definitions') + ->json('data.data.0'); + + $this->assertSame(3, $row['templates_count'], '배열 길이로는 알 수 없으므로 집계가 필요하다'); + $this->assertCount(1, $row['templates']); + } + + /** + * 단건 조회는 전체 템플릿을 그대로 공급한다 (대체 경로 보존 — 기능 축소가 아니다). + * + * @return void + * + * @scenario resource=identity_message_definition,endpoint=detail,observation=response_payload + * @effects identity_definition_detail_returns_every_template, detail_still_returns_full_payload + */ + public function test_definition_detail_still_returns_every_template(): void + { + $definition = $this->makeDefinitionWithChannels('first', ['mail', 'sms', 'push']); + + $row = $this->authRequest() + ->getJson("/api/admin/identity/messages/definitions/{$definition->id}") + ->assertStatus(200) + ->json('data'); + + $this->assertCount(3, $row['templates']); + $this->assertSame( + ['mail', 'push', 'sms'], + collect($row['templates'])->pluck('channel')->sort()->values()->all() + ); + } + + /** + * 목록의 대표 템플릿은 가장 먼저 등록된 것이다 (정의별 1건 보장). + * + * eager load 의 limit(1) 은 부모별이 아니라 배치 전체에 걸리므로, 잘못 구현하면 첫 정의만 + * 템플릿을 받고 나머지는 빈 배열이 된다. + * + * @return void + * + * @scenario resource=identity_message_definition,endpoint=list,observation=response_payload + * @effects identity_definition_every_row_gets_its_own_representative + */ + public function test_every_definition_gets_its_own_representative_template(): void + { + $this->makeDefinitionWithChannels('first', ['mail', 'sms']); + $this->makeDefinitionWithChannels('second', ['sms', 'mail']); + $this->makeDefinitionWithChannels('third', ['push', 'mail']); + + $rows = $this->authRequest() + ->getJson('/api/admin/identity/messages/definitions') + ->json('data.data'); + + foreach ($rows as $row) { + $this->assertCount( + 1, + $row['templates'], + '정의마다 대표 1건씩 — 배치 전체에 limit 이 걸리면 첫 행만 채워진다' + ); + } + + // 대표는 정의별 "가장 먼저 등록된" 채널이어야 한다 (생성 순서 = 위 배열 순서) + $byScope = collect($rows)->keyBy('scope_value'); + $this->assertSame('mail', $byScope['first']['templates'][0]['channel']); + $this->assertSame('sms', $byScope['second']['templates'][0]['channel']); + $this->assertSame('push', $byScope['third']['templates'][0]['channel']); + } + + /** + * 템플릿이 0건인 정의도 `templates` 는 빈 배열이다 (null 금지). + * + * 화면은 `(def.templates ?? []).find(...)` 로 읽으므로 null 이어도 즉시 깨지지는 않는다. + * 그러나 응답 계약상 null 은 "관계를 로드하지 않았다" 를 뜻하고 빈 배열은 "로드했더니 0건" + * 을 뜻한다. 두 상황이 같은 값으로 나오면 소비자가 둘을 구분할 수 없다. + * + * @return void + * + * @scenario resource=identity_message_definition,endpoint=list,templates=zero,observation=response_payload + * @effects identity_definition_zero_template_serializes_as_empty_array + */ + public function test_definition_without_any_template_serializes_templates_as_empty_array(): void + { + $this->makeDefinitionWithChannels('empty', []); + + $row = $this->authRequest() + ->getJson('/api/admin/identity/messages/definitions') + ->assertStatus(200) + ->json('data.data.0'); + + $this->assertArrayHasKey('templates', $row); + $this->assertSame( + [], + $row['templates'], + 'null 은 "로드하지 않음" 을 뜻한다 — 0건은 빈 배열로 구분되어야 한다' + ); + $this->assertSame(0, $row['templates_count']); + } + + /** + * 목록의 대표 템플릿과 단건 조회의 첫 템플릿은 같은 행이다. + * + * 목록은 `firstTemplate`(MIN(id)) 로 대표를 고정하는데 단건의 `templates` 에 정렬이 없으면 + * 첫 항목이 DB 반환 순서(채널 유니크 인덱스 순)를 탄다. 두 응답을 같은 `templates[0]` 계약으로 + * 읽는 소비자는 목록에서 본 것과 다른 템플릿을 열게 된다. + * + * @return void + * + * @scenario resource=identity_message_definition,endpoint=list+detail,observation=response_payload + * @effects identity_definition_list_and_detail_agree_on_representative + */ + public function test_list_representative_matches_detail_first_template(): void + { + // 등록 순서(id)와 채널 사전순이 어긋나도록 만든다 — 정렬이 없으면 두 응답이 갈린다 + $definition = $this->makeDefinitionWithChannels('order', ['sms', 'kakao']); + + $listRow = collect( + $this->authRequest() + ->getJson('/api/admin/identity/messages/definitions') + ->json('data.data') + )->firstWhere('id', $definition->id); + + $detail = $this->authRequest() + ->getJson("/api/admin/identity/messages/definitions/{$definition->id}") + ->assertStatus(200) + ->json('data'); + + $detailIds = collect($detail['templates'])->pluck('id')->all(); + $this->assertSame( + collect($detailIds)->sort()->values()->all(), + $detailIds, + '단건의 templates 는 등록 순서(id)로 정렬되어야 한다' + ); + + $this->assertSame( + $listRow['templates'][0]['id'], + $detail['templates'][0]['id'], + '목록에서 본 대표 템플릿과 단건의 첫 템플릿이 다르면 같은 계약을 읽는 소비자가 갈린다' + ); + } + + /** + * 템플릿 조회 쿼리 수가 정의 수에 비례하지 않는다 (N+1 부재). + * + * @return void + * + * @scenario resource=identity_message_definition,endpoint=list,observation=response_payload + * @effects query_count_does_not_scale_with_row_count + */ + public function test_template_query_count_does_not_grow_with_definition_count(): void + { + $this->makeDefinitionWithChannels('a', ['mail', 'sms']); + + // 권한/설정 캐시를 채워 측정에서 제외한다 + $this->authRequest()->getJson('/api/admin/identity/messages/definitions'); + + $countTemplateQueries = function (): int { + $count = 0; + DB::listen(function ($query) use (&$count) { + if (str_contains($query->sql, 'identity_message_templates')) { + $count++; + } + }); + + $this->authRequest()->getJson('/api/admin/identity/messages/definitions'); + + return $count; + }; + + $withOne = $countTemplateQueries(); + + foreach (['b', 'c', 'd', 'e', 'f'] as $key) { + $this->makeDefinitionWithChannels($key, ['mail', 'sms']); + } + + $withSix = $countTemplateQueries(); + + $this->assertSame( + $withOne, + $withSix, + "정의 1건일 때 {$withOne}회, 6건일 때 {$withSix}회 — 정의 수에 비례하면 N+1 이다" + ); + } +} diff --git a/tests/Feature/Api/Admin/LayoutControllerTest.php b/tests/Feature/Api/Admin/LayoutControllerTest.php index 6cb95a03..7f385c36 100644 --- a/tests/Feature/Api/Admin/LayoutControllerTest.php +++ b/tests/Feature/Api/Admin/LayoutControllerTest.php @@ -2,6 +2,7 @@ namespace Tests\Feature\Api\Admin; +use App\Contracts\Repositories\LayoutRepositoryInterface; use App\Enums\ExtensionOwnerType; use App\Models\Permission; use App\Models\Role; @@ -165,6 +166,7 @@ class LayoutControllerTest extends TestCase 'route_path', 'size', 'size_formatted', + 'has_update', 'lock_version', 'updated_at', ], @@ -174,6 +176,116 @@ class LayoutControllerTest extends TestCase $this->assertCount(3, $response->json('data')); } + /** + * 목록 조회가 본문을 응답에 남기지 않고도 크기를 보고하는지 확인합니다. + * + * 두 가지를 함께 고정한다. ① `getByTemplateId()` 는 `content` 컬럼을 아예 select 하지 않는다 + * — 이 메서드의 남은 소비처는 캐시 무효화뿐인데 본문까지 hydration 하면 템플릿 전체 레이아웃 + * JSON 이 메모리에 올라온다. ② 목록 응답의 크기 표기는 그대로 살아 있다 — 목록 경로 + * (`getListByTemplateId()`)는 본문을 청크로 흘려 읽어 길이만 남기고 본문 자체는 버린다. + * 이 대체 경로가 끊기면 화면의 크기 표기가 전부 `0 B` 가 되므로 본문 프루닝과 한 몸이다. + * + * @effects layout_size_reported_without_reading_content, list_query_does_not_select_content_column + */ + public function test_layout_list_reports_size_without_hydrating_content(): void + { + $layout = TemplateLayout::factory()->create([ + 'template_id' => $this->template->id, + 'name' => 'size-without-content', + 'content' => ['components' => [['type' => 'basic', 'name' => 'Div']]], + ]); + + $rows = app(LayoutRepositoryInterface::class) + ->getByTemplateId($this->template->id); + + $fetched = $rows->firstWhere('id', $layout->id); + + $this->assertNotNull($fetched); + $this->assertArrayNotHasKey('content', $fetched->getAttributes(), '목록 쿼리가 본문을 읽었다'); + + $response = $this->authRequest() + ->getJson("/api/admin/templates/{$this->template->identifier}/layouts"); + + $response->assertStatus(200); + + $row = collect($response->json('data'))->firstWhere('name', 'size-without-content'); + + $this->assertGreaterThan(0, $row['size']); + $this->assertNotSame('0 B', $row['size_formatted']); + } + + /** + * 레이아웃 목록에는 편집 본문과 파싱 결과가 실리지 않는다. + * + * 목록은 파일 선택 트리를 그리는 화면이라 이름·크기·수정일만 쓴다. 본문(`content`)과 + * 파싱 결과(`components`/`data_sources`/`metadata`)까지 함께 내려주면 템플릿의 모든 레이아웃 + * JSON 전문이 한 응답에 담겨, 레이아웃이 수십 개인 템플릿에서는 목록 한 번이 수 MB가 된다. + * 편집 본문은 단건 조회가 공급한다. + * + * @scenario resource=layout,endpoint=list,observation=response_payload + * + * @effects list_omits_layout_content + */ + public function test_layout_list_omits_content_and_parsed_blocks(): void + { + TemplateLayout::factory()->count(2)->create([ + 'template_id' => $this->template->id, + ]); + + $response = $this->authRequest() + ->getJson("/api/admin/templates/{$this->template->identifier}/layouts"); + + $response->assertStatus(200); + + foreach ($response->json('data') as $row) { + foreach (['content', 'components', 'data_sources', 'metadata'] as $heavy) { + $this->assertArrayNotHasKey($heavy, $row, "목록 응답에 {$heavy} 가 실리면 안 된다"); + } + } + } + + /** + * 목록에서 뺀 필드는 단건 조회가 여전히 공급한다 (기능 축소가 아님을 고정). + * + * @scenario resource=layout,endpoint=detail,observation=response_payload + * + * @effects detail_still_returns_full_payload + */ + public function test_layout_detail_still_provides_content_and_parsed_blocks(): void + { + $layout = TemplateLayout::factory()->create([ + 'template_id' => $this->template->id, + 'name' => 'detail-keeps-content', + ]); + + $response = $this->authRequest() + ->getJson("/api/admin/templates/{$this->template->identifier}/layouts/{$layout->name}"); + + $response->assertStatus(200); + + foreach (['content', 'components', 'data_sources', 'metadata'] as $heavy) { + $this->assertArrayHasKey($heavy, $response->json('data'), "단건 응답에는 {$heavy} 가 있어야 한다"); + } + } + + /** + * 화면이 쓰는 크기 표시는 목록에서도 유지된다. + */ + public function test_layout_list_keeps_size_fields_used_by_editor(): void + { + TemplateLayout::factory()->create([ + 'template_id' => $this->template->id, + 'name' => 'size-visible', + ]); + + $response = $this->authRequest() + ->getJson("/api/admin/templates/{$this->template->identifier}/layouts"); + + $response->assertStatus(200); + $this->assertIsInt($response->json('data.0.size')); + $this->assertIsString($response->json('data.0.size_formatted')); + } + /** * 레이아웃 목록 응답에 route_path 필드가 포함된다 (코드 편집기 ?route= 동기화용). * @@ -537,10 +649,6 @@ class LayoutControllerTest extends TestCase 'id', 'layout_id', 'version', - 'endpoint', - 'components', - 'data_sources', - 'metadata', 'changes_summary', 'created_by_name', 'created_at', @@ -551,6 +659,114 @@ class LayoutControllerTest extends TestCase $this->assertCount(3, $response->json('data')); } + /** + * 버전 목록에는 레이아웃 본문이 실리지 않는다. + * + * 버전 행은 저장할 때마다 쌓이며 각 행이 레이아웃 본문 스냅샷을 통째로 갖는다. 목록이 + * 분해된 본문(`components`/`data_sources`/`metadata`/`endpoint`)까지 내려주면 저장을 반복한 + * 레이아웃일수록 목록 응답이 버전 수에 비례해 커진다. 본문은 버전 비교 diff 전용이라 + * 단건 조회가 공급한다. + * + * @scenario resource=layout_version,endpoint=list,observation=response_payload + * + * @effects list_omits_layout_content + */ + public function test_version_list_omits_layout_body(): void + { + $layout = TemplateLayout::factory()->create([ + 'template_id' => $this->template->id, + 'name' => 'versions-light', + ]); + + TemplateLayoutVersion::factory()->count(2)->create([ + 'layout_id' => $layout->id, + ]); + + $response = $this->authRequest() + ->getJson("/api/admin/templates/{$this->template->identifier}/layouts/{$layout->name}/versions"); + + $response->assertStatus(200); + + foreach ($response->json('data') as $row) { + foreach (['content', 'full_content', 'components', 'data_sources', 'metadata', 'endpoint'] as $heavy) { + $this->assertArrayNotHasKey($heavy, $row, "버전 목록에 {$heavy} 가 실리면 안 된다"); + } + } + } + + /** + * 목록 배열이 조건부 필드를 빈 객체로 흘리지 않는지 확인합니다. + * + * `toListArray()` 결과는 컨트롤러가 그대로 응답에 싣기 때문에 Laravel 의 MissingValue 제거 + * 단계를 거치지 않는다. 버전 모델은 `UPDATED_AT` 이 없어 `updated_at` 이 미충족 상태인데, + * 걸러내지 않으면 `"updated_at": {}` 라는 빈 객체가 응답에 남는다. + * + * @effects list_payload_has_no_empty_object_fields + */ + public function test_version_list_does_not_leak_unfulfilled_conditional_fields(): void + { + $layout = TemplateLayout::factory()->create([ + 'template_id' => $this->template->id, + 'name' => 'versions-missing-value', + ]); + + TemplateLayoutVersion::factory()->create(['layout_id' => $layout->id]); + + $response = $this->authRequest() + ->getJson("/api/admin/templates/{$this->template->identifier}/layouts/{$layout->name}/versions"); + + $response->assertStatus(200); + + foreach ($response->json('data') as $row) { + foreach ($row as $key => $value) { + $this->assertNotSame([], $value, "{$key} 가 미충족 조건부 필드로 빈 값이 실렸다"); + } + } + } + + /** + * 버전 목록 조회 건수에는 상한이 있다. + * + * 버전 행은 정리(pruning)되지 않고 무한히 쌓이므로, 상한이 없으면 오래 편집한 레이아웃 + * 하나가 수천 건을 한 응답에 담게 된다. + * + * @effects version_list_request_carries_limit + */ + public function test_version_list_is_capped_and_accepts_limit(): void + { + $layout = TemplateLayout::factory()->create([ + 'template_id' => $this->template->id, + 'name' => 'versions-capped', + ]); + + TemplateLayoutVersion::factory()->count(5)->create([ + 'layout_id' => $layout->id, + ]); + + $url = "/api/admin/templates/{$this->template->identifier}/layouts/{$layout->name}/versions"; + + // limit 지정 시 그 수만큼만, 최신순으로 + $limited = $this->authRequest()->getJson($url.'?limit=2'); + $limited->assertStatus(200); + $this->assertCount(2, $limited->json('data')); + + // 최신순 — 상한이 걸려도 잘려 나가는 것은 오래된 쪽이어야 한다 + $versions = array_column($limited->json('data'), 'version'); + $sortedDesc = $versions; + rsort($sortedDesc); + $this->assertSame($sortedDesc, $versions, '버전 목록은 최신순이어야 한다'); + + // 상한을 넘는 limit 은 거부된다 + $this->authRequest()->getJson($url.'?limit=100000') + ->assertStatus(422) + ->assertJsonValidationErrors('limit'); + + // 미지정이면 기본 상한으로 전체(5건)가 나온다 + $this->authRequest()->getJson($url) + ->assertStatus(200) + ->assertJsonCount(5, 'data'); + } + /** * 버전 목록에 저장자 이름(created_by_name)이 노출되되 created_by ID 는 제외 * @@ -637,6 +853,10 @@ class LayoutControllerTest extends TestCase /** * 특정 버전 조회 성공 + * + * @scenario resource=layout_version,endpoint=detail,observation=response_payload + * + * @effects detail_still_returns_full_payload */ public function test_can_show_specific_version(): void { @@ -696,6 +916,35 @@ class LayoutControllerTest extends TestCase ); } + /** + * 단건 버전 조회는 목록 상한 밖의 오래된 버전에도 도달한다 + * + * 목록은 건수 상한이 있는 경량 조회다. 단건이 그 조회를 재사용하면 상한 밖의 오래된 + * 버전이 404 가 된다 — 단건은 반드시 버전 번호로 직접 조회해야 한다. + * + * @effects version_detail_reaches_rows_beyond_list_cap + */ + public function test_show_version_reaches_versions_beyond_the_list_cap(): void + { + $layout = TemplateLayout::factory()->create([ + 'template_id' => $this->template->id, + 'name' => 'deep-history-layout', + ]); + + // 버전 1 = 가장 오래된 버전. 최신순 상한(기본 100건) 안에 들지 못한다. + for ($version = 1; $version <= 101; $version++) { + TemplateLayoutVersion::factory()->create([ + 'layout_id' => $layout->id, + 'version' => $version, + ]); + } + + $this->authRequest() + ->getJson("/api/admin/templates/{$this->template->identifier}/layouts/{$layout->name}/versions/1") + ->assertStatus(200) + ->assertJsonPath('data.version', 1); + } + /** * 버전 목록(index)에는 full_content 를 포함하지 않는다 (페이로드 비대화 회피) */ diff --git a/tests/Feature/Api/Admin/LayoutExtensionControllerTest.php b/tests/Feature/Api/Admin/LayoutExtensionControllerTest.php index 4c0d46fe..b2071b98 100644 --- a/tests/Feature/Api/Admin/LayoutExtensionControllerTest.php +++ b/tests/Feature/Api/Admin/LayoutExtensionControllerTest.php @@ -272,6 +272,178 @@ class LayoutExtensionControllerTest extends TestCase $this->assertSame('template', $slotExtensions->first()['source_type']); } + /** + * 확장 버전 목록도 본문을 싣지 않고 조회 건수 상한을 따른다. + * + * 레이아웃 본체와 같은 정책 — 버전 행은 저장할 때마다 쌓이고 정리되지 않는다. + */ + public function test_extension_version_list_omits_body_and_honors_limit(): void + { + $extension = $this->makeExtensionPoint(); + + TemplateLayoutExtensionVersion::factory()->count(4)->sequence( + ['version' => 1], ['version' => 2], ['version' => 3], ['version' => 4], + )->create(['extension_id' => $extension->id]); + + $url = "/api/admin/templates/{$this->template->identifier}/layout-extensions/{$extension->id}/versions"; + + $response = $this->authRequest()->getJson($url); + $response->assertStatus(200); + + foreach ($response->json('data') as $row) { + $this->assertArrayNotHasKey('content', $row, '버전 목록에 확장 본문이 실리면 안 된다'); + $this->assertArrayHasKey('version', $row); + $this->assertArrayHasKey('changes_summary', $row); + } + + // limit 지정 시 그 수만큼, 최신순 + $limited = $this->authRequest()->getJson($url.'?limit=2'); + $limited->assertStatus(200); + $this->assertCount(2, $limited->json('data')); + $this->assertSame([4, 3], array_column($limited->json('data'), 'version')); + + // 상한을 넘는 limit 은 거부 + $this->authRequest()->getJson($url.'?limit=100000') + ->assertStatus(422) + ->assertJsonValidationErrors('limit'); + } + + /** + * index - 확장 목록에는 편집 본문이 실리지 않는다. + * + * 목록은 라우트 트리에 "어느 화면에 어떤 확장이 붙어 있는가" 를 그리는 화면이다. 본문까지 + * 함께 내려주면 템플릿에 등록된 모든 확장의 JSON 전문이 한 응답에 담긴다. 본문은 단건 + * 조회가 공급한다. + * + * @scenario resource=layout_extension,endpoint=list,observation=response_payload + * + * @effects list_omits_extension_content + */ + public function test_index_omits_extension_content(): void + { + LayoutExtension::factory()->create([ + 'template_id' => $this->template->id, + 'extension_type' => LayoutExtensionType::Overlay, + 'target_name' => 'admin_user_detail', + 'source_type' => LayoutSourceType::Module, + 'source_identifier' => 'sirsoft-board', + 'priority' => 100, + 'is_active' => true, + ]); + + $response = $this->authRequest() + ->getJson("/api/admin/templates/{$this->template->identifier}/layout-extensions"); + + $response->assertStatus(200); + + $extensions = collect($response->json('data'))->flatMap(fn ($g) => $g['extensions']); + + $this->assertGreaterThan(0, $extensions->count(), '검사할 확장이 있어야 한다'); + + foreach ($extensions as $extension) { + $this->assertArrayNotHasKey('content', $extension, '목록 응답에 content 가 실리면 안 된다'); + } + } + + /** + * index - 라우트 트리가 쓰는 표시 필드는 목록에 그대로 남는다 (기능 축소 아님). + */ + public function test_index_keeps_fields_used_by_route_tree(): void + { + LayoutExtension::factory()->create([ + 'template_id' => $this->template->id, + 'extension_type' => LayoutExtensionType::Overlay, + 'target_name' => 'admin_user_detail', + 'source_type' => LayoutSourceType::Module, + 'source_identifier' => 'sirsoft-board', + 'priority' => 100, + 'is_active' => true, + ]); + + $response = $this->authRequest() + ->getJson("/api/admin/templates/{$this->template->identifier}/layout-extensions"); + + $response->assertStatus(200); + + $extension = collect($response->json('data'))->flatMap(fn ($g) => $g['extensions'])->first(); + + $this->assertNotNull($extension); + + foreach ([ + 'id', 'extension_type', 'target_name', 'source_identifier', 'source_label', + 'is_override', 'priority', 'is_active', 'is_modified', 'host_layouts', 'current_version', + // 목록 화면이 확장마다 크기를 표시한다 — 빼면 전 행이 '0 B' 로 보인다 + 'size', 'size_formatted', + ] as $field) { + $this->assertArrayHasKey($field, $extension, "라우트 트리가 쓰는 {$field} 가 사라졌다"); + } + } + + /** + * 목록의 크기 표기가 실제 본문 크기를 반영하는지 확인합니다. + * + * 본문(`content`)은 목록에서 빠지지만 크기는 서버가 계산해 내려준다. 키만 있고 값이 0 이면 + * 화면에는 모든 확장이 `0 B` 로 나타나므로, 키 존재만으로는 회귀를 잡지 못한다. + * + * @effects extension_size_matches_actual_content_length + */ + public function test_index_reports_actual_content_size(): void + { + LayoutExtension::factory()->create([ + 'template_id' => $this->template->id, + 'extension_type' => LayoutExtensionType::Overlay, + 'target_name' => 'admin_user_detail', + 'source_type' => LayoutSourceType::Module, + 'source_identifier' => 'sirsoft-board', + 'content' => ['components' => [['type' => 'basic', 'name' => 'Div']]], + 'priority' => 100, + 'is_active' => true, + ]); + + $response = $this->authRequest() + ->getJson("/api/admin/templates/{$this->template->identifier}/layout-extensions"); + + $response->assertStatus(200); + + $extension = collect($response->json('data'))->flatMap(fn ($g) => $g['extensions'])->first(); + + $this->assertGreaterThan(0, $extension['size']); + $this->assertNotSame('0 B', $extension['size_formatted']); + } + + /** + * 목록에서 뺀 본문은 단건 조회가 여전히 공급한다. + * + * @scenario resource=layout_extension,endpoint=detail,observation=response_payload + * + * @effects detail_still_returns_full_payload + */ + public function test_show_still_provides_extension_content(): void + { + LayoutExtension::factory()->create([ + 'template_id' => $this->template->id, + 'extension_type' => LayoutExtensionType::Overlay, + 'target_name' => 'admin_user_detail', + 'source_type' => LayoutSourceType::Module, + 'source_identifier' => 'sirsoft-board', + 'priority' => 100, + 'is_active' => true, + ]); + + $response = $this->authRequest() + ->getJson("/api/admin/templates/{$this->template->identifier}/layout-extensions"); + $response->assertStatus(200); + + $extensionId = collect($response->json('data'))->flatMap(fn ($g) => $g['extensions'])->first()['id']; + + $detail = $this->authRequest() + ->getJson("/api/admin/templates/{$this->template->identifier}/layout-extensions/{$extensionId}"); + + $detail->assertStatus(200); + $this->assertArrayHasKey('content', $detail->json('data')); + $this->assertArrayHasKey('lock_version', $detail->json('data')); + } + /** * index - 권한 없는 사용자 403 */ @@ -546,6 +718,60 @@ class LayoutExtensionControllerTest extends TestCase ->assertJsonPath('data.version', 1); } + /** + * showVersion - 단건 조회는 확장 본문(content)을 공급한다 + * + * 목록에서 content 를 뺀 대신 단건이 대체 경로가 된다. 단건이 목록용 조회를 재사용하면 + * content 가 조회되지 않아 버전 비교 diff 가 "전 항목 삭제"로 표시된다. + * + * @effects version_detail_still_returns_content + */ + public function test_show_version_still_provides_extension_content(): void + { + $content = ['extension_point' => 'header', 'components' => [['type' => 'basic', 'name' => 'Span']]]; + $extension = $this->makeExtensionPoint(); + TemplateLayoutExtensionVersion::factory()->create([ + 'extension_id' => $extension->id, + 'version' => 1, + 'content' => $content, + ]); + + $response = $this->authRequest() + ->getJson("/api/admin/templates/{$this->template->identifier}/layout-extensions/{$extension->id}/versions/1"); + + $response->assertStatus(200); + + $payload = $response->json('data.content'); + $this->assertIsString($payload, '단건 버전 응답의 content 는 JSON 문자열이어야 한다'); + $this->assertNotSame('[]', $payload, '단건 버전 조회가 확장 본문을 잃으면 버전 비교가 전 항목 삭제로 표시된다'); + $this->assertSame($content, json_decode($payload, true)); + } + + /** + * showVersion - 목록 상한 밖의 오래된 버전도 단건으로 조회된다 + * + * 단건이 목록용 조회(상한 100건)를 재사용하면 101번째 이전 버전이 404 가 된다. + * + * @effects version_detail_reaches_rows_beyond_list_cap + */ + public function test_show_version_reaches_versions_beyond_the_list_cap(): void + { + $extension = $this->makeExtensionPoint(); + + // 버전 1 = 가장 오래된 버전. 최신순 상한 100건 안에 들지 못한다. + for ($version = 1; $version <= 101; $version++) { + TemplateLayoutExtensionVersion::factory()->create([ + 'extension_id' => $extension->id, + 'version' => $version, + ]); + } + + $this->authRequest() + ->getJson("/api/admin/templates/{$this->template->identifier}/layout-extensions/{$extension->id}/versions/1") + ->assertStatus(200) + ->assertJsonPath('data.version', 1); + } + /** * restoreVersion - content 복원 + 새 버전 생성 */ diff --git a/tests/Feature/Api/Admin/MenuControllerTest.php b/tests/Feature/Api/Admin/MenuControllerTest.php index 8198c5c6..c2f40c34 100644 --- a/tests/Feature/Api/Admin/MenuControllerTest.php +++ b/tests/Feature/Api/Admin/MenuControllerTest.php @@ -35,7 +35,7 @@ class MenuControllerTest extends TestCase /** * 관리자 역할 생성 및 할당 * - * @param array $permissions 사용자에게 부여할 권한 식별자 목록 + * @param array $permissions 사용자에게 부여할 권한 식별자 목록 */ private function createAdminUser(array $permissions = ['core.menus.read', 'core.menus.create', 'core.menus.update', 'core.menus.delete']): User { @@ -129,7 +129,7 @@ class MenuControllerTest extends TestCase * abilities 제한 테스트에는 admin이 아닌 역할을 사용합니다. * isAdmin()은 type='admin'인 권한이 있는 역할이면 통과합니다. * - * @param array $permissions 사용자에게 부여할 권한 식별자 목록 + * @param array $permissions 사용자에게 부여할 권한 식별자 목록 * @return User 생성된 사용자 */ private function createNonAdminUser(array $permissions = []): User @@ -493,6 +493,10 @@ class MenuControllerTest extends TestCase /** * 메뉴 상세 조회 성공 (관계 포함) + * + * @scenario resource=menu,endpoint=detail,observation=response_payload + * + * @effects detail_still_returns_full_payload */ public function test_show_returns_menu_with_relations(): void { diff --git a/tests/Feature/Api/Admin/NotificationDefinitionListPayloadTest.php b/tests/Feature/Api/Admin/NotificationDefinitionListPayloadTest.php new file mode 100644 index 00000000..dac7599b --- /dev/null +++ b/tests/Feature/Api/Admin/NotificationDefinitionListPayloadTest.php @@ -0,0 +1,292 @@ +admin = $this->createAdminWithPermissions(); + $this->token = $this->admin->createToken('test-token')->plainTextToken; + } + + /** + * 인증 헤더가 붙은 요청 빌더를 반환합니다. + * + * @return static + */ + private function authRequest(): static + { + return $this->withHeaders([ + 'Authorization' => 'Bearer '.$this->token, + 'Accept' => 'application/json', + ]); + } + + /** + * core.settings 권한을 가진 관리자를 만듭니다. + * + * @return User 생성된 관리자 + */ + private function createAdminWithPermissions(): User + { + $user = User::factory()->create(); + + $role = Role::create([ + 'name' => ['ko' => '최고관리자'], + 'identifier' => 'super_admin', + 'is_admin' => true, + ]); + + foreach (['core.settings.read', 'core.settings.update'] as $identifier) { + $perm = Permission::firstOrCreate( + ['identifier' => $identifier], + ['name' => ['ko' => $identifier], 'type' => 'admin', 'order' => 1] + ); + $role->permissions()->attach($perm->id); + } + + $user->roles()->attach($role->id); + + return $user; + } + + /** + * 여러 채널 템플릿을 가진 알림 정의를 만듭니다. + * + * @param string $type 정의 타입 + * @param array $channels 만들 채널 목록 + * @param array $customizedChannels is_default=false 로 만들 채널 목록 + * @return NotificationDefinition 생성된 정의 + */ + private function makeDefinition(string $type, array $channels, array $customizedChannels = []): NotificationDefinition + { + $definition = NotificationDefinition::create([ + 'type' => $type, + 'hook_prefix' => 'core.test', + 'extension_type' => 'core', + 'extension_identifier' => 'core', + 'name' => ['ko' => $type], + 'channels' => $channels, + 'hooks' => [], + ]); + + foreach ($channels as $channel) { + NotificationTemplate::create([ + 'definition_id' => $definition->id, + 'channel' => $channel, + 'subject' => ['ko' => "제목-{$type}-{$channel}"], + 'body' => ['ko' => str_repeat("본문-{$channel} ", 50)], + 'is_default' => ! in_array($channel, $customizedChannels, true), + ]); + } + + return $definition; + } + + /** + * template_channel 을 주면 그 채널의 템플릿만 실린다. + * + * @return void + * + * @scenario resource=notification_definition,endpoint=list,observation=response_payload + * @effects notification_list_carries_only_requested_channel_template + */ + public function test_list_carries_only_the_requested_channel_template(): void + { + $this->makeDefinition('welcome', ['mail', 'sms', 'push']); + $this->makeDefinition('reset', ['mail', 'sms']); + + $rows = $this->authRequest() + ->getJson('/api/admin/notification-definitions?template_channel=mail') + ->assertStatus(200) + ->json('data.data'); + + $this->assertCount(2, $rows); + + foreach ($rows as $row) { + $this->assertCount( + 1, + $row['templates'], + '요청한 채널 외의 제목·본문이 실리면 한 페이지를 여는 것만으로 전 채널 본문이 전송된다' + ); + $this->assertSame('mail', $row['templates'][0]['channel']); + } + } + + /** + * template_channel 은 정의 행을 거르지 않는다 (channel 필터와의 차이). + * + * 그 채널 템플릿이 없는 정의도 행은 남아 "채널 미설정" 안내를 띄울 수 있어야 한다. + * + * @return void + * + * @scenario resource=notification_definition,endpoint=list,observation=response_payload + * @effects notification_list_carries_only_requested_channel_template + */ + public function test_template_channel_does_not_filter_out_rows(): void + { + $this->makeDefinition('mail_only', ['mail']); + $this->makeDefinition('sms_only', ['sms']); + + $rows = $this->authRequest() + ->getJson('/api/admin/notification-definitions?template_channel=mail') + ->json('data.data'); + + $this->assertCount(2, $rows, '행 자체는 남아야 한다 — 거르는 것은 channel 파라미터의 역할이다'); + + $byType = collect($rows)->keyBy('type'); + $this->assertCount(1, $byType['mail_only']['templates']); + $this->assertCount(0, $byType['sms_only']['templates'], '요청 채널 템플릿이 없으면 빈 배열'); + } + + /** + * template_channel 이 없으면 템플릿 본문을 아예 싣지 않는다. + * + * 채널을 지정하지 않은 호출자에게 전 채널 본문을 안기면, 한 페이지를 여는 것만으로 + * 정의 수 × 채널 × 로케일만큼의 제목·본문이 응답에 실린다. 전 채널이 필요하면 단건 + * 조회가 제공한다. + * + * @return void + * + * @scenario resource=notification_definition,endpoint=list,observation=response_payload + * @effects notification_list_without_channel_omits_templates + */ + public function test_list_without_template_channel_omits_templates(): void + { + $this->makeDefinition('welcome', ['mail', 'sms', 'push']); + + $row = $this->authRequest() + ->getJson('/api/admin/notification-definitions') + ->json('data.data.0'); + + $this->assertArrayNotHasKey( + 'templates', + $row, + '채널을 지정하지 않으면 템플릿 본문을 싣지 않는다 — 빈 배열도 아니고 키 자체가 없다' + ); + + // 되돌리기 버튼 판정은 템플릿 배열 없이도 성립해야 한다 + $this->assertArrayHasKey('has_customized_templates', $row); + } + + /** + * 커스터마이즈 여부는 요청 채널과 무관하게 정확하다. + * + * 되돌리기 버튼 노출 조건이므로, 다른 채널만 수정한 경우에도 참이어야 한다. + * 템플릿 배열만 훑으면 이 경우를 놓친다. + * + * @return void + * + * @scenario resource=notification_definition,endpoint=list,observation=response_payload + * @effects notification_customized_flag_spans_every_channel + */ + public function test_customized_flag_spans_every_channel_not_just_the_requested_one(): void + { + // sms 만 커스터마이즈 — mail 채널로 조회해도 "수정됨" 이어야 한다 + $this->makeDefinition('partly_customized', ['mail', 'sms'], ['sms']); + $this->makeDefinition('untouched', ['mail', 'sms']); + + $rows = $this->authRequest() + ->getJson('/api/admin/notification-definitions?template_channel=mail') + ->json('data.data'); + + $byType = collect($rows)->keyBy('type'); + + $this->assertTrue( + $byType['partly_customized']['has_customized_templates'], + '다른 채널만 수정된 경우를 놓치면 되돌리기 버튼이 사라진다' + ); + $this->assertFalse($byType['untouched']['has_customized_templates']); + } + + /** + * 템플릿 조회 쿼리 수가 정의 수에 비례하지 않는다 (N+1 부재). + * + * @return void + * + * @scenario resource=notification_definition,endpoint=list,observation=response_payload + * @effects query_count_does_not_scale_with_row_count + */ + public function test_template_query_count_does_not_grow_with_definition_count(): void + { + $this->makeDefinition('first', ['mail', 'sms']); + + // 권한/설정 캐시를 채워 측정에서 제외 + $this->authRequest()->getJson('/api/admin/notification-definitions?template_channel=mail'); + + $countTemplateQueries = function (): int { + $count = 0; + DB::listen(function ($query) use (&$count) { + if (str_contains($query->sql, 'notification_templates')) { + $count++; + } + }); + + $this->authRequest()->getJson('/api/admin/notification-definitions?template_channel=mail'); + + return $count; + }; + + $withOne = $countTemplateQueries(); + + foreach (['b', 'c', 'd', 'e', 'f'] as $type) { + $this->makeDefinition($type, ['mail', 'sms']); + } + + $withSix = $countTemplateQueries(); + + $this->assertSame( + $withOne, + $withSix, + "정의 1건일 때 {$withOne}회, 6건일 때 {$withSix}회 — 정의 수에 비례하면 N+1 이다" + ); + } + + /** + * 단건 조회는 전체 템플릿을 그대로 공급한다 (대체 경로 보존). + * + * @return void + * + * @scenario resource=notification_definition,endpoint=detail,observation=response_payload + * @effects notification_detail_returns_every_channel_template, detail_still_returns_full_payload + */ + public function test_detail_still_returns_every_channel_template(): void + { + $definition = $this->makeDefinition('welcome', ['mail', 'sms', 'push']); + + $row = $this->authRequest() + ->getJson("/api/admin/notification-definitions/{$definition->id}") + ->assertStatus(200) + ->json('data'); + + $this->assertCount(3, $row['templates']); + } +} diff --git a/tests/Feature/Api/Admin/RoleControllerTest.php b/tests/Feature/Api/Admin/RoleControllerTest.php index 874184a7..0c243663 100644 --- a/tests/Feature/Api/Admin/RoleControllerTest.php +++ b/tests/Feature/Api/Admin/RoleControllerTest.php @@ -33,7 +33,7 @@ class RoleControllerTest extends TestCase /** * 관리자 역할 생성 및 할당 * - * @param array $permissions 사용자에게 부여할 권한 식별자 목록 + * @param array $permissions 사용자에게 부여할 권한 식별자 목록 */ private function createAdminUser(array $permissions = ['core.permissions.read', 'core.permissions.create', 'core.permissions.update', 'core.permissions.delete', 'core.users.read']): User { @@ -119,7 +119,7 @@ class RoleControllerTest extends TestCase /** * 테스트용 권한 생성 헬퍼 */ - private function createTestPermission(string $identifier = null): Permission + private function createTestPermission(?string $identifier = null): Permission { return Permission::create([ 'identifier' => $identifier ?? 'test.permission.'.uniqid(), @@ -342,6 +342,11 @@ class RoleControllerTest extends TestCase /** * 역할 상세 조회 성공 (권한 포함) + * + * 목록에서 뺀 권한 집합이 상세에는 그대로 남아 있어야 한다 — 이 계약이 깨지면 권한 편집 + * 화면이 빈 채로 열린다. + * + * @effects detail_still_returns_full_payload */ public function test_show_returns_role_with_permissions(): void { diff --git a/tests/Feature/Api/Admin/TemplateUpdateTest.php b/tests/Feature/Api/Admin/TemplateUpdateTest.php index 09ebd214..84592fd4 100644 --- a/tests/Feature/Api/Admin/TemplateUpdateTest.php +++ b/tests/Feature/Api/Admin/TemplateUpdateTest.php @@ -452,6 +452,11 @@ class TemplateUpdateTest extends TestCase public function test_check_modified_layouts_returns_result(): void { $mockService = Mockery::mock(TemplateService::class); + // 컨트롤러는 조회 전에 식별자 존재를 먼저 확인한다 — 이 선행 호출을 스텁하지 않으면 + // Mockery 가 미정의 호출로 예외를 던지고 그것이 500 으로 잡힌다. + $mockService->shouldReceive('getTemplateInfo') + ->with('test-template') + ->andReturn(['identifier' => 'test-template', 'name' => 'Test Template']); $mockService->shouldReceive('checkModifiedLayouts') ->with('test-template') ->once() diff --git a/tests/Playwright/specs/admin/menu-url-entry-restore.spec.ts b/tests/Playwright/specs/admin/menu-url-entry-restore.spec.ts new file mode 100644 index 00000000..8867c13a --- /dev/null +++ b/tests/Playwright/specs/admin/menu-url-entry-restore.spec.ts @@ -0,0 +1,126 @@ +/** + * 메뉴 관리 — URL 직접 진입 시 선택/편집 상태 복원 (#518 / 공개 #76) + * + * 결함: `initMenuFromUrl` 이 목록 데이터소스에서 행 배열을 꺼낼 때 `dataSource.data` 가 배열이라고 + * 가정했는데, 실제 형태는 응답 envelope 를 그대로 담은 `{success, message, data:{data:[...]}}` 다. + * 그래서 이 핸들러는 3초를 대기하다 조용히 종료했고, `?menu=...&mode=edit` 로 들어와도 패널이 + * 열리지 않았다. 예외도 콘솔 경고도 없어 화면만 비어 있었다. + * + * 하위 메뉴는 목록 응답이 역할을 싣지 않으므로(#76 프루닝), 이 경로가 죽어 있으면 URL 로 들어와 + * 저장할 때 역할이 통째로 해제될 수 있다 — 그래서 브라우저에서 고정한다. + * + * @scenario surface=admin_menu_list,entry=url_direct + * @effects menu_url_entry_restores_selection, menu_url_entry_seeds_roles + */ +import { test, expect, authenticatePage, issueToken } from '../../fixtures/auth'; + +const MENU_API = '/api/admin/menus'; + +test.describe('메뉴 관리 URL 직접 진입 복원 (#76)', () => { + test('하위 메뉴 slug 로 들어오면 편집 패널이 열리고 역할이 폼에 시드된다', async ({ page }) => { + const token = issueToken('core.menus.read', 'core.menus.update'); + await authenticatePage(page, token); + + // 역할이 걸린 하위 메뉴를 찾는다 (단건 조회가 역할을 공급하는지까지 봐야 하므로 필수 조건). + await page.goto('/admin/menus'); + await page.waitForLoadState('domcontentloaded', { timeout: 30_000 }); + + const target = await page.evaluate(async (api) => { + const res = await fetch(`${api}?with_children=true&hierarchical=true`, { + headers: { + Authorization: `Bearer ${localStorage.getItem('auth_token')}`, + Accept: 'application/json', + }, + }); + const body = await res.json(); + const parents = body?.data?.data ?? []; + + for (const parent of parents) { + for (const child of parent.children ?? []) { + const detailRes = await fetch(`${api}/${child.id}`, { + headers: { + Authorization: `Bearer ${localStorage.getItem('auth_token')}`, + Accept: 'application/json', + }, + }); + const detail = await detailRes.json(); + const roleIds = (detail?.data?.roles ?? []).map((role: any) => role.id); + if (roleIds.length > 0) { + return { slug: child.slug, id: child.id, roleIds }; + } + } + } + + return null; + }, MENU_API); + + test.skip(!target, '역할이 걸린 하위 메뉴가 없다'); + + await page.goto(`/admin/menus?menu=${encodeURIComponent(target!.slug)}&mode=edit`); + await page.waitForLoadState('domcontentloaded', { timeout: 30_000 }); + + // 존재를 먼저 확정한 뒤 값을 단언한다. + await expect + .poll( + async () => page.evaluate(() => (window as any).G7Core?.state?.get?.()?.panelMode ?? null), + { timeout: 20_000 }, + ) + .toBe('edit'); + + const state = await page.evaluate(() => { + const st = (window as any).G7Core?.state?.get?.() ?? {}; + return { + selectedId: st.selectedMenu?.id ?? null, + formRoles: st.formData?.roles ?? null, + }; + }); + + expect(state.selectedId).toBe(target!.id); + // 목록 응답에는 하위 메뉴의 역할이 없다 — 단건 보강이 동작해야만 채워진다. + expect(state.formRoles).toEqual(target!.roleIds); + }); + + test('편집 패널의 접근 역할 칩이 화면에 렌더된다', async ({ page }) => { + const token = issueToken('core.menus.read', 'core.menus.update'); + await authenticatePage(page, token); + + await page.goto('/admin/menus'); + await page.waitForLoadState('domcontentloaded', { timeout: 30_000 }); + + const target = await page.evaluate(async (api) => { + const res = await fetch(`${api}?with_children=true&hierarchical=true`, { + headers: { + Authorization: `Bearer ${localStorage.getItem('auth_token')}`, + Accept: 'application/json', + }, + }); + const body = await res.json(); + for (const parent of body?.data?.data ?? []) { + for (const child of parent.children ?? []) { + const detailRes = await fetch(`${api}/${child.id}`, { + headers: { + Authorization: `Bearer ${localStorage.getItem('auth_token')}`, + Accept: 'application/json', + }, + }); + const detail = await detailRes.json(); + const roles = detail?.data?.roles ?? []; + if (roles.length > 0) { + return { slug: child.slug, roleName: roles[0]?.name?.ko ?? roles[0]?.name }; + } + } + } + return null; + }, MENU_API); + + test.skip(!target, '역할이 걸린 하위 메뉴가 없다'); + + await page.goto(`/admin/menus?menu=${encodeURIComponent(target!.slug)}&mode=edit`); + await page.waitForLoadState('domcontentloaded', { timeout: 30_000 }); + + await expect(page.getByText('접근 역할')).toBeVisible({ timeout: 20_000 }); + await expect(page.getByText(String(target!.roleName), { exact: true }).first()).toBeVisible({ + timeout: 20_000, + }); + }); +}); diff --git a/tests/Playwright/specs/admin/notification-definition-channel-roundtrip.spec.ts b/tests/Playwright/specs/admin/notification-definition-channel-roundtrip.spec.ts new file mode 100644 index 00000000..e5082770 --- /dev/null +++ b/tests/Playwright/specs/admin/notification-definition-channel-roundtrip.spec.ts @@ -0,0 +1,222 @@ +/** + * E2E: 알림 정의 채널 탭 왕복 후 목록이 다른 채널을 보게 되는 결함 (#518) + * + * 배경 (3차 실측 결함 #1): + * 채널은 두 곳에서 읽히고 있었다 — URL(`query.channel`)과 페이지 로컬 상태 + * (`_local.activeNotificationChannel`). 채널 탭을 클릭하면 둘 다 갱신되므로 그 순간에는 일치한다. + * 그러나 다른 설정 탭에 갔다 돌아오면 URL 의 channel 은 사라지고(탭 전환은 목록 상태를 승계하지 + * 않는다) 로컬 상태만 이전 채널로 남는다. 그 사이 목록은 기본 채널(mail)로 다시 불려 오고, + * 화면은 남아 있는 이전 채널로 템플릿을 찾는다. + * + * 결과: 정의 행은 정상으로 그려지지만 **행 안의 제목 줄과 「수신자:」 줄만** + * 「이 채널에 대한 템플릿이 없습니다」 로 바뀐다. 예외도 콘솔 에러도 4xx/5xx 도 없다. + * + * 단위 테스트로는 잡히지 않는다 — 두 값이 어긋나는 것은 렌더 시점 차이 때문이고, 종전 표현식은 + * 데이터소스와 소비자에 **글자까지 같은 식**이 적혀 있어 정적 대조로는 동일해 보였다. + * + * 이 스펙은 채널을 URL 단일 출처로 좁힌 뒤의 계약을 고정한다: + * "마지막으로 요청한 template_channel" 과 "화면이 활성으로 표시하는 채널" 이 항상 같다. + * + * @effects notification_channel_tab_roundtrip_keeps_data_and_screen_in_sync, + * notification_channel_switch_requests_that_channel, + * notification_channel_roundtrip_emits_no_console_errors + */ +import { test, expect, issueToken, authenticatePage } from '../../fixtures/auth'; +import type { Page } from '@playwright/test'; + +const DEFINITIONS_TAB = '/admin/settings?tab=notification_definitions'; + +const SETTINGS_PERMISSIONS = [ + 'core.settings.read', + 'core.settings.update', + 'core.admin.notifications.read', +] as const; + +/** 채널 탭 표시명 (lang/ko/notification.php) */ +const CHANNEL_MAIL = '메일'; +const CHANNEL_DATABASE = '사이트내 알림'; + +/** 설정 탭 표시명 (templates/.../lang/partial/ko/admin.json) */ +const TAB_NOTIFICATION = '알림 설정'; +const TAB_IDENTITY = '본인인증'; + +/** + * 알림 정의 목록 요청의 `template_channel` 값을 순서대로 수집합니다. + * + * 화면이 무엇을 보여주는지와 서버가 무엇을 보냈는지를 대조하려면 실제 요청을 봐야 한다 — + * 화면만 보면 "템플릿이 없다" 가 데이터 탓인지 표시 탓인지 구분되지 않는다. + * + * @param page 대상 페이지 + * @returns 수집된 채널 값 배열 (호출 시점까지 누적) + */ +function trackRequestedChannels(page: Page): string[] { + const channels: string[] = []; + + page.on('request', request => { + const url = request.url(); + + if (!url.includes('/api/admin/notification-definitions')) { + return; + } + + const value = new URL(url).searchParams.get('template_channel'); + + if (value) { + channels.push(value); + } + }); + + return channels; +} + +/** + * 알림 정의 탭에 진입해 정의 목록이 실제로 그려질 때까지 기다립니다. + * + * @param page 대상 페이지 + */ +async function gotoDefinitionsTab(page: Page): Promise { + await page.goto(DEFINITIONS_TAB); + await page.waitForLoadState('domcontentloaded', { timeout: 30_000 }); + + await expect(channelTab(page, CHANNEL_MAIL)).toBeVisible({ timeout: 20_000 }); + await expect(channelTab(page, CHANNEL_DATABASE)).toBeVisible({ timeout: 20_000 }); +} + +/** + * 채널 하위 탭 버튼 locator 를 만듭니다. + * + * 접근성 이름에는 아이콘 alt 가 앞에 붙는다(예: `fas fa envelope 메일`). `exact` 로 잡으면 + * 요소를 찾지 못하므로 이름 끝을 정규식으로 맞춘다. + * + * @param page 대상 페이지 + * @param label 채널 표시명 + * @returns 채널 탭 버튼 locator + */ +function channelTab(page: Page, label: string) { + return page.getByRole('button', { name: new RegExp(`${label}$`) }); +} + +/** + * 현재 화면이 활성으로 표시 중인 채널 탭의 이름을 읽습니다. + * + * 활성 표시는 `border-b-2 border-indigo-500` 계열 클래스로만 구분된다. 설정 탭(TabNavigation)도 + * 같은 색 계열을 쓸 수 있으므로 전체 버튼을 훑지 않고 **채널 후보 두 개만** 검사한다. + * + * @param page 대상 페이지 + * @returns 활성 채널 탭의 표시명 (없으면 null) + */ +async function activeChannelLabel(page: Page): Promise { + for (const label of [CHANNEL_MAIL, CHANNEL_DATABASE]) { + const className = (await channelTab(page, label).first().getAttribute('class')) ?? ''; + + if (className.includes('border-indigo-500') || className.includes('border-indigo-400')) { + return label; + } + } + + return null; +} + +/** + * 다른 설정 탭에 갔다가 알림 설정 탭으로 돌아옵니다. + * + * `page.goto` 를 쓰면 안 된다 — 전체 새로고침은 로컬 상태를 통째로 버리므로 재현 조건이 사라진다. + * 결함은 SPA 라우팅(탭 클릭)으로 URL 만 바뀌어 로컬 상태가 살아남을 때 드러난다. + * + * @param page 대상 페이지 + */ +async function settingsTabRoundTrip(page: Page): Promise { + await page.getByRole('tab', { name: TAB_IDENTITY }).click(); + await expect(page).toHaveURL(/tab=identity/, { timeout: 15_000 }); + + await page.getByRole('tab', { name: TAB_NOTIFICATION }).click(); + await expect(page).toHaveURL(/tab=notification_definitions/, { timeout: 15_000 }); + await expect(channelTab(page, CHANNEL_MAIL)).toBeVisible({ + timeout: 20_000, + }); +} + +/** + * 이 스펙은 직렬로 실행한다 — 세 테스트가 모두 관리자 설정 화면을 띄우므로 병렬 세션이 + * 겹치면 목록 적재 대기가 타임아웃되어 제품 결함이 아닌 거짓 실패가 난다. + */ +test.describe.configure({ mode: 'serial' }); + +test.describe('알림 정의 채널 왕복 (#518)', () => { + /** + * @scenario surface=core_settings,action=channel_switch + * + * @effects notification_channel_switch_requests_that_channel + */ + test('채널 탭을 누르면 그 채널로 목록을 다시 불러온다', async ({ page }) => { + await authenticatePage(page, issueToken(...SETTINGS_PERMISSIONS)); + + const requested = trackRequestedChannels(page); + await gotoDefinitionsTab(page); + + await expect.poll(() => requested.length, { timeout: 20_000 }).toBeGreaterThan(0); + expect(requested.at(-1)).toBe('mail'); + + await channelTab(page, CHANNEL_DATABASE).click(); + + await expect(page).toHaveURL(/channel=database/, { timeout: 15_000 }); + await expect.poll(() => requested.at(-1), { timeout: 20_000 }).toBe('database'); + await expect.poll(() => activeChannelLabel(page), { timeout: 15_000 }).toBe(CHANNEL_DATABASE); + }); + + /** + * @scenario surface=core_settings,action=tab_roundtrip + * + * @effects notification_channel_tab_roundtrip_keeps_data_and_screen_in_sync + */ + test('다른 설정 탭에 갔다 돌아와도 요청한 채널과 화면이 가리키는 채널이 같다', async ({ + page, + }) => { + await authenticatePage(page, issueToken(...SETTINGS_PERMISSIONS)); + + const requested = trackRequestedChannels(page); + await gotoDefinitionsTab(page); + + // 기본(mail)이 아닌 채널로 옮겨 둔다 — 왕복 후 어긋남이 드러나려면 두 값이 달라야 한다 + await channelTab(page, CHANNEL_DATABASE).click(); + await expect.poll(() => requested.at(-1), { timeout: 20_000 }).toBe('database'); + + await settingsTabRoundTrip(page); + + // 왕복 직후에는 화면이 잠깐 기본값을 보이다 뒤늦게 뒤집힐 수 있었다(실측 ~0.4초). + // 그래서 한 번 읽고 끝내지 않고, 안정될 때까지 기다린 뒤 두 값을 대조한다. + await page.waitForTimeout(1_500); + + const lastRequested = requested.at(-1); + const active = await activeChannelLabel(page); + + const expectedLabel = lastRequested === 'database' ? CHANNEL_DATABASE : CHANNEL_MAIL; + + expect( + active, + `목록은 ${lastRequested} 로 불러왔는데 화면은 ${active} 을 활성으로 표시한다 — ` + + '두 채널이 어긋나면 행마다 「이 채널에 대한 템플릿이 없습니다」 가 뜬다' + ).toBe(expectedLabel); + }); + + /** + * @scenario surface=core_settings,action=tab_roundtrip + * + * @effects notification_channel_roundtrip_emits_no_console_errors + */ + test('채널 왕복 중 콘솔 에러가 발생하지 않는다', async ({ page }) => { + const errors: string[] = []; + page.on('console', msg => { + if (msg.type() === 'error') errors.push(msg.text()); + }); + + await authenticatePage(page, issueToken(...SETTINGS_PERMISSIONS)); + await gotoDefinitionsTab(page); + + await channelTab(page, CHANNEL_DATABASE).click(); + await expect(page).toHaveURL(/channel=database/, { timeout: 15_000 }); + await settingsTabRoundTrip(page); + + expect(errors).toEqual([]); + }); +}); diff --git a/tests/Playwright/specs/layout-editor/version-history-paging.spec.ts b/tests/Playwright/specs/layout-editor/version-history-paging.spec.ts new file mode 100644 index 00000000..fd4c3898 --- /dev/null +++ b/tests/Playwright/specs/layout-editor/version-history-paging.spec.ts @@ -0,0 +1,125 @@ +/** + * Layout Editor — 버전 이력 목록의 상한 조회와 '더 보기'. + * + * 버전 행은 저장할 때마다 쌓이고 정리되지 않는다. 목록이 전량을 내려주면 오래 쓴 레이아웃일수록 + * 모달 하나를 여는 데 수천 행이 실린다(#518 / 공개 #76 의 목록 하위 컬렉션과 같은 형태). 그래서 + * 서버가 조회 건수를 제한하고, 화면은 한 묶음씩 넓혀 간다. + * + * 단위 테스트(VersionHistoryModal.test.tsx)는 훅의 상태 전이를 잠근다. 이 E2E 는 브라우저에서 + * 1. 실제 편집기에서 버전 모달이 열리고 목록이 뜨는지, + * 2. 첫 요청이 `?limit=` 을 달고 나가는지(상한 없이 전량 요청하지 않는지), + * 3. 한 묶음이 가득 찼을 때만 '더 보기' 가 뜨고, 누르면 **더 넓은 상한**으로 재조회하는지, + * 4. 가득 차지 않으면 '더 보기' 가 뜨지 않는지 + * 를 확인한다. 3·4 는 개발 사이트의 실제 버전 수에 좌우되지 않도록 응답만 고정한다(요청은 실제로 + * 브라우저가 만든 것을 검사한다). + * + * @scenario resource=layout_version,endpoint=list,observation=consumer_screen + * @effects version_list_request_carries_limit, version_list_load_more_widens_limit, version_list_load_more_hidden_when_batch_not_full + */ +import { test, expect, issueToken, authenticatePage } from '../../fixtures/auth'; +import type { Page, Route } from '@playwright/test'; + +/** 서버 기본 한 묶음 크기 — useLayoutVersions.VERSION_PAGE_SIZE 와 같은 값 */ +const VERSION_PAGE_SIZE = 100; + +const VERSIONS_URL_RE = /\/api\/admin\/templates\/[^/]+\/layouts\/[^/]+\/versions(\?|$)/; + +/** + * 버전 목록 응답 한 건을 만듭니다. + * + * @param id 버전 행 ID + * @returns 목록 항목 + */ +function versionRow(id: number): Record { + return { + id, + version: id, + is_latest: id === 1, + created_at: '2026-01-01T00:00:00+09:00', + created_at_formatted: '2026-01-01 00:00', + author_name: 'tester', + changes_summary: { added_count: 1, removed_count: 0, char_diff: 10 }, + }; +} + +/** + * 편집기를 열고 버전 이력 모달을 띄웁니다. + * + * @param page Playwright page + */ +async function openVersionHistory(page: Page): Promise { + const token = issueToken('core.templates.layouts.edit'); + await authenticatePage(page, token); + await page.goto('/admin/layout-editor/sirsoft-basic?route=%2F'); + await page.waitForLoadState('domcontentloaded', { timeout: 30_000 }); + await page.waitForSelector('[data-testid="g7le-preview-frame"]', { timeout: 30_000 }); + + await page.waitForSelector('[data-testid="g7le-toolbar-versions"]', { timeout: 20_000 }); + await page.getByTestId('g7le-toolbar-versions').click(); + await page.waitForSelector('[data-testid="g7le-version-history"]', { timeout: 20_000 }); +} + +/** + * 버전 목록 응답을 고정 개수로 대체합니다. + * + * @param page Playwright page + * @param count 응답에 담을 행 수 + * @param seen 요청 URL 수집 배열 + */ +async function stubVersions(page: Page, count: number, seen: string[]): Promise { + await page.route(VERSIONS_URL_RE, async (route: Route) => { + seen.push(route.request().url()); + await route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({ + success: true, + data: Array.from({ length: count }, (_, i) => versionRow(i + 1)), + }), + }); + }); +} + +test.describe('레이아웃 편집기 버전 이력 — 상한 조회', () => { + test('첫 조회가 상한(limit)을 달고 나간다 @layout-editor', async ({ page }) => { + const seen: string[] = []; + await stubVersions(page, 3, seen); + + await openVersionHistory(page); + await page.waitForSelector('[data-testid="g7le-version-history-list"]', { timeout: 20_000 }); + + expect(seen.length).toBeGreaterThan(0); + // 상한 없이 나가면 오래 쓴 레이아웃에서 전량이 실린다 + expect(seen[0]).toContain(`limit=${VERSION_PAGE_SIZE}`); + }); + + test('한 묶음이 가득 차지 않으면 더 보기가 없다 @layout-editor', async ({ page }) => { + const seen: string[] = []; + await stubVersions(page, 3, seen); + + await openVersionHistory(page); + await page.waitForSelector('[data-testid="g7le-version-history-list"]', { timeout: 20_000 }); + + // 존재를 먼저 확정한 뒤 부재를 단언한다 — 렌더 전에는 어떤 부재 단언도 무조건 통과한다 + await expect(page.getByTestId('g7le-version-row-1')).toBeVisible(); + await expect(page.getByTestId('g7le-version-history-load-more')).toHaveCount(0); + }); + + test('묶음이 가득 차면 더 보기로 상한을 넓혀 재조회한다 @layout-editor', async ({ page }) => { + const seen: string[] = []; + await stubVersions(page, VERSION_PAGE_SIZE, seen); + + await openVersionHistory(page); + await page.waitForSelector('[data-testid="g7le-version-history-list"]', { timeout: 20_000 }); + + const loadMore = page.getByTestId('g7le-version-history-load-more'); + await expect(loadMore).toBeVisible(); + + await loadMore.click(); + await expect.poll(() => seen.length, { timeout: 20_000 }).toBeGreaterThan(1); + + // 두 번째 요청은 반드시 더 넓은 상한이어야 한다 — 같은 상한으로 다시 부르면 + // 같은 묶음이 돌아와 '더 보기' 가 영원히 아무 일도 하지 않는다 + expect(seen[1]).toContain(`limit=${VERSION_PAGE_SIZE * 2}`); + }); +}); diff --git a/tests/Unit/LayoutVersionLimitParityTest.php b/tests/Unit/LayoutVersionLimitParityTest.php new file mode 100644 index 00000000..b063bd82 --- /dev/null +++ b/tests/Unit/LayoutVersionLimitParityTest.php @@ -0,0 +1,96 @@ +assertSame( + LayoutVersionListRequest::MAX_LIMIT, + $this->readNumericConstant('VERSION_MAX_LIMIT'), + '편집기 VERSION_MAX_LIMIT 이 서버 LayoutVersionListRequest::MAX_LIMIT 과 다르다. '. + '화면이 서버 상한을 넘는 limit 을 보내면 버전 목록이 422 로 비어 버린다.' + ); + } + + /** + * 편집기의 한 묶음 크기가 서버 기본 조회 건수와 같다. + * + * 첫 화면이 서버 기본값과 다른 수를 요청하면 '더 보기' 노출 판정("묶음이 가득 찼는가")이 + * 어긋나, 남은 이력이 있는데도 버튼이 사라지거나 그 반대가 된다. + * + * @return void + */ + public function test_editor_page_size_matches_server_default_limit(): void + { + $this->assertSame( + LayoutVersionListRequest::DEFAULT_LIMIT, + $this->readNumericConstant('VERSION_PAGE_SIZE'), + '편집기 VERSION_PAGE_SIZE 가 서버 DEFAULT_LIMIT 과 다르다.' + ); + } + + /** + * 상한이 한 묶음보다 크다 — 그렇지 않으면 '더 보기' 가 처음부터 무의미하다. + * + * @return void + */ + public function test_max_limit_leaves_room_for_at_least_one_more_batch(): void + { + $this->assertGreaterThan( + LayoutVersionListRequest::DEFAULT_LIMIT, + LayoutVersionListRequest::MAX_LIMIT, + '상한이 기본 조회 건수 이하면 더 보기로 넓힐 여지가 없다.' + ); + } + + /** + * 편집기 훅에서 숫자 상수 값을 읽습니다. + * + * @param string $name 상수 이름 + * @return int 상수 값 + */ + private function readNumericConstant(string $name): int + { + $path = base_path(self::HOOK_PATH); + + $this->assertFileExists($path, '편집기 버전 훅 경로가 바뀌었다면 본 테스트의 경로도 함께 고쳐야 한다.'); + + $source = file_get_contents($path); + + $matched = preg_match( + '/export\s+const\s+'.preg_quote($name, '/').'\s*=\s*(\d+)\s*;/', + $source, + $matches + ); + + $this->assertSame( + 1, + $matched, + "편집기 훅에서 {$name} 선언을 찾지 못했다 (이름 변경 또는 선언 형태 변경)." + ); + + return (int) $matches[1]; + } +} diff --git a/tests/Unit/Resources/NotificationDefinitionResourceTest.php b/tests/Unit/Resources/NotificationDefinitionResourceTest.php index eccb31fc..d622e984 100644 --- a/tests/Unit/Resources/NotificationDefinitionResourceTest.php +++ b/tests/Unit/Resources/NotificationDefinitionResourceTest.php @@ -17,9 +17,14 @@ class NotificationDefinitionResourceTest extends TestCase use RefreshDatabase; /** - * templates 관계 미로드 시 null 반환 (json 직렬화 에러 없음) + * templates 관계 미로드 시 키 자체가 빠진다 (json 직렬화 에러 없음) + * + * 종전에는 `relationLoaded ? ... : null` 로 null 을 내보냈다. 그 형태는 Repository 가 목록에서 + * 관계를 무조건 로드하면 가드가 항상 참이 되어 무력화되는 "가짜 가드" 라 `whenLoaded` 로 + * 바꿨고, 미로드 시에는 MissingValue 가 되어 응답에서 키가 빠진다. 소비 화면은 + * `def.templates ?? []` / `def.templates?.[0]` 로 읽어 null 과 부재를 동일하게 다룬다. */ - public function test_templates_returns_null_when_relation_not_loaded(): void + public function test_templates_key_is_omitted_when_relation_not_loaded(): void { $definition = NotificationDefinition::create([ 'type' => 'welcome', @@ -35,7 +40,9 @@ class NotificationDefinitionResourceTest extends TestCase $resource = new NotificationDefinitionResource($definition); $array = $resource->toArray($request); - $this->assertNull($array['templates']); + // 컬렉션이 이 배열을 그대로 응답에 싣기 때문에 MissingValue 가 남아 있으면 `{}` 로 + // 직렬화된다 — Resource 가 직접 걸러내야 한다. + $this->assertArrayNotHasKey('templates', $array); // json_encode 시 에러 없음 검증 $json = json_encode($array); diff --git a/tests/Unit/Support/ApiDoc/ApiDocPipelineTest.php b/tests/Unit/Support/ApiDoc/ApiDocPipelineTest.php index eca69af5..0d4d13fa 100644 --- a/tests/Unit/Support/ApiDoc/ApiDocPipelineTest.php +++ b/tests/Unit/Support/ApiDoc/ApiDocPipelineTest.php @@ -255,6 +255,43 @@ class ApiDocPipelineTest extends TestCase $this->assertStringContainsString('| unknown_field | string | `x` | |', $section); } + #[Test] + public function 사람이_쓴_설명에_인라인_코드_파이프가_있어도_잘리지_않고_승계된다(): void + { + // 회귀: 표 행을 셀로 나눌 때 인라인 코드(`|`) 안의 파이프까지 구분자로 보면 셀 수가 늘고, + // "마지막 셀 = 설명" 규칙이 파이프 뒤 조각을 설명으로 승계한다. 그 결과 재생성 한 번에 + // 문장 앞부분이 통째로 사라진다 — 오류도 경고도 없이 문서만 훼손된다. + // 실제 사례: 스케줄 문서의 command 설명("셸 메타문자(`|`, `;` …)")이 "`, `;` …" 로 잘렸다. + $scaffolder = new ApiDocScaffolder; + + $humanDescription = '실행할 명령. 셸 메타문자(`|`, `;`, `$`)가 포함되면 거부됩니다'; + + $route = [ + 'method' => 'POST', 'uri' => '/api/admin/schedules', 'name' => 'api.admin.schedules.store', + 'controller' => 'C', 'controller_method' => 'store', 'permission' => null, + 'middleware' => [], 'path_params' => [], + ]; + $request = ['request_class' => 'X', 'params' => [ + ['name' => 'command', 'type' => 'string', 'required' => true, 'allowed' => 'max 2000'], + ], 'hook_filters' => []]; + + // 1회차: 생성된 문서의 command 행 설명 셀을 사람이 직접 채운 상태로 만든다 + $first = $scaffolder->endpointSection($route, $request, null, ['status' => null, 'skipped_reason' => 'write-method']); + $humanRow = '| command | body | string | 예 | max 2000 | '.$humanDescription.' |'; + $existing = "# 문서\n\n".preg_replace( + '/^\| command \| body \|.*$/m', + $humanRow, + $first + ); + $this->assertStringContainsString($humanRow, $existing, '전제 실패: 사람 설명이 문서에 들어가지 않았다'); + + // 2회차: 재생성 → 사람 설명이 원문 그대로 승계되어야 한다 + $regenerated = $scaffolder->endpointSection($route, $request, null, ['status' => null, 'skipped_reason' => 'write-method']); + $merged = $scaffolder->mergeDocument($existing, '# 문서', [$regenerated], ['api.admin.schedules.store']); + + $this->assertStringContainsString($humanRow, $merged, '인라인 코드 안의 파이프를 셀 구분자로 보아 설명이 잘렸다'); + } + #[Test] public function 쓰기_메서드는_응답_필드를_실측_제외로_표기한다(): void { @@ -888,4 +925,235 @@ MD; substr_count($regen2, '(사람 서술)') ); } + + /** + * 목록이 아닌 큰 응답(카탈로그·스펙)도 예시 크기 상한 안으로 절단되어야 한다. + * + * 회귀 배경 (#518): 절단 로직이 페이지네이션 형태(`data.data[]`)만 다뤄, 편집기 스펙처럼 + * 목록이 아닌 응답이 통째로 직렬화됐다. templates.md 한 파일이 115KB → 8.65MB 가 됐다. + */ + #[Test] + public function 목록이_아닌_큰_응답도_예시_크기_상한으로_절단된다(): void + { + $scaffolder = new ApiDocScaffolder; + + // 편집기 스펙 형태 — 중첩 객체 안에 큰 배열이 있고 페이지네이션 형태가 아니다. + $palette = []; + for ($i = 0; $i < 400; $i++) { + $palette["component_{$i}"] = [ + 'name' => "Component{$i}", + 'description' => str_repeat('설명 텍스트 ', 40), + 'props' => array_fill(0, 30, 'prop-value-'.str_repeat('x', 60)), + ]; + } + + $body = [ + 'success' => true, + 'message' => '조회 성공', + 'data' => ['palette' => $palette, 'version' => '1.0.0'], + ]; + + $rawSize = strlen((string) json_encode($body, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); + $this->assertGreaterThan(500_000, $rawSize, '전제: 원본이 충분히 커야 절단을 검증할 수 있다'); + + $block = $scaffolder->responseExampleBlock(null, ['status' => 200, 'body' => $body]); + + // 존재를 먼저 확정한 뒤 크기를 단언한다. + $this->assertStringContainsString('```json', $block); + $this->assertLessThan( + 16_384, + strlen($block), + '목록이 아닌 큰 응답도 상한 이하로 절단되어야 한다', + ); + $this->assertStringContainsString('생략', $block, '무엇이 잘렸는지 문서에 남아야 한다'); + } + + /** + * 재생성이 표의 사람 서술을 TODO 로 되돌리지 않아야 한다. + * + * 회귀 배경 (#518): 전체 재생성이 `@generated` 블록의 파라미터/응답 필드 표를 통째로 다시 + * 조립하면서, 정적 추출로 재현할 수 없는 도메인 설명이 TODO 스텁으로 되돌아갔다 + * (54개 파일 1,181행). `--examples-only` 로만 우회 가능했던 것을 재생성 경로에서 막는다. + */ + #[Test] + public function 재생성이_표의_사람_서술을_todo_로_되돌리지_않는다(): void + { + $scaffolder = new ApiDocScaffolder; + + $existing = <<<'MD' + # API + + ### GET /api/admin/things + + **요청 파라미터** + + | 이름 | 위치 | 타입 | 필수 | 기본값 | 설명 | + | --- | --- | --- | --- | --- | --- | + | no_category | query | boolean | 아니오 | — | 카테고리 미지정 상품만 필터 | + | keyword | query | string | 아니오 | — | | + + **응답 필드** (`data` 내부) + + | 필드 | 타입 | 예시 | 설명 | + | --- | --- | --- | --- | + | uuid | string | `abc` | 외부 노출용 UUID | + + + **설명** + + 사람이 쓴 서술. + MD; + + // 재생성 결과 — 두 설명 모두 TODO 스텁으로 조립된 상태 + $newSection = <<<'MD' + ### GET /api/admin/things + + **요청 파라미터** + + | 이름 | 위치 | 타입 | 필수 | 기본값 | 설명 | + | --- | --- | --- | --- | --- | --- | + | no_category | query | boolean | 아니오 | — | | + | keyword | query | string | 아니오 | — | | + + **응답 필드** (`data` 내부) + + | 필드 | 타입 | 예시 | 설명 | + | --- | --- | --- | --- | + | uuid | string | `abc` | | + + + **설명** + MD; + + $merged = $scaffolder->mergeDocument( + $existing, + '# API', + [$newSection], + ['api.admin.things.index'], + ); + + // 사람이 채운 설명은 되살아난다. + $this->assertStringContainsString('카테고리 미지정 상품만 필터', $merged); + $this->assertStringContainsString('외부 노출용 UUID', $merged); + // 원래도 비어 있던 셀은 TODO 로 남는다. + $this->assertStringContainsString('| keyword | query | string | 아니오 | — | |', $merged); + // 블록 밖 사람 서술도 그대로 보존된다. + $this->assertStringContainsString('사람이 쓴 서술.', $merged); + } + + /** + * 실측 데이터가 없어 필드 표가 비면 기존 표를 유지해야 한다. + * + * 회귀 배경 (#518): 대상 데이터가 없는 상태로 재생성하자 응답 필드 표가 "실측 응답에 필드 없음" + * 한 줄로 대체됐다 (reviews.md 388줄 소실). "이번에 데이터가 없었다" 는 사실은 이미 문서화된 + * 필드 목록을 지울 근거가 못 된다. + */ + #[Test] + public function 빈_실측이_기존_응답_필드_표를_지우지_않는다(): void + { + $scaffolder = new ApiDocScaffolder; + + $existing = <<<'MD' + # API + + ### GET /api/admin/things + + **응답 필드** (`data` 내부) + + | 필드 | 타입 | 예시 | 설명 | + | --- | --- | --- | --- | + | id | integer | `99` | 기본 키 (내부 식별자) | + | rating | integer | `5` | 별점 (1~5) | + + **응답 예시** + + ```json + {} + ``` + + MD; + + $newSection = <<<'MD' + ### GET /api/admin/things + + **응답 필드** (`data` 내부) + + + + **응답 예시** + + ```json + {} + ``` + + MD; + + $merged = $scaffolder->mergeDocument($existing, '# API', [$newSection], ['api.admin.things.index']); + + $this->assertStringContainsString('| rating | integer | `5` | 별점 (1~5) |', $merged); + $this->assertStringNotContainsString('실측 응답에 필드 없음', $merged); + } + + /** + * 기존 설명이 있으면 도구가 만든 일반 문구로 덮어쓰지 않는다. + * + * 도구의 설명은 필드명에서 유추한 일반 문구라 사람이 적은 도메인 사실보다 정보량이 적다. + * 회귀 배경 (#518): `플러그인 이름 (다국어 JSON)` 이 `대상의 이름/명칭 (다국어 필드는 …)` 로, + * `desc 는 주문별 가장 늦은 발송일` 같은 서술이 일반 문구로 대체돼 224건이 후퇴했다. + * 값이 바뀌는 컬럼(타입·예시·허용값)은 그대로 재생성되므로 최신성은 유지된다. + */ + #[Test] + public function 기존_설명은_도구의_일반_문구로_덮어쓰지_않는다(): void + { + $scaffolder = new ApiDocScaffolder; + + $existing = <<<'MD' + # API + + ### GET /api/admin/things + + | 필드 | 타입 | 예시 | 설명 | + | --- | --- | --- | --- | + | name | string | `옛값` | 플러그인 이름 (다국어 JSON) | + + MD; + + $newSection = <<<'MD' + ### GET /api/admin/things + + | 필드 | 타입 | 예시 | 설명 | + | --- | --- | --- | --- | + | name | string | `새값` | 대상의 이름/명칭 (다국어 필드는 로케일별 값 객체) | + + MD; + + $merged = $scaffolder->mergeDocument($existing, '# API', [$newSection], ['api.admin.things.index']); + + // 설명은 사람 서술을 유지하고, + $this->assertStringContainsString('플러그인 이름 (다국어 JSON)', $merged); + $this->assertStringNotContainsString('대상의 이름/명칭', $merged); + // 실측으로 갱신되는 예시값은 새 값으로 바뀐다. + $this->assertStringContainsString('`새값`', $merged); + $this->assertStringNotContainsString('`옛값`', $merged); + } + + /** + * 상한 이하의 작은 응답은 손대지 않는다 (절단이 정상 예시를 훼손하지 않음). + */ + #[Test] + public function 상한_이하_응답은_그대로_직렬화된다(): void + { + $scaffolder = new ApiDocScaffolder; + + $body = [ + 'success' => true, + 'message' => '조회 성공', + 'data' => ['id' => 7, 'identifier' => 'sirsoft-admin_basic', 'version' => '1.0.4'], + ]; + + $block = $scaffolder->responseExampleBlock(null, ['status' => 200, 'body' => $body]); + + $this->assertStringContainsString('"identifier": "sirsoft-admin_basic"', $block); + $this->assertStringNotContainsString('생략', $block); + } } diff --git a/tests/scenarios/list-child-collection-pruning.yaml b/tests/scenarios/list-child-collection-pruning.yaml new file mode 100644 index 00000000..9baf2aa2 --- /dev/null +++ b/tests/scenarios/list-child-collection-pruning.yaml @@ -0,0 +1,167 @@ +feature: 목록 응답의 하위 컬렉션·넓은 컬럼 프루닝 (코어) — #518 / 공개 #76 + +description: | + 목록 한 페이지를 여는 것만으로 그 페이지 전 행의 하위 컬렉션과 넓은 본문 컬럼이 응답에 + 실리던 결함을 해소한다. 목록은 화면이 그 행에서 **실제로 그리는 것**만 싣고, 나머지는 + 단건 조회 경로가 공급한다. + + 코어 대상: + · 레이아웃 목록(B) — 본문(content) 을 읽지 않고 DB 가 계산한 길이만 가져온다 + · 버전 목록(C) — 조회 건수 상한 + 화면의 '더 보기' 로 한 묶음씩 확장 + · 확장 목록(D) — 목록 표현(toListArray)에서 본문 제거, 크기 표기는 유지 + · 역할 목록(F) — 권한 배열을 목록에서 제거, 상세/폼은 유지 + · 알림 정의 목록(G) — 채널×로케일 템플릿 본문을 요청 채널 1종으로 스코프 + · IDV 정의 목록(H) — 채널별 템플릿을 대표 1건으로 축소, 총 개수는 집계로 + · 메뉴 목록(M) — 하위 메뉴의 역할 목록을 목록에서 제거 + + 기능 축소가 없다는 것이 이 매트릭스의 핵심이다. 목록에서 뺀 값은 전부 (a) 집계로 대체되거나 + (b) 단건 조회에 그대로 남아 있어야 한다. 어느 쪽도 아니면 그것은 성능 개선이 아니라 기능 삭제다. + + 조건부 필드의 누출도 함께 고정한다. `toListArray()` 결과를 컨트롤러가 그대로 응답에 실으면 + Laravel 의 MissingValue 제거 단계를 거치지 않아 미충족 필드가 `{}` 로 남는다. + +# axis 의미: +# 무엇이 실리고 무엇이 빠지는지를 가르는 축은 (대상 리소스) × (응답 경로) × (관측 지점) 이다. +# 목록에서 뺀 값이 단건에는 남아 있어야 하므로 두 경로를 반드시 짝으로 확인한다. +# +# 관측 지점을 축으로 두는 이유: 응답에서 사라진 것만 단언하면 "그 값을 화면이 쓰고 있었다" +# 는 사실이 잡히지 않는다. M(메뉴)이 정확히 그렇게 새어 나갔다 — payload 단언은 전부 green +# 인데 화면은 하위 메뉴 저장 시 역할 제한을 통째로 잃고 있었다. 그래서 리소스마다 +# 소비 화면 쪽도 함께 관측한다. +# +# "집계로 대체됐는가 / 조건부 필드가 새지 않는가" 는 입력 축이 아니라 검증 항목이라 +# effects 로 둔다 (축으로 만들면 성립하지 않는 조합이 대부분이 된다). +axes: + resource: [layout, layout_version, layout_extension, role, notification_definition, identity_message_definition, menu] + endpoint: [list, detail] + observation: [response_payload, consumer_screen] + +# 단건 × 소비 화면은 메뉴 하나만 남긴다. +# 프루닝은 목록 응답만 바꿨고 단건 payload 는 종전 그대로라, 단건을 읽는 화면(편집기 캔버스· +# 편집 폼)에는 새로 생긴 분기가 없다 — 그 조합을 남겨두면 "목록 테스트를 한 번 더 쓰는" 칸이 된다. +# 메뉴만 예외다. 목록에서 뺀 역할을 화면이 단건 조회로 보강하는 경로가 이번에 새로 생겼고, +# 그 경로가 없으면 하위 메뉴 저장 시 역할 제한이 통째로 사라진다. +exclusions: + - { resource: layout, endpoint: detail, observation: consumer_screen, reason: "단건 payload 무변경 — 편집기 캔버스가 본문을 종전대로 로드한다" } + - { resource: layout_version, endpoint: detail, observation: consumer_screen, reason: "단건 payload 무변경 — 비교/복원이 본문을 종전대로 읽는다" } + - { resource: layout_extension, endpoint: detail, observation: consumer_screen, reason: "단건 payload 무변경 — 캔버스가 본문을 종전대로 로드한다" } + - { resource: role, endpoint: detail, observation: consumer_screen, reason: "단건 payload 무변경 — 역할 폼이 권한 트리를 종전대로 받는다" } + - { resource: notification_definition, endpoint: detail, observation: consumer_screen, reason: "단건 payload 무변경 — 설정 탭이 전 채널 템플릿을 종전대로 받는다" } + - { resource: identity_message_definition, endpoint: detail, observation: consumer_screen, reason: "단건 payload 무변경 — 정의 편집이 전 채널 템플릿을 종전대로 받는다" } + +effects: + # 목록에서 빠졌는가 + - list_omits_layout_content + - list_omits_extension_content + - list_omits_role_permissions + - list_omits_menu_child_roles + # 알림/IDV 정의(G/H): 템플릿 본문을 목록에서 좁혔는가 + 좁힌 값의 대체 지표가 정확한가. + # 두 리소스 모두 "제거" 가 아니라 "스코프 축소" 라, 무엇이 남았는지까지 고정해야 한다. + - notification_list_carries_only_requested_channel_template + - notification_list_without_channel_omits_templates + - notification_customized_flag_spans_every_channel + - identity_definition_list_carries_representative_template_only + - identity_definition_list_reports_total_template_count + - identity_definition_every_row_gets_its_own_representative + # 좁힌 값의 대체 경로 (기능 축소 아님) + - notification_detail_returns_every_channel_template + - identity_definition_detail_returns_every_template + # 소비 화면이 좁혀진 계약 위에서 그대로 동작하는가. + # 서버가 좁혀 보낸 채널과 화면이 찾는 채널은 각각 옳아도 서로 다르면 깨진다 — + # 그래서 "일치하는가" 를 응답이 아니라 화면 쪽에서 관측한다. + - definition_list_channel_scope_matches_consumer + - definition_list_cross_channel_judgment_uses_aggregate + - identity_definition_list_uses_representative_template + # 빠진 값의 대체 경로가 있는가 (기능 축소 아님) + - layout_size_reported_without_reading_content + - extension_size_matches_actual_content_length + - detail_still_returns_full_payload + # 메뉴(M): 목록에서 뺀 하위 역할의 지연 로드 경로 — 화면이 실제로 그 경로를 타는가까지 본다. + # 응답에서 사라진 것만 단언하면, 그 값을 화면이 쓰고 있었다는 사실은 잡히지 않는다. + - menu_detail_provides_child_roles + - menu_selection_hydrates_roles_from_detail + - menu_edit_form_seeds_roles_from_detail + # 버전 단건은 목록이 버린 두 가지(본문·상한 밖 행)를 모두 되돌려주는가. + # 목록용 조회를 단건이 재사용하면 둘 다 조용히 깨진다 — 버전 번호만 단언하는 테스트는 통과한다. + - version_detail_still_returns_content + - version_detail_reaches_rows_beyond_list_cap + # 조회 자체가 넓은 컬럼을 읽지 않는가 + - list_query_does_not_select_content_column + - query_count_does_not_scale_with_row_count + # 버전 목록 상한 + - version_list_request_carries_limit + - version_list_load_more_widens_limit + - version_list_load_more_hidden_when_batch_not_full + # 조건부 필드 누출 + - list_payload_has_no_empty_object_fields + +sub_flows: + - id: version_list_paging + description: 버전 목록은 한 묶음 상한으로 조회하고, 묶음이 가득 찼을 때만 '더 보기' 로 넓힌다 + effects: + - version_list_request_carries_limit + - version_list_load_more_widens_limit + - version_list_load_more_hidden_when_batch_not_full + + - id: version_detail_is_the_replacement_path + description: | + 버전 목록이 뺀 값(본문)과 자른 행(상한 밖)은 버전 단건이 되돌려준다. 목록과 단건이 같은 + 조회를 공유하면 목록 최적화가 그대로 단건의 기능 삭제가 되므로, 두 경로는 반드시 분리된 + 조회를 쓴다. 레이아웃 본체와 확장 양쪽 모두에 적용한다. + effects: + - version_detail_still_returns_content + - version_detail_reaches_rows_beyond_list_cap + + - id: menu_child_roles_lazy_path + description: | + 하위 메뉴의 역할을 목록에서 뺐으므로, 화면은 선택 시 단건 조회로 그 값을 보강해야 한다. + 이 경로가 없으면 하위 메뉴를 열어 저장하는 것만으로 역할 제한이 전부 해제된다 — + 목록 응답만 보는 테스트는 그 손실을 통과시킨다. + effects: + - menu_detail_provides_child_roles + - menu_selection_hydrates_roles_from_detail + - menu_edit_form_seeds_roles_from_detail + + - id: missing_value_leak_guard + description: toListArray() 배열을 그대로 싣는 경로에서 미충족 조건부 필드가 빈 객체로 남지 않는다 + effects: + - list_payload_has_no_empty_object_fields + +test_files: + # B/C/D — 레이아웃·버전·확장 목록 + - tests/Feature/Api/Admin/LayoutControllerTest.php + - tests/Feature/Api/Admin/LayoutExtensionControllerTest.php + # F/M — 역할·메뉴 목록 + 코어 전역 프루닝 계약 + - tests/Feature/Api/Admin/CoreListPayloadPruningTest.php + - tests/Feature/Api/Admin/RoleControllerTest.php + - tests/Feature/Api/Admin/MenuControllerTest.php + # G/H — 알림 정의 / IDV 메시지 정의 목록 + - tests/Feature/Api/Admin/NotificationDefinitionListPayloadTest.php + - tests/Feature/Api/Admin/Identity/AdminIdentityMessageDefinitionListPayloadTest.php + # F/G/H — 관리자 화면의 소비 경로 (권한 개수 열 / 채널 스코프 데이터소스) + - templates/_bundled/sirsoft-admin_basic/__tests__/layouts/admin-role-list-permission-pruning.test.tsx + - templates/_bundled/sirsoft-admin_basic/__tests__/layouts/admin-settings-definition-template-pruning.test.tsx + # B/D — 파일트리·라우트트리가 목록 응답에서 본문을 읽지 않는가 (두 목록의 유일한 소비자) + - templates/_bundled/sirsoft-admin_basic/__tests__/layouts/admin-template-layout-edit-list-pruning.test.tsx + # C — 편집기 버전 이력 화면 (단위) + - resources/js/core/template-engine/layout-editor/__tests__/components/VersionHistoryModal.test.tsx + # C — 편집기 버전 이력 화면 (브라우저) + - tests/Playwright/specs/layout-editor/version-history-paging.spec.ts + # M — 메뉴 관리 화면의 지연 로드 경로 (선택 시 보강 / URL 직접 진입) + - templates/_bundled/sirsoft-admin_basic/__tests__/layouts/admin-menu-list-child-roles.test.tsx + - templates/_bundled/sirsoft-admin_basic/src/handlers/__tests__/initMenuFromUrlHandler.test.ts + +notes: | + E2E 대상은 C(버전 목록) 하나다. B/D/F 는 응답 payload 만 바뀌고 화면 코드가 그대로라 + 브라우저 경로에 새로 생긴 분기가 없다. C 만 화면에 '더 보기' 라는 새 상태가 생겼고, + 그 상태는 "묶음이 가득 찼는가" 로 갈리므로 응답 건수를 고정해 두 갈래를 모두 태운다. + + M(메뉴)은 처음에 B/D/F 와 같은 부류로 분류했으나 그 판단이 틀렸다. 화면이 목록 행 객체를 + 그대로 편집 폼 초기값으로 쓰고 있어, 목록에서 역할을 빼자 하위 메뉴 저장 시 역할 제한이 + 전부 해제됐다. 그래서 M 에는 선택 시 단건 보강 경로를 만들고 프론트 회귀 테스트 2종을 + 붙였다. 프루닝을 하기 전에 "그 값을 화면이 어디서 쓰는가" 를 먼저 실측해야 한다는 것이 + 이 항목의 교훈이다. + + 버전 목록 상한은 프론트 소스만 고쳐서는 반영되지 않는다 — 편집기는 lazy 번들로 서빙되므로 + `php artisan core:build` 로 산출물을 다시 만들어야 브라우저가 새 요청을 보낸다. 위 Playwright + spec 이 그 반영까지 실측한다(첫 작성 시 재빌드 전 상태에서 실제로 red 였다). From 4f6fced2db4bcce0fb178569f6001debfd39caf8 Mon Sep 17 00:00:00 2001 From: HeuJung Date: Thu, 6 Aug 2026 09:02:00 +0900 Subject: [PATCH 2/2] =?UTF-8?q?fix(admin=5Fbasic):=20=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EC=95=84=EC=9B=83=20=EC=BD=94=EB=93=9C=20=ED=8E=B8=EC=A7=91=20?= =?UTF-8?q?=ED=99=94=EB=A9=B4=EC=9D=98=20=ED=8C=A8=EB=84=90=20=ED=8F=AD?= =?UTF-8?q?=C2=B7=EB=AA=A9=EB=A1=9D=20=EA=B0=B1=EC=8B=A0=C2=B7=EB=B2=84?= =?UTF-8?q?=EC=A0=84=20=EC=9D=B4=EB=A0=A5=20=EB=8F=84=EB=8B=AC=20=EB=B2=94?= =?UTF-8?q?=EC=9C=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 리베이스 결과를 브라우저로 검수하다 같은 화면에서 셋을 확인했다. 셋 다 응답에도 콘솔에도 신호가 없고 화면에서만 드러나는 종류다. - 두 카드 어디에도 폭 규약이 없어 flex 가 max-content 비율로 폭을 나눴다. 모듈 파티션 레이아웃의 공백 없는 긴 파일명이 목록의 max-content 를 밀어 올려 편집기가 절반 이하로 눌렸다(실측 목록 814 / 편집기 473 → 384 / 903). 이름은 자르지 않고 접는다 — 잘라내면 어느 파티션인지 구분할 수 없다. - 저장 뒤 상세만 다시 불러서, 방금 저장한 파일의 목록 행이 저장 전 설명·크기·수정일을 그대로 달고 있었다. 목록 응답이 본문을 싣지 않게 된 뒤로 재조회가 수십 KB 라 매 저장마다 부른다. - 서버가 버전 목록에 기본 상한 100 을 도입했는데 이 화면에는 limit 도 '더 보기' 도 없어 상한 밖 버전이 도달 불가였다(실측 114건 중 14건). 페이징이 위지윅 편집기에만 들어가 상한 도입이 이 화면에서는 그대로 기능 축소가 됐다. 회귀는 레이아웃 구조(5건)와 브라우저(6건) 양쪽에서 잠근다. 브라우저 쪽은 수정을 되돌린 상태로 fail 을 먼저 확인했다. --- .../CHANGELOG.md | 6 + .../frontend/partial/admin.json | 4 +- .../language-pack.json | 2 +- .../_bundled/sirsoft-admin_basic/CHANGELOG.md | 3 + ...e-layout-edit-panels-and-versions.test.tsx | 195 ++++++++++++++++++ .../lang/partial/en/admin.json | 4 +- .../lang/partial/ko/admin.json | 4 +- .../layouts/admin_template_layout_edit.json | 64 ++++-- .../_modal_version_history.json | 71 ++++++- ...ode-editor-panels-and-list-refresh.spec.ts | 109 ++++++++++ ...code-editor-version-history-paging.spec.ts | 165 +++++++++++++++ 11 files changed, 599 insertions(+), 28 deletions(-) create mode 100644 templates/_bundled/sirsoft-admin_basic/__tests__/layouts/admin-template-layout-edit-panels-and-versions.test.tsx create mode 100644 templates/_bundled/sirsoft-admin_basic/tests/Playwright/specs/admin/code-editor-panels-and-list-refresh.spec.ts create mode 100644 templates/_bundled/sirsoft-admin_basic/tests/Playwright/specs/admin/code-editor-version-history-paging.spec.ts 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 0c313879..f3da91f7 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 @@ -4,6 +4,12 @@ 형식은 [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/)를 따르며, [Semantic Versioning](https://semver.org/lang/ko/)을 준수합니다. +## [1.0.6] - 2026-08-05 + +### Added + +- 레이아웃 버전 이력의 「이전 버전 더 보기」 버튼 문구 일본어 번역 추가 — 기본 표시 개수를 넘는 오래된 버전을 여는 버튼이 일본어 로케일에서도 정상 표기됩니다. + ## [1.0.5] - 2026-08-01 ### Added 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 55328ee1..5f919366 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 @@ -2159,7 +2159,9 @@ "saved_by": "保存者:{{name}}", "lines": "行", "modified": "編集", - "chars": "個" + "chars": "個", + "load_more": "以前のバージョンをもっと見る", + "limit_reached": "表示できる最大件数に達しました。" }, "wysiwyg_select_layout_title": "ウィジウィグ編集 - レイアウト選択", "wysiwyg_select_layout_description": "編集するレイアウトを選択してください。選択したレイアウトが新しいタブでウィジウィグ編集モードで開きます。", diff --git a/lang-packs/_bundled/g7-template-sirsoft-admin_basic-ja/language-pack.json b/lang-packs/_bundled/g7-template-sirsoft-admin_basic-ja/language-pack.json index 063f3b9c..a69f93ef 100644 --- a/lang-packs/_bundled/g7-template-sirsoft-admin_basic-ja/language-pack.json +++ b/lang-packs/_bundled/g7-template-sirsoft-admin_basic-ja/language-pack.json @@ -12,7 +12,7 @@ "en": "G7 template (sirsoft-admin_basic) Japanese language pack (bundled)", "ja": "G7 テンプレート (sirsoft-admin_basic) 日本語 言語パック(バンドル)" }, - "version": "1.0.5", + "version": "1.0.6", "license": "MIT", "scope": "template", "target_identifier": "sirsoft-admin_basic", diff --git a/templates/_bundled/sirsoft-admin_basic/CHANGELOG.md b/templates/_bundled/sirsoft-admin_basic/CHANGELOG.md index e4cb77a2..6765a254 100644 --- a/templates/_bundled/sirsoft-admin_basic/CHANGELOG.md +++ b/templates/_bundled/sirsoft-admin_basic/CHANGELOG.md @@ -32,6 +32,9 @@ ### Fixed +- 레이아웃 편집 화면에서 파일 목록이 편집기보다 넓게 자리를 차지해 코드가 좁게 보이던 문제를 수정했습니다. 파일 이름이 긴 레이아웃(모듈이 제공하는 부분 화면 등)이 많을수록 편집 영역이 좁아졌습니다. 이제 파일 목록은 일정한 너비를 쓰고 남는 공간은 모두 편집기가 사용하며, 긴 이름은 잘리지 않고 줄바꿈되어 표시됩니다. +- 레이아웃을 저장해도 왼쪽 파일 목록의 설명·크기·수정일이 저장 전 값 그대로 남아 있던 문제를 수정했습니다. 이제 저장 즉시 목록에도 반영됩니다. +- 레이아웃 버전 이력에서 최근 100개까지만 볼 수 있고 그보다 오래된 버전은 열 수 없던 문제를 수정했습니다. 이력 창 아래에 「이전 버전 더 보기」를 추가해 오래된 버전까지 확인하고 복구할 수 있습니다. - 표와 카드 목록에서 항목별 표시 조건이 서로 다르게 판정되던 문제를 수정했습니다. 같은 조건을 써도 표에서는 항상 보이고 카드에서는 아무것도 보이지 않는 식으로 결과가 갈렸으며, 특히 화면 상태값을 참조하는 조건에서 그랬습니다. 이제 두 목록 모두 다른 화면 요소와 같은 기준으로 조건을 판정합니다. - 표 하단 합계 영역에 넣은 내용이 표 본문과 다른 방식으로 처리되던 문제를 수정했습니다. 합계 영역도 각 항목과 같은 규칙으로 값을 해석합니다. - 표 하단 합계 영역의 값이 계산되지 않고 표현식 원문 그대로 표시되던 문제를 수정했습니다. 주문 상세의 합계 행에서 수량·적립금·배송비 자리에 값 대신 알 수 없는 문자열이 보였습니다. diff --git a/templates/_bundled/sirsoft-admin_basic/__tests__/layouts/admin-template-layout-edit-panels-and-versions.test.tsx b/templates/_bundled/sirsoft-admin_basic/__tests__/layouts/admin-template-layout-edit-panels-and-versions.test.tsx new file mode 100644 index 00000000..a5d88450 --- /dev/null +++ b/templates/_bundled/sirsoft-admin_basic/__tests__/layouts/admin-template-layout-edit-panels-and-versions.test.tsx @@ -0,0 +1,195 @@ +/** + * @file admin-template-layout-edit-panels-and-versions.test.tsx + * @description 레이아웃 코드 편집 화면 — 패널 폭 규약 / 저장 후 목록 갱신 / 버전 이력 상한 회귀 테스트 + * + * 브라우저 실측(2026-08-05)에서 이 화면의 결함 3건이 드러났다. 셋 다 응답이나 콘솔에는 + * 아무 신호가 없고 화면에서만 드러나는 종류라, 계약을 레이아웃 JSON 수준에서 고정한다. + * + * 1. **패널 폭 규약 부재** — 파일 목록과 편집기 카드 어느 쪽에도 폭 지정이 없어 flex 가 + * 두 카드의 max-content 비율로 폭을 나눴다. 모듈 파티션 레이아웃 이름은 공백이 없는 + * 80자 이상이라 목록의 max-content 를 900px 이상으로 밀어 올렸고, 편집기가 473px 로 + * 눌렸다(실측 목록 814 / 편집기 473). 이름이 길어질수록 편집기가 좁아지는 구조다. + * + * 2. **저장 후 목록 미갱신** — 저장 성공 시 상세(`current_layout`)만 다시 불러서, 방금 + * 저장한 파일의 목록 행이 저장 전 설명·크기·수정일을 그대로 달고 있었다. 목록 응답이 + * 본문을 싣지 않게 된 뒤로 재조회가 수십 KB 라 매 저장마다 불러도 된다. + * + * 3. **버전 이력 상한 도달 불가** — 서버가 버전 목록에 기본 상한 100 을 도입(#518)했는데 + * 이 화면의 데이터소스는 `limit` 도 '더 보기' 도 없었다. 실측: DB 114건 중 100건만 + * 내려오고 나머지 14건은 어떤 조작으로도 열 수 없었다. + */ + +import fs from 'fs'; +import path from 'path'; +import { describe, it, expect } from 'vitest'; + +const LAYOUT = path.resolve(__dirname, '../../layouts/admin_template_layout_edit.json'); +const VERSION_MODAL = path.resolve( + __dirname, + '../../layouts/partials/admin_template_layout_edit/_modal_version_history.json', +); + +/** 서버 `LayoutVersionListRequest` 의 DEFAULT_LIMIT / MAX_LIMIT 과 같아야 하는 값 */ +const VERSION_PAGE_SIZE = 100; +const VERSION_MAX_LIMIT = 500; + +/** + * JSON 레이아웃을 읽어 파싱합니다. + * + * @param file 대상 파일 절대경로 + * @returns 파싱된 레이아웃 객체 + */ +function readLayout(file: string): any { + return JSON.parse(fs.readFileSync(file, 'utf-8')); +} + +/** + * 트리에서 조건을 만족하는 첫 노드를 찾습니다. + * + * @param node 탐색 시작 노드 + * @param predicate 판정 함수 + * @returns 찾은 노드 또는 null + */ +function findNode(node: any, predicate: (n: any) => boolean): any | null { + if (!node || typeof node !== 'object') return null; + if (!Array.isArray(node) && predicate(node)) return node; + + for (const value of Array.isArray(node) ? node : Object.values(node)) { + const hit = findNode(value, predicate); + if (hit) return hit; + } + + return null; +} + +/** + * 트리에서 조건을 만족하는 모든 노드를 모읍니다. + * + * @param node 탐색 시작 노드 + * @param predicate 판정 함수 + * @returns 찾은 노드 배열 + */ +function collectNodes(node: any, predicate: (n: any) => boolean): any[] { + const found: any[] = []; + + const walk = (n: any): void => { + if (!n || typeof n !== 'object') return; + if (!Array.isArray(n) && predicate(n)) found.push(n); + for (const value of Array.isArray(n) ? n : Object.values(n)) walk(value); + }; + + walk(node); + + return found; +} + +describe('레이아웃 코드 편집 화면 — 패널 폭 규약', () => { + it('파일 목록과 편집기가 각자 폭 규약을 갖는다 (내용 길이로 폭이 갈리지 않는다)', () => { + const layout = readLayout(LAYOUT); + + const listCard = findNode(layout, (n) => n.id === 'file_list_card'); + const editorCard = findNode(layout, (n) => n.id === 'editor_card'); + + expect(listCard, 'file_list_card 노드가 사라졌다').toBeTruthy(); + expect(editorCard, 'editor_card 노드가 사라졌다').toBeTruthy(); + + const listClass = String(listCard.props?.className ?? ''); + const editorClass = String(editorCard.props?.className ?? ''); + + // 목록: 고정 폭 + 줄어들지 않음 + expect(listClass, '파일 목록에 고정 폭이 없으면 긴 파일명이 편집기 폭을 빼앗는다').toMatch(/\bw-\d+\b/); + expect(listClass, '파일 목록이 shrink 되면 고정 폭 선언이 무력해진다').toContain('shrink-0'); + + // 편집기: 남는 폭 전부 + 최소폭 해제 + expect(editorClass, '편집기가 flex-1 이 아니면 남는 폭을 가져가지 못한다').toContain('flex-1'); + expect(editorClass, 'min-w-0 이 없으면 코드의 긴 줄이 편집기 최소폭을 밀어 올린다').toContain('min-w-0'); + }); + + it('파일명과 설명이 줄바꿈되어 목록 폭을 넘지 않는다', () => { + const layout = readLayout(LAYOUT); + + // 파일명 노드 — `{{file.name}}.json` + const nameNode = findNode(layout, (n) => typeof n.text === 'string' && n.text.includes('{{file.name}}.json')); + expect(nameNode, '파일명 노드를 찾지 못했다').toBeTruthy(); + expect( + String(nameNode.props?.className ?? ''), + '모듈 파티션 이름은 공백이 없어 break-all 없이는 끊기지 않는다', + ).toContain('break-all'); + + // 설명 노드 — 설명이 없으면 이름으로 폴백하므로 같은 규칙이 필요하다 + const descNode = findNode( + layout, + (n) => typeof n.text === 'string' && n.text.includes('file.description') && n.text.includes('file.name'), + ); + expect(descNode, '설명 노드를 찾지 못했다').toBeTruthy(); + expect( + String(descNode.props?.className ?? ''), + '설명은 이름으로 폴백하므로 파일명과 같은 줄바꿈 규칙이 필요하다', + ).toContain('break-all'); + }); +}); + +describe('레이아웃 코드 편집 화면 — 저장 후 목록 갱신', () => { + it('저장 성공 시 상세와 목록을 모두 다시 부른다', () => { + const layout = readLayout(LAYOUT); + + const saveCall = findNode( + layout, + (n) => n.handler === 'apiCall' && n.params?.method === 'PUT' && String(n.target ?? '').includes('/layouts/'), + ); + expect(saveCall, '저장 apiCall 액션을 찾지 못했다').toBeTruthy(); + + const refetched = collectNodes(saveCall.onSuccess ?? [], (n) => n.handler === 'refetchDataSource').map( + (n) => n.params?.dataSourceId, + ); + + expect(refetched, '저장 후 상세를 다시 부르지 않으면 lock_version 이 낡는다').toContain('current_layout'); + expect( + refetched, + '저장 후 목록을 다시 부르지 않으면 방금 저장한 행이 저장 전 설명·크기·수정일을 그대로 단다', + ).toContain('layout_files'); + }); +}); + +describe('레이아웃 코드 편집 화면 — 버전 이력 상한', () => { + it('버전 목록 조회에 limit 이 붙고 열 때마다 첫 묶음으로 초기화된다', () => { + const layout = readLayout(LAYOUT); + + const source = (layout.data_sources ?? []).find((d: any) => d.id === 'layout_versions'); + expect(source, 'layout_versions 데이터소스가 사라졌다').toBeTruthy(); + expect( + String(source.endpoint), + 'limit 이 없으면 서버 기본 상한을 넘는 버전은 어떤 조작으로도 열 수 없다', + ).toContain('limit='); + + // 모달 진입 시 상한 초기화 — 남겨두면 다른 레이아웃 이력 조회로 이월된다 + const openSequence = collectNodes( + layout, + (n) => n.handler === 'setState' && n.params?.layoutVersionLimit !== undefined, + ); + expect(openSequence.length, '모달을 열 때 조회 상한을 초기화하지 않는다').toBeGreaterThan(0); + expect(openSequence.some((n) => n.params.layoutVersionLimit === VERSION_PAGE_SIZE)).toBe(true); + }); + + it('더 보기 버튼이 상한을 한 묶음씩 넓히고 서버 상한에서 멈춘다', () => { + const modal = readLayout(VERSION_MODAL); + + const loadMore = findNode(modal, (n) => n.props?.['data-testid'] === 'layout-version-history-load-more'); + expect(loadMore, '더 보기 버튼이 없으면 상한 밖 버전은 도달 불가다').toBeTruthy(); + + const wrapper = findNode( + modal, + (n) => typeof n.if === 'string' && n.if.includes('layoutVersionLimit') && n.if.includes('layout_versions'), + ); + expect(wrapper, '더 보기 노출 조건이 없다').toBeTruthy(); + expect( + String(wrapper.if), + `서버 상한(${VERSION_MAX_LIMIT})에서 멈추지 않으면 그 이상 요청이 422 가 되어 목록 전체가 사라진다`, + ).toContain(String(VERSION_MAX_LIMIT)); + + const raw = JSON.stringify(loadMore); + expect(raw, '더 보기가 상한을 넓히지 않는다').toContain('layoutVersionLimit'); + expect(raw, '넓힌 뒤 다시 조회하지 않으면 화면이 그대로다').toContain('layout_versions'); + expect(raw, `한 묶음(${VERSION_PAGE_SIZE})씩 넓혀야 한다`).toContain(String(VERSION_PAGE_SIZE)); + }); +}); diff --git a/templates/_bundled/sirsoft-admin_basic/lang/partial/en/admin.json b/templates/_bundled/sirsoft-admin_basic/lang/partial/en/admin.json index 46ee8cf4..ac1dfd61 100644 --- a/templates/_bundled/sirsoft-admin_basic/lang/partial/en/admin.json +++ b/templates/_bundled/sirsoft-admin_basic/lang/partial/en/admin.json @@ -2156,7 +2156,9 @@ "saved_by": "Saved by: {{name}}", "lines": "lines", "modified": "modified", - "chars": "chars" + "chars": "chars", + "load_more": "Load older versions", + "limit_reached": "Reached the maximum number of versions that can be listed." }, "extension_version_history": { "title": "Extension Version History", diff --git a/templates/_bundled/sirsoft-admin_basic/lang/partial/ko/admin.json b/templates/_bundled/sirsoft-admin_basic/lang/partial/ko/admin.json index cb36a768..a9b2fd4b 100644 --- a/templates/_bundled/sirsoft-admin_basic/lang/partial/ko/admin.json +++ b/templates/_bundled/sirsoft-admin_basic/lang/partial/ko/admin.json @@ -2160,7 +2160,9 @@ "saved_by": "저장자: {{name}}", "lines": "라인", "modified": "수정", - "chars": "자" + "chars": "자", + "load_more": "이전 버전 더 보기", + "limit_reached": "표시할 수 있는 최대 개수에 도달했습니다." }, "extension_version_history": { "title": "확장 버전 히스토리", diff --git a/templates/_bundled/sirsoft-admin_basic/layouts/admin_template_layout_edit.json b/templates/_bundled/sirsoft-admin_basic/layouts/admin_template_layout_edit.json index 18714b12..12459d06 100644 --- a/templates/_bundled/sirsoft-admin_basic/layouts/admin_template_layout_edit.json +++ b/templates/_bundled/sirsoft-admin_basic/layouts/admin_template_layout_edit.json @@ -76,7 +76,8 @@ "id": "layout_versions", "label_key": "$t:editor.data_source.layout_versions", "type": "api", - "endpoint": "/api/admin/templates/{{route.identifier}}/layouts/{{_global.selectedLayoutName}}/versions", + "comment": "limit 는 서버 기본 상한(100)과 같은 값에서 출발해 '더 보기' 마다 한 묶음씩 넓힌다. 서버가 상한 조회만 지원하므로(#518) 이 파라미터가 없으면 100건을 넘는 버전은 이 화면에서 도달할 수 없다. 상한 500 은 서버 LayoutVersionListRequest::MAX_LIMIT 과 같아야 하며, 넘기면 목록 전체가 422 가 된다.", + "endpoint": "/api/admin/templates/{{route.identifier}}/layouts/{{_global.selectedLayoutName}}/versions?limit={{_global.layoutVersionLimit ?? 100}}", "method": "GET", "auto_fetch": false, "auth_required": true, @@ -229,7 +230,7 @@ "name": "Icon", "props": { "name": "fa-regular fa-eye", - "className": "w-4 h-4" + "className": "text-base" } }, { @@ -312,7 +313,7 @@ "name": "Icon", "props": { "name": "fa-regular fa-eye", - "className": "w-4 h-4" + "className": "text-base" } }, { @@ -330,8 +331,10 @@ "name": "Button", "if": "{{_global.selectedNodeKind !== 'extension' && current_layout?.data?.abilities?.can_update !== false}}", "props": { + "type": "button", "className": "flex-center gap-2 px-4 py-2 bg-gray-900 dark:bg-white text-white dark:text-gray-900 rounded-lg hover:bg-gray-800 dark:hover:bg-gray-100 transition-colors disabled:opacity-50", - "disabled": "{{_global.isSaving}}" + "disabled": "{{_global.isSaving}}", + "data-testid": "layout-save" }, "actions": [ { @@ -376,6 +379,13 @@ "params": { "dataSourceId": "current_layout" } + }, + { + "comment": "목록도 함께 다시 부른다 — 파일 목록은 저장으로 바뀌는 값(설명·크기·수정일)을 표시하므로, 상세만 갱신하면 방금 저장한 파일의 행이 저장 전 값으로 남는다. 목록 응답이 본문을 싣지 않게 된 뒤로 재조회 비용이 수십 KB 수준이라 매 저장마다 불러도 된다.", + "handler": "refetchDataSource", + "params": { + "dataSourceId": "layout_files" + } } ], "onError": [ @@ -405,7 +415,7 @@ "if": "{{!_global.isSaving}}", "props": { "name": "fa-regular fa-floppy-disk", - "className": "w-4 h-4" + "className": "text-base" } }, { @@ -510,7 +520,7 @@ "if": "{{!_global.isSaving}}", "props": { "name": "fa-regular fa-floppy-disk", - "className": "w-4 h-4" + "className": "text-base" } }, { @@ -565,7 +575,7 @@ "name": "Icon", "props": { "name": "fa-regular fa-folder-open", - "className": "text-secondary w-5 h-5" + "className": "text-secondary text-xl" } }, { @@ -763,8 +773,9 @@ "id": "file_list_card", "type": "basic", "name": "Div", + "comment": "파일 목록은 고정 폭(w-96)이다. 폭 규약이 없으면 flex 가 두 카드의 max-content 비율로 나누는데, 모듈 파티션 레이아웃의 긴 파일명(줄바꿈 지점 없는 80자 이상)이 목록의 max-content 를 900px 이상으로 밀어 올려 편집기가 절반 이하로 눌린다.", "props": { - "className": "admin-card" + "className": "admin-card w-96 shrink-0" }, "responsive": { "portable": { @@ -793,7 +804,7 @@ "name": "Icon", "props": { "name": "fa-regular fa-folder-open", - "className": "text-secondary w-5 h-5" + "className": "text-secondary text-xl" } }, { @@ -895,7 +906,7 @@ "type": "basic", "name": "Div", "props": { - "className": "flex-center gap-2" + "className": "flex-center gap-2 min-w-0" }, "children": [ { @@ -903,14 +914,15 @@ "name": "Icon", "props": { "name": "fa-regular fa-file-code", - "className": "w-4 h-4 text-gray-400 dark:text-gray-500" + "className": "text-base text-gray-400 dark:text-gray-500" } }, { "type": "basic", "name": "Span", + "comment": "break-all — 모듈 파티션 레이아웃 이름은 공백이 없어 기본 줄바꿈 규칙으로는 끊기지 않는다. 자르지 않고 접어서 전체 이름을 보이게 한다(잘라내면 어느 파티션인지 구분 불가).", "props": { - "className": "text-primary font-medium text-sm" + "className": "text-primary font-medium text-sm break-all" }, "text": "{{file.name}}.json" }, @@ -928,8 +940,9 @@ { "type": "basic", "name": "P", + "comment": "설명이 없는 레이아웃은 이름으로 폴백하므로 파일명과 같은 줄바꿈 규칙이 필요하다. break-all 이 없으면 모듈 파티션 이름이 목록에 가로 스크롤을 만든다.", "props": { - "className": "form-hint" + "className": "form-hint break-all" }, "text": "{{raw:file.description || file.name}}" }, @@ -1008,7 +1021,7 @@ "name": "Icon", "props": { "name": "{{extGroup.source_type === 'plugin' ? 'fa-solid fa-plug' : 'fa-solid fa-puzzle-piece'}}", - "className": "w-3.5 h-3.5 text-gray-500 dark:text-gray-400" + "className": "text-sm text-gray-500 dark:text-gray-400" } }, { @@ -1090,7 +1103,7 @@ "name": "Icon", "props": { "name": "fa-solid fa-circle-plus", - "className": "w-3.5 h-3.5 text-gray-400 dark:text-gray-500" + "className": "text-sm text-gray-400 dark:text-gray-500" } }, { @@ -1176,8 +1189,9 @@ "id": "editor_card", "type": "basic", "name": "Div", + "comment": "편집기는 남는 폭을 전부 가져간다. min-w-0 이 없으면 코드 영역의 긴 줄이 flex 아이템의 최소 폭을 밀어 올려 목록 쪽으로 폭을 되돌려준다.", "props": { - "className": "admin-card" + "className": "admin-card flex-1 min-w-0" }, "children": [ { @@ -1217,7 +1231,7 @@ "name": "Icon", "props": { "name": "fa-regular fa-file-code", - "className": "text-secondary w-5 h-5" + "className": "text-secondary text-xl" } }, { @@ -1297,7 +1311,9 @@ "type": "basic", "name": "Button", "props": { - "className": "flex-center gap-2 px-3 py-1.5 border border-gray-300 dark:border-gray-600 text-body rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors" + "type": "button", + "className": "flex-center gap-2 px-3 py-1.5 border border-gray-300 dark:border-gray-600 text-body rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors", + "data-testid": "layout-version-history-open" }, "actions": [ { @@ -1321,6 +1337,14 @@ }, { "then": [ + { + "comment": "모달을 열 때마다 조회 상한을 첫 묶음으로 되돌린다. 남겨두면 이전에 넓혀 둔 상한이 다른 레이아웃의 이력 조회에 그대로 이월된다.", + "handler": "setState", + "params": { + "target": "global", + "layoutVersionLimit": 100 + } + }, { "handler": "refetchDataSource", "params": { @@ -1342,7 +1366,7 @@ "name": "Icon", "props": { "name": "fa-solid fa-clock-rotate-left", - "className": "w-4 h-4" + "className": "text-base" } }, { @@ -1408,7 +1432,7 @@ "name": "Icon", "props": { "name": "fa-solid fa-circle-info", - "className": "w-4 h-4" + "className": "text-base" } }, { diff --git a/templates/_bundled/sirsoft-admin_basic/layouts/partials/admin_template_layout_edit/_modal_version_history.json b/templates/_bundled/sirsoft-admin_basic/layouts/partials/admin_template_layout_edit/_modal_version_history.json index 55afdd23..95f6a559 100644 --- a/templates/_bundled/sirsoft-admin_basic/layouts/partials/admin_template_layout_edit/_modal_version_history.json +++ b/templates/_bundled/sirsoft-admin_basic/layouts/partials/admin_template_layout_edit/_modal_version_history.json @@ -23,7 +23,8 @@ "type": "basic", "name": "Div", "props": { - "className": "space-y-3 max-h-96 overflow-y-auto" + "className": "space-y-3 max-h-96 overflow-y-auto", + "data-testid": "layout-version-history-list" }, "children": [ { @@ -36,7 +37,8 @@ }, "blur_until_loaded": "{{_global.isRestoring}}", "props": { - "className": "border border-gray-200 dark:border-gray-700 rounded-lg p-4" + "className": "border border-gray-200 dark:border-gray-700 rounded-lg p-4", + "data-testid": "layout-version-row-{{version.version ?? idx}}" }, "children": [ { @@ -166,7 +168,7 @@ "if": "{{!_global.isRestoring}}", "props": { "name": "refresh", - "className": "w-4 h-4" + "className": "text-base" } }, { @@ -232,6 +234,67 @@ } ] }, + { + "comment": "받은 묶음이 상한만큼 꽉 찼으면 더 오래된 버전이 남아 있다는 뜻이라 '더 보기' 를 노출한다. 서버 상한(500)에 도달하면 감춘다 — 더 넓히면 목록 전체가 422 가 되어 보고 있던 이력까지 사라진다.", + "if": "{{(layout_versions?.data?.length ?? 0) >= (_global.layoutVersionLimit ?? 100) && (_global.layoutVersionLimit ?? 100) < 500}}", + "type": "basic", + "name": "Div", + "props": { + "className": "flex-center justify-center mt-3" + }, + "children": [ + { + "type": "basic", + "name": "Button", + "props": { + "type": "button", + "className": "btn-secondary w-full", + "data-testid": "layout-version-history-load-more" + }, + "actions": [ + { + "type": "click", + "handler": "sequence", + "params": { + "actions": [ + { + "handler": "setState", + "params": { + "target": "global", + "layoutVersionLimit": "{{Math.min((_global.layoutVersionLimit ?? 100) + 100, 500)}}" + } + }, + { + "handler": "refetchDataSource", + "params": { + "dataSourceId": "layout_versions" + } + } + ] + } + } + ], + "children": [ + { + "type": "basic", + "name": "Span", + "text": "$t:admin.templates.modals.version_history.load_more" + } + ] + } + ] + }, + { + "comment": "서버 상한까지 넓혔는데도 묶음이 꽉 차 있으면 더 오래된 버전이 남아 있지만 이 화면에서는 도달할 수 없다. '더 보기' 만 조용히 사라지면 목록이 끝난 것으로 읽히므로 도달 한계를 명시한다.", + "if": "{{(layout_versions?.data?.length ?? 0) >= (_global.layoutVersionLimit ?? 100) && (_global.layoutVersionLimit ?? 100) >= 500}}", + "type": "basic", + "name": "Div", + "props": { + "className": "text-xs text-secondary text-center mt-3", + "data-testid": "layout-version-history-limit-reached" + }, + "text": "$t:admin.templates.modals.version_history.limit_reached" + }, { "type": "basic", "name": "Div", @@ -251,7 +314,7 @@ "name": "Icon", "props": { "name": "info-circle", - "className": "w-4 h-4 text-blue-600 dark:text-blue-400" + "className": "text-base text-blue-600 dark:text-blue-400" } }, { diff --git a/templates/_bundled/sirsoft-admin_basic/tests/Playwright/specs/admin/code-editor-panels-and-list-refresh.spec.ts b/templates/_bundled/sirsoft-admin_basic/tests/Playwright/specs/admin/code-editor-panels-and-list-refresh.spec.ts new file mode 100644 index 00000000..8dc58b85 --- /dev/null +++ b/templates/_bundled/sirsoft-admin_basic/tests/Playwright/specs/admin/code-editor-panels-and-list-refresh.spec.ts @@ -0,0 +1,109 @@ +/** + * sirsoft-admin_basic — 코드 편집기 패널 폭 규약과 저장 후 목록 갱신 + * + * 브라우저 실측(2026-08-05)에서 드러난 두 결함을 화면 쪽에서 잠근다. 둘 다 응답에도 + * 콘솔에도 신호가 없고 화면에서만 드러나는 종류라, 레이아웃 JSON 구조 단언만으로는 + * "실제로 그렇게 그려지는가 / 실제로 그 요청이 나가는가" 가 남지 않는다. + * + * 1. **패널 폭** — 두 카드 어디에도 폭 규약이 없어 flex 가 max-content 비율로 폭을 + * 나눴다. 모듈 파티션 레이아웃 이름(줄바꿈 지점 없는 80자 이상)이 목록을 밀어 올려 + * 편집기가 절반 이하로 눌렸다(실측 목록 814 / 편집기 473). + * 2. **저장 후 목록** — 저장 성공 시 상세만 다시 불러서, 방금 저장한 파일의 목록 행이 + * 저장 전 설명·크기·수정일을 그대로 달고 있었다. + * + * 저장은 응답을 고정해 실제 파일을 건드리지 않는다. 검사 대상은 **브라우저가 실제로 + * 만든 요청 순서** 다 — 목록 재조회가 저장 뒤에 오지 않으면 화면은 낡은 행을 그린다. + * + * @scenario surface=code_editor,entry=direct + * @effects code_editor_editor_panel_wider_than_file_list, code_editor_list_refetched_after_save + */ +import { test, expect, authenticatePage } from '../../fixtures/admin-template-auth'; +import type { Page, Route } from '@playwright/test'; + +/** 목록 조회 — 뒤에 세그먼트가 붙는 단건/버전 조회와 구분한다 */ +const LIST_URL_RE = /\/api\/admin\/templates\/[^/]+\/layouts(\?[^/]*)?$/; + +/** 단건 저장 — 목록 URL 뒤에 레이아웃 이름 한 세그먼트 */ +const SAVE_URL_RE = /\/api\/admin\/templates\/[^/]+\/layouts\/[^/?]+(\?.*)?$/; + +/** + * 코드 편집기를 열고 파일 목록이 그려질 때까지 기다립니다. + * + * @param page Playwright page + * @param token 인증 토큰 + */ +async function openCodeEditor(page: Page, token: string): Promise { + await authenticatePage(page, token); + await page.goto('/admin/templates/sirsoft-admin_basic/edit'); + await page.waitForLoadState('domcontentloaded', { timeout: 30_000 }); + await page.waitForSelector('#file_list_card', { timeout: 30_000 }); + await expect(page.locator('#editor_card').getByText(/\.json$/).first()).toBeVisible({ timeout: 30_000 }); +} + +test.describe('@sirsoft-admin_basic 코드 편집기 패널 폭', () => { + test('편집기가 파일 목록보다 넓고, 긴 파일명이 목록 폭을 넘지 않는다', async ({ page, layoutEditToken }) => { + await openCodeEditor(page, layoutEditToken); + + const measured = await page.evaluate(() => { + const list = document.querySelector('#file_list_card')!; + const editor = document.querySelector('#editor_card')!; + + // 목록 안에서 가로 스크롤을 만드는 요소 — 긴 파일명이 끊기지 않으면 여기서 잡힌다 + const overflowing = [...list.querySelectorAll('*')].filter((e) => e.scrollWidth > e.clientWidth + 1).length; + + return { + listWidth: Math.round(list.getBoundingClientRect().width), + editorWidth: Math.round(editor.getBoundingClientRect().width), + overflowing, + }; + }); + + expect(measured.editorWidth, '편집기가 파일 목록보다 좁으면 코드를 볼 수 없다').toBeGreaterThan( + measured.listWidth, + ); + expect(measured.overflowing, '목록 안에 가로 스크롤이 생기면 파일명이 끊기지 않은 것이다').toBe(0); + }); +}); + +test.describe('@sirsoft-admin_basic 코드 편집기 저장 후 목록 갱신', () => { + test('저장이 성공하면 파일 목록을 다시 부른다', async ({ page, layoutEditToken }) => { + const calls: string[] = []; + + // 저장은 고정 응답 — 실제 파일과 lock_version 을 건드리지 않는다 + await page.route(SAVE_URL_RE, async (route: Route) => { + if (route.request().method() !== 'PUT') { + await route.fallback(); + + return; + } + + calls.push('save'); + await route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({ success: true, message: '저장되었습니다.', data: { lock_version: 1 } }), + }); + }); + + page.on('request', (req) => { + if (req.method() === 'GET' && LIST_URL_RE.test(req.url())) calls.push('list'); + }); + + await openCodeEditor(page, layoutEditToken); + + const before = calls.length; + await page.getByTestId('layout-save').click(); + + await expect + .poll(() => calls.includes('save'), { timeout: 20_000 }) + .toBe(true); + + // 저장 뒤에 목록 조회가 한 번 더 나가야 한다. 상세만 다시 부르면 방금 저장한 행이 + // 저장 전 설명·크기·수정일을 그대로 단 채 남는다. + await expect + .poll(() => calls.slice(calls.indexOf('save')).includes('list'), { timeout: 20_000 }) + .toBe(true); + + expect(calls.length, '진입 시점 요청만으로 통과하면 안 된다').toBeGreaterThan(before); + }); +}); diff --git a/templates/_bundled/sirsoft-admin_basic/tests/Playwright/specs/admin/code-editor-version-history-paging.spec.ts b/templates/_bundled/sirsoft-admin_basic/tests/Playwright/specs/admin/code-editor-version-history-paging.spec.ts new file mode 100644 index 00000000..03ad55bc --- /dev/null +++ b/templates/_bundled/sirsoft-admin_basic/tests/Playwright/specs/admin/code-editor-version-history-paging.spec.ts @@ -0,0 +1,165 @@ +/** + * sirsoft-admin_basic — 코드 편집기 버전 이력의 상한 조회와 '더 보기' + * + * 버전 행은 저장할 때마다 쌓이고 정리되지 않는다. 목록이 전량을 내려주면 오래 쓴 레이아웃일수록 + * 모달 하나를 여는 데 수천 행이 실린다(#518). 서버가 조회 건수를 제한한 뒤로 화면이 상한을 + * 넘겨주지 않으면 100건 밖의 버전은 이 화면에서 도달할 수 없다 — 오류 없이 목록이 끝난 것처럼 + * 보일 뿐이라 화면만 보고는 알 수 없다. + * + * 위지윅 편집기의 같은 기능은 React 훅(useLayoutVersions)이 담당하고 코어 + * layout-editor/version-history-paging.spec.ts 가 잠근다. 이 spec 은 **레이아웃 JSON 이 + * 구동하는 코드 편집기** 라는 별개 표면을 잠근다 — 두 화면은 코드를 공유하지 않으므로 + * 한쪽이 초록이어도 다른 쪽은 전량 요청일 수 있다. + * + * 응답은 고정한다(개발 사이트의 실제 버전 수에 좌우되지 않도록). 요청 URL 은 브라우저가 + * 실제로 만든 것을 검사한다. + * + * @scenario resource=layout_version,endpoint=list,surface=code_editor + * @effects version_list_request_carries_limit, version_list_load_more_widens_limit, version_list_load_more_hidden_when_batch_not_full, version_list_limit_ceiling_disclosed + */ +import { test, expect, authenticatePage } from '../../fixtures/admin-template-auth'; +import type { Page, Route } from '@playwright/test'; + +/** 한 묶음 크기 — admin_template_layout_edit.json 의 layoutVersionLimit 기본값과 같은 값 */ +const VERSION_PAGE_SIZE = 100; + +/** 서버 상한 — LayoutVersionListRequest::MAX_LIMIT 과 같은 값 */ +const VERSION_MAX_LIMIT = 500; + +const VERSIONS_URL_RE = /\/api\/admin\/templates\/[^/]+\/layouts\/[^/]+\/versions(\?|$)/; + +/** + * 버전 목록 응답 한 건을 만듭니다. + * + * @param version 버전 번호 + * @returns 목록 항목 + */ +function versionRow(version: number): Record { + return { + id: version, + version, + is_latest: version === 1, + created_at: '2026-01-01T00:00:00+09:00', + created_by_name: 'tester', + description: `v${version}`, + lines_count: 10, + chars_count: 100, + }; +} + +/** + * 버전 목록 응답을 대체하고, 나간 요청 URL 을 수집합니다. + * + * `count` 를 생략하면 요청한 상한만큼(서버가 가진 버전이 상한보다 많은 상태) 돌려준다. + * 응답 크기가 상한을 따라 커져야 화면에 "이 회차가 반영됐다" 는 관측 지점이 생긴다 — + * 매번 같은 개수를 돌려주면 재렌더를 기다릴 방법이 없어 다음 클릭이 이전 화면에 꽂힌다. + * + * @param page Playwright page + * @param count 응답에 담을 행 수 (생략 시 요청 상한과 동일) + * @param seen 요청 URL 수집 배열 + */ +async function stubVersions(page: Page, count: number | null, seen: string[]): Promise { + await page.route(VERSIONS_URL_RE, async (route: Route) => { + const url = route.request().url(); + seen.push(url); + + const requested = Number(new URL(url).searchParams.get('limit') ?? VERSION_PAGE_SIZE); + const rows = count ?? Math.min(requested, VERSION_MAX_LIMIT); + + await route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({ + success: true, + data: Array.from({ length: rows }, (_, i) => versionRow(i + 1)), + }), + }); + }); +} + +/** + * 코드 편집기를 열고 버전 이력 모달을 띄웁니다. + * + * @param page Playwright page + * @param token 인증 토큰 + */ +async function openVersionHistory(page: Page, token: string): Promise { + await authenticatePage(page, token); + await page.goto('/admin/templates/sirsoft-basic/edit'); + await page.waitForLoadState('domcontentloaded', { timeout: 30_000 }); + + await page.waitForSelector('[data-testid="layout-version-history-open"]', { timeout: 30_000 }); + await page.getByTestId('layout-version-history-open').click(); + await page.waitForSelector('[data-testid="layout-version-history-list"]', { timeout: 20_000 }); +} + +test.describe('@sirsoft-admin_basic 코드 편집기 버전 이력 — 상한 조회', () => { + test('첫 조회가 상한(limit)을 달고 나간다', async ({ page, layoutEditToken }) => { + const seen: string[] = []; + await stubVersions(page, 3, seen); + + await openVersionHistory(page, layoutEditToken); + + expect(seen.length).toBeGreaterThan(0); + // 상한 없이 나가면 오래 쓴 레이아웃에서 전량이 실린다 + expect(seen[seen.length - 1]).toContain(`limit=${VERSION_PAGE_SIZE}`); + }); + + test('한 묶음이 가득 차지 않으면 더 보기가 없다', async ({ page, layoutEditToken }) => { + const seen: string[] = []; + await stubVersions(page, 3, seen); + + await openVersionHistory(page, layoutEditToken); + + // 존재를 먼저 확정한 뒤 부재를 단언한다 — 렌더 전에는 어떤 부재 단언도 무조건 통과한다 + await expect(page.getByTestId('layout-version-row-1')).toBeVisible({ timeout: 20_000 }); + await expect(page.getByTestId('layout-version-history-load-more')).toHaveCount(0); + await expect(page.getByTestId('layout-version-history-limit-reached')).toHaveCount(0); + }); + + test('묶음이 가득 차면 더 보기로 상한을 넓혀 재조회한다', async ({ page, layoutEditToken }) => { + const seen: string[] = []; + await stubVersions(page, VERSION_PAGE_SIZE, seen); + + await openVersionHistory(page, layoutEditToken); + + const loadMore = page.getByTestId('layout-version-history-load-more'); + await expect(loadMore).toBeVisible({ timeout: 20_000 }); + + const before = seen.length; + await loadMore.click(); + await expect.poll(() => seen.length, { timeout: 20_000 }).toBeGreaterThan(before); + + // 두 번째 요청은 반드시 더 넓은 상한이어야 한다 — 같은 상한으로 다시 부르면 + // 같은 묶음이 돌아와 '더 보기' 가 영원히 아무 일도 하지 않는다 + expect(seen[seen.length - 1]).toContain(`limit=${VERSION_PAGE_SIZE * 2}`); + }); + + test('서버 상한까지 넓히면 더 보기 대신 도달 한계를 알린다', async ({ page, layoutEditToken }) => { + const seen: string[] = []; + // 서버에 상한보다 많은 버전이 있는 상태 — 응답은 늘 요청한 상한만큼 꽉 차서 돌아온다 + await stubVersions(page, null, seen); + + await openVersionHistory(page, layoutEditToken); + + const loadMore = page.getByTestId('layout-version-history-load-more'); + await expect(loadMore).toBeVisible({ timeout: 20_000 }); + + // 100 → 200 → 300 → 400 → 500. 다음 회차를 누르기 전에 화면이 그 묶음을 다 그렸는지 + // 확인한다 — 요청이 나갔다는 것만으로 누르면 아직 이전 상한을 들고 있는 화면에 꽂힌다. + for (const expected of [200, 300, 400, VERSION_MAX_LIMIT]) { + await loadMore.click(); + await expect + .poll(() => seen.some((url) => url.includes(`limit=${expected}`)), { timeout: 20_000 }) + .toBe(true); + await expect + .poll(async () => page.locator('[data-testid^="layout-version-row-"]').count(), { timeout: 20_000 }) + .toBe(expected); + } + + // 상한을 넘겨 요청하면 목록 전체가 422 가 되어 보고 있던 이력까지 사라진다. + // 그래서 더 보기는 감추되, 그냥 사라지면 목록이 끝난 것으로 읽히므로 한계를 명시한다. + await expect(page.getByTestId('layout-version-history-limit-reached')).toBeVisible(); + await expect(loadMore).toHaveCount(0); + }); +});