From f38bae447951a63adafa862618ad9b5e9e04472f Mon Sep 17 00:00:00 2001 From: Paulo Porto Date: Mon, 25 May 2026 19:21:35 -0300 Subject: [PATCH] =?UTF-8?q?Implementa=C3=A7=C3=A3o=20do=20tooltip=20nas=20?= =?UTF-8?q?SRs=20do=20Manage=20time?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- content-script.js | 7 + src/content/manage-time-controller.js | 7 + src/content/modal-events.js | 333 ++++++++++++++++++++++++++ src/content/styles.css | 229 +++++++++++++++++- src/domain/time-management-data.js | 174 +++++++++++++- src/views/manage-time-view.js | 173 +++++++++++-- 6 files changed, 889 insertions(+), 34 deletions(-) diff --git a/content-script.js b/content-script.js index f34f3c7..5d32520 100644 --- a/content-script.js +++ b/content-script.js @@ -358,6 +358,11 @@ 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(); @@ -633,6 +638,7 @@ normalizeTimeManagementEntryRowSelection, getActivityRequestSrNumbers, normalizeActivityRequest, + normalizeActivityRequestDetails, normalizeTaskTypeRequest, getTimeManagementTaskTypeOptions, getTimeManagementActivityOptions, @@ -787,6 +793,7 @@ normalizeTimeManagementEntryRowSelection, getActivityRequestSrNumbers, normalizeActivityRequest, + normalizeActivityRequestDetails, normalizeTaskTypeRequest, getTimeManagementTaskTypeOptions, getTimeManagementActivityOptions, diff --git a/src/content/manage-time-controller.js b/src/content/manage-time-controller.js index 70b71ce..d860220 100644 --- a/src/content/manage-time-controller.js +++ b/src/content/manage-time-controller.js @@ -29,6 +29,7 @@ normalizeTimeManagementEntryRowSelection, getActivityRequestSrNumbers, normalizeActivityRequest, + normalizeActivityRequestDetails, normalizeTaskTypeRequest, getTimeManagementTaskTypeOptions, getTimeManagementActivityOptions, @@ -162,6 +163,10 @@ activityPayload, serviceRequests ); + const activityRequestDetailsBySr = normalizeActivityRequestDetails( + activityPayload, + serviceRequests + ); const taskTypes = normalizeTaskTypeRequest(taskTypePayload); const rows = timeEntryRows; const selectedSrNumber = rows[0]?.selectedSrNumber || ""; @@ -188,6 +193,7 @@ serviceRequests, selectedSrNumber, activityOptionsBySr, + activityRequestDetailsBySr, activityRequestUrl, activityRequestPayload: activityPayload, timeEntryRows: normalizedRows, @@ -227,6 +233,7 @@ serviceRequests: [], selectedSrNumber: "", activityOptionsBySr: {}, + activityRequestDetailsBySr: {}, activityRequestUrl: "", activityRequestPayload: null, selectedActivityValue: "", diff --git a/src/content/modal-events.js b/src/content/modal-events.js index e444fac..ebfbaad 100644 --- a/src/content/modal-events.js +++ b/src/content/modal-events.js @@ -37,6 +37,7 @@ filterTimeManagementCombobox, handleTimeManagementComboboxKeydown, } = dependencies; + let timeManagementTooltipHideTimer = 0; function handleOverlayClick(event) { const overlay = document.getElementById( @@ -70,6 +71,16 @@ return; } + if ( + event.target instanceof HTMLInputElement && + event.target.getAttribute("data-combobox-field") + ) { + window.requestAnimationFrame(() => { + positionTimeManagementCombobox(event.target); + scrollSelectedTimeManagementComboboxOptionIntoView(event.target); + }); + } + const actionElement = event.target.closest("[data-action]"); if (!actionElement) { @@ -87,6 +98,13 @@ return; } + if ( + action === "time-management-combobox-option" && + event.target?.closest?.(".arch-panel-extension-service-request-tooltip") + ) { + return; + } + event.preventDefault(); if (action === "close") { @@ -406,6 +424,134 @@ } filterTimeManagementCombobox(target); + positionTimeManagementCombobox(target); + } + + function handleOverlayFocusIn(event) { + const target = event.target; + + if ( + target instanceof HTMLInputElement && + target.getAttribute("data-combobox-field") + ) { + window.requestAnimationFrame(() => { + clearTimeManagementComboboxTooltipOptions( + target.closest(".arch-panel-extension-combobox") + ); + hideTimeManagementServiceRequestTooltipPortal(); + positionTimeManagementCombobox(target); + scrollSelectedTimeManagementComboboxOptionIntoView(target); + }); + } + } + + function handleOverlayPointerOver(event) { + const portal = event.target?.closest?.( + ".arch-panel-extension-service-request-tooltip-portal" + ); + + if (portal instanceof HTMLElement) { + clearTimeManagementTooltipHideTimer(); + return; + } + + const option = event.target?.closest?.( + ".arch-panel-extension-combobox-option.has-tooltip" + ); + + if (option instanceof HTMLElement) { + setActiveTimeManagementComboboxTooltipOption(option); + } + } + + function handleOverlayPointerMove(event) { + if ( + event.target?.closest?.(".arch-panel-extension-service-request-tooltip-portal") + ) { + return; + } + + const option = event.target?.closest?.( + ".arch-panel-extension-combobox-option.has-tooltip" + ); + + if (option instanceof HTMLElement) { + setActiveTimeManagementComboboxTooltipOption(option); + } + } + + function handleOverlayPointerOut(event) { + const portal = event.target?.closest?.( + ".arch-panel-extension-service-request-tooltip-portal" + ); + const nextTarget = event.relatedTarget; + + if (portal instanceof HTMLElement) { + if (nextTarget instanceof Node && portal.contains(nextTarget)) { + return; + } + + scheduleTimeManagementTooltipHide(); + return; + } + + const menu = event.target?.closest?.( + ".arch-panel-extension-combobox-menu.has-service-request-tooltips" + ); + + if (!(menu instanceof HTMLElement)) { + return; + } + + if (nextTarget instanceof Node && menu.contains(nextTarget)) { + return; + } + + scheduleTimeManagementTooltipHide(menu); + } + + function scheduleTimeManagementTooltipHide(menu = null) { + clearTimeManagementTooltipHideTimer(); + timeManagementTooltipHideTimer = window.setTimeout(() => { + const portal = getTimeManagementServiceRequestTooltipPortal(false); + const hoveredOption = menu + ? menu.querySelector(".arch-panel-extension-combobox-option.has-tooltip:hover") + : document.querySelector(".arch-panel-extension-combobox-option.has-tooltip:hover"); + + if ( + !hoveredOption && + !(portal instanceof HTMLElement && portal.matches(":hover")) + ) { + clearTimeManagementComboboxTooltipOptions(menu || document.body); + hideTimeManagementServiceRequestTooltipPortal(); + } + }, 500); + } + + function clearTimeManagementTooltipHideTimer() { + if (timeManagementTooltipHideTimer) { + window.clearTimeout(timeManagementTooltipHideTimer); + timeManagementTooltipHideTimer = 0; + } + } + + function handleOverlayScroll(event) { + const menu = event.target; + + if ( + !(menu instanceof HTMLElement) || + !menu.classList.contains("arch-panel-extension-combobox-menu") + ) { + return; + } + + const activeOption = menu.querySelector( + ".arch-panel-extension-combobox-option.has-tooltip.is-tooltip-active" + ); + + if (activeOption instanceof HTMLElement) { + positionTimeManagementComboboxOptionTooltip(activeOption); + } } function handleOverlayKeydown(event) { @@ -435,6 +581,188 @@ } handleTimeManagementComboboxKeydown(event, target); + positionTimeManagementCombobox(target); + } + + function positionTimeManagementCombobox(input) { + const combobox = input.closest(".arch-panel-extension-combobox"); + const menu = combobox?.querySelector(".arch-panel-extension-combobox-menu"); + + if (!(combobox instanceof HTMLElement) || !(menu instanceof HTMLElement)) { + return; + } + + const inputRect = input.getBoundingClientRect(); + const margin = 16; + const gap = 6; + const viewportHeight = window.innerHeight || document.documentElement.clientHeight; + const viewportWidth = window.innerWidth || document.documentElement.clientWidth; + const spaceBelow = viewportHeight - inputRect.bottom - margin - gap; + const spaceAbove = inputRect.top - margin - gap; + const hasServiceRequestTooltips = menu.classList.contains( + "has-service-request-tooltips" + ); + const openAbove = spaceBelow < 240 && spaceAbove > spaceBelow; + const availableHeight = Math.max(180, (openAbove ? spaceAbove : spaceBelow) - 12); + const desiredHeight = hasServiceRequestTooltips ? 430 : 260; + const menuWidth = hasServiceRequestTooltips + ? Math.min(1120, Math.max(inputRect.width, viewportWidth - inputRect.left - margin)) + : inputRect.width; + + menu.style.top = openAbove ? "auto" : `calc(100% + ${gap}px)`; + menu.style.bottom = openAbove ? `calc(100% + ${gap}px)` : "auto"; + menu.style.maxHeight = `${Math.min(desiredHeight, availableHeight)}px`; + menu.style.width = `${menuWidth}px`; + menu.style.maxWidth = `calc(100vw - ${Math.max(margin * 2, inputRect.left + margin)}px)`; + + const activeOption = menu.querySelector( + ".arch-panel-extension-combobox-option.has-tooltip.is-tooltip-active, .arch-panel-extension-combobox-option.has-tooltip:hover, .arch-panel-extension-combobox-option.has-tooltip:focus, .arch-panel-extension-combobox-option.has-tooltip.is-active" + ); + + if (activeOption instanceof HTMLElement) { + setActiveTimeManagementComboboxTooltipOption(activeOption); + } + } + + function scrollSelectedTimeManagementComboboxOptionIntoView(input) { + const combobox = input.closest(".arch-panel-extension-combobox"); + const menu = combobox?.querySelector(".arch-panel-extension-combobox-menu"); + const selectedOption = menu?.querySelector( + ".arch-panel-extension-combobox-option.is-selected:not([hidden])" + ); + + if (!(menu instanceof HTMLElement) || !(selectedOption instanceof HTMLElement)) { + return; + } + + const targetScrollTop = + selectedOption.offsetTop - + Math.max(0, (menu.clientHeight - selectedOption.offsetHeight) / 2); + + menu.scrollTop = Math.max(0, targetScrollTop); + } + + function setActiveTimeManagementComboboxTooltipOption(option) { + const menu = option.closest(".arch-panel-extension-combobox-menu"); + + if (!(menu instanceof HTMLElement) || option.hidden) { + return; + } + + clearTimeManagementComboboxTooltipOptions(menu, option); + option.classList.add("is-tooltip-active"); + positionTimeManagementComboboxOptionTooltip(option); + } + + function clearTimeManagementComboboxTooltipOptions(root, exceptOption = null) { + if (!(root instanceof Element)) { + return; + } + + root + .querySelectorAll(".arch-panel-extension-combobox-option.is-tooltip-active") + .forEach((item) => { + if (item !== exceptOption) { + item.classList.remove("is-tooltip-active"); + } + }); + } + + function positionTimeManagementComboboxOptionTooltip(option) { + const menu = option.closest(".arch-panel-extension-combobox-menu"); + const tooltip = option.querySelector( + ".arch-panel-extension-service-request-tooltip" + ); + + if (!(menu instanceof HTMLElement) || !(tooltip instanceof HTMLElement)) { + return; + } + + const menuRect = menu.getBoundingClientRect(); + const optionRect = option.getBoundingClientRect(); + const padding = 10; + const viewportWidth = window.innerWidth || document.documentElement.clientWidth; + const viewportHeight = window.innerHeight || document.documentElement.clientHeight; + const portal = getTimeManagementServiceRequestTooltipPortal(); + + if (!(portal instanceof HTMLElement)) { + return; + } + + const tooltipWidth = Math.min( + 420, + Math.max(320, Math.min(menuRect.width * 0.38, viewportWidth - padding * 2)) + ); + const reservedTextWidth = tooltipWidth + 28; + const maxTooltipHeight = Math.max(180, Math.min(380, viewportHeight - padding * 2)); + const optionCenter = optionRect.top + optionRect.height / 2; + const availableRight = viewportWidth - optionRect.right - padding * 2; + const availableLeft = optionRect.left - padding * 2; + const verticalPadding = 12; + + portal.innerHTML = tooltip.innerHTML; + portal.style.width = `${tooltipWidth}px`; + portal.style.maxHeight = `${maxTooltipHeight}px`; + portal.classList.add("is-visible"); + + const tooltipHeight = Math.min( + maxTooltipHeight, + Math.max(120, portal.getBoundingClientRect().height || portal.scrollHeight) + ); + const preferredRight = Math.min( + menuRect.right - tooltipWidth - padding, + viewportWidth - tooltipWidth - padding + ); + const preferredLeft = menuRect.left + padding; + const hasRoomOnRight = availableRight >= tooltipWidth; + const hasRoomOnLeft = availableLeft >= tooltipWidth; + const tooltipLeft = hasRoomOnRight + ? optionRect.right + padding + : hasRoomOnLeft + ? optionRect.left - padding - tooltipWidth + : Math.max(padding, Math.min(preferredRight, preferredLeft)); + const tooltipTop = Math.max( + verticalPadding, + Math.min( + optionCenter - tooltipHeight / 2, + viewportHeight - tooltipHeight - verticalPadding + ) + ); + + portal.style.top = `${tooltipTop}px`; + portal.style.left = `${tooltipLeft}px`; + portal.style.right = "auto"; + portal.style.bottom = "auto"; + portal.style.maxHeight = `${Math.min(maxTooltipHeight, viewportHeight - tooltipTop - verticalPadding)}px`; + menu.style.setProperty( + "--arch-service-request-tooltip-reserved-width", + `${reservedTextWidth}px` + ); + } + + function getTimeManagementServiceRequestTooltipPortal(createIfMissing = true) { + const overlay = document.getElementById(globalThis.ArchPanelConfig.IDS.overlay); + let portal = overlay?.querySelector( + ".arch-panel-extension-service-request-tooltip-portal" + ); + + if (!portal && createIfMissing && overlay) { + portal = document.createElement("div"); + portal.className = + "arch-panel-extension-service-request-tooltip arch-panel-extension-service-request-tooltip-portal"; + portal.setAttribute("role", "tooltip"); + overlay.appendChild(portal); + } + + return portal; + } + + function hideTimeManagementServiceRequestTooltipPortal() { + const portal = getTimeManagementServiceRequestTooltipPortal(false); + + if (portal instanceof HTMLElement) { + portal.classList.remove("is-visible"); + } } function handleTimeManagementHourKeydown(event, input) { @@ -524,6 +852,11 @@ handleOverlayChange, handleOverlayInput, handleOverlayKeydown, + handleOverlayFocusIn, + handleOverlayPointerOver, + handleOverlayPointerMove, + handleOverlayPointerOut, + handleOverlayScroll, }); } diff --git a/src/content/styles.css b/src/content/styles.css index fc5c255..fc1f8f2 100644 --- a/src/content/styles.css +++ b/src/content/styles.css @@ -1619,6 +1619,8 @@ body.arch-panel-extension-modal-open { } .arch-panel-extension-time-management-card { + position: relative; + z-index: 1; display: grid; --arch-time-management-scrollbar-gutter: 16px; gap: 4px; @@ -1630,6 +1632,10 @@ body.arch-panel-extension-modal-open { border-color: rgba(217, 79, 43, 0.42); } +.arch-panel-extension-time-management-card:has(.arch-panel-extension-combobox:focus-within) { + z-index: 40; +} + .arch-panel-extension-time-management-toolbar { display: flex; align-items: end; @@ -1875,6 +1881,10 @@ body.arch-panel-extension-modal-open { overflow: visible; } +.arch-panel-extension-time-management-rows:has(.arch-panel-extension-combobox-option.has-tooltip:hover) { + overflow: visible; +} + .arch-panel-extension-time-management-empty { grid-column: 1 / -1; text-align: center; @@ -1901,6 +1911,7 @@ body.arch-panel-extension-modal-open { } .arch-panel-extension-time-management-entry-row { + position: relative; display: grid; grid-template-columns: minmax(420px, 3.8fr) @@ -1912,6 +1923,10 @@ body.arch-panel-extension-modal-open { gap: 6px; } +.arch-panel-extension-time-management-entry-row:has(.arch-panel-extension-combobox:focus-within) { + z-index: 60; +} + .arch-panel-extension-time-management-entry-row.is-compact { align-items: center; } @@ -1950,9 +1965,14 @@ body.arch-panel-extension-modal-open { .arch-panel-extension-combobox { position: relative; + z-index: 0; display: block; } +.arch-panel-extension-combobox:focus-within { + z-index: 70; +} + .arch-panel-extension-combobox input { min-height: 40px; padding-left: 12px; @@ -1991,6 +2011,13 @@ body.arch-panel-extension-modal-open { white-space: nowrap; } +.arch-panel-extension-combobox-value-text { + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + .arch-panel-extension-combobox:focus-within .arch-panel-extension-combobox-value { display: none; } @@ -2042,9 +2069,10 @@ body.arch-panel-extension-modal-open { .arch-panel-extension-combobox-menu { position: absolute; top: calc(100% + 6px); + bottom: auto; left: 0; right: 0; - z-index: 10000; + z-index: 2147483000; display: none; max-height: 260px; overflow: auto; @@ -2055,6 +2083,15 @@ body.arch-panel-extension-modal-open { padding: 6px; } +.arch-panel-extension-combobox-menu.has-service-request-tooltips { + right: auto; + width: min(1120px, calc(100vw - 72px)); + max-height: min(58vh, 430px); + overscroll-behavior: contain; + padding-bottom: 12px; + scroll-padding-block: 10px 18px; +} + .arch-panel-extension-combobox:focus-within .arch-panel-extension-combobox-menu { display: grid; gap: 4px; @@ -2126,12 +2163,202 @@ body.arch-panel-extension-modal-open { display: none; } +.arch-panel-extension-combobox-option.has-tooltip { + position: static; +} + +.arch-panel-extension-combobox-option.has-tooltip.is-selected::after { + display: none; +} + .arch-panel-extension-combobox-empty { color: var(--wb-text-muted); font-size: 12px; padding: 8px 9px; } +.arch-panel-extension-service-request-tooltip { + --arch-tooltip-panel: var(--wb-panel, #ffffff); + --arch-tooltip-border: var(--wb-border-strong, #c8c8c8); + --arch-tooltip-text: var(--wb-text, #1f1f1f); + --arch-tooltip-muted: var(--wb-text-muted, #6f7782); + --arch-tooltip-link: var(--wb-link, #006b9a); + position: fixed; + top: 10px; + right: auto; + bottom: auto; + left: 10px; + z-index: 2147483647; + display: grid; + gap: 10px; + width: 420px; + max-width: min(420px, calc(100vw - 96px)); + max-height: min(360px, calc(100vh - 20px)); + overflow: auto; + visibility: hidden; + opacity: 0; + pointer-events: auto; + border: 1px solid var(--arch-tooltip-border); + border-radius: 8px; + background: var(--arch-tooltip-panel); + box-shadow: 0 18px 42px rgba(0, 0, 0, 0.24); + color: var(--arch-tooltip-text); + padding: 12px; + transform: translateY(4px); + transition: + opacity 120ms ease 500ms, + transform 120ms ease 500ms, + visibility 0ms linear 500ms; +} + +.arch-panel-extension-combobox-option.has-tooltip > .arch-panel-extension-service-request-tooltip { + display: none; +} + +.arch-panel-extension-service-request-tooltip-portal.is-visible { + visibility: visible; + opacity: 1; + transform: translateY(0); + transition-delay: 0ms; +} + +.arch-panel-extension-combobox-option.has-tooltip:hover .arch-panel-extension-service-request-tooltip, +.arch-panel-extension-combobox-option.has-tooltip:focus .arch-panel-extension-service-request-tooltip, +.arch-panel-extension-combobox-option.has-tooltip.is-tooltip-active .arch-panel-extension-service-request-tooltip { + visibility: visible; + opacity: 1; + transform: translateY(0); + transition-delay: 0ms; +} + +.arch-panel-extension-combobox-menu.has-service-request-tooltips:has(.arch-panel-extension-combobox-option.has-tooltip:hover) + .arch-panel-extension-combobox-option-main, +.arch-panel-extension-combobox-menu.has-service-request-tooltips:has(.arch-panel-extension-combobox-option.has-tooltip:focus) + .arch-panel-extension-combobox-option-main, +.arch-panel-extension-combobox-menu.has-service-request-tooltips:has(.arch-panel-extension-combobox-option.has-tooltip.is-tooltip-active) + .arch-panel-extension-combobox-option-main { + max-width: calc(100% - var(--arch-service-request-tooltip-reserved-width, 448px)); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.arch-panel-extension-service-request-tooltip-title { + display: block; + color: var(--arch-tooltip-text); + font-size: 12px; + font-weight: 800; + line-height: 1.35; +} + +.arch-panel-extension-service-request-tooltip-grid { + display: grid; + grid-template-columns: minmax(110px, 0.42fr) minmax(0, 1fr); + gap: 7px 10px; + align-items: start; +} + +.arch-panel-extension-service-request-tooltip-label { + color: var(--arch-tooltip-muted); + font-size: 10px; + font-weight: 800; + line-height: 1.35; + text-transform: uppercase; +} + +.arch-panel-extension-service-request-tooltip-value { + min-width: 0; + color: var(--arch-tooltip-text); + font-size: 11px; + font-weight: 600; + line-height: 1.4; + overflow-wrap: anywhere; +} + +.arch-panel-extension-service-request-tooltip-excerpt { + display: block; +} + +.arch-panel-extension-service-request-tooltip-more { + display: grid; + gap: 6px; + margin-top: 4px; +} + +.arch-panel-extension-service-request-tooltip-more summary { + width: max-content; + color: var(--arch-tooltip-link); + cursor: pointer; + font-size: 11px; + font-weight: 800; + list-style: none; +} + +.arch-panel-extension-service-request-tooltip-more summary::-webkit-details-marker { + display: none; +} + +.arch-panel-extension-service-request-tooltip-more[open] summary { + margin-bottom: 2px; +} + +.arch-panel-extension-service-request-tooltip-value:has(.arch-panel-extension-service-request-tooltip-more[open]) + .arch-panel-extension-service-request-tooltip-excerpt { + display: none; +} + +@media (max-width: 1180px) { + .arch-panel-extension-combobox-menu.has-service-request-tooltips { + width: min(760px, calc(100vw - 48px)); + } + + .arch-panel-extension-service-request-tooltip { + right: auto; + bottom: auto; + left: 10px; + width: min(340px, calc(100% - 24px)); + transform: translateY(4px); + } + + .arch-panel-extension-combobox-menu.has-service-request-tooltips:has(.arch-panel-extension-combobox-option.has-tooltip:hover) + .arch-panel-extension-combobox-option-main, + .arch-panel-extension-combobox-menu.has-service-request-tooltips:has(.arch-panel-extension-combobox-option.has-tooltip:focus) + .arch-panel-extension-combobox-option-main, + .arch-panel-extension-combobox-menu.has-service-request-tooltips:has(.arch-panel-extension-combobox-option.has-tooltip.is-tooltip-active) + .arch-panel-extension-combobox-option-main { + max-width: calc(100% - var(--arch-service-request-tooltip-reserved-width, 368px)); + } + + .arch-panel-extension-combobox-option.has-tooltip:hover .arch-panel-extension-service-request-tooltip, + .arch-panel-extension-combobox-option.has-tooltip:focus .arch-panel-extension-service-request-tooltip, + .arch-panel-extension-combobox-option.has-tooltip.is-tooltip-active .arch-panel-extension-service-request-tooltip { + transform: translateY(0); + } +} + +@media (max-width: 760px) { + .arch-panel-extension-combobox-menu.has-service-request-tooltips { + width: min(520px, calc(100vw - 32px)); + } + + .arch-panel-extension-combobox-menu.has-service-request-tooltips:has(.arch-panel-extension-combobox-option.has-tooltip:hover) + .arch-panel-extension-combobox-option-main, + .arch-panel-extension-combobox-menu.has-service-request-tooltips:has(.arch-panel-extension-combobox-option.has-tooltip:focus) + .arch-panel-extension-combobox-option-main, + .arch-panel-extension-combobox-menu.has-service-request-tooltips:has(.arch-panel-extension-combobox-option.has-tooltip.is-tooltip-active) + .arch-panel-extension-combobox-option-main { + max-width: 100%; + } + + .arch-panel-extension-service-request-tooltip { + right: auto; + bottom: auto; + left: 8px; + width: min(420px, calc(100vw - 48px)); + max-height: min(320px, calc(100vh - 120px)); + } +} + .arch-panel-extension-time-management-days { display: grid; grid-template-columns: repeat(7, minmax(110px, 1fr)); diff --git a/src/domain/time-management-data.js b/src/domain/time-management-data.js index 54b4ce8..45d746d 100644 --- a/src/domain/time-management-data.js +++ b/src/domain/time-management-data.js @@ -14,6 +14,34 @@ getTimeManagementWeekdayName, createTimeManagementRowSignature, } = dependencies; + const ACTIVITY_REQUEST_DETAIL_FIELDS = [ + "SrNumber", + "ProblemDescription", + "AccountPartyUniqueName", + "ServiceNew_c", + "OptyOwner_c", + "OptyName_c", + "OptyNumber_c", + "ReportedByPartyUniqueName", + "StatusCdMeaning", + "CountryText_c", + "AssigneePersonName", + "OptyStatus_c", + ]; + const ACTIVITY_REQUEST_DETAIL_LABELS = { + SrNumber: "SR Number", + ProblemDescription: "Problem Description", + AccountPartyUniqueName: "Account", + ServiceNew_c: "Service", + OptyOwner_c: "Opportunity Owner", + OptyName_c: "Opportunity", + OptyNumber_c: "Opportunity Number", + ReportedByPartyUniqueName: "Reported By", + StatusCdMeaning: "Status", + CountryText_c: "Country", + AssigneePersonName: "Assignee", + OptyStatus_c: "Opportunity Status", + }; function normalizeInfoWeekResponse(payload) { return extractArray(payload, ["items", "data", "results", "content"]) @@ -275,16 +303,7 @@ function normalizeActivityRequest(payload, serviceRequests) { const activityOptionsBySr = {}; - const activityItemsBySr = new Map(); - - for (const item of extractArray(payload, ["items", "data", "results", "content"])) { - const srNumber = cleanString(getFieldValue(item, ["SrNumber", "srNumber"])); - const srKey = normalizeString(srNumber); - - if (srKey && !activityItemsBySr.has(srKey)) { - activityItemsBySr.set(srKey, item); - } - } + const activityItemsBySr = getActivityRequestItemsBySr(payload); for (const serviceRequest of serviceRequests || []) { const srNumber = cleanString(serviceRequest?.srNumber); @@ -300,6 +319,140 @@ return activityOptionsBySr; } + function normalizeActivityRequestDetails(payload, serviceRequests) { + const detailsBySr = {}; + const activityItemsBySr = getActivityRequestItemsBySr(payload); + const srNumbers = getActivityRequestDetailSrNumbers( + activityItemsBySr, + serviceRequests + ); + + for (const srNumber of srNumbers) { + const srKey = normalizeString(srNumber); + const activityItem = activityItemsBySr.get(srKey); + const details = normalizeActivityRequestDetailRows(activityItem); + + if (details.length) { + detailsBySr[srNumber] = details; + } + } + + return detailsBySr; + } + + function getActivityRequestItemsBySr(payload) { + const activityItemsBySr = new Map(); + + for (const item of extractArray(payload, ["items", "data", "results", "content"])) { + const srNumber = cleanString(getFieldValue(item, ["SrNumber", "srNumber"])); + const srKey = normalizeString(srNumber); + + if (srKey && !activityItemsBySr.has(srKey)) { + activityItemsBySr.set(srKey, item); + } + } + + return activityItemsBySr; + } + + function getActivityRequestDetailSrNumbers(activityItemsBySr, serviceRequests) { + const seen = new Set(); + + return [ + ...(serviceRequests || []).map((item) => cleanString(item?.srNumber)), + ...Array.from(activityItemsBySr.values()).map((item) => + cleanString(getFieldValue(item, ["SrNumber", "srNumber"])) + ), + ].filter((srNumber) => { + const key = normalizeString(srNumber); + + if (!key || seen.has(key)) { + return false; + } + + seen.add(key); + return true; + }); + } + + function normalizeActivityRequestDetailRows(item) { + if (!item || typeof item !== "object" || Array.isArray(item)) { + return []; + } + + const seen = new Set(); + const preferredRows = ACTIVITY_REQUEST_DETAIL_FIELDS.map((fieldName) => + createActivityRequestDetailRow(fieldName, getActivityRequestDetailValue(item, fieldName)) + ).filter(Boolean); + const extraRows = flattenDisplayRecord(item) + .map(([fieldName, value]) => createActivityRequestDetailRow(fieldName, value)) + .filter(Boolean); + + return [...preferredRows, ...extraRows].filter((row) => { + const key = normalizeActivityRequestDetailKey(row.key); + + if (!key || seen.has(key)) { + return false; + } + + seen.add(key); + return true; + }); + } + + function createActivityRequestDetailRow(fieldName, value) { + if (isActivityRequestDetailExcludedKey(fieldName) || isObjectLike(value)) { + return null; + } + + const detailValue = cleanString(value); + + if (!detailValue) { + return null; + } + + return { + key: cleanString(fieldName), + label: formatActivityRequestDetailLabel(fieldName), + value: detailValue, + }; + } + + function getActivityRequestDetailValue(item, fieldName) { + const lowerFirstFieldName = `${fieldName.charAt(0).toLowerCase()}${fieldName.slice(1)}`; + + return getFieldValue(item, [fieldName, lowerFirstFieldName]); + } + + function isActivityRequestDetailExcludedKey(fieldName) { + return normalizeActivityRequestDetailKey(fieldName).includes("SRACTIVITIESC"); + } + + function normalizeActivityRequestDetailKey(fieldName) { + return normalizeString(fieldName).replace(/[^A-Z0-9]/g, ""); + } + + function isObjectLike(value) { + return value && typeof value === "object"; + } + + function formatActivityRequestDetailLabel(fieldName) { + const cleanFieldName = cleanString(fieldName); + const directLabel = ACTIVITY_REQUEST_DETAIL_LABELS[cleanFieldName]; + + if (directLabel) { + return directLabel; + } + + return cleanFieldName + .split(".") + .pop() + .replace(/_c$/i, "") + .replace(/_/g, " ") + .replace(/([a-z0-9])([A-Z])/g, "$1 $2") + .trim(); + } + function normalizeSrActivityFieldOptions(item) { const activityText = firstNonEmptyString([ getFieldValue(item, ["SRActivities_c", "srActivities_c"]), @@ -811,6 +964,7 @@ normalizeTimeManagementEntryRowSelection, getActivityRequestSrNumbers, normalizeActivityRequest, + normalizeActivityRequestDetails, normalizeTaskTypeRequest, getTimeManagementTaskTypeOptions, getTimeManagementActivityOptions, diff --git a/src/views/manage-time-view.js b/src/views/manage-time-view.js index 5418654..56f7db9 100644 --- a/src/views/manage-time-view.js +++ b/src/views/manage-time-view.js @@ -228,17 +228,12 @@