233 lines
7.8 KiB
Svelte
233 lines
7.8 KiB
Svelte
<script lang="ts">
|
|
import { page } from '$app/state';
|
|
import { liveQuery } from 'dexie';
|
|
import { db } from '$lib/db/schema';
|
|
import { collapsedTopicIds } from '$lib/stores/agenda';
|
|
import ContextHeader from './ContextHeader.svelte';
|
|
import ViewTabs from './ViewTabs.svelte';
|
|
import AgendaView from './AgendaView.svelte';
|
|
import JournalView from './JournalView.svelte';
|
|
import PersonsView from './PersonsView.svelte';
|
|
import SnoozedView from './SnoozedView.svelte';
|
|
import ArchivedView from './ArchivedView.svelte';
|
|
import DashboardView from './DashboardView.svelte';
|
|
import CompanyPersonsView from './CompanyPersonsView.svelte';
|
|
import RatingModal from './RatingModal.svelte';
|
|
import RatingsView from './RatingsView.svelte';
|
|
import PersonMeetingsView from './PersonMeetingsView.svelte';
|
|
import PersonTasksView from './PersonTasksView.svelte';
|
|
import TaskCreateModal from './TaskCreateModal.svelte';
|
|
import type { MeetingMeta } from '@ka-note/shared';
|
|
import { getEventsForDate } from '$lib/stores/calendarStore';
|
|
import { today } from '$lib/db/helpers';
|
|
|
|
interface Props {
|
|
contextId: string;
|
|
}
|
|
let { contextId }: Props = $props();
|
|
|
|
const context = liveQuery(() => db.contexts.get(contextId));
|
|
const isDailyLog = $derived(contextId === 'daily-log');
|
|
|
|
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);
|
|
}
|
|
const initialDate = $derived(page.url.searchParams.get('date') ?? undefined);
|
|
let compact = $state(false);
|
|
|
|
let showAutoMeetingHint = $state(false);
|
|
|
|
function timeToMinutes(hhmm: string): number {
|
|
const [h, m] = hhmm.split(':').map(Number);
|
|
return h * 60 + m;
|
|
}
|
|
|
|
function currentTimeHHMM(): string {
|
|
const now = new Date();
|
|
return `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`;
|
|
}
|
|
|
|
function timeIsInRange(now: string, start: string, end: string, bufferMin: number): boolean {
|
|
const n = timeToMinutes(now);
|
|
const s = timeToMinutes(start) - bufferMin;
|
|
const e = timeToMinutes(end) + bufferMin;
|
|
return n >= s && n <= e;
|
|
}
|
|
|
|
$effect(() => {
|
|
const ctx = $context;
|
|
if (ctx?.type === 'meeting' && !isDailyLog) {
|
|
const calendarTitle = (ctx.meta as MeetingMeta | null)?.calendarTitle;
|
|
if (calendarTitle) {
|
|
const events = getEventsForDate(today());
|
|
const match = events.find(e => e.subject.toLowerCase() === calendarTitle.toLowerCase());
|
|
if (match) {
|
|
const now = currentTimeHHMM();
|
|
const isActive = timeIsInRange(now, match.start, match.end, 10);
|
|
if (isActive && mode !== 'meeting') {
|
|
mode = 'meeting';
|
|
showAutoMeetingHint = true;
|
|
setTimeout(() => { showAutoMeetingHint = false; }, 4000);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Rating modal state
|
|
let ratingModal = $state<{ personName: string; topicId: string; historyEntryId: string } | null>(null);
|
|
|
|
// Task create modal state
|
|
let taskModal = $state<{ title: string; assignee: string; historyEntryId: string | null; contextId: string } | null>(null);
|
|
|
|
// Default view based on context type
|
|
$effect(() => {
|
|
if ($context) {
|
|
if (isDailyLog) {
|
|
activeView = 'journal';
|
|
} else if ($context.type === 'meeting') {
|
|
activeView = 'agenda';
|
|
} else {
|
|
activeView = 'dashboard';
|
|
}
|
|
}
|
|
});
|
|
|
|
function handleModeChange(m: 'prep' | 'meeting') {
|
|
mode = m;
|
|
}
|
|
|
|
function handleViewChange(v: string) {
|
|
activeView = v;
|
|
}
|
|
|
|
function toggleCompact() {
|
|
compact = !compact;
|
|
if (compact) {
|
|
db.topics
|
|
.where('contextId').equals(contextId)
|
|
.filter(t => !t.deletedAt)
|
|
.toArray()
|
|
.then(topics => {
|
|
collapsedTopicIds.update(() => new Set(topics.map(t => t.id)));
|
|
});
|
|
} else {
|
|
collapsedTopicIds.set(new Set());
|
|
}
|
|
}
|
|
|
|
let containerEl = $state<HTMLDivElement>();
|
|
|
|
function handleRatingEvent(e: Event) {
|
|
const detail = (e as CustomEvent).detail;
|
|
if (detail?.personName && detail?.topicId && detail?.historyEntryId) {
|
|
ratingModal = detail;
|
|
}
|
|
}
|
|
|
|
async function handleTaskCreateEvent(e: Event) {
|
|
const detail = (e as CustomEvent<{ title: string; assignee: string; historyEntryId: string | null; topicId: string | null }>).detail;
|
|
if (!detail) return;
|
|
// Resolve contextId: historyEntryId → entry.topicId → topic.contextId
|
|
// Fallback: topicId directly → topic.contextId
|
|
let resolvedContextId = contextId;
|
|
let resolvedHistoryEntryId = detail.historyEntryId;
|
|
if (detail.historyEntryId) {
|
|
const entry = await db.historyEntries.get(detail.historyEntryId);
|
|
if (entry) {
|
|
const topic = await db.topics.get(entry.topicId);
|
|
if (topic) resolvedContextId = topic.contextId;
|
|
}
|
|
} else if (detail.topicId) {
|
|
const topic = await db.topics.get(detail.topicId);
|
|
if (topic) resolvedContextId = topic.contextId;
|
|
// Find the most recent history entry for this topic to update text
|
|
const entries = await db.historyEntries
|
|
.where('topicId').equals(detail.topicId)
|
|
.filter(h => !h.deletedAt)
|
|
.sortBy('updatedAt');
|
|
if (entries.length > 0) resolvedHistoryEntryId = entries[entries.length - 1].id;
|
|
}
|
|
taskModal = { title: detail.title, assignee: detail.assignee, historyEntryId: resolvedHistoryEntryId, contextId: resolvedContextId };
|
|
}
|
|
|
|
$effect(() => {
|
|
if (!containerEl) return;
|
|
containerEl.addEventListener('ka-open-rating', handleRatingEvent);
|
|
containerEl.addEventListener('ka-open-task-create', handleTaskCreateEvent);
|
|
return () => {
|
|
containerEl?.removeEventListener('ka-open-rating', handleRatingEvent);
|
|
containerEl?.removeEventListener('ka-open-task-create', handleTaskCreateEvent);
|
|
};
|
|
});
|
|
</script>
|
|
|
|
<div bind:this={containerEl}>
|
|
{#if $context}
|
|
{#if showAutoMeetingHint}
|
|
<div class="mb-3 flex items-center justify-between bg-warning/20 border border-warning text-sm px-3 py-1 rounded">
|
|
<span>Meeting läuft - automatisch in Meeting-Modus gewechselt</span>
|
|
<button class="ml-2 text-muted hover:text-white" onclick={() => showAutoMeetingHint = false}>×</button>
|
|
</div>
|
|
{/if}
|
|
{#if isDailyLog}
|
|
<ContextHeader context={$context} {journalScope} onjournalscopechange={handleScopeChange} />
|
|
<ViewTabs context={$context} {activeView} onviewchange={handleViewChange} />
|
|
{:else}
|
|
<ContextHeader context={$context} {mode} onmodechange={handleModeChange} />
|
|
<ViewTabs context={$context} {activeView} {compact} onviewchange={handleViewChange} ontogglecompact={toggleCompact} />
|
|
{/if}
|
|
|
|
{#if activeView === 'agenda'}
|
|
<AgendaView {contextId} {mode} />
|
|
{:else if activeView === 'journal'}
|
|
<JournalView {contextId} {journalScope} {initialDate} />
|
|
{:else if activeView === 'persons'}
|
|
<PersonsView {contextId} />
|
|
{:else if activeView === 'snoozed'}
|
|
<SnoozedView {contextId} />
|
|
{:else if activeView === 'archived'}
|
|
<ArchivedView {contextId} />
|
|
{:else if activeView === 'dashboard'}
|
|
<DashboardView context={$context} />
|
|
{:else if activeView === 'company-persons'}
|
|
<CompanyPersonsView context={$context} />
|
|
{:else if activeView === 'ratings'}
|
|
<RatingsView personName={$context.name.replace(/^Person /, '')} />
|
|
{:else if activeView === 'person-meetings'}
|
|
<PersonMeetingsView context={$context} />
|
|
{:else if activeView === 'tasks'}
|
|
<PersonTasksView context={$context} />
|
|
{/if}
|
|
{:else}
|
|
<div class="text-muted">Context not found.</div>
|
|
{/if}
|
|
</div>
|
|
|
|
{#if ratingModal}
|
|
<RatingModal
|
|
personName={ratingModal.personName}
|
|
topicId={ratingModal.topicId}
|
|
historyEntryId={ratingModal.historyEntryId}
|
|
onclose={() => ratingModal = null}
|
|
/>
|
|
{/if}
|
|
|
|
{#if taskModal}
|
|
<TaskCreateModal
|
|
title={taskModal.title}
|
|
assignee={taskModal.assignee}
|
|
historyEntryId={taskModal.historyEntryId}
|
|
contextId={taskModal.contextId}
|
|
onclose={() => taskModal = null}
|
|
/>
|
|
{/if}
|