Files
arch-panel-extension-browser/src/content/manage-time-controller.js

945 lines
28 KiB
JavaScript

(() => {
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,
});
})();