Implementação do tooltip nas SRs do Manage time

This commit is contained in:
Paulo Porto
2026-05-25 19:21:35 -03:00
parent c989088ab1
commit f38bae4479
6 changed files with 889 additions and 34 deletions

View File

@@ -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: "",

View File

@@ -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,
});
}

View File

@@ -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));