Shared Goals & Achievement Tracker

Shared Goals & Achievement Tracker

Define, assign, and track your team's objectives in real-time.

Goals Overview

Loading goals...

No team members added yet.

'; goalMembersChecklist.innerHTML = '

Add members first.

'; return; } teamMembers.forEach(member => { // Update config list const memberDiv = document.createElement('div'); memberDiv.className = 'flex justify-between items-center p-2 bg-slate-100 rounded'; memberDiv.innerHTML = `${escapeHTML(member.name)}`; memberListDiv.appendChild(memberDiv); // Update checklist in goal form const checklistItem = document.createElement('div'); checklistItem.className = 'flex items-center'; checklistItem.innerHTML = ` `; goalMembersChecklist.appendChild(checklistItem); }); } function updateGoalsDashboardUI() { goalsDashboard.innerHTML = ''; if (goals.length === 0) { loadingGoals.classList.remove('hidden'); loadingGoals.textContent = "No goals have been set yet. Go to Data Configuration to add one."; return; } loadingGoals.classList.add('hidden'); goals.forEach(goal => { const assignedMembers = teamMembers.filter(m => goal.assignedTo.includes(m.id)); const memberNames = assignedMembers.map(m => escapeHTML(m.name)).join(', ') || 'Unassigned'; const progressColor = goal.progress < 30 ? 'bg-red-500' : goal.progress < 70 ? 'bg-yellow-500' : 'bg-green-500'; const goalCard = document.createElement('div'); goalCard.className = 'bg-white border border-slate-200 rounded-lg p-4 flex flex-col'; goalCard.innerHTML = `

${escapeHTML(goal.title)}

${escapeHTML(goal.description)}

Assigned to: ${memberNames}
${goal.progress}% Complete
`; goalsDashboard.appendChild(goalCard); }); } function updateGoalsConfigUI() { goalListConfig.innerHTML = ''; if (goals.length === 0) { goalListConfig.innerHTML = '

No goals to configure.

'; return; } goals.forEach(goal => { const div = document.createElement('div'); div.className = 'flex justify-between items-center p-2 bg-slate-100 rounded'; div.innerHTML = ` ${escapeHTML(goal.title)}
`; goalListConfig.appendChild(div); }); } // --- Data Handling --- async function handleAddMember(e) { e.preventDefault(); const name = memberNameInput.value.trim(); if (name) { await addDoc(collection(db, `artifacts/${appId}/public/data/team_members`), { name }); memberNameInput.value = ''; } } async function handleRemoveMember(memberId) { await deleteDoc(doc(db, `artifacts/${appId}/public/data/team_members`, memberId)); } async function handleAddGoal(e) { e.preventDefault(); const title = goalTitleInput.value.trim(); const description = goalDescriptionInput.value.trim(); const progress = parseInt(goalProgressInput.value, 10); const assignedTo = Array.from(goalMembersChecklist.querySelectorAll('input:checked')).map(el => el.value); if (title && description) { const goalData = { title, description, progress, assignedTo }; await addDoc(collection(db, `artifacts/${appId}/public/data/goals`), goalData); addGoalForm.reset(); goalProgressValue.textContent = '0'; } } async function handleRemoveGoal(goalId) { await deleteDoc(doc(db, `artifacts/${appId}/public/data/goals`, goalId)); } // --- PDF Generation --- async function generatePdf() { const { jsPDF } = window.jspdf; if (goals.length === 0) { alert("No goals to export."); return; } downloadPdfBtn.textContent = 'Generating PDF...'; downloadPdfBtn.disabled = true; try { const pdf = new jsPDF({ orientation: 'p', unit: 'pt', format: 'a4' }); // Header pdf.setFontSize(22); pdf.setFont("helvetica", "bold"); pdf.setTextColor(40, 52, 71); pdf.text("Shared Goals & Achievement Report", pdf.internal.pageSize.getWidth() / 2, 40, { align: 'center' }); // Table Data const head = [['Goal Title', 'Description', 'Progress (%)', 'Assigned To']]; const body = goals.map(goal => { const assignedNames = teamMembers.filter(m => goal.assignedTo.includes(m.id)).map(m => m.name).join(', '); return [goal.title, goal.description, goal.progress, assignedNames]; }); pdf.autoTable({ head: head, body: body, startY: 60, theme: 'grid', headStyles: { fillColor: [59, 130, 246] }, // Blue header styles: { font: 'Inter', cellPadding: 8 }, }); // Footer const pageCount = pdf.internal.getNumberOfPages(); for (let i = 1; i <= pageCount; i++) { pdf.setPage(i); pdf.setFontSize(8); pdf.setTextColor(150); pdf.text(`Page ${i} of ${pageCount}`, pdf.internal.pageSize.getWidth() / 2, pdf.internal.pageSize.getHeight() - 20, { align: 'center' }); pdf.text(`Generated on: ${new Date().toLocaleString()}`, 40, pdf.internal.pageSize.getHeight() - 20); } pdf.save('Shared_Goals_Report.pdf'); } catch (error) { console.error("Error generating PDF:", error); alert("Could not generate PDF. Please try again."); } finally { downloadPdfBtn.textContent = 'Download Report as PDF'; downloadPdfBtn.disabled = false; } } function escapeHTML(str) { return str.replace(/[&<>"']/g, m => ({'&':'&','<':'<','>':'>','"':'"',"'":'''})[m]); } // --- Initial Setup --- document.addEventListener('DOMContentLoaded', () => { onAuthStateChanged(auth, (user) => { if (user) { setupAppForUser(user); } else { (async () => { try { if (initialAuthToken) { await signInWithCustomToken(auth, initialAuthToken); } else { await signInAnonymously(auth); } } catch (error) { console.error("Authentication failed:", error); setupAppForUser(null); } })(); } }); // Event Listeners prevBtn.addEventListener('click', () => { if (currentTab > 0) changeTab(currentTab - 1); }); nextBtn.addEventListener('click', () => { if (currentTab < tabs.length - 1) changeTab(currentTab + 1); }); downloadPdfBtn.addEventListener('click', generatePdf); addMemberForm.addEventListener('submit', handleAddMember); addGoalForm.addEventListener('submit', handleAddGoal); goalProgressInput.addEventListener('input', (e) => { goalProgressValue.textContent = e.target.value; }); // Event delegation for remove buttons memberListDiv.addEventListener('click', (e) => { if (e.target.classList.contains('remove-member-btn')) { handleRemoveMember(e.target.dataset.id); } }); goalListConfig.addEventListener('click', (e) => { if (e.target.classList.contains('remove-goal-btn')) { handleRemoveGoal(e.target.dataset.id); } // Note: Edit functionality would require a modal or form pre-filling, // which adds complexity beyond the initial scope. }); // Initial state changeTab(0); });
Scroll to Top