mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 20:21:09 +02:00
fix(manacore/web): make notes and finance workbench panels directly usable
Notes: always-visible compose field at top (title + content), expands on focus, Cmd+Enter to save. Note list below. Finance: always-visible quick-add with type toggle, amount + description inline, category chips. No hidden button — ready to use immediately. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
c01eccb852
commit
4ec4694f8f
2 changed files with 346 additions and 409 deletions
|
|
@ -1,6 +1,6 @@
|
|||
<!--
|
||||
Finance — Workbench ListView
|
||||
Monthly overview with quick-add transaction and category breakdown.
|
||||
Always-visible quick-add at top, monthly summary, recent transactions.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import {
|
||||
|
|
@ -47,16 +47,15 @@
|
|||
|
||||
let catMap = $derived(new Map(categories.map((c) => [c.id, c])));
|
||||
|
||||
// Recent transactions (last 10)
|
||||
let recentTxs = $derived(monthTxs.slice(0, 10));
|
||||
let grouped = $derived(groupByDate(recentTxs));
|
||||
|
||||
// Quick add
|
||||
let showAdd = $state(false);
|
||||
// Always-visible quick add
|
||||
let addType = $state<TransactionType>('expense');
|
||||
let addAmount = $state('');
|
||||
let addDesc = $state('');
|
||||
let addCatId = $state<string | null>(null);
|
||||
let showCats = $state(false);
|
||||
|
||||
let filteredCats = $derived(categories.filter((c) => c.type === addType));
|
||||
|
||||
|
|
@ -75,86 +74,91 @@
|
|||
addAmount = '';
|
||||
addDesc = '';
|
||||
addCatId = null;
|
||||
showAdd = false;
|
||||
showCats = false;
|
||||
}
|
||||
|
||||
function handleKeydown(e: KeyboardEvent) {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
handleAdd(e);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="finance-list-view">
|
||||
<!-- Always-visible Quick Add -->
|
||||
<form class="quick-add" onsubmit={handleAdd}>
|
||||
<div class="type-toggle">
|
||||
<button
|
||||
type="button"
|
||||
class="type-btn"
|
||||
class:active={addType === 'expense'}
|
||||
onclick={() => {
|
||||
addType = 'expense';
|
||||
addCatId = null;
|
||||
}}>Ausgabe</button
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="type-btn inc"
|
||||
class:active={addType === 'income'}
|
||||
onclick={() => {
|
||||
addType = 'income';
|
||||
addCatId = null;
|
||||
}}>Einnahme</button
|
||||
>
|
||||
</div>
|
||||
<div class="input-row">
|
||||
<input
|
||||
class="amount-input"
|
||||
type="text"
|
||||
inputmode="decimal"
|
||||
placeholder="0,00"
|
||||
bind:value={addAmount}
|
||||
onkeydown={handleKeydown}
|
||||
/>
|
||||
<span class="currency">€</span>
|
||||
<input
|
||||
class="desc-input"
|
||||
type="text"
|
||||
placeholder="Beschreibung..."
|
||||
bind:value={addDesc}
|
||||
onkeydown={handleKeydown}
|
||||
/>
|
||||
<button type="submit" class="submit-btn" disabled={!addAmount || !addDesc.trim()}>+</button>
|
||||
</div>
|
||||
<!-- Category chips -->
|
||||
<div class="cat-row">
|
||||
{#each filteredCats as cat (cat.id)}
|
||||
<button
|
||||
type="button"
|
||||
class="cat-chip"
|
||||
class:selected={addCatId === cat.id}
|
||||
onclick={() => (addCatId = addCatId === cat.id ? null : cat.id)}
|
||||
>{cat.emoji} {cat.name}</button
|
||||
>
|
||||
{/each}
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- Monthly Summary -->
|
||||
<div class="month-summary">
|
||||
<div class="summary-row">
|
||||
<span class="summary-label">Einnahmen</span>
|
||||
<div class="summary-item">
|
||||
<span class="summary-value income">+{formatCurrency(income)}</span>
|
||||
<span class="summary-label">Einnahmen</span>
|
||||
</div>
|
||||
<div class="summary-row">
|
||||
<span class="summary-label">Ausgaben</span>
|
||||
<div class="summary-item">
|
||||
<span class="summary-value expense">-{formatCurrency(expenses)}</span>
|
||||
<span class="summary-label">Ausgaben</span>
|
||||
</div>
|
||||
<div class="summary-row balance">
|
||||
<span class="summary-label">Bilanz</span>
|
||||
<div class="summary-item">
|
||||
<span class="summary-value" class:income={balance >= 0} class:expense={balance < 0}>
|
||||
{balance >= 0 ? '+' : ''}{formatCurrency(balance)}
|
||||
</span>
|
||||
<span class="summary-label">Bilanz</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add Button -->
|
||||
{#if !showAdd}
|
||||
<button class="add-btn" onclick={() => (showAdd = true)}>+ Transaktion</button>
|
||||
{/if}
|
||||
|
||||
<!-- Quick Add Form -->
|
||||
{#if showAdd}
|
||||
<form class="add-form" onsubmit={handleAdd}>
|
||||
<div class="type-toggle">
|
||||
<button
|
||||
type="button"
|
||||
class="type-btn"
|
||||
class:active={addType === 'expense'}
|
||||
onclick={() => (addType = 'expense')}>Ausgabe</button
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="type-btn income"
|
||||
class:active={addType === 'income'}
|
||||
onclick={() => (addType = 'income')}>Einnahme</button
|
||||
>
|
||||
</div>
|
||||
<div class="add-row">
|
||||
<input
|
||||
class="amount-input"
|
||||
type="text"
|
||||
inputmode="decimal"
|
||||
placeholder="0,00"
|
||||
bind:value={addAmount}
|
||||
autofocus
|
||||
/>
|
||||
<span class="currency">\u20ac</span>
|
||||
</div>
|
||||
<input class="desc-input" type="text" placeholder="Beschreibung..." bind:value={addDesc} />
|
||||
<div class="cat-row">
|
||||
{#each filteredCats as cat (cat.id)}
|
||||
<button
|
||||
type="button"
|
||||
class="cat-chip"
|
||||
class:selected={addCatId === cat.id}
|
||||
onclick={() => (addCatId = addCatId === cat.id ? null : cat.id)}
|
||||
>
|
||||
<span>{cat.emoji}</span>
|
||||
<span>{cat.name}</span>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
<div class="add-actions">
|
||||
<button type="button" class="btn-cancel" onclick={() => (showAdd = false)}>Abbrechen</button
|
||||
>
|
||||
<button type="submit" class="btn-save" disabled={!addAmount || !addDesc.trim()}
|
||||
>Hinzufügen</button
|
||||
>
|
||||
</div>
|
||||
</form>
|
||||
{/if}
|
||||
|
||||
<!-- Recent Transactions -->
|
||||
{#if recentTxs.length > 0}
|
||||
<div class="tx-list">
|
||||
|
|
@ -163,10 +167,10 @@
|
|||
{#each dayTxs as tx (tx.id)}
|
||||
{@const cat = tx.categoryId ? catMap.get(tx.categoryId) : null}
|
||||
<div class="tx-row">
|
||||
<span class="tx-cat-emoji">{cat?.emoji ?? '\ud83d\udcb3'}</span>
|
||||
<span class="tx-emoji">{cat?.emoji ?? '\ud83d\udcb3'}</span>
|
||||
<div class="tx-info">
|
||||
<span class="tx-desc">{tx.description}</span>
|
||||
{#if cat}<span class="tx-cat-name">{cat.name}</span>{/if}
|
||||
{#if cat}<span class="tx-cat">{cat.name}</span>{/if}
|
||||
</div>
|
||||
<span
|
||||
class="tx-amount"
|
||||
|
|
@ -179,87 +183,27 @@
|
|||
{/each}
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if txs.length === 0 && !showAdd}
|
||||
<div class="empty">
|
||||
<p>Noch keine Transaktionen.</p>
|
||||
<button class="add-btn" onclick={() => (showAdd = true)}>Erste Transaktion</button>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="empty">Noch keine Transaktionen diesen Monat.</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.finance-list-view {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.625rem;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
/* ── Summary ─────────────────────────────────── */
|
||||
.month-summary {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
padding: 0.625rem;
|
||||
border-radius: 0.75rem;
|
||||
background: var(--color-surface, rgba(255, 255, 255, 0.04));
|
||||
border: 1px solid var(--color-border, rgba(255, 255, 255, 0.08));
|
||||
}
|
||||
|
||||
.summary-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
|
||||
.summary-row.balance {
|
||||
padding-top: 0.375rem;
|
||||
margin-top: 0.25rem;
|
||||
border-top: 1px solid var(--color-border, rgba(255, 255, 255, 0.08));
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.summary-label {
|
||||
color: var(--color-muted-foreground);
|
||||
}
|
||||
|
||||
.summary-value {
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.summary-value.income {
|
||||
color: #22c55e;
|
||||
}
|
||||
.summary-value.expense {
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
/* ── Add Form ────────────────────────────────── */
|
||||
.add-btn {
|
||||
width: 100%;
|
||||
padding: 0.5rem;
|
||||
border-radius: 0.5rem;
|
||||
background: var(--color-primary, #6366f1);
|
||||
color: white;
|
||||
border: none;
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: filter 0.15s;
|
||||
}
|
||||
.add-btn:hover {
|
||||
filter: brightness(1.1);
|
||||
}
|
||||
|
||||
.add-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
padding: 0.625rem;
|
||||
border-radius: 0.75rem;
|
||||
background: var(--color-surface, rgba(255, 255, 255, 0.06));
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
/* ── Quick Add (always visible) ─────────────── */
|
||||
.quick-add {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.375rem;
|
||||
padding: 0.5rem;
|
||||
border-radius: 0.625rem;
|
||||
background: var(--color-surface, rgba(255, 255, 255, 0.04));
|
||||
border: 1px solid var(--color-border, rgba(255, 255, 255, 0.1));
|
||||
}
|
||||
|
||||
|
|
@ -267,46 +211,49 @@
|
|||
display: flex;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.type-btn {
|
||||
flex: 1;
|
||||
padding: 0.375rem;
|
||||
padding: 0.3rem;
|
||||
border-radius: 0.375rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 600;
|
||||
background: transparent;
|
||||
color: var(--color-muted-foreground);
|
||||
border: 1px solid var(--color-border, rgba(255, 255, 255, 0.1));
|
||||
border: 1px solid var(--color-border, rgba(255, 255, 255, 0.08));
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
.type-btn.active {
|
||||
background: rgba(239, 68, 68, 0.15);
|
||||
background: rgba(239, 68, 68, 0.12);
|
||||
color: #ef4444;
|
||||
border-color: rgba(239, 68, 68, 0.3);
|
||||
border-color: rgba(239, 68, 68, 0.25);
|
||||
}
|
||||
.type-btn.income.active {
|
||||
background: rgba(34, 197, 94, 0.15);
|
||||
.type-btn.inc.active {
|
||||
background: rgba(34, 197, 94, 0.12);
|
||||
color: #22c55e;
|
||||
border-color: rgba(34, 197, 94, 0.3);
|
||||
border-color: rgba(34, 197, 94, 0.25);
|
||||
}
|
||||
|
||||
.add-row {
|
||||
.input-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.amount-input {
|
||||
flex: 1;
|
||||
width: 4.5rem;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-bottom: 2px solid var(--color-border, rgba(255, 255, 255, 0.15));
|
||||
color: var(--color-foreground);
|
||||
font-size: 1.25rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 700;
|
||||
padding: 0.25rem 0;
|
||||
outline: none;
|
||||
text-align: right;
|
||||
font-variant-numeric: tabular-nums;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.amount-input:focus {
|
||||
border-color: var(--color-primary, #6366f1);
|
||||
|
|
@ -318,80 +265,110 @@
|
|||
|
||||
.currency {
|
||||
color: var(--color-muted-foreground);
|
||||
font-size: 1rem;
|
||||
font-size: 0.8125rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.desc-input {
|
||||
flex: 1;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-bottom: 1px solid var(--color-border, rgba(255, 255, 255, 0.1));
|
||||
border-bottom: 2px solid var(--color-border, rgba(255, 255, 255, 0.15));
|
||||
color: var(--color-foreground);
|
||||
font-size: 0.8125rem;
|
||||
padding: 0.375rem 0;
|
||||
padding: 0.25rem 0;
|
||||
outline: none;
|
||||
min-width: 0;
|
||||
}
|
||||
.desc-input:focus {
|
||||
border-color: var(--color-primary, #6366f1);
|
||||
}
|
||||
.desc-input::placeholder {
|
||||
color: var(--color-muted-foreground);
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
width: 1.75rem;
|
||||
height: 1.75rem;
|
||||
border-radius: 0.375rem;
|
||||
background: var(--color-primary, #6366f1);
|
||||
color: white;
|
||||
border: none;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
transition: filter 0.15s;
|
||||
}
|
||||
.submit-btn:hover:not(:disabled) {
|
||||
filter: brightness(1.1);
|
||||
}
|
||||
.submit-btn:disabled {
|
||||
opacity: 0.3;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.cat-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.25rem;
|
||||
gap: 0.2rem;
|
||||
}
|
||||
|
||||
.cat-chip {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
padding: 0.2rem 0.4rem;
|
||||
border-radius: 9999px;
|
||||
font-size: 0.6875rem;
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
border: 1px solid var(--color-border, rgba(255, 255, 255, 0.1));
|
||||
font-size: 0.625rem;
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
border: 1px solid var(--color-border, rgba(255, 255, 255, 0.08));
|
||||
color: var(--color-foreground);
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
.cat-chip:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
.cat-chip.selected {
|
||||
border-color: var(--color-primary, #6366f1);
|
||||
background: rgba(99, 102, 241, 0.15);
|
||||
background: rgba(99, 102, 241, 0.12);
|
||||
}
|
||||
|
||||
.add-actions {
|
||||
/* ── Summary ─────────────────────────────────── */
|
||||
.month-summary {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 0.375rem;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
.btn-cancel,
|
||||
.btn-save {
|
||||
padding: 0.3rem 0.625rem;
|
||||
border-radius: 0.375rem;
|
||||
|
||||
.summary-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 0.375rem 0.25rem;
|
||||
border-radius: 0.5rem;
|
||||
background: var(--color-surface, rgba(255, 255, 255, 0.04));
|
||||
border: 1px solid var(--color-border, rgba(255, 255, 255, 0.06));
|
||||
}
|
||||
|
||||
.summary-value {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
font-weight: 700;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.btn-cancel {
|
||||
background: transparent;
|
||||
.summary-value.income {
|
||||
color: #22c55e;
|
||||
}
|
||||
.summary-value.expense {
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
.summary-label {
|
||||
font-size: 0.5625rem;
|
||||
color: var(--color-muted-foreground);
|
||||
}
|
||||
.btn-cancel:hover {
|
||||
background: var(--color-muted, rgba(255, 255, 255, 0.08));
|
||||
}
|
||||
.btn-save {
|
||||
background: var(--color-primary, #6366f1);
|
||||
color: white;
|
||||
}
|
||||
.btn-save:hover:not(:disabled) {
|
||||
filter: brightness(1.1);
|
||||
}
|
||||
.btn-save:disabled {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.03em;
|
||||
}
|
||||
|
||||
/* ── Transaction List ────────────────────────── */
|
||||
|
|
@ -402,24 +379,23 @@
|
|||
}
|
||||
|
||||
.day-label {
|
||||
font-size: 0.6875rem;
|
||||
font-size: 0.625rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: var(--color-muted-foreground);
|
||||
padding: 0.375rem 0 0.125rem;
|
||||
padding: 0.25rem 0 0.125rem;
|
||||
}
|
||||
|
||||
.tx-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.375rem 0.25rem;
|
||||
border-radius: 0.375rem;
|
||||
gap: 0.375rem;
|
||||
padding: 0.25rem 0.125rem;
|
||||
}
|
||||
|
||||
.tx-cat-emoji {
|
||||
font-size: 0.875rem;
|
||||
.tx-emoji {
|
||||
font-size: 0.8125rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
|
|
@ -431,20 +407,20 @@
|
|||
}
|
||||
|
||||
.tx-desc {
|
||||
font-size: 0.8125rem;
|
||||
font-size: 0.75rem;
|
||||
color: var(--color-foreground);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tx-cat-name {
|
||||
font-size: 0.6875rem;
|
||||
.tx-cat {
|
||||
font-size: 0.625rem;
|
||||
color: var(--color-muted-foreground);
|
||||
}
|
||||
|
||||
.tx-amount {
|
||||
font-size: 0.8125rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
font-variant-numeric: tabular-nums;
|
||||
flex-shrink: 0;
|
||||
|
|
@ -456,15 +432,10 @@
|
|||
color: #ef4444;
|
||||
}
|
||||
|
||||
/* ── Empty ──────────────────────────────────── */
|
||||
.empty {
|
||||
text-align: center;
|
||||
color: var(--color-muted-foreground);
|
||||
font-size: 0.875rem;
|
||||
padding: 2rem 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
font-size: 0.8125rem;
|
||||
padding: 1.5rem 0;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
<!--
|
||||
Notes — Workbench ListView
|
||||
Compact note list with search, inline create, and click to edit.
|
||||
Always-visible compose field at top, note list below.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { useAllNotes, searchNotes, getPreview, formatRelativeTime } from './queries';
|
||||
import { notesStore } from './stores/notes.svelte';
|
||||
import type { Note } from './types';
|
||||
import { NOTE_COLORS } from './types';
|
||||
import type { ViewProps } from '$lib/app-registry';
|
||||
|
||||
let { navigate, goBack, params }: ViewProps = $props();
|
||||
|
|
@ -22,28 +21,36 @@
|
|||
});
|
||||
|
||||
let searchQuery = $state('');
|
||||
let showCreate = $state(false);
|
||||
let editingId = $state<string | null>(null);
|
||||
let editTitle = $state('');
|
||||
let editContent = $state('');
|
||||
let newTitle = $state('');
|
||||
let newContent = $state('');
|
||||
let newColor = $state<string | null>(null);
|
||||
|
||||
// Always-visible compose field
|
||||
let composeTitle = $state('');
|
||||
let composeContent = $state('');
|
||||
let composeFocused = $state(false);
|
||||
|
||||
let filtered = $derived(searchNotes(notes, searchQuery));
|
||||
|
||||
async function handleCreate(e: Event) {
|
||||
e.preventDefault();
|
||||
if (!newTitle.trim() && !newContent.trim()) return;
|
||||
async function handleCompose() {
|
||||
if (!composeTitle.trim() && !composeContent.trim()) return;
|
||||
await notesStore.createNote({
|
||||
title: newTitle.trim() || 'Unbenannt',
|
||||
content: newContent,
|
||||
color: newColor,
|
||||
title: composeTitle.trim() || 'Unbenannt',
|
||||
content: composeContent,
|
||||
});
|
||||
newTitle = '';
|
||||
newContent = '';
|
||||
newColor = null;
|
||||
showCreate = false;
|
||||
composeTitle = '';
|
||||
composeContent = '';
|
||||
composeFocused = false;
|
||||
}
|
||||
|
||||
function handleComposeKeydown(e: KeyboardEvent) {
|
||||
if (e.key === 'Enter' && e.metaKey) {
|
||||
e.preventDefault();
|
||||
handleCompose();
|
||||
}
|
||||
if (e.key === 'Escape') {
|
||||
composeFocused = false;
|
||||
}
|
||||
}
|
||||
|
||||
function startEdit(note: Note) {
|
||||
|
|
@ -64,66 +71,63 @@
|
|||
function cancelEdit() {
|
||||
editingId = null;
|
||||
}
|
||||
|
||||
function handleEditKeydown(e: KeyboardEvent) {
|
||||
if (e.key === 'Escape') cancelEdit();
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="notes-list-view">
|
||||
<!-- Search -->
|
||||
<div class="search-row">
|
||||
<input class="search-input" type="text" placeholder="Suchen..." bind:value={searchQuery} />
|
||||
<button class="add-btn" onclick={() => (showCreate = !showCreate)} title="Neue Notiz">
|
||||
+
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Inline Create -->
|
||||
{#if showCreate}
|
||||
<form class="create-form" onsubmit={handleCreate}>
|
||||
<input
|
||||
class="create-title"
|
||||
type="text"
|
||||
placeholder="Titel..."
|
||||
bind:value={newTitle}
|
||||
autofocus
|
||||
/>
|
||||
<!-- Always-visible compose area -->
|
||||
<div class="compose-area" class:expanded={composeFocused || composeTitle || composeContent}>
|
||||
<input
|
||||
class="compose-title"
|
||||
type="text"
|
||||
placeholder="Neue Notiz..."
|
||||
bind:value={composeTitle}
|
||||
onfocus={() => (composeFocused = true)}
|
||||
onkeydown={handleComposeKeydown}
|
||||
/>
|
||||
{#if composeFocused || composeTitle || composeContent}
|
||||
<textarea
|
||||
class="create-content"
|
||||
placeholder="Notiz schreiben..."
|
||||
bind:value={newContent}
|
||||
class="compose-content"
|
||||
placeholder="Schreibe etwas..."
|
||||
bind:value={composeContent}
|
||||
rows="3"
|
||||
onkeydown={handleComposeKeydown}
|
||||
></textarea>
|
||||
<div class="create-footer">
|
||||
<div class="color-row">
|
||||
{#each NOTE_COLORS as c}
|
||||
<button
|
||||
type="button"
|
||||
class="color-dot"
|
||||
class:selected={newColor === c}
|
||||
style:background={c ?? 'var(--color-muted-foreground)'}
|
||||
style:opacity={c ? 1 : 0.4}
|
||||
onclick={() => (newColor = c)}
|
||||
></button>
|
||||
{/each}
|
||||
</div>
|
||||
<div class="create-actions">
|
||||
<button type="button" class="btn-cancel" onclick={() => (showCreate = false)}
|
||||
>Abbrechen</button
|
||||
<div class="compose-footer">
|
||||
<span class="compose-hint">⌘+Enter zum Speichern</span>
|
||||
<div class="compose-actions">
|
||||
<button
|
||||
class="btn-cancel"
|
||||
onclick={() => {
|
||||
composeTitle = '';
|
||||
composeContent = '';
|
||||
composeFocused = false;
|
||||
}}>Abbrechen</button
|
||||
>
|
||||
<button
|
||||
class="btn-save"
|
||||
onclick={handleCompose}
|
||||
disabled={!composeTitle.trim() && !composeContent.trim()}>Speichern</button
|
||||
>
|
||||
<button type="submit" class="btn-save">Erstellen</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Search (only when notes exist) -->
|
||||
{#if notes.length > 3}
|
||||
<input class="search-input" type="text" placeholder="Suchen..." bind:value={searchQuery} />
|
||||
{/if}
|
||||
|
||||
<!-- Note List -->
|
||||
<div class="note-list">
|
||||
{#each filtered as note (note.id)}
|
||||
{#if editingId === note.id}
|
||||
<!-- Inline Edit -->
|
||||
<div class="note-card editing" onkeydown={handleEditKeydown}>
|
||||
<div
|
||||
class="note-card editing"
|
||||
onkeydown={(e) => {
|
||||
if (e.key === 'Escape') cancelEdit();
|
||||
}}
|
||||
>
|
||||
<input class="edit-title" type="text" bind:value={editTitle} autofocus />
|
||||
<textarea class="edit-content" bind:value={editContent} rows="4"></textarea>
|
||||
<div class="edit-actions">
|
||||
|
|
@ -132,7 +136,6 @@
|
|||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<!-- Note Card -->
|
||||
<button
|
||||
class="note-card"
|
||||
class:pinned={note.isPinned}
|
||||
|
|
@ -142,7 +145,7 @@
|
|||
<div class="note-header">
|
||||
<span class="note-title">{note.title || 'Unbenannt'}</span>
|
||||
{#if note.isPinned}
|
||||
<span class="pin-icon" title="Angepinnt">📌</span>
|
||||
<span class="pin-icon">📌</span>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="note-preview">{getPreview(note.content)}</div>
|
||||
|
|
@ -152,13 +155,8 @@
|
|||
{/each}
|
||||
</div>
|
||||
|
||||
{#if notes.length === 0 && !showCreate}
|
||||
<div class="empty">
|
||||
<p>Noch keine Notizen.</p>
|
||||
<button class="empty-add-btn" onclick={() => (showCreate = true)}
|
||||
>Erste Notiz erstellen</button
|
||||
>
|
||||
</div>
|
||||
{#if notes.length === 0 && !composeFocused}
|
||||
<div class="empty">Tippe oben, um deine erste Notiz zu schreiben.</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
|
@ -170,13 +168,73 @@
|
|||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.search-row {
|
||||
/* ── Compose Area (always visible) ──────────── */
|
||||
.compose-area {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
padding: 0.5rem 0.625rem;
|
||||
border-radius: 0.625rem;
|
||||
background: var(--color-surface, rgba(255, 255, 255, 0.04));
|
||||
border: 1px solid var(--color-border, rgba(255, 255, 255, 0.1));
|
||||
transition:
|
||||
border-color 0.15s,
|
||||
background 0.15s;
|
||||
}
|
||||
|
||||
.compose-area.expanded {
|
||||
border-color: var(--color-primary, #6366f1);
|
||||
background: var(--color-surface, rgba(255, 255, 255, 0.06));
|
||||
}
|
||||
|
||||
.compose-title {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--color-foreground);
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
padding: 0.25rem 0;
|
||||
outline: none;
|
||||
}
|
||||
.compose-title::placeholder {
|
||||
color: var(--color-muted-foreground);
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.compose-content {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--color-foreground);
|
||||
font-size: 0.8125rem;
|
||||
padding: 0.25rem 0;
|
||||
outline: none;
|
||||
resize: vertical;
|
||||
min-height: 2.5rem;
|
||||
font-family: inherit;
|
||||
}
|
||||
.compose-content::placeholder {
|
||||
color: var(--color-muted-foreground);
|
||||
}
|
||||
|
||||
.compose-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.compose-hint {
|
||||
font-size: 0.625rem;
|
||||
color: var(--color-muted-foreground);
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.compose-actions {
|
||||
display: flex;
|
||||
gap: 0.375rem;
|
||||
}
|
||||
|
||||
/* ── Search ─────────────────────────────────── */
|
||||
.search-input {
|
||||
flex: 1;
|
||||
background: var(--color-surface, rgba(255, 255, 255, 0.04));
|
||||
border: 1px solid var(--color-border, rgba(255, 255, 255, 0.1));
|
||||
border-radius: 0.5rem;
|
||||
|
|
@ -192,102 +250,7 @@
|
|||
color: var(--color-muted-foreground);
|
||||
}
|
||||
|
||||
.add-btn {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
border-radius: 0.5rem;
|
||||
background: var(--color-primary, #6366f1);
|
||||
color: white;
|
||||
border: none;
|
||||
font-size: 1.125rem;
|
||||
font-weight: 300;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
transition: filter 0.15s;
|
||||
}
|
||||
.add-btn:hover {
|
||||
filter: brightness(1.1);
|
||||
}
|
||||
|
||||
/* ── Create / Edit Form ─────────────────────── */
|
||||
.create-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.375rem;
|
||||
padding: 0.625rem;
|
||||
border-radius: 0.625rem;
|
||||
background: var(--color-surface, rgba(255, 255, 255, 0.06));
|
||||
border: 1px solid var(--color-border, rgba(255, 255, 255, 0.1));
|
||||
}
|
||||
|
||||
.create-title,
|
||||
.edit-title {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--color-foreground);
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
padding: 0.25rem 0;
|
||||
outline: none;
|
||||
}
|
||||
.create-title::placeholder,
|
||||
.edit-title::placeholder {
|
||||
color: var(--color-muted-foreground);
|
||||
}
|
||||
|
||||
.create-content,
|
||||
.edit-content {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--color-foreground);
|
||||
font-size: 0.8125rem;
|
||||
padding: 0.25rem 0;
|
||||
outline: none;
|
||||
resize: vertical;
|
||||
min-height: 3rem;
|
||||
font-family: inherit;
|
||||
}
|
||||
.create-content::placeholder {
|
||||
color: var(--color-muted-foreground);
|
||||
}
|
||||
|
||||
.create-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.color-row {
|
||||
display: flex;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.color-dot {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
border-radius: 50%;
|
||||
border: 2px solid transparent;
|
||||
cursor: pointer;
|
||||
transition: transform 0.15s;
|
||||
}
|
||||
.color-dot:hover {
|
||||
transform: scale(1.25);
|
||||
}
|
||||
.color-dot.selected {
|
||||
border-color: white;
|
||||
box-shadow: 0 0 0 1px var(--color-primary, #6366f1);
|
||||
}
|
||||
|
||||
.create-actions,
|
||||
.edit-actions {
|
||||
display: flex;
|
||||
gap: 0.375rem;
|
||||
}
|
||||
|
||||
/* ── Buttons ────────────────────────────────── */
|
||||
.btn-cancel,
|
||||
.btn-save {
|
||||
padding: 0.3rem 0.625rem;
|
||||
|
|
@ -297,7 +260,6 @@
|
|||
cursor: pointer;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-cancel {
|
||||
background: transparent;
|
||||
color: var(--color-muted-foreground);
|
||||
|
|
@ -305,14 +267,17 @@
|
|||
.btn-cancel:hover {
|
||||
background: var(--color-muted, rgba(255, 255, 255, 0.08));
|
||||
}
|
||||
|
||||
.btn-save {
|
||||
background: var(--color-primary, #6366f1);
|
||||
color: white;
|
||||
}
|
||||
.btn-save:hover {
|
||||
.btn-save:hover:not(:disabled) {
|
||||
filter: brightness(1.1);
|
||||
}
|
||||
.btn-save:disabled {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* ── Note Cards ─────────────────────────────── */
|
||||
.note-list {
|
||||
|
|
@ -338,12 +303,10 @@
|
|||
.note-card:hover {
|
||||
background: var(--color-muted, rgba(255, 255, 255, 0.08));
|
||||
}
|
||||
|
||||
.note-card.editing {
|
||||
cursor: default;
|
||||
border-left-color: var(--color-primary, #6366f1) !important;
|
||||
}
|
||||
|
||||
.note-card.pinned {
|
||||
background: rgba(99, 102, 241, 0.04);
|
||||
}
|
||||
|
|
@ -353,7 +316,6 @@
|
|||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
}
|
||||
|
||||
.note-title {
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 600;
|
||||
|
|
@ -363,12 +325,10 @@
|
|||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.pin-icon {
|
||||
font-size: 0.6875rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.note-preview {
|
||||
font-size: 0.75rem;
|
||||
color: var(--color-muted-foreground);
|
||||
|
|
@ -376,36 +336,42 @@
|
|||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.note-meta {
|
||||
font-size: 0.6875rem;
|
||||
color: var(--color-muted-foreground);
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
/* ── Empty ──────────────────────────────────── */
|
||||
.edit-title {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--color-foreground);
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
padding: 0.25rem 0;
|
||||
outline: none;
|
||||
}
|
||||
.edit-content {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--color-foreground);
|
||||
font-size: 0.8125rem;
|
||||
padding: 0.25rem 0;
|
||||
outline: none;
|
||||
resize: vertical;
|
||||
min-height: 3rem;
|
||||
font-family: inherit;
|
||||
}
|
||||
.edit-actions {
|
||||
display: flex;
|
||||
gap: 0.375rem;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.empty {
|
||||
text-align: center;
|
||||
color: var(--color-muted-foreground);
|
||||
font-size: 0.875rem;
|
||||
padding: 2rem 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.empty-add-btn {
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 0.5rem;
|
||||
background: var(--color-primary, #6366f1);
|
||||
color: white;
|
||||
border: none;
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
}
|
||||
.empty-add-btn:hover {
|
||||
filter: brightness(1.1);
|
||||
padding: 1.5rem 0;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue