feat: client pipeline view + notes tab + stage badges
- Pipeline/kanban view on Clients page (toggle grid/pipeline) - Pipeline summary bar showing client distribution across stages - Stage badge on client cards and detail page (click to cycle) - Notes tab on ClientDetailPage with add/edit/pin/delete - StageBadge component with color-coded labels - Stage selector in ClientForm - API client methods for notes CRUD
This commit is contained in:
@@ -10,11 +10,12 @@ import {
|
||||
} from 'lucide-react';
|
||||
import { usePinnedClients } from '@/hooks/usePinnedClients';
|
||||
import { cn, formatDate, getRelativeTime, getInitials } from '@/lib/utils';
|
||||
import Badge, { EventTypeBadge, EmailStatusBadge } from '@/components/Badge';
|
||||
import Badge, { EventTypeBadge, EmailStatusBadge, StageBadge } from '@/components/Badge';
|
||||
import { PageLoader } from '@/components/LoadingSpinner';
|
||||
import Modal from '@/components/Modal';
|
||||
import ClientForm from '@/components/ClientForm';
|
||||
import EmailComposeModal from '@/components/EmailComposeModal';
|
||||
import ClientNotes from '@/components/ClientNotes';
|
||||
|
||||
export default function ClientDetailPage() {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
@@ -23,7 +24,7 @@ export default function ClientDetailPage() {
|
||||
const [events, setEvents] = useState<Event[]>([]);
|
||||
const [emails, setEmails] = useState<Email[]>([]);
|
||||
const [activities, setActivities] = useState<ActivityItem[]>([]);
|
||||
const [activeTab, setActiveTab] = useState<'info' | 'activity' | 'events' | 'emails'>('info');
|
||||
const [activeTab, setActiveTab] = useState<'info' | 'notes' | 'activity' | 'events' | 'emails'>('info');
|
||||
const [showEdit, setShowEdit] = useState(false);
|
||||
const [showCompose, setShowCompose] = useState(false);
|
||||
const [deleting, setDeleting] = useState(false);
|
||||
@@ -62,8 +63,9 @@ export default function ClientDetailPage() {
|
||||
setShowEdit(false);
|
||||
};
|
||||
|
||||
const tabs: { key: 'info' | 'activity' | 'events' | 'emails'; label: string; count?: number; icon: typeof Users }[] = [
|
||||
const tabs: { key: typeof activeTab; label: string; count?: number; icon: typeof Users }[] = [
|
||||
{ key: 'info', label: 'Info', icon: Users },
|
||||
{ key: 'notes', label: 'Notes', icon: FileText },
|
||||
{ key: 'activity', label: 'Timeline', count: activities.length, icon: Activity },
|
||||
{ key: 'events', label: 'Events', count: events.length, icon: Calendar },
|
||||
{ key: 'emails', label: 'Emails', count: emails.length, icon: Mail },
|
||||
@@ -90,11 +92,17 @@ export default function ClientDetailPage() {
|
||||
{client.role ? `${client.role} at ` : ''}{client.company}
|
||||
</p>
|
||||
)}
|
||||
{client.tags && client.tags.length > 0 && (
|
||||
<div className="flex flex-wrap gap-1.5 mt-2">
|
||||
{client.tags.map((tag) => <Badge key={tag} color="blue">{tag}</Badge>)}
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-wrap gap-1.5 mt-2">
|
||||
<StageBadge stage={client.stage} onClick={async () => {
|
||||
const stages = ['lead', 'prospect', 'onboarding', 'active', 'inactive'];
|
||||
const currentIdx = stages.indexOf(client.stage || 'lead');
|
||||
const nextStage = stages[(currentIdx + 1) % stages.length];
|
||||
await updateClient(client.id, { stage: nextStage } as any);
|
||||
}} />
|
||||
{client.tags && client.tags.length > 0 && (
|
||||
client.tags.map((tag) => <Badge key={tag} color="blue">{tag}</Badge>)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -240,6 +248,10 @@ export default function ClientDetailPage() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeTab === 'notes' && (
|
||||
<ClientNotes clientId={client.id} />
|
||||
)}
|
||||
|
||||
{activeTab === 'activity' && (
|
||||
<div className="bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-xl">
|
||||
{activities.length === 0 ? (
|
||||
|
||||
Reference in New Issue
Block a user