Files
windwiderstand/src/lib/components/ContactCard.svelte
T
Peter Meier 982a7a3b73
Deploy / verify (push) Successful in 1m10s
Deploy / deploy (push) Successful in 1m45s
feat: QR-Code, vCard-Export, Org-Gruppenansicht im Adressbuch
- vcard.ts: shared vCard-Generator (downloadVCard, downloadVCardAll, buildVCardString)
- QrModal.svelte + QrButton.svelte: wiederverwendbare QR-Komponenten mit PNG-Download
- AdressbuchBlock: Org-Gruppenansicht (Toggle), QR + vCard pro Kontakt,
  Buchstaben-Trennzeilen, Such-Highlight, "Alle exportieren", Print-Styles
- ContactCard: vCard-Download-Button
- PostActions: QR-Button für Seiten-URL
- qrcode npm-Paket hinzugefügt

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 11:15:57 +02:00

70 lines
2.5 KiB
Svelte

<script lang="ts">
import Icon from "@iconify/svelte";
import "$lib/iconify-offline";
import type { ContactEntry } from "$lib/block-types";
import { downloadVCard } from "$lib/vcard";
let { contact }: { contact: ContactEntry } = $props();
const orgNames = $derived(
(contact.organisations ?? [])
.filter((o): o is { _slug?: string; name?: string } => typeof o === "object" && o !== null)
.map((o) => o.name)
.filter(Boolean) as string[]
);
</script>
<div
class="not-prose card-surface relative flex h-full min-w-0 flex-col gap-2 overflow-hidden rounded-sm p-4 transition-[transform,box-shadow,border-color] duration-200 hover:-translate-y-0.5 hover:border-wald-300 hover:shadow-md"
>
<div class="flex flex-col gap-1.5">
<p class="truncate pr-6 text-sm font-medium leading-tight text-stein-900">{contact.name ?? ""}</p>
{#if contact.role || orgNames.length > 0}
<div class="flex flex-wrap gap-1">
{#if contact.role}
<span class="shrink-0 rounded-full border border-stein-200 bg-stein-100 px-1.5 py-0 text-[0.65rem] font-medium text-stein-600">
{contact.role}
</span>
{/if}
{#each orgNames as org}
<span class="shrink-0 rounded-full border border-wald-200 bg-wald-50 px-1.5 py-0 text-[0.65rem] font-medium text-wald-700">
{org}
</span>
{/each}
</div>
{/if}
</div>
<!-- vCard download -->
<button
type="button"
title="Kontakt speichern (.vcf)"
onclick={() => downloadVCard(contact)}
class="absolute top-3 right-3 text-stein-300 transition-colors hover:text-wald-600"
>
<Icon icon="mdi:card-account-details-outline" class="size-4" />
</button>
{#if contact.phone || contact.email}
<div class="flex flex-col gap-1">
{#if contact.phone}
<a href="tel:{contact.phone.replace(/\s/g, '')}" class="inline-flex items-center gap-1.5 text-xs text-stein-600 no-underline hover:text-wald-700">
<Icon icon="mdi:phone-outline" class="size-3.5 shrink-0 text-stein-400" />
<span class="truncate">{contact.phone}</span>
</a>
{/if}
{#if contact.email}
<a href="mailto:{contact.email}" class="inline-flex items-center gap-1.5 text-xs text-stein-600 no-underline hover:text-wald-700">
<Icon icon="mdi:email-outline" class="size-3.5 shrink-0 text-stein-400" />
<span class="truncate">{contact.email}</span>
</a>
{/if}
</div>
{/if}
{#if contact.note}
<p class="text-[11px] text-stein-400">{contact.note}</p>
{/if}
</div>