feat(theme): implement single-file CSS theming engine with dark, light, high-contrast, and soft themes
MindSift CI & Docker Release / Build, Test in Container & Push (push) Failing after 16s
MindSift CI & Docker Release / Build, Test in Container & Push (push) Failing after 16s
This commit is contained in:
1 parent
0f24cbdbf4
commit
b3e651b3b9
18 files changed
+1455
-804
No files matched your search
@@ -9,10 +9,16 @@
|
|||||||
- [x] **CI/CD & Multi-Stage Docker Build (`Dockerfile`, `.gitea/workflows/build-and-publish.yaml`):**
|
- [x] **CI/CD & Multi-Stage Docker Build (`Dockerfile`, `.gitea/workflows/build-and-publish.yaml`):**
|
||||||
- Alle Frontend-Checks (`tsc --noEmit`, `vite build`) und Backend-Tests (`go test -v ./...`) laufen direkt im Multi-Stage Container-Build ohne doppelte Vorab-Steps.
|
- Alle Frontend-Checks (`tsc --noEmit`, `vite build`) und Backend-Tests (`go test -v ./...`) laufen direkt im Multi-Stage Container-Build ohne doppelte Vorab-Steps.
|
||||||
- Push auf `dev` baut und überschreibt `git.dsit.me/sandrodach/mindsift:dev`.
|
- Push auf `dev` baut und überschreibt `git.dsit.me/sandrodach/mindsift:dev`.
|
||||||
- [x] **Monochromes, eckiges Desktop-Design & Zentrale CSS-Steuerung (`frontend/src/index.css`, `frontend/tailwind.config.js`):**
|
- [x] **Single-File CSS Theming Engine & Design-System (`frontend/src/themes/`, `theme.ts`, `tailwind.config.js`):**
|
||||||
- Zentrale CSS-Variablen (`--bg-app`, `--bg-panel`, `--accent-primary`, `--border-color`, `--radius-card: 0px`) in `frontend/src/index.css`.
|
- Jedes Theme ist als **genau eine einzige, eigenständige CSS-Datei** unter `frontend/src/themes/` definiert.
|
||||||
- Eckige Tabs, eckige Panels/Karten (`0px` Radius), nur die Schnell-Eingabe-Blase oben im Header und Profil-Avatare sind rund (`rounded-full`).
|
- Alle Farben, Kontraste, Oberflächen, Rahmen, Statusfarben und Rundungen sind in 6 klaren Gruppen organisiert.
|
||||||
- Untere doppelte Navigationsleiste entfernt; Schnell-Notiz-Blase (`InstantCaptureBar`) sitzt ganz oben im Header über den Tabs.
|
- 4 vorkonfigurierte Themes (Dark, Light, High Contrast, Soft) plus saubere `theme-template.css`-Vorlage für eigene Themes.
|
||||||
|
- Vollständige Trennung von Struktur und Styling: React-Komponenten enthalten keine hardcodierten Farbwerte mehr, sondern ausschließlich semantische Klassen und CSS-Variablen (`text-main`, `text-muted`, `bg-canvas`, `panel-surface`, `checkbox-square`, etc.).
|
||||||
|
- Schnellauswahl im Profil-Dropdown im Header sowie visueller Theme-Manager in den Benutzereinstellungen.
|
||||||
|
- [x] **Monochromes, eckiges Desktop-Design & Zentrale CSS-Steuerung:**
|
||||||
|
- Zentrale CSS-Variablen (`--bg-canvas`, `--bg-panel`, `--accent`, `--border-color`, `--radius-card`, `--radius-btn`, `--radius-input`) in den Theme-Dateien.
|
||||||
|
- Eckige Tabs, eckige Panels/Karten (`0px` Radius im Dark/Monochrome-Theme), nur die Schnell-Eingabe-Blase (`InstantCaptureBar`) oben im Header nutzt `--radius-pill`.
|
||||||
|
- Untere doppelte Navigationsleiste entfernt; Schnell-Notiz-Blase sitzt ganz oben im Header über den Tabs.
|
||||||
- Workspace links als eckiger, monochromer Datei-/Ordner-Baum (File Explorer Tree mit Unterordnern).
|
- Workspace links als eckiger, monochromer Datei-/Ordner-Baum (File Explorer Tree mit Unterordnern).
|
||||||
- [x] **Quick Dump Notes + Triage Swipen + Direkt-Zuordnung (`InstantCaptureBar.tsx`, `TriageDeck.tsx`, `server.go`):**
|
- [x] **Quick Dump Notes + Triage Swipen + Direkt-Zuordnung (`InstantCaptureBar.tsx`, `TriageDeck.tsx`, `server.go`):**
|
||||||
- **Quick Dump (Standard):** Text oben eingeben + `Enter` wirft den Gedanken sofort als Quick Dump (`status: 'inbox'`) ins Triage-Deck.
|
- **Quick Dump (Standard):** Text oben eingeben + `Enter` wirft den Gedanken sofort als Quick Dump (`status: 'inbox'`) ins Triage-Deck.
|
||||||
@@ -28,7 +34,7 @@
|
|||||||
- Öffentliche Selbstregistrierung ist standardmäßig deaktiviert (`allowRegistration: false`). Neue Benutzer können nur über einen **Einladungscode** (`MS-XXXXXXXX`, Tabelle `user_invites`) oder direkt durch einen Admin (`admin` / `team_admin`) angelegt werden.
|
- Öffentliche Selbstregistrierung ist standardmäßig deaktiviert (`allowRegistration: false`). Neue Benutzer können nur über einen **Einladungscode** (`MS-XXXXXXXX`, Tabelle `user_invites`) oder direkt durch einen Admin (`admin` / `team_admin`) angelegt werden.
|
||||||
- [x] **Profile-Dropdown & Vollbild-Einstellungsseiten statt kleinem Modal (`App.tsx`, `UserSettingsView.tsx`, `AdminSettingsView.tsx`):**
|
- [x] **Profile-Dropdown & Vollbild-Einstellungsseiten statt kleinem Modal (`App.tsx`, `UserSettingsView.tsx`, `AdminSettingsView.tsx`):**
|
||||||
- Klick auf den Benutzer oben rechts öffnet ein eckiges, monochromes Dropdown-Menü:
|
- Klick auf den Benutzer oben rechts öffnet ein eckiges, monochromes Dropdown-Menü:
|
||||||
- **Kontoeinstellungen** (`UserSettingsView.tsx` — Vollbild-Ansicht für Profil & Passwort, Multi-Channel-Alarme, Private Webhooks & Daten-Export).
|
- **Kontoeinstellungen** (`UserSettingsView.tsx` — Vollbild-Ansicht für Profil & Passwort, Darstellung & Themes, Multi-Channel-Alarme, Private Webhooks & Daten-Export).
|
||||||
- **Administration** (`AdminSettingsView.tsx` — nur sichtbar für `admin` und `team_admin`; Vollbild-Ansicht für Benutzer, Rollen, Einladungscodes, Organisationen & Teams, Server-Isolation und SMTP-Mailserver).
|
- **Administration** (`AdminSettingsView.tsx` — nur sichtbar für `admin` und `team_admin`; Vollbild-Ansicht für Benutzer, Rollen, Einladungscodes, Organisationen & Teams, Server-Isolation und SMTP-Mailserver).
|
||||||
- **Abmelden** (bereinigt JWT-Token und führt zurück zum Login-Bildschirm).
|
- **Abmelden** (bereinigt JWT-Token und führt zurück zum Login-Bildschirm).
|
||||||
- 3-stufiges Rollensystem: `admin` (Server-Admin), `team_admin` (Team-Admin mit Berechtigung zum Einladen und Verwalten von Teams) und `member` (Mitglied).
|
- 3-stufiges Rollensystem: `admin` (Server-Admin), `team_admin` (Team-Admin mit Berechtigung zum Einladen und Verwalten von Teams) und `member` (Mitglied).
|
||||||
@@ -50,43 +56,85 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🎨 1. Design-System & CSS-Architektur (`frontend/src/index.css`)
|
## 🎨 1. Single-File CSS Theming Engine & Design-System
|
||||||
|
|
||||||
Alle Farben, Abstände und Radien werden zentral in [`frontend/src/index.css`](file:///home/sandro/Dokumente/git/MindSift/frontend/src/index.css) über CSS-Variablen gesteuert:
|
### Philosophie: 1 Datei = 1 komplettes Theme
|
||||||
|
Anstatt Styling in Dutzenden Tailwind-Klassen über hunderte Komponenten zu verstreuen, steuert **eine einzige CSS-Datei** das komplette Erscheinungsbild der Anwendung.
|
||||||
|
|
||||||
|
Jedes Theme liegt unter `frontend/src/themes/` und gruppiert **6 standardisierte Bereiche**:
|
||||||
|
|
||||||
```css
|
```css
|
||||||
:root {
|
:root[data-theme="dein-theme"] {
|
||||||
/* Monochromer Hintergrund (Dunkles Schiefer/Anthrazit) */
|
/* ==========================================================================
|
||||||
--bg-app: #121316;
|
1. AKZENTFARBE (Fokus, Primäraktionen, Highlights)
|
||||||
--bg-panel: #1a1c20;
|
========================================================================== */
|
||||||
--bg-panel-elevated: #22252a;
|
--accent: #2563eb;
|
||||||
--bg-panel-hover: #282c33;
|
|
||||||
|
|
||||||
/* Einzige Akzentfarbe + hellere/gedimmte Variante */
|
|
||||||
--accent-primary: #2563eb;
|
|
||||||
--accent-hover: #3b82f6;
|
--accent-hover: #3b82f6;
|
||||||
--accent-dim: rgba(37, 99, 235, 0.14);
|
--accent-dim: rgba(37, 99, 235, 0.15);
|
||||||
--accent-border: rgba(59, 130, 246, 0.45);
|
--accent-contrast: #ffffff;
|
||||||
|
|
||||||
/* Kanten & Linien */
|
/* ==========================================================================
|
||||||
--border-color: rgba(255, 255, 255, 0.09);
|
2. OBERFLÄCHEN & HINTERGRÜNDE
|
||||||
--border-subtle: rgba(255, 255, 255, 0.05);
|
========================================================================== */
|
||||||
|
--bg-canvas: #0e1013;
|
||||||
|
--bg-panel: #16181d;
|
||||||
|
--bg-panel-elevated: #1f2229;
|
||||||
|
--bg-input: #121418;
|
||||||
|
--bg-code: #0b0c0f;
|
||||||
|
--bg-overlay: rgba(0, 0, 0, 0.75);
|
||||||
|
|
||||||
/* Eckiges Layout (0px für Karten/Panels, minimal für Inputs) */
|
/* ==========================================================================
|
||||||
|
3. TEXT & KONTRASTE
|
||||||
|
========================================================================== */
|
||||||
|
--text-main: #f4f4f5;
|
||||||
|
--text-muted: #a1a1aa;
|
||||||
|
--text-faint: #71717a;
|
||||||
|
--text-on-accent: #ffffff;
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
4. RAHMEN, LINIEN & SCHATTEN
|
||||||
|
========================================================================== */
|
||||||
|
--border-color: rgba(255, 255, 255, 0.08);
|
||||||
|
--border-strong: rgba(255, 255, 255, 0.18);
|
||||||
|
--shadow-panel: 0 4px 20px -2px rgba(0, 0, 0, 0.45);
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
5. STATUS- & SIGNALFARBEN
|
||||||
|
========================================================================== */
|
||||||
|
--status-ok: #22c55e;
|
||||||
|
--status-err: #ef4444;
|
||||||
|
--status-warn: #eab308;
|
||||||
|
--status-info: #3b82f6;
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
6. RUNDUNGEN / GEOMETRIE
|
||||||
|
========================================================================== */
|
||||||
--radius-card: 0px;
|
--radius-card: 0px;
|
||||||
--radius-btn: 2px;
|
--radius-btn: 2px;
|
||||||
--radius-input: 2px;
|
--radius-input: 2px;
|
||||||
|
--radius-checkbox: 2px;
|
||||||
|
--radius-pill: 9999px;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Zentrale Komponenten-Klassen
|
### Verfügbare Themes
|
||||||
- `.panel-surface` / `.panel-elevated`: Eckige Container mit `1px solid var(--border-color)`.
|
|
||||||
- `.btn-action`: Monochromer, kompakter Button (`border-radius: var(--radius-btn)`).
|
| Theme-ID | Datei | Charakter | Standard-Geometrie |
|
||||||
- `.btn-primary`: Akzent-Button auf `--accent-primary`.
|
| :--- | :--- | :--- | :--- |
|
||||||
- `.input-field`: Minimal gerundetes (`2px`) Eingabefeld für Eigenschaften/Optionen.
|
| `dark` | [`theme-dark.css`](file:///home/sandro/Dokumente/git/MindSift/frontend/src/themes/theme-dark.css) | Standard Monochrom / Dark Slate | Streng eckig (`0px` Cards, `2px` Buttons) |
|
||||||
- `.tab-btn` / `.tab-btn-active`: Eckige, monochrome Header-Tabs.
|
| `light` | [`theme-light.css`](file:///home/sandro/Dokumente/git/MindSift/frontend/src/themes/theme-light.css) | Daylight Clean White / Minimalist Light | Modern sauber (`4px` Cards, `4px` Buttons) |
|
||||||
- `.badge-accent`: Kompaktes Akzent-Badge für Zähler und Tags.
|
| `high-contrast` | [`theme-high-contrast.css`](file:///home/sandro/Dokumente/git/MindSift/frontend/src/themes/theme-high-contrast.css) | Reines Schwarz/Weiß mit maximaler Lesbarkeit (AAA Kontrast) | Streng eckig (`0px`, `2px` Rahmen) |
|
||||||
- **Ausnahme für Rundungen (`rounded-full`):** Nur die Schnell-Eingabe-Blase (`InstantCaptureBar`) im Header, der Profil-Avatar und Toggle-Switches sind rund.
|
| `soft` | [`theme-soft.css`](file:///home/sandro/Dokumente/git/MindSift/frontend/src/themes/theme-soft.css) | Soft Rounded Slate mit warmem Indigo-Akzent | Weich gerundet (`12px` Cards, `8px` Buttons) |
|
||||||
|
| *Template* | [`theme-template.css`](file:///home/sandro/Dokumente/git/MindSift/frontend/src/themes/theme-template.css) | Dokumentierte Vorlage zur Erstellung eigener Themes | Voll anpassbar |
|
||||||
|
|
||||||
|
### Semantische Komponenten-Klassen (`frontend/src/index.css`)
|
||||||
|
- `.panel-surface` / `.panel-elevated`: Automatisch über `--radius-card`, `--bg-panel` und `--border-color` gesteuert.
|
||||||
|
- `.btn-primary` / `.btn-action`: Aktionen und Buttons mit `--radius-btn`, `--accent` und `--text-on-accent`.
|
||||||
|
- `.input-field`: Formularfelder mit `--radius-input`, `--bg-input`, `--border-color` und `--text-main`.
|
||||||
|
- `.checkbox-square`: Kontrollkästchen mit `--radius-checkbox` und `--accent`.
|
||||||
|
- `.badge-accent`, `.badge-neutral`, `.badge-ok`, `.badge-err`: Pillen und Zähler.
|
||||||
|
- `.alert-ok`, `.alert-err`: Hinweiskästen mit Signalfarben.
|
||||||
|
- `.capture-pill`: Die markante Schnell-Eingabeleiste im Header mit `--radius-pill`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -98,105 +146,71 @@ MindSift speichert alle Daten lokal und hochperformant in SQLite (`/data/mindsif
|
|||||||
|
|
||||||
1. **`users`**
|
1. **`users`**
|
||||||
- `id TEXT PRIMARY KEY`, `email TEXT UNIQUE`, `password_hash TEXT`, `display_name TEXT`
|
- `id TEXT PRIMARY KEY`, `email TEXT UNIQUE`, `password_hash TEXT`, `display_name TEXT`
|
||||||
- `role TEXT DEFAULT 'member'` (`'admin'` | `'member'`)
|
- `role TEXT DEFAULT 'member'` (`admin`, `team_admin`, `member`)
|
||||||
- `is_active INTEGER DEFAULT 1`
|
- `is_active INTEGER DEFAULT 1`, `created_at DATETIME`, `updated_at DATETIME`
|
||||||
- `discord_webhook_url TEXT DEFAULT ''`
|
2. **`system_settings`** (Instanzweite globale Konfiguration, 1 Row mit `id = 'singleton'`)
|
||||||
- `notify_before_minutes INTEGER DEFAULT 15`
|
- `sharing_mode TEXT DEFAULT 'teams_only'` (`strict_private`, `teams_only`, `open`)
|
||||||
- `created_at DATETIME`, `updated_at DATETIME`
|
- `allow_registration INTEGER DEFAULT 0` (Öffentliche Registrierung erlaubt oder gesperrt)
|
||||||
|
- `instance_name TEXT DEFAULT 'MindSift'`
|
||||||
2. **`thoughts` (Quick Dumps, Triage & Aufgaben)**
|
- `smtp_host TEXT`, `smtp_port INTEGER`, `smtp_user TEXT`, `smtp_pass TEXT`, `smtp_from TEXT`, `smtp_tls INTEGER`
|
||||||
- `id TEXT PRIMARY KEY`, `user_id TEXT`, `content TEXT`, `source TEXT`, `priority TEXT`
|
3. **`user_invites`** (Einladungscodes für neue Registrierungen)
|
||||||
- `status TEXT` (`'inbox'` | `'converted'` | `'discarded'`)
|
- `id TEXT PRIMARY KEY`, `code TEXT UNIQUE` (`MS-XXXXXXXX`), `email TEXT` (optional gebunden)
|
||||||
- `converted_type TEXT` (`'task'` | `'document'` | `'list_item'`)
|
- `role TEXT DEFAULT 'member'`, `team_id TEXT` (optional direkte Team-Zuweisung)
|
||||||
- `converted_ref_id TEXT` (Referenz auf Document oder ListItem)
|
- `created_by TEXT`, `used_by TEXT`, `expires_at DATETIME`, `created_at DATETIME`
|
||||||
- `task_list_id TEXT` (FK auf `task_lists.id`)
|
4. **`organizations` & `organization_teams` & `team_members`**
|
||||||
- `kanban_status TEXT DEFAULT 'todo'` (`'todo'` | `'in_progress'` | `'done'`)
|
- Strukturierung von Benutzern in Abteilungen, Familien oder Projekt-Teams.
|
||||||
- `notes TEXT DEFAULT ''` (zusätzliche Aufgabenbeschreibung/Notizen)
|
- Bildet die Basis für den Sichtbarkeits-Modus `teams_only`.
|
||||||
- `due_date DATETIME` (Fälligkeitsdatum)
|
5. **`spaces` & `space_members`**
|
||||||
- `alarm_sent INTEGER DEFAULT 0` (Flag für Discord-Benachrichtigung)
|
- Geteilte Kollaborations-Räume mit Einladungscode (`invite_code`), Echtzeit-WebSocket Sync und Rollen (`owner`, `editor`, `viewer`).
|
||||||
- `created_at DATETIME`, `updated_at DATETIME`
|
6. **`shared_lists` & `list_items`**
|
||||||
|
- Geteilte Einkaufs-, Pack- und Inventarlisten mit Kategorien, Mengen und Echtzeit-Abhaken.
|
||||||
3. **`task_lists` (Mehrere Aufgabenlisten)**
|
7. **`tasks` & `task_lists`**
|
||||||
- `id TEXT PRIMARY KEY`, `user_id TEXT`, `space_id TEXT` (optional für Team-Sharing)
|
- Persönliche und geteilte Aufgabenlisten mit Kanban-Spalten (`todo`, `in_progress`, `done`), Fälligkeit, Priorität und Alarm-Optionen.
|
||||||
- `name TEXT NOT NULL`, `color TEXT DEFAULT '#2563eb'`, `icon TEXT DEFAULT 'list'`
|
8. **`quick_dumps` (Triage-Deck)**
|
||||||
- `is_default INTEGER DEFAULT 0`, `created_at DATETIME`, `updated_at DATETIME`
|
- Schnelle Einfälle (`status = 'inbox'`, `'discarded'`, `'converted'`), Fälligkeitsdatum und Text-Notiz.
|
||||||
|
9. **`documents` (Workspace Markdown-Wiki)**
|
||||||
4. **`organizations` & `teams` & `team_members`**
|
- Notizen und Dokumentationen mit Folder-Pfad (`folder_path`), Tags, Pinned-Status, JSON-Blocks und physischer `.md`-Spiegelung auf Disk.
|
||||||
- `organizations`: `id TEXT PRIMARY KEY`, `name TEXT`, `owner_id TEXT`, `created_at DATETIME`, `updated_at DATETIME`
|
10. **`user_alarms` & `user_webhooks`**
|
||||||
- `teams`: `id TEXT PRIMARY KEY`, `organization_id TEXT`, `name TEXT`, `created_at DATETIME`
|
- Multi-Channel Alarme (Mail, Discord, Webhook) und ausgehende Event-Webhooks.
|
||||||
- `team_members`: `team_id TEXT`, `user_id TEXT`, `role TEXT DEFAULT 'member'`, `joined_at DATETIME`, `PRIMARY KEY (team_id, user_id)`
|
|
||||||
|
|
||||||
5. **`system_settings` (Admin-Konfiguration)**
|
|
||||||
- `key TEXT PRIMARY KEY`, `value TEXT`, `updated_at DATETIME`
|
|
||||||
- Wichtige Schlüssel:
|
|
||||||
- `sharing_isolation_mode`: `'open'` | `'teams_only'` | `'strict_private'`
|
|
||||||
- `allow_public_registration`: `'true'` | `'false'`
|
|
||||||
|
|
||||||
6. **`documents` & `document_blocks` (Workspace & Disk Sync)**
|
|
||||||
- `documents`: `id TEXT PRIMARY KEY`, `user_id TEXT`, `space_id TEXT`, `title TEXT`, `folder_path TEXT DEFAULT '/'`, `is_archived INTEGER`, `created_at DATETIME`, `updated_at DATETIME`
|
|
||||||
- `document_blocks`: `id TEXT PRIMARY KEY`, `document_id TEXT`, `block_type TEXT` (`paragraph`, `heading1`, `heading2`, `heading3`, `todo`, `bullet`, `callout`, `code`, `table`, `progress`), `content TEXT`, `order_index INTEGER`, `metadata TEXT`, `updated_at DATETIME`
|
|
||||||
|
|
||||||
7. **`spaces`, `space_members`, `shared_lists`, `list_items`, `api_keys`**
|
|
||||||
- Verwaltung geteilter Einkaufs-/Checklisten und privater Ingestion-Keys.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## ⚙️ 3. State-Machine: Quick Dump, Direkt-Zuordnung & Triage
|
## ⚡ 3. Triage & Quick-Dump State Machine
|
||||||
|
|
||||||
```
|
```mermaid
|
||||||
[ HEADER QUICK-CAPTURE BAR ]
|
stateDiagram-v2
|
||||||
│
|
[*] --> InstantCapture: Gedanke eingetippt
|
||||||
┌───────────────────────┼───────────────────────┐
|
InstantCapture --> QuickDump_Inbox: Enter (Standard)
|
||||||
│ Standard (Enter) │ Direkt-Option gewählt │
|
InstantCapture --> TaskCreated: Ziel = 'task'
|
||||||
▼ ▼ ▼
|
InstantCapture --> ListItemCreated: Ziel = 'list_item'
|
||||||
status: 'inbox' status: 'converted' status: 'converted'
|
InstantCapture --> DocCreated: Ziel = 'document'
|
||||||
(Quick Dump Deck) ├── target: 'task' ├── target: 'list_item'
|
|
||||||
│ └── target: 'document' └── Broadcast via WS
|
|
||||||
│
|
|
||||||
├──► Swipe Links / "Wegwerfen" (H / ←) ──► status: 'discarded'
|
|
||||||
├──► "Später" (Leertaste) ──► ans Ende der lokalen Queue
|
|
||||||
├──► Swipe Rechts / "Als Aufgabe" (L/→)──► status: 'converted' (type: 'task')
|
|
||||||
├──► "Mit Termin" (T) ──► status: 'converted' (type: 'task', dueDate)
|
|
||||||
├──► "Als Notiz" (D) ──► status: 'converted' (type: 'document' + Doc angelegt)
|
|
||||||
└──► "Auf Liste" ──► status: 'converted' (type: 'list_item' + Item angelegt)
|
|
||||||
```
|
|
||||||
|
|
||||||
### API-Endpunkt für Capture (`POST /api/v1/inbox`)
|
QuickDump_Inbox --> TriageDeck: Stapel sichten
|
||||||
Akzeptiert:
|
TriageDeck --> Discarded: Swipe links / H (Wegwerfen)
|
||||||
- `content`: Fließtext des Gedankens
|
TriageDeck --> TaskCreated: Swipe rechts / L (Als Aufgabe)
|
||||||
- `priority`: `'low'` | `'medium'` | `'high'`
|
TriageDeck --> TaskCreated: T (Termin-Aufgabe)
|
||||||
- `target`: `'inbox'` (Standard) | `'task'` | `'list_item'` | `'document'`
|
TriageDeck --> DocCreated: D (In Notiz umwandeln)
|
||||||
- `taskListId`: ID der Aufgabenliste (wenn `target === 'task'`)
|
TriageDeck --> ListItemCreated: Auf Liste schieben
|
||||||
- `targetListId`: ID der Zielliste (wenn `target === 'list_item'`)
|
TriageDeck --> QuickDump_Inbox: Leertaste (Später - ans Ende)
|
||||||
- `dueDate`: Optionales ISO-Datum
|
|
||||||
|
Discarded --> [*]
|
||||||
|
TaskCreated --> [*]
|
||||||
|
DocCreated --> [*]
|
||||||
|
ListItemCreated --> [*]
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🔔 4. Discord-Alarm-Scheduler & Webhook-Architektur
|
## 👥 4. Server-Administration & Isolation Modes
|
||||||
|
|
||||||
1. **Hintergrund-Ticker im Go-Backend (`internal/api/server.go`):**
|
Der Server-Administrator (`role = 'admin'`) legt unter `AdminSettingsView.tsx` global fest, wie Benutzer miteinander interagieren dürfen:
|
||||||
- Ein Hintergrund-Goroutine-Ticker läuft alle 60 Sekunden.
|
|
||||||
- Fragt `GetDueTasksForAlarm()` ab: Sucht nach offenen Aufgaben (`status = 'converted'`, `converted_type = 'task'`, `kanban_status != 'done'`), deren `due_date` in der Vorwarnzeit liegt (`due_date <= now + notifyBeforeMinutes`) und bei denen `alarm_sent == 0` ist.
|
|
||||||
- Sendet ein JSON-Embed an die in den Benutzer-Einstellungen hinterlegte `discord_webhook_url`:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"username": "MindSift Reminder",
|
|
||||||
"embeds": [{
|
|
||||||
"title": "⏰ Aufgabe fällig: <Titel>",
|
|
||||||
"description": "<Notizen>",
|
|
||||||
"color": 2450411,
|
|
||||||
"fields": [
|
|
||||||
{"name": "Fällig am", "value": "<Datum>", "inline": true},
|
|
||||||
{"name": "Priorität", "value": "<Prio>", "inline": true}
|
|
||||||
]
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
- Markiert den Gedanken in SQLite mit `MarkTaskAlarmSent(taskID)` als benachrichtigt.
|
|
||||||
|
|
||||||
2. **Eingehende Webhooks / Private API-Tokens:**
|
1. **`strict_private` (Strikt Privat):**
|
||||||
- Erreichbar unter `POST /api/v1/inbox` mit Header `X-API-Key: ms_...`.
|
- Jeder Benutzer arbeitet komplett autark. Keine teamübergreifende Benutzersuche, keine geteilten Spaces.
|
||||||
- Ermöglicht direkte Einbindung in Shell-Skripte, Crons oder Drittsysteme via `curl`.
|
2. **`teams_only` (Teams isoliert — Empfohlener Standard):**
|
||||||
|
- Benutzer sehen im Adressbuch und bei geteilten Spaces ausschließlich Personen, mit denen sie mindestens in einem gemeinsamen Team sind.
|
||||||
|
3. **`open` (Offene Instanz):**
|
||||||
|
- Alle Benutzer können sich gegenseitig sehen, zu Spaces einladen und gemeinsam an Listen arbeiten.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+67
-23
@@ -10,9 +10,16 @@ import {
|
|||||||
LogOut,
|
LogOut,
|
||||||
ChevronDown,
|
ChevronDown,
|
||||||
RotateCcw,
|
RotateCcw,
|
||||||
|
Palette,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { AuthStatus, DashboardStats, SharedList, Thought, User } from './lib/types';
|
import { AuthStatus, DashboardStats, SharedList, Thought, User } from './lib/types';
|
||||||
import { api, clearToken, getToken, triggerHaptic } from './lib/api';
|
import { api, clearToken, getToken, triggerHaptic } from './lib/api';
|
||||||
|
import {
|
||||||
|
AVAILABLE_THEMES,
|
||||||
|
initThemeOnBoot,
|
||||||
|
setActiveTheme as applyTheme,
|
||||||
|
ThemeId,
|
||||||
|
} from './lib/theme';
|
||||||
import { InstantCaptureBar } from './features/capture/InstantCaptureBar';
|
import { InstantCaptureBar } from './features/capture/InstantCaptureBar';
|
||||||
import { TriageDeck } from './features/triage/TriageDeck';
|
import { TriageDeck } from './features/triage/TriageDeck';
|
||||||
import { TasksView } from './features/triage/TasksView';
|
import { TasksView } from './features/triage/TasksView';
|
||||||
@@ -51,9 +58,9 @@ class ViewErrorBoundary extends React.Component<
|
|||||||
render() {
|
render() {
|
||||||
if (this.state.hasError) {
|
if (this.state.hasError) {
|
||||||
return (
|
return (
|
||||||
<div className="p-6 bg-panel border border-border text-xs space-y-3 max-w-lg mx-auto my-8">
|
<div className="p-6 panel-surface text-xs space-y-3 max-w-lg mx-auto my-8">
|
||||||
<h3 className="text-sm font-bold text-white">Ansicht konnte nicht geladen werden</h3>
|
<h3 className="text-sm font-bold text-main">Ansicht konnte nicht geladen werden</h3>
|
||||||
<p className="text-zinc-400">{this.state.errorText}</p>
|
<p className="text-muted">{this.state.errorText}</p>
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
this.setState({ hasError: false, errorText: '' });
|
this.setState({ hasError: false, errorText: '' });
|
||||||
@@ -72,6 +79,7 @@ class ViewErrorBoundary extends React.Component<
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const App: React.FC = () => {
|
export const App: React.FC = () => {
|
||||||
|
const [activeTheme, setActiveThemeState] = useState<ThemeId>(() => initThemeOnBoot());
|
||||||
const [bootLoading, setBootLoading] = useState(true);
|
const [bootLoading, setBootLoading] = useState(true);
|
||||||
const [authStatus, setAuthStatus] = useState<AuthStatus>({
|
const [authStatus, setAuthStatus] = useState<AuthStatus>({
|
||||||
needsSetup: false,
|
needsSetup: false,
|
||||||
@@ -98,6 +106,12 @@ export const App: React.FC = () => {
|
|||||||
const [showProfileMenu, setShowProfileMenu] = useState(false);
|
const [showProfileMenu, setShowProfileMenu] = useState(false);
|
||||||
const dropdownRef = useRef<HTMLDivElement | null>(null);
|
const dropdownRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
|
const handleChangeTheme = (themeId: ThemeId) => {
|
||||||
|
triggerHaptic(10);
|
||||||
|
applyTheme(themeId);
|
||||||
|
setActiveThemeState(themeId);
|
||||||
|
};
|
||||||
|
|
||||||
const refreshAll = async () => {
|
const refreshAll = async () => {
|
||||||
try {
|
try {
|
||||||
const [meRes, deckRes, listsRes] = await Promise.all([
|
const [meRes, deckRes, listsRes] = await Promise.all([
|
||||||
@@ -228,7 +242,7 @@ export const App: React.FC = () => {
|
|||||||
|
|
||||||
if (bootLoading) {
|
if (bootLoading) {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-canvas text-zinc-400 flex items-center justify-center text-xs font-sans">
|
<div className="min-h-screen bg-canvas text-muted flex items-center justify-center text-xs font-sans">
|
||||||
Verbinde mit MindSift…
|
Verbinde mit MindSift…
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -253,9 +267,9 @@ export const App: React.FC = () => {
|
|||||||
: 'Mitglied';
|
: 'Mitglied';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-canvas text-white flex flex-col font-sans selection:bg-accent/30">
|
<div className="min-h-screen bg-canvas text-main flex flex-col font-sans selection:bg-accent/30">
|
||||||
{/* Top Header Bar */}
|
{/* Top Header Bar */}
|
||||||
<header className="sticky top-0 z-30 bg-panel/95 backdrop-blur-md border-b border-border shadow-sm">
|
<header className="sticky top-0 z-30 bg-panel border-b border-border shadow-sm">
|
||||||
<div className="max-w-6xl mx-auto px-3 sm:px-4 py-2 flex items-center justify-between gap-3 sm:gap-6">
|
<div className="max-w-6xl mx-auto px-3 sm:px-4 py-2 flex items-center justify-between gap-3 sm:gap-6">
|
||||||
{/* Logo & Headline */}
|
{/* Logo & Headline */}
|
||||||
<div
|
<div
|
||||||
@@ -265,14 +279,14 @@ export const App: React.FC = () => {
|
|||||||
}}
|
}}
|
||||||
className="flex items-center gap-2.5 cursor-pointer group select-none flex-shrink-0"
|
className="flex items-center gap-2.5 cursor-pointer group select-none flex-shrink-0"
|
||||||
>
|
>
|
||||||
<div className="w-7 h-7 bg-accent text-white flex items-center justify-center">
|
<div className="w-7 h-7 bg-accent text-on-accent rounded-btn flex items-center justify-center">
|
||||||
<Brain className="w-4 h-4" />
|
<Brain className="w-4 h-4" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<span className="font-bold text-sm sm:text-base text-white tracking-tight leading-none">
|
<span className="font-bold text-sm sm:text-base text-main tracking-tight leading-none">
|
||||||
{authStatus.instanceName || 'MindSift'}
|
{authStatus.instanceName || 'MindSift'}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-[11px] text-zinc-400 mt-0.5 hidden sm:inline-block">
|
<span className="text-[11px] text-muted mt-0.5 hidden sm:inline-block">
|
||||||
Der Kopf frei in drei Sekunden
|
Der Kopf frei in drei Sekunden
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -294,29 +308,57 @@ export const App: React.FC = () => {
|
|||||||
triggerHaptic(10);
|
triggerHaptic(10);
|
||||||
setShowProfileMenu((prev) => !prev);
|
setShowProfileMenu((prev) => !prev);
|
||||||
}}
|
}}
|
||||||
className="h-8 pl-1 pr-2.5 rounded-full bg-panel hover:bg-panel-elevated border border-border text-xs font-medium text-white flex items-center gap-2 transition-colors"
|
className="h-8 pl-1 pr-2.5 capture-pill bg-panel hover:bg-panel-elevated border border-border text-xs font-medium text-main flex items-center gap-2 transition-colors"
|
||||||
title="Benutzermenü öffnen"
|
title="Benutzermenü öffnen"
|
||||||
>
|
>
|
||||||
<div className="w-6 h-6 rounded-full bg-accent text-white text-[10px] font-bold flex items-center justify-center">
|
<div className="w-6 h-6 capture-pill bg-accent text-on-accent text-[10px] font-bold flex items-center justify-center">
|
||||||
{user.displayName ? user.displayName.slice(0, 1).toUpperCase() : 'U'}
|
{user.displayName ? user.displayName.slice(0, 1).toUpperCase() : 'U'}
|
||||||
</div>
|
</div>
|
||||||
<span className="max-w-[120px] truncate hidden sm:inline">
|
<span className="max-w-[120px] truncate hidden sm:inline">
|
||||||
{user.displayName}
|
{user.displayName}
|
||||||
</span>
|
</span>
|
||||||
<ChevronDown className="w-3.5 h-3.5 text-zinc-400" />
|
<ChevronDown className="w-3.5 h-3.5 text-muted" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{showProfileMenu && (
|
{showProfileMenu && (
|
||||||
<div className="absolute right-0 top-full mt-1.5 w-60 bg-panel border border-border shadow-2xl z-50 divide-y divide-border text-xs">
|
<div className="absolute right-0 top-full mt-1.5 w-64 panel-surface shadow-panel z-50 divide-y divide-border text-xs">
|
||||||
{/* User Info Header */}
|
{/* User Info Header */}
|
||||||
<div className="p-3 bg-canvas">
|
<div className="p-3 bg-canvas">
|
||||||
<p className="font-bold text-white truncate">{user.displayName}</p>
|
<p className="font-bold text-main truncate">{user.displayName}</p>
|
||||||
<p className="text-[11px] text-zinc-400 truncate">{user.email}</p>
|
<p className="text-[11px] text-muted truncate">{user.email}</p>
|
||||||
<span className="inline-block mt-1.5 px-1.5 py-0.5 bg-panel border border-border text-[10px] text-accent font-semibold">
|
<span className="inline-block mt-1.5 px-1.5 py-0.5 bg-panel border border-border rounded-btn text-[10px] text-accent font-semibold">
|
||||||
{roleLabel}
|
{roleLabel}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Theme Quick Switcher */}
|
||||||
|
<div className="p-2.5 space-y-1.5">
|
||||||
|
<span className="text-[10px] font-semibold text-muted uppercase tracking-wider flex items-center gap-1.5 px-1">
|
||||||
|
<Palette className="w-3 h-3 text-accent" />
|
||||||
|
<span>Design-Theme</span>
|
||||||
|
</span>
|
||||||
|
<div className="grid grid-cols-2 gap-1">
|
||||||
|
{AVAILABLE_THEMES.map((t) => (
|
||||||
|
<button
|
||||||
|
key={t.id}
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleChangeTheme(t.id)}
|
||||||
|
className={`h-7 px-2 text-[11px] font-medium border rounded-btn flex items-center gap-1.5 transition-colors ${
|
||||||
|
activeTheme === t.id
|
||||||
|
? 'bg-panel-elevated border-accent text-main font-semibold'
|
||||||
|
: 'bg-canvas border-border text-muted hover:text-main'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className="w-2.5 h-2.5 border border-strong flex-shrink-0"
|
||||||
|
style={{ backgroundColor: t.previewBg }}
|
||||||
|
/>
|
||||||
|
<span className="truncate">{t.name.replace('MindSift ', '').split(' ')[0]}</span>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Menu Items */}
|
{/* Menu Items */}
|
||||||
<div className="py-1">
|
<div className="py-1">
|
||||||
<button
|
<button
|
||||||
@@ -326,7 +368,7 @@ export const App: React.FC = () => {
|
|||||||
setActiveTab('user_settings');
|
setActiveTab('user_settings');
|
||||||
}}
|
}}
|
||||||
className={`w-full px-3.5 py-2.5 text-left flex items-center gap-2.5 hover:bg-panel-elevated transition-colors ${
|
className={`w-full px-3.5 py-2.5 text-left flex items-center gap-2.5 hover:bg-panel-elevated transition-colors ${
|
||||||
activeTab === 'user_settings' ? 'text-accent font-semibold' : 'text-zinc-200'
|
activeTab === 'user_settings' ? 'text-accent font-semibold' : 'text-main'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<Settings className="w-4 h-4 text-accent" />
|
<Settings className="w-4 h-4 text-accent" />
|
||||||
@@ -343,7 +385,7 @@ export const App: React.FC = () => {
|
|||||||
className={`w-full px-3.5 py-2.5 text-left flex items-center gap-2.5 hover:bg-panel-elevated transition-colors ${
|
className={`w-full px-3.5 py-2.5 text-left flex items-center gap-2.5 hover:bg-panel-elevated transition-colors ${
|
||||||
activeTab === 'admin_settings'
|
activeTab === 'admin_settings'
|
||||||
? 'text-accent font-semibold'
|
? 'text-accent font-semibold'
|
||||||
: 'text-zinc-200'
|
: 'text-main'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<Shield className="w-4 h-4 text-accent" />
|
<Shield className="w-4 h-4 text-accent" />
|
||||||
@@ -356,7 +398,7 @@ export const App: React.FC = () => {
|
|||||||
<div className="py-1">
|
<div className="py-1">
|
||||||
<button
|
<button
|
||||||
onClick={handleLogout}
|
onClick={handleLogout}
|
||||||
className="w-full px-3.5 py-2.5 text-left flex items-center gap-2.5 text-red-400 hover:bg-red-950/30 transition-colors"
|
className="w-full px-3.5 py-2.5 text-left flex items-center gap-2.5 text-status-err hover:bg-status-err-bg transition-colors"
|
||||||
>
|
>
|
||||||
<LogOut className="w-4 h-4" />
|
<LogOut className="w-4 h-4" />
|
||||||
<span>Abmelden</span>
|
<span>Abmelden</span>
|
||||||
@@ -367,7 +409,7 @@ export const App: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Monochrome square tabs attached to header */}
|
{/* Navigation tabs attached to header */}
|
||||||
<nav className="max-w-6xl mx-auto px-1 sm:px-2 overflow-x-auto no-scrollbar border-t border-border">
|
<nav className="max-w-6xl mx-auto px-1 sm:px-2 overflow-x-auto no-scrollbar border-t border-border">
|
||||||
<div className="flex items-stretch">
|
<div className="flex items-stretch">
|
||||||
<button
|
<button
|
||||||
@@ -384,7 +426,7 @@ export const App: React.FC = () => {
|
|||||||
className={
|
className={
|
||||||
activeTab === 'triage'
|
activeTab === 'triage'
|
||||||
? 'badge-accent'
|
? 'badge-accent'
|
||||||
: 'px-1.5 py-0.5 text-[10px] bg-canvas text-zinc-400 border border-border'
|
: 'badge-neutral'
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{stats.inboxCount}
|
{stats.inboxCount}
|
||||||
@@ -406,7 +448,7 @@ export const App: React.FC = () => {
|
|||||||
className={
|
className={
|
||||||
activeTab === 'tasks'
|
activeTab === 'tasks'
|
||||||
? 'badge-accent'
|
? 'badge-accent'
|
||||||
: 'px-1.5 py-0.5 text-[10px] bg-canvas text-zinc-400 border border-border'
|
: 'badge-neutral'
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{stats.tasksOpenCount}
|
{stats.tasksOpenCount}
|
||||||
@@ -427,7 +469,7 @@ export const App: React.FC = () => {
|
|||||||
className={
|
className={
|
||||||
activeTab === 'workspace'
|
activeTab === 'workspace'
|
||||||
? 'badge-accent'
|
? 'badge-accent'
|
||||||
: 'px-1.5 py-0.5 text-[10px] bg-canvas text-zinc-400 border border-border'
|
: 'badge-neutral'
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{stats.docsCount}
|
{stats.docsCount}
|
||||||
@@ -484,6 +526,8 @@ export const App: React.FC = () => {
|
|||||||
{activeTab === 'user_settings' && (
|
{activeTab === 'user_settings' && (
|
||||||
<UserSettingsView
|
<UserSettingsView
|
||||||
currentUser={user}
|
currentUser={user}
|
||||||
|
activeTheme={activeTheme}
|
||||||
|
onThemeChange={handleChangeTheme}
|
||||||
onUserUpdated={(u) => {
|
onUserUpdated={(u) => {
|
||||||
setUser(u);
|
setUser(u);
|
||||||
refreshAll();
|
refreshAll();
|
||||||
|
|||||||
@@ -28,7 +28,9 @@ export const SetupAndAuthView: React.FC<SetupAndAuthViewProps> = ({
|
|||||||
const [setupEmail, setSetupEmail] = useState('');
|
const [setupEmail, setSetupEmail] = useState('');
|
||||||
const [setupPassword, setSetupPassword] = useState('');
|
const [setupPassword, setSetupPassword] = useState('');
|
||||||
const [instanceName, setInstanceName] = useState(authStatus.instanceName || 'MindSift');
|
const [instanceName, setInstanceName] = useState(authStatus.instanceName || 'MindSift');
|
||||||
const [sharingMode, setSharingMode] = useState<'open' | 'teams_only' | 'strict_private'>('teams_only');
|
const [sharingMode, setSharingMode] = useState<'open' | 'teams_only' | 'strict_private'>(
|
||||||
|
'teams_only'
|
||||||
|
);
|
||||||
const [allowRegistration, setAllowRegistration] = useState(false);
|
const [allowRegistration, setAllowRegistration] = useState(false);
|
||||||
const [createSampleData, setCreateSampleData] = useState(false);
|
const [createSampleData, setCreateSampleData] = useState(false);
|
||||||
|
|
||||||
@@ -101,52 +103,50 @@ export const SetupAndAuthView: React.FC<SetupAndAuthViewProps> = ({
|
|||||||
// ============================================================================
|
// ============================================================================
|
||||||
if (authStatus.needsSetup) {
|
if (authStatus.needsSetup) {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-canvas text-white flex flex-col items-center justify-center p-4 font-sans">
|
<div className="min-h-screen bg-canvas text-main flex flex-col items-center justify-center p-4 font-sans">
|
||||||
<div className="w-full max-w-xl bg-panel border border-border shadow-2xl">
|
<div className="w-full max-w-xl panel-surface shadow-panel overflow-hidden">
|
||||||
{/* Top Brand Bar */}
|
{/* Top Brand Bar */}
|
||||||
<div className="p-4 bg-canvas border-b border-border flex items-center justify-between">
|
<div className="p-4 bg-canvas border-b border-border flex items-center justify-between">
|
||||||
<div className="flex items-center gap-2.5">
|
<div className="flex items-center gap-2.5">
|
||||||
<div className="w-8 h-8 bg-accent text-white flex items-center justify-center">
|
<div className="w-8 h-8 bg-accent text-on-accent rounded-btn flex items-center justify-center">
|
||||||
<Brain className="w-4 h-4" />
|
<Brain className="w-4 h-4" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-base font-bold text-white tracking-tight leading-none">
|
<h1 className="text-base font-bold text-main tracking-tight leading-none">
|
||||||
MindSift Ersteinrichtung
|
MindSift Ersteinrichtung
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-[11px] text-zinc-400 mt-0.5">
|
<p className="text-[11px] text-muted mt-0.5">
|
||||||
Initiales Administrator-Konto & Server-Konfiguration
|
Initiales Administrator-Konto & Server-Konfiguration
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span className="px-2 py-0.5 bg-accent/15 border border-accent text-accent text-[10px] font-semibold uppercase tracking-wider">
|
<span className="badge-accent uppercase tracking-wider">First Boot</span>
|
||||||
First Boot
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form onSubmit={handleCompleteSetup} className="p-5 space-y-5 text-xs">
|
<form onSubmit={handleCompleteSetup} className="p-5 space-y-5 text-xs">
|
||||||
<p className="text-zinc-300 leading-relaxed">
|
<p className="text-muted leading-relaxed">
|
||||||
Willkommen auf deiner neuen MindSift-Instanz. Es existiert noch kein Benutzerkonto in
|
Willkommen auf deiner neuen MindSift-Instanz. Es existiert noch kein Benutzerkonto in
|
||||||
der Datenbank. Lege jetzt das erste <strong>Server-Admin-Konto</strong> an und bestimme
|
der Datenbank. Lege jetzt das erste <strong className="text-main">Server-Admin-Konto</strong> an und
|
||||||
die Sicherheitseinstellungen.
|
bestimme die Sicherheitseinstellungen.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{error && (
|
{error && (
|
||||||
<div className="p-3 bg-red-950/40 border border-red-500 text-red-200 flex items-center gap-2">
|
<div className="alert-err flex items-center gap-2">
|
||||||
<AlertCircle className="w-4 h-4 text-red-400 flex-shrink-0" />
|
<AlertCircle className="w-4 h-4 text-status-err flex-shrink-0" />
|
||||||
<span>{error}</span>
|
<span>{error}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Section 1: Admin Account */}
|
{/* Section 1: Admin Account */}
|
||||||
<div className="p-3.5 bg-canvas border border-border space-y-3">
|
<div className="p-3.5 bg-canvas border border-border space-y-3">
|
||||||
<h2 className="text-xs font-bold text-white uppercase tracking-wider flex items-center gap-1.5">
|
<h2 className="text-xs font-bold text-main uppercase tracking-wider flex items-center gap-1.5">
|
||||||
<Shield className="w-3.5 h-3.5 text-accent" />
|
<Shield className="w-3.5 h-3.5 text-accent" />
|
||||||
<span>1. Server-Administrator anlegen</span>
|
<span>1. Server-Administrator anlegen</span>
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-zinc-400 mb-1">Dein Anzeigename *</label>
|
<label className="block text-muted mb-1">Dein Anzeigename *</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={setupName}
|
value={setupName}
|
||||||
@@ -154,24 +154,24 @@ export const SetupAndAuthView: React.FC<SetupAndAuthViewProps> = ({
|
|||||||
placeholder="z.B. Admin / Dein Name"
|
placeholder="z.B. Admin / Dein Name"
|
||||||
required
|
required
|
||||||
autoFocus
|
autoFocus
|
||||||
className="w-full h-8 px-2.5 input-field text-white"
|
className="w-full h-8 px-2.5 input-field"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-zinc-400 mb-1">E-Mail-Adresse (Login) *</label>
|
<label className="block text-muted mb-1">E-Mail-Adresse (Login) *</label>
|
||||||
<input
|
<input
|
||||||
type="email"
|
type="email"
|
||||||
value={setupEmail}
|
value={setupEmail}
|
||||||
onChange={(e) => setSetupEmail(e.target.value)}
|
onChange={(e) => setSetupEmail(e.target.value)}
|
||||||
placeholder="admin@deinedomain.de"
|
placeholder="admin@deinedomain.de"
|
||||||
required
|
required
|
||||||
className="w-full h-8 px-2.5 input-field text-white"
|
className="w-full h-8 px-2.5 input-field"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-zinc-400 mb-1">Administrator-Passwort *</label>
|
<label className="block text-muted mb-1">Administrator-Passwort *</label>
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
value={setupPassword}
|
value={setupPassword}
|
||||||
@@ -179,31 +179,31 @@ export const SetupAndAuthView: React.FC<SetupAndAuthViewProps> = ({
|
|||||||
placeholder="Mindestens 4 Zeichen"
|
placeholder="Mindestens 4 Zeichen"
|
||||||
minLength={4}
|
minLength={4}
|
||||||
required
|
required
|
||||||
className="w-full h-8 px-2.5 input-field text-white"
|
className="w-full h-8 px-2.5 input-field"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Section 2: Server & Isolation */}
|
{/* Section 2: Server & Isolation */}
|
||||||
<div className="p-3.5 bg-canvas border border-border space-y-3">
|
<div className="p-3.5 bg-canvas border border-border space-y-3">
|
||||||
<h2 className="text-xs font-bold text-white uppercase tracking-wider flex items-center gap-1.5">
|
<h2 className="text-xs font-bold text-main uppercase tracking-wider flex items-center gap-1.5">
|
||||||
<Server className="w-3.5 h-3.5 text-accent" />
|
<Server className="w-3.5 h-3.5 text-accent" />
|
||||||
<span>2. Instanz & Berechtigungen</span>
|
<span>2. Instanz & Berechtigungen</span>
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-zinc-400 mb-1">Name der Instanz</label>
|
<label className="block text-muted mb-1">Name der Instanz</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={instanceName}
|
value={instanceName}
|
||||||
onChange={(e) => setInstanceName(e.target.value)}
|
onChange={(e) => setInstanceName(e.target.value)}
|
||||||
placeholder="MindSift"
|
placeholder="MindSift"
|
||||||
className="w-full h-8 px-2.5 input-field text-white"
|
className="w-full h-8 px-2.5 input-field"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-zinc-400 mb-1.5 flex items-center gap-1">
|
<label className="block text-muted mb-1.5 flex items-center gap-1">
|
||||||
<Share2 className="w-3 h-3 text-accent" />
|
<Share2 className="w-3 h-3 text-accent" />
|
||||||
<span>Sichtbarkeit & Sharing-Isolation zwischen Benutzern</span>
|
<span>Sichtbarkeit & Sharing-Isolation zwischen Benutzern</span>
|
||||||
</label>
|
</label>
|
||||||
@@ -213,12 +213,12 @@ export const SetupAndAuthView: React.FC<SetupAndAuthViewProps> = ({
|
|||||||
onClick={() => setSharingMode('strict_private')}
|
onClick={() => setSharingMode('strict_private')}
|
||||||
className={`p-2.5 text-left border transition-colors ${
|
className={`p-2.5 text-left border transition-colors ${
|
||||||
sharingMode === 'strict_private'
|
sharingMode === 'strict_private'
|
||||||
? 'bg-panel border-accent text-white'
|
? 'bg-panel border-accent text-main'
|
||||||
: 'border-border text-zinc-400 hover:text-white'
|
: 'border-border text-muted hover:text-main'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<p className="font-bold text-xs">Strikt Privat</p>
|
<p className="font-bold text-xs">Strikt Privat</p>
|
||||||
<p className="text-[10px] text-zinc-400 mt-0.5">
|
<p className="text-[10px] text-muted mt-0.5">
|
||||||
Keiner sieht andere Nutzer. Komplett isoliert.
|
Keiner sieht andere Nutzer. Komplett isoliert.
|
||||||
</p>
|
</p>
|
||||||
</button>
|
</button>
|
||||||
@@ -228,12 +228,12 @@ export const SetupAndAuthView: React.FC<SetupAndAuthViewProps> = ({
|
|||||||
onClick={() => setSharingMode('teams_only')}
|
onClick={() => setSharingMode('teams_only')}
|
||||||
className={`p-2.5 text-left border transition-colors ${
|
className={`p-2.5 text-left border transition-colors ${
|
||||||
sharingMode === 'teams_only'
|
sharingMode === 'teams_only'
|
||||||
? 'bg-panel border-accent text-white'
|
? 'bg-panel border-accent text-main'
|
||||||
: 'border-border text-zinc-400 hover:text-white'
|
: 'border-border text-muted hover:text-main'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<p className="font-bold text-xs">Teams isoliert</p>
|
<p className="font-bold text-xs">Teams isoliert</p>
|
||||||
<p className="text-[10px] text-zinc-400 mt-0.5">
|
<p className="text-[10px] text-muted mt-0.5">
|
||||||
Nutzer sehen nur Mitglieder ihrer eigenen Teams.
|
Nutzer sehen nur Mitglieder ihrer eigenen Teams.
|
||||||
</p>
|
</p>
|
||||||
</button>
|
</button>
|
||||||
@@ -243,12 +243,12 @@ export const SetupAndAuthView: React.FC<SetupAndAuthViewProps> = ({
|
|||||||
onClick={() => setSharingMode('open')}
|
onClick={() => setSharingMode('open')}
|
||||||
className={`p-2.5 text-left border transition-colors ${
|
className={`p-2.5 text-left border transition-colors ${
|
||||||
sharingMode === 'open'
|
sharingMode === 'open'
|
||||||
? 'bg-panel border-accent text-white'
|
? 'bg-panel border-accent text-main'
|
||||||
: 'border-border text-zinc-400 hover:text-white'
|
: 'border-border text-muted hover:text-main'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<p className="font-bold text-xs">Alle sehen alle</p>
|
<p className="font-bold text-xs">Alle sehen alle</p>
|
||||||
<p className="text-[10px] text-zinc-400 mt-0.5">
|
<p className="text-[10px] text-muted mt-0.5">
|
||||||
Offene Zusammenarbeit auf dem gesamten Server.
|
Offene Zusammenarbeit auf dem gesamten Server.
|
||||||
</p>
|
</p>
|
||||||
</button>
|
</button>
|
||||||
@@ -258,11 +258,12 @@ export const SetupAndAuthView: React.FC<SetupAndAuthViewProps> = ({
|
|||||||
<div className="pt-2 border-t border-border space-y-2">
|
<div className="pt-2 border-t border-border space-y-2">
|
||||||
<label className="flex items-center justify-between cursor-pointer select-none">
|
<label className="flex items-center justify-between cursor-pointer select-none">
|
||||||
<div>
|
<div>
|
||||||
<span className="font-medium text-white block">
|
<span className="font-medium text-main block">
|
||||||
Öffentliche Selbstregistrierung erlauben
|
Öffentliche Selbstregistrierung erlauben
|
||||||
</span>
|
</span>
|
||||||
<span className="text-[11px] text-zinc-400">
|
<span className="text-[11px] text-muted">
|
||||||
Wenn deaktiviert, können neue Nutzer nur per Einladungscode oder durch einen Admin angelegt werden.
|
Wenn deaktiviert, können neue Nutzer nur per Einladungscode oder durch einen
|
||||||
|
Admin angelegt werden.
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
@@ -275,10 +276,10 @@ export const SetupAndAuthView: React.FC<SetupAndAuthViewProps> = ({
|
|||||||
|
|
||||||
<label className="flex items-center justify-between cursor-pointer select-none pt-1">
|
<label className="flex items-center justify-between cursor-pointer select-none pt-1">
|
||||||
<div>
|
<div>
|
||||||
<span className="font-medium text-white block">
|
<span className="font-medium text-main block">
|
||||||
Start-Beispieldaten anlegen
|
Start-Beispieldaten anlegen
|
||||||
</span>
|
</span>
|
||||||
<span className="text-[11px] text-zinc-400">
|
<span className="text-[11px] text-muted">
|
||||||
Erstellt wenige Beispiel-Gedanken und ein Einführungsdokument zum Testen.
|
Erstellt wenige Beispiel-Gedanken und ein Einführungsdokument zum Testen.
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -299,7 +300,11 @@ export const SetupAndAuthView: React.FC<SetupAndAuthViewProps> = ({
|
|||||||
className="btn-primary h-9 px-5 text-xs font-semibold flex items-center gap-2 disabled:opacity-50"
|
className="btn-primary h-9 px-5 text-xs font-semibold flex items-center gap-2 disabled:opacity-50"
|
||||||
>
|
>
|
||||||
<Check className="w-4 h-4" />
|
<Check className="w-4 h-4" />
|
||||||
<span>{submitting ? 'Richte Server ein…' : 'Ersteinrichtung abschließen & Starten'}</span>
|
<span>
|
||||||
|
{submitting
|
||||||
|
? 'Richte Server ein…'
|
||||||
|
: 'Ersteinrichtung abschließen & Starten'}
|
||||||
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@@ -312,24 +317,22 @@ export const SetupAndAuthView: React.FC<SetupAndAuthViewProps> = ({
|
|||||||
// STANDARD LOGIN / INVITE CODE / REGISTRATION VIEW
|
// STANDARD LOGIN / INVITE CODE / REGISTRATION VIEW
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-canvas text-white flex flex-col items-center justify-center p-4 font-sans">
|
<div className="min-h-screen bg-canvas text-main flex flex-col items-center justify-center p-4 font-sans">
|
||||||
<div className="w-full max-w-md bg-panel border border-border shadow-2xl">
|
<div className="w-full max-w-md panel-surface shadow-panel overflow-hidden">
|
||||||
{/* Top Header */}
|
{/* Top Header */}
|
||||||
<div className="p-4 bg-canvas border-b border-border flex items-center justify-between">
|
<div className="p-4 bg-canvas border-b border-border flex items-center justify-between">
|
||||||
<div className="flex items-center gap-2.5">
|
<div className="flex items-center gap-2.5">
|
||||||
<div className="w-7 h-7 bg-accent text-white flex items-center justify-center">
|
<div className="w-7 h-7 bg-accent text-on-accent rounded-btn flex items-center justify-center">
|
||||||
<Brain className="w-4 h-4" />
|
<Brain className="w-4 h-4" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-sm font-bold text-white tracking-tight leading-none">
|
<h1 className="text-sm font-bold text-main tracking-tight leading-none">
|
||||||
{authStatus.instanceName || 'MindSift'}
|
{authStatus.instanceName || 'MindSift'}
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-[11px] text-zinc-400 mt-0.5">
|
<p className="text-[11px] text-muted mt-0.5">Der Kopf frei in drei Sekunden</p>
|
||||||
Der Kopf frei in drei Sekunden
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Lock className="w-4 h-4 text-zinc-500" />
|
<Lock className="w-4 h-4 text-faint" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Navigation Tabs */}
|
{/* Navigation Tabs */}
|
||||||
@@ -342,8 +345,8 @@ export const SetupAndAuthView: React.FC<SetupAndAuthViewProps> = ({
|
|||||||
}}
|
}}
|
||||||
className={`flex-1 h-9 px-3 font-medium flex items-center justify-center gap-1.5 border-r border-border transition-colors ${
|
className={`flex-1 h-9 px-3 font-medium flex items-center justify-center gap-1.5 border-r border-border transition-colors ${
|
||||||
authTab === 'login'
|
authTab === 'login'
|
||||||
? 'bg-panel text-white border-b-2 border-b-accent'
|
? 'bg-panel text-main border-b-2 border-b-accent'
|
||||||
: 'text-zinc-400 hover:text-white'
|
: 'text-muted hover:text-main'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<LogIn className="w-3.5 h-3.5" />
|
<LogIn className="w-3.5 h-3.5" />
|
||||||
@@ -358,8 +361,8 @@ export const SetupAndAuthView: React.FC<SetupAndAuthViewProps> = ({
|
|||||||
}}
|
}}
|
||||||
className={`flex-1 h-9 px-3 font-medium flex items-center justify-center gap-1.5 border-r border-border transition-colors ${
|
className={`flex-1 h-9 px-3 font-medium flex items-center justify-center gap-1.5 border-r border-border transition-colors ${
|
||||||
authTab === 'invite'
|
authTab === 'invite'
|
||||||
? 'bg-panel text-white border-b-2 border-b-accent'
|
? 'bg-panel text-main border-b-2 border-b-accent'
|
||||||
: 'text-zinc-400 hover:text-white'
|
: 'text-muted hover:text-main'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<KeyRound className="w-3.5 h-3.5" />
|
<KeyRound className="w-3.5 h-3.5" />
|
||||||
@@ -375,8 +378,8 @@ export const SetupAndAuthView: React.FC<SetupAndAuthViewProps> = ({
|
|||||||
}}
|
}}
|
||||||
className={`flex-1 h-9 px-3 font-medium flex items-center justify-center gap-1.5 transition-colors ${
|
className={`flex-1 h-9 px-3 font-medium flex items-center justify-center gap-1.5 transition-colors ${
|
||||||
authTab === 'register'
|
authTab === 'register'
|
||||||
? 'bg-panel text-white border-b-2 border-b-accent'
|
? 'bg-panel text-main border-b-2 border-b-accent'
|
||||||
: 'text-zinc-400 hover:text-white'
|
: 'text-muted hover:text-main'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<UserPlus className="w-3.5 h-3.5" />
|
<UserPlus className="w-3.5 h-3.5" />
|
||||||
@@ -388,8 +391,8 @@ export const SetupAndAuthView: React.FC<SetupAndAuthViewProps> = ({
|
|||||||
{/* Form Body */}
|
{/* Form Body */}
|
||||||
<div className="p-5 text-xs">
|
<div className="p-5 text-xs">
|
||||||
{error && (
|
{error && (
|
||||||
<div className="mb-4 p-2.5 bg-red-950/40 border border-red-500 text-red-200 flex items-center gap-2">
|
<div className="mb-4 alert-err flex items-center gap-2">
|
||||||
<AlertCircle className="w-4 h-4 text-red-400 flex-shrink-0" />
|
<AlertCircle className="w-4 h-4 text-status-err flex-shrink-0" />
|
||||||
<span>{error}</span>
|
<span>{error}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -397,7 +400,7 @@ export const SetupAndAuthView: React.FC<SetupAndAuthViewProps> = ({
|
|||||||
{authTab === 'login' && (
|
{authTab === 'login' && (
|
||||||
<form onSubmit={handleLogin} className="space-y-3.5">
|
<form onSubmit={handleLogin} className="space-y-3.5">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-zinc-400 mb-1">E-Mail-Adresse</label>
|
<label className="block text-muted mb-1">E-Mail-Adresse</label>
|
||||||
<input
|
<input
|
||||||
type="email"
|
type="email"
|
||||||
value={email}
|
value={email}
|
||||||
@@ -405,19 +408,19 @@ export const SetupAndAuthView: React.FC<SetupAndAuthViewProps> = ({
|
|||||||
placeholder="name@beispiel.de"
|
placeholder="name@beispiel.de"
|
||||||
required
|
required
|
||||||
autoFocus
|
autoFocus
|
||||||
className="w-full h-8 px-2.5 input-field text-white"
|
className="w-full h-8 px-2.5 input-field"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-zinc-400 mb-1">Passwort</label>
|
<label className="block text-muted mb-1">Passwort</label>
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
value={password}
|
value={password}
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
placeholder="••••••••"
|
placeholder="••••••••"
|
||||||
required
|
required
|
||||||
className="w-full h-8 px-2.5 input-field text-white"
|
className="w-full h-8 px-2.5 input-field"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -433,7 +436,7 @@ export const SetupAndAuthView: React.FC<SetupAndAuthViewProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!authStatus.allowRegistration && (
|
{!authStatus.allowRegistration && (
|
||||||
<p className="text-[11px] text-zinc-500 text-center pt-2 border-t border-border">
|
<p className="text-[11px] text-faint text-center pt-2 border-t border-border">
|
||||||
Öffentliche Registrierung ist deaktiviert. Zugang nur mit Konto oder{' '}
|
Öffentliche Registrierung ist deaktiviert. Zugang nur mit Konto oder{' '}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -452,7 +455,7 @@ export const SetupAndAuthView: React.FC<SetupAndAuthViewProps> = ({
|
|||||||
<form onSubmit={handleRegisterOrInvite} className="space-y-3.5">
|
<form onSubmit={handleRegisterOrInvite} className="space-y-3.5">
|
||||||
{authTab === 'invite' && (
|
{authTab === 'invite' && (
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-zinc-400 mb-1">Einladungscode *</label>
|
<label className="block text-muted mb-1">Einladungscode *</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={inviteCode}
|
value={inviteCode}
|
||||||
@@ -460,37 +463,37 @@ export const SetupAndAuthView: React.FC<SetupAndAuthViewProps> = ({
|
|||||||
placeholder="MS-XXXXXXXX"
|
placeholder="MS-XXXXXXXX"
|
||||||
required
|
required
|
||||||
autoFocus
|
autoFocus
|
||||||
className="w-full h-8 px-2.5 input-field text-white font-mono uppercase"
|
className="w-full h-8 px-2.5 input-field font-mono uppercase"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-zinc-400 mb-1">Anzeigename *</label>
|
<label className="block text-muted mb-1">Anzeigename *</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={displayName}
|
value={displayName}
|
||||||
onChange={(e) => setDisplayName(e.target.value)}
|
onChange={(e) => setDisplayName(e.target.value)}
|
||||||
placeholder="Dein Name"
|
placeholder="Dein Name"
|
||||||
required
|
required
|
||||||
className="w-full h-8 px-2.5 input-field text-white"
|
className="w-full h-8 px-2.5 input-field"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-zinc-400 mb-1">E-Mail-Adresse *</label>
|
<label className="block text-muted mb-1">E-Mail-Adresse *</label>
|
||||||
<input
|
<input
|
||||||
type="email"
|
type="email"
|
||||||
value={email}
|
value={email}
|
||||||
onChange={(e) => setEmail(e.target.value)}
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
placeholder="name@beispiel.de"
|
placeholder="name@beispiel.de"
|
||||||
required
|
required
|
||||||
className="w-full h-8 px-2.5 input-field text-white"
|
className="w-full h-8 px-2.5 input-field"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-zinc-400 mb-1">Passwort *</label>
|
<label className="block text-muted mb-1">Passwort *</label>
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
value={password}
|
value={password}
|
||||||
@@ -498,7 +501,7 @@ export const SetupAndAuthView: React.FC<SetupAndAuthViewProps> = ({
|
|||||||
placeholder="Mindestens 4 Zeichen"
|
placeholder="Mindestens 4 Zeichen"
|
||||||
minLength={4}
|
minLength={4}
|
||||||
required
|
required
|
||||||
className="w-full h-8 px-2.5 input-field text-white"
|
className="w-full h-8 px-2.5 input-field"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -210,15 +210,15 @@ export const InstantCaptureBar: React.FC<InstantCaptureBarProps> = ({
|
|||||||
return (
|
return (
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<form ref={containerRef} onSubmit={handleSubmit} className="relative">
|
<form ref={containerRef} onSubmit={handleSubmit} className="relative">
|
||||||
{/* Round Quick-Capture Input Bar */}
|
{/* Quick-Capture Input Bar (Radius controlled by --radius-pill) */}
|
||||||
<div
|
<div
|
||||||
className={`flex items-center h-9 pl-1.5 pr-1.5 bg-panel border rounded-full transition-colors duration-150 ${
|
className={`flex items-center h-9 pl-1.5 pr-1.5 bg-panel border capture-pill transition-colors duration-150 ${
|
||||||
isListening
|
isListening
|
||||||
? 'border-accent bg-panel'
|
? 'border-accent bg-panel'
|
||||||
: 'border-border focus-within:border-accent'
|
: 'border-border focus-within:border-accent'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="w-6 h-6 rounded-full bg-accent text-white flex items-center justify-center mr-2 flex-shrink-0">
|
<div className="w-6 h-6 capture-pill bg-accent text-on-accent flex items-center justify-center mr-2 flex-shrink-0">
|
||||||
<Sparkles className="w-3 h-3" />
|
<Sparkles className="w-3 h-3" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -244,7 +244,7 @@ export const InstantCaptureBar: React.FC<InstantCaptureBarProps> = ({
|
|||||||
? 'Neue Workspace-Notiz direkt anlegen…'
|
? 'Neue Workspace-Notiz direkt anlegen…'
|
||||||
: 'Quick Dump: Gedanke einfach reinrotzen & später swipen… (C / Enter)'
|
: 'Quick Dump: Gedanke einfach reinrotzen & später swipen… (C / Enter)'
|
||||||
}
|
}
|
||||||
className="w-full bg-transparent text-white placeholder-zinc-500 text-xs sm:text-sm focus:outline-none"
|
className="w-full bg-transparent text-main placeholder-faint text-xs sm:text-sm focus:outline-none"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -254,10 +254,10 @@ export const InstantCaptureBar: React.FC<InstantCaptureBarProps> = ({
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() => setShowOptions((prev) => !prev)}
|
onClick={() => setShowOptions((prev) => !prev)}
|
||||||
title="Ziel wählen (Quick Dump, direkt als Aufgabe, auf Liste oder als Notiz) & Optionen"
|
title="Ziel wählen (Quick Dump, direkt als Aufgabe, auf Liste oder als Notiz) & Optionen"
|
||||||
className={`h-6 px-2.5 rounded-full text-[11px] flex items-center gap-1 transition-colors ${
|
className={`h-6 px-2.5 capture-pill text-[11px] flex items-center gap-1 transition-colors ${
|
||||||
showOptions || target !== 'inbox' || priority !== 'normal' || dueDate
|
showOptions || target !== 'inbox' || priority !== 'normal' || dueDate
|
||||||
? 'bg-accent/20 text-accent border border-accent/40 font-medium'
|
? 'bg-accent-dim text-accent border border-accent-border font-medium'
|
||||||
: 'text-zinc-400 hover:text-white hover:bg-panel-elevated border border-transparent'
|
: 'text-muted hover:text-main hover:bg-panel-elevated border border-transparent'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{target === 'task' ? (
|
{target === 'task' ? (
|
||||||
@@ -281,10 +281,10 @@ export const InstantCaptureBar: React.FC<InstantCaptureBarProps> = ({
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={toggleVoiceDictation}
|
onClick={toggleVoiceDictation}
|
||||||
title="Spracheingabe"
|
title="Spracheingabe"
|
||||||
className={`w-6 h-6 rounded-full flex items-center justify-center transition-colors ${
|
className={`w-6 h-6 capture-pill flex items-center justify-center transition-colors ${
|
||||||
isListening
|
isListening
|
||||||
? 'bg-accent/20 text-accent'
|
? 'bg-accent-dim text-accent'
|
||||||
: 'text-zinc-400 hover:text-white hover:bg-panel-elevated'
|
: 'text-muted hover:text-main hover:bg-panel-elevated'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{isListening ? <MicOff className="w-3 h-3" /> : <Mic className="w-3 h-3" />}
|
{isListening ? <MicOff className="w-3 h-3" /> : <Mic className="w-3 h-3" />}
|
||||||
@@ -294,10 +294,10 @@ export const InstantCaptureBar: React.FC<InstantCaptureBarProps> = ({
|
|||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={!content.trim() || isSubmitting}
|
disabled={!content.trim() || isSubmitting}
|
||||||
className={`h-6 px-2.5 rounded-full font-medium text-[11px] flex items-center gap-1 transition-all ${
|
className={`h-6 px-2.5 capture-pill font-medium text-[11px] flex items-center gap-1 transition-all ${
|
||||||
content.trim()
|
content.trim()
|
||||||
? 'bg-accent hover:bg-accent-hover text-white cursor-pointer shadow-sm'
|
? 'bg-accent hover:bg-accent-hover text-on-accent cursor-pointer shadow-sm'
|
||||||
: 'bg-panel-elevated text-zinc-500 cursor-not-allowed'
|
: 'bg-panel-elevated text-faint cursor-not-allowed'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{justSavedLabel ? (
|
{justSavedLabel ? (
|
||||||
@@ -323,16 +323,16 @@ export const InstantCaptureBar: React.FC<InstantCaptureBarProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Expandable Target & Metadata Panel (Square Monochromatic Dropdown) */}
|
{/* Expandable Target & Metadata Panel (Theme-controlled Dropdown) */}
|
||||||
{showOptions && (
|
{showOptions && (
|
||||||
<div className="absolute top-full left-0 right-0 mt-1.5 z-50 p-3 bg-panel border border-border shadow-2xl space-y-3 text-xs">
|
<div className="absolute top-full left-0 right-0 mt-1.5 z-50 p-3 panel-surface shadow-panel space-y-3 text-xs">
|
||||||
{/* Row 1: Where should this entry go? */}
|
{/* Row 1: Where should this entry go? */}
|
||||||
<div>
|
<div>
|
||||||
<div className="flex items-center justify-between mb-1.5">
|
<div className="flex items-center justify-between mb-1.5">
|
||||||
<span className="text-[11px] font-semibold uppercase tracking-wider text-zinc-400">
|
<span className="text-[11px] font-semibold uppercase tracking-wider text-muted">
|
||||||
Wohin damit? {content.trim() ? '(Klick speichert sofort oder wählt Ziel)' : '(Ziel für Eingabe wählen)'}
|
Wohin damit? {content.trim() ? '(Klick speichert sofort oder wählt Ziel)' : '(Ziel für Eingabe wählen)'}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-[11px] text-zinc-500">
|
<span className="text-[11px] text-faint">
|
||||||
Standard: Quick Dump für späteres Swipen
|
Standard: Quick Dump für späteres Swipen
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -347,10 +347,10 @@ export const InstantCaptureBar: React.FC<InstantCaptureBarProps> = ({
|
|||||||
executeCapture('inbox');
|
executeCapture('inbox');
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className={`h-8 px-2.5 border flex items-center gap-1.5 text-left transition-colors ${
|
className={`h-8 px-2.5 border rounded-btn flex items-center gap-1.5 text-left transition-colors ${
|
||||||
target === 'inbox'
|
target === 'inbox'
|
||||||
? 'bg-accent/20 border-accent text-white font-medium'
|
? 'bg-accent-dim border-accent text-main font-medium'
|
||||||
: 'bg-canvas border-border text-zinc-300 hover:bg-panel-hover'
|
: 'bg-canvas border-border text-muted hover:text-main hover:bg-panel-hover'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<Layers className="w-3.5 h-3.5 text-accent flex-shrink-0" />
|
<Layers className="w-3.5 h-3.5 text-accent flex-shrink-0" />
|
||||||
@@ -366,10 +366,10 @@ export const InstantCaptureBar: React.FC<InstantCaptureBarProps> = ({
|
|||||||
executeCapture('task');
|
executeCapture('task');
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className={`h-8 px-2.5 border flex items-center gap-1.5 text-left transition-colors ${
|
className={`h-8 px-2.5 border rounded-btn flex items-center gap-1.5 text-left transition-colors ${
|
||||||
target === 'task'
|
target === 'task'
|
||||||
? 'bg-accent/20 border-accent text-white font-medium'
|
? 'bg-accent-dim border-accent text-main font-medium'
|
||||||
: 'bg-canvas border-border text-zinc-300 hover:bg-panel-hover'
|
: 'bg-canvas border-border text-muted hover:text-main hover:bg-panel-hover'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<CheckSquare className="w-3.5 h-3.5 text-accent flex-shrink-0" />
|
<CheckSquare className="w-3.5 h-3.5 text-accent flex-shrink-0" />
|
||||||
@@ -385,10 +385,10 @@ export const InstantCaptureBar: React.FC<InstantCaptureBarProps> = ({
|
|||||||
setTargetListId(lists[0].id);
|
setTargetListId(lists[0].id);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className={`h-8 px-2.5 border flex items-center gap-1.5 text-left transition-colors ${
|
className={`h-8 px-2.5 border rounded-btn flex items-center gap-1.5 text-left transition-colors ${
|
||||||
target === 'list_item'
|
target === 'list_item'
|
||||||
? 'bg-accent/20 border-accent text-white font-medium'
|
? 'bg-accent-dim border-accent text-main font-medium'
|
||||||
: 'bg-canvas border-border text-zinc-300 hover:bg-panel-hover'
|
: 'bg-canvas border-border text-muted hover:text-main hover:bg-panel-hover'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<ShoppingCart className="w-3.5 h-3.5 text-accent flex-shrink-0" />
|
<ShoppingCart className="w-3.5 h-3.5 text-accent flex-shrink-0" />
|
||||||
@@ -404,10 +404,10 @@ export const InstantCaptureBar: React.FC<InstantCaptureBarProps> = ({
|
|||||||
executeCapture('document');
|
executeCapture('document');
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className={`h-8 px-2.5 border flex items-center gap-1.5 text-left transition-colors ${
|
className={`h-8 px-2.5 border rounded-btn flex items-center gap-1.5 text-left transition-colors ${
|
||||||
target === 'document'
|
target === 'document'
|
||||||
? 'bg-accent/20 border-accent text-white font-medium'
|
? 'bg-accent-dim border-accent text-main font-medium'
|
||||||
: 'bg-canvas border-border text-zinc-300 hover:bg-panel-hover'
|
: 'bg-canvas border-border text-muted hover:text-main hover:bg-panel-hover'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<FileText className="w-3.5 h-3.5 text-accent flex-shrink-0" />
|
<FileText className="w-3.5 h-3.5 text-accent flex-shrink-0" />
|
||||||
@@ -416,10 +416,10 @@ export const InstantCaptureBar: React.FC<InstantCaptureBarProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Optional Sub-Row: Shared List Picker (always shown if lists exist so 1-click list assignment is instant) */}
|
{/* Optional Sub-Row: Shared List Picker */}
|
||||||
{lists.length > 0 && (
|
{lists.length > 0 && (
|
||||||
<div className="pt-2 border-t border-border flex flex-wrap items-center gap-1.5">
|
<div className="pt-2 border-t border-border flex flex-wrap items-center gap-1.5">
|
||||||
<span className="text-zinc-400 mr-1 flex items-center gap-1">
|
<span className="text-muted mr-1 flex items-center gap-1">
|
||||||
<ShoppingCart className="w-3 h-3 text-accent" />
|
<ShoppingCart className="w-3 h-3 text-accent" />
|
||||||
Liste:
|
Liste:
|
||||||
</span>
|
</span>
|
||||||
@@ -436,10 +436,10 @@ export const InstantCaptureBar: React.FC<InstantCaptureBarProps> = ({
|
|||||||
executeCapture('list_item', lst.id);
|
executeCapture('list_item', lst.id);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className={`h-7 px-2.5 border text-xs flex items-center gap-1.5 transition-colors ${
|
className={`h-7 px-2.5 border rounded-btn text-xs flex items-center gap-1.5 transition-colors ${
|
||||||
isSelected
|
isSelected
|
||||||
? 'bg-accent text-white border-accent font-medium'
|
? 'bg-accent text-on-accent border-accent font-medium'
|
||||||
: 'bg-canvas border-border text-zinc-300 hover:text-white hover:bg-panel-hover'
|
: 'bg-canvas border-border text-muted hover:text-main hover:bg-panel-hover'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<span>{lst.icon}</span>
|
<span>{lst.icon}</span>
|
||||||
@@ -453,7 +453,7 @@ export const InstantCaptureBar: React.FC<InstantCaptureBarProps> = ({
|
|||||||
{/* Row 2: Priority & Due Date */}
|
{/* Row 2: Priority & Due Date */}
|
||||||
<div className="pt-2 border-t border-border flex flex-wrap items-center justify-between gap-2">
|
<div className="pt-2 border-t border-border flex flex-wrap items-center justify-between gap-2">
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
<span className="text-zinc-400 mr-1">Priorität:</span>
|
<span className="text-muted mr-1">Priorität:</span>
|
||||||
{(['low', 'normal', 'high', 'urgent'] as PriorityLevel[]).map((p) => {
|
{(['low', 'normal', 'high', 'urgent'] as PriorityLevel[]).map((p) => {
|
||||||
const labels: Record<PriorityLevel, string> = {
|
const labels: Record<PriorityLevel, string> = {
|
||||||
low: 'Niedrig',
|
low: 'Niedrig',
|
||||||
@@ -467,10 +467,10 @@ export const InstantCaptureBar: React.FC<InstantCaptureBarProps> = ({
|
|||||||
key={p}
|
key={p}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setPriority(p)}
|
onClick={() => setPriority(p)}
|
||||||
className={`px-2.5 py-0.5 border transition-colors text-xs ${
|
className={`px-2.5 py-0.5 border rounded-btn transition-colors text-xs ${
|
||||||
active
|
active
|
||||||
? 'bg-accent text-white border-accent font-medium'
|
? 'bg-accent text-on-accent border-accent font-medium'
|
||||||
: 'bg-canvas text-zinc-400 hover:text-white border-border'
|
: 'bg-canvas text-muted hover:text-main border-border'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{labels[p]}
|
{labels[p]}
|
||||||
@@ -480,7 +480,7 @@ export const InstantCaptureBar: React.FC<InstantCaptureBarProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-zinc-400 flex items-center gap-1">
|
<span className="text-muted flex items-center gap-1">
|
||||||
<Calendar className="w-3 h-3 text-accent" />
|
<Calendar className="w-3 h-3 text-accent" />
|
||||||
Fällig am:
|
Fällig am:
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -148,54 +148,39 @@ export const SharedListsView: React.FC<SharedListsViewProps> = ({ onRefreshGloba
|
|||||||
|
|
||||||
const text = newItemText.trim();
|
const text = newItemText.trim();
|
||||||
const qty = newItemQty.trim();
|
const qty = newItemQty.trim();
|
||||||
const cat = newItemCategory;
|
const cat = newItemCategory || undefined;
|
||||||
|
|
||||||
setNewItemText('');
|
setNewItemText('');
|
||||||
setNewItemQty('');
|
setNewItemQty('');
|
||||||
|
|
||||||
const created = await api.createListItem(activeList.id, text, cat, qty);
|
try {
|
||||||
setLists((prev) =>
|
const item = await api.createListItem(activeList.id, text, qty, cat);
|
||||||
prev.map((l) => {
|
setLists((prev) =>
|
||||||
if (l.id !== activeList.id) return l;
|
prev.map((l) => (l.id === activeList.id ? { ...l, items: [item, ...l.items] } : l))
|
||||||
if (l.items.some((i) => i.id === created.id)) return l;
|
);
|
||||||
return { ...l, items: [created, ...l.items] };
|
onRefreshGlobal();
|
||||||
})
|
} catch (err: any) {
|
||||||
);
|
alert(err.message || 'Fehler beim Erstellen des Listeneintrags');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleToggleItem = async (item: ListItem) => {
|
const handleToggleItem = async (item: ListItem) => {
|
||||||
if (!activeList) return;
|
if (!activeList) return;
|
||||||
triggerHaptic(18);
|
triggerHaptic(10);
|
||||||
const nextCompleted = !item.isCompleted;
|
const nextCompleted = !item.isCompleted;
|
||||||
|
|
||||||
// Optimistic UI update (BRAIN.md Section 3)
|
|
||||||
setLists((prev) =>
|
setLists((prev) =>
|
||||||
prev.map((l) => {
|
prev.map((l) => {
|
||||||
if (l.id !== activeList.id) return l;
|
if (l.id !== activeList.id) return l;
|
||||||
return {
|
return {
|
||||||
...l,
|
...l,
|
||||||
items: l.items.map((i) =>
|
items: l.items.map((i) => (i.id === item.id ? { ...i, isCompleted: nextCompleted } : i)),
|
||||||
i.id === item.id ? { ...i, isCompleted: nextCompleted } : i
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
await api.toggleListItem(activeList.id, item.id);
|
||||||
const updated = await api.toggleListItem(activeList.id, item.id, nextCompleted);
|
onRefreshGlobal();
|
||||||
setLists((prev) =>
|
|
||||||
prev.map((l) => {
|
|
||||||
if (l.id !== activeList.id) return l;
|
|
||||||
return {
|
|
||||||
...l,
|
|
||||||
items: l.items.map((i) => (i.id === item.id ? updated : i)),
|
|
||||||
};
|
|
||||||
})
|
|
||||||
);
|
|
||||||
} catch {
|
|
||||||
// Rollback on error
|
|
||||||
loadListsForSpace(activeSpace!.id);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteItem = async (itemId: string) => {
|
const handleDeleteItem = async (itemId: string) => {
|
||||||
@@ -272,10 +257,10 @@ export const SharedListsView: React.FC<SharedListsViewProps> = ({ onRefreshGloba
|
|||||||
<button
|
<button
|
||||||
key={sp.id}
|
key={sp.id}
|
||||||
onClick={() => setActiveSpace(sp)}
|
onClick={() => setActiveSpace(sp)}
|
||||||
className={`h-8 px-3 text-xs sm:text-sm font-medium flex items-center gap-1.5 transition-colors border ${
|
className={`h-8 px-3 text-xs sm:text-sm font-medium flex items-center gap-1.5 transition-colors border rounded-btn ${
|
||||||
activeSpace?.id === sp.id
|
activeSpace?.id === sp.id
|
||||||
? 'bg-accent text-white border-accent'
|
? 'bg-accent text-on-accent border-accent'
|
||||||
: 'bg-canvas text-zinc-300 hover:text-white border-border'
|
: 'bg-canvas text-muted hover:text-main border-border'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<span>{sp.icon}</span>
|
<span>{sp.icon}</span>
|
||||||
@@ -302,10 +287,10 @@ export const SharedListsView: React.FC<SharedListsViewProps> = ({ onRefreshGloba
|
|||||||
{activeSpace && (
|
{activeSpace && (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div
|
<div
|
||||||
className={`h-8 px-3 text-xs flex items-center gap-1.5 ${
|
className={`h-8 px-3 text-xs flex items-center gap-1.5 rounded-btn ${
|
||||||
wsConnected
|
wsConnected
|
||||||
? 'bg-[#22c55e]/15 text-[#22c55e] border border-[#22c55e]/30'
|
? 'badge-ok'
|
||||||
: 'bg-panel-elevated text-zinc-400 border border-border'
|
: 'badge-neutral'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<Radio className={`w-3.5 h-3.5 ${wsConnected ? 'animate-pulse' : ''}`} />
|
<Radio className={`w-3.5 h-3.5 ${wsConnected ? 'animate-pulse' : ''}`} />
|
||||||
@@ -320,9 +305,9 @@ export const SharedListsView: React.FC<SharedListsViewProps> = ({ onRefreshGloba
|
|||||||
<Users className="w-3.5 h-3.5 text-accent" />
|
<Users className="w-3.5 h-3.5 text-accent" />
|
||||||
<span>Code: {activeSpace.inviteCode}</span>
|
<span>Code: {activeSpace.inviteCode}</span>
|
||||||
{copiedInvite ? (
|
{copiedInvite ? (
|
||||||
<Check className="w-3.5 h-3.5 text-[#22c55e]" />
|
<Check className="w-3.5 h-3.5 text-status-ok" />
|
||||||
) : (
|
) : (
|
||||||
<Copy className="w-3 h-3 text-zinc-400" />
|
<Copy className="w-3 h-3 text-muted" />
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -383,16 +368,16 @@ export const SharedListsView: React.FC<SharedListsViewProps> = ({ onRefreshGloba
|
|||||||
<button
|
<button
|
||||||
key={lst.id}
|
key={lst.id}
|
||||||
onClick={() => setActiveListId(lst.id)}
|
onClick={() => setActiveListId(lst.id)}
|
||||||
className={`h-8 px-3 text-xs sm:text-sm font-medium flex items-center gap-1.5 transition-all border ${
|
className={`h-8 px-3 text-xs sm:text-sm font-medium flex items-center gap-1.5 transition-all border rounded-btn ${
|
||||||
isSelected
|
isSelected
|
||||||
? 'bg-accent text-white border-accent'
|
? 'bg-accent text-on-accent border-accent'
|
||||||
: 'bg-panel text-zinc-300 hover:text-white border-border'
|
: 'bg-panel text-muted hover:text-main border-border'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<span>{lst.icon}</span>
|
<span>{lst.icon}</span>
|
||||||
<span>{lst.title}</span>
|
<span>{lst.title}</span>
|
||||||
<span className={`px-1.5 py-0.2 text-[10px] font-semibold ${
|
<span className={`px-1.5 py-0.2 text-[10px] font-semibold rounded-pill ${
|
||||||
isSelected ? 'bg-white/20 text-white' : 'bg-canvas text-zinc-400'
|
isSelected ? 'bg-white/20 text-on-accent' : 'badge-neutral'
|
||||||
}`}>
|
}`}>
|
||||||
{openCount}
|
{openCount}
|
||||||
</span>
|
</span>
|
||||||
@@ -420,7 +405,7 @@ export const SharedListsView: React.FC<SharedListsViewProps> = ({ onRefreshGloba
|
|||||||
key={ic}
|
key={ic}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setNewListIcon(ic)}
|
onClick={() => setNewListIcon(ic)}
|
||||||
className={`w-9 h-9 flex items-center justify-center transition-colors ${
|
className={`w-9 h-9 flex items-center justify-center rounded-btn transition-colors ${
|
||||||
newListIcon === ic
|
newListIcon === ic
|
||||||
? 'bg-accent/20 border border-accent text-accent'
|
? 'bg-accent/20 border border-accent text-accent'
|
||||||
: 'bg-canvas hover:bg-panel-elevated border border-border'
|
: 'bg-canvas hover:bg-panel-elevated border border-border'
|
||||||
@@ -481,16 +466,16 @@ export const SharedListsView: React.FC<SharedListsViewProps> = ({ onRefreshGloba
|
|||||||
|
|
||||||
{/* Category Quick-Select Pills */}
|
{/* Category Quick-Select Pills */}
|
||||||
<div className="flex flex-wrap items-center gap-1.5">
|
<div className="flex flex-wrap items-center gap-1.5">
|
||||||
<span className="text-xs text-zinc-400 mr-1">Kategorie:</span>
|
<span className="text-xs text-muted mr-1">Kategorie:</span>
|
||||||
{CATEGORIES.map((cat) => (
|
{CATEGORIES.map((cat) => (
|
||||||
<button
|
<button
|
||||||
key={cat}
|
key={cat}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setNewItemCategory((prev) => (prev === cat ? '' : cat))}
|
onClick={() => setNewItemCategory((prev) => (prev === cat ? '' : cat))}
|
||||||
className={`px-2.5 py-0.5 text-xs transition-colors border ${
|
className={`px-2.5 py-0.5 text-xs transition-colors border rounded-pill ${
|
||||||
newItemCategory === cat
|
newItemCategory === cat
|
||||||
? 'bg-accent text-white border-accent font-medium'
|
? 'bg-accent text-on-accent border-accent font-medium'
|
||||||
: 'bg-canvas text-zinc-400 hover:text-white border-border'
|
: 'bg-canvas text-muted hover:text-main border-border'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{cat}
|
{cat}
|
||||||
@@ -500,9 +485,9 @@ export const SharedListsView: React.FC<SharedListsViewProps> = ({ onRefreshGloba
|
|||||||
</form>
|
</form>
|
||||||
|
|
||||||
{/* Open Items */}
|
{/* Open Items */}
|
||||||
<div className="border border-border divide-y divide-border">
|
<div className="border border-border divide-y divide-border rounded-card overflow-hidden">
|
||||||
{openItems.length === 0 ? (
|
{openItems.length === 0 ? (
|
||||||
<div className="py-6 text-center text-zinc-400 text-sm flex flex-col items-center gap-1.5">
|
<div className="py-6 text-center text-muted text-sm flex flex-col items-center gap-1.5">
|
||||||
<Sparkles className="w-4 h-4 text-accent" />
|
<Sparkles className="w-4 h-4 text-accent" />
|
||||||
<span>Alles erledigt. Keine offenen Punkte auf dieser Liste.</span>
|
<span>Alles erledigt. Keine offenen Punkte auf dieser Liste.</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -514,8 +499,8 @@ export const SharedListsView: React.FC<SharedListsViewProps> = ({ onRefreshGloba
|
|||||||
className="px-3 py-2 bg-canvas hover:bg-panel-elevated flex items-center justify-between gap-3 cursor-pointer transition-colors group"
|
className="px-3 py-2 bg-canvas hover:bg-panel-elevated flex items-center justify-between gap-3 cursor-pointer transition-colors group"
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2.5 min-w-0">
|
<div className="flex items-center gap-2.5 min-w-0">
|
||||||
<Circle className="w-4 h-4 text-zinc-500 group-hover:text-accent flex-shrink-0 transition-colors" />
|
<Circle className="w-4 h-4 text-faint group-hover:text-accent flex-shrink-0 transition-colors" />
|
||||||
<span className="text-sm font-normal text-white truncate">
|
<span className="text-sm font-normal text-main truncate">
|
||||||
{item.content}
|
{item.content}
|
||||||
</span>
|
</span>
|
||||||
{item.quantity && (
|
{item.quantity && (
|
||||||
@@ -524,7 +509,7 @@ export const SharedListsView: React.FC<SharedListsViewProps> = ({ onRefreshGloba
|
|||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{item.category && (
|
{item.category && (
|
||||||
<span className="hidden sm:inline px-2 py-0.2 bg-panel text-zinc-400 text-xs">
|
<span className="hidden sm:inline px-2 py-0.2 badge-neutral text-xs">
|
||||||
{item.category}
|
{item.category}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -549,7 +534,7 @@ export const SharedListsView: React.FC<SharedListsViewProps> = ({ onRefreshGloba
|
|||||||
{completedItems.length > 0 && (
|
{completedItems.length > 0 && (
|
||||||
<div className="mt-3 pt-2.5 border-t border-border">
|
<div className="mt-3 pt-2.5 border-t border-border">
|
||||||
<div className="flex items-center justify-between mb-2">
|
<div className="flex items-center justify-between mb-2">
|
||||||
<span className="text-xs font-medium uppercase tracking-wider text-zinc-400">
|
<span className="text-xs font-medium uppercase tracking-wider text-muted">
|
||||||
Erledigt ({completedItems.length})
|
Erledigt ({completedItems.length})
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
@@ -560,7 +545,7 @@ export const SharedListsView: React.FC<SharedListsViewProps> = ({ onRefreshGloba
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="border border-border divide-y divide-border opacity-75">
|
<div className="border border-border divide-y divide-border opacity-75 rounded-card overflow-hidden">
|
||||||
{completedItems.map((item) => (
|
{completedItems.map((item) => (
|
||||||
<div
|
<div
|
||||||
key={item.id}
|
key={item.id}
|
||||||
@@ -569,15 +554,15 @@ export const SharedListsView: React.FC<SharedListsViewProps> = ({ onRefreshGloba
|
|||||||
>
|
>
|
||||||
<div className="flex items-center gap-2.5 min-w-0">
|
<div className="flex items-center gap-2.5 min-w-0">
|
||||||
<CheckCircle2 className="w-4 h-4 text-accent flex-shrink-0" />
|
<CheckCircle2 className="w-4 h-4 text-accent flex-shrink-0" />
|
||||||
<span className="text-sm text-zinc-400 line-through truncate">
|
<span className="text-sm text-muted line-through truncate">
|
||||||
{item.content}
|
{item.content}
|
||||||
</span>
|
</span>
|
||||||
{item.quantity && (
|
{item.quantity && (
|
||||||
<span className="text-xs text-zinc-500">({item.quantity})</span>
|
<span className="text-xs text-faint">({item.quantity})</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{item.completedByName && (
|
{item.completedByName && (
|
||||||
<span className="text-xs text-zinc-500">✓ {item.completedByName}</span>
|
<span className="text-xs text-faint">✓ {item.completedByName}</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -382,40 +382,38 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
|||||||
<span>Zurück</span>
|
<span>Zurück</span>
|
||||||
</button>
|
</button>
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-base font-bold text-white tracking-tight flex items-center gap-2">
|
<h1 className="text-base font-bold text-main tracking-tight flex items-center gap-2">
|
||||||
<Shield className="w-4 h-4 text-accent" />
|
<Shield className="w-4 h-4 text-accent" />
|
||||||
<span>Administration & Team-Steuerung</span>
|
<span>Administration & Team-Steuerung</span>
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-xs text-zinc-400">
|
<p className="text-xs text-muted">
|
||||||
Benutzerverwaltung, Einladungscodes, Organisationen, Teams, Isolationsmodus & Mail-Server
|
Benutzerverwaltung, Einladungscodes, Organisationen, Teams, Isolationsmodus & Mail-Server
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span className="px-2.5 py-1 bg-accent/15 border border-accent text-accent text-xs font-semibold">
|
<span className="badge-accent">
|
||||||
{isServerAdmin ? 'Server-Administrator' : 'Team-Administrator'}
|
{isServerAdmin ? 'Server-Administrator' : 'Team-Administrator'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{statusMsg && (
|
{statusMsg && (
|
||||||
<div
|
<div
|
||||||
className={`p-3 border text-xs flex items-center justify-between gap-2 ${
|
className={`p-3 text-xs flex items-center justify-between gap-2 rounded-card ${
|
||||||
statusMsg.type === 'ok'
|
statusMsg.type === 'ok' ? 'alert-ok' : 'alert-err'
|
||||||
? 'bg-blue-950/30 border-accent text-blue-200'
|
|
||||||
: 'bg-red-950/30 border-red-500 text-red-200'
|
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{statusMsg.type === 'ok' ? (
|
{statusMsg.type === 'ok' ? (
|
||||||
<Check className="w-4 h-4 text-accent flex-shrink-0" />
|
<Check className="w-4 h-4 text-accent flex-shrink-0" />
|
||||||
) : (
|
) : (
|
||||||
<AlertCircle className="w-4 h-4 text-red-400 flex-shrink-0" />
|
<AlertCircle className="w-4 h-4 text-status-err flex-shrink-0" />
|
||||||
)}
|
)}
|
||||||
<span>{statusMsg.text}</span>
|
<span>{statusMsg.text}</span>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={() => setStatusMsg(null)}
|
onClick={() => setStatusMsg(null)}
|
||||||
className="text-zinc-400 hover:text-white text-[11px]"
|
className="text-muted hover:text-main text-[11px]"
|
||||||
>
|
>
|
||||||
Schließen
|
Schließen
|
||||||
</button>
|
</button>
|
||||||
@@ -425,19 +423,19 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
|||||||
{/* Main 2-Column Layout */}
|
{/* Main 2-Column Layout */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 items-start">
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 items-start">
|
||||||
{/* Left Navigation Sidebar */}
|
{/* Left Navigation Sidebar */}
|
||||||
<aside className="bg-panel border border-border divide-y divide-border">
|
<aside className="panel-surface divide-y divide-border overflow-hidden">
|
||||||
<button
|
<button
|
||||||
onClick={() => setSection('users')}
|
onClick={() => setSection('users')}
|
||||||
className={`w-full px-3.5 py-3 text-left text-xs font-medium flex items-center gap-2.5 transition-colors ${
|
className={`w-full px-3.5 py-3 text-left text-xs font-medium flex items-center gap-2.5 transition-colors ${
|
||||||
section === 'users'
|
section === 'users'
|
||||||
? 'bg-panel-elevated text-white border-l-2 border-l-accent'
|
? 'bg-panel-elevated text-main border-l-2 border-l-accent'
|
||||||
: 'text-zinc-400 hover:text-white hover:bg-white/[0.02]'
|
: 'text-muted hover:text-main hover:bg-canvas/50'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<Users className="w-4 h-4 text-accent flex-shrink-0" />
|
<Users className="w-4 h-4 text-accent flex-shrink-0" />
|
||||||
<div>
|
<div>
|
||||||
<p className="font-semibold">Benutzer & Einladungen</p>
|
<p className="font-semibold">Benutzer & Einladungen</p>
|
||||||
<p className="text-[10px] text-zinc-500">Konten, Rollen & Invite-Codes</p>
|
<p className="text-[10px] text-faint">Konten, Rollen & Invite-Codes</p>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@@ -445,14 +443,14 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
|||||||
onClick={() => setSection('orgs')}
|
onClick={() => setSection('orgs')}
|
||||||
className={`w-full px-3.5 py-3 text-left text-xs font-medium flex items-center gap-2.5 transition-colors ${
|
className={`w-full px-3.5 py-3 text-left text-xs font-medium flex items-center gap-2.5 transition-colors ${
|
||||||
section === 'orgs'
|
section === 'orgs'
|
||||||
? 'bg-panel-elevated text-white border-l-2 border-l-accent'
|
? 'bg-panel-elevated text-main border-l-2 border-l-accent'
|
||||||
: 'text-zinc-400 hover:text-white hover:bg-white/[0.02]'
|
: 'text-muted hover:text-main hover:bg-canvas/50'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<Building2 className="w-4 h-4 text-accent flex-shrink-0" />
|
<Building2 className="w-4 h-4 text-accent flex-shrink-0" />
|
||||||
<div>
|
<div>
|
||||||
<p className="font-semibold">Organisationen & Teams</p>
|
<p className="font-semibold">Organisationen & Teams</p>
|
||||||
<p className="text-[10px] text-zinc-500">Teams & Mitglieder zuweisen</p>
|
<p className="text-[10px] text-faint">Teams & Mitglieder zuweisen</p>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@@ -462,14 +460,14 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
|||||||
onClick={() => setSection('server')}
|
onClick={() => setSection('server')}
|
||||||
className={`w-full px-3.5 py-3 text-left text-xs font-medium flex items-center gap-2.5 transition-colors ${
|
className={`w-full px-3.5 py-3 text-left text-xs font-medium flex items-center gap-2.5 transition-colors ${
|
||||||
section === 'server'
|
section === 'server'
|
||||||
? 'bg-panel-elevated text-white border-l-2 border-l-accent'
|
? 'bg-panel-elevated text-main border-l-2 border-l-accent'
|
||||||
: 'text-zinc-400 hover:text-white hover:bg-white/[0.02]'
|
: 'text-muted hover:text-main hover:bg-canvas/50'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<Server className="w-4 h-4 text-accent flex-shrink-0" />
|
<Server className="w-4 h-4 text-accent flex-shrink-0" />
|
||||||
<div>
|
<div>
|
||||||
<p className="font-semibold">Server & Isolation</p>
|
<p className="font-semibold">Server & Isolation</p>
|
||||||
<p className="text-[10px] text-zinc-500">Sichtbarkeit & Registrierung</p>
|
<p className="text-[10px] text-faint">Sichtbarkeit & Registrierung</p>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@@ -477,14 +475,14 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
|||||||
onClick={() => setSection('smtp')}
|
onClick={() => setSection('smtp')}
|
||||||
className={`w-full px-3.5 py-3 text-left text-xs font-medium flex items-center gap-2.5 transition-colors ${
|
className={`w-full px-3.5 py-3 text-left text-xs font-medium flex items-center gap-2.5 transition-colors ${
|
||||||
section === 'smtp'
|
section === 'smtp'
|
||||||
? 'bg-panel-elevated text-white border-l-2 border-l-accent'
|
? 'bg-panel-elevated text-main border-l-2 border-l-accent'
|
||||||
: 'text-zinc-400 hover:text-white hover:bg-white/[0.02]'
|
: 'text-muted hover:text-main hover:bg-canvas/50'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<Mail className="w-4 h-4 text-accent flex-shrink-0" />
|
<Mail className="w-4 h-4 text-accent flex-shrink-0" />
|
||||||
<div>
|
<div>
|
||||||
<p className="font-semibold">E-Mail / SMTP-Server</p>
|
<p className="font-semibold">E-Mail / SMTP-Server</p>
|
||||||
<p className="text-[10px] text-zinc-500">Mailversand für Alarme</p>
|
<p className="text-[10px] text-faint">Mailversand für Alarme</p>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
</>
|
</>
|
||||||
@@ -492,23 +490,23 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
|||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
{/* Right Content Area */}
|
{/* Right Content Area */}
|
||||||
<div className="md:col-span-3 bg-panel border border-border p-5 text-xs">
|
<div className="md:col-span-3 panel-surface p-5 text-xs">
|
||||||
{/* ================================================================= */}
|
{/* ================================================================= */}
|
||||||
{/* SECTION 1: BENUTZER, ROLLEN & EINLADUNGEN */}
|
{/* SECTION 1: BENUTZER, ROLLEN & EINLADUNGEN */}
|
||||||
{/* ================================================================= */}
|
{/* ================================================================= */}
|
||||||
{section === 'users' && (
|
{section === 'users' && (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="border-b border-border pb-3">
|
<div className="border-b border-border pb-3">
|
||||||
<h2 className="text-sm font-bold text-white">Benutzersteuerung, Rollen & Einladungen</h2>
|
<h2 className="text-sm font-bold text-main">Benutzersteuerung, Rollen & Einladungen</h2>
|
||||||
<p className="text-zinc-400 mt-0.5">
|
<p className="text-muted mt-0.5">
|
||||||
Lege Benutzerkonten direkt an oder erzeuge Einladungscodes, mit denen sich eingeladene
|
Lege Benutzerkonten direkt an oder erzeuge Einladungscodes, mit denen sich eingeladene
|
||||||
Personen selbst registrieren können, auch wenn die öffentliche Registrierung gesperrt ist.
|
Personen selbst registrieren können, auch wenn die öffentliche Registrierung gesperrt ist.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Direct User Creation Form (Fixed Layout: 4 clean columns with min-w-0, no overflow!) */}
|
{/* Direct User Creation Form */}
|
||||||
<div className="p-4 bg-canvas border border-border space-y-3">
|
<div className="p-4 bg-canvas border border-border rounded-card space-y-3">
|
||||||
<h3 className="font-bold text-white flex items-center gap-1.5">
|
<h3 className="font-bold text-main flex items-center gap-1.5">
|
||||||
<UserPlus className="w-3.5 h-3.5 text-accent" />
|
<UserPlus className="w-3.5 h-3.5 text-accent" />
|
||||||
<span>Neues Benutzerkonto direkt anlegen</span>
|
<span>Neues Benutzerkonto direkt anlegen</span>
|
||||||
</h3>
|
</h3>
|
||||||
@@ -516,31 +514,31 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
|||||||
<form onSubmit={handleCreateUser} className="space-y-3">
|
<form onSubmit={handleCreateUser} className="space-y-3">
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3">
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3">
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
<label className="block text-zinc-400 mb-1">Anzeigename</label>
|
<label className="block text-muted mb-1">Anzeigename</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={newUserName}
|
value={newUserName}
|
||||||
onChange={(e) => setNewUserName(e.target.value)}
|
onChange={(e) => setNewUserName(e.target.value)}
|
||||||
placeholder="z.B. Anna Beispiel"
|
placeholder="z.B. Anna Beispiel"
|
||||||
required
|
required
|
||||||
className="w-full min-w-0 h-8 px-2.5 input-field text-white"
|
className="w-full min-w-0 h-8 px-2.5 input-field"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
<label className="block text-zinc-400 mb-1">E-Mail-Adresse</label>
|
<label className="block text-muted mb-1">E-Mail-Adresse</label>
|
||||||
<input
|
<input
|
||||||
type="email"
|
type="email"
|
||||||
value={newUserEmail}
|
value={newUserEmail}
|
||||||
onChange={(e) => setNewUserEmail(e.target.value)}
|
onChange={(e) => setNewUserEmail(e.target.value)}
|
||||||
placeholder="anna@beispiel.de"
|
placeholder="anna@beispiel.de"
|
||||||
required
|
required
|
||||||
className="w-full min-w-0 h-8 px-2.5 input-field text-white"
|
className="w-full min-w-0 h-8 px-2.5 input-field"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
<label className="block text-zinc-400 mb-1">Initial-Passwort</label>
|
<label className="block text-muted mb-1">Initial-Passwort</label>
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
value={newUserPassword}
|
value={newUserPassword}
|
||||||
@@ -548,16 +546,16 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
|||||||
placeholder="Mind. 4 Zeichen"
|
placeholder="Mind. 4 Zeichen"
|
||||||
minLength={4}
|
minLength={4}
|
||||||
required
|
required
|
||||||
className="w-full min-w-0 h-8 px-2.5 input-field text-white"
|
className="w-full min-w-0 h-8 px-2.5 input-field"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
<label className="block text-zinc-400 mb-1">Benutzerrolle</label>
|
<label className="block text-muted mb-1">Benutzerrolle</label>
|
||||||
<select
|
<select
|
||||||
value={newUserRole}
|
value={newUserRole}
|
||||||
onChange={(e) => setNewUserRole(e.target.value as UserRole)}
|
onChange={(e) => setNewUserRole(e.target.value as UserRole)}
|
||||||
className="w-full min-w-0 h-8 px-2 input-field text-white bg-panel"
|
className="w-full min-w-0 h-8 px-2 input-field bg-panel"
|
||||||
>
|
>
|
||||||
<option value="member">Mitglied (Standard)</option>
|
<option value="member">Mitglied (Standard)</option>
|
||||||
<option value="team_admin">Team-Admin (Darf einladen)</option>
|
<option value="team_admin">Team-Admin (Darf einladen)</option>
|
||||||
@@ -579,8 +577,8 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Invite Code Generator */}
|
{/* Invite Code Generator */}
|
||||||
<div className="p-4 bg-canvas border border-border space-y-3">
|
<div className="p-4 bg-canvas border border-border rounded-card space-y-3">
|
||||||
<h3 className="font-bold text-white flex items-center gap-1.5">
|
<h3 className="font-bold text-main flex items-center gap-1.5">
|
||||||
<KeyRound className="w-3.5 h-3.5 text-accent" />
|
<KeyRound className="w-3.5 h-3.5 text-accent" />
|
||||||
<span>Einladungscode erzeugen (gültig für 7 Tage)</span>
|
<span>Einladungscode erzeugen (gültig für 7 Tage)</span>
|
||||||
</h3>
|
</h3>
|
||||||
@@ -588,7 +586,7 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
|||||||
<form onSubmit={handleCreateInvite} className="space-y-3">
|
<form onSubmit={handleCreateInvite} className="space-y-3">
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3">
|
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3">
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
<label className="block text-zinc-400 mb-1">
|
<label className="block text-muted mb-1">
|
||||||
Gebundene E-Mail (optional)
|
Gebundene E-Mail (optional)
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@@ -596,16 +594,16 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
|||||||
value={inviteEmail}
|
value={inviteEmail}
|
||||||
onChange={(e) => setInviteEmail(e.target.value)}
|
onChange={(e) => setInviteEmail(e.target.value)}
|
||||||
placeholder="Leer lassen für beliebige E-Mail"
|
placeholder="Leer lassen für beliebige E-Mail"
|
||||||
className="w-full min-w-0 h-8 px-2.5 input-field text-white"
|
className="w-full min-w-0 h-8 px-2.5 input-field"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
<label className="block text-zinc-400 mb-1">Zugewiesene Rolle</label>
|
<label className="block text-muted mb-1">Zugewiesene Rolle</label>
|
||||||
<select
|
<select
|
||||||
value={inviteRole}
|
value={inviteRole}
|
||||||
onChange={(e) => setInviteRole(e.target.value as UserRole)}
|
onChange={(e) => setInviteRole(e.target.value as UserRole)}
|
||||||
className="w-full min-w-0 h-8 px-2 input-field text-white bg-panel"
|
className="w-full min-w-0 h-8 px-2 input-field bg-panel"
|
||||||
>
|
>
|
||||||
<option value="member">Mitglied</option>
|
<option value="member">Mitglied</option>
|
||||||
<option value="team_admin">Team-Admin</option>
|
<option value="team_admin">Team-Admin</option>
|
||||||
@@ -614,13 +612,13 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
<label className="block text-zinc-400 mb-1">
|
<label className="block text-muted mb-1">
|
||||||
Automatisch Team zuweisen (optional)
|
Automatisch Team zuweisen (optional)
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
value={inviteTeamId}
|
value={inviteTeamId}
|
||||||
onChange={(e) => setInviteTeamId(e.target.value)}
|
onChange={(e) => setInviteTeamId(e.target.value)}
|
||||||
className="w-full min-w-0 h-8 px-2 input-field text-white bg-panel"
|
className="w-full min-w-0 h-8 px-2 input-field bg-panel"
|
||||||
>
|
>
|
||||||
<option value="">Kein direktes Team</option>
|
<option value="">Kein direktes Team</option>
|
||||||
{flattenedTeams.map((t) => (
|
{flattenedTeams.map((t) => (
|
||||||
@@ -635,7 +633,7 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
|||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="btn-action h-8 px-4 text-xs font-semibold flex items-center gap-1.5 border-accent text-white"
|
className="btn-action h-8 px-4 text-xs font-semibold flex items-center gap-1.5 border-accent text-main"
|
||||||
>
|
>
|
||||||
<KeyRound className="w-3.5 h-3.5 text-accent" />
|
<KeyRound className="w-3.5 h-3.5 text-accent" />
|
||||||
<span>Einladungscode generieren</span>
|
<span>Einladungscode generieren</span>
|
||||||
@@ -646,29 +644,29 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
|||||||
{/* Active Invites List */}
|
{/* Active Invites List */}
|
||||||
{invites.length > 0 && (
|
{invites.length > 0 && (
|
||||||
<div className="pt-2 border-t border-border space-y-1.5">
|
<div className="pt-2 border-t border-border space-y-1.5">
|
||||||
<p className="text-[11px] font-semibold text-zinc-400">
|
<p className="text-[11px] font-semibold text-muted">
|
||||||
Aktive & bisherige Einladungscodes:
|
Aktive & bisherige Einladungscodes:
|
||||||
</p>
|
</p>
|
||||||
<div className="divide-y divide-border border border-border bg-panel">
|
<div className="divide-y divide-border border border-border bg-panel rounded-card overflow-hidden">
|
||||||
{invites.map((inv) => (
|
{invites.map((inv) => (
|
||||||
<div
|
<div
|
||||||
key={inv.id}
|
key={inv.id}
|
||||||
className="p-2 flex flex-wrap items-center justify-between gap-2"
|
className="p-2 flex flex-wrap items-center justify-between gap-2"
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2.5">
|
<div className="flex items-center gap-2.5">
|
||||||
<code className="px-2 py-0.5 bg-canvas border border-border font-mono font-bold text-accent">
|
<code className="px-2 py-0.5 bg-canvas border border-border font-mono font-bold text-accent rounded-card">
|
||||||
{inv.code}
|
{inv.code}
|
||||||
</code>
|
</code>
|
||||||
<span className="text-zinc-300">
|
<span className="text-muted">
|
||||||
Rolle: <strong>{inv.role}</strong>
|
Rolle: <strong className="text-main">{inv.role}</strong>
|
||||||
{inv.email ? ` · Für: ${inv.email}` : ''}
|
{inv.email ? ` · Für: ${inv.email}` : ''}
|
||||||
</span>
|
</span>
|
||||||
{inv.usedBy ? (
|
{inv.usedBy ? (
|
||||||
<span className="px-1.5 py-0.5 bg-zinc-800 text-zinc-400 text-[10px]">
|
<span className="badge-neutral text-[10px]">
|
||||||
Eingelöst
|
Eingelöst
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<span className="px-1.5 py-0.5 bg-emerald-950/60 border border-emerald-500/40 text-emerald-300 text-[10px]">
|
<span className="badge-ok text-[10px]">
|
||||||
Offen
|
Offen
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -701,23 +699,23 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
|||||||
|
|
||||||
{/* Users List */}
|
{/* Users List */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<h3 className="font-bold text-white">
|
<h3 className="font-bold text-main">
|
||||||
Registrierte Benutzer ({users.length})
|
Registrierte Benutzer ({users.length})
|
||||||
</h3>
|
</h3>
|
||||||
<div className="border border-border divide-y divide-border bg-canvas">
|
<div className="border border-border divide-y divide-border bg-canvas rounded-card overflow-hidden">
|
||||||
{users.map((u) => (
|
{users.map((u) => (
|
||||||
<div key={u.id} className="p-3 space-y-2">
|
<div key={u.id} className="p-3 space-y-2">
|
||||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-2">
|
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-2">
|
||||||
<div>
|
<div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="font-bold text-white">{u.displayName}</span>
|
<span className="font-bold text-main">{u.displayName}</span>
|
||||||
{!u.isActive && (
|
{!u.isActive && (
|
||||||
<span className="px-1.5 py-0.5 bg-red-950 text-red-300 border border-red-500/40 text-[10px]">
|
<span className="badge-err text-[10px]">
|
||||||
Gesperrt
|
Gesperrt
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<p className="text-zinc-400 text-[11px]">{u.email}</p>
|
<p className="text-muted text-[11px]">{u.email}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-wrap items-center gap-2">
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
@@ -726,7 +724,7 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
|||||||
value={u.role}
|
value={u.role}
|
||||||
disabled={!isServerAdmin && u.role === 'admin'}
|
disabled={!isServerAdmin && u.role === 'admin'}
|
||||||
onChange={(e) => handleChangeUserRole(u, e.target.value as UserRole)}
|
onChange={(e) => handleChangeUserRole(u, e.target.value as UserRole)}
|
||||||
className="h-7 px-2 input-field text-[11px] text-white bg-panel"
|
className="h-7 px-2 input-field text-[11px] bg-panel"
|
||||||
title="Benutzerrolle ändern"
|
title="Benutzerrolle ändern"
|
||||||
>
|
>
|
||||||
<option value="member">Mitglied</option>
|
<option value="member">Mitglied</option>
|
||||||
@@ -775,7 +773,7 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
|||||||
value={resetPwdVal}
|
value={resetPwdVal}
|
||||||
onChange={(e) => setResetPwdVal(e.target.value)}
|
onChange={(e) => setResetPwdVal(e.target.value)}
|
||||||
placeholder="Neues Passwort eingeben…"
|
placeholder="Neues Passwort eingeben…"
|
||||||
className="flex-1 h-7 px-2 input-field text-white"
|
className="flex-1 h-7 px-2 input-field"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
onClick={() => handleConfirmResetPassword(u.id)}
|
onClick={() => handleConfirmResetPassword(u.id)}
|
||||||
@@ -804,8 +802,8 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
|||||||
{section === 'orgs' && (
|
{section === 'orgs' && (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="border-b border-border pb-3">
|
<div className="border-b border-border pb-3">
|
||||||
<h2 className="text-sm font-bold text-white">Organisationen & Teams verwalten</h2>
|
<h2 className="text-sm font-bold text-main">Organisationen & Teams verwalten</h2>
|
||||||
<p className="text-zinc-400 mt-0.5">
|
<p className="text-muted mt-0.5">
|
||||||
Erstelle Organisationen und unterteile sie in Teams. Weise Benutzern Teams zu, damit
|
Erstelle Organisationen und unterteile sie in Teams. Weise Benutzern Teams zu, damit
|
||||||
sie im Modus "Teams isoliert" gemeinsam an Listen und Aufgaben arbeiten können.
|
sie im Modus "Teams isoliert" gemeinsam an Listen und Aufgaben arbeiten können.
|
||||||
</p>
|
</p>
|
||||||
@@ -813,8 +811,8 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
|||||||
|
|
||||||
{/* Create Organization & Team */}
|
{/* Create Organization & Team */}
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||||
<div className="p-4 bg-canvas border border-border space-y-3">
|
<div className="p-4 bg-canvas border border-border rounded-card space-y-3">
|
||||||
<h3 className="font-bold text-white">1. Neue Organisation gründen</h3>
|
<h3 className="font-bold text-main">1. Neue Organisation gründen</h3>
|
||||||
<form onSubmit={handleCreateOrg} className="flex gap-2">
|
<form onSubmit={handleCreateOrg} className="flex gap-2">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
@@ -822,7 +820,7 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
|||||||
onChange={(e) => setNewOrgName(e.target.value)}
|
onChange={(e) => setNewOrgName(e.target.value)}
|
||||||
placeholder="z.B. Homelab, Familie, Firma…"
|
placeholder="z.B. Homelab, Familie, Firma…"
|
||||||
required
|
required
|
||||||
className="flex-1 min-w-0 h-8 px-2.5 input-field text-white"
|
className="flex-1 min-w-0 h-8 px-2.5 input-field"
|
||||||
/>
|
/>
|
||||||
<button type="submit" className="btn-primary h-8 px-3 text-xs flex-shrink-0">
|
<button type="submit" className="btn-primary h-8 px-3 text-xs flex-shrink-0">
|
||||||
Anlegen
|
Anlegen
|
||||||
@@ -830,10 +828,10 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="p-4 bg-canvas border border-border space-y-3">
|
<div className="p-4 bg-canvas border border-border rounded-card space-y-3">
|
||||||
<h3 className="font-bold text-white">2. Neues Team in Organisation anlegen</h3>
|
<h3 className="font-bold text-main">2. Neues Team in Organisation anlegen</h3>
|
||||||
{orgs.length === 0 ? (
|
{orgs.length === 0 ? (
|
||||||
<p className="text-zinc-500">
|
<p className="text-faint">
|
||||||
Bitte lege zuerst links eine Organisation an.
|
Bitte lege zuerst links eine Organisation an.
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
@@ -842,7 +840,7 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
|||||||
<select
|
<select
|
||||||
value={selectedOrgId}
|
value={selectedOrgId}
|
||||||
onChange={(e) => setSelectedOrgId(e.target.value)}
|
onChange={(e) => setSelectedOrgId(e.target.value)}
|
||||||
className="w-full min-w-0 h-8 px-2 input-field text-white bg-panel"
|
className="w-full min-w-0 h-8 px-2 input-field bg-panel"
|
||||||
>
|
>
|
||||||
{orgs.map((o) => (
|
{orgs.map((o) => (
|
||||||
<option key={o.id} value={o.id}>
|
<option key={o.id} value={o.id}>
|
||||||
@@ -856,7 +854,7 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
|||||||
onChange={(e) => setNewTeamName(e.target.value)}
|
onChange={(e) => setNewTeamName(e.target.value)}
|
||||||
placeholder="Team-Name (z.B. Infrastruktur)"
|
placeholder="Team-Name (z.B. Infrastruktur)"
|
||||||
required
|
required
|
||||||
className="w-full min-w-0 h-8 px-2.5 input-field text-white"
|
className="w-full min-w-0 h-8 px-2.5 input-field"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
@@ -865,7 +863,7 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
|||||||
value={newTeamDesc}
|
value={newTeamDesc}
|
||||||
onChange={(e) => setNewTeamDesc(e.target.value)}
|
onChange={(e) => setNewTeamDesc(e.target.value)}
|
||||||
placeholder="Kurzbeschreibung (optional)…"
|
placeholder="Kurzbeschreibung (optional)…"
|
||||||
className="flex-1 min-w-0 h-8 px-2.5 input-field text-white"
|
className="flex-1 min-w-0 h-8 px-2.5 input-field"
|
||||||
/>
|
/>
|
||||||
<button type="submit" className="btn-primary h-8 px-3 text-xs flex-shrink-0">
|
<button type="submit" className="btn-primary h-8 px-3 text-xs flex-shrink-0">
|
||||||
Team erstellen
|
Team erstellen
|
||||||
@@ -879,19 +877,19 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
|||||||
{/* Organizations & Nested Teams Display */}
|
{/* Organizations & Nested Teams Display */}
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{orgs.length === 0 ? (
|
{orgs.length === 0 ? (
|
||||||
<div className="p-6 bg-canvas border border-border text-center text-zinc-500">
|
<div className="p-6 bg-canvas border border-border rounded-card text-center text-faint">
|
||||||
Noch keine Organisationen vorhanden.
|
Noch keine Organisationen vorhanden.
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
orgs.map((org) => {
|
orgs.map((org) => {
|
||||||
const orgTeams = Array.isArray(org.teams) ? org.teams : [];
|
const orgTeams = Array.isArray(org.teams) ? org.teams : [];
|
||||||
return (
|
return (
|
||||||
<div key={org.id} className="bg-canvas border border-border">
|
<div key={org.id} className="bg-canvas border border-border rounded-card overflow-hidden">
|
||||||
<div className="p-3 bg-panel-elevated border-b border-border flex items-center justify-between">
|
<div className="p-3 bg-panel-elevated border-b border-border flex items-center justify-between">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Building2 className="w-4 h-4 text-accent" />
|
<Building2 className="w-4 h-4 text-accent" />
|
||||||
<span className="font-bold text-sm text-white">{org.name}</span>
|
<span className="font-bold text-sm text-main">{org.name}</span>
|
||||||
<span className="text-[11px] text-zinc-400">
|
<span className="text-[11px] text-muted">
|
||||||
({orgTeams.length} {orgTeams.length === 1 ? 'Team' : 'Teams'})
|
({orgTeams.length} {orgTeams.length === 1 ? 'Team' : 'Teams'})
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -906,7 +904,7 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
|||||||
|
|
||||||
<div className="p-3 space-y-3">
|
<div className="p-3 space-y-3">
|
||||||
{orgTeams.length === 0 ? (
|
{orgTeams.length === 0 ? (
|
||||||
<p className="text-zinc-500 text-xs">
|
<p className="text-faint text-xs">
|
||||||
Noch keine Teams in dieser Organisation.
|
Noch keine Teams in dieser Organisation.
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
@@ -915,16 +913,16 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={team.id}
|
key={team.id}
|
||||||
className="p-3 bg-panel border border-border space-y-3"
|
className="p-3 bg-panel border border-border rounded-card space-y-3"
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between border-b border-border pb-2">
|
<div className="flex items-center justify-between border-b border-border pb-2">
|
||||||
<div>
|
<div>
|
||||||
<span className="font-bold text-white flex items-center gap-1.5">
|
<span className="font-bold text-main flex items-center gap-1.5">
|
||||||
<Users className="w-3.5 h-3.5 text-accent" />
|
<Users className="w-3.5 h-3.5 text-accent" />
|
||||||
<span>{team.name}</span>
|
<span>{team.name}</span>
|
||||||
</span>
|
</span>
|
||||||
{team.description && (
|
{team.description && (
|
||||||
<p className="text-[11px] text-zinc-400 mt-0.5">
|
<p className="text-[11px] text-muted mt-0.5">
|
||||||
{team.description}
|
{team.description}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
@@ -940,11 +938,11 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
|||||||
|
|
||||||
{/* Team Members */}
|
{/* Team Members */}
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<p className="text-[11px] font-semibold text-zinc-400">
|
<p className="text-[11px] font-semibold text-muted">
|
||||||
Mitglieder ({members.length}):
|
Mitglieder ({members.length}):
|
||||||
</p>
|
</p>
|
||||||
{members.length === 0 ? (
|
{members.length === 0 ? (
|
||||||
<p className="text-[11px] text-zinc-500">
|
<p className="text-[11px] text-faint">
|
||||||
Noch keine Mitglieder zugewiesen.
|
Noch keine Mitglieder zugewiesen.
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
@@ -952,19 +950,19 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
|||||||
{members.map((m) => (
|
{members.map((m) => (
|
||||||
<div
|
<div
|
||||||
key={m.userId}
|
key={m.userId}
|
||||||
className="px-2 py-1 bg-canvas border border-border flex items-center gap-2"
|
className="px-2 py-1 bg-canvas border border-border rounded-btn flex items-center gap-2"
|
||||||
>
|
>
|
||||||
<span className="text-white font-medium">
|
<span className="text-main font-medium">
|
||||||
{m.displayName}
|
{m.displayName}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-[10px] text-zinc-400">
|
<span className="text-[10px] text-muted">
|
||||||
({m.role})
|
({m.role})
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
handleRemoveMemberFromTeam(team.id, m.userId)
|
handleRemoveMemberFromTeam(team.id, m.userId)
|
||||||
}
|
}
|
||||||
className="text-zinc-500 hover:text-red-400"
|
className="text-faint hover:text-status-err"
|
||||||
title="Aus Team entfernen"
|
title="Aus Team entfernen"
|
||||||
>
|
>
|
||||||
×
|
×
|
||||||
@@ -985,7 +983,7 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
|||||||
[team.id]: e.target.value,
|
[team.id]: e.target.value,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
className="flex-1 min-w-[160px] h-7 px-2 input-field text-[11px] text-white bg-canvas"
|
className="flex-1 min-w-[160px] h-7 px-2 input-field text-[11px] bg-canvas"
|
||||||
>
|
>
|
||||||
<option value="">Benutzer auswählen…</option>
|
<option value="">Benutzer auswählen…</option>
|
||||||
{users.map((u) => (
|
{users.map((u) => (
|
||||||
@@ -1003,7 +1001,7 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
|||||||
[team.id]: e.target.value,
|
[team.id]: e.target.value,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
className="h-7 px-2 input-field text-[11px] text-white bg-canvas"
|
className="h-7 px-2 input-field text-[11px] bg-canvas"
|
||||||
>
|
>
|
||||||
<option value="member">Mitglied</option>
|
<option value="member">Mitglied</option>
|
||||||
<option value="team_admin">Team-Admin</option>
|
<option value="team_admin">Team-Admin</option>
|
||||||
@@ -1037,10 +1035,10 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
|||||||
{section === 'server' && isServerAdmin && (
|
{section === 'server' && isServerAdmin && (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="border-b border-border pb-3">
|
<div className="border-b border-border pb-3">
|
||||||
<h2 className="text-sm font-bold text-white">
|
<h2 className="text-sm font-bold text-main">
|
||||||
Server-Grundeinstellungen & Benutzer-Sichtbarkeit
|
Server-Grundeinstellungen & Benutzer-Sichtbarkeit
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-zinc-400 mt-0.5">
|
<p className="text-muted mt-0.5">
|
||||||
Als Server-Admin bestimmst du zentral für alle Nutzer, ob sie komplett isoliert
|
Als Server-Admin bestimmst du zentral für alle Nutzer, ob sie komplett isoliert
|
||||||
arbeiten, sich nur innerhalb ihrer Teams sehen oder ob alle Nutzer für alle sichtbar sind.
|
arbeiten, sich nur innerhalb ihrer Teams sehen oder ob alle Nutzer für alle sichtbar sind.
|
||||||
</p>
|
</p>
|
||||||
@@ -1048,7 +1046,7 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
|||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-zinc-300 font-semibold mb-1">
|
<label className="block text-muted font-semibold mb-1">
|
||||||
Name der MindSift-Instanz
|
Name der MindSift-Instanz
|
||||||
</label>
|
</label>
|
||||||
<div className="flex gap-2 max-w-md">
|
<div className="flex gap-2 max-w-md">
|
||||||
@@ -1058,7 +1056,7 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
|||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setSettings((prev) => ({ ...prev, instanceName: e.target.value }))
|
setSettings((prev) => ({ ...prev, instanceName: e.target.value }))
|
||||||
}
|
}
|
||||||
className="flex-1 h-9 px-3 input-field text-white"
|
className="flex-1 h-9 px-3 input-field"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -1072,7 +1070,7 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
|||||||
|
|
||||||
{/* Sharing Isolation Cards */}
|
{/* Sharing Isolation Cards */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="block text-zinc-300 font-semibold flex items-center gap-1.5">
|
<label className="block text-muted font-semibold flex items-center gap-1.5">
|
||||||
<Share2 className="w-4 h-4 text-accent" />
|
<Share2 className="w-4 h-4 text-accent" />
|
||||||
<span>Globale Sharing- & Sichtbarkeits-Isolation</span>
|
<span>Globale Sharing- & Sichtbarkeits-Isolation</span>
|
||||||
</label>
|
</label>
|
||||||
@@ -1081,14 +1079,14 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => handleSaveSystemSettings({ sharingMode: 'strict_private' })}
|
onClick={() => handleSaveSystemSettings({ sharingMode: 'strict_private' })}
|
||||||
className={`p-4 text-left border transition-colors ${
|
className={`p-4 text-left border rounded-card transition-colors ${
|
||||||
settings.sharingMode === 'strict_private'
|
settings.sharingMode === 'strict_private'
|
||||||
? 'bg-canvas border-accent text-white'
|
? 'bg-canvas border-accent text-main shadow-panel'
|
||||||
: 'bg-canvas/50 border-border text-zinc-400 hover:text-white'
|
: 'bg-canvas/50 border-border text-muted hover:text-main'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<p className="font-bold text-xs text-white">1. Strikt Privat (Keiner sieht andere)</p>
|
<p className="font-bold text-xs text-main">1. Strikt Privat (Keiner sieht andere)</p>
|
||||||
<p className="text-[11px] text-zinc-400 mt-1 leading-relaxed">
|
<p className="text-[11px] text-muted mt-1 leading-relaxed">
|
||||||
Jeder Benutzer arbeitet komplett für sich allein. Keine geteilten Spaces und keine
|
Jeder Benutzer arbeitet komplett für sich allein. Keine geteilten Spaces und keine
|
||||||
Benutzersuche möglich.
|
Benutzersuche möglich.
|
||||||
</p>
|
</p>
|
||||||
@@ -1097,14 +1095,14 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => handleSaveSystemSettings({ sharingMode: 'teams_only' })}
|
onClick={() => handleSaveSystemSettings({ sharingMode: 'teams_only' })}
|
||||||
className={`p-4 text-left border transition-colors ${
|
className={`p-4 text-left border rounded-card transition-colors ${
|
||||||
settings.sharingMode === 'teams_only'
|
settings.sharingMode === 'teams_only'
|
||||||
? 'bg-canvas border-accent text-white'
|
? 'bg-canvas border-accent text-main shadow-panel'
|
||||||
: 'bg-canvas/50 border-border text-zinc-400 hover:text-white'
|
: 'bg-canvas/50 border-border text-muted hover:text-main'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<p className="font-bold text-xs text-white">2. Teams isoliert (Empfohlen)</p>
|
<p className="font-bold text-xs text-main">2. Teams isoliert (Empfohlen)</p>
|
||||||
<p className="text-[11px] text-zinc-400 mt-1 leading-relaxed">
|
<p className="text-[11px] text-muted mt-1 leading-relaxed">
|
||||||
Benutzer sehen ausschließlich Personen, mit denen sie mindestens ein gemeinsames Team
|
Benutzer sehen ausschließlich Personen, mit denen sie mindestens ein gemeinsames Team
|
||||||
teilen.
|
teilen.
|
||||||
</p>
|
</p>
|
||||||
@@ -1113,14 +1111,14 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => handleSaveSystemSettings({ sharingMode: 'open' })}
|
onClick={() => handleSaveSystemSettings({ sharingMode: 'open' })}
|
||||||
className={`p-4 text-left border transition-colors ${
|
className={`p-4 text-left border rounded-card transition-colors ${
|
||||||
settings.sharingMode === 'open'
|
settings.sharingMode === 'open'
|
||||||
? 'bg-canvas border-accent text-white'
|
? 'bg-canvas border-accent text-main shadow-panel'
|
||||||
: 'bg-canvas/50 border-border text-zinc-400 hover:text-white'
|
: 'bg-canvas/50 border-border text-muted hover:text-main'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<p className="font-bold text-xs text-white">3. Offen (Alle sehen alle)</p>
|
<p className="font-bold text-xs text-main">3. Offen (Alle sehen alle)</p>
|
||||||
<p className="text-[11px] text-zinc-400 mt-1 leading-relaxed">
|
<p className="text-[11px] text-muted mt-1 leading-relaxed">
|
||||||
Alle registrierten Benutzer dieser Instanz können sich gegenseitig sehen und Spaces
|
Alle registrierten Benutzer dieser Instanz können sich gegenseitig sehen und Spaces
|
||||||
miteinander teilen.
|
miteinander teilen.
|
||||||
</p>
|
</p>
|
||||||
@@ -1129,10 +1127,10 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Public Registration Toggle */}
|
{/* Public Registration Toggle */}
|
||||||
<div className="p-4 bg-canvas border border-border flex items-center justify-between gap-4">
|
<div className="p-4 bg-canvas border border-border rounded-card flex items-center justify-between gap-4">
|
||||||
<div>
|
<div>
|
||||||
<p className="font-bold text-white">Öffentliche Selbstregistrierung erlauben</p>
|
<p className="font-bold text-main">Öffentliche Selbstregistrierung erlauben</p>
|
||||||
<p className="text-[11px] text-zinc-400 mt-0.5">
|
<p className="text-[11px] text-muted mt-0.5">
|
||||||
Wenn deaktiviert, können sich Fremde auf dem Login-Bildschirm nicht registrieren.
|
Wenn deaktiviert, können sich Fremde auf dem Login-Bildschirm nicht registrieren.
|
||||||
Neue Benutzer benötigen zwingend einen Einladungscode oder müssen von einem Admin
|
Neue Benutzer benötigen zwingend einen Einladungscode oder müssen von einem Admin
|
||||||
angelegt werden.
|
angelegt werden.
|
||||||
@@ -1144,10 +1142,10 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
|||||||
onClick={() =>
|
onClick={() =>
|
||||||
handleSaveSystemSettings({ allowRegistration: !settings.allowRegistration })
|
handleSaveSystemSettings({ allowRegistration: !settings.allowRegistration })
|
||||||
}
|
}
|
||||||
className={`h-8 px-4 text-xs font-semibold border flex-shrink-0 ${
|
className={`h-8 px-4 text-xs font-semibold border rounded-btn flex-shrink-0 transition-colors ${
|
||||||
settings.allowRegistration
|
settings.allowRegistration
|
||||||
? 'bg-accent/20 border-accent text-accent'
|
? 'bg-accent/20 border-accent text-accent'
|
||||||
: 'bg-panel border-border text-zinc-400'
|
: 'bg-panel border-border text-muted hover:text-main'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{settings.allowRegistration ? 'Erlaubt (Offen)' : 'Gesperrt (Nur mit Einladung)'}
|
{settings.allowRegistration ? 'Erlaubt (Offen)' : 'Gesperrt (Nur mit Einladung)'}
|
||||||
@@ -1164,17 +1162,17 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
|||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="border-b border-border pb-3 flex flex-col sm:flex-row sm:items-center justify-between gap-2">
|
<div className="border-b border-border pb-3 flex flex-col sm:flex-row sm:items-center justify-between gap-2">
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-sm font-bold text-white">
|
<h2 className="text-sm font-bold text-main">
|
||||||
E-Mail / SMTP-Server für Aufgaben-Alarme
|
E-Mail / SMTP-Server für Aufgaben-Alarme
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-zinc-400 mt-0.5">
|
<p className="text-muted mt-0.5">
|
||||||
Konfiguriere den Postausgangsserver per Docker-Compose Environment-Variablen oder
|
Konfiguriere den Postausgangsserver per Docker-Compose Environment-Variablen oder
|
||||||
direkt hier über die Oberfläche (wird persistent in der SQLite-Datenbank gespeichert).
|
direkt hier über die Oberfläche (wird persistent in der SQLite-Datenbank gespeichert).
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{settings.smtpConfiguredViaEnv && (
|
{settings.smtpConfiguredViaEnv && (
|
||||||
<span className="px-2.5 py-1 bg-emerald-950/50 border border-emerald-500/40 text-emerald-300 text-[11px] flex-shrink-0">
|
<span className="badge-ok text-[11px] flex-shrink-0">
|
||||||
Docker Env erkannt
|
Docker Env erkannt
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -1182,7 +1180,7 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
|||||||
|
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-zinc-300 font-medium mb-1">
|
<label className="block text-muted font-medium mb-1">
|
||||||
SMTP Host (`SMTP_HOST`)
|
SMTP Host (`SMTP_HOST`)
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@@ -1191,13 +1189,13 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
|||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setSettings((prev) => ({ ...prev, smtpHost: e.target.value }))
|
setSettings((prev) => ({ ...prev, smtpHost: e.target.value }))
|
||||||
}
|
}
|
||||||
placeholder="z.B. smtp.gmail.com oder mail.deinedomain.de"
|
placeholder="z.B. smtp.example.com oder mail.deinedomain.de"
|
||||||
className="w-full h-9 px-3 input-field text-white font-mono"
|
className="w-full h-9 px-3 input-field font-mono"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-zinc-300 font-medium mb-1">
|
<label className="block text-muted font-medium mb-1">
|
||||||
SMTP Port (`SMTP_PORT`)
|
SMTP Port (`SMTP_PORT`)
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@@ -1207,12 +1205,12 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
|||||||
setSettings((prev) => ({ ...prev, smtpPort: Number(e.target.value) }))
|
setSettings((prev) => ({ ...prev, smtpPort: Number(e.target.value) }))
|
||||||
}
|
}
|
||||||
placeholder="587"
|
placeholder="587"
|
||||||
className="w-full h-9 px-3 input-field text-white font-mono"
|
className="w-full h-9 px-3 input-field font-mono"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-zinc-300 font-medium mb-1">
|
<label className="block text-muted font-medium mb-1">
|
||||||
SMTP Benutzername (`SMTP_USER`)
|
SMTP Benutzername (`SMTP_USER`)
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@@ -1222,12 +1220,12 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
|||||||
setSettings((prev) => ({ ...prev, smtpUser: e.target.value }))
|
setSettings((prev) => ({ ...prev, smtpUser: e.target.value }))
|
||||||
}
|
}
|
||||||
placeholder="alerts@deinedomain.de"
|
placeholder="alerts@deinedomain.de"
|
||||||
className="w-full h-9 px-3 input-field text-white font-mono"
|
className="w-full h-9 px-3 input-field font-mono"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-zinc-300 font-medium mb-1">
|
<label className="block text-muted font-medium mb-1">
|
||||||
SMTP Passwort (`SMTP_PASS`)
|
SMTP Passwort (`SMTP_PASS`)
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@@ -1237,12 +1235,12 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
|||||||
setSettings((prev) => ({ ...prev, smtpPass: e.target.value }))
|
setSettings((prev) => ({ ...prev, smtpPass: e.target.value }))
|
||||||
}
|
}
|
||||||
placeholder="••••••••••••"
|
placeholder="••••••••••••"
|
||||||
className="w-full h-9 px-3 input-field text-white font-mono"
|
className="w-full h-9 px-3 input-field font-mono"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-zinc-300 font-medium mb-1">
|
<label className="block text-muted font-medium mb-1">
|
||||||
Absender-Adresse (`SMTP_FROM`)
|
Absender-Adresse (`SMTP_FROM`)
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@@ -1252,7 +1250,7 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
|||||||
setSettings((prev) => ({ ...prev, smtpFrom: e.target.value }))
|
setSettings((prev) => ({ ...prev, smtpFrom: e.target.value }))
|
||||||
}
|
}
|
||||||
placeholder="MindSift <alerts@deinedomain.de>"
|
placeholder="MindSift <alerts@deinedomain.de>"
|
||||||
className="w-full h-9 px-3 input-field text-white"
|
className="w-full h-9 px-3 input-field"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -1264,9 +1262,9 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
|||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setSettings((prev) => ({ ...prev, smtpTls: e.target.checked }))
|
setSettings((prev) => ({ ...prev, smtpTls: e.target.checked }))
|
||||||
}
|
}
|
||||||
className="w-4 h-4 accent-blue-600"
|
className="w-4 h-4 checkbox-square cursor-pointer"
|
||||||
/>
|
/>
|
||||||
<span className="text-white font-medium">
|
<span className="text-main font-medium">
|
||||||
STARTTLS / TLS-Verschlüsselung verwenden (`SMTP_TLS=true`)
|
STARTTLS / TLS-Verschlüsselung verwenden (`SMTP_TLS=true`)
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
@@ -1284,9 +1282,9 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Test SMTP Delivery */}
|
{/* Test SMTP Delivery */}
|
||||||
<div className="p-4 bg-canvas border border-border space-y-2">
|
<div className="p-4 bg-canvas border border-border rounded-card space-y-2">
|
||||||
<h3 className="font-bold text-white">SMTP-Verbindung testen</h3>
|
<h3 className="font-bold text-main">SMTP-Verbindung testen</h3>
|
||||||
<p className="text-[11px] text-zinc-400">
|
<p className="text-[11px] text-muted">
|
||||||
Sendet sofort eine Test-E-Mail mit den oben eingetragenen Daten.
|
Sendet sofort eine Test-E-Mail mit den oben eingetragenen Daten.
|
||||||
</p>
|
</p>
|
||||||
<div className="flex flex-col sm:flex-row gap-2">
|
<div className="flex flex-col sm:flex-row gap-2">
|
||||||
@@ -1295,7 +1293,7 @@ export const AdminSettingsView: React.FC<AdminSettingsViewProps> = ({
|
|||||||
value={testSmtpEmail}
|
value={testSmtpEmail}
|
||||||
onChange={(e) => setTestSmtpEmail(e.target.value)}
|
onChange={(e) => setTestSmtpEmail(e.target.value)}
|
||||||
placeholder="empfaenger@beispiel.de"
|
placeholder="empfaenger@beispiel.de"
|
||||||
className="flex-1 h-8 px-3 input-field text-white"
|
className="flex-1 h-8 px-3 input-field"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -14,20 +14,26 @@ import {
|
|||||||
Globe,
|
Globe,
|
||||||
Send,
|
Send,
|
||||||
AlertCircle,
|
AlertCircle,
|
||||||
|
Palette,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { APIKey, User } from '../../lib/types';
|
import { APIKey, User } from '../../lib/types';
|
||||||
import { api, triggerHaptic } from '../../lib/api';
|
import { api, triggerHaptic } from '../../lib/api';
|
||||||
|
import { AVAILABLE_THEMES, ThemeId } from '../../lib/theme';
|
||||||
|
|
||||||
interface UserSettingsViewProps {
|
interface UserSettingsViewProps {
|
||||||
currentUser: User;
|
currentUser: User;
|
||||||
|
activeTheme: ThemeId;
|
||||||
|
onThemeChange: (themeId: ThemeId) => void;
|
||||||
onUserUpdated: (u: User) => void;
|
onUserUpdated: (u: User) => void;
|
||||||
onBack: () => void;
|
onBack: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserSettingsSection = 'profile' | 'alarms' | 'webhooks';
|
type UserSettingsSection = 'profile' | 'appearance' | 'alarms' | 'webhooks';
|
||||||
|
|
||||||
export const UserSettingsView: React.FC<UserSettingsViewProps> = ({
|
export const UserSettingsView: React.FC<UserSettingsViewProps> = ({
|
||||||
currentUser,
|
currentUser,
|
||||||
|
activeTheme,
|
||||||
|
onThemeChange,
|
||||||
onUserUpdated,
|
onUserUpdated,
|
||||||
onBack,
|
onBack,
|
||||||
}) => {
|
}) => {
|
||||||
@@ -219,7 +225,8 @@ export const UserSettingsView: React.FC<UserSettingsViewProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const baseUrl = window.location.origin;
|
const baseUrl = window.location.origin;
|
||||||
const activeKeyPlaceholder = newlyCreatedKey || apiKeys[0]?.keyPrefix + '••••••••' || 'DEIN_API_KEY';
|
const activeKeyPlaceholder =
|
||||||
|
newlyCreatedKey || (apiKeys[0]?.keyPrefix ? apiKeys[0].keyPrefix + '••••••••' : 'DEIN_API_KEY');
|
||||||
|
|
||||||
const curlTaskExample = `curl -X POST ${baseUrl}/api/v1/inbox \\
|
const curlTaskExample = `curl -X POST ${baseUrl}/api/v1/inbox \\
|
||||||
-H "X-API-Key: ${activeKeyPlaceholder}" \\
|
-H "X-API-Key: ${activeKeyPlaceholder}" \\
|
||||||
@@ -247,14 +254,14 @@ export const UserSettingsView: React.FC<UserSettingsViewProps> = ({
|
|||||||
<span>Zurück</span>
|
<span>Zurück</span>
|
||||||
</button>
|
</button>
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-base font-bold text-white tracking-tight">Kontoeinstellungen</h1>
|
<h1 className="text-base font-bold text-main tracking-tight">Kontoeinstellungen</h1>
|
||||||
<p className="text-xs text-zinc-400">
|
<p className="text-xs text-muted">
|
||||||
Persönliches Profil, Aufgaben-Alarme, Private Webhooks & Daten-Backup
|
Persönliches Profil, Design-Themes, Aufgaben-Alarme, Private Webhooks & Daten-Backup
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span className="px-2.5 py-1 bg-panel border border-border text-xs text-zinc-300 font-medium">
|
<span className="px-2.5 py-1 bg-panel border border-border text-xs text-muted font-medium">
|
||||||
Rolle: <strong className="text-accent">{roleBadgeLabel}</strong>
|
Rolle: <strong className="text-accent">{roleBadgeLabel}</strong>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -262,19 +269,34 @@ export const UserSettingsView: React.FC<UserSettingsViewProps> = ({
|
|||||||
{/* Main 2-Column Layout */}
|
{/* Main 2-Column Layout */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 items-start">
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 items-start">
|
||||||
{/* Left Navigation Sidebar */}
|
{/* Left Navigation Sidebar */}
|
||||||
<aside className="bg-panel border border-border divide-y divide-border">
|
<aside className="panel-surface divide-y divide-border overflow-hidden">
|
||||||
<button
|
<button
|
||||||
onClick={() => setSection('profile')}
|
onClick={() => setSection('profile')}
|
||||||
className={`w-full px-3.5 py-3 text-left text-xs font-medium flex items-center gap-2.5 transition-colors ${
|
className={`w-full px-3.5 py-3 text-left text-xs font-medium flex items-center gap-2.5 transition-colors ${
|
||||||
section === 'profile'
|
section === 'profile'
|
||||||
? 'bg-panel-elevated text-white border-l-2 border-l-accent'
|
? 'bg-panel-elevated text-main border-l-2 border-l-accent'
|
||||||
: 'text-zinc-400 hover:text-white hover:bg-white/[0.02]'
|
: 'text-muted hover:text-main hover:bg-panel-hover'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<UserIcon className="w-4 h-4 text-accent flex-shrink-0" />
|
<UserIcon className="w-4 h-4 text-accent flex-shrink-0" />
|
||||||
<div>
|
<div>
|
||||||
<p className="font-semibold">Profil & Sicherheit</p>
|
<p className="font-semibold">Profil & Sicherheit</p>
|
||||||
<p className="text-[10px] text-zinc-500">Name, E-Mail & Passwort</p>
|
<p className="text-[10px] text-faint">Name, E-Mail & Passwort</p>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={() => setSection('appearance')}
|
||||||
|
className={`w-full px-3.5 py-3 text-left text-xs font-medium flex items-center gap-2.5 transition-colors ${
|
||||||
|
section === 'appearance'
|
||||||
|
? 'bg-panel-elevated text-main border-l-2 border-l-accent'
|
||||||
|
: 'text-muted hover:text-main hover:bg-panel-hover'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<Palette className="w-4 h-4 text-accent flex-shrink-0" />
|
||||||
|
<div>
|
||||||
|
<p className="font-semibold">Darstellung & Themes</p>
|
||||||
|
<p className="text-[10px] text-faint">Farben, Kontraste & Rundungen</p>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@@ -282,14 +304,14 @@ export const UserSettingsView: React.FC<UserSettingsViewProps> = ({
|
|||||||
onClick={() => setSection('alarms')}
|
onClick={() => setSection('alarms')}
|
||||||
className={`w-full px-3.5 py-3 text-left text-xs font-medium flex items-center gap-2.5 transition-colors ${
|
className={`w-full px-3.5 py-3 text-left text-xs font-medium flex items-center gap-2.5 transition-colors ${
|
||||||
section === 'alarms'
|
section === 'alarms'
|
||||||
? 'bg-panel-elevated text-white border-l-2 border-l-accent'
|
? 'bg-panel-elevated text-main border-l-2 border-l-accent'
|
||||||
: 'text-zinc-400 hover:text-white hover:bg-white/[0.02]'
|
: 'text-muted hover:text-main hover:bg-panel-hover'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<Bell className="w-4 h-4 text-accent flex-shrink-0" />
|
<Bell className="w-4 h-4 text-accent flex-shrink-0" />
|
||||||
<div>
|
<div>
|
||||||
<p className="font-semibold">Alarme</p>
|
<p className="font-semibold">Alarme</p>
|
||||||
<p className="text-[10px] text-zinc-500">E-Mail, Discord & Webhooks</p>
|
<p className="text-[10px] text-faint">E-Mail, Discord & Webhooks</p>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@@ -297,44 +319,42 @@ export const UserSettingsView: React.FC<UserSettingsViewProps> = ({
|
|||||||
onClick={() => setSection('webhooks')}
|
onClick={() => setSection('webhooks')}
|
||||||
className={`w-full px-3.5 py-3 text-left text-xs font-medium flex items-center gap-2.5 transition-colors ${
|
className={`w-full px-3.5 py-3 text-left text-xs font-medium flex items-center gap-2.5 transition-colors ${
|
||||||
section === 'webhooks'
|
section === 'webhooks'
|
||||||
? 'bg-panel-elevated text-white border-l-2 border-l-accent'
|
? 'bg-panel-elevated text-main border-l-2 border-l-accent'
|
||||||
: 'text-zinc-400 hover:text-white hover:bg-white/[0.02]'
|
: 'text-muted hover:text-main hover:bg-panel-hover'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<Key className="w-4 h-4 text-accent flex-shrink-0" />
|
<Key className="w-4 h-4 text-accent flex-shrink-0" />
|
||||||
<div>
|
<div>
|
||||||
<p className="font-semibold">Private Webhooks & Export</p>
|
<p className="font-semibold">Private Webhooks & Export</p>
|
||||||
<p className="text-[10px] text-zinc-500">API-Keys, Curl & JSON-Backup</p>
|
<p className="text-[10px] text-faint">API-Keys, Curl & JSON-Backup</p>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
{/* Right Content Area */}
|
{/* Right Content Area */}
|
||||||
<div className="md:col-span-3 bg-panel border border-border p-5 text-xs">
|
<div className="md:col-span-3 panel-surface p-5 text-xs">
|
||||||
{/* ================================================================= */}
|
{/* ================================================================= */}
|
||||||
{/* SECTION 1: PROFIL & SICHERHEIT */}
|
{/* SECTION 1: PROFIL & SICHERHEIT */}
|
||||||
{/* ================================================================= */}
|
{/* ================================================================= */}
|
||||||
{section === 'profile' && (
|
{section === 'profile' && (
|
||||||
<div className="space-y-5 max-w-2xl">
|
<div className="space-y-5 max-w-2xl">
|
||||||
<div className="border-b border-border pb-3">
|
<div className="border-b border-border pb-3">
|
||||||
<h2 className="text-sm font-bold text-white">Profil & Anmeldedaten</h2>
|
<h2 className="text-sm font-bold text-main">Profil & Anmeldedaten</h2>
|
||||||
<p className="text-zinc-400 mt-0.5">
|
<p className="text-muted mt-0.5">
|
||||||
Verwalte deinen Anzeigenamen, deine E-Mail-Adresse und dein persönliches Passwort.
|
Verwalte deinen Anzeigenamen, deine E-Mail-Adresse und dein persönliches Passwort.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{profileMsg && (
|
{profileMsg && (
|
||||||
<div
|
<div
|
||||||
className={`p-3 border flex items-center gap-2 ${
|
className={`flex items-center gap-2 ${
|
||||||
profileMsg.type === 'ok'
|
profileMsg.type === 'ok' ? 'alert-ok' : 'alert-err'
|
||||||
? 'bg-blue-950/30 border-accent text-blue-200'
|
|
||||||
: 'bg-red-950/30 border-red-500 text-red-200'
|
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{profileMsg.type === 'ok' ? (
|
{profileMsg.type === 'ok' ? (
|
||||||
<Check className="w-4 h-4 text-accent flex-shrink-0" />
|
<Check className="w-4 h-4 text-status-ok flex-shrink-0" />
|
||||||
) : (
|
) : (
|
||||||
<AlertCircle className="w-4 h-4 text-red-400 flex-shrink-0" />
|
<AlertCircle className="w-4 h-4 text-status-err flex-shrink-0" />
|
||||||
)}
|
)}
|
||||||
<span>{profileMsg.text}</span>
|
<span>{profileMsg.text}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -343,30 +363,32 @@ export const UserSettingsView: React.FC<UserSettingsViewProps> = ({
|
|||||||
<form onSubmit={handleSaveProfile} className="space-y-4">
|
<form onSubmit={handleSaveProfile} className="space-y-4">
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-zinc-300 font-medium mb-1">Anzeigename</label>
|
<label className="block text-muted font-medium mb-1">Anzeigename</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={displayName}
|
value={displayName}
|
||||||
onChange={(e) => setDisplayName(e.target.value)}
|
onChange={(e) => setDisplayName(e.target.value)}
|
||||||
required
|
required
|
||||||
className="w-full h-9 px-3 input-field text-white"
|
className="w-full h-9 px-3 input-field"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-zinc-300 font-medium mb-1">E-Mail-Adresse (Login)</label>
|
<label className="block text-muted font-medium mb-1">
|
||||||
|
E-Mail-Adresse (Login)
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
type="email"
|
type="email"
|
||||||
value={email}
|
value={email}
|
||||||
onChange={(e) => setEmail(e.target.value)}
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
required
|
required
|
||||||
className="w-full h-9 px-3 input-field text-white"
|
className="w-full h-9 px-3 input-field"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-zinc-300 font-medium mb-1">
|
<label className="block text-muted font-medium mb-1">
|
||||||
Neues Passwort (optional — leer lassen für keine Änderung)
|
Neues Passwort (optional — leer lassen für keine Änderung)
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@@ -374,7 +396,7 @@ export const UserSettingsView: React.FC<UserSettingsViewProps> = ({
|
|||||||
value={newPassword}
|
value={newPassword}
|
||||||
onChange={(e) => setNewPassword(e.target.value)}
|
onChange={(e) => setNewPassword(e.target.value)}
|
||||||
placeholder="Mindestens 4 Zeichen"
|
placeholder="Mindestens 4 Zeichen"
|
||||||
className="w-full h-9 px-3 input-field text-white"
|
className="w-full h-9 px-3 input-field"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -392,30 +414,129 @@ export const UserSettingsView: React.FC<UserSettingsViewProps> = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* ================================================================= */}
|
{/* ================================================================= */}
|
||||||
{/* SECTION 2: ALARME (MULTI-CHANNEL: EMAIL, DISCORD, CUSTOM WEBHOOK) */}
|
{/* SECTION 2: DARSTELLUNG & THEMES (SINGLE-FILE CSS THEMING) */}
|
||||||
|
{/* ================================================================= */}
|
||||||
|
{section === 'appearance' && (
|
||||||
|
<div className="space-y-5">
|
||||||
|
<div className="border-b border-border pb-3">
|
||||||
|
<h2 className="text-sm font-bold text-main">
|
||||||
|
Darstellung & Single-File CSS Themes
|
||||||
|
</h2>
|
||||||
|
<p className="text-muted mt-0.5">
|
||||||
|
Jedes Theme ist eine einzige, eigenständige CSS-Datei, in der alle Farben,
|
||||||
|
Oberflächen, Kontraste und Ecken-Rundungen in 6 festen Gruppen gebündelt sind.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||||
|
{AVAILABLE_THEMES.map((t) => {
|
||||||
|
const isSelected = activeTheme === t.id;
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={t.id}
|
||||||
|
type="button"
|
||||||
|
onClick={() => onThemeChange(t.id)}
|
||||||
|
className={`p-4 text-left border transition-all flex flex-col justify-between gap-3 ${
|
||||||
|
isSelected
|
||||||
|
? 'bg-canvas border-accent shadow-sm'
|
||||||
|
: 'bg-canvas border-border hover:border-strong'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="flex items-start justify-between gap-2">
|
||||||
|
<div>
|
||||||
|
<p className="font-bold text-sm text-main flex items-center gap-2">
|
||||||
|
<span>{t.name}</span>
|
||||||
|
{isSelected && <span className="badge-accent">Aktiv</span>}
|
||||||
|
</p>
|
||||||
|
<p className="text-xs text-muted mt-0.5">{t.subtitle}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Mini Color & Geometry Swatch */}
|
||||||
|
<div className="flex items-center justify-between pt-2 border-t border-border">
|
||||||
|
<div className="flex items-center gap-1.5">
|
||||||
|
<span
|
||||||
|
className="w-5 h-5 border border-strong inline-block"
|
||||||
|
style={{
|
||||||
|
backgroundColor: t.previewBg,
|
||||||
|
borderRadius: t.isRounded ? '6px' : '0px',
|
||||||
|
}}
|
||||||
|
title="Canvas-Hintergrund"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
className="w-5 h-5 border border-strong inline-block"
|
||||||
|
style={{
|
||||||
|
backgroundColor: t.previewPanel,
|
||||||
|
borderRadius: t.isRounded ? '6px' : '0px',
|
||||||
|
}}
|
||||||
|
title="Panel-Oberfläche"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
className="w-5 h-5 border border-strong inline-block"
|
||||||
|
style={{
|
||||||
|
backgroundColor: t.previewAccent,
|
||||||
|
borderRadius: t.isRounded ? '6px' : '0px',
|
||||||
|
}}
|
||||||
|
title="Akzentfarbe"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
className="w-5 h-5 border border-strong inline-block"
|
||||||
|
style={{
|
||||||
|
backgroundColor: t.previewText,
|
||||||
|
borderRadius: t.isRounded ? '6px' : '0px',
|
||||||
|
}}
|
||||||
|
title="Textfarbe"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<code className="text-[10px] font-mono text-faint">{t.file}</code>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="p-3.5 bg-canvas border border-border space-y-1.5">
|
||||||
|
<p className="font-semibold text-main">Eigenes Theme anpassen oder hinzufügen:</p>
|
||||||
|
<p className="text-muted text-[11px] leading-relaxed">
|
||||||
|
Alle Theme-Dateien liegen unter{' '}
|
||||||
|
<code className="font-mono text-accent">frontend/src/themes/</code> (inklusive der
|
||||||
|
Kopiervorlage{' '}
|
||||||
|
<code className="font-mono text-accent">theme-template.css</code>). Dort sind alle
|
||||||
|
Eigenschaften in 6 Blöcken gruppiert: <strong>1. Akzentfarbe</strong>,{' '}
|
||||||
|
<strong>2. Oberflächen</strong>, <strong>3. Textfarben</strong>,{' '}
|
||||||
|
<strong>4. Rahmen & Schatten</strong>, <strong>5. Statusfarben</strong> und{' '}
|
||||||
|
<strong>6. Rundungen</strong>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* ================================================================= */}
|
||||||
|
{/* SECTION 3: ALARME (MULTI-CHANNEL: EMAIL, DISCORD, CUSTOM WEBHOOK) */}
|
||||||
{/* ================================================================= */}
|
{/* ================================================================= */}
|
||||||
{section === 'alarms' && (
|
{section === 'alarms' && (
|
||||||
<div className="space-y-5">
|
<div className="space-y-5">
|
||||||
<div className="border-b border-border pb-3">
|
<div className="border-b border-border pb-3">
|
||||||
<h2 className="text-sm font-bold text-white">Aufgaben-Alarme & Benachrichtigungsziele</h2>
|
<h2 className="text-sm font-bold text-main">
|
||||||
<p className="text-zinc-400 mt-0.5">
|
Aufgaben-Alarme & Benachrichtigungsziele
|
||||||
Wähle aus, über welche Kanäle du benachrichtigt werden möchtest, sobald eine Aufgabe
|
</h2>
|
||||||
mit Fälligkeitsdatum ansteht. Mehrere Ziele können gleichzeitig aktiv sein.
|
<p className="text-muted mt-0.5">
|
||||||
|
Wähle aus, über welche Kanäle du benachrichtigt werden möchtest, sobald eine
|
||||||
|
Aufgabe mit Fälligkeitsdatum ansteht. Mehrere Ziele können gleichzeitig aktiv sein.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{alarmMsg && (
|
{alarmMsg && (
|
||||||
<div
|
<div
|
||||||
className={`p-3 border flex items-center gap-2 ${
|
className={`flex items-center gap-2 ${
|
||||||
alarmMsg.type === 'ok'
|
alarmMsg.type === 'ok' ? 'alert-ok' : 'alert-err'
|
||||||
? 'bg-blue-950/30 border-accent text-blue-200'
|
|
||||||
: 'bg-red-950/30 border-red-500 text-red-200'
|
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{alarmMsg.type === 'ok' ? (
|
{alarmMsg.type === 'ok' ? (
|
||||||
<Check className="w-4 h-4 text-accent flex-shrink-0" />
|
<Check className="w-4 h-4 text-status-ok flex-shrink-0" />
|
||||||
) : (
|
) : (
|
||||||
<AlertCircle className="w-4 h-4 text-red-400 flex-shrink-0" />
|
<AlertCircle className="w-4 h-4 text-status-err flex-shrink-0" />
|
||||||
)}
|
)}
|
||||||
<span>{alarmMsg.text}</span>
|
<span>{alarmMsg.text}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -425,17 +546,17 @@ export const UserSettingsView: React.FC<UserSettingsViewProps> = ({
|
|||||||
{/* Lead Time Selector */}
|
{/* Lead Time Selector */}
|
||||||
<div className="p-3.5 bg-canvas border border-border flex flex-col sm:flex-row sm:items-center justify-between gap-3">
|
<div className="p-3.5 bg-canvas border border-border flex flex-col sm:flex-row sm:items-center justify-between gap-3">
|
||||||
<div>
|
<div>
|
||||||
<label className="font-semibold text-white block">
|
<label className="font-semibold text-main block">
|
||||||
Vorwarnzeit vor Fälligkeit
|
Vorwarnzeit vor Fälligkeit
|
||||||
</label>
|
</label>
|
||||||
<p className="text-[11px] text-zinc-400">
|
<p className="text-[11px] text-muted">
|
||||||
Wann soll MindSift den Alarm vor dem Fälligkeitszeitpunkt auslösen?
|
Wann soll MindSift den Alarm vor dem Fälligkeitszeitpunkt auslösen?
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<select
|
<select
|
||||||
value={notifyBeforeMinutes}
|
value={notifyBeforeMinutes}
|
||||||
onChange={(e) => setNotifyBeforeMinutes(Number(e.target.value))}
|
onChange={(e) => setNotifyBeforeMinutes(Number(e.target.value))}
|
||||||
className="h-9 px-3 input-field text-white bg-panel min-w-[200px]"
|
className="h-9 px-3 input-field min-w-[200px]"
|
||||||
>
|
>
|
||||||
<option value={0}>Pünktlich bei Fälligkeit (0 Min.)</option>
|
<option value={0}>Pünktlich bei Fälligkeit (0 Min.)</option>
|
||||||
<option value={15}>15 Minuten vorher</option>
|
<option value={15}>15 Minuten vorher</option>
|
||||||
@@ -448,10 +569,8 @@ export const UserSettingsView: React.FC<UserSettingsViewProps> = ({
|
|||||||
|
|
||||||
{/* Channel 1: E-Mail */}
|
{/* Channel 1: E-Mail */}
|
||||||
<div
|
<div
|
||||||
className={`p-4 border transition-colors space-y-3 ${
|
className={`p-4 border transition-colors space-y-3 bg-canvas ${
|
||||||
alarmChannels.includes('email')
|
alarmChannels.includes('email') ? 'border-accent' : 'border-border'
|
||||||
? 'bg-canvas border-accent'
|
|
||||||
: 'bg-canvas/60 border-border'
|
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between gap-2">
|
<div className="flex items-center justify-between gap-2">
|
||||||
@@ -464,8 +583,8 @@ export const UserSettingsView: React.FC<UserSettingsViewProps> = ({
|
|||||||
/>
|
/>
|
||||||
<Mail className="w-4 h-4 text-accent" />
|
<Mail className="w-4 h-4 text-accent" />
|
||||||
<div>
|
<div>
|
||||||
<span className="font-bold text-white block">Ziel: E-Mail</span>
|
<span className="font-bold text-main block">Ziel: E-Mail</span>
|
||||||
<span className="text-[11px] text-zinc-400">
|
<span className="text-[11px] text-muted">
|
||||||
Sendet eine E-Mail über den konfigurierten SMTP-Server der Instanz.
|
Sendet eine E-Mail über den konfigurierten SMTP-Server der Instanz.
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -483,7 +602,7 @@ export const UserSettingsView: React.FC<UserSettingsViewProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="pl-6">
|
<div className="pl-6">
|
||||||
<label className="block text-zinc-400 mb-1">
|
<label className="block text-muted mb-1">
|
||||||
Abweichende Empfänger-E-Mail (optional, Standard: {currentUser.email})
|
Abweichende Empfänger-E-Mail (optional, Standard: {currentUser.email})
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@@ -491,17 +610,15 @@ export const UserSettingsView: React.FC<UserSettingsViewProps> = ({
|
|||||||
value={alarmEmail}
|
value={alarmEmail}
|
||||||
onChange={(e) => setAlarmEmail(e.target.value)}
|
onChange={(e) => setAlarmEmail(e.target.value)}
|
||||||
placeholder={currentUser.email}
|
placeholder={currentUser.email}
|
||||||
className="w-full h-8 px-2.5 input-field text-white"
|
className="w-full h-8 px-2.5 input-field"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Channel 2: Discord Webhook */}
|
{/* Channel 2: Discord Webhook */}
|
||||||
<div
|
<div
|
||||||
className={`p-4 border transition-colors space-y-3 ${
|
className={`p-4 border transition-colors space-y-3 bg-canvas ${
|
||||||
alarmChannels.includes('discord')
|
alarmChannels.includes('discord') ? 'border-accent' : 'border-border'
|
||||||
? 'bg-canvas border-accent'
|
|
||||||
: 'bg-canvas/60 border-border'
|
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between gap-2">
|
<div className="flex items-center justify-between gap-2">
|
||||||
@@ -514,9 +631,10 @@ export const UserSettingsView: React.FC<UserSettingsViewProps> = ({
|
|||||||
/>
|
/>
|
||||||
<MessageSquare className="w-4 h-4 text-accent" />
|
<MessageSquare className="w-4 h-4 text-accent" />
|
||||||
<div>
|
<div>
|
||||||
<span className="font-bold text-white block">Ziel: Discord Webhook</span>
|
<span className="font-bold text-main block">Ziel: Discord Webhook</span>
|
||||||
<span className="text-[11px] text-zinc-400">
|
<span className="text-[11px] text-muted">
|
||||||
Sendet eine formatierte Embed-Benachrichtigung direkt in deinen Discord-Kanal.
|
Sendet eine formatierte Embed-Benachrichtigung direkt in deinen
|
||||||
|
Discord-Kanal.
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
@@ -533,23 +651,21 @@ export const UserSettingsView: React.FC<UserSettingsViewProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="pl-6">
|
<div className="pl-6">
|
||||||
<label className="block text-zinc-400 mb-1">Discord Webhook URL</label>
|
<label className="block text-muted mb-1">Discord Webhook URL</label>
|
||||||
<input
|
<input
|
||||||
type="url"
|
type="url"
|
||||||
value={discordWebhookUrl}
|
value={discordWebhookUrl}
|
||||||
onChange={(e) => setDiscordWebhookUrl(e.target.value)}
|
onChange={(e) => setDiscordWebhookUrl(e.target.value)}
|
||||||
placeholder="https://discord.com/api/webhooks/123456789/token..."
|
placeholder="https://discord.com/api/webhooks/123456789/token..."
|
||||||
className="w-full h-8 px-2.5 input-field text-white font-mono"
|
className="w-full h-8 px-2.5 input-field font-mono"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Channel 3: Custom HTTP Webhook */}
|
{/* Channel 3: Custom HTTP Webhook */}
|
||||||
<div
|
<div
|
||||||
className={`p-4 border transition-colors space-y-3 ${
|
className={`p-4 border transition-colors space-y-3 bg-canvas ${
|
||||||
alarmChannels.includes('webhook')
|
alarmChannels.includes('webhook') ? 'border-accent' : 'border-border'
|
||||||
? 'bg-canvas border-accent'
|
|
||||||
: 'bg-canvas/60 border-border'
|
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between gap-2">
|
<div className="flex items-center justify-between gap-2">
|
||||||
@@ -562,11 +678,12 @@ export const UserSettingsView: React.FC<UserSettingsViewProps> = ({
|
|||||||
/>
|
/>
|
||||||
<Globe className="w-4 h-4 text-accent" />
|
<Globe className="w-4 h-4 text-accent" />
|
||||||
<div>
|
<div>
|
||||||
<span className="font-bold text-white block">
|
<span className="font-bold text-main block">
|
||||||
Ziel: Eigener Webhook (HTTP POST JSON)
|
Ziel: Eigener Webhook (HTTP POST JSON)
|
||||||
</span>
|
</span>
|
||||||
<span className="text-[11px] text-zinc-400">
|
<span className="text-[11px] text-muted">
|
||||||
Für eigene Empfänger-Skripte, Ntfy, Gotify, Slack, Mattermost oder Automatisierungen.
|
Für eigene Empfänger-Skripte, Ntfy, Gotify, Slack, Mattermost oder
|
||||||
|
Automatisierungen.
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
@@ -583,13 +700,15 @@ export const UserSettingsView: React.FC<UserSettingsViewProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="pl-6">
|
<div className="pl-6">
|
||||||
<label className="block text-zinc-400 mb-1">Ziel-URL (empfängt JSON-Payload per POST)</label>
|
<label className="block text-muted mb-1">
|
||||||
|
Ziel-URL (empfängt JSON-Payload per POST)
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
type="url"
|
type="url"
|
||||||
value={customWebhookUrl}
|
value={customWebhookUrl}
|
||||||
onChange={(e) => setCustomWebhookUrl(e.target.value)}
|
onChange={(e) => setCustomWebhookUrl(e.target.value)}
|
||||||
placeholder="https://dein-server.local/webhook/mindsift-alarm"
|
placeholder="https://dein-server.local/webhook/mindsift-alarm"
|
||||||
className="w-full h-8 px-2.5 input-field text-white font-mono"
|
className="w-full h-8 px-2.5 input-field font-mono"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -608,15 +727,17 @@ export const UserSettingsView: React.FC<UserSettingsViewProps> = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* ================================================================= */}
|
{/* ================================================================= */}
|
||||||
{/* SECTION 3: PRIVATE WEBHOOKS & EXPORT */}
|
{/* SECTION 4: PRIVATE WEBHOOKS & EXPORT */}
|
||||||
{/* ================================================================= */}
|
{/* ================================================================= */}
|
||||||
{section === 'webhooks' && (
|
{section === 'webhooks' && (
|
||||||
<div className="space-y-5">
|
<div className="space-y-5">
|
||||||
<div className="border-b border-border pb-3">
|
<div className="border-b border-border pb-3">
|
||||||
<h2 className="text-sm font-bold text-white">Private Webhooks (Eingehend) & Daten-Export</h2>
|
<h2 className="text-sm font-bold text-main">
|
||||||
<p className="text-zinc-400 mt-0.5">
|
Private Webhooks (Eingehend) & Daten-Export
|
||||||
Erstelle persönliche API-Schlüssel, um aus externen Skripten, Server-Monitoring oder
|
</h2>
|
||||||
Discord-Bots automatisch Aufgaben und Gedanken in dein MindSift einzuspeisen.
|
<p className="text-muted mt-0.5">
|
||||||
|
Erstelle persönliche API-Schlüssel, um aus externen Skripten, Server-Monitoring
|
||||||
|
oder Discord-Bots automatisch Aufgaben und Gedanken in dein MindSift einzuspeisen.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -627,7 +748,7 @@ export const UserSettingsView: React.FC<UserSettingsViewProps> = ({
|
|||||||
value={newKeyName}
|
value={newKeyName}
|
||||||
onChange={(e) => setNewKeyName(e.target.value)}
|
onChange={(e) => setNewKeyName(e.target.value)}
|
||||||
placeholder="Bezeichnung des Schlüssels (z.B. Proxmox Monitoring, Discord-Forwarder)…"
|
placeholder="Bezeichnung des Schlüssels (z.B. Proxmox Monitoring, Discord-Forwarder)…"
|
||||||
className="flex-1 h-9 px-3 input-field text-white"
|
className="flex-1 h-9 px-3 input-field"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
@@ -639,12 +760,13 @@ export const UserSettingsView: React.FC<UserSettingsViewProps> = ({
|
|||||||
</form>
|
</form>
|
||||||
|
|
||||||
{newlyCreatedKey && (
|
{newlyCreatedKey && (
|
||||||
<div className="p-3 bg-accent/15 border border-accent flex flex-col sm:flex-row sm:items-center justify-between gap-2">
|
<div className="p-3 bg-accent-dim border border-accent flex flex-col sm:flex-row sm:items-center justify-between gap-2">
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
<p className="text-[11px] text-accent font-bold">
|
<p className="text-[11px] text-accent font-bold">
|
||||||
Neuer API-Schlüssel erstellt (bitte jetzt kopieren, wird nur einmal im Klartext angezeigt):
|
Neuer API-Schlüssel erstellt (bitte jetzt kopieren, wird nur einmal im
|
||||||
|
Klartext angezeigt):
|
||||||
</p>
|
</p>
|
||||||
<code className="text-xs font-mono text-white break-all">{newlyCreatedKey}</code>
|
<code className="text-xs font-mono text-main break-all">{newlyCreatedKey}</code>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={() => handleCopy(newlyCreatedKey, 'key')}
|
onClick={() => handleCopy(newlyCreatedKey, 'key')}
|
||||||
@@ -658,16 +780,17 @@ export const UserSettingsView: React.FC<UserSettingsViewProps> = ({
|
|||||||
{/* Existing Keys List */}
|
{/* Existing Keys List */}
|
||||||
<div className="border border-border divide-y divide-border bg-canvas">
|
<div className="border border-border divide-y divide-border bg-canvas">
|
||||||
{apiKeys.length === 0 ? (
|
{apiKeys.length === 0 ? (
|
||||||
<div className="p-4 text-center text-zinc-500">
|
<div className="p-4 text-center text-faint">
|
||||||
Noch keine persönlichen API-Schlüssel vorhanden.
|
Noch keine persönlichen API-Schlüssel vorhanden.
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
apiKeys.map((k) => (
|
apiKeys.map((k) => (
|
||||||
<div key={k.id} className="p-3 flex items-center justify-between gap-3">
|
<div key={k.id} className="p-3 flex items-center justify-between gap-3">
|
||||||
<div>
|
<div>
|
||||||
<p className="font-semibold text-white">{k.name}</p>
|
<p className="font-semibold text-main">{k.name}</p>
|
||||||
<p className="text-[11px] text-zinc-500 font-mono">
|
<p className="text-[11px] text-faint font-mono">
|
||||||
Präfix: {k.keyPrefix}•••••••• · Erstellt: {new Date(k.createdAt).toLocaleDateString()}
|
Präfix: {k.keyPrefix}•••••••• · Erstellt:{' '}
|
||||||
|
{new Date(k.createdAt).toLocaleDateString()}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
@@ -685,7 +808,7 @@ export const UserSettingsView: React.FC<UserSettingsViewProps> = ({
|
|||||||
{/* Curl Example */}
|
{/* Curl Example */}
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<span className="font-semibold text-zinc-300 flex items-center gap-1.5">
|
<span className="font-semibold text-muted flex items-center gap-1.5">
|
||||||
<Terminal className="w-3.5 h-3.5 text-accent" />
|
<Terminal className="w-3.5 h-3.5 text-accent" />
|
||||||
<span>Curl-Beispiel: Direkt als Aufgabe einspeisen</span>
|
<span>Curl-Beispiel: Direkt als Aufgabe einspeisen</span>
|
||||||
</span>
|
</span>
|
||||||
@@ -696,7 +819,7 @@ export const UserSettingsView: React.FC<UserSettingsViewProps> = ({
|
|||||||
{copiedLabel === 'curl' ? 'Kopiert!' : 'Befehl kopieren'}
|
{copiedLabel === 'curl' ? 'Kopiert!' : 'Befehl kopieren'}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<pre className="p-3 bg-canvas border border-border text-[11px] font-mono text-zinc-300 overflow-x-auto whitespace-pre">
|
<pre className="p-3 code-block text-[11px] font-mono overflow-x-auto whitespace-pre">
|
||||||
{curlTaskExample}
|
{curlTaskExample}
|
||||||
</pre>
|
</pre>
|
||||||
</div>
|
</div>
|
||||||
@@ -704,9 +827,10 @@ export const UserSettingsView: React.FC<UserSettingsViewProps> = ({
|
|||||||
{/* Full Data Backup */}
|
{/* Full Data Backup */}
|
||||||
<div className="pt-4 border-t border-border flex flex-col sm:flex-row sm:items-center justify-between gap-3">
|
<div className="pt-4 border-t border-border flex flex-col sm:flex-row sm:items-center justify-between gap-3">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="font-bold text-white">Vollständiger Daten-Export (JSON Backup)</h3>
|
<h3 className="font-bold text-main">Vollständiger Daten-Export (JSON Backup)</h3>
|
||||||
<p className="text-zinc-400 text-[11px]">
|
<p className="text-muted text-[11px]">
|
||||||
Lädt alle deine Gedanken, Aufgaben, Markdown-Dokumente und Listen als portable JSON-Datei herunter.
|
Lädt alle deine Gedanken, Aufgaben, Markdown-Dokumente und Listen als portable
|
||||||
|
JSON-Datei herunter.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -6,16 +6,12 @@ import {
|
|||||||
Trash2,
|
Trash2,
|
||||||
RotateCcw,
|
RotateCcw,
|
||||||
FileText,
|
FileText,
|
||||||
Tag,
|
|
||||||
LayoutList,
|
LayoutList,
|
||||||
Columns,
|
Columns,
|
||||||
ArrowRight,
|
ArrowRight,
|
||||||
ArrowLeft,
|
ArrowLeft,
|
||||||
ChevronRight,
|
|
||||||
ChevronDown,
|
|
||||||
Edit3,
|
Edit3,
|
||||||
X,
|
X,
|
||||||
Clock,
|
|
||||||
Layers,
|
Layers,
|
||||||
Share2,
|
Share2,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
@@ -85,7 +81,9 @@ export const TasksView: React.FC<TasksViewProps> = ({
|
|||||||
const nextState = !task.isCompleted;
|
const nextState = !task.isCompleted;
|
||||||
const nextKanban = nextState ? 'done' : 'todo';
|
const nextKanban = nextState ? 'done' : 'todo';
|
||||||
setTasks((prev) =>
|
setTasks((prev) =>
|
||||||
prev.map((t) => (t.id === task.id ? { ...t, isCompleted: nextState, kanbanStatus: nextKanban } : t))
|
prev.map((t) =>
|
||||||
|
t.id === task.id ? { ...t, isCompleted: nextState, kanbanStatus: nextKanban } : t
|
||||||
|
)
|
||||||
);
|
);
|
||||||
await api.updateThought(task.id, { isCompleted: nextState, kanbanStatus: nextKanban });
|
await api.updateThought(task.id, { isCompleted: nextState, kanbanStatus: nextKanban });
|
||||||
onRefreshStats();
|
onRefreshStats();
|
||||||
@@ -182,7 +180,12 @@ export const TasksView: React.FC<TasksViewProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteTaskList = async (listId: string) => {
|
const handleDeleteTaskList = async (listId: string) => {
|
||||||
if (!window.confirm('Möchten Sie diese Aufgabenliste wirklich löschen? Die enthaltenen Aufgaben bleiben erhalten.')) return;
|
if (
|
||||||
|
!window.confirm(
|
||||||
|
'Möchten Sie diese Aufgabenliste wirklich löschen? Die enthaltenen Aufgaben bleiben erhalten.'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return;
|
||||||
try {
|
try {
|
||||||
await api.deleteTaskList(listId);
|
await api.deleteTaskList(listId);
|
||||||
setTaskLists((prev) => prev.filter((l) => l.id !== listId));
|
setTaskLists((prev) => prev.filter((l) => l.id !== listId));
|
||||||
@@ -194,12 +197,18 @@ export const TasksView: React.FC<TasksViewProps> = ({
|
|||||||
|
|
||||||
const safeTasks = Array.isArray(tasks) ? tasks : [];
|
const safeTasks = Array.isArray(tasks) ? tasks : [];
|
||||||
const safeTaskLists = Array.isArray(taskLists) ? taskLists : [];
|
const safeTaskLists = Array.isArray(taskLists) ? taskLists : [];
|
||||||
|
const safeSpaces = Array.isArray(spaces) ? spaces : [];
|
||||||
|
const safeDiscarded = Array.isArray(discarded) ? discarded : [];
|
||||||
|
|
||||||
const openTasks = safeTasks.filter((t) => !t.isCompleted);
|
const openTasks = safeTasks.filter((t) => !t.isCompleted);
|
||||||
const doneTasks = safeTasks.filter((t) => t.isCompleted);
|
const doneTasks = safeTasks.filter((t) => t.isCompleted);
|
||||||
|
|
||||||
const todoKanban = safeTasks.filter((t) => (!t.kanbanStatus || t.kanbanStatus === 'todo') && !t.isCompleted);
|
const todoKanban = safeTasks.filter(
|
||||||
const inProgressKanban = safeTasks.filter((t) => t.kanbanStatus === 'in_progress' && !t.isCompleted);
|
(t) => (!t.kanbanStatus || t.kanbanStatus === 'todo') && !t.isCompleted
|
||||||
|
);
|
||||||
|
const inProgressKanban = safeTasks.filter(
|
||||||
|
(t) => t.kanbanStatus === 'in_progress' && !t.isCompleted
|
||||||
|
);
|
||||||
const doneKanban = safeTasks.filter((t) => t.kanbanStatus === 'done' || t.isCompleted);
|
const doneKanban = safeTasks.filter((t) => t.kanbanStatus === 'done' || t.isCompleted);
|
||||||
|
|
||||||
const currentSelectedList = safeTaskLists.find((l) => l.id === selectedListId);
|
const currentSelectedList = safeTaskLists.find((l) => l.id === selectedListId);
|
||||||
@@ -212,10 +221,10 @@ export const TasksView: React.FC<TasksViewProps> = ({
|
|||||||
<div className="flex flex-wrap items-center gap-1">
|
<div className="flex flex-wrap items-center gap-1">
|
||||||
<button
|
<button
|
||||||
onClick={() => setSelectedListId('all')}
|
onClick={() => setSelectedListId('all')}
|
||||||
className={`h-8 px-3 text-xs font-medium border transition-colors ${
|
className={`h-8 px-3 text-xs font-medium border rounded-btn transition-colors ${
|
||||||
selectedListId === 'all'
|
selectedListId === 'all'
|
||||||
? 'bg-panel border-accent text-white'
|
? 'bg-panel border-accent text-main'
|
||||||
: 'bg-canvas border-border text-zinc-400 hover:text-white'
|
: 'bg-canvas border-border text-muted hover:text-main'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
Alle Aufgaben
|
Alle Aufgaben
|
||||||
@@ -225,18 +234,16 @@ export const TasksView: React.FC<TasksViewProps> = ({
|
|||||||
<div key={tl.id} className="relative group flex items-center">
|
<div key={tl.id} className="relative group flex items-center">
|
||||||
<button
|
<button
|
||||||
onClick={() => setSelectedListId(tl.id)}
|
onClick={() => setSelectedListId(tl.id)}
|
||||||
className={`h-8 px-3 text-xs font-medium border flex items-center gap-1.5 transition-colors ${
|
className={`h-8 px-3 text-xs font-medium border rounded-btn flex items-center gap-1.5 transition-colors ${
|
||||||
selectedListId === tl.id
|
selectedListId === tl.id
|
||||||
? 'bg-panel border-accent text-white'
|
? 'bg-panel border-accent text-main'
|
||||||
: 'bg-canvas border-border text-zinc-400 hover:text-white'
|
: 'bg-canvas border-border text-muted hover:text-main'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<span>{tl.icon}</span>
|
<span>{tl.icon}</span>
|
||||||
<span>{tl.title}</span>
|
<span>{tl.title}</span>
|
||||||
{tl.tasksCount !== undefined && tl.tasksCount > 0 && (
|
{tl.tasksCount !== undefined && tl.tasksCount > 0 && (
|
||||||
<span className="ml-1 px-1.5 py-0.2 bg-zinc-800 text-[10px] text-zinc-300">
|
<span className="ml-1 badge-neutral">{tl.tasksCount}</span>
|
||||||
{tl.tasksCount}
|
|
||||||
</span>
|
|
||||||
)}
|
)}
|
||||||
{tl.spaceId && (
|
{tl.spaceId && (
|
||||||
<span title="Geteilt mit Team/Space">
|
<span title="Geteilt mit Team/Space">
|
||||||
@@ -248,7 +255,7 @@ export const TasksView: React.FC<TasksViewProps> = ({
|
|||||||
<button
|
<button
|
||||||
onClick={() => handleDeleteTaskList(tl.id)}
|
onClick={() => handleDeleteTaskList(tl.id)}
|
||||||
title="Liste löschen"
|
title="Liste löschen"
|
||||||
className="h-8 px-1.5 bg-panel border-y border-r border-accent text-zinc-500 hover:text-red-400"
|
className="h-8 px-1.5 bg-panel border-y border-r border-accent text-faint hover:text-status-err"
|
||||||
>
|
>
|
||||||
<X className="w-3 h-3" />
|
<X className="w-3 h-3" />
|
||||||
</button>
|
</button>
|
||||||
@@ -258,7 +265,7 @@ export const TasksView: React.FC<TasksViewProps> = ({
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowNewListModal(true)}
|
onClick={() => setShowNewListModal(true)}
|
||||||
className="h-8 px-2.5 bg-canvas hover:bg-panel border border-border border-dashed text-xs text-zinc-400 hover:text-white flex items-center gap-1 transition-colors"
|
className="h-8 px-2.5 bg-canvas hover:bg-panel border border-border border-dashed rounded-btn text-xs text-muted hover:text-main flex items-center gap-1 transition-colors"
|
||||||
title="Neue Aufgabenliste anlegen"
|
title="Neue Aufgabenliste anlegen"
|
||||||
>
|
>
|
||||||
<Plus className="w-3.5 h-3.5" />
|
<Plus className="w-3.5 h-3.5" />
|
||||||
@@ -268,11 +275,11 @@ export const TasksView: React.FC<TasksViewProps> = ({
|
|||||||
|
|
||||||
{/* View Switcher: List vs Kanban */}
|
{/* View Switcher: List vs Kanban */}
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<div className="flex border border-border bg-canvas">
|
<div className="flex border border-border rounded-btn bg-canvas overflow-hidden">
|
||||||
<button
|
<button
|
||||||
onClick={() => setViewMode('list')}
|
onClick={() => setViewMode('list')}
|
||||||
className={`h-8 px-2.5 flex items-center gap-1.5 text-xs transition-colors border-r border-border ${
|
className={`h-8 px-2.5 flex items-center gap-1.5 text-xs transition-colors border-r border-border ${
|
||||||
viewMode === 'list' ? 'bg-panel text-white' : 'text-zinc-400 hover:text-white'
|
viewMode === 'list' ? 'bg-panel text-main font-medium' : 'text-muted hover:text-main'
|
||||||
}`}
|
}`}
|
||||||
title="Listenansicht"
|
title="Listenansicht"
|
||||||
>
|
>
|
||||||
@@ -282,7 +289,9 @@ export const TasksView: React.FC<TasksViewProps> = ({
|
|||||||
<button
|
<button
|
||||||
onClick={() => setViewMode('kanban')}
|
onClick={() => setViewMode('kanban')}
|
||||||
className={`h-8 px-2.5 flex items-center gap-1.5 text-xs transition-colors ${
|
className={`h-8 px-2.5 flex items-center gap-1.5 text-xs transition-colors ${
|
||||||
viewMode === 'kanban' ? 'bg-panel text-white' : 'text-zinc-400 hover:text-white'
|
viewMode === 'kanban'
|
||||||
|
? 'bg-panel text-main font-medium'
|
||||||
|
: 'text-muted hover:text-main'
|
||||||
}`}
|
}`}
|
||||||
title="Kanban-Board"
|
title="Kanban-Board"
|
||||||
>
|
>
|
||||||
@@ -294,7 +303,10 @@ export const TasksView: React.FC<TasksViewProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Quick Add Task Input in active list */}
|
{/* Quick Add Task Input in active list */}
|
||||||
<form onSubmit={handleQuickAddTask} className="flex items-center gap-1.5 bg-panel border border-border p-1.5">
|
<form
|
||||||
|
onSubmit={handleQuickAddTask}
|
||||||
|
className="flex items-center gap-1.5 panel-surface p-1.5"
|
||||||
|
>
|
||||||
<Plus className="w-4 h-4 text-accent ml-1 flex-shrink-0" />
|
<Plus className="w-4 h-4 text-accent ml-1 flex-shrink-0" />
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
@@ -305,14 +317,14 @@ export const TasksView: React.FC<TasksViewProps> = ({
|
|||||||
? `Aufgabe zu "${currentSelectedList.title}" hinzufügen… (Enter)`
|
? `Aufgabe zu "${currentSelectedList.title}" hinzufügen… (Enter)`
|
||||||
: 'Neue Aufgabe anlegen… (Enter)'
|
: 'Neue Aufgabe anlegen… (Enter)'
|
||||||
}
|
}
|
||||||
className="flex-1 bg-transparent px-2 py-1 text-sm text-white placeholder-zinc-500 focus:outline-none"
|
className="flex-1 bg-transparent px-2 py-1 text-sm text-main placeholder-faint focus:outline-none"
|
||||||
/>
|
/>
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<input
|
<input
|
||||||
type="date"
|
type="date"
|
||||||
value={quickDueDate}
|
value={quickDueDate}
|
||||||
onChange={(e) => setQuickDueDate(e.target.value)}
|
onChange={(e) => setQuickDueDate(e.target.value)}
|
||||||
className="h-7 px-2 bg-canvas border border-border text-xs text-zinc-300 focus:outline-none"
|
className="h-7 px-2 input-field text-xs"
|
||||||
title="Fälligkeitsdatum"
|
title="Fälligkeitsdatum"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
@@ -327,21 +339,24 @@ export const TasksView: React.FC<TasksViewProps> = ({
|
|||||||
|
|
||||||
{/* NEW LIST MODAL */}
|
{/* NEW LIST MODAL */}
|
||||||
{showNewListModal && (
|
{showNewListModal && (
|
||||||
<div className="fixed inset-0 bg-black/60 backdrop-blur-sm z-50 flex items-center justify-center p-4">
|
<div className="fixed inset-0 modal-overlay z-50 flex items-center justify-center p-4">
|
||||||
<div className="w-full max-w-sm bg-panel border border-border p-4 space-y-3">
|
<div className="w-full max-w-sm panel-surface shadow-panel p-4 space-y-3">
|
||||||
<div className="flex items-center justify-between border-b border-border pb-2">
|
<div className="flex items-center justify-between border-b border-border pb-2">
|
||||||
<h3 className="text-sm font-semibold text-white flex items-center gap-1.5">
|
<h3 className="text-sm font-semibold text-main flex items-center gap-1.5">
|
||||||
<Layers className="w-4 h-4 text-accent" />
|
<Layers className="w-4 h-4 text-accent" />
|
||||||
<span>Neue Aufgabenliste erstellen</span>
|
<span>Neue Aufgabenliste erstellen</span>
|
||||||
</h3>
|
</h3>
|
||||||
<button onClick={() => setShowNewListModal(false)} className="text-zinc-400 hover:text-white">
|
<button
|
||||||
|
onClick={() => setShowNewListModal(false)}
|
||||||
|
className="text-muted hover:text-main"
|
||||||
|
>
|
||||||
<X className="w-4 h-4" />
|
<X className="w-4 h-4" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form onSubmit={handleCreateTaskList} className="space-y-3">
|
<form onSubmit={handleCreateTaskList} className="space-y-3">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-xs text-zinc-400 mb-1">Titel der Liste</label>
|
<label className="block text-xs text-muted mb-1">Titel der Liste</label>
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
@@ -358,21 +373,23 @@ export const TasksView: React.FC<TasksViewProps> = ({
|
|||||||
placeholder="z.B. Homelab, Einkäufe, Steuern"
|
placeholder="z.B. Homelab, Einkäufe, Steuern"
|
||||||
autoFocus
|
autoFocus
|
||||||
required
|
required
|
||||||
className="flex-1 h-8 input-field px-2.5 text-xs text-white"
|
className="flex-1 h-8 input-field px-2.5 text-xs"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{spaces.length > 0 && (
|
{safeSpaces.length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-xs text-zinc-400 mb-1">Geteilt mit Space (optional)</label>
|
<label className="block text-xs text-muted mb-1">
|
||||||
|
Geteilt mit Space (optional)
|
||||||
|
</label>
|
||||||
<select
|
<select
|
||||||
value={newListSpaceId}
|
value={newListSpaceId}
|
||||||
onChange={(e) => setNewListSpaceId(e.target.value)}
|
onChange={(e) => setNewListSpaceId(e.target.value)}
|
||||||
className="w-full h-8 input-field px-2 text-xs text-white bg-canvas"
|
className="w-full h-8 input-field px-2 text-xs"
|
||||||
>
|
>
|
||||||
<option value="">Privat (nur ich)</option>
|
<option value="">Privat (nur ich)</option>
|
||||||
{spaces.map((sp) => (
|
{safeSpaces.map((sp) => (
|
||||||
<option key={sp.id} value={sp.id}>
|
<option key={sp.id} value={sp.id}>
|
||||||
{sp.icon} {sp.name}
|
{sp.icon} {sp.name}
|
||||||
</option>
|
</option>
|
||||||
@@ -405,29 +422,27 @@ export const TasksView: React.FC<TasksViewProps> = ({
|
|||||||
{/* COLUMN 1: TODO */}
|
{/* COLUMN 1: TODO */}
|
||||||
<div className="bg-canvas border border-border flex flex-col">
|
<div className="bg-canvas border border-border flex flex-col">
|
||||||
<div className="px-3 py-2 bg-panel border-b border-border flex items-center justify-between">
|
<div className="px-3 py-2 bg-panel border-b border-border flex items-center justify-between">
|
||||||
<span className="text-xs font-semibold text-zinc-300 flex items-center gap-1.5">
|
<span className="text-xs font-semibold text-main flex items-center gap-1.5">
|
||||||
<span className="w-2 h-2 bg-zinc-500 inline-block" />
|
<span className="w-2 h-2 bg-faint inline-block" />
|
||||||
<span>Zu erledigen</span>
|
<span>Zu erledigen</span>
|
||||||
</span>
|
</span>
|
||||||
<span className="px-1.5 py-0.2 bg-canvas border border-border text-[10px] text-zinc-400 font-mono">
|
<span className="badge-neutral font-mono">{todoKanban.length}</span>
|
||||||
{todoKanban.length}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="p-2 space-y-2 min-h-[360px] flex-1 overflow-y-auto">
|
<div className="p-2 space-y-2 min-h-[360px] flex-1 overflow-y-auto">
|
||||||
{todoKanban.map((task) => (
|
{todoKanban.map((task) => (
|
||||||
<div
|
<div
|
||||||
key={task.id}
|
key={task.id}
|
||||||
className="bg-panel border border-border p-2.5 space-y-2 hover:border-zinc-500 transition-colors"
|
className="panel-surface p-2.5 space-y-2 hover:border-strong transition-colors"
|
||||||
>
|
>
|
||||||
<div className="flex items-start justify-between gap-2">
|
<div className="flex items-start justify-between gap-2">
|
||||||
<p className="text-xs font-medium text-white break-words flex-1 leading-snug">
|
<p className="text-xs font-medium text-main break-words flex-1 leading-snug">
|
||||||
{task.content}
|
{task.content}
|
||||||
</p>
|
</p>
|
||||||
<button
|
<button
|
||||||
onClick={() => handleConvertToDoc(task)}
|
onClick={() => handleConvertToDoc(task)}
|
||||||
title="Als Notiz vertiefen"
|
title="Als Notiz vertiefen"
|
||||||
className="text-zinc-500 hover:text-accent flex-shrink-0"
|
className="text-faint hover:text-accent flex-shrink-0"
|
||||||
>
|
>
|
||||||
<FileText className="w-3.5 h-3.5" />
|
<FileText className="w-3.5 h-3.5" />
|
||||||
</button>
|
</button>
|
||||||
@@ -441,7 +456,7 @@ export const TasksView: React.FC<TasksViewProps> = ({
|
|||||||
onChange={(e) => setNotesDraft(e.target.value)}
|
onChange={(e) => setNotesDraft(e.target.value)}
|
||||||
placeholder="Detailnotizen eingeben…"
|
placeholder="Detailnotizen eingeben…"
|
||||||
rows={2}
|
rows={2}
|
||||||
className="w-full input-field p-1.5 text-xs text-white resize-none"
|
className="w-full input-field p-1.5 text-xs resize-none"
|
||||||
/>
|
/>
|
||||||
<div className="flex justify-end gap-1">
|
<div className="flex justify-end gap-1">
|
||||||
<button
|
<button
|
||||||
@@ -464,7 +479,7 @@ export const TasksView: React.FC<TasksViewProps> = ({
|
|||||||
setActiveNotesTaskId(task.id);
|
setActiveNotesTaskId(task.id);
|
||||||
setNotesDraft(task.notes || '');
|
setNotesDraft(task.notes || '');
|
||||||
}}
|
}}
|
||||||
className="p-1.5 bg-canvas border border-border text-[11px] text-zinc-400 break-words cursor-pointer hover:border-zinc-500"
|
className="p-1.5 bg-canvas border border-border text-[11px] text-muted break-words cursor-pointer hover:border-strong"
|
||||||
title="Klicken zum Bearbeiten der Notizen"
|
title="Klicken zum Bearbeiten der Notizen"
|
||||||
>
|
>
|
||||||
{task.notes}
|
{task.notes}
|
||||||
@@ -472,10 +487,10 @@ export const TasksView: React.FC<TasksViewProps> = ({
|
|||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{/* Footer & Movement Controls */}
|
{/* Footer & Movement Controls */}
|
||||||
<div className="flex items-center justify-between gap-1 pt-1 border-t border-white/[0.05] text-[11px]">
|
<div className="flex items-center justify-between gap-1 pt-1 border-t border-subtle text-[11px]">
|
||||||
<div className="flex items-center gap-1.5 text-zinc-400">
|
<div className="flex items-center gap-1.5 text-muted">
|
||||||
{task.dueDate && (
|
{task.dueDate && (
|
||||||
<span className="flex items-center gap-1 text-[10px] text-zinc-300">
|
<span className="flex items-center gap-1 text-[10px] text-muted">
|
||||||
<Calendar className="w-2.5 h-2.5" /> {task.dueDate}
|
<Calendar className="w-2.5 h-2.5" /> {task.dueDate}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -485,7 +500,7 @@ export const TasksView: React.FC<TasksViewProps> = ({
|
|||||||
setActiveNotesTaskId(task.id);
|
setActiveNotesTaskId(task.id);
|
||||||
setNotesDraft('');
|
setNotesDraft('');
|
||||||
}}
|
}}
|
||||||
className="text-[10px] text-zinc-500 hover:text-zinc-300"
|
className="text-[10px] text-faint hover:text-main"
|
||||||
>
|
>
|
||||||
+ Notiz
|
+ Notiz
|
||||||
</button>
|
</button>
|
||||||
@@ -506,7 +521,7 @@ export const TasksView: React.FC<TasksViewProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
{todoKanban.length === 0 && (
|
{todoKanban.length === 0 && (
|
||||||
<div className="p-4 text-center text-zinc-600 text-xs">Keine Aufgaben offen</div>
|
<div className="p-4 text-center text-faint text-xs">Keine Aufgaben offen</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -518,25 +533,23 @@ export const TasksView: React.FC<TasksViewProps> = ({
|
|||||||
<span className="w-2 h-2 bg-accent inline-block" />
|
<span className="w-2 h-2 bg-accent inline-block" />
|
||||||
<span>In Bearbeitung</span>
|
<span>In Bearbeitung</span>
|
||||||
</span>
|
</span>
|
||||||
<span className="px-1.5 py-0.2 bg-canvas border border-border text-[10px] text-zinc-400 font-mono">
|
<span className="badge-neutral font-mono">{inProgressKanban.length}</span>
|
||||||
{inProgressKanban.length}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="p-2 space-y-2 min-h-[360px] flex-1 overflow-y-auto">
|
<div className="p-2 space-y-2 min-h-[360px] flex-1 overflow-y-auto">
|
||||||
{inProgressKanban.map((task) => (
|
{inProgressKanban.map((task) => (
|
||||||
<div
|
<div
|
||||||
key={task.id}
|
key={task.id}
|
||||||
className="bg-panel border border-accent/40 p-2.5 space-y-2 hover:border-accent transition-colors"
|
className="bg-panel border border-accent-border p-2.5 space-y-2 hover:border-accent transition-colors"
|
||||||
>
|
>
|
||||||
<div className="flex items-start justify-between gap-2">
|
<div className="flex items-start justify-between gap-2">
|
||||||
<p className="text-xs font-medium text-white break-words flex-1 leading-snug">
|
<p className="text-xs font-medium text-main break-words flex-1 leading-snug">
|
||||||
{task.content}
|
{task.content}
|
||||||
</p>
|
</p>
|
||||||
<button
|
<button
|
||||||
onClick={() => handleConvertToDoc(task)}
|
onClick={() => handleConvertToDoc(task)}
|
||||||
title="Als Notiz vertiefen"
|
title="Als Notiz vertiefen"
|
||||||
className="text-zinc-500 hover:text-accent flex-shrink-0"
|
className="text-faint hover:text-accent flex-shrink-0"
|
||||||
>
|
>
|
||||||
<FileText className="w-3.5 h-3.5" />
|
<FileText className="w-3.5 h-3.5" />
|
||||||
</button>
|
</button>
|
||||||
@@ -550,7 +563,7 @@ export const TasksView: React.FC<TasksViewProps> = ({
|
|||||||
onChange={(e) => setNotesDraft(e.target.value)}
|
onChange={(e) => setNotesDraft(e.target.value)}
|
||||||
placeholder="Detailnotizen eingeben…"
|
placeholder="Detailnotizen eingeben…"
|
||||||
rows={2}
|
rows={2}
|
||||||
className="w-full input-field p-1.5 text-xs text-white resize-none"
|
className="w-full input-field p-1.5 text-xs resize-none"
|
||||||
/>
|
/>
|
||||||
<div className="flex justify-end gap-1">
|
<div className="flex justify-end gap-1">
|
||||||
<button
|
<button
|
||||||
@@ -573,20 +586,20 @@ export const TasksView: React.FC<TasksViewProps> = ({
|
|||||||
setActiveNotesTaskId(task.id);
|
setActiveNotesTaskId(task.id);
|
||||||
setNotesDraft(task.notes || '');
|
setNotesDraft(task.notes || '');
|
||||||
}}
|
}}
|
||||||
className="p-1.5 bg-canvas border border-border text-[11px] text-zinc-400 break-words cursor-pointer hover:border-zinc-500"
|
className="p-1.5 bg-canvas border border-border text-[11px] text-muted break-words cursor-pointer hover:border-strong"
|
||||||
>
|
>
|
||||||
{task.notes}
|
{task.notes}
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{/* Movement Controls */}
|
{/* Movement Controls */}
|
||||||
<div className="flex items-center justify-between gap-1 pt-1 border-t border-white/[0.05] text-[11px]">
|
<div className="flex items-center justify-between gap-1 pt-1 border-t border-subtle text-[11px]">
|
||||||
<button
|
<button
|
||||||
onClick={() => handleKanbanMove(task, 'todo')}
|
onClick={() => handleKanbanMove(task, 'todo')}
|
||||||
className="btn-action h-6 px-1.5 text-[10px] flex items-center gap-1"
|
className="btn-action h-6 px-1.5 text-[10px] flex items-center gap-1"
|
||||||
title="Zurück zu 'Zu erledigen'"
|
title="Zurück zu 'Zu erledigen'"
|
||||||
>
|
>
|
||||||
<ArrowLeft className="w-3 h-3 text-zinc-400" />
|
<ArrowLeft className="w-3 h-3 text-muted" />
|
||||||
<span>Zurück</span>
|
<span>Zurück</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@@ -602,7 +615,7 @@ export const TasksView: React.FC<TasksViewProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
{inProgressKanban.length === 0 && (
|
{inProgressKanban.length === 0 && (
|
||||||
<div className="p-4 text-center text-zinc-600 text-xs">Keine aktiven Aufgaben</div>
|
<div className="p-4 text-center text-faint text-xs">Keine aktiven Aufgaben</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -610,26 +623,24 @@ export const TasksView: React.FC<TasksViewProps> = ({
|
|||||||
{/* COLUMN 3: DONE */}
|
{/* COLUMN 3: DONE */}
|
||||||
<div className="bg-canvas border border-border flex flex-col">
|
<div className="bg-canvas border border-border flex flex-col">
|
||||||
<div className="px-3 py-2 bg-panel border-b border-border flex items-center justify-between">
|
<div className="px-3 py-2 bg-panel border-b border-border flex items-center justify-between">
|
||||||
<span className="text-xs font-semibold text-emerald-400 flex items-center gap-1.5">
|
<span className="text-xs font-semibold text-status-ok flex items-center gap-1.5">
|
||||||
<span className="w-2 h-2 bg-emerald-500 inline-block" />
|
<span className="w-2 h-2 bg-status-ok inline-block" />
|
||||||
<span>Erledigt</span>
|
<span>Erledigt</span>
|
||||||
</span>
|
</span>
|
||||||
<span className="px-1.5 py-0.2 bg-canvas border border-border text-[10px] text-zinc-400 font-mono">
|
<span className="badge-neutral font-mono">{doneKanban.length}</span>
|
||||||
{doneKanban.length}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="p-2 space-y-2 min-h-[360px] flex-1 overflow-y-auto">
|
<div className="p-2 space-y-2 min-h-[360px] flex-1 overflow-y-auto">
|
||||||
{doneKanban.map((task) => (
|
{doneKanban.map((task) => (
|
||||||
<div
|
<div
|
||||||
key={task.id}
|
key={task.id}
|
||||||
className="bg-panel border border-border p-2.5 space-y-2 opacity-70 hover:opacity-100 transition-opacity"
|
className="panel-surface p-2.5 space-y-2 opacity-70 hover:opacity-100 transition-opacity"
|
||||||
>
|
>
|
||||||
<p className="text-xs font-medium text-zinc-400 line-through break-words leading-snug">
|
<p className="text-xs font-medium text-muted line-through break-words leading-snug">
|
||||||
{task.content}
|
{task.content}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="flex items-center justify-between gap-1 pt-1 border-t border-white/[0.05] text-[11px]">
|
<div className="flex items-center justify-between gap-1 pt-1 border-t border-subtle text-[11px]">
|
||||||
<button
|
<button
|
||||||
onClick={() => handleKanbanMove(task, 'todo')}
|
onClick={() => handleKanbanMove(task, 'todo')}
|
||||||
className="btn-action h-6 px-1.5 text-[10px] flex items-center gap-1"
|
className="btn-action h-6 px-1.5 text-[10px] flex items-center gap-1"
|
||||||
@@ -640,7 +651,7 @@ export const TasksView: React.FC<TasksViewProps> = ({
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => handleDeletePermanent(task.id)}
|
onClick={() => handleDeletePermanent(task.id)}
|
||||||
className="text-zinc-500 hover:text-red-400 p-1"
|
className="text-faint hover:text-status-err p-1"
|
||||||
title="Löschen"
|
title="Löschen"
|
||||||
>
|
>
|
||||||
<Trash2 className="w-3 h-3" />
|
<Trash2 className="w-3 h-3" />
|
||||||
@@ -649,7 +660,9 @@ export const TasksView: React.FC<TasksViewProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
{doneKanban.length === 0 && (
|
{doneKanban.length === 0 && (
|
||||||
<div className="p-4 text-center text-zinc-600 text-xs">Noch keine erledigten Aufgaben</div>
|
<div className="p-4 text-center text-faint text-xs">
|
||||||
|
Noch keine erledigten Aufgaben
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -659,11 +672,11 @@ export const TasksView: React.FC<TasksViewProps> = ({
|
|||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{/* Subfilter tabs for List View */}
|
{/* Subfilter tabs for List View */}
|
||||||
<div className="flex items-center justify-between gap-2">
|
<div className="flex items-center justify-between gap-2">
|
||||||
<div className="flex items-center border border-border bg-canvas">
|
<div className="flex items-center border border-border rounded-btn bg-canvas overflow-hidden">
|
||||||
<button
|
<button
|
||||||
onClick={() => setFilter('open')}
|
onClick={() => setFilter('open')}
|
||||||
className={`h-8 px-3.5 text-xs font-medium border-r border-border transition-colors ${
|
className={`h-8 px-3.5 text-xs font-medium border-r border-border transition-colors ${
|
||||||
filter === 'open' ? 'bg-panel text-white' : 'text-zinc-400 hover:text-white'
|
filter === 'open' ? 'bg-panel text-main' : 'text-muted hover:text-main'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
Offen ({openTasks.length})
|
Offen ({openTasks.length})
|
||||||
@@ -672,7 +685,7 @@ export const TasksView: React.FC<TasksViewProps> = ({
|
|||||||
<button
|
<button
|
||||||
onClick={() => setFilter('done')}
|
onClick={() => setFilter('done')}
|
||||||
className={`h-8 px-3.5 text-xs font-medium border-r border-border transition-colors ${
|
className={`h-8 px-3.5 text-xs font-medium border-r border-border transition-colors ${
|
||||||
filter === 'done' ? 'bg-panel text-white' : 'text-zinc-400 hover:text-white'
|
filter === 'done' ? 'bg-panel text-main' : 'text-muted hover:text-main'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
Erledigt ({doneTasks.length})
|
Erledigt ({doneTasks.length})
|
||||||
@@ -681,14 +694,14 @@ export const TasksView: React.FC<TasksViewProps> = ({
|
|||||||
<button
|
<button
|
||||||
onClick={() => setFilter('discarded')}
|
onClick={() => setFilter('discarded')}
|
||||||
className={`h-8 px-3.5 text-xs font-medium transition-colors ${
|
className={`h-8 px-3.5 text-xs font-medium transition-colors ${
|
||||||
filter === 'discarded' ? 'bg-panel text-white' : 'text-zinc-400 hover:text-white'
|
filter === 'discarded' ? 'bg-panel text-main' : 'text-muted hover:text-main'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
Papierkorb ({discarded.length})
|
Papierkorb ({safeDiscarded.length})
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{filter === 'discarded' && discarded.length > 0 && (
|
{filter === 'discarded' && safeDiscarded.length > 0 && (
|
||||||
<button onClick={handlePurgeAllDiscarded} className="btn-action">
|
<button onClick={handlePurgeAllDiscarded} className="btn-action">
|
||||||
<Trash2 className="w-3.5 h-3.5" />
|
<Trash2 className="w-3.5 h-3.5" />
|
||||||
Papierkorb leeren
|
Papierkorb leeren
|
||||||
@@ -697,22 +710,22 @@ export const TasksView: React.FC<TasksViewProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<div className="p-6 text-center text-zinc-500 text-sm">Lade Aufgaben…</div>
|
<div className="p-6 text-center text-faint text-sm">Lade Aufgaben…</div>
|
||||||
) : filter === 'discarded' ? (
|
) : filter === 'discarded' ? (
|
||||||
<div className="panel-surface divide-y divide-border">
|
<div className="panel-surface divide-y divide-border">
|
||||||
{discarded.length === 0 ? (
|
{safeDiscarded.length === 0 ? (
|
||||||
<div className="px-4 py-6 text-center text-zinc-400 text-sm">
|
<div className="px-4 py-6 text-center text-muted text-sm">
|
||||||
Keine verworfenen Gedanken im Papierkorb.
|
Keine verworfenen Gedanken im Papierkorb.
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
discarded.map((item) => (
|
safeDiscarded.map((item) => (
|
||||||
<div
|
<div
|
||||||
key={item.id}
|
key={item.id}
|
||||||
className="px-3 py-2 flex items-center justify-between gap-3 hover:bg-white/[0.02]"
|
className="px-3 py-2 flex items-center justify-between gap-3 hover:bg-panel-hover"
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2.5 min-w-0 flex-1">
|
<div className="flex items-center gap-2.5 min-w-0 flex-1">
|
||||||
<Trash2 className="w-4 h-4 text-zinc-500 flex-shrink-0" />
|
<Trash2 className="w-4 h-4 text-faint flex-shrink-0" />
|
||||||
<p className="text-sm text-zinc-400 line-through truncate">{item.content}</p>
|
<p className="text-sm text-muted line-through truncate">{item.content}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 flex-shrink-0">
|
<div className="flex items-center gap-2 flex-shrink-0">
|
||||||
<button onClick={() => handleRestore(item)} className="btn-action">
|
<button onClick={() => handleRestore(item)} className="btn-action">
|
||||||
@@ -733,7 +746,7 @@ export const TasksView: React.FC<TasksViewProps> = ({
|
|||||||
) : (
|
) : (
|
||||||
<div className="panel-surface divide-y divide-border">
|
<div className="panel-surface divide-y divide-border">
|
||||||
{(filter === 'open' ? openTasks : doneTasks).length === 0 ? (
|
{(filter === 'open' ? openTasks : doneTasks).length === 0 ? (
|
||||||
<div className="px-4 py-6 text-center text-zinc-400 text-sm">
|
<div className="px-4 py-6 text-center text-muted text-sm">
|
||||||
{filter === 'open'
|
{filter === 'open'
|
||||||
? 'Keine offenen Aufgaben vorhanden.'
|
? 'Keine offenen Aufgaben vorhanden.'
|
||||||
: 'Noch keine erledigten Aufgaben.'}
|
: 'Noch keine erledigten Aufgaben.'}
|
||||||
@@ -742,18 +755,18 @@ export const TasksView: React.FC<TasksViewProps> = ({
|
|||||||
(filter === 'open' ? openTasks : doneTasks).map((task) => (
|
(filter === 'open' ? openTasks : doneTasks).map((task) => (
|
||||||
<div
|
<div
|
||||||
key={task.id}
|
key={task.id}
|
||||||
className={`px-3 py-2.5 flex flex-col sm:flex-row sm:items-center justify-between gap-2.5 transition-colors hover:bg-white/[0.02] ${
|
className={`px-3 py-2.5 flex flex-col sm:flex-row sm:items-center justify-between gap-2.5 transition-colors hover:bg-panel-hover ${
|
||||||
task.isCompleted ? 'opacity-65' : ''
|
task.isCompleted ? 'opacity-65' : ''
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="flex items-start gap-3 min-w-0 flex-1">
|
<div className="flex items-start gap-3 min-w-0 flex-1">
|
||||||
{/* Square minimalist checkbox */}
|
{/* Theme-controlled checkbox (--radius-checkbox) */}
|
||||||
<button
|
<button
|
||||||
onClick={() => handleToggleDone(task)}
|
onClick={() => handleToggleDone(task)}
|
||||||
className={`w-4 h-4 mt-0.5 flex items-center justify-center flex-shrink-0 transition-colors border ${
|
className={`w-4 h-4 mt-0.5 checkbox-square flex items-center justify-center flex-shrink-0 transition-colors border ${
|
||||||
task.isCompleted
|
task.isCompleted
|
||||||
? 'bg-accent border-accent text-white'
|
? 'bg-accent border-accent text-on-accent'
|
||||||
: 'border-zinc-500 hover:border-accent text-transparent'
|
: 'border-strong hover:border-accent text-transparent'
|
||||||
}`}
|
}`}
|
||||||
title={task.isCompleted ? 'Als offen markieren' : 'Als erledigt abhaken'}
|
title={task.isCompleted ? 'Als offen markieren' : 'Als erledigt abhaken'}
|
||||||
>
|
>
|
||||||
@@ -763,7 +776,7 @@ export const TasksView: React.FC<TasksViewProps> = ({
|
|||||||
<div className="min-w-0 flex-1 space-y-1">
|
<div className="min-w-0 flex-1 space-y-1">
|
||||||
<p
|
<p
|
||||||
className={`text-sm break-words leading-tight ${
|
className={`text-sm break-words leading-tight ${
|
||||||
task.isCompleted ? 'line-through text-zinc-500' : 'text-white'
|
task.isCompleted ? 'line-through text-faint' : 'text-main'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{task.content}
|
{task.content}
|
||||||
@@ -777,7 +790,7 @@ export const TasksView: React.FC<TasksViewProps> = ({
|
|||||||
onChange={(e) => setNotesDraft(e.target.value)}
|
onChange={(e) => setNotesDraft(e.target.value)}
|
||||||
placeholder="Detailnotizen eingeben…"
|
placeholder="Detailnotizen eingeben…"
|
||||||
rows={2}
|
rows={2}
|
||||||
className="w-full input-field p-2 text-xs text-white resize-none"
|
className="w-full input-field p-2 text-xs resize-none"
|
||||||
/>
|
/>
|
||||||
<div className="flex justify-end gap-1.5">
|
<div className="flex justify-end gap-1.5">
|
||||||
<button
|
<button
|
||||||
@@ -800,7 +813,7 @@ export const TasksView: React.FC<TasksViewProps> = ({
|
|||||||
setActiveNotesTaskId(task.id);
|
setActiveNotesTaskId(task.id);
|
||||||
setNotesDraft(task.notes || '');
|
setNotesDraft(task.notes || '');
|
||||||
}}
|
}}
|
||||||
className="text-xs text-zinc-400 bg-canvas p-1.5 border border-border cursor-pointer hover:border-zinc-500"
|
className="text-xs text-muted bg-canvas p-1.5 border border-border cursor-pointer hover:border-strong"
|
||||||
title="Klicken zum Bearbeiten"
|
title="Klicken zum Bearbeiten"
|
||||||
>
|
>
|
||||||
{task.notes}
|
{task.notes}
|
||||||
@@ -812,20 +825,15 @@ export const TasksView: React.FC<TasksViewProps> = ({
|
|||||||
<span className="badge-accent">Dringend</span>
|
<span className="badge-accent">Dringend</span>
|
||||||
)}
|
)}
|
||||||
{task.priority === 'high' && (
|
{task.priority === 'high' && (
|
||||||
<span className="px-1.5 py-0.2 border border-white/20 text-zinc-300 text-[10px] font-medium">
|
<span className="px-1.5 py-0.5 border border-strong rounded-btn text-main text-[10px] font-medium">
|
||||||
Wichtig
|
Wichtig
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{task.kanbanStatus === 'in_progress' && (
|
{task.kanbanStatus === 'in_progress' && (
|
||||||
<span className="px-1.5 py-0.2 bg-blue-900/40 border border-blue-500/40 text-blue-300 text-[10px]">
|
<span className="badge-accent">⚡ In Bearbeitung</span>
|
||||||
⚡ In Bearbeitung
|
|
||||||
</span>
|
|
||||||
)}
|
)}
|
||||||
{task.tags?.map((tag) => (
|
{task.tags?.map((tag) => (
|
||||||
<span
|
<span key={tag} className="badge-neutral">
|
||||||
key={tag}
|
|
||||||
className="px-1.5 py-0.2 bg-canvas text-zinc-400 text-[10px]"
|
|
||||||
>
|
|
||||||
#{tag}
|
#{tag}
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
@@ -850,7 +858,7 @@ export const TasksView: React.FC<TasksViewProps> = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="relative flex items-center">
|
<div className="relative flex items-center">
|
||||||
<Calendar className="w-3 h-3 text-zinc-400 absolute left-2 pointer-events-none" />
|
<Calendar className="w-3 h-3 text-muted absolute left-2 pointer-events-none" />
|
||||||
<input
|
<input
|
||||||
type="date"
|
type="date"
|
||||||
value={task.dueDate || ''}
|
value={task.dueDate || ''}
|
||||||
|
|||||||
@@ -193,19 +193,19 @@ export const TriageDeck: React.FC<TriageDeckProps> = ({
|
|||||||
switch (priority) {
|
switch (priority) {
|
||||||
case 'urgent':
|
case 'urgent':
|
||||||
return (
|
return (
|
||||||
<span className="px-2 py-0.5 text-[11px] font-semibold border border-accent text-accent">
|
<span className="px-2 py-0.5 text-[11px] font-semibold border border-accent rounded-btn text-accent">
|
||||||
Dringend
|
Dringend
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
case 'high':
|
case 'high':
|
||||||
return (
|
return (
|
||||||
<span className="px-2 py-0.5 text-[11px] font-medium border border-white/20 text-zinc-200">
|
<span className="px-2 py-0.5 text-[11px] font-medium border border-strong rounded-btn text-main">
|
||||||
Wichtig
|
Wichtig
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
return (
|
return (
|
||||||
<span className="px-2 py-0.5 text-[11px] font-medium bg-panel-elevated text-zinc-400 border border-border">
|
<span className="px-2 py-0.5 text-[11px] font-medium bg-panel-elevated text-muted border border-border rounded-btn">
|
||||||
Quick Dump
|
Quick Dump
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
@@ -215,14 +215,14 @@ export const TriageDeck: React.FC<TriageDeckProps> = ({
|
|||||||
const sourceBadge = (source: string) => {
|
const sourceBadge = (source: string) => {
|
||||||
if (source === 'voice') {
|
if (source === 'voice') {
|
||||||
return (
|
return (
|
||||||
<span className="px-2 py-0.5 text-[11px] bg-canvas text-accent border border-border flex items-center gap-1">
|
<span className="px-2 py-0.5 text-[11px] bg-canvas text-accent border border-border rounded-btn flex items-center gap-1">
|
||||||
<Mic className="w-3 h-3" /> Diktat
|
<Mic className="w-3 h-3" /> Diktat
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (source === 'webhook') {
|
if (source === 'webhook') {
|
||||||
return (
|
return (
|
||||||
<span className="px-2 py-0.5 text-[11px] bg-canvas text-zinc-300 border border-border flex items-center gap-1">
|
<span className="px-2 py-0.5 text-[11px] bg-canvas text-muted border border-border rounded-btn flex items-center gap-1">
|
||||||
<Webhook className="w-3 h-3 text-accent" /> Webhook
|
<Webhook className="w-3 h-3 text-accent" /> Webhook
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
@@ -232,13 +232,15 @@ export const TriageDeck: React.FC<TriageDeckProps> = ({
|
|||||||
|
|
||||||
if (!activeCard) {
|
if (!activeCard) {
|
||||||
return (
|
return (
|
||||||
<div className="max-w-xl mx-auto mt-2 p-6 bg-panel border border-border text-center">
|
<div className="max-w-xl mx-auto mt-2 p-6 panel-surface text-center">
|
||||||
<div className="w-11 h-11 bg-accent text-white flex items-center justify-center mx-auto mb-3 shadow-sm">
|
<div className="w-11 h-11 bg-accent text-on-accent rounded-btn flex items-center justify-center mx-auto mb-3 shadow-sm">
|
||||||
<Sparkles className="w-5 h-5" />
|
<Sparkles className="w-5 h-5" />
|
||||||
</div>
|
</div>
|
||||||
<h2 className="text-lg font-bold text-white mb-1">Triage-Deck ist leer</h2>
|
<h2 className="text-lg font-bold text-main mb-1">Triage-Deck ist leer</h2>
|
||||||
<p className="text-zinc-400 text-xs sm:text-sm max-w-md mx-auto mb-4">
|
<p className="text-muted text-xs sm:text-sm max-w-md mx-auto mb-4">
|
||||||
Alle Quick Dump Notizen sind einsortiert oder verworfen. Tippe oben in die Leiste (Taste <kbd className="px-1.5 py-0.5 bg-canvas border border-border text-zinc-300">C</kbd>), um neue Gedanken reinzurotzen.
|
Alle Quick Dump Notizen sind einsortiert oder verworfen. Tippe oben in die Leiste (Taste{' '}
|
||||||
|
<kbd className="px-1.5 py-0.5 bg-canvas border border-border rounded-btn text-main">C</kbd>
|
||||||
|
), um neue Gedanken reinzurotzen.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="flex flex-wrap items-center justify-center gap-2">
|
<div className="flex flex-wrap items-center justify-center gap-2">
|
||||||
@@ -271,8 +273,8 @@ export const TriageDeck: React.FC<TriageDeckProps> = ({
|
|||||||
<span className="badge-accent">
|
<span className="badge-accent">
|
||||||
{cards.length} {cards.length === 1 ? 'Quick Dump' : 'Quick Dumps'} offen
|
{cards.length} {cards.length === 1 ? 'Quick Dump' : 'Quick Dumps'} offen
|
||||||
</span>
|
</span>
|
||||||
<span className="hidden sm:inline text-xs text-zinc-600">•</span>
|
<span className="hidden sm:inline text-xs text-faint">•</span>
|
||||||
<span className="hidden sm:flex items-center gap-1 text-xs text-zinc-400">
|
<span className="hidden sm:flex items-center gap-1 text-xs text-muted">
|
||||||
<Keyboard className="w-3 h-3" /> ← Wegwerfen • → Aufgabe • D Notiz • Leertaste Später
|
<Keyboard className="w-3 h-3" /> ← Wegwerfen • → Aufgabe • D Notiz • Leertaste Später
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -294,11 +296,11 @@ export const TriageDeck: React.FC<TriageDeckProps> = ({
|
|||||||
{nextCard && (
|
{nextCard && (
|
||||||
<div
|
<div
|
||||||
key={nextCard.id + '-preview'}
|
key={nextCard.id + '-preview'}
|
||||||
className="absolute inset-x-3 top-3 bottom-0 bg-panel/60 border border-border p-4 opacity-40 scale-[0.97] pointer-events-none flex flex-col justify-between"
|
className="absolute inset-x-3 top-3 bottom-0 panel-surface p-4 opacity-40 scale-[0.97] pointer-events-none flex flex-col justify-between"
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2">{priorityBadge(nextCard.priority)}</div>
|
<div className="flex items-center gap-2">{priorityBadge(nextCard.priority)}</div>
|
||||||
<p className="text-sm text-zinc-400 line-clamp-2">{nextCard.content}</p>
|
<p className="text-sm text-muted line-clamp-2">{nextCard.content}</p>
|
||||||
<div className="text-[11px] text-zinc-500">Nächster Quick Dump…</div>
|
<div className="text-[11px] text-faint">Nächster Quick Dump…</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -321,19 +323,19 @@ export const TriageDeck: React.FC<TriageDeckProps> = ({
|
|||||||
initial={{ scale: 0.97, opacity: 0, y: 10 }}
|
initial={{ scale: 0.97, opacity: 0, y: 10 }}
|
||||||
animate={{ scale: 1, opacity: 1, y: 0 }}
|
animate={{ scale: 1, opacity: 1, y: 0 }}
|
||||||
exit={{ scale: 0.93, opacity: 0 }}
|
exit={{ scale: 0.93, opacity: 0 }}
|
||||||
className="relative w-full h-full bg-panel border border-border p-4 sm:p-5 cursor-grab active:cursor-grabbing flex flex-col justify-between overflow-hidden"
|
className="relative w-full h-full panel-surface p-4 sm:p-5 cursor-grab active:cursor-grabbing flex flex-col justify-between overflow-hidden"
|
||||||
>
|
>
|
||||||
{/* Dynamic Swipe Overlays */}
|
{/* Dynamic Swipe Overlays */}
|
||||||
<motion.div
|
<motion.div
|
||||||
style={{ opacity: keepOpacity }}
|
style={{ opacity: keepOpacity }}
|
||||||
className="pointer-events-none absolute top-3.5 right-3.5 px-2.5 py-1 bg-accent/20 border border-accent text-accent font-semibold text-xs tracking-wide uppercase"
|
className="pointer-events-none absolute top-3.5 right-3.5 px-2.5 py-1 bg-accent-dim border border-accent rounded-btn text-accent font-semibold text-xs tracking-wide uppercase"
|
||||||
>
|
>
|
||||||
Als Aufgabe 👉
|
Als Aufgabe 👉
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
<motion.div
|
<motion.div
|
||||||
style={{ opacity: discardOpacity }}
|
style={{ opacity: discardOpacity }}
|
||||||
className="pointer-events-none absolute top-3.5 left-3.5 px-2.5 py-1 bg-white/10 border border-white/30 text-zinc-200 font-semibold text-xs tracking-wide uppercase"
|
className="pointer-events-none absolute top-3.5 left-3.5 px-2.5 py-1 bg-panel-elevated border border-strong rounded-btn text-main font-semibold text-xs tracking-wide uppercase"
|
||||||
>
|
>
|
||||||
👈 Wegwerfen
|
👈 Wegwerfen
|
||||||
</motion.div>
|
</motion.div>
|
||||||
@@ -352,12 +354,12 @@ export const TriageDeck: React.FC<TriageDeckProps> = ({
|
|||||||
setIsEditingCard((prev) => !prev);
|
setIsEditingCard((prev) => !prev);
|
||||||
}}
|
}}
|
||||||
title="Quick Dump Text vor dem Zuordnen anpassen"
|
title="Quick Dump Text vor dem Zuordnen anpassen"
|
||||||
className="h-6 px-2 bg-canvas hover:bg-panel-hover border border-border text-[11px] text-zinc-400 hover:text-white flex items-center gap-1"
|
className="h-6 px-2 bg-canvas hover:bg-panel-hover border border-border rounded-btn text-[11px] text-muted hover:text-main flex items-center gap-1"
|
||||||
>
|
>
|
||||||
<Edit3 className="w-3 h-3" />
|
<Edit3 className="w-3 h-3" />
|
||||||
<span>Bearbeiten</span>
|
<span>Bearbeiten</span>
|
||||||
</button>
|
</button>
|
||||||
<span className="text-xs text-zinc-400 flex items-center gap-1">
|
<span className="text-xs text-muted flex items-center gap-1">
|
||||||
<Clock className="w-3 h-3" />
|
<Clock className="w-3 h-3" />
|
||||||
{new Date(activeCard.createdAt).toLocaleString('de-DE', {
|
{new Date(activeCard.createdAt).toLocaleString('de-DE', {
|
||||||
day: '2-digit',
|
day: '2-digit',
|
||||||
@@ -378,7 +380,7 @@ export const TriageDeck: React.FC<TriageDeckProps> = ({
|
|||||||
onChange={(e) => setEditDraft(e.target.value)}
|
onChange={(e) => setEditDraft(e.target.value)}
|
||||||
rows={3}
|
rows={3}
|
||||||
autoFocus
|
autoFocus
|
||||||
className="w-full input-field p-2 text-sm text-white resize-none"
|
className="w-full input-field p-2 text-sm resize-none"
|
||||||
/>
|
/>
|
||||||
<div className="flex items-center justify-end gap-1.5">
|
<div className="flex items-center justify-end gap-1.5">
|
||||||
<button
|
<button
|
||||||
@@ -398,7 +400,7 @@ export const TriageDeck: React.FC<TriageDeckProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<p className="text-base sm:text-xl font-medium text-white leading-relaxed break-words">
|
<p className="text-base sm:text-xl font-medium text-main leading-relaxed break-words">
|
||||||
{activeCard.content}
|
{activeCard.content}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
@@ -414,14 +416,14 @@ export const TriageDeck: React.FC<TriageDeckProps> = ({
|
|||||||
</span>
|
</span>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<span className="text-xs text-zinc-500">
|
<span className="text-xs text-faint">
|
||||||
Links wischen = Wegwerfen • Rechts wischen = Als Aufgabe sichern
|
Links wischen = Wegwerfen • Rechts wischen = Als Aufgabe sichern
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{activeCard.dueDate && (
|
{activeCard.dueDate && (
|
||||||
<span className="px-2 py-0.5 bg-canvas text-zinc-300 border border-border text-xs">
|
<span className="px-2 py-0.5 bg-canvas text-muted border border-border rounded-btn text-xs">
|
||||||
📅 {activeCard.dueDate}
|
📅 {activeCard.dueDate}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -432,8 +434,8 @@ export const TriageDeck: React.FC<TriageDeckProps> = ({
|
|||||||
|
|
||||||
{/* Convert to Task with Due Date Popover */}
|
{/* Convert to Task with Due Date Popover */}
|
||||||
{showDateModal && (
|
{showDateModal && (
|
||||||
<div className="p-3 bg-panel border border-border flex flex-wrap items-center justify-between gap-3">
|
<div className="p-3 panel-surface flex flex-wrap items-center justify-between gap-3">
|
||||||
<div className="flex items-center gap-2 text-xs text-zinc-300">
|
<div className="flex items-center gap-2 text-xs text-muted">
|
||||||
<Calendar className="w-4 h-4 text-accent" />
|
<Calendar className="w-4 h-4 text-accent" />
|
||||||
<span>Aufgabe mit Fälligkeitsdatum anlegen:</span>
|
<span>Aufgabe mit Fälligkeitsdatum anlegen:</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -456,8 +458,8 @@ export const TriageDeck: React.FC<TriageDeckProps> = ({
|
|||||||
|
|
||||||
{/* Convert to Shared List Item Popover */}
|
{/* Convert to Shared List Item Popover */}
|
||||||
{showListSelector && (
|
{showListSelector && (
|
||||||
<div className="p-3 bg-panel border border-border">
|
<div className="p-3 panel-surface">
|
||||||
<p className="text-xs text-zinc-400 mb-2">
|
<p className="text-xs text-muted mb-2">
|
||||||
Quick Dump direkt einer Liste zuordnen:
|
Quick Dump direkt einer Liste zuordnen:
|
||||||
</p>
|
</p>
|
||||||
<div className="flex flex-wrap gap-1.5">
|
<div className="flex flex-wrap gap-1.5">
|
||||||
@@ -475,7 +477,7 @@ export const TriageDeck: React.FC<TriageDeckProps> = ({
|
|||||||
</button>
|
</button>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<span className="text-xs text-zinc-400">Keine Listen vorhanden.</span>
|
<span className="text-xs text-muted">Keine Listen vorhanden.</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -489,7 +491,7 @@ export const TriageDeck: React.FC<TriageDeckProps> = ({
|
|||||||
title="Wegwerfen (Taste H oder Pfeil links)"
|
title="Wegwerfen (Taste H oder Pfeil links)"
|
||||||
className="btn-action h-8 px-2.5 justify-center"
|
className="btn-action h-8 px-2.5 justify-center"
|
||||||
>
|
>
|
||||||
<X className="w-3.5 h-3.5 text-zinc-400" />
|
<X className="w-3.5 h-3.5 text-muted" />
|
||||||
<span>Wegwerfen</span>
|
<span>Wegwerfen</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@@ -502,7 +504,7 @@ export const TriageDeck: React.FC<TriageDeckProps> = ({
|
|||||||
cards.length <= 1 ? 'opacity-40 cursor-not-allowed' : ''
|
cards.length <= 1 ? 'opacity-40 cursor-not-allowed' : ''
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<SkipForward className="w-3.5 h-3.5 text-zinc-400" />
|
<SkipForward className="w-3.5 h-3.5 text-muted" />
|
||||||
<span>Später</span>
|
<span>Später</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@@ -514,7 +516,7 @@ export const TriageDeck: React.FC<TriageDeckProps> = ({
|
|||||||
}}
|
}}
|
||||||
title="Auf eine geteilte Liste setzen"
|
title="Auf eine geteilte Liste setzen"
|
||||||
className={`btn-action h-8 px-2.5 justify-center ${
|
className={`btn-action h-8 px-2.5 justify-center ${
|
||||||
showListSelector ? 'border-accent text-accent bg-accent/20' : ''
|
showListSelector ? 'border-accent text-accent bg-accent-dim' : ''
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<ShoppingCart className="w-3.5 h-3.5 text-accent" />
|
<ShoppingCart className="w-3.5 h-3.5 text-accent" />
|
||||||
@@ -539,7 +541,7 @@ export const TriageDeck: React.FC<TriageDeckProps> = ({
|
|||||||
}}
|
}}
|
||||||
title="Als Aufgabe mit Fälligkeitsdatum (Taste T)"
|
title="Als Aufgabe mit Fälligkeitsdatum (Taste T)"
|
||||||
className={`btn-action h-8 px-2.5 justify-center ${
|
className={`btn-action h-8 px-2.5 justify-center ${
|
||||||
showDateModal ? 'border-accent text-accent bg-accent/20' : ''
|
showDateModal ? 'border-accent text-accent bg-accent-dim' : ''
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<Calendar className="w-3.5 h-3.5 text-accent" />
|
<Calendar className="w-3.5 h-3.5 text-accent" />
|
||||||
@@ -561,16 +563,16 @@ export const TriageDeck: React.FC<TriageDeckProps> = ({
|
|||||||
{cards.length > 1 && (
|
{cards.length > 1 && (
|
||||||
<div className="pt-2">
|
<div className="pt-2">
|
||||||
<div className="flex items-center justify-between mb-1.5 px-0.5">
|
<div className="flex items-center justify-between mb-1.5 px-0.5">
|
||||||
<span className="text-xs font-semibold uppercase tracking-wider text-zinc-400 flex items-center gap-1.5">
|
<span className="text-xs font-semibold uppercase tracking-wider text-muted flex items-center gap-1.5">
|
||||||
<Layers className="w-3.5 h-3.5 text-accent" />
|
<Layers className="w-3.5 h-3.5 text-accent" />
|
||||||
Warteschlange ({cards.length - 1} weitere Quick Dumps)
|
Warteschlange ({cards.length - 1} weitere Quick Dumps)
|
||||||
</span>
|
</span>
|
||||||
<span className="text-[11px] text-zinc-500">
|
<span className="text-[11px] text-faint">
|
||||||
Klicken zum Vorholen oder direkt zuordnen
|
Klicken zum Vorholen oder direkt zuordnen
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-panel border border-border divide-y divide-border max-h-60 overflow-y-auto">
|
<div className="panel-surface divide-y divide-border max-h-60 overflow-y-auto">
|
||||||
{cards.slice(1).map((item) => (
|
{cards.slice(1).map((item) => (
|
||||||
<div
|
<div
|
||||||
key={item.id}
|
key={item.id}
|
||||||
@@ -581,7 +583,7 @@ export const TriageDeck: React.FC<TriageDeckProps> = ({
|
|||||||
className="flex-1 min-w-0 cursor-pointer flex items-center gap-2"
|
className="flex-1 min-w-0 cursor-pointer flex items-center gap-2"
|
||||||
>
|
>
|
||||||
<span className="w-1.5 h-1.5 bg-accent flex-shrink-0" />
|
<span className="w-1.5 h-1.5 bg-accent flex-shrink-0" />
|
||||||
<span className="text-xs text-zinc-200 truncate group-hover:text-white">
|
<span className="text-xs text-muted truncate group-hover:text-main">
|
||||||
{item.content}
|
{item.content}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -593,7 +595,7 @@ export const TriageDeck: React.FC<TriageDeckProps> = ({
|
|||||||
handleAction('keep', { convertTo: 'task', thoughtOverride: item })
|
handleAction('keep', { convertTo: 'task', thoughtOverride: item })
|
||||||
}
|
}
|
||||||
title="Direkt zur Aufgabe machen"
|
title="Direkt zur Aufgabe machen"
|
||||||
className="h-6 px-2 bg-canvas hover:bg-accent hover:text-white border border-border text-[11px] text-zinc-300 flex items-center gap-1 transition-colors"
|
className="h-6 px-2 bg-canvas hover:bg-accent hover:text-on-accent border border-border rounded-btn text-[11px] text-muted flex items-center gap-1 transition-colors"
|
||||||
>
|
>
|
||||||
<Check className="w-3 h-3" />
|
<Check className="w-3 h-3" />
|
||||||
<span className="hidden sm:inline">Aufgabe</span>
|
<span className="hidden sm:inline">Aufgabe</span>
|
||||||
@@ -605,7 +607,7 @@ export const TriageDeck: React.FC<TriageDeckProps> = ({
|
|||||||
handleAction('keep', { convertTo: 'document', thoughtOverride: item })
|
handleAction('keep', { convertTo: 'document', thoughtOverride: item })
|
||||||
}
|
}
|
||||||
title="Direkt als Workspace-Notiz anlegen"
|
title="Direkt als Workspace-Notiz anlegen"
|
||||||
className="h-6 px-2 bg-canvas hover:bg-panel-elevated border border-border text-[11px] text-zinc-300 flex items-center gap-1 transition-colors"
|
className="h-6 px-2 bg-canvas hover:bg-panel-elevated border border-border rounded-btn text-[11px] text-muted hover:text-main flex items-center gap-1 transition-colors"
|
||||||
>
|
>
|
||||||
<FileText className="w-3 h-3 text-accent" />
|
<FileText className="w-3 h-3 text-accent" />
|
||||||
<span className="hidden sm:inline">Notiz</span>
|
<span className="hidden sm:inline">Notiz</span>
|
||||||
@@ -622,7 +624,7 @@ export const TriageDeck: React.FC<TriageDeckProps> = ({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
title={`Direkt auf Liste "${lists[0].title}" setzen`}
|
title={`Direkt auf Liste "${lists[0].title}" setzen`}
|
||||||
className="h-6 px-2 bg-canvas hover:bg-panel-elevated border border-border text-[11px] text-zinc-300 flex items-center gap-1 transition-colors"
|
className="h-6 px-2 bg-canvas hover:bg-panel-elevated border border-border rounded-btn text-[11px] text-muted hover:text-main flex items-center gap-1 transition-colors"
|
||||||
>
|
>
|
||||||
<ShoppingCart className="w-3 h-3 text-accent" />
|
<ShoppingCart className="w-3 h-3 text-accent" />
|
||||||
<span className="hidden md:inline">{lists[0].title}</span>
|
<span className="hidden md:inline">{lists[0].title}</span>
|
||||||
@@ -633,7 +635,7 @@ export const TriageDeck: React.FC<TriageDeckProps> = ({
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() => handleAction('discard', { thoughtOverride: item })}
|
onClick={() => handleAction('discard', { thoughtOverride: item })}
|
||||||
title="Wegwerfen"
|
title="Wegwerfen"
|
||||||
className="h-6 w-6 bg-canvas hover:bg-panel-elevated border border-border text-zinc-400 hover:text-white flex items-center justify-center transition-colors"
|
className="h-6 w-6 bg-canvas hover:bg-panel-elevated border border-border rounded-btn text-muted hover:text-main flex items-center justify-center transition-colors"
|
||||||
>
|
>
|
||||||
<X className="w-3 h-3" />
|
<X className="w-3 h-3" />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -6,15 +6,12 @@ import {
|
|||||||
FolderOpen,
|
FolderOpen,
|
||||||
ChevronRight,
|
ChevronRight,
|
||||||
ChevronDown,
|
ChevronDown,
|
||||||
FileText,
|
|
||||||
Search,
|
Search,
|
||||||
Pin,
|
Pin,
|
||||||
Trash2,
|
Trash2,
|
||||||
Download,
|
Download,
|
||||||
CheckSquare,
|
CheckSquare,
|
||||||
Heading1,
|
|
||||||
Heading2,
|
Heading2,
|
||||||
Heading3,
|
|
||||||
List,
|
List,
|
||||||
Code,
|
Code,
|
||||||
Sparkles,
|
Sparkles,
|
||||||
@@ -389,10 +386,10 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (target) setSelectedDoc(target);
|
if (target) setSelectedDoc(target);
|
||||||
}}
|
}}
|
||||||
className={`inline-flex items-center gap-1 px-1.5 py-0.2 mx-0.5 border text-xs font-medium cursor-pointer transition-colors ${
|
className={`inline-flex items-center gap-1 px-1.5 py-0.2 mx-0.5 border text-xs font-medium cursor-pointer rounded-btn transition-colors ${
|
||||||
target
|
target
|
||||||
? 'bg-accent/20 border-accent/40 text-accent hover:bg-accent/30'
|
? 'bg-accent/20 border-accent/40 text-accent hover:bg-accent/30'
|
||||||
: 'bg-zinc-800 border-zinc-700 text-zinc-400 hover:text-white'
|
: 'badge-neutral hover:text-main'
|
||||||
}`}
|
}`}
|
||||||
title={target ? `Öffne: ${target.title}` : `Dokument "${linkTitle}" existiert noch nicht`}
|
title={target ? `Öffne: ${target.title}` : `Dokument "${linkTitle}" existiert noch nicht`}
|
||||||
>
|
>
|
||||||
@@ -467,8 +464,8 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
style={{ paddingLeft: `${depth * 14 + 16}px` }}
|
style={{ paddingLeft: `${depth * 14 + 16}px` }}
|
||||||
className={`group flex items-center justify-between py-1.5 pr-2 border-l-2 transition-colors cursor-pointer text-xs ${
|
className={`group flex items-center justify-between py-1.5 pr-2 border-l-2 transition-colors cursor-pointer text-xs ${
|
||||||
isSelected
|
isSelected
|
||||||
? 'bg-panel-elevated border-accent text-white font-medium'
|
? 'bg-panel-elevated border-accent text-main font-medium'
|
||||||
: 'border-transparent text-zinc-400 hover:text-white hover:bg-white/[0.02]'
|
: 'border-transparent text-muted hover:text-main hover:bg-canvas/50'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2 min-w-0 flex-1">
|
<div className="flex items-center gap-2 min-w-0 flex-1">
|
||||||
@@ -490,10 +487,10 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
<div
|
<div
|
||||||
onClick={() => toggleFolder(node.path)}
|
onClick={() => toggleFolder(node.path)}
|
||||||
style={{ paddingLeft: `${node.depth * 14 + 8}px` }}
|
style={{ paddingLeft: `${node.depth * 14 + 8}px` }}
|
||||||
className="group flex items-center justify-between py-1.5 pr-2 hover:bg-panel-elevated cursor-pointer transition-colors text-xs text-zinc-300"
|
className="group flex items-center justify-between py-1.5 pr-2 hover:bg-panel-elevated cursor-pointer transition-colors text-xs text-muted"
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-1.5 min-w-0">
|
<div className="flex items-center gap-1.5 min-w-0">
|
||||||
<span className="text-zinc-500 hover:text-zinc-300">
|
<span className="text-faint hover:text-muted">
|
||||||
{isExpanded ? (
|
{isExpanded ? (
|
||||||
<ChevronDown className="w-3 h-3 flex-shrink-0" />
|
<ChevronDown className="w-3 h-3 flex-shrink-0" />
|
||||||
) : (
|
) : (
|
||||||
@@ -503,10 +500,10 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
{isExpanded ? (
|
{isExpanded ? (
|
||||||
<FolderOpen className="w-3.5 h-3.5 text-accent flex-shrink-0" />
|
<FolderOpen className="w-3.5 h-3.5 text-accent flex-shrink-0" />
|
||||||
) : (
|
) : (
|
||||||
<Folder className="w-3.5 h-3.5 text-zinc-400 flex-shrink-0" />
|
<Folder className="w-3.5 h-3.5 text-muted flex-shrink-0" />
|
||||||
)}
|
)}
|
||||||
<span className="font-medium text-xs text-zinc-200 truncate">{node.name}</span>
|
<span className="font-medium text-xs text-main truncate">{node.name}</span>
|
||||||
<span className="text-[10px] text-zinc-500 font-mono">({totalItems})</span>
|
<span className="text-[10px] text-faint font-mono">({totalItems})</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="opacity-0 group-hover:opacity-100 flex items-center gap-1 transition-opacity">
|
<div className="opacity-0 group-hover:opacity-100 flex items-center gap-1 transition-opacity">
|
||||||
@@ -516,7 +513,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
handleCreateDoc(node.path);
|
handleCreateDoc(node.path);
|
||||||
}}
|
}}
|
||||||
className="p-1 hover:bg-panel-hover text-zinc-400 hover:text-white"
|
className="p-1 hover:bg-panel-elevated text-muted hover:text-main rounded-btn"
|
||||||
title="Neue Datei in diesem Ordner"
|
title="Neue Datei in diesem Ordner"
|
||||||
>
|
>
|
||||||
<FilePlus className="w-3 h-3" />
|
<FilePlus className="w-3 h-3" />
|
||||||
@@ -527,7 +524,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
handleStartCreateFolder(node.path);
|
handleStartCreateFolder(node.path);
|
||||||
}}
|
}}
|
||||||
className="p-1 hover:bg-panel-hover text-zinc-400 hover:text-white"
|
className="p-1 hover:bg-panel-elevated text-muted hover:text-main rounded-btn"
|
||||||
title="Neuer Unterordner"
|
title="Neuer Unterordner"
|
||||||
>
|
>
|
||||||
<FolderPlus className="w-3 h-3" />
|
<FolderPlus className="w-3 h-3" />
|
||||||
@@ -540,7 +537,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
style={{ paddingLeft: `${(node.depth + 1) * 14 + 8}px` }}
|
style={{ paddingLeft: `${(node.depth + 1) * 14 + 8}px` }}
|
||||||
className="py-1 pr-2 flex items-center gap-1 bg-canvas"
|
className="py-1 pr-2 flex items-center gap-1 bg-canvas"
|
||||||
>
|
>
|
||||||
<Folder className="w-3 h-3 text-zinc-400 flex-shrink-0" />
|
<Folder className="w-3 h-3 text-muted flex-shrink-0" />
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
autoFocus
|
autoFocus
|
||||||
@@ -556,7 +553,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleConfirmCreateFolder}
|
onClick={handleConfirmCreateFolder}
|
||||||
className="p-1 text-accent hover:text-white"
|
className="p-1 text-accent hover:text-main rounded-btn"
|
||||||
title="Bestätigen"
|
title="Bestätigen"
|
||||||
>
|
>
|
||||||
<Check className="w-3.5 h-3.5" />
|
<Check className="w-3.5 h-3.5" />
|
||||||
@@ -564,7 +561,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setIsCreatingFolder(false)}
|
onClick={() => setIsCreatingFolder(false)}
|
||||||
className="p-1 text-zinc-400 hover:text-white"
|
className="p-1 text-muted hover:text-main rounded-btn"
|
||||||
title="Abbrechen"
|
title="Abbrechen"
|
||||||
>
|
>
|
||||||
<X className="w-3.5 h-3.5" />
|
<X className="w-3.5 h-3.5" />
|
||||||
@@ -579,7 +576,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
{node.subfolders.length === 0 && node.docs.length === 0 && !isCreatingSubfolder && (
|
{node.subfolders.length === 0 && node.docs.length === 0 && !isCreatingSubfolder && (
|
||||||
<div
|
<div
|
||||||
style={{ paddingLeft: `${(node.depth + 1) * 14 + 14}px` }}
|
style={{ paddingLeft: `${(node.depth + 1) * 14 + 14}px` }}
|
||||||
className="py-1 text-[11px] text-zinc-600 italic"
|
className="py-1 text-[11px] text-faint italic"
|
||||||
>
|
>
|
||||||
(leer)
|
(leer)
|
||||||
</div>
|
</div>
|
||||||
@@ -598,13 +595,13 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
<div className="lg:col-span-4 panel-surface flex flex-col self-start">
|
<div className="lg:col-span-4 panel-surface flex flex-col self-start">
|
||||||
<div className="p-2 panel-header space-y-2">
|
<div className="p-2 panel-header space-y-2">
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Search className="w-3.5 h-3.5 text-zinc-400 absolute left-2.5 top-1/2 -translate-y-1/2" />
|
<Search className="w-3.5 h-3.5 text-muted absolute left-2.5 top-1/2 -translate-y-1/2" />
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={searchQuery}
|
value={searchQuery}
|
||||||
onChange={(e) => setSearchQuery(e.target.value)}
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
placeholder="Dateien durchsuchen…"
|
placeholder="Dateien durchsuchen…"
|
||||||
className="w-full h-7 pl-7 pr-2 input-field"
|
className="w-full h-7 pl-7 pr-2 input-field text-xs"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -615,7 +612,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
className="flex-1 btn-action"
|
className="flex-1 btn-action"
|
||||||
title="Neue Datei erstellen"
|
title="Neue Datei erstellen"
|
||||||
>
|
>
|
||||||
<FilePlus className="w-3.5 h-3.5 text-zinc-400" />
|
<FilePlus className="w-3.5 h-3.5 text-muted" />
|
||||||
<span>Neue Datei</span>
|
<span>Neue Datei</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@@ -625,7 +622,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
className="flex-1 btn-action"
|
className="flex-1 btn-action"
|
||||||
title="Neuen Ordner erstellen"
|
title="Neuen Ordner erstellen"
|
||||||
>
|
>
|
||||||
<FolderPlus className="w-3.5 h-3.5 text-zinc-400" />
|
<FolderPlus className="w-3.5 h-3.5 text-muted" />
|
||||||
<span>Neuer Ordner</span>
|
<span>Neuer Ordner</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -633,7 +630,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
|
|
||||||
{isCreatingFolder && newFolderParent === null && (
|
{isCreatingFolder && newFolderParent === null && (
|
||||||
<div className="p-2 panel-header bg-canvas flex items-center gap-1.5">
|
<div className="p-2 panel-header bg-canvas flex items-center gap-1.5">
|
||||||
<Folder className="w-3.5 h-3.5 text-zinc-400 flex-shrink-0" />
|
<Folder className="w-3.5 h-3.5 text-muted flex-shrink-0" />
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
autoFocus
|
autoFocus
|
||||||
@@ -644,12 +641,12 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
if (e.key === 'Escape') setIsCreatingFolder(false);
|
if (e.key === 'Escape') setIsCreatingFolder(false);
|
||||||
}}
|
}}
|
||||||
placeholder="Neuer Ordnername…"
|
placeholder="Neuer Ordnername…"
|
||||||
className="flex-1 h-6 px-1.5 input-field"
|
className="flex-1 h-6 px-1.5 input-field text-xs"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleConfirmCreateFolder}
|
onClick={handleConfirmCreateFolder}
|
||||||
className="p-1 text-accent hover:text-white"
|
className="p-1 text-accent hover:text-main rounded-btn"
|
||||||
title="Bestätigen"
|
title="Bestätigen"
|
||||||
>
|
>
|
||||||
<Check className="w-3.5 h-3.5" />
|
<Check className="w-3.5 h-3.5" />
|
||||||
@@ -657,7 +654,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setIsCreatingFolder(false)}
|
onClick={() => setIsCreatingFolder(false)}
|
||||||
className="p-1 text-zinc-400 hover:text-white"
|
className="p-1 text-muted hover:text-main rounded-btn"
|
||||||
title="Abbrechen"
|
title="Abbrechen"
|
||||||
>
|
>
|
||||||
<X className="w-3.5 h-3.5" />
|
<X className="w-3.5 h-3.5" />
|
||||||
@@ -666,10 +663,10 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{isEmpty ? (
|
{isEmpty ? (
|
||||||
<div className="p-6 my-3 mx-2 border border-dashed border-border bg-canvas/50 text-center">
|
<div className="p-6 my-3 mx-2 border border-dashed border-border bg-canvas/50 text-center rounded-card">
|
||||||
<Folder className="w-7 h-7 text-zinc-600 mx-auto mb-2" />
|
<Folder className="w-7 h-7 text-faint mx-auto mb-2" />
|
||||||
<h5 className="text-xs font-semibold text-zinc-300 mb-1">Keine Dateien vorhanden</h5>
|
<h5 className="text-xs font-semibold text-muted mb-1">Keine Dateien vorhanden</h5>
|
||||||
<p className="text-[11px] text-zinc-500 mb-3 max-w-xs mx-auto leading-relaxed">
|
<p className="text-[11px] text-faint mb-3 max-w-xs mx-auto leading-relaxed">
|
||||||
Erstelle eine neue Datei oder lege Ordner an, um deine Dokumente übersichtlich zu strukturieren.
|
Erstelle eine neue Datei oder lege Ordner an, um deine Dokumente übersichtlich zu strukturieren.
|
||||||
</p>
|
</p>
|
||||||
<div className="flex items-center justify-center gap-2">
|
<div className="flex items-center justify-center gap-2">
|
||||||
@@ -678,7 +675,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
onClick={() => handleCreateDoc(null)}
|
onClick={() => handleCreateDoc(null)}
|
||||||
className="btn-action"
|
className="btn-action"
|
||||||
>
|
>
|
||||||
<FilePlus className="w-3.5 h-3.5 text-zinc-400" />
|
<FilePlus className="w-3.5 h-3.5 text-muted" />
|
||||||
<span>Neue Datei</span>
|
<span>Neue Datei</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@@ -686,7 +683,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
onClick={() => handleStartCreateFolder(null)}
|
onClick={() => handleStartCreateFolder(null)}
|
||||||
className="btn-action"
|
className="btn-action"
|
||||||
>
|
>
|
||||||
<FolderPlus className="w-3.5 h-3.5 text-zinc-400" />
|
<FolderPlus className="w-3.5 h-3.5 text-muted" />
|
||||||
<span>Neuer Ordner</span>
|
<span>Neuer Ordner</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -696,7 +693,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
{rootTree.map(renderFolder)}
|
{rootTree.map(renderFolder)}
|
||||||
{rootDocs.map((doc) => renderDocItem(doc, 0))}
|
{rootDocs.map((doc) => renderDocItem(doc, 0))}
|
||||||
{filteredDocs.length === 0 && searchQuery && (
|
{filteredDocs.length === 0 && searchQuery && (
|
||||||
<div className="p-4 text-center text-xs text-zinc-500">
|
<div className="p-4 text-center text-xs text-faint">
|
||||||
Keine Dateien gefunden für "{searchQuery}"
|
Keine Dateien gefunden für "{searchQuery}"
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -710,14 +707,14 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
<div className="p-4 panel-surface space-y-3">
|
<div className="p-4 panel-surface space-y-3">
|
||||||
{/* Document Header & Metadata */}
|
{/* Document Header & Metadata */}
|
||||||
<div className="flex flex-wrap items-center justify-between gap-3 pb-3 panel-header">
|
<div className="flex flex-wrap items-center justify-between gap-3 pb-3 panel-header">
|
||||||
<div className="flex flex-wrap items-center gap-2 text-xs text-zinc-400">
|
<div className="flex flex-wrap items-center gap-2 text-xs text-muted">
|
||||||
<span className="badge-accent">
|
<span className="badge-accent">
|
||||||
<Check className="w-3 h-3" />
|
<Check className="w-3 h-3" />
|
||||||
{saveState === 'saving' ? 'Speichert…' : 'Gespeichert'}
|
{saveState === 'saving' ? 'Speichert…' : 'Gespeichert'}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
<Folder className="w-3.5 h-3.5 text-zinc-500" />
|
<Folder className="w-3.5 h-3.5 text-faint" />
|
||||||
<select
|
<select
|
||||||
value={getDocFolder(selectedDoc)}
|
value={getDocFolder(selectedDoc)}
|
||||||
onChange={(e) => handleChangeDocFolder(e.target.value)}
|
onChange={(e) => handleChangeDocFolder(e.target.value)}
|
||||||
@@ -773,14 +770,14 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setShowIconPicker((prev) => !prev)}
|
onClick={() => setShowIconPicker((prev) => !prev)}
|
||||||
className="w-10 h-10 panel-surface hover:bg-panel-elevated text-xl flex items-center justify-center flex-shrink-0 transition-colors"
|
className="w-10 h-10 panel-surface hover:bg-panel-elevated text-xl flex items-center justify-center flex-shrink-0 transition-colors rounded-card"
|
||||||
title="Icon ändern"
|
title="Icon ändern"
|
||||||
>
|
>
|
||||||
{selectedDoc.icon}
|
{selectedDoc.icon}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{showIconPicker && (
|
{showIconPicker && (
|
||||||
<div className="absolute top-12 left-0 z-20 p-2.5 panel-surface flex flex-wrap gap-1 w-64 shadow-xl border border-border">
|
<div className="absolute top-12 left-0 z-20 p-2.5 panel-surface flex flex-wrap gap-1 w-64 shadow-panel border border-border rounded-card">
|
||||||
{ICONS.map((ic) => (
|
{ICONS.map((ic) => (
|
||||||
<button
|
<button
|
||||||
key={ic}
|
key={ic}
|
||||||
@@ -789,7 +786,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
persistDocument({ ...selectedDoc, icon: ic });
|
persistDocument({ ...selectedDoc, icon: ic });
|
||||||
setShowIconPicker(false);
|
setShowIconPicker(false);
|
||||||
}}
|
}}
|
||||||
className="w-8 h-8 hover:bg-panel-elevated text-base flex items-center justify-center transition-colors"
|
className="w-8 h-8 hover:bg-panel-elevated text-base flex items-center justify-center rounded-btn transition-colors"
|
||||||
>
|
>
|
||||||
{ic}
|
{ic}
|
||||||
</button>
|
</button>
|
||||||
@@ -802,17 +799,17 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
value={selectedDoc.title}
|
value={selectedDoc.title}
|
||||||
onChange={(e) => persistDocument({ ...selectedDoc, title: e.target.value })}
|
onChange={(e) => persistDocument({ ...selectedDoc, title: e.target.value })}
|
||||||
placeholder="Dokument-Titel…"
|
placeholder="Dokument-Titel…"
|
||||||
className="w-full bg-transparent text-xl sm:text-2xl font-bold text-white focus:outline-none"
|
className="w-full bg-transparent text-xl sm:text-2xl font-bold text-main focus:outline-none placeholder:text-faint"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* NOTION / WORD RICH FORMATTING TOOLBAR */}
|
{/* NOTION / WORD RICH FORMATTING TOOLBAR */}
|
||||||
<div className="p-1.5 bg-canvas border border-border flex flex-wrap items-center gap-1 text-xs">
|
<div className="p-1.5 bg-canvas border border-border rounded-card flex flex-wrap items-center gap-1 text-xs">
|
||||||
{/* Text styling buttons */}
|
{/* Text styling buttons */}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => applyInlineFormat('**')}
|
onClick={() => applyInlineFormat('**')}
|
||||||
className="p-1 hover:bg-panel text-zinc-300 hover:text-white"
|
className="p-1 hover:bg-panel-elevated text-muted hover:text-main rounded-btn"
|
||||||
title="Fett (**text**)"
|
title="Fett (**text**)"
|
||||||
>
|
>
|
||||||
<Bold className="w-3.5 h-3.5" />
|
<Bold className="w-3.5 h-3.5" />
|
||||||
@@ -820,7 +817,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => applyInlineFormat('*')}
|
onClick={() => applyInlineFormat('*')}
|
||||||
className="p-1 hover:bg-panel text-zinc-300 hover:text-white"
|
className="p-1 hover:bg-panel-elevated text-muted hover:text-main rounded-btn"
|
||||||
title="Kursiv (*text*)"
|
title="Kursiv (*text*)"
|
||||||
>
|
>
|
||||||
<Italic className="w-3.5 h-3.5" />
|
<Italic className="w-3.5 h-3.5" />
|
||||||
@@ -828,7 +825,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => applyInlineFormat('~~')}
|
onClick={() => applyInlineFormat('~~')}
|
||||||
className="p-1 hover:bg-panel text-zinc-300 hover:text-white"
|
className="p-1 hover:bg-panel-elevated text-muted hover:text-main rounded-btn"
|
||||||
title="Durchgestrichen (~~text~~)"
|
title="Durchgestrichen (~~text~~)"
|
||||||
>
|
>
|
||||||
<Strikethrough className="w-3.5 h-3.5" />
|
<Strikethrough className="w-3.5 h-3.5" />
|
||||||
@@ -836,7 +833,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => applyInlineFormat('`')}
|
onClick={() => applyInlineFormat('`')}
|
||||||
className="p-1 hover:bg-panel text-zinc-300 hover:text-white"
|
className="p-1 hover:bg-panel-elevated text-muted hover:text-main rounded-btn"
|
||||||
title="Inline Code (`code`)"
|
title="Inline Code (`code`)"
|
||||||
>
|
>
|
||||||
<Code className="w-3.5 h-3.5" />
|
<Code className="w-3.5 h-3.5" />
|
||||||
@@ -844,7 +841,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => applyInlineFormat('[Link](', ')')}
|
onClick={() => applyInlineFormat('[Link](', ')')}
|
||||||
className="p-1 hover:bg-panel text-zinc-300 hover:text-white"
|
className="p-1 hover:bg-panel-elevated text-muted hover:text-main rounded-btn"
|
||||||
title="Link einfügen"
|
title="Link einfügen"
|
||||||
>
|
>
|
||||||
<Link className="w-3.5 h-3.5" />
|
<Link className="w-3.5 h-3.5" />
|
||||||
@@ -857,14 +854,14 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setShowWikiLinkMenu((prev) => !prev)}
|
onClick={() => setShowWikiLinkMenu((prev) => !prev)}
|
||||||
className="px-2 py-0.5 bg-panel border border-border text-[11px] text-accent flex items-center gap-1 hover:bg-panel-hover"
|
className="px-2 py-0.5 bg-panel border border-border text-[11px] text-accent flex items-center gap-1 hover:bg-panel-elevated rounded-btn"
|
||||||
title="Auf anderes Dokument verlinken ([[Dokumentname]])"
|
title="Auf anderes Dokument verlinken ([[Dokumentname]])"
|
||||||
>
|
>
|
||||||
<span>[[ Wiki-Link ]]</span>
|
<span>[[ Wiki-Link ]]</span>
|
||||||
</button>
|
</button>
|
||||||
{showWikiLinkMenu && (
|
{showWikiLinkMenu && (
|
||||||
<div className="absolute top-7 left-0 z-30 w-56 bg-panel border border-border p-1.5 shadow-2xl space-y-1">
|
<div className="absolute top-7 left-0 z-30 w-56 bg-panel border border-border p-1.5 shadow-panel rounded-card space-y-1">
|
||||||
<p className="text-[10px] text-zinc-400 px-1.5 pb-1 border-b border-border">
|
<p className="text-[10px] text-muted px-1.5 pb-1 border-b border-border">
|
||||||
Dokument zum Verlinken wählen:
|
Dokument zum Verlinken wählen:
|
||||||
</p>
|
</p>
|
||||||
<div className="max-h-48 overflow-y-auto">
|
<div className="max-h-48 overflow-y-auto">
|
||||||
@@ -875,14 +872,14 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
key={d.id}
|
key={d.id}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => insertWikiLink(d.title)}
|
onClick={() => insertWikiLink(d.title)}
|
||||||
className="w-full text-left px-2 py-1 text-xs text-zinc-300 hover:text-white hover:bg-panel-hover truncate flex items-center gap-1.5"
|
className="w-full text-left px-2 py-1 text-xs text-muted hover:text-main hover:bg-panel-elevated rounded-btn truncate flex items-center gap-1.5"
|
||||||
>
|
>
|
||||||
<span>{d.icon}</span>
|
<span>{d.icon}</span>
|
||||||
<span className="truncate">{d.title}</span>
|
<span className="truncate">{d.title}</span>
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
{docs.length <= 1 && (
|
{docs.length <= 1 && (
|
||||||
<div className="p-2 text-center text-[11px] text-zinc-500">
|
<div className="p-2 text-center text-[11px] text-faint">
|
||||||
Keine weiteren Dokumente vorhanden
|
Keine weiteren Dokumente vorhanden
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -903,7 +900,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
'| Spalte 1 | Spalte 2 | Spalte 3 |\n|---|---|---|\n| Inhalt A | Inhalt B | Inhalt C |\n| Wert 1 | Wert 2 | Wert 3 |'
|
'| Spalte 1 | Spalte 2 | Spalte 3 |\n|---|---|---|\n| Inhalt A | Inhalt B | Inhalt C |\n| Wert 1 | Wert 2 | Wert 3 |'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
className="px-2 py-0.5 bg-panel border border-border text-[11px] text-zinc-300 hover:text-white flex items-center gap-1"
|
className="px-2 py-0.5 bg-panel border border-border text-[11px] text-muted hover:text-main rounded-btn flex items-center gap-1"
|
||||||
title="Tabelle einfügen"
|
title="Tabelle einfügen"
|
||||||
>
|
>
|
||||||
<TableIcon className="w-3 h-3 text-accent" />
|
<TableIcon className="w-3 h-3 text-accent" />
|
||||||
@@ -913,7 +910,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => addBlock('progress', undefined, '50')}
|
onClick={() => addBlock('progress', undefined, '50')}
|
||||||
className="px-2 py-0.5 bg-panel border border-border text-[11px] text-zinc-300 hover:text-white flex items-center gap-1"
|
className="px-2 py-0.5 bg-panel border border-border text-[11px] text-muted hover:text-main rounded-btn flex items-center gap-1"
|
||||||
title="Fortschrittsbalken einfügen"
|
title="Fortschrittsbalken einfügen"
|
||||||
>
|
>
|
||||||
<Percent className="w-3 h-3 text-accent" />
|
<Percent className="w-3 h-3 text-accent" />
|
||||||
@@ -923,7 +920,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => addBlock('divider')}
|
onClick={() => addBlock('divider')}
|
||||||
className="p-1 hover:bg-panel text-zinc-400 hover:text-white"
|
className="p-1 hover:bg-panel-elevated text-muted hover:text-main rounded-btn"
|
||||||
title="Trennlinie"
|
title="Trennlinie"
|
||||||
>
|
>
|
||||||
<Minus className="w-3.5 h-3.5" />
|
<Minus className="w-3.5 h-3.5" />
|
||||||
@@ -946,7 +943,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
value={block.content}
|
value={block.content}
|
||||||
onChange={(e) => updateBlock(block.id, { content: e.target.value })}
|
onChange={(e) => updateBlock(block.id, { content: e.target.value })}
|
||||||
placeholder="Überschrift 1…"
|
placeholder="Überschrift 1…"
|
||||||
className="w-full bg-transparent text-xl font-bold text-white focus:outline-none py-1"
|
className="w-full bg-transparent text-xl font-bold text-main focus:outline-none py-1 placeholder:text-faint"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -957,7 +954,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
value={block.content}
|
value={block.content}
|
||||||
onChange={(e) => updateBlock(block.id, { content: e.target.value })}
|
onChange={(e) => updateBlock(block.id, { content: e.target.value })}
|
||||||
placeholder="Abschnitts-Überschrift…"
|
placeholder="Abschnitts-Überschrift…"
|
||||||
className="w-full bg-transparent text-base font-semibold text-accent focus:outline-none pt-2 pb-1"
|
className="w-full bg-transparent text-base font-semibold text-accent focus:outline-none pt-2 pb-1 placeholder:text-faint"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -968,7 +965,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
value={block.content}
|
value={block.content}
|
||||||
onChange={(e) => updateBlock(block.id, { content: e.target.value })}
|
onChange={(e) => updateBlock(block.id, { content: e.target.value })}
|
||||||
placeholder="Unterabschnitt…"
|
placeholder="Unterabschnitt…"
|
||||||
className="w-full bg-transparent text-sm font-semibold text-zinc-200 focus:outline-none pt-1"
|
className="w-full bg-transparent text-sm font-semibold text-main focus:outline-none pt-1 placeholder:text-faint"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -980,10 +977,10 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
value={block.content}
|
value={block.content}
|
||||||
onChange={(e) => updateBlock(block.id, { content: e.target.value })}
|
onChange={(e) => updateBlock(block.id, { content: e.target.value })}
|
||||||
placeholder="Text schreiben… (nutze [[Dokument]] für Wiki-Links)"
|
placeholder="Text schreiben… (nutze [[Dokument]] für Wiki-Links)"
|
||||||
className="w-full bg-transparent text-xs sm:text-sm text-zinc-200 leading-relaxed focus:outline-none resize-none font-normal"
|
className="w-full bg-transparent text-xs sm:text-sm text-main leading-relaxed focus:outline-none resize-none font-normal placeholder:text-faint"
|
||||||
/>
|
/>
|
||||||
{block.content.includes('[[') && (
|
{block.content.includes('[[') && (
|
||||||
<div className="text-xs text-zinc-400 pt-0.5">
|
<div className="text-xs text-muted pt-0.5">
|
||||||
{renderTextWithWikiLinks(block.content)}
|
{renderTextWithWikiLinks(block.content)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -997,7 +994,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={!!block.checked}
|
checked={!!block.checked}
|
||||||
onChange={(e) => updateBlock(block.id, { checked: e.target.checked })}
|
onChange={(e) => updateBlock(block.id, { checked: e.target.checked })}
|
||||||
className="w-3.5 h-3.5 accent-accent cursor-pointer"
|
className="w-4 h-4 checkbox-square cursor-pointer"
|
||||||
/>
|
/>
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<input
|
<input
|
||||||
@@ -1005,12 +1002,12 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
value={block.content}
|
value={block.content}
|
||||||
onChange={(e) => updateBlock(block.id, { content: e.target.value })}
|
onChange={(e) => updateBlock(block.id, { content: e.target.value })}
|
||||||
placeholder="To-Do Punkt…"
|
placeholder="To-Do Punkt…"
|
||||||
className={`w-full bg-transparent text-xs sm:text-sm focus:outline-none ${
|
className={`w-full bg-transparent text-xs sm:text-sm focus:outline-none placeholder:text-faint ${
|
||||||
block.checked ? 'line-through text-zinc-500' : 'text-white'
|
block.checked ? 'line-through text-faint' : 'text-main'
|
||||||
}`}
|
}`}
|
||||||
/>
|
/>
|
||||||
{block.content.includes('[[') && (
|
{block.content.includes('[[') && (
|
||||||
<div className="text-xs text-zinc-400">
|
<div className="text-xs text-muted">
|
||||||
{renderTextWithWikiLinks(block.content)}
|
{renderTextWithWikiLinks(block.content)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -1021,17 +1018,17 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
{/* BULLET */}
|
{/* BULLET */}
|
||||||
{block.type === 'bullet' && (
|
{block.type === 'bullet' && (
|
||||||
<div className="flex items-center gap-2.5 py-0.5">
|
<div className="flex items-center gap-2.5 py-0.5">
|
||||||
<span className="w-1.5 h-1.5 bg-accent flex-shrink-0" />
|
<span className="w-1.5 h-1.5 bg-accent rounded-full flex-shrink-0" />
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={block.content}
|
value={block.content}
|
||||||
onChange={(e) => updateBlock(block.id, { content: e.target.value })}
|
onChange={(e) => updateBlock(block.id, { content: e.target.value })}
|
||||||
placeholder="Listenpunkt…"
|
placeholder="Listenpunkt…"
|
||||||
className="w-full bg-transparent text-xs sm:text-sm text-zinc-200 focus:outline-none"
|
className="w-full bg-transparent text-xs sm:text-sm text-main focus:outline-none placeholder:text-faint"
|
||||||
/>
|
/>
|
||||||
{block.content.includes('[[') && (
|
{block.content.includes('[[') && (
|
||||||
<div className="text-xs text-zinc-400">
|
<div className="text-xs text-muted">
|
||||||
{renderTextWithWikiLinks(block.content)}
|
{renderTextWithWikiLinks(block.content)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -1041,7 +1038,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
|
|
||||||
{/* CALLOUT */}
|
{/* CALLOUT */}
|
||||||
{block.type === 'callout' && (
|
{block.type === 'callout' && (
|
||||||
<div className="p-2.5 bg-canvas border border-accent/40 flex items-start gap-2">
|
<div className="p-2.5 bg-canvas border border-accent/40 rounded-card flex items-start gap-2">
|
||||||
<Sparkles className="w-3.5 h-3.5 text-accent flex-shrink-0 mt-0.5" />
|
<Sparkles className="w-3.5 h-3.5 text-accent flex-shrink-0 mt-0.5" />
|
||||||
<div className="flex-1 space-y-1">
|
<div className="flex-1 space-y-1">
|
||||||
<textarea
|
<textarea
|
||||||
@@ -1049,10 +1046,10 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
value={block.content}
|
value={block.content}
|
||||||
onChange={(e) => updateBlock(block.id, { content: e.target.value })}
|
onChange={(e) => updateBlock(block.id, { content: e.target.value })}
|
||||||
placeholder="Wichtiger Hinweis oder Zusammenfassung…"
|
placeholder="Wichtiger Hinweis oder Zusammenfassung…"
|
||||||
className="w-full bg-transparent text-xs sm:text-sm text-zinc-200 focus:outline-none resize-none"
|
className="w-full bg-transparent text-xs sm:text-sm text-main focus:outline-none resize-none placeholder:text-faint"
|
||||||
/>
|
/>
|
||||||
{block.content.includes('[[') && (
|
{block.content.includes('[[') && (
|
||||||
<div className="text-xs text-zinc-400">
|
<div className="text-xs text-muted">
|
||||||
{renderTextWithWikiLinks(block.content)}
|
{renderTextWithWikiLinks(block.content)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -1062,22 +1059,22 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
|
|
||||||
{/* CODE */}
|
{/* CODE */}
|
||||||
{block.type === 'code' && (
|
{block.type === 'code' && (
|
||||||
<div className="p-2.5 bg-canvas border border-border font-mono text-xs text-zinc-200">
|
<div className="p-2.5 code-block">
|
||||||
<textarea
|
<textarea
|
||||||
rows={Math.max(2, (block.content || '').split('\n').length)}
|
rows={Math.max(2, (block.content || '').split('\n').length)}
|
||||||
value={block.content}
|
value={block.content}
|
||||||
onChange={(e) => updateBlock(block.id, { content: e.target.value })}
|
onChange={(e) => updateBlock(block.id, { content: e.target.value })}
|
||||||
placeholder="// Code oder Shell-Kommando…"
|
placeholder="// Code oder Shell-Kommando…"
|
||||||
className="w-full bg-transparent focus:outline-none resize-none font-mono"
|
className="w-full bg-transparent focus:outline-none resize-none font-mono text-xs text-main placeholder:text-faint"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* TABLE */}
|
{/* TABLE */}
|
||||||
{block.type === 'table' && (
|
{block.type === 'table' && (
|
||||||
<div className="p-2.5 bg-canvas border border-border space-y-2">
|
<div className="p-2.5 bg-canvas border border-border rounded-card space-y-2">
|
||||||
<div className="flex items-center justify-between text-[11px] text-zinc-400 border-b border-border pb-1">
|
<div className="flex items-center justify-between text-[11px] text-muted border-b border-border pb-1">
|
||||||
<span className="flex items-center gap-1 font-semibold text-zinc-300">
|
<span className="flex items-center gap-1 font-semibold text-main">
|
||||||
<TableIcon className="w-3 h-3 text-accent" /> Markdown Tabelle
|
<TableIcon className="w-3 h-3 text-accent" /> Markdown Tabelle
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -1086,25 +1083,25 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
value={block.content}
|
value={block.content}
|
||||||
onChange={(e) => updateBlock(block.id, { content: e.target.value })}
|
onChange={(e) => updateBlock(block.id, { content: e.target.value })}
|
||||||
placeholder="| Spalte 1 | Spalte 2 |\n|---|---|\n| Inhalt | Wert |"
|
placeholder="| Spalte 1 | Spalte 2 |\n|---|---|\n| Inhalt | Wert |"
|
||||||
className="w-full bg-transparent font-mono text-xs text-zinc-200 focus:outline-none resize-none"
|
className="w-full bg-transparent font-mono text-xs text-main focus:outline-none resize-none placeholder:text-faint"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* PROGRESS BAR */}
|
{/* PROGRESS BAR */}
|
||||||
{block.type === 'progress' && (
|
{block.type === 'progress' && (
|
||||||
<div className="p-2.5 bg-canvas border border-border space-y-2">
|
<div className="p-2.5 bg-canvas border border-border rounded-card space-y-2">
|
||||||
<div className="flex items-center justify-between text-xs">
|
<div className="flex items-center justify-between text-xs">
|
||||||
<span className="text-zinc-300 font-medium flex items-center gap-1">
|
<span className="text-main font-medium flex items-center gap-1">
|
||||||
<Percent className="w-3 h-3 text-accent" /> Fortschritt
|
<Percent className="w-3 h-3 text-accent" /> Fortschritt
|
||||||
</span>
|
</span>
|
||||||
<span className="font-mono text-accent font-semibold">
|
<span className="font-mono text-accent font-semibold">
|
||||||
{parseInt(block.content) || 0}%
|
{parseInt(block.content) || 0}%
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full bg-panel h-2.5 overflow-hidden border border-border">
|
<div className="w-full bg-panel h-2.5 overflow-hidden border border-border rounded-pill">
|
||||||
<div
|
<div
|
||||||
className="bg-accent h-full transition-all"
|
className="bg-accent h-full transition-all rounded-pill"
|
||||||
style={{ width: `${Math.min(100, Math.max(0, parseInt(block.content) || 0))}%` }}
|
style={{ width: `${Math.min(100, Math.max(0, parseInt(block.content) || 0))}%` }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -1132,7 +1129,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => moveBlock(idx, -1)}
|
onClick={() => moveBlock(idx, -1)}
|
||||||
className="p-1 hover:bg-panel-elevated text-zinc-400 hover:text-white transition-colors"
|
className="p-1 hover:bg-panel-elevated text-muted hover:text-main rounded-btn transition-colors"
|
||||||
title="Nach oben"
|
title="Nach oben"
|
||||||
>
|
>
|
||||||
<ArrowUp className="w-3 h-3" />
|
<ArrowUp className="w-3 h-3" />
|
||||||
@@ -1140,7 +1137,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => moveBlock(idx, 1)}
|
onClick={() => moveBlock(idx, 1)}
|
||||||
className="p-1 hover:bg-panel-elevated text-zinc-400 hover:text-white transition-colors"
|
className="p-1 hover:bg-panel-elevated text-muted hover:text-main rounded-btn transition-colors"
|
||||||
title="Nach unten"
|
title="Nach unten"
|
||||||
>
|
>
|
||||||
<ArrowDown className="w-3 h-3" />
|
<ArrowDown className="w-3 h-3" />
|
||||||
@@ -1148,7 +1145,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => removeBlock(block.id)}
|
onClick={() => removeBlock(block.id)}
|
||||||
className="p-1 hover:bg-red-500/20 text-zinc-400 hover:text-red-400 transition-colors"
|
className="p-1 hover:bg-status-err/20 text-muted hover:text-status-err rounded-btn transition-colors"
|
||||||
title="Block entfernen"
|
title="Block entfernen"
|
||||||
>
|
>
|
||||||
<Trash2 className="w-3 h-3" />
|
<Trash2 className="w-3 h-3" />
|
||||||
@@ -1160,7 +1157,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
|
|
||||||
{/* Bottom Add Block Toolbar */}
|
{/* Bottom Add Block Toolbar */}
|
||||||
<div className="mt-4 pt-3 border-t border-border flex flex-wrap items-center gap-1.5">
|
<div className="mt-4 pt-3 border-t border-border flex flex-wrap items-center gap-1.5">
|
||||||
<span className="text-xs text-zinc-400 mr-1">+ Neuer Block:</span>
|
<span className="text-xs text-muted mr-1">+ Neuer Block:</span>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -1242,7 +1239,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="p-8 panel-surface text-center text-zinc-400 text-xs">
|
<div className="p-8 panel-surface text-center text-muted text-xs">
|
||||||
Wähle links eine Datei im Datei-Explorer aus oder erstelle eine neue Datei.
|
Wähle links eine Datei im Datei-Explorer aus oder erstelle eine neue Datei.
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
+123
-50
@@ -1,47 +1,23 @@
|
|||||||
|
/* ==========================================================================
|
||||||
|
MINDSIFT THEME ENGINE — ZENTRALE EINBINDUNG
|
||||||
|
Jedes Theme ist eine eigenständige CSS-Datei unter src/themes/ mit 6 Gruppen:
|
||||||
|
1. Akzentfarbe
|
||||||
|
2. Oberflächen & Hintergründe
|
||||||
|
3. Text & Kontraste
|
||||||
|
4. Rahmen, Linien & Schatten
|
||||||
|
5. Status- & Signalfarben
|
||||||
|
6. Rundungen (Geometrie)
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
@import './themes/theme-dark.css';
|
||||||
|
@import './themes/theme-light.css';
|
||||||
|
@import './themes/theme-high-contrast.css';
|
||||||
|
@import './themes/theme-soft.css';
|
||||||
|
|
||||||
@tailwind base;
|
@tailwind base;
|
||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
/* ==========================================================================
|
|
||||||
MINDFSIFT DESIGN SYSTEM - ZENTRALE STEUERUNG
|
|
||||||
Hier können Farben, Blauton, Kontraste und Rundungen zentral angepasst werden!
|
|
||||||
========================================================================== */
|
|
||||||
|
|
||||||
:root {
|
|
||||||
/* --- 1. AKZENTFARBE (Blauton hier regeln) --- */
|
|
||||||
--accent-primary: #1d8cf8; /* Haupt-Akzentblau */
|
|
||||||
--accent-hover: #3b9cfc; /* Hellere Nuance beim Hover */
|
|
||||||
--accent-dim: rgba(29, 140, 248, 0.14); /* Dezenter Hintergrund für Badges */
|
|
||||||
--accent-border: rgba(29, 140, 248, 0.35);/* Rand für aktive Badges */
|
|
||||||
|
|
||||||
/* --- 2. OBERFLÄCHEN & HINTERGRÜNDE --- */
|
|
||||||
--bg-app: #16171a; /* Gesamte Canvas-Hintergrundfarbe */
|
|
||||||
--bg-panel: #222428; /* Karten, Sidebar, Header, Modals */
|
|
||||||
--bg-panel-elevated: #282a2f; /* Ausgewählte Menüpunkte, Toolbars, Hover */
|
|
||||||
--bg-panel-hover: #32353b; /* Stärkerer Hover-Effekt */
|
|
||||||
--bg-input: #16171a; /* Eingabefelder und Quick-Inputs */
|
|
||||||
|
|
||||||
/* --- 3. RAHMEN & TRENNLINIEN --- */
|
|
||||||
--border-color: rgba(255, 255, 255, 0.08); /* Subtile Linien & Kanten */
|
|
||||||
--border-subtle: rgba(255, 255, 255, 0.05);
|
|
||||||
|
|
||||||
/* --- 4. TEXTFARBEN --- */
|
|
||||||
--text-main: #ffffff; /* Haupttext */
|
|
||||||
--text-muted: #a1a1aa; /* Sekundärtext (Grau) */
|
|
||||||
--text-faint: #71717a; /* Platzhalter & Hilfetexte */
|
|
||||||
|
|
||||||
/* --- 5. STATUS- UND SIGNALFARBEN --- */
|
|
||||||
--color-success: #22c55e;
|
|
||||||
--color-danger: #ef4444;
|
|
||||||
--color-warning: #f97316;
|
|
||||||
|
|
||||||
/* --- 6. RUNDUNGEN (Zentral konfigurierbar) --- */
|
|
||||||
--radius-card: 0px; /* Karten & Panels (0px = eckig) */
|
|
||||||
--radius-btn: 0px; /* Standard-Buttons & Tabs */
|
|
||||||
--radius-input: 0px; /* Textfelder für Optionen & Eigenschaften */
|
|
||||||
--radius-pill: 9999px; /* Runde Elemente (z.B. Quick-Capture) */
|
|
||||||
}
|
|
||||||
|
|
||||||
* {
|
* {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
-webkit-tap-highlight-color: transparent;
|
-webkit-tap-highlight-color: transparent;
|
||||||
@@ -52,9 +28,10 @@ body {
|
|||||||
color: var(--text-main);
|
color: var(--text-main);
|
||||||
font-family: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
font-family: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
|
transition: background-color 140ms ease, color 140ms ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Subtiler monochromer Scrollbar */
|
/* Scrollbar über Theme-Variablen */
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
width: 5px;
|
width: 5px;
|
||||||
height: 5px;
|
height: 5px;
|
||||||
@@ -65,16 +42,16 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb {
|
::-webkit-scrollbar-thumb {
|
||||||
background: rgba(255, 255, 255, 0.12);
|
background: var(--scrollbar-thumb);
|
||||||
border-radius: var(--radius-btn);
|
border-radius: var(--radius-btn);
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb:hover {
|
::-webkit-scrollbar-thumb:hover {
|
||||||
background: rgba(255, 255, 255, 0.22);
|
background: var(--scrollbar-thumb-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ==========================================================================
|
/* ==========================================================================
|
||||||
WIEDERVERWENDBARE SEMANTISCHE KLASSEN
|
SEMANTISCHE KOMPONENTEN-KLASSEN (100% Theme-gesteuert)
|
||||||
========================================================================== */
|
========================================================================== */
|
||||||
|
|
||||||
@layer components {
|
@layer components {
|
||||||
@@ -85,11 +62,33 @@ body {
|
|||||||
border-radius: var(--radius-card);
|
border-radius: var(--radius-card);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.panel-elevated {
|
||||||
|
background-color: var(--bg-panel-elevated);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: var(--radius-card);
|
||||||
|
}
|
||||||
|
|
||||||
.panel-header {
|
.panel-header {
|
||||||
border-bottom: 1px solid var(--border-color);
|
border-bottom: 1px solid var(--border-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Monochromer, eckiger Standard-Aktionsbutton */
|
/* Automatische Übernahme von --radius-card auf gerahmten Panels & Boxen */
|
||||||
|
.bg-panel.border,
|
||||||
|
.bg-canvas.border {
|
||||||
|
border-radius: var(--radius-card);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Quick-Capture-Blase & Profil-Pill im Header */
|
||||||
|
.capture-pill {
|
||||||
|
border-radius: var(--radius-pill);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Aufgaben-Checkboxen */
|
||||||
|
.checkbox-square {
|
||||||
|
border-radius: var(--radius-checkbox);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Standard-Aktionsbutton */
|
||||||
.btn-action {
|
.btn-action {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -122,8 +121,9 @@ body {
|
|||||||
padding-left: 0.75rem;
|
padding-left: 0.75rem;
|
||||||
padding-right: 0.75rem;
|
padding-right: 0.75rem;
|
||||||
background-color: var(--accent-primary);
|
background-color: var(--accent-primary);
|
||||||
|
border: 1px solid var(--accent-border);
|
||||||
border-radius: var(--radius-btn);
|
border-radius: var(--radius-btn);
|
||||||
color: #ffffff;
|
color: var(--text-on-accent);
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
transition: all 120ms ease;
|
transition: all 120ms ease;
|
||||||
@@ -133,7 +133,7 @@ body {
|
|||||||
background-color: var(--accent-hover);
|
background-color: var(--accent-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Kleiner eckiger Icon-Button */
|
/* Kleiner Icon-Button */
|
||||||
.btn-icon {
|
.btn-icon {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -166,9 +166,9 @@ body {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.btn-icon-danger:hover {
|
.btn-icon-danger:hover {
|
||||||
background-color: rgba(239, 68, 68, 0.15);
|
background-color: var(--bg-danger);
|
||||||
color: var(--color-danger);
|
color: var(--color-danger);
|
||||||
border-color: rgba(239, 68, 68, 0.3);
|
border-color: var(--border-danger);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Zentrales Texteingabefeld */
|
/* Zentrales Texteingabefeld */
|
||||||
@@ -211,7 +211,7 @@ body {
|
|||||||
background-color: var(--bg-panel-elevated);
|
background-color: var(--bg-panel-elevated);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Status Badge */
|
/* Status Badges */
|
||||||
.badge-accent {
|
.badge-accent {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -219,11 +219,84 @@ body {
|
|||||||
padding: 0.125rem 0.5rem;
|
padding: 0.125rem 0.5rem;
|
||||||
background-color: var(--accent-dim);
|
background-color: var(--accent-dim);
|
||||||
border: 1px solid var(--accent-border);
|
border: 1px solid var(--accent-border);
|
||||||
|
border-radius: var(--radius-btn);
|
||||||
color: var(--accent-primary);
|
color: var(--accent-primary);
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.badge-neutral {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.25rem;
|
||||||
|
padding: 0.125rem 0.375rem;
|
||||||
|
background-color: var(--bg-input);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: var(--radius-btn);
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 0.65rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-ok {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.25rem;
|
||||||
|
padding: 0.125rem 0.375rem;
|
||||||
|
background-color: var(--bg-success);
|
||||||
|
border: 1px solid var(--border-success);
|
||||||
|
border-radius: var(--radius-btn);
|
||||||
|
color: var(--color-success);
|
||||||
|
font-size: 0.65rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-err {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.25rem;
|
||||||
|
padding: 0.125rem 0.375rem;
|
||||||
|
background-color: var(--bg-danger);
|
||||||
|
border: 1px solid var(--border-danger);
|
||||||
|
border-radius: var(--radius-btn);
|
||||||
|
color: var(--color-danger);
|
||||||
|
font-size: 0.65rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Status Alert Boxen */
|
||||||
|
.alert-ok {
|
||||||
|
padding: 0.75rem;
|
||||||
|
background-color: var(--bg-success);
|
||||||
|
border: 1px solid var(--border-success);
|
||||||
|
border-radius: var(--radius-card);
|
||||||
|
color: var(--text-main);
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-err {
|
||||||
|
padding: 0.75rem;
|
||||||
|
background-color: var(--bg-danger);
|
||||||
|
border: 1px solid var(--border-danger);
|
||||||
|
border-radius: var(--radius-card);
|
||||||
|
color: var(--color-danger);
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Code- & Terminal-Blöcke */
|
||||||
|
.code-block {
|
||||||
|
background-color: var(--bg-code);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: var(--radius-card);
|
||||||
|
color: var(--text-main);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Modal-Hintergrund-Overlay */
|
||||||
|
.modal-overlay {
|
||||||
|
background-color: var(--bg-overlay);
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
}
|
||||||
|
|
||||||
/* File Tree Item */
|
/* File Tree Item */
|
||||||
.tree-node-active {
|
.tree-node-active {
|
||||||
background-color: var(--bg-panel-elevated);
|
background-color: var(--bg-panel-elevated);
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
export type ThemeId = 'dark' | 'light' | 'high-contrast' | 'soft';
|
||||||
|
|
||||||
|
export interface ThemeOption {
|
||||||
|
id: ThemeId;
|
||||||
|
name: string;
|
||||||
|
subtitle: string;
|
||||||
|
file: string;
|
||||||
|
previewBg: string;
|
||||||
|
previewPanel: string;
|
||||||
|
previewAccent: string;
|
||||||
|
previewText: string;
|
||||||
|
isRounded: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const THEME_STORAGE_KEY = 'mindsift_active_theme';
|
||||||
|
|
||||||
|
export const AVAILABLE_THEMES: ThemeOption[] = [
|
||||||
|
{
|
||||||
|
id: 'dark',
|
||||||
|
name: 'MindSift Dark (Standard)',
|
||||||
|
subtitle: 'Monochrom, dunkel & eckig (0px)',
|
||||||
|
file: 'src/themes/theme-dark.css',
|
||||||
|
previewBg: '#121316',
|
||||||
|
previewPanel: '#1a1c20',
|
||||||
|
previewAccent: '#2563eb',
|
||||||
|
previewText: '#f4f4f5',
|
||||||
|
isRounded: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'light',
|
||||||
|
name: 'MindSift Light (Tagmodus)',
|
||||||
|
subtitle: 'Helles Papier-Design & klare Kontraste',
|
||||||
|
file: 'src/themes/theme-light.css',
|
||||||
|
previewBg: '#f3f4f6',
|
||||||
|
previewPanel: '#ffffff',
|
||||||
|
previewAccent: '#2563eb',
|
||||||
|
previewText: '#111827',
|
||||||
|
isRounded: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'high-contrast',
|
||||||
|
name: 'High Contrast (Scharf)',
|
||||||
|
subtitle: 'Tiefschwarz (#000) & weiße Konturen',
|
||||||
|
file: 'src/themes/theme-high-contrast.css',
|
||||||
|
previewBg: '#000000',
|
||||||
|
previewPanel: '#09090b',
|
||||||
|
previewAccent: '#38bdf8',
|
||||||
|
previewText: '#ffffff',
|
||||||
|
isRounded: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'soft',
|
||||||
|
name: 'Soft Slate (Abgerundet)',
|
||||||
|
subtitle: 'Schiefer-Blau mit weichen Radien (10px)',
|
||||||
|
file: 'src/themes/theme-soft.css',
|
||||||
|
previewBg: '#0f172a',
|
||||||
|
previewPanel: '#1e293b',
|
||||||
|
previewAccent: '#3b82f6',
|
||||||
|
previewText: '#f8fafc',
|
||||||
|
isRounded: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export function getActiveTheme(): ThemeId {
|
||||||
|
try {
|
||||||
|
const saved = localStorage.getItem(THEME_STORAGE_KEY) as ThemeId | null;
|
||||||
|
if (saved && AVAILABLE_THEMES.some((t) => t.id === saved)) {
|
||||||
|
return saved;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
return 'dark';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setActiveTheme(themeId: ThemeId): void {
|
||||||
|
try {
|
||||||
|
localStorage.setItem(THEME_STORAGE_KEY, themeId);
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
if (typeof document !== 'undefined') {
|
||||||
|
document.documentElement.setAttribute('data-theme', themeId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function initThemeOnBoot(): ThemeId {
|
||||||
|
const active = getActiveTheme();
|
||||||
|
if (typeof document !== 'undefined') {
|
||||||
|
document.documentElement.setAttribute('data-theme', active);
|
||||||
|
}
|
||||||
|
return active;
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
/* ==========================================================================
|
||||||
|
MINDSIFT THEME: DARK MONOCHROME (Standard)
|
||||||
|
Alle Farben, Kontraste und Rundungen zentral in einer einzigen Datei.
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
:root,
|
||||||
|
:root[data-theme="dark"] {
|
||||||
|
/* --- 1. AKZENTFARBE --- */
|
||||||
|
--accent-primary: #2563eb;
|
||||||
|
--accent-hover: #3b82f6;
|
||||||
|
--accent-dim: rgba(37, 99, 235, 0.14);
|
||||||
|
--accent-border: rgba(59, 130, 246, 0.45);
|
||||||
|
--text-on-accent: #ffffff;
|
||||||
|
|
||||||
|
/* --- 2. OBERFLÄCHEN & HINTERGRÜNDE --- */
|
||||||
|
--bg-app: #121316;
|
||||||
|
--bg-panel: #1a1c20;
|
||||||
|
--bg-panel-elevated: #22252a;
|
||||||
|
--bg-panel-hover: #2a2e35;
|
||||||
|
--bg-input: #121316;
|
||||||
|
--bg-overlay: rgba(0, 0, 0, 0.72);
|
||||||
|
--bg-code: #0d0e11;
|
||||||
|
|
||||||
|
/* --- 3. TEXT & KONTRASTE --- */
|
||||||
|
--text-main: #f4f4f5;
|
||||||
|
--text-muted: #a1a1aa;
|
||||||
|
--text-faint: #71717a;
|
||||||
|
|
||||||
|
/* --- 4. RAHMEN, LINIEN & SCHATTEN --- */
|
||||||
|
--border-color: rgba(255, 255, 255, 0.09);
|
||||||
|
--border-subtle: rgba(255, 255, 255, 0.05);
|
||||||
|
--border-strong: rgba(255, 255, 255, 0.22);
|
||||||
|
--shadow-panel: 0 10px 30px rgba(0, 0, 0, 0.45);
|
||||||
|
--scrollbar-thumb: rgba(255, 255, 255, 0.14);
|
||||||
|
--scrollbar-thumb-hover: rgba(255, 255, 255, 0.26);
|
||||||
|
|
||||||
|
/* --- 5. STATUS- & SIGNALFARBEN --- */
|
||||||
|
--color-success: #22c55e;
|
||||||
|
--bg-success: rgba(34, 197, 94, 0.12);
|
||||||
|
--border-success: rgba(34, 197, 94, 0.38);
|
||||||
|
|
||||||
|
--color-danger: #ef4444;
|
||||||
|
--bg-danger: rgba(239, 68, 68, 0.12);
|
||||||
|
--border-danger: rgba(239, 68, 68, 0.38);
|
||||||
|
|
||||||
|
--color-warning: #f59e0b;
|
||||||
|
--bg-warning: rgba(245, 158, 11, 0.12);
|
||||||
|
--border-warning: rgba(245, 158, 11, 0.38);
|
||||||
|
|
||||||
|
/* --- 6. RUNDUNGEN (Geometrie) --- */
|
||||||
|
--radius-card: 0px;
|
||||||
|
--radius-btn: 2px;
|
||||||
|
--radius-input: 2px;
|
||||||
|
--radius-checkbox: 0px;
|
||||||
|
--radius-pill: 9999px;
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
/* ==========================================================================
|
||||||
|
MINDSIFT THEME: HIGH CONTRAST (Maximaler Kontrast & Schärfe)
|
||||||
|
Alle Farben, Kontraste und Rundungen zentral in einer einzigen Datei.
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
:root[data-theme="high-contrast"] {
|
||||||
|
/* --- 1. AKZENTFARBE --- */
|
||||||
|
--accent-primary: #38bdf8;
|
||||||
|
--accent-hover: #7dd3fc;
|
||||||
|
--accent-dim: rgba(56, 189, 248, 0.22);
|
||||||
|
--accent-border: #38bdf8;
|
||||||
|
--text-on-accent: #000000;
|
||||||
|
|
||||||
|
/* --- 2. OBERFLÄCHEN & HINTERGRÜNDE --- */
|
||||||
|
--bg-app: #000000;
|
||||||
|
--bg-panel: #09090b;
|
||||||
|
--bg-panel-elevated: #18181b;
|
||||||
|
--bg-panel-hover: #27272a;
|
||||||
|
--bg-input: #000000;
|
||||||
|
--bg-overlay: rgba(0, 0, 0, 0.88);
|
||||||
|
--bg-code: #000000;
|
||||||
|
|
||||||
|
/* --- 3. TEXT & KONTRASTE --- */
|
||||||
|
--text-main: #ffffff;
|
||||||
|
--text-muted: #e4e4e7;
|
||||||
|
--text-faint: #a1a1aa;
|
||||||
|
|
||||||
|
/* --- 4. RAHMEN, LINIEN & SCHATTEN --- */
|
||||||
|
--border-color: rgba(255, 255, 255, 0.38);
|
||||||
|
--border-subtle: rgba(255, 255, 255, 0.20);
|
||||||
|
--border-strong: #ffffff;
|
||||||
|
--shadow-panel: 0 0 0 1px #ffffff;
|
||||||
|
--scrollbar-thumb: rgba(255, 255, 255, 0.45);
|
||||||
|
--scrollbar-thumb-hover: #ffffff;
|
||||||
|
|
||||||
|
/* --- 5. STATUS- & SIGNALFARBEN --- */
|
||||||
|
--color-success: #4ade80;
|
||||||
|
--bg-success: rgba(74, 222, 128, 0.20);
|
||||||
|
--border-success: #4ade80;
|
||||||
|
|
||||||
|
--color-danger: #f87171;
|
||||||
|
--bg-danger: rgba(248, 113, 113, 0.20);
|
||||||
|
--border-danger: #f87171;
|
||||||
|
|
||||||
|
--color-warning: #fbbf24;
|
||||||
|
--bg-warning: rgba(251, 191, 36, 0.20);
|
||||||
|
--border-warning: #fbbf24;
|
||||||
|
|
||||||
|
/* --- 6. RUNDUNGEN (Geometrie: Komplett scharfkantig) --- */
|
||||||
|
--radius-card: 0px;
|
||||||
|
--radius-btn: 0px;
|
||||||
|
--radius-input: 0px;
|
||||||
|
--radius-checkbox: 0px;
|
||||||
|
--radius-pill: 0px;
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
/* ==========================================================================
|
||||||
|
MINDSIFT THEME: LIGHT (Tagmodus / Hell & Klar)
|
||||||
|
Alle Farben, Kontraste und Rundungen zentral in einer einzigen Datei.
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
:root[data-theme="light"] {
|
||||||
|
/* --- 1. AKZENTFARBE --- */
|
||||||
|
--accent-primary: #2563eb;
|
||||||
|
--accent-hover: #1d4ed8;
|
||||||
|
--accent-dim: rgba(37, 99, 235, 0.10);
|
||||||
|
--accent-border: rgba(37, 99, 235, 0.38);
|
||||||
|
--text-on-accent: #ffffff;
|
||||||
|
|
||||||
|
/* --- 2. OBERFLÄCHEN & HINTERGRÜNDE --- */
|
||||||
|
--bg-app: #f3f4f6;
|
||||||
|
--bg-panel: #ffffff;
|
||||||
|
--bg-panel-elevated: #ebedf0;
|
||||||
|
--bg-panel-hover: #e2e5ea;
|
||||||
|
--bg-input: #ffffff;
|
||||||
|
--bg-overlay: rgba(15, 23, 42, 0.45);
|
||||||
|
--bg-code: #f1f5f9;
|
||||||
|
|
||||||
|
/* --- 3. TEXT & KONTRASTE --- */
|
||||||
|
--text-main: #111827;
|
||||||
|
--text-muted: #4b5563;
|
||||||
|
--text-faint: #9ca3af;
|
||||||
|
|
||||||
|
/* --- 4. RAHMEN, LINIEN & SCHATTEN --- */
|
||||||
|
--border-color: rgba(17, 24, 39, 0.13);
|
||||||
|
--border-subtle: rgba(17, 24, 39, 0.06);
|
||||||
|
--border-strong: rgba(17, 24, 39, 0.28);
|
||||||
|
--shadow-panel: 0 8px 24px rgba(15, 23, 42, 0.08);
|
||||||
|
--scrollbar-thumb: rgba(17, 24, 39, 0.18);
|
||||||
|
--scrollbar-thumb-hover: rgba(17, 24, 39, 0.32);
|
||||||
|
|
||||||
|
/* --- 5. STATUS- & SIGNALFARBEN --- */
|
||||||
|
--color-success: #15803d;
|
||||||
|
--bg-success: rgba(22, 163, 74, 0.10);
|
||||||
|
--border-success: rgba(22, 163, 74, 0.35);
|
||||||
|
|
||||||
|
--color-danger: #b91c1c;
|
||||||
|
--bg-danger: rgba(220, 38, 38, 0.09);
|
||||||
|
--border-danger: rgba(220, 38, 38, 0.35);
|
||||||
|
|
||||||
|
--color-warning: #b45309;
|
||||||
|
--bg-warning: rgba(217, 119, 6, 0.10);
|
||||||
|
--border-warning: rgba(217, 119, 6, 0.35);
|
||||||
|
|
||||||
|
/* --- 6. RUNDUNGEN (Geometrie) --- */
|
||||||
|
--radius-card: 0px;
|
||||||
|
--radius-btn: 2px;
|
||||||
|
--radius-input: 2px;
|
||||||
|
--radius-checkbox: 0px;
|
||||||
|
--radius-pill: 9999px;
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
/* ==========================================================================
|
||||||
|
MINDSIFT THEME: SOFT SLATE (Abgerundete Geometrie & Sanftes Schiefer-Blau)
|
||||||
|
Demonstriert, wie über die Rundungs-Variablen in einer einzigen Datei
|
||||||
|
das gesamte App-Layout auf abgerundete Ecken umschaltet.
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
:root[data-theme="soft"] {
|
||||||
|
/* --- 1. AKZENTFARBE --- */
|
||||||
|
--accent-primary: #3b82f6;
|
||||||
|
--accent-hover: #60a5fa;
|
||||||
|
--accent-dim: rgba(59, 130, 246, 0.15);
|
||||||
|
--accent-border: rgba(96, 165, 250, 0.40);
|
||||||
|
--text-on-accent: #ffffff;
|
||||||
|
|
||||||
|
/* --- 2. OBERFLÄCHEN & HINTERGRÜNDE --- */
|
||||||
|
--bg-app: #0f172a;
|
||||||
|
--bg-panel: #1e293b;
|
||||||
|
--bg-panel-elevated: #293548;
|
||||||
|
--bg-panel-hover: #334155;
|
||||||
|
--bg-input: #0f172a;
|
||||||
|
--bg-overlay: rgba(2, 6, 23, 0.72);
|
||||||
|
--bg-code: #090d16;
|
||||||
|
|
||||||
|
/* --- 3. TEXT & KONTRASTE --- */
|
||||||
|
--text-main: #f8fafc;
|
||||||
|
--text-muted: #94a3b8;
|
||||||
|
--text-faint: #64748b;
|
||||||
|
|
||||||
|
/* --- 4. RAHMEN, LINIEN & SCHATTEN --- */
|
||||||
|
--border-color: rgba(148, 163, 184, 0.16);
|
||||||
|
--border-subtle: rgba(148, 163, 184, 0.08);
|
||||||
|
--border-strong: rgba(148, 163, 184, 0.32);
|
||||||
|
--shadow-panel: 0 12px 32px rgba(2, 6, 23, 0.40);
|
||||||
|
--scrollbar-thumb: rgba(148, 163, 184, 0.22);
|
||||||
|
--scrollbar-thumb-hover: rgba(148, 163, 184, 0.38);
|
||||||
|
|
||||||
|
/* --- 5. STATUS- & SIGNALFARBEN --- */
|
||||||
|
--color-success: #10b981;
|
||||||
|
--bg-success: rgba(16, 185, 129, 0.14);
|
||||||
|
--border-success: rgba(16, 185, 129, 0.38);
|
||||||
|
|
||||||
|
--color-danger: #f43f5e;
|
||||||
|
--bg-danger: rgba(244, 63, 94, 0.14);
|
||||||
|
--border-danger: rgba(244, 63, 94, 0.38);
|
||||||
|
|
||||||
|
--color-warning: #f59e0b;
|
||||||
|
--bg-warning: rgba(245, 158, 11, 0.14);
|
||||||
|
--border-warning: rgba(245, 158, 11, 0.38);
|
||||||
|
|
||||||
|
/* --- 6. RUNDUNGEN (Weich abgerundete Ecken überall) --- */
|
||||||
|
--radius-card: 10px;
|
||||||
|
--radius-btn: 6px;
|
||||||
|
--radius-input: 6px;
|
||||||
|
--radius-checkbox: 4px;
|
||||||
|
--radius-pill: 9999px;
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
/* ==========================================================================
|
||||||
|
MINDSIFT THEME-VORLAGE (TEMPLATE)
|
||||||
|
Kopiere diese Datei, benenne [data-theme="mein-theme"] um und passe
|
||||||
|
die 6 Gruppen nach deinen Wünschen an.
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
:root[data-theme="custom-template"] {
|
||||||
|
/* --- 1. AKZENTFARBE (Buttons, aktive Tabs, Fokus-Ränder, Badges) --- */
|
||||||
|
--accent-primary: #2563eb;
|
||||||
|
--accent-hover: #3b82f6;
|
||||||
|
--accent-dim: rgba(37, 99, 235, 0.14);
|
||||||
|
--accent-border: rgba(59, 130, 246, 0.45);
|
||||||
|
--text-on-accent: #ffffff;
|
||||||
|
|
||||||
|
/* --- 2. OBERFLÄCHEN & HINTERGRÜNDE --- */
|
||||||
|
--bg-app: #121316; /* Seiten-Hintergrund (Canvas) */
|
||||||
|
--bg-panel: #1a1c20; /* Karten, Header, Sidebar, Dropdowns */
|
||||||
|
--bg-panel-elevated: #22252a; /* Hervorgehobene Elemente & aktive Zeilen */
|
||||||
|
--bg-panel-hover: #2a2e35; /* Hover-Effekt auf Oberflächen */
|
||||||
|
--bg-input: #121316; /* Textfelder, Selects, Textareas */
|
||||||
|
--bg-overlay: rgba(0, 0, 0, 0.72); /* Hintergrund bei Modals/Dialogen */
|
||||||
|
--bg-code: #0d0e11; /* Code-Blöcke & Terminal-Boxen */
|
||||||
|
|
||||||
|
/* --- 3. TEXT & KONTRASTE --- */
|
||||||
|
--text-main: #f4f4f5; /* Haupttext & Überschriften */
|
||||||
|
--text-muted: #a1a1aa; /* Sekundärtext & Beschreibungen */
|
||||||
|
--text-faint: #71717a; /* Platzhalter & dezente Hinweise */
|
||||||
|
|
||||||
|
/* --- 4. RAHMEN, LINIEN & SCHATTEN --- */
|
||||||
|
--border-color: rgba(255, 255, 255, 0.09);
|
||||||
|
--border-subtle: rgba(255, 255, 255, 0.05);
|
||||||
|
--border-strong: rgba(255, 255, 255, 0.22);
|
||||||
|
--shadow-panel: 0 10px 30px rgba(0, 0, 0, 0.45);
|
||||||
|
--scrollbar-thumb: rgba(255, 255, 255, 0.14);
|
||||||
|
--scrollbar-thumb-hover: rgba(255, 255, 255, 0.26);
|
||||||
|
|
||||||
|
/* --- 5. STATUS- & SIGNALFARBEN --- */
|
||||||
|
--color-success: #22c55e;
|
||||||
|
--bg-success: rgba(34, 197, 94, 0.12);
|
||||||
|
--border-success: rgba(34, 197, 94, 0.38);
|
||||||
|
|
||||||
|
--color-danger: #ef4444;
|
||||||
|
--bg-danger: rgba(239, 68, 68, 0.12);
|
||||||
|
--border-danger: rgba(239, 68, 68, 0.38);
|
||||||
|
|
||||||
|
--color-warning: #f59e0b;
|
||||||
|
--bg-warning: rgba(245, 158, 11, 0.12);
|
||||||
|
--border-warning: rgba(245, 158, 11, 0.38);
|
||||||
|
|
||||||
|
/* --- 6. RUNDUNGEN (Geometrie) --- */
|
||||||
|
--radius-card: 0px; /* Karten, Panels, Container (0px = eckig) */
|
||||||
|
--radius-btn: 2px; /* Buttons & Badges */
|
||||||
|
--radius-input: 2px; /* Eingabefelder & Dropdowns */
|
||||||
|
--radius-checkbox: 0px; /* Aufgaben-Checkboxen */
|
||||||
|
--radius-pill: 9999px; /* Quick-Capture-Blase & Profil-Pill */
|
||||||
|
}
|
||||||
@@ -17,6 +17,13 @@ export default {
|
|||||||
hover: 'var(--bg-panel-hover)',
|
hover: 'var(--bg-panel-hover)',
|
||||||
border: 'var(--border-color)',
|
border: 'var(--border-color)',
|
||||||
},
|
},
|
||||||
|
input: 'var(--bg-input)',
|
||||||
|
overlay: 'var(--bg-overlay)',
|
||||||
|
code: 'var(--bg-code)',
|
||||||
|
main: 'var(--text-main)',
|
||||||
|
muted: 'var(--text-muted)',
|
||||||
|
faint: 'var(--text-faint)',
|
||||||
|
'on-accent': 'var(--text-on-accent)',
|
||||||
accent: {
|
accent: {
|
||||||
DEFAULT: 'var(--accent-primary)',
|
DEFAULT: 'var(--accent-primary)',
|
||||||
hover: 'var(--accent-hover)',
|
hover: 'var(--accent-hover)',
|
||||||
@@ -28,20 +35,45 @@ export default {
|
|||||||
hover: 'var(--accent-hover)',
|
hover: 'var(--accent-hover)',
|
||||||
dark: 'var(--accent-primary)',
|
dark: 'var(--accent-primary)',
|
||||||
},
|
},
|
||||||
|
status: {
|
||||||
|
ok: 'var(--color-success)',
|
||||||
|
'ok-bg': 'var(--bg-success)',
|
||||||
|
'ok-border': 'var(--border-success)',
|
||||||
|
err: 'var(--color-danger)',
|
||||||
|
'err-bg': 'var(--bg-danger)',
|
||||||
|
'err-border': 'var(--border-danger)',
|
||||||
|
warn: 'var(--color-warning)',
|
||||||
|
'warn-bg': 'var(--bg-warning)',
|
||||||
|
'warn-border': 'var(--border-warning)',
|
||||||
|
},
|
||||||
border: 'var(--border-color)',
|
border: 'var(--border-color)',
|
||||||
},
|
},
|
||||||
borderColor: {
|
borderColor: {
|
||||||
DEFAULT: 'var(--border-color)',
|
DEFAULT: 'var(--border-color)',
|
||||||
border: 'var(--border-color)',
|
border: 'var(--border-color)',
|
||||||
subtle: 'var(--border-subtle)',
|
subtle: 'var(--border-subtle)',
|
||||||
|
strong: 'var(--border-strong)',
|
||||||
},
|
},
|
||||||
borderRadius: {
|
borderRadius: {
|
||||||
card: 'var(--radius-card)',
|
card: 'var(--radius-card)',
|
||||||
btn: 'var(--radius-btn)',
|
btn: 'var(--radius-btn)',
|
||||||
input: 'var(--radius-input)',
|
input: 'var(--radius-input)',
|
||||||
|
checkbox: 'var(--radius-checkbox)',
|
||||||
|
pill: 'var(--radius-pill)',
|
||||||
|
},
|
||||||
|
boxShadow: {
|
||||||
|
panel: 'var(--shadow-panel)',
|
||||||
},
|
},
|
||||||
fontFamily: {
|
fontFamily: {
|
||||||
sans: ['Inter', 'system-ui', '-apple-system', 'BlinkMacSystemFont', 'Segoe UI', 'Roboto', 'sans-serif'],
|
sans: [
|
||||||
|
'Inter',
|
||||||
|
'system-ui',
|
||||||
|
'-apple-system',
|
||||||
|
'BlinkMacSystemFont',
|
||||||
|
'Segoe UI',
|
||||||
|
'Roboto',
|
||||||
|
'sans-serif',
|
||||||
|
],
|
||||||
mono: ['JetBrains Mono', 'Fira Code', 'monospace'],
|
mono: ['JetBrains Mono', 'Fira Code', 'monospace'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in new issue
Block a user