feat: add database export and import functionality with user confirmation
This commit is contained in:
@@ -509,6 +509,29 @@
|
|||||||
class="w-full px-4 py-2 border border-gray-600 bg-gray-700 text-gray-100 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent">
|
class="w-full px-4 py-2 border border-gray-600 bg-gray-700 text-gray-100 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Database Export/Import -->
|
||||||
|
<div class="mt-6 pt-4 border-t border-gray-600">
|
||||||
|
<h3 class="text-sm font-semibold text-gray-300 mb-3 flex items-center gap-2">
|
||||||
|
<i data-lucide="database" class="w-4 h-4 text-purple-400"></i>
|
||||||
|
Datenbank Verwaltung
|
||||||
|
</h3>
|
||||||
|
<div class="flex flex-wrap gap-3">
|
||||||
|
<button id="btnExportDB" class="flex items-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors">
|
||||||
|
<i data-lucide="download" class="w-4 h-4"></i>
|
||||||
|
Datenbank exportieren
|
||||||
|
</button>
|
||||||
|
<button id="btnImportDB" class="flex items-center gap-2 px-4 py-2 bg-green-600 hover:bg-green-700 text-white rounded-lg transition-colors">
|
||||||
|
<i data-lucide="upload" class="w-4 h-4"></i>
|
||||||
|
Datenbank importieren
|
||||||
|
</button>
|
||||||
|
<input type="file" id="importDBFile" accept=".json" class="hidden">
|
||||||
|
</div>
|
||||||
|
<p class="text-xs text-gray-500 mt-2">
|
||||||
|
<i data-lucide="info" class="w-3 h-3 inline"></i>
|
||||||
|
Export erstellt eine JSON-Datei mit allen Einträgen und Einstellungen. Import überschreibt alle vorhandenen Daten.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</details>
|
</details>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -3215,6 +3215,115 @@ async function handleExportPDF() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Export entire database
|
||||||
|
*/
|
||||||
|
async function exportDatabase() {
|
||||||
|
try {
|
||||||
|
// Fetch all data
|
||||||
|
const entries = await fetchEntries();
|
||||||
|
const settings = {
|
||||||
|
employeeName: await getSetting('employeeName') || '',
|
||||||
|
employeeId: await getSetting('employeeId') || '',
|
||||||
|
bundesland: await getSetting('bundesland') || 'NW',
|
||||||
|
vacationDaysPerYear: await getSetting('vacationDaysPerYear') || 30
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create export object
|
||||||
|
const exportData = {
|
||||||
|
version: '1.0',
|
||||||
|
exportDate: new Date().toISOString(),
|
||||||
|
entries: entries,
|
||||||
|
settings: settings
|
||||||
|
};
|
||||||
|
|
||||||
|
// Convert to JSON
|
||||||
|
const jsonString = JSON.stringify(exportData, null, 2);
|
||||||
|
const blob = new Blob([jsonString], { type: 'application/json' });
|
||||||
|
|
||||||
|
// Create download link
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = `zeiterfassung_backup_${new Date().toISOString().split('T')[0]}.json`;
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
document.body.removeChild(a);
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
|
||||||
|
showNotification(`Datenbank exportiert: ${entries.length} Einträge`, 'success');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error exporting database:', error);
|
||||||
|
showNotification('Fehler beim Exportieren der Datenbank', 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import entire database
|
||||||
|
*/
|
||||||
|
async function importDatabase(file) {
|
||||||
|
try {
|
||||||
|
const text = await file.text();
|
||||||
|
const importData = JSON.parse(text);
|
||||||
|
|
||||||
|
// Validate data structure
|
||||||
|
if (!importData.entries || !Array.isArray(importData.entries)) {
|
||||||
|
throw new Error('Ungültiges Datenbankformat');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Confirm before overwriting
|
||||||
|
const confirmed = confirm(
|
||||||
|
`Möchten Sie die Datenbank wirklich importieren?\n\n` +
|
||||||
|
`Dies wird alle ${importData.entries.length} Einträge importieren und vorhandene Daten überschreiben.\n\n` +
|
||||||
|
`Export-Datum: ${new Date(importData.exportDate).toLocaleString('de-DE')}`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!confirmed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete all existing entries
|
||||||
|
const existingEntries = await fetchEntries();
|
||||||
|
for (const entry of existingEntries) {
|
||||||
|
await fetch(`/api/entries/${entry.id}`, { method: 'DELETE' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Import entries
|
||||||
|
let imported = 0;
|
||||||
|
for (const entry of importData.entries) {
|
||||||
|
try {
|
||||||
|
// Remove ID to create new entries
|
||||||
|
const { id, ...entryData } = entry;
|
||||||
|
await fetch('/api/entries', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(entryData)
|
||||||
|
});
|
||||||
|
imported++;
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error importing entry:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Import settings
|
||||||
|
if (importData.settings) {
|
||||||
|
await setSetting('employeeName', importData.settings.employeeName || '');
|
||||||
|
await setSetting('employeeId', importData.settings.employeeId || '');
|
||||||
|
await setSetting('bundesland', importData.settings.bundesland || 'NW');
|
||||||
|
await setSetting('vacationDaysPerYear', importData.settings.vacationDaysPerYear || 30);
|
||||||
|
await loadSettings(); // Reload settings to UI
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reload view
|
||||||
|
await reloadView();
|
||||||
|
|
||||||
|
showNotification(`Datenbank importiert: ${imported} Einträge`, 'success');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error importing database:', error);
|
||||||
|
showNotification('Fehler beim Importieren der Datenbank: ' + error.message, 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show a temporary notification
|
* Show a temporary notification
|
||||||
*/
|
*/
|
||||||
@@ -3535,6 +3644,19 @@ function initializeEventListeners() {
|
|||||||
showNotification('Personalnummer gespeichert', 'success');
|
showNotification('Personalnummer gespeichert', 'success');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Database export/import
|
||||||
|
document.getElementById('btnExportDB').addEventListener('click', exportDatabase);
|
||||||
|
document.getElementById('btnImportDB').addEventListener('click', () => {
|
||||||
|
document.getElementById('importDBFile').click();
|
||||||
|
});
|
||||||
|
document.getElementById('importDBFile').addEventListener('change', (e) => {
|
||||||
|
const file = e.target.files[0];
|
||||||
|
if (file) {
|
||||||
|
importDatabase(file);
|
||||||
|
e.target.value = ''; // Reset file input
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Close modal when clicking outside
|
// Close modal when clicking outside
|
||||||
document.getElementById('entryModal').addEventListener('click', (e) => {
|
document.getElementById('entryModal').addEventListener('click', (e) => {
|
||||||
if (e.target.id === 'entryModal') {
|
if (e.target.id === 'entryModal') {
|
||||||
|
|||||||
Reference in New Issue
Block a user