import { emit, on } from "../lib/bus.js";
import { navigateToView, navigateToItem } from "../lib/router.js";
import { api } from "../lib/api.js";
class OpsShell extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: "open" });
this._currentView = "chat";
this._currentParams = {};
this._takeoverCount = 0;
this.shadowRoot.innerHTML = `
`;
}
connectedCallback() {
this._unsub = on("sse:status", (s) => {
const el = this.shadowRoot.getElementById("sseStatus");
el.textContent = s.ok ? "SSE: connected" : "SSE: disconnected (retrying…)";
});
// Listen for view switch requests from other components
this._unsubSwitch = on("ui:switchView", ({ view }) => {
if (view) this.setView(view, {}, { updateUrl: true });
});
// Listen for router changes (popstate, initial load)
this._unsubRouter = on("router:change", ({ view, params }) => {
this.setView(view, params, { updateUrl: false });
});
// Navigation - intercept clicks on nav links
const navBtns = this.shadowRoot.querySelectorAll(".nav-btn");
for (const btn of navBtns) {
btn.onclick = (e) => {
e.preventDefault();
const view = btn.dataset.view;
this.setView(view, {}, { updateUrl: true });
};
}
// Notification bell click
const bell = this.shadowRoot.getElementById("notificationBell");
bell.onclick = () => {
this.setView("takeovers", {}, { updateUrl: true });
};
// Listen for new takeovers via SSE - update badge immediately
this._unsubTakeover = on("takeover:created", () => {
this._takeoverCount++;
this.updateTakeoverBadge(this._takeoverCount);
});
// Start polling for takeovers
this.pollTakeovers();
this._pollInterval = setInterval(() => this.pollTakeovers(), 30000);
}
disconnectedCallback() {
this._unsub?.();
this._unsubSwitch?.();
this._unsubRouter?.();
this._unsubTakeover?.();
if (this._pollInterval) clearInterval(this._pollInterval);
}
async pollTakeovers() {
try {
const data = await api.takeovers({ limit: 1 });
const count = data.pending_count || (data.items?.length || 0);
this._takeoverCount = count;
this.updateTakeoverBadge(count);
} catch (e) {
// Silently fail - don't break the UI
console.debug("Error polling takeovers:", e);
}
}
updateTakeoverBadge(count) {
const badge = this.shadowRoot.getElementById("takeoverBadge");
const bell = this.shadowRoot.getElementById("notificationBell");
if (count > 0) {
badge.textContent = count > 99 ? "99+" : count;
badge.style.display = "inline";
bell.classList.add("has-pending");
bell.title = `${count} takeover(s) pendiente(s)`;
} else {
badge.style.display = "none";
bell.classList.remove("has-pending");
bell.title = "No hay takeovers pendientes";
}
}
setView(viewName, params = {}, { updateUrl = true } = {}) {
this._currentView = viewName;
this._currentParams = params;
// Update nav buttons
const navBtns = this.shadowRoot.querySelectorAll(".nav-btn");
for (const btn of navBtns) {
btn.classList.toggle("active", btn.dataset.view === viewName);
}
// Update views
const views = this.shadowRoot.querySelectorAll(".view");
for (const view of views) {
const isActive = view.id === `view${viewName.charAt(0).toUpperCase() + viewName.slice(1)}`;
view.classList.toggle("active", isActive);
}
// Update URL if requested
if (updateUrl) {
if (params.id) {
navigateToItem(viewName, params.id);
} else {
navigateToView(viewName);
}
}
// Emit event for components that need to know about route params
emit("router:viewChanged", { view: viewName, params });
}
}
customElements.define("ops-shell", OpsShell);