Update 13 files
- /TeacherDashboard/index.html - /TeacherDashboard/TeacherDashboard.code-workspace - /TeacherDashboard/components/resourceModal.html - /TeacherDashboard/components/subjectModal.html - /TeacherDashboard/components/topicModal.html - /TeacherDashboard/js/main.js - /TeacherDashboard/js/subjects/colors.js - /TeacherDashboard/js/subjects/subjectManager.js - /TeacherDashboard/js/subjects/SubjectModel.js - /TeacherDashboard/js/subjects/SubjectStorage.js - /TeacherDashboard/js/topics/TopicModel.js - /TeacherDashboard/js/topics/topicManager.js - /TeacherDashboard/styles/main.css
This commit is contained in:
committed by
Matthias Grief
parent
3977f48367
commit
2369f7158d
8
TeacherDashboard/TeacherDashboard.code-workspace
Normal file
8
TeacherDashboard/TeacherDashboard.code-workspace
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "."
|
||||
}
|
||||
],
|
||||
"settings": {}
|
||||
}
|
||||
33
TeacherDashboard/components/resourceModal.html
Normal file
33
TeacherDashboard/components/resourceModal.html
Normal file
@@ -0,0 +1,33 @@
|
||||
<!-- Resource Modal -->
|
||||
<div id="resourceModal" class="modal hidden fixed inset-0 bg-black/30 flex items-center justify-center">
|
||||
<div class="bg-white rounded-2xl p-8 w-full max-w-2xl shadow-2xl border-2 border-gray-100">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h3 class="text-xl font-bold">Materialien hochladen</h3>
|
||||
<button onclick="closeModal('resourceModal')" class="text-gray-500 hover:text-gray-700">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
<form id="resourceForm" class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Thema</label>
|
||||
<select name="topic" class="mt-1 block w-full rounded-lg border-gray-300 shadow-sm">
|
||||
<!-- Will be populated dynamically -->
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Dateien</label>
|
||||
<input type="file" multiple name="files" class="mt-1 block w-full" accept=".pdf,.jpg,.png,.jpeg">
|
||||
</div>
|
||||
<div class="flex justify-end gap-4">
|
||||
<button type="button" onclick="closeModal('resourceModal')"
|
||||
class="px-4 py-2 text-gray-700 bg-gray-100 rounded-lg hover:bg-gray-200">
|
||||
Abbrechen
|
||||
</button>
|
||||
<button type="submit"
|
||||
class="px-6 py-3 text-white bg-[var(--primary-color)] rounded-xl hover:bg-[var(--secondary-color)] shadow-lg hover:shadow-xl transition">
|
||||
Hochladen
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
67
TeacherDashboard/components/subjectModal.html
Normal file
67
TeacherDashboard/components/subjectModal.html
Normal file
@@ -0,0 +1,67 @@
|
||||
<!-- Subject Modal -->
|
||||
<div id="subjectModal" class="modal hidden fixed inset-0 bg-black/30 flex items-center justify-center">
|
||||
<div class="bg-white rounded-2xl p-8 w-full max-w-2xl shadow-2xl border-2 border-gray-100">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h3 class="text-xl font-bold">Neues Fach erstellen</h3>
|
||||
<button onclick="closeModal('subjectModal')" class="text-gray-500 hover:text-gray-700">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
<form id="subjectForm" class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Fachname</label>
|
||||
<input type="text" name="displayName" class="mt-1 block w-full rounded-lg border-gray-300 shadow-sm">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Beschreibung</label>
|
||||
<textarea name="description" rows="3"
|
||||
class="mt-1 block w-full rounded-lg border-gray-300 shadow-sm"
|
||||
placeholder="Kurze Beschreibung des Fachs..."></textarea>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Farbe</label>
|
||||
<div id="colorPalette" class="mt-2 grid grid-cols-5 gap-2"></div>
|
||||
<input type="hidden" id="selectedColor" name="color">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Icon</label>
|
||||
<div class="mt-2 h-[120px] overflow-y-auto grid grid-cols-5 gap-2 p-1">
|
||||
<div class="icon-option p-2 border rounded-lg cursor-pointer" data-icon="fa-book"><i class="fas fa-book"></i></div>
|
||||
<div class="icon-option p-2 border rounded-lg cursor-pointer" data-icon="fa-calculator"><i class="fas fa-calculator"></i></div>
|
||||
<div class="icon-option p-2 border rounded-lg cursor-pointer" data-icon="fa-flask"><i class="fas fa-flask"></i></div>
|
||||
<div class="icon-option p-2 border rounded-lg cursor-pointer" data-icon="fa-atom"><i class="fas fa-atom"></i></div>
|
||||
<div class="icon-option p-2 border rounded-lg cursor-pointer" data-icon="fa-globe-europe"><i class="fas fa-globe-europe"></i></div>
|
||||
<div class="icon-option p-2 border rounded-lg cursor-pointer" data-icon="fa-palette"><i class="fas fa-palette"></i></div>
|
||||
<div class="icon-option p-2 border rounded-lg cursor-pointer" data-icon="fa-music"><i class="fas fa-music"></i></div>
|
||||
<div class="icon-option p-2 border rounded-lg cursor-pointer" data-icon="fa-dumbbell"><i class="fas fa-dumbbell"></i></div>
|
||||
<div class="icon-option p-2 border rounded-lg cursor-pointer" data-icon="fa-language"><i class="fas fa-language"></i></div>
|
||||
<div class="icon-option p-2 border rounded-lg cursor-pointer" data-icon="fa-microscope"><i class="fas fa-microscope"></i></div>
|
||||
<div class="icon-option p-2 border rounded-lg cursor-pointer" data-icon="fa-keyboard"><i class="fas fa-keyboard"></i></div>
|
||||
<div class="icon-option p-2 border rounded-lg cursor-pointer" data-icon="fa-dna"><i class="fas fa-dna"></i></div>
|
||||
<div class="icon-option p-2 border rounded-lg cursor-pointer" data-icon="fa-brain"><i class="fas fa-brain"></i></div>
|
||||
<div class="icon-option p-2 border rounded-lg cursor-pointer" data-icon="fa-history"><i class="fas fa-history"></i></div>
|
||||
<div class="icon-option p-2 border rounded-lg cursor-pointer" data-icon="fa-chart-line"><i class="fas fa-chart-line"></i></div>
|
||||
<div class="icon-option p-2 border rounded-lg cursor-pointer" data-icon="fa-draw-polygon"><i class="fas fa-draw-polygon"></i></div>
|
||||
<div class="icon-option p-2 border rounded-lg cursor-pointer" data-icon="fa-running"><i class="fas fa-running"></i></div>
|
||||
<div class="icon-option p-2 border rounded-lg cursor-pointer" data-icon="fa-theater-masks"><i class="fas fa-theater-masks"></i></div>
|
||||
</div>
|
||||
<input type="hidden" id="selectedIcon" name="icon">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end gap-4">
|
||||
<button type="button" onclick="closeModal('subjectModal')"
|
||||
class="px-4 py-2 text-gray-700 bg-gray-100 rounded-lg hover:bg-gray-200">
|
||||
Abbrechen
|
||||
</button>
|
||||
<button type="submit"
|
||||
class="px-6 py-3 text-white bg-[var(--primary-color)] rounded-xl hover:bg-[var(--secondary-color)] shadow-lg hover:shadow-xl transition">
|
||||
Speichern
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
74
TeacherDashboard/components/topicModal.html
Normal file
74
TeacherDashboard/components/topicModal.html
Normal file
@@ -0,0 +1,74 @@
|
||||
<!-- Topic Modal -->
|
||||
<div id="topicModal" class="modal hidden fixed inset-0 bg-black/30 flex items-center justify-center">
|
||||
<div class="bg-white rounded-2xl p-8 w-full max-w-[90vw] h-[95vh] shadow-2xl border-2 border-gray-100 overflow-y-auto">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h3 class="text-xl font-bold">Neues Thema erstellen</h3>
|
||||
<button onclick="closeModal('topicModal')" class="text-gray-500 hover:text-gray-700">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
<form id="topicForm" class="space-y-8">
|
||||
<div class="grid grid-cols-2 gap-8">
|
||||
<!-- Left column - Topic details -->
|
||||
<div class="space-y-6">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Themenname</label>
|
||||
<input type="text" name="displayName" class="mt-1 block w-full rounded-lg border-gray-300 shadow-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Fach</label>
|
||||
<select id="topicSubjectSelect" name="subject" class="mt-1 block w-full rounded-lg border-gray-300 shadow-sm" required>
|
||||
<option value="">Fach auswählen...</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Kurzbeschreibung</label>
|
||||
<textarea name="description" rows="2" class="mt-1 block w-full rounded-lg border-gray-300 shadow-sm"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right column - Content and preview -->
|
||||
<div>
|
||||
<div class="mb-6">
|
||||
<div class="flex justify-between items-center mb-2">
|
||||
<label class="block text-sm font-medium text-gray-700">Bildungsinhalt</label>
|
||||
<div class="flex gap-2">
|
||||
<button type="button" onclick="topicManager.undo()"
|
||||
class="text-gray-500 hover:text-gray-700 p-1" title="Rückgängig (Ctrl+Z)">
|
||||
<i class="fas fa-undo"></i>
|
||||
</button>
|
||||
<button type="button" onclick="topicManager.redo()"
|
||||
class="text-gray-500 hover:text-gray-700 p-1" title="Wiederholen (Ctrl+Shift+Z)">
|
||||
<i class="fas fa-redo"></i>
|
||||
</button>
|
||||
<button type="button" onclick="topicManager.deleteSelected()"
|
||||
class="text-red-500 hover:text-red-700 p-1" title="Ausgewählten Inhalt löschen">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="quillEditor" class="h-[400px] mt-1"></div>
|
||||
<input type="hidden" name="content" id="quillContent">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Preview section -->
|
||||
<div class="border rounded-lg p-8 mt-8 bg-gray-50">
|
||||
<h4 class="text-lg font-medium text-gray-700 mb-6">Vorschau</h4>
|
||||
<div id="contentPreview" class="prose prose-lg max-w-none min-h-[300px] bg-white p-6 rounded-lg shadow-sm quill-content"></div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end gap-4">
|
||||
<button type="button" onclick="closeModal('topicModal')"
|
||||
class="px-4 py-2 text-gray-700 bg-gray-100 rounded-lg hover:bg-gray-200">
|
||||
Abbrechen
|
||||
</button>
|
||||
<button type="submit"
|
||||
class="px-6 py-3 text-white bg-[var(--primary-color)] rounded-xl hover:bg-[var(--secondary-color)] shadow-lg hover:shadow-xl transition">
|
||||
Speichern
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
152
TeacherDashboard/index.html
Normal file
152
TeacherDashboard/index.html
Normal file
@@ -0,0 +1,152 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Lehrer Dashboard</title>
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
|
||||
<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
|
||||
<link rel="stylesheet" href="styles/main.css">
|
||||
<link href="https://cdn.quilljs.com/1.3.6/quill.snow.css" rel="stylesheet">
|
||||
<script src="https://cdn.quilljs.com/1.3.6/quill.min.js"></script>
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism.min.css" rel="stylesheet" />
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-python.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-java.min.js"></script>
|
||||
<script>
|
||||
tailwind.config = {
|
||||
theme: {
|
||||
extend: {
|
||||
typography: {
|
||||
DEFAULT: {
|
||||
css: {
|
||||
maxWidth: 'none',
|
||||
color: '#333',
|
||||
h1: {
|
||||
color: '#111',
|
||||
fontWeight: '700',
|
||||
fontSize: '2em'
|
||||
},
|
||||
h2: {
|
||||
color: '#222',
|
||||
fontWeight: '600',
|
||||
fontSize: '1.5em'
|
||||
},
|
||||
'code::before': { content: 'none' },
|
||||
'code::after': { content: 'none' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: [require('@tailwindcss/typography')]
|
||||
}
|
||||
</script>
|
||||
<!-- Add Tailwind Typography Plugin -->
|
||||
<script src="https://cdn.tailwindcss.com?plugins=typography"></script>
|
||||
</head>
|
||||
<body class="min-h-screen bg-gray-50">
|
||||
<!-- Left Sidebar -->
|
||||
<nav class="sidebar fixed w-[280px] h-full bg-[var(--primary-color)] p-8">
|
||||
<!-- Logo and Brand -->
|
||||
<div class="mb-8">
|
||||
<h2 class="text-2xl font-bold text-white">TeacherDash</h2>
|
||||
</div>
|
||||
|
||||
<!-- User Profile -->
|
||||
<div class="flex items-center gap-4 mb-8 p-3 rounded-xl bg-white/10">
|
||||
<div class="w-12 h-12 rounded-full bg-white/20 flex items-center justify-center">
|
||||
<i class="fas fa-user text-white"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-white font-medium">Max Mustermann</h3>
|
||||
<p class="text-white/70 text-sm">Mathematiklehrer</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Navigation Links -->
|
||||
<nav class="space-y-2">
|
||||
<a href="#" class="flex items-center gap-3 text-white/90 hover:bg-white/10 p-3 rounded-xl transition-all">
|
||||
<i class="fas fa-home w-6"></i>
|
||||
<span>Dashboard</span>
|
||||
</a>
|
||||
<a href="#" class="flex items-center gap-3 text-white/90 hover:bg-white/10 p-3 rounded-xl transition-all">
|
||||
<i class="fas fa-book w-6"></i>
|
||||
<span>Fächer</span>
|
||||
</a>
|
||||
<a href="#" class="flex items-center gap-3 text-white/90 hover:bg-white/10 p-3 rounded-xl transition-all">
|
||||
<i class="fas fa-tasks w-6"></i>
|
||||
<span>Aufgaben</span>
|
||||
</a>
|
||||
<a href="#" class="flex items-center gap-3 text-white/90 hover:bg-white/10 p-3 rounded-xl transition-all">
|
||||
<i class="fas fa-calendar w-6"></i>
|
||||
<span>Kalender</span>
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
<!-- Bottom Section -->
|
||||
<div class="absolute bottom-8 left-8 right-8">
|
||||
<a href="#" class="flex items-center gap-3 text-white/90 hover:bg-white/10 p-3 rounded-xl transition-all">
|
||||
<i class="fas fa-cog w-6"></i>
|
||||
<span>Einstellungen</span>
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="ml-[280px] p-8">
|
||||
<h1 class="text-2xl font-bold mb-6">Dashboard</h1>
|
||||
|
||||
<!-- Action Cards -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mb-8">
|
||||
<!-- Subject Card -->
|
||||
<div class="bg-white p-6 rounded-xl shadow-lg border border-gray-100">
|
||||
<h3 class="text-lg font-semibold mb-4">Fach erstellen</h3>
|
||||
<p class="text-gray-600 mb-4">Erstellen Sie ein neues Fach für Ihren Unterricht.</p>
|
||||
<button onclick="openSubjectModal()"
|
||||
class="px-4 py-2 bg-[var(--primary-color)] text-white rounded-lg hover:bg-[var(--secondary-color)]">
|
||||
Neues Fach
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Topic Card -->
|
||||
<div class="bg-white p-6 rounded-xl shadow-lg border border-gray-100">
|
||||
<h3 class="text-lg font-semibold mb-4">Thema hinzufügen</h3>
|
||||
<p class="text-gray-600 mb-4">Fügen Sie ein neues Thema zu einem Fach hinzu.</p>
|
||||
<button onclick="openModal('topicModal')"
|
||||
class="px-4 py-2 bg-[var(--primary-color)] text-white rounded-lg hover:bg-[var(--secondary-color)]">
|
||||
Neues Thema
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Resource Card -->
|
||||
<div class="bg-white p-6 rounded-xl shadow-lg border border-gray-100">
|
||||
<h3 class="text-lg font-semibold mb-4">Materialien hochladen</h3>
|
||||
<p class="text-gray-600 mb-4">Laden Sie Unterrichtsmaterialien hoch.</p>
|
||||
<button onclick="openModal('resourceModal')"
|
||||
class="px-4 py-2 bg-[var(--primary-color)] text-white rounded-lg hover:bg-[var(--secondary-color)]">
|
||||
Materialien hochladen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recent Activity Section -->
|
||||
<div class="bg-white p-6 rounded-xl shadow-lg border border-gray-100">
|
||||
<h2 class="text-xl font-semibold mb-4">Letzte Aktivitäten</h2>
|
||||
<div id="recentActivity" class="space-y-4">
|
||||
<!-- Activity items will be loaded dynamically -->
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Include modal components -->
|
||||
<div id="modalContainer"></div>
|
||||
|
||||
<!-- Update script import to use type="module" -->
|
||||
<script type="module" src="js/main.js"></script>
|
||||
|
||||
<!-- Remove the inline script since it's now handled in main.js -->
|
||||
</body>
|
||||
</html>
|
||||
113
TeacherDashboard/js/main.js
Normal file
113
TeacherDashboard/js/main.js
Normal file
@@ -0,0 +1,113 @@
|
||||
import { SubjectManager } from './subjects/subjectManager.js';
|
||||
import { TopicManager } from './topics/topicManager.js';
|
||||
|
||||
// Make modal functions globally available
|
||||
window.openModal = (modalId) => {
|
||||
const modal = document.getElementById(modalId);
|
||||
modal.classList.remove('hidden');
|
||||
|
||||
if (modalId === 'topicModal') {
|
||||
document.dispatchEvent(new Event('openTopicModal'));
|
||||
}
|
||||
|
||||
gsap.fromTo(modal.children[0],
|
||||
{ y: -50, opacity: 0 },
|
||||
{ y: 0, opacity: 1, duration: 0.3 }
|
||||
);
|
||||
};
|
||||
|
||||
window.closeModal = (modalId) => {
|
||||
const modal = document.getElementById(modalId);
|
||||
gsap.to(modal.children[0], {
|
||||
y: -50,
|
||||
opacity: 0,
|
||||
duration: 0.3,
|
||||
onComplete: () => {
|
||||
modal.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Make this function globally available
|
||||
window.openSubjectModal = () => {
|
||||
openModal('subjectModal');
|
||||
};
|
||||
|
||||
async function loadModals() {
|
||||
const modalContainer = document.getElementById('modalContainer');
|
||||
const modals = ['subjectModal', 'topicModal', 'resourceModal'];
|
||||
|
||||
for (const modal of modals) {
|
||||
try {
|
||||
const response = await fetch(`components/${modal}.html`);
|
||||
const html = await response.text();
|
||||
modalContainer.innerHTML += html;
|
||||
} catch (error) {
|
||||
console.error(`Error loading ${modal}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize color picker functionality for subject modal
|
||||
initializeColorPicker();
|
||||
}
|
||||
|
||||
function initializeColorPicker() {
|
||||
const colorOptions = document.querySelectorAll('.color-option');
|
||||
colorOptions.forEach(option => {
|
||||
option.addEventListener('click', function() {
|
||||
document.querySelectorAll('.color-option').forEach(opt =>
|
||||
opt.classList.remove('ring-2', 'ring-blue-500'));
|
||||
this.classList.add('ring-2', 'ring-blue-500');
|
||||
document.getElementById('selectedColor').value = this.dataset.color;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize everything after DOM content is loaded
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
await loadModals();
|
||||
const subjectManager = new SubjectManager();
|
||||
// Make topicManager globally available
|
||||
window.topicManager = new TopicManager();
|
||||
|
||||
// Initialize event listeners for the subject form
|
||||
const form = document.getElementById('subjectForm');
|
||||
if (form) {
|
||||
form.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const submitButton = form.querySelector('button[type="submit"]');
|
||||
submitButton.disabled = true;
|
||||
submitButton.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Speichern...';
|
||||
|
||||
try {
|
||||
await subjectManager.handleSubjectSubmit(e);
|
||||
showNotification('Fach erfolgreich erstellt!', 'success');
|
||||
} catch (error) {
|
||||
showNotification(error.message || 'Fehler beim Erstellen des Fachs', 'error');
|
||||
} finally {
|
||||
submitButton.disabled = false;
|
||||
submitButton.innerHTML = 'Speichern';
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function showNotification(message, type = 'success') {
|
||||
const notification = document.createElement('div');
|
||||
notification.className = `fixed bottom-4 right-4 p-4 rounded-lg text-white ${
|
||||
type === 'success' ? 'bg-green-500' : 'bg-red-500'
|
||||
}`;
|
||||
notification.textContent = message;
|
||||
document.body.appendChild(notification);
|
||||
|
||||
gsap.to(notification, {
|
||||
opacity: 0,
|
||||
y: 20,
|
||||
delay: 3,
|
||||
duration: 0.5,
|
||||
onComplete: () => notification.remove()
|
||||
});
|
||||
}
|
||||
|
||||
// Form submissions and other JavaScript functions
|
||||
// ... rest of your JavaScript code ...
|
||||
47
TeacherDashboard/js/subjects/SubjectModel.js
Normal file
47
TeacherDashboard/js/subjects/SubjectModel.js
Normal file
@@ -0,0 +1,47 @@
|
||||
export class SubjectModel {
|
||||
constructor(data) {
|
||||
this.id = data.id || crypto.randomUUID();
|
||||
this.name = data.name;
|
||||
this.description = data.description || '';
|
||||
this.color = data.color;
|
||||
this.icon = data.icon;
|
||||
this.createdAt = data.createdAt || new Date().toISOString();
|
||||
this.updatedAt = new Date().toISOString();
|
||||
this.topics = data.topics || [];
|
||||
this.resources = data.resources || [];
|
||||
this.metadata = {
|
||||
lastAccessed: new Date().toISOString(),
|
||||
topicCount: 0,
|
||||
resourceCount: 0
|
||||
};
|
||||
this.content = data.content || '';
|
||||
this.materials = data.materials || [];
|
||||
}
|
||||
|
||||
validate() {
|
||||
const required = ['name', 'color', 'icon'];
|
||||
const missing = required.filter(field => !this[field]);
|
||||
|
||||
if (missing.length > 0) {
|
||||
throw new Error(`Missing required fields: ${missing.join(', ')}`);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
id: this.id,
|
||||
name: this.name,
|
||||
description: this.description,
|
||||
color: this.color,
|
||||
icon: this.icon,
|
||||
createdAt: this.createdAt,
|
||||
updatedAt: this.updatedAt,
|
||||
topics: this.topics,
|
||||
resources: this.resources,
|
||||
metadata: this.metadata,
|
||||
content: this.content,
|
||||
materials: this.materials
|
||||
};
|
||||
}
|
||||
}
|
||||
39
TeacherDashboard/js/subjects/SubjectStorage.js
Normal file
39
TeacherDashboard/js/subjects/SubjectStorage.js
Normal file
@@ -0,0 +1,39 @@
|
||||
export class SubjectStorage {
|
||||
constructor() {
|
||||
this.storageKey = 'teacherDash_subjects';
|
||||
}
|
||||
|
||||
async getAllSubjects() {
|
||||
try {
|
||||
const data = localStorage.getItem(this.storageKey);
|
||||
return data ? JSON.parse(data) : [];
|
||||
} catch (error) {
|
||||
console.error('Error loading subjects:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async saveSubject(subject) {
|
||||
try {
|
||||
const subjects = await this.getAllSubjects();
|
||||
const existingIndex = subjects.findIndex(s => s.id === subject.id);
|
||||
|
||||
if (existingIndex >= 0) {
|
||||
subjects[existingIndex] = subject;
|
||||
} else {
|
||||
subjects.push(subject);
|
||||
}
|
||||
|
||||
localStorage.setItem(this.storageKey, JSON.stringify(subjects));
|
||||
return subject;
|
||||
} catch (error) {
|
||||
throw new Error('Failed to save subject: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
async deleteSubject(subjectId) {
|
||||
const subjects = await this.getAllSubjects();
|
||||
const filtered = subjects.filter(s => s.id !== subjectId);
|
||||
localStorage.setItem(this.storageKey, JSON.stringify(filtered));
|
||||
}
|
||||
}
|
||||
21
TeacherDashboard/js/subjects/colors.js
Normal file
21
TeacherDashboard/js/subjects/colors.js
Normal file
@@ -0,0 +1,21 @@
|
||||
export const subjectColors = {
|
||||
red: { hex: '#FF6B6B', name: 'Rot' },
|
||||
teal: { hex: '#4ECDC4', name: 'Türkis' },
|
||||
blue: { hex: '#45B7D1', name: 'Blau' },
|
||||
green: { hex: '#96CEB4', name: 'Grün' },
|
||||
brown: { hex: '#D4A373', name: 'Braun' },
|
||||
purple: { hex: '#9B5DE5', name: 'Lila' },
|
||||
pink: { hex: '#F15BB5', name: 'Pink' },
|
||||
yellow: { hex: '#FEE440', name: 'Gelb' },
|
||||
lightBlue: { hex: '#00BBF9', name: 'Hellblau' },
|
||||
mint: { hex: '#00F5D4', name: 'Mint' }
|
||||
};
|
||||
|
||||
export function generateColorPalette() {
|
||||
return Object.entries(subjectColors)
|
||||
.map(([key, color]) => ({
|
||||
id: key,
|
||||
hex: color.hex,
|
||||
name: color.name
|
||||
}));
|
||||
}
|
||||
139
TeacherDashboard/js/subjects/subjectManager.js
Normal file
139
TeacherDashboard/js/subjects/subjectManager.js
Normal file
@@ -0,0 +1,139 @@
|
||||
import { generateColorPalette } from './colors.js';
|
||||
import { SubjectModel } from './SubjectModel.js';
|
||||
import { SubjectStorage } from './SubjectStorage.js';
|
||||
|
||||
export class SubjectManager {
|
||||
constructor() {
|
||||
this.storage = new SubjectStorage();
|
||||
this.subjects = [];
|
||||
this.initializeEventListeners();
|
||||
}
|
||||
|
||||
initializeEventListeners() {
|
||||
const form = document.getElementById('subjectForm');
|
||||
if (form) {
|
||||
form.addEventListener('submit', (e) => this.handleSubjectSubmit(e));
|
||||
}
|
||||
|
||||
this.initializeColorPicker();
|
||||
this.initializeIconPicker();
|
||||
}
|
||||
|
||||
initializeColorPicker() {
|
||||
const palette = generateColorPalette();
|
||||
const container = document.getElementById('colorPalette');
|
||||
|
||||
if (container) {
|
||||
container.innerHTML = palette.map(color => `
|
||||
<div class="color-option"
|
||||
style="background-color: ${color.hex}"
|
||||
data-color="${color.hex}"
|
||||
title="${color.name}">
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
this.addColorPickerListeners();
|
||||
}
|
||||
}
|
||||
|
||||
initializeIconPicker() {
|
||||
const iconOptions = document.querySelectorAll('.icon-option');
|
||||
iconOptions.forEach(option => {
|
||||
option.addEventListener('click', () => {
|
||||
iconOptions.forEach(opt => opt.classList.remove('selected'));
|
||||
option.classList.add('selected');
|
||||
document.getElementById('selectedIcon').value = option.dataset.icon;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
addColorPickerListeners() {
|
||||
const colorOptions = document.querySelectorAll('.color-option');
|
||||
colorOptions.forEach(option => {
|
||||
option.addEventListener('click', () => {
|
||||
colorOptions.forEach(opt => opt.classList.remove('selected'));
|
||||
option.classList.add('selected');
|
||||
document.getElementById('selectedColor').value = option.dataset.color;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async handleSubjectSubmit(e) {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(e.target);
|
||||
|
||||
try {
|
||||
const subjectData = {
|
||||
name: formData.get('displayName'),
|
||||
description: formData.get('description'),
|
||||
color: formData.get('color'),
|
||||
icon: formData.get('icon'),
|
||||
materials: await this.processFiles(formData.getAll('materials[]'))
|
||||
};
|
||||
|
||||
const subject = new SubjectModel(subjectData);
|
||||
subject.validate();
|
||||
|
||||
await this.storage.saveSubject(subject.toJSON());
|
||||
this.updateSubjectsList(subject.toJSON());
|
||||
closeModal('subjectModal');
|
||||
e.target.reset();
|
||||
|
||||
return subject;
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to create subject: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async processFiles(files) {
|
||||
return Array.from(files).map(file => ({
|
||||
name: file.name,
|
||||
size: file.size,
|
||||
type: file.type,
|
||||
lastModified: file.lastModified
|
||||
}));
|
||||
}
|
||||
|
||||
async loadSubjects() {
|
||||
const subjects = await this.storage.getAllSubjects();
|
||||
subjects.forEach(subject => this.updateSubjectsList(subject));
|
||||
}
|
||||
|
||||
validateSubjectForm(formData) {
|
||||
const requiredFields = ['displayName', 'color', 'icon'];
|
||||
return requiredFields.every(field => formData.get(field)?.trim());
|
||||
}
|
||||
|
||||
updateSubjectsList(subject) {
|
||||
const container = document.getElementById('subjectsList');
|
||||
if (!container) {
|
||||
const mainContent = document.querySelector('main');
|
||||
const subjectsSection = document.createElement('div');
|
||||
subjectsSection.className = 'mt-8';
|
||||
subjectsSection.innerHTML = `
|
||||
<h2 class="text-xl font-semibold mb-4">Meine Fächer</h2>
|
||||
<div id="subjectsList" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
</div>
|
||||
`;
|
||||
mainContent.appendChild(subjectsSection);
|
||||
}
|
||||
|
||||
const subjectElement = this.createSubjectElement(subject);
|
||||
document.getElementById('subjectsList').appendChild(subjectElement);
|
||||
}
|
||||
|
||||
createSubjectElement(subject) {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'bg-white p-4 rounded-xl shadow-sm border border-gray-100 flex items-center gap-4';
|
||||
div.innerHTML = `
|
||||
<div class="w-10 h-10 rounded-lg flex items-center justify-center" style="background-color: ${subject.color}">
|
||||
<i class="fas ${subject.icon} text-white"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-medium">${subject.name}</h3>
|
||||
<p class="text-sm text-gray-500">${subject.description || ''}</p>
|
||||
</div>
|
||||
`;
|
||||
return div;
|
||||
}
|
||||
}
|
||||
0
TeacherDashboard/js/topics/TopicModel.js
Normal file
0
TeacherDashboard/js/topics/TopicModel.js
Normal file
178
TeacherDashboard/js/topics/topicManager.js
Normal file
178
TeacherDashboard/js/topics/topicManager.js
Normal file
@@ -0,0 +1,178 @@
|
||||
import { SubjectStorage } from '../subjects/SubjectStorage.js';
|
||||
|
||||
export class TopicManager {
|
||||
constructor() {
|
||||
this.subjectStorage = new SubjectStorage();
|
||||
this.quill = null;
|
||||
this.initializeEventListeners();
|
||||
}
|
||||
|
||||
initializeEditor() {
|
||||
const toolbarOptions = [
|
||||
['undo', 'redo'],
|
||||
[{ 'header': [1, 2, false] }],
|
||||
['bold', 'italic', 'underline', 'strike'],
|
||||
['blockquote', 'code-block'],
|
||||
[{ 'list': 'ordered'}, { 'list': 'bullet' }],
|
||||
[{ 'script': 'sub'}, { 'script': 'super' }],
|
||||
[{ 'indent': '-1'}, { 'indent': '+1' }],
|
||||
['link', 'image', 'formula'],
|
||||
['clean']
|
||||
];
|
||||
|
||||
const editorOptions = {
|
||||
modules: {
|
||||
toolbar: toolbarOptions,
|
||||
keyboard: {
|
||||
bindings: {
|
||||
custom1: {
|
||||
key: 'Z',
|
||||
shortKey: true,
|
||||
handler: () => this.undo()
|
||||
},
|
||||
custom2: {
|
||||
key: 'Z',
|
||||
shortKey: true,
|
||||
shiftKey: true,
|
||||
handler: () => this.redo()
|
||||
},
|
||||
custom3: {
|
||||
key: 'Delete',
|
||||
handler: () => this.deleteSelected()
|
||||
}
|
||||
}
|
||||
},
|
||||
history: {
|
||||
delay: 1000,
|
||||
maxStack: 500,
|
||||
userOnly: true
|
||||
}
|
||||
},
|
||||
theme: 'snow',
|
||||
formats: [
|
||||
'header',
|
||||
'bold', 'italic', 'underline', 'strike',
|
||||
'list', 'bullet',
|
||||
'indent',
|
||||
'link', 'image',
|
||||
'code-block',
|
||||
'blockquote',
|
||||
'clean'
|
||||
]
|
||||
};
|
||||
|
||||
this.quill = new Quill('#quillEditor', editorOptions);
|
||||
this.quill.on('text-change', () => {
|
||||
this.updatePreview();
|
||||
});
|
||||
}
|
||||
|
||||
undo() {
|
||||
if (this.quill) {
|
||||
this.quill.history.undo();
|
||||
}
|
||||
}
|
||||
|
||||
redo() {
|
||||
if (this.quill) {
|
||||
this.quill.history.redo();
|
||||
}
|
||||
}
|
||||
|
||||
deleteSelected() {
|
||||
if (this.quill) {
|
||||
const range = this.quill.getSelection();
|
||||
if (range) {
|
||||
if (range.length > 0) {
|
||||
// Delete selected text/content
|
||||
this.quill.deleteText(range.index, range.length);
|
||||
} else {
|
||||
// Delete current line if no selection
|
||||
const [line] = this.quill.getLine(range.index);
|
||||
const lineLength = line.length();
|
||||
this.quill.deleteText(range.index - lineLength, lineLength);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async loadSubjectsIntoSelect() {
|
||||
const select = document.getElementById('topicSubjectSelect');
|
||||
if (!select) return;
|
||||
|
||||
// Clear existing options except the first placeholder
|
||||
while (select.options.length > 1) {
|
||||
select.remove(1);
|
||||
}
|
||||
|
||||
try {
|
||||
const subjects = await this.subjectStorage.getAllSubjects();
|
||||
subjects.forEach(subject => {
|
||||
const option = document.createElement('option');
|
||||
option.value = subject.id;
|
||||
option.textContent = subject.name;
|
||||
select.appendChild(option);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error loading subjects:', error);
|
||||
}
|
||||
}
|
||||
|
||||
initializeEventListeners() {
|
||||
const form = document.getElementById('topicForm');
|
||||
if (form) {
|
||||
form.addEventListener('submit', (e) => this.handleTopicSubmit(e));
|
||||
}
|
||||
document.addEventListener('openTopicModal', () => {
|
||||
this.loadSubjectsIntoSelect();
|
||||
if (!this.quill) {
|
||||
this.initializeEditor();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
updatePreview() {
|
||||
const content = this.quill.root.innerHTML;
|
||||
const preview = document.getElementById('contentPreview');
|
||||
const quillContent = document.getElementById('quillContent');
|
||||
|
||||
if (preview) {
|
||||
// Preserve classes for styling while updating content
|
||||
preview.innerHTML = content;
|
||||
// Update hidden input with content
|
||||
if (quillContent) {
|
||||
quillContent.value = content;
|
||||
}
|
||||
// Re-render math if present
|
||||
if (window.MathJax) {
|
||||
MathJax.typeset([preview]);
|
||||
}
|
||||
// Highlight code blocks if any
|
||||
if (window.Prism) {
|
||||
Prism.highlightAllUnder(preview);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleTopicSubmit(e) {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(e.target);
|
||||
const content = this.quill.root.innerHTML;
|
||||
const subjectId = formData.get('subject');
|
||||
|
||||
if (!subjectId) {
|
||||
throw new Error('Bitte wählen Sie ein Fach aus');
|
||||
}
|
||||
|
||||
// Add your topic saving logic here
|
||||
|
||||
closeModal('topicModal');
|
||||
}
|
||||
|
||||
clearEditor() {
|
||||
if (this.quill) {
|
||||
this.quill.setContents([]);
|
||||
this.updatePreview();
|
||||
}
|
||||
}
|
||||
}
|
||||
184
TeacherDashboard/styles/main.css
Normal file
184
TeacherDashboard/styles/main.css
Normal file
@@ -0,0 +1,184 @@
|
||||
:root {
|
||||
--primary-color: #4f46e5;
|
||||
--secondary-color: #3730a3;
|
||||
--background-color: #f8fafc;
|
||||
--text-color: #1e293b;
|
||||
--accent-color: #818cf8;
|
||||
}
|
||||
|
||||
.color-option {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
border: 2px solid #e2e8f0;
|
||||
}
|
||||
|
||||
.color-option:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.color-option.selected {
|
||||
border: 2px solid var(--primary-color);
|
||||
box-shadow: 0 0 0 2px rgba(79, 70, 229, 0.3);
|
||||
}
|
||||
|
||||
.modal {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.modal:not(.hidden) {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.modal > div {
|
||||
transform: translateY(0);
|
||||
transition: transform 0.3s ease, opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.modal.hidden > div {
|
||||
transform: translateY(-20px);
|
||||
}
|
||||
|
||||
/* Action Cards */
|
||||
.bg-white {
|
||||
background-color: white;
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.bg-white:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 25px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
button {
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
opacity: 0.7;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Sidebar Styles */
|
||||
.sidebar {
|
||||
background: linear-gradient(to bottom, var(--primary-color), var(--secondary-color));
|
||||
}
|
||||
|
||||
.sidebar a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.sidebar a:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
/* Form Styles */
|
||||
input[type="text"],
|
||||
textarea,
|
||||
select {
|
||||
width: 100%;
|
||||
padding: 0.75rem;
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid #e2e8f0;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
input[type="text"]:focus,
|
||||
textarea:focus,
|
||||
select:focus {
|
||||
outline: none;
|
||||
border-color: var(--primary-color);
|
||||
box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.1);
|
||||
}
|
||||
|
||||
/* Icon Selector Styles */
|
||||
.icon-option {
|
||||
transition: all 0.2s ease;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.icon-option:hover {
|
||||
transform: scale(1.05);
|
||||
border-color: var(--primary-color);
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.icon-option.selected {
|
||||
background-color: var(--primary-color);
|
||||
color: white;
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
/* Enhanced Quill content styling */
|
||||
.quill-content {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.quill-content h1 {
|
||||
font-size: 2em;
|
||||
font-weight: 700;
|
||||
margin: 1em 0 0.5em;
|
||||
color: #111;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.quill-content h2 {
|
||||
font-size: 1.5em;
|
||||
font-weight: 600;
|
||||
margin: 1em 0 0.5em;
|
||||
color: #222;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.quill-content p {
|
||||
margin: 0 0 1em;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.quill-content ul, .quill-content ol {
|
||||
margin: 0 0 1em 1.5em;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.quill-content ul {
|
||||
list-style-type: disc;
|
||||
}
|
||||
|
||||
.quill-content ol {
|
||||
list-style-type: decimal;
|
||||
}
|
||||
|
||||
.quill-content pre {
|
||||
background: #f8f9fa;
|
||||
padding: 1em;
|
||||
border-radius: 6px;
|
||||
margin: 0 0 1em;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.quill-content blockquote {
|
||||
border-left: 4px solid #e5e7eb;
|
||||
padding: 0.5em 0 0.5em 1em;
|
||||
margin: 0 0 1em;
|
||||
color: #4b5563;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.quill-content code {
|
||||
background: #f3f4f6;
|
||||
padding: 0.2em 0.4em;
|
||||
border-radius: 4px;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
||||
font-size: 0.9em;
|
||||
color: #e11d48;
|
||||
}
|
||||
|
||||
/* Rest of your CSS styles */
|
||||
Reference in New Issue
Block a user