feat: real notifications, interaction logging, bulk email compose
This commit is contained in:
@@ -2,7 +2,8 @@ import { useEffect, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { api } from '@/lib/api';
|
||||
import type { Client, Event, Email, InsightsData } from '@/types';
|
||||
import { Users, Calendar, Mail, Plus, ArrowRight, Gift, Heart, Clock, AlertTriangle, Sparkles, UserCheck, PhoneForwarded, Star } from 'lucide-react';
|
||||
import type { Interaction } from '@/types';
|
||||
import { Users, Calendar, Mail, Plus, ArrowRight, Gift, Heart, Clock, AlertTriangle, Sparkles, UserCheck, PhoneForwarded, Star, Phone, FileText, MoreHorizontal } from 'lucide-react';
|
||||
import { formatDate, getDaysUntil, getInitials } from '@/lib/utils';
|
||||
import { EventTypeBadge } from '@/components/Badge';
|
||||
import { PageLoader } from '@/components/LoadingSpinner';
|
||||
@@ -13,6 +14,7 @@ export default function DashboardPage() {
|
||||
const [events, setEvents] = useState<Event[]>([]);
|
||||
const [emails, setEmails] = useState<Email[]>([]);
|
||||
const [insights, setInsights] = useState<InsightsData | null>(null);
|
||||
const [recentInteractions, setRecentInteractions] = useState<Interaction[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const { pinnedIds, togglePin, isPinned } = usePinnedClients();
|
||||
|
||||
@@ -22,11 +24,13 @@ export default function DashboardPage() {
|
||||
api.getEvents({ upcoming: 7 }).catch(() => []),
|
||||
api.getEmails({ status: 'draft' }).catch(() => []),
|
||||
api.getInsights().catch(() => null),
|
||||
]).then(([c, e, em, ins]) => {
|
||||
api.getRecentInteractions(5).catch(() => []),
|
||||
]).then(([c, e, em, ins, ri]) => {
|
||||
setClients(c);
|
||||
setEvents(e);
|
||||
setEmails(em);
|
||||
setInsights(ins as InsightsData | null);
|
||||
setRecentInteractions(ri as Interaction[]);
|
||||
setLoading(false);
|
||||
});
|
||||
}, []);
|
||||
@@ -248,6 +252,51 @@ export default function DashboardPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Recent Interactions */}
|
||||
<div className="bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-xl">
|
||||
<div className="flex items-center gap-2 px-5 py-4 border-b border-slate-100 dark:border-slate-700">
|
||||
<Phone className="w-4 h-4 text-indigo-500" />
|
||||
<h2 className="font-semibold text-slate-900 dark:text-slate-100">Recent Interactions</h2>
|
||||
</div>
|
||||
<div className="divide-y divide-slate-100 dark:divide-slate-700">
|
||||
{recentInteractions.length === 0 ? (
|
||||
<p className="px-5 py-8 text-center text-sm text-slate-400 dark:text-slate-500">No interactions logged yet</p>
|
||||
) : (
|
||||
recentInteractions.map((interaction) => {
|
||||
const typeIcons: Record<string, typeof Phone> = {
|
||||
call: Phone, meeting: Users, email: Mail, note: FileText, other: MoreHorizontal,
|
||||
};
|
||||
const typeColors: Record<string, string> = {
|
||||
call: 'text-green-600 dark:text-green-400', meeting: 'text-blue-600 dark:text-blue-400',
|
||||
email: 'text-purple-600 dark:text-purple-400', note: 'text-amber-600 dark:text-amber-400', other: 'text-slate-500',
|
||||
};
|
||||
const Icon = typeIcons[interaction.type] || MoreHorizontal;
|
||||
return (
|
||||
<Link
|
||||
key={interaction.id}
|
||||
to={`/clients/${interaction.clientId}`}
|
||||
className="flex items-center gap-3 px-5 py-3 hover:bg-slate-50 dark:hover:bg-slate-700/50 transition-colors"
|
||||
>
|
||||
<Icon className={`w-4 h-4 ${typeColors[interaction.type] || 'text-slate-400'}`} />
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-medium text-slate-900 dark:text-slate-100 truncate">{interaction.title}</p>
|
||||
<p className="text-xs text-slate-500 dark:text-slate-400 truncate">
|
||||
{interaction.client ? `${interaction.client.firstName} ${interaction.client.lastName}` : ''}
|
||||
{interaction.duration ? ` · ${interaction.duration}min` : ''}
|
||||
</p>
|
||||
</div>
|
||||
<span className="text-xs text-slate-400 dark:text-slate-500 whitespace-nowrap">
|
||||
{formatDate(interaction.contactedAt)}
|
||||
</span>
|
||||
</Link>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 gap-6">
|
||||
{/* Recent Clients */}
|
||||
<div className="bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-xl">
|
||||
<div className="flex items-center justify-between px-5 py-4 border-b border-slate-100 dark:border-slate-700">
|
||||
|
||||
Reference in New Issue
Block a user