diff --git a/app/carnet/page.tsx b/app/carnet/page.tsx index 51d39527..b835aa49 100644 --- a/app/carnet/page.tsx +++ b/app/carnet/page.tsx @@ -8,6 +8,7 @@ import { NotesView } from "@/components/carnet/notes-view"; import { Editor } from "@/components/carnet/editor"; import { PanelResizer } from "@/components/carnet/panel-resizer"; import { useMediaQuery } from "@/hooks/use-media-query"; +import { ContactsView } from '@/components/carnet/contacts-view'; // Layout modes export enum PaneLayout { @@ -27,6 +28,17 @@ interface Note { etag: string; } +interface Contact { + id: string; + fullName: string; + email: string; + phone?: string; + organization?: string; + address?: string; + notes?: string; + group?: string; +} + export default function CarnetPage() { const { data: session, status } = useSession(); const [isLoading, setIsLoading] = useState(true); @@ -39,6 +51,9 @@ export default function CarnetPage() { const [selectedFolder, setSelectedFolder] = useState('Notes'); const [notes, setNotes] = useState([]); const [isLoadingNotes, setIsLoadingNotes] = useState(true); + const [contacts, setContacts] = useState([]); + const [selectedContact, setSelectedContact] = useState(null); + const [isLoadingContacts, setIsLoadingContacts] = useState(true); // Panel widths state const [navWidth, setNavWidth] = useState(220); @@ -136,6 +151,72 @@ export default function CarnetPage() { fetchNotes(); }, [selectedFolder]); + const fetchContacts = async (folder: string) => { + try { + setIsLoadingContacts(true); + const response = await fetch(`/api/nextcloud/files?folder=${folder}`); + if (response.ok) { + const files = await response.json(); + const vcfFiles = files.filter((file: any) => file.basename.endsWith('.vcf')); + + // Parse VCF files and extract contact information + const parsedContacts = await Promise.all( + vcfFiles.map(async (file: any) => { + const contentResponse = await fetch(`/api/nextcloud/files/content?id=${file.id}`); + if (contentResponse.ok) { + const content = await contentResponse.text(); + return parseVCard(content); + } + return null; + }) + ); + + setContacts(parsedContacts.filter(Boolean)); + } + } catch (error) { + console.error('Error fetching contacts:', error); + } finally { + setIsLoadingContacts(false); + } + }; + + const parseVCard = (content: string): Contact => { + const lines = content.split('\n'); + const contact: Partial = {}; + + lines.forEach(line => { + if (line.startsWith('FN:')) { + contact.fullName = line.substring(3).trim(); + } else if (line.startsWith('EMAIL;')) { + contact.email = line.split(':')[1].trim(); + } else if (line.startsWith('TEL;')) { + contact.phone = line.split(':')[1].trim(); + } else if (line.startsWith('ORG:')) { + contact.organization = line.substring(4).trim(); + } else if (line.startsWith('ADR;')) { + contact.address = line.split(':')[1].trim(); + } else if (line.startsWith('NOTE:')) { + contact.notes = line.substring(5).trim(); + } + }); + + return { + id: Math.random().toString(36).substr(2, 9), + fullName: contact.fullName || 'Unknown', + email: contact.email || '', + phone: contact.phone, + organization: contact.organization, + address: contact.address, + notes: contact.notes + } as Contact; + }; + + useEffect(() => { + if (selectedFolder === 'Contacts') { + fetchContacts(selectedFolder); + } + }, [selectedFolder]); + // Handle panel resizing const handleNavResize = (e: MouseEvent) => { if (!isDraggingNav) return; @@ -286,14 +367,23 @@ export default function CarnetPage() { {showNotes && ( <>
- + {selectedFolder === 'Contacts' ? ( + + ) : ( + + )}
{/* Notes Resizer */} diff --git a/components/carnet/contacts-view.tsx b/components/carnet/contacts-view.tsx new file mode 100644 index 00000000..974f40e8 --- /dev/null +++ b/components/carnet/contacts-view.tsx @@ -0,0 +1,139 @@ +"use client"; + +import React, { useState } from 'react'; +import { Search, User, Mail, Phone, Building, MapPin, ChevronRight } from 'lucide-react'; + +interface Contact { + id: string; + fullName: string; + email: string; + phone?: string; + organization?: string; + address?: string; + notes?: string; + group?: string; +} + +interface ContactsViewProps { + contacts: Contact[]; + onContactSelect: (contact: Contact) => void; + selectedContact: Contact | null; + loading?: boolean; +} + +export const ContactsView: React.FC = ({ + contacts, + onContactSelect, + selectedContact, + loading = false +}) => { + const [searchQuery, setSearchQuery] = useState(''); + + const filteredContacts = contacts.filter(contact => + contact.fullName.toLowerCase().includes(searchQuery.toLowerCase()) || + contact.email.toLowerCase().includes(searchQuery.toLowerCase()) || + contact.organization?.toLowerCase().includes(searchQuery.toLowerCase()) + ); + + return ( +
+ {/* Search Bar */} +
+
+ setSearchQuery(e.target.value)} + placeholder="Rechercher un contact..." + className="w-full pl-9 pr-4 py-2 bg-white border border-carnet-border rounded-md text-sm text-carnet-text-primary placeholder-carnet-text-muted focus:outline-none focus:ring-1 focus:ring-primary" + /> + +
+
+ + {/* Contacts List */} +
+ {loading ? ( +
Chargement...
+ ) : filteredContacts.length === 0 ? ( +
Aucun contact trouvé
+ ) : ( +
    + {filteredContacts.map((contact) => ( +
  • onContactSelect(contact)} + className={`p-4 cursor-pointer hover:bg-carnet-hover ${ + selectedContact?.id === contact.id ? 'bg-carnet-hover' : '' + }`} + > +
    +
    +
    + +
    +
    +
    {contact.fullName}
    +
    {contact.email}
    +
    +
    + +
    +
  • + ))} +
+ )} +
+ + {/* Contact Details */} + {selectedContact && ( +
+
+
+
+ +
+
+

+ {selectedContact.fullName} +

+ {selectedContact.organization && ( +

+ {selectedContact.organization} +

+ )} +
+
+ +
+ {selectedContact.email && ( +
+ + {selectedContact.email} +
+ )} + {selectedContact.phone && ( +
+ + {selectedContact.phone} +
+ )} + {selectedContact.address && ( +
+ + {selectedContact.address} +
+ )} + {selectedContact.notes && ( +
+

Notes

+

{selectedContact.notes}

+
+ )} +
+
+
+ )} +
+ ); +}; \ No newline at end of file diff --git a/components/carnet/navigation.tsx b/components/carnet/navigation.tsx index 3e91a3b5..6c3e3f34 100644 --- a/components/carnet/navigation.tsx +++ b/components/carnet/navigation.tsx @@ -1,30 +1,37 @@ "use client"; import React, { useState } from 'react'; -import { Search, BookOpen, Tag, Trash2, Star, Archive, X, Folder, FileText, Calendar, Heart, Users, LucideIcon } from 'lucide-react'; +import { Search, FileText, Calendar, Heart, Users, ChevronRight, Folder } from 'lucide-react'; interface NavigationProps { nextcloudFolders: string[]; onFolderSelect: (folder: string) => void; } -type FolderType = 'Notes' | 'Diary' | 'Health' | 'Contacts'; - -interface FolderConfig { - icon: LucideIcon; - order: number; +interface ContactGroup { + name: string; + contacts: string[]; } -// Define folder order and icons -const FOLDER_CONFIG: Record = { - 'Notes': { icon: FileText, order: 1 }, - 'Diary': { icon: Calendar, order: 2 }, - 'Health': { icon: Heart, order: 3 }, - 'Contacts': { icon: Users, order: 4 } -}; - export default function Navigation({ nextcloudFolders, onFolderSelect }: NavigationProps) { const [searchQuery, setSearchQuery] = useState(''); + const [expandedGroups, setExpandedGroups] = useState>(new Set()); + const [contactGroups, setContactGroups] = useState([ + { name: 'Family', contacts: [] }, + { name: 'Friends', contacts: [] }, + { name: 'Work', contacts: [] }, + { name: 'Other', contacts: [] } + ]); + + const toggleGroup = (groupName: string) => { + const newExpanded = new Set(expandedGroups); + if (newExpanded.has(groupName)) { + newExpanded.delete(groupName); + } else { + newExpanded.add(groupName); + } + setExpandedGroups(newExpanded); + }; const getFolderIcon = (folder: string) => { switch (folder) { @@ -41,54 +48,82 @@ export default function Navigation({ nextcloudFolders, onFolderSelect }: Navigat } }; - // Sort folders according to the specified order - const sortedFolders = [...nextcloudFolders].sort((a, b) => { - const orderA = (FOLDER_CONFIG[a as FolderType]?.order) || 999; - const orderB = (FOLDER_CONFIG[b as FolderType]?.order) || 999; - return orderA - orderB; - }); - return ( -
- {/* Search */} -
+
+ {/* Search Bar */} +
setSearchQuery(e.target.value)} - placeholder="Recherché..." + placeholder="Rechercher..." className="w-full pl-9 pr-4 py-2 bg-white border border-carnet-border rounded-md text-sm text-carnet-text-primary placeholder-carnet-text-muted focus:outline-none focus:ring-1 focus:ring-primary" /> - {searchQuery && ( - - )}
- {/* Folders */} -
-
- {sortedFolders.map((folder) => { - const Icon = getFolderIcon(folder); - return ( - - ); - })} -
+ {/* Folders List */} +
+
    + {nextcloudFolders.map((folder) => ( +
  • + {folder === 'Contacts' ? ( + <> + + {/* Contact Groups */} +
      + {contactGroups.map((group) => ( +
    • + + {expandedGroups.has(group.name) && ( +
        + {group.contacts.map((contact) => ( +
      • + +
      • + ))} +
      + )} +
    • + ))} +
    + + ) : ( + + )} +
  • + ))} +
);