mirror of
https://github.com/R0m1k3/LogiFlow.git
synced 2026-10-20 05:28:22 +02:00
Improve display of commercial processing status for avoirs
Adjusted the display of the "Traité por commercial" status by wrapping the icon in a div to ensure consistent tooltip behavior across different browsers. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 9fcf4b21-eb0c-4e53-a567-e2ce4a6ad869 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/1957c339-2757-4d1f-8e92-e9f71a1ce58e/9fcf4b21-eb0c-4e53-a567-e2ce4a6ad869/7eY9Hl8
This commit is contained in:
1 parent
c2edd36ccc
commit
acbc36fda1
2 files changed
+3
-582
No files matched your search
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
+3
-582
@@ -989,7 +989,9 @@ export default function Avoirs() {
|
|||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
{avoir.amount !== null && avoir.amount !== undefined ? `${Number(avoir.amount).toFixed(2)} €` : 'Non spécifié'}
|
{avoir.amount !== null && avoir.amount !== undefined ? `${Number(avoir.amount).toFixed(2)} €` : 'Non spécifié'}
|
||||||
{avoir.commercialProcessed && (
|
{avoir.commercialProcessed && (
|
||||||
<UserCheck className="ml-2 h-4 w-4 text-purple-600" title="Traité par commercial" />
|
<div title="Traité par commercial">
|
||||||
|
<UserCheck className="ml-2 h-4 w-4 text-purple-600" />
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
@@ -1692,584 +1694,3 @@ export default function Avoirs() {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
<div className="relative flex-1">
|
|
||||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-4 w-4" />
|
|
||||||
<Input
|
|
||||||
placeholder="Rechercher par référence, fournisseur, commentaire..."
|
|
||||||
value={searchTerm}
|
|
||||||
onChange={(e) => setSearchTerm(e.target.value)}
|
|
||||||
className="pl-10"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Filtrage maintenant géré par le contexte global Layout */}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Avoirs List - Format Table */}
|
|
||||||
<div className="bg-white rounded-lg shadow overflow-hidden">
|
|
||||||
<div className="overflow-x-auto">
|
|
||||||
{isLoading ? (
|
|
||||||
<div className="p-4">
|
|
||||||
<div className="animate-pulse space-y-4">
|
|
||||||
{Array.from({ length: 3 }).map((_, i) => (
|
|
||||||
<div key={i} className="h-16 bg-gray-200 rounded"></div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : filteredAvoirs.length === 0 ? (
|
|
||||||
<div className="p-12 text-center">
|
|
||||||
<FileText className="mx-auto h-12 w-12 text-gray-400" />
|
|
||||||
<h3 className="mt-4 text-sm font-medium text-gray-900">Aucun avoir</h3>
|
|
||||||
<p className="mt-1 text-sm text-gray-500">
|
|
||||||
{searchTerm ? "Aucun avoir ne correspond à votre recherche." : "Commencez par créer votre premier avoir."}
|
|
||||||
</p>
|
|
||||||
{!searchTerm && (
|
|
||||||
<Button className="mt-4" onClick={() => setIsCreateDialogOpen(true)}>
|
|
||||||
<Plus className="h-4 w-4 mr-2" />
|
|
||||||
Créer un avoir
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<table className="min-w-full divide-y divide-gray-200">
|
|
||||||
<thead className="bg-gray-50">
|
|
||||||
<tr>
|
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
||||||
Fournisseur
|
|
||||||
</th>
|
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
||||||
Statut
|
|
||||||
</th>
|
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
||||||
Référence
|
|
||||||
</th>
|
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
||||||
Magasin
|
|
||||||
</th>
|
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
||||||
Montant
|
|
||||||
</th>
|
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
||||||
Créé le
|
|
||||||
</th>
|
|
||||||
<th className="px-6 py-3 text-center text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
||||||
Vérification
|
|
||||||
</th>
|
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
||||||
Actions
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody className="bg-white divide-y divide-gray-200">
|
|
||||||
{filteredAvoirs?.filter(avoir => avoir && avoir.id).map((avoir) => {
|
|
||||||
const canEditDelete = ['admin', 'directeur'].includes((user as any)?.role);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<tr key={avoir?.id || Math.random()} className={`hover:bg-gray-50 ${avoir?.nocodbVerified ? 'bg-gray-100 opacity-75' : ''}`}>
|
|
||||||
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
|
|
||||||
{avoir.supplier?.name || 'Fournisseur non défini'}
|
|
||||||
</td>
|
|
||||||
<td className="px-6 py-4 whitespace-nowrap">
|
|
||||||
<Select
|
|
||||||
value={avoir?.status || ''}
|
|
||||||
onValueChange={(newStatus) => handleStatusChange(avoir.id, newStatus)}
|
|
||||||
>
|
|
||||||
<SelectTrigger className="w-full">
|
|
||||||
<SelectValue />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="En attente de demande">En attente de demande</SelectItem>
|
|
||||||
<SelectItem value="Demandé">Demandé</SelectItem>
|
|
||||||
<SelectItem value="Reçu">Reçu</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</td>
|
|
||||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
|
||||||
#{avoir.invoiceReference || 'Sans référence'}
|
|
||||||
</td>
|
|
||||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
|
||||||
<div className="flex items-center">
|
|
||||||
<div
|
|
||||||
className="w-3 h-3 rounded-full mr-2"
|
|
||||||
style={{ backgroundColor: avoir.group?.color || '#gray' }}
|
|
||||||
/>
|
|
||||||
{avoir.group?.name || 'Magasin non défini'}
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
|
|
||||||
<div className="flex items-center">
|
|
||||||
{avoir.amount !== null && avoir.amount !== undefined ? `${Number(avoir.amount).toFixed(2)} €` : 'Non spécifié'}
|
|
||||||
{avoir.commercialProcessed && (
|
|
||||||
<div title="Avoir fait par commercial">
|
|
||||||
<UserCheck className="h-4 w-4 text-blue-600 ml-2" />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{avoir.webhookSent && (
|
|
||||||
<div title="Webhook envoyé">
|
|
||||||
<Send className="h-4 w-4 text-green-600 ml-2" />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
|
||||||
<div>
|
|
||||||
<div>
|
|
||||||
{avoir?.createdAt
|
|
||||||
? format(new Date(avoir.createdAt), "dd/MM/yyyy", { locale: fr })
|
|
||||||
: 'Non défini'
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<div className="text-xs text-gray-400">
|
|
||||||
Par {avoir?.creator?.firstName && avoir?.creator?.lastName
|
|
||||||
? `${avoir.creator.firstName} ${avoir.creator.lastName}`
|
|
||||||
: avoir?.creator?.username || 'Utilisateur inconnu'
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td className="px-6 py-4 whitespace-nowrap text-center text-sm">
|
|
||||||
{avoir.invoiceReference?.trim() ? (
|
|
||||||
<div className="flex items-center justify-center">
|
|
||||||
{verifyingAvoirs.has(avoir.id) ? (
|
|
||||||
<Clock className="h-4 w-4 text-blue-500 animate-spin" />
|
|
||||||
) : (avoirVerificationResults[avoir.id]?.exists === true || avoir.nocodbVerified) ? (
|
|
||||||
<div title={`Vérifié: ${avoirVerificationResults[avoir.id]?.exists ? 'cache' : avoir.nocodbVerified ? 'validé' : 'inconnu'}`}>
|
|
||||||
<CheckCircle className="h-4 w-4 text-green-500 cursor-help" />
|
|
||||||
</div>
|
|
||||||
) : avoirVerificationResults[avoir.id]?.exists === false ? (
|
|
||||||
<XCircle className="h-4 w-4 text-red-500 cursor-help" />
|
|
||||||
) : (
|
|
||||||
<button
|
|
||||||
onClick={() => handleVerifyAvoirInvoice(avoir)}
|
|
||||||
className="h-4 w-4 text-gray-400 hover:text-blue-500 transition-colors"
|
|
||||||
title="Cliquer pour vérifier la facture"
|
|
||||||
>
|
|
||||||
<Search className="h-4 w-4" />
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<span className="text-gray-400 text-xs">Sans réf.</span>
|
|
||||||
)}
|
|
||||||
</td>
|
|
||||||
<td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
{/* 📋 Icône Upload pour avoirs "Reçu" */}
|
|
||||||
{avoir?.status === 'Reçu' && (
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
className="h-8 w-8 p-0 text-blue-600 hover:text-blue-700 hover:bg-blue-50"
|
|
||||||
title="Envoyer fichier avoir"
|
|
||||||
onClick={() => handleOpenAvoirModal(avoir)}
|
|
||||||
>
|
|
||||||
<Upload className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Bouton Valider - apparaît si vérification réussie et pas encore validé */}
|
|
||||||
{avoirVerificationResults[avoir.id]?.exists === true && !avoir.nocodbVerified && (
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
className="h-8 w-8 p-0 text-green-600 hover:text-green-700 hover:bg-green-50"
|
|
||||||
title="Valider l'avoir"
|
|
||||||
onClick={() => handleValidateAvoir(avoir.id)}
|
|
||||||
>
|
|
||||||
<CheckCircle className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Bouton Dévalider - pour admins sur avoirs validés */}
|
|
||||||
{avoir.nocodbVerified && canEditDelete && (
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
className="h-8 w-8 p-0 text-orange-600 hover:text-orange-700 hover:bg-orange-50"
|
|
||||||
title="Dévalider l'avoir"
|
|
||||||
onClick={() => handleDevalidateAvoir(avoir.id)}
|
|
||||||
>
|
|
||||||
<XCircle className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Bouton Commentaire - apparaît seulement s'il y a un commentaire */}
|
|
||||||
{avoir.comment && avoir.comment.trim() !== '' && (
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
className="h-8 w-8 p-0 text-purple-600 hover:text-purple-700 hover:bg-purple-50"
|
|
||||||
title="Voir le commentaire"
|
|
||||||
onClick={() => handleViewComment(avoir.comment || '')}
|
|
||||||
>
|
|
||||||
<MessageSquare className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{canEditDelete && (
|
|
||||||
<>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
className="h-8 w-8 p-0"
|
|
||||||
title="Modifier"
|
|
||||||
onClick={() => handleEdit(avoir)}
|
|
||||||
>
|
|
||||||
<Edit className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
className="h-8 w-8 p-0 text-red-600 hover:text-red-700 hover:bg-red-50"
|
|
||||||
title="Supprimer"
|
|
||||||
onClick={() => handleDelete(avoir)}
|
|
||||||
>
|
|
||||||
<Trash2 className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{!canEditDelete && avoir?.status !== 'Reçu' && (
|
|
||||||
<span className="text-gray-400 text-xs">Lecture seule</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Edit Modal */}
|
|
||||||
<Dialog open={isEditDialogOpen} onOpenChange={setIsEditDialogOpen}>
|
|
||||||
<DialogContent className="sm:max-w-[425px]">
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle>Modifier l'avoir</DialogTitle>
|
|
||||||
<DialogDescription>
|
|
||||||
Modifiez les informations de l'avoir.
|
|
||||||
</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
|
|
||||||
<Form {...editForm}>
|
|
||||||
<form onSubmit={editForm.handleSubmit(onEditSubmit)} className="space-y-4">
|
|
||||||
<FormField
|
|
||||||
control={editForm.control}
|
|
||||||
name="supplierId"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Fournisseur *</FormLabel>
|
|
||||||
<Select
|
|
||||||
value={field.value.toString()}
|
|
||||||
onValueChange={(value) => field.onChange(parseInt(value))}
|
|
||||||
>
|
|
||||||
<FormControl>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue placeholder="Sélectionnez un fournisseur" />
|
|
||||||
</SelectTrigger>
|
|
||||||
</FormControl>
|
|
||||||
<SelectContent>
|
|
||||||
{suppliers.map((supplier) => (
|
|
||||||
<SelectItem key={supplier.id} value={supplier.id.toString()}>
|
|
||||||
{supplier.name}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<FormField
|
|
||||||
control={editForm.control}
|
|
||||||
name="invoiceReference"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Référence facture</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input placeholder="Référence de la facture (optionnel)" {...field} />
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<FormField
|
|
||||||
control={editForm.control}
|
|
||||||
name="amount"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Montant</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input
|
|
||||||
type="number"
|
|
||||||
step="0.01"
|
|
||||||
placeholder="Montant en euros (optionnel)"
|
|
||||||
value={editAmountValue}
|
|
||||||
onChange={(e) => {
|
|
||||||
// ✅ Utiliser seulement l'état local, complètement séparé de React Hook Form
|
|
||||||
setEditAmountValue(e.target.value);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<FormField
|
|
||||||
control={editForm.control}
|
|
||||||
name="status"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Statut</FormLabel>
|
|
||||||
<Select value={field.value} onValueChange={field.onChange}>
|
|
||||||
<FormControl>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue />
|
|
||||||
</SelectTrigger>
|
|
||||||
</FormControl>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="En attente de demande">En attente de demande</SelectItem>
|
|
||||||
<SelectItem value="Demandé">Demandé</SelectItem>
|
|
||||||
<SelectItem value="Reçu">Reçu</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<FormField
|
|
||||||
control={editForm.control}
|
|
||||||
name="comment"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Commentaire</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Textarea
|
|
||||||
placeholder="Commentaire ou description (optionnel)"
|
|
||||||
{...field}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<FormField
|
|
||||||
control={editForm.control}
|
|
||||||
name="commercialProcessed"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="flex flex-row items-start space-x-3 space-y-0">
|
|
||||||
<FormControl>
|
|
||||||
<Checkbox
|
|
||||||
checked={field.value}
|
|
||||||
onCheckedChange={field.onChange}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<div className="space-y-1 leading-none">
|
|
||||||
<FormLabel>
|
|
||||||
Avoir fait par commercial
|
|
||||||
</FormLabel>
|
|
||||||
</div>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="flex justify-end space-x-2 pt-4">
|
|
||||||
<Button type="button" variant="outline" onClick={() => setIsEditDialogOpen(false)}>
|
|
||||||
Annuler
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
type="submit"
|
|
||||||
disabled={editAvoirMutation.isPending}
|
|
||||||
>
|
|
||||||
{editAvoirMutation.isPending ? "Modification..." : "Modifier"}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</Form>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
|
|
||||||
{/* Delete Modal */}
|
|
||||||
<AlertDialog open={isDeleteDialogOpen} onOpenChange={setIsDeleteDialogOpen}>
|
|
||||||
<AlertDialogContent>
|
|
||||||
<AlertDialogHeader>
|
|
||||||
<AlertDialogTitle>Confirmer la suppression</AlertDialogTitle>
|
|
||||||
<AlertDialogDescription>
|
|
||||||
Êtes-vous sûr de vouloir supprimer cet avoir ? Cette action ne peut pas être annulée.
|
|
||||||
{selectedAvoir && (
|
|
||||||
<div className="mt-4 p-3 bg-gray-50 rounded-lg">
|
|
||||||
<div className="text-sm">
|
|
||||||
<div><strong>Fournisseur:</strong> {selectedAvoir?.supplier?.name || 'Fournisseur non défini'}</div>
|
|
||||||
<div><strong>Référence:</strong> {selectedAvoir?.invoiceReference || 'Sans référence'}</div>
|
|
||||||
<div><strong>Montant:</strong> {selectedAvoir?.amount !== null && selectedAvoir?.amount !== undefined ? `${Number(selectedAvoir.amount).toFixed(2)} €` : 'Non spécifié'}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</AlertDialogDescription>
|
|
||||||
</AlertDialogHeader>
|
|
||||||
<AlertDialogFooter>
|
|
||||||
<AlertDialogCancel>Annuler</AlertDialogCancel>
|
|
||||||
<AlertDialogAction
|
|
||||||
onClick={confirmDelete}
|
|
||||||
disabled={deleteAvoirMutation.isPending}
|
|
||||||
className="bg-red-600 hover:bg-red-700"
|
|
||||||
>
|
|
||||||
{deleteAvoirMutation.isPending ? "Suppression..." : "Supprimer"}
|
|
||||||
</AlertDialogAction>
|
|
||||||
</AlertDialogFooter>
|
|
||||||
</AlertDialogContent>
|
|
||||||
</AlertDialog>
|
|
||||||
|
|
||||||
{/* 🌐 Modal d'envoi d'avoir (comme rapprochement) */}
|
|
||||||
<Dialog open={showAvoirModal} onOpenChange={setShowAvoirModal}>
|
|
||||||
<DialogContent className="sm:max-w-[425px]">
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle>Envoyer Fichier Avoir</DialogTitle>
|
|
||||||
</DialogHeader>
|
|
||||||
<div className="grid gap-4 py-4">
|
|
||||||
{selectedAvoirForUpload && (
|
|
||||||
<div className="bg-gray-50 p-3 rounded-md">
|
|
||||||
<div className="text-sm">
|
|
||||||
<div className="font-medium">{selectedAvoirForUpload.supplier?.name || 'Fournisseur non défini'}</div>
|
|
||||||
<div className="text-gray-600">
|
|
||||||
Référence: {selectedAvoirForUpload.invoiceReference || 'Non renseigné'}
|
|
||||||
</div>
|
|
||||||
<div className="text-gray-600">
|
|
||||||
Magasin: {selectedAvoirForUpload?.group?.name || 'Magasin non défini'}
|
|
||||||
</div>
|
|
||||||
<div className="text-gray-600">
|
|
||||||
Montant: {selectedAvoirForUpload?.amount !== null && selectedAvoirForUpload?.amount !== undefined ? `${Number(selectedAvoirForUpload.amount).toFixed(2)} €` : 'Non spécifié'}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="grid gap-2">
|
|
||||||
<Label htmlFor="avoir-pdf-file">Fichier PDF</Label>
|
|
||||||
<input
|
|
||||||
id="avoir-pdf-file"
|
|
||||||
type="file"
|
|
||||||
accept=".pdf"
|
|
||||||
onChange={handleFileChange}
|
|
||||||
className="w-full p-2 border border-gray-300 rounded-md"
|
|
||||||
/>
|
|
||||||
{selectedFile && (
|
|
||||||
<div className="text-sm text-green-600">
|
|
||||||
Fichier sélectionné: {selectedFile.name}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{selectedAvoirForUpload && (
|
|
||||||
<div className="text-xs text-gray-500">
|
|
||||||
Envoi via webhook configuré pour {selectedAvoirForUpload.group?.name || 'magasin'}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<DialogFooter>
|
|
||||||
<Button variant="outline" onClick={handleCloseAvoirModal}>
|
|
||||||
Annuler
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={handleSendAvoir}
|
|
||||||
disabled={!selectedFile || isUploading}
|
|
||||||
>
|
|
||||||
{isUploading ? "Envoi..." : "Envoyer"}
|
|
||||||
</Button>
|
|
||||||
</DialogFooter>
|
|
||||||
</DialogContent>
|
|
||||||
</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 avoir 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>
|
|
||||||
|
|
||||||
{selectedAvoirForUpload && (
|
|
||||||
<div className="bg-blue-50 p-3 rounded-md">
|
|
||||||
<div className="text-sm">
|
|
||||||
<div className="font-medium text-blue-900">
|
|
||||||
{selectedAvoirForUpload.supplier?.name || 'Fournisseur non défini'}
|
|
||||||
</div>
|
|
||||||
<div className="text-blue-700">
|
|
||||||
Référence: {selectedAvoirForUpload.invoiceReference || 'Non renseigné'}
|
|
||||||
</div>
|
|
||||||
<div className="text-blue-700">
|
|
||||||
Fichier: {selectedFile?.name}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
|
|
||||||
{/* Modal de commentaire */}
|
|
||||||
<Dialog open={showCommentModal} onOpenChange={setShowCommentModal}>
|
|
||||||
<DialogContent className="sm:max-w-[425px]">
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle>Commentaire</DialogTitle>
|
|
||||||
<DialogDescription>
|
|
||||||
Détails du commentaire de cet avoir
|
|
||||||
</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
<div className="py-4">
|
|
||||||
<div className="p-4 bg-gray-50 rounded-lg">
|
|
||||||
<p className="text-sm text-gray-700 whitespace-pre-wrap">
|
|
||||||
{selectedComment}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-end">
|
|
||||||
<Button variant="outline" onClick={() => setShowCommentModal(false)}>
|
|
||||||
Fermer
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Reference in new issue
Block a user