carnet panel contact

This commit is contained in:
alma 2025-04-20 20:16:54 +02:00
parent a8e040f097
commit 5059afe510
2 changed files with 209 additions and 68 deletions

View File

@ -431,6 +431,78 @@ export default function CarnetPage() {
}
};
const handleContactSave = async (contact: Contact) => {
try {
const vcfContent = generateVCard(contact);
const response = await fetch('/api/nextcloud/files', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
path: `/files/cube-${session?.user?.id}/Private/Contacts/${selectedFolder}`,
content: vcfContent
}),
});
if (!response.ok) {
throw new Error('Failed to save contact');
}
// Refresh contacts list
fetchContacts(selectedFolder);
} catch (error) {
console.error('Error saving contact:', error);
}
};
const handleContactDelete = async (contact: Contact) => {
if (!confirm('Êtes-vous sûr de vouloir supprimer ce contact ?')) {
return;
}
try {
const response = await fetch('/api/nextcloud/files', {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
path: `/files/cube-${session?.user?.id}/Private/Contacts/${selectedFolder}`
}),
});
if (!response.ok) {
throw new Error('Failed to delete contact');
}
// Clear selected contact and refresh list
setSelectedContact(null);
fetchContacts('Contacts');
} catch (error) {
console.error('Error deleting contact:', error);
}
};
const generateVCard = (contact: Contact): string => {
const lines = [
'BEGIN:VCARD',
'VERSION:3.0',
`UID:${contact.id}`,
'PRODID:-//Infomaniak//Workspace//pim',
contact.fullName ? `FN:${contact.fullName}` : '',
contact.fullName ? `N:${contact.fullName.split(' ').reverse().join(';')};` : '',
contact.organization ? `ORG:${contact.organization}` : '',
contact.email ? `EMAIL;TYPE=WORK:${contact.email}` : '',
contact.phone ? `TEL;TYPE=WORK:${contact.phone}` : '',
contact.address ? `ADR;TYPE=WORK:;;${contact.address};;;;` : '',
contact.notes ? `NOTE:${contact.notes}` : '',
'END:VCARD'
].filter(Boolean);
return lines.join('\n');
};
if (isLoading) {
return (
<div className="flex h-screen items-center justify-center">
@ -497,7 +569,11 @@ export default function CarnetPage() {
{/* Editor Panel */}
<div className="flex-1 overflow-hidden">
{selectedFolder === 'Contacts' || selectedFolder.endsWith('.vcf') ? (
<ContactDetails contact={selectedContact} />
<ContactDetails
contact={selectedContact}
onSave={handleContactSave}
onDelete={handleContactDelete}
/>
) : (
<Editor
note={selectedNote}

View File

@ -1,7 +1,7 @@
"use client";
import React from 'react';
import { User, Mail, Phone, Building, MapPin } from 'lucide-react';
import React, { useState } from 'react';
import { User, Mail, Phone, Building, MapPin, Edit2, Save, X } from 'lucide-react';
interface Contact {
id: string;
@ -16,9 +16,20 @@ interface Contact {
interface ContactDetailsProps {
contact: Contact | null;
onSave?: (contact: Contact) => void;
onDelete?: (contact: Contact) => void;
}
export const ContactDetails: React.FC<ContactDetailsProps> = ({ contact }) => {
export const ContactDetails: React.FC<ContactDetailsProps> = ({ contact, onSave, onDelete }) => {
const [isEditing, setIsEditing] = useState(false);
const [editedContact, setEditedContact] = useState<Contact | null>(null);
// Initialize edited contact when a contact is selected
React.useEffect(() => {
setEditedContact(contact);
setIsEditing(false);
}, [contact]);
if (!contact) {
return (
<div className="flex flex-col h-full bg-carnet-bg items-center justify-center text-carnet-text-muted">
@ -28,83 +39,137 @@ export const ContactDetails: React.FC<ContactDetailsProps> = ({ contact }) => {
);
}
const handleSave = () => {
if (editedContact && onSave) {
onSave(editedContact);
setIsEditing(false);
}
};
const handleCancel = () => {
setEditedContact(contact);
setIsEditing(false);
};
const renderField = (label: string, value: string | undefined, field: keyof Contact, icon: React.ReactNode) => {
const bgColor = {
email: 'bg-blue-50',
phone: 'bg-green-50',
organization: 'bg-purple-50',
address: 'bg-orange-50'
}[field] || 'bg-gray-50';
const textColor = {
email: 'text-blue-500',
phone: 'text-green-500',
organization: 'text-purple-500',
address: 'text-orange-500'
}[field] || 'text-gray-500';
return (
<div className="flex items-center space-x-3">
<div className={`h-8 w-8 rounded-full ${bgColor} flex items-center justify-center`}>
<div className={textColor}>{icon}</div>
</div>
<div className="flex-1">
<p className="text-xs text-carnet-text-muted">{label}</p>
{isEditing ? (
<input
type="text"
value={editedContact?.[field] || ''}
onChange={(e) => setEditedContact(prev => prev ? {...prev, [field]: e.target.value} : null)}
className="w-full text-sm text-carnet-text-primary bg-transparent border-b border-primary focus:outline-none"
/>
) : (
<p className="text-sm text-carnet-text-primary">{value}</p>
)}
</div>
</div>
);
};
return (
<div className="flex flex-col h-full bg-carnet-bg p-6">
<div className="space-y-6">
{/* Contact Header */}
<div className="flex justify-between items-start mb-6">
<div className="flex items-center space-x-4">
<div className="h-16 w-16 rounded-full bg-primary/10 flex items-center justify-center">
<User className="h-8 w-8 text-primary" />
</div>
<div>
<h2 className="text-xl font-semibold text-carnet-text-primary">
{contact.fullName || contact.email || 'Sans nom'}
</h2>
{contact.organization && (
<p className="text-sm text-carnet-text-muted">
{contact.organization}
</p>
{isEditing ? (
<input
type="text"
value={editedContact?.fullName || ''}
onChange={(e) => setEditedContact(prev => prev ? {...prev, fullName: e.target.value} : null)}
className="text-xl font-semibold text-carnet-text-primary bg-transparent border-b border-primary focus:outline-none"
placeholder="Nom complet"
/>
) : (
<h2 className="text-xl font-semibold text-carnet-text-primary">
{contact.fullName || contact.email || 'Sans nom'}
</h2>
)}
</div>
</div>
{/* Contact Information */}
<div className="space-y-4">
{contact.email && (
<div className="flex items-center space-x-3">
<div className="h-8 w-8 rounded-full bg-blue-50 flex items-center justify-center">
<Mail className="h-4 w-4 text-blue-500" />
</div>
<div>
<p className="text-xs text-carnet-text-muted">Email</p>
<p className="text-sm text-carnet-text-primary">{contact.email}</p>
</div>
</div>
)}
{contact.phone && (
<div className="flex items-center space-x-3">
<div className="h-8 w-8 rounded-full bg-green-50 flex items-center justify-center">
<Phone className="h-4 w-4 text-green-500" />
</div>
<div>
<p className="text-xs text-carnet-text-muted">Téléphone</p>
<p className="text-sm text-carnet-text-primary">{contact.phone}</p>
</div>
</div>
)}
{contact.organization && (
<div className="flex items-center space-x-3">
<div className="h-8 w-8 rounded-full bg-purple-50 flex items-center justify-center">
<Building className="h-4 w-4 text-purple-500" />
</div>
<div>
<p className="text-xs text-carnet-text-muted">Organisation</p>
<p className="text-sm text-carnet-text-primary">{contact.organization}</p>
</div>
</div>
)}
{contact.address && (
<div className="flex items-center space-x-3">
<div className="h-8 w-8 rounded-full bg-orange-50 flex items-center justify-center">
<MapPin className="h-4 w-4 text-orange-500" />
</div>
<div>
<p className="text-xs text-carnet-text-muted">Adresse</p>
<p className="text-sm text-carnet-text-primary">{contact.address}</p>
</div>
</div>
<div className="flex space-x-2">
{isEditing ? (
<>
<button
onClick={handleSave}
className="p-2 rounded-full hover:bg-primary/10 text-primary"
title="Enregistrer"
>
<Save className="h-5 w-5" />
</button>
<button
onClick={handleCancel}
className="p-2 rounded-full hover:bg-red-100 text-red-500"
title="Annuler"
>
<X className="h-5 w-5" />
</button>
</>
) : (
<>
<button
onClick={() => setIsEditing(true)}
className="p-2 rounded-full hover:bg-primary/10 text-primary"
title="Modifier"
>
<Edit2 className="h-5 w-5" />
</button>
{onDelete && (
<button
onClick={() => onDelete(contact)}
className="p-2 rounded-full hover:bg-red-100 text-red-500"
title="Supprimer"
>
<X className="h-5 w-5" />
</button>
)}
</>
)}
</div>
</div>
{/* Notes Section */}
{contact.notes && (
<div className="mt-6">
<h3 className="text-sm font-medium text-carnet-text-primary mb-2">Notes</h3>
<p className="text-sm text-carnet-text-muted whitespace-pre-wrap">{contact.notes}</p>
</div>
<div className="space-y-4">
{renderField('Email', contact.email, 'email', <Mail className="h-4 w-4" />)}
{renderField('Téléphone', contact.phone, 'phone', <Phone className="h-4 w-4" />)}
{renderField('Organisation', contact.organization, 'organization', <Building className="h-4 w-4" />)}
{renderField('Adresse', contact.address, 'address', <MapPin className="h-4 w-4" />)}
</div>
<div className="mt-6">
<h3 className="text-sm font-medium text-carnet-text-primary mb-2">Notes</h3>
{isEditing ? (
<textarea
value={editedContact?.notes || ''}
onChange={(e) => setEditedContact(prev => prev ? {...prev, notes: e.target.value} : null)}
className="w-full h-32 text-sm text-carnet-text-muted bg-transparent border rounded-md p-2 focus:outline-none focus:border-primary"
placeholder="Ajouter des notes..."
/>
) : (
<p className="text-sm text-carnet-text-muted whitespace-pre-wrap">{contact.notes}</p>
)}
</div>
</div>