feat: Adressbuch-Block mit Suche, Org- und Buchstabenfilter
Deploy / verify (push) Successful in 1m14s
Deploy / deploy (push) Successful in 1m18s

- AdressbuchBlock: listet alle Kontakte alphabetisch mit Suche,
  Org-Filter-Chips und A–Z-Buchstabenfilter; /adressbuch-Seite
- ContactCard: neues Design passend zu OrganisationsCard (Badges, Links)
- ContactsBlock: Karussell mit snap-scroll, volle Breite
- block-types, blog-utils, cms: AdressbuchBlockData, resolveAdressbuchBlocks,
  getContacts; BlockRenderer registriert adressbuch
- Kontakt-Seite: Link auf /adressbuch statt eingebettetem Block

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Peter Meier
2026-06-04 10:08:54 +02:00
parent 7bd62b75b0
commit 363d4edb76
9 changed files with 298 additions and 23 deletions
+27
View File
@@ -8,6 +8,7 @@ import {
getEntryBySlug,
getCalendarBySlug,
getCalendarItemBySlug,
getContacts,
getCalendarItems,
} from "./cms";
import type { RowContentLayout } from "./block-types";
@@ -833,3 +834,29 @@ export async function resolveStellingnahmeGeneratorBlocks(
}
}
}
export async function resolveAdressbuchBlocks(
layout: RowContentLayout | null | undefined,
): Promise<void> {
if (!layout) return;
const rows = [layout.row1Content ?? [], layout.row2Content ?? [], layout.row3Content ?? []];
let contacts: unknown[] | null = null;
for (const content of rows) {
if (!Array.isArray(content)) continue;
for (const item of content) {
if (typeof item !== "object" || item === null) continue;
if ((item as { _type?: string })._type !== "adressbuch") continue;
if (contacts === null) contacts = await getContacts();
const block = item as Record<string, unknown>;
const exclude = (block.excludeContacts as unknown[]) ?? [];
const excludeSlugs = new Set(
exclude.map((e) =>
typeof e === "string" ? e : (e as { _slug?: string })?._slug ?? ""
).filter(Boolean)
);
block.resolvedContacts = excludeSlugs.size
? contacts.filter((c) => !excludeSlugs.has((c as { _slug?: string })._slug ?? ""))
: contacts;
}
}
}