- organization_id -> org_id - api_name -> name (puis slugification) - Ajouter logs détaillés pour debug - Améliorer la gestion des cas où org_id est null
201 lines
6.3 KiB
TypeScript
201 lines
6.3 KiB
TypeScript
// components/staff/contracts/ManualSignedContractUpload.tsx
|
|
"use client";
|
|
|
|
import { useState } from "react";
|
|
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
|
import { Button } from "@/components/ui/button";
|
|
import { toast } from "sonner";
|
|
import { Upload, FileText, Loader2, CheckCircle2, AlertCircle } from "lucide-react";
|
|
|
|
interface ManualSignedContractUploadProps {
|
|
contractId: string;
|
|
contractNumber: string;
|
|
open: boolean;
|
|
onOpenChange: (open: boolean) => void;
|
|
onSuccess?: () => void;
|
|
}
|
|
|
|
export function ManualSignedContractUpload({
|
|
contractId,
|
|
contractNumber,
|
|
open,
|
|
onOpenChange,
|
|
onSuccess,
|
|
}: ManualSignedContractUploadProps) {
|
|
const [selectedFile, setSelectedFile] = useState<File | null>(null);
|
|
const [uploading, setUploading] = useState(false);
|
|
const [uploadSuccess, setUploadSuccess] = useState(false);
|
|
|
|
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
const file = event.target.files?.[0];
|
|
if (file) {
|
|
if (file.type !== "application/pdf") {
|
|
toast.error("Le fichier doit être un PDF");
|
|
return;
|
|
}
|
|
if (file.size > 10 * 1024 * 1024) {
|
|
toast.error("Le fichier ne doit pas dépasser 10 Mo");
|
|
return;
|
|
}
|
|
setSelectedFile(file);
|
|
setUploadSuccess(false);
|
|
}
|
|
};
|
|
|
|
const handleUpload = async () => {
|
|
if (!selectedFile) {
|
|
toast.error("Veuillez sélectionner un fichier PDF");
|
|
return;
|
|
}
|
|
|
|
console.log("📤 [UPLOAD CLIENT] Début upload pour contrat:", contractId);
|
|
setUploading(true);
|
|
|
|
try {
|
|
const formData = new FormData();
|
|
formData.append("file", selectedFile);
|
|
|
|
const url = `/api/staff/contrats/${contractId}/upload-signed-pdf`;
|
|
console.log("📤 [UPLOAD CLIENT] URL de l'API:", url);
|
|
|
|
const response = await fetch(url, {
|
|
method: "POST",
|
|
body: formData,
|
|
});
|
|
|
|
console.log("📤 [UPLOAD CLIENT] Réponse reçue, status:", response.status);
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json();
|
|
console.error("❌ [UPLOAD CLIENT] Erreur:", errorData);
|
|
throw new Error(errorData.error || "Erreur lors de l'upload");
|
|
}
|
|
|
|
const result = await response.json();
|
|
console.log("✅ [UPLOAD CLIENT] Upload réussi:", result);
|
|
|
|
setUploadSuccess(true);
|
|
toast.success("Contrat signé uploadé avec succès");
|
|
|
|
// Attendre 1 seconde puis fermer et notifier le succès
|
|
setTimeout(() => {
|
|
onOpenChange(false);
|
|
if (onSuccess) {
|
|
onSuccess();
|
|
}
|
|
// Réinitialiser l'état
|
|
setSelectedFile(null);
|
|
setUploadSuccess(false);
|
|
}, 1000);
|
|
|
|
} catch (error) {
|
|
console.error("Erreur lors de l'upload:", error);
|
|
toast.error(error instanceof Error ? error.message : "Erreur lors de l'upload");
|
|
} finally {
|
|
setUploading(false);
|
|
}
|
|
};
|
|
|
|
const handleClose = () => {
|
|
if (!uploading) {
|
|
setSelectedFile(null);
|
|
setUploadSuccess(false);
|
|
onOpenChange(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<Dialog open={open} onOpenChange={handleClose}>
|
|
<DialogContent className="sm:max-w-md">
|
|
<DialogHeader>
|
|
<DialogTitle className="flex items-center gap-2">
|
|
<Upload className="size-5" />
|
|
Upload manuel du contrat signé
|
|
</DialogTitle>
|
|
</DialogHeader>
|
|
|
|
<div className="space-y-4 py-4">
|
|
<div className="text-sm text-muted-foreground">
|
|
<p>Contrat: <span className="font-medium text-foreground">{contractNumber}</span></p>
|
|
<p className="mt-2">
|
|
Uploadez le PDF du contrat signé. Il sera stocké dans S3 au même emplacement
|
|
qu'un contrat signé via Docuseal.
|
|
</p>
|
|
</div>
|
|
|
|
<div className="space-y-3">
|
|
<label
|
|
htmlFor="pdf-upload"
|
|
className="flex flex-col items-center justify-center w-full h-32 border-2 border-dashed border-gray-300 rounded-lg cursor-pointer bg-gray-50 hover:bg-gray-100 transition-colors"
|
|
>
|
|
<div className="flex flex-col items-center justify-center pt-5 pb-6">
|
|
{selectedFile ? (
|
|
<>
|
|
<FileText className="size-8 text-green-600 mb-2" />
|
|
<p className="text-sm text-gray-600">
|
|
{selectedFile.name}
|
|
</p>
|
|
<p className="text-xs text-gray-500">
|
|
{(selectedFile.size / 1024 / 1024).toFixed(2)} Mo
|
|
</p>
|
|
</>
|
|
) : (
|
|
<>
|
|
<Upload className="size-8 text-gray-400 mb-2" />
|
|
<p className="text-sm text-gray-600">
|
|
Cliquez pour sélectionner un PDF
|
|
</p>
|
|
<p className="text-xs text-gray-500">
|
|
Maximum 10 Mo
|
|
</p>
|
|
</>
|
|
)}
|
|
</div>
|
|
<input
|
|
id="pdf-upload"
|
|
type="file"
|
|
accept="application/pdf"
|
|
className="hidden"
|
|
onChange={handleFileChange}
|
|
disabled={uploading || uploadSuccess}
|
|
/>
|
|
</label>
|
|
|
|
{uploadSuccess && (
|
|
<div className="flex items-center gap-2 text-sm text-green-600 bg-green-50 p-3 rounded-lg">
|
|
<CheckCircle2 className="size-4" />
|
|
<span>Upload réussi</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<div className="flex justify-end gap-2 pt-2">
|
|
<Button
|
|
variant="outline"
|
|
onClick={handleClose}
|
|
disabled={uploading}
|
|
>
|
|
Annuler
|
|
</Button>
|
|
<Button
|
|
onClick={handleUpload}
|
|
disabled={!selectedFile || uploading || uploadSuccess}
|
|
>
|
|
{uploading ? (
|
|
<>
|
|
<Loader2 className="size-4 mr-2 animate-spin" />
|
|
Upload en cours...
|
|
</>
|
|
) : (
|
|
<>
|
|
<Upload className="size-4 mr-2" />
|
|
Uploader
|
|
</>
|
|
)}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</DialogContent>
|
|
</Dialog>
|
|
);
|
|
}
|