Changes
Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com>
This commit is contained in:
co-authored by
renee-png
parent
cee0bb0915
commit
dfc8e59407
@@ -12,6 +12,7 @@ import { supabase } from "@/integrations/supabase/client";
|
||||
import { useAuth } from "@/lib/auth";
|
||||
import { Loader2, Plus, Trash2 } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import { ContactPickerPopover } from "./contact-picker-popover";
|
||||
|
||||
const boardMember = z.object({
|
||||
name: z.string().trim().min(1, "Required").max(120),
|
||||
@@ -299,9 +300,25 @@ export function ClientFormDialog({
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<label className="text-sm font-medium">Board members</label>
|
||||
<Button type="button" variant="outline" size="sm" onClick={addBoardMember}>
|
||||
<Plus className="h-3 w-3 mr-1" /> Add member
|
||||
</Button>
|
||||
<div className="flex gap-2">
|
||||
<ContactPickerPopover
|
||||
label="From contacts"
|
||||
onPick={(c) =>
|
||||
form.setValue("board_members", [
|
||||
...board,
|
||||
{
|
||||
name: c.name,
|
||||
role: c.title ?? "",
|
||||
email: c.email ?? "",
|
||||
phone: c.phone ?? "",
|
||||
},
|
||||
])
|
||||
}
|
||||
/>
|
||||
<Button type="button" variant="outline" size="sm" onClick={addBoardMember}>
|
||||
<Plus className="h-3 w-3 mr-1" /> Add member
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
{board.length === 0 && (
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { supabase } from "@/integrations/supabase/client";
|
||||
import { Search, UserSearch } from "lucide-react";
|
||||
|
||||
interface PickResult {
|
||||
id: string;
|
||||
name: string;
|
||||
email: string | null;
|
||||
phone: string | null;
|
||||
title: string | null;
|
||||
company: string | null;
|
||||
contact_type: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compact "pick from contacts" popover used inside list-style editors
|
||||
* (e.g. board members rows in the client form).
|
||||
*/
|
||||
export function ContactPickerPopover({
|
||||
onPick,
|
||||
label = "From contacts",
|
||||
}: {
|
||||
onPick: (c: PickResult) => void;
|
||||
label?: string;
|
||||
}) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [q, setQ] = useState("");
|
||||
const [rows, setRows] = useState<PickResult[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
setQ("");
|
||||
supabase
|
||||
.from("contacts")
|
||||
.select("id, name, email, phone, title, company, contact_type")
|
||||
.is("archived_at", null)
|
||||
.order("name")
|
||||
.limit(200)
|
||||
.then(({ data }) => setRows((data ?? []) as PickResult[]));
|
||||
}, [open]);
|
||||
|
||||
const filtered = useMemo(() => {
|
||||
const term = q.trim().toLowerCase();
|
||||
if (!term) return rows;
|
||||
return rows.filter(
|
||||
(r) =>
|
||||
r.name.toLowerCase().includes(term) ||
|
||||
(r.company ?? "").toLowerCase().includes(term) ||
|
||||
(r.email ?? "").toLowerCase().includes(term),
|
||||
);
|
||||
}, [rows, q]);
|
||||
|
||||
return (
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button type="button" variant="outline" size="sm">
|
||||
<UserSearch className="h-3.5 w-3.5 mr-1.5" />
|
||||
{label}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-80 p-0" align="end">
|
||||
<div className="p-2 border-b">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground" />
|
||||
<Input
|
||||
autoFocus
|
||||
value={q}
|
||||
onChange={(e) => setQ(e.target.value)}
|
||||
placeholder="Search contacts…"
|
||||
className="h-8 pl-8 text-sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="max-h-72 overflow-auto">
|
||||
{filtered.length === 0 ? (
|
||||
<p className="text-xs text-muted-foreground p-3">No matching contacts.</p>
|
||||
) : (
|
||||
<ul className="divide-y">
|
||||
{filtered.map((c) => (
|
||||
<li key={c.id}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
onPick(c);
|
||||
setOpen(false);
|
||||
}}
|
||||
className="w-full text-left px-3 py-2 hover:bg-muted/60 flex items-center justify-between gap-2"
|
||||
>
|
||||
<div className="min-w-0">
|
||||
<div className="text-sm font-medium truncate">{c.name}</div>
|
||||
<div className="text-[11px] text-muted-foreground truncate">
|
||||
{[c.company, c.email].filter(Boolean).join(" · ") || "—"}
|
||||
</div>
|
||||
</div>
|
||||
<Badge variant="outline" className="text-[10px] shrink-0 capitalize">
|
||||
{c.contact_type}
|
||||
</Badge>
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user