Spaces:
Running
Running
| document.addEventListener('DOMContentLoaded', function() { | |
| initCharts(); | |
| // Initialize charts | |
| function initCharts() { | |
| // Revenue Line Chart | |
| const revenueCtx = document.getElementById('revenueChart'); | |
| if (revenueCtx) { | |
| new Chart(revenueCtx, { | |
| type: 'line', | |
| data: { | |
| labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct'], | |
| datasets: [{ | |
| label: 'Revenue', | |
| data: [12000, 19000, 15000, 18000, 21000, 25000, 22000, 28000, 26000, 30000], | |
| borderColor: '#6366f1', | |
| backgroundColor: 'rgba(99, 102, 241, 0.1)', | |
| borderWidth: 2, | |
| tension: 0.4, | |
| fill: true | |
| }] | |
| }, | |
| options: { | |
| responsive: true, | |
| maintainAspectRatio: false, | |
| plugins: { | |
| legend: { | |
| display: false | |
| } | |
| }, | |
| scales: { | |
| y: { | |
| beginAtZero: true, | |
| grid: { | |
| display: true, | |
| color: 'rgba(0, 0, 0, 0.05)' | |
| } | |
| }, | |
| x: { | |
| grid: { | |
| display: false | |
| } | |
| } | |
| } | |
| } | |
| }); | |
| } | |
| // Expense Doughnut Chart | |
| const expenseCtx = document.getElementById('expenseChart'); | |
| if (expenseCtx) { | |
| new Chart(expenseCtx, { | |
| type: 'doughnut', | |
| data: { | |
| labels: ['Supplies', 'Software', 'Marketing', 'Salaries', 'Office'], | |
| datasets: [{ | |
| data: [12000, 8000, 5000, 30000, 7000], | |
| backgroundColor: [ | |
| '#6366f1', | |
| '#8b5cf6', | |
| '#ec4899', | |
| '#f97316', | |
| '#10b981' | |
| ], | |
| borderWidth: 0 | |
| }] | |
| }, | |
| options: { | |
| responsive: true, | |
| maintainAspectRatio: false, | |
| plugins: { | |
| legend: { | |
| position: 'right' | |
| } | |
| }, | |
| cutout: '70%' | |
| } | |
| }); | |
| } | |
| } | |
| // Initialize tooltips | |
| const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]')); | |
| tooltipTriggerList.map(function (tooltipTriggerEl) { | |
| return new bootstrap.Tooltip(tooltipTriggerEl); | |
| }); | |
| // Dark mode toggle functionality | |
| const darkModeToggle = document.getElementById('darkModeToggle'); | |
| if (darkModeToggle) { | |
| darkModeToggle.addEventListener('click', function() { | |
| document.documentElement.classList.toggle('dark'); | |
| localStorage.setItem('darkMode', document.documentElement.classList.contains('dark')); | |
| }); | |
| } | |
| // Check for saved dark mode preference | |
| if (localStorage.getItem('darkMode') === 'true') { | |
| document.documentElement.classList.add('dark'); | |
| } | |
| // Invoice generation form handling | |
| const invoiceForm = document.getElementById('invoiceForm'); | |
| if (invoiceForm) { | |
| invoiceForm.addEventListener('submit', function(e) { | |
| e.preventDefault(); | |
| // Process form data and generate invoice | |
| console.log('Generating invoice...'); | |
| }); | |
| } | |
| // Responsive sidebar toggle | |
| document.addEventListener('click', function(e) { | |
| if (e.target.closest('#sidebarToggle')) { | |
| document.querySelector('custom-sidebar').classList.toggle('-translate-x-full'); | |
| } | |
| }); | |
| // Handle form submissions | |
| document.querySelectorAll('form').forEach(form => { | |
| form.addEventListener('submit', function(e) { | |
| e.preventDefault(); | |
| // Show loading state | |
| const submitBtn = form.querySelector('button[type="submit"]'); | |
| if (submitBtn) { | |
| const originalText = submitBtn.innerHTML; | |
| submitBtn.innerHTML = '<div class="loading-spinner"></div> Processing...'; | |
| submitBtn.disabled = true; | |
| // Simulate API call | |
| setTimeout(() => { | |
| submitBtn.innerHTML = originalText; | |
| submitBtn.disabled = false; | |
| alert('Form submitted successfully!'); | |
| }, 1500); | |
| } | |
| }); | |
| }); | |
| }); | |
| // AI Processor functionality | |
| document.addEventListener('DOMContentLoaded', function() { | |
| const documentUpload = document.getElementById('documentUpload'); | |
| const uploadedDocuments = document.getElementById('uploadedDocuments'); | |
| const aiPreviewContainer = document.getElementById('aiPreviewContainer'); | |
| const processBtn = document.getElementById('processBtn'); | |
| // Handle document upload for AI processor | |
| if (documentUpload) { | |
| documentUpload.addEventListener('change', function(e) { | |
| const files = Array.from(e.target.files); | |
| if (files.length === 0) return; | |
| aiPreviewContainer.classList.remove('hidden'); | |
| uploadedDocuments.innerHTML = ''; | |
| files.forEach(file => { | |
| const fileContainer = document.createElement('div'); | |
| fileContainer.className = 'bg-gray-50 rounded-lg p-4 flex items-start'; | |
| const fileIcon = document.createElement('div'); | |
| fileIcon.className = 'w-10 h-10 bg-indigo-100 rounded-full flex items-center justify-center mr-3 flex-shrink-0'; | |
| let iconName = 'file'; | |
| if (file.type.match('image.*')) iconName = 'image'; | |
| else if (file.type === 'application/pdf') iconName = 'file-text'; | |
| else if (file.type.match('word')) iconName = 'file-text'; | |
| fileIcon.innerHTML = `<i data-feather="${iconName}" class="text-indigo-600"></i>`; | |
| const fileInfo = document.createElement('div'); | |
| fileInfo.className = 'flex-1 min-w-0'; | |
| const fileName = document.createElement('p'); | |
| fileName.className = 'text-sm font-medium text-gray-900 truncate'; | |
| fileName.textContent = file.name; | |
| const fileDetails = document.createElement('div'); | |
| fileDetails.className = 'flex items-center text-xs text-gray-500 mt-1'; | |
| fileDetails.innerHTML = ` | |
| <span>${(file.size / 1024).toFixed(2)} KB</span> | |
| <span class="mx-2">•</span> | |
| <span>${file.type}</span> | |
| `; | |
| fileInfo.appendChild(fileName); | |
| fileInfo.appendChild(fileDetails); | |
| fileContainer.appendChild(fileIcon); | |
| fileContainer.appendChild(fileInfo); | |
| uploadedDocuments.appendChild(fileContainer); | |
| feather.replace(); | |
| }); | |
| }); | |
| } | |
| // Process button handler | |
| if (processBtn) { | |
| processBtn.addEventListener('click', async function() { | |
| const files = documentUpload.files; | |
| if (files.length === 0) { | |
| alert('Please upload documents first'); | |
| return; | |
| } | |
| processBtn.innerHTML = '<div class="loading-spinner"></div> Processing...'; | |
| processBtn.disabled = true; | |
| // Simulate AI processing | |
| setTimeout(() => { | |
| processBtn.innerHTML = '<i data-feather="check" class="mr-2"></i> Processing Complete'; | |
| feather.replace(); | |
| alert('Documents processed successfully! Extracted data can now be reviewed.'); | |
| }, 3000); | |
| }); | |
| } | |
| }); | |
| // File upload and processing | |
| document.addEventListener('DOMContentLoaded', function() { | |
| const invoiceUpload = document.getElementById('invoiceUpload'); | |
| const uploadedFiles = document.getElementById('uploadedFiles'); | |
| const previewContainer = document.getElementById('previewContainer'); | |
| const scanBtn = document.getElementById('scanBtn'); | |
| // Handle file upload | |
| invoiceUpload.addEventListener('change', function(e) { | |
| const files = Array.from(e.target.files); | |
| if (files.length === 0) return; | |
| previewContainer.classList.remove('hidden'); | |
| uploadedFiles.innerHTML = ''; | |
| files.forEach(file => { | |
| const fileContainer = document.createElement('div'); | |
| fileContainer.className = 'bg-gray-50 rounded-lg p-3 flex items-center'; | |
| const fileIcon = document.createElement('div'); | |
| fileIcon.className = 'w-10 h-10 bg-indigo-100 rounded-full flex items-center justify-center mr-3'; | |
| fileIcon.innerHTML = '<i data-feather="file" class="text-indigo-600"></i>'; | |
| const fileInfo = document.createElement('div'); | |
| fileInfo.className = 'flex-1 min-w-0'; | |
| const fileName = document.createElement('p'); | |
| fileName.className = 'text-sm font-medium text-gray-900 truncate'; | |
| fileName.textContent = file.name; | |
| const fileSize = document.createElement('p'); | |
| fileSize.className = 'text-xs text-gray-500'; | |
| fileSize.textContent = `${(file.size / 1024).toFixed(2)} KB`; | |
| fileInfo.appendChild(fileName); | |
| fileInfo.appendChild(fileSize); | |
| fileContainer.appendChild(fileIcon); | |
| fileContainer.appendChild(fileInfo); | |
| uploadedFiles.appendChild(fileContainer); | |
| feather.replace(); | |
| // Process the file | |
| processUploadedFile(file); | |
| }); | |
| }); | |
| // Scan button handler | |
| scanBtn.addEventListener('click', function() { | |
| // This would trigger mobile device camera in production | |
| alert('Scanning feature would open device camera to capture invoice'); | |
| }); | |
| }); | |
| // Process uploaded file | |
| async function processUploadedFile(file) { | |
| const reader = new FileReader(); | |
| reader.onload = async function(e) { | |
| try { | |
| if (file.type === 'application/pdf') { | |
| // Process PDF | |
| const pdfBytes = new Uint8Array(e.target.result); | |
| const pdfDoc = await PDFLib.PDFDocument.load(pdfBytes); | |
| const textContent = await extractTextFromPDF(pdfDoc); | |
| extractLineItems(textContent); | |
| } else if (file.type.match('image.*')) { | |
| // Process image | |
| const imageText = await extractTextFromImage(e.target.result); | |
| extractLineItems(imageText); | |
| } | |
| } catch (error) { | |
| console.error('Error processing file:', error); | |
| } | |
| }; | |
| reader.readAsArrayBuffer(file); | |
| } | |
| // Extract text from PDF | |
| async function extractTextFromPDF(pdfDoc) { | |
| const pages = pdfDoc.getPages(); | |
| let textContent = ''; | |
| for (const page of pages) { | |
| const text = await page.getTextContent(); | |
| textContent += text.items.map(item => item.str).join(' '); | |
| } | |
| return textContent; | |
| } | |
| // Extract text from image using OCR | |
| async function extractTextFromImage(imageData) { | |
| const worker = Tesseract.createWorker(); | |
| await worker.load(); | |
| await worker.loadLanguage('eng'); | |
| await worker.initialize('eng'); | |
| const { data: { text } } = await worker.recognize(imageData); | |
| await worker.terminate(); | |
| return text; | |
| } | |
| // Extract line items from text | |
| function extractLineItems(text) { | |
| // This is a simplified example - in production you'd use more sophisticated NLP | |
| const lines = text.split('\n'); | |
| const items = []; | |
| let foundItems = false; | |
| lines.forEach(line => { | |
| // Simple pattern matching for line items | |
| const itemMatch = line.match(/(\d+)\s+([\w\s]+)\s+(\d+\.\d{2})/i); | |
| if (itemMatch) { | |
| items.push({ | |
| description: itemMatch[2].trim(), | |
| quantity: parseInt(itemMatch[1]), | |
| rate: parseFloat(itemMatch[3]), | |
| amount: parseInt(itemMatch[1]) * parseFloat(itemMatch[3]) | |
| }); | |
| foundItems = true; | |
| } | |
| // Look for totals | |
| const totalMatch = line.match(/total.*?(\d+\.\d{2})/i); | |
| if (totalMatch) { | |
| console.log('Found total:', totalMatch[1]); | |
| } | |
| }); | |
| if (foundItems) { | |
| populateInvoiceItems(items); | |
| } | |
| } | |
| // Populate invoice items table | |
| function populateInvoiceItems(items) { | |
| const tbody = document.querySelector('table tbody'); | |
| tbody.innerHTML = ''; | |
| items.forEach(item => { | |
| const row = document.createElement('tr'); | |
| row.innerHTML = ` | |
| <td class="px-6 py-4 whitespace-nowrap"> | |
| <input type="text" value="${item.description}" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500"> | |
| </td> | |
| <td class="px-6 py-4 whitespace-nowrap"> | |
| <input type="number" class="w-20 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500" value="${item.quantity}"> | |
| </td> | |
| <td class="px-6 py-4 whitespace-nowrap"> | |
| <input type="number" class="w-24 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500" value="${item.rate}"> | |
| </td> | |
| <td class="px-6 py-4 whitespace-nowrap">${item.amount.toFixed(2)}</td> | |
| <td class="px-6 py-4 whitespace-nowrap"> | |
| <button class="text-red-500 hover:text-red-700"> | |
| <i data-feather="trash-2"></i> | |
| </button> | |
| </td> | |
| `; | |
| tbody.appendChild(row); | |
| }); | |
| feather.replace(); | |
| } | |
| // Enhanced AI processing with real API integration | |
| async function processInvoiceData(data) { | |
| try { | |
| // Using OpenAI API for enhanced processing | |
| const response = await fetch('https://api.openai.com/v1/chat/completions', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| 'Authorization': `Bearer ${YOUR_OPENAI_KEY}` | |
| }, | |
| body: JSON.stringify({ | |
| model: "gpt-4", | |
| messages: [{ | |
| role: "system", | |
| content: "You are an expert invoice processing AI. Extract all relevant fields from this invoice data." | |
| }, { | |
| role: "user", | |
| content: JSON.stringify(data) | |
| }], | |
| temperature: 0.2 | |
| }) | |
| }); | |
| const result = await response.json(); | |
| return parseAIResponse(result.choices[0].message.content); | |
| } catch (error) { | |
| console.error('AI Processing Error:', error); | |
| return null; | |
| } | |
| } | |
| // New AI features | |
| function initAIFeatures() { | |
| // Smart suggestions | |
| document.querySelectorAll('input').forEach(input => { | |
| input.addEventListener('focus', async () => { | |
| const suggestions = await getAISuggestions(input.name); | |
| showAISuggestions(input, suggestions); | |
| }); | |
| }); | |
| // Auto-categorization | |
| document.querySelectorAll('.category-select').forEach(select => { | |
| select.addEventListener('change', async (e) => { | |
| if (e.target.value === 'auto') { | |
| const items = getLineItems(); | |
| const categories = await predictCategories(items); | |
| applyCategories(categories); | |
| } | |
| }); | |
| }); | |
| } | |
| // Initialize AI features when DOM loads | |
| document.addEventListener('DOMContentLoaded', initAIFeatures); | |