Re_Figma_Code/components/WorkNoteView.tsx
2025-10-22 10:27:06 +05:30

817 lines
34 KiB
TypeScript

import React, { useState, useRef, useEffect, useMemo } from 'react';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from './ui/card';
import { Button } from './ui/button';
import { Input } from './ui/input';
import { Avatar, AvatarFallback } from './ui/avatar';
import { Badge } from './ui/badge';
import { Tabs, TabsContent, TabsList, TabsTrigger } from './ui/tabs';
import { ScrollArea } from './ui/scroll-area';
import { Separator } from './ui/separator';
import { Textarea } from './ui/textarea';
import {
ArrowLeft,
Send,
Smile,
Paperclip,
Users,
FileText,
Download,
Eye,
MoreHorizontal,
MessageSquare,
Clock,
CheckCircle,
AlertCircle,
Search,
Hash,
AtSign,
Phone,
Video,
Settings,
Pin,
Share,
Archive,
Plus,
Filter,
Calendar,
Zap,
Activity,
Bell,
Star,
Flag,
X
} from 'lucide-react';
interface Message {
id: string;
user: {
name: string;
avatar: string;
role: string;
};
content: string;
timestamp: string;
mentions?: string[];
isSystem?: boolean;
attachments?: {
name: string;
url: string;
type: string;
}[];
reactions?: {
emoji: string;
users: string[];
}[];
isHighPriority?: boolean;
}
interface Participant {
name: string;
avatar: string;
role: string;
status: 'online' | 'away' | 'offline';
email: string;
lastSeen?: string;
permissions: string[];
}
interface WorkNoteViewProps {
requestId: string;
onBack?: () => void;
}
// Get request data from the same source as RequestDetail
const REQUEST_DATABASE = {
'RE-REQ-001': {
id: 'RE-REQ-001',
title: 'Marketing Campaign Budget Approval',
department: 'Marketing',
priority: 'high',
status: 'pending'
},
'RE-REQ-002': {
id: 'RE-REQ-002',
title: 'IT Equipment Purchase',
department: 'IT',
priority: 'medium',
status: 'in-review'
}
};
// Static data to prevent re-renders
const MOCK_PARTICIPANTS: Participant[] = [
{
name: 'Sarah Chen',
avatar: 'SC',
role: 'Initiator',
status: 'online',
email: 'sarah.chen@royalenfield.com',
permissions: ['read', 'write', 'mention']
},
{
name: 'Mike Johnson',
avatar: 'MJ',
role: 'Team Lead',
status: 'online',
email: 'mike.johnson@royalenfield.com',
permissions: ['read', 'write', 'mention', 'approve']
},
{
name: 'Lisa Wong',
avatar: 'LW',
role: 'Finance Manager',
status: 'away',
email: 'lisa.wong@royalenfield.com',
lastSeen: '5 minutes ago',
permissions: ['read', 'write', 'mention', 'approve']
},
{
name: 'Anna Smith',
avatar: 'AS',
role: 'Spectator',
status: 'online',
email: 'anna.smith@royalenfield.com',
permissions: ['read', 'write', 'mention']
},
{
name: 'John Doe',
avatar: 'JD',
role: 'Spectator',
status: 'offline',
email: 'john.doe@royalenfield.com',
lastSeen: '2 hours ago',
permissions: ['read']
},
{
name: 'Emily Davis',
avatar: 'ED',
role: 'Creative Director',
status: 'online',
email: 'emily.davis@royalenfield.com',
permissions: ['read', 'write', 'mention']
}
];
const MOCK_DOCUMENTS = [
{
name: 'Q4_Marketing_Strategy.pdf',
size: '2.4 MB',
uploadedBy: 'Sarah Chen',
uploadedAt: '2024-10-05 14:32',
type: 'PDF',
url: '#'
},
{
name: 'Budget_Breakdown.xlsx',
size: '1.1 MB',
uploadedBy: 'Sarah Chen',
uploadedAt: '2024-10-05 14:35',
type: 'Excel',
url: '#'
},
{
name: 'Previous_Campaign_ROI.pdf',
size: '856 KB',
uploadedBy: 'Anna Smith',
uploadedAt: '2024-10-06 09:15',
type: 'PDF',
url: '#'
},
{
name: 'Competitor_Analysis.pptx',
size: '3.2 MB',
uploadedBy: 'Emily Davis',
uploadedAt: '2024-10-06 15:22',
type: 'PowerPoint',
url: '#'
}
];
const INITIAL_MESSAGES: Message[] = [
{
id: '1',
user: { name: 'Sarah Chen', avatar: 'SC', role: 'Initiator' },
content: 'Hi everyone! I\'ve submitted the marketing campaign budget request for Q4. Please review the attached documents and let me know if you need any additional information.',
timestamp: '2024-10-05 14:30',
isSystem: false,
reactions: [
{ emoji: '👍', users: ['Mike Johnson', 'Anna Smith'] },
{ emoji: '📋', users: ['Lisa Wong'] }
]
},
{
id: '2',
user: { name: 'System', avatar: 'SY', role: 'System' },
content: 'Request RE-REQ-001 has been created and assigned to Mike Johnson for initial review.',
timestamp: '2024-10-05 14:31',
isSystem: true
},
{
id: '3',
user: { name: 'Anna Smith', avatar: 'AS', role: 'Spectator' },
content: 'I\'ve added the previous campaign ROI data to help with the decision. The numbers show a 285% ROI from our last similar campaign. @Mike Johnson @Lisa Wong please check it out when you have a moment.',
timestamp: '2024-10-06 09:15',
mentions: ['Mike Johnson', 'Lisa Wong'],
attachments: [
{ name: 'Previous_Campaign_ROI.pdf', url: '#', type: 'pdf' }
]
},
{
id: '4',
user: { name: 'Mike Johnson', avatar: 'MJ', role: 'Team Lead' },
content: 'Thanks @Anna Smith! The historical data is very helpful. After reviewing the strategy document and budget breakdown, I believe this campaign is well-planned and has strong potential. I\'m approving this and forwarding to Finance for final review.',
timestamp: '2024-10-06 10:30',
mentions: ['Anna Smith'],
reactions: [
{ emoji: '✅', users: ['Sarah Chen', 'Anna Smith'] }
]
},
{
id: '5',
user: { name: 'System', avatar: 'SY', role: 'System' },
content: 'Request approved by Mike Johnson and forwarded to Lisa Wong for finance review.',
timestamp: '2024-10-06 10:31',
isSystem: true
},
{
id: '6',
user: { name: 'Emily Davis', avatar: 'ED', role: 'Creative Director' },
content: 'Great work on the strategy @Sarah Chen! I\'ve also added our competitor analysis to provide more context. The creative assets timeline looks achievable.',
timestamp: '2024-10-06 15:22',
mentions: ['Sarah Chen'],
attachments: [
{ name: 'Competitor_Analysis.pptx', url: '#', type: 'pptx' }
]
},
{
id: '7',
user: { name: 'Lisa Wong', avatar: 'LW', role: 'Finance Manager' },
content: 'I\'m currently reviewing the budget allocation and comparing it with Q3 spending. @Sarah Chen can you clarify the expected timeline for the LinkedIn ads campaign? Also, do we have approval from legal for the content strategy?',
timestamp: '2024-10-07 14:20',
mentions: ['Sarah Chen'],
isHighPriority: true
},
{
id: '8',
user: { name: 'Sarah Chen', avatar: 'SC', role: 'Initiator' },
content: 'Hi @Lisa Wong! For the LinkedIn campaign:\n\n• Launch: November 1st\n• Duration: 8 weeks\n• Budget distribution: 40% first 4 weeks, 60% last 4 weeks\n\nRegarding legal approval - I\'ll coordinate with the legal team this week. The content strategy follows our established brand guidelines.',
timestamp: '2024-10-07 15:45',
mentions: ['Lisa Wong']
}
];
// Utility functions
const getStatusColor = (status: string) => {
switch (status) {
case 'online': return 'bg-green-500';
case 'away': return 'bg-yellow-500';
case 'offline': return 'bg-gray-400';
default: return 'bg-gray-400';
}
};
const getStatusText = (status: string) => {
switch (status) {
case 'online': return 'Online';
case 'away': return 'Away';
case 'offline': return 'Offline';
default: return 'Unknown';
}
};
const formatMessage = (content: string) => {
// Enhanced mention highlighting with better regex
return content
.replace(/@([\w\s]+)(?=\s|$|[.,!?])/g, '<span class="inline-flex items-center px-2 py-1 rounded-md bg-blue-100 text-blue-800 font-medium text-sm">@$1</span>')
.replace(/\n/g, '<br />');
};
const getFileIcon = (type: string) => {
switch (type.toLowerCase()) {
case 'pdf': return '📄';
case 'excel': case 'xlsx': return '📊';
case 'powerpoint': case 'pptx': return '📊';
case 'word': case 'docx': return '📝';
case 'image': case 'png': case 'jpg': case 'jpeg': return '🖼️';
default: return '📎';
}
};
export function WorkNoteView({ requestId, onBack }: WorkNoteViewProps) {
const [message, setMessage] = useState('');
const [isTyping, setIsTyping] = useState(false);
const [activeTab, setActiveTab] = useState('chat');
const [searchTerm, setSearchTerm] = useState('');
const [showEmojiPicker, setShowEmojiPicker] = useState(false);
const [messages, setMessages] = useState<Message[]>(INITIAL_MESSAGES);
const [showSidebar, setShowSidebar] = useState(false);
const messagesEndRef = useRef<HTMLDivElement>(null);
// Get request info
const requestInfo = useMemo(() => {
const data = REQUEST_DATABASE[requestId as keyof typeof REQUEST_DATABASE];
return data || {
id: requestId,
title: 'Unknown Request',
department: 'Unknown',
priority: 'medium',
status: 'pending'
};
}, [requestId]);
const onlineParticipants = MOCK_PARTICIPANTS.filter(p => p.status === 'online');
const filteredMessages = messages.filter(msg =>
msg.content.toLowerCase().includes(searchTerm.toLowerCase()) ||
msg.user.name.toLowerCase().includes(searchTerm.toLowerCase())
);
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
};
useEffect(() => {
scrollToBottom();
}, [messages]);
const handleSendMessage = () => {
if (message.trim()) {
const newMessage: Message = {
id: Date.now().toString(),
user: { name: 'You', avatar: 'YO', role: 'Current User' },
content: message,
timestamp: new Date().toLocaleString(),
mentions: extractMentions(message),
isHighPriority: message.includes('!important') || message.includes('urgent')
};
setMessages(prev => [...prev, newMessage]);
setMessage('');
}
};
const extractMentions = (text: string): string[] => {
const mentionRegex = /@([\w\s]+)(?=\s|$|[.,!?])/g;
const mentions = [];
let match;
while ((match = mentionRegex.exec(text)) !== null) {
mentions.push(match[1].trim());
}
return mentions;
};
const handleKeyPress = (e: React.KeyboardEvent) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
handleSendMessage();
}
};
const addReaction = (messageId: string, emoji: string) => {
setMessages(prev => prev.map(msg => {
if (msg.id === messageId) {
const reactions = msg.reactions || [];
const existingReaction = reactions.find(r => r.emoji === emoji);
if (existingReaction) {
if (existingReaction.users.includes('You')) {
existingReaction.users = existingReaction.users.filter(u => u !== 'You');
if (existingReaction.users.length === 0) {
return { ...msg, reactions: reactions.filter(r => r.emoji !== emoji) };
}
} else {
existingReaction.users.push('You');
}
} else {
reactions.push({ emoji, users: ['You'] });
}
return { ...msg, reactions };
}
return msg;
}));
};
return (
<div className="h-screen max-h-screen flex flex-col bg-gray-50 overflow-hidden">
{/* Header */}
<div className="bg-white border-b border-gray-200 px-3 sm:px-6 py-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2 sm:gap-4 min-w-0 flex-1">
<Button variant="ghost" size="icon" onClick={onBack} className="shrink-0">
<ArrowLeft className="h-5 w-5" />
</Button>
<div className="flex items-center gap-2 sm:gap-4 min-w-0 flex-1">
<div className="w-10 h-10 sm:w-12 sm:h-12 bg-gradient-to-br from-blue-500 to-purple-600 rounded-xl flex items-center justify-center shadow-lg shrink-0">
<MessageSquare className="w-5 h-5 sm:w-6 sm:h-6 text-white" />
</div>
<div className="min-w-0 flex-1">
<h1 className="text-lg sm:text-2xl font-bold text-gray-900">Work Notes</h1>
<div className="flex items-center gap-2 mt-1">
<p className="text-gray-600 text-sm sm:text-base truncate">{requestInfo.title}</p>
<Badge variant="outline" className="text-xs shrink-0">
{requestId}
</Badge>
</div>
</div>
</div>
</div>
<div className="flex items-center gap-3">
<div className="flex items-center gap-2 text-sm text-gray-600">
<div className="flex -space-x-2">
{onlineParticipants.slice(0, 3).map((participant, index) => (
<Avatar key={index} className="h-8 w-8 ring-2 ring-white shadow-sm">
<AvatarFallback className="bg-blue-500 text-white text-xs font-semibold">
{participant.avatar}
</AvatarFallback>
</Avatar>
))}
{onlineParticipants.length > 3 && (
<div className="h-8 w-8 rounded-full bg-gray-100 ring-2 ring-white flex items-center justify-center text-xs font-medium text-gray-600">
+{onlineParticipants.length - 3}
</div>
)}
</div>
</div>
<Button
variant="outline"
size="sm"
onClick={() => setShowSidebar(true)}
className="lg:hidden"
>
<Users className="w-4 h-4" />
</Button>
<Button
variant="outline"
size="sm"
onClick={() => setShowSidebar(!showSidebar)}
className="lg:hidden"
>
<Users className="h-4 w-4" />
</Button>
</div>
</div>
</div>
<div className="flex-1 flex overflow-hidden relative">
{/* Main Chat Area */}
<div className="flex-1 flex flex-col min-w-0">
<Tabs value={activeTab} onValueChange={setActiveTab} className="flex-1 flex flex-col">
{/* Tab Navigation */}
<div className="bg-white border-b border-gray-200 px-2 sm:px-3 lg:px-6">
<TabsList className="grid w-full max-w-full sm:max-w-md grid-cols-3 bg-gray-100 h-10">
<TabsTrigger value="chat" className="flex items-center gap-1 sm:gap-2 text-xs sm:text-sm px-2">
<MessageSquare className="w-3 h-3 sm:w-4 sm:h-4" />
<span className="hidden xs:inline">Messages</span>
<span className="xs:hidden">Chat</span>
</TabsTrigger>
<TabsTrigger value="files" className="flex items-center gap-1 sm:gap-2 text-xs sm:text-sm px-2">
<FileText className="w-3 h-3 sm:w-4 sm:h-4" />
<span>Files</span>
</TabsTrigger>
<TabsTrigger value="activity" className="flex items-center gap-1 sm:gap-2 text-xs sm:text-sm px-2">
<Activity className="w-3 h-3 sm:w-4 sm:h-4" />
<span className="hidden xs:inline">Activity</span>
<span className="xs:hidden">Act</span>
</TabsTrigger>
</TabsList>
</div>
{/* Chat Tab */}
<TabsContent value="chat" className="flex-1 flex flex-col m-0">
{/* Search Bar */}
<div className="bg-white border-b border-gray-200 px-2 sm:px-3 lg:px-6 py-2 sm:py-3">
<div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
<Input
placeholder="Search messages..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="pl-10 bg-gray-50 border-gray-200 h-9 sm:h-10"
/>
</div>
</div>
{/* Messages Area */}
<div className="flex-1 overflow-y-auto px-2 sm:px-3 lg:px-6 py-2 sm:py-4">
<div className="space-y-3 sm:space-y-6 max-w-full">
{filteredMessages.map((msg) => (
<div key={msg.id} className={`flex gap-2 sm:gap-3 lg:gap-4 ${msg.isSystem ? 'justify-center' : ''}`}>
{!msg.isSystem && (
<Avatar className="h-8 w-8 sm:h-10 sm:w-10 lg:h-12 lg:w-12 flex-shrink-0 ring-1 sm:ring-2 ring-white shadow-sm">
<AvatarFallback className={`text-white font-semibold text-xs sm:text-sm ${
msg.user.role === 'Initiator' ? 'bg-green-600' :
msg.user.role === 'Current User' ? 'bg-blue-500' :
msg.user.role === 'System' ? 'bg-gray-500' :
'bg-slate-600'
}`}>
{msg.user.avatar}
</AvatarFallback>
</Avatar>
)}
<div className={`flex-1 min-w-0 ${msg.isSystem ? 'text-center max-w-xs sm:max-w-md mx-auto' : ''}`}>
{msg.isSystem ? (
<div className="inline-flex items-center gap-2 sm:gap-3 px-3 sm:px-4 py-1.5 sm:py-2 bg-gray-100 rounded-full">
<Activity className="w-3 h-3 sm:w-4 sm:h-4 text-gray-500 flex-shrink-0" />
<span className="text-xs sm:text-sm text-gray-700">{msg.content}</span>
<span className="text-xs text-gray-500 hidden sm:inline">{msg.timestamp}</span>
</div>
) : (
<div>
{/* Message Header */}
<div className="flex items-center gap-2 sm:gap-3 mb-1 sm:mb-2 flex-wrap">
<span className="font-semibold text-gray-900 text-sm sm:text-base truncate">{msg.user.name}</span>
<Badge variant="outline" className="text-xs flex-shrink-0">
{msg.user.role}
</Badge>
<span className="text-xs text-gray-500 flex items-center gap-1 flex-shrink-0">
<Clock className="w-3 h-3" />
{msg.timestamp}
</span>
{msg.isHighPriority && (
<Badge variant="destructive" className="text-xs flex-shrink-0">
<Flag className="w-3 h-3 mr-1" />
Priority
</Badge>
)}
</div>
{/* Message Content */}
<div className="bg-white rounded-lg border border-gray-200 p-3 sm:p-4 shadow-sm">
<div
className="text-gray-800 leading-relaxed text-sm sm:text-base"
dangerouslySetInnerHTML={{ __html: formatMessage(msg.content) }}
/>
{/* Attachments */}
{msg.attachments && msg.attachments.length > 0 && (
<div className="mt-2 sm:mt-3 pt-2 sm:pt-3 border-t border-gray-100">
<div className="space-y-2">
{msg.attachments.map((attachment, index) => (
<div key={index} className="flex items-center gap-2 sm:gap-3 p-2 bg-gray-50 rounded-lg">
<span className="text-lg sm:text-xl flex-shrink-0">{getFileIcon(attachment.type)}</span>
<span className="text-xs sm:text-sm font-medium text-gray-700 flex-1 truncate">
{attachment.name}
</span>
<Button variant="ghost" size="sm" className="h-8 w-8 p-0 flex-shrink-0">
<Download className="w-3 h-3 sm:w-4 sm:h-4" />
</Button>
</div>
))}
</div>
</div>
)}
{/* Reactions */}
{msg.reactions && msg.reactions.length > 0 && (
<div className="flex items-center gap-1 sm:gap-2 mt-2 sm:mt-3 pt-2 sm:pt-3 border-t border-gray-100 flex-wrap">
{msg.reactions.map((reaction, index) => (
<button
key={index}
onClick={() => addReaction(msg.id, reaction.emoji)}
className={`flex items-center gap-1 px-2 py-1 rounded-full text-xs sm:text-sm transition-colors flex-shrink-0 ${
reaction.users.includes('You')
? 'bg-blue-100 text-blue-800 border border-blue-200'
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
}`}
>
<span>{reaction.emoji}</span>
<span className="text-xs font-medium">{reaction.users.length}</span>
</button>
))}
<Button
variant="ghost"
size="sm"
className="h-6 w-6 sm:h-7 sm:w-7 p-0 flex-shrink-0"
onClick={() => setShowEmojiPicker(!showEmojiPicker)}
>
<Plus className="w-2 h-2 sm:w-3 sm:h-3" />
</Button>
</div>
)}
</div>
</div>
)}
</div>
</div>
))}
{isTyping && (
<div className="flex gap-2 sm:gap-4">
<Avatar className="h-8 w-8 sm:h-10 sm:w-10 lg:h-12 lg:w-12 flex-shrink-0">
<AvatarFallback className="bg-gray-400 text-white">
<div className="flex gap-0.5 sm:gap-1">
<div className="w-1 h-1 bg-white rounded-full animate-bounce"></div>
<div className="w-1 h-1 bg-white rounded-full animate-bounce" style={{ animationDelay: '0.1s' }}></div>
<div className="w-1 h-1 bg-white rounded-full animate-bounce" style={{ animationDelay: '0.2s' }}></div>
</div>
</AvatarFallback>
</Avatar>
<div className="flex items-center text-xs sm:text-sm text-gray-500 bg-gray-100 px-3 sm:px-4 py-2 rounded-lg">
Someone is typing...
</div>
</div>
)}
<div ref={messagesEndRef} />
</div>
</div>
{/* Message Input */}
<div className="bg-white border-t border-gray-200 p-2 sm:p-3 lg:p-6">
<div className="max-w-full">
<div className="flex flex-col gap-2 sm:gap-4">
<div className="flex-1">
<Textarea
placeholder="Type your message... Use @username to mention someone"
value={message}
onChange={(e) => setMessage(e.target.value)}
onKeyPress={handleKeyPress}
className="min-h-[50px] sm:min-h-[60px] resize-none border-gray-200 focus:ring-blue-500 focus:border-blue-500 w-full text-sm"
rows={2}
/>
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-center mt-2 gap-2">
<div className="flex items-center gap-1 sm:gap-2 order-2 sm:order-1">
<Button variant="ghost" size="sm" className="text-gray-500 h-7 w-7 sm:h-8 sm:w-8 p-0">
<Paperclip className="h-3 w-3 sm:h-4 sm:w-4" />
</Button>
<Button variant="ghost" size="sm" className="text-gray-500 h-7 w-7 sm:h-8 sm:w-8 p-0">
<Smile className="h-3 w-3 sm:h-4 sm:w-4" />
</Button>
<Button variant="ghost" size="sm" className="text-gray-500 h-7 w-7 sm:h-8 sm:w-8 p-0 hidden sm:flex">
<AtSign className="h-4 w-4" />
</Button>
<Button variant="ghost" size="sm" className="text-gray-500 h-7 w-7 sm:h-8 sm:w-8 p-0 hidden sm:flex">
<Hash className="h-4 w-4" />
</Button>
</div>
<div className="flex items-center gap-2 order-1 sm:order-2">
<span className="text-xs text-gray-500 hidden sm:inline">
{message.length}/2000
</span>
<Button
onClick={handleSendMessage}
disabled={!message.trim()}
className="bg-blue-600 hover:bg-blue-700 min-w-0 h-8 sm:h-9"
size="sm"
>
<Send className="h-3 w-3 sm:h-4 sm:w-4 sm:mr-2" />
<span className="hidden sm:inline ml-1">Send</span>
</Button>
</div>
</div>
</div>
</div>
</div>
</div>
</TabsContent>
{/* Files Tab */}
<TabsContent value="files" className="flex-1 p-2 sm:p-3 lg:p-6 m-0">
<div className="max-w-full">
<div className="flex flex-col sm:flex-row sm:items-center justify-between mb-4 sm:mb-6 gap-3">
<h3 className="text-base sm:text-lg font-semibold text-gray-900">Shared Files</h3>
<Button className="gap-2 text-sm h-9">
<Plus className="w-4 h-4" />
<span className="hidden xs:inline">Upload File</span>
<span className="xs:hidden">Upload</span>
</Button>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3 sm:gap-4">
{MOCK_DOCUMENTS.map((doc, index) => (
<Card key={index} className="hover:shadow-lg transition-shadow">
<CardContent className="p-3 sm:p-4">
<div className="flex items-start gap-2 sm:gap-3">
<div className="w-10 h-10 sm:w-12 sm:h-12 bg-blue-100 rounded-lg flex items-center justify-center flex-shrink-0">
<span className="text-lg sm:text-2xl">{getFileIcon(doc.type)}</span>
</div>
<div className="flex-1 min-w-0">
<h4 className="font-medium text-gray-900 truncate text-sm sm:text-base">{doc.name}</h4>
<p className="text-xs sm:text-sm text-gray-600 mt-1">
{doc.size} {doc.type}
</p>
<p className="text-xs text-gray-500 mt-1">
by {doc.uploadedBy} {doc.uploadedAt}
</p>
</div>
</div>
<div className="flex items-center gap-2 mt-3 sm:mt-4">
<Button variant="outline" size="sm" className="flex-1 text-xs sm:text-sm h-8 sm:h-9">
<Eye className="h-3 w-3 sm:h-4 sm:w-4 mr-1 sm:mr-2" />
View
</Button>
<Button variant="outline" size="sm" className="flex-1 text-xs sm:text-sm h-8 sm:h-9">
<Download className="h-3 w-3 sm:h-4 sm:w-4 mr-1 sm:mr-2" />
Download
</Button>
</div>
</CardContent>
</Card>
))}
</div>
</div>
</TabsContent>
{/* Activity Tab */}
<TabsContent value="activity" className="flex-1 p-2 sm:p-3 lg:p-6 m-0">
<div className="max-w-full">
<h3 className="text-base sm:text-lg font-semibold text-gray-900 mb-4 sm:mb-6">Recent Activity</h3>
<div className="space-y-3 sm:space-y-4">
{messages.filter(msg => msg.isSystem).map((msg) => (
<div key={msg.id} className="flex items-start gap-3 sm:gap-4 p-3 sm:p-4 bg-gray-50 rounded-lg">
<div className="w-7 h-7 sm:w-8 sm:h-8 bg-blue-100 rounded-full flex items-center justify-center flex-shrink-0">
<Activity className="w-3 h-3 sm:w-4 sm:h-4 text-blue-600" />
</div>
<div className="flex-1 min-w-0">
<p className="text-gray-900 text-sm sm:text-base">{msg.content}</p>
<p className="text-xs sm:text-sm text-gray-500 mt-1">{msg.timestamp}</p>
</div>
</div>
))}
</div>
</div>
</TabsContent>
</Tabs>
</div>
{/* Mobile Sidebar Overlay */}
{showSidebar && (
<div
className="fixed inset-0 bg-black bg-opacity-50 z-40 lg:hidden"
onClick={() => setShowSidebar(false)}
/>
)}
{/* Participants Sidebar */}
<div className={`
w-72 sm:w-80 bg-white border-l border-gray-200 flex flex-col
lg:relative lg:translate-x-0 lg:shadow-none
${showSidebar ? 'fixed right-0 top-0 bottom-0 z-50 shadow-xl' : 'hidden lg:flex'}
`}>
<div className="p-4 sm:p-6 border-b border-gray-200">
<div className="flex items-center justify-between mb-4">
<h3 className="text-base sm:text-lg font-semibold text-gray-900">Participants</h3>
<Button
variant="ghost"
size="sm"
onClick={() => setShowSidebar(false)}
className="lg:hidden h-8 w-8 p-0"
>
<X className="h-4 w-4" />
</Button>
</div>
<div className="space-y-3 sm:space-y-4">
{MOCK_PARTICIPANTS.map((participant, index) => (
<div key={index} className="flex items-center gap-3">
<div className="relative">
<Avatar className="h-9 w-9 sm:h-10 sm:w-10">
<AvatarFallback className={`text-white font-semibold text-sm ${
participant.role === 'Initiator' ? 'bg-green-600' : 'bg-slate-600'
}`}>
{participant.avatar}
</AvatarFallback>
</Avatar>
<div className={`absolute -bottom-1 -right-1 w-3 h-3 rounded-full border-2 border-white ${getStatusColor(participant.status)}`}></div>
</div>
<div className="flex-1 min-w-0">
<p className="font-medium text-gray-900 truncate text-sm sm:text-base">{participant.name}</p>
<div className="flex items-center gap-2">
<p className="text-xs text-gray-500">{participant.role}</p>
<span className="text-xs text-gray-400"></span>
<p className="text-xs text-gray-500">{getStatusText(participant.status)}</p>
</div>
{participant.lastSeen && participant.status === 'offline' && (
<p className="text-xs text-gray-400">{participant.lastSeen}</p>
)}
</div>
<Button variant="ghost" size="sm" className="h-8 w-8 p-0">
<MoreHorizontal className="h-4 w-4" />
</Button>
</div>
))}
</div>
</div>
<div className="p-4 sm:p-6">
<h4 className="font-semibold text-gray-900 mb-3 text-sm sm:text-base">Quick Actions</h4>
<div className="space-y-2">
<Button variant="outline" size="sm" className="w-full justify-start gap-2 h-9 text-sm">
<Users className="h-4 w-4" />
Add Participant
</Button>
<Button variant="outline" size="sm" className="w-full justify-start gap-2 h-9 text-sm">
<Bell className="h-4 w-4" />
Manage Notifications
</Button>
<Button variant="outline" size="sm" className="w-full justify-start gap-2 h-9 text-sm">
<Archive className="h-4 w-4" />
Archive Chat
</Button>
</div>
</div>
</div>
</div>
</div>
);
}