feat(14-01): render attachment messages with distinct styling and metadata

- Import formatFileSize from attachmentValidation service
- Add isAttachmentMessage helper to detect attachment messages
- Render attachment messages with light blue background (#e3f2fd)
- Display paperclip icon with accessible aria-label
- Show '[name] attached [filename]' format with file size if available
- Update PropTypes to include optional attachment fields
- Maintain React.memo for performance
This commit is contained in:
Nuwan 2026-02-05 19:43:46 +05:30
parent 6d1cf489d7
commit b8bfd23a9c
1 changed files with 92 additions and 32 deletions

View File

@ -1,6 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { formatTimestamp } from '../../../utils/formatTimestamp';
import { formatFileSize } from '../../../services/attachmentValidation';
/**
* Get initials for avatar from sender name
@ -8,67 +9,120 @@ import { formatTimestamp } from '../../../utils/formatTimestamp';
* @param {string} name - Sender name
* @returns {string} Initials (e.g., "JD" for "John Doe")
*/
const getInitials = (name) => {
const getInitials = name => {
if (!name) return '?';
const parts = name.trim().split(' ');
if (parts.length === 1) return parts[0][0].toUpperCase();
return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
};
/**
* Check if message is an attachment
* @param {Object} message - Message object
* @returns {boolean} True if message has attachment
*/
const isAttachmentMessage = message => {
return message.attachmentId && message.attachmentName;
};
/**
* JKChatMessage - Individual message display component
*
* Displays a single chat message with avatar, sender name, message text, and timestamp.
* Supports attachment messages with distinct styling and metadata display.
*
* Features:
* - Avatar with initials (first + last name)
* - Sender name in bold
* - Relative timestamp (via formatTimestamp utility)
* - Message text with word wrapping
* - Attachment display with paperclip icon, filename, and size
* - React.memo for performance optimization
*/
const JKChatMessage = ({ message }) => {
const { senderName, message: text, createdAt } = message;
const { senderName, message: text, createdAt, attachmentName, attachmentSize } = message;
return (
<div style={{
// Common avatar style
const avatarStyle = {
width: '36px',
height: '36px',
borderRadius: '50%',
backgroundColor: '#007bff',
color: 'white',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontWeight: 'bold',
fontSize: '14px',
flexShrink: 0
};
// Attachment message styling
if (isAttachmentMessage(message)) {
const attachmentStyle = {
display: 'flex',
gap: '12px',
marginBottom: '16px',
padding: '8px',
borderRadius: '4px',
backgroundColor: '#f8f9fa'
}}>
{/* Avatar */}
<div style={{
width: '36px',
height: '36px',
borderRadius: '50%',
backgroundColor: '#007bff',
color: 'white',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontWeight: 'bold',
fontSize: '14px',
flexShrink: 0
}}>
{getInitials(senderName)}
backgroundColor: '#e3f2fd',
border: '1px solid #bbdefb'
};
const fileIconStyle = {
fontSize: '16px',
marginRight: '4px'
};
return (
<div style={attachmentStyle}>
{/* Avatar */}
<div style={avatarStyle}>{getInitials(senderName)}</div>
{/* Attachment content */}
<div style={{ flex: 1, minWidth: 0 }}>
<div style={{ display: 'flex', alignItems: 'baseline', gap: '8px', marginBottom: '4px' }}>
<span style={{ fontWeight: 600, fontSize: '14px', color: '#212529' }}>{senderName || 'Unknown'}</span>
<span style={{ fontSize: '12px', color: '#6c757d' }}>{formatTimestamp(createdAt)}</span>
</div>
<div style={{ fontSize: '14px', color: '#212529', display: 'flex', alignItems: 'center' }}>
<span style={fileIconStyle} role="img" aria-label="attachment">
📎
</span>
<span>
attached <strong>{attachmentName}</strong>
{attachmentSize && (
<span style={{ color: '#6c757d', marginLeft: '4px' }}>({formatFileSize(attachmentSize)})</span>
)}
</span>
</div>
</div>
</div>
);
}
// Regular text message
return (
<div
style={{
display: 'flex',
gap: '12px',
marginBottom: '16px',
padding: '8px',
borderRadius: '4px',
backgroundColor: '#f8f9fa'
}}
>
{/* Avatar */}
<div style={avatarStyle}>{getInitials(senderName)}</div>
{/* Message content */}
<div style={{ flex: 1, minWidth: 0 }}>
<div style={{ display: 'flex', alignItems: 'baseline', gap: '8px', marginBottom: '4px' }}>
<span style={{ fontWeight: 600, fontSize: '14px', color: '#212529' }}>
{senderName || 'Unknown'}
</span>
<span style={{ fontSize: '12px', color: '#6c757d' }}>
{formatTimestamp(createdAt)}
</span>
</div>
<div style={{ fontSize: '14px', color: '#212529', wordWrap: 'break-word' }}>
{text}
<span style={{ fontWeight: 600, fontSize: '14px', color: '#212529' }}>{senderName || 'Unknown'}</span>
<span style={{ fontSize: '12px', color: '#6c757d' }}>{formatTimestamp(createdAt)}</span>
</div>
<div style={{ fontSize: '14px', color: '#212529', wordWrap: 'break-word' }}>{text}</div>
</div>
</div>
);
@ -79,8 +133,14 @@ JKChatMessage.propTypes = {
id: PropTypes.string.isRequired,
senderId: PropTypes.string.isRequired,
senderName: PropTypes.string,
message: PropTypes.string.isRequired,
createdAt: PropTypes.string.isRequired
message: PropTypes.string, // Optional for attachment-only messages
createdAt: PropTypes.string.isRequired,
// Attachment fields (optional)
attachmentId: PropTypes.string,
attachmentName: PropTypes.string,
attachmentType: PropTypes.oneOf(['notation', 'audio']),
purpose: PropTypes.string,
attachmentSize: PropTypes.number
}).isRequired
};