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

362 lines
14 KiB
TypeScript

import React, { useState, useRef, useEffect } from 'react';
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '../ui/dialog';
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 {
Send,
Smile,
Paperclip,
Users,
FileText,
Download,
Eye,
MoreHorizontal
} from 'lucide-react';
interface Message {
id: string;
user: {
name: string;
avatar: string;
role: string;
};
content: string;
timestamp: string;
mentions?: string[];
isSystem?: boolean;
}
interface WorkNoteModalProps {
open: boolean;
onClose: () => void;
requestId: string;
}
export function WorkNoteModal({ open, onClose, requestId }: WorkNoteModalProps) {
const [message, setMessage] = useState('');
const [isTyping, setIsTyping] = useState(false);
const messagesEndRef = useRef<HTMLDivElement>(null);
const participants = [
{ name: 'Sarah Chen', avatar: 'SC', role: 'Initiator', status: 'online' },
{ name: 'Mike Johnson', avatar: 'MJ', role: 'Team Lead', status: 'online' },
{ name: 'Lisa Wong', avatar: 'LW', role: 'Finance Manager', status: 'away' },
{ name: 'Anna Smith', avatar: 'AS', role: 'Spectator', status: 'offline' },
{ name: 'John Doe', avatar: 'JD', role: 'Spectator', status: 'online' }
];
const documents = [
{
name: 'Q4_Marketing_Strategy.pdf',
size: '2.4 MB',
uploadedBy: 'Sarah Chen',
uploadedAt: '2024-10-05 14:32'
},
{
name: 'Budget_Breakdown.xlsx',
size: '1.1 MB',
uploadedBy: 'Sarah Chen',
uploadedAt: '2024-10-05 14:35'
},
{
name: 'Previous_Campaign_ROI.pdf',
size: '856 KB',
uploadedBy: 'Anna Smith',
uploadedAt: '2024-10-06 09:15'
}
];
const [messages, setMessages] = useState<Message[]>([
{
id: '1',
user: { name: 'Sarah Chen', avatar: 'SC', role: 'Initiator' },
content: 'Hi everyone! I\'ve submitted the marketing campaign budget request. Please review the attached documents.',
timestamp: '2024-10-05 14:30',
isSystem: false
},
{
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. @Mike Johnson @Lisa Wong please check it out.',
timestamp: '2024-10-06 09:15',
mentions: ['Mike Johnson', 'Lisa Wong']
},
{
id: '4',
user: { name: 'Mike Johnson', avatar: 'MJ', role: 'Team Lead' },
content: 'Thanks @Anna Smith! The numbers look good. I\'m approving this and forwarding to Finance.',
timestamp: '2024-10-06 10:30',
mentions: ['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: 'Lisa Wong', avatar: 'LW', role: 'Finance Manager' },
content: 'I\'m reviewing the budget allocation. @Sarah Chen can you clarify the expected timeline for the LinkedIn ads campaign?',
timestamp: '2024-10-07 14:20',
mentions: ['Sarah Chen']
}
]);
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)
};
setMessages([...messages, newMessage]);
setMessage('');
}
};
const extractMentions = (text: string): string[] => {
const mentionRegex = /@(\w+\s?\w+)/g;
const mentions = [];
let match;
while ((match = mentionRegex.exec(text)) !== null) {
mentions.push(match[1]);
}
return mentions;
};
const handleKeyPress = (e: React.KeyboardEvent) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
handleSendMessage();
}
};
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 formatMessage = (content: string) => {
// Simple mention highlighting
return content.replace(/@(\w+\s?\w+)/g, '<span class="text-blue-600 font-medium">@$1</span>');
};
return (
<Dialog open={open} onOpenChange={onClose}>
<DialogContent className="max-w-4xl h-[80vh] flex flex-col">
<DialogHeader>
<DialogTitle>Work Notes - {requestId}</DialogTitle>
<DialogDescription>
Collaborate with all request participants
</DialogDescription>
</DialogHeader>
<div className="flex-1 flex gap-4">
{/* Main Chat Area */}
<div className="flex-1 flex flex-col">
<Tabs defaultValue="chat" className="flex-1 flex flex-col">
<TabsList className="grid w-full grid-cols-2">
<TabsTrigger value="chat">Chat</TabsTrigger>
<TabsTrigger value="media">Media</TabsTrigger>
</TabsList>
<TabsContent value="chat" className="flex-1 flex flex-col">
<ScrollArea className="flex-1 p-4 border rounded-lg">
<div className="space-y-4">
{messages.map((msg) => (
<div key={msg.id} className={`flex gap-3 ${msg.isSystem ? 'justify-center' : ''}`}>
{!msg.isSystem && (
<Avatar className="h-8 w-8 flex-shrink-0">
<AvatarFallback className={`text-white text-xs ${
msg.user.role === 'Initiator' ? 'bg-re-green' :
msg.user.role === 'Current User' ? 'bg-blue-500' :
'bg-re-light-green'
}`}>
{msg.user.avatar}
</AvatarFallback>
</Avatar>
)}
<div className={`flex-1 ${msg.isSystem ? 'text-center' : ''}`}>
{msg.isSystem ? (
<div className="inline-flex items-center gap-2 px-3 py-1 bg-muted rounded-full text-sm text-muted-foreground">
{msg.content}
<span className="text-xs">{msg.timestamp}</span>
</div>
) : (
<>
<div className="flex items-center gap-2 mb-1">
<span className="font-medium text-sm">{msg.user.name}</span>
<Badge variant="outline" className="text-xs">
{msg.user.role}
</Badge>
<span className="text-xs text-muted-foreground">
{msg.timestamp}
</span>
</div>
<div
className="text-sm bg-muted/30 p-3 rounded-lg"
dangerouslySetInnerHTML={{ __html: formatMessage(msg.content) }}
/>
</>
)}
</div>
</div>
))}
{isTyping && (
<div className="flex gap-3">
<Avatar className="h-8 w-8">
<AvatarFallback className="bg-gray-400 text-white text-xs">
...
</AvatarFallback>
</Avatar>
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<div className="flex gap-1">
<div className="w-2 h-2 bg-muted-foreground rounded-full animate-bounce"></div>
<div className="w-2 h-2 bg-muted-foreground rounded-full animate-bounce" style={{ animationDelay: '0.1s' }}></div>
<div className="w-2 h-2 bg-muted-foreground rounded-full animate-bounce" style={{ animationDelay: '0.2s' }}></div>
</div>
Someone is typing...
</div>
</div>
)}
<div ref={messagesEndRef} />
</div>
</ScrollArea>
{/* Message Input */}
<div className="mt-4 flex gap-2">
<div className="flex-1 relative">
<Input
placeholder="Type your message... Use @username to mention someone"
value={message}
onChange={(e) => setMessage(e.target.value)}
onKeyPress={handleKeyPress}
className="pr-20"
/>
<div className="absolute right-2 top-1/2 transform -translate-y-1/2 flex gap-1">
<Button variant="ghost" size="sm">
<Smile className="h-4 w-4" />
</Button>
<Button variant="ghost" size="sm">
<Paperclip className="h-4 w-4" />
</Button>
</div>
</div>
<Button onClick={handleSendMessage} disabled={!message.trim()}>
<Send className="h-4 w-4" />
</Button>
</div>
</TabsContent>
<TabsContent value="media" className="flex-1">
<div className="p-4 border rounded-lg h-full">
<h4 className="font-medium mb-4">Documents ({documents.length})</h4>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{documents.map((doc, index) => (
<div key={index} className="border rounded-lg p-4 hover:bg-muted/30 transition-colors">
<div className="flex items-start gap-3">
<FileText className="h-8 w-8 text-muted-foreground flex-shrink-0 mt-1" />
<div className="flex-1 min-w-0">
<p className="font-medium truncate">{doc.name}</p>
<p className="text-sm text-muted-foreground">
{doc.size} by {doc.uploadedBy}
</p>
<p className="text-xs text-muted-foreground">
{doc.uploadedAt}
</p>
</div>
<div className="flex gap-1">
<Button variant="ghost" size="sm">
<Eye className="h-4 w-4" />
</Button>
<Button variant="ghost" size="sm">
<Download className="h-4 w-4" />
</Button>
</div>
</div>
</div>
))}
</div>
</div>
</TabsContent>
</Tabs>
</div>
{/* Participants Sidebar */}
<div className="w-72 border-l pl-4">
<div className="flex items-center justify-between mb-4">
<h4 className="font-medium">Participants</h4>
<Badge variant="outline">{participants.length}</Badge>
</div>
<div className="space-y-3">
{participants.map((participant, index) => (
<div key={index} className="flex items-center gap-3">
<div className="relative">
<Avatar className="h-8 w-8">
<AvatarFallback className={`text-white text-xs ${
participant.role === 'Initiator' ? 'bg-re-green' : 'bg-re-light-green'
}`}>
{participant.avatar}
</AvatarFallback>
</Avatar>
<div className={`absolute -bottom-1 -right-1 w-3 h-3 rounded-full border-2 border-background ${getStatusColor(participant.status)}`}></div>
</div>
<div className="flex-1 min-w-0">
<p className="font-medium text-sm truncate">{participant.name}</p>
<p className="text-xs text-muted-foreground">{participant.role}</p>
</div>
<Button variant="ghost" size="sm">
<MoreHorizontal className="h-4 w-4" />
</Button>
</div>
))}
</div>
<div className="mt-6">
<h4 className="font-medium mb-3">Quick Actions</h4>
<div className="space-y-2">
<Button variant="outline" size="sm" className="w-full justify-start">
<Users className="h-4 w-4 mr-2" />
Add Participant
</Button>
<Button variant="outline" size="sm" className="w-full justify-start">
<Paperclip className="h-4 w-4 mr-2" />
Upload File
</Button>
</div>
</div>
</div>
</div>
</DialogContent>
</Dialog>
);
}