upd meniu

This commit is contained in:
beo3000 2026-02-20 17:49:04 +01:00
parent fa4d3046d0
commit 81a5a90b20
12 changed files with 89 additions and 23 deletions

View File

@ -21,10 +21,16 @@
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'));
const projects = $derived(($contexts ?? []).filter((c: AgendaContext) => c.type === 'project'));
const companies = $derived(($contexts ?? []).filter((c: AgendaContext) => c.type === 'company'));
const people = $derived(($contexts ?? []).filter((c: AgendaContext) => c.type === 'person'));
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('');
@ -103,9 +109,13 @@
<!-- Jour Fixes -->
<div class="mb-1 mt-4 flex items-center justify-between pl-1 pr-1">
<span class="text-xs uppercase text-muted">Jour Fixes</span>
<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 '&#9660;'}</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>
@ -138,12 +148,17 @@
</button>
</li>
</ul>
{/if}
<!-- Projects -->
<div class="mb-1 mt-4 flex items-center justify-between pl-1 pr-1">
<span class="text-xs uppercase text-muted">Projects</span>
<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 '&#9660;'}</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>
@ -176,12 +191,17 @@
</button>
</li>
</ul>
{/if}
<!-- Companies -->
<div class="mb-1 mt-4 flex items-center justify-between pl-1 pr-1">
<span class="text-xs uppercase text-muted">Firmen</span>
<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 '&#9660;'}</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>
@ -214,12 +234,17 @@
</button>
</li>
</ul>
{/if}
<!-- People -->
<div class="mb-1 mt-4 flex items-center justify-between pl-1 pr-1">
<span class="text-xs uppercase text-muted">People</span>
<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 '&#9660;'}</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>
@ -252,6 +277,7 @@
</button>
</li>
</ul>
{/if}
{#if isDev}
<div class="mt-auto border-t border-border pt-3">

View File

@ -28,6 +28,7 @@ export async function upsertContext(ctx: Partial<AgendaContext> & { id: string }
sortOrder: 0,
meta: null,
archivedAt: null,
isFavorite: false,
deletedAt: null,
updatedAt: now(),
version: 1,
@ -57,6 +58,13 @@ export async function unarchiveContext(id: string): Promise<void> {
}
}
export async function toggleFavorite(id: string): Promise<void> {
const ctx = await db.contexts.get(id);
if (ctx) {
await db.contexts.update(id, { isFavorite: !ctx.isFavorite, updatedAt: now(), version: ctx.version + 1 });
}
}
export async function findContextByMentionName(name: string, type: 'person' | 'project' | 'company'): Promise<AgendaContext | undefined> {
const q = name.toLowerCase();
return db.contexts

View File

@ -60,6 +60,14 @@ export class KaNoteDB extends Dexie {
if (rating.comment === undefined) rating.comment = null;
});
});
this.version(6).stores({
contexts: 'id, type, sortOrder, deletedAt, archivedAt, isFavorite'
}).upgrade(tx => {
return tx.table('contexts').toCollection().modify(ctx => {
if (ctx.isFavorite === undefined) ctx.isFavorite = false;
});
});
}
}

View File

