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/pages/admin/registrations/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Current File : /home/gositeme/domains/lavocat.ca/private_html/src/pages/admin/registrations/[id].tsx
import { useState, useEffect, useRef } from 'react';
import { useRouter } from 'next/router';
import Link from 'next/link';
import RegistrationForm from '@/components/RegistrationForm';
import { useSession } from 'next-auth/react';
import LayoutWithSidebar from '@/components/LayoutWithSidebar';
import { NextPage } from 'next';
import { format } from 'date-fns';
import { motion, AnimatePresence } from 'framer-motion';
import DocumentViewer from '@/components/DocumentViewer';
import DocumentManager from '@/components/DocumentManager';
import mammoth from 'mammoth'; // For .docx
import * as XLSX from 'xlsx'; // For .xlsx

interface Document {
  id: string;
  name: string;
  url: string;
  type: string;
  createdAt: string;
}

interface Registration {
  id: string;
  firstName: string;
  lastName: string;
  email: string;
  phone: string;
  birthDate: string;
  detaineeInfo: {
    name: string;
    facility: string;
    inmateId: string;
    incarcerationDate: string;
    expectedReleaseDate?: string | null;
  } | null;
  relationship: string;
  preferredLanguage: 'fr' | 'en';
  preferredContactMethod: 'email' | 'phone' | 'mail';
  message?: string | null;
  status: 'PENDING' | 'APPROVED' | 'REJECTED' | 'MISSING_DOCUMENTS' | 'DOCUMENTS_UNDER_REVIEW' | 'ADDITIONAL_INFO_NEEDED' | 'VERIFICATION_IN_PROGRESS' | 'LAWYER_VERIFICATION' | 'FACILITY_VERIFICATION' | 'DOCUMENTS_EXPIRED' | 'DOCUMENTS_INCOMPLETE' | 'INFORMATION_MISMATCH' | 'PENDING_PAYMENT' | 'PAYMENT_RECEIVED' | 'PENDING_LAWYER_APPROVAL' | 'PENDING_FACILITY_APPROVAL' | 'ON_HOLD' | 'ESCALATED' | 'FINAL_REVIEW' | 'COMPLETED' | 'WebAd';
  createdAt: string;
  updatedAt: string;
  userId?: string | null;
  address?: {
    street: string;
    city: string;
    state: string;
    postalCode: string;
    country: string;
  } | null;
  documents?: Document[];
  reasonForJoining?: string | null;
  urgentNeeds?: string | null;
}

type OpenPreview = {
  doc: any;
  window: Window | null;
  isMaximized?: boolean;
  position?: { x: number; y: number };
  zIndex?: number;
  id?: string;
};

type DraggableModalProps = {
  doc: any; // You can replace 'any' with 'Document' if the structure matches
  isMaximized?: boolean;
  position?: { x: number; y: number };
  zIndex?: number;
  onClose: () => void;
  onMaximize: () => void;
  onDrag: (pos: { x: number; y: number }) => void;
  onFocus: () => void;
};

const translations = {
  en: {
    back: 'Back to dashboard',
    status: {
      pending: 'Pending',
      approved: 'Approved',
      rejected: 'Rejected',
    },
    updateStatusError: 'Error updating status',
    updateRegistrationError: 'Error updating registration',
    loadRegistrationError: 'Error loading registration',
    document: {
      documents: 'Documents',
      add: 'Add Document',
      uploading: 'Uploading...',
      uploadError: 'Error uploading document',
      delete: 'Delete Document',
      deleteError: 'Error deleting document',
      unknownUploadError: 'Unknown error uploading document',
    },
    close: 'Close',
  },
  fr: {
    back: 'Retour au tableau de bord',
    status: {
      pending: 'En attente',
      approved: 'Approuvé',
      rejected: 'Rejeté',
    },
    updateStatusError: 'Erreur lors de la mise à jour du statut',
    updateRegistrationError: 'Erreur lors de la mise à jour de la demande',
    loadRegistrationError: 'Erreur lors du chargement de la demande',
    document: {
      documents: 'Documents',
      add: 'Ajouter un document',
      uploading: 'Téléchargement...',
      uploadError: 'Erreur lors du téléchargement du document',
      delete: 'Supprimer le document',
      deleteError: 'Erreur lors de la suppression du document',
      unknownUploadError: 'Erreur inconnue lors du téléchargement du document',
    },
    close: 'Fermer',
  }
};

