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.quebec/private_html/src/components/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Current File : /home/gositeme/domains/lavocat.quebec/private_html/src/components/CaseProgressTracker.tsx
import React, { useState, useEffect } from 'react';
import { useSession } from 'next-auth/react';
import { 
  CheckCircle, 
  Clock, 
  AlertTriangle, 
  Calendar,
  TrendingUp,
  Target,
  Award,
  Users,
  FileText,
  MessageSquare,
  BarChart3,
  Zap,
  Flag,
  Star,
  ArrowRight,
  ArrowLeft,
  Play,
  Pause,
  Square,
  Edit
} from 'lucide-react';
import { format } from 'date-fns';
import toast from 'react-hot-toast';

interface CaseMilestone {
  id: string;
  title: string;
  description?: string;
  status: 'PENDING' | 'IN_PROGRESS' | 'COMPLETED' | 'BLOCKED';
  dueDate?: string;
  completedDate?: string;
  assignedTo?: string;
  priority: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
  type: 'RESEARCH' | 'FILING' | 'HEARING' | 'SETTLEMENT' | 'TRIAL' | 'APPEAL' | 'CLOSURE';
  dependencies?: string[];
  estimatedHours?: number;
  actualHours?: number;
  notes?: string;
}

interface CaseProgress {
  caseId: string;
  overallProgress: number;
  currentPhase: string;
  estimatedCompletion: string;
  totalMilestones: number;
  completedMilestones: number;
  overdueMilestones: number;
  nextMilestone?: CaseMilestone;
  recentActivity: Array<{
    id: string;
    type: 'MILESTONE_COMPLETED' | 'MILESTONE_STARTED' | 'TASK_COMPLETED' | 'DOCUMENT_ADDED' | 'UPDATE_POSTED';
    title: string;
    description?: string;
    timestamp: string;
    user: {
      id: string;
      name: string;
      role: string;
    };
  }>;
  milestones: CaseMilestone[];
}

interface CaseProgressTrackerProps {
  caseId: string;
  caseTitle: string;
  onProgressUpdate?: () => void;
}

