/* ============================================================================= DASHBOARDS MODULE - Business Intelligence Dashboards ============================================================================= */ (function () { "use strict"; // ============================================================================= // STATE // ============================================================================= const state = { dashboards: [], dataSources: [], currentDashboard: null, selectedWidgetType: null, isEditing: false, widgets: [], filters: { category: "all", search: "", }, }; // ============================================================================= // INITIALIZATION // ============================================================================= function init() { loadDashboards(); loadDataSources(); bindEvents(); console.log("Dashboards module initialized"); } function bindEvents() { // Search input const searchInput = document.getElementById("dashboard-search"); if (searchInput) { searchInput.addEventListener("input", (e) => { state.filters.search = e.target.value; filterDashboards(); }); } // Category filter const categorySelect = document.getElementById("category-filter"); if (categorySelect) { categorySelect.addEventListener("change", (e) => { state.filters.category = e.target.value; filterDashboards(); }); } // Dashboard card clicks document.addEventListener("click", (e) => { const card = e.target.closest(".dashboard-card"); if (card && !e.target.closest("button")) { const dashboardId = card.dataset.id; if (dashboardId) { openDashboard(dashboardId); } } }); } // ============================================================================= // DASHBOARD CRUD // ============================================================================= async function loadDashboards() { try { const response = await fetch("/api/dashboards"); if (response.ok) { const data = await response.json(); state.dashboards = data.dashboards || []; renderDashboardList(); } } catch (e) { console.error("Failed to load dashboards:", e); } } function renderDashboardList() { const container = document.getElementById("dashboards-grid"); if (!container) return; const filtered = state.dashboards.filter((d) => { const matchesSearch = !state.filters.search || d.name.toLowerCase().includes(state.filters.search.toLowerCase()); const matchesCategory = state.filters.category === "all" || d.category === state.filters.category; return matchesSearch && matchesCategory; }); if (filtered.length === 0) { container.innerHTML = `
πŸ“Š

No dashboards found

Create your first dashboard to visualize your data

`; return; } container.innerHTML = filtered .map( (dashboard) => `
πŸ“Š

${escapeHtml(dashboard.name)}

${escapeHtml(dashboard.description || "No description")}

${escapeHtml(dashboard.category || "General")} Updated ${formatRelativeTime(dashboard.updated_at)}
`, ) .join(""); } function filterDashboards() { renderDashboardList(); } // ============================================================================= // CREATE DASHBOARD MODAL // ============================================================================= function showCreateDashboardModal() { const modal = document.getElementById("createDashboardModal"); if (modal) { modal.style.display = "flex"; // Reset form const form = modal.querySelector("form"); if (form) form.reset(); } } function closeCreateDashboardModal() { const modal = document.getElementById("createDashboardModal"); if (modal) { modal.style.display = "none"; } } async function createDashboard(formData) { const data = { name: formData.get("name") || "Untitled Dashboard", description: formData.get("description") || "", category: formData.get("category") || "general", is_public: formData.get("is_public") === "on", }; try { const response = await fetch("/api/dashboards", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data), }); if (response.ok) { const result = await response.json(); showNotification("Dashboard created", "success"); closeCreateDashboardModal(); loadDashboards(); // Open the new dashboard for editing if (result.id) { openDashboard(result.id); } } else { showNotification("Failed to create dashboard", "error"); } } catch (e) { console.error("Failed to create dashboard:", e); showNotification("Failed to create dashboard", "error"); } } // Handle form submission document.addEventListener("submit", (e) => { if (e.target.id === "createDashboardForm") { e.preventDefault(); createDashboard(new FormData(e.target)); } else if (e.target.id === "addDataSourceForm") { e.preventDefault(); addDataSource(new FormData(e.target)); } else if (e.target.id === "addWidgetForm") { e.preventDefault(); addWidget(new FormData(e.target)); } }); // ============================================================================= // DASHBOARD VIEWER // ============================================================================= async function openDashboard(dashboardId) { try { const response = await fetch(`/api/dashboards/${dashboardId}`); if (response.ok) { const dashboard = await response.json(); state.currentDashboard = dashboard; state.widgets = dashboard.widgets || []; showDashboardViewer(dashboard); } } catch (e) { console.error("Failed to open dashboard:", e); showNotification("Failed to load dashboard", "error"); } } function showDashboardViewer(dashboard) { const viewer = document.getElementById("dashboard-viewer"); const list = document.getElementById("dashboards-list"); if (viewer) viewer.classList.remove("hidden"); if (list) list.classList.add("hidden"); // Update title const titleEl = document.getElementById("viewer-dashboard-name"); if (titleEl) titleEl.textContent = dashboard.name; // Render widgets renderWidgets(dashboard.widgets || []); } function closeDashboardViewer() { const viewer = document.getElementById("dashboard-viewer"); const list = document.getElementById("dashboards-list"); if (viewer) viewer.classList.add("hidden"); if (list) list.classList.remove("hidden"); state.currentDashboard = null; state.isEditing = false; } function renderWidgets(widgets) { const container = document.getElementById("widgets-grid"); if (!container) return; if (widgets.length === 0) { container.innerHTML = `
πŸ“ˆ

No widgets yet

Add widgets to visualize your data

`; return; } container.innerHTML = widgets .map( (widget) => `

${escapeHtml(widget.title)}

${renderWidgetContent(widget)}
`, ) .join(""); } function renderWidgetContent(widget) { // Placeholder rendering - in production, this would render actual charts const icons = { line_chart: "πŸ“ˆ", bar_chart: "πŸ“Š", pie_chart: "πŸ₯§", area_chart: "πŸ“‰", scatter_plot: "⚬", kpi: "🎯", table: "πŸ“‹", gauge: "⏲️", map: "πŸ—ΊοΈ", text: "πŸ“", }; return `
${icons[widget.widget_type] || "πŸ“Š"} ${widget.widget_type}
`; } // ============================================================================= // DASHBOARD ACTIONS // ============================================================================= async function refreshDashboard() { if (!state.currentDashboard) return; showNotification("Refreshing dashboard...", "info"); await openDashboard(state.currentDashboard.id); showNotification("Dashboard refreshed", "success"); } function editDashboard() { if (!state.currentDashboard) return; state.isEditing = true; const viewer = document.getElementById("dashboard-viewer"); if (viewer) { viewer.classList.add("editing"); } showNotification("Edit mode enabled", "info"); } function editDashboardById(dashboardId) { openDashboard(dashboardId).then(() => { editDashboard(); }); } function shareDashboard() { if (!state.currentDashboard) return; const shareUrl = `${window.location.origin}/dashboards/${state.currentDashboard.id}`; navigator.clipboard.writeText(shareUrl).then(() => { showNotification("Share link copied to clipboard", "success"); }); } function exportDashboard() { if (!state.currentDashboard) return; // Export as JSON const data = JSON.stringify(state.currentDashboard, null, 2); const blob = new Blob([data], { type: "application/json" }); const url = URL.createObjectURL(blob); const link = document.createElement("a"); link.download = `${state.currentDashboard.name}.json`; link.href = url; link.click(); URL.revokeObjectURL(url); showNotification("Dashboard exported", "success"); } async function duplicateDashboard(dashboardId) { try { const response = await fetch(`/api/dashboards/${dashboardId}/duplicate`, { method: "POST", }); if (response.ok) { showNotification("Dashboard duplicated", "success"); loadDashboards(); } else { showNotification("Failed to duplicate dashboard", "error"); } } catch (e) { console.error("Failed to duplicate dashboard:", e); showNotification("Failed to duplicate dashboard", "error"); } } async function deleteDashboard(dashboardId) { if (!confirm("Delete this dashboard? This cannot be undone.")) return; try { const response = await fetch(`/api/dashboards/${dashboardId}`, { method: "DELETE", }); if (response.ok) { showNotification("Dashboard deleted", "success"); loadDashboards(); if (state.currentDashboard?.id === dashboardId) { closeDashboardViewer(); } } else { showNotification("Failed to delete dashboard", "error"); } } catch (e) { console.error("Failed to delete dashboard:", e); showNotification("Failed to delete dashboard", "error"); } } // ============================================================================= // DATA SOURCES // ============================================================================= async function loadDataSources() { try { const response = await fetch("/api/dashboards/data-sources"); if (response.ok) { const data = await response.json(); state.dataSources = data.data_sources || []; renderDataSourcesList(); } } catch (e) { console.error("Failed to load data sources:", e); } } function renderDataSourcesList() { const container = document.getElementById("data-sources-list"); if (!container) return; if (state.dataSources.length === 0) { container.innerHTML = `

No data sources configured

`; return; } container.innerHTML = state.dataSources .map( (source) => `
${getSourceIcon(source.source_type)}
${escapeHtml(source.name)} ${source.source_type}
`, ) .join(""); } function getSourceIcon(sourceType) { const icons = { postgresql: "🐘", mysql: "🐬", api: "πŸ”Œ", csv: "πŸ“„", json: "πŸ“‹", elasticsearch: "πŸ”", mongodb: "πŸƒ", }; return icons[sourceType] || "πŸ“Š"; } function showAddDataSourceModal() { const modal = document.getElementById("addDataSourceModal"); if (modal) { modal.style.display = "flex"; const form = modal.querySelector("form"); if (form) form.reset(); } } function closeAddDataSourceModal() { const modal = document.getElementById("addDataSourceModal"); if (modal) { modal.style.display = "none"; } } async function testDataSourceConnection() { const form = document.getElementById("addDataSourceForm"); if (!form) return; const formData = new FormData(form); showNotification("Testing connection...", "info"); try { const response = await fetch("/api/dashboards/data-sources/test", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ source_type: formData.get("source_type"), connection_string: formData.get("connection_string"), }), }); if (response.ok) { showNotification("Connection successful!", "success"); } else { showNotification("Connection failed", "error"); } } catch (e) { showNotification("Connection test failed", "error"); } } async function addDataSource(formData) { const data = { name: formData.get("name"), source_type: formData.get("source_type"), connection_string: formData.get("connection_string"), }; try { const response = await fetch("/api/dashboards/data-sources", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data), }); if (response.ok) { showNotification("Data source added", "success"); closeAddDataSourceModal(); loadDataSources(); } else { showNotification("Failed to add data source", "error"); } } catch (e) { console.error("Failed to add data source:", e); showNotification("Failed to add data source", "error"); } } async function removeDataSource(sourceId) { if (!confirm("Remove this data source?")) return; try { const response = await fetch(`/api/dashboards/data-sources/${sourceId}`, { method: "DELETE", }); if (response.ok) { showNotification("Data source removed", "success"); loadDataSources(); } } catch (e) { console.error("Failed to remove data source:", e); } } // ============================================================================= // WIDGETS // ============================================================================= function showAddWidgetModal() { const modal = document.getElementById("addWidgetModal"); if (modal) { modal.style.display = "flex"; state.selectedWidgetType = null; updateWidgetTypeSelection(); } } function closeAddWidgetModal() { const modal = document.getElementById("addWidgetModal"); if (modal) { modal.style.display = "none"; } } function selectWidgetType(widgetType) { state.selectedWidgetType = widgetType; updateWidgetTypeSelection(); } function updateWidgetTypeSelection() { document.querySelectorAll(".widget-option").forEach((btn) => { btn.classList.toggle( "selected", btn.dataset.type === state.selectedWidgetType, ); }); // Show/hide configuration section const configSection = document.getElementById("widget-config-section"); if (configSection) { configSection.style.display = state.selectedWidgetType ? "block" : "none"; } } async function addWidget(formData) { if (!state.currentDashboard || !state.selectedWidgetType) { showNotification("Please select a widget type", "error"); return; } const data = { dashboard_id: state.currentDashboard.id, widget_type: state.selectedWidgetType, title: formData.get("widget_title") || "Untitled Widget", data_source_id: formData.get("data_source_id"), config: { width: parseInt(formData.get("width")) || 1, height: parseInt(formData.get("height")) || 1, }, }; try { const response = await fetch( `/api/dashboards/${state.currentDashboard.id}/widgets`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data), }, ); if (response.ok) { showNotification("Widget added", "success"); closeAddWidgetModal(); openDashboard(state.currentDashboard.id); } else { showNotification("Failed to add widget", "error"); } } catch (e) { console.error("Failed to add widget:", e); showNotification("Failed to add widget", "error"); } } function editWidget(widgetId) { // TODO: Implement widget editing modal showNotification("Widget editing coming soon", "info"); } async function removeWidget(widgetId) { if (!confirm("Remove this widget?")) return; try { const response = await fetch(`/api/dashboards/widgets/${widgetId}`, { method: "DELETE", }); if (response.ok) { showNotification("Widget removed", "success"); if (state.currentDashboard) { openDashboard(state.currentDashboard.id); } } } catch (e) { console.error("Failed to remove widget:", e); } } // ============================================================================= // UTILITIES // ============================================================================= function escapeHtml(text) { if (!text) return ""; const div = document.createElement("div"); div.textContent = text; return div.innerHTML; } function formatRelativeTime(dateString) { if (!dateString) return "Never"; try { const date = new Date(dateString); const now = new Date(); const diffMs = now - date; const diffMin = Math.floor(diffMs / 60000); const diffHour = Math.floor(diffMin / 60); const diffDay = Math.floor(diffHour / 24); if (diffMin < 1) return "just now"; if (diffMin < 60) return `${diffMin}m ago`; if (diffHour < 24) return `${diffHour}h ago`; if (diffDay < 7) return `${diffDay}d ago`; return date.toLocaleDateString(); } catch { return dateString; } } function showNotification(message, type) { if (typeof window.showNotification === "function") { window.showNotification(message, type); } else if (typeof window.GBAlerts !== "undefined") { if (type === "success") window.GBAlerts.success("Dashboards", message); else if (type === "error") window.GBAlerts.error("Dashboards", message); else window.GBAlerts.info("Dashboards", message); } else { console.log(`[${type}] ${message}`); } } // ============================================================================= // EXPORT TO WINDOW // ============================================================================= // Create Dashboard Modal window.showCreateDashboardModal = showCreateDashboardModal; window.closeCreateDashboardModal = closeCreateDashboardModal; // Dashboard Viewer window.openDashboard = openDashboard; window.closeDashboardViewer = closeDashboardViewer; window.refreshDashboard = refreshDashboard; window.editDashboard = editDashboard; window.editDashboardById = editDashboardById; window.shareDashboard = shareDashboard; window.exportDashboard = exportDashboard; window.duplicateDashboard = duplicateDashboard; window.deleteDashboard = deleteDashboard; // Data Sources window.showAddDataSourceModal = showAddDataSourceModal; window.closeAddDataSourceModal = closeAddDataSourceModal; window.testDataSourceConnection = testDataSourceConnection; window.removeDataSource = removeDataSource; // Widgets window.showAddWidgetModal = showAddWidgetModal; window.closeAddWidgetModal = closeAddWidgetModal; window.selectWidgetType = selectWidgetType; window.editWidget = editWidget; window.removeWidget = removeWidget; // ============================================================================= // INITIALIZE // ============================================================================= if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", init); } else { init(); } })();