pmtool / src /pages /messages.tsx
devarshia5's picture
Upload 487 files
d97b8f9 verified
Raw
History Blame Contribute Delete
19.7 kB
import React, { useState } from "react";
import Layout from "@/components/layout/layout";
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Badge } from "@/components/ui/badge";
import { Separator } from "@/components/ui/separator";
import { Send, Search, Phone, Video, MessageSquare, Inbox, Paperclip, MoreHorizontal, Plus, Users } from "lucide-react";
interface Message {
id: string;
sender: string;
content: string;
timestamp: string;
isRead: boolean;
isOutgoing: boolean;
avatar?: string;
}
interface Conversation {
id: string;
participant: string;
lastMessage: string;
timestamp: string;
unreadCount: number;
avatar?: string;
isOnline: boolean;
isGroup?: boolean;
members?: string[];
}
const Messages = () => {
const [conversations, setConversations] = useState<Conversation[]>([
{
id: "1",
participant: "Team Alpha",
lastMessage: "Let's discuss the project timeline",
timestamp: "10:30 AM",
unreadCount: 3,
isOnline: true,
isGroup: true,
members: ["Jane Smith", "Alex Johnson", "Sarah Williams"]
},
{
id: "2",
participant: "Jane Smith",
lastMessage: "I've finished the UI designs",
timestamp: "Yesterday",
unreadCount: 0,
isOnline: true
},
{
id: "3",
participant: "Alex Johnson",
lastMessage: "Can we review the backlog items?",
timestamp: "Yesterday",
unreadCount: 2,
isOnline: false
},
{
id: "4",
participant: "Sarah Williams",
lastMessage: "The API integration is complete",
timestamp: "Oct 15",
unreadCount: 0,
isOnline: false
},
{
id: "5",
participant: "Project Leads",
lastMessage: "Meeting scheduled for tomorrow",
timestamp: "Oct 14",
unreadCount: 0,
isOnline: true,
isGroup: true,
members: ["Michael Brown", "Emma Davis", "David Wilson"]
}
]);
const [activeConversation, setActiveConversation] = useState<string>("1");
const [messageInput, setMessageInput] = useState("");
const [searchQuery, setSearchQuery] = useState("");
const [messages, setMessages] = useState<Record<string, Message[]>>({
"1": [
{
id: "1-1",
sender: "Alex Johnson",
content: "Hey team, we should discuss the project timeline",
timestamp: "10:15 AM",
isRead: true,
isOutgoing: false
},
{
id: "1-2",
sender: "Sarah Williams",
content: "I agree. I'm available this afternoon",
timestamp: "10:20 AM",
isRead: true,
isOutgoing: false
},
{
id: "1-3",
sender: "You",
content: "Let's schedule a meeting for 2 PM",
timestamp: "10:25 AM",
isRead: true,
isOutgoing: true
},
{
id: "1-4",
sender: "Jane Smith",
content: "Works for me. I'll prepare the agenda",
timestamp: "10:30 AM",
isRead: false,
isOutgoing: false
}
],
"2": [
{
id: "2-1",
sender: "Jane Smith",
content: "I've been working on the UI designs",
timestamp: "Yesterday, 4:30 PM",
isRead: true,
isOutgoing: false
},
{
id: "2-2",
sender: "You",
content: "How's it coming along?",
timestamp: "Yesterday, 4:35 PM",
isRead: true,
isOutgoing: true
},
{
id: "2-3",
sender: "Jane Smith",
content: "Just finished! I'll share the files",
timestamp: "Yesterday, 4:40 PM",
isRead: true,
isOutgoing: false
},
{
id: "2-4",
sender: "Jane Smith",
content: "I've finished the UI designs and shared them in the design folder",
timestamp: "Yesterday, 4:45 PM",
isRead: true,
isOutgoing: false
}
]
});
const filteredConversations = conversations.filter(
(conversation) =>
conversation.participant.toLowerCase().includes(searchQuery.toLowerCase())
);
const handleSendMessage = () => {
if (messageInput.trim() === "") return;
const newMessage: Message = {
id: `${activeConversation}-${Date.now()}`,
sender: "You",
content: messageInput,
timestamp: "Just now",
isRead: true,
isOutgoing: true
};
setMessages((prevMessages) => ({
...prevMessages,
[activeConversation]: [
...(prevMessages[activeConversation] || []),
newMessage
]
}));
// Update last message in conversation list
setConversations(
conversations.map((conversation) =>
conversation.id === activeConversation
? {
...conversation,
lastMessage: messageInput,
timestamp: "Just now",
unreadCount: 0
}
: conversation
)
);
setMessageInput("");
};
const markAsRead = (conversationId: string) => {
setConversations(
conversations.map((conversation) =>
conversation.id === conversationId
? { ...conversation, unreadCount: 0 }
: conversation
)
);
};
const handleConversationClick = (conversationId: string) => {
setActiveConversation(conversationId);
markAsRead(conversationId);
};
const getInitials = (name: string) => {
return name
.split(" ")
.map((n) => n[0])
.join("")
.toUpperCase();
};
const currentConversation = conversations.find(
(conversation) => conversation.id === activeConversation
);
return (
<Layout>
<div className="container mx-auto py-6">
<div className="flex h-[calc(100vh-12rem)] overflow-hidden rounded-lg border">
{/* Sidebar */}
<div className="w-full max-w-xs border-r flex flex-col">
<div className="p-4 border-b">
<div className="relative">
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
<Input
placeholder="Search conversations..."
className="pl-8"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
/>
</div>
</div>
<Tabs defaultValue="all" className="flex-1 flex flex-col">
<TabsList className="grid grid-cols-3 px-4 py-2">
<TabsTrigger value="all">All</TabsTrigger>
<TabsTrigger value="unread">Unread</TabsTrigger>
<TabsTrigger value="groups">Groups</TabsTrigger>
</TabsList>
<ScrollArea className="flex-1">
<TabsContent value="all" className="m-0">
{filteredConversations.map((conversation) => (
<div
key={conversation.id}
className={`flex items-center gap-3 p-3 cursor-pointer hover:bg-muted/50 ${
activeConversation === conversation.id ? "bg-muted" : ""
}`}
onClick={() => handleConversationClick(conversation.id)}
>
<div className="relative">
<Avatar>
<AvatarImage src={conversation.avatar} />
<AvatarFallback>
{conversation.isGroup ? (
<Users className="h-4 w-4" />
) : (
getInitials(conversation.participant)
)}
</AvatarFallback>
</Avatar>
{conversation.isOnline && (
<span className="absolute bottom-0 right-0 h-3 w-3 rounded-full bg-green-500 border-2 border-background"></span>
)}
</div>
<div className="flex-1 min-w-0">
<div className="flex justify-between items-center">
<span className="font-medium truncate">
{conversation.participant}
</span>
<span className="text-xs text-muted-foreground">
{conversation.timestamp}
</span>
</div>
<div className="flex justify-between items-center mt-1">
<p className="text-sm text-muted-foreground truncate">
{conversation.lastMessage}
</p>
{conversation.unreadCount > 0 && (
<Badge variant="default" className="ml-auto">
{conversation.unreadCount}
</Badge>
)}
</div>
</div>
</div>
))}
</TabsContent>
<TabsContent value="unread" className="m-0">
{filteredConversations
.filter((c) => c.unreadCount > 0)
.map((conversation) => (
<div
key={conversation.id}
className={`flex items-center gap-3 p-3 cursor-pointer hover:bg-muted/50 ${
activeConversation === conversation.id ? "bg-muted" : ""
}`}
onClick={() => handleConversationClick(conversation.id)}
>
<Avatar>
<AvatarImage src={conversation.avatar} />
<AvatarFallback>
{conversation.isGroup ? (
<Users className="h-4 w-4" />
) : (
getInitials(conversation.participant)
)}
</AvatarFallback>
</Avatar>
<div className="flex-1 min-w-0">
<div className="flex justify-between items-center">
<span className="font-medium truncate">
{conversation.participant}
</span>
<span className="text-xs text-muted-foreground">
{conversation.timestamp}
</span>
</div>
<div className="flex justify-between items-center mt-1">
<p className="text-sm text-muted-foreground truncate">
{conversation.lastMessage}
</p>
<Badge variant="default">
{conversation.unreadCount}
</Badge>
</div>
</div>
</div>
))}
</TabsContent>
<TabsContent value="groups" className="m-0">
{filteredConversations
.filter((c) => c.isGroup)
.map((conversation) => (
<div
key={conversation.id}
className={`flex items-center gap-3 p-3 cursor-pointer hover:bg-muted/50 ${
activeConversation === conversation.id ? "bg-muted" : ""
}`}
onClick={() => handleConversationClick(conversation.id)}
>
<Avatar>
<AvatarImage src={conversation.avatar} />
<AvatarFallback>
<Users className="h-4 w-4" />
</AvatarFallback>
</Avatar>
<div className="flex-1 min-w-0">
<div className="flex justify-between items-center">
<span className="font-medium truncate">
{conversation.participant}
</span>
<span className="text-xs text-muted-foreground">
{conversation.timestamp}
</span>
</div>
<div className="flex justify-between items-center mt-1">
<p className="text-sm text-muted-foreground truncate">
{conversation.members?.join(", ")}
</p>
{conversation.unreadCount > 0 && (
<Badge variant="default">
{conversation.unreadCount}
</Badge>
)}
</div>
</div>
</div>
))}
</TabsContent>
</ScrollArea>
<div className="p-3 border-t">
<Button className="w-full">
<Plus className="h-4 w-4 mr-2" />
New Message
</Button>
</div>
</Tabs>
</div>
{/* Main Content */}
<div className="flex-1 flex flex-col">
{currentConversation ? (
<>
{/* Chat Header */}
<div className="p-4 border-b flex items-center justify-between">
<div className="flex items-center">
<Avatar className="h-10 w-10 mr-3">
<AvatarImage src={currentConversation.avatar} />
<AvatarFallback>
{currentConversation.isGroup ? (
<Users className="h-4 w-4" />
) : (
getInitials(currentConversation.participant)
)}
</AvatarFallback>
</Avatar>
<div>
<h2 className="font-semibold flex items-center">
{currentConversation.participant}
{currentConversation.isOnline && (
<span className="ml-2 h-2 w-2 rounded-full bg-green-500"></span>
)}
</h2>
{currentConversation.isGroup && (
<p className="text-xs text-muted-foreground">
{currentConversation.members?.join(", ")}
</p>
)}
</div>
</div>
<div className="flex items-center gap-2">
<Button variant="ghost" size="icon">
<Phone className="h-4 w-4" />
</Button>
<Button variant="ghost" size="icon">
<Video className="h-4 w-4" />
</Button>
<Button variant="ghost" size="icon">
<MoreHorizontal className="h-4 w-4" />
</Button>
</div>
</div>
{/* Messages */}
<ScrollArea className="flex-1 p-4">
<div className="space-y-4">
{messages[activeConversation]?.map((message) => (
<div
key={message.id}
className={`flex ${
message.isOutgoing ? "justify-end" : "justify-start"
}`}
>
{!message.isOutgoing && (
<Avatar className="h-8 w-8 mr-2 mt-1">
<AvatarFallback>
{getInitials(message.sender)}
</AvatarFallback>
</Avatar>
)}
<div
className={`max-w-[70%] ${
message.isOutgoing
? "bg-primary text-primary-foreground"
: "bg-muted"
} rounded-lg p-3`}
>
{!message.isOutgoing && (
<p className="text-xs font-medium mb-1">
{message.sender}
</p>
)}
<p>{message.content}</p>
<p className="text-xs mt-1 opacity-70 text-right">
{message.timestamp}
</p>
</div>
</div>
))}
</div>
</ScrollArea>
{/* Message Input */}
<div className="p-4 border-t">
<div className="flex gap-2">
<Button variant="outline" size="icon">
<Paperclip className="h-4 w-4" />
</Button>
<Input
placeholder="Type a message..."
value={messageInput}
onChange={(e) => setMessageInput(e.target.value)}
onKeyPress={(e) => {
if (e.key === "Enter") handleSendMessage();
}}
/>
<Button
size="icon"
onClick={handleSendMessage}
disabled={messageInput.trim() === ""}
>
<Send className="h-4 w-4" />
</Button>
</div>
</div>
</>
) : (
<div className="flex-1 flex items-center justify-center flex-col p-4">
<MessageSquare className="h-12 w-12 text-muted-foreground mb-4" />
<h3 className="text-xl font-semibold mb-2">Your Messages</h3>
<p className="text-muted-foreground text-center mb-4">
Select a conversation or start a new one
</p>
<Button>
<Plus className="h-4 w-4 mr-2" />
New Message
</Button>
</div>
)}
</div>
</div>
</div>
</Layout>
);
};
export default Messages;