182 lines
6 KiB
TypeScript
182 lines
6 KiB
TypeScript
// components/staff/BulkESignProgressModal.tsx
|
|
"use client";
|
|
|
|
import React from "react";
|
|
import { X, FileSignature, CheckCircle, XCircle, Clock, Loader2 } from "lucide-react";
|
|
|
|
type ContractESignProgress = {
|
|
id: string;
|
|
contractNumber?: string;
|
|
employeeName?: string;
|
|
status: 'pending' | 'processing' | 'success' | 'error';
|
|
error?: string;
|
|
submissionId?: string;
|
|
};
|
|
|
|
type BulkESignProgressModalProps = {
|
|
isOpen: boolean;
|
|
onClose: () => void;
|
|
contracts: ContractESignProgress[];
|
|
onCancel?: () => void;
|
|
isProcessing: boolean;
|
|
totalCount: number;
|
|
completedCount: number;
|
|
successCount: number;
|
|
errorCount: number;
|
|
};
|
|
|
|
export default function BulkESignProgressModal({
|
|
isOpen,
|
|
onClose,
|
|
contracts,
|
|
onCancel,
|
|
isProcessing,
|
|
totalCount,
|
|
completedCount,
|
|
successCount,
|
|
errorCount
|
|
}: BulkESignProgressModalProps) {
|
|
if (!isOpen) return null;
|
|
|
|
const progressPercentage = totalCount > 0 ? (completedCount / totalCount) * 100 : 0;
|
|
|
|
const getStatusIcon = (status: ContractESignProgress['status']) => {
|
|
switch (status) {
|
|
case 'pending':
|
|
return <Clock className="size-4 text-gray-400" />;
|
|
case 'processing':
|
|
return <Loader2 className="size-4 text-blue-500 animate-spin" />;
|
|
case 'success':
|
|
return <CheckCircle className="size-4 text-green-500" />;
|
|
case 'error':
|
|
return <XCircle className="size-4 text-red-500" />;
|
|
}
|
|
};
|
|
|
|
const getStatusText = (contract: ContractESignProgress) => {
|
|
switch (contract.status) {
|
|
case 'pending':
|
|
return 'En attente';
|
|
case 'processing':
|
|
return 'Création de la signature en cours...';
|
|
case 'success':
|
|
return `Signature électronique créée`;
|
|
case 'error':
|
|
return `Erreur: ${contract.error || 'Erreur inconnue'}`;
|
|
}
|
|
};
|
|
|
|
const getStatusColor = (status: ContractESignProgress['status']) => {
|
|
switch (status) {
|
|
case 'pending':
|
|
return 'text-gray-600';
|
|
case 'processing':
|
|
return 'text-blue-600';
|
|
case 'success':
|
|
return 'text-green-600';
|
|
case 'error':
|
|
return 'text-red-600';
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
|
<div className="bg-white rounded-lg p-6 w-full max-w-2xl max-h-[80vh] flex flex-col">
|
|
{/* Header */}
|
|
<div className="flex items-center justify-between mb-4">
|
|
<div className="flex items-center gap-2">
|
|
<FileSignature className="size-5 text-purple-600" />
|
|
<h2 className="text-lg font-semibold">Envoi des signatures électroniques</h2>
|
|
</div>
|
|
{!isProcessing && (
|
|
<button
|
|
onClick={onClose}
|
|
className="p-1 hover:bg-gray-100 rounded"
|
|
>
|
|
<X className="size-5" />
|
|
</button>
|
|
)}
|
|
</div>
|
|
|
|
{/* Progress Bar */}
|
|
<div className="mb-4">
|
|
<div className="flex justify-between text-sm text-gray-600 mb-2">
|
|
<span>Progression: {completedCount}/{totalCount}</span>
|
|
<span>{Math.round(progressPercentage)}%</span>
|
|
</div>
|
|
<div className="w-full bg-gray-200 rounded-full h-2">
|
|
<div
|
|
className="bg-purple-500 h-2 rounded-full transition-all duration-300"
|
|
style={{ width: `${progressPercentage}%` }}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Summary Stats */}
|
|
<div className="grid grid-cols-3 gap-4 mb-4">
|
|
<div className="text-center p-2 bg-green-50 rounded">
|
|
<div className="text-lg font-semibold text-green-600">{successCount}</div>
|
|
<div className="text-xs text-green-600">Succès</div>
|
|
</div>
|
|
<div className="text-center p-2 bg-red-50 rounded">
|
|
<div className="text-lg font-semibold text-red-600">{errorCount}</div>
|
|
<div className="text-xs text-red-600">Erreurs</div>
|
|
</div>
|
|
<div className="text-center p-2 bg-gray-50 rounded">
|
|
<div className="text-lg font-semibold text-gray-600">{totalCount - completedCount}</div>
|
|
<div className="text-xs text-gray-600">Restants</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Contracts List */}
|
|
<div className="flex-1 overflow-y-auto border rounded max-h-64">
|
|
<div className="space-y-1 p-2">
|
|
{contracts.map((contract) => (
|
|
<div
|
|
key={contract.id}
|
|
className={`flex items-center gap-3 p-2 rounded text-sm ${
|
|
contract.status === 'processing' ? 'bg-blue-50' :
|
|
contract.status === 'success' ? 'bg-green-50' :
|
|
contract.status === 'error' ? 'bg-red-50' : 'bg-gray-50'
|
|
}`}
|
|
>
|
|
{getStatusIcon(contract.status)}
|
|
<div className="flex-1 min-w-0">
|
|
<div className="font-medium truncate">
|
|
{contract.contractNumber || contract.id}
|
|
</div>
|
|
<div className="text-xs text-gray-500 truncate">
|
|
{contract.employeeName || 'Nom non disponible'}
|
|
</div>
|
|
</div>
|
|
<div className={`text-xs ${getStatusColor(contract.status)} truncate max-w-xs`}>
|
|
{getStatusText(contract)}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Actions */}
|
|
<div className="flex justify-end gap-2 mt-4">
|
|
{isProcessing && onCancel && (
|
|
<button
|
|
onClick={onCancel}
|
|
className="px-4 py-2 text-sm bg-red-600 text-white rounded hover:bg-red-700"
|
|
>
|
|
Annuler
|
|
</button>
|
|
)}
|
|
{!isProcessing && (
|
|
<button
|
|
onClick={onClose}
|
|
className="px-4 py-2 text-sm bg-blue-600 text-white rounded hover:bg-blue-700"
|
|
>
|
|
Fermer
|
|
</button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|