Legal Case Time Tracking Software

Legal Case Time Tracking Software

Live Time Tracker

00:00:00

No active timer

Today's Time Log

Case Description Duration Billable

No cases added yet.

`; return; } state.cases.forEach(c => { const caseEntries = state.timeEntries.filter(e => e.caseId === c.id); const totalSeconds = caseEntries.reduce((acc, entry) => acc + entry.duration, 0); const totalBillable = (totalSeconds / 3600) * c.rate; const item = document.createElement('div'); item.className = 'p-4 border border-gray-200 rounded-lg'; item.innerHTML = `

${c.name}

${c.client} - ${formatCurrency(c.rate)}/hr

Total Hours Logged: ${formatHours(totalSeconds)} Total Billable: ${formatCurrency(totalBillable)}
`; caseListContainer.appendChild(item); }); } // --- TIMER LOGIC --- function startTimer() { const caseId = parseInt(timerCaseSelect.value); if (!caseId) { alert('Please select a case before starting the timer.'); return; } if (state.activeTimer.intervalId) { alert('A timer is already running.'); return; } state.activeTimer.caseId = caseId; state.activeTimer.startTime = Date.now(); state.activeTimer.intervalId = setInterval(() => { const elapsed = Math.floor((Date.now() - state.activeTimer.startTime) / 1000); timerDisplay.textContent = formatDuration(elapsed); }, 1000); const caseInfo = state.cases.find(c => c.id === caseId); activeTimerCaseDisplay.textContent = `Tracking: ${caseInfo.name}`; startTimerBtn.disabled = true; stopTimerBtn.disabled = false; timerCaseSelect.disabled = true; } function stopTimer() { if (!state.activeTimer.intervalId) return; clearInterval(state.activeTimer.intervalId); const endTime = Date.now(); const duration = Math.floor((endTime - state.activeTimer.startTime) / 1000); const description = prompt(`Timer stopped at ${formatDuration(duration)}. Please enter a description for this time entry:`, ""); if (description !== null && description.trim() !== '') { const newEntry = { id: Date.now(), caseId: state.activeTimer.caseId, description: description.trim(), duration: duration, date: new Date().toISOString().split('T')[0] }; state.timeEntries.push(newEntry); renderAll(); } // Reset timer state state.activeTimer.intervalId = null; state.activeTimer.startTime = null; state.activeTimer.caseId = null; timerDisplay.textContent = '00:00:00'; activeTimerCaseDisplay.textContent = 'No active timer'; startTimerBtn.disabled = false; stopTimerBtn.disabled = true; timerCaseSelect.disabled = false; timerCaseSelect.value = ''; } // --- EVENT HANDLERS --- function switchTab(tabName) { state.currentTab = tabName; Object.values(tabs).forEach(tab => tab.classList.remove('active')); Object.values(contents).forEach(content => content.classList.add('hidden')); tabs[tabName].classList.add('active'); contents[tabName].classList.remove('hidden'); updateNavButtons(); } function updateNavButtons() { navButtons.prev.disabled = state.currentTab === 'dashboard'; navButtons.next.disabled = state.currentTab === 'management'; } caseForm.addEventListener('submit', (e) => { e.preventDefault(); const caseData = { id: state.editingCaseId ? state.editingCaseId : Date.now(), name: caseNameInput.value.trim(), client: clientNameInput.value.trim(), rate: parseFloat(hourlyRateInput.value) }; if (state.editingCaseId) { const index = state.cases.findIndex(c => c.id === state.editingCaseId); if (index !== -1) state.cases[index] = caseData; } else { state.cases.push(caseData); } resetCaseForm(); renderAll(); }); caseListContainer.addEventListener('click', (e) => { const target = e.target; const id = parseInt(target.dataset.id); if (target.classList.contains('edit-case-btn')) { const caseToEdit = state.cases.find(c => c.id === id); if (caseToEdit) { caseFormTitle.textContent = 'Edit Case'; state.editingCaseId = caseToEdit.id; caseNameInput.value = caseToEdit.name; clientNameInput.value = caseToEdit.client; hourlyRateInput.value = caseToEdit.rate; cancelCaseEditBtn.classList.remove('hidden'); caseForm.querySelector('button[type="submit"]').textContent = 'Update Case'; } } else if (target.classList.contains('delete-case-btn')) { if (confirm('Are you sure you want to delete this case? This will also remove all its time entries.')) { state.cases = state.cases.filter(c => c.id !== id); state.timeEntries = state.timeEntries.filter(e => e.caseId !== id); renderAll(); } } }); downloadPdfBtn.addEventListener('click', () => { const { jsPDF } = window.jspdf; const doc = new jsPDF(); doc.setFontSize(20); doc.text("Timesheet Report", 14, 22); doc.setFontSize(11); doc.setTextColor(100); doc.text(`Report Generated: ${new Date().toLocaleDateString()}`, 14, 30); const head = [['Date', 'Case Name', 'Description', 'Duration (Hrs)', 'Billable Amount']]; const body = state.timeEntries.map(entry => { const caseInfo = state.cases.find(c => c.id === entry.caseId); if (!caseInfo) return []; const billableAmount = (entry.duration / 3600) * caseInfo.rate; return [ entry.date, caseInfo.name, entry.description, formatHours(entry.duration), formatCurrency(billableAmount) ]; }).filter(row => row.length > 0); doc.autoTable({ startY: 35, head: head, body: body, theme: 'striped', headStyles: { fillColor: [5, 150, 105] }, // emerald-600 }); doc.save('timesheet_report.pdf'); }); function resetCaseForm() { caseForm.reset(); state.editingCaseId = null; caseFormTitle.textContent = 'Add New Case'; cancelCaseEditBtn.classList.add('hidden'); caseForm.querySelector('button[type="submit"]').textContent = 'Save Case'; } // --- ATTACH EVENT LISTENERS --- tabs.dashboard.addEventListener('click', () => switchTab('dashboard')); tabs.management.addEventListener('click', () => switchTab('management')); navButtons.next.addEventListener('click', () => switchTab('management')); navButtons.prev.addEventListener('click', () => switchTab('dashboard')); startTimerBtn.addEventListener('click', startTimer); stopTimerBtn.addEventListener('click', stopTimer); cancelCaseEditBtn.addEventListener('click', resetCaseForm); // --- INITIALIZATION --- function init() { loadInitialData(); renderAll(); updateNavButtons(); } init(); });
Scroll to Top