mirror of
https://github.com/R0m1k3/LogiFlow.git
synced 2026-10-21 22:18:17 +02:00
Add a waiting modal and timeout for webhook processing
Introduce a waiting modal with a countdown timer for webhook processing, handle request timeouts with an AbortController, and improve error reporting for webhook failures. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 98f01874-e00c-4bbe-9d71-b0b338001848 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/1957c339-2757-4d1f-8e92-e9f71a1ce58e/98f01874-e00c-4bbe-9d71-b0b338001848/NGEmabJ
This commit is contained in:
1 parent
a77a596880
commit
0e81fbaf41
1 file changed
+119
-3
@@ -57,6 +57,11 @@ export default function BLReconciliation() {
|
|||||||
const [selectedFile, setSelectedFile] = useState<File | null>(null);
|
const [selectedFile, setSelectedFile] = useState<File | null>(null);
|
||||||
const [isUploading, setIsUploading] = useState(false);
|
const [isUploading, setIsUploading] = useState(false);
|
||||||
|
|
||||||
|
// État pour le modal d'attente du webhook
|
||||||
|
const [showWaitingModal, setShowWaitingModal] = useState(false);
|
||||||
|
const [processingSeconds, setProcessingSeconds] = useState(0);
|
||||||
|
const [processingTimeout, setProcessingTimeout] = useState<NodeJS.Timeout | null>(null);
|
||||||
|
|
||||||
// État pour le système de vérification de facture
|
// État pour le système de vérification de facture
|
||||||
const [verificationResults, setVerificationResults] = useState<Record<number, any>>({});
|
const [verificationResults, setVerificationResults] = useState<Record<number, any>>({});
|
||||||
const [verifyingDeliveries, setVerifyingDeliveries] = useState<Set<number>>(new Set());
|
const [verifyingDeliveries, setVerifyingDeliveries] = useState<Set<number>>(new Set());
|
||||||
@@ -349,6 +354,29 @@ export default function BLReconciliation() {
|
|||||||
setIsUploading(false);
|
setIsUploading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleCloseWaitingModal = () => {
|
||||||
|
setShowWaitingModal(false);
|
||||||
|
setProcessingSeconds(0);
|
||||||
|
if (processingTimeout) {
|
||||||
|
clearTimeout(processingTimeout);
|
||||||
|
setProcessingTimeout(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const startProcessingTimer = () => {
|
||||||
|
setProcessingSeconds(0);
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
setProcessingSeconds(prev => {
|
||||||
|
if (prev >= 60) {
|
||||||
|
clearInterval(interval);
|
||||||
|
return 60;
|
||||||
|
}
|
||||||
|
return prev + 1;
|
||||||
|
});
|
||||||
|
}, 1000);
|
||||||
|
setProcessingTimeout(interval);
|
||||||
|
};
|
||||||
|
|
||||||
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const file = event.target.files?.[0];
|
const file = event.target.files?.[0];
|
||||||
if (file && file.type === 'application/pdf') {
|
if (file && file.type === 'application/pdf') {
|
||||||
@@ -381,7 +409,11 @@ export default function BLReconciliation() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fermer le modal de sélection et ouvrir le modal d'attente
|
||||||
|
setShowInvoiceModal(false);
|
||||||
|
setShowWaitingModal(true);
|
||||||
setIsUploading(true);
|
setIsUploading(true);
|
||||||
|
startProcessingTimer();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
@@ -390,27 +422,51 @@ export default function BLReconciliation() {
|
|||||||
formData.append('blNumber', selectedDeliveryForInvoice.blNumber || '');
|
formData.append('blNumber', selectedDeliveryForInvoice.blNumber || '');
|
||||||
formData.append('type', 'Facture');
|
formData.append('type', 'Facture');
|
||||||
|
|
||||||
|
// Créer un AbortController pour gérer le timeout
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timeoutId = setTimeout(() => controller.abort(), 60000); // 60 secondes
|
||||||
|
|
||||||
const response = await fetch(selectedDeliveryForInvoice.group.webhookUrl, {
|
const response = await fetch(selectedDeliveryForInvoice.group.webhookUrl, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: formData,
|
body: formData,
|
||||||
|
signal: controller.signal,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`Erreur ${response.status}: ${response.statusText}`);
|
throw new Error(`Erreur ${response.status}: ${response.statusText}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleCloseWaitingModal();
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: "Succès",
|
title: "Succès",
|
||||||
description: "Facture envoyée avec succès via le webhook",
|
description: "Facture traitée avec succès via le webhook",
|
||||||
});
|
});
|
||||||
|
|
||||||
handleCloseInvoiceModal();
|
// Reset des données
|
||||||
|
setSelectedDeliveryForInvoice(null);
|
||||||
|
setSelectedFile(null);
|
||||||
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
handleCloseWaitingModal();
|
||||||
|
|
||||||
|
let errorMessage = "Impossible d'envoyer la facture";
|
||||||
|
if (error.name === 'AbortError') {
|
||||||
|
errorMessage = "Le traitement a pris trop de temps (timeout de 60 secondes)";
|
||||||
|
} else if (error.message) {
|
||||||
|
errorMessage = `Impossible d'envoyer la facture: ${error.message}`;
|
||||||
|
}
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: "Erreur",
|
title: "Erreur",
|
||||||
description: `Impossible d'envoyer la facture: ${error.message}`,
|
description: errorMessage,
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Réouvrir le modal de sélection en cas d'erreur
|
||||||
|
setShowInvoiceModal(true);
|
||||||
} finally {
|
} finally {
|
||||||
setIsUploading(false);
|
setIsUploading(false);
|
||||||
}
|
}
|
||||||
@@ -1171,6 +1227,66 @@ export default function BLReconciliation() {
|
|||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
|
{/* Modal d'attente pour le traitement du webhook */}
|
||||||
|
<Dialog open={showWaitingModal} onOpenChange={() => {}}>
|
||||||
|
<DialogContent className="sm:max-w-[400px]">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle className="flex items-center space-x-2">
|
||||||
|
<Clock className="w-5 h-5 text-blue-600 animate-spin" />
|
||||||
|
<span>Traitement en cours</span>
|
||||||
|
</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
<div className="grid gap-6 py-6">
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="w-16 h-16 mx-auto mb-4 relative">
|
||||||
|
<div className="w-16 h-16 border-4 border-gray-200 border-t-blue-600 rounded-full animate-spin"></div>
|
||||||
|
<div className="absolute inset-0 flex items-center justify-center">
|
||||||
|
<span className="text-xs font-bold text-blue-600">{processingSeconds}s</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<h3 className="font-medium text-gray-900">
|
||||||
|
Traitement de votre facture en cours...
|
||||||
|
</h3>
|
||||||
|
<p className="text-sm text-gray-600">
|
||||||
|
Le workflow peut prendre jusqu'à 1 minute.
|
||||||
|
<br />
|
||||||
|
Veuillez patienter.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-4 bg-gray-100 rounded-full h-2 w-full">
|
||||||
|
<div
|
||||||
|
className="bg-blue-600 h-2 rounded-full transition-all duration-1000 ease-out"
|
||||||
|
style={{ width: `${Math.min((processingSeconds / 60) * 100, 100)}%` }}
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-2 text-xs text-gray-500">
|
||||||
|
{processingSeconds < 60 ? `${60 - processingSeconds}s restantes (max)` : 'Finalisation...'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{selectedDeliveryForInvoice && (
|
||||||
|
<div className="bg-blue-50 p-3 rounded-md">
|
||||||
|
<div className="text-sm">
|
||||||
|
<div className="font-medium text-blue-900">
|
||||||
|
{selectedDeliveryForInvoice.supplier?.name}
|
||||||
|
</div>
|
||||||
|
<div className="text-blue-700">
|
||||||
|
BL: {selectedDeliveryForInvoice.blNumber || 'Non renseigné'}
|
||||||
|
</div>
|
||||||
|
<div className="text-blue-700">
|
||||||
|
Fichier: {selectedFile?.name}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user