Attorney Workload & Case Allocation Manager

Attorney Workload & Case Allocation Manager

A dashboard to visualize workloads and manage case assignments.

Attorney Roster & Workload

Unassigned Cases

No attorneys configured. Please add attorneys in the Data Configuration tab.

`; return; } attorneys.forEach(attorney => { const assignedCases = cases.filter(c => c.attorneyId === attorney.id); const currentLoad = assignedCases.reduce((sum, c) => sum + c.complexity, 0); const loadPercentage = (currentLoad / attorney.capacity) * 100; let bgColor = 'bg-green-100'; let textColor = 'text-green-800'; let progressBg = 'bg-green-500'; if (loadPercentage > 75) { bgColor = 'bg-yellow-100'; textColor = 'text-yellow-800'; progressBg = 'bg-yellow-500'; } if (loadPercentage >= 100) { bgColor = 'bg-red-100'; textColor = 'text-red-800'; progressBg = 'bg-red-500'; } let casesHtml = assignedCases.map(c => `
  • ${c.name}

    ${c.type} - Value: ${formatCurrency(c.value)}

    Complexity: ${c.complexity}
  • `).join(''); if (assignedCases.length === 0) { casesHtml = '

    No cases assigned.

    '; } const attorneyCard = `

    ${attorney.name}

    ${attorney.specialty}

    Workload: ${currentLoad} / ${attorney.capacity}

    Assigned Cases:

      ${casesHtml}
    `; container.innerHTML += attorneyCard; }); }; /** * Renders the list of unassigned cases on the dashboard. */ const renderDashboardUnassignedCases = () => { const container = document.getElementById('unassigned-cases-container'); if (!container) return; // Null check const unassignedCases = cases.filter(c => c.attorneyId === null); container.innerHTML = ''; if (unassignedCases.length === 0) { container.innerHTML = '
    No unassigned cases.
    '; return; } unassignedCases.forEach(c => { const attorneyOptions = attorneys.map(a => ``).join(''); const caseCard = `

    ${c.name}

    ${c.type}

    Complexity: ${c.complexity} Value: ${formatCurrency(c.value)}
    `; container.innerHTML += caseCard; }); }; /** * Renders the editable table of attorneys in the configuration tab. */ const renderConfigAttorneys = () => { const container = document.getElementById('attorney-list-container'); if (!container) return; // Null check let tableHtml = `
    `; if (attorneys.length === 0) { tableHtml += ``; } else { attorneys.forEach(a => { tableHtml += ` `; }); } tableHtml += `
    Name Specialty Capacity Actions
    No attorneys added.
    ${a.name} ${a.specialty} ${a.capacity}
    `; container.innerHTML = tableHtml; }; /** * Renders the editable table of unassigned cases in the configuration tab. */ const renderConfigCases = () => { const container = document.getElementById('case-list-container'); if (!container) return; // Null check const unassignedCases = cases.filter(c => c.attorneyId === null); let tableHtml = `
    `; if (unassignedCases.length === 0) { tableHtml += ``; } else { unassignedCases.forEach(c => { tableHtml += ` `; }); } tableHtml += `
    Case Name Type Complexity Actions
    No unassigned cases.
    ${c.name} ${c.type} ${c.complexity}
    `; container.innerHTML = tableHtml; }; /** * Master render function to update the entire UI. */ const renderAll = () => { renderDashboardAttorneys(); renderDashboardUnassignedCases(); renderConfigAttorneys(); renderConfigCases(); }; // --- EVENT HANDLERS & FORM LOGIC --- function handleAttorneySave(e) { e.preventDefault(); const id = document.getElementById('attorney-id').value; const name = document.getElementById('attorney-name').value; const specialty = document.getElementById('attorney-specialty').value; const capacity = parseInt(document.getElementById('attorney-capacity').value); if (id) { // Update existing const index = attorneys.findIndex(a => a.id == id); if (index !== -1) { attorneys[index] = { ...attorneys[index], name, specialty, capacity }; } } else { // Add new attorneys.push({ id: getNextId(attorneys), name, specialty, capacity }); } clearAttorneyForm(); renderAll(); } window.clearAttorneyForm = () => { attorneyForm.reset(); document.getElementById('attorney-id').value = ''; }; window.editAttorney = (id) => { const attorney = attorneys.find(a => a.id === id); if (attorney) { document.getElementById('attorney-id').value = attorney.id; document.getElementById('attorney-name').value = attorney.name; document.getElementById('attorney-specialty').value = attorney.specialty; document.getElementById('attorney-capacity').value = attorney.capacity; document.getElementById('attorney-name').focus(); } }; window.deleteAttorney = (id) => { if (cases.some(c => c.attorneyId === id)) { // Simple validation; a more robust app would use a custom modal console.warn("Cannot delete attorney with assigned cases. Please reassign cases first."); // In a real app, you would show a user-friendly message here instead of an alert. // For now, we will prevent deletion and log to console. return; } attorneys = attorneys.filter(a => a.id !== id); renderAll(); }; function handleCaseSave(e) { e.preventDefault(); const id = document.getElementById('case-id').value; const name = document.getElementById('case-name').value; const type = document.getElementById('case-type').value; const complexity = parseInt(document.getElementById('case-complexity').value); const value = parseFloat(document.getElementById('case-value').value); if (id) { // Update const index = cases.findIndex(c => c.id == id); if (index !== -1) { cases[index] = { ...cases[index], name, type, complexity, value }; } } else { // Add new cases.push({ id: getNextId(cases), name, type, complexity, value, attorneyId: null }); } clearCaseForm(); renderAll(); } window.clearCaseForm = () => { caseForm.reset(); document.getElementById('case-id').value = ''; }; window.editCase = (id) => { const caseItem = cases.find(c => c.id === id); if (caseItem) { document.getElementById('case-id').value = caseItem.id; document.getElementById('case-name').value = caseItem.name; document.getElementById('case-type').value = caseItem.type; document.getElementById('case-complexity').value = caseItem.complexity; document.getElementById('case-value').value = caseItem.value; document.getElementById('case-name').focus(); } }; window.deleteCase = (id) => { cases = cases.filter(c => c.id !== id); renderAll(); }; window.assignCase = (caseId) => { const selectElement = document.getElementById(`assign-attorney-${caseId}`); if (!selectElement) return; const attorneyId = parseInt(selectElement.value); if (isNaN(attorneyId) || attorneyId === "") return; const caseIndex = cases.findIndex(c => c.id === caseId); if (caseIndex !== -1) { cases[caseIndex].attorneyId = attorneyId; renderAll(); } }; // --- TAB NAVIGATION --- window.changeTab = (tabIndex) => { currentTab = tabIndex; const tabs = document.querySelectorAll('.tab'); const tabContents = document.querySelectorAll('.tab-content'); tabs.forEach((tab, index) => { if (index === tabIndex) { tab.classList.remove('tab-inactive'); tab.classList.add('tab-active'); } else { tab.classList.remove('tab-active'); tab.classList.add('tab-inactive'); } }); tabContents.forEach((content, index) => { content.style.display = index === tabIndex ? 'block' : 'none'; }); }; window.navigateTab = (direction) => { const numTabs = document.querySelectorAll('.tab').length; let newTab = currentTab + direction; if (newTab < 0) newTab = numTabs - 1; if (newTab >= numTabs) newTab = 0; changeTab(newTab); }; // --- PDF DOWNLOAD FUNCTIONALITY --- window.downloadPDF = () => { const { jsPDF } = window.jspdf; const doc = new jsPDF({ orientation: 'p', unit: 'mm', format: 'a4' }); doc.setFontSize(18); doc.text('Attorney Workload & Case Allocation Summary', 14, 22); doc.setFontSize(11); doc.setTextColor(100); doc.text(`Generated on: ${new Date().toLocaleDateString('en-US')}`, 14, 30); const head = [['Attorney', 'Specialty', 'Assigned Cases', 'Workload (Points)', 'Capacity']]; const body = attorneys.map(attorney => { const assignedCases = cases.filter(c => c.attorneyId === attorney.id); const currentLoad = assignedCases.reduce((sum, c) => sum + c.complexity, 0); const caseList = assignedCases.map(c => `- ${c.name} (C: ${c.complexity})`).join('\n') || 'None'; return [ attorney.name, attorney.specialty, caseList, `${currentLoad}`, `${attorney.capacity}` ]; }); doc.autoTable({ startY: 40, head: head, body: body, theme: 'striped', headStyles: { fillColor: [22, 160, 133] }, styles: { cellPadding: 2, fontSize: 9 }, didDrawCell: (data) => { // For multiline text in 'Assigned Cases' column if (data.column.index === 2 && data.cell.section === 'body') { doc.autoTableText(data.cell.text, data.cell.x + 2, data.cell.y + 2, { halign: 'left', valign: 'top' }); } } }); const unassignedCases = cases.filter(c => c.attorneyId === null); if (unassignedCases.length > 0) { const unassignedHead = [['Case Name / Client', 'Type', 'Complexity', 'Value']]; const unassignedBody = unassignedCases.map(c => [c.name, c.type, c.complexity, formatCurrency(c.value)]); doc.autoTable({ startY: doc.autoTable.previous.finalY + 15, head: [['Unassigned Cases']], theme: 'plain', styles: { fontSize: 14, fontStyle: 'bold' } }); doc.autoTable({ startY: doc.autoTable.previous.finalY + 2, head: unassignedHead, body: unassignedBody, theme: 'grid', headStyles: { fillColor: [44, 62, 80] }, }); } doc.save('Attorney_Workload_Summary.pdf'); }; // --- INITIAL RENDER --- renderAll(); });
    Scroll to Top