T.ME/BIBIL_0DAY
CasperSecurity


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/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Current File : /home/gositeme/domains/lavocat.ca/private_html/src/components/CaseTimeline.tsx
import React, { useState } from 'react';
import { format } from 'date-fns';
import { 
  Calendar, 
  CheckCircle, 
  Clock, 
  AlertTriangle, 
  FileText, 
  Users, 
  DollarSign, 
  Scale, 
  MapPin, 
  MessageSquare,
  ChevronRight,
  ChevronDown,
  ExternalLink,
  Star,
  Award,
  Zap
} from 'lucide-react';

interface TimelineEvent {
  id: string;
  title: string;
  description?: string;
  date: string;
  type: 'milestone' | 'update' | 'deadline' | 'achievement' | 'warning';
  status: 'completed' | 'pending' | 'upcoming' | 'overdue';
  icon?: React.ReactNode;
  metadata?: {
    participants?: string[];
    location?: string;
    amount?: number;
    documents?: string[];
  };
}

interface CaseTimelineProps {
  caseData: any;
  className?: string;
}

const getEventIcon = (type: string, status: string) => {
  const baseClasses = "h-6 w-6";
  
  switch (type) {
    case 'milestone':
      return status === 'completed' ? 
        <CheckCircle className={`${baseClasses} text-green-500`} /> :
        <Calendar className={`${baseClasses} text-blue-500`} />;
    case 'update':
      return <MessageSquare className={`${baseClasses} text-purple-500`} />;
    case 'deadline':
      return status === 'overdue' ? 
        <AlertTriangle className={`${baseClasses} text-red-500`} /> :
        <Clock className={`${baseClasses} text-orange-500`} />;
    case 'achievement':
      return <Award className={`${baseClasses} text-yellow-500`} />;
    case 'warning':
      return <AlertTriangle className={`${baseClasses} text-red-500`} />;
    default:
      return <FileText className={`${baseClasses} text-gray-500`} />;
  }
};

const getStatusColor = (status: string) => {
  switch (status) {
    case 'completed': return 'border-green-500 bg-green-50';
    case 'pending': return 'border-yellow-500 bg-yellow-50';
    case 'upcoming': return 'border-blue-500 bg-blue-50';
    case 'overdue': return 'border-red-500 bg-red-50';
    default: return 'border-gray-300 bg-gray-50';
  }
};

const getStatusText = (status: string) => {
  switch (status) {
    case 'completed': return 'Completed';
    case 'pending': return 'In Progress';
    case 'upcoming': return 'Upcoming';
    case 'overdue': return 'Overdue';
    default: return 'Unknown';
  }
};

