feat(admin): Cockpit — Suche/Filter, Kennzahlen, klickbare Kunden-Detailansicht

- Kennzahlen-Kacheln mit Unterzeile (neu/30T, gesperrt, ARR) + Plan-Leiste
- Toolbar: Suche (E-Mail/Firma) + Plan-Filter, live
- Kundentabelle: Zeilen klickbar → Detailansicht
- Detail: Profil, Abo-Historie, Instanzen mit Öffnen/Sperren/Reaktivieren
- fix: Abmelden-Button hatte keinen Handler

E2E: Detail, Suspend→Counter, Reactivate verifiziert.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-05-31 12:04:32 +02:00
parent 9e24ce8dd6
commit db357b8103
39 changed files with 352 additions and 103 deletions
+27
View File
@@ -873,6 +873,33 @@ nav [class*="font-bold"] {
.admin-table td { padding: 12px; border-bottom: 1px solid var(--rapport-border); vertical-align: top; }
.admin-tag { display: inline-block; background: var(--rapport-accent); color: #fff; font-size: 9px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; padding: 2px 7px; border-radius: 5px; vertical-align: middle; }
/* Admin-Cockpit: Stat-Unterzeile, Plan-Leiste, Toolbar, klickbare Zeilen */
.admin-stat-sub { font-size: 11px; color: var(--rapport-accent-2); margin-top: 4px; }
.admin-planbar { display: flex; flex-wrap: wrap; gap: 8px; margin: 16px 0 4px; }
.admin-planchip { font-size: 12px; background: var(--rapport-surface2); border: 1px solid var(--rapport-border); border-radius: 8px; padding: 6px 12px; }
.dark .admin-planchip { background: #1a1714; border-color: #2d2926; }
.admin-toolbar { display: flex; gap: 10px; margin: 22px 0 10px; }
.admin-search { flex: 1; margin: 0; }
.admin-filter { width: 180px; margin: 0; }
@media (max-width: 600px) { .admin-toolbar { flex-direction: column; } .admin-filter { width: 100%; } }
.admin-row { cursor: pointer; transition: background .12s; }
.admin-row:hover { background: var(--rapport-surface2); }
.dark .admin-row:hover { background: #1a1714; }
.admin-chevron { color: var(--rapport-text-3); font-size: 20px; line-height: 1; }
.admin-badge { display: inline-block; font-size: 11px; font-weight: 600; padding: 2px 9px; border-radius: 6px;
background: rgba(176,120,72,0.12); color: var(--rapport-accent-2); text-transform: capitalize; }
.admin-badge.ok { background: #ecefe6; color: #4a5a32; }
.admin-badge.warn { background: #f8e4e4; color: #9a3a3a; }
.admin-section { font-weight: 700; font-size: 13px; color: var(--rapport-text); margin: 24px 0 6px; padding-bottom: 6px; border-bottom: 1px solid var(--rapport-border); }
.admin-mini { width: auto; padding: 6px 14px; font-size: 12px; text-decoration: none; background: var(--rapport-surface2); color: var(--rapport-text); border: 1px solid var(--rapport-border); }
.admin-mini:hover { border-color: var(--rapport-accent); }
.admin-mini.warn { color: #9a3a3a; border-color: #e0b0b0; }
.admin-mini.ok { color: #4a5a32; border-color: #c2cdb0; }
.dark .admin-mini { background: #1a1714; color: #ece9e3; border-color: #2d2926; }
/* Footer-Spalten (Rechts-Links + Hosting-Hinweise) */
.rapport-footer-links {
display: grid; grid-template-columns: repeat(4, 1fr); gap: 32px;
+2 -2
View File
@@ -35,8 +35,8 @@
<meta name="twitter:title" content="Admin">
<meta name="twitter:description" content="Freie Studio Management Software für Architekturbüros — Zeiterfassung, Rechnungen, Projekte, QR-Einzahlungsscheine. Tauri &#43; React. AGPL-3.0.">
<link rel="preload" href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" as="style" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" rel="stylesheet" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link rel="preload" href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" as="style" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
<link href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" rel="stylesheet" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
+2 -2
View File
@@ -56,8 +56,8 @@ Limits (Benutzer, Projekte, Speicher) richten sich nach dem gewählten Plan.
1. Geltungsbereich Diese AGB gelten für alle Verträge über die Bereitstellung gehosteter Rapport-Instanzen zwischen dem Anbieter und dem Kunden (nachfolgend «Kunde»). Abweichende Bedingungen des Kunden gelten nur bei ausdrücklicher schriftlicher Zustimmung.
2. Leistungsumfang Der Anbieter stellt dem Kunden eine gehostete Instanz der Rapport-Software gemäss dem gewählten Abo (Solo, Studio, Business) bereit. Der Funktionsumfang und die Limits (Benutzer, Projekte, Speicher) richten sich nach dem gewählten Plan.">
<link rel="preload" href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" as="style" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" rel="stylesheet" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link rel="preload" href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" as="style" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
<link href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" rel="stylesheet" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
+2 -2
View File
@@ -33,8 +33,8 @@
<meta name="twitter:title" content="Categories">
<meta name="twitter:description" content="Freie Studio Management Software für Architekturbüros — Zeiterfassung, Rechnungen, Projekte, QR-Einzahlungsscheine. Tauri &#43; React. AGPL-3.0.">
<link rel="preload" href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" as="style" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" rel="stylesheet" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link rel="preload" href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" as="style" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
<link href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" rel="stylesheet" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
+2 -2
View File
@@ -60,8 +60,8 @@ Zahlungsdaten: Die Zahlungsabwicklung erfolgt über Stripe. Wir speichern keine
2. Welche Daten wir verarbeiten Kontodaten: E-Mail-Adresse, Passwort (verschlüsselt gespeichert), sowie optional Firma, Ansprechperson, Rechnungsadresse und Telefon.
Zahlungsdaten: Die Zahlungsabwicklung erfolgt über Stripe. Wir speichern keine vollständigen Kreditkartendaten; Stripe verarbeitet diese als eigener Verantwortlicher.">
<link rel="preload" href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" as="style" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" rel="stylesheet" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link rel="preload" href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" as="style" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
<link href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" rel="stylesheet" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
+2 -2
View File
@@ -54,8 +54,8 @@
<meta name="twitter:description" content="Vom Erstkontakt mit dem Kunden bis zur Schlussrechnung — der typische Weg eines Projekts durch Rapport.
Übersicht Kunde → Offerte → Projekt → Zeit → Akonto → Schluss anlegen erstellen (aus Offerte) erfassen -Rechnung -Rechnung ↓ QR-Schein 1 · Kunde anlegen Kunden → Neu — siehe Einrichtung § 3.">
<link rel="preload" href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" as="style" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" rel="stylesheet" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link rel="preload" href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" as="style" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
<link href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" rel="stylesheet" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
+2 -2
View File
@@ -67,8 +67,8 @@ Neu / Verbessert
Diverse Verbesserungen und Bugfixes (Details werden im Release auf Gitea gepflegt) Bekannte Einschränkungen
Builds sind Tauri-signiert, aber noch nicht Apple-notarisiert — siehe Installation § Gatekeeper Linux- und Windows-Builds noch nicht verfügbar 0.8.00.8.1 — Patch-Releases Patch-Reihe mit kleineren Verbesserungen und Bugfixes. Details siehe Releases auf Gitea .">
<link rel="preload" href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" as="style" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" rel="stylesheet" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link rel="preload" href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" as="style" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
<link href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" rel="stylesheet" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
+2 -2
View File
@@ -62,8 +62,8 @@ Diese Seite beschreibt die Desktop-App (Single-User). Wer im Team arbeitet und R
Speicherort (Desktop-App) Die Desktop-App speichert alles lokal — keine Cloud, kein Server.
macOS ~/Library/Application Support/com.rapport.app/ Dort liegt eine einzelne localStorage-Datenbank des WebView, in der alle Rapport-Daten als JSON unter dem Key studio_data_v1 gespeichert sind:">
<link rel="preload" href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" as="style" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" rel="stylesheet" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link rel="preload" href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" as="style" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
<link href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" rel="stylesheet" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
+2 -2
View File
@@ -59,8 +59,8 @@ Reihenfolge Die Reihenfolge ist wichtig — jede Stufe baut auf der vorherigen a
Reihenfolge Die Reihenfolge ist wichtig — jede Stufe baut auf der vorherigen auf:
1. Bürodaten → 2. Mitarbeiter → 3. Kunden → 4. Projekte ▼ ▼ ▼ ▼ Briefbogen, Zeiterfassung, Adressen, Zeiterfassung, QR-Schein, Lohn Rechnungen Rechnungen Login 1 · Bürodaten Einstellungen → Bürodaten">
<link rel="preload" href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" as="style" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" rel="stylesheet" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link rel="preload" href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" as="style" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
<link href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" rel="stylesheet" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
+2 -2
View File
@@ -125,8 +125,8 @@ macOS: Xcode Command Line Tools (xcode-select --install) Windows: Microsoft C&#4
Voraussetzungen Tool Version Node.js ≥ 20 (für Vite 8) npm ≥ 10 Rust toolchain ≥ 1.77.2 (via rustup) Plattform-Tools siehe Tauri Prerequisites Plattform-spezifisch:
macOS: Xcode Command Line Tools (xcode-select --install) Windows: Microsoft C&#43;&#43; Build Tools &#43; WebView2 Linux: webkit2gtk-4.1, librsvg2-dev, libayatana-appindicator3-dev, build-essential Setup git clone https://git.kgva.ch/karim/RAPPORT.git cd RAPPORT/APP npm install Entwicklung Web-Modus (HMR, schnellster Loop) npm run dev # http://localhost:3000 Hot-Module-Replacement Schnellster Iteration-Loop für UI-Arbeit Datenpersistierung: Browser-localStorage Native Window (Tauri-Fenster mit Desktop-Integration) npx tauri dev Echtes Tauri-Fenster System-Tray, Updater, native APIs verfügbar Erster Start dauert lange (Rust-Compile) Architektur in einem Absatz RAPPORT ist eine monolithische SPA: ein React-Root in App.jsx hält den gesamten App-State in einem useState({...}), persistiert ihn synchron in localStorage unter studio_data_v1, und übergibt ihn als Props an lazy-geladene Views. Kein Routing-Framework, kein State-Library, kein TypeScript, kein CSS-Framework. Der Rust-Teil ist 109 Zeilen und macht nur drei Dinge: System-Tray, Window-Hide-on-Close, Plugin-Registrierung (Updater, Process, Log). Keine #[tauri::command] — Frontend ↔ Backend kommuniziert nur über das Event rapport:navigate (Tray → Frontend).">
<link rel="preload" href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" as="style" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" rel="stylesheet" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link rel="preload" href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" as="style" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
<link href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" rel="stylesheet" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
+2 -2
View File
@@ -47,8 +47,8 @@ Die Pre-Release-Builds sind signiert über Tauri, aber (noch) nicht über die Ap
01 · Installation DMG von Gitea Releases herunterladen. Rapport in den Programme-Ordner ziehen. Beim ersten Start: Systemeinstellungen → Datenschutz &amp; Sicherheit öffnen und Rapport zulassen.
Die Pre-Release-Builds sind signiert über Tauri, aber (noch) nicht über die Apple-Notarisierung gegangen — daher der manuelle Freigabe-Schritt.">
<link rel="preload" href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" as="style" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" rel="stylesheet" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link rel="preload" href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" as="style" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
<link href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" rel="stylesheet" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
+2 -2
View File
@@ -37,8 +37,8 @@ Erste Schritte Quick-StartIn sechs Schritten von Null zur ersten Rechnung. Insta
<meta name="twitter:description" content="Vollständige Anleitung zu RAPPORT von der Installation über den täglichen Arbeitsablauf bis zur Cloud-Variante und Eigen-Builds.
Erste Schritte Quick-StartIn sechs Schritten von Null zur ersten Rechnung. InstallationmacOS, Gatekeeper, Signatur, geplante Plattformen. EinrichtungBürodaten, Mitarbeiter, Kunden, Projekte initial anlegen. Im Alltag Typischer ArbeitsablaufKunde → Offerte → Projekt → Zeit → Rechnung. Datenhaltung &amp; BackupWo die Daten liegen, wie du sie sicherst und wiederherstellst. TroubleshootingApp startet nicht, Daten weg, Update hängt. Für Fortgeschrittene Web-Modus (Multi-User)Rapport im Browser via Supabase — für Studios mit mehreren Nutzern. Entwicklung &amp; BuildAus dem Quellcode kompilieren, beitragen, eigenes Release. ChangelogVersionsgeschichte und Breaking Changes. Hilfe &amp; Support Bei Bugs oder weiteren Fragen → Issue auf Gitea . Siehe auch die FAQ für häufige Fragen.">
<link rel="preload" href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" as="style" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" rel="stylesheet" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link rel="preload" href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" as="style" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
<link href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" rel="stylesheet" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
+2 -2
View File
@@ -74,8 +74,8 @@ Voraussetzungen Plattform Status Versionen macOS Apple Silicon (M1 M4) ✅ U
<meta name="twitter:description" content="Schritt-für-Schritt-Anleitung für die Installation der Desktop-App.
Voraussetzungen Plattform Status Versionen macOS Apple Silicon (M1 M4) ✅ Unterstützt macOS 12&#43; macOS Intel ⚠ Build auf Anfrage macOS 12&#43; Linux 🕐 Geplant — Windows 🕐 Geplant — Eine Portierung auf Linux und Windows ist mit Tauri 2 möglich. Issue erstellen , wenn du eine Plattform brauchst.">
<link rel="preload" href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" as="style" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" rel="stylesheet" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link rel="preload" href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" as="style" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
<link href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" rel="stylesheet" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
+2 -2
View File
@@ -62,8 +62,8 @@ App startet nicht “Rapport ist beschädigt” beim ersten Start Ursache: macOS
Lösung: siehe Installation § 3. Kurz:
xattr -d com.apple.quarantine /Applications/Rapport.app App startet, zeigt aber schwarzen Bildschirm Ursache: WebView-Cache korrupt.">
<link rel="preload" href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" as="style" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" rel="stylesheet" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link rel="preload" href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" as="style" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
<link href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" rel="stylesheet" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
+2 -2
View File
@@ -97,8 +97,8 @@ Wann brauchst du das? Anwendungsfall Empfehlung Solo-Büro, ein Mac Desktop-App
Diese Seite bleibt als Referenz erhalten, der empfohlene Weg für Multi-User-Setups ist Rapport Server.
Wann brauchst du das? Anwendungsfall Empfehlung Solo-Büro, ein Mac Desktop-App — siehe Installation 25 Personen, gleicher Standort Rapport Server auf einem Mac Mini im LAN Verteiltes Team / Home-Office Rapport Server mit SSL &#43; Reverse Proxy Hosted Backend (eigener VPS) Rapport Server auf Linux-VPS Architektur (Kurzfassung) ┌────────────┐ HTTPS ┌──────────────┐ SQL ┌────────────┐ │ Browser │ ──────────────│ nginx │ ─────────────│ Postgres │ │ / Desktop │ │ (Frontend) │ │ &#43; GoTrue │ └────────────┘ └──────────────┘ │ &#43; REST │ │ &#43; Realtime │ │ &#43; Storage │ └────────────┘ Frontend: dieselbe React-App, aber Vite-Build statt Tauri (npm run build) Backend: Postgres-Stack (Rapport Server) Auth: E-Mail / Passwort über GoTrue Storage: Belege, Logos in Object-Storage Setup Alle Setup-Schritte (Repo klonen, .env erstellen, Migrations syncen, Docker-Compose starten, Reverse-Proxy konfigurieren) sind in Rapport Server dokumentiert.">
<link rel="preload" href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" as="style" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" rel="stylesheet" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link rel="preload" href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" as="style" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
<link href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" rel="stylesheet" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
+2 -2
View File
@@ -46,8 +46,8 @@ Komponente Für wen Aktuelle Version Desktop-App Solo-Büro, lokale Datenhaltung
Neuerungen — siehe Changelog für Details.
macOS Architektur Download Apple Silicon (M1M4) RAPPORT_0.8.2_aarch64.dmg Intel (x86_64) auf Anfrage Erstinstallation: Systemeinstellungen → Datenschutz &amp; Sicherheit öffnen und Rapport zulassen. Die Builds sind über Tauri signiert, aber (noch) nicht Apple-notarisiert.">
<link rel="preload" href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" as="style" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" rel="stylesheet" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link rel="preload" href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" as="style" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
<link href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" rel="stylesheet" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
+2 -2
View File
@@ -40,8 +40,8 @@ Ist Rapport kostenlos? Ja, vollständig. Quellcode unter GNU AGPL-3.0-or-later.
Für wen ist Rapport gedacht? Für kleine Architekturbüros in der Schweiz. Die Strukturen folgen der SIA 102 (Phasen, Honorar). Rapport wurde für den internen Gebrauch entwickelt und wird mit diesem Projekt öffentlich geteilt.
Ist Rapport kostenlos? Ja, vollständig. Quellcode unter GNU AGPL-3.0-or-later. Keine versteckten Kosten, keine Telemetrie. Die Daten bleiben lokal auf deinem Computer bzw. in deiner Instanz. Du hast die komplette Kontrolle über deine Daten.">
<link rel="preload" href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" as="style" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" rel="stylesheet" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link rel="preload" href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" as="style" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
<link href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" rel="stylesheet" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
+2 -2
View File
@@ -67,8 +67,8 @@ Rapport prüft beim Start automatisch auf neue Versionen und installiert Updates
Funktionsweise Beim App-Start:
Abfrage gegen https://git.kgva.ch/karim/RAPPORT/releases/latest.json Versionsvergleich mit lokaler version im Tauri-Bundle Bei neuer Version → Update-Dialog Bei Bestätigung → Download &#43; Signaturprüfung &#43; Installation &#43; Neustart Sicherheit Updates werden mit dem Tauri-Updater-Schlüssel signiert Manipulierte Downloads werden abgelehnt Quellcode und Build sind reproduzierbar (Gitea CI, geplant) Optionen Update installieren — Download &amp; Neustart Diese Version überspringen — überspringt nur diese eine Version Später erinnern — beim nächsten Start erneut fragen Updates können in den Einstellungen komplett deaktiviert werden.">
<link rel="preload" href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" as="style" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" rel="stylesheet" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link rel="preload" href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" as="style" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
<link href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" rel="stylesheet" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
+2 -2
View File
@@ -37,8 +37,8 @@ Module ZeiterfassungTages- &amp; Wochenraster mit Drag &amp; Drop. Rechnungen &a
<meta name="twitter:description" content="Die Bausteine von RAPPORT Studio-Management für Schweizer Architekturbüros.
Module ZeiterfassungTages- &amp; Wochenraster mit Drag &amp; Drop. Rechnungen &amp; OffertenQR-Einzahlungsscheine, SIA-Phasen, Akonto. Projekt- &amp; KundenverwaltungSIA 102, Budget, Phasen, Beteiligte. MitarbeiterFerien, Absenzen, Lohnabrechnung. Spesen &amp; BürobudgetBelegupload, Jahresbudget, Internes. Protokolle &amp; LieferscheineSitzungsprotokolle, Briefe, Lieferscheine. Auto-UpdaterSignierte Updates via Tauri. System-TrayHide-on-Close, Quick-Open.">
<link rel="preload" href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" as="style" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" rel="stylesheet" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link rel="preload" href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" as="style" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
<link href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" rel="stylesheet" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
+2 -2
View File
@@ -63,8 +63,8 @@ Ferienverwaltung, interne Stunden / Absenzen und Lohnabrechnung. Jahresabschluss
Stammdaten Pro Mitarbeiter:
Name, Eintrittsdatum, Pensum (%) Stundensatz (intern, für Rechnungen) Ferienanspruch (Tage / Jahr) Lohn (monatlich, brutto) Ferienverwaltung Prorata-Berechnung bei Eintritt unter Jahr Ferien-Saldo in Tagen (live) Halbtage unterstützt Übertrag ins Folgejahr oder Auszahlung Absenzen Krankheit, Militär, Mutterschaft, unbezahlter Urlaub — getrennt erfasst, mit Auswertung pro Mitarbeiter.">
<link rel="preload" href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" as="style" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" rel="stylesheet" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link rel="preload" href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" as="style" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
<link href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" rel="stylesheet" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
+2 -2
View File
@@ -57,8 +57,8 @@ Projekte nach SIA 102 mit Budget, Phasen und Beteiligten. Erstellung aus einer O
Projektstruktur Jedes Projekt besitzt:
Stammdaten — Nummer, Bezeichnung, Standort, Bauschätzwert Kunde — verknüpft mit Kundendatenbank Beteiligte — Bauleitung, Fachplaner, Behörden Phasen — SIA 102 (Vorprojekt, Bauprojekt, Ausschreibung, …) Budget — Gesamthonorar, pro Phase aufgeteilt SIA 102 Standard-Phasenverteilung wird vorgeschlagen, kann pro Projekt überschrieben werden.">
<link rel="preload" href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" as="style" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" rel="stylesheet" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link rel="preload" href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" as="style" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
<link href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" rel="stylesheet" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
+2 -2
View File
@@ -80,8 +80,8 @@ Briefe Brief-Editor mit:
Empfänger aus Kundendatenbank Bezugszeile, Anrede, Text, Grussformel Briefbogen-Vorlage mit Logo PDF-Export Lieferscheine Pro Lieferung:
Empfänger, Datum, Bezug Positionen (Plan-Nummer, Bezeichnung, Anzahl, Massstab) Unterschriftenfeld Konsistentes Erscheinungsbild über alle Dokumenttypen — eine Briefbogen-Vorlage, mehrere Verwendungen.">
<link rel="preload" href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" as="style" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" rel="stylesheet" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link rel="preload" href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" as="style" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
<link href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" rel="stylesheet" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
+2 -2
View File
@@ -93,8 +93,8 @@ Workflow Offerte erstellen — auf Basis SIA 102 oder pauschal Kunde nimmt an
Ausgelesen aus:
Bürodaten — IBAN, Empfänger-Adresse Kundendaten — Schuldner-Adresse Rechnungs-Daten — Betrag, Referenz, Zusatzinformation Honorarmodelle Modell Berechnung Verwendung Stundensatz Aus Zeiterfassung × Mitarbeiter-Stundensatz Kleinaufträge, Beratung SIA-Phasen Bauschätzwert × Honorarsatz × Phasenanteil Reguläre Architektur-Aufträge Pauschal Fester Betrag Auf Wunsch des Kunden PDF-Export Druckfertige Rechnung inkl. QR-Schein. Layout aus dem Büro-Briefbogen (mit Logo). Mehrsprachig DE/FR/IT (geplant).">
<link rel="preload" href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" as="style" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" rel="stylesheet" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link rel="preload" href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" as="style" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
<link href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" rel="stylesheet" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
+2 -2
View File
@@ -79,8 +79,8 @@ Datum, Betrag, Kategorie Beleg-Upload (PDF, JPG, PNG) Projekt-Zuordnung (optiona
Jahresbudget Übersicht über:
Einnahmen — Rechnungsbeträge, sortiert nach Eingang Ausgaben — Spesen, Bürokosten, Löhne, Sozialabzüge Saldo pro Monat / Quartal / Jahr Auswertung Einnahmen pro Kunde / Projekt Ausgaben pro Kategorie / Mitarbeiter Erfolgsrechnung pro Geschäftsjahr (vereinfacht) Verwandte Module Mitarbeiter — Spesen-Erstattung in der Lohnabrechnung Rechnungen — Einnahmen-Quelle">
<link rel="preload" href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" as="style" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" rel="stylesheet" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link rel="preload" href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" as="style" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
<link href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" rel="stylesheet" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
+2 -2
View File
@@ -81,8 +81,8 @@ Verhalten Aktion Verhalten Fenster schliessen (⌘W oder rotes X) App läuft im
Schnellzugriff über die Menüleiste mit Hide-on-Close. Beim Schliessen läuft Rapport im Hintergrund weiter — Cmd&#43;Q beendet die App vollständig.
Verhalten Aktion Verhalten Fenster schliessen (⌘W oder rotes X) App läuft im Tray weiter Cmd&#43;Q App wird vollständig beendet Klick auf Tray-Icon Fenster nach vorne, oder zeigen Rechtsklick auf Tray-Icon Menü mit Schnellzugriffen Tray-Menü Rapport zeigen — Fenster nach vorne Neue Zeiterfassung — direkt im Zeit-Modul Neue Rechnung — direkt im Rechnungs-Modul Letzte Projekte — Quick-Open der letzten 5 Projekte Einstellungen Rapport beenden Konfiguration In den Einstellungen:">
<link rel="preload" href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" as="style" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" rel="stylesheet" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link rel="preload" href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" as="style" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
<link href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" rel="stylesheet" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
+2 -2
View File
@@ -58,8 +58,8 @@ Tages- und Wochenraster mit Drag &amp; Drop. Auswertungen pro Mitarbeiter und Pr
Konzept Die Zeiterfassung ist das Kernmodul von RAPPORT — alle anderen Module (Rechnungen, Auswertungen, Lohnabrechnung) greifen auf die hier erfassten Stunden zu.
Eingabe Wochenraster mit den 5 (oder 7) Arbeitstagen Halbstunden-Slots von 06:00 bis 22:00 Klick oder Drag über mehrere Slots Jeder Eintrag wird einem Projekt zugewiesen (Pflichtfeld) Mehrfacheinträge pro Slot möglich (z. B. parallele Telefonate) Auswertungen Pro Mitarbeiter und pro Projekt:">
<link rel="preload" href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" as="style" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" rel="stylesheet" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link rel="preload" href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" as="style" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
<link href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" rel="stylesheet" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
+2 -2
View File
@@ -35,8 +35,8 @@
<meta name="twitter:title" content="Hosting-Preise">
<meta name="twitter:description" content="Freie Studio Management Software für Architekturbüros — Zeiterfassung, Rechnungen, Projekte, QR-Einzahlungsscheine. Tauri &#43; React. AGPL-3.0.">
<link rel="preload" href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" as="style" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" rel="stylesheet" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link rel="preload" href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" as="style" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
<link href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" rel="stylesheet" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
+2 -2
View File
@@ -34,8 +34,8 @@
<meta name="twitter:title" content="Hosting">
<meta name="twitter:description" content="Gehostet in der Schweiz · Ohne eigenen Server RAPPORT Hosting Ihre eigene Rapport-Instanz — in Minuten startklar. Studio-Management für Architekturbüros, gehostet, gewartet und gesichert. Sie arbeiten, wir kümmern uns um den Rest. Jetzt starten Preise ansehen ↗ Schweizer Hosting Tägliche Backups Jederzeit kündbar Keine Installation SO EINFACH">
<link rel="preload" href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" as="style" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" rel="stylesheet" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link rel="preload" href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" as="style" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
<link href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" rel="stylesheet" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
+2 -2
View File
@@ -66,8 +66,8 @@ Einzelunternehmen nach schweizerischem Recht. [UID/MwSt-Nr., falls vorhanden —
Verantwortlich für den Inhalt Karim Gabriele Varano, Adresse wie oben.
Haftungsausschluss Die Inhalte dieser Website wurden mit grösstmöglicher Sorgfalt erstellt. Für die Richtigkeit, Vollständigkeit und Aktualität der Inhalte wird keine Gewähr übernommen. Die Nutzung des Dienstes erfolgt gemäss den AGB.">
<link rel="preload" href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" as="style" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" rel="stylesheet" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link rel="preload" href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" as="style" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
<link href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" rel="stylesheet" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
+2 -2
View File
@@ -35,8 +35,8 @@
<meta name="twitter:title" content="RAPPORT">
<meta name="twitter:description" content="Pre-Release 0.8.2 · Aktiv in Entwicklung RAPPORT Studio Administration Die Studio Management Software für Architekturbüros mit offenem Quellcode — Zeiterfassung, Rechnungen, Offerten, Projekte, Mitarbeiter und QR-Einzahlungsscheine in einer App. Download · 0.8.2 (macOS) Quellcode ↗ AGPL-3.0 Tauri &#43; React macOS (später Linux &amp; Windows) Lokal / Selfhosting ZIEL">
<link rel="preload" href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" as="style" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" rel="stylesheet" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link rel="preload" href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" as="style" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
<link href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" rel="stylesheet" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
+127 -16
View File
@@ -345,35 +345,146 @@
return;
}
const stat = (label, val) =>
'<div class="admin-stat"><div class="admin-stat-num">' + esc(val) + "</div>" +
'<div class="admin-stat-label">' + label + "</div></div>";
adminCache = { stats, accounts: accounts.accounts };
paintAdmin();
}
const rows = accounts.accounts.map((a) =>
"<tr>" +
// Such-/Filter-State des Cockpits.
let adminCache = null;
let adminSearch = "";
let adminPlanFilter = "";
function statCard(label, val, sub) {
return '<div class="admin-stat"><div class="admin-stat-num">' + esc(val) + "</div>" +
'<div class="admin-stat-label">' + esc(label) + "</div>" +
(sub ? '<div class="admin-stat-sub">' + esc(sub) + "</div>" : "") + "</div>";
}
function paintAdmin() {
const { stats } = adminCache;
const planNames = Object.values(stats.byPlan || {});
const planChips = planNames.length
? '<div class="admin-planbar">' + planNames.map((p) =>
'<div class="admin-planchip"><b>' + esc(p.name) + "</b> · " + esc(p.count) +
' <span class="muted">(CHF ' + esc(p.revenue) + ")</span></div>"
).join("") + "</div>"
: "";
// Kunden filtern (Suche über Email/Firma + Plan-Filter).
const q = adminSearch.trim().toLowerCase();
const list = adminCache.accounts.filter((a) => {
const matchesQ = !q || (a.email + " " + (a.company || "")).toLowerCase().includes(q);
const matchesP = !adminPlanFilter || a.plan === adminPlanFilter;
return matchesQ && matchesP;
});
const rows = list.map((a) =>
'<tr class="admin-row" data-id="' + esc(a.id) + '">' +
"<td><b>" + esc(a.email) + "</b>" +
(a.company ? '<div class="muted" style="font-size:12px">' + esc(a.company) + "</div>" : "") + "</td>" +
"<td>" + (a.plan ? esc(a.plan) + '<div class="muted" style="font-size:12px">' + esc(a.sub_status || "") + "</div>" : '<span class="muted">—</span>') + "</td>" +
"<td style=\"text-align:center\">" + esc(a.instance_count) + "</td>" +
"<td class=\"muted\" style=\"font-size:12px\">" + esc(new Date(a.created_at).toLocaleDateString("de-CH")) + "</td>" +
"<td>" + (a.plan ? '<span class="admin-badge">' + esc(a.plan) + "</span>" +
(a.sub_status && a.sub_status !== "active" ? ' <span class="muted" style="font-size:11px">' + esc(a.sub_status) + "</span>" : "")
: '<span class="muted">—</span>') + "</td>" +
'<td style="text-align:center">' + esc(a.instance_count) + "</td>" +
'<td class="muted" style="font-size:12px">' + esc(new Date(a.created_at).toLocaleDateString("de-CH")) + "</td>" +
'<td style="text-align:right"><span class="admin-chevron"></span></td>' +
"</tr>"
).join("");
const planOptions = ['<option value="">Alle Pläne</option>']
.concat(planNames.map((p) => '<option value="' + esc(p.name.toLowerCase()) + '"' +
(adminPlanFilter === p.name.toLowerCase() ? " selected" : "") + ">" + esc(p.name) + "</option>"))
.join("");
const html =
'<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:20px">' +
'<div class="hosting-title" style="text-align:left;margin:0">Admin</div>' +
'<div class="hosting-title" style="text-align:left;margin:0">Cockpit</div>' +
'<button class="hosting-link" id="alogout">Abmelden</button></div>' +
'<div class="admin-stats">' +
stat("Kunden", stats.accounts) +
stat("Aktive Abos", stats.activeSubscriptions) +
stat("Instanzen", stats.activeInstances + "/" + stats.instances) +
stat("MRR (CHF)", stats.mrrChf) +
statCard("Kunden", stats.accounts, "+" + (stats.newAccounts30d || 0) + " (30 T.)") +
statCard("Aktive Abos", stats.activeSubscriptions) +
statCard("Instanzen", stats.activeInstances + "/" + stats.instances,
(stats.suspendedInstances ? stats.suspendedInstances + " gesperrt" : "")) +
statCard("MRR", "CHF " + stats.mrrChf, "ARR CHF " + (stats.arrChf || stats.mrrChf * 12)) +
"</div>" +
'<div style="margin:24px 0 10px;font-weight:600;font-size:14px">Kunden</div>' +
'<table class="admin-table"><thead><tr><th>Konto</th><th>Abo</th><th>Inst.</th><th>Seit</th></tr></thead>' +
"<tbody>" + (rows || '<tr><td colspan="4" class="muted">Noch keine Kunden.</td></tr>') + "</tbody></table>";
planChips +
'<div class="admin-toolbar">' +
'<input class="hosting-input admin-search" id="asearch" placeholder="Suche E-Mail / Firma…" value="' + esc(adminSearch) + '">' +
'<select class="hosting-input admin-filter" id="afilter">' + planOptions + "</select>" +
"</div>" +
'<table class="admin-table"><thead><tr><th>Konto</th><th>Abo</th><th>Inst.</th><th>Seit</th><th></th></tr></thead>' +
"<tbody>" + (rows || '<tr><td colspan="5" class="muted">Keine Treffer.</td></tr>') + "</tbody></table>";
root.innerHTML = card(html, true);
root.querySelector("#alogout").onclick = () => { adminTok.clear(); renderAdminLogin(""); };
const s = root.querySelector("#asearch");
s.oninput = () => { adminSearch = s.value; const pos = s.selectionStart; paintAdmin(); const n = root.querySelector("#asearch"); n.focus(); n.setSelectionRange(pos, pos); };
root.querySelector("#afilter").onchange = (e) => { adminPlanFilter = e.target.value; paintAdmin(); };
root.querySelectorAll(".admin-row").forEach((r) =>
(r.onclick = () => renderAdminDetail(r.dataset.id))
);
}
// — Kunden-Detailansicht (Profil, Abo-Historie, Instanzen + Aktionen) —
async function renderAdminDetail(id) {
root.innerHTML = card('<div class="hosting-sub">Lädt…</div>', true);
let d;
try { d = await adminApi("GET", "/admin/accounts/" + id); }
catch (err) {
if (err.status === 401 || err.status === 403) { adminTok.clear(); return renderAdminLogin("Bitte neu anmelden."); }
root.innerHTML = card('<div class="hosting-msg err">' + esc(err.message) + "</div>"); return;
}
const { account: a, subscriptions, instances } = d;
const profile = [
["Firma", a.company], ["Ansprechperson", a.contact_name],
["Adresse", [a.street, [a.zip, a.city].filter(Boolean).join(" "), a.country].filter(Boolean).join(", ")],
["Telefon", a.phone],
].filter(([, v]) => v).map(([k, v]) =>
'<div class="hosting-row"><span class="muted">' + esc(k) + "</span><span>" + esc(v) + "</span></div>"
).join("") || '<div class="muted" style="padding:8px 0">Keine Profildaten.</div>';
const subRows = subscriptions.length ? subscriptions.map((s) =>
'<div class="hosting-row"><span><span class="admin-badge">' + esc(s.plan) + "</span> " + esc(s.status) +
(s.priceChf != null ? ' <span class="muted">CHF ' + esc(s.priceChf) + "</span>" : "") + "</span>" +
'<span class="muted" style="font-size:12px">' + esc(new Date(s.created_at).toLocaleDateString("de-CH")) + "</span></div>"
).join("") : '<div class="muted" style="padding:8px 0">Kein Abo.</div>';
const instRows = instances.length ? instances.map((i) =>
'<div class="hosting-row"><div><b>' + esc(i.label || i.studio_slug) + "</b> " +
'<span class="admin-badge ' + (i.status === "active" ? "ok" : "warn") + '">' + esc(i.status) + "</span>" +
'<div class="muted" style="font-size:12px">' + esc(i.instance_url) + "</div></div>" +
'<div style="display:flex;gap:8px">' +
'<a class="hosting-btn admin-mini" href="' + esc(i.instance_url) + '" target="_blank" rel="noreferrer">Öffnen</a>' +
(i.status === "active"
? '<button class="hosting-btn admin-mini warn" data-act="suspend" data-iid="' + esc(i.id) + '">Sperren</button>'
: '<button class="hosting-btn admin-mini ok" data-act="reactivate" data-iid="' + esc(i.id) + '">Reaktivieren</button>') +
"</div></div>"
).join("") : '<div class="muted" style="padding:8px 0">Keine Instanzen.</div>';
const html =
'<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:6px">' +
'<button class="hosting-link" id="aback"> Zurück</button>' +
'<button class="hosting-link" id="alogout">Abmelden</button></div>' +
'<div class="hosting-title" style="text-align:left;margin:0 0 2px">' + esc(a.email) + "</div>" +
'<div class="hosting-sub" style="text-align:left;margin-bottom:22px">Kunde seit ' +
esc(new Date(a.created_at).toLocaleDateString("de-CH")) + "</div>" +
'<div class="admin-section">Profil</div>' + profile +
'<div class="admin-section">Abo-Historie</div>' + subRows +
'<div class="admin-section">Instanzen</div>' + instRows;
root.innerHTML = card(html, true);
root.querySelector("#aback").onclick = () => paintAdmin();
root.querySelector("#alogout").onclick = () => { adminTok.clear(); renderAdminLogin(""); };
root.querySelectorAll("[data-act]").forEach((b) =>
(b.onclick = async () => {
b.disabled = true;
try {
await adminApi("POST", "/admin/instances/" + b.dataset.iid + "/" + b.dataset.act);
renderAdminDetail(id); // neu laden
} catch (err) { alert(err.message); b.disabled = false; }
})
);
}
({ login: renderLogin, register: renderRegister, konto: renderKonto, preise: renderPreise, admin: renderAdmin }[page] || renderLogin)();
+2 -2
View File
@@ -35,8 +35,8 @@
<meta name="twitter:title" content="Konto">
<meta name="twitter:description" content="Freie Studio Management Software für Architekturbüros — Zeiterfassung, Rechnungen, Projekte, QR-Einzahlungsscheine. Tauri &#43; React. AGPL-3.0.">
<link rel="preload" href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" as="style" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" rel="stylesheet" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link rel="preload" href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" as="style" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
<link href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" rel="stylesheet" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
+2 -2
View File
@@ -40,8 +40,8 @@ Quellcode: git.kgva.ch/karim/RAPPORT Autor: Karim Gabriele Varano Lizenz Lizenzi
RAPPORT RAPPORT — Studio Management Software für Architekturbüros.
Quellcode: git.kgva.ch/karim/RAPPORT Autor: Karim Gabriele Varano Lizenz Lizenziert unter GNU Affero General Public License v3.0 oder höher (AGPL-3.0-or-later ).">
<link rel="preload" href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" as="style" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" rel="stylesheet" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link rel="preload" href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" as="style" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
<link href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" rel="stylesheet" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
+2 -2
View File
@@ -35,8 +35,8 @@
<meta name="twitter:title" content="Anmelden">
<meta name="twitter:description" content="Freie Studio Management Software für Architekturbüros — Zeiterfassung, Rechnungen, Projekte, QR-Einzahlungsscheine. Tauri &#43; React. AGPL-3.0.">
<link rel="preload" href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" as="style" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" rel="stylesheet" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link rel="preload" href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" as="style" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
<link href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" rel="stylesheet" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
+2 -2
View File
@@ -35,8 +35,8 @@
<meta name="twitter:title" content="Registrieren">
<meta name="twitter:description" content="Freie Studio Management Software für Architekturbüros — Zeiterfassung, Rechnungen, Projekte, QR-Einzahlungsscheine. Tauri &#43; React. AGPL-3.0.">
<link rel="preload" href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" as="style" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" rel="stylesheet" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link rel="preload" href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" as="style" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
<link href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" rel="stylesheet" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
+2 -2
View File
@@ -40,8 +40,8 @@ Wann brauchst du Rapport Server? Szenario Lösung Ein Mensch, ein Mac Desktop-Ap
Rapport Server — der vollständige Selfhost-Stack für Rapport. Eigene Daten, eigene Domain, eigener Server. Komplett Open-Source, Docker-Compose, AGPL-3.0.
Wann brauchst du Rapport Server? Szenario Lösung Ein Mensch, ein Mac Desktop-App reicht — Installation Mehrere Personen im Studio Rapport Server auf einem Mac Mini oder Linux-Server Verteiltes Team, Home-Office, Mobile-Zugriff Rapport Server mit Reverse-Proxy &#43; SSL Cloud-Hosting bei einem Anbieter Rapport Server auf VPS/Hetzner/etc. Die Desktop-App speichert lokal als JSON. Rapport Server bringt Postgres &#43; Multi-User &#43; Realtime-Sync — für alle, die zu zweit oder im Team arbeiten.">
<link rel="preload" href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" as="style" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" rel="stylesheet" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link rel="preload" href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" as="style" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
<link href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" rel="stylesheet" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
+2 -2
View File
@@ -33,8 +33,8 @@
<meta name="twitter:title" content="Tags">
<meta name="twitter:description" content="Freie Studio Management Software für Architekturbüros — Zeiterfassung, Rechnungen, Projekte, QR-Einzahlungsscheine. Tauri &#43; React. AGPL-3.0.">
<link rel="preload" href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" as="style" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link href="/css/compiled/main.min.9e7ba5cc2f8fede57d631892f51554687d9697bebe78354e35d5baa7683fa57a.css" rel="stylesheet" integrity="sha256-nnulzC&#43;P7eV9YxiS9RVUaH2Wl76&#43;eDVONdW6p2g/pXo=" />
<link rel="preload" href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" as="style" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
<link href="/css/compiled/main.min.4e6c8bd9baa81422228dbc44a656d3be761d2660e4820d4258f4959717cb7fe0.css" rel="stylesheet" integrity="sha256-TmyL2bqoFCIijbxEplbTvnYdJmDkgg1CWPSVlxfLf&#43;A=" />
+127 -16
View File
@@ -345,35 +345,146 @@
return;
}
const stat = (label, val) =>
'<div class="admin-stat"><div class="admin-stat-num">' + esc(val) + "</div>" +
'<div class="admin-stat-label">' + label + "</div></div>";
adminCache = { stats, accounts: accounts.accounts };
paintAdmin();
}
const rows = accounts.accounts.map((a) =>
"<tr>" +
// Such-/Filter-State des Cockpits.
let adminCache = null;
let adminSearch = "";
let adminPlanFilter = "";
function statCard(label, val, sub) {
return '<div class="admin-stat"><div class="admin-stat-num">' + esc(val) + "</div>" +
'<div class="admin-stat-label">' + esc(label) + "</div>" +
(sub ? '<div class="admin-stat-sub">' + esc(sub) + "</div>" : "") + "</div>";
}
function paintAdmin() {
const { stats } = adminCache;
const planNames = Object.values(stats.byPlan || {});
const planChips = planNames.length
? '<div class="admin-planbar">' + planNames.map((p) =>
'<div class="admin-planchip"><b>' + esc(p.name) + "</b> · " + esc(p.count) +
' <span class="muted">(CHF ' + esc(p.revenue) + ")</span></div>"
).join("") + "</div>"
: "";
// Kunden filtern (Suche über Email/Firma + Plan-Filter).
const q = adminSearch.trim().toLowerCase();
const list = adminCache.accounts.filter((a) => {
const matchesQ = !q || (a.email + " " + (a.company || "")).toLowerCase().includes(q);
const matchesP = !adminPlanFilter || a.plan === adminPlanFilter;
return matchesQ && matchesP;
});
const rows = list.map((a) =>
'<tr class="admin-row" data-id="' + esc(a.id) + '">' +
"<td><b>" + esc(a.email) + "</b>" +
(a.company ? '<div class="muted" style="font-size:12px">' + esc(a.company) + "</div>" : "") + "</td>" +
"<td>" + (a.plan ? esc(a.plan) + '<div class="muted" style="font-size:12px">' + esc(a.sub_status || "") + "</div>" : '<span class="muted">—</span>') + "</td>" +
"<td style=\"text-align:center\">" + esc(a.instance_count) + "</td>" +
"<td class=\"muted\" style=\"font-size:12px\">" + esc(new Date(a.created_at).toLocaleDateString("de-CH")) + "</td>" +
"<td>" + (a.plan ? '<span class="admin-badge">' + esc(a.plan) + "</span>" +
(a.sub_status && a.sub_status !== "active" ? ' <span class="muted" style="font-size:11px">' + esc(a.sub_status) + "</span>" : "")
: '<span class="muted">—</span>') + "</td>" +
'<td style="text-align:center">' + esc(a.instance_count) + "</td>" +
'<td class="muted" style="font-size:12px">' + esc(new Date(a.created_at).toLocaleDateString("de-CH")) + "</td>" +
'<td style="text-align:right"><span class="admin-chevron"></span></td>' +
"</tr>"
).join("");
const planOptions = ['<option value="">Alle Pläne</option>']
.concat(planNames.map((p) => '<option value="' + esc(p.name.toLowerCase()) + '"' +
(adminPlanFilter === p.name.toLowerCase() ? " selected" : "") + ">" + esc(p.name) + "</option>"))
.join("");
const html =
'<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:20px">' +
'<div class="hosting-title" style="text-align:left;margin:0">Admin</div>' +
'<div class="hosting-title" style="text-align:left;margin:0">Cockpit</div>' +
'<button class="hosting-link" id="alogout">Abmelden</button></div>' +
'<div class="admin-stats">' +
stat("Kunden", stats.accounts) +
stat("Aktive Abos", stats.activeSubscriptions) +
stat("Instanzen", stats.activeInstances + "/" + stats.instances) +
stat("MRR (CHF)", stats.mrrChf) +
statCard("Kunden", stats.accounts, "+" + (stats.newAccounts30d || 0) + " (30 T.)") +
statCard("Aktive Abos", stats.activeSubscriptions) +
statCard("Instanzen", stats.activeInstances + "/" + stats.instances,
(stats.suspendedInstances ? stats.suspendedInstances + " gesperrt" : "")) +
statCard("MRR", "CHF " + stats.mrrChf, "ARR CHF " + (stats.arrChf || stats.mrrChf * 12)) +
"</div>" +
'<div style="margin:24px 0 10px;font-weight:600;font-size:14px">Kunden</div>' +
'<table class="admin-table"><thead><tr><th>Konto</th><th>Abo</th><th>Inst.</th><th>Seit</th></tr></thead>' +
"<tbody>" + (rows || '<tr><td colspan="4" class="muted">Noch keine Kunden.</td></tr>') + "</tbody></table>";
planChips +
'<div class="admin-toolbar">' +
'<input class="hosting-input admin-search" id="asearch" placeholder="Suche E-Mail / Firma…" value="' + esc(adminSearch) + '">' +
'<select class="hosting-input admin-filter" id="afilter">' + planOptions + "</select>" +
"</div>" +
'<table class="admin-table"><thead><tr><th>Konto</th><th>Abo</th><th>Inst.</th><th>Seit</th><th></th></tr></thead>' +
"<tbody>" + (rows || '<tr><td colspan="5" class="muted">Keine Treffer.</td></tr>') + "</tbody></table>";
root.innerHTML = card(html, true);
root.querySelector("#alogout").onclick = () => { adminTok.clear(); renderAdminLogin(""); };
const s = root.querySelector("#asearch");
s.oninput = () => { adminSearch = s.value; const pos = s.selectionStart; paintAdmin(); const n = root.querySelector("#asearch"); n.focus(); n.setSelectionRange(pos, pos); };
root.querySelector("#afilter").onchange = (e) => { adminPlanFilter = e.target.value; paintAdmin(); };
root.querySelectorAll(".admin-row").forEach((r) =>
(r.onclick = () => renderAdminDetail(r.dataset.id))
);
}
// — Kunden-Detailansicht (Profil, Abo-Historie, Instanzen + Aktionen) —
async function renderAdminDetail(id) {
root.innerHTML = card('<div class="hosting-sub">Lädt…</div>', true);
let d;
try { d = await adminApi("GET", "/admin/accounts/" + id); }
catch (err) {
if (err.status === 401 || err.status === 403) { adminTok.clear(); return renderAdminLogin("Bitte neu anmelden."); }
root.innerHTML = card('<div class="hosting-msg err">' + esc(err.message) + "</div>"); return;
}
const { account: a, subscriptions, instances } = d;
const profile = [
["Firma", a.company], ["Ansprechperson", a.contact_name],
["Adresse", [a.street, [a.zip, a.city].filter(Boolean).join(" "), a.country].filter(Boolean).join(", ")],
["Telefon", a.phone],
].filter(([, v]) => v).map(([k, v]) =>
'<div class="hosting-row"><span class="muted">' + esc(k) + "</span><span>" + esc(v) + "</span></div>"
).join("") || '<div class="muted" style="padding:8px 0">Keine Profildaten.</div>';
const subRows = subscriptions.length ? subscriptions.map((s) =>
'<div class="hosting-row"><span><span class="admin-badge">' + esc(s.plan) + "</span> " + esc(s.status) +
(s.priceChf != null ? ' <span class="muted">CHF ' + esc(s.priceChf) + "</span>" : "") + "</span>" +
'<span class="muted" style="font-size:12px">' + esc(new Date(s.created_at).toLocaleDateString("de-CH")) + "</span></div>"
).join("") : '<div class="muted" style="padding:8px 0">Kein Abo.</div>';
const instRows = instances.length ? instances.map((i) =>
'<div class="hosting-row"><div><b>' + esc(i.label || i.studio_slug) + "</b> " +
'<span class="admin-badge ' + (i.status === "active" ? "ok" : "warn") + '">' + esc(i.status) + "</span>" +
'<div class="muted" style="font-size:12px">' + esc(i.instance_url) + "</div></div>" +
'<div style="display:flex;gap:8px">' +
'<a class="hosting-btn admin-mini" href="' + esc(i.instance_url) + '" target="_blank" rel="noreferrer">Öffnen</a>' +
(i.status === "active"
? '<button class="hosting-btn admin-mini warn" data-act="suspend" data-iid="' + esc(i.id) + '">Sperren</button>'
: '<button class="hosting-btn admin-mini ok" data-act="reactivate" data-iid="' + esc(i.id) + '">Reaktivieren</button>') +
"</div></div>"
).join("") : '<div class="muted" style="padding:8px 0">Keine Instanzen.</div>';
const html =
'<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:6px">' +
'<button class="hosting-link" id="aback"> Zurück</button>' +
'<button class="hosting-link" id="alogout">Abmelden</button></div>' +
'<div class="hosting-title" style="text-align:left;margin:0 0 2px">' + esc(a.email) + "</div>" +
'<div class="hosting-sub" style="text-align:left;margin-bottom:22px">Kunde seit ' +
esc(new Date(a.created_at).toLocaleDateString("de-CH")) + "</div>" +
'<div class="admin-section">Profil</div>' + profile +
'<div class="admin-section">Abo-Historie</div>' + subRows +
'<div class="admin-section">Instanzen</div>' + instRows;
root.innerHTML = card(html, true);
root.querySelector("#aback").onclick = () => paintAdmin();
root.querySelector("#alogout").onclick = () => { adminTok.clear(); renderAdminLogin(""); };
root.querySelectorAll("[data-act]").forEach((b) =>
(b.onclick = async () => {
b.disabled = true;
try {
await adminApi("POST", "/admin/instances/" + b.dataset.iid + "/" + b.dataset.act);
renderAdminDetail(id); // neu laden
} catch (err) { alert(err.message); b.disabled = false; }
})
);
}
({ login: renderLogin, register: renderRegister, konto: renderKonto, preise: renderPreise, admin: renderAdmin }[page] || renderLogin)();