added wiedervorlage

This commit is contained in:
beo3000 2026-02-21 08:28:28 +01:00
parent ed9597c2dd
commit 1da4050813
8 changed files with 203 additions and 9 deletions

View File

@ -8,6 +8,7 @@
import RenderedMarkdown from './RenderedMarkdown.svelte';
import DateNavigator from './DateNavigator.svelte';
import ConfirmDialog from './ConfirmDialog.svelte';
import WiedervorlageSection from './WiedervorlageSection.svelte';
interface Props {
contextId: string;
@ -22,6 +23,7 @@
let entryEditor: MarkdownEditor;
let selectedDate = $state(today());
let selectedLinkedContextId = $state('');
let wiedervorlageChecked = $state(false);
// All meeting contexts for the link dropdown
const meetingContexts = liveQuery(() =>
@ -70,13 +72,14 @@
} else {
const text = body ? `${title}\n${body}` : title;
await getOrCreateJournalTopic();
await createHistoryEntry(JOURNAL_TOPIC_ID, selectedDate, text);
await createHistoryEntry(JOURNAL_TOPIC_ID, selectedDate, text, null, wiedervorlageChecked);
}
entryTitle = '';
entryText = '';
entryEditor?.clear();
selectedLinkedContextId = '';
wiedervorlageChecked = false;
}
function handleTitleKeypress(e: KeyboardEvent) {
@ -192,14 +195,24 @@
minHeight="60px"
onchange={(md) => entryText = md}
/>
<button
class="self-start rounded bg-accent px-4 py-2 font-bold text-white hover:brightness-110"
onclick={handleAddEntry}
>
+ {selectedLinkedContextId ? 'Thema hinzufügen' : 'Notiz hinzufügen'}
</button>
<div class="flex items-center gap-4">
<button
class="rounded bg-accent px-4 py-2 font-bold text-white hover:brightness-110"
onclick={handleAddEntry}
>
+ {selectedLinkedContextId ? 'Thema hinzufügen' : 'Notiz hinzufügen'}
</button>
{#if !selectedLinkedContextId}
<label class="flex cursor-pointer items-center gap-1.5 text-sm text-muted">
<input type="checkbox" bind:checked={wiedervorlageChecked} class="accent-amber-500" />
Wiedervorlage
</label>
{/if}
</div>
</div>
<WiedervorlageSection date={selectedDate} />
{#if filteredEntries.length > 0}
<div class="mb-8 border-l-2 border-[#555] pl-5">
<div class="mb-4 text-xl font-bold text-accent">{selectedDate}</div>

View File

@ -0,0 +1,110 @@
<script lang="ts">
import { liveQuery } from 'dexie';
import { db } from '$lib/db/schema';
import { resolveWiedervorlage, setWiedervorlage, convertToTopic } from '$lib/db/repositories';
import type { HistoryEntry } from '@ka-note/shared';
interface Props {
entry: HistoryEntry;
}
let { entry }: Props = $props();
let showVerschieben = $state(false);
let newDate = $state('');
let showConvert = $state(false);
let selectedContextId = $state('');
const meetingContexts = liveQuery(() =>
db.contexts
.filter(c => !c.deletedAt && c.type === 'meeting' && c.id !== 'daily-log')
.sortBy('sortOrder')
);
const lines = $derived(entry.text.split('\n'));
const title = $derived(lines[0]);
const body = $derived(lines.slice(1).join('\n').trim());
async function handleOk() {
await resolveWiedervorlage(entry.id);
}
async function handleVerschieben() {
if (!newDate) return;
await setWiedervorlage(entry.id, newDate);
showVerschieben = false;
newDate = '';
}
async function handleConvert() {
if (!selectedContextId) return;
await convertToTopic(entry.id, selectedContextId);
showConvert = false;
selectedContextId = '';
}
</script>
<div class="mb-3 rounded-lg border border-amber-500/60 bg-amber-950/30 p-3">
<div class="mb-2 flex items-start gap-2">
<span class="text-xs font-semibold uppercase tracking-wider text-amber-400">⏰ Wiedervorlage {entry.wiedervorlageDate}</span>
</div>
<div class="mb-3">
<div class="font-bold text-white">{title}</div>
{#if body}
<div class="mt-1 text-sm text-[#ccc]">{body}</div>
{/if}
</div>
{#if showVerschieben}
<div class="mb-2 flex items-center gap-2">
<input
type="date"
class="rounded border border-[#444] bg-bg px-2 py-1 text-sm text-white"
bind:value={newDate}
/>
<button
class="rounded bg-amber-600 px-3 py-1 text-sm font-bold text-white hover:brightness-110"
onclick={handleVerschieben}
>OK</button>
<button
class="rounded bg-[#444] px-3 py-1 text-sm text-white hover:bg-[#555]"
onclick={() => showVerschieben = false}
>Abbrechen</button>
</div>
{:else if showConvert}
<div class="mb-2 flex items-center gap-2">
<select
class="flex-1 rounded border border-[#444] bg-bg px-2 py-1 text-sm text-white"
bind:value={selectedContextId}
>
<option value="">— Kontext wählen —</option>
{#each $meetingContexts ?? [] as ctx}
<option value={ctx.id}>{ctx.name}</option>
{/each}
</select>
<button
class="rounded bg-accent px-3 py-1 text-sm font-bold text-white hover:brightness-110"
onclick={handleConvert}
disabled={!selectedContextId}
>Erstellen</button>
<button
class="rounded bg-[#444] px-3 py-1 text-sm text-white hover:bg-[#555]"
onclick={() => showConvert = false}
>Abbrechen</button>
</div>
{:else}
<div class="flex gap-2">
<button
class="rounded bg-green-700 px-3 py-1 text-sm font-bold text-white hover:brightness-110"
onclick={handleOk}
>Ok</button>
<button
class="rounded bg-amber-700 px-3 py-1 text-sm text-white hover:brightness-110"
onclick={() => showVerschieben = true}
>Verschieben</button>
<button
class="rounded bg-[#444] px-3 py-1 text-sm text-white hover:bg-[#555]"
onclick={() => showConvert = true}
>In Thema wandeln</button>
</div>
{/if}
</div>

View File

@ -0,0 +1,22 @@
<script lang="ts">
import { pendingWiedervorlage } from '$lib/stores/agenda';
import WiedervorlageCard from './WiedervorlageCard.svelte';
interface Props {
date: string;
}
let { date }: Props = $props();
const pending = $derived(pendingWiedervorlage(date));
</script>
{#if ($pending ?? []).length > 0}
<div class="mb-6 rounded-lg border border-amber-500/40 bg-amber-950/20 p-4">
<div class="mb-3 text-sm font-semibold uppercase tracking-wider text-amber-400">
Wiedervorlage ({($pending ?? []).length})
</div>
{#each $pending ?? [] as entry (entry.id)}
<WiedervorlageCard {entry} />
{/each}
</div>
{/if}

View File

@ -1,5 +1,5 @@
import { db } from './schema';
import { newId, now } from './helpers';
import { newId, now, today } from './helpers';
import type { AgendaContext, Topic, HistoryEntry, Rating, ContextType, TopicStatus, ProjectMeta, PersonMeta, CompanyMeta } from '@ka-note/shared';
// --- Contexts ---
@ -203,8 +203,9 @@ export async function getAllHistoryByContext(contextId: string): Promise<(Histor
return allHistory;
}
export async function createHistoryEntry(topicId: string, date: string, text: string, linkedContextId: string | null = null): Promise<HistoryEntry> {
export async function createHistoryEntry(topicId: string, date: string, text: string, linkedContextId: string | null = null, wiedervorlage = false): Promise<HistoryEntry> {
const existing = await getHistoryByTopic(topicId);
const autoWiedervorlage = date > today() || wiedervorlage;
const entry: HistoryEntry = {
id: newId(),
topicId,
@ -213,6 +214,8 @@ export async function createHistoryEntry(topicId: string, date: string, text: st
sortOrder: existing.length,
linkedContextId,
doneAt: null,
wiedervorlageDate: autoWiedervorlage ? date : null,
wiedervorlageResolvedAt: null,
updatedAt: now(),
deletedAt: null,
version: 1
@ -221,6 +224,30 @@ export async function createHistoryEntry(topicId: string, date: string, text: st
return entry;
}
export async function setWiedervorlage(id: string, date: string): Promise<void> {
const entry = await db.historyEntries.get(id);
if (entry) {
await db.historyEntries.update(id, { wiedervorlageDate: date, wiedervorlageResolvedAt: null, updatedAt: now(), version: entry.version + 1 });
}
}
export async function resolveWiedervorlage(id: string): Promise<void> {
const entry = await db.historyEntries.get(id);
if (entry) {
await db.historyEntries.update(id, { wiedervorlageResolvedAt: now(), updatedAt: now(), version: entry.version + 1 });
}
}
export async function convertToTopic(entryId: string, contextId: string): Promise<Topic> {
const entry = await db.historyEntries.get(entryId);
if (!entry) throw new Error('Entry not found');
const lines = entry.text.split('\n');
const title = lines[0].trim();
const topic = await createTopic(contextId, title);
await resolveWiedervorlage(entryId);
return topic;
}
export async function updateHistoryEntry(id: string, text: string): Promise<void> {
const entry = await db.historyEntries.get(id);
if (entry) {

View File

@ -68,6 +68,15 @@ export class KaNoteDB extends Dexie {
if (ctx.isFavorite === undefined) ctx.isFavorite = false;
});
});
this.version(7).stores({
historyEntries: 'id, topicId, date, sortOrder, deletedAt, linkedContextId, doneAt, wiedervorlageDate, wiedervorlageResolvedAt'
}).upgrade(tx => {
return tx.table('historyEntries').toCollection().modify(entry => {
if (entry.wiedervorlageDate === undefined) entry.wiedervorlageDate = null;
if (entry.wiedervorlageResolvedAt === undefined) entry.wiedervorlageResolvedAt = null;
});
});
}
}

View File

@ -118,3 +118,12 @@ export function ratingsForPerson(personName: string) {
.toArray()
);
}
export function pendingWiedervorlage(date: string) {
return liveQuery(() =>
db.historyEntries
.where('wiedervorlageDate').belowOrEqual(date)
.filter(e => !e.wiedervorlageResolvedAt && !e.deletedAt)
.toArray()
);
}

View File

@ -52,6 +52,8 @@ function mapHistoryEntry(row: typeof historyEntries.$inferSelect): HistoryEntry
sortOrder: row.sortOrder,
linkedContextId: row.linkedContextId,
doneAt: row.doneAt,
wiedervorlageDate: (row as any).wiedervorlageDate ?? null,
wiedervorlageResolvedAt: (row as any).wiedervorlageResolvedAt ?? null,
updatedAt: row.updatedAt,
deletedAt: row.deletedAt,
version: row.version,

View File

@ -57,6 +57,8 @@ export interface HistoryEntry extends SyncEntity {
sortOrder: number;
linkedContextId: string | null;
doneAt: string | null;
wiedervorlageDate: string | null; // YYYY-MM-DD
wiedervorlageResolvedAt: string | null; // ISO timestamp
}
export interface Rating extends SyncEntity {