(function () { "use strict"; const redwoodStyles = "\n .opportunities-extension-scroll-lock {\n overflow: hidden !important;\n }\n\n .opportunities-extension-modal,\n .opportunities-extension-modal * {\n box-sizing: border-box;\n font-family: \"Oracle Sans\", Arial, Helvetica, sans-serif;\n }\n\n .opportunities-extension-modal {\n position: fixed;\n inset: 0;\n z-index: 2147483647;\n background: #f5f4f2;\n color: #000000;\n }\n\n .opportunities-extension-modal[hidden] {\n display: none !important;\n }\n\n .opportunities-extension-shell {\n height: 100vh;\n min-height: 0;\n display: grid;\n grid-template-rows: auto minmax(0, 1fr);\n background: #f5f4f2;\n }\n\n .opportunities-extension-header {\n position: relative;\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 24px;\n padding: 28px 40px 22px;\n border-bottom: 1px solid #dedbd7;\n background:\n linear-gradient(90deg, #8f624a 0 11%, #c74634 11% 23%, #6f5a7f 23% 36%, #00758f 36% 51%, #d4b06a 51% 63%, transparent 63% 100%) top left / 100% 6px no-repeat,\n #ffffff;\n }\n\n .opportunities-extension-header::before {\n position: absolute;\n left: 40px;\n bottom: -1px;\n width: 64px;\n height: 3px;\n background: #00758f;\n content: \"\";\n }\n\n .opportunities-extension-header h1 {\n margin: 0;\n color: #000000;\n font-size: 24px;\n font-weight: 700;\n line-height: 1.2;\n letter-spacing: 0;\n }\n\n .opportunities-extension-header p {\n margin: 0;\n color: #5f5a55;\n font-size: 14px;\n line-height: 1.4;\n }\n\n .opportunities-extension-subtitle-row {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 8px;\n margin-top: 6px;\n }\n\n .opportunities-extension-auth-badge {\n display: inline-flex;\n align-items: center;\n min-height: 22px;\n padding: 2px 8px;\n border-radius: 999px;\n color: #ffffff;\n font-size: 12px;\n font-weight: 700;\n line-height: 1.2;\n }\n\n .opportunities-extension-auth-badge-authenticated {\n background: #3f6f17;\n }\n\n .opportunities-extension-auth-badge-unauthenticated {\n background: #c5331f;\n }\n\n .opportunities-extension-auth-badge-pending {\n background: #6f5a7f;\n }\n\n .opportunities-extension-products-progress {\n display: inline-flex;\n align-items: center;\n min-height: 22px;\n padding: 2px 8px;\n border-radius: 999px;\n background: #e8f4f6;\n color: #006d7a;\n font-size: 12px;\n font-weight: 700;\n line-height: 1.2;\n }\n\n .opportunities-extension-products-progress[hidden] {\n display: none !important;\n }\n\n .opportunities-extension-products-cache-timestamp {\n display: inline-flex;\n align-items: center;\n min-height: 22px;\n padding: 2px 8px;\n border-radius: 999px;\n background: #f0eeeb;\n color: #5f5a55;\n font-size: 11px;\n font-weight: 600;\n line-height: 1.2;\n }\n\n .opportunities-extension-products-cache-timestamp[hidden] {\n display: none !important;\n }\n\n .opportunities-extension-products-progress[data-state=\"running\"]::before {\n width: 12px;\n height: 12px;\n margin-right: 6px;\n border: 2px solid currentColor;\n border-right-color: transparent;\n border-radius: 50%;\n content: \"\";\n animation: opportunities-extension-spin .8s linear infinite;\n }\n\n .opportunities-extension-products-progress[data-state=\"complete\"] {\n background: #e2f2e5;\n color: #356d19;\n }\n\n .opportunities-extension-products-progress[data-state=\"complete-with-errors\"],\n .opportunities-extension-products-progress[data-state=\"error\"] {\n background: #f9e3e1;\n color: #a52b1c;\n }\n\n @keyframes opportunities-extension-spin {\n to { transform: rotate(360deg); }\n }\n\n .opportunities-extension-icon-button {\n width: 36px;\n height: 36px;\n flex: 0 0 36px;\n border: 1px solid transparent;\n border-radius: 4px;\n background: transparent;\n color: #312d2a;\n font-size: 18px;\n font-weight: 600;\n line-height: 1;\n cursor: pointer;\n }\n\n .opportunities-extension-header-actions {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n }\n\n .opportunities-extension-icon-button svg {\n width: 18px;\n height: 18px;\n vertical-align: middle;\n }\n\n .opportunities-extension-icon-button .opportunities-extension-settings-icon {\n width: 20px;\n height: 20px;\n }\n\n .opportunities-extension-icon-button:hover,\n .opportunities-extension-icon-button:focus {\n border-color: #b8b2ad;\n background: #f5f4f2;\n outline: none;\n }\n\n .opportunities-extension-body {\n display: grid;\n min-height: 0;\n grid-template-rows: auto auto minmax(0, 1fr);\n gap: 16px;\n padding: 22px 40px 36px;\n background: #f5f4f2;\n overflow: hidden;\n }\n\n .opportunities-extension-form {\n display: flex;\n align-items: end;\n flex-wrap: wrap;\n gap: 24px;\n min-height: 82px;\n max-width: none;\n margin: 0;\n padding: 14px 16px;\n border: 1px solid #dedbd7;\n border-radius: 4px;\n background: #ffffff;\n }\n\n .opportunities-extension-field {\n display: grid;\n max-width: max-content;\n min-width: 270px;\n width: max-content;\n gap: 6px;\n color: #312d2a;\n font-size: 13px;\n font-weight: 600;\n line-height: 1.3;\n }\n\n .opportunities-extension-stage-dashboard {\n display: grid;\n grid-template-columns: repeat(6, minmax(0, 1fr));\n gap: 12px;\n }\n\n .opportunities-extension-stage-dashboard-card {\n display: grid;\n gap: 6px;\n min-width: 0;\n padding: 12px 16px;\n border: 1px solid #dedbd7;\n border-top: 3px solid #5f5a55;\n border-radius: 4px;\n background: #ffffff;\n }\n\n .opportunities-extension-stage-dashboard-card span {\n color: #5f5a55;\n font-size: 11px;\n font-weight: 700;\n line-height: 1.2;\n }\n\n .opportunities-extension-stage-dashboard-value {\n overflow: hidden;\n color: #312d2a;\n font-size: 22px;\n font-weight: 700;\n line-height: 1.2;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .opportunities-extension-stage-dashboard-card[data-stage=\"SQL\"] {\n border-top-color: #008aa6;\n }\n\n .opportunities-extension-total-dashboard-card {\n border-top-color: #312d2a;\n }\n\n .opportunities-extension-stage-dashboard-card[data-stage=\"PIPELINE\"] {\n border-top-color: #6f6863;\n }\n\n .opportunities-extension-stage-dashboard-card[data-stage=\"UPSIDE\"] {\n border-top-color: #c87b00;\n }\n\n .opportunities-extension-stage-dashboard-card[data-stage=\"FORECAST\"] {\n border-top-color: #755b8b;\n }\n\n .opportunities-extension-stage-dashboard-card[data-stage=\"WON\"] {\n border-top-color: #4b7f22;\n }\n\n .opportunities-extension-stage-filter {\n display: grid;\n grid-template-rows: auto 44px;\n gap: 6px;\n min-width: 0;\n }\n\n .opportunities-extension-stage-filter-label {\n color: #312d2a;\n font-size: 13px;\n font-weight: 600;\n line-height: 1.3;\n }\n\n .opportunities-extension-stage-filter-controls {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 8px;\n }\n\n .opportunities-extension-stage-filter-button {\n min-height: 36px;\n padding: 6px 12px;\n border: 1px solid currentColor;\n border-radius: 18px;\n background: #ffffff;\n color: #5f5a55;\n font-size: 12px;\n font-weight: 700;\n letter-spacing: 0;\n line-height: 1.2;\n cursor: pointer;\n }\n\n .opportunities-extension-stage-filter-button[data-stage=\"SQL\"] {\n color: #006b84;\n }\n\n .opportunities-extension-stage-filter-button[data-stage=\"PIPELINE\"] {\n color: #5f5a55;\n }\n\n .opportunities-extension-stage-filter-button[data-stage=\"UPSIDE\"] {\n color: #945400;\n }\n\n .opportunities-extension-stage-filter-button[data-stage=\"FORECAST\"] {\n color: #624e74;\n }\n\n .opportunities-extension-stage-filter-button[data-stage=\"WON\"] {\n color: #3f6f17;\n }\n\n .opportunities-extension-stage-filter-button[aria-pressed=\"true\"][data-stage=\"SQL\"] {\n border-color: #006b84;\n background: #006b84;\n color: #ffffff;\n }\n\n .opportunities-extension-stage-filter-button[aria-pressed=\"true\"][data-stage=\"PIPELINE\"] {\n border-color: #5f5a55;\n background: #5f5a55;\n color: #ffffff;\n }\n\n .opportunities-extension-stage-filter-button[aria-pressed=\"true\"][data-stage=\"UPSIDE\"] {\n border-color: #945400;\n background: #945400;\n color: #ffffff;\n }\n\n .opportunities-extension-stage-filter-button[aria-pressed=\"true\"][data-stage=\"FORECAST\"] {\n border-color: #624e74;\n background: #624e74;\n color: #ffffff;\n }\n\n .opportunities-extension-stage-filter-button[aria-pressed=\"true\"][data-stage=\"WON\"] {\n border-color: #3f6f17;\n background: #3f6f17;\n color: #ffffff;\n }\n\n .opportunities-extension-stage-filter-button:focus-visible {\n outline: 2px solid #00758f;\n outline-offset: 2px;\n }\n\n .opportunities-extension-status-filter {\n display: grid;\n grid-template-rows: auto 44px;\n gap: 6px;\n min-width: 0;\n }\n\n .opportunities-extension-status-filter-label {\n color: #312d2a;\n font-size: 13px;\n font-weight: 600;\n line-height: 1.3;\n }\n\n .opportunities-extension-status-filter-controls {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 8px;\n }\n\n .opportunities-extension-status-filter-button {\n min-height: 36px;\n padding: 6px 12px;\n border: 1px solid currentColor;\n border-radius: 18px;\n background: #ffffff;\n font-size: 12px;\n font-weight: 700;\n letter-spacing: 0;\n line-height: 1.2;\n cursor: pointer;\n }\n\n .opportunities-extension-status-filter-button[data-status=\"OPEN\"] {\n color: #197a3d;\n }\n\n .opportunities-extension-status-filter-button[data-status=\"CLOSED\"] {\n color: #4b5563;\n }\n\n .opportunities-extension-status-filter-button[data-status=\"LOST\"] {\n color: #b13b34;\n }\n\n .opportunities-extension-status-filter-button[data-status=\"WON\"] {\n color: #1f5f99;\n }\n\n .opportunities-extension-status-filter-button[aria-pressed=\"true\"][data-status=\"OPEN\"] {\n border-color: #197a3d;\n background: #197a3d;\n color: #ffffff;\n }\n\n .opportunities-extension-status-filter-button[aria-pressed=\"true\"][data-status=\"CLOSED\"] {\n border-color: #4b5563;\n background: #4b5563;\n color: #ffffff;\n }\n\n .opportunities-extension-status-filter-button[aria-pressed=\"true\"][data-status=\"LOST\"] {\n border-color: #b13b34;\n background: #b13b34;\n color: #ffffff;\n }\n\n .opportunities-extension-status-filter-button[aria-pressed=\"true\"][data-status=\"WON\"] {\n border-color: #1f5f99;\n background: #1f5f99;\n color: #ffffff;\n }\n\n .opportunities-extension-status-filter-button:focus-visible {\n outline: 2px solid #00758f;\n outline-offset: 2px;\n }\n\n .opportunities-extension-customer-filter {\n position: relative;\n display: grid;\n grid-template-rows: auto 44px;\n gap: 6px;\n min-width: 220px;\n }\n\n .opportunities-extension-customer-filter-label {\n color: #312d2a;\n font-size: 13px;\n font-weight: 600;\n line-height: 1.3;\n }\n\n .opportunities-extension-customer-filter-trigger {\n position: relative;\n min-width: 220px;\n min-height: 44px;\n padding: 10px 38px 10px 12px;\n border: 1px solid #b8b2ad;\n border-radius: 3px;\n background: #ffffff;\n color: #312d2a;\n font-size: 14px;\n line-height: 1.3;\n text-align: left;\n cursor: pointer;\n }\n\n .opportunities-extension-customer-filter-trigger::after {\n position: absolute;\n top: 50%;\n right: 16px;\n width: 0;\n height: 0;\n border-top: 6px solid #312d2a;\n border-right: 5px solid transparent;\n border-left: 5px solid transparent;\n content: \"\";\n pointer-events: none;\n transform: translateY(-50%);\n }\n\n .opportunities-extension-customer-filter-trigger:disabled {\n cursor: not-allowed;\n background: #f5f4f2;\n color: #7d7772;\n }\n\n .opportunities-extension-customer-filter-trigger:focus-visible {\n border-color: #312d2a;\n box-shadow: 0 0 0 1px #312d2a;\n outline: none;\n }\n\n .opportunities-extension-customer-filter-panel {\n position: absolute;\n top: calc(100% + 8px);\n left: 0;\n z-index: 4;\n display: grid;\n width: min(360px, calc(100vw - 80px));\n gap: 10px;\n padding: 12px;\n border: 1px solid #8f8a85;\n border-radius: 4px;\n background: #ffffff;\n box-shadow: 0 4px 12px rgba(0, 0, 0, .18);\n }\n\n .opportunities-extension-customer-filter-panel[hidden] {\n display: none !important;\n }\n\n .opportunities-extension-customer-filter-search {\n width: 100%;\n min-height: 40px;\n padding: 8px 10px;\n border: 1px solid #b8b2ad;\n border-radius: 3px;\n background: #ffffff;\n color: #312d2a;\n font-size: 14px;\n }\n\n .opportunities-extension-customer-filter-search:focus {\n border-color: #312d2a;\n box-shadow: 0 0 0 1px #312d2a;\n outline: none;\n }\n\n .opportunities-extension-customer-filter-select-all,\n .opportunities-extension-customer-filter-option {\n display: flex;\n align-items: center;\n gap: 8px;\n color: #312d2a;\n font-size: 13px;\n line-height: 1.35;\n }\n\n .opportunities-extension-customer-filter-select-all {\n min-height: 32px;\n padding-bottom: 8px;\n border-bottom: 1px solid #dedbd7;\n font-weight: 700;\n }\n\n .opportunities-extension-customer-filter-panel input[type=\"checkbox\"] {\n width: 16px;\n height: 16px;\n flex: 0 0 16px;\n accent-color: #00758f;\n }\n\n .opportunities-extension-customer-filter-list {\n display: block;\n max-height: 250px;\n overflow: auto;\n }\n\n .opportunities-extension-customer-filter-option {\n display: grid;\n grid-template-columns: 16px minmax(0, 1fr);\n align-items: start;\n min-height: 0;\n height: auto !important;\n padding: 8px 4px;\n cursor: pointer;\n }\n\n .opportunities-extension-customer-filter-option input[type=\"checkbox\"] {\n margin-top: 1px;\n }\n\n .opportunities-extension-customer-filter-option span {\n display: block;\n min-width: 0;\n line-height: 18px;\n overflow-wrap: anywhere;\n white-space: normal;\n }\n\n .opportunities-extension-customer-filter-option:hover {\n background: #f5f4f2;\n }\n\n .opportunities-extension-customer-filter-empty {\n margin: 4px 0;\n color: #5f5a55;\n font-size: 13px;\n }\n\n .opportunities-extension-table-search-field {\n display: grid;\n grid-template-rows: auto 44px;\n gap: 6px;\n width: 260px;\n color: #312d2a;\n font-size: 13px;\n font-weight: 600;\n line-height: 1.3;\n }\n\n .opportunities-extension-table-search-field input {\n width: 100%;\n min-height: 44px;\n padding: 10px 12px;\n border: 1px solid #b8b2ad;\n border-radius: 3px;\n background: #ffffff;\n color: #312d2a;\n font-size: 14px;\n line-height: 1.3;\n }\n\n .opportunities-extension-table-search-field input:focus {\n border-color: #312d2a;\n box-shadow: 0 0 0 1px #312d2a;\n outline: none;\n }\n\n .opportunities-extension-select-wrap {\n position: relative;\n display: block;\n width: 270px;\n max-width: 100%;\n }\n\n .opportunities-extension-select-wrap::after {\n position: absolute;\n top: 50%;\n right: 16px;\n width: 0;\n height: 0;\n border-left: 5px solid transparent;\n border-right: 5px solid transparent;\n border-top: 6px solid #000000;\n content: \"\";\n pointer-events: none;\n transform: translateY(-50%);\n }\n\n .opportunities-extension-field select {\n width: 100%;\n min-width: 0;\n max-width: 100%;\n min-height: 44px;\n box-sizing: border-box;\n appearance: none;\n border: 1px solid #b8b2ad;\n border-radius: 3px;\n background: #ffffff;\n color: #000000;\n font-size: 14px;\n line-height: 1.3;\n padding: 11px 44px 11px 12px;\n }\n\n .opportunities-extension-field select:focus {\n border-color: #312d2a;\n box-shadow: 0 0 0 1px #312d2a;\n outline: none;\n }\n\n .opportunities-extension-table-surface {\n min-height: 0;\n height: 100%;\n border: 1px solid #dedbd7;\n border-radius: 4px;\n background: #ffffff;\n overflow-x: auto;\n overflow-y: auto;\n }\n\n .opportunities-extension-table {\n width: 100%;\n min-width: 0;\n border-collapse: collapse;\n table-layout: fixed;\n color: #312d2a;\n font-size: 13px;\n line-height: 1.35;\n }\n\n .opportunities-extension-table thead {\n background: #faf9f8;\n }\n\n .opportunities-extension-table th {\n position: sticky;\n top: 0;\n z-index: 1;\n height: 44px;\n border-bottom: 1px solid #b8b2ad;\n background: #faf9f8;\n color: #312d2a;\n font-size: 12px;\n font-weight: 700;\n text-align: left;\n white-space: nowrap;\n }\n\n .opportunities-extension-table td {\n min-height: 48px;\n padding: 12px 16px;\n border-bottom: 1px solid #ebe8e5;\n vertical-align: middle;\n overflow-wrap: anywhere;\n }\n\n .opportunities-extension-table th:nth-child(1) {\n width: 16%;\n }\n\n .opportunities-extension-table th:nth-child(2) {\n width: 8%;\n }\n\n .opportunities-extension-table th:nth-child(3) {\n width: 18%;\n }\n\n .opportunities-extension-table th:nth-child(4) {\n width: 11%;\n }\n\n .opportunities-extension-table th:nth-child(5) {\n width: 8%;\n }\n\n .opportunities-extension-table th:nth-child(6) {\n width: 8%;\n }\n\n .opportunities-extension-table th:nth-child(7) {\n width: 7%;\n }\n\n .opportunities-extension-table th:nth-child(8) {\n width: 6%;\n }\n\n .opportunities-extension-table th:nth-child(9) {\n width: 7%;\n }\n\n .opportunities-extension-table th:nth-child(10) {\n width: 7%;\n }\n\n .opportunities-extension-table th:nth-child(11) {\n width: 4%;\n }\n\n .opportunities-extension-table td:nth-child(2),\n .opportunities-extension-table td:nth-child(5),\n .opportunities-extension-table td:nth-child(6),\n .opportunities-extension-table td:nth-child(8) {\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n\n .opportunities-extension-table td:nth-child(9) {\n padding-right: 6px;\n padding-left: 6px;\n white-space: nowrap;\n overflow: visible;\n }\n\n .opportunities-extension-table td:nth-child(7) {\n padding-right: 6px;\n padding-left: 6px;\n white-space: nowrap;\n overflow: visible;\n }\n\n .opportunities-extension-table td:nth-child(10) {\n padding-right: 6px;\n padding-left: 6px;\n font-size: 12px;\n white-space: nowrap;\n }\n\n .opportunities-extension-table td:nth-child(11) {\n padding-right: 6px;\n padding-left: 6px;\n text-align: center;\n white-space: nowrap;\n }\n\n .opportunities-extension-table tbody tr:hover {\n background: #f7f6f4;\n }\n\n .opportunities-extension-table tbody tr:last-child td {\n border-bottom: 0;\n }\n\n .opportunities-extension-table a {\n color: #006b84;\n text-decoration: underline;\n text-decoration-thickness: 1px;\n text-underline-offset: 2px;\n }\n\n .opportunities-extension-table a:hover {\n color: #004f63;\n }\n\n .opportunities-extension-opty-number-content {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n max-width: 100%;\n }\n\n .opportunities-extension-name-content {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n }\n\n .opportunities-extension-name-content > a {\n min-width: 0;\n }\n\n .opportunities-extension-actions-content {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 4px;\n white-space: nowrap;\n }\n\n .opportunities-extension-products-count {\n display: inline-grid;\n width: auto;\n min-width: 20px;\n height: 20px;\n place-items: center;\n flex: 0 0 auto;\n box-sizing: border-box;\n padding: 0 3px;\n border: 1px solid #7ca7b2;\n border-radius: 999px;\n background: #e8f4f6;\n color: #006d7a;\n font-size: 10px;\n font-weight: 700;\n line-height: 1;\n text-align: center;\n vertical-align: middle;\n cursor: pointer;\n }\n\n .opportunities-extension-products-count:hover,\n .opportunities-extension-products-count:focus-visible,\n .opportunities-extension-products-count[aria-expanded=\"true\"] {\n border-color: #00758f;\n background: #00758f;\n color: #ffffff;\n outline: none;\n }\n\n .opportunities-extension-products-count-error {\n border-color: #d8887e;\n background: #f9e3e1;\n color: #a52b1c;\n }\n\n .opportunities-extension-products-detail-row:hover {\n background: transparent !important;\n }\n\n .opportunities-extension-products-detail-cell {\n padding: 0 !important;\n }\n\n .opportunities-extension-products-detail {\n padding: 14px 20px 18px;\n border-bottom: 1px solid #c9c5c1;\n background: #f0eeeb;\n }\n\n .opportunities-extension-products-detail-heading {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n margin-bottom: 10px;\n color: #312d2a;\n }\n\n .opportunities-extension-ai-summary {\n box-sizing: border-box;\n width: 100%;\n min-width: 0;\n margin: 0 0 12px;\n padding: 12px 16px;\n border: 1px solid #dedbd7;\n border-radius: 6px;\n background: #ffffff;\n color: #312d2a;\n font-size: 13px;\n line-height: 1.5;\n text-align: left;\n white-space: normal;\n overflow-wrap: anywhere;\n }\n\n .opportunities-extension-ai-summary strong {\n display: block;\n font-size: 13px;\n font-weight: 600;\n }\n\n .opportunities-extension-ai-summary p {\n margin: 4px 0 0;\n font: inherit;\n white-space: pre-wrap;\n }\n\n .opportunities-extension-ai-summary-error {\n color: #b42318;\n border-color: #d9928b;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-ai-summary {\n background: #292827;\n color: #f6f4f2;\n border-color: #5f5a55;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-ai-summary-error {\n color: #ffb4ab;\n border-color: #a45c55;\n }\n\n .opportunities-extension-products-detail-heading strong {\n font-size: 13px;\n }\n\n .opportunities-extension-products-detail-heading span {\n color: #5f5a55;\n font-size: 11px;\n font-weight: 600;\n text-transform: uppercase;\n }\n\n .opportunities-extension-products-table {\n width: 100%;\n table-layout: fixed;\n border-collapse: collapse;\n border: 1px solid #dedbd7;\n background: #ffffff;\n font-size: 12px;\n }\n\n .opportunities-extension-products-table th,\n .opportunities-extension-products-table td {\n position: static;\n height: auto;\n padding: 8px 10px;\n border-bottom: 1px solid #ebe8e5;\n background: transparent;\n overflow-wrap: anywhere;\n vertical-align: middle;\n white-space: normal;\n }\n\n .opportunities-extension-products-table th {\n background: #faf9f8;\n font-size: 11px;\n }\n\n .opportunities-extension-products-table th:nth-child(1) { width: 16%; }\n .opportunities-extension-products-table th:nth-child(2) { width: 16%; }\n .opportunities-extension-products-table th:nth-child(3) { width: 8%; }\n .opportunities-extension-products-table th:nth-child(4) { width: 10%; }\n .opportunities-extension-products-table th:nth-child(5) { width: 8%; }\n .opportunities-extension-products-table th:nth-child(6) { width: 8%; }\n .opportunities-extension-products-table th:nth-child(7) { width: 7%; }\n .opportunities-extension-products-table th:nth-child(8) { width: 10%; }\n .opportunities-extension-products-table th:nth-child(9) { width: 12%; }\n .opportunities-extension-products-table th:nth-child(10) { width: 5%; }\n\n .opportunities-extension-products-empty {\n margin: 0;\n padding: 10px 0;\n color: #5f5a55;\n font-size: 13px;\n }\n\n .opportunities-extension-copy-button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: 0 0 auto;\n width: 24px;\n height: 24px;\n padding: 3px;\n border: 0;\n border-radius: 3px;\n background: transparent;\n color: #00758f;\n cursor: pointer;\n }\n\n .opportunities-extension-copy-button svg {\n width: 16px;\n height: 16px;\n fill: currentColor;\n }\n\n .opportunities-extension-copy-button:hover,\n .opportunities-extension-copy-button:focus-visible {\n background: #e8f4f6;\n color: #004f63;\n outline: none;\n }\n\n .opportunities-extension-action-button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 32px;\n height: 32px;\n padding: 5px;\n border: 1px solid transparent;\n border-radius: 4px;\n background: transparent;\n color: #00758f;\n cursor: pointer;\n }\n\n .opportunities-extension-action-button svg {\n width: 21px;\n height: 21px;\n }\n\n .opportunities-extension-action-button:hover,\n .opportunities-extension-action-button:focus-visible {\n border-color: #7ca7b2;\n background: #e8f4f6;\n color: #004f63;\n outline: none;\n }\n\n .opportunities-extension-action-button:disabled {\n cursor: wait;\n opacity: .72;\n }\n\n .opportunities-extension-action-button-loading svg {\n animation: opportunities-extension-pulse 1s ease-in-out infinite;\n }\n\n @keyframes opportunities-extension-pulse {\n 50% { opacity: .35; }\n }\n\n .opportunities-extension-copy-toast {\n position: fixed;\n right: 28px;\n bottom: 28px;\n z-index: 3;\n max-width: min(360px, calc(100vw - 56px));\n padding: 12px 16px;\n border-radius: 4px;\n background: #2f6f18;\n box-shadow: 0 4px 12px rgba(0, 0, 0, .18);\n color: #ffffff;\n font-size: 13px;\n font-weight: 700;\n }\n\n .opportunities-extension-copy-toast-error {\n background: #a52b1c;\n }\n\n .opportunities-extension-copy-toast[hidden] {\n display: none !important;\n }\n\n .opportunities-extension-stage-badge {\n display: inline-flex;\n align-items: center;\n min-height: 24px;\n padding: 3px 8px;\n border-radius: 12px;\n background: #ebe8e5;\n color: #312d2a;\n font-size: 11px;\n font-weight: 700;\n line-height: 1.2;\n white-space: nowrap;\n }\n\n .opportunities-extension-stage-badge[data-stage=\"SQL\"] {\n background: #d9f0f5;\n color: #006b84;\n }\n\n .opportunities-extension-stage-badge[data-stage=\"PIPELINE\"] {\n background: #ebe8e5;\n color: #5f5a55;\n }\n\n .opportunities-extension-stage-badge[data-stage=\"UPSIDE\"] {\n background: #fff0d8;\n color: #945400;\n }\n\n .opportunities-extension-stage-badge[data-stage=\"FORECAST\"] {\n background: #eee8f5;\n color: #624e74;\n }\n\n .opportunities-extension-stage-badge[data-stage=\"WON\"] {\n background: #e5f1d9;\n color: #3f6f17;\n }\n\n .opportunities-extension-status-badge {\n display: inline-flex;\n align-items: center;\n min-height: 24px;\n padding: 3px 8px;\n border-radius: 12px;\n background: #ebe8e5;\n color: #312d2a;\n font-size: 11px;\n font-weight: 700;\n line-height: 1.2;\n white-space: nowrap;\n }\n\n .opportunities-extension-status-badge[data-status=\"OPEN\"] {\n background: #e2f2e5;\n color: #197a3d;\n }\n\n .opportunities-extension-status-badge[data-status=\"CLOSED\"] {\n background: #e5e7eb;\n color: #4b5563;\n }\n\n .opportunities-extension-status-badge[data-status=\"LOST\"] {\n background: #f9e3e1;\n color: #b13b34;\n }\n\n .opportunities-extension-status-badge[data-status=\"WON\"] {\n background: #e3edf8;\n color: #1f5f99;\n }\n\n .opportunities-extension-sort-button {\n position: relative;\n display: inline-flex;\n align-items: center;\n width: 100%;\n min-height: 44px;\n padding: 8px 42px 8px 16px;\n border: 0;\n background: transparent;\n color: inherit;\n font: inherit;\n font-weight: inherit;\n letter-spacing: 0;\n line-height: 1.2;\n text-align: left;\n white-space: normal;\n cursor: pointer;\n }\n\n .opportunities-extension-table-header-label {\n display: inline-flex;\n align-items: center;\n min-height: 44px;\n padding: 8px 6px;\n }\n\n .opportunities-extension-sort-button::after {\n position: absolute;\n right: 15px;\n color: #5f5a55;\n content: \"↕\";\n font-size: 16px;\n font-weight: 400;\n }\n\n .opportunities-extension-sort-button[data-sort-direction=\"ascending\"]::after {\n color: #00758f;\n content: \"↑\";\n }\n\n .opportunities-extension-sort-button[data-sort-direction=\"descending\"]::after {\n color: #00758f;\n content: \"↓\";\n }\n\n .opportunities-extension-sort-button:hover {\n background: #f0eeeb;\n }\n\n .opportunities-extension-sort-button:focus-visible {\n outline: 2px solid #00758f;\n outline-offset: -2px;\n }\n\n .opportunities-extension-table-status {\n height: 152px;\n color: #5f5a55;\n font-size: 14px;\n text-align: center;\n }\n\n .opportunities-extension-visually-hidden {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border: 0;\n }\n\n .opportunities-extension-preferences-modal {\n position: absolute;\n inset: 0;\n z-index: 3;\n display: grid;\n place-items: center;\n padding: 24px;\n background: rgba(0, 0, 0, .42);\n }\n\n .opportunities-extension-preferences-modal[hidden] {\n display: none !important;\n }\n\n .opportunities-extension-account-plans-modal {\n position: absolute;\n inset: 0;\n z-index: 4;\n display: grid;\n place-items: center;\n padding: 12px;\n background: rgba(0, 0, 0, .42);\n }\n\n .opportunities-extension-account-plans-modal[hidden] {\n display: none !important;\n }\n\n .opportunities-extension-account-plans-dialog {\n display: grid;\n width: min(1180px, 100%);\n height: min(760px, calc(100vh - 24px));\n min-width: 0;\n grid-template-rows: auto minmax(0, 1fr);\n grid-template-columns: minmax(0, 1fr);\n overflow: hidden;\n border: 1px solid #8f8a85;\n border-radius: 4px;\n background: #ffffff;\n box-shadow: 0 8px 24px rgba(0, 0, 0, .24);\n color: #312d2a;\n }\n\n .opportunities-extension-account-plans-header {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 16px;\n padding: 14px 20px;\n border-bottom: 1px solid #dedbd7;\n }\n\n .opportunities-extension-account-plans-header h2 {\n margin: 0;\n font-size: 20px;\n line-height: 1.2;\n }\n\n .opportunities-extension-account-plans-header p {\n margin: 5px 0 0;\n color: #5f5a55;\n font-size: 13px;\n line-height: 1.35;\n }\n\n .opportunities-extension-account-plans-content {\n display: grid;\n min-width: 0;\n grid-template-rows: auto minmax(0, 1fr);\n grid-template-columns: minmax(0, 1fr);\n min-height: 0;\n overflow: hidden;\n }\n\n .opportunities-extension-account-plans-content > .opportunities-extension-account-plans-skeleton {\n grid-row: 1 / -1;\n }\n\n .opportunities-extension-account-plans-tabs-navigation {\n position: relative;\n min-width: 0;\n border-bottom: 1px solid #dedbd7;\n }\n\n .opportunities-extension-account-plans-tabs-navigation .opportunities-extension-account-plans-tab-navigation-button[hidden] {\n display: none;\n }\n\n .opportunities-extension-account-plans-tabs {\n display: flex;\n min-width: 0;\n gap: 4px;\n overflow-x: auto;\n padding: 8px 20px 0;\n scrollbar-width: none;\n }\n\n .opportunities-extension-account-plans-tabs::-webkit-scrollbar {\n display: none;\n }\n\n .opportunities-extension-account-plans-tab-navigation-button {\n display: grid;\n position: absolute;\n z-index: 1;\n top: 50%;\n width: 32px;\n height: 32px;\n place-items: center;\n padding: 0;\n border: 1px solid #8f8a85;\n border-radius: 4px;\n background: #ffffff;\n color: #312d2a;\n box-shadow: 0 2px 6px rgba(0, 0, 0, .16);\n transform: translateY(-50%);\n cursor: pointer;\n }\n\n .opportunities-extension-account-plans-tab-navigation-button:first-child {\n left: 8px;\n }\n\n .opportunities-extension-account-plans-tab-navigation-button:last-child {\n right: 8px;\n }\n\n .opportunities-extension-account-plans-tab-navigation-button:hover:not(:disabled) {\n border-color: #00758f;\n background: #f0eeeb;\n color: #005e73;\n }\n\n .opportunities-extension-account-plans-tab-navigation-button:disabled {\n border-color: #dedbd7;\n color: #b8b2ad;\n cursor: default;\n }\n\n .opportunities-extension-account-plans-tab-navigation-button:focus-visible {\n outline: 2px solid #00758f;\n outline-offset: 2px;\n }\n\n .opportunities-extension-account-plans-tab-navigation-button svg {\n width: 18px;\n height: 18px;\n }\n\n .opportunities-extension-account-plans-panels {\n min-width: 0;\n min-height: 0;\n overflow: hidden;\n }\n\n .opportunities-extension-account-plan-tab {\n min-height: 38px;\n flex: 0 0 auto;\n padding: 7px 12px;\n border: 0;\n border-bottom: 3px solid transparent;\n background: transparent;\n color: #5f5a55;\n font-size: 13px;\n font-weight: 600;\n cursor: pointer;\n }\n\n .opportunities-extension-account-plan-tab:hover {\n background: #f0eeeb;\n color: #312d2a;\n }\n\n .opportunities-extension-account-plan-tab[aria-selected=\"true\"] {\n border-bottom-color: #00758f;\n color: #006d7a;\n }\n\n .opportunities-extension-account-plan-tab:focus-visible {\n outline: 2px solid #00758f;\n outline-offset: -2px;\n }\n\n .opportunities-extension-account-plan-panel {\n display: grid;\n height: 100%;\n min-height: 0;\n grid-template-rows: auto minmax(0, 1fr);\n box-sizing: border-box;\n padding: 16px 20px;\n }\n\n .opportunities-extension-account-plan-panel[hidden] {\n display: none !important;\n }\n\n .opportunities-extension-account-plan-details {\n display: grid;\n grid-template-columns: repeat(3, minmax(0, 1fr));\n gap: 0 24px;\n margin: 0;\n }\n\n .opportunities-extension-account-plan-details > div {\n display: grid;\n grid-template-columns: minmax(104px, 38%) minmax(0, 1fr);\n gap: 10px;\n min-height: 42px;\n align-items: center;\n padding: 7px 0;\n border-bottom: 1px solid #ebe8e5;\n }\n\n .opportunities-extension-account-plan-details dt {\n color: #5f5a55;\n font-size: 12px;\n font-weight: 700;\n }\n\n .opportunities-extension-account-plan-details dd {\n min-width: 0;\n margin: 0;\n color: #312d2a;\n font-size: 13px;\n overflow-wrap: anywhere;\n }\n\n .opportunities-extension-consumption-trend {\n display: grid;\n min-height: 0;\n grid-template-rows: auto minmax(0, 1fr);\n margin-top: 14px;\n padding-top: 12px;\n border-top: 1px solid #dedbd7;\n }\n\n .opportunities-extension-consumption-trend-header {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: 24px;\n }\n\n .opportunities-extension-consumption-periods {\n display: flex;\n align-items: center;\n gap: 4px;\n }\n\n .opportunities-extension-consumption-periods button {\n min-height: 38px;\n padding: 7px 14px;\n border: 1px solid transparent;\n border-radius: 4px;\n background: transparent;\n color: #312d2a;\n font-size: 13px;\n font-weight: 600;\n line-height: 1.2;\n cursor: pointer;\n }\n\n .opportunities-extension-consumption-periods button:hover {\n background: #f0eeeb;\n }\n\n .opportunities-extension-consumption-periods button[aria-pressed=\"true\"] {\n border-color: #00758f;\n background: #e7f6f8;\n color: #005e73;\n }\n\n .opportunities-extension-consumption-periods button:focus-visible,\n .opportunities-extension-consumption-navigation-button:focus-visible {\n outline: 2px solid #00758f;\n outline-offset: 2px;\n }\n\n .opportunities-extension-consumption-chart {\n display: grid;\n min-height: 0;\n grid-template-rows: auto minmax(0, 1fr);\n margin-top: 4px;\n }\n\n .opportunities-extension-consumption-navigation {\n display: grid;\n grid-template-columns: 36px minmax(130px, auto) 36px;\n align-items: center;\n justify-content: center;\n gap: 8px;\n min-height: 40px;\n margin-bottom: 2px;\n }\n\n .opportunities-extension-consumption-navigation strong {\n color: #312d2a;\n font-size: 13px;\n text-align: center;\n }\n\n .opportunities-extension-consumption-navigation-button {\n display: grid;\n width: 36px;\n height: 36px;\n place-items: center;\n padding: 0;\n border: 1px solid #8f8a85;\n border-radius: 4px;\n background: #ffffff;\n color: #312d2a;\n cursor: pointer;\n }\n\n .opportunities-extension-consumption-navigation-button:hover:not(:disabled) {\n border-color: #00758f;\n background: #f0eeeb;\n color: #005e73;\n }\n\n .opportunities-extension-consumption-navigation-button:disabled {\n border-color: #dedbd7;\n color: #b8b2ad;\n cursor: default;\n }\n\n .opportunities-extension-consumption-navigation-button svg {\n width: 18px;\n height: 18px;\n }\n\n .opportunities-extension-consumption-figure {\n position: relative;\n min-width: 0;\n min-height: 0;\n margin: 0;\n scrollbar-width: none;\n }\n\n .opportunities-extension-consumption-figure::-webkit-scrollbar {\n display: none;\n }\n\n .opportunities-extension-consumption-visual {\n display: grid;\n height: 100%;\n min-height: 0;\n grid-template-columns: minmax(0, 1fr) 176px;\n align-items: center;\n gap: 16px;\n }\n\n .opportunities-extension-consumption-svg {\n display: block;\n width: 100%;\n height: 300px;\n min-height: 0;\n overflow: visible;\n }\n\n .opportunities-extension-consumption-svg .consumption-grid-line {\n stroke: #dedbd7;\n stroke-width: 1;\n shape-rendering: crispEdges;\n }\n\n .opportunities-extension-consumption-svg .consumption-axis-label,\n .opportunities-extension-consumption-svg .consumption-x-label {\n fill: #5f5a55;\n font-family: Arial, sans-serif;\n font-size: 12px;\n }\n\n .opportunities-extension-consumption-svg .consumption-value-label {\n fill: #312d2a;\n font-family: Arial, sans-serif;\n font-size: 12px;\n font-weight: 700;\n }\n\n .opportunities-extension-consumption-svg .consumption-bar-funded {\n fill: #b77888;\n }\n\n .opportunities-extension-consumption-svg .consumption-bar-overage {\n fill: #de7907;\n }\n\n .opportunities-extension-consumption-svg .consumption-average-line {\n stroke: #de7907;\n stroke-width: 1.5;\n stroke-dasharray: 6 6;\n }\n\n .opportunities-extension-consumption-svg .consumption-bar-group {\n cursor: default;\n outline: none;\n }\n\n .opportunities-extension-consumption-svg .consumption-bar-group rect {\n transition: filter .16s ease, opacity .16s ease, stroke .16s ease;\n }\n\n .opportunities-extension-consumption-svg .consumption-bar-group:hover rect,\n .opportunities-extension-consumption-svg .consumption-bar-group:focus rect {\n filter: brightness(1.06);\n stroke: #312d2a;\n stroke-width: 1.5;\n }\n\n .opportunities-extension-consumption-svg .consumption-animated-label {\n opacity: 0;\n animation: opportunities-extension-consumption-label-in .24s ease-out forwards;\n }\n\n .opportunities-extension-consumption-svg .consumption-animated-average {\n opacity: 0;\n animation: opportunities-extension-consumption-average-in .35s .32s ease-out forwards;\n }\n\n @keyframes opportunities-extension-consumption-label-in {\n from { opacity: 0; transform: translateY(4px); }\n to { opacity: 1; transform: translateY(0); }\n }\n\n @keyframes opportunities-extension-consumption-average-in {\n from { opacity: 0; }\n to { opacity: 1; }\n }\n\n .opportunities-extension-consumption-legend {\n display: grid;\n align-content: center;\n gap: 4px;\n }\n\n .opportunities-extension-consumption-legend button {\n display: grid;\n min-width: 0;\n grid-template-columns: 16px minmax(0, 1fr);\n align-items: center;\n gap: 8px;\n padding: 6px 4px;\n border: 0;\n border-radius: 4px;\n background: transparent;\n color: #312d2a;\n font-size: 12px;\n line-height: 1.25;\n text-align: left;\n cursor: pointer;\n }\n\n .opportunities-extension-consumption-legend button:hover {\n background: #f0eeeb;\n }\n\n .opportunities-extension-consumption-legend button:focus-visible {\n outline: 2px solid #00758f;\n outline-offset: 1px;\n }\n\n .opportunities-extension-consumption-legend button[aria-pressed=\"false\"] {\n color: #77726d;\n opacity: .55;\n }\n\n .opportunities-extension-consumption-legend-swatch {\n display: block;\n width: 12px;\n height: 12px;\n justify-self: center;\n }\n\n .opportunities-extension-consumption-legend-funded {\n background: #b77888;\n }\n\n .opportunities-extension-consumption-legend-overage {\n background: #de7907;\n }\n\n .opportunities-extension-consumption-legend-average {\n height: 0;\n border-top: 2px dashed #de7907;\n }\n\n .opportunities-extension-consumption-tooltip {\n position: absolute;\n z-index: 2;\n display: grid;\n min-width: 190px;\n max-width: 260px;\n gap: 4px;\n padding: 10px 12px;\n border: 1px solid #8f8a85;\n border-radius: 4px;\n background: #ffffff;\n box-shadow: 0 4px 12px rgba(0, 0, 0, .2);\n color: #312d2a;\n font-size: 12px;\n line-height: 1.3;\n pointer-events: none;\n }\n\n .opportunities-extension-consumption-tooltip[hidden] {\n display: none !important;\n }\n\n .opportunities-extension-consumption-tooltip strong {\n margin-bottom: 2px;\n font-size: 13px;\n }\n\n .opportunities-extension-consumption-chart-skeleton {\n height: 100%;\n min-height: 0;\n padding: 34px 62px 24px;\n box-sizing: border-box;\n }\n\n .opportunities-extension-consumption-chart-skeleton-plot {\n display: flex;\n height: 100%;\n min-height: 220px;\n align-items: flex-end;\n justify-content: space-around;\n gap: 12px;\n padding: 0 14px;\n border-bottom: 1px solid #dedbd7;\n border-left: 1px solid #dedbd7;\n }\n\n .opportunities-extension-consumption-chart-skeleton-plot span {\n width: min(38px, 6%);\n border-radius: 2px 2px 0 0;\n background: #dedbd7;\n animation: opportunities-extension-skeleton-pulse 1.15s ease-in-out infinite alternate;\n }\n\n .opportunities-extension-consumption-chart-empty,\n .opportunities-extension-consumption-chart-error {\n display: grid;\n min-height: 220px;\n place-items: center;\n margin: 0;\n color: #5f5a55;\n font-size: 14px;\n text-align: center;\n }\n\n .opportunities-extension-consumption-chart-error {\n color: #9f2d20;\n }\n\n .opportunities-extension-account-plan-status-indicator {\n display: inline-block;\n width: 10px;\n height: 10px;\n border-radius: 50%;\n background: #9b9691;\n box-shadow: 0 0 0 3px #efedeb;\n vertical-align: middle;\n }\n\n .opportunities-extension-account-plan-status-indicator[data-active=\"true\"] {\n background: #3f6f17;\n box-shadow: 0 0 0 3px #e2f2e5;\n }\n\n .opportunities-extension-account-plan-tab .opportunities-extension-account-plan-status-indicator {\n margin-right: 8px;\n }\n\n .opportunities-extension-account-plans-skeleton-tabs {\n display: flex;\n min-height: 46px;\n align-items: center;\n padding: 0 20px;\n border-bottom: 1px solid #dedbd7;\n }\n\n .opportunities-extension-account-plans-skeleton {\n display: grid;\n height: 100%;\n min-height: 0;\n grid-template-rows: auto minmax(0, 1fr);\n overflow: hidden;\n }\n\n .opportunities-extension-account-plans-skeleton-panel {\n padding: 16px 20px;\n }\n\n .opportunities-extension-account-plans-skeleton-tab {\n display: inline-flex;\n align-items: center;\n gap: 9px;\n min-height: 38px;\n padding: 0 12px;\n border-bottom: 3px solid #00758f;\n }\n\n .opportunities-extension-account-plans-skeleton-status {\n display: block;\n width: 10px;\n height: 10px;\n flex: 0 0 auto;\n border-radius: 50%;\n background: #dedbd7;\n box-shadow: 0 0 0 3px #efedeb;\n animation: opportunities-extension-skeleton-pulse 1.15s ease-in-out infinite alternate;\n }\n\n .opportunities-extension-account-plans-skeleton-details {\n display: grid;\n grid-template-columns: repeat(3, minmax(0, 1fr));\n gap: 0 24px;\n padding: 0;\n }\n\n .opportunities-extension-account-plans-skeleton-details > div {\n display: grid;\n grid-template-columns: minmax(104px, 38%) minmax(0, 1fr);\n align-items: center;\n gap: 10px;\n min-height: 42px;\n padding: 7px 0;\n border-bottom: 1px solid #ebe8e5;\n }\n\n .opportunities-extension-account-plans-skeleton-bar {\n display: block;\n height: 12px;\n border-radius: 3px;\n background: #dedbd7;\n animation: opportunities-extension-skeleton-pulse 1.15s ease-in-out infinite alternate;\n }\n\n .opportunities-extension-account-plans-skeleton-tabs .opportunities-extension-account-plans-skeleton-bar {\n height: 14px;\n }\n\n .opportunities-extension-account-plans-skeleton-label {\n width: 74%;\n height: 10px;\n }\n\n .opportunities-extension-account-plans-skeleton-trend {\n display: grid;\n min-height: 0;\n grid-template-rows: auto auto minmax(0, 1fr);\n margin: 14px 0 0;\n padding-top: 12px;\n border-top: 1px solid #dedbd7;\n }\n\n .opportunities-extension-account-plans-skeleton-trend-header {\n display: flex;\n justify-content: flex-end;\n min-height: 38px;\n }\n\n .opportunities-extension-account-plans-skeleton-periods {\n display: flex;\n align-items: center;\n gap: 16px;\n }\n\n .opportunities-extension-account-plans-skeleton-periods .opportunities-extension-account-plans-skeleton-bar {\n height: 12px;\n }\n\n .opportunities-extension-account-plans-skeleton-period-active {\n height: 38px !important;\n border: 1px solid #8cc8d3;\n background: #e7f6f8;\n }\n\n .opportunities-extension-account-plans-skeleton-navigation {\n display: grid;\n grid-template-columns: 32px 124px 32px;\n align-items: center;\n justify-content: center;\n gap: 8px;\n min-height: 54px;\n }\n\n .opportunities-extension-account-plans-skeleton-navigation .opportunities-extension-account-plans-skeleton-bar {\n justify-self: center;\n }\n\n .opportunities-extension-account-plans-skeleton-chart {\n display: grid;\n min-height: 0;\n grid-template-columns: minmax(0, 1fr) 150px;\n align-items: center;\n gap: 28px;\n padding: 12px 14px 0 62px;\n }\n\n .opportunities-extension-account-plans-skeleton-plot {\n display: flex;\n position: relative;\n height: 300px;\n min-height: 0;\n box-sizing: border-box;\n align-items: flex-end;\n justify-content: space-around;\n gap: 12px;\n padding: 0 14px 64px;\n background: repeating-linear-gradient(to bottom, transparent 0, transparent 58px, #ebe8e5 58px, #ebe8e5 59px) left top / 100% 236px no-repeat;\n }\n\n .opportunities-extension-account-plans-skeleton-plot::before,\n .opportunities-extension-account-plans-skeleton-plot::after {\n position: absolute;\n display: block;\n content: \"\";\n background: #dedbd7;\n }\n\n .opportunities-extension-account-plans-skeleton-plot::before {\n top: 0;\n bottom: 64px;\n left: 0;\n width: 1px;\n }\n\n .opportunities-extension-account-plans-skeleton-plot::after {\n right: 0;\n bottom: 64px;\n left: 0;\n height: 1px;\n }\n\n .opportunities-extension-account-plans-skeleton-plot .opportunities-extension-account-plans-skeleton-bar {\n width: min(38px, 7%);\n flex: 0 1 38px;\n border-radius: 2px 2px 0 0;\n }\n\n .opportunities-extension-account-plans-skeleton-legend {\n display: grid;\n align-content: center;\n gap: 18px;\n min-height: 0;\n }\n\n .opportunities-extension-account-plans-skeleton-legend .opportunities-extension-account-plans-skeleton-bar {\n height: 11px;\n }\n\n @keyframes opportunities-extension-skeleton-pulse {\n from { opacity: .48; }\n to { opacity: 1; }\n }\n\n @media (prefers-reduced-motion: reduce) {\n .opportunities-extension-account-plans-skeleton-bar,\n .opportunities-extension-consumption-chart-skeleton-plot span,\n .opportunities-extension-consumption-svg .consumption-animated-label,\n .opportunities-extension-consumption-svg .consumption-animated-average {\n animation: none;\n opacity: 1;\n }\n }\n\n .opportunities-extension-account-plans-empty,\n .opportunities-extension-account-plans-error {\n margin: 0;\n padding: 28px 20px;\n color: #5f5a55;\n font-size: 14px;\n }\n\n .opportunities-extension-account-plans-error {\n color: #9f2d20;\n }\n\n .opportunities-extension-preferences-dialog {\n display: grid;\n width: min(680px, 100%);\n max-height: min(720px, calc(100vh - 48px));\n grid-template-rows: auto minmax(0, 1fr) auto;\n overflow: hidden;\n border: 1px solid #8f8a85;\n border-radius: 4px;\n background: #ffffff;\n box-shadow: 0 8px 24px rgba(0, 0, 0, .24);\n color: #312d2a;\n }\n\n .opportunities-extension-preferences-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 16px;\n padding: 18px 20px;\n border-bottom: 1px solid #dedbd7;\n }\n\n .opportunities-extension-preferences-header h2 {\n margin: 0;\n font-size: 20px;\n font-weight: 700;\n line-height: 1.2;\n }\n\n .opportunities-extension-preferences-content {\n min-height: 0;\n overflow: auto;\n padding: 20px;\n }\n\n .opportunities-extension-preferences-fieldset {\n min-width: 0;\n margin: 0;\n padding: 0;\n border: 0;\n }\n\n .opportunities-extension-preferences-fieldset legend {\n margin-bottom: 12px;\n padding: 0;\n color: #312d2a;\n font-size: 15px;\n font-weight: 700;\n line-height: 1.3;\n }\n\n .opportunities-extension-preferences-options {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 4px 20px;\n }\n\n .opportunities-extension-preferences-option {\n display: flex;\n align-items: flex-start;\n gap: 10px;\n min-width: 0;\n padding: 9px 8px;\n border-radius: 4px;\n color: #312d2a;\n font-size: 14px;\n line-height: 1.35;\n cursor: pointer;\n }\n\n .opportunities-extension-preferences-option:hover {\n background: #f0eeeb;\n }\n\n .opportunities-extension-preferences-option input {\n width: 18px;\n height: 18px;\n flex: 0 0 18px;\n margin: 0;\n accent-color: #00758f;\n cursor: pointer;\n }\n\n .opportunities-extension-preferences-cache-field {\n display: grid;\n width: min(280px, 100%);\n gap: 6px;\n margin-top: 24px;\n color: #312d2a;\n font-size: 14px;\n font-weight: 700;\n }\n\n .opportunities-extension-preferences-cache-field select {\n width: 100%;\n height: 40px;\n padding: 0 36px 0 12px;\n border: 1px solid #8f8a85;\n border-radius: 4px;\n background: #ffffff;\n color: #312d2a;\n font-size: 14px;\n font-weight: 400;\n }\n\n .opportunities-extension-preferences-cache-field select:focus {\n border-color: #00758f;\n outline: 2px solid #bde7ee;\n outline-offset: 0;\n }\n\n .opportunities-extension-preferences-footer {\n display: flex;\n justify-content: flex-end;\n padding: 14px 20px;\n border-top: 1px solid #dedbd7;\n }\n\n .opportunities-extension-button {\n min-width: 88px;\n min-height: 36px;\n padding: 7px 16px;\n border: 1px solid transparent;\n border-radius: 4px;\n font-size: 14px;\n font-weight: 600;\n line-height: 1.2;\n cursor: pointer;\n }\n\n .opportunities-extension-button-primary {\n border-color: #00758f;\n background: #00758f;\n color: #ffffff;\n }\n\n .opportunities-extension-button-primary:hover,\n .opportunities-extension-button-primary:focus-visible {\n border-color: #005e73;\n background: #005e73;\n outline: none;\n }\n\n .opportunities-extension-debug-panel {\n position: fixed;\n right: 24px;\n bottom: 24px;\n z-index: 1;\n width: min(640px, calc(100vw - 48px));\n max-height: min(560px, calc(100vh - 120px));\n overflow: auto;\n border: 1px solid #8f8a85;\n border-radius: 4px;\n background: #ffffff;\n box-shadow: 0 2px 8px rgba(0, 0, 0, .16);\n padding: 16px;\n color: #000000;\n }\n\n .opportunities-extension-debug-panel[hidden] {\n display: none !important;\n }\n\n .opportunities-extension-debug-panel h2 {\n margin: 0;\n font-size: 18px;\n font-weight: 700;\n line-height: 1.25;\n }\n\n .opportunities-extension-debug-panel p {\n margin: 4px 0 0;\n color: #5f5a55;\n font-size: 13px;\n }\n\n .opportunities-extension-debug-panel-header {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 12px;\n margin-bottom: 12px;\n }\n\n .opportunities-extension-debug-visibility-button {\n width: 34px;\n height: 34px;\n flex: 0 0 34px;\n color: #312d2a;\n }\n\n .opportunities-extension-debug-visibility-button svg {\n width: 20px;\n height: 20px;\n }\n\n .opportunities-extension-debug-list {\n display: grid;\n gap: 10px;\n }\n\n .opportunities-extension-debug-item {\n border: 1px solid #dedbd7;\n border-left-width: 4px;\n border-radius: 4px;\n background: #faf9f8;\n }\n\n .opportunities-extension-debug-item[open] {\n background: #ffffff;\n }\n\n .opportunities-extension-debug-item-success {\n border-left-color: #3f6f17;\n }\n\n .opportunities-extension-debug-item-success .opportunities-extension-debug-status {\n background: #e2f2e5;\n color: #356d19;\n }\n\n .opportunities-extension-debug-item-failed {\n border-left-color: #c5331f;\n }\n\n .opportunities-extension-debug-item-failed .opportunities-extension-debug-status {\n background: #f9e3e1;\n color: #a52b1c;\n }\n\n .opportunities-extension-debug-item-pending {\n border-left-color: #6f5a7f;\n }\n\n .opportunities-extension-debug-item-pending .opportunities-extension-debug-status {\n background: #eee8f2;\n color: #6f5a7f;\n }\n\n .opportunities-extension-debug-heading {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n min-height: 42px;\n padding: 9px 12px 9px 10px;\n cursor: pointer;\n list-style: none;\n }\n\n .opportunities-extension-debug-heading::-webkit-details-marker {\n display: none;\n }\n\n .opportunities-extension-debug-heading::before {\n content: \"⌄\";\n width: 14px;\n color: #5f5a55;\n font-size: 16px;\n line-height: 1;\n transform: rotate(-90deg);\n transition: transform .15s ease;\n }\n\n .opportunities-extension-debug-item[open] .opportunities-extension-debug-heading::before {\n transform: rotate(0deg);\n }\n\n .opportunities-extension-debug-summary-meta {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n margin-left: auto;\n }\n\n .opportunities-extension-debug-http-status,\n .opportunities-extension-debug-status {\n display: inline-flex;\n align-items: center;\n min-height: 22px;\n padding: 0 8px;\n border-radius: 999px;\n font-size: 11px;\n font-weight: 700;\n line-height: 1;\n text-transform: uppercase;\n }\n\n .opportunities-extension-debug-http-status {\n background: #e8f4f6;\n color: #006d7a;\n }\n\n .opportunities-extension-debug-status {\n background: #ebe8e5;\n color: #5f5a55;\n }\n\n .opportunities-extension-debug-heading strong {\n font-size: 13px;\n }\n\n .opportunities-extension-debug-item pre {\n margin: 0;\n padding: 8px 12px 12px;\n color: #312d2a;\n font-family: Consolas, \"Courier New\", monospace;\n font-size: 12px;\n line-height: 1.45;\n white-space: pre-wrap;\n word-break: break-word;\n }\n\n .opportunities-extension-debug-empty {\n padding: 14px;\n border: 1px dashed #b8b2ad;\n border-radius: 4px;\n color: #5f5a55;\n font-size: 13px;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] {\n background: #1f1f1f;\n color: #f6f4f2;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-shell,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-body {\n background: #1f1f1f;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-header {\n border-bottom-color: #4e4a46;\n background:\n linear-gradient(90deg, #a8795d 0 11%, #dd5a47 11% 23%, #876e9b 23% 36%, #00a6be 36% 51%, #dfbd76 51% 63%, transparent 63% 100%) top left / 100% 6px no-repeat,\n #292827;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-header h1,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-stage-dashboard-value,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-table,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-field,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-stage-filter-label,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-status-filter-label,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-customer-filter-label,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-table-search-field {\n color: #f6f4f2;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-header p,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-account-plans-header p,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-stage-dashboard-card span,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-customer-filter-empty,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-products-detail-heading span,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-products-empty,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-account-plans-empty,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-account-plan-details dt,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-debug-panel p,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-debug-empty {\n color: #c9c5c1;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-account-plans-error {\n color: #ffb4a8;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-consumption-chart-error {\n color: #ffb4a8;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-account-plans-skeleton-bar {\n background: #5b5753;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-account-plans-skeleton-status {\n background: #5b5753;\n box-shadow: 0 0 0 3px #3b3937;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-account-plans-skeleton-period-active {\n border-color: #3f7580;\n background: #23474e;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-consumption-chart-skeleton-plot span {\n background: #5b5753;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-account-plan-status-indicator {\n background: #8f8a85;\n box-shadow: 0 0 0 3px #3b3937;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-account-plan-status-indicator[data-active=\"true\"] {\n background: #7fb05b;\n box-shadow: 0 0 0 3px #314527;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-form,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-stage-dashboard-card,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-table-surface,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-customer-filter-panel,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-preferences-dialog,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-account-plans-dialog,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-products-table,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-debug-panel,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-debug-item[open] {\n border-color: #4e4a46;\n background: #292827;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-customer-filter-trigger,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-customer-filter-search,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-table-search-field input,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-field select,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-preferences-cache-field select,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-stage-filter-button,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-status-filter-button {\n border-color: #6a6560;\n background: #333130;\n color: #f6f4f2;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-customer-filter-trigger::after {\n border-top-color: #f6f4f2;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-select-wrap::after {\n border-top-color: #f6f4f2;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-customer-filter-trigger:disabled {\n background: #292827;\n color: #918b86;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-customer-filter-search::placeholder,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-table-search-field input::placeholder {\n color: #b5b0ab;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-customer-filter-select-all,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-customer-filter-option,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-preferences-header h2,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-account-plans-header h2,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-preferences-fieldset legend,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-preferences-option,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-preferences-cache-field,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-products-detail-heading,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-account-plan-details dd,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-debug-heading,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-debug-item pre,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-debug-panel h2 {\n color: #f6f4f2;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-consumption-navigation strong {\n color: #f6f4f2;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-consumption-chart-empty {\n color: #c9c5c1;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-customer-filter-select-all,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-table th {\n border-bottom-color: #4e4a46;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-preferences-header,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-preferences-footer,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-account-plans-header,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-account-plans-tabs-navigation,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-account-plans-tabs,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-account-plan-details > div,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-account-plans-skeleton-tabs,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-account-plans-skeleton-details > div {\n border-color: #4e4a46;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-consumption-trend,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-consumption-chart-skeleton-plot,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-account-plans-skeleton-trend,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-account-plans-skeleton-plot {\n border-color: #4e4a46;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-account-plans-skeleton-plot {\n background: repeating-linear-gradient(to bottom, transparent 0, transparent 58px, #4e4a46 58px, #4e4a46 59px) left top / 100% 236px no-repeat;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-account-plans-skeleton-plot::before,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-account-plans-skeleton-plot::after {\n background: #4e4a46;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-account-plan-tab {\n color: #c9c5c1;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-account-plan-tab:hover {\n background: #3b3937;\n color: #f6f4f2;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-account-plan-tab[aria-selected=\"true\"] {\n border-bottom-color: #43c4d5;\n color: #91e4ed;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-consumption-periods button {\n color: #f6f4f2;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-consumption-periods button:hover {\n background: #3b3937;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-consumption-periods button[aria-pressed=\"true\"] {\n border-color: #43c4d5;\n background: #173e43;\n color: #91e4ed;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-consumption-navigation-button,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-account-plans-tab-navigation-button {\n border-color: #6a6560;\n background: #333130;\n color: #f6f4f2;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-consumption-navigation-button:hover:not(:disabled),\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-account-plans-tab-navigation-button:hover:not(:disabled) {\n border-color: #43c4d5;\n background: #3b3937;\n color: #91e4ed;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-consumption-navigation-button:disabled,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-account-plans-tab-navigation-button:disabled {\n border-color: #4e4a46;\n color: #746f6a;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-consumption-svg .consumption-grid-line {\n stroke: #4e4a46;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-consumption-svg .consumption-axis-label,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-consumption-svg .consumption-x-label {\n fill: #c9c5c1;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-consumption-svg .consumption-value-label {\n fill: #f6f4f2;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-consumption-svg .consumption-bar-funded {\n fill: #ca8798;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-consumption-svg .consumption-bar-overage {\n fill: #f09a32;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-consumption-svg .consumption-average-line {\n stroke: #f09a32;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-consumption-svg .consumption-bar-group:hover rect,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-consumption-svg .consumption-bar-group:focus rect {\n stroke: #f6f4f2;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-consumption-legend button {\n color: #f6f4f2;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-consumption-legend button:hover {\n background: #3b3937;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-consumption-legend button[aria-pressed=\"false\"] {\n color: #a8a39e;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-consumption-legend-funded {\n background: #ca8798;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-consumption-legend-overage {\n background: #f09a32;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-consumption-legend-average {\n border-top-color: #f09a32;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-consumption-tooltip {\n border-color: #6a6560;\n background: #333130;\n color: #f6f4f2;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-preferences-option:hover {\n background: #3b3937;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-preferences-option input {\n accent-color: #43c4d5;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-button-primary {\n border-color: #00a6be;\n background: #008aa6;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-button-primary:hover,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-button-primary:focus-visible {\n border-color: #43c4d5;\n background: #006d7a;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-customer-filter-option:hover,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-table tbody tr:hover,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-sort-button:hover,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-icon-button:hover,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-icon-button:focus {\n background: #3b3937;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-table thead,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-table th,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-products-table th {\n background: #333130;\n color: #f6f4f2;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-table td {\n border-bottom-color: #3f3c39;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-products-detail {\n border-bottom-color: #4e4a46;\n background: #252423;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-products-table th,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-products-table td {\n border-bottom-color: #3f3c39;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-products-count {\n border-color: #2f9bae;\n background: #1d4e55;\n color: #91e4ed;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-products-count:hover,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-products-count:focus-visible,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-products-count[aria-expanded=\"true\"] {\n border-color: #43c4d5;\n background: #008aa6;\n color: #ffffff;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-products-progress[data-state=\"running\"] {\n background: #1d4e55;\n color: #91e4ed;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-products-progress[data-state=\"complete\"] {\n background: #244f2e;\n color: #a8dfb7;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-products-progress[data-state=\"complete-with-errors\"],\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-products-progress[data-state=\"error\"],\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-products-count-error {\n border-color: #b13b34;\n background: #5f2926;\n color: #ffb4ad;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-products-cache-timestamp {\n background: #3b3937;\n color: #c9c5c1;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-table a,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-copy-button,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-action-button {\n color: #43c4d5;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-table a:hover,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-copy-button:hover,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-copy-button:focus-visible,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-action-button:hover,\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-action-button:focus-visible {\n background: #1d4e55;\n color: #91e4ed;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-stage-filter-button[data-stage=\"SQL\"] {\n color: #43c4d5;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-stage-filter-button[data-stage=\"PIPELINE\"] {\n color: #d1cbc6;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-stage-filter-button[data-stage=\"UPSIDE\"] {\n color: #ffbf62;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-stage-filter-button[data-stage=\"FORECAST\"] {\n color: #d2b7e9;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-stage-filter-button[data-stage=\"WON\"] {\n color: #9bd46a;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-stage-dashboard-card[data-stage=\"SQL\"] {\n border-top-color: #43c4d5;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-total-dashboard-card {\n border-top-color: #f6f4f2;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-stage-dashboard-card[data-stage=\"PIPELINE\"] {\n border-top-color: #c0b9b3;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-stage-dashboard-card[data-stage=\"UPSIDE\"] {\n border-top-color: #ffb84d;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-stage-dashboard-card[data-stage=\"FORECAST\"] {\n border-top-color: #cba7e7;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-stage-dashboard-card[data-stage=\"WON\"] {\n border-top-color: #92cf5b;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-stage-filter-button[aria-pressed=\"true\"][data-stage=\"SQL\"] {\n border-color: #008aa6;\n background: #008aa6;\n color: #ffffff;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-stage-filter-button[aria-pressed=\"true\"][data-stage=\"PIPELINE\"] {\n border-color: #6f6863;\n background: #6f6863;\n color: #ffffff;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-stage-filter-button[aria-pressed=\"true\"][data-stage=\"UPSIDE\"] {\n border-color: #b96e00;\n background: #b96e00;\n color: #ffffff;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-stage-filter-button[aria-pressed=\"true\"][data-stage=\"FORECAST\"] {\n border-color: #755b8b;\n background: #755b8b;\n color: #ffffff;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-stage-filter-button[aria-pressed=\"true\"][data-stage=\"WON\"] {\n border-color: #4b7f22;\n background: #4b7f22;\n color: #ffffff;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-status-filter-button[data-status=\"OPEN\"] {\n color: #69c985;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-status-filter-button[data-status=\"CLOSED\"] {\n color: #c3cad3;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-status-filter-button[data-status=\"LOST\"] {\n color: #ff9d94;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-status-filter-button[data-status=\"WON\"] {\n color: #83bfff;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-status-filter-button[aria-pressed=\"true\"][data-status=\"OPEN\"] {\n border-color: #197a3d;\n background: #197a3d;\n color: #ffffff;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-status-filter-button[aria-pressed=\"true\"][data-status=\"CLOSED\"] {\n border-color: #4b5563;\n background: #4b5563;\n color: #ffffff;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-status-filter-button[aria-pressed=\"true\"][data-status=\"LOST\"] {\n border-color: #b13b34;\n background: #b13b34;\n color: #ffffff;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-status-filter-button[aria-pressed=\"true\"][data-status=\"WON\"] {\n border-color: #1f5f99;\n background: #1f5f99;\n color: #ffffff;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-debug-item {\n border-color: #4e4a46;\n background: #252423;\n }\n\n .opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-icon-button {\n color: #f6f4f2;\n }\n\n @media (min-width: 1201px) and (max-width: 1450px) {\n .opportunities-extension-table th:nth-child(1) {\n width: 16%;\n }\n\n .opportunities-extension-table th:nth-child(3) {\n width: 18%;\n }\n\n .opportunities-extension-table th:nth-child(4) {\n width: 12%;\n }\n\n .opportunities-extension-table th:nth-child(6) {\n width: 7%;\n }\n\n .opportunities-extension-table th:nth-child(7) {\n width: 8%;\n }\n\n .opportunities-extension-table th:nth-child(10) {\n width: 10%;\n }\n }\n\n @media (max-width: 1200px) {\n .opportunities-extension-table {\n min-width: 1180px;\n }\n\n .opportunities-extension-table th:nth-child(1) {\n width: 15%;\n }\n\n .opportunities-extension-table th:nth-child(3) {\n width: 17%;\n }\n\n .opportunities-extension-table th:nth-child(4) {\n width: 13%;\n }\n\n .opportunities-extension-table th:nth-child(6) {\n width: 6%;\n }\n\n .opportunities-extension-table th:nth-child(7) {\n width: 9%;\n }\n\n .opportunities-extension-table th:nth-child(10) {\n width: 11%;\n }\n }\n\n @media (max-width: 700px) {\n .opportunities-extension-header {\n padding: 24px 20px 18px;\n }\n\n .opportunities-extension-header::before {\n left: 20px;\n }\n\n .opportunities-extension-header h1 {\n font-size: 22px;\n }\n\n .opportunities-extension-header p {\n font-size: 14px;\n }\n\n .opportunities-extension-body {\n padding: 16px 20px 24px;\n }\n\n .opportunities-extension-form {\n align-items: stretch;\n flex-wrap: wrap;\n gap: 14px;\n min-height: auto;\n padding: 12px;\n }\n\n .opportunities-extension-field {\n max-width: none;\n width: 100%;\n }\n\n .opportunities-extension-customer-filter {\n width: 100%;\n }\n\n .opportunities-extension-customer-filter-trigger {\n width: 100%;\n }\n\n .opportunities-extension-customer-filter-panel {\n width: min(360px, calc(100vw - 64px));\n }\n\n .opportunities-extension-table-search-field {\n width: 100%;\n }\n\n .opportunities-extension-stage-dashboard {\n grid-template-columns: repeat(2, minmax(0, 1fr));\n }\n\n .opportunities-extension-stage-dashboard-value {\n font-size: 18px;\n }\n\n .opportunities-extension-preferences-modal {\n padding: 12px;\n }\n\n .opportunities-extension-account-plans-modal {\n padding: 12px;\n }\n\n .opportunities-extension-preferences-dialog {\n max-height: calc(100vh - 24px);\n }\n\n .opportunities-extension-account-plans-dialog {\n height: calc(100vh - 24px);\n }\n\n .opportunities-extension-account-plan-panel {\n padding: 12px 14px;\n }\n\n .opportunities-extension-account-plan-details {\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 0 14px;\n }\n\n .opportunities-extension-account-plan-details > div {\n grid-template-columns: 1fr;\n align-content: center;\n gap: 3px;\n min-height: 54px;\n padding: 5px 0;\n }\n\n .opportunities-extension-account-plans-skeleton-details {\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 0 14px;\n padding: 0;\n }\n\n .opportunities-extension-account-plans-skeleton-details > div {\n grid-template-columns: 1fr;\n align-content: center;\n gap: 5px;\n min-height: 54px;\n padding: 5px 0;\n }\n\n .opportunities-extension-account-plans-skeleton-trend {\n margin: 14px 0 0;\n }\n\n .opportunities-extension-account-plans-skeleton-periods {\n display: grid;\n grid-template-columns: repeat(4, minmax(0, 1fr));\n align-items: center;\n gap: 6px;\n }\n\n .opportunities-extension-account-plans-skeleton-periods .opportunities-extension-account-plans-skeleton-bar {\n width: 100% !important;\n }\n\n .opportunities-extension-account-plans-skeleton-chart {\n grid-template-columns: minmax(0, 1fr);\n gap: 12px;\n padding: 12px 8px 0 34px;\n }\n\n .opportunities-extension-account-plans-skeleton-legend {\n display: flex;\n min-height: 24px;\n align-items: center;\n justify-content: center;\n gap: 12px;\n }\n\n .opportunities-extension-consumption-trend-header {\n align-items: stretch;\n }\n\n .opportunities-extension-consumption-periods {\n display: grid;\n grid-template-columns: repeat(4, minmax(0, 1fr));\n gap: 3px;\n }\n\n .opportunities-extension-consumption-periods button {\n min-width: 0;\n padding: 7px 4px;\n font-size: 12px;\n }\n\n .opportunities-extension-consumption-chart {\n min-height: 0;\n }\n\n .opportunities-extension-consumption-svg {\n width: 760px;\n height: 245px;\n }\n\n .opportunities-extension-consumption-visual {\n width: 920px;\n grid-template-columns: 760px 144px;\n gap: 12px;\n }\n\n .opportunities-extension-consumption-figure {\n overflow-x: auto;\n overscroll-behavior-inline: contain;\n }\n\n .opportunities-extension-consumption-chart-skeleton {\n min-height: 0;\n padding: 24px 18px 18px;\n }\n\n .opportunities-extension-consumption-chart-skeleton-plot {\n min-height: 180px;\n gap: 4px;\n padding: 0 6px;\n }\n\n .opportunities-extension-preferences-options {\n grid-template-columns: 1fr;\n }\n\n .opportunities-extension-stage-filter-controls {\n gap: 6px;\n }\n\n .opportunities-extension-status-filter-controls {\n gap: 6px;\n }\n\n .opportunities-extension-table-surface {\n min-width: 0;\n min-height: calc(100vh - 230px);\n }\n\n .opportunities-extension-debug-panel {\n right: 12px;\n bottom: 12px;\n width: calc(100vw - 24px);\n max-height: calc(100vh - 96px);\n }\n }\n \n/* Full-page diagnostics, scoped to the extension and its current theme. */\n.opportunities-extension-modal .opportunities-extension-debug-panel {\n --debug-surface: #fff;\n --debug-code: #f5f6f7;\n --debug-text: #312d2a;\n --debug-muted: #5f5a55;\n --debug-border: #dedbd7;\n --debug-success: #22613b;\n --debug-success-bg: #e1f1e5;\n --debug-failed: #a12721;\n --debug-failed-bg: #fce8e6;\n --debug-pending: #805000;\n --debug-pending-bg: #fff1ce;\n --debug-info: #075e87;\n --debug-info-bg: #e5f1fa;\n --debug-key: #075e87;\n --debug-string: #22613b;\n --debug-number: #865000;\n --debug-literal: #73429b;\n position: fixed;\n inset: 0;\n z-index: 50;\n display: grid;\n grid-template-rows: auto auto minmax(0, 1fr);\n width: auto;\n height: auto;\n max-height: none;\n min-width: 0;\n box-sizing: border-box;\n overflow: hidden;\n margin: 0;\n padding: 20px;\n border: 0;\n border-radius: 0;\n background: var(--debug-surface);\n color: var(--debug-text);\n color-scheme: light;\n}\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-debug-panel {\n --debug-surface: #292827;\n --debug-code: #222120;\n --debug-text: #f6f4f2;\n --debug-muted: #c9c5c1;\n --debug-border: #514c47;\n --debug-success: #a9e3bd;\n --debug-success-bg: #214d34;\n --debug-failed: #ffb4ab;\n --debug-failed-bg: #582c2a;\n --debug-pending: #ffe099;\n --debug-pending-bg: #59441e;\n --debug-info: #a8d9ff;\n --debug-info-bg: #233f52;\n --debug-key: #8ed6f0;\n --debug-string: #a9e3bd;\n --debug-number: #ffe099;\n --debug-literal: #d6b7f1;\n color: #f6f4f2;\n color-scheme: dark;\n}\n.opportunities-extension-debug-actions { display: flex; gap: 8px; flex: 0 0 auto; }\n.opportunities-extension-modal .opportunities-extension-debug-panel .opportunities-extension-debug-list {\n overflow: auto;\n min-height: 0;\n min-width: 0;\n align-content: start;\n scrollbar-gutter: stable;\n padding: 2px 8px 12px 2px;\n}\n.opportunities-extension-modal .opportunities-extension-debug-panel .opportunities-extension-debug-item {\n --debug-state: var(--debug-info);\n --debug-state-bg: var(--debug-info-bg);\n border-color: var(--debug-border);\n border-left-color: var(--debug-state);\n background: var(--debug-surface);\n min-width: 0;\n}\n.opportunities-extension-modal .opportunities-extension-debug-panel .opportunities-extension-debug-item-success { --debug-state: var(--debug-success); --debug-state-bg: var(--debug-success-bg); }\n.opportunities-extension-modal .opportunities-extension-debug-panel .opportunities-extension-debug-item-failed { --debug-state: var(--debug-failed); --debug-state-bg: var(--debug-failed-bg); }\n.opportunities-extension-modal .opportunities-extension-debug-panel .opportunities-extension-debug-item-pending { --debug-state: var(--debug-pending); --debug-state-bg: var(--debug-pending-bg); }\n.opportunities-extension-modal .opportunities-extension-debug-panel .opportunities-extension-debug-status {\n color: var(--debug-state);\n background: var(--debug-state-bg);\n}\n.opportunities-extension-modal .opportunities-extension-debug-panel .opportunities-extension-debug-http-status,\n.opportunities-extension-debug-method {\n color: var(--debug-info);\n background: var(--debug-info-bg);\n}\n.opportunities-extension-debug-method {\n display: inline-flex;\n align-items: center;\n min-height: 22px;\n padding: 0 8px;\n border-radius: 4px;\n font: 700 11px/1.2 Consolas, monospace;\n}\n.opportunities-extension-debug-method[data-method=\"POST\"] { color: var(--debug-literal); background: var(--debug-code); }\n.opportunities-extension-modal .opportunities-extension-debug-panel .opportunities-extension-debug-http-status[data-http-class=\"2\"] { color: var(--debug-success); background: var(--debug-success-bg); }\n.opportunities-extension-modal .opportunities-extension-debug-panel .opportunities-extension-debug-http-status[data-http-class=\"4\"],\n.opportunities-extension-modal .opportunities-extension-debug-panel .opportunities-extension-debug-http-status[data-http-class=\"5\"] { color: var(--debug-failed); background: var(--debug-failed-bg); }\n.opportunities-extension-modal .opportunities-extension-debug-panel .opportunities-extension-debug-http-status[data-http-class=\"pending\"] { color: var(--debug-pending); background: var(--debug-pending-bg); }\n.opportunities-extension-debug-details { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; padding: 0 14px 16px; }\n.opportunities-extension-debug-block { min-width: 0; border-left: 2px solid var(--debug-info); padding-left: 10px; }\n.opportunities-extension-debug-block[data-direction=\"response\"] { border-left-color: var(--debug-success); }\n.opportunities-extension-modal .opportunities-extension-debug-block h3 { margin: 0 0 8px; color: var(--debug-info); font-size: 12px; }\n.opportunities-extension-modal .opportunities-extension-debug-block[data-direction=\"response\"] h3 { color: var(--debug-success); }\n.opportunities-extension-modal .opportunities-extension-debug-panel .opportunities-extension-debug-item pre {\n min-width: 0;\n margin: 0;\n padding: 12px;\n max-height: 380px;\n overflow: auto;\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n color: var(--debug-text);\n background: var(--debug-code);\n line-height: 1.6;\n tab-size: 2;\n}\n.opportunities-extension-debug-json-key { color: var(--debug-key); }\n.opportunities-extension-debug-json-string { color: var(--debug-string); }\n.opportunities-extension-debug-json-number { color: var(--debug-number); }\n.opportunities-extension-debug-json-literal { color: var(--debug-literal); }\n@media (max-width: 760px) {\n .opportunities-extension-modal .opportunities-extension-debug-panel { padding: 12px; }\n .opportunities-extension-debug-details { grid-template-columns: minmax(0, 1fr); }\n}\n.opportunities-extension-debug-tabs {\n display: flex;\n flex-wrap: wrap;\n gap: 4px;\n margin-bottom: 12px;\n padding-bottom: 8px;\n border-bottom: 1px solid #dedbd7;\n}\n\n.opportunities-extension-debug-tabs button {\n max-width: 100%;\n min-height: 32px;\n padding: 6px 8px;\n border: 0;\n border-bottom: 2px solid transparent;\n border-radius: 0;\n background: transparent;\n color: inherit;\n font: inherit;\n font-size: 12px;\n line-height: 1.35;\n overflow-wrap: anywhere;\n cursor: pointer;\n}\n\n.opportunities-extension-debug-tabs button:hover {\n background: #f5f4f2;\n}\n\n.opportunities-extension-debug-tabs button[aria-selected=\"true\"] {\n border-bottom-color: #00758f;\n background: #e8f4f6;\n color: #006b84;\n font-weight: 700;\n}\n\n.opportunities-extension-debug-tabs button:focus-visible,\n.opportunities-extension-debug-list[role=\"tabpanel\"]:focus-visible {\n outline: 2px solid #00758f;\n outline-offset: -2px;\n}\n\n.opportunities-extension-debug-time {\n font-size: 11px;\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n}\n\n.opportunities-extension-debug-heading {\n flex-wrap: wrap;\n}\n\n.opportunities-extension-debug-heading strong {\n flex: 1 1 180px;\n min-width: 0;\n overflow-wrap: anywhere;\n}\n\n.opportunities-extension-debug-summary-meta {\n flex-wrap: wrap;\n justify-content: flex-end;\n}\n\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-debug-tabs button {\n color: #f6f4f2;\n}\n\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-debug-heading::before {\n color: #c9c5c1;\n}\n\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-debug-tabs {\n border-bottom-color: #514c47;\n}\n\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-debug-tabs button:hover {\n background: #383633;\n}\n\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-debug-tabs button[aria-selected=\"true\"] {\n background: #173f48;\n border-bottom-color: #8cc8d3;\n color: #bde7ee;\n}\n\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-debug-tabs button:focus-visible,\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-debug-list[role=\"tabpanel\"]:focus-visible {\n outline-color: #8cc8d3;\n}\n.opportunities-extension-debug-duration {\n color: #55514d;\n font-size: 11px;\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n}\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-debug-duration { color: #d6d2ce; }\n\n/* Compact filters use available CSS width, including Windows display scaling. */\n.opportunities-extension-products-progress,\n.opportunities-extension-auth-badge { opacity: 1; transition: opacity 300ms ease; }\n.opportunities-extension-products-progress[data-fading=\"true\"],\n.opportunities-extension-auth-badge[data-fading=\"true\"] { opacity: 0; }\n.opportunities-extension-auth-badge[hidden] { display: none; }\n@media (prefers-reduced-motion: reduce) {\n .opportunities-extension-products-progress,\n .opportunities-extension-auth-badge { transition: none; }\n}\n.opportunities-extension-crm-sr-badge {\n border: 1px solid #408254;\n color: #185b30;\n background: #e1f1e5;\n}\n.opportunities-extension-crm-sr-badge[data-state=\"missing\"] { color: #9f201b; background: #fde8e7; border-color: #c74640; }\n.opportunities-extension-crm-sr-badge[data-state=\"error\"] { color: #785000; background: #fff0cd; border-color: #987021; }\n.opportunities-extension-wl-actions-badge[data-state=\"loading\"] { color: #5f5a55; background: #eceae8; border-color: #8f8a85; }\n.opportunities-extension-crm-sr-badge:focus-visible { outline: 2px solid currentColor; outline-offset: 2px; }\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-crm-sr-badge { color: #b8e4c6; background: #214d34; border-color: #6eac82; }\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-crm-sr-badge[data-state=\"missing\"] { color: #ffd9d5; background: #542824; border-color: #ef938b; }\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-crm-sr-badge[data-state=\"error\"] { color: #ffe0a3; background: #584219; border-color: #c99b4b; }\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-wl-actions-badge[data-state=\"loading\"] { color: #dedbd7; background: #484541; border-color: #8f8a85; }\n\n.opportunities-extension-form {\n display: grid;\n grid-template-columns: repeat(4, minmax(0, 1fr)) max-content max-content;\n align-items: end;\n gap: 12px;\n min-height: 0;\n padding: 12px;\n}\n\n.opportunities-extension-form > *,\n.opportunities-extension-badge-filters > section {\n min-width: 0;\n width: 100%;\n max-width: none;\n grid-template-rows: auto 32px;\n gap: 4px;\n font-size: 12px;\n}\n\n.opportunities-extension-form > * > span:first-child,\n.opportunities-extension-badge-filters > section > span:first-child {\n font-size: 12px;\n line-height: 16px;\n}\n\n.opportunities-extension-form .opportunities-extension-select-wrap {\n width: 100%;\n}\n\n.opportunities-extension-form .opportunities-extension-field select,\n.opportunities-extension-form .opportunities-extension-customer-filter-trigger,\n.opportunities-extension-form .opportunities-extension-table-search-field input {\n box-sizing: border-box;\n width: 100%;\n min-width: 0;\n height: 32px;\n min-height: 32px;\n padding: 4px 8px;\n font-size: 12px;\n line-height: 20px;\n}\n\n.opportunities-extension-form .opportunities-extension-field select,\n.opportunities-extension-form .opportunities-extension-customer-filter-trigger {\n padding-right: 26px;\n}\n\n.opportunities-extension-form .opportunities-extension-select-wrap::after,\n.opportunities-extension-form .opportunities-extension-customer-filter-trigger::after {\n right: 10px;\n border-left-width: 4px;\n border-right-width: 4px;\n border-top-width: 5px;\n}\n\n.opportunities-extension-form .opportunities-extension-stage-filter-controls,\n.opportunities-extension-form .opportunities-extension-status-filter-controls {\n flex-wrap: nowrap;\n gap: 4px;\n}\n\n.opportunities-extension-form .opportunities-extension-stage-filter-button,\n.opportunities-extension-form .opportunities-extension-status-filter-button,\n.opportunities-extension-form .opportunities-extension-activity-filter-button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n box-sizing: border-box;\n height: 28px;\n min-height: 28px;\n padding: 4px 7px;\n border: 1px solid currentColor;\n border-radius: 18px;\n font-family: inherit;\n font-size: 11px;\n font-weight: 700;\n letter-spacing: 0;\n line-height: 18px;\n white-space: nowrap;\n}\n\n.opportunities-extension-cell-actions > .opportunities-extension-products-count,\n.opportunities-extension-cell-actions > .opportunities-extension-products-ramp,\n.opportunities-extension-cell-actions > .opportunities-extension-crm-sr-badge {\n display: inline-flex;\n align-items: center;\n justify-content: flex-start;\n box-sizing: border-box;\n flex: 0 0 auto;\n gap: 4px;\n height: 20px;\n min-height: 20px;\n margin: 0;\n padding: 0 6px;\n border-width: 1px;\n border-style: solid;\n border-radius: 8px;\n font-family: inherit;\n font-size: 10px;\n font-style: normal;\n font-weight: 600;\n line-height: 16px;\n letter-spacing: 0;\n text-align: center;\n vertical-align: middle;\n white-space: nowrap;\n}\n\n.opportunities-extension-cell-actions .opportunities-extension-badge-icon,\n.opportunities-extension-activity-filter-button .opportunities-extension-badge-icon {\n display: block;\n order: -1;\n flex: 0 0 16px;\n width: 16px;\n height: 16px;\n margin: 0;\n padding: 0;\n border: 0;\n color: inherit;\n fill: none;\n stroke: currentColor;\n stroke-width: 2.25;\n stroke-linecap: round;\n stroke-linejoin: round;\n pointer-events: none;\n}\n\n@media (max-width: 1150px) {\n .opportunities-extension-form {\n grid-template-columns: repeat(4, minmax(0, 1fr));\n }\n .opportunities-extension-badge-filters > .opportunities-extension-status-filter,\n .opportunities-extension-badge-filters > .opportunities-extension-stage-filter {\n grid-column: span 2;\n }\n}\n\n@media (max-width: 700px) {\n .opportunities-extension-form {\n grid-template-columns: repeat(2, minmax(0, 1fr));\n }\n .opportunities-extension-form .opportunities-extension-stage-filter-controls,\n .opportunities-extension-form .opportunities-extension-status-filter-controls {\n flex-wrap: wrap;\n }\n .opportunities-extension-badge-filters > .opportunities-extension-status-filter,\n .opportunities-extension-badge-filters > .opportunities-extension-stage-filter {\n grid-template-rows: auto auto;\n }\n}\n\n@media (max-width: 420px) {\n .opportunities-extension-form { grid-template-columns: minmax(0, 1fr); }\n .opportunities-extension-badge-filters > .opportunities-extension-status-filter,\n .opportunities-extension-badge-filters > .opportunities-extension-stage-filter { grid-column: auto; }\n}\n\n.opportunities-extension-badge-filters { display: contents; }\n.opportunities-extension-activity-filter { display: grid; }\n.opportunities-extension-activity-filter[hidden] { display: none; }\n.opportunities-extension-activity-filter-controls { display: flex; align-items: center; flex-wrap: nowrap; gap: 4px; }\n/* Flexible fields share the space left by the four compact badge groups. */\n.opportunities-extension-form[data-activity-filters=\"true\"] {\n grid-template-columns: repeat(4, minmax(0, 1fr)) repeat(4, max-content);\n column-gap: 8px;\n}\n.opportunities-extension-activity-filter-button {\n min-width: 32px;\n gap: 4px;\n color: #185b30;\n background: #fff;\n cursor: pointer;\n}\n.opportunities-extension-activity-filter-button[data-value=\"missing\"] { color: #9f201b; }\n.opportunities-extension-activity-filter-button[data-value=\"under4\"] { color: #005f7d; }\n.opportunities-extension-activity-filter-button[aria-pressed=\"true\"] { color: #fff; background: #25653a; border-color: #25653a; }\n.opportunities-extension-activity-filter-button[aria-pressed=\"true\"][data-value=\"missing\"] { background: #a12d27; border-color: #a12d27; }\n.opportunities-extension-activity-filter-button[aria-pressed=\"true\"][data-value=\"under4\"] { background: #006b86; border-color: #006b86; }\n.opportunities-extension-activity-filter-button:focus-visible { outline: 2px solid currentColor; outline-offset: 2px; }\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-activity-filter { color: #eeeae6; }\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-activity-filter-button { color: #b8e4c6; }\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-activity-filter-button:not([aria-pressed=\"true\"]) { background: #333130; border-color: #6a6560; }\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-activity-filter-button[data-value=\"missing\"] { color: #ffd9d5; }\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-activity-filter-button[data-value=\"under4\"] { color: #b5e9fc; }\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-activity-filter-button[aria-pressed=\"true\"] { border-color: currentColor; }\n@media (max-width: 1350px) {\n .opportunities-extension-form[data-activity-filters=\"true\"] { grid-template-columns: repeat(4, minmax(0, 1fr)); }\n .opportunities-extension-form[data-activity-filters=\"true\"] .opportunities-extension-badge-filters {\n display: flex;\n grid-column: 1 / -1;\n flex-wrap: wrap;\n align-items: start;\n gap: 12px 20px;\n }\n .opportunities-extension-form[data-activity-filters=\"true\"] .opportunities-extension-badge-filters > section {\n width: auto;\n max-width: 100%;\n grid-template-rows: auto auto;\n }\n}\n@media (max-width: 700px) {\n .opportunities-extension-form[data-activity-filters=\"true\"] { grid-template-columns: repeat(2, minmax(0, 1fr)); }\n .opportunities-extension-activity-filter-controls { flex-wrap: wrap; }\n}\n@media (max-width: 420px) {\n .opportunities-extension-form[data-activity-filters=\"true\"] { grid-template-columns: minmax(0, 1fr); }\n}\n\n.opportunities-extension-add-action {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n box-sizing: border-box;\n flex: 0 0 16px;\n width: 16px;\n height: 16px;\n padding: 0;\n margin: 0 0 0 2px;\n border: 0;\n border-radius: 3px;\n background: transparent;\n color: inherit;\n font: inherit;\n cursor: pointer;\n}\n.opportunities-extension-add-action:hover { background: rgba(127, 127, 127, .2); }\n.opportunities-extension-add-action:focus-visible { outline: 2px solid currentColor; outline-offset: 2px; }\n.opportunities-extension-action-form-modal {\n position: absolute;\n inset: 0;\n z-index: 5;\n display: grid;\n place-items: center;\n padding: 12px;\n background: rgba(0, 0, 0, .42);\n}\n.opportunities-extension-action-form-dialog {\n box-sizing: border-box;\n width: min(560px, 100%);\n max-height: calc(100vh - 24px);\n overflow-y: auto;\n background: #fff;\n color: #312d2a;\n border: 1px solid #b9b4ae;\n border-radius: 6px;\n box-shadow: 0 12px 36px rgba(0, 0, 0, .25);\n}\n.opportunities-extension-action-form {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 14px;\n padding: 18px;\n}\n.opportunities-extension-action-form-heading { min-width: 0; overflow-wrap: anywhere; }\n.opportunities-extension-action-form-context {\n display: grid;\n grid-template-columns: max-content minmax(0, 1fr);\n gap: 6px 12px;\n margin: 12px 0 0;\n font-size: 12px;\n line-height: 18px;\n}\n.opportunities-extension-action-form-context dt { font-weight: 600; }\n.opportunities-extension-action-form-context dd { margin: 0; min-width: 0; overflow-wrap: anywhere; }\n.opportunities-extension-action-form-field { display: grid; gap: 6px; min-width: 0; font-size: 13px; }\n.opportunities-extension-action-form-field[hidden] { display: none; }\n.opportunities-extension-action-form-status { margin: 0; font-size: 13px; overflow-wrap: anywhere; }\n.opportunities-extension-action-form-status[role=\"alert\"] { color: #a12d27; }\n.opportunities-extension-action-form-wide,\n.opportunities-extension-action-form-footer { grid-column: 1 / -1; }\n.opportunities-extension-action-form-field input,\n.opportunities-extension-action-form-field select,\n.opportunities-extension-action-form-field textarea {\n box-sizing: border-box;\n min-width: 0;\n width: 100%;\n border: 1px solid #8f8983;\n border-radius: 4px;\n padding: 8px;\n font: inherit;\n line-height: 20px;\n color: inherit;\n background: #fff;\n}\n.opportunities-extension-action-form-field textarea { resize: vertical; min-height: 96px; }\n.opportunities-extension-action-form-field select { height: 38px; }\n.opportunities-extension-action-form-field select[hidden] { display: none; }\n.opportunities-extension-sr-form-dialog { width: min(900px, 100%); }\n.opportunities-extension-sr-confirmation { z-index: 6; }\n.opportunities-extension-sr-confirm-dialog { width: min(800px, 100%); display: flex; flex-direction: column; overflow: hidden; }\n.opportunities-extension-sr-confirm-dialog > header,\n.opportunities-extension-sr-confirm-dialog > footer { flex-shrink: 0; }\n.opportunities-extension-sr-confirm-dialog > footer { padding: 16px 20px; }\n.opportunities-extension-sr-confirm-body { min-height: 0; overflow: auto; padding: 0 20px; }\n.opportunities-extension-sr-confirm-body > p { font-size: 13px; font-weight: 600; }\n.opportunities-extension-sr-confirm-payload { margin: 0; padding: 16px; border: 1px solid #b9b4ae; border-radius: 4px; background: #f6f5f4; color: #312d2a; font: 13px/1.6 Consolas, monospace; white-space: pre-wrap; overflow-wrap: anywhere; }\n.opportunities-extension-sr-confirmation :focus-visible { outline: 2px solid #00758f; outline-offset: 2px; }\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-sr-confirm-payload { background: #211f1d; color: #eeeae6; border-color: #756f68; }\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-sr-confirmation :focus-visible { outline-color: #80d6e8; }\n.opportunities-extension-sr-form-dialog > .opportunities-extension-account-plans-header { padding: 20px 24px; }\n.opportunities-extension-sr-form-dialog > .opportunities-extension-action-form { gap: 20px 24px; padding: 24px; align-items: start; }\n.opportunities-extension-sr-form-dialog .opportunities-extension-action-form-field { gap: 8px; }\n.opportunities-extension-sr-form-dialog textarea { min-height: 112px; }\n.opportunities-extension-sr-form-dialog .opportunities-extension-sr-service-option { padding: 12px 0; gap: 10px; }\n.opportunities-extension-sr-form-dialog .opportunities-extension-sr-service-abstract { font-size: 13px; line-height: 20px; }\n@media (min-width: 1000px) {\n .opportunities-extension-sr-form-dialog { width: min(1120px, 100%); }\n .opportunities-extension-sr-form-dialog > .opportunities-extension-action-form { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 16px 24px; }\n .opportunities-extension-sr-services { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: 28px; align-items: start; }\n .opportunities-extension-sr-services .opportunities-extension-sr-service-option { margin: 0; border-top: 1px solid #aaa6a040; }\n .opportunities-extension-sr-services .opportunities-extension-sr-service-option:nth-child(-n+2) { border-top: 0; }\n}\n@media (max-width: 600px) {\n .opportunities-extension-sr-form-dialog > .opportunities-extension-account-plans-header { padding: 16px; }\n .opportunities-extension-sr-form-dialog > .opportunities-extension-action-form { grid-template-columns: minmax(0, 1fr); gap: 18px; padding: 16px; }\n}\n.opportunities-extension-sr-lookup { min-width: 0; padding: 0; margin: 0; border: 0; font-size: 13px; }\n.opportunities-extension-sr-lookup legend { padding: 0 0 8px; font-weight: 600; }\n.opportunities-extension-sr-lookup[aria-busy=\"true\"] > p { opacity: .65; }\n.opportunities-extension-sr-service-option { display: flex; align-items: flex-start; gap: 8px; padding: 9px 0; cursor: pointer; }\n.opportunities-extension-sr-service-option + .opportunities-extension-sr-service-option { border-top: 1px solid #aaa6a040; }\n.opportunities-extension-sr-service-option input { flex: 0 0 16px; width: 16px; height: 16px; margin: 2px 0 0; accent-color: #00758f; }\n.opportunities-extension-sr-service-option > span { min-width: 0; overflow-wrap: anywhere; }\n.opportunities-extension-sr-service-name { display: block; font-weight: 600; line-height: 20px; }\n.opportunities-extension-sr-service-abstract { display: block; margin-top: 3px; color: #625d58; font-size: 12px; line-height: 18px; white-space: pre-wrap; }\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-sr-service-abstract { color: #c7c2bc; }\n.opportunities-extension-sr-lookup-retry { color: inherit; background: transparent; border: 1px solid #8f8983; border-radius: 4px; padding: 5px 10px; cursor: pointer; }\n.opportunities-extension-sr-lookup-retry[hidden] { display: none; }\n.opportunities-extension-action-form [aria-invalid=\"true\"] { border-color: #b3261e; outline: 1px solid #b3261e; }\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-action-form [aria-invalid=\"true\"] { border-color: #ffaaa4; outline-color: #ffaaa4; }\n.opportunities-extension-modal .opportunities-extension-copy-toast { z-index: 20; }\n.opportunities-extension-action-combobox { position: relative; min-width: 0; }\n.opportunities-extension-action-combobox input { height: 38px; }\n.opportunities-extension-action-options {\n position: absolute;\n z-index: 10;\n top: calc(100% + 4px);\n left: 0;\n right: 0;\n box-sizing: border-box;\n max-height: 180px;\n overflow-y: auto;\n overscroll-behavior: contain;\n border: 1px solid #8f8983;\n border-radius: 4px;\n box-shadow: 0 4px 12px rgba(0, 0, 0, .2);\n background: #fff;\n color: #292725;\n}\n.opportunities-extension-action-options[data-placement=\"above\"] { top: auto; bottom: calc(100% + 4px); }\n.opportunities-extension-action-options[hidden] { display: none; }\n.opportunities-extension-action-option { padding: 8px; line-height: 20px; overflow-wrap: anywhere; cursor: pointer; }\n.opportunities-extension-action-option:hover,\n.opportunities-extension-action-option[aria-selected=\"true\"] { background: #e3f2f5; color: #005b70; }\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-action-options { background: #211f1d; color: #eeeae6; border-color: #918a82; }\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-action-option:hover,\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-action-option[aria-selected=\"true\"] { background: #174955; color: #c9f4ff; }\n.opportunities-extension-action-form[data-state=\"loading\"] .opportunities-extension-action-combobox input:disabled { animation: opportunities-action-options-loading 1s ease-in-out infinite alternate; }\n.opportunities-extension-action-form[data-state=\"loading\"] .opportunities-extension-action-form-field > :disabled {\n animation: opportunities-action-options-loading 1s ease-in-out infinite alternate;\n}\n@keyframes opportunities-action-options-loading { from { opacity: .45; } to { opacity: .85; } }\n@media (prefers-reduced-motion: reduce) {\n .opportunities-extension-action-form[data-state=\"loading\"] .opportunities-extension-action-form-field > :disabled,\n .opportunities-extension-action-form[data-state=\"loading\"] .opportunities-extension-action-combobox input:disabled { animation: none; }\n}\n.opportunities-extension-action-form-footer { display: flex; justify-content: flex-end; gap: 8px; }\n.opportunities-extension-action-form-footer button {\n border: 1px solid #8f8983;\n border-radius: 4px;\n padding: 7px 14px;\n font: inherit;\n font-size: 13px;\n background: transparent;\n color: inherit;\n cursor: pointer;\n}\n.opportunities-extension-action-form-footer button:disabled { opacity: .55; cursor: not-allowed; }\n.opportunities-extension-action-form :focus-visible { outline: 2px solid #00758f; outline-offset: 2px; }\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-action-form-dialog {\n color-scheme: dark;\n background: #292725;\n color: #eeeae6;\n border-color: #756f68;\n}\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-action-form-field input,\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-action-form-field select,\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-action-form-field textarea { background: #211f1d; border-color: #918a82; }\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-action-form-status[role=\"alert\"] { color: #ffd9d5; }\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-action-form :focus-visible { outline-color: #80d6e8; }\n@media (max-width: 420px) {\n .opportunities-extension-action-form { grid-template-columns: minmax(0, 1fr); }\n}\n\n/* Keep layout rules on the outer table, never on expanded product tables. */\n.opportunities-extension-table[data-opportunities-layout=\"inline-actions\"] {\n width: 100%;\n min-width: 1120px;\n table-layout: fixed;\n}\n\n/* Fixed table layout shares leftover width only between the two auto columns. */\n.opportunities-extension-table > thead > tr > th[data-column=\"name\"] { width: auto; }\n.opportunities-extension-table > thead > tr > th[data-column=\"customer\"] { width: auto; }\n.opportunities-extension-table > thead > tr > th[data-column=\"owner\"] { width: 132px; }\n.opportunities-extension-table > thead > tr > th[data-column=\"optyNumber\"] { width: 106px; }\n.opportunities-extension-table > thead > tr > th[data-column=\"revenue\"] { width: 124px; }\n.opportunities-extension-table > thead > tr > th[data-column=\"closeDate\"] { width: 100px; }\n.opportunities-extension-table > thead > tr > th[data-column=\"status\"] { width: 114px; }\n.opportunities-extension-table > thead > tr > th[data-column=\"winProbability\"] { width: 110px; }\n.opportunities-extension-table > thead > tr > th[data-column=\"stage\"] { width: 94px; }\n.opportunities-extension-table > thead > tr > th[data-column=\"lastUpdateDate\"] { width: 136px; }\n\n.opportunities-extension-table > tbody > tr > td[data-column] {\n padding: 10px 8px;\n overflow: visible;\n text-overflow: clip;\n white-space: normal;\n font-variant-numeric: tabular-nums;\n}\n\n.opportunities-extension-table > tbody > tr > td[data-column=\"name\"],\n.opportunities-extension-table > tbody > tr > td[data-column=\"customer\"] {\n position: relative;\n padding-right: 16px;\n padding-left: 12px;\n padding-bottom: 38px;\n vertical-align: top;\n}\n\n.opportunities-extension-table > tbody > tr > td[data-column=\"owner\"] {\n padding-right: 16px;\n}\n\n.opportunities-extension-table > tbody > tr > td[data-column=\"closeDate\"],\n.opportunities-extension-table > tbody > tr > td[data-column=\"lastUpdateDate\"],\n.opportunities-extension-table > tbody > tr > td[data-column=\"status\"],\n.opportunities-extension-table > tbody > tr > td[data-column=\"stage\"],\n.opportunities-extension-table > tbody > tr > td[data-column=\"winProbability\"] {\n white-space: nowrap;\n}\n\n.opportunities-extension-cell-content {\n display: block;\n min-width: 0;\n min-height: 2.7em;\n line-height: 1.35;\n}\n\n.opportunities-extension-cell-content > :first-child {\n display: block;\n min-width: 0;\n overflow-wrap: anywhere;\n}\n\n.opportunities-extension-cell-actions {\n position: absolute;\n left: 12px;\n right: 16px;\n bottom: 10px;\n display: flex;\n align-items: center;\n justify-content: flex-start;\n gap: 6px;\n height: 24px;\n}\n\n.opportunities-extension-cell-actions .opportunities-extension-action-button {\n flex: 0 0 24px;\n width: 24px;\n height: 24px;\n padding: 2px;\n}\n\n.opportunities-extension-table > tbody > tr > td[data-column=\"customer\"] .opportunities-extension-cell-actions {\n min-width: 84px;\n}\n\n.opportunities-extension-cell-actions .opportunities-extension-action-button svg {\n width: 18px;\n height: 18px;\n}\n\n.opportunities-extension-cell-actions button:focus-visible {\n outline: 2px solid currentColor;\n outline-offset: 2px;\n}\n\n.opportunities-extension-products-ramp {\n min-width: 0;\n max-width: 100%;\n border: 1px solid #bba5cf;\n background: #f0e8f7;\n color: #63358a;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-products-ramp {\n background: #43334f;\n color: #ead5fc;\n border-color: #9372ac;\n}\n\n.opportunities-extension-products-ramp[data-missing=\"true\"] {\n background: #fde8e7;\n color: #9f201b;\n border-color: #c74640;\n}\n\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-products-ramp[data-missing=\"true\"] {\n background: #542824;\n color: #ffd9d5;\n border-color: #ef938b;\n}\n\n.opportunities-extension-table > thead > tr > th[data-column] .opportunities-extension-sort-button {\n display: grid;\n grid-template-columns: minmax(0, 1fr) 12px;\n gap: 6px;\n padding: 10px 8px;\n align-items: center;\n}\n\n.opportunities-extension-table > thead > tr > th[data-column=\"name\"] .opportunities-extension-sort-button,\n.opportunities-extension-table > thead > tr > th[data-column=\"customer\"] .opportunities-extension-sort-button {\n padding-left: 12px;\n padding-right: 16px;\n}\n\n.opportunities-extension-sort-label {\n min-width: 0;\n white-space: normal;\n overflow-wrap: anywhere;\n}\n\n.opportunities-extension-table > thead > tr > th[data-column] .opportunities-extension-sort-button::after {\n position: static;\n justify-self: end;\n}\n.opportunities-extension-crm-sr-badge[type=\"button\"] { cursor: pointer; font-family: inherit; }\n.opportunities-extension-sr-popover {\n position: fixed; z-index: 8; box-sizing: border-box; overflow: auto;\n padding: 12px; border: 1px solid #b9b5b0; border-radius: 6px;\n background: #fff; color: #302e2b; box-shadow: 0 6px 24px #0003;\n font: 12px/1.5 Arial, sans-serif; text-align: left; overflow-wrap: anywhere;\n}\n.opportunities-extension-sr-popover section { padding: 8px 0; }\n.opportunities-extension-sr-popover section + section { border-top: 1px solid #dedbd7; }\n.opportunities-extension-sr-number { display: flex; align-items: center; gap: 8px; padding-right: 30px; font-weight: 600; }\n.opportunities-extension-sr-popover a { color: #00677d; text-decoration: underline; }\n.opportunities-extension-sr-details { white-space: pre-wrap; margin-top: 6px; }\n.opportunities-extension-sr-children { margin: 10px 0 0 8px; padding-left: 10px; border-left: 2px solid #8c969a; overflow-wrap: anywhere; }\n.opportunities-extension-sr-children[aria-busy=\"true\"] { min-height: 28px; animation: opportunities-action-options-loading 1s ease-in-out infinite alternate; }\n.opportunities-extension-sr-children[role=\"alert\"] { color: #a12d27; }\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-sr-children[role=\"alert\"] { color: #ffd9d5; }\n@media (prefers-reduced-motion: reduce) { .opportunities-extension-sr-children[aria-busy=\"true\"] { animation: none; } }\n.opportunities-extension-sr-dismiss {\n position: absolute; top: 6px; right: 6px; width: 28px; height: 28px;\n display: grid; place-items: center; padding: 4px; border: 0;\n background: transparent; color: inherit; cursor: pointer;\n}\n.opportunities-extension-sr-dismiss svg { width: 16px; height: 16px; }\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-sr-popover { background: #292827; color: #f1efec; border-color: #77716b; }\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-sr-popover section + section { border-color: #5f5953; }\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-sr-popover a { color: #7ddced; }\n.opportunities-extension-title-row {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: 8px 16px;\n min-width: 0;\n flex: 1;\n}\n.opportunities-extension-title-row h1 {\n display: block;\n margin: 0;\n padding: 0;\n line-height: 1;\n}\n.opportunities-extension-title-row .opportunities-extension-subtitle-row {\n display: flex;\n align-items: center;\n align-self: center;\n flex-wrap: wrap;\n gap: 8px;\n margin: 0;\n padding: 0;\n min-width: 0;\n}\n.opportunities-extension-title-row .opportunities-extension-subtitle-row > span {\n box-sizing: border-box;\n margin: 0;\n min-height: 22px;\n line-height: 16px;\n}\n.opportunities-extension-title-row + .opportunities-extension-header-actions { flex-shrink: 0; }\n.opportunities-extension-refresh-all:disabled { opacity: .65; cursor: wait; }\n.opportunities-extension-refresh-all:focus-visible { outline: 2px solid currentColor; outline-offset: 2px; }\n.opportunities-extension-refresh-all[aria-busy=\"true\"] svg { animation: opportunities-refresh-spin 1s linear infinite; }\n@keyframes opportunities-refresh-spin { to { transform: rotate(360deg); } }\n@media (prefers-reduced-motion: reduce) {\n .opportunities-extension-refresh-all[aria-busy=\"true\"] svg { animation: none; }\n}\n.opportunities-extension-option-description { display: block; font-size: 12px; line-height: 1.4; margin-top: 4px; white-space: normal; overflow-wrap: anywhere; opacity: .85; }\n.opportunities-extension-action-form-dialog.opportunities-extension-internal-sr-dialog { width: min(760px, 100%); }\n\n.opportunities-extension-account-plans-dialog.opportunities-extension-install-base-dialog {\n width: min(1700px, 100%);\n}\n.opportunities-extension-install-base-dialog .opportunities-extension-account-plans-header {\n padding: 10px 12px;\n gap: 12px;\n}\n.opportunities-extension-install-base-dialog .opportunities-extension-account-plans-header h2 {\n font-size: 16px;\n line-height: 22px;\n}\n.opportunities-extension-install-base-dialog .opportunities-extension-account-plans-header p {\n font-size: 12px;\n line-height: 16px;\n margin-top: 3px;\n overflow-wrap: anywhere;\n}\n.opportunities-extension-install-base-dialog .opportunities-extension-icon-button {\n width: 28px;\n height: 28px;\n min-width: 28px;\n min-height: 28px;\n padding: 4px;\n}\n.opportunities-extension-install-base-dialog .opportunities-extension-customer-table-toolbar {\n padding: 8px 12px;\n}\n.opportunities-extension-install-base-dialog .opportunities-extension-customer-table-search {\n width: min(320px, 100%);\n height: 32px;\n padding: 4px 8px;\n font-size: 12px;\n line-height: 20px;\n}\n.opportunities-extension-install-base-content {\n min-width: 0;\n min-height: 0;\n overflow: auto;\n scrollbar-gutter: stable;\n}\n.opportunities-extension-install-base-message { margin: 10px 12px; font-size: 12px; line-height: 16px; }\n.opportunities-extension-install-base-table {\n width: 100%; min-width: 1120px; table-layout: fixed; border-collapse: collapse; font-size: 11px;\n}\n.opportunities-extension-install-base-table col { width: 7%; }\n.opportunities-extension-install-base-table .install-base-col-partyName { width: 12%; }\n.opportunities-extension-install-base-table .install-base-col-productGroupName { width: 13%; }\n.opportunities-extension-install-base-table .install-base-col-productName { width: 13%; }\n.opportunities-extension-install-base-table .install-base-col-quantity { width: 5%; }\n.opportunities-extension-install-base-table .install-base-col-quantityType { width: 8%; }\n.opportunities-extension-install-base-table th,\n.opportunities-extension-install-base-table td {\n padding: 8px 6px; text-align: left; vertical-align: middle; overflow-wrap: anywhere;\n border-bottom: 1px solid #e8e5e1; line-height: 1.4;\n}\n.opportunities-extension-install-base-table th { position: sticky; top: 0; z-index: 1; background: #faf9f8; }\n.opportunities-extension-install-base-table td[data-format=\"amount\"],\n.opportunities-extension-install-base-table td[data-format=\"quantity\"] { text-align: right; font-variant-numeric: tabular-nums; }\n.opportunities-extension-install-base-table td[data-format=\"date\"] { white-space: nowrap; }\n.opportunities-extension-install-base-status {\n display: inline-block; padding: 2px 5px; border-radius: 12px; font-size: 10px; font-weight: 600;\n color: #5f5a55; background: #eceae8; white-space: nowrap;\n}\n.opportunities-extension-install-base-status[data-status=\"ACTIVE\"] { color: #17693a; background: #e1f1e5; }\n.opportunities-extension-install-base-status[data-status=\"UNSUPPORTED\"] { color: #855300; background: #fff0d6; }\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-install-base-content,\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-install-base-table,\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-install-base-table th,\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-install-base-table td { color: #f6f4f2; }\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-install-base-message { color: #c9c5c1; }\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-install-base-message[role=\"alert\"] { color: #ffb4ab; }\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-install-base-table th { background: #353331; }\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-install-base-table th,\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-install-base-table td { border-color: #484541; }\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-install-base-status { color: #dedbd7; background: #484541; }\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-install-base-status[data-status=\"ACTIVE\"] { color: #b8e4c6; background: #214d34; }\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-install-base-status[data-status=\"UNSUPPORTED\"] { color: #ffdfa2; background: #614418; }\n@media (prefers-reduced-motion: reduce) {\n .opportunities-extension-install-base-table .opportunities-extension-account-plans-skeleton-bar { animation: none; }\n}\n\n.opportunities-extension-account-plans-dialog.opportunities-extension-service-requests-dialog { width: min(1700px, 100%); }\n.opportunities-extension-service-requests-content { min-width: 0; min-height: 0; overflow: auto; scrollbar-gutter: stable; }\n.opportunities-extension-service-requests-message { margin: 16px; font-size: 13px; }\n.opportunities-extension-service-requests-table {\n width: 100%; min-width: 1200px; table-layout: fixed; border-collapse: collapse; font-size: 12px;\n}\n.opportunities-extension-service-requests-table col { width: 7%; }\n.opportunities-extension-service-requests-table .service-requests-col-name { width: 17%; }\n.opportunities-extension-service-requests-table .service-requests-col-id { width: 10%; }\n.opportunities-extension-service-requests-table .service-requests-col-severity { width: 5%; }\n.opportunities-extension-service-requests-table .service-requests-col-subStatus { width: 14%; }\n.opportunities-extension-service-requests-table .service-requests-col-productName { width: 12%; }\n.opportunities-extension-service-requests-table th,\n.opportunities-extension-service-requests-table td {\n padding: 12px 8px; text-align: left; vertical-align: middle; overflow-wrap: anywhere;\n border-bottom: 1px solid #e8e5e1; line-height: 1.4;\n}\n.opportunities-extension-service-requests-table th { position: sticky; top: 0; z-index: 1; background: #faf9f8; }\n.opportunities-extension-service-requests-table td[data-field=\"created\"],\n.opportunities-extension-service-requests-table td[data-field=\"lastUpdated\"],\n.opportunities-extension-service-requests-table td[data-field=\"id\"] { white-space: nowrap; font-variant-numeric: tabular-nums; }\n.opportunities-extension-service-request-status {\n display: inline-block; padding: 3px 6px; border-radius: 12px; font-size: 10px; font-weight: 600;\n color: #5f5a55; background: #eceae8;\n}\n.opportunities-extension-service-request-status[data-status=\"OPEN\"] { color: #17693a; background: #e1f1e5; }\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-service-requests-content,\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-service-requests-table,\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-service-requests-table th,\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-service-requests-table td { color: #f6f4f2; }\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-service-requests-message { color: #c9c5c1; }\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-service-requests-message[role=\"alert\"] { color: #ffb4ab; }\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-service-requests-table th { background: #353331; }\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-service-requests-table th,\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-service-requests-table td { border-color: #484541; }\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-service-request-status { color: #dedbd7; background: #484541; }\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-service-request-status[data-status=\"OPEN\"] { color: #b8e4c6; background: #214d34; }\n@media (prefers-reduced-motion: reduce) {\n .opportunities-extension-service-requests-table .opportunities-extension-account-plans-skeleton-bar { animation: none; }\n}\n\n/* Scope sizing to Cloud Consumption, not other dialogs sharing its components. */\n[data-consumption-layout=\"compact\"] .opportunities-extension-account-plans-header { padding: 10px 12px; gap: 12px; }\n[data-consumption-layout=\"compact\"] .opportunities-extension-account-plans-header h2 { font-size: 16px; line-height: 22px; }\n[data-consumption-layout=\"compact\"] .opportunities-extension-account-plans-header p { font-size: 12px; line-height: 16px; margin-top: 3px; overflow-wrap: anywhere; }\n[data-consumption-layout=\"compact\"] .opportunities-extension-icon-button { width: 28px; height: 28px; min-width: 28px; min-height: 28px; padding: 4px; }\n[data-consumption-layout=\"compact\"] .opportunities-extension-account-plans-tabs { padding: 4px 12px 0; }\n[data-consumption-layout=\"compact\"] .opportunities-extension-account-plan-tab { min-height: 30px; padding: 4px 8px; font-size: 12px; }\n[data-consumption-layout=\"compact\"] .opportunities-extension-account-plan-panel { padding: 8px 12px; }\n[data-consumption-layout=\"compact\"] .opportunities-extension-account-plan-details { gap: 0 12px; }\n[data-consumption-layout=\"compact\"] .opportunities-extension-account-plan-details > div { grid-template-columns: minmax(85px, 35%) minmax(0, 1fr); min-height: 32px; padding: 4px 0; gap: 6px; }\n[data-consumption-layout=\"compact\"] .opportunities-extension-account-plan-details dt { font-size: 11px; line-height: 16px; }\n[data-consumption-layout=\"compact\"] .opportunities-extension-account-plan-details dd { font-size: 12px; line-height: 16px; }\n[data-consumption-layout=\"compact\"] .opportunities-extension-consumption-trend { margin-top: 8px; padding-top: 6px; }\n[data-consumption-layout=\"compact\"] .opportunities-extension-consumption-periods button { min-height: 28px; padding: 4px 8px; font-size: 12px; }\n[data-consumption-layout=\"compact\"] .opportunities-extension-consumption-navigation { grid-template-columns: 28px minmax(120px, auto) 28px; min-height: 30px; }\n[data-consumption-layout=\"compact\"] .opportunities-extension-consumption-navigation strong { font-size: 12px; }\n[data-consumption-layout=\"compact\"] .opportunities-extension-consumption-navigation-button { width: 28px; height: 28px; }\n[data-consumption-layout=\"compact\"] .opportunities-extension-consumption-visual { grid-template-columns: minmax(0, 1fr) 150px; gap: 8px; }\n[data-consumption-layout=\"compact\"] .opportunities-extension-consumption-svg { height: 100%; max-height: 100%; min-width: 0; }\n[data-consumption-layout=\"compact\"] .opportunities-extension-consumption-legend button { font-size: 11px; padding: 4px; gap: 5px; }\n\n/* Small viewports must scroll rather than clip details or collapse the chart. */\n@media (max-height: 560px), (max-width: 700px) {\n [data-consumption-layout=\"compact\"] .opportunities-extension-account-plans-panels { overflow: auto; }\n [data-consumption-layout=\"compact\"] .opportunities-extension-account-plan-panel { height: auto; min-height: 100%; grid-template-rows: auto minmax(280px, 1fr); }\n}\n@media (max-width: 700px) {\n [data-consumption-layout=\"compact\"] .opportunities-extension-account-plan-details { grid-template-columns: minmax(0, 1fr); }\n [data-consumption-layout=\"compact\"] .opportunities-extension-consumption-visual { grid-template-columns: minmax(0, 1fr); grid-template-rows: minmax(0, 1fr) auto; }\n [data-consumption-layout=\"compact\"] .opportunities-extension-consumption-legend { display: flex; flex-wrap: wrap; }\n}\n\n.opportunities-extension-plan-detail-badges { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; }\n.opportunities-extension-consumption-trend-header { align-items: center; justify-content: space-between; gap: 12px; }\n.opportunities-extension-consumption-trend-header > .opportunities-extension-plan-detail-badges { min-width: 0; }\n.opportunities-extension-plan-detail-skeleton { width: 64px; height: 20px; }\n.opportunities-extension-projection-badge {\n display: inline-flex; align-items: center; justify-content: center; border: 1px solid transparent;\n padding: 3px 8px; border-radius: 12px; font-size: 11px; font-weight: 600; line-height: 1.3;\n color: #5f5a55; background: #eceae8; white-space: nowrap;\n}\nbutton.opportunities-extension-projection-badge { cursor: pointer; }\n.opportunities-extension-projection-badge[data-status=\"forfeit\"] { color: #a12424; background: #fbe5e5; }\n.opportunities-extension-projection-badge[data-status=\"overage\"] { color: #185a9d; background: #e0edfc; }\n.opportunities-extension-projection-badge:focus-visible { outline: 2px solid currentColor; outline-offset: 2px; }\n.opportunities-extension-projection-popover {\n position: fixed; z-index: 8; box-sizing: border-box; padding: 14px; overflow: auto; overscroll-behavior: contain;\n border: 1px solid #b9b4ae; border-radius: 4px; background: #ffffff; color: #312d2a;\n box-shadow: 0 4px 16px #0003; font-size: 12px; line-height: 1.4; overflow-wrap: anywhere;\n}\n.opportunities-extension-projection-popover[hidden] { display: none; }\n.opportunities-extension-projection-popover .opportunities-extension-projection-dismiss {\n display: grid; place-items: center; width: 24px; height: 24px; margin-left: auto; margin-bottom: 8px; padding: 4px; border: 1px solid #b9b4ae;\n border-radius: 4px; background: transparent; color: inherit; cursor: pointer; font-size: 12px;\n}\n.opportunities-extension-projection-dismiss svg { width: 14px; height: 14px; }\n.opportunities-extension-projection-line h3 { margin: 0 0 8px; font-size: 12px; }\n.opportunities-extension-projection-fields { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px 16px; margin: 0; }\n.opportunities-extension-projection-fields > div { min-width: 0; }\n.opportunities-extension-projection-fields dt { font-weight: 600; color: #5f5a55; font-size: 11px; }\n.opportunities-extension-projection-fields dd { margin: 2px 0 0; font-variant-numeric: tabular-nums; }\n.opportunities-extension-projection-fields a { color: #006d7a; text-decoration: underline; }\n.opportunities-extension-projection-children { margin-top: 14px; padding-left: 12px; border-left: 2px solid #dedbd7; }\n.opportunities-extension-projection-children > section + section { margin-top: 16px; }\n.opportunities-extension-plan-detail-error { color: #a12424; font-size: 12px; }\n.opportunities-extension-projection-empty { color: #5f5a55; font-size: 12px; }\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-projection-popover { color: #f1efec; background: #302e2b; border-color: #716b64; }\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-projection-badge { color: #dedbd7; background: #484541; }\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-projection-badge[data-status=\"forfeit\"] { color: #ffb9b9; background: #682e32; }\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-projection-badge[data-status=\"overage\"] { color: #c5dfff; background: #224b76; }\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-projection-fields dt,\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-projection-empty { color: #cec9c2; }\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-projection-fields a { color: #76dbea; }\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-plan-detail-error { color: #ffb9b9; }\n@media (max-width: 480px) { .opportunities-extension-projection-fields { grid-template-columns: minmax(0, 1fr); } }\n@media (max-width: 480px) {\n .opportunities-extension-consumption-trend-header > .opportunities-extension-consumption-periods { grid-template-columns: repeat(2, minmax(0, 1fr)); }\n}\n@media (prefers-reduced-motion: reduce) { .opportunities-extension-plan-detail-skeleton { animation: none; } }\n\n.opportunities-extension-account-plan-details > .opportunities-extension-contract-row {\n align-items: center;\n}\n.opportunities-extension-contract-row > dt,\n.opportunities-extension-contract-row > dd,\n.opportunities-extension-contract-ratecard > span {\n line-height: 20px;\n}\n.opportunities-extension-contract-ratecard {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: 6px 10px;\n}\n.opportunities-extension-ratecard-button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n align-self: center;\n box-sizing: border-box;\n height: 28px;\n margin: 0;\n flex: 0 0 auto;\n padding: 0 8px;\n border: 1px solid #8aaeb9;\n border-radius: 4px;\n background: #fff;\n color: #006b80;\n font: inherit;\n font-size: 12px;\n font-weight: 600;\n line-height: 20px;\n cursor: pointer;\n}\n.opportunities-extension-ratecard-button:hover { background: #e8f4f6; }\n.opportunities-extension-ratecard-overlay {\n position: absolute;\n inset: 0;\n z-index: 8;\n display: flex;\n justify-content: flex-end;\n overflow: hidden;\n overflow: clip;\n background: rgba(0, 0, 0, 0);\n transition: background-color 320ms ease;\n}\n.opportunities-extension-ratecard-overlay[data-open=\"true\"] { background: rgba(0, 0, 0, .32); }\n.opportunities-extension-ratecard-drawer {\n --ratecard-bg: #fff;\n --ratecard-heading: #faf9f8;\n --ratecard-text: #312d2a;\n --ratecard-muted: #615a55;\n --ratecard-border: #dedbd8;\n --ratecard-accent: #00758f;\n --ratecard-skeleton: #eae7e4;\n display: grid;\n grid-template-rows: auto auto auto minmax(0, 1fr);\n width: min(1520px, calc(100% - 48px));\n height: 100%;\n min-width: 0;\n box-sizing: border-box;\n overflow: hidden;\n background: var(--ratecard-bg);\n color: var(--ratecard-text);\n box-shadow: -6px 0 24px rgba(0, 0, 0, .2);\n transform: translateX(100%);\n transition: transform 320ms cubic-bezier(.22, .61, .36, 1);\n}\n.opportunities-extension-ratecard-overlay[data-open=\"true\"] .opportunities-extension-ratecard-drawer { transform: translateX(0); }\n.opportunities-extension-ratecard-header > div { min-width: 0; overflow-wrap: anywhere; }\n.opportunities-extension-ratecard-plan-details { padding: 12px 20px 0; }\n.opportunities-extension-ratecard-toolbar { display: flex; align-items: center; gap: 16px; padding: 12px 20px; }\n.opportunities-extension-ratecard-search {\n width: min(360px, 100%);\n min-width: 0;\n box-sizing: border-box;\n padding: 9px 12px;\n border: 1px solid var(--ratecard-muted);\n border-radius: 4px;\n background: var(--ratecard-bg);\n color: var(--ratecard-text);\n font: inherit;\n font-size: 13px;\n}\n.opportunities-extension-ratecard-search::placeholder { color: var(--ratecard-muted); opacity: 1; }\n.opportunities-extension-ratecard-count { color: var(--ratecard-muted); font-size: 12px; white-space: nowrap; }\n.opportunities-extension-ratecard-viewport { overflow: auto; min-height: 0; min-width: 0; scrollbar-gutter: stable; }\n.opportunities-extension-ratecard-table { width: 100%; min-width: 1100px; table-layout: fixed; border-collapse: separate; border-spacing: 0; font-size: 12px; }\n.opportunities-extension-ratecard-table th, .opportunities-extension-ratecard-table td {\n padding: 11px 10px;\n border-bottom: 1px solid var(--ratecard-border);\n vertical-align: top;\n overflow-wrap: anywhere;\n text-align: left;\n}\n.opportunities-extension-ratecard-table th { position: sticky; top: 0; z-index: 1; background: var(--ratecard-heading); }\n.opportunities-extension-ratecard-sort {\n display: grid;\n grid-template-columns: minmax(0, 1fr) 12px;\n align-items: center;\n gap: 5px;\n width: 100%;\n padding: 0;\n border: 0;\n background: transparent;\n color: inherit;\n font: inherit;\n font-weight: 600;\n text-align: left;\n cursor: pointer;\n}\n.opportunities-extension-ratecard-sort-icon { color: var(--ratecard-accent); text-align: center; }\n.opportunities-extension-ratecard-table .opportunities-extension-ratecard-numeric { text-align: right; font-variant-numeric: tabular-nums; }\n.opportunities-extension-ratecard-table .opportunities-extension-ratecard-notice { padding: 28px 20px; text-align: center; }\n.opportunities-extension-ratecard-notice [role=\"alert\"] { color: #b3261e; }\n.opportunities-extension-ratecard-skeleton { display: block; width: 85%; height: 14px; border-radius: 3px; background: var(--ratecard-skeleton); animation: opportunities-ratecard-pulse 1.1s ease-in-out infinite alternate; }\n.opportunities-extension-ratecard-drawer :focus-visible, .opportunities-extension-ratecard-button:focus-visible { outline: 2px solid #00758f; outline-offset: 2px; }\n.opportunities-extension-ratecard-overlay[data-closing=\"true\"] { pointer-events: none; background: rgba(0, 0, 0, 0); transition-duration: 180ms; }\n.opportunities-extension-ratecard-overlay[data-closing=\"true\"] .opportunities-extension-ratecard-drawer { transform: translateX(100%); transition-duration: 180ms; }\n[data-theme=\"dark\"] .opportunities-extension-ratecard-drawer {\n --ratecard-bg: #292725;\n --ratecard-heading: #35322f;\n --ratecard-text: #f5f3f1;\n --ratecard-muted: #c2bcb6;\n --ratecard-border: #514c47;\n --ratecard-accent: #59d1e5;\n --ratecard-skeleton: #514c47;\n}\n[data-theme=\"dark\"] .opportunities-extension-ratecard-button { color: #59d1e5; border-color: #6298a2; background: #292725; }\n[data-theme=\"dark\"] .opportunities-extension-ratecard-button:hover { background: #313f40; }\n[data-theme=\"dark\"] .opportunities-extension-ratecard-notice [role=\"alert\"] { color: #ffb4ab; }\n[data-theme=\"dark\"] .opportunities-extension-ratecard-drawer :focus-visible, [data-theme=\"dark\"] .opportunities-extension-ratecard-button:focus-visible { outline-color: #59d1e5; }\n@keyframes opportunities-ratecard-pulse { from { opacity: .45; } to { opacity: 1; } }\n@media (max-width: 720px) {\n .opportunities-extension-ratecard-drawer { width: 100%; }\n .opportunities-extension-ratecard-toolbar { flex-wrap: wrap; gap: 8px; }\n}\n@media (prefers-reduced-motion: reduce) {\n .opportunities-extension-ratecard-overlay, .opportunities-extension-ratecard-drawer, .opportunities-extension-ratecard-skeleton,\n .opportunities-extension-ratecard-overlay[data-closing=\"true\"], .opportunities-extension-ratecard-overlay[data-closing=\"true\"] .opportunities-extension-ratecard-drawer { animation: none; transition: none; }\n}\n\n.opportunities-extension-account-plans-dialog.opportunities-extension-install-base-dialog,\n.opportunities-extension-account-plans-dialog.opportunities-extension-service-requests-dialog {\n grid-template-rows: auto auto minmax(0, 1fr);\n}\n.opportunities-extension-customer-table-toolbar { padding: 12px 16px; border-bottom: 1px solid #dedbd7; }\n.opportunities-extension-customer-table-search {\n box-sizing: border-box;\n width: min(420px, 100%);\n min-width: 0;\n height: 38px;\n padding: 8px 12px;\n border: 1px solid #8f8a85;\n border-radius: 4px;\n background: #fff;\n color: #312d2a;\n font: inherit;\n font-size: 13px;\n}\n.opportunities-extension-customer-table-search::placeholder { color: #5f5a55; opacity: 1; }\n.opportunities-extension-customer-table-search:focus-visible { outline: 2px solid #00758f; outline-offset: 2px; }\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-customer-table-toolbar { border-color: #484541; }\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-customer-table-search { background: #353331; color: #f6f4f2; border-color: #8f8a85; }\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-customer-table-search::placeholder { color: #c9c5c1; }\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-customer-table-search:focus-visible { outline-color: #59d1e5; }\n\n.opportunities-extension-views { min-height: 0; min-width: 0; display: grid; grid-template-rows: auto minmax(0, 1fr); gap: 8px; }\n.opportunities-extension-views > [hidden] { display: none; }\n.opportunities-extension-view-tabs { display: flex; gap: 20px; border-bottom: 1px solid #dedbd7; }\n.opportunities-extension-view-tabs button { border: 0; border-bottom: 3px solid transparent; background: transparent; color: #625d58; padding: 6px 2px 9px; font: 600 13px/20px Arial, sans-serif; cursor: pointer; }\n.opportunities-extension-view-tabs button[aria-selected=\"true\"] { border-bottom-color: #00758f; color: #00657b; }\n.opportunities-extension-view-tabs button:focus-visible { outline: 2px solid #00758f; outline-offset: -2px; }\n.opportunities-extension-view-tabs button { display: inline-flex; align-items: center; gap: 6px; }\n.opportunities-extension-view-tabs button svg { width: 16px; height: 16px; flex: 0 0 16px; }\n.opportunities-extension-calendar {\n --fc-border-color: #dedbd7; --fc-page-bg-color: #fff; --fc-neutral-bg-color: #f5f4f2;\n --fc-neutral-text-color: #625d58; --fc-today-bg-color: #eef7fa;\n --fc-button-bg-color: #fff; --fc-button-text-color: #312d2a; --fc-button-border-color: #b9b4ae;\n --fc-button-hover-bg-color: #e6f3f5; --fc-button-hover-border-color: #00758f;\n --fc-button-active-bg-color: #dceff3; --fc-button-active-border-color: #00758f;\n min-height: 0; min-width: 0; overflow: hidden; display: flex; flex-direction: column; padding: 12px; background: #fff; color: #312d2a;\n border: 1px solid var(--fc-border-color); border-radius: 4px; font: 13px/1.4 Arial, sans-serif;\n}\n.opportunities-calendar-grid { flex: 1 1 0; min-height: 0; min-width: 0; }\n.opportunities-extension-calendar .fc { min-width: 620px; }\n.opportunities-extension-calendar .fc .fc-toolbar { gap: 12px; flex-wrap: wrap; margin-bottom: 16px; }\n.opportunities-extension-calendar .fc .fc-toolbar-title { font-size: 18px; line-height: 26px; letter-spacing: 0; }\n.opportunities-extension-calendar .fc .fc-button { font-size: 13px; line-height: 20px; padding: 5px 10px; border-radius: 4px; box-shadow: none; }\n.opportunities-extension-calendar .fc .fc-button:focus-visible { outline: 2px solid #00758f; outline-offset: 2px; }\n.opportunities-extension-calendar .fc .fc-button svg { width: 16px; height: 16px; margin-right: 6px; vertical-align: -3px; }\n.opportunities-extension-calendar .fc a { color: inherit; text-decoration: none; }\n.opportunities-extension-calendar .fc th { background: var(--fc-neutral-bg-color); font-size: 12px; font-weight: 600; padding: 4px 0; }\n.opportunities-extension-calendar .fc .fc-daygrid-day-number { font-size: 11px; line-height: 18px; padding: 1px 6px; }\n.opportunities-extension-calendar .fc .fc-daygrid-day-events { min-height: 0; margin-bottom: 0; }\n.opportunities-extension-calendar .fc .fc-daygrid-day-bottom { margin: 0; }\n.opportunities-extension-calendar .fc .fc-daygrid-day-frame { min-height: 0; padding-top: 0; }\n/* Event content must not contribute to the table's intrinsic row heights. */\n.opportunities-extension-calendar .fc .fc-daygrid-body tbody { height: 100%; }\n.opportunities-extension-calendar .fc .fc-daygrid-body tr { height: calc(100% / var(--opportunities-calendar-weeks, 5)); }\n.opportunities-extension-calendar .fc .fc-daygrid-day { position: relative; }\n.opportunities-extension-calendar .fc .fc-daygrid-day-frame { position: absolute; inset: 0; }\n.opportunities-extension-calendar .fc .fc-daygrid-week-number { top: 0; left: 0; padding: 3px 6px; background: var(--fc-neutral-bg-color); color: inherit; font: 600 10px/16px Arial, sans-serif; white-space: nowrap; }\n.opportunities-calendar-day-count { position: absolute; top: 2px; left: 6px; min-width: 18px; height: 16px; padding: 0 4px; border-radius: 8px; background: #dceff3; color: #005467; font: 600 10px/16px Arial, sans-serif; text-align: center; }\n.fc-daygrid-week-number ~ .opportunities-calendar-day-count { left: 82px; }\n.opportunities-calendar-day-count[hidden] { display: none; }\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-calendar-day-count:not([data-empty=\"true\"]) { background: #16454f; color: #b9eaf4; }\n.opportunities-extension-calendar .fc .fc-day-other { background: var(--fc-neutral-bg-color); }\n.opportunities-extension-calendar .fc .fc-daygrid-event { white-space: normal; margin: 3px 4px; border-radius: 4px; cursor: pointer; border-width: 0 0 0 3px; border-style: solid; }\n.opportunities-extension-calendar .fc .fc-event-main { color: inherit; }\n.opportunities-calendar-event-content { min-width: 0; padding: 5px 6px; overflow-wrap: anywhere; }\n.opportunities-calendar-event-content strong { display: block; font-size: 12px; line-height: 16px; font-weight: 600; }\n.opportunities-calendar-event-content span { display: block; margin-top: 3px; font-size: 11px; line-height: 15px; }\n.opportunities-calendar-event-content strong,\n.opportunities-calendar-event-content span,\n.opportunities-calendar-day-item strong,\n.opportunities-calendar-day-item span { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n.opportunities-extension-calendar .fc .fc-daygrid-more-link { color: #00657b; font-size: 11px; line-height: 16px; padding: 0 5px; }\n.opportunities-calendar-day-list { min-height: 0; overflow-y: auto; padding: 8px 20px 20px; }\n.opportunities-calendar-day-item { display: block; width: 100%; padding: 12px 0; border: 0; border-bottom: 1px solid #dedbd7; background: transparent; color: #00657b; text-align: left; cursor: pointer; overflow-wrap: anywhere; }\n.opportunities-calendar-day-item strong, .opportunities-calendar-day-item span { display: block; font: 600 13px/20px Arial, sans-serif; }\n.opportunities-calendar-day-item span { margin-top: 4px; font-weight: 400; }\n.opportunities-calendar-day-item:focus-visible { outline: 2px solid currentColor; outline-offset: 2px; }\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-calendar-day-item { border-bottom-color: #514d48; color: #80d6e8; }\n.opportunities-extension-calendar .fc .opportunities-calendar-stage-SQL { background: #dff1f4; border-color: #00829d; color: #005467; }\n.opportunities-extension-calendar .fc .opportunities-calendar-stage-PIPELINE,\n.opportunities-extension-calendar .fc .opportunities-calendar-stage-OTHER { background: #efeeec; border-color: #706a64; color: #49443f; }\n.opportunities-extension-calendar .fc .opportunities-calendar-stage-UPSIDE { background: #fff0da; border-color: #ba7000; color: #774700; }\n.opportunities-extension-calendar .fc .opportunities-calendar-stage-FORECAST { background: #f0e8f7; border-color: #8262a3; color: #624379; }\n.opportunities-extension-calendar .fc .opportunities-calendar-stage-WON { background: #e2f1e5; border-color: #2f8050; color: #215939; }\n.opportunities-calendar-message { margin: 0 0 12px; font-size: 13px; }\n.opportunities-calendar-message[hidden], .opportunities-calendar-grid[hidden], .opportunities-calendar-undated[hidden] { display: none; }\n.opportunities-calendar-undated { flex: 0 0 auto; max-height: 90px; overflow-y: auto; margin-top: 6px; border-top: 1px solid var(--fc-border-color); padding-top: 4px; }\n.opportunities-calendar-undated summary { cursor: pointer; font-size: 12px; line-height: 18px; }\n.opportunities-calendar-undated h3 { font-size: 13px; margin: 0 0 8px; }\n.opportunities-calendar-undated button { display: block; background: none; border: 0; text-align: left; font: inherit; padding: 6px 0; color: inherit; text-decoration: underline; cursor: pointer; overflow-wrap: anywhere; }\n.opportunities-calendar-detail .opportunities-extension-account-plans-header { align-items: start; gap: 16px; }\n.opportunities-calendar-detail .opportunities-extension-action-form-dialog { display: grid; grid-template-rows: auto minmax(0, 1fr); overflow: hidden; }\n.opportunities-calendar-detail h2 { font-size: 18px; overflow-wrap: anywhere; }\n.opportunities-calendar-detail-content { padding: 20px; overflow-y: auto; }\n.opportunities-calendar-detail-content a { color: #006b82; }\n.opportunities-calendar-detail-badges { display: flex; gap: 8px; margin-top: 18px; }\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-view-tabs { border-color: #514d48; }\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-view-tabs button { color: #c7c2bc; }\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-view-tabs button[aria-selected=\"true\"] { color: #80d6e8; border-bottom-color: #80d6e8; }\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-calendar {\n --fc-border-color: #514d48; --fc-page-bg-color: #292725; --fc-neutral-bg-color: #33302d;\n --fc-neutral-text-color: #c7c2bc; --fc-today-bg-color: #223b3f;\n --fc-button-bg-color: #292725; --fc-button-text-color: #eeeae6; --fc-button-border-color: #756f68;\n --fc-button-hover-bg-color: #234850; --fc-button-hover-border-color: #80d6e8;\n --fc-button-active-bg-color: #234850; --fc-button-active-border-color: #80d6e8;\n background: #292725; color: #eeeae6;\n}\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-calendar .fc .opportunities-calendar-stage-SQL { background: #16454f; color: #b9eaf4; border-color: #67cde0; }\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-calendar .fc .opportunities-calendar-stage-PIPELINE,\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-calendar .fc .opportunities-calendar-stage-OTHER { background: #403c38; color: #e4dfd9; border-color: #b8b1a8; }\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-calendar .fc .opportunities-calendar-stage-UPSIDE { background: #533e1e; color: #ffe0a9; border-color: #dba44f; }\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-calendar .fc .opportunities-calendar-stage-FORECAST { background: #493657; color: #ead5fc; border-color: #b998d8; }\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-calendar .fc .opportunities-calendar-stage-WON { background: #244a32; color: #c7edce; border-color: #84c396; }\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-extension-calendar .fc .fc-daygrid-more-link,\n.opportunities-extension-modal[data-theme=\"dark\"] .opportunities-calendar-detail-content a { color: #80d6e8; }\n@media (max-width: 700px) {\n .opportunities-extension-body { overflow-y: auto; grid-template-rows: max-content max-content minmax(480px, 1fr); }\n .opportunities-extension-views { min-height: 480px; }\n .opportunities-extension-calendar { padding: 10px; overflow-x: auto; }\n .opportunities-extension-calendar .fc { min-width: 0; }\n .opportunities-extension-calendar .fc .fc-view-harness { min-width: 560px; }\n .opportunities-extension-calendar .fc .fc-toolbar-title { font-size: 16px; }\n .opportunities-extension-calendar .fc .fc-daygrid-day-frame { padding-top: 22px; }\n .opportunities-calendar-day-count { top: 25px; }\n .fc-daygrid-week-number ~ .opportunities-calendar-day-count { left: 6px; }\n}\n\n.oe-time-dialog { --time-bg: #fff; --time-input-bg: #fff; --time-muted: #faf9f8; --time-border: #dedbd7; --time-input-border: #8f8983; --time-accent: #00758f; width: min(1600px, 100%); max-height: calc(100dvh - 24px); display: flex; flex-direction: column; overflow: hidden; font-size: 13px; line-height: 20px; }\n.opportunities-extension-modal[data-theme=\"dark\"] .oe-time-dialog { --time-bg: #292725; --time-input-bg: #211f1d; --time-muted: #35322f; --time-border: #514c47; --time-input-border: #918a82; --time-accent: #80d6e8; }\n.oe-time-modal { grid-template-columns: minmax(0, 1fr); }\n.oe-time-dialog, .oe-time-body { min-width: 0; }\n.oe-time-dialog * { box-sizing: border-box; }\n.oe-time-dialog [hidden] { display: none !important; }\n.oe-time-header { flex-shrink: 0; }\n.oe-time-body { padding: 18px; overflow: auto; min-height: 0; }\n.oe-time-toolbar { display: flex; align-items: center; justify-content: space-between; gap: 16px; margin-bottom: 20px; }\n.oe-time-navigation { display: flex; align-items: center; gap: 8px; }\n.oe-time-ending { display: grid; gap: 4px; text-align: right; }\n.oe-time-summary { display: grid; grid-template-columns: repeat(4, 1fr); padding: 12px 0; gap: 12px; text-align: center; border-block: 1px solid var(--time-border); margin-bottom: 18px; }\n.oe-time-body button { min-width: 38px; min-height: 38px; display: inline-flex; gap: 8px; align-items: center; justify-content: center; color: inherit; background: transparent; border: 1px solid var(--time-input-border); border-radius: 4px; padding: 7px; cursor: pointer; font: inherit; }\n.oe-time-dialog button:hover:not(:disabled) { background: var(--time-muted); border-color: var(--time-accent); }\n.oe-time-dialog svg { width: 17px; height: 17px; flex-shrink: 0; }\n.oe-time-dialog input, .oe-time-dialog select, .oe-time-dialog textarea { font: inherit; line-height: 20px; color: inherit; background: var(--time-input-bg); border: 1px solid var(--time-input-border); border-radius: 4px; padding: 8px; min-height: 38px; }\n.oe-time-dialog select { height: 38px; }\n.oe-time-dialog :focus-visible { outline: 2px solid var(--time-accent); outline-offset: 2px; }\n.oe-time-dialog :disabled { opacity: .55; cursor: not-allowed; }\n.oe-time-grid { overflow-x: auto; }\n.oe-time-grid table { width: 100%; min-width: 1060px; border-collapse: collapse; table-layout: fixed; }\n.oe-time-grid th { background: var(--time-muted); padding: 10px 5px; font-weight: 600; text-align: center; border-bottom: 1px solid var(--time-border); }\n.oe-time-grid th:first-child { width: 23%; }\n.oe-time-grid th:nth-child(2), .oe-time-grid th:nth-child(3) { width: 12%; }\n.oe-time-grid th:nth-last-child(-n+3) { width: 46px; }\n.oe-time-grid td { padding: 10px 5px; border-bottom: 1px solid var(--time-border); vertical-align: middle; text-align: center; }\n.oe-time-grid select { width: 100%; }\n.oe-time-grid input[type=\"number\"] { width: min(100%, 60px); padding: 6px 3px; text-align: center; }\n.oe-time-grid .opportunities-extension-action-combobox input { width: 100%; text-align: left; text-overflow: ellipsis; }\n.oe-time-grid .opportunities-extension-action-options { text-align: left; }\n.oe-time-dialog .opportunities-extension-select-tooltip { position: fixed; z-index: 100; width: min(560px, calc(100vw - 40px)); max-height: min(440px, calc(100dvh - 48px)); overflow: auto; padding: 14px; border: 1px solid var(--time-input-border); border-radius: 4px; background: var(--time-input-bg); color: inherit; box-shadow: 0 4px 16px #0003; text-align: left; }\n.oe-time-sr-details { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px 18px; margin: 0; }\n.oe-time-sr-details > div { min-width: 0; overflow-wrap: anywhere; }\n.oe-time-sr-details dt { font-size: 11px; opacity: .75; margin-bottom: 2px; }\n.oe-time-sr-details dd { margin: 0; white-space: pre-wrap; }\n.oe-time-sr-details .oe-time-sr-description { grid-column: 1 / -1; }\n.oe-time-day > span { display: block; }\n.oe-time-day .oe-time-day-label { display: inline-flex; align-items: center; gap: 4px; margin-bottom: 9px; }\n.oe-time-day-total { font-size: 10px; background: var(--time-accent); color: var(--time-bg); min-width: 16px; border-radius: 8px; padding: 2px 4px; }\n.oe-time-notes textarea { width: 100%; resize: vertical; }\n.oe-time-notes-box { position: relative; text-align: left; background: var(--time-input-bg); border: 1px solid var(--time-input-border); border-radius: 4px; padding: 14px 12px 42px; }\n.oe-time-notes-count { display: block; font-weight: 600; opacity: .8; margin-bottom: 8px; }\n.oe-time-notes-box textarea { border: 0; min-height: 128px; padding: 0; }\n.oe-time-notes-box .oe-time-notes-collapse { position: absolute; right: 0; bottom: 0; background: var(--time-muted); }\n.oe-time-footer { flex-wrap: wrap; justify-content: flex-start; margin-top: 18px; }\n.oe-time-footer button { padding: 7px 14px; }\n.oe-time-status { padding: 12px 0; }\n.oe-time-status[role=\"alert\"] { color: #a12d27; }\n.oe-time-dialog [aria-invalid=\"true\"] { border-color: #b3261e; outline: 1px solid #b3261e; }\n.opportunities-extension-modal[data-theme=\"dark\"] .oe-time-status[role=\"alert\"] { color: #ffd9d5; }\n.opportunities-extension-modal[data-theme=\"dark\"] .oe-time-dialog [aria-invalid=\"true\"] { border-color: #ffaaa4; outline-color: #ffaaa4; }\n@media (max-width: 700px) { .oe-time-modal { padding: 8px; } .oe-time-dialog { max-height: calc(100dvh - 16px); } .oe-time-body { padding: 12px; } .oe-time-summary { grid-template-columns: repeat(2, 1fr); text-align: left; } .oe-time-toolbar { flex-wrap: wrap; } .oe-time-ending { text-align: left; } }\n"; function loadOpportunityCalendarLibrary() { /* MIT License Copyright (c) 2021 Adam Shaw Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*! FullCalendar Standard Bundle v6.1.19 Docs & License: https://fullcalendar.io/docs/initialize-globals (c) 2024 Adam Shaw */ var FullCalendar=function(e){"use strict";var t,n,r,i,s,o,a,l,c,d={},u=[],h=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i;function f(e,t){for(var n in t)e[n]=t[n];return e}function g(e){var t=e.parentNode;t&&t.removeChild(e)}function p(e,n,r){var i,s,o,a={};for(o in n)"key"==o?i=n[o]:"ref"==o?s=n[o]:a[o]=n[o];if(arguments.length>2&&(a.children=arguments.length>3?t.call(arguments,2):r),"function"==typeof e&&null!=e.defaultProps)for(o in e.defaultProps)void 0===a[o]&&(a[o]=e.defaultProps[o]);return m(e,a,i,s,null)}function m(e,t,i,s,o){var a={type:e,props:t,key:i,ref:s,__k:null,__:null,__b:0,__e:null,__d:void 0,__c:null,__h:null,constructor:void 0,__v:null==o?++r:o};return null==o&&null!=n.vnode&&n.vnode(a),a}function v(){return{current:null}}function y(e){return e.children}function b(e,t,n){"-"===t[0]?e.setProperty(t,null==n?"":n):e[t]=null==n?"":"number"!=typeof n||h.test(t)?n:n+"px"}function E(e,t,n,r,i){var s;e:if("style"===t)if("string"==typeof n)e.style.cssText=n;else{if("string"==typeof r&&(e.style.cssText=r=""),r)for(t in r)n&&t in n||b(e.style,t,"");if(n)for(t in n)r&&n[t]===r[t]||b(e.style,t,n[t])}else if("o"===t[0]&&"n"===t[1])s=t!==(t=t.replace(/Capture$/,"")),t=t.toLowerCase()in e?t.toLowerCase().slice(2):t.slice(2),e.l||(e.l={}),e.l[t+s]=n,n?r||e.addEventListener(t,s?A:S,s):e.removeEventListener(t,s?A:S,s);else if("dangerouslySetInnerHTML"!==t){if(i)t=t.replace(/xlink(H|:h)/,"h").replace(/sName$/,"s");else if("width"!==t&&"height"!==t&&"href"!==t&&"list"!==t&&"form"!==t&&"tabIndex"!==t&&"download"!==t&&t in e)try{e[t]=null==n?"":n;break e}catch(e){}"function"==typeof n||(null==n||!1===n&&-1==t.indexOf("-")?e.removeAttribute(t):e.setAttribute(t,n))}}function S(e){s=!0;try{return this.l[e.type+!1](n.event?n.event(e):e)}finally{s=!1}}function A(e){s=!0;try{return this.l[e.type+!0](n.event?n.event(e):e)}finally{s=!1}}function D(e,t){this.props=e,this.context=t}function w(e,t){if(null==t)return e.__?w(e.__,e.__.__k.indexOf(e)+1):null;for(var n;tt&&o.sort((function(e,t){return e.__v.__b-t.__v.__b})));_.__r=0}function T(e,t,n,r,i,s,o,a,l,c){var h,f,g,p,v,b,E,S=r&&r.__k||u,A=S.length;for(n.__k=[],h=0;h0?m(p.type,p.props,p.key,p.ref?p.ref:null,p.__v):p)){if(p.__=n,p.__b=n.__b+1,null===(g=S[h])||g&&p.key==g.key&&p.type===g.type)S[h]=void 0;else for(f=0;f=0;t--)if((n=e.__k[t])&&(r=O(n)))return r;return null}function N(e,t,r,i,s,o,a,l,c){var d,u,h,g,p,m,v,b,E,S,A,w,C,R,x,_=t.type;if(void 0!==t.constructor)return null;null!=r.__h&&(c=r.__h,l=t.__e=r.__e,t.__h=null,o=[l]),(d=n.__b)&&d(t);try{e:if("function"==typeof _){if(b=t.props,E=(d=_.contextType)&&i[d.__c],S=d?E?E.props.value:d.__:i,r.__c?v=(u=t.__c=r.__c).__=u.__E:("prototype"in _&&_.prototype.render?t.__c=u=new _(b,S):(t.__c=u=new D(b,S),u.constructor=_,u.render=z),E&&E.sub(u),u.props=b,u.state||(u.state={}),u.context=S,u.__n=i,h=u.__d=!0,u.__h=[],u._sb=[]),null==u.__s&&(u.__s=u.state),null!=_.getDerivedStateFromProps&&(u.__s==u.state&&(u.__s=f({},u.__s)),f(u.__s,_.getDerivedStateFromProps(b,u.__s))),g=u.props,p=u.state,u.__v=t,h)null==_.getDerivedStateFromProps&&null!=u.componentWillMount&&u.componentWillMount(),null!=u.componentDidMount&&u.__h.push(u.componentDidMount);else{if(null==_.getDerivedStateFromProps&&b!==g&&null!=u.componentWillReceiveProps&&u.componentWillReceiveProps(b,S),!u.__e&&null!=u.shouldComponentUpdate&&!1===u.shouldComponentUpdate(b,u.__s,S)||t.__v===r.__v){for(t.__v!==r.__v&&(u.props=b,u.state=u.__s,u.__d=!1),t.__e=r.__e,t.__k=r.__k,t.__k.forEach((function(e){e&&(e.__=t)})),A=0;A3;)n.pop()();if(n[1]>>1,1),t.i.removeChild(e)}}),U(p(ue,{context:t.context},e.__v),t.l)):t.l&&t.componentWillUnmount()}function fe(e,t){var n=p(he,{__v:e,i:t});return n.containerInfo=t,n}(ce.prototype=new D).__a=function(e){var t=this,n=le(t.__v),r=t.o.get(e);return r[0]++,function(i){var s=function(){t.props.revealOrder?(r.push(i),de(t,e,r)):i()};n?n(s):s()}},ce.prototype.render=function(e){this.u=null,this.o=new Map;var t=M(e.children);e.revealOrder&&"b"===e.revealOrder[0]&&t.reverse();for(var n=t.length;n--;)this.o.set(t[n],this.u=[1,0,this.u]);return e.children},ce.prototype.componentDidUpdate=ce.prototype.componentDidMount=function(){var e=this;this.o.forEach((function(t,n){de(e,n,t)}))};var ge="undefined"!=typeof Symbol&&Symbol.for&&Symbol.for("react.element")||60103,pe=/^(?:accent|alignment|arabic|baseline|cap|clip(?!PathU)|color|dominant|fill|flood|font|glyph(?!R)|horiz|image|letter|lighting|marker(?!H|W|U)|overline|paint|pointer|shape|stop|strikethrough|stroke|text(?!L)|transform|underline|unicode|units|v|vector|vert|word|writing|x(?!C))[A-Z]/,me="undefined"!=typeof document,ve=function(e){return("undefined"!=typeof Symbol&&"symbol"==typeof Symbol()?/fil|che|rad/i:/fil|che|ra/i).test(e)};D.prototype.isReactComponent={},["componentWillMount","componentWillReceiveProps","componentWillUpdate"].forEach((function(e){Object.defineProperty(D.prototype,e,{configurable:!0,get:function(){return this["UNSAFE_"+e]},set:function(t){Object.defineProperty(this,e,{configurable:!0,writable:!0,value:t})}})}));var ye=n.event;function be(){}function Ee(){return this.cancelBubble}function Se(){return this.defaultPrevented}n.event=function(e){return ye&&(e=ye(e)),e.persist=be,e.isPropagationStopped=Ee,e.isDefaultPrevented=Se,e.nativeEvent=e};var Ae={configurable:!0,get:function(){return this.class}},De=n.vnode;n.vnode=function(e){var t=e.type,n=e.props,r=n;if("string"==typeof t){var i=-1===t.indexOf("-");for(var s in r={},n){var o=n[s];me&&"children"===s&&"noscript"===t||"value"===s&&"defaultValue"in n&&null==o||("defaultValue"===s&&"value"in n&&null==n.value?s="value":"download"===s&&!0===o?o="":/ondoubleclick/i.test(s)?s="ondblclick":/^onchange(textarea|input)/i.test(s+t)&&!ve(n.type)?s="oninput":/^onfocus$/i.test(s)?s="onfocusin":/^onblur$/i.test(s)?s="onfocusout":/^on(Ani|Tra|Tou|BeforeInp|Compo)/.test(s)?s=s.toLowerCase():i&&pe.test(s)?s=s.replace(/[A-Z0-9]/g,"-$&").toLowerCase():null===o&&(o=void 0),/^oninput$/i.test(s)&&(s=s.toLowerCase(),r[s]&&(s="oninputCapture")),r[s]=o)}"select"==t&&r.multiple&&Array.isArray(r.value)&&(r.value=M(n.children).forEach((function(e){e.props.selected=-1!=r.value.indexOf(e.props.value)}))),"select"==t&&null!=r.defaultValue&&(r.value=M(n.children).forEach((function(e){e.props.selected=r.multiple?-1!=r.defaultValue.indexOf(e.props.value):r.defaultValue==e.props.value}))),e.props=r,n.class!=n.className&&(Ae.enumerable="className"in n,null!=n.className&&(r.class=n.className),Object.defineProperty(r,"className",Ae))}e.$$typeof=ge,De&&De(e)};var we=n.__r;n.__r=function(e){we&&we(e),e.__c};const Ce=[],Re=new Map;function xe(e){Ce.push(e),Re.forEach(t=>{Te(t,e)})}function _e(e){let t=Re.get(e);if(!t||!t.isConnected){if(t=e.querySelector("style[data-fullcalendar]"),!t){t=document.createElement("style"),t.setAttribute("data-fullcalendar","");const n=function(){void 0===ke&&(ke=function(){const e=document.querySelector('meta[name="csp-nonce"]');if(e&&e.hasAttribute("content"))return e.getAttribute("content");const t=document.querySelector("script[nonce]");if(t)return t.nonce||"";return""}());return ke}();n&&(t.nonce=n);const r=e===document?document.head:e,i=e===document?r.querySelector("script,link[rel=stylesheet],link[as=style],style"):r.firstChild;r.insertBefore(t,i)}Re.set(e,t),function(e){for(const t of Ce)Te(e,t)}(t)}}function Te(e,t){const{sheet:n}=e,r=n.cssRules.length;t.split("}").forEach((e,t)=>{(e=e.trim())&&n.insertRule(e+"}",r+t)})}let ke;"undefined"!=typeof document&&_e(document);xe(':root{--fc-small-font-size:.85em;--fc-page-bg-color:#fff;--fc-neutral-bg-color:hsla(0,0%,82%,.3);--fc-neutral-text-color:grey;--fc-border-color:#ddd;--fc-button-text-color:#fff;--fc-button-bg-color:#2c3e50;--fc-button-border-color:#2c3e50;--fc-button-hover-bg-color:#1e2b37;--fc-button-hover-border-color:#1a252f;--fc-button-active-bg-color:#1a252f;--fc-button-active-border-color:#151e27;--fc-event-bg-color:#3788d8;--fc-event-border-color:#3788d8;--fc-event-text-color:#fff;--fc-event-selected-overlay-color:rgba(0,0,0,.25);--fc-more-link-bg-color:#d0d0d0;--fc-more-link-text-color:inherit;--fc-event-resizer-thickness:8px;--fc-event-resizer-dot-total-width:8px;--fc-event-resizer-dot-border-width:1px;--fc-non-business-color:hsla(0,0%,84%,.3);--fc-bg-event-color:#8fdf82;--fc-bg-event-opacity:0.3;--fc-highlight-color:rgba(188,232,241,.3);--fc-today-bg-color:rgba(255,220,40,.15);--fc-now-indicator-color:red}.fc-not-allowed,.fc-not-allowed .fc-event{cursor:not-allowed}.fc{display:flex;flex-direction:column;font-size:1em}.fc,.fc *,.fc :after,.fc :before{box-sizing:border-box}.fc table{border-collapse:collapse;border-spacing:0;font-size:1em}.fc th{text-align:center}.fc td,.fc th{padding:0;vertical-align:top}.fc a[data-navlink]{cursor:pointer}.fc a[data-navlink]:hover{text-decoration:underline}.fc-direction-ltr{direction:ltr;text-align:left}.fc-direction-rtl{direction:rtl;text-align:right}.fc-theme-standard td,.fc-theme-standard th{border:1px solid var(--fc-border-color)}.fc-liquid-hack td,.fc-liquid-hack th{position:relative}@font-face{font-family:fcicons;font-style:normal;font-weight:400;src:url("data:application/x-font-ttf;charset=utf-8;base64,AAEAAAALAIAAAwAwT1MvMg8SBfAAAAC8AAAAYGNtYXAXVtKNAAABHAAAAFRnYXNwAAAAEAAAAXAAAAAIZ2x5ZgYydxIAAAF4AAAFNGhlYWQUJ7cIAAAGrAAAADZoaGVhB20DzAAABuQAAAAkaG10eCIABhQAAAcIAAAALGxvY2ED4AU6AAAHNAAAABhtYXhwAA8AjAAAB0wAAAAgbmFtZXsr690AAAdsAAABhnBvc3QAAwAAAAAI9AAAACAAAwPAAZAABQAAApkCzAAAAI8CmQLMAAAB6wAzAQkAAAAAAAAAAAAAAAAAAAABEAAAAAAAAAAAAAAAAAAAAABAAADpBgPA/8AAQAPAAEAAAAABAAAAAAAAAAAAAAAgAAAAAAADAAAAAwAAABwAAQADAAAAHAADAAEAAAAcAAQAOAAAAAoACAACAAIAAQAg6Qb//f//AAAAAAAg6QD//f//AAH/4xcEAAMAAQAAAAAAAAAAAAAAAQAB//8ADwABAAAAAAAAAAAAAgAANzkBAAAAAAEAAAAAAAAAAAACAAA3OQEAAAAAAQAAAAAAAAAAAAIAADc5AQAAAAABAWIAjQKeAskAEwAAJSc3NjQnJiIHAQYUFwEWMjc2NCcCnuLiDQ0MJAz/AA0NAQAMJAwNDcni4gwjDQwM/wANIwz/AA0NDCMNAAAAAQFiAI0CngLJABMAACUBNjQnASYiBwYUHwEHBhQXFjI3AZ4BAA0N/wAMJAwNDeLiDQ0MJAyNAQAMIw0BAAwMDSMM4uINIwwNDQAAAAIA4gC3Ax4CngATACcAACUnNzY0JyYiDwEGFB8BFjI3NjQnISc3NjQnJiIPAQYUHwEWMjc2NCcB87e3DQ0MIw3VDQ3VDSMMDQ0BK7e3DQ0MJAzVDQ3VDCQMDQ3zuLcMJAwNDdUNIwzWDAwNIwy4twwkDA0N1Q0jDNYMDA0jDAAAAgDiALcDHgKeABMAJwAAJTc2NC8BJiIHBhQfAQcGFBcWMjchNzY0LwEmIgcGFB8BBwYUFxYyNwJJ1Q0N1Q0jDA0Nt7cNDQwjDf7V1Q0N1QwkDA0Nt7cNDQwkDLfWDCMN1Q0NDCQMt7gMIw0MDNYMIw3VDQ0MJAy3uAwjDQwMAAADAFUAAAOrA1UAMwBoAHcAABMiBgcOAQcOAQcOARURFBYXHgEXHgEXHgEzITI2Nz4BNz4BNz4BNRE0JicuAScuAScuASMFITIWFx4BFx4BFx4BFREUBgcOAQcOAQcOASMhIiYnLgEnLgEnLgE1ETQ2Nz4BNz4BNz4BMxMhMjY1NCYjISIGFRQWM9UNGAwLFQkJDgUFBQUFBQ4JCRULDBgNAlYNGAwLFQkJDgUFBQUFBQ4JCRULDBgN/aoCVgQIBAQHAwMFAQIBAQIBBQMDBwQECAT9qgQIBAQHAwMFAQIBAQIBBQMDBwQECASAAVYRGRkR/qoRGRkRA1UFBAUOCQkVDAsZDf2rDRkLDBUJCA4FBQUFBQUOCQgVDAsZDQJVDRkLDBUJCQ4FBAVVAgECBQMCBwQECAX9qwQJAwQHAwMFAQICAgIBBQMDBwQDCQQCVQUIBAQHAgMFAgEC/oAZEhEZGRESGQAAAAADAFUAAAOrA1UAMwBoAIkAABMiBgcOAQcOAQcOARURFBYXHgEXHgEXHgEzITI2Nz4BNz4BNz4BNRE0JicuAScuAScuASMFITIWFx4BFx4BFx4BFREUBgcOAQcOAQcOASMhIiYnLgEnLgEnLgE1ETQ2Nz4BNz4BNz4BMxMzFRQWMzI2PQEzMjY1NCYrATU0JiMiBh0BIyIGFRQWM9UNGAwLFQkJDgUFBQUFBQ4JCRULDBgNAlYNGAwLFQkJDgUFBQUFBQ4JCRULDBgN/aoCVgQIBAQHAwMFAQIBAQIBBQMDBwQECAT9qgQIBAQHAwMFAQIBAQIBBQMDBwQECASAgBkSEhmAERkZEYAZEhIZgBEZGREDVQUEBQ4JCRUMCxkN/asNGQsMFQkIDgUFBQUFBQ4JCBUMCxkNAlUNGQsMFQkJDgUEBVUCAQIFAwIHBAQIBf2rBAkDBAcDAwUBAgICAgEFAwMHBAMJBAJVBQgEBAcCAwUCAQL+gIASGRkSgBkSERmAEhkZEoAZERIZAAABAOIAjQMeAskAIAAAExcHBhQXFjI/ARcWMjc2NC8BNzY0JyYiDwEnJiIHBhQX4uLiDQ0MJAzi4gwkDA0N4uINDQwkDOLiDCQMDQ0CjeLiDSMMDQ3h4Q0NDCMN4uIMIw0MDOLiDAwNIwwAAAABAAAAAQAAa5n0y18PPPUACwQAAAAAANivOVsAAAAA2K85WwAAAAADqwNVAAAACAACAAAAAAAAAAEAAAPA/8AAAAQAAAAAAAOrAAEAAAAAAAAAAAAAAAAAAAALBAAAAAAAAAAAAAAAAgAAAAQAAWIEAAFiBAAA4gQAAOIEAABVBAAAVQQAAOIAAAAAAAoAFAAeAEQAagCqAOoBngJkApoAAQAAAAsAigADAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAA4ArgABAAAAAAABAAcAAAABAAAAAAACAAcAYAABAAAAAAADAAcANgABAAAAAAAEAAcAdQABAAAAAAAFAAsAFQABAAAAAAAGAAcASwABAAAAAAAKABoAigADAAEECQABAA4ABwADAAEECQACAA4AZwADAAEECQADAA4APQADAAEECQAEAA4AfAADAAEECQAFABYAIAADAAEECQAGAA4AUgADAAEECQAKADQApGZjaWNvbnMAZgBjAGkAYwBvAG4Ac1ZlcnNpb24gMS4wAFYAZQByAHMAaQBvAG4AIAAxAC4AMGZjaWNvbnMAZgBjAGkAYwBvAG4Ac2ZjaWNvbnMAZgBjAGkAYwBvAG4Ac1JlZ3VsYXIAUgBlAGcAdQBsAGEAcmZjaWNvbnMAZgBjAGkAYwBvAG4Ac0ZvbnQgZ2VuZXJhdGVkIGJ5IEljb01vb24uAEYAbwBuAHQAIABnAGUAbgBlAHIAYQB0AGUAZAAgAGIAeQAgAEkAYwBvAE0AbwBvAG4ALgAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=") format("truetype")}.fc-icon{speak:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;display:inline-block;font-family:fcicons!important;font-style:normal;font-variant:normal;font-weight:400;height:1em;line-height:1;text-align:center;text-transform:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:1em}.fc-icon-chevron-left:before{content:"\\e900"}.fc-icon-chevron-right:before{content:"\\e901"}.fc-icon-chevrons-left:before{content:"\\e902"}.fc-icon-chevrons-right:before{content:"\\e903"}.fc-icon-minus-square:before{content:"\\e904"}.fc-icon-plus-square:before{content:"\\e905"}.fc-icon-x:before{content:"\\e906"}.fc .fc-button{border-radius:0;font-family:inherit;font-size:inherit;line-height:inherit;margin:0;overflow:visible;text-transform:none}.fc .fc-button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}.fc .fc-button{-webkit-appearance:button}.fc .fc-button:not(:disabled){cursor:pointer}.fc .fc-button{background-color:transparent;border:1px solid transparent;border-radius:.25em;display:inline-block;font-size:1em;font-weight:400;line-height:1.5;padding:.4em .65em;text-align:center;-webkit-user-select:none;-moz-user-select:none;user-select:none;vertical-align:middle}.fc .fc-button:hover{text-decoration:none}.fc .fc-button:focus{box-shadow:0 0 0 .2rem rgba(44,62,80,.25);outline:0}.fc .fc-button:disabled{opacity:.65}.fc .fc-button-primary{background-color:var(--fc-button-bg-color);border-color:var(--fc-button-border-color);color:var(--fc-button-text-color)}.fc .fc-button-primary:hover{background-color:var(--fc-button-hover-bg-color);border-color:var(--fc-button-hover-border-color);color:var(--fc-button-text-color)}.fc .fc-button-primary:disabled{background-color:var(--fc-button-bg-color);border-color:var(--fc-button-border-color);color:var(--fc-button-text-color)}.fc .fc-button-primary:focus{box-shadow:0 0 0 .2rem rgba(76,91,106,.5)}.fc .fc-button-primary:not(:disabled).fc-button-active,.fc .fc-button-primary:not(:disabled):active{background-color:var(--fc-button-active-bg-color);border-color:var(--fc-button-active-border-color);color:var(--fc-button-text-color)}.fc .fc-button-primary:not(:disabled).fc-button-active:focus,.fc .fc-button-primary:not(:disabled):active:focus{box-shadow:0 0 0 .2rem rgba(76,91,106,.5)}.fc .fc-button .fc-icon{font-size:1.5em;vertical-align:middle}.fc .fc-button-group{display:inline-flex;position:relative;vertical-align:middle}.fc .fc-button-group>.fc-button{flex:1 1 auto;position:relative}.fc .fc-button-group>.fc-button.fc-button-active,.fc .fc-button-group>.fc-button:active,.fc .fc-button-group>.fc-button:focus,.fc .fc-button-group>.fc-button:hover{z-index:1}.fc-direction-ltr .fc-button-group>.fc-button:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-1px}.fc-direction-ltr .fc-button-group>.fc-button:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.fc-direction-rtl .fc-button-group>.fc-button:not(:first-child){border-bottom-right-radius:0;border-top-right-radius:0;margin-right:-1px}.fc-direction-rtl .fc-button-group>.fc-button:not(:last-child){border-bottom-left-radius:0;border-top-left-radius:0}.fc .fc-toolbar{align-items:center;display:flex;justify-content:space-between}.fc .fc-toolbar.fc-header-toolbar{margin-bottom:1.5em}.fc .fc-toolbar.fc-footer-toolbar{margin-top:1.5em}.fc .fc-toolbar-title{font-size:1.75em;margin:0}.fc-direction-ltr .fc-toolbar>*>:not(:first-child){margin-left:.75em}.fc-direction-rtl .fc-toolbar>*>:not(:first-child){margin-right:.75em}.fc-direction-rtl .fc-toolbar-ltr{flex-direction:row-reverse}.fc .fc-scroller{-webkit-overflow-scrolling:touch;position:relative}.fc .fc-scroller-liquid{height:100%}.fc .fc-scroller-liquid-absolute{bottom:0;left:0;position:absolute;right:0;top:0}.fc .fc-scroller-harness{direction:ltr;overflow:hidden;position:relative}.fc .fc-scroller-harness-liquid{height:100%}.fc-direction-rtl .fc-scroller-harness>.fc-scroller{direction:rtl}.fc-theme-standard .fc-scrollgrid{border:1px solid var(--fc-border-color)}.fc .fc-scrollgrid,.fc .fc-scrollgrid table{table-layout:fixed;width:100%}.fc .fc-scrollgrid table{border-left-style:hidden;border-right-style:hidden;border-top-style:hidden}.fc .fc-scrollgrid{border-bottom-width:0;border-collapse:separate;border-right-width:0}.fc .fc-scrollgrid-liquid{height:100%}.fc .fc-scrollgrid-section,.fc .fc-scrollgrid-section table,.fc .fc-scrollgrid-section>td{height:1px}.fc .fc-scrollgrid-section-liquid>td{height:100%}.fc .fc-scrollgrid-section>*{border-left-width:0;border-top-width:0}.fc .fc-scrollgrid-section-footer>*,.fc .fc-scrollgrid-section-header>*{border-bottom-width:0}.fc .fc-scrollgrid-section-body table,.fc .fc-scrollgrid-section-footer table{border-bottom-style:hidden}.fc .fc-scrollgrid-section-sticky>*{background:var(--fc-page-bg-color);position:sticky;z-index:3}.fc .fc-scrollgrid-section-header.fc-scrollgrid-section-sticky>*{top:0}.fc .fc-scrollgrid-section-footer.fc-scrollgrid-section-sticky>*{bottom:0}.fc .fc-scrollgrid-sticky-shim{height:1px;margin-bottom:-1px}.fc-sticky{position:sticky}.fc .fc-view-harness{flex-grow:1;position:relative}.fc .fc-view-harness-active>.fc-view{bottom:0;left:0;position:absolute;right:0;top:0}.fc .fc-col-header-cell-cushion{display:inline-block;padding:2px 4px}.fc .fc-bg-event,.fc .fc-highlight,.fc .fc-non-business{bottom:0;left:0;position:absolute;right:0;top:0}.fc .fc-non-business{background:var(--fc-non-business-color)}.fc .fc-bg-event{background:var(--fc-bg-event-color);opacity:var(--fc-bg-event-opacity)}.fc .fc-bg-event .fc-event-title{font-size:var(--fc-small-font-size);font-style:italic;margin:.5em}.fc .fc-highlight{background:var(--fc-highlight-color)}.fc .fc-cell-shaded,.fc .fc-day-disabled{background:var(--fc-neutral-bg-color)}a.fc-event,a.fc-event:hover{text-decoration:none}.fc-event.fc-event-draggable,.fc-event[href]{cursor:pointer}.fc-event .fc-event-main{position:relative;z-index:2}.fc-event-dragging:not(.fc-event-selected){opacity:.75}.fc-event-dragging.fc-event-selected{box-shadow:0 2px 7px rgba(0,0,0,.3)}.fc-event .fc-event-resizer{display:none;position:absolute;z-index:4}.fc-event-selected .fc-event-resizer,.fc-event:hover .fc-event-resizer{display:block}.fc-event-selected .fc-event-resizer{background:var(--fc-page-bg-color);border-color:inherit;border-radius:calc(var(--fc-event-resizer-dot-total-width)/2);border-style:solid;border-width:var(--fc-event-resizer-dot-border-width);height:var(--fc-event-resizer-dot-total-width);width:var(--fc-event-resizer-dot-total-width)}.fc-event-selected .fc-event-resizer:before{bottom:-20px;content:"";left:-20px;position:absolute;right:-20px;top:-20px}.fc-event-selected,.fc-event:focus{box-shadow:0 2px 5px rgba(0,0,0,.2)}.fc-event-selected:before,.fc-event:focus:before{bottom:0;content:"";left:0;position:absolute;right:0;top:0;z-index:3}.fc-event-selected:after,.fc-event:focus:after{background:var(--fc-event-selected-overlay-color);bottom:-1px;content:"";left:-1px;position:absolute;right:-1px;top:-1px;z-index:1}.fc-h-event{background-color:var(--fc-event-bg-color);border:1px solid var(--fc-event-border-color);display:block}.fc-h-event .fc-event-main{color:var(--fc-event-text-color)}.fc-h-event .fc-event-main-frame{display:flex}.fc-h-event .fc-event-time{max-width:100%;overflow:hidden}.fc-h-event .fc-event-title-container{flex-grow:1;flex-shrink:1;min-width:0}.fc-h-event .fc-event-title{display:inline-block;left:0;max-width:100%;overflow:hidden;right:0;vertical-align:top}.fc-h-event.fc-event-selected:before{bottom:-10px;top:-10px}.fc-direction-ltr .fc-daygrid-block-event:not(.fc-event-start),.fc-direction-rtl .fc-daygrid-block-event:not(.fc-event-end){border-bottom-left-radius:0;border-left-width:0;border-top-left-radius:0}.fc-direction-ltr .fc-daygrid-block-event:not(.fc-event-end),.fc-direction-rtl .fc-daygrid-block-event:not(.fc-event-start){border-bottom-right-radius:0;border-right-width:0;border-top-right-radius:0}.fc-h-event:not(.fc-event-selected) .fc-event-resizer{bottom:0;top:0;width:var(--fc-event-resizer-thickness)}.fc-direction-ltr .fc-h-event:not(.fc-event-selected) .fc-event-resizer-start,.fc-direction-rtl .fc-h-event:not(.fc-event-selected) .fc-event-resizer-end{cursor:w-resize;left:calc(var(--fc-event-resizer-thickness)*-.5)}.fc-direction-ltr .fc-h-event:not(.fc-event-selected) .fc-event-resizer-end,.fc-direction-rtl .fc-h-event:not(.fc-event-selected) .fc-event-resizer-start{cursor:e-resize;right:calc(var(--fc-event-resizer-thickness)*-.5)}.fc-h-event.fc-event-selected .fc-event-resizer{margin-top:calc(var(--fc-event-resizer-dot-total-width)*-.5);top:50%}.fc-direction-ltr .fc-h-event.fc-event-selected .fc-event-resizer-start,.fc-direction-rtl .fc-h-event.fc-event-selected .fc-event-resizer-end{left:calc(var(--fc-event-resizer-dot-total-width)*-.5)}.fc-direction-ltr .fc-h-event.fc-event-selected .fc-event-resizer-end,.fc-direction-rtl .fc-h-event.fc-event-selected .fc-event-resizer-start{right:calc(var(--fc-event-resizer-dot-total-width)*-.5)}.fc .fc-popover{box-shadow:0 2px 6px rgba(0,0,0,.15);position:absolute;z-index:9999}.fc .fc-popover-header{align-items:center;display:flex;flex-direction:row;justify-content:space-between;padding:3px 4px}.fc .fc-popover-title{margin:0 2px}.fc .fc-popover-close{cursor:pointer;font-size:1.1em;opacity:.65}.fc-theme-standard .fc-popover{background:var(--fc-page-bg-color);border:1px solid var(--fc-border-color)}.fc-theme-standard .fc-popover-header{background:var(--fc-neutral-bg-color)}');class Me{constructor(e){this.drainedOption=e,this.isRunning=!1,this.isDirty=!1,this.pauseDepths={},this.timeoutId=0}request(e){this.isDirty=!0,this.isPaused()||(this.clearTimeout(),null==e?this.tryDrain():this.timeoutId=setTimeout(this.tryDrain.bind(this),e))}pause(e=""){let{pauseDepths:t}=this;t[e]=(t[e]||0)+1,this.clearTimeout()}resume(e="",t){let{pauseDepths:n}=this;if(e in n){if(t)delete n[e];else{n[e]-=1,n[e]<=0&&delete n[e]}this.tryDrain()}}isPaused(){return Object.keys(this.pauseDepths).length}tryDrain(){if(!this.isRunning&&!this.isPaused()){for(this.isRunning=!0;this.isDirty;)this.isDirty=!1,this.drained();this.isRunning=!1}}clear(){this.clearTimeout(),this.isDirty=!1,this.pauseDepths={}}clearTimeout(){this.timeoutId&&(clearTimeout(this.timeoutId),this.timeoutId=0)}drained(){this.drainedOption&&this.drainedOption()}}function Ie(e){e.parentNode&&e.parentNode.removeChild(e)}function Oe(e,t){if(e.closest)return e.closest(t);if(!document.documentElement.contains(e))return null;do{if(Ne(e,t))return e;e=e.parentElement||e.parentNode}while(null!==e&&1===e.nodeType);return null}function Ne(e,t){return(e.matches||e.matchesSelector||e.msMatchesSelector).call(e,t)}function Pe(e,t){let n=e instanceof HTMLElement?[e]:e,r=[];for(let e=0;e{let r=Oe(n.target,e);r&&t.call(r,n,r)}}(n,r);return e.addEventListener(t,i),()=>{e.removeEventListener(t,i)}}const Ve=["webkitTransitionEnd","otransitionend","oTransitionEnd","msTransitionEnd","transitionend"];function Ge(e,t){let n=r=>{t(r),Ve.forEach(t=>{e.removeEventListener(t,n)})};Ve.forEach(t=>{e.addEventListener(t,n)})}function Qe(e){return Object.assign({onClick:e},qe(e))}function qe(e){return{tabIndex:0,onKeyDown(t){"Enter"!==t.key&&" "!==t.key||(e(t),t.preventDefault())}}}let Ye=0;function Ze(){return Ye+=1,String(Ye)}function Xe(){document.body.classList.add("fc-not-allowed")}function $e(){document.body.classList.remove("fc-not-allowed")}function Je(e){e.style.userSelect="none",e.style.webkitUserSelect="none",e.addEventListener("selectstart",We)}function Ke(e){e.style.userSelect="",e.style.webkitUserSelect="",e.removeEventListener("selectstart",We)}function et(e){e.addEventListener("contextmenu",We)}function tt(e){e.removeEventListener("contextmenu",We)}function nt(e){let t,n,r=[],i=[];for("string"==typeof e?i=e.split(/\s*,\s*/):"function"==typeof e?i=[e]:Array.isArray(e)&&(i=e),t=0;te.replace("$"+n,t||""),e):n}function lt(e,t){return e-t}function ct(e){return e%1==0}function dt(e){let t=e.querySelector(".fc-scrollgrid-shrink-frame"),n=e.querySelector(".fc-scrollgrid-shrink-cushion");if(!t)throw new Error("needs fc-scrollgrid-shrink-frame className");if(!n)throw new Error("needs fc-scrollgrid-shrink-cushion className");return e.getBoundingClientRect().width-t.getBoundingClientRect().width+n.getBoundingClientRect().width}const ut=["years","months","days","milliseconds"],ht=/^(-?)(?:(\d+)\.)?(\d+):(\d\d)(?::(\d\d)(?:\.(\d\d\d))?)?/;function ft(e,t){return"string"==typeof e?function(e){let t=ht.exec(e);if(t){let e=t[1]?-1:1;return{years:0,months:0,days:e*(t[2]?parseInt(t[2],10):0),milliseconds:e*(60*(t[3]?parseInt(t[3],10):0)*60*1e3+60*(t[4]?parseInt(t[4],10):0)*1e3+1e3*(t[5]?parseInt(t[5],10):0)+(t[6]?parseInt(t[6],10):0))}}return null}(e):"object"==typeof e&&e?gt(e):"number"==typeof e?gt({[t||"milliseconds"]:e}):null}function gt(e){let t={years:e.years||e.year||0,months:e.months||e.month||0,days:e.days||e.day||0,milliseconds:60*(e.hours||e.hour||0)*60*1e3+60*(e.minutes||e.minute||0)*1e3+1e3*(e.seconds||e.second||0)+(e.milliseconds||e.millisecond||e.ms||0)},n=e.weeks||e.week;return n&&(t.days+=7*n,t.specifiedWeeks=!0),t}function pt(e,t){return{years:e.years+t.years,months:e.months+t.months,days:e.days+t.days,milliseconds:e.milliseconds+t.milliseconds}}function mt(e,t){return{years:e.years*t,months:e.months*t,days:e.days*t,milliseconds:e.milliseconds*t}}function vt(e){return yt(e)/864e5}function yt(e){return 31536e6*e.years+2592e6*e.months+864e5*e.days+e.milliseconds}function bt(e,t){let n=null;for(let r=0;r10&&(null==t?r=r.replace("Z",""):0!==t&&(r=r.replace("Z",Vt(t,!0)))),r}function Lt(e){return e.toISOString().replace(/T.*$/,"")}function Wt(e){return e.toISOString().match(/^\d{4}-\d{2}/)[0]}function Ft(e){return ot(e.getUTCHours(),2)+":"+ot(e.getUTCMinutes(),2)+":"+ot(e.getUTCSeconds(),2)}function Vt(e,t=!1){let n=e<0?"-":"+",r=Math.abs(e),i=Math.floor(r/60),s=Math.round(r%60);return t?`${n+ot(i,2)}:${ot(s,2)}`:`GMT${n}${i}${s?":"+ot(s,2):""}`}function Gt(e,t,n){let r,i;return function(...s){if(r){if(!St(r,s)){n&&n(i);let r=e.apply(this,s);t&&t(r,i)||(i=r)}}else i=e.apply(this,s);return r=s,i}}function Qt(e,t,n){let r,i;return s=>{if(r){if(!Cn(r,s)){n&&n(i);let r=e.call(this,s);t&&t(r,i)||(i=r)}}else i=e.call(this,s);return r=s,i}}const qt={week:3,separator:9,omitZeroMinute:9,meridiem:9,omitCommas:9},Yt={timeZoneName:7,era:6,year:5,month:4,day:2,weekday:2,hour:1,minute:1,second:1},Zt=/\s*([ap])\.?m\.?/i,Xt=/,/g,$t=/\s+/g,Jt=/\u200e/g,Kt=/UTC|GMT/;class en{constructor(e){let t={},n={},r=9;for(let i in e)if(i in qt){n[i]=e[i];qt[i]<9&&(r=Math.min(qt[i],r))}else t[i]=e[i],i in Yt&&(r=Math.min(Yt[i],r));this.standardDateProps=t,this.extendedSettings=n,this.smallestUnitNum=r,this.buildFormattingFunc=Gt(tn)}format(e,t){return this.buildFormattingFunc(this.standardDateProps,this.extendedSettings,t)(e)}formatRange(e,t,n,r){let{standardDateProps:i,extendedSettings:s}=this,o=function(e,t,n){if(n.getMarkerYear(e)!==n.getMarkerYear(t))return 5;if(n.getMarkerMonth(e)!==n.getMarkerMonth(t))return 4;if(n.getMarkerDay(e)!==n.getMarkerDay(t))return 2;if(zt(e)!==zt(t))return 1;return 0}(e.marker,t.marker,n.calendarSystem);if(!o)return this.format(e,n);let a=o;!(a>1)||"numeric"!==i.year&&"2-digit"!==i.year||"numeric"!==i.month&&"2-digit"!==i.month||"numeric"!==i.day&&"2-digit"!==i.day||(a=1);let l=this.format(e,n),c=this.format(t,n);if(l===c)return l;let d=tn(function(e,t){let n={};for(let r in e)(!(r in Yt)||Yt[r]<=t)&&(n[r]=e[r]);return n}(i,a),s,n),u=d(e),h=d(t),f=function(e,t,n,r){let i=0;for(;iVt(e.timeZoneOffset):0===r&&t.week?e=>function(e,t,n,r,i){let s=[];"long"===i?s.push(n):"short"!==i&&"narrow"!==i||s.push(t);"long"!==i&&"short"!==i||s.push(" ");s.push(r.simpleNumberFormat.format(e)),"rtl"===r.options.direction&&s.reverse();return s.join("")}(n.computeWeekNumber(e.marker),n.weekText,n.weekTextLong,n.locale,t.week):function(e,t,n){e=Object.assign({},e),t=Object.assign({},t),function(e,t){e.timeZoneName&&(e.hour||(e.hour="2-digit"),e.minute||(e.minute="2-digit"));"long"===e.timeZoneName&&(e.timeZoneName="short");t.omitZeroMinute&&(e.second||e.millisecond)&&delete t.omitZeroMinute}(e,t),e.timeZone="UTC";let r,i=new Intl.DateTimeFormat(n.locale.codes,e);if(t.omitZeroMinute){let t=Object.assign({},e);delete t.minute,r=new Intl.DateTimeFormat(n.locale.codes,t)}return s=>{let o,{marker:a}=s;return o=r&&!a.getUTCMinutes()?r:i,function(e,t,n,r,i){e=e.replace(Jt,""),"short"===n.timeZoneName&&(e=function(e,t){let n=!1;e=e.replace(Kt,()=>(n=!0,t)),n||(e+=" "+t);return e}(e,"UTC"===i.timeZone||null==t.timeZoneOffset?"UTC":Vt(t.timeZoneOffset)));r.omitCommas&&(e=e.replace(Xt,"").trim());r.omitZeroMinute&&(e=e.replace(":00",""));!1===r.meridiem?e=e.replace(Zt,"").trim():"narrow"===r.meridiem?e=e.replace(Zt,(e,t)=>t.toLocaleLowerCase()):"short"===r.meridiem?e=e.replace(Zt,(e,t)=>t.toLocaleLowerCase()+"m"):"lowercase"===r.meridiem&&(e=e.replace(Zt,e=>e.toLocaleLowerCase()));return e=(e=e.replace($t," ")).trim()}(o.format(a),s,e,t,n)}}(e,t,n)}function nn(e,t){let n=t.markerToArray(e.marker);return{marker:e.marker,timeZoneOffset:e.timeZoneOffset,array:n,year:n[0],month:n[1],day:n[2],hour:n[3],minute:n[4],second:n[5],millisecond:n[6]}}function rn(e,t,n,r){let i=nn(e,n.calendarSystem);return{date:i,start:i,end:t?nn(t,n.calendarSystem):null,timeZone:n.timeZone,localeCodes:n.locale.codes,defaultSeparator:r||n.defaultSeparator}}class sn{constructor(e){this.cmdStr=e}format(e,t,n){return t.cmdFormatter(this.cmdStr,rn(e,null,t,n))}formatRange(e,t,n,r){return n.cmdFormatter(this.cmdStr,rn(e,t,n,r))}}class on{constructor(e){this.func=e}format(e,t,n){return this.func(rn(e,null,t,n))}formatRange(e,t,n,r){return this.func(rn(e,t,n,r))}}function an(e){return"object"==typeof e&&e?new en(e):"string"==typeof e?new sn(e):"function"==typeof e?new on(e):null}const ln={navLinkDayClick:yn,navLinkWeekClick:yn,duration:ft,bootstrapFontAwesome:yn,buttonIcons:yn,customButtons:yn,defaultAllDayEventDuration:ft,defaultTimedEventDuration:ft,nextDayThreshold:ft,scrollTime:ft,scrollTimeReset:Boolean,slotMinTime:ft,slotMaxTime:ft,dayPopoverFormat:an,slotDuration:ft,snapDuration:ft,headerToolbar:yn,footerToolbar:yn,defaultRangeSeparator:String,titleRangeSeparator:String,forceEventDuration:Boolean,dayHeaders:Boolean,dayHeaderFormat:an,dayHeaderClassNames:yn,dayHeaderContent:yn,dayHeaderDidMount:yn,dayHeaderWillUnmount:yn,dayCellClassNames:yn,dayCellContent:yn,dayCellDidMount:yn,dayCellWillUnmount:yn,initialView:String,aspectRatio:Number,weekends:Boolean,weekNumberCalculation:yn,weekNumbers:Boolean,weekNumberClassNames:yn,weekNumberContent:yn,weekNumberDidMount:yn,weekNumberWillUnmount:yn,editable:Boolean,viewClassNames:yn,viewDidMount:yn,viewWillUnmount:yn,nowIndicator:Boolean,nowIndicatorSnap:yn,nowIndicatorClassNames:yn,nowIndicatorContent:yn,nowIndicatorDidMount:yn,nowIndicatorWillUnmount:yn,showNonCurrentDates:Boolean,lazyFetching:Boolean,startParam:String,endParam:String,timeZoneParam:String,timeZone:String,locales:yn,locale:yn,themeSystem:String,dragRevertDuration:Number,dragScroll:Boolean,allDayMaintainDuration:Boolean,unselectAuto:Boolean,dropAccept:yn,eventOrder:nt,eventOrderStrict:Boolean,handleWindowResize:Boolean,windowResizeDelay:Number,longPressDelay:Number,eventDragMinDistance:Number,expandRows:Boolean,height:yn,contentHeight:yn,direction:String,weekNumberFormat:an,eventResizableFromStart:Boolean,displayEventTime:Boolean,displayEventEnd:Boolean,weekText:String,weekTextLong:String,progressiveEventRendering:Boolean,businessHours:yn,initialDate:yn,now:yn,eventDataTransform:yn,stickyHeaderDates:yn,stickyFooterScrollbar:yn,viewHeight:yn,defaultAllDay:Boolean,eventSourceFailure:yn,eventSourceSuccess:yn,eventDisplay:String,eventStartEditable:Boolean,eventDurationEditable:Boolean,eventOverlap:yn,eventConstraint:yn,eventAllow:yn,eventBackgroundColor:String,eventBorderColor:String,eventTextColor:String,eventColor:String,eventClassNames:yn,eventContent:yn,eventDidMount:yn,eventWillUnmount:yn,selectConstraint:yn,selectOverlap:yn,selectAllow:yn,droppable:Boolean,unselectCancel:String,slotLabelFormat:yn,slotLaneClassNames:yn,slotLaneContent:yn,slotLaneDidMount:yn,slotLaneWillUnmount:yn,slotLabelClassNames:yn,slotLabelContent:yn,slotLabelDidMount:yn,slotLabelWillUnmount:yn,dayMaxEvents:yn,dayMaxEventRows:yn,dayMinWidth:Number,slotLabelInterval:ft,allDayText:String,allDayClassNames:yn,allDayContent:yn,allDayDidMount:yn,allDayWillUnmount:yn,slotMinWidth:Number,navLinks:Boolean,eventTimeFormat:an,rerenderDelay:Number,moreLinkText:yn,moreLinkHint:yn,selectMinDistance:Number,selectable:Boolean,selectLongPressDelay:Number,eventLongPressDelay:Number,selectMirror:Boolean,eventMaxStack:Number,eventMinHeight:Number,eventMinWidth:Number,eventShortHeight:Number,slotEventOverlap:Boolean,plugins:yn,firstDay:Number,dayCount:Number,dateAlignment:String,dateIncrement:ft,hiddenDays:yn,fixedWeekCount:Boolean,validRange:yn,visibleRange:yn,titleFormat:yn,eventInteractive:Boolean,noEventsText:String,viewHint:yn,navLinkHint:yn,closeHint:String,timeHint:String,eventHint:String,moreLinkClick:yn,moreLinkClassNames:yn,moreLinkContent:yn,moreLinkDidMount:yn,moreLinkWillUnmount:yn,monthStartFormat:an,handleCustomRendering:yn,customRenderingMetaMap:yn,customRenderingReplaces:Boolean},cn={eventDisplay:"auto",defaultRangeSeparator:" - ",titleRangeSeparator:" – ",defaultTimedEventDuration:"01:00:00",defaultAllDayEventDuration:{day:1},forceEventDuration:!1,nextDayThreshold:"00:00:00",dayHeaders:!0,initialView:"",aspectRatio:1.35,headerToolbar:{start:"title",center:"",end:"today prev,next"},weekends:!0,weekNumbers:!1,weekNumberCalculation:"local",editable:!1,nowIndicator:!1,scrollTime:"06:00:00",scrollTimeReset:!0,slotMinTime:"00:00:00",slotMaxTime:"24:00:00",showNonCurrentDates:!0,lazyFetching:!0,startParam:"start",endParam:"end",timeZoneParam:"timeZone",timeZone:"local",locales:[],locale:"",themeSystem:"standard",dragRevertDuration:500,dragScroll:!0,allDayMaintainDuration:!1,unselectAuto:!0,dropAccept:"*",eventOrder:"start,-duration,allDay,title",dayPopoverFormat:{month:"long",day:"numeric",year:"numeric"},handleWindowResize:!0,windowResizeDelay:100,longPressDelay:1e3,eventDragMinDistance:5,expandRows:!1,navLinks:!1,selectable:!1,eventMinHeight:15,eventMinWidth:30,eventShortHeight:30,monthStartFormat:{month:"long",day:"numeric"},nowIndicatorSnap:"auto"},dn={datesSet:yn,eventsSet:yn,eventAdd:yn,eventChange:yn,eventRemove:yn,windowResize:yn,eventClick:yn,eventMouseEnter:yn,eventMouseLeave:yn,select:yn,unselect:yn,loading:yn,_unmount:yn,_beforeprint:yn,_afterprint:yn,_noEventDrop:yn,_noEventResize:yn,_resize:yn,_scrollRequest:yn},un={buttonText:yn,buttonHints:yn,views:yn,plugins:yn,initialEvents:yn,events:yn,eventSources:yn},hn={headerToolbar:fn,footerToolbar:fn,buttonText:fn,buttonHints:fn,buttonIcons:fn,dateIncrement:fn,plugins:gn,events:gn,eventSources:gn,resources:gn};function fn(e,t){return"object"==typeof e&&"object"==typeof t&&e&&t?Cn(e,t):e===t}function gn(e,t){return Array.isArray(e)&&Array.isArray(t)?St(e,t):e===t}const pn={type:String,component:yn,buttonText:String,buttonTextKey:String,dateProfileGeneratorClass:yn,usesMinMaxTime:Boolean,classNames:yn,content:yn,didMount:yn,willUnmount:yn};function mn(e){return En(e,hn)}function vn(e,t){let n={},r={};for(let r in t)r in e&&(n[r]=t[r](e[r]));for(let n in e)n in t||(r[n]=e[n]);return{refined:n,extra:r}}function yn(e){return e}const{hasOwnProperty:bn}=Object.prototype;function En(e,t){let n={};if(t)for(let r in t)if(t[r]===fn){let t=[];for(let i=e.length-1;i>=0;i-=1){let s=e[i][r];if("object"==typeof s&&s)t.unshift(s);else if(void 0!==s){n[r]=s;break}}t.length&&(n[r]=En(t))}for(let t=e.length-1;t>=0;t-=1){let r=e[t];for(let e in r)e in n||(n[e]=r[e])}return n}function Sn(e,t){let n={};for(let r in e)t(e[r],r)&&(n[r]=e[r]);return n}function An(e,t){let n={};for(let r in e)n[r]=t(e[r],r);return n}function Dn(e){let t={};for(let n of e)t[n]=!0;return t}function wn(e){let t=[];for(let n in e)t.push(e[n]);return t}function Cn(e,t){if(e===t)return!0;for(let n in e)if(bn.call(e,n)&&!(n in t))return!1;for(let n in t)if(bn.call(t,n)&&e[n]!==t[n])return!1;return!0}const Rn=/^on[A-Z]/;function xn(e,t,n={}){if(e===t)return!0;for(let r in t)if(!(r in e)||!_n(e[r],t[r],n[r]))return!1;for(let n in e)if(!(n in t))return!1;return!0}function _n(e,t,n){return e===t||!0===n||!!n&&n(e,t)}function Tn(e,t=0,n,r=1){let i=[];null==n&&(n=Object.keys(e).length);for(let s=t;s=1?Math.min(i,s):i}(e,this.weekDow,this.weekDoy)}format(e,t,n={}){return t.format({marker:e,timeZoneOffset:null!=n.forcedTzo?n.forcedTzo:this.offsetForMarker(e)},this)}formatRange(e,t,n,r={}){return r.isEndExclusive&&(t=Ct(t,-1)),n.formatRange({marker:e,timeZoneOffset:null!=r.forcedStartTzo?r.forcedStartTzo:this.offsetForMarker(e)},{marker:t,timeZoneOffset:null!=r.forcedEndTzo?r.forcedEndTzo:this.offsetForMarker(t)},this,r.defaultSeparator)}formatIso(e,t={}){let n=null;return t.omitTimeZoneOffset||(n=null!=t.forcedTzo?t.forcedTzo:this.offsetForMarker(e)),Ut(e,n,t.omitTime)}timestampToMarker(e){return"local"===this.timeZone?Bt(Nt(new Date(e))):"UTC"!==this.timeZone&&this.namedTimeZoneImpl?Bt(this.namedTimeZoneImpl.timestampToArray(e)):new Date(e)}offsetForMarker(e){return"local"===this.timeZone?-Pt(Ht(e)).getTimezoneOffset():"UTC"===this.timeZone?0:this.namedTimeZoneImpl?this.namedTimeZoneImpl.offsetForArray(Ht(e)):null}toDate(e,t){return"local"===this.timeZone?Pt(Ht(e)):"UTC"===this.timeZone?new Date(e.valueOf()):this.namedTimeZoneImpl?new Date(e.valueOf()-1e3*this.namedTimeZoneImpl.offsetForArray(Ht(e))*60):new Date(e.valueOf()-(t||0))}}class Hn{constructor(e){this.iconOverrideOption&&this.setIconOverride(e[this.iconOverrideOption])}setIconOverride(e){let t,n;if("object"==typeof e&&e){for(n in t=Object.assign({},this.iconClasses),e)t[n]=this.applyIconOverridePrefix(e[n]);this.iconClasses=t}else!1===e&&(this.iconClasses={})}applyIconOverridePrefix(e){let t=this.iconOverridePrefix;return t&&0!==e.indexOf(t)&&(e=t+e),e}getClass(e){return this.classes[e]||""}getIconClass(e,t){let n;return n=t&&this.rtlIconClasses&&this.rtlIconClasses[e]||this.iconClasses[e],n?`${this.baseIconClass} ${n}`:""}getCustomButtonIconClass(e){let t;return this.iconOverrideCustomButtonOption&&(t=e[this.iconOverrideCustomButtonOption],t)?`${this.baseIconClass} ${this.applyIconOverridePrefix(t)}`:""}}function Bn(e){e();let t=n.debounceRendering,r=[];for(n.debounceRendering=function(e){r.push(e)},U(p(jn,{}),document.createElement("div"));r.length;)r.shift()();n.debounceRendering=t}Hn.prototype.classes={},Hn.prototype.iconClasses={},Hn.prototype.baseIconClass="",Hn.prototype.iconOverridePrefix="";class jn extends D{render(){return p("div",{})}componentDidMount(){this.setState({})}}function zn(e){let t=function(e,t){var n={__c:t="__cC"+c++,__:e,Consumer:function(e,t){return e.children(t)},Provider:function(e){var n,r;return this.getChildContext||(n=[],(r={})[t]=this,this.getChildContext=function(){return r},this.shouldComponentUpdate=function(e){this.props.value!==e.value&&n.some((function(e){e.__e=!0,x(e)}))},this.sub=function(e){n.push(e);var t=e.componentWillUnmount;e.componentWillUnmount=function(){n.splice(n.indexOf(e),1),t&&t.call(e)}}),e.children}};return n.Provider.__=n.Consumer.contextType=n}(e),n=t.Provider;return t.Provider=function(){let e=!this.getChildContext,t=n.apply(this,arguments);if(e){let e=[];this.shouldComponentUpdate=t=>{this.props.value!==t.value&&e.forEach(e=>{e.context=t.value,e.forceUpdate()})},this.sub=t=>{e.push(t);let n=t.componentWillUnmount;t.componentWillUnmount=()=>{e.splice(e.indexOf(t),1),n&&n.call(t)}}}return t},t}class Un{constructor(e,t,n,r){this.execFunc=e,this.emitter=t,this.scrollTime=n,this.scrollTimeReset=r,this.handleScrollRequest=e=>{this.queuedRequest=Object.assign({},this.queuedRequest||{},e),this.drain()},t.on("_scrollRequest",this.handleScrollRequest),this.fireInitialScroll()}detach(){this.emitter.off("_scrollRequest",this.handleScrollRequest)}update(e){e&&this.scrollTimeReset?this.fireInitialScroll():this.drain()}fireInitialScroll(){this.handleScrollRequest({time:this.scrollTime})}drain(){this.queuedRequest&&this.execFunc(this.queuedRequest)&&(this.queuedRequest=null)}}const Ln=zn({});function Wn(e,t,n,r,i,s,o,a,l,c,d,u,h,f){return{dateEnv:i,nowManager:s,options:n,pluginHooks:a,emitter:d,dispatch:l,getCurrentData:c,calendarApi:u,viewSpec:e,viewApi:t,dateProfileGenerator:r,theme:o,isRtl:"rtl"===n.direction,addResizeHandler(e){d.on("_resize",e)},removeResizeHandler(e){d.off("_resize",e)},createScrollResponder:e=>new Un(e,d,ft(n.scrollTime),n.scrollTimeReset),registerInteractiveComponent:h,unregisterInteractiveComponent:f}}class Fn extends D{shouldComponentUpdate(e,t){return!xn(this.props,e,this.propEquality)||!xn(this.state,t,this.stateEquality)}safeSetState(e){xn(this.state,Object.assign(Object.assign({},this.state),e),this.stateEquality)||this.setState(e)}}Fn.addPropsEquality=function(e){let t=Object.create(this.prototype.propEquality);Object.assign(t,e),this.prototype.propEquality=t},Fn.addStateEquality=function(e){let t=Object.create(this.prototype.stateEquality);Object.assign(t,e),this.prototype.stateEquality=t},Fn.contextType=Ln,Fn.prototype.propEquality={},Fn.prototype.stateEquality={};class Vn extends Fn{}function Gn(e,t){"function"==typeof e?e(t):e&&(e.current=t)}Vn.contextType=Ln;class Qn extends Vn{constructor(){super(...arguments),this.id=Ze(),this.queuedDomNodes=[],this.currentDomNodes=[],this.handleEl=e=>{const{options:t}=this.context,{generatorName:n}=this.props;t.customRenderingReplaces&&qn(n,t)||this.updateElRef(e)},this.updateElRef=e=>{this.props.elRef&&Gn(this.props.elRef,e)}}render(){const{props:e,context:t}=this,{options:n}=t,{customGenerator:r,defaultGenerator:s,renderProps:o}=e,a=Yn(e,[],this.handleEl);let l,c,d=!1,u=[];if(null!=r){const e="function"==typeof r?r(o,p):r;if(!0===e)d=!0;else{const t=e&&"object"==typeof e;t&&"html"in e?a.dangerouslySetInnerHTML={__html:e.html}:t&&"domNodes"in e?u=Array.prototype.slice.call(e.domNodes):(t?i(e):"function"!=typeof e)?l=e:c=e}}else d=!qn(e.generatorName,n);return d&&s&&(l=s(o)),this.queuedDomNodes=u,this.currentGeneratorMeta=c,p(e.elTag,a,l)}componentDidMount(){this.applyQueueudDomNodes(),this.triggerCustomRendering(!0)}componentDidUpdate(){this.applyQueueudDomNodes(),this.triggerCustomRendering(!0)}componentWillUnmount(){this.triggerCustomRendering(!1)}triggerCustomRendering(e){var t;const{props:n,context:r}=this,{handleCustomRendering:i,customRenderingMetaMap:s}=r.options;if(i){const r=null!==(t=this.currentGeneratorMeta)&&void 0!==t?t:null==s?void 0:s[n.generatorName];r&&i(Object.assign(Object.assign({id:this.id,isActive:e,containerEl:this.base,reportNewContainerEl:this.updateElRef,generatorMeta:r},n),{elClasses:(n.elClasses||[]).filter(Zn)}))}}applyQueueudDomNodes(){const{queuedDomNodes:e,currentDomNodes:t}=this,n=this.base;if(!St(e,t)){t.forEach(Ie);for(let t of e)n.appendChild(t);this.currentDomNodes=e}}}function qn(e,t){var n;return Boolean(t.handleCustomRendering&&e&&(null===(n=t.customRenderingMetaMap)||void 0===n?void 0:n[e]))}function Yn(e,t,n){const r=Object.assign(Object.assign({},e.elAttrs),{ref:n});return(e.elClasses||t)&&(r.className=(e.elClasses||[]).concat(t||[]).concat(r.className||[]).filter(Boolean).join(" ")),e.elStyle&&(r.style=e.elStyle),r}function Zn(e){return Boolean(e)}Qn.addPropsEquality({elClasses:St,elStyle:Cn,elAttrs:function(e,t){const n=function(e,t){let n=[];for(let r in e)bn.call(e,r)&&(r in t||n.push(r));for(let r in t)bn.call(t,r)&&e[r]!==t[r]&&n.push(r);return n}(e,t);for(let e of n)if(!Rn.test(e))return!1;return!0},renderProps:Cn});const Xn=zn(0);class $n extends D{constructor(){super(...arguments),this.InnerContent=Jn.bind(void 0,this),this.handleEl=e=>{this.el=e,this.props.elRef&&(Gn(this.props.elRef,e),e&&this.didMountMisfire&&this.componentDidMount())}}render(){const{props:e}=this,t=function(e,t){const n="function"==typeof e?e(t):e||[];return"string"==typeof n?[n]:n}(e.classNameGenerator,e.renderProps);if(e.children){const n=Yn(e,t,this.handleEl),r=e.children(this.InnerContent,e.renderProps,n);return e.elTag?p(e.elTag,n,r):r}return p(Qn,Object.assign(Object.assign({},e),{elRef:this.handleEl,elTag:e.elTag||"div",elClasses:(e.elClasses||[]).concat(t),renderId:this.context}))}componentDidMount(){var e,t;this.el?null===(t=(e=this.props).didMount)||void 0===t||t.call(e,Object.assign(Object.assign({},this.props.renderProps),{el:this.el})):this.didMountMisfire=!0}componentWillUnmount(){var e,t;null===(t=(e=this.props).willUnmount)||void 0===t||t.call(e,Object.assign(Object.assign({},this.props.renderProps),{el:this.el}))}}function Jn(e,t){const n=e.props;return p(Qn,Object.assign({renderProps:n.renderProps,generatorName:n.generatorName,customGenerator:n.customGenerator,defaultGenerator:n.defaultGenerator,renderId:e.context},t))}$n.contextType=Xn;class Kn extends Vn{render(){let{props:e,context:t}=this,{options:n}=t,r={view:t.viewApi};return p($n,{elRef:e.elRef,elTag:e.elTag||"div",elAttrs:e.elAttrs,elClasses:[...er(e.viewSpec),...e.elClasses||[]],elStyle:e.elStyle,renderProps:r,classNameGenerator:n.viewClassNames,generatorName:void 0,didMount:n.viewDidMount,willUnmount:n.viewWillUnmount},()=>e.children)}}function er(e){return[`fc-${e.type}-view`,"fc-view"]}function tr(e,t){let n,r,i=[],{start:s}=t;for(e.sort(nr),n=0;ns&&i.push({start:s,end:r.start}),r.end>s&&(s=r.end);return st.start)&&(null===e.start||null===t.end||e.start=e.start)&&(null===e.end||null!==t.end&&t.end<=e.end)}function ar(e,t){return(null===e.start||t>=e.start)&&(null===e.end||t=yt(t)&&(r=wt(r,1))}return e.start&&(n=Mt(e.start),r&&r<=n&&(r=wt(n,1))),{start:n,end:r}}function dr(e){let t=cr(e);return xt(t.start,t.end)>1}function ur(e,t,n,r){return"year"===r?ft(n.diffWholeYears(e,t),"year"):"month"===r?ft(n.diffWholeMonths(e,t),"month"):_t(e,t)}class hr{constructor(e){this.props=e,this.initHiddenDays()}buildPrev(e,t,n){let{dateEnv:r}=this.props,i=r.subtract(r.startOf(t,e.currentRangeUnit),e.dateIncrement);return this.build(i,-1,n)}buildNext(e,t,n){let{dateEnv:r}=this.props,i=r.add(r.startOf(t,e.currentRangeUnit),e.dateIncrement);return this.build(i,1,n)}build(e,t,n=!0){let r,i,s,o,a,l,{props:c}=this;var d,u;return r=this.buildValidRange(),r=this.trimHiddenDays(r),n&&(d=e,e=null!=(u=r).start&&d=u.end?new Date(u.end.valueOf()-1):d),i=this.buildCurrentRangeInfo(e,t),s=/^(year|month|week|day)$/.test(i.unit),o=this.buildRenderRange(this.trimHiddenDays(i.range),i.unit,s),o=this.trimHiddenDays(o),a=o,c.showNonCurrentDates||(a=rr(a,i.range)),a=this.adjustActiveRange(a),a=rr(a,r),l=sr(i.range,r),ar(o,e)||(e=o.start),{currentDate:e,validRange:r,currentRange:i.range,currentRangeUnit:i.unit,isRangeAllDay:s,activeRange:a,renderRange:o,slotMinTime:c.slotMinTime,slotMaxTime:c.slotMaxTime,isValid:l,dateIncrement:this.buildDateIncrement(i.duration)}}buildValidRange(){let e=this.props.validRangeInput,t="function"==typeof e?e.call(this.props.calendarApi,this.props.dateEnv.toDate(this.props.nowManager.getDateMarker())):e;return this.refineRange(t)||{start:null,end:null}}buildCurrentRangeInfo(e,t){let n,{props:r}=this,i=null,s=null,o=null;return r.duration?(i=r.duration,s=r.durationUnit,o=this.buildRangeFromDuration(e,t,i,s)):(n=this.props.dayCount)?(s="day",o=this.buildRangeFromDayCount(e,t,n)):(o=this.buildCustomVisibleRange(e))?s=r.dateEnv.greatestWholeUnit(o.start,o.end).unit:(i=this.getFallbackDuration(),s=Et(i).unit,o=this.buildRangeFromDuration(e,t,i,s)),{duration:i,unit:s,range:o}}getFallbackDuration(){return ft({day:1})}adjustActiveRange(e){let{dateEnv:t,usesMinMaxTime:n,slotMinTime:r,slotMaxTime:i}=this.props,{start:s,end:o}=e;return n&&(vt(r)<0&&(s=Mt(s),s=t.add(s,r)),vt(i)>1&&(o=Mt(o),o=wt(o,-1),o=t.add(o,i))),{start:s,end:o}}buildRangeFromDuration(e,t,n,r){let i,s,o,{dateEnv:a,dateAlignment:l}=this.props;if(!l){let{dateIncrement:e}=this.props;l=e&&yt(e)!o[e.defId].recurringDef);for(let e in o){let n=o[e];if(n.recurringDef){let{duration:o}=n.recurringDef;o||(o=n.allDay?s.defaultAllDayEventDuration:s.defaultTimedEventDuration);let l=pr(n,o,t,r,i.recurringTypes);for(let t of l){let n=fr(e,{start:t,end:r.add(t,o)});a[n.instanceId]=n}}}return{defs:o,instances:a}}function pr(e,t,n,r,i){let s=i[e.recurringDef.typeId].expand(e.recurringDef.typeData,{start:r.subtract(n.start,t),end:n.end},r);return e.allDay&&(s=s.map(Mt)),s}const mr={id:String,groupId:String,title:String,url:String,interactive:Boolean},vr={start:yn,end:yn,date:yn,allDay:Boolean},yr=Object.assign(Object.assign(Object.assign({},mr),vr),{extendedProps:yn});function br(e,t,n,r,i=Sr(n),s,o){let{refined:a,extra:l}=Er(e,n,i),c=function(e,t){let n=null;e&&(n=e.defaultAllDay);null==n&&(n=t.options.defaultAllDay);return n}(t,n),d=function(e,t,n,r){for(let i=0;i{return n=t,r=e,Boolean(n.groupId&&n.groupId===r.groupId);var n,r});return r.defs[t.defId]=t,r.instances[n.instanceId]=n,r}return{defs:{},instances:{}}}function Rr(){return{defs:{},instances:{}}}function xr(e,t){return{defs:Object.assign(Object.assign({},e.defs),t.defs),instances:Object.assign(Object.assign({},e.instances),t.instances)}}function _r(e,t){let n=Sn(e.defs,t),r=Sn(e.instances,e=>n[e.defId]);return{defs:n,instances:r}}function Tr(e){return Array.isArray(e)?e:"string"==typeof e?e.split(/\s+/):[]}const kr={display:String,editable:Boolean,startEditable:Boolean,durationEditable:Boolean,constraint:yn,overlap:yn,allow:yn,className:Tr,classNames:Tr,color:String,backgroundColor:String,borderColor:String,textColor:String},Mr={display:null,startEditable:null,durationEditable:null,constraints:[],overlap:null,allows:[],backgroundColor:"",borderColor:"",textColor:"",classNames:[]};function Ir(e,t){let n=function(e,t){return Array.isArray(e)?Dr(e,null,t,!0):"object"==typeof e&&e?Dr([e],null,t,!0):null!=e?String(e):null}(e.constraint,t);return{display:e.display||null,startEditable:null!=e.startEditable?e.startEditable:e.editable,durationEditable:null!=e.durationEditable?e.durationEditable:e.editable,constraints:null!=n?[n]:[],overlap:null!=e.overlap?e.overlap:null,allows:null!=e.allow?[e.allow]:[],backgroundColor:e.backgroundColor||e.color||"",borderColor:e.borderColor||e.color||"",textColor:e.textColor||"",classNames:(e.className||[]).concat(e.classNames||[])}}function Or(e){return e.reduce(Nr,Mr)}function Nr(e,t){return{display:null!=t.display?t.display:e.display,startEditable:null!=t.startEditable?t.startEditable:e.startEditable,durationEditable:null!=t.durationEditable?t.durationEditable:e.durationEditable,constraints:e.constraints.concat(t.constraints),overlap:"boolean"==typeof t.overlap?t.overlap:e.overlap,allows:e.allows.concat(t.allows),backgroundColor:t.backgroundColor||e.backgroundColor,borderColor:t.borderColor||e.borderColor,textColor:t.textColor||e.textColor,classNames:e.classNames.concat(t.classNames)}}const Pr={id:String,defaultAllDay:Boolean,url:String,format:String,events:yn,eventDataTransform:yn,success:yn,failure:yn};function Hr(e,t,n=Br(t)){let r;if("string"==typeof e?r={url:e}:"function"==typeof e||Array.isArray(e)?r={events:e}:"object"==typeof e&&e&&(r=e),r){let{refined:i,extra:s}=vn(r,n),o=function(e,t){let n=t.pluginHooks.eventSourceDefs;for(let t=n.length-1;t>=0;t-=1){let r=n[t].parseMeta(e);if(r)return{sourceDefId:t,meta:r}}return null}(i,t);if(o)return{_raw:e,isFetching:!1,latestFetchId:"",fetchRange:null,defaultAllDay:i.defaultAllDay,eventDataTransform:i.eventDataTransform,success:i.success,failure:i.failure,publicId:i.id||"",sourceId:Ze(),sourceDefId:o.sourceDefId,meta:o.meta,ui:Ir(i,t),extendedProps:s}}return null}function Br(e){return Object.assign(Object.assign(Object.assign({},kr),Pr),e.pluginHooks.eventSourceRefiners)}function jr(e,t,n,r,i){switch(t.type){case"RECEIVE_EVENTS":return function(e,t,n,r,i,s){if(t&&n===t.latestFetchId){let n=Dr(zr(i,t,s),t,s);return r&&(n=gr(n,r,s)),xr(Wr(e,t.sourceId),n)}return e}(e,n[t.sourceId],t.fetchId,t.fetchRange,t.rawEvents,i);case"RESET_RAW_EVENTS":return function(e,t,n,r,i){const{defIdMap:s,instanceIdMap:o}=function(e){const{defs:t,instances:n}=e,r={},i={};for(let e in t){const n=t[e],{publicId:i}=n;i&&(r[i]=e)}for(let e in n){const r=n[e],s=t[r.defId],{publicId:o}=s;o&&(i[o]=e)}return{defIdMap:r,instanceIdMap:i}}(e);return gr(Dr(zr(n,t,i),t,i,!1,s,o),r,i)}(e,n[t.sourceId],t.rawEvents,r.activeRange,i);case"ADD_EVENTS":return function(e,t,n,r){n&&(t=gr(t,n,r));return xr(e,t)}(e,t.eventStore,r?r.activeRange:null,i);case"RESET_EVENTS":return t.eventStore;case"MERGE_EVENTS":return xr(e,t.eventStore);case"PREV":case"NEXT":case"CHANGE_DATE":case"CHANGE_VIEW_TYPE":return r?gr(e,r.activeRange,i):e;case"REMOVE_EVENTS":return function(e,t){let{defs:n,instances:r}=e,i={},s={};for(let e in n)t.defs[e]||(i[e]=n[e]);for(let e in r)!t.instances[e]&&i[r[e].defId]&&(s[e]=r[e]);return{defs:i,instances:s}}(e,t.eventStore);case"REMOVE_EVENT_SOURCE":return Wr(e,t.sourceId);case"REMOVE_ALL_EVENT_SOURCES":return _r(e,e=>!e.sourceId);case"REMOVE_ALL_EVENTS":return{defs:{},instances:{}};default:return e}}function zr(e,t,n){let r=n.options.eventDataTransform,i=t?t.eventDataTransform:null;return i&&(e=Ur(e,i)),r&&(e=Ur(e,r)),e}function Ur(e,t){let n;if(t){n=[];for(let r of e){let e=t(r);e?n.push(e):null==e&&n.push(r)}}else n=e;return n}function Lr(e,t,n){let{defs:r}=e,i=An(e.instances,e=>r[e.defId].allDay?e:Object.assign(Object.assign({},e),{range:{start:n.createMarker(t.toDate(e.range.start,e.forcedStartTzo)),end:n.createMarker(t.toDate(e.range.end,e.forcedEndTzo))},forcedStartTzo:n.canComputeOffset?null:e.forcedStartTzo,forcedEndTzo:n.canComputeOffset?null:e.forcedEndTzo}));return{defs:r,instances:i}}function Wr(e,t){return _r(e,e=>e.sourceId!==t)}class Fr{constructor(){this.handlers={},this.thisContext=null}setThisContext(e){this.thisContext=e}setOptions(e){this.options=e}on(e,t){!function(e,t,n){(e[t]||(e[t]=[])).push(n)}(this.handlers,e,t)}off(e,t){!function(e,t,n){n?e[t]&&(e[t]=e[t].filter(e=>e!==n)):delete e[t]}(this.handlers,e,t)}trigger(e,...t){let n=this.handlers[e]||[],r=this.options&&this.options[e],i=[].concat(r||[],n);for(let e of i)e.apply(this.thisContext,t)}hasHandlers(e){return Boolean(this.handlers[e]&&this.handlers[e].length||this.options&&this.options[e])}}const Vr={startTime:"09:00",endTime:"17:00",daysOfWeek:[1,2,3,4,5],display:"inverse-background",classNames:"fc-non-business",groupId:"_businessHours"};function Gr(e,t){return Dr(function(e){let t;t=!0===e?[{}]:Array.isArray(e)?e.filter(e=>e.daysOfWeek):"object"==typeof e&&e?[e]:[];return t=t.map(e=>Object.assign(Object.assign({},Vr),e)),t}(e),null,t)}function Qr(e,t,n){n.emitter.trigger("select",Object.assign(Object.assign({},qr(e,n)),{jsEvent:t?t.origEvent:null,view:n.viewApi||n.calendarApi.view}))}function qr(e,t){let n={};for(let r of t.pluginHooks.dateSpanTransforms)Object.assign(n,r(e,t));var r,i;return Object.assign(n,(r=e,i=t.dateEnv,Object.assign(Object.assign({},Ai(r.range,i,r.allDay)),{allDay:r.allDay}))),n}function Yr(e,t,n){let{dateEnv:r,options:i}=n,s=t;return e?(s=Mt(s),s=r.add(s,i.defaultAllDayEventDuration)):s=r.add(s,i.defaultTimedEventDuration),s}function Zr(e,t,n,r){let i=oi(e.defs,t),s={defs:{},instances:{}};for(let t in e.defs){let o=e.defs[t];s.defs[t]=Xr(o,i[t],n,r)}for(let t in e.instances){let o=e.instances[t],a=s.defs[o.defId];s.instances[t]=$r(o,a,i[o.defId],n,r)}return s}function Xr(e,t,n,r){let i=n.standardProps||{};null==i.hasEnd&&t.durationEditable&&(n.startDelta||n.endDelta)&&(i.hasEnd=!0);let s=Object.assign(Object.assign(Object.assign({},e),i),{ui:Object.assign(Object.assign({},e.ui),i.ui)});n.extendedProps&&(s.extendedProps=Object.assign(Object.assign({},s.extendedProps),n.extendedProps));for(let e of r.pluginHooks.eventDefMutationAppliers)e(s,n,r);return!s.hasEnd&&r.options.forceEventDuration&&(s.hasEnd=!0),s}function $r(e,t,n,r,i){let{dateEnv:s}=i,o=r.standardProps&&!0===r.standardProps.allDay,a=r.standardProps&&!1===r.standardProps.hasEnd,l=Object.assign({},e);return o&&(l.range=lr(l.range)),r.datesDelta&&n.startEditable&&(l.range={start:s.add(l.range.start,r.datesDelta),end:s.add(l.range.end,r.datesDelta)}),r.startDelta&&n.durationEditable&&(l.range={start:s.add(l.range.start,r.startDelta),end:l.range.end}),r.endDelta&&n.durationEditable&&(l.range={start:l.range.start,end:s.add(l.range.end,r.endDelta)}),a&&(l.range={start:l.range.start,end:Yr(t.allDay,l.range.start,i)}),t.allDay&&(l.range={start:Mt(l.range.start),end:Mt(l.range.end)}),l.range.endai(e,t))}function ai(e,t){let n=[];return t[""]&&n.push(t[""]),t[e.defId]&&n.push(t[e.defId]),n.push(e.ui),Or(n)}function li(e,t){let n=e.map(ci);return n.sort((e,n)=>rt(e,n,t)),n.map(e=>e._seg)}function ci(e){let{eventRange:t}=e,n=t.def,r=t.instance?t.instance.range:t.range,i=r.start?r.start.valueOf():0,s=r.end?r.end.valueOf():0;return Object.assign(Object.assign(Object.assign({},n.extendedProps),n),{id:n.publicId,start:i,end:s,duration:s-i,allDay:Number(n.allDay),_seg:e})}function di(e,t){let{pluginHooks:n}=t,r=n.isDraggableTransformers,{def:i,ui:s}=e.eventRange,o=s.startEditable;for(let e of r)o=e(o,i,s,t);return o}function ui(e,t){return e.isStart&&e.eventRange.ui.durationEditable&&t.options.eventResizableFromStart}function hi(e,t){return e.isEnd&&e.eventRange.ui.durationEditable}function fi(e,t,n,r,i,s,o){let{dateEnv:a,options:l}=n,{displayEventTime:c,displayEventEnd:d}=l,u=e.eventRange.def,h=e.eventRange.instance;null==c&&(c=!1!==r),null==d&&(d=!1!==i);let f=h.range.start,g=h.range.end,p=s||e.start||e.eventRange.range.start,m=o||e.end||e.eventRange.range.end,v=Mt(f).valueOf()===Mt(p).valueOf(),y=Mt(Ct(g,-1)).valueOf()===Mt(Ct(m,-1)).valueOf();return c&&!u.allDay&&(v||y)?(p=v?f:p,m=y?g:m,d&&u.hasEnd?a.formatRange(p,m,t,{forcedStartTzo:s?null:h.forcedStartTzo,forcedEndTzo:o?null:h.forcedEndTzo}):a.format(p,t,{forcedTzo:s?null:h.forcedStartTzo})):""}function gi(e,t,n){let r=e.eventRange.range;return{isPast:r.end<=(n||t.start),isFuture:r.start>=(n||t.end),isToday:t&&ar(t,r.start)}}function pi(e){let t=["fc-event"];return e.isMirror&&t.push("fc-event-mirror"),e.isDraggable&&t.push("fc-event-draggable"),(e.isStartResizable||e.isEndResizable)&&t.push("fc-event-resizable"),e.isDragging&&t.push("fc-event-dragging"),e.isResizing&&t.push("fc-event-resizing"),e.isSelected&&t.push("fc-event-selected"),e.isStart&&t.push("fc-event-start"),e.isEnd&&t.push("fc-event-end"),e.isPast&&t.push("fc-event-past"),e.isToday&&t.push("fc-event-today"),e.isFuture&&t.push("fc-event-future"),t}function mi(e){return e.instance?e.instance.instanceId:`${e.def.defId}:${e.range.start.toISOString()}`}function vi(e,t){let{def:n,instance:r}=e.eventRange,{url:i}=n;if(i)return{href:i};let{emitter:s,options:o}=t,{eventInteractive:a}=o;return null==a&&(a=n.interactive,null==a&&(a=Boolean(s.hasHandlers("eventClick")))),a?qe(e=>{s.trigger("eventClick",{el:e.target,event:new Kr(t,n,r),jsEvent:e,view:t.viewApi})}):{}}const yi={start:yn,end:yn,allDay:Boolean};function bi(e,t,n){let r=function(e,t){let{refined:n,extra:r}=vn(e,yi),i=n.start?t.createMarkerMeta(n.start):null,s=n.end?t.createMarkerMeta(n.end):null,{allDay:o}=n;null==o&&(o=i&&i.isTimeUnspecified&&(!s||s.isTimeUnspecified));return Object.assign({range:{start:i?i.marker:null,end:s?s.marker:null},allDay:o},r)}(e,t),{range:i}=r;if(!i.start)return null;if(!i.end){if(null==n)return null;i.end=t.add(i.start,n)}return r}function Ei(e,t){return ir(e.range,t.range)&&e.allDay===t.allDay&&function(e,t){for(let n in t)if("range"!==n&&"allDay"!==n&&e[n]!==t[n])return!1;for(let n in e)if(!(n in t))return!1;return!0}(e,t)}function Si(e,t,n){return Object.assign(Object.assign({},Ai(e,t,n)),{timeZone:t.timeZone})}function Ai(e,t,n){return{start:t.toDate(e.start),end:t.toDate(e.end),startStr:t.formatIso(e.start,{omitTime:n}),endStr:t.formatIso(e.end,{omitTime:n})}}function Di(e,t,n){let r=!1,i=function(e){r||(r=!0,t(e))},s=function(e){r||(r=!0,n(e))},o=e(i,s);o&&"function"==typeof o.then&&o.then(i,s)}class wi extends Error{constructor(e,t){super(e),this.response=t}}function Ci(e,t,n){const r={method:e=e.toUpperCase()};return"GET"===e?t+=(-1===t.indexOf("?")?"?":"&")+new URLSearchParams(n):(r.body=new URLSearchParams(n),r.headers={"Content-Type":"application/x-www-form-urlencoded"}),fetch(t,r).then(e=>{if(e.ok)return e.json().then(t=>[t,e],()=>{throw new wi("Failure parsing JSON",e)});throw new wi("Request failed",e)})}let Ri;function xi(){return null==Ri&&(Ri=function(){if("undefined"==typeof document)return!0;let e=document.createElement("div");e.style.position="absolute",e.style.top="0px",e.style.left="0px",e.innerHTML="
",e.querySelector("table").style.height="100px",e.querySelector("div").style.height="100%",document.body.appendChild(e);let t=e.querySelector("div").offsetHeight>0;return document.body.removeChild(e),t}()),Ri}class _i extends Vn{constructor(){super(...arguments),this.state={forPrint:!1},this.handleBeforePrint=()=>{Bn(()=>{this.setState({forPrint:!0})})},this.handleAfterPrint=()=>{Bn(()=>{this.setState({forPrint:!1})})}}render(){let{props:e}=this,{options:t}=e,{forPrint:n}=this.state,r=n||"auto"===t.height||"auto"===t.contentHeight,i=r||null==t.height?"":t.height,s=["fc",n?"fc-media-print":"fc-media-screen","fc-direction-"+t.direction,e.theme.getClass("root")];return xi()||s.push("fc-liquid-hack"),e.children(s,i,r,n)}componentDidMount(){let{emitter:e}=this.props;e.on("_beforeprint",this.handleBeforePrint),e.on("_afterprint",this.handleAfterPrint)}componentWillUnmount(){let{emitter:e}=this.props;e.off("_beforeprint",this.handleBeforePrint),e.off("_afterprint",this.handleAfterPrint)}}class Ti{constructor(e){this.component=e.component,this.isHitComboAllowed=e.isHitComboAllowed||null}destroy(){}}function ki(e){return{[e.component.uid]:e}}const Mi={};class Ii extends D{constructor(e,t){super(e,t),this.handleRefresh=()=>{let e=this.computeTiming();e.state.nowDate.valueOf()!==this.state.nowDate.valueOf()&&this.setState(e.state),this.clearTimeout(),this.setTimeout(e.waitMs)},this.handleVisibilityChange=()=>{document.hidden||this.handleRefresh()},this.state=this.computeTiming().state}render(){let{props:e,state:t}=this;return e.children(t.nowDate,t.todayRange)}componentDidMount(){this.setTimeout(),this.context.nowManager.addResetListener(this.handleRefresh),document.addEventListener("visibilitychange",this.handleVisibilityChange)}componentDidUpdate(e){e.unit!==this.props.unit&&(this.clearTimeout(),this.setTimeout())}componentWillUnmount(){this.clearTimeout(),this.context.nowManager.removeResetListener(this.handleRefresh),document.removeEventListener("visibilitychange",this.handleVisibilityChange)}computeTiming(){let e,t,{props:n,context:r}=this,i=r.nowManager.getDateMarker(),{nowIndicatorSnap:s}=r.options;if("auto"===s&&(s=/year|month|week|day/.test(n.unit)||1===(n.unitValue||1)),s){e=r.dateEnv.startOf(i,n.unit),t=r.dateEnv.add(e,ft(1,n.unit)).valueOf()-i.valueOf()}else e=i,t=6e4;return t=Math.min(864e5,t),{state:{nowDate:e,todayRange:Oi(e)},waitMs:t}}setTimeout(e=this.computeTiming().waitMs){this.timeoutId=setTimeout(()=>{const e=this.computeTiming();this.setState(e.state,()=>{this.setTimeout(e.waitMs)})},e)}clearTimeout(){this.timeoutId&&clearTimeout(this.timeoutId)}}function Oi(e){let t=Mt(e);return{start:t,end:wt(t,1)}}Ii.contextType=Ln;class Ni{getCurrentData(){return this.currentDataManager.getCurrentData()}dispatch(e){this.currentDataManager.dispatch(e)}get view(){return this.getCurrentData().viewApi}batchRendering(e){e()}updateSize(){this.trigger("_resize",!0)}setOption(e,t){this.dispatch({type:"SET_OPTION",optionName:e,rawOptionValue:t})}getOption(e){return this.currentDataManager.currentCalendarOptionsInput[e]}getAvailableLocaleCodes(){return Object.keys(this.getCurrentData().availableRawLocales)}on(e,t){let{currentDataManager:n}=this;n.currentCalendarOptionsRefiners[e]?n.emitter.on(e,t):console.warn(`Unknown listener name '${e}'`)}off(e,t){this.currentDataManager.emitter.off(e,t)}trigger(e,...t){this.currentDataManager.emitter.trigger(e,...t)}changeView(e,t){this.batchRendering(()=>{if(this.unselect(),t)if(t.start&&t.end)this.dispatch({type:"CHANGE_VIEW_TYPE",viewType:e}),this.dispatch({type:"SET_OPTION",optionName:"visibleRange",rawOptionValue:t});else{let{dateEnv:n}=this.getCurrentData();this.dispatch({type:"CHANGE_VIEW_TYPE",viewType:e,dateMarker:n.createMarker(t)})}else this.dispatch({type:"CHANGE_VIEW_TYPE",viewType:e})})}zoomTo(e,t){let n;t=t||"day",n=this.getCurrentData().viewSpecs[t]||this.getUnitViewSpec(t),this.unselect(),n?this.dispatch({type:"CHANGE_VIEW_TYPE",viewType:n.type,dateMarker:e}):this.dispatch({type:"CHANGE_DATE",dateMarker:e})}getUnitViewSpec(e){let t,n,{viewSpecs:r,toolbarConfig:i}=this.getCurrentData(),s=[].concat(i.header?i.header.viewsWithButtons:[],i.footer?i.footer.viewsWithButtons:[]);for(let e in r)s.push(e);for(t=0;t{this.dispatch({type:"REMOVE_EVENTS",eventStore:ei(e)})}})}getEventById(e){let t=this.getCurrentData(),{defs:n,instances:r}=t.eventStore;e=String(e);for(let i in n){let s=n[i];if(s.publicId===e){if(s.recurringDef)return new Kr(t,s,null);for(let e in r){let n=r[e];if(n.defId===s.defId)return new Kr(t,s,n)}}}return null}getEvents(){let e=this.getCurrentData();return ti(e.eventStore,e)}removeAllEvents(){this.dispatch({type:"REMOVE_ALL_EVENTS"})}getEventSources(){let e=this.getCurrentData(),t=e.eventSources,n=[];for(let r in t)n.push(new Jr(e,t[r]));return n}getEventSourceById(e){let t=this.getCurrentData(),n=t.eventSources;e=String(e);for(let r in n)if(n[r].publicId===e)return new Jr(t,n[r]);return null}addEventSource(e){let t=this.getCurrentData();if(e instanceof Jr)return t.eventSources[e.internalEventSource.sourceId]||this.dispatch({type:"ADD_EVENT_SOURCES",sources:[e.internalEventSource]}),e;let n=Hr(e,t);return n?(this.dispatch({type:"ADD_EVENT_SOURCES",sources:[n]}),new Jr(t,n)):null}removeAllEventSources(){this.dispatch({type:"REMOVE_ALL_EVENT_SOURCES"})}refetchEvents(){this.dispatch({type:"FETCH_EVENT_SOURCES",isRefetch:!0})}scrollToTime(e){let t=ft(e);t&&this.trigger("_scrollRequest",{time:t})}}function Pi(e,t){return e.left>=t.left&&e.left=t.top&&e.topthis.eventUiBuilders[t]||Gt(Wi));for(let n in t){let c=t[n],d=s[n]||Ui,u=this.eventUiBuilders[n];l[n]={businessHours:c.businessHours||e.businessHours,dateSelection:r[n]||null,eventStore:d,eventUiBases:u(e.eventUiBases[""],c.ui,i[n]),eventSelection:d.instances[e.eventSelection]?e.eventSelection:"",eventDrag:o[n]||null,eventResize:a[n]||null}}return l}_splitDateSpan(e){let t={};if(e){let n=this.getKeysForDateSpan(e);for(let r of n)t[r]=e}return t}_getKeysForEventDefs(e){return An(e.defs,e=>this.getKeysForEventDef(e))}_splitEventStore(e,t){let{defs:n,instances:r}=e,i={};for(let e in n)for(let r of t[e])i[r]||(i[r]={defs:{},instances:{}}),i[r].defs[e]=n[e];for(let e in r){let n=r[e];for(let r of t[n.defId])i[r]&&(i[r].instances[e]=n)}return i}_splitIndividualUi(e,t){let n={};for(let r in e)if(r)for(let i of t[r])n[i]||(n[i]={}),n[i][r]=e[r];return n}_splitInteraction(e){let t={};if(e){let n=this._splitEventStore(e.affectedEvents,this._getKeysForEventDefs(e.affectedEvents)),r=this._getKeysForEventDefs(e.mutatedEvents),i=this._splitEventStore(e.mutatedEvents,r),s=r=>{t[r]||(t[r]={affectedEvents:n[r]||Ui,mutatedEvents:i[r]||Ui,isEvent:e.isEvent})};for(let e in n)s(e);for(let e in i)s(e)}return t}}function Wi(e,t,n){let r=[];e&&r.push(e),t&&r.push(t);let i={"":Or(r)};return n&&Object.assign(i,n),i}function Fi(e,t,n,r){return{dow:e.getUTCDay(),isDisabled:Boolean(r&&(!r.activeRange||!ar(r.activeRange,e))),isOther:Boolean(r&&!ar(r.currentRange,e)),isToday:Boolean(t&&ar(t,e)),isPast:Boolean(n?en:!!t&&e>=t.end)}}function Vi(e,t){let n=["fc-day","fc-day-"+At[e.dow]];return e.isDisabled?n.push("fc-day-disabled"):(e.isToday&&(n.push("fc-day-today"),n.push(t.getClass("today"))),e.isPast&&n.push("fc-day-past"),e.isFuture&&n.push("fc-day-future"),e.isOther&&n.push("fc-day-other")),n}const Gi=an({year:"numeric",month:"long",day:"numeric"}),Qi=an({week:"long"});function qi(e,t,n="day",r=!0){const{dateEnv:i,options:s,calendarApi:o}=e;let a=i.format(t,"week"===n?Qi:Gi);if(s.navLinks){let e=i.toDate(t);const l=e=>{let r="day"===n?s.navLinkDayClick:"week"===n?s.navLinkWeekClick:null;"function"==typeof r?r.call(o,i.toDate(t),e):("string"==typeof r&&(n=r),o.zoomTo(t,n))};return Object.assign({title:at(s.navLinkHint,[a,e],a),"data-navlink":""},r?Qe(l):{onClick:l})}return{"aria-label":a}}let Yi,Zi=null;function Xi(){return null===Zi&&(Zi=function(){let e=document.createElement("div");Be(e,{position:"absolute",top:-1e3,left:0,border:0,padding:0,overflow:"scroll",direction:"rtl"}),e.innerHTML="
",document.body.appendChild(e);let t=e.firstChild.getBoundingClientRect().left>e.getBoundingClientRect().left;return Ie(e),t}()),Zi}function $i(){return Yi||(Yi=function(){let e=document.createElement("div");e.style.overflow="scroll",e.style.position="absolute",e.style.top="-9999px",e.style.left="-9999px",document.body.appendChild(e);let t=Ji(e);return document.body.removeChild(e),t}()),Yi}function Ji(e){return{x:e.offsetHeight-e.clientHeight,y:e.offsetWidth-e.clientWidth}}function Ki(e,t=!1){let n=window.getComputedStyle(e),r=parseInt(n.borderLeftWidth,10)||0,i=parseInt(n.borderRightWidth,10)||0,s=parseInt(n.borderTopWidth,10)||0,o=parseInt(n.borderBottomWidth,10)||0,a=Ji(e),l=a.y-r-i,c={borderLeft:r,borderRight:i,borderTop:s,borderBottom:o,scrollbarBottom:a.x-s-o,scrollbarLeft:0,scrollbarRight:0};return Xi()&&"rtl"===n.direction?c.scrollbarLeft=l:c.scrollbarRight=l,t&&(c.paddingLeft=parseInt(n.paddingLeft,10)||0,c.paddingRight=parseInt(n.paddingRight,10)||0,c.paddingTop=parseInt(n.paddingTop,10)||0,c.paddingBottom=parseInt(n.paddingBottom,10)||0),c}function es(e,t=!1,n){let r=n?e.getBoundingClientRect():ts(e),i=Ki(e,t),s={left:r.left+i.borderLeft+i.scrollbarLeft,right:r.right-i.borderRight-i.scrollbarRight,top:r.top+i.borderTop,bottom:r.bottom-i.borderBottom-i.scrollbarBottom};return t&&(s.left+=i.paddingLeft,s.right-=i.paddingRight,s.top+=i.paddingTop,s.bottom-=i.paddingBottom),s}function ts(e){let t=e.getBoundingClientRect();return{left:t.left+window.scrollX,top:t.top+window.scrollY,right:t.right+window.scrollX,bottom:t.bottom+window.scrollY}}function ns(e){let t=[];for(;e instanceof HTMLElement;){let n=window.getComputedStyle(e);if("fixed"===n.position)break;/(auto|scroll)/.test(n.overflow+n.overflowY+n.overflowX)&&t.push(e),e=e.parentNode}return t}class rs{constructor(e,t,n,r){this.els=t;let i=this.originClientRect=e.getBoundingClientRect();n&&this.buildElHorizontals(i.left),r&&this.buildElVerticals(i.top)}buildElHorizontals(e){let t=[],n=[];for(let r of this.els){let i=r.getBoundingClientRect();t.push(i.left-e),n.push(i.right-e)}this.lefts=t,this.rights=n}buildElVerticals(e){let t=[],n=[];for(let r of this.els){let i=r.getBoundingClientRect();t.push(i.top-e),n.push(i.bottom-e)}this.tops=t,this.bottoms=n}leftToIndex(e){let t,{lefts:n,rights:r}=this,i=n.length;for(t=0;t=n[t]&&e=n[t]&&e0}canScrollHorizontally(){return this.getMaxScrollLeft()>0}canScrollUp(){return this.getScrollTop()>0}canScrollDown(){return this.getScrollTop()0}canScrollRight(){return this.getScrollLeft()e.thickness||1)){this.getEntryThickness=e,this.strictOrder=!1,this.allowReslicing=!1,this.maxCoord=-1,this.maxStackCnt=-1,this.levelCoords=[],this.entriesByLevel=[],this.stackCnts={}}addSegs(e){let t=[];for(let n of e)this.insertEntry(n,t);return t}insertEntry(e,t){let n=this.findInsertion(e);this.isInsertionValid(n,e)?this.insertEntryAt(e,n):this.handleInvalidInsertion(n,e,t)}isInsertionValid(e,t){return(-1===this.maxCoord||e.levelCoord+this.getEntryThickness(t)<=this.maxCoord)&&(-1===this.maxStackCnt||e.stackCnti.end&&this.insertEntry({index:e.index,thickness:e.thickness,span:{start:i.end,end:r.end}},n)}insertEntryAt(e,t){let{entriesByLevel:n,levelCoords:r}=this;-1===t.lateral?(gs(r,t.level,t.levelCoord),gs(n,t.level,[e])):gs(n[t.level],t.lateral,e),this.stackCnts[us(e)]=t.stackCnt}findInsertion(e){let{levelCoords:t,entriesByLevel:n,strictOrder:r,stackCnts:i}=this,s=t.length,o=0,a=-1,l=-1,c=null,d=0;for(let u=0;u=o+this.getEntryThickness(e))break;let h,f=n[u],g=ps(f,e.span.start,ds),p=g[0]+g[1];for(;(h=f[p])&&h.span.starto&&(o=e,c=h,a=u,l=p),e===o&&(d=Math.max(d,i[us(h)]+1)),p+=1}}let u=0;if(c)for(u=a+1;un(e[i-1]))return[i,0];for(;ro))return[s,1];r=s+1}}return[r,0]}class ms{constructor(e,t){this.emitter=new Fr}destroy(){}setMirrorIsVisible(e){}setMirrorNeedsRevert(e){}setAutoScrollEnabled(e){}}const vs={},ys={startTime:ft,duration:ft,create:Boolean,sourceId:String};function bs(e){let{refined:t,extra:n}=vn(e,ys);return{startTime:t.startTime||null,duration:t.duration||null,create:null==t.create||t.create,sourceId:t.sourceId,leftoverProps:n}}function Es(e,t){return an(!e||t>10?{weekday:"short"}:t>1?{weekday:"short",month:"numeric",day:"numeric",omitCommas:!0}:{weekday:"long"})}const Ss="fc-col-header-cell";function As(e){return e.text}class Ds extends Vn{render(){let{dateEnv:e,options:t,theme:n,viewApi:r}=this.context,{props:i}=this,{date:s,dateProfile:o}=i,a=Fi(s,i.todayRange,null,o),l=[Ss].concat(Vi(a,n)),c=e.format(s,i.dayHeaderFormat),d=!a.isDisabled&&i.colCnt>1?qi(this.context,s):{},u=e.toDate(s);e.namedTimeZoneImpl&&(u=Ct(u,36e5));let h=Object.assign(Object.assign(Object.assign({date:u,view:r},i.extraRenderProps),{text:c}),a);return p($n,{elTag:"th",elClasses:l,elAttrs:Object.assign({role:"columnheader",colSpan:i.colSpan,"data-date":a.isDisabled?void 0:Lt(s)},i.extraDataAttrs),renderProps:h,generatorName:"dayHeaderContent",customGenerator:t.dayHeaderContent,defaultGenerator:As,classNameGenerator:t.dayHeaderClassNames,didMount:t.dayHeaderDidMount,willUnmount:t.dayHeaderWillUnmount},e=>p("div",{className:"fc-scrollgrid-sync-inner"},!a.isDisabled&&p(e,{elTag:"a",elAttrs:d,elClasses:["fc-col-header-cell-cushion",i.isSticky&&"fc-sticky"]})))}}const ws=an({weekday:"long"});class Cs extends Vn{render(){let{props:e}=this,{dateEnv:t,theme:n,viewApi:r,options:i}=this.context,s=wt(new Date(2592e5),e.dow),o={dow:e.dow,isDisabled:!1,isFuture:!1,isPast:!1,isToday:!1,isOther:!1},a=t.format(s,e.dayHeaderFormat),l=Object.assign(Object.assign(Object.assign(Object.assign({date:s},o),{view:r}),e.extraRenderProps),{text:a});return p($n,{elTag:"th",elClasses:[Ss,...Vi(o,n),...e.extraClassNames||[]],elAttrs:Object.assign({role:"columnheader",colSpan:e.colSpan},e.extraDataAttrs),renderProps:l,generatorName:"dayHeaderContent",customGenerator:i.dayHeaderContent,defaultGenerator:As,classNameGenerator:i.dayHeaderClassNames,didMount:i.dayHeaderDidMount,willUnmount:i.dayHeaderWillUnmount},n=>p("div",{className:"fc-scrollgrid-sync-inner"},p(n,{elTag:"a",elClasses:["fc-col-header-cell-cushion",e.isSticky&&"fc-sticky"],elAttrs:{"aria-label":t.format(s,ws)}})))}}class Rs extends Vn{constructor(){super(...arguments),this.createDayHeaderFormatter=Gt(xs)}render(){let{context:e}=this,{dates:t,dateProfile:n,datesRepDistinctDays:r,renderIntro:i}=this.props,s=this.createDayHeaderFormatter(e.options.dayHeaderFormat,r,t.length);return p(Ii,{unit:"day"},(e,o)=>p("tr",{role:"row"},i&&i("day"),t.map(e=>r?p(Ds,{key:e.toISOString(),date:e,dateProfile:n,todayRange:o,colCnt:t.length,dayHeaderFormat:s}):p(Cs,{key:e.getUTCDay(),dow:e.getUTCDay(),dayHeaderFormat:s}))))}}function xs(e,t,n){return e||Es(t,n)}class _s{constructor(e,t){let n=e.start,{end:r}=e,i=[],s=[],o=-1;for(;n=t.length?t[t.length-1]+1:t[n]}}class Ts{constructor(e,t){let n,r,i,{dates:s}=e;if(t){for(r=s[0].getUTCDay(),n=1;n!p[e.instanceId])}),u=d.defs,h=d.instances,f=oi(u,e.eventUiBases);var g,p;for(let r in l){let o=l[r],g=o.range,p=c[o.defId],m=a[o.defId];if(!Hs(p.constraints,g,d,e.businessHours,t))return!1;let{eventOverlap:v}=t.options,y="function"==typeof v?v:null;for(let e in h){let n=h[e];if(sr(g,n.range)){if(!1===f[n.defId].overlap&&s.isEvent)return!1;if(!1===p.overlap)return!1;if(y&&!y(new Kr(t,u[n.defId],n),new Kr(t,m,o)))return!1}}let b=i.eventStore;for(let e of p.allows){let i,s=Object.assign(Object.assign({},n),{range:o.range,allDay:m.allDay}),a=b.defs[m.defId],l=b.instances[r];if(i=a?new Kr(t,a,l):new Kr(t,m),!e(qr(s,t),i))return!1}}return!0}(e,t,n,r))&&!(e.dateSelection&&!function(e,t,n,r){let i=e.eventStore,s=i.defs,o=i.instances,a=e.dateSelection,l=a.range,{selectionConfig:c}=t.getCurrentData();r&&(c=r(c));if(!Hs(c.constraints,l,i,e.businessHours,t))return!1;let{selectOverlap:d}=t.options,u="function"==typeof d?d:null;for(let e in o){let n=o[e];if(sr(l,n.range)){if(!1===c.overlap)return!1;if(u&&!u(new Kr(t,s[n.defId],n),null))return!1}}for(let e of c.allows){let r=Object.assign(Object.assign({},n),a);if(!e(qr(r,t),null))return!1}return!0}(e,t,n,r))}function Hs(e,t,n,r,i){for(let s of e)if(!zs(Bs(s,t,n,r,i),t))return!1;return!0}function Bs(e,t,n,r,i){return"businessHours"===e?js(gr(r,t,i)):"string"==typeof e?js(_r(n,t=>t.groupId===e)):"object"==typeof e&&e?js(gr(e,t,i)):[]}function js(e){let{instances:t}=e,n=[];for(let e in t)n.push(t[e].range);return n}function zs(e,t){for(let n of e)if(or(n,t))return!0;return!1}const Us=/^(visible|hidden)$/;class Ls extends Vn{constructor(){super(...arguments),this.handleEl=e=>{this.el=e,Gn(this.props.elRef,e)}}render(){let{props:e}=this,{liquid:t,liquidIsAbsolute:n}=e,r=t&&n,i=["fc-scroller"];return t&&(n?i.push("fc-scroller-liquid-absolute"):i.push("fc-scroller-liquid")),p("div",{ref:this.handleEl,className:i.join(" "),style:{overflowX:e.overflowX,overflowY:e.overflowY,left:r&&-(e.overcomeLeft||0)||"",right:r&&-(e.overcomeRight||0)||"",bottom:r&&-(e.overcomeBottom||0)||"",marginLeft:!r&&-(e.overcomeLeft||0)||"",marginRight:!r&&-(e.overcomeRight||0)||"",marginBottom:!r&&-(e.overcomeBottom||0)||"",maxHeight:e.maxHeight||""}},e.children)}needsXScrolling(){if(Us.test(this.props.overflowX))return!1;let{el:e}=this,t=this.el.getBoundingClientRect().width-this.getYScrollbarWidth(),{children:n}=e;for(let e=0;et)return!0}return!1}needsYScrolling(){if(Us.test(this.props.overflowY))return!1;let{el:e}=this,t=this.el.getBoundingClientRect().height-this.getXScrollbarWidth(),{children:n}=e;for(let e=0;et)return!0}return!1}getXScrollbarWidth(){return Us.test(this.props.overflowX)?0:this.el.offsetHeight-this.el.clientHeight}getYScrollbarWidth(){return Us.test(this.props.overflowY)?0:this.el.offsetWidth-this.el.clientWidth}}class Ws{constructor(e){this.masterCallback=e,this.currentMap={},this.depths={},this.callbackMap={},this.handleValue=(e,t)=>{let{depths:n,currentMap:r}=this,i=!1,s=!1;null!==e?(i=t in r,r[t]=e,n[t]=(n[t]||0)+1,s=!0):(n[t]-=1,n[t]||(delete r[t],delete this.callbackMap[t],i=!0)),this.masterCallback&&(i&&this.masterCallback(null,String(t)),s&&this.masterCallback(e,String(t)))}}createRef(e){let t=this.callbackMap[e];return t||(t=this.callbackMap[e]=t=>{this.handleValue(t,String(e))}),t}collect(e,t,n){return Tn(this.currentMap,e,t,n)}getAll(){return wn(this.currentMap)}}function Fs(e){let t=Pe(e,".fc-scrollgrid-shrink"),n=0;for(let e of t)n=Math.max(n,dt(e));return Math.ceil(n)}function Vs(e,t){return e.liquid&&t.liquid}function Gs(e,t){return null!=t.maxHeight||Vs(e,t)}function Qs(e,t,n,r){let{expandRows:i}=n;return"function"==typeof t.content?t.content(n):p("table",{role:"presentation",className:[t.tableClassName,e.syncRowHeights?"fc-scrollgrid-sync-table":""].join(" "),style:{minWidth:n.tableMinWidth,width:n.clientWidth,height:i?n.clientHeight:""}},n.tableColGroupNode,p(r?"thead":"tbody",{role:"presentation"},"function"==typeof t.rowContent?t.rowContent(n):t.rowContent))}function qs(e,t){return St(e,t,Cn)}function Ys(e,t){let n=[];for(let r of e){let e=r.span||1;for(let i=0;ie,qs),this.renderMicroColGroup=Gt(Ys),this.scrollerRefs=new Ws,this.scrollerElRefs=new Ws(this._handleScrollerEl.bind(this)),this.state={shrinkWidth:null,forceYScrollbars:!1,scrollerClientWidths:{},scrollerClientHeights:{}},this.handleSizing=()=>{this.safeSetState(Object.assign({shrinkWidth:this.computeShrinkWidth()},this.computeScrollerDims()))}}render(){let{props:e,state:t,context:n}=this,r=e.sections||[],i=this.processCols(e.cols),s=this.renderMicroColGroup(i,t.shrinkWidth),o=$s(e.liquid,n);e.collapsibleWidth&&o.push("fc-scrollgrid-collapsible");let a,l=r.length,c=0,d=[],u=[],h=[];for(;c{}},r);return p(r?"th":"td",{ref:n.elRef,role:"presentation"},p("div",{className:"fc-scroller-harness"+(c?" fc-scroller-harness-liquid":"")},p(Ls,{ref:this.scrollerRefs.createRef(u),elRef:this.scrollerElRefs.createRef(u),overflowY:d,overflowX:i.liquid?"hidden":"visible",maxHeight:e.maxHeight,liquid:c,liquidIsAbsolute:!0},h)))}_handleScrollerEl(e,t){let n=function(e,t){for(let n of e)if(n.key===t)return n;return null}(this.props.sections,t);n&&Gn(n.chunk.scrollerElRef,e)}componentDidMount(){this.handleSizing(),this.context.addResizeHandler(this.handleSizing)}componentDidUpdate(){this.handleSizing()}componentWillUnmount(){this.context.removeResizeHandler(this.handleSizing)}computeShrinkWidth(){return Xs(this.props.cols)?Fs(this.scrollerElRefs.getAll()):0}computeScrollerDims(){let e=$i(),{scrollerRefs:t,scrollerElRefs:n}=this,r=!1,i={},s={};for(let e in t.currentMap){let n=t.currentMap[e];if(n&&n.needsYScrolling()){r=!0;break}}for(let t of this.props.sections){let o=t.key,a=n.currentMap[o];if(a){let t=a.parentNode;i[o]=Math.floor(t.getBoundingClientRect().width-(r?e.y:0)),s[o]=Math.floor(t.getBoundingClientRect().height)}}return{forceYScrollbars:r,scrollerClientWidths:i,scrollerClientHeights:s}}}no.addStateEquality({scrollerClientWidths:Cn,scrollerClientHeights:Cn});class ro extends Vn{constructor(){super(...arguments),this.buildPublicEvent=Gt((e,t,n)=>new Kr(e,t,n)),this.handleEl=e=>{this.el=e,Gn(this.props.elRef,e),e&&ii(e,this.props.seg)}}render(){const{props:e,context:t}=this,{options:n}=t,{seg:r}=e,{eventRange:i}=r,{ui:s}=i,o={event:this.buildPublicEvent(t,i.def,i.instance),view:t.viewApi,timeText:e.timeText,textColor:s.textColor,backgroundColor:s.backgroundColor,borderColor:s.borderColor,isDraggable:!e.disableDragging&&di(r,t),isStartResizable:!e.disableResizing&&ui(r,t),isEndResizable:!e.disableResizing&&hi(r),isMirror:Boolean(e.isDragging||e.isResizing||e.isDateSelecting),isStart:Boolean(r.isStart),isEnd:Boolean(r.isEnd),isPast:Boolean(e.isPast),isFuture:Boolean(e.isFuture),isToday:Boolean(e.isToday),isSelected:Boolean(e.isSelected),isDragging:Boolean(e.isDragging),isResizing:Boolean(e.isResizing)};return p($n,{elRef:this.handleEl,elTag:e.elTag,elAttrs:e.elAttrs,elClasses:[...pi(o),...r.eventRange.ui.classNames,...e.elClasses||[]],elStyle:e.elStyle,renderProps:o,generatorName:"eventContent",customGenerator:n.eventContent,defaultGenerator:e.defaultGenerator,classNameGenerator:n.eventClassNames,didMount:n.eventDidMount,willUnmount:n.eventWillUnmount},e.children)}componentDidUpdate(e){this.el&&this.props.seg!==e.seg&&ii(this.el,this.props.seg)}}class io extends Vn{render(){let{props:e,context:t}=this,{options:n}=t,{seg:r}=e,{ui:i}=r.eventRange,s=fi(r,n.eventTimeFormat||e.defaultTimeFormat,t,e.defaultDisplayEventTime,e.defaultDisplayEventEnd);return p(ro,Object.assign({},e,{elTag:"a",elStyle:{borderColor:i.borderColor,backgroundColor:i.backgroundColor},elAttrs:vi(r,t),defaultGenerator:so,timeText:s}),(e,t)=>p(y,null,p(e,{elTag:"div",elClasses:["fc-event-main"],elStyle:{color:t.textColor}}),Boolean(t.isStartResizable)&&p("div",{className:"fc-event-resizer fc-event-resizer-start"}),Boolean(t.isEndResizable)&&p("div",{className:"fc-event-resizer fc-event-resizer-end"})))}}function so(e){return p("div",{className:"fc-event-main-frame"},e.timeText&&p("div",{className:"fc-event-time"},e.timeText),p("div",{className:"fc-event-title-container"},p("div",{className:"fc-event-title fc-sticky"},e.event.title||p(y,null," "))))}io.addPropsEquality({seg:Cn});const oo=e=>p(Ln.Consumer,null,t=>{let{options:n}=t,r={isAxis:e.isAxis,date:t.dateEnv.toDate(e.date),view:t.viewApi};return p($n,{elRef:e.elRef,elTag:e.elTag||"div",elAttrs:e.elAttrs,elClasses:e.elClasses,elStyle:e.elStyle,renderProps:r,generatorName:"nowIndicatorContent",customGenerator:n.nowIndicatorContent,classNameGenerator:n.nowIndicatorClassNames,didMount:n.nowIndicatorDidMount,willUnmount:n.nowIndicatorWillUnmount},e.children)}),ao=an({day:"numeric"});class lo extends Vn{constructor(){super(...arguments),this.refineRenderProps=Qt(uo)}render(){let{props:e,context:t}=this,{options:n}=t,r=this.refineRenderProps({date:e.date,dateProfile:e.dateProfile,todayRange:e.todayRange,isMonthStart:e.isMonthStart||!1,showDayNumber:e.showDayNumber,extraRenderProps:e.extraRenderProps,viewApi:t.viewApi,dateEnv:t.dateEnv,monthStartFormat:n.monthStartFormat});return p($n,{elRef:e.elRef,elTag:e.elTag,elAttrs:Object.assign(Object.assign({},e.elAttrs),r.isDisabled?{}:{"data-date":Lt(e.date)}),elClasses:[...Vi(r,t.theme),...e.elClasses||[]],elStyle:e.elStyle,renderProps:r,generatorName:"dayCellContent",customGenerator:n.dayCellContent,defaultGenerator:e.defaultGenerator,classNameGenerator:r.isDisabled?void 0:n.dayCellClassNames,didMount:n.dayCellDidMount,willUnmount:n.dayCellWillUnmount},e.children)}}function co(e){return Boolean(e.dayCellContent||qn("dayCellContent",e))}function uo(e){let{date:t,dateEnv:n,dateProfile:r,isMonthStart:i}=e,s=Fi(t,e.todayRange,null,r),o=e.showDayNumber?n.format(t,i?e.monthStartFormat:ao):"";return Object.assign(Object.assign(Object.assign({date:n.toDate(t),view:e.viewApi},s),{isMonthStart:i,dayNumberText:o}),e.extraRenderProps)}class ho extends Vn{render(){let{props:e}=this,{seg:t}=e;return p(ro,{elTag:"div",elClasses:["fc-bg-event"],elStyle:{backgroundColor:t.eventRange.ui.backgroundColor},defaultGenerator:fo,seg:t,timeText:"",isDragging:!1,isResizing:!1,isDateSelecting:!1,isSelected:!1,isPast:e.isPast,isFuture:e.isFuture,isToday:e.isToday,disableDragging:!0,disableResizing:!0})}}function fo(e){let{title:t}=e.event;return t&&p("div",{className:"fc-event-title"},e.event.title)}function go(e){return p("div",{className:"fc-"+e})}const po=e=>p(Ln.Consumer,null,t=>{let{dateEnv:n,options:r}=t,{date:i}=e,s=r.weekNumberFormat||e.defaultFormat,o={num:n.computeWeekNumber(i),text:n.format(i,s),date:i};return p($n,{elRef:e.elRef,elTag:e.elTag,elAttrs:e.elAttrs,elClasses:e.elClasses,elStyle:e.elStyle,renderProps:o,generatorName:"weekNumberContent",customGenerator:r.weekNumberContent,defaultGenerator:mo,classNameGenerator:r.weekNumberClassNames,didMount:r.weekNumberDidMount,willUnmount:r.weekNumberWillUnmount},e.children)});function mo(e){return e.text}class vo extends Vn{constructor(){super(...arguments),this.state={titleId:Le()},this.handleRootEl=e=>{this.rootEl=e,this.props.elRef&&Gn(this.props.elRef,e)},this.handleDocumentMouseDown=e=>{const t=ze(e);this.rootEl.contains(t)||this.handleCloseClick()},this.handleDocumentKeyDown=e=>{"Escape"===e.key&&this.handleCloseClick()},this.handleCloseClick=()=>{let{onClose:e}=this.props;e&&e()}}render(){let{theme:e,options:t}=this.context,{props:n,state:r}=this,i=["fc-popover",e.getClass("popover")].concat(n.extraClassNames||[]);return fe(p("div",Object.assign({},n.extraAttrs,{id:n.id,className:i.join(" "),"aria-labelledby":r.titleId,ref:this.handleRootEl}),p("div",{className:"fc-popover-header "+e.getClass("popoverHeader")},p("span",{className:"fc-popover-title",id:r.titleId},n.title),p("span",{className:"fc-popover-close "+e.getIconClass("close"),title:t.closeHint,onClick:this.handleCloseClick})),p("div",{className:"fc-popover-body "+e.getClass("popoverContent")},n.children)),n.parentEl)}componentDidMount(){document.addEventListener("mousedown",this.handleDocumentMouseDown),document.addEventListener("keydown",this.handleDocumentKeyDown),this.updateSize()}componentWillUnmount(){document.removeEventListener("mousedown",this.handleDocumentMouseDown),document.removeEventListener("keydown",this.handleDocumentKeyDown)}updateSize(){let{isRtl:e}=this.context,{alignmentEl:t,alignGridTop:n}=this.props,{rootEl:r}=this,i=function(e){let t=ns(e),n=e.getBoundingClientRect();for(let e of t){let t=Hi(n,e.getBoundingClientRect());if(!t)return null;n=t}return n}(t);if(i){let s=r.getBoundingClientRect(),o=n?Oe(t,".fc-scrollgrid").getBoundingClientRect().top:i.top,a=e?i.right-s.width:i.left;o=Math.max(o,10),a=Math.min(a,document.documentElement.clientWidth-10-s.width),a=Math.max(a,10);let l=r.offsetParent.getBoundingClientRect();Be(r,{top:o-l.top,left:a-l.left})}}}class yo extends ls{constructor(){super(...arguments),this.handleRootEl=e=>{this.rootEl=e,e?this.context.registerInteractiveComponent(this,{el:e,useEventCenter:!1}):this.context.unregisterInteractiveComponent(this)}}render(){let{options:e,dateEnv:t}=this.context,{props:n}=this,{startDate:r,todayRange:i,dateProfile:s}=n,o=t.format(r,e.dayPopoverFormat);return p(lo,{elRef:this.handleRootEl,date:r,dateProfile:s,todayRange:i},(t,r,i)=>p(vo,{elRef:i.ref,id:n.id,title:o,extraClassNames:["fc-more-popover"].concat(i.className||[]),extraAttrs:i,parentEl:n.parentEl,alignmentEl:n.alignmentEl,alignGridTop:n.alignGridTop,onClose:n.onClose},co(e)&&p(t,{elTag:"div",elClasses:["fc-more-popover-misc"]}),n.children))}queryHit(e,t,n,r){let{rootEl:i,props:s}=this;return e>=0&&e=0&&t{this.linkEl=e,this.props.elRef&&Gn(this.props.elRef,e)},this.handleClick=e=>{let{props:t,context:n}=this,{moreLinkClick:r}=n.options,i=So(t).start;function s(e){let{def:t,instance:r,range:i}=e.eventRange;return{event:new Kr(n,t,r),start:n.dateEnv.toDate(i.start),end:n.dateEnv.toDate(i.end),isStart:e.isStart,isEnd:e.isEnd}}"function"==typeof r&&(r=r({date:i,allDay:Boolean(t.allDayDate),allSegs:t.allSegs.map(s),hiddenSegs:t.hiddenSegs.map(s),jsEvent:e,view:n.viewApi})),r&&"popover"!==r?"string"==typeof r&&n.calendarApi.zoomTo(i,r):this.setState({isPopoverOpen:!0})},this.handlePopoverClose=()=>{this.setState({isPopoverOpen:!1})}}render(){let{props:e,state:t}=this;return p(Ln.Consumer,null,n=>{let{viewApi:r,options:i,calendarApi:s}=n,{moreLinkText:o}=i,{moreCnt:a}=e,l=So(e),c="function"==typeof o?o.call(s,a):`+${a} ${o}`,d=at(i.moreLinkHint,[a],c),u={num:a,shortText:"+"+a,text:c,view:r};return p(y,null,Boolean(e.moreCnt)&&p($n,{elTag:e.elTag||"a",elRef:this.handleLinkEl,elClasses:[...e.elClasses||[],"fc-more-link"],elStyle:e.elStyle,elAttrs:Object.assign(Object.assign(Object.assign({},e.elAttrs),Qe(this.handleClick)),{title:d,"aria-expanded":t.isPopoverOpen,"aria-controls":t.isPopoverOpen?t.popoverId:""}),renderProps:u,generatorName:"moreLinkContent",customGenerator:i.moreLinkContent,defaultGenerator:e.defaultGenerator||Eo,classNameGenerator:i.moreLinkClassNames,didMount:i.moreLinkDidMount,willUnmount:i.moreLinkWillUnmount},e.children),t.isPopoverOpen&&p(yo,{id:t.popoverId,startDate:l.start,endDate:l.end,dateProfile:e.dateProfile,todayRange:e.todayRange,extraDateSpan:e.extraDateSpan,parentEl:this.parentEl,alignmentEl:e.alignmentElRef?e.alignmentElRef.current:this.linkEl,alignGridTop:e.alignGridTop,forceTimed:e.forceTimed,onClose:this.handlePopoverClose},e.popoverContent()))})}componentDidMount(){this.updateParentEl()}componentDidUpdate(){this.updateParentEl()}updateParentEl(){this.linkEl&&(this.parentEl=Oe(this.linkEl,".fc-view-harness"))}}function Eo(e){return e.text}function So(e){if(e.allDayDate)return{start:e.allDayDate,end:wt(e.allDayDate,1)};let{hiddenSegs:t}=e;return{start:Ao(t),end:(n=t,n.reduce(wo).eventRange.range.end)};var n}function Ao(e){return e.reduce(Do).eventRange.range.start}function Do(e,t){return e.eventRange.range.startt.eventRange.range.end?e:t}var Co={__proto__:null,BASE_OPTION_DEFAULTS:cn,BaseComponent:Vn,BgEvent:ho,CalendarImpl:Ni,CalendarRoot:_i,ContentContainer:$n,CustomRenderingStore:class extends class{constructor(){this.handlers=[]}set(e){this.currentValue=e;for(let t of this.handlers)t(e)}subscribe(e){this.handlers.push(e),void 0!==this.currentValue&&e(this.currentValue)}}{constructor(){super(...arguments),this.map=new Map}handle(e){const{map:t}=this;let n=!1;e.isActive?(t.set(e.id,e),n=!0):t.has(e.id)&&(t.delete(e.id),n=!0),n&&this.set(t)}},DateComponent:ls,DateEnv:Pn,DateProfileGenerator:hr,DayCellContainer:lo,DayHeader:Rs,DaySeriesModel:_s,DayTableModel:Ts,DelayedRunner:Me,ElementDragging:ms,ElementScrollController:os,Emitter:Fr,EventContainer:ro,EventImpl:Kr,Interaction:Ti,MoreLinkContainer:bo,NamedTimeZoneImpl:class{constructor(e){this.timeZoneName=e}},NowIndicatorContainer:oo,NowTimer:Ii,PositionCache:rs,RefMap:Ws,ScrollController:ss,ScrollResponder:Un,Scroller:Ls,SegHierarchy:cs,SimpleScrollGrid:no,Slicer:ks,Splitter:Li,StandardEvent:io,TableDateCell:Ds,TableDowCell:Cs,Theme:Hn,ViewContainer:Kn,ViewContextType:Ln,WeekNumberContainer:po,WindowScrollController:as,addDays:wt,addDurations:pt,addMs:Ct,addWeeks:Dt,allowContextMenu:tt,allowSelection:Ke,applyMutationToEventStore:Zr,applyStyle:Be,asCleanDays:function(e){return e.years||e.months||e.milliseconds?0:e.days},asRoughMinutes:function(e){return yt(e)/6e4},asRoughMs:yt,asRoughSeconds:function(e){return yt(e)/1e3},binarySearch:ps,buildElAttrs:Yn,buildEntryKey:us,buildEventApis:ti,buildEventRangeKey:mi,buildIsoString:Ut,buildNavLinkAttrs:qi,buildSegTimeText:fi,collectFromHash:Tn,combineEventUis:Or,compareByFieldSpecs:rt,compareNumbers:lt,compareObjs:xn,computeEarliestSegStart:Ao,computeEdges:Ki,computeFallbackHeaderFormat:Es,computeInnerRect:es,computeRect:ts,computeShrinkWidth:Fs,computeVisibleDayRange:cr,config:vs,constrainPoint:Bi,createDuration:ft,createEmptyEventStore:Rr,createEventInstance:fr,createEventUi:Ir,createFormatter:an,diffDates:ur,diffDayAndTime:_t,diffDays:xt,diffPoints:zi,diffWeeks:Rt,diffWholeDays:kt,diffWholeWeeks:Tt,disableCursor:Xe,elementClosest:Oe,elementMatches:Ne,enableCursor:$e,eventTupleToStore:wr,filterHash:Sn,findDirectChildren:function(e,t){let n=e instanceof HTMLElement?[e]:e,r=[];for(let e=0;e{let o=r.length,a=s.length,l=0;for(;l{let o={};for(let a in s)if(i[a])if(St(r[a],s[a]))o[a]=i[a];else{n&&n(i[a]);let r=e.apply(this,s[a]);o[a]=t&&t(r,i[a])?i[a]:r}else o[a]=e.apply(this,s[a]);return r=s,i=o,o}},memoizeObjArg:Qt,mergeEventStores:xr,multiplyDuration:mt,padStart:ot,parseBusinessHours:Gr,parseClassNames:Tr,parseDragMeta:bs,parseEventDef:Ar,parseFieldSpecs:nt,parseMarker:Nn,pointInsideRect:Pi,preventContextMenu:et,preventDefault:We,preventSelection:Je,rangeContainsMarker:ar,rangeContainsRange:or,rangesEqual:ir,rangesIntersect:sr,refineEventDef:Er,refineProps:vn,removeElement:Ie,removeExact:function(e,t){let n=0,r=0;for(;r2&&(a.children=arguments.length>3?t.call(arguments,2):r),m(e.type,a,i||e.key,s||e.ref,null)},createElement:p,createRef:v,h:p,hydrate:function e(t,n){U(t,n,e)},get isValidElement(){return i},get options(){return n},render:U,toChildArray:M};const xo=[],_o={code:"en",week:{dow:0,doy:4},direction:"ltr",buttonText:{prev:"prev",next:"next",prevYear:"prev year",nextYear:"next year",year:"year",today:"today",month:"month",week:"week",day:"day",list:"list"},weekText:"W",weekTextLong:"Week",closeHint:"Close",timeHint:"Time",eventHint:"Event",allDayText:"all-day",moreLinkText:"more",noEventsText:"No events to display"},To=Object.assign(Object.assign({},_o),{buttonHints:{prev:"Previous $0",next:"Next $0",today:(e,t)=>"day"===t?"Today":"This "+e},viewHint:"$0 view",navLinkHint:"Go to $0",moreLinkHint:e=>`Show ${e} more event${1===e?"":"s"}`});function ko(e){let t=e.length>0?e[0].code:"en",n=xo.concat(e),r={en:To};for(let e of n)r[e.code]=e;return{map:r,defaultCode:t}}function Mo(e,t){return"object"!=typeof e||Array.isArray(e)?function(e,t){let n=[].concat(e||[]),r=function(e,t){for(let n=0;n0;e-=1){let n=r.slice(0,e).join("-");if(t[n])return t[n]}}return null}(n,t)||To;return Io(e,n,r)}(e,t):Io(e.code,[e.code],e)}function Io(e,t,n){let r=En([_o,n],["buttonText"]);delete r.code;let{week:i}=r;return delete r.week,{codeArg:e,codes:t,week:i,simpleNumberFormat:new Intl.NumberFormat(e),options:r}}function Oo(e){return{id:Ze(),name:e.name,premiumReleaseDate:e.premiumReleaseDate?new Date(e.premiumReleaseDate):void 0,deps:e.deps||[],reducers:e.reducers||[],isLoadingFuncs:e.isLoadingFuncs||[],contextInit:[].concat(e.contextInit||[]),eventRefiners:e.eventRefiners||{},eventDefMemberAdders:e.eventDefMemberAdders||[],eventSourceRefiners:e.eventSourceRefiners||{},isDraggableTransformers:e.isDraggableTransformers||[],eventDragMutationMassagers:e.eventDragMutationMassagers||[],eventDefMutationAppliers:e.eventDefMutationAppliers||[],dateSelectionTransformers:e.dateSelectionTransformers||[],datePointTransforms:e.datePointTransforms||[],dateSpanTransforms:e.dateSpanTransforms||[],views:e.views||{},viewPropsTransformers:e.viewPropsTransformers||[],isPropsValid:e.isPropsValid||null,externalDefTransforms:e.externalDefTransforms||[],viewContainerAppends:e.viewContainerAppends||[],eventDropTransformers:e.eventDropTransformers||[],componentInteractions:e.componentInteractions||[],calendarInteractions:e.calendarInteractions||[],themeClasses:e.themeClasses||{},eventSourceDefs:e.eventSourceDefs||[],cmdFormatter:e.cmdFormatter,recurringTypes:e.recurringTypes||[],namedTimeZonedImpl:e.namedTimeZonedImpl,initialView:e.initialView||"",elementDraggingImpl:e.elementDraggingImpl,optionChangeHandlers:e.optionChangeHandlers||{},scrollGridImpl:e.scrollGridImpl||null,listenerRefiners:e.listenerRefiners||{},optionRefiners:e.optionRefiners||{},propSetHandlers:e.propSetHandlers||{}}}function No(){let e,t=[],n=[];return(r,i)=>(e&&St(r,t)&&St(i,n)||(e=function(e,t){let n={},r={premiumReleaseDate:void 0,reducers:[],isLoadingFuncs:[],contextInit:[],eventRefiners:{},eventDefMemberAdders:[],eventSourceRefiners:{},isDraggableTransformers:[],eventDragMutationMassagers:[],eventDefMutationAppliers:[],dateSelectionTransformers:[],datePointTransforms:[],dateSpanTransforms:[],views:{},viewPropsTransformers:[],isPropsValid:null,externalDefTransforms:[],viewContainerAppends:[],eventDropTransformers:[],componentInteractions:[],calendarInteractions:[],themeClasses:{},eventSourceDefs:[],cmdFormatter:null,recurringTypes:[],namedTimeZonedImpl:null,initialView:"",elementDraggingImpl:null,optionChangeHandlers:{},scrollGridImpl:null,listenerRefiners:{},optionRefiners:{},propSetHandlers:{}};function i(e){for(let o of e){const e=o.name,a=n[e];void 0===a?(n[e]=o.id,i(o.deps),s=o,r={premiumReleaseDate:Po((t=r).premiumReleaseDate,s.premiumReleaseDate),reducers:t.reducers.concat(s.reducers),isLoadingFuncs:t.isLoadingFuncs.concat(s.isLoadingFuncs),contextInit:t.contextInit.concat(s.contextInit),eventRefiners:Object.assign(Object.assign({},t.eventRefiners),s.eventRefiners),eventDefMemberAdders:t.eventDefMemberAdders.concat(s.eventDefMemberAdders),eventSourceRefiners:Object.assign(Object.assign({},t.eventSourceRefiners),s.eventSourceRefiners),isDraggableTransformers:t.isDraggableTransformers.concat(s.isDraggableTransformers),eventDragMutationMassagers:t.eventDragMutationMassagers.concat(s.eventDragMutationMassagers),eventDefMutationAppliers:t.eventDefMutationAppliers.concat(s.eventDefMutationAppliers),dateSelectionTransformers:t.dateSelectionTransformers.concat(s.dateSelectionTransformers),datePointTransforms:t.datePointTransforms.concat(s.datePointTransforms),dateSpanTransforms:t.dateSpanTransforms.concat(s.dateSpanTransforms),views:Object.assign(Object.assign({},t.views),s.views),viewPropsTransformers:t.viewPropsTransformers.concat(s.viewPropsTransformers),isPropsValid:s.isPropsValid||t.isPropsValid,externalDefTransforms:t.externalDefTransforms.concat(s.externalDefTransforms),viewContainerAppends:t.viewContainerAppends.concat(s.viewContainerAppends),eventDropTransformers:t.eventDropTransformers.concat(s.eventDropTransformers),calendarInteractions:t.calendarInteractions.concat(s.calendarInteractions),componentInteractions:t.componentInteractions.concat(s.componentInteractions),themeClasses:Object.assign(Object.assign({},t.themeClasses),s.themeClasses),eventSourceDefs:t.eventSourceDefs.concat(s.eventSourceDefs),cmdFormatter:s.cmdFormatter||t.cmdFormatter,recurringTypes:t.recurringTypes.concat(s.recurringTypes),namedTimeZonedImpl:s.namedTimeZonedImpl||t.namedTimeZonedImpl,initialView:t.initialView||s.initialView,elementDraggingImpl:t.elementDraggingImpl||s.elementDraggingImpl,optionChangeHandlers:Object.assign(Object.assign({},t.optionChangeHandlers),s.optionChangeHandlers),scrollGridImpl:s.scrollGridImpl||t.scrollGridImpl,listenerRefiners:Object.assign(Object.assign({},t.listenerRefiners),s.listenerRefiners),optionRefiners:Object.assign(Object.assign({},t.optionRefiners),s.optionRefiners),propSetHandlers:Object.assign(Object.assign({},t.propSetHandlers),s.propSetHandlers)}):a!==o.id&&console.warn(`Duplicate plugin '${e}'`)}var t,s}return e&&i(e),i(t),r}(r,i)),t=r,n=i,e)}function Po(e,t){return void 0===e?t:void 0===t?e:new Date(Math.max(e.valueOf(),t.valueOf()))}class Ho extends Hn{}function Bo(e,t,n,r){if(t[e])return t[e];let i=function(e,t,n,r){let i=n[e],s=r[e],o=e=>i&&null!==i[e]?i[e]:s&&null!==s[e]?s[e]:null,a=o("component"),l=o("superType"),c=null;if(l){if(l===e)throw new Error("Can't have a custom view type that references itself");c=Bo(l,t,n,r)}!a&&c&&(a=c.component);if(!a)return null;return{type:e,component:a,defaults:Object.assign(Object.assign({},c?c.defaults:{}),i?i.rawOptions:{}),overrides:Object.assign(Object.assign({},c?c.overrides:{}),s?s.rawOptions:{})}}(e,t,n,r);return i&&(t[e]=i),i}function jo(e){return An(e,zo)}function zo(e){let t="function"==typeof e?{component:e}:e,{component:n}=t;return t.content?n=Uo(t):!n||n.prototype instanceof Vn||(n=Uo(Object.assign(Object.assign({},t),{content:n}))),{superType:t.type,component:n,rawOptions:t}}function Uo(e){return t=>p(Ln.Consumer,null,n=>p($n,{elTag:"div",elClasses:er(n.viewSpec),renderProps:Object.assign(Object.assign({},t),{nextDayThreshold:n.options.nextDayThreshold}),generatorName:void 0,customGenerator:e.content,classNameGenerator:e.classNames,didMount:e.didMount,willUnmount:e.willUnmount}))}function Lo(e,t,n,r){let i=jo(e),s=jo(t.views);return An(function(e,t){let n,r={};for(n in e)Bo(n,r,e,t);for(n in t)Bo(n,r,e,t);return r}(i,s),e=>function(e,t,n,r,i){let s=e.overrides.duration||e.defaults.duration||r.duration||n.duration,o=null,a="",l="",c={};if(s&&(o=function(e){let t=JSON.stringify(e),n=Wo[t];void 0===n&&(n=ft(e),Wo[t]=n);return n}(s),o)){let e=Et(o);a=e.unit,1===e.value&&(l=a,c=t[a]?t[a].rawOptions:{})}let d=t=>{let n=t.buttonText||{},r=e.defaults.buttonTextKey;return null!=r&&null!=n[r]?n[r]:null!=n[e.type]?n[e.type]:null!=n[l]?n[l]:null},u=t=>{let n=t.buttonHints||{},r=e.defaults.buttonTextKey;return null!=r&&null!=n[r]?n[r]:null!=n[e.type]?n[e.type]:null!=n[l]?n[l]:null};return{type:e.type,component:e.component,duration:o,durationUnit:a,singleUnit:l,optionDefaults:e.defaults,optionOverrides:Object.assign(Object.assign({},c),e.overrides),buttonTextOverride:d(r)||d(n)||e.overrides.buttonText,buttonTextDefault:d(i)||e.defaults.buttonText||d(cn)||e.type,buttonTitleOverride:u(r)||u(n)||e.overrides.buttonHint,buttonTitleDefault:u(i)||e.defaults.buttonHint||u(cn)}}(e,s,t,n,r))}Ho.prototype.classes={root:"fc-theme-standard",tableCellShaded:"fc-cell-shaded",buttonGroup:"fc-button-group",button:"fc-button fc-button-primary",buttonActive:"fc-button-active"},Ho.prototype.baseIconClass="fc-icon",Ho.prototype.iconClasses={close:"fc-icon-x",prev:"fc-icon-chevron-left",next:"fc-icon-chevron-right",prevYear:"fc-icon-chevrons-left",nextYear:"fc-icon-chevrons-right"},Ho.prototype.rtlIconClasses={prev:"fc-icon-chevron-right",next:"fc-icon-chevron-left",prevYear:"fc-icon-chevrons-right",nextYear:"fc-icon-chevrons-left"},Ho.prototype.iconOverrideOption="buttonIcons",Ho.prototype.iconOverrideCustomButtonOption="icon",Ho.prototype.iconOverridePrefix="fc-icon-";let Wo={};function Fo(e,t,n){let r=t?t.activeRange:null;return Qo({},function(e,t){let n=Br(t),r=[].concat(e.eventSources||[]),i=[];e.initialEvents&&r.unshift(e.initialEvents);e.events&&r.unshift(e.events);for(let e of r){let r=Hr(e,t,n);r&&i.push(r)}return i}(e,n),r,n)}function Vo(e,t,n,r){let i=n?n.activeRange:null;switch(t.type){case"ADD_EVENT_SOURCES":return Qo(e,t.sources,i,r);case"REMOVE_EVENT_SOURCE":return s=e,o=t.sourceId,Sn(s,e=>e.sourceId!==o);case"PREV":case"NEXT":case"CHANGE_DATE":case"CHANGE_VIEW_TYPE":return n?qo(e,i,r):e;case"FETCH_EVENT_SOURCES":return Yo(e,t.sourceIds?Dn(t.sourceIds):Xo(e,r),i,t.isRefetch||!1,r);case"RECEIVE_EVENTS":case"RECEIVE_EVENT_ERROR":return function(e,t,n,r){let i=e[t];if(i&&n===i.latestFetchId)return Object.assign(Object.assign({},e),{[t]:Object.assign(Object.assign({},i),{isFetching:!1,fetchRange:r})});return e}(e,t.sourceId,t.fetchId,t.fetchRange);case"REMOVE_ALL_EVENT_SOURCES":return{};default:return e}var s,o}function Go(e){for(let t in e)if(e[t].isFetching)return!0;return!1}function Qo(e,t,n,r){let i={};for(let e of t)i[e.sourceId]=e;return n&&(i=qo(i,n,r)),Object.assign(Object.assign({},e),i)}function qo(e,t,n){return Yo(e,Sn(e,e=>function(e,t,n){if(!$o(e,n))return!e.latestFetchId;return!n.options.lazyFetching||!e.fetchRange||e.isFetching||t.starte.fetchRange.end}(e,t,n)),t,!1,n)}function Yo(e,t,n,r,i){let s={};for(let o in e){let a=e[o];t[o]?s[o]=Zo(a,n,r,i):s[o]=a}return s}function Zo(e,t,n,r){let{options:i,calendarApi:s}=r,o=r.pluginHooks.eventSourceDefs[e.sourceDefId],a=Ze();return o.fetch({eventSource:e,range:t,isRefetch:n,context:r},n=>{let{rawEvents:o}=n;i.eventSourceSuccess&&(o=i.eventSourceSuccess.call(s,o,n.response)||o),e.success&&(o=e.success.call(s,o,n.response)||o),r.dispatch({type:"RECEIVE_EVENTS",sourceId:e.sourceId,fetchId:a,fetchRange:t,rawEvents:o})},n=>{let o=!1;i.eventSourceFailure&&(i.eventSourceFailure.call(s,n),o=!0),e.failure&&(e.failure(n),o=!0),o||console.warn(n.message,n),r.dispatch({type:"RECEIVE_EVENT_ERROR",sourceId:e.sourceId,fetchId:a,fetchRange:t,error:n})}),Object.assign(Object.assign({},e),{isFetching:!0,latestFetchId:a})}function Xo(e,t){return Sn(e,e=>$o(e,t))}function $o(e,t){return!t.pluginHooks.eventSourceDefs[e.sourceDefId].ignoreRange}function Jo(e,t){switch(t.type){case"UNSELECT_DATES":return null;case"SELECT_DATES":return t.selection;default:return e}}function Ko(e,t){switch(t.type){case"UNSELECT_EVENT":return"";case"SELECT_EVENT":return t.eventInstanceId;default:return e}}function ea(e,t){let n;switch(t.type){case"UNSET_EVENT_DRAG":return null;case"SET_EVENT_DRAG":return n=t.state,{affectedEvents:n.affectedEvents,mutatedEvents:n.mutatedEvents,isEvent:n.isEvent};default:return e}}function ta(e,t){let n;switch(t.type){case"UNSET_EVENT_RESIZE":return null;case"SET_EVENT_RESIZE":return n=t.state,{affectedEvents:n.affectedEvents,mutatedEvents:n.mutatedEvents,isEvent:n.isEvent};default:return e}}function na(e,t,n,r,i){return{header:e.headerToolbar?ra(e.headerToolbar,e,t,n,r,i):null,footer:e.footerToolbar?ra(e.footerToolbar,e,t,n,r,i):null}}function ra(e,t,n,r,i,s){let o={},a=[],l=!1;for(let c in e){let d=ia(e[c],t,n,r,i,s);o[c]=d.widgets,a.push(...d.viewsWithButtons),l=l||d.hasTitle}return{sectionWidgets:o,viewsWithButtons:a,hasTitle:l}}function ia(e,t,n,r,i,s){let o="rtl"===t.direction,a=t.customButtons||{},l=n.buttonText||{},c=t.buttonText||{},d=n.buttonHints||{},u=t.buttonHints||{},h=e?e.split(" "):[],f=[],g=!1;return{widgets:h.map(e=>e.split(",").map(e=>{if("title"===e)return g=!0,{buttonName:e};let n,h,p,m,v,y;if(n=a[e])p=e=>{n.click&&n.click.call(e.target,e,e.target)},(m=r.getCustomButtonIconClass(n))||(m=r.getIconClass(e,o))||(v=n.text),y=n.hint||n.text;else if(h=i[e]){f.push(e),p=()=>{s.changeView(e)},(v=h.buttonTextOverride)||(m=r.getIconClass(e,o))||(v=h.buttonTextDefault);let n=h.buttonTextOverride||h.buttonTextDefault;y=at(h.buttonTitleOverride||h.buttonTitleDefault||t.viewHint,[n,e],n)}else if(s[e])if(p=()=>{s[e]()},(v=l[e])||(m=r.getIconClass(e,o))||(v=c[e]),"prevYear"===e||"nextYear"===e){let t="prevYear"===e?"prev":"next";y=at(d[t]||u[t],[c.year||"year","year"],c[e])}else y=t=>at(d[e]||u[e],[c[t]||t,t],c[e]);return{buttonName:e,buttonClick:p,buttonIcon:m,buttonText:v,buttonHint:y}})),viewsWithButtons:f,hasTitle:g}}class sa{constructor(e,t,n){this.type=e,this.getCurrentData=t,this.dateEnv=n}get calendar(){return this.getCurrentData().calendarApi}get title(){return this.getCurrentData().viewTitle}get activeStart(){return this.dateEnv.toDate(this.getCurrentData().dateProfile.activeRange.start)}get activeEnd(){return this.dateEnv.toDate(this.getCurrentData().dateProfile.activeRange.end)}get currentStart(){return this.dateEnv.toDate(this.getCurrentData().dateProfile.currentRange.start)}get currentEnd(){return this.dateEnv.toDate(this.getCurrentData().dateProfile.currentRange.end)}getOption(e){return this.getCurrentData().options[e]}}function oa(e,t){let n=wn(t.getCurrentData().eventSources);if(1===n.length&&1===e.length&&Array.isArray(n[0]._raw)&&Array.isArray(e[0]))return void t.dispatch({type:"RESET_RAW_EVENTS",sourceId:n[0].sourceId,rawEvents:e[0]});let r=[];for(let t of e){let e=!1;for(let r=0;rArray.isArray(e.events)?e.events:null,fetch(e,t){t({rawEvents:e.eventSource.meta})}}]}),Oo({name:"func-event-source",eventSourceDefs:[{parseMeta:e=>"function"==typeof e.events?e.events:null,fetch(e,t,n){const{dateEnv:r}=e.context;Di(e.eventSource.meta.bind(null,Si(e.range,r)),e=>t({rawEvents:e}),n)}}]}),Oo({name:"json-event-source",eventSourceRefiners:{method:String,extraParams:yn,startParam:String,endParam:String,timeZoneParam:String},eventSourceDefs:[{parseMeta:e=>!e.url||"json"!==e.format&&e.format?null:{url:e.url,format:"json",method:(e.method||"GET").toUpperCase(),extraParams:e.extraParams,startParam:e.startParam,endParam:e.endParam,timeZoneParam:e.timeZoneParam},fetch(e,t,n){const{meta:r}=e.eventSource,i=function(e,t,n){let r,i,s,o,{dateEnv:a,options:l}=n,c={};r=e.startParam,null==r&&(r=l.startParam);i=e.endParam,null==i&&(i=l.endParam);s=e.timeZoneParam,null==s&&(s=l.timeZoneParam);o="function"==typeof e.extraParams?e.extraParams():e.extraParams||{};Object.assign(c,o),c[r]=a.formatIso(t.start),c[i]=a.formatIso(t.end),"local"!==a.timeZone&&(c[s]=a.timeZone);return c}(r,e.range,e.context);Ci(r.method,r.url,i).then(([e,n])=>{t({rawEvents:e,response:n})},n)}}]}),Oo({name:"simple-recurring-event",recurringTypes:[{parse(e,t){if(e.daysOfWeek||e.startTime||e.endTime||e.startRecur||e.endRecur){let i,s={daysOfWeek:e.daysOfWeek||null,startTime:e.startTime||null,endTime:e.endTime||null,startRecur:e.startRecur?t.createMarker(e.startRecur):null,endRecur:e.endRecur?t.createMarker(e.endRecur):null,dateEnv:t};return e.duration&&(i=e.duration),!i&&e.startTime&&e.endTime&&(n=e.endTime,r=e.startTime,i={years:n.years-r.years,months:n.months-r.months,days:n.days-r.days,milliseconds:n.milliseconds-r.milliseconds}),{allDayGuess:Boolean(!e.startTime&&!e.endTime),duration:i,typeData:s}}var n,r;return null},expand(e,t,n){let r=rr(t,{start:e.startRecur,end:e.endRecur});return r?function(e,t,n,r,i){let s=e?Dn(e):null,o=Mt(i.start),a=i.end,l=[];t&&(t.milliseconds<0?a=wt(a,1):t.milliseconds>=864e5&&(o=wt(o,-1)));for(;oGo(e.eventSources)],propSetHandlers:{dateProfile:function(e,t){t.emitter.trigger("datesSet",Object.assign(Object.assign({},Si(e.activeRange,t.dateEnv)),{view:t.viewApi}))},eventStore:function(e,t){let{emitter:n}=t;n.hasHandlers("eventsSet")&&n.trigger("eventsSet",ti(e,t))}}})];class la{constructor(e,t){this.runTaskOption=e,this.drainedOption=t,this.queue=[],this.delayedRunner=new Me(this.drain.bind(this))}request(e,t){this.queue.push(e),this.delayedRunner.request(t)}pause(e){this.delayedRunner.pause(e)}resume(e,t){this.delayedRunner.resume(e,t)}drain(){let{queue:e}=this;for(;e.length;){let t,n=[];for(;t=e.shift();)this.runTask(t),n.push(t);this.drained(n)}}runTask(e){this.runTaskOption&&this.runTaskOption(e)}drained(e){this.drainedOption&&this.drainedOption(e)}}function ca(e,t,n){let r;return r=/^(year|month)$/.test(e.currentRangeUnit)?e.currentRange:e.activeRange,n.formatRange(r.start,r.end,an(t.titleFormat||function(e){let{currentRangeUnit:t}=e;if("year"===t)return{year:"numeric"};if("month"===t)return{year:"numeric",month:"long"};let n=kt(e.currentRange.start,e.currentRange.end);if(null!==n&&n>1)return{year:"numeric",month:"short",day:"numeric"};return{year:"numeric",month:"long",day:"numeric"}}(e)),{isEndExclusive:e.isRangeAllDay,defaultSeparator:t.titleRangeSeparator})}class da{constructor(){this.resetListeners=new Set}handleInput(e,t){const n=this.dateEnv;if(e!==n&&("function"==typeof t?this.nowFn=t:n||(this.nowAnchorDate=e.toDate(t?e.createMarker(t):e.createNowMarker()),this.nowAnchorQueried=Date.now()),this.dateEnv=e,n))for(const e of this.resetListeners.values())e()}getDateMarker(){return this.nowAnchorDate?this.dateEnv.timestampToMarker(this.nowAnchorDate.valueOf()+(Date.now()-this.nowAnchorQueried)):this.dateEnv.createMarker(this.nowFn())}addResetListener(e){this.resetListeners.add(e)}removeResetListener(e){this.resetListeners.delete(e)}}class ua{constructor(e){this.computeCurrentViewData=Gt(this._computeCurrentViewData),this.organizeRawLocales=Gt(ko),this.buildLocale=Gt(Mo),this.buildPluginHooks=No(),this.buildDateEnv=Gt(ha),this.buildTheme=Gt(fa),this.parseToolbars=Gt(na),this.buildViewSpecs=Gt(Lo),this.buildDateProfileGenerator=Qt(ga),this.buildViewApi=Gt(pa),this.buildViewUiProps=Qt(ya),this.buildEventUiBySource=Gt(ma,Cn),this.buildEventUiBases=Gt(va),this.parseContextBusinessHours=Qt(Ea),this.buildTitle=Gt(ca),this.nowManager=new da,this.emitter=new Fr,this.actionRunner=new la(this._handleAction.bind(this),this.updateData.bind(this)),this.currentCalendarOptionsInput={},this.currentCalendarOptionsRefined={},this.currentViewOptionsInput={},this.currentViewOptionsRefined={},this.currentCalendarOptionsRefiners={},this.optionsForRefining=[],this.optionsForHandling=[],this.getCurrentData=()=>this.data,this.dispatch=e=>{this.actionRunner.request(e)},this.props=e,this.actionRunner.pause(),this.nowManager=new da;let t={},n=this.computeOptionsData(e.optionOverrides,t,e.calendarApi),r=n.calendarOptions.initialView||n.pluginHooks.initialView,i=this.computeCurrentViewData(r,n,e.optionOverrides,t);e.calendarApi.currentDataManager=this,this.emitter.setThisContext(e.calendarApi),this.emitter.setOptions(i.options);let s={nowManager:this.nowManager,dateEnv:n.dateEnv,options:n.calendarOptions,pluginHooks:n.pluginHooks,calendarApi:e.calendarApi,dispatch:this.dispatch,emitter:this.emitter,getCurrentData:this.getCurrentData},o=function(e,t,n){let r=e.initialDate;return null!=r?t.createMarker(r):n.getDateMarker()}(n.calendarOptions,n.dateEnv,this.nowManager),a=i.dateProfileGenerator.build(o);ar(a.activeRange,o)||(o=a.currentRange.start);for(let e of n.pluginHooks.contextInit)e(s);let l=Fo(n.calendarOptions,a,s),c={dynamicOptionOverrides:t,currentViewType:r,currentDate:o,dateProfile:a,businessHours:this.parseContextBusinessHours(s),eventSources:l,eventUiBases:{},eventStore:{defs:{},instances:{}},renderableEventStore:{defs:{},instances:{}},dateSelection:null,eventSelection:"",eventDrag:null,eventResize:null,selectionConfig:this.buildViewUiProps(s).selectionConfig},d=Object.assign(Object.assign({},s),c);for(let e of n.pluginHooks.reducers)Object.assign(c,e(null,null,d));ba(c,s)&&this.emitter.trigger("loading",!0),this.state=c,this.updateData(),this.actionRunner.resume()}resetOptions(e,t){let{props:n}=this;void 0===t?n.optionOverrides=e:(n.optionOverrides=Object.assign(Object.assign({},n.optionOverrides||{}),e),this.optionsForRefining.push(...t)),(void 0===t||t.length)&&this.actionRunner.request({type:"NOTHING"})}_handleAction(e){let{props:t,state:n,emitter:r}=this,i=function(e,t){switch(t.type){case"SET_OPTION":return Object.assign(Object.assign({},e),{[t.optionName]:t.rawOptionValue});default:return e}}(n.dynamicOptionOverrides,e),s=this.computeOptionsData(t.optionOverrides,i,t.calendarApi),o=function(e,t){switch(t.type){case"CHANGE_VIEW_TYPE":e=t.viewType}return e}(n.currentViewType,e),a=this.computeCurrentViewData(o,s,t.optionOverrides,i);t.calendarApi.currentDataManager=this,r.setThisContext(t.calendarApi),r.setOptions(a.options);let l={nowManager:this.nowManager,dateEnv:s.dateEnv,options:s.calendarOptions,pluginHooks:s.pluginHooks,calendarApi:t.calendarApi,dispatch:this.dispatch,emitter:r,getCurrentData:this.getCurrentData},{currentDate:c,dateProfile:d}=n;this.data&&this.data.dateProfileGenerator!==a.dateProfileGenerator&&(d=a.dateProfileGenerator.build(c)),c=function(e,t){switch(t.type){case"CHANGE_DATE":return t.dateMarker;default:return e}}(c,e),d=function(e,t,n,r){let i;switch(t.type){case"CHANGE_VIEW_TYPE":return r.build(t.dateMarker||n);case"CHANGE_DATE":return r.build(t.dateMarker);case"PREV":if(i=r.buildPrev(e,n),i.isValid)return i;break;case"NEXT":if(i=r.buildNext(e,n),i.isValid)return i}return e}(d,e,c,a.dateProfileGenerator),"PREV"!==e.type&&"NEXT"!==e.type&&ar(d.currentRange,c)||(c=d.currentRange.start);let u=Vo(n.eventSources,e,d,l),h=jr(n.eventStore,e,u,d,l),f=Go(u)&&!a.options.progressiveEventRendering&&n.renderableEventStore||h,{eventUiSingleBase:g,selectionConfig:p}=this.buildViewUiProps(l),m=this.buildEventUiBySource(u),v={dynamicOptionOverrides:i,currentViewType:o,currentDate:c,dateProfile:d,eventSources:u,eventStore:h,renderableEventStore:f,selectionConfig:p,eventUiBases:this.buildEventUiBases(f.defs,g,m),businessHours:this.parseContextBusinessHours(l),dateSelection:Jo(n.dateSelection,e),eventSelection:Ko(n.eventSelection,e),eventDrag:ea(n.eventDrag,e),eventResize:ta(n.eventResize,e)},y=Object.assign(Object.assign({},l),v);for(let t of s.pluginHooks.reducers)Object.assign(v,t(n,e,y));let b=ba(n,l),E=ba(v,l);!b&&E?r.trigger("loading",!0):b&&!E&&r.trigger("loading",!1),this.state=v,t.onAction&&t.onAction(e)}updateData(){let{props:e,state:t}=this,n=this.data,r=this.computeOptionsData(e.optionOverrides,t.dynamicOptionOverrides,e.calendarApi),i=this.computeCurrentViewData(t.currentViewType,r,e.optionOverrides,t.dynamicOptionOverrides),s=this.data=Object.assign(Object.assign(Object.assign({nowManager:this.nowManager,viewTitle:this.buildTitle(t.dateProfile,i.options,r.dateEnv),calendarApi:e.calendarApi,dispatch:this.dispatch,emitter:this.emitter,getCurrentData:this.getCurrentData},r),i),t),o=r.pluginHooks.optionChangeHandlers,a=n&&n.calendarOptions,l=r.calendarOptions;if(a&&a!==l){a.timeZone!==l.timeZone&&(t.eventSources=s.eventSources=function(e,t,n){let r=t?t.activeRange:null;return Yo(e,Xo(e,n),r,!0,n)}(s.eventSources,t.dateProfile,s),t.eventStore=s.eventStore=Lr(s.eventStore,n.dateEnv,s.dateEnv),t.renderableEventStore=s.renderableEventStore=Lr(s.renderableEventStore,n.dateEnv,s.dateEnv));for(let e in o)-1===this.optionsForHandling.indexOf(e)&&a[e]===l[e]||o[e](l[e],s)}this.optionsForHandling=[],e.onData&&e.onData(s)}computeOptionsData(e,t,n){if(!this.optionsForRefining.length&&e===this.stableOptionOverrides&&t===this.stableDynamicOptionOverrides)return this.stableCalendarOptionsData;let{refinedOptions:r,pluginHooks:i,localeDefaults:s,availableLocaleData:o,extra:a}=this.processRawCalendarOptions(e,t);Sa(a);let l=this.buildDateEnv(r.timeZone,r.locale,r.weekNumberCalculation,r.firstDay,r.weekText,i,o,r.defaultRangeSeparator),c=this.buildViewSpecs(i.views,this.stableOptionOverrides,this.stableDynamicOptionOverrides,s),d=this.buildTheme(r,i),u=this.parseToolbars(r,this.stableOptionOverrides,d,c,n);return this.stableCalendarOptionsData={calendarOptions:r,pluginHooks:i,dateEnv:l,viewSpecs:c,theme:d,toolbarConfig:u,localeDefaults:s,availableRawLocales:o.map}}processRawCalendarOptions(e,t){let{locales:n,locale:r}=mn([cn,e,t]),i=this.organizeRawLocales(n),s=i.map,o=this.buildLocale(r||i.defaultCode,s).options,a=this.buildPluginHooks(e.plugins||[],aa),l=this.currentCalendarOptionsRefiners=Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({},ln),dn),un),a.listenerRefiners),a.optionRefiners),c={},d=mn([cn,o,e,t]),u={},h=this.currentCalendarOptionsInput,f=this.currentCalendarOptionsRefined,g=!1;for(let e in d)-1===this.optionsForRefining.indexOf(e)&&(d[e]===h[e]||hn[e]&&e in h&&hn[e](h[e],d[e]))?u[e]=f[e]:l[e]?(u[e]=l[e](d[e]),g=!0):c[e]=h[e];return g&&(this.currentCalendarOptionsInput=d,this.currentCalendarOptionsRefined=u,this.stableOptionOverrides=e,this.stableDynamicOptionOverrides=t),this.optionsForHandling.push(...this.optionsForRefining),this.optionsForRefining=[],{rawOptions:this.currentCalendarOptionsInput,refinedOptions:this.currentCalendarOptionsRefined,pluginHooks:a,availableLocaleData:i,localeDefaults:o,extra:c}}_computeCurrentViewData(e,t,n,r){let i=t.viewSpecs[e];if(!i)throw new Error(`viewType "${e}" is not available. Please make sure you've loaded all neccessary plugins`);let{refinedOptions:s,extra:o}=this.processRawViewOptions(i,t.pluginHooks,t.localeDefaults,n,r);return Sa(o),this.nowManager.handleInput(t.dateEnv,s.now),{viewSpec:i,options:s,dateProfileGenerator:this.buildDateProfileGenerator({dateProfileGeneratorClass:i.optionDefaults.dateProfileGeneratorClass,nowManager:this.nowManager,duration:i.duration,durationUnit:i.durationUnit,usesMinMaxTime:i.optionDefaults.usesMinMaxTime,dateEnv:t.dateEnv,calendarApi:this.props.calendarApi,slotMinTime:s.slotMinTime,slotMaxTime:s.slotMaxTime,showNonCurrentDates:s.showNonCurrentDates,dayCount:s.dayCount,dateAlignment:s.dateAlignment,dateIncrement:s.dateIncrement,hiddenDays:s.hiddenDays,weekends:s.weekends,validRangeInput:s.validRange,visibleRangeInput:s.visibleRange,fixedWeekCount:s.fixedWeekCount}),viewApi:this.buildViewApi(e,this.getCurrentData,t.dateEnv)}}processRawViewOptions(e,t,n,r,i){let s=mn([cn,e.optionDefaults,n,r,e.optionOverrides,i]),o=Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({},ln),dn),un),pn),t.listenerRefiners),t.optionRefiners),a={},l=this.currentViewOptionsInput,c=this.currentViewOptionsRefined,d=!1,u={};for(let e in s)s[e]===l[e]||hn[e]&&hn[e](s[e],l[e])?a[e]=c[e]:(s[e]===this.currentCalendarOptionsInput[e]||hn[e]&&hn[e](s[e],this.currentCalendarOptionsInput[e])?e in this.currentCalendarOptionsRefined&&(a[e]=this.currentCalendarOptionsRefined[e]):o[e]?a[e]=o[e](s[e]):u[e]=s[e],d=!0);return d&&(this.currentViewOptionsInput=s,this.currentViewOptionsRefined=a),{rawOptions:this.currentViewOptionsInput,refinedOptions:this.currentViewOptionsRefined,extra:u}}}function ha(e,t,n,r,i,s,o,a){let l=Mo(t||o.defaultCode,o.map);return new Pn({calendarSystem:"gregory",timeZone:e,namedTimeZoneImpl:s.namedTimeZonedImpl,locale:l,weekNumberCalculation:n,firstDay:r,weekText:i,cmdFormatter:s.cmdFormatter,defaultSeparator:a})}function fa(e,t){return new(t.themeClasses[e.themeSystem]||Ho)(e)}function ga(e){return new(e.dateProfileGeneratorClass||hr)(e)}function pa(e,t,n){return new sa(e,t,n)}function ma(e){return An(e,e=>e.ui)}function va(e,t,n){let r={"":t};for(let t in e){let i=e[t];i.sourceId&&n[i.sourceId]&&(r[t]=n[i.sourceId])}return r}function ya(e){let{options:t}=e;return{eventUiSingleBase:Ir({display:t.eventDisplay,editable:t.editable,startEditable:t.eventStartEditable,durationEditable:t.eventDurationEditable,constraint:t.eventConstraint,overlap:"boolean"==typeof t.eventOverlap?t.eventOverlap:void 0,allow:t.eventAllow,backgroundColor:t.eventBackgroundColor,borderColor:t.eventBorderColor,textColor:t.eventTextColor,color:t.eventColor},e),selectionConfig:Ir({constraint:t.selectConstraint,overlap:"boolean"==typeof t.selectOverlap?t.selectOverlap:void 0,allow:t.selectAllow},e)}}function ba(e,t){for(let n of t.pluginHooks.isLoadingFuncs)if(n(e))return!0;return!1}function Ea(e){return Gr(e.options.businessHours,e)}function Sa(e,t){for(let n in e)console.warn(`Unknown option '${n}'`+(t?` for view '${t}'`:""))}class Aa extends Vn{render(){return p("div",{className:"fc-toolbar-chunk"},...this.props.widgetGroups.map(e=>this.renderWidgetGroup(e)))}renderWidgetGroup(e){let{props:t}=this,{theme:n}=this.context,r=[],i=!0;for(let s of e){let{buttonName:e,buttonClick:o,buttonText:a,buttonIcon:l,buttonHint:c}=s;if("title"===e)i=!1,r.push(p("h2",{className:"fc-toolbar-title",id:t.titleId},t.title));else{let i=e===t.activeButton,s=!t.isTodayEnabled&&"today"===e||!t.isPrevEnabled&&"prev"===e||!t.isNextEnabled&&"next"===e,d=[`fc-${e}-button`,n.getClass("button")];i&&d.push(n.getClass("buttonActive")),r.push(p("button",{type:"button",title:"function"==typeof c?c(t.navUnit):c,disabled:s,"aria-pressed":i,className:d.join(" "),onClick:o},a||(l?p("span",{className:l,role:"img"}):"")))}}if(r.length>1){return p("div",{className:i&&n.getClass("buttonGroup")||""},...r)}return r[0]}}class Da extends Vn{render(){let e,t,{model:n,extraClassName:r}=this.props,i=!1,s=n.sectionWidgets,o=s.center;return s.left?(i=!0,e=s.left):e=s.start,s.right?(i=!0,t=s.right):t=s.end,p("div",{className:[r||"","fc-toolbar",i?"fc-toolbar-ltr":""].join(" ")},this.renderSection("start",e||[]),this.renderSection("center",o||[]),this.renderSection("end",t||[]))}renderSection(e,t){let{props:n}=this;return p(Aa,{key:e,widgetGroups:t,title:n.title,navUnit:n.navUnit,activeButton:n.activeButton,isTodayEnabled:n.isTodayEnabled,isPrevEnabled:n.isPrevEnabled,isNextEnabled:n.isNextEnabled,titleId:n.titleId})}}class wa extends Vn{constructor(){super(...arguments),this.state={availableWidth:null},this.handleEl=e=>{this.el=e,Gn(this.props.elRef,e),this.updateAvailableWidth()},this.handleResize=()=>{this.updateAvailableWidth()}}render(){let{props:e,state:t}=this,{aspectRatio:n}=e,r=["fc-view-harness",n||e.liquid||e.height?"fc-view-harness-active":"fc-view-harness-passive"],i="",s="";return n?null!==t.availableWidth?i=t.availableWidth/n:s=1/n*100+"%":i=e.height||"",p("div",{"aria-labelledby":e.labeledById,ref:this.handleEl,className:r.join(" "),style:{height:i,paddingBottom:s}},e.children)}componentDidMount(){this.context.addResizeHandler(this.handleResize)}componentWillUnmount(){this.context.removeResizeHandler(this.handleResize)}updateAvailableWidth(){this.el&&this.props.aspectRatio&&this.setState({availableWidth:this.el.offsetWidth})}}class Ca extends Ti{constructor(e){super(e),this.handleSegClick=(e,t)=>{let{component:n}=this,{context:r}=n,i=si(t);if(i&&n.isValidSegDownEl(e.target)){let s=Oe(e.target,".fc-event-forced-url"),o=s?s.querySelector("a[href]").href:"";r.emitter.trigger("eventClick",{el:t,event:new Kr(n.context,i.eventRange.def,i.eventRange.instance),jsEvent:e,view:r.viewApi}),o&&!e.defaultPrevented&&(window.location.href=o)}},this.destroy=Fe(e.el,"click",".fc-event",this.handleSegClick)}}class Ra extends Ti{constructor(e){super(e),this.handleEventElRemove=e=>{e===this.currentSegEl&&this.handleSegLeave(null,this.currentSegEl)},this.handleSegEnter=(e,t)=>{si(t)&&(this.currentSegEl=t,this.triggerEvent("eventMouseEnter",e,t))},this.handleSegLeave=(e,t)=>{this.currentSegEl&&(this.currentSegEl=null,this.triggerEvent("eventMouseLeave",e,t))},this.removeHoverListeners=function(e,t,n,r){let i;return Fe(e,"mouseover",t,(e,t)=>{if(t!==i){i=t,n(e,t);let s=e=>{i=null,r(e,t),t.removeEventListener("mouseleave",s)};t.addEventListener("mouseleave",s)}})}(e.el,".fc-event",this.handleSegEnter,this.handleSegLeave)}destroy(){this.removeHoverListeners()}triggerEvent(e,t,n){let{component:r}=this,{context:i}=r,s=si(n);t&&!r.isValidSegDownEl(t.target)||i.emitter.trigger(e,{el:n,event:new Kr(i,s.eventRange.def,s.eventRange.instance),jsEvent:t,view:i.viewApi})}}class xa extends Fn{constructor(){super(...arguments),this.buildViewContext=Gt(Wn),this.buildViewPropTransformers=Gt(Ta),this.buildToolbarProps=Gt(_a),this.headerRef={current:null},this.footerRef={current:null},this.interactionsStore={},this.state={viewLabelId:Le()},this.registerInteractiveComponent=(e,t)=>{let n=function(e,t){return{component:e,el:t.el,useEventCenter:null==t.useEventCenter||t.useEventCenter,isHitComboAllowed:t.isHitComboAllowed||null}}(e,t),r=[Ca,Ra].concat(this.props.pluginHooks.componentInteractions).map(e=>new e(n));this.interactionsStore[e.uid]=r,Mi[e.uid]=n},this.unregisterInteractiveComponent=e=>{let t=this.interactionsStore[e.uid];if(t){for(let e of t)e.destroy();delete this.interactionsStore[e.uid]}delete Mi[e.uid]},this.resizeRunner=new Me(()=>{this.props.emitter.trigger("_resize",!0),this.props.emitter.trigger("windowResize",{view:this.props.viewApi})}),this.handleWindowResize=e=>{let{options:t}=this.props;t.handleWindowResize&&e.target===window&&this.resizeRunner.request(t.windowResizeDelay)}}render(){let e,{props:t}=this,{toolbarConfig:n,options:r}=t,i=!1,s="";t.isHeightAuto||t.forPrint?s="":null!=r.height?i=!0:null!=r.contentHeight?s=r.contentHeight:e=Math.max(r.aspectRatio,.5);let o=this.buildViewContext(t.viewSpec,t.viewApi,t.options,t.dateProfileGenerator,t.dateEnv,t.nowManager,t.theme,t.pluginHooks,t.dispatch,t.getCurrentData,t.emitter,t.calendarApi,this.registerInteractiveComponent,this.unregisterInteractiveComponent),a=n.header&&n.header.hasTitle?this.state.viewLabelId:void 0;return p(Ln.Provider,{value:o},p(Ii,{unit:"day"},r=>{let o=this.buildToolbarProps(t.viewSpec,t.dateProfile,t.dateProfileGenerator,t.currentDate,r,t.viewTitle);return p(y,null,n.header&&p(Da,Object.assign({ref:this.headerRef,extraClassName:"fc-header-toolbar",model:n.header,titleId:a},o)),p(wa,{liquid:i,height:s,aspectRatio:e,labeledById:a},this.renderView(t),this.buildAppendContent()),n.footer&&p(Da,Object.assign({ref:this.footerRef,extraClassName:"fc-footer-toolbar",model:n.footer,titleId:""},o)))}))}componentDidMount(){let{props:e}=this;this.calendarInteractions=e.pluginHooks.calendarInteractions.map(t=>new t(e)),window.addEventListener("resize",this.handleWindowResize);let{propSetHandlers:t}=e.pluginHooks;for(let n in t)t[n](e[n],e)}componentDidUpdate(e){let{props:t}=this,{propSetHandlers:n}=t.pluginHooks;for(let r in n)t[r]!==e[r]&&n[r](t[r],t)}componentWillUnmount(){window.removeEventListener("resize",this.handleWindowResize),this.resizeRunner.clear();for(let e of this.calendarInteractions)e.destroy();this.props.emitter.trigger("_unmount")}buildAppendContent(){let{props:e}=this;return p(y,{},...e.pluginHooks.viewContainerAppends.map(t=>t(e)))}renderView(e){let{pluginHooks:t}=e,{viewSpec:n}=e,r={dateProfile:e.dateProfile,businessHours:e.businessHours,eventStore:e.renderableEventStore,eventUiBases:e.eventUiBases,dateSelection:e.dateSelection,eventSelection:e.eventSelection,eventDrag:e.eventDrag,eventResize:e.eventResize,isHeightAuto:e.isHeightAuto,forPrint:e.forPrint},i=this.buildViewPropTransformers(t.viewPropsTransformers);for(let t of i)Object.assign(r,t.transform(r,e));return p(n.component,Object.assign({},r))}}function _a(e,t,n,r,i,s){let o=n.build(i,void 0,!1),a=n.buildPrev(t,r,!1),l=n.buildNext(t,r,!1);return{title:s,activeButton:e.type,navUnit:e.singleUnit,isTodayEnabled:o.isValid&&!ar(t.currentRange,i),isPrevEnabled:a.isValid,isNextEnabled:l.isValid}}function Ta(e){return e.map(e=>new e)}function ka(e){let t=Mo(e.locale||"en",ko([]).map);return new Pn(Object.assign(Object.assign({timeZone:cn.timeZone,calendarSystem:"gregory"},e),{locale:t}))}vs.touchMouseIgnoreWait=500;let Ma=0,Ia=0,Oa=!1;class Na{constructor(e){this.subjectEl=null,this.selector="",this.handleSelector="",this.shouldIgnoreMove=!1,this.shouldWatchScroll=!0,this.isDragging=!1,this.isTouchDragging=!1,this.wasTouchScroll=!1,this.handleMouseDown=e=>{if(!this.shouldIgnoreMouse()&&function(e){return 0===e.button&&!e.ctrlKey}(e)&&this.tryStart(e)){let t=this.createEventFromMouse(e,!0);this.emitter.trigger("pointerdown",t),this.initScrollWatch(t),this.shouldIgnoreMove||document.addEventListener("mousemove",this.handleMouseMove),document.addEventListener("mouseup",this.handleMouseUp)}},this.handleMouseMove=e=>{let t=this.createEventFromMouse(e);this.recordCoords(t),this.emitter.trigger("pointermove",t)},this.handleMouseUp=e=>{document.removeEventListener("mousemove",this.handleMouseMove),document.removeEventListener("mouseup",this.handleMouseUp),this.emitter.trigger("pointerup",this.createEventFromMouse(e)),this.cleanup()},this.handleTouchStart=e=>{if(this.tryStart(e)){this.isTouchDragging=!0;let t=this.createEventFromTouch(e,!0);this.emitter.trigger("pointerdown",t),this.initScrollWatch(t);let n=e.target;this.shouldIgnoreMove||n.addEventListener("touchmove",this.handleTouchMove),n.addEventListener("touchend",this.handleTouchEnd),n.addEventListener("touchcancel",this.handleTouchEnd),window.addEventListener("scroll",this.handleTouchScroll,!0)}},this.handleTouchMove=e=>{let t=this.createEventFromTouch(e);this.recordCoords(t),this.emitter.trigger("pointermove",t)},this.handleTouchEnd=e=>{if(this.isDragging){let t=e.target;t.removeEventListener("touchmove",this.handleTouchMove),t.removeEventListener("touchend",this.handleTouchEnd),t.removeEventListener("touchcancel",this.handleTouchEnd),window.removeEventListener("scroll",this.handleTouchScroll,!0),this.emitter.trigger("pointerup",this.createEventFromTouch(e)),this.cleanup(),this.isTouchDragging=!1,Ma+=1,setTimeout(()=>{Ma-=1},vs.touchMouseIgnoreWait)}},this.handleTouchScroll=()=>{this.wasTouchScroll=!0},this.handleScroll=e=>{if(!this.shouldIgnoreMove){let t=window.scrollX-this.prevScrollX+this.prevPageX,n=window.scrollY-this.prevScrollY+this.prevPageY;this.emitter.trigger("pointermove",{origEvent:e,isTouch:this.isTouchDragging,subjectEl:this.subjectEl,pageX:t,pageY:n,deltaX:t-this.origPageX,deltaY:n-this.origPageY})}},this.containerEl=e,this.emitter=new Fr,e.addEventListener("mousedown",this.handleMouseDown),e.addEventListener("touchstart",this.handleTouchStart,{passive:!0}),Ia+=1,1===Ia&&window.addEventListener("touchmove",Pa,{passive:!1})}destroy(){this.containerEl.removeEventListener("mousedown",this.handleMouseDown),this.containerEl.removeEventListener("touchstart",this.handleTouchStart,{passive:!0}),Ia-=1,Ia||window.removeEventListener("touchmove",Pa,{passive:!1})}tryStart(e){let t=this.querySubjectEl(e),n=e.target;return!(!t||this.handleSelector&&!Oe(n,this.handleSelector))&&(this.subjectEl=t,this.isDragging=!0,this.wasTouchScroll=!1,!0)}cleanup(){Oa=!1,this.isDragging=!1,this.subjectEl=null,this.destroyScrollWatch()}querySubjectEl(e){return this.selector?Oe(e.target,this.selector):this.containerEl}shouldIgnoreMouse(){return Ma||this.isTouchDragging}cancelTouchScroll(){this.isDragging&&(Oa=!0)}initScrollWatch(e){this.shouldWatchScroll&&(this.recordCoords(e),window.addEventListener("scroll",this.handleScroll,!0))}recordCoords(e){this.shouldWatchScroll&&(this.prevPageX=e.pageX,this.prevPageY=e.pageY,this.prevScrollX=window.scrollX,this.prevScrollY=window.scrollY)}destroyScrollWatch(){this.shouldWatchScroll&&window.removeEventListener("scroll",this.handleScroll,!0)}createEventFromMouse(e,t){let n=0,r=0;return t?(this.origPageX=e.pageX,this.origPageY=e.pageY):(n=e.pageX-this.origPageX,r=e.pageY-this.origPageY),{origEvent:e,isTouch:!1,subjectEl:this.subjectEl,pageX:e.pageX,pageY:e.pageY,deltaX:n,deltaY:r}}createEventFromTouch(e,t){let n,r,i=e.touches,s=0,o=0;return i&&i.length?(n=i[0].pageX,r=i[0].pageY):(n=e.pageX,r=e.pageY),t?(this.origPageX=n,this.origPageY=r):(s=n-this.origPageX,o=r-this.origPageY),{origEvent:e,isTouch:!0,subjectEl:this.subjectEl,pageX:n,pageY:r,deltaX:s,deltaY:o}}}function Pa(e){Oa&&e.preventDefault()}class Ha{constructor(){this.isVisible=!1,this.sourceEl=null,this.mirrorEl=null,this.sourceElRect=null,this.parentNode=document.body,this.zIndex=9999,this.revertDuration=0}start(e,t,n){this.sourceEl=e,this.sourceElRect=this.sourceEl.getBoundingClientRect(),this.origScreenX=t-window.scrollX,this.origScreenY=n-window.scrollY,this.deltaX=0,this.deltaY=0,this.updateElPosition()}handleMove(e,t){this.deltaX=e-window.scrollX-this.origScreenX,this.deltaY=t-window.scrollY-this.origScreenY,this.updateElPosition()}setIsVisible(e){e?this.isVisible||(this.mirrorEl&&(this.mirrorEl.style.display=""),this.isVisible=e,this.updateElPosition()):this.isVisible&&(this.mirrorEl&&(this.mirrorEl.style.display="none"),this.isVisible=e)}stop(e,t){let n=()=>{this.cleanup(),t()};e&&this.mirrorEl&&this.isVisible&&this.revertDuration&&(this.deltaX||this.deltaY)?this.doRevertAnimation(n,this.revertDuration):setTimeout(n,0)}doRevertAnimation(e,t){let n=this.mirrorEl,r=this.sourceEl.getBoundingClientRect();n.style.transition="top "+t+"ms,left "+t+"ms",Be(n,{left:r.left,top:r.top}),Ge(n,()=>{n.style.transition="",e()})}cleanup(){this.mirrorEl&&(Ie(this.mirrorEl),this.mirrorEl=null),this.sourceEl=null}updateElPosition(){this.sourceEl&&this.isVisible&&Be(this.getMirrorEl(),{left:this.sourceElRect.left+this.deltaX,top:this.sourceElRect.top+this.deltaY})}getMirrorEl(){let e=this.sourceElRect,t=this.mirrorEl;return t||(t=this.mirrorEl=this.sourceEl.cloneNode(!0),t.style.userSelect="none",t.style.webkitUserSelect="none",t.style.pointerEvents="none",t.classList.add("fc-event-dragging"),Be(t,{position:"fixed",zIndex:this.zIndex,visibility:"",boxSizing:"border-box",width:e.right-e.left,height:e.bottom-e.top,right:"auto",bottom:"auto",margin:0}),this.parentNode.appendChild(t)),t}}class Ba extends ss{constructor(e,t){super(),this.handleScroll=()=>{this.scrollTop=this.scrollController.getScrollTop(),this.scrollLeft=this.scrollController.getScrollLeft(),this.handleScrollChange()},this.scrollController=e,this.doesListening=t,this.scrollTop=this.origScrollTop=e.getScrollTop(),this.scrollLeft=this.origScrollLeft=e.getScrollLeft(),this.scrollWidth=e.getScrollWidth(),this.scrollHeight=e.getScrollHeight(),this.clientWidth=e.getClientWidth(),this.clientHeight=e.getClientHeight(),this.clientRect=this.computeClientRect(),this.doesListening&&this.getEventTarget().addEventListener("scroll",this.handleScroll)}destroy(){this.doesListening&&this.getEventTarget().removeEventListener("scroll",this.handleScroll)}getScrollTop(){return this.scrollTop}getScrollLeft(){return this.scrollLeft}setScrollTop(e){this.scrollController.setScrollTop(e),this.doesListening||(this.scrollTop=Math.max(Math.min(e,this.getMaxScrollTop()),0),this.handleScrollChange())}setScrollLeft(e){this.scrollController.setScrollLeft(e),this.doesListening||(this.scrollLeft=Math.max(Math.min(e,this.getMaxScrollLeft()),0),this.handleScrollChange())}getClientWidth(){return this.clientWidth}getClientHeight(){return this.clientHeight}getScrollWidth(){return this.scrollWidth}getScrollHeight(){return this.scrollHeight}handleScrollChange(){}}class ja extends Ba{constructor(e,t){super(new os(e),t)}getEventTarget(){return this.scrollController.el}computeClientRect(){return es(this.scrollController.el)}}class za extends Ba{constructor(e){super(new as,e)}getEventTarget(){return window}computeClientRect(){return{left:this.scrollLeft,right:this.scrollLeft+this.clientWidth,top:this.scrollTop,bottom:this.scrollTop+this.clientHeight}}handleScrollChange(){this.clientRect=this.computeClientRect()}}const Ua="function"==typeof performance?performance.now:Date.now;class La{constructor(){this.isEnabled=!0,this.scrollQuery=[window,".fc-scroller"],this.edgeThreshold=50,this.maxVelocity=300,this.pointerScreenX=null,this.pointerScreenY=null,this.isAnimating=!1,this.scrollCaches=null,this.everMovedUp=!1,this.everMovedDown=!1,this.everMovedLeft=!1,this.everMovedRight=!1,this.animate=()=>{if(this.isAnimating){let e=this.computeBestEdge(this.pointerScreenX+window.scrollX,this.pointerScreenY+window.scrollY);if(e){let t=Ua();this.handleSide(e,(t-this.msSinceRequest)/1e3),this.requestAnimation(t)}else this.isAnimating=!1}}}start(e,t,n){this.isEnabled&&(this.scrollCaches=this.buildCaches(n),this.pointerScreenX=null,this.pointerScreenY=null,this.everMovedUp=!1,this.everMovedDown=!1,this.everMovedLeft=!1,this.everMovedRight=!1,this.handleMove(e,t))}handleMove(e,t){if(this.isEnabled){let n=e-window.scrollX,r=t-window.scrollY,i=null===this.pointerScreenY?0:r-this.pointerScreenY,s=null===this.pointerScreenX?0:n-this.pointerScreenX;i<0?this.everMovedUp=!0:i>0&&(this.everMovedDown=!0),s<0?this.everMovedLeft=!0:s>0&&(this.everMovedRight=!0),this.pointerScreenX=n,this.pointerScreenY=r,this.isAnimating||(this.isAnimating=!0,this.requestAnimation(Ua()))}}stop(){if(this.isEnabled){this.isAnimating=!1;for(let e of this.scrollCaches)e.destroy();this.scrollCaches=null}}requestAnimation(e){this.msSinceRequest=e,requestAnimationFrame(this.animate)}handleSide(e,t){let{scrollCache:n}=e,{edgeThreshold:r}=this,i=r-e.distance,s=i*i/(r*r)*this.maxVelocity*t,o=1;switch(e.name){case"left":o=-1;case"right":n.setScrollLeft(n.getScrollLeft()+s*o);break;case"top":o=-1;case"bottom":n.setScrollTop(n.getScrollTop()+s*o)}}computeBestEdge(e,t){let{edgeThreshold:n}=this,r=null,i=this.scrollCaches||[];for(let s of i){let i=s.clientRect,o=e-i.left,a=i.right-e,l=t-i.top,c=i.bottom-t;o>=0&&a>=0&&l>=0&&c>=0&&(l<=n&&this.everMovedUp&&s.canScrollUp()&&(!r||r.distance>l)&&(r={scrollCache:s,name:"top",distance:l}),c<=n&&this.everMovedDown&&s.canScrollDown()&&(!r||r.distance>c)&&(r={scrollCache:s,name:"bottom",distance:c}),o<=n&&this.everMovedLeft&&s.canScrollLeft()&&(!r||r.distance>o)&&(r={scrollCache:s,name:"left",distance:o}),a<=n&&this.everMovedRight&&s.canScrollRight()&&(!r||r.distance>a)&&(r={scrollCache:s,name:"right",distance:a}))}return r}buildCaches(e){return this.queryScrollEls(e).map(e=>e===window?new za(!1):new ja(e,!1))}queryScrollEls(e){let t=[];for(let n of this.scrollQuery)"object"==typeof n?t.push(n):t.push(...Array.prototype.slice.call(e.getRootNode().querySelectorAll(n)));return t}}class Wa extends ms{constructor(e,t){super(e),this.containerEl=e,this.delay=null,this.minDistance=0,this.touchScrollAllowed=!0,this.mirrorNeedsRevert=!1,this.isInteracting=!1,this.isDragging=!1,this.isDelayEnded=!1,this.isDistanceSurpassed=!1,this.delayTimeoutId=null,this.onPointerDown=e=>{this.isDragging||(this.isInteracting=!0,this.isDelayEnded=!1,this.isDistanceSurpassed=!1,Je(document.body),et(document.body),e.isTouch||e.origEvent.preventDefault(),this.emitter.trigger("pointerdown",e),this.isInteracting&&!this.pointer.shouldIgnoreMove&&(this.mirror.setIsVisible(!1),this.mirror.start(e.subjectEl,e.pageX,e.pageY),this.startDelay(e),this.minDistance||this.handleDistanceSurpassed(e)))},this.onPointerMove=e=>{if(this.isInteracting){if(this.emitter.trigger("pointermove",e),!this.isDistanceSurpassed){let t,n=this.minDistance,{deltaX:r,deltaY:i}=e;t=r*r+i*i,t>=n*n&&this.handleDistanceSurpassed(e)}this.isDragging&&("scroll"!==e.origEvent.type&&(this.mirror.handleMove(e.pageX,e.pageY),this.autoScroller.handleMove(e.pageX,e.pageY)),this.emitter.trigger("dragmove",e))}},this.onPointerUp=e=>{this.isInteracting&&(this.isInteracting=!1,Ke(document.body),tt(document.body),this.emitter.trigger("pointerup",e),this.isDragging&&(this.autoScroller.stop(),this.tryStopDrag(e)),this.delayTimeoutId&&(clearTimeout(this.delayTimeoutId),this.delayTimeoutId=null))};let n=this.pointer=new Na(e);n.emitter.on("pointerdown",this.onPointerDown),n.emitter.on("pointermove",this.onPointerMove),n.emitter.on("pointerup",this.onPointerUp),t&&(n.selector=t),this.mirror=new Ha,this.autoScroller=new La}destroy(){this.pointer.destroy(),this.onPointerUp({})}startDelay(e){"number"==typeof this.delay?this.delayTimeoutId=setTimeout(()=>{this.delayTimeoutId=null,this.handleDelayEnd(e)},this.delay):this.handleDelayEnd(e)}handleDelayEnd(e){this.isDelayEnded=!0,this.tryStartDrag(e)}handleDistanceSurpassed(e){this.isDistanceSurpassed=!0,this.tryStartDrag(e)}tryStartDrag(e){this.isDelayEnded&&this.isDistanceSurpassed&&(this.pointer.wasTouchScroll&&!this.touchScrollAllowed||(this.isDragging=!0,this.mirrorNeedsRevert=!1,this.autoScroller.start(e.pageX,e.pageY,this.containerEl),this.emitter.trigger("dragstart",e),!1===this.touchScrollAllowed&&this.pointer.cancelTouchScroll()))}tryStopDrag(e){this.mirror.stop(this.mirrorNeedsRevert,this.stopDrag.bind(this,e))}stopDrag(e){this.isDragging=!1,this.emitter.trigger("dragend",e)}setIgnoreMove(e){this.pointer.shouldIgnoreMove=e}setMirrorIsVisible(e){this.mirror.setIsVisible(e)}setMirrorNeedsRevert(e){this.mirrorNeedsRevert=e}setAutoScrollEnabled(e){this.autoScroller.isEnabled=e}}class Fa{constructor(e){this.el=e,this.origRect=ts(e),this.scrollCaches=ns(e).map(e=>new ja(e,!0))}destroy(){for(let e of this.scrollCaches)e.destroy()}computeLeft(){let e=this.origRect.left;for(let t of this.scrollCaches)e+=t.origScrollLeft-t.getScrollLeft();return e}computeTop(){let e=this.origRect.top;for(let t of this.scrollCaches)e+=t.origScrollTop-t.getScrollTop();return e}isWithinClipping(e,t){let n={left:e,top:t};for(let e of this.scrollCaches)if(!Va(e.getEventTarget())&&!Pi(n,e.clientRect))return!1;return!0}}function Va(e){let t=e.tagName;return"HTML"===t||"BODY"===t}class Ga{constructor(e,t){this.useSubjectCenter=!1,this.requireInitial=!0,this.disablePointCheck=!1,this.initialHit=null,this.movingHit=null,this.finalHit=null,this.handlePointerDown=e=>{let{dragging:t}=this;this.initialHit=null,this.movingHit=null,this.finalHit=null,this.prepareHits(),this.processFirstCoord(e),this.initialHit||!this.requireInitial?(t.setIgnoreMove(!1),this.emitter.trigger("pointerdown",e)):t.setIgnoreMove(!0)},this.handleDragStart=e=>{this.emitter.trigger("dragstart",e),this.handleMove(e,!0)},this.handleDragMove=e=>{this.emitter.trigger("dragmove",e),this.handleMove(e)},this.handlePointerUp=e=>{this.releaseHits(),this.emitter.trigger("pointerup",e)},this.handleDragEnd=e=>{this.movingHit&&this.emitter.trigger("hitupdate",null,!0,e),this.finalHit=this.movingHit,this.movingHit=null,this.emitter.trigger("dragend",e)},this.droppableStore=t,e.emitter.on("pointerdown",this.handlePointerDown),e.emitter.on("dragstart",this.handleDragStart),e.emitter.on("dragmove",this.handleDragMove),e.emitter.on("pointerup",this.handlePointerUp),e.emitter.on("dragend",this.handleDragEnd),this.dragging=e,this.emitter=new Fr}processFirstCoord(e){let t,n={left:e.pageX,top:e.pageY},r=n,i=e.subjectEl;i instanceof HTMLElement&&(t=ts(i),r=Bi(r,t));let s=this.initialHit=this.queryHitForOffset(r.left,r.top);if(s){if(this.useSubjectCenter&&t){let e=Hi(t,s.rect);e&&(r=ji(e))}this.coordAdjust=zi(r,n)}else this.coordAdjust={left:0,top:0}}handleMove(e,t){let n=this.queryHitForOffset(e.pageX+this.coordAdjust.left,e.pageY+this.coordAdjust.top);!t&&Qa(this.movingHit,n)||(this.movingHit=n,this.emitter.trigger("hitupdate",n,!1,e))}prepareHits(){this.offsetTrackers=An(this.droppableStore,e=>(e.component.prepareHits(),new Fa(e.el)))}releaseHits(){let{offsetTrackers:e}=this;for(let t in e)e[t].destroy();this.offsetTrackers={}}queryHitForOffset(e,t){let{droppableStore:n,offsetTrackers:r}=this,i=null;for(let s in n){let o=n[s].component,a=r[s];if(a&&a.isWithinClipping(e,t)){let n=a.computeLeft(),r=a.computeTop(),l=e-n,c=t-r,{origRect:d}=a,u=d.right-d.left,h=d.bottom-d.top;if(l>=0&&l=0&&ci.layer)&&(e.componentId=s,e.context=o.context,e.rect.left+=n,e.rect.right+=n,e.rect.top+=r,e.rect.bottom+=r,i=e)}}}return i}}function Qa(e,t){return!e&&!t||Boolean(e)===Boolean(t)&&Ei(e.dateSpan,t.dateSpan)}function qa(e,t){let n={};for(let r of t.pluginHooks.datePointTransforms)Object.assign(n,r(e,t));var r,i;return Object.assign(n,(r=e,{date:(i=t.dateEnv).toDate(r.range.start),dateStr:i.formatIso(r.range.start,{omitTime:r.allDay}),allDay:r.allDay})),n}class Ya extends Ti{constructor(e){super(e),this.subjectEl=null,this.subjectSeg=null,this.isDragging=!1,this.eventRange=null,this.relevantEvents=null,this.receivingContext=null,this.validMutation=null,this.mutatedRelevantEvents=null,this.handlePointerDown=e=>{let t=e.origEvent.target,{component:n,dragging:r}=this,{mirror:i}=r,{options:s}=n.context,o=n.context;this.subjectEl=e.subjectEl;let a=this.subjectSeg=si(e.subjectEl),l=(this.eventRange=a.eventRange).instance.instanceId;this.relevantEvents=Cr(o.getCurrentData().eventStore,l),r.minDistance=e.isTouch?0:s.eventDragMinDistance,r.delay=e.isTouch&&l!==n.props.eventSelection?function(e){let{options:t}=e.context,n=t.eventLongPressDelay;null==n&&(n=t.longPressDelay);return n}(n):null,s.fixedMirrorParent?i.parentNode=s.fixedMirrorParent:i.parentNode=Oe(t,".fc"),i.revertDuration=s.dragRevertDuration;let c=n.isValidSegDownEl(t)&&!Oe(t,".fc-event-resizer");r.setIgnoreMove(!c),this.isDragging=c&&e.subjectEl.classList.contains("fc-event-draggable")},this.handleDragStart=e=>{let t=this.component.context,n=this.eventRange,r=n.instance.instanceId;e.isTouch?r!==this.component.props.eventSelection&&t.dispatch({type:"SELECT_EVENT",eventInstanceId:r}):t.dispatch({type:"UNSELECT_EVENT"}),this.isDragging&&(t.calendarApi.unselect(e),t.emitter.trigger("eventDragStart",{el:this.subjectEl,event:new Kr(t,n.def,n.instance),jsEvent:e.origEvent,view:t.viewApi}))},this.handleHitUpdate=(e,t)=>{if(!this.isDragging)return;let n=this.relevantEvents,r=this.hitDragging.initialHit,i=this.component.context,s=null,o=null,a=null,l=!1,c={affectedEvents:n,mutatedEvents:{defs:{},instances:{}},isEvent:!0};if(e){s=e.context;let t=s.options;i===s||t.editable&&t.droppable?(o=function(e,t,n,r){let i=e.dateSpan,s=t.dateSpan,o=i.range.start,a=s.range.start,l={};i.allDay!==s.allDay&&(l.allDay=s.allDay,l.hasEnd=t.context.options.allDayMaintainDuration,o=s.allDay?Mt(n):n);let c=ur(o,a,e.context.dateEnv,e.componentId===t.componentId?e.largeUnit:null);c.milliseconds&&(l.allDay=!1);let d={datesDelta:c,standardProps:l};for(let n of r)n(d,e,t);return d}(r,e,this.eventRange.instance.range.start,s.getCurrentData().pluginHooks.eventDragMutationMassagers),o&&(a=Zr(n,s.getCurrentData().eventUiBases,o,s),c.mutatedEvents=a,Is(c,e.dateProfile,s)||(l=!0,o=null,a=null,c.mutatedEvents={defs:{},instances:{}}))):s=null}this.displayDrag(s,c),l?Xe():$e(),t||(i===s&&Qa(r,e)&&(o=null),this.dragging.setMirrorNeedsRevert(!o),this.dragging.setMirrorIsVisible(!e||!this.subjectEl.getRootNode().querySelector(".fc-event-mirror")),this.receivingContext=s,this.validMutation=o,this.mutatedRelevantEvents=a)},this.handlePointerUp=()=>{this.isDragging||this.cleanup()},this.handleDragEnd=e=>{if(this.isDragging){let t=this.component.context,n=t.viewApi,{receivingContext:r,validMutation:i}=this,s=this.eventRange.def,o=this.eventRange.instance,a=new Kr(t,s,o),l=this.relevantEvents,c=this.mutatedRelevantEvents,{finalHit:d}=this.hitDragging;if(this.clearDrag(),t.emitter.trigger("eventDragStop",{el:this.subjectEl,event:a,jsEvent:e.origEvent,view:n}),i){if(r===t){let r=new Kr(t,c.defs[s.defId],o?c.instances[o.instanceId]:null);t.dispatch({type:"MERGE_EVENTS",eventStore:c});let d={oldEvent:a,event:r,relatedEvents:ti(c,t,o),revert(){t.dispatch({type:"MERGE_EVENTS",eventStore:l})}},u={};for(let e of t.getCurrentData().pluginHooks.eventDropTransformers)Object.assign(u,e(i,t));t.emitter.trigger("eventDrop",Object.assign(Object.assign(Object.assign({},d),u),{el:e.subjectEl,delta:i.datesDelta,jsEvent:e.origEvent,view:n})),t.emitter.trigger("eventChange",d)}else if(r){let i={event:a,relatedEvents:ti(l,t,o),revert(){t.dispatch({type:"MERGE_EVENTS",eventStore:l})}};t.emitter.trigger("eventLeave",Object.assign(Object.assign({},i),{draggedEl:e.subjectEl,view:n})),t.dispatch({type:"REMOVE_EVENTS",eventStore:l}),t.emitter.trigger("eventRemove",i);let u=c.defs[s.defId],h=c.instances[o.instanceId],f=new Kr(r,u,h);r.dispatch({type:"MERGE_EVENTS",eventStore:c});let g={event:f,relatedEvents:ti(c,r,h),revert(){r.dispatch({type:"REMOVE_EVENTS",eventStore:c})}};r.emitter.trigger("eventAdd",g),e.isTouch&&r.dispatch({type:"SELECT_EVENT",eventInstanceId:o.instanceId}),r.emitter.trigger("drop",Object.assign(Object.assign({},qa(d.dateSpan,r)),{draggedEl:e.subjectEl,jsEvent:e.origEvent,view:d.context.viewApi})),r.emitter.trigger("eventReceive",Object.assign(Object.assign({},g),{draggedEl:e.subjectEl,view:d.context.viewApi}))}}else t.emitter.trigger("_noEventDrop")}this.cleanup()};let{component:t}=this,{options:n}=t.context,r=this.dragging=new Wa(e.el);r.pointer.selector=Ya.SELECTOR,r.touchScrollAllowed=!1,r.autoScroller.isEnabled=n.dragScroll;let i=this.hitDragging=new Ga(this.dragging,Mi);i.useSubjectCenter=e.useEventCenter,i.emitter.on("pointerdown",this.handlePointerDown),i.emitter.on("dragstart",this.handleDragStart),i.emitter.on("hitupdate",this.handleHitUpdate),i.emitter.on("pointerup",this.handlePointerUp),i.emitter.on("dragend",this.handleDragEnd)}destroy(){this.dragging.destroy()}displayDrag(e,t){let n=this.component.context,r=this.receivingContext;r&&r!==e&&(r===n?r.dispatch({type:"SET_EVENT_DRAG",state:{affectedEvents:t.affectedEvents,mutatedEvents:{defs:{},instances:{}},isEvent:!0}}):r.dispatch({type:"UNSET_EVENT_DRAG"})),e&&e.dispatch({type:"SET_EVENT_DRAG",state:t})}clearDrag(){let e=this.component.context,{receivingContext:t}=this;t&&t.dispatch({type:"UNSET_EVENT_DRAG"}),e!==t&&e.dispatch({type:"UNSET_EVENT_DRAG"})}cleanup(){this.subjectSeg=null,this.isDragging=!1,this.eventRange=null,this.relevantEvents=null,this.receivingContext=null,this.validMutation=null,this.mutatedRelevantEvents=null}}Ya.SELECTOR=".fc-event-draggable, .fc-event-resizable";const Za={fixedMirrorParent:yn},Xa={dateClick:yn,eventDragStart:yn,eventDragStop:yn,eventDrop:yn,eventResizeStart:yn,eventResizeStop:yn,eventResize:yn,drop:yn,eventReceive:yn,eventLeave:yn};class $a{constructor(e,t){this.receivingContext=null,this.droppableEvent=null,this.suppliedDragMeta=null,this.dragMeta=null,this.handleDragStart=e=>{this.dragMeta=this.buildDragMeta(e.subjectEl)},this.handleHitUpdate=(e,t,n)=>{let{dragging:r}=this.hitDragging,i=null,s=null,o=!1,a={affectedEvents:{defs:{},instances:{}},mutatedEvents:{defs:{},instances:{}},isEvent:this.dragMeta.create};e&&(i=e.context,this.canDropElOnCalendar(n.subjectEl,i)&&(s=function(e,t,n){let r=Object.assign({},t.leftoverProps);for(let i of n.pluginHooks.externalDefTransforms)Object.assign(r,i(e,t));let{refined:i,extra:s}=Er(r,n),o=Ar(i,s,t.sourceId,e.allDay,n.options.forceEventDuration||Boolean(t.duration),n),a=e.range.start;e.allDay&&t.startTime&&(a=n.dateEnv.add(a,t.startTime));let l=t.duration?n.dateEnv.add(a,t.duration):Yr(e.allDay,a,n),c=fr(o.defId,{start:a,end:l});return{def:o,instance:c}}(e.dateSpan,this.dragMeta,i),a.mutatedEvents=wr(s),o=!Is(a,e.dateProfile,i),o&&(a.mutatedEvents={defs:{},instances:{}},s=null))),this.displayDrag(i,a),r.setMirrorIsVisible(t||!s||!document.querySelector(".fc-event-mirror")),o?Xe():$e(),t||(r.setMirrorNeedsRevert(!s),this.receivingContext=i,this.droppableEvent=s)},this.handleDragEnd=e=>{let{receivingContext:t,droppableEvent:n}=this;if(this.clearDrag(),t&&n){let r=this.hitDragging.finalHit,i=r.context.viewApi,s=this.dragMeta;if(t.emitter.trigger("drop",Object.assign(Object.assign({},qa(r.dateSpan,t)),{draggedEl:e.subjectEl,jsEvent:e.origEvent,view:i})),s.create){let r=wr(n);t.dispatch({type:"MERGE_EVENTS",eventStore:r}),e.isTouch&&t.dispatch({type:"SELECT_EVENT",eventInstanceId:n.instance.instanceId}),t.emitter.trigger("eventReceive",{event:new Kr(t,n.def,n.instance),relatedEvents:[],revert(){t.dispatch({type:"REMOVE_EVENTS",eventStore:r})},draggedEl:e.subjectEl,view:i})}}this.receivingContext=null,this.droppableEvent=null};let n=this.hitDragging=new Ga(e,Mi);n.requireInitial=!1,n.emitter.on("dragstart",this.handleDragStart),n.emitter.on("hitupdate",this.handleHitUpdate),n.emitter.on("dragend",this.handleDragEnd),this.suppliedDragMeta=t}buildDragMeta(e){return"object"==typeof this.suppliedDragMeta?bs(this.suppliedDragMeta):"function"==typeof this.suppliedDragMeta?bs(this.suppliedDragMeta(e)):function(e){let t=function(e,t){let n=vs.dataAttrPrefix,r=(n?n+"-":"")+t;return e.getAttribute("data-"+r)||""}(e,"event");return bs(t?JSON.parse(t):{create:!1})}(e)}displayDrag(e,t){let n=this.receivingContext;n&&n!==e&&n.dispatch({type:"UNSET_EVENT_DRAG"}),e&&e.dispatch({type:"SET_EVENT_DRAG",state:t})}clearDrag(){this.receivingContext&&this.receivingContext.dispatch({type:"UNSET_EVENT_DRAG"})}canDropElOnCalendar(e,t){let n=t.options.dropAccept;return"function"==typeof n?n.call(t.calendarApi,e):"string"!=typeof n||!n||Boolean(Ne(e,n))}}vs.dataAttrPrefix="";class Ja extends ms{constructor(e){super(e),this.shouldIgnoreMove=!1,this.mirrorSelector="",this.currentMirrorEl=null,this.handlePointerDown=e=>{this.emitter.trigger("pointerdown",e),this.shouldIgnoreMove||this.emitter.trigger("dragstart",e)},this.handlePointerMove=e=>{this.shouldIgnoreMove||this.emitter.trigger("dragmove",e)},this.handlePointerUp=e=>{this.emitter.trigger("pointerup",e),this.shouldIgnoreMove||this.emitter.trigger("dragend",e)};let t=this.pointer=new Na(e);t.emitter.on("pointerdown",this.handlePointerDown),t.emitter.on("pointermove",this.handlePointerMove),t.emitter.on("pointerup",this.handlePointerUp)}destroy(){this.pointer.destroy()}setIgnoreMove(e){this.shouldIgnoreMove=e}setMirrorIsVisible(e){if(e)this.currentMirrorEl&&(this.currentMirrorEl.style.visibility="",this.currentMirrorEl=null);else{let e=this.mirrorSelector?document.querySelector(this.mirrorSelector):null;e&&(this.currentMirrorEl=e,e.style.visibility="hidden")}}}var Ka=Oo({name:"@fullcalendar/interaction",componentInteractions:[class extends Ti{constructor(e){super(e),this.handlePointerDown=e=>{let{dragging:t}=this,n=e.origEvent.target;t.setIgnoreMove(!this.component.isValidDateDownEl(n))},this.handleDragEnd=e=>{let{component:t}=this,{pointer:n}=this.dragging;if(!n.wasTouchScroll){let{initialHit:n,finalHit:r}=this.hitDragging;if(n&&r&&Qa(n,r)){let{context:r}=t,i=Object.assign(Object.assign({},qa(n.dateSpan,r)),{dayEl:n.dayEl,jsEvent:e.origEvent,view:r.viewApi||r.calendarApi.view});r.emitter.trigger("dateClick",i)}}},this.dragging=new Wa(e.el),this.dragging.autoScroller.isEnabled=!1;let t=this.hitDragging=new Ga(this.dragging,ki(e));t.emitter.on("pointerdown",this.handlePointerDown),t.emitter.on("dragend",this.handleDragEnd)}destroy(){this.dragging.destroy()}},class extends Ti{constructor(e){super(e),this.dragSelection=null,this.handlePointerDown=e=>{let{component:t,dragging:n}=this,{options:r}=t.context,i=r.selectable&&t.isValidDateDownEl(e.origEvent.target);n.setIgnoreMove(!i),n.delay=e.isTouch?function(e){let{options:t}=e.context,n=t.selectLongPressDelay;null==n&&(n=t.longPressDelay);return n}(t):null},this.handleDragStart=e=>{this.component.context.calendarApi.unselect(e)},this.handleHitUpdate=(e,t)=>{let{context:n}=this.component,r=null,i=!1;if(e){let t=this.hitDragging.initialHit;e.componentId===t.componentId&&this.isHitComboAllowed&&!this.isHitComboAllowed(t,e)||(r=function(e,t,n){let r=e.dateSpan,i=t.dateSpan,s=[r.range.start,r.range.end,i.range.start,i.range.end];s.sort(lt);let o={};for(let r of n){let n=r(e,t);if(!1===n)return null;n&&Object.assign(o,n)}return o.range={start:s[0],end:s[3]},o.allDay=r.allDay,o}(t,e,n.pluginHooks.dateSelectionTransformers)),r&&Os(r,e.dateProfile,n)||(i=!0,r=null)}r?n.dispatch({type:"SELECT_DATES",selection:r}):t||n.dispatch({type:"UNSELECT_DATES"}),i?Xe():$e(),t||(this.dragSelection=r)},this.handlePointerUp=e=>{this.dragSelection&&(Qr(this.dragSelection,e,this.component.context),this.dragSelection=null)};let{component:t}=e,{options:n}=t.context,r=this.dragging=new Wa(e.el);r.touchScrollAllowed=!1,r.minDistance=n.selectMinDistance||0,r.autoScroller.isEnabled=n.dragScroll;let i=this.hitDragging=new Ga(this.dragging,ki(e));i.emitter.on("pointerdown",this.handlePointerDown),i.emitter.on("dragstart",this.handleDragStart),i.emitter.on("hitupdate",this.handleHitUpdate),i.emitter.on("pointerup",this.handlePointerUp)}destroy(){this.dragging.destroy()}},Ya,class extends Ti{constructor(e){super(e),this.draggingSegEl=null,this.draggingSeg=null,this.eventRange=null,this.relevantEvents=null,this.validMutation=null,this.mutatedRelevantEvents=null,this.handlePointerDown=e=>{let{component:t}=this,n=si(this.querySegEl(e)),r=this.eventRange=n.eventRange;this.dragging.minDistance=t.context.options.eventDragMinDistance,this.dragging.setIgnoreMove(!this.component.isValidSegDownEl(e.origEvent.target)||e.isTouch&&this.component.props.eventSelection!==r.instance.instanceId)},this.handleDragStart=e=>{let{context:t}=this.component,n=this.eventRange;this.relevantEvents=Cr(t.getCurrentData().eventStore,this.eventRange.instance.instanceId);let r=this.querySegEl(e);this.draggingSegEl=r,this.draggingSeg=si(r),t.calendarApi.unselect(),t.emitter.trigger("eventResizeStart",{el:r,event:new Kr(t,n.def,n.instance),jsEvent:e.origEvent,view:t.viewApi})},this.handleHitUpdate=(e,t,n)=>{let{context:r}=this.component,i=this.relevantEvents,s=this.hitDragging.initialHit,o=this.eventRange.instance,a=null,l=null,c=!1,d={affectedEvents:i,mutatedEvents:{defs:{},instances:{}},isEvent:!0};if(e){e.componentId===s.componentId&&this.isHitComboAllowed&&!this.isHitComboAllowed(s,e)||(a=function(e,t,n,r){let i=e.context.dateEnv,s=e.dateSpan.range.start,o=t.dateSpan.range.start,a=ur(s,o,i,e.largeUnit);if(n){if(i.add(r.start,a)r.start)return{endDelta:a};return null}(s,e,n.subjectEl.classList.contains("fc-event-resizer-start"),o.range))}a&&(l=Zr(i,r.getCurrentData().eventUiBases,a,r),d.mutatedEvents=l,Is(d,e.dateProfile,r)||(c=!0,a=null,l=null,d.mutatedEvents=null)),l?r.dispatch({type:"SET_EVENT_RESIZE",state:d}):r.dispatch({type:"UNSET_EVENT_RESIZE"}),c?Xe():$e(),t||(a&&Qa(s,e)&&(a=null),this.validMutation=a,this.mutatedRelevantEvents=l)},this.handleDragEnd=e=>{let{context:t}=this.component,n=this.eventRange.def,r=this.eventRange.instance,i=new Kr(t,n,r),s=this.relevantEvents,o=this.mutatedRelevantEvents;if(t.emitter.trigger("eventResizeStop",{el:this.draggingSegEl,event:i,jsEvent:e.origEvent,view:t.viewApi}),this.validMutation){let a=new Kr(t,o.defs[n.defId],r?o.instances[r.instanceId]:null);t.dispatch({type:"MERGE_EVENTS",eventStore:o});let l={oldEvent:i,event:a,relatedEvents:ti(o,t,r),revert(){t.dispatch({type:"MERGE_EVENTS",eventStore:s})}};t.emitter.trigger("eventResize",Object.assign(Object.assign({},l),{el:this.draggingSegEl,startDelta:this.validMutation.startDelta||ft(0),endDelta:this.validMutation.endDelta||ft(0),jsEvent:e.origEvent,view:t.viewApi})),t.emitter.trigger("eventChange",l)}else t.emitter.trigger("_noEventResize");this.draggingSeg=null,this.relevantEvents=null,this.validMutation=null};let{component:t}=e,n=this.dragging=new Wa(e.el);n.pointer.selector=".fc-event-resizer",n.touchScrollAllowed=!1,n.autoScroller.isEnabled=t.context.options.dragScroll;let r=this.hitDragging=new Ga(this.dragging,ki(e));r.emitter.on("pointerdown",this.handlePointerDown),r.emitter.on("dragstart",this.handleDragStart),r.emitter.on("hitupdate",this.handleHitUpdate),r.emitter.on("dragend",this.handleDragEnd)}destroy(){this.dragging.destroy()}querySegEl(e){return Oe(e.subjectEl,".fc-event")}}],calendarInteractions:[class{constructor(e){this.context=e,this.isRecentPointerDateSelect=!1,this.matchesCancel=!1,this.matchesEvent=!1,this.onSelect=e=>{e.jsEvent&&(this.isRecentPointerDateSelect=!0)},this.onDocumentPointerDown=e=>{let t=this.context.options.unselectCancel,n=ze(e.origEvent);this.matchesCancel=!!Oe(n,t),this.matchesEvent=!!Oe(n,Ya.SELECTOR)},this.onDocumentPointerUp=e=>{let{context:t}=this,{documentPointer:n}=this,r=t.getCurrentData();if(!n.wasTouchScroll){if(r.dateSelection&&!this.isRecentPointerDateSelect){let n=t.options.unselectAuto;!n||n&&this.matchesCancel||t.calendarApi.unselect(e)}r.eventSelection&&!this.matchesEvent&&t.dispatch({type:"UNSELECT_EVENT"})}this.isRecentPointerDateSelect=!1};let t=this.documentPointer=new Na(document);t.shouldIgnoreMove=!0,t.shouldWatchScroll=!1,t.emitter.on("pointerdown",this.onDocumentPointerDown),t.emitter.on("pointerup",this.onDocumentPointerUp),e.emitter.on("select",this.onSelect)}destroy(){this.context.emitter.off("select",this.onSelect),this.documentPointer.destroy()}}],elementDraggingImpl:Wa,optionRefiners:Za,listenerRefiners:Xa});class el extends ls{constructor(){super(...arguments),this.headerElRef={current:null}}renderSimpleLayout(e,t){let{props:n,context:r}=this,i=[],s=eo(r.options);return e&&i.push({type:"header",key:"header",isSticky:s,chunk:{elRef:this.headerElRef,tableClassName:"fc-col-header",rowContent:e}}),i.push({type:"body",key:"body",liquid:!0,chunk:{content:t}}),p(Kn,{elClasses:["fc-daygrid"],viewSpec:r.viewSpec},p(no,{liquid:!n.isHeightAuto&&!n.forPrint,collapsibleWidth:n.forPrint,cols:[],sections:i}))}renderHScrollLayout(e,t,n,r){let i=this.context.pluginHooks.scrollGridImpl;if(!i)throw new Error("No ScrollGrid implementation");let{props:s,context:o}=this,a=!s.forPrint&&eo(o.options),l=!s.forPrint&&to(o.options),c=[];return e&&c.push({type:"header",key:"header",isSticky:a,chunks:[{key:"main",elRef:this.headerElRef,tableClassName:"fc-col-header",rowContent:e}]}),c.push({type:"body",key:"body",liquid:!0,chunks:[{key:"main",content:t}]}),l&&c.push({type:"footer",key:"footer",isSticky:!0,chunks:[{key:"main",content:Ks}]}),p(Kn,{elClasses:["fc-daygrid"],viewSpec:o.viewSpec},p(i,{liquid:!s.isHeightAuto&&!s.forPrint,forPrint:s.forPrint,collapsibleWidth:s.forPrint,colGroups:[{cols:[{span:n,minWidth:r}]}],sections:c}))}}function tl(e,t){let n=[];for(let e=0;e{let n=(e.eventDrag?e.eventDrag.affectedInstances:null)||(e.eventResize?e.eventResize.affectedInstances:null)||{};return p(y,null,t.map(t=>{let r=t.eventRange.instance.instanceId;return p("div",{className:"fc-daygrid-event-harness",key:r,style:{visibility:n[r]?"hidden":""}},sl(t)?p(al,Object.assign({seg:t,isDragging:!1,isSelected:r===e.eventSelection,defaultDisplayEventEnd:!1},gi(t,e.todayRange))):p(ol,Object.assign({seg:t,isDragging:!1,isResizing:!1,isDateSelecting:!1,isSelected:r===e.eventSelection,defaultDisplayEventEnd:!1},gi(t,e.todayRange))))}))}})}}function dl(e){let t=[],n=[];for(let r of e)t.push(r.seg),r.isVisible||n.push(r.seg);return{allSegs:t,invisibleSegs:n}}const ul=an({week:"narrow"});class hl extends ls{constructor(){super(...arguments),this.rootElRef={current:null},this.state={dayNumberId:Le()},this.handleRootEl=e=>{Gn(this.rootElRef,e),Gn(this.props.elRef,e)}}render(){let{context:e,props:t,state:n,rootElRef:r}=this,{options:i,dateEnv:s}=e,{date:o,dateProfile:a}=t;const l=t.showDayNumber&&function(e,t,n){const{start:r,end:i}=t,s=Ct(i,-1),o=n.getYear(r),a=n.getMonth(r),l=n.getYear(s),c=n.getMonth(s);return!(o===l&&a===c)&&Boolean(e.valueOf()===r.valueOf()||1===n.getDay(e)&&e.valueOf()p("div",{ref:t.innerElRef,className:"fc-daygrid-day-frame fc-scrollgrid-sync-inner",style:{minHeight:t.minHeight}},t.showWeekNumber&&p(po,{elTag:"a",elClasses:["fc-daygrid-week-number"],elAttrs:qi(e,o,"week"),date:o,defaultFormat:ul}),!a.isDisabled&&(t.showDayNumber||co(i)||t.forceDayTop)?p("div",{className:"fc-daygrid-day-top"},p(s,{elTag:"a",elClasses:["fc-daygrid-day-number",l&&"fc-daygrid-month-start"],elAttrs:Object.assign(Object.assign({},qi(e,o)),{id:n.dayNumberId})})):t.showDayNumber?p("div",{className:"fc-daygrid-day-top",style:{visibility:"hidden"}},p("a",{className:"fc-daygrid-day-number"}," ")):void 0,p("div",{className:"fc-daygrid-day-events",ref:t.fgContentElRef},t.fgContent,p("div",{className:"fc-daygrid-day-bottom",style:{marginTop:t.moreMarginTop}},p(cl,{allDayDate:o,singlePlacements:t.singlePlacements,moreCnt:t.moreCnt,alignmentElRef:r,alignGridTop:!t.showDayNumber,extraDateSpan:t.extraDateSpan,dateProfile:t.dateProfile,eventSelection:t.eventSelection,eventDrag:t.eventDrag,eventResize:t.eventResize,todayRange:t.todayRange}))),p("div",{className:"fc-daygrid-day-bg"},t.bgContent)))}}function fl(e){return e.dayNumberText||p(y,null," ")}function gl(e){return e.eventRange.instance.instanceId+":"+e.firstCol}function pl(e){return gl(e)+":"+e.lastCol}function ml(e,t,n,r,i,s,o){let a=new yl(t=>{let n=e[t.index].eventRange.instance.instanceId+":"+t.span.start+":"+(t.span.end-1);return i[n]||1});a.allowReslicing=!0,a.strictOrder=r,!0===t||!0===n?(a.maxCoord=s,a.hiddenConsumes=!0):"number"==typeof t?a.maxStackCnt=t:"number"==typeof n&&(a.maxStackCnt=n,a.hiddenConsumes=!0);let l=[],c=[];for(let t=0;t1,o=r.span.start===e;d+=r.levelCoord-c,c=r.levelCoord+r.thickness,s?(d+=r.thickness,o&&u.push({seg:vl(i,r.span.start,r.span.end,n),isVisible:!0,isAbsolute:!0,absoluteTop:r.levelCoord,marginTop:0})):o&&(u.push({seg:vl(i,r.span.start,r.span.end,n),isVisible:!0,isAbsolute:!1,absoluteTop:r.levelCoord,marginTop:d}),d=0)}i.push(l),s.push(u),o.push(d)}return{singleColPlacements:i,multiColPlacements:s,leftoverMargins:o}}(u,e,o),p=[],m=[];for(let e of c){f[e.firstCol].push({seg:e,isVisible:!1,isAbsolute:!0,absoluteTop:0,marginTop:0});for(let t=e.firstCol;t<=e.lastCol;t+=1)h[t].push({seg:vl(e,t,t+1,o),isVisible:!1,isAbsolute:!1,absoluteTop:0,marginTop:0})}for(let e=0;e!this.forceHidden[us(e)];for(let e=0;e{e&&this.updateSizing(!0)}}render(){let{props:e,state:t,context:n}=this,{options:r}=n,i=e.cells.length,s=nl(e.businessHourSegs,i),o=nl(e.bgEventSegs,i),a=nl(this.getHighlightSegs(),i),l=nl(this.getMirrorSegs(),i),{singleColPlacements:c,multiColPlacements:d,moreCnts:u,moreMarginTops:h}=ml(li(e.fgEventSegs,r.eventOrder),e.dayMaxEvents,e.dayMaxEventRows,r.eventOrderStrict,t.segHeights,t.maxContentHeight,e.cells),f=e.eventDrag&&e.eventDrag.affectedInstances||e.eventResize&&e.eventResize.affectedInstances||{};return p("tr",{ref:this.rootElRef,role:"row"},e.renderIntro&&e.renderIntro(),e.cells.map((t,n)=>{let r=this.renderFgSegs(n,e.forPrint?c[n]:d[n],e.todayRange,f),i=this.renderFgSegs(n,function(e,t){if(!e.length)return[];let n=function(e){let t={};for(let n of e)for(let e of n)t[e.seg.eventRange.instance.instanceId]=e.absoluteTop;return t}(t);return e.map(e=>({seg:e,isVisible:!0,isAbsolute:!0,absoluteTop:n[e.eventRange.instance.instanceId],marginTop:0}))}(l[n],d),e.todayRange,{},Boolean(e.eventDrag),Boolean(e.eventResize),!1);return p(hl,{key:t.key,elRef:this.cellElRefs.createRef(t.key),innerElRef:this.frameElRefs.createRef(t.key),dateProfile:e.dateProfile,date:t.date,showDayNumber:e.showDayNumbers,showWeekNumber:e.showWeekNumbers&&0===n,forceDayTop:e.showWeekNumbers,todayRange:e.todayRange,eventSelection:e.eventSelection,eventDrag:e.eventDrag,eventResize:e.eventResize,extraRenderProps:t.extraRenderProps,extraDataAttrs:t.extraDataAttrs,extraClassNames:t.extraClassNames,extraDateSpan:t.extraDateSpan,moreCnt:u[n],moreMarginTop:h[n],singlePlacements:c[n],fgContentElRef:this.fgElRefs.createRef(t.key),fgContent:p(y,null,p(y,null,r),p(y,null,i)),bgContent:p(y,null,this.renderFillSegs(a[n],"highlight"),this.renderFillSegs(s[n],"non-business"),this.renderFillSegs(o[n],"bg-event")),minHeight:e.cellMinHeight})}))}componentDidMount(){this.updateSizing(!0),this.context.addResizeHandler(this.handleResize)}componentDidUpdate(e,t){let n=this.props;this.updateSizing(!Cn(e,n))}componentWillUnmount(){this.context.removeResizeHandler(this.handleResize)}getHighlightSegs(){let{props:e}=this;return e.eventDrag&&e.eventDrag.segs.length?e.eventDrag.segs:e.eventResize&&e.eventResize.segs.length?e.eventResize.segs:e.dateSelectionSegs}getMirrorSegs(){let{props:e}=this;return e.eventResize&&e.eventResize.segs.length?e.eventResize.segs:[]}renderFgSegs(e,t,n,r,i,s,o){let{context:a}=this,{eventSelection:l}=this.props,{framePositions:c}=this.state,d=1===this.props.cells.length,u=i||s||o,h=[];if(c)for(let e of t){let{seg:t}=e,{instanceId:f}=t.eventRange.instance,g=e.isVisible&&!r[f],m=e.isAbsolute,v="",y="";m&&(a.isRtl?(y=0,v=c.lefts[t.lastCol]-c.lefts[t.firstCol]):(v=0,y=c.rights[t.firstCol]-c.rights[t.lastCol])),h.push(p("div",{className:"fc-daygrid-event-harness"+(m?" fc-daygrid-event-harness-abs":""),key:gl(t),ref:u?null:this.segHarnessRefs.createRef(pl(t)),style:{visibility:g?"":"hidden",marginTop:m?"":e.marginTop,top:m?e.absoluteTop:"",left:v,right:y}},sl(t)?p(al,Object.assign({seg:t,isDragging:i,isSelected:f===l,defaultDisplayEventEnd:d},gi(t,n))):p(ol,Object.assign({seg:t,isDragging:i,isResizing:s,isDateSelecting:o,isSelected:f===l,defaultDisplayEventEnd:d},gi(t,n)))))}return h}renderFillSegs(e,t){let{isRtl:n}=this.context,{todayRange:r}=this.props,{framePositions:i}=this.state,s=[];if(i)for(let o of e){let e=n?{right:0,left:i.lefts[o.lastCol]-i.lefts[o.firstCol]}:{left:0,right:i.rights[o.firstCol]-i.rights[o.lastCol]};s.push(p("div",{key:mi(o.eventRange),className:"fc-daygrid-bg-harness",style:e},"bg-event"===t?p(ho,Object.assign({seg:o},gi(o,r))):go(t)))}return p(y,{},...s)}updateSizing(e){let{props:t,state:n,frameElRefs:r}=this;if(!t.forPrint&&null!==t.clientWidth){if(e){let e=t.cells.map(e=>r.currentMap[e.key]);if(e.length){let t=this.rootElRef.current,r=new rs(t,e,!0,!1);n.framePositions&&n.framePositions.similarTo(r)||this.setState({framePositions:new rs(t,e,!0,!1)})}}const i=this.state.segHeights,s=this.querySegHeights(),o=!0===t.dayMaxEvents||!0===t.dayMaxEventRows;this.safeSetState({segHeights:Object.assign(Object.assign({},i),s),maxContentHeight:o?this.computeMaxContentHeight():null})}}querySegHeights(){let e=this.segHarnessRefs.currentMap,t={};for(let n in e){let r=Math.round(e[n].getBoundingClientRect().height);t[n]=Math.max(t[n]||0,r)}return t}computeMaxContentHeight(){let e=this.props.cells[0].key,t=this.cellElRefs.currentMap[e],n=this.fgElRefs.currentMap[e];return t.getBoundingClientRect().bottom-n.getBoundingClientRect().top}getCellEls(){let e=this.cellElRefs.currentMap;return this.props.cells.map(t=>e[t.key])}}bl.addStateEquality({segHeights:Cn});class El extends ls{constructor(){super(...arguments),this.splitBusinessHourSegs=Gt(tl),this.splitBgEventSegs=Gt(Sl),this.splitFgEventSegs=Gt(tl),this.splitDateSelectionSegs=Gt(tl),this.splitEventDrag=Gt(rl),this.splitEventResize=Gt(rl),this.rowRefs=new Ws}render(){let{props:e,context:t}=this,n=e.cells.length,r=this.splitBusinessHourSegs(e.businessHourSegs,n),i=this.splitBgEventSegs(e.bgEventSegs,n),s=this.splitFgEventSegs(e.fgEventSegs,n),o=this.splitDateSelectionSegs(e.dateSelectionSegs,n),a=this.splitEventDrag(e.eventDrag,n),l=this.splitEventResize(e.eventResize,n),c=n>=7&&e.clientWidth?e.clientWidth/t.options.aspectRatio/6:null;return p(Ii,{unit:"day"},(t,d)=>p(y,null,e.cells.map((t,u)=>p(bl,{ref:this.rowRefs.createRef(u),key:t.length?t[0].date.toISOString():u,showDayNumbers:n>1,showWeekNumbers:e.showWeekNumbers,todayRange:d,dateProfile:e.dateProfile,cells:t,renderIntro:e.renderRowIntro,businessHourSegs:r[u],eventSelection:e.eventSelection,bgEventSegs:i[u],fgEventSegs:s[u],dateSelectionSegs:o[u],eventDrag:a[u],eventResize:l[u],dayMaxEvents:e.dayMaxEvents,dayMaxEventRows:e.dayMaxEventRows,clientWidth:e.clientWidth,clientHeight:e.clientHeight,cellMinHeight:c,forPrint:e.forPrint}))))}componentDidMount(){this.registerInteractiveComponent()}componentDidUpdate(){this.registerInteractiveComponent()}registerInteractiveComponent(){if(!this.rootEl){const e=this.rowRefs.currentMap[0].getCellEls()[0],t=e?e.closest(".fc-daygrid-body"):null;t&&(this.rootEl=t,this.context.registerInteractiveComponent(this,{el:t,isHitComboAllowed:this.props.isHitComboAllowed}))}}componentWillUnmount(){this.rootEl&&(this.context.unregisterInteractiveComponent(this),this.rootEl=null)}prepareHits(){this.rowPositions=new rs(this.rootEl,this.rowRefs.collect().map(e=>e.getCellEls()[0]),!1,!0),this.colPositions=new rs(this.rootEl,this.rowRefs.currentMap[0].getCellEls(),!0,!1)}queryHit(e,t){let{colPositions:n,rowPositions:r}=this,i=n.leftToIndex(e),s=r.topToIndex(t);if(null!=s&&null!=i){let e=this.props.cells[s][i];return{dateProfile:this.props.dateProfile,dateSpan:Object.assign({range:this.getCellRange(s,i),allDay:!0},e.extraDateSpan),dayEl:this.getCellEl(s,i),rect:{left:n.lefts[i],right:n.rights[i],top:r.tops[s],bottom:r.bottoms[s]},layer:0}}return null}getCellEl(e,t){return this.rowRefs.currentMap[e].getCellEls()[t]}getCellRange(e,t){let n=this.props.cells[e][t].date;return{start:n,end:wt(n,1)}}}function Sl(e,t){return tl(e.filter(Al),t)}function Al(e){return e.eventRange.def.allDay}class Dl extends ls{constructor(){super(...arguments),this.elRef={current:null},this.needsScrollReset=!1}render(){let{props:e}=this,{dayMaxEventRows:t,dayMaxEvents:n,expandRows:r}=e,i=!0===n||!0===t;i&&!r&&(i=!1,t=null,n=null);let s=["fc-daygrid-body",i?"fc-daygrid-body-balanced":"fc-daygrid-body-unbalanced",r?"":"fc-daygrid-body-natural"];return p("div",{ref:this.elRef,className:s.join(" "),style:{width:e.clientWidth,minWidth:e.tableMinWidth}},p("table",{role:"presentation",className:"fc-scrollgrid-sync-table",style:{width:e.clientWidth,minWidth:e.tableMinWidth,height:r?e.clientHeight:""}},e.colGroupNode,p("tbody",{role:"presentation"},p(El,{dateProfile:e.dateProfile,cells:e.cells,renderRowIntro:e.renderRowIntro,showWeekNumbers:e.showWeekNumbers,clientWidth:e.clientWidth,clientHeight:e.clientHeight,businessHourSegs:e.businessHourSegs,bgEventSegs:e.bgEventSegs,fgEventSegs:e.fgEventSegs,dateSelectionSegs:e.dateSelectionSegs,eventSelection:e.eventSelection,eventDrag:e.eventDrag,eventResize:e.eventResize,dayMaxEvents:n,dayMaxEventRows:t,forPrint:e.forPrint,isHitComboAllowed:e.isHitComboAllowed}))))}componentDidMount(){this.requestScrollReset()}componentDidUpdate(e){e.dateProfile!==this.props.dateProfile?this.requestScrollReset():this.flushScrollReset()}requestScrollReset(){this.needsScrollReset=!0,this.flushScrollReset()}flushScrollReset(){if(this.needsScrollReset&&this.props.clientWidth){const e=function(e,t){let n;t.currentRangeUnit.match(/year|month/)&&(n=e.querySelector(`[data-date="${Wt(t.currentDate)}-01"]`));n||(n=e.querySelector(`[data-date="${Lt(t.currentDate)}"]`));return n}(this.elRef.current,this.props.dateProfile);if(e){const t=e.closest(".fc-daygrid-body"),n=t.closest(".fc-scroller"),r=e.getBoundingClientRect().top-t.getBoundingClientRect().top;n.scrollTop=r?r+1:0}this.needsScrollReset=!1}}}class wl extends ks{constructor(){super(...arguments),this.forceDayIfListItem=!0}sliceRange(e,t){return t.sliceRange(e)}}class Cl extends ls{constructor(){super(...arguments),this.slicer=new wl,this.tableRef={current:null}}render(){let{props:e,context:t}=this;return p(Dl,Object.assign({ref:this.tableRef},this.slicer.sliceProps(e,e.dateProfile,e.nextDayThreshold,t,e.dayTableModel),{dateProfile:e.dateProfile,cells:e.dayTableModel.cells,colGroupNode:e.colGroupNode,tableMinWidth:e.tableMinWidth,renderRowIntro:e.renderRowIntro,dayMaxEvents:e.dayMaxEvents,dayMaxEventRows:e.dayMaxEventRows,showWeekNumbers:e.showWeekNumbers,expandRows:e.expandRows,headerAlignElRef:e.headerAlignElRef,clientWidth:e.clientWidth,clientHeight:e.clientHeight,forPrint:e.forPrint}))}}function Rl(e,t){let n=new _s(e.renderRange,t);return new Ts(n,/year|month|week/.test(e.currentRangeUnit))}class xl extends hr{buildRenderRange(e,t,n){let r=super.buildRenderRange(e,t,n),{props:i}=this;return _l({currentRange:r,snapToWeek:/^(year|month)$/.test(t),fixedWeekCount:i.fixedWeekCount,dateEnv:i.dateEnv})}}function _l(e){let t,{dateEnv:n,currentRange:r}=e,{start:i,end:s}=r;if(e.snapToWeek&&(i=n.startOfWeek(i),t=n.startOfWeek(s),t.valueOf()!==s.valueOf()&&(s=Dt(t,1))),e.fixedWeekCount){let e=n.startOfWeek(n.startOfMonth(wt(r.end,-1)));s=Dt(s,6-Math.ceil(Rt(e,s)))}return{start:i,end:s}}xe(':root{--fc-daygrid-event-dot-width:8px}.fc-daygrid-day-events:after,.fc-daygrid-day-events:before,.fc-daygrid-day-frame:after,.fc-daygrid-day-frame:before,.fc-daygrid-event-harness:after,.fc-daygrid-event-harness:before{clear:both;content:"";display:table}.fc .fc-daygrid-body{position:relative;z-index:1}.fc .fc-daygrid-day.fc-day-today{background-color:var(--fc-today-bg-color)}.fc .fc-daygrid-day-frame{min-height:100%;position:relative}.fc .fc-daygrid-day-top{display:flex;flex-direction:row-reverse}.fc .fc-day-other .fc-daygrid-day-top{opacity:.3}.fc .fc-daygrid-day-number{padding:4px;position:relative;z-index:4}.fc .fc-daygrid-month-start{font-size:1.1em;font-weight:700}.fc .fc-daygrid-day-events{margin-top:1px}.fc .fc-daygrid-body-balanced .fc-daygrid-day-events{left:0;position:absolute;right:0}.fc .fc-daygrid-body-unbalanced .fc-daygrid-day-events{min-height:2em;position:relative}.fc .fc-daygrid-body-natural .fc-daygrid-day-events{margin-bottom:1em}.fc .fc-daygrid-event-harness{position:relative}.fc .fc-daygrid-event-harness-abs{left:0;position:absolute;right:0;top:0}.fc .fc-daygrid-bg-harness{bottom:0;position:absolute;top:0}.fc .fc-daygrid-day-bg .fc-non-business{z-index:1}.fc .fc-daygrid-day-bg .fc-bg-event{z-index:2}.fc .fc-daygrid-day-bg .fc-highlight{z-index:3}.fc .fc-daygrid-event{margin-top:1px;z-index:6}.fc .fc-daygrid-event.fc-event-mirror{z-index:7}.fc .fc-daygrid-day-bottom{font-size:.85em;margin:0 2px}.fc .fc-daygrid-day-bottom:after,.fc .fc-daygrid-day-bottom:before{clear:both;content:"";display:table}.fc .fc-daygrid-more-link{border-radius:3px;cursor:pointer;line-height:1;margin-top:1px;max-width:100%;overflow:hidden;padding:2px;position:relative;white-space:nowrap;z-index:4}.fc .fc-daygrid-more-link:hover{background-color:rgba(0,0,0,.1)}.fc .fc-daygrid-week-number{background-color:var(--fc-neutral-bg-color);color:var(--fc-neutral-text-color);min-width:1.5em;padding:2px;position:absolute;text-align:center;top:0;z-index:5}.fc .fc-more-popover .fc-popover-body{min-width:220px;padding:10px}.fc-direction-ltr .fc-daygrid-event.fc-event-start,.fc-direction-rtl .fc-daygrid-event.fc-event-end{margin-left:2px}.fc-direction-ltr .fc-daygrid-event.fc-event-end,.fc-direction-rtl .fc-daygrid-event.fc-event-start{margin-right:2px}.fc-direction-ltr .fc-daygrid-more-link{float:left}.fc-direction-ltr .fc-daygrid-week-number{border-radius:0 0 3px 0;left:0}.fc-direction-rtl .fc-daygrid-more-link{float:right}.fc-direction-rtl .fc-daygrid-week-number{border-radius:0 0 0 3px;right:0}.fc-liquid-hack .fc-daygrid-day-frame{position:static}.fc-daygrid-event{border-radius:3px;font-size:var(--fc-small-font-size);position:relative;white-space:nowrap}.fc-daygrid-block-event .fc-event-time{font-weight:700}.fc-daygrid-block-event .fc-event-time,.fc-daygrid-block-event .fc-event-title{padding:1px}.fc-daygrid-dot-event{align-items:center;display:flex;padding:2px 0}.fc-daygrid-dot-event .fc-event-title{flex-grow:1;flex-shrink:1;font-weight:700;min-width:0;overflow:hidden}.fc-daygrid-dot-event.fc-event-mirror,.fc-daygrid-dot-event:hover{background:rgba(0,0,0,.1)}.fc-daygrid-dot-event.fc-event-selected:before{bottom:-10px;top:-10px}.fc-daygrid-event-dot{border:calc(var(--fc-daygrid-event-dot-width)/2) solid var(--fc-event-border-color);border-radius:calc(var(--fc-daygrid-event-dot-width)/2);box-sizing:content-box;height:0;margin:0 4px;width:0}.fc-direction-ltr .fc-daygrid-event .fc-event-time{margin-right:3px}.fc-direction-rtl .fc-daygrid-event .fc-event-time{margin-left:3px}');var Tl=Oo({name:"@fullcalendar/daygrid",initialView:"dayGridMonth",views:{dayGrid:{component:class extends el{constructor(){super(...arguments),this.buildDayTableModel=Gt(Rl),this.headerRef={current:null},this.tableRef={current:null}}render(){let{options:e,dateProfileGenerator:t}=this.context,{props:n}=this,r=this.buildDayTableModel(n.dateProfile,t),i=e.dayHeaders&&p(Rs,{ref:this.headerRef,dateProfile:n.dateProfile,dates:r.headerDates,datesRepDistinctDays:1===r.rowCnt}),s=t=>p(Cl,{ref:this.tableRef,dateProfile:n.dateProfile,dayTableModel:r,businessHours:n.businessHours,dateSelection:n.dateSelection,eventStore:n.eventStore,eventUiBases:n.eventUiBases,eventSelection:n.eventSelection,eventDrag:n.eventDrag,eventResize:n.eventResize,nextDayThreshold:e.nextDayThreshold,colGroupNode:t.tableColGroupNode,tableMinWidth:t.tableMinWidth,dayMaxEvents:e.dayMaxEvents,dayMaxEventRows:e.dayMaxEventRows,showWeekNumbers:e.weekNumbers,expandRows:!n.isHeightAuto,headerAlignElRef:this.headerElRef,clientWidth:t.clientWidth,clientHeight:t.clientHeight,forPrint:n.forPrint});return e.dayMinWidth?this.renderHScrollLayout(i,s,r.colCnt,e.dayMinWidth):this.renderSimpleLayout(i,s)}},dateProfileGeneratorClass:xl},dayGridDay:{type:"dayGrid",duration:{days:1}},dayGridWeek:{type:"dayGrid",duration:{weeks:1}},dayGridMonth:{type:"dayGrid",duration:{months:1},fixedWeekCount:!0},dayGridYear:{type:"dayGrid",duration:{years:1}}}});class kl extends Li{getKeyInfo(){return{allDay:{},timed:{}}}getKeysForDateSpan(e){return e.allDay?["allDay"]:["timed"]}getKeysForEventDef(e){return e.allDay?ri(e)?["timed","allDay"]:["allDay"]:["timed"]}}const Ml=an({hour:"numeric",minute:"2-digit",omitZeroMinute:!0,meridiem:"short"});function Il(e){let t=["fc-timegrid-slot","fc-timegrid-slot-label",e.isLabeled?"fc-scrollgrid-shrink":"fc-timegrid-slot-minor"];return p(Ln.Consumer,null,n=>{if(!e.isLabeled)return p("td",{className:t.join(" "),"data-time":e.isoTimeStr});let{dateEnv:r,options:i,viewApi:s}=n,o=null==i.slotLabelFormat?Ml:Array.isArray(i.slotLabelFormat)?an(i.slotLabelFormat[0]):an(i.slotLabelFormat),a={level:0,time:e.time,date:r.toDate(e.date),view:s,text:r.format(e.date,o)};return p($n,{elTag:"td",elClasses:t,elAttrs:{"data-time":e.isoTimeStr},renderProps:a,generatorName:"slotLabelContent",customGenerator:i.slotLabelContent,defaultGenerator:Ol,classNameGenerator:i.slotLabelClassNames,didMount:i.slotLabelDidMount,willUnmount:i.slotLabelWillUnmount},e=>p("div",{className:"fc-timegrid-slot-label-frame fc-scrollgrid-shrink-frame"},p(e,{elTag:"div",elClasses:["fc-timegrid-slot-label-cushion","fc-scrollgrid-shrink-cushion"]})))})}function Ol(e){return e.text}class Nl extends Vn{render(){return this.props.slatMetas.map(e=>p("tr",{key:e.key},p(Il,Object.assign({},e))))}}const Pl=an({week:"short"});class Hl extends ls{constructor(){super(...arguments),this.allDaySplitter=new kl,this.headerElRef={current:null},this.rootElRef={current:null},this.scrollerElRef={current:null},this.state={slatCoords:null},this.handleScrollTopRequest=e=>{let t=this.scrollerElRef.current;t&&(t.scrollTop=e)},this.renderHeadAxis=(e,t="")=>{let{options:n}=this.context,{dateProfile:r}=this.props,i=r.renderRange,s=1===xt(i.start,i.end)?qi(this.context,i.start,"week"):{};return n.weekNumbers&&"day"===e?p(po,{elTag:"th",elClasses:["fc-timegrid-axis","fc-scrollgrid-shrink"],elAttrs:{"aria-hidden":!0},date:i.start,defaultFormat:Pl},e=>p("div",{className:["fc-timegrid-axis-frame","fc-scrollgrid-shrink-frame","fc-timegrid-axis-frame-liquid"].join(" "),style:{height:t}},p(e,{elTag:"a",elClasses:["fc-timegrid-axis-cushion","fc-scrollgrid-shrink-cushion","fc-scrollgrid-sync-inner"],elAttrs:s}))):p("th",{"aria-hidden":!0,className:"fc-timegrid-axis"},p("div",{className:"fc-timegrid-axis-frame",style:{height:t}}))},this.renderTableRowAxis=e=>{let{options:t,viewApi:n}=this.context,r={text:t.allDayText,view:n};return p($n,{elTag:"td",elClasses:["fc-timegrid-axis","fc-scrollgrid-shrink"],elAttrs:{"aria-hidden":!0},renderProps:r,generatorName:"allDayContent",customGenerator:t.allDayContent,defaultGenerator:Bl,classNameGenerator:t.allDayClassNames,didMount:t.allDayDidMount,willUnmount:t.allDayWillUnmount},t=>p("div",{className:["fc-timegrid-axis-frame","fc-scrollgrid-shrink-frame",null==e?" fc-timegrid-axis-frame-liquid":""].join(" "),style:{height:e}},p(t,{elTag:"span",elClasses:["fc-timegrid-axis-cushion","fc-scrollgrid-shrink-cushion","fc-scrollgrid-sync-inner"]})))},this.handleSlatCoords=e=>{this.setState({slatCoords:e})}}renderSimpleLayout(e,t,n){let{context:r,props:i}=this,s=[],o=eo(r.options);return e&&s.push({type:"header",key:"header",isSticky:o,chunk:{elRef:this.headerElRef,tableClassName:"fc-col-header",rowContent:e}}),t&&(s.push({type:"body",key:"all-day",chunk:{content:t}}),s.push({type:"body",key:"all-day-divider",outerContent:p("tr",{role:"presentation",className:"fc-scrollgrid-section"},p("td",{className:"fc-timegrid-divider "+r.theme.getClass("tableCellShaded")}))})),s.push({type:"body",key:"body",liquid:!0,expandRows:Boolean(r.options.expandRows),chunk:{scrollerElRef:this.scrollerElRef,content:n}}),p(Kn,{elRef:this.rootElRef,elClasses:["fc-timegrid"],viewSpec:r.viewSpec},p(no,{liquid:!i.isHeightAuto&&!i.forPrint,collapsibleWidth:i.forPrint,cols:[{width:"shrink"}],sections:s}))}renderHScrollLayout(e,t,n,r,i,s,o){let a=this.context.pluginHooks.scrollGridImpl;if(!a)throw new Error("No ScrollGrid implementation");let{context:l,props:c}=this,d=!c.forPrint&&eo(l.options),u=!c.forPrint&&to(l.options),h=[];e&&h.push({type:"header",key:"header",isSticky:d,syncRowHeights:!0,chunks:[{key:"axis",rowContent:e=>p("tr",{role:"presentation"},this.renderHeadAxis("day",e.rowSyncHeights[0]))},{key:"cols",elRef:this.headerElRef,tableClassName:"fc-col-header",rowContent:e}]}),t&&(h.push({type:"body",key:"all-day",syncRowHeights:!0,chunks:[{key:"axis",rowContent:e=>p("tr",{role:"presentation"},this.renderTableRowAxis(e.rowSyncHeights[0]))},{key:"cols",content:t}]}),h.push({key:"all-day-divider",type:"body",outerContent:p("tr",{role:"presentation",className:"fc-scrollgrid-section"},p("td",{colSpan:2,className:"fc-timegrid-divider "+l.theme.getClass("tableCellShaded")}))}));let f=l.options.nowIndicator;return h.push({type:"body",key:"body",liquid:!0,expandRows:Boolean(l.options.expandRows),chunks:[{key:"axis",content:e=>p("div",{className:"fc-timegrid-axis-chunk"},p("table",{"aria-hidden":!0,style:{height:e.expandRows?e.clientHeight:""}},e.tableColGroupNode,p("tbody",null,p(Nl,{slatMetas:s}))),p("div",{className:"fc-timegrid-now-indicator-container"},p(Ii,{unit:f?"minute":"day"},e=>{let t=f&&o&&o.safeComputeTop(e);return"number"==typeof t?p(oo,{elClasses:["fc-timegrid-now-indicator-arrow"],elStyle:{top:t},isAxis:!0,date:e}):null})))},{key:"cols",scrollerElRef:this.scrollerElRef,content:n}]}),u&&h.push({key:"footer",type:"footer",isSticky:!0,chunks:[{key:"axis",content:Ks},{key:"cols",content:Ks}]}),p(Kn,{elRef:this.rootElRef,elClasses:["fc-timegrid"],viewSpec:l.viewSpec},p(a,{liquid:!c.isHeightAuto&&!c.forPrint,forPrint:c.forPrint,collapsibleWidth:!1,colGroups:[{width:"shrink",cols:[{width:"shrink"}]},{cols:[{span:r,minWidth:i}]}],sections:h}))}getAllDayMaxEventProps(){let{dayMaxEvents:e,dayMaxEventRows:t}=this.context.options;return!0!==e&&!0!==t||(e=void 0,t=5),{dayMaxEvents:e,dayMaxEventRows:t}}}function Bl(e){return e.text}class jl{constructor(e,t,n){this.positions=e,this.dateProfile=t,this.slotDuration=n}safeComputeTop(e){let{dateProfile:t}=this;if(ar(t.currentRange,e)){let n=Mt(e),r=e.valueOf()-n.valueOf();if(r>=yt(t.slotMinTime)&&r{let o={time:i.time,date:t.dateEnv.toDate(i.date),view:t.viewApi};return p("tr",{key:i.key,ref:r.createRef(i.key)},e.axis&&p(Il,Object.assign({},i)),p($n,{elTag:"td",elClasses:["fc-timegrid-slot","fc-timegrid-slot-lane",!i.isLabeled&&"fc-timegrid-slot-minor"],elAttrs:{"data-time":i.isoTimeStr},renderProps:o,generatorName:"slotLaneContent",customGenerator:n.slotLaneContent,classNameGenerator:n.slotLaneClassNames,didMount:n.slotLaneDidMount,willUnmount:n.slotLaneWillUnmount}))}))}}class Ul extends Vn{constructor(){super(...arguments),this.rootElRef={current:null},this.slatElRefs=new Ws}render(){let{props:e,context:t}=this;return p("div",{ref:this.rootElRef,className:"fc-timegrid-slots"},p("table",{"aria-hidden":!0,className:t.theme.getClass("table"),style:{minWidth:e.tableMinWidth,width:e.clientWidth,height:e.minHeight}},e.tableColGroupNode,p(zl,{slatElRefs:this.slatElRefs,axis:e.axis,slatMetas:e.slatMetas})))}componentDidMount(){this.updateSizing()}componentDidUpdate(){this.updateSizing()}componentWillUnmount(){this.props.onCoords&&this.props.onCoords(null)}updateSizing(){let{context:e,props:t}=this;if(t.onCoords&&null!==t.clientWidth){this.rootElRef.current.offsetHeight&&t.onCoords(new jl(new rs(this.rootElRef.current,(n=this.slatElRefs.currentMap,t.slatMetas.map(e=>n[e.key])),!1,!0),this.props.dateProfile,e.options.slotDuration))}var n}}function Ll(e,t){let n,r=[];for(n=0;nec(e.hiddenSegs,e),defaultGenerator:Vl,forceTimed:!0},e=>p(e,{elTag:"div",elClasses:["fc-timegrid-more-link-inner","fc-sticky"]}))}}function Vl(e){return e.shortText}function Gl(e,t,n){let r=new cs;null!=t&&(r.strictOrder=t),null!=n&&(r.maxStackCnt=n);let i=hs(r.addSegs(e)),s=function(e){const{entriesByLevel:t}=e,n=Zl((e,t)=>e+":"+t,(r,i)=>{let s=Ql(function(e,t,n){let{levelCoords:r,entriesByLevel:i}=e,s=i[t][n],o=r[t]+s.thickness,a=r.length,l=t;for(;lus(e),(e,r,i)=>{let s,{nextLevelNodes:o,thickness:a}=e,l=a+i,c=a/l,d=[];if(o.length)for(let e of o)if(void 0===s){let t=n(e,r,l);s=t[0],d.push(t[1])}else{let t=n(e,s,0);d.push(t[1])}else s=t;let u=(s-r)*c;return[s-u,Object.assign(Object.assign({},e),{thickness:u,nextLevelNodes:d})]});return e.map(e=>n(e,0,0)[1])}(s,1),{segRects:function(e){let t=[];const n=Zl((e,t,n)=>us(e),(e,n,i)=>{let s=Object.assign(Object.assign({},e),{levelCoord:n,stackDepth:i,stackForward:0});return t.push(s),s.stackForward=r(e.nextLevelNodes,n+e.thickness,i+1)+1});function r(e,t,r){let i=0;for(let s of e)i=Math.max(n(s,t,r),i);return i}return r(e,0,0),t}(s),hiddenGroups:i}}function Ql(e,t){if(!e)return[[],0];let{level:n,lateralStart:r,lateralEnd:i}=e,s=r,o=[];for(;s{let i=e(...r);return i in n?n[i]:n[i]=t(...r)}}function Xl(e,t,n=null,r=0){let i=[];if(n)for(let s=0;sp("div",{className:"fc-timegrid-col-frame"},p("div",{className:"fc-timegrid-col-bg"},this.renderFillSegs(e.businessHourSegs,"non-business"),this.renderFillSegs(e.bgEventSegs,"bg-event"),this.renderFillSegs(e.dateSelectionSegs,"highlight")),p("div",{className:"fc-timegrid-col-events"},this.renderFgSegs(o,s,!1,!1,!1)),p("div",{className:"fc-timegrid-col-events"},this.renderFgSegs(i,{},Boolean(e.eventDrag),Boolean(e.eventResize),Boolean(r),"mirror")),p("div",{className:"fc-timegrid-now-indicator-container"},this.renderNowIndicator(e.nowIndicatorSegs)),co(n)&&p(t,{elTag:"div",elClasses:["fc-timegrid-col-misc"]})))}renderFgSegs(e,t,n,r,i,s){let{props:o}=this;return o.forPrint?ec(e,o):this.renderPositionedFgSegs(e,t,n,r,i,s)}renderPositionedFgSegs(e,t,n,r,i,s){let{eventMaxStack:o,eventShortHeight:a,eventOrderStrict:l,eventMinHeight:c}=this.context.options,{date:d,slatCoords:u,eventSelection:h,todayRange:f,nowDate:g}=this.props,m=n||r||i,v=Xl(e,d,u,c),{segPlacements:b,hiddenGroups:E}=function(e,t,n,r){let i=[],s=[];for(let n=0;n{let{seg:o,rect:l}=e,c=o.eventRange.instance.instanceId,d=m||Boolean(!t[c]&&l),u=tc(l&&l.span),v=!m&&l?this.computeSegHStyle(l):{left:0,right:0},y=Boolean(l)&&l.stackForward>0,b=Boolean(l)&&l.span.end-l.span.start{let c=tc(e.span),d=(u=e.entries,h=t,u.map(e=>h[e.index]));var u,h;return p(Fl,{key:Ut(Ao(d)),hiddenSegs:d,top:c.top,bottom:c.bottom,extraDateSpan:n,dateProfile:r,todayRange:i,nowDate:s,eventSelection:o,eventDrag:a,eventResize:l})}))}renderFillSegs(e,t){let{props:n,context:r}=this,i=Xl(e,n.date,n.slatCoords,r.options.eventMinHeight).map((r,i)=>{let s=e[i];return p("div",{key:mi(s.eventRange),className:"fc-timegrid-bg-harness",style:tc(r)},"bg-event"===t?p(ho,Object.assign({seg:s},gi(s,n.todayRange,n.nowDate))):go(t))});return p(y,null,i)}renderNowIndicator(e){let{slatCoords:t,date:n}=this.props;return t?e.map((e,r)=>p(oo,{key:r,elClasses:["fc-timegrid-now-indicator-line"],elStyle:{top:t.computeDateTop(e.start,n)},isAxis:!1,date:n})):null}computeSegHStyle(e){let t,n,{isRtl:r,options:i}=this.context,s=i.slotEventOverlap,o=e.levelCoord,a=e.levelCoord+e.thickness;s&&(a=Math.min(1,o+2*(a-o))),r?(t=1-a,n=o):(t=o,n=1-a);let l={zIndex:e.stackDepth+1,left:100*t+"%",right:100*n+"%"};return s&&!e.stackForward&&(l[r?"marginLeft":"marginRight"]=20),l}}function ec(e,{todayRange:t,nowDate:n,eventSelection:r,eventDrag:i,eventResize:s}){let o=(i?i.affectedInstances:null)||(s?s.affectedInstances:null)||{};return p(y,null,e.map(e=>{let i=e.eventRange.instance.instanceId;return p("div",{key:i,style:{visibility:o[i]?"hidden":""}},p(Jl,Object.assign({seg:e,isDragging:!1,isResizing:!1,isDateSelecting:!1,isSelected:i===r,isShort:!1},gi(e,t,n))))}))}function tc(e){return e?{top:e.start,bottom:-e.end}:{top:"",bottom:""}}class nc extends Vn{constructor(){super(...arguments),this.splitFgEventSegs=Gt(Ll),this.splitBgEventSegs=Gt(Ll),this.splitBusinessHourSegs=Gt(Ll),this.splitNowIndicatorSegs=Gt(Ll),this.splitDateSelectionSegs=Gt(Ll),this.splitEventDrag=Gt(Wl),this.splitEventResize=Gt(Wl),this.rootElRef={current:null},this.cellElRefs=new Ws}render(){let{props:e,context:t}=this,n=t.options.nowIndicator&&e.slatCoords&&e.slatCoords.safeComputeTop(e.nowDate),r=e.cells.length,i=this.splitFgEventSegs(e.fgEventSegs,r),s=this.splitBgEventSegs(e.bgEventSegs,r),o=this.splitBusinessHourSegs(e.businessHourSegs,r),a=this.splitNowIndicatorSegs(e.nowIndicatorSegs,r),l=this.splitDateSelectionSegs(e.dateSelectionSegs,r),c=this.splitEventDrag(e.eventDrag,r),d=this.splitEventResize(e.eventResize,r);return p("div",{className:"fc-timegrid-cols",ref:this.rootElRef},p("table",{role:"presentation",style:{minWidth:e.tableMinWidth,width:e.clientWidth}},e.tableColGroupNode,p("tbody",{role:"presentation"},p("tr",{role:"row"},e.axis&&p("td",{"aria-hidden":!0,className:"fc-timegrid-col fc-timegrid-axis"},p("div",{className:"fc-timegrid-col-frame"},p("div",{className:"fc-timegrid-now-indicator-container"},"number"==typeof n&&p(oo,{elClasses:["fc-timegrid-now-indicator-arrow"],elStyle:{top:n},isAxis:!0,date:e.nowDate})))),e.cells.map((t,n)=>p(Kl,{key:t.key,elRef:this.cellElRefs.createRef(t.key),dateProfile:e.dateProfile,date:t.date,nowDate:e.nowDate,todayRange:e.todayRange,extraRenderProps:t.extraRenderProps,extraDataAttrs:t.extraDataAttrs,extraClassNames:t.extraClassNames,extraDateSpan:t.extraDateSpan,fgEventSegs:i[n],bgEventSegs:s[n],businessHourSegs:o[n],nowIndicatorSegs:a[n],dateSelectionSegs:l[n],eventDrag:c[n],eventResize:d[n],slatCoords:e.slatCoords,eventSelection:e.eventSelection,forPrint:e.forPrint}))))))}componentDidMount(){this.updateCoords()}componentDidUpdate(){this.updateCoords()}updateCoords(){let{props:e}=this;var t;e.onColCoords&&null!==e.clientWidth&&e.onColCoords(new rs(this.rootElRef.current,(t=this.cellElRefs.currentMap,e.cells.map(e=>t[e.key])),!0,!1))}}class rc extends ls{constructor(){super(...arguments),this.processSlotOptions=Gt(ic),this.state={slatCoords:null},this.handleRootEl=e=>{e?this.context.registerInteractiveComponent(this,{el:e,isHitComboAllowed:this.props.isHitComboAllowed}):this.context.unregisterInteractiveComponent(this)},this.handleScrollRequest=e=>{let{onScrollTopRequest:t}=this.props,{slatCoords:n}=this.state;if(t&&n){if(e.time){let r=n.computeTimeTop(e.time);r=Math.ceil(r),r&&(r+=1),t(r)}return!0}return!1},this.handleColCoords=e=>{this.colCoords=e},this.handleSlatCoords=e=>{this.setState({slatCoords:e}),this.props.onSlatCoords&&this.props.onSlatCoords(e)}}render(){let{props:e,state:t}=this;return p("div",{className:"fc-timegrid-body",ref:this.handleRootEl,style:{width:e.clientWidth,minWidth:e.tableMinWidth}},p(Ul,{axis:e.axis,dateProfile:e.dateProfile,slatMetas:e.slatMetas,clientWidth:e.clientWidth,minHeight:e.expandRows?e.clientHeight:"",tableMinWidth:e.tableMinWidth,tableColGroupNode:e.axis?e.tableColGroupNode:null,onCoords:this.handleSlatCoords}),p(nc,{cells:e.cells,axis:e.axis,dateProfile:e.dateProfile,businessHourSegs:e.businessHourSegs,bgEventSegs:e.bgEventSegs,fgEventSegs:e.fgEventSegs,dateSelectionSegs:e.dateSelectionSegs,eventSelection:e.eventSelection,eventDrag:e.eventDrag,eventResize:e.eventResize,todayRange:e.todayRange,nowDate:e.nowDate,nowIndicatorSegs:e.nowIndicatorSegs,clientWidth:e.clientWidth,tableMinWidth:e.tableMinWidth,tableColGroupNode:e.tableColGroupNode,slatCoords:t.slatCoords,onColCoords:this.handleColCoords,forPrint:e.forPrint}))}componentDidMount(){this.scrollResponder=this.context.createScrollResponder(this.handleScrollRequest)}componentDidUpdate(e){this.scrollResponder.update(e.dateProfile!==this.props.dateProfile)}componentWillUnmount(){this.scrollResponder.detach()}queryHit(e,t){let{dateEnv:n,options:r}=this.context,{colCoords:i}=this,{dateProfile:s}=this.props,{slatCoords:o}=this.state,{snapDuration:a,snapsPerSlot:l}=this.processSlotOptions(this.props.slotDuration,r.snapDuration),c=i.leftToIndex(e),d=o.positions.topToIndex(t);if(null!=c&&null!=d){let e=this.props.cells[c],r=o.positions.tops[d],u=o.positions.getHeight(d),h=(t-r)/u,f=d*l+Math.floor(h*l),g=this.props.cells[c].date,p=pt(s.slotMinTime,mt(a,f)),m=n.add(g,p),v=n.add(m,a);return{dateProfile:s,dateSpan:Object.assign({range:{start:m,end:v},allDay:!1},e.extraDateSpan),dayEl:i.els[c],rect:{left:i.lefts[c],right:i.rights[c],top:r,bottom:r+u},layer:0}}return null}}function ic(e,t){let n=t||e,r=bt(e,n);return null===r&&(n=e,r=1),{snapDuration:n,snapsPerSlot:r}}class sc extends ks{sliceRange(e,t){let n=[];for(let r=0;rp(rc,Object.assign({ref:this.timeColsRef},this.slicer.sliceProps(e,n,null,t,o),{forPrint:e.forPrint,axis:e.axis,dateProfile:n,slatMetas:e.slatMetas,slotDuration:e.slotDuration,cells:r.cells[0],tableColGroupNode:e.tableColGroupNode,tableMinWidth:e.tableMinWidth,clientWidth:e.clientWidth,clientHeight:e.clientHeight,expandRows:e.expandRows,nowDate:a,nowIndicatorSegs:i&&this.slicer.sliceNowDate(a,n,s,t,o),todayRange:l,onScrollTopRequest:e.onScrollTopRequest,onSlatCoords:e.onSlatCoords})))}}function ac(e,t,n){let r=[];for(let i of e.headerDates)r.push({start:n.add(i,t.slotMinTime),end:n.add(i,t.slotMaxTime)});return r}const lc=[{hours:1},{minutes:30},{minutes:15},{seconds:30},{seconds:15}];function cc(e,t,n,r,i){let s=new Date(0),o=e,a=ft(0),l=n||function(e){let t,n,r;for(t=lc.length-1;t>=0;t-=1)if(n=ft(lc[t]),r=bt(n,e),null!==r&&r>1)return n;return e}(r),c=[];for(;yt(o)table,.fc .fc-timegrid-slots{position:relative;z-index:1}.fc .fc-timegrid-slot{border-bottom:0;height:1.5em}.fc .fc-timegrid-slot:empty:before{content:"\\00a0"}.fc .fc-timegrid-slot-minor{border-top-style:dotted}.fc .fc-timegrid-slot-label-cushion{display:inline-block;white-space:nowrap}.fc .fc-timegrid-slot-label{vertical-align:middle}.fc .fc-timegrid-axis-cushion,.fc .fc-timegrid-slot-label-cushion{padding:0 4px}.fc .fc-timegrid-axis-frame-liquid{height:100%}.fc .fc-timegrid-axis-frame{align-items:center;display:flex;justify-content:flex-end;overflow:hidden}.fc .fc-timegrid-axis-cushion{flex-shrink:0;max-width:60px}.fc-direction-ltr .fc-timegrid-slot-label-frame{text-align:right}.fc-direction-rtl .fc-timegrid-slot-label-frame{text-align:left}.fc-liquid-hack .fc-timegrid-axis-frame-liquid{bottom:0;height:auto;left:0;position:absolute;right:0;top:0}.fc .fc-timegrid-col.fc-day-today{background-color:var(--fc-today-bg-color)}.fc .fc-timegrid-col-frame{min-height:100%;position:relative}.fc-media-screen.fc-liquid-hack .fc-timegrid-col-frame{bottom:0;height:auto;left:0;position:absolute;right:0;top:0}.fc-media-screen .fc-timegrid-cols{bottom:0;left:0;position:absolute;right:0;top:0}.fc-media-screen .fc-timegrid-cols>table{height:100%}.fc-media-screen .fc-timegrid-col-bg,.fc-media-screen .fc-timegrid-col-events,.fc-media-screen .fc-timegrid-now-indicator-container{left:0;position:absolute;right:0;top:0}.fc .fc-timegrid-col-bg{z-index:2}.fc .fc-timegrid-col-bg .fc-non-business{z-index:1}.fc .fc-timegrid-col-bg .fc-bg-event{z-index:2}.fc .fc-timegrid-col-bg .fc-highlight{z-index:3}.fc .fc-timegrid-bg-harness{left:0;position:absolute;right:0}.fc .fc-timegrid-col-events{z-index:3}.fc .fc-timegrid-now-indicator-container{bottom:0;overflow:hidden}.fc-direction-ltr .fc-timegrid-col-events{margin:0 2.5% 0 2px}.fc-direction-rtl .fc-timegrid-col-events{margin:0 2px 0 2.5%}.fc-timegrid-event-harness{position:absolute}.fc-timegrid-event-harness>.fc-timegrid-event{bottom:0;left:0;position:absolute;right:0;top:0}.fc-timegrid-event-harness-inset .fc-timegrid-event,.fc-timegrid-event.fc-event-mirror,.fc-timegrid-more-link{box-shadow:0 0 0 1px var(--fc-page-bg-color)}.fc-timegrid-event,.fc-timegrid-more-link{border-radius:3px;font-size:var(--fc-small-font-size)}.fc-timegrid-event{margin-bottom:1px}.fc-timegrid-event .fc-event-main{padding:1px 1px 0}.fc-timegrid-event .fc-event-time{font-size:var(--fc-small-font-size);margin-bottom:1px;white-space:nowrap}.fc-timegrid-event-short .fc-event-main-frame{flex-direction:row;overflow:hidden}.fc-timegrid-event-short .fc-event-time:after{content:"\\00a0-\\00a0"}.fc-timegrid-event-short .fc-event-title{font-size:var(--fc-small-font-size)}.fc-timegrid-more-link{background:var(--fc-more-link-bg-color);color:var(--fc-more-link-text-color);cursor:pointer;margin-bottom:1px;position:absolute;z-index:9999}.fc-timegrid-more-link-inner{padding:3px 2px;top:0}.fc-direction-ltr .fc-timegrid-more-link{right:0}.fc-direction-rtl .fc-timegrid-more-link{left:0}.fc .fc-timegrid-now-indicator-arrow,.fc .fc-timegrid-now-indicator-line{pointer-events:none}.fc .fc-timegrid-now-indicator-line{border-color:var(--fc-now-indicator-color);border-style:solid;border-width:1px 0 0;left:0;position:absolute;right:0;z-index:4}.fc .fc-timegrid-now-indicator-arrow{border-color:var(--fc-now-indicator-color);border-style:solid;margin-top:-5px;position:absolute;z-index:4}.fc-direction-ltr .fc-timegrid-now-indicator-arrow{border-bottom-color:transparent;border-top-color:transparent;border-width:5px 0 5px 6px;left:0}.fc-direction-rtl .fc-timegrid-now-indicator-arrow{border-bottom-color:transparent;border-top-color:transparent;border-width:5px 6px 5px 0;right:0}');var uc=Oo({name:"@fullcalendar/timegrid",initialView:"timeGridWeek",optionRefiners:{allDaySlot:Boolean},views:{timeGrid:{component:class extends Hl{constructor(){super(...arguments),this.buildTimeColsModel=Gt(dc),this.buildSlatMetas=Gt(cc)}render(){let{options:e,dateEnv:t,dateProfileGenerator:n}=this.context,{props:r}=this,{dateProfile:i}=r,s=this.buildTimeColsModel(i,n),o=this.allDaySplitter.splitProps(r),a=this.buildSlatMetas(i.slotMinTime,i.slotMaxTime,e.slotLabelInterval,e.slotDuration,t),{dayMinWidth:l}=e,c=!l,d=l,u=e.dayHeaders&&p(Rs,{dates:s.headerDates,dateProfile:i,datesRepDistinctDays:!0,renderIntro:c?this.renderHeadAxis:null}),h=!1!==e.allDaySlot&&(t=>p(Cl,Object.assign({},o.allDay,{dateProfile:i,dayTableModel:s,nextDayThreshold:e.nextDayThreshold,tableMinWidth:t.tableMinWidth,colGroupNode:t.tableColGroupNode,renderRowIntro:c?this.renderTableRowAxis:null,showWeekNumbers:!1,expandRows:!1,headerAlignElRef:this.headerElRef,clientWidth:t.clientWidth,clientHeight:t.clientHeight,forPrint:r.forPrint},this.getAllDayMaxEventProps()))),f=t=>p(oc,Object.assign({},o.timed,{dayTableModel:s,dateProfile:i,axis:c,slotDuration:e.slotDuration,slatMetas:a,forPrint:r.forPrint,tableColGroupNode:t.tableColGroupNode,tableMinWidth:t.tableMinWidth,clientWidth:t.clientWidth,clientHeight:t.clientHeight,onSlatCoords:this.handleSlatCoords,expandRows:t.expandRows,onScrollTopRequest:this.handleScrollTopRequest}));return d?this.renderHScrollLayout(u,h,f,s.colCnt,l,a,this.state.slatCoords):this.renderSimpleLayout(u,h,f)}},usesMinMaxTime:!0,allDaySlot:!0,slotDuration:"00:30:00",slotEventOverlap:!0},timeGridDay:{type:"timeGrid",duration:{days:1}},timeGridWeek:{type:"timeGrid",duration:{weeks:1}}}});class hc extends Vn{constructor(){super(...arguments),this.state={textId:Le()}}render(){let{theme:e,dateEnv:t,options:n,viewApi:r}=this.context,{cellId:i,dayDate:s,todayRange:o}=this.props,{textId:a}=this.state,l=Fi(s,o),c=n.listDayFormat?t.format(s,n.listDayFormat):"",d=n.listDaySideFormat?t.format(s,n.listDaySideFormat):"",u=Object.assign({date:t.toDate(s),view:r,textId:a,text:c,sideText:d,navLinkAttrs:qi(this.context,s),sideNavLinkAttrs:qi(this.context,s,"day",!1)},l);return p($n,{elTag:"tr",elClasses:["fc-list-day",...Vi(l,e)],elAttrs:{"data-date":Lt(s)},renderProps:u,generatorName:"dayHeaderContent",customGenerator:n.dayHeaderContent,defaultGenerator:fc,classNameGenerator:n.dayHeaderClassNames,didMount:n.dayHeaderDidMount,willUnmount:n.dayHeaderWillUnmount},t=>p("th",{scope:"colgroup",colSpan:3,id:i,"aria-labelledby":a},p(t,{elTag:"div",elClasses:["fc-list-day-cushion",e.getClass("tableCellShaded")]})))}}function fc(e){return p(y,null,e.text&&p("a",Object.assign({id:e.textId,className:"fc-list-day-text"},e.navLinkAttrs),e.text),e.sideText&&p("a",Object.assign({"aria-hidden":!0,className:"fc-list-day-side-text"},e.sideNavLinkAttrs),e.sideText))}const gc=an({hour:"numeric",minute:"2-digit",meridiem:"short"});class pc extends Vn{render(){let{props:e,context:t}=this,{options:n}=t,{seg:r,timeHeaderId:i,eventHeaderId:s,dateHeaderId:o}=e,a=n.eventTimeFormat||gc;return p(ro,Object.assign({},e,{elTag:"tr",elClasses:["fc-list-event",r.eventRange.def.url&&"fc-event-forced-url"],defaultGenerator:()=>function(e,t){let n=vi(e,t);return p("a",Object.assign({},n),e.eventRange.def.title)}(r,t),seg:r,timeText:"",disableDragging:!0,disableResizing:!0}),(e,n)=>p(y,null,function(e,t,n,r,i){let{options:s}=n;if(!1!==s.displayEventTime){let o,a=e.eventRange.def,l=e.eventRange.instance,c=!1;if(a.allDay?c=!0:dr(e.eventRange.range)?e.isStart?o=fi(e,t,n,null,null,l.range.start,e.end):e.isEnd?o=fi(e,t,n,null,null,e.start,l.range.end):c=!0:o=fi(e,t,n),c){let e={text:n.options.allDayText,view:n.viewApi};return p($n,{elTag:"td",elClasses:["fc-list-event-time"],elAttrs:{headers:`${r} ${i}`},renderProps:e,generatorName:"allDayContent",customGenerator:s.allDayContent,defaultGenerator:mc,classNameGenerator:s.allDayClassNames,didMount:s.allDayDidMount,willUnmount:s.allDayWillUnmount})}return p("td",{className:"fc-list-event-time"},o)}return null}(r,a,t,i,o),p("td",{"aria-hidden":!0,className:"fc-list-event-graphic"},p("span",{className:"fc-list-event-dot",style:{borderColor:n.borderColor||n.backgroundColor}})),p(e,{elTag:"td",elClasses:["fc-list-event-title"],elAttrs:{headers:`${s} ${o}`}})))}}function mc(e){return e.text}function vc(e){return e.text}function yc(e){let t=Mt(e.renderRange.start),n=e.renderRange.end,r=[],i=[];for(;t*{border-left:0;border-right:0}.fc .fc-list-sticky .fc-list-day>*{background:var(--fc-page-bg-color);position:sticky;top:0}.fc .fc-list-table thead{left:-10000px;position:absolute}.fc .fc-list-table tbody>tr:first-child th{border-top:0}.fc .fc-list-table th{padding:0}.fc .fc-list-day-cushion,.fc .fc-list-table td{padding:8px 14px}.fc .fc-list-day-cushion:after{clear:both;content:"";display:table}.fc-theme-standard .fc-list-day-cushion{background-color:var(--fc-neutral-bg-color)}.fc-direction-ltr .fc-list-day-text,.fc-direction-rtl .fc-list-day-side-text{float:left}.fc-direction-ltr .fc-list-day-side-text,.fc-direction-rtl .fc-list-day-text{float:right}.fc-direction-ltr .fc-list-table .fc-list-event-graphic{padding-right:0}.fc-direction-rtl .fc-list-table .fc-list-event-graphic{padding-left:0}.fc .fc-list-event.fc-event-forced-url{cursor:pointer}.fc .fc-list-event:hover td{background-color:var(--fc-list-event-hover-bg-color)}.fc .fc-list-event-graphic,.fc .fc-list-event-time{white-space:nowrap;width:1px}.fc .fc-list-event-dot{border:calc(var(--fc-list-event-dot-width)/2) solid var(--fc-event-border-color);border-radius:calc(var(--fc-list-event-dot-width)/2);box-sizing:content-box;display:inline-block;height:0;width:0}.fc .fc-list-event-title a{color:inherit;text-decoration:none}.fc .fc-list-event.fc-event-forced-url:hover a{text-decoration:underline}');function bc(e){return!1===e?null:an(e)}var Ec=Oo({name:"@fullcalendar/list",optionRefiners:{listDayFormat:bc,listDaySideFormat:bc,noEventsClassNames:yn,noEventsContent:yn,noEventsDidMount:yn,noEventsWillUnmount:yn},views:{list:{component:class extends ls{constructor(){super(...arguments),this.computeDateVars=Gt(yc),this.eventStoreToSegs=Gt(this._eventStoreToSegs),this.state={timeHeaderId:Le(),eventHeaderId:Le(),dateHeaderIdRoot:Le()},this.setRootEl=e=>{e?this.context.registerInteractiveComponent(this,{el:e}):this.context.unregisterInteractiveComponent(this)}}render(){let{props:e,context:t}=this,{dayDates:n,dayRanges:r}=this.computeDateVars(e.dateProfile),i=this.eventStoreToSegs(e.eventStore,e.eventUiBases,r);return p(Kn,{elRef:this.setRootEl,elClasses:["fc-list",t.theme.getClass("table"),!1!==t.options.stickyHeaderDates?"fc-list-sticky":""],viewSpec:t.viewSpec},p(Ls,{liquid:!e.isHeightAuto,overflowX:e.isHeightAuto?"visible":"hidden",overflowY:e.isHeightAuto?"visible":"auto"},i.length>0?this.renderSegList(i,n):this.renderEmptyMessage()))}renderEmptyMessage(){let{options:e,viewApi:t}=this.context,n={text:e.noEventsText,view:t};return p($n,{elTag:"div",elClasses:["fc-list-empty"],renderProps:n,generatorName:"noEventsContent",customGenerator:e.noEventsContent,defaultGenerator:vc,classNameGenerator:e.noEventsClassNames,didMount:e.noEventsDidMount,willUnmount:e.noEventsWillUnmount},e=>p(e,{elTag:"div",elClasses:["fc-list-empty-cushion"]}))}renderSegList(e,t){let{theme:n,options:r}=this.context,{timeHeaderId:i,eventHeaderId:s,dateHeaderIdRoot:o}=this.state,a=function(e){let t,n,r=[];for(t=0;t{let c=[];for(let n=0;n{e&&this.updateSize()}}render(){const{context:e,props:t,state:n}=this,{options:r}=e,{clientWidth:i,clientHeight:s}=n,o=n.monthHPadding||0,a=Math.min(null!=i?Math.floor(i/(r.multiMonthMinWidth+o)):1,r.multiMonthMaxColumns)||1,l=100/a+"%",c=null==i?null:i/a-o,d=null!=i&&1===a,u=this.splitDateProfileByMonth(e.dateProfileGenerator,t.dateProfile,e.dateEnv,!d&&r.fixedWeekCount,r.showNonCurrentDates),h=this.buildMonthFormat(r.multiMonthTitleFormat,u),f=["fc-multimonth",d?"fc-multimonth-singlecol":"fc-multimonth-multicol",null!=c&&c<400?"fc-multimonth-compact":"",t.isHeightAuto?"":"fc-scroller"];return p(Kn,{elRef:this.scrollElRef,elClasses:f,viewSpec:e.viewSpec},u.map((e,n)=>{const r=Wt(e.currentRange.start);return p(Sc,Object.assign({},t,{key:r,isoDateStr:r,elRef:0===n?this.firstMonthElRef:void 0,titleFormat:h,dateProfile:e,width:l,tableWidth:c,clientWidth:i,clientHeight:s}))}))}componentDidMount(){this.updateSize(),this.context.addResizeHandler(this.handleSizing),this.requestScrollReset()}componentDidUpdate(e){Cn(e,this.props)||this.handleSizing(!1),e.dateProfile!==this.props.dateProfile?this.requestScrollReset():this.flushScrollReset()}componentWillUnmount(){this.context.removeResizeHandler(this.handleSizing)}updateSize(){const e=this.scrollElRef.current,t=this.firstMonthElRef.current;e&&this.setState({clientWidth:e.clientWidth,clientHeight:e.clientHeight}),t&&e&&null==this.state.monthHPadding&&this.setState({monthHPadding:e.clientWidth-t.firstChild.offsetWidth})}requestScrollReset(){this.needsScrollReset=!0,this.flushScrollReset()}flushScrollReset(){if(this.needsScrollReset&&null!=this.state.monthHPadding){const{currentDate:e}=this.props.dateProfile,t=this.scrollElRef.current,n=t.querySelector(`[data-date="${Wt(e)}"]`);t.scrollTop=n.getBoundingClientRect().top-this.firstMonthElRef.current.getBoundingClientRect().top,this.needsScrollReset=!1}}shouldComponentUpdate(){return!0}},dateProfileGeneratorClass:xl,multiMonthMinWidth:350,multiMonthMaxColumns:3},multiMonthYear:{type:"multiMonth",duration:{years:1},fixedWeekCount:!0,showNonCurrentDates:!1}}});return aa.push(Ka,Tl,uc,Ec,_c),e.Calendar=class extends Ni{constructor(e,t={}){super(),this.isRendering=!1,this.isRendered=!1,this.currentClassNames=[],this.customContentRenderId=0,this.handleAction=e=>{switch(e.type){case"SET_EVENT_DRAG":case"SET_EVENT_RESIZE":this.renderRunner.tryDrain()}},this.handleData=e=>{this.currentData=e,this.renderRunner.request(e.calendarOptions.rerenderDelay)},this.handleRenderRequest=()=>{if(this.isRendering){this.isRendered=!0;let{currentData:e}=this;Bn(()=>{U(p(_i,{options:e.calendarOptions,theme:e.theme,emitter:e.emitter},(t,n,r,i)=>(this.setClassNames(t),this.setHeight(n),p(Xn.Provider,{value:this.customContentRenderId},p(xa,Object.assign({isHeightAuto:r,forPrint:i},e))))),this.el)})}else this.isRendered&&(this.isRendered=!1,U(null,this.el),this.setClassNames([]),this.setHeight(""))},function(e){e.isConnected&&e.getRootNode&&_e(e.getRootNode())}(e),this.el=e,this.renderRunner=new Me(this.handleRenderRequest),new ua({optionOverrides:t,calendarApi:this,onAction:this.handleAction,onData:this.handleData})}render(){let e=this.isRendering;e?this.customContentRenderId+=1:this.isRendering=!0,this.renderRunner.request(),e&&this.updateSize()}destroy(){this.isRendering&&(this.isRendering=!1,this.renderRunner.request())}updateSize(){Bn(()=>{super.updateSize()})}batchRendering(e){this.renderRunner.pause("batchRendering"),e(),this.renderRunner.resume("batchRendering")}pauseRendering(){this.renderRunner.pause("pauseRendering")}resumeRendering(){this.renderRunner.resume("pauseRendering",!0)}resetOptions(e,t){this.currentDataManager.resetOptions(e,t)}setClassNames(e){if(!St(e,this.currentClassNames)){let{classList:t}=this.el;for(let e of this.currentClassNames)t.remove(e);for(let n of e)t.add(n);this.currentClassNames=e}}setHeight(e){je(this.el,"height",e)}},e.Draggable=class{constructor(e,t={}){this.handlePointerDown=e=>{let{dragging:t}=this,{minDistance:n,longPressDelay:r}=this.settings;t.minDistance=null!=n?n:e.isTouch?0:cn.eventDragMinDistance,t.delay=e.isTouch?null!=r?r:cn.longPressDelay:0},this.handleDragStart=e=>{e.isTouch&&this.dragging.delay&&e.subjectEl.classList.contains("fc-event")&&this.dragging.mirror.getMirrorEl().classList.add("fc-event-selected")},this.settings=t;let n=this.dragging=new Wa(e);n.touchScrollAllowed=!1,null!=t.itemSelector&&(n.pointer.selector=t.itemSelector),null!=t.appendTo&&(n.mirror.parentNode=t.appendTo),n.emitter.on("pointerdown",this.handlePointerDown),n.emitter.on("dragstart",this.handleDragStart),new $a(n,t.eventData)}destroy(){this.dragging.destroy()}},e.Internal=Co,e.JsonRequestError=wi,e.Preact=Ro,e.ThirdPartyDraggable=class{constructor(e,t){let n=document;e===document||e instanceof Element?(n=e,t=t||{}):t=e||{};let r=this.dragging=new Ja(n);"string"==typeof t.itemSelector?r.pointer.selector=t.itemSelector:n===document&&(r.pointer.selector="[data-event]"),"string"==typeof t.mirrorSelector&&(r.mirrorSelector=t.mirrorSelector),new $a(r,t.eventData).hitDragging.disablePointCheck=!0}destroy(){this.dragging.destroy()}},e.createPlugin=Oo,e.formatDate=function(e,t={}){let n=ka(t),r=an(t),i=n.createMarkerMeta(e);return i?n.format(i.marker,r,{forcedTzo:i.forcedTzo}):""},e.formatRange=function(e,t,n){let r=ka("object"==typeof n&&n?n:{}),i=an(n),s=r.createMarkerMeta(e),o=r.createMarkerMeta(t);return s&&o?r.formatRange(s.marker,o.marker,i,{forcedStartTzo:s.forcedTzo,forcedEndTzo:o.forcedTzo,isEndExclusive:n.isEndExclusive,defaultSeparator:cn.defaultRangeSeparator}):""},e.globalLocales=xo,e.globalPlugins=aa,e.sliceEvents=function(e,t){return ni(e.eventStore,e.eventUiBases,e.dateProfile.activeRange,t?e.nextDayThreshold:null).fg},e.version="6.1.19",Object.defineProperty(e,"__esModule",{value:!0}),e}({}); return FullCalendar; } // Source: src/shared/time-dates.js function createTimeDates() { const months = "JAN FEB MAR APR MAY JUN JUL AUG SEP OCT NOV DEC".split(" "); function parse(value) { const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(String(value)); if (!match) return null; const date = new Date(`${value}T00:00:00.000Z`); return Number.isFinite(date.getTime()) && date.toISOString().slice(0, 10) === value ? date : null; } function fromApi(value) { const match = /^(\d{2})-([A-Z]{3})-(\d{4})$/.exec(String(value)); if (!match) return null; const month = months.indexOf(match[2]) + 1; return parse(`${match[3]}-${String(month).padStart(2, "0")}-${match[1]}`); } function toApi(value) { const date = parse(value); if (!date) throw new Error("Select a valid reference date."); return `${String(date.getUTCDate()).padStart(2, "0")}-${months[date.getUTCMonth()]}-${date.getUTCFullYear()}`; } function shift(value, days) { const date = parse(value); if (!date) return value; date.setUTCDate(date.getUTCDate() + days); return date.toISOString().slice(0, 10); } function today() { const date = new Date(); return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`; } return { parse, fromApi, toApi, shift, today }; } // Source: src/features/manage-time/view.js function createManageTime({ MODAL_ID, environment, searchableSelect, dates, requestWeek, requestServices = async () => [], requestExpand = async () => [], requestTaskTypes = async () => [] }) { const { document } = environment; let active = null; let ready = false; let launcher = null; function setReady(value) { ready = !!value; if (launcher) { launcher.disabled = !ready; launcher.title = ready ? "Manage Time" : "Waiting for SR session ready"; } } function node(tag, className = "", text) { const el = document.createElement(tag); el.className = className; if (text !== undefined) el.textContent = text; return el; } function icon(name) { const paths = { clock: "M22 12a10 10 0 1 1-20 0 10 10 0 0 1 20 0ZM12 6v6l4 2", left: "m15 18-6-6 6-6", right: "m9 18 6-6-6-6", close: "m6 6 12 12M18 6 6 18", up: "m6 15 6-6 6 6", add: "M12 5v14M5 12h14", copy: "M9 9h12v12H9ZM5 15H3V3h12v2", notes: "M4 3h16v18H4ZM8 7h8M8 11h8M8 15h5", trash: "M3 6h18M9 6V3h6v3M5 6l1 15h12l1-15M10 10v7m4-7v7", save: "M5 3h12l4 4v14H3V3ZM7 3v6h10V3M7 21v-8h10v8" }; const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); svg.setAttribute("viewBox", "0 0 24 24"); svg.setAttribute("aria-hidden", "true"); svg.setAttribute("focusable", "false"); const path = document.createElementNS("http://www.w3.org/2000/svg", "path"); path.setAttribute("d", paths[name]); path.setAttribute("fill", "none"); path.setAttribute("stroke", "currentColor"); path.setAttribute("stroke-width", "1.8"); path.setAttribute("stroke-linecap", "round"); path.setAttribute("stroke-linejoin", "round"); svg.append(path); return svg; } function button(label, symbol, onClick, text = false) { const el = node("button"); el.type = "button"; el.title = label; el.setAttribute("aria-label", label); el.append(icon(symbol)); if (text) el.append(node("span", "", label)); if (onClick) el.addEventListener("click", onClick); return el; } function createButton() { const trigger = button("Manage Time", "clock", () => open(trigger)); trigger.className = "opportunities-extension-icon-button"; launcher = trigger; setReady(ready); return trigger; } function close(restoreFocus = true) { if (!active) return false; const previous = active; active = null; previous.modal.remove(); if (restoreFocus && previous.trigger?.isConnected) previous.trigger.focus(); return true; } function open(trigger) { if (!ready) return; const host = document.getElementById(MODAL_ID); if (!host || host.hidden) return; close(false); const modal = node("section", "opportunities-extension-action-form-modal oe-time-modal"); modal.setAttribute("role", "dialog"); modal.setAttribute("aria-modal", "true"); modal.setAttribute("aria-labelledby", "oe-time-title"); const dialog = node("div", "opportunities-extension-action-form-dialog oe-time-dialog"); const header = node("header", "opportunities-extension-account-plans-header oe-time-header"); const title = node("h2", "", "Manage Time"); title.id = "oe-time-title"; const closeButton = button("Close Manage Time", "close", () => close()); closeButton.className = "opportunities-extension-icon-button"; header.append(title, closeButton); const body = node("div", "oe-time-body"); const toolbar = node("div", "oe-time-toolbar"); const navigation = node("div", "oe-time-navigation"); const date = node("input"); date.type = "date"; date.value = dates.today(); date.required = true; date.setAttribute("aria-label", "Reference date"); const ending = node("div", "oe-time-ending"); const endingValue = node("strong", "", "--"); ending.append(node("span", "", "Week Ending"), endingValue); const changeWeek = days => { if (dates.parse(date.value)) { date.value = dates.shift(date.value, days); load(); } }; navigation.append(button("Previous week", "left", () => changeWeek(-7)), date, button("Next week", "right", () => changeWeek(7))); date.addEventListener("change", load); toolbar.append(navigation, ending); const summary = node("div", "oe-time-summary"), sums = {}; for (const label of ["Service", "Non Service", "Time Off", "Total Hours"]) { const item = node("div"); sums[label] = node("strong", "", "0 hrs"); item.append(node("span", "", `${label}: `), sums[label]); summary.append(item); } const status = node("div", "oe-time-status"); status.setAttribute("role", "status"); const retry = button("Retry", "clock", load, true); retry.hidden = true; const serviceStatus = node("div", "oe-time-status"); serviceStatus.setAttribute("role", "status"); serviceStatus.hidden = true; const serviceRetry = button("Retry Service Requests", "clock", () => loadServices(entry.initialWeekId, true), true); serviceRetry.hidden = true; const taskStatus = node("div", "oe-time-status"); taskStatus.setAttribute("role", "status"); const taskRetry = button("Retry Task Types", "clock", loadTaskTypes, true); taskRetry.hidden = true; const scroll = node("div", "oe-time-grid"), table = node("table"); table.setAttribute("aria-label", "Weekly time entries"); scroll.append(table); const footer = node("footer", "opportunities-extension-action-form-footer oe-time-footer"); const add = button("Add", "add", () => addRow("Service"), true); const addNonService = button("Add (Non service)", "add", () => addRow("Non Service"), true); const save = button("Save", "save", null, true); save.disabled = true; save.title = "Saving time entries is not available yet."; footer.append(add, addNonService, save); body.append(toolbar, summary, status, retry, serviceStatus, serviceRetry, taskStatus, taskRetry, scroll, footer); dialog.append(header, body); modal.append(dialog); host.append(modal); const entry = { modal, trigger, version: 0, days: [], rows: [], drafts: new Map(), tbody: null, dayTotals: [], services: [], servicesStarted: false, servicesReady: false, expanded: new Map() }; active = entry; entry.taskTypes = []; entry.taskTypesReady = false; let combos = []; modal.addEventListener("scroll", event => { if (event.target === body || event.target === scroll) combos.forEach(combo => combo.sync()); }, true); modal.addEventListener("keydown", event => { if (event.key === "Escape") { event.preventDefault(); event.stopPropagation(); close(); } if (event.key !== "Tab") return; const elements = [...dialog.querySelectorAll("button:not(:disabled), input:not(:disabled), textarea:not(:disabled)")].filter(el => !el.hidden && el.getClientRects().length); const first = elements[0], last = elements[elements.length - 1]; if (event.shiftKey && document.activeElement === first) { event.preventDefault(); last?.focus(); } else if (!event.shiftKey && document.activeElement === last) { event.preventDefault(); first?.focus(); } }); function blankRow(type) { return { type, hours: {}, notes: "", srNumber: "", activity: "", taskType: "", notesOpen: false }; } function total(value) { return Number(Number(value).toFixed(2)).toString(); } function updateTotals() { const groups = { Service: 0, "Non Service": 0, "Time Off": 0, "Total Hours": 0 }; entry.days.forEach((day, i) => { let sum = 0; entry.rows.forEach(row => { const hours = Number(row.hours[day.Date]) || 0; sum += hours; groups[row.type] += hours; groups["Total Hours"] += hours; }); entry.dayTotals[i].textContent = total(sum); }); Object.entries(groups).forEach(([label, value]) => { sums[label].textContent = `${total(value)} hrs`; }); } function addRow(type, source) { if (!entry.tbody || add.disabled) return; entry.rows.push(source ? { ...source, hours: { ...source.hours } } : blankRow(type)); renderRows(); } function renderRows() { const focusedId = document.activeElement?.id; combos = []; entry.tbody.replaceChildren(); entry.rows.forEach((row, index) => { const tr = node("tr"); for (const label of [row.type === "Service" ? "Service Request" : "Non Service", "Activity", "Task Type"]) { const td = node("td"), select = node("select"); select.id = `oe-time-${index}-${label.toLowerCase().replaceAll(" ", "-")}`; let change = () => {}; select.setAttribute("aria-label", `${label}, row ${index + 1}`); const placeholder = node("option", "", label); placeholder.value = ""; select.append(placeholder); if (label === "Service Request") { for (const sr of entry.services) { const option = node("option", "", sr.srTitle ? `${sr.srTitle} (${sr.srNumber})` : sr.srNumber); option.value = sr.srNumber; option.title = option.textContent; select.append(option); } select.value = row.srNumber; select.disabled = !entry.servicesReady; select.title = entry.servicesReady ? "Service Request" : "Loading Service Requests"; change = () => { row.srNumber = select.value; row.activity = ""; renderRows(); }; } else if (label === "Activity") { const source = entry.expanded.get(row.type === "Service" ? row.srNumber : "non-service-sr"); const raw = String(source?.SRActivities_c || ""); const activities = [...new Set(raw.split(raw.includes(";") ? ";" : ",").map(value => value.trim()).filter(Boolean))]; for (const activity of activities) { const option = node("option", "", activity); option.value = activity; select.append(option); } select.value = row.activity; select.disabled = !activities.length; select.title = activities.length ? "Activity" : "No activities available for this Service Request"; change = () => { row.activity = select.value; }; } else if (label === "Task Type") { const tag = row.type === "Service" ? "SERVICE" : "NON_SERVICE"; const taskTypes = entry.taskTypes.filter(item => item.tags_c === tag); for (const item of taskTypes) { const option = node("option", "", item.lookUpMeaning_c); option.value = item.lookUpCode_c; select.append(option); } if (entry.taskTypesReady && !taskTypes.some(item => item.lookUpCode_c === row.taskType)) row.taskType = ""; select.value = row.taskType; select.disabled = !entry.taskTypesReady || !taskTypes.length; select.title = !entry.taskTypesReady ? "Loading Task Types" : taskTypes.length ? "Task Type" : "No Task Types available for this row type"; change = () => { row.taskType = select.value; }; } else { select.disabled = true; select.title = "Options are not available yet."; } td.append(select); tr.append(td); select.addEventListener("change", change); const combo = searchableSelect(select, td, change, { floating: true, tooltipContent: label === "Service Request" ? serviceRequestTooltip : undefined }); combo.input.setAttribute("aria-label", `${label}, row ${index + 1}`); combos.push(combo); } for (const day of entry.days) { const td = node("td"), input = node("input"); input.type = "number"; input.min = "0"; input.max = "24"; input.step = "1"; input.value = row.hours[day.Date] ?? ""; input.setAttribute("aria-label", `Hours ${day.Date}, row ${index + 1}`); input.addEventListener("input", () => { const valid = input.value === "" || (Number.isFinite(input.valueAsNumber) && input.valueAsNumber >= 0 && input.valueAsNumber <= 24); input.setAttribute("aria-invalid", String(!valid)); row.hours[day.Date] = valid ? input.value : ""; updateTotals(); }); td.append(input); tr.append(td); } const notesCell = node("td"), notesRow = node("tr", "oe-time-notes"); notesRow.hidden = !row.notesOpen; const noteCell = node("td"); noteCell.colSpan = entry.days.length + 6; const notesBox = node("div", "oe-time-notes-box"); const remaining = node("span", "oe-time-notes-count", `${2000 - row.notes.length} characters left`); const notes = node("textarea"); notes.rows = 6; notes.maxLength = 2000; notes.value = row.notes; notes.setAttribute("aria-label", `Notes, row ${index + 1}`); notes.addEventListener("input", () => { row.notes = notes.value.slice(0, 2000); remaining.textContent = `${2000 - row.notes.length} characters left`; }); const collapse = button(`Collapse notes, row ${index + 1}`, "up", () => { row.notesOpen = false; notesRow.hidden = true; notesButton.setAttribute("aria-expanded", "false"); notesButton.focus(); }); collapse.className = "oe-time-notes-collapse"; notesBox.append(remaining, notes, collapse); noteCell.append(notesBox); notesRow.append(noteCell); const notesButton = button(`Notes, row ${index + 1}`, "notes", () => { row.notesOpen = !row.notesOpen; notesRow.hidden = !row.notesOpen; notesButton.setAttribute("aria-expanded", String(row.notesOpen)); if (!notesRow.hidden) notes.focus(); }); notesButton.setAttribute("aria-expanded", String(row.notesOpen)); notesCell.append(notesButton); tr.append(notesCell); const copy = node("td"); copy.append(button(`Copy row ${index + 1}`, "copy", () => addRow(row.type, row))); tr.append(copy); const remove = node("td"); remove.append(button(`Delete row ${index + 1}`, "trash", () => { entry.rows.splice(index, 1); renderRows(); })); tr.append(remove); entry.tbody.append(tr, notesRow); }); updateTotals(); combos.find(combo => combo.input.id === focusedId)?.input.focus(); } function serviceRequestTooltip(srNumber) { const sr = entry.expanded.get(srNumber); if (!sr) return null; const details = node("dl", "oe-time-sr-details"); for (const [key, label] of [ ["SrNumber", "SR number"], ["AccountPartyUniqueName", "Account"], ["OptyName_c", "Opportunity"], ["OptyNumber_c", "Opty number"], ["OptyOwner_c", "Opty owner"], ["OptyStatus_c", "Opty status"], ["ServiceNew_c", "Service"], ["ReportedByPartyUniqueName", "Requested by"], ["CountryText_c", "Country"], ["StatusCdMeaning", "Status"], ["AssigneePersonName", "Service lead"], ["ProblemDescription", "Description"] ]) { const field = node("div", key === "ProblemDescription" ? "oe-time-sr-description" : ""); field.append(node("dt", "", label), node("dd", "", String(sr[key] ?? "-"))); details.append(field); } return details; } async function loadTaskTypes() { taskStatus.hidden = false; taskStatus.textContent = "Loading Task Types..."; taskStatus.setAttribute("role", "status"); taskRetry.hidden = true; try { const items = await requestTaskTypes(); if (active !== entry) return; entry.taskTypes = items.filter(item => typeof item?.lookUpCode_c === "string" && item.lookUpCode_c.trim() && typeof item.lookUpMeaning_c === "string" && item.lookUpMeaning_c.trim()); entry.taskTypesReady = true; taskStatus.textContent = "No Task Types available."; taskStatus.hidden = entry.taskTypes.length > 0; if (entry.tbody) renderRows(); } catch (error) { if (active !== entry) return; taskStatus.textContent = error.message || "Unable to load Task Types."; taskStatus.setAttribute("role", "alert"); taskRetry.hidden = false; } } async function loadServices(weekId, retrying = false) { if (entry.servicesStarted && !retrying) return; entry.servicesStarted = true; entry.initialWeekId = weekId; serviceRetry.hidden = true; serviceStatus.hidden = false; serviceStatus.setAttribute("role", "status"); serviceStatus.textContent = "Loading Service Requests..."; try { const services = entry.servicesReady ? entry.services : await requestServices(weekId); if (active !== entry) return; entry.services = services; entry.servicesReady = true; serviceStatus.textContent = services.length ? "" : "No active Service Requests."; serviceStatus.hidden = services.length > 0; if (entry.tbody) renderRows(); serviceStatus.hidden = false; serviceStatus.textContent = "Loading activities..."; const expanded = await requestExpand(services.map(sr => sr.srNumber)); if (active !== entry) return; entry.expanded = new Map(expanded.map(sr => [sr.SrNumber, sr])); serviceStatus.hidden = true; if (entry.tbody) renderRows(); } catch (error) { if (active !== entry) return; serviceStatus.textContent = error.message || "Unable to load Service Requests."; serviceStatus.setAttribute("role", "alert"); serviceRetry.hidden = false; } } function renderWeek(items) { entry.days = items; const key = items.map(day => day.Date).join("|"); if (!entry.drafts.has(key)) entry.drafts.set(key, Array.from({ length: 3 }, () => blankRow("Service"))); entry.rows = entry.drafts.get(key); const head = node("thead"), row = node("tr"); for (const label of ["Service Request", "Activity", "Task Type"]) row.append(node("th", "", label)); entry.dayTotals = []; for (const day of items) { const parsed = dates.fromApi(day.Date), th = node("th", "oe-time-day"); const dayLabel = node("span", "oe-time-day-label", ["Su", "M", "T", "W", "Th", "F", "Sa"][parsed.getUTCDay()]); const count = node("span", "oe-time-day-total", "0"); entry.dayTotals.push(count); dayLabel.append(count); th.append(dayLabel, node("span", "", parsed.toLocaleDateString("en-US", { month: "short", day: "numeric", timeZone: "UTC" }))); row.append(th); } for (const label of ["Notes", "Copy", ""]) row.append(node("th", "", label)); [...row.children].forEach(th => th.setAttribute("scope", "col")); head.append(row); entry.tbody = node("tbody"); table.replaceChildren(head, entry.tbody); endingValue.textContent = dates.fromApi(items[0].EndDate).toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric", timeZone: "UTC" }); renderRows(); } async function load() { const version = ++entry.version; entry.tbody = null; table.replaceChildren(); endingValue.textContent = "--"; Object.values(sums).forEach(value => { value.textContent = "--"; }); add.disabled = addNonService.disabled = true; retry.hidden = true; status.hidden = false; status.setAttribute("role", "status"); scroll.setAttribute("aria-busy", "false"); if (!dates.parse(date.value)) { status.textContent = "Select a valid reference date."; return; } status.textContent = "Loading week details..."; scroll.setAttribute("aria-busy", "true"); try { const items = await requestWeek(date.value); if (active !== entry || entry.version !== version) return; renderWeek(items); add.disabled = addNonService.disabled = false; status.hidden = true; loadServices(items[0].WeekId); } catch (error) { if (active !== entry || entry.version !== version) return; status.textContent = error.message || "Unable to load week details."; status.setAttribute("role", "alert"); retry.hidden = false; } finally { if (active === entry && entry.version === version) scroll.setAttribute("aria-busy", "false"); } } date.focus(); load(); loadTaskTypes(); } return { createButton, open, close, setReady }; } // Source: src/shared/messages.js /** Stable wire names. All payloads remain plain, serializable objects. */ function createMessageTypes() { return Object.freeze({ requestWeekDetails: "opportunitiesExtension.requestWeekDetails", requestLookupTaskType: "opportunitiesExtension.requestLookupTaskType", requestServiceRequestExpand: "opportunitiesExtension.requestServiceRequestExpand", requestResourceUser: "opportunitiesExtension.requestResourceUser", requestActiveServiceRequests: "opportunitiesExtension.requestActiveServiceRequests", getXsrfToken: "opportunitiesExtension.getXsrfToken", refreshXsrfCookie: "opportunitiesExtension.refreshXsrfCookie", requestOpptyProducts: "opportunitiesExtension.requestOpptyProducts", opptyProductsProgress: "opportunitiesExtension.opptyProductsProgress", opptyProductsRequestDebug: "opportunitiesExtension.opptyProductsRequestDebug", requestConsumerTokenService: "opportunitiesExtension.requestConsumerTokenService", requestAccountPlans: "opportunitiesExtension.requestAccountPlans", requestInstallBase: "opportunitiesExtension.requestInstallBase", requestCustomerServiceRequest: "opportunitiesExtension.requestCustomerServiceRequest", requestWlActions: "opportunitiesExtension.requestWlActions", requestActionTeamMembers: "opportunitiesExtension.requestActionTeamMembers", requestActionFindLookup: "opportunitiesExtension.requestActionFindLookup", requestMgmtAction: "opportunitiesExtension.requestMgmtAction", requestInternalServiceRequest: "opportunitiesExtension.requestInternalServiceRequest", requestSrLookupPrimaryPillar: "opportunitiesExtension.requestSrLookupPrimaryPillar", requestPostServiceRequest: "opportunitiesExtension.requestPostServiceRequest", requestPostInternalServiceRequest: "opportunitiesExtension.requestPostInternalServiceRequest", prepareSrSession: "opportunitiesExtension.prepareSrSession", requestSrLookupLanguage: "opportunitiesExtension.requestSrLookupLanguage", requestSrLookupTechPlatform: "opportunitiesExtension.requestSrLookupTechPlatform", requestLookupTypePlatform: "opportunitiesExtension.requestLookupTypePlatform", requestInternalSrServices: "opportunitiesExtension.requestInternalSrServices", requestSrLookupOpportunityBusiness: "opportunitiesExtension.requestSrLookupOpportunityBusiness", requestCurrentUser: "opportunitiesExtension.requestCurrentUser", invalidateProductsCache: "opportunitiesExtension.invalidateProductsCache", requestConsumptionTrend: "opportunitiesExtension.requestConsumptionTrend", requestConsumptionPlanDetail: "opportunitiesExtension.requestConsumptionPlanDetail", requestRatecardByPlan: "opportunitiesExtension.requestRatecardByPlan" }); } // Source: src/shared/debug-body.js function createDebugBodyPreview(text) { return { value: text || "(empty response body)", truncated: false }; } // Source: src/content/contracts.js function createContentContracts() { const TILE_ID = "c_5c719a88b5624b1eaf81defe2c2ex4x5"; const TILE_LABEL_ID = `${TILE_ID}_0`; const SALES_GROUP_SELECTOR = "#yourapps_groupNode_sales"; const ADD_ICON_SELECTOR = ".flat-grid-cell.flat-grid-cell-addicon"; const MODAL_ID = "opportunities-extension-modal"; const STYLE_ID = "opportunities-extension-redwood-styles"; const ESCAPE_LISTENER_KEY = "opportunitiesExtensionEscapeListener"; const DEBUG_PANEL_ID = "opportunities-extension-debug-panel"; const OPPORTUNITIES_TABLE_ID = "opportunities-extension-table"; const STAGE_FILTERS_ID = "opportunities-extension-stage-filters"; const STATUS_FILTERS_ID = "opportunities-extension-status-filters"; const CUSTOMER_FILTER_BUTTON_ID = "opportunities-extension-customer-filter-button"; const CUSTOMER_FILTER_PANEL_ID = "opportunities-extension-customer-filter-panel"; const CUSTOMER_FILTER_SEARCH_ID = "opportunities-extension-customer-filter-search"; const CUSTOMER_FILTER_SELECT_ALL_ID = "opportunities-extension-customer-filter-select-all"; const CUSTOMER_FILTER_LIST_ID = "opportunities-extension-customer-filter-list"; const OWNER_FILTER_BUTTON_ID = "opportunities-extension-owner-filter-button"; const OWNER_FILTER_PANEL_ID = "opportunities-extension-owner-filter-panel"; const OWNER_FILTER_SEARCH_ID = "opportunities-extension-owner-filter-search"; const OWNER_FILTER_SELECT_ALL_ID = "opportunities-extension-owner-filter-select-all"; const OWNER_FILTER_LIST_ID = "opportunities-extension-owner-filter-list"; const TABLE_SEARCH_ID = "opportunities-extension-table-search"; const STAGE_DASHBOARD_ID = "opportunities-extension-stage-dashboard"; const PRODUCTS_PROGRESS_ID = "opportunities-extension-products-progress"; const PRODUCTS_CACHE_TIMESTAMP_ID = "opportunities-extension-products-cache-timestamp"; const FILTER_PREFERENCES_STORAGE_KEY = "opportunities-extension-filter-preferences"; const THEME_STORAGE_KEY = "opportunities-extension-theme"; const OPPORTUNITY_TYPE_PREFERENCES_STORAGE_KEY = "opportunities-extension-opportunity-type-preferences"; const PREFERENCES_MODAL_ID = "opportunities-extension-preferences-modal"; const ACCOUNT_PLANS_MODAL_ID = "opportunities-extension-account-plans-modal"; const PRODUCTS_MESSAGE_LISTENER_KEY = "opportunitiesExtensionProductsMessageListener"; const OPPORTUNITY_DETAIL_URL = "https://eeho.fa.us2.oraclecloud.com/fscmUI/redwood/cx-sales/application/container/opportunities/opportunities-detail?puid="; const ACCOUNT_DETAIL_URL = "https://eeho.fa.us2.oraclecloud.com/fscmUI/redwood/cx-sales/application/container/accounts/accounts-detail?id="; const DEBUG_BODY_LIMIT = 12000; const OPPORTUNITIES_PAGE_LIMIT = 15; const DEFAULT_OPPTY_PRODUCTS_CACHE_HOURS = 24; const TOKEN_RELAY_URL = "https://eeho.fa.us2.oraclecloud.com/fscmRestApi/tokenrelay"; const OPPORTUNITIES_QUERY_URL = "https://eeho.fa.us2.oraclecloud.com/crmRestApi/searchResources/11.13.18.05/custom-actions/queries"; const AUTH_STATUS = { idle: "idle", pending: "pending", authenticated: "authenticated", unauthenticated: "unauthenticated" }; const CONSUMPTION_TREND_PERIODS = [ { value: "quarterly", label: "Quarterly", windowSize: 8 }, { value: "monthly", label: "Monthly", windowSize: 13 }, { value: "weekly", label: "Weekly", windowSize: 13 }, { value: "daily", label: "Daily", windowSize: 30 } ]; const CONSUMPTION_TREND_SERIES = [ { value: "funded", label: "Funded Allocation" }, { value: "overage", label: "Overage" }, { value: "average", label: "Average Consumption" } ]; const DEFAULT_CONSUMPTION_TREND_PERIOD = "weekly"; const SVG_NAMESPACE = "http://www.w3.org/2000/svg"; const STAGE_OPTIONS = ["SQL", "PIPELINE", "UPSIDE", "FORECAST", "WON"]; const STATUS_OPTIONS = ["OPEN", "CLOSED", "LOST", "WON"]; const OPPORTUNITY_TYPE_OPTIONS = [ { label: "All records I can see", value: "ORA_ALLOPTIES" }, { label: "I am credit receiver", value: "ORA_CREDITRECEIVER_ISME" }, { label: "I am on the team", value: "ORA_MYSALESTEAMOPTIES" }, { label: "I or my subordinate is a credit receiver", value: "ORA_CREDITRECEIVER_MYORG" }, { label: "I own", value: "ORA_MYOPTIES" }, { label: "My subordinates are on the team", value: "ORA_MYSUBORDSSALESTEAMOPTIES" }, { label: "My subordinates own", value: "ORA_MYSUBORDINATESOPTIES" }, { label: "My territory", value: "ORA_MYASSGTERROPTIES" }, { label: "My territory hierarchy", value: "ORA_MYASSGDESCTERROPTIES" }, { label: "My territory membership", value: "ORA_MYASSGMEMBTERROPTIES" }, { label: "My territory membership hierarchy", value: "ORA_MYASSGMEMBDESCTERROPTIES" } ]; const DEFAULT_OPPORTUNITY_TYPE_VALUES = [ "ORA_MYSALESTEAMOPTIES", "ORA_MYASSGTERROPTIES", "ORA_CREDITRECEIVER_ISME" ]; const OPPTY_PRODUCTS_CACHE_OPTIONS = [ { label: "1 hour", value: 1 }, { label: "6 hours", value: 6 }, { label: "12 hours", value: 12 }, { label: "24 hours (default)", value: 24 }, { label: "48 hours", value: 48 }, { label: "7 days", value: 168 } ]; const PERIOD_OPTIONS = [ "Current Quarter", "Next Quarter", "Previous Quarter", "Current Fiscal Year", "4 Rolling Quarters (CQ + 3)", "RENEWALS (Current + Past Due)" ]; const ALLOWED_PATHS = [ "/hcmUI/faces/FuseWelcome", "/fscmUI/faces/FuseWelcome" ]; return { TILE_ID, TILE_LABEL_ID, SALES_GROUP_SELECTOR, ADD_ICON_SELECTOR, MODAL_ID, STYLE_ID, ESCAPE_LISTENER_KEY, DEBUG_PANEL_ID, OPPORTUNITIES_TABLE_ID, STAGE_FILTERS_ID, STATUS_FILTERS_ID, CUSTOMER_FILTER_BUTTON_ID, CUSTOMER_FILTER_PANEL_ID, CUSTOMER_FILTER_SEARCH_ID, CUSTOMER_FILTER_SELECT_ALL_ID, CUSTOMER_FILTER_LIST_ID, OWNER_FILTER_BUTTON_ID, OWNER_FILTER_PANEL_ID, OWNER_FILTER_SEARCH_ID, OWNER_FILTER_SELECT_ALL_ID, OWNER_FILTER_LIST_ID, TABLE_SEARCH_ID, STAGE_DASHBOARD_ID, PRODUCTS_PROGRESS_ID, PRODUCTS_CACHE_TIMESTAMP_ID, FILTER_PREFERENCES_STORAGE_KEY, THEME_STORAGE_KEY, OPPORTUNITY_TYPE_PREFERENCES_STORAGE_KEY, PREFERENCES_MODAL_ID, ACCOUNT_PLANS_MODAL_ID, PRODUCTS_MESSAGE_LISTENER_KEY, OPPORTUNITY_DETAIL_URL, ACCOUNT_DETAIL_URL, DEBUG_BODY_LIMIT, OPPORTUNITIES_PAGE_LIMIT, DEFAULT_OPPTY_PRODUCTS_CACHE_HOURS, TOKEN_RELAY_URL, OPPORTUNITIES_QUERY_URL, AUTH_STATUS, CONSUMPTION_TREND_PERIODS, CONSUMPTION_TREND_SERIES, DEFAULT_CONSUMPTION_TREND_PERIOD, SVG_NAMESPACE, STAGE_OPTIONS, STATUS_OPTIONS, OPPORTUNITY_TYPE_OPTIONS, DEFAULT_OPPORTUNITY_TYPE_VALUES, OPPTY_PRODUCTS_CACHE_OPTIONS, PERIOD_OPTIONS, ALLOWED_PATHS }; } // Source: src/auth/session-state.js function createAuthSessionState({ AUTH_STATUS } = {}) { return { tokenRelayRequest: null, consumerTokenServiceRequest: null, accessToken: "", principal: "", authStatus: AUTH_STATUS.idle }; } // Source: src/auth/sales-common-state.js /** @returns {{salesCommonToken: null | {access_token: string, expiresAt: number}, salesCommonTokenRequest: null | Promise}} */ function createAuthSalesCommonState() { return { salesCommonToken: null, salesCommonTokenRequest: null }; } // Source: src/features/opportunities/query-state.js function createOpportunitiesQueryState() { return { periodRequestVersion: 0 }; } // Source: src/features/opportunities/table-state.js function createOpportunitiesTableState() { return { opportunitiesTableState: { items: [], status: "idle", message: "", sortKey: "", sortDirection: "ascending" } }; } // Source: src/features/opportunities/filter-state.js function createOpportunitiesFilterState({ STAGE_OPTIONS, STATUS_OPTIONS } = {}) { return { selectedStages: new Set(STAGE_OPTIONS), selectedStatuses: new Set(STATUS_OPTIONS), selectedCustomerKeys: new Set(), customerSearch: "", selectedOwnerKeys: new Set(), ownerSearch: "", tableSearch: "", hasSavedCustomerFilter: false, hasSavedOwnerFilter: false, customerFilterInitialized: false, ownerFilterInitialized: false }; } // Source: src/features/preferences/state.js function createPreferencesState({ DEFAULT_OPPORTUNITY_TYPE_VALUES, DEFAULT_OPPTY_PRODUCTS_CACHE_HOURS } = {}) { return { selectedOpportunityTypeValues: new Set(DEFAULT_OPPORTUNITY_TYPE_VALUES), opptyProductsCacheHours: DEFAULT_OPPTY_PRODUCTS_CACHE_HOURS }; } // Source: src/features/preferences/request-cache.js function createFormRequestCache({ getHours, getUserId }) { const entries = new Map(); const pending = new Map(); let generation = 0; let owner = ""; const queries = new Set([ "requestWeekDetails", "requestActiveServiceRequests", "requestServiceRequestExpand", "requestLookupTaskType", "requestActionTeamMembers", "requestActionFindLookup", "requestSrLookupPrimaryPillar", "requestSrLookupLanguage", "requestSrLookupTechPlatform", "requestSrLookupOpportunityBusiness", "requestLookupTypePlatform", "requestInternalSrServices" ]); const writes = new Set(["requestMgmtAction", "requestPostInternalServiceRequest", "requestPostServiceRequest"]); function invalidate() { generation++; entries.clear(); pending.clear(); } function maxAge() { const hours = Number(getHours()); return (Number.isFinite(hours) && hours > 0 ? Math.min(hours, 168) : 24) * 3600000; } function copy(value) { return JSON.parse(JSON.stringify(value)); } function stable(value) { if (Array.isArray(value)) return value.map(stable); if (value && typeof value === "object") return Object.fromEntries(Object.keys(value).sort().map(key => [key, stable(value[key])])); return value; } async function send(message, transport) { const userId = String(getUserId() || "").trim().toLowerCase(); if (userId !== owner) { invalidate(); owner = userId; } const operation = String(message.type || "").split(".").pop(); const duration = maxAge(); const request = queries.has(operation) || writes.has(operation) || operation === "requestResourceUser" ? { ...message, cacheMaxAgeMs: duration } : message; if (operation === "invalidateProductsCache") invalidate(); // Only explicitly known read operations are reusable. Writes and token generation always execute. if (!queries.has(operation) || !userId) { const result = await transport(request); if (result?.ok && writes.has(operation) && !(operation === "requestPostServiceRequest" && message.phase === "prepare")) invalidate(); return result; } const key = JSON.stringify(stable(message)); const hit = entries.get(key); if (hit && Date.now() >= hit.fetchedAt && Date.now() - hit.fetchedAt < duration) { return { ...copy(hit.result), debugRequests: [], fromCache: true, cachedAt: hit.fetchedAt }; } entries.delete(key); if (pending.has(key)) { const result = await pending.get(key); return { ...copy(result), debugRequests: [] }; } const version = generation; const promise = transport(request).then(result => { if (result?.ok && version === generation) { // Keep this per-page cache bounded and avoid persisting credentials or customer data to disk. if (entries.size >= 500) entries.delete(entries.keys().next().value); entries.set(key, { result: copy({ ...result, debugRequests: [] }), fetchedAt: Date.now() }); } return result; }); pending.set(key, promise); try { return await promise; } finally { if (pending.get(key) === promise) pending.delete(key); } } return { send, invalidate }; } // Source: src/features/products/content-state.js function createProductsContentState() { return { currentProductsRequestId: "", productsRenderQueued: false, opptyProductsByNumber: new Map(), expandedProductOptyNumbers: new Set(), productsProgress: { state: "idle", completed: 0, total: 0, failed: 0, cacheHits: 0 }, productsLastCachedAt: 0 }; } // Source: src/features/ai-summary/state.js function createAiSummaryState() { return { aiSummariesByOptyNumber: new Map() }; } // Source: src/features/consumption/plans-state.js function createConsumptionPlansState() { return { accountPlansRequestState: new Map() }; } // Source: src/features/consumption/trend-state.js function createConsumptionTrendState() { return { consumptionTrendCache: new Map(), consumptionTrendWindowOffsets: new Map(), consumptionTrendSeriesFilters: new Map() }; } // Source: src/features/opportunities/dashboard-state.js function createOpportunitiesDashboardState({ STAGE_OPTIONS } = {}) { return { stageDashboardAmounts: new Map([["TOTAL", 0], ...STAGE_OPTIONS.map((stage) => [stage, 0])]) }; } // Source: src/features/debug/state.js function createDebugState() { return { requestLog: [], debugSecretsVisible: false }; } // Source: src/features/opportunities/columns.js function createOpportunitiesColumns({ formatOracleResponseDate, formatRevenue, formatWinProbability, getNestedValue, getOwnerName }) { return [ { key: "name", label: "Name", value: (item) => item.Name, display: (item) => item.Name }, { key: "optyNumber", label: "Opty Number", value: (item) => item.OptyNumber, display: (item) => item.OptyNumber }, { key: "customer", label: "Customer", value: (item) => getNestedValue(item, ["CustomerAccount", "PartyUniqueName"]), display: (item) => getNestedValue(item, ["CustomerAccount", "PartyUniqueName"]) }, { key: "owner", label: "Owner", value: getOwnerName, display: getOwnerName }, { key: "revenue", label: "Amount", value: (item) => getNestedValue(item, ["PrimaryRevenue", "RevnAmount"]), display: formatRevenue }, { key: "closeDate", label: "Close Date", value: (item) => item.EffectiveDate, display: (item) => formatOracleResponseDate(item.EffectiveDate) }, { key: "status", label: "Status", value: (item) => item.StatusCode, display: (item) => item.StatusCode }, { key: "winProbability", label: "Win Probability", value: (item) => getNestedValue(item, ["PrimaryRevenue", "WinProb"]), display: formatWinProbability }, { key: "stage", label: "Stage", value: (item) => item.ForecastGroup_c, display: (item) => item.ForecastGroup_c }, { key: "lastUpdateDate", label: "Last Update Date", value: (item) => item.LastUpdateDate, display: (item) => formatOracleResponseDate(item.LastUpdateDate, true) } ]; } // Source: src/features/opportunities/badge-content.js /* * Lucide icons: package, chart-no-axes-column-increasing, clipboard-list, list-checks. * https://github.com/lucide-icons/lucide (ISC License) * Copyright (c) 2026 Lucide Icons and Contributors * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ function createOpportunityBadgeContent({ environment }) { const shapes = { products: [ ["path", { d: "M11 21.73a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73z" }], ["path", { d: "M12 22V12" }], ["polyline", { points: "3.29 7 12 12 20.71 7" }], ["path", { d: "m7.5 4.27 9 5.15" }] ], ramp: [ ["path", { d: "M5 21v-6" }], ["path", { d: "M12 21V9" }], ["path", { d: "M19 21V3" }] ], sr: [ ["rect", { width: "8", height: "4", x: "8", y: "2", rx: "1", ry: "1" }], ["path", { d: "M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" }], ["path", { d: "M12 11h4" }], ["path", { d: "M12 16h4" }], ["path", { d: "M8 11h.01" }], ["path", { d: "M8 16h.01" }] ], actions: [ ["path", { d: "M13 5h8" }], ["path", { d: "M13 12h8" }], ["path", { d: "M13 19h8" }], ["path", { d: "m3 17 2 2 4-4" }], ["path", { d: "m3 7 2 2 4-4" }] ] }; function setContent(badge, kind, value) { badge.textContent = String(value); const namespace = "http://www.w3.org/2000/svg"; const icon = environment.document.createElementNS(namespace, "svg"); for (const [name, attribute] of Object.entries({ class: "opportunities-extension-badge-icon", viewBox: "0 0 24 24", width: "16", height: "16", fill: "none", stroke: "currentColor", "stroke-width": "2.25", "stroke-linecap": "round", "stroke-linejoin": "round", "aria-hidden": "true", focusable: "false", "data-badge-icon": kind })) icon.setAttribute(name, attribute); for (const [tag, attributes] of shapes[kind]) { const shape = environment.document.createElementNS(namespace, tag); for (const [name, attribute] of Object.entries(attributes)) shape.setAttribute(name, attribute); icon.append(shape); } badge.append(icon); } return { setContent }; } // Source: src/content/fusion-page.js function createContentFusionPage({ ADD_ICON_SELECTOR, ALLOWED_PATHS, SALES_GROUP_SELECTOR, TILE_ID, TILE_LABEL_ID, appendPath, openOpportunitiesModal, environment }) { const { Node, document, window } = environment; function createOpportunitiesTile() { const wrapper = document.createElement("div"); wrapper.className = "flat-grid-cell"; const item = document.createElement("div"); item.id = TILE_ID; item.className = "app-nav-item opportunities-extension-tile"; item.setAttribute("filmstrip", "Opportunities Extension"); item.setAttribute("page", "undefined"); item.setAttribute("index", "0"); item.setAttribute("type", "subcluster"); item.setAttribute("title", "Opportunities Extension"); item.setAttribute("group", "groupNode_tools"); item.setAttribute("destinationurl", "https://gxpap.oracle.com/ords/pgxpap/f?p=138"); item.setAttribute("targetframe", "_blank"); item.setAttribute("isdesturlexist", "true"); item.setAttribute("role", "presentation"); const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); svg.setAttribute("viewBox", "0 0 48 48"); svg.setAttribute("style", "fill:currentColor"); svg.setAttribute("class", "svg-nav suiicon svg-bkgd09"); svg.setAttribute("data-icon", "navi_reportsearch"); svg.setAttribute("role", "presentation"); svg.setAttribute("focusable", "false"); appendPath(svg, "svg-shortcut", "M28 42.5l-3 2.7v-1.7c-.4 0-1.4 0-2.5.6-1.3 1-1.5 1.6-1.5 1.6s-.4-1.2.8-2.7c1.2-1.6 2.6-1.7 3.2-1.6v-1.6l3 2.7z"); appendPath(svg, "svg-cluster", "M28.5 41.3c.6 0 1.2.5 1.2 1.2s-.6 1.2-1.2 1.2-1.2-.5-1.2-1.2.5-1.2 1.2-1.2zm-4 0c.6 0 1.2.5 1.2 1.2s-.6 1.2-1.2 1.2c-.7 0-1.2-.5-1.2-1.2s.5-1.2 1.2-1.2zm-4 0c.7 0 1.2.5 1.2 1.2s-.5 1.2-1.2 1.2-1.2-.5-1.2-1.2.5-1.2 1.2-1.2z"); appendPath(svg, "svg-icon15", "M16 31l-1.6-1-3.4 6.5s0 1 .5 1.4c.5.2 1.4-.4 1.4-.4l3-6.7z"); appendPath(svg, "svg-icon03", "M36 10H12c-.8 0-2 1.2-2 2v20c0 .4.2.8.5 1l2-3.6c-1-1.4-1.6-3-1.6-5 0-4.2 3.3-7.6 7.4-7.6H20V16h2v1.7c.7.4 1.3 1 1.8 1.5.6.5 1 1 1.3 1.8h4v7h-4c-.3.7-.8 1.4-1.4 2H35v2H20.3l-2 .2H18L17 34h19c.8 0 2-1.2 2-2V12c0-.8-1.2-2-2-2zm-23 4v-2h22v2H13zm22 14h-5V17h5v11z"); appendPath(svg, "svg-icon12", "M18.5 19c-3 0-5.5 2.5-5.5 5.5s2.5 5.5 5.5 5.5 5.5-2.5 5.5-5.5-2.5-5.5-5.5-5.5zm0 9c-2 0-3.5-1.6-3.5-3.5 0-2 1.6-3.5 3.5-3.5s3.5 1.6 3.5 3.5c0 2-1.6 3.5-3.5 3.5z"); appendPath(svg, "svg-outline", "M35 34.56H13a2.7 2.7 0 0 1-3-3V14a2.76 2.76 0 0 1 3-3h22a2.74 2.74 0 0 1 3 3v17.56a2.68 2.68 0 0 1-3 3zM16.98 22.32a4.72 4.72 0 1 0 4.73 4.73 4.72 4.72 0 0 0-4.73-4.73zM24 25h3.47v4.72H24V25zm5.78-4.66h4.69v9.38h-4.69v-9.38zM13.5 14.5h20.9v2.4H13.5v-2.4zm6.9 17.59l-.01-1.94zm-2.04-12.75l-5.35-.02zm2.13 12.67H35.7zm-.09-8.05L20.39 19z"); appendPath(svg, "svg-outline", "M16.98 31.72a4.68 4.68 0 1 0-4.75-4.67 4.74 4.74 0 0 0 4.75 4.67zm-1.44-.5l-3.6 6.83zM8 40"); const link = document.createElement("a"); link.id = TILE_LABEL_ID; link.className = "app-nav-label flat-grid-nav-label"; link.href = "#"; link.textContent = "Opportunities Extension"; item.append(svg, link); wrapper.append(item); wrapper.addEventListener("click", handleTileClick); return wrapper; } function handleTileClick(event) { event.preventDefault(); event.stopPropagation(); openOpportunitiesModal(); } function isAllowedPage() { if (window.location.hostname !== "eeho.fa.us2.oraclecloud.com") { return false; } return ALLOWED_PATHS.some((path) => { return window.location.pathname === path || window.location.pathname.startsWith(`${path}/`); }); } function findInsertionPoint(salesGroup) { const addIconInsideGroup = salesGroup.querySelector(ADD_ICON_SELECTOR); if (addIconInsideGroup) { return addIconInsideGroup; } const container = salesGroup.closest(".flat-grid, .flat-grid-container, [id*='yourapps'], [id*='groupNode']") || salesGroup.parentElement || document.body; const addIconCells = Array.from(container.querySelectorAll(ADD_ICON_SELECTOR)); return addIconCells.find((cell) => { return Boolean(salesGroup.compareDocumentPosition(cell) & Node.DOCUMENT_POSITION_FOLLOWING); }) || null; } function insertTile() { if (document.getElementById(TILE_ID)) { return true; } const salesGroup = document.querySelector(SALES_GROUP_SELECTOR); const addIconCell = salesGroup ? findInsertionPoint(salesGroup) : null; if (!salesGroup || !addIconCell) { return false; } addIconCell.before(createOpportunitiesTile()); return true; } return { createOpportunitiesTile, handleTileClick, isAllowedPage, findInsertionPoint, insertTile }; } // Source: src/content/modal.js function createContentModal({ ACCOUNT_PLANS_MODAL_ID, AUTH_STATUS, CUSTOMER_FILTER_BUTTON_ID, CUSTOMER_FILTER_LIST_ID, CUSTOMER_FILTER_PANEL_ID, CUSTOMER_FILTER_SEARCH_ID, CUSTOMER_FILTER_SELECT_ALL_ID, DEBUG_PANEL_ID, ESCAPE_LISTENER_KEY, MODAL_ID, OPPORTUNITIES_TABLE_ID, OWNER_FILTER_BUTTON_ID, OWNER_FILTER_LIST_ID, OWNER_FILTER_PANEL_ID, OWNER_FILTER_SEARCH_ID, OWNER_FILTER_SELECT_ALL_ID, PERIOD_OPTIONS, PREFERENCES_MODAL_ID, PRODUCTS_CACHE_TIMESTAMP_ID, PRODUCTS_PROGRESS_ID, STAGE_DASHBOARD_ID, STAGE_FILTERS_ID, STATUS_FILTERS_ID, TABLE_SEARCH_ID, applyTheme, authSessionState, closeAccountPlansModal, closeInstallBaseModal, closeServiceRequestsModal, closeActionForm, closeCustomerFilterPanel, closeOwnerFilterPanel, closePreferencesModal, createSettingsIcon, createRefreshAllButton, createManageTimeButton, createActivityFilterControls, mountOpportunityViews, debugState, ensureExtensionStyles, getCustomerOptions, getOwnerOptions, getSavedOpportunityTypeValues, getSavedOpptyProductsCacheHours, getSavedTheme, openPreferencesModal, opportunitiesFilterState, opportunitiesQueryState, preferencesState, renderCustomerFilter, renderCustomerFilterList, renderOpportunitiesTable, renderOwnerFilter, renderOwnerFilterList, renderStageFilterButtons, renderStatusFilterButtons, requestOpportunitiesForPeriod, closeCrmSrPopover, requestTokenRelayOnce, ensureCurrentUser, prepareSrSession, resetCustomerAndOwnerFiltersForPeriod, resetOpportunitiesTable, restoreFilterPreferences, saveFilterPreferences, toggleCustomerFilterPanel, toggleDebugPanel, toggleOwnerFilterPanel, environment }) { const { document, window, setTimeout, clearTimeout, matchMedia } = environment; let authCompletion = null; function resetAuthCompletion() { if (!authCompletion) return; clearTimeout(authCompletion.waitTimer); clearTimeout(authCompletion.hideTimer); authCompletion.badge.setAttribute("data-fading", "false"); authCompletion = null; } function openOpportunitiesModal() { ensureExtensionStyles(); debugState.debugSecretsVisible = false; const existingModal = document.getElementById(MODAL_ID); if (existingModal) { existingModal.hidden = false; prepareSrSession?.(); existingModal.querySelector("select").focus(); document.documentElement.classList.add("opportunities-extension-scroll-lock"); ensureEscapeKeyHandler(); ensureCurrentUser().then(() => { if (existingModal.hidden) return; return requestTokenRelayOnce(); }) .then(() => renderOpportunitiesTable()).catch(() => {}); return; } const overlay = document.createElement("div"); debugState.requestLog = []; authSessionState.tokenRelayRequest = null; authSessionState.consumerTokenServiceRequest = null; authSessionState.accessToken = ""; authSessionState.principal = ""; opportunitiesQueryState.periodRequestVersion = 0; authSessionState.authStatus = AUTH_STATUS.idle; resetOpportunitiesTable(); preferencesState.selectedOpportunityTypeValues = new Set(getSavedOpportunityTypeValues()); preferencesState.opptyProductsCacheHours = getSavedOpptyProductsCacheHours(); overlay.id = MODAL_ID; overlay.className = "opportunities-extension-modal"; overlay.setAttribute("data-theme", getSavedTheme()); overlay.setAttribute("role", "dialog"); overlay.setAttribute("aria-modal", "true"); overlay.setAttribute("aria-labelledby", "opportunities-extension-title"); const shell = document.createElement("section"); shell.className = "opportunities-extension-shell"; const header = document.createElement("header"); header.className = "opportunities-extension-header"; const title = document.createElement("h1"); title.id = "opportunities-extension-title"; title.textContent = "Opportunities Extension"; const authBadge = document.createElement("span"); authBadge.className = "opportunities-extension-auth-badge opportunities-extension-auth-badge-pending"; authBadge.setAttribute("data-auth-status", AUTH_STATUS.pending); authBadge.textContent = "Authenticating"; const subtitleRow = document.createElement("div"); subtitleRow.className = "opportunities-extension-subtitle-row"; const productsProgressBadge = document.createElement("span"); productsProgressBadge.id = PRODUCTS_PROGRESS_ID; productsProgressBadge.className = "opportunities-extension-products-progress"; productsProgressBadge.setAttribute("role", "status"); productsProgressBadge.hidden = true; const productsCacheTimestamp = document.createElement("span"); productsCacheTimestamp.id = PRODUCTS_CACHE_TIMESTAMP_ID; productsCacheTimestamp.className = "opportunities-extension-products-cache-timestamp"; productsCacheTimestamp.setAttribute("role", "status"); productsCacheTimestamp.hidden = true; subtitleRow.append(authBadge, productsProgressBadge, productsCacheTimestamp); const titleBlock = document.createElement("div"); titleBlock.className = "opportunities-extension-title-row"; titleBlock.append(title, subtitleRow); const closeButton = document.createElement("button"); closeButton.type = "button"; closeButton.className = "opportunities-extension-icon-button"; closeButton.setAttribute("aria-label", "Fechar"); closeButton.textContent = "X"; closeButton.addEventListener("click", closeOpportunitiesModal); const themeButton = document.createElement("button"); themeButton.type = "button"; themeButton.className = "opportunities-extension-icon-button opportunities-extension-theme-toggle"; themeButton.addEventListener("click", () => { const nextTheme = overlay.getAttribute("data-theme") === "dark" ? "light" : "dark"; applyTheme(overlay, themeButton, nextTheme); }); applyTheme(overlay, themeButton, overlay.getAttribute("data-theme")); const preferencesButton = document.createElement("button"); preferencesButton.type = "button"; preferencesButton.className = "opportunities-extension-icon-button"; preferencesButton.setAttribute("aria-label", "Abrir preferências"); preferencesButton.title = "Preferences"; preferencesButton.append(createSettingsIcon()); preferencesButton.addEventListener("click", openPreferencesModal); const headerActions = document.createElement("div"); headerActions.className = "opportunities-extension-header-actions"; headerActions.append(themeButton, createRefreshAllButton(() => select.value), createManageTimeButton(), preferencesButton, closeButton); header.append(titleBlock, headerActions); const body = document.createElement("main"); body.className = "opportunities-extension-body"; const form = document.createElement("form"); form.className = "opportunities-extension-form"; const field = document.createElement("label"); field.className = "opportunities-extension-field"; const labelText = document.createElement("span"); labelText.textContent = "Reference"; const selectWrap = document.createElement("span"); selectWrap.className = "opportunities-extension-select-wrap"; const select = document.createElement("select"); select.name = "opportunityPeriod"; PERIOD_OPTIONS.forEach((optionLabel) => { const option = document.createElement("option"); option.value = optionLabel; option.textContent = optionLabel; select.append(option); }); select.addEventListener("change", () => { resetCustomerAndOwnerFiltersForPeriod(); saveFilterPreferences(select.value); requestOpportunitiesForPeriod(select.value); }); selectWrap.append(select); field.append(labelText, selectWrap); restoreFilterPreferences(select); const stageFilter = document.createElement("section"); stageFilter.className = "opportunities-extension-stage-filter"; const stageFilterLabel = document.createElement("span"); stageFilterLabel.className = "opportunities-extension-stage-filter-label"; stageFilterLabel.textContent = "Stage"; const stageFilters = document.createElement("div"); stageFilters.id = STAGE_FILTERS_ID; stageFilters.className = "opportunities-extension-stage-filter-controls"; stageFilters.setAttribute("role", "group"); stageFilters.setAttribute("aria-label", "Filter by stage"); stageFilter.append(stageFilterLabel, stageFilters); const customerFilter = document.createElement("section"); customerFilter.className = "opportunities-extension-customer-filter"; const customerFilterLabel = document.createElement("span"); customerFilterLabel.className = "opportunities-extension-customer-filter-label"; customerFilterLabel.textContent = "Customer"; const customerFilterButton = document.createElement("button"); customerFilterButton.id = CUSTOMER_FILTER_BUTTON_ID; customerFilterButton.type = "button"; customerFilterButton.className = "opportunities-extension-customer-filter-trigger"; customerFilterButton.setAttribute("aria-expanded", "false"); customerFilterButton.setAttribute("aria-controls", CUSTOMER_FILTER_PANEL_ID); customerFilterButton.addEventListener("click", toggleCustomerFilterPanel); const customerFilterPanel = document.createElement("section"); customerFilterPanel.id = CUSTOMER_FILTER_PANEL_ID; customerFilterPanel.className = "opportunities-extension-customer-filter-panel"; customerFilterPanel.hidden = true; const customerSearchInput = document.createElement("input"); customerSearchInput.id = CUSTOMER_FILTER_SEARCH_ID; customerSearchInput.className = "opportunities-extension-customer-filter-search"; customerSearchInput.type = "search"; customerSearchInput.placeholder = "Search customers"; customerSearchInput.setAttribute("aria-label", "Search customers"); customerSearchInput.addEventListener("input", () => { opportunitiesFilterState.customerSearch = customerSearchInput.value; saveFilterPreferences(); renderCustomerFilterList(); }); const selectAllLabel = document.createElement("label"); selectAllLabel.className = "opportunities-extension-customer-filter-select-all"; const selectAllCheckbox = document.createElement("input"); selectAllCheckbox.id = CUSTOMER_FILTER_SELECT_ALL_ID; selectAllCheckbox.type = "checkbox"; selectAllCheckbox.addEventListener("change", () => { const customerOptions = getCustomerOptions(); opportunitiesFilterState.selectedCustomerKeys = selectAllCheckbox.checked ? new Set(customerOptions.map((customer) => customer.key)) : new Set(); opportunitiesFilterState.hasSavedCustomerFilter = true; saveFilterPreferences(); renderCustomerFilter(); renderOpportunitiesTable(); }); const selectAllText = document.createElement("span"); selectAllText.textContent = "Select all"; selectAllLabel.append(selectAllCheckbox, selectAllText); const customerFilterList = document.createElement("div"); customerFilterList.id = CUSTOMER_FILTER_LIST_ID; customerFilterList.className = "opportunities-extension-customer-filter-list"; customerFilterPanel.append(customerSearchInput, selectAllLabel, customerFilterList); customerFilter.append(customerFilterLabel, customerFilterButton, customerFilterPanel); const ownerFilter = document.createElement("section"); ownerFilter.className = "opportunities-extension-customer-filter"; const ownerFilterLabel = document.createElement("span"); ownerFilterLabel.className = "opportunities-extension-customer-filter-label"; ownerFilterLabel.textContent = "Owner"; const ownerFilterButton = document.createElement("button"); ownerFilterButton.id = OWNER_FILTER_BUTTON_ID; ownerFilterButton.type = "button"; ownerFilterButton.className = "opportunities-extension-customer-filter-trigger"; ownerFilterButton.setAttribute("aria-expanded", "false"); ownerFilterButton.setAttribute("aria-controls", OWNER_FILTER_PANEL_ID); ownerFilterButton.addEventListener("click", toggleOwnerFilterPanel); const ownerFilterPanel = document.createElement("section"); ownerFilterPanel.id = OWNER_FILTER_PANEL_ID; ownerFilterPanel.className = "opportunities-extension-customer-filter-panel"; ownerFilterPanel.hidden = true; const ownerSearchInput = document.createElement("input"); ownerSearchInput.id = OWNER_FILTER_SEARCH_ID; ownerSearchInput.className = "opportunities-extension-customer-filter-search"; ownerSearchInput.type = "search"; ownerSearchInput.placeholder = "Search owners"; ownerSearchInput.setAttribute("aria-label", "Search owners"); ownerSearchInput.addEventListener("input", () => { opportunitiesFilterState.ownerSearch = ownerSearchInput.value; saveFilterPreferences(); renderOwnerFilterList(); }); const ownerSelectAllLabel = document.createElement("label"); ownerSelectAllLabel.className = "opportunities-extension-customer-filter-select-all"; const ownerSelectAllCheckbox = document.createElement("input"); ownerSelectAllCheckbox.id = OWNER_FILTER_SELECT_ALL_ID; ownerSelectAllCheckbox.type = "checkbox"; ownerSelectAllCheckbox.addEventListener("change", () => { const ownerOptions = getOwnerOptions(); opportunitiesFilterState.selectedOwnerKeys = ownerSelectAllCheckbox.checked ? new Set(ownerOptions.map((owner) => owner.key)) : new Set(); opportunitiesFilterState.hasSavedOwnerFilter = true; saveFilterPreferences(); renderOwnerFilter(); renderOpportunitiesTable(); }); const ownerSelectAllText = document.createElement("span"); ownerSelectAllText.textContent = "Select all"; ownerSelectAllLabel.append(ownerSelectAllCheckbox, ownerSelectAllText); const ownerFilterList = document.createElement("div"); ownerFilterList.id = OWNER_FILTER_LIST_ID; ownerFilterList.className = "opportunities-extension-customer-filter-list"; ownerFilterPanel.append(ownerSearchInput, ownerSelectAllLabel, ownerFilterList); ownerFilter.append(ownerFilterLabel, ownerFilterButton, ownerFilterPanel); const tableSearchField = document.createElement("label"); tableSearchField.className = "opportunities-extension-table-search-field"; const tableSearchLabel = document.createElement("span"); tableSearchLabel.textContent = "Search"; const tableSearchInput = document.createElement("input"); tableSearchInput.id = TABLE_SEARCH_ID; tableSearchInput.type = "search"; tableSearchInput.placeholder = "Search opportunities"; tableSearchInput.setAttribute("aria-label", "Search opportunities"); tableSearchInput.value = opportunitiesFilterState.tableSearch; tableSearchInput.addEventListener("input", () => { opportunitiesFilterState.tableSearch = tableSearchInput.value; saveFilterPreferences(); renderOpportunitiesTable(); }); tableSearchField.append(tableSearchLabel, tableSearchInput); const statusFilter = document.createElement("section"); statusFilter.className = "opportunities-extension-status-filter"; const statusFilterLabel = document.createElement("span"); statusFilterLabel.className = "opportunities-extension-status-filter-label"; statusFilterLabel.textContent = "Status"; const statusFilters = document.createElement("div"); statusFilters.id = STATUS_FILTERS_ID; statusFilters.className = "opportunities-extension-status-filter-controls"; statusFilters.setAttribute("role", "group"); statusFilters.setAttribute("aria-label", "Filter by status"); statusFilter.append(statusFilterLabel, statusFilters); form.addEventListener("submit", (event) => event.preventDefault()); const badgeFilters = createActivityFilterControls(form, statusFilter, stageFilter); form.append(field, customerFilter, ownerFilter, tableSearchField, badgeFilters); body.append(form); const stageDashboard = document.createElement("section"); stageDashboard.id = STAGE_DASHBOARD_ID; stageDashboard.className = "opportunities-extension-stage-dashboard"; stageDashboard.setAttribute("aria-label", "Opportunity amount by stage"); body.append(stageDashboard); const tableSurface = document.createElement("section"); tableSurface.className = "opportunities-extension-table-surface"; const opportunitiesTable = document.createElement("table"); opportunitiesTable.id = OPPORTUNITIES_TABLE_ID; opportunitiesTable.className = "opportunities-extension-table"; opportunitiesTable.setAttribute("aria-label", "Opportunities"); tableSurface.append(opportunitiesTable); mountOpportunityViews(body, tableSurface); const debugPanel = document.createElement("aside"); debugPanel.id = DEBUG_PANEL_ID; debugPanel.className = "opportunities-extension-debug-panel"; debugPanel.hidden = true; body.append(debugPanel); shell.append(header, body); overlay.append(shell); document.body.append(overlay); document.documentElement.classList.add("opportunities-extension-scroll-lock"); ensureEscapeKeyHandler(); renderStageFilterButtons(); renderStatusFilterButtons(); renderCustomerFilter(); renderOwnerFilter(); renderOpportunitiesTable(); requestOpportunitiesForPeriod(select.value); prepareSrSession?.(); select.focus(); } function updateAuthBadge(status) { const badge = document.querySelector(`#${MODAL_ID} .opportunities-extension-auth-badge`); if (!badge) { return; } const badgeStatus = status === AUTH_STATUS.authenticated || status === AUTH_STATUS.unauthenticated ? status : AUTH_STATUS.pending; if (authCompletion && (authCompletion.badge !== badge || badgeStatus !== AUTH_STATUS.authenticated)) resetAuthCompletion(); badge.hidden = Boolean(authCompletion?.hidden); if (badgeStatus === AUTH_STATUS.authenticated && !authCompletion) { const current = { badge, hidden: false, waitTimer: null, hideTimer: null }; authCompletion = current; badge.setAttribute("data-fading", "false"); current.waitTimer = setTimeout(() => { if (authCompletion !== current) return; badge.setAttribute("data-fading", "true"); const reducedMotion = typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches; current.hideTimer = setTimeout(() => { if (authCompletion !== current) return; current.hidden = true; badge.hidden = true; }, reducedMotion ? 0 : 300); }, 5000); } badge.setAttribute("data-auth-status", badgeStatus); badge.className = `opportunities-extension-auth-badge opportunities-extension-auth-badge-${badgeStatus}`; badge.textContent = badgeStatus === AUTH_STATUS.authenticated ? "Authenticated" : badgeStatus === AUTH_STATUS.unauthenticated ? "Unauthenticated" : "Authenticating"; } function closeOpportunitiesModal() { resetAuthCompletion(); closeCrmSrPopover(); const modal = document.getElementById(MODAL_ID); if (modal) { closePreferencesModal(); closeAccountPlansModal(); closeInstallBaseModal(); closeServiceRequestsModal(); closeActionForm(false); modal.hidden = true; } debugState.debugSecretsVisible = false; document.documentElement.classList.remove("opportunities-extension-scroll-lock"); } function ensureEscapeKeyHandler() { if (window[ESCAPE_LISTENER_KEY]) { return; } window[ESCAPE_LISTENER_KEY] = true; document.addEventListener("keydown", (event) => { const modal = document.getElementById(MODAL_ID); if (!modal || modal.hidden) { return; } if (event.ctrlKey && event.key.toLowerCase() === "d") { event.preventDefault(); event.stopPropagation(); toggleDebugPanel(); return; } if (event.key === "Escape") { if (closeActionForm()) { event.preventDefault(); return; } if (closeServiceRequestsModal()) { event.preventDefault(); return; } if (closeInstallBaseModal()) { event.preventDefault(); return; } const accountPlansModal = document.getElementById(ACCOUNT_PLANS_MODAL_ID); if (accountPlansModal && !accountPlansModal.hidden) { closeAccountPlansModal(); event.preventDefault(); return; } const preferencesModal = document.getElementById(PREFERENCES_MODAL_ID); if (preferencesModal && !preferencesModal.hidden) { closePreferencesModal(); event.preventDefault(); return; } if (closeCustomerFilterPanel()) { event.preventDefault(); return; } if (closeOwnerFilterPanel()) { event.preventDefault(); return; } closeOpportunitiesModal(); } }); document.addEventListener("pointerdown", (event) => { const modal = document.getElementById(MODAL_ID); const panel = document.getElementById(CUSTOMER_FILTER_PANEL_ID); const ownerPanel = document.getElementById(OWNER_FILTER_PANEL_ID); if (!modal || modal.hidden) { return; } if (panel && !panel.hidden) { const customerFilter = panel.closest(".opportunities-extension-customer-filter"); if (customerFilter && !customerFilter.contains(event.target)) { closeCustomerFilterPanel(false); } } if (ownerPanel && !ownerPanel.hidden) { const ownerFilter = ownerPanel.closest(".opportunities-extension-customer-filter"); if (ownerFilter && !ownerFilter.contains(event.target)) { closeOwnerFilterPanel(false); } } }); } return { openOpportunitiesModal, updateAuthBadge, closeOpportunitiesModal, ensureEscapeKeyHandler }; } // Source: src/content/theme.js function createContentTheme({ THEME_STORAGE_KEY, createThemeIcon, environment }) { const getLocalStorage = () => environment.localStorage; function getSavedTheme() { try { return getLocalStorage().getItem(THEME_STORAGE_KEY) === "dark" ? "dark" : "light"; } catch (error) { return "light"; } } function applyTheme(modal, button, theme) { const isDarkTheme = theme === "dark"; modal.setAttribute("data-theme", isDarkTheme ? "dark" : "light"); button.textContent = ""; button.setAttribute("aria-label", isDarkTheme ? "Ativar modo claro" : "Ativar modo escuro"); button.title = isDarkTheme ? "Light mode" : "Dark mode"; button.append(createThemeIcon(isDarkTheme ? "sun" : "moon")); try { getLocalStorage().setItem(THEME_STORAGE_KEY, isDarkTheme ? "dark" : "light"); } catch (error) { // Continue with the selected theme when browser storage is unavailable. } } return { getSavedTheme, applyTheme }; } // Source: src/content/icons.js function createContentIcons({ appendPath, environment }) { const { document } = environment; function createThemeIcon(iconName) { const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); const path = document.createElementNS("http://www.w3.org/2000/svg", "path"); svg.setAttribute("viewBox", "0 0 24 24"); svg.setAttribute("aria-hidden", "true"); svg.setAttribute("focusable", "false"); if (iconName === "sun") { path.setAttribute("d", "M12 3V1m0 22v-2m9-9h2M1 12h2m15.36 6.36 1.42 1.42M4.22 4.22l1.42 1.42m12.72 0 1.42-1.42M4.22 19.78l1.42-1.42M16.5 12a4.5 4.5 0 1 1-9 0 4.5 4.5 0 0 1 9 0Z"); } else { path.setAttribute("d", "M20.5 14.2A8.5 8.5 0 0 1 9.8 3.5a8.5 8.5 0 1 0 10.7 10.7Z"); } path.setAttribute("fill", "none"); path.setAttribute("stroke", "currentColor"); path.setAttribute("stroke-linecap", "round"); path.setAttribute("stroke-linejoin", "round"); path.setAttribute("stroke-width", "1.8"); svg.append(path); return svg; } function createSettingsIcon() { const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); const upperControl = document.createElementNS("http://www.w3.org/2000/svg", "circle"); const lowerControl = document.createElementNS("http://www.w3.org/2000/svg", "circle"); svg.setAttribute("viewBox", "0 0 24 24"); svg.setAttribute("class", "opportunities-extension-settings-icon"); svg.setAttribute("aria-hidden", "true"); svg.setAttribute("focusable", "false"); appendPath(svg, "", "M4 7h16M4 17h16"); [upperControl, lowerControl].forEach((control, index) => { control.setAttribute("cx", index === 0 ? "9" : "15"); control.setAttribute("cy", index === 0 ? "7" : "17"); control.setAttribute("r", "2.5"); control.setAttribute("fill", "currentColor"); control.setAttribute("stroke", "currentColor"); control.setAttribute("stroke-width", "1.8"); }); svg.querySelector("path").setAttribute("fill", "none"); svg.querySelector("path").setAttribute("stroke", "currentColor"); svg.querySelector("path").setAttribute("stroke-linecap", "round"); svg.querySelector("path").setAttribute("stroke-width", "1.8"); svg.append(upperControl, lowerControl); return svg; } function createCloseIcon() { const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); const path = document.createElementNS("http://www.w3.org/2000/svg", "path"); svg.setAttribute("viewBox", "0 0 24 24"); svg.setAttribute("aria-hidden", "true"); svg.setAttribute("focusable", "false"); path.setAttribute("d", "m6 6 12 12M18 6 6 18"); path.setAttribute("fill", "none"); path.setAttribute("stroke", "currentColor"); path.setAttribute("stroke-linecap", "round"); path.setAttribute("stroke-width", "1.8"); svg.append(path); return svg; } function createRefreshIcon() { const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); const path = document.createElementNS("http://www.w3.org/2000/svg", "path"); svg.setAttribute("viewBox", "0 0 24 24"); svg.setAttribute("aria-hidden", "true"); svg.setAttribute("focusable", "false"); path.setAttribute("d", "M20 7v5h-5M4 17v-5h5M6.1 6.1A8 8 0 0 1 19.7 10M4.3 14a8 8 0 0 0 13.6 3.9"); path.setAttribute("fill", "none"); path.setAttribute("stroke", "currentColor"); path.setAttribute("stroke-width", "1.8"); path.setAttribute("stroke-linecap", "round"); path.setAttribute("stroke-linejoin", "round"); svg.append(path); return svg; } function createViewIcon(view) { const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); svg.setAttribute("viewBox", "0 0 24 24"); svg.setAttribute("aria-hidden", "true"); svg.setAttribute("focusable", "false"); const path = document.createElementNS("http://www.w3.org/2000/svg", "path"); const calendarFrame = "M8 2v4m8-4v4M3 10h18M5 4h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2Z"; path.setAttribute("d", view === "today" ? `${calendarFrame}M9 16l2 2 4-4` : view === "week" ? `${calendarFrame}M6 14h12v4H6Z M10 14v4m4-4v4` : view === "calendar" ? "M8 2v4m8-4v4M3 10h18M5 4h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2ZM7 14h2m6 0h2m-10 4h2m6 0h2" : "M4 3h16a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1ZM3 9h18M9 9v12M3 15h18"); path.setAttribute("fill", "none"); path.setAttribute("stroke", "currentColor"); path.setAttribute("stroke-width", "1.8"); path.setAttribute("stroke-linecap", "round"); path.setAttribute("stroke-linejoin", "round"); svg.append(path); return svg; } return { createThemeIcon, createSettingsIcon, createCloseIcon, createRefreshIcon, createViewIcon }; } // Source: src/content/svg.js function createSvgPaths({ environment }) { const { document } = environment; function appendPath(svg, className, d) { const path = document.createElementNS("http://www.w3.org/2000/svg", "path"); path.setAttribute("class", className); path.setAttribute("d", d); svg.append(path); } return { appendPath }; } // Source: src/content/styles.js function createContentStyles({ STYLE_ID, redwoodStyles, environment }) { const { document } = environment; function ensureExtensionStyles() { if (document.getElementById(STYLE_ID)) { return; } const style = document.createElement("style"); style.id = STYLE_ID; style.textContent = redwoodStyles; document.head.append(style); } return { ensureExtensionStyles }; } // Source: src/auth/content-session.js function createAuthContentSession({ messageTypes, AUTH_STATUS, appendBackgroundRequestLog, authSessionState, requestTokenRelay, sendRuntimeMessage, updateAuthBadge }) { function requestTokenRelayOnce() { if (!authSessionState.tokenRelayRequest) { authSessionState.authStatus = AUTH_STATUS.pending; updateAuthBadge(authSessionState.authStatus); authSessionState.tokenRelayRequest = requestTokenRelay() .then((token) => { authSessionState.accessToken = token; authSessionState.authStatus = AUTH_STATUS.authenticated; requestConsumerTokenServiceOnce().catch(() => {}); return token; }) .catch((error) => { authSessionState.accessToken = ""; authSessionState.principal = ""; authSessionState.authStatus = AUTH_STATUS.unauthenticated; throw error; }) .finally(() => { updateAuthBadge(authSessionState.authStatus); }); } else { updateAuthBadge(authSessionState.authStatus); } return authSessionState.tokenRelayRequest; } function requestConsumerTokenServiceOnce() { if (!authSessionState.consumerTokenServiceRequest) { authSessionState.consumerTokenServiceRequest = sendRuntimeMessage({ type: messageTypes.requestConsumerTokenService }).then((response) => { appendBackgroundRequestLog(response && response.debugRequests); if (!response || !response.ok) { throw new Error(response && response.error ? response.error : "Unable to load consumer token service."); } return response.authorizations; }); } return authSessionState.consumerTokenServiceRequest; } return { requestTokenRelayOnce, requestConsumerTokenServiceOnce }; } // Source: src/auth/current-user.js function createCurrentUser({ sendRuntimeMessage, appendBackgroundRequestLog, messageTypes }) { let pending = null; let allowed = false; let userId = ""; function ensure() { if (!pending) { pending = sendRuntimeMessage({ type: messageTypes.requestCurrentUser }).then(response => { appendBackgroundRequestLog(response?.debugRequests); const roles = response?.user?.roles; userId = response?.ok === true && typeof response.user?.id === "string" ? response.user.id : ""; allowed = response?.ok === true && Array.isArray(roles) && roles.every(role => typeof role === "string") && !roles.includes("SALES_REP"); return allowed; }).catch(() => { allowed = false; return false; }); } return pending; } return { ensure, allowsCrmActivities: () => allowed, getUserId: () => userId }; } // Source: src/auth/sales-common.js function createAuthSalesCommon({ TOKEN_RELAY_URL, authSalesCommonState, requestTokenRelay }) { function requestTokenRelaySalesCommon() { if (authSalesCommonState.salesCommonToken && Date.now() < authSalesCommonState.salesCommonToken.expiresAt) { return Promise.resolve(authSalesCommonState.salesCommonToken.access_token); } if (authSalesCommonState.salesCommonTokenRequest) { return authSalesCommonState.salesCommonTokenRequest; } authSalesCommonState.salesCommonTokenRequest = requestTokenRelay({ name: "requestTokenRelaySalesCommon", url: `${TOKEN_RELAY_URL}?scopesfq=urn%3Aopc%3Aresource%3Afusion%3Aeeho%3Asales-common%2F`, includeExpiry: true }).then((token) => { authSalesCommonState.salesCommonToken = token; return token.access_token; }).finally(() => { authSalesCommonState.salesCommonTokenRequest = null; }); return authSalesCommonState.salesCommonTokenRequest; } function invalidateSalesCommonToken(rejectedToken) { if (authSalesCommonState.salesCommonToken && authSalesCommonState.salesCommonToken.access_token === rejectedToken) { authSalesCommonState.salesCommonToken = null; } } return { requestTokenRelaySalesCommon, invalidateSalesCommonToken }; } // Source: src/auth/token-relay.js function createAuthTokenRelay({ TOKEN_RELAY_URL, authSessionState, createDebugBody, getXsrfToken, logRequestFailure, logRequestStart, logRequestSuccess, maskToken, readResponseBodyForDebug, refreshXsrfCookie, renderDebugPanel, environment }) { const { fetch } = environment; async function requestTokenRelay({ name = "requestTokenRelay", url = TOKEN_RELAY_URL, includeExpiry = false } = {}) { if (url === TOKEN_RELAY_URL) authSessionState.principal = ""; let refreshResult = null; let refreshAttempted = false; while (true) { const xsrfTokenSource = await getXsrfToken(); if (!xsrfTokenSource.token) { if (refreshAttempted) { const requestEntry = createTokenRelayRequestEntry(xsrfTokenSource, refreshResult, name, url); logRequestFailure(requestEntry, "XSRF token cookie not found after automatic refresh."); throw new Error("XSRF token cookie not found after automatic refresh."); } refreshResult = await refreshXsrfCookie("missing-cookie"); refreshAttempted = true; continue; } const requestEntry = createTokenRelayRequestEntry(xsrfTokenSource, refreshResult, name, url); try { const response = await fetch(url, { method: "GET", credentials: "include", headers: { "x-xsrf-token": xsrfTokenSource.token } }); const responseBody = await readResponseBodyForDebug(response); if (!response.ok) { logRequestSuccess(requestEntry, response, responseBody); if (!refreshAttempted && isInvalidXsrfStatus(response.status)) { refreshResult = await refreshXsrfCookie(`tokenrelay-${response.status}`); refreshAttempted = true; requestEntry.metadata.xsrfRefresh = formatXsrfRefreshResult(refreshResult); renderDebugPanel(); continue; } throw new Error(`Token relay failed with status ${response.status}.`); } let responseData; try { responseData = await response.json(); } catch (error) { logRequestSuccess(requestEntry, response, responseBody); if (!refreshAttempted) { refreshResult = await refreshXsrfCookie("tokenrelay-invalid-response"); refreshAttempted = true; requestEntry.metadata.xsrfRefresh = formatXsrfRefreshResult(refreshResult); renderDebugPanel(); continue; } throw error; } if (!responseData || typeof responseData.access_token !== "string" || !responseData.access_token) { logRequestSuccess(requestEntry, response, responseBody); if (!refreshAttempted) { refreshResult = await refreshXsrfCookie("tokenrelay-missing-access-token"); refreshAttempted = true; requestEntry.metadata.xsrfRefresh = formatXsrfRefreshResult(refreshResult); renderDebugPanel(); continue; } throw new Error("Token relay response does not contain access_token."); } logRequestSuccess(requestEntry, response, responseBody); if (url === TOKEN_RELAY_URL) { authSessionState.principal = typeof responseData.principal === "string" ? responseData.principal.trim() : ""; } if (includeExpiry) { const lifetime = Number(responseData.expires_in); return { access_token: responseData.access_token, expiresAt: Number.isFinite(lifetime) && lifetime > 0 ? requestEntry.startedAt.getTime() + lifetime * 1000 : 0 }; } return responseData.access_token; } catch (error) { logRequestFailure(requestEntry, error.message || "Request failed."); throw error; } } } function createTokenRelayRequestEntry(xsrfTokenSource, refreshResult, name = "requestTokenRelay", url = TOKEN_RELAY_URL) { return logRequestStart({ name, method: "GET", url, headers: { "x-xsrf-token": xsrfTokenSource.token || "(missing)" }, metadata: { xsrfCookieName: xsrfTokenSource.cookieName || "(missing)", xsrfTokenSource: xsrfTokenSource.source, xsrfMatchedCookies: xsrfTokenSource.matchedCookieNames && xsrfTokenSource.matchedCookieNames.length ? xsrfTokenSource.matchedCookieNames.join(", ") : "(none)", xsrfLookupDetails: xsrfTokenSource.lookupDetails && xsrfTokenSource.lookupDetails.length ? xsrfTokenSource.lookupDetails.join(" | ") : "(none)", xsrfTokenError: xsrfTokenSource.error || "", xsrfRefresh: formatXsrfRefreshResult(refreshResult) } }); } function isInvalidXsrfStatus(status) { return status === 400 || status === 401 || status === 403; } function formatXsrfRefreshResult(result) { if (!result) { return "not-requested"; } const outcome = result.refreshed ? "refreshed" : "not-refreshed"; const cookieName = result.cookieName || "(none)"; const duration = Number.isFinite(result.durationMs) ? `${result.durationMs}ms` : "unknown"; return `${result.reason}; ${outcome}; cookie=${cookieName}; duration=${duration}; ${result.error || ""}`.trim(); } return { requestTokenRelay, createTokenRelayRequestEntry, isInvalidXsrfStatus, formatXsrfRefreshResult }; } // Source: src/auth/xsrf-content.js function createAuthXsrfContent({ getXsrfTokenFromExtensionCookies, environment }) { const { document } = environment; async function getXsrfToken() { const extensionCookie = await getXsrfTokenFromExtensionCookies(); if (extensionCookie.token) { return extensionCookie; } const xsrfCookie = document.cookie .split(";") .map((cookie) => cookie.trim()) .find((cookie) => cookie.startsWith("XSRF-TOKEN-")); if (!xsrfCookie) { return { token: "", cookieName: "", source: extensionCookie.error ? "document.cookie fallback after cookies API error" : "document.cookie fallback", matchedCookieNames: extensionCookie.matchedCookieNames || [], lookupDetails: extensionCookie.lookupDetails || [], error: extensionCookie.error || "" }; } const cookieName = xsrfCookie.slice(0, xsrfCookie.indexOf("=")); const tokenValue = xsrfCookie.slice(xsrfCookie.indexOf("=") + 1); return { token: decodeURIComponent(tokenValue), cookieName, source: "document.cookie", matchedCookieNames: extensionCookie.matchedCookieNames || [], lookupDetails: extensionCookie.lookupDetails || [], error: "" }; } return { getXsrfToken }; } // Source: src/platform/content-messages.js function createPlatformContentMessages({ messageTypes, requestCache, environment }) { const { browser, chrome } = environment; function sendRuntimeMessage(message) { return requestCache ? requestCache.send(message, sendUncached) : sendUncached(message); } function sendUncached(message) { const runtimeApi = typeof browser !== "undefined" ? browser : chrome; if (!runtimeApi || !runtimeApi.runtime || !runtimeApi.runtime.sendMessage) { return Promise.reject(new Error("Runtime messaging API unavailable.")); } if (typeof browser !== "undefined") { return runtimeApi.runtime.sendMessage(message); } return new Promise((resolve, reject) => { runtimeApi.runtime.sendMessage(message, (response) => { const lastError = runtimeApi.runtime.lastError; if (lastError) { reject(new Error(lastError.message)); return; } resolve(response); }); }); } function getXsrfTokenFromExtensionCookies() { const runtimeApi = typeof browser !== "undefined" ? browser : chrome; if (!runtimeApi || !runtimeApi.runtime || !runtimeApi.runtime.sendMessage) { return Promise.resolve({ token: "", cookieName: "", source: "unavailable cookies API", error: "Runtime messaging API unavailable." }); } return new Promise((resolve) => { runtimeApi.runtime.sendMessage({ type: messageTypes.getXsrfToken }, (response) => { const lastError = runtimeApi.runtime.lastError; if (lastError) { resolve({ token: "", cookieName: "", source: "cookies API", matchedCookieNames: [], lookupDetails: [], error: lastError.message }); return; } resolve({ token: response && response.ok ? response.token : "", cookieName: response && response.ok ? response.cookieName : "", source: "cookies API", matchedCookieNames: response && response.matchedCookieNames ? response.matchedCookieNames : [], lookupDetails: response && response.lookupDetails ? response.lookupDetails : [], error: response && response.error ? response.error : "" }); }); }); } function refreshXsrfCookie(reason) { const runtimeApi = typeof browser !== "undefined" ? browser : chrome; if (!runtimeApi || !runtimeApi.runtime || !runtimeApi.runtime.sendMessage) { return Promise.resolve({ ok: false, refreshed: false, cookieName: "", durationMs: 0, reason, error: "Runtime messaging API unavailable." }); } return new Promise((resolve) => { runtimeApi.runtime.sendMessage({ type: messageTypes.refreshXsrfCookie }, (response) => { const lastError = runtimeApi.runtime.lastError; if (lastError) { resolve({ ok: false, refreshed: false, cookieName: "", durationMs: 0, reason, error: lastError.message }); return; } resolve({ ...(response || { ok: false, refreshed: false, cookieName: "", durationMs: 0, error: "Empty refresh response." }), reason }); }); }); } return { sendRuntimeMessage, getXsrfTokenFromExtensionCookies, refreshXsrfCookie }; } // Source: src/features/opportunities/controller.js function createOpportunitiesController({ STAGE_OPTIONS, STATUS_OPTIONS, authSessionState, consumptionPlansState, consumptionTrendState, getFiscalPeriodRange, opportunitiesDashboardState, opportunitiesFilterState, opportunitiesQueryState, opportunitiesTableState, preferencesState, productsContentState, renderProductsProgress, requestOpportunitiesPage, resetCrmServiceRequests, resetWorkloadActions, requestOpptyProductsInBackground, requestTokenRelayOnce, ensureCurrentUser, setOpportunitiesTableState }) { async function requestOpportunitiesForPeriod(period) { const dateRange = getFiscalPeriodRange(period, new Date()); const requestVersion = ++opportunitiesQueryState.periodRequestVersion; resetWorkloadActions(); await ensureCurrentUser(); if (!isCurrentPeriodRequest(requestVersion)) return; if (preferencesState.selectedOpportunityTypeValues.size === 0) { setOpportunitiesTableState({ items: [], status: "empty", message: "Select at least one Opportunity Type View in Preferences." }); return; } if (!dateRange) { setOpportunitiesTableState({ items: [], status: "empty", message: "No opportunities to display for this period." }); return; } try { const token = authSessionState.accessToken || await requestTokenRelayOnce(); if (requestVersion !== opportunitiesQueryState.periodRequestVersion) { return; } await requestOpportunities(token, period, dateRange, requestVersion); } catch (error) { // Authentication and request errors are recorded by their respective request handlers. } } async function requestOpportunities(token, period, dateRange, requestVersion) { if (isCurrentPeriodRequest(requestVersion)) { productsContentState.currentProductsRequestId = ""; productsContentState.opptyProductsByNumber = new Map(); productsContentState.expandedProductOptyNumbers = new Set(); productsContentState.productsProgress = { state: "idle", completed: 0, total: 0, failed: 0, cacheHits: 0 }; consumptionPlansState.accountPlansRequestState = new Map(); consumptionTrendState.consumptionTrendCache = new Map(); consumptionTrendState.consumptionTrendWindowOffsets = new Map(); consumptionTrendState.consumptionTrendSeriesFilters = new Map(); renderProductsProgress(); setOpportunitiesTableState({ items: [], status: "loading", message: "Loading opportunities..." }); } try { if (!isCurrentPeriodRequest(requestVersion)) return; const allItems = await requestOpportunitiesPage(token, period, dateRange, 0, [], requestVersion, 1); if (allItems && isCurrentPeriodRequest(requestVersion)) { setOpportunitiesTableState({ items: allItems, status: "ready", message: "" }); requestOpptyProductsInBackground(allItems, token); } } catch (error) { if (isCurrentPeriodRequest(requestVersion)) { setOpportunitiesTableState({ items: [], status: "error", message: "Unable to load opportunities." }); } throw error; } } function resetOpportunitiesTable() { resetWorkloadActions(); resetCrmServiceRequests(); opportunitiesFilterState.selectedStages = new Set(STAGE_OPTIONS); opportunitiesFilterState.selectedStatuses = new Set(STATUS_OPTIONS); opportunitiesFilterState.selectedCustomerKeys = new Set(); opportunitiesFilterState.customerSearch = ""; opportunitiesFilterState.selectedOwnerKeys = new Set(); opportunitiesFilterState.ownerSearch = ""; opportunitiesFilterState.tableSearch = ""; opportunitiesFilterState.hasSavedCustomerFilter = false; opportunitiesFilterState.hasSavedOwnerFilter = false; opportunitiesFilterState.customerFilterInitialized = false; opportunitiesFilterState.ownerFilterInitialized = false; productsContentState.currentProductsRequestId = ""; productsContentState.opptyProductsByNumber = new Map(); productsContentState.expandedProductOptyNumbers = new Set(); productsContentState.productsProgress = { state: "idle", completed: 0, total: 0, failed: 0, cacheHits: 0 }; productsContentState.productsLastCachedAt = 0; consumptionPlansState.accountPlansRequestState = new Map(); consumptionTrendState.consumptionTrendCache = new Map(); consumptionTrendState.consumptionTrendWindowOffsets = new Map(); consumptionTrendState.consumptionTrendSeriesFilters = new Map(); opportunitiesDashboardState.stageDashboardAmounts = new Map([["TOTAL", 0], ...STAGE_OPTIONS.map((stage) => [stage, 0])]); opportunitiesTableState.opportunitiesTableState = { items: [], status: "idle", message: "", sortKey: "", sortDirection: "ascending" }; } function isCurrentPeriodRequest(requestVersion) { return requestVersion === opportunitiesQueryState.periodRequestVersion; } return { requestOpportunitiesForPeriod, requestOpportunities, resetOpportunitiesTable, isCurrentPeriodRequest }; } // Source: src/services/fusion/opportunities.js function createFusionOpportunities({ OPPORTUNITIES_PAGE_LIMIT, OPPORTUNITIES_QUERY_URL, isCurrentPeriodRequest, logRequestFailure, logRequestStart, logRequestSuccess, maskToken, getOpportunityTypeValues, readResponseBodyForDebug, environment }) { const { fetch } = environment; async function requestOpportunitiesPage(token, period, dateRange, offset, accumulatedItems, requestVersion, page) { if (!isCurrentPeriodRequest(requestVersion)) { return null; } const payload = createOpportunitiesQueryPayload(dateRange, offset); const requestBody = JSON.stringify(payload); const requestEntry = logRequestStart({ name: "requestOpportunities", method: "POST", url: OPPORTUNITIES_QUERY_URL, headers: { Accept: "application/json", Authorization: `Bearer ${token}`, "Content-Type": "application/json", Origin: "https://eeho.fa.us2.oraclecloud.com", Preference: "transient" }, body: JSON.stringify(payload, null, 2), metadata: { period, page, offset, startCloseDate: dateRange.startCloseDate, endCloseDate: dateRange.endCloseDate } }); try { const response = await fetch(OPPORTUNITIES_QUERY_URL, { method: "POST", credentials: "include", headers: { Accept: "application/json", Authorization: `Bearer ${token}`, "Content-Type": "application/json", Origin: "https://eeho.fa.us2.oraclecloud.com", Preference: "transient" }, body: requestBody }); const responseBody = await readResponseBodyForDebug(response); logRequestSuccess(requestEntry, response, responseBody); if (!response.ok) { throw new Error(`Opportunities query failed with status ${response.status}.`); } const responseData = await response.json(); const pageItems = Array.isArray(responseData.items) ? responseData.items : []; const allItems = accumulatedItems.concat(pageItems); if (!responseData.hasMore) { return allItems; } const responseOffset = Number(responseData.offset); const responseCount = Number(responseData.count); const receivedCount = Number.isFinite(responseCount) ? responseCount : pageItems.length; const currentOffset = Number.isFinite(responseOffset) ? responseOffset : offset; if (receivedCount <= 0) { throw new Error("Opportunities query returned hasMore without additional results."); } return requestOpportunitiesPage( token, period, dateRange, currentOffset + receivedCount, allItems, requestVersion, page + 1 ); } catch (error) { logRequestFailure(requestEntry, error.message || "Request failed."); throw error; } } function createOpportunitiesQueryPayload(dateRange, offset) { return { aggregationResults: false, applications: ["ORACLE-ISS-APP"], onlyData: true, entity: "Opportunity", q: { op: "$and", criteria: [ { op: "$or", criteria: Array.from(getOpportunityTypeValues(), (value) => ({ op: "$eq", attribute: "RecordSet", value })) }, { op: "$wi", attribute: "EffectiveDate", value1: dateRange.startCloseDate, value2: dateRange.endCloseDate, dynamicDate: false } ] }, keywords: null, keywordsFields: [ "Name", "OptyNumber", "PrimaryRevenue.WinProb", "CustomerAccount", "PrimaryRevenue.RevnAmount", "EffectiveDate", "StatusCode", "DealRisk_c", "Owner", "ForecastGroup_c", "LastUpdateDate", "PrimaryRevenue.RevnAmountCurcyCode", "OptyId" ], fields: [ "Name", "OptyNumber", "PrimaryRevenue.WinProb", "CustomerAccount", "PrimaryRevenue.RevnAmount", "EffectiveDate", "StatusCode", "DealRisk_c", "Owner", "ForecastGroup_c", "LastUpdateDate", "PrimaryRevenue.RevnAmountCurcyCode", "OptyId", "CustomerAccount.PartyUniqueName", "CustomerAccount.PartyId", "CustomerAccount.PartyNumber", "CustomerAccount.Region_c", "CustomerAccount.PrimaryAddress.CountryCode" ], sort: [], language: "en", skipInValidFields: true, skipHiddenFromUIFields: true, copiedFrom: "queries/d5dbcd01-9ff3-40be-9b5b-64744cbf7162", limit: OPPORTUNITIES_PAGE_LIMIT, offset }; } return { requestOpportunitiesPage, createOpportunitiesQueryPayload }; } // Source: src/services/fusion/crm-service-requests.js function createFusionCrmServiceRequests({ isCurrentPeriodRequest, logRequestStart, logRequestSuccess, logRequestFailure, readResponseBodyForDebug, maskToken, environment }) { const { fetch } = environment; const url = "https://eeho.fa.us2.oraclecloud.com/crmRestApi/searchResources/latest/custom-actions/queries"; function createPayload(offset) { return { aggregationResults: false, applications: ["ORACLE-ISS-APP"], onlyData: true, entity: "CRMServiceRequest", copiedFrom: "queries/c521309c-1310-49a0-9723-da2eb1988205", q: { op: "$and", criteria: [ { op: "$in", attribute: "StatusCd", values: ["SVC_PENDING_DELIVERY"] }, { op: "$eq", attribute: "RecordSet", value: "ORA_SVC_MY_SR_TEAM_SRS" } ] }, keywords: null, fields: ["SrNumber", "OpportunityName_Id_c.OptyNumber", "ReportedHours_c", "CustomerDeadline_c", "PrimaryPillarNew_c", "_actions", "Title", "Owner.PersonFirstName", "Owner.PersonLastName", "Owner.PrimaryEmail.EmailAddress", "DoNotCallFlag", "DoNotEmailFlag"], sort: [{ attribute: "_score", direction: "descending" }], language: "en", keywordsFields: ["SrNumber", "ServiceNew_Id_c.RecordName", "CustomerAccount", "OpportunityName_Id_c", "OpportunityName_Id_c.OptyNumber", "Owner"], skipInValidFields: true, limit: 15, offset }; } async function requestCrmServiceRequest(token, version, isActive = () => isCurrentPeriodRequest(version), onProgress = () => {}) { const items = []; let offset = 0; let attempt = 1; while (isActive()) { const payload = createPayload(offset); const headers = { Accept: "application/json", "Content-Type": "application/json", Authorization: `Bearer ${token}`, Origin: "https://eeho.fa.us2.oraclecloud.com", Preference: "transient" }; const entry = logRequestStart({ name: "resquestCrmServiceRequest", method: "POST", url, headers: { ...headers }, body: JSON.stringify(payload, null, 2), metadata: { offset, attempt } }); try { const response = await fetch(url, { method: "POST", credentials: "include", headers, body: JSON.stringify(payload) }); logRequestSuccess(entry, response, await readResponseBodyForDebug(response)); if (response.status !== 200) throw new Error(`CRM service request query failed with status ${response.status}.`); const data = await response.json(); if (!data || !Array.isArray(data.items) || typeof data.hasMore !== "boolean") { throw new Error("CRM service request query returned an invalid response."); } if (!isActive()) return null; const received = data.count === undefined ? data.items.length : Number(data.count); const current = data.offset === undefined ? offset : Number(data.offset); if (data.hasMore && (!data.items.length || !Number.isInteger(received) || received <= 0 || !Number.isInteger(current) || current !== offset || current + received <= offset)) { throw new Error("CRM service request pagination did not advance."); } items.push(...data.items); onProgress(items.length, Number.isFinite(data.totalResults) && data.totalResults >= items.length ? data.totalResults : null); if (!data.hasMore) return items; offset = current + received; attempt = 1; } catch (error) { logRequestFailure(entry, error.message || "CRM service request query failed."); if (attempt === 5) throw error; attempt += 1; } } return null; } return { createPayload, requestCrmServiceRequest }; } // Source: src/features/opportunities/crm-service-requests.js function createOpportunityCrmServiceRequests({ requestCrmServiceRequest, isCurrentPeriodRequest, bindPopover, closePopover, createSrButton, closeSrForm, allowsCrmActivities, reportProgress, activityCache, setBadgeContent, formatDate, environment }) { let status = "idle"; let byOpportunity = new Map(); let pending = null; let generation = 0; let loadedItems = []; function reset() { closeSrForm(); closePopover(); generation += 1; pending = null; status = "idle"; byOpportunity = new Map(); loadedItems = []; reportProgress("idle"); } function load(token, version, cacheOptions = null) { if (!allowsCrmActivities() || !isCurrentPeriodRequest(version)) return; if (status === "ready") { return Promise.resolve(cacheOptions ? activityCache.write("sr", "all", cacheOptions.cachedAt, loadedItems) : undefined).then(() => true); } if (!pending) { const request = run(token, version, generation, cacheOptions); pending = request; request.finally(() => { if (pending === request) pending = null; }); } return pending; } async function run(token, version, currentGeneration, cacheOptions) { const isActive = () => currentGeneration === generation; status = "loading"; try { const cached = cacheOptions ? await activityCache.read("sr", "all", cacheOptions.cachedAt) : null; if (!isActive()) return false; if (!cached && cacheOptions?.cacheOnly) { status = "idle"; return false; } let items = cached?.items; if (!items) { reportProgress("running", 0); items = await requestCrmServiceRequest(token, version, isActive, (count, total) => { if (isActive()) reportProgress("running", count, total); }); if (items && isActive() && cacheOptions) await activityCache.write("sr", "all", cacheOptions.cachedAt, items); } if (!items || !isActive()) return false; const grouped = new Map(); for (const item of items) { const number = item?.OpportunityName_Id_c?.OptyNumber; if (!number) continue; const key = String(number); if (!grouped.has(key)) grouped.set(key, []); grouped.get(key).push(item); } byOpportunity = grouped; loadedItems = items; status = "ready"; if (!cached) reportProgress("complete", items.length, items.length); return true; } catch { if (isActive()) { status = "error"; reportProgress("error"); } return false; } } function getSummary(optyNumber) { if (!allowsCrmActivities() || !optyNumber) return null; const items = byOpportunity.get(String(optyNumber)) || []; const hours = items.map(item => item.ReportedHours_c).filter(value => (typeof value === "number" || (typeof value === "string" && value.trim() !== "")) && Number.isFinite(Number(value)) ).map(Number); return { state: status === "ready" ? items.length ? "matched" : "missing" : status, reportedHours: hours.length === items.length ? hours.reduce((total, value) => total + value, 0) : null, hasUnderFourHours: status === "ready" && hours.some(value => value < 4) }; } function createBadge(optyNumber) { if (!allowsCrmActivities() || !optyNumber || status === "idle" || status === "loading") return null; const items = byOpportunity.get(String(optyNumber)) || []; const badge = environment.document.createElement(items.length && status === "ready" ? "button" : "span"); badge.className = "opportunities-extension-crm-sr-badge"; badge.setAttribute("data-state", status === "error" ? "error" : items.length ? "matched" : "missing"); const summary = getSummary(optyNumber); const reportedHours = summary.reportedHours !== null ? new Intl.NumberFormat("en-US", { useGrouping: false, maximumFractionDigits: 10 }).format(summary.reportedHours) : "N/D"; setBadgeContent(badge, "sr", status === "error" ? "?" : items.length ? `${reportedHours}${reportedHours === "N/D" ? "" : "h"}` : "0h"); const display = value => value === null || value === undefined || value === "" ? "N/D" : String(value); const descriptions = items.map(item => [ `Title: ${display(item.Title)}`, `Opportunity: ${display(item.OpportunityName_Id_c?.OptyNumber)}`, `Reported Hours: ${display(item.ReportedHours_c)}`, `Customer Deadline: ${display(formatDate(item.CustomerDeadline_c))}`, `Owner: ${[item.Owner?.PersonFirstName, item.Owner?.PersonLastName].filter(Boolean).join(" ") || "N/D"}`, `Email: ${display(item.Owner?.PrimaryEmail?.EmailAddress)}`, `Do Not Call: ${display(item.DoNotCallFlag)}`, `Do Not Email: ${display(item.DoNotEmailFlag)}` ].join("\n")); const description = status === "error" ? "Unable to check CRM service requests. See Debug Requests." : !items.length ? "No matching CRM service request with status SVC_PENDING_DELIVERY in your SR team." : items.map((item, index) => `SR Number: ${display(item.SrNumber)}\n${descriptions[index]}`).join("\n\n"); badge.setAttribute("aria-label", description); badge.title = "Whether a Service Request has been created for the opportunities."; if (items.length && status === "ready") bindPopover(badge, items, descriptions); if (!items.length && status === "ready") badge.append(createSrButton(String(optyNumber))); badge.tabIndex = 0; return badge; } return { reset, load, createBadge, getSummary }; } // Source: src/features/opportunities/product-activities.js function createProductActivities({ productsContentState, opportunitiesQueryState, loadCrm, loadActions, blockActions, render }) { let cycle = null; function begin(requestId, token, opportunityNumbers = null) { cycle = { requestId, token, numbers: opportunityNumbers && new Set(opportunityNumbers.map(String)), version: opportunitiesQueryState.periodRequestVersion, completed: false }; } function complete(requestId) { const current = cycle; if (!current || current.completed || current.requestId !== requestId || productsContentState.currentProductsRequestId !== requestId) return; current.completed = true; const entries = [...productsContentState.opptyProductsByNumber].filter(([number]) => !current.numbers || current.numbers.has(String(number))); const products = entries.map(([, result]) => result).filter(result => !result.error); if (!products.length) return; const cachedAt = Math.max(...products.map(result => Number(result.cachedAt) || 0)); const isActive = () => cycle === current && productsContentState.currentProductsRequestId === requestId && opportunitiesQueryState.periodRequestVersion === current.version; // Product cache hits do not guarantee that the corresponding activity cache exists. return Promise.resolve(loadCrm(current.token, current.version, { cachedAt, cacheOnly: false })).then(async success => { if (!isActive()) return; if (success === false) { products.forEach(blockActions); render(); return; } const pending = []; const seen = new Set(); for (const result of products) { const items = result.items.filter(item => { const id = String(item?.RevnId ?? ""); if (seen.has(id)) return false; seen.add(id); return true; }); pending.push(loadActions({ ...result, items }, true, { cachedAt: result.cachedAt, cacheOnly: false })); } await Promise.allSettled(pending); if (!isActive()) return; if (cachedAt > 0) productsContentState.productsLastCachedAt = cachedAt; render(); }).catch(() => { if (isActive()) { products.forEach(blockActions); render(); } }); } return { begin, complete }; } // Source: src/features/opportunities/refresh-all.js function createRefreshAll({ messageTypes, sendRuntimeMessage, activityCache, resetRelatedData, opportunitiesQueryState, productsContentState, consumptionPlansState, consumptionTrendState, aiSummaryState, reload, onFailure, notify, createRefreshIcon, environment }) { let pending = null; function refresh(period) { if (pending) return pending; opportunitiesQueryState.periodRequestVersion += 1; productsContentState.currentProductsRequestId = ""; productsContentState.opptyProductsByNumber = new Map(); productsContentState.expandedProductOptyNumbers = new Set(); productsContentState.productsProgress = { state: "idle", completed: 0, total: 0, failed: 0, cacheHits: 0 }; consumptionPlansState.accountPlansRequestState = new Map(); consumptionTrendState.consumptionTrendCache = new Map(); consumptionTrendState.consumptionTrendWindowOffsets = new Map(); consumptionTrendState.consumptionTrendSeriesFilters = new Map(); aiSummaryState.aiSummariesByOptyNumber = new Map(); resetRelatedData(); pending = (async () => { await activityCache.invalidate(); const response = await sendRuntimeMessage({ type: messageTypes.invalidateProductsCache }); if (!response?.ok) throw new Error(response?.error || "Unable to clear product cache."); await reload(period); })().catch(error => { onFailure(error); throw error; }).finally(() => { pending = null; }); return pending; } function createButton(getPeriod) { const button = environment.document.createElement("button"); button.type = "button"; button.className = "opportunities-extension-icon-button opportunities-extension-refresh-all"; button.append(createRefreshIcon()); button.title = "Refresh all"; button.setAttribute("aria-label", "Refresh all"); button.addEventListener("click", async () => { if (button.disabled) return; button.disabled = true; button.title = "Refreshing..."; button.setAttribute("aria-busy", "true"); try { await refresh(getPeriod()); } catch (error) { notify(error.message || "Unable to refresh cached data.", true); } finally { button.disabled = false; button.title = "Refresh all"; button.setAttribute("aria-busy", "false"); } }); return button; } return { refresh, createButton }; } // Source: src/features/opportunities/activity-cache.js function createActivityCache({ authSessionState, preferencesState, environment }) { const { browser, chrome } = environment; const invalidatedKey = "opportunitiesExtension.activities.invalidatedAt.v1"; async function invalidate() { await storage("set", { [invalidatedKey]: Date.now() }); const entries = await storage("get", null); const keys = Object.keys(entries || {}).filter(key => key.startsWith("opportunitiesExtension.activities.v1:")); if (keys.length) await storage("remove", keys); } function storage(method, argument) { if (browser?.storage?.local) return browser.storage.local[method](argument); if (!chrome?.storage?.local) return Promise.reject(new Error("Extension storage unavailable.")); return new Promise((resolve, reject) => { chrome.storage.local[method](argument, result => { const error = chrome.runtime?.lastError; if (error) reject(new Error(error.message)); else resolve(result); }); }); } function context(kind, id, timestamp) { const principal = authSessionState.principal; const fetchedAt = Number(timestamp); const hours = Number(preferencesState.opptyProductsCacheHours); const ttl = Number.isFinite(hours) && hours > 0 ? Math.min(hours, 720) * 3600000 : 86400000; if (!principal || !Number.isFinite(fetchedAt) || fetchedAt <= 0 || fetchedAt > Date.now() || Date.now() - fetchedAt >= ttl) return null; const identity = [principal, kind, String(id)]; const legacyKey = `opportunitiesExtension.activities.v1:${JSON.stringify(identity)}`; // SR snapshots from different product updates must not overwrite each other. if (kind === "sr") identity.push(fetchedAt); return { key: `opportunitiesExtension.activities.v1:${JSON.stringify(identity)}`, legacyKey, fetchedAt, ttl }; } async function read(kind, id, timestamp) { const scope = context(kind, id, timestamp); if (!scope) return null; try { const invalidated = await storage("get", invalidatedKey); if (scope.fetchedAt <= Number(invalidated?.[invalidatedKey] || 0)) return null; const result = await storage("get", scope.key); let entry = result?.[scope.key]; if (!entry && scope.key !== scope.legacyKey) { const legacy = await storage("get", scope.legacyKey); entry = legacy?.[scope.legacyKey]; } return entry?.fetchedAt === scope.fetchedAt && Array.isArray(entry.items) && entry.items.every(item => item && typeof item === "object" && !Array.isArray(item)) && typeof entry.hasNext === "boolean" ? entry : null; } catch { return null; } } async function write(kind, id, timestamp, items, hasNext = false) { const scope = context(kind, id, timestamp); if (!scope || !Array.isArray(items)) return; try { const invalidated = await storage("get", invalidatedKey); if (scope.fetchedAt <= Number(invalidated?.[invalidatedKey] || 0)) return; await storage("set", { [scope.key]: { fetchedAt: scope.fetchedAt, items, hasNext: Boolean(hasNext) } }); if (kind === "sr") { const entries = await storage("get", null); const prefix = `${scope.legacyKey.slice(0, -1)},`; const expired = Object.keys(entries || {}).filter(key => key.startsWith(prefix) && Number(entries[key]?.fetchedAt) <= Math.max(Date.now() - scope.ttl, Number(invalidated?.[invalidatedKey] || 0))); if (expired.length) await storage("remove", expired); } } catch { /* Storage failure must not discard successfully fetched data. */ } } return { invalidate, read, write }; } // Source: src/features/opportunities/activity-progress.js function createActivityProgress({ MODAL_ID, environment }) { const { document, setTimeout, clearTimeout, matchMedia } = environment; const entries = new Map(); function update(key, state, completed = 0, total = null) { const host = document?.querySelector?.(`#${MODAL_ID} .opportunities-extension-subtitle-row`); if (!host) return; let entry = entries.get(key); if (!entry) { const badge = document.createElement("span"); badge.className = "opportunities-extension-products-progress"; badge.setAttribute("role", "status"); badge.setAttribute("aria-live", "polite"); host.append(badge); entry = { badge, timer: null, hideTimer: null, state: "idle" }; entries.set(key, entry); } if (state === "complete" && entry.state === "complete") return; clearTimeout(entry.timer); clearTimeout(entry.hideTimer); entry.state = state; const label = key === "session" ? "SR session" : key === "sr" ? "Service Requests" : "Actions"; entry.badge.hidden = state === "idle"; entry.badge.setAttribute("data-state", state); entry.badge.setAttribute("data-fading", "false"); entry.badge.setAttribute("aria-busy", String(state === "running")); const count = total === null ? String(completed) : `${completed}/${total}`; entry.badge.textContent = state === "error" ? `${label} unavailable` : state === "complete-with-errors" ? `${label} ${count} (errors)` : state === "complete" ? `${label} ready ${completed}` : `${label} ${count}`; if (key === "session") entry.badge.textContent = state === "complete" ? "SR session ready" : state === "error" ? "SR session unavailable" : "SR session loading"; entry.badge.title = key === "session" ? "Service Request session initialization" : key === "sr" ? "CRM service request records loaded" : "Product workloads checked for Actions in the selected Reference, regardless of display filters"; if (state === "complete") entry.timer = setTimeout(() => { entry.badge.setAttribute("data-fading", "true"); const reduced = typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches; entry.hideTimer = setTimeout(() => { entry.badge.hidden = true; }, reduced ? 0 : 300); }, 5000); } return { update }; } // Source: src/features/opportunities/activity-filters.js function createActivityFilters({ allowsCrmActivities, getSrSummary, getActionSummary, renderTable, setBadgeContent, environment }) { const selections = { sr: new Set(["matched", "missing"]), actions: new Set(["matched", "missing"]) }; let container = null; let form = null; const groups = []; const buttons = []; function createControls(parentForm, statusFilter, stageFilter) { form = parentForm; groups.length = 0; buttons.length = 0; container = environment.document.createElement("div"); container.className = "opportunities-extension-badge-filters"; for (const [kind, title, options] of [ ["sr", "Service Request", ["matched", "under4", "missing"]], ["actions", "WL Action", ["matched", "missing"]] ]) { const section = environment.document.createElement("section"); section.className = "opportunities-extension-activity-filter"; const label = environment.document.createElement("span"); label.textContent = title; const tooltip = kind === "sr" ? "Whether a Service Request has been created for the opportunities." : "Whether the Action is registered in Workload Workbench."; label.title = tooltip; const controls = environment.document.createElement("div"); controls.className = "opportunities-extension-activity-filter-controls"; controls.setAttribute("role", "group"); controls.setAttribute("aria-label", `Filter by ${title}`); for (const value of options) { const button = environment.document.createElement("button"); button.type = "button"; button.className = "opportunities-extension-activity-filter-button"; button.setAttribute("data-value", value); setBadgeContent(button, kind, value === "under4" ? "< 4h" : ""); const description = value === "under4" ? "At least one SR with reported hours below 4; replaces other SR filters." : `${kind === "sr" ? "Service Request" : "Action"} ${value === "matched" ? "registered" : "not registered"}`; button.title = `${tooltip}\n${description}`; button.setAttribute("aria-label", description); button.addEventListener("click", () => toggle(kind, value)); buttons.push({ button, kind, value }); controls.append(button); } section.append(label, controls); groups.push(section); container.append(section); } container.append(statusFilter, stageFilter); render(); return container; } function render() { if (!container) return; const allowed = allowsCrmActivities(); form.setAttribute("data-activity-filters", String(allowed)); for (const group of groups) group.hidden = !allowed; for (const { button, kind, value } of buttons) { button.setAttribute("aria-pressed", String(selections[kind].has(value))); } } function toggle(kind, value) { const selected = selections[kind]; if (!selected || !allowsCrmActivities()) return; if (selected.has(value)) selected.delete(value); else { if (value === "under4") selected.clear(); else selected.delete("under4"); selected.add(value); } render(); renderTable(); } function matchesSelection(selected, summary) { // Unrestricted filters retain rows whose activities are not known yet. if (selected.has("matched") && selected.has("missing")) return true; if (!summary) return false; if (selected.has("under4")) return summary.state === "matched" && summary.hasUnderFourHours; return selected.has(summary.state); } function matches(item) { if (!allowsCrmActivities()) return true; return matchesSelection(selections.sr, getSrSummary(item.OptyNumber)) && matchesSelection(selections.actions, getActionSummary(item.OptyNumber)); } return { createControls, render, toggle, matches }; } // Source: src/features/opportunities/crm-sr-popover.js function createCrmSrPopover({ MODAL_ID, createCopyButton, createCloseIcon, requestDetails, formatDate, openInternalSrForm, environment }) { const { document, window, setTimeout, clearTimeout } = environment; let active = null; let sequence = 0; function close(restoreFocus = false) { if (!active) return; const entry = active; active = null; clearTimeout(entry.timer); entry.panel.remove(); entry.button.setAttribute("aria-expanded", "false"); document.removeEventListener("pointerdown", entry.outside, true); document.removeEventListener("keydown", entry.keydown, true); window.removeEventListener("resize", entry.position); document.removeEventListener("scroll", entry.scroll, true); if (restoreFocus && entry.button.isConnected) { entry.suppressFocus = true; entry.button.focus(); entry.suppressFocus = false; } } function bind(button, items, descriptions) { button.type = "button"; button.setAttribute("aria-haspopup", "dialog"); button.setAttribute("aria-expanded", "false"); const id = `opportunities-extension-sr-details-${++sequence}`; button.setAttribute("aria-controls", id); const entry = { button, panel: null, timer: null, pinned: false, suppressFocus: false }; const contains = target => target && (button.contains(target) || entry.panel?.contains(target)); entry.outside = event => { if (!contains(event.target)) close(); }; entry.scroll = event => { if (!entry.panel.contains(event.target)) close(); }; entry.keydown = event => { if (event.key === "Escape") { event.preventDefault(); event.stopPropagation(); close(true); } }; entry.position = () => { const rect = button.getBoundingClientRect(); const width = Math.min(480, window.innerWidth - 24); entry.panel.style.width = `${width}px`; entry.panel.style.maxHeight = `${Math.min(480, window.innerHeight - 24)}px`; const height = entry.panel.getBoundingClientRect().height; entry.panel.style.left = `${Math.max(12, Math.min(rect.left, window.innerWidth - width - 12))}px`; entry.panel.style.top = `${Math.max(12, Math.min(rect.bottom + 6, window.innerHeight - height - 12))}px`; }; function leave() { clearTimeout(entry.timer); if (!entry.pinned && !contains(document.activeElement)) { entry.timer = setTimeout(() => { if (active === entry) close(); }, 200); } } function focusOut(event) { if (!contains(event.relatedTarget) && active === entry) close(); } function open() { if (entry.suppressFocus) return; clearTimeout(entry.timer); if (active === entry) return; close(); const host = document.getElementById(MODAL_ID); if (!host) return; const panel = document.createElement("div"); entry.panel = panel; entry.pinned = false; panel.id = id; panel.className = "opportunities-extension-sr-popover"; panel.setAttribute("role", "dialog"); panel.setAttribute("aria-label", "CRM service requests"); const dismiss = document.createElement("button"); dismiss.type = "button"; dismiss.className = "opportunities-extension-sr-dismiss"; dismiss.title = "Close service request details"; dismiss.setAttribute("aria-label", dismiss.title); dismiss.append(createCloseIcon()); dismiss.addEventListener("click", () => close(true)); panel.append(dismiss); const detailLoads = []; items.forEach((item, index) => { const section = document.createElement("section"); const heading = document.createElement("div"); heading.className = "opportunities-extension-sr-number"; const number = item.SrNumber; const link = document.createElement(number ? "a" : "span"); link.textContent = number ? String(number) : "N/D"; if (number) { link.href = `https://eeho.fa.us2.oraclecloud.com/fscmUI/redwood/service/ec/container/sr/edit?srNumber=${encodeURIComponent(String(number))}`; link.target = "_blank"; link.rel = "noopener noreferrer"; } const label = document.createElement("span"); label.textContent = "SR Number:"; heading.append(label, link); if (number) heading.append(createCopyButton(String(number), "SR number", "SR Number")); const add = document.createElement("button"); add.type = "button"; add.className = "opportunities-extension-copy-button opportunities-extension-internal-sr-add"; add.title = "Create Internal Service Request"; add.setAttribute("aria-label", add.title); add.setAttribute("aria-haspopup", "dialog"); const icon = document.createElementNS("http://www.w3.org/2000/svg", "svg"); icon.setAttribute("viewBox", "0 0 24 24"); icon.setAttribute("aria-hidden", "true"); icon.setAttribute("focusable", "false"); const path = document.createElementNS("http://www.w3.org/2000/svg", "path"); path.setAttribute("d", "M12 5v14M5 12h14"); path.setAttribute("stroke", "currentColor"); path.setAttribute("stroke-width", "2"); path.setAttribute("stroke-linecap", "round"); icon.append(path); add.append(icon); add.addEventListener("click", event => { event.preventDefault(); event.stopPropagation(); close(); openInternalSrForm(item, button); }); heading.append(add); const details = document.createElement("div"); details.className = "opportunities-extension-sr-details"; details.textContent = descriptions[index]; section.append(heading, details); const children = document.createElement("div"); children.className = "opportunities-extension-sr-children"; children.setAttribute("aria-busy", "true"); children.setAttribute("role", "status"); children.textContent = "Loading SR details..."; section.append(children); detailLoads.push(async () => { try { const records = await requestDetails(item.SrId); if (active !== entry || entry.panel !== panel) return; children.textContent = ""; if (!records.length) children.textContent = "No further details."; for (const record of records) { const child = document.createElement("section"); const row = document.createElement("div"); row.className = "opportunities-extension-sr-number"; const label = document.createElement("span"); label.textContent = "Internal SR Number:"; const link = document.createElement(record.SrNumber ? "a" : "span"); link.textContent = record.SrNumber ? String(record.SrNumber) : "N/D"; if (record.SrNumber) { link.href = `https://eeho.fa.us2.oraclecloud.com/fscmUI/redwood/service/ec/container/sr/edit?srNumber=${encodeURIComponent(String(record.SrNumber))}&srId=${encodeURIComponent(String(record.SrId ?? ""))}`; link.target = "_blank"; link.rel = "noopener noreferrer"; } row.append(label, link); if (record.SrNumber) row.append(createCopyButton(String(record.SrNumber), "SR number", "SR Number")); const body = document.createElement("div"); body.className = "opportunities-extension-sr-details"; body.textContent = ["Title", "OpportunityName_c", "StatusCd", "ServiceNew_c", "AccountPartyName", "LastUpdateDate", "SvcHealth_c", "AssigneePersonName", "OpenDate"].map(key => `${key === "ServiceNew_c" ? "Service Queue" : key}: ${record[key] == null || record[key] === "" ? "N/D" : String(key === "LastUpdateDate" || key === "OpenDate" ? formatDate(record[key]) : record[key])}`).join("\n"); child.append(row, body); children.append(child); } } catch (error) { if (active !== entry || entry.panel !== panel) return; children.setAttribute("role", "alert"); children.textContent = error.message || "Unable to load SR details."; } finally { if (active === entry && entry.panel === panel) { children.setAttribute("aria-busy", "false"); entry.position(); } } }); panel.append(section); }); panel.addEventListener("pointerenter", () => clearTimeout(entry.timer)); panel.addEventListener("pointerleave", leave); panel.addEventListener("focusout", focusOut); host.append(panel); active = entry; detailLoads.forEach(load => load()); button.setAttribute("aria-expanded", "true"); entry.position(); document.addEventListener("pointerdown", entry.outside, true); document.addEventListener("keydown", entry.keydown, true); document.addEventListener("scroll", entry.scroll, true); window.addEventListener("resize", entry.position); } button.addEventListener("pointerenter", open); button.addEventListener("pointerleave", leave); button.addEventListener("focus", open); button.addEventListener("focusout", focusOut); button.addEventListener("click", event => { event.stopPropagation(); if (active === entry && entry.pinned) close(); else { open(); if (active === entry) { entry.pinned = true; entry.panel.querySelector("a, button").focus(); } } }); } return { bind, close }; } // Source: src/features/opportunities/crm-sr-form.js function createCrmSrForm({ MODAL_ID, allowsCrmActivities, isMissing, getOpportunity, requestSave, notify, formatDate, requestPillars, requestLanguage, requestPlatform, requestServices, createCloseIcon, searchableSelect, environment }) { const { document } = environment; let active = null; function node(tag, className, text) { const element = document.createElement(tag); if (className) element.className = className; if (text !== undefined) element.textContent = text; return element; } function close(restoreFocus = true) { if (!active) return false; if (active.saving) return true; const previous = active; active = null; previous.modal.remove(); if (restoreFocus && previous.trigger?.isConnected) previous.trigger.focus(); return true; } function createButton(number) { const button = node("button", "opportunities-extension-add-action", "+"); button.type = "button"; button.title = "Add Service Request"; button.setAttribute("aria-label", `Add Service Request for ${number}`); button.setAttribute("aria-haspopup", "dialog"); button.addEventListener("click", event => { event.preventDefault(); event.stopPropagation(); open(number, button); }); return button; } function open(number, trigger) { if (active?.saving) return; const host = document.getElementById(MODAL_ID); if (!host || host.hidden || !allowsCrmActivities() || !isMissing(number)) return; close(false); const modal = node("section", "opportunities-extension-action-form-modal"); modal.setAttribute("role", "dialog"); modal.setAttribute("aria-modal", "true"); modal.setAttribute("aria-labelledby", "opportunities-extension-sr-form-title"); const dialog = node("div", "opportunities-extension-action-form-dialog opportunities-extension-sr-form-dialog"); const header = node("header", "opportunities-extension-account-plans-header"); const heading = node("div", "opportunities-extension-action-form-heading"); const title = node("h2", "", "Add Service Request"); title.id = "opportunities-extension-sr-form-title"; heading.append(title, node("p", "", String(number))); const opportunity = getOpportunity(number); const details = node("dl", "opportunities-extension-action-form-context"); for (const [label, value] of [ ["Customer", opportunity?.CustomerAccount?.PartyUniqueName], ["Name", opportunity?.Name], ["Close Date", formatDate(opportunity?.EffectiveDate)] ]) { details.append(node("dt", "", label), node("dd", "", value == null || value === "" ? "-" : String(value))); } heading.append(details); const dismiss = node("button", "opportunities-extension-icon-button"); dismiss.type = "button"; dismiss.setAttribute("aria-label", "Close Service Request form"); dismiss.append(createCloseIcon()); dismiss.addEventListener("click", () => close()); header.append(heading, dismiss); const form = node("form", "opportunities-extension-action-form"); form.noValidate = true; const fields = new Map(); const combos = new Map(); const loadingFields = new Set(["primaryPillar", "language", "platform", "services"]); const availableFields = new Set(); let serviceItems = []; const serviceRadios = []; let serviceGroup; const field = node("label", "opportunities-extension-action-form-field"); const select = node("select"); select.id = "opportunities-extension-sr-primary-pillar"; select.name = "primaryPillar"; select.required = true; field.htmlFor = select.id; field.append(node("span", "", "Primary pillar (required)"), select); const autocomplete = searchableSelect(select, field); fields.set("primaryPillar", select); combos.set(select, autocomplete); const status = node("p", "opportunities-extension-action-form-status opportunities-extension-action-form-wide"); status.setAttribute("role", "status"); const footer = node("footer", "opportunities-extension-action-form-footer"); const retry = node("button", "", "Retry"); retry.type = "button"; const cancel = node("button", "", "Cancel"); const save = node("button", "opportunities-extension-action-form-save", "Save"); save.type = "submit"; save.disabled = true; cancel.type = "button"; cancel.addEventListener("click", () => close()); footer.append(retry, save, cancel); form.append(field); const extraControls = []; const extraLoads = []; function textField(name, label, type) { const wrapper = node("label", "opportunities-extension-action-form-field" + (type === "textarea" ? " opportunities-extension-action-form-wide" : "")); const input = node(type === "textarea" ? "textarea" : "input"); if (type === "textarea") input.rows = 4; else input.type = type; input.id = `opportunities-extension-sr-${name}`; input.name = name; input.required = true; fields.set(name, input); wrapper.htmlFor = input.id; wrapper.append(node("span", "", `${label} (required)`), input); form.append(wrapper); extraControls.push(input); } textField("customerDeadline", "Customer deadline", "date"); function lookupField(name, label, request, radios = false) { const wrapper = node(radios ? "fieldset" : "div", "opportunities-extension-sr-lookup" + (radios ? " opportunities-extension-action-form-wide" : "")); const content = node("div", radios ? "opportunities-extension-sr-services" : ""); const message = node("p", "opportunities-extension-action-form-status"); const retryOption = node("button", "opportunities-extension-sr-lookup-retry", "Retry"); retryOption.type = "button"; retryOption.setAttribute("aria-label", `Retry ${label}`); retryOption.hidden = true; extraControls.push(retryOption); let input, combo; if (radios) { wrapper.append(node("legend", "", `${label} (required)`)); serviceGroup = wrapper; } else { const field = node("label", "opportunities-extension-action-form-field"); input = node("select"); input.id = `opportunities-extension-sr-${name}`; input.name = name; input.required = true; fields.set(name, input); field.htmlFor = input.id; field.append(node("span", "", `${label} (required)`), input); combo = searchableSelect(input, field); combos.set(input, combo); content.append(field); extraControls.push(combo.input); } wrapper.append(content, message, retryOption); form.append(wrapper); let loading = false; async function loadOptions() { if (loading || !current()) return; loading = true; loadingFields.add(name); availableFields.delete(name); updateSave(); retryOption.hidden = true; message.hidden = false; message.setAttribute("role", "status"); message.textContent = `Loading ${label.toLowerCase()}...`; wrapper.setAttribute("aria-busy", "true"); if (input) { input.disabled = true; input.textContent = ""; combo.sync(); } try { const items = await request(); if (!current()) return; if (items.length) availableFields.add(name); if (input) { const blank = node("option", "", items.length ? `Select ${label.toLowerCase()}` : "No options available"); blank.value = ""; input.append(blank); items.forEach(item => { const option = node("option", "", item.name); option.value = item.code; input.append(option); }); input.value = ""; input.disabled = !items.length; } else { content.textContent = ""; serviceItems = items; serviceRadios.length = 0; const seen = new Set(); items.forEach((item, index) => { if (seen.has(String(item.Id))) return; seen.add(String(item.Id)); const row = node("label", "opportunities-extension-sr-service-option"); const radio = node("input"); radio.type = "radio"; radio.name = "service"; radio.required = true; serviceRadios.push(radio); radio.value = String(item.Id); radio.id = `opportunities-extension-sr-service-${index}`; const copy = node("span"); const abstract = node("span", "opportunities-extension-sr-service-abstract", item.Abstract_c || ""); abstract.id = `${radio.id}-description`; radio.setAttribute("aria-describedby", abstract.id); row.htmlFor = radio.id; copy.append(node("span", "opportunities-extension-sr-service-name", item.RecordName), abstract); row.append(radio, copy); content.append(row); extraControls.push(radio); }); } message.textContent = items.length ? "" : "No options available."; message.hidden = !!items.length; } catch (error) { if (!current()) return; message.setAttribute("role", "alert"); message.textContent = error.message || `Unable to load ${label.toLowerCase()}.`; retryOption.hidden = false; } finally { loading = false; loadingFields.delete(name); if (current()) updateSave(); if (current()) { combo?.sync(); wrapper.setAttribute("aria-busy", "false"); } } } retryOption.addEventListener("click", loadOptions); extraLoads.push(loadOptions); } lookupField("language", "Language of delivery", requestLanguage); lookupField("platform", "Platform", requestPlatform); textField("description", "Description", "textarea"); lookupField("services", "Services", requestServices, true); form.append(status, footer); dialog.append(header, form); modal.append(dialog); host.append(modal); const view = { modal, trigger, loading: false }; active = view; const current = () => active === view && allowsCrmActivities(); function updateSave() { save.disabled = loadingFields.size > 0 || availableFields.size < 4 || Boolean(view.saving); } function confirmPayload(payload, requestHeaders = {}) { return new Promise(resolve => { const overlay = node("section", "opportunities-extension-action-form-modal opportunities-extension-sr-confirmation"); overlay.setAttribute("role", "dialog"); overlay.setAttribute("aria-modal", "true"); overlay.setAttribute("aria-labelledby", "opportunities-extension-sr-confirm-title"); const panel = node("div", "opportunities-extension-action-form-dialog opportunities-extension-sr-confirm-dialog"); const heading = node("header", "opportunities-extension-account-plans-header"); const title = node("h2", "", "Confirm Service Request"); title.id = "opportunities-extension-sr-confirm-title"; heading.append(title); const body = node("div", "opportunities-extension-sr-confirm-body"); const label = node("p", "", "requestPostServiceRequest"); label.id = "opportunities-extension-sr-confirm-payload-label"; const preview = node("pre", "opportunities-extension-sr-confirm-payload", JSON.stringify(payload, null, 2)); preview.tabIndex = 0; preview.setAttribute("aria-labelledby", label.id); body.append(label, preview); const headerPreviews = []; for (const name of ["requestSrTokenCatalog", "requestPostServiceRequest"]) { if (!requestHeaders[name]) continue; const headerLabel = node("p", "", `${name} - Headers`); headerLabel.id = `opportunities-extension-sr-confirm-${name}-headers`; const headerPreview = node("pre", "opportunities-extension-sr-confirm-payload", JSON.stringify(requestHeaders[name], null, 2)); headerPreview.tabIndex = 0; headerPreview.setAttribute("aria-labelledby", headerLabel.id); headerPreviews.push(headerPreview); body.append(headerLabel, headerPreview); if (name === "requestSrTokenCatalog" && !requestHeaders[name]["x-appbuilder-client-id"]) { body.append(node("p", "opportunities-extension-action-form-status", "x-appbuilder-client-id: not yet observed in SeaaS requests.")); } } const footer = node("footer", "opportunities-extension-action-form-footer"); const cancel = node("button", "", "Cancel"); const confirm = node("button", "", "Confirm Save"); cancel.type = confirm.type = "button"; footer.append(cancel, confirm); panel.append(heading, body, footer); overlay.append(panel); dialog.inert = true; dialog.setAttribute("aria-hidden", "true"); modal.setAttribute("aria-modal", "false"); modal.append(overlay); let settled = false; function finish(confirmed) { if (settled) return; settled = true; overlay.remove(); dialog.inert = false; dialog.setAttribute("aria-hidden", "false"); modal.setAttribute("aria-modal", "true"); resolve(confirmed); } cancel.addEventListener("click", () => finish(false)); confirm.addEventListener("click", () => finish(true)); overlay.addEventListener("keydown", event => { event.stopPropagation(); if (event.key === "Escape") { event.preventDefault(); finish(false); } if (event.key === "Tab") { const controls = [preview, ...headerPreviews, cancel, confirm]; const index = controls.indexOf(document.activeElement); if (index < 0 || (event.shiftKey ? index === 0 : index === controls.length - 1)) { event.preventDefault(); controls[event.shiftKey ? controls.length - 1 : 0].focus(); } } }); cancel.focus(); }); } form.addEventListener("submit", async event => { event.preventDefault(); if (!current() || view.saving || save.disabled) return; const invalid = []; const value = name => String(fields.get(name)?.value || "").trim(); for (const [name, input] of fields) { let valid = !!value(name); if (combos.has(input)) valid = valid && Array.from(input.children).some(option => option.value === value(name)); if (name === "customerDeadline") { const date = value(name); valid = /^\d{4}-\d{2}-\d{2}$/.test(date) && Number.isFinite(Date.parse(`${date}T00:00:00Z`)) && new Date(`${date}T00:00:00Z`).toISOString().slice(0, 10) === date; } const control = combos.get(input)?.input || input; control.setAttribute("aria-invalid", String(!valid)); if (!valid) invalid.push(control); } const selected = serviceRadios.find(radio => radio.checked); const service = serviceItems.find(item => String(item.Id) === selected?.value); serviceGroup.setAttribute("aria-invalid", String(!service)); if (!service) invalid.push(serviceRadios[0]); if (invalid.length) { status.hidden = false; status.setAttribute("role", "alert"); status.textContent = "Complete the highlighted required fields with valid values."; invalid[0]?.focus(); return; } const payload = { AccountPartyId: opportunity?.CustomerAccount?.PartyId, OpportunityName_Id_c: opportunity?.OptyId, Title: `${service.RecordName}-${number}-${opportunity?.CustomerAccount?.PartyUniqueName || ""}`, RegionText_c: opportunity?.CustomerAccount?.Region_c, CountryText_c: opportunity?.CustomerAccount?.PrimaryAddress?.CountryCode, ServiceNew_Id_c: service.Id, ProblemDescription: value("description"), Language_c: value("language"), CustomerDeadline_c: value("customerDeadline"), TechPlatformLookUp_c: value("platform"), PrimaryPillarNew_c: value("primaryPillar") }; view.saving = true; const controls = [dismiss, retry, cancel, ...fields.values(), ...serviceRadios, ...extraControls]; const disabled = controls.map(control => [control, control.disabled]); controls.forEach(control => { control.disabled = true; }); combos.forEach(combo => combo.sync()); updateSave(); save.textContent = "Preparing..."; form.setAttribute("aria-busy", "true"); status.hidden = true; try { const prepared = await requestSave({ phase: "prepare", payload }); if (!current() || host.hidden || !await confirmPayload(prepared.payload, prepared.headers)) return; save.textContent = "Saving..."; await requestSave({ phase: "confirm", payload: prepared.payload }); view.saving = false; close(); notify("Service Request saved successfully."); } catch (error) { status.hidden = false; status.setAttribute("role", "alert"); status.textContent = error.message || "Unable to save Service Request."; notify(status.textContent, true); } finally { view.saving = false; if (current()) { disabled.forEach(([control, wasDisabled]) => { control.disabled = wasDisabled; }); combos.forEach(combo => combo.sync()); updateSave(); save.textContent = "Save"; form.setAttribute("aria-busy", "false"); save.focus(); } } }); function option(value, text) { const item = node("option", "", text); item.value = value; select.append(item); } async function load() { if (view.loading || !current()) return; view.loading = true; loadingFields.add("primaryPillar"); availableFields.delete("primaryPillar"); updateSave(); retry.hidden = true; select.disabled = true; select.textContent = ""; option("", "Loading..."); autocomplete.sync(); form.setAttribute("data-state", "loading"); form.setAttribute("aria-busy", "true"); status.hidden = false; status.setAttribute("role", "status"); status.textContent = "Loading primary pillars..."; try { const items = await requestPillars(); if (!current()) return; select.textContent = ""; option("", items.length ? "Select primary pillar" : "No options available"); items.forEach(item => option(item.code, item.name)); select.value = ""; select.disabled = !items.length; if (items.length) availableFields.add("primaryPillar"); form.setAttribute("data-state", "ready"); status.hidden = true; } catch (error) { if (!current()) return; form.setAttribute("data-state", "error"); select.textContent = ""; option("", "Unavailable"); status.setAttribute("role", "alert"); status.textContent = error.message || "Unable to load primary pillars."; retry.hidden = false; } finally { view.loading = false; loadingFields.delete("primaryPillar"); if (current()) updateSave(); if (current()) { autocomplete.sync(); form.setAttribute("aria-busy", "false"); } } } retry.addEventListener("click", load); modal.addEventListener("click", event => { if (event.target === modal) close(); }); modal.addEventListener("keydown", event => { if (event.key === "Escape") { event.preventDefault(); event.stopPropagation(); close(); } if (event.key !== "Tab") return; const controls = [dismiss, autocomplete.input, ...extraControls, retry, save, cancel].filter(control => !control.disabled && !control.hidden); const index = controls.indexOf(document.activeElement); if (index < 0 || (event.shiftKey ? index === 0 : index === controls.length - 1)) { event.preventDefault(); controls[event.shiftKey ? controls.length - 1 : 0].focus(); } }); dismiss.focus(); load(); extraLoads.forEach(loadOptions => loadOptions()); } return { createButton, close, open }; } // Source: src/features/opportunities/internal-sr-form.js function createInternalSrForm({ MODAL_ID, searchableSelect, formatDate, getOpportunity, requestPlatform, requestServices, requestSave, notify, environment }) { const { document } = environment; let active = null; function node(tag, className, text) { const el = document.createElement(tag); if (className) el.className = className; if (text !== undefined) el.textContent = text; return el; } function close(restoreFocus = true) { if (!active) return false; if (active.saving) return true; const previous = active; active = null; previous.modal.remove(); if (restoreFocus && previous.trigger?.isConnected) previous.trigger.focus(); return true; } function open(parent, trigger) { if (active?.saving) return; const host = document.getElementById(MODAL_ID); if (!host || host.hidden) return; close(false); const modal = node("section", "opportunities-extension-action-form-modal"); modal.setAttribute("role", "dialog"); modal.setAttribute("aria-modal", "true"); modal.setAttribute("aria-labelledby", "internal-sr-form-title"); const dialog = node("div", "opportunities-extension-action-form-dialog opportunities-extension-internal-sr-dialog"); const header = node("header", "opportunities-extension-account-plans-header"); const heading = node("div", "opportunities-extension-action-form-heading"); const title = node("h2", "", "Create Internal Service Request"); title.id = "internal-sr-form-title"; const context = node("dl", "opportunities-extension-action-form-context"); const opportunity = getOpportunity(parent.OpportunityName_Id_c?.OptyNumber); for (const [label, value] of [["SR Number", parent.SrNumber], ["Opportunity", opportunity?.Name], ["Title", parent.Title]]) { context.append(node("dt", "", label), node("dd", "", value == null || value === "" ? "N/D" : String(value))); } heading.append(title, context); header.append(heading); const form = node("form", "opportunities-extension-action-form"); form.noValidate = true; const fields = new Map(); const combos = new Map(); const ready = new Set(); function field(name, label, type, wide = false) { const wrapper = node("label", "opportunities-extension-action-form-field" + (wide ? " opportunities-extension-action-form-wide" : "")); const input = node(type === "textarea" || type === "select" ? type : "input"); if (!["textarea", "select"].includes(type)) input.type = type; if (type === "textarea") input.rows = 4; input.id = `internal-sr-${name}`; input.name = name; fields.set(name, input); input.required = name !== "customerDeadline"; input.setAttribute("aria-required", String(input.required)); wrapper.htmlFor = input.id; wrapper.append(node("span", "", input.required ? `${label} (required)` : label), input); form.append(wrapper); return { input, wrapper }; } const titleInput = field("title", "Title", "text", true).input; const deadline = field("customerDeadline", "Customer Deadline", "text").input; deadline.readOnly = true; deadline.value = formatDate(parent.CustomerDeadline_c) || "N/D"; field("internalDeadline", "Internal Deadline", "date"); const platform = field("platform", "Platform", "select", true); field("description", "Description", "textarea", true); const services = field("services", "Services", "select", true); const footer = node("footer", "opportunities-extension-action-form-footer"); const cancel = node("button", "", "Cancel"); const save = node("button", "opportunities-extension-action-form-save", "Save"); save.type = "submit"; save.disabled = true; const feedback = node("p", "opportunities-extension-action-form-status opportunities-extension-action-form-wide"); feedback.setAttribute("role", "status"); feedback.hidden = true; cancel.type = "button"; cancel.addEventListener("click", () => close()); footer.append(save, cancel); form.append(feedback, footer); dialog.append(header, form); modal.append(dialog); host.append(modal); const entry = { modal, trigger, saving: false }; active = entry; const validDate = value => /^\d{4}-\d{2}-\d{2}$/.test(value) && Number.isFinite(Date.parse(`${value}T00:00:00Z`)) && new Date(`${value}T00:00:00Z`).toISOString().slice(0, 10) === value; form.addEventListener("submit", async event => { event.preventDefault(); if (active !== entry || entry.saving || ready.size !== 2) return; let firstInvalid = null; for (const [name, input] of fields) { if (!input.required) continue; const value = String(input.value || "").trim(); const valid = !!value && (name !== "internalDeadline" || validDate(value)) && (!combos.has(name) || Array.from(input.children).some(option => option.value === value)); input.setAttribute("aria-invalid", String(!valid)); const visible = combos.get(name)?.input || input; visible.setAttribute("aria-invalid", String(!valid)); if (!valid && !firstInvalid) firstInvalid = visible; } feedback.hidden = false; if (firstInvalid) { feedback.setAttribute("role", "alert"); feedback.textContent = "Complete all required fields with valid values."; firstInvalid.focus(); return; } const value = name => String(fields.get(name).value || "").trim(); const dateParts = value("customerDeadline").match(/^(\d{2})\/(\d{2})\/(\d{4})$/); const payload = { Title: value("title"), ServiceNew_Id_c: value("services"), ProblemDescription: value("description"), CustomerDeadline_c: dateParts ? `${dateParts[3]}-${dateParts[2]}-${dateParts[1]}` : "", TechPlatformLookUp_c: value("platform"), ServiceRequest_Id_SRToInternalSR: String(parent.SrId ?? ""), SvcQuestionsCollection_c: [], InternalDeadline_c: value("internalDeadline") }; entry.saving = true; form.setAttribute("aria-busy", "true"); save.disabled = cancel.disabled = true; save.textContent = "Saving..."; for (const [name, input] of fields) { input.disabled = true; combos.get(name)?.sync(); } feedback.setAttribute("role", "status"); feedback.textContent = "Creating Internal Service Request..."; try { await requestSave(payload); entry.saving = false; close(); notify("Internal Service Request created successfully."); } catch (error) { if (active !== entry) return; feedback.setAttribute("role", "alert"); feedback.textContent = error.message || "Unable to create Internal Service Request."; notify(feedback.textContent, true); } finally { entry.saving = false; if (active === entry) { form.setAttribute("aria-busy", "false"); save.disabled = cancel.disabled = false; save.textContent = "Save"; for (const [name, input] of fields) { input.disabled = false; combos.get(name)?.sync(); } } } }); function load(target, request, service) { const { input, wrapper } = target; input.disabled = true; const combo = searchableSelect(input, wrapper); combos.set(input.name, combo); const status = node("p", "opportunities-extension-action-form-status", "Loading..."); status.setAttribute("role", "status"); wrapper.append(status); wrapper.setAttribute("aria-busy", "true"); Promise.resolve().then(request).then(items => { if (active !== entry) return; const placeholder = node("option", "", items.length ? "Select an option" : "No options available"); placeholder.value = ""; input.append(placeholder); const seen = new Set(); for (const item of items) { const value = String(service ? item.Id : item.code); if (seen.has(value)) continue; seen.add(value); const option = node("option", "", service ? item.RecordName : item.name); option.value = value; if (service && item.Description_c) option.setAttribute("data-description", item.Description_c); input.append(option); } input.value = ""; input.disabled = !items.length; if (items.length) ready.add(input.name); save.disabled = ready.size !== 2; status.textContent = items.length ? "" : "No options available."; status.hidden = !!items.length; combo.sync(); }).catch(error => { if (active !== entry) return; status.setAttribute("role", "alert"); status.textContent = error.message || "Unable to load options. Close and reopen the form to retry."; }).finally(() => { if (active === entry) wrapper.setAttribute("aria-busy", "false"); }); } load(platform, requestPlatform, false); load(services, () => requestServices(parent.SrId), true); modal.addEventListener("keydown", event => { if (event.key === "Escape") { event.preventDefault(); event.stopPropagation(); close(); } if (event.key === "Tab") { const focusable = Array.from(form.querySelectorAll("input, textarea, button")).filter(el => !el.disabled && !el.hidden); const first = focusable[0], last = focusable.at(-1); if (event.shiftKey && document.activeElement === first) { event.preventDefault(); last.focus(); } else if (!event.shiftKey && document.activeElement === last) { event.preventDefault(); first.focus(); } } }); titleInput.focus(); } return { open, close }; } // Source: src/features/opportunities/searchable-select.js function createSearchableSelect({ environment }) { const { document } = environment; function element(tag, className, text) { const node = document.createElement(tag); if (className) node.className = className; if (text !== undefined) node.textContent = text; return node; } function searchableSelect(select, field, onChange, { floating = false, tooltipContent } = {}) { const box = element("div", "opportunities-extension-action-combobox"); const input = element("input"); input.type = "text"; input.autocomplete = "off"; input.required = !!select.required; input.setAttribute("aria-required", String(!!select.required)); select.addEventListener("invalid", event => { event.preventDefault(); input.focus(); }); input.id = `${select.id}-search`; field.htmlFor = input.id; const list = element("div", "opportunities-extension-action-options"); list.id = `${select.id}-options`; list.setAttribute("role", "listbox"); input.setAttribute("role", "combobox"); input.setAttribute("aria-autocomplete", "list"); input.setAttribute("aria-controls", list.id); input.setAttribute("aria-expanded", "false"); select.hidden = true; select.tabIndex = -1; box.append(input, list); field.append(box); const tooltip = tooltipContent ? element("div", "opportunities-extension-select-tooltip") : null; if (tooltip) { tooltip.id = `${select.id}-tooltip`; tooltip.setAttribute("role", "tooltip"); tooltip.hidden = true; box.append(tooltip); } let tooltipAnchor = null; function hideTooltip() { if (!tooltip) return; tooltip.hidden = true; tooltipAnchor?.removeAttribute?.("aria-describedby"); tooltipAnchor = null; } function showTooltip(value, anchor) { hideTooltip(); if (!tooltip || !value) return; const content = tooltipContent(value); if (!content) return; tooltip.replaceChildren(content); tooltip.hidden = false; tooltipAnchor = anchor; anchor.setAttribute("aria-describedby", tooltip.id); const rect = anchor.getBoundingClientRect?.(); const bounds = input.closest?.(".opportunities-extension-action-form-dialog")?.getBoundingClientRect(); if (!rect || !bounds) return; const size = tooltip.getBoundingClientRect(); const left = rect.right + 8 + size.width <= bounds.right ? rect.right + 8 : Math.max(bounds.left + 8, rect.left - size.width - 8); tooltip.style.left = `${left}px`; tooltip.style.top = `${Math.max(bounds.top + 8, Math.min(rect.top, bounds.bottom - size.height - 8))}px`; } function leaveTooltip(event) { if (!tooltip?.contains?.(event.relatedTarget)) hideTooltip(); } tooltip?.addEventListener("mouseleave", hideTooltip); list.addEventListener("scroll", hideTooltip); let matches = [], index = -1; const options = () => Array.from(select.children).filter(item => item.value); const normalize = value => String(value).normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase(); function closeList() { hideTooltip(); list.hidden = true; input.setAttribute("aria-expanded", "false"); input.setAttribute("aria-activedescendant", ""); } function sync() { closeList(); input.disabled = !!select.disabled; input.placeholder = select.children[0]?.textContent || "Select"; input.value = options().find(item => item.value === select.value)?.textContent || ""; input.title = tooltipContent ? "" : input.value; } function highlight() { Array.from(list.children).forEach((node, i) => node.setAttribute("aria-selected", String(i === index))); input.setAttribute("aria-activedescendant", matches[index] ? `${list.id}-${index}` : ""); list.children[index]?.scrollIntoView?.({ block: "nearest" }); if (matches[index]) showTooltip(matches[index].value, list.children[index]); } function choose(i) { if (!matches[i]) return; const changed = select.value !== matches[i].value; select.value = matches[i].value; sync(); if (changed) onChange?.(); } function positionList() { const bounds = input.closest?.(".opportunities-extension-action-form-dialog")?.getBoundingClientRect(); const anchor = input.getBoundingClientRect?.(); if (!bounds || !anchor) return; const below = Math.max(0, bounds.bottom - anchor.bottom - 8); const above = Math.max(0, anchor.top - bounds.top - 8); const opensAbove = below < 180 && above > below; list.setAttribute("data-placement", opensAbove ? "above" : "below"); list.style.maxHeight = `${Math.min(180, opensAbove ? above : below)}px`; if (floating) { list.style.position = "fixed"; list.style.left = `${anchor.left}px`; list.style.width = `${anchor.width}px`; list.style.right = "auto"; list.style.bottom = "auto"; list.style.top = `${opensAbove ? anchor.top - 4 : anchor.bottom + 4}px`; list.style.transform = opensAbove ? "translateY(-100%)" : "none"; } } function render(query = "") { if (input.disabled) return; hideTooltip(); matches = options().filter(item => normalize(`${item.textContent} ${item.getAttribute?.("data-description") || ""} ${item.value}`).includes(normalize(query))); index = -1; list.textContent = ""; matches.forEach((item, i) => { const row = element("div", "opportunities-extension-action-option", item.textContent); const description = item.getAttribute?.("data-description"); if (description) row.append(element("small", "opportunities-extension-option-description", description)); row.id = `${list.id}-${i}`; row.setAttribute("role", "option"); row.setAttribute("aria-selected", "false"); row.addEventListener("mousedown", event => event.preventDefault()); row.addEventListener("click", event => { event.preventDefault(); choose(i); }); if (tooltip) { row.addEventListener("mouseenter", () => showTooltip(item.value, row)); row.addEventListener("mouseleave", leaveTooltip); } list.append(row); }); if (!matches.length) { const empty = element("div", "opportunities-extension-action-option", "No results"); empty.setAttribute("role", "status"); list.append(empty); } positionList(); list.hidden = false; input.setAttribute("aria-expanded", "true"); input.setAttribute("aria-activedescendant", ""); } input.addEventListener("click", () => { render(); input.select?.(); }); input.addEventListener("input", () => render(input.value)); input.addEventListener("blur", sync); if (tooltip) { input.addEventListener("mouseenter", () => { if (list.hidden) showTooltip(select.value, input); }); input.addEventListener("mouseleave", leaveTooltip); } input.addEventListener("keydown", event => { if (event.key === "ArrowDown" || event.key === "ArrowUp") { event.preventDefault(); if (list.hidden) render(); if (matches.length) index = (index + (event.key === "ArrowDown" ? 1 : index < 0 ? 0 : -1) + matches.length) % matches.length; highlight(); } else if (event.key === "Enter" && !list.hidden) { event.preventDefault(); choose(index < 0 && matches.length === 1 ? 0 : index); } else if (event.key === "Escape" && (!list.hidden || (tooltip && !tooltip.hidden))) { event.preventDefault(); event.stopPropagation(); sync(); } else if (event.key === "Tab") sync(); }); sync(); return { input, sync }; } return searchableSelect; } // Source: src/features/opportunities/internal-sr-session.js function createInternalSrSession({ query, environment, sessionUrl, sessionLabel = "Internal SR" }) { const { document, setTimeout } = environment; let recovery = null; const wait = duration => new Promise(resolve => setTimeout(resolve, duration)); const now = () => environment.performance?.now() ?? Date.now(); function isUnauthorized(result) { const status = result?.httpStatus ?? result?.debugRequests?.at(-1)?.httpStatus; if (status != null) return Number(status) === 401; // Older background workers may return only the request error text. return /failed with status 401\.$/.test(result?.error || ""); } async function warmSession(id) { const frame = document.createElement("iframe"); frame.src = sessionUrl || "https://eeho.fa.us2.oraclecloud.com/fscmUI/redwood/service/ec/container/sr/edit?srNumber=SR0001421040&srId=302963804324543&view=showisr"; frame.title = `${sessionLabel} session initialization`; frame.tabIndex = -1; frame.setAttribute("aria-hidden", "true"); frame.setAttribute("sandbox", "allow-scripts allow-same-origin allow-forms"); frame.style.cssText = "position:fixed;width:1px;height:1px;left:-10000px;top:0;border:0;opacity:0;pointer-events:none;"; let blocked = false; let loaded = false; frame.addEventListener("error", () => { blocked = true; }); frame.addEventListener("load", () => { loaded = true; }); try { if (!document.body) throw new Error(`The current page cannot initialize the ${sessionLabel} session.`); document.body.append(frame); const deadline = now() + 60000; for (let attempt = 0; now() < deadline; attempt += 1) { // Check immediately, then briefly poll faster before backing off. if (attempt > 0) await wait(Math.min(attempt <= 4 ? 250 : 1000, deadline - now())); if (now() >= deadline) break; if (blocked) throw new Error(`The ${sessionLabel} session iframe could not be loaded. Sign in manually and try again.`); const result = await query(id); if (result?.ok) return { id, result }; if (!isUnauthorized(result)) throw new Error(result?.error || `Unable to load ${sessionLabel} data.`); } throw new Error(`${sessionLabel} session timed out after 60 seconds: the API still returned HTTP 401. ${loaded ? "The iframe emitted a load event, but authentication was not confirmed." : "The iframe did not emit a load event."} Sign in manually and try again.`); } finally { // Stop remaining page resources without stopping the caller's page. try { frame.contentWindow?.stop(); } catch { /* Redirects can make the frame cross-origin. */ } frame.src = "about:blank"; frame.remove(); } } async function request(id) { let result = await query(id); for (let attempt = 0; !result?.ok && isUnauthorized(result) && attempt < 2; attempt += 1) { if (!recovery) { recovery = warmSession(id); const pending = recovery; pending.finally(() => { if (recovery === pending) recovery = null; }).catch(() => {}); } const recovered = await recovery; result = recovered.id === id ? recovered.result : await query(id); } if (!result?.ok) throw new Error(result?.error || `Unable to load ${sessionLabel} data.`); return result.items; } return { request }; } // Source: src/features/opportunities/sr-session-preparation.js function createSrSessionPreparation({ ensureCurrentUser, allowsCrmActivities, sendRequest, reportProgress, appendLog, onReady = async () => {} }) { let pending = null; function ensure() { if (pending) return pending; pending = (async () => { try { await ensureCurrentUser(); if (!allowsCrmActivities()) return; reportProgress("running"); const result = await sendRequest(); appendLog(result?.debugRequests || []); if (!result?.ok) throw new Error(result?.error || "Session initialization failed."); reportProgress("complete"); await onReady(); } catch { reportProgress("error"); } })(); return pending; } return { ensure }; } // Source: src/features/opportunities/workload-actions.js function createOpportunityWorkloadActions({ authSessionState, allowsCrmActivities, requestConsumerTokenServiceOnce, sendRuntimeMessage, appendBackgroundRequestLog, queueProductsTableRender, productsContentState, messageTypes, reportProgress, activityCache, createActionButton, closeActionForm, setBadgeContent, environment }) { let session = newSession(); function newSession() { return { enabled: true, records: new Map(), queue: [], running: 0, progress: new Set() }; } function updateProgress(current) { if (current !== session || !current.enabled || !current.progress.size) return; const records = [...current.progress]; const completed = records.filter(record => record.status !== "loading").length; const failed = records.filter(record => record.status === "error").length; reportProgress(completed < records.length ? "running" : failed ? "complete-with-errors" : "complete", completed, records.length); } function reset(enabled = true) { closeActionForm(false); session.enabled = false; for (const job of session.queue) job.record.resolveCompletion(); session.queue.length = 0; session = newSession(); session.enabled = enabled; reportProgress("idle"); } function idsFor(products) { return [...new Set((products?.items || []).map(item => String(item?.RevnId ?? "").trim()).filter(Boolean))]; } function loadProducts(products, refresh = false, cacheOptions = null) { if (!session.enabled || !allowsCrmActivities()) return; const pending = []; if ([...session.progress].every(record => record.status !== "loading")) session.progress.clear(); for (const revnId of idsFor(products)) { if (!refresh && session.records.has(revnId)) { pending.push(session.records.get(revnId).completion); continue; } session.progress.delete(session.records.get(revnId)); const record = { status: "loading", actions: [], hasNext: false }; record.completion = new Promise(resolve => { record.resolveCompletion = resolve; }); pending.push(record.completion); session.records.set(revnId, record); if (!cacheOptions) session.progress.add(record); session.queue.push({ revnId, record, cacheOptions }); } updateProgress(session); pump(session); return Promise.all(pending); } function reopen() { reset(); for (const products of productsContentState.opptyProductsByNumber.values()) loadProducts(products); queueProductsTableRender(); } function blockProducts(products) { if (!allowsCrmActivities()) return; for (const id of idsFor(products)) { session.records.set(id, { status: "error", actions: [], hasNext: false, error: "CRM service requests unavailable." }); } updateProgress(session); } function pump(current) { while (current.enabled && current === session && current.running < 4 && current.queue.length) { const job = current.queue.shift(); current.running += 1; run(current, job).finally(() => { current.running -= 1; pump(current); }); } } async function run(current, { revnId, record, cacheOptions }) { try { const cached = cacheOptions ? await activityCache.read("actions", revnId, cacheOptions.cachedAt) : null; if (!current.enabled || current !== session || current.records.get(revnId) !== record || !allowsCrmActivities()) return; if (cached) { record.actions = cached.items; record.hasNext = cached.hasNext; record.status = "ready"; return; } if (cacheOptions?.cacheOnly) { record.status = "error"; record.error = "No matching activity cache for this product update."; return; } current.progress.add(record); updateProgress(current); let result; for (let attempt = 1; attempt <= 5; attempt += 1) { try { if (!current.enabled || current !== session || current.records.get(revnId) !== record || !allowsCrmActivities()) return; const authorizations = await requestConsumerTokenServiceOnce(); if (!current.enabled || current !== session || current.records.get(revnId) !== record || !allowsCrmActivities()) return; result = await sendRuntimeMessage({ type: messageTypes.requestWlActions, revnId, authorization: authorizations?.workloadActions }); if (!current.enabled || current !== session || current.records.get(revnId) !== record || !allowsCrmActivities()) return; appendBackgroundRequestLog(result?.debugRequests); if (!result?.ok) throw new Error(result?.error || "Unable to load workload actions."); break; } catch (error) { if (attempt === 5) throw error; } } record.actions = result.actions; record.hasNext = result.hasNext; record.status = "ready"; if (cacheOptions) await activityCache.write("actions", revnId, cacheOptions.cachedAt, result.actions, result.hasNext); } catch (error) { if (!current.enabled || current !== session) return; record.status = "error"; record.error = error.message || "Unable to load workload actions."; } finally { record.resolveCompletion(); if (current.enabled && current === session) { updateProgress(current); queueProductsTableRender(); } } } function getSummary(optyNumber) { if (!session.enabled || !allowsCrmActivities() || !optyNumber) return null; const products = productsContentState.opptyProductsByNumber.get(String(optyNumber)); if (!products) return null; const ids = idsFor(products); const records = ids.map(id => session.records.get(id)); const actions = records.flatMap(record => record?.actions || []); const pending = records.some(record => !record || record.status === "loading"); const uncertain = products.error || (products.items || []).some(item => !String(item?.RevnId ?? "").trim()) || records.some(record => record?.status === "error" || record?.hasNext); const principal = authSessionState.principal; const owned = Boolean(principal?.trim()) && actions.some(action => typeof action.owner === "string" && action.owner.trim().toLowerCase() === principal.trim().toLowerCase()); const state = owned ? "matched" : pending ? "loading" : uncertain || !principal ? "error" : "missing"; return { state, actions, records, principal, pending, uncertain }; } async function applySavedAction(optyNumber, revnId, response, submittedAction = {}) { const products = productsContentState.opptyProductsByNumber.get(String(optyNumber)); const id = String(revnId); if (!session.enabled || !allowsCrmActivities() || !idsFor(products).includes(id)) return false; const returned = Array.isArray(response) ? response : Array.isArray(response?.actions) ? response.actions : [response?.action || response]; const saved = returned.filter(action => action && typeof action === "object" && action.id != null && (action.revenueId == null || String(action.revenueId) === id)); if (!saved.length) return false; const previous = session.records.get(id); const actions = [...(previous?.actions || [])]; for (const action of saved) { // The POST can return a display name in owner, unlike the workload GET. const ownerEmail = typeof action.ownerEmail === "string" && action.ownerEmail.trim() ? action.ownerEmail.trim() : submittedAction.ownerEmail || action.owner || ""; const normalized = { ...action, ownerEmail, owner: ownerEmail }; const index = actions.findIndex(item => String(item.id) === String(action.id)); if (index === -1) actions.push(normalized); else actions[index] = { ...actions[index], ...normalized }; } const record = { status: "ready", actions, hasNext: previous?.hasNext || false, completion: Promise.resolve(), resolveCompletion() {} }; session.records.set(id, record); previous?.resolveCompletion?.(); session.progress.delete(previous); queueProductsTableRender(); if (products.cachedAt) await activityCache.write("actions", id, products.cachedAt, actions, record.hasNext); return true; } function createBadge(optyNumber) { const summary = getSummary(optyNumber); if (!summary) return null; const { state, actions, records, principal, pending, uncertain } = summary; const badge = environment.document.createElement("span"); badge.className = "opportunities-extension-crm-sr-badge opportunities-extension-wl-actions-badge"; badge.setAttribute("data-state", state); setBadgeContent(badge, "actions", state === "matched" || state === "missing" ? actions.length : state === "loading" ? "..." : "?"); badge.title = actions.length ? actions.map(action => [ `Action: ${action.name ?? "N/D"}`, `ID: ${action.id ?? "N/D"}`, `Status: ${action.status ?? "N/D"}`, `Owner: ${action.owner || "N/D"}`, `Email: ${action.ownerEmail || "N/D"}`, `Workload: ${action.workloadName || "N/D"}`, `Description: ${action.workloadDescription || "N/D"}`, `Start: ${action.startDate || "N/D"}`, `End: ${action.endDate || "N/D"}` ].join("\n")).join("\n\n") : state === "missing" ? "No actions found for this opportunity's products." : state === "loading" ? "Loading workload actions..." : "Unable to confirm workload actions. Check product RevnId and Debug Requests."; if (records.some(record => record?.hasNext)) badge.title += "\nAdditional actions exist; pagination is not enabled."; const errors = [...new Set(records.filter(record => record?.error).map(record => record.error))]; if (errors.length) badge.title += `\n${errors.join("\n")}`; if (!principal) badge.title += "\nTokenrelay principal is unavailable; ownership cannot be verified."; else if (actions.length && state === "missing") badge.title += "\nNone of the returned actions belong to the signed-in principal."; if (actions.length && (pending || uncertain)) badge.title += "\nSome workloads are still loading or could not be fully checked."; badge.tabIndex = 0; badge.setAttribute("aria-label", badge.title); if (state === "missing") badge.append(createActionButton(String(optyNumber))); return badge; } return { reset, reopen, loadProducts, blockProducts, createBadge, getSummary, applySavedAction }; } // Source: src/features/opportunities/workload-action-form.js function createWorkloadActionForm({ MODAL_ID, allowsCrmActivities, isMissingAction, getOpportunity, getProducts, requestTeamMembers, requestLookup, getCurrentUserId, saveAction, onActionSaved, notify, formatDate, createCloseIcon, searchableSelect, environment }) { const { document } = environment; let active = null; function element(tag, className, text) { const node = document.createElement(tag); if (className) node.className = className; if (text !== undefined) node.textContent = text; return node; } function close(restoreFocus = true) { if (!active) return false; if (active.saving) return true; const previous = active; active = null; previous.modal.remove(); if (restoreFocus && previous.trigger?.isConnected) previous.trigger.focus(); return true; } function createButton(optyNumber) { const button = element("button", "opportunities-extension-add-action", "+"); button.type = "button"; button.title = "Add Action"; button.setAttribute("aria-label", `Add Action for ${optyNumber}`); button.setAttribute("aria-haspopup", "dialog"); button.addEventListener("click", event => { event.preventDefault(); event.stopPropagation(); open(optyNumber, button); }); return button; } function open(optyNumber, trigger) { if (!allowsCrmActivities() || !isMissingAction(optyNumber)) return; const host = document.getElementById(MODAL_ID); if (!host || host.hidden) return; close(false); const modal = element("section", "opportunities-extension-action-form-modal"); modal.setAttribute("role", "dialog"); modal.setAttribute("aria-modal", "true"); modal.setAttribute("aria-labelledby", "opportunities-extension-action-form-title"); const dialog = element("div", "opportunities-extension-action-form-dialog"); const header = element("header", "opportunities-extension-account-plans-header"); const heading = element("div", "opportunities-extension-action-form-heading"); const title = element("h2", "", "Add Action"); title.id = "opportunities-extension-action-form-title"; heading.append(title, element("p", "", String(optyNumber))); const opportunity = getOpportunity(optyNumber); const details = element("dl", "opportunities-extension-action-form-context"); for (const [label, value] of [ ["Customer", opportunity?.CustomerAccount?.PartyUniqueName], ["Name", opportunity?.Name], ["Close Date", formatDate(opportunity?.EffectiveDate)] ]) { details.append(element("dt", "", label), element("dd", "", value == null || value === "" ? "-" : String(value))); } heading.append(details); const dismiss = element("button", "opportunities-extension-icon-button"); dismiss.type = "button"; dismiss.title = "Close"; dismiss.setAttribute("aria-label", "Close Action form"); dismiss.append(createCloseIcon()); dismiss.addEventListener("click", () => close()); header.append(heading, dismiss); const form = element("form", "opportunities-extension-action-form"); form.noValidate = true; const workloads = [...new Map(getProducts(optyNumber).filter(product => /^\d+$/.test(String(product?.RevnId ?? ""))) .map(product => [String(product.RevnId), product])).entries()]; const workloadField = element("label", "opportunities-extension-action-form-field opportunities-extension-action-form-wide"); const workload = element("select"); workload.id = "opportunities-extension-action-form-workload"; workloadField.htmlFor = workload.id; function option(select, value, label) { const item = element("option", "", label); item.value = value; select.append(item); } option(workload, "", "Select workload"); for (const [id, product] of workloads) option(workload, id, `${product.WorkloadName_c || product.ProdGroupName || "Workload"} (${id})`); workload.value = workloads.length === 1 ? workloads[0][0] : ""; workloadField.hidden = workloads.length <= 1; workloadField.append(element("span", "", "Workload"), workload); const status = element("p", "opportunities-extension-action-form-status opportunities-extension-action-form-wide"); status.setAttribute("role", "status"); form.append(workloadField, status); const searchable = new Map(); searchable.set(workload, searchableSelect(workload, workloadField, () => loadOptions())); const fields = []; const selects = []; const lookupKeys = { role: "WWB_ACTION_ROLE", action: "WWB_ACTION_NAME", team: "WWB_ACTION_TEAM", status: "WWB_ACTION_STATUS", complexity: "WWB_ACTION_COMPLEXITY" }; for (const [name, label, type] of [ ["owner", "Owner", "select"], ["role", "Role", "select"], ["action", "Action", "select"], ["team", "Team", "select"], ["startDate", "Start date", "date"], ["endDate", "End Date", "date"], ["status", "Status", "select"], ["complexity", "Complexity", "select"], ["notes", "Notes", "textarea"] ]) { const field = element("label", "opportunities-extension-action-form-field"); const input = element(type === "select" || type === "textarea" ? type : "input"); if (type === "select") selects.push({ input, label, name }); else if (type === "textarea") input.rows = 5; else input.type = type; input.disabled = true; input.required = name !== "notes"; input.setAttribute("aria-required", String(input.required)); input.name = name; input.id = `opportunities-extension-action-form-${name}`; field.htmlFor = input.id; if (["notes", "team", "action"].includes(name)) field.className += " opportunities-extension-action-form-wide"; field.append(element("span", "", `${label} (${input.required ? "required" : "optional"})`), input); if (type === "select") searchable.set(input, searchableSelect(input, field)); form.append(field); fields.push(input); } const footer = element("footer", "opportunities-extension-action-form-footer"); const save = element("button", "opportunities-extension-action-form-save", "Save"); save.type = "submit"; save.disabled = true; save.title = "Save Action"; const retry = element("button", "", "Retry"); retry.type = "button"; retry.hidden = true; const cancel = element("button", "", "Cancel"); cancel.type = "button"; cancel.addEventListener("click", () => close()); footer.append(retry, save, cancel); form.append(footer); dialog.append(header, form); modal.append(dialog); host.append(modal); const view = { modal, trigger, generation: 0, lookup: null, loadingId: null }; active = view; const isOpen = () => active === view && allowsCrmActivities(); let ready = false; function validate() { const invalid = []; const value = name => String(fields.find(field => field.name === name)?.value || "").trim(); for (const field of fields) { let valid = !field.required || !!value(field.name); if (field.required && searchable.has(field)) valid = Array.from(field.children).some(option => option.value && option.value === field.value); if (field.type === "date") { const date = value(field.name); valid = /^\d{4}-\d{2}-\d{2}$/.test(date) && Number.isFinite(Date.parse(`${date}T00:00:00.000Z`)); if (valid) valid = new Date(`${date}T00:00:00.000Z`).toISOString().slice(0, 10) === date; if (field.name === "endDate" && value("startDate") > date) valid = false; } const control = searchable.get(field)?.input || field; control.setAttribute("aria-invalid", String(!valid)); if (!valid) invalid.push(control); } return { invalid, value }; } form.addEventListener("submit", async event => { event.preventDefault(); if (!ready || !isOpen() || view.saving) return; const { invalid, value } = validate(); if (invalid.length) { status.hidden = false; status.setAttribute("role", "alert"); status.textContent = "Complete the highlighted required fields. End date must be on or after start date."; invalid[0].focus(); return; } const userId = getCurrentUserId(); if (!userId) { notify("Unable to save: current user is unavailable.", true); return; } const payload = { parentType: "WORKLOAD", parentId: workload.value, action: { ownerEmail: value("owner"), role: value("role"), name: value("action"), team: value("team"), tags: "", startDate: `${value("startDate")}T00:00:00.000Z`, endDate: `${value("endDate")}T00:00:00.000Z`, status: value("status"), complexity: value("complexity"), notes: value("notes") ? [value("notes")] : [], objective: "", createdBy: userId, updatedBy: userId } }; view.saving = true; save.disabled = cancel.disabled = dismiss.disabled = workload.disabled = true; fields.forEach(field => { field.disabled = true; }); searchable.forEach(control => control.sync()); save.textContent = "Saving..."; form.setAttribute("aria-busy", "true"); status.hidden = true; try { const result = await saveAction(payload, isOpen); if (!result) return; view.saving = false; close(); try { await onActionSaved(optyNumber, payload.parentId, result.result, payload.action); } catch { notify("Action saved, but the local badge could not be updated.", true); return; } notify("Action saved successfully."); } catch (error) { status.hidden = false; status.setAttribute("role", "alert"); status.textContent = error.message || "Unable to save Action."; notify(status.textContent, true); } finally { view.saving = false; if (active === view) { save.disabled = cancel.disabled = dismiss.disabled = workload.disabled = false; fields.forEach(field => { field.disabled = false; }); searchable.forEach(control => control.sync()); save.textContent = "Save"; form.setAttribute("aria-busy", "false"); } } }); const settled = promise => promise.then(result => ({ result }), error => ({ error })); async function loadOptions(refresh = false) { const id = workload.value; if (!isOpen() || (view.loadingId === id && !refresh)) return; const generation = ++view.generation; const isCurrent = () => isOpen() && view.generation === generation; ready = false; save.disabled = true; retry.hidden = true; status.hidden = false; status.setAttribute("role", "status"); fields.forEach(input => { input.disabled = true; }); selects.forEach(({ input }) => { input.textContent = ""; option(input, "", "Loading..."); }); const syncSearch = () => selects.forEach(({ input }) => searchable.get(input).sync()); syncSearch(); if (!workloads.length) { form.setAttribute("data-state", "error"); form.setAttribute("aria-busy", "false"); status.setAttribute("role", "alert"); status.textContent = "No product with a valid RevnId is available for this opportunity."; selects.forEach(({ input }) => { input.textContent = ""; option(input, "", "Unavailable"); }); syncSearch(); return; } if (!view.lookup || refresh) view.lookup = settled(requestLookup(isOpen)); if (!workloads.some(([value]) => value === id)) { view.loadingId = null; form.setAttribute("data-state", "waiting"); form.setAttribute("aria-busy", "false"); status.textContent = "Select a workload to load Action options."; return; } view.loadingId = id; form.setAttribute("data-state", "loading"); form.setAttribute("aria-busy", "true"); status.textContent = "Loading Action options..."; try { const [lookup, team] = await Promise.all([view.lookup, settled(requestTeamMembers(id, isCurrent))]); if (!isCurrent()) return; if (lookup.error || team.error) throw lookup.error || team.error; const values = selects.map(({ name }) => name === "owner" ? team.result.members.map(({ email }) => ({ code: email, name: email })) : lookup.result.lookups[lookupKeys[name]]); const defaults = { owner: getCurrentUserId(), role: "Cloud Architect", action: "Success Plan & Consumption review", status: "Complete" }; const normalize = value => String(value ?? "").trim().toLowerCase(); for (const [index, { input, label, name }] of selects.entries()) { input.textContent = ""; option(input, "", values[index].length ? `Select ${label.toLowerCase()}` : "No options available"); for (const item of values[index]) option(input, item.code, item.name); const match = defaults[name] && values[index].find(item => normalize(name === "owner" ? item.code : item.name) === normalize(defaults[name])); input.value = match ? match.code : ""; } fields.forEach(input => { input.disabled = false; }); selects.forEach(({ input }, index) => { input.disabled = !values[index].length; }); form.setAttribute("data-state", "ready"); ready = true; save.disabled = false; status.hidden = true; syncSearch(); if (document.activeElement === dismiss) { const first = fields.find(field => !field.disabled); (searchable.get(first)?.input || first)?.focus(); } } catch (error) { if (!isCurrent()) return; form.setAttribute("data-state", "error"); status.setAttribute("role", "alert"); status.textContent = error.message || "Unable to load Action options."; selects.forEach(({ input }) => { input.textContent = ""; option(input, "", "Unavailable"); }); syncSearch(); retry.hidden = false; } finally { if (isCurrent()) { view.loadingId = null; form.setAttribute("aria-busy", "false"); } } } retry.addEventListener("click", () => { if (!view.loadingId) loadOptions(true); }); workload.addEventListener("change", () => loadOptions()); modal.addEventListener("click", event => { if (event.target === modal) close(); }); modal.addEventListener("keydown", event => { if (event.key !== "Tab") return; const controls = [dismiss, ...(!workloadField.hidden ? [workload] : []), ...fields, retry, save, cancel] .map(control => searchable.get(control)?.input || control) .filter(control => !control.disabled && !control.hidden); const index = controls.indexOf(document.activeElement); if (!controls.length) { event.preventDefault(); return; } if (index === -1 || (!event.shiftKey && index === controls.length - 1) || (event.shiftKey && index === 0)) { event.preventDefault(); controls[event.shiftKey ? controls.length - 1 : 0].focus(); } }); if (!workloadField.hidden) searchable.get(workload).input.focus(); else dismiss.focus(); loadOptions(); } return { createButton, open, close }; } // Source: src/features/opportunities/workload-action-options.js function createWorkloadActionOptions({ requestConsumerTokenServiceOnce, sendRuntimeMessage, appendBackgroundRequestLog, messageTypes }) { async function request(type, workloadId, isCurrent, payload) { const authorizations = await requestConsumerTokenServiceOnce(); if (!isCurrent()) return null; const authorization = authorizations?.workloadActions; if (!authorization?.value) throw new Error("WWB-TS uiaasHeader.value is unavailable."); const result = await sendRuntimeMessage({ type, ...(workloadId === undefined ? {} : { workloadId }), ...(payload ? { payload } : {}), authorization }); appendBackgroundRequestLog(result?.debugRequests || []); if (!result?.ok) throw new Error(result?.error || "Unable to load Action options."); return result; } return { save: (payload, isCurrent) => request(messageTypes.requestMgmtAction, undefined, isCurrent, payload), requestTeamMembers: (id, isCurrent) => request(messageTypes.requestActionTeamMembers, id, isCurrent), requestLookup: isCurrent => request(messageTypes.requestActionFindLookup, undefined, isCurrent) }; } // Source: src/features/opportunities/fiscal-periods.js function createOpportunitiesFiscalPeriods() { function getFiscalPeriodRange(period, currentDate) { const currentYear = currentDate.getFullYear(); const currentMonth = currentDate.getMonth(); const fiscalStartYear = currentMonth >= 5 ? currentYear : currentYear - 1; const currentQuarterIndex = Math.floor(((currentMonth - 5 + 12) % 12) / 3); if (period === "Current Fiscal Year") { const isBeforeFiscalYearStart = currentMonth < 5; const startYear = isBeforeFiscalYearStart ? currentYear - 1 : currentYear; const endYear = isBeforeFiscalYearStart ? currentYear : currentYear + 1; return createDateRange(startYear, 5, endYear, 4); } let quarterOffset; let numberOfQuarters = 1; if (period === "Current Quarter") { quarterOffset = currentQuarterIndex; } else if (period === "Next Quarter") { quarterOffset = currentQuarterIndex + 1; } else if (period === "Previous Quarter") { quarterOffset = currentQuarterIndex - 1; } else if (period === "4 Rolling Quarters (CQ + 3)") { quarterOffset = currentQuarterIndex; numberOfQuarters = 4; } else { return null; } const start = new Date(fiscalStartYear, 5 + (quarterOffset * 3), 1); const end = new Date(start.getFullYear(), start.getMonth() + (numberOfQuarters * 3), 0); return { startCloseDate: formatOracleDate(start), endCloseDate: formatOracleDate(end) }; } function createDateRange(startYear, startMonth, endYear, endMonth) { return { startCloseDate: formatOracleDate(new Date(startYear, startMonth, 1)), endCloseDate: formatOracleDate(new Date(endYear, endMonth + 1, 0)) }; } function formatOracleDate(date) { const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, "0"); const day = String(date.getDate()).padStart(2, "0"); return `${year}-${month}-${day}T00:00:00.000Z`; } return { getFiscalPeriodRange, createDateRange, formatOracleDate }; } // Source: src/features/opportunities/table.js function createOpportunitiesTable({ ACCOUNT_DETAIL_URL, OPPORTUNITIES_COLUMNS, OPPORTUNITIES_TABLE_ID, OPPORTUNITY_DETAIL_URL, createCloudConsumptionButton, createInstallBaseButton, createServiceRequestsButton, createCopyOpportunityButton, createDetailLink, createProductsCountButton, createProductsRampBadge, createCrmServiceRequestBadge, closeCrmSrPopover, createWorkloadActionsBadge, createProductsDetailRow, createStageBadge, createStatusBadge, displayOpportunityValue, getNestedValue, getSortedOpportunities, opportunitiesTableState, productsContentState, renderCustomerFilter, renderOwnerFilter, renderActivityFilters, renderStageDashboard, renderCalendar, resetCustomerFilter, resetOwnerFilter, environment }) { const { document } = environment; function setOpportunitiesTableState(nextState) { if (nextState.status === "ready" && Array.isArray(nextState.items)) { resetCustomerFilter(nextState.items); resetOwnerFilter(nextState.items); } opportunitiesTableState.opportunitiesTableState = { ...opportunitiesTableState.opportunitiesTableState, ...nextState, sortKey: nextState.items ? "" : opportunitiesTableState.opportunitiesTableState.sortKey, sortDirection: nextState.items ? "ascending" : opportunitiesTableState.opportunitiesTableState.sortDirection }; renderOpportunitiesTable(); renderCustomerFilter(); renderOwnerFilter(); } function renderOpportunitiesTable() { renderActivityFilters(); const table = document.getElementById(OPPORTUNITIES_TABLE_ID); if (!table) { return; } closeCrmSrPopover(); table.textContent = ""; table.setAttribute("data-opportunities-layout", "inline-actions"); table.setAttribute("aria-busy", opportunitiesTableState.opportunitiesTableState.status === "loading" ? "true" : "false"); const caption = document.createElement("caption"); caption.className = "opportunities-extension-visually-hidden"; caption.textContent = "Opportunities"; const tableHead = document.createElement("thead"); const headerRow = document.createElement("tr"); OPPORTUNITIES_COLUMNS.forEach((column) => { const header = document.createElement("th"); const isSorted = opportunitiesTableState.opportunitiesTableState.sortKey === column.key; header.scope = "col"; header.setAttribute("data-column", column.key); if (column.sortable === false) { const label = document.createElement("span"); label.className = "opportunities-extension-table-header-label"; label.textContent = column.label; header.append(label); } else { header.setAttribute("aria-sort", isSorted ? opportunitiesTableState.opportunitiesTableState.sortDirection : "none"); const sortButton = document.createElement("button"); sortButton.type = "button"; sortButton.className = "opportunities-extension-sort-button"; sortButton.setAttribute("data-sort-direction", isSorted ? opportunitiesTableState.opportunitiesTableState.sortDirection : "none"); sortButton.setAttribute("aria-label", `Sort by ${column.label}${isSorted ? `, ${opportunitiesTableState.opportunitiesTableState.sortDirection}` : ""}`); const sortLabel = document.createElement("span"); sortLabel.className = "opportunities-extension-sort-label"; sortLabel.textContent = column.label; sortButton.append(sortLabel); sortButton.addEventListener("click", () => sortOpportunitiesBy(column.key)); header.append(sortButton); } headerRow.append(header); }); tableHead.append(headerRow); const tableBody = document.createElement("tbody"); const items = getSortedOpportunities(); renderStageDashboard(items, opportunitiesTableState.opportunitiesTableState.items.length); renderCalendar?.(items, opportunitiesTableState.opportunitiesTableState); if (opportunitiesTableState.opportunitiesTableState.status === "loading" || opportunitiesTableState.opportunitiesTableState.status === "error" || opportunitiesTableState.opportunitiesTableState.status === "empty" || (opportunitiesTableState.opportunitiesTableState.status === "ready" && items.length === 0)) { const row = document.createElement("tr"); const cell = document.createElement("td"); cell.className = "opportunities-extension-table-status"; cell.colSpan = OPPORTUNITIES_COLUMNS.length; cell.textContent = opportunitiesTableState.opportunitiesTableState.message || "No opportunities found."; row.append(cell); tableBody.append(row); } else { items.forEach((item) => { const row = document.createElement("tr"); OPPORTUNITIES_COLUMNS.forEach((column) => { const cell = document.createElement("td"); cell.setAttribute("data-column", column.key); if (column.key === "stage") { cell.append(createStageBadge(column.display(item))); } else if (column.key === "status") { cell.append(createStatusBadge(column.display(item))); } else if (column.key === "name" || column.key === "optyNumber") { const opportunityLink = createDetailLink( column.display(item), item.OptyNumber ? `${OPPORTUNITY_DETAIL_URL}${encodeURIComponent(item.OptyNumber)}` : "" ); if (column.key === "name") { const nameCellContent = document.createElement("span"); nameCellContent.className = "opportunities-extension-cell-content"; const productActions = document.createElement("span"); productActions.className = "opportunities-extension-cell-actions"; const productsResult = item.OptyNumber && productsContentState.opptyProductsByNumber.get(String(item.OptyNumber)); if (productsResult) { productActions.append(createProductsCountButton(item.OptyNumber, productsResult)); const rampBadge = createProductsRampBadge(productsResult); if (rampBadge) productActions.append(rampBadge); } const srBadge = createCrmServiceRequestBadge(item.OptyNumber); if (srBadge) productActions.append(srBadge); const actionsBadge = createWorkloadActionsBadge(item.OptyNumber); if (actionsBadge) productActions.append(actionsBadge); nameCellContent.append(opportunityLink, productActions); cell.append(nameCellContent); } else if (column.key === "optyNumber" && item.OptyNumber) { const opportunityCellContent = document.createElement("span"); opportunityCellContent.className = "opportunities-extension-opty-number-content"; opportunityCellContent.append(opportunityLink, createCopyOpportunityButton(item.OptyNumber)); cell.append(opportunityCellContent); } else { cell.append(opportunityLink); } } else if (column.key === "customer") { const customerCellContent = document.createElement("span"); customerCellContent.className = "opportunities-extension-cell-content"; const customerLink = createDetailLink( column.display(item), getNestedValue(item, ["CustomerAccount", "PartyId"]) ? `${ACCOUNT_DETAIL_URL}${encodeURIComponent(getNestedValue(item, ["CustomerAccount", "PartyId"]))}` : "" ); const customerActions = document.createElement("span"); customerActions.className = "opportunities-extension-cell-actions"; customerActions.append(createCloudConsumptionButton(item), createInstallBaseButton(item), createServiceRequestsButton(item)); customerCellContent.append(customerLink, customerActions); cell.append(customerCellContent); } else { cell.textContent = displayOpportunityValue(column, item); } row.append(cell); }); tableBody.append(row); if (item.OptyNumber && productsContentState.expandedProductOptyNumbers.has(String(item.OptyNumber))) { tableBody.append(createProductsDetailRow(item)); } }); } table.append(caption, tableHead, tableBody); } function sortOpportunitiesBy(key) { const isSameColumn = opportunitiesTableState.opportunitiesTableState.sortKey === key; opportunitiesTableState.opportunitiesTableState.sortKey = key; opportunitiesTableState.opportunitiesTableState.sortDirection = isSameColumn && opportunitiesTableState.opportunitiesTableState.sortDirection === "ascending" ? "descending" : "ascending"; renderOpportunitiesTable(); } return { setOpportunitiesTableState, renderOpportunitiesTable, sortOpportunitiesBy }; } // Source: src/features/opportunities/calendar.js function createOpportunityCalendar({ environment, loadLibrary, getState, getItems, getVersion, formatRevenue, formatDate, getOwnerName, createDetailLink, createCopyButton, createCloseIcon, createStageBadge, createStatusBadge, opportunityUrl, modalId, closeSrPopover, createViewIcon }) { const { document } = environment; let elements = null; let calendar = null; let library = null; let selectedView = "overview"; let anchoredVersion = null; let details = null; let lastEvents = ""; let dayCounts = new Map(); const dayBadges = new Map(); let resizeObserver = null; let layoutFrame = null; let eventLimit = 2; function visibleEventLimit(height, weeks, headerHeight) { return Math.max(0, Math.min(2, Math.floor((height / Math.max(1, weeks) - headerHeight - 26) / 48))); } function scheduleLayout() { for (const [name, icon] of [["today", "today"], ["dayGridMonth", "calendar"], ["dayGridWeek", "week"]]) { const button = elements?.calendarHost.querySelector?.(`.fc-${name}-button`); if (button && !button.querySelector("svg")) button.prepend(createViewIcon(icon)); } const win = environment.window; if (!win?.requestAnimationFrame || layoutFrame !== null) return; layoutFrame = win.requestAnimationFrame(() => { layoutFrame = null; if (!calendar || selectedView !== "calendar" || !elements.calendarHost.clientHeight) return; calendar.updateSize(); const frame = elements.calendarHost.querySelector(".fc-daygrid-day-frame"); const events = frame?.querySelector(".fc-daygrid-day-events"); if (!frame || !events) return; const scroller = frame.closest(".fc-scroller"); const weeks = elements.calendarHost.querySelectorAll(".fc-daygrid-body tr").length; if (!scroller || !weeks) return; elements.calendarHost.style.setProperty("--opportunities-calendar-weeks", String(weeks)); const headerHeight = events.getBoundingClientRect().top - frame.getBoundingClientRect().top; const limit = visibleEventLimit(scroller.clientHeight, weeks, headerHeight); if (limit !== eventLimit) { eventLimit = limit; calendar.setOption("dayMaxEvents", limit); } }); } function dateKey(date) { return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`; } function fiscalWeekLabel(date) { // FullCalendar's week hook supplies a UTC date marker, not a local instant. const start = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate())); start.setUTCDate(start.getUTCDate() - start.getUTCDay()); const end = new Date(start.getTime() + 6 * 86400000); const year = end.getUTCMonth() >= 5 ? end.getUTCFullYear() : end.getUTCFullYear() - 1; const fiscalStart = new Date(Date.UTC(year, 5, 1)); const firstSunday = fiscalStart.getTime() - fiscalStart.getUTCDay() * 86400000; const representative = start < fiscalStart ? fiscalStart : start; const quarter = Math.floor(((representative.getUTCMonth() + 7) % 12) / 3) + 1; const week = Math.round((start.getTime() - firstSunday) / (7 * 86400000)) + 1; return `FY${String(year + 1).slice(-2)}-Q${quarter}-W${week}`; } function updateDayBadge(badge, key) { const count = dayCounts.get(key) || 0; badge.hidden = count === 0; badge.textContent = String(count); badge.title = `${count} oportunidade${count === 1 ? "" : "s"}`; badge.setAttribute("aria-label", badge.title); badge.setAttribute("data-empty", String(count === 0)); } function node(tag, className, text) { const el = document.createElement(tag); el.className = className; if (text !== undefined) el.textContent = text; return el; } function closeDate(value) { if (typeof value !== "string" || !/^\d{4}-\d{2}-\d{2}(?:T|$)/.test(value)) return null; const day = value.slice(0, 10); const date = new Date(`${day}T00:00:00Z`); return Number.isFinite(date.getTime()) && date.toISOString().slice(0, 10) === day ? day : null; } function eventsFor(items) { return items.flatMap((item, index) => { const start = closeDate(item.EffectiveDate); if (!start) return []; return [{ id: String(item.OptyNumber || item.OptyId || index), title: String(item.Name || item.OptyNumber || "-"), start, allDay: true, extendedProps: { item }, classNames: [`opportunities-calendar-stage-${stage(item)}`] }]; }); } function stage(item) { return ["SQL", "PIPELINE", "UPSIDE", "FORECAST", "WON"].includes(item.ForecastGroup_c) ? item.ForecastGroup_c : "OTHER"; } function initialDate(items, now = new Date()) { const today = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`; const dates = items.map(item => closeDate(item.EffectiveDate)).filter(Boolean).sort(); return dates.find(date => date >= today) || dates.at(-1) || today; } function closeDetails(restoreFocus = true) { if (!details) return false; const previous = details; details = null; previous.overlay.remove(); if (restoreFocus) (previous.trigger?.isConnected ? previous.trigger : elements?.tabs.calendar)?.focus(); return true; } function showDialog(heading, content, contentControls, trigger) { closeDetails(false); const host = document.getElementById(modalId); if (!host || host.hidden) return; const overlay = node("section", "opportunities-extension-action-form-modal opportunities-calendar-detail"); overlay.setAttribute("role", "dialog"); overlay.setAttribute("aria-modal", "true"); overlay.setAttribute("aria-labelledby", "opportunities-calendar-detail-title"); const panel = node("div", "opportunities-extension-action-form-dialog"); const header = node("header", "opportunities-extension-account-plans-header"); const title = node("h2", "", heading); title.id = "opportunities-calendar-detail-title"; const dismiss = node("button", "opportunities-extension-icon-button"); dismiss.type = "button"; dismiss.title = "Close"; dismiss.setAttribute("aria-label", "Close opportunity details"); dismiss.append(createCloseIcon()); dismiss.addEventListener("click", () => closeDetails()); header.append(title, dismiss); panel.append(header, content); overlay.append(panel); host.append(overlay); details = { overlay, trigger }; overlay.addEventListener("click", event => { if (event.target === overlay) closeDetails(); }); overlay.addEventListener("keydown", event => { if (event.key === "Escape") { event.preventDefault(); event.stopPropagation(); closeDetails(); } if (event.key === "Tab") { const controls = [dismiss, ...contentControls]; const index = controls.indexOf(document.activeElement); if (index < 0 || (event.shiftKey ? index === 0 : index === controls.length - 1)) { event.preventDefault(); controls[event.shiftKey ? controls.length - 1 : 0].focus(); } } }); dismiss.focus(); } function showDetails(item, trigger) { const content = node("div", "opportunities-calendar-detail-content"); const link = createDetailLink(item.OptyNumber || "-", item.OptyNumber ? opportunityUrl + encodeURIComponent(item.OptyNumber) : ""); const identity = node("div", "opportunities-extension-opty-number-content"); identity.append(link); const copy = item.OptyNumber ? createCopyButton(item.OptyNumber) : null; if (copy) identity.append(copy); const summary = node("dl", "opportunities-extension-action-form-context"); for (const [label, value] of [["Customer", item.CustomerAccount?.PartyUniqueName], ["Owner", getOwnerName(item)], ["Close Date", closeDate(item.EffectiveDate) ? formatDate(item.EffectiveDate) : "N/D"], ["Amount", formatRevenue(item)]]) { summary.append(node("dt", "", label), node("dd", "", String(value || "-"))); } const badges = node("div", "opportunities-calendar-detail-badges"); badges.append(createStatusBadge(item.StatusCode), createStageBadge(item.ForecastGroup_c)); content.append(identity, summary, badges); showDialog(item.Name || item.OptyNumber || "Opportunity", content, item.OptyNumber ? [link, copy] : [], trigger); } function showDayOpportunities({ date, allSegs, jsEvent }) { jsEvent.preventDefault(); const trigger = jsEvent.currentTarget || elements?.tabs.calendar; const content = node("div", "opportunities-calendar-day-list"); const controls = []; const events = new Map(allSegs.map(({ event }) => [event.id, event])); for (const event of events.values()) { const item = event.extendedProps.item; const button = node("button", "opportunities-calendar-day-item"); button.type = "button"; const summary = `${item.OptyNumber || "-"} | ${item.ForecastGroup_c || "N/D"} | ${formatRevenue(item)}`; button.append(node("strong", "", event.title), node("span", "", summary)); button.title = `${event.title}\n${summary}`; button.addEventListener("click", () => showDetails(item, trigger)); controls.push(button); content.append(button); } showDialog(`${formatDate(date.toISOString().slice(0, 10))} | ${events.size} oportunidades`, content, controls, trigger); // A truthy non-string result prevents FullCalendar from opening its measured popover. return true; } function mount(body, tableSurface) { resizeObserver?.disconnect(); if (layoutFrame !== null) environment.window?.cancelAnimationFrame(layoutFrame); layoutFrame = null; eventLimit = 2; calendar?.destroy(); calendar = null; lastEvents = ""; anchoredVersion = null; selectedView = "overview"; dayBadges.clear(); dayCounts.clear(); closeDetails(false); const workspace = node("section", "opportunities-extension-views"); const tablist = node("div", "opportunities-extension-view-tabs"); tablist.setAttribute("role", "tablist"); tablist.setAttribute("aria-label", "Visualiza\u00e7\u00e3o das oportunidades"); const tabs = {}; const surface = node("section", "opportunities-extension-calendar"); const message = node("p", "opportunities-calendar-message"); message.setAttribute("role", "status"); const calendarHost = node("div", "opportunities-calendar-grid"); const undated = node("details", "opportunities-calendar-undated"); surface.append(message, calendarHost, undated); for (const [key, label, panel] of [["overview", "Vis\u00e3o geral", tableSurface], ["calendar", "Calend\u00e1rio", surface]]) { const tab = node("button", ""); tab.type = "button"; tab.id = `opportunities-view-${key}-tab`; tab.append(createViewIcon(key), node("span", "", label)); panel.id = `opportunities-view-${key}`; panel.setAttribute("role", "tabpanel"); panel.setAttribute("aria-labelledby", tab.id); tab.setAttribute("role", "tab"); tab.setAttribute("aria-controls", panel.id); tab.addEventListener("click", () => selectView(key)); tab.addEventListener("keydown", event => { if (!["ArrowLeft", "ArrowRight", "Home", "End"].includes(event.key)) return; event.preventDefault(); const next = event.key === "Home" ? "overview" : event.key === "End" ? "calendar" : key === "overview" ? "calendar" : "overview"; selectView(next); tabs[next].focus(); }); tabs[key] = tab; tablist.append(tab); } workspace.append(tablist, tableSurface, surface); body.append(workspace); elements = { workspace, tableSurface, surface, message, calendarHost, undated, tabs }; if (environment.window?.ResizeObserver) { resizeObserver = new environment.window.ResizeObserver(scheduleLayout); resizeObserver.observe(calendarHost); } selectView("overview"); } function selectView(view) { if (!elements || !["overview", "calendar"].includes(view)) return; closeDetails(false); closeSrPopover(); selectedView = view; elements.tableSurface.hidden = view !== "overview"; elements.surface.hidden = view !== "calendar"; for (const [key, tab] of Object.entries(elements.tabs)) { tab.setAttribute("aria-selected", String(view === key)); tab.tabIndex = view === key ? 0 : -1; } render(getItems(), getState()); if (view === "calendar") calendar?.updateSize(); scheduleLayout(); } function render(items, state) { if (!elements || selectedView !== "calendar") return; const { surface, message, calendarHost, undated } = elements; surface.setAttribute("aria-busy", String(state.status === "loading")); if (["loading", "error", "empty", "idle"].includes(state.status)) { message.textContent = state.message || (state.status === "loading" ? "Loading opportunities..." : "No opportunities found."); message.hidden = false; calendarHost.hidden = true; undated.hidden = true; closeDetails(false); return; } message.textContent = items.length ? "" : "No opportunities found."; message.hidden = !!items.length; calendarHost.hidden = false; const events = eventsFor(items); dayCounts = new Map(); for (const event of events) dayCounts.set(event.start, (dayCounts.get(event.start) || 0) + 1); for (const { badge, key } of dayBadges.values()) updateDayBadge(badge, key); const anchor = initialDate(state.items); if (!calendar) { try { library ||= loadLibrary(); calendar = new library.Calendar(calendarHost, { initialView: "dayGridMonth", initialDate: anchor, height: "100%", expandRows: true, fixedWeekCount: false, datesSet: scheduleLayout, locale: "pt-br", firstDay: 0, editable: false, selectable: false, eventInteractive: true, headerToolbar: { left: "prev,next today", center: "title", right: "dayGridMonth,dayGridWeek" }, buttonText: { today: "Hoje", month: "M\u00eas", week: "Semana" }, buttonHints: { prev: "Anterior", next: "Pr\u00f3ximo", today: "Hoje", dayGridMonth: "M\u00eas", dayGridWeek: "Semana" }, dayMaxEvents: 2, moreLinkText: count => `... +${count}`, displayEventTime: false, moreLinkHint: count => `Ver mais ${count} oportunidades`, moreLinkClick: showDayOpportunities, weekNumbers: true, weekNumberContent: ({ date }) => ({ domNodes: [node("span", "opportunities-calendar-fiscal-week", fiscalWeekLabel(date))] }), dayCellDidMount: ({ el, date }) => { const badge = node("span", "opportunities-calendar-day-count"); const key = dateKey(date); updateDayBadge(badge, key); el.querySelector(".fc-daygrid-day-frame").append(badge); dayBadges.set(el, { badge, key }); }, dayCellWillUnmount: ({ el }) => { dayBadges.get(el)?.badge.remove(); dayBadges.delete(el); }, eventOrder: "title", eventDisplay: "block", events, eventContent: ({ event }) => { const item = event.extendedProps.item; const card = node("div", "opportunities-calendar-event-content"); const summary = node("span", "", `${item.OptyNumber || "-"} | ${item.ForecastGroup_c || "N/D"} | ${formatRevenue(item)}`); summary.title = summary.textContent; card.append(node("strong", "", event.title), summary); return { domNodes: [card] }; }, eventDidMount: ({ el, event }) => { el.title = `${event.title}\n${event.extendedProps.item.CustomerAccount?.PartyUniqueName || ""}\n${formatDate(event.startStr)} | ${event.extendedProps.item.ForecastGroup_c || "-"}`; el.setAttribute("role", "button"); el.setAttribute("aria-label", el.title); }, eventClick: ({ event, el, jsEvent }) => { jsEvent.preventDefault(); showDetails(event.extendedProps.item, el); } }); calendar.render(); lastEvents = JSON.stringify(events); anchoredVersion = getVersion(); } catch { calendar?.destroy(); calendar = null; message.textContent = "Unable to display the calendar. Use the overview or try again."; message.hidden = false; calendarHost.hidden = true; undated.hidden = true; return; } } const signature = JSON.stringify(events); calendar.batchRendering(() => { if (anchoredVersion !== getVersion()) { calendar.gotoDate(anchor); anchoredVersion = getVersion(); closeDetails(false); } if (signature !== lastEvents) { calendar.removeAllEvents(); calendar.addEventSource(events); lastEvents = signature; closeDetails(false); } }); const missing = items.filter(item => !closeDate(item.EffectiveDate)); undated.hidden = !missing.length; undated.textContent = ""; if (missing.length) { undated.append(node("summary", "", `Sem Close Date v\u00e1lido (${missing.length})`)); for (const item of missing) { const button = node("button", "", `${item.OptyNumber || "-"} | ${item.Name || "-"}`); button.type = "button"; button.addEventListener("click", () => showDetails(item, button)); undated.append(button); } } scheduleLayout(); } return { mount, render, selectView, closeDetails, closeDate, eventsFor, initialDate, fiscalWeekLabel, visibleEventLimit }; } // Source: src/features/opportunities/dashboard.js function createOpportunitiesDashboard({ STAGE_DASHBOARD_ID, STAGE_OPTIONS, getNestedValue, opportunitiesDashboardState, environment }) { const { document, requestAnimationFrame, performance } = environment; function renderStageDashboard(items, totalCount = items.length) { const dashboard = document.getElementById(STAGE_DASHBOARD_ID); if (!dashboard) { return; } dashboard.textContent = ""; const totalCard = document.createElement("section"); const totalLabel = document.createElement("span"); const totalValue = document.createElement("strong"); totalCard.className = "opportunities-extension-stage-dashboard-card opportunities-extension-total-dashboard-card"; totalCard.setAttribute("data-stage", "TOTAL"); totalLabel.textContent = "Total Opportunities"; totalValue.className = "opportunities-extension-stage-dashboard-value"; totalValue.setAttribute("aria-label", `${items.length} filtered opportunities out of ${totalCount} total`); animateStageDashboardValue( totalValue, "TOTAL", items.length, (amount) => `${Math.round(amount).toLocaleString()}/${totalCount.toLocaleString()}` ); totalCard.append(totalLabel, totalValue); dashboard.append(totalCard); STAGE_OPTIONS.forEach((stage) => { const stageItems = items.filter((item) => item.ForecastGroup_c === stage); const amount = stageItems.reduce((total, item) => { const value = Number(getNestedValue(item, ["PrimaryRevenue", "RevnAmount"])); return Number.isFinite(value) ? total + value : total; }, 0); const card = document.createElement("section"); const label = document.createElement("span"); const value = document.createElement("strong"); card.className = "opportunities-extension-stage-dashboard-card"; card.setAttribute("data-stage", stage); label.textContent = stage; value.className = "opportunities-extension-stage-dashboard-value"; value.setAttribute("aria-label", `${stage} total amount`); animateStageDashboardValue( value, stage, amount, createStageAmountFormatter(stageItems) ); card.append(label, value); dashboard.append(card); }); } function createStageAmountFormatter(items) { const currencies = new Set(items .map((item) => getNestedValue(item, ["PrimaryRevenue", "RevnAmountCurcyCode"])) .filter(Boolean)); if (currencies.size === 1) { const [currency] = currencies; try { const formatter = new Intl.NumberFormat(undefined, { style: "currency", currency, maximumFractionDigits: 0 }); return (amount) => formatter.format(amount); } catch (error) { // Fall through to a neutral numeric total when the currency code is invalid. } } return (amount) => amount.toLocaleString(undefined, { maximumFractionDigits: 0 }); } function animateStageDashboardValue(element, stage, target, format) { const current = opportunitiesDashboardState.stageDashboardAmounts.get(stage) || 0; opportunitiesDashboardState.stageDashboardAmounts.set(stage, target); if (current === target) { element.textContent = format(target); return; } const startTime = performance.now(); const duration = 480; function tick(now) { const progress = Math.min((now - startTime) / duration, 1); const easedProgress = 1 - Math.pow(1 - progress, 3); const value = current + ((target - current) * easedProgress); element.textContent = format(value); if (progress < 1) { requestAnimationFrame(tick); } else { element.textContent = format(target); } } requestAnimationFrame(tick); } return { renderStageDashboard, createStageAmountFormatter, animateStageDashboardValue }; } // Source: src/features/opportunities/filters.js function createOpportunitiesFilters({ CUSTOMER_FILTER_BUTTON_ID, CUSTOMER_FILTER_LIST_ID, CUSTOMER_FILTER_PANEL_ID, CUSTOMER_FILTER_SEARCH_ID, CUSTOMER_FILTER_SELECT_ALL_ID, OPPORTUNITIES_COLUMNS, OWNER_FILTER_BUTTON_ID, OWNER_FILTER_LIST_ID, OWNER_FILTER_PANEL_ID, OWNER_FILTER_SEARCH_ID, OWNER_FILTER_SELECT_ALL_ID, STAGE_FILTERS_ID, STAGE_OPTIONS, STATUS_FILTERS_ID, STATUS_OPTIONS, getCustomerFilterSummary, getCustomerKey, getCustomerOptions, getOwnerKey, getOwnerOptions, opportunitiesFilterState, opportunitiesTableState, renderOpportunitiesTable, saveFilterPreferences, stringifySearchValue, matchesActivities, environment }) { const { document } = environment; function resetCustomerAndOwnerFiltersForPeriod() { opportunitiesFilterState.selectedCustomerKeys = new Set(); opportunitiesFilterState.selectedOwnerKeys = new Set(); opportunitiesFilterState.customerSearch = ""; opportunitiesFilterState.ownerSearch = ""; opportunitiesFilterState.hasSavedCustomerFilter = false; opportunitiesFilterState.hasSavedOwnerFilter = false; opportunitiesFilterState.customerFilterInitialized = false; opportunitiesFilterState.ownerFilterInitialized = false; } function renderStageFilterButtons() { const controls = document.getElementById(STAGE_FILTERS_ID); if (!controls) { return; } controls.textContent = ""; STAGE_OPTIONS.forEach((stage) => { const button = document.createElement("button"); const isPressed = opportunitiesFilterState.selectedStages.has(stage); button.type = "button"; button.className = "opportunities-extension-stage-filter-button"; button.setAttribute("data-stage", stage); button.setAttribute("aria-pressed", isPressed ? "true" : "false"); button.textContent = stage; button.addEventListener("click", () => toggleStageFilter(stage)); controls.append(button); }); } function toggleStageFilter(stage) { if (opportunitiesFilterState.selectedStages.has(stage)) { opportunitiesFilterState.selectedStages.delete(stage); } else { opportunitiesFilterState.selectedStages.add(stage); } saveFilterPreferences(); renderStageFilterButtons(); renderOpportunitiesTable(); } function renderStatusFilterButtons() { const controls = document.getElementById(STATUS_FILTERS_ID); if (!controls) { return; } controls.textContent = ""; STATUS_OPTIONS.forEach((status) => { const button = document.createElement("button"); const isPressed = opportunitiesFilterState.selectedStatuses.has(status); button.type = "button"; button.className = "opportunities-extension-status-filter-button"; button.setAttribute("data-status", status); button.setAttribute("aria-pressed", isPressed ? "true" : "false"); button.textContent = status; button.addEventListener("click", () => toggleStatusFilter(status)); controls.append(button); }); } function toggleStatusFilter(status) { if (opportunitiesFilterState.selectedStatuses.has(status)) { opportunitiesFilterState.selectedStatuses.delete(status); } else { opportunitiesFilterState.selectedStatuses.add(status); } saveFilterPreferences(); renderStatusFilterButtons(); renderOpportunitiesTable(); } function toggleCustomerFilterPanel() { const panel = document.getElementById(CUSTOMER_FILTER_PANEL_ID); const button = document.getElementById(CUSTOMER_FILTER_BUTTON_ID); if (!panel || !button || button.disabled) { return; } panel.hidden = !panel.hidden; button.setAttribute("aria-expanded", panel.hidden ? "false" : "true"); if (!panel.hidden) { const searchInput = document.getElementById(CUSTOMER_FILTER_SEARCH_ID); searchInput.focus(); } } function closeCustomerFilterPanel(restoreFocus = true) { const panel = document.getElementById(CUSTOMER_FILTER_PANEL_ID); const button = document.getElementById(CUSTOMER_FILTER_BUTTON_ID); if (!panel || panel.hidden) { return false; } panel.hidden = true; button.setAttribute("aria-expanded", "false"); if (restoreFocus) { button.focus(); } return true; } function toggleOwnerFilterPanel() { const panel = document.getElementById(OWNER_FILTER_PANEL_ID); const button = document.getElementById(OWNER_FILTER_BUTTON_ID); if (!panel || !button || button.disabled) { return; } panel.hidden = !panel.hidden; button.setAttribute("aria-expanded", panel.hidden ? "false" : "true"); if (!panel.hidden) { const searchInput = document.getElementById(OWNER_FILTER_SEARCH_ID); searchInput.focus(); } } function closeOwnerFilterPanel(restoreFocus = true) { const panel = document.getElementById(OWNER_FILTER_PANEL_ID); const button = document.getElementById(OWNER_FILTER_BUTTON_ID); if (!panel || panel.hidden) { return false; } panel.hidden = true; button.setAttribute("aria-expanded", "false"); if (restoreFocus) { button.focus(); } return true; } function resetCustomerFilter(items) { const customerOptions = getCustomerOptions(items); if (!opportunitiesFilterState.customerFilterInitialized) { opportunitiesFilterState.selectedCustomerKeys = new Set(customerOptions.map((customer) => customer.key)); opportunitiesFilterState.customerFilterInitialized = true; } } function resetOwnerFilter(items) { const ownerOptions = getOwnerOptions(items); if (!opportunitiesFilterState.ownerFilterInitialized) { opportunitiesFilterState.selectedOwnerKeys = new Set(ownerOptions.map((owner) => owner.key)); opportunitiesFilterState.ownerFilterInitialized = true; } } function renderCustomerFilter() { const trigger = document.getElementById(CUSTOMER_FILTER_BUTTON_ID); const searchInput = document.getElementById(CUSTOMER_FILTER_SEARCH_ID); const selectAllCheckbox = document.getElementById(CUSTOMER_FILTER_SELECT_ALL_ID); const customerOptions = getCustomerOptions(); if (!trigger || !searchInput || !selectAllCheckbox) { return; } const selectedCount = customerOptions.filter((customer) => opportunitiesFilterState.selectedCustomerKeys.has(customer.key)).length; trigger.disabled = customerOptions.length === 0; trigger.textContent = getCustomerFilterSummary(customerOptions.length, selectedCount); searchInput.value = opportunitiesFilterState.customerSearch; selectAllCheckbox.disabled = customerOptions.length === 0; selectAllCheckbox.checked = customerOptions.length > 0 && selectedCount === customerOptions.length; selectAllCheckbox.indeterminate = selectedCount > 0 && selectedCount < customerOptions.length; renderCustomerFilterList(); } function renderCustomerFilterList() { const list = document.getElementById(CUSTOMER_FILTER_LIST_ID); if (!list) { return; } const normalizedSearch = opportunitiesFilterState.customerSearch.trim().toLocaleLowerCase(); const customerOptions = getCustomerOptions().filter((customer) => { return customer.label.toLocaleLowerCase().includes(normalizedSearch); }); list.textContent = ""; if (customerOptions.length === 0) { const empty = document.createElement("p"); empty.className = "opportunities-extension-customer-filter-empty"; empty.textContent = opportunitiesFilterState.customerSearch ? "No matching customers." : "No customers available."; list.append(empty); return; } customerOptions.forEach((customer) => { const option = document.createElement("label"); option.className = "opportunities-extension-customer-filter-option"; const checkbox = document.createElement("input"); checkbox.type = "checkbox"; checkbox.checked = opportunitiesFilterState.selectedCustomerKeys.has(customer.key); checkbox.addEventListener("change", () => { if (checkbox.checked) { opportunitiesFilterState.selectedCustomerKeys.add(customer.key); } else { opportunitiesFilterState.selectedCustomerKeys.delete(customer.key); } opportunitiesFilterState.hasSavedCustomerFilter = true; saveFilterPreferences(); renderCustomerFilter(); renderOpportunitiesTable(); }); const label = document.createElement("span"); label.textContent = customer.label; option.append(checkbox, label); list.append(option); }); } function renderOwnerFilter() { const trigger = document.getElementById(OWNER_FILTER_BUTTON_ID); const searchInput = document.getElementById(OWNER_FILTER_SEARCH_ID); const selectAllCheckbox = document.getElementById(OWNER_FILTER_SELECT_ALL_ID); const ownerOptions = getOwnerOptions(); if (!trigger || !searchInput || !selectAllCheckbox) { return; } const selectedCount = ownerOptions.filter((owner) => opportunitiesFilterState.selectedOwnerKeys.has(owner.key)).length; trigger.disabled = ownerOptions.length === 0; trigger.textContent = getCustomerFilterSummary(ownerOptions.length, selectedCount) .replace("customer", "owner"); searchInput.value = opportunitiesFilterState.ownerSearch; selectAllCheckbox.disabled = ownerOptions.length === 0; selectAllCheckbox.checked = ownerOptions.length > 0 && selectedCount === ownerOptions.length; selectAllCheckbox.indeterminate = selectedCount > 0 && selectedCount < ownerOptions.length; renderOwnerFilterList(); } function renderOwnerFilterList() { const list = document.getElementById(OWNER_FILTER_LIST_ID); if (!list) { return; } const normalizedSearch = opportunitiesFilterState.ownerSearch.trim().toLocaleLowerCase(); const ownerOptions = getOwnerOptions().filter((owner) => { return owner.label.toLocaleLowerCase().includes(normalizedSearch); }); list.textContent = ""; if (ownerOptions.length === 0) { const empty = document.createElement("p"); empty.className = "opportunities-extension-customer-filter-empty"; empty.textContent = opportunitiesFilterState.ownerSearch ? "No matching owners." : "No owners available."; list.append(empty); return; } ownerOptions.forEach((owner) => { const option = document.createElement("label"); option.className = "opportunities-extension-customer-filter-option"; const checkbox = document.createElement("input"); checkbox.type = "checkbox"; checkbox.checked = opportunitiesFilterState.selectedOwnerKeys.has(owner.key); checkbox.addEventListener("change", () => { if (checkbox.checked) { opportunitiesFilterState.selectedOwnerKeys.add(owner.key); } else { opportunitiesFilterState.selectedOwnerKeys.delete(owner.key); } opportunitiesFilterState.hasSavedOwnerFilter = true; saveFilterPreferences(); renderOwnerFilter(); renderOpportunitiesTable(); }); const label = document.createElement("span"); label.textContent = owner.label; option.append(checkbox, label); list.append(option); }); } function getSortedOpportunities() { const normalizedSearch = opportunitiesFilterState.tableSearch.trim().toLocaleLowerCase(); const allStages = STAGE_OPTIONS.every(stage => opportunitiesFilterState.selectedStages.has(stage)); const allCustomers = getCustomerOptions().every(option => opportunitiesFilterState.selectedCustomerKeys.has(option.key)); const allOwners = getOwnerOptions().every(option => opportunitiesFilterState.selectedOwnerKeys.has(option.key)); const items = opportunitiesTableState.opportunitiesTableState.items.filter((item) => { return (allStages || opportunitiesFilterState.selectedStages.has(item.ForecastGroup_c)) && matchesSelectedStatus(item.StatusCode) && (allCustomers || opportunitiesFilterState.selectedCustomerKeys.has(getCustomerKey(item))) && (allOwners || opportunitiesFilterState.selectedOwnerKeys.has(getOwnerKey(item))) && matchesTableSearch(item, normalizedSearch) && matchesActivities(item); }); const column = OPPORTUNITIES_COLUMNS.find((candidate) => candidate.key === opportunitiesTableState.opportunitiesTableState.sortKey); if (!column) { return items; } const direction = opportunitiesTableState.opportunitiesTableState.sortDirection === "ascending" ? 1 : -1; return items.sort((first, second) => { const firstValue = column.value(first); const secondValue = column.value(second); const firstIsEmpty = firstValue === null || firstValue === undefined || firstValue === ""; const secondIsEmpty = secondValue === null || secondValue === undefined || secondValue === ""; if (firstIsEmpty || secondIsEmpty) { if (firstIsEmpty && secondIsEmpty) { return 0; } return firstIsEmpty ? 1 : -1; } const firstNumber = Number(firstValue); const secondNumber = Number(secondValue); if (Number.isFinite(firstNumber) && Number.isFinite(secondNumber)) { return (firstNumber - secondNumber) * direction; } return String(firstValue).localeCompare(String(secondValue), undefined, { numeric: true, sensitivity: "base" }) * direction; }); } function matchesTableSearch(item, normalizedSearch) { if (!normalizedSearch) { return true; } return OPPORTUNITIES_COLUMNS.some((column) => { const rawValue = stringifySearchValue(column.value(item)); const displayValue = stringifySearchValue(column.display(item)); return `${rawValue} ${displayValue}`.toLocaleLowerCase().includes(normalizedSearch); }); } function matchesSelectedStatus(statusCode) { if (STATUS_OPTIONS.every(status => opportunitiesFilterState.selectedStatuses.has(status))) return true; if (statusCode === "WON_PENDING") { return opportunitiesFilterState.selectedStatuses.has("WON"); } return opportunitiesFilterState.selectedStatuses.has(statusCode); } return { resetCustomerAndOwnerFiltersForPeriod, renderStageFilterButtons, toggleStageFilter, renderStatusFilterButtons, toggleStatusFilter, toggleCustomerFilterPanel, closeCustomerFilterPanel, toggleOwnerFilterPanel, closeOwnerFilterPanel, resetCustomerFilter, resetOwnerFilter, renderCustomerFilter, renderCustomerFilterList, renderOwnerFilter, renderOwnerFilterList, getSortedOpportunities, matchesTableSearch, matchesSelectedStatus }; } // Source: src/features/opportunities/formatters.js function createOpportunitiesFormatters({ opportunitiesTableState }) { function getCustomerOptions(items) { const optionsByKey = new Map(); (items || opportunitiesTableState.opportunitiesTableState.items).forEach((item) => { const key = getCustomerKey(item); const label = getNestedValue(item, ["CustomerAccount", "PartyUniqueName"]); if (key && label && !optionsByKey.has(key)) { optionsByKey.set(key, { key, label: String(label) }); } }); return Array.from(optionsByKey.values()).sort((first, second) => { return first.label.localeCompare(second.label, undefined, { sensitivity: "base" }); }); } function getCustomerKey(item) { const partyId = getNestedValue(item, ["CustomerAccount", "PartyId"]); const partyName = getNestedValue(item, ["CustomerAccount", "PartyUniqueName"]); if (partyId !== null && partyId !== undefined && partyId !== "") { return `party:${partyId}`; } return partyName ? `name:${partyName}` : ""; } function getOwnerName(item) { const owner = item.Owner; if (typeof owner === "string" || typeof owner === "number") { return owner; } if (owner && typeof owner === "object") { const firstName = owner.PersonFirstName; const lastName = owner.PersonLastName; if (firstName || lastName) { return [firstName, lastName].filter(Boolean).join(" "); } return owner.PartyName || owner.PartyUniqueName || owner.Name || owner.DisplayName || owner.ResourceName || owner.OwnerName || ""; } return ""; } function getCustomerFilterSummary(total, selected) { if (total === 0) { return "No customers"; } if (selected === total) { return "All customers"; } if (selected === 0) { return "No customers"; } return `${selected} customer${selected === 1 ? "" : "s"}`; } function getOwnerOptions(items) { const optionsByKey = new Map(); (items || opportunitiesTableState.opportunitiesTableState.items).forEach((item) => { const key = getOwnerKey(item); const label = String(getOwnerName(item)).trim() || "-"; if (key && label && !optionsByKey.has(key)) { optionsByKey.set(key, { key, label: String(label) }); } }); return Array.from(optionsByKey.values()).sort((first, second) => { return first.label.localeCompare(second.label, undefined, { sensitivity: "base" }); }); } function getOwnerKey(item) { const owner = item.Owner; const partyId = owner && typeof owner === "object" ? owner.PartyId : ""; const name = String(getOwnerName(item)).trim(); if (!name) return "owner:unassigned"; if (partyId !== null && partyId !== undefined && partyId !== "") { return `party:${partyId}`; } return name ? `name:${name}` : ""; } function stringifySearchValue(value) { if (value === null || value === undefined) { return ""; } if (typeof value === "object") { return JSON.stringify(value); } return String(value); } function displayOpportunityValue(column, item) { const value = column.display(item); return value === null || value === undefined || value === "" ? "-" : String(value); } function getNestedValue(value, path) { return path.reduce((result, key) => result && result[key], value); } function formatWinProbability(item) { const value = getNestedValue(item, ["PrimaryRevenue", "WinProb"]); const number = Number(value); return Number.isFinite(number) ? `${number}%` : value; } function formatRevenue(item) { const amount = getNestedValue(item, ["PrimaryRevenue", "RevnAmount"]); const currency = getNestedValue(item, ["PrimaryRevenue", "RevnAmountCurcyCode"]); const number = Number(amount); if (!Number.isFinite(number)) { return amount; } if (!currency) { return number.toLocaleString(); } try { return new Intl.NumberFormat(undefined, { style: "currency", currency }).format(number); } catch (error) { return `${currency} ${number.toLocaleString()}`; } } function formatOracleResponseDate(value, includeTime) { if (typeof value !== "string") { return value; } const match = value.match(/^(\d{4})-(\d{2})-(\d{2})(?:T(\d{2}):(\d{2}))?/); if (!match) { return value; } const date = `${match[3]}/${match[2]}/${match[1]}`; return includeTime && match[4] ? `${date} ${match[4]}:${match[5]}` : date; } return { getCustomerOptions, getCustomerKey, getOwnerName, getCustomerFilterSummary, getOwnerOptions, getOwnerKey, stringifySearchValue, displayOpportunityValue, getNestedValue, formatWinProbability, formatRevenue, formatOracleResponseDate }; } // Source: src/features/opportunities/links.js function createOpportunitiesLinks({ MODAL_ID, environment }) { const { document, navigator, window } = environment; function createDetailLink(label, href) { if (label === null || label === undefined || label === "") { const placeholder = document.createElement("span"); placeholder.textContent = "-"; return placeholder; } if (!href) { const text = document.createElement("span"); text.textContent = String(label); return text; } const link = document.createElement("a"); link.href = href; link.target = "_blank"; link.rel = "noopener noreferrer"; link.textContent = String(label); return link; } function createCopyOpportunityButton(optyNumber, label = "opportunity number", confirmationLabel = "Opty Number") { const button = document.createElement("button"); const icon = document.createElementNS("http://www.w3.org/2000/svg", "svg"); const path = document.createElementNS("http://www.w3.org/2000/svg", "path"); button.type = "button"; button.className = "opportunities-extension-copy-button"; button.setAttribute("aria-label", `Copy ${label} ${optyNumber}`); button.title = `Copy ${label}`; button.addEventListener("click", (event) => { event.preventDefault(); event.stopPropagation(); copyOpportunityNumber(String(optyNumber), label, confirmationLabel); }); icon.setAttribute("viewBox", "0 0 24 24"); icon.setAttribute("aria-hidden", "true"); path.setAttribute("d", "M8 8V5.5A2.5 2.5 0 0 1 10.5 3h8A2.5 2.5 0 0 1 21 5.5v8a2.5 2.5 0 0 1-2.5 2.5H16v2.5a2.5 2.5 0 0 1-2.5 2.5h-8A2.5 2.5 0 0 1 3 18.5v-8A2.5 2.5 0 0 1 5.5 8H8zm2 0h3.5A2.5 2.5 0 0 1 16 10.5v3.5h2.5a.5.5 0 0 0 .5-.5v-8a.5.5 0 0 0-.5-.5h-8a.5.5 0 0 0-.5.5V8zm3.5 2h-8a.5.5 0 0 0-.5.5v8a.5.5 0 0 0 .5.5h8a.5.5 0 0 0 .5-.5v-8a.5.5 0 0 0-.5-.5z"); icon.append(path); button.append(icon); return button; } async function copyOpportunityNumber(optyNumber, label = "opportunity number", confirmationLabel = "Opty Number") { try { if (navigator.clipboard && navigator.clipboard.writeText) { await navigator.clipboard.writeText(optyNumber); } else { const input = document.createElement("textarea"); input.value = optyNumber; input.setAttribute("readonly", "true"); input.style.position = "fixed"; input.style.opacity = "0"; document.body.append(input); input.select(); document.execCommand("copy"); input.remove(); } showCopyConfirmation(`${confirmationLabel} ${optyNumber} copied.`); } catch (error) { showCopyConfirmation(`Unable to copy ${label}.`, true); } } function showCopyConfirmation(message, isError = false) { const modal = document.getElementById(MODAL_ID); if (!modal) { return; } let toast = modal.querySelector(".opportunities-extension-copy-toast"); if (!toast) { toast = document.createElement("div"); toast.className = "opportunities-extension-copy-toast"; toast.setAttribute("role", "status"); modal.append(toast); } toast.classList.toggle("opportunities-extension-copy-toast-error", isError); toast.textContent = message; toast.hidden = false; window.clearTimeout(toast.copyTimeout); toast.copyTimeout = window.setTimeout(() => { toast.hidden = true; }, 2400); } function createStageBadge(stage) { const badge = document.createElement("span"); const normalizedStage = typeof stage === "string" ? stage.toUpperCase() : ""; badge.className = "opportunities-extension-stage-badge"; badge.setAttribute("data-stage", normalizedStage); badge.textContent = normalizedStage || "-"; return badge; } function createStatusBadge(status) { const badge = document.createElement("span"); const normalizedStatus = typeof status === "string" ? status.toUpperCase() : ""; badge.className = "opportunities-extension-status-badge"; badge.setAttribute("data-status", normalizedStatus); badge.textContent = normalizedStatus || "-"; return badge; } return { createDetailLink, createCopyOpportunityButton, copyOpportunityNumber, showCopyConfirmation, createStageBadge, createStatusBadge }; } // Source: src/features/preferences/storage.js function createPreferencesStorage({ DEFAULT_OPPORTUNITY_TYPE_VALUES, DEFAULT_OPPTY_PRODUCTS_CACHE_HOURS, FILTER_PREFERENCES_STORAGE_KEY, MODAL_ID, OPPORTUNITY_TYPE_OPTIONS, OPPORTUNITY_TYPE_PREFERENCES_STORAGE_KEY, OPPTY_PRODUCTS_CACHE_OPTIONS, PERIOD_OPTIONS, STAGE_OPTIONS, STATUS_OPTIONS, opportunitiesFilterState, environment }) { const { document } = environment; const getLocalStorage = () => environment.localStorage; function restoreFilterPreferences(select) { try { const rawPreferences = getLocalStorage().getItem(FILTER_PREFERENCES_STORAGE_KEY); if (!rawPreferences) { return; } const preferences = JSON.parse(rawPreferences); const savedStages = Array.isArray(preferences.stages) ? preferences.stages.filter((stage) => STAGE_OPTIONS.includes(stage)) : null; const savedStatuses = Array.isArray(preferences.statuses) ? preferences.statuses.filter((status) => STATUS_OPTIONS.includes(status)) : null; if (savedStages) { opportunitiesFilterState.selectedStages = new Set(savedStages); } if (savedStatuses) { opportunitiesFilterState.selectedStatuses = new Set(savedStatuses); } if (Array.isArray(preferences.customerKeys)) { opportunitiesFilterState.selectedCustomerKeys = new Set(preferences.customerKeys.map(String)); opportunitiesFilterState.hasSavedCustomerFilter = true; opportunitiesFilterState.customerFilterInitialized = true; } if (Array.isArray(preferences.ownerKeys)) { opportunitiesFilterState.selectedOwnerKeys = new Set(preferences.ownerKeys.map(String)); opportunitiesFilterState.hasSavedOwnerFilter = true; opportunitiesFilterState.ownerFilterInitialized = true; } opportunitiesFilterState.customerSearch = typeof preferences.customerSearch === "string" ? preferences.customerSearch : ""; opportunitiesFilterState.ownerSearch = typeof preferences.ownerSearch === "string" ? preferences.ownerSearch : ""; opportunitiesFilterState.tableSearch = typeof preferences.tableSearch === "string" ? preferences.tableSearch : ""; if (PERIOD_OPTIONS.includes(preferences.period)) { select.value = preferences.period; } } catch (error) { // Ignore malformed or unavailable browser storage and use default filters. } } function saveFilterPreferences(period) { try { const currentPeriod = period || document.querySelector(`#${MODAL_ID} select`)?.value || PERIOD_OPTIONS[0]; getLocalStorage().setItem(FILTER_PREFERENCES_STORAGE_KEY, JSON.stringify({ period: currentPeriod, stages: Array.from(opportunitiesFilterState.selectedStages), statuses: Array.from(opportunitiesFilterState.selectedStatuses), customerKeys: opportunitiesFilterState.hasSavedCustomerFilter ? Array.from(opportunitiesFilterState.selectedCustomerKeys) : null, ownerKeys: opportunitiesFilterState.hasSavedOwnerFilter ? Array.from(opportunitiesFilterState.selectedOwnerKeys) : null, customerSearch: opportunitiesFilterState.customerSearch, ownerSearch: opportunitiesFilterState.ownerSearch, tableSearch: opportunitiesFilterState.tableSearch })); } catch (error) { // Ignore unavailable browser storage; filters still work for the current modal. } } function getSavedOpportunityTypeValues() { try { const rawPreferences = getLocalStorage().getItem(OPPORTUNITY_TYPE_PREFERENCES_STORAGE_KEY); if (!rawPreferences) { return DEFAULT_OPPORTUNITY_TYPE_VALUES; } const preferences = JSON.parse(rawPreferences); const values = Array.isArray(preferences.opportunityTypeValues) ? preferences.opportunityTypeValues : null; if (!values) { return DEFAULT_OPPORTUNITY_TYPE_VALUES; } const allowedValues = new Set(OPPORTUNITY_TYPE_OPTIONS.map((option) => option.value)); return values.filter((value) => allowedValues.has(value)); } catch (error) { return DEFAULT_OPPORTUNITY_TYPE_VALUES; } } function getSavedOpptyProductsCacheHours() { try { const rawPreferences = getLocalStorage().getItem(OPPORTUNITY_TYPE_PREFERENCES_STORAGE_KEY); if (!rawPreferences) { return DEFAULT_OPPTY_PRODUCTS_CACHE_HOURS; } const preferences = JSON.parse(rawPreferences); const cacheHours = Number(preferences.opptyProductsCacheHours); return OPPTY_PRODUCTS_CACHE_OPTIONS.some((option) => option.value === cacheHours) ? cacheHours : DEFAULT_OPPTY_PRODUCTS_CACHE_HOURS; } catch (error) { return DEFAULT_OPPTY_PRODUCTS_CACHE_HOURS; } } return { restoreFilterPreferences, saveFilterPreferences, getSavedOpportunityTypeValues, getSavedOpptyProductsCacheHours }; } // Source: src/features/preferences/view.js function createPreferencesView({ DEFAULT_OPPTY_PRODUCTS_CACHE_HOURS, MODAL_ID, OPPORTUNITY_TYPE_OPTIONS, OPPORTUNITY_TYPE_PREFERENCES_STORAGE_KEY, OPPTY_PRODUCTS_CACHE_OPTIONS, PERIOD_OPTIONS, PREFERENCES_MODAL_ID, createCloseIcon, getSavedOpportunityTypeValues, getSavedOpptyProductsCacheHours, preferencesState, requestOpportunitiesForPeriod, environment }) { const { document } = environment; const getLocalStorage = () => environment.localStorage; function openPreferencesModal() { const overlay = document.getElementById(MODAL_ID); if (!overlay) { return; } const existingModal = document.getElementById(PREFERENCES_MODAL_ID); if (existingModal) { const savedValues = new Set(getSavedOpportunityTypeValues()); existingModal.querySelectorAll("input[name='opportunityTypeView']").forEach((checkbox) => { checkbox.checked = savedValues.has(checkbox.value); }); const cacheSelect = existingModal.querySelector("select[name='opptyProductsCacheHours']"); if (cacheSelect) { cacheSelect.value = String(getSavedOpptyProductsCacheHours()); } existingModal.hidden = false; existingModal.querySelector("input")?.focus(); return; } const preferencesModal = document.createElement("section"); preferencesModal.id = PREFERENCES_MODAL_ID; preferencesModal.className = "opportunities-extension-preferences-modal"; preferencesModal.setAttribute("role", "dialog"); preferencesModal.setAttribute("aria-modal", "true"); preferencesModal.setAttribute("aria-labelledby", "opportunities-extension-preferences-title"); const dialog = document.createElement("div"); dialog.className = "opportunities-extension-preferences-dialog"; const header = document.createElement("header"); header.className = "opportunities-extension-preferences-header"; const title = document.createElement("h2"); title.id = "opportunities-extension-preferences-title"; title.textContent = "Preferences"; const closeButton = document.createElement("button"); closeButton.type = "button"; closeButton.className = "opportunities-extension-icon-button"; closeButton.setAttribute("aria-label", "Fechar preferências"); closeButton.title = "Close"; closeButton.append(createCloseIcon()); closeButton.addEventListener("click", closePreferencesModal); header.append(title, closeButton); const content = document.createElement("div"); content.className = "opportunities-extension-preferences-content"; const fieldset = document.createElement("fieldset"); fieldset.className = "opportunities-extension-preferences-fieldset"; const legend = document.createElement("legend"); legend.textContent = "Opportunity Type View"; fieldset.append(legend); const selectedValues = new Set(getSavedOpportunityTypeValues()); const options = document.createElement("div"); options.className = "opportunities-extension-preferences-options"; OPPORTUNITY_TYPE_OPTIONS.forEach((option) => { const label = document.createElement("label"); label.className = "opportunities-extension-preferences-option"; const checkbox = document.createElement("input"); checkbox.type = "checkbox"; checkbox.name = "opportunityTypeView"; checkbox.value = option.value; checkbox.checked = selectedValues.has(option.value); const text = document.createElement("span"); text.textContent = option.label; label.append(checkbox, text); options.append(label); }); fieldset.append(options); const cacheField = document.createElement("label"); cacheField.className = "opportunities-extension-preferences-cache-field"; const cacheLabel = document.createElement("span"); cacheLabel.textContent = "Cache duration"; const cacheSelect = document.createElement("select"); cacheSelect.name = "opptyProductsCacheHours"; cacheSelect.setAttribute("aria-label", "Cache duration"); OPPTY_PRODUCTS_CACHE_OPTIONS.forEach((option) => { const optionElement = document.createElement("option"); optionElement.value = String(option.value); optionElement.textContent = option.label; optionElement.selected = option.value === getSavedOpptyProductsCacheHours(); cacheSelect.append(optionElement); }); cacheField.append(cacheLabel, cacheSelect); content.append(fieldset, cacheField); const footer = document.createElement("footer"); footer.className = "opportunities-extension-preferences-footer"; const saveButton = document.createElement("button"); saveButton.type = "button"; saveButton.className = "opportunities-extension-button opportunities-extension-button-primary"; saveButton.textContent = "Save"; saveButton.addEventListener("click", () => { const selectedValues = Array.from(preferencesModal.querySelectorAll("input[name='opportunityTypeView']:checked"), (checkbox) => checkbox.value); const selectedCacheHours = Number(preferencesModal.querySelector("select[name='opptyProductsCacheHours']")?.value); preferencesState.selectedOpportunityTypeValues = new Set(selectedValues); preferencesState.opptyProductsCacheHours = OPPTY_PRODUCTS_CACHE_OPTIONS.some((option) => option.value === selectedCacheHours) ? selectedCacheHours : DEFAULT_OPPTY_PRODUCTS_CACHE_HOURS; try { getLocalStorage().setItem(OPPORTUNITY_TYPE_PREFERENCES_STORAGE_KEY, JSON.stringify({ opportunityTypeValues: selectedValues, opptyProductsCacheHours: preferencesState.opptyProductsCacheHours })); } catch (error) { // Continue with the saved selection for the active modal when storage is unavailable. } closePreferencesModal(); const periodSelect = overlay.querySelector("select[name='opportunityPeriod']"); requestOpportunitiesForPeriod(periodSelect?.value || PERIOD_OPTIONS[0]); }); footer.append(saveButton); dialog.append(header, content, footer); preferencesModal.append(dialog); overlay.append(preferencesModal); preferencesModal.querySelector("input")?.focus(); } function closePreferencesModal() { const preferencesModal = document.getElementById(PREFERENCES_MODAL_ID); if (preferencesModal) { preferencesModal.hidden = true; } } return { openPreferencesModal, closePreferencesModal }; } // Source: src/features/products/content-jobs.js function createProductsContentJobs({ messageTypes, PRODUCTS_CACHE_TIMESTAMP_ID, PRODUCTS_MESSAGE_LISTENER_KEY, PRODUCTS_PROGRESS_ID, appendBackgroundRequestLog, preferencesState, productsContentState, renderOpportunitiesTable, beginProductActivities, completeProductActivities, sendRuntimeMessage, environment }) { const { browser, chrome, document, window, setTimeout, clearTimeout, matchMedia } = environment; let completion = null; function updateCompletionVisibility(badge) { const requestId = productsContentState.currentProductsRequestId; const complete = productsContentState.productsProgress.state === "complete"; if (completion && (!complete || completion.requestId !== requestId || completion.badge !== badge)) { clearTimeout(completion.waitTimer); clearTimeout(completion.hideTimer); completion.badge.setAttribute("data-fading", "false"); completion = null; } if (!complete) { badge.setAttribute("data-fading", "false"); return; } if (completion) { badge.hidden = completion.hidden; return; } const current = { requestId, badge, hidden: false, waitTimer: null, hideTimer: null }; completion = current; badge.setAttribute("data-fading", "false"); current.waitTimer = setTimeout(() => { if (completion !== current) return; badge.setAttribute("data-fading", "true"); const reducedMotion = typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches; current.hideTimer = setTimeout(() => { if (completion !== current) return; current.hidden = true; badge.hidden = true; }, reducedMotion ? 0 : 300); }, 5000); } function requestOpptyProductsInBackground(opportunities, token) { const optyNumbers = Array.from(new Set(opportunities .map((item) => item && item.OptyNumber) .filter(Boolean) .map(String))); const requestId = `${Date.now()}-${Math.random().toString(36).slice(2)}`; productsContentState.currentProductsRequestId = requestId; beginProductActivities(requestId, token, optyNumbers); productsContentState.opptyProductsByNumber = new Map(); productsContentState.expandedProductOptyNumbers = new Set(); productsContentState.productsProgress = { state: optyNumbers.length ? "running" : "complete", completed: 0, total: optyNumbers.length, failed: 0, cacheHits: 0 }; renderProductsProgress(); if (optyNumbers.length === 0) { return; } sendRuntimeMessage({ type: messageTypes.requestOpptyProducts, requestId, accessToken: token, optyNumbers, cacheMaxAgeMs: preferencesState.opptyProductsCacheHours * 60 * 60 * 1000 }).then((response) => { if (response && response.ok === false && response.requestId === productsContentState.currentProductsRequestId && productsContentState.productsProgress.state === "running") { productsContentState.productsProgress.state = "error"; productsContentState.productsProgress.failed = productsContentState.productsProgress.total; renderProductsProgress(); } }).catch(() => { if (requestId === productsContentState.currentProductsRequestId && productsContentState.productsProgress.state === "running") { productsContentState.productsProgress.state = "error"; productsContentState.productsProgress.failed = productsContentState.productsProgress.total; renderProductsProgress(); } }); } function ensureProductsBackgroundMessageHandler() { if (window[PRODUCTS_MESSAGE_LISTENER_KEY]) { return; } const runtimeApi = typeof browser !== "undefined" ? browser : chrome; if (!runtimeApi || !runtimeApi.runtime || !runtimeApi.runtime.onMessage) { return; } window[PRODUCTS_MESSAGE_LISTENER_KEY] = true; runtimeApi.runtime.onMessage.addListener((message) => { if (!message || message.requestId !== productsContentState.currentProductsRequestId) { return false; } if (message.type === messageTypes.opptyProductsRequestDebug) { if (message.entry && typeof message.entry.debugKey === "string") appendBackgroundRequestLog([message.entry]); return false; } if (message.type !== messageTypes.opptyProductsProgress) { return false; } if (message.optyNumber) { productsContentState.opptyProductsByNumber.set(String(message.optyNumber), { items: Array.isArray(message.items) ? message.items : [], fromCache: Boolean(message.fromCache), cachedAt: Number(message.cachedAt) || 0, error: message.error || "" }); } productsContentState.productsProgress = { state: message.state || productsContentState.productsProgress.state, completed: Number(message.completed) || 0, total: Number(message.total) || productsContentState.productsProgress.total, failed: Number(message.failed) || 0, cacheHits: Number(message.cacheHits) || 0 }; renderProductsProgress(); if (message.state === "complete" || message.state === "complete-with-errors") completeProductActivities(message.requestId); queueProductsTableRender(); return false; }); } function renderProductsProgress() { const badge = document.getElementById(PRODUCTS_PROGRESS_ID); if (!badge) { return; } badge.hidden = productsContentState.productsProgress.state === "idle"; badge.setAttribute("data-state", productsContentState.productsProgress.state); if (productsContentState.productsProgress.state === "running") { badge.textContent = `Products ${productsContentState.productsProgress.completed}/${productsContentState.productsProgress.total}`; badge.title = `${productsContentState.productsProgress.cacheHits} loaded from cache`; } else if (productsContentState.productsProgress.state === "complete") { badge.textContent = `Products ready ${productsContentState.productsProgress.total}`; badge.title = `${productsContentState.productsProgress.cacheHits} loaded from cache`; } else if (productsContentState.productsProgress.state === "complete-with-errors") { badge.textContent = `Products ${productsContentState.productsProgress.total - productsContentState.productsProgress.failed}/${productsContentState.productsProgress.total}`; badge.title = `${productsContentState.productsProgress.failed} product requests failed`; } else if (productsContentState.productsProgress.state === "error") { badge.textContent = "Products unavailable"; badge.title = "Unable to load opportunity products"; } else { badge.textContent = "Products ready 0"; } updateCompletionVisibility(badge); const timestamp = document.getElementById(PRODUCTS_CACHE_TIMESTAMP_ID); if (timestamp) { timestamp.hidden = !productsContentState.productsLastCachedAt; timestamp.textContent = productsContentState.productsLastCachedAt ? `Last update: ${formatProductsCacheTimestamp(productsContentState.productsLastCachedAt)}` : ""; timestamp.title = productsContentState.productsLastCachedAt ? `Latest product update: ${formatProductsCacheTimestamp(productsContentState.productsLastCachedAt)}` : ""; } } function formatProductsCacheTimestamp(timestamp) { try { return new Intl.DateTimeFormat("en-GB", { dateStyle: "short", timeStyle: "short" }).format(new Date(timestamp)); } catch (error) { return new Date(timestamp).toLocaleString(); } } function queueProductsTableRender() { if (productsContentState.productsRenderQueued) { return; } productsContentState.productsRenderQueued = true; window.requestAnimationFrame(() => { productsContentState.productsRenderQueued = false; renderOpportunitiesTable(); }); } return { requestOpptyProductsInBackground, ensureProductsBackgroundMessageHandler, renderProductsProgress, formatProductsCacheTimestamp, queueProductsTableRender }; } // Source: src/features/products/details.js function createProductsDetails({ OPPORTUNITIES_COLUMNS, setBadgeContent, createAiSummaryAlert, formatOracleResponseDate, productsContentState, renderOpportunitiesTable, requestGenerateAiSummary, environment }) { const { document } = environment; function createProductsRampBadge(productsResult) { const values = (Array.isArray(productsResult.items) ? productsResult.items : []) .map(item => item && item.RampMonths_c) .map(value => value === null || value === undefined || String(value).trim() === "" ? "N/D" : String(value).trim()) .filter((value, index, items) => value !== "N/D" || items.indexOf(value) === index); if (!values.length) return null; const badge = document.createElement("span"); badge.className = "opportunities-extension-products-ramp"; badge.setAttribute("data-missing", String(values.includes("N/D"))); setBadgeContent(badge, "ramp", values.join(" | ")); badge.title = `Ramp Months: ${values.join(", ")}`; badge.setAttribute("aria-label", badge.title); return badge; } function createProductsCountButton(optyNumber, productsResult) { const normalizedOptyNumber = String(optyNumber); const button = document.createElement("button"); const count = Array.isArray(productsResult.items) ? productsResult.items.length : 0; const isExpanded = productsContentState.expandedProductOptyNumbers.has(normalizedOptyNumber); button.type = "button"; button.className = "opportunities-extension-products-count"; setBadgeContent(button, "products", count); button.setAttribute("aria-expanded", String(isExpanded)); button.setAttribute("aria-label", `${count} product${count === 1 ? "" : "s"} for opportunity ${normalizedOptyNumber}`); button.title = productsResult.error ? productsResult.error : `${count} product${count === 1 ? "" : "s"}`; button.classList.toggle("opportunities-extension-products-count-error", Boolean(productsResult.error)); button.addEventListener("click", (event) => { event.preventDefault(); event.stopPropagation(); if (isExpanded) { productsContentState.expandedProductOptyNumbers.delete(normalizedOptyNumber); } else { productsContentState.expandedProductOptyNumbers.add(normalizedOptyNumber); requestGenerateAiSummary(normalizedOptyNumber); } renderOpportunitiesTable(); }); return button; } function createProductsDetailRow(opportunity) { const row = document.createElement("tr"); const cell = document.createElement("td"); const optyNumber = String(opportunity.OptyNumber); const productsResult = productsContentState.opptyProductsByNumber.get(optyNumber); const products = productsResult && Array.isArray(productsResult.items) ? productsResult.items : []; row.className = "opportunities-extension-products-detail-row"; cell.colSpan = OPPORTUNITIES_COLUMNS.length; cell.className = "opportunities-extension-products-detail-cell"; const panel = document.createElement("section"); panel.className = "opportunities-extension-products-detail"; const heading = document.createElement("div"); heading.className = "opportunities-extension-products-detail-heading"; const title = document.createElement("strong"); title.textContent = `Products for ${optyNumber}`; const summary = document.createElement("span"); summary.textContent = productsResult && productsResult.fromCache ? "Cached" : "Updated"; heading.append(title, summary); panel.append(heading); panel.append(createAiSummaryAlert(optyNumber)); if (productsResult && productsResult.error) { const error = document.createElement("p"); error.className = "opportunities-extension-products-empty"; error.textContent = productsResult.error; panel.append(error); } else if (products.length === 0) { const empty = document.createElement("p"); empty.className = "opportunities-extension-products-empty"; empty.textContent = "No products found for this opportunity."; panel.append(empty); } else { const table = document.createElement("table"); table.className = "opportunities-extension-products-table"; table.setAttribute("aria-label", `Products for opportunity ${optyNumber}`); const columns = [ { label: "Product Group", value: (item) => item.ProdGroupName || "-" }, { label: "Workload", value: (item) => item.WorkloadName_c || "-" }, { label: "Currency", value: (item) => item.RevnAmountCurcyCode || "-" }, { label: "Amount", value: (item) => formatProductAmount(item.RevnAmount, item.RevnAmountCurcyCode) }, { label: "Type", value: (item) => item.TypeCode || "-" }, { label: "Status", value: (item) => item.StatusCode || "-" }, { label: "Win Probability", value: (item) => formatProductWinProbability(item.WinProb) }, { label: "Close date", value: (item) => formatOracleResponseDate(item.EffectiveDate) || "-" }, { label: "Consumption Start", value: (item) => formatOracleResponseDate(item.ConsumptionStartDate_c) || "-" }, { label: "Ramp Months", value: (item) => item.RampMonths_c === null || item.RampMonths_c === undefined || String(item.RampMonths_c).trim() === "" ? "N/D" : formatProductNumber(item.RampMonths_c) } ]; const head = document.createElement("thead"); const headerRow = document.createElement("tr"); columns.forEach((column) => { const header = document.createElement("th"); header.scope = "col"; header.textContent = column.label; headerRow.append(header); }); head.append(headerRow); const body = document.createElement("tbody"); products.forEach((product) => { const productRow = document.createElement("tr"); columns.forEach((column) => { const productCell = document.createElement("td"); productCell.textContent = String(column.value(product)); productRow.append(productCell); }); body.append(productRow); }); table.append(head, body); panel.append(table); } cell.append(panel); row.append(cell); return row; } function formatProductNumber(value) { const number = Number(value); return Number.isFinite(number) ? new Intl.NumberFormat("en-US", { maximumFractionDigits: 2 }).format(number) : "-"; } function formatProductAmount(value, currency) { const amount = Number(value); if (!Number.isFinite(amount)) { return "-"; } try { return new Intl.NumberFormat("en-US", { style: "currency", currency: currency || "USD" }).format(amount); } catch (error) { return `${currency || ""} ${formatProductNumber(amount)}`.trim(); } } function formatProductWinProbability(value) { const number = Number(value); return Number.isFinite(number) ? `${number}%` : "-"; } return { createProductsRampBadge, createProductsCountButton, createProductsDetailRow, formatProductNumber, formatProductAmount, formatProductWinProbability }; } // Source: src/features/ai-summary/content.js function createAiSummaryContent({ aiSummaryState, invalidateSalesCommonToken, logRequestFailure, logRequestStart, logRequestSuccess, readResponseBodyForDebug, requestTokenRelaySalesCommon, environment }) { const { document, fetch } = environment; function requestGenerateAiSummary(optyNumber) { const key = String(optyNumber); const existing = aiSummaryState.aiSummariesByOptyNumber.get(key); if (existing && existing.status === "loading") { return existing.promise; } const state = { status: "loading", summaryText: "", error: "", promise: null }; aiSummaryState.aiSummariesByOptyNumber.set(key, state); state.promise = (async () => { let entry; try { let token = await requestTokenRelaySalesCommon(); if (!token) { throw new Error("Authentication is required to generate the summary."); } const path = "/api/sales-common/gen-ai/v1/summarizations/generate"; const url = `https://eeho.fa.us2.oraclecloud.com${path}`; const payload = { path, promptCode: "cx.sales.generate_opportunity_summary", object: "opportunities", puid: key }; let response; for (let attempt = 0; attempt < 2; attempt += 1) { const headers = { Accept: "application/json", Authorization: `Bearer ${token}`, "Content-Type": "application/json" }; entry = logRequestStart({ name: "requestGenerateAiSummary", method: "POST", url, headers, body: JSON.stringify(payload, null, 2), metadata: { optyNumber: key, attempt: attempt + 1 } }); response = await fetch(url, { method: "POST", credentials: "include", headers, body: JSON.stringify(payload) }); logRequestSuccess(entry, response, await readResponseBodyForDebug(response)); if (response.status !== 401 && response.status !== 403) { break; } // A late failure for an old token must not discard a newly refreshed token. invalidateSalesCommonToken(token); if (attempt === 0) { token = await requestTokenRelaySalesCommon(); } } if (!response.ok) { throw new Error(`requestGenerateAiSummary failed with status ${response.status}.`); } let data; try { data = await response.json(); } catch (error) { throw new Error("requestGenerateAiSummary returned an invalid JSON response."); } if (!data || typeof data.summaryText !== "string" || !data.summaryText.trim()) { throw new Error("requestGenerateAiSummary returned an empty summary."); } state.summaryText = data.summaryText; state.status = "ready"; } catch (error) { state.status = "error"; state.error = error.message || "Unable to generate the AI summary."; if (entry) { logRequestFailure(entry, state.error); } } finally { // Update only visible summaries, without reopening rows or rebuilding the table. document.querySelectorAll(".opportunities-extension-ai-summary").forEach((alert) => { if (alert.dataset.optyNumber === key) { alert.replaceWith(createAiSummaryAlert(key)); } }); } })(); return state.promise; } function createAiSummaryAlert(optyNumber) { const state = aiSummaryState.aiSummariesByOptyNumber.get(optyNumber); const alert = document.createElement("div"); alert.className = "opportunities-extension-ai-summary"; alert.dataset.optyNumber = optyNumber; alert.setAttribute("role", "status"); alert.setAttribute("aria-live", "polite"); alert.setAttribute("aria-busy", String(!state || state.status === "loading")); const title = document.createElement("strong"); title.textContent = "Summary generated by AI"; const description = document.createElement("p"); description.textContent = !state || state.status === "loading" ? "Generating summary..." : state.status === "error" ? state.error : state.summaryText; alert.classList.toggle("opportunities-extension-ai-summary-error", Boolean(state && state.status === "error")); alert.append(title, description); return alert; } return { requestGenerateAiSummary, createAiSummaryAlert }; } // Source: src/features/consumption/plans-action.js function createConsumptionPlansAction({ messageTypes, appendBackgroundRequestLog, consumptionPlansState, getNestedValue, openAccountPlansModal, renderOpportunitiesTable, requestConsumerTokenServiceOnce, sendRuntimeMessage, showCopyConfirmation, environment }) { const { document } = environment; function createCloudConsumptionButton(opportunity) { const button = document.createElement("button"); const optyNumber = String(opportunity.OptyNumber || ""); const partyNumber = getNestedValue(opportunity, ["CustomerAccount", "PartyNumber"]); const requestState = consumptionPlansState.accountPlansRequestState.get(optyNumber); const isLoading = requestState && requestState.status === "loading"; button.type = "button"; button.className = "opportunities-extension-action-button"; button.setAttribute("aria-label", `View cloud consumption for ${optyNumber || "opportunity"}`); button.title = isLoading ? "Loading customer consumption" : "View customer consumption"; button.disabled = Boolean(isLoading); button.classList.toggle("opportunities-extension-action-button-loading", Boolean(isLoading)); button.append(createCloudConsumptionIcon()); button.addEventListener("click", () => { if (!partyNumber) { showCopyConfirmation("Customer Party Number is unavailable.", true); return; } const currentRequests = consumptionPlansState.accountPlansRequestState; currentRequests.set(optyNumber, { status: "loading" }); openAccountPlansModal(opportunity, [], "loading"); renderOpportunitiesTable(); requestConsumerTokenServiceOnce() .then((authorizations) => sendRuntimeMessage({ type: messageTypes.requestAccountPlans, partyNumber: String(partyNumber), authorizations })) .then((response) => { if (consumptionPlansState.accountPlansRequestState !== currentRequests) return; appendBackgroundRequestLog(response && response.debugRequests); if (!response || !response.ok) { throw new Error(response && response.error ? response.error : "Unable to load cloud consumption."); } consumptionPlansState.accountPlansRequestState.set(optyNumber, { status: "ready", plans: Array.isArray(response.plans) ? response.plans : [] }); renderOpportunitiesTable(); openAccountPlansModal(opportunity, response.plans || [], "ready"); }).catch((error) => { if (consumptionPlansState.accountPlansRequestState !== currentRequests) return; const errorMessage = error.message || "Unable to load cloud consumption."; consumptionPlansState.accountPlansRequestState.set(optyNumber, { status: "error", error: errorMessage }); renderOpportunitiesTable(); openAccountPlansModal(opportunity, [], "error", errorMessage); showCopyConfirmation(errorMessage, true); }); }); return button; } function createCloudConsumptionIcon() { const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); const cloud = document.createElementNS("http://www.w3.org/2000/svg", "path"); const usage = document.createElementNS("http://www.w3.org/2000/svg", "path"); svg.setAttribute("viewBox", "0 0 24 24"); svg.setAttribute("aria-hidden", "true"); svg.setAttribute("focusable", "false"); cloud.setAttribute("d", "M7.2 18.5h10.1a4.2 4.2 0 0 0 .5-8.37A6.25 6.25 0 0 0 5.9 8.75a4.9 4.9 0 0 0 1.3 9.75Z"); usage.setAttribute("d", "M9 15v-2m3 2v-4m3 4V9"); [cloud, usage].forEach((path) => { path.setAttribute("fill", "none"); path.setAttribute("stroke", "currentColor"); path.setAttribute("stroke-linecap", "round"); path.setAttribute("stroke-linejoin", "round"); path.setAttribute("stroke-width", "1.7"); }); svg.append(cloud, usage); return svg; } return { createCloudConsumptionButton, createCloudConsumptionIcon }; } // Source: src/features/install-base/action.js function createInstallBaseAction({ messageTypes, requestConsumerTokenServiceOnce, sendRuntimeMessage, appendBackgroundRequestLog, openInstallBaseModal, environment }) { const { document } = environment; const pending = new Map(); async function load(partyNumber) { const authorizations = await requestConsumerTokenServiceOnce(); const response = await sendRuntimeMessage({ type: messageTypes.requestInstallBase, partyNumber, authorizations: { accountSummary: authorizations.accountSummary, installBase: authorizations.installBase } }); appendBackgroundRequestLog(response?.debugRequests); if (!response?.ok) throw new Error(response?.error || "Unable to load installed base."); return response.items; } function createInstallBaseButton(opportunity) { const button = document.createElement("button"); button.type = "button"; button.className = "opportunities-extension-action-button"; button.title = "View customer installed base"; button.setAttribute("aria-label", `View installed base for ${opportunity.CustomerAccount?.PartyUniqueName || "customer"}`); const icon = document.createElementNS("http://www.w3.org/2000/svg", "svg"); icon.setAttribute("viewBox", "0 0 24 24"); icon.setAttribute("aria-hidden", "true"); icon.setAttribute("focusable", "false"); const path = document.createElementNS("http://www.w3.org/2000/svg", "path"); path.setAttribute("d", "M4 3h16v7H4z M4 14h16v7H4z M7 6.5h.01 M7 17.5h.01 M11 6.5h6 M11 17.5h6 M8 10v4 M16 10v4"); path.setAttribute("fill", "none"); path.setAttribute("stroke", "currentColor"); path.setAttribute("stroke-width", "1.7"); path.setAttribute("stroke-linecap", "round"); path.setAttribute("stroke-linejoin", "round"); icon.append(path); button.append(icon); button.addEventListener("click", async () => { const view = openInstallBaseModal(opportunity, button); const partyNumber = String(opportunity.CustomerAccount?.PartyNumber || ""); try { if (!partyNumber) throw new Error("Customer Party Number is unavailable."); if (!pending.has(partyNumber)) { const request = load(partyNumber).finally(() => { if (pending.get(partyNumber) === request) pending.delete(partyNumber); }); pending.set(partyNumber, request); } view.render(await pending.get(partyNumber)); } catch (error) { view.render([], error.message || "Unable to load installed base."); } }); return button; } return { createInstallBaseButton, resetRequests: () => pending.clear() }; } // Source: src/features/install-base/view.js function createInstallBaseView({ MODAL_ID, createCloseIcon, formatOracleResponseDate, createSearch, environment }) { const { document } = environment; let active = null; const columns = [ ["partyName", "Customer"], ["productGroupName", "Product Group"], ["productName", "Product"], ["currentSupport", "Current Support", "amount"], ["orderNumber", "Order Number"], ["supportStatus", "Support Status", "status"], ["quantityType", "Quantity Type"], ["quantity", "Quantity", "quantity"], ["supportStartDate", "Support Start Date", "date"], ["supportEndDate", "Support End Date", "date"], ["licenseValue", "License Value", "amount"], ["shipDate", "Ship Date", "date"] ]; function element(tag, className, text) { const node = document.createElement(tag); if (className) node.className = className; if (text !== undefined) node.textContent = text; return node; } function closeInstallBaseModal() { if (!active) return false; const previous = active; active = null; previous.modal.remove(); if (previous.trigger?.isConnected) previous.trigger.focus(); return true; } function formatValue(value, type) { if (value === null || value === undefined || value === "") return "-"; if (type === "date") return formatOracleResponseDate(value); if ((type === "amount" || type === "quantity") && Number.isFinite(Number(value))) { return new Intl.NumberFormat("en-US", { minimumFractionDigits: type === "amount" ? 2 : 0, maximumFractionDigits: type === "amount" ? 2 : 4 }).format(Number(value)); } return String(value).trim(); } function createTable(items, loading = false) { const table = element("table", "opportunities-extension-install-base-table"); table.setAttribute("aria-label", "Installed base items"); const head = element("thead"); const header = element("tr"); const group = element("colgroup"); columns.forEach(([key, label]) => { const col = element("col"); col.className = `install-base-col-${key}`; group.append(col); const th = element("th", "", label); th.scope = "col"; header.append(th); }); head.append(header); const body = element("tbody"); (loading ? Array.from({ length: 8 }, () => ({})) : items).forEach(item => { const row = element("tr"); columns.forEach(([key, , type]) => { const cell = element("td"); if (loading) { const bar = element("span", "opportunities-extension-account-plans-skeleton-bar"); bar.setAttribute("aria-hidden", "true"); cell.append(bar); } else { const value = item[key]; const text = formatValue(value, type); if (type === "status") { const badge = element("span", "opportunities-extension-install-base-status", text); badge.dataset.status = text.toUpperCase(); cell.append(badge); } else cell.textContent = text; if (type) cell.dataset.format = type; } row.append(cell); }); body.append(row); }); table.append(group, head, body); return table; } function openInstallBaseModal(opportunity, trigger) { closeInstallBaseModal(); const overlay = document.getElementById(MODAL_ID); if (!overlay || overlay.hidden) return { render() {} }; const modal = element("section", "opportunities-extension-account-plans-modal"); modal.setAttribute("role", "dialog"); modal.setAttribute("aria-modal", "true"); modal.setAttribute("aria-busy", "true"); modal.setAttribute("aria-labelledby", "opportunities-extension-install-base-title"); const dialog = element("div", "opportunities-extension-account-plans-dialog opportunities-extension-install-base-dialog"); const header = element("header", "opportunities-extension-account-plans-header"); const heading = element("div"); const title = element("h2", "", "Installed Base"); title.id = "opportunities-extension-install-base-title"; heading.append(title, element("p", "", opportunity.CustomerAccount?.PartyUniqueName || "Customer")); const close = element("button", "opportunities-extension-icon-button"); close.type = "button"; close.title = "Close"; close.setAttribute("aria-label", "Close installed base"); close.append(createCloseIcon()); close.addEventListener("click", closeInstallBaseModal); header.append(heading, close); const content = element("div", "opportunities-extension-install-base-content"); content.tabIndex = 0; content.setAttribute("role", "region"); content.setAttribute("aria-label", "Installed base results"); const status = element("p", "opportunities-extension-install-base-message", "Loading installed base..."); status.setAttribute("role", "status"); let loadedItems = [], loaded = false, failure = ""; const search = createSearch({ label: "Search installed base", onSearch: renderResults, getValues: item => columns.flatMap(([key, , type]) => [item[key], formatValue(item[key], type)]) }); const toolbar = element("div", "opportunities-extension-customer-table-toolbar"); toolbar.append(search.input); content.append(status, createTable([], true)); dialog.append(header, toolbar, content); modal.append(dialog); overlay.append(modal); active = { modal, trigger }; modal.addEventListener("keydown", event => { if (event.key === "Tab") { event.preventDefault(); const controls = [close, search.input, content]; const index = controls.indexOf(document.activeElement); controls[(index + (event.shiftKey ? -1 : 1) + controls.length) % controls.length].focus(); } }); close.focus(); function renderResults() { if (!loaded || active?.modal !== modal || overlay.hidden) return; const items = failure ? [] : search.filter(loadedItems); content.textContent = ""; status.textContent = failure || (loadedItems.length ? `${items.length} of ${loadedItems.length} installed base items${items.length ? "" : ". No matching records."}` : "No installed base items found for this customer."); status.setAttribute("role", failure ? "alert" : "status"); content.append(status); if (!failure && items.length) content.append(createTable(items)); content.scrollTop = 0; } return { render(items, error = "") { // A closed or replaced dialog must never receive a late response. if (active?.modal !== modal || overlay.hidden) return; modal.setAttribute("aria-busy", "false"); loadedItems = items; failure = error; loaded = true; search.reset(); renderResults(); } }; } return { openInstallBaseModal, closeInstallBaseModal, createTable }; } // Source: src/features/opportunities/customer-table-search.js function createCustomerTableSearch({ environment }) { function createSearch({ label, onSearch, getValues }) { const input = environment.document.createElement("input"); input.type = "search"; input.className = "opportunities-extension-customer-table-search"; input.placeholder = label; input.setAttribute("aria-label", label); input.addEventListener("input", onSearch); const normalize = value => String(value ?? "").normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase(); let index = new WeakMap(); return { input, reset() { index = new WeakMap(); }, filter(items) { const terms = normalize(input.value).trim().split(/\s+/).filter(Boolean); if (!terms.length) return items; return items.filter(item => { if (!index.has(item)) index.set(item, getValues(item).map(normalize)); return terms.every(term => index.get(item).some(value => value.includes(term))); }); } }; } return { createSearch }; } // Source: src/features/service-requests/action.js function createServiceRequestsAction({ messageTypes, requestConsumerTokenServiceOnce, sendRuntimeMessage, appendBackgroundRequestLog, openServiceRequestsModal, environment }) { const { document } = environment; const pending = new Map(); async function load(partyNumber) { const authorizations = await requestConsumerTokenServiceOnce(); const response = await sendRuntimeMessage({ type: messageTypes.requestCustomerServiceRequest, partyNumber, authorizations: { accountSummary: authorizations.accountSummary, customerServiceRequests: authorizations.customerServiceRequests } }); appendBackgroundRequestLog(response?.debugRequests); if (!response?.ok) throw new Error(response?.error || "Unable to load service requests."); return response.items; } function createServiceRequestsButton(opportunity) { const button = document.createElement("button"); button.type = "button"; button.className = "opportunities-extension-action-button"; button.title = "View customer service requests"; button.setAttribute("aria-label", `View service requests for ${opportunity.CustomerAccount?.PartyUniqueName || "customer"}`); const icon = document.createElementNS("http://www.w3.org/2000/svg", "svg"); icon.setAttribute("viewBox", "0 0 24 24"); icon.setAttribute("aria-hidden", "true"); icon.setAttribute("focusable", "false"); const path = document.createElementNS("http://www.w3.org/2000/svg", "path"); path.setAttribute("d", "M4 14v-3a8 8 0 0 1 16 0v3 M4 11H3v7h4v-7H4 M20 11h1v7h-4v-7h3 M20 18v1a2 2 0 0 1-2 2h-6"); path.setAttribute("fill", "none"); path.setAttribute("stroke", "currentColor"); path.setAttribute("stroke-width", "1.7"); path.setAttribute("stroke-linecap", "round"); path.setAttribute("stroke-linejoin", "round"); icon.append(path); button.append(icon); button.addEventListener("click", async () => { const view = openServiceRequestsModal(opportunity, button); const partyNumber = String(opportunity.CustomerAccount?.PartyNumber || ""); try { if (!partyNumber) throw new Error("Customer Party Number is unavailable."); if (!pending.has(partyNumber)) { const request = load(partyNumber).finally(() => { if (pending.get(partyNumber) === request) pending.delete(partyNumber); }); pending.set(partyNumber, request); } view.render(await pending.get(partyNumber)); } catch (error) { view.render([], error.message || "Unable to load service requests."); } }); return button; } return { createServiceRequestsButton, resetRequests: () => pending.clear() }; } // Source: src/features/service-requests/view.js function createServiceRequestsView({ MODAL_ID, createCloseIcon, formatOracleResponseDate, createSearch, environment }) { const { document } = environment; let active = null; const columns = [ ["name", "Name", item => item.customer?.name], ["registryId", "Registry ID", item => item.customer?.registryId], ["id", "SR Number", item => item.id], ["severity", "Severity", item => item.severity], ["status", "Status", item => item.status], ["subStatus", "Substatus", item => item.subStatus], ["productName", "Product Name", item => item.component?.productName], ["os", "OS", item => item.os], ["csi", "CSI", item => item.csi], ["created", "Creation date", item => item.created], ["lastUpdated", "Modified", item => item.lastUpdated] ]; function element(tag, className, text) { const node = document.createElement(tag); if (className) node.className = className; if (text !== undefined) node.textContent = text; return node; } function closeServiceRequestsModal() { if (!active) return false; const previous = active; active = null; previous.modal.remove(); if (previous.trigger?.isConnected) previous.trigger.focus(); return true; } function formatValue(value, key) { const text = value === null || value === undefined || value === "" ? "-" : String(value); return text !== "-" && (key === "created" || key === "lastUpdated") ? formatOracleResponseDate(value) : text; } function createTable(items, loading = false) { const table = element("table", "opportunities-extension-service-requests-table"); table.setAttribute("aria-label", "Customer service requests"); const group = element("colgroup"); const head = element("thead"); const header = element("tr"); columns.forEach(([key, label]) => { group.append(element("col", `service-requests-col-${key}`)); const th = element("th", "", label); th.scope = "col"; header.append(th); }); head.append(header); const body = element("tbody"); (loading ? Array.from({ length: 8 }, () => ({})) : items).forEach(item => { const row = element("tr"); columns.forEach(([key, , getValue]) => { const cell = element("td"); cell.dataset.field = key; if (loading) { const bar = element("span", "opportunities-extension-account-plans-skeleton-bar"); bar.setAttribute("aria-hidden", "true"); cell.append(bar); } else { const value = getValue(item); const text = formatValue(value, key); if (key === "status") { const badge = element("span", "opportunities-extension-service-request-status", text); badge.dataset.status = text.trim().toUpperCase(); cell.append(badge); } else cell.textContent = text; } row.append(cell); }); body.append(row); }); table.append(group, head, body); return table; } function openServiceRequestsModal(opportunity, trigger) { closeServiceRequestsModal(); const overlay = document.getElementById(MODAL_ID); if (!overlay || overlay.hidden) return { render() {} }; const modal = element("section", "opportunities-extension-account-plans-modal"); modal.setAttribute("role", "dialog"); modal.setAttribute("aria-modal", "true"); modal.setAttribute("aria-busy", "true"); modal.setAttribute("aria-labelledby", "opportunities-extension-service-requests-title"); const dialog = element("div", "opportunities-extension-account-plans-dialog opportunities-extension-service-requests-dialog"); const header = element("header", "opportunities-extension-account-plans-header"); const heading = element("div"); const title = element("h2", "", "Service Requests"); title.id = "opportunities-extension-service-requests-title"; heading.append(title, element("p", "", opportunity.CustomerAccount?.PartyUniqueName || "Customer")); const close = element("button", "opportunities-extension-icon-button"); close.type = "button"; close.title = "Close"; close.setAttribute("aria-label", "Close service requests"); close.append(createCloseIcon()); close.addEventListener("click", closeServiceRequestsModal); header.append(heading, close); const content = element("div", "opportunities-extension-service-requests-content"); content.tabIndex = 0; content.setAttribute("role", "region"); content.setAttribute("aria-label", "Service requests results"); const status = element("p", "opportunities-extension-service-requests-message", "Loading service requests..."); status.setAttribute("role", "status"); let loadedItems = [], loaded = false, failure = ""; const search = createSearch({ label: "Search service requests", onSearch: renderResults, getValues: item => columns.flatMap(([key, , getValue]) => [getValue(item), formatValue(getValue(item), key)]) }); const toolbar = element("div", "opportunities-extension-customer-table-toolbar"); toolbar.append(search.input); content.append(status, createTable([], true)); dialog.append(header, toolbar, content); modal.append(dialog); overlay.append(modal); active = { modal, trigger }; modal.addEventListener("keydown", event => { if (event.key === "Tab") { event.preventDefault(); const controls = [close, search.input, content]; const index = controls.indexOf(document.activeElement); controls[(index + (event.shiftKey ? -1 : 1) + controls.length) % controls.length].focus(); } }); close.focus(); function renderResults() { if (!loaded || active?.modal !== modal || overlay.hidden) return; const items = failure ? [] : search.filter(loadedItems); content.textContent = ""; status.textContent = failure || (loadedItems.length ? `${items.length} of ${loadedItems.length} service requests${items.length ? "" : ". No matching records."}` : "No open service requests found for this customer."); status.setAttribute("role", failure ? "alert" : "status"); content.append(status); if (!failure && items.length) content.append(createTable(items)); content.scrollTop = 0; } return { render(items, error = "") { if (active?.modal !== modal || overlay.hidden) return; modal.setAttribute("aria-busy", "false"); loadedItems = items; failure = error; loaded = true; search.reset(); renderResults(); } }; } return { openServiceRequestsModal, closeServiceRequestsModal, createTable }; } // Source: src/features/consumption/plans-view.js function createConsumptionPlansView({ ACCOUNT_PLANS_MODAL_ID, DEFAULT_CONSUMPTION_TREND_PERIOD, MODAL_ID, createConsumptionHeader, createConsumptionTrendSection, loadPlanDetailForPanel, closePlanDetailPopover, createRatecardButton, closeRatecardDrawer, createSvgElement, createPlanDetails, getNestedValue, loadConsumptionTrendForPanel, environment }) { const { document, requestAnimationFrame, setTimeout, window } = environment; function openAccountPlansModal(opportunity, plans, state = "ready", errorMessage = "") { const overlay = document.getElementById(MODAL_ID); const optyNumber = String(opportunity.OptyNumber || ""); const existingModal = document.getElementById(ACCOUNT_PLANS_MODAL_ID); if (!overlay) { return; } if (state !== "loading" && ( !existingModal || existingModal.hidden || existingModal.dataset.optyNumber !== optyNumber )) { return; } closePlanDetailPopover(); closeRatecardDrawer({ immediate: true, restoreFocus: false }); existingModal?.remove(); const modal = document.createElement("section"); modal.id = ACCOUNT_PLANS_MODAL_ID; modal.className = "opportunities-extension-account-plans-modal"; modal.dataset.optyNumber = optyNumber; modal.dataset.customerLabel = getNestedValue(opportunity, ["CustomerAccount", "PartyUniqueName"]) || opportunity.Name || opportunity.OptyNumber || "Opportunity"; modal.setAttribute("role", "dialog"); modal.setAttribute("aria-modal", "true"); modal.setAttribute("aria-labelledby", "opportunities-extension-account-plans-title"); modal.setAttribute("aria-busy", String(state === "loading")); const dialog = document.createElement("div"); dialog.className = "opportunities-extension-account-plans-dialog"; dialog.setAttribute("data-consumption-layout", "compact"); const header = createConsumptionHeader({ titleId: "opportunities-extension-account-plans-title", customerLabel: modal.dataset.customerLabel, closeLabel: "Close cloud consumption", onClose: closeAccountPlansModal }); const content = document.createElement("div"); content.className = "opportunities-extension-account-plans-content"; if (state === "loading") { content.append(createAccountPlansSkeleton()); } else if (state === "error") { const error = document.createElement("p"); error.className = "opportunities-extension-account-plans-error"; error.setAttribute("role", "alert"); error.textContent = errorMessage || "Unable to load cloud consumption."; content.append(error); } else if (!Array.isArray(plans) || plans.length === 0) { const empty = document.createElement("p"); empty.className = "opportunities-extension-account-plans-empty"; empty.textContent = "No account plans found for this customer."; content.append(empty); } else { const tabList = document.createElement("div"); tabList.className = "opportunities-extension-account-plans-tabs"; tabList.setAttribute("role", "tablist"); tabList.setAttribute("aria-label", "Account plans"); const tabsNavigation = createAccountPlansTabsNavigation(tabList); const panels = document.createElement("div"); panels.className = "opportunities-extension-account-plans-panels"; plans.forEach((plan, index) => { const tabId = `opportunities-extension-account-plan-tab-${index}`; const panelId = `opportunities-extension-account-plan-panel-${index}`; const tab = document.createElement("button"); tab.id = tabId; tab.type = "button"; tab.className = "opportunities-extension-account-plan-tab"; tab.setAttribute("role", "tab"); tab.setAttribute("aria-controls", panelId); tab.setAttribute("aria-selected", String(index === 0)); tab.tabIndex = index === 0 ? 0 : -1; const planLabel = [plan.planType, plan.subPlanNum] .filter((value) => value !== null && value !== undefined && value !== "") .join(" ") || `Plan ${index + 1}`; const headerStatus = plan.hdrStatusCode === null || plan.hdrStatusCode === undefined || plan.hdrStatusCode === "" ? "Unknown" : String(plan.hdrStatusCode); tab.setAttribute("aria-label", `${planLabel}. Header Status: ${headerStatus}`); tab.append(createAccountPlanStatusIndicator(headerStatus), document.createTextNode(planLabel)); const panel = document.createElement("section"); panel.id = panelId; panel.className = "opportunities-extension-account-plan-panel"; panel.setAttribute("role", "tabpanel"); panel.setAttribute("aria-labelledby", tabId); panel.hidden = index !== 0; panel.append(createAccountPlanDetails(plan), createConsumptionTrendSection(plan)); tab.addEventListener("click", () => { closePlanDetailPopover(); closeRatecardDrawer({ immediate: true, restoreFocus: false }); tabList.querySelectorAll("[role='tab']").forEach((candidate) => { const isSelected = candidate === tab; candidate.setAttribute("aria-selected", String(isSelected)); candidate.tabIndex = isSelected ? 0 : -1; }); panels.querySelectorAll("[role='tabpanel']").forEach((candidate) => { candidate.hidden = candidate !== panel; }); const trendSection = panel.querySelector(".opportunities-extension-consumption-trend"); loadConsumptionTrendForPanel( panel, plan, trendSection ? trendSection.dataset.period : DEFAULT_CONSUMPTION_TREND_PERIOD ); loadPlanDetailForPanel(panel, plan); }); tabList.append(tab); panels.append(panel); }); content.append(tabsNavigation, panels); } dialog.append(header, content); modal.append(dialog); overlay.append(modal); modal.querySelector("[role='tab'], .opportunities-extension-icon-button")?.focus(); if (state === "ready" && Array.isArray(plans) && plans.length > 0) { const firstPanel = modal.querySelector("[role='tabpanel']"); if (firstPanel) { loadConsumptionTrendForPanel(firstPanel, plans[0], DEFAULT_CONSUMPTION_TREND_PERIOD); loadPlanDetailForPanel(firstPanel, plans[0]); } } } function createAccountPlansTabsNavigation(tabList) { const navigation = document.createElement("div"); navigation.className = "opportunities-extension-account-plans-tabs-navigation"; const previousButton = createAccountPlansTabNavigationButton("previous"); const nextButton = createAccountPlansTabNavigationButton("next"); const updateNavigation = () => { const maximumScroll = Math.max(0, tabList.scrollWidth - tabList.clientWidth); const tabs = Array.from(tabList.querySelectorAll("[role='tab']")); const viewportStart = tabList.scrollLeft; const viewportEnd = viewportStart + tabList.clientWidth; const hasContentBefore = tabList.scrollLeft > 1 || tabs.some((tab) => tab.offsetLeft < viewportStart - 1); const hasContentAfter = tabList.scrollLeft < maximumScroll - 1 || tabs.some((tab) => (tab.offsetLeft + tab.offsetWidth) > viewportEnd + 1); previousButton.hidden = !hasContentBefore; nextButton.hidden = !hasContentAfter; }; const scrollTabs = (direction) => { tabList.scrollBy({ left: direction * Math.max(160, Math.floor(tabList.clientWidth * .72)), behavior: "smooth" }); }; previousButton.addEventListener("click", () => scrollTabs(-1)); nextButton.addEventListener("click", () => scrollTabs(1)); tabList.addEventListener("scroll", updateNavigation, { passive: true }); const handleResize = () => { if (!navigation.isConnected) { window.removeEventListener("resize", handleResize); return; } updateNavigation(); }; window.addEventListener("resize", handleResize, { passive: true }); requestAnimationFrame(() => requestAnimationFrame(updateNavigation)); setTimeout(updateNavigation, 0); navigation.append(previousButton, tabList, nextButton); return navigation; } function createAccountPlansTabNavigationButton(direction) { const isPrevious = direction === "previous"; const button = document.createElement("button"); button.type = "button"; button.className = "opportunities-extension-account-plans-tab-navigation-button"; button.setAttribute("aria-label", isPrevious ? "Show previous account plans" : "Show next account plans"); button.title = isPrevious ? "Previous plans" : "Next plans"; const icon = createSvgElement("svg", { viewBox: "0 0 24 24", "aria-hidden": "true", focusable: "false" }); icon.append(createSvgElement("path", { d: isPrevious ? "m14.5 5-7 7 7 7" : "m9.5 5 7 7-7 7", fill: "none", stroke: "currentColor", "stroke-linecap": "round", "stroke-linejoin": "round", "stroke-width": "2" })); button.append(icon); return button; } function createAccountPlansSkeleton() { const skeleton = document.createElement("div"); skeleton.className = "opportunities-extension-account-plans-skeleton"; skeleton.setAttribute("role", "status"); const announcement = document.createElement("span"); announcement.className = "opportunities-extension-visually-hidden"; announcement.textContent = "Loading cloud consumption"; const tabs = document.createElement("div"); tabs.className = "opportunities-extension-account-plans-skeleton-tabs"; const tab = document.createElement("span"); tab.className = "opportunities-extension-account-plans-skeleton-tab"; const status = document.createElement("span"); status.className = "opportunities-extension-account-plans-skeleton-status"; const tabLabel = document.createElement("span"); tabLabel.className = "opportunities-extension-account-plans-skeleton-bar"; tabLabel.style.width = "104px"; tab.append(status, tabLabel); tabs.append(tab); const details = document.createElement("div"); details.className = "opportunities-extension-account-plans-skeleton-details"; [72, 88, 64, 78, 58, 68].forEach((width) => { const row = document.createElement("div"); const label = document.createElement("span"); const value = document.createElement("span"); label.className = "opportunities-extension-account-plans-skeleton-bar opportunities-extension-account-plans-skeleton-label"; value.className = "opportunities-extension-account-plans-skeleton-bar opportunities-extension-account-plans-skeleton-value"; value.style.width = `${width}%`; row.append(label, value); details.append(row); }); const trend = document.createElement("div"); trend.className = "opportunities-extension-account-plans-skeleton-trend"; const trendHeader = document.createElement("div"); trendHeader.className = "opportunities-extension-account-plans-skeleton-trend-header"; const periods = document.createElement("div"); periods.className = "opportunities-extension-account-plans-skeleton-periods"; [58, 52, 56, 38].forEach((width, index) => { const period = document.createElement("span"); period.className = "opportunities-extension-account-plans-skeleton-bar"; period.style.width = `${width}px`; if (index === 2) period.classList.add("opportunities-extension-account-plans-skeleton-period-active"); periods.append(period); }); trendHeader.append(periods); const navigation = document.createElement("div"); navigation.className = "opportunities-extension-account-plans-skeleton-navigation"; [32, 124, 32].forEach((width) => { const item = document.createElement("span"); item.className = "opportunities-extension-account-plans-skeleton-bar"; item.style.width = `${width}px`; navigation.append(item); }); const chart = document.createElement("div"); chart.className = "opportunities-extension-account-plans-skeleton-chart"; const plot = document.createElement("div"); plot.className = "opportunities-extension-account-plans-skeleton-plot"; [34, 42, 38, 48, 44, 58, 64, 55, 76, 68, 86, 62].forEach((height) => { const bar = document.createElement("span"); bar.className = "opportunities-extension-account-plans-skeleton-bar"; bar.style.height = `${height}%`; plot.append(bar); }); const legend = document.createElement("div"); legend.className = "opportunities-extension-account-plans-skeleton-legend"; [112, 68, 132].forEach((width) => { const item = document.createElement("span"); item.className = "opportunities-extension-account-plans-skeleton-bar"; item.style.width = `${width}px`; legend.append(item); }); chart.append(plot, legend); trend.append(trendHeader, navigation, chart); const panel = document.createElement("div"); panel.className = "opportunities-extension-account-plan-panel opportunities-extension-account-plans-skeleton-panel"; panel.append(details, trend); skeleton.append(announcement, tabs, panel); return skeleton; } function createAccountPlanStatusIndicator(statusValue) { const indicator = document.createElement("span"); const isActive = String(statusValue).toUpperCase() === "ACTIVE"; indicator.className = "opportunities-extension-account-plan-status-indicator"; indicator.dataset.active = String(isActive); indicator.setAttribute("aria-hidden", "true"); indicator.title = `Header Status: ${statusValue}`; return indicator; } function createAccountPlanDetails(plan) { return createPlanDetails(plan, { contractAction: createRatecardButton(plan) }); } function closeAccountPlansModal() { closePlanDetailPopover(); closeRatecardDrawer({ immediate: true, restoreFocus: false }); const modal = document.getElementById(ACCOUNT_PLANS_MODAL_ID); if (modal) { modal.hidden = true; } } return { openAccountPlansModal, createAccountPlansTabsNavigation, createAccountPlansTabNavigationButton, createAccountPlansSkeleton, createAccountPlanStatusIndicator, createAccountPlanDetails, closeAccountPlansModal }; } // Source: src/features/consumption/header-view.js function createConsumptionHeaderView({ createCloseIcon, formatOracleResponseDate, environment }) { const { document } = environment; function createHeader({ titleId, customerLabel, closeLabel, onClose, titleText = "Cloud Consumption" }) { const header = document.createElement("header"); header.className = "opportunities-extension-account-plans-header"; const heading = document.createElement("div"); const title = document.createElement("h2"); title.id = titleId; title.textContent = titleText; const subtitle = document.createElement("p"); subtitle.textContent = customerLabel; heading.append(title, subtitle); const closeButton = document.createElement("button"); closeButton.type = "button"; closeButton.className = "opportunities-extension-icon-button"; closeButton.setAttribute("aria-label", closeLabel); closeButton.title = "Close"; closeButton.append(createCloseIcon()); closeButton.addEventListener("click", onClose); header.append(heading, closeButton); return header; } function createDetails(plan, { contractAction = null } = {}) { const details = document.createElement("dl"); details.className = "opportunities-extension-account-plan-details"; const fields = [ { label: "Booking Customer", value: plan.bookingCustomer }, { label: "Sold To Party Name", value: plan.soldToPartyName }, { label: "Contract ID", value: plan.contractId }, { label: "Header Status", value: plan.hdrStatusCode }, { label: "Start Date", value: formatOracleResponseDate(plan.startDate) }, { label: "End Date", value: formatOracleResponseDate(plan.endDate) } ]; fields.forEach(field => { const group = document.createElement("div"); const term = document.createElement("dt"); const description = document.createElement("dd"); term.textContent = field.label; description.textContent = field.value === null || field.value === undefined || field.value === "" ? "-" : String(field.value); if (field.label === "Contract ID" && contractAction) { group.className = "opportunities-extension-contract-row"; const value = document.createElement("span"); value.textContent = description.textContent; description.textContent = ""; description.className = "opportunities-extension-contract-ratecard"; description.append(value, contractAction); } group.append(term, description); details.append(group); }); return details; } return { createHeader, createDetails }; } // Source: src/features/ratecard/model.js function createRatecardModel() { const columns = [ { key: "orderNumber", label: "Order Number", width: 7 }, { key: "partNum", label: "Part Number", width: 6 }, { key: "productName", label: "Product Name", width: 22 }, { key: "uom", label: "UOM", width: 10 }, { key: "subLnStartSt", label: "Start Date", type: "date", width: 8 }, { key: "subLnEndDt", label: "End Date", type: "date", width: 8 }, { key: "usageMax", label: "Usage Max", type: "number", width: 5 }, { key: "rateCardDiscountPerc", label: "Discount (%)", type: "percent", width: 7 }, { key: "listPriceUsd", label: "List price (USD)", type: "price", width: 9 }, { key: "netUnitPriceUsd", label: "Net Unit Price (USD)", type: "price", width: 9 }, { key: "overagePriceUsd", label: "Overage Net Unit Price (USD)", type: "price", width: 9 } ]; const collator = new Intl.Collator("en", { numeric: true, sensitivity: "base" }); const prices = new Intl.NumberFormat("en-US", { minimumFractionDigits: 4, maximumFractionDigits: 20 }); const numbers = new Intl.NumberFormat("en-US", { maximumFractionDigits: 20 }); const months = ["jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"]; const missing = value => value === null || value === undefined || String(value).trim() === "" || String(value).trim() === "-"; const numeric = value => !missing(value) && Number.isFinite(Number(value)) ? Number(value) : null; const normalize = value => String(value ?? "").normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().trim(); function formatValue(item, column) { const value = item[column.key]; if (missing(value)) return "-"; const number = numeric(value); if (number !== null) { if (column.type === "price") return prices.format(number); if (column.type === "percent") return `${numbers.format(number)}%`; if (column.type === "number") return numbers.format(number); } return String(value).trim(); } function dateValue(value) { const match = String(value).match(/^(\d{1,2})-([a-z]{3})-(\d{4})$/i); if (match) { const month = months.indexOf(match[2].toLowerCase()); return month < 0 ? null : Date.UTC(Number(match[3]), month, Number(match[1])); } const iso = String(value).match(/^(\d{4})-(\d{2})-(\d{2})(?:T.*)?$/); return iso ? Date.UTC(Number(iso[1]), Number(iso[2]) - 1, Number(iso[3])) : null; } function selectItems(items, search = "", sortKey = "", direction = "asc") { const query = normalize(search); const filtered = items.filter(item => !query || columns.some(column => normalize(item[column.key]).includes(query) || normalize(formatValue(item, column)).includes(query))); const column = columns.find(candidate => candidate.key === sortKey); if (!column) return filtered; const sortValue = item => { const value = item[column.key]; if (missing(value)) return null; if (column.type === "date") return dateValue(value); return ["price", "number", "percent"].includes(column.type) ? numeric(value) : String(value); }; return filtered.sort((a, b) => { const left = sortValue(a), right = sortValue(b); if (left === null || right === null) return left === right ? 0 : left === null ? 1 : -1; const compared = typeof left === "number" ? left - right : collator.compare(left, right); return direction === "desc" ? -compared : compared; }); } return { columns, formatValue, selectItems }; } // Source: src/features/ratecard/controller.js function createRatecardController({ messageTypes, requestConsumerTokenServiceOnce, sendRuntimeMessage, appendBackgroundRequestLog, openRatecardDrawer, closePlanDetailPopover, environment }) { const pending = new Map(); async function request(planNumber) { const authorizations = await requestConsumerTokenServiceOnce(); const response = await sendRuntimeMessage({ type: messageTypes.requestRatecardByPlan, planNumber, authorization: authorizations && authorizations.accountPlans }); appendBackgroundRequestLog(response?.debugRequests); if (!response?.ok) throw new Error(response?.error || "Unable to load rate card."); return response.items; } async function showRatecard(plan, trigger) { closePlanDetailPopover(); const handle = openRatecardDrawer(plan, trigger); if (!handle) return; try { const planNumber = String(plan.subPlanNum ?? "").trim(); if (!planNumber) throw new Error("Plan number is unavailable for this account plan."); if (!pending.has(planNumber)) { const promise = request(planNumber).finally(() => { if (pending.get(planNumber) === promise) pending.delete(planNumber); }); pending.set(planNumber, promise); } handle.showItems(await pending.get(planNumber)); } catch (error) { handle.showError(error.message || "Unable to load rate card."); } } function createRatecardButton(plan) { const button = environment.document.createElement("button"); button.type = "button"; button.className = "opportunities-extension-ratecard-button"; button.textContent = "Rate Card"; button.title = "View rate card"; button.setAttribute("aria-haspopup", "dialog"); button.addEventListener("click", () => showRatecard(plan, button)); return button; } return { createRatecardButton, showRatecard, resetRequests: () => pending.clear() }; } // Source: src/features/ratecard/view.js function createRatecardView({ ACCOUNT_PLANS_MODAL_ID, columns, formatValue, selectItems, createConsumptionHeader, createPlanDetails, environment }) { const { document, setTimeout, clearTimeout, requestAnimationFrame, cancelAnimationFrame } = environment; let active = null; let closing = null; let closeTimer = null; function closeRatecardDrawer({ immediate = false, restoreFocus = true } = {}) { if (closeTimer !== null) clearTimeout(closeTimer); closeTimer = null; closing?.remove(); closing = null; if (!active) return; const previous = active; active = null; if (previous.openFrame !== null) cancelAnimationFrame(previous.openFrame); previous.background.inert = previous.wasInert; if (immediate) previous.root.remove(); else { closing = previous.root; closing.dataset.closing = "true"; closing.inert = true; closeTimer = setTimeout(() => { closing?.remove(); closing = null; closeTimer = null; }, 200); } if (restoreFocus && previous.trigger?.isConnected) previous.trigger.focus({ preventScroll: true }); } function openRatecardDrawer(plan, trigger) { const parent = document.getElementById(ACCOUNT_PLANS_MODAL_ID); const background = parent?.querySelector(".opportunities-extension-account-plans-dialog"); if (!parent || parent.hidden || !background) return null; closeRatecardDrawer({ immediate: true, restoreFocus: false }); const element = (tag, className, text) => { const node = document.createElement(tag); if (className) node.className = `opportunities-extension-ratecard-${className}`; if (text !== undefined) node.textContent = text; return node; }; const root = element("div", "overlay"); root.dataset.open = "false"; const panel = element("section", "drawer"); panel.setAttribute("role", "dialog"); panel.setAttribute("aria-modal", "true"); panel.setAttribute("aria-labelledby", "opportunities-extension-ratecard-title"); panel.setAttribute("aria-busy", "true"); const header = createConsumptionHeader({ titleId: "opportunities-extension-ratecard-title", titleText: "Rate Card", customerLabel: parent.dataset.customerLabel, closeLabel: "Close rate card", onClose: () => closeRatecardDrawer() }); header.classList.add("opportunities-extension-ratecard-header"); const close = header.querySelector("button"); const details = createPlanDetails(plan); details.classList.add("opportunities-extension-ratecard-plan-details"); const toolbar = element("div", "toolbar"); const search = element("input", "search"); search.type = "search"; search.placeholder = "Search rate card"; search.setAttribute("aria-label", "Search rate card"); const count = element("span", "count", "Loading rate card..."); count.setAttribute("role", "status"); toolbar.append(search, count); const viewport = element("div", "viewport"); viewport.tabIndex = 0; viewport.setAttribute("role", "region"); viewport.setAttribute("aria-label", "Rate card items"); const table = element("table", "table"); const caption = element("caption", null, "Rate card items"); caption.className = "opportunities-extension-visually-hidden"; const colgroup = element("colgroup"); const head = element("thead"); const headRow = element("tr"); const sortHeaders = []; const current = { root, background, wasInert: background.inert, trigger, items: [], status: "loading", sortKey: "", direction: "asc", openFrame: null }; active = current; background.inert = true; const alive = () => active === current && root.isConnected && !parent.hidden; columns.forEach(column => { const col = element("col"); col.style.width = `${column.width}%`; colgroup.append(col); const th = element("th"); th.scope = "col"; th.setAttribute("aria-sort", "none"); const sort = element("button", "sort"); sort.type = "button"; sort.title = `Sort by ${column.label}`; const label = element("span", null, column.label); const icon = element("span", "sort-icon", "\u2195"); icon.setAttribute("aria-hidden", "true"); sort.append(label, icon); sort.addEventListener("click", () => { current.direction = current.sortKey === column.key && current.direction === "asc" ? "desc" : "asc"; current.sortKey = column.key; updateSortHeaders(); if (current.status === "ready") renderRows(); }); sortHeaders.push({ th, icon, key: column.key }); th.append(sort); headRow.append(th); }); head.append(headRow); const body = element("tbody"); table.append(caption, colgroup, head, body); viewport.append(table); panel.append(header, details, toolbar, viewport); root.append(panel); parent.append(root); function updateSortHeaders() { sortHeaders.forEach(({ th, icon, key }) => { const selected = key === current.sortKey; th.setAttribute("aria-sort", selected ? current.direction === "asc" ? "ascending" : "descending" : "none"); icon.textContent = !selected ? "\u2195" : current.direction === "asc" ? "\u2191" : "\u2193"; }); } function showNotice(message, error = false) { body.textContent = ""; const row = element("tr"); const cell = element("td", "notice"); cell.colSpan = columns.length; const text = element("p", null, message); if (error) text.setAttribute("role", "alert"); cell.append(text); row.append(cell); body.append(row); } function renderRows() { if (!alive()) return; body.textContent = ""; const visible = selectItems(current.items, search.value, current.sortKey, current.direction); count.textContent = `${visible.length} of ${current.items.length} items`; if (!visible.length) { showNotice(current.items.length ? "No matching rate card items." : "No rate card items found for this plan."); } visible.forEach(item => { const row = element("tr"); columns.forEach(column => { const cell = element("td", null, formatValue(item, column)); if (["price", "number", "percent"].includes(column.type)) cell.className = "opportunities-extension-ratecard-numeric"; row.append(cell); }); body.append(row); }); viewport.scrollTop = 0; } for (let index = 0; index < 7; index += 1) { const row = element("tr"); row.setAttribute("aria-hidden", "true"); columns.forEach(() => { const cell = element("td"); const skeleton = element("span", "skeleton"); cell.append(skeleton); row.append(cell); }); body.append(row); } search.addEventListener("input", () => { if (current.status === "ready") renderRows(); }); root.addEventListener("click", event => { if (event.target === root) closeRatecardDrawer(); }); root.addEventListener("keydown", event => { if (event.key === "Escape") { event.preventDefault(); event.stopPropagation(); closeRatecardDrawer(); } else if (event.key === "Tab") { const controls = [close, search, viewport, ...headRow.querySelectorAll("button")]; const index = controls.indexOf(document.activeElement); if (index < 0 || (event.shiftKey && index === 0) || (!event.shiftKey && index === controls.length - 1)) { event.preventDefault(); controls[event.shiftKey ? controls.length - 1 : 0].focus({ preventScroll: true }); } } }); // Focusing offscreen content must not scroll the clipped overlay and cancel the slide. search.focus({ preventScroll: true }); // Paint the offscreen position before transitioning to the fully open position. current.openFrame = requestAnimationFrame(() => { if (!alive()) return; current.openFrame = requestAnimationFrame(() => { current.openFrame = null; if (alive()) root.dataset.open = "true"; }); }); return { showItems(items) { if (!alive()) return; current.items = items; current.status = "ready"; panel.setAttribute("aria-busy", "false"); renderRows(); }, showError(message) { if (!alive()) return; current.status = "error"; panel.setAttribute("aria-busy", "false"); count.textContent = "Rate card unavailable"; showNotice(message, true); } }; } return { openRatecardDrawer, closeRatecardDrawer }; } // Source: src/features/consumption/plan-detail-controller.js function createConsumptionPlanDetailController({ messageTypes, requestConsumerTokenServiceOnce, sendRuntimeMessage, appendBackgroundRequestLog, renderPlanDetailBadges, environment }) { const { document } = environment; const states = new WeakMap(); const pending = new Map(); function createPlanDetailSection(plan) { const section = document.createElement("div"); section.className = "opportunities-extension-plan-detail-badges"; section.dataset.subPlanNum = String(plan.subPlanNum || "").trim(); section.setAttribute("aria-live", "polite"); states.set(section, { status: "idle" }); return section; } async function request(subPlanNum) { const authorizations = await requestConsumerTokenServiceOnce(); const response = await sendRuntimeMessage({ type: messageTypes.requestConsumptionPlanDetail, subPlanNum, authorization: authorizations && authorizations.accountPlans }); appendBackgroundRequestLog(response?.debugRequests); if (!response?.ok) throw new Error(response?.error || "Unable to load consumption plan details."); return response.items; } function loadPlanDetailForPanel(panel, plan) { const section = panel?.querySelector(".opportunities-extension-plan-detail-badges"); if (!section) return Promise.resolve(); const state = states.get(section); if (state?.status === "ready") return Promise.resolve(); if (state?.status === "loading") return state.promise; const subPlanNum = String(plan.subPlanNum || "").trim(); section.textContent = ""; section.setAttribute("aria-busy", "true"); const skeleton = document.createElement("span"); skeleton.className = "opportunities-extension-account-plans-skeleton-bar opportunities-extension-plan-detail-skeleton"; skeleton.setAttribute("role", "status"); skeleton.setAttribute("aria-label", "Loading plan projection"); section.append(skeleton); const current = { status: "loading", promise: null }; states.set(section, current); current.promise = (async () => { try { if (!subPlanNum) throw new Error("Sub plan number is unavailable."); if (!pending.has(subPlanNum)) { const promise = request(subPlanNum).finally(() => { if (pending.get(subPlanNum) === promise) pending.delete(subPlanNum); }); pending.set(subPlanNum, promise); } const items = await pending.get(subPlanNum); if (!section.isConnected || states.get(section) !== current) return; current.status = "ready"; renderPlanDetailBadges(section, items); } catch (error) { if (!section.isConnected || states.get(section) !== current) return; current.status = "error"; section.textContent = ""; const notice = document.createElement("span"); notice.className = "opportunities-extension-plan-detail-error"; notice.setAttribute("role", "alert"); notice.textContent = error.message || "Unable to load plan details."; section.append(notice); } finally { if (states.get(section) === current) section.setAttribute("aria-busy", "false"); } })(); return current.promise; } return { createPlanDetailSection, loadPlanDetailForPanel, resetRequests: () => pending.clear() }; } // Source: src/features/consumption/plan-detail-view.js function createConsumptionPlanDetailView({ createCloseIcon, environment }) { const { document, window, setTimeout, clearTimeout } = environment; let active = null; let sequence = 0; const fields = [ ["pricingModel", "Pricing model"], ["lineStatus", "Line status"], ["startDate", "Start date"], ["endDate", "End date"], ["committedCredits", "Committed credits", true], ["consumed", "Consumed", true], ["consumptionL30Days", "Consumption last 30 days", true], ["monthsRemaining", "Months remaining"], ["projectedConsumption", "Projected consumption", true], ["projectedDelta", "Projected delta", true], ["orderId", "Order number"] ]; function node(tag, className, text) { const result = document.createElement(tag); if (className) result.className = className; if (text !== undefined) result.textContent = text; return result; } function statusBadge(record, tag = "span") { const text = String(record.projectedStatus || "Unknown"); const badge = node(tag, "opportunities-extension-projection-badge", text); badge.dataset.status = text.trim().toLowerCase(); return badge; } function orderLink(record, text) { try { const url = new URL(record.orderTrackerURL); if (url.origin !== "https://accountdashboard.oracle.com" || url.username || url.password) return null; const link = node("a", "", text); link.href = url.href; link.target = "_blank"; link.rel = "noopener noreferrer"; return link; } catch { return null; } } function createLineDetails(record) { const section = node("section", "opportunities-extension-projection-line"); const heading = node("h3"); heading.append(statusBadge(record)); section.append(heading); const details = node("dl", "opportunities-extension-projection-fields"); fields.forEach(([key, label, numeric]) => { const raw = record[key]; let text = raw === null || raw === undefined || raw === "" ? "-" : String(raw); if (numeric && text !== "-" && Number.isFinite(Number(raw))) { text = new Intl.NumberFormat("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(Number(raw)); } const group = node("div"); const value = node("dd"); const link = key === "orderId" && text !== "-" ? orderLink(record, text) : null; if (link) value.append(link); else value.textContent = text; group.append(node("dt", "", label), value); details.append(group); }); section.append(details); if (Array.isArray(record.clines) && record.clines.length) { const children = node("div", "opportunities-extension-projection-children"); record.clines.forEach(child => children.append(createLineDetails(child))); section.append(children); } return section; } function closePlanDetailPopover(restoreFocus = false) { if (!active) return; const previous = active; active = null; clearTimeout(previous.timer); previous.popover.hidden = true; previous.button.setAttribute("aria-expanded", "false"); document.removeEventListener("pointerdown", previous.outside, true); window.removeEventListener("resize", previous.resize); if (restoreFocus && previous.button.isConnected) { previous.suppressFocus = true; previous.button.focus(); previous.suppressFocus = false; } } function positionPopover(entry) { const rect = entry.button.getBoundingClientRect(); const viewportWidth = window.innerWidth; const viewportHeight = window.innerHeight; const width = Math.min(560, viewportWidth - 24); const height = Math.min(520, viewportHeight - 24); entry.popover.style.width = `${width}px`; entry.popover.style.maxHeight = `${height}px`; const below = viewportHeight - rect.bottom - 12; const actualHeight = Math.min(entry.popover.getBoundingClientRect().height, height); entry.popover.style.left = `${Math.max(12, Math.min(rect.left, viewportWidth - width - 12))}px`; entry.popover.style.top = `${Math.max(12, below >= actualHeight ? rect.bottom + 4 : Math.min(rect.top - actualHeight - 4, viewportHeight - actualHeight - 12))}px`; } function createInteractiveBadge(record) { const wrapper = node("div", "opportunities-extension-projection-wrapper"); const button = statusBadge(record, "button"); button.type = "button"; button.setAttribute("aria-label", `Projected status: ${button.textContent}. View plan details`); button.setAttribute("aria-haspopup", "dialog"); button.setAttribute("aria-expanded", "false"); const popover = node("div", "opportunities-extension-projection-popover"); popover.id = `opportunities-extension-projection-${++sequence}`; popover.hidden = true; popover.setAttribute("role", "dialog"); popover.setAttribute("aria-label", "Consumption plan details"); popover.tabIndex = -1; button.setAttribute("aria-controls", popover.id); const dismiss = node("button", "opportunities-extension-projection-dismiss"); dismiss.type = "button"; dismiss.setAttribute("aria-label", "Close plan details"); dismiss.title = "Close plan details"; dismiss.append(createCloseIcon()); dismiss.addEventListener("click", () => closePlanDetailPopover(true)); popover.append(dismiss, createLineDetails(record)); const entry = { button, popover, wrapper, pinned: false, timer: null, suppressFocus: false, outside: event => { if (!wrapper.contains(event.target)) closePlanDetailPopover(); }, resize: () => positionPopover(entry) }; function open() { if (active !== entry) { closePlanDetailPopover(); entry.pinned = false; active = entry; document.addEventListener("pointerdown", entry.outside, true); window.addEventListener("resize", entry.resize); } clearTimeout(entry.timer); popover.hidden = false; button.setAttribute("aria-expanded", "true"); positionPopover(entry); } wrapper.addEventListener("pointerenter", open); wrapper.addEventListener("pointerleave", () => { if (entry.pinned || wrapper.contains(document.activeElement)) return; entry.timer = setTimeout(() => { if (active === entry) closePlanDetailPopover(); }, 180); }); wrapper.addEventListener("focusin", () => { if (!entry.suppressFocus) open(); }); wrapper.addEventListener("focusout", event => { if (!wrapper.contains(event.relatedTarget) && active === entry) closePlanDetailPopover(); }); button.addEventListener("click", () => { if (active === entry && entry.pinned) closePlanDetailPopover(); else { open(); entry.pinned = true; } }); wrapper.addEventListener("keydown", event => { if (event.key === "Escape" && active === entry) { event.preventDefault(); event.stopPropagation(); closePlanDetailPopover(true); } }); wrapper.append(button, popover); return wrapper; } function renderPlanDetailBadges(section, items) { if (active && section.contains(active.button)) closePlanDetailPopover(); section.textContent = ""; if (!items.length) { section.append(node("span", "opportunities-extension-projection-empty", "No projection data")); return; } items.forEach(record => section.append(createInteractiveBadge(record))); } return { renderPlanDetailBadges, createLineDetails, closePlanDetailPopover }; } // Source: src/features/consumption/trend-controller.js function createConsumptionTrendController({ messageTypes, CONSUMPTION_TREND_PERIODS, DEFAULT_CONSUMPTION_TREND_PERIOD, appendBackgroundRequestLog, consumptionTrendState, createPlanDetailSection, renderConsumptionTrendChart, requestConsumerTokenServiceOnce, sendRuntimeMessage, environment }) { const { document } = environment; function createConsumptionTrendSection(plan) { const section = document.createElement("section"); section.className = "opportunities-extension-consumption-trend"; section.dataset.subPlanNum = String(plan.subPlanNum || ""); section.dataset.period = DEFAULT_CONSUMPTION_TREND_PERIOD; const header = document.createElement("header"); header.className = "opportunities-extension-consumption-trend-header"; const periodControls = document.createElement("div"); periodControls.className = "opportunities-extension-consumption-periods"; periodControls.setAttribute("role", "group"); periodControls.setAttribute("aria-label", "Consumption period"); CONSUMPTION_TREND_PERIODS.forEach((periodOption) => { const button = document.createElement("button"); button.type = "button"; button.dataset.period = periodOption.value; button.setAttribute("aria-pressed", String(periodOption.value === DEFAULT_CONSUMPTION_TREND_PERIOD)); button.textContent = periodOption.label; button.addEventListener("click", () => { section.dataset.period = periodOption.value; consumptionTrendState.consumptionTrendWindowOffsets.set( createConsumptionTrendKey(section.dataset.subPlanNum, periodOption.value), 0 ); loadConsumptionTrendForPanel(section.closest("[role='tabpanel']"), plan, periodOption.value); }); periodControls.append(button); }); const chart = document.createElement("div"); chart.className = "opportunities-extension-consumption-chart"; chart.setAttribute("aria-live", "polite"); header.append(createPlanDetailSection(plan), periodControls); section.append(header, chart); return section; } function loadConsumptionTrendForPanel(panel, plan, period) { if (!panel) { return; } const section = panel.querySelector(".opportunities-extension-consumption-trend"); if (!section) { return; } const subPlanNum = String(plan.subPlanNum || "").trim(); const selectedPeriod = CONSUMPTION_TREND_PERIODS.some((item) => item.value === period) ? period : DEFAULT_CONSUMPTION_TREND_PERIOD; const key = createConsumptionTrendKey(subPlanNum, selectedPeriod); const chart = section.querySelector(".opportunities-extension-consumption-chart"); section.dataset.period = selectedPeriod; chart.dataset.requestKey = key; updateConsumptionPeriodButtons(section, selectedPeriod); if (!subPlanNum) { renderConsumptionTrendError(chart, "Sub plan number is unavailable."); return; } const cached = consumptionTrendState.consumptionTrendCache.get(key); renderConsumptionTrendSkeleton(chart); if (cached && cached.status === "loading") { cached.promise .then((items) => renderConsumptionTrendIfCurrent(section, chart, items, selectedPeriod, subPlanNum, key)) .catch((error) => renderConsumptionTrendErrorIfCurrent(chart, error.message, key)); return; } const request = requestConsumerTokenServiceOnce() .then((authorizations) => sendRuntimeMessage({ type: messageTypes.requestConsumptionTrend, subPlanNum, period: selectedPeriod, authorization: authorizations && authorizations.accountPlans })) .then((response) => { appendBackgroundRequestLog(response && response.debugRequests); if (!response || !response.ok) { throw new Error(response && response.error ? response.error : "Unable to load consumption trend."); } return Array.isArray(response.items) ? response.items : []; }) .finally(() => { if (consumptionTrendState.consumptionTrendCache.get(key)?.promise === request) { consumptionTrendState.consumptionTrendCache.delete(key); } }); consumptionTrendState.consumptionTrendCache.set(key, { status: "loading", promise: request }); request .then((items) => renderConsumptionTrendIfCurrent(section, chart, items, selectedPeriod, subPlanNum, key)) .catch((error) => renderConsumptionTrendErrorIfCurrent(chart, error.message, key)); } function createConsumptionTrendKey(subPlanNum, period) { return `${subPlanNum}:${period}`; } function updateConsumptionPeriodButtons(section, period) { section.querySelectorAll(".opportunities-extension-consumption-periods button").forEach((button) => { button.setAttribute("aria-pressed", String(button.dataset.period === period)); }); } function renderConsumptionTrendIfCurrent(section, chart, items, period, subPlanNum, key) { if (chart.dataset.requestKey === key) { renderConsumptionTrendChart(section, chart, items, period, subPlanNum); } } function renderConsumptionTrendErrorIfCurrent(chart, errorMessage, key) { if (chart.dataset.requestKey === key) { renderConsumptionTrendError(chart, errorMessage || "Unable to load consumption trend."); } } function renderConsumptionTrendSkeleton(chart) { chart.textContent = ""; chart.setAttribute("aria-busy", "true"); const skeleton = document.createElement("div"); skeleton.className = "opportunities-extension-consumption-chart-skeleton"; const announcement = document.createElement("span"); announcement.className = "opportunities-extension-visually-hidden"; announcement.textContent = "Loading consumption trend"; const plot = document.createElement("div"); plot.className = "opportunities-extension-consumption-chart-skeleton-plot"; for (let index = 0; index < 13; index += 1) { const bar = document.createElement("span"); bar.style.height = `${28 + ((index * 17) % 58)}%`; plot.append(bar); } skeleton.append(announcement, plot); chart.append(skeleton); } function renderConsumptionTrendError(chart, errorMessage) { chart.textContent = ""; chart.setAttribute("aria-busy", "false"); const error = document.createElement("p"); error.className = "opportunities-extension-consumption-chart-error"; error.setAttribute("role", "alert"); error.textContent = errorMessage; chart.append(error); } return { createConsumptionTrendSection, loadConsumptionTrendForPanel, createConsumptionTrendKey, updateConsumptionPeriodButtons, renderConsumptionTrendIfCurrent, renderConsumptionTrendErrorIfCurrent, renderConsumptionTrendSkeleton, renderConsumptionTrendError }; } // Source: src/features/consumption/chart.js function createConsumptionChart({ CONSUMPTION_TREND_PERIODS, CONSUMPTION_TREND_SERIES, SVG_NAMESPACE, calculateAverageConsumption, consumptionTrendState, createConsumptionTrendKey, createNiceConsumptionMaximum, formatCompactConsumptionNumber, formatConsumptionCurrency, formatConsumptionPointLabel, formatConsumptionRange, normalizeConsumptionTrendItems, environment }) { const { document, window } = environment; function renderConsumptionTrendChart(section, chart, items, period, subPlanNum) { chart.textContent = ""; chart.setAttribute("aria-busy", "false"); const points = normalizeConsumptionTrendItems(items); if (points.length === 0) { const empty = document.createElement("p"); empty.className = "opportunities-extension-consumption-chart-empty"; empty.textContent = "No consumption data found for this period."; chart.append(empty); return; } const option = CONSUMPTION_TREND_PERIODS.find((item) => item.value === period); const windowSize = option ? option.windowSize : points.length; const offsetKey = createConsumptionTrendKey(subPlanNum, period); const maxOffset = Math.max(0, Math.ceil(points.length / windowSize) - 1); const offset = Math.min(consumptionTrendState.consumptionTrendWindowOffsets.get(offsetKey) || 0, maxOffset); const end = Math.max(0, points.length - (offset * windowSize)); const start = Math.max(0, end - windowSize); const visiblePoints = points.slice(start, end); consumptionTrendState.consumptionTrendWindowOffsets.set(offsetKey, offset); const navigation = document.createElement("div"); navigation.className = "opportunities-extension-consumption-navigation"; const olderButton = createConsumptionNavigationButton("older", start === 0); const newerButton = createConsumptionNavigationButton("newer", end === points.length); const range = document.createElement("strong"); range.textContent = formatConsumptionRange(visiblePoints, period); olderButton.addEventListener("click", () => { consumptionTrendState.consumptionTrendWindowOffsets.set(offsetKey, Math.min(maxOffset, offset + 1)); renderConsumptionTrendChart(section, chart, items, period, subPlanNum); }); newerButton.addEventListener("click", () => { consumptionTrendState.consumptionTrendWindowOffsets.set(offsetKey, Math.max(0, offset - 1)); renderConsumptionTrendChart(section, chart, items, period, subPlanNum); }); navigation.append(olderButton, range, newerButton); const figure = document.createElement("figure"); figure.className = "opportunities-extension-consumption-figure"; const visual = document.createElement("div"); visual.className = "opportunities-extension-consumption-visual"; const tooltip = document.createElement("div"); tooltip.className = "opportunities-extension-consumption-tooltip"; tooltip.hidden = true; tooltip.setAttribute("role", "tooltip"); const activeSeries = getConsumptionTrendSeriesFilter(subPlanNum); const svg = createConsumptionSvg( visiblePoints, period, end === points.length, activeSeries, tooltip ); const legend = createConsumptionLegend( section, chart, items, period, subPlanNum, activeSeries ); visual.append(svg, legend); figure.append(visual, tooltip); chart.append(navigation, figure); } function getConsumptionTrendSeriesFilter(subPlanNum) { if (!consumptionTrendState.consumptionTrendSeriesFilters.has(subPlanNum)) { consumptionTrendState.consumptionTrendSeriesFilters.set( subPlanNum, new Set(CONSUMPTION_TREND_SERIES.map((series) => series.value)) ); } return consumptionTrendState.consumptionTrendSeriesFilters.get(subPlanNum); } function createConsumptionLegend(section, chart, items, period, subPlanNum, activeSeries) { const legend = document.createElement("div"); legend.className = "opportunities-extension-consumption-legend"; legend.setAttribute("role", "group"); legend.setAttribute("aria-label", "Chart series filters"); CONSUMPTION_TREND_SERIES.forEach((series) => { const button = document.createElement("button"); const swatch = document.createElement("span"); const label = document.createElement("span"); button.type = "button"; button.dataset.series = series.value; button.setAttribute("aria-pressed", String(activeSeries.has(series.value))); button.setAttribute("aria-label", `${activeSeries.has(series.value) ? "Hide" : "Show"} ${series.label}`); swatch.className = `opportunities-extension-consumption-legend-swatch opportunities-extension-consumption-legend-${series.value}`; swatch.setAttribute("aria-hidden", "true"); label.textContent = series.label; button.append(swatch, label); button.addEventListener("click", () => { if (activeSeries.has(series.value)) { activeSeries.delete(series.value); } else { activeSeries.add(series.value); } renderConsumptionTrendChart(section, chart, items, period, subPlanNum); chart.querySelector(`[data-series="${series.value}"]`)?.focus(); }); legend.append(button); }); return legend; } function createConsumptionNavigationButton(direction, disabled) { const button = document.createElement("button"); button.type = "button"; button.className = "opportunities-extension-consumption-navigation-button"; button.disabled = disabled; button.setAttribute("aria-label", direction === "older" ? "Show older consumption data" : "Show newer consumption data"); button.title = direction === "older" ? "Older" : "Newer"; const icon = createSvgElement("svg", { viewBox: "0 0 24 24", "aria-hidden": "true", focusable: "false" }); icon.append(createSvgElement("path", { d: direction === "older" ? "m14.5 5-7 7 7 7" : "m9.5 5 7 7-7 7", fill: "none", stroke: "currentColor", "stroke-linecap": "round", "stroke-linejoin": "round", "stroke-width": "2" })); button.append(icon); return button; } function createConsumptionSvg(points, period, hasPartialLatestPoint, activeSeries, tooltip) { const width = 1000; const height = 350; const plot = { left: 62, top: 24, right: 980, bottom: 258 }; const plotWidth = plot.right - plot.left; const plotHeight = plot.bottom - plot.top; const average = calculateAverageConsumption(points, hasPartialLatestPoint, activeSeries); const maximum = createNiceConsumptionMaximum(Math.max(average, ...points.map((point) => point.total))); const svg = createSvgElement("svg", { viewBox: `0 0 ${width} ${height}`, role: "img", "aria-label": `${period} cloud consumption chart` }); svg.classList.add("opportunities-extension-consumption-svg"); for (let tick = 0; tick <= 5; tick += 1) { const value = (maximum / 5) * tick; const y = plot.bottom - ((value / maximum) * plotHeight); svg.append( createSvgElement("line", { x1: plot.left, y1: y, x2: plot.right, y2: y, class: "consumption-grid-line" }), createSvgText(plot.left - 10, y + 4, formatCompactConsumptionNumber(value), "consumption-axis-label", "end") ); } const step = plotWidth / Math.max(points.length, 1); const barWidth = Math.min(44, Math.max(10, step * 0.58)); points.forEach((point, index) => { const x = plot.left + (step * index) + ((step - barWidth) / 2); const fundedAmount = activeSeries.has("funded") ? point.funded : 0; const overageAmount = activeSeries.has("overage") ? point.overage : 0; const visibleTotal = fundedAmount + overageAmount; const fundedHeight = (fundedAmount / maximum) * plotHeight; const overageHeight = (overageAmount / maximum) * plotHeight; const group = createSvgElement("g", { class: "consumption-bar-group", tabindex: "0", role: "img", "aria-label": createConsumptionPointAriaLabel(point, period) }); ["pointerenter", "pointermove", "focus"].forEach((eventName) => { group.addEventListener(eventName, (event) => { showConsumptionTooltip(tooltip, point, period, event); }); }); ["pointerleave", "blur"].forEach((eventName) => { group.addEventListener(eventName, () => { tooltip.hidden = true; }); }); if (fundedHeight > 0) { const fundedBar = createSvgElement("rect", { x, y: plot.bottom - fundedHeight, width: barWidth, height: fundedHeight, class: "consumption-bar-funded" }); appendConsumptionBarAnimation( fundedBar, plot.bottom, plot.bottom - fundedHeight, fundedHeight, index * 34 ); group.append(fundedBar); } if (overageHeight > 0) { const overageTop = plot.bottom - fundedHeight - overageHeight; const overageBar = createSvgElement("rect", { x, y: overageTop, width: barWidth, height: overageHeight, class: "consumption-bar-overage" }); appendConsumptionBarAnimation( overageBar, plot.bottom - fundedHeight, overageTop, overageHeight, (index * 34) + 90 ); group.append(overageBar); } const top = plot.bottom - fundedHeight - overageHeight; if (visibleTotal > 0) { const valueLabel = createSvgText( x + (barWidth / 2), Math.max(plot.top + 11, top - 7), formatCompactConsumptionNumber(visibleTotal), "consumption-value-label consumption-animated-label", "middle" ); valueLabel.style.animationDelay = `${(index * 34) + 360}ms`; group.append(valueLabel); } const label = createSvgText( x + (barWidth / 2), plot.bottom + 18, formatConsumptionPointLabel(point, period), "consumption-x-label", period === "monthly" || period === "quarterly" ? "middle" : "end" ); if (period === "weekly" || period === "daily") { label.setAttribute("transform", `rotate(-65 ${x + (barWidth / 2)} ${plot.bottom + 18})`); } group.append(label); svg.append(group); }); if (activeSeries.has("average")) { const averageY = plot.bottom - ((average / maximum) * plotHeight); const averageLine = createSvgElement("line", { x1: plot.left, y1: averageY, x2: plot.right, y2: averageY, class: "consumption-average-line consumption-animated-average" }); svg.append(averageLine); } return svg; } function appendConsumptionBarAnimation(element, fromY, toY, height, delayMs) { if (prefersReducedMotion()) { return; } element.append( createSvgElement("animate", { attributeName: "y", from: fromY, to: toY, dur: "0.52s", begin: `${delayMs}ms`, fill: "freeze", calcMode: "spline", keyTimes: "0;1", keySplines: "0.2 0.8 0.2 1" }), createSvgElement("animate", { attributeName: "height", from: "0", to: height, dur: "0.52s", begin: `${delayMs}ms`, fill: "freeze", calcMode: "spline", keyTimes: "0;1", keySplines: "0.2 0.8 0.2 1" }) ); } function prefersReducedMotion() { return typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches; } function createConsumptionPointAriaLabel(point, period) { return `${formatConsumptionPointLabel(point, period)}. ` + `Funded Allocation: ${formatConsumptionCurrency(point.funded, point.currencyCode)}. ` + `Overage: ${formatConsumptionCurrency(point.overage, point.currencyCode)}. ` + `Total: ${formatConsumptionCurrency(point.total, point.currencyCode)}.`; } function showConsumptionTooltip(tooltip, point, period, event) { const figure = tooltip.parentElement; if (!figure) { return; } tooltip.textContent = ""; const title = document.createElement("strong"); title.textContent = formatConsumptionPointLabel(point, period); const details = document.createElement("span"); details.textContent = `Funded Allocation: ${formatConsumptionCurrency(point.funded, point.currencyCode)}`; const overage = document.createElement("span"); overage.textContent = `Overage: ${formatConsumptionCurrency(point.overage, point.currencyCode)}`; const total = document.createElement("span"); total.textContent = `Total: ${formatConsumptionCurrency(point.total, point.currencyCode)}`; tooltip.append(title, details, overage, total); tooltip.hidden = false; const figureBounds = figure.getBoundingClientRect(); const targetBounds = event.currentTarget.getBoundingClientRect(); const pointerX = Number.isFinite(event.clientX) && event.clientX > 0 ? event.clientX : targetBounds.left + (targetBounds.width / 2); const pointerY = Number.isFinite(event.clientY) && event.clientY > 0 ? event.clientY : targetBounds.top; const left = Math.min( Math.max(8, pointerX - figureBounds.left + 12), Math.max(8, figureBounds.width - tooltip.offsetWidth - 8) ); const top = Math.max(8, pointerY - figureBounds.top - tooltip.offsetHeight - 10); tooltip.style.left = `${left}px`; tooltip.style.top = `${top}px`; } function createSvgElement(name, attributes = {}) { const element = document.createElementNS(SVG_NAMESPACE, name); Object.entries(attributes).forEach(([key, value]) => { element.setAttribute(key, String(value)); }); return element; } function createSvgText(x, y, value, className, anchor) { const text = createSvgElement("text", { x, y, class: className, "text-anchor": anchor }); text.textContent = value; return text; } return { renderConsumptionTrendChart, getConsumptionTrendSeriesFilter, createConsumptionLegend, createConsumptionNavigationButton, createConsumptionSvg, appendConsumptionBarAnimation, prefersReducedMotion, createConsumptionPointAriaLabel, showConsumptionTooltip, createSvgElement, createSvgText }; } // Source: src/features/consumption/model.js function createConsumptionModel() { function normalizeConsumptionTrendItems(items) { const points = new Map(); (Array.isArray(items) ? items : []).forEach((item) => { const dateKey = String(item.fyDate || (Array.isArray(item.fiscalTime) ? item.fiscalTime.join("|") : "")); if (!dateKey) { return; } if (!points.has(dateKey)) { points.set(dateKey, { dateKey, date: parseConsumptionDate(item.fyDate), fiscalTime: Array.isArray(item.fiscalTime) ? item.fiscalTime.slice() : [], fiscalQtr: item.fiscalQtr || "", funded: 0, overage: 0, arr: 0, currencyCode: item.currencyCode || "USD" }); } const point = points.get(dateKey); const amount = toFiniteNumber(item.usedAmount); const category = String(item.usageCategory || "Funded Allocation").toUpperCase(); if (category === "OVERAGE") { point.overage += amount; } else { point.funded += amount; } point.arr = Math.max(point.arr, toFiniteNumber(item.arrCd)); }); return Array.from(points.values()) .map((point) => ({ ...point, total: point.funded + point.overage })) .sort((left, right) => left.dateKey.localeCompare(right.dateKey)); } function getConsumptionPointTotal(point, activeSeries) { const selectedSeries = activeSeries && typeof activeSeries.has === "function" ? activeSeries : new Set(["funded", "overage"]); return (selectedSeries.has("funded") ? point.funded : 0) + (selectedSeries.has("overage") ? point.overage : 0); } function calculateAverageConsumption(points, hasPartialLatestPoint, activeSeries) { const completePoints = hasPartialLatestPoint && points.length > 1 ? points.slice(0, -1) : points; return completePoints.reduce( (sum, point) => sum + getConsumptionPointTotal(point, activeSeries), 0 ) / Math.max(completePoints.length, 1); } function createNiceConsumptionMaximum(value) { if (!Number.isFinite(value) || value <= 0) { return 1; } const magnitude = 10 ** Math.floor(Math.log10(value)); const normalized = value / magnitude; const step = normalized <= 1 ? 1 : normalized <= 2 ? 2 : normalized <= 5 ? 5 : 10; return step * magnitude; } function formatConsumptionPointLabel(point, period) { if (period === "quarterly") { return point.fiscalTime[0] || point.fiscalQtr || point.dateKey; } if (period === "monthly") { return point.fiscalTime[1] || formatConsumptionDate(point.date, { month: "short" }); } return point.fiscalTime[0] || formatConsumptionDate(point.date, { day: "2-digit", month: "short", year: "2-digit" }); } function formatConsumptionRange(points, period) { if (points.length === 0) { return "No data"; } if (period === "quarterly") { return `${formatConsumptionPointLabel(points[0], period)} - ${formatConsumptionPointLabel(points[points.length - 1], period)}`; } if (period === "monthly") { const options = { month: "short", year: "numeric" }; return `${formatConsumptionDate(points[0].date, options)} - ${formatConsumptionDate(points[points.length - 1].date, options)}`; } const options = { day: "2-digit", month: "short" }; return `${formatConsumptionDate(points[0].date, options)} - ${formatConsumptionDate(points[points.length - 1].date, options)}`; } function parseConsumptionDate(value) { if (!value) { return null; } const parts = String(value).split("-").map(Number); return parts.length === 3 && parts.every(Number.isFinite) ? new Date(parts[0], parts[1] - 1, parts[2]) : null; } function formatConsumptionDate(date, options) { if (!(date instanceof Date) || Number.isNaN(date.getTime())) { return "-"; } return new Intl.DateTimeFormat("en-US", options).format(date).replace(",", ""); } function formatCompactConsumptionNumber(value) { const amount = toFiniteNumber(value); if (Math.abs(amount) >= 1000000) { return `${(amount / 1000000).toFixed(amount >= 10000000 ? 0 : 1).replace(".0", "")}M`; } if (Math.abs(amount) >= 1000) { return `${Math.round(amount / 1000)}K`; } return String(Math.round(amount)); } function formatConsumptionCurrency(value, currencyCode) { try { return new Intl.NumberFormat("en-US", { style: "currency", currency: currencyCode || "USD", maximumFractionDigits: 0 }).format(toFiniteNumber(value)); } catch (error) { return `${currencyCode || "USD"} ${Math.round(toFiniteNumber(value)).toLocaleString("en-US")}`; } } function toFiniteNumber(value) { const number = Number(value); return Number.isFinite(number) ? number : 0; } return { normalizeConsumptionTrendItems, getConsumptionPointTotal, calculateAverageConsumption, createNiceConsumptionMaximum, formatConsumptionPointLabel, formatConsumptionRange, parseConsumptionDate, formatConsumptionDate, formatCompactConsumptionNumber, formatConsumptionCurrency, toFiniteNumber }; } // Source: src/features/debug/content-log.js function createDebugContentLog({ previewDebugBody, debugState, renderDebugPanel }) { function logRequestStart(details) { const entry = { id: debugState.requestLog.length + 1, name: details.name, method: details.method, url: details.url, headers: details.headers, status: "pending", startedAt: new Date(), completedAt: null, durationMs: null, httpStatus: null, statusText: "", requestHeaders: details.headers, requestBody: details.body || "", metadata: details.metadata || {}, responseHeaders: {}, responseBody: "", responseBodyTruncated: false, error: "" }; debugState.requestLog.push(entry); renderDebugPanel(); return entry; } function logRequestSuccess(entry, response, responseBody) { entry.status = response.ok ? "success" : "failed"; entry.completedAt = new Date(); entry.durationMs = entry.completedAt.getTime() - entry.startedAt.getTime(); entry.httpStatus = response.status; entry.statusText = response.statusText || ""; entry.responseHeaders = headersToObject(response.headers); entry.responseBody = responseBody.value; entry.responseBodyTruncated = responseBody.truncated; renderDebugPanel(); } function logRequestFailure(entry, errorMessage) { entry.status = "failed"; entry.completedAt = new Date(); entry.durationMs = entry.completedAt.getTime() - entry.startedAt.getTime(); entry.error = errorMessage; renderDebugPanel(); } function appendBackgroundRequestLog(entries) { if (!Array.isArray(entries) || entries.length === 0) { return; } entries.forEach((entry) => { const existing = entry.debugKey && debugState.requestLog.find(item => item.debugKey === entry.debugKey); if (existing && existing.status !== "pending" && entry.status === "pending") return; const normalized = { id: existing ? existing.id : debugState.requestLog.length + 1, debugKey: entry.debugKey, name: entry.name || "backgroundRequest", method: entry.method || "GET", url: entry.url || "", headers: entry.requestHeaders || {}, status: entry.status || "failed", startedAt: new Date(entry.startedAt), completedAt: entry.completedAt ? new Date(entry.completedAt) : null, durationMs: entry.durationMs, httpStatus: entry.httpStatus, statusText: entry.statusText || "", requestHeaders: entry.requestHeaders || {}, requestBody: entry.requestBody || "", metadata: entry.metadata || { executionContext: "background" }, responseHeaders: entry.responseHeaders || {}, responseBody: entry.responseBody || "", responseBodyTruncated: Boolean(entry.responseBodyTruncated), error: entry.error || "" }; if (existing) Object.assign(existing, normalized); else debugState.requestLog.push(normalized); }); renderDebugPanel(); } async function readResponseBodyForDebug(response) { try { const text = await response.clone().text(); return createDebugBody(text); } catch (error) { return { value: `Unable to read response body: ${error.message || "unknown error"}`, truncated: false }; } } function createDebugBody(text) { return previewDebugBody(text); } function headersToObject(headers) { const headerMap = {}; headers.forEach((value, key) => { headerMap[key] = value; }); return headerMap; } function formatHeaderBlock(headers) { const entries = Object.entries(headers); if (entries.length === 0) { return "(none)"; } return entries .map(([key, value]) => `${key}: ${debugState.debugSecretsVisible || !isSensitiveDebugKey(key) ? value : formatHiddenDebugValue(value)}`) .join("\n"); } function formatDebugBody(body, requestName = "") { const text = body || "(none)"; try { const normalized = String(text).replace(/^\uFEFF/, "").trimStart() .replace(/^\)\]\}',?\s*/, "") .replace(/^while\s*\(1\);\s*/, ""); let value = JSON.parse(normalized); // Some services serialize their JSON document inside a JSON string. for (let depth = 0; depth < 2 && typeof value === "string" && /^[\s]*[\[{]/.test(value); depth += 1) { try { value = JSON.parse(value); } catch { break; } } if (!debugState.debugSecretsVisible && requestName === "requestSrTokenCatalog") { value = value && typeof value === "object" && !Array.isArray(value) ? { ...value, ...(Object.hasOwn(value, "result") ? { result: formatHiddenDebugValue(value.result) } : {}) } : formatHiddenDebugValue(value); } return JSON.stringify(debugState.debugSecretsVisible ? value : maskSensitiveDebugValue(value), null, 2); } catch (error) { if (!debugState.debugSecretsVisible && /^(requestSrTokenCatalog|requestCurrentUser|requestTokenRelay.*)$/.test(requestName)) return formatHiddenDebugValue(text); return text; } } function maskSensitiveDebugValue(value, parentKey = "") { if (Array.isArray(value)) { return value.map((item) => maskSensitiveDebugValue(item, parentKey)); } if (!value || typeof value !== "object") { return value; } return Object.fromEntries(Object.entries(value).map(([key, child]) => { const isSensitive = isSensitiveDebugKey(key) || (key === "value" && /uiaasHeader|uiassHeader/i.test(parentKey)); return [key, isSensitive ? formatHiddenDebugValue(child) : maskSensitiveDebugValue(child, key)]; })); } function isSensitiveDebugKey(key) { return /authorization|cookie|password|secret|(^|[_-])token($|[_-])|token$|x-appbuilder-client-id/i.test(key); } function formatHiddenDebugValue(value) { if (value === null || value === undefined || value === "") { return "(missing)"; } return `(present, ${String(value).length} characters)`; } function formatMetadataBlock(metadata) { const entries = Object.entries(metadata); if (entries.length === 0) { return "(none)"; } return entries .map(([key, value]) => `${key}: ${value || "(none)"}`) .join("\n"); } function maskToken(token) { if (token.length <= 10) { return "(present)"; } return `${token.slice(0, 4)}...${token.slice(-4)}`; } return { logRequestStart, logRequestSuccess, logRequestFailure, appendBackgroundRequestLog, readResponseBodyForDebug, createDebugBody, headersToObject, formatHeaderBlock, formatDebugBody, maskSensitiveDebugValue, isSensitiveDebugKey, formatHiddenDebugValue, formatMetadataBlock, maskToken }; } // Source: src/features/debug/content-view.js function createDebugContentView({ DEBUG_PANEL_ID, MODAL_ID, debugState, formatDebugBody, formatHeaderBlock, formatMetadataBlock, createCloseIcon, environment }) { const { document } = environment; const categories = [ ["all", "All"], ["authentication", "Authentication"], ["opportunities", "Opportunities"], ["products", "Products"], ["ai", "AI"], ["consumption", "Consumption"], ["installBase", "Installed Base"], ["serviceRequests", "Service Requests"], ["actions", "Actions"], ["other", "Other"] ]; let selectedCategory = "all"; const expandedIds = new Set(); let returnFocus = null; function getRequestCategory(entry) { if (/^request(?:CurrentUser|TokenRelay|ConsumerTokenService|CustomerTokenService)/.test(entry.name)) return "authentication"; if (entry.name === "requestOpportunities") return "opportunities"; if (entry.name === "requestOpptyProducts") return "products"; if (["requestWlActions", "requestActionTeamMembers", "requestActionFindLookup", "requestMgmtAction"].includes(entry.name)) return "actions"; if (entry.name === "requestGenerateAiSummary") return "ai"; if (entry.name === "requestInstallBase") return "installBase"; if (["requestSRInfo", "requestLookupTypePlatform", "requestInternalSrServices", "requestPostInternalServiceRequest"].includes(entry.name)) return "serviceRequests"; if (["requestCustomerServiceRequest", "resquestCrmServiceRequest", "requestInternalServiceRequest", "requestSrLookupPrimaryPillar", "requestSrLookupLanguage", "requestSrLookupTechPlatform", "requestSrLookupOpportunityBusiness", "requestResourceUser", "requestSrCatalog", "requestSrTokenCatalog", "requestPostServiceRequest"].includes(entry.name)) return "serviceRequests"; if (/^request(?:AccountSummary|AccountPlans|ConsumptionService|ConsumptionPlanDetail|RatecardByPlan)/.test(entry.name)) return "consumption"; return "other"; } function getChronologicalRequests() { return [...debugState.requestLog].sort((a, b) => (new Date(a.startedAt).getTime() || 0) - (new Date(b.startedAt).getTime() || 0) || a.id - b.id); } function toggleDebugPanel() { const modal = document.getElementById(MODAL_ID); if (!modal || modal.hidden) { return; } const panel = document.getElementById(DEBUG_PANEL_ID); if (!panel) { return; } if (!panel.hidden) { closeDebugPanel(); return; } returnFocus = document.activeElement; panel.hidden = false; renderDebugPanel(); panel.querySelectorAll(".opportunities-extension-debug-close-button")[0]?.focus({ preventScroll: true }); } function closeDebugPanel() { const panel = document.getElementById(DEBUG_PANEL_ID); if (!panel) return; panel.hidden = true; if (returnFocus?.isConnected) returnFocus.focus({ preventScroll: true }); returnFocus = null; } function renderDebugPanel() { const panel = document.getElementById(DEBUG_PANEL_ID); if (!panel || panel.hidden) { return; } for (const item of panel.querySelectorAll("details[data-request-id]")) { const id = item.getAttribute("data-request-id"); if (item.open) expandedIds.add(id); else expandedIds.delete(id); } const focusedCategory = document.activeElement && document.activeElement.getAttribute("data-debug-category"); const focusedControl = document.activeElement && document.activeElement.getAttribute("data-debug-focus"); const listScrollTop = panel.querySelectorAll(".opportunities-extension-debug-list")[0]?.scrollTop || 0; const scrollTop = panel.scrollTop; panel.textContent = ""; panel.setAttribute("role", "dialog"); panel.setAttribute("aria-modal", "true"); panel.setAttribute("aria-labelledby", `${DEBUG_PANEL_ID}-title`); const focusTargets = new Map(); const panelHeader = document.createElement("header"); panelHeader.className = "opportunities-extension-debug-panel-header"; const heading = document.createElement("div"); const title = document.createElement("h2"); title.id = `${DEBUG_PANEL_ID}-title`; title.textContent = "Debug Requests"; const summary = document.createElement("p"); summary.textContent = `${debugState.requestLog.length} request${debugState.requestLog.length === 1 ? "" : "s"} since modal opened.`; const running = debugState.requestLog.filter(entry => entry.status === "pending").length; if (running) summary.textContent += ` ${running} running.`; heading.append(title, summary); const visibilityButton = document.createElement("button"); visibilityButton.type = "button"; visibilityButton.className = "opportunities-extension-icon-button opportunities-extension-debug-visibility-button"; visibilityButton.setAttribute("data-debug-focus", "visibility"); focusTargets.set("visibility", visibilityButton); visibilityButton.setAttribute("aria-pressed", String(debugState.debugSecretsVisible)); visibilityButton.setAttribute("aria-label", debugState.debugSecretsVisible ? "Hide sensitive values" : "Show sensitive values"); visibilityButton.title = debugState.debugSecretsVisible ? "Hide sensitive values" : "Show sensitive values"; visibilityButton.append(createDebugVisibilityIcon(debugState.debugSecretsVisible)); visibilityButton.addEventListener("click", () => { debugState.debugSecretsVisible = !debugState.debugSecretsVisible; renderDebugPanel(); }); const closeButton = document.createElement("button"); closeButton.type = "button"; closeButton.className = "opportunities-extension-icon-button opportunities-extension-debug-close-button"; closeButton.title = "Close debug"; closeButton.setAttribute("aria-label", "Close debug"); closeButton.setAttribute("data-debug-focus", "close"); closeButton.append(createCloseIcon()); closeButton.addEventListener("click", closeDebugPanel); focusTargets.set("close", closeButton); const actions = document.createElement("div"); actions.className = "opportunities-extension-debug-actions"; actions.append(visibilityButton, closeButton); panelHeader.append(heading, actions); const entries = getChronologicalRequests(); const availableCategories = categories.filter(([key]) => key === "all" || entries.some(entry => getRequestCategory(entry) === key)); if (!availableCategories.some(([key]) => key === selectedCategory)) selectedCategory = "all"; const tabs = document.createElement("div"); tabs.className = "opportunities-extension-debug-tabs"; tabs.setAttribute("role", "tablist"); tabs.setAttribute("aria-label", "Request categories"); const tabButtons = []; availableCategories.forEach(([key, label], index) => { const categoryEntries = key === "all" ? entries : entries.filter(entry => getRequestCategory(entry) === key); const pending = categoryEntries.filter(entry => entry.status === "pending").length; const button = document.createElement("button"); button.type = "button"; button.id = `${DEBUG_PANEL_ID}-tab-${key}`; button.setAttribute("role", "tab"); button.setAttribute("data-debug-category", key); button.setAttribute("aria-selected", String(key === selectedCategory)); button.setAttribute("aria-controls", `${DEBUG_PANEL_ID}-requests`); button.tabIndex = key === selectedCategory ? 0 : -1; button.textContent = `${label} (${categoryEntries.length})${pending ? ` / ${pending} running` : ""}`; button.addEventListener("click", () => { selectedCategory = key; renderDebugPanel(); }); button.addEventListener("keydown", event => { let nextIndex; if (event.key === "ArrowRight") nextIndex = (index + 1) % availableCategories.length; else if (event.key === "ArrowLeft") nextIndex = (index + availableCategories.length - 1) % availableCategories.length; else if (event.key === "Home") nextIndex = 0; else if (event.key === "End") nextIndex = availableCategories.length - 1; else return; event.preventDefault(); selectedCategory = availableCategories[nextIndex][0]; renderDebugPanel(); }); tabs.append(button); tabButtons.push(button); }); const list = document.createElement("div"); list.className = "opportunities-extension-debug-list"; list.id = `${DEBUG_PANEL_ID}-requests`; list.setAttribute("role", "tabpanel"); list.setAttribute("aria-labelledby", `${DEBUG_PANEL_ID}-tab-${selectedCategory}`); list.tabIndex = 0; list.setAttribute("data-debug-focus", "list"); focusTargets.set("list", list); const summaries = []; if (debugState.requestLog.length === 0) { const empty = document.createElement("div"); empty.className = "opportunities-extension-debug-empty"; empty.textContent = "No requests recorded yet."; list.append(empty); } entries.filter(entry => selectedCategory === "all" || getRequestCategory(entry) === selectedCategory).forEach((entry) => { const item = document.createElement("details"); item.className = `opportunities-extension-debug-item opportunities-extension-debug-item-${entry.status}`; item.setAttribute("data-request-id", String(entry.id)); item.open = expandedIds.has(String(entry.id)); const heading = document.createElement("summary"); heading.className = "opportunities-extension-debug-heading"; heading.setAttribute("data-debug-focus", `request-${entry.id}`); focusTargets.set(`request-${entry.id}`, heading); summaries.push(heading); const name = document.createElement("strong"); name.textContent = `${entry.id}. ${entry.name}`; if (entry.name === "requestOpptyProducts" && entry.metadata.optyNumber) { name.textContent += ` (${entry.metadata.optyNumber}, offset ${entry.metadata.offset})`; } const httpStatus = document.createElement("span"); httpStatus.className = "opportunities-extension-debug-http-status"; httpStatus.textContent = entry.httpStatus ? String(entry.httpStatus) : "pending"; httpStatus.setAttribute("data-http-class", entry.httpStatus ? String(Math.floor(entry.httpStatus / 100)) : "pending"); const status = document.createElement("span"); status.className = "opportunities-extension-debug-status"; status.textContent = entry.status === "pending" ? "running" : entry.status; const summaryMeta = document.createElement("span"); summaryMeta.className = "opportunities-extension-debug-summary-meta"; const startedTime = document.createElement("time"); startedTime.className = "opportunities-extension-debug-time"; startedTime.textContent = formatDebugTime(entry.startedAt); startedTime.title = `Started: ${entry.startedAt.toLocaleString()}`; const method = document.createElement("span"); method.className = "opportunities-extension-debug-method"; method.textContent = entry.method || "GET"; method.setAttribute("data-method", method.textContent.toUpperCase()); const duration = document.createElement("span"); duration.className = "opportunities-extension-debug-duration"; const hasDuration = typeof entry.durationMs === "number" && Number.isFinite(entry.durationMs) && entry.durationMs >= 0; duration.textContent = hasDuration ? entry.durationMs < 1000 ? `${Math.round(entry.durationMs)} ms` : `${(entry.durationMs / 1000).toFixed(2)} s` : entry.status === "pending" ? "In progress" : "N/D"; duration.title = hasDuration ? `Request duration: ${entry.durationMs} ms` : "Request duration not yet available"; summaryMeta.append(startedTime, method, httpStatus, status, duration); heading.append(name, summaryMeta); const lines = [ `${entry.method} ${entry.url}`, `Started: ${formatDebugTime(entry.startedAt)}`, entry.completedAt ? `Completed: ${formatDebugTime(entry.completedAt)} (${entry.durationMs}ms)` : "Completed: pending", entry.httpStatus ? `HTTP: ${entry.httpStatus} ${entry.statusText}`.trim() : "", entry.error ? `Error: ${entry.error}` : "", `Response body truncated: ${entry.responseBodyTruncated ? "yes" : "no"}` ].filter(Boolean); const details = document.createElement("div"); details.className = "opportunities-extension-debug-details"; appendDebugBlock(details, "Request", lines.join("\n")); appendDebugBlock(details, "Request Metadata", formatMetadataBlock(entry.metadata)); appendDebugBlock(details, "Request Headers", formatHeaderBlock(entry.requestHeaders)); appendDebugBlock(details, "Request Body", formatDebugBody(entry.requestBody)); appendDebugBlock(details, "Response Headers", formatHeaderBlock(entry.responseHeaders)); appendDebugBlock(details, "Response Body", formatDebugBody(entry.responseBody, entry.name)); item.append(heading, details); list.append(item); }); panel.append(panelHeader, tabs, list); panel.onkeydown = event => { if (event.key === "Escape") { event.preventDefault(); event.stopPropagation(); closeDebugPanel(); } else if (event.key === "Tab") { const controls = [visibilityButton, closeButton, ...tabButtons.filter(button => button.tabIndex === 0), list, ...summaries]; const index = controls.indexOf(document.activeElement); if (index < 0 || (event.shiftKey && index === 0) || (!event.shiftKey && index === controls.length - 1)) { event.preventDefault(); controls[event.shiftKey ? controls.length - 1 : 0].focus({ preventScroll: true }); } } }; if (focusedCategory) tabButtons.find(button => button.getAttribute("data-debug-category") === selectedCategory).focus({ preventScroll: true }); else if (focusedControl) focusTargets.get(focusedControl)?.focus({ preventScroll: true }); list.scrollTop = listScrollTop; panel.scrollTop = scrollTop; } function createDebugVisibilityIcon(secretsVisible) { const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); const eye = document.createElementNS("http://www.w3.org/2000/svg", "path"); const pupil = document.createElementNS("http://www.w3.org/2000/svg", "circle"); svg.setAttribute("viewBox", "0 0 24 24"); svg.setAttribute("aria-hidden", "true"); svg.setAttribute("focusable", "false"); eye.setAttribute("d", "M2.5 12s3.5-6.5 9.5-6.5 9.5 6.5 9.5 6.5-3.5 6.5-9.5 6.5S2.5 12 2.5 12Z"); pupil.setAttribute("cx", "12"); pupil.setAttribute("cy", "12"); pupil.setAttribute("r", "2.75"); [eye, pupil].forEach((shape) => { shape.setAttribute("fill", "none"); shape.setAttribute("stroke", "currentColor"); shape.setAttribute("stroke-linecap", "round"); shape.setAttribute("stroke-linejoin", "round"); shape.setAttribute("stroke-width", "1.7"); }); svg.append(eye, pupil); if (secretsVisible) { const slash = document.createElementNS("http://www.w3.org/2000/svg", "path"); slash.setAttribute("d", "M4 4l16 16"); slash.setAttribute("fill", "none"); slash.setAttribute("stroke", "currentColor"); slash.setAttribute("stroke-linecap", "round"); slash.setAttribute("stroke-width", "1.9"); svg.append(slash); } return svg; } function appendDebugBlock(container, label, value) { const block = document.createElement("section"); block.className = "opportunities-extension-debug-block"; block.setAttribute("data-direction", label.startsWith("Response") ? "response" : "request"); const heading = document.createElement("h3"); const content = document.createElement("pre"); heading.textContent = label; appendJsonTokens(content, value); block.append(heading, content); container.append(block); } function appendJsonTokens(content, value) { const text = String(value ?? ""); try { JSON.parse(text); } catch { content.textContent = text; return; } // Highlight only already-masked display text; never interpret response content as HTML. const tokens = [...text.matchAll(/"(?:[^"\\]|\\.)*"\s*:|"(?:[^"\\]|\\.)*"|-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?|\b(?:true|false|null)\b/g)]; let offset = 0; const append = (textValue, kind) => { if (!textValue) return; const span = document.createElement("span"); span.textContent = textValue; if (kind) span.className = `opportunities-extension-debug-json-${kind}`; content.append(span); }; tokens.forEach(token => { append(text.slice(offset, token.index)); const value = token[0]; const kind = value.startsWith('"') ? value.endsWith(":") ? "key" : "string" : /^(true|false|null)$/.test(value) ? "literal" : "number"; append(value, kind); offset = token.index + value.length; }); append(text.slice(offset)); } function formatDebugTime(date) { return date.toLocaleTimeString(undefined, { hour: "2-digit", minute: "2-digit", second: "2-digit" }); } return { toggleDebugPanel, closeDebugPanel, renderDebugPanel, createDebugVisibilityIcon, appendDebugBlock, formatDebugTime, getRequestCategory, getChronologicalRequests }; } // Source: src/content/main.js /** Preserve the existing document_idle initialization and observer behavior. */ function startContentApplication({ environment, page, styles, products }) { if (!page.isAllowedPage()) return; styles.ensureExtensionStyles(); products.ensureProductsBackgroundMessageHandler(); if (page.insertTile()) return; const observer = new environment.MutationObserver(() => page.insertTile()); observer.observe(environment.document.documentElement, { childList: true, subtree: true }); } // Source: src/content/application.js function createContentApplication(overrides = {}) { const environment = { document: globalThis.document, window: globalThis.window, navigator: globalThis.navigator, get localStorage() { return globalThis.localStorage; }, fetch: globalThis.fetch, browser: globalThis.browser, chrome: globalThis.chrome, MutationObserver: globalThis.MutationObserver, Node: globalThis.Node, requestAnimationFrame: globalThis.requestAnimationFrame, performance: globalThis.performance, cancelAnimationFrame: globalThis.cancelAnimationFrame, setTimeout: globalThis.setTimeout, clearTimeout: globalThis.clearTimeout, matchMedia: globalThis.matchMedia, ...overrides }; const messageTypes = createMessageTypes(); const currentUser = createCurrentUser({ messageTypes, sendRuntimeMessage: (...args) => platformContentMessages.sendRuntimeMessage(...args), appendBackgroundRequestLog: (...args) => debugContentLog.appendBackgroundRequestLog(...args) }); const contracts = createContentContracts(); const searchableSelect = createSearchableSelect({ environment }); const authSessionState = createAuthSessionState(contracts); const authSalesCommonState = createAuthSalesCommonState(); const opportunitiesQueryState = createOpportunitiesQueryState(); const opportunitiesTableState = createOpportunitiesTableState(); const opportunitiesFilterState = createOpportunitiesFilterState(contracts); const preferencesState = createPreferencesState(contracts); const activityCache = createActivityCache({ authSessionState, preferencesState, environment }); const productsContentState = createProductsContentState(); const aiSummaryState = createAiSummaryState(); const consumptionPlansState = createConsumptionPlansState(); const consumptionTrendState = createConsumptionTrendState(); const opportunitiesDashboardState = createOpportunitiesDashboardState(contracts); const debugState = createDebugState(); const columns = createOpportunitiesColumns({ formatOracleResponseDate: (...args) => opportunitiesFormatters.formatOracleResponseDate(...args), formatRevenue: (...args) => opportunitiesFormatters.formatRevenue(...args), formatWinProbability: (...args) => opportunitiesFormatters.formatWinProbability(...args), getNestedValue: (...args) => opportunitiesFormatters.getNestedValue(...args), getOwnerName: (...args) => opportunitiesFormatters.getOwnerName(...args) }); const contentFusionPage = createContentFusionPage({ ADD_ICON_SELECTOR: contracts.ADD_ICON_SELECTOR, ALLOWED_PATHS: contracts.ALLOWED_PATHS, SALES_GROUP_SELECTOR: contracts.SALES_GROUP_SELECTOR, TILE_ID: contracts.TILE_ID, TILE_LABEL_ID: contracts.TILE_LABEL_ID, appendPath: (...args) => svgPaths.appendPath(...args), openOpportunitiesModal: (...args) => contentModal.openOpportunitiesModal(...args), environment }); const contentModal = createContentModal({ createManageTimeButton: () => manageTime.createButton(), prepareSrSession: () => srSessionPreparation.ensure(), closeActionForm: (...args) => manageTime.close(...args) || internalSrForm.close(...args) || opportunityCalendar.closeDetails(...args) || crmSrForm.close(...args) || workloadActionForm.close(...args), mountOpportunityViews: (...args) => opportunityCalendar.mount(...args), createActivityFilterControls: (...args) => activityFilters.createControls(...args), closeCrmSrPopover: () => crmSrPopover.close(), ensureCurrentUser: () => currentUser.ensure(), ACCOUNT_PLANS_MODAL_ID: contracts.ACCOUNT_PLANS_MODAL_ID, AUTH_STATUS: contracts.AUTH_STATUS, CUSTOMER_FILTER_BUTTON_ID: contracts.CUSTOMER_FILTER_BUTTON_ID, CUSTOMER_FILTER_LIST_ID: contracts.CUSTOMER_FILTER_LIST_ID, CUSTOMER_FILTER_PANEL_ID: contracts.CUSTOMER_FILTER_PANEL_ID, CUSTOMER_FILTER_SEARCH_ID: contracts.CUSTOMER_FILTER_SEARCH_ID, CUSTOMER_FILTER_SELECT_ALL_ID: contracts.CUSTOMER_FILTER_SELECT_ALL_ID, DEBUG_PANEL_ID: contracts.DEBUG_PANEL_ID, ESCAPE_LISTENER_KEY: contracts.ESCAPE_LISTENER_KEY, MODAL_ID: contracts.MODAL_ID, OPPORTUNITIES_TABLE_ID: contracts.OPPORTUNITIES_TABLE_ID, OWNER_FILTER_BUTTON_ID: contracts.OWNER_FILTER_BUTTON_ID, OWNER_FILTER_LIST_ID: contracts.OWNER_FILTER_LIST_ID, OWNER_FILTER_PANEL_ID: contracts.OWNER_FILTER_PANEL_ID, OWNER_FILTER_SEARCH_ID: contracts.OWNER_FILTER_SEARCH_ID, OWNER_FILTER_SELECT_ALL_ID: contracts.OWNER_FILTER_SELECT_ALL_ID, PERIOD_OPTIONS: contracts.PERIOD_OPTIONS, PREFERENCES_MODAL_ID: contracts.PREFERENCES_MODAL_ID, PRODUCTS_CACHE_TIMESTAMP_ID: contracts.PRODUCTS_CACHE_TIMESTAMP_ID, PRODUCTS_PROGRESS_ID: contracts.PRODUCTS_PROGRESS_ID, STAGE_DASHBOARD_ID: contracts.STAGE_DASHBOARD_ID, STAGE_FILTERS_ID: contracts.STAGE_FILTERS_ID, STATUS_FILTERS_ID: contracts.STATUS_FILTERS_ID, TABLE_SEARCH_ID: contracts.TABLE_SEARCH_ID, applyTheme: (...args) => contentTheme.applyTheme(...args), authSessionState, closeAccountPlansModal: (...args) => consumptionPlansView.closeAccountPlansModal(...args), closeInstallBaseModal: (...args) => installBaseView.closeInstallBaseModal(...args), closeServiceRequestsModal: (...args) => serviceRequestsView.closeServiceRequestsModal(...args), closeCustomerFilterPanel: (...args) => opportunitiesFilters.closeCustomerFilterPanel(...args), closeOwnerFilterPanel: (...args) => opportunitiesFilters.closeOwnerFilterPanel(...args), closePreferencesModal: (...args) => preferencesView.closePreferencesModal(...args), createSettingsIcon: (...args) => contentIcons.createSettingsIcon(...args), createRefreshAllButton: (...args) => refreshAll.createButton(...args), debugState, ensureExtensionStyles: (...args) => contentStyles.ensureExtensionStyles(...args), getCustomerOptions: (...args) => opportunitiesFormatters.getCustomerOptions(...args), getOwnerOptions: (...args) => opportunitiesFormatters.getOwnerOptions(...args), getSavedOpportunityTypeValues: (...args) => preferencesStorage.getSavedOpportunityTypeValues(...args), getSavedOpptyProductsCacheHours: (...args) => preferencesStorage.getSavedOpptyProductsCacheHours(...args), getSavedTheme: (...args) => contentTheme.getSavedTheme(...args), openPreferencesModal: (...args) => preferencesView.openPreferencesModal(...args), opportunitiesFilterState, opportunitiesQueryState, preferencesState, renderCustomerFilter: (...args) => opportunitiesFilters.renderCustomerFilter(...args), renderCustomerFilterList: (...args) => opportunitiesFilters.renderCustomerFilterList(...args), renderOpportunitiesTable: (...args) => opportunitiesTable.renderOpportunitiesTable(...args), renderOwnerFilter: (...args) => opportunitiesFilters.renderOwnerFilter(...args), renderOwnerFilterList: (...args) => opportunitiesFilters.renderOwnerFilterList(...args), renderStageFilterButtons: (...args) => opportunitiesFilters.renderStageFilterButtons(...args), renderStatusFilterButtons: (...args) => opportunitiesFilters.renderStatusFilterButtons(...args), requestOpportunitiesForPeriod: (...args) => opportunitiesController.requestOpportunitiesForPeriod(...args), requestTokenRelayOnce: (...args) => authContentSession.requestTokenRelayOnce(...args), resetCustomerAndOwnerFiltersForPeriod: (...args) => opportunitiesFilters.resetCustomerAndOwnerFiltersForPeriod(...args), resetOpportunitiesTable: (...args) => opportunitiesController.resetOpportunitiesTable(...args), restoreFilterPreferences: (...args) => preferencesStorage.restoreFilterPreferences(...args), saveFilterPreferences: (...args) => preferencesStorage.saveFilterPreferences(...args), toggleCustomerFilterPanel: (...args) => opportunitiesFilters.toggleCustomerFilterPanel(...args), toggleDebugPanel: (...args) => debugContentView.toggleDebugPanel(...args), toggleOwnerFilterPanel: (...args) => opportunitiesFilters.toggleOwnerFilterPanel(...args), environment }); const contentTheme = createContentTheme({ THEME_STORAGE_KEY: contracts.THEME_STORAGE_KEY, createThemeIcon: (...args) => contentIcons.createThemeIcon(...args), environment }); const contentIcons = createContentIcons({ appendPath: (...args) => svgPaths.appendPath(...args), environment }); const svgPaths = createSvgPaths({ environment }); const manageTime = createManageTime({ MODAL_ID: contracts.MODAL_ID, environment, searchableSelect, dates: createTimeDates(), async requestTaskTypes() { const result = await platformContentMessages.sendRuntimeMessage({ type: messageTypes.requestLookupTaskType }); debugContentLog.appendBackgroundRequestLog(result?.debugRequests || []); if (!result?.ok) throw new Error(result?.error || "Unable to load Task Types."); return result.items; }, async requestExpand(srNumbers) { const result = await platformContentMessages.sendRuntimeMessage({ type: messageTypes.requestServiceRequestExpand, srNumbers }); debugContentLog.appendBackgroundRequestLog(result?.debugRequests || []); if (!result?.ok) throw new Error(result?.error || "Unable to load activities."); return result.items; }, async requestServices(weekId) { await srSessionPreparation.ensure(); await currentUser.ensure(); const result = await platformContentMessages.sendRuntimeMessage({ type: messageTypes.requestActiveServiceRequests, weekId, emailAddress: currentUser.getUserId() }); debugContentLog.appendBackgroundRequestLog(result?.debugRequests || []); if (!result?.ok) throw new Error(result?.error || "Unable to load Service Requests."); return result.items; }, async requestWeek(dateReference) { const result = await platformContentMessages.sendRuntimeMessage({ type: messageTypes.requestWeekDetails, dateReference }); debugContentLog.appendBackgroundRequestLog(result?.debugRequests || []); if (!result?.ok) throw new Error(result?.error || "Unable to load week details."); return result.items; } }); const contentStyles = createContentStyles({ STYLE_ID: contracts.STYLE_ID, redwoodStyles, environment }); const authContentSession = createAuthContentSession({ messageTypes, AUTH_STATUS: contracts.AUTH_STATUS, appendBackgroundRequestLog: (...args) => debugContentLog.appendBackgroundRequestLog(...args), authSessionState, requestTokenRelay: (...args) => authTokenRelay.requestTokenRelay(...args), sendRuntimeMessage: (...args) => platformContentMessages.sendRuntimeMessage(...args), updateAuthBadge: (...args) => contentModal.updateAuthBadge(...args) }); const authSalesCommon = createAuthSalesCommon({ TOKEN_RELAY_URL: contracts.TOKEN_RELAY_URL, authSalesCommonState, requestTokenRelay: (...args) => authTokenRelay.requestTokenRelay(...args) }); const authTokenRelay = createAuthTokenRelay({ authSessionState, TOKEN_RELAY_URL: contracts.TOKEN_RELAY_URL, createDebugBody: (...args) => debugContentLog.createDebugBody(...args), getXsrfToken: (...args) => authXsrfContent.getXsrfToken(...args), logRequestFailure: (...args) => debugContentLog.logRequestFailure(...args), logRequestStart: (...args) => debugContentLog.logRequestStart(...args), logRequestSuccess: (...args) => debugContentLog.logRequestSuccess(...args), maskToken: (...args) => debugContentLog.maskToken(...args), readResponseBodyForDebug: (...args) => debugContentLog.readResponseBodyForDebug(...args), refreshXsrfCookie: (...args) => platformContentMessages.refreshXsrfCookie(...args), renderDebugPanel: (...args) => debugContentView.renderDebugPanel(...args), environment }); const authXsrfContent = createAuthXsrfContent({ getXsrfTokenFromExtensionCookies: (...args) => platformContentMessages.getXsrfTokenFromExtensionCookies(...args), environment }); const platformContentMessages = createPlatformContentMessages({ requestCache: createFormRequestCache({ getHours: () => preferencesState.opptyProductsCacheHours, getUserId: () => currentUser.getUserId() }), messageTypes, environment }); const opportunitiesController = createOpportunitiesController({ ensureCurrentUser: () => currentUser.ensure(), resetWorkloadActions: (...args) => opportunityWorkloadActions.reset(...args), STAGE_OPTIONS: contracts.STAGE_OPTIONS, STATUS_OPTIONS: contracts.STATUS_OPTIONS, authSessionState, consumptionPlansState, consumptionTrendState, getFiscalPeriodRange: (...args) => opportunitiesFiscalPeriods.getFiscalPeriodRange(...args), opportunitiesDashboardState, opportunitiesFilterState, opportunitiesQueryState, opportunitiesTableState, preferencesState, productsContentState, renderProductsProgress: (...args) => productsContentJobs.renderProductsProgress(...args), requestOpportunitiesPage: (...args) => fusionOpportunities.requestOpportunitiesPage(...args), resetCrmServiceRequests: () => opportunityCrmServiceRequests.reset(), requestOpptyProductsInBackground: (...args) => productsContentJobs.requestOpptyProductsInBackground(...args), requestTokenRelayOnce: (...args) => authContentSession.requestTokenRelayOnce(...args), setOpportunitiesTableState: (...args) => opportunitiesTable.setOpportunitiesTableState(...args) }); const fusionOpportunities = createFusionOpportunities({ OPPORTUNITIES_PAGE_LIMIT: contracts.OPPORTUNITIES_PAGE_LIMIT, OPPORTUNITIES_QUERY_URL: contracts.OPPORTUNITIES_QUERY_URL, isCurrentPeriodRequest: (...args) => opportunitiesController.isCurrentPeriodRequest(...args), logRequestFailure: (...args) => debugContentLog.logRequestFailure(...args), logRequestStart: (...args) => debugContentLog.logRequestStart(...args), logRequestSuccess: (...args) => debugContentLog.logRequestSuccess(...args), maskToken: (...args) => debugContentLog.maskToken(...args), getOpportunityTypeValues: () => preferencesState.selectedOpportunityTypeValues, readResponseBodyForDebug: (...args) => debugContentLog.readResponseBodyForDebug(...args), environment }); const opportunitiesFiscalPeriods = createOpportunitiesFiscalPeriods(); const fusionCrmServiceRequests = createFusionCrmServiceRequests({ isCurrentPeriodRequest: (...args) => opportunitiesController.isCurrentPeriodRequest(...args), logRequestStart: (...args) => debugContentLog.logRequestStart(...args), logRequestSuccess: (...args) => debugContentLog.logRequestSuccess(...args), logRequestFailure: (...args) => debugContentLog.logRequestFailure(...args), readResponseBodyForDebug: (...args) => debugContentLog.readResponseBodyForDebug(...args), maskToken: (...args) => debugContentLog.maskToken(...args), environment }); const internalSrSession = createInternalSrSession({ environment, query: async srId => { const result = await platformContentMessages.sendRuntimeMessage({ type: messageTypes.requestInternalServiceRequest, srId }); debugContentLog.appendBackgroundRequestLog(result?.debugRequests || []); return result; } }); const crmSrPopover = createCrmSrPopover({ openInternalSrForm: (parent, trigger) => internalSrForm.open(parent, trigger), formatDate: value => opportunitiesFormatters.formatOracleResponseDate(value), requestDetails: srId => internalSrSession.request(srId), MODAL_ID: contracts.MODAL_ID, createCopyButton: (...args) => opportunitiesLinks.createCopyOpportunityButton(...args), createCloseIcon: (...args) => contentIcons.createCloseIcon(...args), environment }); const opportunityBadgeContent = createOpportunityBadgeContent({ environment }); async function requestSrFormLookup(type, fields = {}) { const result = await platformContentMessages.sendRuntimeMessage({ type, ...fields }); debugContentLog.appendBackgroundRequestLog(result?.debugRequests || []); if (!result?.ok) throw new Error(result?.error || "Unable to load Service Request options."); return result.items; } const internalSrForm = createInternalSrForm({ notify: (...args) => opportunitiesLinks.showCopyConfirmation(...args), requestSave: async payload => { const result = await platformContentMessages.sendRuntimeMessage({ type: messageTypes.requestPostInternalServiceRequest, payload, emailAddress: currentUser.getUserId() }); debugContentLog.appendBackgroundRequestLog(result?.debugRequests || []); if (!result?.ok) throw new Error(result?.error || "Unable to create Internal Service Request."); return result.result; }, getOpportunity: number => number == null ? null : opportunitiesTableState.opportunitiesTableState.items.find(item => String(item.OptyNumber) === String(number)), MODAL_ID: contracts.MODAL_ID, environment, searchableSelect, formatDate: value => opportunitiesFormatters.formatOracleResponseDate(value), requestPlatform: () => requestSrFormLookup(messageTypes.requestLookupTypePlatform), requestServices: parentServiceId => requestSrFormLookup(messageTypes.requestInternalSrServices, { parentServiceId, emailAddress: currentUser.getUserId() }) }); const crmSrForm = createCrmSrForm({ requestSave: async fields => { const result = await platformContentMessages.sendRuntimeMessage({ type: messageTypes.requestPostServiceRequest, ...fields, emailAddress: currentUser.getUserId() }); debugContentLog.appendBackgroundRequestLog(result?.debugRequests || []); if (!result?.ok) throw new Error(result?.error || "Unable to save Service Request."); return result; }, notify: (...args) => opportunitiesLinks.showCopyConfirmation(...args), getOpportunity: number => opportunitiesTableState.opportunitiesTableState.items.find(item => String(item.OptyNumber) === String(number)), formatDate: value => opportunitiesFormatters.formatOracleResponseDate(value), requestLanguage: () => requestSrFormLookup(messageTypes.requestSrLookupLanguage), requestPlatform: () => requestSrFormLookup(messageTypes.requestSrLookupTechPlatform), requestServices: () => requestSrFormLookup(messageTypes.requestSrLookupOpportunityBusiness, { emailAddress: currentUser.getUserId() }), searchableSelect, MODAL_ID: contracts.MODAL_ID, environment, allowsCrmActivities: () => currentUser.allowsCrmActivities(), isMissing: number => opportunityCrmServiceRequests.getSummary(number)?.state === "missing", createCloseIcon: () => contentIcons.createCloseIcon(), requestPillars: () => requestSrFormLookup(messageTypes.requestSrLookupPrimaryPillar) }); const opportunityCrmServiceRequests = createOpportunityCrmServiceRequests({ formatDate: value => opportunitiesFormatters.formatOracleResponseDate(value), createSrButton: number => crmSrForm.createButton(number), closeSrForm: () => { internalSrForm.close(false); crmSrForm.close(false); }, setBadgeContent: (...args) => opportunityBadgeContent.setContent(...args), activityCache, reportProgress: (...args) => activityProgress.update("sr", ...args), allowsCrmActivities: () => currentUser.allowsCrmActivities(), bindPopover: (...args) => crmSrPopover.bind(...args), closePopover: (...args) => crmSrPopover.close(...args), requestCrmServiceRequest: (...args) => fusionCrmServiceRequests.requestCrmServiceRequest(...args), isCurrentPeriodRequest: (...args) => opportunitiesController.isCurrentPeriodRequest(...args), environment }); const opportunityCalendar = createOpportunityCalendar({ createViewIcon: view => contentIcons.createViewIcon(view), environment, loadLibrary: () => loadOpportunityCalendarLibrary(), getState: () => opportunitiesTableState.opportunitiesTableState, getItems: () => opportunitiesFilters.getSortedOpportunities(), getVersion: () => opportunitiesQueryState.periodRequestVersion, formatRevenue: item => opportunitiesFormatters.formatRevenue(item), formatDate: value => opportunitiesFormatters.formatOracleResponseDate(value), getOwnerName: item => opportunitiesFormatters.getOwnerName(item), createDetailLink: (...args) => opportunitiesLinks.createDetailLink(...args), createCopyButton: (...args) => opportunitiesLinks.createCopyOpportunityButton(...args), createCloseIcon: () => contentIcons.createCloseIcon(), createStageBadge: value => opportunitiesLinks.createStageBadge(value), createStatusBadge: value => opportunitiesLinks.createStatusBadge(value), opportunityUrl: contracts.OPPORTUNITY_DETAIL_URL, modalId: contracts.MODAL_ID, closeSrPopover: () => crmSrPopover.close() }); const opportunitiesTable = createOpportunitiesTable({ renderCalendar: (...args) => opportunityCalendar.render(...args), renderActivityFilters: () => activityFilters.render(), closeCrmSrPopover: (...args) => crmSrPopover.close(...args), createWorkloadActionsBadge: (...args) => opportunityWorkloadActions.createBadge(...args), ACCOUNT_DETAIL_URL: contracts.ACCOUNT_DETAIL_URL, OPPORTUNITIES_COLUMNS: columns, OPPORTUNITIES_TABLE_ID: contracts.OPPORTUNITIES_TABLE_ID, OPPORTUNITY_DETAIL_URL: contracts.OPPORTUNITY_DETAIL_URL, createCloudConsumptionButton: (...args) => consumptionPlansAction.createCloudConsumptionButton(...args), createInstallBaseButton: (...args) => installBaseAction.createInstallBaseButton(...args), createServiceRequestsButton: (...args) => serviceRequestsAction.createServiceRequestsButton(...args), createCopyOpportunityButton: (...args) => opportunitiesLinks.createCopyOpportunityButton(...args), createDetailLink: (...args) => opportunitiesLinks.createDetailLink(...args), createProductsCountButton: (...args) => productsDetails.createProductsCountButton(...args), createProductsRampBadge: (...args) => productsDetails.createProductsRampBadge(...args), createCrmServiceRequestBadge: (...args) => opportunityCrmServiceRequests.createBadge(...args), createProductsDetailRow: (...args) => productsDetails.createProductsDetailRow(...args), createStageBadge: (...args) => opportunitiesLinks.createStageBadge(...args), createStatusBadge: (...args) => opportunitiesLinks.createStatusBadge(...args), displayOpportunityValue: (...args) => opportunitiesFormatters.displayOpportunityValue(...args), getNestedValue: (...args) => opportunitiesFormatters.getNestedValue(...args), getSortedOpportunities: (...args) => opportunitiesFilters.getSortedOpportunities(...args), opportunitiesTableState, productsContentState, renderCustomerFilter: (...args) => opportunitiesFilters.renderCustomerFilter(...args), renderOwnerFilter: (...args) => opportunitiesFilters.renderOwnerFilter(...args), renderStageDashboard: (...args) => opportunitiesDashboard.renderStageDashboard(...args), resetCustomerFilter: (...args) => opportunitiesFilters.resetCustomerFilter(...args), resetOwnerFilter: (...args) => opportunitiesFilters.resetOwnerFilter(...args), environment }); const opportunitiesDashboard = createOpportunitiesDashboard({ STAGE_DASHBOARD_ID: contracts.STAGE_DASHBOARD_ID, STAGE_OPTIONS: contracts.STAGE_OPTIONS, getNestedValue: (...args) => opportunitiesFormatters.getNestedValue(...args), opportunitiesDashboardState, environment }); const opportunitiesFilters = createOpportunitiesFilters({ matchesActivities: item => activityFilters.matches(item), CUSTOMER_FILTER_BUTTON_ID: contracts.CUSTOMER_FILTER_BUTTON_ID, CUSTOMER_FILTER_LIST_ID: contracts.CUSTOMER_FILTER_LIST_ID, CUSTOMER_FILTER_PANEL_ID: contracts.CUSTOMER_FILTER_PANEL_ID, CUSTOMER_FILTER_SEARCH_ID: contracts.CUSTOMER_FILTER_SEARCH_ID, CUSTOMER_FILTER_SELECT_ALL_ID: contracts.CUSTOMER_FILTER_SELECT_ALL_ID, OPPORTUNITIES_COLUMNS: columns, OWNER_FILTER_BUTTON_ID: contracts.OWNER_FILTER_BUTTON_ID, OWNER_FILTER_LIST_ID: contracts.OWNER_FILTER_LIST_ID, OWNER_FILTER_PANEL_ID: contracts.OWNER_FILTER_PANEL_ID, OWNER_FILTER_SEARCH_ID: contracts.OWNER_FILTER_SEARCH_ID, OWNER_FILTER_SELECT_ALL_ID: contracts.OWNER_FILTER_SELECT_ALL_ID, STAGE_FILTERS_ID: contracts.STAGE_FILTERS_ID, STAGE_OPTIONS: contracts.STAGE_OPTIONS, STATUS_FILTERS_ID: contracts.STATUS_FILTERS_ID, STATUS_OPTIONS: contracts.STATUS_OPTIONS, getCustomerFilterSummary: (...args) => opportunitiesFormatters.getCustomerFilterSummary(...args), getCustomerKey: (...args) => opportunitiesFormatters.getCustomerKey(...args), getCustomerOptions: (...args) => opportunitiesFormatters.getCustomerOptions(...args), getOwnerKey: (...args) => opportunitiesFormatters.getOwnerKey(...args), getOwnerOptions: (...args) => opportunitiesFormatters.getOwnerOptions(...args), opportunitiesFilterState, opportunitiesTableState, renderOpportunitiesTable: (...args) => opportunitiesTable.renderOpportunitiesTable(...args), saveFilterPreferences: (...args) => preferencesStorage.saveFilterPreferences(...args), stringifySearchValue: (...args) => opportunitiesFormatters.stringifySearchValue(...args), environment }); const opportunitiesFormatters = createOpportunitiesFormatters({ opportunitiesTableState }); const opportunitiesLinks = createOpportunitiesLinks({ MODAL_ID: contracts.MODAL_ID, environment }); const preferencesStorage = createPreferencesStorage({ DEFAULT_OPPORTUNITY_TYPE_VALUES: contracts.DEFAULT_OPPORTUNITY_TYPE_VALUES, DEFAULT_OPPTY_PRODUCTS_CACHE_HOURS: contracts.DEFAULT_OPPTY_PRODUCTS_CACHE_HOURS, FILTER_PREFERENCES_STORAGE_KEY: contracts.FILTER_PREFERENCES_STORAGE_KEY, MODAL_ID: contracts.MODAL_ID, OPPORTUNITY_TYPE_OPTIONS: contracts.OPPORTUNITY_TYPE_OPTIONS, OPPORTUNITY_TYPE_PREFERENCES_STORAGE_KEY: contracts.OPPORTUNITY_TYPE_PREFERENCES_STORAGE_KEY, OPPTY_PRODUCTS_CACHE_OPTIONS: contracts.OPPTY_PRODUCTS_CACHE_OPTIONS, PERIOD_OPTIONS: contracts.PERIOD_OPTIONS, STAGE_OPTIONS: contracts.STAGE_OPTIONS, STATUS_OPTIONS: contracts.STATUS_OPTIONS, opportunitiesFilterState, environment }); const preferencesView = createPreferencesView({ DEFAULT_OPPTY_PRODUCTS_CACHE_HOURS: contracts.DEFAULT_OPPTY_PRODUCTS_CACHE_HOURS, MODAL_ID: contracts.MODAL_ID, OPPORTUNITY_TYPE_OPTIONS: contracts.OPPORTUNITY_TYPE_OPTIONS, OPPORTUNITY_TYPE_PREFERENCES_STORAGE_KEY: contracts.OPPORTUNITY_TYPE_PREFERENCES_STORAGE_KEY, OPPTY_PRODUCTS_CACHE_OPTIONS: contracts.OPPTY_PRODUCTS_CACHE_OPTIONS, PERIOD_OPTIONS: contracts.PERIOD_OPTIONS, PREFERENCES_MODAL_ID: contracts.PREFERENCES_MODAL_ID, createCloseIcon: (...args) => contentIcons.createCloseIcon(...args), getSavedOpportunityTypeValues: (...args) => preferencesStorage.getSavedOpportunityTypeValues(...args), getSavedOpptyProductsCacheHours: (...args) => preferencesStorage.getSavedOpptyProductsCacheHours(...args), preferencesState, requestOpportunitiesForPeriod: (...args) => opportunitiesController.requestOpportunitiesForPeriod(...args), environment }); const refreshAll = createRefreshAll({ createRefreshIcon: () => contentIcons.createRefreshIcon(), messageTypes, activityCache, opportunitiesQueryState, productsContentState, consumptionPlansState, consumptionTrendState, aiSummaryState, environment, sendRuntimeMessage: (...args) => platformContentMessages.sendRuntimeMessage(...args), reload: (...args) => opportunitiesController.requestOpportunitiesForPeriod(...args), onFailure: error => opportunitiesTable.setOpportunitiesTableState({ status: "error", message: error.message || "Unable to refresh cached data." }), notify: (...args) => opportunitiesLinks.showCopyConfirmation(...args), resetRelatedData: () => { opportunityCrmServiceRequests.reset(); opportunityWorkloadActions.reset(); consumptionPlansView.closeAccountPlansModal(); installBaseView.closeInstallBaseModal(); serviceRequestsView.closeServiceRequestsModal(); installBaseAction.resetRequests(); serviceRequestsAction.resetRequests(); ratecardController.resetRequests(); consumptionPlanDetailController.resetRequests(); productsContentJobs.renderProductsProgress(); opportunitiesTable.setOpportunitiesTableState({ status: "loading", message: "Refreshing data..." }); } }); const activityProgress = createActivityProgress({ MODAL_ID: contracts.MODAL_ID, environment }); const srSessionPreparation = createSrSessionPreparation({ async onReady() { const result = await platformContentMessages.sendRuntimeMessage({ type: messageTypes.requestResourceUser, emailAddress: currentUser.getUserId() }); debugContentLog.appendBackgroundRequestLog(result?.debugRequests || []); if (!result?.ok) throw new Error(result?.error || "Unable to load resource user."); }, ensureCurrentUser: () => currentUser.ensure(), allowsCrmActivities: () => currentUser.allowsCrmActivities(), sendRequest: () => platformContentMessages.sendRuntimeMessage({ type: messageTypes.prepareSrSession }), reportProgress: state => { activityProgress.update("session", state); manageTime.setReady(state === "complete"); }, appendLog: entries => debugContentLog.appendBackgroundRequestLog(entries) }); const activityFilters = createActivityFilters({ setBadgeContent: (...args) => opportunityBadgeContent.setContent(...args), allowsCrmActivities: () => currentUser.allowsCrmActivities(), getSrSummary: number => opportunityCrmServiceRequests.getSummary(number), getActionSummary: number => opportunityWorkloadActions.getSummary(number), renderTable: () => opportunitiesTable.renderOpportunitiesTable(), environment }); const productActivities = createProductActivities({ productsContentState, opportunitiesQueryState, loadCrm: (...args) => opportunityCrmServiceRequests.load(...args), loadActions: (...args) => opportunityWorkloadActions.loadProducts(...args), blockActions: products => opportunityWorkloadActions.blockProducts(products), render: () => { productsContentJobs.renderProductsProgress(); productsContentJobs.queueProductsTableRender(); } }); const productsContentJobs = createProductsContentJobs({ beginProductActivities: (...args) => productActivities.begin(...args), completeProductActivities: (...args) => productActivities.complete(...args), appendBackgroundRequestLog: (...args) => debugContentLog.appendBackgroundRequestLog(...args), messageTypes, PRODUCTS_CACHE_TIMESTAMP_ID: contracts.PRODUCTS_CACHE_TIMESTAMP_ID, PRODUCTS_MESSAGE_LISTENER_KEY: contracts.PRODUCTS_MESSAGE_LISTENER_KEY, PRODUCTS_PROGRESS_ID: contracts.PRODUCTS_PROGRESS_ID, preferencesState, productsContentState, renderOpportunitiesTable: (...args) => opportunitiesTable.renderOpportunitiesTable(...args), sendRuntimeMessage: (...args) => platformContentMessages.sendRuntimeMessage(...args), environment }); const workloadActionOptions = createWorkloadActionOptions({ requestConsumerTokenServiceOnce: (...args) => authContentSession.requestConsumerTokenServiceOnce(...args), sendRuntimeMessage: (...args) => platformContentMessages.sendRuntimeMessage(...args), appendBackgroundRequestLog: (...args) => debugContentLog.appendBackgroundRequestLog(...args), messageTypes }); const workloadActionForm = createWorkloadActionForm({ searchableSelect, onActionSaved: (...args) => opportunityWorkloadActions.applySavedAction(...args), saveAction: (...args) => workloadActionOptions.save(...args), notify: (...args) => opportunitiesLinks.showCopyConfirmation(...args), getCurrentUserId: () => currentUser.getUserId(), requestTeamMembers: (...args) => workloadActionOptions.requestTeamMembers(...args), requestLookup: (...args) => workloadActionOptions.requestLookup(...args), getProducts: number => productsContentState.opptyProductsByNumber.get(String(number))?.items || [], getOpportunity: number => opportunitiesTableState.opportunitiesTableState.items.find(item => String(item.OptyNumber) === String(number)), formatDate: value => opportunitiesFormatters.formatOracleResponseDate(value), MODAL_ID: contracts.MODAL_ID, allowsCrmActivities: () => currentUser.allowsCrmActivities(), isMissingAction: number => opportunityWorkloadActions.getSummary(number)?.state === "missing", createCloseIcon: () => contentIcons.createCloseIcon(), environment }); const opportunityWorkloadActions = createOpportunityWorkloadActions({ setBadgeContent: (...args) => opportunityBadgeContent.setContent(...args), createActionButton: number => workloadActionForm.createButton(number), closeActionForm: (...args) => workloadActionForm.close(...args), activityCache, reportProgress: (...args) => activityProgress.update("actions", ...args), allowsCrmActivities: () => currentUser.allowsCrmActivities(), authSessionState, requestConsumerTokenServiceOnce: (...args) => authContentSession.requestConsumerTokenServiceOnce(...args), sendRuntimeMessage: (...args) => platformContentMessages.sendRuntimeMessage(...args), appendBackgroundRequestLog: (...args) => debugContentLog.appendBackgroundRequestLog(...args), queueProductsTableRender: () => productsContentJobs.queueProductsTableRender(), productsContentState, messageTypes, environment }); const productsDetails = createProductsDetails({ setBadgeContent: (...args) => opportunityBadgeContent.setContent(...args), OPPORTUNITIES_COLUMNS: columns, createAiSummaryAlert: (...args) => aiSummaryContent.createAiSummaryAlert(...args), formatOracleResponseDate: (...args) => opportunitiesFormatters.formatOracleResponseDate(...args), productsContentState, renderOpportunitiesTable: (...args) => opportunitiesTable.renderOpportunitiesTable(...args), requestGenerateAiSummary: (...args) => aiSummaryContent.requestGenerateAiSummary(...args), environment }); const aiSummaryContent = createAiSummaryContent({ aiSummaryState, invalidateSalesCommonToken: (...args) => authSalesCommon.invalidateSalesCommonToken(...args), logRequestFailure: (...args) => debugContentLog.logRequestFailure(...args), logRequestStart: (...args) => debugContentLog.logRequestStart(...args), logRequestSuccess: (...args) => debugContentLog.logRequestSuccess(...args), readResponseBodyForDebug: (...args) => debugContentLog.readResponseBodyForDebug(...args), requestTokenRelaySalesCommon: (...args) => authSalesCommon.requestTokenRelaySalesCommon(...args), environment }); const customerTableSearch = createCustomerTableSearch({ environment }); const installBaseView = createInstallBaseView({ createSearch: (...args) => customerTableSearch.createSearch(...args), MODAL_ID: contracts.MODAL_ID, createCloseIcon: (...args) => contentIcons.createCloseIcon(...args), formatOracleResponseDate: (...args) => opportunitiesFormatters.formatOracleResponseDate(...args), environment }); const installBaseAction = createInstallBaseAction({ messageTypes, requestConsumerTokenServiceOnce: (...args) => authContentSession.requestConsumerTokenServiceOnce(...args), sendRuntimeMessage: (...args) => platformContentMessages.sendRuntimeMessage(...args), appendBackgroundRequestLog: (...args) => debugContentLog.appendBackgroundRequestLog(...args), openInstallBaseModal: (...args) => installBaseView.openInstallBaseModal(...args), environment }); const serviceRequestsView = createServiceRequestsView({ createSearch: (...args) => customerTableSearch.createSearch(...args), MODAL_ID: contracts.MODAL_ID, createCloseIcon: (...args) => contentIcons.createCloseIcon(...args), formatOracleResponseDate: (...args) => opportunitiesFormatters.formatOracleResponseDate(...args), environment }); const serviceRequestsAction = createServiceRequestsAction({ messageTypes, requestConsumerTokenServiceOnce: (...args) => authContentSession.requestConsumerTokenServiceOnce(...args), sendRuntimeMessage: (...args) => platformContentMessages.sendRuntimeMessage(...args), appendBackgroundRequestLog: (...args) => debugContentLog.appendBackgroundRequestLog(...args), openServiceRequestsModal: (...args) => serviceRequestsView.openServiceRequestsModal(...args), environment }); const consumptionPlansAction = createConsumptionPlansAction({ messageTypes, appendBackgroundRequestLog: (...args) => debugContentLog.appendBackgroundRequestLog(...args), consumptionPlansState, getNestedValue: (...args) => opportunitiesFormatters.getNestedValue(...args), openAccountPlansModal: (...args) => consumptionPlansView.openAccountPlansModal(...args), renderOpportunitiesTable: (...args) => opportunitiesTable.renderOpportunitiesTable(...args), requestConsumerTokenServiceOnce: (...args) => authContentSession.requestConsumerTokenServiceOnce(...args), sendRuntimeMessage: (...args) => platformContentMessages.sendRuntimeMessage(...args), showCopyConfirmation: (...args) => opportunitiesLinks.showCopyConfirmation(...args), environment }); const consumptionPlanDetailView = createConsumptionPlanDetailView({ createCloseIcon: (...args) => contentIcons.createCloseIcon(...args), environment }); const consumptionPlanDetailController = createConsumptionPlanDetailController({ messageTypes, requestConsumerTokenServiceOnce: (...args) => authContentSession.requestConsumerTokenServiceOnce(...args), sendRuntimeMessage: (...args) => platformContentMessages.sendRuntimeMessage(...args), appendBackgroundRequestLog: (...args) => debugContentLog.appendBackgroundRequestLog(...args), renderPlanDetailBadges: (...args) => consumptionPlanDetailView.renderPlanDetailBadges(...args), environment }); const ratecardModel = createRatecardModel(); const consumptionHeaderView = createConsumptionHeaderView({ createCloseIcon: (...args) => contentIcons.createCloseIcon(...args), formatOracleResponseDate: (...args) => opportunitiesFormatters.formatOracleResponseDate(...args), environment }); const ratecardView = createRatecardView({ ACCOUNT_PLANS_MODAL_ID: contracts.ACCOUNT_PLANS_MODAL_ID, columns: ratecardModel.columns, formatValue: (...args) => ratecardModel.formatValue(...args), selectItems: (...args) => ratecardModel.selectItems(...args), createPlanDetails: (...args) => consumptionHeaderView.createDetails(...args), createConsumptionHeader: (...args) => consumptionHeaderView.createHeader(...args), environment }); const ratecardController = createRatecardController({ messageTypes, requestConsumerTokenServiceOnce: (...args) => authContentSession.requestConsumerTokenServiceOnce(...args), sendRuntimeMessage: (...args) => platformContentMessages.sendRuntimeMessage(...args), appendBackgroundRequestLog: (...args) => debugContentLog.appendBackgroundRequestLog(...args), openRatecardDrawer: (...args) => ratecardView.openRatecardDrawer(...args), closePlanDetailPopover: (...args) => consumptionPlanDetailView.closePlanDetailPopover(...args), environment }); const consumptionPlansView = createConsumptionPlansView({ ACCOUNT_PLANS_MODAL_ID: contracts.ACCOUNT_PLANS_MODAL_ID, DEFAULT_CONSUMPTION_TREND_PERIOD: contracts.DEFAULT_CONSUMPTION_TREND_PERIOD, MODAL_ID: contracts.MODAL_ID, createConsumptionHeader: (...args) => consumptionHeaderView.createHeader(...args), createConsumptionTrendSection: (...args) => consumptionTrendController.createConsumptionTrendSection(...args), loadPlanDetailForPanel: (...args) => consumptionPlanDetailController.loadPlanDetailForPanel(...args), closePlanDetailPopover: (...args) => consumptionPlanDetailView.closePlanDetailPopover(...args), createRatecardButton: (...args) => ratecardController.createRatecardButton(...args), closeRatecardDrawer: (...args) => ratecardView.closeRatecardDrawer(...args), createSvgElement: (...args) => consumptionChart.createSvgElement(...args), createPlanDetails: (...args) => consumptionHeaderView.createDetails(...args), getNestedValue: (...args) => opportunitiesFormatters.getNestedValue(...args), loadConsumptionTrendForPanel: (...args) => consumptionTrendController.loadConsumptionTrendForPanel(...args), environment }); const consumptionTrendController = createConsumptionTrendController({ createPlanDetailSection: (...args) => consumptionPlanDetailController.createPlanDetailSection(...args), messageTypes, CONSUMPTION_TREND_PERIODS: contracts.CONSUMPTION_TREND_PERIODS, DEFAULT_CONSUMPTION_TREND_PERIOD: contracts.DEFAULT_CONSUMPTION_TREND_PERIOD, appendBackgroundRequestLog: (...args) => debugContentLog.appendBackgroundRequestLog(...args), consumptionTrendState, renderConsumptionTrendChart: (...args) => consumptionChart.renderConsumptionTrendChart(...args), requestConsumerTokenServiceOnce: (...args) => authContentSession.requestConsumerTokenServiceOnce(...args), sendRuntimeMessage: (...args) => platformContentMessages.sendRuntimeMessage(...args), environment }); const consumptionChart = createConsumptionChart({ CONSUMPTION_TREND_PERIODS: contracts.CONSUMPTION_TREND_PERIODS, CONSUMPTION_TREND_SERIES: contracts.CONSUMPTION_TREND_SERIES, SVG_NAMESPACE: contracts.SVG_NAMESPACE, calculateAverageConsumption: (...args) => consumptionModel.calculateAverageConsumption(...args), consumptionTrendState, createConsumptionTrendKey: (...args) => consumptionTrendController.createConsumptionTrendKey(...args), createNiceConsumptionMaximum: (...args) => consumptionModel.createNiceConsumptionMaximum(...args), formatCompactConsumptionNumber: (...args) => consumptionModel.formatCompactConsumptionNumber(...args), formatConsumptionCurrency: (...args) => consumptionModel.formatConsumptionCurrency(...args), formatConsumptionPointLabel: (...args) => consumptionModel.formatConsumptionPointLabel(...args), formatConsumptionRange: (...args) => consumptionModel.formatConsumptionRange(...args), normalizeConsumptionTrendItems: (...args) => consumptionModel.normalizeConsumptionTrendItems(...args), environment }); const consumptionModel = createConsumptionModel({ }); const debugContentLog = createDebugContentLog({ previewDebugBody: createDebugBodyPreview, debugState, renderDebugPanel: (...args) => debugContentView.renderDebugPanel(...args) }); const debugContentView = createDebugContentView({ createCloseIcon: (...args) => contentIcons.createCloseIcon(...args), DEBUG_PANEL_ID: contracts.DEBUG_PANEL_ID, MODAL_ID: contracts.MODAL_ID, debugState, formatDebugBody: (...args) => debugContentLog.formatDebugBody(...args), formatHeaderBlock: (...args) => debugContentLog.formatHeaderBlock(...args), formatMetadataBlock: (...args) => debugContentLog.formatMetadataBlock(...args), environment }); function start() { startContentApplication({ environment, page: contentFusionPage, styles: contentStyles, products: productsContentJobs }); } return { opportunityCalendar, workloadActionForm, activityFilters, start, contracts, opportunityWorkloadActions, fusionCrmServiceRequests, opportunityCrmServiceRequests, authSessionState, authSalesCommonState, opportunitiesQueryState, opportunitiesTableState, opportunitiesFilterState, preferencesState, productsContentState, aiSummaryState, consumptionPlansState, consumptionTrendState, opportunitiesDashboardState, debugState, contentFusionPage, contentModal, contentTheme, contentIcons, contentStyles, authContentSession, currentUser, authSalesCommon, authTokenRelay, authXsrfContent, platformContentMessages, opportunitiesController, fusionOpportunities, opportunitiesFiscalPeriods, opportunitiesTable, opportunitiesDashboard, opportunitiesFilters, opportunitiesFormatters, opportunitiesLinks, preferencesStorage, preferencesView, productsContentJobs, productActivities, activityProgress, srSessionPreparation, refreshAll, activityCache, productsDetails, aiSummaryContent, consumptionPlansAction, installBaseAction, installBaseView, serviceRequestsAction, serviceRequestsView, consumptionPlansView, consumptionPlanDetailController, consumptionPlanDetailView, ratecardModel, ratecardView, ratecardController, consumptionTrendController, consumptionChart, consumptionModel, debugContentLog, debugContentView }; } createContentApplication().start(); })();