(() => { function create(dependencies) { const { 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, normalizeTaskTypeRequest, getTimeManagementTaskTypeOptions, getTimeManagementActivityOptions, findTimeManagementServiceRequest, findOptionValue, } = dependencies; let requestCounter = 0; function toggleTimeManagementDatePicker() { const selectedDate = getTimeManagementSelectedDate(); const pickerDate = parseDatePreservingDateOnly(selectedDate); const fallback = Number.isNaN(pickerDate.getTime()) ? new Date() : pickerDate; appState.timeManagementDatePicker = { isOpen: !appState.timeManagementDatePicker.isOpen, year: fallback.getFullYear(), month: fallback.getMonth(), }; renderModal(); } function shiftTimeManagementDatePickerMonth(delta) { const picker = appState.timeManagementDatePicker; const nextDate = new Date(picker.year, picker.month + delta, 1); appState.timeManagementDatePicker = { isOpen: true, year: nextDate.getFullYear(), month: nextDate.getMonth(), }; renderModal(); } async function selectTimeManagementDate(selectedDate) { appState.timeManagementDatePicker = { ...appState.timeManagementDatePicker, isOpen: false, }; await loadTimeManagementData(selectedDate); } async function shiftTimeManagementDateByWeeks(delta) { const selectedDate = getTimeManagementSelectedDate(); const parsedDate = parseDatePreservingDateOnly(selectedDate); const baseDate = Number.isNaN(parsedDate.getTime()) ? new Date() : parsedDate; baseDate.setDate(baseDate.getDate() + (Number(delta) || 0) * 7); appState.timeManagementDatePicker = { ...appState.timeManagementDatePicker, isOpen: false, }; await loadTimeManagementData(getDateInputValue(baseDate)); } function getTimeManagementSelectedDate() { return appState.timeManagementModal?.selectedDate || getDateInputValue(new Date()); } async function openTimeManagementModal() { const selectedDate = getStoredTimeManagementSelectedDate(); appState.timeManagementModal = createTimeManagementLoadingState(selectedDate); renderModal(); await loadTimeManagementData(selectedDate); } async function loadTimeManagementData(selectedDate) { const normalizedDate = cleanString(selectedDate) || getDateInputValue(new Date()); const requestId = `${Date.now()}-${++requestCounter}`; setStoredTimeManagementSelectedDate(normalizedDate); appState.timeManagementDatePicker = { ...appState.timeManagementDatePicker, isOpen: false, }; appState.timeManagementModal = { ...(appState.timeManagementModal || {}), requestId, selectedDate: normalizedDate, status: "loading", errorMessage: "", }; renderModal(); try { const resourcePartyId = getCurrentUserResourcePartyId(); const taskTypePromise = fetchTaskTypeRequest(); if (!resourcePartyId) { throw new Error("resourceCurrentUser.resourcePartyId nao esta disponivel."); } const weekInfoPayload = await comcipRepository.fetchInfoWeekRequest( normalizedDate ); const weekDays = normalizeInfoWeekResponse(weekInfoPayload); const weekId = cleanString(weekDays[0]?.weekId); if (!weekId) { throw new Error("WeekId nao encontrado no retorno de infoWeekRequest."); } const timeEntryWeekUrl = comcipRepository.buildTimeEntryWeekUrl( resourcePartyId, weekId ); const [srPayload, taskTypePayload, timeEntryWeekPayload] = await Promise.all([ comcipRepository.fetchActiveServiceRequests(resourcePartyId, weekId), taskTypePromise, comcipRepository.fetchTimeEntryWeek(timeEntryWeekUrl), ]); const timeEntryRows = normalizeTimeManagementEntryRows( timeEntryWeekPayload, weekDays ); const serviceRequests = mergeTimeManagementServiceRequests( normalizeTimeManagementServiceRequests(srPayload), timeEntryRows ); const activitySrNumbers = getActivityRequestSrNumbers( srPayload, serviceRequests ); const activityRequest = await fetchCachedActivityRequest(activitySrNumbers); const activityRequestUrl = activityRequest.url; const activityPayload = activityRequest.payload; const activityOptionsBySr = normalizeActivityRequest( activityPayload, serviceRequests ); const taskTypes = normalizeTaskTypeRequest(taskTypePayload); const rows = timeEntryRows; const selectedSrNumber = rows[0]?.selectedSrNumber || ""; const normalizedRows = rows.map((row, index) => normalizeTimeManagementEntryRowSelection( row, index, activityOptionsBySr, taskTypes ) ); if (appState.timeManagementModal?.requestId !== requestId) { return; } appState.timeManagementModal = { requestId, selectedDate: normalizedDate, status: "ready", errorMessage: "", weekId, weekDays, serviceRequests, selectedSrNumber, activityOptionsBySr, activityRequestUrl, activityRequestPayload: activityPayload, timeEntryRows: normalizedRows, selectedActivityValue: normalizedRows[0]?.selectedActivityValue || "", taskTypes, selectedTaskTypeValue: normalizedRows[0]?.selectedTaskTypeValue || "", highlightRowId: "", deletedTimeEntryIds: [], isSaving: false, saveErrorMessage: "", }; renderModal(); } catch (error) { if (appState.timeManagementModal?.requestId !== requestId) { return; } appState.timeManagementModal = { ...(appState.timeManagementModal || {}), requestId, selectedDate: normalizedDate, status: "error", errorMessage: getErrorMessage(error), }; renderModal(); } } function createTimeManagementLoadingState(selectedDate) { return { requestId: "", selectedDate, status: "loading", errorMessage: "", weekId: "", weekDays: [], serviceRequests: [], selectedSrNumber: "", activityOptionsBySr: {}, activityRequestUrl: "", activityRequestPayload: null, selectedActivityValue: "", taskTypes: [], selectedTaskTypeValue: "", timeEntryRows: [], highlightRowId: "", deletedTimeEntryIds: [], isSaving: false, saveErrorMessage: "", }; } function createBlankTimeManagementEntryRow(srNumber = "", source = "blank") { return { id: `row-${Date.now()}-${Math.random().toString(16).slice(2)}`, timeEntryId: "", selectedSrNumber: cleanString(srNumber), selectedActivityValue: "", selectedTaskTypeValue: "", dayHours: {}, source, originalSignature: "", }; } function addTimeManagementEntryRow(type) { if (!appState.timeManagementModal) { return; } const existingDuplicateRow = findExistingTimeManagementDuplicateRow( appState.timeManagementModal, type ); if (existingDuplicateRow) { highlightTimeManagementDuplicateRow(existingDuplicateRow.id); return; } const srNumber = type === "non-service" ? "non-service-sr" : ""; const nextRow = createBlankTimeManagementEntryRow(srNumber, "manual"); const nextRows = [ ...getTimeManagementRows(appState.timeManagementModal), nextRow, ]; appState.timeManagementModal = { ...appState.timeManagementModal, timeEntryRows: nextRows, highlightRowId: "", saveErrorMessage: "", }; appState.pendingFocusSelector = getTimeManagementComboboxFocusSelector( nextRow.id, type === "non-service" ? "taskType" : "sr" ); renderModal(); } function removeTimeManagementEntryRow(rowId) { if (!appState.timeManagementModal) { return; } const normalizedRowId = cleanString(rowId); const rows = getTimeManagementRows(appState.timeManagementModal); const removedRow = rows.find( (row) => cleanString(row?.id) === normalizedRowId ); const removedTimeEntryId = cleanString(removedRow?.timeEntryId); const nextRows = rows.filter( (row) => cleanString(row?.id) !== normalizedRowId ); const deletedTimeEntryIds = removedTimeEntryId ? Array.from( new Set([ ...(appState.timeManagementModal.deletedTimeEntryIds || []), removedTimeEntryId, ]) ) : appState.timeManagementModal.deletedTimeEntryIds || []; appState.timeManagementModal = { ...appState.timeManagementModal, timeEntryRows: nextRows, selectedSrNumber: nextRows[0]?.selectedSrNumber || "", selectedActivityValue: nextRows[0]?.selectedActivityValue || "", selectedTaskTypeValue: nextRows[0]?.selectedTaskTypeValue || "", highlightRowId: "", deletedTimeEntryIds, saveErrorMessage: "", }; appState.pendingFocusSelector = getTimeManagementPostDeleteFocusSelector( nextRows, rows, normalizedRowId ); renderModal(); } function getTimeManagementComboboxFocusSelector(rowId, field) { return `.arch-panel-extension-combobox input[data-row-id="${cssEscape(rowId)}"][data-combobox-field="${cssEscape(field)}"]`; } function getTimeManagementPostDeleteFocusSelector( nextRows, previousRows, removedRowId ) { if (!nextRows.length) { return ""; } const removedIndex = previousRows.findIndex( (row) => cleanString(row?.id) === cleanString(removedRowId) ); const focusIndex = Math.min(Math.max(removedIndex, 0), nextRows.length - 1); const focusRow = nextRows[focusIndex]; return focusRow?.id ? `.arch-panel-extension-time-management-delete[data-row-id="${cssEscape(focusRow.id)}"]` : ""; } function getTimeManagementRowById(modal, rowId) { const rows = getTimeManagementRows(modal); const normalizedRowId = cleanString(rowId); return ( rows.find((row) => cleanString(row?.id) === normalizedRowId) || rows[0] || null ); } function updateTimeManagementRows(modal, rowId, nextRow) { const rows = getTimeManagementRows(modal); const normalizedRowId = cleanString(rowId || nextRow?.id); return rows.map((row, index) => { const isTarget = normalizedRowId ? cleanString(row?.id) === normalizedRowId : index === 0; return isTarget ? { ...row, ...nextRow, id: row?.id || nextRow?.id || `row-${index + 1}`, } : row; }); } function updateTimeManagementSelectedSr(srNumber, rowId = "") { if (!appState.timeManagementModal) { return; } const targetRow = getTimeManagementRowById(appState.timeManagementModal, rowId); const selectedServiceRequest = findTimeManagementServiceRequest( appState.timeManagementModal, srNumber ); const selectedSrNumber = selectedServiceRequest?.srNumber || cleanString(srNumber); const duplicateRow = findDuplicateTimeManagementSrRow( appState.timeManagementModal, selectedSrNumber, rowId ); if (duplicateRow) { highlightTimeManagementDuplicateRow(duplicateRow.id); return; } const nextRow = { ...(targetRow || createBlankTimeManagementEntryRow()), selectedSrNumber, selectedActivityValue: "", selectedTaskTypeValue: "", }; const nextRows = updateTimeManagementRows( appState.timeManagementModal, rowId, nextRow ); appState.timeManagementModal = { ...appState.timeManagementModal, timeEntryRows: nextRows, selectedSrNumber: nextRows[0]?.selectedSrNumber || selectedSrNumber, selectedActivityValue: nextRows[0]?.selectedActivityValue || "", selectedTaskTypeValue: nextRows[0]?.selectedTaskTypeValue || "", highlightRowId: "", saveErrorMessage: "", }; renderModal(); } function updateTimeManagementField(fieldName, inputValue, rowId = "") { if (!appState.timeManagementModal) { return; } const targetRow = getTimeManagementRowById(appState.timeManagementModal, rowId); const normalizedInput = cleanString(inputValue); let value = normalizedInput; if (fieldName === "selectedActivityValue") { const options = getTimeManagementActivityOptions( appState.timeManagementModal, targetRow?.selectedSrNumber || appState.timeManagementModal.selectedSrNumber ); value = findOptionValue(options, normalizedInput) || normalizedInput; } if (fieldName === "selectedTaskTypeValue") { const options = getTimeManagementTaskTypeOptions( appState.timeManagementModal, targetRow?.selectedSrNumber || appState.timeManagementModal.selectedSrNumber ); value = findOptionValue(options, normalizedInput) || normalizedInput; const duplicateRow = findDuplicateTimeManagementNonServiceTaskTypeRow( appState.timeManagementModal, value, rowId ); if (duplicateRow) { highlightTimeManagementDuplicateRow(duplicateRow.id); return; } } const nextRows = updateTimeManagementRows(appState.timeManagementModal, rowId, { ...(targetRow || createBlankTimeManagementEntryRow()), [fieldName]: value, }); appState.timeManagementModal = { ...appState.timeManagementModal, timeEntryRows: nextRows, [fieldName]: value, highlightRowId: "", saveErrorMessage: "", }; } function findDuplicateTimeManagementSrRow(modal, srNumber, currentRowId = "") { const normalizedSrNumber = normalizeString(srNumber); const normalizedCurrentRowId = cleanString(currentRowId); if (!normalizedSrNumber || isNonServiceRequestSrNumber(srNumber)) { return null; } return ( getTimeManagementRows(modal).find((row) => { return ( cleanString(row?.id) !== normalizedCurrentRowId && normalizeString(row?.selectedSrNumber) === normalizedSrNumber ); }) || null ); } function findExistingTimeManagementDuplicateRow(modal, type) { const seen = new Map(); for (const row of getTimeManagementRows(modal)) { const rowId = cleanString(row?.id); let key = ""; if (type === "non-service") { if (!isNonServiceRequestSrNumber(row?.selectedSrNumber)) { continue; } key = normalizeString(row?.selectedTaskTypeValue); } else { if (isNonServiceRequestSrNumber(row?.selectedSrNumber)) { continue; } key = normalizeString(row?.selectedSrNumber); } if (!key) { continue; } if (seen.has(key)) { return ( getTimeManagementRows(modal).find( (candidate) => cleanString(candidate?.id) === seen.get(key) ) || row ); } seen.set(key, rowId); } return null; } function findDuplicateTimeManagementNonServiceTaskTypeRow( modal, taskTypeValue, currentRowId = "" ) { const normalizedTaskTypeValue = normalizeString(taskTypeValue); const normalizedCurrentRowId = cleanString(currentRowId); if (!normalizedTaskTypeValue) { return null; } const currentRow = getTimeManagementRowById(modal, currentRowId); if (!isNonServiceRequestSrNumber(currentRow?.selectedSrNumber)) { return null; } return ( getTimeManagementRows(modal).find((row) => { return ( cleanString(row?.id) !== normalizedCurrentRowId && isNonServiceRequestSrNumber(row?.selectedSrNumber) && normalizeString(row?.selectedTaskTypeValue) === normalizedTaskTypeValue ); }) || null ); } function highlightTimeManagementDuplicateRow(rowId) { const normalizedRowId = cleanString(rowId); if (!appState.timeManagementModal || !normalizedRowId) { return; } appState.pendingTimeManagementHighlightRowId = normalizedRowId; appState.timeManagementModal = { ...appState.timeManagementModal, highlightRowId: normalizedRowId, }; renderModal(); } function updateTimeManagementDayHours(rowId, dateKey, inputValue) { if (!appState.timeManagementModal) { return; } const targetRow = getTimeManagementRowById(appState.timeManagementModal, rowId); const nextRow = { ...(targetRow || createBlankTimeManagementEntryRow()), dayHours: { ...(targetRow?.dayHours || {}), [cleanString(dateKey)]: cleanString(inputValue), }, }; appState.timeManagementModal = { ...appState.timeManagementModal, timeEntryRows: updateTimeManagementRows( appState.timeManagementModal, rowId, nextRow ), highlightRowId: "", saveErrorMessage: "", }; renderModal(); } async function saveTimeManagementChanges() { const modal = appState.timeManagementModal; if (!modal || modal.isSaving || !isTimeManagementSaveEnabled(modal)) { return; } appState.timeManagementModal = { ...modal, isSaving: true, saveErrorMessage: "", }; renderModal(); try { const resourceId = getCurrentUserResourcePartyId(); const weekId = cleanString(modal.weekId); if (!resourceId) { throw new Error("resourceCurrentUser.resourcePartyId nao esta disponivel."); } if (!weekId) { throw new Error("WeekId nao encontrado para gravacao do apontamento."); } const changes = getTimeManagementPendingChanges(modal); const baseUrl = COMCIP_REQUEST.timeEntriesUrl; const updatedPayload = changes.updatedRows.map((row) => createTimeManagementSavePayload(row, modal.weekDays, weekId, resourceId, { includeId: true, }) ); const newPayload = changes.newRows.map((row) => createTimeManagementSavePayload(row, modal.weekDays, weekId, resourceId) ); for (const timeEntryId of changes.deletedIds) { await comcipRepository.mutateComcipPayload( "DELETE", `${baseUrl}/${encodeURIComponent(timeEntryId)}`, null, "timeEntryDelete" ); } if (updatedPayload.length) { await comcipRepository.mutateComcipPayload( "PUT", baseUrl, updatedPayload, "timeEntryUpdate" ); } if (newPayload.length) { await comcipRepository.mutateComcipPayload( "POST", baseUrl, newPayload, "timeEntryCreate" ); } await loadTimeManagementData( modal.selectedDate || getDateInputValue(new Date()) ); } catch (error) { appState.timeManagementModal = { ...(appState.timeManagementModal || modal), isSaving: false, saveErrorMessage: getErrorMessage(error), }; renderModal(); } } function selectTimeManagementComboboxOption(field, value, rowId = "") { if (field === "sr") { updateTimeManagementSelectedSr(value || "", rowId); return; } if (field === "activity") { updateTimeManagementField("selectedActivityValue", value || "", rowId); renderModal(); return; } if (field === "taskType") { updateTimeManagementField("selectedTaskTypeValue", value || "", rowId); renderModal(); } } function filterTimeManagementCombobox(input) { const query = normalizeString(input.value); const combobox = input.closest(".arch-panel-extension-combobox"); if (!combobox) { return; } const options = Array.from( combobox.querySelectorAll(".arch-panel-extension-combobox-option") ); for (const option of options) { const searchText = normalizeString(option.getAttribute("data-search") || ""); option.hidden = Boolean(query) && !searchText.includes(query); } setActiveTimeManagementComboboxOption( combobox, getVisibleTimeManagementComboboxOptions(combobox).find((option) => option.classList.contains("is-selected") ) || getVisibleTimeManagementComboboxOptions(combobox)[0] ); } function handleTimeManagementComboboxKeydown(event, input) { const combobox = input.closest(".arch-panel-extension-combobox"); if (!combobox) { return; } if (event.key === "Escape") { clearActiveTimeManagementComboboxOption(combobox); input.blur(); return; } const visibleOptions = getVisibleTimeManagementComboboxOptions(combobox); if (!visibleOptions.length) { return; } event.preventDefault(); if (event.key === "Enter") { const activeOption = visibleOptions.find((option) => option.classList.contains("is-active")) || visibleOptions[0]; activeOption.click(); return; } const activeIndex = visibleOptions.findIndex((option) => option.classList.contains("is-active") ); const selectedIndex = visibleOptions.findIndex((option) => option.classList.contains("is-selected") ); const baseIndex = activeIndex >= 0 ? activeIndex : selectedIndex; let nextIndex = 0; if (event.key === "ArrowDown") { nextIndex = baseIndex >= 0 ? baseIndex + 1 : 0; } else if (event.key === "ArrowUp") { nextIndex = baseIndex >= 0 ? baseIndex - 1 : visibleOptions.length - 1; } else if (event.key === "Home") { nextIndex = 0; } else if (event.key === "End") { nextIndex = visibleOptions.length - 1; } if (nextIndex < 0) { nextIndex = visibleOptions.length - 1; } else if (nextIndex >= visibleOptions.length) { nextIndex = 0; } setActiveTimeManagementComboboxOption(combobox, visibleOptions[nextIndex]); } function getVisibleTimeManagementComboboxOptions(combobox) { return Array.from( combobox.querySelectorAll(".arch-panel-extension-combobox-option") ).filter((option) => !option.hidden); } function setActiveTimeManagementComboboxOption(combobox, option) { clearActiveTimeManagementComboboxOption(combobox); if (!option) { return; } option.classList.add("is-active"); combobox .querySelector("input[data-combobox-field]") ?.setAttribute("aria-activedescendant", option.id || ""); option.scrollIntoView({ block: "nearest" }); } function clearActiveTimeManagementComboboxOption(combobox) { combobox .querySelectorAll(".arch-panel-extension-combobox-option.is-active") .forEach((option) => option.classList.remove("is-active")); combobox .querySelector("input[data-combobox-field]") ?.removeAttribute("aria-activedescendant"); } async function fetchCachedActivityRequest(srNumbers) { const cacheKey = getActivityRequestCacheKey(srNumbers); if (!cacheKey) { return { url: "", payload: null, }; } const cachedRequest = appState.timeManagementActivityCache.get(cacheKey); if (cachedRequest?.payload) { return cachedRequest; } if (cachedRequest?.promise) { return cachedRequest.promise; } const requestPromise = comcipRepository .fetchActivityRequest(srNumbers) .then((result) => { appState.timeManagementActivityCache.set(cacheKey, { url: result.url, payload: result.payload, }); return result; }) .catch((error) => { appState.timeManagementActivityCache.delete(cacheKey); throw error; }); appState.timeManagementActivityCache.set(cacheKey, { promise: requestPromise, }); return requestPromise; } function getActivityRequestCacheKey(srNumbers) { const normalizedSrNumbers = (srNumbers || []) .map((srNumber) => cleanString(srNumber).toUpperCase()) .filter((srNumber) => /^SR\d+$/i.test(srNumber)) .sort(); return normalizedSrNumbers.join("|"); } async function fetchTaskTypeRequest() { if (appState.taskTypeCache.promise) { return appState.taskTypeCache.promise; } appState.taskTypeCache.promise = (async () => { return readCachedTaskTypePayload().catch(() => null); })().finally(() => { appState.taskTypeCache.promise = null; }); return appState.taskTypeCache.promise; } async function refreshTaskTypeCache() { const payload = await comcipRepository.fetchTaskTypePayload(); await writeCachedTaskTypePayload(payload); return payload; } function getCurrentUserResourcePartyId() { const resourceCurrentUser = appState.dataset?.resourceCurrentUser; const directResourcePartyId = cleanString( getFieldValue(resourceCurrentUser, [ "resourcePartyId", "ResourcePartyId", "ResourcePartyID", "partyId", "PartyId", ]) ); if (directResourcePartyId) { return directResourcePartyId; } const candidates = Array.isArray(resourceCurrentUser) ? resourceCurrentUser : extractArray(resourceCurrentUser, ["items", "data", "results", "content"]); const source = candidates[0] || resourceCurrentUser; return cleanString( getFieldValue(source, [ "resourcePartyId", "ResourcePartyId", "ResourcePartyID", "partyId", "PartyId", ]) ); } return Object.freeze({ toggleTimeManagementDatePicker, shiftTimeManagementDatePickerMonth, selectTimeManagementDate, shiftTimeManagementDateByWeeks, openTimeManagementModal, loadTimeManagementData, addTimeManagementEntryRow, removeTimeManagementEntryRow, updateTimeManagementSelectedSr, updateTimeManagementField, updateTimeManagementDayHours, saveTimeManagementChanges, selectTimeManagementComboboxOption, filterTimeManagementCombobox, handleTimeManagementComboboxKeydown, refreshTaskTypeCache, }); } globalThis.ArchPanelManageTimeController = Object.freeze({ create, }); })();