const CaseTimeline: React.FC<CaseTimelineProps> = ({ caseData, className = '' }) => {
  const [expandedEvents, setExpandedEvents] = useState<Set<string>>(new Set());
  const [selectedFilter, setSelectedFilter] = useState<string>('all');

  // Helper function to safely parse dates
  const safeParseDate = (dateString: string | null | undefined): Date | null => {
    if (!dateString) return null;
    const date = new Date(dateString);
    return isNaN(date.getTime()) ? null : date;
  };

  // Generate timeline events from case data
  const generateTimelineEvents = (): TimelineEvent[] => {
    const events: TimelineEvent[] = [];

    // Validate caseData exists
    if (!caseData) return events;

    // Case creation
    if (caseData.createdAt) {
      events.push({
        id: 'case-created',
        title: 'Case Created',
        description: `Case "${caseData.title || 'Untitled Case'}" was created and made public`,
        date: caseData.createdAt,
        type: 'milestone',
        status: 'completed',
        metadata: {
          participants: [caseData.creator?.name || 'Unknown']
        }
      });
    }

    // Application deadline
    if (caseData.applicationDeadline) {
      const deadlineDate = safeParseDate(caseData.applicationDeadline);
      if (deadlineDate) {
        const now = new Date();
        const status = deadlineDate < now ? 'overdue' : 'upcoming';
        
        events.push({
          id: 'application-deadline',
          title: 'Application Deadline',
          description: 'Last day to submit applications for this case',
          date: caseData.applicationDeadline,
          type: 'deadline',
          status,
          metadata: {
            participants: ['All Applicants']
          }
        });
      }
    }

    // Filing date
    if (caseData.filingDate) {
      const filingDate = safeParseDate(caseData.filingDate);
      if (filingDate) {
        const now = new Date();
        const status = filingDate < now ? 'completed' : 'upcoming';
        
        events.push({
          id: 'case-filed',
          title: 'Case Filed',
          description: 'Legal case was officially filed with the court',
          date: caseData.filingDate,
          type: 'milestone',
          status,
          metadata: {
            location: caseData.court || 'Court',
            participants: [caseData.leadLawyer?.name || 'Lead Lawyer']
          }
        });
      }
    }

    // Case updates
    if (caseData.caseUpdates && Array.isArray(caseData.caseUpdates)) {
      caseData.caseUpdates.forEach((update: any, index: number) => {
        if (update && update.id && update.createdAt) {
          events.push({
            id: `update-${update.id}`,
            title: update.title || 'Case Update',
            description: update.description,
            date: update.createdAt,
            type: 'update',
            status: 'completed',
            metadata: {
              participants: [update.author?.name || 'Unknown']
            }
          });
        }
      });
    }

    // Expected completion
    if (caseData.expectedDuration && caseData.createdAt) {
      const createdDate = safeParseDate(caseData.createdAt);
      if (createdDate && typeof caseData.expectedDuration === 'number') {
        const expectedDate = new Date(createdDate.getTime() + (caseData.expectedDuration * 24 * 60 * 60 * 1000));
        const now = new Date();
        const status = expectedDate < now ? 'overdue' : 'upcoming';
        
        events.push({
          id: 'expected-completion',
          title: 'Expected Completion',
          description: `Estimated completion date based on ${caseData.expectedDuration} days duration`,
          date: expectedDate.toISOString(),
          type: 'milestone',
          status,
          metadata: {
            participants: [caseData.leadLawyer?.name || 'Lead Lawyer']
          }
        });
      }
    }

    // Achievements based on stats
    if (caseData._count?.registrations && caseData._count.registrations >= 5) {
      events.push({
        id: 'high-interest',
        title: 'High Interest Achieved',
        description: 'Case has received significant interest from the community',
        date: new Date().toISOString(),
        type: 'achievement',
        status: 'completed',
        metadata: {
          participants: ['Community']
        }
      });
    }

    if (caseData._count?.supporters && caseData._count.supporters >= 10) {
      events.push({
        id: 'community-support',
        title: 'Strong Community Support',
        description: 'Case has gained strong community backing',
        date: new Date().toISOString(),
        type: 'achievement',
        status: 'completed',
        metadata: {
          participants: ['Supporters']
        }
      });
    }

    // Sort events by date
    return events.sort((a, b) => {
      const dateA = safeParseDate(a.date);
      const dateB = safeParseDate(b.date);
      if (!dateA || !dateB) return 0;
      return dateA.getTime() - dateB.getTime();
    });
  };

  const timelineEvents = generateTimelineEvents();
  
  const filteredEvents = selectedFilter === 'all' 
    ? timelineEvents 
    : timelineEvents.filter(event => event.type === selectedFilter);

  const toggleEventExpansion = (eventId: string) => {
    const newExpanded = new Set(expandedEvents);
    if (newExpanded.has(eventId)) {
      newExpanded.delete(eventId);
    } else {
      newExpanded.add(eventId);
    }
    setExpandedEvents(newExpanded);
  };

  const getProgressPercentage = () => {
    if (timelineEvents.length === 0) return 0;
    const completedEvents = timelineEvents.filter(event => event.status === 'completed');
    return Math.round((completedEvents.length / timelineEvents.length) * 100);
  };

  // Safe date formatting
  const safeFormatDate = (dateString: string) => {
    const date = safeParseDate(dateString);
    if (!date) return 'Invalid Date';
    try {
      return format(date, 'MMM d, yyyy');
    } catch {
      return 'Invalid Date';
    }
  };

  const safeFormatFullDate = (dateString: string) => {
    const date = safeParseDate(dateString);
    if (!date) return 'Invalid Date';
    try {
      return format(date, 'PPP');
    } catch {
      return 'Invalid Date';
    }
  };

  const safeFormatTime = (dateString: string) => {
    const date = safeParseDate(dateString);
    if (!date) return 'Invalid Time';
    try {
      return format(date, 'p');
    } catch {
      return 'Invalid Time';
    }
  };

  return (
    <div className={`bg-white rounded-xl shadow-sm border border-gray-200 p-8 ${className}`}>
      {/* Header */}
      <div className="flex items-center gap-3 mb-6">
        <div className="w-10 h-10 bg-gradient-to-br from-blue-500 to-purple-600 rounded-lg flex items-center justify-center">
          <Zap className="h-5 w-5 text-white" />
        </div>
        <div>
          <h2 className="text-2xl font-bold text-gray-900">Case Timeline</h2>
          <p className="text-gray-600">Track the progress and key milestones</p>
        </div>
      </div>

      {/* Progress Overview */}
      <div className="mb-8">
        <div className="flex items-center justify-between mb-3">
          <span className="text-sm font-medium text-gray-700">Overall Progress</span>
          <span className="text-sm font-bold text-blue-600">{getProgressPercentage()}%</span>
        </div>
        <div className="w-full bg-gray-200 rounded-full h-3">
          <div 
            className="bg-gradient-to-r from-blue-500 to-purple-600 h-3 rounded-full transition-all duration-1000 ease-out"
            style={{ width: `${getProgressPercentage()}%` }}
          ></div>
        </div>
        <div className="flex justify-between text-xs text-gray-500 mt-2">
          <span>{timelineEvents.filter(e => e.status === 'completed').length} completed</span>
          <span>{timelineEvents.filter(e => e.status === 'pending').length} in progress</span>
          <span>{timelineEvents.filter(e => e.status === 'upcoming').length} upcoming</span>
        </div>
      </div>

      {/* Filter Tabs */}
      <div className="flex flex-wrap gap-2 mb-6">
        {[
          { key: 'all', label: 'All Events', count: timelineEvents.length },
          { key: 'milestone', label: 'Milestones', count: timelineEvents.filter(e => e.type === 'milestone').length },
          { key: 'update', label: 'Updates', count: timelineEvents.filter(e => e.type === 'update').length },
          { key: 'deadline', label: 'Deadlines', count: timelineEvents.filter(e => e.type === 'deadline').length },
          { key: 'achievement', label: 'Achievements', count: timelineEvents.filter(e => e.type === 'achievement').length }
        ].map(filter => (
          <button
            key={filter.key}
            onClick={() => setSelectedFilter(filter.key)}
            className={`px-4 py-2 rounded-lg text-sm font-medium transition-colors ${
              selectedFilter === filter.key
                ? 'bg-blue-600 text-white'
                : 'bg-gray-100 text-gray-700 hover:bg-gray-200'
            }`}
          >
            {filter.label} ({filter.count})
          </button>
        ))}
      </div>

      {/* Timeline */}
      <div className="relative">
        {/* Timeline Line */}
        <div className="absolute left-6 top-0 bottom-0 w-0.5 bg-gray-200"></div>
        
        <div className="space-y-6">
          {filteredEvents.map((event, index) => {
            const isExpanded = expandedEvents.has(event.id);
            const isLast = index === filteredEvents.length - 1;
            
            return (
              <div key={event.id} className="relative">
                {/* Timeline Dot */}
                <div className={`absolute left-6 w-4 h-4 rounded-full border-2 transform -translate-x-1/2 -translate-y-1/2 ${
                  event.status === 'completed' ? 'bg-green-500 border-green-500' :
                  event.status === 'pending' ? 'bg-yellow-500 border-yellow-500' :
                  event.status === 'overdue' ? 'bg-red-500 border-red-500' :
                  'bg-blue-500 border-blue-500'
                }`}>
                  {event.status === 'completed' && (
                    <CheckCircle className="w-4 h-4 text-white" />
                  )}
                </div>

                {/* Event Content */}
                <div className={`ml-12 p-4 rounded-lg border transition-all duration-200 hover:shadow-md ${
                  getStatusColor(event.status)
                } ${isExpanded ? 'shadow-md' : ''}`}>
                  <div className="flex items-start justify-between">
                    <div className="flex items-start gap-3 flex-1">
                      <div className="flex-shrink-0 mt-1">
                        {getEventIcon(event.type, event.status)}
                      </div>
                      <div className="flex-1 min-w-0">
                        <div className="flex items-center gap-2 mb-1">
                          <h3 className="font-semibold text-gray-900">{event.title}</h3>
                          <span className={`px-2 py-1 text-xs font-medium rounded-full ${
                            event.status === 'completed' ? 'bg-green-100 text-green-800' :
                            event.status === 'pending' ? 'bg-yellow-100 text-yellow-800' :
                            event.status === 'overdue' ? 'bg-red-100 text-red-800' :
                            'bg-blue-100 text-blue-800'
                          }`}>
                            {getStatusText(event.status)}
                          </span>
                        </div>
                        
                        <p className="text-sm text-gray-600 mb-2">
                          {safeFormatDate(event.date)}
                        </p>
                        
                        {event.description && (
                          <p className="text-gray-700 mb-3">{event.description}</p>
                        )}

                        {/* Metadata */}
                        {event.metadata && (
                          <div className="space-y-2">
                            {event.metadata.participants && (
                              <div className="flex items-center gap-2 text-sm text-gray-600">
                                <Users className="h-4 w-4" />
                                <span>{event.metadata.participants.join(', ')}</span>
                              </div>
                            )}
                            {event.metadata.location && (
                              <div className="flex items-center gap-2 text-sm text-gray-600">
                                <MapPin className="h-4 w-4" />
                                <span>{event.metadata.location}</span>
                              </div>
                            )}
                            {event.metadata.amount && (
                              <div className="flex items-center gap-2 text-sm text-gray-600">
                                <DollarSign className="h-4 w-4" />
                                <span>${event.metadata.amount.toLocaleString()}</span>
                              </div>
                            )}
                          </div>
                        )}
                      </div>
                    </div>

                    {/* Expand/Collapse Button */}
                    <button
                      onClick={() => toggleEventExpansion(event.id)}
                      className="flex-shrink-0 p-1 hover:bg-gray-100 rounded transition-colors"
                    >
                      {isExpanded ? (
                        <ChevronDown className="h-4 w-4 text-gray-500" />
                      ) : (
                        <ChevronRight className="h-4 w-4 text-gray-500" />
                      )}
                    </button>
                  </div>

                  {/* Expanded Content */}
                  {isExpanded && (
                    <div className="mt-4 pt-4 border-t border-gray-200">
                      <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
                        <div>
                          <h4 className="font-medium text-gray-900 mb-2">Event Details</h4>
                          <div className="space-y-2 text-sm">
                            <div>
                              <span className="font-medium text-gray-700">Type:</span>
                              <span className="ml-2 text-gray-600 capitalize">{event.type}</span>
                            </div>
                            <div>
                              <span className="font-medium text-gray-700">Date:</span>
                              <span className="ml-2 text-gray-600">
                                {safeFormatFullDate(event.date)}
                              </span>
                            </div>
                            <div>
                              <span className="font-medium text-gray-700">Time:</span>
                              <span className="ml-2 text-gray-600">
                                {safeFormatTime(event.date)}
                              </span>
                            </div>
                          </div>
                        </div>
                        
                        {event.metadata && (
                          <div>
                            <h4 className="font-medium text-gray-900 mb-2">Additional Info</h4>
                            <div className="space-y-2 text-sm">
                              {event.metadata.documents && event.metadata.documents.length > 0 && (
                                <div>
                                  <span className="font-medium text-gray-700">Documents:</span>
                                  <div className="mt-1 space-y-1">
                                    {event.metadata.documents.map((doc, idx) => (
                                      <div key={idx} className="flex items-center gap-2 text-blue-600 hover:text-blue-800">
                                        <FileText className="h-3 w-3" />
                                        <span className="text-xs">{doc}</span>
                                      </div>
                                    ))}
                                  </div>
                                </div>
                              )}
                            </div>
                          </div>
                        )}
                      </div>
                    </div>
                  )}
                </div>
              </div>
            );
          })}
        </div>

        {/* End Cap */}
        <div className="absolute left-6 bottom-0 w-4 h-4 rounded-full bg-gray-300 transform -translate-x-1/2 translate-y-1/2"></div>
      </div>

      {/* Empty State */}
      {filteredEvents.length === 0 && (
        <div className="text-center py-12">
          <Clock className="h-12 w-12 text-gray-300 mx-auto mb-4" />
          <h3 className="text-lg font-medium text-gray-900 mb-2">No events found</h3>
          <p className="text-gray-500">Try selecting a different filter or check back later for updates.</p>
        </div>
      )}
    </div>
  );
};

export default CaseTimeline; 

CasperSecurity Mini