272 lines
9.5 KiB
TypeScript
272 lines
9.5 KiB
TypeScript
"use client"
|
|
|
|
import { useState, useEffect, useCallback } from "react"
|
|
import { Badge } from "@/components/ui/badge"
|
|
import { Button } from "@/components/ui/button"
|
|
import { Textarea } from "@/components/ui/textarea"
|
|
import {
|
|
Send,
|
|
Paperclip,
|
|
Smile,
|
|
MoreHorizontal,
|
|
Folder,
|
|
MessageSquare
|
|
} from "lucide-react"
|
|
import MessageBus from "@/lib/4nk/MessageBus"
|
|
import { iframeUrl } from "@/app/page"
|
|
import { FolderChatData } from "@/lib/4nk/models/FolderData"
|
|
import { use4NK, EnrichedFolderData } from "@/lib/contexts/FourNKContext"
|
|
|
|
// Interface pour les props (accepte null)
|
|
interface FolderChatProps {
|
|
folder: EnrichedFolderData | null;
|
|
}
|
|
|
|
export default function FolderChat({ folder }: FolderChatProps) {
|
|
const [newMessage, setNewMessage] = useState("")
|
|
const [activeTab, setActiveTab] = useState<'owner' | 'general'>('owner');
|
|
const [ownerMessages, setOwnerMessages] = useState<FolderChatData[]>([]);
|
|
const [generalMessages, setGeneralMessages] = useState<FolderChatData[]>([]);
|
|
|
|
const {
|
|
userPairingId,
|
|
setFolderProcesses,
|
|
setFolderPrivateData,
|
|
} = use4NK();
|
|
|
|
useEffect(() => {
|
|
setOwnerMessages(folder?.messages_owner || []);
|
|
setGeneralMessages(folder?.messages || []);
|
|
}, [folder]);
|
|
|
|
// Filtre les messages basé sur l'onglet actif
|
|
const filteredMessages = activeTab === 'owner' ? ownerMessages : generalMessages;
|
|
|
|
const handleProcessUpdate = useCallback(async (processId: string, key: string, value: any) => {
|
|
// Note : 'value' est l'objet newMessageData que vous avez passé
|
|
try {
|
|
const messageBus = MessageBus.getInstance(iframeUrl);
|
|
await messageBus.isReady();
|
|
|
|
const updateData = {
|
|
[key]: value
|
|
};
|
|
|
|
// 1. Mettre à jour le process
|
|
const updatedProcess = await messageBus.updateProcess(processId, updateData, [], null);
|
|
console.log("Process mis à jour :", updatedProcess);
|
|
|
|
if (!updatedProcess) {
|
|
throw new Error('updateProcess n\'a pas retourné de process mis à jour');
|
|
}
|
|
|
|
// 2. Extraire le newStateId
|
|
const newStateId = updatedProcess.diffs[0]?.state_id;
|
|
if (!newStateId) {
|
|
throw new Error('No new state id found');
|
|
}
|
|
|
|
// 3. Notifier et Valider
|
|
await messageBus.notifyProcessUpdate(processId, newStateId);
|
|
await messageBus.validateState(processId, newStateId);
|
|
|
|
// 4. Mettre à jour l'objet process dans le contexte
|
|
setFolderProcesses((prevProcesses: any) => ({
|
|
...prevProcesses,
|
|
[processId]: updatedProcess.current_process
|
|
}));
|
|
|
|
// 5. Mettre à jour le cache des données privées (CORRIGÉ)
|
|
|
|
// D'abord, convertir l'objet 'value' en Map, comme l'attend loadFoldersFrom4NK
|
|
const valueAsMap = new Map(Object.entries(value));
|
|
|
|
// Ensuite, créer l'objet conteneur structuré
|
|
const privateDataForCache = {
|
|
[key]: valueAsMap
|
|
};
|
|
|
|
// Enfin, stocker cet objet structuré dans le cache
|
|
setFolderPrivateData((prevData) => ({
|
|
...prevData,
|
|
[newStateId]: privateDataForCache // <-- Utiliser l'objet formaté
|
|
}));
|
|
|
|
console.log('Process & cache de données privées mis à jour avec succès.');
|
|
|
|
} catch (error) {
|
|
console.error('Error updating field:', error);
|
|
}
|
|
}, [setFolderProcesses, setFolderPrivateData]);
|
|
|
|
const handleSendMessage = useCallback(() => {
|
|
if (newMessage.trim() && folder) {
|
|
console.log(`Envoi message [${activeTab}] dans le dossier:`, folder?.name, "Msg:", newMessage)
|
|
const key = activeTab === 'owner' ? 'messages_owner' : 'messages'
|
|
|
|
const newMessageData: FolderChatData = {
|
|
timestamp: Date.now(),
|
|
sender: (userPairingId ? userPairingId : ''),
|
|
receiver: '',
|
|
fromRole: 'owner',
|
|
toRole: 'owner',
|
|
message: newMessage,
|
|
}
|
|
|
|
if (activeTab === 'owner') {
|
|
setOwnerMessages(prevMessages => [...prevMessages, newMessageData]);
|
|
} else {
|
|
setGeneralMessages(prevMessages => [...prevMessages, newMessageData]);
|
|
}
|
|
|
|
// Appelle la fonction mémorisée
|
|
handleProcessUpdate(folder.processId, key, newMessageData)
|
|
|
|
setNewMessage("")
|
|
}
|
|
}, [
|
|
// La fonction doit être recréée si une de ces valeurs change :
|
|
newMessage,
|
|
folder,
|
|
activeTab,
|
|
userPairingId,
|
|
handleProcessUpdate, // <-- Mettez la fonction mémorisée ici
|
|
setOwnerMessages,
|
|
setGeneralMessages,
|
|
setNewMessage
|
|
]);
|
|
|
|
// Si aucun dossier n'est sélectionné, afficher un placeholder
|
|
if (!folder) {
|
|
return (
|
|
<div className="flex h-full items-center justify-center bg-gray-800 text-gray-500 p-6">
|
|
<div className="text-center">
|
|
<MessageSquare className="h-12 w-12 mx-auto mb-4" />
|
|
<h3 className="text-lg font-medium text-gray-100 mb-2">
|
|
Chat de dossier
|
|
</h3>
|
|
<p className="text-gray-400">
|
|
Sélectionnez un dossier pour voir la conversation
|
|
</p>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// Si un dossier EST sélectionné, afficher le chat complet
|
|
return (
|
|
<div className="flex flex-col h-full bg-gray-800 text-gray-100">
|
|
|
|
{/* En-tête du chat */}
|
|
<div className="p-4 border-b border-gray-700">
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex items-center space-x-3">
|
|
<div className="w-10 h-10 bg-green-800 rounded-full flex items-center justify-center flex-shrink-0">
|
|
<Folder className="h-5 w-5 text-green-400" />
|
|
</div>
|
|
<div>
|
|
<h3 className="font-medium text-gray-100">
|
|
{folder.name}
|
|
</h3>
|
|
{/* ID du dossier supprimé */}
|
|
</div>
|
|
</div>
|
|
<Button variant="ghost" size="sm" className="text-gray-400 hover:text-gray-100 hover:bg-gray-700">
|
|
<MoreHorizontal className="h-4 w-4" />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Onglets "Owner" / "General" */}
|
|
<div className="p-2 flex border-b border-gray-700 bg-gray-900">
|
|
<Button
|
|
variant={activeTab === 'owner' ? "secondary" : "ghost"}
|
|
size="sm"
|
|
onClick={() => setActiveTab('owner')}
|
|
className={`flex-1 ${activeTab === 'owner' ? 'bg-gray-700 text-white' : 'text-gray-400 hover:text-white'}`}
|
|
>
|
|
Propriétaires
|
|
</Button>
|
|
<Button
|
|
variant={activeTab === 'general' ? "secondary" : "ghost"}
|
|
size="sm"
|
|
onClick={() => setActiveTab('general')}
|
|
className={`flex-1 ${activeTab === 'general' ? 'bg-gray-700 text-white' : 'text-gray-400 hover:text-white'}`}
|
|
>
|
|
Général
|
|
</Button>
|
|
</div>
|
|
|
|
{/* Zone des messages */}
|
|
<div className="flex-1 overflow-y-auto p-4 space-y-4 bg-gray-900">
|
|
{filteredMessages.length > 0 ? filteredMessages.map((msg, i) => (
|
|
<div
|
|
key={i}
|
|
className={`flex items-start gap-3 ${msg.sender === userPairingId ? 'justify-end' : ''}`}
|
|
>
|
|
{msg.sender != userPairingId && (
|
|
<div className="w-8 h-8 bg-blue-800 rounded-full flex items-center justify-center flex-shrink-0">
|
|
<span className="text-xs text-blue-300 font-medium">{msg.sender.slice(0, 2)}</span>
|
|
</div>
|
|
)}
|
|
<div>
|
|
<div
|
|
className={`p-3 rounded-lg ${msg.sender === 'me'
|
|
? 'bg-blue-600 text-white rounded-br-none'
|
|
: 'bg-gray-700 text-gray-100 rounded-bl-none'
|
|
}`}
|
|
>
|
|
{msg.sender != userPairingId && (
|
|
<p className="text-xs font-medium text-blue-300 mb-1">KAAK</p>
|
|
)}
|
|
<p>{msg.message}</p>
|
|
</div>
|
|
<p className={`text-xs text-gray-500 mt-1 ${msg.sender === userPairingId ? 'text-right' : ''}`}>
|
|
{new Date(Number(msg.timestamp)).toLocaleTimeString('fr-FR', {
|
|
hour: '2-digit',
|
|
minute: '2-digit'
|
|
})}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
)) : (
|
|
<div className="flex h-full items-center justify-center text-center text-gray-500 p-4">
|
|
<p>Aucun message dans le chat "{activeTab}"</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Input de message */}
|
|
<div className="p-4 border-t border-gray-700">
|
|
<div className="flex items-end space-x-2">
|
|
<Button variant="ghost" size="sm" className="text-gray-400 hover:text-gray-100 hover:bg-gray-700">
|
|
<Paperclip className="h-5 w-5" />
|
|
</Button>
|
|
<Button variant="ghost" size="sm" className="text-gray-400 hover:text-gray-100 hover:bg-gray-700">
|
|
<Smile className="h-5 w-5" />
|
|
</Button>
|
|
<Textarea
|
|
placeholder={`Message (${activeTab})...`}
|
|
value={newMessage}
|
|
onChange={(e) => setNewMessage(e.target.value)}
|
|
onKeyPress={(e) => {
|
|
if (e.key === "Enter" && !e.shiftKey) {
|
|
e.preventDefault();
|
|
handleSendMessage();
|
|
}
|
|
}}
|
|
rows={1}
|
|
className="resize-none flex-1 bg-gray-700 border-gray-700 text-gray-100 placeholder-gray-400 focus:border-blue-500 focus:ring-0"
|
|
/>
|
|
<Button
|
|
onClick={handleSendMessage}
|
|
disabled={!newMessage.trim()}
|
|
className="bg-blue-600 hover:bg-blue-700 text-white"
|
|
>
|
|
<Send className="h-4 w-4" />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
} |