Neah/components/carnet/navigation.tsx
2025-04-21 11:54:23 +02:00

259 lines
9.0 KiB
TypeScript

"use client";
import React, { useState, useEffect } from 'react';
import { Search, BookOpen, Tag, Trash2, Star, Archive, X, Folder, FileText, Calendar, Heart, Users, LucideIcon, ChevronRight } from 'lucide-react';
import { Card } from '@/components/ui/card';
interface NavigationProps {
nextcloudFolders: string[];
onFolderSelect: (folder: string) => void;
}
type FolderType = 'Notes' | 'Diary' | 'Health' | 'Contacts';
interface FolderConfig {
icon: LucideIcon;
order: number;
displayName: string;
}
// Define folder order, icons and display names
const FOLDER_CONFIG: Record<FolderType, FolderConfig> = {
'Notes': { icon: FileText, order: 1, displayName: 'Bloc-notes' },
'Diary': { icon: Calendar, order: 2, displayName: 'Journal' },
'Health': { icon: Heart, order: 3, displayName: 'Carnet de santé' },
'Contacts': { icon: Users, order: 4, displayName: 'Carnet d\'adresses' }
};
interface ContactFile {
id: string;
filename: string;
basename: string;
lastmod: string;
}
interface Contact {
id: string;
name: string;
email: string;
phone: string;
address: string;
notes: string;
tags: string[];
created_at: string;
updated_at: string;
user_id: string;
vcard: string;
}
export default function Navigation({ nextcloudFolders, onFolderSelect }: NavigationProps) {
const [searchQuery, setSearchQuery] = useState('');
const [expandedContacts, setExpandedContacts] = useState(false);
const [contactFiles, setContactFiles] = useState<ContactFile[]>([]);
const [isLoadingContacts, setIsLoadingContacts] = useState(false);
const getFolderIcon = (folder: string) => {
switch (folder) {
case 'Notes':
return FileText;
case 'Diary':
return Calendar;
case 'Health':
return Heart;
case 'Contacts':
return Users;
default:
return FileText;
}
};
// 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;
});
const fetchContactFiles = async () => {
try {
setIsLoadingContacts(true);
const response = await fetch('/api/nextcloud/files?folder=Contacts');
if (response.ok) {
const files = await response.json();
// Only log the number of files received, not their contents
console.log(`Received ${files.length} files from Nextcloud`);
// Filter for VCF files and map to ContactFile interface
const vcfFiles = files
.filter((file: any) => file.basename.endsWith('.vcf'))
.map((file: any) => ({
id: file.etag,
filename: file.filename,
basename: file.basename,
lastmod: file.lastmod
}));
// Only log the number of VCF files processed
console.log(`Processed ${vcfFiles.length} VCF files`);
setContactFiles(vcfFiles);
}
} catch (error) {
console.error('Error fetching contact files:', error);
setContactFiles([]);
} finally {
setIsLoadingContacts(false);
}
};
useEffect(() => {
if (expandedContacts) {
fetchContactFiles();
}
}, [expandedContacts]);
const handleVcfFile = async (file: File) => {
try {
const text = await file.text();
const vcards = text.split('END:VCARD').filter(vcard => vcard.trim());
const processedContacts = vcards.map(vcard => {
const lines = vcard.split('\n');
const contact: Contact = {
id: crypto.randomUUID(),
name: '',
email: '',
phone: '',
address: '',
notes: '',
tags: [],
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
user_id: '',
vcard: vcard + 'END:VCARD'
};
lines.forEach(line => {
if (line.startsWith('FN:')) {
contact.name = line.substring(3);
} else if (line.startsWith('EMAIL;')) {
contact.email = line.split(':')[1];
} else if (line.startsWith('TEL;')) {
contact.phone = line.split(':')[1];
} else if (line.startsWith('ADR;')) {
contact.address = line.split(':')[1];
}
});
return contact;
});
// Only log the number of contacts processed, not their details
console.log(`Processed ${processedContacts.length} contacts from VCF file`);
// Convert Contact objects to ContactFile objects
const contactFiles: ContactFile[] = processedContacts.map(contact => ({
id: contact.id,
filename: `${contact.name}.vcf`,
basename: contact.name,
lastmod: new Date().toISOString()
}));
setContactFiles(prev => [...prev, ...contactFiles]);
} catch (error) {
console.error('Error processing VCF file:', error);
}
};
return (
<div className="flex flex-col h-full bg-carnet-sidebar">
<Card className="p-4">
{/* Search */}
<div className="space-y-4">
<div className="flex items-center gap-2">
<FileText className="h-4 w-4 text-carnet-text-primary" />
<span className="text-sm font-semibold text-carnet-text-primary">PAGES</span>
</div>
<div className="relative">
<input
type="text"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder="Recherche..."
className="w-full pl-8 pr-4 py-1.5 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"
/>
<Search className="absolute left-2 top-1.5 h-3.5 w-3.5 text-carnet-text-muted" />
{searchQuery && (
<button
onClick={() => setSearchQuery('')}
className="absolute right-2 top-1.5 text-carnet-text-muted hover:text-carnet-text-primary"
>
<X className="h-3.5 w-3.5" />
</button>
)}
</div>
</div>
</Card>
{/* Folders */}
<div className="flex-1 overflow-y-auto p-4">
<h2 className="text-xs font-semibold text-carnet-text-primary mb-2">VUES</h2>
<div className="space-y-1">
{sortedFolders.map((folder) => {
const config = FOLDER_CONFIG[folder as FolderType];
const Icon = config?.icon || FileText;
const displayName = config?.displayName || folder;
return (
<div key={folder}>
<button
onClick={() => {
if (folder === 'Contacts') {
setExpandedContacts(!expandedContacts);
} else {
onFolderSelect(folder);
}
}}
className="w-full flex items-center px-2 py-1.5 text-sm rounded-md text-carnet-text-primary hover:bg-carnet-hover"
>
<Icon className="h-3.5 w-3.5 flex-shrink-0" />
<span className="ml-2 truncate">{displayName}</span>
{folder === 'Contacts' && (
<ChevronRight
className={`h-3.5 w-3.5 flex-shrink-0 ml-auto transition-transform ${
expandedContacts ? 'transform rotate-90' : ''
}`}
/>
)}
</button>
{folder === 'Contacts' && expandedContacts && (
<div className="ml-4 mt-1 space-y-1">
{isLoadingContacts ? (
<div className="px-3 py-2 text-sm text-carnet-text-muted">Chargement...</div>
) : contactFiles.length === 0 ? (
<div className="px-3 py-2 text-sm text-carnet-text-muted">Aucun contact</div>
) : (
contactFiles.map((file) => {
return (
<button
key={file.id}
onClick={() => {
// When clicking a VCF file, we want to select the Contacts folder
// and pass the file information to the parent component
onFolderSelect('Contacts');
// You might want to add a callback here to handle the selected contact
}}
className="w-full flex items-center space-x-2 px-3 py-2 text-sm rounded-md text-carnet-text-muted hover:bg-carnet-hover"
>
<span>{file.basename.replace('.vcf', '')}</span>
</button>
);
})
)}
</div>
)}
</div>
);
})}
</div>
</div>
</div>
);
}