Get members and set member list in the chat sidebar

This commit is contained in:
Omar Oughriss 2025-10-20 15:59:04 +02:00
parent 01f2c4a804
commit f5112cea92

View File

@ -28,20 +28,54 @@ import {
Zap,
} from "lucide-react"
import { useSearchParams } from "next/navigation"
import { PairingProcess } from "@/lib/4nk/models/PairingProcess"
interface ChatProps {
heightClass?: string
processes?: any
myProcesses?: string[]
}
export default function Chat({ heightClass = "h-[calc(100vh-8rem)]" }: ChatProps) {
const [selectedConversation, setSelectedConversation] = useState("global")
export default function Chat({ heightClass = "h-[calc(100vh-8rem)]", processes, myProcesses }: ChatProps) {
const [selectedConversation, setSelectedConversation] = useState("")
const [newMessage, setNewMessage] = useState("")
const [pairingProcesses, setPairingProcesses] = useState<PairingProcess[]>([])
const [isLoading, setIsLoading] = useState(true)
const searchParams = useSearchParams()
const userId = searchParams.get("user")
const messageType = searchParams.get("message")
const groupType = searchParams.get("type")
// Filter pairing processes when processes prop changes
useEffect(() => {
if (processes && Object.keys(processes).length > 0) {
setIsLoading(true)
// Filter pairing processes (those with memberPublicName in publicData)
const pairingList: PairingProcess[] = []
Object.entries(processes).forEach(([processId, process]) => {
// Get the latest state
const latestState = process.states?.[process.states.length - 2] // -2 because last state is usually empty
// Check if memberPublicName field exists (even if empty) - indicates pairing process
if (latestState?.public_data?.hasOwnProperty('memberPublicName')) {
const memberPublicName = latestState.public_data.memberPublicName || `Pairing ${processId.slice(0, 8)}`
pairingList.push({
id: processId,
memberPublicName: memberPublicName
})
}
})
setPairingProcesses(pairingList)
setIsLoading(false)
} else {
setIsLoading(true)
setPairingProcesses([])
}
}, [processes, myProcesses])
useEffect(() => {
if (messageType === "new") {
if (userId) {
@ -51,7 +85,6 @@ export default function Chat({ heightClass = "h-[calc(100vh-8rem)]" }: ChatProps
setSelectedConversation(userId)
setNewMessage(`${data.subject ? `[${data.subject}] ` : ""}${data.content}`)
sessionStorage.removeItem("newMessage")
showNotification("info", `Conversation ouverte avec ${data.userName}`)
}
} else if (groupType === "group") {
const groupData = sessionStorage.getItem("newGroupMessage")
@ -60,227 +93,44 @@ export default function Chat({ heightClass = "h-[calc(100vh-8rem)]" }: ChatProps
setSelectedConversation("group-new")
setNewMessage(`${data.subject ? `[${data.subject}] ` : ""}${data.content}`)
sessionStorage.removeItem("newGroupMessage")
showNotification("info", `Conversation de groupe créée avec ${data.users.length} utilisateur(s)`)
}
}
}
}, [userId, messageType, groupType])
const showNotification = (type: "success" | "error" | "info", message: string) => {
console.log(`${type.toUpperCase()}: ${message}`)
}
// Create conversations array with pairing processes only
const conversations = [
{
id: "global",
name: "My Work",
type: "group",
avatar: "MW",
lastMessage: "Bienvenue sur le chat de lespace My Work",
lastMessageTime: "Maintenant",
unreadCount: 0,
isOnline: false,
isTyping: false,
members: 0,
},
{
id: "1",
name: "Marie Dubois",
type: "direct",
avatar: "MD",
lastMessage: "Parfait, merci pour la validation !",
lastMessageTime: "14:32",
...pairingProcesses.map(process => {
// Generate avatar from memberPublicName or processId
let avatar = "P" // Default for Pairing
if (process.memberPublicName && typeof process.memberPublicName === 'string' && process.memberPublicName.trim().length > 0) {
// Use memberPublicName if not empty
avatar = process.memberPublicName.split(' ').map(n => n[0]).join('').toUpperCase().slice(0, 2)
} else {
// Use first 2 chars of processId if memberPublicName is empty
avatar = process.id.slice(0, 2).toUpperCase()
}
// Safe display name
const displayName = typeof process.memberPublicName === 'string' && process.memberPublicName.trim().length > 0
? process.memberPublicName
: `Membre ${process.id.slice(0, 8)}`
return {
id: process.id,
name: displayName,
avatar: avatar,
lastMessage: "",
lastMessageTime: "",
unreadCount: 0,
isOnline: true,
isTyping: false,
},
{
id: "2",
name: "Équipe Juridique",
type: "group",
avatar: "EJ",
lastMessage: "IA DocV: Analyse terminée pour Contrat_Client_ABC.pdf",
lastMessageTime: "13:45",
unreadCount: 1,
isOnline: false,
isTyping: false,
members: 5,
},
{
id: "3",
name: "Sophie Laurent",
type: "direct",
avatar: "SL",
lastMessage: "Pouvez-vous m'envoyer le rapport ?",
lastMessageTime: "12:20",
unreadCount: 1,
isOnline: false,
isTyping: false,
},
{
id: "4",
name: "Direction",
type: "group",
avatar: "DIR",
lastMessage: "Réunion reportée à demain 10h",
lastMessageTime: "11:15",
unreadCount: 0,
isOnline: false,
isTyping: false,
members: 3,
},
{
id: "5",
name: "Thomas Rousseau",
type: "direct",
avatar: "TR",
lastMessage: "Merci pour l'info !",
lastMessageTime: "Hier",
unreadCount: 0,
isOnline: true,
isTyping: true,
},
pairingId: process.id
}
})
]
const messages = [
{
id: "1",
senderId: "marie",
senderName: "Marie Dubois",
content: "Bonjour ! J'ai besoin de votre avis sur le nouveau contrat client.",
timestamp: "14:20",
type: "text",
status: "read",
},
{
id: "2",
senderId: "me",
senderName: "Moi",
content: "Bien sûr, pouvez-vous me l'envoyer ?",
timestamp: "14:22",
type: "text",
status: "read",
},
{
id: "3",
senderId: "marie",
senderName: "Marie Dubois",
content: "",
timestamp: "14:25",
type: "file",
fileName: "Contrat_Client_ABC.pdf",
fileSize: "2.3 MB",
status: "read",
},
{
id: "4",
senderId: "me",
senderName: "Moi",
content:
"J'ai relu le contrat, tout me semble correct. Les clauses de confidentialité sont bien définies.",
timestamp: "14:30",
type: "text",
status: "read",
},
{
id: "5",
senderId: "marie",
senderName: "Marie Dubois",
content: "Parfait, merci pour la validation !",
timestamp: "14:32",
type: "text",
status: "delivered",
},
{
id: "6",
senderId: "ai",
senderName: "IA DocV",
content: `📄 **Analyse IA du document "Contrat_Client_ABC.pdf"**
**Type de document :** PDF (2.3 MB)
**Statut :** Validé
**Dernière modification :** Il y a 2 heures
**📊 Analyse du contenu :**
Document juridique détecté avec haute précision
3 tag(s) identifié(s) : contrat, client, juridique
Résumé automatique disponible
47 pages analysées
12 clauses contractuelles détectées
**🎯 Métriques de qualité :**
Lisibilité : 92%
Conformité juridique : 100%
Sécurité documentaire : Maximale
Complétude des informations : 95%
**🔍 Points clés identifiés :**
Durée du contrat : 12 mois
Montant total : 150 000 HT
Clauses de confidentialité : Présentes et conformes
Propriété intellectuelle : Bien définie
Conditions de résiliation : Équilibrées
**🛡 Analyse de conformité RGPD :**
Données personnelles : Détectées (coordonnées client)
Durée de conservation : Conforme (7 ans)
Droit à l'oubli : Applicable après expiration
Consentement : Explicite
** Recommandations :**
Document prêt pour signature
📋 Archivage permanent recommandé
🔄 Révision suggérée dans 11 mois
📧 Notification client automatique activée
**📈 Score global : 94/100**
*Analyse générée automatiquement par l'IA DocV - Fiabilité : 98%*`,
timestamp: "14:35",
type: "ai_analysis",
status: "delivered",
analysisType: "document",
documentName: "Contrat_Client_ABC.pdf",
confidence: 98,
processingTime: "2.3s",
},
{
id: "7",
senderId: "ai",
senderName: "IA DocV",
content: `🔍 **Analyse comparative - Dossier Contrats**
**📊 Analyse de 8 documents similaires :**
Contrats clients : 5 documents
Avenants : 2 documents
Conditions générales : 1 document
**📈 Tendances identifiées :**
Montant moyen des contrats : +15% vs trimestre précédent
Durée moyenne : 14 mois (stable)
Taux de renouvellement : 87% ( +5%)
** Points d'attention :**
2 contrats expirent dans les 30 jours
1 clause de révision tarifaire à activer
Mise à jour RGPD requise sur 3 documents
**🎯 Actions recommandées :**
1. Planifier renouvellement contrats Q1 2024
2. Standardiser les clauses de confidentialité
3. Créer un modèle basé sur ce contrat (performance optimale)
*Analyse prédictive activée - Prochaine révision : 15 février 2024*`,
timestamp: "14:37",
type: "ai_analysis",
status: "delivered",
analysisType: "comparative",
confidence: 95,
processingTime: "4.1s",
},
]
const filteredConversations = conversations
const messages: any[] = []
const currentConversation = conversations.find((conv) => conv.id === selectedConversation)
@ -389,11 +239,17 @@ export default function Chat({ heightClass = "h-[calc(100vh-8rem)]" }: ChatProps
</div>
<div className="flex-1 overflow-y-auto">
{filteredConversations.map((conversation) => (
{isLoading ? (
<div className="flex items-center justify-center p-8">
<div className="text-gray-500 dark:text-gray-400">Chargement des processus de pairing...</div>
</div>
) : filteredConversations.length > 0 ? (
filteredConversations.map((conversation) => (
<div
key={conversation.id}
onClick={() => setSelectedConversation(conversation.id)}
className={`p-4 border-b cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-700 ${selectedConversation === conversation.id
className={`p-4 border-b cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-700 ${
selectedConversation === conversation.id
? "bg-blue-50 dark:bg-blue-900 border-r-2 border-blue-500 dark:border-blue-400"
: ""
}`}
@ -401,46 +257,20 @@ export default function Chat({ heightClass = "h-[calc(100vh-8rem)]" }: ChatProps
<div className="flex items-start space-x-3">
<div className="relative">
<div className="w-12 h-12 bg-blue-100 dark:bg-blue-800 rounded-full flex items-center justify-center">
{conversation.type === "group" ? (
<Users className="h-6 w-6 text-blue-600 dark:text-blue-400" />
) : (
<span className="text-blue-600 dark:text-blue-400 font-medium">{conversation.avatar}</span>
)}
</div>
{conversation.isOnline && conversation.type === "direct" && (
{conversation.isOnline && (
<Circle className="absolute -bottom-1 -right-1 h-4 w-4 text-green-500 fill-current" />
)}
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center justify-between">
<h3 className="font-medium text-gray-900 dark:text-gray-100 truncate">{conversation.name}</h3>
<span className="text-xs text-gray-500 dark:text-gray-400">{conversation.lastMessageTime}</span>
</div>
<div className="flex items-center justify-between mt-1">
<p className="text-sm text-gray-600 dark:text-gray-300 truncate">
{conversation.isTyping ? (
<span className="text-blue-600 dark:text-blue-400 italic">En train d'écrire...</span>
) : (
<span
className={conversation.lastMessage.includes("IA DocV:") ? "text-purple-600 dark:text-purple-400 font-medium" : ""}
>
{conversation.lastMessage}
</span>
)}
{'pairingId' in conversation && (
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1 font-mono">
ID: {conversation.pairingId.slice(0, 8)}...{conversation.pairingId.slice(-4)}
</p>
{conversation.unreadCount > 0 && (
<Badge
className={`text-white text-xs px-2 py-1 rounded-full ${conversation.lastMessage.includes("IA DocV:")
? "bg-purple-600 dark:bg-purple-400"
: "bg-blue-600 dark:bg-blue-400"
}`}
>
{conversation.unreadCount}
</Badge>
)}
</div>
{conversation.type === "group" && (
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">{conversation.members} membres</p>
)}
</div>
</div>
@ -457,24 +287,16 @@ export default function Chat({ heightClass = "h-[calc(100vh-8rem)]" }: ChatProps
<div className="flex items-center space-x-3">
<div className="relative">
<div className="w-10 h-10 bg-blue-100 dark:bg-blue-800 rounded-full flex items-center justify-center">
{currentConversation.type === "group" ? (
<Users className="h-5 w-5 text-blue-600 dark:text-blue-400" />
) : (
<span className="text-blue-600 dark:text-blue-400 font-medium">{currentConversation.avatar}</span>
)}
</div>
{currentConversation.isOnline && currentConversation.type === "direct" && (
{currentConversation.isOnline && (
<Circle className="absolute -bottom-1 -right-1 h-3 w-3 text-green-500 fill-current" />
)}
</div>
<div>
<h3 className="font-medium text-gray-900 dark:text-gray-100">{currentConversation.name}</h3>
<p className="text-sm text-gray-500 dark:text-gray-400">
{currentConversation.type === "group"
? `${currentConversation.members} membres`
: currentConversation.isOnline
? "En ligne"
: "Hors ligne"}
{currentConversation.isOnline ? "En ligne" : "Hors ligne"}
</p>
</div>
</div>
@ -487,44 +309,13 @@ export default function Chat({ heightClass = "h-[calc(100vh-8rem)]" }: ChatProps
</div>
<div className="flex-1 overflow-y-auto p-4 space-y-4 bg-gray-50 dark:bg-gray-900">
{messages.map((message) => (
<div key={message.id}>
{message.type === "ai_analysis" ? (
renderAIMessage(message)
) : (
<div className={`flex ${message.senderId === "me" ? "justify-end" : "justify-start"}`}>
<div
className={`max-w-xs lg:max-w-md px-4 py-2 rounded-lg ${message.senderId === "me"
? "bg-blue-600 text-white"
: "bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 shadow-sm"
}`}
>
{message.type === "text" ? (
<p className="text-sm">{message.content}</p>
) : message.type === "file" ? (
<div className="flex items-center space-x-3 p-2">
<File className="h-8 w-8 text-gray-400 dark:text-gray-500" />
<div className="flex-1">
<p className="text-sm font-medium">{message.fileName}</p>
<p className="text-xs text-gray-500 dark:text-gray-400">{message.fileSize}</p>
</div>
<Button variant="outline" size="sm">
<Download className="h-4 w-4" />
</Button>
</div>
) : null}
<div
className={`flex items-center justify-between mt-1 ${message.senderId === "me" ? "text-blue-100" : "text-gray-500 dark:text-gray-400"
}`}
>
<span className="text-xs">{message.timestamp}</span>
{message.senderId === "me" && <div className="ml-2">{getStatusIcon(message.status)}</div>}
<div className="flex items-center justify-center h-full">
<div className="text-center">
<MessageSquare className="h-12 w-12 mx-auto text-gray-400 dark:text-gray-500 mb-4" />
<h3 className="text-lg font-medium text-gray-900 dark:text-gray-100 mb-2">Aucun message</h3>
<p className="text-gray-600 dark:text-gray-400">Commencez une conversation en envoyant un message</p>
</div>
</div>
</div>
)}
</div>
))}
</div>
<div className="p-4 border-t border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800">