diff --git a/frontend/public/app.js b/frontend/public/app.js index c2413b3..ccfde08 100644 --- a/frontend/public/app.js +++ b/frontend/public/app.js @@ -11,6 +11,9 @@ const currentViewTitle = document.getElementById("current-view-title"); const globalDateControls = document.getElementById("global-date-controls"); const globalStartDateInput = document.getElementById("global-start-date"); const globalEndDateInput = document.getElementById("global-end-date"); +const organizationHistoryControls = document.getElementById("organization-history-controls"); +const organizationMonthSelect = document.getElementById("organization-month-select"); +const organizationCompareBtn = document.getElementById("organization-compare-btn"); const navButtons = Array.from(document.querySelectorAll(".header-center [data-view]")); const organizationFrame = document.getElementById("organization-frame"); const organizationStage = document.getElementById("organization-stage"); @@ -155,6 +158,89 @@ const globalDateState = { endDate: "2026-01-31", }; +const organizationHistoryState = { + selectedMonth: "", + currentMonth: "", +}; + +function padDatePart(value) { + return String(value).padStart(2, "0"); +} + +function getCurrentMonthValue() { + const now = new Date(); + return `${now.getFullYear()}-${padDatePart(now.getMonth() + 1)}`; +} + +function getMonthLabel(monthValue) { + const [, month] = String(monthValue || "").split("-"); + if (!month) return ""; + const monthNumber = Number(month); + if (!Number.isInteger(monthNumber) || monthNumber <= 0) return ""; + return `${monthNumber}월`; +} + +function getMonthEndDate(monthValue) { + const [yearText, monthText] = String(monthValue || "").split("-"); + const year = Number(yearText); + const month = Number(monthText); + if (!Number.isInteger(year) || !Number.isInteger(month) || month <= 0) return ""; + const lastDay = new Date(year, month, 0); + return `${lastDay.getFullYear()}-${padDatePart(lastDay.getMonth() + 1)}-${padDatePart(lastDay.getDate())}`; +} + +function getTodayDate() { + const now = new Date(); + return `${now.getFullYear()}-${padDatePart(now.getMonth() + 1)}-${padDatePart(now.getDate())}`; +} + +function syncOrganizationHistoryControls() { + if (!organizationHistoryControls) return; + const visible = currentView === "organization"; + organizationHistoryControls.classList.toggle("hidden", !visible); + if (organizationCompareBtn) { + const isCurrentMonth = !organizationHistoryState.selectedMonth || organizationHistoryState.selectedMonth === organizationHistoryState.currentMonth; + organizationCompareBtn.classList.toggle("hidden", !visible || isCurrentMonth); + } +} + +function initializeOrganizationMonthOptions() { + if (!organizationMonthSelect) return; + const now = new Date(); + const year = now.getFullYear(); + const monthCount = now.getMonth() + 1; + organizationMonthSelect.innerHTML = ""; + for (let month = monthCount; month >= 1; month -= 1) { + const value = `${year}-${padDatePart(month)}`; + const option = document.createElement("option"); + option.value = value; + option.textContent = month === monthCount ? `${month}월 (최신)` : `${month}월`; + organizationMonthSelect.append(option); + } + organizationHistoryState.currentMonth = `${year}-${padDatePart(monthCount)}`; + organizationHistoryState.selectedMonth = organizationHistoryState.currentMonth; + organizationMonthSelect.value = organizationHistoryState.selectedMonth; + syncOrganizationHistoryControls(); +} + +function postOrganizationHistoryState() { + if (!organizationFrame?.contentWindow) return; + const selectedMonth = organizationHistoryState.selectedMonth || organizationHistoryState.currentMonth; + const currentMonth = organizationHistoryState.currentMonth || getCurrentMonthValue(); + const isHistorical = Boolean(selectedMonth) && selectedMonth !== currentMonth; + organizationFrame.contentWindow.postMessage( + { + source: "total-control", + type: "organization-history-view", + month: selectedMonth, + asOfDate: isHistorical ? getMonthEndDate(selectedMonth) : "", + historical: isHistorical, + }, + window.location.origin, + ); + syncOrganizationHistoryControls(); +} + function getGlobalAsOfDate() { return globalDateState.endDate || ""; } @@ -188,7 +274,6 @@ function shouldShowGlobalDateControls() { return currentView === "ledger" || currentView === "project" || currentView === "team" - || currentView === "organization" || currentView === "seatmap-admin" || currentView === "seatmap-readonly"; } @@ -196,6 +281,7 @@ function shouldShowGlobalDateControls() { function syncGlobalDateControlVisibility() { if (!globalDateControls) return; globalDateControls.classList.toggle("hidden", !shouldShowGlobalDateControls()); + syncOrganizationHistoryControls(); } function syncGlobalDateControlInputs() { @@ -244,7 +330,6 @@ async function ensureGlobalDateRangeLoaded() { globalDateState.endDate = ends.length ? String(ends[ends.length - 1]).slice(0, 10) : ""; globalDateState.loaded = true; syncGlobalDateControlInputs(); - postGlobalDateRangeToFrame(organizationFrame); postGlobalDateRangeToFrame(projectFrame); postGlobalDateRangeToFrame(teamFrame); } catch (error) { @@ -1446,6 +1531,8 @@ function setActiveView(view) { if (isOrganization && previousView !== "organization" && organizationFrame) { const frameSrc = organizationFrame.dataset.src || organizationFrame.src; organizationFrame.src = resolveAppUrl(frameSrc); + } else if (isOrganization) { + postOrganizationHistoryState(); } if (isProject && previousView !== "project" && projectFrame) { const frameSrc = projectFrame.dataset.src || projectFrame.src; @@ -1547,7 +1634,6 @@ if (logoutBtn) { if (globalStartDateInput) { globalStartDateInput.addEventListener("change", () => { globalDateState.startDate = globalStartDateInput.value || ""; - postGlobalDateRangeToFrame(organizationFrame); postGlobalDateRangeToFrame(projectFrame); postGlobalDateRangeToFrame(teamFrame); }); @@ -1556,7 +1642,6 @@ if (globalStartDateInput) { if (globalEndDateInput) { globalEndDateInput.addEventListener("change", () => { globalDateState.endDate = globalEndDateInput.value || ""; - postGlobalDateRangeToFrame(organizationFrame); postGlobalDateRangeToFrame(projectFrame); postGlobalDateRangeToFrame(teamFrame); if (currentView === "seatmap-admin" || currentView === "seatmap-readonly") { @@ -1567,7 +1652,7 @@ if (globalEndDateInput) { } organizationFrame?.addEventListener("load", () => { - postGlobalDateRangeToFrame(organizationFrame); + postOrganizationHistoryState(); }); projectFrame?.addEventListener("load", () => { @@ -1591,6 +1676,32 @@ navButtons.forEach((button) => { }); }); +if (organizationMonthSelect) { + initializeOrganizationMonthOptions(); + organizationMonthSelect.addEventListener("change", () => { + organizationHistoryState.selectedMonth = organizationMonthSelect.value || organizationHistoryState.currentMonth; + postOrganizationHistoryState(); + }); +} + +if (organizationCompareBtn) { + organizationCompareBtn.addEventListener("click", () => { + if (!organizationFrame?.contentWindow) return; + const fromDate = getMonthEndDate(organizationHistoryState.selectedMonth); + const toDate = getTodayDate(); + if (!fromDate) return; + organizationFrame.contentWindow.postMessage( + { + source: "total-control", + type: "open-history-compare", + fromDate, + toDate, + }, + window.location.origin, + ); + }); +} + Object.values(seatMapDom).forEach((dom) => { dom.officeTabs?.addEventListener("click", (event) => { const button = event.target.closest("[data-seatmap-office]"); diff --git a/frontend/public/index.html b/frontend/public/index.html index 135b78e..f2bd8f4 100644 --- a/frontend/public/index.html +++ b/frontend/public/index.html @@ -13,7 +13,7 @@ - +