Files
Brancheneinstufung2/company-explorer/frontend/src/components/ContactsManager.tsx

317 lines
14 KiB
TypeScript

import { useState, useEffect } from 'react'
import { Users, Star, Mail, User, Activity, Plus, X, Save } from 'lucide-react'
import clsx from 'clsx'
export type ContactRole = 'Operativer Entscheider' | 'Infrastruktur-Verantwortlicher' | 'Wirtschaftlicher Entscheider' | 'Innovations-Treiber'
export type ContactStatus =
| '' // Leer
// Manual
| 'Soft Denied' | 'Bounced' | 'Redirect' | 'Interested' | 'Hard denied'
// Auto
| 'Init' | '1st Step' | '2nd Step' | 'Not replied'
export interface Contact {
id?: number
gender: 'männlich' | 'weiblich'
title: string
first_name: string
last_name: string
email: string
job_title: string
language: 'De' | 'En'
role: ContactRole
status: ContactStatus
is_primary: boolean
}
interface ContactsManagerProps {
contacts?: Contact[]
initialContactId?: number | null // NEW
onAddContact?: (contact: Contact) => void
onEditContact?: (contact: Contact) => void
}
export function ContactsManager({ contacts = [], initialContactId, onAddContact, onEditContact }: ContactsManagerProps) {
const [editingContact, setEditingContact] = useState<Contact | null>(null)
const [isFormOpen, setIsFormOpen] = useState(false)
// Auto-open edit form if initialContactId is provided
useEffect(() => {
if (initialContactId && contacts.length > 0) {
const contact = contacts.find(c => c.id === initialContactId)
if (contact) {
setEditingContact({ ...contact })
setIsFormOpen(true)
}
}
}, [initialContactId, contacts])
const roleColors: Record<ContactRole, string> = {
'Operativer Entscheider': 'text-blue-400 border-blue-400/30 bg-blue-900/20',
'Infrastruktur-Verantwortlicher': 'text-orange-400 border-orange-400/30 bg-orange-900/20',
'Wirtschaftlicher Entscheider': 'text-green-400 border-green-400/30 bg-green-900/20',
'Innovations-Treiber': 'text-purple-400 border-purple-400/30 bg-purple-900/20'
}
const statusColors: Record<string, string> = {
'': 'text-slate-600 italic',
'Soft Denied': 'text-slate-400',
'Bounced': 'text-red-500',
'Redirect': 'text-yellow-500',
'Interested': 'text-green-500',
'Hard denied': 'text-red-700',
'Init': 'text-slate-300',
'1st Step': 'text-blue-300',
'2nd Step': 'text-blue-400',
'Not replied': 'text-slate-500',
}
const handleAddNew = () => {
setEditingContact({
gender: 'männlich',
title: '',
first_name: '',
last_name: '',
email: '',
job_title: '',
language: 'De',
role: 'Operativer Entscheider',
status: '',
is_primary: false
})
setIsFormOpen(true)
}
const handleEdit = (contact: Contact) => {
setEditingContact({ ...contact })
setIsFormOpen(true)
}
const handleSave = () => {
if (editingContact) {
if (editingContact.id) {
onEditContact && onEditContact(editingContact)
} else {
onAddContact && onAddContact(editingContact)
}
}
setIsFormOpen(false)
setEditingContact(null)
}
if (isFormOpen && editingContact) {
return (
<div className="bg-slate-900/50 rounded-lg p-4 border border-slate-700 space-y-4 animate-in fade-in slide-in-from-bottom-2">
<div className="flex justify-between items-center border-b border-slate-700 pb-2 mb-2">
<h3 className="text-sm font-bold text-white">
{editingContact.id ? 'Edit Contact' : 'New Contact'}
</h3>
<button onClick={() => setIsFormOpen(false)} className="text-slate-400 hover:text-white">
<X className="h-4 w-4" />
</button>
</div>
{/* Salutation / Address Section */}
<div className="grid grid-cols-2 gap-3">
<div className="space-y-1">
<label className="text-[10px] uppercase text-slate-500 font-bold">Gender / Salutation</label>
<select
className="w-full bg-slate-800 border border-slate-700 rounded px-2 py-1.5 text-sm text-white focus:border-blue-500 outline-none"
value={editingContact.gender}
onChange={e => setEditingContact({...editingContact, gender: e.target.value as any})}
>
<option value="männlich">Male / Herr</option>
<option value="weiblich">Female / Frau</option>
</select>
</div>
<div className="space-y-1">
<label className="text-[10px] uppercase text-slate-500 font-bold">Academic Title</label>
<input
className="w-full bg-slate-800 border border-slate-700 rounded px-2 py-1.5 text-sm text-white focus:border-blue-500 outline-none"
value={editingContact.title}
placeholder="e.g. Dr., Prof."
onChange={e => setEditingContact({...editingContact, title: e.target.value})}
/>
</div>
</div>
<div className="grid grid-cols-2 gap-3">
<div className="space-y-1">
<label className="text-[10px] uppercase text-slate-500 font-bold">First Name</label>
<input
className="w-full bg-slate-800 border border-slate-700 rounded px-2 py-1.5 text-sm text-white focus:border-blue-500 outline-none"
value={editingContact.first_name}
onChange={e => setEditingContact({...editingContact, first_name: e.target.value})}
/>
</div>
<div className="space-y-1">
<label className="text-[10px] uppercase text-slate-500 font-bold">Last Name</label>
<input
className="w-full bg-slate-800 border border-slate-700 rounded px-2 py-1.5 text-sm text-white focus:border-blue-500 outline-none"
value={editingContact.last_name}
onChange={e => setEditingContact({...editingContact, last_name: e.target.value})}
/>
</div>
</div>
<div className="grid grid-cols-2 gap-3">
<div className="space-y-1">
<label className="text-[10px] uppercase text-slate-500 font-bold">Email</label>
<input
className="w-full bg-slate-800 border border-slate-700 rounded px-2 py-1.5 text-sm text-white focus:border-blue-500 outline-none"
value={editingContact.email}
onChange={e => setEditingContact({...editingContact, email: e.target.value})}
/>
</div>
<div className="space-y-1">
<label className="text-[10px] uppercase text-slate-500 font-bold">Job Title (Card)</label>
<input
className="w-full bg-slate-800 border border-slate-700 rounded px-2 py-1.5 text-sm text-white focus:border-blue-500 outline-none"
value={editingContact.job_title}
onChange={e => setEditingContact({...editingContact, job_title: e.target.value})}
/>
</div>
</div>
<div className="grid grid-cols-2 gap-3">
<div className="space-y-1">
<label className="text-[10px] uppercase text-slate-500 font-bold">Our Role Interpretation</label>
<select
className="w-full bg-slate-800 border border-slate-700 rounded px-2 py-1.5 text-sm text-white focus:border-blue-500 outline-none"
value={editingContact.role}
onChange={e => setEditingContact({...editingContact, role: e.target.value as ContactRole})}
>
{Object.keys(roleColors).map(r => <option key={r} value={r}>{r}</option>)}
</select>
</div>
<div className="space-y-1">
<label className="text-[10px] uppercase text-slate-500 font-bold">Marketing Status</label>
<select
className="w-full bg-slate-800 border border-slate-700 rounded px-2 py-1.5 text-sm text-white focus:border-blue-500 outline-none"
value={editingContact.status}
onChange={e => setEditingContact({...editingContact, status: e.target.value as ContactStatus})}
>
<option value="">&lt;leer&gt;</option>
{Object.keys(statusColors).filter(s => s !== '').map(s => <option key={s} value={s}>{s}</option>)}
</select>
</div>
</div>
<div className="grid grid-cols-2 gap-3">
<div className="space-y-1">
<label className="text-[10px] uppercase text-slate-500 font-bold">Language</label>
<select
className="w-full bg-slate-800 border border-slate-700 rounded px-2 py-1.5 text-sm text-white focus:border-blue-500 outline-none"
value={editingContact.language}
onChange={e => setEditingContact({...editingContact, language: e.target.value as any})}
>
<option value="De">De</option>
<option value="En">En</option>
</select>
</div>
<div className="flex items-center pt-5">
<label className="flex items-center gap-2 cursor-pointer text-sm text-slate-300 hover:text-white">
<input
type="checkbox"
checked={editingContact.is_primary}
onChange={e => setEditingContact({...editingContact, is_primary: e.target.checked})}
className="rounded border-slate-700 bg-slate-800 text-blue-500 focus:ring-blue-500"
/>
Primary Contact
</label>
</div>
</div>
<div className="flex gap-2 pt-2">
<button
onClick={handleSave}
className="flex-1 bg-blue-600 hover:bg-blue-500 text-white text-sm font-bold py-2 rounded flex items-center justify-center gap-2"
>
<Save className="h-4 w-4" /> Save Contact
</button>
</div>
</div>
)
}
return (
<div className="space-y-4">
<div className="flex items-center justify-between">
<h3 className="text-sm font-semibold text-slate-400 uppercase tracking-wider flex items-center gap-2">
<Users className="h-4 w-4" /> Contacts List
</h3>
<button
onClick={handleAddNew}
className="flex items-center gap-1 px-3 py-1 bg-blue-600/20 text-blue-400 border border-blue-500/30 rounded hover:bg-blue-600 hover:text-white transition-all text-xs font-bold"
>
<Plus className="h-3.5 w-3.5" /> ADD
</button>
</div>
<div className="space-y-3">
{contacts.length === 0 ? (
<div className="p-8 rounded-xl border border-dashed border-slate-800 text-center text-slate-600">
<Users className="h-8 w-8 mx-auto mb-3 opacity-20" />
<p className="text-sm font-medium">No contacts yet.</p>
<p className="text-xs mt-1 opacity-70">Click "ADD" to create the first contact for this account.</p>
</div>
) : (
contacts.map(contact => (
<div
key={contact.id}
className={clsx(
"relative bg-slate-800/30 border rounded-lg p-3 transition-all hover:bg-slate-800/50 group cursor-pointer",
contact.is_primary ? "border-blue-500/30 shadow-lg shadow-blue-900/10" : "border-slate-800"
)}
onClick={() => handleEdit(contact)}
>
{contact.is_primary && (
<div className="absolute top-2 right-2 text-blue-500" title="Primary Contact">
<Star className="h-3 w-3 fill-current" />
</div>
)}
<div className="flex items-start gap-3">
<div className="p-2 bg-slate-900 rounded-full text-slate-400 shrink-0 mt-1">
<User className="h-4 w-4" />
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-0.5">
<span className="text-sm font-bold text-slate-200 truncate">
{contact.title ? `${contact.title} ` : ''}{contact.first_name} {contact.last_name}
</span>
<span className="text-[10px] text-slate-500 border border-slate-700 px-1 rounded">
{contact.language}
</span>
</div>
<div className="text-xs text-slate-400 mb-2 truncate font-medium">
{contact.job_title}
</div>
<div className="flex flex-wrap gap-2 mb-2">
<span className={clsx("text-[10px] px-1.5 py-0.5 rounded border font-medium", roleColors[contact.role] || "text-slate-400 border-slate-700")}>
{contact.role}
</span>
</div>
<div className="flex items-center gap-3 text-[10px] text-slate-500 font-mono">
<div className="flex items-center gap-1 truncate">
<Mail className="h-3 w-3" />
{contact.email}
</div>
<div className={clsx("flex items-center gap-1 font-bold ml-auto mr-8", statusColors[contact.status])}>
<Activity className="h-3 w-3" />
{contact.status || '<leer>'}
</div>
</div>
</div>
</div>
</div>
))
)}
</div>
</div>
)
}