enhanced ext editor in worknote page
This commit is contained in:
parent
da1d0538e9
commit
47a5a436aa
@ -1,4 +1,4 @@
|
|||||||
import { useState } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { Bell, Settings, User, Plus, Search, Home, FileText, CheckCircle, LogOut, PanelLeft, PanelLeftClose } from 'lucide-react';
|
import { Bell, Settings, User, Plus, Search, Home, FileText, CheckCircle, LogOut, PanelLeft, PanelLeftClose } from 'lucide-react';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
@ -14,7 +14,7 @@ interface PageLayoutProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function PageLayout({ children, currentPage = 'dashboard', onNavigate, onNewRequest }: PageLayoutProps) {
|
export function PageLayout({ children, currentPage = 'dashboard', onNavigate, onNewRequest }: PageLayoutProps) {
|
||||||
const [sidebarOpen, setSidebarOpen] = useState(true);
|
const [sidebarOpen, setSidebarOpen] = useState(false);
|
||||||
|
|
||||||
const menuItems = [
|
const menuItems = [
|
||||||
{ id: 'dashboard', label: 'Dashboard', icon: Home },
|
{ id: 'dashboard', label: 'Dashboard', icon: Home },
|
||||||
@ -27,11 +27,52 @@ export function PageLayout({ children, currentPage = 'dashboard', onNavigate, on
|
|||||||
setSidebarOpen(!sidebarOpen);
|
setSidebarOpen(!sidebarOpen);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Handle responsive behavior: sidebar open on desktop, closed on mobile
|
||||||
|
useEffect(() => {
|
||||||
|
const handleResize = () => {
|
||||||
|
// 768px is the md breakpoint in Tailwind
|
||||||
|
if (window.innerWidth >= 768) {
|
||||||
|
setSidebarOpen(true); // Always open on desktop
|
||||||
|
} else {
|
||||||
|
setSidebarOpen(false); // Closed by default on mobile
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set initial state
|
||||||
|
handleResize();
|
||||||
|
|
||||||
|
// Add event listener
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
return () => window.removeEventListener('resize', handleResize);
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen flex w-full bg-background">
|
<div className="min-h-screen flex w-full bg-background">
|
||||||
{/* Sidebar */}
|
{/* Mobile Overlay */}
|
||||||
<div className={`${sidebarOpen ? 'w-64' : 'w-0'} transition-all duration-300 ease-in-out overflow-hidden flex-shrink-0`}>
|
{sidebarOpen && (
|
||||||
<div className="w-64 h-full border-r border-gray-800 bg-black">
|
<div
|
||||||
|
className="fixed inset-0 bg-black/50 z-40 md:hidden"
|
||||||
|
onClick={() => setSidebarOpen(false)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Sidebar - Hidden on mobile by default, toggleable on desktop */}
|
||||||
|
<aside className={`
|
||||||
|
fixed md:relative
|
||||||
|
inset-y-0 left-0
|
||||||
|
w-64
|
||||||
|
transform transition-transform duration-300 ease-in-out
|
||||||
|
${sidebarOpen ? 'translate-x-0' : '-translate-x-full'}
|
||||||
|
md:translate-x-0
|
||||||
|
${sidebarOpen ? 'md:w-64' : 'md:w-0 md:-translate-x-full'}
|
||||||
|
z-50 md:z-auto
|
||||||
|
flex-shrink-0
|
||||||
|
border-r border-gray-800 bg-black
|
||||||
|
overflow-y-auto
|
||||||
|
`}>
|
||||||
|
<div className="w-64 h-full">
|
||||||
<div className="p-4 border-b border-gray-800">
|
<div className="p-4 border-b border-gray-800">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="w-10 h-10 bg-re-green rounded-lg flex items-center justify-center shrink-0">
|
<div className="w-10 h-10 bg-re-green rounded-lg flex items-center justify-center shrink-0">
|
||||||
@ -48,7 +89,13 @@ export function PageLayout({ children, currentPage = 'dashboard', onNavigate, on
|
|||||||
{menuItems.map((item) => (
|
{menuItems.map((item) => (
|
||||||
<button
|
<button
|
||||||
key={item.id}
|
key={item.id}
|
||||||
onClick={() => onNavigate?.(item.id)}
|
onClick={() => {
|
||||||
|
onNavigate?.(item.id);
|
||||||
|
// Close sidebar on mobile after navigation
|
||||||
|
if (window.innerWidth < 768) {
|
||||||
|
setSidebarOpen(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
className={`w-full flex items-center gap-3 px-3 py-2 rounded-lg text-sm transition-colors ${
|
className={`w-full flex items-center gap-3 px-3 py-2 rounded-lg text-sm transition-colors ${
|
||||||
currentPage === item.id
|
currentPage === item.id
|
||||||
? 'bg-re-green text-white font-medium'
|
? 'bg-re-green text-white font-medium'
|
||||||
@ -74,7 +121,7 @@ export function PageLayout({ children, currentPage = 'dashboard', onNavigate, on
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</aside>
|
||||||
|
|
||||||
{/* Main Content Area */}
|
{/* Main Content Area */}
|
||||||
<div className="flex-1 flex flex-col min-w-0">
|
<div className="flex-1 flex flex-col min-w-0">
|
||||||
|
|||||||
@ -305,7 +305,9 @@ export function WorkNoteChat({ requestId, onBack }: WorkNoteChatProps) {
|
|||||||
const [showEmojiPicker, setShowEmojiPicker] = useState(false);
|
const [showEmojiPicker, setShowEmojiPicker] = useState(false);
|
||||||
const [messages, setMessages] = useState<Message[]>(INITIAL_MESSAGES);
|
const [messages, setMessages] = useState<Message[]>(INITIAL_MESSAGES);
|
||||||
const [showSidebar, setShowSidebar] = useState(false);
|
const [showSidebar, setShowSidebar] = useState(false);
|
||||||
|
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
|
||||||
const messagesEndRef = useRef<HTMLDivElement>(null);
|
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||||||
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
// Get request info
|
// Get request info
|
||||||
const requestInfo = useMemo(() => {
|
const requestInfo = useMemo(() => {
|
||||||
@ -334,26 +336,116 @@ export function WorkNoteChat({ requestId, onBack }: WorkNoteChatProps) {
|
|||||||
}, [messages]);
|
}, [messages]);
|
||||||
|
|
||||||
const handleSendMessage = () => {
|
const handleSendMessage = () => {
|
||||||
if (message.trim()) {
|
if (message.trim() || selectedFiles.length > 0) {
|
||||||
|
const attachments = selectedFiles.map(file => ({
|
||||||
|
name: file.name,
|
||||||
|
url: URL.createObjectURL(file),
|
||||||
|
type: file.type.split('/')[1] || 'file'
|
||||||
|
}));
|
||||||
|
|
||||||
const newMessage: Message = {
|
const newMessage: Message = {
|
||||||
id: Date.now().toString(),
|
id: Date.now().toString(),
|
||||||
user: { name: 'You', avatar: 'YO', role: 'Current User' },
|
user: { name: 'You', avatar: 'YO', role: 'Current User' },
|
||||||
content: message,
|
content: message,
|
||||||
timestamp: new Date().toLocaleString(),
|
timestamp: new Date().toLocaleString('en-US', {
|
||||||
|
month: 'short',
|
||||||
|
day: 'numeric',
|
||||||
|
year: 'numeric',
|
||||||
|
hour: 'numeric',
|
||||||
|
minute: 'numeric',
|
||||||
|
hour12: true
|
||||||
|
}),
|
||||||
mentions: extractMentions(message),
|
mentions: extractMentions(message),
|
||||||
isHighPriority: message.includes('!important') || message.includes('urgent')
|
isHighPriority: message.includes('!important') || message.includes('urgent'),
|
||||||
|
attachments: attachments.length > 0 ? attachments : undefined
|
||||||
};
|
};
|
||||||
setMessages(prev => [...prev, newMessage]);
|
setMessages(prev => [...prev, newMessage]);
|
||||||
setMessage('');
|
setMessage('');
|
||||||
|
setSelectedFiles([]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleFileSelect = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
if (e.target.files) {
|
||||||
|
const filesArray = Array.from(e.target.files);
|
||||||
|
setSelectedFiles(prev => [...prev, ...filesArray]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRemoveFile = (index: number) => {
|
||||||
|
setSelectedFiles(prev => prev.filter((_, i) => i !== index));
|
||||||
|
// Reset file input to allow reselecting the same file
|
||||||
|
if (fileInputRef.current) {
|
||||||
|
fileInputRef.current.value = '';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEmojiSelect = (emoji: string) => {
|
||||||
|
setMessage(prev => prev + emoji);
|
||||||
|
setShowEmojiPicker(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAttachmentClick = () => {
|
||||||
|
fileInputRef.current?.click();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Emoji picker data - Expanded collection
|
||||||
|
const emojiList = [
|
||||||
|
// Smileys & Emotions
|
||||||
|
'😊', '😂', '🤣', '😁', '😃', '😄', '😅', '😆', '😉', '😌',
|
||||||
|
'😍', '🥰', '😘', '😗', '😙', '😚', '🙂', '🤗', '🤩', '🤔',
|
||||||
|
'🤨', '😐', '😑', '😶', '🙄', '😏', '😒', '🙃', '😬', '🤐',
|
||||||
|
'😴', '😪', '😵', '🤯', '🤪', '😜', '😝', '😛', '🤤', '😋',
|
||||||
|
'😎', '🤓', '🧐', '😕', '😟', '🙁', '☹️', '😮', '😯', '😲',
|
||||||
|
'😳', '🥺', '😦', '😧', '😨', '😰', '😥', '😢', '😭', '😱',
|
||||||
|
'😖', '😣', '😞', '😓', '😩', '😫', '🥱', '😤', '😡', '😠',
|
||||||
|
'🤬', '😈', '👿', '💀', '☠️', '💩', '🤡', '👹', '👺', '👻',
|
||||||
|
|
||||||
|
// Gestures & Body
|
||||||
|
'👋', '🤚', '🖐️', '✋', '🖖', '👌', '🤌', '🤏', '✌️', '🤞',
|
||||||
|
'🤟', '🤘', '🤙', '👈', '👉', '👆', '🖕', '👇', '☝️', '👍',
|
||||||
|
'👎', '✊', '👊', '🤛', '🤜', '👏', '🙌', '👐', '🤲', '🤝',
|
||||||
|
'🙏', '💪', '🦾', '🦿', '🦵', '🦶', '👂', '🦻', '👃', '🧠',
|
||||||
|
|
||||||
|
// Hearts & Love
|
||||||
|
'❤️', '🧡', '💛', '💚', '💙', '💜', '🖤', '🤍', '🤎', '💔',
|
||||||
|
'❣️', '💕', '💞', '💓', '💗', '💖', '💘', '💝', '💟', '❤️🔥',
|
||||||
|
|
||||||
|
// Work & Office
|
||||||
|
'💼', '📊', '📈', '📉', '💻', '⌨️', '🖥️', '🖨️', '🖱️', '💾',
|
||||||
|
'💿', '📱', '☎️', '📞', '📟', '📠', '📧', '✉️', '📨', '📩',
|
||||||
|
'📮', '📪', '📫', '📬', '📭', '📄', '📃', '📑', '📝', '✏️',
|
||||||
|
'✒️', '🖊️', '🖋️', '📏', '📐', '📌', '📍', '🗂️', '📁', '📂',
|
||||||
|
|
||||||
|
// Success & Achievement
|
||||||
|
'✅', '✔️', '☑️', '🎯', '🎖️', '🏆', '🥇', '🥈', '🥉', '⭐',
|
||||||
|
'🌟', '✨', '💫', '🔥', '💥', '⚡', '💯', '🎉', '🎊', '🎈',
|
||||||
|
|
||||||
|
// Alerts & Symbols
|
||||||
|
'⚠️', '🚫', '❌', '⛔', '🚷', '🚯', '🚱', '🚳', '🔞', '📵',
|
||||||
|
'❗', '❓', '❕', '❔', '‼️', '⁉️', '💢', '💬', '💭', '🗯️',
|
||||||
|
|
||||||
|
// Time & Calendar
|
||||||
|
'⏰', '⏱️', '⏲️', '⏳', '⌛', '📅', '📆', '🗓️', '📇', '🕐',
|
||||||
|
'🕑', '🕒', '🕓', '🕔', '🕕', '🕖', '🕗', '🕘', '🕙', '🕚',
|
||||||
|
|
||||||
|
// Actions & Arrows
|
||||||
|
'🚀', '🎯', '🎲', '🎰', '🧩', '🔍', '🔎', '🔑', '🗝️', '🔒',
|
||||||
|
'🔓', '🔐', '🔏', '🔔', '🔕', '📣', '📢', '💡', '🔦', '🏮',
|
||||||
|
'➕', '➖', '✖️', '➗', '♾️', '‼️', '⁉️', '❓', '❔', '❕',
|
||||||
|
'🔄', '🔃', '🔂', '▶️', '⏸️', '⏯️', '⏹️', '⏺️', '⏭️', '⏮️',
|
||||||
|
'⏩', '⏪', '⏫', '⏬', '◀️', '🔼', '🔽', '➡️', '⬅️', '⬆️',
|
||||||
|
'⬇️', '↗️', '↘️', '↙️', '↖️', '↕️', '↔️', '↪️', '↩️', '⤴️'
|
||||||
|
];
|
||||||
|
|
||||||
const extractMentions = (text: string): string[] => {
|
const extractMentions = (text: string): string[] => {
|
||||||
const mentionRegex = /@([\w\s]+)(?=\s|$|[.,!?])/g;
|
const mentionRegex = /@([\w\s]+)(?=\s|$|[.,!?])/g;
|
||||||
const mentions = [];
|
const mentions: string[] = [];
|
||||||
let match;
|
let match;
|
||||||
while ((match = mentionRegex.exec(text)) !== null) {
|
while ((match = mentionRegex.exec(text)) !== null) {
|
||||||
mentions.push(match[1].trim());
|
if (match[1]) {
|
||||||
|
mentions.push(match[1].trim());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return mentions;
|
return mentions;
|
||||||
};
|
};
|
||||||
@ -392,8 +484,8 @@ export function WorkNoteChat({ requestId, onBack }: WorkNoteChatProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-screen max-h-screen flex flex-col bg-gray-50 overflow-hidden">
|
<div className="h-screen max-h-screen flex flex-col bg-gray-50 overflow-hidden">
|
||||||
{/* Header */}
|
{/* Header - Fixed at top */}
|
||||||
<div className="bg-white border-b border-gray-200 px-3 sm:px-6 py-4">
|
<div className="bg-white border-b border-gray-200 px-3 sm:px-6 py-4 flex-shrink-0">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-2 sm:gap-4 min-w-0 flex-1">
|
<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">
|
<Button variant="ghost" size="icon" onClick={onBack} className="shrink-0">
|
||||||
@ -456,9 +548,9 @@ export function WorkNoteChat({ requestId, onBack }: WorkNoteChatProps) {
|
|||||||
<div className="flex-1 flex overflow-hidden relative">
|
<div className="flex-1 flex overflow-hidden relative">
|
||||||
{/* Main Chat Area */}
|
{/* Main Chat Area */}
|
||||||
<div className="flex-1 flex flex-col min-w-0">
|
<div className="flex-1 flex flex-col min-w-0">
|
||||||
<Tabs value={activeTab} onValueChange={setActiveTab} className="flex-1 flex flex-col">
|
<Tabs value={activeTab} onValueChange={setActiveTab} className="flex-1 flex flex-col overflow-hidden">
|
||||||
{/* Tab Navigation */}
|
{/* Tab Navigation - Fixed */}
|
||||||
<div className="bg-white border-b border-gray-200 px-2 sm:px-3 lg:px-6">
|
<div className="bg-white border-b border-gray-200 px-2 sm:px-3 lg:px-6 flex-shrink-0">
|
||||||
<TabsList className="grid w-full max-w-full sm:max-w-md grid-cols-3 bg-gray-100 h-10">
|
<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">
|
<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" />
|
<MessageSquare className="w-3 h-3 sm:w-4 sm:h-4" />
|
||||||
@ -478,9 +570,9 @@ export function WorkNoteChat({ requestId, onBack }: WorkNoteChatProps) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Chat Tab */}
|
{/* Chat Tab */}
|
||||||
<TabsContent value="chat" className="flex-1 flex flex-col m-0">
|
<TabsContent value="chat" className="flex-1 flex flex-col m-0 overflow-hidden min-h-0">
|
||||||
{/* Search Bar */}
|
{/* Search Bar - Fixed */}
|
||||||
<div className="bg-white border-b border-gray-200 px-2 sm:px-3 lg:px-6 py-2 sm:py-3">
|
<div className="bg-white border-b border-gray-200 px-2 sm:px-3 lg:px-6 py-2 sm:py-3 flex-shrink-0">
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
|
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
|
||||||
<Input
|
<Input
|
||||||
@ -492,8 +584,8 @@ export function WorkNoteChat({ requestId, onBack }: WorkNoteChatProps) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Messages Area */}
|
{/* Messages Area - Fixed height with proper scrolling */}
|
||||||
<div className="flex-1 overflow-y-auto px-2 sm:px-3 lg:px-6 py-2 sm:py-4">
|
<div className="flex-1 overflow-y-auto overflow-x-hidden px-2 sm:px-3 lg:px-6 py-2 sm:py-4 min-h-0">
|
||||||
<div className="space-y-3 sm:space-y-6 max-w-full">
|
<div className="space-y-3 sm:space-y-6 max-w-full">
|
||||||
{filteredMessages.map((msg) => (
|
{filteredMessages.map((msg) => (
|
||||||
<div key={msg.id} className={`flex gap-2 sm:gap-3 lg:gap-4 ${msg.isSystem ? 'justify-center' : ''}`}>
|
<div key={msg.id} className={`flex gap-2 sm:gap-3 lg:gap-4 ${msg.isSystem ? 'justify-center' : ''}`}>
|
||||||
@ -617,57 +709,145 @@ export function WorkNoteChat({ requestId, onBack }: WorkNoteChatProps) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Message Input */}
|
{/* Message Input - Fixed at bottom */}
|
||||||
<div className="bg-white border-t border-gray-200 p-2 sm:p-3 lg:p-6">
|
<div className="bg-white border-t border-gray-200 p-2 sm:p-3 lg:p-6 flex-shrink-0">
|
||||||
<div className="max-w-full">
|
<div className="max-w-full">
|
||||||
<div className="flex flex-col gap-2 sm:gap-4">
|
{/* Hidden File Input */}
|
||||||
<div className="flex-1">
|
<input
|
||||||
<Textarea
|
type="file"
|
||||||
placeholder="Type your message... Use @username to mention someone"
|
ref={fileInputRef}
|
||||||
value={message}
|
onChange={handleFileSelect}
|
||||||
onChange={(e) => setMessage(e.target.value)}
|
className="hidden"
|
||||||
onKeyPress={handleKeyPress}
|
multiple
|
||||||
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"
|
accept="image/*,.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.txt"
|
||||||
rows={2}
|
/>
|
||||||
/>
|
|
||||||
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-center mt-2 gap-2">
|
{/* Selected Files Preview - Scrollable if many files */}
|
||||||
<div className="flex items-center gap-1 sm:gap-2 order-2 sm:order-1">
|
{selectedFiles.length > 0 && (
|
||||||
<Button variant="ghost" size="sm" className="text-gray-500 h-7 w-7 sm:h-8 sm:w-8 p-0">
|
<div className="mb-3 space-y-2 max-h-32 overflow-y-auto pr-2">
|
||||||
<Paperclip className="h-3 w-3 sm:h-4 sm:w-4" />
|
{selectedFiles.map((file, index) => (
|
||||||
|
<div key={index} className="flex items-center gap-2 p-2 bg-blue-50 rounded-lg border border-blue-200">
|
||||||
|
<span className="text-lg flex-shrink-0">{getFileIcon(file.type.split('/')[1] || 'file')}</span>
|
||||||
|
<span className="text-sm text-gray-700 flex-1 truncate min-w-0">{file.name}</span>
|
||||||
|
<span className="text-xs text-gray-500 flex-shrink-0">{(file.size / 1024).toFixed(1)} KB</span>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => handleRemoveFile(index)}
|
||||||
|
className="h-6 w-6 p-0 hover:bg-red-100 flex-shrink-0"
|
||||||
|
>
|
||||||
|
<X className="h-3 w-3 text-red-600" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="ghost" size="sm" className="text-gray-500 h-7 w-7 sm:h-8 sm:w-8 p-0">
|
</div>
|
||||||
<Smile className="h-3 w-3 sm:h-4 sm:w-4" />
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Textarea with Emoji Picker */}
|
||||||
|
<div className="relative mb-2">
|
||||||
|
<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}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Emoji Picker Popup */}
|
||||||
|
{showEmojiPicker && (
|
||||||
|
<div className="absolute bottom-full left-0 mb-2 bg-white border border-gray-200 rounded-lg shadow-xl p-3 z-50 w-full sm:w-96 max-h-80 overflow-y-auto">
|
||||||
|
<div className="flex items-center justify-between mb-3 sticky top-0 bg-white pb-2 border-b">
|
||||||
|
<span className="text-sm font-semibold text-gray-700">Pick an emoji</span>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setShowEmojiPicker(false)}
|
||||||
|
className="h-6 w-6 p-0"
|
||||||
|
>
|
||||||
|
<X className="h-3 w-3" />
|
||||||
</Button>
|
</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">
|
</div>
|
||||||
|
<div className="grid grid-cols-8 sm:grid-cols-10 gap-1">
|
||||||
|
{emojiList.map((emoji, index) => (
|
||||||
|
<button
|
||||||
|
key={index}
|
||||||
|
onClick={() => handleEmojiSelect(emoji)}
|
||||||
|
className="text-xl sm:text-2xl hover:bg-gray-100 rounded p-1 transition-colors flex items-center justify-center"
|
||||||
|
title={emoji}
|
||||||
|
>
|
||||||
|
{emoji}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Action Buttons Row - Always visible */}
|
||||||
|
<div className="flex items-center justify-between gap-2 flex-shrink-0">
|
||||||
|
{/* Left side - Action buttons */}
|
||||||
|
<div className="flex items-center gap-1 sm:gap-2 flex-shrink-0">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className="text-gray-500 h-8 w-8 p-0 hover:bg-blue-50 hover:text-blue-600 flex-shrink-0"
|
||||||
|
onClick={handleAttachmentClick}
|
||||||
|
title="Attach file"
|
||||||
|
>
|
||||||
|
<Paperclip className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className="text-gray-500 h-8 w-8 p-0 hover:bg-blue-50 hover:text-blue-600 flex-shrink-0"
|
||||||
|
onClick={() => setShowEmojiPicker(!showEmojiPicker)}
|
||||||
|
title="Add emoji"
|
||||||
|
>
|
||||||
|
<Smile className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className="text-gray-500 h-8 w-8 p-0 hidden sm:flex hover:bg-blue-50 hover:text-blue-600 flex-shrink-0"
|
||||||
|
onClick={() => setMessage(prev => prev + '@')}
|
||||||
|
title="Mention someone"
|
||||||
|
>
|
||||||
<AtSign className="h-4 w-4" />
|
<AtSign className="h-4 w-4" />
|
||||||
</Button>
|
</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">
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className="text-gray-500 h-8 w-8 p-0 hidden sm:flex hover:bg-blue-50 hover:text-blue-600 flex-shrink-0"
|
||||||
|
onClick={() => setMessage(prev => prev + '#')}
|
||||||
|
title="Add hashtag"
|
||||||
|
>
|
||||||
<Hash className="h-4 w-4" />
|
<Hash className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 order-1 sm:order-2">
|
|
||||||
<span className="text-xs text-gray-500 hidden sm:inline">
|
{/* Right side - Character count and Send button */}
|
||||||
|
<div className="flex items-center gap-2 ml-auto flex-shrink-0">
|
||||||
|
<span className="text-xs text-gray-500 hidden md:inline whitespace-nowrap">
|
||||||
{message.length}/2000
|
{message.length}/2000
|
||||||
</span>
|
</span>
|
||||||
<Button
|
<Button
|
||||||
onClick={handleSendMessage}
|
onClick={handleSendMessage}
|
||||||
disabled={!message.trim()}
|
disabled={!message.trim() && selectedFiles.length === 0}
|
||||||
className="bg-blue-600 hover:bg-blue-700 min-w-0 h-8 sm:h-9"
|
className="bg-blue-600 hover:bg-blue-700 h-8 sm:h-9 px-3 sm:px-4 disabled:opacity-50 disabled:cursor-not-allowed flex-shrink-0"
|
||||||
size="sm"
|
size="sm"
|
||||||
>
|
>
|
||||||
<Send className="h-3 w-3 sm:h-4 sm:w-4 sm:mr-2" />
|
<Send className="h-4 w-4 sm:mr-2" />
|
||||||
<span className="hidden sm:inline ml-1">Send</span>
|
<span className="hidden sm:inline">Send</span>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
{/* Files Tab */}
|
{/* Files Tab */}
|
||||||
<TabsContent value="files" className="flex-1 p-2 sm:p-3 lg:p-6 m-0">
|
<TabsContent value="files" className="flex-1 p-2 sm:p-3 lg:p-6 m-0 pt-0">
|
||||||
<div className="max-w-full">
|
<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">
|
<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>
|
<h3 className="text-base sm:text-lg font-semibold text-gray-900">Shared Files</h3>
|
||||||
@ -714,7 +894,7 @@ export function WorkNoteChat({ requestId, onBack }: WorkNoteChatProps) {
|
|||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
{/* Activity Tab */}
|
{/* Activity Tab */}
|
||||||
<TabsContent value="activity" className="flex-1 p-2 sm:p-3 lg:p-6 m-0">
|
<TabsContent value="activity" className="flex-1 p-2 sm:p-3 lg:p-6 m-0 pt-0">
|
||||||
<div className="max-w-full">
|
<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>
|
<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">
|
<div className="space-y-3 sm:space-y-4">
|
||||||
|
|||||||
@ -604,6 +604,30 @@
|
|||||||
[data-slot="dialog-content"] {
|
[data-slot="dialog-content"] {
|
||||||
background-color: rgb(255 255 255) !important;
|
background-color: rgb(255 255 255) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Custom scrollbar for better UX */
|
||||||
|
.overflow-y-auto::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overflow-y-auto::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overflow-y-auto::-webkit-scrollbar-thumb {
|
||||||
|
background: rgb(203 213 225);
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overflow-y-auto::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: rgb(148 163 184);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Firefox scrollbar */
|
||||||
|
.overflow-y-auto {
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: rgb(203 213 225) transparent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
html {
|
html {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user