@ -13,16 +13,16 @@ export async function seedIfEmpty(): Promise<void> {
const ts = now();
const contexts: AgendaContext[] = [
{ id: 'daily-log', name: 'Daily Log / Inbox', type: 'meeting', sortOrder: 0, meta: null, archivedAt: null, updatedAt: ts, deletedAt: null, version: 1 },
{ id: 'jf-sysadmins', name: 'JF Team Sysadmins', type: 'meeting', sortOrder: 1, meta: null, archivedAt: null, updatedAt: ts, deletedAt: null, version: 1 },
{ id: 'jf-devs', name: 'JF Developer', type: 'meeting', sortOrder: 2, meta: null, archivedAt: null, updatedAt: ts, deletedAt: null, version: 1 },
{ id: 'p-tisax', name: 'Project TISAX', type: 'project', sortOrder: 0, meta: { status: '#stInArbeit', owner: 'STEFE', links: 'FileServer/Projects/TISAX' }, archivedAt: null, updatedAt: ts, deletedAt: null, version: 1 },
{ id: 'p-cloud-migration', name: 'Project CLOUD-MIGRATION', type: 'project', sortOrder: 1, meta: { status: '#stBlocked', owner: 'CHFI', links: 'Jira-1234' }, archivedAt: null, updatedAt: ts, deletedAt: null, version: 1 },
{ id: 'u-stefe', name: 'Person STEFE', type: 'person', sortOrder: 0, meta: { fullName: 'Stefan E.', email: 'stefe@example.com', phone: '+49 123 456', duSince: '2020', personSubType: 'employee' }, archivedAt: null, updatedAt: ts, deletedAt: null, version: 1 },
{ id: 'u-chfi', name: 'Person CHFI', type: 'person', sortOrder: 1, meta: { fullName: 'Christoph F.', email: 'chfi@example.com', phone: '98765', duSince: '2024', personSubType: 'employee' }, archivedAt: null, updatedAt: ts, deletedAt: null, version: 1 },
{ id: 'u-vendor-x', name: 'Person VENDOR-X', type: 'person', sortOrder: 2, meta: { fullName: 'Hr. Müller (Vendor X)', email: 'sales@vendor-x.com', phone: '', duSince: '', personSubType: 'contact' }, archivedAt: null, updatedAt: ts, deletedAt: null, version: 1 },
{ id: 'u-chrkl', name: 'Person ChrKl', type: 'person', sortOrder: 3, meta: { fullName: 'Christian Kl.', email: '', phone: '', duSince: '', personSubType: 'colleague' }, archivedAt: null, updatedAt: ts, deletedAt: null, version: 1 },
{ id: 'f-vendor-x', name: 'Firma VENDOR-X', type: 'company', sortOrder: 0, meta: { website: 'https://vendor-x.com', address: 'Musterstr. 1, 12345 Berlin' }, archivedAt: null, updatedAt: ts, deletedAt: null, version: 1 }
{ id: 'daily-log', name: 'Daily Log / Inbox', type: 'meeting', sortOrder: 0, meta: null, archivedAt: null, isFavorite: true, updatedAt: ts, deletedAt: null, version: 1 },
{ id: 'jf-sysadmins', name: 'JF Team Sysadmins', type: 'meeting', sortOrder: 1, meta: null, archivedAt: null, isFavorite: true, updatedAt: ts, deletedAt: null, version: 1 },
{ id: 'jf-devs', name: 'JF Developer', type: 'meeting', sortOrder: 2, meta: null, archivedAt: null, isFavorite: true, updatedAt: ts, deletedAt: null, version: 1 },
{ id: 'p-tisax', name: 'Project TISAX', type: 'project', sortOrder: 0, meta: { status: '#stInArbeit', owner: 'STEFE', links: 'FileServer/Projects/TISAX' }, archivedAt: null, isFavorite: true, updatedAt: ts, deletedAt: null, version: 1 },
{ id: 'p-cloud-migration', name: 'Project CLOUD-MIGRATION', type: 'project', sortOrder: 1, meta: { status: '#stBlocked', owner: 'CHFI', links: 'Jira-1234' }, archivedAt: null, isFavorite: false, updatedAt: ts, deletedAt: null, version: 1 },
{ id: 'u-stefe', name: 'Person STEFE', type: 'person', sortOrder: 0, meta: { fullName: 'Stefan E.', email: 'stefe@example.com', phone: '+49 123 456', duSince: '2020', personSubType: 'employee' }, archivedAt: null, isFavorite: true, updatedAt: ts, deletedAt: null, version: 1 },
{ id: 'u-chfi', name: 'Person CHFI', type: 'person', sortOrder: 1, meta: { fullName: 'Christoph F.', email: 'chfi@example.com', phone: '98765', duSince: '2024', personSubType: 'employee' }, archivedAt: null, isFavorite: false, updatedAt: ts, deletedAt: null, version: 1 },
{ id: 'u-vendor-x', name: 'Person VENDOR-X', type: 'person', sortOrder: 2, meta: { fullName: 'Hr. Müller (Vendor X)', email: 'sales@vendor-x.com', phone: '', duSince: '', personSubType: 'contact' }, archivedAt: null, isFavorite: false, updatedAt: ts, deletedAt: null, version: 1 },
{ id: 'u-chrkl', name: 'Person ChrKl', type: 'person', sortOrder: 3, meta: { fullName: 'Christian Kl.', email: '', phone: '', duSince: '', personSubType: 'colleague' }, archivedAt: null, isFavorite: false, updatedAt: ts, deletedAt: null, version: 1 },
{ id: 'f-vendor-x', name: 'Firma VENDOR-X', type: 'company', sortOrder: 0, meta: { website: 'https://vendor-x.com', address: 'Musterstr. 1, 12345 Berlin' }, archivedAt: null, isFavorite: true, updatedAt: ts, deletedAt: null, version: 1 }
];
// Topic IDs

View File

@ -47,7 +47,7 @@
<!-- Sidebar -->
<aside
class="fixed z-40 flex h-full w-[250px] flex-col border-r border-border bg-sidebar p-5 transition-transform md:static md:translate-x-0 {sidebarOpen ? 'translate-x-0' : '-translate-x-full'}"
class="fixed z-40 flex h-full w-[250px] flex-col border-r border-border bg-sidebar p-5 overflow-y-auto transition-transform md:static md:translate-x-0 {sidebarOpen ? 'translate-x-0' : '-translate-x-full'}"
>
<Sidebar onnavigate={closeSidebar} />
</aside>

View File

@ -2,7 +2,7 @@
import { goto } from '$app/navigation';
import { liveQuery } from 'dexie';
import { db } from '$lib/db/schema';
import { softDeleteContext } from '$lib/db/repositories';
import { softDeleteContext, toggleFavorite } from '$lib/db/repositories';
import ConfirmDialog from '$lib/components/ConfirmDialog.svelte';
const allCompanies = liveQuery(() =>
@ -36,6 +36,11 @@
<div class="flex flex-col gap-2">
{#each companies as ctx (ctx.id)}
<div class="flex items-center gap-2">
<button
class="rounded border border-[#444] bg-sidebar px-2.5 py-2.5 transition-colors hover:border-[#666] {ctx.isFavorite ? 'text-yellow-400' : 'text-[#555] hover:text-yellow-400/60'}"
onclick={() => toggleFavorite(ctx.id)}
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]"
onclick={() => goto(`/context/${ctx.id}`)}

View File

@ -2,7 +2,7 @@
import { goto } from '$app/navigation';
import { liveQuery } from 'dexie';
import { db } from '$lib/db/schema';
import { softDeleteContext } from '$lib/db/repositories';
import { softDeleteContext, toggleFavorite } from '$lib/db/repositories';
import ConfirmDialog from '$lib/components/ConfirmDialog.svelte';
const allMeetings = liveQuery(() =>
@ -32,6 +32,11 @@
<div class="flex flex-col gap-2">
{#each meetings as ctx (ctx.id)}
<div class="flex items-center gap-2">
<button
class="rounded border border-[#444] bg-sidebar px-2.5 py-2.5 transition-colors hover:border-[#666] {ctx.isFavorite ? 'text-yellow-400' : 'text-[#555] hover:text-yellow-400/60'}"
onclick={() => toggleFavorite(ctx.id)}
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]"
onclick={() => goto(`/context/${ctx.id}`)}

View File

@ -2,7 +2,7 @@
import { goto } from '$app/navigation';
import { liveQuery } from 'dexie';
import { db } from '$lib/db/schema';
import { softDeleteContext } from '$lib/db/repositories';
import { softDeleteContext, toggleFavorite } from '$lib/db/repositories';
import ConfirmDialog from '$lib/components/ConfirmDialog.svelte';
import type { PersonMeta, PersonSubType } from '@ka-note/shared';
@ -50,6 +50,11 @@
{#each persons as ctx (ctx.id)}
{@const subType = ((ctx.meta as PersonMeta | null)?.personSubType ?? 'contact') as PersonSubType}
<div class="flex items-center gap-2">
<button
class="rounded border border-[#444] bg-sidebar px-2.5 py-2.5 transition-colors hover:border-[#666] {ctx.isFavorite ? 'text-yellow-400' : 'text-[#555] hover:text-yellow-400/60'}"
onclick={() => toggleFavorite(ctx.id)}
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]"
onclick={() => goto(`/context/${ctx.id}`)}

View File

@ -2,7 +2,7 @@
import { goto } from '$app/navigation';
import { liveQuery } from 'dexie';
import { db } from '$lib/db/schema';
import { softDeleteContext } from '$lib/db/repositories';
import { softDeleteContext, toggleFavorite } from '$lib/db/repositories';
import ConfirmDialog from '$lib/components/ConfirmDialog.svelte';
import type { AgendaContext, ProjectMeta } from '@ka-note/shared';
@ -43,6 +43,11 @@
<div class="mb-8 flex flex-col gap-2">
{#each activeProjects as ctx (ctx.id)}
<div class="flex items-center gap-2">
<button
class="rounded border border-[#444] bg-sidebar px-2.5 py-2.5 transition-colors hover:border-[#666] {ctx.isFavorite ? 'text-yellow-400' : 'text-[#555] hover:text-yellow-400/60'}"
onclick={() => toggleFavorite(ctx.id)}
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]"
onclick={() => goto(`/context/${ctx.id}`)}

View File

@ -7,6 +7,7 @@ export const contexts = sqliteTable('contexts', {
sortOrder: integer('sort_order').notNull().default(0),
meta: text('meta'), // JSON string
archivedAt: text('archived_at'),
isFavorite: integer('is_favorite', { mode: 'boolean' }).notNull().default(false),
updatedAt: text('updated_at').notNull(),
deletedAt: text('deleted_at'),
version: integer('version').notNull().default(1),

View File

@ -21,6 +21,7 @@ function mapContext(row: typeof contexts.$inferSelect): AgendaContext {
sortOrder: row.sortOrder,
meta: row.meta ? JSON.parse(row.meta) : null,
archivedAt: row.archivedAt,
isFavorite: row.isFavorite,
updatedAt: row.updatedAt,
deletedAt: row.deletedAt,
version: row.version,
@ -219,6 +220,7 @@ export async function seedDailyLog(): Promise<void> {
sortOrder: 0,
meta: null,
archivedAt: null,
isFavorite: true,
updatedAt: now(),
deletedAt: null,
version: 1,

View File

@ -15,6 +15,7 @@ export interface AgendaContext extends SyncEntity {
sortOrder: number;
meta: ProjectMeta | PersonMeta | CompanyMeta | null;
archivedAt: string | null;
isFavorite: boolean;
}
export interface ProjectMeta {