752 lines
28 KiB
JavaScript
752 lines
28 KiB
JavaScript
/**
|
|
* MYP Platform Advanced UI Components
|
|
* Erweiterte Komponenten: Progress-Bars, File-Upload, Datepicker
|
|
* Version: 2.0.0
|
|
*/
|
|
|
|
(function() {
|
|
'use strict';
|
|
|
|
// Namespace erweitern
|
|
window.MYP = window.MYP || {};
|
|
window.MYP.Advanced = window.MYP.Advanced || {};
|
|
|
|
/**
|
|
* Progress Bar Component
|
|
*/
|
|
class ProgressBar {
|
|
constructor(container, options = {}) {
|
|
this.container = typeof container === 'string' ? document.querySelector(container) : container;
|
|
this.options = {
|
|
value: 0,
|
|
max: 100,
|
|
showLabel: true,
|
|
showPercentage: true,
|
|
animated: true,
|
|
color: 'blue',
|
|
size: 'md',
|
|
striped: false,
|
|
...options
|
|
};
|
|
|
|
this.currentValue = this.options.value;
|
|
this.init();
|
|
}
|
|
|
|
init() {
|
|
if (!this.container) {
|
|
console.error('ProgressBar: Container nicht gefunden');
|
|
return;
|
|
}
|
|
|
|
this.render();
|
|
}
|
|
|
|
render() {
|
|
const percentage = Math.round((this.currentValue / this.options.max) * 100);
|
|
const sizeClass = this.getSizeClass();
|
|
const colorClass = this.getColorClass();
|
|
|
|
this.container.innerHTML = `
|
|
<div class="progress-bar-container ${sizeClass}">
|
|
${this.options.showLabel ? `
|
|
<div class="flex justify-between items-center mb-2">
|
|
<span class="text-sm font-medium text-slate-700 dark:text-slate-300">
|
|
${this.options.label || 'Fortschritt'}
|
|
</span>
|
|
${this.options.showPercentage ? `
|
|
<span class="text-sm font-medium text-slate-700 dark:text-slate-300">
|
|
${percentage}%
|
|
</span>
|
|
` : ''}
|
|
</div>
|
|
` : ''}
|
|
|
|
<div class="progress-bar-track ${sizeClass}">
|
|
<div class="progress-bar-fill ${colorClass} ${this.options.animated ? 'animated' : ''} ${this.options.striped ? 'striped' : ''}"
|
|
style="width: ${percentage}%"
|
|
role="progressbar"
|
|
aria-valuenow="${this.currentValue}"
|
|
aria-valuemin="0"
|
|
aria-valuemax="${this.options.max}">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
getSizeClass() {
|
|
const sizes = {
|
|
'sm': 'h-2',
|
|
'md': 'h-3',
|
|
'lg': 'h-4',
|
|
'xl': 'h-6'
|
|
};
|
|
return sizes[this.options.size] || sizes.md;
|
|
}
|
|
|
|
getColorClass() {
|
|
const colors = {
|
|
'blue': 'bg-blue-500',
|
|
'green': 'bg-green-500',
|
|
'red': 'bg-red-500',
|
|
'yellow': 'bg-yellow-500',
|
|
'purple': 'bg-purple-500',
|
|
'indigo': 'bg-indigo-500'
|
|
};
|
|
return colors[this.options.color] || colors.blue;
|
|
}
|
|
|
|
setValue(value, animate = true) {
|
|
const oldValue = this.currentValue;
|
|
this.currentValue = Math.max(0, Math.min(this.options.max, value));
|
|
|
|
if (animate) {
|
|
this.animateToValue(oldValue, this.currentValue);
|
|
} else {
|
|
this.render();
|
|
}
|
|
}
|
|
|
|
animateToValue(from, to) {
|
|
const duration = 500; // ms
|
|
const steps = 60;
|
|
const stepValue = (to - from) / steps;
|
|
let currentStep = 0;
|
|
|
|
const animate = () => {
|
|
if (currentStep < steps) {
|
|
this.currentValue = from + (stepValue * currentStep);
|
|
this.render();
|
|
currentStep++;
|
|
requestAnimationFrame(animate);
|
|
} else {
|
|
this.currentValue = to;
|
|
this.render();
|
|
}
|
|
};
|
|
|
|
animate();
|
|
}
|
|
|
|
increment(amount = 1) {
|
|
this.setValue(this.currentValue + amount);
|
|
}
|
|
|
|
decrement(amount = 1) {
|
|
this.setValue(this.currentValue - amount);
|
|
}
|
|
|
|
reset() {
|
|
this.setValue(0);
|
|
}
|
|
|
|
complete() {
|
|
this.setValue(this.options.max);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Advanced File Upload Component
|
|
*/
|
|
class FileUpload {
|
|
constructor(container, options = {}) {
|
|
this.container = typeof container === 'string' ? document.querySelector(container) : container;
|
|
this.options = {
|
|
multiple: false,
|
|
accept: '*/*',
|
|
maxSize: 50 * 1024 * 1024, // 50MB
|
|
maxFiles: 10,
|
|
dragDrop: true,
|
|
showProgress: true,
|
|
showPreview: true,
|
|
uploadUrl: '/api/upload',
|
|
chunkSize: 1024 * 1024, // 1MB chunks
|
|
...options
|
|
};
|
|
|
|
this.files = [];
|
|
this.uploads = new Map();
|
|
this.init();
|
|
}
|
|
|
|
init() {
|
|
if (!this.container) {
|
|
console.error('FileUpload: Container nicht gefunden');
|
|
return;
|
|
}
|
|
|
|
this.render();
|
|
this.setupEventListeners();
|
|
}
|
|
|
|
render() {
|
|
this.container.innerHTML = `
|
|
<div class="file-upload-area" id="fileUploadArea">
|
|
<div class="file-upload-dropzone ${this.options.dragDrop ? 'drag-enabled' : ''}" id="dropzone">
|
|
<div class="text-center py-12">
|
|
<svg class="mx-auto h-12 w-12 text-slate-400" stroke="currentColor" fill="none" viewBox="0 0 48 48">
|
|
<path d="M28 8H12a4 4 0 00-4 4v20m32-12v8m0 0v8a4 4 0 01-4 4H12a4 4 0 01-4-4v-4m32-4l-3.172-3.172a4 4 0 00-5.656 0L28 28M8 32l9.172-9.172a4 4 0 015.656 0L28 28m0 0l4 4m4-24h8m-4-4v8m-12 4h.02" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
|
</svg>
|
|
<div class="mt-4">
|
|
<label for="fileInput" class="cursor-pointer">
|
|
<span class="mt-2 block text-sm font-medium text-slate-900 dark:text-white">
|
|
${this.options.dragDrop ? 'Dateien hierher ziehen oder' : ''}
|
|
<span class="text-blue-600 dark:text-blue-400 hover:text-blue-500">durchsuchen</span>
|
|
</span>
|
|
</label>
|
|
<input type="file"
|
|
id="fileInput"
|
|
name="files"
|
|
${this.options.multiple ? 'multiple' : ''}
|
|
accept="${this.options.accept}"
|
|
class="sr-only">
|
|
</div>
|
|
<p class="mt-1 text-xs text-slate-500 dark:text-slate-400">
|
|
${this.getFileTypeText()} • Max. ${this.formatFileSize(this.options.maxSize)}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="file-list mt-4" id="fileList"></div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
setupEventListeners() {
|
|
const fileInput = this.container.querySelector('#fileInput');
|
|
const dropzone = this.container.querySelector('#dropzone');
|
|
|
|
// File Input Change
|
|
fileInput.addEventListener('change', (e) => {
|
|
this.handleFiles(Array.from(e.target.files));
|
|
});
|
|
|
|
if (this.options.dragDrop) {
|
|
// Drag and Drop Events
|
|
dropzone.addEventListener('dragover', (e) => {
|
|
e.preventDefault();
|
|
dropzone.classList.add('drag-over');
|
|
});
|
|
|
|
dropzone.addEventListener('dragleave', (e) => {
|
|
e.preventDefault();
|
|
dropzone.classList.remove('drag-over');
|
|
});
|
|
|
|
dropzone.addEventListener('drop', (e) => {
|
|
e.preventDefault();
|
|
dropzone.classList.remove('drag-over');
|
|
this.handleFiles(Array.from(e.dataTransfer.files));
|
|
});
|
|
}
|
|
}
|
|
|
|
handleFiles(fileList) {
|
|
for (const file of fileList) {
|
|
if (this.validateFile(file)) {
|
|
this.addFile(file);
|
|
}
|
|
}
|
|
|
|
this.renderFileList();
|
|
}
|
|
|
|
validateFile(file) {
|
|
// Dateigröße prüfen
|
|
if (file.size > this.options.maxSize) {
|
|
this.showError(`Datei "${file.name}" ist zu groß. Maximum: ${this.formatFileSize(this.options.maxSize)}`);
|
|
return false;
|
|
}
|
|
|
|
// Anzahl Dateien prüfen
|
|
if (!this.options.multiple && this.files.length > 0) {
|
|
this.files = []; // Ersetze einzelne Datei
|
|
} else if (this.files.length >= this.options.maxFiles) {
|
|
this.showError(`Maximal ${this.options.maxFiles} Dateien erlaubt`);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
addFile(file) {
|
|
const fileData = {
|
|
id: this.generateId(),
|
|
file: file,
|
|
name: file.name,
|
|
size: file.size,
|
|
type: file.type,
|
|
status: 'pending',
|
|
progress: 0,
|
|
error: null
|
|
};
|
|
|
|
this.files.push(fileData);
|
|
|
|
// Preview generieren
|
|
if (this.options.showPreview && file.type.startsWith('image/')) {
|
|
this.generatePreview(fileData);
|
|
}
|
|
}
|
|
|
|
generatePreview(fileData) {
|
|
const reader = new FileReader();
|
|
reader.onload = (e) => {
|
|
fileData.preview = e.target.result;
|
|
this.renderFileList();
|
|
};
|
|
reader.readAsDataURL(fileData.file);
|
|
}
|
|
|
|
renderFileList() {
|
|
const fileListContainer = this.container.querySelector('#fileList');
|
|
|
|
if (this.files.length === 0) {
|
|
fileListContainer.innerHTML = '';
|
|
return;
|
|
}
|
|
|
|
fileListContainer.innerHTML = this.files.map(fileData => `
|
|
<div class="file-item" data-file-id="${fileData.id}">
|
|
<div class="flex items-center space-x-4 p-4 bg-slate-50 dark:bg-slate-800 rounded-lg">
|
|
${fileData.preview ? `
|
|
<img src="${fileData.preview}" class="w-12 h-12 object-cover rounded" alt="Preview">
|
|
` : `
|
|
<div class="w-12 h-12 bg-slate-200 dark:bg-slate-700 rounded flex items-center justify-center">
|
|
<svg class="w-6 h-6 text-slate-400" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fill-rule="evenodd" d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4z" clip-rule="evenodd"/>
|
|
</svg>
|
|
</div>
|
|
`}
|
|
|
|
<div class="flex-1 min-w-0">
|
|
<div class="flex items-center justify-between">
|
|
<p class="text-sm font-medium text-slate-900 dark:text-white truncate">
|
|
${fileData.name}
|
|
</p>
|
|
<button class="remove-file text-slate-400 hover:text-red-500" data-file-id="${fileData.id}">
|
|
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
<p class="text-xs text-slate-500 dark:text-slate-400">
|
|
${this.formatFileSize(fileData.size)} • ${this.getStatusText(fileData.status)}
|
|
</p>
|
|
|
|
${this.options.showProgress && fileData.status === 'uploading' ? `
|
|
<div class="mt-2">
|
|
<div class="w-full bg-slate-200 dark:bg-slate-600 rounded-full h-2">
|
|
<div class="bg-blue-500 h-2 rounded-full transition-all duration-300"
|
|
style="width: ${fileData.progress}%"></div>
|
|
</div>
|
|
</div>
|
|
` : ''}
|
|
|
|
${fileData.error ? `
|
|
<p class="text-xs text-red-500 mt-1">${fileData.error}</p>
|
|
` : ''}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`).join('');
|
|
|
|
// Event Listeners für Remove-Buttons
|
|
fileListContainer.querySelectorAll('.remove-file').forEach(button => {
|
|
button.addEventListener('click', (e) => {
|
|
const fileId = e.target.closest('.remove-file').dataset.fileId;
|
|
this.removeFile(fileId);
|
|
});
|
|
});
|
|
}
|
|
|
|
removeFile(fileId) {
|
|
this.files = this.files.filter(f => f.id !== fileId);
|
|
this.renderFileList();
|
|
}
|
|
|
|
async uploadFiles() {
|
|
const pendingFiles = this.files.filter(f => f.status === 'pending');
|
|
|
|
for (const fileData of pendingFiles) {
|
|
await this.uploadFile(fileData);
|
|
}
|
|
}
|
|
|
|
async uploadFile(fileData) {
|
|
fileData.status = 'uploading';
|
|
fileData.progress = 0;
|
|
this.renderFileList();
|
|
|
|
try {
|
|
const formData = new FormData();
|
|
formData.append('file', fileData.file);
|
|
|
|
const response = await fetch(this.options.uploadUrl, {
|
|
method: 'POST',
|
|
body: formData,
|
|
headers: {
|
|
'X-CSRFToken': document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || ''
|
|
}
|
|
});
|
|
|
|
if (response.ok) {
|
|
fileData.status = 'completed';
|
|
fileData.progress = 100;
|
|
const result = await response.json();
|
|
fileData.url = result.url;
|
|
} else {
|
|
throw new Error(`Upload fehlgeschlagen: ${response.status}`);
|
|
}
|
|
} catch (error) {
|
|
fileData.status = 'error';
|
|
fileData.error = error.message;
|
|
}
|
|
|
|
this.renderFileList();
|
|
}
|
|
|
|
getFileTypeText() {
|
|
if (this.options.accept === '*/*') return 'Alle Dateitypen';
|
|
if (this.options.accept.includes('image/')) return 'Bilder';
|
|
if (this.options.accept.includes('.pdf')) return 'PDF-Dateien';
|
|
return 'Spezifische Dateitypen';
|
|
}
|
|
|
|
getStatusText(status) {
|
|
const statusTexts = {
|
|
'pending': 'Wartend',
|
|
'uploading': 'Wird hochgeladen...',
|
|
'completed': 'Abgeschlossen',
|
|
'error': 'Fehler'
|
|
};
|
|
return statusTexts[status] || status;
|
|
}
|
|
|
|
formatFileSize(bytes) {
|
|
if (bytes === 0) return '0 Bytes';
|
|
const k = 1024;
|
|
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
}
|
|
|
|
generateId() {
|
|
return 'file_' + Math.random().toString(36).substr(2, 9);
|
|
}
|
|
|
|
showError(message) {
|
|
if (window.showToast) {
|
|
window.showToast(message, 'error');
|
|
} else {
|
|
alert(message);
|
|
}
|
|
}
|
|
|
|
getFiles() {
|
|
return this.files;
|
|
}
|
|
|
|
getCompletedFiles() {
|
|
return this.files.filter(f => f.status === 'completed');
|
|
}
|
|
|
|
clear() {
|
|
this.files = [];
|
|
this.renderFileList();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Simple Datepicker Component
|
|
*/
|
|
class DatePicker {
|
|
constructor(input, options = {}) {
|
|
this.input = typeof input === 'string' ? document.querySelector(input) : input;
|
|
this.options = {
|
|
format: 'dd.mm.yyyy',
|
|
minDate: null,
|
|
maxDate: null,
|
|
disabledDates: [],
|
|
language: 'de',
|
|
closeOnSelect: true,
|
|
showWeekNumbers: false,
|
|
...options
|
|
};
|
|
|
|
this.isOpen = false;
|
|
this.currentDate = new Date();
|
|
this.selectedDate = null;
|
|
this.init();
|
|
}
|
|
|
|
init() {
|
|
if (!this.input) {
|
|
console.error('DatePicker: Input-Element nicht gefunden');
|
|
return;
|
|
}
|
|
|
|
this.setupInput();
|
|
this.createCalendar();
|
|
this.setupEventListeners();
|
|
}
|
|
|
|
setupInput() {
|
|
this.input.setAttribute('readonly', 'true');
|
|
this.input.classList.add('datepicker-input');
|
|
|
|
// Container für Input und Calendar
|
|
this.container = document.createElement('div');
|
|
this.container.className = 'datepicker-container relative';
|
|
this.input.parentNode.insertBefore(this.container, this.input);
|
|
this.container.appendChild(this.input);
|
|
}
|
|
|
|
createCalendar() {
|
|
this.calendar = document.createElement('div');
|
|
this.calendar.className = 'datepicker-calendar absolute top-full left-0 mt-1 bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-600 rounded-lg shadow-lg z-50 hidden';
|
|
this.calendar.innerHTML = this.renderCalendar();
|
|
this.container.appendChild(this.calendar);
|
|
}
|
|
|
|
renderCalendar() {
|
|
const year = this.currentDate.getFullYear();
|
|
const month = this.currentDate.getMonth();
|
|
const monthName = this.getMonthName(month);
|
|
|
|
return `
|
|
<div class="datepicker-header p-4 border-b border-slate-200 dark:border-slate-600">
|
|
<div class="flex items-center justify-between">
|
|
<button type="button" class="prev-month p-1 hover:bg-slate-100 dark:hover:bg-slate-700 rounded">
|
|
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fill-rule="evenodd" d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z" clip-rule="evenodd"/>
|
|
</svg>
|
|
</button>
|
|
<div class="text-sm font-medium text-slate-900 dark:text-white">
|
|
${monthName} ${year}
|
|
</div>
|
|
<button type="button" class="next-month p-1 hover:bg-slate-100 dark:hover:bg-slate-700 rounded">
|
|
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="datepicker-body p-4">
|
|
<div class="grid grid-cols-7 gap-1 mb-2">
|
|
${this.getWeekdayHeaders()}
|
|
</div>
|
|
<div class="grid grid-cols-7 gap-1">
|
|
${this.getDaysOfMonth(year, month)}
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
getWeekdayHeaders() {
|
|
const weekdays = ['Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So'];
|
|
return weekdays.map(day =>
|
|
`<div class="text-xs font-medium text-slate-500 dark:text-slate-400 text-center p-1">${day}</div>`
|
|
).join('');
|
|
}
|
|
|
|
getDaysOfMonth(year, month) {
|
|
const firstDay = new Date(year, month, 1);
|
|
const lastDay = new Date(year, month + 1, 0);
|
|
const startDate = new Date(firstDay);
|
|
startDate.setDate(startDate.getDate() - ((firstDay.getDay() + 6) % 7));
|
|
|
|
const days = [];
|
|
const current = new Date(startDate);
|
|
|
|
while (current <= lastDay || current.getMonth() === month) {
|
|
const isCurrentMonth = current.getMonth() === month;
|
|
const isToday = this.isToday(current);
|
|
const isSelected = this.isSelectedDate(current);
|
|
const isDisabled = this.isDisabledDate(current);
|
|
|
|
const classes = [
|
|
'w-8 h-8 text-sm rounded cursor-pointer flex items-center justify-center transition-colors',
|
|
isCurrentMonth ? 'text-slate-900 dark:text-white' : 'text-slate-400 dark:text-slate-600',
|
|
isToday ? 'bg-blue-100 dark:bg-blue-900 text-blue-900 dark:text-blue-100' : '',
|
|
isSelected ? 'bg-blue-500 text-white' : '',
|
|
!isDisabled && isCurrentMonth ? 'hover:bg-slate-100 dark:hover:bg-slate-700' : '',
|
|
isDisabled ? 'cursor-not-allowed opacity-50' : ''
|
|
].filter(Boolean);
|
|
|
|
days.push(`
|
|
<div class="${classes.join(' ')}"
|
|
data-date="${this.formatDateForData(current)}"
|
|
${isDisabled ? '' : 'data-selectable="true"'}>
|
|
${current.getDate()}
|
|
</div>
|
|
`);
|
|
|
|
current.setDate(current.getDate() + 1);
|
|
|
|
if (days.length >= 42) break; // Max 6 Wochen
|
|
}
|
|
|
|
return days.join('');
|
|
}
|
|
|
|
setupEventListeners() {
|
|
// Input click
|
|
this.input.addEventListener('click', () => {
|
|
this.toggle();
|
|
});
|
|
|
|
// Calendar clicks
|
|
this.calendar.addEventListener('click', (e) => {
|
|
if (e.target.classList.contains('prev-month')) {
|
|
this.previousMonth();
|
|
} else if (e.target.classList.contains('next-month')) {
|
|
this.nextMonth();
|
|
} else if (e.target.dataset.selectable) {
|
|
this.selectDate(new Date(e.target.dataset.date));
|
|
}
|
|
});
|
|
|
|
// Click outside
|
|
document.addEventListener('click', (e) => {
|
|
if (!this.container.contains(e.target)) {
|
|
this.close();
|
|
}
|
|
});
|
|
}
|
|
|
|
toggle() {
|
|
if (this.isOpen) {
|
|
this.close();
|
|
} else {
|
|
this.open();
|
|
}
|
|
}
|
|
|
|
open() {
|
|
this.calendar.classList.remove('hidden');
|
|
this.isOpen = true;
|
|
this.updateCalendar();
|
|
}
|
|
|
|
close() {
|
|
this.calendar.classList.add('hidden');
|
|
this.isOpen = false;
|
|
}
|
|
|
|
selectDate(date) {
|
|
this.selectedDate = new Date(date);
|
|
this.input.value = this.formatDate(date);
|
|
|
|
// Custom Event
|
|
this.input.dispatchEvent(new CustomEvent('dateselected', {
|
|
detail: { date: new Date(date) }
|
|
}));
|
|
|
|
if (this.options.closeOnSelect) {
|
|
this.close();
|
|
}
|
|
}
|
|
|
|
previousMonth() {
|
|
this.currentDate.setMonth(this.currentDate.getMonth() - 1);
|
|
this.updateCalendar();
|
|
}
|
|
|
|
nextMonth() {
|
|
this.currentDate.setMonth(this.currentDate.getMonth() + 1);
|
|
this.updateCalendar();
|
|
}
|
|
|
|
updateCalendar() {
|
|
this.calendar.innerHTML = this.renderCalendar();
|
|
}
|
|
|
|
isToday(date) {
|
|
const today = new Date();
|
|
return date.toDateString() === today.toDateString();
|
|
}
|
|
|
|
isSelectedDate(date) {
|
|
return this.selectedDate && date.toDateString() === this.selectedDate.toDateString();
|
|
}
|
|
|
|
isDisabledDate(date) {
|
|
if (this.options.minDate && date < this.options.minDate) return true;
|
|
if (this.options.maxDate && date > this.options.maxDate) return true;
|
|
return this.options.disabledDates.some(disabled =>
|
|
date.toDateString() === disabled.toDateString()
|
|
);
|
|
}
|
|
|
|
formatDate(date) {
|
|
const day = date.getDate().toString().padStart(2, '0');
|
|
const month = (date.getMonth() + 1).toString().padStart(2, '0');
|
|
const year = date.getFullYear();
|
|
|
|
return this.options.format
|
|
.replace('dd', day)
|
|
.replace('mm', month)
|
|
.replace('yyyy', year);
|
|
}
|
|
|
|
formatDateForData(date) {
|
|
return date.toISOString().split('T')[0];
|
|
}
|
|
|
|
getMonthName(monthIndex) {
|
|
const months = [
|
|
'Januar', 'Februar', 'März', 'April', 'Mai', 'Juni',
|
|
'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'
|
|
];
|
|
return months[monthIndex];
|
|
}
|
|
|
|
setValue(date) {
|
|
if (date) {
|
|
this.selectDate(new Date(date));
|
|
}
|
|
}
|
|
|
|
getValue() {
|
|
return this.selectedDate;
|
|
}
|
|
|
|
clear() {
|
|
this.selectedDate = null;
|
|
this.input.value = '';
|
|
}
|
|
}
|
|
|
|
// Globale API
|
|
window.MYP.Advanced = {
|
|
ProgressBar,
|
|
FileUpload,
|
|
DatePicker,
|
|
|
|
// Convenience Functions
|
|
createProgressBar: (container, options) => new ProgressBar(container, options),
|
|
createFileUpload: (container, options) => new FileUpload(container, options),
|
|
createDatePicker: (input, options) => new DatePicker(input, options)
|
|
};
|
|
|
|
// Auto-Initialize
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Auto-initialize datepickers
|
|
document.querySelectorAll('[data-datepicker]').forEach(input => {
|
|
const options = JSON.parse(input.dataset.datepicker || '{}');
|
|
new DatePicker(input, options);
|
|
});
|
|
|
|
// Auto-initialize file uploads
|
|
document.querySelectorAll('[data-file-upload]').forEach(container => {
|
|
const options = JSON.parse(container.dataset.fileUpload || '{}');
|
|
new FileUpload(container, options);
|
|
});
|
|
|
|
console.log('🚀 MYP Advanced Components geladen');
|
|
});
|
|
|
|
})();
|