Compare commits
187 Commits
7b3a6287b2
...
data
| Author | SHA1 | Date | |
|---|---|---|---|
| b29b7317bc | |||
| b62582b796 | |||
| 33c6d1e896 | |||
| 82e690c447 | |||
| b178523220 | |||
| 3fcbd91fd6 | |||
| 1ab2e56352 | |||
| 1a0b14fac4 | |||
| ae65a78d30 | |||
| b81f677ff9 | |||
| df2a1c1ecf | |||
| e2c257e425 | |||
| 5bae6b7756 | |||
| 2285cb9356 | |||
| 217d6b4c48 | |||
| 8f98386736 | |||
| e09de044da | |||
| 80730ebbb8 | |||
| e55e1b60bf | |||
| 036719175c | |||
| 2c6454c30f | |||
| 93d20afd34 | |||
| 62d4f5c6e2 | |||
| a902572772 | |||
| e5e42520cb | |||
| 4d161738aa | |||
| 64d79924e9 | |||
| 0767115801 | |||
| 33425de39e | |||
| 30b72fc298 | |||
| bfefea0699 | |||
| d84577c44d | |||
| 26d2f79629 | |||
| cddf087d68 | |||
| c6cda48d11 | |||
| 373e4700de | |||
| ee1b657c13 | |||
| 74e3a583eb | |||
| 339dc1d187 | |||
| 73a45cfcdc | |||
| 0ac2345e3d | |||
|
|
8bf74cc992 | ||
|
|
8127f87e03 | ||
|
|
95df518900 | ||
|
|
f031e94fd8 | ||
|
|
22997c1add | ||
|
|
f15dba0786 | ||
|
|
7841c4854b | ||
| 3b4c16e85d | |||
| 9906eb03fe | |||
|
|
760a6aadfa | ||
|
|
66134424e2 | ||
|
|
706a91b193 | ||
| ea876fb29d | |||
| 370a3f6cd8 | |||
| 7d96a3b5ad | |||
| ca1902c7bb | |||
| 64a2c5b279 | |||
| de5f560ee7 | |||
| d526b97873 | |||
| 9e00549391 | |||
| 9366fa71af | |||
| 45400cd71e | |||
| c5e5895303 | |||
| 36e001ae97 | |||
| c1a5eaf653 | |||
| 11a97b62f0 | |||
| 8a629399ce | |||
| e2a9423a6a | |||
| a309d32d52 | |||
| 6f1800d9a6 | |||
| 5229c614f4 | |||
|
|
580725f2f7 | ||
| b40f384123 | |||
| 2b23bf8abc | |||
| 8164e2c7bc | |||
| 443eb138ec | |||
| 7b4b6a3f92 | |||
| 7f2ebd6bb4 | |||
| e66612de93 | |||
| 6f70a6bf71 | |||
| 1d53f7b41a | |||
| 2865895a67 | |||
| d05f1396a2 | |||
| 30f0290363 | |||
| a03740c2b4 | |||
| 0debe09f64 | |||
| 1a89a62149 | |||
| 347b682576 | |||
| 9a5a8d1f2b | |||
| b5d62ce51e | |||
| 2cdb378883 | |||
| e719ccbee0 | |||
| 28b0338a15 | |||
| 0651fb4752 | |||
| b11e9afa6a | |||
| 7ad0ab8114 | |||
| c0b9a6cbf8 | |||
| cf94ffd320 | |||
| 8df0d03f8f | |||
| 26882ae897 | |||
| 1796145bb3 | |||
| 7e19ede38d | |||
| f20f67ebc0 | |||
| ad52836ca4 | |||
| d810ca68a0 | |||
| d4aea00b4d | |||
| 677ba40104 | |||
| f9048c5826 | |||
| e208bd2cf1 | |||
| 3548dea926 | |||
| f4dfa39b7d | |||
| d52e28c3d5 | |||
| c9991a4192 | |||
| 88d9fac5da | |||
| 66de72edc0 | |||
| 69670dd58f | |||
| 0d16aecf3a | |||
| 2d8931d140 | |||
| 5a6778de40 | |||
| 4506dbf8f6 | |||
| c21f5605a2 | |||
| a336e55759 | |||
| 9ce7c00b69 | |||
| 462d97b6cf | |||
| c6a682fc62 | |||
| 611373b8d2 | |||
| d4303dce98 | |||
| 6399f714fb | |||
| f802da4a46 | |||
| 2170f3b8a4 | |||
| 62b5fcbfd6 | |||
| 6429b45fff | |||
| 7e7e1bdff0 | |||
|
|
24808e35f6 | ||
|
|
68adca35e5 | ||
| 491adbaef8 | |||
| 7fde6df4c1 | |||
| 3396ed062e | |||
| 50c603e251 | |||
| 66f45fb80d | |||
|
|
0797cc8984 | ||
| 8aed9c646c | |||
| bd3182aae5 | |||
| dba4ba35ea | |||
| 6717eea965 | |||
| e7e68417d4 | |||
| a01f0ef1ad | |||
| 859d7d94fa | |||
| ffa951ea53 | |||
| 2fe2373665 | |||
| 3fd457d413 | |||
| 3c5959bdf7 | |||
| cd51dfa9fd | |||
| 2f8caf80bc | |||
| 9a97b6d7a4 | |||
| a632e9b057 | |||
|
|
eff28fcc79 | ||
|
|
f7b819eed4 | ||
|
|
331cb08609 | ||
|
|
a33a68dacb | ||
|
|
bf4c099b62 | ||
|
|
c2946a07f2 | ||
|
|
875ef0d42f | ||
|
|
6dcfcfa4a6 | ||
|
|
f8c405f3c4 | ||
|
|
1cf92503ed | ||
|
|
fbb2d471de | ||
|
|
f97ab2d69f | ||
|
|
69824b610c | ||
|
|
3c37c4ad61 | ||
|
|
ca2e970389 | ||
| 72c7c94ce2 | |||
| 21f5e28ca3 | |||
| 2254185cfa | |||
|
|
4e77ae1b61 | ||
|
|
77ce58243f | ||
|
|
3f7e24be32 | ||
|
|
661d9b117e | ||
|
|
cefa4ca38f | ||
|
|
1138310639 | ||
|
|
b9fee3ae7c | ||
|
|
5deda366f3 | ||
|
|
e1c878e6ae | ||
|
|
0d7b9b2d6a | ||
|
|
85dfc53b13 | ||
|
|
ab0397dbe4 |
|
Before Width: | Height: | Size: 4.6 KiB |
3
Push
@@ -1,3 +0,0 @@
|
|||||||
neues Push, nach Änderungen
|
|
||||||
cd "/Users/christianheubaum/Documents/Claude/BNI Sonne"
|
|
||||||
git add -u && git commit -m "Update" && git push
|
|
||||||
839
admin.html
@@ -1,839 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="de">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>BNI Admin-Panel</title>
|
|
||||||
<style>
|
|
||||||
:root {
|
|
||||||
--red: #cc0000; --red-dark: #990000;
|
|
||||||
--navy: #1a2744; --bg: #f0f2f5;
|
|
||||||
--card: #ffffff; --text: #1a1a2e;
|
|
||||||
--muted: #6b7280; --border: #e5e7eb;
|
|
||||||
--success: #16a34a; --warning: #d97706;
|
|
||||||
}
|
|
||||||
* { margin:0; padding:0; box-sizing:border-box; }
|
|
||||||
body { font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif; background:var(--bg); color:var(--text); min-height:100vh; }
|
|
||||||
|
|
||||||
.header { background:var(--navy); color:white; padding:14px 24px; display:flex; align-items:center; gap:16px; }
|
|
||||||
.header img { height:40px; border-radius:6px; object-fit:contain; }
|
|
||||||
.header-title { font-size:1.1rem; font-weight:700; }
|
|
||||||
.header-chapter { font-size:0.82rem; color:rgba(255,255,255,0.6); }
|
|
||||||
.header-back { margin-left:auto; color:rgba(255,255,255,0.7); text-decoration:none; font-size:0.85rem; }
|
|
||||||
.header-back:hover { color:white; }
|
|
||||||
|
|
||||||
.tabs { background:white; border-bottom:1px solid var(--border); display:flex; padding:0 24px; }
|
|
||||||
.tab { padding:12px 20px; font-size:0.85rem; font-weight:600; color:var(--muted); cursor:pointer; border-bottom:2px solid transparent; }
|
|
||||||
.tab.active { color:var(--red); border-bottom-color:var(--red); }
|
|
||||||
|
|
||||||
.content { padding:24px; max-width:900px; }
|
|
||||||
|
|
||||||
.card { background:white; border-radius:12px; border:1px solid var(--border); padding:20px; margin-bottom:16px; }
|
|
||||||
.card-title { font-weight:700; font-size:0.95rem; margin-bottom:14px; }
|
|
||||||
.card-subtitle { font-size:0.78rem; font-weight:700; color:var(--muted); text-transform:uppercase; letter-spacing:1px; margin-bottom:10px; }
|
|
||||||
|
|
||||||
.btn { padding:8px 16px; border-radius:8px; font-size:0.85rem; font-weight:600; cursor:pointer; border:none; display:inline-flex; align-items:center; gap:6px; }
|
|
||||||
.btn-primary { background:var(--red); color:white; }
|
|
||||||
.btn-primary:hover { background:var(--red-dark); }
|
|
||||||
.btn-secondary { background:#f3f4f6; color:var(--text); border:1px solid var(--border); }
|
|
||||||
.btn-secondary:hover { background:#e5e7eb; }
|
|
||||||
.btn-ghost { background:transparent; color:var(--muted); border:1px solid var(--border); }
|
|
||||||
.btn-danger { background:#fee2e2; color:#dc2626; }
|
|
||||||
.btn-sm { padding:5px 10px; font-size:0.78rem; }
|
|
||||||
.btn:disabled { opacity:0.5; cursor:not-allowed; }
|
|
||||||
.w-full { width:100%; justify-content:center; }
|
|
||||||
|
|
||||||
label { display:block; font-size:0.78rem; font-weight:700; color:var(--muted); text-transform:uppercase; letter-spacing:0.5px; margin-bottom:4px; margin-top:10px; }
|
|
||||||
input[type=text], input[type=password], select, textarea {
|
|
||||||
width:100%; padding:9px 12px; border:1px solid var(--border); border-radius:8px; font-size:0.88rem; outline:none;
|
|
||||||
}
|
|
||||||
input:focus, select:focus { border-color:var(--red); }
|
|
||||||
|
|
||||||
.form-row { display:grid; grid-template-columns:1fr 1fr; gap:12px; }
|
|
||||||
|
|
||||||
.sv-item { display:flex; align-items:center; gap:10px; padding:10px 0; border-bottom:1px solid var(--border); }
|
|
||||||
.sv-photo { width:44px; height:44px; border-radius:50%; object-fit:cover; flex-shrink:0; }
|
|
||||||
.sv-initials { width:44px; height:44px; border-radius:50%; display:flex; align-items:center; justify-content:center; color:white; font-size:0.8rem; font-weight:700; flex-shrink:0; }
|
|
||||||
.sv-info { flex:1; min-width:0; }
|
|
||||||
.sv-name { font-weight:600; font-size:0.9rem; }
|
|
||||||
.sv-meta { font-size:0.78rem; color:var(--muted); }
|
|
||||||
|
|
||||||
.member-item { display:flex; align-items:center; gap:10px; padding:8px 0; border-bottom:1px solid var(--border); }
|
|
||||||
.chapter-item { padding:10px 14px; background:#f8f9fa; border:1px solid var(--border); border-radius:8px; margin-bottom:8px; display:flex; align-items:center; gap:12px; }
|
|
||||||
|
|
||||||
.status-msg { font-size:0.83rem; min-height:20px; margin-top:6px; }
|
|
||||||
|
|
||||||
.modal-overlay { display:none; position:fixed; inset:0; background:rgba(0,0,0,0.5); z-index:1000; align-items:center; justify-content:center; }
|
|
||||||
.modal-overlay.open { display:flex; }
|
|
||||||
.modal { background:white; border-radius:16px; padding:24px; width:min(520px,92vw); max-height:90vh; overflow-y:auto; }
|
|
||||||
.modal-header { display:flex; justify-content:space-between; align-items:center; margin-bottom:16px; }
|
|
||||||
.modal-title { font-weight:700; font-size:1.05rem; }
|
|
||||||
.modal-close { background:none; border:none; font-size:1.3rem; cursor:pointer; color:var(--muted); }
|
|
||||||
.modal-footer { display:flex; gap:8px; justify-content:flex-end; margin-top:16px; padding-top:12px; border-top:1px solid var(--border); }
|
|
||||||
|
|
||||||
.photo-thumb { width:64px; height:64px; border-radius:50%; object-fit:cover; border:2px solid var(--border); }
|
|
||||||
.photo-actions { display:flex; gap:8px; flex-wrap:wrap; align-items:center; margin-top:8px; }
|
|
||||||
.btn-file-label { display:inline-flex; align-items:center; gap:6px; padding:5px 10px; background:#f3f4f6; border:1px solid var(--border); border-radius:8px; font-size:0.78rem; font-weight:600; cursor:pointer; }
|
|
||||||
|
|
||||||
.tab-content { display:none; }
|
|
||||||
.tab-content.active { display:block; }
|
|
||||||
|
|
||||||
.import-chapter-list { max-height:280px; overflow-y:auto; border:1px solid var(--border); border-radius:8px; }
|
|
||||||
.import-member-row { display:flex; align-items:center; gap:10px; padding:8px 12px; border-bottom:1px solid var(--border); cursor:pointer; }
|
|
||||||
.import-member-row:hover { background:#f8f9fa; }
|
|
||||||
.import-member-row:last-child { border-bottom:none; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<div class="header">
|
|
||||||
<img id="admin-logo" src="logo.jpeg" alt="BNI">
|
|
||||||
<div>
|
|
||||||
<div class="header-title">BNI Admin-Panel</div>
|
|
||||||
<div class="header-chapter" id="admin-chapter-name">—</div>
|
|
||||||
</div>
|
|
||||||
<a href="index.html" class="header-back">← Zurück zur Steuerung</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="tabs">
|
|
||||||
<div class="tab active" onclick="switchAdminTab('stammvertreter')">Stammvertreter</div>
|
|
||||||
<div class="tab" onclick="switchAdminTab('mitglieder')">Mitglieder</div>
|
|
||||||
<div class="tab" id="superadmin-tab" onclick="switchAdminTab('superadmin')" style="display:none;">Superadmin</div>
|
|
||||||
<div class="tab" onclick="switchAdminTab('einstellungen')">Einstellungen</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="content">
|
|
||||||
|
|
||||||
<!-- ============================================================ STAMMVERTRETER -->
|
|
||||||
<div id="tab-stammvertreter" class="tab-content active">
|
|
||||||
<div class="card">
|
|
||||||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:14px;">
|
|
||||||
<div class="card-title" style="margin:0;">Stammvertreter-Vorrat</div>
|
|
||||||
<div style="display:flex;gap:8px;">
|
|
||||||
<button class="btn btn-secondary btn-sm" onclick="openImportFromChapter()">⬇ Aus anderem Chapter</button>
|
|
||||||
<button class="btn btn-primary btn-sm" onclick="openAddSvAdmin()">+ Hinzufügen</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="admin-sv-list"><div style="color:var(--muted);font-size:0.88rem;">Lade…</div></div>
|
|
||||||
</div>
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-title">Gitea-Synchronisation</div>
|
|
||||||
<p style="font-size:0.85rem;color:var(--muted);margin-bottom:12px;">Stammvertreter-Liste auf dem Server sichern, damit sie nach einem Browser-Reset wiederherstellbar ist.</p>
|
|
||||||
<div style="display:flex;gap:8px;">
|
|
||||||
<button class="btn btn-secondary btn-sm" onclick="downloadSvFromGitea()">🔄 Vom Server laden</button>
|
|
||||||
<button class="btn btn-primary btn-sm" onclick="uploadSvToGitea()">📤 Auf Server sichern</button>
|
|
||||||
</div>
|
|
||||||
<div class="status-msg" id="sv-sync-status"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ============================================================ MITGLIEDER -->
|
|
||||||
<div id="tab-mitglieder" class="tab-content">
|
|
||||||
<div class="card">
|
|
||||||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:14px;">
|
|
||||||
<div class="card-title" style="margin:0;">Mitglieder</div>
|
|
||||||
<div style="display:flex;gap:8px;">
|
|
||||||
<button class="btn btn-secondary btn-sm" onclick="fetchMembersAdmin()">🔄 Vom Server laden</button>
|
|
||||||
<button class="btn btn-primary btn-sm" onclick="uploadMembersAdmin()">📤 Auf Server hochladen</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="status-msg" id="members-status"></div>
|
|
||||||
<div id="admin-members-list" style="margin-top:10px;"><div style="color:var(--muted);font-size:0.88rem;">Lade…</div></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ============================================================ SUPERADMIN -->
|
|
||||||
<div id="tab-superadmin" class="tab-content">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-title">Alle Chapter</div>
|
|
||||||
<div id="all-chapters-list"><div style="color:var(--muted);font-size:0.88rem;">Lade…</div></div>
|
|
||||||
</div>
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-title">Update-Rollout</div>
|
|
||||||
<p style="font-size:0.85rem;color:var(--muted);margin-bottom:12px;">Neue Versionsnummer in version.json pushen → alle Nutzer aller Chapter sehen das Update-Banner beim nächsten Start.</p>
|
|
||||||
<label>Aktuelle Version auf Gitea</label>
|
|
||||||
<div id="current-version-display" style="font-size:0.88rem;padding:8px 0;color:var(--muted);">—</div>
|
|
||||||
<label>Neue Version</label>
|
|
||||||
<div style="display:flex;gap:8px;">
|
|
||||||
<input type="text" id="new-version-input" placeholder="z.B. 1.2.0" style="flex:1;">
|
|
||||||
<input type="text" id="new-version-notes" placeholder="Hinweis (optional)" style="flex:2;">
|
|
||||||
<button class="btn btn-primary btn-sm" onclick="pushVersionUpdate()">Pushen</button>
|
|
||||||
</div>
|
|
||||||
<div class="status-msg" id="version-status"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ============================================================ EINSTELLUNGEN -->
|
|
||||||
<div id="tab-einstellungen" class="tab-content">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-title">Admin-Token (Gitea API)</div>
|
|
||||||
<p style="font-size:0.85rem;color:var(--muted);margin-bottom:10px;">Wird für alle Uploads auf den Gitea-Server benötigt. Wird nur lokal im Browser gespeichert.</p>
|
|
||||||
<div style="display:flex;gap:8px;">
|
|
||||||
<input type="password" id="settings-token" placeholder="Gitea API Token" style="flex:1;">
|
|
||||||
<button class="btn btn-primary btn-sm" onclick="saveSettingsToken()">Speichern</button>
|
|
||||||
</div>
|
|
||||||
<div class="status-msg" id="token-status"></div>
|
|
||||||
</div>
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-title">Chapter-Informationen</div>
|
|
||||||
<div id="chapter-info-display" style="font-size:0.88rem;color:var(--muted);">Lade…</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div><!-- .content -->
|
|
||||||
|
|
||||||
<!-- ============================================================ MODALS -->
|
|
||||||
|
|
||||||
<!-- SV Add/Edit Modal -->
|
|
||||||
<div class="modal-overlay" id="modal-sv-admin">
|
|
||||||
<div class="modal">
|
|
||||||
<div class="modal-header">
|
|
||||||
<div class="modal-title" id="sv-admin-modal-title">Stammvertreter hinzufügen</div>
|
|
||||||
<button class="modal-close" onclick="closeAdminModal('modal-sv-admin')">✕</button>
|
|
||||||
</div>
|
|
||||||
<input type="hidden" id="sv-admin-id">
|
|
||||||
<label>Name *</label>
|
|
||||||
<input type="text" id="sv-admin-name" placeholder="Vor- und Nachname">
|
|
||||||
<div class="form-row">
|
|
||||||
<div><label>Firma</label><input type="text" id="sv-admin-company" placeholder="Firma GmbH"></div>
|
|
||||||
<div><label>Fachgebiet</label><input type="text" id="sv-admin-specialty" placeholder="Fachgebiet"></div>
|
|
||||||
</div>
|
|
||||||
<label>Foto</label>
|
|
||||||
<div style="display:flex;align-items:center;gap:12px;margin-top:8px;">
|
|
||||||
<img id="sv-admin-photo-thumb" class="photo-thumb" src="" alt="" style="display:none;">
|
|
||||||
<div class="photo-actions">
|
|
||||||
<label class="btn-file-label">📁 Datei wählen<input type="file" id="sv-admin-photo-file" accept="image/*" style="display:none" onchange="handleSvAdminPhotoFile(this)"></label>
|
|
||||||
<button class="btn btn-danger btn-sm" id="sv-admin-remove-photo" onclick="removeSvAdminPhoto()" style="display:none;">✕ Entfernen</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button class="btn btn-danger btn-sm" id="sv-admin-delete-btn" onclick="deleteSvAdmin()" style="display:none;">Löschen</button>
|
|
||||||
<button class="btn btn-ghost" onclick="closeAdminModal('modal-sv-admin')">Abbrechen</button>
|
|
||||||
<button class="btn btn-primary" onclick="saveSvAdminModal()">Speichern</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Member Photo Modal -->
|
|
||||||
<div class="modal-overlay" id="modal-member-photo">
|
|
||||||
<div class="modal">
|
|
||||||
<div class="modal-header">
|
|
||||||
<div class="modal-title" id="member-photo-modal-title">Foto bearbeiten</div>
|
|
||||||
<button class="modal-close" onclick="closeAdminModal('modal-member-photo')">✕</button>
|
|
||||||
</div>
|
|
||||||
<input type="hidden" id="member-photo-name">
|
|
||||||
<div style="display:flex;align-items:center;gap:16px;margin:16px 0;">
|
|
||||||
<img id="member-photo-thumb" class="photo-thumb" style="width:80px;height:80px;display:none;" src="" alt="">
|
|
||||||
<div id="member-photo-initials" style="width:80px;height:80px;border-radius:50%;display:flex;align-items:center;justify-content:center;color:white;font-size:1.4rem;font-weight:700;flex-shrink:0;"></div>
|
|
||||||
<div class="photo-actions" style="flex-direction:column;align-items:flex-start;">
|
|
||||||
<label class="btn-file-label">📁 Foto wählen<input type="file" id="member-photo-file" accept="image/*" style="display:none" onchange="handleMemberPhotoFile(this)"></label>
|
|
||||||
<button class="btn btn-danger btn-sm" id="member-photo-remove" onclick="removeMemberPhoto()" style="display:none;margin-top:6px;">✕ Foto entfernen</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button class="btn btn-ghost" onclick="closeAdminModal('modal-member-photo')">Abbrechen</button>
|
|
||||||
<button class="btn btn-primary" onclick="saveMemberPhoto()">Speichern</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Import from Chapter Modal -->
|
|
||||||
<div class="modal-overlay" id="modal-import-chapter">
|
|
||||||
<div class="modal">
|
|
||||||
<div class="modal-header">
|
|
||||||
<div class="modal-title">Aus anderem Chapter importieren</div>
|
|
||||||
<button class="modal-close" onclick="closeAdminModal('modal-import-chapter')">✕</button>
|
|
||||||
</div>
|
|
||||||
<div id="import-chapter-step1">
|
|
||||||
<p style="font-size:0.85rem;color:var(--muted);margin-bottom:12px;">Chapter auswählen, aus dem du ein Profil übernehmen möchtest:</p>
|
|
||||||
<div id="import-chapter-select-list"><div style="color:var(--muted);">Lade Chapter…</div></div>
|
|
||||||
</div>
|
|
||||||
<div id="import-chapter-step2" style="display:none;">
|
|
||||||
<button class="btn btn-ghost btn-sm" onclick="showImportStep1()" style="margin-bottom:12px;">← Zurück</button>
|
|
||||||
<p style="font-size:0.85rem;color:var(--muted);margin-bottom:8px;">Mitglied auswählen:</p>
|
|
||||||
<div id="import-member-list" class="import-chapter-list"></div>
|
|
||||||
</div>
|
|
||||||
<div class="status-msg" id="import-status"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
// ============================================================
|
|
||||||
// SHARED CONSTANTS (copy from index.html)
|
|
||||||
// ============================================================
|
|
||||||
const APP_VERSION = '1.1.0';
|
|
||||||
const GITEA_RAW = 'http://72.60.134.73:3000/api/v1/repos/cheubaum/BNI-Vorstellungsrunde/raw';
|
|
||||||
const GITEA_API = 'http://72.60.134.73:3000/api/v1/repos/cheubaum/BNI-Vorstellungsrunde';
|
|
||||||
const CHAPTER_CONFIG_KEY = 'bni-active-chapter';
|
|
||||||
function giteaUrl(path) { return `${GITEA_RAW}/${path}?ref=main`; }
|
|
||||||
function giteaAuthUrl(path, token) { return `${GITEA_API}/contents/${path}?token=${encodeURIComponent(token)}`; }
|
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// CHAPTER CONFIG
|
|
||||||
// ============================================================
|
|
||||||
function loadChapterConfig() {
|
|
||||||
try { return JSON.parse(localStorage.getItem(CHAPTER_CONFIG_KEY) || 'null'); } catch(e) { return null; }
|
|
||||||
}
|
|
||||||
const activeChapter = loadChapterConfig();
|
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// STORAGE KEYS
|
|
||||||
// ============================================================
|
|
||||||
function storageKey() { return activeChapter ? `bni-chapter-${activeChapter.code}-v2` : 'bni-chapter-default-v2'; }
|
|
||||||
function extrasKey(name) { return (name||'').toLowerCase().trim(); }
|
|
||||||
function extrasStorageKey() { return activeChapter ? `bni-extras-${activeChapter.code}` : 'bni-profile-extras'; }
|
|
||||||
function svStorageKey() { return activeChapter ? `bni-stammvertreter-${activeChapter.code}` : 'bni-stammvertreter'; }
|
|
||||||
|
|
||||||
function loadState() {
|
|
||||||
try { return JSON.parse(localStorage.getItem(storageKey()) || 'null') || defaultState(); } catch(e) { return defaultState(); }
|
|
||||||
}
|
|
||||||
function saveState(s) { localStorage.setItem(storageKey(), JSON.stringify(s)); }
|
|
||||||
function defaultState() { return { members:[], meetingOrder:[], currentIndex:-1, substitutes:{}, absentIds:[], medicalIds:[], visitors:[], visitorsOrder:[], visitorsCurrentIndex:-1, mode:'members', defaultTimer:60, memberTimers:{} }; }
|
|
||||||
|
|
||||||
function loadExtras() { try { return JSON.parse(localStorage.getItem(extrasStorageKey())||'{}'); } catch(e) { return {}; } }
|
|
||||||
function saveExtras(e) { localStorage.setItem(extrasStorageKey(), JSON.stringify(e)); }
|
|
||||||
|
|
||||||
function loadStammvertreter() { try { return JSON.parse(localStorage.getItem(svStorageKey())||'[]'); } catch(e) { return []; } }
|
|
||||||
function saveStammvertreter(list) { localStorage.setItem(svStorageKey(), JSON.stringify(list)); }
|
|
||||||
|
|
||||||
function getAdminToken() { return localStorage.getItem('bni-gitea-admin-token'); }
|
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// UTILS
|
|
||||||
// ============================================================
|
|
||||||
function escHtml(s) { return (s||'').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"'); }
|
|
||||||
function getInitials(n) { if(!n)return'?'; return n.trim().split(/\s+/).map(w=>w[0]).join('').toUpperCase().slice(0,2); }
|
|
||||||
function getColor(n) { const c=['#1a2744','#cc0000','#0f3460','#1e3a5f','#4a1942']; let h=0; for(let ch of(n||''))h=(h*31+ch.charCodeAt(0))&0xffffffff; return c[Math.abs(h)%c.length]; }
|
|
||||||
|
|
||||||
function resizePhotoToBase64(file, maxPx=400) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = e => {
|
|
||||||
const img = new Image();
|
|
||||||
img.onload = () => {
|
|
||||||
let w=img.width, h=img.height;
|
|
||||||
if(w>maxPx||h>maxPx){ if(w>h){h=Math.round(h*maxPx/w);w=maxPx;}else{w=Math.round(w*maxPx/h);h=maxPx;} }
|
|
||||||
const canvas=document.createElement('canvas'); canvas.width=w; canvas.height=h;
|
|
||||||
canvas.getContext('2d').drawImage(img,0,0,w,h);
|
|
||||||
resolve(canvas.toDataURL('image/jpeg',0.82));
|
|
||||||
};
|
|
||||||
img.onerror=reject; img.src=e.target.result;
|
|
||||||
};
|
|
||||||
reader.onerror=reject; reader.readAsDataURL(file);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function avatarHtml(name, photoB64, photoUrl, size=44) {
|
|
||||||
const src = photoB64 || photoUrl || '';
|
|
||||||
if (src) return `<img src="${escHtml(src)}" style="width:${size}px;height:${size}px;border-radius:50%;object-fit:cover;flex-shrink:0;" onerror="this.replaceWith(makeInitialsEl('${escHtml(name)}',${size}))">`;
|
|
||||||
const el = document.createElement('div');
|
|
||||||
el.style.cssText = `width:${size}px;height:${size}px;border-radius:50%;background:${getColor(name)};display:flex;align-items:center;justify-content:center;color:white;font-size:${Math.round(size*0.32)}px;font-weight:700;flex-shrink:0;`;
|
|
||||||
el.textContent = getInitials(name);
|
|
||||||
return el.outerHTML;
|
|
||||||
}
|
|
||||||
|
|
||||||
function openAdminModal(id) { document.getElementById(id).classList.add('open'); }
|
|
||||||
function closeAdminModal(id) { document.getElementById(id).classList.remove('open'); }
|
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// TABS
|
|
||||||
// ============================================================
|
|
||||||
function switchAdminTab(tab) {
|
|
||||||
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
|
|
||||||
document.querySelectorAll('.tab-content').forEach(t => t.classList.remove('active'));
|
|
||||||
document.querySelector(`[onclick="switchAdminTab('${tab}')"]`).classList.add('active');
|
|
||||||
document.getElementById(`tab-${tab}`).classList.add('active');
|
|
||||||
if (tab === 'stammvertreter') renderSvList();
|
|
||||||
if (tab === 'mitglieder') renderMembersList();
|
|
||||||
if (tab === 'superadmin') { loadAllChapters(); loadCurrentVersion(); }
|
|
||||||
if (tab === 'einstellungen') renderSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// STAMMVERTRETER
|
|
||||||
// ============================================================
|
|
||||||
let _svAdminPhotoB64 = null;
|
|
||||||
|
|
||||||
function renderSvList() {
|
|
||||||
const list = loadStammvertreter();
|
|
||||||
const container = document.getElementById('admin-sv-list');
|
|
||||||
if (!list.length) {
|
|
||||||
container.innerHTML = `<div style="color:var(--muted);font-size:0.88rem;padding:8px 0;">Noch keine Stammvertreter gespeichert.</div>`;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
container.innerHTML = '';
|
|
||||||
list.forEach(sv => {
|
|
||||||
const extras = loadExtras();
|
|
||||||
const photoB64 = sv.photoB64 || (extras[extrasKey(sv.name)] && extras[extrasKey(sv.name)].photoB64) || null;
|
|
||||||
const div = document.createElement('div');
|
|
||||||
div.className = 'sv-item';
|
|
||||||
div.innerHTML = `${avatarHtml(sv.name, photoB64, null, 44)}
|
|
||||||
<div class="sv-info">
|
|
||||||
<div class="sv-name">${escHtml(sv.name)}</div>
|
|
||||||
<div class="sv-meta">${escHtml(sv.company||'')}${sv.specialty?' · '+escHtml(sv.specialty):''}</div>
|
|
||||||
</div>
|
|
||||||
<button class="btn btn-ghost btn-sm" onclick="openEditSvAdmin('${sv.id}')">✏ Bearbeiten</button>`;
|
|
||||||
container.appendChild(div);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function openAddSvAdmin() {
|
|
||||||
document.getElementById('sv-admin-id').value = '';
|
|
||||||
document.getElementById('sv-admin-modal-title').textContent = 'Stammvertreter hinzufügen';
|
|
||||||
document.getElementById('sv-admin-name').value = '';
|
|
||||||
document.getElementById('sv-admin-company').value = '';
|
|
||||||
document.getElementById('sv-admin-specialty').value = '';
|
|
||||||
document.getElementById('sv-admin-delete-btn').style.display = 'none';
|
|
||||||
_svAdminPhotoB64 = null;
|
|
||||||
updateSvAdminPhotoUI(null);
|
|
||||||
openAdminModal('modal-sv-admin');
|
|
||||||
}
|
|
||||||
|
|
||||||
function openEditSvAdmin(svId) {
|
|
||||||
const sv = loadStammvertreter().find(s => s.id === svId);
|
|
||||||
if (!sv) return;
|
|
||||||
document.getElementById('sv-admin-id').value = sv.id;
|
|
||||||
document.getElementById('sv-admin-modal-title').textContent = 'Stammvertreter bearbeiten';
|
|
||||||
document.getElementById('sv-admin-name').value = sv.name;
|
|
||||||
document.getElementById('sv-admin-company').value = sv.company || '';
|
|
||||||
document.getElementById('sv-admin-specialty').value = sv.specialty || '';
|
|
||||||
document.getElementById('sv-admin-delete-btn').style.display = '';
|
|
||||||
_svAdminPhotoB64 = sv.photoB64 || null;
|
|
||||||
updateSvAdminPhotoUI(_svAdminPhotoB64);
|
|
||||||
openAdminModal('modal-sv-admin');
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateSvAdminPhotoUI(b64) {
|
|
||||||
_svAdminPhotoB64 = b64;
|
|
||||||
const thumb = document.getElementById('sv-admin-photo-thumb');
|
|
||||||
const btn = document.getElementById('sv-admin-remove-photo');
|
|
||||||
if (b64) { thumb.src = b64; thumb.style.display = 'block'; btn.style.display = 'inline-flex'; }
|
|
||||||
else { thumb.src = ''; thumb.style.display = 'none'; btn.style.display = 'none'; }
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleSvAdminPhotoFile(input) {
|
|
||||||
const file = input.files && input.files[0];
|
|
||||||
if (!file) return;
|
|
||||||
resizePhotoToBase64(file).then(b64 => updateSvAdminPhotoUI(b64)).catch(e => alert(e.message));
|
|
||||||
input.value = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeSvAdminPhoto() { updateSvAdminPhotoUI(null); }
|
|
||||||
|
|
||||||
function saveSvAdminModal() {
|
|
||||||
const name = document.getElementById('sv-admin-name').value.trim();
|
|
||||||
if (!name) { alert('Bitte Namen eingeben.'); return; }
|
|
||||||
const list = loadStammvertreter();
|
|
||||||
const existingId = document.getElementById('sv-admin-id').value;
|
|
||||||
const sv = {
|
|
||||||
id: existingId || 'sv-' + Date.now(),
|
|
||||||
name,
|
|
||||||
company: document.getElementById('sv-admin-company').value.trim(),
|
|
||||||
specialty: document.getElementById('sv-admin-specialty').value.trim(),
|
|
||||||
photoB64: _svAdminPhotoB64 || null,
|
|
||||||
};
|
|
||||||
if (existingId) {
|
|
||||||
const idx = list.findIndex(s => s.id === existingId);
|
|
||||||
if (idx >= 0) list[idx] = sv; else list.push(sv);
|
|
||||||
} else {
|
|
||||||
list.push(sv);
|
|
||||||
}
|
|
||||||
saveStammvertreter(list);
|
|
||||||
closeAdminModal('modal-sv-admin');
|
|
||||||
renderSvList();
|
|
||||||
}
|
|
||||||
|
|
||||||
function deleteSvAdmin() {
|
|
||||||
const svId = document.getElementById('sv-admin-id').value;
|
|
||||||
if (!confirm('Stammvertreter löschen?')) return;
|
|
||||||
saveStammvertreter(loadStammvertreter().filter(s => s.id !== svId));
|
|
||||||
closeAdminModal('modal-sv-admin');
|
|
||||||
renderSvList();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// IMPORT FROM OTHER CHAPTER
|
|
||||||
// ============================================================
|
|
||||||
async function openImportFromChapter() {
|
|
||||||
document.getElementById('import-status').textContent = '';
|
|
||||||
showImportStep1();
|
|
||||||
openAdminModal('modal-import-chapter');
|
|
||||||
|
|
||||||
const listEl = document.getElementById('import-chapter-select-list');
|
|
||||||
listEl.innerHTML = '<div style="color:var(--muted);font-size:0.85rem;">Lade Chapter…</div>';
|
|
||||||
try {
|
|
||||||
const res = await fetch(`${GITEA_API}/contents/chapters?ref=main`);
|
|
||||||
const items = await res.json();
|
|
||||||
const chapters = Array.isArray(items) ? items.filter(i => i.type === 'dir') : [];
|
|
||||||
if (!chapters.length) { listEl.innerHTML = '<div style="color:var(--muted);">Keine anderen Chapter gefunden.</div>'; return; }
|
|
||||||
listEl.innerHTML = '';
|
|
||||||
for (const ch of chapters) {
|
|
||||||
if (activeChapter && ch.name === activeChapter.code) continue;
|
|
||||||
try {
|
|
||||||
const cfgRes = await fetch(giteaUrl(`chapters/${ch.name}/config.json`));
|
|
||||||
const cfg = cfgRes.ok ? await cfgRes.json() : {};
|
|
||||||
const btn = document.createElement('div');
|
|
||||||
btn.className = 'chapter-item';
|
|
||||||
btn.style.cursor = 'pointer';
|
|
||||||
btn.innerHTML = `<div><div style="font-weight:600;">${escHtml(cfg.chapterName||ch.name)}</div><div style="font-size:0.78rem;color:var(--muted);">${ch.name}</div></div><span style="margin-left:auto;color:var(--red);font-size:0.85rem;">Mitglieder laden →</span>`;
|
|
||||||
btn.onclick = () => loadChapterMembersForImport(ch.name, cfg.chapterName || ch.name);
|
|
||||||
listEl.appendChild(btn);
|
|
||||||
} catch(e) { /* skip */ }
|
|
||||||
}
|
|
||||||
} catch(e) {
|
|
||||||
listEl.innerHTML = '<div style="color:#dc2626;">Fehler beim Laden der Chapter.</div>';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function showImportStep1() {
|
|
||||||
document.getElementById('import-chapter-step1').style.display = '';
|
|
||||||
document.getElementById('import-chapter-step2').style.display = 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadChapterMembersForImport(code, chapterName) {
|
|
||||||
document.getElementById('import-chapter-step1').style.display = 'none';
|
|
||||||
document.getElementById('import-chapter-step2').style.display = '';
|
|
||||||
const listEl = document.getElementById('import-member-list');
|
|
||||||
listEl.innerHTML = '<div style="padding:12px;color:var(--muted);">Lade Mitglieder…</div>';
|
|
||||||
try {
|
|
||||||
const res = await fetch(giteaUrl(`chapters/${code}/members.json`));
|
|
||||||
if (!res.ok) { listEl.innerHTML = '<div style="padding:12px;color:#dc2626;">Keine Mitglieder gefunden.</div>'; return; }
|
|
||||||
const data = await res.json();
|
|
||||||
const members = data.members || [];
|
|
||||||
if (!members.length) { listEl.innerHTML = '<div style="padding:12px;color:var(--muted);">Keine Mitglieder vorhanden.</div>'; return; }
|
|
||||||
listEl.innerHTML = '';
|
|
||||||
members.forEach(m => {
|
|
||||||
const photoB64 = m.photoData || null;
|
|
||||||
const row = document.createElement('div');
|
|
||||||
row.className = 'import-member-row';
|
|
||||||
row.innerHTML = `${avatarHtml(m.name, photoB64, m.photoUrl, 36)}
|
|
||||||
<div>
|
|
||||||
<div style="font-weight:600;font-size:0.88rem;">${escHtml(m.name)}</div>
|
|
||||||
<div style="font-size:0.75rem;color:var(--muted);">${escHtml(m.company||'')}${m.specialty?' · '+escHtml(m.specialty):''}</div>
|
|
||||||
</div>
|
|
||||||
<span style="margin-left:auto;font-size:0.75rem;color:var(--muted);">${escHtml(chapterName)}</span>`;
|
|
||||||
row.onclick = () => importMemberAsStammvertreter(m, chapterName);
|
|
||||||
listEl.appendChild(row);
|
|
||||||
});
|
|
||||||
} catch(e) {
|
|
||||||
listEl.innerHTML = '<div style="padding:12px;color:#dc2626;">Fehler: ' + e.message + '</div>';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function importMemberAsStammvertreter(member, chapterName) {
|
|
||||||
const existing = loadStammvertreter();
|
|
||||||
if (existing.find(s => s.name.toLowerCase() === member.name.toLowerCase())) {
|
|
||||||
if (!confirm(`"${member.name}" ist bereits in deiner Liste. Trotzdem importieren?`)) return;
|
|
||||||
}
|
|
||||||
const sv = {
|
|
||||||
id: 'sv-' + Date.now(),
|
|
||||||
name: member.name,
|
|
||||||
company: member.company || '',
|
|
||||||
specialty: member.specialty || '',
|
|
||||||
photoB64: member.photoData || null,
|
|
||||||
fromChapter: chapterName,
|
|
||||||
};
|
|
||||||
existing.push(sv);
|
|
||||||
saveStammvertreter(existing);
|
|
||||||
document.getElementById('import-status').textContent = `✓ "${member.name}" übernommen.`;
|
|
||||||
document.getElementById('import-status').style.color = 'var(--success)';
|
|
||||||
renderSvList();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// STAMMVERTRETER GITEA SYNC
|
|
||||||
// ============================================================
|
|
||||||
async function uploadSvToGitea() {
|
|
||||||
const token = getAdminToken();
|
|
||||||
if (!token) { alert('Bitte zuerst Admin-Token in den Einstellungen speichern.'); return; }
|
|
||||||
const statusEl = document.getElementById('sv-sync-status');
|
|
||||||
statusEl.textContent = 'Lädt hoch…'; statusEl.style.color = 'var(--muted)';
|
|
||||||
const list = loadStammvertreter();
|
|
||||||
const data = { lastUpdated: new Date().toISOString().slice(0,10), substitutes: list.map(s => ({ id:s.id, name:s.name, company:s.company, specialty:s.specialty, photoB64: s.photoB64||null, fromChapter:s.fromChapter||null })) };
|
|
||||||
const content = btoa(unescape(encodeURIComponent(JSON.stringify(data, null, 2))));
|
|
||||||
const path = `chapters/${activeChapter.code}/stammvertreter.json`;
|
|
||||||
try {
|
|
||||||
let sha = null;
|
|
||||||
const shaRes = await fetch(giteaAuthUrl(path, token));
|
|
||||||
if (shaRes.ok) sha = (await shaRes.json()).sha;
|
|
||||||
const body = { message: 'Stammvertreter aktualisiert', content };
|
|
||||||
if (sha) body.sha = sha;
|
|
||||||
const res = await fetch(giteaAuthUrl(path, token), { method: sha?'PUT':'POST', headers: { 'Content-Type':'application/json' }, body: JSON.stringify(body) });
|
|
||||||
if (res.ok) { statusEl.textContent = `✓ ${list.length} Stammvertreter gesichert`; statusEl.style.color = 'var(--success)'; }
|
|
||||||
else { statusEl.textContent = 'Fehler beim Upload.'; statusEl.style.color = '#dc2626'; }
|
|
||||||
} catch(e) { statusEl.textContent = 'Verbindungsfehler.'; statusEl.style.color = '#dc2626'; }
|
|
||||||
}
|
|
||||||
|
|
||||||
async function downloadSvFromGitea() {
|
|
||||||
const statusEl = document.getElementById('sv-sync-status');
|
|
||||||
statusEl.textContent = 'Lade…'; statusEl.style.color = 'var(--muted)';
|
|
||||||
try {
|
|
||||||
const res = await fetch(giteaUrl(`chapters/${activeChapter.code}/stammvertreter.json`), { cache: 'no-store' });
|
|
||||||
if (!res.ok) { statusEl.textContent = 'Keine Daten auf Server.'; statusEl.style.color = 'var(--muted)'; return; }
|
|
||||||
const data = await res.json();
|
|
||||||
if (data.substitutes) { saveStammvertreter(data.substitutes); renderSvList(); statusEl.textContent = `✓ ${data.substitutes.length} Stammvertreter geladen`; statusEl.style.color = 'var(--success)'; }
|
|
||||||
} catch(e) { statusEl.textContent = 'Verbindungsfehler.'; statusEl.style.color = '#dc2626'; }
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// MITGLIEDER
|
|
||||||
// ============================================================
|
|
||||||
function renderMembersList() {
|
|
||||||
const state = loadState();
|
|
||||||
const extras = loadExtras();
|
|
||||||
const container = document.getElementById('admin-members-list');
|
|
||||||
if (!state.members.length) {
|
|
||||||
container.innerHTML = `<div style="color:var(--muted);font-size:0.88rem;">Keine Mitglieder vorhanden. Erst Mitglieder über die Steuerung (BNI-Sync) importieren.</div>`;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
container.innerHTML = '';
|
|
||||||
state.members.forEach(m => {
|
|
||||||
const ex = extras[extrasKey(m.name)] || {};
|
|
||||||
const photoB64 = ex.photoB64 || ex.offlinePhotoB64 || null;
|
|
||||||
const div = document.createElement('div');
|
|
||||||
div.className = 'member-item';
|
|
||||||
div.style.cursor = 'pointer';
|
|
||||||
div.title = 'Foto bearbeiten';
|
|
||||||
div.innerHTML = `${avatarHtml(m.name, photoB64, m.photoUrl, 40)}
|
|
||||||
<div style="flex:1;min-width:0;">
|
|
||||||
<div style="font-weight:600;font-size:0.88rem;">${escHtml(m.name)}</div>
|
|
||||||
<div style="font-size:0.75rem;color:var(--muted);">${escHtml(m.company||'')}${m.specialty?' · '+escHtml(m.specialty):''}</div>
|
|
||||||
</div>
|
|
||||||
<span style="font-size:0.72rem;color:${photoB64?'var(--success)':'#d97706'};">${photoB64?'✓ Foto':'+ Foto'}</span>`;
|
|
||||||
div.onclick = () => openMemberPhotoModal(m.name, photoB64);
|
|
||||||
container.appendChild(div);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let _memberPhotoB64 = null;
|
|
||||||
|
|
||||||
function openMemberPhotoModal(name, currentPhotoB64) {
|
|
||||||
_memberPhotoB64 = currentPhotoB64 || null;
|
|
||||||
document.getElementById('member-photo-name').value = name;
|
|
||||||
document.getElementById('member-photo-modal-title').textContent = name;
|
|
||||||
updateMemberPhotoUI(_memberPhotoB64, name);
|
|
||||||
openAdminModal('modal-member-photo');
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateMemberPhotoUI(b64, name) {
|
|
||||||
_memberPhotoB64 = b64;
|
|
||||||
const thumb = document.getElementById('member-photo-thumb');
|
|
||||||
const initials = document.getElementById('member-photo-initials');
|
|
||||||
const removeBtn = document.getElementById('member-photo-remove');
|
|
||||||
if (b64) {
|
|
||||||
thumb.src = b64; thumb.style.display = 'block';
|
|
||||||
initials.style.display = 'none';
|
|
||||||
removeBtn.style.display = 'inline-flex';
|
|
||||||
} else {
|
|
||||||
thumb.src = ''; thumb.style.display = 'none';
|
|
||||||
initials.style.display = 'flex';
|
|
||||||
initials.style.background = getColor(name || '');
|
|
||||||
initials.textContent = getInitials(name || '');
|
|
||||||
removeBtn.style.display = 'none';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleMemberPhotoFile(input) {
|
|
||||||
const file = input.files && input.files[0];
|
|
||||||
if (!file) return;
|
|
||||||
const name = document.getElementById('member-photo-name').value;
|
|
||||||
resizePhotoToBase64(file).then(b64 => updateMemberPhotoUI(b64, name)).catch(e => alert(e.message));
|
|
||||||
input.value = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeMemberPhoto() {
|
|
||||||
updateMemberPhotoUI(null, document.getElementById('member-photo-name').value);
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveMemberPhoto() {
|
|
||||||
const name = document.getElementById('member-photo-name').value;
|
|
||||||
const extras = loadExtras();
|
|
||||||
const key = extrasKey(name);
|
|
||||||
if (!extras[key]) extras[key] = {};
|
|
||||||
if (_memberPhotoB64) {
|
|
||||||
extras[key].photoB64 = _memberPhotoB64;
|
|
||||||
} else {
|
|
||||||
delete extras[key].photoB64;
|
|
||||||
delete extras[key].offlinePhotoB64;
|
|
||||||
}
|
|
||||||
saveExtras(extras);
|
|
||||||
closeAdminModal('modal-member-photo');
|
|
||||||
renderMembersList();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchMembersAdmin() {
|
|
||||||
const statusEl = document.getElementById('members-status');
|
|
||||||
statusEl.textContent = 'Lade vom Server…'; statusEl.style.color = 'var(--muted)';
|
|
||||||
try {
|
|
||||||
const res = await fetch(giteaUrl(`chapters/${activeChapter.code}/members.json`), { cache: 'no-store' });
|
|
||||||
if (!res.ok) { statusEl.textContent = 'Keine Mitglieder auf Server.'; return; }
|
|
||||||
const data = await res.json();
|
|
||||||
if (!data.members || !data.members.length) { statusEl.textContent = 'Liste ist leer.'; return; }
|
|
||||||
const state = loadState();
|
|
||||||
const extras = loadExtras();
|
|
||||||
const map = new Map(state.members.map(m => [m.id, m]));
|
|
||||||
data.members.forEach(m => {
|
|
||||||
map.set(m.id, Object.assign({}, map.get(m.id)||{}, m));
|
|
||||||
if (m.photoData) { const key=extrasKey(m.name); if(!extras[key])extras[key]={}; extras[key].photoB64=m.photoData; }
|
|
||||||
});
|
|
||||||
state.members = Array.from(map.values());
|
|
||||||
const known = new Set(state.meetingOrder);
|
|
||||||
state.members.forEach(m => { if(!known.has(m.id)) state.meetingOrder.push(m.id); });
|
|
||||||
saveState(state); saveExtras(extras);
|
|
||||||
statusEl.textContent = `✓ ${data.members.length} Mitglieder geladen`; statusEl.style.color = 'var(--success)';
|
|
||||||
renderMembersList();
|
|
||||||
} catch(e) { statusEl.textContent = 'Verbindungsfehler.'; statusEl.style.color = '#dc2626'; }
|
|
||||||
}
|
|
||||||
|
|
||||||
async function uploadMembersAdmin() {
|
|
||||||
const token = getAdminToken();
|
|
||||||
if (!token) { alert('Bitte zuerst Admin-Token in den Einstellungen speichern.'); return; }
|
|
||||||
const statusEl = document.getElementById('members-status');
|
|
||||||
statusEl.textContent = 'Lädt hoch…'; statusEl.style.color = 'var(--muted)';
|
|
||||||
try {
|
|
||||||
const state = loadState();
|
|
||||||
const extras = loadExtras();
|
|
||||||
const members = state.members.map(m => {
|
|
||||||
const ex = extras[extrasKey(m.name)] || {};
|
|
||||||
const entry = { id:m.id, name:m.name, company:m.company, specialty:m.specialty, photoUrl:m.photoUrl||'', profileUrl:m.profileUrl||'' };
|
|
||||||
if (ex.photoB64 || ex.offlinePhotoB64) entry.photoData = ex.photoB64 || ex.offlinePhotoB64;
|
|
||||||
return entry;
|
|
||||||
});
|
|
||||||
const data = { lastUpdated: new Date().toISOString().slice(0,10), members };
|
|
||||||
const content = btoa(unescape(encodeURIComponent(JSON.stringify(data, null, 2))));
|
|
||||||
const path = `chapters/${activeChapter.code}/members.json`;
|
|
||||||
const url = giteaAuthUrl(path, token);
|
|
||||||
console.log('[Upload] URL:', url);
|
|
||||||
statusEl.textContent = 'Prüfe SHA…';
|
|
||||||
let sha = null;
|
|
||||||
const shaRes = await fetch(url);
|
|
||||||
console.log('[Upload] SHA-Status:', shaRes.status);
|
|
||||||
if (shaRes.ok) sha = (await shaRes.json()).sha;
|
|
||||||
const body = { message:`Mitglieder aktualisiert ${new Date().toLocaleDateString('de')}`, content };
|
|
||||||
if (sha) body.sha = sha;
|
|
||||||
statusEl.textContent = sha ? 'Aktualisiere…' : 'Erstelle…';
|
|
||||||
const res = await fetch(url, { method:sha?'PUT':'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(body) });
|
|
||||||
console.log('[Upload] Upload-Status:', res.status);
|
|
||||||
if (res.ok) { statusEl.textContent = `✓ ${members.length} Mitglieder hochgeladen`; statusEl.style.color = 'var(--success)'; }
|
|
||||||
else {
|
|
||||||
const errText = await res.text();
|
|
||||||
console.error('[Upload] Fehler:', errText);
|
|
||||||
statusEl.textContent = `Fehler ${res.status}: ${errText.slice(0,80)}`; statusEl.style.color = '#dc2626';
|
|
||||||
}
|
|
||||||
} catch(e) {
|
|
||||||
console.error('[Upload] Exception:', e);
|
|
||||||
statusEl.textContent = `Fehler: ${e.message}`; statusEl.style.color = '#dc2626';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// SUPERADMIN
|
|
||||||
// ============================================================
|
|
||||||
async function loadAllChapters() {
|
|
||||||
const container = document.getElementById('all-chapters-list');
|
|
||||||
container.innerHTML = '<div style="color:var(--muted);font-size:0.85rem;">Lade…</div>';
|
|
||||||
try {
|
|
||||||
const res = await fetch(`${GITEA_API}/contents/chapters?ref=main`);
|
|
||||||
const items = await res.json();
|
|
||||||
const chapters = Array.isArray(items) ? items.filter(i => i.type === 'dir') : [];
|
|
||||||
container.innerHTML = '';
|
|
||||||
for (const ch of chapters) {
|
|
||||||
try {
|
|
||||||
const cfgRes = await fetch(giteaUrl(`chapters/${ch.name}/config.json`));
|
|
||||||
const cfg = cfgRes.ok ? await cfgRes.json() : {};
|
|
||||||
const membersRes = await fetch(giteaUrl(`chapters/${ch.name}/members.json`));
|
|
||||||
const membersData = membersRes.ok ? await membersRes.json() : {};
|
|
||||||
const div = document.createElement('div');
|
|
||||||
div.className = 'chapter-item';
|
|
||||||
div.innerHTML = `<div style="flex:1;">
|
|
||||||
<div style="font-weight:600;">${escHtml(cfg.chapterName||ch.name)}</div>
|
|
||||||
<div style="font-size:0.78rem;color:var(--muted);">Code: <code>${ch.name}</code> · Mitglieder: ${(membersData.members||[]).length} · Stand: ${membersData.lastUpdated||'—'}</div>
|
|
||||||
</div>`;
|
|
||||||
container.appendChild(div);
|
|
||||||
} catch(e) { /* skip */ }
|
|
||||||
}
|
|
||||||
if (!container.children.length) container.innerHTML = '<div style="color:var(--muted);">Keine Chapter gefunden.</div>';
|
|
||||||
} catch(e) {
|
|
||||||
container.innerHTML = '<div style="color:#dc2626;">Fehler beim Laden.</div>';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadCurrentVersion() {
|
|
||||||
try {
|
|
||||||
const res = await fetch(giteaUrl('version.json'), { cache:'no-store' });
|
|
||||||
if (res.ok) {
|
|
||||||
const data = await res.json();
|
|
||||||
document.getElementById('current-version-display').textContent = data.version || '—';
|
|
||||||
document.getElementById('new-version-input').placeholder = data.version ? `z.B. ${data.version.split('.').map((n,i)=>i===2?+n+1:n).join('.')}` : 'z.B. 1.2.0';
|
|
||||||
}
|
|
||||||
} catch(e) { /* ignore */ }
|
|
||||||
}
|
|
||||||
|
|
||||||
async function pushVersionUpdate() {
|
|
||||||
const token = getAdminToken();
|
|
||||||
if (!token) { alert('Bitte Admin-Token in den Einstellungen eintragen.'); return; }
|
|
||||||
const version = document.getElementById('new-version-input').value.trim();
|
|
||||||
const notes = document.getElementById('new-version-notes').value.trim();
|
|
||||||
if (!version) { alert('Bitte Versionsnummer eingeben.'); return; }
|
|
||||||
const statusEl = document.getElementById('version-status');
|
|
||||||
statusEl.textContent = 'Pushe…'; statusEl.style.color = 'var(--muted)';
|
|
||||||
const content = btoa(unescape(encodeURIComponent(JSON.stringify({ version, notes }, null, 2))));
|
|
||||||
try {
|
|
||||||
let sha = null;
|
|
||||||
const shaRes = await fetch(giteaAuthUrl('version.json', token));
|
|
||||||
if (shaRes.ok) sha = (await shaRes.json()).sha;
|
|
||||||
const body = { message:`Version ${version}`, content };
|
|
||||||
if (sha) body.sha = sha;
|
|
||||||
const res = await fetch(giteaAuthUrl('version.json', token), { method:sha?'PUT':'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(body) });
|
|
||||||
if (res.ok) {
|
|
||||||
statusEl.textContent = `✓ Version ${version} gepusht – alle Nutzer sehen das Update-Banner`;
|
|
||||||
statusEl.style.color = 'var(--success)';
|
|
||||||
document.getElementById('current-version-display').textContent = version;
|
|
||||||
} else { statusEl.textContent = 'Fehler beim Pushen.'; statusEl.style.color = '#dc2626'; }
|
|
||||||
} catch(e) { statusEl.textContent = 'Verbindungsfehler.'; statusEl.style.color = '#dc2626'; }
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// EINSTELLUNGEN
|
|
||||||
// ============================================================
|
|
||||||
function renderSettings() {
|
|
||||||
const token = getAdminToken();
|
|
||||||
document.getElementById('settings-token').value = token ? '••••••••' : '';
|
|
||||||
document.getElementById('settings-token').onfocus = function() { if (this.value==='••••••••') this.value=''; };
|
|
||||||
if (activeChapter) {
|
|
||||||
const cfg = activeChapter.config || {};
|
|
||||||
document.getElementById('chapter-info-display').innerHTML = `
|
|
||||||
<div><strong>Chapter:</strong> ${escHtml(cfg.chapterName||'—')}</div>
|
|
||||||
<div><strong>Code:</strong> <code>${activeChapter.code}</code></div>
|
|
||||||
<div><strong>Version:</strong> ${APP_VERSION}</div>
|
|
||||||
<div><strong>Aktiviert am:</strong> ${activeChapter.activatedAt ? new Date(activeChapter.activatedAt).toLocaleDateString('de') : '—'}</div>`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveSettingsToken() {
|
|
||||||
const val = document.getElementById('settings-token').value;
|
|
||||||
const realVal = val === '••••••••' ? getAdminToken() : val.trim();
|
|
||||||
if (realVal) { localStorage.setItem('bni-gitea-admin-token', realVal); document.getElementById('token-status').textContent = '✓ Gespeichert'; document.getElementById('token-status').style.color = 'var(--success)'; }
|
|
||||||
else { localStorage.removeItem('bni-gitea-admin-token'); document.getElementById('token-status').textContent = 'Token entfernt.'; document.getElementById('token-status').style.color = 'var(--muted)'; }
|
|
||||||
if (getAdminToken()) document.getElementById('superadmin-tab').style.display = '';
|
|
||||||
setTimeout(() => { document.getElementById('token-status').textContent = ''; }, 3000);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// INIT
|
|
||||||
// ============================================================
|
|
||||||
if (!activeChapter) {
|
|
||||||
document.body.innerHTML = `<div style="display:flex;align-items:center;justify-content:center;height:100vh;flex-direction:column;gap:16px;color:var(--muted);">
|
|
||||||
<div style="font-size:1.2rem;font-weight:600;">Kein Chapter aktiviert</div>
|
|
||||||
<div>Bitte zuerst <a href="index.html" style="color:var(--red);">das Tool öffnen</a> und den Lizenzcode eingeben.</div>
|
|
||||||
</div>`;
|
|
||||||
} else {
|
|
||||||
const cfg = activeChapter.config || {};
|
|
||||||
document.getElementById('admin-chapter-name').textContent = cfg.chapterName || activeChapter.code;
|
|
||||||
if (cfg.logoUrl && cfg.logoUrl.startsWith('http')) document.getElementById('admin-logo').src = cfg.logoUrl;
|
|
||||||
if (getAdminToken()) document.getElementById('superadmin-tab').style.display = '';
|
|
||||||
renderSvList();
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
500
anleitung.html
@@ -1,500 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="de">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>BNI Vorstellungsrunde – Anleitung</title>
|
|
||||||
<style>
|
|
||||||
:root {
|
|
||||||
--red: #cc0000; --navy: #1a2744; --bg: #f0f2f5;
|
|
||||||
--card: #fff; --muted: #6b7280; --border: #e5e7eb;
|
|
||||||
--success: #16a34a;
|
|
||||||
}
|
|
||||||
* { margin:0; padding:0; box-sizing:border-box; }
|
|
||||||
body { font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif; background:var(--bg); color:#1a1a2e; line-height:1.6; }
|
|
||||||
|
|
||||||
.cover { background:linear-gradient(160deg,#0f1f45,#1a2744,#0d1b35); color:white; padding:60px 40px; text-align:center; }
|
|
||||||
.cover h1 { font-size:2rem; font-weight:800; letter-spacing:-0.5px; }
|
|
||||||
.cover .subtitle { color:rgba(255,255,255,0.6); margin-top:8px; font-size:1rem; }
|
|
||||||
.cover .badge { display:inline-block; margin-top:24px; background:var(--red); color:white; padding:6px 18px; border-radius:20px; font-size:0.82rem; font-weight:600; }
|
|
||||||
|
|
||||||
.toc { background:white; padding:24px 32px; border-bottom:1px solid var(--border); }
|
|
||||||
.toc h2 { font-size:0.78rem; font-weight:700; color:var(--muted); text-transform:uppercase; letter-spacing:1px; margin-bottom:12px; }
|
|
||||||
.toc ol { padding-left:18px; }
|
|
||||||
.toc li { font-size:0.9rem; padding:3px 0; }
|
|
||||||
.toc a { color:var(--navy); text-decoration:none; }
|
|
||||||
.toc a:hover { color:var(--red); }
|
|
||||||
|
|
||||||
.content { max-width:820px; margin:0 auto; padding:32px 24px 64px; }
|
|
||||||
|
|
||||||
.section { margin-bottom:48px; }
|
|
||||||
.section-header { display:flex; align-items:center; gap:14px; margin-bottom:20px; padding-bottom:12px; border-bottom:2px solid var(--red); }
|
|
||||||
.step-num { width:36px; height:36px; background:var(--red); color:white; border-radius:50%; display:flex; align-items:center; justify-content:center; font-weight:800; font-size:1rem; flex-shrink:0; }
|
|
||||||
.section-header h2 { font-size:1.25rem; font-weight:700; color:var(--navy); }
|
|
||||||
|
|
||||||
.card { background:white; border-radius:12px; border:1px solid var(--border); padding:20px; margin-bottom:16px; }
|
|
||||||
.tip { background:#fef3c7; border:1px solid #fcd34d; border-radius:10px; padding:14px 16px; font-size:0.88rem; margin-bottom:16px; }
|
|
||||||
.tip strong { color:#92400e; }
|
|
||||||
.info { background:#eff6ff; border:1px solid #bfdbfe; border-radius:10px; padding:14px 16px; font-size:0.88rem; margin-bottom:16px; }
|
|
||||||
|
|
||||||
.steps { counter-reset:step; }
|
|
||||||
.step { display:flex; gap:14px; margin-bottom:12px; align-items:flex-start; }
|
|
||||||
.step::before { counter-increment:step; content:counter(step); width:24px; height:24px; background:var(--navy); color:white; border-radius:50%; font-size:0.78rem; font-weight:700; display:flex; align-items:center; justify-content:center; flex-shrink:0; margin-top:2px; }
|
|
||||||
.step p { font-size:0.9rem; }
|
|
||||||
.step code { background:#f3f4f6; padding:1px 6px; border-radius:4px; font-size:0.85rem; }
|
|
||||||
|
|
||||||
/* ——— MOCKUPS ——— */
|
|
||||||
.mockup { background:#1a1a2e; border-radius:12px; overflow:hidden; margin:16px 0; box-shadow:0 4px 20px rgba(0,0,0,0.2); }
|
|
||||||
.mockup-bar { background:#0f1420; padding:8px 14px; display:flex; align-items:center; gap:8px; }
|
|
||||||
.mockup-dot { width:10px; height:10px; border-radius:50%; }
|
|
||||||
.mockup-body { padding:16px; }
|
|
||||||
|
|
||||||
.mock-activation { background:linear-gradient(160deg,#0f1f45,#1a2744); padding:32px 20px; text-align:center; border-radius:8px; }
|
|
||||||
.mock-activation h3 { color:white; font-size:1.1rem; margin-bottom:6px; }
|
|
||||||
.mock-activation p { color:rgba(255,255,255,0.5); font-size:0.82rem; margin-bottom:20px; }
|
|
||||||
.mock-input { background:rgba(255,255,255,0.1); border:1px solid rgba(255,255,255,0.2); border-radius:8px; padding:10px 14px; color:white; font-size:0.9rem; text-align:center; margin-bottom:12px; width:100%; max-width:280px; }
|
|
||||||
.mock-btn-red { background:#cc0000; color:white; border:none; border-radius:8px; padding:10px 24px; font-weight:700; cursor:pointer; font-size:0.9rem; }
|
|
||||||
.mock-btn-gray { background:#374151; color:white; border:none; border-radius:8px; padding:8px 16px; font-weight:600; cursor:pointer; font-size:0.82rem; }
|
|
||||||
|
|
||||||
.mock-panel { background:#1e2235; border-radius:10px; padding:12px; }
|
|
||||||
.mock-member { display:flex; align-items:center; gap:10px; padding:8px 10px; background:rgba(255,255,255,0.05); border-radius:8px; margin-bottom:6px; }
|
|
||||||
.mock-avatar { width:36px; height:36px; border-radius:50%; object-fit:cover; flex-shrink:0; display:flex; align-items:center; justify-content:center; font-size:0.75rem; font-weight:700; color:white; }
|
|
||||||
.mock-name { font-size:0.85rem; font-weight:600; color:white; }
|
|
||||||
.mock-role { font-size:0.72rem; color:rgba(255,255,255,0.5); }
|
|
||||||
.mock-badge { padding:2px 8px; border-radius:12px; font-size:0.7rem; font-weight:700; margin-left:auto; }
|
|
||||||
.mock-badge.absent { background:rgba(204,0,0,0.2); color:#f87171; }
|
|
||||||
.mock-badge.present { background:rgba(22,163,74,0.2); color:#4ade80; }
|
|
||||||
|
|
||||||
.mock-timer { text-align:center; padding:20px; }
|
|
||||||
.mock-timer-ring { width:100px; height:100px; border-radius:50%; border:6px solid #cc0000; display:flex; align-items:center; justify-content:center; margin:0 auto 12px; }
|
|
||||||
.mock-timer-num { font-size:2rem; font-weight:800; color:white; }
|
|
||||||
.mock-timer-name { color:white; font-weight:700; font-size:1rem; }
|
|
||||||
.mock-timer-company { color:rgba(255,255,255,0.5); font-size:0.8rem; }
|
|
||||||
|
|
||||||
.mock-tabs { display:flex; border-bottom:1px solid rgba(255,255,255,0.1); margin-bottom:12px; }
|
|
||||||
.mock-tab { padding:8px 14px; font-size:0.78rem; color:rgba(255,255,255,0.4); border-bottom:2px solid transparent; }
|
|
||||||
.mock-tab.active { color:#cc0000; border-bottom-color:#cc0000; }
|
|
||||||
|
|
||||||
.mock-sub-modal { background:#2a2f45; border-radius:12px; padding:16px; }
|
|
||||||
.mock-sub-modal h4 { color:white; font-size:0.9rem; margin-bottom:12px; }
|
|
||||||
.mock-field { background:rgba(255,255,255,0.1); border-radius:6px; padding:8px 10px; color:rgba(255,255,255,0.7); font-size:0.8rem; margin-bottom:8px; }
|
|
||||||
.mock-sv-card { display:flex; align-items:center; gap:8px; background:rgba(255,255,255,0.07); border-radius:8px; padding:8px 10px; margin-bottom:6px; border:1px solid transparent; cursor:pointer; }
|
|
||||||
.mock-sv-card:hover { border-color:rgba(204,0,0,0.5); }
|
|
||||||
.mock-sv-name { color:white; font-size:0.82rem; font-weight:600; }
|
|
||||||
.mock-sv-meta { color:rgba(255,255,255,0.45); font-size:0.72rem; }
|
|
||||||
|
|
||||||
.admin-mockup { background:#f0f2f5; border-radius:12px; overflow:hidden; border:1px solid var(--border); margin:16px 0; }
|
|
||||||
.admin-header { background:#1a2744; color:white; padding:12px 16px; font-size:0.9rem; font-weight:700; }
|
|
||||||
.admin-tabs { background:white; display:flex; border-bottom:1px solid var(--border); padding:0 16px; }
|
|
||||||
.admin-tab { padding:10px 16px; font-size:0.82rem; font-weight:600; color:var(--muted); border-bottom:2px solid transparent; }
|
|
||||||
.admin-tab.active { color:var(--red); border-bottom-color:var(--red); }
|
|
||||||
.admin-body { padding:16px; }
|
|
||||||
.admin-member-row { display:flex; align-items:center; gap:10px; padding:8px 0; border-bottom:1px solid var(--border); cursor:pointer; }
|
|
||||||
.admin-member-row:last-child { border-bottom:none; }
|
|
||||||
.admin-photo-badge { font-size:0.72rem; margin-left:auto; }
|
|
||||||
.admin-photo-badge.ok { color:var(--success); }
|
|
||||||
.admin-photo-badge.missing { color:#d97706; }
|
|
||||||
|
|
||||||
.highlight-box { border-left:3px solid var(--red); padding:10px 16px; background:white; border-radius:0 8px 8px 0; margin:12px 0; font-size:0.88rem; }
|
|
||||||
|
|
||||||
@media print {
|
|
||||||
.cover { -webkit-print-color-adjust:exact; print-color-adjust:exact; }
|
|
||||||
.section { page-break-inside:avoid; }
|
|
||||||
.mockup, .admin-mockup { -webkit-print-color-adjust:exact; print-color-adjust:exact; }
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<div class="cover">
|
|
||||||
<div style="font-size:2.5rem; margin-bottom:8px;">🌟</div>
|
|
||||||
<h1>BNI Vorstellungsrunde</h1>
|
|
||||||
<div class="subtitle">Benutzeranleitung für Chapter-Admins</div>
|
|
||||||
<div class="badge">Version 1.1 · April 2026</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="toc">
|
|
||||||
<h2>Inhalt</h2>
|
|
||||||
<ol>
|
|
||||||
<li><a href="#start">Tool starten</a></li>
|
|
||||||
<li><a href="#aktivierung">Erstmalige Aktivierung</a></li>
|
|
||||||
<li><a href="#ansichten">Ansichten: Display & Steuerung</a></li>
|
|
||||||
<li><a href="#vorbereitung">Meeting vorbereiten</a></li>
|
|
||||||
<li><a href="#durchfuehrung">Meeting durchführen</a></li>
|
|
||||||
<li><a href="#vertreter">Vertreter eintragen</a></li>
|
|
||||||
<li><a href="#besucher">Besucher eintragen</a></li>
|
|
||||||
<li><a href="#admin">Admin-Panel: Fotos & Stammvertreter</a></li>
|
|
||||||
<li><a href="#offline">Offline-Nutzung</a></li>
|
|
||||||
</ol>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="content">
|
|
||||||
|
|
||||||
<!-- ========== 1. START ========== -->
|
|
||||||
<div class="section" id="start">
|
|
||||||
<div class="section-header">
|
|
||||||
<div class="step-num">1</div>
|
|
||||||
<h2>Tool starten</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card">
|
|
||||||
<div class="steps">
|
|
||||||
<div class="step"><p>Den Ordner <code>BNI Sonne</code> im Finder öffnen.</p></div>
|
|
||||||
<div class="step"><p>Die Datei <strong>start.command</strong> doppelklicken — es öffnet sich kurz ein Terminal-Fenster, dann startet Chrome automatisch mit dem Tool.</p></div>
|
|
||||||
<div class="step"><p>Das Terminal-Fenster im Hintergrund lassen (nicht schließen, solange du das Tool nutzt).</p></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="tip">
|
|
||||||
<strong>Hinweis:</strong> Beim ersten Doppelklick erscheint eventuell eine Sicherheitswarnung von macOS. Dann: Rechtsklick auf <code>start.command</code> → „Öffnen" → „Öffnen" bestätigen. Ab dem zweiten Mal klappt der Doppelklick direkt.
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mockup">
|
|
||||||
<div class="mockup-bar">
|
|
||||||
<div class="mockup-dot" style="background:#ff5f57;"></div>
|
|
||||||
<div class="mockup-dot" style="background:#febc2e;"></div>
|
|
||||||
<div class="mockup-dot" style="background:#28c840;"></div>
|
|
||||||
<span style="color:rgba(255,255,255,0.4);font-size:0.78rem;margin-left:8px;">Terminal</span>
|
|
||||||
</div>
|
|
||||||
<div class="mockup-body">
|
|
||||||
<p style="color:#4ade80;font-size:0.82rem;font-family:monospace;">$ python3 -m http.server 8181</p>
|
|
||||||
<p style="color:rgba(255,255,255,0.4);font-size:0.82rem;font-family:monospace;">Serving HTTP on 0.0.0.0 port 8181 …</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ========== 2. AKTIVIERUNG ========== -->
|
|
||||||
<div class="section" id="aktivierung">
|
|
||||||
<div class="section-header">
|
|
||||||
<div class="step-num">2</div>
|
|
||||||
<h2>Erstmalige Aktivierung</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="info">
|
|
||||||
Dieser Schritt ist nur beim <strong>allerersten Start</strong> auf einem neuen Gerät oder Browser nötig. Danach öffnet das Tool direkt.
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card">
|
|
||||||
<div class="steps">
|
|
||||||
<div class="step"><p>Den <strong>Lizenzcode</strong> deines Chapters eingeben (erhältst du vom Administrator).</p></div>
|
|
||||||
<div class="step"><p>Auf <strong>„Aktivieren"</strong> klicken. Das Tool lädt einmalig die Chapter-Daten vom Server.</p></div>
|
|
||||||
<div class="step"><p>Nach erfolgreicher Aktivierung öffnet sich direkt die Steuerungsansicht.</p></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mockup">
|
|
||||||
<div class="mockup-bar">
|
|
||||||
<div class="mockup-dot" style="background:#ff5f57;"></div>
|
|
||||||
<div class="mockup-dot" style="background:#febc2e;"></div>
|
|
||||||
<div class="mockup-dot" style="background:#28c840;"></div>
|
|
||||||
<span style="color:rgba(255,255,255,0.4);font-size:0.78rem;margin-left:8px;">http://localhost:8181</span>
|
|
||||||
</div>
|
|
||||||
<div class="mockup-body">
|
|
||||||
<div class="mock-activation">
|
|
||||||
<h3>BNI Vorstellungsrunde</h3>
|
|
||||||
<p>Bitte Chapter-Code eingeben</p>
|
|
||||||
<div style="margin-bottom:8px;"><input class="mock-input" placeholder="z. B. sonne-7x4m9k" readonly></div>
|
|
||||||
<button class="mock-btn-red">Aktivieren</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ========== 3. ANSICHTEN ========== -->
|
|
||||||
<div class="section" id="ansichten">
|
|
||||||
<div class="section-header">
|
|
||||||
<div class="step-num">3</div>
|
|
||||||
<h2>Ansichten: Display & Steuerung</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card">
|
|
||||||
<p style="font-size:0.9rem;">Das Tool läuft in <strong>zwei Fenstern gleichzeitig</strong>:</p>
|
|
||||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-top:14px;">
|
|
||||||
<div style="background:#f8f9fa;border-radius:8px;padding:14px;border-left:3px solid var(--navy);">
|
|
||||||
<div style="font-weight:700;font-size:0.88rem;color:var(--navy);margin-bottom:6px;">🖥 Display-Fenster</div>
|
|
||||||
<div style="font-size:0.82rem;color:var(--muted);">Wird auf dem Präsentationsmonitor / Beamer angezeigt. Zeigt das aktuelle Mitglied groß mit Timer.</div>
|
|
||||||
</div>
|
|
||||||
<div style="background:#f8f9fa;border-radius:8px;padding:14px;border-left:3px solid var(--red);">
|
|
||||||
<div style="font-weight:700;font-size:0.88rem;color:var(--red);margin-bottom:6px;">🎛 Steuerungs-Fenster</div>
|
|
||||||
<div style="font-size:0.82rem;color:var(--muted);">Dein Laptop-Bildschirm. Hier steuerst du den Ablauf, trägst Vertreter ein und navigierst.</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="highlight-box">
|
|
||||||
Beim Öffnen des Tools wird automatisch ein zweites Tab für das Display geöffnet. Ziehe dieses Tab auf den Beamer-Bildschirm und drücke <strong>F11</strong> für Vollbild.
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mockup">
|
|
||||||
<div class="mockup-bar">
|
|
||||||
<div class="mockup-dot" style="background:#ff5f57;"></div>
|
|
||||||
<div class="mockup-dot" style="background:#febc2e;"></div>
|
|
||||||
<div class="mockup-dot" style="background:#28c840;"></div>
|
|
||||||
<span style="color:rgba(255,255,255,0.4);font-size:0.78rem;margin-left:8px;">Display-Ansicht (Beamer)</span>
|
|
||||||
</div>
|
|
||||||
<div class="mockup-body">
|
|
||||||
<div class="mock-timer">
|
|
||||||
<div style="color:rgba(255,255,255,0.4);font-size:0.75rem;margin-bottom:10px;">JETZT SPRICHT</div>
|
|
||||||
<div class="mock-timer-ring">
|
|
||||||
<span class="mock-timer-num">45</span>
|
|
||||||
</div>
|
|
||||||
<div class="mock-timer-name">Maria Mustermann</div>
|
|
||||||
<div class="mock-timer-company">Muster GmbH · Steuerberatung</div>
|
|
||||||
<div style="color:rgba(255,255,255,0.3);font-size:0.75rem;margin-top:8px;">Nächster: Hans Müller</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ========== 4. VORBEREITUNG ========== -->
|
|
||||||
<div class="section" id="vorbereitung">
|
|
||||||
<div class="section-header">
|
|
||||||
<div class="step-num">4</div>
|
|
||||||
<h2>Meeting vorbereiten</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card">
|
|
||||||
<p style="font-weight:700;font-size:0.9rem;margin-bottom:10px;">Vor dem Meeting: Abwesenheiten markieren</p>
|
|
||||||
<div class="steps">
|
|
||||||
<div class="step"><p>Im Steuerungs-Fenster: Tab <strong>„Mitglieder"</strong> öffnen.</p></div>
|
|
||||||
<div class="step"><p>Auf den Namen eines abwesenden Mitglieds klicken → kleines Menü öffnet sich.</p></div>
|
|
||||||
<div class="step"><p><strong>„Abwesend"</strong> wählen — das Mitglied wird aus der Vorstellungsrunde ausgeblendet (roter Punkt).</p></div>
|
|
||||||
<div class="step"><p>Optional: Reihenfolge per <strong>Drag & Drop</strong> anpassen.</p></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mockup">
|
|
||||||
<div class="mockup-bar">
|
|
||||||
<div class="mockup-dot" style="background:#ff5f57;"></div>
|
|
||||||
<div class="mockup-dot" style="background:#febc2e;"></div>
|
|
||||||
<div class="mockup-dot" style="background:#28c840;"></div>
|
|
||||||
<span style="color:rgba(255,255,255,0.4);font-size:0.78rem;margin-left:8px;">Steuerung – Mitgliederliste</span>
|
|
||||||
</div>
|
|
||||||
<div class="mockup-body">
|
|
||||||
<div class="mock-tabs">
|
|
||||||
<div class="mock-tab active">Mitglieder</div>
|
|
||||||
<div class="mock-tab">Vertretungen</div>
|
|
||||||
<div class="mock-tab">Besucher</div>
|
|
||||||
</div>
|
|
||||||
<div class="mock-panel">
|
|
||||||
<div class="mock-member">
|
|
||||||
<div class="mock-avatar" style="background:#1a2744;">AM</div>
|
|
||||||
<div><div class="mock-name">Anna Müller</div><div class="mock-role">Steuerberatung</div></div>
|
|
||||||
<div class="mock-badge present">anwesend</div>
|
|
||||||
</div>
|
|
||||||
<div class="mock-member" style="opacity:0.5;">
|
|
||||||
<div class="mock-avatar" style="background:#7f1d1d;">KS</div>
|
|
||||||
<div><div class="mock-name">Klaus Schmidt</div><div class="mock-role">IT-Systeme</div></div>
|
|
||||||
<div class="mock-badge absent">abwesend</div>
|
|
||||||
</div>
|
|
||||||
<div class="mock-member">
|
|
||||||
<div class="mock-avatar" style="background:#1e3a5f;">SL</div>
|
|
||||||
<div><div class="mock-name">Sandra Lang</div><div class="mock-role">Marketing</div></div>
|
|
||||||
<div class="mock-badge present">anwesend</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ========== 5. DURCHFÜHRUNG ========== -->
|
|
||||||
<div class="section" id="durchfuehrung">
|
|
||||||
<div class="section-header">
|
|
||||||
<div class="step-num">5</div>
|
|
||||||
<h2>Meeting durchführen</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card">
|
|
||||||
<div class="steps">
|
|
||||||
<div class="step"><p>Auf <strong>„Start"</strong> klicken — der Timer startet für das erste Mitglied.</p></div>
|
|
||||||
<div class="step"><p>Wenn die Zeit abgelaufen ist (Timer rot): <strong>„Weiter →"</strong> drücken für das nächste Mitglied.</p></div>
|
|
||||||
<div class="step"><p>Mit <strong>„← Zurück"</strong> kannst du jederzeit zum vorherigen Mitglied springen.</p></div>
|
|
||||||
<div class="step"><p>Timer lässt sich jederzeit mit <strong>„Pause / Fortsetzen"</strong> anhalten.</p></div>
|
|
||||||
<div class="step"><p>Am Ende erscheint eine Abschluss-Ansicht — mit <strong>„Neu starten"</strong> zurück zum Anfang.</p></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="tip">
|
|
||||||
<strong>Tipp:</strong> Die Tastenkombination <strong>→ Pfeiltaste</strong> auf der Tastatur funktioniert als „Weiter" — praktisch für die Fernbedienung.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ========== 6. VERTRETER ========== -->
|
|
||||||
<div class="section" id="vertreter">
|
|
||||||
<div class="section-header">
|
|
||||||
<div class="step-num">6</div>
|
|
||||||
<h2>Vertreter eintragen</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card">
|
|
||||||
<p style="font-size:0.9rem;margin-bottom:12px;">Wenn ein Mitglied einen Vertreter schickt, wird dieser in der Vorstellungsrunde an Stelle des Mitglieds angezeigt.</p>
|
|
||||||
<div class="steps">
|
|
||||||
<div class="step"><p>Tab <strong>„Vertretungen"</strong> öffnen.</p></div>
|
|
||||||
<div class="step"><p>Mitglied aus der Liste wählen, das vertreten wird.</p></div>
|
|
||||||
<div class="step"><p>Falls die Person bereits als <strong>Stammvertreter</strong> gespeichert ist: oben aus der Schnellauswahl wählen — alle Felder werden automatisch ausgefüllt.</p></div>
|
|
||||||
<div class="step"><p>Andernfalls: Name, Firma und Fachgebiet des Vertreters eingeben und ggf. ein Foto hochladen.</p></div>
|
|
||||||
<div class="step"><p>Auf <strong>„Speichern"</strong> klicken.</p></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mockup">
|
|
||||||
<div class="mockup-bar">
|
|
||||||
<div class="mockup-dot" style="background:#ff5f57;"></div>
|
|
||||||
<div class="mockup-dot" style="background:#febc2e;"></div>
|
|
||||||
<div class="mockup-dot" style="background:#28c840;"></div>
|
|
||||||
<span style="color:rgba(255,255,255,0.4);font-size:0.78rem;margin-left:8px;">Steuerung – Vertretung eintragen</span>
|
|
||||||
</div>
|
|
||||||
<div class="mockup-body">
|
|
||||||
<div class="mock-sub-modal">
|
|
||||||
<h4>Vertreter für Klaus Schmidt</h4>
|
|
||||||
<div style="font-size:0.72rem;color:rgba(255,255,255,0.4);margin-bottom:8px;">STAMMVERTRETER WÄHLEN</div>
|
|
||||||
<div class="mock-sv-card">
|
|
||||||
<div class="mock-avatar" style="background:#1a2744;width:32px;height:32px;">MF</div>
|
|
||||||
<div><div class="mock-sv-name">Max Fischer</div><div class="mock-sv-meta">Fischer GmbH · Versicherung</div></div>
|
|
||||||
</div>
|
|
||||||
<div class="mock-sv-card">
|
|
||||||
<div class="mock-avatar" style="background:#4a1942;width:32px;height:32px;">LB</div>
|
|
||||||
<div><div class="mock-sv-name">Lisa Berger</div><div class="mock-sv-meta">Berger & Co · Marketing</div></div>
|
|
||||||
</div>
|
|
||||||
<div style="font-size:0.72rem;color:rgba(255,255,255,0.4);margin:10px 0 6px;">ODER MANUELL EINGEBEN</div>
|
|
||||||
<div class="mock-field">Name des Vertreters …</div>
|
|
||||||
<div class="mock-field">Firma …</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ========== 7. BESUCHER ========== -->
|
|
||||||
<div class="section" id="besucher">
|
|
||||||
<div class="section-header">
|
|
||||||
<div class="step-num">7</div>
|
|
||||||
<h2>Besucher eintragen</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card">
|
|
||||||
<div class="steps">
|
|
||||||
<div class="step"><p>Tab <strong>„Besucher"</strong> öffnen.</p></div>
|
|
||||||
<div class="step"><p>Auf <strong>„+ Besucher hinzufügen"</strong> klicken.</p></div>
|
|
||||||
<div class="step"><p>Name und optional Firma eingeben und speichern.</p></div>
|
|
||||||
<div class="step"><p>Besucher erscheinen am Ende der Vorstellungsrunde nach allen Mitgliedern.</p></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="tip">
|
|
||||||
<strong>Tipp:</strong> Besucher werden nicht dauerhaft gespeichert — sie werden nach dem Meeting automatisch gelöscht, wenn die Runde zurückgesetzt wird.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ========== 8. ADMIN ========== -->
|
|
||||||
<div class="section" id="admin">
|
|
||||||
<div class="section-header">
|
|
||||||
<div class="step-num">8</div>
|
|
||||||
<h2>Admin-Panel: Fotos & Stammvertreter</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="info">
|
|
||||||
Das Admin-Panel öffnest du über <code>http://localhost:8181/admin.html</code> — am einfachsten über den Link im Steuerungs-Fenster unter dem Tab <strong>„Admin"</strong>.
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card">
|
|
||||||
<p style="font-weight:700;font-size:0.9rem;margin-bottom:10px;">Mitgliederfotos hinzufügen</p>
|
|
||||||
<div class="steps">
|
|
||||||
<div class="step"><p>Im Admin-Panel Tab <strong>„Mitglieder"</strong> öffnen.</p></div>
|
|
||||||
<div class="step"><p>Auf ein Mitglied mit <strong>„+ Foto"</strong> klicken.</p></div>
|
|
||||||
<div class="step"><p>Foto aus dem Finder wählen (JPG/PNG, beliebige Größe — wird automatisch verkleinert).</p></div>
|
|
||||||
<div class="step"><p>Auf <strong>„Speichern"</strong> klicken — das Foto ist jetzt lokal gespeichert.</p></div>
|
|
||||||
<div class="step"><p>Nach allen Fotos: <strong>„Auf Server hochladen"</strong> — dann haben alle Geräte die Fotos beim nächsten „Vom Server laden".</p></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="admin-mockup">
|
|
||||||
<div class="admin-header">BNI Admin-Panel · BNI Chapter Sonne</div>
|
|
||||||
<div class="admin-tabs">
|
|
||||||
<div class="admin-tab">Stammvertreter</div>
|
|
||||||
<div class="admin-tab active">Mitglieder</div>
|
|
||||||
<div class="admin-tab">Einstellungen</div>
|
|
||||||
</div>
|
|
||||||
<div class="admin-body">
|
|
||||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;">
|
|
||||||
<span style="font-weight:700;font-size:0.9rem;">Mitglieder</span>
|
|
||||||
<div style="display:flex;gap:8px;">
|
|
||||||
<span style="background:#f3f4f6;border:1px solid var(--border);border-radius:6px;padding:5px 10px;font-size:0.78rem;font-weight:600;">🔄 Vom Server laden</span>
|
|
||||||
<span style="background:var(--red);color:white;border-radius:6px;padding:5px 10px;font-size:0.78rem;font-weight:600;">📤 Auf Server hochladen</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="admin-member-row">
|
|
||||||
<div class="mock-avatar" style="background:#1a2744;width:40px;height:40px;font-size:0.8rem;">AM</div>
|
|
||||||
<div style="flex:1;"><div style="font-size:0.88rem;font-weight:600;">Anna Müller</div><div style="font-size:0.75rem;color:var(--muted);">Steuerberatung</div></div>
|
|
||||||
<span class="admin-photo-badge ok">✓ Foto</span>
|
|
||||||
</div>
|
|
||||||
<div class="admin-member-row">
|
|
||||||
<div class="mock-avatar" style="background:#4a1942;width:40px;height:40px;font-size:0.8rem;">KS</div>
|
|
||||||
<div style="flex:1;"><div style="font-size:0.88rem;font-weight:600;">Klaus Schmidt</div><div style="font-size:0.75rem;color:var(--muted);">IT-Systeme</div></div>
|
|
||||||
<span class="admin-photo-badge missing">+ Foto</span>
|
|
||||||
</div>
|
|
||||||
<div class="admin-member-row">
|
|
||||||
<div class="mock-avatar" style="background:#1e3a5f;width:40px;height:40px;font-size:0.8rem;">SL</div>
|
|
||||||
<div style="flex:1;"><div style="font-size:0.88rem;font-weight:600;">Sandra Lang</div><div style="font-size:0.75rem;color:var(--muted);">Marketing</div></div>
|
|
||||||
<span class="admin-photo-badge ok">✓ Foto</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card" style="margin-top:16px;">
|
|
||||||
<p style="font-weight:700;font-size:0.9rem;margin-bottom:10px;">Stammvertreter anlegen</p>
|
|
||||||
<p style="font-size:0.88rem;color:var(--muted);margin-bottom:12px;">Stammvertreter sind Personen, die regelmäßig als Vertreter kommen. Einmal anlegen → bei jeder Vertretung schnell auswählen.</p>
|
|
||||||
<div class="steps">
|
|
||||||
<div class="step"><p>Tab <strong>„Stammvertreter"</strong> öffnen.</p></div>
|
|
||||||
<div class="step"><p>Auf <strong>„+ Hinzufügen"</strong> klicken.</p></div>
|
|
||||||
<div class="step"><p>Name, Firma, Fachgebiet und Foto eingeben.</p></div>
|
|
||||||
<div class="step"><p>Optional: <strong>„Aus anderem Chapter"</strong> — Mitglied aus einem anderen Chapter-Verzeichnis als Stammvertreter übernehmen.</p></div>
|
|
||||||
<div class="step"><p>Auf <strong>„Auf Server sichern"</strong> klicken, damit die Liste nicht verloren geht.</p></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ========== 9. OFFLINE ========== -->
|
|
||||||
<div class="section" id="offline">
|
|
||||||
<div class="section-header">
|
|
||||||
<div class="step-num">9</div>
|
|
||||||
<h2>Offline-Nutzung</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card">
|
|
||||||
<p style="font-size:0.9rem;margin-bottom:12px;">Das Tool funktioniert <strong>vollständig ohne Internet</strong>, sobald es einmal online eingerichtet wurde.</p>
|
|
||||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;">
|
|
||||||
<div style="background:#f0fdf4;border-radius:8px;padding:12px;border:1px solid #bbf7d0;">
|
|
||||||
<div style="font-weight:700;color:var(--success);font-size:0.85rem;margin-bottom:6px;">✓ Funktioniert offline</div>
|
|
||||||
<ul style="font-size:0.82rem;color:#166534;padding-left:16px;line-height:1.7;">
|
|
||||||
<li>Meeting starten & steuern</li>
|
|
||||||
<li>Timer, Navigation</li>
|
|
||||||
<li>Abwesenheiten markieren</li>
|
|
||||||
<li>Vertreter & Besucher eintragen</li>
|
|
||||||
<li>Stammvertreter verwenden</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div style="background:#fef2f2;border-radius:8px;padding:12px;border:1px solid #fecaca;">
|
|
||||||
<div style="font-weight:700;color:#dc2626;font-size:0.85rem;margin-bottom:6px;">✗ Braucht Internet</div>
|
|
||||||
<ul style="font-size:0.82rem;color:#991b1b;padding-left:16px;line-height:1.7;">
|
|
||||||
<li>Erstmalige Aktivierung</li>
|
|
||||||
<li>Mitglieder vom Server laden</li>
|
|
||||||
<li>Fotos auf Server hochladen</li>
|
|
||||||
<li>Stammvertreter sichern</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="highlight-box">
|
|
||||||
<strong>Empfehlung:</strong> Vor jedem Meeting einmal kurz „Vom Server laden" klicken — so hast du immer die aktuellste Mitgliederliste & Fotos, auch wenn beim Meeting kein Internet verfügbar ist.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- FOOTER -->
|
|
||||||
<div style="text-align:center;padding:32px 0;color:var(--muted);font-size:0.8rem;border-top:1px solid var(--border);margin-top:32px;">
|
|
||||||
BNI Vorstellungsrunde · Interne Anleitung · Bei Fragen an den Administrator wenden
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div><!-- .content -->
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
6
chapters/epsilon-847362/config.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"chapterName": "BNI Chapter Epsilon",
|
||||||
|
"syncUrl": "https://bni-ulm.com/epsilon/de/memberlist?chapterName=®ionIds=43671&isChapterwebsite=true",
|
||||||
|
"websiteUrl": "https://bni-ulm.de/epsilon",
|
||||||
|
"defaultTimer": 60
|
||||||
|
}
|
||||||
53
chapters/galaxis-go26for37/stammvertreter.json
Normal file
BIN
chapters/helios-2cbws6/BNI Logo.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
7
chapters/helios-2cbws6/config.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"chapterName": "BNI Chapter Helios",
|
||||||
|
"logoUrl": "BNI Logo.png",
|
||||||
|
"syncUrl": "https://bni-konstanz.de/helios/de/memberlist?chapterName=40832®ionIds=6023&isChapterwebsite=true",
|
||||||
|
"websiteUrl": "https://bni-konstanz.de/helios",
|
||||||
|
"defaultTimer": 60
|
||||||
|
}
|
||||||
259
chapters/helios-2cbws6/members.json
Normal file
205
chapters/helios-2cbws6/stammvertreter.json
Normal file
6
chapters/kallisto-57621678sx/config.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"chapterName": "BNI Chapter Kallisto",
|
||||||
|
"syncUrl": "https://bni-ulm.com/kallisto/de/memberlist?chapterName=35844®ionIds=43671$isChapterwebsite",
|
||||||
|
"websiteUrl": "https://bni-ulm.de/kallisto",
|
||||||
|
"defaultTimer": 60
|
||||||
|
}
|
||||||
172
chapters/kallisto-57621678sx/members.json
Normal file
BIN
chapters/lumora-567294212/BNI Logo.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
82
chapters/lumora-567294212/agenda.json
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "willkommen",
|
||||||
|
"label": "Willkommen",
|
||||||
|
"type": "embed",
|
||||||
|
"embedUrl": "https://show.zohopublic.eu/publish/cb5g063c60ec44c3347ad841c139671232dff"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "trainingsbeitrag",
|
||||||
|
"label": "Trainingsbeitrag",
|
||||||
|
"type": "embed",
|
||||||
|
"embedUrl": "https://show.zohopublic.eu/publish/cb5g066608ae9da024f8e87c0e705dbe6852f"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "visitenkarten",
|
||||||
|
"label": "Visitenkarten",
|
||||||
|
"type": "image",
|
||||||
|
"imagePath": "agenda/visitenkarten.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "verleihung-der-lumora",
|
||||||
|
"label": "Verleihung der Lumora",
|
||||||
|
"type": "embed",
|
||||||
|
"embedUrl": "https://show.zohopublic.eu/publish/cb5g0385ba8f3e8a8422ab051f1591bcf598d"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "bericht-des-mitgliederkoordinators",
|
||||||
|
"label": "Bericht des Mitgliederkoordinators",
|
||||||
|
"type": "embed",
|
||||||
|
"embedUrl": "https://show.zohopublic.eu/publish/cb5g0d27561ab34c141a39f981ab6b36fbd03"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "break-out",
|
||||||
|
"label": "Break Out",
|
||||||
|
"type": "embed",
|
||||||
|
"embedUrl": "https://show.zohopublic.eu/publish/cb5g0fbb79876ba15437abf469c098a72b789"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "naechste-hauptpraesentation",
|
||||||
|
"label": "Nächste Hauptpräsentation",
|
||||||
|
"type": "embed",
|
||||||
|
"embedUrl": "https://show.zohopublic.eu/publish/cb5g0ba90bb948a91426d9bb4bbb59bb5bf9d"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "marketing-ergebnisrunde-erklaerung",
|
||||||
|
"label": "Marketing- Ergebnisrunde Erklärung",
|
||||||
|
"type": "embed",
|
||||||
|
"embedUrl": "https://show.zohopublic.eu/publish/cb5g0848053a6eea54f18bbaaee7d33861386"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "feedback-gaeste",
|
||||||
|
"label": "Feedback Gäste",
|
||||||
|
"type": "embed",
|
||||||
|
"embedUrl": "https://show.zohopublic.eu/publish/cb5g0da62ecf51cdf4d9285097db2c9b80b26"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ziehung-des-tagespreises",
|
||||||
|
"label": "Ziehung des Tagespreises",
|
||||||
|
"type": "embed",
|
||||||
|
"embedUrl": "https://show.zohopublic.eu/publish/cb5g0b231f4b1bdf34850a8a8baca4b2d6972"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "bericht-des-schatzmeisters",
|
||||||
|
"label": "Bericht des Schatzmeisters",
|
||||||
|
"type": "embed",
|
||||||
|
"embedUrl": "https://show.zohopublic.eu/publish/cb5g0249a5f37a97e419db184bef50bcbdb8e"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "bericht-des-eventkoordinators",
|
||||||
|
"label": "Bericht des Eventkoordinators",
|
||||||
|
"type": "embed",
|
||||||
|
"embedUrl": "https://show.zohopublic.eu/publish/cb5g0ad05a18b97214634818823cd3bd3ebca"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "zitat",
|
||||||
|
"label": "Zitat",
|
||||||
|
"type": "embed",
|
||||||
|
"embedUrl": "https://show.zohopublic.eu/publish/cb5g03d4fc1ad7a8141c098addbee9dfcbffb"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
BIN
chapters/lumora-567294212/agenda/visitenkarten.jpg
Normal file
|
After Width: | Height: | Size: 197 KiB |
7
chapters/lumora-567294212/config.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"chapterName": "BNI Chapter Lumora Hybrid",
|
||||||
|
"logoUrl": "BNI Logo.png",
|
||||||
|
"syncUrl": "https://bni-ulm.com/lumora/de/memberlist?chapterName=44269®ionIds=43671&isChapterwebsite=true",
|
||||||
|
"websiteUrl": "https://bni-ulm.de/lumora",
|
||||||
|
"defaultTimer": 60
|
||||||
|
}
|
||||||
184
chapters/lumora-567294212/members.json
Normal file
93
chapters/lumora-567294212/stammvertreter.json
Normal file
46
chapters/sonne-7x4m9k/agenda.json
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "bericht-der-schatzmeisterin",
|
||||||
|
"label": "Bericht der Schatzmeisterin",
|
||||||
|
"type": "image",
|
||||||
|
"imagePath": "agenda/bericht-der-schatzmeisterin.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "willkommen",
|
||||||
|
"label": "Willkommen",
|
||||||
|
"type": "image",
|
||||||
|
"imagePath": "agenda/willkommen.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ende-logo",
|
||||||
|
"label": "Ende - Logo",
|
||||||
|
"type": "image",
|
||||||
|
"imagePath": "agenda/ende-logo.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "bericht-des-mitgliederkoordinators-besuchertag",
|
||||||
|
"label": "Bericht des Mitgliederkoordinators Besuchertag",
|
||||||
|
"type": "image",
|
||||||
|
"imagePath": "agenda/bericht-des-mitgliederkoordinators-besuchertag.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "bericht-des-mitgliederkoordinators-megafruehstueck",
|
||||||
|
"label": "Bericht des Mitgliederkoordinators Megafrühstück",
|
||||||
|
"type": "image",
|
||||||
|
"imagePath": "agenda/bericht-des-mitgliederkoordinators-megafruehstueck.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "logo",
|
||||||
|
"label": "Logo",
|
||||||
|
"type": "image",
|
||||||
|
"imagePath": "agenda/logo.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "logo-2",
|
||||||
|
"label": "Logo 2",
|
||||||
|
"type": "image",
|
||||||
|
"imagePath": "agenda/logo-2.jpg"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
BIN
chapters/sonne-7x4m9k/agenda/bericht-der-schatzmeisterin.jpg
Normal file
|
After Width: | Height: | Size: 193 KiB |
|
After Width: | Height: | Size: 124 KiB |
|
After Width: | Height: | Size: 137 KiB |
|
After Width: | Height: | Size: 149 KiB |
BIN
chapters/sonne-7x4m9k/agenda/ende-logo.jpg
Normal file
|
After Width: | Height: | Size: 72 KiB |
BIN
chapters/sonne-7x4m9k/agenda/logo-2.jpg
Normal file
|
After Width: | Height: | Size: 72 KiB |
BIN
chapters/sonne-7x4m9k/agenda/logo.jpg
Normal file
|
After Width: | Height: | Size: 72 KiB |
BIN
chapters/sonne-7x4m9k/agenda/schatzmeister-offline.jpg
Normal file
|
After Width: | Height: | Size: 277 KiB |
BIN
chapters/sonne-7x4m9k/agenda/willkommen.jpg
Normal file
|
After Width: | Height: | Size: 72 KiB |
@@ -2,5 +2,6 @@
|
|||||||
"chapterName": "BNI Chapter Sonne",
|
"chapterName": "BNI Chapter Sonne",
|
||||||
"syncUrl": "https://bni-ulm.com/sonne/de/memberlist?chapterName=6035®ionIds=43671&isChapterwebsite=true",
|
"syncUrl": "https://bni-ulm.com/sonne/de/memberlist?chapterName=6035®ionIds=43671&isChapterwebsite=true",
|
||||||
"websiteUrl": "https://bni-ulm.de/sonne",
|
"websiteUrl": "https://bni-ulm.de/sonne",
|
||||||
"defaultTimer": 60
|
"defaultTimer": 60,
|
||||||
}
|
"memberCoordinatorReport": false
|
||||||
|
}
|
||||||
134
chapters/sonne-7x4m9k/coordinator-report-2025-2026.json
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
{
|
||||||
|
"year": "2025-2026",
|
||||||
|
"metrics": [
|
||||||
|
{
|
||||||
|
"key": "attendance",
|
||||||
|
"label": "Anwesenheit",
|
||||||
|
"unit": "%",
|
||||||
|
"annualTarget": 0,
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"month": "2025-04",
|
||||||
|
"value": 98
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"month": "2025-05",
|
||||||
|
"value": 98
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "members",
|
||||||
|
"label": "Mitgliederzahl",
|
||||||
|
"unit": "",
|
||||||
|
"annualTarget": 0,
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"month": "2025-04",
|
||||||
|
"value": 41
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"month": "2025-05",
|
||||||
|
"value": 42
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "newMembers",
|
||||||
|
"label": "Neue Mitglieder",
|
||||||
|
"unit": "",
|
||||||
|
"annualTarget": 0,
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"month": "2025-04",
|
||||||
|
"value": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"month": "2025-05",
|
||||||
|
"value": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "guests",
|
||||||
|
"label": "Gäste / Besucher",
|
||||||
|
"unit": "",
|
||||||
|
"annualTarget": 0,
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"month": "2025-04",
|
||||||
|
"value": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"month": "2025-05",
|
||||||
|
"value": 6
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "oneOnOne",
|
||||||
|
"label": "4-Augen-Gespräche",
|
||||||
|
"unit": "",
|
||||||
|
"annualTarget": 0,
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"month": "2025-04",
|
||||||
|
"value": 118
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"month": "2025-05",
|
||||||
|
"value": 72
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "cte",
|
||||||
|
"label": "CTE",
|
||||||
|
"unit": "",
|
||||||
|
"annualTarget": 0,
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"month": "2025-04",
|
||||||
|
"value": 254
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"month": "2025-05",
|
||||||
|
"value": 59
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "referrals",
|
||||||
|
"label": "Empfehlungen",
|
||||||
|
"unit": "",
|
||||||
|
"annualTarget": 0,
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"month": "2025-04",
|
||||||
|
"value": 70
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"month": "2025-05",
|
||||||
|
"value": 32
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "revenueThank",
|
||||||
|
"label": "Umsatzdanke",
|
||||||
|
"unit": "",
|
||||||
|
"annualTarget": 0,
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"month": "2025-04",
|
||||||
|
"value": 106513
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"month": "2025-05",
|
||||||
|
"value": 231210
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"updatedAt": "2026-05-21"
|
||||||
|
}
|
||||||
134
chapters/sonne-7x4m9k/coordinator-report-2026-2027.json
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
{
|
||||||
|
"year": "2026-2027",
|
||||||
|
"metrics": [
|
||||||
|
{
|
||||||
|
"key": "attendance",
|
||||||
|
"label": "Anwesenheit",
|
||||||
|
"unit": "%",
|
||||||
|
"annualTarget": 95,
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"month": "2026-04",
|
||||||
|
"value": 96
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"month": "2026-05",
|
||||||
|
"value": 97
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "members",
|
||||||
|
"label": "Mitgliederzahl",
|
||||||
|
"unit": "",
|
||||||
|
"annualTarget": 49,
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"month": "2026-04",
|
||||||
|
"value": 42
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"month": "2026-05",
|
||||||
|
"value": 42
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "newMembers",
|
||||||
|
"label": "Neue Mitglieder",
|
||||||
|
"unit": "",
|
||||||
|
"annualTarget": 7,
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"month": "2026-04",
|
||||||
|
"value": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"month": "2026-05",
|
||||||
|
"value": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "guests",
|
||||||
|
"label": "Gäste / Besucher",
|
||||||
|
"unit": "",
|
||||||
|
"annualTarget": 10.5,
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"month": "2026-04",
|
||||||
|
"value": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"month": "2026-05",
|
||||||
|
"value": 6
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "oneOnOne",
|
||||||
|
"label": "4-Augen-Gespräche",
|
||||||
|
"unit": "",
|
||||||
|
"annualTarget": 168,
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"month": "2026-04",
|
||||||
|
"value": 56
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"month": "2026-05",
|
||||||
|
"value": 72
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "cte",
|
||||||
|
"label": "CTE",
|
||||||
|
"unit": "",
|
||||||
|
"annualTarget": 705.6,
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"month": "2026-04",
|
||||||
|
"value": 208
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"month": "2026-05",
|
||||||
|
"value": 59
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "referrals",
|
||||||
|
"label": "Empfehlungen",
|
||||||
|
"unit": "",
|
||||||
|
"annualTarget": 100.8,
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"month": "2026-04",
|
||||||
|
"value": 34
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"month": "2026-05",
|
||||||
|
"value": 32
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "revenueThank",
|
||||||
|
"label": "Umsatzdanke",
|
||||||
|
"unit": "",
|
||||||
|
"annualTarget": 146034,
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"month": "2026-04",
|
||||||
|
"value": 26259
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"month": "2026-05",
|
||||||
|
"value": 231210
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"updatedAt": "2026-05-24"
|
||||||
|
}
|
||||||
61
chapters/sonne-7x4m9k/coordinator-report.json
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
{
|
||||||
|
"updatedAt": "2026-04-27",
|
||||||
|
"metrics": [
|
||||||
|
{
|
||||||
|
"key": "attendance",
|
||||||
|
"label": "Anwesenheit",
|
||||||
|
"unit": "%",
|
||||||
|
"target": 95,
|
||||||
|
"current": 96
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "members",
|
||||||
|
"label": "Mitgliederzahl",
|
||||||
|
"unit": "",
|
||||||
|
"target": 42,
|
||||||
|
"current": 42
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "newMembers",
|
||||||
|
"label": "Neue Mitglieder",
|
||||||
|
"unit": "",
|
||||||
|
"target": 1.3,
|
||||||
|
"current": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "guests",
|
||||||
|
"label": "Gäste / Besucher",
|
||||||
|
"unit": "",
|
||||||
|
"target": 10.5,
|
||||||
|
"current": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "oneOnOne",
|
||||||
|
"label": "4-Augen-Gespräche",
|
||||||
|
"unit": "",
|
||||||
|
"target": 168,
|
||||||
|
"current": 56
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "cte",
|
||||||
|
"label": "CTE",
|
||||||
|
"unit": "",
|
||||||
|
"target": 705.6,
|
||||||
|
"current": 208
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "referrals",
|
||||||
|
"label": "Empfehlungen",
|
||||||
|
"unit": "",
|
||||||
|
"target": 100.8,
|
||||||
|
"current": 34
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "revenueThank",
|
||||||
|
"label": "Umsatzdanke",
|
||||||
|
"unit": "",
|
||||||
|
"target": 146034,
|
||||||
|
"current": 26259
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
221
chapters/sonne-7x4m9k/stammvertreter.json
Normal file
4491
index.html
@@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"version": "1.1.0",
|
|
||||||
"notes": "Multi-Chapter-Support mit Lizenzcode"
|
|
||||||
}
|
|
||||||