feat: integrate dashboard modules and document history
This commit is contained in:
@@ -8,9 +8,16 @@ const logoutBtn = document.getElementById("logout-btn");
|
||||
const userBadge = document.getElementById("user-badge");
|
||||
const userPopover = document.getElementById("user-popover");
|
||||
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 navButtons = Array.from(document.querySelectorAll(".header-center [data-view]"));
|
||||
const organizationFrame = document.getElementById("organization-frame");
|
||||
const organizationStage = document.getElementById("organization-stage");
|
||||
const projectFrame = document.getElementById("project-frame");
|
||||
const projectStage = document.getElementById("project-stage");
|
||||
const teamFrame = document.getElementById("team-frame");
|
||||
const teamStage = document.getElementById("team-stage");
|
||||
const seatMapAdminStage = document.getElementById("seatmap-admin-stage");
|
||||
const seatMapReadonlyStage = document.getElementById("seatmap-readonly-stage");
|
||||
const emptyStage = document.getElementById("empty-stage");
|
||||
@@ -142,6 +149,11 @@ const seatMapState = {
|
||||
};
|
||||
|
||||
let currentView = "organization";
|
||||
const globalDateState = {
|
||||
loaded: true,
|
||||
startDate: "2026-01-01",
|
||||
endDate: "2026-01-31",
|
||||
};
|
||||
|
||||
function getSession() {
|
||||
try {
|
||||
@@ -159,6 +171,71 @@ function clearSession() {
|
||||
sessionStorage.removeItem(sessionKey);
|
||||
}
|
||||
|
||||
function buildAuthHeaders(headers) {
|
||||
const nextHeaders = new Headers(headers || {});
|
||||
const token = getSession()?.token;
|
||||
if (token && !nextHeaders.has("Authorization")) {
|
||||
nextHeaders.set("Authorization", `Bearer ${token}`);
|
||||
}
|
||||
return nextHeaders;
|
||||
}
|
||||
|
||||
function shouldShowGlobalDateControls() {
|
||||
return currentView === "ledger" || currentView === "project" || currentView === "team" || currentView === "organization";
|
||||
}
|
||||
|
||||
function syncGlobalDateControlVisibility() {
|
||||
if (!globalDateControls) return;
|
||||
globalDateControls.classList.toggle("hidden", !shouldShowGlobalDateControls());
|
||||
}
|
||||
|
||||
function syncGlobalDateControlInputs() {
|
||||
if (globalStartDateInput) globalStartDateInput.value = globalDateState.startDate || "";
|
||||
if (globalEndDateInput) globalEndDateInput.value = globalDateState.endDate || "";
|
||||
}
|
||||
|
||||
function getGlobalDateRangePayload() {
|
||||
return {
|
||||
source: "total-control",
|
||||
type: "date-range",
|
||||
startDate: globalDateState.startDate || "",
|
||||
endDate: globalDateState.endDate || "",
|
||||
};
|
||||
}
|
||||
|
||||
function postGlobalDateRangeToFrame(frame) {
|
||||
if (!frame?.contentWindow || !globalDateState.loaded) return;
|
||||
frame.contentWindow.postMessage(getGlobalDateRangePayload(), window.location.origin);
|
||||
}
|
||||
|
||||
function notifyEmbeddedTabActivated() {
|
||||
if (currentView === "project" && projectFrame?.contentWindow) {
|
||||
projectFrame.contentWindow.postMessage({ source: "total-control", type: "tab-activated", tab: "project" }, window.location.origin);
|
||||
}
|
||||
if (currentView === "team" && teamFrame?.contentWindow) {
|
||||
teamFrame.contentWindow.postMessage({ source: "total-control", type: "tab-activated", tab: "mh" }, window.location.origin);
|
||||
}
|
||||
}
|
||||
|
||||
async function ensureGlobalDateRangeLoaded() {
|
||||
if (globalDateState.loaded) return;
|
||||
try {
|
||||
const payload = await fetchJson("/api/integration/summary");
|
||||
const work = payload?.date_ranges?.work || {};
|
||||
const voucher = payload?.date_ranges?.voucher || {};
|
||||
const starts = [work.min_work_date, voucher.min_voucher_date].filter(Boolean).sort();
|
||||
const ends = [work.max_work_date, voucher.max_voucher_date].filter(Boolean).sort();
|
||||
globalDateState.startDate = starts[0] ? String(starts[0]).slice(0, 10) : "";
|
||||
globalDateState.endDate = ends.length ? String(ends[ends.length - 1]).slice(0, 10) : "";
|
||||
globalDateState.loaded = true;
|
||||
syncGlobalDateControlInputs();
|
||||
postGlobalDateRangeToFrame(projectFrame);
|
||||
postGlobalDateRangeToFrame(teamFrame);
|
||||
} catch (error) {
|
||||
console.error("공통 기간을 불러오지 못했습니다.", error);
|
||||
}
|
||||
}
|
||||
|
||||
function hideUserPopover() {
|
||||
userPopover?.classList.add("hidden");
|
||||
}
|
||||
@@ -1063,7 +1140,11 @@ function handleEmbeddedNavigationMessage(event) {
|
||||
}
|
||||
|
||||
async function fetchJson(url, options) {
|
||||
const response = await fetch(resolveAppUrl(url), options);
|
||||
const requestOptions = {
|
||||
...options,
|
||||
headers: buildAuthHeaders(options?.headers),
|
||||
};
|
||||
const response = await fetch(resolveAppUrl(url), requestOptions);
|
||||
let payload = null;
|
||||
try {
|
||||
payload = await response.json();
|
||||
@@ -1220,6 +1301,9 @@ async function saveSeatLayout() {
|
||||
body: JSON.stringify({ placements: seatMapState.draftPlacements }),
|
||||
});
|
||||
await loadSeatMapData(true);
|
||||
if (organizationFrame?.contentWindow) {
|
||||
organizationFrame.contentWindow.postMessage({ type: "seatmap-layout-updated" }, window.location.origin);
|
||||
}
|
||||
setSeatMapStatus("자리배치를 저장했습니다.", "success");
|
||||
} catch (error) {
|
||||
setSeatMapStatus(error.message || "자리배치도 저장에 실패했습니다.", "error");
|
||||
@@ -1285,6 +1369,10 @@ function setActiveView(view) {
|
||||
if (currentViewTitle) {
|
||||
currentViewTitle.textContent = viewLabels[currentView];
|
||||
}
|
||||
syncGlobalDateControlVisibility();
|
||||
if (shouldShowGlobalDateControls()) {
|
||||
ensureGlobalDateRangeLoaded();
|
||||
}
|
||||
|
||||
navButtons.forEach((button) => {
|
||||
const active = button.dataset.view === currentView;
|
||||
@@ -1293,12 +1381,22 @@ function setActiveView(view) {
|
||||
});
|
||||
|
||||
const isOrganization = currentView === "organization";
|
||||
const isProject = currentView === "project";
|
||||
const isTeam = currentView === "team";
|
||||
const isSeatMapAdmin = currentView === "seatmap-admin";
|
||||
const isSeatMapReadonly = currentView === "seatmap-readonly";
|
||||
if (organizationStage) {
|
||||
organizationStage.hidden = !isOrganization;
|
||||
organizationStage.style.display = isOrganization ? "flex" : "none";
|
||||
}
|
||||
if (projectStage) {
|
||||
projectStage.hidden = !isProject;
|
||||
projectStage.style.display = isProject ? "flex" : "none";
|
||||
}
|
||||
if (teamStage) {
|
||||
teamStage.hidden = !isTeam;
|
||||
teamStage.style.display = isTeam ? "flex" : "none";
|
||||
}
|
||||
if (seatMapAdminStage) {
|
||||
seatMapAdminStage.hidden = !isSeatMapAdmin;
|
||||
seatMapAdminStage.style.display = isSeatMapAdmin ? "flex" : "none";
|
||||
@@ -1308,7 +1406,7 @@ function setActiveView(view) {
|
||||
seatMapReadonlyStage.style.display = isSeatMapReadonly ? "flex" : "none";
|
||||
}
|
||||
if (emptyStage) {
|
||||
const showEmpty = !isOrganization && !isSeatMapAdmin && !isSeatMapReadonly;
|
||||
const showEmpty = !isOrganization && !isProject && !isTeam && !isSeatMapAdmin && !isSeatMapReadonly;
|
||||
emptyStage.hidden = !showEmpty;
|
||||
emptyStage.style.display = showEmpty ? "flex" : "none";
|
||||
}
|
||||
@@ -1317,9 +1415,22 @@ function setActiveView(view) {
|
||||
const frameSrc = organizationFrame.dataset.src || organizationFrame.src;
|
||||
organizationFrame.src = resolveAppUrl(frameSrc);
|
||||
}
|
||||
if (isProject && previousView !== "project" && projectFrame) {
|
||||
const frameSrc = projectFrame.dataset.src || projectFrame.src;
|
||||
projectFrame.src = resolveAppUrl(frameSrc);
|
||||
} else if (isProject) {
|
||||
postGlobalDateRangeToFrame(projectFrame);
|
||||
}
|
||||
if (isTeam && previousView !== "team" && teamFrame) {
|
||||
const frameSrc = teamFrame.dataset.src || teamFrame.src;
|
||||
teamFrame.src = resolveAppUrl(frameSrc);
|
||||
} else if (isTeam) {
|
||||
postGlobalDateRangeToFrame(teamFrame);
|
||||
}
|
||||
if (isSeatMapAdmin || isSeatMapReadonly) {
|
||||
loadSeatMapData();
|
||||
}
|
||||
notifyEmbeddedTabActivated();
|
||||
}
|
||||
|
||||
function renderAuth() {
|
||||
@@ -1329,7 +1440,7 @@ function renderAuth() {
|
||||
dashboardPanel.classList.toggle("hidden", !authenticated);
|
||||
if (authenticated) {
|
||||
const displayName = session.user.display_name || "접속자";
|
||||
const rank = "-";
|
||||
const rank = session.user.rank || "-";
|
||||
const employeeId = session.user.username || "-";
|
||||
userBadge.innerHTML = `<span class="user-chip-icon">◎</span><span class="user-chip-text"><strong>${escapeHtml(displayName)}</strong><em>${escapeHtml(rank)}</em></span><span class="user-chip-caret" aria-hidden="true">▾</span>`;
|
||||
userBadge.title = `${displayName} / -`;
|
||||
@@ -1363,7 +1474,7 @@ if (loginForm) {
|
||||
loginMessage.textContent = "로그인 처리 중입니다.";
|
||||
const formData = new FormData(loginForm);
|
||||
try {
|
||||
const payload = await fetchJson("/api/mock-login", {
|
||||
const payload = await fetchJson("/api/auth/login", {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
});
|
||||
@@ -1388,14 +1499,51 @@ if (userBadge) {
|
||||
}
|
||||
|
||||
if (logoutBtn) {
|
||||
logoutBtn.addEventListener("click", (event) => {
|
||||
logoutBtn.addEventListener("click", async (event) => {
|
||||
event.stopPropagation();
|
||||
try {
|
||||
await fetchJson("/api/auth/logout", {
|
||||
method: "POST",
|
||||
});
|
||||
} catch {
|
||||
// Ignore logout API errors and clear the local session regardless.
|
||||
}
|
||||
clearSession();
|
||||
hideUserPopover();
|
||||
renderAuth();
|
||||
});
|
||||
}
|
||||
|
||||
if (globalStartDateInput) {
|
||||
globalStartDateInput.addEventListener("change", () => {
|
||||
globalDateState.startDate = globalStartDateInput.value || "";
|
||||
postGlobalDateRangeToFrame(projectFrame);
|
||||
postGlobalDateRangeToFrame(teamFrame);
|
||||
});
|
||||
}
|
||||
|
||||
if (globalEndDateInput) {
|
||||
globalEndDateInput.addEventListener("change", () => {
|
||||
globalDateState.endDate = globalEndDateInput.value || "";
|
||||
postGlobalDateRangeToFrame(projectFrame);
|
||||
postGlobalDateRangeToFrame(teamFrame);
|
||||
});
|
||||
}
|
||||
|
||||
projectFrame?.addEventListener("load", () => {
|
||||
postGlobalDateRangeToFrame(projectFrame);
|
||||
if (currentView === "project") {
|
||||
notifyEmbeddedTabActivated();
|
||||
}
|
||||
});
|
||||
|
||||
teamFrame?.addEventListener("load", () => {
|
||||
postGlobalDateRangeToFrame(teamFrame);
|
||||
if (currentView === "team") {
|
||||
notifyEmbeddedTabActivated();
|
||||
}
|
||||
});
|
||||
|
||||
navButtons.forEach((button) => {
|
||||
button.addEventListener("click", () => {
|
||||
hideUserPopover();
|
||||
@@ -1549,6 +1697,7 @@ document.addEventListener("click", () => {
|
||||
|
||||
window.addEventListener("message", handleEmbeddedNavigationMessage);
|
||||
|
||||
syncGlobalDateControlInputs();
|
||||
setActiveView(currentView);
|
||||
renderAuth();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user