Implement comprehensive contact list redesign with drag-and-drop categorization

Major Features:
- Add drag-and-drop relationship categorization system with 6 categories in left sidebar
- Replace NAO Status filter with Relationship filter (including "Undefined" option)
- Add comprehensive filtering and sorting controls with clear filters option
- Move drag handles outside contact cards for cleaner layout

Contact Card Redesign:
- Reduce contact height to avatar size (44px desktop, 40px mobile)
- Implement 3-column layout: Name & Title | Email | Relationship & Counts
- Move email to dedicated second column with baseline alignment
- Add phone number for "Aza Mafi" with proper baseline alignment
- Replace horizontal chip layout with vertical two-row structure in right column
- Remove tags and NAO status from contact display for cleaner design

Layout Improvements:
- Reduce vertical spacing between contacts (spacing={1})
- Minimize internal padding (p: 1) and optimize avatar positioning
- Increase first column width (320px) to prevent title truncation
- Add proper padding between columns for better readability
- Position drag handles 20px from contact cards with alignment to filter dropdown

Visual Enhancements:
- Add relationship category boxes to sidebar with drag-over effects
- Include instructional text for drag-and-drop functionality
- Color-coded relationship categories with matching chips
- Compact design showing more contacts per page while maintaining usability

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
main
Margeigh Novotny 2 months ago
parent 20b8a6f7f8
commit 3cbed466a0
  1. 120
      src/components/layout/DashboardLayout.tsx
  2. 947
      src/pages/ContactListPage.tsx
  3. 1
      src/types/contact.ts

@ -33,6 +33,11 @@ import {
Hub,
Dashboard,
Notifications,
FamilyRestroom,
Person,
Business,
Work,
People,
} from '@mui/icons-material';
import BottomNavigation from '../navigation/BottomNavigation';
import { notificationService } from '../../services/notificationService';
@ -52,6 +57,14 @@ interface DashboardLayoutProps {
children: ReactNode;
}
interface RelationshipCategory {
id: string;
name: string;
icon: ReactNode;
color: string;
count: number;
}
const DashboardLayout = ({ children }: DashboardLayoutProps) => {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
@ -64,6 +77,7 @@ const DashboardLayout = ({ children }: DashboardLayoutProps) => {
pending: 0,
byType: { vouch: 0, praise: 0, connection: 0, group_invite: 0, message: 0, system: 0 }
});
const [dragOverCategory, setDragOverCategory] = useState<string | null>(null);
const location = useLocation();
const navigate = useNavigate();
@ -74,6 +88,15 @@ const DashboardLayout = ({ children }: DashboardLayoutProps) => {
{ text: 'Chat', icon: <Chat />, path: '/messages' },
];
const relationshipCategories: RelationshipCategory[] = [
{ id: 'close_family', name: 'Close Family', icon: <FamilyRestroom />, color: '#d32f2f', count: 0 },
{ id: 'family', name: 'Family', icon: <People />, color: '#f57c00', count: 0 },
{ id: 'friend', name: 'Friend', icon: <Person />, color: '#388e3c', count: 0 },
{ id: 'colleague', name: 'Colleague', icon: <Work />, color: '#1976d2', count: 0 },
{ id: 'business', name: 'Business', icon: <Business />, color: '#7b1fa2', count: 0 },
{ id: 'acquaintance', name: 'Acquaintance', icon: <Groups />, color: '#616161', count: 0 },
];
// Load notification summary for badge
useEffect(() => {
const loadNotificationSummary = async () => {
@ -133,6 +156,34 @@ const DashboardLayout = ({ children }: DashboardLayoutProps) => {
return false;
};
const handleDragOver = (e: React.DragEvent, categoryId: string) => {
e.preventDefault();
setDragOverCategory(categoryId);
};
const handleDragLeave = (e: React.DragEvent) => {
e.preventDefault();
setDragOverCategory(null);
};
const handleDrop = (e: React.DragEvent, categoryId: string) => {
e.preventDefault();
setDragOverCategory(null);
const contactData = e.dataTransfer.getData('application/json');
if (contactData) {
try {
const contact = JSON.parse(contactData);
// Emit custom event for contact categorization
window.dispatchEvent(new CustomEvent('contactCategorized', {
detail: { contactId: contact.id, category: categoryId, contact }
}));
} catch (error) {
console.error('Error parsing contact data:', error);
}
}
};
const renderNavItem = (item: NavItem, level: number = 0) => {
const hasChildren = item.children && item.children.length > 0;
const isExpanded = expandedItems.has(item.text);
@ -211,10 +262,77 @@ const DashboardLayout = ({ children }: DashboardLayoutProps) => {
</Typography>
</Box>
<List sx={{ flex: 1, py: 2, overflow: 'hidden' }}>
<List sx={{ py: 2, flexShrink: 0 }}>
{navItems.map((item) => renderNavItem(item))}
</List>
{/* Relationship Categories */}
<Box sx={{ px: 2, pb: 2, flexGrow: 1, overflow: 'auto' }}>
<Divider sx={{ mb: 2 }} />
<Typography variant="subtitle2" sx={{ mb: 1, fontWeight: 600, color: 'text.secondary', px: 1 }}>
Relationship Categories
</Typography>
<Typography variant="caption" sx={{ mb: 2, color: 'text.secondary', px: 1, fontSize: '0.7rem', lineHeight: 1.2, display: 'block' }}>
Drag and drop contacts into a category to automatically set sharing permissions.
</Typography>
<Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 1 }}>
{relationshipCategories.map((category) => (
<Box
key={category.id}
onDragOver={(e) => handleDragOver(e, category.id)}
onDragLeave={handleDragLeave}
onDrop={(e) => handleDrop(e, category.id)}
sx={{
minHeight: 80,
border: 2,
borderColor: dragOverCategory === category.id ? category.color : 'divider',
borderStyle: dragOverCategory === category.id ? 'solid' : 'dashed',
borderRadius: 2,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
p: 1,
cursor: 'pointer',
backgroundColor: dragOverCategory === category.id ? `${category.color}10` : 'transparent',
transition: 'all 0.2s ease-in-out',
'&:hover': {
borderColor: category.color,
backgroundColor: `${category.color}08`,
},
}}
>
<Box sx={{ color: category.color, mb: 0.5 }}>
{category.icon}
</Box>
<Typography
variant="caption"
sx={{
textAlign: 'center',
fontSize: '0.7rem',
fontWeight: 500,
lineHeight: 1.2
}}
>
{category.name}
</Typography>
{category.count > 0 && (
<Typography
variant="caption"
sx={{
color: 'text.secondary',
fontSize: '0.6rem',
mt: 0.5
}}
>
{category.count}
</Typography>
)}
</Box>
))}
</Box>
</Box>
</Box>
);

File diff suppressed because it is too large Load Diff

@ -12,6 +12,7 @@ export interface Contact {
tags?: string[];
groupIds?: string[];
naoStatus?: 'member' | 'invited' | 'not_invited';
relationshipCategory?: 'close_family' | 'family' | 'friend' | 'colleague' | 'business' | 'acquaintance';
invitedAt?: Date;
joinedAt?: Date;
createdAt: Date;

Loading…
Cancel
Save