Compare commits
4 Commits
085713400c
...
7982f7b300
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7982f7b300 | ||
|
|
22a1052080 | ||
|
|
5c49d4eb67 | ||
|
|
8fcf7254a2 |
@ -1,79 +0,0 @@
|
|||||||
import { Skeleton } from "@/components/ui/skeleton"
|
|
||||||
|
|
||||||
export default function ChatLoading() {
|
|
||||||
return (
|
|
||||||
<div className="h-[calc(100vh-8rem)] flex">
|
|
||||||
{/* Sidebar */}
|
|
||||||
<div className="w-80 border-r bg-white flex flex-col">
|
|
||||||
<div className="p-4 border-b">
|
|
||||||
<div className="flex items-center justify-between mb-4">
|
|
||||||
<Skeleton className="h-6 w-24" />
|
|
||||||
<Skeleton className="h-8 w-8" />
|
|
||||||
</div>
|
|
||||||
<Skeleton className="h-10 w-full" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex-1 overflow-y-auto">
|
|
||||||
{[...Array(5)].map((_, i) => (
|
|
||||||
<div key={i} className="p-4 border-b">
|
|
||||||
<div className="flex items-start space-x-3">
|
|
||||||
<Skeleton className="h-12 w-12 rounded-full" />
|
|
||||||
<div className="flex-1">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<Skeleton className="h-4 w-24" />
|
|
||||||
<Skeleton className="h-3 w-12" />
|
|
||||||
</div>
|
|
||||||
<Skeleton className="h-3 w-32 mt-2" />
|
|
||||||
<Skeleton className="h-3 w-16 mt-1" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Main Chat Area */}
|
|
||||||
<div className="flex-1 flex flex-col">
|
|
||||||
{/* Header */}
|
|
||||||
<div className="p-4 border-b bg-white">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div className="flex items-center space-x-3">
|
|
||||||
<Skeleton className="h-10 w-10 rounded-full" />
|
|
||||||
<div>
|
|
||||||
<Skeleton className="h-4 w-32" />
|
|
||||||
<Skeleton className="h-3 w-20 mt-1" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<Skeleton className="h-8 w-8" />
|
|
||||||
<Skeleton className="h-8 w-8" />
|
|
||||||
<Skeleton className="h-8 w-8" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Messages */}
|
|
||||||
<div className="flex-1 overflow-y-auto p-4 space-y-4 bg-gray-50">
|
|
||||||
{[...Array(6)].map((_, i) => (
|
|
||||||
<div key={i} className={`flex ${i % 2 === 0 ? "justify-start" : "justify-end"}`}>
|
|
||||||
<div className={`max-w-xs lg:max-w-md p-4 rounded-lg ${i % 2 === 0 ? "bg-white" : "bg-blue-600"}`}>
|
|
||||||
<Skeleton className={`h-4 w-48 ${i % 2 !== 0 ? "bg-blue-500" : ""}`} />
|
|
||||||
<Skeleton className={`h-3 w-16 mt-2 ${i % 2 !== 0 ? "bg-blue-500" : ""}`} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Input */}
|
|
||||||
<div className="p-4 border-t bg-white">
|
|
||||||
<div className="flex items-end space-x-2">
|
|
||||||
<Skeleton className="h-8 w-8" />
|
|
||||||
<Skeleton className="h-20 flex-1" />
|
|
||||||
<Skeleton className="h-8 w-8" />
|
|
||||||
<Skeleton className="h-8 w-16" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import Chat from "@/components/4nk/Chat"
|
|
||||||
|
|
||||||
export default function ChatPage() {
|
|
||||||
return <Chat heightClass="h-[calc(100vh-8rem)]" />
|
|
||||||
}
|
|
||||||
@ -14,6 +14,8 @@ import {
|
|||||||
SortAsc,
|
SortAsc,
|
||||||
SortDesc,
|
SortDesc,
|
||||||
X,
|
X,
|
||||||
|
MessageSquare,
|
||||||
|
List,
|
||||||
} from "lucide-react"
|
} from "lucide-react"
|
||||||
import { FolderData, FolderCreated, FolderPrivateFields, setDefaultFolderRoles } from "@/lib/4nk/models/FolderData"
|
import { FolderData, FolderCreated, FolderPrivateFields, setDefaultFolderRoles } from "@/lib/4nk/models/FolderData"
|
||||||
import MessageBus from "@/lib/4nk/MessageBus"
|
import MessageBus from "@/lib/4nk/MessageBus"
|
||||||
@ -22,6 +24,7 @@ import UserStore from "@/lib/4nk/UserStore"
|
|||||||
import FolderModal from "@/components/4nk/FolderModal"
|
import FolderModal from "@/components/4nk/FolderModal"
|
||||||
import AuthModal from "@/components/4nk/AuthModal"
|
import AuthModal from "@/components/4nk/AuthModal"
|
||||||
import Iframe from "@/components/4nk/Iframe"
|
import Iframe from "@/components/4nk/Iframe"
|
||||||
|
import FolderChat from "@/components/4nk/FolderChat"
|
||||||
|
|
||||||
type FolderType = 'contrat' | 'projet' | 'rapport' | 'finance' | 'rh' | 'marketing' | 'autre';
|
type FolderType = 'contrat' | 'projet' | 'rapport' | 'finance' | 'rh' | 'marketing' | 'autre';
|
||||||
|
|
||||||
@ -32,6 +35,7 @@ export default function FoldersPage() {
|
|||||||
const [currentPath, setCurrentPath] = useState<string[]>(["Racine"])
|
const [currentPath, setCurrentPath] = useState<string[]>(["Racine"])
|
||||||
const [folderType, setFolderType] = useState<FolderType | null>(null);
|
const [folderType, setFolderType] = useState<FolderType | null>(null);
|
||||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
|
const [activeTab, setActiveTab] = useState<"folders" | "chat">("folders");
|
||||||
|
|
||||||
// 4NK Integration states
|
// 4NK Integration states
|
||||||
const [isConnected, setIsConnected] = useState(false)
|
const [isConnected, setIsConnected] = useState(false)
|
||||||
@ -356,103 +360,142 @@ export default function FoldersPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
|
{activeTab === "folders" && (
|
||||||
|
<Button
|
||||||
|
onClick={() => handleOpenModal("autre")}
|
||||||
|
disabled={!isConnected}
|
||||||
|
className="flex items-center space-x-2"
|
||||||
|
>
|
||||||
|
<FolderPlus className="h-4 w-4" />
|
||||||
|
<span>Nouveau dossier</span>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Tabs */}
|
||||||
|
<div className="flex items-center space-x-4 mb-4">
|
||||||
|
<div className="flex bg-gray-700 rounded-lg p-1">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleOpenModal("autre")}
|
variant={activeTab === "folders" ? "default" : "ghost"}
|
||||||
disabled={!isConnected}
|
size="sm"
|
||||||
|
onClick={() => setActiveTab("folders")}
|
||||||
className="flex items-center space-x-2"
|
className="flex items-center space-x-2"
|
||||||
>
|
>
|
||||||
<FolderPlus className="h-4 w-4" />
|
<List className="h-4 w-4" />
|
||||||
<span>Nouveau dossier</span>
|
<span>Dossiers</span>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant={activeTab === "chat" ? "default" : "ghost"}
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setActiveTab("chat")}
|
||||||
|
className="flex items-center space-x-2"
|
||||||
|
>
|
||||||
|
<MessageSquare className="h-4 w-4" />
|
||||||
|
<span>Chat</span>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Search and filters */}
|
{/* Search and filters - only show for folders tab */}
|
||||||
<div className="flex items-center space-x-4">
|
{activeTab === "folders" && (
|
||||||
<div className="flex-1 relative">
|
<div className="flex items-center space-x-4">
|
||||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400" />
|
<div className="flex-1 relative">
|
||||||
<Input
|
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400" />
|
||||||
placeholder="Rechercher des dossiers..."
|
<Input
|
||||||
value={searchTerm}
|
placeholder="Rechercher des dossiers..."
|
||||||
onChange={(e) => setSearchTerm(e.target.value)}
|
value={searchTerm}
|
||||||
className="pl-10"
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
/>
|
className="pl-10"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setSortOrder(sortOrder === "asc" ? "desc" : "asc")}
|
||||||
|
>
|
||||||
|
{sortOrder === "asc" ? <SortAsc className="h-4 w-4" /> : <SortDesc className="h-4 w-4" />}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
onClick={() => setSortOrder(sortOrder === "asc" ? "desc" : "asc")}
|
|
||||||
>
|
|
||||||
{sortOrder === "asc" ? <SortAsc className="h-4 w-4" /> : <SortDesc className="h-4 w-4" />}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Content */}
|
{/* Content */}
|
||||||
<div className="flex-1 p-6 bg-gray-900">
|
<div className="flex-1 bg-gray-900">
|
||||||
{loadingFolders ? (
|
{activeTab === "folders" ? (
|
||||||
<div className="flex items-center justify-center h-64">
|
<div className="p-6">
|
||||||
<div className="text-center">
|
{loadingFolders ? (
|
||||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600 mx-auto mb-4"></div>
|
<div className="flex items-center justify-center h-64">
|
||||||
<p className="text-gray-400">Chargement des dossiers...</p>
|
<div className="text-center">
|
||||||
</div>
|
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600 mx-auto mb-4"></div>
|
||||||
</div>
|
<p className="text-gray-400">Chargement des dossiers...</p>
|
||||||
) : sortedFolders.length === 0 ? (
|
</div>
|
||||||
<div className="flex items-center justify-center h-64">
|
</div>
|
||||||
<div className="text-center">
|
) : sortedFolders.length === 0 ? (
|
||||||
<Folder className="h-12 w-12 text-gray-400 mx-auto mb-4" />
|
<div className="flex items-center justify-center h-64">
|
||||||
<h3 className="text-lg font-medium text-gray-100 mb-2">Aucun dossier</h3>
|
<div className="text-center">
|
||||||
<p className="text-gray-400 mb-4">
|
<Folder className="h-12 w-12 text-gray-400 mx-auto mb-4" />
|
||||||
{searchTerm ? "Aucun dossier ne correspond à votre recherche." : "Commencez par créer votre premier dossier."}
|
<h3 className="text-lg font-medium text-gray-100 mb-2">Aucun dossier</h3>
|
||||||
</p>
|
<p className="text-gray-400 mb-4">
|
||||||
{!searchTerm && (
|
{searchTerm ? "Aucun dossier ne correspond à votre recherche." : "Commencez par créer votre premier dossier."}
|
||||||
<Button
|
</p>
|
||||||
onClick={() => handleOpenModal("autre")}
|
{!searchTerm && (
|
||||||
disabled={!isConnected}
|
<Button
|
||||||
>
|
onClick={() => handleOpenModal("autre")}
|
||||||
<FolderPlus className="h-4 w-4 mr-2" />
|
disabled={!isConnected}
|
||||||
Créer un dossier
|
>
|
||||||
</Button>
|
<FolderPlus className="h-4 w-4 mr-2" />
|
||||||
)}
|
Créer un dossier
|
||||||
</div>
|
</Button>
|
||||||
</div>
|
)}
|
||||||
) : (
|
</div>
|
||||||
<div className="space-y-4">
|
</div>
|
||||||
{/* Folder list */}
|
) : (
|
||||||
<div className="space-y-2">
|
<div className="space-y-4">
|
||||||
{sortedFolders.map((folder) => (
|
{/* Folder list */}
|
||||||
<Card key={folder.folderNumber} className="hover:shadow-md transition-shadow bg-gray-800 border border-gray-700">
|
<div className="space-y-2">
|
||||||
<CardContent className="p-4">
|
{sortedFolders.map((folder) => (
|
||||||
<div className="flex items-center justify-between">
|
<Card key={folder.folderNumber} className="hover:shadow-md transition-shadow bg-gray-800 border border-gray-700">
|
||||||
<div className="flex items-center space-x-3">
|
<CardContent className="p-4">
|
||||||
<Folder className="h-5 w-5 text-blue-600" />
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div className="flex items-center space-x-3">
|
||||||
<h3 className="font-medium text-gray-100">{folder.name}</h3>
|
<Folder className="h-5 w-5 text-blue-600" />
|
||||||
<p className="text-sm text-gray-400">{folder.description}</p>
|
<div>
|
||||||
<div className="flex items-center space-x-4 mt-1">
|
<h3 className="font-medium text-gray-100">{folder.name}</h3>
|
||||||
<span className="text-xs text-gray-400">#{folder.folderNumber}</span>
|
<p className="text-sm text-gray-400">{folder.description}</p>
|
||||||
<span className="text-xs text-gray-400">
|
<div className="flex items-center space-x-4 mt-1">
|
||||||
<Clock className="h-3 w-3 inline mr-1" />
|
<span className="text-xs text-gray-400">#{folder.folderNumber}</span>
|
||||||
{new Date(folder.updated_at).toLocaleDateString()}
|
<span className="text-xs text-gray-400">
|
||||||
</span>
|
<Clock className="h-3 w-3 inline mr-1" />
|
||||||
|
{new Date(folder.updated_at).toLocaleDateString()}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
{folder.notes.length > 0 && (
|
||||||
|
<Badge variant="outline">{folder.notes.length} notes</Badge>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</CardContent>
|
||||||
|
</Card>
|
||||||
<div className="flex items-center space-x-2">
|
))}
|
||||||
{folder.notes.length > 0 && (
|
</div>
|
||||||
<Badge variant="outline">{folder.notes.length} notes</Badge>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
) : (
|
||||||
|
<FolderChat
|
||||||
|
heightClass="h-full"
|
||||||
|
folderProcesses={folderProcesses}
|
||||||
|
myFolderProcesses={myFolderProcesses}
|
||||||
|
folderPrivateData={folderPrivateData}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
338
components/4nk/FolderChat.tsx
Normal file
338
components/4nk/FolderChat.tsx
Normal file
@ -0,0 +1,338 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { useState, useEffect } from "react"
|
||||||
|
import { Badge } from "@/components/ui/badge"
|
||||||
|
import { Button } from "@/components/ui/button"
|
||||||
|
import { Input } from "@/components/ui/input"
|
||||||
|
import { Textarea } from "@/components/ui/textarea"
|
||||||
|
import {
|
||||||
|
MessageSquare,
|
||||||
|
Search,
|
||||||
|
Send,
|
||||||
|
Paperclip,
|
||||||
|
Smile,
|
||||||
|
MoreHorizontal,
|
||||||
|
Users,
|
||||||
|
Circle,
|
||||||
|
Folder,
|
||||||
|
Clock,
|
||||||
|
} from "lucide-react"
|
||||||
|
|
||||||
|
interface FolderMember {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
avatar: string
|
||||||
|
isOnline: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FolderConversation {
|
||||||
|
id: string
|
||||||
|
folderNumber: string
|
||||||
|
folderName: string
|
||||||
|
members: FolderMember[]
|
||||||
|
lastMessage: string
|
||||||
|
lastMessageTime: string
|
||||||
|
unreadCount: number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FolderChatProps {
|
||||||
|
heightClass?: string
|
||||||
|
folderProcesses?: any
|
||||||
|
myFolderProcesses?: string[]
|
||||||
|
folderPrivateData?: Record<string, Record<string, any>>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function FolderChat({
|
||||||
|
heightClass = "h-[calc(100vh-8rem)]",
|
||||||
|
folderProcesses,
|
||||||
|
myFolderProcesses = [],
|
||||||
|
folderPrivateData = {}
|
||||||
|
}: FolderChatProps) {
|
||||||
|
const [selectedConversation, setSelectedConversation] = useState("")
|
||||||
|
const [newMessage, setNewMessage] = useState("")
|
||||||
|
const [folderConversations, setFolderConversations] = useState<FolderConversation[]>([])
|
||||||
|
const [isLoading, setIsLoading] = useState(true)
|
||||||
|
const [searchQuery, setSearchQuery] = useState("")
|
||||||
|
|
||||||
|
// Extract folder conversations from processes
|
||||||
|
useEffect(() => {
|
||||||
|
if (folderProcesses && Object.keys(folderProcesses).length > 0) {
|
||||||
|
setIsLoading(true)
|
||||||
|
|
||||||
|
const conversations: FolderConversation[] = []
|
||||||
|
|
||||||
|
Object.entries(folderProcesses).forEach(([processId, process]: [string, any]) => {
|
||||||
|
// Only include processes that belong to the user
|
||||||
|
if (!myFolderProcesses.includes(processId)) return
|
||||||
|
|
||||||
|
const latestState = process.states?.[0]
|
||||||
|
if (!latestState) return
|
||||||
|
|
||||||
|
// Check if this process has a folderNumber (indicates it's a folder process)
|
||||||
|
const folderNumber = latestState.pcd_commitment?.folderNumber
|
||||||
|
if (!folderNumber) return
|
||||||
|
|
||||||
|
// Get private data for folder name
|
||||||
|
const privateData = folderPrivateData[latestState.state_id]
|
||||||
|
const folderName = privateData?.name || `Dossier ${folderNumber}`
|
||||||
|
|
||||||
|
// Extract members from roles.owner.members
|
||||||
|
const ownerMembers = latestState.roles?.owner?.members || []
|
||||||
|
const members: FolderMember[] = ownerMembers.map((memberId: string, index: number) => {
|
||||||
|
// Generate avatar from member ID
|
||||||
|
const avatar = memberId.slice(0, 2).toUpperCase()
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: memberId,
|
||||||
|
name: `Membre ${memberId.slice(0, 8)}`, // Could be enhanced with real names
|
||||||
|
avatar: avatar,
|
||||||
|
isOnline: Math.random() > 0.5 // Random online status for demo
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
conversations.push({
|
||||||
|
id: processId,
|
||||||
|
folderNumber: folderNumber,
|
||||||
|
folderName: folderName,
|
||||||
|
members: members,
|
||||||
|
lastMessage: "",
|
||||||
|
lastMessageTime: "",
|
||||||
|
unreadCount: 0
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
setFolderConversations(conversations)
|
||||||
|
setIsLoading(false)
|
||||||
|
} else {
|
||||||
|
setIsLoading(true)
|
||||||
|
setFolderConversations([])
|
||||||
|
}
|
||||||
|
}, [folderProcesses, myFolderProcesses, folderPrivateData])
|
||||||
|
|
||||||
|
// Filter conversations based on search query
|
||||||
|
const filteredConversations = folderConversations.filter(conversation => {
|
||||||
|
if (!searchQuery.trim()) return true
|
||||||
|
|
||||||
|
const matchesNumber = conversation.folderNumber.toLowerCase().includes(searchQuery.toLowerCase())
|
||||||
|
const matchesName = conversation.folderName.toLowerCase().includes(searchQuery.toLowerCase())
|
||||||
|
const matchesId = conversation.id.toLowerCase().includes(searchQuery.toLowerCase())
|
||||||
|
|
||||||
|
return matchesNumber || matchesName || matchesId
|
||||||
|
})
|
||||||
|
|
||||||
|
const currentConversation = folderConversations.find((conv) => conv.id === selectedConversation)
|
||||||
|
|
||||||
|
const handleSendMessage = () => {
|
||||||
|
if (newMessage.trim()) {
|
||||||
|
console.log("Sending message to folder:", selectedConversation, "Message:", newMessage)
|
||||||
|
// Here implement the actual message sending logic
|
||||||
|
setNewMessage("")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`${heightClass} flex`}>
|
||||||
|
{/* Sidebar with folder conversations */}
|
||||||
|
<div className="w-80 border-r bg-white dark:bg-gray-800 flex flex-col">
|
||||||
|
<div className="p-4 border-b border-gray-200 dark:border-gray-700">
|
||||||
|
<div className="mb-4">
|
||||||
|
<h2 className="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-3">
|
||||||
|
Chat Dossiers
|
||||||
|
</h2>
|
||||||
|
<div className="relative">
|
||||||
|
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400" />
|
||||||
|
<Input
|
||||||
|
placeholder="Rechercher un dossier..."
|
||||||
|
value={searchQuery}
|
||||||
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
|
className="pl-10 bg-gray-50 dark:bg-gray-700 border-gray-200 dark:border-gray-600"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex-1 overflow-y-auto">
|
||||||
|
{isLoading ? (
|
||||||
|
<div className="flex items-center justify-center p-8">
|
||||||
|
<div className="text-gray-500 dark:text-gray-400">Chargement des dossiers...</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
|
||||||
|
? "bg-blue-50 dark:bg-blue-900 border-r-2 border-blue-500 dark:border-blue-400"
|
||||||
|
: ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="flex items-start space-x-3">
|
||||||
|
<div className="relative">
|
||||||
|
<div className="w-12 h-12 bg-green-100 dark:bg-green-800 rounded-full flex items-center justify-center">
|
||||||
|
<Folder className="h-6 w-6 text-green-600 dark:text-green-400" />
|
||||||
|
</div>
|
||||||
|
</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.folderName}
|
||||||
|
</h3>
|
||||||
|
{conversation.members.length > 0 && (
|
||||||
|
<Badge variant="secondary" className="ml-2">
|
||||||
|
<Users className="h-3 w-3 mr-1" />
|
||||||
|
{conversation.members.length}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||||
|
#{conversation.folderNumber}
|
||||||
|
</p>
|
||||||
|
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||||
|
{conversation.members.length} membre{conversation.members.length > 1 ? 's' : ''}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<div className="flex items-center justify-center p-8">
|
||||||
|
<div className="text-center">
|
||||||
|
<Folder className="h-8 w-8 mx-auto text-gray-400 dark:text-gray-500 mb-2" />
|
||||||
|
<p className="text-gray-500 dark:text-gray-400">Aucun dossier trouvé</p>
|
||||||
|
<p className="text-xs text-gray-400 dark:text-gray-500 mt-1">
|
||||||
|
Essayez de rechercher par nom ou numéro
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Main chat area */}
|
||||||
|
<div className="flex-1 flex flex-col">
|
||||||
|
{currentConversation ? (
|
||||||
|
<>
|
||||||
|
{/* Chat header */}
|
||||||
|
<div className="p-4 border-b border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center space-x-3">
|
||||||
|
<div className="w-10 h-10 bg-green-100 dark:bg-green-800 rounded-full flex items-center justify-center">
|
||||||
|
<Folder className="h-5 w-5 text-green-600 dark:text-green-400" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="font-medium text-gray-900 dark:text-gray-100">
|
||||||
|
{currentConversation.folderName}
|
||||||
|
</h3>
|
||||||
|
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||||||
|
Dossier #{currentConversation.folderNumber} • {currentConversation.members.length} membre{currentConversation.members.length > 1 ? 's' : ''}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Button variant="outline" size="sm">
|
||||||
|
<Users className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
<Button variant="outline" size="sm">
|
||||||
|
<MoreHorizontal className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Members list */}
|
||||||
|
{currentConversation.members.length > 0 && (
|
||||||
|
<div className="mt-3 pt-3 border-t border-gray-200 dark:border-gray-700">
|
||||||
|
<div className="flex items-center space-x-2 mb-2">
|
||||||
|
<Users className="h-4 w-4 text-gray-500" />
|
||||||
|
<span className="text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
|
Membres du dossier
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{currentConversation.members.map((member) => (
|
||||||
|
<div
|
||||||
|
key={member.id}
|
||||||
|
className="flex items-center space-x-2 bg-gray-100 dark:bg-gray-700 rounded-full px-3 py-1"
|
||||||
|
>
|
||||||
|
<div className="relative">
|
||||||
|
<div className="w-6 h-6 bg-blue-100 dark:bg-blue-800 rounded-full flex items-center justify-center">
|
||||||
|
<span className="text-xs text-blue-600 dark:text-blue-400 font-medium">
|
||||||
|
{member.avatar}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{member.isOnline && (
|
||||||
|
<Circle className="absolute -bottom-1 -right-1 h-3 w-3 text-green-500 fill-current" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<span className="text-xs text-gray-700 dark:text-gray-300">
|
||||||
|
{member.name}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Messages area */}
|
||||||
|
<div className="flex-1 overflow-y-auto p-4 space-y-4 bg-gray-50 dark:bg-gray-900">
|
||||||
|
<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 avec les membres de ce dossier
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Message input */}
|
||||||
|
<div className="p-4 border-t border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800">
|
||||||
|
<div className="flex items-end space-x-2">
|
||||||
|
<Button variant="outline" size="sm">
|
||||||
|
<Paperclip className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
<div className="flex-1">
|
||||||
|
<Textarea
|
||||||
|
placeholder="Tapez votre message..."
|
||||||
|
value={newMessage}
|
||||||
|
onChange={(e) => setNewMessage(e.target.value)}
|
||||||
|
onKeyPress={(e) => {
|
||||||
|
if (e.key === "Enter" && !e.shiftKey) {
|
||||||
|
e.preventDefault();
|
||||||
|
handleSendMessage();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
rows={1}
|
||||||
|
className="resize-none bg-gray-50 dark:bg-gray-900 border border-gray-300 dark:border-gray-700"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Button variant="outline" size="sm">
|
||||||
|
<Smile className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleSendMessage} disabled={!newMessage.trim()}>
|
||||||
|
<Send className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<div className="flex-1 flex items-center justify-center bg-gray-50 dark:bg-gray-900">
|
||||||
|
<div className="text-center">
|
||||||
|
<Folder 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">
|
||||||
|
Sélectionnez un dossier
|
||||||
|
</h3>
|
||||||
|
<p className="text-gray-600 dark:text-gray-400">
|
||||||
|
Choisissez un dossier pour commencer à discuter avec ses membres
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user