This commit is contained in:
beo3000 2026-02-27 21:19:43 +01:00
parent e8800dc88c
commit 5e188e2f94
7 changed files with 45 additions and 20 deletions

View File

@ -1 +1 @@
1.1.89
1.1.92

View File

@ -172,7 +172,7 @@
const allHistory = await db.historyEntries.filter(h => !h.deletedAt).toArray();
const allContexts = await db.contexts.filter(c => !c.deletedAt).toArray();
const results: { topicId: string; historyEntryId: string; topicTitle: string; contextName: string; date: string; text: string }[] = [];
const results: { topicId: string; historyEntryId: string; topicTitle: string; contextId: string; contextName: string; date: string; text: string }[] = [];
for (const h of allHistory) {
const topic = allTopics.find(t => t.id === h.topicId);
@ -194,6 +194,7 @@
topicId: topic.id,
historyEntryId: h.id,
topicTitle: topic.title,
contextId: ctx?.id ?? '',
contextName: ctx?.name ?? 'Unknown',
date: h.date,
text: h.text
@ -531,14 +532,23 @@
</div>
{#each $activityLog ?? [] as entry}
<div class="mb-2.5 border-l-2 border-[#444] pl-2.5" data-topic-id={entry.topicId} data-history-entry-id={entry.historyEntryId}>
<div class="mb-0.5 text-xs text-[#aaa]">
{entry.date} in <span class="text-accent">{entry.contextName}</span>: {entry.topicTitle}
<div class="mb-2.5 border-l-2 border-[#444] pl-2.5">
<div class="mb-0.5 flex items-baseline gap-1 text-xs text-[#aaa]">
<a
href="/context/daily-log?date={entry.date}"
class="font-mono text-[#888] hover:text-white underline-offset-2 hover:underline"
>{entry.date}</a>
<span>in</span>
<a
href="/context/{entry.contextId}"
class="text-accent hover:brightness-125 underline-offset-2 hover:underline"
>{entry.contextName}</a>:
<span>{entry.topicTitle}</span>
</div>
<RenderedMarkdown text={entry.text} />
</div>
{:else}
<div class="text-muted">No entries found.</div>
<div class="text-muted">Keine Einträge gefunden.</div>
{/each}
</div>

View File

@ -40,7 +40,7 @@
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} Mo.`;
if (years > 0 && months > 0) return `${years}J ${months}M`;
if (years > 0) return `${years} J.`;
return `${months} Mo.`;
}
@ -66,6 +66,7 @@
{@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">
<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>
@ -77,19 +78,23 @@
title={ctx.isFavorite ? 'Aus Sidebar entfernen' : 'In Sidebar anzeigen'}
>&#9733;</button>
<button
class="flex flex-1 items-center justify-between rounded-lg border border-[#444] bg-sidebar px-4 py-3 text-left transition-colors hover:border-[#666] hover:bg-[#2a2a2a]"
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}`)}
>
<span class="font-bold text-white">{displayName(ctx.name)}</span>
<span class="flex items-center gap-2">
{#if showBirthday && meta?.birthday}
<span class="text-xs text-muted">🎂 {formatDate(meta.birthday)}</span>
{/if}
{#if showTenure && meta?.joinDate}
<span class="text-xs text-muted" title="seit {formatDate(meta.joinDate)}">{tenure(meta.joinDate)}</span>
{/if}
<span class="rounded px-2 py-0.5 text-xs {subTypeColors[subType]}">{subTypeLabels[subType]}</span>
</span>
<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"

View File

@ -77,7 +77,7 @@
{#if createError}<p class="mb-3 -mt-2 text-xs text-red-400">{createError}</p>{/if}
{/if}
<div class="mb-4 flex flex-wrap gap-1 border-b border-[#333] pb-2">
<div class="mb-4 flex gap-1 overflow-x-auto border-b border-[#333] pb-2 scrollbar-none">
{#each tabs as tab}
{@const count = tab.key === 'all' ? persons.length : persons.filter(c => ((c.meta as PersonMeta | null)?.personSubType ?? 'contact') === tab.key).length}
{#if count > 0 || tab.key === 'all'}

Binary file not shown.

Binary file not shown.

View File

@ -126,6 +126,16 @@ export async function runIfMissed(): Promise<void> {
}
}
function parseCreatedAt(filename: string): string {
// filename: backup-{userId}-YYYY-MM-DDTHH-MM-SS.zip
const base = filename.slice(0, -4); // strip .zip
const ts = base.slice(base.lastIndexOf('-', base.length - 16) + 1);
// ts: "2026-02-27T14-30-45" → restore colons in time part
const iso = ts.replace(/T(\d{2})-(\d{2})-(\d{2})$/, 'T$1:$2:$3') + 'Z';
const d = new Date(iso);
return isNaN(d.getTime()) ? new Date(0).toISOString() : d.toISOString();
}
export async function listBackups(): Promise<BackupEntry[]> {
await ensureBackupDir();
const entries = await fs.readdir(BACKUP_DIR);
@ -133,7 +143,7 @@ export async function listBackups(): Promise<BackupEntry[]> {
for (const name of entries) {
if (!name.endsWith('.zip')) continue;
const stat = await fs.stat(path.join(BACKUP_DIR, name));
result.push({ filename: name, size: stat.size, createdAt: stat.mtime.toISOString() });
result.push({ filename: name, size: stat.size, createdAt: parseCreatedAt(name) });
}
return result.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
}