121 lines
4.4 KiB
Svelte
121 lines
4.4 KiB
Svelte
<script lang="ts">
|
|
import { goto } from '$app/navigation';
|
|
import { softDeleteContext, toggleFavorite, reorderContext } from '$lib/db/repositories';
|
|
import ConfirmDialog from '$lib/components/ConfirmDialog.svelte';
|
|
import type { AgendaContext, PersonMeta, PersonSubType } from '@ka-note/shared';
|
|
|
|
interface Props {
|
|
persons: AgendaContext[];
|
|
sortable?: boolean;
|
|
}
|
|
let { persons, sortable = false }: Props = $props();
|
|
|
|
function displayName(name: string): string {
|
|
return name.replace('Person ', '');
|
|
}
|
|
|
|
const subTypeLabels: Record<PersonSubType, string> = {
|
|
contact: 'Kontakt',
|
|
employee: 'Mitarbeiter',
|
|
colleague: 'Kollege',
|
|
family: 'Familie',
|
|
acquaintance: 'Bekannte/r'
|
|
};
|
|
|
|
const subTypeColors: Record<PersonSubType, string> = {
|
|
contact: 'bg-[#555] text-[#ccc]',
|
|
employee: 'bg-accent/30 text-accent',
|
|
colleague: 'bg-[#00b894]/30 text-[#00b894]',
|
|
family: 'bg-pink-900/40 text-pink-300',
|
|
acquaintance: 'bg-purple-900/40 text-purple-300'
|
|
};
|
|
|
|
function formatDate(iso: string): string {
|
|
const [y, m, d] = iso.split('-');
|
|
return `${d}.${m}.${y}`;
|
|
}
|
|
|
|
function tenure(joinDate: string): string {
|
|
const start = new Date(joinDate);
|
|
const now = new Date();
|
|
let years = now.getFullYear() - start.getFullYear();
|
|
let months = now.getMonth() - start.getMonth();
|
|
if (months < 0) { years--; months += 12; }
|
|
if (years > 0 && months > 0) return `${years}J ${months}M`;
|
|
if (years > 0) return `${years} J.`;
|
|
return `${months} Mo.`;
|
|
}
|
|
|
|
let confirmDeleteId = $state<string | null>(null);
|
|
let confirmDeleteName = $state('');
|
|
|
|
function requestDelete(id: string, name: string) {
|
|
confirmDeleteId = id;
|
|
confirmDeleteName = name;
|
|
}
|
|
|
|
async function handleDelete() {
|
|
if (confirmDeleteId) await softDeleteContext(confirmDeleteId);
|
|
confirmDeleteId = null;
|
|
}
|
|
</script>
|
|
|
|
{#if persons.length > 0}
|
|
<div class="flex flex-col gap-2">
|
|
{#each persons as ctx, i (ctx.id)}
|
|
{@const meta = ctx.meta as PersonMeta | null}
|
|
{@const subType = (meta?.personSubType ?? 'contact') as PersonSubType}
|
|
{@const showBirthday = subType === 'family' || subType === 'employee' || subType === 'colleague'}
|
|
{@const showTenure = subType === 'employee' && !!meta?.joinDate}
|
|
{@const hasInfo = (showBirthday && !!meta?.birthday) || showTenure}
|
|
<div class="flex items-center gap-2">
|
|
{#if sortable}
|
|
<div class="flex flex-col">
|
|
<button class="text-[#555] hover:text-[#aaa] disabled:opacity-20 leading-none px-1" onclick={() => reorderContext(ctx.id, 'up')} disabled={i === 0} title="Nach oben">▲</button>
|
|
<button class="text-[#555] hover:text-[#aaa] disabled:opacity-20 leading-none px-1" onclick={() => reorderContext(ctx.id, 'down')} disabled={i === persons.length - 1} title="Nach unten">▼</button>
|
|
</div>
|
|
{/if}
|
|
<button
|
|
class="rounded border border-[#444] bg-sidebar px-2.5 py-2.5 transition-colors hover:border-[#666] {ctx.isFavorite ? 'text-yellow-400' : 'text-[#555] hover:text-yellow-400/60'}"
|
|
onclick={() => toggleFavorite(ctx.id)}
|
|
title={ctx.isFavorite ? 'Aus Sidebar entfernen' : 'In Sidebar anzeigen'}
|
|
>★</button>
|
|
<button
|
|
class="flex flex-1 flex-col rounded-lg border border-[#444] bg-sidebar px-4 py-3 text-left transition-colors hover:border-[#666] hover:bg-[#2a2a2a]"
|
|
onclick={() => goto(`/context/${ctx.id}`)}
|
|
>
|
|
<div class="flex w-full items-center justify-between gap-2">
|
|
<span class="font-bold text-white">{displayName(ctx.name)}</span>
|
|
<span class="shrink-0 rounded px-2 py-0.5 text-xs {subTypeColors[subType]}">{subTypeLabels[subType]}</span>
|
|
</div>
|
|
{#if hasInfo}
|
|
<div class="mt-1 flex items-center gap-3 text-xs text-muted">
|
|
{#if showBirthday && meta?.birthday}
|
|
<span>🎂 {formatDate(meta.birthday)}</span>
|
|
{/if}
|
|
{#if showTenure && meta?.joinDate}
|
|
<span title="seit {formatDate(meta.joinDate)}">⏱ {tenure(meta.joinDate)}</span>
|
|
{/if}
|
|
</div>
|
|
{/if}
|
|
</button>
|
|
<button
|
|
class="rounded border border-[#444] bg-sidebar px-2.5 py-2.5 text-[#888] transition-colors hover:border-red-900 hover:text-red-400"
|
|
onclick={() => requestDelete(ctx.id, displayName(ctx.name))}
|
|
title="Löschen"
|
|
>🗑</button>
|
|
</div>
|
|
{/each}
|
|
</div>
|
|
{:else}
|
|
<div class="text-muted">Keine Personen vorhanden.</div>
|
|
{/if}
|
|
|
|
{#if confirmDeleteId}
|
|
<ConfirmDialog
|
|
message={`Person \u201E${confirmDeleteName}\u201C wirklich löschen?`}
|
|
onconfirm={handleDelete}
|
|
oncancel={() => confirmDeleteId = null}
|
|
/>
|
|
{/if}
|