/* ============================================================================= GOALS/OKR MODULE - Objectives & Key Results ============================================================================= */ (function () { "use strict"; // ============================================================================= // STATE // ============================================================================= const state = { currentView: "dashboard", objectives: [], selectedObjective: null, filters: { status: "all", owner: "all", period: "current", }, }; // ============================================================================= // INITIALIZATION // ============================================================================= function init() { loadObjectives(); bindEvents(); console.log("Goals module initialized"); } function bindEvents() { // View toggle buttons document.querySelectorAll(".view-btn").forEach((btn) => { btn.addEventListener("click", function () { const view = this.dataset.view; if (view) { switchGoalsView(view); } }); }); // Objective cards document.addEventListener("click", (e) => { const card = e.target.closest(".objective-card"); if (card) { const objectiveId = card.dataset.id; if (objectiveId) { selectObjective(objectiveId); } } }); } // ============================================================================= // VIEW SWITCHING // ============================================================================= function switchGoalsView(view) { state.currentView = view; // Update button states document.querySelectorAll(".view-btn").forEach((btn) => { btn.classList.toggle("active", btn.dataset.view === view); }); // Update view panels document.querySelectorAll(".goals-view").forEach((panel) => { panel.classList.toggle("active", panel.id === `${view}-view`); }); // Load view-specific data if using HTMX if (typeof htmx !== "undefined") { const viewContainer = document.getElementById("goals-content"); if (viewContainer) { htmx.ajax("GET", `/api/ui/goals/${view}`, { target: viewContainer }); } } } // ============================================================================= // DETAILS PANEL // ============================================================================= function toggleGoalsPanel() { const panel = document.getElementById("details-panel"); if (panel) { panel.classList.toggle("collapsed"); } } function openGoalsPanel() { const panel = document.getElementById("details-panel"); if (panel) { panel.classList.remove("collapsed"); } } function closeGoalsPanel() { const panel = document.getElementById("details-panel"); if (panel) { panel.classList.add("collapsed"); } } // ============================================================================= // OBJECTIVES // ============================================================================= async function loadObjectives() { try { const response = await fetch("/api/goals/objectives"); if (response.ok) { const data = await response.json(); state.objectives = data.objectives || []; renderObjectives(); } } catch (e) { console.error("Failed to load objectives:", e); } } function renderObjectives() { const container = document.getElementById("objectives-list"); if (!container) return; if (state.objectives.length === 0) { container.innerHTML = `
🎯

No objectives yet

Create your first objective to start tracking goals

`; return; } container.innerHTML = state.objectives .map( (obj) => `

${escapeHtml(obj.title)}

${obj.status}
${obj.progress || 0}%
${escapeHtml(obj.owner_name || "Unassigned")} ${formatDate(obj.end_date)}
`, ) .join(""); } function selectObjective(objectiveId) { const objective = state.objectives.find((o) => o.id === objectiveId); if (!objective) return; state.selectedObjective = objective; renderObjectives(); renderObjectiveDetails(objective); openGoalsPanel(); } function renderObjectiveDetails(objective) { const container = document.getElementById("objective-details"); if (!container) return; container.innerHTML = `

${escapeHtml(objective.title)}

${objective.status}
${objective.progress || 0}%

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

${escapeHtml(objective.owner_name || "Unassigned")}

${formatDate(objective.start_date)} - ${formatDate(objective.end_date)}

${renderKeyResults(objective.key_results || [])}
`; } function renderKeyResults(keyResults) { if (keyResults.length === 0) { return '

No key results defined

'; } return keyResults .map( (kr) => `
${escapeHtml(kr.title)} ${kr.current_value || 0} / ${kr.target_value || 100}
`, ) .join(""); } // ============================================================================= // CRUD OPERATIONS // ============================================================================= function showCreateObjectiveModal() { const modal = document.getElementById("create-objective-modal"); if (modal) { if (modal.showModal) { modal.showModal(); } else { modal.classList.add("open"); } } else { // Fallback: create a simple prompt-based flow const title = prompt("Enter objective title:"); if (title) { createObjective({ title }); } } } function closeCreateObjectiveModal() { const modal = document.getElementById("create-objective-modal"); if (modal) { if (modal.close) { modal.close(); } else { modal.classList.remove("open"); } } } async function createObjective(data) { try { const response = await fetch("/api/goals/objectives", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data), }); if (response.ok) { showNotification("Objective created", "success"); loadObjectives(); closeCreateObjectiveModal(); } else { showNotification("Failed to create objective", "error"); } } catch (e) { console.error("Failed to create objective:", e); showNotification("Failed to create objective", "error"); } } function editObjective(objectiveId) { const objective = state.objectives.find((o) => o.id === objectiveId); if (!objective) return; const newTitle = prompt("Edit objective title:", objective.title); if (newTitle && newTitle !== objective.title) { updateObjective(objectiveId, { title: newTitle }); } } async function updateObjective(objectiveId, data) { try { const response = await fetch(`/api/goals/objectives/${objectiveId}`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data), }); if (response.ok) { showNotification("Objective updated", "success"); loadObjectives(); } else { showNotification("Failed to update objective", "error"); } } catch (e) { console.error("Failed to update objective:", e); showNotification("Failed to update objective", "error"); } } async function deleteObjective(objectiveId) { if (!confirm("Delete this objective? This cannot be undone.")) return; try { const response = await fetch(`/api/goals/objectives/${objectiveId}`, { method: "DELETE", }); if (response.ok) { showNotification("Objective deleted", "success"); state.selectedObjective = null; closeGoalsPanel(); loadObjectives(); } else { showNotification("Failed to delete objective", "error"); } } catch (e) { console.error("Failed to delete objective:", e); showNotification("Failed to delete objective", "error"); } } function addKeyResult(objectiveId) { const title = prompt("Enter key result title:"); if (!title) return; const targetValue = prompt("Enter target value:", "100"); if (!targetValue) return; createKeyResult(objectiveId, { title, target_value: parseFloat(targetValue) || 100, current_value: 0, }); } async function createKeyResult(objectiveId, data) { try { const response = await fetch( `/api/goals/objectives/${objectiveId}/key-results`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data), }, ); if (response.ok) { showNotification("Key result added", "success"); loadObjectives(); } else { showNotification("Failed to add key result", "error"); } } catch (e) { console.error("Failed to create key result:", e); showNotification("Failed to add key result", "error"); } } // ============================================================================= // UTILITIES // ============================================================================= function escapeHtml(text) { if (!text) return ""; const div = document.createElement("div"); div.textContent = text; return div.innerHTML; } function formatDate(dateString) { if (!dateString) return "Not set"; try { const date = new Date(dateString); 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("Goals", message); else if (type === "error") window.GBAlerts.error("Goals", message); else window.GBAlerts.info("Goals", message); } else { console.log(`[${type}] ${message}`); } } // ============================================================================= // EXPORT TO WINDOW // ============================================================================= window.switchGoalsView = switchGoalsView; window.toggleGoalsPanel = toggleGoalsPanel; window.openGoalsPanel = openGoalsPanel; window.closeGoalsPanel = closeGoalsPanel; window.selectObjective = selectObjective; window.showCreateObjectiveModal = showCreateObjectiveModal; window.closeCreateObjectiveModal = closeCreateObjectiveModal; window.createObjective = createObjective; window.editObjective = editObjective; window.updateObjective = updateObjective; window.deleteObjective = deleteObjective; window.addKeyResult = addKeyResult; // ============================================================================= // INITIALIZE // ============================================================================= if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", init); } else { init(); } })();