comp person list
This commit is contained in:
parent
a352518569
commit
9886b602f0
|
|
@ -0,0 +1,46 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { AgendaContext } from '@ka-note/shared';
|
||||||
|
import { liveQuery } from 'dexie';
|
||||||
|
import { db } from '$lib/db/schema';
|
||||||
|
import { extractMentions, extractAssignments, extractCompanies } from '$lib/utils/extractors';
|
||||||
|
import PersonList from './PersonList.svelte';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
context: AgendaContext;
|
||||||
|
}
|
||||||
|
let { context }: Props = $props();
|
||||||
|
|
||||||
|
const entityName = $derived(context.name.replace(/^Firma /, ''));
|
||||||
|
|
||||||
|
const linkedPersons = liveQuery(async () => {
|
||||||
|
const allHistory = await db.historyEntries.filter(h => !h.deletedAt).toArray();
|
||||||
|
const personContexts = await db.contexts.filter(c => !c.deletedAt && c.type === 'person').toArray();
|
||||||
|
|
||||||
|
const matchingPersonNames = new Set<string>();
|
||||||
|
for (const h of allHistory) {
|
||||||
|
if (extractCompanies(h.text).includes(entityName)) {
|
||||||
|
for (const m of extractMentions(h.text)) {
|
||||||
|
matchingPersonNames.add(m);
|
||||||
|
}
|
||||||
|
for (const a of extractAssignments(h.text)) {
|
||||||
|
matchingPersonNames.add(a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return personContexts.filter(p => {
|
||||||
|
const pName = p.name.replace(/^Person\s+/, '');
|
||||||
|
return matchingPersonNames.has(pName);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const persons = $derived($linkedPersons ?? []);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="mx-auto max-w-3xl p-4">
|
||||||
|
{#if persons.length > 0}
|
||||||
|
<PersonList {persons} />
|
||||||
|
{:else}
|
||||||
|
<div class="text-muted">Keine zugeordneten Personen.</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
import PersonsView from './PersonsView.svelte';
|
import PersonsView from './PersonsView.svelte';
|
||||||
import SnoozedView from './SnoozedView.svelte';
|
import SnoozedView from './SnoozedView.svelte';
|
||||||
import DashboardView from './DashboardView.svelte';
|
import DashboardView from './DashboardView.svelte';
|
||||||
|
import CompanyPersonsView from './CompanyPersonsView.svelte';
|
||||||
import RatingModal from './RatingModal.svelte';
|
import RatingModal from './RatingModal.svelte';
|
||||||
import RatingsView from './RatingsView.svelte';
|
import RatingsView from './RatingsView.svelte';
|
||||||
|
|
||||||
|
|
@ -93,6 +94,8 @@
|
||||||
<SnoozedView {contextId} />
|
<SnoozedView {contextId} />
|
||||||
{:else if activeView === 'dashboard'}
|
{:else if activeView === 'dashboard'}
|
||||||
<DashboardView context={$context} />
|
<DashboardView context={$context} />
|
||||||
|
{:else if activeView === 'company-persons'}
|
||||||
|
<CompanyPersonsView context={$context} />
|
||||||
{:else if activeView === 'ratings'}
|
{:else if activeView === 'ratings'}
|
||||||
<RatingsView personName={$context.name.replace(/^Person /, '')} />
|
<RatingsView personName={$context.name.replace(/^Person /, '')} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
import { mention } from '$lib/actions/mention';
|
import { mention } from '$lib/actions/mention';
|
||||||
import RenderedMarkdown from './RenderedMarkdown.svelte';
|
import RenderedMarkdown from './RenderedMarkdown.svelte';
|
||||||
import MarkdownEditor from './MarkdownEditor.svelte';
|
import MarkdownEditor from './MarkdownEditor.svelte';
|
||||||
|
import EditableMarkdown from './EditableMarkdown.svelte';
|
||||||
import ConfirmDialog from './ConfirmDialog.svelte';
|
import ConfirmDialog from './ConfirmDialog.svelte';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
@ -202,30 +203,6 @@
|
||||||
return results.sort((a, b) => b.date.localeCompare(a.date));
|
return results.sort((a, b) => b.date.localeCompare(a.date));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Linked persons (company only): find person contexts mentioned alongside this company
|
|
||||||
const linkedPersons = liveQuery(async () => {
|
|
||||||
if (!isCompany) return [];
|
|
||||||
const allHistory = await db.historyEntries.filter(h => !h.deletedAt).toArray();
|
|
||||||
const personContexts = await db.contexts.filter(c => !c.deletedAt && c.type === 'person').toArray();
|
|
||||||
|
|
||||||
const matchingPersonNames = new Set<string>();
|
|
||||||
for (const h of allHistory) {
|
|
||||||
if (extractCompanies(h.text).includes(entityName)) {
|
|
||||||
for (const mention of extractMentions(h.text)) {
|
|
||||||
matchingPersonNames.add(mention);
|
|
||||||
}
|
|
||||||
for (const assignment of extractAssignments(h.text)) {
|
|
||||||
matchingPersonNames.add(assignment);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return personContexts.filter(p => {
|
|
||||||
const pName = p.name.replace(/^Person\s+/, '');
|
|
||||||
return matchingPersonNames.has(pName);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Archive / unarchive
|
// Archive / unarchive
|
||||||
async function handleArchive() {
|
async function handleArchive() {
|
||||||
await archiveContext(context.id);
|
await archiveContext(context.id);
|
||||||
|
|
@ -243,21 +220,6 @@
|
||||||
upsertContext({ id: context.id, meta: meta as any });
|
upsertContext({ id: context.id, meta: meta as any });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Local state for meta notes — only sync from DB on context switch
|
|
||||||
let metaNotes = $state('');
|
|
||||||
let metaNotesContextId = '';
|
|
||||||
$effect(() => {
|
|
||||||
if (context.id !== metaNotesContextId) {
|
|
||||||
metaNotesContextId = context.id;
|
|
||||||
metaNotes = (context.meta as any)?.notes ?? '';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let metaNotesTimer: ReturnType<typeof setTimeout>;
|
|
||||||
function handleMetaNotesChange(md: string) {
|
|
||||||
metaNotes = md;
|
|
||||||
clearTimeout(metaNotesTimer);
|
|
||||||
metaNotesTimer = setTimeout(() => updateMeta('notes', md), 500);
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Person sub-type selector (always visible for persons) -->
|
<!-- Person sub-type selector (always visible for persons) -->
|
||||||
|
|
@ -375,11 +337,11 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-2.5 flex flex-col">
|
<div class="mb-2.5 flex flex-col">
|
||||||
<label class="mb-1 text-sm text-[#aaa]">Notizen:</label>
|
<label class="mb-1 text-sm text-[#aaa]">Notizen:</label>
|
||||||
<MarkdownEditor
|
<EditableMarkdown
|
||||||
content={metaNotes}
|
content={(context.meta as CompanyMeta)?.notes ?? ''}
|
||||||
placeholder="Notizen..."
|
placeholder="Notizen..."
|
||||||
minHeight="60px"
|
minHeight="60px"
|
||||||
onchange={handleMetaNotesChange}
|
onchange={(md) => updateMeta('notes', md)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
|
|
@ -406,11 +368,11 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-2.5 flex flex-col">
|
<div class="mb-2.5 flex flex-col">
|
||||||
<label class="mb-1 text-sm text-[#aaa]">Notizen:</label>
|
<label class="mb-1 text-sm text-[#aaa]">Notizen:</label>
|
||||||
<MarkdownEditor
|
<EditableMarkdown
|
||||||
content={metaNotes}
|
content={(context.meta as PersonMeta)?.notes ?? ''}
|
||||||
placeholder="Notizen..."
|
placeholder="Notizen..."
|
||||||
minHeight="60px"
|
minHeight="60px"
|
||||||
onchange={handleMetaNotesChange}
|
onchange={(md) => updateMeta('notes', md)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
@ -509,24 +471,6 @@
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<!-- Linked persons (company only) -->
|
|
||||||
{#if isCompany}
|
|
||||||
{@const persons = $linkedPersons ?? []}
|
|
||||||
{#if persons.length > 0}
|
|
||||||
<div class="mb-5 rounded-lg border border-[#444] bg-sidebar p-4">
|
|
||||||
<div class="mb-3 text-sm font-bold text-[#00b894]">Verknüpfte Personen</div>
|
|
||||||
{#each persons as p (p.id)}
|
|
||||||
<button
|
|
||||||
class="mb-1.5 block text-sm text-[#ccc] hover:text-white"
|
|
||||||
onclick={() => goto(`/context/${p.id}`)}
|
|
||||||
>
|
|
||||||
{p.name.replace(/^Person\s+/, '')}
|
|
||||||
</button>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<!-- Activity log -->
|
<!-- Activity log -->
|
||||||
<div class="rounded-lg border-l-[5px] {isProject ? 'border-l-project-tag' : isCompany ? 'border-l-company-tag' : 'border-l-info'} bg-card-bg p-4">
|
<div class="rounded-lg border-l-[5px] {isProject ? 'border-l-project-tag' : isCompany ? 'border-l-company-tag' : 'border-l-info'} bg-card-bg p-4">
|
||||||
<div class="mb-2.5 border-b border-[#444] pb-1 text-xl font-bold {isProject ? 'text-[#a29bfe]' : isCompany ? 'text-[#00b894]' : 'text-info'}">
|
<div class="mb-2.5 border-b border-[#444] pb-1 text-xl font-bold {isProject ? 'text-[#a29bfe]' : isCompany ? 'text-[#00b894]' : 'text-info'}">
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,91 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import RenderedMarkdown from './RenderedMarkdown.svelte';
|
||||||
|
import MarkdownEditor from './MarkdownEditor.svelte';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
content: string;
|
||||||
|
placeholder?: string;
|
||||||
|
minHeight?: string;
|
||||||
|
onchange?: (markdown: string) => void;
|
||||||
|
class?: string;
|
||||||
|
}
|
||||||
|
let { content, placeholder = 'Click to edit...', minHeight = '60px', onchange, class: className = '' }: Props = $props();
|
||||||
|
|
||||||
|
let editing = $state(false);
|
||||||
|
let editorContent = $state('');
|
||||||
|
let wrapper: HTMLDivElement;
|
||||||
|
let editorRef: MarkdownEditor;
|
||||||
|
|
||||||
|
function enterEdit() {
|
||||||
|
editorContent = content;
|
||||||
|
editing = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function save() {
|
||||||
|
const md = editorRef?.getMarkdown() ?? editorContent;
|
||||||
|
editing = false;
|
||||||
|
if (md !== content) {
|
||||||
|
onchange?.(md);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancel() {
|
||||||
|
editing = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleFocusOut(e: FocusEvent) {
|
||||||
|
if (!editing) return;
|
||||||
|
const related = e.relatedTarget as Node | null;
|
||||||
|
// Stay in edit mode if focus moved within the wrapper (Tiptap nested elements)
|
||||||
|
if (related && wrapper?.contains(related)) return;
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleKeydown(e: KeyboardEvent) {
|
||||||
|
if (e.key === 'Escape') {
|
||||||
|
e.preventDefault();
|
||||||
|
cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||||
|
<div
|
||||||
|
bind:this={wrapper}
|
||||||
|
class="editable-markdown {className}"
|
||||||
|
onfocusout={handleFocusOut}
|
||||||
|
onkeydown={handleKeydown}
|
||||||
|
>
|
||||||
|
{#if editing}
|
||||||
|
<MarkdownEditor
|
||||||
|
bind:this={editorRef}
|
||||||
|
content={editorContent}
|
||||||
|
{placeholder}
|
||||||
|
{minHeight}
|
||||||
|
onchange={(md) => editorContent = md}
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
|
<!-- svelte-ignore a11y_click_events_have_key_events a11y_no_static_element_interactions -->
|
||||||
|
<div class="editable-markdown-read" onclick={enterEdit}>
|
||||||
|
{#if content}
|
||||||
|
<RenderedMarkdown text={content} />
|
||||||
|
{:else}
|
||||||
|
<span class="text-sm text-[#666] italic">{placeholder}</span>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.editable-markdown-read {
|
||||||
|
cursor: text;
|
||||||
|
min-height: 2em;
|
||||||
|
padding: 0.25rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
}
|
||||||
|
.editable-markdown-read:hover {
|
||||||
|
border-color: #444;
|
||||||
|
background: rgba(255, 255, 255, 0.02);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { goto } from '$app/navigation';
|
||||||
|
import { softDeleteContext, toggleFavorite } from '$lib/db/repositories';
|
||||||
|
import ConfirmDialog from '$lib/components/ConfirmDialog.svelte';
|
||||||
|
import type { AgendaContext, PersonMeta, PersonSubType } from '@ka-note/shared';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
persons: AgendaContext[];
|
||||||
|
}
|
||||||
|
let { persons }: Props = $props();
|
||||||
|
|
||||||
|
function displayName(name: string): string {
|
||||||
|
return name.replace('Person ', '');
|
||||||
|
}
|
||||||
|
|
||||||
|
const subTypeLabels: Record<PersonSubType, string> = {
|
||||||
|
contact: 'Kontakt',
|
||||||
|
employee: 'Mitarbeiter',
|
||||||
|
colleague: 'Kollege'
|
||||||
|
};
|
||||||
|
|
||||||
|
const subTypeColors: Record<PersonSubType, string> = {
|
||||||
|
contact: 'bg-[#555] text-[#ccc]',
|
||||||
|
employee: 'bg-accent/30 text-accent',
|
||||||
|
colleague: 'bg-[#00b894]/30 text-[#00b894]'
|
||||||
|
};
|
||||||
|
|
||||||
|
let confirmDeleteId = $state<string | null>(null);
|
||||||
|
let confirmDeleteName = $state('');
|
||||||
|
|
||||||
|
function requestDelete(id: string, name: string) {
|
||||||
|
confirmDeleteId = id;
|
||||||
|
confirmDeleteName = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleDelete() {
|
||||||
|
if (confirmDeleteId) await softDeleteContext(confirmDeleteId);
|
||||||
|
confirmDeleteId = null;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if persons.length > 0}
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
{#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'}
|
||||||
|
>★</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}`)}
|
||||||
|
>
|
||||||
|
<span class="font-bold text-white">{displayName(ctx.name)}</span>
|
||||||
|
<span class="rounded px-2 py-0.5 text-xs {subTypeColors[subType]}">{subTypeLabels[subType]}</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="rounded border border-[#444] bg-sidebar px-2.5 py-2.5 text-[#888] transition-colors hover:border-red-900 hover:text-red-400"
|
||||||
|
onclick={() => requestDelete(ctx.id, displayName(ctx.name))}
|
||||||
|
title="Löschen"
|
||||||
|
>🗑</button>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div class="text-muted">Keine Personen vorhanden.</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if confirmDeleteId}
|
||||||
|
<ConfirmDialog
|
||||||
|
message={`Person \u201E${confirmDeleteName}\u201C wirklich löschen?`}
|
||||||
|
onconfirm={handleDelete}
|
||||||
|
oncancel={() => confirmDeleteId = null}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
const isDailyLog = $derived(context.id === 'daily-log');
|
const isDailyLog = $derived(context.id === 'daily-log');
|
||||||
|
|
||||||
const isPerson = $derived(context.type === 'person');
|
const isPerson = $derived(context.type === 'person');
|
||||||
|
const isCompany = $derived(context.type === 'company');
|
||||||
const isEmployee = $derived(isPerson && ((context.meta as PersonMeta | null)?.personSubType ?? 'contact') === 'employee');
|
const isEmployee = $derived(isPerson && ((context.meta as PersonMeta | null)?.personSubType ?? 'contact') === 'employee');
|
||||||
|
|
||||||
const tabs = $derived<Tab[]>(
|
const tabs = $derived<Tab[]>(
|
||||||
|
|
@ -29,6 +30,7 @@
|
||||||
]
|
]
|
||||||
: [
|
: [
|
||||||
{ id: 'dashboard', label: `Dashboard: ${context.name.replace(/^(Project |Person |Firma )/, '')}` },
|
{ id: 'dashboard', label: `Dashboard: ${context.name.replace(/^(Project |Person |Firma )/, '')}` },
|
||||||
|
...(isCompany ? [{ id: 'company-persons', label: 'Personen' }] : []),
|
||||||
...(isEmployee ? [{ id: 'ratings', label: 'Bewertungen' }] : [])
|
...(isEmployee ? [{ id: 'ratings', label: 'Bewertungen' }] : [])
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,84 +1,16 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { goto } from '$app/navigation';
|
|
||||||
import { liveQuery } from 'dexie';
|
import { liveQuery } from 'dexie';
|
||||||
import { db } from '$lib/db/schema';
|
import { db } from '$lib/db/schema';
|
||||||
import { softDeleteContext, toggleFavorite } from '$lib/db/repositories';
|
import PersonList from '$lib/components/PersonList.svelte';
|
||||||
import ConfirmDialog from '$lib/components/ConfirmDialog.svelte';
|
|
||||||
import type { PersonMeta, PersonSubType } from '@ka-note/shared';
|
|
||||||
|
|
||||||
const allPersons = liveQuery(() =>
|
const allPersons = liveQuery(() =>
|
||||||
db.contexts.filter(c => !c.deletedAt && c.type === 'person').sortBy('sortOrder')
|
db.contexts.filter(c => !c.deletedAt && c.type === 'person').sortBy('sortOrder')
|
||||||
);
|
);
|
||||||
|
|
||||||
const persons = $derived($allPersons ?? []);
|
const persons = $derived($allPersons ?? []);
|
||||||
|
|
||||||
function displayName(name: string): string {
|
|
||||||
return name.replace('Person ', '');
|
|
||||||
}
|
|
||||||
|
|
||||||
const subTypeLabels: Record<PersonSubType, string> = {
|
|
||||||
contact: 'Kontakt',
|
|
||||||
employee: 'Mitarbeiter',
|
|
||||||
colleague: 'Kollege'
|
|
||||||
};
|
|
||||||
|
|
||||||
const subTypeColors: Record<PersonSubType, string> = {
|
|
||||||
contact: 'bg-[#555] text-[#ccc]',
|
|
||||||
employee: 'bg-accent/30 text-accent',
|
|
||||||
colleague: 'bg-[#00b894]/30 text-[#00b894]'
|
|
||||||
};
|
|
||||||
|
|
||||||
let confirmDeleteId = $state<string | null>(null);
|
|
||||||
let confirmDeleteName = $state('');
|
|
||||||
|
|
||||||
function requestDelete(id: string, name: string) {
|
|
||||||
confirmDeleteId = id;
|
|
||||||
confirmDeleteName = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleDelete() {
|
|
||||||
if (confirmDeleteId) await softDeleteContext(confirmDeleteId);
|
|
||||||
confirmDeleteId = null;
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="mx-auto max-w-3xl p-6">
|
<div class="mx-auto max-w-3xl p-6">
|
||||||
<h1 class="mb-6 text-2xl font-bold text-white">Alle Personen</h1>
|
<h1 class="mb-6 text-2xl font-bold text-white">Alle Personen</h1>
|
||||||
|
<PersonList {persons} />
|
||||||
{#if persons.length > 0}
|
|
||||||
<div class="flex flex-col gap-2">
|
|
||||||
{#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'}
|
|
||||||
>★</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}`)}
|
|
||||||
>
|
|
||||||
<span class="font-bold text-white">{displayName(ctx.name)}</span>
|
|
||||||
<span class="rounded px-2 py-0.5 text-xs {subTypeColors[subType]}">{subTypeLabels[subType]}</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="rounded border border-[#444] bg-sidebar px-2.5 py-2.5 text-[#888] transition-colors hover:border-red-900 hover:text-red-400"
|
|
||||||
onclick={() => requestDelete(ctx.id, displayName(ctx.name))}
|
|
||||||
title="Löschen"
|
|
||||||
>🗑</button>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
{:else}
|
|
||||||
<div class="text-muted">Keine Personen vorhanden.</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if confirmDeleteId}
|
|
||||||
<ConfirmDialog
|
|
||||||
message={`Person \u201E${confirmDeleteName}\u201C wirklich löschen?`}
|
|
||||||
onconfirm={handleDelete}
|
|
||||||
oncancel={() => confirmDeleteId = null}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue