Admin: Mitglieder mit Fotos auf Gitea hochladen
This commit is contained in:
96
index.html
96
index.html
@@ -1668,6 +1668,7 @@
|
|||||||
// ============================================================
|
// ============================================================
|
||||||
const APP_VERSION = '1.1.0';
|
const APP_VERSION = '1.1.0';
|
||||||
const GITEA_RAW = 'http://72.60.134.73:3000/api/v1/repos/cheubaum/BNI-Sonne/raw';
|
const GITEA_RAW = 'http://72.60.134.73:3000/api/v1/repos/cheubaum/BNI-Sonne/raw';
|
||||||
|
const GITEA_API = 'http://72.60.134.73:3000/api/v1/repos/cheubaum/BNI-Sonne';
|
||||||
function giteaUrl(path) { return `${GITEA_RAW}/${path}?ref=main`; }
|
function giteaUrl(path) { return `${GITEA_RAW}/${path}?ref=main`; }
|
||||||
const CHAPTER_CONFIG_KEY = 'bni-active-chapter';
|
const CHAPTER_CONFIG_KEY = 'bni-active-chapter';
|
||||||
|
|
||||||
@@ -4159,11 +4160,19 @@ async function fetchChapterMembersFromGitea() {
|
|||||||
if (!res.ok) return;
|
if (!res.ok) return;
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
if (!data.members || !data.members.length) return;
|
if (!data.members || !data.members.length) return;
|
||||||
|
const extras = loadExtras();
|
||||||
const mergedMap = new Map(state.members.map(m => [m.id, m]));
|
const mergedMap = new Map(state.members.map(m => [m.id, m]));
|
||||||
data.members.forEach(m => {
|
data.members.forEach(m => {
|
||||||
const ex = mergedMap.get(m.id);
|
const ex = mergedMap.get(m.id);
|
||||||
mergedMap.set(m.id, ex ? Object.assign({}, ex, m) : m);
|
mergedMap.set(m.id, ex ? Object.assign({}, ex, m) : m);
|
||||||
|
// Embed photoData into extras so existing photo display picks it up
|
||||||
|
if (m.photoData) {
|
||||||
|
const key = extrasKey(m.name);
|
||||||
|
if (!extras[key]) extras[key] = {};
|
||||||
|
extras[key].offlinePhotoB64 = m.photoData;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
saveExtras(extras);
|
||||||
state.members = Array.from(mergedMap.values());
|
state.members = Array.from(mergedMap.values());
|
||||||
const knownIds = new Set(state.meetingOrder);
|
const knownIds = new Set(state.meetingOrder);
|
||||||
state.members.forEach(m => { if (!knownIds.has(m.id)) state.meetingOrder.push(m.id); });
|
state.members.forEach(m => { if (!knownIds.has(m.id)) state.meetingOrder.push(m.id); });
|
||||||
@@ -4175,6 +4184,65 @@ async function fetchChapterMembersFromGitea() {
|
|||||||
} catch(e) { /* ignore */ }
|
} catch(e) { /* ignore */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildMembersJson() {
|
||||||
|
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.offlinePhotoB64) entry.photoData = ex.offlinePhotoB64;
|
||||||
|
return entry;
|
||||||
|
});
|
||||||
|
return { lastUpdated: new Date().toISOString().slice(0, 10), members };
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveAdminToken() {
|
||||||
|
const val = (document.getElementById('admin-token-input').value || '').trim();
|
||||||
|
if (val) localStorage.setItem('bni-gitea-admin-token', val);
|
||||||
|
else localStorage.removeItem('bni-gitea-admin-token');
|
||||||
|
renderAdminPanel();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function uploadMembersToGitea() {
|
||||||
|
const token = localStorage.getItem('bni-gitea-admin-token');
|
||||||
|
if (!token) { alert('Bitte zuerst den Admin-Token speichern.'); return; }
|
||||||
|
const statusEl = document.getElementById('upload-status');
|
||||||
|
statusEl.textContent = 'Lädt hoch…';
|
||||||
|
statusEl.style.color = 'var(--muted)';
|
||||||
|
|
||||||
|
const data = buildMembersJson();
|
||||||
|
const jsonStr = JSON.stringify(data, null, 2);
|
||||||
|
const content = btoa(unescape(encodeURIComponent(jsonStr)));
|
||||||
|
const path = `chapters/${activeChapter.code}/members.json`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get current SHA
|
||||||
|
let sha = null;
|
||||||
|
const shaRes = await fetch(`${GITEA_API}/contents/${path}`, { headers: { 'Authorization': `token ${token}` } });
|
||||||
|
if (shaRes.ok) sha = (await shaRes.json()).sha;
|
||||||
|
|
||||||
|
const body = { message: `Mitglieder aktualisiert ${new Date().toLocaleDateString('de')}`, content };
|
||||||
|
if (sha) body.sha = sha;
|
||||||
|
|
||||||
|
const res = await fetch(`${GITEA_API}/contents/${path}`, {
|
||||||
|
method: sha ? 'PUT' : 'POST',
|
||||||
|
headers: { 'Authorization': `token ${token}`, 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(body)
|
||||||
|
});
|
||||||
|
if (res.ok) {
|
||||||
|
activeChapter.lastMemberUpload = new Date().toISOString();
|
||||||
|
saveChapterConfig(activeChapter);
|
||||||
|
statusEl.textContent = `✓ ${data.members.length} Mitglieder hochgeladen`;
|
||||||
|
statusEl.style.color = 'var(--success)';
|
||||||
|
} else {
|
||||||
|
statusEl.textContent = 'Fehler beim Upload. Token prüfen.';
|
||||||
|
statusEl.style.color = '#dc2626';
|
||||||
|
}
|
||||||
|
} catch(e) {
|
||||||
|
statusEl.textContent = 'Verbindungsfehler.';
|
||||||
|
statusEl.style.color = '#dc2626';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function renderAdminPanel() {
|
function renderAdminPanel() {
|
||||||
const panel = document.getElementById('admin-panel');
|
const panel = document.getElementById('admin-panel');
|
||||||
if (!panel) return;
|
if (!panel) return;
|
||||||
@@ -4182,7 +4250,8 @@ function renderAdminPanel() {
|
|||||||
const code = activeChapter && activeChapter.code;
|
const code = activeChapter && activeChapter.code;
|
||||||
const lastChecked = activeChapter && activeChapter.lastChecked ? new Date(activeChapter.lastChecked).toLocaleString('de') : '—';
|
const lastChecked = activeChapter && activeChapter.lastChecked ? new Date(activeChapter.lastChecked).toLocaleString('de') : '—';
|
||||||
const lastSync = activeChapter && activeChapter.lastMemberSync ? new Date(activeChapter.lastMemberSync).toLocaleString('de') : '—';
|
const lastSync = activeChapter && activeChapter.lastMemberSync ? new Date(activeChapter.lastMemberSync).toLocaleString('de') : '—';
|
||||||
const maskedCode = code ? code.slice(0, -3).replace(/./g, '·') + code.slice(-3) : '—';
|
const lastUpload = activeChapter && activeChapter.lastMemberUpload ? new Date(activeChapter.lastMemberUpload).toLocaleString('de') : '—';
|
||||||
|
const hasToken = !!localStorage.getItem('bni-gitea-admin-token');
|
||||||
|
|
||||||
panel.innerHTML = `<div style="padding:16px;overflow-y:auto;height:100%;">
|
panel.innerHTML = `<div style="padding:16px;overflow-y:auto;height:100%;">
|
||||||
<div style="font-weight:700;font-size:0.9rem;margin-bottom:14px;">Chapter-Administration</div>
|
<div style="font-weight:700;font-size:0.9rem;margin-bottom:14px;">Chapter-Administration</div>
|
||||||
@@ -4190,13 +4259,26 @@ function renderAdminPanel() {
|
|||||||
<div style="background:#f8f9fa;border:1px solid var(--border);border-radius:8px;padding:12px;margin-bottom:14px;">
|
<div style="background:#f8f9fa;border:1px solid var(--border);border-radius:8px;padding:12px;margin-bottom:14px;">
|
||||||
<div style="font-size:0.78rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:1px;margin-bottom:6px;">Aktives Chapter</div>
|
<div style="font-size:0.78rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:1px;margin-bottom:6px;">Aktives Chapter</div>
|
||||||
<div style="font-weight:600;margin-bottom:2px;">${cfg.chapterName || '—'}</div>
|
<div style="font-weight:600;margin-bottom:2px;">${cfg.chapterName || '—'}</div>
|
||||||
<div style="font-size:0.82rem;color:var(--muted);">Version: <strong>${APP_VERSION}</strong></div>
|
<div style="font-size:0.82rem;color:var(--muted);">Version: <strong>${APP_VERSION}</strong> · Lizenzprüfung: ${lastChecked}</div>
|
||||||
<div style="font-size:0.82rem;color:var(--muted);">Letzte Lizenzprüfung: ${lastChecked}</div>
|
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-secondary btn-sm" style="width:100%;margin-bottom:6px;" onclick="fetchChapterMembersFromGitea()">🔄 Mitglieder vom Server laden</button>
|
|
||||||
<div style="font-size:0.78rem;color:var(--muted);margin-bottom:14px;">Letzte Synchronisation: ${lastSync}</div>
|
<div style="font-size:0.78rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:1px;margin-bottom:6px;">Mitglieder laden</div>
|
||||||
<button class="btn btn-secondary btn-sm" style="width:100%;margin-bottom:6px;" onclick="checkForUpdates()">🔍 Auf Updates prüfen</button>
|
<button class="btn btn-secondary btn-sm" style="width:100%;margin-bottom:4px;" onclick="fetchChapterMembersFromGitea()">🔄 Vom Server laden</button>
|
||||||
<div style="font-size:0.78rem;color:var(--muted);margin-bottom:20px;"></div>
|
<div style="font-size:0.78rem;color:var(--muted);margin-bottom:14px;">Letzter Download: ${lastSync}</div>
|
||||||
|
|
||||||
|
<div style="font-size:0.78rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:1px;margin-bottom:6px;">Mitglieder hochladen (Admin)</div>
|
||||||
|
<div style="display:flex;gap:6px;margin-bottom:6px;">
|
||||||
|
<input id="admin-token-input" type="password" placeholder="Gitea API Token"
|
||||||
|
value="${hasToken ? '••••••••' : ''}"
|
||||||
|
style="flex:1;padding:6px 10px;border:1px solid var(--border);border-radius:6px;font-size:0.82rem;"
|
||||||
|
onfocus="if(this.value==='••••••••')this.value=''">
|
||||||
|
<button class="btn btn-secondary btn-sm" onclick="saveAdminToken()">Speichern</button>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-primary btn-sm" style="width:100%;margin-bottom:4px;" onclick="uploadMembersToGitea()">📤 Auf Gitea hochladen (inkl. Fotos)</button>
|
||||||
|
<div id="upload-status" style="font-size:0.78rem;min-height:18px;margin-bottom:4px;"></div>
|
||||||
|
<div style="font-size:0.78rem;color:var(--muted);margin-bottom:14px;">Letzter Upload: ${lastUpload}</div>
|
||||||
|
|
||||||
|
<button class="btn btn-secondary btn-sm" style="width:100%;margin-bottom:16px;" onclick="checkForUpdates()">🔍 Auf Programm-Updates prüfen</button>
|
||||||
` : `<div style="color:var(--muted);font-size:0.88rem;margin-bottom:20px;">Kein Chapter aktiviert.</div>`}
|
` : `<div style="color:var(--muted);font-size:0.88rem;margin-bottom:20px;">Kein Chapter aktiviert.</div>`}
|
||||||
<div style="border-top:1px solid var(--border);padding-top:14px;">
|
<div style="border-top:1px solid var(--border);padding-top:14px;">
|
||||||
<button class="btn btn-ghost btn-sm" style="width:100%;" onclick="confirmSwitchChapter()">↔ Chapter wechseln</button>
|
<button class="btn btn-ghost btn-sm" style="width:100%;" onclick="confirmSwitchChapter()">↔ Chapter wechseln</button>
|
||||||
|
|||||||
Reference in New Issue
Block a user