![]() Server : Apache/2 System : Linux server-15-235-50-60 5.15.0-164-generic #174-Ubuntu SMP Fri Nov 14 20:25:16 UTC 2025 x86_64 User : gositeme ( 1004) PHP Version : 8.2.29 Disable Function : exec,system,passthru,shell_exec,proc_close,proc_open,dl,popen,show_source,posix_kill,posix_mkfifo,posix_getpwuid,posix_setpgid,posix_setsid,posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_uname Directory : /home/gositeme/domains/lavocat.ca/private_html/src/components/ |
import React, { useState, useEffect } from 'react';
import { DollarSign, Clock, Calendar, FileText, TrendingUp, AlertTriangle, CheckCircle, User, Scale, Brain, Zap, Target, CreditCard, Receipt, PieChart, BarChart3, Download, Mail, Eye } from 'lucide-react';
interface BillingEntry {
id: string;
caseId: string;
caseName: string;
clientName: string;
lawyerId: string;
lawyerName: string;
date: string;
time: string;
duration: number; // in minutes
activity: string;
billableRate: number;
amount: number;
status: 'pending' | 'approved' | 'invoiced' | 'paid';
category: 'research' | 'drafting' | 'court_appearance' | 'client_meeting' | 'travel' | 'phone_call' | 'email';
description: string;
attachments?: string[];
}
interface Invoice {
id: string;
invoiceNumber: string;
caseId: string;
caseName: string;
clientId: string;
clientName: string;
clientEmail: string;
dateIssued: string;
dateDue: string;
status: 'draft' | 'sent' | 'paid' | 'overdue' | 'cancelled';
subtotal: number;
tax: number;
total: number;
paidAmount: number;
billingEntries: BillingEntry[];
paymentTerms: string;
notes?: string;
}
interface BillingMetrics {
totalRevenue: number;
pendingBilling: number;
outstandingInvoices: number;
collectionRate: number;
averageBillableRate: number;
totalBillableHours: number;
utilizationRate: number;
revenueGrowth: number;
}
const SmartBillingIntegration: React.FC = () => {
const [activeTab, setActiveTab] = useState<'dashboard' | 'time_entries' | 'invoices' | 'analytics' | 'settings'>('dashboard');
const [billingEntries, setBillingEntries] = useState<BillingEntry[]>([]);
const [invoices, setInvoices] = useState<Invoice[]>([]);
const [metrics, setMetrics] = useState<BillingMetrics | null>(null);
const [selectedPeriod, setSelectedPeriod] = useState<'week' | 'month' | 'quarter' | 'year'>('month');
useEffect(() => {
loadBillingData();
loadInvoices();
loadMetrics();
}, [selectedPeriod]);
const loadBillingData = () => {
const entries: BillingEntry[] = [
{
id: 'bill_001',
caseId: 'case_bordeaux_2024',
caseName: 'Bordeaux Detention Center Class Action',
clientName: 'Multiple Detainees',
lawyerId: 'lawyer_justin',
lawyerName: 'Justin Wee',
date: '2025-03-08',
time: '09:00',
duration: 180, // 3 hours
activity: 'Class Action Petition Drafting',
billableRate: 650,
amount: 1950,
status: 'approved',
category: 'drafting',
description: 'Comprehensive class action petition drafting with AI-assisted legal research and fact compilation',
attachments: ['petition_draft_v1.pdf', 'research_notes.pdf']
},
{
id: 'bill_002',
caseId: 'case_bordeaux_2024',
caseName: 'Bordeaux Detention Center Class Action',
clientName: 'Multiple Detainees',
lawyerId: 'lawyer_audrey',
lawyerName: 'Audrey Labrecque',
date: '2025-03-08',
time: '14:00',
duration: 120, // 2 hours
activity: 'Emergency Injunction Motion',
billableRate: 550,
amount: 1100,
status: 'approved',
category: 'drafting',
description: 'Drafted emergency preliminary injunction motion with supporting affidavits',
attachments: ['injunction_motion.pdf']
},
{
id: 'bill_003',
caseId: 'case_imm_001',
caseName: 'Rodriguez Immigration Case',
clientName: 'Maria Rodriguez',
lawyerId: 'lawyer_marie',
lawyerName: 'Marie-Christine Vachon',
date: '2025-03-07',
time: '10:30',
duration: 90,
activity: 'Detention Center Client Visit',
billableRate: 450,
amount: 675,
status: 'pending',
category: 'client_meeting',
description: 'Initial consultation and case assessment at Laval Immigration Holding Centre'
},
{
id: 'bill_004',
caseId: 'case_bordeaux_2024',
caseName: 'Bordeaux Detention Center Class Action',
clientName: 'Multiple Detainees',
lawyerId: 'lawyer_david',
lawyerName: 'David Frappier',
date: '2025-03-06',
time: '11:00',
duration: 240, // 4 hours
activity: 'Expert Witness Consultation',
billableRate: 600,
amount: 2400,
status: 'invoiced',
category: 'research',
description: 'Consultation with detention conditions expert and medical professionals'
}
];
setBillingEntries(entries);
};
const loadInvoices = () => {
const invoiceData: Invoice[] = [
{
id: 'inv_001',
invoiceNumber: 'INV-2025-001',
caseId: 'case_bordeaux_2024',
caseName: 'Bordeaux Detention Center Class Action',
clientId: 'client_bordeaux',
clientName: 'Bordeaux Class Action Fund',
clientEmail: 'finance@bordeauxfund.org',
dateIssued: '2025-03-01',
dateDue: '2025-03-31',
status: 'sent',
subtotal: 24750.00,
tax: 3712.50,
total: 28462.50,
paidAmount: 0,
paymentTerms: 'Net 30',
billingEntries: billingEntries.filter(entry => entry.status === 'invoiced'),
notes: 'Class action legal services for February 2025'
},
{
id: 'inv_002',
invoiceNumber: 'INV-2025-002',
caseId: 'case_imm_001',
caseName: 'Rodriguez Immigration Case',
clientId: 'client_rodriguez',
clientName: 'Maria Rodriguez',
clientEmail: 'maria.rodriguez@email.com',
dateIssued: '2025-02-28',
dateDue: '2025-03-28',
status: 'paid',
subtotal: 3500.00,
tax: 525.00,
total: 4025.00,
paidAmount: 4025.00,
paymentTerms: 'Net 30',
billingEntries: [],
notes: 'Immigration detention defense - flat fee arrangement'
}
];
setInvoices(invoiceData);
};
const loadMetrics = () => {
const metricsData: BillingMetrics = {
totalRevenue: 847293,
pendingBilling: 47580,
outstandingInvoices: 156745,
collectionRate: 94.7,
averageBillableRate: 575,
totalBillableHours: 1472,
utilizationRate: 87.3,
revenueGrowth: 23.4
};
setMetrics(metricsData);
};
const generateInvoice = (entries: BillingEntry[]) => {
const subtotal = entries.reduce((sum, entry) => sum + entry.amount, 0);
const tax = subtotal * 0.15; // 15% tax rate
const total = subtotal + tax;
const newInvoice: Invoice = {
id: `inv_${Date.now()}`,
invoiceNumber: `INV-2025-${String(invoices.length + 1).padStart(3, '0')}`,
caseId: entries[0]?.caseId || '',
caseName: entries[0]?.caseName || '',
clientId: `client_${entries[0]?.caseId}`,
clientName: entries[0]?.clientName || '',
clientEmail: 'client@email.com',
dateIssued: new Date().toISOString().split('T')[0],
dateDue: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString().split('T')[0],
status: 'draft',
subtotal,
tax,
total,
paidAmount: 0,
paymentTerms: 'Net 30',
billingEntries: entries,
notes: `Legal services for ${new Date().toLocaleDateString('en-US', { month: 'long', year: 'numeric' })}`
};
setInvoices([newInvoice, ...invoices]);
// Update billing entries status
setBillingEntries(prev =>
prev.map(entry =>
entries.find(e => e.id === entry.id)
? { ...entry, status: 'invoiced' as const }
: entry
)
);
};
const categoryColors = {
research: 'bg-blue-100 text-blue-800',
drafting: 'bg-purple-100 text-purple-800',
court_appearance: 'bg-red-100 text-red-800',
client_meeting: 'bg-green-100 text-green-800',
travel: 'bg-yellow-100 text-yellow-800',
phone_call: 'bg-orange-100 text-orange-800',
email: 'bg-gray-100 text-gray-800'
};
const statusColors = {
pending: 'bg-yellow-100 text-yellow-800',
approved: 'bg-green-100 text-green-800',
invoiced: 'bg-blue-100 text-blue-800',
paid: 'bg-purple-100 text-purple-800',
draft: 'bg-gray-100 text-gray-800',
sent: 'bg-blue-100 text-blue-800',
overdue: 'bg-red-100 text-red-800',
cancelled: 'bg-gray-100 text-gray-800'
};
return (
<div className="min-h-screen bg-gradient-to-br from-blue-50 via-purple-50 to-indigo-50 p-6">
{/* Header */}
<div className="mb-8">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-4">
<div className="p-3 bg-gradient-to-r from-green-600 to-emerald-600 rounded-xl">
<DollarSign className="h-8 w-8 text-white" />
</div>
<div>
<h1 className="text-3xl font-bold bg-gradient-to-r from-green-600 to-emerald-600 bg-clip-text text-transparent">
💰 SmartBilling Pro Suite
</h1>
<p className="text-gray-600 flex items-center space-x-2">
<Brain className="h-4 w-4" />
<span>AI-Powered Billing, Invoicing & Financial Management</span>
</p>
</div>
</div>
<div className="flex items-center space-x-4">
<select
value={selectedPeriod}
onChange={(e) => setSelectedPeriod(e.target.value as any)}
className="px-3 py-2 border border-gray-300 rounded-lg text-sm"
>
<option value="week">This Week</option>
<option value="month">This Month</option>
<option value="quarter">This Quarter</option>
<option value="year">This Year</option>
</select>
<button className="flex items-center space-x-2 px-4 py-2 bg-gradient-to-r from-purple-600 to-blue-600 text-white rounded-lg font-medium hover:shadow-lg transition-all">
<Zap className="h-4 w-4" />
<span>Auto-Generate Invoice</span>
</button>
</div>
</div>
</div>
{/* Navigation Tabs */}
<div className="mb-6">
<div className="flex space-x-1 bg-white rounded-lg p-1 shadow-md">
{[
{ id: 'dashboard', label: 'Dashboard', icon: Target },
{ id: 'time_entries', label: 'Time Entries', icon: Clock },
{ id: 'invoices', label: 'Invoices', icon: Receipt },
{ id: 'analytics', label: 'Analytics', icon: BarChart3 },
{ id: 'settings', label: 'Settings', icon: Calendar }
].map((tab) => {
const IconComponent = tab.icon;
return (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id as any)}
className={`flex items-center space-x-2 px-4 py-2 rounded-md text-sm font-medium transition-all ${
activeTab === tab.id
? 'bg-green-600 text-white shadow-md'
: 'text-gray-600 hover:text-green-600 hover:bg-gray-50'
}`}
>
<IconComponent className="h-4 w-4" />
<span>{tab.label}</span>
</button>
);
})}
</div>
</div>
{/* Dashboard View */}
{activeTab === 'dashboard' && (
<div className="space-y-6">
{/* Key Metrics */}
<div className="grid grid-cols-4 gap-6">
<div className="bg-white rounded-xl p-6 shadow-lg border border-gray-100">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">Total Revenue</p>
<p className="text-3xl font-bold text-green-600">${metrics?.totalRevenue.toLocaleString()}</p>
</div>
<div className="p-3 bg-green-100 rounded-lg">
<DollarSign className="h-6 w-6 text-green-600" />
</div>
</div>
<div className="mt-4 flex items-center text-sm">
<TrendingUp className="h-4 w-4 text-green-500 mr-1" />
<span className="text-green-600">+{metrics?.revenueGrowth}% vs last period</span>
</div>
</div>
<div className="bg-white rounded-xl p-6 shadow-lg border border-gray-100">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">Pending Billing</p>
<p className="text-3xl font-bold text-yellow-600">${metrics?.pendingBilling.toLocaleString()}</p>
</div>
<div className="p-3 bg-yellow-100 rounded-lg">
<Clock className="h-6 w-6 text-yellow-600" />
</div>
</div>
<div className="mt-4 flex items-center text-sm">
<AlertTriangle className="h-4 w-4 text-yellow-500 mr-1" />
<span className="text-yellow-600">Ready to invoice</span>
</div>
</div>
<div className="bg-white rounded-xl p-6 shadow-lg border border-gray-100">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">Outstanding</p>
<p className="text-3xl font-bold text-red-600">${metrics?.outstandingInvoices.toLocaleString()}</p>
</div>
<div className="p-3 bg-red-100 rounded-lg">
<Receipt className="h-6 w-6 text-red-600" />
</div>
</div>
<div className="mt-4 flex items-center text-sm">
<AlertTriangle className="h-4 w-4 text-red-500 mr-1" />
<span className="text-red-600">Awaiting payment</span>
</div>
</div>
<div className="bg-white rounded-xl p-6 shadow-lg border border-gray-100">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">Collection Rate</p>
<p className="text-3xl font-bold text-blue-600">{metrics?.collectionRate}%</p>
</div>
<div className="p-3 bg-blue-100 rounded-lg">
<TrendingUp className="h-6 w-6 text-blue-600" />
</div>
</div>
<div className="mt-4 flex items-center text-sm">
<CheckCircle className="h-4 w-4 text-blue-500 mr-1" />
<span className="text-blue-600">Above industry avg</span>
</div>
</div>
</div>
{/* Recent Activity */}
<div className="grid grid-cols-2 gap-6">
{/* Recent Time Entries */}
<div className="bg-white rounded-xl shadow-lg border border-gray-100 p-6">
<h2 className="text-xl font-bold text-gray-900 mb-6 flex items-center">
<Clock className="h-5 w-5 mr-2" />
Recent Time Entries
</h2>
<div className="space-y-4">
{billingEntries.slice(0, 5).map((entry) => (
<div key={entry.id} className="p-4 bg-gray-50 rounded-lg border border-gray-200">
<div className="flex items-center justify-between mb-2">
<div>
<h3 className="font-semibold text-gray-900">{entry.activity}</h3>
<p className="text-sm text-gray-600">{entry.caseName}</p>
</div>
<div className="text-right">
<p className="font-bold text-green-600">${entry.amount}</p>
<span className={`px-2 py-1 rounded-full text-xs font-medium ${statusColors[entry.status]}`}>
{entry.status.toUpperCase()}
</span>
</div>
</div>
<div className="flex items-center justify-between text-sm text-gray-600">
<div className="flex items-center space-x-4">
<span>{entry.lawyerName}</span>
<span>{entry.duration}min</span>
<span className={`px-2 py-1 rounded-full text-xs ${categoryColors[entry.category]}`}>
{entry.category.replace('_', ' ').toUpperCase()}
</span>
</div>
<span>{entry.date}</span>
</div>
</div>
))}
</div>
<button className="w-full mt-4 px-4 py-2 bg-blue-600 text-white rounded-lg font-medium hover:bg-blue-700 transition-colors">
View All Time Entries
</button>
</div>
{/* Recent Invoices */}
<div className="bg-white rounded-xl shadow-lg border border-gray-100 p-6">
<h2 className="text-xl font-bold text-gray-900 mb-6 flex items-center">
<Receipt className="h-5 w-5 mr-2" />
Recent Invoices
</h2>
<div className="space-y-4">
{invoices.slice(0, 5).map((invoice) => (
<div key={invoice.id} className="p-4 bg-gray-50 rounded-lg border border-gray-200">
<div className="flex items-center justify-between mb-2">
<div>
<h3 className="font-semibold text-gray-900">{invoice.invoiceNumber}</h3>
<p className="text-sm text-gray-600">{invoice.clientName}</p>
</div>
<div className="text-right">
<p className="font-bold text-green-600">${invoice.total.toLocaleString()}</p>
<span className={`px-2 py-1 rounded-full text-xs font-medium ${statusColors[invoice.status]}`}>
{invoice.status.toUpperCase()}
</span>
</div>
</div>
<div className="flex items-center justify-between text-sm text-gray-600">
<div className="flex items-center space-x-4">
<span>Due: {invoice.dateDue}</span>
{invoice.status === 'paid' && (
<span className="text-green-600">✓ Paid</span>
)}
</div>
<div className="flex items-center space-x-2">
<button className="p-1 hover:bg-gray-200 rounded">
<Eye className="h-4 w-4 text-gray-600" />
</button>
<button className="p-1 hover:bg-gray-200 rounded">
<Download className="h-4 w-4 text-gray-600" />
</button>
<button className="p-1 hover:bg-gray-200 rounded">
<Mail className="h-4 w-4 text-gray-600" />
</button>
</div>
</div>
</div>
))}
</div>
<button className="w-full mt-4 px-4 py-2 bg-green-600 text-white rounded-lg font-medium hover:bg-green-700 transition-colors">
Create New Invoice
</button>
</div>
</div>
</div>
)}
{/* Time Entries View */}
{activeTab === 'time_entries' && (
<div className="bg-white rounded-xl shadow-lg border border-gray-100 p-6">
<div className="flex items-center justify-between mb-6">
<h2 className="text-xl font-bold text-gray-900">Time Entries</h2>
<div className="flex items-center space-x-4">
<select className="px-3 py-2 border border-gray-300 rounded-lg text-sm">
<option>All Status</option>
<option>Pending</option>
<option>Approved</option>
<option>Invoiced</option>
</select>
<button
onClick={() => {
const approvedEntries = billingEntries.filter(entry => entry.status === 'approved');
if (approvedEntries.length > 0) {
generateInvoice(approvedEntries);
}
}}
className="px-4 py-2 bg-green-600 text-white rounded-lg font-medium hover:bg-green-700 transition-colors"
>
Generate Invoice
</button>
</div>
</div>
<div className="overflow-x-auto">
<table className="w-full">
<thead>
<tr className="border-b border-gray-200">
<th className="text-left py-3 px-4 font-medium text-gray-900">Date/Time</th>
<th className="text-left py-3 px-4 font-medium text-gray-900">Lawyer</th>
<th className="text-left py-3 px-4 font-medium text-gray-900">Case</th>
<th className="text-left py-3 px-4 font-medium text-gray-900">Activity</th>
<th className="text-left py-3 px-4 font-medium text-gray-900">Duration</th>
<th className="text-left py-3 px-4 font-medium text-gray-900">Rate</th>
<th className="text-left py-3 px-4 font-medium text-gray-900">Amount</th>
<th className="text-left py-3 px-4 font-medium text-gray-900">Status</th>
</tr>
</thead>
<tbody>
{billingEntries.map((entry) => (
<tr key={entry.id} className="border-b border-gray-100 hover:bg-gray-50">
<td className="py-3 px-4">
<div>
<p className="font-medium text-gray-900">{entry.date}</p>
<p className="text-sm text-gray-600">{entry.time}</p>
</div>
</td>
<td className="py-3 px-4">
<p className="text-sm text-gray-900">{entry.lawyerName}</p>
</td>
<td className="py-3 px-4">
<div>
<p className="font-medium text-gray-900">{entry.caseName}</p>
<p className="text-sm text-gray-600">{entry.clientName}</p>
</div>
</td>
<td className="py-3 px-4">
<div>
<p className="font-medium text-gray-900">{entry.activity}</p>
<span className={`px-2 py-1 rounded-full text-xs font-medium ${categoryColors[entry.category]}`}>
{entry.category.replace('_', ' ').toUpperCase()}
</span>
</div>
</td>
<td className="py-3 px-4">
<p className="text-sm text-gray-900">{Math.floor(entry.duration / 60)}h {entry.duration % 60}m</p>
</td>
<td className="py-3 px-4">
<p className="text-sm text-gray-900">${entry.billableRate}/hr</p>
</td>
<td className="py-3 px-4">
<p className="font-bold text-green-600">${entry.amount}</p>
</td>
<td className="py-3 px-4">
<span className={`px-2 py-1 rounded-full text-xs font-medium ${statusColors[entry.status]}`}>
{entry.status.toUpperCase()}
</span>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
)}
</div>
);
};
export default SmartBillingIntegration;