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, '@$1') .replace(/\n/g, ''); }; 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(INITIAL_MESSAGES); const [showSidebar, setShowSidebar] = useState(false); const messagesEndRef = useRef(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 ( {/* Header */} Work Notes {requestInfo.title} {requestId} {onlineParticipants.slice(0, 3).map((participant, index) => ( {participant.avatar} ))} {onlineParticipants.length > 3 && ( +{onlineParticipants.length - 3} )} setShowSidebar(true)} className="lg:hidden" > setShowSidebar(!showSidebar)} className="lg:hidden" > {/* Main Chat Area */} {/* Tab Navigation */} Messages Chat Files Activity Act {/* Chat Tab */} {/* Search Bar */} setSearchTerm(e.target.value)} className="pl-10 bg-gray-50 border-gray-200 h-9 sm:h-10" /> {/* Messages Area */} {filteredMessages.map((msg) => ( {!msg.isSystem && ( {msg.user.avatar} )} {msg.isSystem ? ( {msg.content} {msg.timestamp} ) : ( {/* Message Header */} {msg.user.name} {msg.user.role} {msg.timestamp} {msg.isHighPriority && ( Priority )} {/* Message Content */} {/* Attachments */} {msg.attachments && msg.attachments.length > 0 && ( {msg.attachments.map((attachment, index) => ( {getFileIcon(attachment.type)} {attachment.name} ))} )} {/* Reactions */} {msg.reactions && msg.reactions.length > 0 && ( {msg.reactions.map((reaction, index) => ( 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' }`} > {reaction.emoji} {reaction.users.length} ))} setShowEmojiPicker(!showEmojiPicker)} > )} )} ))} {isTyping && ( Someone is typing... )} {/* Message Input */} 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} /> {message.length}/2000 Send {/* Files Tab */} Shared Files Upload File Upload {MOCK_DOCUMENTS.map((doc, index) => ( {getFileIcon(doc.type)} {doc.name} {doc.size} ⢠{doc.type} by {doc.uploadedBy} ⢠{doc.uploadedAt} View Download ))} {/* Activity Tab */} Recent Activity {messages.filter(msg => msg.isSystem).map((msg) => ( {msg.content} {msg.timestamp} ))} {/* Mobile Sidebar Overlay */} {showSidebar && ( setShowSidebar(false)} /> )} {/* Participants Sidebar */} Participants setShowSidebar(false)} className="lg:hidden h-8 w-8 p-0" > {MOCK_PARTICIPANTS.map((participant, index) => ( {participant.avatar} {participant.name} {participant.role} ⢠{getStatusText(participant.status)} {participant.lastSeen && participant.status === 'offline' && ( {participant.lastSeen} )} ))} Quick Actions Add Participant Manage Notifications Archive Chat ); }
{requestInfo.title}
{doc.size} ⢠{doc.type}
by {doc.uploadedBy} ⢠{doc.uploadedAt}
{msg.content}
{msg.timestamp}
{participant.name}
{participant.role}
{getStatusText(participant.status)}
{participant.lastSeen}