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:
parent
6d1cf489d7
commit
b8bfd23a9c
|
|
@ -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
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue