diff --git a/src/components/Badge.tsx b/src/components/Badge.tsx index 8a1b685..fc3278f 100644 --- a/src/components/Badge.tsx +++ b/src/components/Badge.tsx @@ -53,3 +53,24 @@ export function EmailStatusBadge({ status }: { status: string }) { }; return {status}; } + +const stageLabels: Record = { + lead: 'Lead', + prospect: 'Prospect', + onboarding: 'Onboarding', + active: 'Active', + inactive: 'Inactive', +}; + +const stageColors: Record = { + lead: 'gray', + prospect: 'blue', + onboarding: 'yellow', + active: 'green', + inactive: 'red', +}; + +export function StageBadge({ stage, onClick }: { stage?: string; onClick?: () => void }) { + const s = stage || 'lead'; + return {stageLabels[s] || s}; +} diff --git a/src/components/ClientForm.tsx b/src/components/ClientForm.tsx index 11d60d3..d59e31d 100644 --- a/src/components/ClientForm.tsx +++ b/src/components/ClientForm.tsx @@ -28,6 +28,7 @@ export default function ClientForm({ initialData, onSubmit, loading }: ClientFor family: initialData?.family || { spouse: '', children: [] }, notes: initialData?.notes || '', tags: initialData?.tags || [], + stage: initialData?.stage || 'lead', }); const [tagInput, setTagInput] = useState(''); @@ -109,6 +110,18 @@ export default function ClientForm({ initialData, onSubmit, loading }: ClientFor + {/* Stage */} +
+ + +
+ {/* Address */}
diff --git a/src/components/ClientNotes.tsx b/src/components/ClientNotes.tsx new file mode 100644 index 0000000..613c348 --- /dev/null +++ b/src/components/ClientNotes.tsx @@ -0,0 +1,214 @@ +import { useEffect, useState, useRef } from 'react'; +import { api } from '@/lib/api'; +import type { ClientNote } from '@/types'; +import { Pin, Trash2, Edit3, Check, X, Plus, StickyNote } from 'lucide-react'; +import { cn, getRelativeTime } from '@/lib/utils'; + +interface ClientNotesProps { + clientId: string; +} + +export default function ClientNotes({ clientId }: ClientNotesProps) { + const [notes, setNotes] = useState([]); + const [loading, setLoading] = useState(true); + const [newNote, setNewNote] = useState(''); + const [adding, setAdding] = useState(false); + const [editingId, setEditingId] = useState(null); + const [editContent, setEditContent] = useState(''); + const textareaRef = useRef(null); + + const fetchNotes = async () => { + try { + const data = await api.getClientNotes(clientId); + setNotes(data); + } catch { + // ignore + } finally { + setLoading(false); + } + }; + + useEffect(() => { + fetchNotes(); + }, [clientId]); + + const handleAdd = async () => { + if (!newNote.trim()) return; + setAdding(true); + try { + const note = await api.createClientNote(clientId, newNote.trim()); + setNotes(prev => [note, ...prev]); + setNewNote(''); + } catch { + // ignore + } finally { + setAdding(false); + } + }; + + const handleDelete = async (noteId: string) => { + if (!confirm('Delete this note?')) return; + try { + await api.deleteClientNote(clientId, noteId); + setNotes(prev => prev.filter(n => n.id !== noteId)); + } catch { + // ignore + } + }; + + const handleTogglePin = async (note: ClientNote) => { + try { + const updated = await api.updateClientNote(clientId, note.id, { pinned: !note.pinned }); + setNotes(prev => prev.map(n => n.id === note.id ? updated : n) + .sort((a, b) => { + if (a.pinned && !b.pinned) return -1; + if (!a.pinned && b.pinned) return 1; + return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(); + })); + } catch { + // ignore + } + }; + + const handleStartEdit = (note: ClientNote) => { + setEditingId(note.id); + setEditContent(note.content); + }; + + const handleSaveEdit = async (noteId: string) => { + if (!editContent.trim()) return; + try { + const updated = await api.updateClientNote(clientId, noteId, { content: editContent.trim() }); + setNotes(prev => prev.map(n => n.id === noteId ? updated : n)); + setEditingId(null); + } catch { + // ignore + } + }; + + if (loading) { + return ( +
+

Loading notes...

+
+ ); + } + + return ( +
+ {/* New note input */} +
+