diff --git a/backend/app/db.py b/backend/app/db.py index c696db5..cb97550 100755 --- a/backend/app/db.py +++ b/backend/app/db.py @@ -673,7 +673,7 @@ def ensure_history_backfill(cur) -> None: m.id, m.name, COALESCE(m.company, ''), COALESCE(m.rank, ''), COALESCE(m.role, ''), COALESCE(m.department, ''), COALESCE(m.grp, ''), COALESCE(m.division, ''), COALESCE(m.team, ''), COALESCE(m.cell, ''), COALESCE(m.work_status, ''), COALESCE(m.work_time, ''), COALESCE(m.phone, ''), COALESCE(m.email, ''), COALESCE(m.photo_url, ''), - COALESCE(m.updated_at, m.created_at, NOW()), NULL, %s, NULL, 'initial-backfill' + TIMESTAMPTZ '1970-01-01 00:00:00+00', NULL, %s, NULL, 'initial-backfill' FROM members AS m WHERE NOT EXISTS ( SELECT 1 @@ -692,7 +692,7 @@ def ensure_history_backfill(cur) -> None: ) SELECT sp.member_id, sp.seat_map_id, sp.seat_slot_id, COALESCE(sp.seat_label, ''), - COALESCE(sp.updated_at, NOW()), NULL, %s, NULL, 'initial-backfill' + TIMESTAMPTZ '1970-01-01 00:00:00+00', NULL, %s, NULL, 'initial-backfill' FROM seat_positions AS sp WHERE NOT EXISTS ( SELECT 1 @@ -702,3 +702,23 @@ def ensure_history_backfill(cur) -> None: """, (revision_id,), ) + + cur.execute( + """ + UPDATE member_versions + SET valid_from = TIMESTAMPTZ '1970-01-01 00:00:00+00' + WHERE revision_no = %s + AND change_reason = 'initial-backfill' + """, + (revision_id,), + ) + + cur.execute( + """ + UPDATE seat_assignment_versions + SET valid_from = TIMESTAMPTZ '1970-01-01 00:00:00+00' + WHERE revision_no = %s + AND change_reason = 'initial-backfill' + """, + (revision_id,), + ) diff --git a/backend/app/main.py b/backend/app/main.py index b786a5d..7b48295 100755 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -4308,8 +4308,8 @@ def get_seat_layout(seat_map_id: int, as_of: str | None = None) -> dict[str, obj @app.get("/api/seat-maps/{seat_map_id}/viewer") -def get_seat_map_viewer(seat_map_id: int) -> HTMLResponse: - layout = fetch_seat_layout(seat_map_id) +def get_seat_map_viewer(seat_map_id: int, as_of: str | None = None) -> HTMLResponse: + layout = fetch_seat_layout(seat_map_id, parse_as_of(as_of)) seat_map = layout.get("seat_map") or {} if seat_map.get("source_type") not in {"dxf", "fixed_html"}: raise HTTPException(status_code=400, detail="Viewer is only available for supported seat maps.") diff --git a/frontend/public/app.js b/frontend/public/app.js index 5d34fe3..c2413b3 100644 --- a/frontend/public/app.js +++ b/frontend/public/app.js @@ -155,6 +155,10 @@ const globalDateState = { endDate: "2026-01-31", }; +function getGlobalAsOfDate() { + return globalDateState.endDate || ""; +} + function getSession() { try { return JSON.parse(sessionStorage.getItem(sessionKey) || "null"); @@ -181,7 +185,12 @@ function buildAuthHeaders(headers) { } function shouldShowGlobalDateControls() { - return currentView === "ledger" || currentView === "project" || currentView === "team" || currentView === "organization"; + return currentView === "ledger" + || currentView === "project" + || currentView === "team" + || currentView === "organization" + || currentView === "seatmap-admin" + || currentView === "seatmap-readonly"; } function syncGlobalDateControlVisibility() { @@ -208,6 +217,12 @@ function postGlobalDateRangeToFrame(frame) { frame.contentWindow.postMessage(getGlobalDateRangePayload(), window.location.origin); } +function buildAsOfQuery() { + const asOf = getGlobalAsOfDate(); + if (!asOf) return ""; + return `?as_of=${encodeURIComponent(asOf)}`; +} + function notifyEmbeddedTabActivated() { if (currentView === "project" && projectFrame?.contentWindow) { projectFrame.contentWindow.postMessage({ source: "total-control", type: "tab-activated", tab: "project" }, window.location.origin); @@ -229,6 +244,7 @@ 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) { @@ -858,7 +874,7 @@ function renderDxfSeatMapBoard() { seatMapBoard.innerHTML = `