wiki/c-work/0-ideas/2026-06-CustomerCases-step2-communication-mockup.html

762 lines
43 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>PORTA · Lösungen (Solution-Schicht Mockup)</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:opsz,wght@9..40,400;9..40,500;9..40,600;9..40,700&display=swap" rel="stylesheet" />
<style>
:root {
--color-bg: #F8F9FA;
--bg-primary: #ffffff;
--bg-secondary: #F7FAFC;
--bg-dark: #EDF2F7;
--surface-color: #F7FAFC;
--text-primary: #1A202C;
--text-secondary: #4A5568;
--text-tertiary: #718096;
--border-color: #E2E8F0;
--border-dark: #CBD5E0;
--primary-color: #4A6FA5;
--primary-color-dark: #3D5D8A;
--primary-light: rgba(74, 111, 165, 0.1);
--primary-color-light: rgba(74, 111, 165, 0.15);
--success: #38A169;
--success-bg: #C6F6D5;
--warning: #D69E2E;
--warning-bg: #FAF089;
--error: #C53030;
--error-bg: #FED7D7;
--gray: #718096;
--radius-lg: 10px;
--radius-md: 8px;
--radius-sm: 6px;
--font: "DM Sans", system-ui, -apple-system, sans-serif;
}
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; height: 100%; }
body {
font-family: var(--font);
background: var(--bg-primary);
color: var(--text-primary);
font-size: 14px;
-webkit-font-smoothing: antialiased;
}
/* ===== App shell ===== */
.app { display: flex; height: 100vh; overflow: hidden; }
.sidebar {
width: 280px; min-width: 280px; height: 100%;
background: var(--surface-color);
border-right: 1px solid var(--border-color);
display: flex; flex-direction: column;
}
.logo {
display: flex; align-items: center; gap: 8px;
padding: 20px 18px; border-bottom: 1px solid var(--border-color);
font-size: 22px; font-weight: 700; letter-spacing: -0.02em;
}
.logo .dot { width: 28px; height: 28px; border-radius: 8px; background: linear-gradient(135deg, var(--primary-color), var(--primary-color-dark)); display:flex; align-items:center; justify-content:center; color:#fff; font-size:15px; }
.logo .pow { color: var(--text-primary); }
.logo .on { color: var(--primary-color); }
.nav { flex: 1; overflow-y: auto; padding: 12px 10px; }
.nav .group { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-tertiary); padding: 14px 10px 6px; }
.nav a {
display: flex; align-items: center; gap: 10px;
padding: 9px 12px; border-radius: var(--radius-sm);
color: var(--text-secondary); text-decoration: none; font-weight: 500;
margin: 1px 0; cursor: pointer; font-size: 13.5px;
}
.nav a:hover { background: rgba(0,0,0,0.03); color: var(--text-primary); }
.nav a.active { background: var(--primary-light); color: var(--primary-color-dark); font-weight: 600; }
.nav a .ic { width: 18px; text-align: center; opacity: 0.85; }
.nav .sub { padding-left: 14px; border-left: 1px solid var(--border-color); margin-left: 16px; }
.userbox { padding: 14px; border-top: 1px solid var(--border-color); display: flex; align-items: center; gap: 10px; }
.avatar { width: 34px; height: 34px; border-radius: 50%; background: var(--primary-color); color: #fff; display:flex; align-items:center; justify-content:center; font-weight:600; font-size:13px; }
.userbox .meta { line-height: 1.2; }
.userbox .nm { font-weight: 600; font-size: 13px; }
.userbox .rl { font-size: 11px; color: var(--text-tertiary); }
/* ===== Main ===== */
.main { flex: 1; min-width: 0; display: flex; flex-direction: column; overflow: hidden; }
.topbar {
height: 56px; flex-shrink: 0; border-bottom: 1px solid var(--border-color);
display: flex; align-items: center; justify-content: space-between; padding: 0 24px;
background: var(--bg-primary);
}
.crumb { font-size: 13px; color: var(--text-tertiary); }
.crumb b { color: var(--text-primary); font-weight: 600; }
.topbar .right { display: flex; align-items: center; gap: 14px; color: var(--text-tertiary); font-size: 13px; }
.content { flex: 1; overflow-y: auto; padding: 24px 28px; background: var(--bg-primary); }
.wrap { max-width: 1180px; margin: 0 auto; }
/* ===== Page header ===== */
.pagehead { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; margin-bottom: 22px; }
.pagehead h1 { margin: 0 0 4px; font-size: 23px; font-weight: 700; letter-spacing: -0.01em; }
.pagehead p { margin: 0; color: var(--text-secondary); font-size: 13.5px; max-width: 640px; }
/* ===== Buttons ===== */
.btn { display: inline-flex; align-items: center; gap: 8px; border: none; border-radius: var(--radius-sm); font-family: var(--font); font-weight: 500; font-size: 14px; padding: 8px 18px; cursor: pointer; transition: all .18s cubic-bezier(.4,0,.2,1); letter-spacing: .01em; white-space: nowrap; }
.btn-primary { background: linear-gradient(180deg, var(--primary-color), var(--primary-color-dark)); color: #fff; box-shadow: 0 1px 2px rgba(0,0,0,.12), inset 0 1px 0 rgba(255,255,255,.12); }
.btn-primary:hover { transform: translateY(-1px); box-shadow: 0 2px 6px rgba(0,0,0,.16); }
.btn-secondary { background: #fff; color: var(--text-secondary); border: 1px solid var(--border-color); box-shadow: 0 1px 2px rgba(0,0,0,.06); }
.btn-secondary:hover { background: var(--gray); color: #fff; border-color: var(--gray); }
.btn-success { background: linear-gradient(180deg, var(--success), #2F855A); color: #fff; }
.btn-success:hover { transform: translateY(-1px); }
.btn-ghost { background: transparent; color: var(--primary-color); border: 1px solid transparent; }
.btn-ghost:hover { background: var(--primary-light); }
.btn-sm { padding: 6px 13px; font-size: 12.5px; }
.btn[disabled] { opacity: .5; cursor: not-allowed; }
/* ===== Cards / list ===== */
.toolbar { display: flex; align-items: center; gap: 10px; margin-bottom: 16px; }
.search { flex: 1; max-width: 320px; position: relative; }
.search input { width: 100%; padding: 8px 12px 8px 32px; border: 1px solid var(--border-color); border-radius: var(--radius-sm); font-family: var(--font); font-size: 13.5px; background: #fff; }
.search .ic { position: absolute; left: 10px; top: 50%; transform: translateY(-50%); color: var(--text-tertiary); }
.seg { display: inline-flex; border: 1px solid var(--border-color); border-radius: var(--radius-sm); overflow: hidden; }
.seg button { border: none; background: #fff; padding: 7px 14px; font-family: var(--font); font-size: 13px; color: var(--text-secondary); cursor: pointer; }
.seg button.on { background: var(--primary-light); color: var(--primary-color-dark); font-weight: 600; }
.seg button + button { border-left: 1px solid var(--border-color); }
.sol-list { display: grid; gap: 12px; }
.sol-card {
border: 1px solid var(--border-color); border-radius: var(--radius-md); background: #fff;
padding: 16px 18px; display: grid; grid-template-columns: 42px 1fr auto; gap: 14px; align-items: center;
cursor: pointer; transition: all .15s ease;
}
.sol-card:hover { border-color: var(--border-dark); box-shadow: 0 4px 14px rgba(0,0,0,.06); transform: translateY(-1px); }
.sol-ic { width: 42px; height: 42px; border-radius: var(--radius-md); display: flex; align-items: center; justify-content: center; font-size: 20px; background: var(--primary-light); }
.sol-main .nm { font-weight: 600; font-size: 15px; margin-bottom: 3px; }
.sol-main .ds { color: var(--text-secondary); font-size: 13px; }
.sol-meta { display: flex; flex-direction: column; align-items: flex-end; gap: 6px; text-align: right; }
.sol-meta .line { font-size: 12px; color: var(--text-tertiary); }
.sol-meta .line b { color: var(--text-secondary); font-weight: 600; }
.badge { display: inline-flex; align-items: center; gap: 5px; font-size: 11.5px; font-weight: 600; padding: 3px 9px; border-radius: 999px; }
.badge .d { width: 7px; height: 7px; border-radius: 50%; }
.b-active { background: var(--success-bg); color: #22643f; }
.b-active .d { background: var(--success); }
.b-inactive { background: #EDF2F7; color: var(--text-secondary); }
.b-inactive .d { background: var(--gray); }
.b-error { background: var(--error-bg); color: #822; }
.b-error .d { background: var(--error); }
.b-running { background: var(--primary-light); color: var(--primary-color-dark); }
.b-running .d { background: var(--primary-color); animation: pulse 1.2s infinite; }
.b-warn { background: #FEFCBF; color: #744210; }
.b-warn .d { background: var(--warning); }
@keyframes pulse { 0%,100%{opacity:1;} 50%{opacity:.3;} }
/* ===== Detail ===== */
.backlink { display: inline-flex; align-items: center; gap: 6px; color: var(--text-secondary); font-size: 13px; cursor: pointer; margin-bottom: 14px; background:none; border:none; font-family:var(--font); padding:0;}
.backlink:hover { color: var(--primary-color); }
.detail-head { display: flex; align-items: flex-start; gap: 16px; margin-bottom: 18px; }
.detail-head .sol-ic { width: 52px; height: 52px; font-size: 25px; }
.detail-head h1 { margin: 0 0 5px; font-size: 21px; }
.detail-head .sub { color: var(--text-secondary); font-size: 13px; }
.detail-actions { margin-left: auto; display: flex; gap: 8px; align-items: center; }
.tabs { display: flex; gap: 2px; border-bottom: 1px solid var(--border-color); margin-bottom: 22px; }
.tabs button { border: none; background: none; font-family: var(--font); font-size: 14px; font-weight: 500; color: var(--text-secondary); padding: 11px 16px; cursor: pointer; border-bottom: 2px solid transparent; margin-bottom: -1px; }
.tabs button:hover { color: var(--text-primary); }
.tabs button.on { color: var(--primary-color-dark); border-bottom-color: var(--primary-color); font-weight: 600; }
.tabpane { display: none; }
.tabpane.on { display: block; animation: fade .2s ease; }
@keyframes fade { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
.grid2 { display: grid; grid-template-columns: 1fr 320px; gap: 22px; align-items: start; }
@media (max-width: 900px){ .grid2 { grid-template-columns: 1fr; } }
.card { border: 1px solid var(--border-color); border-radius: var(--radius-md); background: #fff; overflow: hidden; }
.card-h { padding: 13px 16px; border-bottom: 1px solid var(--border-color); font-weight: 600; font-size: 14px; background: var(--bg-secondary); display:flex; align-items:center; justify-content:space-between; }
.card-h .hint { font-weight: 500; font-size: 11.5px; color: var(--text-tertiary); }
.card-b { padding: 16px; }
.field { margin-bottom: 16px; }
.field:last-child { margin-bottom: 0; }
.field label { display: block; font-weight: 600; font-size: 12.5px; margin-bottom: 6px; }
.field .desc { font-size: 11.5px; color: var(--text-tertiary); margin: -3px 0 7px; }
.field input[type=text], .field input[type=number], .field select, .field textarea {
width: 100%; padding: 8px 11px; border: 1px solid var(--border-color); border-radius: var(--radius-sm);
font-family: var(--font); font-size: 13.5px; background: #fff; color: var(--text-primary);
}
.field input:focus, .field select:focus, .field textarea:focus { outline: none; border-color: var(--primary-color); box-shadow: 0 0 0 3px var(--primary-light); }
.row2 { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.chips { display: flex; flex-wrap: wrap; gap: 7px; }
.chip { display: inline-flex; align-items: center; gap: 6px; background: var(--primary-light); color: var(--primary-color-dark); border-radius: 999px; padding: 5px 11px; font-size: 12.5px; font-weight: 500; }
.chip .x { cursor: pointer; opacity: .6; }
.chip.add { background: #fff; border: 1px dashed var(--border-dark); color: var(--text-secondary); cursor: pointer; }
.toggle { display: inline-flex; align-items: center; gap: 9px; cursor: pointer; }
.switch { width: 38px; height: 22px; border-radius: 999px; background: var(--success); position: relative; transition: background .2s; }
.switch::after { content: ""; position: absolute; top: 2px; left: 18px; width: 18px; height: 18px; border-radius: 50%; background: #fff; transition: left .2s; box-shadow: 0 1px 2px rgba(0,0,0,.2); }
.switch.off { background: var(--border-dark); }
.switch.off::after { left: 2px; }
.sidecard .kv { display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px dashed var(--border-color); font-size: 13px; }
.sidecard .kv:last-child { border-bottom: none; }
.sidecard .kv span { color: var(--text-tertiary); }
.sidecard .kv b { font-weight: 600; }
table.runs { width: 100%; border-collapse: collapse; font-size: 13px; }
table.runs th { text-align: left; font-size: 11px; text-transform: uppercase; letter-spacing: .04em; color: var(--text-tertiary); padding: 9px 14px; border-bottom: 1px solid var(--border-color); background: var(--bg-secondary); }
table.runs td { padding: 11px 14px; border-bottom: 1px solid var(--border-color); }
table.runs tr:hover td { background: var(--bg-secondary); }
table.runs tr:last-child td { border-bottom: none; }
.mono { font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 12px; color: var(--text-secondary); }
/* run trace steps */
.trace { display: grid; gap: 8px; }
.step { display: grid; grid-template-columns: 24px 1fr auto; gap: 11px; align-items: center; padding: 10px 13px; border: 1px solid var(--border-color); border-radius: var(--radius-sm); background: #fff; }
.step .dotc { width: 24px; height: 24px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 12px; color: #fff; }
.step .nm { font-weight: 500; }
.step .nm small { color: var(--text-tertiary); font-weight: 400; }
.step .dur { font-size: 12px; color: var(--text-tertiary); }
.ok { background: var(--success); }
.warnc { background: var(--warning); }
/* output files */
.files { display: grid; gap: 10px; }
.file { display: grid; grid-template-columns: 38px 1fr auto; gap: 12px; align-items: center; padding: 11px 14px; border: 1px solid var(--border-color); border-radius: var(--radius-sm); background: #fff; }
.file .pic { width: 38px; height: 38px; border-radius: var(--radius-sm); background: var(--error-bg); color: #9b2c2c; display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 11px; }
.file .nm { font-weight: 500; }
.file .mt { font-size: 12px; color: var(--text-tertiary); }
.graph-strip { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; padding: 14px; background: var(--bg-secondary); border: 1px solid var(--border-color); border-radius: var(--radius-md); margin-top: 14px; }
.gnode { background: #fff; border: 1px solid var(--border-dark); border-radius: var(--radius-sm); padding: 7px 11px; font-size: 12px; font-weight: 500; display: flex; align-items: center; gap: 6px; }
.gnode .tag { font-size: 10px; background: var(--primary-light); color: var(--primary-color-dark); padding: 1px 6px; border-radius: 4px; font-weight: 600; }
.garrow { color: var(--text-tertiary); }
.gnode.ai { border-color: var(--primary-color); }
.note { display: flex; gap: 9px; padding: 11px 13px; border-radius: var(--radius-sm); font-size: 12.5px; line-height: 1.5; }
.note-info { background: var(--primary-light); color: var(--primary-color-dark); }
.note-warn { background: #FEFCBF; color: #744210; }
/* ===== Modal ===== */
.overlay { position: fixed; inset: 0; background: rgba(26,32,44,.5); display: none; align-items: center; justify-content: center; z-index: 100; padding: 24px; }
.overlay.on { display: flex; }
.modal { background: #fff; border-radius: var(--radius-lg); width: 100%; max-width: 720px; max-height: 88vh; overflow: hidden; display: flex; flex-direction: column; box-shadow: 0 24px 60px rgba(0,0,0,.3); }
.modal-h { padding: 18px 22px; border-bottom: 1px solid var(--border-color); display: flex; align-items: center; justify-content: space-between; }
.modal-h h2 { margin: 0; font-size: 18px; }
.modal-h .close { background: none; border: none; font-size: 22px; color: var(--text-tertiary); cursor: pointer; line-height: 1; }
.modal-b { padding: 22px; overflow-y: auto; }
.modal-f { padding: 16px 22px; border-top: 1px solid var(--border-color); display: flex; justify-content: flex-end; gap: 10px; }
.tmpl-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
@media (max-width: 640px){ .tmpl-grid { grid-template-columns: 1fr; } }
.tmpl { border: 1px solid var(--border-color); border-radius: var(--radius-md); padding: 14px; cursor: pointer; transition: all .15s; }
.tmpl:hover, .tmpl.sel { border-color: var(--primary-color); background: var(--primary-light); }
.tmpl .ti { font-size: 22px; margin-bottom: 8px; }
.tmpl .tn { font-weight: 600; font-size: 14px; margin-bottom: 3px; }
.tmpl .td { font-size: 12px; color: var(--text-secondary); }
.divider { display: flex; align-items: center; gap: 12px; margin: 20px 0; color: var(--text-tertiary); font-size: 12px; }
.divider::before, .divider::after { content: ""; flex: 1; height: 1px; background: var(--border-color); }
.toast { position: fixed; bottom: 24px; left: 50%; transform: translateX(-50%) translateY(20px); background: var(--text-primary); color: #fff; padding: 12px 20px; border-radius: var(--radius-md); font-size: 13.5px; font-weight: 500; box-shadow: 0 8px 24px rgba(0,0,0,.25); opacity: 0; transition: all .25s; z-index: 200; display: flex; align-items: center; gap: 9px; }
.toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
.toast .d { width: 8px; height: 8px; border-radius: 50%; background: var(--success); }
</style>
</head>
<body>
<div class="app">
<!-- Sidebar -->
<aside class="sidebar">
<div class="logo"><span class="dot">P</span><span><span class="pow">Power</span><span class="on">On</span></span></div>
<nav class="nav">
<div class="group">Plattform</div>
<a><span class="ic"></span> Dashboard</a>
<a><span class="ic"></span> Workspace</a>
<div class="group">Feature · Trustee</div>
<a><span class="ic"></span> Übersicht</a>
<a><span class="ic">🧾</span> Belege</a>
<a><span class="ic">📊</span> Buchhaltungssystem</a>
<a><span class="ic">🔌</span> Datenquellen</a>
<a class="active"><span class="ic"></span> Lösungen</a>
<div class="group">Administration</div>
<a><span class="ic">👥</span> Benutzer &amp; Rollen</a>
<a><span class="ic">💳</span> Abrechnung</a>
</nav>
<div class="userbox">
<div class="avatar">PB</div>
<div class="meta"><div class="nm">Päde Bruderer</div><div class="rl">trustee-admin · Pling</div></div>
</div>
</aside>
<!-- Main -->
<div class="main">
<div class="topbar">
<div class="crumb">Trustee · <b id="crumb">Lösungen</b></div>
<div class="right"><span>Kaffee-Klatsch Holding</span><span>·</span><span>◑ Hell</span></div>
</div>
<div class="content">
<div class="wrap">
<!-- ===================== VIEW: LIST ===================== -->
<section id="viewList">
<div class="pagehead">
<div>
<h1>Lösungen</h1>
<p>Wiederkehrende Aufgaben als konfigurierte Workflows — einmal einstellen, automatisch laufen lassen, Ergebnisse ansehen. Kein Code nötig.</p>
</div>
<button class="btn btn-primary" onclick="openCatalog()"><span></span> Neue Lösung</button>
</div>
<div class="toolbar">
<div class="search"><span class="ic"></span><input type="text" placeholder="Lösungen durchsuchen…" /></div>
<div class="seg">
<button class="on">Alle</button>
<button>Aktiv</button>
<button>Inaktiv</button>
</div>
</div>
<div class="sol-list" id="solList"></div>
</section>
<!-- ===================== VIEW: DETAIL ===================== -->
<section id="viewDetail" style="display:none;">
<button class="backlink" onclick="showList()">← Zurück zu Lösungen</button>
<div class="detail-head">
<div class="sol-ic" id="dIcon">📊</div>
<div>
<h1 id="dName"></h1>
<div class="sub" id="dSub"></div>
</div>
<div class="detail-actions">
<button class="btn btn-secondary btn-sm" onclick="toast('Workflow im Graphical Editor geöffnet')">Im Editor öffnen</button>
<button class="btn btn-success btn-sm" id="dRunBtn" onclick="runNow()">▶ Jetzt ausführen</button>
</div>
</div>
<div class="tabs">
<button class="on" onclick="switchTab(event,'tEinst')">Einstellungen</button>
<button onclick="switchTab(event,'tTest')">Testlauf</button>
<button onclick="switchTab(event,'tRuns')">Läufe</button>
<button onclick="switchTab(event,'tOut')">Ausgabe</button>
</div>
<!-- TAB: Einstellungen -->
<div class="tabpane on" id="tEinst">
<div class="grid2">
<div style="display:grid; gap:18px;">
<div class="card">
<div class="card-h">Konfiguration <span class="hint">aus settingsSchema generiert</span></div>
<div class="card-b" id="settingsBody"></div>
</div>
<div class="card">
<div class="card-h">Trigger &amp; Zeitplan</div>
<div class="card-b" id="triggerBody"></div>
</div>
</div>
<div style="display:grid; gap:18px;">
<div class="card sidecard">
<div class="card-h">Status</div>
<div class="card-b" id="statusBody"></div>
</div>
<div class="card">
<div class="card-h">Workflow</div>
<div class="card-b">
<div style="font-size:12.5px; color:var(--text-secondary); margin-bottom:8px;">Diese Lösung kapselt einen Graphen aus vorhandenen Bausteinen:</div>
<div id="graphStrip"></div>
</div>
</div>
</div>
</div>
<div style="display:flex; justify-content:flex-end; gap:10px; margin-top:18px;">
<button class="btn btn-secondary" onclick="showList()">Abbrechen</button>
<button class="btn btn-primary" onclick="toast('Einstellungen gespeichert')">Speichern</button>
</div>
</div>
<!-- TAB: Testlauf -->
<div class="tabpane" id="tTest">
<div class="card">
<div class="card-h">Probelauf (Dry-Run) <span class="hint">keine Mails, keine Seiteneffekte</span></div>
<div class="card-b">
<p style="margin-top:0; color:var(--text-secondary); font-size:13.5px;">Führt die Lösung mit den aktuellen Einstellungen testweise aus. Reports werden erzeugt und angezeigt, aber <b>nicht</b> versendet.</p>
<button class="btn btn-primary" onclick="startTest()">▶ Testlauf starten</button>
<div id="testTrace" style="margin-top:18px;"></div>
</div>
</div>
</div>
<!-- TAB: Läufe -->
<div class="tabpane" id="tRuns">
<div class="card">
<div class="card-h">Lauf-Historie</div>
<table class="runs" id="runsTable"></table>
</div>
<div class="card" style="margin-top:18px;">
<div class="card-h">Lauf-Trace <span class="hint">Lauf #1042 · 15.05.2026 06:00</span></div>
<div class="card-b">
<div class="trace" id="traceBody"></div>
</div>
</div>
</div>
<!-- TAB: Ausgabe -->
<div class="tabpane" id="tOut">
<div class="card">
<div class="card-h">Erzeugte Dokumente <span class="hint">archiviert im Mandanten-Datenraum</span></div>
<div class="card-b"><div class="files" id="filesBody"></div></div>
</div>
</div>
</section>
</div>
</div>
</div>
</div>
<!-- ===================== MODAL: Katalog / Neu ===================== -->
<div class="overlay" id="catalog">
<div class="modal">
<div class="modal-h">
<h2>Neue Lösung anlegen</h2>
<button class="close" onclick="closeCatalog()">×</button>
</div>
<div class="modal-b">
<div style="font-weight:600; margin-bottom:12px; font-size:14px;">Aus Vorlage (Use-Case-Katalog)</div>
<div class="tmpl-grid">
<div class="tmpl" onclick="selTmpl(this)"><div class="ti">🔄</div><div class="tn">Systeme synchronisieren</div><div class="td">Quelle → Ziel, periodisch (z. B. SelectLine → RMA)</div></div>
<div class="tmpl" onclick="selTmpl(this)"><div class="ti">📈</div><div class="tn">Periodisches Reporting</div><div class="td">Daten konsolidieren, PDF erzeugen, verteilen</div></div>
<div class="tmpl" onclick="selTmpl(this)"><div class="ti">📥</div><div class="tn">Dokumente verarbeiten</div><div class="td">Dateien einlesen, KI-Auswertung, Antwort</div></div>
<div class="tmpl" onclick="selTmpl(this)"><div class="ti">✉️</div><div class="tn">Benachrichtigung</div><div class="td">Bei Ereignis rollenbasiert mailen</div></div>
</div>
<div class="divider">oder mit KI beschreiben</div>
<div class="field">
<label>Beschreibe dein Bedürfnis — die KI baut den Workflow</label>
<div class="desc">Der Workflow-Agent komponiert daraus einen Graphen aus vorhandenen Bausteinen. Du konfigurierst danach nur noch die Einstellungen.</div>
<textarea rows="3" placeholder="z. B. «Schicke mir am 15. jedes Monats einen konsolidierten Bericht über alle 5 Store-Buchhaltungen und je einen Kennzahlen-Report an jeden Store-Manager.»"></textarea>
</div>
<div class="note note-info"><span></span><span>Vorlagen werden deterministisch instanziiert (kein KI-Risiko). KI-Generierung erzeugt einen Entwurf, den du vor dem Aktivieren prüfst.</span></div>
</div>
<div class="modal-f">
<button class="btn btn-secondary" onclick="closeCatalog()">Abbrechen</button>
<button class="btn btn-primary" onclick="generateSolution()">✦ Lösung erstellen</button>
</div>
</div>
</div>
<div class="toast" id="toast"><span class="d"></span><span id="toastMsg">Gespeichert</span></div>
<script>
const solutions = {
pling: {
icon: "📊",
name: "Monatsreporting Kaffee-Klatsch",
sub: "Periodisches Reporting · 5 RMA-Buchhaltungen · aus Vorlage",
status: "active",
statusLabel: "Aktiv",
lastRun: "15.05.2026 06:00 · erfolgreich",
nextRun: "15.06.2026 06:00",
restricted: true,
kv: [
["Status", '<span class="badge b-active"><span class="d"></span>Aktiv</span>'],
["Letzter Lauf", "15.05.2026 · OK"],
["Nächster Lauf", "15.06.2026 06:00"],
["Läufe gesamt", "7"],
["Erfolgsquote", "100%"],
["Instanzen", "5 Stores"]
],
settings: `
<div class="field">
<label>Feature-Instanzen (Fan-out)</label>
<div class="desc">Über welche Buchhaltungen läuft die Lösung — eine Solution, mehrere Instanzen.</div>
<div class="chips">
<span class="chip">Coffeeshop Zürich <span class="x">×</span></span>
<span class="chip">Coffeeshop Bern <span class="x">×</span></span>
<span class="chip">Coffeeshop Basel <span class="x">×</span></span>
<span class="chip">Coffeeshop Luzern <span class="x">×</span></span>
<span class="chip">Coffeeshop St. Gallen <span class="x">×</span></span>
<span class="chip add"> Instanz</span>
</div>
</div>
<div class="field">
<label>Berichts-Periode</label>
<div class="row2">
<select><option>Vormonat (Standard)</option><option>Year-to-date</option><option>Freie Periode</option></select>
<input type="text" value="Mai 2026" />
</div>
</div>
<div class="field">
<label>Konten-Bereiche</label>
<div class="desc">Kundenspezifisch — als Settings, nicht im Code.</div>
<div class="row2">
<div><div style="font-size:11px;color:var(--text-tertiary);margin-bottom:4px;">Umsatz</div><input type="text" value="30003999"/></div>
<div><div style="font-size:11px;color:var(--text-tertiary);margin-bottom:4px;">Warenkosten</div><input type="text" value="60006299"/></div>
</div>
<div class="row2" style="margin-top:10px;">
<div><div style="font-size:11px;color:var(--text-tertiary);margin-bottom:4px;">Personalaufwand</div><input type="text" value="50005099"/></div>
<div></div>
</div>
</div>
<div class="field">
<label>Empfänger (rollenbasiert)</label>
<div class="desc">Verteiler wird automatisch aus Trustee-Rollen aufgelöst — keine Listen pflegen (rbac.queryUsersByRole).</div>
<div class="chips">
<span class="chip">Holding-Bericht → trustee-accountant <span class="x">×</span></span>
<span class="chip">Store-Kennzahlen → trustee-client <span class="x">×</span></span>
<span class="chip">Lauf-Protokoll → trustee-admin <span class="x">×</span></span>
</div>
</div>
<div class="field">
<label class="toggle"><span class="switch off"></span> Bei unvollständiger Buchhaltung blockieren</label>
<div class="desc">Aus = durchlaufen &amp; im Bericht markieren (stopOnIncomplete: false). Ein = warten bis alle 5 fertig.</div>
</div>`,
trigger: `
<div class="field">
<label class="toggle"><span class="switch"></span> Geplant (Zeitplan)</label>
<div class="desc" style="margin-left:48px;">Cron: <span class="mono">0 6 15 * *</span> · Europe/Zurich — am 15. um 06:00.</div>
</div>
<div class="row2">
<div class="field"><label>Tag im Monat</label><input type="number" value="15"/></div>
<div class="field"><label>Uhrzeit</label><input type="text" value="06:00"/></div>
</div>
<div class="field">
<label class="toggle"><span class="switch"></span> Manueller Start erlaubt</label>
</div>
<div class="field">
<label>Wer darf manuell starten</label>
<div class="desc">Zugriffskontrolle am Trigger (accessControl.requiredRoles).</div>
<select><option>Nur trustee-admin</option><option>trustee-admin + trustee-accountant</option><option>Alle mit Zugriff</option></select>
</div>`,
graph: [
["trigger", "Zeitplan / Manuell"],
["loop", "5 Instanzen"],
["trustee", "Salden holen"],
["data", "Konsolidieren"],
["ai", "6 PDFs erzeugen", true],
["rbac", "Empfänger auflösen"],
["outlook", "Versenden"]
],
runs: [
["#1042", "15.05.2026 06:00", "Zeitplan", "active", "Erfolgreich", "2m 14s", "6 PDFs · 8 Mails"],
["#1041", "15.04.2026 06:00", "Zeitplan", "warn", "Mit Hinweis", "2m 41s", "1 BuHa unvollständig"],
["#1038", "15.03.2026 06:00", "Zeitplan", "active", "Erfolgreich", "2m 02s", "6 PDFs · 8 Mails"],
["#1031", "21.02.2026 14:22", "Manuell · Päde", "active", "Erfolgreich", "2m 10s", "Ad-hoc YTD"]
],
trace: [
["ok", "trigger.scheduled", "Cron 0 6 15 * *", "0.1s"],
["ok", "flow.loop · 5 Instanzen", "parallel, concurrency 5", "1.2s"],
["ok", "trustee.refreshAccountingData", "5/5 Buchhaltungen gesynct", "44s"],
["ok", "trustee.queryData · Salden", "akt. + Vorjahr", "9s"],
["ok", "data.consolidate", "sumByKey → HoldingFigures", "0.6s"],
["ok", "ai.generateDocument · Holding", "1 PDF, neutralisiert", "31s"],
["ok", "ai.generateDocument · Store-KPI ×5", "5 PDFs", "26s"],
["ok", "rbac.queryUsersByRole", "8 Empfänger aufgelöst", "0.4s"],
["ok", "email.sendEmail", "8 Mails versendet", "3s"]
],
files: [
["Monatsbericht Holding Mai 2026", "PDF · 412 KB · Holding-Instanz", "PDF"],
["Kennzahlen Coffeeshop Zürich", "PDF · 88 KB · Store-Instanz", "PDF"],
["Kennzahlen Coffeeshop Bern", "PDF · 86 KB · Store-Instanz", "PDF"],
["Kennzahlen Coffeeshop Basel", "PDF · 87 KB · Store-Instanz", "PDF"],
["Kennzahlen Coffeeshop Luzern", "PDF · 85 KB · Store-Instanz", "PDF"],
["Kennzahlen Coffeeshop St. Gallen", "PDF · 84 KB · Store-Instanz", "PDF"]
]
},
selectline: {
icon: "🔄",
name: "SelectLine → RMA Sync",
sub: "Systeme synchronisieren · täglich · aus Vorlage",
status: "active", statusLabel: "Aktiv",
lastRun: "04.06.2026 06:00 · erfolgreich", nextRun: "05.06.2026 06:00",
restricted: false,
kv: [
["Status", '<span class="badge b-active"><span class="d"></span>Aktiv</span>'],
["Letzter Lauf", "04.06.2026 · OK"],
["Nächster Lauf", "05.06.2026 06:00"],
["Läufe gesamt", "63"],
["Erfolgsquote", "98%"]
],
settings: `
<div class="field"><label>Quell-System</label><select><option>SelectLine (API)</option></select></div>
<div class="field"><label>Ziel-System</label><select><option>RunMyAccounts (RMA)</option></select></div>
<div class="field">
<label>Konten-Mapping</label>
<div class="desc">Zuordnung Quell- zu Zielkonten — kundenspezifisch, als Settings.</div>
<div class="chips"><span class="chip">1000 → 1020 <span class="x">×</span></span><span class="chip">3400 → 3000 <span class="x">×</span></span><span class="chip add"> Mapping</span></div>
</div>
<div class="field"><label class="toggle"><span class="switch"></span> Duplikat-Prüfung aktiv</label></div>`,
trigger: `
<div class="field"><label class="toggle"><span class="switch"></span> Geplant (täglich)</label><div class="desc" style="margin-left:48px;">Cron <span class="mono">0 6 * * *</span> · Europe/Zurich</div></div>
<div class="field"><label>Uhrzeit</label><input type="text" value="06:00"/></div>`,
graph: [["trigger","Täglich 06:00"],["selectline","Rechnungen lesen"],["data","Mapping"],["rma","Buchen", true]],
runs: [
["#0631","04.06.2026 06:00","Zeitplan","active","Erfolgreich","48s","14 Rechnungen"],
["#0630","03.06.2026 06:00","Zeitplan","active","Erfolgreich","51s","9 Rechnungen"],
["#0629","02.06.2026 06:00","Zeitplan","error","Fehler","12s","API-Timeout SelectLine"]
],
trace: [
["ok","trigger.scheduled","Cron 0 6 * * *","0.1s"],
["ok","selectline.listInvoices","14 neue Rechnungen","6s"],
["ok","data.mapAccounts","Mapping angewandt","0.3s"],
["ok","rma.pushInvoice","14 gebucht, 0 Duplikate","41s"]
],
files: [["Sync-Protokoll 04.06.2026","CSV · 12 KB","CSV"]]
},
pwg: {
icon: "📥",
name: "Mietzins-Bestätigungen (PWG)",
sub: "Dokumente verarbeiten · Pilot · per KI generiert",
status: "inactive", statusLabel: "Inaktiv (Entwurf)",
lastRun: "—", nextRun: "nicht aktiviert",
restricted: false,
kv: [
["Status", '<span class="badge b-inactive"><span class="d"></span>Inaktiv</span>'],
["Herkunft", "KI-Entwurf"],
["Letzter Lauf", "—"],
["Nächster Lauf", "nicht aktiviert"]
],
settings: `
<div class="field"><label>SharePoint-Ordner</label><input type="text" value="/Mietzins/Eingang 2026"/></div>
<div class="field"><label>Abacus-Mandant</label><select><option>PWG Immobilien AG</option></select></div>
<div class="field"><label>Empfänger (Übersicht)</label><div class="chips"><span class="chip">trustee-accountant <span class="x">×</span></span><span class="chip add"> Rolle</span></div></div>
<div class="note note-warn"><span>⚠</span><span>KI-Entwurf — bitte Einstellungen prüfen und einen Testlauf machen, bevor du aktivierst.</span></div>`,
trigger: `<div class="field"><label class="toggle"><span class="switch off"></span> Geplant</label><div class="desc" style="margin-left:48px;">Noch nicht aktiviert.</div></div>`,
graph: [["trigger","Manuell"],["sharepoint","Dateien lesen"],["loop","pro Datei"],["ai","Extrahieren", true],["data","Schreiben"],["email","Antwort"]],
runs: [],
trace: [],
files: []
}
};
const listOrder = ["pling","selectline","pwg"];
function _badge(s){
const map = { active:["b-active","Aktiv"], inactive:["b-inactive","Inaktiv"], error:["b-error","Fehler"], warn:["b-warn","Hinweis"], running:["b-running","Läuft"] };
const [cls,lbl] = map[s] || map.inactive;
return `<span class="badge ${cls}"><span class="d"></span>${lbl}</span>`;
}
function renderList(){
const el = document.getElementById("solList");
el.innerHTML = listOrder.map(id => {
const s = solutions[id];
return `<div class="sol-card" onclick="showDetail('${id}')">
<div class="sol-ic">${s.icon}</div>
<div class="sol-main">
<div class="nm">${s.name}</div>
<div class="ds">${s.sub}</div>
</div>
<div class="sol-meta">
${_badge(s.status)}
<div class="line">Letzter Lauf: <b>${s.lastRun}</b></div>
<div class="line">Nächster: <b>${s.nextRun}</b></div>
</div>
</div>`;
}).join("");
}
let current = null;
function showDetail(id){
current = id;
const s = solutions[id];
document.getElementById("crumb").textContent = s.name;
document.getElementById("dIcon").textContent = s.icon;
document.getElementById("dName").textContent = s.name;
document.getElementById("dSub").textContent = s.sub;
document.getElementById("settingsBody").innerHTML = s.settings;
document.getElementById("triggerBody").innerHTML = s.trigger;
document.getElementById("statusBody").innerHTML = s.kv.map(([k,v]) => `<div class="kv"><span>${k}</span><b>${v}</b></div>`).join("");
document.getElementById("graphStrip").innerHTML = `<div class="graph-strip">` + s.graph.map((g,i) => {
const arrow = i < s.graph.length-1 ? '<span class="garrow">→</span>' : '';
return `<div class="gnode ${g[2]?'ai':''}"><span class="tag">${g[0]}</span>${g[1]}</div>${arrow}`;
}).join("") + `</div>`;
// run button restriction
const rb = document.getElementById("dRunBtn");
if (s.restricted){ rb.title = "Nur trustee-admin"; }
// runs table
const rt = document.getElementById("runsTable");
if (s.runs.length){
rt.innerHTML = `<thead><tr><th>Lauf</th><th>Zeitpunkt</th><th>Auslöser</th><th>Status</th><th>Dauer</th><th>Ergebnis</th></tr></thead><tbody>` +
s.runs.map(r => `<tr><td class="mono">${r[0]}</td><td>${r[1]}</td><td>${r[2]}</td><td>${_badge(r[3])}</td><td>${r[5]}</td><td>${r[6]}</td></tr>`).join("") +
`</tbody>`;
} else {
rt.innerHTML = `<tbody><tr><td style="padding:24px; text-align:center; color:var(--text-tertiary);">Noch keine Läufe — Lösung ist nicht aktiviert.</td></tr></tbody>`;
}
// trace
document.getElementById("traceBody").innerHTML = s.trace.length
? s.trace.map(t => `<div class="step"><span class="dotc ${t[0]==='ok'?'ok':'warnc'}">${t[0]==='ok'?'✓':'!'}</span><span class="nm">${t[1]} <small>· ${t[2]}</small></span><span class="dur">${t[3]}</span></div>`).join("")
: `<div style="color:var(--text-tertiary); font-size:13px;">Kein Lauf vorhanden.</div>`;
// files
document.getElementById("filesBody").innerHTML = s.files.length
? s.files.map(f => `<div class="file"><span class="pic">${f[2]}</span><div><div class="nm">${f[0]}</div><div class="mt">${f[1]}</div></div><button class="btn btn-ghost btn-sm" onclick="toast('Download (Mockup)')">Öffnen</button></div>`).join("")
: `<div style="color:var(--text-tertiary); font-size:13px;">Noch keine Dokumente erzeugt.</div>`;
document.getElementById("viewList").style.display = "none";
document.getElementById("viewDetail").style.display = "block";
// reset to first tab
document.querySelectorAll("#viewDetail .tabs button").forEach((b,i)=>b.classList.toggle("on",i===0));
document.querySelectorAll("#viewDetail .tabpane").forEach((p,i)=>p.classList.toggle("on",p.id==="tEinst"));
document.getElementById("testTrace").innerHTML = "";
document.querySelector(".content").scrollTop = 0;
}
function showList(){
current = null;
document.getElementById("crumb").textContent = "Lösungen";
document.getElementById("viewDetail").style.display = "none";
document.getElementById("viewList").style.display = "block";
}
function switchTab(e, id){
document.querySelectorAll("#viewDetail .tabs button").forEach(b=>b.classList.remove("on"));
e.currentTarget.classList.add("on");
document.querySelectorAll("#viewDetail .tabpane").forEach(p=>p.classList.toggle("on",p.id===id));
}
function startTest(){
const s = solutions[current];
const box = document.getElementById("testTrace");
box.innerHTML = `<div class="note note-info"><span>✦</span><span>Testlauf gestartet — Dry-Run, keine Mails werden versendet.</span></div><div class="trace" id="liveTrace" style="margin-top:12px;"></div>`;
const lt = document.getElementById("liveTrace");
const steps = s.trace.length ? s.trace : [["ok","workflow.run","Beispiel-Schritt","1s"]];
let i = 0;
(function next(){
if (i >= steps.length){ toast("Testlauf erfolgreich · 6 Dokumente erzeugt (nicht versendet)"); return; }
const t = steps[i];
const div = document.createElement("div");
div.className = "step";
div.innerHTML = `<span class="dotc ${t[0]==='ok'?'ok':'warnc'}">${t[0]==='ok'?'✓':'!'}</span><span class="nm">${t[1]} <small>· ${t[2]}</small></span><span class="dur">${t[3]}</span>`;
div.style.opacity = 0;
lt.appendChild(div);
requestAnimationFrame(()=>{ div.style.transition="opacity .25s"; div.style.opacity=1; });
i++; setTimeout(next, 420);
})();
}
function runNow(){
const s = solutions[current];
if (s.restricted) toast("Lauf gestartet (Rolle trustee-admin bestätigt) ▶");
else toast("Lauf gestartet ▶");
}
// catalog modal
function openCatalog(){ document.getElementById("catalog").classList.add("on"); }
function closeCatalog(){ document.getElementById("catalog").classList.remove("on"); document.querySelectorAll(".tmpl").forEach(t=>t.classList.remove("sel")); }
function selTmpl(el){ document.querySelectorAll(".tmpl").forEach(t=>t.classList.remove("sel")); el.classList.add("sel"); }
function generateSolution(){ closeCatalog(); toast("✦ Workflow erstellt — jetzt Einstellungen konfigurieren"); showDetail("pling"); }
// toast
let toastTimer;
function toast(msg){
const t = document.getElementById("toast");
document.getElementById("toastMsg").textContent = msg;
t.classList.add("show");
clearTimeout(toastTimer);
toastTimer = setTimeout(()=>t.classList.remove("show"), 2600);
}
document.getElementById("catalog").addEventListener("click", e => { if (e.target.id === "catalog") closeCatalog(); });
renderList();
</script>
</body>
</html>