mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-04-05 12:41:27 +00:00
234 lines
6.4 KiB
JavaScript
234 lines
6.4 KiB
JavaScript
import React, { useState, useRef, useEffect } from 'react';
|
|
import { Send, Paperclip } from 'lucide-react';
|
|
import FileUpload from './FileUpload';
|
|
import { uploadFile } from '@/app/actions/messagerieAction';
|
|
import logger from '@/utils/logger';
|
|
|
|
const MessageInput = ({
|
|
onSendMessage,
|
|
onTypingStart,
|
|
onTypingStop,
|
|
disabled = false,
|
|
placeholder = 'Tapez votre message...',
|
|
conversationId = null,
|
|
senderId = null,
|
|
}) => {
|
|
const [message, setMessage] = useState('');
|
|
const [isTyping, setIsTyping] = useState(false);
|
|
const [selectedFile, setSelectedFile] = useState(null);
|
|
const [showFileUpload, setShowFileUpload] = useState(false);
|
|
const textareaRef = useRef(null);
|
|
const typingTimeoutRef = useRef(null);
|
|
|
|
// Ajuster la hauteur du textarea automatiquement
|
|
useEffect(() => {
|
|
if (textareaRef.current) {
|
|
textareaRef.current.style.height = 'auto';
|
|
textareaRef.current.style.height = `${Math.min(textareaRef.current.scrollHeight, 120)}px`;
|
|
}
|
|
}, [message]);
|
|
|
|
const handleInputChange = (e) => {
|
|
const value = e.target.value;
|
|
setMessage(value);
|
|
|
|
// Gestion du statut de frappe
|
|
if (value.trim() && !isTyping) {
|
|
setIsTyping(true);
|
|
onTypingStart?.();
|
|
}
|
|
|
|
// Réinitialiser le timeout de frappe
|
|
if (typingTimeoutRef.current) {
|
|
clearTimeout(typingTimeoutRef.current);
|
|
}
|
|
|
|
typingTimeoutRef.current = setTimeout(() => {
|
|
if (isTyping) {
|
|
setIsTyping(false);
|
|
onTypingStop?.();
|
|
}
|
|
}, 1000);
|
|
};
|
|
|
|
const handleKeyDown = (e) => {
|
|
if (e.key === 'Enter' && !e.shiftKey) {
|
|
e.preventDefault();
|
|
handleSend();
|
|
}
|
|
};
|
|
|
|
const handleSend = () => {
|
|
const trimmedMessage = message.trim();
|
|
logger.debug(' MessageInput: handleSend appelé:', {
|
|
message,
|
|
trimmedMessage,
|
|
disabled,
|
|
});
|
|
|
|
if (!trimmedMessage || disabled) {
|
|
logger.debug(' MessageInput: Message vide ou désactivé');
|
|
return;
|
|
}
|
|
|
|
logger.debug(
|
|
' MessageInput: Appel de onSendMessage avec:',
|
|
trimmedMessage
|
|
);
|
|
onSendMessage(trimmedMessage);
|
|
setMessage('');
|
|
|
|
// Arrêter le statut de frappe
|
|
if (isTyping) {
|
|
setIsTyping(false);
|
|
onTypingStop?.();
|
|
}
|
|
|
|
// Effacer le timeout
|
|
if (typingTimeoutRef.current) {
|
|
clearTimeout(typingTimeoutRef.current);
|
|
}
|
|
};
|
|
|
|
const handleFileSelect = (e) => {
|
|
const file = e.target.files[0];
|
|
if (file) {
|
|
setSelectedFile(file);
|
|
setShowFileUpload(true);
|
|
}
|
|
// Réinitialiser l'input file
|
|
e.target.value = '';
|
|
};
|
|
|
|
const handleFileUpload = async (
|
|
file,
|
|
conversationId,
|
|
senderId,
|
|
onProgress
|
|
) => {
|
|
try {
|
|
const result = await uploadFile(
|
|
file,
|
|
conversationId,
|
|
senderId,
|
|
onProgress
|
|
);
|
|
|
|
// Envoyer un message avec le fichier
|
|
onSendMessage('', {
|
|
type: 'file',
|
|
fileName: file.name,
|
|
fileSize: file.size,
|
|
fileType: file.type,
|
|
fileUrl: result.fileUrl,
|
|
});
|
|
|
|
// Réinitialiser l'état
|
|
setSelectedFile(null);
|
|
setShowFileUpload(false);
|
|
|
|
return result;
|
|
} catch (error) {
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
const handleCancelFileUpload = () => {
|
|
setSelectedFile(null);
|
|
setShowFileUpload(false);
|
|
};
|
|
|
|
// Nettoyage du timeout lors du démontage du composant
|
|
useEffect(() => {
|
|
return () => {
|
|
if (typingTimeoutRef.current) {
|
|
clearTimeout(typingTimeoutRef.current);
|
|
}
|
|
};
|
|
}, []);
|
|
|
|
return (
|
|
<div className="border-t border-gray-200 bg-white">
|
|
{/* Aperçu d'upload de fichier */}
|
|
{showFileUpload && selectedFile && (
|
|
<div className="p-4 border-b border-gray-200">
|
|
<FileUpload
|
|
file={selectedFile}
|
|
onUpload={handleFileUpload}
|
|
onCancel={handleCancelFileUpload}
|
|
conversationId={conversationId}
|
|
senderId={senderId}
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
{/* Zone de saisie */}
|
|
<div className="p-4">
|
|
<div className="flex items-end space-x-3">
|
|
{/* Zone de saisie */}
|
|
<div className="flex-1 relative">
|
|
<textarea
|
|
ref={textareaRef}
|
|
value={message}
|
|
onChange={handleInputChange}
|
|
onKeyDown={handleKeyDown}
|
|
placeholder={placeholder}
|
|
disabled={disabled}
|
|
className="w-full px-4 py-3 bg-gray-50 border border-gray-200 rounded-xl resize-none focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all"
|
|
style={{ minHeight: '44px', maxHeight: '120px' }}
|
|
/>
|
|
</div>
|
|
|
|
{/* Boutons à droite (trombone au-dessus, envoi en dessous) */}
|
|
<div className="flex flex-col space-y-2 flex-shrink-0">
|
|
{/* Bouton d'ajout de fichier */}
|
|
<div className="relative">
|
|
<input
|
|
type="file"
|
|
id="file-upload"
|
|
className="hidden"
|
|
onChange={handleFileSelect}
|
|
accept="image/*,application/pdf,.doc,.docx,.xls,.xlsx,.txt"
|
|
/>
|
|
<label
|
|
htmlFor="file-upload"
|
|
className="flex items-center justify-center w-10 h-10 text-gray-500 hover:text-gray-700 hover:bg-gray-100 rounded-full cursor-pointer transition-colors"
|
|
>
|
|
<Paperclip className="w-5 h-5" />
|
|
</label>
|
|
</div>
|
|
|
|
{/* Bouton d'envoi */}
|
|
<button
|
|
onClick={handleSend}
|
|
disabled={!message.trim() || disabled}
|
|
className={`flex items-center justify-center w-10 h-10 rounded-full transition-all ${
|
|
message.trim() && !disabled
|
|
? 'bg-blue-500 hover:bg-blue-600 text-white shadow-lg hover:shadow-xl'
|
|
: 'bg-gray-300 text-gray-500 cursor-not-allowed'
|
|
}`}
|
|
>
|
|
<Send className="w-5 h-5" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Indicateur de limite de caractères (optionnel) */}
|
|
{message.length > 900 && (
|
|
<div className="mt-2 text-right">
|
|
<span
|
|
className={`text-xs ${
|
|
message.length > 1000 ? 'text-red-500' : 'text-yellow-500'
|
|
}`}
|
|
>
|
|
{message.length}/1000
|
|
</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default MessageInput;
|