mention feature addd
This commit is contained in:
parent
61ba649ac4
commit
7efc5c5d94
@ -105,9 +105,10 @@ const getStatusText = (status: 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
|
||||
.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 />');
|
||||
};
|
||||
|
||||
@ -689,6 +690,26 @@ export function WorkNoteChat({ requestId, onBack, messages: externalMessages, on
|
||||
|
||||
const handleSendMessage = async () => {
|
||||
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 => ({
|
||||
name: file.name,
|
||||
url: URL.createObjectURL(file),
|
||||
@ -707,19 +728,26 @@ export function WorkNoteChat({ requestId, onBack, messages: externalMessages, on
|
||||
minute: 'numeric',
|
||||
hour12: true
|
||||
}),
|
||||
mentions: extractMentions(message),
|
||||
mentions: mentions,
|
||||
isHighPriority: message.includes('!important') || message.includes('urgent'),
|
||||
attachments: attachments.length > 0 ? attachments : undefined,
|
||||
isCurrentUser: true
|
||||
};
|
||||
// console.log('new message ->', newMessage, onSend);
|
||||
|
||||
// If external onSend provided, delegate to caller (RequestDetail will POST and refresh)
|
||||
if (onSend) {
|
||||
try { await onSend(message, selectedFiles); } catch { /* ignore */ }
|
||||
} else {
|
||||
// Fallback: call backend directly
|
||||
// Fallback: call backend directly with mentions
|
||||
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 mapped = Array.isArray(rows) ? rows.map((m: any) => {
|
||||
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 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[] = [];
|
||||
let match;
|
||||
while ((match = mentionRegex.exec(text)) !== null) {
|
||||
@ -1033,6 +1062,7 @@ export function WorkNoteChat({ requestId, onBack, messages: externalMessages, on
|
||||
mentions.push(match[1].trim());
|
||||
}
|
||||
}
|
||||
console.log('[Extract Mentions] Found:', mentions, 'from text:', text);
|
||||
return mentions;
|
||||
};
|
||||
|
||||
@ -1361,8 +1391,95 @@ export function WorkNoteChat({ requestId, onBack, messages: externalMessages, on
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Textarea with Emoji Picker */}
|
||||
{/* Textarea with Mention Dropdown and Emoji Picker */}
|
||||
<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
|
||||
placeholder="Type your message... Use @username to mention someone"
|
||||
value={message}
|
||||
@ -1427,7 +1544,7 @@ export function WorkNoteChat({ requestId, onBack, messages: externalMessages, on
|
||||
<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"
|
||||
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 + '@')}
|
||||
title="Mention someone"
|
||||
>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user