diff --git a/src/pages/ContactListPage.tsx b/src/pages/ContactListPage.tsx index 694cc55..94e657c 100644 --- a/src/pages/ContactListPage.tsx +++ b/src/pages/ContactListPage.tsx @@ -118,13 +118,6 @@ const ContactListPage = () => { } }; - const formatDate = (date: Date) => { - return new Intl.DateTimeFormat('en-US', { - year: 'numeric', - month: 'short', - day: 'numeric' - }).format(date); - }; const getNaoStatusIndicator = (contact: Contact) => { switch (contact.naoStatus) { @@ -390,137 +383,69 @@ const ContactListPage = () => { borderColor: 'divider', '&:hover': !isSelectionMode ? { borderColor: 'primary.main', - boxShadow: theme.shadows[4], - transform: 'translateY(-2px)', + boxShadow: theme.shadows[2], + transform: 'translateY(-1px)', } : {}, }} > - - - {/* Top row on mobile: Avatar + Name + Action Button */} - - - {!contact.profileImage && contact.name.charAt(0)} - - - - - - {contact.name} - - {getSourceIcon(contact.source)} - - + + {/* Desktop Layout */} + + {/* Avatar */} + + {!contact.profileImage && contact.name.charAt(0)} + + + {/* Left Column - Basic Info */} + + {/* Name with source icon inline */} + - {contact.position} {contact.company && `at ${contact.company}`} + {contact.name} + {getSourceIcon(contact.source)} - {/* Action buttons - always visible on the right */} - - {/* Select button for selection mode */} - {isSelectionMode && ( - - )} - - {/* Invite to NAO button for non-members (not in selection mode) */} - {!isSelectionMode && contact.naoStatus === 'not_invited' && ( - - )} - - - - {/* Bottom section: Email + Status chips + Tags */} - + {/* Job Title & Company */} + + {contact.position}{contact.company && ` at ${contact.company}`} + + + {/* Email */} { > {contact.email} - - {/* Compact status and metrics chips */} - - {contact.groupIds && contact.groupIds.length > 0 && ( - } - label={contact.groupIds.length} - size="small" - variant="outlined" - sx={{ - fontSize: '0.65rem', - height: 18, - borderRadius: 1, - backgroundColor: alpha(theme.palette.success.main, 0.04), - borderColor: alpha(theme.palette.success.main, 0.12), - color: 'success.main', - '& .MuiChip-icon': { fontSize: 12 }, - }} - /> - )} - - {/* NAO Status Indicator */} - {(() => { - const naoStatus = getNaoStatusIndicator(contact); - return naoStatus ? ( - - ) : null; - })()} - - {/* Simplified Vouch and Praise counts */} - {(() => { - const counts = getVouchPraiseCounts(contact); - const totalVouches = counts.vouchesSent + counts.vouchesReceived; - const totalPraises = counts.praisesSent + counts.praisesReceived; - - return ( - <> - {totalVouches > 0 && ( - } - label={totalVouches} - size="small" - variant="outlined" - sx={{ - fontSize: '0.65rem', - height: 18, - borderRadius: 1, - backgroundColor: alpha(theme.palette.primary.main, 0.04), - borderColor: alpha(theme.palette.primary.main, 0.12), - color: 'primary.main', - '& .MuiChip-icon': { fontSize: 12 }, - }} - /> - )} - {totalPraises > 0 && ( - } - label={totalPraises} - size="small" - variant="outlined" - sx={{ - fontSize: '0.65rem', - height: 18, - borderRadius: 1, - backgroundColor: alpha('#f8bbd9', 0.3), - borderColor: alpha('#d81b60', 0.3), - color: '#d81b60', - '& .MuiChip-icon': { fontSize: 12 }, - }} - /> - )} - - ); - })()} - - - {/* Tags - only show first 2 on mobile */} - {contact.tags && contact.tags.length > 0 && ( - - {contact.tags.slice(0, 2).map((tag) => ( - - ))} - {contact.tags.length > 2 && ( - - )} - - )} - {/* Desktop layout (hidden on mobile) */} + {/* Middle Column - Vouch & Praise Counts + Tags */} - - {contact.groupIds && contact.groupIds.length > 0 && ( - } - label={contact.groupIds.length} - size="small" - variant="outlined" - sx={{ - fontSize: '0.75rem', - height: 20, - borderRadius: 1, - backgroundColor: alpha(theme.palette.success.main, 0.04), - borderColor: alpha(theme.palette.success.main, 0.12), - color: 'success.main', - '& .MuiChip-icon': { fontSize: 14 }, - }} - /> - )} - - {/* NAO Status Indicator */} - {(() => { - const naoStatus = getNaoStatusIndicator(contact); - return naoStatus ? ( - - ) : null; - })()} - - {/* Vouch and Praise Indicators with directional arrows - desktop only */} + {/* Vouches and Praises Row */} + {(() => { const counts = getVouchPraiseCounts(contact); const totalVouches = counts.vouchesSent + counts.vouchesReceived; @@ -740,11 +504,12 @@ const ContactListPage = () => { : `Vouches: ${counts.vouchesSent} sent (hidden until they join)`} sx={{ fontSize: '0.75rem', - height: 20, + height: 24, borderRadius: 1, backgroundColor: alpha(theme.palette.primary.main, 0.04), borderColor: alpha(theme.palette.primary.main, 0.12), color: 'primary.main', + fontWeight: 500, '& .MuiChip-icon': { fontSize: 14 }, '& .MuiChip-label': { fontSize: '0.75rem', @@ -752,6 +517,7 @@ const ContactListPage = () => { }, }} /> + } label={ @@ -781,11 +547,12 @@ const ContactListPage = () => { : `Praises: ${counts.praisesSent} sent (hidden until they join)`} sx={{ fontSize: '0.75rem', - height: 20, + height: 24, borderRadius: 1, backgroundColor: alpha('#f8bbd9', 0.3), borderColor: alpha('#d81b60', 0.3), color: '#d81b60', + fontWeight: 500, '& .MuiChip-icon': { fontSize: 14 }, '& .MuiChip-label': { fontSize: '0.75rem', @@ -797,32 +564,382 @@ const ContactListPage = () => { ); })()} + + {/* Tags Row */} + {contact.tags && contact.tags.length > 0 && ( + + {contact.tags.slice(0, 3).map((tag) => ( + + ))} + {contact.tags.length > 3 && ( + + )} + + )} + + + {/* Right Column - Network Status Only (Push to Far Right) */} + + {(() => { + const naoStatus = getNaoStatusIndicator(contact); + if (isSelectionMode) { + return ( + + ); + } else if (contact.naoStatus === 'not_invited') { + return ( + + ); + } else { + return naoStatus ? ( + + ) : null; + } + })()} + + {contact.groupIds && contact.groupIds.length > 0 && ( + } + label={`${contact.groupIds.length} group${contact.groupIds.length > 1 ? 's' : ''}`} + size="small" + variant="outlined" + sx={{ + backgroundColor: alpha(theme.palette.primary.main, 0.04), + borderColor: alpha(theme.palette.primary.main, 0.2), + color: 'primary.main', + fontWeight: 500, + fontSize: '0.75rem', + height: 24 + }} + /> + )} + + + + {/* Mobile Layout */} + + {/* Avatar */} + + {!contact.profileImage && contact.name.charAt(0)} + + + {/* Main Content */} + + {/* Name with source icon */} + + + {contact.name} + + {getSourceIcon(contact.source)} + - - {contact.email} + {/* Job Title & Company */} + + {contact.position}{contact.company && ` at ${contact.company}`} - - {contact.tags?.map((tag) => ( - + {contact.email} + + + {/* Info Chips Row (excluding NAO status) */} + + {/* Groups Count */} + {contact.groupIds && contact.groupIds.length > 0 && ( + } + label={`${contact.groupIds.length} group${contact.groupIds.length > 1 ? 's' : ''}`} + size="small" variant="outlined" - sx={{ - borderRadius: 1, + sx={{ backgroundColor: alpha(theme.palette.primary.main, 0.04), - borderColor: alpha(theme.palette.primary.main, 0.12), + borderColor: alpha(theme.palette.primary.main, 0.2), color: 'primary.main', fontWeight: 500, + fontSize: '0.65rem', + height: 20 }} /> - ))} + )} + + {/* Vouch & Praise Counts */} + {(() => { + const counts = getVouchPraiseCounts(contact); + const totalVouches = counts.vouchesSent + counts.vouchesReceived; + const totalPraises = counts.praisesSent + counts.praisesReceived; + + return ( + <> + {totalVouches > 0 && ( + } + label={totalVouches} + size="small" + variant="outlined" + sx={{ + fontSize: '0.6rem', + height: 18, + borderRadius: 1, + backgroundColor: alpha(theme.palette.primary.main, 0.04), + borderColor: alpha(theme.palette.primary.main, 0.12), + color: 'primary.main', + '& .MuiChip-icon': { fontSize: 10 }, + }} + /> + )} + {totalPraises > 0 && ( + } + label={totalPraises} + size="small" + variant="outlined" + sx={{ + fontSize: '0.6rem', + height: 18, + borderRadius: 1, + backgroundColor: alpha('#f8bbd9', 0.3), + borderColor: alpha('#d81b60', 0.3), + color: '#d81b60', + '& .MuiChip-icon': { fontSize: 10 }, + }} + /> + )} + + ); + })()} - - Added {formatDate(contact.createdAt)} - + {/* Tags Row */} + {contact.tags && contact.tags.length > 0 && ( + + {contact.tags.slice(0, 2).map((tag) => ( + + ))} + {contact.tags.length > 2 && ( + + )} + + )} + + + {/* Right Column - Status & Action */} + + {(() => { + const naoStatus = getNaoStatusIndicator(contact); + if (isSelectionMode) { + return ( + + ); + } else if (contact.naoStatus === 'not_invited') { + return ( + + ); + } else { + return naoStatus ? ( + + ) : null; + } + })()} diff --git a/src/services/dataService.ts b/src/services/dataService.ts index 2fca703..6a1b707 100644 --- a/src/services/dataService.ts +++ b/src/services/dataService.ts @@ -8,11 +8,23 @@ export const dataService = { try { const response = await fetch('/contacts.json'); const contactsData = await response.json(); - const contacts = contactsData.map((contact: any) => ({ - ...contact, - createdAt: new Date(contact.createdAt), - updatedAt: new Date(contact.updatedAt) - })); + const contacts = contactsData.map((contact: any) => { + const processedContact = { + ...contact, + createdAt: new Date(contact.createdAt), + updatedAt: new Date(contact.updatedAt) + }; + + // Convert optional date fields if they exist + if (contact.joinedAt) { + processedContact.joinedAt = new Date(contact.joinedAt); + } + if (contact.invitedAt) { + processedContact.invitedAt = new Date(contact.invitedAt); + } + + return processedContact; + }); resolve(contacts); } catch (error) { console.error('Failed to load contacts:', error); @@ -30,11 +42,21 @@ export const dataService = { const contactsData = await response.json(); const contact = contactsData.find((c: any) => c.id === id); if (contact) { - resolve({ + const processedContact = { ...contact, createdAt: new Date(contact.createdAt), updatedAt: new Date(contact.updatedAt) - }); + }; + + // Convert optional date fields if they exist + if (contact.joinedAt) { + processedContact.joinedAt = new Date(contact.joinedAt); + } + if (contact.invitedAt) { + processedContact.invitedAt = new Date(contact.invitedAt); + } + + resolve(processedContact); } else { resolve(undefined); }