feat(dashboard): filter AP2 courses strictly by user account profession
This commit is contained in:
parent
b0a5edaebb
commit
1cf27fbd52
3 changed files with 248 additions and 61 deletions
|
|
@ -114,12 +114,12 @@
|
|||
}
|
||||
},
|
||||
|
||||
async register(name, email, password, role = 'solo', rememberMe = false) {
|
||||
async register(name, email, password, role = 'solo', rememberMe = false, profession = '') {
|
||||
try {
|
||||
const res = await fetch('/api/auth/register', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name, email, password, role })
|
||||
body: JSON.stringify({ name, email, password, role, profession })
|
||||
});
|
||||
const data = await res.json().catch(() => ({}));
|
||||
if (!res.ok) {
|
||||
|
|
@ -577,11 +577,26 @@
|
|||
<label style="display: block; font-size: 0.8rem; font-weight: 700; color: #0f172a; margin-bottom: 4px;">Deine Rolle / Lernziel:</label>
|
||||
<select id="auth-select-role" style="width: 100%; padding: 9px 12px; border: 1px solid #cbd5e1; border-radius: 8px; font-size: 0.88rem; outline: none; background: white; box-sizing: border-box;">
|
||||
<option value="solo">🎓 Solo-Selbstlerner & Quereinsteiger</option>
|
||||
<option value="student">💼 Auszubildender Fachinformatiker (FIAE / FISI)</option>
|
||||
<option value="student">💼 Auszubildender IT-Beruf</option>
|
||||
<option value="teacher">👨🏫 Lehrkraft / Dozent / Ausbilder</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div id="auth-field-profession" style="display: none;">
|
||||
<label style="display: block; font-size: 0.8rem; font-weight: 700; color: #0f172a; margin-bottom: 4px;">Dein IT-Ausbildungsberuf (optional):</label>
|
||||
<select id="auth-select-profession" style="width: 100%; padding: 9px 12px; border: 1px solid #cbd5e1; border-radius: 8px; font-size: 0.88rem; outline: none; background: white; box-sizing: border-box;">
|
||||
<option value="">Keine Angabe / Quereinsteiger</option>
|
||||
<option value="FISI">🖥️ Fachinformatiker Systemintegration (FISI)</option>
|
||||
<option value="FIAE">💻 Fachinformatiker Anwendungsentwicklung (FIAE)</option>
|
||||
<option value="FIDP">📊 Fachinformatiker Daten- & Prozessanalyse (FIDP)</option>
|
||||
<option value="FIDV">🌐 Fachinformatiker Digitale Vernetzung (FIDV)</option>
|
||||
<option value="ITSE">⚡ IT-System-Elektroniker/in (ITSE)</option>
|
||||
<option value="ITSM">💼 Kaufleute für IT-System-Management (ITSM)</option>
|
||||
<option value="KDM">📈 Kaufleute für Digitalisierungsmanagement (KDM)</option>
|
||||
</select>
|
||||
<span style="display: block; font-size: 0.74rem; color: #64748b; margin-top: 3px;">Schaltet deinen spezifischen IHK AP2-Prüfungskurs auf dem Dashboard frei.</span>
|
||||
</div>
|
||||
|
||||
<!-- Session-Einstellung (Standard: nicht angehakt für Login-Zwang nach Schließen des Browsers) -->
|
||||
<div style="display: flex; align-items: center; gap: 8px; margin-top: 2px;">
|
||||
<input type="checkbox" id="auth-remember-me" style="width: 16px; height: 16px; cursor: pointer;">
|
||||
|
|
@ -618,6 +633,7 @@
|
|||
const tabReg = document.getElementById('auth-tab-register');
|
||||
const nameField = document.getElementById('auth-field-name');
|
||||
const roleField = document.getElementById('auth-field-role');
|
||||
const profField = document.getElementById('auth-field-profession');
|
||||
const submitBtn = document.getElementById('auth-submit-btn');
|
||||
const errorBox = document.getElementById('auth-error-box');
|
||||
const form = document.getElementById('auth-form');
|
||||
|
|
@ -634,8 +650,9 @@
|
|||
tabLog.style.color = '#64748b';
|
||||
tabLog.style.boxShadow = 'none';
|
||||
|
||||
nameField.style.display = 'block';
|
||||
roleField.style.display = 'block';
|
||||
if (nameField) nameField.style.display = 'block';
|
||||
if (roleField) roleField.style.display = 'block';
|
||||
if (profField) profField.style.display = 'block';
|
||||
submitBtn.innerText = 'Jetzt kostenlos registrieren →';
|
||||
submitBtn.style.background = '#0284c7';
|
||||
} else {
|
||||
|
|
@ -648,8 +665,9 @@
|
|||
tabReg.style.boxShadow = 'none';
|
||||
}
|
||||
|
||||
nameField.style.display = 'none';
|
||||
roleField.style.display = 'none';
|
||||
if (nameField) nameField.style.display = 'none';
|
||||
if (roleField) roleField.style.display = 'none';
|
||||
if (profField) profField.style.display = 'none';
|
||||
submitBtn.innerText = 'Anmelden →';
|
||||
submitBtn.style.background = '#059669';
|
||||
}
|
||||
|
|
@ -678,6 +696,7 @@
|
|||
const password = document.getElementById('auth-input-password').value;
|
||||
const name = document.getElementById('auth-input-name')?.value.trim() || email.split('@')[0];
|
||||
const role = document.getElementById('auth-select-role')?.value || 'solo';
|
||||
const profession = document.getElementById('auth-select-profession')?.value || '';
|
||||
const rememberMe = document.getElementById('auth-remember-me')?.checked || false;
|
||||
|
||||
submitBtn.disabled = true;
|
||||
|
|
@ -685,7 +704,7 @@
|
|||
|
||||
let result;
|
||||
if (isRegister) {
|
||||
result = await window.AUTH.register(name, email, password, role, rememberMe);
|
||||
result = await window.AUTH.register(name, email, password, role, rememberMe, profession);
|
||||
} else {
|
||||
result = await window.AUTH.login(email, password, rememberMe);
|
||||
}
|
||||
|
|
|
|||
230
dashboard.html
230
dashboard.html
|
|
@ -257,6 +257,7 @@
|
|||
<div id="nav-profile-pill" style="display: flex; align-items: center; gap: 6px; background: rgba(255, 255, 255, 0.08); padding: 4px 10px; border-radius: 99px; border: 1px solid rgba(255, 255, 255, 0.12);">
|
||||
<span id="nav-auth-user" style="font-size: 0.82rem; font-weight: 700; color: #38bdf8;">👤 Lädt...</span>
|
||||
<span id="nav-auth-role" style="font-size: 0.68rem; font-weight: 800; background: rgba(56, 189, 248, 0.15); color: #7dd3fc; padding: 2px 6px; border-radius: 99px; text-transform: uppercase;"></span>
|
||||
<span id="nav-auth-profession" style="display: none; font-size: 0.68rem; font-weight: 800; background: rgba(16, 185, 129, 0.25); color: #34d399; padding: 2px 7px; border-radius: 99px; text-transform: uppercase; border: 1px solid rgba(16, 185, 129, 0.4);" title="Aktiver IT-Ausbildungsberuf"></span>
|
||||
</div>
|
||||
<button type="button" class="btn" id="nav-btn-logout" style="padding: 6px 12px; font-size: 0.82rem; background: rgba(239, 68, 68, 0.15); color: #f87171; border: 1px solid rgba(239, 68, 68, 0.3);">
|
||||
<span class="btn-text-desktop">Abmelden</span>
|
||||
|
|
@ -374,16 +375,16 @@
|
|||
|
||||
<!-- Category Filter Tabs -->
|
||||
<div class="portal-filter-tabs" style="margin-bottom: 20px;">
|
||||
<button type="button" class="portal-tab-pill active" onclick="filterCourses('all', this)">🌟 Alle Fachbereiche (16)</button>
|
||||
<button type="button" class="portal-tab-pill" onclick="filterCourses('ihk', this)">🎓 IHK Prüfung (2)</button>
|
||||
<button type="button" class="portal-tab-pill" onclick="filterCourses('devops', this)">🐧 DevOps, Linux & AD (5)</button>
|
||||
<button type="button" class="portal-tab-pill" onclick="filterCourses('coding', this)">💻 Backend & Systems (6)</button>
|
||||
<button type="button" class="portal-tab-pill" onclick="filterCourses('data', this)">🗄️ Daten & Web (3)</button>
|
||||
<button type="button" class="portal-tab-pill active" id="tab-pill-all" onclick="filterCourses('all', this)">🌟 Alle Fachbereiche</button>
|
||||
<button type="button" class="portal-tab-pill" id="tab-pill-ihk" onclick="filterCourses('ihk', this)">🎓 IHK Prüfung</button>
|
||||
<button type="button" class="portal-tab-pill" id="tab-pill-devops" onclick="filterCourses('devops', this)">🐧 DevOps, Linux & AD</button>
|
||||
<button type="button" class="portal-tab-pill" id="tab-pill-coding" onclick="filterCourses('coding', this)">💻 Backend & Systems</button>
|
||||
<button type="button" class="portal-tab-pill" id="tab-pill-data" onclick="filterCourses('data', this)">🗄️ Daten & Web</button>
|
||||
</div>
|
||||
|
||||
<!-- AUSBILDUNGSPLAN & KMK-LERNFELDER BEREICH -->
|
||||
<div class="apprenticeship-panel" id="azubi-panel">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 16px; margin-bottom: 16px;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 16px; margin-bottom: 12px;">
|
||||
<div style="display: flex; align-items: center; gap: 12px;">
|
||||
<div style="width: 42px; height: 42px; border-radius: 10px; background: linear-gradient(135deg, #0284c7, #1d4ed8); display: flex; align-items: center; justify-content: center; font-size: 1.4rem; color: white;">
|
||||
🎓
|
||||
|
|
@ -395,9 +396,11 @@
|
|||
</div>
|
||||
|
||||
<!-- Profession Dropdown & Save Button -->
|
||||
<div style="display: flex; flex-direction: column; align-items: flex-end; gap: 6px;">
|
||||
<div style="display: flex; align-items: center; gap: 10px; flex-wrap: wrap;">
|
||||
<label for="azubi-prof-select" style="font-size: 0.85rem; font-weight: 700; color: #475569;">Mein IT-Beruf:</label>
|
||||
<select id="azubi-prof-select" onchange="onProfessionChanged()" style="padding: 7px 12px; font-size: 0.86rem; font-weight: 600; border-radius: 8px; border: 1px solid #cbd5e1; background: #f8fafc; color: #0f172a; cursor: pointer;">
|
||||
<option value="">-- Kein Ausbildungsberuf gewählt --</option>
|
||||
<option value="FISI">🖥️ FISI - Systemintegration</option>
|
||||
<option value="FIAE">💻 FIAE - Anwendungsentwicklung</option>
|
||||
<option value="FIDP">📊 FIDP - Daten- & Prozessanalyse</option>
|
||||
|
|
@ -405,11 +408,17 @@
|
|||
<option value="ITSE">⚡ ITSE - IT-System-Elektronik</option>
|
||||
<option value="ITSM">💼 ITSM - IT-System-Management</option>
|
||||
<option value="KDM">📈 KDM - Digitalisierungsmanagement</option>
|
||||
<option value="ALL">🌟 Alle Ausbildungsberufe anzeigen (Dozent / Admin)</option>
|
||||
</select>
|
||||
<button type="button" class="btn" id="btn-save-apprenticeship" onclick="saveApprenticeshipChoice()" style="background: #0284c7; color: white; padding: 7px 14px; font-size: 0.82rem; font-weight: 700; border-radius: 8px; border: none; cursor: pointer;" title="Beruf und Lehrjahr in deinem Profil speichern">
|
||||
💾 Speichern
|
||||
<button type="button" class="btn" id="btn-save-apprenticeship" onclick="saveApprenticeshipChoice()" style="background: #0284c7; color: white; padding: 7px 14px; font-size: 0.82rem; font-weight: 700; border-radius: 8px; border: none; cursor: pointer;" title="Beruf in deinem Account speichern">
|
||||
💾 Im Account speichern
|
||||
</button>
|
||||
</div>
|
||||
<!-- Account Status Info Badge -->
|
||||
<div id="azubi-prof-account-badge" style="font-size: 0.78rem; display: flex; align-items: center; gap: 6px; padding: 4px 10px; border-radius: 6px; background: #f1f5f9; border: 1px solid #e2e8f0; color: #475569;">
|
||||
<span id="azubi-prof-account-msg">Lade Berufsstatus...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Year Filter Tabs & Assigned Filter -->
|
||||
|
|
@ -1189,7 +1198,7 @@
|
|||
</div>
|
||||
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 18px; margin-top: 16px;">
|
||||
<div style="background: #ffffff; border: 1px solid #e2e8f0; border-radius: 12px; padding: 20px; box-shadow: var(--shadow-sm);">
|
||||
<div class="ihk-quick-card" data-ihk-profession="common" style="background: #ffffff; border: 1px solid #e2e8f0; border-radius: 12px; padding: 20px; box-shadow: var(--shadow-sm);">
|
||||
<h4 style="margin: 0 0 6px 0; color: #1e3a8a; font-size: 1.05rem;">📘 AP1: IT-Arbeitsplatz (LF 1–8)</h4>
|
||||
<p style="font-size: 0.86rem; color: #64748b; margin: 0 0 14px 0;">Gemeinsame Kernqualifikationen für alle IT-Berufe: Verträge, Ergonomie, Subnetting, BSI, BPMN 2.0, Belege & Netzplan.</p>
|
||||
<a href="workspace.html?course=ihk_ap1&chapter=01_lf1_unternehmen_und_arbeitsplatz" class="btn-start-ide" style="font-size: 0.84rem; padding: 8px 14px;">
|
||||
|
|
@ -1197,7 +1206,7 @@
|
|||
</a>
|
||||
</div>
|
||||
|
||||
<div style="background: #ffffff; border: 1px solid #e2e8f0; border-radius: 12px; padding: 20px; box-shadow: var(--shadow-sm);">
|
||||
<div class="ihk-quick-card" data-ihk-profession="FISI" style="background: #ffffff; border: 1px solid #e2e8f0; border-radius: 12px; padding: 20px; box-shadow: var(--shadow-sm);">
|
||||
<h4 style="margin: 0 0 6px 0; color: #991b1b; font-size: 1.05rem;">📕 AP2: Systemintegration (FISI)</h4>
|
||||
<p style="font-size: 0.86rem; color: #64748b; margin: 0 0 14px 0;">VLAN & Routing, VRT-Symbole, Active Directory, RAID 0-10, Backup 3-2-1-1-0, Enterprise SQL & 40h-Projektleitfaden.</p>
|
||||
<a href="workspace.html?course=ihk_ap2_fisi&chapter=01_lf9_netzwerkinfrastruktur_und_routing" class="btn-start-ide" style="font-size: 0.84rem; padding: 8px 14px; background: #991b1b;">
|
||||
|
|
@ -1205,7 +1214,7 @@
|
|||
</a>
|
||||
</div>
|
||||
|
||||
<div style="background: #ffffff; border: 1px solid #e2e8f0; border-radius: 12px; padding: 20px; box-shadow: var(--shadow-sm);">
|
||||
<div class="ihk-quick-card" data-ihk-profession="FIAE" style="background: #ffffff; border: 1px solid #e2e8f0; border-radius: 12px; padding: 20px; box-shadow: var(--shadow-sm);">
|
||||
<h4 style="margin: 0 0 6px 0; color: #0369a1; font-size: 1.05rem;">💻 AP2: Anwendungsentwicklung (FIAE)</h4>
|
||||
<p style="font-size: 0.86rem; color: #64748b; margin: 0 0 14px 0;">Clean Architecture, GoF-Entwurfsmuster, UML 2.5, Algorithmen (O-Notation), OOP & SOLID, REST/GraphQL & 80h-Projektleitfaden.</p>
|
||||
<a href="workspace.html?course=ihk_ap2_fiae&chapter=01_software_architektur_und_design_patterns" class="btn-start-ide" style="font-size: 0.84rem; padding: 8px 14px; background: #0369a1;">
|
||||
|
|
@ -1213,7 +1222,7 @@
|
|||
</a>
|
||||
</div>
|
||||
|
||||
<div style="background: #ffffff; border: 1px solid #e2e8f0; border-radius: 12px; padding: 20px; box-shadow: var(--shadow-sm);">
|
||||
<div class="ihk-quick-card" data-ihk-profession="FIDP" style="background: #ffffff; border: 1px solid #e2e8f0; border-radius: 12px; padding: 20px; box-shadow: var(--shadow-sm);">
|
||||
<h4 style="margin: 0 0 6px 0; color: #0d9488; font-size: 1.05rem;">📊 AP2: Daten- & Prozessanalyse (FIDP)</h4>
|
||||
<p style="font-size: 0.86rem; color: #64748b; margin: 0 0 14px 0;">Prozessoptimierung, BPMN 2.0, ISO 25012 Datenqualität, ETL/CDC, Star-Schema, Statistik, ML-Evaluation & 40h-Projektleitfaden.</p>
|
||||
<a href="workspace.html?course=ihk_ap2_fidp&chapter=01_prozessanalyse_und_prozessoptimierung" class="btn-start-ide" style="font-size: 0.84rem; padding: 8px 14px; background: #0d9488;">
|
||||
|
|
@ -1221,7 +1230,7 @@
|
|||
</a>
|
||||
</div>
|
||||
|
||||
<div style="background: #ffffff; border: 1px solid #e2e8f0; border-radius: 12px; padding: 20px; box-shadow: var(--shadow-sm);">
|
||||
<div class="ihk-quick-card" data-ihk-profession="FIDV" style="background: #ffffff; border: 1px solid #e2e8f0; border-radius: 12px; padding: 20px; box-shadow: var(--shadow-sm);">
|
||||
<h4 style="margin: 0 0 6px 0; color: #b45309; font-size: 1.05rem;">🏭 AP2: Digitale Vernetzung (FIDV)</h4>
|
||||
<p style="font-size: 0.86rem; color: #64748b; margin: 0 0 14px 0;">Cyber-Physische Systeme, RAMI 4.0, IO-Link, Profinet IO, TSN, OPC UA/MQTT, 5G-Campus, IEC 62443, Purdue & 40h-Projekt.</p>
|
||||
<a href="workspace.html?course=ihk_ap2_fidv&chapter=01_industrie40_und_cyber_physische_systeme" class="btn-start-ide" style="font-size: 0.84rem; padding: 8px 14px; background: #b45309;">
|
||||
|
|
@ -1229,7 +1238,7 @@
|
|||
</a>
|
||||
</div>
|
||||
|
||||
<div style="background: #ffffff; border: 1px solid #e2e8f0; border-radius: 12px; padding: 20px; box-shadow: var(--shadow-sm);">
|
||||
<div class="ihk-quick-card" data-ihk-profession="ITSE" style="background: #ffffff; border: 1px solid #e2e8f0; border-radius: 12px; padding: 20px; box-shadow: var(--shadow-sm);">
|
||||
<h4 style="margin: 0 0 6px 0; color: #d97706; font-size: 1.05rem;">⚡ AP2: IT-System-Elektroniker (ITSE)</h4>
|
||||
<p style="font-size: 0.86rem; color: #64748b; margin: 0 0 14px 0;">VDE 0100/0701, DGUV V3, USV (VFI), LWL-Spleißen & OTDR, EMV, Brandschottung & 40h-Projektleitfaden.</p>
|
||||
<a href="workspace.html?course=ihk_ap2_itse&chapter=01_elektrotechnik_und_schutzmassnahmen_vde0100" class="btn-start-ide" style="font-size: 0.84rem; padding: 8px 14px; background: #d97706;">
|
||||
|
|
@ -1237,7 +1246,7 @@
|
|||
</a>
|
||||
</div>
|
||||
|
||||
<div style="background: #ffffff; border: 1px solid #e2e8f0; border-radius: 12px; padding: 20px; box-shadow: var(--shadow-sm);">
|
||||
<div class="ihk-quick-card" data-ihk-profession="ITSM" style="background: #ffffff; border: 1px solid #e2e8f0; border-radius: 12px; padding: 20px; box-shadow: var(--shadow-sm);">
|
||||
<h4 style="margin: 0 0 6px 0; color: #0284c7; font-size: 1.05rem;">💼 AP2: IT-System-Management (ITSM)</h4>
|
||||
<p style="font-size: 0.86rem; color: #64748b; margin: 0 0 14px 0;">7P-Marketing, Handelskalkulation, TCO/ROI, EVB-IT Verträge, SLAs, ITIL & 40h-Projektleitfaden.</p>
|
||||
<a href="workspace.html?course=ihk_ap2_itsm&chapter=01_it_marktanalyse_wettbewerb_und_swot" class="btn-start-ide" style="font-size: 0.84rem; padding: 8px 14px; background: #0284c7;">
|
||||
|
|
@ -1245,7 +1254,7 @@
|
|||
</a>
|
||||
</div>
|
||||
|
||||
<div style="background: #ffffff; border: 1px solid #e2e8f0; border-radius: 12px; padding: 20px; box-shadow: var(--shadow-sm);">
|
||||
<div class="ihk-quick-card" data-ihk-profession="KDM" style="background: #ffffff; border: 1px solid #e2e8f0; border-radius: 12px; padding: 20px; box-shadow: var(--shadow-sm);">
|
||||
<h4 style="margin: 0 0 6px 0; color: #059669; font-size: 1.05rem;">📈 AP2: Digitalisierungsmanagement (KDM)</h4>
|
||||
<p style="font-size: 0.86rem; color: #64748b; margin: 0 0 14px 0;">Business Model Canvas, Plattformökonomie, BPMN 2.0, GoBD/DMS, XRechnung, DSFA & 40h-Projektleitfaden.</p>
|
||||
<a href="workspace.html?course=ihk_ap2_kdm&chapter=01_digitale_geschaeftsmodelle_und_bmc" class="btn-start-ide" style="font-size: 0.84rem; padding: 8px 14px; background: #059669;">
|
||||
|
|
@ -1253,13 +1262,25 @@
|
|||
</a>
|
||||
</div>
|
||||
|
||||
<div style="background: #ffffff; border: 1px solid #e2e8f0; border-radius: 12px; padding: 20px; box-shadow: var(--shadow-sm);">
|
||||
<div class="ihk-quick-card" data-ihk-profession="common" style="background: #ffffff; border: 1px solid #e2e8f0; border-radius: 12px; padding: 20px; box-shadow: var(--shadow-sm);">
|
||||
<h4 style="margin: 0 0 6px 0; color: #0f172a; font-size: 1.05rem;">⚡ Subnetting-Drill Master</h4>
|
||||
<p style="font-size: 0.86rem; color: #64748b; margin: 0 0 14px 0;">Interaktiver Rechentrainer: Netz-ID, Broadcast-Adresse, Hostbereich & Präfixlängen im Sekundentakt trainieren.</p>
|
||||
<a href="courses/ihk_ap1/11_subnetting_drill_master/index.html" class="btn-start-ide" style="font-size: 0.84rem; padding: 8px 14px; background: #0f172a;">
|
||||
<span>▶ Subnetting-Drill öffnen</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Hinweisfeld wenn kein Ausbildungsberuf im Account gewählt ist -->
|
||||
<div id="ihk-no-prof-hint" style="display: none; grid-column: 1 / -1; background: #f8fafc; border: 2px dashed #94a3b8; border-radius: 12px; padding: 24px; text-align: center;">
|
||||
<div style="font-size: 2rem; margin-bottom: 8px;">🔒</div>
|
||||
<h4 style="margin: 0 0 6px 0; color: #1e293b; font-size: 1.05rem; font-weight: 800;">Fachspezifischer AP2-Prüfungskurs gesperrt</h4>
|
||||
<p style="font-size: 0.88rem; color: #64748b; margin: 0 0 14px 0; max-width: 580px; margin-inline: auto;">
|
||||
Auf deinem Dashboard werden nur Kurse für deinen im Account hinterlegten Ausbildungsberuf angezeigt. Wähle oben im Ausbildungsplan deinen IT-Beruf aus und klicke auf Speichern, um deinen passenden AP2-Trainer freizuschalten.
|
||||
</p>
|
||||
<button type="button" onclick="document.getElementById('azubi-panel').scrollIntoView({behavior:'smooth'})" class="btn" style="background: #0284c7; color: white; padding: 8px 18px; font-weight: 700; border: none; border-radius: 8px; cursor: pointer;">
|
||||
🎓 IT-Ausbildungsberuf wählen →
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
|
@ -1481,18 +1502,83 @@
|
|||
} catch (e) {}
|
||||
}
|
||||
|
||||
// Profession-Mapping: Welche AP2-Kurse gehören zu welchem Ausbildungsberuf?
|
||||
const AP2_PROFESSION_MAP = {
|
||||
'ihk_ap2_fisi': 'FISI',
|
||||
'ihk_ap2_fiae': 'FIAE',
|
||||
'ihk_ap2_fidp': 'FIDP',
|
||||
'ihk_ap2_fidv': 'FIDV',
|
||||
'ihk_ap2_itse': 'ITSE',
|
||||
'ihk_ap2_itsm': 'ITSM',
|
||||
'ihk_ap2_kdm': 'KDM'
|
||||
};
|
||||
|
||||
const PROFESSION_NAMES = {
|
||||
'FISI': 'Fachinformatiker Systemintegration (FISI)',
|
||||
'FIAE': 'Fachinformatiker Anwendungsentwicklung (FIAE)',
|
||||
'FIDP': 'Fachinformatiker Daten- & Prozessanalyse (FIDP)',
|
||||
'FIDV': 'Fachinformatiker Digitale Vernetzung (FIDV)',
|
||||
'ITSE': 'IT-System-Elektroniker/in (ITSE)',
|
||||
'ITSM': 'Kaufleute für IT-System-Management (ITSM)',
|
||||
'KDM': 'Kaufleute für Digitalisierungsmanagement (KDM)',
|
||||
'ALL': 'Alle 7 IT-Berufe (Dozent / Admin)'
|
||||
};
|
||||
|
||||
// Filter State
|
||||
let currentCategory = 'all';
|
||||
let currentYear = 'all'; // 'all', 1, 2, 3, or 'assigned'
|
||||
let currentProfession = 'FISI';
|
||||
let accountProfession = ''; // Im DB-Account hinterlegter Beruf
|
||||
let currentProfession = ''; // Aktuelle Dropdown-Auswahl im Dashboard
|
||||
let userAssignedCourses = [];
|
||||
|
||||
// Helper: Berufs-Status-Badge im Azubi-Panel aktualisieren
|
||||
function updateProfessionStatusBadge() {
|
||||
const msgEl = document.getElementById('azubi-prof-account-msg');
|
||||
const badgeEl = document.getElementById('azubi-prof-account-badge');
|
||||
const navProf = document.getElementById('nav-auth-profession');
|
||||
if (!msgEl || !badgeEl) return;
|
||||
|
||||
if (navProf) {
|
||||
if (currentProfession && currentProfession !== 'ALL') {
|
||||
navProf.innerText = currentProfession;
|
||||
navProf.style.display = 'inline-block';
|
||||
} else if (currentProfession === 'ALL') {
|
||||
navProf.innerText = 'ALLE BERUFE';
|
||||
navProf.style.display = 'inline-block';
|
||||
} else {
|
||||
navProf.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
if (!currentProfession) {
|
||||
badgeEl.style.background = '#fef2f2';
|
||||
badgeEl.style.borderColor = '#fecaca';
|
||||
badgeEl.style.color = '#991b1b';
|
||||
msgEl.innerHTML = '⚠️ <strong>Kein Ausbildungsberuf im Account angegeben:</strong> Spezifische AP2-Prüfungskurse sind ausgeblendet. Wähle oben deinen Beruf und klicke auf Speichern.';
|
||||
} else if (currentProfession === 'ALL') {
|
||||
badgeEl.style.background = '#eff6ff';
|
||||
badgeEl.style.borderColor = '#bfdbfe';
|
||||
badgeEl.style.color = '#1e40af';
|
||||
msgEl.innerHTML = '🌟 <strong>Dozenten- & Admin-Vollansicht:</strong> Alle 7 IT-Ausbildungsberufe sind auf dem Dashboard sichtbar.';
|
||||
} else if (currentProfession === accountProfession) {
|
||||
badgeEl.style.background = '#f0fdf4';
|
||||
badgeEl.style.borderColor = '#bbf7d0';
|
||||
badgeEl.style.color = '#166534';
|
||||
msgEl.innerHTML = `✅ <strong>Im Account gespeichert:</strong> ${PROFESSION_NAMES[currentProfession] || currentProfession}. Es wird exklusiv dein passender AP2-Prüfungskurs angezeigt.`;
|
||||
} else {
|
||||
badgeEl.style.background = '#fffbeb';
|
||||
badgeEl.style.borderColor = '#fde68a';
|
||||
badgeEl.style.color = '#92400e';
|
||||
msgEl.innerHTML = `⏳ <strong>Vorschau für:</strong> ${PROFESSION_NAMES[currentProfession] || currentProfession} (noch nicht im Account gespeichert). Klicke auf <strong>💾 Im Account speichern</strong>.`;
|
||||
}
|
||||
}
|
||||
|
||||
// Unified Card Filtering
|
||||
function applyAllFilters() {
|
||||
const cards = document.querySelectorAll('.portal-card');
|
||||
const query = (document.getElementById('course-filter-input').value || '').toLowerCase().trim();
|
||||
|
||||
const manifestProf = (window.IT_AUSBILDUNGEN && window.IT_AUSBILDUNGEN.getProfession)
|
||||
const manifestProf = (window.IT_AUSBILDUNGEN && window.IT_AUSBILDUNGEN.getProfession && currentProfession && currentProfession !== 'ALL')
|
||||
? window.IT_AUSBILDUNGEN.getProfession(currentProfession)
|
||||
: null;
|
||||
|
||||
|
|
@ -1502,19 +1588,34 @@
|
|||
}
|
||||
|
||||
let visibleCount = 0;
|
||||
let counts = { all: 0, ihk: 0, devops: 0, coding: 0, data: 0 };
|
||||
|
||||
cards.forEach(card => {
|
||||
const cardCat = card.getAttribute('data-category');
|
||||
const cardId = card.getAttribute('data-course-id');
|
||||
const cardTitle = (card.getAttribute('data-title') || '').toLowerCase();
|
||||
|
||||
// 1. Category check
|
||||
// 1. Ausbildungsberuf-Prüfung:
|
||||
// AP2 Kurse für Ausbildungsberufe sieht man NUR, wenn dieser Beruf im Account / Filter gewählt ist!
|
||||
const requiredProf = AP2_PROFESSION_MAP[cardId];
|
||||
let matchesProfession = true;
|
||||
if (requiredProf) {
|
||||
if (currentProfession === 'ALL') {
|
||||
matchesProfession = true;
|
||||
} else if (currentProfession && currentProfession === requiredProf) {
|
||||
matchesProfession = true;
|
||||
} else {
|
||||
matchesProfession = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Category check
|
||||
const matchesCategory = (currentCategory === 'all' || cardCat === currentCategory);
|
||||
|
||||
// 2. Search query check
|
||||
// 3. Search query check
|
||||
const matchesQuery = (!query || cardTitle.includes(query) || card.innerText.toLowerCase().includes(query));
|
||||
|
||||
// 3. Year / Assigned check
|
||||
// 4. Year / Assigned check
|
||||
let matchesYearOrAssigned = true;
|
||||
if (currentYear === 'assigned') {
|
||||
matchesYearOrAssigned = userAssignedCourses.includes(cardId);
|
||||
|
|
@ -1522,13 +1623,54 @@
|
|||
matchesYearOrAssigned = yearAllowedCourses.includes(cardId);
|
||||
}
|
||||
|
||||
if (matchesCategory && matchesQuery && matchesYearOrAssigned) {
|
||||
// Zähler für Fachbereichs-Pills nur bei erlaubtem Beruf mitzählen
|
||||
if (matchesProfession) {
|
||||
counts.all++;
|
||||
if (cardCat && counts[cardCat] !== undefined) {
|
||||
counts[cardCat]++;
|
||||
}
|
||||
}
|
||||
|
||||
if (matchesProfession && matchesCategory && matchesQuery && matchesYearOrAssigned) {
|
||||
card.style.display = 'flex';
|
||||
visibleCount++;
|
||||
} else {
|
||||
card.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
// Filter auch auf den IHK AP1 & AP2 Schnellwahl-Bereich anwenden
|
||||
const ihkCards = document.querySelectorAll('.ihk-quick-card');
|
||||
ihkCards.forEach(card => {
|
||||
const prof = card.getAttribute('data-ihk-profession');
|
||||
if (!prof || prof === 'common') {
|
||||
card.style.display = 'block';
|
||||
} else if (currentProfession === 'ALL') {
|
||||
card.style.display = 'block';
|
||||
} else if (currentProfession && currentProfession === prof) {
|
||||
card.style.display = 'block';
|
||||
} else {
|
||||
card.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
const noProfHint = document.getElementById('ihk-no-prof-hint');
|
||||
if (noProfHint) {
|
||||
noProfHint.style.display = (!currentProfession || currentProfession === '') ? 'block' : 'none';
|
||||
}
|
||||
|
||||
// Tab Pills Zähler dynamisch aktualisieren
|
||||
const pillAll = document.getElementById('tab-pill-all');
|
||||
const pillIhk = document.getElementById('tab-pill-ihk');
|
||||
const pillDevops = document.getElementById('tab-pill-devops');
|
||||
const pillCoding = document.getElementById('tab-pill-coding');
|
||||
const pillData = document.getElementById('tab-pill-data');
|
||||
|
||||
if (pillAll) pillAll.innerText = `🌟 Alle Fachbereiche (${counts.all})`;
|
||||
if (pillIhk) pillIhk.innerText = `🎓 IHK Prüfung (${counts.ihk})`;
|
||||
if (pillDevops) pillDevops.innerText = `🐧 DevOps, Linux & AD (${counts.devops})`;
|
||||
if (pillCoding) pillCoding.innerText = `💻 Backend & Systems (${counts.coding})`;
|
||||
if (pillData) pillData.innerText = `🗄️ Daten & Web (${counts.data})`;
|
||||
}
|
||||
|
||||
// 3. Live Category Filter
|
||||
|
|
@ -1562,6 +1704,7 @@
|
|||
const select = document.getElementById('azubi-prof-select');
|
||||
if (select) {
|
||||
currentProfession = select.value;
|
||||
updateProfessionStatusBadge();
|
||||
renderLernfelder(currentProfession, currentYear);
|
||||
applyAllFilters();
|
||||
}
|
||||
|
|
@ -1570,7 +1713,7 @@
|
|||
// 3e. Save Apprenticeship Choice to Backend
|
||||
async function saveApprenticeshipChoice() {
|
||||
const select = document.getElementById('azubi-prof-select');
|
||||
const prof = select ? select.value : 'FISI';
|
||||
const prof = select ? select.value : '';
|
||||
const yr = (typeof currentYear === 'number') ? currentYear : 1;
|
||||
const btn = document.getElementById('btn-save-apprenticeship');
|
||||
if (btn) btn.innerText = 'Speichere...';
|
||||
|
|
@ -1587,11 +1730,14 @@
|
|||
body: JSON.stringify({ profession: prof, training_year: yr })
|
||||
});
|
||||
if (res.ok) {
|
||||
accountProfession = prof;
|
||||
currentProfession = prof;
|
||||
updateProfessionStatusBadge();
|
||||
if (btn) {
|
||||
btn.innerText = '✓ Gespeichert';
|
||||
btn.innerText = '✓ Im Account gespeichert!';
|
||||
btn.style.background = '#10b981';
|
||||
setTimeout(() => {
|
||||
btn.innerText = '💾 Speichern';
|
||||
btn.innerText = '💾 Im Account speichern';
|
||||
btn.style.background = '#0284c7';
|
||||
}, 2000);
|
||||
}
|
||||
|
|
@ -1601,15 +1747,20 @@
|
|||
user.training_year = yr;
|
||||
localStorage.setItem('auth_user', JSON.stringify(user));
|
||||
}
|
||||
applyAllFilters();
|
||||
} else {
|
||||
if (btn) btn.innerText = 'Fehler';
|
||||
if (btn) btn.innerText = 'Fehler beim Speichern';
|
||||
}
|
||||
} catch (e) {
|
||||
if (btn) btn.innerText = 'Fehler';
|
||||
if (btn) btn.innerText = 'Fehler beim Speichern';
|
||||
}
|
||||
} else {
|
||||
accountProfession = prof;
|
||||
currentProfession = prof;
|
||||
updateProfessionStatusBadge();
|
||||
if (btn) btn.innerText = '✓ Lokal gemerkt';
|
||||
setTimeout(() => { if (btn) btn.innerText = '💾 Speichern'; }, 2000);
|
||||
setTimeout(() => { if (btn) btn.innerText = '💾 Im Account speichern'; }, 2000);
|
||||
applyAllFilters();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1743,11 +1894,11 @@
|
|||
roleEl.innerText = user.role === 'admin' ? 'Admin' : (user.role === 'teacher' ? 'Dozent' : 'Schüler');
|
||||
}
|
||||
|
||||
if (user.profession) {
|
||||
currentProfession = user.profession;
|
||||
accountProfession = user.profession || '';
|
||||
currentProfession = accountProfession || ((user.role === 'admin' || user.role === 'teacher') ? 'ALL' : '');
|
||||
const profSel = document.getElementById('azubi-prof-select');
|
||||
if (profSel) profSel.value = user.profession;
|
||||
}
|
||||
if (profSel) profSel.value = currentProfession;
|
||||
updateProfessionStatusBadge();
|
||||
|
||||
if (user.training_year) {
|
||||
const yrPill = document.getElementById(`pill-year-${user.training_year}`);
|
||||
|
|
@ -1776,6 +1927,12 @@
|
|||
const mTl = document.getElementById('mobile-drawer-teacher-link');
|
||||
if (mTl) mTl.style.display = 'flex';
|
||||
}
|
||||
} else {
|
||||
accountProfession = '';
|
||||
currentProfession = '';
|
||||
const profSel = document.getElementById('azubi-prof-select');
|
||||
if (profSel) profSel.value = '';
|
||||
updateProfessionStatusBadge();
|
||||
}
|
||||
|
||||
// Gelöste Kapitel sammeln (localStorage + API)
|
||||
|
|
@ -1827,10 +1984,12 @@
|
|||
if (resMe.ok) {
|
||||
const meData = await resMe.json();
|
||||
if (meData.user) {
|
||||
if (meData.user.profession) {
|
||||
currentProfession = meData.user.profession;
|
||||
if (meData.user.profession !== undefined) {
|
||||
accountProfession = meData.user.profession || '';
|
||||
currentProfession = accountProfession || ((user && (user.role === 'admin' || user.role === 'teacher')) ? 'ALL' : '');
|
||||
const profSel = document.getElementById('azubi-prof-select');
|
||||
if (profSel) profSel.value = currentProfession;
|
||||
updateProfessionStatusBadge();
|
||||
}
|
||||
if (meData.user.training_year && currentYear === 'all') {
|
||||
const yrPill = document.getElementById(`pill-year-${meData.user.training_year}`);
|
||||
|
|
@ -1847,6 +2006,7 @@
|
|||
if (countTxt) countTxt.innerText = `Dir wurden ${userAssignedCourses.length} Kurse von deinem Ausbilder zugeteilt.`;
|
||||
}
|
||||
}
|
||||
applyAllFilters();
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
|
|
|
|||
18
server.py
18
server.py
|
|
@ -568,7 +568,7 @@ class PlatformRequestHandler(http.server.SimpleHTTPRequestHandler):
|
|||
return self.send_json({
|
||||
"classrooms": parsed_classes,
|
||||
"assigned_courses": list(all_assigned),
|
||||
"profession": (u_row and u_row["profession"]) or (parsed_classes and parsed_classes[0].get("profession")) or "FISI",
|
||||
"profession": (u_row["profession"] if (u_row and u_row["profession"] is not None) else "") or (parsed_classes and parsed_classes[0].get("profession")) or "",
|
||||
"training_year": (u_row and u_row["training_year"]) or (parsed_classes and parsed_classes[0].get("training_year")) or 1
|
||||
})
|
||||
|
||||
|
|
@ -761,19 +761,23 @@ class PlatformRequestHandler(http.server.SimpleHTTPRequestHandler):
|
|||
if role not in ("student", "teacher", "solo"):
|
||||
role = "solo"
|
||||
|
||||
profession = str(body.get("profession", "")).strip()[:10]
|
||||
if profession not in ("FISI", "FIAE", "FIDP", "FIDV", "ITSE", "ITSM", "KDM", "ALL"):
|
||||
profession = ""
|
||||
|
||||
pwd_hash, salt = hash_password(password)
|
||||
conn = get_db()
|
||||
try:
|
||||
c = conn.cursor()
|
||||
c.execute("INSERT INTO users (email, password_hash, salt, name, role) VALUES (?, ?, ?, ?, ?)",
|
||||
(email, pwd_hash, salt, name, role))
|
||||
c.execute("INSERT INTO users (email, password_hash, salt, name, role, profession) VALUES (?, ?, ?, ?, ?, ?)",
|
||||
(email, pwd_hash, salt, name, role, profession))
|
||||
user_id = c.lastrowid
|
||||
conn.commit()
|
||||
token = generate_token(user_id, email, role)
|
||||
return self.send_json({
|
||||
"message": "Erfolgreich registriert!",
|
||||
"token": token,
|
||||
"user": {"id": user_id, "email": email, "name": name, "role": role, "xp": 0, "level": 1}
|
||||
"user": {"id": user_id, "email": email, "name": name, "role": role, "profession": profession, "xp": 0, "level": 1}
|
||||
}, 201)
|
||||
except sqlite3.IntegrityError:
|
||||
return self.send_json({"error": "Ein Benutzer mit dieser E-Mail existiert bereits"}, 409)
|
||||
|
|
@ -815,6 +819,8 @@ class PlatformRequestHandler(http.server.SimpleHTTPRequestHandler):
|
|||
"email": user["email"],
|
||||
"name": user["name"],
|
||||
"role": user["role"],
|
||||
"profession": user["profession"] if "profession" in user.keys() and user["profession"] else "",
|
||||
"training_year": user["training_year"] if "training_year" in user.keys() and user["training_year"] else 1,
|
||||
"xp": user["xp"],
|
||||
"level": user["level"],
|
||||
"streak_days": user["streak_days"]
|
||||
|
|
@ -982,7 +988,9 @@ class PlatformRequestHandler(http.server.SimpleHTTPRequestHandler):
|
|||
if not user_token:
|
||||
return self.send_json({"error": "Nicht authentifiziert"}, 401)
|
||||
|
||||
profession = str(body.get("profession", "FISI")).strip()[:10]
|
||||
profession = str(body.get("profession", "")).strip()[:10]
|
||||
if profession not in ("FISI", "FIAE", "FIDP", "FIDV", "ITSE", "ITSM", "KDM", "ALL", ""):
|
||||
profession = ""
|
||||
try:
|
||||
training_year = int(body.get("training_year", 1))
|
||||
if training_year not in (1, 2, 3):
|
||||
|
|
|
|||
Loading…
Reference in a new issue