diff --git a/ka-note/VERSION b/ka-note/VERSION index 20d2c5a..bfb7fa3 100644 --- a/ka-note/VERSION +++ b/ka-note/VERSION @@ -1 +1 @@ -1.0.52 \ No newline at end of file +1.0.54 \ No newline at end of file diff --git a/ka-note/client/src/lib/components/ContextHeader.svelte b/ka-note/client/src/lib/components/ContextHeader.svelte index ffb4b5b..fed0b4e 100644 --- a/ka-note/client/src/lib/components/ContextHeader.svelte +++ b/ka-note/client/src/lib/components/ContextHeader.svelte @@ -6,10 +6,13 @@ context: AgendaContext; mode?: 'prep' | 'meeting'; onmodechange?: (mode: 'prep' | 'meeting') => void; + journalScope?: 'business' | 'private'; + onjournalscopechange?: (scope: 'business' | 'private') => void; } - let { context, mode, onmodechange }: Props = $props(); + let { context, mode, onmodechange, journalScope, onjournalscopechange }: Props = $props(); const showModeSwitch = $derived(!!onmodechange && context.type === 'meeting' && context.id !== 'daily-log'); + const showScopeSwitch = $derived(!!onjournalscopechange && context.id === 'daily-log'); const canRename = $derived(context.id !== 'daily-log'); let editing = $state(false); let nameInput = $state(''); @@ -83,4 +86,16 @@ {/if} + {#if showScopeSwitch} +
+ + +
+ {/if} diff --git a/ka-note/client/src/lib/components/ContextPage.svelte b/ka-note/client/src/lib/components/ContextPage.svelte index 3545152..9d09baf 100644 --- a/ka-note/client/src/lib/components/ContextPage.svelte +++ b/ka-note/client/src/lib/components/ContextPage.svelte @@ -24,6 +24,15 @@ let mode = $state<'prep' | 'meeting'>(contextId === 'daily-log' ? 'meeting' : 'prep'); let activeView = $state('journal'); + const SCOPE_KEY = 'journal-scope'; + let journalScope = $state<'business' | 'private'>( + (typeof localStorage !== 'undefined' ? localStorage.getItem(SCOPE_KEY) : null) === 'private' + ? 'private' : 'business' + ); + function handleScopeChange(s: 'business' | 'private') { + journalScope = s; + localStorage.setItem(SCOPE_KEY, s); + } let compact = $state(false); // Rating modal state @@ -84,7 +93,7 @@
{#if $context} {#if isDailyLog} - + {:else} @@ -94,7 +103,7 @@ {#if activeView === 'agenda'} {:else if activeView === 'journal'} - + {:else if activeView === 'persons'} {:else if activeView === 'snoozed'} diff --git a/ka-note/client/src/lib/components/DashboardView.svelte b/ka-note/client/src/lib/components/DashboardView.svelte index a861af7..6dbd687 100644 --- a/ka-note/client/src/lib/components/DashboardView.svelte +++ b/ka-note/client/src/lib/components/DashboardView.svelte @@ -221,14 +221,36 @@ upsertContext({ id: context.id, meta: meta as any }); } + function calcAge(birthday: string | undefined): string { + if (!birthday) return ''; + const b = new Date(birthday); + if (isNaN(b.getTime())) return ''; + const now = new Date(); + let age = now.getFullYear() - b.getFullYear(); + const md = now.getMonth() - b.getMonth(); + if (md < 0 || (md === 0 && now.getDate() < b.getDate())) age--; + return age >= 0 ? `${age} J.` : ''; + } + + function calcTenure(joinDate: string | undefined): string { + if (!joinDate) return ''; + const j = new Date(joinDate); + if (isNaN(j.getTime())) return ''; + const now = new Date(); + let years = now.getFullYear() - j.getFullYear(); + const md = now.getMonth() - j.getMonth(); + if (md < 0 || (md === 0 && now.getDate() < j.getDate())) years--; + return years >= 0 ? `${years} Jahr${years !== 1 ? 'e' : ''}` : ''; + } + {#if isPerson} - {@const subTypeColors = { contact: 'border-[#555] text-[#ccc]', employee: 'border-accent text-accent', colleague: 'border-[#00b894] text-[#00b894]' } as Record} -
+ {@const subTypeColors = { contact: 'border-[#555] text-[#ccc]', employee: 'border-accent text-accent', colleague: 'border-[#00b894] text-[#00b894]', family: 'border-[#e84393] text-[#e84393]', acquaintance: 'border-[#a29bfe] text-[#a29bfe]' } as Record} +
Typ: - {#each [['contact', 'Kontakt'], ['employee', 'Mitarbeiter'], ['colleague', 'Kollege']] as [value, label]} + {#each [['contact', 'Kontakt'], ['employee', 'Mitarbeiter'], ['colleague', 'Kollege'], ['family', 'Familie'], ['acquaintance', 'Bekannte']] as [value, label]}
{:else} {@const meta = (context.meta ?? { fullName: '', email: '', phone: '', duSince: '' }) as PersonMeta} + {@const age = calcAge(meta.birthday)} + {@const tenure = calcTenure(meta.joinDate)} + {@const showJoinDate = personSubType === 'employee' || personSubType === 'colleague'}
updateMeta('fullName', e.currentTarget.value)} />
-
- - updateMeta('email', e.currentTarget.value)} /> -
-
- - updateMeta('phone', e.currentTarget.value)} /> -
-
- - updateMeta('duSince', e.currentTarget.value)} /> +
+
+ + updateMeta('email', e.currentTarget.value)} /> +
+
+ + updateMeta('phone', e.currentTarget.value)} /> +
+
+ + updateMeta('birthday', e.currentTarget.value)} /> +
+
+ + updateMeta('duSince', e.currentTarget.value)} /> +
+ {#if showJoinDate} +
+ + updateMeta('joinDate', e.currentTarget.value)} /> +
+ {/if}
diff --git a/ka-note/client/src/lib/components/JournalView.svelte b/ka-note/client/src/lib/components/JournalView.svelte index 249ee92..8feb944 100644 --- a/ka-note/client/src/lib/components/JournalView.svelte +++ b/ka-note/client/src/lib/components/JournalView.svelte @@ -13,8 +13,9 @@ interface Props { contextId: string; + journalScope?: 'business' | 'private'; } - let { contextId }: Props = $props(); + let { contextId, journalScope = 'business' }: Props = $props(); const isDailyLog = $derived(contextId === 'daily-log'); @@ -45,10 +46,10 @@ .toArray(); }); - // Filter journal entries by selected date + // Filter journal entries by selected date and scope const filteredEntries = $derived( ($journalEntries ?? []) - .filter(e => e.date === selectedDate) + .filter(e => e.date === selectedDate && (journalScope === 'private' ? !!e.isPrivate : !e.isPrivate)) .sort((a, b) => b.sortOrder - a.sortOrder) ); @@ -86,15 +87,16 @@ } } + const isPrivate = journalScope === 'private'; if (selectedLinkedContextId) { const topic = await createTopic(selectedLinkedContextId, title); if (body) { - await createHistoryEntry(topic.id, selectedDate, body); + await createHistoryEntry(topic.id, selectedDate, body, null, false, isPrivate); } } else { const text = body ? `${title}\n${body}` : title; await getOrCreateJournalTopic(); - await createHistoryEntry(JOURNAL_TOPIC_ID, selectedDate, text, null, wiedervorlageChecked); + await createHistoryEntry(JOURNAL_TOPIC_ID, selectedDate, text, null, wiedervorlageChecked, isPrivate); } entryTitle = ''; @@ -186,12 +188,40 @@ } return [...groups.entries()].sort(([a], [b]) => b.localeCompare(a)); }); + + // Birthday banner — filtered by scope + const allPersons = liveQuery(() => + db.contexts.filter(c => !c.deletedAt && c.type === 'person').toArray() + ); + const BUSINESS_SUBTYPES = new Set(['employee', 'colleague']); + const PRIVATE_SUBTYPES = new Set(['family', 'acquaintance']); + const birthdayPersons = $derived( + ($allPersons ?? []).filter(p => { + const meta = p.meta as { birthday?: string; personSubType?: string } | null; + const bd = meta?.birthday; + if (!bd || bd.slice(5) !== selectedDate.slice(5)) return false; + const sub = meta?.personSubType; + if (journalScope === 'private') return !!sub && PRIVATE_SUBTYPES.has(sub); + // business: employee/colleague/contact/undefined + return !sub || !PRIVATE_SUBTYPES.has(sub); + }) + ); {#if isDailyLog} selectedDate = d} /> + {#if birthdayPersons.length > 0} +
+ 🎂 + Geburtstag heute: + {#each birthdayPersons as p} + {p.name} + {/each} +
+ {/if} +
- + {#if filteredEntries.length > 0}
diff --git a/ka-note/client/src/lib/components/WiedervorlageSection.svelte b/ka-note/client/src/lib/components/WiedervorlageSection.svelte index c45a0e6..a631aa6 100644 --- a/ka-note/client/src/lib/components/WiedervorlageSection.svelte +++ b/ka-note/client/src/lib/components/WiedervorlageSection.svelte @@ -4,18 +4,24 @@ interface Props { date: string; + journalScope?: 'business' | 'private'; } - let { date }: Props = $props(); + let { date, journalScope = 'business' }: Props = $props(); - const pending = $derived(pendingWiedervorlage(date)); + const allPending = $derived(pendingWiedervorlage(date)); + const pending = $derived( + ($allPending ?? []).filter(e => + journalScope === 'private' ? !!e.isPrivate : !e.isPrivate + ) + ); -{#if ($pending ?? []).length > 0} +{#if pending.length > 0}
- Wiedervorlage ({($pending ?? []).length}) + Wiedervorlage ({pending.length})
- {#each $pending ?? [] as entry (entry.id)} + {#each pending as entry (entry.id)} {/each}
diff --git a/ka-note/client/src/lib/db/repositories.ts b/ka-note/client/src/lib/db/repositories.ts index 032e179..6960a47 100644 --- a/ka-note/client/src/lib/db/repositories.ts +++ b/ka-note/client/src/lib/db/repositories.ts @@ -211,7 +211,7 @@ export async function getAllHistoryByContext(contextId: string): Promise<(Histor return allHistory; } -export async function createHistoryEntry(topicId: string, date: string, text: string, linkedContextId: string | null = null, wiedervorlage = false): Promise { +export async function createHistoryEntry(topicId: string, date: string, text: string, linkedContextId: string | null = null, wiedervorlage = false, isPrivate = false): Promise { const existing = await getHistoryByTopic(topicId); const autoWiedervorlage = date > today() || wiedervorlage; const entry: HistoryEntry = { @@ -226,7 +226,8 @@ export async function createHistoryEntry(topicId: string, date: string, text: st wiedervorlageResolvedAt: null, updatedAt: now(), deletedAt: null, - version: 1 + version: 1, + ...(isPrivate ? { isPrivate: true } : {}) }; await db.historyEntries.put(entry); return entry; diff --git a/ka-note/shared/src/types.ts b/ka-note/shared/src/types.ts index 26efcbc..4fc525a 100644 --- a/ka-note/shared/src/types.ts +++ b/ka-note/shared/src/types.ts @@ -8,7 +8,7 @@ export interface SyncEntity { export type ContextType = 'meeting' | 'project' | 'person' | 'company'; -export type PersonSubType = 'contact' | 'employee' | 'colleague'; +export type PersonSubType = 'contact' | 'employee' | 'colleague' | 'family' | 'acquaintance'; export interface AgendaContext extends SyncEntity { name: string; @@ -30,6 +30,8 @@ export interface PersonMeta { email: string; phone: string; duSince: string; + birthday?: string; // YYYY-MM-DD + joinDate?: string; // YYYY-MM-DD (employee/colleague) personSubType?: PersonSubType; notes?: string; } @@ -60,6 +62,7 @@ export interface HistoryEntry extends SyncEntity { doneAt: string | null; wiedervorlageDate: string | null; // YYYY-MM-DD wiedervorlageResolvedAt: string | null; // ISO timestamp + isPrivate?: boolean; // undefined/false = business (default) } export interface Rating extends SyncEntity {