+
📈 AP2: Digitalisierungsmanagement (KDM)
Business Model Canvas, Plattformökonomie, BPMN 2.0, GoBD/DMS, XRechnung, DSFA & 40h-Projektleitfaden.
@@ -1253,13 +1262,25 @@
-
+
⚡ Subnetting-Drill Master
Interaktiver Rechentrainer: Netz-ID, Broadcast-Adresse, Hostbereich & Präfixlängen im Sekundentakt trainieren.
▶ Subnetting-Drill öffnen
+
+
+
+
🔒
+
Fachspezifischer AP2-Prüfungskurs gesperrt
+
+ 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.
+
+
+
@@ -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 = '⚠️
Kein Ausbildungsberuf im Account angegeben: 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 = '🌟
Dozenten- & Admin-Vollansicht: 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 = `✅
Im Account gespeichert: ${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 = `⏳
Vorschau für: ${PROFESSION_NAMES[currentProfession] || currentProfession} (noch nicht im Account gespeichert). Klicke auf
💾 Im Account speichern.`;
+ }
+ }
+
// 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;
- const profSel = document.getElementById('azubi-prof-select');
- if (profSel) profSel.value = 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 = 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) {
diff --git a/server.py b/server.py
index c77a7a0..7ab9465 100644
--- a/server.py
+++ b/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):