mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 20:21:09 +02:00
feat(manacore/web): expand contact detail page with all fields
Add quick actions (call, email, SMS), work section (company, position, website), address (street/city/postal/country), social media (LinkedIn, Twitter, Instagram, GitHub), and mobile phone to both view and edit modes. Edit form organized in card sections matching the view layout. Extend Contact type and store with all new fields. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
bc0ffb4440
commit
86d31c97f5
4 changed files with 423 additions and 97 deletions
|
|
@ -21,11 +21,21 @@ export function toContact(local: LocalContact): Contact {
|
||||||
displayName,
|
displayName,
|
||||||
email: local.email || null,
|
email: local.email || null,
|
||||||
phone: local.phone || null,
|
phone: local.phone || null,
|
||||||
|
mobile: local.mobile || null,
|
||||||
company: local.company || null,
|
company: local.company || null,
|
||||||
jobTitle: local.jobTitle || null,
|
jobTitle: local.jobTitle || null,
|
||||||
|
street: local.street || null,
|
||||||
|
city: local.city || null,
|
||||||
|
postalCode: local.postalCode || null,
|
||||||
|
country: local.country || null,
|
||||||
notes: local.notes || null,
|
notes: local.notes || null,
|
||||||
photoUrl: local.photoUrl || null,
|
photoUrl: local.photoUrl || null,
|
||||||
birthday: local.birthday || null,
|
birthday: local.birthday || null,
|
||||||
|
website: local.website || null,
|
||||||
|
linkedin: local.linkedin || null,
|
||||||
|
twitter: local.twitter || null,
|
||||||
|
instagram: local.instagram || null,
|
||||||
|
github: local.github || null,
|
||||||
tags: (local.tags || []).map((name, i) => ({ id: `tag-${i}`, name, color: null })),
|
tags: (local.tags || []).map((name, i) => ({ id: `tag-${i}`, name, color: null })),
|
||||||
tagIds: local.tagIds ?? [],
|
tagIds: local.tagIds ?? [],
|
||||||
isFavorite: local.isFavorite ?? false,
|
isFavorite: local.isFavorite ?? false,
|
||||||
|
|
|
||||||
|
|
@ -45,17 +45,28 @@ export const contactsStore = {
|
||||||
return toContact(newLocal);
|
return toContact(newLocal);
|
||||||
},
|
},
|
||||||
|
|
||||||
async updateContact(id: string, data: Partial<Contact>) {
|
async updateContact(id: string, data: Partial<Contact> & Record<string, unknown>) {
|
||||||
const updateData: Partial<LocalContact> = {};
|
const updateData: Partial<LocalContact> = {};
|
||||||
if (data.firstName !== undefined) updateData.firstName = data.firstName ?? undefined;
|
if (data.firstName !== undefined) updateData.firstName = data.firstName ?? undefined;
|
||||||
if (data.lastName !== undefined) updateData.lastName = data.lastName ?? undefined;
|
if (data.lastName !== undefined) updateData.lastName = data.lastName ?? undefined;
|
||||||
if (data.email !== undefined) updateData.email = data.email ?? undefined;
|
if (data.email !== undefined) updateData.email = data.email ?? undefined;
|
||||||
if (data.phone !== undefined) updateData.phone = data.phone ?? undefined;
|
if (data.phone !== undefined) updateData.phone = data.phone ?? undefined;
|
||||||
|
if (data.mobile !== undefined) updateData.mobile = data.mobile as string | undefined;
|
||||||
if (data.company !== undefined) updateData.company = data.company ?? undefined;
|
if (data.company !== undefined) updateData.company = data.company ?? undefined;
|
||||||
if (data.jobTitle !== undefined) updateData.jobTitle = data.jobTitle ?? undefined;
|
if (data.jobTitle !== undefined) updateData.jobTitle = data.jobTitle ?? undefined;
|
||||||
|
if (data.street !== undefined) updateData.street = data.street as string | undefined;
|
||||||
|
if (data.city !== undefined) updateData.city = data.city as string | undefined;
|
||||||
|
if (data.postalCode !== undefined)
|
||||||
|
updateData.postalCode = data.postalCode as string | undefined;
|
||||||
|
if (data.country !== undefined) updateData.country = data.country as string | undefined;
|
||||||
if (data.notes !== undefined) updateData.notes = data.notes ?? undefined;
|
if (data.notes !== undefined) updateData.notes = data.notes ?? undefined;
|
||||||
if (data.photoUrl !== undefined) updateData.photoUrl = data.photoUrl ?? undefined;
|
if (data.photoUrl !== undefined) updateData.photoUrl = data.photoUrl ?? undefined;
|
||||||
if (data.birthday !== undefined) updateData.birthday = data.birthday ?? undefined;
|
if (data.birthday !== undefined) updateData.birthday = data.birthday ?? undefined;
|
||||||
|
if (data.website !== undefined) updateData.website = data.website as string | undefined;
|
||||||
|
if (data.linkedin !== undefined) updateData.linkedin = data.linkedin as string | undefined;
|
||||||
|
if (data.twitter !== undefined) updateData.twitter = data.twitter as string | undefined;
|
||||||
|
if (data.instagram !== undefined) updateData.instagram = data.instagram as string | undefined;
|
||||||
|
if (data.github !== undefined) updateData.github = data.github as string | undefined;
|
||||||
if (data.tags !== undefined) updateData.tags = data.tags?.map((t) => t.name) ?? [];
|
if (data.tags !== undefined) updateData.tags = data.tags?.map((t) => t.name) ?? [];
|
||||||
if (data.isFavorite !== undefined) updateData.isFavorite = data.isFavorite;
|
if (data.isFavorite !== undefined) updateData.isFavorite = data.isFavorite;
|
||||||
if (data.isArchived !== undefined) updateData.isArchived = data.isArchived;
|
if (data.isArchived !== undefined) updateData.isArchived = data.isArchived;
|
||||||
|
|
|
||||||
|
|
@ -41,11 +41,21 @@ export interface Contact {
|
||||||
displayName?: string | null;
|
displayName?: string | null;
|
||||||
email?: string | null;
|
email?: string | null;
|
||||||
phone?: string | null;
|
phone?: string | null;
|
||||||
|
mobile?: string | null;
|
||||||
company?: string | null;
|
company?: string | null;
|
||||||
jobTitle?: string | null;
|
jobTitle?: string | null;
|
||||||
|
street?: string | null;
|
||||||
|
city?: string | null;
|
||||||
|
postalCode?: string | null;
|
||||||
|
country?: string | null;
|
||||||
notes?: string | null;
|
notes?: string | null;
|
||||||
photoUrl?: string | null;
|
photoUrl?: string | null;
|
||||||
birthday?: string | null;
|
birthday?: string | null;
|
||||||
|
website?: string | null;
|
||||||
|
linkedin?: string | null;
|
||||||
|
twitter?: string | null;
|
||||||
|
instagram?: string | null;
|
||||||
|
github?: string | null;
|
||||||
tags: Array<{ id: string; name: string; color: string | null }>;
|
tags: Array<{ id: string; name: string; color: string | null }>;
|
||||||
tagIds: string[];
|
tagIds: string[];
|
||||||
isFavorite: boolean;
|
isFavorite: boolean;
|
||||||
|
|
|
||||||
|
|
@ -12,10 +12,17 @@
|
||||||
PencilSimple,
|
PencilSimple,
|
||||||
Envelope,
|
Envelope,
|
||||||
Phone,
|
Phone,
|
||||||
|
DeviceMobile,
|
||||||
Buildings,
|
Buildings,
|
||||||
|
Briefcase,
|
||||||
MapPin,
|
MapPin,
|
||||||
Cake,
|
Cake,
|
||||||
Note,
|
Note,
|
||||||
|
Globe,
|
||||||
|
GithubLogo,
|
||||||
|
LinkedinLogo,
|
||||||
|
TwitterLogo,
|
||||||
|
InstagramLogo,
|
||||||
ShareNetwork,
|
ShareNetwork,
|
||||||
} from '@manacore/shared-icons';
|
} from '@manacore/shared-icons';
|
||||||
import { ShareModal } from '@manacore/shared-uload';
|
import { ShareModal } from '@manacore/shared-uload';
|
||||||
|
|
@ -44,10 +51,20 @@
|
||||||
lastName: contact.lastName,
|
lastName: contact.lastName,
|
||||||
email: contact.email,
|
email: contact.email,
|
||||||
phone: contact.phone,
|
phone: contact.phone,
|
||||||
|
mobile: contact.mobile,
|
||||||
company: contact.company,
|
company: contact.company,
|
||||||
jobTitle: contact.jobTitle,
|
jobTitle: contact.jobTitle,
|
||||||
|
street: contact.street,
|
||||||
|
city: contact.city,
|
||||||
|
postalCode: contact.postalCode,
|
||||||
|
country: contact.country,
|
||||||
notes: contact.notes,
|
notes: contact.notes,
|
||||||
birthday: contact.birthday,
|
birthday: contact.birthday,
|
||||||
|
website: contact.website,
|
||||||
|
linkedin: contact.linkedin,
|
||||||
|
twitter: contact.twitter,
|
||||||
|
instagram: contact.instagram,
|
||||||
|
github: contact.github,
|
||||||
};
|
};
|
||||||
isEditing = true;
|
isEditing = true;
|
||||||
}
|
}
|
||||||
|
|
@ -89,6 +106,9 @@
|
||||||
: ''
|
: ''
|
||||||
);
|
);
|
||||||
let shareTitle = $derived(contact ? getDisplayName(contact) : '');
|
let shareTitle = $derived(contact ? getDisplayName(contact) : '');
|
||||||
|
|
||||||
|
const inputClass =
|
||||||
|
'w-full rounded-lg border border-border bg-background px-3 py-2 text-sm focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/20';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
|
|
@ -200,138 +220,353 @@
|
||||||
<!-- Contact Details -->
|
<!-- Contact Details -->
|
||||||
{#if isEditing}
|
{#if isEditing}
|
||||||
<!-- Edit Form -->
|
<!-- Edit Form -->
|
||||||
<div class="rounded-xl border border-border bg-card p-6">
|
<div class="space-y-4">
|
||||||
<h2 class="mb-4 text-sm font-semibold uppercase tracking-wider text-muted-foreground">
|
<!-- Name -->
|
||||||
Bearbeiten
|
<div class="rounded-xl border border-border bg-card p-5">
|
||||||
</h2>
|
<h2 class="mb-3 text-sm font-semibold uppercase tracking-wider text-muted-foreground">
|
||||||
<div class="space-y-3">
|
Name
|
||||||
|
</h2>
|
||||||
<div class="grid grid-cols-2 gap-3">
|
<div class="grid grid-cols-2 gap-3">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Vorname"
|
placeholder="Vorname"
|
||||||
value={editData.firstName ?? ''}
|
value={editData.firstName ?? ''}
|
||||||
oninput={(e) => (editData.firstName = e.currentTarget.value || null)}
|
oninput={(e) => (editData.firstName = e.currentTarget.value || null)}
|
||||||
class="rounded-lg border border-border bg-background px-3 py-2 text-sm focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/20"
|
class={inputClass}
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Nachname"
|
placeholder="Nachname"
|
||||||
value={editData.lastName ?? ''}
|
value={editData.lastName ?? ''}
|
||||||
oninput={(e) => (editData.lastName = e.currentTarget.value || null)}
|
oninput={(e) => (editData.lastName = e.currentTarget.value || null)}
|
||||||
class="rounded-lg border border-border bg-background px-3 py-2 text-sm focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/20"
|
class={inputClass}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<input
|
</div>
|
||||||
type="email"
|
|
||||||
placeholder="E-Mail"
|
<!-- Contact -->
|
||||||
value={editData.email ?? ''}
|
<div class="rounded-xl border border-border bg-card p-5">
|
||||||
oninput={(e) => (editData.email = e.currentTarget.value || null)}
|
<h2 class="mb-3 text-sm font-semibold uppercase tracking-wider text-muted-foreground">
|
||||||
class="w-full rounded-lg border border-border bg-background px-3 py-2 text-sm focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/20"
|
Kontakt
|
||||||
/>
|
</h2>
|
||||||
<input
|
<div class="space-y-3">
|
||||||
type="tel"
|
<input
|
||||||
placeholder="Telefon"
|
type="email"
|
||||||
value={editData.phone ?? ''}
|
placeholder="E-Mail"
|
||||||
oninput={(e) => (editData.phone = e.currentTarget.value || null)}
|
value={editData.email ?? ''}
|
||||||
class="w-full rounded-lg border border-border bg-background px-3 py-2 text-sm focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/20"
|
oninput={(e) => (editData.email = e.currentTarget.value || null)}
|
||||||
/>
|
class={inputClass}
|
||||||
<input
|
/>
|
||||||
type="text"
|
<div class="grid grid-cols-2 gap-3">
|
||||||
placeholder="Unternehmen"
|
<input
|
||||||
value={editData.company ?? ''}
|
type="tel"
|
||||||
oninput={(e) => (editData.company = e.currentTarget.value || null)}
|
placeholder="Mobil"
|
||||||
class="w-full rounded-lg border border-border bg-background px-3 py-2 text-sm focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/20"
|
value={editData.mobile ?? ''}
|
||||||
/>
|
oninput={(e) => (editData.mobile = e.currentTarget.value || null)}
|
||||||
<input
|
class={inputClass}
|
||||||
type="text"
|
/>
|
||||||
placeholder="Position"
|
<input
|
||||||
value={editData.jobTitle ?? ''}
|
type="tel"
|
||||||
oninput={(e) => (editData.jobTitle = e.currentTarget.value || null)}
|
placeholder="Telefon"
|
||||||
class="w-full rounded-lg border border-border bg-background px-3 py-2 text-sm focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/20"
|
value={editData.phone ?? ''}
|
||||||
/>
|
oninput={(e) => (editData.phone = e.currentTarget.value || null)}
|
||||||
|
class={inputClass}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Work -->
|
||||||
|
<div class="rounded-xl border border-border bg-card p-5">
|
||||||
|
<h2 class="mb-3 text-sm font-semibold uppercase tracking-wider text-muted-foreground">
|
||||||
|
Arbeit
|
||||||
|
</h2>
|
||||||
|
<div class="space-y-3">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Unternehmen"
|
||||||
|
value={editData.company ?? ''}
|
||||||
|
oninput={(e) => (editData.company = e.currentTarget.value || null)}
|
||||||
|
class={inputClass}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Position"
|
||||||
|
value={editData.jobTitle ?? ''}
|
||||||
|
oninput={(e) => (editData.jobTitle = e.currentTarget.value || null)}
|
||||||
|
class={inputClass}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="url"
|
||||||
|
placeholder="Website"
|
||||||
|
value={editData.website ?? ''}
|
||||||
|
oninput={(e) => (editData.website = e.currentTarget.value || null)}
|
||||||
|
class={inputClass}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Address -->
|
||||||
|
<div class="rounded-xl border border-border bg-card p-5">
|
||||||
|
<h2 class="mb-3 text-sm font-semibold uppercase tracking-wider text-muted-foreground">
|
||||||
|
Adresse
|
||||||
|
</h2>
|
||||||
|
<div class="space-y-3">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Straße & Hausnummer"
|
||||||
|
value={editData.street ?? ''}
|
||||||
|
oninput={(e) => (editData.street = e.currentTarget.value || null)}
|
||||||
|
class={inputClass}
|
||||||
|
/>
|
||||||
|
<div class="grid grid-cols-[5rem_1fr] gap-3">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="PLZ"
|
||||||
|
value={editData.postalCode ?? ''}
|
||||||
|
oninput={(e) => (editData.postalCode = e.currentTarget.value || null)}
|
||||||
|
class={inputClass}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Stadt"
|
||||||
|
value={editData.city ?? ''}
|
||||||
|
oninput={(e) => (editData.city = e.currentTarget.value || null)}
|
||||||
|
class={inputClass}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Land"
|
||||||
|
value={editData.country ?? ''}
|
||||||
|
oninput={(e) => (editData.country = e.currentTarget.value || null)}
|
||||||
|
class={inputClass}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Birthday -->
|
||||||
|
<div class="rounded-xl border border-border bg-card p-5">
|
||||||
|
<h2 class="mb-3 text-sm font-semibold uppercase tracking-wider text-muted-foreground">
|
||||||
|
Geburtstag
|
||||||
|
</h2>
|
||||||
<input
|
<input
|
||||||
type="date"
|
type="date"
|
||||||
placeholder="Geburtstag"
|
|
||||||
value={editData.birthday ?? ''}
|
value={editData.birthday ?? ''}
|
||||||
oninput={(e) => (editData.birthday = e.currentTarget.value || null)}
|
oninput={(e) => (editData.birthday = e.currentTarget.value || null)}
|
||||||
class="w-full rounded-lg border border-border bg-background px-3 py-2 text-sm focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/20"
|
class={inputClass}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Notes -->
|
||||||
|
<div class="rounded-xl border border-border bg-card p-5">
|
||||||
|
<h2 class="mb-3 text-sm font-semibold uppercase tracking-wider text-muted-foreground">
|
||||||
|
Notizen
|
||||||
|
</h2>
|
||||||
<textarea
|
<textarea
|
||||||
placeholder="Notizen"
|
placeholder="Notizen zum Kontakt..."
|
||||||
value={editData.notes ?? ''}
|
value={editData.notes ?? ''}
|
||||||
oninput={(e) => (editData.notes = e.currentTarget.value || null)}
|
oninput={(e) => (editData.notes = e.currentTarget.value || null)}
|
||||||
rows="3"
|
rows="4"
|
||||||
class="w-full rounded-lg border border-border bg-background px-3 py-2 text-sm focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/20"
|
class={inputClass}
|
||||||
></textarea>
|
></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="flex justify-end gap-2 pt-2">
|
<!-- Social Media -->
|
||||||
<button
|
<div class="rounded-xl border border-border bg-card p-5">
|
||||||
type="button"
|
<h2 class="mb-3 text-sm font-semibold uppercase tracking-wider text-muted-foreground">
|
||||||
onclick={cancelEdit}
|
Social Media
|
||||||
class="rounded-lg border border-border px-4 py-2 text-sm font-medium text-foreground hover:bg-muted"
|
</h2>
|
||||||
>
|
<div class="space-y-3">
|
||||||
Abbrechen
|
<input
|
||||||
</button>
|
type="url"
|
||||||
<button
|
placeholder="LinkedIn URL"
|
||||||
type="button"
|
value={editData.linkedin ?? ''}
|
||||||
onclick={saveEdit}
|
oninput={(e) => (editData.linkedin = e.currentTarget.value || null)}
|
||||||
class="rounded-lg bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90"
|
class={inputClass}
|
||||||
>
|
/>
|
||||||
Speichern
|
<input
|
||||||
</button>
|
type="text"
|
||||||
|
placeholder="Twitter / X"
|
||||||
|
value={editData.twitter ?? ''}
|
||||||
|
oninput={(e) => (editData.twitter = e.currentTarget.value || null)}
|
||||||
|
class={inputClass}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Instagram"
|
||||||
|
value={editData.instagram ?? ''}
|
||||||
|
oninput={(e) => (editData.instagram = e.currentTarget.value || null)}
|
||||||
|
class={inputClass}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="GitHub"
|
||||||
|
value={editData.github ?? ''}
|
||||||
|
oninput={(e) => (editData.github = e.currentTarget.value || null)}
|
||||||
|
class={inputClass}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Actions -->
|
||||||
|
<div class="flex justify-end gap-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onclick={cancelEdit}
|
||||||
|
class="rounded-lg border border-border px-4 py-2 text-sm font-medium text-foreground hover:bg-muted"
|
||||||
|
>Abbrechen</button
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onclick={saveEdit}
|
||||||
|
class="rounded-lg bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90"
|
||||||
|
>Speichern</button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
|
<!-- Quick Actions -->
|
||||||
|
{#if contact.email || contact.phone || contact.mobile}
|
||||||
|
<div class="mb-4 flex gap-2">
|
||||||
|
{#if contact.phone}
|
||||||
|
<a
|
||||||
|
href="tel:{contact.phone}"
|
||||||
|
class="flex items-center gap-1.5 rounded-lg border border-border px-3 py-2 text-sm font-medium text-foreground transition-colors hover:bg-muted"
|
||||||
|
>
|
||||||
|
<Phone size={16} /> Anrufen
|
||||||
|
</a>
|
||||||
|
{/if}
|
||||||
|
{#if contact.email}
|
||||||
|
<a
|
||||||
|
href="mailto:{contact.email}"
|
||||||
|
class="flex items-center gap-1.5 rounded-lg border border-border px-3 py-2 text-sm font-medium text-foreground transition-colors hover:bg-muted"
|
||||||
|
>
|
||||||
|
<Envelope size={16} /> E-Mail
|
||||||
|
</a>
|
||||||
|
{/if}
|
||||||
|
{#if contact.mobile}
|
||||||
|
<a
|
||||||
|
href="sms:{contact.mobile}"
|
||||||
|
class="flex items-center gap-1.5 rounded-lg border border-border px-3 py-2 text-sm font-medium text-foreground transition-colors hover:bg-muted"
|
||||||
|
>
|
||||||
|
<DeviceMobile size={16} /> SMS
|
||||||
|
</a>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<!-- Detail Cards -->
|
<!-- Detail Cards -->
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<!-- Contact Info -->
|
<!-- Contact Info -->
|
||||||
<div class="rounded-xl border border-border bg-card p-5">
|
<div class="rounded-xl border border-border bg-card p-5">
|
||||||
<h2 class="mb-3 text-sm font-semibold uppercase tracking-wider text-muted-foreground">
|
<h2 class="mb-3 text-sm font-semibold uppercase tracking-wider text-muted-foreground">
|
||||||
Kontaktdaten
|
Kontakt
|
||||||
</h2>
|
</h2>
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
{#if contact.email}
|
{#if contact.email}
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<Envelope size={16} class="flex-shrink-0 text-muted-foreground" />
|
<Envelope size={16} class="flex-shrink-0 text-muted-foreground" />
|
||||||
<a href="mailto:{contact.email}" class="text-sm text-primary hover:underline">
|
<a href="mailto:{contact.email}" class="text-sm text-primary hover:underline"
|
||||||
{contact.email}
|
>{contact.email}</a
|
||||||
</a>
|
>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{#if contact.mobile}
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<DeviceMobile size={16} class="flex-shrink-0 text-muted-foreground" />
|
||||||
|
<a href="tel:{contact.mobile}" class="text-sm text-primary hover:underline"
|
||||||
|
>{contact.mobile}</a
|
||||||
|
>
|
||||||
|
<span class="text-xs text-muted-foreground">Mobil</span>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if contact.phone}
|
{#if contact.phone}
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<Phone size={16} class="flex-shrink-0 text-muted-foreground" />
|
<Phone size={16} class="flex-shrink-0 text-muted-foreground" />
|
||||||
<a href="tel:{contact.phone}" class="text-sm text-primary hover:underline">
|
<a href="tel:{contact.phone}" class="text-sm text-primary hover:underline"
|
||||||
{contact.phone}
|
>{contact.phone}</a
|
||||||
</a>
|
>
|
||||||
</div>
|
<span class="text-xs text-muted-foreground">Telefon</span>
|
||||||
{/if}
|
|
||||||
{#if contact.company}
|
|
||||||
<div class="flex items-center gap-3">
|
|
||||||
<Buildings size={16} class="flex-shrink-0 text-muted-foreground" />
|
|
||||||
<span class="text-sm text-foreground">{contact.company}</span>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{#if contact.birthday}
|
|
||||||
<div class="flex items-center gap-3">
|
|
||||||
<Cake size={16} class="flex-shrink-0 text-muted-foreground" />
|
|
||||||
<span class="text-sm text-foreground">
|
|
||||||
{new Date(contact.birthday).toLocaleDateString('de-DE', {
|
|
||||||
day: 'numeric',
|
|
||||||
month: 'long',
|
|
||||||
year: 'numeric',
|
|
||||||
})}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
{#if !contact.email && !contact.phone && !contact.mobile}
|
||||||
{#if !contact.email && !contact.phone && !contact.company && !contact.birthday}
|
|
||||||
<p class="text-sm text-muted-foreground">Keine Kontaktdaten hinterlegt.</p>
|
<p class="text-sm text-muted-foreground">Keine Kontaktdaten hinterlegt.</p>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Work -->
|
||||||
|
{#if contact.company || contact.jobTitle || contact.website}
|
||||||
|
<div class="rounded-xl border border-border bg-card p-5">
|
||||||
|
<h2 class="mb-3 text-sm font-semibold uppercase tracking-wider text-muted-foreground">
|
||||||
|
Arbeit
|
||||||
|
</h2>
|
||||||
|
<div class="space-y-3">
|
||||||
|
{#if contact.company}
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<Buildings size={16} class="flex-shrink-0 text-muted-foreground" />
|
||||||
|
<span class="text-sm text-foreground">{contact.company}</span>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{#if contact.jobTitle}
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<Briefcase size={16} class="flex-shrink-0 text-muted-foreground" />
|
||||||
|
<span class="text-sm text-foreground">{contact.jobTitle}</span>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{#if contact.website}
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<Globe size={16} class="flex-shrink-0 text-muted-foreground" />
|
||||||
|
<a
|
||||||
|
href={contact.website.startsWith('http')
|
||||||
|
? contact.website
|
||||||
|
: `https://${contact.website}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
class="text-sm text-primary hover:underline">{contact.website}</a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<!-- Address -->
|
||||||
|
{#if contact.street || contact.city || contact.postalCode || contact.country}
|
||||||
|
<div class="rounded-xl border border-border bg-card p-5">
|
||||||
|
<h2 class="mb-3 text-sm font-semibold uppercase tracking-wider text-muted-foreground">
|
||||||
|
Adresse
|
||||||
|
</h2>
|
||||||
|
<div class="flex items-start gap-3">
|
||||||
|
<MapPin size={16} class="mt-0.5 flex-shrink-0 text-muted-foreground" />
|
||||||
|
<div class="text-sm text-foreground">
|
||||||
|
{#if contact.street}<div>{contact.street}</div>{/if}
|
||||||
|
{#if contact.postalCode || contact.city}
|
||||||
|
<div>{[contact.postalCode, contact.city].filter(Boolean).join(' ')}</div>
|
||||||
|
{/if}
|
||||||
|
{#if contact.country}<div>{contact.country}</div>{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<!-- Birthday -->
|
||||||
|
{#if contact.birthday}
|
||||||
|
<div class="rounded-xl border border-border bg-card p-5">
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<Cake size={16} class="flex-shrink-0 text-muted-foreground" />
|
||||||
|
<span class="text-sm text-foreground">
|
||||||
|
{new Date(contact.birthday).toLocaleDateString('de-DE', {
|
||||||
|
day: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
year: 'numeric',
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<!-- Notes -->
|
<!-- Notes -->
|
||||||
{#if contact.notes}
|
{#if contact.notes}
|
||||||
<div class="rounded-xl border border-border bg-card p-5">
|
<div class="rounded-xl border border-border bg-card p-5">
|
||||||
|
|
@ -342,6 +577,69 @@
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<!-- Social Media -->
|
||||||
|
{#if contact.linkedin || contact.twitter || contact.instagram || contact.github}
|
||||||
|
<div class="rounded-xl border border-border bg-card p-5">
|
||||||
|
<h2 class="mb-3 text-sm font-semibold uppercase tracking-wider text-muted-foreground">
|
||||||
|
Social Media
|
||||||
|
</h2>
|
||||||
|
<div class="space-y-3">
|
||||||
|
{#if contact.linkedin}
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<LinkedinLogo size={16} class="flex-shrink-0 text-muted-foreground" />
|
||||||
|
<a
|
||||||
|
href={contact.linkedin.startsWith('http')
|
||||||
|
? contact.linkedin
|
||||||
|
: `https://linkedin.com/in/${contact.linkedin}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
class="text-sm text-primary hover:underline">{contact.linkedin}</a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{#if contact.twitter}
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<TwitterLogo size={16} class="flex-shrink-0 text-muted-foreground" />
|
||||||
|
<a
|
||||||
|
href={contact.twitter.startsWith('http')
|
||||||
|
? contact.twitter
|
||||||
|
: `https://x.com/${contact.twitter}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
class="text-sm text-primary hover:underline">{contact.twitter}</a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{#if contact.instagram}
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<InstagramLogo size={16} class="flex-shrink-0 text-muted-foreground" />
|
||||||
|
<a
|
||||||
|
href={contact.instagram.startsWith('http')
|
||||||
|
? contact.instagram
|
||||||
|
: `https://instagram.com/${contact.instagram}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
class="text-sm text-primary hover:underline">{contact.instagram}</a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{#if contact.github}
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<GithubLogo size={16} class="flex-shrink-0 text-muted-foreground" />
|
||||||
|
<a
|
||||||
|
href={contact.github.startsWith('http')
|
||||||
|
? contact.github
|
||||||
|
: `https://github.com/${contact.github}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
class="text-sm text-primary hover:underline">{contact.github}</a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<!-- Tags -->
|
<!-- Tags -->
|
||||||
{#if contact.tags.length > 0}
|
{#if contact.tags.length > 0}
|
||||||
<div class="rounded-xl border border-border bg-card p-5">
|
<div class="rounded-xl border border-border bg-card p-5">
|
||||||
|
|
@ -350,9 +648,9 @@
|
||||||
</h2>
|
</h2>
|
||||||
<div class="flex flex-wrap gap-2">
|
<div class="flex flex-wrap gap-2">
|
||||||
{#each contact.tags as tag (tag.id)}
|
{#each contact.tags as tag (tag.id)}
|
||||||
<span class="rounded-full bg-primary/10 px-3 py-1 text-xs font-medium text-primary">
|
<span class="rounded-full bg-primary/10 px-3 py-1 text-xs font-medium text-primary"
|
||||||
{tag.name}
|
>{tag.name}</span
|
||||||
</span>
|
>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -360,26 +658,23 @@
|
||||||
|
|
||||||
<!-- Metadata -->
|
<!-- Metadata -->
|
||||||
<div class="rounded-xl border border-border bg-card p-5">
|
<div class="rounded-xl border border-border bg-card p-5">
|
||||||
<h2 class="mb-3 text-sm font-semibold uppercase tracking-wider text-muted-foreground">
|
|
||||||
Details
|
|
||||||
</h2>
|
|
||||||
<div class="grid grid-cols-2 gap-y-2 text-xs text-muted-foreground">
|
<div class="grid grid-cols-2 gap-y-2 text-xs text-muted-foreground">
|
||||||
<span>Erstellt</span>
|
<span>Erstellt</span>
|
||||||
<span>
|
<span
|
||||||
{new Date(contact.createdAt).toLocaleDateString('de-DE', {
|
>{new Date(contact.createdAt).toLocaleDateString('de-DE', {
|
||||||
day: 'numeric',
|
day: 'numeric',
|
||||||
month: 'short',
|
month: 'short',
|
||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
})}
|
})}</span
|
||||||
</span>
|
>
|
||||||
<span>Aktualisiert</span>
|
<span>Aktualisiert</span>
|
||||||
<span>
|
<span
|
||||||
{new Date(contact.updatedAt).toLocaleDateString('de-DE', {
|
>{new Date(contact.updatedAt).toLocaleDateString('de-DE', {
|
||||||
day: 'numeric',
|
day: 'numeric',
|
||||||
month: 'short',
|
month: 'short',
|
||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
})}
|
})}</span
|
||||||
</span>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue