![]() 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/public_html/src/components/ |
'use client';
import React, { useState, useEffect } from 'react';
import { useSession } from 'next-auth/react';
import { useRouter } from 'next/router';
import {
Search,
Filter,
Plus,
Eye,
Edit,
Calendar,
User,
Building,
Clock,
CheckCircle,
XCircle,
AlertTriangle,
DollarSign,
MapPin,
FileText
} from 'lucide-react';
// UI components replaced with standard HTML elements and Tailwind CSS
interface Case {
id: string;
title: string;
description: string;
caseNumber?: string;
caseType: string;
status: string;
jurisdiction: string;
court?: string;
priority: string;
budget?: number;
expectedDuration?: number;
filingDate?: string;
applicationDeadline?: string;
isAcceptingApplications: boolean;
requiresApproval: boolean;
createdAt: string;
updatedAt: string;
logoUrl?: string;
leadLawyer: {
id: string;
name: string;
email: string;
title?: string;
specialization?: string;
};
creator: {
id: string;
name: string;
email: string;
};
firmName?: string;
_count: {
registrations: number;
caseAssignments: number;
caseUpdates: number;
};
}
interface CaseWidgetProps {
title?: string;
maxCases?: number;
showCreateButton?: boolean;
showViewAll?: boolean;
showMyCases?: boolean;
showAssignedCases?: boolean;
showPublicCases?: boolean;
showFilters?: boolean;
className?: string;
}
const CaseWidget: React.FC<CaseWidgetProps> = ({
title = 'Case Management',
maxCases = 5,
showCreateButton = true,
showViewAll = true,
showMyCases = false,
showAssignedCases = false,
showPublicCases = false,
showFilters = true,
className = ''
}) => {
const { data: session } = useSession();
const router = useRouter();
const [cases, setCases] = useState<Case[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState('');
const [searchTerm, setSearchTerm] = useState('');
const [statusFilter, setStatusFilter] = useState('all');
useEffect(() => {
if (session?.user) {
fetchCases();
}
}, [session]);
const fetchCases = async () => {
try {
setLoading(true);
// Build query parameters based on props
const params = new URLSearchParams();
if (showMyCases) params.append('myCases', 'true');
if (showAssignedCases) params.append('assignedCases', 'true');
if (showPublicCases) params.append('publicCases', 'true');
const response = await fetch(`/api/cases?${params.toString()}`, {
credentials: 'same-origin',
});
if (response.ok) {
const data = await response.json();
setCases(data.cases || []);
} else {
setError('Failed to fetch cases');
}
} catch (error) {
console.error('Error fetching cases:', error);
setError('Error loading cases');
} finally {
setLoading(false);
}
};
const getStatusIcon = (status: string) => {
switch (status) {
case 'active': return <CheckCircle className="h-4 w-4 text-green-500" />;
case 'pending': return <Clock className="h-4 w-4 text-yellow-500" />;
case 'closed': return <XCircle className="h-4 w-4 text-gray-500" />;
case 'suspended': return <AlertTriangle className="h-4 w-4 text-red-500" />;
default: return <Clock className="h-4 w-4 text-gray-400" />;
}
};
const getPriorityColor = (priority: string) => {
switch (priority) {
case 'urgent': return 'bg-red-100 text-red-800 border-red-200';
case 'high': return 'bg-orange-100 text-orange-800 border-orange-200';
case 'medium': return 'bg-blue-100 text-blue-800 border-blue-200';
case 'low': return 'bg-gray-100 text-gray-800 border-gray-200';
default: return 'bg-gray-100 text-gray-800 border-gray-200';
}
};
const filteredCases = cases
.filter(case_ =>
case_.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
case_.description.toLowerCase().includes(searchTerm.toLowerCase()) ||
(case_.caseNumber && case_.caseNumber.toLowerCase().includes(searchTerm.toLowerCase()))
)
.filter(case_ => statusFilter === 'all' || case_.status === statusFilter)
.slice(0, maxCases);
const handleCreateCase = () => {
router.push('/hire/new-case');
};
const handleViewCase = (caseId: string) => {
router.push(`/admin/cases/${caseId}`);
};
const handleEditCase = (caseId: string) => {
router.push(`/admin/cases/${caseId}?edit=true`);
};
const handleViewAll = () => {
router.push('/admin/cases');
};
if (loading) {
return (
<div className={`bg-white rounded-lg shadow-sm border ${className}`}>
<div className="p-6 border-b border-gray-200">
<div className="flex items-center justify-between">
<h3 className="text-lg font-semibold text-gray-900">{title}</h3>
{showCreateButton && (
<button
className="inline-flex items-center px-3 py-2 bg-blue-600 text-white text-sm rounded-lg hover:bg-blue-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
disabled
>
<Plus className="h-4 w-4 mr-1" />
Create Case
</button>
)}
</div>
</div>
<div className="p-6">
<div className="space-y-3">
{[...Array(3)].map((_, i) => (
<div key={i} className="animate-pulse">
<div className="h-20 bg-gray-200 rounded"></div>
</div>
))}
</div>
</div>
</div>
);
}
if (error) {
return (
<div className={`bg-white rounded-lg shadow-sm border ${className}`}>
<div className="p-6 border-b border-gray-200">
<h3 className="text-lg font-semibold text-gray-900">{title}</h3>
</div>
<div className="p-6">
<div className="text-center py-4">
<AlertTriangle className="h-8 w-8 text-red-500 mx-auto mb-2" />
<p className="text-red-600">{error}</p>
<button
className="inline-flex items-center px-3 py-2 border border-gray-300 text-gray-700 text-sm rounded-lg hover:bg-gray-50 transition-colors mt-2"
onClick={fetchCases}
>
Retry
</button>
</div>
</div>
</div>
);
}
return (
<div className={`bg-white rounded-lg shadow-sm border ${className}`}>
<div className="p-6 border-b border-gray-200">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Building className="h-5 w-5" />
<h3 className="text-lg font-semibold text-gray-900">{title}</h3>
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-800 border border-gray-200">
{cases.length}
</span>
</div>
{showCreateButton && (
<button
className="inline-flex items-center px-3 py-2 bg-blue-600 text-white text-sm rounded-lg hover:bg-blue-700 transition-colors"
onClick={handleCreateCase}
title="Create a new case"
>
<Plus className="h-4 w-4 mr-1" />
Create Case
</button>
)}
</div>
</div>
<div className="p-6 space-y-4">
{/* Search and Filter */}
{showFilters && (
<div className="flex gap-2">
<div className="flex-1 relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400" />
<input
type="text"
placeholder="Search cases..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
</div>
<select
value={statusFilter}
onChange={(e) => setStatusFilter(e.target.value)}
className="px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent w-32"
>
<option value="all">All Status</option>
<option value="active">Active</option>
<option value="pending">Pending</option>
<option value="closed">Closed</option>
<option value="suspended">Suspended</option>
</select>
</div>
)}
{/* Cases List */}
{filteredCases.length === 0 ? (
<div className="text-center py-8">
<Building className="h-12 w-12 text-gray-400 mx-auto mb-3" />
<p className="text-gray-500 mb-2">
{searchTerm || statusFilter !== 'all'
? 'No cases match your search criteria'
: 'No cases found'
}
</p>
{showCreateButton && (
<button
className="inline-flex items-center px-3 py-2 border border-gray-300 text-gray-700 text-sm rounded-lg hover:bg-gray-50 transition-colors"
onClick={handleCreateCase}
>
<Plus className="h-4 w-4 mr-1" />
Create Your First Case
</button>
)}
</div>
) : (
<div className="space-y-3">
{filteredCases.map((case_) => (
<div
key={case_.id}
className="border rounded-lg p-4 hover:bg-gray-50 transition-colors cursor-pointer"
onClick={() => handleViewCase(case_.id)}
>
<div className="flex items-start gap-3 mb-2">
{/* Case Logo */}
<div className="flex-shrink-0">
{case_.logoUrl ? (
<img
src={case_.logoUrl}
alt={`${case_.title} Logo`}
className="w-12 h-12 rounded-lg object-cover border border-gray-200"
/>
) : (
<div className="w-12 h-12 rounded-lg bg-gradient-to-br from-blue-500/20 to-indigo-500/20 border border-gray-200 flex items-center justify-center">
<FileText className="w-6 h-6 text-gray-600" />
</div>
)}
</div>
<div className="flex-1 min-w-0">
<h3 className="font-semibold text-gray-900 truncate">
{case_.title}
</h3>
{case_.caseNumber && (
<p className="text-sm text-gray-500">#{case_.caseNumber}</p>
)}
</div>
<div className="flex items-center gap-2 ml-2">
{getStatusIcon(case_.status)}
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium border ${getPriorityColor(case_.priority)}`}>
{case_.priority}
</span>
</div>
</div>
<p className="text-sm text-gray-600 mb-3 line-clamp-2">
{case_.description}
</p>
<div className="flex items-center justify-between text-xs text-gray-500">
<div className="flex items-center gap-4">
<div className="flex items-center gap-1">
<User className="h-3 w-3" />
<span>{case_.leadLawyer.name}</span>
</div>
<div className="flex items-center gap-1">
<MapPin className="h-3 w-3" />
<span>{case_.jurisdiction}</span>
</div>
<div className="flex items-center gap-1">
<Calendar className="h-3 w-3" />
<span>{new Date(case_.createdAt).toLocaleDateString()}</span>
</div>
</div>
<div className="flex items-center gap-1">
<span>{case_._count.registrations} applications</span>
</div>
</div>
{/* Quick Actions */}
<div className="flex items-center gap-2 mt-3 pt-3 border-t">
<button
className="inline-flex items-center px-3 py-2 border border-gray-300 text-gray-700 text-sm rounded-lg hover:bg-gray-50 transition-colors"
onClick={(e) => {
e.stopPropagation();
handleViewCase(case_.id);
}}
>
<Eye className="h-3 w-3 mr-1" />
View
</button>
<button
className="inline-flex items-center px-3 py-2 border border-gray-300 text-gray-700 text-sm rounded-lg hover:bg-gray-50 transition-colors"
onClick={(e) => {
e.stopPropagation();
handleEditCase(case_.id);
}}
>
<Edit className="h-3 w-3 mr-1" />
Edit
</button>
{case_.budget && (
<div className="flex items-center gap-1 text-xs text-gray-500">
<DollarSign className="h-3 w-3" />
<span>${case_.budget.toLocaleString()}</span>
</div>
)}
</div>
</div>
))}
</div>
)}
{/* View All Button */}
{showViewAll && cases.length > maxCases && (
<div className="text-center pt-4 border-t">
<button
className="inline-flex items-center px-4 py-2 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition-colors"
onClick={handleViewAll}
>
View All Cases ({cases.length})
</button>
</div>
)}
</div>
</div>
);
};
export default CaseWidget;