1155 lines
30 KiB
JavaScript
1155 lines
30 KiB
JavaScript
(() => {
|
|
const {
|
|
TARGET_ORIGIN,
|
|
TARGET_PATHNAME,
|
|
TARGET_PARAM,
|
|
TARGET_PARAM_VALUE,
|
|
TARGET_APP_URL,
|
|
HCM_ORIGIN,
|
|
HCM_WELCOME_PATHNAME,
|
|
HCM_MY_INFORMATION_APPS_GROUP_ID,
|
|
WORKLIST_ORIGIN,
|
|
WORKLIST_SAASUI_PATHNAME,
|
|
USER_BUTTON_LABEL,
|
|
ALLOWED_STAGE_TYPES,
|
|
SELECTORS,
|
|
IDS,
|
|
CLASSES,
|
|
STORAGE_KEYS,
|
|
CACHE,
|
|
DETAIL_SORT_KEYS,
|
|
BRIDGE,
|
|
API_ENDPOINTS,
|
|
COMCIP_REQUEST,
|
|
COMCIP_TIME_ENTRY_FRAME_ID,
|
|
ACTION_TEAM_OPTIONS,
|
|
ACTION_COMPLEXITY_OPTIONS,
|
|
} = globalThis.ArchPanelConfig;
|
|
|
|
const storageService = globalThis.ArchPanelStorageService;
|
|
const chromeService = globalThis.ArchPanelChromeService;
|
|
const {
|
|
normalizeText,
|
|
cleanString,
|
|
firstNonEmptyString,
|
|
normalizeString,
|
|
toNumber,
|
|
toIsoDateString,
|
|
extractArray,
|
|
mapWithConcurrency,
|
|
getFieldValue,
|
|
flattenDisplayRecord,
|
|
isVisible,
|
|
formatWholeNumber,
|
|
formatCurrency,
|
|
formatHours,
|
|
formatWeekNumber,
|
|
formatOperationalWeekLabel,
|
|
formatPercent,
|
|
formatProgressPercent,
|
|
formatDate,
|
|
getDateInputValue,
|
|
formatDateInputDisplay,
|
|
formatDateForTimeEntryRequest,
|
|
parseTimeEntryDate,
|
|
formatDateTime,
|
|
getLocalDateKey,
|
|
getOperationalWeekInfo,
|
|
getCalendarRowWeekDate,
|
|
parseDatePreservingDateOnly,
|
|
getErrorMessage,
|
|
escapeHtml,
|
|
cssEscape,
|
|
} = globalThis.ArchPanelUtils;
|
|
const {
|
|
getStoredTheme,
|
|
setStoredTheme,
|
|
getStoredSidebarCollapsed,
|
|
setStoredSidebarCollapsed,
|
|
getStoredCalendarView,
|
|
setStoredCalendarView,
|
|
getStoredTimeManagementSelectedDate,
|
|
setStoredTimeManagementSelectedDate,
|
|
} = globalThis.ArchPanelPreferencesService.create({
|
|
STORAGE_KEYS,
|
|
getDateInputValue,
|
|
parseDatePreservingDateOnly,
|
|
});
|
|
const {
|
|
applyDialogTheme,
|
|
ensureStyles,
|
|
applyTheme,
|
|
findUserAnchor,
|
|
resolveInsertionTarget,
|
|
lockScroll,
|
|
unlockScroll,
|
|
} = globalThis.ArchPanelShellView.create({
|
|
IDS,
|
|
CLASSES,
|
|
USER_BUTTON_LABEL,
|
|
normalizeText,
|
|
isVisible,
|
|
});
|
|
const {
|
|
normalizeWorkload,
|
|
isWonWorkload,
|
|
normalizeTeamValue,
|
|
teamContainsUser,
|
|
stripTransientFields,
|
|
createExportPayload,
|
|
createDatasetSnapshot,
|
|
normalizeCachedExportPayload,
|
|
} = globalThis.ArchPanelWorkloadDatasetDomain.create({
|
|
ALLOWED_STAGE_TYPES,
|
|
cleanString,
|
|
firstNonEmptyString,
|
|
normalizeString,
|
|
toNumber,
|
|
extractArray,
|
|
formatDateTime,
|
|
normalizePendingServiceRequests: (...args) =>
|
|
comcipRepository.normalizePendingServiceRequests(...args),
|
|
});
|
|
const {
|
|
buildWorkloadStartCalendar,
|
|
getWeekStartDate,
|
|
getCalendarAnchorDate,
|
|
} = globalThis.ArchPanelWorkloadCalendarDomain.create({
|
|
getLocalDateKey,
|
|
getOperationalWeekInfo,
|
|
getCalendarRowWeekDate,
|
|
formatOperationalWeekLabel,
|
|
formatDate,
|
|
parseDatePreservingDateOnly,
|
|
});
|
|
const {
|
|
readCachedDataset,
|
|
writeCachedDataset,
|
|
readCachedTaskTypePayload,
|
|
writeCachedTaskTypePayload,
|
|
writeCachedWorklistToken,
|
|
readCachedWorklistToken,
|
|
} = globalThis.ArchPanelCacheRepository.create({
|
|
CACHE,
|
|
storageService,
|
|
normalizeCachedExportPayload,
|
|
cleanString,
|
|
});
|
|
const hcmPage = globalThis.ArchPanelHcmPage.create({
|
|
HCM_MY_INFORMATION_APPS_GROUP_ID,
|
|
WORKLIST_ORIGIN,
|
|
WORKLIST_SAASUI_PATHNAME,
|
|
IDS,
|
|
cleanString,
|
|
hcmTileTemplate: globalThis.ArchPanelTemplates.hcmTile,
|
|
onOpenArchPanel: openModal,
|
|
writeCachedWorklistToken,
|
|
});
|
|
|
|
const appState = {
|
|
theme: getStoredTheme(),
|
|
sidebarCollapsed: getStoredSidebarCollapsed(),
|
|
calendarYear: new Date().getFullYear(),
|
|
calendarMonth: new Date().getMonth(),
|
|
calendarView: getStoredCalendarView(),
|
|
calendarAnchorDateKey: getLocalDateKey(new Date()),
|
|
status: "idle",
|
|
loadingMessage: "",
|
|
loadingProgress: 0,
|
|
errorMessage: "",
|
|
dataset: null,
|
|
sessionAuthHeaders: {},
|
|
detailModal: null,
|
|
actionFormModal: null,
|
|
rampComparisonModal: null,
|
|
forecastUpdateConfirmModal: null,
|
|
timeEntriesDrawer: null,
|
|
timeManagementModal: null,
|
|
taskTypeCache: {
|
|
promise: null,
|
|
},
|
|
timeManagementActivityCache: new Map(),
|
|
pendingTimeManagementHighlightRowId: "",
|
|
timeManagementDatePicker: {
|
|
isOpen: false,
|
|
year: new Date().getFullYear(),
|
|
month: new Date().getMonth(),
|
|
},
|
|
pendingFocusSelector: "",
|
|
spaFrameLifecycleIntervalId: null,
|
|
lastSpaFrameLifecycleError: "",
|
|
};
|
|
|
|
let syncFrame = 0;
|
|
let restoreFocusTarget = null;
|
|
let closeTimer = 0;
|
|
const {
|
|
ensurePageBridge,
|
|
fetchJson,
|
|
sendComcipRequestMessage,
|
|
ensureSpaFrame,
|
|
handleBridgeMessage,
|
|
} = globalThis.ArchPanelTransportService.create({
|
|
TARGET_ORIGIN,
|
|
TARGET_APP_URL,
|
|
IDS,
|
|
BRIDGE,
|
|
COMCIP_REQUEST,
|
|
COMCIP_TIME_ENTRY_FRAME_ID,
|
|
chromeService,
|
|
cleanString,
|
|
firstNonEmptyString,
|
|
isHcmWelcomePage,
|
|
getSessionAuthHeaders: () => appState.sessionAuthHeaders,
|
|
setSessionAuthHeaders: (headers) => {
|
|
appState.sessionAuthHeaders = headers;
|
|
},
|
|
});
|
|
const createExportHtml = globalThis.ArchPanelExportHtml.createExportHtml;
|
|
const {
|
|
renderServiceRequestDetailTable,
|
|
renderSortButton,
|
|
renderOpportunityLink,
|
|
renderWorkloadLink,
|
|
getHeaderSummary,
|
|
} = globalThis.ArchPanelRenderHelpers.create({
|
|
appState,
|
|
escapeHtml,
|
|
cleanString,
|
|
formatWholeNumber,
|
|
formatCurrency,
|
|
formatDate,
|
|
formatHours,
|
|
});
|
|
let workbenchController = null;
|
|
let modalEvents = null;
|
|
|
|
function getSortedDetailItems() {
|
|
return workbenchController?.getSortedDetailItems() || [];
|
|
}
|
|
|
|
function findWorkloadById(workloadId) {
|
|
return workbenchController?.findWorkloadById(workloadId) || null;
|
|
}
|
|
|
|
function isTargetPage() {
|
|
const currentUrl = new URL(window.location.href);
|
|
|
|
return (
|
|
currentUrl.origin === TARGET_ORIGIN &&
|
|
currentUrl.pathname === TARGET_PATHNAME &&
|
|
currentUrl.searchParams.get(TARGET_PARAM) === TARGET_PARAM_VALUE
|
|
);
|
|
}
|
|
|
|
function isHcmWelcomePage() {
|
|
const currentUrl = new URL(window.location.href);
|
|
|
|
return (
|
|
currentUrl.origin === HCM_ORIGIN &&
|
|
currentUrl.pathname === HCM_WELCOME_PATHNAME
|
|
);
|
|
}
|
|
|
|
function scheduleSync() {
|
|
if (syncFrame) {
|
|
return;
|
|
}
|
|
|
|
syncFrame = window.requestAnimationFrame(() => {
|
|
syncFrame = 0;
|
|
syncButton();
|
|
});
|
|
}
|
|
|
|
function syncButton() {
|
|
const mount = document.getElementById(IDS.mount);
|
|
const hcmTile = document.getElementById(IDS.hcmTile);
|
|
|
|
if (isHcmWelcomePage()) {
|
|
mount?.remove();
|
|
ensureStyles();
|
|
ensureModal();
|
|
hcmPage.syncHcmTile();
|
|
ensureSpaFrame();
|
|
hcmPage.syncWorklistIframeToken();
|
|
return;
|
|
}
|
|
|
|
if (!isTargetPage()) {
|
|
mount?.remove();
|
|
hcmTile?.remove();
|
|
removeModal();
|
|
unlockScroll();
|
|
return;
|
|
}
|
|
|
|
hcmTile?.remove();
|
|
|
|
const header = document.querySelector(SELECTORS.header);
|
|
const functionsContainer = header?.querySelector(SELECTORS.functions);
|
|
const anchor = functionsContainer ? findUserAnchor(functionsContainer) : null;
|
|
|
|
if (!header || !functionsContainer) {
|
|
return;
|
|
}
|
|
|
|
ensureStyles();
|
|
|
|
const themedMount = mount ?? createMount();
|
|
const overlay = ensureModal();
|
|
|
|
applyTheme(header, functionsContainer, anchor, themedMount, overlay);
|
|
|
|
const targetSibling = resolveInsertionTarget(
|
|
functionsContainer,
|
|
anchor,
|
|
themedMount
|
|
);
|
|
|
|
if (
|
|
themedMount.parentElement !== functionsContainer ||
|
|
themedMount.nextElementSibling !== targetSibling
|
|
) {
|
|
const verifiedTarget =
|
|
targetSibling && targetSibling.parentElement === functionsContainer
|
|
? targetSibling
|
|
: null;
|
|
|
|
if (verifiedTarget) {
|
|
functionsContainer.insertBefore(themedMount, verifiedTarget);
|
|
} else {
|
|
functionsContainer.appendChild(themedMount);
|
|
}
|
|
}
|
|
}
|
|
|
|
function createMount() {
|
|
const mount = document.createElement("div");
|
|
mount.id = IDS.mount;
|
|
mount.className = "arch-panel-extension-entry";
|
|
|
|
const button = document.createElement("button");
|
|
button.id = IDS.button;
|
|
button.type = "button";
|
|
button.className = "arch-panel-extension-button";
|
|
button.textContent = "Arch panel";
|
|
button.setAttribute("aria-label", "Open Arch panel");
|
|
button.addEventListener("click", openModal);
|
|
|
|
mount.appendChild(button);
|
|
|
|
return mount;
|
|
}
|
|
|
|
function ensureModal() {
|
|
const existingOverlay = document.getElementById(IDS.overlay);
|
|
|
|
if (existingOverlay) {
|
|
return existingOverlay;
|
|
}
|
|
|
|
const overlay = document.createElement("div");
|
|
overlay.id = IDS.overlay;
|
|
overlay.className = "arch-panel-extension-overlay";
|
|
overlay.hidden = true;
|
|
overlay.setAttribute("aria-hidden", "true");
|
|
overlay.addEventListener("click", modalEvents.handleOverlayClick);
|
|
overlay.addEventListener("change", modalEvents.handleOverlayChange);
|
|
overlay.addEventListener("input", modalEvents.handleOverlayInput);
|
|
overlay.addEventListener("keydown", modalEvents.handleOverlayKeydown);
|
|
overlay.addEventListener("focusin", modalEvents.handleOverlayFocusIn);
|
|
overlay.addEventListener("pointerover", modalEvents.handleOverlayPointerOver);
|
|
overlay.addEventListener("pointermove", modalEvents.handleOverlayPointerMove);
|
|
overlay.addEventListener("pointerout", modalEvents.handleOverlayPointerOut);
|
|
overlay.addEventListener("scroll", modalEvents.handleOverlayScroll, true);
|
|
|
|
document.body.appendChild(overlay);
|
|
renderModal();
|
|
|
|
return overlay;
|
|
}
|
|
|
|
function renderModal() {
|
|
const overlay = document.getElementById(IDS.overlay);
|
|
|
|
if (!overlay) {
|
|
return;
|
|
}
|
|
|
|
const previousScrollState = captureModalScrollState(overlay);
|
|
const activeFocusSelector = getRestorableFocusSelector(document.activeElement);
|
|
const requestedFocusSelector = appState.pendingFocusSelector || activeFocusSelector;
|
|
|
|
overlay.innerHTML = createModalMarkup();
|
|
applyDialogTheme(overlay, appState.theme);
|
|
|
|
restoreModalScrollState(overlay, previousScrollState);
|
|
|
|
window.requestAnimationFrame(() => {
|
|
if (requestedFocusSelector && shouldRestoreFocusAfterRender()) {
|
|
const focusTarget = overlay.querySelector(requestedFocusSelector);
|
|
|
|
if (focusTarget instanceof HTMLElement) {
|
|
focusTarget.focus({ preventScroll: true });
|
|
if (focusTarget instanceof HTMLInputElement) {
|
|
focusTarget.select();
|
|
}
|
|
}
|
|
}
|
|
|
|
appState.pendingFocusSelector = "";
|
|
scrollPendingTimeManagementHighlightIntoView(overlay);
|
|
});
|
|
}
|
|
|
|
function shouldRestoreFocusAfterRender() {
|
|
if (appState.pendingFocusSelector) {
|
|
return true;
|
|
}
|
|
|
|
return (
|
|
!appState.detailModal &&
|
|
!appState.actionFormModal &&
|
|
!appState.rampComparisonModal &&
|
|
!appState.forecastUpdateConfirmModal &&
|
|
!appState.timeManagementModal
|
|
);
|
|
}
|
|
|
|
function scrollPendingTimeManagementHighlightIntoView(overlay) {
|
|
const rowId = cleanString(appState.pendingTimeManagementHighlightRowId);
|
|
|
|
if (!rowId) {
|
|
return;
|
|
}
|
|
|
|
appState.pendingTimeManagementHighlightRowId = "";
|
|
const row = overlay.querySelector(
|
|
`.arch-panel-extension-time-management-entry-row[data-row-id="${cssEscape(rowId)}"]`
|
|
);
|
|
|
|
if (row instanceof HTMLElement) {
|
|
row.scrollIntoView({
|
|
behavior: "smooth",
|
|
block: "center",
|
|
inline: "nearest",
|
|
});
|
|
}
|
|
}
|
|
|
|
function captureModalScrollState(overlay) {
|
|
const mainShell = overlay.querySelector(".arch-panel-extension-main-shell");
|
|
const shellBody = overlay.querySelector(".arch-panel-extension-shell-body");
|
|
const dialog = overlay.querySelector(".arch-panel-extension-dialog");
|
|
const detailTable = overlay.querySelector(
|
|
".arch-panel-extension-table-wrap.is-detail"
|
|
);
|
|
|
|
return {
|
|
windowX: window.scrollX,
|
|
windowY: window.scrollY,
|
|
documentTop: document.documentElement.scrollTop,
|
|
bodyTop: document.body.scrollTop,
|
|
mainShellTop: mainShell ? mainShell.scrollTop : 0,
|
|
shellBodyTop: shellBody ? shellBody.scrollTop : 0,
|
|
dialogTop: dialog ? dialog.scrollTop : 0,
|
|
detailTableTop: detailTable ? detailTable.scrollTop : 0,
|
|
detailTableLeft: detailTable ? detailTable.scrollLeft : 0,
|
|
};
|
|
}
|
|
|
|
function restoreModalScrollState(overlay, scrollState) {
|
|
const restore = () => {
|
|
const mainShell = overlay.querySelector(".arch-panel-extension-main-shell");
|
|
const shellBody = overlay.querySelector(".arch-panel-extension-shell-body");
|
|
const dialog = overlay.querySelector(".arch-panel-extension-dialog");
|
|
const detailTable = overlay.querySelector(
|
|
".arch-panel-extension-table-wrap.is-detail"
|
|
);
|
|
|
|
if (mainShell) {
|
|
mainShell.scrollTop = scrollState.mainShellTop;
|
|
}
|
|
|
|
if (shellBody) {
|
|
shellBody.scrollTop = scrollState.shellBodyTop;
|
|
}
|
|
|
|
if (dialog) {
|
|
dialog.scrollTop = scrollState.dialogTop;
|
|
}
|
|
|
|
if (detailTable) {
|
|
detailTable.scrollTop = scrollState.detailTableTop;
|
|
detailTable.scrollLeft = scrollState.detailTableLeft;
|
|
}
|
|
|
|
document.documentElement.scrollTop = scrollState.documentTop;
|
|
document.body.scrollTop = scrollState.bodyTop;
|
|
window.scrollTo(scrollState.windowX, scrollState.windowY);
|
|
};
|
|
|
|
window.requestAnimationFrame(() => {
|
|
restore();
|
|
window.requestAnimationFrame(restore);
|
|
});
|
|
}
|
|
|
|
function getRestorableFocusSelector(element) {
|
|
if (!(element instanceof HTMLElement)) {
|
|
return "";
|
|
}
|
|
|
|
const action = element.getAttribute("data-action");
|
|
|
|
if (
|
|
action === "calendar-prev" ||
|
|
action === "calendar-next" ||
|
|
action === "calendar-today"
|
|
) {
|
|
return `[data-action="${action}"]`;
|
|
}
|
|
|
|
if (action === "calendar-view") {
|
|
const calendarView = element.getAttribute("data-calendar-view");
|
|
|
|
return calendarView
|
|
? `[data-action="calendar-view"][data-calendar-view="${calendarView}"]`
|
|
: `[data-action="${action}"]`;
|
|
}
|
|
|
|
const detailScope = element.getAttribute("data-detail-scope");
|
|
const detailValue = element.getAttribute("data-detail-value");
|
|
|
|
if (detailScope === "calendar-day" && detailValue) {
|
|
return getCalendarDayFocusSelector(detailValue);
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
function getCalendarDayFocusSelector(dateKey) {
|
|
return `[data-action="open-detail"][data-detail-scope="calendar-day"][data-detail-value="${dateKey}"]`;
|
|
}
|
|
|
|
function createModalMarkup() {
|
|
return `
|
|
<div
|
|
id="${IDS.dialog}"
|
|
class="arch-panel-extension-dialog"
|
|
role="dialog"
|
|
aria-modal="true"
|
|
aria-labelledby="arch-panel-extension-title"
|
|
tabindex="-1"
|
|
>
|
|
<header class="arch-panel-extension-shell-header">
|
|
<div class="arch-panel-extension-brand">
|
|
<span class="arch-panel-extension-brand-mark">Arch</span>
|
|
<div>
|
|
<p class="arch-panel-extension-brand-label">Enterprise workload intelligence</p>
|
|
<h1 id="arch-panel-extension-title" class="arch-panel-extension-title">
|
|
Arch panel
|
|
</h1>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="arch-panel-extension-shell-actions">
|
|
<span class="arch-panel-extension-shell-note">
|
|
${getHeaderSummary()}
|
|
</span>
|
|
<button
|
|
type="button"
|
|
class="arch-panel-extension-theme-toggle"
|
|
data-action="toggle-sidebar"
|
|
aria-label="Toggle sidebar"
|
|
>
|
|
${appState.sidebarCollapsed ? "Show sidebar" : "Hide sidebar"}
|
|
</button>
|
|
<button
|
|
type="button"
|
|
class="arch-panel-extension-theme-toggle"
|
|
data-action="toggle-theme"
|
|
aria-label="Toggle dashboard theme"
|
|
>
|
|
${appState.theme === "dark" ? "Light mode" : "Dark mode"}
|
|
</button>
|
|
<button
|
|
type="button"
|
|
class="arch-panel-extension-close"
|
|
data-action="close"
|
|
aria-label="Close Arch panel"
|
|
>
|
|
Close
|
|
</button>
|
|
</div>
|
|
</header>
|
|
|
|
<div class="arch-panel-extension-shell-body${appState.sidebarCollapsed ? " is-sidebar-collapsed" : ""}">
|
|
${renderMainContent()}
|
|
${renderDetailModal()}
|
|
${renderActionFormModal()}
|
|
${renderRampComparisonModal()}
|
|
${renderForecastUpdateConfirmModal()}
|
|
${renderTimeEntriesDrawer()}
|
|
${renderTimeManagementModal()}
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
const {
|
|
buildRampForecastComparisonRows,
|
|
hasRampForecastMismatch,
|
|
isNonZeroAmount,
|
|
parseForecastMonthNumber,
|
|
createForecastUpdatePayload,
|
|
} = globalThis.ArchPanelRampForecastDomain.create({
|
|
toNumber,
|
|
normalizeString,
|
|
parseDatePreservingDateOnly,
|
|
formatWeekNumber,
|
|
});
|
|
|
|
const {
|
|
getTimeManagementRows,
|
|
isTimeManagementSaveEnabled,
|
|
getTimeManagementPendingChanges,
|
|
createTimeManagementRowSignature,
|
|
createTimeManagementSavePayload,
|
|
formatTimeManagementHourPayloadValue,
|
|
getTimeManagementDayKey,
|
|
getTimeManagementWeekdayName,
|
|
isNonServiceRequestSrNumber,
|
|
} = globalThis.ArchPanelTimeManagementDomain.create({
|
|
cleanString,
|
|
normalizeString,
|
|
toNumber,
|
|
parseTimeEntryDate,
|
|
getLocalDateKey,
|
|
});
|
|
|
|
const {
|
|
normalizeInfoWeekResponse,
|
|
normalizeTimeManagementServiceRequests,
|
|
formatTimeManagementServiceRequestLabel,
|
|
mergeTimeManagementServiceRequests,
|
|
normalizeTimeManagementEntryRows,
|
|
normalizeTimeManagementEntryRowSelection,
|
|
getActivityRequestSrNumbers,
|
|
normalizeActivityRequest,
|
|
normalizeActivityRequestDetails,
|
|
normalizeTaskTypeRequest,
|
|
getTimeManagementTaskTypeOptions,
|
|
getTimeManagementActivityOptions,
|
|
findTimeManagementServiceRequest,
|
|
findTimeManagementServiceRequestByNumber,
|
|
findOptionValue,
|
|
findOptionLabel,
|
|
normalizeTimeEntriesSummary,
|
|
groupTimeEntriesByResourceName,
|
|
getTimeEntryTimeSpent,
|
|
getTimeEntryActivity,
|
|
getTimeEntryTaskType,
|
|
} = globalThis.ArchPanelTimeManagementData.create({
|
|
cleanString,
|
|
normalizeString,
|
|
firstNonEmptyString,
|
|
extractArray,
|
|
getFieldValue,
|
|
flattenDisplayRecord,
|
|
toNumber,
|
|
parseTimeEntryDate,
|
|
getLocalDateKey,
|
|
getTimeManagementDayKey,
|
|
getTimeManagementWeekdayName,
|
|
createTimeManagementRowSignature,
|
|
});
|
|
|
|
const workbenchRepository = globalThis.ArchPanelWorkbenchRepository.create({
|
|
TARGET_ORIGIN,
|
|
API_ENDPOINTS,
|
|
fetchJson,
|
|
extractArray,
|
|
cleanString,
|
|
firstNonEmptyString,
|
|
getFieldValue,
|
|
toNumber,
|
|
normalizeWorkload,
|
|
isWonWorkload,
|
|
normalizeTeamValue,
|
|
});
|
|
|
|
const comcipRepository = globalThis.ArchPanelComcipRepository.create({
|
|
COMCIP_REQUEST,
|
|
sendComcipRequest: sendComcipRequestMessage,
|
|
cleanString,
|
|
firstNonEmptyString,
|
|
extractArray,
|
|
getFieldValue,
|
|
toNumber,
|
|
formatDateForTimeEntryRequest,
|
|
});
|
|
|
|
const {
|
|
renderMainContent,
|
|
renderWorkloadTableRow,
|
|
} = globalThis.ArchPanelDashboardView.create({
|
|
appState,
|
|
buildWorkloadStartCalendar,
|
|
hasRampForecastMismatch,
|
|
renderOpportunityLink,
|
|
renderWorkloadLink,
|
|
escapeHtml,
|
|
cleanString,
|
|
formatWholeNumber,
|
|
formatCurrency,
|
|
formatPercent,
|
|
formatProgressPercent,
|
|
formatDate,
|
|
});
|
|
|
|
const {
|
|
renderDetailModal,
|
|
renderActionFormModal,
|
|
renderRampComparisonModal,
|
|
renderForecastUpdateConfirmModal,
|
|
renderTimeEntriesDrawer,
|
|
} = globalThis.ArchPanelWorkbenchModalsView.create({
|
|
appState,
|
|
DETAIL_SORT_KEYS,
|
|
ACTION_TEAM_OPTIONS,
|
|
ACTION_COMPLEXITY_OPTIONS,
|
|
getSortedDetailItems,
|
|
renderServiceRequestDetailTable,
|
|
renderSortButton,
|
|
renderWorkloadTableRow,
|
|
findWorkloadById,
|
|
renderOpportunityLink,
|
|
groupTimeEntriesByResourceName,
|
|
getTimeEntryTimeSpent,
|
|
getTimeEntryActivity,
|
|
getTimeEntryTaskType,
|
|
isNonZeroAmount,
|
|
escapeHtml,
|
|
cleanString,
|
|
formatWholeNumber,
|
|
formatCurrency,
|
|
formatDate,
|
|
formatHours,
|
|
});
|
|
|
|
const {
|
|
renderTimeManagementModal,
|
|
} = globalThis.ArchPanelManageTimeView.create({
|
|
appState,
|
|
escapeHtml,
|
|
cleanString,
|
|
normalizeString,
|
|
formatDateInputDisplay,
|
|
getDateInputValue,
|
|
formatHours,
|
|
getWeekStartDate,
|
|
getLocalDateKey,
|
|
getTimeManagementDayKey,
|
|
isTimeManagementSaveEnabled,
|
|
getTimeManagementRows,
|
|
getTimeManagementActivityOptions,
|
|
findTimeManagementServiceRequestByNumber,
|
|
formatTimeManagementServiceRequestLabel,
|
|
isNonServiceRequestSrNumber,
|
|
findOptionValue,
|
|
findOptionLabel,
|
|
getTimeManagementTaskTypeOptions,
|
|
formatTimeManagementHourPayloadValue,
|
|
});
|
|
|
|
const manageTimeController = globalThis.ArchPanelManageTimeController.create({
|
|
appState,
|
|
COMCIP_REQUEST,
|
|
comcipRepository,
|
|
readCachedTaskTypePayload,
|
|
writeCachedTaskTypePayload,
|
|
getStoredTimeManagementSelectedDate,
|
|
setStoredTimeManagementSelectedDate,
|
|
renderModal,
|
|
cleanString,
|
|
normalizeString,
|
|
getDateInputValue,
|
|
parseDatePreservingDateOnly,
|
|
getFieldValue,
|
|
extractArray,
|
|
getErrorMessage,
|
|
cssEscape,
|
|
getTimeManagementRows,
|
|
isTimeManagementSaveEnabled,
|
|
getTimeManagementPendingChanges,
|
|
createTimeManagementSavePayload,
|
|
isNonServiceRequestSrNumber,
|
|
normalizeInfoWeekResponse,
|
|
normalizeTimeManagementServiceRequests,
|
|
mergeTimeManagementServiceRequests,
|
|
normalizeTimeManagementEntryRows,
|
|
normalizeTimeManagementEntryRowSelection,
|
|
getActivityRequestSrNumbers,
|
|
normalizeActivityRequest,
|
|
normalizeActivityRequestDetails,
|
|
normalizeTaskTypeRequest,
|
|
getTimeManagementTaskTypeOptions,
|
|
getTimeManagementActivityOptions,
|
|
findTimeManagementServiceRequest,
|
|
findOptionValue,
|
|
});
|
|
const {
|
|
toggleTimeManagementDatePicker,
|
|
shiftTimeManagementDatePickerMonth,
|
|
selectTimeManagementDate,
|
|
shiftTimeManagementDateByWeeks,
|
|
openTimeManagementModal,
|
|
loadTimeManagementData,
|
|
addTimeManagementEntryRow,
|
|
removeTimeManagementEntryRow,
|
|
updateTimeManagementSelectedSr,
|
|
updateTimeManagementField,
|
|
updateTimeManagementDayHours,
|
|
saveTimeManagementChanges,
|
|
selectTimeManagementComboboxOption,
|
|
filterTimeManagementCombobox,
|
|
handleTimeManagementComboboxKeydown,
|
|
refreshTaskTypeCache,
|
|
} = manageTimeController;
|
|
|
|
workbenchController = globalThis.ArchPanelWorkbenchController.create({
|
|
appState,
|
|
IDS,
|
|
DETAIL_SORT_KEYS,
|
|
hcmPage,
|
|
workbenchRepository,
|
|
comcipRepository,
|
|
readCachedDataset,
|
|
writeCachedDataset,
|
|
readCachedWorklistToken,
|
|
setStoredCalendarView,
|
|
refreshTaskTypeCache,
|
|
renderModal,
|
|
cleanString,
|
|
normalizeString,
|
|
toNumber,
|
|
toIsoDateString,
|
|
mapWithConcurrency,
|
|
getLocalDateKey,
|
|
getErrorMessage,
|
|
formatWholeNumber,
|
|
formatProgressPercent,
|
|
formatDate,
|
|
teamContainsUser,
|
|
stripTransientFields,
|
|
createExportPayload,
|
|
createDatasetSnapshot,
|
|
buildRampForecastComparisonRows,
|
|
parseForecastMonthNumber,
|
|
createForecastUpdatePayload,
|
|
normalizeTimeEntriesSummary,
|
|
getCalendarAnchorDate,
|
|
});
|
|
const {
|
|
refreshData,
|
|
hydrateDatasetFromCache,
|
|
openDetailModal,
|
|
openActionFormModal,
|
|
openRampComparisonModal,
|
|
openForecastUpdateConfirmModal,
|
|
submitForecastUpdate,
|
|
openTimeEntriesDrawer,
|
|
shiftCalendarPeriod,
|
|
goToCurrentCalendarPeriod,
|
|
setCalendarView,
|
|
submitActionForm,
|
|
toggleDetailSort,
|
|
} = workbenchController;
|
|
|
|
modalEvents = globalThis.ArchPanelModalEvents.create({
|
|
appState,
|
|
renderModal,
|
|
closeModal,
|
|
closeDetailModal,
|
|
toggleTheme,
|
|
toggleSidebar,
|
|
refreshData,
|
|
shiftCalendarPeriod,
|
|
goToCurrentCalendarPeriod,
|
|
setCalendarView,
|
|
downloadFile,
|
|
createExportHtml,
|
|
openDetailModal,
|
|
openActionFormModal,
|
|
openRampComparisonModal,
|
|
openForecastUpdateConfirmModal,
|
|
submitForecastUpdate,
|
|
openTimeEntriesDrawer,
|
|
openTimeManagementModal,
|
|
addTimeManagementEntryRow,
|
|
saveTimeManagementChanges,
|
|
removeTimeManagementEntryRow,
|
|
toggleTimeManagementDatePicker,
|
|
shiftTimeManagementDateByWeeks,
|
|
shiftTimeManagementDatePickerMonth,
|
|
selectTimeManagementDate,
|
|
selectTimeManagementComboboxOption,
|
|
submitActionForm,
|
|
toggleDetailSort,
|
|
loadTimeManagementData,
|
|
updateTimeManagementSelectedSr,
|
|
updateTimeManagementField,
|
|
updateTimeManagementDayHours,
|
|
filterTimeManagementCombobox,
|
|
handleTimeManagementComboboxKeydown,
|
|
});
|
|
|
|
function openModal() {
|
|
const overlay = ensureModal();
|
|
|
|
if (!overlay) {
|
|
return;
|
|
}
|
|
|
|
window.clearTimeout(closeTimer);
|
|
restoreFocusTarget =
|
|
document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
|
|
overlay.hidden = false;
|
|
overlay.setAttribute("aria-hidden", "false");
|
|
lockScroll();
|
|
|
|
window.requestAnimationFrame(() => {
|
|
overlay.classList.add(CLASSES.overlayOpen);
|
|
const primaryAction = overlay.querySelector("[data-action='close']");
|
|
primaryAction?.focus();
|
|
});
|
|
|
|
if (!appState.dataset && appState.status !== "loading") {
|
|
void hydrateDatasetFromCache();
|
|
}
|
|
}
|
|
|
|
function closeModal() {
|
|
const overlay = document.getElementById(IDS.overlay);
|
|
|
|
if (!overlay || overlay.hidden) {
|
|
return;
|
|
}
|
|
|
|
overlay.classList.remove(CLASSES.overlayOpen);
|
|
overlay.setAttribute("aria-hidden", "true");
|
|
unlockScroll();
|
|
appState.detailModal = null;
|
|
appState.actionFormModal = null;
|
|
appState.rampComparisonModal = null;
|
|
appState.forecastUpdateConfirmModal = null;
|
|
appState.timeEntriesDrawer = null;
|
|
appState.timeManagementModal = null;
|
|
|
|
if (restoreFocusTarget && restoreFocusTarget.isConnected) {
|
|
restoreFocusTarget.focus();
|
|
}
|
|
|
|
restoreFocusTarget = null;
|
|
|
|
window.clearTimeout(closeTimer);
|
|
closeTimer = window.setTimeout(() => {
|
|
overlay.hidden = true;
|
|
}, 160);
|
|
}
|
|
|
|
function closeDetailModal() {
|
|
if (appState.detailModal?.scope === "calendar-day" && appState.detailModal.value) {
|
|
appState.pendingFocusSelector = getCalendarDayFocusSelector(
|
|
appState.detailModal.value
|
|
);
|
|
}
|
|
|
|
appState.detailModal = null;
|
|
appState.timeEntriesDrawer = null;
|
|
appState.timeManagementModal = null;
|
|
appState.rampComparisonModal = null;
|
|
appState.forecastUpdateConfirmModal = null;
|
|
}
|
|
|
|
function downloadFile(filename, content, contentType) {
|
|
const blob = new Blob([content], { type: contentType });
|
|
const url = URL.createObjectURL(blob);
|
|
const anchor = document.createElement("a");
|
|
anchor.href = url;
|
|
anchor.download = filename;
|
|
anchor.click();
|
|
window.setTimeout(() => {
|
|
URL.revokeObjectURL(url);
|
|
}, 1000);
|
|
}
|
|
|
|
function toggleTheme() {
|
|
appState.theme = appState.theme === "dark" ? "light" : "dark";
|
|
setStoredTheme(appState.theme);
|
|
renderModal();
|
|
}
|
|
|
|
function toggleSidebar() {
|
|
appState.sidebarCollapsed = !appState.sidebarCollapsed;
|
|
setStoredSidebarCollapsed(appState.sidebarCollapsed);
|
|
renderModal();
|
|
}
|
|
|
|
function removeModal() {
|
|
const overlay = document.getElementById(IDS.overlay);
|
|
|
|
window.clearTimeout(closeTimer);
|
|
restoreFocusTarget = null;
|
|
|
|
if (overlay) {
|
|
overlay.remove();
|
|
}
|
|
}
|
|
|
|
function handleGlobalKeydown(event) {
|
|
if (event.key !== "Escape") {
|
|
return;
|
|
}
|
|
|
|
if (appState.actionFormModal) {
|
|
appState.actionFormModal = null;
|
|
renderModal();
|
|
return;
|
|
}
|
|
|
|
if (appState.timeManagementModal) {
|
|
appState.timeManagementModal = null;
|
|
renderModal();
|
|
return;
|
|
}
|
|
|
|
if (appState.forecastUpdateConfirmModal) {
|
|
appState.forecastUpdateConfirmModal = null;
|
|
renderModal();
|
|
return;
|
|
}
|
|
|
|
if (appState.rampComparisonModal) {
|
|
appState.rampComparisonModal = null;
|
|
renderModal();
|
|
return;
|
|
}
|
|
|
|
if (appState.timeEntriesDrawer) {
|
|
appState.timeEntriesDrawer = null;
|
|
renderModal();
|
|
return;
|
|
}
|
|
|
|
if (appState.detailModal) {
|
|
closeDetailModal();
|
|
renderModal();
|
|
return;
|
|
}
|
|
|
|
closeModal();
|
|
}
|
|
|
|
function observePage() {
|
|
const observer = new MutationObserver(() => {
|
|
scheduleSync();
|
|
});
|
|
|
|
observer.observe(document.documentElement, {
|
|
childList: true,
|
|
subtree: true,
|
|
attributes: true,
|
|
attributeFilter: ["src"],
|
|
});
|
|
}
|
|
|
|
function observeHistory() {
|
|
const { pushState, replaceState } = window.history;
|
|
|
|
window.history.pushState = function pushStatePatched(...args) {
|
|
const result = pushState.apply(this, args);
|
|
scheduleSync();
|
|
return result;
|
|
};
|
|
|
|
window.history.replaceState = function replaceStatePatched(...args) {
|
|
const result = replaceState.apply(this, args);
|
|
scheduleSync();
|
|
return result;
|
|
};
|
|
|
|
window.addEventListener("popstate", scheduleSync);
|
|
window.addEventListener("hashchange", scheduleSync);
|
|
}
|
|
|
|
function observeWorklistPageLoadToken() {
|
|
if (!isHcmWelcomePage()) {
|
|
return;
|
|
}
|
|
|
|
const syncLoadedToken = () => {
|
|
hcmPage.syncWorklistIframeToken({ force: true });
|
|
};
|
|
|
|
if (document.readyState === "loading") {
|
|
document.addEventListener("DOMContentLoaded", syncLoadedToken, { once: true });
|
|
} else {
|
|
syncLoadedToken();
|
|
}
|
|
|
|
window.addEventListener("load", syncLoadedToken);
|
|
window.addEventListener("pageshow", syncLoadedToken);
|
|
}
|
|
|
|
function observeSpaFrameLifecycle() {
|
|
if (!isHcmWelcomePage()) {
|
|
return;
|
|
}
|
|
|
|
if (appState.spaFrameLifecycleIntervalId) {
|
|
return;
|
|
}
|
|
|
|
const syncFrame = () => {
|
|
void ensureSpaFrame().catch((error) => {
|
|
appState.lastSpaFrameLifecycleError =
|
|
error instanceof Error ? error.message : String(error);
|
|
});
|
|
};
|
|
|
|
const stopSyncFrame = () => {
|
|
if (!appState.spaFrameLifecycleIntervalId) {
|
|
return;
|
|
}
|
|
|
|
window.clearInterval(appState.spaFrameLifecycleIntervalId);
|
|
appState.spaFrameLifecycleIntervalId = null;
|
|
};
|
|
|
|
if (document.readyState === "loading") {
|
|
document.addEventListener("DOMContentLoaded", syncFrame, { once: true });
|
|
} else {
|
|
syncFrame();
|
|
}
|
|
|
|
window.addEventListener("load", syncFrame);
|
|
window.addEventListener("pageshow", syncFrame);
|
|
window.addEventListener("pagehide", stopSyncFrame);
|
|
window.addEventListener("beforeunload", stopSyncFrame);
|
|
appState.spaFrameLifecycleIntervalId = window.setInterval(syncFrame, 15000);
|
|
}
|
|
|
|
window.addEventListener("message", handleBridgeMessage);
|
|
document.addEventListener("keydown", handleGlobalKeydown);
|
|
void ensurePageBridge();
|
|
observePage();
|
|
observeHistory();
|
|
observeWorklistPageLoadToken();
|
|
observeSpaFrameLifecycle();
|
|
scheduleSync();
|
|
})();
|