refactor(frontend): Phase 3 - create advanced composite components
Extract complex UI sections into reusable composite components New Components Created: 1. HeatBadges (heats/HeatBadges.jsx) - Displays competition heats with compact notation - Configurable max visible badges with "+X more" overflow - Tooltips with full heat information 2. UserListItem (users/UserListItem.jsx) - Reusable user entry with avatar, username, full name - Optional heat badges display - Flexible action button slot (render props pattern) - Online/offline status support 3. ParticipantsSidebar (events/ParticipantsSidebar.jsx) - Complete sidebar component for event participants - Filter checkbox for hiding users from own heats - Participant and online counters - Integrated UserListItem with match actions 4. FileTransferProgress (webrtc/FileTransferProgress.jsx) - WebRTC P2P file transfer UI - Progress bar with percentage - Send/Cancel actions 5. LinkShareInput (webrtc/LinkShareInput.jsx) - Fallback link sharing when WebRTC unavailable - Google Drive, Dropbox link support Pages Refactored: - EventChatPage: 564 → 471 lines (-93 lines, -16%) * Replaced 90-line participants sidebar with <ParticipantsSidebar /> * Removed duplicate formatHeat logic (now in HeatBadges) - MatchChatPage: 446 → 369 lines (-77 lines, -17%) * Replaced 56-line file transfer UI with <FileTransferProgress /> * Replaced 39-line link input form with <LinkShareInput /> Phase 3 Total: -170 lines Grand Total (Phase 1+2+3): -559 lines (-17%) Final Results: - EventChatPage: 761 → 471 lines (-290 lines, -38% reduction) - MatchChatPage: 567 → 369 lines (-198 lines, -35% reduction) Benefits: - Massive complexity reduction in largest components - Composite components can be reused across pages - Better testability - each component tested independently - Cleaner code organization - single responsibility principle - Easier maintenance - changes in one place propagate everywhere
This commit is contained in:
65
frontend/src/components/heats/HeatBadges.jsx
Normal file
65
frontend/src/components/heats/HeatBadges.jsx
Normal file
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Reusable Heat Badges component
|
||||
* Displays competition heats with compact notation (e.g., "J&J NOV 1 L")
|
||||
*
|
||||
* @param {Array} heats - Array of heat objects with { competitionType, division, heatNumber, role }
|
||||
* @param {number} maxVisible - Maximum number of badges to show before "+X more" (default: 3)
|
||||
* @param {boolean} compact - Use compact display (default: true)
|
||||
* @param {string} className - Additional CSS classes for container
|
||||
*
|
||||
* @example
|
||||
* <HeatBadges heats={userHeats} maxVisible={3} />
|
||||
* // Renders: "J&J NOV 1 L" "STR INT 2 F" "+2"
|
||||
*/
|
||||
const HeatBadges = ({
|
||||
heats = [],
|
||||
maxVisible = 3,
|
||||
compact = true,
|
||||
className = ''
|
||||
}) => {
|
||||
if (!heats || heats.length === 0) return null;
|
||||
|
||||
/**
|
||||
* Format heat object into compact notation
|
||||
* Example: { competitionType: 'Jack & Jill', division: 'Novice', heatNumber: 1, role: 'Leader' }
|
||||
* Returns: "J&J NOV 1 L"
|
||||
*/
|
||||
const formatHeat = (heat) => {
|
||||
const parts = [
|
||||
heat.competitionType?.abbreviation || '',
|
||||
heat.division?.abbreviation || '',
|
||||
heat.heatNumber,
|
||||
];
|
||||
if (heat.role) {
|
||||
parts.push(heat.role.charAt(0)); // L or F
|
||||
}
|
||||
return parts.join(' ');
|
||||
};
|
||||
|
||||
const visibleHeats = heats.slice(0, maxVisible);
|
||||
const remainingCount = heats.length - maxVisible;
|
||||
|
||||
return (
|
||||
<div className={`flex flex-wrap gap-1 ${className}`}>
|
||||
{visibleHeats.map((heat, idx) => (
|
||||
<span
|
||||
key={idx}
|
||||
className="text-xs px-1.5 py-0.5 bg-amber-100 text-amber-800 rounded font-mono"
|
||||
title={`${heat.competitionType?.name || ''} ${heat.division?.name || ''} Heat ${heat.heatNumber} (${heat.role || ''})`}
|
||||
>
|
||||
{formatHeat(heat)}
|
||||
</span>
|
||||
))}
|
||||
{remainingCount > 0 && (
|
||||
<span
|
||||
className="text-xs px-1.5 py-0.5 bg-gray-200 text-gray-700 rounded font-mono"
|
||||
title={`${remainingCount} more heat${remainingCount > 1 ? 's' : ''}`}
|
||||
>
|
||||
+{remainingCount}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default HeatBadges;
|
||||
Reference in New Issue
Block a user