feat: real notifications, interaction logging, bulk email compose

This commit is contained in:
2026-01-30 00:48:13 +00:00
parent b43bdf3c71
commit 691e8170f3
8 changed files with 863 additions and 118 deletions

View File

@@ -16,6 +16,8 @@ import Modal from '@/components/Modal';
import ClientForm from '@/components/ClientForm';
import EmailComposeModal from '@/components/EmailComposeModal';
import ClientNotes from '@/components/ClientNotes';
import LogInteractionModal from '@/components/LogInteractionModal';
import type { Interaction } from '@/types';
export default function ClientDetailPage() {
const { id } = useParams<{ id: string }>();
@@ -25,8 +27,10 @@ export default function ClientDetailPage() {
const [emails, setEmails] = useState<Email[]>([]);
const [activities, setActivities] = useState<ActivityItem[]>([]);
const [activeTab, setActiveTab] = useState<'info' | 'notes' | 'activity' | 'events' | 'emails'>('info');
const [interactions, setInteractions] = useState<Interaction[]>([]);
const [showEdit, setShowEdit] = useState(false);
const [showCompose, setShowCompose] = useState(false);
const [showLogInteraction, setShowLogInteraction] = useState(false);
const [deleting, setDeleting] = useState(false);
const { togglePin, isPinned } = usePinnedClients();
@@ -36,6 +40,7 @@ export default function ClientDetailPage() {
api.getEvents({ clientId: id }).then(setEvents).catch(() => {});
api.getEmails({ clientId: id }).then(setEmails).catch(() => {});
api.getClientActivity(id).then(setActivities).catch(() => {});
api.getClientInteractions(id).then(setInteractions).catch(() => {});
}
}, [id, fetchClient]);
@@ -111,7 +116,11 @@ export default function ClientDetailPage() {
<CheckCircle2 className="w-4 h-4" />
<span className="hidden sm:inline">Contacted</span>
</button>
<button onClick={() => setShowCompose(true)} className="flex items-center gap-2 px-3 py-2 bg-blue-50 text-blue-700 rounded-lg text-sm font-medium hover:bg-blue-100 transition-colors">
<button onClick={() => setShowLogInteraction(true)} className="flex items-center gap-2 px-3 py-2 bg-indigo-50 text-indigo-700 dark:bg-indigo-900/30 dark:text-indigo-300 rounded-lg text-sm font-medium hover:bg-indigo-100 dark:hover:bg-indigo-900/50 transition-colors">
<Phone className="w-4 h-4" />
<span className="hidden sm:inline">Log Interaction</span>
</button>
<button onClick={() => setShowCompose(true)} className="flex items-center gap-2 px-3 py-2 bg-blue-50 text-blue-700 dark:bg-blue-900/30 dark:text-blue-300 rounded-lg text-sm font-medium hover:bg-blue-100 dark:hover:bg-blue-900/50 transition-colors">
<Sparkles className="w-4 h-4" />
<span className="hidden sm:inline">Generate Email</span>
</button>
@@ -260,12 +269,13 @@ export default function ClientDetailPage() {
<div className="relative">
{activities.map((item, index) => {
const iconMap: Record<string, { icon: typeof Mail; color: string; bg: string }> = {
email_sent: { icon: Send, color: 'text-emerald-600', bg: 'bg-emerald-100' },
email_drafted: { icon: FileText, color: 'text-amber-600', bg: 'bg-amber-100' },
event_created: { icon: Calendar, color: 'text-blue-600', bg: 'bg-blue-100' },
client_contacted: { icon: CheckCircle2, color: 'text-emerald-600', bg: 'bg-emerald-100' },
client_created: { icon: UserPlus, color: 'text-purple-600', bg: 'bg-purple-100' },
client_updated: { icon: RefreshCw, color: 'text-slate-600', bg: 'bg-slate-100' },
email_sent: { icon: Send, color: 'text-emerald-600', bg: 'bg-emerald-100 dark:bg-emerald-900/30' },
email_drafted: { icon: FileText, color: 'text-amber-600', bg: 'bg-amber-100 dark:bg-amber-900/30' },
event_created: { icon: Calendar, color: 'text-blue-600', bg: 'bg-blue-100 dark:bg-blue-900/30' },
client_contacted: { icon: CheckCircle2, color: 'text-emerald-600', bg: 'bg-emerald-100 dark:bg-emerald-900/30' },
client_created: { icon: UserPlus, color: 'text-purple-600', bg: 'bg-purple-100 dark:bg-purple-900/30' },
client_updated: { icon: RefreshCw, color: 'text-slate-600', bg: 'bg-slate-100 dark:bg-slate-700' },
interaction: { icon: Phone, color: 'text-indigo-600 dark:text-indigo-400', bg: 'bg-indigo-100 dark:bg-indigo-900/30' },
};
const { icon: Icon, color, bg } = iconMap[item.type] || iconMap.client_updated;
@@ -344,6 +354,22 @@ export default function ClientDetailPage() {
clientName={`${client.firstName} ${client.lastName}`}
onGenerated={(email) => setEmails((prev) => [email, ...prev])}
/>
{/* Log Interaction Modal */}
<LogInteractionModal
isOpen={showLogInteraction}
onClose={() => setShowLogInteraction(false)}
clientId={client.id}
clientName={`${client.firstName} ${client.lastName}`}
onCreated={() => {
// Refresh interactions and activity
if (id) {
api.getClientInteractions(id).then(setInteractions).catch(() => {});
api.getClientActivity(id).then(setActivities).catch(() => {});
fetchClient(id); // refresh lastContactedAt
}
}}
/>
</div>
);
}