const CaseProgressTracker: React.FC<CaseProgressTrackerProps> = ({ 
  caseId, 
  caseTitle, 
  onProgressUpdate 
}) => {
  const { data: session } = useSession();
  const [progress, setProgress] = useState<CaseProgress | null>(null);
  const [loading, setLoading] = useState(true);
  const [showMilestoneForm, setShowMilestoneForm] = useState(false);
  const [selectedMilestone, setSelectedMilestone] = useState<CaseMilestone | null>(null);
  const [filterType, setFilterType] = useState<string>('all');
  const [filterStatus, setFilterStatus] = useState<string>('all');

  // Form state
  const [formData, setFormData] = useState({
    title: '',
    description: '',
    type: 'RESEARCH' as const,
    priority: 'MEDIUM' as const,
    dueDate: '',
    estimatedHours: '',
    assignedTo: '',
    notes: ''
  });

  useEffect(() => {
    if (caseId) {
      fetchProgress();
    }
  }, [caseId]);

  const fetchProgress = async () => {
    try {
      const response = await fetch(
      if (response.ok) {
        const data = await response.json();
        setProgress(data);
      }
    } catch (error) {
      toast.error('Failed to load progress data');
    } finally {
      setLoading(false);
    }
  };

  const handleCreateMilestone = async (e: React.FormEvent) => {
    e.preventDefault();
    
    try {
      const response = await fetch(`/api/cases/${caseId}/milestones
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          ...formData,
          estimatedHours: formData.estimatedHours ? parseFloat(formData.estimatedHours) : null,
        }),
      });

      if (response.ok) {
        toast.success('Milestone created successfully');
        setShowMilestoneForm(false);
        setFormData({
          title: '',
          description: '',
          type: 'RESEARCH',
          priority: 'MEDIUM',
          dueDate: '',
          estimatedHours: '',
          assignedTo: '',
          notes: ''
        });
        fetchProgress();
        onProgressUpdate?.();
      } else {
        const error = await response.json();
        toast.error(error.message || 'Failed to create milestone');
      }
    } catch (error) {
      toast.error('Failed to create milestone');
    }
  };

  const handleUpdateMilestone = async (milestoneId: string, updates: Partial<CaseMilestone>) => {
    try {
      const response = await fetch(`/api/cases/${caseId}/milestones/${milestoneId}
        method: 'PUT',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(updates),
      });

      if (response.ok) {
        toast.success('Milestone updated successfully');
        fetchProgress();
        onProgressUpdate?.();
      } else {
        const error = await response.json();
        toast.error(error.message || 'Failed to update milestone');
      }
    } catch (error) {
      toast.error('Failed to update milestone');
    }
  };

  const getStatusColor = (status: string) => {
    switch (status) {
      case 'COMPLETED': return 'bg-green-100 text-green-800 border-green-200';
      case 'IN_PROGRESS': return 'bg-blue-100 text-blue-800 border-blue-200';
      case 'PENDING': return 'bg-yellow-100 text-yellow-800 border-yellow-200';
      case 'BLOCKED': return 'bg-red-100 text-red-800 border-red-200';
      default: return 'bg-gray-100 text-gray-800 border-gray-200';
    }
  };

  const getStatusIcon = (status: string) => {
    switch (status) {
      case 'COMPLETED': return <CheckCircle className="h-4 w-4 text-green-500" />;
      case 'IN_PROGRESS': return <Play className="h-4 w-4 text-blue-500" />;
      case 'PENDING': return <Clock className="h-4 w-4 text-yellow-500" />;
      case 'BLOCKED': return <Pause className="h-4 w-4 text-red-500" />;
      default: return <Clock className="h-4 w-4 text-gray-400" />;
    }
  };

  const getTypeIcon = (type: string) => {
    switch (type) {
      case 'RESEARCH': return <FileText className="h-4 w-4" />;
      case 'FILING': return <FileText className="h-4 w-4" />;
      case 'HEARING': return <Users className="h-4 w-4" />;
      case 'SETTLEMENT': return <MessageSquare className="h-4 w-4" />;
      case 'TRIAL': return <Award className="h-4 w-4" />;
      case 'APPEAL': return <TrendingUp className="h-4 w-4" />;
      case 'CLOSURE': return <CheckCircle className="h-4 w-4" />;
      default: return <Target className="h-4 w-4" />;
    }
  };

  const getPriorityColor = (priority: string) => {
    switch (priority) {
      case 'CRITICAL': 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 getPhaseColor = (phase: string) => {
    switch (phase.toLowerCase()) {
      case 'initiation': return 'bg-blue-100 text-blue-800';
      case 'discovery': return 'bg-purple-100 text-purple-800';
      case 'pre-trial': return 'bg-orange-100 text-orange-800';
      case 'trial': return 'bg-red-100 text-red-800';
      case 'post-trial': return 'bg-green-100 text-green-800';
      case 'appeal': return 'bg-indigo-100 text-indigo-800';
      case 'closure': return 'bg-gray-100 text-gray-800';
      default: return 'bg-blue-100 text-blue-800';
    }
  };

  const filteredMilestones = progress?.milestones.filter(milestone => {
    if (filterType !== 'all' && milestone.type !== filterType) return false;
    if (filterStatus !== 'all' && milestone.status !== filterStatus) return false;
    return true;
  }) || [];

  if (loading) {
    return (
      <div className="bg-white rounded-lg shadow-sm border p-6">
        <div className="animate-pulse space-y-4">
          <div className="h-6 bg-gray-200 rounded w-1/3"></div>
          <div className="h-4 bg-gray-200 rounded w-1/2"></div>
          <div className="space-y-2">
            {[1, 2, 3].map(i => (
              <div key={i} className="h-16 bg-gray-200 rounded"></div>
            ))}
          </div>
        </div>
      </div>
    );
  }

  if (!progress) {
    return (
      <div className="bg-white rounded-lg shadow-sm border p-6">
        <div className="text-center py-8 text-gray-500">
          <Target className="h-12 w-12 mx-auto mb-4 text-gray-300" />
          <p className="text-lg font-medium">No progress data available</p>
          <p className="text-sm">Progress tracking will appear here once milestones are created</p>
        </div>
      </div>
    );
  }

  return (
    <div className="bg-white rounded-lg shadow-sm border">
      {/* Header */}
      <div className="p-6 border-b border-gray-200">
        <div className="flex items-center justify-between mb-4">
          <div>
            <h2 className="text-lg font-semibold text-gray-900 flex items-center">
              <TrendingUp className="h-5 w-5 mr-2 text-blue-600" />
              Case Progress
            </h2>
            <p className="text-sm text-gray-600">Track case milestones and overall progress</p>
          </div>
          <button
            onClick={() => setShowMilestoneForm(true)}
            className="inline-flex items-center px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
          >
            <Target className="h-4 w-4 mr-2" />
            Add Milestone
          </button>
        </div>

        {/* Progress Overview */}
        <div className="grid grid-cols-1 md:grid-cols-4 gap-4 mb-6">
          <div className="text-center">
            <div className="text-2xl font-bold text-blue-600">{progress.overallProgress}%</div>
            <div className="text-sm text-gray-600">Overall Progress</div>
          </div>
          <div className="text-center">
            <div className="text-2xl font-bold text-green-600">{progress.completedMilestones}</div>
            <div className="text-sm text-gray-600">Completed</div>
          </div>
          <div className="text-center">
            <div className="text-2xl font-bold text-yellow-600">{progress.totalMilestones - progress.completedMilestones}</div>
            <div className="text-sm text-gray-600">Remaining</div>
          </div>
          <div className="text-center">
            <div className="text-2xl font-bold text-red-600">{progress.overdueMilestones}</div>
            <div className="text-sm text-gray-600">Overdue</div>
          </div>
        </div>

        {/* Progress Bar */}
        <div className="mb-4">
          <div className="flex items-center justify-between mb-2">
            <span className="text-sm font-medium text-gray-700">Progress</span>
            <span className="text-sm text-gray-500">{progress.overallProgress}%</span>
          </div>
          <div className="w-full bg-gray-200 rounded-full h-2">
            <div 
              className="bg-gradient-to-r from-blue-500 to-green-500 h-2 rounded-full transition-all duration-300"
              style={{ width: `${progress.overallProgress}%
            ></div>
          </div>
        </div>

        {/* Current Phase */}
        <div className="flex items-center justify-between">
          <div>
            <span className="text-sm text-gray-600">Current Phase:</span>
            <span className={`ml-2 px-2 py-1 rounded-full text-xs font-medium ${getPhaseColor(progress.currentPhase)}
              {progress.currentPhase}
            </span>
          </div>
          <div className="text-right">
            <span className="text-sm text-gray-600">Estimated Completion:</span>
            <div className="text-sm font-medium text-gray-900">
              {format(new Date(progress.estimatedCompletion), 'MMM d, yyyy')}
            </div>
          </div>
        </div>
      </div>

      {/* Filters */}
      <div className="p-4 border-b border-gray-200 bg-gray-50">
        <div className="flex flex-wrap items-center gap-4">
          <select
            value={filterType}
            onChange={(e) => setFilterType(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"
          >
            <option value="all">All Types</option>
            <option value="RESEARCH">Research</option>
            <option value="FILING">Filing</option>
            <option value="HEARING">Hearing</option>
            <option value="SETTLEMENT">Settlement</option>
            <option value="TRIAL">Trial</option>
            <option value="APPEAL">Appeal</option>
            <option value="CLOSURE">Closure</option>
          </select>

          <select
            value={filterStatus}
            onChange={(e) => setFilterStatus(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"
          >
            <option value="all">All Status</option>
            <option value="PENDING">Pending</option>
            <option value="IN_PROGRESS">In Progress</option>
            <option value="COMPLETED">Completed</option>
            <option value="BLOCKED">Blocked</option>
          </select>
        </div>
      </div>

      {/* Milestones */}
      <div className="p-6">
        <h3 className="text-lg font-semibold text-gray-900 mb-4">Milestones</h3>
        
        {filteredMilestones.length === 0 ? (
          <div className="text-center py-8 text-gray-500">
            <Target className="h-12 w-12 mx-auto mb-4 text-gray-300" />
            <p className="text-lg font-medium">No milestones found</p>
            <p className="text-sm">Create your first milestone to track progress</p>
          </div>
        ) : (
          <div className="space-y-4">
            {filteredMilestones.map((milestone, index) => (
              <div
                key={milestone.id}
                className="border border-gray-200 rounded-lg p-4 hover:shadow-md transition-shadow"
              >
                <div className="flex items-start justify-between">
                  <div className="flex-1 min-w-0">
                    <div className="flex items-center gap-3 mb-2">
                      {getStatusIcon(milestone.status)}
                      <div className="flex items-center gap-2">
                        {getTypeIcon(milestone.type)}
                        <h4 className="font-medium text-gray-900">{milestone.title}</h4>
                      </div>
                      <span className={`text-xs px-2 py-1 rounded-full border ${getStatusColor(milestone.status)}
                        {milestone.status.replace('_', ' ')}
                      </span>
                      <span className={`text-xs px-2 py-1 rounded-full border ${getPriorityColor(milestone.priority)}
                        {milestone.priority}
                      </span>
                    </div>
                    
                    {milestone.description && (
                      <p className="text-sm text-gray-600 mb-3">{milestone.description}</p>
                    )}

                    <div className="flex items-center gap-4 text-sm text-gray-500">
                      {milestone.dueDate && (
                        <div className="flex items-center gap-1">
                          <Calendar className="h-4 w-4" />
                          <span className={new Date(milestone.dueDate) < new Date() && milestone.status !== 'COMPLETED' ? 'text-red-600 font-medium' : ''}>
                            {format(new Date(milestone.dueDate), 'MMM d, yyyy')}
                          </span>
                        </div>
                      )}

                      {milestone.estimatedHours && (
                        <div className="flex items-center gap-1">
                          <Clock className="h-4 w-4" />
                          <span>{milestone.estimatedHours}h estimated</span>
                        </div>
                      )}

                      {milestone.completedDate && (
                        <div className="flex items-center gap-1">
                          <CheckCircle className="h-4 w-4 text-green-500" />
                          <span>Completed {format(new Date(milestone.completedDate), 'MMM d, yyyy')}</span>
                        </div>
                      )}
                    </div>
                  </div>

                  <div className="flex items-center gap-2 ml-4">
                    {milestone.status === 'PENDING' && (
                      <button
                        onClick={() => handleUpdateMilestone(milestone.id, { status: 'IN_PROGRESS' })}
                        className="p-2 text-blue-400 hover:text-blue-600 hover:bg-blue-50 rounded-lg transition-colors"
                        title="Start milestone"
                      >
                        <Play className="h-4 w-4" />
                      </button>
                    )}
                    
                    {milestone.status === 'IN_PROGRESS' && (
                      <button
                        onClick={() => handleUpdateMilestone(milestone.id, { status: 'COMPLETED', completedDate: new Date().toISOString() })}
                        className="p-2 text-green-400 hover:text-green-600 hover:bg-green-50 rounded-lg transition-colors"
                        title="Mark as completed"
                      >
                        <CheckCircle className="h-4 w-4" />
                      </button>
                    )}
                    
                    <button
                      onClick={() => setSelectedMilestone(milestone)}
                      className="p-2 text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded-lg transition-colors"
                      title="Edit milestone"
                    >
                      <Edit className="h-4 w-4" />
                    </button>
                  </div>
                </div>
              </div>
            ))}
          </div>
        )}
      </div>

      {/* Recent Activity */}
      {progress.recentActivity.length > 0 && (
        <div className="p-6 border-t border-gray-200">
          <h3 className="text-lg font-semibold text-gray-900 mb-4">Recent Activity</h3>
          <div className="space-y-3">
            {progress.recentActivity.slice(0, 5).map((activity) => (
              <div key={activity.id} className="flex items-start gap-3">
                <div className="w-2 h-2 bg-blue-500 rounded-full mt-2"></div>
                <div className="flex-1 min-w-0">
                  <p className="text-sm text-gray-900">{activity.title}</p>
                  {activity.description && (
                    <p className="text-xs text-gray-600 mt-1">{activity.description}</p>
                  )}
                  <div className="flex items-center gap-2 mt-1 text-xs text-gray-500">
                    <span>{activity.user.name}</span>
                    <span>•</span>
                    <span>{format(new Date(activity.timestamp), 'MMM d, h:mm a')}</span>
                  </div>
                </div>
              </div>
            ))}
          </div>
        </div>
      )}

      {/* Create Milestone Modal */}
      {showMilestoneForm && (
        <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50">
          <div className="bg-white rounded-lg shadow-xl w-full max-w-2xl max-h-[90vh] overflow-y-auto">
            <div className="p-6 border-b border-gray-200">
              <h3 className="text-lg font-semibold text-gray-900">Create New Milestone</h3>
              <p className="text-sm text-gray-600 mt-1">Add a new milestone to track case progress</p>
            </div>

            <form onSubmit={handleCreateMilestone} className="p-6 space-y-6">
              <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
                <div className="md:col-span-2">
                  <label className="block text-sm font-medium text-gray-700 mb-2">
                    Milestone Title *
                  </label>
                  <input
                    type="text"
                    value={formData.title}
                    onChange={(e) => setFormData({ ...formData, title: e.target.value })}
                    className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
                    required
                  />
                </div>

                <div className="md:col-span-2">
                  <label className="block text-sm font-medium text-gray-700 mb-2">
                    Description
                  </label>
                  <textarea
                    value={formData.description}
                    onChange={(e) => setFormData({ ...formData, description: e.target.value })}
                    rows={3}
                    className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
                  />
                </div>

                <div>
                  <label className="block text-sm font-medium text-gray-700 mb-2">
                    Type *
                  </label>
                  <select
                    value={formData.type}
                    onChange={(e) => setFormData({ ...formData, type: e.target.value as any })}
                    className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
                    required
                  >
                    <option value="RESEARCH">Research</option>
                    <option value="FILING">Filing</option>
                    <option value="HEARING">Hearing</option>
                    <option value="SETTLEMENT">Settlement</option>
                    <option value="TRIAL">Trial</option>
                    <option value="APPEAL">Appeal</option>
                    <option value="CLOSURE">Closure</option>
                  </select>
                </div>

                <div>
                  <label className="block text-sm font-medium text-gray-700 mb-2">
                    Priority
                  </label>
                  <select
                    value={formData.priority}
                    onChange={(e) => setFormData({ ...formData, priority: e.target.value as any })}
                    className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
                  >
                    <option value="LOW">Low</option>
                    <option value="MEDIUM">Medium</option>
                    <option value="HIGH">High</option>
                    <option value="CRITICAL">Critical</option>
                  </select>
                </div>

                <div>
                  <label className="block text-sm font-medium text-gray-700 mb-2">
                    Due Date
                  </label>
                  <input
                    type="date"
                    value={formData.dueDate}
                    onChange={(e) => setFormData({ ...formData, dueDate: e.target.value })}
                    className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
                  />
                </div>

                <div>
                  <label className="block text-sm font-medium text-gray-700 mb-2">
                    Estimated Hours
                  </label>
                  <input
                    type="number"
                    step="0.5"
                    value={formData.estimatedHours}
                    onChange={(e) => setFormData({ ...formData, estimatedHours: e.target.value })}
                    className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
                  />
                </div>

                <div className="md:col-span-2">
                  <label className="block text-sm font-medium text-gray-700 mb-2">
                    Notes
                  </label>
                  <textarea
                    value={formData.notes}
                    onChange={(e) => setFormData({ ...formData, notes: e.target.value })}
                    rows={2}
                    className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
                  />
                </div>
              </div>

              <div className="flex items-center justify-end gap-3 pt-4 border-t border-gray-200">
                <button
                  type="button"
                  onClick={() => setShowMilestoneForm(false)}
                  className="px-4 py-2 text-gray-700 bg-gray-100 rounded-lg hover:bg-gray-200 transition-colors"
                >
                  Cancel
                </button>
                <button
                  type="submit"
                  className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
                >
                  Create Milestone
                </button>
              </div>
            </form>
          </div>
        </div>
      )}
    </div>
  );
};

export default CaseProgressTracker; 

CasperSecurity Mini