Rebuttal Letter Point Organizer
Rebuttal Strategy Matrix (Editable)
ID
Opposing Argument/Claim
Your Rebuttal Point
Evidence Source
Status
Go to the "Add Points" tab to input the arguments you need to counter.
Add Argument to Counter
Current Points Added
No points added yet.
Go to the "Add Points" tab to input the arguments you need to counter.
`; return; } rebuttalPoints.forEach(point => { dashboardList.appendChild(createDashboardItem(point)); }); // Setup listeners for the new elements setupDashboardListeners(); } /** * Creates an editable item row for the dashboard */ function createDashboardItem(point) { const itemEl = document.createElement("div"); itemEl.className = "rpo-dash-item"; itemEl.dataset.id = point.id; // Data-label attributes help with responsive view on smaller screens itemEl.innerHTML = ` `; return itemEl; } /** * Attaches listeners to the dashboard elements */ function setupDashboardListeners() { if (!dashboardList) return; // Listen for all input/change events to update state dashboardList.querySelectorAll('input, select, textarea').forEach(element => { element.addEventListener('change', handleDashboardUpdate); element.addEventListener('input', handleDashboardUpdate); }); // Listen for removal clicks (delegation) dashboardList.querySelectorAll('button[data-action="remove-dash"]').forEach(button => { button.addEventListener('click', handleDashboardRemove); }); } /** * Handles updates made directly to items (inputs, selects, textareas) */ function handleDashboardUpdate(e) { const itemEl = e.target.closest(".rpo-dash-item"); if (!itemEl) return; const itemId = parseInt(itemEl.dataset.id); const itemIndex = rebuttalPoints.findIndex(i => i.id === itemId); if (itemIndex === -1) return; const field = e.target.dataset.field; if (!field) return; // Update the master state array rebuttalPoints[itemIndex][field] = e.target.value; } /** * Handles removing an item directly from the dashboard */ function handleDashboardRemove(e) { const itemEl = e.target.closest(".rpo-dash-item"); if (itemEl) { const idToRemove = parseInt(itemEl.dataset.id); rebuttalPoints = rebuttalPoints.filter(point => point.id !== idToRemove); renderDashboard(); // Re-render to clear item and ensure clean state updateConfigListDisplay(); // Update config list too } } /** * Generates a PDF report from the dashboard data */ function downloadPDF() { if (typeof window.jspdf === 'undefined' || typeof window.jspdf.jsPDF.autoTable === 'undefined') { alert("Error: PDF libraries could not be loaded. Please try again."); return; } if (rebuttalPoints.length === 0) { alert("The rebuttal matrix is empty. Please add points first."); return; } const { jsPDF } = window.jspdf; const doc = new jsPDF("l", "pt", "a4"); // Landscape orientation for wide table const margin = 40; let yPos = margin; const tableHead = [["ID", "Opposing Argument", "Your Rebuttal Point", "Evidence/Source", "Status"]]; const tableBody = []; // Get data *from the dashboard's current state* rebuttalPoints.forEach((point) => { tableBody.push([ point.id, point.argument, point.rebuttal, point.evidence, point.status ]); }); // Document Title doc.setFontSize(18); doc.text("Rebuttal Strategy Matrix", margin, yPos); yPos += 30; doc.autoTable({ startY: yPos, head: tableHead, body: tableBody, theme: 'striped', headStyles: { fillColor: [0, 115, 230], // Blue textColor: [255, 255, 255], }, columnStyles: { 0: { cellWidth: 30 }, // ID 1: { cellWidth: 180 }, // Opposing Argument 2: { cellWidth: 180 }, // Your Rebuttal 3: { cellWidth: 100 }, // Evidence 4: { cellWidth: 70 }, // Status }, styles: { fontSize: 8, cellPadding: 3, overflow: 'linebreak' }, margin: { left: margin, right: margin } }); doc.save("Rebuttal_Strategy_Matrix.pdf"); } /** * Helper to escape HTML */ function escapeHTML(str) { if (!str) return ""; return str .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } // --- 4. INITIALIZATION & EVENT LISTENERS --- // Tab Listeners tabButtons.forEach((btn) => { btn.addEventListener("click", () => showTab(btn.dataset.target)); }); navButtons.forEach((btn) => { btn.addEventListener("click", () => showTab(btn.dataset.target)); }); // Config Tab Listeners if (addPointForm) { addPointForm.addEventListener("submit", handleAddPoint); } if (configList) { configList.addEventListener("click", handleConfigListClick); } // Dashboard Tab Listeners if (pdfBtn) { pdfBtn.addEventListener("click", downloadPDF); } // Load sample data rebuttalPoints = [ { id: pointIdCounter++, argument: 'The financial forecast is overly optimistic, relying on a 15% annual growth rate that is unsubstantiated.', rebuttal: 'The 15% growth rate is anchored in the Q3 market data for Sector X and corroborated by the independent analysis found in Exhibit C.', evidence: 'Exhibit C, Jones/Smith Market Analysis 2024; Q3 Earnings Report, p. 5', status: 'Ready to Write' }, { id: pointIdCounter++, argument: 'The team lacks the technical expertise to integrate the legacy database system.', rebuttal: 'We have retained a former employee, Dr. Lee, who personally built the legacy system to serve as a paid consultant for the integration phase.', evidence: 'Consulting Contract: Lee, A. and Acme Corp, signed 2025-10-01.', status: 'Finalized' } ]; updateConfigListDisplay(); // Initial State showTab("rpo-tab-dashboard"); });