const AdminRegistrationDetail: NextPage = () => {
  const router = useRouter();
  const { id } = router.query;
  const { data: session, status } = useSession();
  const [registration, setRegistration] = useState<Registration | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState('');
  const [isUpdating, setIsUpdating] = useState(false);
  const [isUpdatingStatus, setIsUpdatingStatus] = useState(false);
  const [selectedDocument, setSelectedDocument] = useState<{
    url: string;
    type: string;
    name: string;
  } | null>(null);
  const [isUploading, setIsUploading] = useState(false);
  const [uploadError, setUploadError] = useState('');
  const [isDeleting, setIsDeleting] = useState<string | null>(null);
  const [previewDoc, setPreviewDoc] = useState<{ url: string; name: string } | null>(null);
  const [isMaximized, setIsMaximized] = useState(false);
  const [openPreviews, setOpenPreviews] = useState<OpenPreview[]>([]); // [{doc, isMaximized, position, zIndex, id}]
  const zIndexCounter = useRef(1000);
  const [isEditing, setIsEditing] = useState(false);

  // Move upload handler to top-level for DocumentManager
  const onUploadFiles = async (files: File[]) => {
    setIsUploading(true);
    setUploadError('');
    try {
      for (let i = 0; i < files.length; i++) {
        const file = files[i];
        const formData = new FormData();
        formData.append('file', file);
        const uploadResponse = await fetch(`/api/admin/registrations/${id}/documents`, {
          method: 'POST',
          body: formData,
        });
        const contentType = uploadResponse.headers.get('content-type');
        let document;
        if (contentType && contentType.includes('application/json')) {
          document = await uploadResponse.json();
        } else {
          const text = await uploadResponse.text();
          throw new Error(text || 'Unknown error uploading document');
        }
        setRegistration(prev => prev ? {
          ...prev,
          documents: [...(prev.documents || []), document],
        } : null);
      }
    } catch (err) {
      setUploadError(err instanceof Error ? err.message : 'Error uploading document');
      console.error(err);
    } finally {
      setIsUploading(false);
    }
  };

  // Move delete handler to top-level for DocumentManager and document list
  const handleDeleteDocument = async (documentId: string) => {
    if (!id || !documentId) return;
    setIsDeleting(documentId);
    try {
      const response = await fetch(`/api/admin/registrations/${id}/documents/${documentId}`, {
        method: 'DELETE',
      });
      if (!response.ok) {
        throw new Error('Error deleting document');
      }
      setRegistration(prev => {
        if (!prev) return null;
        return {
          ...prev,
          documents: prev.documents?.filter(doc => doc.id !== documentId) || [],
        };
      });
    } catch (error) {
      console.error('Error deleting document:', error);
      setUploadError('Error deleting document');
    } finally {
      setIsDeleting(null);
    }
  };

  // Check if we're in edit mode from URL query
  useEffect(() => {
    if (router.query.edit === 'true') {
      setIsEditing(true);
    }
  }, [router.query.edit]);

  useEffect(() => {
    if (id) {
      fetch(`/api/admin/registrations/${id}`)
        .then(res => res.json())
        .then(data => setRegistration(data))
        .catch(err => setError('Failed to load registration'));
    }
  }, [id]);

  const getStatusLabel = (status: string) => {
    const { locale } = router;
    // Normalize status to uppercase for consistent matching
    const normalizedStatus = status.toUpperCase();
    const statusMap: { [key: string]: { en: string; fr: string } } = {
      PENDING: {
        en: 'Pending',
        fr: 'En attente'
      },
      MISSING_DOCUMENTS: {
        en: 'Missing Documents',
        fr: 'Documents manquants'
      },
      DOCUMENTS_UNDER_REVIEW: {
        en: 'Documents Under Review',
        fr: 'Documents en révision'
      },
      ADDITIONAL_INFO_NEEDED: {
        en: 'Additional Info Needed',
        fr: 'Informations supplémentaires requises'
      },
      VERIFICATION_IN_PROGRESS: {
        en: 'Verification In Progress',
        fr: 'Vérification en cours'
      },
      LAWYER_VERIFICATION: {
        en: 'Lawyer Verification',
        fr: 'Vérification par l\'avocat'
      },
      FACILITY_VERIFICATION: {
        en: 'Facility Verification',
        fr: 'Vérification par l\'établissement'
      },
      DOCUMENTS_EXPIRED: {
        en: 'Documents Expired',
        fr: 'Documents expirés'
      },
      DOCUMENTS_INCOMPLETE: {
        en: 'Documents Incomplete',
        fr: 'Documents incomplets'
      },
      INFORMATION_MISMATCH: {
        en: 'Information Mismatch',
        fr: 'Incohérence d\'informations'
      },
      PENDING_PAYMENT: {
        en: 'Pending Payment',
        fr: 'Paiement en attente'
      },
      PAYMENT_RECEIVED: {
        en: 'Payment Received',
        fr: 'Paiement reçu'
      },
      PENDING_LAWYER_APPROVAL: {
        en: 'Pending Lawyer Approval',
        fr: 'En attente de l\'approbation de l\'avocat'
      },
      PENDING_FACILITY_APPROVAL: {
        en: 'Pending Facility Approval',
        fr: 'En attente de l\'approbation de l\'établissement'
      },
      ON_HOLD: {
        en: 'On Hold',
        fr: 'En attente'
      },
      ESCALATED: {
        en: 'Escalated',
        fr: 'Escaladé'
      },
      FINAL_REVIEW: {
        en: 'Final Review',
        fr: 'Révision finale'
      },
      APPROVED: {
        en: 'Approved',
        fr: 'Approuvé'
      },
      REJECTED: {
        en: 'Rejected',
        fr: 'Rejeté'
      },
      COMPLETED: {
        en: 'Completed',
        fr: 'Terminé'
      },
      WebAd: {
        en: 'Web Ad',
        fr: 'Publicité web'
      }
    };
    return statusMap[normalizedStatus]?.[locale as 'en' | 'fr'] || status;
  };

  const handleStatusChange = async (newStatus: string) => {
    if (!registration) return;
    setIsUpdatingStatus(true);
    try {
      const response = await fetch(`/api/admin/registrations/${registration.id}/status`, {
        method: 'PATCH',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ status: newStatus }),
      });
      
      if (!response.ok) {
        let errorMessage = 'Failed to update status';
        try {
          const errorData = await response.json();
          errorMessage = errorData.error || errorData.message || errorMessage;
        } catch (parseError) {
          console.warn('Could not parse error response:', parseError);
          errorMessage = `Server returned ${response.status}: ${response.statusText}`;
        }
        throw new Error(errorMessage);
      }
      
      const updatedRegistration = await response.json();
      setRegistration(updatedRegistration);
      
      // Clear any previous error
      setError('');
      
    } catch (error) {
      console.error('Error updating status:', error);
      const errorMessage = error instanceof Error ? error.message : 'Failed to update status';
      setError(errorMessage);
    } finally {
      setIsUpdatingStatus(false);
    }
  };

  const handleSave = async (formData: any) => {
    try {
      const response = await fetch(`/api/admin/registrations/${id}`, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(formData),
      });
      if (!response.ok) {
        const errorText = await response.text();
        console.error('Error response:', response.status, errorText);
        throw new Error('Failed to update registration');
      }
      router.push('/admin');
    } catch (error) {
      console.error('Error updating registration:', error);
    }
  };

  const getStatusDescription = (status: string) => {
    const { locale } = router;
    const descriptionMap: { [key: string]: { en: string; fr: string } } = {
      PENDING: {
        en: 'The registration is pending initial review.',
        fr: 'L\'inscription est en attente de révision initiale.'
      },
      MISSING_DOCUMENTS: {
        en: 'Required documents are missing from the registration.',
        fr: 'Des documents requis sont manquants dans l\'inscription.'
      },
      DOCUMENTS_UNDER_REVIEW: {
        en: 'Submitted documents are currently being reviewed.',
        fr: 'Les documents soumis sont actuellement en cours d\'examen.'
      },
      ADDITIONAL_INFO_NEEDED: {
        en: 'More information is required to proceed with the registration.',
        fr: 'Des informations supplémentaires sont nécessaires pour poursuivre l\'inscription.'
      },
      VERIFICATION_IN_PROGRESS: {
        en: 'The registration is currently being verified.',
        fr: 'L\'inscription est actuellement en cours de vérification.'
      },
      LAWYER_VERIFICATION: {
        en: 'Documents are being verified by a lawyer.',
        fr: 'Les documents sont en cours de vérification par un avocat.'
      },
      FACILITY_VERIFICATION: {
        en: 'Documents are being verified by the facility.',
        fr: 'Les documents sont en cours de vérification par l\'établissement.'
      },
      DOCUMENTS_EXPIRED: {
        en: 'One or more submitted documents have expired.',
        fr: 'Un ou plusieurs documents soumis ont expiré.'
      },
      DOCUMENTS_INCOMPLETE: {
        en: 'Submitted documents are incomplete or invalid.',
        fr: 'Les documents soumis sont incomplets ou invalides.'
      },
      INFORMATION_MISMATCH: {
        en: 'There is a mismatch in the provided information.',
        fr: 'Il y a une incohérence dans les informations fournies.'
      },
      PENDING_PAYMENT: {
        en: 'Waiting for payment to be processed.',
        fr: 'En attente du traitement du paiement.'
      },
      PAYMENT_RECEIVED: {
        en: 'Payment has been successfully received.',
        fr: 'Le paiement a été reçu avec succès.'
      },
      PENDING_LAWYER_APPROVAL: {
        en: 'Registration is waiting for lawyer approval.',
        fr: 'L\'inscription est en attente de l\'approbation de l\'avocat.'
      },
      PENDING_FACILITY_APPROVAL: {
        en: 'Registration is waiting for facility approval.',
        fr: 'L\'inscription est en attente de l\'approbation de l\'établissement.'
      },
      ON_HOLD: {
        en: 'The registration is temporarily on hold.',
        fr: 'L\'inscription est temporairement en attente.'
      },
      ESCALATED: {
        en: 'The registration requires special attention.',
        fr: 'L\'inscription nécessite une attention particulière.'
      },
      FINAL_REVIEW: {
        en: 'The registration is in its final review stage.',
        fr: 'L\'inscription est dans sa phase finale d\'examen.'
      },
      APPROVED: {
        en: 'The registration has been approved and is active.',
        fr: 'L\'inscription a été approuvée et est active.'
      },
      REJECTED: {
        en: 'The registration has been rejected and cannot proceed.',
        fr: 'L\'inscription a été rejetée et ne peut pas continuer.'
      },
      COMPLETED: {
        en: 'The registration process has been fully completed.',
        fr: 'Le processus d\'inscription est entièrement terminé.'
      },
      WebAd: {
        en: 'Registration from web advertisement.',
        fr: 'Inscription provenant d\'une publicité web.'
      }
    };
    return descriptionMap[status]?.[locale as 'en' | 'fr'] || 
      (locale === 'fr' ? 'Le statut de l\'inscription est en cours de traitement.' : 'The registration status is being processed.');
  };

  // Update openPreview to prevent duplicate modals and bring to front if already open
  const openPreview = (doc: any) => {
    setOpenPreviews(prev => {
      const existing = prev.find(w => w.doc.id === doc.id);
      if (existing) {
        // Bring to front
        return prev.map(w => w.doc.id === doc.id ? { ...w, zIndex: ++zIndexCounter.current } : w);
      }
      // Open new modal
      return [
        ...prev,
        {
          doc,
          window: null,
          isMaximized: false,
          position: { x: 100 + prev.length * 40, y: 100 + prev.length * 40 },
          zIndex: ++zIndexCounter.current,
          id: doc.id,
        },
      ];
    });
  };

  // Helper to close a preview window
  const closePreview = (id: string) => setOpenPreviews(prev => prev.filter(w => w.id !== id));

  // Helper to toggle maximize
  const toggleMaximize = (id: string) => setOpenPreviews(prev => prev.map(w => w.id === id ? { ...w, isMaximized: !w.isMaximized } : w));

  // Helper to bring to front
  const bringToFront = (id: string) => setOpenPreviews(prev => prev.map(w => w.id === id ? { ...w, zIndex: ++zIndexCounter.current } : w));

  // Helper to update position
  const updatePosition = (id: string, pos: { x: number; y: number }) => setOpenPreviews(prev => prev.map(w => w.id === id ? { ...w, position: pos } : w));

  // Ensure documents are mapped to FileItem structure for DocumentManager
  const documentFiles = (registration?.documents || []).map((doc: any) => ({
    id: doc.id,
    name: doc.name,
    url: doc.url,
    type: doc.type || '',
  }));

  if (!registration) {
    return <div>Loading...</div>;
  }

  return (
    <LayoutWithSidebar>
      <div className="p-6">
        <div className="flex justify-between items-center mb-6">
          <h1 className="text-2xl font-bold">
            {isEditing ? 'Edit Registration' : 'Registration Details'}
          </h1>
          <div className="flex gap-2">
            {isEditing ? (
              <>
                <button
                  onClick={() => setIsEditing(false)}
                  className="inline-flex items-center px-4 py-2 bg-gray-600 text-white rounded-lg hover:bg-gray-700 transition-colors shadow-md hover:shadow-lg"
                >
                  <svg className="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                    <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
                  </svg>
                  Cancel Edit
                </button>
                <button
                  onClick={() => router.push('/admin/dashboard')}
                  className="inline-flex items-center px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors shadow-md hover:shadow-lg"
                >
                  <svg className="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                    <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 19l-7-7m0 0l7-7m-7 7h18" />
                  </svg>
                  Back to Dashboard
                </button>
              </>
            ) : (
              <>
                <button
                  onClick={() => setIsEditing(true)}
                  className="inline-flex items-center px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors shadow-md hover:shadow-lg"
                >
                  <svg className="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                    <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
                  </svg>
                  Edit Registration
                </button>
                <button
                  onClick={() => router.push('/admin/dashboard')}
                  className="inline-flex items-center px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors shadow-md hover:shadow-lg"
                >
                  <svg className="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                    <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 19l-7-7m0 0l7-7m-7 7h18" />
                  </svg>
                  Back to Dashboard
                </button>
              </>
            )}
          </div>
        </div>

        {/* Status Update Section */}
        <div className="bg-white rounded-lg shadow-lg p-6 mb-6">
          <div className="flex items-center justify-between">
            <div>
              <h2 className="text-lg font-semibold mb-2">Current Status</h2>
              <span className={`px-3 py-1 rounded-full text-sm font-medium ${
                registration.status.toUpperCase() === 'APPROVED' ? 'bg-green-100 text-green-800' :
                registration.status.toUpperCase() === 'REJECTED' ? 'bg-red-100 text-red-800' :
                registration.status.toUpperCase() === 'COMPLETED' ? 'bg-green-100 text-green-800' :
                registration.status.toUpperCase() === 'MISSING_DOCUMENTS' ? 'bg-orange-100 text-orange-800' :
                registration.status.toUpperCase() === 'DOCUMENTS_UNDER_REVIEW' ? 'bg-blue-100 text-blue-800' :
                registration.status.toUpperCase() === 'ADDITIONAL_INFO_NEEDED' ? 'bg-yellow-100 text-yellow-800' :
                registration.status.toUpperCase() === 'VERIFICATION_IN_PROGRESS' ? 'bg-purple-100 text-purple-800' :
                registration.status.toUpperCase() === 'LAWYER_VERIFICATION' ? 'bg-indigo-100 text-indigo-800' :
                registration.status.toUpperCase() === 'FACILITY_VERIFICATION' ? 'bg-indigo-100 text-indigo-800' :
                registration.status.toUpperCase() === 'DOCUMENTS_EXPIRED' ? 'bg-red-100 text-red-800' :
                registration.status.toUpperCase() === 'DOCUMENTS_INCOMPLETE' ? 'bg-orange-100 text-orange-800' :
                registration.status.toUpperCase() === 'INFORMATION_MISMATCH' ? 'bg-red-100 text-red-800' :
                registration.status.toUpperCase() === 'PENDING_PAYMENT' ? 'bg-yellow-100 text-yellow-800' :
                registration.status.toUpperCase() === 'PAYMENT_RECEIVED' ? 'bg-green-100 text-green-800' :
                registration.status.toUpperCase() === 'PENDING_LAWYER_APPROVAL' ? 'bg-blue-100 text-blue-800' :
                registration.status.toUpperCase() === 'PENDING_FACILITY_APPROVAL' ? 'bg-blue-100 text-blue-800' :
                registration.status.toUpperCase() === 'ON_HOLD' ? 'bg-gray-100 text-gray-800' :
                registration.status.toUpperCase() === 'ESCALATED' ? 'bg-red-100 text-red-800' :
                registration.status.toUpperCase() === 'FINAL_REVIEW' ? 'bg-purple-100 text-purple-800' :
                registration.status === 'WebAd' ? 'bg-pink-100 text-pink-800' :
                'bg-yellow-100 text-yellow-800'
              }`}>
                {getStatusLabel(registration.status)}
              </span>
              <p className="text-sm text-gray-600 mt-1">
                {getStatusDescription(registration.status)}
              </p>
            </div>
            <div className="flex items-center gap-4">
              <select
                value={registration.status}
                onChange={(e) => handleStatusChange(e.target.value)}
                disabled={isUpdatingStatus}
                className="px-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent"
              >
                <option value="PENDING">{getStatusLabel('PENDING')}</option>
                <option value="MISSING_DOCUMENTS">{getStatusLabel('MISSING_DOCUMENTS')}</option>
                <option value="DOCUMENTS_UNDER_REVIEW">{getStatusLabel('DOCUMENTS_UNDER_REVIEW')}</option>
                <option value="ADDITIONAL_INFO_NEEDED">{getStatusLabel('ADDITIONAL_INFO_NEEDED')}</option>
                <option value="VERIFICATION_IN_PROGRESS">{getStatusLabel('VERIFICATION_IN_PROGRESS')}</option>
                <option value="LAWYER_VERIFICATION">{getStatusLabel('LAWYER_VERIFICATION')}</option>
                <option value="FACILITY_VERIFICATION">{getStatusLabel('FACILITY_VERIFICATION')}</option>
                <option value="DOCUMENTS_EXPIRED">{getStatusLabel('DOCUMENTS_EXPIRED')}</option>
                <option value="DOCUMENTS_INCOMPLETE">{getStatusLabel('DOCUMENTS_INCOMPLETE')}</option>
                <option value="INFORMATION_MISMATCH">{getStatusLabel('INFORMATION_MISMATCH')}</option>
                <option value="PENDING_PAYMENT">{getStatusLabel('PENDING_PAYMENT')}</option>
                <option value="PAYMENT_RECEIVED">{getStatusLabel('PAYMENT_RECEIVED')}</option>
                <option value="PENDING_LAWYER_APPROVAL">{getStatusLabel('PENDING_LAWYER_APPROVAL')}</option>
                <option value="PENDING_FACILITY_APPROVAL">{getStatusLabel('PENDING_FACILITY_APPROVAL')}</option>
                <option value="ON_HOLD">{getStatusLabel('ON_HOLD')}</option>
                <option value="ESCALATED">{getStatusLabel('ESCALATED')}</option>
                <option value="FINAL_REVIEW">{getStatusLabel('FINAL_REVIEW')}</option>
                <option value="APPROVED">{getStatusLabel('APPROVED')}</option>
                <option value="REJECTED">{getStatusLabel('REJECTED')}</option>
                <option value="COMPLETED">{getStatusLabel('COMPLETED')}</option>
                <option value="WebAd">{getStatusLabel('WebAd')}</option>
              </select>
              {isUpdatingStatus && (
                <div className="animate-spin rounded-full h-5 w-5 border-t-2 border-b-2 border-blue-500"></div>
              )}
            </div>
          </div>
        </div>

        {error && <div className="text-red-500 mb-4">{error}</div>}
        
        {isEditing ? (
          <RegistrationForm
            initialData={registration}
            initialValues={registration}
            isEditing={true}
            onSave={handleSave}
            isAdmin={true}
            mode="admin"
            isFrench={registration?.preferredLanguage === 'fr'}
          />
        ) : (
          <div className="bg-white rounded-lg shadow-lg p-6 mb-6">
            <h2 className="text-xl font-bold mb-4">Registration Information</h2>
            <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
              <div>
                <h3 className="font-semibold text-gray-700 mb-2">Personal Information</h3>
                <div className="space-y-2">
                  <p><span className="font-medium">Name:</span> {registration.firstName} {registration.lastName}</p>
                  <p><span className="font-medium">Email:</span> {registration.email}</p>
                  <p><span className="font-medium">Phone:</span> {registration.phone}</p>
                  <p><span className="font-medium">Birth Date:</span> {registration.birthDate ? new Date(registration.birthDate).toLocaleDateString() : 'Not provided'}</p>
                  <p><span className="font-medium">Relationship:</span> {registration.relationship}</p>
                  <p><span className="font-medium">Preferred Language:</span> {registration.preferredLanguage === 'fr' ? 'French' : 'English'}</p>
                  <p><span className="font-medium">Preferred Contact:</span> {registration.preferredContactMethod}</p>
                </div>
              </div>
              
              <div>
                <h3 className="font-semibold text-gray-700 mb-2">Address</h3>
                {registration.address ? (
                  <div className="space-y-2">
                    <p><span className="font-medium">Street:</span> {registration.address.street}</p>
                    <p><span className="font-medium">City:</span> {registration.address.city}</p>
                    <p><span className="font-medium">State/Province:</span> {registration.address.state}</p>
                    <p><span className="font-medium">Postal Code:</span> {registration.address.postalCode}</p>
                    <p><span className="font-medium">Country:</span> {registration.address.country}</p>
                  </div>
                ) : (
                  <p className="text-gray-500">No address provided</p>
                )}
              </div>
              
              {registration.detaineeInfo && (
                <div>
                  <h3 className="font-semibold text-gray-700 mb-2">Detainee Information</h3>
                  <div className="space-y-2">
                    <p><span className="font-medium">Name:</span> {registration.detaineeInfo.name}</p>
                    <p><span className="font-medium">Facility:</span> {registration.detaineeInfo.facility}</p>
                    <p><span className="font-medium">Inmate ID:</span> {registration.detaineeInfo.inmateId}</p>
                    <p><span className="font-medium">Incarceration Date:</span> {registration.detaineeInfo.incarcerationDate ? new Date(registration.detaineeInfo.incarcerationDate).toLocaleDateString() : 'Not provided'}</p>
                    {registration.detaineeInfo.expectedReleaseDate && (
                      <p><span className="font-medium">Expected Release:</span> {new Date(registration.detaineeInfo.expectedReleaseDate).toLocaleDateString()}</p>
                    )}
                  </div>
                </div>
              )}
              
              <div>
                <h3 className="font-semibold text-gray-700 mb-2">Additional Information</h3>
                <div className="space-y-2">
                  {registration.reasonForJoining && (
                    <div className="mb-2">
                      <span className="font-medium">Reason for Joining:</span> {registration.reasonForJoining}
                    </div>
                  )}
                  {registration.urgentNeeds && (
                    <div className="mb-2">
                      <span className="font-medium">Urgent Needs:</span> {registration.urgentNeeds}
                    </div>
                  )}
                  {registration.message && (
                    <p><span className="font-medium">Message:</span> {registration.message}</p>
                  )}
                  <p><span className="font-medium">Created:</span> {new Date(registration.createdAt).toLocaleDateString()}</p>
                  <p><span className="font-medium">Last Updated:</span> {new Date(registration.updatedAt).toLocaleDateString()}</p>
                </div>
              </div>
            </div>
          </div>
        )}

        {/* Documents (user-style) */}
        <div className="bg-white rounded-xl shadow-lg p-6 mt-8">
          <h2 className="text-2xl font-bold mb-6">Documents</h2>
          {/* Document Upload - Drag & Drop */}
          <div
            className={`relative flex flex-col items-center justify-center p-8 mb-8 rounded-2xl border-2 border-dashed transition-all duration-200 ${isUploading ? 'border-blue-500 bg-blue-50/60' : 'border-gray-300 bg-white/60'} backdrop-blur-md shadow-lg`}
            onDragOver={e => { e.preventDefault(); e.stopPropagation(); }}
            onDrop={e => {
              e.preventDefault(); e.stopPropagation();
              if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
                const files = Array.from(e.dataTransfer.files);
                onUploadFiles(files);
              }
            }}
            style={{ minHeight: 140 }}
          >
            <input
              type="file"
              multiple
              onChange={e => {
                if (e.target.files) onUploadFiles(Array.from(e.target.files));
              }}
              disabled={isUploading}
              className="absolute inset-0 opacity-0 cursor-pointer"
              style={{ zIndex: 2 }}
            />
            <div className="flex flex-col items-center pointer-events-none">
              <svg className="w-12 h-12 text-blue-400 mb-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M7 16V4a1 1 0 011-1h8a1 1 0 011 1v12m-5 4v-4m0 0l-2 2m2-2l2 2" /></svg>
              <span className="font-semibold text-gray-700">Drag & drop files here or <span className="text-blue-600 underline">browse</span></span>
              <span className="text-xs text-gray-500 mt-1">PDF, images, video, audio, Word, Excel, and more. Max 10MB each.</span>
            </div>
          </div>
          {uploadError && (
            <p className="mt-2 text-sm text-red-600">{uploadError}</p>
          )}
          {/* Document List */}
          <div className="space-y-4">
            {registration?.documents?.map((doc) => (
              <div key={doc.id} className="flex items-center justify-between p-4 bg-gray-50 rounded-lg shadow-sm">
                <div className="flex items-center space-x-4">
                  <button
                    onClick={() => openPreview({ url: doc.url, name: doc.name, type: doc.type, id: doc.id })}
                    className="text-primary hover:text-primary-dark font-medium"
                  >
                    {doc.name}
                  </button>
                  <span className="text-xs text-gray-500">{doc.type}</span>
                </div>
                <div className="flex items-center gap-2">
                  <a
                    href={doc.url}
                    target="_blank"
                    rel="noopener noreferrer"
                    className="px-2 py-1 text-green-600 hover:text-green-800 rounded border border-green-100 hover:border-green-300 text-xs"
                    download
                  >
                    Download
                  </a>
                  <button
                    onClick={() => handleDeleteDocument(doc.id)}
                    disabled={isDeleting === doc.id}
                    className="ml-2 text-red-500 hover:text-red-700 p-1 rounded-full"
                    title="Delete"
                  >
                    {isDeleting === doc.id ? '...' : <svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" /></svg>}
                  </button>
                </div>
              </div>
            ))}
          </div>
        </div>
        {/* Unified Document Viewer Modal */}
        {openPreviews.length > 0 && (
          <button
            onClick={() => setOpenPreviews([])}
            className="fixed top-24 right-8 z-50 bg-red-600 text-white px-4 py-2 rounded shadow hover:bg-red-700"
          >
            Close All Viewers
          </button>
        )}
        <AnimatePresence>
          {openPreviews
            .filter(preview => typeof preview.id === 'string')
            .map(({ doc, isMaximized, position, zIndex, id }) => (
              <DraggableModal
                key={id}
                doc={doc}
                isMaximized={isMaximized}
                position={position}
                zIndex={zIndex}
                onClose={() => closePreview(id as string)}
                onMaximize={() => toggleMaximize(id as string)}
                onDrag={(pos: { x: number; y: number }) => updatePosition(id as string, pos)}
                onFocus={() => bringToFront(id as string)}
              />
            ))}
        </AnimatePresence>

        {/* Actions */}
        <div className="text-center mt-8">
          <button
            onClick={() => router.push('/admin/dashboard')}
            className="bg-gray-600 text-white px-8 py-4 rounded-lg font-semibold shadow-lg hover:bg-gray-700 transition-all duration-200"
          >
            Back to Dashboard
          </button>
        </div>
      </div>
    </LayoutWithSidebar>
  );
};

