Added alphabetical tabs
X-Lovable-Edit-ID: edt-7760f53e-bc6b-4db9-9ebb-9f5872f6102c Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com>
This commit is contained in:
@@ -43,6 +43,7 @@ function ContactsIndex() {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [selected, setSelected] = useState<Set<string>>(new Set());
|
||||
const [bulkType, setBulkType] = useState<string>("");
|
||||
const [letter, setLetter] = useState<string>("all");
|
||||
|
||||
const load = async () => {
|
||||
setLoading(true);
|
||||
@@ -70,7 +71,12 @@ function ContactsIndex() {
|
||||
load();
|
||||
}, []);
|
||||
|
||||
const filtered = useMemo(() => {
|
||||
const letterOf = (name: string | null | undefined) => {
|
||||
const ch = (name?.[0] ?? "#").toUpperCase();
|
||||
return /[A-Z]/.test(ch) ? ch : "#";
|
||||
};
|
||||
|
||||
const preLetterFiltered = useMemo(() => {
|
||||
const term = q.trim().toLowerCase();
|
||||
return rows.filter((r) => {
|
||||
if (view === "archived" ? !r.archived_at : !!r.archived_at) return false;
|
||||
@@ -85,13 +91,26 @@ function ContactsIndex() {
|
||||
});
|
||||
}, [rows, q, typeFilter, view]);
|
||||
|
||||
const letterCounts = useMemo(() => {
|
||||
const counts: Record<string, number> = { all: preLetterFiltered.length };
|
||||
for (const r of preLetterFiltered) {
|
||||
const l = letterOf(r.name);
|
||||
counts[l] = (counts[l] ?? 0) + 1;
|
||||
}
|
||||
return counts;
|
||||
}, [preLetterFiltered]);
|
||||
|
||||
const filtered = useMemo(() => {
|
||||
if (letter === "all") return preLetterFiltered;
|
||||
return preLetterFiltered.filter((r) => letterOf(r.name) === letter);
|
||||
}, [preLetterFiltered, letter]);
|
||||
|
||||
const grouped = useMemo(() => {
|
||||
const map = new Map<string, ContactRow[]>();
|
||||
for (const r of filtered) {
|
||||
const ch = (r.name?.[0] ?? "#").toUpperCase();
|
||||
const letter = /[A-Z]/.test(ch) ? ch : "#";
|
||||
if (!map.has(letter)) map.set(letter, []);
|
||||
map.get(letter)!.push(r);
|
||||
const l = letterOf(r.name);
|
||||
if (!map.has(l)) map.set(l, []);
|
||||
map.get(l)!.push(r);
|
||||
}
|
||||
return Array.from(map.entries()).sort(([a], [b]) => {
|
||||
if (a === "#") return 1;
|
||||
@@ -265,6 +284,34 @@ function ContactsIndex() {
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="mb-4 flex flex-wrap gap-1">
|
||||
<Button
|
||||
size="sm"
|
||||
variant={letter === "all" ? "default" : "outline"}
|
||||
className="h-8 px-2.5"
|
||||
onClick={() => setLetter("all")}
|
||||
>
|
||||
All ({letterCounts.all ?? 0})
|
||||
</Button>
|
||||
{"ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("").concat("#").map((L) => {
|
||||
const count = letterCounts[L] ?? 0;
|
||||
const disabled = count === 0;
|
||||
return (
|
||||
<Button
|
||||
key={L}
|
||||
size="sm"
|
||||
variant={letter === L ? "default" : "outline"}
|
||||
className="h-8 w-8 p-0 text-xs"
|
||||
disabled={disabled}
|
||||
onClick={() => setLetter(L)}
|
||||
title={`${L} (${count})`}
|
||||
>
|
||||
{L}
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{selected.size > 0 && (
|
||||
<Card className="mb-4 border-primary/50 bg-primary/5">
|
||||
<CardContent className="p-3 flex flex-wrap items-center gap-2">
|
||||
|
||||
Reference in New Issue
Block a user