${team.name}
`).join('') || 'No teams created yet.
'; // Members memberTeamSelect.innerHTML = state.teams.map(t => ``).join('') || ''; membersList.innerHTML = state.members.map(member => { const team = state.teams.find(t => t.id === member.teamId); return `
${member.name}
(${team ? team.name : 'No Team'})
No members added yet.
'; } // SECTION: Event Handlers function handleTabClick(tabName) { state.activeTab = tabName; render(); } function handleNavClick(direction) { const currentIndex = TABS.indexOf(state.activeTab); const newIndex = currentIndex + direction; if (newIndex >= 0 && newIndex < TABS.length) { state.activeTab = TABS[newIndex]; render(); } } function handleAddTeam(e) { e.preventDefault(); const name = teamNameInput.value.trim(); if (name) { const newTeam = { id: Date.now(), name }; state.teams.push(newTeam); if (state.teams.length === 1) state.selectedTeamId = newTeam.id; teamNameInput.value = ''; saveState(); render(); } } function handleDeleteTeam(teamId) { state.teams = state.teams.filter(t => t.id !== teamId); state.members = state.members.filter(m => m.teamId !== teamId); // Also remove members of that team state.updates = state.updates.filter(u => !state.members.find(m => m.id === u.memberId && m.teamId === teamId)); if (state.selectedTeamId === teamId) { state.selectedTeamId = state.teams.length > 0 ? state.teams[0].id : null; } saveState(); render(); } function handleAddMember(e) { e.preventDefault(); const name = memberNameInput.value.trim(); const teamId = parseInt(memberTeamSelect.value); if (name && teamId) { state.members.push({ id: Date.now(), name, teamId }); memberNameInput.value = ''; saveState(); render(); } } function handleDeleteMember(memberId) { state.members = state.members.filter(m => m.id !== memberId); state.updates = state.updates.filter(u => u.memberId !== memberId); saveState(); render(); } function handleSubmitUpdate(e) { e.preventDefault(); const memberId = parseInt(memberSelector.value); const date = new Date().toISOString().slice(0, 10); const updateData = { memberId, date, yesterday: updateYesterday.value.trim(), today: updateToday.value.trim(), blockers: updateBlockers.value.trim() }; // Remove existing update for this member on this day, if any state.updates = state.updates.filter(u => !(u.memberId === memberId && u.date === date)); state.updates.push(updateData); saveState(); submitUpdateForm.reset(); alert('Update submitted successfully!'); // Switch to dashboard to show the new update const member = state.members.find(m => m.id === memberId); state.selectedTeamId = member.teamId; state.selectedDate = date; state.activeTab = 'dashboard'; render(); } function generatePDF() { const { jsPDF } = window.jspdf; const doc = new jsPDF({ orientation: 'p', unit: 'mm', format: 'a4' }); if (!state.selectedTeamId) { alert("Please select a team to generate a report."); return; } // --- Data Gathering --- const selectedTeam = state.teams.find(t => t.id === state.selectedTeamId); const membersOfSelectedTeam = state.members.filter(m => m.teamId === state.selectedTeamId); const updatesForReport = membersOfSelectedTeam.map(member => { return state.updates.find(u => u.memberId === member.id && u.date === state.selectedDate) || { memberId: member.id, date: state.selectedDate, yesterday: 'No update submitted.', today: '', blockers: '' }; }); const date = new Date(state.selectedDate).toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' }); // --- PDF Header --- doc.setFontSize(22); doc.setFont('helvetica', 'bold'); doc.text('Daily Standup Report', 105, 20, { align: 'center' }); doc.setFontSize(16); doc.setFont('helvetica', 'normal'); doc.text(`Team: ${selectedTeam.name}`, 105, 30, { align: 'center' }); doc.setFontSize(10); doc.setTextColor(150); doc.text(`Date: ${date}`, 105, 35, { align: 'center' }); // --- Updates Table --- const tableBody = []; updatesForReport.forEach(update => { const member = state.members.find(m => m.id === update.memberId); tableBody.push([ { content: member.name, styles: { fontStyle: 'bold', fillColor: '#f1f5f9' } }, { content: `Yesterday: ${update.yesterday}\nToday: ${update.today}\nBlockers: ${update.blockers}`, styles: {}} ]); }); doc.autoTable({ body: tableBody, startY: 45, theme: 'grid', columnStyles: { 0: { cellWidth: 40 }, 1: { cellWidth: 'auto' } }, styles: { cellPadding: 3, fontSize: 10, valign: 'middle' }, didDrawPage: function (data) { // Footer const pageCount = doc.internal.getNumberOfPages(); doc.setFontSize(8); doc.setTextColor(150); doc.text('Page ' + data.pageNumber + ' of ' + pageCount, data.settings.margin.left, doc.internal.pageSize.height - 10); } }); // --- Save PDF --- const teamName = selectedTeam.name.replace(/\s+/g, '_'); doc.save(`${teamName}_Standup_${state.selectedDate}.pdf`); } // SECTION: Event Listeners Object.keys(tabButtons).forEach(tabId => { if (tabButtons[tabId]) tabButtons[tabId].addEventListener('click', () => handleTabClick(tabId)); }); if (prevTabBtn) prevTabBtn.addEventListener('click', () => handleNavClick(-1)); if (nextTabBtn) nextTabBtn.addEventListener('click', () => handleNavClick(1)); if (teamSelector) teamSelector.addEventListener('change', (e) => { state.selectedTeamId = parseInt(e.target.value); renderDashboard(); }); if (dateSelector) dateSelector.addEventListener('change', (e) => { state.selectedDate = e.target.value; renderDashboard(); }); if (downloadPdfBtn) downloadPdfBtn.addEventListener('click', generatePDF); if (submitUpdateForm) submitUpdateForm.addEventListener('submit', handleSubmitUpdate); if (addTeamForm) addTeamForm.addEventListener('submit', handleAddTeam); if (addMemberForm) addMemberForm.addEventListener('submit', handleAddMember); // Event delegation for delete buttons if (teamsList) teamsList.addEventListener('click', (e) => { if (e.target && e.target.classList.contains('delete-team-btn')) { if (confirm('Delete this team and all its members?')) handleDeleteTeam(parseInt(e.target.dataset.id)); } }); if (membersList) membersList.addEventListener('click', (e) => { if (e.target && e.target.classList.contains('delete-member-btn')) { if (confirm('Delete this team member?')) handleDeleteMember(parseInt(e.target.dataset.id)); } }); // SECTION: Initial Application Load loadState(); render(); });