10349 lines
903 KiB
JavaScript
10349 lines
903 KiB
JavaScript
(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-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-duplicate-row { outline: 2px solid #b3261e; outline-offset: -2px; animation: oe-time-duplicate-fade 5.5s linear forwards; }\n.opportunities-extension-modal[data-theme=\"dark\"] .oe-time-duplicate-row { --time-duplicate-color: #ffaaa4; }\n@keyframes oe-time-duplicate-fade { 0%, 90.909% { outline-color: var(--time-duplicate-color, #b3261e); } 100% { outline-color: transparent; } }\n@media (prefers-reduced-motion: reduce) { .oe-time-duplicate-row { animation-timing-function: step-end; } }\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-loading-status { position: absolute; width: 1px; height: 1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }\n.oe-time-skeleton { display: inline-block; vertical-align: middle; border-radius: 4px; background: var(--time-border); color: transparent; animation: oe-time-pulse 1.3s ease-in-out infinite alternate; }\n.oe-time-skeleton-select { display: block; width: 100%; height: 38px; }\n.oe-time-skeleton-control { width: min(100%, 60px); height: 38px; }\n.oe-time-skeleton-heading { width: 65%; height: 20px; margin: 17px 0; }\n.oe-time-skeleton-summary { min-width: 44px; height: 20px; }\n@keyframes oe-time-pulse { from { opacity: .4; } to { opacity: 1; } }\n@media (prefers-reduced-motion: reduce) { .oe-time-skeleton { animation: none; } }\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;t<e.__k.length;t++)if(null!=(n=e.__k[t])&&null!=n.__e)return n.__e;return"function"==typeof e.type?w(e):null}function C(e){var t,n;if(null!=(e=e.__)&&null!=e.__c){for(e.__e=e.__c.base=null,t=0;t<e.__k.length;t++)if(null!=(n=e.__k[t])&&null!=n.__e){e.__e=e.__c.base=n.__e;break}return C(e)}}function R(e){s?setTimeout(e):l(e)}function x(e){(!e.__d&&(e.__d=!0)&&o.push(e)&&!_.__r++||a!==n.debounceRendering)&&((a=n.debounceRendering)||R)(_)}function _(){var e,t,n,r,i,s,a,l;for(o.sort((function(e,t){return e.__v.__b-t.__v.__b}));e=o.shift();)e.__d&&(t=o.length,r=void 0,i=void 0,a=(s=(n=e).__v).__e,(l=n.__P)&&(r=[],(i=f({},s)).__v=s.__v+1,N(l,s,i,n.__n,void 0!==l.ownerSVGElement,null!=s.__h?[a]:null,r,null==a?w(s):a,s.__h),P(r,s),s.__e!=a&&C(s)),o.length>t&&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;h<t.length;h++)if(null!=(p=n.__k[h]=null==(p=t[h])||"boolean"==typeof p?null:"string"==typeof p||"number"==typeof p||"bigint"==typeof p?m(null,p,null,null,p):Array.isArray(p)?m(y,{children:p},null,null,null):p.__b>0?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<A;f++){if((g=S[f])&&p.key==g.key&&p.type===g.type){S[f]=void 0;break}g=null}N(e,p,g=g||d,i,s,o,a,l,c),v=p.__e,(f=p.ref)&&g.ref!=f&&(E||(E=[]),g.ref&&E.push(g.ref,null,p),E.push(f,p.__c||v,p)),null!=v?(null==b&&(b=v),"function"==typeof p.type&&p.__k===g.__k?p.__d=l=k(p,l,e):l=I(e,p,g,S,v,l),"function"==typeof n.type&&(n.__d=l)):l&&g.__e==l&&l.parentNode!=e&&(l=w(g))}for(n.__e=b,h=A;h--;)null!=S[h]&&("function"==typeof n.type&&null!=S[h].__e&&S[h].__e==n.__d&&(n.__d=O(r).nextSibling),j(S[h],S[h]));if(E)for(h=0;h<E.length;h++)B(E[h],E[++h],E[++h])}function k(e,t,n){for(var r,i=e.__k,s=0;i&&s<i.length;s++)(r=i[s])&&(r.__=e,t="function"==typeof r.type?k(r,t,n):I(n,r,r,i,r.__e,t));return t}function M(e,t){return t=t||[],null==e||"boolean"==typeof e||(Array.isArray(e)?e.some((function(e){M(e,t)})):t.push(e)),t}function I(e,t,n,r,i,s){var o,a,l;if(void 0!==t.__d)o=t.__d,t.__d=void 0;else if(null==n||i!=s||null==i.parentNode)e:if(null==s||s.parentNode!==e)e.appendChild(i),o=null;else{for(a=s,l=0;(a=a.nextSibling)&&l<r.length;l+=1)if(a==i)break e;e.insertBefore(i,s),o=s}return void 0!==o?o:i.nextSibling}function O(e){var t,n,r;if(null==e.type||"string"==typeof e.type)return e.__e;if(e.__k)for(t=e.__k.length-1;t>=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;A<u._sb.length;A++)u.__h.push(u._sb[A]);u._sb=[],u.__h.length&&a.push(u);break e}null!=u.componentWillUpdate&&u.componentWillUpdate(b,u.__s,S),null!=u.componentDidUpdate&&u.__h.push((function(){u.componentDidUpdate(g,p,m)}))}if(u.context=S,u.props=b,u.__P=e,w=n.__r,C=0,"prototype"in _&&_.prototype.render){for(u.state=u.__s,u.__d=!1,w&&w(t),d=u.render(u.props,u.state,u.context),R=0;R<u._sb.length;R++)u.__h.push(u._sb[R]);u._sb=[]}else do{u.__d=!1,w&&w(t),d=u.render(u.props,u.state,u.context),u.state=u.__s}while(u.__d&&++C<25);u.state=u.__s,null!=u.getChildContext&&(i=f(f({},i),u.getChildContext())),h||null==u.getSnapshotBeforeUpdate||(m=u.getSnapshotBeforeUpdate(g,p)),x=null!=d&&d.type===y&&null==d.key?d.props.children:d,T(e,Array.isArray(x)?x:[x],t,r,i,s,o,a,l,c),u.base=t.__e,t.__h=null,u.__h.length&&a.push(u),v&&(u.__E=u.__=null),u.__e=!1}else null==o&&t.__v===r.__v?(t.__k=r.__k,t.__e=r.__e):t.__e=H(r.__e,t,r,i,s,o,a,c);(d=n.diffed)&&d(t)}catch(e){t.__v=null,(c||null!=o)&&(t.__e=l,t.__h=!!c,o[o.indexOf(l)]=null),n.__e(e,t,r)}}function P(e,t){n.__c&&n.__c(t,e),e.some((function(t){try{e=t.__h,t.__h=[],e.some((function(e){e.call(t)}))}catch(e){n.__e(e,t.__v)}}))}function H(e,n,r,i,s,o,a,l){var c,u,h,f=r.props,p=n.props,m=n.type,v=0;if("svg"===m&&(s=!0),null!=o)for(;v<o.length;v++)if((c=o[v])&&"setAttribute"in c==!!m&&(m?c.localName===m:3===c.nodeType)){e=c,o[v]=null;break}if(null==e){if(null===m)return document.createTextNode(p);e=s?document.createElementNS("http://www.w3.org/2000/svg",m):document.createElement(m,p.is&&p),o=null,l=!1}if(null===m)f===p||l&&e.data===p||(e.data=p);else{if(o=o&&t.call(e.childNodes),u=(f=r.props||d).dangerouslySetInnerHTML,h=p.dangerouslySetInnerHTML,!l){if(null!=o)for(f={},v=0;v<e.attributes.length;v++)f[e.attributes[v].name]=e.attributes[v].value;(h||u)&&(h&&(u&&h.__html==u.__html||h.__html===e.innerHTML)||(e.innerHTML=h&&h.__html||""))}if(function(e,t,n,r,i){var s;for(s in n)"children"===s||"key"===s||s in t||E(e,s,null,n[s],r);for(s in t)i&&"function"!=typeof t[s]||"children"===s||"key"===s||"value"===s||"checked"===s||n[s]===t[s]||E(e,s,t[s],n[s],r)}(e,p,f,s,l),h)n.__k=[];else if(v=n.props.children,T(e,Array.isArray(v)?v:[v],n,r,i,s&&"foreignObject"!==m,o,a,o?o[0]:r.__k&&w(r,0),l),null!=o)for(v=o.length;v--;)null!=o[v]&&g(o[v]);l||("value"in p&&void 0!==(v=p.value)&&(v!==e.value||"progress"===m&&!v||"option"===m&&v!==f.value)&&E(e,"value",v,f.value,!1),"checked"in p&&void 0!==(v=p.checked)&&v!==e.checked&&E(e,"checked",v,f.checked,!1))}return e}function B(e,t,r){try{"function"==typeof e?e(t):e.current=t}catch(e){n.__e(e,r)}}function j(e,t,r){var i,s;if(n.unmount&&n.unmount(e),(i=e.ref)&&(i.current&&i.current!==e.__e||B(i,null,t)),null!=(i=e.__c)){if(i.componentWillUnmount)try{i.componentWillUnmount()}catch(e){n.__e(e,t)}i.base=i.__P=null,e.__c=void 0}if(i=e.__k)for(s=0;s<i.length;s++)i[s]&&j(i[s],t,r||"function"!=typeof e.type);r||null==e.__e||g(e.__e),e.__=e.__e=e.__d=void 0}function z(e,t,n){return this.constructor(e,n)}function U(e,r,i){var s,o,a;n.__&&n.__(e,r),o=(s="function"==typeof i)?null:i&&i.__k||r.__k,a=[],N(r,e=(!s&&i||r).__k=p(y,null,[e]),o||d,d,void 0!==r.ownerSVGElement,!s&&i?[i]:o?null:r.firstChild?t.call(r.childNodes):null,a,!s&&i?i:o?o.__e:r.firstChild,s),P(a,e)}t=u.slice,n={__e:function(e,t,n,r){for(var i,s,o;t=t.__;)if((i=t.__c)&&!i.__)try{if((s=i.constructor)&&null!=s.getDerivedStateFromError&&(i.setState(s.getDerivedStateFromError(e)),o=i.__d),null!=i.componentDidCatch&&(i.componentDidCatch(e,r||{}),o=i.__d),o)return i.__E=i}catch(t){e=t}throw e}},r=0,i=function(e){return null!=e&&void 0===e.constructor},s=!1,D.prototype.setState=function(e,t){var n;n=null!=this.__s&&this.__s!==this.state?this.__s:this.__s=f({},this.state),"function"==typeof e&&(e=e(f({},n),this.props)),e&&f(n,e),null!=e&&this.__v&&(t&&this._sb.push(t),x(this))},D.prototype.forceUpdate=function(e){this.__v&&(this.__e=!0,e&&this.__h.push(e),x(this))},D.prototype.render=y,o=[],l="function"==typeof Promise?Promise.prototype.then.bind(Promise.resolve()):setTimeout,_.__r=0,c=0;var L,W,F,V=[],G=[],Q=n.__b,q=n.__r,Y=n.diffed,Z=n.__c,X=n.unmount;function $(){for(var e;e=V.shift();)if(e.__P&&e.__H)try{e.__H.__h.forEach(ee),e.__H.__h.forEach(te),e.__H.__h=[]}catch(t){e.__H.__h=[],n.__e(t,e.__v)}}n.__b=function(e){L=null,Q&&Q(e)},n.__r=function(e){q&&q(e);var t=(L=e.__c).__H;t&&(W===L?(t.__h=[],L.__h=[],t.__.forEach((function(e){e.__N&&(e.__=e.__N),e.__V=G,e.__N=e.i=void 0}))):(t.__h.forEach(ee),t.__h.forEach(te),t.__h=[])),W=L},n.diffed=function(e){Y&&Y(e);var t=e.__c;t&&t.__H&&(t.__H.__h.length&&(1!==V.push(t)&&F===n.requestAnimationFrame||((F=n.requestAnimationFrame)||K)($)),t.__H.__.forEach((function(e){e.i&&(e.__H=e.i),e.__V!==G&&(e.__=e.__V),e.i=void 0,e.__V=G}))),W=L=null},n.__c=function(e,t){t.some((function(e){try{e.__h.forEach(ee),e.__h=e.__h.filter((function(e){return!e.__||te(e)}))}catch(r){t.some((function(e){e.__h&&(e.__h=[])})),t=[],n.__e(r,e.__v)}})),Z&&Z(e,t)},n.unmount=function(e){X&&X(e);var t,r=e.__c;r&&r.__H&&(r.__H.__.forEach((function(e){try{ee(e)}catch(e){t=e}})),r.__H=void 0,t&&n.__e(t,r.__v))};var J="function"==typeof requestAnimationFrame;function K(e){var t,n=function(){clearTimeout(r),J&&cancelAnimationFrame(t),setTimeout(e)},r=setTimeout(n,100);J&&(t=requestAnimationFrame(n))}function ee(e){var t=L,n=e.__c;"function"==typeof n&&(e.__c=void 0,n()),L=t}function te(e){var t=L;e.__c=e.__(),L=t}function ne(e,t){for(var n in e)if("__source"!==n&&!(n in t))return!0;for(var r in t)if("__source"!==r&&e[r]!==t[r])return!0;return!1}function re(e){this.props=e}(re.prototype=new D).isPureReactComponent=!0,re.prototype.shouldComponentUpdate=function(e,t){return ne(this.props,e)||ne(this.state,t)};var ie=n.__b;n.__b=function(e){e.type&&e.type.__f&&e.ref&&(e.props.ref=e.ref,e.ref=null),ie&&ie(e)};var se=n.__e;n.__e=function(e,t,n,r){if(e.then)for(var i,s=t;s=s.__;)if((i=s.__c)&&i.__c)return null==t.__e&&(t.__e=n.__e,t.__k=n.__k),i.__c(e,t);se(e,t,n,r)};var oe=n.unmount;function ae(){this.__u=0,this.t=null,this.__b=null}function le(e){var t=e.__.__c;return t&&t.__a&&t.__a(e)}function ce(){this.u=null,this.o=null}n.unmount=function(e){var t=e.__c;t&&t.__R&&t.__R(),t&&!0===e.__h&&(e.type=null),oe&&oe(e)},(ae.prototype=new D).__c=function(e,t){var n=t.__c,r=this;null==r.t&&(r.t=[]),r.t.push(n);var i=le(r.__v),s=!1,o=function(){s||(s=!0,n.__R=null,i?i(a):a())};n.__R=o;var a=function(){if(!--r.__u){if(r.state.__a){var e=r.state.__a;r.__v.__k[0]=function e(t,n,r){return t&&(t.__v=null,t.__k=t.__k&&t.__k.map((function(t){return e(t,n,r)})),t.__c&&t.__c.__P===n&&(t.__e&&r.insertBefore(t.__e,t.__d),t.__c.__e=!0,t.__c.__P=r)),t}(e,e.__c.__P,e.__c.__O)}var t;for(r.setState({__a:r.__b=null});t=r.t.pop();)t.forceUpdate()}},l=!0===t.__h;r.__u++||l||r.setState({__a:r.__b=r.__v.__k[0]}),e.then(o,o)},ae.prototype.componentWillUnmount=function(){this.t=[]},ae.prototype.render=function(e,t){if(this.__b){if(this.__v.__k){var n=document.createElement("div"),r=this.__v.__k[0].__c;this.__v.__k[0]=function e(t,n,r){return t&&(t.__c&&t.__c.__H&&(t.__c.__H.__.forEach((function(e){"function"==typeof e.__c&&e.__c()})),t.__c.__H=null),null!=(t=function(e,t){for(var n in t)e[n]=t[n];return e}({},t)).__c&&(t.__c.__P===r&&(t.__c.__P=n),t.__c=null),t.__k=t.__k&&t.__k.map((function(t){return e(t,n,r)}))),t}(this.__b,n,r.__O=r.__P)}this.__b=null}var i=t.__a&&p(y,null,e.fallback);return i&&(i.__h=null),[p(y,null,t.__a?null:e.children),i]};var de=function(e,t,n){if(++n[1]===n[0]&&e.o.delete(t),e.props.revealOrder&&("t"!==e.props.revealOrder[0]||!e.o.size))for(n=e.u;n;){for(;n.length>3;)n.pop()();if(n[1]<n[0])break;e.u=n=n[2]}};function ue(e){return this.getChildContext=function(){return e.context},e.children}function he(e){var t=this,n=e.i;t.componentWillUnmount=function(){U(null,t.l),t.l=null,t.i=null},t.i&&t.i!==n&&t.componentWillUnmount(),e.__v?(t.l||(t.i=n,t.l={nodeType:1,parentNode:n,childNodes:[],appendChild:function(e){this.childNodes.push(e),t.i.appendChild(e)},insertBefore:function(e,n){this.childNodes.push(e),t.i.appendChild(e)},removeChild:function(e){this.childNodes.splice(this.childNodes.indexOf(e)>>>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<n.length;e+=1){let i=n[e].querySelectorAll(t);for(let e=0;e<i.length;e+=1)r.push(i[e])}return r}const He=/(top|left|right|bottom|width|height)$/i;function Be(e,t){for(let n in t)je(e,n,t[n])}function je(e,t,n){null==n?e.style[t]="":"number"==typeof n&&He.test(t)?e.style[t]=n+"px":e.style[t]=n}function ze(e){var t,n;return null!==(n=null===(t=e.composedPath)||void 0===t?void 0:t.call(e)[0])&&void 0!==n?n:e.target}let Ue=0;function Le(){return Ue+=1,"fc-dom-"+Ue}function We(e){e.preventDefault()}function Fe(e,t,n,r){let i=function(e,t){return n=>{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;t<i.length;t+=1)n=i[t],"string"==typeof n?r.push("-"===n.charAt(0)?{field:n.substring(1),order:-1}:{field:n,order:1}):"function"==typeof n&&r.push({func:n});return r}function rt(e,t,n){let r,i;for(r=0;r<n.length;r+=1)if(i=it(e,t,n[r]),i)return i;return 0}function it(e,t,n){return n.func?n.func(e,t):st(e[n.field],t[n.field])*(n.order||1)}function st(e,t){return e||t?null==t?-1:null==e?1:"string"==typeof e||"string"==typeof t?String(e).localeCompare(String(t)):e-t:0}function ot(e,t){let n=String(e);return"000".substr(0,t-n.length)+n}function at(e,t,n){return"function"==typeof e?e(...t):"string"==typeof e?t.reduce((e,t,n)=>e.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;r<ut.length;r+=1){let i=ut[r];if(t[i]){let r=e[i]/t[i];if(!ct(r)||null!==n&&n!==r)return null;n=r}else if(e[i])return null}return n}function Et(e){let t=e.milliseconds;if(t){if(t%1e3!=0)return{unit:"millisecond",value:t};if(t%6e4!=0)return{unit:"second",value:t/1e3};if(t%36e5!=0)return{unit:"minute",value:t/6e4};if(t)return{unit:"hour",value:t/36e5}}return e.days?e.specifiedWeeks&&e.days%7==0?{unit:"week",value:e.days/7}:{unit:"day",value:e.days}:e.months?{unit:"month",value:e.months}:e.years?{unit:"year",value:e.years}:{unit:"millisecond",value:0}}function St(e,t,n){if(e===t)return!0;let r,i=e.length;if(i!==t.length)return!1;for(r=0;r<i;r+=1)if(!(n?n(e[r],t[r]):e[r]===t[r]))return!1;return!0}const At=["sun","mon","tue","wed","thu","fri","sat"];function Dt(e,t){let n=Ht(e);return n[2]+=7*t,Bt(n)}function wt(e,t){let n=Ht(e);return n[2]+=t,Bt(n)}function Ct(e,t){let n=Ht(e);return n[6]+=t,Bt(n)}function Rt(e,t){return xt(e,t)/7}function xt(e,t){return(t.valueOf()-e.valueOf())/864e5}function _t(e,t){let n=Mt(e),r=Mt(t);return{years:0,months:0,days:Math.round(xt(n,r)),milliseconds:t.valueOf()-r.valueOf()-(e.valueOf()-n.valueOf())}}function Tt(e,t){let n=kt(e,t);return null!==n&&n%7==0?n/7:null}function kt(e,t){return zt(e)===zt(t)?Math.round(xt(e,t)):null}function Mt(e){return Bt([e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate()])}function It(e,t,n,r){let i=Bt([t,0,1+Ot(t,n,r)]),s=Mt(e),o=Math.round(xt(i,s));return Math.floor(o/7)+1}function Ot(e,t,n){let r=7+t-n;return-((7+Bt([e,0,r]).getUTCDay()-t)%7)+r-1}function Nt(e){return[e.getFullYear(),e.getMonth(),e.getDate(),e.getHours(),e.getMinutes(),e.getSeconds(),e.getMilliseconds()]}function Pt(e){return new Date(e[0],e[1]||0,null==e[2]?1:e[2],e[3]||0,e[4]||0,e[5]||0)}function Ht(e){return[e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds()]}function Bt(e){return 1===e.length&&(e=e.concat([0])),new Date(Date.UTC(...e))}function jt(e){return!isNaN(e.valueOf())}function zt(e){return 1e3*e.getUTCHours()*60*60+1e3*e.getUTCMinutes()*60+1e3*e.getUTCSeconds()+e.getUTCMilliseconds()}function Ut(e,t,n=!1){let r=e.toISOString();return r=r.replace(".000",""),n&&(r=r.replace("T00:00:00Z","")),r.length>10&&(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(;i<e.length;){let s=e.indexOf(t,i);if(-1===s)break;let o=e.substr(0,s);i=s+t.length;let a=e.substr(i),l=0;for(;l<n.length;){let e=n.indexOf(r,l);if(-1===e)break;let t=n.substr(0,e);l=e+r.length;let i=n.substr(l);if(o===t&&a===i)return{before:o,after:a}}}return null}(l,u,c,h),g=s.separator||r||n.defaultSeparator||"";return f?f.before+u+g+h+f.after:l+g+c}getSmallestUnit(){switch(this.smallestUnitNum){case 7:case 6:case 5:return"year";case 4:return"month";case 3:return"week";case 2:return"day";default:return"time"}}}function tn(e,t,n){let r=Object.keys(e).length;return 1===r&&"short"===e.timeZoneName?e=>Vt(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<n;s+=r){let t=e[s];void 0!==t&&i.push(t)}return i}let kn={};var Mn,In;Mn="gregory",In=class{getMarkerYear(e){return e.getUTCFullYear()}getMarkerMonth(e){return e.getUTCMonth()}getMarkerDay(e){return e.getUTCDate()}arrayToMarker(e){return Bt(e)}markerToArray(e){return Ht(e)}},kn[Mn]=In;const On=/^\s*(\d{4})(-?(\d{2})(-?(\d{2})([T ](\d{2}):?(\d{2})(:?(\d{2})(\.(\d+))?)?(Z|(([-+])(\d{2})(:?(\d{2}))?))?)?)?)?$/;function Nn(e){let t=On.exec(e);if(t){let e=new Date(Date.UTC(Number(t[1]),t[3]?Number(t[3])-1:0,Number(t[5]||1),Number(t[7]||0),Number(t[8]||0),Number(t[10]||0),t[12]?1e3*Number("0."+t[12]):0));if(jt(e)){let n=null;return t[13]&&(n=("-"===t[15]?-1:1)*(60*Number(t[16]||0)+Number(t[18]||0))),{marker:e,isTimeUnspecified:!t[6],timeZoneOffset:n}}}return null}class Pn{constructor(e){let t=this.timeZone=e.timeZone,n="local"!==t&&"UTC"!==t;e.namedTimeZoneImpl&&n&&(this.namedTimeZoneImpl=new e.namedTimeZoneImpl(t)),this.canComputeOffset=Boolean(!n||this.namedTimeZoneImpl),this.calendarSystem=function(e){return new kn[e]}(e.calendarSystem),this.locale=e.locale,this.weekDow=e.locale.week.dow,this.weekDoy=e.locale.week.doy,"ISO"===e.weekNumberCalculation&&(this.weekDow=1,this.weekDoy=4),"number"==typeof e.firstDay&&(this.weekDow=e.firstDay),"function"==typeof e.weekNumberCalculation&&(this.weekNumberFunc=e.weekNumberCalculation),this.weekText=null!=e.weekText?e.weekText:e.locale.options.weekText,this.weekTextLong=(null!=e.weekTextLong?e.weekTextLong:e.locale.options.weekTextLong)||this.weekText,this.cmdFormatter=e.cmdFormatter,this.defaultSeparator=e.defaultSeparator}createMarker(e){let t=this.createMarkerMeta(e);return null===t?null:t.marker}createNowMarker(){return this.canComputeOffset?this.timestampToMarker((new Date).valueOf()):Bt(Nt(new Date))}createMarkerMeta(e){if("string"==typeof e)return this.parse(e);let t=null;return"number"==typeof e?t=this.timestampToMarker(e):e instanceof Date?(e=e.valueOf(),isNaN(e)||(t=this.timestampToMarker(e))):Array.isArray(e)&&(t=Bt(e)),null!==t&&jt(t)?{marker:t,isTimeUnspecified:!1,forcedTzo:null}:null}parse(e){let t=Nn(e);if(null===t)return null;let{marker:n}=t,r=null;return null!==t.timeZoneOffset&&(this.canComputeOffset?n=this.timestampToMarker(n.valueOf()-60*t.timeZoneOffset*1e3):r=t.timeZoneOffset),{marker:n,isTimeUnspecified:t.isTimeUnspecified,forcedTzo:r}}getYear(e){return this.calendarSystem.getMarkerYear(e)}getMonth(e){return this.calendarSystem.getMarkerMonth(e)}getDay(e){return this.calendarSystem.getMarkerDay(e)}add(e,t){let n=this.calendarSystem.markerToArray(e);return n[0]+=t.years,n[1]+=t.months,n[2]+=t.days,n[6]+=t.milliseconds,this.calendarSystem.arrayToMarker(n)}subtract(e,t){let n=this.calendarSystem.markerToArray(e);return n[0]-=t.years,n[1]-=t.months,n[2]-=t.days,n[6]-=t.milliseconds,this.calendarSystem.arrayToMarker(n)}addYears(e,t){let n=this.calendarSystem.markerToArray(e);return n[0]+=t,this.calendarSystem.arrayToMarker(n)}addMonths(e,t){let n=this.calendarSystem.markerToArray(e);return n[1]+=t,this.calendarSystem.arrayToMarker(n)}diffWholeYears(e,t){let{calendarSystem:n}=this;return zt(e)===zt(t)&&n.getMarkerDay(e)===n.getMarkerDay(t)&&n.getMarkerMonth(e)===n.getMarkerMonth(t)?n.getMarkerYear(t)-n.getMarkerYear(e):null}diffWholeMonths(e,t){let{calendarSystem:n}=this;return zt(e)===zt(t)&&n.getMarkerDay(e)===n.getMarkerDay(t)?n.getMarkerMonth(t)-n.getMarkerMonth(e)+12*(n.getMarkerYear(t)-n.getMarkerYear(e)):null}greatestWholeUnit(e,t){let n=this.diffWholeYears(e,t);return null!==n?{unit:"year",value:n}:(n=this.diffWholeMonths(e,t),null!==n?{unit:"month",value:n}:(n=Tt(e,t),null!==n?{unit:"week",value:n}:(n=kt(e,t),null!==n?{unit:"day",value:n}:(n=function(e,t){return(t.valueOf()-e.valueOf())/36e5}(e,t),ct(n)?{unit:"hour",value:n}:(n=function(e,t){return(t.valueOf()-e.valueOf())/6e4}(e,t),ct(n)?{unit:"minute",value:n}:(n=function(e,t){return(t.valueOf()-e.valueOf())/1e3}(e,t),ct(n)?{unit:"second",value:n}:{unit:"millisecond",value:t.valueOf()-e.valueOf()}))))))}countDurationsBetween(e,t,n){let r;return n.years&&(r=this.diffWholeYears(e,t),null!==r)?r/(vt(n)/365):n.months&&(r=this.diffWholeMonths(e,t),null!==r)?r/function(e){return vt(e)/30}(n):n.days&&(r=kt(e,t),null!==r)?r/vt(n):(t.valueOf()-e.valueOf())/yt(n)}startOf(e,t){return"year"===t?this.startOfYear(e):"month"===t?this.startOfMonth(e):"week"===t?this.startOfWeek(e):"day"===t?Mt(e):"hour"===t?function(e){return Bt([e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours()])}(e):"minute"===t?function(e){return Bt([e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes()])}(e):"second"===t?function(e){return Bt([e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds()])}(e):null}startOfYear(e){return this.calendarSystem.arrayToMarker([this.calendarSystem.getMarkerYear(e)])}startOfMonth(e){return this.calendarSystem.arrayToMarker([this.calendarSystem.getMarkerYear(e),this.calendarSystem.getMarkerMonth(e)])}startOfWeek(e){return this.calendarSystem.arrayToMarker([this.calendarSystem.getMarkerYear(e),this.calendarSystem.getMarkerMonth(e),e.getUTCDate()-(e.getUTCDay()-this.weekDow+7)%7])}computeWeekNumber(e){return this.weekNumberFunc?this.weekNumberFunc(this.toDate(e)):function(e,t,n){let r=e.getUTCFullYear(),i=It(e,r,t,n);if(i<1)return It(e,r-1,t,n);let s=It(e,r+1,t,n);return 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;n<e.length;n+=1)r=e[n],r.start>s&&i.push({start:s,end:r.start}),r.end>s&&(s=r.end);return s<t.end&&i.push({start:s,end:t.end}),i}function nr(e,t){return e.start.valueOf()-t.start.valueOf()}function rr(e,t){let{start:n,end:r}=e,i=null;return null!==t.start&&(n=null===n?t.start:new Date(Math.max(n.valueOf(),t.start.valueOf()))),null!=t.end&&(r=null===r?t.end:new Date(Math.min(r.valueOf(),t.end.valueOf()))),(null===n||null===r||n<r)&&(i={start:n,end:r}),i}function ir(e,t){return(null===e.start?null:e.start.valueOf())===(null===t.start?null:t.start.valueOf())&&(null===e.end?null:e.end.valueOf())===(null===t.end?null:t.end.valueOf())}function sr(e,t){return(null===e.end||null===t.start||e.end>t.start)&&(null===e.start||null===t.end||e.start<t.end)}function or(e,t){return(null===e.start||null!==t.start&&t.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<e.end)}function lr(e){let t=Math.floor(xt(e.start,e.end))||1,n=Mt(e.start);return{start:n,end:wt(n,t)}}function cr(e,t=ft(0)){let n=null,r=null;if(e.end){r=Mt(e.end);let n=e.end.valueOf()-r.valueOf();n&&n>=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.start?u.start:null!=u.end&&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)<yt(n)?Et(e).unit:r}function c(){i=a.startOf(e,l),s=a.add(i,n),o={start:i,end:s}}return vt(n)<=1&&this.isHiddenDay(i)&&(i=this.skipHiddenDays(i,t),i=Mt(i)),c(),this.trimHiddenDays(o)||(e=this.skipHiddenDays(e,t),c()),o}buildRangeFromDayCount(e,t,n){let r,{dateEnv:i,dateAlignment:s}=this.props,o=0,a=e;s&&(a=i.startOf(a,s)),a=Mt(a),a=this.skipHiddenDays(a,t),r=a;do{r=wt(r,1),this.isHiddenDay(r)||(o+=1)}while(o<n);return{start:a,end:r}}buildCustomVisibleRange(e){let{props:t}=this,n=t.visibleRangeInput,r="function"==typeof n?n.call(t.calendarApi,t.dateEnv.toDate(e)):n,i=this.refineRange(r);return!i||null!=i.start&&null!=i.end?i:null}buildRenderRange(e,t,n){return e}buildDateIncrement(e){let t,{dateIncrement:n}=this.props;return n||((t=this.props.dateAlignment)?ft(1,t):e||ft({days:1}))}refineRange(e){if(e){let t=function(e,t){let n=null,r=null;return e.start&&(n=t.createMarker(e.start)),e.end&&(r=t.createMarker(e.end)),n||r?n&&r&&r<n?null:{start:n,end:r}:null}(e,this.props.dateEnv);return t&&(t=cr(t)),t}return null}initHiddenDays(){let e,t=this.props.hiddenDays||[],n=[],r=0;for(!1===this.props.weekends&&t.push(0,6),e=0;e<7;e+=1)(n[e]=-1!==t.indexOf(e))||(r+=1);if(!r)throw new Error("invalid hiddenDays");this.isHiddenDayHash=n}trimHiddenDays(e){let{start:t,end:n}=e;return t&&(t=this.skipHiddenDays(t)),n&&(n=this.skipHiddenDays(n,-1,!0)),null==t||null==n||t<n?{start:t,end:n}:null}isHiddenDay(e){return e instanceof Date&&(e=e.getUTCDay()),this.isHiddenDayHash[e]}skipHiddenDays(e,t=1,n=!1){for(;this.isHiddenDayHash[(e.getUTCDay()+(n?t:0)+7)%7];)e=wt(e,t);return e}}function fr(e,t,n,r){return{instanceId:Ze(),defId:e,range:t,forcedStartTzo:null==n?null:n,forcedEndTzo:null==r?null:r}}function gr(e,t,n){let{dateEnv:r,pluginHooks:i,options:s}=n,{defs:o,instances:a}=e;a=Sn(a,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<r.length;i+=1){let s=r[i].parse(e,n);if(s){let{allDay:n}=e;return null==n&&(n=t,null==n&&(n=s.allDayGuess,null==n&&(n=!1))),{allDay:n,duration:s.duration,typeData:s.typeData,typeId:i}}}return null}(a,c,n.dateEnv,n.pluginHooks.recurringTypes);if(d){let e=Ar(a,l,t?t.sourceId:"",d.allDay,Boolean(d.duration),n,s);return e.recurringDef={typeId:d.typeId,typeData:d.typeData,duration:d.duration},{def:e,instance:null}}let u=function(e,t,n,r){let i,s,{allDay:o}=e,a=null,l=!1,c=null,d=null!=e.start?e.start:e.date;if(i=n.dateEnv.createMarkerMeta(d),i)a=i.marker;else if(!r)return null;null!=e.end&&(s=n.dateEnv.createMarkerMeta(e.end));null==o&&(o=null!=t?t:(!i||i.isTimeUnspecified)&&(!s||s.isTimeUnspecified));o&&a&&(a=Mt(a));s&&(c=s.marker,o&&(c=Mt(c)),a&&c<=a&&(c=null));c?l=!0:r||(l=n.options.forceEventDuration||!1,c=n.dateEnv.add(a,o?n.options.defaultAllDayEventDuration:n.options.defaultTimedEventDuration));return{allDay:o,hasEnd:l,range:{start:a,end:c},forcedStartTzo:i?i.forcedTzo:null,forcedEndTzo:s?s.forcedTzo:null}}(a,c,n,r);if(u){let e=Ar(a,l,t?t.sourceId:"",u.allDay,u.hasEnd,n,s),r=fr(e.defId,u.range,u.forcedStartTzo,u.forcedEndTzo);return o&&e.publicId&&o[e.publicId]&&(r.instanceId=o[e.publicId]),{def:e,instance:r}}return null}function Er(e,t,n=Sr(t)){return vn(e,n)}function Sr(e){return Object.assign(Object.assign(Object.assign({},kr),yr),e.pluginHooks.eventRefiners)}function Ar(e,t,n,r,i,s,o){let a={title:e.title||"",groupId:e.groupId||"",publicId:e.id||"",url:e.url||"",recurringDef:null,defId:(o&&e.id?o[e.id]:"")||Ze(),sourceId:n,allDay:r,hasEnd:i,interactive:e.interactive,ui:Ir(e,s),extendedProps:Object.assign(Object.assign({},e.extendedProps||{}),t)};for(let t of s.pluginHooks.eventDefMemberAdders)Object.assign(a,t(e));return Object.freeze(a.ui.classNames),Object.freeze(a.extendedProps),a}function Dr(e,t,n,r,i,s){let o={defs:{},instances:{}},a=Sr(n);for(let l of e){let e=br(l,t,n,r,a,i,s);e&&wr(e,o)}return o}function wr(e,t={defs:{},instances:{}}){return t.defs[e.def.defId]=e.def,e.instance&&(t.instances[e.instance.instanceId]=e.instance),t}function Cr(e,t){let n=e.instances[t];if(n){let t=e.defs[n.defId],r=_r(e,e=>{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.end<l.range.start&&(l.range.end=Yr(t.allDay,l.range.start,i)),l}class Jr{constructor(e,t){this.context=e,this.internalEventSource=t}remove(){this.context.dispatch({type:"REMOVE_EVENT_SOURCE",sourceId:this.internalEventSource.sourceId})}refetch(){this.context.dispatch({type:"FETCH_EVENT_SOURCES",sourceIds:[this.internalEventSource.sourceId],isRefetch:!0})}get id(){return this.internalEventSource.publicId}get url(){return this.internalEventSource.meta.url}get format(){return this.internalEventSource.meta.format}}class Kr{constructor(e,t,n){this._context=e,this._def=t,this._instance=n||null}setProp(e,t){if(e in vr)console.warn("Could not set date-related prop 'name'. Use one of the date-related methods instead.");else if("id"===e)t=mr[e](t),this.mutate({standardProps:{publicId:t}});else if(e in mr)t=mr[e](t),this.mutate({standardProps:{[e]:t}});else if(e in kr){let n=kr[e](t);n="color"===e?{backgroundColor:t,borderColor:t}:"editable"===e?{startEditable:t,durationEditable:t}:{[e]:t},this.mutate({standardProps:{ui:n}})}else console.warn(`Could not set prop '${e}'. Use setExtendedProp instead.`)}setExtendedProp(e,t){this.mutate({extendedProps:{[e]:t}})}setStart(e,t={}){let{dateEnv:n}=this._context,r=n.createMarker(e);if(r&&this._instance){let e=ur(this._instance.range.start,r,n,t.granularity);t.maintainDuration?this.mutate({datesDelta:e}):this.mutate({startDelta:e})}}setEnd(e,t={}){let n,{dateEnv:r}=this._context;if((null==e||(n=r.createMarker(e),n))&&this._instance)if(n){let e=ur(this._instance.range.end,n,r,t.granularity);this.mutate({endDelta:e})}else this.mutate({standardProps:{hasEnd:!1}})}setDates(e,t,n={}){let r,{dateEnv:i}=this._context,s={allDay:n.allDay},o=i.createMarker(e);var a,l;if(o&&((null==t||(r=i.createMarker(t),r))&&this._instance)){let e=this._instance.range;!0===n.allDay&&(e=lr(e));let t=ur(e.start,o,i,n.granularity);if(r){let o=ur(e.end,r,i,n.granularity);l=o,(a=t).years===l.years&&a.months===l.months&&a.days===l.days&&a.milliseconds===l.milliseconds?this.mutate({datesDelta:t,standardProps:s}):this.mutate({startDelta:t,endDelta:o,standardProps:s})}else s.hasEnd=!1,this.mutate({datesDelta:t,standardProps:s})}}moveStart(e){let t=ft(e);t&&this.mutate({startDelta:t})}moveEnd(e){let t=ft(e);t&&this.mutate({endDelta:t})}moveDates(e){let t=ft(e);t&&this.mutate({datesDelta:t})}setAllDay(e,t={}){let n={allDay:e},{maintainDuration:r}=t;null==r&&(r=this._context.options.allDayMaintainDuration),this._def.allDay!==e&&(n.hasEnd=r),this.mutate({standardProps:n})}formatRange(e){let{dateEnv:t}=this._context,n=this._instance,r=an(e);return this._def.hasEnd?t.formatRange(n.range.start,n.range.end,r,{forcedStartTzo:n.forcedStartTzo,forcedEndTzo:n.forcedEndTzo}):t.format(n.range.start,r,{forcedTzo:n.forcedStartTzo})}mutate(e){let t=this._instance;if(t){let n=this._def,r=this._context,{eventStore:i}=r.getCurrentData(),s=Cr(i,t.instanceId);s=Zr(s,{"":{display:"",startEditable:!0,durationEditable:!0,constraints:[],overlap:null,allows:[],backgroundColor:"",borderColor:"",textColor:"",classNames:[]}},e,r);let o=new Kr(r,n,t);this._def=s.defs[n.defId],this._instance=s.instances[t.instanceId],r.dispatch({type:"MERGE_EVENTS",eventStore:s}),r.emitter.trigger("eventChange",{oldEvent:o,event:this,relatedEvents:ti(s,r,t),revert(){r.dispatch({type:"RESET_EVENTS",eventStore:i})}})}}remove(){let e=this._context,t=ei(this);e.dispatch({type:"REMOVE_EVENTS",eventStore:t}),e.emitter.trigger("eventRemove",{event:this,relatedEvents:[],revert(){e.dispatch({type:"MERGE_EVENTS",eventStore:t})}})}get source(){let{sourceId:e}=this._def;return e?new Jr(this._context,this._context.getCurrentData().eventSources[e]):null}get start(){return this._instance?this._context.dateEnv.toDate(this._instance.range.start):null}get end(){return this._instance&&this._def.hasEnd?this._context.dateEnv.toDate(this._instance.range.end):null}get startStr(){let e=this._instance;return e?this._context.dateEnv.formatIso(e.range.start,{omitTime:this._def.allDay,forcedTzo:e.forcedStartTzo}):""}get endStr(){let e=this._instance;return e&&this._def.hasEnd?this._context.dateEnv.formatIso(e.range.end,{omitTime:this._def.allDay,forcedTzo:e.forcedEndTzo}):""}get id(){return this._def.publicId}get groupId(){return this._def.groupId}get allDay(){return this._def.allDay}get title(){return this._def.title}get url(){return this._def.url}get display(){return this._def.ui.display||"auto"}get startEditable(){return this._def.ui.startEditable}get durationEditable(){return this._def.ui.durationEditable}get constraint(){return this._def.ui.constraints[0]||null}get overlap(){return this._def.ui.overlap}get allow(){return this._def.ui.allows[0]||null}get backgroundColor(){return this._def.ui.backgroundColor}get borderColor(){return this._def.ui.borderColor}get textColor(){return this._def.ui.textColor}get classNames(){return this._def.ui.classNames}get extendedProps(){return this._def.extendedProps}toPlainObject(e={}){let t=this._def,{ui:n}=t,{startStr:r,endStr:i}=this,s={allDay:t.allDay};return t.title&&(s.title=t.title),r&&(s.start=r),i&&(s.end=i),t.publicId&&(s.id=t.publicId),t.groupId&&(s.groupId=t.groupId),t.url&&(s.url=t.url),n.display&&"auto"!==n.display&&(s.display=n.display),e.collapseColor&&n.backgroundColor&&n.backgroundColor===n.borderColor?s.color=n.backgroundColor:(n.backgroundColor&&(s.backgroundColor=n.backgroundColor),n.borderColor&&(s.borderColor=n.borderColor)),n.textColor&&(s.textColor=n.textColor),n.classNames.length&&(s.classNames=n.classNames),Object.keys(t.extendedProps).length&&(e.collapseExtendedProps?Object.assign(s,t.extendedProps):s.extendedProps=t.extendedProps),s}toJSON(){return this.toPlainObject()}}function ei(e){let t=e._def,n=e._instance;return{defs:{[t.defId]:t},instances:n?{[n.instanceId]:n}:{}}}function ti(e,t,n){let{defs:r,instances:i}=e,s=[],o=n?n.instanceId:"";for(let e in i){let n=i[e],a=r[n.defId];n.instanceId!==o&&s.push(new Kr(t,a,n))}return s}function ni(e,t,n,r){let i={},s={},o={},a=[],l=[],c=oi(e.defs,t);for(let t in e.defs){let n=e.defs[t];"inverse-background"===c[n.defId].display&&(n.groupId?(i[n.groupId]=[],o[n.groupId]||(o[n.groupId]=n)):s[t]=[])}for(let t in e.instances){let o=e.instances[t],d=e.defs[o.defId],u=c[d.defId],h=o.range,f=!d.allDay&&r?cr(h,r):h,g=rr(f,n);g&&("inverse-background"===u.display?d.groupId?i[d.groupId].push(g):s[o.defId].push(g):"none"!==u.display&&("background"===u.display?a:l).push({def:d,ui:u,instance:o,range:g,isStart:f.start&&f.start.valueOf()===g.start.valueOf(),isEnd:f.end&&f.end.valueOf()===g.end.valueOf()}))}for(let e in i){let t=tr(i[e],n);for(let n of t){let t=o[e],r=c[t.defId];a.push({def:t,ui:r,instance:null,range:n,isStart:!1,isEnd:!1})}}for(let t in s){let r=tr(s[t],n);for(let n of r)a.push({def:e.defs[t],ui:c[t],instance:null,range:n,isStart:!1,isEnd:!1})}return{bg:a,fg:l}}function ri(e){return"background"===e.ui.display||"inverse-background"===e.ui.display}function ii(e,t){e.fcSeg=t}function si(e){return e.fcSeg||e.parentNode.fcSeg||null}function oi(e,t){return An(e,e=>ai(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="<table><tr><td><div></div></td></tr></table>",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<s.length;t+=1)if(n=r[s[t]],n&&n.singleUnit===e)return n;return null}prev(){this.unselect(),this.dispatch({type:"PREV"})}next(){this.unselect(),this.dispatch({type:"NEXT"})}prevYear(){let e=this.getCurrentData();this.unselect(),this.dispatch({type:"CHANGE_DATE",dateMarker:e.dateEnv.addYears(e.currentDate,-1)})}nextYear(){let e=this.getCurrentData();this.unselect(),this.dispatch({type:"CHANGE_DATE",dateMarker:e.dateEnv.addYears(e.currentDate,1)})}today(){let e=this.getCurrentData();this.unselect(),this.dispatch({type:"CHANGE_DATE",dateMarker:e.nowManager.getDateMarker()})}gotoDate(e){let t=this.getCurrentData();this.unselect(),this.dispatch({type:"CHANGE_DATE",dateMarker:t.dateEnv.createMarker(e)})}incrementDate(e){let t=this.getCurrentData(),n=ft(e);n&&(this.unselect(),this.dispatch({type:"CHANGE_DATE",dateMarker:t.dateEnv.add(t.currentDate,n)}))}getDate(){let e=this.getCurrentData();return e.dateEnv.toDate(e.currentDate)}formatDate(e,t){let{dateEnv:n}=this.getCurrentData();return n.format(n.createMarker(e),an(t))}formatRange(e,t,n){let{dateEnv:r}=this.getCurrentData();return r.formatRange(r.createMarker(e),r.createMarker(t),an(n),n)}formatIso(e,t){let{dateEnv:n}=this.getCurrentData();return n.formatIso(n.createMarker(e),{omitTime:t})}select(e,t){let n;n=null==t?null!=e.start?e:{start:e,end:null}:{start:e,end:t};let r=this.getCurrentData(),i=bi(n,r.dateEnv,ft({days:1}));i&&(this.dispatch({type:"SELECT_DATES",selection:i}),Qr(i,null,r))}unselect(e){let t=this.getCurrentData();t.dateSelection&&(this.dispatch({type:"UNSELECT_DATES"}),function(e,t){t.emitter.trigger("unselect",{jsEvent:e?e.origEvent:null,view:t.viewApi||t.calendarApi.view})}(e,t))}addEvent(e,t){if(e instanceof Kr){let t=e._def,n=e._instance;return this.getCurrentData().eventStore.defs[t.defId]||(this.dispatch({type:"ADD_EVENTS",eventStore:wr({def:t,instance:n})}),this.triggerEventAdd(e)),e}let n,r=this.getCurrentData();if(t instanceof Jr)n=t.internalEventSource;else if("boolean"==typeof t)t&&([n]=wn(r.eventSources));else if(null!=t){let e=this.getEventSourceById(t);if(!e)return console.warn(`Could not find an event source with ID "${t}"`),null;n=e.internalEventSource}let i=br(e,n,r,!1);if(i){let e=new Kr(r,i.def,i.def.recurringDef?null:i.instance);return this.dispatch({type:"ADD_EVENTS",eventStore:wr(i)}),this.triggerEventAdd(e),e}return null}triggerEventAdd(e){let{emitter:t}=this.getCurrentData();t.trigger("eventAdd",{event:e,relatedEvents:[],revert:()=>{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.right&&e.top>=t.top&&e.top<t.bottom}function Hi(e,t){let n={left:Math.max(e.left,t.left),right:Math.min(e.right,t.right),top:Math.max(e.top,t.top),bottom:Math.min(e.bottom,t.bottom)};return n.left<n.right&&n.top<n.bottom&&n}function Bi(e,t){return{left:Math.min(Math.max(e.left,t.left),t.right),top:Math.min(Math.max(e.top,t.top),t.bottom)}}function ji(e){return{left:(e.left+e.right)/2,top:(e.top+e.bottom)/2}}function zi(e,t){return{left:e.left-t.left,top:e.top-t.top}}const Ui={defs:{},instances:{}};class Li{constructor(){this.getKeysForEventDefs=Gt(this._getKeysForEventDefs),this.splitDateSelection=Gt(this._splitDateSpan),this.splitEventStore=Gt(this._splitEventStore),this.splitIndividualUi=Gt(this._splitIndividualUi),this.splitEventDrag=Gt(this._splitInteraction),this.splitEventResize=Gt(this._splitInteraction),this.eventUiBuilders={}}splitProps(e){let t=this.getKeyInfo(e),n=this.getKeysForEventDefs(e.eventStore),r=this.splitDateSelection(e.dateSelection),i=this.splitIndividualUi(e.eventUiBases,n),s=this.splitEventStore(e.eventStore,n),o=this.splitEventDrag(e.eventDrag),a=this.splitEventResize(e.eventResize),l={};this.eventUiBuilders=An(t,(e,t)=>this.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?e<n:!!t&&e<t.start),isFuture:Boolean(n?e>n:!!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="<div></div>",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<i;t+=1)if(e>=n[t]&&e<r[t])return t}topToIndex(e){let t,{tops:n,bottoms:r}=this,i=n.length;for(t=0;t<i;t+=1)if(e>=n[t]&&e<r[t])return t}getWidth(e){return this.rights[e]-this.lefts[e]}getHeight(e){return this.bottoms[e]-this.tops[e]}similarTo(e){return is(this.tops||[],e.tops||[])&&is(this.bottoms||[],e.bottoms||[])&&is(this.lefts||[],e.lefts||[])&&is(this.rights||[],e.rights||[])}}function is(e,t){const n=e.length;if(n!==t.length)return!1;for(let r=0;r<n;r++)if(Math.round(e[r])!==Math.round(t[r]))return!1;return!0}class ss{getMaxScrollTop(){return this.getScrollHeight()-this.getClientHeight()}getMaxScrollLeft(){return this.getScrollWidth()-this.getClientWidth()}canScrollVertically(){return this.getMaxScrollTop()>0}canScrollHorizontally(){return this.getMaxScrollLeft()>0}canScrollUp(){return this.getScrollTop()>0}canScrollDown(){return this.getScrollTop()<this.getMaxScrollTop()}canScrollLeft(){return this.getScrollLeft()>0}canScrollRight(){return this.getScrollLeft()<this.getMaxScrollLeft()}}class os extends ss{constructor(e){super(),this.el=e}getScrollTop(){return this.el.scrollTop}getScrollLeft(){return this.el.scrollLeft}setScrollTop(e){this.el.scrollTop=e}setScrollLeft(e){this.el.scrollLeft=e}getScrollWidth(){return this.el.scrollWidth}getScrollHeight(){return this.el.scrollHeight}getClientHeight(){return this.el.clientHeight}getClientWidth(){return this.el.clientWidth}}class as extends ss{getScrollTop(){return window.scrollY}getScrollLeft(){return window.scrollX}setScrollTop(e){window.scroll(window.scrollX,e)}setScrollLeft(e){window.scroll(e,window.scrollY)}getScrollWidth(){return document.documentElement.scrollWidth}getScrollHeight(){return document.documentElement.scrollHeight}getClientHeight(){return document.documentElement.clientHeight}getClientWidth(){return document.documentElement.clientWidth}}class ls extends Vn{constructor(){super(...arguments),this.uid=Ze()}prepareHits(){}queryHit(e,t,n,r){return null}isValidSegDownEl(e){return!this.props.eventDrag&&!this.props.eventResize&&!Oe(e,".fc-event-mirror")}isValidDateDownEl(e){return!(Oe(e,".fc-event:not(.fc-bg-event)")||Oe(e,".fc-more-link")||Oe(e,"a[data-navlink]")||Oe(e,".fc-popover"))}}class cs{constructor(e=(e=>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.stackCnt<this.maxStackCnt)}handleInvalidInsertion(e,t,n){if(this.allowReslicing&&e.touchingEntry){const r=Object.assign(Object.assign({},t),{span:fs(t.span,e.touchingEntry.span)});n.push(r),this.splitEntry(t,e.touchingEntry,n)}else n.push(t)}splitEntry(e,t,n){let r=e.span,i=t.span;r.start<i.start&&this.insertEntry({index:e.index,thickness:e.thickness,span:{start:r.start,end:i.start}},n),r.end>i.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<s;u+=1){const s=t[u];if(!r&&s>=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.start<e.span.end;){let e=s+this.getEntryThickness(h);e>o&&(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;u<s&&t[u]<o;)u+=1;let h=-1;return u<s&&t[u]===o&&(h=ps(n[u],e.span.end,ds)[0]),{touchingLevel:a,touchingLateral:l,touchingEntry:c,stackCnt:d,levelCoord:o,level:u,lateral:h}}toRects(){let{entriesByLevel:e,levelCoords:t}=this,n=e.length,r=[];for(let i=0;i<n;i+=1){let n=e[i],s=t[i];for(let e of n)r.push(Object.assign(Object.assign({},e),{thickness:this.getEntryThickness(e),levelCoord:s}))}return r}}function ds(e){return e.span.end}function us(e){return e.index+":"+e.span.start}function hs(e){let t=[];for(let i of e){let e=[],s={span:i.span,entries:[i]};for(let i of t)fs(i.span,s.span)?s={entries:i.entries.concat(s.entries),span:(n=i.span,r=s.span,{start:Math.min(n.start,r.start),end:Math.max(n.end,r.end)})}:e.push(i);e.push(s),t=e}var n,r;return t}function fs(e,t){let n=Math.max(e.start,t.start),r=Math.min(e.end,t.end);return n<r?{start:n,end:r}:null}function gs(e,t,n){e.splice(t,0,n)}function ps(e,t,n){let r=0,i=e.length;if(!i||t<n(e[r]))return[0,0];if(t>n(e[i-1]))return[i,0];for(;r<i;){let s=Math.floor(r+(i-r)/2),o=n(e[s]);if(t<o)i=s;else{if(!(t>o))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<r;)t.isHiddenDay(n)?i.push(o+.5):(o+=1,i.push(o),s.push(n)),n=wt(n,1);this.dates=s,this.indices=i,this.cnt=s.length}sliceRange(e){let t=this.getDateDayIndex(e.start),n=this.getDateDayIndex(wt(e.end,-1)),r=Math.max(0,t),i=Math.min(this.cnt-1,n);return r=Math.ceil(r),i=Math.floor(i),r<=i?{firstIndex:r,lastIndex:i,isStart:t===r,isEnd:n===i}:null}getDateDayIndex(e){let{indices:t}=this,n=Math.floor(xt(this.dates[0],e));return n<0?t[0]-1: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<s.length&&s[n].getUTCDay()!==r;n+=1);i=Math.ceil(s.length/n)}else i=1,n=s.length;this.rowCnt=i,this.colCnt=n,this.daySeries=e,this.cells=this.buildCells(),this.headerDates=this.buildHeaderDates()}buildCells(){let e=[];for(let t=0;t<this.rowCnt;t+=1){let n=[];for(let e=0;e<this.colCnt;e+=1)n.push(this.buildCell(t,e));e.push(n)}return e}buildCell(e,t){let n=this.daySeries.dates[e*this.colCnt+t];return{key:n.toISOString(),date:n}}buildHeaderDates(){let e=[];for(let t=0;t<this.colCnt;t+=1)e.push(this.cells[0][t].date);return e}sliceRange(e){let{colCnt:t}=this,n=this.daySeries.sliceRange(e),r=[];if(n){let{firstIndex:e,lastIndex:i}=n,s=e;for(;s<=i;){let o=Math.floor(s/t),a=Math.min((o+1)*t,i+1);r.push({row:o,firstCol:s%t,lastCol:(a-1)%t,isStart:n.isStart&&s===e,isEnd:n.isEnd&&a-1===i}),s=a}}return r}}class ks{constructor(){this.sliceBusinessHours=Gt(this._sliceBusinessHours),this.sliceDateSelection=Gt(this._sliceDateSpan),this.sliceEventStore=Gt(this._sliceEventStore),this.sliceEventDrag=Gt(this._sliceInteraction),this.sliceEventResize=Gt(this._sliceInteraction),this.forceDayIfListItem=!1}sliceProps(e,t,n,r,...i){let{eventUiBases:s}=e,o=this.sliceEventStore(e.eventStore,s,t,n,...i);return{dateSelectionSegs:this.sliceDateSelection(e.dateSelection,t,n,s,r,...i),businessHourSegs:this.sliceBusinessHours(e.businessHours,t,n,r,...i),fgEventSegs:o.fg,bgEventSegs:o.bg,eventDrag:this.sliceEventDrag(e.eventDrag,s,t,n,...i),eventResize:this.sliceEventResize(e.eventResize,s,t,n,...i),eventSelection:e.eventSelection}}sliceNowDate(e,t,n,r,...i){return this._sliceDateSpan({range:{start:e,end:Ct(e,1)},allDay:!1},t,n,{},r,...i)}_sliceBusinessHours(e,t,n,r,...i){return e?this._sliceEventStore(gr(e,Ms(t,Boolean(n)),r),{},t,n,...i).bg:[]}_sliceEventStore(e,t,n,r,...i){if(e){let s=ni(e,t,Ms(n,Boolean(r)),r);return{bg:this.sliceEventRanges(s.bg,i),fg:this.sliceEventRanges(s.fg,i)}}return{bg:[],fg:[]}}_sliceInteraction(e,t,n,r,...i){if(!e)return null;let s=ni(e.mutatedEvents,t,Ms(n,Boolean(r)),r);return{segs:this.sliceEventRanges(s.fg,i),affectedInstances:e.affectedEvents.instances,isEvent:e.isEvent}}_sliceDateSpan(e,t,n,r,i,...s){if(!e)return[];let o=Ms(t,Boolean(n)),a=rr(e.range,o);if(a){let t=function(e,t,n){let r=Er({editable:!1},n),i=Ar(r.refined,r.extra,"",e.allDay,!0,n);return{def:i,ui:ai(i,t),instance:fr(i.defId,e.range),range:e.range,isStart:!0,isEnd:!0}}(e=Object.assign(Object.assign({},e),{range:a}),r,i),n=this.sliceRange(e.range,...s);for(let e of n)e.eventRange=t;return n}return[]}sliceEventRanges(e,t){let n=[];for(let r of e)n.push(...this.sliceEventRange(r,t));return n}sliceEventRange(e,t){let n=e.range;this.forceDayIfListItem&&"list-item"===e.ui.display&&(n={start:n.start,end:wt(n.start,1)});let r=this.sliceRange(n,...t);for(let t of r)t.eventRange=e,t.isStart=e.isStart&&t.isStart,t.isEnd=e.isEnd&&t.isEnd;return r}}function Ms(e,t){let n=e.activeRange;return t?n:{start:Ct(n.start,e.slotMinTime.milliseconds),end:Ct(n.end,e.slotMaxTime.milliseconds-864e5)}}function Is(e,t,n){let{instances:r}=e.mutatedEvents;for(let e in r)if(!or(t.validRange,r[e].range))return!1;return Ns({eventDrag:e},n)}function Os(e,t,n){return!!or(t.validRange,e.range)&&Ns({dateSelection:e},n)}function Ns(e,t){let n=t.getCurrentData(),r=Object.assign({businessHours:n.businessHours,dateSelection:"",eventStore:n.eventStore,eventUiBases:n.eventUiBases,eventSelection:"",eventDrag:null,eventResize:null},e);return(t.pluginHooks.isPropsValid||Ps)(r,t)}function Ps(e,t,n={},r){return!(e.eventDrag&&!function(e,t,n,r){let i=t.getCurrentData(),s=e.eventDrag,o=s.mutatedEvents,a=o.defs,l=o.instances,c=oi(a,s.isEvent?e.eventUiBases:{"":i.selectionConfig});r&&(c=An(c,r));let d=(g=e.eventStore,p=s.affectedEvents.instances,{defs:g.defs,instances:Sn(g.instances,e=>!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;e<n.length;e+=1){if(n[e].getBoundingClientRect().width>t)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;e<n.length;e+=1){if(n[e].getBoundingClientRect().height>t)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;i<e;i+=1)n.push(p("col",{style:{width:"shrink"===r.width?Zs(t):r.width||"",minWidth:r.minWidth||""}}))}return p("colgroup",{},...n)}function Zs(e){return null==e?4:e}function Xs(e){for(let t of e)if("shrink"===t.width)return!0;return!1}function $s(e,t){let n=["fc-scrollgrid",t.theme.getClass("table")];return e&&n.push("fc-scrollgrid-liquid"),n}function Js(e,t){let n=["fc-scrollgrid-section","fc-scrollgrid-section-"+e.type,e.className];return t&&e.liquid&&null==e.maxHeight&&n.push("fc-scrollgrid-section-liquid"),e.isSticky&&n.push("fc-scrollgrid-section-sticky"),n}function Ks(e){return p("div",{className:"fc-scrollgrid-sticky-shim",style:{width:e.clientWidth,minWidth:e.tableMinWidth}})}function eo(e){let{stickyHeaderDates:t}=e;return null!=t&&"auto"!==t||(t="auto"===e.height||"auto"===e.viewHeight),t}function to(e){let{stickyFooterScrollbar:t}=e;return null!=t&&"auto"!==t||(t="auto"===e.height||"auto"===e.viewHeight),t}class no extends Vn{constructor(){super(...arguments),this.processCols=Gt(e=>e,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<l&&"header"===(a=r[c]).type;)d.push(this.renderSection(a,s,!0)),c+=1;for(;c<l&&"body"===(a=r[c]).type;)u.push(this.renderSection(a,s,!1)),c+=1;for(;c<l&&"footer"===(a=r[c]).type;)h.push(this.renderSection(a,s,!0)),c+=1;let f=!xi();const g={role:"rowgroup"};return p("table",{role:"grid",className:o.join(" "),style:{height:e.height}},Boolean(!f&&d.length)&&p("thead",g,...d),Boolean(!f&&u.length)&&p("tbody",g,...u),Boolean(!f&&h.length)&&p("tfoot",g,...h),f&&p("tbody",g,...d,...u,...h))}renderSection(e,t,n){return"outerContent"in e?p(y,{key:e.key},e.outerContent):p("tr",{key:e.key,role:"presentation",className:Js(e,this.props.liquid).join(" ")},this.renderChunkTd(e,t,e.chunk,n))}renderChunkTd(e,t,n,r){if("outerContent"in n)return n.outerContent;let{props:i}=this,{forceYScrollbars:s,scrollerClientWidths:o,scrollerClientHeights:a}=this.state,l=Gs(i,e),c=Vs(i,e),d=i.liquid?s?"scroll":l?"auto":"hidden":"visible",u=e.key,h=Qs(e,n,{tableColGroupNode:t,tableMinWidth:"",clientWidth:i.collapsibleWidth||void 0===o[u]?null:o[u],clientHeight:void 0!==a[u]?a[u]:null,expandRows:e.expandRows,syncRowHeights:!1,rowSyncHeights:[],reportRowHeightChange:()=>{}},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<n&&t>=0&&t<r?{dateProfile:s.dateProfile,dateSpan:Object.assign({allDay:!s.forceTimed,range:{start:s.startDate,end:s.endDate}},s.extraDateSpan),dayEl:i,rect:{left:0,top:0,right:n,bottom:r},layer:1}:null}}class bo extends Vn{constructor(){super(...arguments),this.state={isPopoverOpen:!1,popoverId:Le()},this.handleLinkEl=e=>{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.start<t.eventRange.range.start?e:t}function wo(e,t){return e.eventRange.range.end>t.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<n.length;e+=1){let i=n[e].children;for(let e=0;e<i.length;e+=1){let n=i[e];t&&!Ne(n,t)||r.push(n)}}return r},findElements:Pe,flexibleCompare:st,formatDayString:Lt,formatIsoMonthStr:Wt,formatIsoTimeString:Ft,getAllowYScrolling:Gs,getCanVGrowWithinCell:xi,getClippingParents:ns,getDateMeta:Fi,getDayClassNames:Vi,getDefaultEventEnd:Yr,getElSeg:si,getEntrySpanEnd:ds,getEventTargetViaRoot:ze,getIsRtlScrollbarOnLeft:Xi,getRectCenter:ji,getRelevantEvents:Cr,getScrollGridClassNames:$s,getScrollbarWidths:$i,getSectionClassNames:Js,getSectionHasLiquidHeight:Vs,getSegAnchorAttrs:vi,getSegMeta:gi,getSlotClassNames:function(e,t){let n=["fc-slot","fc-slot-"+At[e.dow]];return e.isDisabled?n.push("fc-slot-disabled"):(e.isToday&&(n.push("fc-slot-today"),n.push(t.getClass("today"))),e.isPast&&n.push("fc-slot-past"),e.isFuture&&n.push("fc-slot-future")),n},getStickyFooterScrollbar:to,getStickyHeaderDates:eo,getUniqueDomId:Le,greatestDurationDenominator:Et,groupIntersectingEntries:hs,guid:Ze,hasBgRendering:ri,hasCustomDayCellContent:co,hasShrinkWidth:Xs,identity:yn,injectStyles:xe,interactionSettingsStore:Mi,interactionSettingsToStore:ki,intersectRanges:rr,intersectRects:Hi,intersectSpans:fs,isArraysEqual:St,isColPropsEqual:qs,isDateSelectionValid:Os,isDateSpansEqual:Ei,isInt:ct,isInteractionValid:Is,isMultiDayRange:dr,isPropsEqual:Cn,isPropsValid:Ps,isValidDate:jt,mapHash:An,memoize:Gt,memoizeArraylike:function(e,t,n){let r=[],i=[];return s=>{let o=r.length,a=s.length,l=0;for(;l<o;l+=1)if(s[l]){if(!St(r[l],s[l])){n&&n(i[l]);let r=e.apply(this,s[l]);t&&t(r,i[l])||(i[l]=r)}}else n&&n(i[l]);for(;l<a;l+=1)i[l]=e.apply(this,s[l]);return r=s,i.splice(a),i}},memoizeHashlike:function(e,t,n){let r={},i={};return s=>{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(;r<e.length;)e[r]===t?(e.splice(r,1),n+=1):r+=1;return n},renderChunkContent:Qs,renderFill:go,renderMicroColGroup:Ys,renderScrollShim:Ks,requestJson:Ci,sanitizeShrinkWidth:Zs,setRef:Gn,sliceEventStore:ni,sortEventSegs:li,startOfDay:Mt,translateRect:function(e,t,n){return{left:e.left+t,right:e.right+t,top:e.top+n,bottom:e.bottom+n}},triggerDateSelect:Qr,unpromisify:Di,whenTransitionDone:Ge,wholeDivideDurations:bt},Ro={__proto__:null,createPortal:fe,createContext:zn,flushSync:Bn,Component:D,Fragment:y,cloneElement:function(e,n,r){var i,s,o,a=f({},e.props);for(o in n)"key"==o?i=n[o]:"ref"==o?s=n[o]:a[o]=n[o];return arguments.length>2&&(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;n<e.length;n+=1){let r=e[n].toLocaleLowerCase().split("-");for(let e=r.length;e>0;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.start<e.fetchRange.start||t.end>e.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;r<n.length;r+=1)if(n[r]._raw===t){n.splice(r,1),e=!0;break}e||r.push(t)}for(let e of n)t.dispatch({type:"REMOVE_EVENT_SOURCE",sourceId:e.sourceId});for(let e of r)t.calendarApi.addEventSource(e)}const aa=[Oo({name:"array-event-source",eventSourceDefs:[{ignoreRange:!0,parseMeta:e=>Array.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(;o<a;){let e;s&&!s[o.getUTCDay()]||(e=t?r.add(o,t):o,l.push(r.createMarker(n.toDate(e)))),o=wt(o,1)}return l}(e.daysOfWeek,e.startTime,e.dateEnv,n,r):[]}}],eventRefiners:{daysOfWeek:yn,startTime:ft,endTime:ft,duration:ft,startRecur:yn,endRecur:yn}}),Oo({name:"change-handler",optionChangeHandlers:{events(e,t){oa([e],t)},eventSources:oa}}),Oo({name:"misc",isLoadingFuncs:[e=>Go(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<u&&c>=0&&c<h){let e=o.queryHit(l,c,u,h);e&&or(e.dateProfile.activeRange,e.dateSpan.range)&&(this.disablePointCheck||a.el.contains(a.el.getRootNode().elementFromPoint(l+n-window.scrollX,c+r-window.scrollY)))&&(!i||e.layer>i.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.end)return{startDelta:a}}else if(i.add(r.end,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<t;e+=1)n[e]=[];for(let t of e)n[t.row].push(t);return n}function nl(e,t){let n=[];for(let e=0;e<t;e+=1)n[e]=[];for(let t of e)n[t.firstCol].push(t);return n}function rl(e,t){let n=[];if(e){for(let r=0;r<t;r+=1)n[r]={affectedInstances:e.affectedInstances,isEvent:e.isEvent,segs:[]};for(let t of e.segs)n[t.row].segs.push(t)}else for(let e=0;e<t;e+=1)n[e]=null;return n}const il=an({hour:"numeric",minute:"2-digit",omitZeroMinute:!0,meridiem:"narrow"});function sl(e){let{display:t}=e.eventRange.ui;return"list-item"===t||"auto"===t&&!e.eventRange.def.allDay&&e.firstCol===e.lastCol&&e.isStart&&e.isEnd}class ol extends Vn{render(){let{props:e}=this;return p(io,Object.assign({},e,{elClasses:["fc-daygrid-event","fc-daygrid-block-event","fc-h-event"],defaultTimeFormat:il,defaultDisplayEventEnd:e.defaultDisplayEventEnd,disableResizing:!e.seg.eventRange.def.allDay}))}}class al extends Vn{render(){let{props:e,context:t}=this,{options:n}=t,{seg:r}=e,i=fi(r,n.eventTimeFormat||il,t,!0,e.defaultDisplayEventEnd);return p(ro,Object.assign({},e,{elTag:"a",elClasses:["fc-daygrid-event","fc-daygrid-dot-event"],elAttrs:vi(e.seg,t),defaultGenerator:ll,timeText:i,isResizing:!1,isDateSelecting:!1}))}}function ll(e){return p(y,null,p("div",{className:"fc-daygrid-event-dot",style:{borderColor:e.borderColor||e.backgroundColor}}),e.timeText&&p("div",{className:"fc-event-time"},e.timeText),p("div",{className:"fc-event-title"},e.event.title||p(y,null," ")))}class cl extends Vn{constructor(){super(...arguments),this.compileSegs=Gt(dl)}render(){let{props:e}=this,{allSegs:t,invisibleSegs:n}=this.compileSegs(e.singlePlacements);return p(bo,{elClasses:["fc-daygrid-more-link"],dateProfile:e.dateProfile,todayRange:e.todayRange,allDayDate:e.allDayDate,moreCnt:e.moreCnt,allSegs:t,hiddenSegs:n,alignmentElRef:e.alignmentElRef,alignGridTop:e.alignGridTop,extraDateSpan:e.extraDateSpan,popoverContent:()=>{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()<i.valueOf())}(o,a.currentRange,s);return p(lo,{elTag:"td",elRef:this.handleRootEl,elClasses:["fc-daygrid-day",...t.extraClassNames||[]],elAttrs:Object.assign(Object.assign(Object.assign({},t.extraDataAttrs),t.showDayNumber?{"aria-labelledby":n.dayNumberId}:{}),{role:"gridcell"}),defaultGenerator:fl,date:o,dateProfile:a,todayRange:t.todayRange,showDayNumber:t.showDayNumber,isMonthStart:l,extraRenderProps:t.extraRenderProps},(s,a)=>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;t<e.length;t+=1){let n=e[t],r=pl(n);null!=i[r]?l.push({index:t,span:{start:n.firstCol,end:n.lastCol+1}}):c.push(n)}let d=a.addSegs(l),u=a.toRects(),{singleColPlacements:h,multiColPlacements:f,leftoverMargins:g}=function(e,t,n){let r=function(e,t){let n=[];for(let e=0;e<t;e+=1)n.push([]);for(let t of e)for(let e=t.span.start;e<t.span.end;e+=1)n[e].push(t);return n}(e,n.length),i=[],s=[],o=[];for(let e=0;e<n.length;e+=1){let a=r[e],l=[],c=0,d=0;for(let r of a){let i=t[r.index];l.push({seg:vl(i,e,e+1,n),isVisible:!0,isAbsolute:!1,absoluteTop:r.levelCoord,marginTop:r.levelCoord-c}),c=r.levelCoord+r.thickness}let u=[];c=0,d=0;for(let r of a){let i=t[r.index],s=r.span.end-r.span.start>1,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<o.length;e+=1)p.push(0);for(let t of d){let n=e[t.index],r=t.span;f[r.start].push({seg:vl(n,r.start,r.end,o),isVisible:!1,isAbsolute:!0,absoluteTop:0,marginTop:0});for(let e=r.start;e<r.end;e+=1)p[e]+=1,h[e].push({seg:vl(n,e,e+1,o),isVisible:!1,isAbsolute:!1,absoluteTop:0,marginTop:0})}for(let e=0;e<o.length;e+=1)m.push(g[e]);return{singleColPlacements:h,multiColPlacements:f,moreCnts:p,moreMarginTops:m}}function vl(e,t,n,r){if(e.firstCol===t&&e.lastCol===n-1)return e;let i=e.eventRange,s=i.range,o=rr(s,{start:r[t].date,end:wt(r[n-1].date,1)});return Object.assign(Object.assign({},e),{firstCol:t,lastCol:n-1,eventRange:{def:i.def,ui:Object.assign(Object.assign({},i.ui),{durationEditable:!1}),instance:i.instance,range:o},isStart:e.isStart&&o.start.valueOf()===s.start.valueOf(),isEnd:e.isEnd&&o.end.valueOf()===s.end.valueOf()})}class yl extends cs{constructor(){super(...arguments),this.hiddenConsumes=!1,this.forceHidden={}}addSegs(e){const t=super.addSegs(e),{entriesByLevel:n}=this,r=e=>!this.forceHidden[us(e)];for(let e=0;e<n.length;e+=1)n[e]=n[e].filter(r);return t}handleInvalidInsertion(e,t,n){const{entriesByLevel:r,forceHidden:i}=this,{touchingEntry:s,touchingLevel:o,touchingLateral:a}=e;if(this.hiddenConsumes&&s){const e=us(s);if(!i[e])if(this.allowReslicing){const e=Object.assign(Object.assign({},s),{span:fs(s.span,t.span)});i[us(e)]=!0,r[o][a]=e,n.push(e),this.splitEntry(s,t,n)}else i[e]=!0,n.push(s)}super.handleInvalidInsertion(e,t,n)}}class bl extends ls{constructor(){super(...arguments),this.cellElRefs=new Ws,this.frameElRefs=new Ws,this.fgElRefs=new Ws,this.segHarnessRefs=new Ws,this.rootElRef={current:null},this.state={framePositions:null,maxContentHeight:null,segHeights:{}},this.handleResize=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<yt(t.slotMaxTime))return this.computeTimeTop(ft(r))}return null}computeDateTop(e,t){return t||(t=Mt(e)),this.computeTimeTop(ft(e.valueOf()-t.valueOf()))}computeTimeTop(e){let t,n,{positions:r,dateProfile:i}=this,s=r.els.length,o=(e.milliseconds-yt(i.slotMinTime))/yt(this.slotDuration);return o=Math.max(0,o),o=Math.min(s,o),t=Math.floor(o),t=Math.min(t,s-1),n=o-t,r.tops[t]+r.getHeight(t)*n}}class zl extends Vn{render(){let{props:e,context:t}=this,{options:n}=t,{slatElRefs:r}=e;return p("tbody",null,e.slatMetas.map((i,s)=>{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;n<t;n+=1)r.push([]);if(e)for(n=0;n<e.length;n+=1)r[e[n].col].push(e[n]);return r}function Wl(e,t){let n=[];if(e){for(let r=0;r<t;r+=1)n[r]={affectedInstances:e.affectedInstances,isEvent:e.isEvent,segs:[]};for(let t of e.segs)n[t.col].segs.push(t)}else for(let e=0;e<t;e+=1)n[e]=null;return n}class Fl extends Vn{render(){let{props:e}=this;return p(bo,{elClasses:["fc-timegrid-more-link"],elStyle:{top:e.top,bottom:e.bottom},allDayDate:null,moreCnt:e.hiddenSegs.length,allSegs:e.hiddenSegs,hiddenSegs:e.hiddenSegs,extraDateSpan:e.extraDateSpan,dateProfile:e.dateProfile,todayRange:e.todayRange,popoverContent:()=>ec(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(;l<a&&r[l]<o;l+=1);for(;l<a;l+=1){let e,t=i[l],n=ps(t,s.span.start,ds),r=n[0]+n[1],o=r;for(;(e=t[o])&&e.span.start<s.span.end;)o+=1;if(r<o)return{level:l,lateralStart:r,lateralEnd:o}}return null}(e,r,i),n),o=t[r][i];return[Object.assign(Object.assign({},o),{nextLevelNodes:s[0]}),o.thickness+s[1]]});return Ql(t.length?{level:0,lateralStart:0,lateralEnd:t[0].length}:null,n)[0]}(r);return s=function(e,t){const n=Zl((e,t,n)=>us(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<i;)o.push(t(n,s)),s+=1;return o.sort(ql),[o.map(Yl),o[0][1]]}function ql(e,t){return t[1]-e[1]}function Yl(e){return e[0]}function Zl(e,t){const n={};return(...r)=>{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;s<e.length;s+=1){let o=e[s],a=n.computeDateTop(o.start,t),l=Math.max(a+(r||0),n.computeDateTop(o.end,t));i.push({start:Math.round(a),end:Math.round(l)})}return i}const $l=an({hour:"numeric",minute:"2-digit",meridiem:!1});class Jl extends Vn{render(){return p(io,Object.assign({},this.props,{elClasses:["fc-timegrid-event","fc-v-event",this.props.isShort&&"fc-timegrid-event-short"],defaultTimeFormat:$l}))}}class Kl extends Vn{constructor(){super(...arguments),this.sortEventSegs=Gt(li)}render(){let{props:e,context:t}=this,{options:n}=t,r=n.selectMirror,i=e.eventDrag&&e.eventDrag.segs||e.eventResize&&e.eventResize.segs||r&&e.dateSelectionSegs||[],s=e.eventDrag&&e.eventDrag.affectedInstances||e.eventResize&&e.eventResize.affectedInstances||{},o=this.sortEventSegs(e.fgEventSegs,n.eventOrder);return p(lo,{elTag:"td",elRef:e.elRef,elClasses:["fc-timegrid-col",...e.extraClassNames||[]],elAttrs:Object.assign({role:"gridcell"},e.extraDataAttrs),date:e.date,dateProfile:e.dateProfile,todayRange:e.todayRange,extraRenderProps:e.extraRenderProps},t=>p("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<e.length;n+=1){let r=t[n];r?i.push({index:n,thickness:1,span:r}):s.push(e[n])}let{segRects:o,hiddenGroups:a}=Gl(i,n,r),l=[];for(let t of o)l.push({seg:e[t.index],rect:t});for(let e of s)l.push({seg:e,rect:null});return{segPlacements:l,hiddenGroups:a}}(e,v,l,o);return p(y,null,this.renderHiddenGroups(E,e),b.map(e=>{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<a;return p("div",{className:"fc-timegrid-event-harness"+(y?" fc-timegrid-event-harness-inset":""),key:s||c,style:Object.assign(Object.assign({visibility:d?"":"hidden"},u),v)},p(Jl,Object.assign({seg:o,isDragging:n,isResizing:r,isDateSelecting:i,isSelected:c===h,isShort:b},gi(o,f,g))))}))}renderHiddenGroups(e,t){let{extraDateSpan:n,dateProfile:r,todayRange:i,nowDate:s,eventSelection:o,eventDrag:a,eventResize:l}=this.props;return p(y,null,e.map(e=>{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;r<t.length;r+=1){let i=rr(e,t[r]);i&&n.push({start:i.start,end:i.end,isStart:i.start.valueOf()===e.start.valueOf(),isEnd:i.end.valueOf()===e.end.valueOf(),col:r})}return n}}class oc extends ls{constructor(){super(...arguments),this.buildDayRanges=Gt(ac),this.slicer=new sc,this.timeColsRef={current:null}}render(){let{props:e,context:t}=this,{dateProfile:n,dayTableModel:r}=e,{nowIndicator:i,nextDayThreshold:s}=t.options,o=this.buildDayRanges(r,n,t.dateEnv);return p(Ii,{unit:i?"minute":"day"},(a,l)=>p(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)<yt(t);){let e=i.add(s,o),t=null!==bt(a,l);c.push({date:e,time:o,key:e.toISOString(),isoTimeStr:Ft(e),isLabeled:t}),o=pt(o,r),a=pt(a,r)}return c}function dc(e,t){let n=new _s(e.renderRange,t);return new Ts(n,!1)}xe('.fc-v-event{background-color:var(--fc-event-bg-color);border:1px solid var(--fc-event-border-color);display:block}.fc-v-event .fc-event-main{color:var(--fc-event-text-color);height:100%}.fc-v-event .fc-event-main-frame{display:flex;flex-direction:column;height:100%}.fc-v-event .fc-event-time{flex-grow:0;flex-shrink:0;max-height:100%;overflow:hidden}.fc-v-event .fc-event-title-container{flex-grow:1;flex-shrink:1;min-height:0}.fc-v-event .fc-event-title{bottom:0;max-height:100%;overflow:hidden;top:0}.fc-v-event:not(.fc-event-start){border-top-left-radius:0;border-top-right-radius:0;border-top-width:0}.fc-v-event:not(.fc-event-end){border-bottom-left-radius:0;border-bottom-right-radius:0;border-bottom-width:0}.fc-v-event.fc-event-selected:before{left:-10px;right:-10px}.fc-v-event .fc-event-resizer-start{cursor:n-resize}.fc-v-event .fc-event-resizer-end{cursor:s-resize}.fc-v-event:not(.fc-event-selected) .fc-event-resizer{height:var(--fc-event-resizer-thickness);left:0;right:0}.fc-v-event:not(.fc-event-selected) .fc-event-resizer-start{top:calc(var(--fc-event-resizer-thickness)/-2)}.fc-v-event:not(.fc-event-selected) .fc-event-resizer-end{bottom:calc(var(--fc-event-resizer-thickness)/-2)}.fc-v-event.fc-event-selected .fc-event-resizer{left:50%;margin-left:calc(var(--fc-event-resizer-dot-total-width)/-2)}.fc-v-event.fc-event-selected .fc-event-resizer-start{top:calc(var(--fc-event-resizer-dot-total-width)/-2)}.fc-v-event.fc-event-selected .fc-event-resizer-end{bottom:calc(var(--fc-event-resizer-dot-total-width)/-2)}.fc .fc-timegrid .fc-daygrid-body{z-index:2}.fc .fc-timegrid-divider{padding:0 0 2px}.fc .fc-timegrid-body{min-height:100%;position:relative;z-index:1}.fc .fc-timegrid-axis-chunk{position:relative}.fc .fc-timegrid-axis-chunk>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<n;)r.push(t),i.push({start:t,end:wt(t,1)}),t=wt(t,1);return{dayDates:r,dayRanges:i}}xe(':root{--fc-list-event-dot-width:10px;--fc-list-event-hover-bg-color:#f5f5f5}.fc-theme-standard .fc-list{border:1px solid var(--fc-border-color)}.fc .fc-list-empty{align-items:center;background-color:var(--fc-neutral-bg-color);display:flex;height:100%;justify-content:center}.fc .fc-list-empty-cushion{margin:5em 0}.fc .fc-list-table{border-style:hidden;width:100%}.fc .fc-list-table tr>*{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<e.length;t+=1)n=e[t],(r[n.dayIndex]||(r[n.dayIndex]=[])).push(n);return r}(e);return p(Ii,{unit:"day"},(e,l)=>{let c=[];for(let n=0;n<a.length;n+=1){let d=a[n];if(d){let a=Lt(t[n]),u=o+"-"+a;c.push(p(hc,{key:a,cellId:u,dayDate:t[n],todayRange:l})),d=li(d,r.eventOrder);for(let t of d)c.push(p(pc,Object.assign({key:a+":"+t.eventRange.instance.instanceId,seg:t,isDragging:!1,isResizing:!1,isDateSelecting:!1,isSelected:!1,timeHeaderId:i,eventHeaderId:s,dateHeaderId:u},gi(t,l,e))))}}return p("table",{className:"fc-list-table "+n.getClass("table")},p("thead",null,p("tr",null,p("th",{scope:"col",id:i},r.timeHint),p("th",{scope:"col","aria-hidden":!0}),p("th",{scope:"col",id:s},r.eventHint))),p("tbody",null,c))})}_eventStoreToSegs(e,t,n){return this.eventRangesToSegs(ni(e,t,this.props.dateProfile.activeRange,this.context.options.nextDayThreshold).fg,n)}eventRangesToSegs(e,t){let n=[];for(let r of e)n.push(...this.eventRangeToSegs(r,t));return n}eventRangeToSegs(e,t){let n,r,i,{dateEnv:s}=this.context,{nextDayThreshold:o}=this.context.options,a=e.range,l=e.def.allDay,c=[];for(n=0;n<t.length;n+=1)if(r=rr(a,t[n]),r&&(i={component:this,eventRange:e,start:r.start,end:r.end,isStart:e.isStart&&r.start.valueOf()===a.start.valueOf(),isEnd:e.isEnd&&r.end.valueOf()===a.end.valueOf(),dayIndex:n},c.push(i),!i.isEnd&&!l&&n+1<t.length&&a.end<s.add(t[n+1].start,o))){i.end=a.end,i.isEnd=!0;break}return c}},buttonTextKey:"list",listDayFormat:{month:"long",day:"numeric",year:"numeric"}},listDay:{type:"list",duration:{days:1},listDayFormat:{weekday:"long"}},listWeek:{type:"list",duration:{weeks:1},listDayFormat:{weekday:"long"},listDaySideFormat:{month:"long",day:"numeric",year:"numeric"}},listMonth:{type:"list",duration:{month:1},listDaySideFormat:{weekday:"long"}},listYear:{type:"list",duration:{year:1},listDaySideFormat:{weekday:"long"}}}});class Sc extends ls{constructor(){super(...arguments),this.buildDayTableModel=Gt(Rl),this.slicer=new wl,this.state={labelId:Le()}}render(){const{props:e,state:t,context:n}=this,{dateProfile:r,forPrint:i}=e,{options:s}=n,o=this.buildDayTableModel(r,n.dateProfileGenerator),a=this.slicer.sliceProps(e,r,s.nextDayThreshold,n,o),l=null!=e.tableWidth?e.tableWidth/s.aspectRatio:null,c=o.cells.length,d=null!=l?l/c:null;return p("div",{ref:e.elRef,"data-date":e.isoDateStr,className:"fc-multimonth-month",style:{width:e.width},role:"grid","aria-labelledby":t.labelId},p("div",{className:"fc-multimonth-header",style:{marginBottom:d},role:"presentation"},p("div",{className:"fc-multimonth-title",id:t.labelId},n.dateEnv.format(e.dateProfile.currentRange.start,e.titleFormat)),p("table",{className:["fc-multimonth-header-table",n.theme.getClass("table")].join(" "),role:"presentation"},p("thead",{role:"rowgroup"},p(Rs,{dateProfile:e.dateProfile,dates:o.headerDates,datesRepDistinctDays:!1})))),p("div",{className:["fc-multimonth-daygrid","fc-daygrid","fc-daygrid-body",!i&&"fc-daygrid-body-balanced",i&&"fc-daygrid-body-unbalanced",i&&"fc-daygrid-body-natural"].join(" "),style:{marginTop:-d}},p("table",{className:["fc-multimonth-daygrid-table",n.theme.getClass("table")].join(" "),style:{height:i?"":l},role:"presentation"},p("tbody",{role:"rowgroup"},p(El,Object.assign({},a,{dateProfile:r,cells:o.cells,eventSelection:e.eventSelection,dayMaxEvents:!i,dayMaxEventRows:!i,showWeekNumbers:s.weekNumbers,clientWidth:e.clientWidth,clientHeight:e.clientHeight,forPrint:i}))))))}}const Ac=ft(1,"month");function Dc(e,t,n,r,i){const{start:s,end:o}=t.currentRange;let a=s;const l=[];for(;a.valueOf()<o.valueOf();){const s=n.add(a,Ac),o={start:e.skipHiddenDays(a),end:e.skipHiddenDays(s,-1,!0)};let c=_l({currentRange:o,snapToWeek:!0,fixedWeekCount:r,dateEnv:n});c={start:e.skipHiddenDays(c.start),end:e.skipHiddenDays(c.end,-1,!0)};const d=t.activeRange?rr(t.activeRange,i?c:o):null;l.push({currentDate:t.currentDate,isValid:t.isValid,validRange:t.validRange,renderRange:c,activeRange:d,currentRange:o,currentRangeUnit:"month",isRangeAllDay:!0,dateIncrement:t.dateIncrement,slotMinTime:t.slotMaxTime,slotMaxTime:t.slotMinTime}),a=s}return l}const wc=an({year:"numeric",month:"long"}),Cc=an({month:"long"});function Rc(e,t){return e||(t[0].currentRange.start.getUTCFullYear()!==t[t.length-1].currentRange.start.getUTCFullYear()?wc:Cc)}const xc={multiMonthTitleFormat:an,multiMonthMaxColumns:Number,multiMonthMinWidth:Number};xe(".fc .fc-multimonth{border:1px solid var(--fc-border-color);display:flex;flex-wrap:wrap;overflow-x:hidden;overflow-y:auto}.fc .fc-multimonth-title{font-size:1.2em;font-weight:700;padding:1em 0;text-align:center}.fc .fc-multimonth-daygrid{background:var(--fc-page-bg-color)}.fc .fc-multimonth-daygrid-table,.fc .fc-multimonth-header-table{table-layout:fixed;width:100%}.fc .fc-multimonth-daygrid-table{border-top-style:hidden!important}.fc .fc-multimonth-singlecol .fc-multimonth{position:relative}.fc .fc-multimonth-singlecol .fc-multimonth-header{background:var(--fc-page-bg-color);position:relative;top:0;z-index:2}.fc .fc-multimonth-singlecol .fc-multimonth-daygrid{position:relative;z-index:1}.fc .fc-multimonth-singlecol .fc-multimonth-daygrid-table,.fc .fc-multimonth-singlecol .fc-multimonth-header-table{border-left-style:hidden;border-right-style:hidden}.fc .fc-multimonth-singlecol .fc-multimonth-month:last-child .fc-multimonth-daygrid-table{border-bottom-style:hidden}.fc .fc-multimonth-multicol{line-height:1}.fc .fc-multimonth-multicol .fc-multimonth-month{padding:0 1.2em 1.2em}.fc .fc-multimonth-multicol .fc-daygrid-more-link{border:1px solid var(--fc-event-border-color);display:block;float:none;padding:1px}.fc .fc-multimonth-compact{line-height:1}.fc .fc-multimonth-compact .fc-multimonth-daygrid-table,.fc .fc-multimonth-compact .fc-multimonth-header-table{font-size:.9em}.fc-media-screen .fc-multimonth-singlecol .fc-multimonth-header{position:sticky}.fc-media-print .fc-multimonth{overflow:visible}");var _c=Oo({name:"@fullcalendar/multimonth",initialView:"multiMonthYear",optionRefiners:xc,views:{multiMonth:{component:class extends ls{constructor(){super(...arguments),this.splitDateProfileByMonth=Gt(Dc),this.buildMonthFormat=Gt(Rc),this.scrollElRef={current:null},this.firstMonthElRef={current:null},this.needsScrollReset=!1,this.handleSizing=e=>{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, requestSave, requestDelete, notify = () => {}, requestEntries = async () => [], requestServices = async () => [], requestExpand = async () => [], requestTaskTypes = async () => [] }) {
|
||
const { document } = environment;
|
||
let active = null;
|
||
let ready = false;
|
||
let launcher = null;
|
||
const referenceDateKey = "opportunities-extension-manage-time-reference-date";
|
||
let lastReferenceDate = "";
|
||
function savedReferenceDate() {
|
||
try {
|
||
const saved = environment.localStorage?.getItem(referenceDateKey);
|
||
if (dates.parse(saved)) lastReferenceDate = saved;
|
||
} catch { /* Keep the in-memory selection when storage is unavailable. */ }
|
||
return dates.parse(lastReferenceDate) ? lastReferenceDate : dates.today();
|
||
}
|
||
function saveReferenceDate(value) {
|
||
if (!dates.parse(value)) return;
|
||
lastReferenceDate = value;
|
||
try { environment.localStorage?.setItem(referenceDateKey, value); }
|
||
catch { /* Keep the selection for this page even if persistence is blocked. */ }
|
||
}
|
||
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;
|
||
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(trigger) {
|
||
if (active?.saving) return;
|
||
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 = savedReferenceDate(); 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 changeDate = () => { saveReferenceDate(date.value); load(); };
|
||
const changeWeek = days => { if (dates.parse(date.value)) { date.value = dates.shift(date.value, days); changeDate(); } };
|
||
navigation.append(button("Previous week", "left", () => changeWeek(-7)), date, button("Next week", "right", () => changeWeek(7)));
|
||
date.addEventListener("change", changeDate); 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", "", "0h");
|
||
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", saveEntries, true); save.disabled = true;
|
||
const saveStatus = node("div", "oe-time-status"); saveStatus.hidden = true; saveStatus.setAttribute("role", "status");
|
||
footer.append(add, addNonService, save);
|
||
body.append(toolbar, summary, status, retry, serviceStatus, serviceRetry, taskStatus, taskRetry, scroll, saveStatus, 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.deletions = new Map();
|
||
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: {}, invalidHours: {}, notes: "", srNumber: "", activity: "", taskType: "", notesOpen: false }; }
|
||
const hourFields = ["sundayTaskHours", "mondayTaskHours", "tuesdayTaskHours", "wednesdayTaskHours", "thursdayTaskHours", "fridayTaskHours", "saturdayTaskHours"];
|
||
function rowPayload(row) {
|
||
const payload = { srNumber: row.type === "Non Service" ? "non-service-sr" : row.srNumber,
|
||
activityCode: row.activity, taskType: row.taskType, deliveryType: row.deliveryType || "",
|
||
notes: row.notes, weekId: entry.days[0]?.WeekId, tag: row.type === "Non Service" ? "NON_SERVICE" : "SERVICE" };
|
||
for (const day of entry.days) payload[hourFields[dates.fromApi(day.Date).getUTCDay()]] = Number(row.hours[day.Date]) || 0;
|
||
return payload;
|
||
}
|
||
function signature(row) { return JSON.stringify(rowPayload(row)); }
|
||
function touched(row) { return row.id || row.persisted || row.srNumber || row.activity || row.taskType || row.notes.trim() || Object.values(row.hours).some(value => value !== "") || Object.values(row.invalidHours).some(Boolean); }
|
||
function validRow(row) {
|
||
return !!row.taskType && (row.type === "Non Service" || (!!row.srNumber && !!row.activity))
|
||
&& !Object.values(row.invalidHours).some(Boolean)
|
||
&& Object.values(row.hours).every(value => Number.isFinite(Number(value)) && Number(value) >= 0 && Number(value) <= 24)
|
||
&& Object.values(row.hours).some(value => Number(value) > 0);
|
||
}
|
||
function changedRows() { return entry.rows.filter(row => touched(row) && signature(row) !== row.baseline); }
|
||
function pendingDeletes() {
|
||
const key = entry.days.map(day => day.Date).join("|");
|
||
if (!entry.deletions.has(key)) entry.deletions.set(key, new Set());
|
||
return entry.deletions.get(key);
|
||
}
|
||
function updateSave() {
|
||
const rows = changedRows();
|
||
const deletions = pendingDeletes();
|
||
save.disabled = (rows.length > 0 && !requestSave) || (deletions.size > 0 && !requestDelete) || !entry.tbody || entry.saving || (!rows.length && !deletions.size)
|
||
|| entry.rows.some(row => touched(row) && !validRow(row)) || rows.some(row => row.persisted && !row.id);
|
||
save.title = save.disabled ? "Complete the required fields and enter hours to save changes." : "Save time entries";
|
||
}
|
||
async function saveEntries() {
|
||
updateSave();
|
||
if (save.disabled) return;
|
||
const rows = changedRows(), knownIds = new Set(entry.rows.filter(row => row.id).map(row => String(row.id)));
|
||
const controls = [];
|
||
function lock(node) {
|
||
if (["BUTTON", "INPUT", "TEXTAREA", "SELECT"].includes(String(node.tagName || node.tag).toUpperCase())) { controls.push([node, node.disabled]); node.disabled = true; }
|
||
Array.from(node.children).forEach(lock);
|
||
}
|
||
entry.saving = true; lock(modal);
|
||
saveStatus.hidden = false; saveStatus.setAttribute("role", "status"); saveStatus.textContent = "Saving time entries...";
|
||
let savedCount = 0, deletedCount = 0;
|
||
try {
|
||
const deletions = pendingDeletes();
|
||
for (const id of [...deletions]) {
|
||
await requestDelete(id);
|
||
deletions.delete(id);
|
||
deletedCount++;
|
||
}
|
||
for (const method of ["PUT", "POST"]) {
|
||
const group = rows.filter(row => method === "PUT" ? !!row.id : !row.id);
|
||
if (!group.length) continue;
|
||
const result = await requestSave(method, group.map(row => ({ ...rowPayload(row), ...(row.id ? { id: row.id } : {}) })));
|
||
group.forEach(row => { row.persisted = true; row.baseline = signature(row); });
|
||
savedCount += group.length;
|
||
if (method === "POST") {
|
||
let records = Array.isArray(result) ? result : Array.isArray(result?.items) ? result.items : [];
|
||
function assignIds() {
|
||
const candidates = rowsFromEntries(records, entry.days);
|
||
for (const row of group.filter(row => !row.id)) {
|
||
const match = candidates.find(candidate => candidate.id && !knownIds.has(String(candidate.id)) && signature(candidate) === row.baseline);
|
||
if (match) { row.id = match.id; knownIds.add(String(match.id)); }
|
||
}
|
||
}
|
||
assignIds();
|
||
if (group.some(row => !row.id)) { records = await requestEntries(entry.days[0].WeekId); assignIds(); }
|
||
if (group.some(row => !row.id)) throw new Error("Entries saved, but their IDs could not be confirmed. Reopen Manage Time before editing these entries.");
|
||
}
|
||
}
|
||
saveStatus.textContent = [savedCount ? `${savedCount} time ${savedCount === 1 ? "entry" : "entries"} saved.` : "", deletedCount ? `${deletedCount} time ${deletedCount === 1 ? "entry" : "entries"} deleted.` : ""].filter(Boolean).join(" ");
|
||
notify(saveStatus.textContent);
|
||
saveStatus.hidden = true;
|
||
} catch (error) {
|
||
saveStatus.setAttribute("role", "alert");
|
||
saveStatus.textContent = `${savedCount ? `${savedCount} saved. ` : ""}${deletedCount ? `${deletedCount} deleted. ` : ""}${error.message || "Unable to save time entries."}`;
|
||
notify(saveStatus.textContent, true);
|
||
} finally {
|
||
entry.saving = false;
|
||
controls.forEach(([control, disabled]) => { control.disabled = disabled; });
|
||
if (active === entry) { renderRows(); updateSave(); }
|
||
}
|
||
}
|
||
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)}h`; });
|
||
updateSave();
|
||
}
|
||
function addRow(type, source) {
|
||
if (!entry.tbody || add.disabled) return;
|
||
entry.rows.push(source ? { ...source, id: undefined, persisted: false, baseline: undefined, duplicateHighlightAt: undefined,
|
||
...(source.type === "Service" ? { srNumber: "", activity: "" } : {}),
|
||
invalidHours: { ...source.invalidHours }, hours: { ...source.hours } } : blankRow(type)); renderRows();
|
||
}
|
||
function renderRows() {
|
||
if (entry.saving) return;
|
||
const focusedId = document.activeElement?.id;
|
||
combos = [];
|
||
entry.tbody.replaceChildren();
|
||
entry.rows.forEach((row, index) => {
|
||
const tr = node("tr");
|
||
if (row.duplicateHighlightAt && Date.now() - row.duplicateHighlightAt < 5500) {
|
||
tr.className = "oe-time-duplicate-row";
|
||
tr.setAttribute("style", `animation-delay: -${Math.max(0, Date.now() - row.duplicateHighlightAt)}ms`);
|
||
tr.addEventListener("animationend", () => { tr.className = ""; row.duplicateHighlightAt = undefined; });
|
||
}
|
||
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);
|
||
}
|
||
if (row.srNumber && !entry.services.some(sr => sr.srNumber === row.srNumber)) {
|
||
const option = node("option", "", row.srNumber); option.value = row.srNumber; select.append(option);
|
||
}
|
||
select.value = row.srNumber;
|
||
select.disabled = !entry.servicesReady;
|
||
select.title = entry.servicesReady ? "Service Request" : "Loading Service Requests";
|
||
change = () => {
|
||
const duplicate = select.value && entry.rows.find(other => other !== row && other.type === "Service" && other.srNumber === select.value);
|
||
if (duplicate) {
|
||
duplicate.duplicateHighlightAt = Date.now();
|
||
select.value = row.srNumber;
|
||
notify("This Service Request is already selected in another row.", true);
|
||
renderRows(); return;
|
||
}
|
||
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))];
|
||
if (row.activity && !activities.includes(row.activity)) activities.push(row.activity);
|
||
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; updateSave(); };
|
||
} 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 (row.taskType && !taskTypes.some(item => item.lookUpCode_c === row.taskType)) {
|
||
const option = node("option", "", row.taskType); option.value = row.taskType; select.append(option);
|
||
}
|
||
select.value = row.taskType;
|
||
select.disabled = !entry.taskTypesReady || (!taskTypes.length && !row.taskType);
|
||
select.title = !entry.taskTypesReady ? "Loading Task Types" : taskTypes.length ? "Task Type" : "No Task Types available for this row type";
|
||
change = () => { row.taskType = select.value; updateSave(); };
|
||
} 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,
|
||
listWidth: label === "Service Request" ? 640 : 0,
|
||
listMaxHeight: label === "Service Request" ? 360 : 180,
|
||
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-invalid", String(!!row.invalidHours[day.Date]));
|
||
input.setAttribute("aria-label", `Hours ${day.Date}, row ${index + 1}`);
|
||
input.addEventListener("input", () => {
|
||
const valid = !input.validity?.badInput && (input.value === "" || (Number.isFinite(input.valueAsNumber) && input.valueAsNumber >= 0 && input.valueAsNumber <= 24));
|
||
if (!valid) {
|
||
input.value = row.hours[day.Date] ?? "";
|
||
notify("Enter hours between 0 and 24.", true);
|
||
return;
|
||
}
|
||
row.invalidHours[day.Date] = !valid;
|
||
input.setAttribute("aria-invalid", String(!valid)); row.hours[day.Date] = 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`; updateSave(); });
|
||
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", () => {
|
||
if (entry.saving) return;
|
||
if (row.persisted && !row.id) {
|
||
saveStatus.hidden = false; saveStatus.setAttribute("role", "alert");
|
||
saveStatus.textContent = "Reopen Manage Time to obtain the saved entry ID before deleting it."; return;
|
||
}
|
||
if (row.id) pendingDeletes().add(String(row.id));
|
||
entry.rows.splice(index, 1); renderRows();
|
||
saveStatus.hidden = false; saveStatus.setAttribute("role", "status");
|
||
saveStatus.textContent = pendingDeletes().size ? `${pendingDeletes().size} pending ${pendingDeletes().size === 1 ? "deletion" : "deletions"}. Click Save to apply.` : "Unsaved row removed.";
|
||
})); 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.className = "oe-time-status oe-time-loading-status";
|
||
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.className = "oe-time-status";
|
||
taskStatus.textContent = "No Task Types available."; taskStatus.hidden = entry.taskTypes.length > 0;
|
||
if (entry.tbody) renderRows();
|
||
} catch (error) {
|
||
if (active !== entry) return;
|
||
taskStatus.className = "oe-time-status";
|
||
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.className = "oe-time-status oe-time-loading-status";
|
||
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.className = "oe-time-status";
|
||
serviceStatus.textContent = error.message || "Unable to load Service Requests.";
|
||
serviceStatus.setAttribute("role", "alert"); serviceRetry.hidden = false;
|
||
}
|
||
}
|
||
function rowsFromEntries(records, days) {
|
||
return records.map(record => {
|
||
const row = blankRow(record.tag === "NON_SERVICE" || record.srNumber === "non-service-sr" ? "Non Service" : "Service");
|
||
row.id = record.id;
|
||
row.deliveryType = record.deliveryType || "";
|
||
row.srNumber = record.srNumber || "";
|
||
row.activity = record.activityCode || "";
|
||
row.taskType = record.taskType || "";
|
||
row.notes = typeof record.notes === "string" ? record.notes : "";
|
||
for (const day of days) {
|
||
const value = record[hourFields[dates.fromApi(day.Date).getUTCDay()]];
|
||
row.hours[day.Date] = value == null ? "" : String(value);
|
||
}
|
||
row.baseline = signature(row);
|
||
return row;
|
||
});
|
||
}
|
||
function renderWeek(items, records) {
|
||
entry.days = items;
|
||
const key = items.map(day => day.Date).join("|");
|
||
if (!entry.drafts.has(key)) entry.drafts.set(key, records.length ? rowsFromEntries(records, items) : 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();
|
||
}
|
||
function renderSkeleton() {
|
||
const head = node("thead"), heading = node("tr"), skeletonBody = node("tbody");
|
||
for (let column = 0; column < 13; column++) {
|
||
const cell = node("th");
|
||
cell.append(node("span", "oe-time-skeleton oe-time-skeleton-heading")); heading.append(cell);
|
||
}
|
||
head.append(heading);
|
||
for (let row = 0; row < 3; row++) {
|
||
const tr = node("tr", "oe-time-skeleton-row");
|
||
for (let column = 0; column < 13; column++) {
|
||
const cell = node("td");
|
||
cell.append(node("span", `oe-time-skeleton ${column < 3 ? "oe-time-skeleton-select" : "oe-time-skeleton-control"}`)); tr.append(cell);
|
||
}
|
||
skeletonBody.append(tr);
|
||
}
|
||
table.setAttribute("aria-hidden", "true"); table.replaceChildren(head, skeletonBody);
|
||
}
|
||
function finishSkeleton() {
|
||
table.setAttribute("aria-hidden", "false");
|
||
endingValue.className = "";
|
||
Object.values(sums).forEach(value => { value.className = ""; });
|
||
status.className = "oe-time-status";
|
||
}
|
||
async function load() {
|
||
if (entry.saving) return;
|
||
finishSkeleton();
|
||
const version = ++entry.version; entry.tbody = null; table.replaceChildren(); endingValue.textContent = "--";
|
||
save.disabled = true; saveStatus.hidden = true;
|
||
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.className = "oe-time-status oe-time-loading-status";
|
||
endingValue.className = "oe-time-skeleton oe-time-skeleton-summary";
|
||
Object.values(sums).forEach(value => { value.className = "oe-time-skeleton oe-time-skeleton-summary"; });
|
||
renderSkeleton();
|
||
status.textContent = "Loading week details..."; scroll.setAttribute("aria-busy", "true");
|
||
try {
|
||
const items = await requestWeek(date.value);
|
||
if (active !== entry || entry.version !== version) return;
|
||
status.textContent = "Loading time entries...";
|
||
const key = items.map(day => day.Date).join("|");
|
||
const records = entry.drafts.has(key) ? [] : await requestEntries(items[0].WeekId);
|
||
if (active !== entry || entry.version !== version) return;
|
||
finishSkeleton(); renderWeek(items, records); add.disabled = addNonService.disabled = false; status.hidden = true;
|
||
loadServices(items[0].WeekId);
|
||
} catch (error) {
|
||
if (active !== entry || entry.version !== version) return;
|
||
finishSkeleton(); table.replaceChildren();
|
||
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",
|
||
requestTimeEntries: "opportunitiesExtension.requestTimeEntries",
|
||
requestTimeEntriesSave: "opportunitiesExtension.requestTimeEntriesSave",
|
||
requestTimeEntriesDelete: "opportunitiesExtension.requestTimeEntriesDelete",
|
||
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)"
|
||
];
|
||
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<string>}} */
|
||
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", "requestTimeEntries", "requestActiveServiceRequests", "requestServiceRequestExpand", "requestLookupTaskType",
|
||
"requestActionTeamMembers", "requestActionFindLookup", "requestSrLookupPrimaryPillar", "requestSrLookupLanguage",
|
||
"requestSrLookupTechPlatform", "requestSrLookupOpportunityBusiness", "requestLookupTypePlatform", "requestInternalSrServices"
|
||
]);
|
||
const writes = new Set(["requestTimeEntriesDelete", "requestTimeEntriesSave", "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: "$and",
|
||
criteria: [{
|
||
op: "$or",
|
||
criteria: Array.from(getOpportunityTypeValues(), (value) => ({
|
||
op: "$eq",
|
||
attribute: "RecordSet",
|
||
value
|
||
}))
|
||
}]
|
||
},
|
||
{
|
||
op: "$wi",
|
||
attribute: "EffectiveDate",
|
||
value1: new Date(dateRange.startCloseDate).toISOString().replace(/Z$/, "+0000"),
|
||
value2: new Date(dateRange.endCloseDate).toISOString().replace(/Z$/, "+0000")
|
||
}
|
||
]
|
||
},
|
||
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: null,
|
||
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); }
|
||
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) 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, listWidth = 0, listMaxHeight = 180 } = {}) {
|
||
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(withTooltip = true) {
|
||
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 (withTooltip && 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() {
|
||
let bounds = input.closest?.(".opportunities-extension-action-form-dialog")?.getBoundingClientRect();
|
||
const anchor = input.getBoundingClientRect?.();
|
||
if (!bounds || !anchor) return;
|
||
if (floating && listWidth && document.documentElement?.clientWidth) {
|
||
bounds = { top: 0, left: 0, right: document.documentElement.clientWidth, bottom: document.documentElement.clientHeight };
|
||
}
|
||
const below = Math.max(0, bounds.bottom - anchor.bottom - 8);
|
||
const above = Math.max(0, anchor.top - bounds.top - 8);
|
||
const opensAbove = below < listMaxHeight && above > below;
|
||
list.setAttribute("data-placement", opensAbove ? "above" : "below");
|
||
list.style.maxHeight = `${Math.min(listMaxHeight, opensAbove ? above : below)}px`;
|
||
if (floating) {
|
||
const width = listWidth ? Math.min(Math.max(anchor.width, listWidth), bounds.right - bounds.left - 16) : anchor.width;
|
||
list.style.position = "fixed";
|
||
list.style.left = `${listWidth ? Math.max(bounds.left + 8, Math.min(anchor.left, bounds.right - width - 8)) : anchor.left}px`;
|
||
list.style.width = `${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 = matches.findIndex(item => item.value === select.value);
|
||
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");
|
||
highlight(false);
|
||
}
|
||
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() {
|
||
// Oracle fiscal years start on June 1; each range ends at the next period's start.
|
||
const FISCAL_START_MONTH = 5;
|
||
|
||
function getFiscalPeriodRange(period, currentDate) {
|
||
const currentYear = currentDate.getFullYear();
|
||
const currentMonth = currentDate.getMonth();
|
||
const fiscalStartYear = currentMonth >= FISCAL_START_MONTH ? currentYear : currentYear - 1;
|
||
const currentQuarterIndex = Math.floor(((currentMonth - FISCAL_START_MONTH + 12) % 12) / 3);
|
||
|
||
if (period === "Current Fiscal Year") {
|
||
return createDateRange(fiscalStartYear, FISCAL_START_MONTH, fiscalStartYear + 1, FISCAL_START_MONTH);
|
||
}
|
||
|
||
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 startMonth = FISCAL_START_MONTH + (quarterOffset * 3);
|
||
return createDateRange(fiscalStartYear, startMonth, fiscalStartYear, startMonth + (numberOfQuarters * 3));
|
||
}
|
||
|
||
function createDateRange(startYear, startMonth, endYear, endMonth) {
|
||
return {
|
||
startCloseDate: formatOracleDate(new Date(startYear, startMonth, 1)),
|
||
endCloseDate: formatOracleDate(new Date(endYear, endMonth, 1))
|
||
};
|
||
}
|
||
|
||
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(),
|
||
notify: (...args) => opportunitiesLinks.showCopyConfirmation(...args),
|
||
async requestDelete(id) {
|
||
await srSessionPreparation.ensure();
|
||
const result = await platformContentMessages.sendRuntimeMessage({ type: messageTypes.requestTimeEntriesDelete, id });
|
||
debugContentLog.appendBackgroundRequestLog(result?.debugRequests || []);
|
||
if (!result?.ok) throw new Error(result?.error || "Unable to delete time entry.");
|
||
return result.result;
|
||
},
|
||
async requestSave(method, items) {
|
||
await srSessionPreparation.ensure();
|
||
await currentUser.ensure();
|
||
const result = await platformContentMessages.sendRuntimeMessage({ type: messageTypes.requestTimeEntriesSave, method, items, emailAddress: currentUser.getUserId() });
|
||
debugContentLog.appendBackgroundRequestLog(result?.debugRequests || []);
|
||
if (!result?.ok) throw new Error(result?.error || "Unable to save time entries.");
|
||
return result.result;
|
||
},
|
||
async requestEntries(weekId) {
|
||
await srSessionPreparation.ensure();
|
||
await currentUser.ensure();
|
||
const result = await platformContentMessages.sendRuntimeMessage({ type: messageTypes.requestTimeEntries, weekId, emailAddress: currentUser.getUserId() });
|
||
debugContentLog.appendBackgroundRequestLog(result?.debugRequests || []);
|
||
if (!result?.ok) throw new Error(result?.error || "Unable to load time entries.");
|
||
return result.items;
|
||
},
|
||
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();
|
||
})();
|