function DraggableModal({ doc, isMaximized, position, zIndex, onClose, onMaximize, onDrag, onFocus }: DraggableModalProps) {
  const modalRef = useRef(null);
  const [dragging, setDragging] = useState(false);
  const [offset, setOffset] = useState({ x: 0, y: 0 });
  const [textContent, setTextContent] = useState<string | null>(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [officeText, setOfficeText] = useState<string | null>(null);
  const [officeLoading, setOfficeLoading] = useState(false);
  const [officeError, setOfficeError] = useState<string | null>(null);

  const TEXT_TYPES = [
    'text/plain',
    'text/markdown',
    'text/csv',
    'application/json',
    'text/x-log',
    'text/x-yaml',
    'text/yaml',
    'text/xml',
    'application/xml',
    'text/html',
    'text/css',
    'text/javascript',
    'application/javascript',
    'application/typescript',
    'text/typescript',
  ];
  const TEXT_EXTENSIONS = [
    '.txt', '.md', '.csv', '.log', '.json', '.ts', '.js', '.css', '.html', '.xml', '.yaml', '.yml'
  ];
  const isTextFile = () => {
    const ext = doc.name ? doc.name.slice(doc.name.lastIndexOf('.')).toLowerCase() : '';
    return TEXT_TYPES.includes(doc.type) || TEXT_EXTENSIONS.includes(ext);
  };

  const isDocx = doc.name?.toLowerCase().endsWith('.docx');
  const isXlsx = doc.name?.toLowerCase().endsWith('.xlsx');
  const isPptx = doc.name?.toLowerCase().endsWith('.pptx');

  useEffect(() => {
    if (isTextFile()) {
      setLoading(true);
      setError(null);
      fetch(doc.url)
        .then(res => {
          if (!res.ok) throw new Error('Failed to fetch file');
          return res.text();
        })
        .then(setTextContent)
        .catch(e => setError(e.message))
        .finally(() => setLoading(false));
    } else if (isDocx) {
      setOfficeLoading(true);
      setOfficeError(null);
      fetch(doc.url)
        .then(res => res.arrayBuffer())
        .then(arrayBuffer => mammoth.extractRawText({ arrayBuffer }))
        .then(result => setOfficeText(result.value))
        .catch(e => setOfficeError('Failed to extract text from DOCX'))
        .finally(() => setOfficeLoading(false));
    } else if (isXlsx) {
      setOfficeLoading(true);
      setOfficeError(null);
      fetch(doc.url)
        .then(res => res.arrayBuffer())
        .then(arrayBuffer => {
          const workbook = XLSX.read(arrayBuffer, { type: 'array' });
          let text = '';
          workbook.SheetNames.forEach(sheetName => {
            const sheet = workbook.Sheets[sheetName];
            text += `Sheet: ${sheetName}\n`;
            text += XLSX.utils.sheet_to_csv(sheet);
            text += '\n';
          });
          setOfficeText(text);
        })
        .catch(e => setOfficeError('Failed to extract text from XLSX'))
        .finally(() => setOfficeLoading(false));
    } else if (isPptx) {
      setOfficeLoading(true);
      setOfficeError(null);
      // No robust in-browser PPTX parser, fallback to message
      setTimeout(() => {
        setOfficeError('Text preview for PowerPoint is not supported.');
        setOfficeLoading(false);
      }, 500);
    }
    // eslint-disable-next-line
  }, [doc.url, doc.name]);

  const handleHeaderMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
    // Only start drag if not clicking a button
    if (isMaximized || (e.target instanceof HTMLElement && e.target.closest('button'))) return;
    setDragging(true);
    const safePosition = position || { x: 0, y: 0 };
    setOffset({
      x: e.clientX - safePosition.x,
      y: e.clientY - safePosition.y
    });
    onFocus();
  };
  const handleMouseMove = (e: MouseEvent) => {
    if (!dragging || isMaximized) return;
    // Clamp to viewport
    const newX = Math.max(0, Math.min(window.innerWidth - 400, e.clientX - offset.x));
    const newY = Math.max(0, Math.min(window.innerHeight - 200, e.clientY - offset.y));
    onDrag({ x: newX, y: newY });
  };
  const handleMouseUp = () => setDragging(false);
  useEffect(() => {
    if (dragging) {
      window.addEventListener('mousemove', handleMouseMove);
      window.addEventListener('mouseup', handleMouseUp);
    } else {
      window.removeEventListener('mousemove', handleMouseMove);
      window.removeEventListener('mouseup', handleMouseUp);
    }
    return () => {
      window.removeEventListener('mousemove', handleMouseMove);
      window.removeEventListener('mouseup', handleMouseUp);
    };
  }, [dragging, handleMouseMove]);
  return (
    <div
      ref={modalRef}
      className={`fixed z-[${zIndex}] ${isMaximized ? 'inset-0' : ''}`}
      style={isMaximized ? { left: 0, top: 0 } : { left: (position?.x ?? 0), top: (position?.y ?? 0) }}
      onMouseDown={onFocus}
    >
      <div className={`bg-white rounded-2xl shadow-2xl ${isMaximized ? 'w-screen h-screen max-w-none max-h-none' : 'max-w-2xl w-full p-6'} relative flex flex-col border-2 border-primary`}
        style={{ cursor: dragging ? 'grabbing' : 'default' }}
      >
        {/* Modal Header (Drag Handle) */}
        <div
          className="flex items-center justify-between px-4 py-2 bg-gray-100 rounded-t-2xl border-b border-gray-200 select-none cursor-move"
          style={{ userSelect: 'none' }}
          onMouseDown={handleHeaderMouseDown}
        >
          <span className="font-semibold text-lg text-gray-900 truncate">{doc.name}</span>
          <div className="flex items-center gap-2 ml-4">
            <button
              onClick={onMaximize}
              className="text-gray-400 hover:text-gray-700 mr-2"
              title={isMaximized ? 'Restore' : 'Maximize'}
              tabIndex={-1}
            >
              {isMaximized ? (
                <svg className="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><rect x="6" y="6" width="12" height="12" rx="2" strokeWidth="2" /><path d="M9 9h6v6H9z" /></svg>
              ) : (
                <svg className="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><rect x="4" y="4" width="16" height="16" rx="2" strokeWidth="2" /></svg>
              )}
            </button>
            <button
              onClick={onClose}
              className="text-gray-400 hover:text-gray-700"
              tabIndex={-1}
            >
              <svg className="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" /></svg>
            </button>
          </div>
        </div>
        {/* Preview content by type */}
        {isDocx || isXlsx || isPptx ? (
          officeLoading ? (
            <div className="p-8 text-center text-gray-500">Loading preview...</div>
          ) : officeError ? (
            <div className="p-8 text-center text-red-600">{officeError}</div>
          ) : (
            <div className="p-4 max-h-[70vh] overflow-auto bg-gray-900 text-gray-100 rounded-lg shadow-inner border border-gray-300 text-left mt-6">
              <pre className="whitespace-pre-wrap break-words text-sm font-mono">{officeText}</pre>
            </div>
          )
        ) : doc.type?.startsWith('image') ? (
          <img src={doc.url} alt={doc.name} className="max-h-[60vh] mx-auto rounded-lg mt-6" style={isMaximized ? { maxHeight: '90vh' } : {}} />
        ) : doc.type?.startsWith('video') ? (
          <video src={doc.url} controls className="max-h-[60vh] mx-auto rounded-lg mt-6" style={isMaximized ? { maxHeight: '90vh' } : {}} />
        ) : doc.type?.startsWith('audio') ? (
          <audio src={doc.url} controls className="w-full mt-6" />
        ) : doc.type?.includes('pdf') ? (
          <iframe src={doc.url} className="w-full h-[60vh] rounded-lg mt-6" title={doc.name} style={isMaximized ? { height: '90vh' } : {}} />
        ) : isTextFile() ? (
          loading ? (
            <div className="p-8 text-center text-gray-500">Loading preview...</div>
          ) : error ? (
            <div className="p-8 text-center text-red-600">Failed to load file: {error}</div>
          ) : (
            <div className="p-4 max-h-[70vh] overflow-auto bg-gray-900 text-gray-100 rounded-lg shadow-inner border border-gray-300 text-left mt-6">
              <pre className="whitespace-pre-wrap break-words text-sm font-mono">{textContent}</pre>
            </div>
          )
        ) : (
          <div className="text-gray-500 mt-6">Preview not available for this file type.</div>
        )}
      </div>
    </div>
  );
}

export default AdminRegistrationDetail; 

CasperSecurity Mini