207 lines
No EOL
6.4 KiB
TypeScript
207 lines
No EOL
6.4 KiB
TypeScript
import React from "react";
|
|
import { Plus, User, Mail, Phone, Building2, Edit, Trash2 } from "lucide-react";
|
|
import PrimaryButton_v2 from "@/components/PrimaryButton_v2";
|
|
import { Contacts } from "@/types/clientType";
|
|
|
|
// ============================================
|
|
// TYPES
|
|
// ============================================
|
|
|
|
interface ClientContactsListProps {
|
|
contacts: Contacts[];
|
|
onCreateContact: () => void;
|
|
onEditContact: (contact: Contacts) => void;
|
|
onDeleteContact?: (contactId: string | number) => void;
|
|
showDeleteButton?: boolean;
|
|
}
|
|
|
|
// ============================================
|
|
// SOUS-COMPOSANTS
|
|
// ============================================
|
|
|
|
const EmptyState: React.FC = () => (
|
|
<div className="text-center py-12 bg-white dark:bg-gray-950 rounded-2xl border border-dashed border-gray-200 dark:border-gray-800">
|
|
<User className="w-12 h-12 text-gray-300 mx-auto mb-3" />
|
|
<p className="text-gray-500 font-medium">Aucun contact enregistré</p>
|
|
<p className="text-sm text-gray-400 mt-1">
|
|
Cliquez sur "Ajouter un contact" pour commencer
|
|
</p>
|
|
</div>
|
|
);
|
|
|
|
const ContactAvatar: React.FC<{ nom: string; prenom: string }> = ({ nom, prenom }) => (
|
|
<div className="w-10 h-10 rounded-full bg-gray-100 dark:bg-gray-800 flex items-center justify-center text-gray-600 dark:text-gray-300 font-bold">
|
|
{prenom?.[0] || ""}{nom?.[0] || ""}
|
|
</div>
|
|
);
|
|
|
|
const DefaultBadge: React.FC = () => (
|
|
<span className="px-2 py-0.5 bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-300 text-[10px] font-bold uppercase tracking-wider rounded-full border border-green-200 dark:border-green-800">
|
|
Défaut
|
|
</span>
|
|
);
|
|
|
|
interface ContactInfoRowProps {
|
|
icon: React.ElementType;
|
|
value: string;
|
|
href?: string;
|
|
type?: "email" | "tel";
|
|
}
|
|
|
|
const ContactInfoRow: React.FC<ContactInfoRowProps> = ({ icon: Icon, value, href, type }) => {
|
|
const content = (
|
|
<>
|
|
<Icon className="w-4 h-4" />
|
|
{value}
|
|
</>
|
|
);
|
|
|
|
if (href) {
|
|
return (
|
|
<a
|
|
href={type === "email" ? `mailto:${href}` : type === "tel" ? `tel:${href}` : href}
|
|
className="flex items-center gap-2 text-sm text-gray-600 dark:text-gray-400 hover:text-[#007E45] transition-colors"
|
|
>
|
|
{content}
|
|
</a>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="flex items-center gap-2 text-sm text-gray-600 dark:text-gray-400">
|
|
{content}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
interface ContactCardProps {
|
|
contact: Contacts;
|
|
onEdit: (contact: Contacts) => void;
|
|
onDelete?: (contactId: string | number) => void;
|
|
showDeleteButton?: boolean;
|
|
}
|
|
|
|
const ContactCard: React.FC<ContactCardProps> = ({
|
|
contact,
|
|
onEdit,
|
|
onDelete,
|
|
showDeleteButton = false,
|
|
}) => (
|
|
<div className="bg-white dark:bg-gray-950 border border-gray-200 dark:border-gray-800 rounded-xl p-5 hover:shadow-md transition-shadow relative group">
|
|
{/* Header */}
|
|
<div className="flex justify-between items-start mb-4">
|
|
<div className="flex items-center gap-3">
|
|
<ContactAvatar nom={contact.nom} prenom={contact.prenom} />
|
|
<div>
|
|
<h4 className="font-bold text-gray-900 dark:text-white">
|
|
{contact.prenom} {contact.nom}
|
|
</h4>
|
|
<p className="text-xs text-gray-500">
|
|
{contact.fonction || "Pas de poste défini"}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
{contact.est_defaut && <DefaultBadge />}
|
|
</div>
|
|
|
|
{/* Contact Info */}
|
|
<div className="space-y-2 mb-4">
|
|
{contact.email && (
|
|
<ContactInfoRow
|
|
icon={Mail}
|
|
value={contact.email}
|
|
href={contact.email}
|
|
type="email"
|
|
/>
|
|
)}
|
|
{contact.telephone && (
|
|
<ContactInfoRow
|
|
icon={Phone}
|
|
value={contact.telephone}
|
|
href={contact.telephone}
|
|
type="tel"
|
|
/>
|
|
)}
|
|
<ContactInfoRow
|
|
icon={Building2}
|
|
value={contact.fonction || "Non spécifié"}
|
|
/>
|
|
</div>
|
|
|
|
{/* Actions */}
|
|
<div className="pt-4 border-t border-gray-100 dark:border-gray-800 flex justify-end gap-2">
|
|
<button
|
|
onClick={() => onEdit(contact)}
|
|
className="p-2 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg text-gray-400 hover:text-blue-500 transition-colors"
|
|
title="Modifier"
|
|
>
|
|
<Edit className="w-4 h-4" />
|
|
</button>
|
|
{showDeleteButton && onDelete && contact.numero && (
|
|
<button
|
|
onClick={() => onDelete(contact.numero!)}
|
|
className="p-2 hover:bg-red-50 dark:hover:bg-red-900/20 rounded-lg text-gray-400 hover:text-red-500 transition-colors"
|
|
title="Supprimer"
|
|
>
|
|
<Trash2 className="w-4 h-4" />
|
|
</button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
|
|
// ============================================
|
|
// COMPOSANT PRINCIPAL
|
|
// ============================================
|
|
|
|
const ClientContactsList: React.FC<ClientContactsListProps> = ({
|
|
contacts,
|
|
onCreateContact,
|
|
onEditContact,
|
|
onDeleteContact,
|
|
showDeleteButton = false,
|
|
}) => {
|
|
const contactCount = contacts?.length || 0;
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* Header */}
|
|
<div className="flex justify-between items-center">
|
|
<h3 className="font-bold text-gray-900 dark:text-white">
|
|
Liste des contacts ({contactCount})
|
|
</h3>
|
|
<PrimaryButton_v2 icon={Plus} onClick={onCreateContact}>
|
|
Ajouter un contact
|
|
</PrimaryButton_v2>
|
|
</div>
|
|
|
|
{/* Content */}
|
|
{contactCount === 0 ? (
|
|
<EmptyState />
|
|
) : (
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
{contacts?.map((contact, index) => (
|
|
<ContactCard
|
|
key={index}
|
|
contact={contact}
|
|
onEdit={onEditContact}
|
|
onDelete={onDeleteContact}
|
|
showDeleteButton={showDeleteButton}
|
|
/>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default ClientContactsList;
|
|
|
|
// Export des sous-composants si besoin
|
|
export {
|
|
EmptyState,
|
|
ContactAvatar,
|
|
DefaultBadge,
|
|
ContactInfoRow,
|
|
ContactCard,
|
|
}; |