mention feature addd

This commit is contained in:
laxmanhalaki 2025-11-10 20:10:41 +05:30
parent 61ba649ac4
commit 7efc5c5d94

View File

@ -105,9 +105,10 @@ const getStatusText = (status: string) => {
}; };
const formatMessage = (content: string) => { const formatMessage = (content: string) => {
// Enhanced mention highlighting with better regex // Enhanced mention highlighting - Blue color with extra bold font for high visibility
// Matches: @test user11 or @Test User11 (any case, stops before next sentence/punctuation)
return content 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(/@([A-Za-z0-9]+(?:\s+[A-Za-z0-9]+)*?)(?=\s+(?:[a-z][a-z\s]*)?(?:[.,!?;:]|$))/g, '<span class="inline-flex items-center px-2.5 py-0.5 rounded-md bg-blue-50 text-blue-800 font-black text-base border-2 border-blue-400 shadow-sm">@$1</span>')
.replace(/\n/g, '<br />'); .replace(/\n/g, '<br />');
}; };
@ -689,6 +690,26 @@ export function WorkNoteChat({ requestId, onBack, messages: externalMessages, on
const handleSendMessage = async () => { const handleSendMessage = async () => {
if (message.trim() || selectedFiles.length > 0) { if (message.trim() || selectedFiles.length > 0) {
// Extract mentions from message
const mentions = extractMentions(message);
// Find mentioned user IDs from participants
const mentionedUserIds = mentions
.map(mentionedName => {
const participant = participants.find(p =>
p.name.toLowerCase().includes(mentionedName.toLowerCase())
);
console.log('[Mention Match] Looking for:', mentionedName, 'Found participant:', participant ? `${participant.name} (${(participant as any)?.userId})` : 'NOT FOUND');
return (participant as any)?.userId;
})
.filter(Boolean);
console.log('[WorkNoteChat] 📝 MESSAGE:', message);
console.log('[WorkNoteChat] 👥 ALL PARTICIPANTS:', participants.map(p => ({ name: p.name, userId: (p as any)?.userId })));
console.log('[WorkNoteChat] 🎯 MENTIONS EXTRACTED:', mentions);
console.log('[WorkNoteChat] 🆔 USER IDS FOUND:', mentionedUserIds);
console.log('[WorkNoteChat] 📤 SENDING TO BACKEND:', { message, mentions: mentionedUserIds });
const attachments = selectedFiles.map(file => ({ const attachments = selectedFiles.map(file => ({
name: file.name, name: file.name,
url: URL.createObjectURL(file), url: URL.createObjectURL(file),
@ -707,19 +728,26 @@ export function WorkNoteChat({ requestId, onBack, messages: externalMessages, on
minute: 'numeric', minute: 'numeric',
hour12: true hour12: true
}), }),
mentions: extractMentions(message), mentions: mentions,
isHighPriority: message.includes('!important') || message.includes('urgent'), isHighPriority: message.includes('!important') || message.includes('urgent'),
attachments: attachments.length > 0 ? attachments : undefined, attachments: attachments.length > 0 ? attachments : undefined,
isCurrentUser: true isCurrentUser: true
}; };
// console.log('new message ->', newMessage, onSend);
// If external onSend provided, delegate to caller (RequestDetail will POST and refresh) // If external onSend provided, delegate to caller (RequestDetail will POST and refresh)
if (onSend) { if (onSend) {
try { await onSend(message, selectedFiles); } catch { /* ignore */ } try { await onSend(message, selectedFiles); } catch { /* ignore */ }
} else { } else {
// Fallback: call backend directly // Fallback: call backend directly with mentions
try { try {
await createWorkNoteMultipart(effectiveRequestId, { message }, selectedFiles); await createWorkNoteMultipart(
effectiveRequestId,
{
message,
mentions: mentionedUserIds // Send mentioned user IDs to backend
},
selectedFiles
);
const rows = await getWorkNotes(effectiveRequestId); const rows = await getWorkNotes(effectiveRequestId);
const mapped = Array.isArray(rows) ? rows.map((m: any) => { const mapped = Array.isArray(rows) ? rows.map((m: any) => {
const noteUserId = m.userId || m.user_id; const noteUserId = m.userId || m.user_id;
@ -1025,7 +1053,8 @@ export function WorkNoteChat({ requestId, onBack, messages: externalMessages, on
]; ];
const extractMentions = (text: string): string[] => { const extractMentions = (text: string): string[] => {
const mentionRegex = /@([\w\s]+)(?=\s|$|[.,!?])/g; // Use the SAME regex pattern as formatMessage to ensure consistency
const mentionRegex = /@([A-Za-z0-9]+(?:\s+[A-Za-z0-9]+)*?)(?=\s+(?:[a-z][a-z\s]*)?(?:[.,!?;:]|$))/g;
const mentions: string[] = []; const mentions: string[] = [];
let match; let match;
while ((match = mentionRegex.exec(text)) !== null) { while ((match = mentionRegex.exec(text)) !== null) {
@ -1033,6 +1062,7 @@ export function WorkNoteChat({ requestId, onBack, messages: externalMessages, on
mentions.push(match[1].trim()); mentions.push(match[1].trim());
} }
} }
console.log('[Extract Mentions] Found:', mentions, 'from text:', text);
return mentions; return mentions;
}; };
@ -1361,8 +1391,95 @@ export function WorkNoteChat({ requestId, onBack, messages: externalMessages, on
</div> </div>
)} )}
{/* Textarea with Emoji Picker */} {/* Textarea with Mention Dropdown and Emoji Picker */}
<div className="relative mb-2"> <div className="relative mb-2">
{/* Mention Suggestions Dropdown - Shows above textarea */}
{(() => {
const lastAtIndex = message.lastIndexOf('@');
const hasAt = lastAtIndex >= 0;
const textAfterAt = hasAt ? message.slice(lastAtIndex + 1) : '';
// Don't show if:
// 1. No @ found
// 2. Text after @ is too long (>20 chars)
// 3. Text after @ ends with a space (completed mention)
// 4. Text after @ contains a space (already selected a user)
const endsWithSpace = textAfterAt.endsWith(' ');
const containsSpace = textAfterAt.trim().includes(' ');
const shouldShowDropdown = hasAt &&
textAfterAt.length <= 20 &&
!endsWithSpace &&
!containsSpace;
console.log('[Mention Debug]', {
hasAt,
textAfterAt: `"${textAfterAt}"`,
endsWithSpace,
containsSpace,
shouldShowDropdown,
participantsCount: participants.length
});
if (!shouldShowDropdown) return null;
const searchTerm = textAfterAt.toLowerCase();
const filteredParticipants = participants.filter(p => {
// Exclude current user from mention suggestions
const isCurrentUserInList = (p as any).userId === currentUserId;
if (isCurrentUserInList) return false;
// Filter by search term (empty search term shows all)
if (searchTerm) {
return p.name.toLowerCase().includes(searchTerm);
}
return true; // Show all if no search term
});
console.log('[Mention Debug] Filtered participants:', filteredParticipants.length);
return (
<div className="absolute bottom-full left-0 mb-2 bg-white border-2 border-blue-300 rounded-lg shadow-2xl p-3 z-[100] w-full sm:max-w-md">
<p className="text-sm font-semibold text-gray-900 mb-2">💬 Mention someone</p>
<div className="max-h-60 overflow-y-auto space-y-1">
{filteredParticipants.length > 0 ? (
filteredParticipants.map((participant, idx) => (
<button
key={idx}
type="button"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
const lastAt = message.lastIndexOf('@');
const before = message.slice(0, lastAt);
setMessage(before + '@' + participant.name + ' ');
}}
className="w-full flex items-center gap-3 p-3 hover:bg-blue-50 rounded-lg text-left transition-colors border border-transparent hover:border-blue-200"
>
<Avatar className="h-10 w-10">
<AvatarFallback className={`text-white text-sm font-semibold ${
participant.role === 'Initiator' ? 'bg-green-600' :
participant.role === 'Approver' ? 'bg-purple-600' :
'bg-blue-500'
}`}>
{participant.avatar}
</AvatarFallback>
</Avatar>
<div className="flex-1 min-w-0">
<p className="text-sm font-semibold text-gray-900">{participant.name}</p>
<p className="text-xs text-gray-600">{participant.role}</p>
</div>
</button>
))
) : (
<p className="text-sm text-gray-500 text-center py-4">
{searchTerm ? `No participants found matching "${searchTerm}"` : 'No other participants available'}
</p>
)}
</div>
</div>
);
})()}
<Textarea <Textarea
placeholder="Type your message... Use @username to mention someone" placeholder="Type your message... Use @username to mention someone"
value={message} value={message}
@ -1427,7 +1544,7 @@ export function WorkNoteChat({ requestId, onBack, messages: externalMessages, on
<Button <Button
variant="ghost" variant="ghost"
size="sm" 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" className="text-gray-500 h-8 w-8 p-0 hover:bg-blue-50 hover:text-blue-600 flex-shrink-0"
onClick={() => setMessage(prev => prev + '@')} onClick={() => setMessage(prev => prev + '@')}
title="Mention someone" title="Mention someone"
> >