311 lines
10 KiB
Svelte
311 lines
10 KiB
Svelte
<script lang="ts">
|
|
import { page } from '$app/stores';
|
|
import { goto } from '$app/navigation';
|
|
|
|
import { liveQuery } from 'dexie';
|
|
import { db } from '$lib/db/schema';
|
|
import { resetAndReseed } from '$lib/db/seed';
|
|
import { upsertContext } from '$lib/db/repositories';
|
|
import { newId } from '$lib/db/helpers';
|
|
import type { AgendaContext, ContextType } from '@ka-note/shared';
|
|
import { account, logout } from '$lib/auth/authStore.js';
|
|
|
|
const isDev = import.meta.env.DEV;
|
|
|
|
interface Props {
|
|
onnavigate?: () => void;
|
|
}
|
|
let { onnavigate }: Props = $props();
|
|
|
|
const currentId = $derived($page.params.id ?? 'daily-log');
|
|
|
|
const contexts = liveQuery(() => db.contexts.filter(c => !c.deletedAt && !c.archivedAt).sortBy('sortOrder'));
|
|
|
|
const dailyLog = $derived(($contexts ?? []).find((c: AgendaContext) => c.id === 'daily-log'));
|
|
const meetings = $derived(($contexts ?? []).filter((c: AgendaContext) => c.type === 'meeting' && c.id !== 'daily-log' && c.isFavorite));
|
|
const projects = $derived(($contexts ?? []).filter((c: AgendaContext) => c.type === 'project' && c.isFavorite));
|
|
const companies = $derived(($contexts ?? []).filter((c: AgendaContext) => c.type === 'company' && c.isFavorite));
|
|
const people = $derived(($contexts ?? []).filter((c: AgendaContext) => c.type === 'person' && c.isFavorite));
|
|
|
|
let collapsed: Record<string, boolean> = $state({});
|
|
|
|
function toggleSection(key: string) {
|
|
collapsed[key] = !collapsed[key];
|
|
}
|
|
|
|
let creatingType: ContextType | null = $state(null);
|
|
let newName = $state('');
|
|
|
|
function navigate(id: string) {
|
|
goto(`/context/${id}`);
|
|
onnavigate?.();
|
|
}
|
|
|
|
function displayName(ctx: AgendaContext): string {
|
|
if (ctx.type === 'project') return ctx.name.replace('Project ', '');
|
|
if (ctx.type === 'company') return ctx.name.replace('Firma ', '');
|
|
if (ctx.type === 'person') return ctx.name.replace('Person ', '');
|
|
return ctx.name;
|
|
}
|
|
|
|
function startCreating(type: ContextType) {
|
|
creatingType = type;
|
|
newName = '';
|
|
}
|
|
|
|
function cancelCreating() {
|
|
creatingType = null;
|
|
newName = '';
|
|
}
|
|
|
|
async function createNewContext() {
|
|
const name = newName.trim();
|
|
if (!name || !creatingType) return;
|
|
|
|
const type = creatingType;
|
|
const slug = name.toLowerCase().replace(/\s+/g, '-');
|
|
const id = type === 'company' ? `f-${slug}` : type === 'person' ? `u-${slug}` : type === 'project' ? `p-${slug}` : newId();
|
|
const fullName = type === 'company' ? `Firma ${name}` : type === 'project' ? `Project ${name}` : type === 'person' ? `Person ${name}` : name;
|
|
|
|
const existing = ($contexts ?? []).filter((c: AgendaContext) => c.type === type);
|
|
const sortOrder = existing.length;
|
|
|
|
const meta = type === 'person' ? { fullName: '', email: '', phone: '', duSince: '', personSubType: 'contact' as const } : undefined;
|
|
await upsertContext({ id, name: fullName, type, sortOrder, ...(meta ? { meta } : {}) });
|
|
cancelCreating();
|
|
navigate(id);
|
|
}
|
|
|
|
function handleInputKeydown(e: KeyboardEvent) {
|
|
if (e.key === 'Enter') {
|
|
e.preventDefault();
|
|
createNewContext();
|
|
} else if (e.key === 'Escape') {
|
|
cancelCreating();
|
|
}
|
|
}
|
|
|
|
function autofocus(node: HTMLElement) {
|
|
node.focus();
|
|
}
|
|
</script>
|
|
|
|
<div class="mb-4 border-b-2 border-accent pb-1 text-lg font-bold uppercase tracking-wider text-accent">
|
|
Ka-Note
|
|
</div>
|
|
|
|
<!-- General -->
|
|
<div class="mb-1 mt-4 pl-1 text-xs uppercase text-muted">General</div>
|
|
<ul class="m-0 list-none p-0">
|
|
{#if dailyLog}
|
|
<li>
|
|
<button
|
|
class="mb-1 w-full rounded px-2.5 py-2 text-left text-sm transition-colors {currentId === 'daily-log' ? 'bg-accent font-bold text-white' : 'text-[#ccc] hover:bg-white/5 hover:text-white'}"
|
|
onclick={() => navigate('daily-log')}
|
|
>
|
|
{dailyLog.name}
|
|
</button>
|
|
</li>
|
|
{/if}
|
|
</ul>
|
|
|
|
<!-- Jour Fixes -->
|
|
<div class="mb-1 mt-4 flex items-center justify-between pl-1 pr-1">
|
|
<button class="text-xs uppercase text-muted hover:text-white flex items-center gap-1" onclick={() => toggleSection('meetings')}>
|
|
<span class="inline-block text-[10px] transition-transform {collapsed['meetings'] ? '-rotate-90' : ''}">{@html '▼'}</span>
|
|
Jour Fixes
|
|
</button>
|
|
<button class="text-muted hover:text-white text-sm leading-none" onclick={() => startCreating('meeting')}>+</button>
|
|
</div>
|
|
{#if !collapsed['meetings']}
|
|
<ul class="m-0 list-none p-0">
|
|
{#each meetings as ctx}
|
|
<li>
|
|
<button
|
|
class="mb-1 w-full rounded px-2.5 py-2 text-left text-sm transition-colors {currentId === ctx.id ? 'bg-accent font-bold text-white' : 'text-[#ccc] hover:bg-white/5 hover:text-white'}"
|
|
onclick={() => navigate(ctx.id)}
|
|
>
|
|
{ctx.name}
|
|
</button>
|
|
</li>
|
|
{/each}
|
|
{#if creatingType === 'meeting'}
|
|
<li>
|
|
<input
|
|
class="mb-1 w-full rounded bg-white/10 px-2.5 py-2 text-sm text-white outline-none placeholder:text-muted"
|
|
placeholder="Name..."
|
|
bind:value={newName}
|
|
onkeydown={handleInputKeydown}
|
|
onblur={cancelCreating}
|
|
use:autofocus
|
|
/>
|
|
</li>
|
|
{/if}
|
|
<li>
|
|
<button
|
|
class="mb-1 w-full rounded px-2.5 py-2 text-left text-xs text-[#666] transition-colors hover:bg-white/5 hover:text-[#aaa]"
|
|
onclick={() => { goto('/meetings'); onnavigate?.(); }}
|
|
>
|
|
Alle Jour Fixes →
|
|
</button>
|
|
</li>
|
|
</ul>
|
|
{/if}
|
|
|
|
<!-- Projects -->
|
|
<div class="mb-1 mt-4 flex items-center justify-between pl-1 pr-1">
|
|
<button class="text-xs uppercase text-muted hover:text-white flex items-center gap-1" onclick={() => toggleSection('projects')}>
|
|
<span class="inline-block text-[10px] transition-transform {collapsed['projects'] ? '-rotate-90' : ''}">{@html '▼'}</span>
|
|
Projects
|
|
</button>
|
|
<button class="text-muted hover:text-white text-sm leading-none" onclick={() => startCreating('project')}>+</button>
|
|
</div>
|
|
{#if !collapsed['projects']}
|
|
<ul class="m-0 list-none p-0">
|
|
{#each projects as ctx}
|
|
<li>
|
|
<button
|
|
class="mb-1 w-full rounded px-2.5 py-2 text-left text-sm transition-colors {currentId === ctx.id ? 'bg-accent font-bold text-white' : 'text-[#ccc] hover:bg-white/5 hover:text-white'}"
|
|
onclick={() => navigate(ctx.id)}
|
|
>
|
|
{displayName(ctx)}
|
|
</button>
|
|
</li>
|
|
{/each}
|
|
{#if creatingType === 'project'}
|
|
<li>
|
|
<input
|
|
class="mb-1 w-full rounded bg-white/10 px-2.5 py-2 text-sm text-white outline-none placeholder:text-muted"
|
|
placeholder="Name..."
|
|
bind:value={newName}
|
|
onkeydown={handleInputKeydown}
|
|
onblur={cancelCreating}
|
|
use:autofocus
|
|
/>
|
|
</li>
|
|
{/if}
|
|
<li>
|
|
<button
|
|
class="mb-1 w-full rounded px-2.5 py-2 text-left text-xs text-[#666] transition-colors hover:bg-white/5 hover:text-[#aaa]"
|
|
onclick={() => { goto('/projects'); onnavigate?.(); }}
|
|
>
|
|
Alle Projekte →
|
|
</button>
|
|
</li>
|
|
</ul>
|
|
{/if}
|
|
|
|
<!-- Companies -->
|
|
<div class="mb-1 mt-4 flex items-center justify-between pl-1 pr-1">
|
|
<button class="text-xs uppercase text-muted hover:text-white flex items-center gap-1" onclick={() => toggleSection('companies')}>
|
|
<span class="inline-block text-[10px] transition-transform {collapsed['companies'] ? '-rotate-90' : ''}">{@html '▼'}</span>
|
|
Firmen
|
|
</button>
|
|
<button class="text-muted hover:text-white text-sm leading-none" onclick={() => startCreating('company')}>+</button>
|
|
</div>
|
|
{#if !collapsed['companies']}
|
|
<ul class="m-0 list-none p-0">
|
|
{#each companies as ctx}
|
|
<li>
|
|
<button
|
|
class="mb-1 w-full rounded px-2.5 py-2 text-left text-sm transition-colors {currentId === ctx.id ? 'bg-accent font-bold text-white' : 'text-[#ccc] hover:bg-white/5 hover:text-white'}"
|
|
onclick={() => navigate(ctx.id)}
|
|
>
|
|
{displayName(ctx)}
|
|
</button>
|
|
</li>
|
|
{/each}
|
|
{#if creatingType === 'company'}
|
|
<li>
|
|
<input
|
|
class="mb-1 w-full rounded bg-white/10 px-2.5 py-2 text-sm text-white outline-none placeholder:text-muted"
|
|
placeholder="Name..."
|
|
bind:value={newName}
|
|
onkeydown={handleInputKeydown}
|
|
onblur={cancelCreating}
|
|
use:autofocus
|
|
/>
|
|
</li>
|
|
{/if}
|
|
<li>
|
|
<button
|
|
class="mb-1 w-full rounded px-2.5 py-2 text-left text-xs text-[#666] transition-colors hover:bg-white/5 hover:text-[#aaa]"
|
|
onclick={() => { goto('/companies'); onnavigate?.(); }}
|
|
>
|
|
Alle Firmen →
|
|
</button>
|
|
</li>
|
|
</ul>
|
|
{/if}
|
|
|
|
<!-- People -->
|
|
<div class="mb-1 mt-4 flex items-center justify-between pl-1 pr-1">
|
|
<button class="text-xs uppercase text-muted hover:text-white flex items-center gap-1" onclick={() => toggleSection('people')}>
|
|
<span class="inline-block text-[10px] transition-transform {collapsed['people'] ? '-rotate-90' : ''}">{@html '▼'}</span>
|
|
People
|
|
</button>
|
|
<button class="text-muted hover:text-white text-sm leading-none" onclick={() => startCreating('person')}>+</button>
|
|
</div>
|
|
{#if !collapsed['people']}
|
|
<ul class="m-0 list-none p-0">
|
|
{#each people as ctx}
|
|
<li>
|
|
<button
|
|
class="mb-1 w-full rounded px-2.5 py-2 text-left text-sm transition-colors {currentId === ctx.id ? 'bg-accent font-bold text-white' : 'text-[#ccc] hover:bg-white/5 hover:text-white'}"
|
|
onclick={() => navigate(ctx.id)}
|
|
>
|
|
{displayName(ctx)}
|
|
</button>
|
|
</li>
|
|
{/each}
|
|
{#if creatingType === 'person'}
|
|
<li>
|
|
<input
|
|
class="mb-1 w-full rounded bg-white/10 px-2.5 py-2 text-sm text-white outline-none placeholder:text-muted"
|
|
placeholder="Name..."
|
|
bind:value={newName}
|
|
onkeydown={handleInputKeydown}
|
|
onblur={cancelCreating}
|
|
use:autofocus
|
|
/>
|
|
</li>
|
|
{/if}
|
|
<li>
|
|
<button
|
|
class="mb-1 w-full rounded px-2.5 py-2 text-left text-xs text-[#666] transition-colors hover:bg-white/5 hover:text-[#aaa]"
|
|
onclick={() => { goto('/persons'); onnavigate?.(); }}
|
|
>
|
|
Alle Personen →
|
|
</button>
|
|
</li>
|
|
</ul>
|
|
{/if}
|
|
|
|
<div class="mt-auto border-t border-border pt-3">
|
|
<button
|
|
class="mb-1 w-full rounded px-2.5 py-2 text-left text-xs text-muted transition-colors hover:bg-white/5 hover:text-white"
|
|
onclick={() => { goto('/settings'); onnavigate?.(); }}
|
|
>
|
|
Einstellungen
|
|
</button>
|
|
{#if $account}
|
|
<div class="mb-2 truncate px-1 text-xs text-muted" title={$account.username}>
|
|
{$account.name ?? $account.username}
|
|
</div>
|
|
{/if}
|
|
<button
|
|
class="w-full rounded px-2.5 py-2 text-left text-xs text-muted transition-colors hover:bg-white/5 hover:text-white"
|
|
onclick={() => logout()}
|
|
>
|
|
Sign out
|
|
</button>
|
|
{#if isDev}
|
|
<button
|
|
class="mt-1 w-full rounded bg-danger/20 px-2.5 py-2 text-left text-xs text-danger hover:bg-danger/30 transition-colors"
|
|
onclick={async () => { await resetAndReseed(); location.reload(); }}
|
|
>
|
|
Reset Seed Data
|
|
</button>
|
|
{/if}
|
|
</div>
|